chore: 移除废弃的增项服务模块并升级服务架构

- 删除 Addon 相关控制器、模型、语言包和视图文件
- 新增服务类型支持一口价属性配置
- 新增系统监控、用户资料、订单评价等后端管理页面
- 新增数据库表结构及索引优化脚本
- 更新应用版本号至 1.0.44
- 更新小程序应用标识
- 完善 .gitignore 配置
这个提交包含在:
2026-11-10 16:47:41 +08:00
父节点 e2b0044b64
当前提交 9e83a869e1
共有 60 个文件被更改,包括 5710 次插入 和 1020 次删除

查看文件

@@ -26,7 +26,6 @@ class Service extends Backend
{
parent::_initialize();
$this->model = new \app\admin\model\Service;
$this->view->assign("priceTypeList", $this->model->getPriceTypeList());
$this->view->assign("statusList", $this->model->getStatusList());
// 获取统一服务字典表(fa_service_type,不绑定单一类目,主服务与增项服务同源)
@@ -66,7 +65,7 @@ class Service extends Backend
}
// 一次性批量查询当前页商品的细分规格,避免 N+1 查询
$allSpecs = Db::name('service_spec')
->field('id,service_id,name,description')
->field('id,service_id,name,base_price,description')
->where('service_id', 'in', $serviceIds)
->where('status', 'normal')
->order('weigh desc, id asc')
@@ -77,7 +76,8 @@ class Service extends Backend
if (!isset($specMap[$sid])) {
$specMap[$sid] = [];
}
$specMap[$sid][] = $sp['name'];
$spPrice = (float)$sp['base_price'];
$specMap[$sid][] = $spPrice > 0 ? ($sp['name'] . '(¥' . round($spPrice) . ')') : $sp['name'];
}
foreach ($rows as &$r) {
$sid = (int)$r['id'];
@@ -125,7 +125,7 @@ class Service extends Backend
$serviceId = $this->model->id;
// 处理商品规格列表
$this->syncSpecs($serviceId, $this->request->post('specs/a', []), isset($params['base_price']) ? (float)$params['base_price'] : 0);
$this->syncSpecs($serviceId, $this->request->post('specs/a', []));
Db::commit();
} catch (ValidateException|PDOException|Exception $e) {
@@ -168,7 +168,7 @@ class Service extends Backend
$result = $row->allowField(true)->save($params);
// 同步处理商品规格列表
$this->syncSpecs($row->id, $this->request->post('specs/a', []), isset($params['base_price']) ? (float)$params['base_price'] : (float)$row->base_price);
$this->syncSpecs($row->id, $this->request->post('specs/a', []));
Db::commit();
} catch (ValidateException|PDOException|Exception $e) {
@@ -220,20 +220,75 @@ class Service extends Backend
if (isset($params['service_types_raw'])) {
if (is_string($params['service_types_raw'])) {
$parsedTypes = json_decode($params['service_types_raw'], true) ?: [];
$params['service_types'] = $params['service_types_raw'];
} else {
$parsedTypes = is_array($params['service_types_raw']) ? $params['service_types_raw'] : [];
$params['service_types'] = json_encode($parsedTypes, JSON_UNESCAPED_UNICODE);
}
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'])) {
$params['addons'] = is_string($params['addons_raw']) ? $params['addons_raw'] : json_encode($params['addons_raw'], JSON_UNESCAPED_UNICODE);
$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,商品无需单独填价格
// 自动从关联的主服务(优先默认首选主服务)同步底层 base_price 与 price_type
if (!empty($parsedTypes)) {
$defItem = $parsedTypes[0];
foreach ($parsedTypes as $pt) {
@@ -251,9 +306,9 @@ class Service extends Backend
}
/**
* 同步商品规格型号表(规格不设独立商品价格,仅标识具体施工标的物型号)
* 同步商品规格型号表(支持特殊规格单独设价覆盖,留空或0则沿用商品默认服务价)
*/
protected function syncSpecs($serviceId, $specsInput, $defaultBasePrice = 0.00)
protected function syncSpecs($serviceId, $specsInput)
{
if (empty($serviceId)) return;
$now = time();
@@ -264,6 +319,9 @@ class Service extends Backend
$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++;
@@ -271,7 +329,7 @@ class Service extends Backend
if ($specId > 0) {
Db::name('service_spec')->where('id', $specId)->where('service_id', $serviceId)->update([
'name' => $name,
'base_price' => $defaultBasePrice,
'base_price' => $specPrice,
'description' => $specDesc,
'weigh' => $weigh,
'updatetime' => $now,
@@ -281,7 +339,7 @@ class Service extends Backend
$newId = Db::name('service_spec')->insertGetId([
'service_id' => $serviceId,
'name' => $name,
'base_price' => $defaultBasePrice,
'base_price' => $specPrice,
'description' => $specDesc,
'weigh' => $weigh,
'status' => 'normal',