458 行
18 KiB
PHP
458 行
18 KiB
PHP
<?php
|
||
|
||
namespace app\admin\controller;
|
||
|
||
use app\common\controller\Backend;
|
||
use think\Db;
|
||
use Exception;
|
||
|
||
/**
|
||
* 师傅档案与资质管理
|
||
*
|
||
* @icon fa fa-wrench
|
||
*/
|
||
class Master extends Backend
|
||
{
|
||
/**
|
||
* Master模型对象
|
||
* @var \app\admin\model\Master
|
||
*/
|
||
protected $model = null;
|
||
protected $relationSearch = true;
|
||
protected $searchFields = 'id,realname,mobile,city';
|
||
|
||
public function _initialize()
|
||
{
|
||
parent::_initialize();
|
||
$this->model = new \app\admin\model\Master;
|
||
$this->view->assign("workStatusList", $this->model->getWorkStatusList());
|
||
$this->view->assign("verifyStatusList", $this->model->getVerifyStatusList());
|
||
}
|
||
|
||
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
|
||
->with(['user'])
|
||
->where($where)
|
||
->order($sort, $order)
|
||
->paginate($limit);
|
||
|
||
$items = $list->items();
|
||
$masterIds = array_column($items, 'id');
|
||
$skillsGroup = [];
|
||
if (!empty($masterIds)) {
|
||
$allSkills = Db::name('master_skill')
|
||
->where('master_id', 'in', $masterIds)
|
||
->where('status', 'normal')
|
||
->select();
|
||
foreach ($allSkills as $sk) {
|
||
$name = !empty($sk['skill_name']) ? $sk['skill_name'] : '';
|
||
if ($name) {
|
||
$skillsGroup[$sk['master_id']][] = $name;
|
||
}
|
||
}
|
||
}
|
||
foreach ($items as &$it) {
|
||
$it['skills_list'] = $skillsGroup[$it['id']] ?? [];
|
||
}
|
||
unset($it);
|
||
|
||
return json(['total' => $list->total(), 'rows' => $items]);
|
||
}
|
||
|
||
/**
|
||
* 新增认证师傅档案(校验用户唯一绑定,强制初始化资金与履约统计为 0)
|
||
*/
|
||
public function add()
|
||
{
|
||
if (false === $this->request->isPost()) {
|
||
return $this->view->fetch();
|
||
}
|
||
$params = $this->request->post('row/a', []);
|
||
if (!$params) {
|
||
$this->error(__('Parameter %s can not be empty', ''));
|
||
}
|
||
$params = $this->preExcludeFields($params);
|
||
$uid = (int)($params['user_id'] ?? 0);
|
||
if ($uid <= 0) {
|
||
$this->error('请选择关联的平台注册用户账号');
|
||
}
|
||
if (Db::name('master')->where('user_id', $uid)->count() > 0) {
|
||
$this->error('该用户已绑定过师傅档案,请勿重复创建');
|
||
}
|
||
$params['money'] = 0.00;
|
||
$params['frozen_money'] = 0.00;
|
||
$params['order_count'] = 0;
|
||
$params['level'] = max(1, (int)($params['level'] ?? 1));
|
||
$params['score'] = 5.00;
|
||
$params['accept_rate'] = 100.00;
|
||
$params['ontime_rate'] = 100.00;
|
||
|
||
$result = $this->model->allowField(true)->save($params);
|
||
if ($result !== false) {
|
||
$this->success();
|
||
}
|
||
$this->error($this->model->getError() ?: __('No rows were inserted'));
|
||
}
|
||
|
||
/**
|
||
* 删除保护:已产生承接工单、钱包余额或结算流水的师傅禁止物理删除
|
||
*/
|
||
public function del($ids = null)
|
||
{
|
||
if (false === $this->request->isPost()) {
|
||
$this->error(__('Invalid parameters'));
|
||
}
|
||
$ids = $ids ?: $this->request->post('ids');
|
||
$idArr = array_values(array_filter(array_map('intval', explode(',', (string)$ids))));
|
||
if (!$idArr) {
|
||
$this->error(__('Parameter %s can not be empty', 'ids'));
|
||
}
|
||
$hasOrder = Db::name('order')->where('master_id', 'in', $idArr)->count();
|
||
$hasFund = Db::name('master')->where('id', 'in', $idArr)->where('money > 0 OR frozen_money > 0')->count();
|
||
$hasSettle = Db::name('settle')->where('master_id', 'in', $idArr)->count();
|
||
if ($hasOrder > 0 || $hasFund > 0 || $hasSettle > 0) {
|
||
$this->error('所选师傅已关联历史工单、结算记录或存在钱包资金,禁止物理删除;如需停用请关闭听单状态');
|
||
}
|
||
Db::startTrans();
|
||
try {
|
||
Db::name('master_skill')->where('master_id', 'in', $idArr)->delete();
|
||
$deleted = Db::name('master')->where('id', 'in', $idArr)->delete();
|
||
Db::commit();
|
||
} catch (Exception $e) {
|
||
Db::rollback();
|
||
$this->error($e->getMessage());
|
||
}
|
||
if ($deleted) {
|
||
$this->success();
|
||
}
|
||
$this->error(__('No rows were deleted'));
|
||
}
|
||
|
||
/**
|
||
* 编辑师傅资料与入驻资质审核(状态变更自动触发通知)
|
||
*/
|
||
public function edit($ids = null)
|
||
{
|
||
$row = $this->model->get($ids);
|
||
if (!$row) {
|
||
$this->error(__('No Results were found'));
|
||
}
|
||
$oldVerify = (string)$row['verify_status'];
|
||
|
||
if ($this->request->isPost()) {
|
||
$params = $this->request->post("row/a");
|
||
if ($params) {
|
||
$params = $this->preExcludeFields($params);
|
||
// 禁止在师傅档案编辑中直接篡改钱包可用余额、冻结资金与累计完单数
|
||
unset($params['money'], $params['frozen_money'], $params['order_count']);
|
||
|
||
// 若提交的身份证号包含脱敏星号 *,保留原数据库记录不覆盖;若为新填写明文则做合法性校验
|
||
if (isset($params['idcard_no'])) {
|
||
$idcardInput = trim((string)$params['idcard_no']);
|
||
if (strpos($idcardInput, '*') !== false) {
|
||
unset($params['idcard_no']);
|
||
} elseif ($idcardInput !== '' && !preg_match('/^(^\d{15}$)|(^\d{17}([0-9]|X|x)$)$/', $idcardInput)) {
|
||
$this->error('请输入正确的15位或18位身份证号码');
|
||
}
|
||
}
|
||
|
||
// 若 city-picker 因省市格式差异未选新值导致提交为空而原库有城市,保留原城市不清空
|
||
if (isset($params['city']) && trim((string)$params['city']) === '' && !empty($row['city'])) {
|
||
unset($params['city']);
|
||
}
|
||
|
||
if (isset($params['verify_status']) && $params['verify_status'] === 'reject' && empty(trim((string)($params['audit_msg'] ?? '')))) {
|
||
$this->error('驳回实名认证时请填写审核意见说明');
|
||
}
|
||
$result = false;
|
||
Db::startTrans();
|
||
try {
|
||
$result = $row->allowField(true)->save($params);
|
||
$newVerify = isset($params['verify_status']) ? (string)$params['verify_status'] : $oldVerify;
|
||
if ($newVerify !== $oldVerify && in_array($newVerify, ['pass', 'reject'], true)) {
|
||
$now = time();
|
||
$title = $newVerify === 'pass' ? '师傅入驻审核通过' : '师傅入驻审核未通过';
|
||
$content = $newVerify === 'pass'
|
||
? '您的师傅实名认证已审核通过,现在可开启听单承接同城服务工单。'
|
||
: ('您的师傅认证申请未通过,原因:' . (trim((string)($params['audit_msg'] ?? '')) ?: '请核对资料后重新提交'));
|
||
Db::name('message')->insert([
|
||
'receive_type' => 'master',
|
||
'receive_id' => (int)$row['id'],
|
||
'type' => 'audit',
|
||
'title' => $title,
|
||
'content' => $content,
|
||
'order_id' => 0,
|
||
'is_read' => 0,
|
||
'createtime' => $now,
|
||
]);
|
||
if ((int)$row['user_id'] > 0) {
|
||
Db::name('message')->insert([
|
||
'receive_type' => 'user',
|
||
'receive_id' => (int)$row['user_id'],
|
||
'type' => 'audit',
|
||
'title' => $title,
|
||
'content' => $content,
|
||
'order_id' => 0,
|
||
'is_read' => 0,
|
||
'createtime' => $now,
|
||
]);
|
||
}
|
||
}
|
||
Db::commit();
|
||
} catch (Exception $e) {
|
||
Db::rollback();
|
||
$this->error($e->getMessage());
|
||
}
|
||
if ($result !== false) {
|
||
$this->success();
|
||
}
|
||
$this->error(__('No rows were updated'));
|
||
}
|
||
$this->error(__('Parameter %s can not be empty', ''));
|
||
}
|
||
|
||
$this->view->assign("row", $row);
|
||
return $this->view->fetch();
|
||
}
|
||
|
||
/**
|
||
* 师傅服务技能管理(保留既有证书与元数据)
|
||
*/
|
||
public function skills($ids = null)
|
||
{
|
||
$row = $this->model->find($ids);
|
||
if (!$row) {
|
||
$this->error(__('No Results were found'));
|
||
}
|
||
|
||
if ($this->request->isPost()) {
|
||
$selectedSkills = (array)$this->request->post('skills/a', []);
|
||
$customText = trim((string)$this->request->post('custom_text', ''));
|
||
|
||
$customNames = [];
|
||
if (!empty($customText)) {
|
||
$parts = preg_split('/[,,\s]+/u', $customText);
|
||
foreach ($parts as $p) {
|
||
$p = trim($p);
|
||
if (!empty($p) && !in_array($p, $customNames, true) && !in_array($p, $selectedSkills, true)) {
|
||
$customNames[] = $p;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 缓存师傅原有技能的证书图片与分类ID,防止保存时丢失证书
|
||
$existingRows = Db::name('master_skill')->where('master_id', $row['id'])->select();
|
||
$existingMap = [];
|
||
foreach ($existingRows as $er) {
|
||
$existingMap[(string)$er['skill_name']] = $er;
|
||
}
|
||
|
||
$dictRows = !empty($selectedSkills)
|
||
? Db::name('skill_dict')->where('name', 'in', $selectedSkills)->select()
|
||
: [];
|
||
$dictMap = [];
|
||
foreach ($dictRows as $dr) {
|
||
$dictMap[$dr['name']] = $dr;
|
||
}
|
||
|
||
$now = time();
|
||
Db::startTrans();
|
||
try {
|
||
Db::name('master_skill')->where('master_id', $row['id'])->delete();
|
||
|
||
foreach ($selectedSkills as $sName) {
|
||
$dict = $dictMap[$sName] ?? null;
|
||
$old = $existingMap[$sName] ?? null;
|
||
Db::name('master_skill')->insert([
|
||
'master_id' => $row['id'],
|
||
'cat_id' => $old ? (int)$old['cat_id'] : 0,
|
||
'skill_id' => $dict ? (int)$dict['id'] : ($old ? (int)$old['skill_id'] : 0),
|
||
'skill_name' => $sName,
|
||
'category' => $dict ? $dict['category'] : ($old && !empty($old['category']) ? $old['category'] : '服务技能'),
|
||
'is_custom' => 0,
|
||
'cert_image' => $old ? (string)$old['cert_image'] : '',
|
||
'status' => 'normal',
|
||
'createtime' => $old ? (int)$old['createtime'] : $now,
|
||
]);
|
||
}
|
||
|
||
foreach ($customNames as $cName) {
|
||
$exist = Db::name('skill_dict')->where('name', $cName)->find();
|
||
if (!$exist) {
|
||
$skillDictId = Db::name('skill_dict')->insertGetId([
|
||
'category_id' => 0,
|
||
'category' => '其他技能',
|
||
'name' => $cName,
|
||
'source' => 'custom',
|
||
'master_id' => $row['id'],
|
||
'weigh' => 0,
|
||
'status' => 'normal',
|
||
'createtime' => $now,
|
||
'updatetime' => $now,
|
||
]);
|
||
} else {
|
||
$skillDictId = (int)$exist['id'];
|
||
}
|
||
$old = $existingMap[$cName] ?? null;
|
||
Db::name('master_skill')->insert([
|
||
'master_id' => $row['id'],
|
||
'cat_id' => $old ? (int)$old['cat_id'] : 0,
|
||
'skill_id' => $skillDictId,
|
||
'skill_name' => $cName,
|
||
'category' => $exist && !empty($exist['category']) ? $exist['category'] : '其他技能',
|
||
'is_custom' => 1,
|
||
'cert_image' => $old ? (string)$old['cert_image'] : '',
|
||
'status' => 'normal',
|
||
'createtime' => $old ? (int)$old['createtime'] : $now,
|
||
]);
|
||
}
|
||
|
||
Db::commit();
|
||
} catch (Exception $e) {
|
||
Db::rollback();
|
||
$this->error($e->getMessage());
|
||
}
|
||
|
||
$this->success('技能配置更新成功');
|
||
}
|
||
|
||
$categories = Db::name('skill_category')->where('status', 'normal')->order('weigh desc')->select();
|
||
$allDictSkills = Db::name('skill_dict')->where('status', 'normal')->order('weigh desc')->select();
|
||
|
||
$catTree = [];
|
||
foreach ($categories as $cat) {
|
||
$catTree[$cat['name']] = [];
|
||
}
|
||
foreach ($allDictSkills as $item) {
|
||
$cName = $item['category'];
|
||
if (!isset($catTree[$cName])) {
|
||
$catTree[$cName] = [];
|
||
}
|
||
$catTree[$cName][] = $item;
|
||
}
|
||
|
||
$mySkills = Db::name('master_skill')->where('master_id', $row['id'])->select();
|
||
$selectedSkills = [];
|
||
$customArr = [];
|
||
foreach ($mySkills as $ms) {
|
||
if ((int)$ms['is_custom'] === 1 && !Db::name('skill_dict')->where('name', $ms['skill_name'])->where('source', '<>', 'custom')->count()) {
|
||
$customArr[] = $ms['skill_name'];
|
||
} else {
|
||
$selectedSkills[] = $ms['skill_name'];
|
||
}
|
||
}
|
||
|
||
$this->view->assign('row', $row);
|
||
$this->view->assign('catTree', $catTree);
|
||
$this->view->assign('selectedSkills', $selectedSkills);
|
||
$this->view->assign('customText', implode(' ', $customArr));
|
||
return $this->view->fetch();
|
||
}
|
||
|
||
/**
|
||
* 师傅全景档案画像
|
||
*/
|
||
public function detail($ids = null)
|
||
{
|
||
$row = $this->model->with(['user'])->where('master.id', (int)$ids)->find();
|
||
if (!$row) {
|
||
$this->error(__('No Results were found'));
|
||
}
|
||
|
||
$orderModel = new \app\admin\model\Order;
|
||
$statusMap = $orderModel->getStatusList();
|
||
|
||
// 1. 最近接单记录
|
||
$recentOrders = Db::name('order')
|
||
->where('master_id', $row['id'])
|
||
->order('id desc')
|
||
->limit(8)
|
||
->select();
|
||
foreach ($recentOrders as &$o) {
|
||
$o['status_label'] = $statusMap[(int)$o['status']] ?? ('状态' . $o['status']);
|
||
}
|
||
unset($o);
|
||
|
||
// 2. 提现流水
|
||
$recentWithdraws = Db::name('withdraw')
|
||
->where('master_id', $row['id'])
|
||
->order('id desc')
|
||
->limit(6)
|
||
->select();
|
||
|
||
// 3. 工单佣金结算记录
|
||
$recentSettles = Db::name('settle')
|
||
->alias('s')
|
||
->join('order o', 'o.id = s.order_id', 'LEFT')
|
||
->where('s.master_id', $row['id'])
|
||
->field('s.*, o.order_no, o.service_name')
|
||
->order('s.id desc')
|
||
->limit(6)
|
||
->select();
|
||
|
||
// 4. 客户评价记录
|
||
$recentReviews = Db::name('review')
|
||
->alias('r')
|
||
->join('order o', 'o.id = r.order_id', 'LEFT')
|
||
->join('user u', 'u.id = r.user_id', 'LEFT')
|
||
->where('r.master_id', $row['id'])
|
||
->field('r.*, o.order_no, o.service_name, u.nickname as user_nickname')
|
||
->order('r.id desc')
|
||
->limit(5)
|
||
->select();
|
||
|
||
// 5. 服务技能与认证证书
|
||
$skills = Db::name('master_skill')
|
||
->alias('s')
|
||
->join('service_cat c', 'c.id = s.cat_id', 'LEFT')
|
||
->where('s.master_id', $row['id'])
|
||
->field('s.*, c.name as cat_name')
|
||
->select();
|
||
|
||
// 5.5 技能证书记录
|
||
$certs = Db::name('master_cert')
|
||
->field('id,cert_code,cert_name,is_custom,cert_image,expire_date,is_long_term,status,audit_msg,updatetime')
|
||
->where('master_id', $row['id'])
|
||
->order('id desc')
|
||
->select();
|
||
|
||
// 6. 钱包资金变动流水
|
||
$walletLogs = Db::name('wallet_log')
|
||
->where('user_type', 'master')
|
||
->where('user_id', $row['id'])
|
||
->order('id desc')
|
||
->limit(6)
|
||
->select();
|
||
|
||
// 7. 证件防伪水印安全转换(方案1:腾讯云数据万象动态水印)
|
||
if (!empty($row['idcard_front'])) {
|
||
$row['idcard_front_watermark'] = \app\common\library\TencentVerifyService::getWatermarkUrl($row['idcard_front']);
|
||
}
|
||
if (!empty($row['idcard_back'])) {
|
||
$row['idcard_back_watermark'] = \app\common\library\TencentVerifyService::getWatermarkUrl($row['idcard_back']);
|
||
}
|
||
|
||
$this->view->assign([
|
||
'row' => $row,
|
||
'recentOrders' => $recentOrders,
|
||
'recentWithdraws' => $recentWithdraws,
|
||
'recentSettles' => $recentSettles,
|
||
'recentReviews' => $recentReviews,
|
||
'skills' => $skills,
|
||
'certs' => $certs,
|
||
'walletLogs' => $walletLogs,
|
||
]);
|
||
|
||
return $this->view->fetch();
|
||
}
|
||
}
|