Accept Merge Request #219: (feature/hant -> develop)

Merge Request: 导出逻辑

Created By: @todayswind
Accepted By: @todayswind
URL: https://g-bcrc3009.coding.net/p/allocatr/d/allocatr/git/merge/219?initial=true
This commit is contained in:
todayswind 2025-07-11 15:00:48 +08:00 committed by Coding
commit c0c01284b0
7 changed files with 396 additions and 53 deletions

View File

@ -2,26 +2,13 @@
namespace app\admin\command; namespace app\admin\command;
use app\admin\addresmart\Address;
use app\admin\controller\AutoDispatchLogic;
use app\admin\controller\orders\DispatchLogic;
use app\admin\controller\SendMailLogic;
use app\admin\model\Order;
use app\admin\model\OrderDispatch;
use app\admin\model\OrderReview;
use app\admin\model\Worker;
use app\admin\model\WorkerItem;
use app\admin\controller\AmapTrait; use app\admin\controller\AmapTrait;
use think\Collection; use app\admin\controller\AutoDispatchLogic;
use app\admin\model\Order;
use think\console\Command; use think\console\Command;
use think\console\Input; use think\console\Input;
use think\console\Output; use think\console\Output;
use think\Db; use think\Exception;
use think\exception\DbException;
use think\Hook;
use think\Lang;
use think\Model;
use function Symfony\Component\Clock\now;
class Test extends Command class Test extends Command
{ {
@ -35,8 +22,18 @@ class Test extends Command
protected function execute(Input $input, Output $output) protected function execute(Input $input, Output $output)
{ {
$order = Order::where('id',221)->select()[0]; // $order = Order::where('id','>=',694)->select();
AutoDispatchLogic::autoDispatch($order); try {
$order = Order::get(5126);
AutoDispatchLogic::autoDispatch($order,null,true);
}catch (Exception $e){
$output->writeln('<error>' . $e->getMessage() . '</error>');
$output->writeln('<comment>' . $e->getFile() . ':' . $e->getLine() . '</comment>');
$output->writeln($e->getTraceAsString());
}
dd(1);
} }

View File

@ -14,11 +14,16 @@ use app\admin\model\OrderLog;
use app\admin\model\orders\Dispatchlog; use app\admin\model\orders\Dispatchlog;
use app\admin\model\Worker; use app\admin\model\Worker;
use app\admin\model\WorkerItem; use app\admin\model\WorkerItem;
use app\common\cache\SimpleFileCache;
use app\common\cache\ThinkphpCacheAdapter;
use app\common\controller\Backend; use app\common\controller\Backend;
use app\common\Logic\OrderLogic; use app\common\Logic\OrderLogic;
use Carbon\Carbon; use Carbon\Carbon;
use Carbon\Traits\Creator; use Carbon\Traits\Creator;
use fast\Tree; use fast\Tree;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Settings;
use think\Cache;
use think\Db; use think\Db;
use think\Exception; use think\Exception;
use think\exception\DbException; use think\exception\DbException;
@ -28,6 +33,9 @@ use think\Hook;
use think\Loader; use think\Loader;
use think\Model; use think\Model;
use function Symfony\Component\Clock\now; use function Symfony\Component\Clock\now;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
/** /**
* 订单列管理 * 订单列管理
@ -127,12 +135,11 @@ class Order extends Backend
*/ */
public function index() public function index($getArray = false, $page = 0, $input_limit = 0)
{ {
$this->request->filter(['strip_tags', 'trim']); $this->request->filter(['strip_tags', 'trim']);
$group = \model('auth_group_access')->where('uid', $this->auth->id)->find()->group_id ?? 0; $group = \model('auth_group_access')->where('uid', $this->auth->id)->find()->group_id ?? 0;
$user = \model('admin')->find($this->auth->id); $user = \model('admin')->find($this->auth->id);
$this->assignconfig('permissions', [ $this->assignconfig('permissions', [
'add' => $this->auth->check('order/add'), 'add' => $this->auth->check('order/add'),
'edit' => $this->auth->check('order/edit'), 'edit' => $this->auth->check('order/edit'),
@ -149,8 +156,7 @@ class Order extends Backend
]); ]);
if (false === $this->request->isAjax()) { if (false === $this->request->isAjax() && $getArray == false) {
return $this->view->fetch(); return $this->view->fetch();
} }
//如果发送的来源是 Selectpage则转发到 Selectpage //如果发送的来源是 Selectpage则转发到 Selectpage
@ -221,7 +227,7 @@ class Order extends Backend
}); });
} }
$list = $build $build
->with(['user' => function ($q) { ->with(['user' => function ($q) {
$q->field('id,nickname'); $q->field('id,nickname');
}, 'area' => function ($q) { }, 'area' => function ($q) {
@ -236,8 +242,15 @@ class Order extends Backend
} }
]] ]]
) )
->order($sort, $order) ->order($sort, $order);
->paginate($limit); if ($getArray) {
$list = $build->paginate([
'list_rows' => $input_limit,
'page' => $page
]);
} else {
$list = $build->paginate($limit);
}
foreach ($list as $item) { foreach ($list as $item) {
$item->aftersale_btn = false; $item->aftersale_btn = false;
@ -255,7 +268,6 @@ class Order extends Backend
} }
unset($item->source); unset($item->source);
} }
$result = ['total' => $list->total(), 'rows' => $list->items()]; $result = ['total' => $list->total(), 'rows' => $list->items()];
return json($result); return json($result);
} }
@ -1087,4 +1099,48 @@ class Order extends Backend
return json($result); return json($result);
} }
public function export()
{
// 表头结构(可从参数获取或手动定义)
$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' => '无数据可导出']);
}
$columns = [];
foreach ($sample['rows'][0] as $key => $value) {
$columns[] = ['field' => $key, 'title' => $key];
}
}
// 数据获取函数:分页获取数据(支持返回 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) . ',';
}
}
$item->images = $url;
}
return $result->getData()['rows'] ?? [];
};
$this->exportExcelByStream($columns, $fetchData, '订单导出_' . date('Ymd_His') . '.xlsx');
}
} }

View File

@ -24,6 +24,9 @@
<div id="toolbar" class="toolbar"> <div id="toolbar" class="toolbar">
<a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}" ><i class="fa fa-refresh"></i> </a> <a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}" ><i class="fa fa-refresh"></i> </a>
<a href="javascript:;" class="btn btn-success btn-edit {:$auth->check('order/edit')?'':'hide'}" title="{:__('Edit')}" ><i class="fa fa-edit"></i> {:__('Edit')}</a> <a href="javascript:;" class="btn btn-success btn-edit {:$auth->check('order/edit')?'':'hide'}" title="{:__('Edit')}" ><i class="fa fa-edit"></i> {:__('Edit')}</a>
<a href="javascript:;" class="btn btn-success btn-export" id="btn-export">
<i class="fa fa-download"></i> 导出
</a>
<!-- <div class="dropdown btn-group {:$auth->check('order/multi')?'':'hide'}">--> <!-- <div class="dropdown btn-group {:$auth->check('order/multi')?'':'hide'}">-->
<!-- <a class="btn btn-primary btn-more dropdown-toggle btn-disabled disabled" data-toggle="dropdown"><i class="fa fa-cog"></i> {:__('More')}</a>--> <!-- <a class="btn btn-primary btn-more dropdown-toggle btn-disabled disabled" data-toggle="dropdown"><i class="fa fa-cog"></i> {:__('More')}</a>-->
<!-- <ul class="dropdown-menu text-left" role="menu">--> <!-- <ul class="dropdown-menu text-left" role="menu">-->

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; namespace app\common\controller;
use app\admin\library\Auth; use app\admin\library\Auth;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use think\Config; use think\Config;
use think\Controller; use think\Controller;
use think\Db; use think\Db;
@ -762,4 +765,78 @@ class Backend extends Controller
} }
return $res; 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;
}
} }

View File

@ -95,6 +95,7 @@ ${data.receive_type == 1 ? '已收定金' : '已收全款'}
renderDefault: true, renderDefault: true,
searchFormVisible: true, searchFormVisible: true,
search: false, search: false,
showExport: false,
columns: [ columns: [
[ [
{checkbox: true}, {checkbox: true},
@ -531,6 +532,32 @@ ${data.receive_type == 1 ? '已收定金' : '已收全款'}
clickParent: true clickParent: true
}); });
$('#btn-export').on('click', function () {
var options = $("#table").bootstrapTable('getOptions');
// 提取列信息(不含 checkbox
var columns = [];
$.each(options.columns[0], function (i, item) {
if (item.field && !item.checkbox && !item.visible === false) {
columns.push({
field: item.field,
title: item.title
});
}
});
var params = {
columns: JSON.stringify(columns),
filter: options.queryParams({}).filter || {},
op: options.queryParams({}).op || {},
sort: options.sortName,
order: options.sortOrder
};
// console.log($.param(params))
var url = '/admin/order/export?' + $.param(params);
window.open(url); // 发起文件下载
});
}, },
add: function () { add: function () {
$("#mybuttom").on("click", function () { $("#mybuttom").on("click", function () {