文件
yjyf/server/application/admin/controller/user/User.php
MiAn fafddceaf2 feat: 新增发票抬头、晒单、钱包日志等模型及后台管理功能
- 新增 invoice_title、showcase、wallet_log 模型及对应语言包
- 新增后台控制器、视图及搜索字段配置
- 修复服务类目删除保护逻辑
- 优化后台登录页面文案及样式
- 新增前端角色切换逻辑及页面拦截
- 新增 SVG 图标及样式变量
- 修复接口权限及下拉框默认值问题
2026-11-11 17:02:35 +08:00

331 行
14 KiB
PHP
可执行文件

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

<?php
namespace app\admin\controller\user;
use app\common\controller\Backend;
use app\common\library\Auth;
use think\Db;
/**
* 业主与会员管理
*
* @icon fa fa-user
*/
class User extends Backend
{
protected $relationSearch = true;
protected $searchFields = 'id,username,nickname,mobile';
/**
* @var \app\admin\model\User
*/
protected $model = null;
public function _initialize()
{
parent::_initialize();
$this->model = new \app\admin\model\User;
}
/**
* 会员列表(批量聚合业务角色、订单数与实付消费额)
*/
public function index()
{
$this->request->filter(['strip_tags', 'trim']);
if ($this->request->isAjax()) {
if ($this->request->request('keyField')) {
return $this->selectpage();
}
list($where, $sort, $order, $offset, $limit) = $this->buildparams();
$list = $this->model
->with('group')
->where($where)
->order($sort, $order)
->paginate($limit);
$rows = $list->items();
$uids = array_column($rows, 'id');
$masterMap = [];
$merchantMap = [];
$memberMap = [];
$orderStatMap = [];
$couponCntMap = [];
if (!empty($uids)) {
$masters = Db::name('master')->field('id,user_id,realname,verify_status')->where('user_id', 'in', $uids)->select();
foreach ($masters as $m) {
$masterMap[(int)$m['user_id']] = $m;
}
$merchants = Db::name('merchant')->field('id,user_id,name,verify_status')->where('user_id', 'in', $uids)->select();
foreach ($merchants as $mc) {
$merchantMap[(int)$mc['user_id']] = $mc;
}
$members = Db::name('merchant_member')->alias('mm')
->join('merchant m', 'm.id = mm.merchant_id', 'LEFT')
->where('mm.user_id', 'in', $uids)
->field('mm.user_id, m.name as merchant_name')
->select();
foreach ($members as $mb) {
$memberMap[(int)$mb['user_id']] = $mb['merchant_name'];
}
$orderAggs = Db::name('order')
->field('user_id, COUNT(*) AS cnt, SUM(CASE WHEN pay_status = 1 AND status <> 9 THEN pay_amount ELSE 0 END) AS spent')
->where('user_id', 'in', $uids)
->group('user_id')
->select();
foreach ($orderAggs as $oa) {
$orderStatMap[(int)$oa['user_id']] = [
'cnt' => (int)$oa['cnt'],
'spent' => number_format((float)$oa['spent'], 2, '.', ''),
];
}
$couponAggs = Db::name('user_coupon')
->field('user_id, COUNT(*) AS cnt')
->where('user_id', 'in', $uids)
->where('status', 'unused')
->group('user_id')
->select();
foreach ($couponAggs as $ca) {
$couponCntMap[(int)$ca['user_id']] = (int)$ca['cnt'];
}
}
foreach ($rows as $v) {
$uid = (int)$v->id;
$v->avatar = $v->avatar ? (strpos($v->avatar, '/assets/') === 0 ? $v->avatar : cdnurl($v->avatar, true)) : letter_avatar($v->nickname);
$v->hidden(['password', 'salt']);
$roles = [];
if (isset($masterMap[$uid]) && $masterMap[$uid]['verify_status'] === 'pass') {
$roles[] = ['type' => 'master', 'label' => '认证师傅 (' . $masterMap[$uid]['realname'] . ')'];
}
if (isset($merchantMap[$uid]) && $merchantMap[$uid]['verify_status'] === 'pass') {
$roles[] = ['type' => 'merchant', 'label' => '签约企业 (' . $merchantMap[$uid]['name'] . ')'];
} elseif (isset($memberMap[$uid]) && !empty($memberMap[$uid])) {
$roles[] = ['type' => 'member', 'label' => '企业子账号 (' . $memberMap[$uid] . ')'];
}
if (empty($roles)) {
$roles[] = ['type' => 'owner', 'label' => '个人业主'];
}
$v['role_badges'] = $roles;
$v['order_count'] = $orderStatMap[$uid]['cnt'] ?? 0;
$v['total_spent'] = $orderStatMap[$uid]['spent'] ?? '0.00';
$v['unused_coupons'] = $couponCntMap[$uid] ?? 0;
}
return json(["total" => $list->total(), "rows" => $rows]);
}
return $this->view->fetch();
}
/**
* 会员业务全景详情看板
*/
public function detail($ids = null)
{
$row = $this->model->find($ids);
if (!$row) {
$this->error(__('No Results were found'));
}
$row->avatar = $row->avatar ? (strpos($row->avatar, '/assets/') === 0 ? $row->avatar : cdnurl($row->avatar, true)) : letter_avatar($row->nickname);
$uid = (int)$row['id'];
$masterProfile = Db::name('master')->where('user_id', $uid)->find();
$merchantProfile = Db::name('merchant')->where('user_id', $uid)->find();
if (!$merchantProfile) {
$memberBind = Db::name('merchant_member')->alias('mm')
->join('merchant m', 'm.id = mm.merchant_id', 'LEFT')
->where('mm.user_id', $uid)
->field('m.*, mm.remark as member_remark')
->find();
if ($memberBind) {
$merchantProfile = $memberBind;
}
}
$addressList = Db::name('address')
->where('user_id', $uid)
->order('is_default desc, id desc')
->limit(6)
->select();
$statusMap = (new \app\admin\model\Order)->getStatusList();
$recentOrders = Db::name('order')->alias('o')
->join('master m', 'm.id = o.master_id', 'LEFT')
->where('o.user_id', $uid)
->field('o.*, m.realname as master_name')
->order('o.id desc')
->limit(8)
->select();
foreach ($recentOrders as &$o) {
$o['status_label'] = $statusMap[(int)$o['status']] ?? ('状态' . $o['status']);
}
unset($o);
$orderAgg = Db::name('order')
->field('COUNT(*) AS total_cnt, SUM(CASE WHEN pay_status = 1 AND status <> 9 THEN pay_amount ELSE 0 END) AS total_spent')
->where('user_id', $uid)
->find();
$coupons = Db::name('user_coupon')->alias('uc')
->join('coupon c', 'c.id = uc.coupon_id', 'LEFT')
->join('order o', 'o.id = uc.order_id', 'LEFT')
->where('uc.user_id', $uid)
->field('uc.*, c.name as coupon_name, c.type as coupon_type, c.amount as coupon_amount, c.discount as coupon_discount, c.min_amount, o.order_no')
->order('uc.id desc')
->limit(8)
->select();
$this->view->assign([
'row' => $row,
'masterProfile' => $masterProfile,
'merchantProfile' => $merchantProfile,
'addressList' => $addressList,
'recentOrders' => $recentOrders,
'coupons' => $coupons,
'orderCount' => (int)($orderAgg['total_cnt'] ?? 0),
'totalSpent' => number_format((float)($orderAgg['total_spent'] ?? 0), 2, '.', ''),
]);
return $this->view->fetch();
}
/**
* 添加
*/
public function add()
{
if ($this->request->isPost()) {
$this->token();
$params = $this->request->post("row/a", []);
if (!$params) {
$this->error(__('Parameter %s can not be empty', ''));
}
$username = trim((string)($params['username'] ?? ''));
$nickname = trim((string)($params['nickname'] ?? ''));
$mobile = trim((string)($params['mobile'] ?? ''));
$email = trim((string)($params['email'] ?? ''));
$password = (string)($params['password'] ?? '');
if ($username === '' || $nickname === '') {
$this->error('用户名与昵称不能为空');
}
if (Db::name('user')->where('username', $username)->count() > 0) {
$this->error('该用户名已存在');
}
if ($mobile !== '' && Db::name('user')->where('mobile', $mobile)->count() > 0) {
$this->error('该手机号已被注册');
}
if ($email !== '' && Db::name('user')->where('email', $email)->count() > 0) {
$this->error('该邮箱已被绑定');
}
if (strlen($password) < 6 || strlen($password) > 30) {
$this->error('登录密码长度须在6-30位之间');
}
$salt = \fast\Random::alnum();
$params['salt'] = $salt;
$params['password'] = Auth::instance()->getEncryptPassword($password, $salt);
$params['jointime'] = time();
unset($params['money'], $params['score']);
$result = $this->model->allowField(true)->save($params);
if ($result !== false) {
$this->success();
}
$this->error($this->model->getError() ?: __('No rows were inserted'));
}
$this->view->assign('groupList', build_select('row[group_id]', \app\admin\model\UserGroup::column('id,name'), 1, ['class' => 'form-control selectpicker']));
return $this->view->fetch();
}
/**
* 编辑(兼容微信授权注册无邮箱/无IP记录会员的状态修改与资料维护)
*/
public function edit($ids = null)
{
$row = $this->model->get($ids);
if (!$row) {
$this->error(__('No Results were found'));
}
if ($this->request->isPost()) {
$this->token();
$params = $this->request->post("row/a", []);
if (!$params) {
$this->error(__('Parameter %s can not be empty', ''));
}
$uid = (int)$row['id'];
$username = isset($params['username']) ? trim((string)$params['username']) : (string)$row['username'];
$nickname = isset($params['nickname']) ? trim((string)$params['nickname']) : (string)$row['nickname'];
$mobile = isset($params['mobile']) ? trim((string)$params['mobile']) : (string)$row['mobile'];
$email = isset($params['email']) ? trim((string)$params['email']) : (string)$row['email'];
$password = (string)($params['password'] ?? '');
if ($username === '') {
$this->error('用户名不能为空');
}
if (Db::name('user')->where('username', $username)->where('id', '<>', $uid)->count() > 0) {
$this->error('该用户名已被其他账号占用');
}
if ($mobile !== '' && !preg_match('/^1[3-9]\d{9}$/', $mobile)) {
$this->error('手机号格式不正确');
}
if ($mobile !== '' && Db::name('user')->where('mobile', $mobile)->where('id', '<>', $uid)->count() > 0) {
$this->error('该手机号已被其他账号绑定');
}
if ($email !== '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
$this->error('电子邮箱格式不正确');
}
if ($email !== '' && Db::name('user')->where('email', $email)->where('id', '<>', $uid)->count() > 0) {
$this->error('该邮箱已被其他账号绑定');
}
if ($password !== '' && (strlen($password) < 6 || strlen($password) > 30)) {
$this->error('重置密码长度须在6-30位之间');
}
// 禁止通过会员资料编辑裸改资金余额与积分,保障账实一致
unset($params['money'], $params['score'], $params['id']);
if ($nickname === '') {
$params['nickname'] = $row['nickname'] ?: $username;
}
$result = $row->allowField(true)->save($params);
if ($result !== false) {
$this->success();
}
$this->error($row->getError() ?: __('No rows were updated'));
}
$this->view->assign('groupList', build_select('row[group_id]', \app\admin\model\UserGroup::column('id,name'), $row['group_id'], ['class' => 'form-control selectpicker']));
$this->view->assign('row', $row);
return $this->view->fetch();
}
/**
* 删除(校验关联业务数据,防止产生孤儿工单与师傅档案)
*/
public function del($ids = "")
{
if (!$this->request->isPost()) {
$this->error(__("Invalid parameters"));
}
$ids = $ids ? $ids : $this->request->post("ids");
$row = $this->model->get($ids);
$this->modelValidate = true;
if (!$row) {
$this->error(__('No Results were found'));
}
$uid = (int)$row['id'];
if (Db::name('order')->where('user_id', $uid)->count() > 0
|| Db::name('master')->where('user_id', $uid)->count() > 0
|| Db::name('merchant')->where('user_id', $uid)->count() > 0) {
$this->error('该会员已关联历史订单、师傅或商家档案,禁止物理删除;如需停用请将状态设为隐藏');
}
Auth::instance()->delete($uid);
$this->success();
}
}