- 新增 invoice_title、showcase、wallet_log 模型及对应语言包 - 新增后台控制器、视图及搜索字段配置 - 修复服务类目删除保护逻辑 - 优化后台登录页面文案及样式 - 新增前端角色切换逻辑及页面拦截 - 新增 SVG 图标及样式变量 - 修复接口权限及下拉框默认值问题
331 行
14 KiB
PHP
可执行文件
331 行
14 KiB
PHP
可执行文件
<?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();
|
||
}
|
||
}
|