文件
yjyf/server/application/admin/controller/Service.php
MiAn 9e83a869e1 chore: 移除废弃的增项服务模块并升级服务架构
- 删除 Addon 相关控制器、模型、语言包和视图文件
- 新增服务类型支持一口价属性配置
- 新增系统监控、用户资料、订单评价等后端管理页面
- 新增数据库表结构及索引优化脚本
- 更新应用版本号至 1.0.44
- 更新小程序应用标识
- 完善 .gitignore 配置
2026-11-10 16:47:41 +08:00

360 行
14 KiB
PHP

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

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

<?php
namespace app\admin\controller;
use app\common\controller\Backend;
use think\Db;
use think\exception\PDOException;
use think\exception\ValidateException;
use Exception;
/**
* 商品管理(商品仅定义施工标的物与细分规格,服务价格统一配置在关联的主服务与增项服务上)
*
* @icon fa fa-cubes
*/
class Service extends Backend
{
/**
* Service模型对象
* @var \app\admin\model\Service
*/
protected $model = null;
protected $relationSearch = true;
public function _initialize()
{
parent::_initialize();
$this->model = new \app\admin\model\Service;
$this->view->assign("statusList", $this->model->getStatusList());
// 获取统一服务字典表(fa_service_type,不绑定单一类目,主服务与增项服务同源)
$typesPool = Db::name('service_type')
->where('status', 'normal')
->order('weigh desc, id asc')
->select();
$this->view->assign("typesPool", $typesPool);
}
public function index()
{
$this->request->filter(['strip_tags', 'trim']);
if (false === $this->request->isAjax()) {
$cats = Db::name('service_cat')
->where('status', 'normal')
->order('weigh desc, id asc')
->select();
$this->view->assign('catList', $cats);
return $this->view->fetch();
}
if ($this->request->request('keyField')) {
return $this->selectpage();
}
[$where, $sort, $order, $offset, $limit] = $this->buildparams();
$list = $this->model
->with(['cat'])
->where($where)
->order($sort, $order)
->paginate($limit);
$rows = $list->items();
if (!empty($rows)) {
$serviceIds = [];
foreach ($rows as $r) {
$serviceIds[] = (int)$r['id'];
}
// 一次性批量查询当前页商品的细分规格,避免 N+1 查询
$allSpecs = Db::name('service_spec')
->field('id,service_id,name,base_price,description')
->where('service_id', 'in', $serviceIds)
->where('status', 'normal')
->order('weigh desc, id asc')
->select();
$specMap = [];
foreach ($allSpecs as $sp) {
$sid = (int)$sp['service_id'];
if (!isset($specMap[$sid])) {
$specMap[$sid] = [];
}
$spPrice = (float)$sp['base_price'];
$specMap[$sid][] = $spPrice > 0 ? ($sp['name'] . '(¥' . round($spPrice) . ')') : $sp['name'];
}
foreach ($rows as &$r) {
$sid = (int)$r['id'];
$r['specs_list'] = isset($specMap[$sid]) ? $specMap[$sid] : [];
// 直接在服务端解析为数组返回,防止前端 bootstrap-table escape:true 将 JSON 双引号转义为 &quot; 导致解析失败
$stArr = [];
if (!empty($r['service_types'])) {
$stArr = is_array($r['service_types']) ? $r['service_types'] : (json_decode($r['service_types'], true) ?: []);
}
$adArr = [];
if (!empty($r['addons'])) {
$adArr = is_array($r['addons']) ? $r['addons'] : (json_decode($r['addons'], true) ?: []);
}
$r['service_types_list'] = $stArr;
$r['addons_list'] = $adArr;
}
unset($r);
}
$result = ['total' => $list->total(), 'rows' => $rows];
return json($result);
}
/**
* 添加商品并配置关联服务定价
*/
public function add()
{
if ($this->request->isPost()) {
$params = $this->request->post("row/a");
if ($params) {
$params = $this->preExcludeFields($params);
if ($this->dataLimit && $this->dataLimitFieldAutoFill) {
$params[$this->dataLimitField] = $this->auth->id;
}
$params = $this->formatServiceConfig($params);
$result = false;
Db::startTrans();
try {
$result = $this->model->allowField(true)->save($params);
$serviceId = $this->model->id;
// 处理商品规格列表
$this->syncSpecs($serviceId, $this->request->post('specs/a', []));
Db::commit();
} catch (ValidateException|PDOException|Exception $e) {
Db::rollback();
$this->error($e->getMessage());
}
if ($result !== false) {
$this->success();
} else {
$this->error(__('No rows were inserted'));
}
}
$this->error(__('Parameter %s can not be empty', ''));
}
return $this->view->fetch();
}
/**
* 编辑商品与关联服务定价
*/
public function edit($ids = null)
{
$row = $this->model->get($ids);
if (!$row) {
$this->error(__('No Results were found'));
}
$adminIds = $this->getDataLimitAdminIds();
if (is_array($adminIds) && !in_array($row[$this->dataLimitField], $adminIds)) {
$this->error(__('You have no permission'));
}
if ($this->request->isPost()) {
$params = $this->request->post("row/a");
if ($params) {
$params = $this->preExcludeFields($params);
$params = $this->formatServiceConfig($params);
$result = false;
Db::startTrans();
try {
$result = $row->allowField(true)->save($params);
// 同步处理商品规格列表
$this->syncSpecs($row->id, $this->request->post('specs/a', []));
Db::commit();
} catch (ValidateException|PDOException|Exception $e) {
Db::rollback();
$this->error($e->getMessage());
}
if ($result !== false) {
$this->success();
} else {
$this->error(__('No rows were updated'));
}
}
$this->error(__('Parameter %s can not be empty', ''));
}
// 解析当前商品已关联的主服务与增项服务配置
$serviceTypes = [];
if (!empty($row->service_types)) {
$serviceTypes = is_array($row->service_types) ? $row->service_types : json_decode($row->service_types, true);
}
$addons = [];
if (!empty($row->addons)) {
$addons = is_array($row->addons) ? $row->addons : json_decode($row->addons, true);
}
// 获取已有商品规格
$specs = Db::name('service_spec')
->where('service_id', $row->id)
->where('status', 'normal')
->order('weigh desc, id asc')
->select();
$catName = Db::name('service_cat')->where('id', $row->cat_id)->value('name') ?: '未归类';
$this->view->assign("row", $row);
$this->view->assign("catName", $catName);
$this->view->assign("serviceTypes", $serviceTypes ?: []);
$this->view->assign("assignedAddons", $addons ?: []);
$this->view->assign("specs", $specs ?: []);
return $this->view->fetch();
}
/**
* 格式化商品关联的主服务与增项服务 JSON,并自动提取默认主服务价格用于底层索引兼容
*/
protected function formatServiceConfig($params)
{
$parsedTypes = [];
if (isset($params['service_types_raw'])) {
if (is_string($params['service_types_raw'])) {
$parsedTypes = json_decode($params['service_types_raw'], true) ?: [];
} else {
$parsedTypes = is_array($params['service_types_raw']) ? $params['service_types_raw'] : [];
}
unset($params['service_types_raw']);
if (empty($parsedTypes)) {
throw new ValidateException('请至少开启 1 项主服务');
}
$dictSupportMap = Db::name('service_type')->column('support_fixed', 'code');
$hasDefault = false;
foreach ($parsedTypes as &$pt) {
$code = isset($pt['code']) ? (string)$pt['code'] : '';
$supportFixed = isset($dictSupportMap[$code])
? (int)$dictSupportMap[$code]
: (isset($pt['support_fixed']) ? (int)$pt['support_fixed'] : 1);
$price = ($supportFixed === 1 && isset($pt['price'])) ? round((float)$pt['price'], 2) : 0.00;
$pt['price'] = $price;
$pt['support_fixed'] = $supportFixed;
if ($supportFixed === 1 && !empty($pt['is_default']) && !$hasDefault) {
$pt['is_default'] = 1;
$hasDefault = true;
} else {
$pt['is_default'] = 0;
}
}
unset($pt);
if (!$hasDefault && !empty($parsedTypes)) {
foreach ($parsedTypes as &$ptDef) {
if ((int)$ptDef['support_fixed'] === 1) {
$ptDef['is_default'] = 1;
$hasDefault = true;
break;
}
}
unset($ptDef);
if (!$hasDefault) {
$parsedTypes[0]['is_default'] = 1;
}
}
$params['service_types'] = json_encode(array_values($parsedTypes), JSON_UNESCAPED_UNICODE);
}
if (isset($params['addons_raw'])) {
$parsedAddons = is_string($params['addons_raw'])
? (json_decode($params['addons_raw'], true) ?: [])
: (is_array($params['addons_raw']) ? $params['addons_raw'] : []);
unset($params['addons_raw']);
$cleanAddons = [];
foreach ($parsedAddons as $idx => $ad) {
$adName = isset($ad['name']) ? trim((string)$ad['name']) : '';
$adPrice = isset($ad['price']) ? round((float)$ad['price'], 2) : 0.00;
if ($adName === '' || $adPrice <= 0) {
continue;
}
$cleanAddons[] = [
'id' => isset($ad['id']) ? (int)$ad['id'] : ($idx + 1),
'code' => isset($ad['code']) ? (string)$ad['code'] : ('addon_' . ($idx + 1)),
'name' => $adName,
'unit' => !empty($ad['unit']) ? (string)$ad['unit'] : '份',
'price' => $adPrice,
];
}
$params['addons'] = json_encode($cleanAddons, JSON_UNESCAPED_UNICODE);
}
// 自动从关联的主服务(优先默认首选主服务)同步底层 base_price 与 price_type
if (!empty($parsedTypes)) {
$defItem = $parsedTypes[0];
foreach ($parsedTypes as $pt) {
if (!empty($pt['is_default'])) {
$defItem = $pt;
break;
}
}
$supportFixed = isset($defItem['support_fixed']) ? (int)$defItem['support_fixed'] : 1;
$params['price_type'] = $supportFixed === 1 ? 'fixed' : 'quote';
$params['base_price'] = $supportFixed === 1 ? (float)$defItem['price'] : 0.00;
}
return $params;
}
/**
* 同步商品规格型号表(支持特殊规格单独设价覆盖,留空或0则沿用商品默认服务价)
*/
protected function syncSpecs($serviceId, $specsInput)
{
if (empty($serviceId)) return;
$now = time();
$keepIds = [];
if (!empty($specsInput) && is_array($specsInput)) {
$idx = 0;
foreach ($specsInput as $item) {
$name = trim(isset($item['name']) ? $item['name'] : '');
if ($name === '') continue;
$specDesc = isset($item['description']) ? trim($item['description']) : '';
$specPrice = (isset($item['base_price']) && is_numeric($item['base_price']) && (float)$item['base_price'] > 0)
? round((float)$item['base_price'], 2)
: 0.00;
$specId = isset($item['id']) ? (int)$item['id'] : 0;
$weigh = max(0, 100 - $idx * 5);
$idx++;
if ($specId > 0) {
Db::name('service_spec')->where('id', $specId)->where('service_id', $serviceId)->update([
'name' => $name,
'base_price' => $specPrice,
'description' => $specDesc,
'weigh' => $weigh,
'updatetime' => $now,
]);
$keepIds[] = $specId;
} else {
$newId = Db::name('service_spec')->insertGetId([
'service_id' => $serviceId,
'name' => $name,
'base_price' => $specPrice,
'description' => $specDesc,
'weigh' => $weigh,
'status' => 'normal',
'createtime' => $now,
'updatetime' => $now,
]);
$keepIds[] = $newId;
}
}
}
$delQuery = Db::name('service_spec')->where('service_id', $serviceId);
if (!empty($keepIds)) {
$delQuery->whereNotIn('id', $keepIds);
}
$delQuery->delete();
}
}