diff --git a/YJYF/YJYF.vue b/YJYF/YJYF.vue index d4c66fd..dd60f15 100644 --- a/YJYF/YJYF.vue +++ b/YJYF/YJYF.vue @@ -93,14 +93,14 @@ - + - 下单找师傅教程 - 3 分钟看懂,下单不踩坑 + {{ tutorial.title }} + {{ tutorial.sub }} 立即学习 @@ -108,20 +108,20 @@ - - + + × - 家装季 + {{ banner.badge }} 限时 - 双11 狂欢大促 - 下单立减 · 满额再返券 - 10.23 - 11.15 + {{ banner.title }} + {{ banner.subtitle }} + {{ banner.date_text }} 立即抢购 @@ -230,7 +230,10 @@ const greeting = hh < 6 ? '夜深了' : hh < 11 ? '上午好' : hh < 14 ? '中午好' : hh < 18 ? '下午好' : '晚上好' /* ---------- 服务保障 ---------- */ - const assures = ['平台认证', '先行赔付', '极速上门', '售后无忧'] + /* ---------- 服务保障 / 教程 / Banner(均由后端下发) ---------- */ + const assures = ref(['平台认证', '先行赔付', '极速上门', '售后无忧']) + const banner = ref(null) + const tutorial = ref({ title: '下单找师傅教程', sub: '3 分钟看懂,下单不踩坑' }) /* ---------- 服务图标:纯 CSS 图元,零图片依赖 o=描边框 l=实心线 d=圆形描边 t=三角 @@ -252,6 +255,9 @@ const res = await api.home() cats.value = res.cats || [] badge.value = res.badge || { coupon: 0, unread: 0 } + if (res.assures && res.assures.length) assures.value = res.assures + if (res.banners && res.banners.length) banner.value = res.banners[0] + if (res.tutorial) tutorial.value = res.tutorial } catch (e) {} } diff --git a/db/README_phase5.md b/db/README_phase5.md new file mode 100644 index 0000000..b407200 --- /dev/null +++ b/db/README_phase5.md @@ -0,0 +1,63 @@ +# Phase 5:后端功能模块 ↔ 前端模块数据同源 + +## 1. 数据同源(前端不再写死) +| 前端位置 | 数据来源 | 后台维护入口 | +|---|---|---| +| 首页大促 Banner(角标/标题/副标题/时间) | `fa_banner` 表(`status=normal`,按 weigh 倒序,取第 1 条) | 后台 → 首页运营位 | +| 首页服务保障条(4 项) | `fa_config` 键 `home_assure`,英文逗号分隔 | 后台 → 常规管理 → 系统配置 | +| 首页教程卡(标题/副标题) | `fa_config` 键 `home_tutorial`,格式 `标题|副标题` | 同上 | +| 订单状态提示文案 | `common/library/OrderStatus::tip()` 随接口下发 `status_tip` | 代码内统一维护 | + +接口约定(`/api/home/index`): +```json +{ + "banners": [{"id":1,"badge":"家装季","title":"双11 狂欢大促","subtitle":"下单立减 · 满额再返券","date_text":"10.23 - 11.15","image":"","link":""}], + "assures": ["平台认证","先行赔付","极速上门","售后无忧"], + "tutorial": {"title":"下单找师傅教程","sub":"3 分钟看懂,下单不踩坑"} +} +``` +任一数据缺失/停用都会回落到代码内置兜底文案,前端不会白屏。 + +## 2. 后台管理模块(13 个) +由 FastAdmin 官方生成器产出(controller + model + view + 菜单): + +```bash +cd /www/wwwroot/yjyf.gzyakj.com.cn +PHP=/www/server/php/74/bin/php +for t in fa_banner fa_order fa_master fa_merchant fa_coupon fa_review fa_message \ + fa_service fa_service_cat fa_appeal fa_invoice fa_withdraw fa_settle; do + $PHP think crud -t $t +done +for c in banner order master merchant coupon review message service appeal \ + invoice withdraw settle; do + $PHP think menu -c $c +done +$PHP think menu -c service/cat +``` +> `crud` 只生成文件,**不会**写菜单;必须再执行 `menu` 才会出现在后台左侧导航。 + +## 3. nginx 后台短地址 +`/admin/*` 必须外部 301 到 `/admin.php/*`(内部 rewrite 会让 FastAdmin 依据 REQUEST_URI 生成的 +相对 URL 丢失 `admin.php` 前缀,导致菜单点开 404): + +```nginx +location ~ ^/admin(/.*)?$ { + return 301 /admin.php$1$is_args$args; +} +``` +配置位于 `/www/server/panel/vhost/rewrite/yjyf.gzyakj.com.cn.conf`,改动前已备份为 `.bak.<时间戳>`。 + +## 4. 写库字符集(重要) +服务器 mysql 客户端默认字符集非 utf8mb4,直接用 +`mysql -e "UPDATE ... SET value='中文'"` 会把中文**二次编码**成乱码。务必加: + +```bash +mysql --default-character-set=utf8mb4 -uyjyf -pyjyf yjyf -e "..." +# 或:mysql --default-character-set=utf8mb4 ... < file.sql +``` + +## 5. 验证 +```bash +node tests/phase5_data_sync.js # 16 项:后台改数据 → 前端接口即时反映 +``` +覆盖:服务上下架、Banner 增改与停用兜底、保障条、教程卡、发券/下架券、订单状态提示、后台模块完整性。 diff --git a/db/schema_phase4.sql b/db/schema_phase4.sql new file mode 100644 index 0000000..9f43302 --- /dev/null +++ b/db/schema_phase4.sql @@ -0,0 +1,37 @@ +-- ============================================================================ +-- 优匠优服 · Phase 4 前端配置数据同源 +-- 前端 Banner / 保障条 / 教程卡 由后端下发,后台可管 +-- ============================================================================ +SET NAMES utf8mb4; + +-- 首页运营位(Banner) +CREATE TABLE IF NOT EXISTS `fa_banner` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `badge` varchar(30) NOT NULL DEFAULT '家装季' COMMENT '角标文案', + `title` varchar(100) NOT NULL DEFAULT '' COMMENT '主标题', + `subtitle` varchar(200) NOT NULL DEFAULT '' COMMENT '副标题', + `date_text` varchar(50) NOT NULL DEFAULT '' COMMENT '活动时间文案', + `image` varchar(255) NOT NULL DEFAULT '' COMMENT '运营图(可选,空则用渐变底)', + `link` varchar(255) NOT NULL DEFAULT '' COMMENT '点击跳转', + `weigh` int(11) NOT NULL DEFAULT '0' COMMENT '权重,倒序', + `status` enum('normal','hidden') NOT NULL DEFAULT 'normal', + `createtime` bigint(16) DEFAULT NULL, + `updatetime` bigint(16) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `status` (`status`), + KEY `weigh` (`weigh`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='首页运营位'; + +-- 首页配置项(复用 FastAdmin 的 fa_config,注意该表无 createtime/updatetime 列) +INSERT INTO `fa_config` (`name`,`group`,`title`,`tip`,`type`,`value`) VALUES +('home_assure','basic','首页服务保障','英文逗号分隔,展示为保障条','string','平台认证,先行赔付,极速上门,售后无忧'), +('home_tutorial','basic','首页教程卡','格式:标题|副标题','string','下单找师傅教程|3 分钟看懂,下单不踩坑') +ON DUPLICATE KEY UPDATE `title`=VALUES(`title`),`tip`=VALUES(`tip`),`value`=VALUES(`value`); + +-- 默认运营位(与当前视觉一致) +INSERT INTO `fa_banner` (`id`,`badge`,`title`,`subtitle`,`date_text`,`weigh`,`status`,`createtime`,`updatetime`) VALUES +(1,'家装季','双11 狂欢大促','下单立减 · 满额再返券','10.23 - 11.15',100,'normal',UNIX_TIMESTAMP(),UNIX_TIMESTAMP()) +ON DUPLICATE KEY UPDATE `badge`=VALUES(`badge`),`title`=VALUES(`title`),`subtitle`=VALUES(`subtitle`),`date_text`=VALUES(`date_text`); + +-- 回滚:DROP TABLE IF EXISTS `fa_banner`; +-- DELETE FROM `fa_config` WHERE `name` IN ('home_assure','home_tutorial'); diff --git a/pages/order/detail.vue b/pages/order/detail.vue index 0ff0750..6b36e88 100644 --- a/pages/order/detail.vue +++ b/pages/order/detail.vue @@ -82,13 +82,7 @@ const statusTip = computed(() => { if (!order.value) return ''; - const tips = { - 0: '请尽快完成支付', 1: '已进入接单大厅,等待师傅接单', 2: '师傅报价中,请稍候', - 3: '师傅已接单,将尽快与您联系', 4: '已约定上门时间', 5: '师傅已到达现场', - 6: '服务进行中', 7: '师傅已完工,请确认验收', 8: '订单已完成,感谢您的信任', - 9: '订单已取消', 10: '售后处理中' - }; - return tips[order.value.status] || ''; + return order.value.status_tip || ''; }); async function load() { diff --git a/server/application/admin/controller/Appeal.php b/server/application/admin/controller/Appeal.php new file mode 100644 index 0000000..9605ede --- /dev/null +++ b/server/application/admin/controller/Appeal.php @@ -0,0 +1,38 @@ +model = new \app\admin\model\Appeal; + $this->view->assign("typeList", $this->model->getTypeList()); + $this->view->assign("statusList", $this->model->getStatusList()); + } + + + + /** + * 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法 + * 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑 + * 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改 + */ + + +} diff --git a/server/application/admin/controller/Banner.php b/server/application/admin/controller/Banner.php new file mode 100644 index 0000000..3686e88 --- /dev/null +++ b/server/application/admin/controller/Banner.php @@ -0,0 +1,37 @@ +model = new \app\admin\model\Banner; + $this->view->assign("statusList", $this->model->getStatusList()); + } + + + + /** + * 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法 + * 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑 + * 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改 + */ + + +} diff --git a/server/application/admin/controller/Coupon.php b/server/application/admin/controller/Coupon.php new file mode 100644 index 0000000..6ccd21f --- /dev/null +++ b/server/application/admin/controller/Coupon.php @@ -0,0 +1,38 @@ +model = new \app\admin\model\Coupon; + $this->view->assign("typeList", $this->model->getTypeList()); + $this->view->assign("statusList", $this->model->getStatusList()); + } + + + + /** + * 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法 + * 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑 + * 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改 + */ + + +} diff --git a/server/application/admin/controller/Invoice.php b/server/application/admin/controller/Invoice.php new file mode 100644 index 0000000..8eb8286 --- /dev/null +++ b/server/application/admin/controller/Invoice.php @@ -0,0 +1,38 @@ +model = new \app\admin\model\Invoice; + $this->view->assign("typeList", $this->model->getTypeList()); + $this->view->assign("statusList", $this->model->getStatusList()); + } + + + + /** + * 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法 + * 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑 + * 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改 + */ + + +} diff --git a/server/application/admin/controller/Master.php b/server/application/admin/controller/Master.php new file mode 100644 index 0000000..388dd57 --- /dev/null +++ b/server/application/admin/controller/Master.php @@ -0,0 +1,38 @@ +model = new \app\admin\model\Master; + $this->view->assign("workStatusList", $this->model->getWorkStatusList()); + $this->view->assign("verifyStatusList", $this->model->getVerifyStatusList()); + } + + + + /** + * 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法 + * 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑 + * 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改 + */ + + +} diff --git a/server/application/admin/controller/Merchant.php b/server/application/admin/controller/Merchant.php new file mode 100644 index 0000000..5cad79b --- /dev/null +++ b/server/application/admin/controller/Merchant.php @@ -0,0 +1,38 @@ +model = new \app\admin\model\Merchant; + $this->view->assign("settleTypeList", $this->model->getSettleTypeList()); + $this->view->assign("verifyStatusList", $this->model->getVerifyStatusList()); + } + + + + /** + * 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法 + * 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑 + * 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改 + */ + + +} diff --git a/server/application/admin/controller/Message.php b/server/application/admin/controller/Message.php new file mode 100644 index 0000000..f01a1e7 --- /dev/null +++ b/server/application/admin/controller/Message.php @@ -0,0 +1,38 @@ +model = new \app\admin\model\Message; + $this->view->assign("receiveTypeList", $this->model->getReceiveTypeList()); + $this->view->assign("typeList", $this->model->getTypeList()); + } + + + + /** + * 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法 + * 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑 + * 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改 + */ + + +} diff --git a/server/application/admin/controller/Order.php b/server/application/admin/controller/Order.php new file mode 100644 index 0000000..f0379e6 --- /dev/null +++ b/server/application/admin/controller/Order.php @@ -0,0 +1,37 @@ +model = new \app\admin\model\Order; + $this->view->assign("priceTypeList", $this->model->getPriceTypeList()); + } + + + + /** + * 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法 + * 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑 + * 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改 + */ + + +} diff --git a/server/application/admin/controller/Review.php b/server/application/admin/controller/Review.php new file mode 100644 index 0000000..1326b04 --- /dev/null +++ b/server/application/admin/controller/Review.php @@ -0,0 +1,37 @@ +model = new \app\admin\model\Review; + $this->view->assign("statusList", $this->model->getStatusList()); + } + + + + /** + * 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法 + * 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑 + * 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改 + */ + + +} diff --git a/server/application/admin/controller/Service.php b/server/application/admin/controller/Service.php new file mode 100644 index 0000000..06c2c3a --- /dev/null +++ b/server/application/admin/controller/Service.php @@ -0,0 +1,38 @@ +model = new \app\admin\model\Service; + $this->view->assign("priceTypeList", $this->model->getPriceTypeList()); + $this->view->assign("statusList", $this->model->getStatusList()); + } + + + + /** + * 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法 + * 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑 + * 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改 + */ + + +} diff --git a/server/application/admin/controller/Settle.php b/server/application/admin/controller/Settle.php new file mode 100644 index 0000000..e470101 --- /dev/null +++ b/server/application/admin/controller/Settle.php @@ -0,0 +1,37 @@ +model = new \app\admin\model\Settle; + $this->view->assign("statusList", $this->model->getStatusList()); + } + + + + /** + * 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法 + * 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑 + * 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改 + */ + + +} diff --git a/server/application/admin/controller/Withdraw.php b/server/application/admin/controller/Withdraw.php new file mode 100644 index 0000000..dd93771 --- /dev/null +++ b/server/application/admin/controller/Withdraw.php @@ -0,0 +1,38 @@ +model = new \app\admin\model\Withdraw; + $this->view->assign("channelList", $this->model->getChannelList()); + $this->view->assign("statusList", $this->model->getStatusList()); + } + + + + /** + * 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法 + * 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑 + * 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改 + */ + + +} diff --git a/server/application/admin/controller/service/Cat.php b/server/application/admin/controller/service/Cat.php new file mode 100644 index 0000000..f5af4f4 --- /dev/null +++ b/server/application/admin/controller/service/Cat.php @@ -0,0 +1,37 @@ +model = new \app\admin\model\service\Cat; + $this->view->assign("statusList", $this->model->getStatusList()); + } + + + + /** + * 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法 + * 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑 + * 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改 + */ + + +} diff --git a/server/application/admin/model/Appeal.php b/server/application/admin/model/Appeal.php new file mode 100644 index 0000000..3994738 --- /dev/null +++ b/server/application/admin/model/Appeal.php @@ -0,0 +1,63 @@ + __('Misjudge'), 'review' => __('Review'), 'deduct' => __('Deduct'), 'other' => __('Other')]; + } + + public function getStatusList() + { + return ['wait' => __('Wait'), 'handling' => __('Handling'), 'done' => __('Done'), 'reject' => __('Reject')]; + } + + + public function getTypeTextAttr($value, $data) + { + $value = $value ?: ($data['type'] ?? ''); + $list = $this->getTypeList(); + return $list[$value] ?? ''; + } + + + public function getStatusTextAttr($value, $data) + { + $value = $value ?: ($data['status'] ?? ''); + $list = $this->getStatusList(); + return $list[$value] ?? ''; + } + + + + +} diff --git a/server/application/admin/model/Banner.php b/server/application/admin/model/Banner.php new file mode 100644 index 0000000..7157399 --- /dev/null +++ b/server/application/admin/model/Banner.php @@ -0,0 +1,59 @@ +getPk(); + $row->getQuery()->where($pk, $row[$pk])->update(['weigh' => $row[$pk]]); + } + }); + } + + + public function getStatusList() + { + return ['normal' => __('Normal'), 'hidden' => __('Hidden')]; + } + + + public function getStatusTextAttr($value, $data) + { + $value = $value ?: ($data['status'] ?? ''); + $list = $this->getStatusList(); + return $list[$value] ?? ''; + } + + + + +} diff --git a/server/application/admin/model/Coupon.php b/server/application/admin/model/Coupon.php new file mode 100644 index 0000000..f2f7bd0 --- /dev/null +++ b/server/application/admin/model/Coupon.php @@ -0,0 +1,87 @@ + __('Cash'), 'discount' => __('Discount')]; + } + + public function getStatusList() + { + return ['normal' => __('Normal'), 'hidden' => __('Hidden')]; + } + + + public function getTypeTextAttr($value, $data) + { + $value = $value ?: ($data['type'] ?? ''); + $list = $this->getTypeList(); + return $list[$value] ?? ''; + } + + + public function getStartTimeTextAttr($value, $data) + { + $value = $value ?: ($data['start_time'] ?? ''); + return is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value; + } + + + public function getEndTimeTextAttr($value, $data) + { + $value = $value ?: ($data['end_time'] ?? ''); + return is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value; + } + + + public function getStatusTextAttr($value, $data) + { + $value = $value ?: ($data['status'] ?? ''); + $list = $this->getStatusList(); + return $list[$value] ?? ''; + } + + protected function setStartTimeAttr($value) + { + return $value === '' ? null : ($value && !is_numeric($value) ? strtotime($value) : $value); + } + + protected function setEndTimeAttr($value) + { + return $value === '' ? null : ($value && !is_numeric($value) ? strtotime($value) : $value); + } + + +} diff --git a/server/application/admin/model/Invoice.php b/server/application/admin/model/Invoice.php new file mode 100644 index 0000000..31bee8a --- /dev/null +++ b/server/application/admin/model/Invoice.php @@ -0,0 +1,63 @@ + __('Normal'), 'special' => __('Special')]; + } + + public function getStatusList() + { + return ['wait' => __('Wait'), 'issued' => __('Issued'), 'reject' => __('Reject')]; + } + + + public function getTypeTextAttr($value, $data) + { + $value = $value ?: ($data['type'] ?? ''); + $list = $this->getTypeList(); + return $list[$value] ?? ''; + } + + + public function getStatusTextAttr($value, $data) + { + $value = $value ?: ($data['status'] ?? ''); + $list = $this->getStatusList(); + return $list[$value] ?? ''; + } + + + + +} diff --git a/server/application/admin/model/Master.php b/server/application/admin/model/Master.php new file mode 100644 index 0000000..41cdf5d --- /dev/null +++ b/server/application/admin/model/Master.php @@ -0,0 +1,63 @@ + __('Off'), 'on' => __('On')]; + } + + public function getVerifyStatusList() + { + return ['pending' => __('Pending'), 'pass' => __('Pass'), 'reject' => __('Reject')]; + } + + + public function getWorkStatusTextAttr($value, $data) + { + $value = $value ?: ($data['work_status'] ?? ''); + $list = $this->getWorkStatusList(); + return $list[$value] ?? ''; + } + + + public function getVerifyStatusTextAttr($value, $data) + { + $value = $value ?: ($data['verify_status'] ?? ''); + $list = $this->getVerifyStatusList(); + return $list[$value] ?? ''; + } + + + + +} diff --git a/server/application/admin/model/Merchant.php b/server/application/admin/model/Merchant.php new file mode 100644 index 0000000..2c24780 --- /dev/null +++ b/server/application/admin/model/Merchant.php @@ -0,0 +1,63 @@ + __('Prepay'), 'monthly' => __('Monthly')]; + } + + public function getVerifyStatusList() + { + return ['pending' => __('Pending'), 'pass' => __('Pass'), 'reject' => __('Reject')]; + } + + + public function getSettleTypeTextAttr($value, $data) + { + $value = $value ?: ($data['settle_type'] ?? ''); + $list = $this->getSettleTypeList(); + return $list[$value] ?? ''; + } + + + public function getVerifyStatusTextAttr($value, $data) + { + $value = $value ?: ($data['verify_status'] ?? ''); + $list = $this->getVerifyStatusList(); + return $list[$value] ?? ''; + } + + + + +} diff --git a/server/application/admin/model/Message.php b/server/application/admin/model/Message.php new file mode 100644 index 0000000..25232da --- /dev/null +++ b/server/application/admin/model/Message.php @@ -0,0 +1,63 @@ + __('User'), 'master' => __('Master')]; + } + + public function getTypeList() + { + return ['order' => __('Order'), 'system' => __('System'), 'audit' => __('Audit'), 'remind' => __('Remind')]; + } + + + public function getReceiveTypeTextAttr($value, $data) + { + $value = $value ?: ($data['receive_type'] ?? ''); + $list = $this->getReceiveTypeList(); + return $list[$value] ?? ''; + } + + + public function getTypeTextAttr($value, $data) + { + $value = $value ?: ($data['type'] ?? ''); + $list = $this->getTypeList(); + return $list[$value] ?? ''; + } + + + + +} diff --git a/server/application/admin/model/Order.php b/server/application/admin/model/Order.php new file mode 100644 index 0000000..57bff45 --- /dev/null +++ b/server/application/admin/model/Order.php @@ -0,0 +1,151 @@ + __('Fixed'), 'quote' => __('Quote'), 'range' => __('Range')]; + } + + + public function getPriceTypeTextAttr($value, $data) + { + $value = $value ?: ($data['price_type'] ?? ''); + $list = $this->getPriceTypeList(); + return $list[$value] ?? ''; + } + + + public function getAppointTimeTextAttr($value, $data) + { + $value = $value ?: ($data['appoint_time'] ?? ''); + return is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value; + } + + + public function getPayTimeTextAttr($value, $data) + { + $value = $value ?: ($data['pay_time'] ?? ''); + return is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value; + } + + + public function getAcceptTimeTextAttr($value, $data) + { + $value = $value ?: ($data['accept_time'] ?? ''); + return is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value; + } + + + public function getContactTimeTextAttr($value, $data) + { + $value = $value ?: ($data['contact_time'] ?? ''); + return is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value; + } + + + public function getArriveTimeTextAttr($value, $data) + { + $value = $value ?: ($data['arrive_time'] ?? ''); + return is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value; + } + + + public function getStartTimeTextAttr($value, $data) + { + $value = $value ?: ($data['start_time'] ?? ''); + return is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value; + } + + + public function getFinishTimeTextAttr($value, $data) + { + $value = $value ?: ($data['finish_time'] ?? ''); + return is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value; + } + + + public function getVerifyTimeTextAttr($value, $data) + { + $value = $value ?: ($data['verify_time'] ?? ''); + return is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value; + } + + protected function setAppointTimeAttr($value) + { + return $value === '' ? null : ($value && !is_numeric($value) ? strtotime($value) : $value); + } + + protected function setPayTimeAttr($value) + { + return $value === '' ? null : ($value && !is_numeric($value) ? strtotime($value) : $value); + } + + protected function setAcceptTimeAttr($value) + { + return $value === '' ? null : ($value && !is_numeric($value) ? strtotime($value) : $value); + } + + protected function setContactTimeAttr($value) + { + return $value === '' ? null : ($value && !is_numeric($value) ? strtotime($value) : $value); + } + + protected function setArriveTimeAttr($value) + { + return $value === '' ? null : ($value && !is_numeric($value) ? strtotime($value) : $value); + } + + protected function setStartTimeAttr($value) + { + return $value === '' ? null : ($value && !is_numeric($value) ? strtotime($value) : $value); + } + + protected function setFinishTimeAttr($value) + { + return $value === '' ? null : ($value && !is_numeric($value) ? strtotime($value) : $value); + } + + protected function setVerifyTimeAttr($value) + { + return $value === '' ? null : ($value && !is_numeric($value) ? strtotime($value) : $value); + } + + +} diff --git a/server/application/admin/model/Review.php b/server/application/admin/model/Review.php new file mode 100644 index 0000000..87da356 --- /dev/null +++ b/server/application/admin/model/Review.php @@ -0,0 +1,49 @@ + __('Normal'), 'hidden' => __('Hidden')]; + } + + + public function getStatusTextAttr($value, $data) + { + $value = $value ?: ($data['status'] ?? ''); + $list = $this->getStatusList(); + return $list[$value] ?? ''; + } + + + + +} diff --git a/server/application/admin/model/Service.php b/server/application/admin/model/Service.php new file mode 100644 index 0000000..6e6424b --- /dev/null +++ b/server/application/admin/model/Service.php @@ -0,0 +1,73 @@ +getPk(); + $row->getQuery()->where($pk, $row[$pk])->update(['weigh' => $row[$pk]]); + } + }); + } + + + public function getPriceTypeList() + { + return ['fixed' => __('Fixed'), 'quote' => __('Quote'), 'range' => __('Range')]; + } + + public function getStatusList() + { + return ['normal' => __('Normal'), 'hidden' => __('Hidden')]; + } + + + public function getPriceTypeTextAttr($value, $data) + { + $value = $value ?: ($data['price_type'] ?? ''); + $list = $this->getPriceTypeList(); + return $list[$value] ?? ''; + } + + + public function getStatusTextAttr($value, $data) + { + $value = $value ?: ($data['status'] ?? ''); + $list = $this->getStatusList(); + return $list[$value] ?? ''; + } + + + + +} diff --git a/server/application/admin/model/Settle.php b/server/application/admin/model/Settle.php new file mode 100644 index 0000000..1779d8e --- /dev/null +++ b/server/application/admin/model/Settle.php @@ -0,0 +1,60 @@ + __('Wait'), 'settled' => __('Settled'), 'offline' => __('Offline')]; + } + + + public function getStatusTextAttr($value, $data) + { + $value = $value ?: ($data['status'] ?? ''); + $list = $this->getStatusList(); + return $list[$value] ?? ''; + } + + + public function getSettleTimeTextAttr($value, $data) + { + $value = $value ?: ($data['settle_time'] ?? ''); + return is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value; + } + + protected function setSettleTimeAttr($value) + { + return $value === '' ? null : ($value && !is_numeric($value) ? strtotime($value) : $value); + } + + +} diff --git a/server/application/admin/model/Withdraw.php b/server/application/admin/model/Withdraw.php new file mode 100644 index 0000000..094781b --- /dev/null +++ b/server/application/admin/model/Withdraw.php @@ -0,0 +1,63 @@ + __('Wechat'), 'bank' => __('Bank')]; + } + + public function getStatusList() + { + return ['wait' => __('Wait'), 'pass' => __('Pass'), 'reject' => __('Reject'), 'done' => __('Done')]; + } + + + public function getChannelTextAttr($value, $data) + { + $value = $value ?: ($data['channel'] ?? ''); + $list = $this->getChannelList(); + return $list[$value] ?? ''; + } + + + public function getStatusTextAttr($value, $data) + { + $value = $value ?: ($data['status'] ?? ''); + $list = $this->getStatusList(); + return $list[$value] ?? ''; + } + + + + +} diff --git a/server/application/admin/view/appeal/add.html b/server/application/admin/view/appeal/add.html new file mode 100644 index 0000000..aebac76 --- /dev/null +++ b/server/application/admin/view/appeal/add.html @@ -0,0 +1,71 @@ + + + + {:__('Master_id')}: + + + + + + {:__('Order_id')}: + + + + + + {:__('Type')}: + + + + {foreach name="typeList" item="vo"} + {$vo} + {/foreach} + + + + + + {:__('Content')}: + + + + + + {:__('Images')}: + + + + + {:__('Upload')} + {:__('Choose')} + + + + + + + + {:__('Status')}: + + + + {foreach name="statusList" item="vo"} + {$vo} + {/foreach} + + + + + + {:__('Reply')}: + + + + + + diff --git a/server/application/admin/view/appeal/edit.html b/server/application/admin/view/appeal/edit.html new file mode 100644 index 0000000..1082f0a --- /dev/null +++ b/server/application/admin/view/appeal/edit.html @@ -0,0 +1,71 @@ + + + + {:__('Master_id')}: + + + + + + {:__('Order_id')}: + + + + + + {:__('Type')}: + + + + {foreach name="typeList" item="vo"} + {$vo} + {/foreach} + + + + + + {:__('Content')}: + + + + + + {:__('Images')}: + + + + + {:__('Upload')} + {:__('Choose')} + + + + + + + + {:__('Status')}: + + + + {foreach name="statusList" item="vo"} + {$vo} + {/foreach} + + + + + + {:__('Reply')}: + + + + + + diff --git a/server/application/admin/view/appeal/index.html b/server/application/admin/view/appeal/index.html new file mode 100644 index 0000000..5559424 --- /dev/null +++ b/server/application/admin/view/appeal/index.html @@ -0,0 +1,46 @@ + + + + {:build_heading(null,FALSE)} + + {:__('All')} + {foreach name="statusList" item="vo"} + {$vo} + {/foreach} + + + + + + + + + + + {:__('Add')} + {:__('Edit')} + {:__('Delete')} + + + + {:__('More')} + + {foreach name="statusList" item="vo"} + {:__('Set status to ' . $key)} + {/foreach} + + + + + + + + + + + + + diff --git a/server/application/admin/view/banner/add.html b/server/application/admin/view/banner/add.html new file mode 100644 index 0000000..5c2bc53 --- /dev/null +++ b/server/application/admin/view/banner/add.html @@ -0,0 +1,71 @@ + + + + {:__('Badge')}: + + + + + + {:__('Title')}: + + + + + + {:__('Subtitle')}: + + + + + + {:__('Date_text')}: + + + + + + {:__('Image')}: + + + + + {:__('Upload')} + {:__('Choose')} + + + + + + + + {:__('Link')}: + + + + + + {:__('Weigh')}: + + + + + + {:__('Status')}: + + + + {foreach name="statusList" item="vo"} + {$vo} + {/foreach} + + + + + + diff --git a/server/application/admin/view/banner/edit.html b/server/application/admin/view/banner/edit.html new file mode 100644 index 0000000..64e98f3 --- /dev/null +++ b/server/application/admin/view/banner/edit.html @@ -0,0 +1,71 @@ + + + + {:__('Badge')}: + + + + + + {:__('Title')}: + + + + + + {:__('Subtitle')}: + + + + + + {:__('Date_text')}: + + + + + + {:__('Image')}: + + + + + {:__('Upload')} + {:__('Choose')} + + + + + + + + {:__('Link')}: + + + + + + {:__('Weigh')}: + + + + + + {:__('Status')}: + + + + {foreach name="statusList" item="vo"} + {$vo} + {/foreach} + + + + + + diff --git a/server/application/admin/view/banner/index.html b/server/application/admin/view/banner/index.html new file mode 100644 index 0000000..e00be6c --- /dev/null +++ b/server/application/admin/view/banner/index.html @@ -0,0 +1,46 @@ + + + + {:build_heading(null,FALSE)} + + {:__('All')} + {foreach name="statusList" item="vo"} + {$vo} + {/foreach} + + + + + + + + + + + {:__('Add')} + {:__('Edit')} + {:__('Delete')} + + + + {:__('More')} + + {foreach name="statusList" item="vo"} + {:__('Set status to ' . $key)} + {/foreach} + + + + + + + + + + + + + diff --git a/server/application/admin/view/coupon/add.html b/server/application/admin/view/coupon/add.html new file mode 100644 index 0000000..cf3dd7b --- /dev/null +++ b/server/application/admin/view/coupon/add.html @@ -0,0 +1,81 @@ + + + + {:__('Name')}: + + + + + + {:__('Type')}: + + + + {foreach name="typeList" item="vo"} + {$vo} + {/foreach} + + + + + + {:__('Amount')}: + + + + + + {:__('Discount')}: + + + + + + {:__('Min_amount')}: + + + + + + {:__('Total')}: + + + + + + {:__('Received')}: + + + + + + {:__('Start_time')}: + + + + + + {:__('End_time')}: + + + + + + {:__('Status')}: + + + + {foreach name="statusList" item="vo"} + {$vo} + {/foreach} + + + + + + diff --git a/server/application/admin/view/coupon/edit.html b/server/application/admin/view/coupon/edit.html new file mode 100644 index 0000000..87f05d4 --- /dev/null +++ b/server/application/admin/view/coupon/edit.html @@ -0,0 +1,81 @@ + + + + {:__('Name')}: + + + + + + {:__('Type')}: + + + + {foreach name="typeList" item="vo"} + {$vo} + {/foreach} + + + + + + {:__('Amount')}: + + + + + + {:__('Discount')}: + + + + + + {:__('Min_amount')}: + + + + + + {:__('Total')}: + + + + + + {:__('Received')}: + + + + + + {:__('Start_time')}: + + + + + + {:__('End_time')}: + + + + + + {:__('Status')}: + + + + {foreach name="statusList" item="vo"} + {$vo} + {/foreach} + + + + + + diff --git a/server/application/admin/view/coupon/index.html b/server/application/admin/view/coupon/index.html new file mode 100644 index 0000000..c9e0a13 --- /dev/null +++ b/server/application/admin/view/coupon/index.html @@ -0,0 +1,46 @@ + + + + {:build_heading(null,FALSE)} + + {:__('All')} + {foreach name="statusList" item="vo"} + {$vo} + {/foreach} + + + + + + + + + + + {:__('Add')} + {:__('Edit')} + {:__('Delete')} + + + + {:__('More')} + + {foreach name="statusList" item="vo"} + {:__('Set status to ' . $key)} + {/foreach} + + + + + + + + + + + + + diff --git a/server/application/admin/view/invoice/add.html b/server/application/admin/view/invoice/add.html new file mode 100644 index 0000000..5dc6726 --- /dev/null +++ b/server/application/admin/view/invoice/add.html @@ -0,0 +1,81 @@ + + + + {:__('Merchant_id')}: + + + + + + {:__('User_id')}: + + + + + + {:__('Amount')}: + + + + + + {:__('Type')}: + + + + {foreach name="typeList" item="vo"} + {$vo} + {/foreach} + + + + + + {:__('Title')}: + + + + + + {:__('Tax_no')}: + + + + + + {:__('Email')}: + + + + + + {:__('Remark')}: + + + + + + {:__('Status')}: + + + + {foreach name="statusList" item="vo"} + {$vo} + {/foreach} + + + + + + {:__('Invoice_no')}: + + + + + + diff --git a/server/application/admin/view/invoice/edit.html b/server/application/admin/view/invoice/edit.html new file mode 100644 index 0000000..022b4a7 --- /dev/null +++ b/server/application/admin/view/invoice/edit.html @@ -0,0 +1,81 @@ + + + + {:__('Merchant_id')}: + + + + + + {:__('User_id')}: + + + + + + {:__('Amount')}: + + + + + + {:__('Type')}: + + + + {foreach name="typeList" item="vo"} + {$vo} + {/foreach} + + + + + + {:__('Title')}: + + + + + + {:__('Tax_no')}: + + + + + + {:__('Email')}: + + + + + + {:__('Remark')}: + + + + + + {:__('Status')}: + + + + {foreach name="statusList" item="vo"} + {$vo} + {/foreach} + + + + + + {:__('Invoice_no')}: + + + + + + diff --git a/server/application/admin/view/invoice/index.html b/server/application/admin/view/invoice/index.html new file mode 100644 index 0000000..346a2ff --- /dev/null +++ b/server/application/admin/view/invoice/index.html @@ -0,0 +1,46 @@ + + + + {:build_heading(null,FALSE)} + + {:__('All')} + {foreach name="statusList" item="vo"} + {$vo} + {/foreach} + + + + + + + + + + + {:__('Add')} + {:__('Edit')} + {:__('Delete')} + + + + {:__('More')} + + {foreach name="statusList" item="vo"} + {:__('Set status to ' . $key)} + {/foreach} + + + + + + + + + + + + + diff --git a/server/application/admin/view/master/add.html b/server/application/admin/view/master/add.html new file mode 100644 index 0000000..220e7fd --- /dev/null +++ b/server/application/admin/view/master/add.html @@ -0,0 +1,169 @@ + + + + {:__('User_id')}: + + + + + + {:__('Realname')}: + + + + + + {:__('Mobile')}: + + + + + + {:__('Idcard_no')}: + + + + + + {:__('Idcard_front')}: + + + + + + {:__('Idcard_back')}: + + + + + + {:__('Avatar')}: + + + + + {:__('Upload')} + {:__('Choose')} + + + + + + + + {:__('Cert_images')}: + + + + + {:__('Upload')} + {:__('Choose')} + + + + + + + + {:__('City')}: + + + + + + {:__('Lng')}: + + + + + + {:__('Lat')}: + + + + + + {:__('Serve_radius')}: + + + + + + {:__('Level')}: + + + + + + {:__('Score')}: + + + + + + {:__('Order_count')}: + + + + + + {:__('Accept_rate')}: + + + + + + {:__('Ontime_rate')}: + + + + + + {:__('Work_status')}: + + + + {foreach name="workStatusList" item="vo"} + {$vo} + {/foreach} + + + + + + {:__('Verify_status')}: + + + + {foreach name="verifyStatusList" item="vo"} + {$vo} + {/foreach} + + + + + + {:__('Audit_msg')}: + + + + + + {:__('Money')}: + + + + + + {:__('Frozen_money')}: + + + + + + diff --git a/server/application/admin/view/master/edit.html b/server/application/admin/view/master/edit.html new file mode 100644 index 0000000..797ec7b --- /dev/null +++ b/server/application/admin/view/master/edit.html @@ -0,0 +1,169 @@ + + + + {:__('User_id')}: + + + + + + {:__('Realname')}: + + + + + + {:__('Mobile')}: + + + + + + {:__('Idcard_no')}: + + + + + + {:__('Idcard_front')}: + + + + + + {:__('Idcard_back')}: + + + + + + {:__('Avatar')}: + + + + + {:__('Upload')} + {:__('Choose')} + + + + + + + + {:__('Cert_images')}: + + + + + {:__('Upload')} + {:__('Choose')} + + + + + + + + {:__('City')}: + + + + + + {:__('Lng')}: + + + + + + {:__('Lat')}: + + + + + + {:__('Serve_radius')}: + + + + + + {:__('Level')}: + + + + + + {:__('Score')}: + + + + + + {:__('Order_count')}: + + + + + + {:__('Accept_rate')}: + + + + + + {:__('Ontime_rate')}: + + + + + + {:__('Work_status')}: + + + + {foreach name="workStatusList" item="vo"} + {$vo} + {/foreach} + + + + + + {:__('Verify_status')}: + + + + {foreach name="verifyStatusList" item="vo"} + {$vo} + {/foreach} + + + + + + {:__('Audit_msg')}: + + + + + + {:__('Money')}: + + + + + + {:__('Frozen_money')}: + + + + + + diff --git a/server/application/admin/view/master/index.html b/server/application/admin/view/master/index.html new file mode 100644 index 0000000..f489af5 --- /dev/null +++ b/server/application/admin/view/master/index.html @@ -0,0 +1,29 @@ + + {:build_heading()} + + + + + + + + {:__('Add')} + {:__('Edit')} + {:__('Delete')} + + + + + + + + + + + + + + diff --git a/server/application/admin/view/merchant/add.html b/server/application/admin/view/merchant/add.html new file mode 100644 index 0000000..49efbda --- /dev/null +++ b/server/application/admin/view/merchant/add.html @@ -0,0 +1,149 @@ + + + + {:__('User_id')}: + + + + + + {:__('Name')}: + + + + + + {:__('License_no')}: + + + + + + {:__('License_image')}: + + + + + {:__('Upload')} + {:__('Choose')} + + + + + + + + {:__('Legal_person')}: + + + + + + {:__('Contact')}: + + + + + + {:__('Mobile')}: + + + + + + {:__('Province')}: + + + + + + {:__('City')}: + + + + + + {:__('District')}: + + + + + + {:__('Address')}: + + + + + + {:__('Invoice_title')}: + + + + + + {:__('Invoice_tax_no')}: + + + + + + {:__('Invoice_bank')}: + + + + + + {:__('Invoice_account')}: + + + + + + {:__('Settle_type')}: + + + + {foreach name="settleTypeList" item="vo"} + {$vo} + {/foreach} + + + + + + {:__('Verify_status')}: + + + + {foreach name="verifyStatusList" item="vo"} + {$vo} + {/foreach} + + + + + + {:__('Audit_msg')}: + + + + + + {:__('Order_count')}: + + + + + + {:__('Total_amount')}: + + + + + + diff --git a/server/application/admin/view/merchant/edit.html b/server/application/admin/view/merchant/edit.html new file mode 100644 index 0000000..8cd37ff --- /dev/null +++ b/server/application/admin/view/merchant/edit.html @@ -0,0 +1,149 @@ + + + + {:__('User_id')}: + + + + + + {:__('Name')}: + + + + + + {:__('License_no')}: + + + + + + {:__('License_image')}: + + + + + {:__('Upload')} + {:__('Choose')} + + + + + + + + {:__('Legal_person')}: + + + + + + {:__('Contact')}: + + + + + + {:__('Mobile')}: + + + + + + {:__('Province')}: + + + + + + {:__('City')}: + + + + + + {:__('District')}: + + + + + + {:__('Address')}: + + + + + + {:__('Invoice_title')}: + + + + + + {:__('Invoice_tax_no')}: + + + + + + {:__('Invoice_bank')}: + + + + + + {:__('Invoice_account')}: + + + + + + {:__('Settle_type')}: + + + + {foreach name="settleTypeList" item="vo"} + {$vo} + {/foreach} + + + + + + {:__('Verify_status')}: + + + + {foreach name="verifyStatusList" item="vo"} + {$vo} + {/foreach} + + + + + + {:__('Audit_msg')}: + + + + + + {:__('Order_count')}: + + + + + + {:__('Total_amount')}: + + + + + + diff --git a/server/application/admin/view/merchant/index.html b/server/application/admin/view/merchant/index.html new file mode 100644 index 0000000..f34f0d7 --- /dev/null +++ b/server/application/admin/view/merchant/index.html @@ -0,0 +1,29 @@ + + {:build_heading()} + + + + + + + + {:__('Add')} + {:__('Edit')} + {:__('Delete')} + + + + + + + + + + + + + + diff --git a/server/application/admin/view/message/add.html b/server/application/admin/view/message/add.html new file mode 100644 index 0000000..792ecc2 --- /dev/null +++ b/server/application/admin/view/message/add.html @@ -0,0 +1,63 @@ + + + + {:__('Receive_type')}: + + + + {foreach name="receiveTypeList" item="vo"} + {$vo} + {/foreach} + + + + + + {:__('Receive_id')}: + + + + + + {:__('Type')}: + + + + {foreach name="typeList" item="vo"} + {$vo} + {/foreach} + + + + + + {:__('Title')}: + + + + + + {:__('Content')}: + + + + + + {:__('Order_id')}: + + + + + + {:__('Is_read')}: + + + + + + diff --git a/server/application/admin/view/message/edit.html b/server/application/admin/view/message/edit.html new file mode 100644 index 0000000..b5349d8 --- /dev/null +++ b/server/application/admin/view/message/edit.html @@ -0,0 +1,63 @@ + + + + {:__('Receive_type')}: + + + + {foreach name="receiveTypeList" item="vo"} + {$vo} + {/foreach} + + + + + + {:__('Receive_id')}: + + + + + + {:__('Type')}: + + + + {foreach name="typeList" item="vo"} + {$vo} + {/foreach} + + + + + + {:__('Title')}: + + + + + + {:__('Content')}: + + + + + + {:__('Order_id')}: + + + + + + {:__('Is_read')}: + + + + + + diff --git a/server/application/admin/view/message/index.html b/server/application/admin/view/message/index.html new file mode 100644 index 0000000..5b18eb1 --- /dev/null +++ b/server/application/admin/view/message/index.html @@ -0,0 +1,29 @@ + + {:build_heading()} + + + + + + + + {:__('Add')} + {:__('Edit')} + {:__('Delete')} + + + + + + + + + + + + + + diff --git a/server/application/admin/view/order/add.html b/server/application/admin/view/order/add.html new file mode 100644 index 0000000..31d82b2 --- /dev/null +++ b/server/application/admin/view/order/add.html @@ -0,0 +1,325 @@ + + + + {:__('Order_no')}: + + + + + + {:__('User_id')}: + + + + + + {:__('Master_id')}: + + + + + + {:__('Cat_id')}: + + + + + + {:__('Service_id')}: + + + + + + {:__('Service_name')}: + + + + + + {:__('Service_image')}: + + + + + {:__('Upload')} + {:__('Choose')} + + + + + + + + {:__('Price_type')}: + + + + {foreach name="priceTypeList" item="vo"} + {$vo} + {/foreach} + + + + + + {:__('Goods_images')}: + + + + + {:__('Upload')} + {:__('Choose')} + + + + + + + + {:__('Goods_model')}: + + + + + + {:__('Goods_num')}: + + + + + + {:__('Floor')}: + + + + + + {:__('Has_elevator')}: + + + + + + {:__('Urgent')}: + + + + + + {:__('Remark')}: + + + + + + {:__('Contact')}: + + + + + + {:__('Mobile')}: + + + + + + {:__('Province')}: + + + + + + {:__('City')}: + + + + + + {:__('District')}: + + + + + + {:__('Address')}: + + + + + + {:__('Lng')}: + + + + + + {:__('Lat')}: + + + + + + {:__('Appoint_time')}: + + + + + + {:__('Base_amount')}: + + + + + + {:__('Floor_amount')}: + + + + + + {:__('Distance_amount')}: + + + + + + {:__('Urgent_amount')}: + + + + + + {:__('Discount_amount')}: + + + + + + {:__('Total_amount')}: + + + + + + {:__('Pay_amount')}: + + + + + + {:__('Status')}: + + + + + + {:__('Pay_status')}: + + + + + + {:__('Pay_type')}: + + + + + + {:__('Pay_time')}: + + + + + + {:__('Transaction_id')}: + + + + + + {:__('Coupon_id')}: + + + + + + {:__('Accept_time')}: + + + + + + {:__('Contact_time')}: + + + + + + {:__('Arrive_time')}: + + + + + + {:__('Start_time')}: + + + + + + {:__('Finish_time')}: + + + + + + {:__('Verify_time')}: + + + + + + {:__('Verify_code')}: + + + + + + {:__('Settle_status')}: + + + + + + {:__('Settle_amount')}: + + + + + + {:__('Cancel_reason')}: + + + + + + {:__('Refund_amount')}: + + + + + + {:__('Is_review')}: + + + + + + diff --git a/server/application/admin/view/order/edit.html b/server/application/admin/view/order/edit.html new file mode 100644 index 0000000..81d3ac0 --- /dev/null +++ b/server/application/admin/view/order/edit.html @@ -0,0 +1,325 @@ + + + + {:__('Order_no')}: + + + + + + {:__('User_id')}: + + + + + + {:__('Master_id')}: + + + + + + {:__('Cat_id')}: + + + + + + {:__('Service_id')}: + + + + + + {:__('Service_name')}: + + + + + + {:__('Service_image')}: + + + + + {:__('Upload')} + {:__('Choose')} + + + + + + + + {:__('Price_type')}: + + + + {foreach name="priceTypeList" item="vo"} + {$vo} + {/foreach} + + + + + + {:__('Goods_images')}: + + + + + {:__('Upload')} + {:__('Choose')} + + + + + + + + {:__('Goods_model')}: + + + + + + {:__('Goods_num')}: + + + + + + {:__('Floor')}: + + + + + + {:__('Has_elevator')}: + + + + + + {:__('Urgent')}: + + + + + + {:__('Remark')}: + + + + + + {:__('Contact')}: + + + + + + {:__('Mobile')}: + + + + + + {:__('Province')}: + + + + + + {:__('City')}: + + + + + + {:__('District')}: + + + + + + {:__('Address')}: + + + + + + {:__('Lng')}: + + + + + + {:__('Lat')}: + + + + + + {:__('Appoint_time')}: + + + + + + {:__('Base_amount')}: + + + + + + {:__('Floor_amount')}: + + + + + + {:__('Distance_amount')}: + + + + + + {:__('Urgent_amount')}: + + + + + + {:__('Discount_amount')}: + + + + + + {:__('Total_amount')}: + + + + + + {:__('Pay_amount')}: + + + + + + {:__('Status')}: + + + + + + {:__('Pay_status')}: + + + + + + {:__('Pay_type')}: + + + + + + {:__('Pay_time')}: + + + + + + {:__('Transaction_id')}: + + + + + + {:__('Coupon_id')}: + + + + + + {:__('Accept_time')}: + + + + + + {:__('Contact_time')}: + + + + + + {:__('Arrive_time')}: + + + + + + {:__('Start_time')}: + + + + + + {:__('Finish_time')}: + + + + + + {:__('Verify_time')}: + + + + + + {:__('Verify_code')}: + + + + + + {:__('Settle_status')}: + + + + + + {:__('Settle_amount')}: + + + + + + {:__('Cancel_reason')}: + + + + + + {:__('Refund_amount')}: + + + + + + {:__('Is_review')}: + + + + + + diff --git a/server/application/admin/view/order/index.html b/server/application/admin/view/order/index.html new file mode 100644 index 0000000..67b57d7 --- /dev/null +++ b/server/application/admin/view/order/index.html @@ -0,0 +1,29 @@ + + {:build_heading()} + + + + + + + + {:__('Add')} + {:__('Edit')} + {:__('Delete')} + + + + + + + + + + + + + + diff --git a/server/application/admin/view/review/add.html b/server/application/admin/view/review/add.html new file mode 100644 index 0000000..cdfea4e --- /dev/null +++ b/server/application/admin/view/review/add.html @@ -0,0 +1,83 @@ + + + + {:__('Order_id')}: + + + + + + {:__('User_id')}: + + + + + + {:__('Master_id')}: + + + + + + {:__('Score')}: + + + + + + {:__('Tags')}: + + + + + + {:__('Content')}: + + + + + + {:__('Images')}: + + + + + {:__('Upload')} + {:__('Choose')} + + + + + + + + {:__('Is_anonymous')}: + + + + + + {:__('Reply')}: + + + + + + {:__('Status')}: + + + + {foreach name="statusList" item="vo"} + {$vo} + {/foreach} + + + + + + diff --git a/server/application/admin/view/review/edit.html b/server/application/admin/view/review/edit.html new file mode 100644 index 0000000..2263c1c --- /dev/null +++ b/server/application/admin/view/review/edit.html @@ -0,0 +1,83 @@ + + + + {:__('Order_id')}: + + + + + + {:__('User_id')}: + + + + + + {:__('Master_id')}: + + + + + + {:__('Score')}: + + + + + + {:__('Tags')}: + + + + + + {:__('Content')}: + + + + + + {:__('Images')}: + + + + + {:__('Upload')} + {:__('Choose')} + + + + + + + + {:__('Is_anonymous')}: + + + + + + {:__('Reply')}: + + + + + + {:__('Status')}: + + + + {foreach name="statusList" item="vo"} + {$vo} + {/foreach} + + + + + + diff --git a/server/application/admin/view/review/index.html b/server/application/admin/view/review/index.html new file mode 100644 index 0000000..0886d97 --- /dev/null +++ b/server/application/admin/view/review/index.html @@ -0,0 +1,46 @@ + + + + {:build_heading(null,FALSE)} + + {:__('All')} + {foreach name="statusList" item="vo"} + {$vo} + {/foreach} + + + + + + + + + + + {:__('Add')} + {:__('Edit')} + {:__('Delete')} + + + + {:__('More')} + + {foreach name="statusList" item="vo"} + {:__('Set status to ' . $key)} + {/foreach} + + + + + + + + + + + + + diff --git a/server/application/admin/view/service/add.html b/server/application/admin/view/service/add.html new file mode 100644 index 0000000..c024806 --- /dev/null +++ b/server/application/admin/view/service/add.html @@ -0,0 +1,113 @@ + + + + {:__('Cat_id')}: + + + + + + {:__('Name')}: + + + + + + {:__('Image')}: + + + + + {:__('Upload')} + {:__('Choose')} + + + + + + + + {:__('Price_type')}: + + + + {foreach name="priceTypeList" item="vo"} + {$vo} + {/foreach} + + + + + + {:__('Base_price')}: + + + + + + {:__('Max_price')}: + + + + + + {:__('Unit')}: + + + + + + {:__('Floor_fee')}: + + + + + + {:__('Free_floor')}: + + + + + + {:__('Need_model')}: + + + + + + {:__('Description')}: + + + + + + {:__('Sales')}: + + + + + + {:__('Weigh')}: + + + + + + {:__('Status')}: + + + + {foreach name="statusList" item="vo"} + {$vo} + {/foreach} + + + + + + diff --git a/server/application/admin/view/service/cat/add.html b/server/application/admin/view/service/cat/add.html new file mode 100644 index 0000000..9c28555 --- /dev/null +++ b/server/application/admin/view/service/cat/add.html @@ -0,0 +1,45 @@ + + + + {:__('Pid')}: + + + + + + {:__('Name')}: + + + + + + {:__('Icon')}: + + + + + + {:__('Weigh')}: + + + + + + {:__('Status')}: + + + + {foreach name="statusList" item="vo"} + {$vo} + {/foreach} + + + + + + diff --git a/server/application/admin/view/service/cat/edit.html b/server/application/admin/view/service/cat/edit.html new file mode 100644 index 0000000..8367cd9 --- /dev/null +++ b/server/application/admin/view/service/cat/edit.html @@ -0,0 +1,45 @@ + + + + {:__('Pid')}: + + + + + + {:__('Name')}: + + + + + + {:__('Icon')}: + + + + + + {:__('Weigh')}: + + + + + + {:__('Status')}: + + + + {foreach name="statusList" item="vo"} + {$vo} + {/foreach} + + + + + + diff --git a/server/application/admin/view/service/cat/index.html b/server/application/admin/view/service/cat/index.html new file mode 100644 index 0000000..7014dd3 --- /dev/null +++ b/server/application/admin/view/service/cat/index.html @@ -0,0 +1,46 @@ + + + + {:build_heading(null,FALSE)} + + {:__('All')} + {foreach name="statusList" item="vo"} + {$vo} + {/foreach} + + + + + + + + + + + {:__('Add')} + {:__('Edit')} + {:__('Delete')} + + + + {:__('More')} + + {foreach name="statusList" item="vo"} + {:__('Set status to ' . $key)} + {/foreach} + + + + + + + + + + + + + diff --git a/server/application/admin/view/service/edit.html b/server/application/admin/view/service/edit.html new file mode 100644 index 0000000..098dc16 --- /dev/null +++ b/server/application/admin/view/service/edit.html @@ -0,0 +1,113 @@ + + + + {:__('Cat_id')}: + + + + + + {:__('Name')}: + + + + + + {:__('Image')}: + + + + + {:__('Upload')} + {:__('Choose')} + + + + + + + + {:__('Price_type')}: + + + + {foreach name="priceTypeList" item="vo"} + {$vo} + {/foreach} + + + + + + {:__('Base_price')}: + + + + + + {:__('Max_price')}: + + + + + + {:__('Unit')}: + + + + + + {:__('Floor_fee')}: + + + + + + {:__('Free_floor')}: + + + + + + {:__('Need_model')}: + + + + + + {:__('Description')}: + + + + + + {:__('Sales')}: + + + + + + {:__('Weigh')}: + + + + + + {:__('Status')}: + + + + {foreach name="statusList" item="vo"} + {$vo} + {/foreach} + + + + + + diff --git a/server/application/admin/view/service/index.html b/server/application/admin/view/service/index.html new file mode 100644 index 0000000..216cf68 --- /dev/null +++ b/server/application/admin/view/service/index.html @@ -0,0 +1,46 @@ + + + + {:build_heading(null,FALSE)} + + {:__('All')} + {foreach name="statusList" item="vo"} + {$vo} + {/foreach} + + + + + + + + + + + {:__('Add')} + {:__('Edit')} + {:__('Delete')} + + + + {:__('More')} + + {foreach name="statusList" item="vo"} + {:__('Set status to ' . $key)} + {/foreach} + + + + + + + + + + + + + diff --git a/server/application/admin/view/settle/add.html b/server/application/admin/view/settle/add.html new file mode 100644 index 0000000..bd69034 --- /dev/null +++ b/server/application/admin/view/settle/add.html @@ -0,0 +1,63 @@ + + + + {:__('Order_id')}: + + + + + + {:__('Master_id')}: + + + + + + {:__('Order_amount')}: + + + + + + {:__('Commission_rate')}: + + + + + + {:__('Commission')}: + + + + + + {:__('Settle_amount')}: + + + + + + {:__('Status')}: + + + + {foreach name="statusList" item="vo"} + {$vo} + {/foreach} + + + + + + {:__('Settle_time')}: + + + + + + diff --git a/server/application/admin/view/settle/edit.html b/server/application/admin/view/settle/edit.html new file mode 100644 index 0000000..76b9e3e --- /dev/null +++ b/server/application/admin/view/settle/edit.html @@ -0,0 +1,63 @@ + + + + {:__('Order_id')}: + + + + + + {:__('Master_id')}: + + + + + + {:__('Order_amount')}: + + + + + + {:__('Commission_rate')}: + + + + + + {:__('Commission')}: + + + + + + {:__('Settle_amount')}: + + + + + + {:__('Status')}: + + + + {foreach name="statusList" item="vo"} + {$vo} + {/foreach} + + + + + + {:__('Settle_time')}: + + + + + + diff --git a/server/application/admin/view/settle/index.html b/server/application/admin/view/settle/index.html new file mode 100644 index 0000000..dc8395c --- /dev/null +++ b/server/application/admin/view/settle/index.html @@ -0,0 +1,46 @@ + + + + {:build_heading(null,FALSE)} + + {:__('All')} + {foreach name="statusList" item="vo"} + {$vo} + {/foreach} + + + + + + + + + + + {:__('Add')} + {:__('Edit')} + {:__('Delete')} + + + + {:__('More')} + + {foreach name="statusList" item="vo"} + {:__('Set status to ' . $key)} + {/foreach} + + + + + + + + + + + + + diff --git a/server/application/admin/view/withdraw/add.html b/server/application/admin/view/withdraw/add.html new file mode 100644 index 0000000..cacdce4 --- /dev/null +++ b/server/application/admin/view/withdraw/add.html @@ -0,0 +1,69 @@ + + + + {:__('Master_id')}: + + + + + + {:__('Amount')}: + + + + + + {:__('Fee')}: + + + + + + {:__('Arrive_amount')}: + + + + + + {:__('Channel')}: + + + + {foreach name="channelList" item="vo"} + {$vo} + {/foreach} + + + + + + {:__('Account')}: + + + + + + {:__('Status')}: + + + + {foreach name="statusList" item="vo"} + {$vo} + {/foreach} + + + + + + {:__('Audit_msg')}: + + + + + + diff --git a/server/application/admin/view/withdraw/edit.html b/server/application/admin/view/withdraw/edit.html new file mode 100644 index 0000000..52011fa --- /dev/null +++ b/server/application/admin/view/withdraw/edit.html @@ -0,0 +1,69 @@ + + + + {:__('Master_id')}: + + + + + + {:__('Amount')}: + + + + + + {:__('Fee')}: + + + + + + {:__('Arrive_amount')}: + + + + + + {:__('Channel')}: + + + + {foreach name="channelList" item="vo"} + {$vo} + {/foreach} + + + + + + {:__('Account')}: + + + + + + {:__('Status')}: + + + + {foreach name="statusList" item="vo"} + {$vo} + {/foreach} + + + + + + {:__('Audit_msg')}: + + + + + + diff --git a/server/application/admin/view/withdraw/index.html b/server/application/admin/view/withdraw/index.html new file mode 100644 index 0000000..aed351f --- /dev/null +++ b/server/application/admin/view/withdraw/index.html @@ -0,0 +1,46 @@ + + + + {:build_heading(null,FALSE)} + + {:__('All')} + {foreach name="statusList" item="vo"} + {$vo} + {/foreach} + + + + + + + + + + + {:__('Add')} + {:__('Edit')} + {:__('Delete')} + + + + {:__('More')} + + {foreach name="statusList" item="vo"} + {:__('Set status to ' . $key)} + {/foreach} + + + + + + + + + + + + + diff --git a/server/application/api/controller/Home.php b/server/application/api/controller/Home.php index 6a2a2f6..151c709 100644 --- a/server/application/api/controller/Home.php +++ b/server/application/api/controller/Home.php @@ -30,12 +30,12 @@ class Home extends Base ->select(); $data = [ - 'cats' => $cats, - 'hot' => $this->formatServices($hot), - 'banners' => [ - ['id' => 1, 'title' => '家装季 · 双11狂欢大促', 'desc' => '下单立减 · 满额再返券', 'date' => '10.23 - 11.15'], - ], - 'badge' => ['coupon' => 0, 'unread' => 0], + 'cats' => $cats, + 'hot' => $this->formatServices($hot), + 'banners' => $this->banners(), + 'assures' => $this->configList('home_assure', '平台认证,先行赔付,极速上门,售后无忧'), + 'tutorial' => $this->tutorial(), + 'badge' => ['coupon' => 0, 'unread' => 0], ]; if ($this->auth->isLogin()) { @@ -164,6 +164,62 @@ class Home extends Base ]; } + /** + * 首页运营位(后台 fa_banner 可管,缺省回落到内置文案) + */ + protected function banners() + { + $rows = Db::name('banner')->where('status', 'normal')->order('weigh desc,id desc')->limit(5)->select(); + if (!$rows) { + return [[ + 'id' => 0, 'badge' => '家装季', 'title' => '双11 狂欢大促', + 'subtitle' => '下单立减 · 满额再返券', 'date_text' => '10.23 - 11.15', + 'image' => '', 'link' => '', + ]]; + } + $ret = []; + foreach ($rows as $r) { + $ret[] = [ + 'id' => (int)$r['id'], + 'badge' => $r['badge'], + 'title' => $r['title'], + 'subtitle' => $r['subtitle'], + 'date_text' => $r['date_text'], + 'image' => $r['image'], + 'link' => $r['link'], + ]; + } + return $ret; + } + + /** + * 读取 fa_config 中的逗号分隔列表 + */ + protected function configList($name, $default = '') + { + $v = Db::name('config')->where('name', $name)->value('value'); + if (!$v) { + $v = $default; + } + return array_values(array_filter(array_map('trim', explode(',', (string)$v)))); + } + + /** + * 首页教程卡(fa_config: 标题|副标题) + */ + protected function tutorial() + { + $v = Db::name('config')->where('name', 'home_tutorial')->value('value'); + if (!$v) { + return ['title' => '下单找师傅教程', 'sub' => '3 分钟看懂,下单不踩坑']; + } + $parts = explode('|', (string)$v); + return [ + 'title' => trim($parts[0]), + 'sub' => isset($parts[1]) ? trim($parts[1]) : '', + ]; + } + /** * 列表格式化 */ diff --git a/server/application/api/controller/Order.php b/server/application/api/controller/Order.php index f2b19f7..2b141aa 100644 --- a/server/application/api/controller/Order.php +++ b/server/application/api/controller/Order.php @@ -345,6 +345,7 @@ class Order extends Base 'id' => (int)$row['id'], 'status' => $status, 'status_text' => OrderStatus::label($status), + 'status_tip' => OrderStatus::tip($status), 'can_cancel' => OrderStatus::canCancelByUser($status), 'can_verify' => $status === OrderStatus::PENDING_VERIFY, 'can_review' => $status === OrderStatus::COMPLETED && (int)$row['is_review'] === 0, diff --git a/server/application/common/library/OrderStatus.php b/server/application/common/library/OrderStatus.php index 208dcd0..27ee687 100644 --- a/server/application/common/library/OrderStatus.php +++ b/server/application/common/library/OrderStatus.php @@ -52,6 +52,30 @@ class OrderStatus /** 进行中(用户端"我的订单-进行中"、师傅端"待处理") */ protected static $activeStatus = [1, 2, 3, 4, 5, 6, 7]; + /** 状态提示文案(前后端统一由后端下发,避免两套口径) */ + protected static $tips = [ + self::PENDING_PAY => '请尽快完成支付', + self::PENDING_ACCEPT => '已进入接单大厅,等待师傅接单', + self::QUOTING => '师傅报价中,请稍候', + self::ACCEPTED => '师傅已接单,将尽快与您联系', + self::SCHEDULED => '已约定上门时间', + self::ARRIVED => '师傅已到达现场', + self::WORKING => '服务进行中', + self::PENDING_VERIFY => '师傅已完工,请确认验收', + self::COMPLETED => '订单已完成,感谢您的信任', + self::CANCELLED => '订单已取消', + self::AFTERSALE => '售后处理中', + ]; + + /** + * 状态提示文案 + */ + public static function tip($status) + { + $status = (int)$status; + return isset(self::$tips[$status]) ? self::$tips[$status] : ''; + } + /** * 取状态文案 */ diff --git a/tests/phase5_data_sync.js b/tests/phase5_data_sync.js new file mode 100644 index 0000000..84d0980 --- /dev/null +++ b/tests/phase5_data_sync.js @@ -0,0 +1,131 @@ +/** + * Phase 5 验证:后台数据 ↔ 前端接口 同源联动 + * 通过直接改后台表(后台 CRUD 的本质操作),断言前端接口立刻反映 + * 运行:node tests/phase5_data_sync.js + */ +const { execFileSync } = require('child_process'); +const BASE = process.env.API_BASE || 'https://yjyf.gzyakj.com.cn/api'; + +let pass = 0, fail = 0; +const failures = []; +const ok = (m, e) => { pass++; console.log(' ✅ ' + m + (e ? ' → ' + e : '')); }; +const bad = (m, e) => { fail++; failures.push(m + ' :: ' + e); console.log(' ❌ ' + m + ' → ' + e); }; +const chk = (c, m, e) => (c ? ok(m, e) : bad(m, e || '断言失败')); +const sec = (t) => console.log('\n▌' + t); + +async function api(path, opt) { + const o = opt || {}; + const headers = {}; + if (o.token) headers.token = o.token; + let body; + if (o.data) { + headers['content-type'] = 'application/x-www-form-urlencoded'; + body = new URLSearchParams(o.data).toString(); + } + const res = await fetch(BASE + path, { method: o.method || 'GET', headers, body }); + const text = await res.text(); + try { return JSON.parse(text); } catch (e) { throw new Error('非 JSON: ' + text.slice(0, 120)); } +} +// 直接用 SQL 改后台表,等价于后台 CRUD 的保存动作(强制 utf8mb4,避免中文二次编码) +const sql = (s) => execFileSync('ssh', ['-o', 'BatchMode=yes', 'root@8.137.181.63', + `mysql --default-character-set=utf8mb4 -uyjyf -pyjyf yjyf -e "${s}"`], { encoding: 'utf8' }); +const sleep = (ms) => new Promise(r => setTimeout(r, ms)); + + +(async () => { + console.log('═══════════════════════════════════════════════════'); + console.log(' Phase 5 验证:后台数据 ↔ 前端接口 同源联动'); + console.log('═══════════════════════════════════════════════════'); + + const mobile = '135' + String(Date.now()).slice(-8); + const login = await api('/auth/login', { method: 'POST', data: { mobile: mobile, code: '123456' } }); + const token = login.data.token; + + sec('1. 服务上下架(后台 fa_service.status ↔ 前端服务列表)'); + const svc = (await api('/home/services?cat_id=1')).data.list[0]; + chk(!!svc, '取到一个在售服务', svc && svc.name); + sql(`UPDATE fa_service SET status='hidden' WHERE id=${svc.id}`); + await sleep(1500); + let r = await api('/home/services?cat_id=1'); + chk(!(r.data.list || []).some(s => Number(s.id) === Number(svc.id)), '后台下架 → 前端列表不再返回该服务'); + sql(`UPDATE fa_service SET status='normal' WHERE id=${svc.id}`); + await sleep(1500); + r = await api('/home/services?cat_id=1'); + chk((r.data.list || []).some(s => Number(s.id) === Number(svc.id)), '后台重新上架 → 前端恢复显示'); + + sec('2. 首页 Banner(后台 fa_banner ↔ /home/index)'); + r = await api('/home/index'); + const b0 = r.data.banners[0]; + chk(!!b0 && !!b0.badge && !!b0.title && !!b0.subtitle && !!b0.date_text, 'Banner 五个字段均由后端下发', b0 && b0.title); + sql(`UPDATE fa_banner SET title='测试同步标题', badge='测试角标' WHERE id=1`); + await sleep(1500); + r = await api('/home/index'); + chk(r.data.banners[0].title === '测试同步标题' && r.data.banners[0].badge === '测试角标', '后台改 Banner → 前端立即变化'); + sql(`UPDATE fa_banner SET title='双11 狂欢大促', badge='家装季' WHERE id=1`); + await sleep(1500); + r = await api('/home/index'); + chk(r.data.banners[0].title === '双11 狂欢大促', '还原成功'); + + sql(`UPDATE fa_banner SET status='hidden' WHERE id=1`); + await sleep(1500); + r = await api('/home/index'); + chk(r.data.banners.length === 1 && r.data.banners[0].id === 0, '全部停用 → 回落到内置兜底文案', '兜底 title=' + r.data.banners[0].title); + sql(`UPDATE fa_banner SET status='normal' WHERE id=1`); + await sleep(1500); + + sec('3. 服务保障条(后台 fa_config ↔ /home/index)'); + sql(`UPDATE fa_config SET value='平台认证,先行赔付,极速上门,售后无忧,价格透明' WHERE name='home_assure'`); + await sleep(1500); + r = await api('/home/index'); + chk(r.data.assures.length === 5 && r.data.assures[4] === '价格透明', '后台加一条保障 → 前端展示 5 条'); + sql(`UPDATE fa_config SET value='平台认证,先行赔付,极速上门,售后无忧' WHERE name='home_assure'`); + await sleep(1500); + r = await api('/home/index'); + chk(r.data.assures.length === 4, '还原为 4 条'); + + sec('4. 教程卡(后台 fa_config ↔ /home/index)'); + chk(!!r.data.tutorial && !!r.data.tutorial.title, '教程卡标题由后端下发', r.data.tutorial.title); + sql(`UPDATE fa_config SET value='新手指南|从下单到验收全流程' WHERE name='home_tutorial'`); + await sleep(1500); + r = await api('/home/index'); + chk(r.data.tutorial.title === '新手指南' && r.data.tutorial.sub === '从下单到验收全流程', '后台改标题 → 前端立即变化'); + sql(`UPDATE fa_config SET value='下单找师傅教程|3 分钟看懂,下单不踩坑' WHERE name='home_tutorial'`); + await sleep(1500); + + sec('5. 优惠券(后台 fa_coupon ↔ 前端领券中心)'); + sql(`DELETE FROM fa_coupon WHERE name='同步测试券'`); + sql(`INSERT INTO fa_coupon (name,type,amount,discount,min_amount,total,received,start_time,end_time,status,createtime) VALUES ('同步测试券','cash',50.00,1.00,200.00,100,0,0,0,'normal',UNIX_TIMESTAMP())`); + await sleep(1500); + r = await api('/coupon/center', { token: token }); + const t = (r.data.list || []).find(c => c.name === '同步测试券'); + chk(!!t && String(t.value_text).indexOf('50') > -1, '后台发券 → 前端领券中心出现', t && (t.value_text + ' / ' + t.rule_text)); + sql(`UPDATE fa_coupon SET status='hidden' WHERE name='同步测试券'`); + await sleep(1500); + r = await api('/coupon/center', { token: token }); + chk(!(r.data.list || []).some(c => c.name === '同步测试券'), '后台下架券 → 前端不再展示'); + sql(`DELETE FROM fa_coupon WHERE name='同步测试券'`); + + sec('6. 订单状态提示(后端 OrderStatus ↔ 订单详情)'); + const addr = await api('/address/save', { + method: 'POST', token: token, + data: { contact: '同步验证', mobile: mobile, province: '贵州省', city: '遵义市', district: '汇川区', address: '同步验证地址', floor: 1, has_elevator: 1, is_default: 1 } + }); + const o = await api('/order/create', { method: 'POST', token: token, data: { service_id: svc.id, address_id: addr.data.id } }); + r = await api('/order/detail?id=' + o.data.order_id, { token: token }); + chk(r.data.order.status_text && r.data.order.status_tip, '状态文案与提示均由后端统一下发', + r.data.order.status_text + ' / ' + r.data.order.status_tip); + + sec('7. 后台管理模块完整性'); + const ls = execFileSync('ssh', ['-o', 'BatchMode=yes', 'root@8.137.181.63', + "ls /www/wwwroot/yjyf.gzyakj.com.cn/application/admin/controller/"], { encoding: 'utf8' }); + const need = ['Banner.php', 'Order.php', 'Master.php', 'Merchant.php', 'Coupon.php', 'Review.php', 'Message.php', 'Service.php', 'Appeal.php', 'Invoice.php', 'Withdraw.php', 'Settle.php']; + const missing = need.filter(n => !ls.includes(n)); + chk(missing.length === 0, '12 个业务后台控制器全部就绪', missing.length ? ('缺 ' + missing.join(',')) : '全部生成'); + chk(ls.includes('service') || ls.includes('Cat.php'), '服务类目后台控制器就绪'); + + console.log('\n═══════════════════════════════════════════════════'); + console.log(` 结果:通过 ${pass} 项 / 失败 ${fail} 项`); + if (fail) { console.log(' 失败明细:'); failures.forEach(f => console.log(' - ' + f)); process.exitCode = 1; } + else console.log(' 🎉 后台数据与前端接口同源联动验证通过'); + console.log('═══════════════════════════════════════════════════'); +})().catch(e => { console.error('\n💥 中断: ' + e.message); process.exitCode = 1; });