feat: 新增发票抬头、晒单、钱包日志等模型及后台管理功能

- 新增 invoice_title、showcase、wallet_log 模型及对应语言包
- 新增后台控制器、视图及搜索字段配置
- 修复服务类目删除保护逻辑
- 优化后台登录页面文案及样式
- 新增前端角色切换逻辑及页面拦截
- 新增 SVG 图标及样式变量
- 修复接口权限及下拉框默认值问题
这个提交包含在:
2026-11-11 17:02:35 +08:00
父节点 5908cf6b16
当前提交 fafddceaf2
共有 332 个文件被更改,包括 33892 次插入 和 8835 次删除

查看文件

@@ -3,20 +3,22 @@
namespace app\admin\controller;
use app\common\controller\Backend;
use think\Db;
use think\exception\PDOException;
/**
* 消息中心
* 消息通知中心
*
* @icon fa fa-circle-o
* @icon fa fa-bell-o
*/
class Message extends Backend
{
/**
* Message模型对象
* @var \app\admin\model\Message
*/
protected $model = null;
protected $searchFields = 'id,title,content';
public function _initialize()
{
@@ -26,13 +28,189 @@ class Message extends Backend
$this->view->assign("typeList", $this->model->getTypeList());
}
/**
* 消息列表(批量补齐接收人真实姓名/昵称、手机号及关联工单号)
*/
public function index()
{
$this->request->filter(['strip_tags', 'trim']);
if (false === $this->request->isAjax()) {
return $this->view->fetch();
}
if ($this->request->request('keyField')) {
return $this->selectpage();
}
[$where, $sort, $order, $offset, $limit] = $this->buildparams();
$list = $this->model
->where($where)
->order($sort, $order)
->paginate($limit);
$rows = $list->items();
$userIds = [];
$masterIds = [];
$orderIds = [];
foreach ($rows as $r) {
if ($r['receive_type'] === 'master' && (int)$r['receive_id'] > 0) {
$masterIds[] = (int)$r['receive_id'];
} elseif ($r['receive_type'] === 'user' && (int)$r['receive_id'] > 0) {
$userIds[] = (int)$r['receive_id'];
}
if ((int)$r['order_id'] > 0) {
$orderIds[] = (int)$r['order_id'];
}
}
$userMap = $userIds
? Db::name('user')->where('id', 'in', array_unique($userIds))->column('id,nickname,username,mobile', 'id')
: [];
$masterMap = $masterIds
? Db::name('master')->where('id', 'in', array_unique($masterIds))->column('id,realname,mobile', 'id')
: [];
$orderMap = $orderIds
? Db::name('order')->where('id', 'in', array_unique($orderIds))->column('order_no', 'id')
: [];
foreach ($rows as &$item) {
$rid = (int)$item['receive_id'];
if ($item['receive_type'] === 'master') {
$m = $masterMap[$rid] ?? null;
$item['receiver_name'] = $m ? ($m['realname'] ?: ('师傅#' . $rid)) : ('师傅#' . $rid);
$item['receiver_mobile'] = $m['mobile'] ?? '';
} else {
$u = $userMap[$rid] ?? null;
$item['receiver_name'] = $u ? ($u['nickname'] ?: ($u['username'] ?: ('用户#' . $rid))) : ('用户#' . $rid);
$item['receiver_mobile'] = $u['mobile'] ?? '';
}
$oid = (int)$item['order_id'];
$item['order_no'] = $oid > 0 ? ($orderMap[$oid] ?? '') : '';
}
unset($item);
return json(['total' => $list->total(), 'rows' => $rows]);
}
/**
* 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法
* 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑
* 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改
* 消息详情回看
*/
public function detail($ids = null)
{
$row = $this->model->get($ids);
if (!$row) {
$this->error(__('No Results were found'));
}
$receiver = [];
if ($row['receive_type'] === 'master' && (int)$row['receive_id'] > 0) {
$m = Db::name('master')->where('id', (int)$row['receive_id'])->field('id,realname,mobile,city')->find();
if ($m) {
$receiver = ['name' => $m['realname'], 'mobile' => $m['mobile'], 'extra' => $m['city'] ?: '认证师傅'];
}
} elseif ((int)$row['receive_id'] > 0) {
$u = Db::name('user')->where('id', (int)$row['receive_id'])->field('id,nickname,username,mobile')->find();
if ($u) {
$receiver = ['name' => $u['nickname'] ?: $u['username'], 'mobile' => $u['mobile'], 'extra' => '平台注册用户'];
}
}
$order = (int)$row['order_id'] > 0
? Db::name('order')->where('id', (int)$row['order_id'])->field('id,order_no,service_name,pay_amount,status')->find()
: null;
$this->view->assign([
'row' => $row,
'receiver' => $receiver,
'order' => $order ?: [],
]);
return $this->view->fetch();
}
/**
* 下发站内通知(支持选择单个或多个用户/师傅批量推送)
*/
public function add()
{
if (false === $this->request->isPost()) {
return $this->view->fetch();
}
$params = $this->request->post('row/a', []);
$receiveType = in_array($params['receive_type'] ?? '', ['user', 'master'], true) ? $params['receive_type'] : 'user';
$rawIds = $receiveType === 'master'
? ($params['receive_master_id'] ?? ($params['receive_id'] ?? ''))
: ($params['receive_user_id'] ?? ($params['receive_id'] ?? ''));
$idArr = array_values(array_unique(array_filter(array_map('intval', explode(',', (string)$rawIds)))));
if (!$idArr) {
$this->error('请至少选择一位消息接收对象');
}
$title = trim((string)($params['title'] ?? ''));
$content = trim((string)($params['content'] ?? ''));
if ($title === '' || $content === '') {
$this->error('通知标题与正文内容不能为空');
}
$msgType = in_array($params['type'] ?? '', ['order', 'system', 'audit', 'remind'], true) ? $params['type'] : 'system';
$orderId = max(0, (int)($params['order_id'] ?? 0));
$isRead = !empty($params['is_read']) ? 1 : 0;
$now = time();
$insertRows = [];
foreach ($idArr as $rid) {
$insertRows[] = [
'receive_type' => $receiveType,
'receive_id' => $rid,
'type' => $msgType,
'title' => mb_substr($title, 0, 100),
'content' => mb_substr($content, 0, 500),
'order_id' => $orderId,
'is_read' => $isRead,
'createtime' => $now,
];
}
Db::startTrans();
try {
Db::name('message')->insertAll($insertRows);
Db::commit();
} catch (PDOException|\Exception $e) {
Db::rollback();
$this->error($e->getMessage());
}
$this->success('成功发送 ' . count($insertRows) . ' 条站内通知');
}
/**
* 编辑站内通知
*/
public function edit($ids = null)
{
$row = $this->model->get($ids);
if (!$row) {
$this->error(__('No Results were found'));
}
if (false === $this->request->isPost()) {
$this->view->assign('row', $row);
return $this->view->fetch();
}
$params = $this->request->post('row/a', []);
$receiveType = in_array($params['receive_type'] ?? '', ['user', 'master'], true) ? $params['receive_type'] : $row['receive_type'];
$receiveId = $receiveType === 'master'
? (int)($params['receive_master_id'] ?? ($params['receive_id'] ?? 0))
: (int)($params['receive_user_id'] ?? ($params['receive_id'] ?? 0));
if ($receiveId <= 0) {
$this->error('请选择有效的接收对象');
}
$data = [
'receive_type' => $receiveType,
'receive_id' => $receiveId,
'type' => in_array($params['type'] ?? '', ['order', 'system', 'audit', 'remind'], true) ? $params['type'] : $row['type'],
'order_id' => max(0, (int)($params['order_id'] ?? 0)),
'title' => mb_substr(trim((string)($params['title'] ?? '')), 0, 100),
'content' => mb_substr(trim((string)($params['content'] ?? '')), 0, 500),
'is_read' => !empty($params['is_read']) ? 1 : 0,
];
if ($data['title'] === '' || $data['content'] === '') {
$this->error('通知标题与正文内容不能为空');
}
$row->save($data);
$this->success();
}
}