导出逻辑

This commit is contained in:
todaywindy 2025-07-11 15:00:04 +08:00
parent a7c8c023a8
commit 354eb07186
4 changed files with 325 additions and 92 deletions

View File

@ -14,12 +14,16 @@ use app\admin\model\OrderLog;
use app\admin\model\orders\Dispatchlog;
use app\admin\model\Worker;
use app\admin\model\WorkerItem;
use app\common\cache\SimpleFileCache;
use app\common\cache\ThinkphpCacheAdapter;
use app\common\controller\Backend;
use app\common\Logic\OrderLogic;
use Carbon\Carbon;
use Carbon\Traits\Creator;
use fast\Tree;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Settings;
use think\Cache;
use think\Db;
use think\Exception;
use think\exception\DbException;
@ -32,6 +36,7 @@ use function Symfony\Component\Clock\now;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
/**
* 订单列管理
*
@ -130,7 +135,7 @@ class Order extends Backend
*/
public function index($getArray = false,$page = 0,$input_limit = 0)
public function index($getArray = false, $page = 0, $input_limit = 0)
{
$this->request->filter(['strip_tags', 'trim']);
$group = \model('auth_group_access')->where('uid', $this->auth->id)->find()->group_id ?? 0;
@ -178,8 +183,8 @@ class Order extends Backend
->where($where);
$filter = (array)json_decode(input()['filter'] ?? '', true);
if(isset($filter['audit_time'])){
$build->where('status',\app\admin\model\Order::STATUS_FINISHED);
if (isset($filter['audit_time'])) {
$build->where('status', \app\admin\model\Order::STATUS_FINISHED);
}
$admin_filter = $filter['user.nickname'] ?? false;
@ -200,13 +205,13 @@ class Order extends Backend
$build->whereIn('item_id', $item_ids);
}
if (!is_null($is_timeout)) {
$build->where('is_overtime',$is_timeout);
$build->where('is_overtime', $is_timeout);
}
if ($group == 2 ) { // 录单员
$build->where('admin_id',$user->id);
}elseif ($group == 6){ // 派单员
if ($group == 2) { // 录单员
$build->where('admin_id', $user->id);
} elseif ($group == 6) { // 派单员
// 生成 SQL 语句
$ids = $user->area_ids ?? '';
if ($ids == '') {
@ -238,12 +243,12 @@ class Order extends Backend
]]
)
->order($sort, $order);
if ($getArray){
if ($getArray) {
$list = $build->paginate([
'list_rows' => $input_limit,
'page' => $page
]);
}else{
} else {
$list = $build->paginate($limit);
}
@ -252,13 +257,13 @@ class Order extends Backend
if ($item->aftersale_id == 0 && $this->auth->check('aftersales/aftersale/add') && $item->status == \app\admin\model\Order::STATUS_FINISHED) { //$item->status == \app\admin\model\Order::STATUS_FINISHED &&
$item->aftersale_btn = true;
}
if (isset($item->getRelation('source')->parent->title)){
$item->source_total_name = '【' . $item->getRelation('source')->parent->title . '】' . ($item->getRelation('source')->title??'');
}else{
$item->source_total_name = ($item->getRelation('source')->title??'');
if (isset($item->getRelation('source')->parent->title)) {
$item->source_total_name = '【' . $item->getRelation('source')->parent->title . '】' . ($item->getRelation('source')->title ?? '');
} else {
$item->source_total_name = ($item->getRelation('source')->title ?? '');
}
if($item->status <60){
if ($item->status < 60) {
$item->audit_time = null;
}
unset($item->source);
@ -314,7 +319,7 @@ class Order extends Backend
$sources = array_column($sources, null, 'id');
if (!in_array($sources[$params['source']]['pid'],[3,8])&& empty($params['customer'])){
if (!in_array($sources[$params['source']]['pid'], [3, 8]) && empty($params['customer'])) {
$this->error('请输入客户名称');
}
$params['source_shop'] = $sources[$params['source']]['title'] ?? null;
@ -335,23 +340,23 @@ class Order extends Backend
$params['order_no'] = $this->generateOrderNumber();
$params['create_time'] = date('Y-m-d H:i:s');
$params['update_time'] = date('Y-m-d H:i:s');
$params['receive_type'] = $params['receive_type']?: 1;
$params['receive_type'] = $params['receive_type'] ?: 1;
$params['audit_time'] = date('Y-m-d H:i:s');
$result = $this->model->allowField(true)->save($params);
$auth = clone $this->auth;
$order = \app\admin\model\Order::where('id',$this->model->id)->with(['source' => [
$order = \app\admin\model\Order::where('id', $this->model->id)->with(['source' => [
'parent' => function ($q) {
$q->field('id,title');
}
]]
)->find();
// dd($order);
if (isset($order->getRelation('source')->parent->title)){
if (isset($order->getRelation('source')->parent->title)) {
$order->source_total_name = '【' . $order->getRelation('source')->parent->title . '】' .
($order->getRelation('source')->title??'');
}else{
$order->source_total_name = ($order->getRelation('source')->title??'');
($order->getRelation('source')->title ?? '');
} else {
$order->source_total_name = ($order->getRelation('source')->title ?? '');
}
unset($order->source);
//日志
@ -433,11 +438,11 @@ class Order extends Backend
// $params['create_time'] = date('Y-m-d H:i:s');
$params['update_time'] = date('Y-m-d H:i:s');
$params['receive_type'] = $params['receive_type']?: 1;
$params['receive_type'] = $params['receive_type'] ?: 1;
if($order->receive_type != $params['receive_type']){
if ($order->receive_type != $params['receive_type']) {
//修改派单表
OrderDispatch::where('order_id',$order->id)->whereBetween('status',[0,30])->update(['is_receipt' => $params['receive_type']== 1? 1:0]);
OrderDispatch::where('order_id', $order->id)->whereBetween('status', [0, 30])->update(['is_receipt' => $params['receive_type'] == 1 ? 1 : 0]);
}
// 更新订单信息
@ -578,11 +583,11 @@ class Order extends Backend
$dispatch_admin = AuthGroupAccess::where('group_id', 6)
->column('uid');
$res = array_values(array_intersect($res,$dispatch_admin));
$res = array_values(array_intersect($res, $dispatch_admin));
$message = $params['detail'] ?? '';
$abnormals = model('abnormal')->where('type', 3)
->where('id',$params['abnormal_id'])->find();
->where('id', $params['abnormal_id'])->find();
$insert = [];
foreach ($res as $re) {
@ -590,7 +595,7 @@ class Order extends Backend
'to_id' => $re,
'type' => 1,
'title' => '订单内容变更',
'content' => '【订单内容变更】您有一条订单'.$order->order_no.'信息被修改,异常类型【'.$abnormals->title.'】,备注:【' . $message . '】,请注意查收。'
'content' => '【订单内容变更】您有一条订单' . $order->order_no . '信息被修改,异常类型【' . $abnormals->title . '】,备注:【' . $message . '】,请注意查收。'
];
}
$build = new Message();
@ -637,7 +642,7 @@ class Order extends Backend
'update_time' => now()->format('Y-m-d H:m:s'),
];
$orderDispatch = OrderDispatch::where('order_id',$order->id)->whereBetween('status','>=',0)->find();
$orderDispatch = OrderDispatch::where('order_id', $order->id)->whereBetween('status', '>=', 0)->find();
if ($params['abnormal_id'] == 2 || $params['abnormal_id'] == 3) {
@ -739,7 +744,7 @@ class Order extends Backend
$result = $order->allowField(true)->save($params);
$dispatch = OrderDispatch::where('order_id', $order->id)->where('status','>=',0)->find();
$dispatch = OrderDispatch::where('order_id', $order->id)->where('status', '>=', 0)->find();
if (!empty($dispatch)) {
$orderLogic = new OrderLogic();
$orderLogic->cancelOrderDispatch($dispatch, $this->auth, '订单被取消', false);
@ -1097,76 +1102,44 @@ class Order extends Backend
public function export()
{
// 默认分页配置
$limit = 1000; // 每页导出 500 条
$page = 1;
$allRows = [];
do {
// 调用 index 方法(返回 JSON 格式数据)
$result = $this->index(true,$page,$limit); // index(true) 表示返回原始数据数组而不是 view
$result = $result->getData();
if (!isset($result['rows'])) {
break;
}
$rows = $result['rows'];
$count = count($rows);
if ($count == 0 || count($allRows) > 3000) {
break;
}
$allRows = array_merge($allRows, $rows);
$page++;
} while ($count == $limit); // 如果最后一页不足 limit说明结束了
if (empty($allRows)) {
// 表头结构(可从参数获取或手动定义)
$columns = json_decode($this->request->get('columns', ''), true);
// dd($columns);
if (!$columns) {
// 用第一页数据自动生成字段
$sample = $this->index(true, 1, 1)->getData();
if (!isset($sample['rows'][0])) {
return json(['code' => 0, 'msg' => '无数据可导出']);
}
// 开启磁盘缓存,防止内存爆掉
Settings::setLocale('fr');
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
// ===== 表头处理 =====
$columns = json_decode($this->request->get('columns', ''), true);
if (!$columns && isset($allRows[0])) {
$columns = [];
foreach ($allRows[0] as $key => $value) {
foreach ($sample['rows'][0] as $key => $value) {
$columns[] = ['field' => $key, 'title' => $key];
}
}
$titles = array_column($columns, 'title');
$fields = array_column($columns, 'field');
// 写入表头(从 A1 开始)
foreach ($titles as $col => $title) {
$sheet->setCellValueByColumnAndRow($col + 1, 1, $title);
// 数据获取函数:分页获取数据(支持返回 array
$fetchData = function ($page, $limit) {
$result = $this->index(true, $page, $limit);
$order = new \app\admin\model\Order();
foreach ($result->getData()['rows'] as $item){
$item->status = $order->getStatusList()[$item->status] ?? '';
$item->dispatch_type = $item->dispatch_type == 1 ? '手动派单' : '自动派单';
$item->is_overtime = $item->is_overtime ? '超时' : '未超时';
$item->receive_type = $item->receive_type == 1 ? '已收定金' : '已收全款';
$images = explode(',',$item->images ?? '');
$url = '';
if ($images){
foreach ($images as $img){
if($img == '')continue;
$url .= cdnurl($img) . ',';
}
// ===== 分批写入数据行 =====
$rowIndex = 2;
foreach ($allRows as $row) {
foreach ($fields as $colIndex => $field) {
$value = isset($row[$field]) ? $row[$field] : '';
$sheet->setCellValueByColumnAndRow($colIndex + 1, $rowIndex, $value);
}
$rowIndex++;
$item->images = $url;
}
return $result->getData()['rows'] ?? [];
};
// 保存文件
$filename = '导出_' . date('Ymd_His') . '.xlsx';
$filepath = __DIR__ .'/' . $filename;
(new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet))->save($filepath);
return json([
'code' => 1,
'msg' => '导出成功',
'url' => request()->domain() . '/' . $filename
]);
$this->exportExcelByStream($columns, $fetchData, '订单导出_' . date('Ymd_His') . '.xlsx');
}

View File

@ -0,0 +1,109 @@
<?php
namespace app\common\cache;
use Psr\SimpleCache\CacheInterface;
class SimpleFileCache implements CacheInterface
{
protected $path;
protected $prefix;
public function __construct($path = null, $prefix = 'spreadsheet_')
{
$this->path = $path ?? CACHE_PATH;
$this->prefix = $prefix;
if (!is_dir($this->path)) {
mkdir($this->path, 0777, true);
}
}
protected function getFilePath(string $key): string
{
return $this->path . DIRECTORY_SEPARATOR . $this->prefix . md5($key) . '.cache';
}
public function get($key, $default = null): mixed
{
$file = $this->getFilePath($key);
if (!file_exists($file)) {
return $default;
}
$content = file_get_contents($file);
$data = unserialize($content);
if ($data['expire'] !== 0 && $data['expire'] < time()) {
@unlink($file);
return $default;
}
return $data['value'];
}
public function set($key, $value, $ttl = null): bool
{
$file = $this->getFilePath($key);
$expire = $ttl instanceof \DateInterval ? (new \DateTime())->add($ttl)->getTimestamp() : ($ttl ? time() + $ttl : 0);
$data = [
'expire' => $expire,
'value' => $value,
];
return file_put_contents($file, serialize($data)) !== false;
}
public function delete($key): bool
{
$file = $this->getFilePath($key);
return file_exists($file) ? unlink($file) : true;
}
public function clear(): bool
{
$files = glob($this->path . '/' . $this->prefix . '*.cache');
foreach ($files as $file) {
unlink($file);
}
return true;
}
public function getMultiple($keys, $default = null): iterable
{
$results = [];
foreach ($keys as $key) {
$results[$key] = $this->get($key, $default);
}
return $results;
}
public function setMultiple($values, $ttl = null): bool
{
$success = true;
foreach ($values as $key => $value) {
if (!$this->set($key, $value, $ttl)) {
$success = false;
}
}
return $success;
}
public function deleteMultiple($keys): bool
{
$success = true;
foreach ($keys as $key) {
if (!$this->delete($key)) {
$success = false;
}
}
return $success;
}
public function has($key): bool
{
$file = $this->getFilePath($key);
if (!file_exists($file)) return false;
$data = unserialize(file_get_contents($file));
if ($data['expire'] !== 0 && $data['expire'] < time()) {
@unlink($file);
return false;
}
return true;
}
}

View File

@ -0,0 +1,74 @@
<?php
namespace app\common\cache;
use Psr\SimpleCache\CacheInterface;
class ThinkphpCacheAdapter implements CacheInterface
{
protected $cache;
public function __construct($cache)
{
$this->cache = $cache;
}
public function get($key, $default = null): mixed
{
$value = $this->cache->get($key);
return $value === false ? $default : $value;
}
public function set($key, $value, $ttl = null): bool
{
if ($ttl === null) {
return $this->cache->set($key, $value);
}
return $this->cache->set($key, $value, $ttl);
}
public function delete($key): bool
{
return $this->cache->delete($key);
}
public function clear(): bool
{
return $this->cache->clear();
}
public function getMultiple($keys, $default = null): iterable
{
$results = [];
foreach ($keys as $key) {
$results[$key] = $this->get($key, $default);
}
return $results;
}
public function setMultiple($values, $ttl = null): bool
{
$success = true;
foreach ($values as $key => $value) {
if (!$this->set($key, $value, $ttl)) {
$success = false;
}
}
return $success;
}
public function deleteMultiple($keys): bool
{
$success = true;
foreach ($keys as $key) {
if (!$this->delete($key)) {
$success = false;
}
}
return $success;
}
public function has($key): bool
{
return $this->cache->has($key);
}
}

View File

@ -3,6 +3,9 @@
namespace app\common\controller;
use app\admin\library\Auth;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use think\Config;
use think\Controller;
use think\Db;
@ -762,4 +765,78 @@ class Backend extends Controller
}
return $res;
}
protected function exportExcelByStream(array $columns, callable $fetchData, string $filename = '导出.xlsx', int $limit = 1000)
{
$page = 1;
$rowIndex = 2;
// 创建 Spreadsheet 和 Sheet
$spreadsheet = new Spreadsheet();
$spreadsheet->removeSheetByIndex(0);
$sheet = new Worksheet($spreadsheet, 'Sheet1');
$spreadsheet->addSheet($sheet);
$spreadsheet->setActiveSheetIndex(0);
$sheet = $spreadsheet->getActiveSheet();
$writer = new Xlsx($spreadsheet);
$writer->setPreCalculateFormulas(false);
// 获取字段映射
$titles = array_column($columns, 'title');
$fields = array_column($columns, 'field');
// 写入表头
$sheet->fromArray($titles, null, 'A1');
// 分页写入数据
do {
$rows = $fetchData($page, $limit);
$count = count($rows);
if ($count === 0) break;
foreach ($rows as $row) {
$dataRow = [];
foreach ($fields as $field) {
$value = $this->getNestedValue($row->toArray(), $field);
if (is_array($value) || is_object($value)) {
$value = json_encode($value, JSON_UNESCAPED_UNICODE);
}
$dataRow[] = $value;
}
$sheet->fromArray($dataRow, null, 'A' . $rowIndex);
$rowIndex++;
}
$page++;
} while ($count === $limit);
// 输出为下载流
$filename = $filename ?: ('导出_' . date('Ymd_His') . '.xlsx');
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment;filename="' . $filename . '"');
header('Cache-Control: max-age=0');
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Pragma: public');
$writer->save('php://output');
$spreadsheet->disconnectWorksheets();
unset($spreadsheet);
exit;
}
protected function getNestedValue(array $data, string $path, $default = '')
{
$segments = explode('.', $path);
foreach ($segments as $segment) {
if (is_array($data) && array_key_exists($segment, $data)) {
$data = $data[$segment];
} elseif (is_object($data) && isset($data->{$segment})) {
$data = $data->{$segment};
} else {
return $default;
}
}
return $data;
}
}