Accept Merge Request #53: (feature/hant -> develop)
Merge Request: 运营补单 Created By: @todayswind Accepted By: @todayswind URL: https://g-bcrc3009.coding.net/p/allocatr/d/allocatr/git/merge/53?initial=true
This commit is contained in:
commit
8c9b769746
|
|
@ -3,12 +3,26 @@
|
|||
namespace app\admin\command;
|
||||
|
||||
use app\admin\addresmart\Address;
|
||||
use app\admin\controller\orders\DispatchLogic;
|
||||
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 think\Collection;
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
use think\Db;
|
||||
use think\exception\DbException;
|
||||
use think\Hook;
|
||||
use think\Model;
|
||||
use function Symfony\Component\Clock\now;
|
||||
|
||||
class Test extends Command
|
||||
{
|
||||
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('test')
|
||||
|
|
@ -17,10 +31,12 @@ class Test extends Command
|
|||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$string = '张三 13800138000 120113196808214821深圳市龙华区龙华街道1980科技文化产业园3栋317 地板';
|
||||
$r = Address::smart($string);
|
||||
dd($r);
|
||||
$order = (new Order())->find(110);
|
||||
$id = (new DispatchLogic())->getMaxScoreWorker($order);
|
||||
dd($id);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
namespace app\admin\controller;
|
||||
|
||||
use app\admin\addresmart\Address;
|
||||
use app\admin\model\AuthGroupAccess;
|
||||
use app\admin\controller\orders\DispatchLogic;
|
||||
use app\admin\model\order\Invoice;
|
||||
use app\admin\model\OrderDispatch;
|
||||
use app\admin\model\Worker;
|
||||
|
|
@ -27,7 +27,6 @@ use function Symfony\Component\Clock\now;
|
|||
*/
|
||||
class Order extends Backend
|
||||
{
|
||||
|
||||
/**
|
||||
* Order模型对象
|
||||
* @var \app\admin\model\Order
|
||||
|
|
@ -347,24 +346,13 @@ class Order extends Backend
|
|||
|
||||
private function autoDispatch($order)
|
||||
{
|
||||
if ($order->dispatch_type != 2) {
|
||||
return false;
|
||||
}
|
||||
//
|
||||
// if ($order->dispatch_type != 2) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
$worker_ids = (new Worker())->where('area_id', $order->area_id)
|
||||
->where('status', 1)
|
||||
->field(['id', 'area_id', 'lng'], 'lat')
|
||||
->column('id');
|
||||
|
||||
$worker_items_ids = (new WorkerItem())
|
||||
->where('item_id', $order->item_id)
|
||||
->whereIn('worker_id', $worker_ids)
|
||||
->field(['worker_id'], 'lat')
|
||||
->column('worker_id');
|
||||
|
||||
$out_workers = array_intersect($worker_ids, $worker_items_ids);
|
||||
|
||||
$worker_id = $out_workers[0] ?? false;
|
||||
$worker_id = (new DispatchLogic())->getMaxScoreWorker($order);
|
||||
|
||||
if (!$worker_id) {
|
||||
$order->dispatch_type = 1;
|
||||
|
|
@ -389,7 +377,7 @@ class Order extends Backend
|
|||
$orderDispatch->allowField(true)->save($insert);
|
||||
$order->status = \app\admin\model\Order::STATUS_DISPATCHED;
|
||||
$order->dispatch_time = date('Y-m-d H:i:s');
|
||||
// $order->dispatch_admin_id = $this->auth->id;
|
||||
// $order->dispatch_admin_id = $this->auth->id;
|
||||
$order->worker_id = $worker_id;
|
||||
$order->save();
|
||||
|
||||
|
|
|
|||
310
application/admin/controller/orders/DispatchLogic.php
Normal file
310
application/admin/controller/orders/DispatchLogic.php
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
<?php
|
||||
|
||||
namespace app\admin\controller\orders;
|
||||
|
||||
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 think\Collection;
|
||||
use think\Db;
|
||||
use think\exception\DbException;
|
||||
use function Symfony\Component\Clock\now;
|
||||
|
||||
/**
|
||||
* 自动派单
|
||||
*
|
||||
* @icon
|
||||
*/
|
||||
class DispatchLogic
|
||||
{
|
||||
|
||||
|
||||
function getMaxScoreWorker($order){
|
||||
$worker_info = Db::query("SELECT id, `name`, lng, lat, distance,star
|
||||
FROM (
|
||||
SELECT id, `name`, lng, lat,star,
|
||||
ST_Distance_Sphere(
|
||||
point(lng, lat),
|
||||
point(?, ?)
|
||||
) AS distance
|
||||
FROM fa_worker
|
||||
) AS t
|
||||
WHERE distance < 40000
|
||||
ORDER BY distance;",[$order->lng,$order->lat]);
|
||||
|
||||
$worker_ids = array_column($worker_info,'id');
|
||||
|
||||
|
||||
$worker_items_ids = (new WorkerItem())
|
||||
->where('item_id', $order->item_id)
|
||||
->whereIn('worker_id', $worker_ids)
|
||||
->field(['worker_id'])
|
||||
->column('worker_id');
|
||||
|
||||
$out_worker_ids = array_intersect($worker_ids, $worker_items_ids);
|
||||
|
||||
$worker_doings = (new OrderDispatch())
|
||||
->whereIn('worker_id', $out_worker_ids)
|
||||
->group('worker_id')
|
||||
->field(['worker_id','count(if(status > 0 & status < 60,1,null)) count'])
|
||||
->select();
|
||||
|
||||
$worker_doing_map = [];
|
||||
foreach ($worker_doings as $item){
|
||||
$worker_doing_map[$item->worker_id] = $item->toArray();
|
||||
}
|
||||
|
||||
// 获取工人信息;
|
||||
$worker_rate = $this->getWorkerRate($out_worker_ids);
|
||||
|
||||
$worker_scores = [];
|
||||
$worker_rate_map = array_column($worker_rate,null,'worker_id');
|
||||
|
||||
foreach ($worker_info as $item){
|
||||
if (in_array($item['id'],$out_worker_ids)){
|
||||
$worker_scores [] = [
|
||||
'id' => $item['id'],
|
||||
'distance' => $item['distance'],
|
||||
// 'star' => $item['star'],
|
||||
'doing' => $worker_doing_map[$item['id']]['count'] ?? 0,
|
||||
'status' => $worker_rate_map[$item['id']] ?? [
|
||||
'arrive_rate' => 0,
|
||||
'refuse_rate' => 0,
|
||||
'trans_rate' => 0,
|
||||
'good_rate' => 0,
|
||||
'avg_time_diff' => 0,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
$worker_score_map = [];
|
||||
|
||||
foreach ($worker_scores as $worker_score){
|
||||
$worker_score_map [$worker_score['id']] = $this->scoreWorker($worker_score);
|
||||
}
|
||||
|
||||
// 根据得分排序(从高到低)
|
||||
arsort($worker_score_map); // 保持键名不变
|
||||
|
||||
// 取出第一个 key(就是得分最高的 ID)
|
||||
return array_key_first($worker_score_map);
|
||||
}
|
||||
|
||||
|
||||
function scoreWorker($worker, $maxDistance = 10000, $requiredSkills = []) {
|
||||
// 距离得分(越近分数越高)
|
||||
$geoScore = max(0, 1 - ($worker['distance'] / $maxDistance)); // 范围 [0,1]
|
||||
|
||||
// 技能得分(假设这个人不匹配)
|
||||
$skillScore = count($requiredSkills) ? 0 : 1;
|
||||
|
||||
// 当前状态得分(未完成订单越少越好)
|
||||
$doing = $worker['doing'];
|
||||
$statusScore = $doing >= 10 ? 0 : (10 - $doing) / 10;
|
||||
|
||||
// 历史表现得分(各项 0~1)
|
||||
$s = $worker['status'];
|
||||
|
||||
$historyScore = (
|
||||
0.4 * (floatval($s['good_rate']) / 100) +
|
||||
0.25 * (floatval($s['trans_rate']) / 100) +
|
||||
0.2 * (floatval($s['arrive_rate']) / 100) +
|
||||
0.1 * (1 - min(floatval($s['avg_time_diff']) / 10, 1)) + // 联系时间越短越好
|
||||
0.05 * (1 - floatval($s['refuse_rate']) / 100) // 拒单率越低越好
|
||||
);
|
||||
|
||||
// 综合得分(加权)
|
||||
$totalScore =
|
||||
0.3 * $geoScore +
|
||||
0.2 * $skillScore +
|
||||
0.2 * $statusScore +
|
||||
0.3 * $historyScore;
|
||||
|
||||
return round($totalScore, 4);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private function getWorkerRate($worker_ids){
|
||||
|
||||
$filter = [
|
||||
'start_time' => now()->modify('-31 days')->format('Y-m-d H:i:s'),
|
||||
'end_timne'=> now()->format('Y-m-d H:i:s')
|
||||
];
|
||||
|
||||
//派单表
|
||||
$dispatchSubsql = $this->dispatchSubsql($filter);
|
||||
//订单表
|
||||
$orderSubsql = $this->oderSubsql($filter);
|
||||
//评分表
|
||||
$viewSubsql = $this->viewSubsql($filter);
|
||||
|
||||
$list = (new Worker())->alias('fa_worker')
|
||||
->whereIn('id',$worker_ids)
|
||||
->field([
|
||||
'fa_worker.*',
|
||||
'IFNULL(a.dispatch_count, 0) AS dispatch_count',
|
||||
'IFNULL(a.get_count, 0) AS get_count',
|
||||
'IFNULL(a.refuse_count, 0) AS refuse_count',
|
||||
'IFNULL(a.arrive_count, 0) AS arrive_count',
|
||||
'IFNULL(a.avg_time_diff, 0) AS avg_time_diff',
|
||||
'IFNULL(b.finish_num, 0) AS finish_num',
|
||||
'IFNULL(b.total, 0) AS total',
|
||||
'IFNULL(b.performance, 0) AS performance',
|
||||
'IFNULL(b.refund_total, 0) AS refund_total',
|
||||
'IFNULL(b.refund_count, 0) AS refund_count',
|
||||
'IFNULL(b.cost, 0) AS cost',
|
||||
'IFNULL(c.good_count, 0) AS good_count'
|
||||
])
|
||||
->join([$dispatchSubsql => 'a'], 'fa_worker.id = a.worker_id', 'LEFT')
|
||||
->join([$orderSubsql => 'b'], 'fa_worker.id = b.worker_id', 'LEFT')
|
||||
->join([$viewSubsql => 'c'], 'fa_worker.id = c.worker_id', 'LEFT')
|
||||
->select();
|
||||
|
||||
|
||||
$this->_toList($list);
|
||||
$out = [];
|
||||
foreach ($list as $item){
|
||||
$out [] = [
|
||||
'worker_id' => $item->id,
|
||||
'arrive_rate' => $item->arrive_rate,
|
||||
'refuse_rate' => $item->refuse_rate,
|
||||
'trans_rate' => $item->trans_rate,
|
||||
'good_rate' => $item->good_rate,
|
||||
'avg_time_diff' => $item->avg_time_diff,
|
||||
|
||||
|
||||
];
|
||||
}
|
||||
return $out;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return bool|Collection|PDOStatement|string
|
||||
* @throws DbException
|
||||
*/
|
||||
public function viewSubsql($filter=[]){
|
||||
//评分表
|
||||
$viewSubsql = OrderReview::where('worker_star',5)->field(['worker_id','count(*) as good_count']);
|
||||
|
||||
if(!empty($filter['start_time'])){
|
||||
$viewSubsql->where('create_time','>=',$filter['start_time']);
|
||||
}
|
||||
if(!empty($filter['end_time'])){
|
||||
$viewSubsql->where('create_time','<=',$filter['start_time']);
|
||||
}
|
||||
return $viewSubsql->group('worker_id')->buildSql();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool|PDOStatement|string|Collection
|
||||
* @throws DbException
|
||||
*/
|
||||
public function dispatchSubsql($filter): Collection|bool|string|PDOStatement
|
||||
{
|
||||
$builder = new OrderDispatch();
|
||||
$fields = [
|
||||
'worker_id',
|
||||
// 使用 IFNULL 确保结果为 null 时返回 0
|
||||
"IFNULL(COUNT(*), 0) AS dispatch_count", //分配数
|
||||
"IFNULL(COUNT(CASE WHEN status NOT IN (0, -10) THEN 1 END), 0) AS get_count", //接单数
|
||||
//"COUNT(CASE WHEN status IN (60) THEN 1 END) AS finish_count", //完成数
|
||||
"IFNULL(COUNT(CASE WHEN status NOT IN (-10) THEN 1 END), 0) AS refuse_count", //拒绝数
|
||||
"IFNULL(COUNT(arrive_time), 0) AS arrive_count", //上门数
|
||||
"IFNULL(AVG(CASE WHEN status = 60 AND arrive_time IS NOT NULL THEN UNIX_TIMESTAMP(arrive_time) - UNIX_TIMESTAMP(create_time) END), 0) AS avg_time_diff", //联系时效
|
||||
];
|
||||
$builder->field($fields);
|
||||
|
||||
if(!empty($filter['start_time'])){
|
||||
$builder->where('create_time','>=',$filter['start_time']);
|
||||
}
|
||||
if(!empty($filter['end_time'])){
|
||||
$builder->where('create_time','<=',$filter['start_time']);
|
||||
}
|
||||
|
||||
$builder->group('worker_id');
|
||||
return $builder->buildSql();
|
||||
}
|
||||
|
||||
|
||||
|
||||
//图表统计
|
||||
|
||||
/**
|
||||
* @throws DbException
|
||||
*/
|
||||
public function oderSubsql($filter = []): Collection|bool|string|PDOStatement
|
||||
{
|
||||
|
||||
$orderValid = implode(',',(new Order())->tabStatus(Order::TAB_VALID));
|
||||
|
||||
//"COUNT(CASE WHEN status IN (".$orderValid.") THEN 1 END) AS ing_num",
|
||||
$fields = [
|
||||
'worker_id',
|
||||
// 使用 IFNULL 确保结果为 null 时返回 0
|
||||
"IFNULL(COUNT(CASE WHEN status = 60 THEN 1 END), 0) AS finish_num", //完成数
|
||||
//"COUNT(CASE WHEN status IN (".$orderValid.") THEN 1 END) AS count_num", //总订单数 (排除取消 和草稿)
|
||||
"IFNULL(SUM(CASE WHEN status = 60 THEN total END), 0) AS total", //成效额
|
||||
"IFNULL(SUM(CASE WHEN status = 60 THEN performance END), 0) AS performance", //业绩
|
||||
"IFNULL(SUM(CASE WHEN status = 60 THEN cost END), 0) AS cost", //成效额
|
||||
|
||||
// "SUM(CASE WHEN status = 60 THEN (cost + material_cost) END) AS cost_total", //总成本
|
||||
|
||||
"IFNULL(SUM(CASE WHEN status = 60 THEN (refund_amount + worker_refund_amount) END), 0) AS refund_total", //退款总数
|
||||
"IFNULL(COUNT(CASE WHEN refund_amount > 0 OR worker_refund_amount > 0 THEN 1 END), 0) AS refund_count", //退款订单数量
|
||||
//"AVG(CASE WHEN status > 10 THEN UNIX_TIMESTAMP(dispatch_time) - UNIX_TIMESTAMP(create_time) END) AS avg_time_diff", //派单时效
|
||||
// "SUM(CASE WHEN status = 60 THEN (field1 + field2) END) AS performance",
|
||||
];
|
||||
|
||||
$builder = (new Order())->field($fields);
|
||||
|
||||
if(!empty($filter['start_time'])){
|
||||
$builder->where('create_time','>=',$filter['start_time']);
|
||||
}
|
||||
if(!empty($filter['end_time'])){
|
||||
$builder->where('create_time','<=',$filter['start_time']);
|
||||
}
|
||||
|
||||
//->where('dispatch_admin_id','>',0);
|
||||
return $builder->group('worker_id')->buildSql();
|
||||
|
||||
}
|
||||
|
||||
private function _toList($list)
|
||||
{
|
||||
foreach ($list as &$datum){
|
||||
//利润率 = 总业绩/总成效额
|
||||
$datum->performance_rate = $this->_calc($datum->performance,$datum->total,4,true);
|
||||
//转化率 = 完单数 / 总接单数
|
||||
$datum->trans_rate = $this->_calc($datum->finish_num,$datum->get_count,4,true);
|
||||
//变现值 = 总业绩 / 总接单数
|
||||
$datum->cash_value = $this->_calc($datum->performance,$datum->get_count,2);
|
||||
//拒单率 = 拒绝数 / 派单数
|
||||
$datum->refuse_rate = $this->_calc($datum->refuse_count,$datum->dispatch_count,4,true);
|
||||
//上门率 = 打卡数 / 接单数
|
||||
$datum->arrive_rate = $this->_calc($datum->arrive_count,$datum->get_count,4,true);
|
||||
//好评率
|
||||
$datum->good_rate = $this->_calc($datum->good_count,$datum->finish_num,4,true);
|
||||
|
||||
$datum->avg_time_diff = $this->_calc($datum->avg_time_diff,3600,2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function _calc($a, $b, int $scale=4, bool $is_percent=false): int|string
|
||||
{
|
||||
$a = $a??0;
|
||||
$b = $b??0;
|
||||
$val = $b > 0 ? bcdiv($a,$b,$scale) : '0.00';
|
||||
|
||||
if($is_percent){
|
||||
return bcmul($val,100,2);
|
||||
}
|
||||
return $val;
|
||||
}
|
||||
}
|
||||
|
|
@ -42,13 +42,12 @@ class Item extends Backend
|
|||
{
|
||||
$build = new Order();
|
||||
|
||||
$start = now()->modify('-14 days')->format('Y-m-d');
|
||||
$start = now()->modify('-7 days')->format('Y-m-d');
|
||||
$end_at = now()->format('Y-m-d 23:29:59');
|
||||
|
||||
$filter = json_decode(request()->get('filter','[]'),true);
|
||||
|
||||
if (!empty($filter['daterange'])) {
|
||||
$arr = explode(' - ', $filter['daterange']);
|
||||
$filter = request()->get('range','');
|
||||
if (!empty($filter)){
|
||||
$arr = explode(' - ', $filter);
|
||||
if (trim($arr[0])) {
|
||||
$start = trim($arr[0]);
|
||||
}
|
||||
|
|
@ -56,8 +55,14 @@ class Item extends Backend
|
|||
$end_at = trim($arr[1]) . ' 23:29:59';
|
||||
}
|
||||
}
|
||||
$offset = request()->get('offset',0);
|
||||
$limit = request()->get('limit',15);
|
||||
$area_id = request()->get('area_id');
|
||||
|
||||
if ($area_id) {
|
||||
$area_id = $this->trimSpecialZeros($area_id);
|
||||
$build->where('area_id', 'like', $area_id . '%');
|
||||
}
|
||||
|
||||
|
||||
$build->whereBetween('create_time', [$start, $end_at])
|
||||
->field([
|
||||
'item_title name', // 类型
|
||||
|
|
@ -101,6 +106,21 @@ class Item extends Backend
|
|||
|
||||
}
|
||||
|
||||
function trimSpecialZeros($str) {
|
||||
if (strlen($str) !== 6) {
|
||||
return $str; // 非6位字符串直接返回
|
||||
}
|
||||
|
||||
if (substr($str, -4) === "0000") {
|
||||
return substr($str, 0, 2); // 去掉后4位
|
||||
} elseif (substr($str, -2) === "00") {
|
||||
return substr($str, 0, 4); // 去掉后2位
|
||||
}
|
||||
|
||||
return $str; // 不符合条件则原样返回
|
||||
}
|
||||
|
||||
|
||||
public function chartData()
|
||||
{
|
||||
$build = new Order();
|
||||
|
|
@ -161,16 +181,32 @@ class Item extends Backend
|
|||
$refundRate[] = $total > 0 ? round($refund / $total * 100, 2) : 0;
|
||||
$monetizedValue[] = $total / $count;
|
||||
}
|
||||
$result = [
|
||||
'xAxis' => $xAxis,
|
||||
'series' => [
|
||||
['name' => '总业绩(¥)', 'type' => 'bar', 'yAxisIndex' => 0, 'data' => $totalPerformance],
|
||||
['name' => '转化率(%)', 'type' => 'bar', 'yAxisIndex' => 1, 'data' => $conversionRate],
|
||||
['name' => '利润率(%)', 'type' => 'bar', 'yAxisIndex' => 1, 'data' => $profitRate],
|
||||
['name' => '退款率(%)', 'type' => 'bar', 'yAxisIndex' => 1, 'data' => $refundRate],
|
||||
['name' => '变现值(¥)', 'type' => 'line', 'yAxisIndex' => 0, 'data' => $monetizedValue],
|
||||
]
|
||||
];
|
||||
|
||||
if ($totalPerformance){
|
||||
$result = [
|
||||
'xAxis' => $xAxis,
|
||||
'series' => [
|
||||
['name' => '总业绩(¥)', 'type' => 'bar', 'yAxisIndex' => 0, 'data' => $totalPerformance],
|
||||
['name' => '转化率(%)', 'type' => 'bar', 'yAxisIndex' => 1, 'data' => $conversionRate],
|
||||
['name' => '利润率(%)', 'type' => 'bar', 'yAxisIndex' => 1, 'data' => $profitRate],
|
||||
['name' => '退款率(%)', 'type' => 'bar', 'yAxisIndex' => 1, 'data' => $refundRate],
|
||||
['name' => '变现值(¥)', 'type' => 'line', 'yAxisIndex' => 0, 'data' => $monetizedValue],
|
||||
]
|
||||
];
|
||||
}else{
|
||||
$result = [
|
||||
'xAxis' => ['无'],
|
||||
'series' => [
|
||||
['name' => '总业绩(¥)', 'type' => 'bar', 'yAxisIndex' => 0, 'data' => [0]],
|
||||
['name' => '转化率(%)', 'type' => 'bar', 'yAxisIndex' => 1, 'data' => [0]],
|
||||
['name' => '利润率(%)', 'type' => 'bar', 'yAxisIndex' => 1, 'data' => [0]],
|
||||
['name' => '退款率(%)', 'type' => 'bar', 'yAxisIndex' => 1, 'data' => [0]],
|
||||
['name' => '变现值(¥)', 'type' => 'line', 'yAxisIndex' => 0, 'data' => [0]],
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
|
|
|||
241
application/admin/controller/supplement/Orders.php
Normal file
241
application/admin/controller/supplement/Orders.php
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
<?php
|
||||
|
||||
namespace app\admin\controller\supplement;
|
||||
|
||||
use app\common\controller\Backend;
|
||||
use think\Db;
|
||||
use think\exception\PDOException;
|
||||
use think\exception\ValidateException;
|
||||
|
||||
/**
|
||||
* 补单记录管理
|
||||
*
|
||||
* @icon fa fa-circle-o
|
||||
*/
|
||||
class Orders extends Backend
|
||||
{
|
||||
|
||||
/**
|
||||
* Orders模型对象
|
||||
* @var \app\admin\model\supplement\Orders
|
||||
*/
|
||||
protected $model = null;
|
||||
protected $sources = null;
|
||||
protected $items = null;
|
||||
protected $itemsformattedTree = null;
|
||||
|
||||
public function _initialize()
|
||||
{
|
||||
parent::_initialize();
|
||||
$this->model = new \app\admin\model\supplement\Orders;
|
||||
|
||||
|
||||
$sources = Db::name('source')
|
||||
->where('status', 1)
|
||||
->field(['id', 'title', 'key_word', 'pid'])
|
||||
->order('pid', 'asc')
|
||||
->order('sort', 'desc')
|
||||
->select();
|
||||
$this->sources = $sources;
|
||||
$filtered = array_filter($sources, function ($item) {
|
||||
return $item['pid'] == 0;
|
||||
});
|
||||
|
||||
$pid_map = array_column($filtered, null, 'id');
|
||||
$res = [];
|
||||
foreach ($sources as $item) {
|
||||
if ($item['pid'] != 0 && isset($pid_map[$item['pid']])) {
|
||||
$res [] = [
|
||||
...$item, 'ptitle' => $pid_map[$item['pid']]['title']
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$items = Db::name('item')
|
||||
->where('status', 1)
|
||||
->field(['id', 'title', 'key_word', 'pid'])
|
||||
->order('pid', 'asc')
|
||||
->order('sort', 'desc')
|
||||
->select();
|
||||
$tree = $this->buildTree($items);
|
||||
$formattedTree = $this->formatTree($tree);
|
||||
|
||||
$this->items = $items;
|
||||
$this->itemsformattedTree = $formattedTree;
|
||||
|
||||
|
||||
$this->view->assign("sources", $res);
|
||||
$this->view->assign("items", $formattedTree);
|
||||
|
||||
|
||||
$this->view->assign("statusList", $this->model->getStatusList());
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
//设置过滤方法
|
||||
$this->request->filter(['strip_tags', 'trim']);
|
||||
if (false === $this->request->isAjax()) {
|
||||
return $this->view->fetch();
|
||||
}
|
||||
//如果发送的来源是 Selectpage,则转发到 Selectpage
|
||||
if ($this->request->request('keyField')) {
|
||||
return $this->selectpage();
|
||||
}
|
||||
[$where, $sort, $order, $offset, $limit] = $this->buildparams();
|
||||
|
||||
|
||||
$list = $this->model
|
||||
->where($where)
|
||||
->with(['user' => function ($q) {
|
||||
$q->field('id,nickname');
|
||||
},
|
||||
'area' => function ($q) {
|
||||
$q->field('id,area_code,merge_name');
|
||||
},
|
||||
])
|
||||
->order($sort, $order)
|
||||
->paginate($limit);
|
||||
$result = ['total' => $list->total(), 'rows' => $list->items()];
|
||||
return json($result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法
|
||||
* 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑
|
||||
* 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改
|
||||
*/
|
||||
|
||||
|
||||
public function add()
|
||||
{
|
||||
if (false === $this->request->isPost()) {
|
||||
return $this->view->fetch();
|
||||
}
|
||||
$params = $this->request->post('row/a');
|
||||
if (empty($params)) {
|
||||
$this->error(__('Parameter %s can not be empty', ''));
|
||||
}
|
||||
$params = $this->preExcludeFields($params);
|
||||
|
||||
if ($this->dataLimit && $this->dataLimitFieldAutoFill) {
|
||||
$params[$this->dataLimitField] = $this->auth->id;
|
||||
}
|
||||
$result = false;
|
||||
Db::startTrans();
|
||||
try {
|
||||
//是否采用模型验证
|
||||
if ($this->modelValidate) {
|
||||
$name = str_replace("\\model\\", "\\validate\\", get_class($this->model));
|
||||
$validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.add' : $name) : $this->modelValidate;
|
||||
$this->model->validateFailException()->validate($validate);
|
||||
}
|
||||
|
||||
$sources = $this->sources;
|
||||
$sources = array_column($sources, 'title', 'id');
|
||||
$params['source_name'] = $sources[$params['source']] ?? null;
|
||||
$params['source_id'] = $params['source'];
|
||||
|
||||
$params['item_name'] = $this->findElementByValue($this->itemsformattedTree, $params['item_id'] ?? null);
|
||||
|
||||
|
||||
$params['admin_id'] = ($params['admin_id'] ?? -1) == -1 ? $this->auth->id : $params['admin_id'];
|
||||
if (empty($params['admin_id'])) {
|
||||
$params['admin_id'] = $this->auth->id;
|
||||
}
|
||||
$params['status'] = 0;
|
||||
$params['created_at'] = date('Y-m-d H:i:s');
|
||||
$params['updated_at'] = date('Y-m-d H:i:s');
|
||||
// dd($params);
|
||||
$result = $this->model->allowField(true)->save($params);
|
||||
Db::commit();
|
||||
} catch (ValidateException | PDOException | Exception $e) {
|
||||
Db::rollback();
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
if ($result === false) {
|
||||
$this->error(__('No rows were inserted'));
|
||||
}
|
||||
$this->success();
|
||||
}
|
||||
|
||||
|
||||
function findElementByValue($data, $targetValue, $path = [])
|
||||
{
|
||||
foreach ($data as $item) {
|
||||
// 将当前节点的 label 添加到路径中
|
||||
$newPath = array_merge($path, [$item['label']]);
|
||||
|
||||
// 如果找到目标值,返回路径
|
||||
if ($item['value'] == $targetValue) {
|
||||
return implode(' / ', $newPath);
|
||||
}
|
||||
|
||||
// 如果当前节点有 children,递归搜索
|
||||
if (isset($item['children']) && is_array($item['children'])) {
|
||||
$result = $this->findElementByValue($item['children'], $targetValue, $newPath);
|
||||
if ($result) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null; // 如果找不到返回 null
|
||||
}
|
||||
|
||||
public function edit($ids = null)
|
||||
{
|
||||
if (!$ids) {
|
||||
if (request()->isPost()) {
|
||||
$ids = input('id');
|
||||
if (!$ids) {
|
||||
$this->error('缺少订单ID');
|
||||
}
|
||||
} else {
|
||||
$this->error('缺少订单ID');
|
||||
}
|
||||
}
|
||||
|
||||
// 获取当前ID对应的订单信息
|
||||
$order = $this->model->get($ids);
|
||||
if (!$order) {
|
||||
$this->error('订单不存在');
|
||||
}
|
||||
|
||||
// 判断是否为POST请求,进行更新操作
|
||||
if (request()->isPost()) {
|
||||
// 获取表单提交的数据
|
||||
$params = input('post.row/a');
|
||||
|
||||
$sources = $this->sources;
|
||||
$sources = array_column($sources, 'title', 'id');
|
||||
$params['source_name'] = $sources[$params['source']] ?? null;
|
||||
$params['source_id'] = $params['source'];
|
||||
|
||||
$params['item_name'] = $this->findElementByValue($this->itemsformattedTree, $params['item_id'] ?? null);
|
||||
|
||||
unset($params['source']);
|
||||
|
||||
$params['admin_id'] = ($params['admin_id'] ?? -1) == -1 ? $this->auth->id : $params['admin_id'];
|
||||
if (empty($params['admin_id'])) {
|
||||
$params['admin_id'] = $this->auth->id;
|
||||
}
|
||||
$params['updated_at'] = date('Y-m-d H:i:s');
|
||||
// 更新订单信息
|
||||
$order->save($params);
|
||||
|
||||
// 返回成功信息
|
||||
$this->success('更新成功', 'index');
|
||||
}
|
||||
$area = new \app\admin\model\Area();
|
||||
$area_name = $area->getNameByCode($order->area_id);
|
||||
$order->area_name = str_replace(',', '/', $area_name);
|
||||
|
||||
// 将订单数据传递到视图
|
||||
$this->assign('row', $order);
|
||||
// 渲染编辑页面
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
27
application/admin/lang/zh-cn/supplement/orders.php
Normal file
27
application/admin/lang/zh-cn/supplement/orders.php
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'Id' => 'ID',
|
||||
'Area_id' => '区域 ID',
|
||||
'Area_name' => '地址',
|
||||
'Source_id' => '所属店铺 ID',
|
||||
'Source_name' => '所属店铺 ID',
|
||||
'Platform_order_no' => '平台订单编号',
|
||||
'Item_id' => '服务类型 ID',
|
||||
'Item_name' => '服务类型 ID',
|
||||
'Buyer_account' => '刷手账号',
|
||||
'Amount' => '支付金额',
|
||||
'Commission' => '刷单佣金',
|
||||
'Screenshots' => '截图 JSON(下单图、评价图等)',
|
||||
'Status' => '订单状态',
|
||||
'Status 0' => '待发货',
|
||||
'Set status to 0' => '设为待发货',
|
||||
'Status 1' => '待评价',
|
||||
'Set status to 1' => '设为待评价',
|
||||
'Status 2' => '评价超时',
|
||||
'Set status to 2' => '设为评价超时',
|
||||
'Admin_id' => '运营人员 ID',
|
||||
'Remark' => '备注信息',
|
||||
'Created_at' => '创建时间',
|
||||
'Updated_at' => '最后更新时间'
|
||||
];
|
||||
51
application/admin/model/supplement/Orders.php
Normal file
51
application/admin/model/supplement/Orders.php
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace app\admin\model\supplement;
|
||||
|
||||
use app\admin\model\Admin;
|
||||
use app\admin\model\Area;
|
||||
use think\Model;
|
||||
|
||||
|
||||
class Orders extends Model
|
||||
{
|
||||
|
||||
// 表名
|
||||
protected $name = 'supplement_orders';
|
||||
|
||||
// 自动写入时间戳字段
|
||||
protected $autoWriteTimestamp = false;
|
||||
|
||||
// 定义时间戳字段名
|
||||
protected $createTime = false;
|
||||
protected $updateTime = false;
|
||||
protected $deleteTime = false;
|
||||
|
||||
// 追加属性
|
||||
protected $append = [
|
||||
'status_text'
|
||||
];
|
||||
|
||||
|
||||
|
||||
public function getStatusList()
|
||||
{
|
||||
return ['0' => __('Status 0'), '1' => __('Status 1'), '2' => __('Status 2'), '3' => '被驳回'];
|
||||
}
|
||||
|
||||
|
||||
public function getStatusTextAttr($value, $data)
|
||||
{
|
||||
$value = $value ?: ($data['status'] ?? '');
|
||||
$list = $this->getStatusList();
|
||||
return $list[$value] ?? '';
|
||||
}
|
||||
|
||||
public function area(){
|
||||
return $this->belongsTo(Area::class,'area_id','area_code');
|
||||
}
|
||||
public function user(){
|
||||
return $this->belongsTo(Admin::class,'admin_id');
|
||||
}
|
||||
|
||||
}
|
||||
27
application/admin/validate/supplement/Orders.php
Normal file
27
application/admin/validate/supplement/Orders.php
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace app\admin\validate\supplement;
|
||||
|
||||
use think\Validate;
|
||||
|
||||
class Orders extends Validate
|
||||
{
|
||||
/**
|
||||
* 验证规则
|
||||
*/
|
||||
protected $rule = [
|
||||
];
|
||||
/**
|
||||
* 提示消息
|
||||
*/
|
||||
protected $message = [
|
||||
];
|
||||
/**
|
||||
* 验证场景
|
||||
*/
|
||||
protected $scene = [
|
||||
'add' => [],
|
||||
'edit' => [],
|
||||
];
|
||||
|
||||
}
|
||||
|
|
@ -43,6 +43,18 @@
|
|||
</section>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="second">
|
||||
<div id="chart-filter-table" style="margin-top:20px;margin-bottom: 30px;">
|
||||
<div style="display: inline-block;width: 300px;position: relative">
|
||||
<input class="form-control" data-toggle="city-picker" type="text" placeholder="地区" id="area-table">
|
||||
<input style="display: none" type="text" id="area_id">
|
||||
</div>
|
||||
<div style="display: inline-block;width: 200px;position: relative">
|
||||
<input type="text" class="form-control datetimerange" data-locale='{"format":"YYYY-MM-DD"}'
|
||||
placeholder="指定日期" name="filter[daterange]" id="daterange-table" autocomplete="off" style="width: 180px;">
|
||||
</div>
|
||||
<!-- 查询按钮 -->
|
||||
<button class="btn btn-default" id="filter-btn-table" style="margin-left: 15px;">查询</button>
|
||||
</div>
|
||||
<table id="table2" class="table table-striped table-bordered table-hover">
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
|||
98
application/admin/view/supplement/orders/add.html
Normal file
98
application/admin/view/supplement/orders/add.html
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
<form id="add-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label col-xs-12 col-sm-2">店铺:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<select id="c-source" data-live-search="true" title="请选择" data-rule="required" name="row[source]" class="form-control selectpicker show-tick">
|
||||
{foreach $sources as $item}
|
||||
<option data-subtext="{$item['ptitle']}" value="{$item['id']}">{$item['title']}</option>
|
||||
{/foreach}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label col-xs-12 col-sm-2">区域:</label>
|
||||
<div class='col-xs-12 col-sm-8'>
|
||||
<input id="c-city" data-rule="required" class="form-control" data-toggle="city-picker" type="text" value="" />
|
||||
<input id="area_id" style="display: none" class="form-control" name="row[area_id]" hidden type="text" value="" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label col-xs-12 col-sm-2">{:__('Platform_order_no')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<input id="c-platform_order_no" data-rule="required" class="form-control" name="row[platform_order_no]" type="text">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-xs-12 col-sm-2">服务类型:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<input type="text" id="item_id" class="zd-input__inner">
|
||||
<input type="text" id="item_id_value" style="display: none" name="row[item_id]" class="zd-input__inner">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-xs-12 col-sm-2">{:__('Buyer_account')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<input id="c-buyer_account" data-rule="required" class="form-control" name="row[buyer_account]" type="text">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-xs-12 col-sm-2">{:__('Amount')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<input id="c-amount" data-rule="required" class="form-control" step="0.01" name="row[amount]" type="number" value="0.00">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-xs-12 col-sm-2">{:__('Commission')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<input id="c-commission" data-rule="required" class="form-control" step="0.01" name="row[commission]" type="number" value="0.00">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-xs-12 col-sm-2">图片:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<div class="input-group">
|
||||
<input id="c-images" class="form-control" size="50" name="row[screenshots]" type="text">
|
||||
<div class="input-group-addon no-border no-padding">
|
||||
<span><button type="button" id="faupload-images" class="btn btn-danger faupload" data-input-id="c-images" data-mimetype="image/gif,image/jpeg,image/png,image/jpg,image/bmp,image/webp" data-multiple="true" data-preview-id="p-images"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
|
||||
<span><button type="button" id="fachoose-images" class="btn btn-primary fachoose" data-input-id="c-images" data-mimetype="image/*" data-multiple="true"><i class="fa fa-list"></i> {:__('Choose')}</button></span>
|
||||
</div>
|
||||
<span class="msg-box n-right" for="c-images"></span>
|
||||
</div>
|
||||
<ul class="row list-inline faupload-preview" id="p-images"></ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
|
||||
<div class="radio">
|
||||
{foreach name="statusList" item="vo"}
|
||||
<label for="row[status]-{$key}"><input id="row[status]-{$key}" name="row[status]" type="radio" value="{$key}" {in name="key" value="0"}checked{/in} /> {$vo}</label>
|
||||
{/foreach}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-xs-12 col-sm-2">{:__('Remark')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<textarea id="c-remark" class="form-control " rows="5" name="row[remark]" cols="50"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group layer-footer">
|
||||
<label class="control-label col-xs-12 col-sm-2"></label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<button type="submit" class="btn btn-primary btn-embossed disabled">{:__('OK')}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<link rel="stylesheet" href="/assets/css/select.css">
|
||||
|
||||
<script>
|
||||
var items = {:json_encode($items); };
|
||||
</script>
|
||||
98
application/admin/view/supplement/orders/edit.html
Normal file
98
application/admin/view/supplement/orders/edit.html
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
<form id="add-form" class="form-horizontal" role="form" data-toggle="validator" method="POST" action="">
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label col-xs-12 col-sm-2">店铺:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<select id="c-source" data-live-search="true" title="请选择" data-rule="required" name="row[source]" class="form-control selectpicker show-tick">
|
||||
{foreach $sources as $item}
|
||||
<option {if $item['id'] == $row.source_id} selected {/if} data-subtext="{$item['ptitle']}" value="{$item['id']}">{$item['title']}</option>
|
||||
{/foreach}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label col-xs-12 col-sm-2">区域:</label>
|
||||
<div class='col-xs-12 col-sm-8'>
|
||||
<input id="c-city" value="{$row.area_name}" data-rule="required" class="form-control" data-toggle="city-picker" type="text" value="" />
|
||||
<input id="area_id" value="{$row.area_id}" style="display: none" class="form-control" name="row[area_id]" hidden type="text" value="" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label col-xs-12 col-sm-2">{:__('Platform_order_no')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<input id="c-platform_order_no" value="{$row.platform_order_no}" data-rule="required" class="form-control" name="row[platform_order_no]" type="text">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-xs-12 col-sm-2">服务类型:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<input type="text" id="item_id" value="{$row.item_name}" data-value="{$row.item_name}" class="zd-input__inner">
|
||||
<input type="text" id="item_id_value" value="{$row.item_id}" style="display: none" name="row[item_id]" class="zd-input__inner">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-xs-12 col-sm-2">{:__('Buyer_account')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<input id="c-buyer_account" value="{$row.buyer_account}" data-rule="required" class="form-control" name="row[buyer_account]" type="text">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-xs-12 col-sm-2">{:__('Amount')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<input id="c-amount" data-rule="required" value="{$row.amount}" class="form-control" step="0.01" name="row[amount]" type="number" value="0.00">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-xs-12 col-sm-2">{:__('Commission')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<input id="c-commission" data-rule="required" value="{$row.commission}" class="form-control" step="0.01" name="row[commission]" type="number" value="0.00">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-xs-12 col-sm-2">图片:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<div class="input-group">
|
||||
<input id="c-images" class="form-control" size="50" name="row[screenshots]" type="text">
|
||||
<div class="input-group-addon no-border no-padding">
|
||||
<span><button type="button" id="faupload-images" class="btn btn-danger faupload" data-input-id="c-images" data-mimetype="image/gif,image/jpeg,image/png,image/jpg,image/bmp,image/webp" data-multiple="true" data-preview-id="p-images"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
|
||||
<span><button type="button" id="fachoose-images" class="btn btn-primary fachoose" data-input-id="c-images" data-mimetype="image/*" data-multiple="true"><i class="fa fa-list"></i> {:__('Choose')}</button></span>
|
||||
</div>
|
||||
<span class="msg-box n-right" for="c-images"></span>
|
||||
</div>
|
||||
<ul class="row list-inline faupload-preview" id="p-images"></ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label col-xs-12 col-sm-2">{:__('Status')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
|
||||
<div class="radio">
|
||||
{foreach name="statusList" item="vo"}
|
||||
<label for="row[status]-{$key}"><input id="row[status]-{$key}" name="row[status]" type="radio" value="{$key}" {in name="key" value="0"}checked{/in} /> {$vo}</label>
|
||||
{/foreach}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-xs-12 col-sm-2">{:__('Remark')}:</label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<textarea id="c-remark" value="{$row.remark}" class="form-control " rows="5" name="row[remark]" cols="50"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group layer-footer">
|
||||
<label class="control-label col-xs-12 col-sm-2"></label>
|
||||
<div class="col-xs-12 col-sm-8">
|
||||
<button type="submit" class="btn btn-primary btn-embossed disabled">{:__('OK')}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<link rel="stylesheet" href="/assets/css/select.css">
|
||||
|
||||
<script>
|
||||
var items = {:json_encode($items); };
|
||||
</script>
|
||||
46
application/admin/view/supplement/orders/index.html
Normal file
46
application/admin/view/supplement/orders/index.html
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<div class="panel panel-default panel-intro">
|
||||
|
||||
<div class="panel-heading">
|
||||
{:build_heading(null,FALSE)}
|
||||
<ul class="nav nav-tabs" data-field="status">
|
||||
<li class="{:$Think.get.status === null ? 'active' : ''}"><a href="#t-all" data-value="" data-toggle="tab">{:__('All')}</a></li>
|
||||
{foreach name="statusList" item="vo"}
|
||||
<li class="{:$Think.get.status === (string)$key ? 'active' : ''}"><a href="#t-{$key}" data-value="{$key}" data-toggle="tab">{$vo}</a></li>
|
||||
{/foreach}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="panel-body">
|
||||
<div id="myTabContent" class="tab-content">
|
||||
<div class="tab-pane fade active in" id="one">
|
||||
<div class="widget-body no-padding">
|
||||
<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-success btn-add {:$auth->check('supplement/orders/add')?'':'hide'}" title="{:__('Add')}" ><i class="fa fa-plus"></i> {:__('Add')}</a>
|
||||
<a href="javascript:;" class="btn btn-success btn-edit btn-disabled disabled {:$auth->check('supplement/orders/edit')?'':'hide'}" title="{:__('Edit')}" ><i class="fa fa-pencil"></i> {:__('Edit')}</a>
|
||||
<a href="javascript:;" class="btn btn-danger btn-del btn-disabled disabled {:$auth->check('supplement/orders/del')?'':'hide'}" title="{:__('Delete')}" ><i class="fa fa-trash"></i> {:__('Delete')}</a>
|
||||
|
||||
|
||||
<div class="dropdown btn-group {:$auth->check('supplement/orders/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>
|
||||
<ul class="dropdown-menu text-left" role="menu">
|
||||
{foreach name="statusList" item="vo"}
|
||||
<li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:" data-params="status={$key}">{:__('Set status to ' . $key)}</a></li>
|
||||
{/foreach}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<table id="table" class="table table-striped table-bordered table-hover table-nowrap"
|
||||
data-operate-edit="{:$auth->check('supplement/orders/edit')}"
|
||||
data-operate-del="{:$auth->check('supplement/orders/del')}"
|
||||
width="100%">
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -110,7 +110,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'cascader'], function
|
|||
{field: 'order_no', title: __('Order_no'), operate: 'LIKE'},
|
||||
{field: 'customer', title: __('Customer'), operate: 'LIKE'},
|
||||
{field: 'tel', title: __('Tel'), operate: 'LIKE'},
|
||||
{field: 'area.merge_name', title: __('Area_id')},
|
||||
{field: 'area.merge_name', title: __('Area_id'),searchable:false},
|
||||
{
|
||||
field: 'address',
|
||||
title: __('Address'),
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'echarts', 'echarts-theme', 'template','addtabs','moment'], function ($, undefined, Backend, Table, Form,echarts,undefined,Template,Datatable,Moment) {
|
||||
define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'echarts', 'echarts-theme', 'template', 'addtabs', 'moment','citypicker'], function ($, undefined, Backend, Table, Form, echarts, undefined, Template, Datatable, Moment) {
|
||||
|
||||
var Controller = {
|
||||
|
||||
index: function () {
|
||||
//绑定事件
|
||||
Controller.api.datepicker();
|
||||
Controller.api.areapicker();
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
var $targetPanel = $($(this).attr("href"));
|
||||
var tabVal = $(this).data('val');
|
||||
|
|
@ -16,23 +18,23 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'echarts', 'echarts-t
|
|||
// 表格2
|
||||
var table2 = $("#table2");
|
||||
table2.bootstrapTable({
|
||||
url: 'statistics/item/list' + location.search,
|
||||
url: 'statistics/item/list',
|
||||
toolbar: '#toolbar1',
|
||||
sortName: 'id',
|
||||
search: false,
|
||||
commonSearch:true,
|
||||
commonSearch: false,
|
||||
visible: false,
|
||||
showToggle: false,
|
||||
showColumns: false,
|
||||
showExport: true,
|
||||
searchFormVisible:true,
|
||||
searchFormVisible: true,
|
||||
columns: [
|
||||
[
|
||||
// {field: 'id', title: __('ID'),visible:true,operate: false},
|
||||
{field: 'name', title: '项目类型',operate: false},
|
||||
{field: 'name', title: '项目类型', operate: false},
|
||||
{field: 'total', title: __('成效额(¥)'), operate: false},
|
||||
{field: 'performance', title: __('总业绩(¥)'), operate: false},
|
||||
{field: 'count_num', title: __('总订单数'),operate: false},
|
||||
{field: 'count_num', title: __('总订单数'), operate: false},
|
||||
|
||||
{field: 'performance_rate', title: __('利润率(%)'), operate: false},
|
||||
|
||||
|
|
@ -45,40 +47,10 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'echarts', 'echarts-t
|
|||
{field: 'avg_time_diff', title: __('派单时效(小时)'), operate: false},
|
||||
|
||||
{field: 'cost_total', title: __('总成本(¥)'), operate: false},
|
||||
{field: 'finish_num', title: __('完单数'),operate: false},
|
||||
{field: 'finish_num', title: __('完单数'), operate: false},
|
||||
|
||||
{field: 'refund_count', title: __('退款单数'), operate: false},
|
||||
{field: 'refund_total', title: __('退款金额(¥)'), operate: false},
|
||||
|
||||
{field: 'daterange', title: __('时间筛选'), addclass:'datetimerange',
|
||||
autocomplete:false,
|
||||
operate: "RANGE",
|
||||
datetimeFormat: "YYYY-MM-DD",
|
||||
//defaultValue:today()+' - '+today(),
|
||||
data:'autocomplete="off" data-local={"format":"YYYY-MM-DD"}',
|
||||
visible:false,
|
||||
defaultValue: Config.default_daterange
|
||||
},
|
||||
// {field: 'operate', title: __('Operate'), table: table2, events:
|
||||
// Table.api.events.operate,
|
||||
// formatter: Table.api.formatter.operate,
|
||||
// buttons: [
|
||||
// {
|
||||
// name: 'aftersales',
|
||||
// text:"售后列表",
|
||||
// title:"售后列表",
|
||||
// icon: 'fa fa-list',
|
||||
// url: function(row){
|
||||
// return 'aftersales/aftersale/index?from=2&man_id='+row.id;
|
||||
// },
|
||||
// extend: 'data-toggle="tooltip" data-container="body"',
|
||||
// classname: 'btn btn-xs btn-default btn-dialog',
|
||||
// visible:function(row){
|
||||
// return true;
|
||||
// }
|
||||
// },
|
||||
// ]
|
||||
// }
|
||||
]
|
||||
]
|
||||
});
|
||||
|
|
@ -87,84 +59,34 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'echarts', 'echarts-t
|
|||
}
|
||||
|
||||
});
|
||||
|
||||
// 触发 tab 后发起 ajax 获取图表数据
|
||||
$('ul.nav-tabs li.active a[data-toggle="tab"]').on("shown.bs.tab", function () {
|
||||
getChartData();
|
||||
Controller.api.getChartData();
|
||||
});
|
||||
|
||||
|
||||
function getChartData(){
|
||||
// 获取日期范围值
|
||||
var daterange = $('#daterange').val();
|
||||
|
||||
// 构建查询参数
|
||||
var params = {
|
||||
'daterange': daterange,
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: "statistics/item/chartData", //
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
data:params,
|
||||
success: function (response) {
|
||||
Controller.api.chart(response);
|
||||
},
|
||||
error: function () {
|
||||
console.error("图表数据加载失败");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
var form = $("#chart-filter");
|
||||
var ranges = {};
|
||||
ranges[__('Today')] = [Moment().startOf('day'), Moment().endOf('day')];
|
||||
ranges[__('Yesterday')] = [Moment().subtract(1, 'days').startOf('day'), Moment().subtract(1, 'days').endOf('day')];
|
||||
ranges[__('Last 7 Days')] = [Moment().subtract(6, 'days').startOf('day'), Moment().endOf('day')];
|
||||
ranges[__('Last 30 Days')] = [Moment().subtract(29, 'days').startOf('day'), Moment().endOf('day')];
|
||||
ranges[__('This Month')] = [Moment().startOf('month'), Moment().endOf('month')];
|
||||
ranges[__('Last Month')] = [Moment().subtract(1, 'month').startOf('month'), Moment().subtract(1, 'month').endOf('month')];
|
||||
ranges[__('今年')] = [Moment().startOf('year'), Moment().endOf('year')];
|
||||
var options = {
|
||||
timePicker: false,
|
||||
autoUpdateInput: false,
|
||||
timePickerSeconds: true,
|
||||
timePicker24Hour: true,
|
||||
autoApply: true,
|
||||
locale: {
|
||||
format: 'YYYY-MM-DD',
|
||||
customRangeLabel: __("Custom Range"),
|
||||
applyLabel: __("Apply"),
|
||||
cancelLabel: __("Clear"),
|
||||
},
|
||||
ranges: ranges,
|
||||
};
|
||||
var callback = function (start, end) {
|
||||
$(this.element).val(start.format(options.locale.format) + " - " + end.format(options.locale.format));
|
||||
};
|
||||
require(['bootstrap-daterangepicker'], function () {
|
||||
$(".datetimerange", form).each(function () {
|
||||
$(this).on('apply.daterangepicker', function (ev, picker) {
|
||||
callback.call(picker, picker.startDate, picker.endDate);
|
||||
var label = picker.chosenLabel;
|
||||
$(picker.element).data('label', label).trigger("change");
|
||||
});
|
||||
$(this).on('cancel.daterangepicker', function (ev, picker) {
|
||||
$(this).val('');
|
||||
});
|
||||
$(this).daterangepicker($.extend({}, options), callback);
|
||||
});
|
||||
});
|
||||
|
||||
// 手动触发一次激活 tab 的 shown.bs.tab
|
||||
getChartData();
|
||||
Controller.api.getChartData();
|
||||
// 绑定查询按钮的点击事件
|
||||
$('#filter-btn').on('click', function() {
|
||||
getChartData();
|
||||
$('#filter-btn').on('click', function () {
|
||||
Controller.api.getChartData();
|
||||
});
|
||||
|
||||
$('#filter-btn-table').on('click', function () {
|
||||
|
||||
const area_id = $('#area_id').val();
|
||||
const range = $('#daterange-table').val();
|
||||
let data = '';
|
||||
if (area_id !== ''){
|
||||
data += 'area_id=' + area_id+'&';
|
||||
}
|
||||
if (area_id !== ''){
|
||||
data += 'range=' + range;
|
||||
}
|
||||
// data = encodeURIComponent(data);
|
||||
$("#table2").bootstrapTable('refresh',{
|
||||
url:'statistics/item/list?' + data,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
|
|
@ -183,11 +105,11 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'echarts', 'echarts-t
|
|||
bindevent: function () {
|
||||
Form.api.bindevent($("form[role=form]"));
|
||||
},
|
||||
chart(chartData){
|
||||
chart(chartData) {
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'shadow' }
|
||||
axisPointer: {type: 'shadow'}
|
||||
},
|
||||
legend: {
|
||||
data: chartData.series.map(s => s.name)
|
||||
|
|
@ -197,20 +119,92 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'echarts', 'echarts-t
|
|||
data: chartData.xAxis
|
||||
},
|
||||
yAxis: [
|
||||
{ type: 'value', name: '金额 (¥)' },
|
||||
{ type: 'value', name: '比率 (%)' }
|
||||
{type: 'value', name: '金额 (¥)'},
|
||||
{type: 'value', name: '比率 (%)'}
|
||||
],
|
||||
series: chartData.series
|
||||
};
|
||||
|
||||
// 初始化图表
|
||||
// 初始化图表
|
||||
const myChart = echarts.init(document.getElementById('bar-chart'));
|
||||
myChart.setOption(option);
|
||||
|
||||
// 监听窗口大小变化,自动重新绘制图表
|
||||
window.addEventListener('resize', function() {
|
||||
window.addEventListener('resize', function () {
|
||||
myChart.resize();
|
||||
});
|
||||
},
|
||||
datepicker: function () {
|
||||
var ranges = {};
|
||||
ranges[__('Today')] = [Moment().startOf('day'), Moment().endOf('day')];
|
||||
ranges[__('Yesterday')] = [Moment().subtract(1, 'days').startOf('day'), Moment().subtract(1, 'days').endOf('day')];
|
||||
ranges[__('Last 7 Days')] = [Moment().subtract(6, 'days').startOf('day'), Moment().endOf('day')];
|
||||
ranges[__('Last 30 Days')] = [Moment().subtract(29, 'days').startOf('day'), Moment().endOf('day')];
|
||||
ranges[__('This Month')] = [Moment().startOf('month'), Moment().endOf('month')];
|
||||
ranges[__('Last Month')] = [Moment().subtract(1, 'month').startOf('month'), Moment().subtract(1, 'month').endOf('month')];
|
||||
ranges[__('今年')] = [Moment().startOf('year'), Moment().endOf('year')];
|
||||
var options = {
|
||||
timePicker: false,
|
||||
autoUpdateInput: false,
|
||||
timePickerSeconds: true,
|
||||
timePicker24Hour: true,
|
||||
autoApply: true,
|
||||
locale: {
|
||||
format: 'YYYY-MM-DD',
|
||||
customRangeLabel: __("Custom Range"),
|
||||
applyLabel: __("Apply"),
|
||||
cancelLabel: __("Clear"),
|
||||
},
|
||||
ranges: ranges,
|
||||
};
|
||||
var callback = function (start, end) {
|
||||
$(this.element).val(start.format(options.locale.format) + " - " + end.format(options.locale.format));
|
||||
};
|
||||
require(['bootstrap-daterangepicker'], function () {
|
||||
$(".datetimerange").each(function () {
|
||||
$(this).on('apply.daterangepicker', function (ev, picker) {
|
||||
callback.call(picker, picker.startDate, picker.endDate);
|
||||
var label = picker.chosenLabel;
|
||||
$(picker.element).data('label', label).trigger("change");
|
||||
});
|
||||
$(this).on('cancel.daterangepicker', function (ev, picker) {
|
||||
$(this).val('');
|
||||
});
|
||||
$(this).daterangepicker($.extend({}, options), callback);
|
||||
});
|
||||
});
|
||||
},
|
||||
areapicker: function () {
|
||||
$("#area-table").citypicker();
|
||||
|
||||
$("#area-table").on("cp:updated", function() {
|
||||
var citypicker = $(this).data("citypicker");
|
||||
var code = citypicker.getCode("district") || citypicker.getCode("city") || citypicker.getCode("province");
|
||||
// table.bootstrapTable('refresh',{query: {area_code: code}});
|
||||
$('#area_id').val(code);
|
||||
});
|
||||
|
||||
},
|
||||
getChartData: function () {
|
||||
// 获取日期范围值
|
||||
var daterange = $('#daterange').val();
|
||||
|
||||
// 构建查询参数
|
||||
var params = {
|
||||
'daterange': daterange,
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: "statistics/item/chartData", //
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
data: params,
|
||||
success: function (response) {
|
||||
Controller.api.chart(response);
|
||||
},
|
||||
error: function () {
|
||||
console.error("图表数据加载失败");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
134
public/assets/js/backend/supplement/orders.js
Normal file
134
public/assets/js/backend/supplement/orders.js
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
define(['jquery', 'bootstrap', 'backend', 'table', 'form','cascader'], function ($, undefined, Backend, Table, Form) {
|
||||
|
||||
var Controller = {
|
||||
index: function () {
|
||||
// 初始化表格参数配置
|
||||
Table.api.init({
|
||||
extend: {
|
||||
index_url: 'supplement/orders/index' + location.search,
|
||||
add_url: 'supplement/orders/add',
|
||||
edit_url: 'supplement/orders/edit',
|
||||
del_url: 'supplement/orders/del',
|
||||
multi_url: 'supplement/orders/multi',
|
||||
import_url: 'supplement/orders/import',
|
||||
table: 'supplement_orders',
|
||||
}
|
||||
});
|
||||
|
||||
var table = $("#table");
|
||||
|
||||
// 初始化表格
|
||||
table.bootstrapTable({
|
||||
url: $.fn.bootstrapTable.defaults.extend.index_url,
|
||||
pk: 'id',
|
||||
sortName: 'id',
|
||||
fixedColumns: true,
|
||||
fixedRightNumber: 1,
|
||||
columns: [
|
||||
[
|
||||
{checkbox: true},
|
||||
{field: 'id', title: __('Id')},
|
||||
{field: 'user.nickname', title: '创建人'},
|
||||
{field: 'area.merge_name', title: '地区',searchable:false},
|
||||
{field: 'item_name', title: __('Item_name')},
|
||||
{field: 'source_name', title: __('Source_name')},
|
||||
{field: 'platform_order_no', title: __('Platform_order_no'), operate: 'LIKE'},
|
||||
{field: 'buyer_account', title: __('Buyer_account'), operate: 'LIKE'},
|
||||
{field: 'amount', title: __('Amount'), operate:'BETWEEN'},
|
||||
{field: 'commission', title: __('Commission'), operate:'BETWEEN'},
|
||||
{field: 'screenshots', title: '图片', operate: 'LIKE', table: table, class: 'autocontent', formatter: Table.api.formatter.content},
|
||||
{field: 'status', title: __('Status'), searchList: {"0":__('Status 0'),"1":__('Status 1'),"2":__('Status 2')}, formatter: Table.api.formatter.status},
|
||||
{field: 'created_at', title: __('Created_at'), operate:'RANGE', addclass:'datetimerange', autocomplete:false},
|
||||
{field: 'updated_at', title: __('Updated_at'), operate:'RANGE', addclass:'datetimerange', autocomplete:false},
|
||||
{field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
|
||||
]
|
||||
]
|
||||
});
|
||||
|
||||
// 为表格绑定事件
|
||||
Table.api.bindevent(table);
|
||||
},
|
||||
add: function () {
|
||||
Controller.api.bindevent();
|
||||
Controller.api.map();
|
||||
},
|
||||
edit: function () {
|
||||
Controller.api.bindevent();
|
||||
Controller.api.map();
|
||||
},
|
||||
api: {
|
||||
bindevent: function () {
|
||||
Form.api.bindevent($("form[role=form]"));
|
||||
},
|
||||
map:function () {
|
||||
$("#c-city").on("cp:updated", function () {
|
||||
var citypicker = $(this).data("citypicker");
|
||||
var code = citypicker.getCode("district") || citypicker.getCode("city") || citypicker.getCode("province");
|
||||
|
||||
$("#area_id").val(code);
|
||||
$("#area_name").val(citypicker.getVal());
|
||||
});
|
||||
// $("#area_map").data("callback", function (res) {
|
||||
// Form.api.target($('#c-address'));
|
||||
// });
|
||||
$(document).on('click', "#area_map", function (e) {
|
||||
const data = $("#c-city").val();
|
||||
if (!data){
|
||||
Toastr.error('请先选择区域');
|
||||
return false;
|
||||
}
|
||||
|
||||
var that = this;
|
||||
var callback = $(that).data('callback');
|
||||
var input_id = $(that).data("input-id") ? $(that).data("input-id") : "";
|
||||
var lat_id = $(that).data("lat-id") ? $(that).data("lat-id") : "";
|
||||
var lng_id = $(that).data("lng-id") ? $(that).data("lng-id") : "";
|
||||
var zoom_id = $(that).data("zoom-id") ? $(that).data("zoom-id") : "";
|
||||
var lat = lat_id ? $("#" + lat_id).val() : '';
|
||||
var lng = lng_id ? $("#" + lng_id).val() : '';
|
||||
var city_code = $("#c-city").val();
|
||||
var zoom = zoom_id ? $("#" + zoom_id).val() : '';
|
||||
var url = "/addons/address/index/select?a=1";
|
||||
url += (lat && lng) ? 'lat=' + lat + '&lng=' + lng +
|
||||
(input_id ? "&address=" + $("#" + input_id).val() : "")
|
||||
+(zoom ? "&zoom=" + zoom : "") : ''
|
||||
;
|
||||
if (city_code){
|
||||
url += city_code ? "&city_code=" + city_code : "";
|
||||
}
|
||||
// console.log(url);
|
||||
Fast.api.open(url, '位置选择', {
|
||||
callback: function (res) {
|
||||
input_id && $("#" + input_id).val(res.address).trigger("change");
|
||||
lat_id && $("#" + lat_id).val(res.lat).trigger("change");
|
||||
lng_id && $("#" + lng_id).val(res.lng).trigger("change");
|
||||
zoom_id && $("#" + zoom_id).val(res.zoom).trigger("change");
|
||||
|
||||
try {
|
||||
//执行回调函数
|
||||
if (typeof callback === 'function') {
|
||||
callback.call(that, res);
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
var _data = items;
|
||||
|
||||
$('#item_id').zdCascader({
|
||||
data: _data,
|
||||
onChange: function ($this, data, allPathData) {
|
||||
// console.log(data,allPathData);
|
||||
$('#item_id_value').val(data.value);
|
||||
}
|
||||
});
|
||||
$('#item_id').val($('#item_id').data('value')).focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
return Controller;
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user