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(); } }