52 Commits d56112b2ee ... f535a9fe65

Author SHA1 Message Date
  张田田 f535a9fe65 修复注册信息是提交按钮报错 1 week ago
  张田田 903c4f7e86 提交 1 month ago
  张田田 9736020728 上传 1 month ago
  张田田 ff9b6806bf 把vantui去除掉了,减少包体积 1 month ago
  张田田 24b2dc6fe8 售后 2 months ago
  张田田 7a2536ddf2 Merge branch 'story-234' of ssh://121.43.162.141:10022/six.fe/health.applet into story-afterSale 2 months ago
  张田田 231d0bd8a8 优化我的页面登录 2 months ago
  张田田 66f8a68159 售后 2 months ago
  张田田 a606b7ad12 售后 2 months ago
  张田田 7dafa243c7 售后 2 months ago
  张田田 3873253768 提交修改 2 months ago
  张田田 5275cca25c 提交修改 2 months ago
  张田田 48f3ada7a7 协商历史图片可以放大轮播查看 2 months ago
  张田田 529a66ccb5 Merge branch 'story-234' of ssh://121.43.162.141:10022/six.fe/health.applet into story-afterSale 2 months ago
  张田田 2b0621b2c5 售后 2 months ago
  张田田 dcb51e3e54 bug-627 2 months ago
  张田田 3c3e852f03 Merge branch 'story-238' of ssh://121.43.162.141:10022/six.fe/health.applet into story-234 2 months ago
  张田田 8b6ccb2ca7 优化错误提示 2 months ago
  张田田 72f715ee8f 优化评价样式 2 months ago
  张田田 29e2adf927 bug-622 2 months ago
  张田田 2a49c7233f 物流 2 months ago
  张田田 459a3dbb9a bug-625 3 months ago
  张田田 c1b29ff61f 优化物流样式 3 months ago
  张田田 2904b6c952 物流 3 months ago
  张田田 8a2514fb20 调整评价样式 3 months ago
  张田田 966db4ec0b 物流 3 months ago
  张田田 b2da3c8e91 售后 3 months ago
  张田田 bd05dcf210 售后 3 months ago
  张田田 c0bcb9efe2 售后 3 months ago
  张田田 1cf8bb3558 物流 3 months ago
  张田田 d90c386c1d 1 3 months ago
  张田田 3c051ac179 Merge branch 'story-237' of ssh://121.43.162.141:10022/six.fe/health.applet into story-238 3 months ago
  张田田 0d6bf52415 取消预约功能 3 months ago
  张田田 ed89425c13 bug-611 3 months ago
  张田田 14320a0af2 bug-612 3 months ago
  张田田 c8e8cc9e9f bug-616 3 months ago
  张田田 b895f086fe Merge branch 'develop' of ssh://121.43.162.141:10022/six.fe/health.applet into story-237 3 months ago
  张田田 fa515b38ab 1 3 months ago
  张田田 37e83d344e 预约功能 3 months ago
  张田田 0d87425c52 Merge branch 'story-237' of ssh://121.43.162.141:10022/six.fe/health.applet into story-238 3 months ago
  张田田 1aefa2fed2 优化小程序主包大小 3 months ago
  张田田 b0f1e91596 去除测试 3 months ago
  张田田 9414b7c151 Merge branch 'develop' of ssh://121.43.162.141:10022/six.fe/health.applet into story-237 3 months ago
  张田田 994eee073e 取消预约 3 months ago
  张田田 5627ceffb8 变更 3 months ago
  张田田 6ddbae23b5 Merge branch 'develop' of ssh://121.43.162.141:10022/six.fe/health.applet into story-237 3 months ago
  张田田 2bddede525 Merge branch 'develop' of ssh://121.43.162.141:10022/six.fe/health.applet into story-237 3 months ago
  张田田 4cf5d6ab75 评价 3 months ago
  张田田 2285057a1b 评价 3 months ago
  张田田 2cb78e689d 评价 3 months ago
  张田田 5cd10ed878 去除test页面 3 months ago
  张田田 427bd59847 评价 3 months ago
100 changed files with 7185 additions and 476 deletions
  1. 0 1
      .cloudbase/container/debug.json
  2. 7 0
      .gitignore
  3. 0 6
      .idea/vcs.xml
  4. 1 1
      miniprogram/app.config.ts
  5. 11 2
      miniprogram/app.json
  6. 16 0
      miniprogram/module/afterSale/pages/questionnaire/questionnaire.json
  7. 208 0
      miniprogram/module/afterSale/pages/questionnaire/questionnaire.scss
  8. 223 0
      miniprogram/module/afterSale/pages/questionnaire/questionnaire.ts
  9. 68 0
      miniprogram/module/afterSale/pages/questionnaire/questionnaire.wxml
  10. 20 0
      miniprogram/module/afterSale/request.ts
  11. 1 2
      miniprogram/module/article/pages/add-address/add-address.json
  12. 51 31
      miniprogram/module/article/pages/add-address/add-address.ts
  13. 1 7
      miniprogram/module/article/pages/add-address/add-address.wxml
  14. 0 3
      miniprogram/module/article/pages/confirm-receiving/confirm-receiving.ts
  15. 0 1
      miniprogram/module/article/pages/order-detail/order-detail.ts
  16. 0 48
      miniprogram/module/article/pages/order-list/order-list.scss
  17. 3 3
      miniprogram/module/article/pages/order-list/order-list.wxml
  18. 0 1
      miniprogram/module/article/pages/success-page/success-page.ts
  19. 13 0
      miniprogram/module/care/pages/cancelAppointment/cancelAppointment.json
  20. 229 0
      miniprogram/module/care/pages/cancelAppointment/cancelAppointment.scss
  21. 36 0
      miniprogram/module/care/pages/cancelAppointment/cancelAppointment.ts
  22. 41 0
      miniprogram/module/care/pages/cancelAppointment/cancelAppointment.wxml
  23. 50 4
      miniprogram/module/care/pages/care/verifyRecord.scss
  24. 79 4
      miniprogram/module/care/pages/care/verifyRecord.ts
  25. 89 68
      miniprogram/module/care/pages/care/verifyRecord.wxml
  26. 0 1
      miniprogram/module/care/pages/careDetail/careDetail.ts
  27. 14 24
      miniprogram/module/care/pages/offlineTreatment/offlineTreatment.ts
  28. 2 1
      miniprogram/module/care/pages/offlineTreatment/offlineTreatment.wxml
  29. 0 1
      miniprogram/module/care/pages/reportRecord/reportRecord.ts
  30. 31 1
      miniprogram/module/care/request.ts
  31. 0 1
      miniprogram/module/charts/components/record-index/health-index.ts
  32. 14 8
      miniprogram/module/chats/components/guide/guide.wxml
  33. 0 4
      miniprogram/module/chats/components/questionnaire/questionnaire.ts
  34. 3 2
      miniprogram/module/chats/pages/consultation-record/consultation-record.json
  35. 69 19
      miniprogram/module/chats/pages/consultation-record/consultation-record.scss
  36. 0 3
      miniprogram/module/chats/pages/consultation-record/consultation-record.ts
  37. 21 18
      miniprogram/module/chats/pages/consultation-record/consultation-record.wxml
  38. 2 2
      miniprogram/module/chats/pages/index/index.scss
  39. 13 14
      miniprogram/module/health/pages/scheme/scheme.scss
  40. 0 2
      miniprogram/module/health/pages/scheme/scheme.ts
  41. 1 1
      miniprogram/module/health/pages/scheme/scheme.wxml
  42. 0 1
      miniprogram/module/health/pages/status/status.ts
  43. 0 1
      miniprogram/module/health/tools/health-index.ts
  44. 6 0
      miniprogram/module/order/components/after-sale-type-popup/after-sale-type-popup.json
  45. 210 0
      miniprogram/module/order/components/after-sale-type-popup/after-sale-type-popup.scss
  46. 75 0
      miniprogram/module/order/components/after-sale-type-popup/after-sale-type-popup.ts
  47. 57 0
      miniprogram/module/order/components/after-sale-type-popup/after-sale-type-popup.wxml
  48. 10 0
      miniprogram/module/order/components/refund-confirm-popup/refund-confirm-popup.json
  49. 282 0
      miniprogram/module/order/components/refund-confirm-popup/refund-confirm-popup.scss
  50. 148 0
      miniprogram/module/order/components/refund-confirm-popup/refund-confirm-popup.ts
  51. 119 0
      miniprogram/module/order/components/refund-confirm-popup/refund-confirm-popup.wxml
  52. 7 0
      miniprogram/module/order/components/refund-proof-popup/refund-proof-popup.json
  53. 178 0
      miniprogram/module/order/components/refund-proof-popup/refund-proof-popup.scss
  54. 99 0
      miniprogram/module/order/components/refund-proof-popup/refund-proof-popup.ts
  55. 49 0
      miniprogram/module/order/components/refund-proof-popup/refund-proof-popup.wxml
  56. 8 0
      miniprogram/module/order/components/refund-reason-popup/refund-reason-popup.json
  57. 72 0
      miniprogram/module/order/components/refund-reason-popup/refund-reason-popup.scss
  58. 65 0
      miniprogram/module/order/components/refund-reason-popup/refund-reason-popup.ts
  59. 35 0
      miniprogram/module/order/components/refund-reason-popup/refund-reason-popup.wxml
  60. 24 0
      miniprogram/module/order/model/evaluate.model.ts
  61. 1 3
      miniprogram/module/order/pages/appointment-success/appointment-success.json
  62. 1 4
      miniprogram/module/order/pages/appointment-success/appointment-success.ts
  63. 2 3
      miniprogram/module/order/pages/appointment/appointment.json
  64. 49 40
      miniprogram/module/order/pages/appointment/appointment.scss
  65. 146 63
      miniprogram/module/order/pages/appointment/appointment.ts
  66. 2 2
      miniprogram/module/order/pages/appointment/appointment.wxml
  67. 4 4
      miniprogram/module/order/pages/confirme-order/confirme-order.scss
  68. 1 1
      miniprogram/module/order/pages/confirme-order/confirme-order.wxml
  69. 8 0
      miniprogram/module/order/pages/goods-evaluate/goods-evaluate.json
  70. 225 0
      miniprogram/module/order/pages/goods-evaluate/goods-evaluate.scss
  71. 205 0
      miniprogram/module/order/pages/goods-evaluate/goods-evaluate.ts
  72. 72 0
      miniprogram/module/order/pages/goods-evaluate/goods-evaluate.wxml
  73. 8 0
      miniprogram/module/order/pages/goods-evaluateDetail/goods-evaluateDetail.json
  74. 349 0
      miniprogram/module/order/pages/goods-evaluateDetail/goods-evaluateDetail.scss
  75. 164 0
      miniprogram/module/order/pages/goods-evaluateDetail/goods-evaluateDetail.ts
  76. 57 0
      miniprogram/module/order/pages/goods-evaluateDetail/goods-evaluateDetail.wxml
  77. 10 0
      miniprogram/module/order/pages/logistics-detail/logistics-detail.json
  78. 395 0
      miniprogram/module/order/pages/logistics-detail/logistics-detail.scss
  79. 188 0
      miniprogram/module/order/pages/logistics-detail/logistics-detail.ts
  80. 100 0
      miniprogram/module/order/pages/logistics-detail/logistics-detail.wxml
  81. 6 0
      miniprogram/module/order/pages/negotiation-history/negotiation-history.json
  82. 150 0
      miniprogram/module/order/pages/negotiation-history/negotiation-history.scss
  83. 73 0
      miniprogram/module/order/pages/negotiation-history/negotiation-history.ts
  84. 46 0
      miniprogram/module/order/pages/negotiation-history/negotiation-history.wxml
  85. 8 0
      miniprogram/module/order/pages/offline-evaluate/offline-evaluate.json
  86. 238 0
      miniprogram/module/order/pages/offline-evaluate/offline-evaluate.scss
  87. 218 0
      miniprogram/module/order/pages/offline-evaluate/offline-evaluate.ts
  88. 79 0
      miniprogram/module/order/pages/offline-evaluate/offline-evaluate.wxml
  89. 8 0
      miniprogram/module/order/pages/offline-evaluateDetail/offline-evaluateDetail.json
  90. 412 0
      miniprogram/module/order/pages/offline-evaluateDetail/offline-evaluateDetail.scss
  91. 125 0
      miniprogram/module/order/pages/offline-evaluateDetail/offline-evaluateDetail.ts
  92. 71 0
      miniprogram/module/order/pages/offline-evaluateDetail/offline-evaluateDetail.wxml
  93. 1 1
      miniprogram/module/order/pages/order-detail/order-detail.wxml
  94. 5 2
      miniprogram/module/order/pages/other-detail/other-detail.json
  95. 41 1
      miniprogram/module/order/pages/other-detail/other-detail.scss
  96. 226 55
      miniprogram/module/order/pages/other-detail/other-detail.ts
  97. 74 11
      miniprogram/module/order/pages/other-detail/other-detail.wxml
  98. 12 0
      miniprogram/module/order/pages/refund-processing/refund-processing.json
  99. 314 0
      miniprogram/module/order/pages/refund-processing/refund-processing.scss
  100. 330 0
      miniprogram/module/order/pages/refund-processing/refund-processing.ts

+ 0 - 1
.cloudbase/container/debug.json

@@ -1 +0,0 @@
-{"containers":[],"config":{}}

+ 7 - 0
.gitignore

@@ -13,4 +13,11 @@ $RECYCLE.BIN/
 # Node.js
 node_modules/
 miniprogram_npm
+npm*-debug.log
 project.private.config.json
+
+# WeChat DevTools
+.cloudbase/
+
+# IDE
+.idea/

+ 0 - 6
.idea/vcs.xml

@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="VcsDirectoryMappings">
-    <mapping directory="$PROJECT_DIR$" vcs="Git" />
-  </component>
-</project>

+ 1 - 1
miniprogram/app.config.ts

@@ -7,8 +7,8 @@ if (env === "trial") {
   host = "test.hzliuzhi.com";
 } else if (env === "develop") {
   host = "wx.hzliuzhi.com:4433";
+  // host="test.hzliuzhi.com"
 }
-console.log("host",host)
 export const HOST = host;
 export const Base_URL = `https://${host}/manager/fdhb-mobile`;
 export const Upload_URL = `https://${host}/manager/file`;

+ 11 - 2
miniprogram/app.json

@@ -83,7 +83,8 @@
         "pages/care/verifyRecord",
         "pages/offlineTreatment/offlineTreatment",
         "pages/careDetail/careDetail",
-        "pages/reportRecord/reportRecord"
+        "pages/reportRecord/reportRecord",
+        "pages/cancelAppointment/cancelAppointment"
       ]
     },
     {
@@ -95,7 +96,15 @@
         "pages/other-detail/other-detail",
         "pages/confirme-order/confirme-order",
         "pages/appointment/appointment",
-        "pages/appointment-success/appointment-success"
+        "pages/appointment-success/appointment-success",
+        "pages/goods-evaluate/goods-evaluate",
+        "pages/goods-evaluateDetail/goods-evaluateDetail",
+        "pages/offline-evaluate/offline-evaluate",
+        "pages/offline-evaluateDetail/offline-evaluateDetail",
+        "pages/logistics-detail/logistics-detail",
+        "pages/refund-success/refund-success",
+        "pages/refund-processing/refund-processing",
+        "pages/negotiation-history/negotiation-history"
       ]
     }
   ],

+ 16 - 0
miniprogram/module/afterSale/pages/questionnaire/questionnaire.json

@@ -0,0 +1,16 @@
+{
+  "renderer": "skyline",
+  "navigationBarTextStyle": "white",
+  "navigationBarBackgroundColor": "#0f2226",
+  "backgroundColor": "#0f2226",
+  "backgroundColorContent": "#ffffff",
+  "backgroundColorTop": "#0f2226",
+  "backgroundColorBottom": "#0f2226",
+  "component": true,
+  "usingComponents": {
+    "t-icon": "tdesign-miniprogram/icon/icon",
+    "t-navbar": "tdesign-miniprogram/navbar/navbar",
+    "t-radio": "tdesign-miniprogram/radio/radio",
+    "t-radio-group": "tdesign-miniprogram/radio-group/radio-group"
+  }
+}

+ 208 - 0
miniprogram/module/afterSale/pages/questionnaire/questionnaire.scss

@@ -0,0 +1,208 @@
+.page-container {
+  background-color: #f5f5f5;
+  height: calc(100vh - 64px);
+  box-sizing: border-box;
+}
+
+.questionnaire-container {
+  padding: 32rpx;
+  padding-bottom: 180rpx; // 为底部固定按钮留出足够空间(按钮高度88rpx + padding 48rpx + 安全区域 + 额外空间)
+  background-color: #fff;
+  min-height: 100%;
+}
+
+/* 标题 */
+.questionnaire-title {
+  font-size: 40rpx;
+  font-weight: bold;
+  color: #333;
+  text-align: center;
+  margin-bottom: 32rpx;
+}
+
+/* 介绍文字 */
+.questionnaire-intro {
+  font-size: 28rpx;
+  color: black;
+  line-height: 1.8;
+  margin-bottom: 32rpx;
+  text-align: justify;
+}
+
+/* 分数说明 */
+.score-explanation {
+  margin-bottom: 32rpx;
+}
+
+.score-explanation-text {
+  font-size: 28rpx;
+  color: #333;
+  line-height: 1.6;
+  font-weight: bold;
+}
+
+/* 问题组 */
+.question-groups {
+  margin-bottom: 32rpx;
+}
+
+.question-group {
+  margin-bottom: 32rpx;
+}
+
+.group-title {
+  font-size: 35rpx;
+  font-weight: bold;
+  color: #333;
+}
+
+/* 问题列表 */
+.question-list {
+  display: flex;
+  flex-direction: column;
+  gap: 32rpx;
+}
+
+.question-item {
+  padding-bottom: 32rpx;
+  border-bottom: 1px solid #f0f0f0;
+
+  &:last-child {
+    border-bottom: none;
+  }
+}
+
+.question-name {
+  font-size: 28rpx;
+  color: #333;
+  line-height: 1.6;
+  font-weight: bold;
+  margin: 20rpx 0 20rpx 0;
+}
+
+/* 分数选择 */
+.score-radio-group {
+  width: 100%;
+}
+
+.score-options {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  flex-wrap: wrap;
+}
+
+.score-radio-item {
+  margin-right: 32rpx;
+
+  &:last-child {
+    margin-right: 0;
+  }
+}
+
+/* 其他意见 */
+.other-comment-section {
+  margin-top: 32rpx;
+  margin-bottom: 32rpx;
+}
+
+.other-comment-title {
+  font-size: 28rpx;
+  color: #333;
+  margin-bottom: 16rpx;
+}
+
+.other-comment-input {
+  width: 100%;
+  min-height: 200rpx;
+  padding: 20rpx;
+  background-color: #f8f8f8;
+  border: 2rpx solid #e0e0e0;
+  border-radius: 8rpx;
+  font-size: 26rpx;
+  color: #333;
+  box-sizing: border-box;
+  line-height: 1.6;
+}
+
+/* 结束语 */
+.questionnaire-end {
+  font-size: 26rpx;
+  color: black;
+  margin-top: 22rpx;
+  margin-bottom: 32rpx;
+  padding-top: 32rpx;
+  border-top: 1px solid #f0f0f0;
+}
+
+/* 加载状态 */
+.loading-container {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  min-height: 400rpx;
+  padding: 32rpx;
+}
+
+.loading-text {
+  font-size: 28rpx;
+  color: #999;
+}
+
+/* 空数据状态 */
+.empty-container {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  min-height: 400rpx;
+  padding: 32rpx;
+}
+
+.empty-text {
+  font-size: 28rpx;
+  color: #999;
+}
+
+/* 保存按钮 */
+.save-button-container {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  padding: 24rpx 32rpx;
+  padding-bottom: calc(env(safe-area-inset-bottom));
+  background-color: #fff;
+  border-top: 1px solid #f0f0f0;
+  box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
+  z-index: 100;
+  box-sizing: border-box;
+}
+
+.save-button {
+  width: 100%;
+  height: 88rpx;
+  line-height: 88rpx;
+  background: linear-gradient(135deg, #1d6ff6 0%, #0d5dd8 100%);
+  border-radius: 44rpx;
+  color: #fff;
+  font-size: 32rpx;
+  font-weight: bold;
+  border: none;
+  display: block;
+  text-align: center;
+  transition: all 0.3s;
+
+  &::after {
+    border: none;
+  }
+
+  &[disabled] {
+    background: #ccc;
+    opacity: 0.6;
+  }
+
+  &:not([disabled]):active {
+    opacity: 0.8;
+    transform: scale(0.98);
+  }
+}

+ 223 - 0
miniprogram/module/afterSale/pages/questionnaire/questionnaire.ts

@@ -0,0 +1,223 @@
+
+import {
+  getQuestionnaireMethod,
+  submitQuestionnaireMethod,
+} from "../../request";
+
+export interface Questionnaire {
+  id: number; //	满意度调研问卷ID
+  name: string; //满意度调研问卷名称
+  startSentence: string; //开头语
+  endSentence: string; //结束语
+  suggestion: string; //其他意见
+  groups: Array<{
+    name: string; //	问题组名称
+    items: Array<{
+      name: string;
+      score?: string; // 原始分数(可选)
+      selectedScore?: number | string; // 用户选择的分数
+    }>;
+  }>;
+}
+Page({
+  onLoad(options: { id?: string }) {
+    // 如果没有传递 id,使用默认 id 1,或者可以根据实际情况调整
+    const questionnaireId = options.id ? Number(options.id) : 1;
+    this.setData({
+      "followDetail.id": questionnaireId,
+    });
+    if (questionnaireId) {
+      this.getQuestionnaire(questionnaireId);
+    } else {
+      wx.showToast({
+        title: "问卷ID不能为空",
+        icon: "none",
+        duration: 2000,
+      });
+    }
+  },
+
+  data: {
+    scoreList: [
+      {
+        label: "1分",
+        value: "1",
+      },
+      {
+        label: "2分",
+        value: "2",
+      },
+      {
+        label: "3分",
+        value: "3",
+      },
+      {
+        label: "4分",
+        value: "4",
+      },
+      {
+        label: "5分",
+        value: "5",
+      },
+    ],
+    followDetail: {
+      id: 0,
+    } as Questionnaire,
+    loading: true, // 加载状态
+    showEmpty: false, // 显示空数据
+    saving: false, // 保存状态
+    suggestion: "", // 其他意见
+  },
+
+  async getQuestionnaire(id: number) {
+    // 模拟后端数据(根据图片内容)
+    // const mockData: Questionnaire = {
+    //   id: id,
+    //   name: "就诊体验满意度",
+    //   startSentence:
+    //     "感谢您选择我院就诊,为了持续改进医疗服务质量,我们诚挚地邀请您参与本次满意度调研。您的真实反馈对我们非常重要,我们将严格保密您的个人信息,请您根据实际就诊体验,花费几分钟时间完成本次调研。",
+    //   endSentence:
+    //     "再次感谢您的参与,我们会根据您的反馈持续改进服务质量再次感谢您的参与,我们会根据您的反馈持续改进服务质量。再次感谢您的参与,我们会根据您的反馈持续改进服务质量再次感谢您的参与,我们会根据您的反馈持续改进服务质量再次感谢您的参与,我们会根据您的反馈持续改进服务质量再次感谢您的参与,我们会根据您的反馈持续改进服务质量",
+    //   suggestion: "其他意见",
+    //   groups: [
+    //     {
+    //       name: "一、就诊环境评价",
+    //       items: [
+    //         {
+    //           name: "医院门诊/住院部的整洁度",
+    //           score: "1",
+    //         },
+    //         {
+    //           name: "医院的通风情况",
+    //           score: "2",
+    //         },
+    //         {
+    //           name: "医院的采光情况",
+    //           score: "3",
+    //         },
+    //         {
+    //           name: "医院的噪音控制",
+    //           score: "4",
+    //         },
+    //         {
+    //           name: "候诊区域的舒适度(座椅、空间等)",
+    //           score: "5",
+    //         },
+    //       ],
+    //     },
+    //   ],
+    // };
+
+    // 模拟网络延迟
+    // await new Promise((resolve) => setTimeout(resolve, 500));
+
+    // this.setData({
+    //   followDetail: mockData,
+    //   loading: false,
+    // });
+    // 实际接口调用
+    let list = await getQuestionnaireMethod(id);
+    const questionnaireData = list?.data as Questionnaire;
+    // 确保 id 字段存在,如果后端没有返回 id,使用传入的 id
+    const finalData = questionnaireData
+      ? { ...questionnaireData, id: questionnaireData.id || id }
+      : ({ id } as Questionnaire);
+    this.setData({
+      loading: false,
+      followDetail: finalData,
+      showEmpty: JSON.stringify(list?.data) === "{}" ? true : false,
+    });
+  },
+
+  // 选择分数(radio-group change 事件)
+  onScoreChange(e: WechatMiniprogram.CustomEvent) {
+    const { groupIndex, itemIndex } = e.currentTarget.dataset;
+    const score = e.detail.value;
+    const groups = [...this.data.followDetail.groups];
+    groups[groupIndex].items[itemIndex].selectedScore = score;
+
+    this.setData({
+      "followDetail.groups": groups,
+    });
+  },
+
+  // 输入其他意见
+  onCommentInput(e: WechatMiniprogram.Input) {
+    this.setData({
+      suggestion: e.detail.value,
+    });
+  },
+
+  // 保存问卷
+  async onSave() {
+    // 检查是否所有问题都已回答
+    const groups = this.data.followDetail?.groups || [];
+    // let allAnswered = true;
+    // let unansweredQuestions: string[] = [];
+    // for (const group of groups) {
+    //   for (const item of group.items) {
+    //     if (!item.selectedScore) {
+    //       allAnswered = false;
+    //       unansweredQuestions.push(item.name);
+    //     }
+    //   }
+    // }
+
+    // if (!allAnswered) {
+    //   wx.showToast({
+    //     title: "请完成所有问题的评分",
+    //     icon: "none",
+    //     duration: 2000,
+    //   });
+    //   return;
+    // }
+    // 准备提交数据
+    const submitData = {
+      groups: groups.map((group) => ({
+        name: group.name,
+        items: group.items.map((item) => ({
+          name: item.name,
+          score: item.selectedScore ? String(item.selectedScore) : "",
+        })),
+      })),
+      startSentence: this.data.followDetail.startSentence,
+      endSentence: this.data.followDetail.endSentence,
+      suggestion: this.data.suggestion,
+      name: this.data.followDetail.name,
+    };
+    this.setData({
+      saving: true,
+    });
+
+    try {
+      // 实际接口调用
+      await submitQuestionnaireMethod(
+        this.data.followDetail.id,
+        submitData as AnyObject
+      );
+      wx.showToast({
+        title: "提交成功",
+        icon: "success",
+        duration: 2000,
+      });
+      setTimeout(() => {
+        wx.redirectTo({
+          url: "/pages/home/home",
+        });
+      }, 2000);
+    } catch (error) {
+      wx.showToast({
+        title: "保存失败,请重试",
+        icon: "none",
+        duration: 2000,
+      });
+      this.setData({
+        saving: false,
+      });
+    } finally {
+      this.setData({
+        saving: false,
+      });
+    }
+  },
+});

+ 68 - 0
miniprogram/module/afterSale/pages/questionnaire/questionnaire.wxml

@@ -0,0 +1,68 @@
+<t-navbar title="满意度问卷" left-arrow />
+<scroll-view class="page-container" scroll-y>
+  <!-- 加载状态 -->
+  <view class="loading-container" wx:if="{{loading}}">
+    <view class="loading-text">加载中...</view>
+  </view>
+
+  <!-- 问卷内容 -->
+  <view class="questionnaire-container" wx:elif="{{!showEmpty}}">
+    <!-- 标题 -->
+    <view class="questionnaire-title">{{followDetail.name || '满意度问卷'}}</view>
+
+    <!-- 介绍文字 -->
+    <view class="questionnaire-intro" wx:if="{{followDetail.startSentence}}">
+      {{followDetail.startSentence}}
+    </view>
+
+    <!-- 分数说明 -->
+    <view class="score-explanation">
+      <text class="score-explanation-text">分数说明:1分表示非常不满意,2分表示不满意,3分表示一般,4分表示满意,5分表示非常满意</text>
+    </view>
+
+    <!-- 问题组 -->
+    <view class="question-groups" wx:if="{{followDetail.groups}}">
+      <view class="question-group" wx:for="{{followDetail.groups}}" wx:key="index" wx:for-index="groupIndex">
+        <!-- 问题组标题 -->
+        <view class="group-title">{{item.name}}</view>
+
+        <!-- 问题列表 -->
+        <view class="question-list">
+          <view class="question-item" wx:for="{{item.items}}" wx:key="index" wx:for-index="itemIndex">
+            <!-- 问题名称 -->
+            <view class="question-name">{{itemIndex + 1}}、{{item.name}}</view>
+
+            <!-- 分数选择(单选) -->
+            <t-radio-group value="{{item.selectedScore || item.score}}" borderless t-class="box" bind:change="onScoreChange" data-group-index="{{groupIndex}}" data-item-index="{{itemIndex}}">
+              <t-radio block="{{false}}" :label="{{item.label}}" :value="{{item.value}}" wx:for="{{scoreList}}" wx:key="value" style="margin-right:20rpx" />
+            </t-radio-group>
+          </view>
+        </view>
+      </view>
+    </view>
+
+    <!-- 其他意见 -->
+    <view class="other-comment-section">
+      <view class="other-comment-title">其他意见:</view>
+      <textarea class="other-comment-input" placeholder="请给我们留言您的其他意见" value="{{suggestion}}" bindinput="onCommentInput" maxlength="500" show-confirm-bar="{{false}}" />
+    </view>
+
+    <!-- 结束语 -->
+    <view class="questionnaire-end">
+      {{followDetail.endSentence}}
+    </view>
+  </view>
+
+  <!-- 无数据提示 -->
+  <view class="empty-container" wx:elif="{{showEmpty}}">
+    <view class="empty-text">暂无问卷数据</view>
+  </view>
+</scroll-view>
+
+<!-- 保存按钮 -->
+<view class="save-button-container" wx:if="{{!showEmpty}}">
+  <button class="save-button" bindtap="onSave" disabled="{{saving}}">
+    <text wx:if="{{saving}}">保存中...</text>
+    <text wx:else>保存</text>
+  </button>
+</view>

+ 20 - 0
miniprogram/module/afterSale/request.ts

@@ -0,0 +1,20 @@
+import { Get, Post } from "../../lib/request/method";
+
+// 获取问卷详情
+export function getQuestionnaireMethod(id: number) {
+  return Post(`/satisfiesyManage/getContent/${id}`, {
+    transform({ data }: AnyObject) {
+      return data;
+    },
+  });
+}
+// 满意度调研问卷提交
+export function submitQuestionnaireMethod(id: number, data: AnyObject) {
+  console.log(data, "data===");
+  return Post(`/satisfiesyManage/submitContent/${id}`, {
+    ...data,
+    transform({ data }: AnyObject) {
+      return data;
+    },
+  });
+}

+ 1 - 2
miniprogram/module/article/pages/add-address/add-address.json

@@ -13,7 +13,6 @@
     "t-cell-group": "tdesign-miniprogram/cell-group/cell-group",
     "t-textarea": "tdesign-miniprogram/textarea/textarea",
     "t-picker-item": "tdesign-miniprogram/picker-item/picker-item",
-    "van-area": "@vant/weapp/area/index",
-    "van-popup": "@vant/weapp/popup/index"
+    "t-cascader": "tdesign-miniprogram/cascader/cascader"
   }
 }

+ 51 - 31
miniprogram/module/article/pages/add-address/add-address.ts

@@ -11,6 +11,37 @@ import {
   deleteAddressMethod,
   updateAddressMethod,
 } from "../../request";
+
+// 将 @vant/area-data 扁平格式转为 TDesign Cascader 树形格式
+function convertAreaDataToTree(areaList: { province_list: Record<string, string>; city_list: Record<string, string>; county_list: Record<string, string> }) {
+  const { province_list, city_list, county_list } = areaList;
+
+  const cityMap: Record<string, any[]> = {};
+  for (const cityCode in city_list) {
+    const prefix = cityCode.substring(0, 2);
+    if (!cityMap[prefix]) cityMap[prefix] = [];
+    cityMap[prefix].push({ label: city_list[cityCode], value: cityCode, children: [] });
+  }
+
+  const countyMap: Record<string, any[]> = {};
+  for (const countyCode in county_list) {
+    const prefix = countyCode.substring(0, 4);
+    if (!countyMap[prefix]) countyMap[prefix] = [];
+    countyMap[prefix].push({ label: county_list[countyCode], value: countyCode });
+  }
+
+  const result: any[] = [];
+  for (const provinceCode in province_list) {
+    const children = (cityMap[provinceCode.substring(0, 2)] || []).map((city: any) => ({
+      ...city,
+      children: countyMap[city.value.substring(0, 4)] || [],
+    }));
+    if (children.length > 0) {
+      result.push({ label: province_list[provinceCode], value: provinceCode, children });
+    }
+  }
+  return result;
+}
 // module/diet/pages/delivery-address/delivery-address.ts
 // import parseAddress from '../../utils/smart-parse-address';
 Page({
@@ -20,6 +51,7 @@ Page({
   data: {
     type: "",
     areaList,
+    areaTreeData: convertAreaDataToTree(areaList),
     id: "",
     formData: {
       id: "",
@@ -52,21 +84,23 @@ Page({
     this.setData({ detail: "" });
   },
   onRegionConfirm(e: any) {
+    const selectedOptions = e.detail.selectedOptions;
+    if (!selectedOptions || selectedOptions.length < 3) return;
     this.setData({
       region:
-        e.detail.values[0].name +
+        selectedOptions[0].label +
         ", " +
-        e.detail.values[1].name +
+        selectedOptions[1].label +
         "," +
-        e.detail.values[2].name,
-      regionValue: e.detail.values,
+        selectedOptions[2].label,
+      regionValue: e.detail.value,
       regionPickerVisible: false,
-      "formData.provinceCode": e.detail.values[0].code,
-      "formData.provinceName": e.detail.values[0].name,
-      "formData.cityCode": e.detail.values[1].code,
-      "formData.cityName": e.detail.values[1].name,
-      "formData.areaCode": e.detail.values[2].code,
-      "formData.areaName": e.detail.values[2].name,
+      "formData.provinceCode": selectedOptions[0].value,
+      "formData.provinceName": selectedOptions[0].label,
+      "formData.cityCode": selectedOptions[1].value,
+      "formData.cityName": selectedOptions[1].label,
+      "formData.areaCode": selectedOptions[2].value,
+      "formData.areaName": selectedOptions[2].label,
     });
   },
   onRegionCancel(e: any) {
@@ -172,29 +206,15 @@ Page({
         await this.onAddress(this.data.formData);
       }
       this.setData({ saving: false });
-      // if (this.data.type === "orderDetail") {
-      //   // 订单详情页
-      //   wx.redirectTo({
-      //     url: "/module/article/pages/order-detail/order-detail",
-      //   });
-      // } else if (this.data.type === "orderList") {
-      //   // 订单列表页
-      //   wx.redirectTo({
-      //     url: "/module/article/pages/order-list/order-list",
-      //   });
-      // }
-      // 返回上一页
+      // 返回上一页(避免 redirectTo 造成 manage-address 重复入栈)
       const pages = getCurrentPages();
       if (pages.length > 1) {
-        const prevPage = pages[pages.length - 2];
-        if (
-          prevPage.route ===
-          "module/article/pages/manage-address/manage-address"
-        ) {
-          wx.redirectTo({
-            url: "/module/article/pages/manage-address/manage-address",
-          });
-        }
+        wx.navigateBack();
+      } else {
+        // 极端情况下没有上一页(比如被 reLaunch 打开),兜底回地址管理
+        wx.redirectTo({
+          url: "/module/article/pages/manage-address/manage-address",
+        });
       }
       // 保存成功后可自动跳转或提示
     } catch (error: any) {

+ 1 - 7
miniprogram/module/article/pages/add-address/add-address.wxml

@@ -111,13 +111,7 @@
   </view>
 </scroll-view>
 
-<van-popup show="{{ regionPickerVisible }}" position="bottom" bind:close="onRegionCancel">
-  <van-area
-    area-list="{{ areaList }}"
-    bind:confirm="onRegionConfirm"
-    bind:cancel="onRegionCancel"
-  />
-</van-popup>
+<t-cascader visible="{{ regionPickerVisible }}" title="选择地区" options="{{ areaTreeData }}" value="{{ regionValue }}" bind:change="onRegionConfirm" bind:close="onRegionCancel" />
 
 
 <!-- 底部按钮 -->

+ 0 - 3
miniprogram/module/article/pages/confirm-receiving/confirm-receiving.ts

@@ -19,7 +19,6 @@ Page({
     }>
   },
   async onLoad(options: any) {
-    console.log("onLoad", options)
     if (options.patientConditioningRecordId) {
       this.setData({ patientConditioningRecordId: options.patientConditioningRecordId });
     }
@@ -32,7 +31,6 @@ Page({
       }));
       this.setData({ goodsList: goodsListWithChecked });
     }
-    console.log("goodsList", this.data.goodsList)
   },
   properties: {
   },
@@ -65,7 +63,6 @@ Page({
       wx.showToast({ title: '请至少选择一个商品', icon: 'none' });
       return;
     }
-    console.log(selectedIds, "selectedIds======选中的商品id")
     let that = this;
     wx.showModal({
       title: '提示',

+ 0 - 1
miniprogram/module/article/pages/order-detail/order-detail.ts

@@ -12,7 +12,6 @@ import {
 Page({
   behaviors: [PageContainerBehavior, tickleBehavior],
   onLoad(options: any) {
-    console.log(options, "options");
     if (options.id) {
       this.setData({ id: options.id });
     }

+ 0 - 48
miniprogram/module/article/pages/order-list/order-list.scss

@@ -157,58 +157,10 @@
 .order-actions {
   display: flex;
   gap: 20rpx;
-  transition: none !important;
 }
 
 .order-actions .t-button {
   margin: 0 !important;
-  min-width: 120rpx !important;
-  width: 120rpx !important;
-}
-
-// 为立即支付按钮设置固定宽度,防止loading状态时宽度变化
-.order-actions .t-button[data-id] {
-  min-width: 120rpx !important;
-  width: 120rpx !important;
-}
-
-// 节流状态下的按钮样式
-.order-actions .t-button[disabled] {
-  opacity: 1 !important;
-  cursor: not-allowed !important;
-  background-color: #f5f5f5 !important;
-  border-color: #e5e5e5 !important;
-  color: #c0c0c0 !important;
-}
-
-// 针对 t-design 按钮的自定义样式
-.outline-btn {
-  background: #fff !important;
-  border: 1px solid #1890ff !important; // 主色边框
-  color: #1890ff !important; // 主色文字
-  box-shadow: none !important;
-  border-radius: 30rpx !important;
-  transition: none !important;
-  // margin-right: 20rpx !important;
-}
-
-// 让按钮内的 loading 和文字显式为品牌蓝
-.outline-btn .t-loading__spinner,
-.outline-btn .t-loading__text {
-  color: #1890ff !important;
-}
-.outline-btn .t-loading__spinner .t-loading__dot::before,
-.outline-btn .t-loading__spinner--dots .t-loading__dot,
-.outline-btn .t-loading__circular {
-  color: #1890ff !important;
-  background-color: #1890ff !important;
-}
-
-.outline-btn.cancel {
-  border-color: #ccc !important;
-  color: black !important;
-  margin-right: 20rpx;
-  transition: none !important;
 }
 
 .contact {

+ 3 - 3
miniprogram/module/article/pages/order-list/order-list.wxml

@@ -85,12 +85,12 @@
       <view class="order-footer">
         <view class="order-price">订单金额:<text class="order-amount">¥{{item.cost}}</text></view>
 
-        <view class="order-actions">
+        <view class="order-actions" style="--td-button-border-radius:30rpx;">
           <!-- 按状态显示不同按钮 -->
-          <t-button wx:if="{{item.orderStatus==='0'}}" size="small" type="default" class="outline-btn cancel" bindtap="onCancel" data-id="{{item.id}}" disabled="{{throttleTimers.cancel}}">
+          <t-button wx:if="{{item.orderStatus==='0'}}" size="small" type="default" class="outline-btn cancel" bindtap="onCancel" data-id="{{item.id}}" disabled="{{throttleTimers.cancel}}" style="--td-button-default-color:#000;--td-button-default-bg-color:#fff;--td-button-default-border-color:#ccc;" hover-class="none">
             取消订单
           </t-button>
-          <t-button wx:if="{{item.orderStatus==='0'}}" size="small" type="primary" bindtap="onPay" class="outline-btn" data-id="{{item.id}}" disabled="{{(payingId===item.id) || throttleTimers.pay}}">
+          <t-button wx:if="{{item.orderStatus==='0'}}" size="small" type="primary" bindtap="onPay" class="outline-btn" data-id="{{item.id}}" disabled="{{(payingId===item.id) || throttleTimers.pay}}" style="--td-button-primary-color:#fff;--td-button-primary-bg-color:#1890ff;--td-button-primary-border-color:#1890ff;" hover-class="none">
             立即支付
           </t-button>
           <!-- <t-button wx:if="{{item.orderStatus==='6' || item.orderStatus==='345'}}" size="small" type="default" class="outline-btn cancel" bindtap="onSeeLogistics" data-id="{{item.id}}" disabled="{{throttleTimers.logistics}}">

+ 0 - 1
miniprogram/module/article/pages/success-page/success-page.ts

@@ -18,7 +18,6 @@ Page({
     successImg: 'https://wx.hzliuzhi.com:4433/manager/file/statics/2025/07/11/img_success@3x_20250711171518A500.png',
   },
   onLoad(options: any) {
-    console.log(options, "options");
     if (options.title) {
       this.setData({ title: options.title });
     }

+ 13 - 0
miniprogram/module/care/pages/cancelAppointment/cancelAppointment.json

@@ -0,0 +1,13 @@
+{
+  "renderer": "skyline",
+  "navigationBarTextStyle": "white",
+  "navigationBarBackgroundColor": "#0f2226",
+  "backgroundColor": "#0f2226",
+  "backgroundColorContent": "#ffffff",
+  "backgroundColorTop": "#0f2226",
+  "backgroundColorBottom": "#0f2226",
+  "component": true,
+  "usingComponents": {
+    "t-icon": "tdesign-miniprogram/icon/icon"
+  }
+}

+ 229 - 0
miniprogram/module/care/pages/cancelAppointment/cancelAppointment.scss

@@ -0,0 +1,229 @@
+.page-container {
+  background-color: #f3f3f3;
+  padding-top: 10px;
+  padding-bottom: 140rpx;
+  height: calc(100vh - 44px);
+  box-sizing: border-box;
+  -webkit-overflow-scrolling: touch;
+  .verify-record-page {
+    background: white;
+    // padding: 24rpx 0;
+    padding-bottom: 50px;
+  }
+  .title {
+    font-size: 32rpx;
+    font-weight: bold;
+    padding: 24rpx 32rpx 12rpx 32rpx;
+    color: #222;
+  }
+  .record-card {
+    background: #fff;
+    border-radius: 16rpx;
+    margin: 0 24rpx 24rpx 24rpx;
+    padding: 24rpx 24rpx 18rpx 24rpx;
+    box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03);
+  }
+  .record-header {
+    font-size: 28rpx;
+    font-weight: 500;
+    color: #222;
+    margin-bottom: 12rpx;
+    display: flex;
+    align-items: center;
+  }
+  .record-index {
+    margin-right: 8rpx;
+  }
+  .record-time {
+    color: #222;
+  }
+  .record-row {
+    font-size: 26rpx;
+    color: #666;
+    margin-bottom: 8rpx;
+    display: flex;
+    flex-wrap: wrap;
+  }
+
+  .opt-img {
+    display: flex;
+    justify-content: baseline;
+    .img-box {
+      width: 300rpx;
+      height: 200rpx;
+      border-radius: 10rpx;
+    }
+  }
+  .label {
+    color: #999;
+    margin-right: 8rpx;
+  }
+}
+
+.bottom-actions {
+  position: fixed;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  // padding: 24rpx 32rpx calc(24rpx + env(safe-area-inset-bottom));
+  padding: 24rpx 32rpx calc(env(safe-area-inset-bottom));
+  background: #f3f3f3;
+  box-sizing: border-box;
+}
+
+.bottom-btn {
+  height: 88rpx;
+  line-height: 88rpx;
+  border-radius: 12rpx;
+  background: #1976d2;
+  color: #fff;
+  font-size: 30rpx;
+  text-align: center;
+}
+.empty-state {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  min-height: 300rpx;
+  padding: 40rpx;
+
+  .empty-text {
+    color: #999;
+    font-size: 28rpx;
+  }
+}
+// 预约成功卡片
+.appointment-success-card {
+  background: #fff;
+  border-radius: 16rpx;
+  // margin: 24rpx;
+  padding: 30rpx 32rpx 40rpx;
+  box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03);
+  margin-bottom: 20rpx;
+}
+
+.appointment-actions {
+  margin-top: 30rpx;
+  display: flex;
+  justify-content: center;
+  gap: 24rpx;
+}
+
+.action-btn {
+  min-width: 220rpx;
+  height: 72rpx;
+  line-height: 72rpx;
+  border-radius: 12rpx;
+  font-size: 28rpx;
+  text-align: center;
+  box-sizing: border-box;
+  padding: 0 24rpx;
+}
+
+.action-btn.cancel {
+  background: #fff;
+  color: #1976d2;
+  border: 2rpx solid #1976d2;
+  margin-right: 20px;
+}
+
+.action-btn.modify {
+  background: #1976d2;
+  color: #fff;
+  border: 2rpx solid #1976d2;
+}
+
+.appointment-success-title {
+  font-size: 48rpx;
+  font-weight: 600;
+  color: #000;
+  text-align: center;
+  margin-bottom: 30rpx;
+}
+
+// 二维码容器
+.qr-code-container {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  margin-bottom: 50rpx;
+}
+
+.qr-code-image {
+  width: 400rpx;
+  height: 400rpx;
+  background: #fff;
+}
+
+.qr-code-placeholder {
+  width: 260rpx;
+  height: 260rpx;
+  background: #f5f5f5;
+  border: 2rpx dashed #ddd;
+  border-radius: 8rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.qr-code-text {
+  font-size: 28rpx;
+  color: #999;
+}
+
+// 预约信息
+.appointment-info {
+  margin-bottom: 50rpx;
+  margin-left: 20rpx;
+}
+
+.info-item {
+  display: flex;
+  align-items: flex-start;
+  margin-bottom: 25rpx;
+  font-size: 30rpx;
+  line-height: 1.8;
+}
+
+.info-label {
+  color: #000;
+  margin-right: 16rpx;
+  // min-width: 180rpx;
+  flex-shrink: 0;
+}
+
+.info-value {
+  color: #000;
+  flex: 1;
+  word-break: break-all;
+  font-weight: 600;
+}
+
+// 提示信息
+// .appointment-tip {
+//   margin-top: 50rpx;
+//   padding-top: 40rpx;
+//   border-top: 1rpx solid #eee;
+// }
+
+.tip-text {
+  font-size: 26rpx;
+  color: #000;
+  line-height: 1.8;
+  text-align: center;
+}
+.evaluate-box{
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.btn-review {
+  padding: 12rpx 40rpx;
+  border-radius: 12rpx;
+  font-size: 26rpx;
+  text-align: center;
+  background-color: #fff;
+  color: #1976d2;
+  border: 1px solid #1976d2;
+  margin-left: 20rpx;
+}

+ 36 - 0
miniprogram/module/care/pages/cancelAppointment/cancelAppointment.ts

@@ -0,0 +1,36 @@
+Page({
+  data: {
+    appointmentInfo: {
+      operateTime: '',
+      operateDuration: '',
+      conditioningProgramSupplierName: '',
+      cpSupplierDetailAddress: '',
+      cpSupplierPhone: '',
+      cpSupplierQrCode: '',
+    },
+  },
+  onLoad(options: any) {
+    if (options.appointmentInfo) {
+      try {
+        const appointmentInfo = JSON.parse(decodeURIComponent(options.appointmentInfo));
+        this.setData({ appointmentInfo });
+        this.generateQRCode(appointmentInfo?.cpSupplierQrCode || "");
+        return;
+      } catch (e) {
+        // 解析失败
+      }
+    }
+  },
+  // 生成二维码
+  generateQRCode(qrCodeUrl: any) {
+    this.setData({
+      qrCodeUrl
+    });
+  },
+  onBack() {
+    wx.redirectTo({
+      url: `/module/care/pages/offlineTreatment/offlineTreatment`,
+    });
+  },
+
+});

+ 41 - 0
miniprogram/module/care/pages/cancelAppointment/cancelAppointment.wxml

@@ -0,0 +1,41 @@
+<t-navbar title="取消预约" left-arrow />
+<scroll-view class="page-container" scroll-y>
+  <!-- 预约成功卡片 -->
+  <view class="appointment-success-card">
+    <!-- 标题 -->
+    <view class="appointment-success-title">取消预约成功</view>
+
+    <!-- 预约详情 -->
+    <view class="appointment-info">
+     <view class="info-item">
+        <text class="info-label">预约项目:</text>
+        <text class="info-value">{{appointmentInfo.arrangeTime}}</text>
+      </view>
+      <view class="info-item" wx:if="{{appointmentInfo.arrangeTime}}">
+        <text class="info-label">开始服务时间:</text>
+        <text class="info-value">{{appointmentInfo.arrangeTime}}</text>
+      </view>
+      <view class="info-item" wx:if="{{appointmentInfo.arrangeDuration}}">
+        <text class="info-label">服务所需时间:</text>
+        <text class="info-value">{{appointmentInfo.arrangeDuration}}分钟</text>
+      </view>
+      <view class="info-item" wx:if="{{appointmentInfo.conditioningProgramSupplierName}}">
+        <text class="info-label">服务机构:</text>
+        <text class="info-value" wx:if="{{appointmentInfo.pieTime}}">{{appointmentInfo.conditioningProgramSupplierName}}</text>
+        <text class="info-value" wx:else>待分配</text>
+      </view>
+      <view class="info-item" wx:if="{{appointmentInfo.cpSupplierDetailAddress}}">
+        <text class="info-label">服务地址:</text>
+        <text class="info-value">{{appointmentInfo.cpSupplierDetailAddress}}</text>
+      </view>
+      <view class="info-item" wx:if="{{appointmentInfo.cpSupplierPhone}}">
+        <text class="info-label">联系电话:</text>
+        <text class="info-value">{{appointmentInfo.cpSupplierPhone}}</text>
+      </view>
+    </view>
+  </view>
+  <!-- 返回按钮 -->
+  <view class="bottom-actions">
+    <view class="bottom-btn" catchtap="onBack">返回</view>
+  </view>
+</scroll-view>

+ 50 - 4
miniprogram/module/care/pages/care/verifyRecord.scss

@@ -43,7 +43,7 @@
     display: flex;
     flex-wrap: wrap;
   }
-  
+
   .opt-img {
     display: flex;
     justify-content: baseline;
@@ -64,7 +64,7 @@
   align-items: center;
   min-height: 300rpx;
   padding: 40rpx;
-  
+
   .empty-text {
     color: #999;
     font-size: 28rpx;
@@ -76,10 +76,41 @@
   border-radius: 16rpx;
   // margin: 24rpx;
   padding: 30rpx 32rpx 40rpx;
-  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.03);
+  box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03);
   margin-bottom: 20rpx;
 }
 
+.appointment-actions {
+  margin-top: 30rpx;
+  display: flex;
+  justify-content: center;
+  gap: 24rpx;
+}
+
+.action-btn {
+  min-width: 220rpx;
+  height: 72rpx;
+  line-height: 72rpx;
+  border-radius: 12rpx;
+  font-size: 28rpx;
+  text-align: center;
+  box-sizing: border-box;
+  padding: 0 24rpx;
+}
+
+.action-btn.cancel {
+  background: #fff;
+  color: #1976d2;
+  border: 2rpx solid #1976d2;
+  margin-right: 20px;
+}
+
+.action-btn.modify {
+  background: #1976d2;
+  color: #fff;
+  border: 2rpx solid #1976d2;
+}
+
 .appointment-success-title {
   font-size: 48rpx;
   font-weight: 600;
@@ -158,4 +189,19 @@
   color: #000;
   line-height: 1.8;
   text-align: center;
-}
+}
+.evaluate-box{
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.btn-review {
+  padding: 12rpx 40rpx;
+  border-radius: 12rpx;
+  font-size: 26rpx;
+  text-align: center;
+  background-color: #fff;
+  color: #1976d2;
+  border: 1px solid #1976d2;
+  margin-left: 20rpx;
+}

+ 79 - 4
miniprogram/module/care/pages/care/verifyRecord.ts

@@ -1,9 +1,15 @@
 import { Post } from "../../../../lib/request/method";
+import { cancelAppointmentMethod } from "../../request";
 Page({
   data: {
-    id: "",
+    id: 0,
+    recordListId: '',
     recordList: [],
     showAppointmentSuccess: true,
+    //是否显示修改预约按钮
+    isShowUpdateButton: false,
+    //isShowCancleButton
+    isShowCancleButton: false,
     qrCodeUrl: "",
     appointmentInfo: {
       operateTime: '',
@@ -17,7 +23,14 @@ Page({
   },
   onLoad(options: any) {
     if (options.id) {
-      this.getVerifyRecordList(options.id);
+      this.setData({
+        id: options?.id
+      })
+    }
+  },
+  onShow() {
+    if (this.data.id) {
+      this.getVerifyRecordList(this.data.id);
     }
   },
   // 生成二维码
@@ -41,8 +54,9 @@ Page({
       if (res && res.patientConditioningSwagItemOffline && JSON.stringify(res.patientConditioningSwagItemOffline) !== '{}') {
         this.setData({
           showAppointmentInfo: true,
-        });
-        this.setData({
+          isShowUpdateButton: res.isShowUpdateButton,
+          isShowCancleButton: res.isShowCancleButton,
+          recordListId: res.patientConditioningSwagItemOffline.id,
           appointmentInfo: res.patientConditioningSwagItemOffline,
         });
         this.generateQRCode(this.data.appointmentInfo.cpSupplierQrCode);
@@ -64,5 +78,66 @@ Page({
       console.error("核销记录列表", error);
     }
   },
+  // 去评价
+  onReview(e: WechatMiniprogram.TouchEvent) {
+    const { evaluateTime } = e.currentTarget.dataset.goods;
+    if (evaluateTime) {
+      // 线下服务评价详情
+      wx.navigateTo({
+        url: `/module/order/pages/offline-evaluateDetail/offline-evaluateDetail?goodsInfo=${encodeURIComponent(JSON.stringify(e.currentTarget.dataset.goods))}`,
+      });
+    } else {
+      // 去线下服务评价
+      wx.navigateTo({
+        url: `/module/order/pages/offline-evaluate/offline-evaluate?goodsInfo=${encodeURIComponent(JSON.stringify(e.currentTarget.dataset.goods))}`,
+      });
+
+    }
+  },
+
+  onCancelAppointment() {
+    wx.showModal({
+      title: "取消预约",
+      content: "预约取消后无法恢复!",
+      showCancel: true,
+      confirmText: "确定",
+      cancelText: "再想想",
+      success: async (res) => {
+        if (!res.confirm) return;
+        try {
+          wx.showLoading({ title: "取消中" });
+          await cancelAppointmentMethod(Number(this.data.recordListId || 0));
+          wx.hideLoading();
+          wx.showToast({ title: "取消成功", icon: "success" });
+          // 取消成功之后到取消预约页面
+          setTimeout(() => {
+            const appointmentInfoParam = encodeURIComponent(
+              JSON.stringify(this.data.appointmentInfo || {})
+            );
+            wx.navigateTo({
+              url: `/module/care/pages/cancelAppointment/cancelAppointment?appointmentInfo=${appointmentInfoParam}`,
+            });
+          }, 300);
+        } catch (error: any) {
+          wx.hideLoading();
+          wx.showToast({
+            title: error?.errMsg || "取消失败",
+            icon: "none",
+          });
+        }
+      },
+    });
+  },
+
+  onModifyAppointment() {
+    const arrangeTime =
+      (this.data.appointmentInfo as any).arrangeTime ||
+      (this.data.appointmentInfo as any).appointmentTime ||
+      "";
+
+    wx.navigateTo({
+      url: `/module/order/pages/appointment/appointment?mode=edit&arrangeTime=${encodeURIComponent(arrangeTime)}`,
+    });
+  },
 
 });

+ 89 - 68
miniprogram/module/care/pages/care/verifyRecord.wxml

@@ -1,82 +1,103 @@
 <t-navbar title="核销记录" left-arrow />
 <scroll-view class="page-container" scroll-y>
   <!-- 预约成功卡片 -->
-    <view class="appointment-success-card" wx:if="{{showAppointmentInfo}}">
-      <!-- 标题 -->
-      <view class="appointment-success-title">预约成功</view>
-      
-      <!-- 二维码 -->
-      <view class="qr-code-container" wx:if="{{qrCodeUrl}}">
-        <image 
-          class="qr-code-image" 
-          src="{{qrCodeUrl}}" 
-          mode="aspectFit"
-        />
-      </view>
-      
-      <!-- 预约详情 -->
-      <view class="appointment-info">
-        <view class="info-item" wx:if="{{appointmentInfo.arrangeTime}}">
-          <text class="info-label">开始服务时间:</text>
-          <text class="info-value">{{appointmentInfo.arrangeTime}}</text>
-        </view>
-        <view class="info-item" wx:if="{{appointmentInfo.arrangeDuration}}">
-          <text class="info-label">服务时长:</text>
-          <text class="info-value">{{appointmentInfo.arrangeDuration}}分钟</text>
-        </view>
-        <view class="info-item" wx:if="{{appointmentInfo.conditioningProgramSupplierName}}">
-          <text class="info-label">服务机构:</text>
-          <text class="info-value" wx:if="{{appointmentInfo.pieTime}}">{{appointmentInfo.conditioningProgramSupplierName}}</text>
-          <text class="info-value" wx:else>待分配</text>
-        </view>
-        <view class="info-item" wx:if="{{appointmentInfo.cpSupplierDetailAddress}}">
-          <text class="info-label">服务地址:</text>
-          <text class="info-value">{{appointmentInfo.cpSupplierDetailAddress}}</text>
-        </view>
-        <view class="info-item" wx:if="{{appointmentInfo.cpSupplierPhone}}">
-          <text class="info-label">联系电话:</text>
-          <text class="info-value">{{appointmentInfo.cpSupplierPhone}}</text>
-        </view>
-      </view>
-      
-      <!-- 提示信息 -->
-      <view class="appointment-tip">
-        <text class="tip-text">为了保证您的服务体验,请提前5分钟到达门店</text>
-      </view>
+  <view class="appointment-success-card" wx:if="{{showAppointmentInfo}}">
+    <!-- 标题 -->
+    <view class="appointment-success-title">预约成功</view>
+
+    <!-- 二维码 -->
+    <view class="qr-code-container" wx:if="{{qrCodeUrl}}">
+      <image class="qr-code-image" src="{{qrCodeUrl}}" mode="aspectFit" />
     </view>
-<view class="verify-record-page" wx:if="{{recordList.length>0}}">
-  <block wx:for="{{recordList}}" wx:key="id">
-    <view class="record-card">
-      <view class="record-header">
-        <text class="record-index">{{index + 1}}、</text>
-        <text class="record-time">{{item.operateTime}}</text>
+
+    <!-- 预约详情 -->
+    <view class="appointment-info">
+      <view class="info-item" wx:if="{{appointmentInfo.arrangeTime}}">
+        <text class="info-label">开始服务时间:</text>
+        <text class="info-value">{{appointmentInfo.arrangeTime}}</text>
       </view>
-      <view class="record-row" wx:if="{{item.operateBy}}">
-        <text class="label">操作人:</text>
-        <text style="color:black">{{item.operateBy}}</text>
+      <view class="info-item" wx:if="{{appointmentInfo.arrangeDuration}}">
+        <text class="info-label">服务时长:</text>
+        <text class="info-value">{{appointmentInfo.arrangeDuration}}分钟</text>
       </view>
-       <!-- 新增加的一列--机构 -->
-        <view class="record-row" wx:if="{{item.operateBy}}">
-        <text class="label">机构:</text>
-        <text style="color:black">{{item.operateBy}}</text>
+      <view class="info-item" wx:if="{{appointmentInfo.conditioningProgramSupplierName}}">
+        <text class="info-label">服务机构:</text>
+        <text class="info-value" wx:if="{{appointmentInfo.pieTime}}">{{appointmentInfo.conditioningProgramSupplierName}}</text>
+        <text class="info-value" wx:else>待分配</text>
       </view>
-       <!-- end -->
-      <view class="record-row" wx:if="{{item.feedback}}">
-        <text class="label">治疗备注:</text>
-        <text style="color:black">{{item.feedback}}</text>
+      <view class="info-item" wx:if="{{appointmentInfo.cpSupplierDetailAddress}}">
+        <text class="info-label">服务地址:</text>
+        <text class="info-value">{{appointmentInfo.cpSupplierDetailAddress}}</text>
       </view>
-      <view class="record-row" wx:if="{{item.acuPointNames}}">
-        <text class="label">穴位:</text>
-        <text style="color:black">{{item.acuPointNames}}</text>
+      <view class="info-item" wx:if="{{appointmentInfo.cpSupplierPhone}}">
+        <text class="info-label">联系电话:</text>
+        <text class="info-value">{{appointmentInfo.cpSupplierPhone}}</text>
+      </view>
+    </view>
+
+    <!-- 提示信息 -->
+    <view class="appointment-tip">
+      <text class="tip-text">为了保证您的服务体验,请提前5分钟到达门店</text>
+    </view>
+    <!-- 取消预约/修改预约 -->
+    <view
+      class="appointment-actions"
+      wx:if="{{isShowCancleButton || isShowUpdateButton}}"
+    >
+      <view
+        class="action-btn cancel"
+        wx:if="{{isShowCancleButton}}"
+        catchtap="onCancelAppointment"
+      >
+        取消预约
       </view>
-       <view class="record-row opt-img" wx:if="{{item.photo}}">
-        <text class="label">操作照片:</text>
-     <image src="{{item.photo}}" class="img-box" />
+      <view
+        class="action-btn modify"
+        wx:if="{{isShowUpdateButton}}"
+        catchtap="onModifyAppointment"
+      >
+        修改预约
       </view>
     </view>
-  </block>
-</view>
- <view wx:else class="empty-state">
+  </view>
+  <view class="verify-record-page" wx:if="{{recordList.length>0}}">
+    <block wx:for="{{recordList}}" wx:key="id">
+      <view class="record-card">
+        <view class="evaluate-box">
+          <view class="record-header">
+            <text class="record-index">{{index + 1}}、</text>
+            <text class="record-time">{{item.operateTime}}</text>
+          </view>
+          <view class="btn-review" catchtap="onReview" data-goods="{{item}}">
+            {{item.evaluateTime ? "已评价" : "评价"}}
+          </view>
+        </view>
+        <view class="record-row" wx:if="{{item.operateBy}}">
+          <text class="label">操作人:</text>
+          <text style="color:black">{{item.operateBy}}</text>
+        </view>
+        <!-- 新增加的一列--机构 -->
+        <view class="record-row" wx:if="{{item.conditioningProgramSupplierName}}">
+          <text class="label">机构:</text>
+          <text style="color:black">{{item.conditioningProgramSupplierName}}</text>
+        </view>
+        <!-- end -->
+        <view class="record-row" wx:if="{{item.feedback}}">
+          <text class="label">治疗备注:</text>
+          <text style="color:black">{{item.feedback}}</text>
+        </view>
+        <view class="record-row" wx:if="{{item.acuPointNames}}">
+          <text class="label">穴位:</text>
+          <text style="color:black">{{item.acuPointNames}}</text>
+        </view>
+        <view class="record-row opt-img" wx:if="{{item.photo}}">
+          <text class="label">操作照片:</text>
+          <image src="{{item.photo}}" class="img-box" />
+        </view>
+      </view>
+    </block>
+  </view>
+  <view wx:else class="empty-state">
     <text class="empty-text">暂无核销数据</text>
   </view>
 </scroll-view>

+ 0 - 1
miniprogram/module/care/pages/careDetail/careDetail.ts

@@ -101,7 +101,6 @@ Page({
 //健康咨询记录
 onChatRecord(e: any) {
   const item = e.currentTarget.dataset.item;
-  console.log(item,"咨询记录数据",e)
   if (item) {
   wx.navigateTo({
       url: `/module/chats/pages/consultation-record/consultation-record?item=${encodeURIComponent(JSON.stringify(item))}`,

+ 14 - 24
miniprogram/module/care/pages/offlineTreatment/offlineTreatment.ts

@@ -13,7 +13,6 @@ Page({
     treatmentId: "",
   },
   async onLoad(options: any) {
-    console.log(options, '传过来的参数')
     if (options.id) {
       this.setData({
         treatmentId: options.id,
@@ -22,11 +21,21 @@ Page({
     // await this.getOfflineTreatmentList("");
   },
   async onShow() {
-    await this.getOfflineTreatmentList("");
+    // 返回本页时要沿用当前 Tab 的筛选条件
+    await this.getOfflineTreatmentList(this.data.currentTab ?? "");
   },
   // 跳转到核销记录
   onRecord(e: any) {
-    const id = e.currentTarget.dataset.id;
+    const { item } = e.currentTarget.dataset;
+    const { id, offlineId, conditioningProgramName, offlineDuration, itemImgSecond, estimatedStartDate } = item;
+    const goodsInfo = {
+      name: conditioningProgramName || '',
+      duration: offlineDuration || 0,
+      image: itemImgSecond || '',
+      offlineId: offlineId || '',
+      serviceTime: estimatedStartDate || '',
+    }
+    wx.setStorageSync('goodsInfo', JSON.stringify(goodsInfo));
     if (id) {
       wx.navigateTo({
         url: `/module/care/pages/care/verifyRecord?id=${id}`,
@@ -35,7 +44,6 @@ Page({
   },
   // 去预约
   goAppointment(e: any) {
-
     const { item } = e.currentTarget.dataset;
     const { offlineId, conditioningProgramName, offlineDuration, itemImgSecond, estimatedStartDate } = item;
     const goodsInfo = {
@@ -45,11 +53,11 @@ Page({
       offlineId: offlineId || '',
       serviceTime: estimatedStartDate || '',
     }
-    console.log(goodsInfo, "goodsInfo======去预约页面")
     if (offlineId) {
+      wx.setStorageSync('goodsInfo', JSON.stringify(goodsInfo));
       // 去预约页面
       wx.navigateTo({
-        url: `/module/order/pages/appointment/appointment?goodsInfo=${JSON.stringify(goodsInfo)}`,
+        url: `/module/order/pages/appointment/appointment`,
       });
     } else {
       wx.showToast({
@@ -57,35 +65,21 @@ Page({
         icon: "none",
       });
     }
-    // const id = e.currentTarget.dataset.id;
-    // if (id) {
-    //   // 跳转到预约页面,传递项目id
-    //   wx.navigateTo({
-    //     url: `/module/care/pages/careDetail/careDetail?id=${id}`,
-    //   });
-    // }
   },
   onTabChange(e: any) {
     const progress = e.detail.value;
     this.setData({
       currentTab: progress,
     });
-    // 不立即清空数据,避免空状态闪烁
     this.getOfflineTreatmentList(progress);
   },
   // 获取线下非药物治疗记录列表
   async getOfflineTreatmentList(progress: string) {
-    // 启用加载态,保留当前列表,避免空态闪烁
     this.setData({ isLoading: true });
     try {
       const res = await getOfflineTreatmentListMethod(progress);
-      console.log(res.data, "res.data")
-      // 确保 res.data 是数组,避免 undefined 或 null 导致的错误
       const list = Array.isArray(res?.data) ? res.data : [];
-      // 如果传入了treatmentId,标记匹配的项目为高亮并置顶
       const treatmentId = this.data.treatmentId;
-      console.log(treatmentId, "treatmentId")
-      console.log(list, "list")
       if (treatmentId && list.length > 0) {
         const highlightedList = list.map((item: any) => ({
           ...item,
@@ -95,7 +89,6 @@ Page({
         const highlightedItem = highlightedList.find((item: any) => item.isHighlighted);
         const otherItems = highlightedList.filter((item: any) => !item.isHighlighted);
         const sortedList = highlightedItem ? [highlightedItem, ...otherItems] : highlightedList;
-        console.log(sortedList, "sortedList")
 
         this.setData({ treatmentList: sortedList, isLoading: false });
       } else {
@@ -103,9 +96,6 @@ Page({
         this.setData({ treatmentList: list, isLoading: false });
       }
     } catch (error: any) {
-
-      console.error('获取数据失败:', error);
-      // 只有在真正出错时才显示错误提示
       wx.showToast({
         title: error.errMsg || "获取数据失败",
         icon: "none",

+ 2 - 1
miniprogram/module/care/pages/offlineTreatment/offlineTreatment.wxml

@@ -10,9 +10,10 @@
     </view>
    <view wx:elif="{{treatmentList.length>0}}">
       <block wx:for="{{treatmentList}}" wx:key="id">
-        <view class="treatment-detail-card {{item.isHighlighted ? 'treatment-detail-card--highlighted' : ''}}" bindtap="onRecord" data-id="{{item.id}}">
+        <view class="treatment-detail-card {{item.isHighlighted ? 'treatment-detail-card--highlighted' : ''}}" bindtap="onRecord" data-item="{{item}}">
           <view>
             <view class="row between" wx:if="{{item.conditioningProgramName}}">
+
               <text class="label bold">项目:{{item.conditioningProgramName}}</text>
             </view>
             <view class="row" wx:if="{{item.operateBy}}">

+ 0 - 1
miniprogram/module/care/pages/reportRecord/reportRecord.ts

@@ -24,7 +24,6 @@ Page({
       wx.showLoading({ title: "加载中" });
       try {
         const data = await healthReportListMethod();
-        console.log("data333",data);
         this.setData({
           "_healthReportList.data": data,
           "_healthReportList.loaded": true,

+ 31 - 1
miniprogram/module/care/request.ts

@@ -20,4 +20,34 @@ export function getOfflineTreatmentListMethod(progress: string) {
       return data;
     }
   })
-}
+}
+//患者线下调理方案修改预约时间  offlineId线下任务id  time预约时间
+export function updateAppointmentMethod(offlineId: string, time: string) {
+  return Post(`/patientCrManage/pofflineCpUpdateApply/${offlineId}`, {}, {
+    params: {
+      time,
+    },
+    transform({ data }: AnyObject) {
+      return data;
+    },
+  })
+}
+  //患者线下调理方案预约取消
+  export function cancelAppointmentMethod(offlineId: number) {
+    return Post(`/patientCrManage/pofflineCpCancleApply/${offlineId}`, {}, {
+      transform({ data }: AnyObject) {
+        return data;
+      }
+    })
+  }
+  //患者线下调理方案预约
+  export function appointmentMethod(offlineId: string, time: string) {
+    return Post(`/patientCrManage/pofflineCpApply/${offlineId}`, {}, {
+      params: {
+        time,
+      },
+      transform({ data }: AnyObject) {
+        return data;
+      }
+    })
+  }

+ 0 - 1
miniprogram/module/charts/components/record-index/health-index.ts

@@ -84,7 +84,6 @@ export function healthIndex2Chart(model: App.Health.Index.Model, index = 0): Any
 
 export function transformHealthIndex2Chart(data: App.Health.Index.Data[]): AnyObject[] {
   const list = gather(data, healthIndex2Chart)
-  console.log(list, 'transformHealthIndex2Chart-->list');
 
   const charts = [];
   for (const item of list) {

+ 14 - 8
miniprogram/module/chats/components/guide/guide.wxml

@@ -2,6 +2,12 @@
   module.exports.getClassName = function (active) {
     return active ? '' : 'disabled';
   }
+  module.exports.cellClass = function (active, isLast) {
+    var cls = 'cell-border-gradient';
+    if (!active) cls += ' disabled';
+    if (isLast) cls += ' no-border';
+    return cls;
+  }
 </wxs>
 <!--module/chats/components/guide/guide.wxml-->
 <view class="chat-card left steward-wrapper">
@@ -36,24 +42,24 @@
     >
       <t-icon name="chevron-right" color="#CCCCCC" size="40rpx" slot="note" />
     </t-cell>
-    <t-cell 
-      t-class="cell-border-gradient {{_.getClassName(active)}}" 
+    <t-cell
+      t-class="{{_.cellClass(active, !i18n.chats.consult)}}"
       t-class-hover="custom-cell-hover"
        t-class-title="health-analysis-title"
-      title="3、{{i18n.chats.healthData}}" 
-      hover="{{active}}" 
+      title="3、{{i18n.chats.healthData}}"
+      hover="{{active}}"
       bind:tap="handleC"
       style="--td-cell-hover-color: #F5F5F5;"
     >
       <t-icon name="chevron-right" color="#CCCCCC" size="40rpx" slot="note" />
     </t-cell>
-    <t-cell 
+    <t-cell
       wx:if="{{i18n.chats.consult}}"
-      t-class="cell-border-gradient no-border {{_.getClassName(active)}}" 
+      t-class="{{_.cellClass(active, true)}}"
       t-class-hover="custom-cell-hover"
        t-class-title="health-analysis-title"
-      title="4、{{i18n.chats.consult}}" 
-      hover="{{active}}" 
+      title="4、{{i18n.chats.consult}}"
+      hover="{{active}}"
       bind:tap="handleE"
       style="--td-cell-hover-color: #F5F5F5;"
     >

+ 0 - 4
miniprogram/module/chats/components/questionnaire/questionnaire.ts

@@ -36,7 +36,6 @@ Component({
     attached: function () {
       let isAnalysis: number;
       isAnalysis = wx.getStorageSync("isAnalysis");
-      console.log("isAnalysis", isAnalysis);
       if (isAnalysis === 3 || isAnalysis === 4 || isAnalysis === 5) {
         // 对话管家
         this._start();
@@ -93,9 +92,7 @@ Component({
       this.triggerEvent("to", option.detail.id);
     },
     boxBottom(event: boxBottom) {
-      console.log("event==boxBottom", event);
       this.setData({ inputBoxBottom: event.detail.inputBoxBottom });
-      console.log("this.data.inputBoxBottom", this.data.inputBoxBottom);
       this.triggerEvent("boxBottom", {
         inputBoxBottom: this.data.inputBoxBottom + 100,
       });
@@ -229,7 +226,6 @@ Component({
       }
       try {
         // messageType 1 是随访。messageType 2 是健康评估和对话管家。messageType 3 是在线咨询
-        console.log("this.data.messageType", this.data.messageType);
         if (this.data.messageType === 1) {
           this._createMessage({
             id: `again.${Date.now()}`,

+ 3 - 2
miniprogram/module/chats/pages/consultation-record/consultation-record.json

@@ -3,6 +3,7 @@
   "component": true,
   "usingComponents": {
     "t-navbar": "tdesign-miniprogram/navbar/navbar",
-    "t-avatar": "tdesign-miniprogram/avatar/avatar"
+    "t-avatar": "tdesign-miniprogram/avatar/avatar",
+    "towxml": "../../towxml/towxml"
   }
-}
+}

+ 69 - 19
miniprogram/module/chats/pages/consultation-record/consultation-record.scss

@@ -6,16 +6,20 @@
   box-sizing: border-box;
   overflow-y: auto;
   padding-top: 10px;
+  padding-bottom: env(safe-area-inset-bottom);
   background-color: #f5f5f5;
 }
 
 .consultation-container {
-  padding: 40rpx 30rpx;
-  background-color: #ffffff;
+  padding: 0rpx 0rpx calc(30rpx + env(safe-area-inset-bottom));
+  background-color: #f5f5f5;
 }
 
 .consultation-session {
-  margin-bottom: 60rpx;
+  margin-bottom: 20rpx;
+  background-color: #ffffff;
+  border-radius: 16rpx;
+  padding: 30rpx 24rpx;
 
   &:last-child {
     margin-bottom: 0;
@@ -26,29 +30,66 @@
   text-align: center;
   font-size: 24rpx;
   color: #999999;
-  margin: 0rpx 0 10rpx;
+  margin: 10rpx 0 20rpx;
 }
 
 .status-message {
   text-align: center;
   font-size: 24rpx;
   color: #999999;
-  margin: 15rpx 0 15rpx 0;
+  margin: 10rpx 0 20rpx;
+}
+
+.session-end {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  // margin-top: 48rpx;
+  padding-top: 36rpx;
+  border-top: 1rpx solid #f0f0f0;
+  justify-content: center;
+
+  &__line {
+    flex: 1;
+    height: 1rpx;
+    background-color: #e0e0e0;
+    align-self: center;
+  }
+
+  &__content {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    padding: 0 24rpx;
+  }
+
+  &__time {
+    font-size: 24rpx;
+    color: #999999;
+    line-height: 1.5;
+  }
+
+  &__text {
+    font-size: 24rpx;
+    color: #999999;
+    line-height: 1.5;
+  }
 }
 
 .messages-list {
   display: flex;
   flex-direction: column;
-  gap: 30rpx;
+  // gap: 30rpx;
+  // padding-bottom: 50rpx;
 }
 
 .message-item {
   display: flex;
-  align-items: flex-start;
-  margin: 20rpx 0 20rpx 0;
+  // align-items: flex-start;
+  margin-bottom: 30rpx;
 
   &--agent {
-    justify-content: flex-start;
+    // justify-content: flex-start;
   }
 
   &--user {
@@ -61,23 +102,23 @@
 }
 
 .message-bubble {
-  max-width: 500rpx;
+  max-width: 480rpx;
   padding: 20rpx 24rpx;
-  border-radius: 12rpx;
+  border-radius: 16rpx;
   position: relative;
   word-wrap: break-word;
-  margin-left: 20rpx;
 
   &--agent {
     background-color: #ffffff;
     border: 1rpx solid #e5e5e5;
+    margin-left: 16rpx;
 
     // 三角形尾巴(指向左侧头像)
     &::before {
       content: "";
       position: absolute;
       left: -10rpx;
-      top: 16rpx;
+      top: 20rpx;
       width: 0;
       height: 0;
       border-top: 10rpx solid transparent;
@@ -90,8 +131,8 @@
     &::after {
       content: "";
       position: absolute;
-      left: -11rpx;
-      top: 16rpx;
+      left: -12rpx;
+      top: 20rpx;
       width: 0;
       height: 0;
       border-top: 10rpx solid transparent;
@@ -103,13 +144,14 @@
 
   &--user {
     background-color: #409eff;
-    margin-right: 20rpx;
+    margin-right: 16rpx;
+
     // 三角形尾巴(指向右侧头像)
     &::before {
       content: "";
       position: absolute;
       right: -10rpx;
-      top: 16rpx;
+      top: 20rpx;
       width: 0;
       height: 0;
       border-top: 10rpx solid transparent;
@@ -121,27 +163,31 @@
 
 .message-text {
   font-size: 28rpx;
-  color: #000000;
-  line-height: 1.5;
+  color: #333333;
+  line-height: 1.6;
   word-wrap: break-word;
 
   &--user {
     color: #ffffff;
   }
 }
+
 .message-item--system {
   width: 100%;
   align-items: center;
   justify-content: center;
+
   .message-bubble--system {
     width: 100%;
     text-align: center;
+
     .message-text--system {
       color: #999999 !important;
       font-size: 24rpx;
     }
   }
 }
+
 .message-image {
   width: 200rpx;
   height: 200rpx;
@@ -149,3 +195,7 @@
   display: block;
   border-radius: 8rpx;
 }
+
+.bottom-safe-space {
+  height: calc(24rpx + env(safe-area-inset-bottom));
+}

+ 0 - 3
miniprogram/module/chats/pages/consultation-record/consultation-record.ts

@@ -35,7 +35,6 @@ Page({
   },
   observers: {},
   onLoad(options: any) {
-    console.log(options, "咨询记录数据");
     if (options.item) {
       const item = JSON.parse(decodeURIComponent(options.item));
       this.setData({
@@ -58,7 +57,6 @@ Page({
         `/consultManage/pageConsult?pageNum=${this.data.page}&pageSize=${this.data.pageSize}`,
         {}
       );
-      console.log("res=咨询记录列表", res.data.data);
 
       if (res.data && res.data.data && res.data.data.length > 0) {
         // 追加新数据到现有列表
@@ -101,7 +99,6 @@ Page({
   },
 
   onScrollToLower() {
-    console.log("onScrollToLower");
     if (this.data.isPage) {
       this.loadData();
     }

+ 21 - 18
miniprogram/module/chats/pages/consultation-record/consultation-record.wxml

@@ -1,12 +1,12 @@
 <!--module/chats/pages/consultation-record/consultation-record.wxml-->
 <t-navbar title="{{i18n.consultChat.title}}" left-arrow />
-<scroll-view 
-  id="scrollview" 
-  class="page-scroll__container" 
-  type="list" 
-  scroll-y 
-  enhanced="{{true}}" 
-  enable-passive 
+<scroll-view
+  id="scrollview"
+  class="page-scroll__container"
+  type="list"
+  scroll-y
+  enhanced="{{true}}"
+  enable-passive
   style="height: calc(100vh - 180rpx);"
   bindscrolltolower="onScrollToLower"
   lower-threshold="{{50}}"
@@ -17,7 +17,7 @@
       <view class="consultation-session">
         <view class="timestamp">{{item.startTime}}</view>
         <view class="status-message">开始{{i18n.consultChat._}}</view>
-        
+
         <!-- 消息列表 -->
         <view class="messages-list">
           <block wx:for="{{item.items}}" wx:key="id" wx:for-item="message">
@@ -28,10 +28,10 @@
                 <text class="message-text message-text--system">{{message.messageContent}}</text>
               </view>
             </view>
-            
+
             <!-- 患者消息 (sendType === '1') - 右侧显示 -->
             <view wx:elif="{{message.sendType === '1'}}" class="message-item message-item--user">
-              <view class="message-bubble {{message.messageType==='1'?'message-bubble--user':''}}">
+              <view class="message-bubble message-bubble--user">
                 <!-- 文本消息 -->
                 <block wx:if="{{message.messageType === '1'}}">
                   <text class="message-text message-text--user">{{message.messageContent}}</text>
@@ -43,14 +43,14 @@
               </view>
               <t-avatar class="message-avatar" icon="user" size="40px" />
             </view>
-            
+
             <!-- 医生/AI消息 (sendType === '2' 或 '4') - 左侧显示 -->
             <view wx:else class="message-item message-item--agent">
               <t-avatar class="message-avatar message-avatar--agent" icon="{{message.sendType === '2' ? 'user' : 'service'}}" size="40px" />
-              <view class="message-bubble {{message.messageType==='1'?'message-bubble--agent':''}}">
+              <view class="message-bubble message-bubble--agent">
                 <!-- 文本消息 -->
                 <block wx:if="{{message.messageType === '1'}}">
-                  <text class="message-text">{{message.messageContent}}</text>
+                  <towxml markdown="{{message.messageContent}}" theme="light" />
                 </block>
                 <!-- 图片消息 -->
                 <block wx:elif="{{message.messageType === '2'}}">
@@ -60,13 +60,17 @@
             </view>
           </block>
         </view>
-        
+
         <!-- 咨询结束 -->
-        <view class="timestamp">{{item.endTime}}</view>
-        <view class="status-message">{{i18n.consultChat._}}结束</view>
+        <view class="session-end">
+          <view class="session-end__content">
+            <view class="session-end__time">{{item.endTime}}</view>
+            <view class="session-end__text">{{i18n.consultChat._}}结束</view>
+          </view>
+        </view>
       </view>
     </block>
-    
+
     <!-- 加载更多/无更多数据提示 -->
     <view class="loading-container" style="text-align: center; padding: 20rpx; color: #999; font-size: 24rpx;" wx:if="{{isPage}}">
       <view wx:if="{{isLoading}}">加载中...</view>
@@ -75,4 +79,3 @@
   </view>
 </scroll-view>
 
-  

+ 2 - 2
miniprogram/module/chats/pages/index/index.scss

@@ -23,9 +23,9 @@
   right: 0;
   bottom: 0;
   z-index: 100;
-  background-color: #fff;
+  // background-color: #fff;
   padding-bottom: env(safe-area-inset-bottom);
-  box-shadow: 0 -2rpx 6rpx rgba(0, 0, 0, 0.1);
+  // box-shadow: 0 -2rpx 6rpx rgba(0, 0, 0, 0.1);
 }
 
 .chat {

+ 13 - 14
miniprogram/module/health/pages/scheme/scheme.scss

@@ -209,31 +209,30 @@
   left: 0;
   right: 0;
   width: 100%;
-  min-height: 98rpx;
-  // background: linear-gradient(90deg, #1d6ff6 0%, #4a90ff 100%);
-  background: white;
+  background: #fff;
   display: flex;
   align-items: center;
-  justify-content: flex-end;
+  justify-content: center;
   z-index: 100;
-  box-shadow: 0 -4rpx 20rpx rgba(29, 111, 246, 0.2);
+  box-shadow: 0 -2rpx 12rpx rgba(0, 0, 0, 0.06);
   box-sizing: border-box;
-  padding-top: 24rpx;
-  padding-right: 32rpx;
-  
+  padding: 20rpx 32rpx 32rpx;
+
   .buy-btn-text {
-    color: #ffffff;
+    width: 100%;
+    height: 88rpx;
+    line-height: 88rpx;
+    color: #fff;
     font-size: 30rpx;
+    font-weight: 600;
     letter-spacing: 2rpx;
-    line-height: 1.2;
     text-align: center;
-    background: #1d6ff6;
-    padding: 10px 30px;
-    border-radius: 5px;
+    background: linear-gradient(135deg, #2f6df6 0%, #1f5de8 100%);
+    border-radius: 44rpx;
   }
 }
 
-// 底部占位,避免内容被按钮遮盖(98rpx按钮高度 + 安全区域)
+// 底部占位,避免内容被按钮遮盖
 .scheme-bottom-placeholder {
   height: 150rpx;
   width: 100%;

+ 0 - 2
miniprogram/module/health/pages/scheme/scheme.ts

@@ -39,7 +39,6 @@ Component({
 
         this.setData({ dataset });
       } catch (error) {
-        console.log(error);
 
         getTickleContext
           .call(this)
@@ -73,7 +72,6 @@ Component({
       const type: string = (item.buyType || "").toLowerCase();
       const url: string = item.buyUrl || "";
       const shortImageUrl: string = item.shortImageUrl || "";
-      console.log(item, "item", type, url);
       //  跳转小程序
       if (type === "miniprogram") {
         // 如果没有跳转链接,直接显示小程序码或提示错误

+ 1 - 1
miniprogram/module/health/pages/scheme/scheme.wxml

@@ -47,7 +47,7 @@
   </scroll-view>
   
   <!-- 底部固定去购买按钮 -->
-  <view class="scheme-bottom-buy-btn" style="padding-bottom: {{container.safeBottomOffset-10}}px;" bindtap="goToProductPage" wx:if="{{true}}">
+  <view class="scheme-bottom-buy-btn" bindtap="goToProductPage" wx:if="{{true}}">
     <view class="buy-btn-text">{{i18n.orderText.goPay}}</view>
   </view>
 </view>

+ 0 - 1
miniprogram/module/health/pages/status/status.ts

@@ -30,7 +30,6 @@ Component({
           }
         });
         this.setData({ health })
-        console.log(health, '指标数据health');
       } catch (error) {
         getTickleContext.call(this).showErrorMessage(error.errMsg, 0)
       }

+ 0 - 1
miniprogram/module/health/tools/health-index.ts

@@ -81,7 +81,6 @@ export function healthIndex2Progress(model: App.Health.Index.Model[]): AnyObject
       valueType, valueOffset: `${valueOffset}%`,
       valueOffsetLeft, valueOffsetRight,
     }
-    // console.log(a, '12345-->', length);
     
     return a;
   })

+ 6 - 0
miniprogram/module/order/components/after-sale-type-popup/after-sale-type-popup.json

@@ -0,0 +1,6 @@
+{
+  "component": true,
+  "usingComponents": {
+    "t-popup": "tdesign-miniprogram/popup/popup"
+  }
+}

+ 210 - 0
miniprogram/module/order/components/after-sale-type-popup/after-sale-type-popup.scss

@@ -0,0 +1,210 @@
+.after-sale-popup {
+  height: 56vh;
+  min-height: 520rpx;
+  background: #fff;
+  border-top-left-radius: 32rpx;
+  border-top-right-radius: 32rpx;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+
+.after-sale-popup__header {
+  height: 116rpx;
+  position: relative;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-weight: 600;
+  font-size: 34rpx;
+  color: #1f1f1f;
+  background: #f2f2f2;
+}
+
+.after-sale-popup__close {
+  position: absolute;
+  right: 24rpx;
+  height: 116rpx;
+  width: 72rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 48rpx;
+  color: #444;
+}
+
+.after-sale-popup__header-spacer {
+  width: 72rpx;
+  height: 1px;
+}
+
+.after-sale-popup__content {
+  flex: 1;
+  overflow: auto;
+  padding: 0 24rpx;
+  box-sizing: border-box;
+  background: #fff;
+}
+
+.after-sale-popup__label {
+  margin-top: 30rpx;
+  margin-bottom: 20rpx;
+  font-size: 28rpx;
+  color: #222;
+}
+
+.after-sale-type-btn-list {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 20rpx;
+}
+
+.after-sale-type-btn {
+  border-radius: 10rpx;
+  border: 2rpx solid #d7d7d7;
+  height: 72rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 26rpx;
+  color: #3a3a3a;
+  background: #fff;
+  padding: 0 36rpx;
+  box-sizing: border-box;
+}
+
+.after-sale-type-btn--active {
+  border-color: #2b6df5;
+  color: #1D6FF6;
+  background: #f7faff;
+}
+
+.after-sale-type-card {
+  margin-top: 24rpx;
+  border-radius: 12rpx;
+  padding: 18rpx 20rpx;
+  display: flex;
+  align-items: center;
+  gap: 28rpx;
+  background: #f3f3f3;
+  border: 1rpx solid #ececec;
+  box-sizing: border-box;
+}
+
+.after-sale-type-card--active {
+  border-color: #2b6df5;
+  background: #fff;
+}
+
+.after-sale-type-card__left {
+  flex-shrink: 0;
+  // margin-right: 20rpx;
+}
+
+.after-sale-type-card__icon {
+  width: 132rpx;
+  height: 108rpx;
+  background: #e6e6e6;
+  border-radius: 8rpx;
+  position: relative;
+}
+
+.after-sale-type-card__placeholder {
+  width: 132rpx;
+  height: 108rpx;
+  background: #f5f5f5;
+  border-radius: 8rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border: 1px solid #e8e8e8;
+}
+
+.placeholder-icon {
+  font-size: 48rpx;
+}
+
+.after-sale-type-card__icon::after {
+  content: "";
+  width: 28rpx;
+  height: 20rpx;
+  border: 2rpx solid #b8b8b8;
+  border-radius: 4rpx;
+  position: absolute;
+  left: 50%;
+  top: 50%;
+  transform: translate(-50%, -50%);
+}
+
+.after-sale-type-card__main {
+  flex: 1;
+  min-width: 0;
+  height: 90rpx;
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+}
+
+.after-sale-type-card__name {
+  font-size: 28rpx;
+  font-weight: 600;
+  color: #2a2a2a;
+}
+
+.after-sale-type-card__meta {
+  font-size: 24rpx;
+  color: #989898;
+}
+
+.after-sale-type-card__right {
+  flex-shrink: 0;
+  height: 90rpx;
+  min-width: 110rpx;
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  align-items: flex-end;
+}
+
+.after-sale-type-card__price {
+  font-size: 28rpx;
+  font-weight: 600;
+  color: #2a2a2a;
+}
+
+.after-sale-type-card__count {
+  font-size: 24rpx;
+  color: #989898;
+}
+
+.after-sale-popup__footer {
+  flex-shrink: 0;
+  padding: 20rpx 32rpx 32rpx;
+  background: #fff;
+  box-shadow: 0 -2rpx 12rpx rgba(0, 0, 0, 0.06);
+}
+
+.after-sale-popup__next-btn {
+  width: 100%;
+  height: 88rpx;
+  line-height: 88rpx;
+  border-radius: 44rpx;
+  background: linear-gradient(135deg, #2f6df6 0%, #1f5de8 100%);
+  color: #fff;
+  text-align: center;
+  font-size: 30rpx;
+  font-weight: 600;
+  letter-spacing: 2rpx;
+  transition: transform 0.16s ease, opacity 0.16s ease;
+}
+
+.after-sale-popup__next-btn:active {
+  opacity: 0.9;
+  transform: scale(0.98);
+}
+
+.title {
+  flex: 1;
+  text-align: center;
+  line-height: 1;
+}

+ 75 - 0
miniprogram/module/order/components/after-sale-type-popup/after-sale-type-popup.ts

@@ -0,0 +1,75 @@
+import { Get } from "../../../../lib/request/method";
+Component({
+  behaviors: [],
+  properties: {
+    visible: { type: Boolean, value: false },
+    siteOption: {
+      type: Object,
+      value: {
+        name: "",
+        price: 0,
+        meta1: "",
+        meta2: "",
+      },
+    },
+  },
+  data: {
+    selectedKey: "",
+    afterSaleTypes: [] as Array<{ label: string; value: string }>,
+  },
+  observers: {
+    visible(v: boolean) {
+      if (v && this.data.afterSaleTypes.length > 0) {
+        this.setData({ selectedKey: this.data.afterSaleTypes[0].value });
+      }
+    },
+  },
+  lifetimes: {
+    attached() {
+      this.fetchAfterSaleTypes();
+    },
+  },
+  methods: {
+    async fetchAfterSaleTypes() {
+      try {
+        const res = await Get<any[]>("/dict/getDicts");
+        const afterSaleDict = (res as any)?.data?.find(
+          (item: any) => item.dictType === "aftersale_type"
+        );
+        if (afterSaleDict?.items?.length) {
+          const types = afterSaleDict.items.map((item: any) => ({
+            label: item.dictLabel,
+            value: item.dictValue,
+          }));
+          this.setData({
+            afterSaleTypes: types,
+            selectedKey: types[0].value,
+          });
+        }
+      } catch {
+        wx.showToast({ title: "获取售后类型失败", icon: "none" });
+      }
+    },
+    onPopupVisibleChange(e: WechatMiniprogram.CustomEvent<{ visible: boolean }>) {
+      if (!e?.detail?.visible) {
+        this.triggerEvent("close");
+      }
+    },
+    onClose() {
+      this.triggerEvent("close");
+    },
+    onSelectType(e: WechatMiniprogram.TouchEvent) {
+      const type = (e?.currentTarget?.dataset as { type?: string })?.type;
+      if (!type) return;
+      this.setData({ selectedKey: type });
+    },
+    onNext() {
+      const key = this.data.selectedKey;
+      if (!key) {
+        wx.showToast({ title: "请选择售后类型", icon: "none" });
+        return;
+      }
+      this.triggerEvent("next", { selectedKey: key });
+    },
+  },
+});

+ 57 - 0
miniprogram/module/order/components/after-sale-type-popup/after-sale-type-popup.wxml

@@ -0,0 +1,57 @@
+<t-popup
+  placement="bottom"
+  visible="{{visible}}"
+  show-overlay="{{true}}"
+  close-on-overlay-click="{{true}}"
+  bind:visible-change="onPopupVisibleChange"
+>
+  <view class="after-sale-popup">
+    <view class="after-sale-popup__header">
+      <view class="title">选择售后类型</view>
+      <text class="after-sale-popup__close" catchtap="onClose">×</text>
+      <view class="after-sale-popup__header-spacer" />
+    </view>
+
+    <view class="after-sale-popup__content">
+      <view class="after-sale-popup__label">请选择售后类型:</view>
+
+      <view class="after-sale-type-btn-list">
+        <view
+          wx:for="{{afterSaleTypes}}"
+          wx:key="value"
+          class="after-sale-type-btn {{selectedKey === item.value ? 'after-sale-type-btn--active' : ''}}"
+          data-type="{{item.value}}"
+          bindtap="onSelectType"
+        >
+          {{item.label}}
+        </view>
+      </view>
+
+      <view
+        class="after-sale-type-card"
+        data-type="site"
+      >
+        <view class="after-sale-type-card__left">
+          <image class="after-sale-type-card__icon" src="{{siteOption.image}}" mode="aspectFill" wx:if="{{siteOption.image}}" />
+          <view class="after-sale-type-card__placeholder" wx:else>
+            <text class="placeholder-icon">📦</text>
+          </view>
+        </view>
+
+        <view class="after-sale-type-card__main">
+          <view class="after-sale-type-card__name">{{siteOption.name}}</view>
+          <view class="after-sale-type-card__meta">{{siteOption.meta1}}</view>
+        </view>
+
+        <view class="after-sale-type-card__right">
+          <view class="after-sale-type-card__price">¥{{siteOption.price}}</view>
+          <view class="after-sale-type-card__count">{{siteOption.meta2}}</view>
+        </view>
+      </view>
+    </view>
+
+    <view class="after-sale-popup__footer">
+      <view class="after-sale-popup__next-btn" bindtap="onNext">下一步</view>
+    </view>
+  </view>
+</t-popup>

+ 10 - 0
miniprogram/module/order/components/refund-confirm-popup/refund-confirm-popup.json

@@ -0,0 +1,10 @@
+{
+  "component": true,
+  "usingComponents": {
+    "t-popup": "tdesign-miniprogram/popup/popup",
+    "t-picker": "tdesign-miniprogram/picker/picker",
+    "t-picker-item": "tdesign-miniprogram/picker-item/picker-item",
+    "t-icon": "tdesign-miniprogram/icon/icon",
+    "t-button": "tdesign-miniprogram/button/button"
+  }
+}

+ 282 - 0
miniprogram/module/order/components/refund-confirm-popup/refund-confirm-popup.scss

@@ -0,0 +1,282 @@
+.refund-confirm-popup {
+  height: 60vh;
+  min-height: 760rpx;
+  background: #fff;
+  border-top-left-radius: 24rpx;
+  border-top-right-radius: 24rpx;
+  overflow: hidden;
+  display: flex;
+  flex-direction: column;
+}
+
+.refund-confirm-popup__header {
+  height: 100rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  position: relative;
+  font-size: 34rpx;
+  font-weight: 600;
+  background: #f2f2f2;
+}
+
+.refund-confirm-popup__close {
+  position: absolute;
+  right: 24rpx;
+  width: 72rpx;
+  height: 96rpx;
+  line-height: 96rpx;
+  text-align: center;
+  font-size: 42rpx;
+  color: #555;
+}
+
+.refund-confirm-popup__header-spacer {
+  width: 72rpx;
+}
+
+.refund-confirm-popup__content {
+  flex: 1;
+  padding: 14rpx 18rpx;
+  overflow: auto;
+  background: #fff;
+}
+
+.goods-card {
+  display: flex;
+  align-items: center;
+  gap: 18rpx;
+  background: #fff;
+  padding: 16rpx;
+  border-radius: 12rpx;
+  // border: 1rpx solid #f3f3f3;
+  margin-bottom: 10px;
+}
+
+.goods-card__icon {
+  width: 112rpx;
+  height: 112rpx;
+  border-radius: 10rpx;
+  background: #efefef;
+}
+
+.goods-card__icon--placeholder {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #f5f5f5;
+}
+
+.placeholder-icon {
+  font-size: 48rpx;
+}
+
+.goods-card__main {
+  flex: 1;
+  // margin-left: 15px;
+}
+
+.goods-card__name {
+  font-size: 32rpx;
+  font-weight: 500;
+  margin-bottom: 10px;
+}
+
+.goods-card__meta {
+  font-size: 26rpx;
+  color: #a0a0a0;
+  margin-top: 8rpx;
+}
+
+.goods-card__right {
+  text-align: center;
+}
+
+.goods-card__price {
+  font-size: 34rpx;
+  font-weight: 600;
+  margin-bottom: 10px;
+}
+
+.goods-card__count {
+  font-size: 26rpx;
+  color: #8f8f8f;
+  margin-top: 6rpx;
+}
+
+.form-row {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  min-height: 74rpx;
+  padding: 0 10rpx;
+  position: relative;
+  z-index: 10;
+}
+
+.label {
+  font-size: 32rpx;
+  color: #222;
+}
+
+.value {
+  font-size: 30rpx;
+  color: #222;
+}
+
+.value.select {
+  color: #333;
+  border: 1rpx solid #c8c8c8;
+  border-radius: 10rpx;
+  font-size: 26rpx;
+  padding: 10rpx 20rpx;
+  display: flex;
+}
+
+.refund-status-value {
+  padding: 6rpx 10rpx;
+}
+
+.refund-status-radio-group {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: flex-end;
+  gap: 18rpx;
+}
+
+.value.link {
+  color: #222;
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+  // gap: 10rpx;
+}
+
+.arrow-icon {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #b0b0b0;
+}
+
+.edit-icon {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.value.red {
+  color: #e81818;
+}
+
+.value.amount {
+  font-weight: 600;
+}
+
+.amount-title {
+  font-weight: 600;
+}
+
+.divider {
+  height: 1rpx;
+  background: #f0f0f0;
+  margin: 20rpx 0rpx 20rpx 0rpx;
+}
+
+.refund-confirm-popup__footer {
+  flex-shrink: 0;
+  background: #fff;
+  padding: 20rpx 32rpx 32rpx;
+  box-shadow: 0 -2rpx 12rpx rgba(0, 0, 0, 0.06);
+}
+
+.refund-amount-line {
+  color: #e81818;
+  font-size: 35rpx;
+  margin-bottom: 10rpx;
+  display: flex;
+  align-items: center;
+}
+
+.submit-btn {
+  width: 100%;
+  height: 88rpx;
+  line-height: 88rpx;
+  text-align: center;
+  border-radius: 12rpx;
+  background: #2f6df6;
+  color: #fff;
+  font-size: 30rpx;
+  font-weight: 500;
+}
+
+.wechat-price {
+  font-weight: 600;
+  color: #e81818;
+}
+
+.amount-popup {
+  width: 640rpx;
+  background: #fff;
+  border-radius: 16rpx;
+  overflow: hidden;
+  box-shadow: 0 16rpx 48rpx rgba(0, 0, 0, 0.18);
+}
+
+.amount-popup__header {
+  position: relative;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 30rpx 24rpx 18rpx;
+  font-size: 34rpx;
+  font-weight: 600;
+  color: #111;
+}
+
+
+.amount-popup__body {
+  padding: 8rpx 36rpx 34rpx;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+
+.hint {
+  width: 520rpx;
+  font-size: 26rpx;
+  color: #8a8a8a;
+  text-align: left;
+}
+
+.amount-input {
+  margin-top: 22rpx;
+  border: 2rpx solid #d8d8d8;
+  width: 520rpx;
+  height: 78rpx;
+  line-height: 78rpx;
+  padding: 0 18rpx;
+  font-size: 34rpx;
+  color: #111;
+  box-sizing: border-box;
+}
+
+.actions {
+  margin-top: 26rpx;
+  display: flex;
+  justify-content: center;
+  gap: 28rpx;
+  width: 520rpx;
+}
+
+.action-item {
+  flex: 1;
+}
+
+.action-btn {
+  border-radius: 8rpx !important;
+}
+.confirm-btn {
+ margin-left: 20rpx !important;
+}

+ 148 - 0
miniprogram/module/order/components/refund-confirm-popup/refund-confirm-popup.ts

@@ -0,0 +1,148 @@
+import { Get } from "../../../../lib/request/method";
+Component({
+  properties: {
+    visible: { type: Boolean, value: false },
+    goodsName: { type: String, value: "" },
+    goodsImage: { type: String, value: "" },
+    goodsPrice: { type: String, value: "" },
+    goodsMeta1: { type: String, value: "" },
+    goodsMeta2: { type: String, value: "" },
+    refundReason: { type: String, value: "" },
+    refundStatus: { type: String, value: "" },
+    refundAmount: { type: Number, value: 0 },
+    maxAmount: { type: Number, value: 0 },
+    proofImages: { type: Array, value: [] },
+    showReceiptStatus: { type: Boolean, value: true },
+  },
+  data: {
+    statusPickerVisible: false,
+    statusPickerOptions: [] as Array<{ label: string; value: string }>,
+    statusPickerValue: [] as string[],
+    statusLabel: "",
+    // 退款金额编辑器(整屏弹窗)
+    amountEditorVisible: false,
+    amountInput: "",
+  },
+  observers: {
+    refundStatus(v: string) {
+      const options = (this as any).data?.statusPickerOptions || [];
+      const matched = options.find((item: any) => item.value === v);
+      if (matched) {
+        (this as any).setData({
+          statusPickerValue: [v],
+          statusLabel: matched.label,
+        });
+      }
+    },
+    visible(v: boolean) {
+      if (v && (this as any).data?.statusPickerOptions?.length) {
+        const currentValue = (this as any).data?.statusPickerValue?.[0];
+        if (currentValue) {
+          (this as any).triggerEvent("changeStatus", { status: currentValue });
+        }
+      }
+    },
+  },
+  lifetimes: {
+    attached() {
+      (this as any).fetchStatusOptions();
+    },
+  },
+  methods: {
+    getStatusLabel(value: string): string {
+      const options = (this as any).data?.statusPickerOptions || [];
+      return options.find((item: any) => item.value === value)?.label || "";
+    },
+    async fetchStatusOptions() {
+      try {
+        const res = await Get<any[]>("/dict/getDicts");
+        const dict = (res as any)?.data?.find(
+          (item: any) => item.dictType === "aftersale_receipt_status"
+        );
+        if (dict?.items?.length) {
+          const options = dict.items.map((item: any) => ({
+            label: item.dictLabel,
+            value: item.dictValue,
+          }));
+          // 默认选中最后一个(未收到货)
+          const lastOption = options[options.length - 1];
+          const current = (this as any).data?.refundStatus;
+          const matched = options.find((item: any) => item.value === current);
+          const value = matched ? current : lastOption.value;
+          const label = matched ? matched.label : lastOption.label;
+          (this as any).setData({
+            statusPickerOptions: options,
+            statusPickerValue: [value],
+            statusLabel: label,
+          });
+          // 回传默认值给父页面
+          (this as any).triggerEvent("changeStatus", { status: value });
+        }
+      } catch {
+        wx.showToast({ title: "获取商品状态失败", icon: "none" });
+      }
+    },
+    onPopupVisibleChange(e: WechatMiniprogram.CustomEvent<{ visible: boolean }>) {
+      if (!e?.detail?.visible) {
+        (this as any).triggerEvent("close");
+      }
+    },
+    onAmountPopupVisibleChange(e: WechatMiniprogram.CustomEvent<{ visible: boolean }>) {
+      if (!e?.detail?.visible) {
+        (this as any).setData({ amountEditorVisible: false });
+      }
+    },
+    onClose() {
+      (this as any).triggerEvent("close");
+    },
+    onOpenStatusPicker() {
+      (this as any).setData({ statusPickerVisible: true });
+    },
+    onStatusPickerChange(e: any) {
+      const valueArr = e?.detail?.value as Array<string | number> | undefined;
+      const status = String(valueArr?.[0] ?? "");
+      (this as any).triggerEvent("changeStatus", { status });
+      (this as any).setData({ statusPickerVisible: false });
+    },
+    onStatusPickerClose() {
+      (this as any).setData({ statusPickerVisible: false });
+    },
+    onEditReason() {
+      (this as any).triggerEvent("editReason");
+    },
+    onEditAmount() {
+      const cur = Number((this as any).data?.refundAmount ?? 0);
+      (this as any).setData({ amountInput: cur ? String(cur) : "0", amountEditorVisible: true });
+    },
+    onEditProof() {
+      (this as any).triggerEvent("editProof");
+    },
+    onAmountInput(e: WechatMiniprogram.Input) {
+      const raw = e.detail?.value || "";
+      // 保留数字与小数点
+      const cleaned = raw.replace(/[^\d.]/g, "");
+      (this as any).setData({ amountInput: cleaned });
+    },
+    onAmountClose() {
+      (this as any).setData({ amountEditorVisible: false });
+    },
+    onAmountConfirm() {
+      const maxAmount = Number((this as any).data?.maxAmount ?? 0);
+      const amount = Number((this as any).data?.amountInput ?? 0);
+      if (!amount || Number.isNaN(amount)) {
+        wx.showToast({ title: "请输入退款金额", icon: "none" });
+        return;
+      }
+      if (maxAmount && amount > maxAmount) {
+        wx.showToast({ title: "不能超过最高可退金额", icon: "none" });
+        return;
+      }
+      const fixed = Number(amount.toFixed(2));
+      (this as any).triggerEvent("amountConfirm", { amount: fixed });
+      (this as any).setData({ amountEditorVisible: false });
+    },
+    onSubmit() {
+      (this as any).triggerEvent("submit");
+    },
+  },
+});

+ 119 - 0
miniprogram/module/order/components/refund-confirm-popup/refund-confirm-popup.wxml

@@ -0,0 +1,119 @@
+<t-popup
+  placement="bottom"
+  visible="{{visible}}"
+  show-overlay="{{true}}"
+  close-on-overlay-click="{{true}}"
+  bind:visible-change="onPopupVisibleChange"
+>
+  <view class="refund-confirm-popup">
+    <view class="refund-confirm-popup__header">
+      <view class="title">确认退款信息</view>
+      <text class="refund-confirm-popup__close" catchtap="onClose">×</text>
+      <view class="refund-confirm-popup__header-spacer" />
+    </view>
+
+    <view class="refund-confirm-popup__content">
+      <view class="goods-card">
+        <image class="goods-card__icon" src="{{goodsImage}}" mode="aspectFill" wx:if="{{goodsImage}}" />
+        <view class="goods-card__icon goods-card__icon--placeholder" wx:else>
+          <text class="placeholder-icon">📦</text>
+        </view>
+        <view class="goods-card__main">
+          <view class="goods-card__name">{{goodsName}}</view>
+          <view class="goods-card__meta">{{goodsMeta1}}</view>
+        </view>
+        <view class="goods-card__right">
+          <view class="goods-card__price">¥{{goodsPrice || '0.00'}}</view>
+          <view class="goods-card__count">{{goodsMeta2}}</view>
+        </view>
+      </view>
+
+      <view class="form-row" wx:if="{{showReceiptStatus}}">
+        <text class="label">商品状态</text>
+        <view class="value select" bindtap="onOpenStatusPicker">
+          {{statusLabel}}
+          <t-icon name="chevron-right" class="arrow-icon" size="30rpx" color="#b0b0b0" />
+        </view>
+      </view>
+
+      <view class="form-row" catchtap="onEditReason">
+        <text class="label">退款原因</text>
+        <view class="value link">
+          {{refundReason || '请选择'}}
+          <t-icon name="chevron-right" class="arrow-icon" size="30rpx" color="#b0b0b0" />
+        </view>
+      </view>
+
+      <view class="divider"></view>
+
+      <view class="form-row" catchtap="onEditAmount">
+        <text class="label amount-title">退款金额</text>
+        <view class="value link">
+          <t-icon name="edit" class="edit-icon" size="28rpx" color="#b0b0b0" />
+          修改
+        </view>
+      </view>
+      <view class="form-row">
+        <text class="label">退回微信</text>
+        <view class="value amount">¥{{refundAmount}}</view>
+      </view>
+
+      <view class="form-row" catchtap="onEditProof">
+        <text class="label">上传描述和凭证<text style="color:red">*</text></text>
+        <view class="value link red">
+          {{proofImages.length ? '已上传' + proofImages.length + '张' : '上传有助处理退款'}}
+          <t-icon name="chevron-right" class="arrow-icon" size="30rpx" color="#b0b0b0" />
+        </view>
+      </view>
+    </view>
+
+    <view class="refund-confirm-popup__footer">
+      <view class="refund-amount-line"><text>退回微信¥</text><text class="wechat-price">{{refundAmount}}</text></view>
+      <view class="submit-btn" bindtap="onSubmit">提交申请</view>
+    </view>
+  </view>
+</t-popup>
+
+<t-picker
+  visible="{{statusPickerVisible}}"
+  value="{{statusPickerValue}}"
+  title=""
+  use-popup="{{true}}"
+  header="{{true}}"
+  cancel-btn="取消"
+  confirm-btn="确定"
+  bind:change="onStatusPickerChange"
+  bind:close="onStatusPickerClose"
+  z-index="12000"
+  style="--td-picker-z-index: 12000; --td-popup-z-index: 12000;"
+>
+  <t-picker-item options="{{statusPickerOptions}}" />
+</t-picker>
+
+<t-popup
+  placement="center"
+  visible="{{amountEditorVisible}}"
+  show-overlay="{{true}}"
+  close-on-overlay-click="{{true}}"
+  bind:visible-change="onAmountPopupVisibleChange"
+  z-index="12000"
+  style="--td-popup-z-index: 12000;"
+>
+  <view class="amount-popup">
+    <view class="amount-popup__header">
+      <view class="title">请输入退款总金额</view>
+    </view>
+    <view class="amount-popup__body">
+      <view class="hint">最高可退¥{{maxAmount}}元</view>
+      <input class="amount-input" type="digit" value="{{amountInput}}" bindinput="onAmountInput" />
+      <view class="actions">
+        <view class="action-item">
+          <t-button class="action-btn" block size="medium" variant="outline" theme="primary" catchtap="onAmountClose">取消</t-button>
+        </view>
+        <view class="action-item">
+          <t-button class="action-btn confirm-btn" block size="medium" theme="primary" catchtap="onAmountConfirm">确定</t-button>
+        </view>
+      </view>
+    </view>
+  </view>
+</t-popup>

+ 7 - 0
miniprogram/module/order/components/refund-proof-popup/refund-proof-popup.json

@@ -0,0 +1,7 @@
+{
+  "component": true,
+  "usingComponents": {
+    "t-popup": "tdesign-miniprogram/popup/popup",
+    "t-icon": "tdesign-miniprogram/icon/icon"
+  }
+}

+ 178 - 0
miniprogram/module/order/components/refund-proof-popup/refund-proof-popup.scss

@@ -0,0 +1,178 @@
+.proof-popup {
+  height: 72vh;
+  min-height: 760rpx;
+  background: #fff;
+  border-top-left-radius: 24rpx;
+  border-top-right-radius: 24rpx;
+  overflow: hidden;
+  display: flex;
+  flex-direction: column;
+}
+
+.proof-popup__header {
+  height: 100rpx;
+  background: #f2f2f2;
+  position: relative;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 34rpx;
+  font-weight: 600;
+}
+
+.proof-popup__close {
+  position: absolute;
+  right: 24rpx;
+  width: 72rpx;
+  height: 108rpx;
+  line-height: 108rpx;
+  text-align: center;
+  font-size: 46rpx;
+}
+
+.proof-popup__header-spacer {
+  width: 72rpx;
+}
+
+.proof-popup__content {
+  flex: 1;
+  padding: 20rpx;
+  background: #f5f5f5;
+}
+
+.desc-input {
+  width: 100%;
+  min-height: 200rpx;
+  background: #fff;
+  border: 1rpx solid #ececec;
+  padding: 16rpx;
+  box-sizing: border-box;
+  font-size: 32rpx;
+  border-radius: 10rpx;
+}
+
+.desc-count {
+  text-align: right;
+  color: #888;
+  font-size: 28rpx;
+  margin: 8rpx 4rpx 12rpx;
+}
+
+.image-list {
+  margin-top: 16rpx;
+  display: flex;
+  flex-wrap: wrap;
+  // gap: 16rpx;
+}
+
+.img-item {
+  width: 160rpx;
+  height: 160rpx;
+  position: relative;
+  border-radius: 12rpx;
+  overflow: hidden;
+  margin-right: 16rpx;
+  margin-bottom: 20rpx;
+}
+
+.img {
+  width: 100%;
+  height: 100%;
+  border-radius: 12rpx;
+}
+
+.remove {
+  position: absolute;
+  top: 0;
+  right: 0;
+  width: 40rpx;
+  height: 40rpx;
+  border-bottom-left-radius: 16rpx;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #fff;
+  font-size: 24rpx;
+  z-index: 2;
+}
+
+.image-uploader {
+  width: 160rpx;
+  height: 160rpx;
+  background: #f0f2f5;
+  border-radius: 12rpx;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  box-sizing: border-box;
+}
+
+.upload-icon {
+  margin-bottom: 8rpx;
+}
+
+.upload-text {
+  font-size: 24rpx;
+  color: #666;
+}
+
+.upload-empty-container {
+  margin-top: 16rpx;
+}
+
+.image-uploader-empty {
+  border: 1rpx dashed #ccc;
+  background: #fff;
+  min-height: 200rpx;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  border-radius: 8rpx;
+}
+
+.upload-icon-empty {
+  margin-bottom: 12rpx;
+}
+
+.upload-title {
+  font-size: 30rpx;
+  color: #333;
+}
+
+.upload-sub {
+  font-size: 26rpx;
+  color: #999;
+  margin-top: 6rpx;
+}
+
+.upload-tips {
+  font-size: 26rpx;
+  color: #666;
+  margin-top: 16rpx;
+}
+
+.proof-popup__footer {
+  flex-shrink: 0;
+  padding: 20rpx 32rpx 32rpx;
+  background: #fff;
+  box-shadow: 0 -2rpx 12rpx rgba(0, 0, 0, 0.06);
+}
+
+.done-btn {
+  width: 100%;
+  height: 88rpx;
+  line-height: 88rpx;
+  border-radius: 12rpx;
+  text-align: center;
+  color: #fff;
+  background: #2f6df6;
+  font-size: 34rpx;
+
+  &--disabled {
+    opacity: 0.5;
+    pointer-events: none;
+  }
+}

+ 99 - 0
miniprogram/module/order/components/refund-proof-popup/refund-proof-popup.ts

@@ -0,0 +1,99 @@
+import { upload } from "../../../../lib/request/upload";
+
+Component({
+  properties: {
+    visible: { type: Boolean, value: false },
+    desc: { type: String, value: "" },
+    images: { type: Array, value: [] },
+  },
+  data: {
+    inputDesc: "",
+    inputImages: [] as string[],
+    uploading: false,
+  },
+  observers: {
+    visible(v: boolean) {
+      if (v) {
+        this.setData({
+          inputDesc: this.data.desc || "",
+          inputImages: [...(this.data.images as string[])],
+        });
+      }
+    },
+  },
+  methods: {
+    onPopupVisibleChange(e: WechatMiniprogram.CustomEvent<{ visible: boolean }>) {
+      if (!e?.detail?.visible) this.triggerEvent("close");
+    },
+    onClose() {
+      this.triggerEvent("close");
+    },
+    onDescInput(e: WechatMiniprogram.Input) {
+      const value = (e.detail?.value || "").slice(0, 200);
+      this.setData({ inputDesc: value });
+    },
+    onChooseImage() {
+      const current = this.data.inputImages.length;
+      const remain = 9 - current;
+      if (remain <= 0) {
+        wx.showToast({ title: "最多上传9张", icon: "none" });
+        return;
+      }
+      wx.chooseMedia({
+        count: remain,
+        mediaType: ["image"],
+        sourceType: ["album", "camera"],
+        camera: "back",
+        success: (res) => {
+          const tempPaths = res.tempFiles.map((f) => f.tempFilePath);
+          this.setData({ uploading: true });
+          this._uploadSequentially(tempPaths, 0);
+        },
+      });
+    },
+    _uploadSequentially(paths: string[], index: number) {
+      if (index >= paths.length) {
+        this.setData({ uploading: false });
+        return;
+      }
+      upload<string, any>({
+        params: { name: "file", file: paths[index] },
+        transform({ data }: any): string {
+          return (data as any)?.url || (data as any);
+        },
+      }).then(
+        (url) => {
+          this.setData({
+            inputImages: [...this.data.inputImages, url],
+          });
+          this._uploadSequentially(paths, index + 1);
+        },
+        (error: any) => {
+          wx.showToast({
+            title: error?.errMsg || "上传失败",
+            icon: "none",
+          });
+          this.setData({ uploading: false });
+        }
+      );
+    },
+    onRemoveImage(e: WechatMiniprogram.TouchEvent) {
+      const index = Number((e.currentTarget.dataset as { index?: number })?.index ?? -1);
+      if (index < 0) return;
+      const next = [...this.data.inputImages];
+      next.splice(index, 1);
+      this.setData({ inputImages: next });
+    },
+    onConfirm() {
+      if (this.data.uploading) return;
+      if (!this.data.inputDesc.trim() && this.data.inputImages.length === 0) {
+        wx.showToast({ title: "请填写描述或上传凭证", icon: "none" });
+        return;
+      }
+      this.triggerEvent("confirm", {
+        desc: this.data.inputDesc,
+        images: this.data.inputImages,
+      });
+    },
+  },
+});

+ 49 - 0
miniprogram/module/order/components/refund-proof-popup/refund-proof-popup.wxml

@@ -0,0 +1,49 @@
+<t-popup
+  placement="bottom"
+  visible="{{visible}}"
+  show-overlay="{{true}}"
+  close-on-overlay-click="{{true}}"
+  bind:visible-change="onPopupVisibleChange"
+  z-index="12000"
+  style="--td-popup-z-index: 12000;"
+>
+  <view class="proof-popup">
+    <view class="proof-popup__header">
+      <view class="title">上传描述或凭证</view>
+      <text class="proof-popup__close" catchtap="onClose">×</text>
+      <view class="proof-popup__header-spacer" />
+    </view>
+
+    <view class="proof-popup__content">
+      <textarea class="desc-input" placeholder="补充描述,有助于商家更好的处理售后问题" maxlength="200" value="{{inputDesc}}" bindinput="onDescInput" />
+      <view class="desc-count">{{inputDesc.length}}/200</view>
+
+      <view class="upload-empty-container" wx:if="{{inputImages.length === 0}}">
+        <view class="image-uploader-empty" catchtap="onChooseImage">
+          <t-icon name="camera" size="56rpx" color="#111" class="upload-icon-empty" />
+          <view class="upload-title">上传凭证</view>
+          <view class="upload-sub">(最多9张)</view>
+        </view>
+        <view class="upload-tips">请提供以下凭证:包裹内件实物图片</view>
+      </view>
+
+      <view class="image-list" wx:else>
+        <view class="img-item" wx:for="{{inputImages}}" wx:key="index">
+          <image class="img" src="{{item}}" mode="aspectFill" />
+          <view class="remove" data-index="{{index}}" catchtap="onRemoveImage">
+            <t-icon name="close" size="24rpx" color="#fff" />
+          </view>
+        </view>
+        
+        <view class="image-uploader" catchtap="onChooseImage" wx:if="{{inputImages.length < 9}}">
+          <t-icon name="camera" size="56rpx" color="#999" class="upload-icon" />
+          <view class="upload-text">还可传{{9 - inputImages.length}}个</view>
+        </view>
+      </view>
+    </view>
+
+    <view class="proof-popup__footer">
+      <view class="done-btn {{uploading ? 'done-btn--disabled' : ''}}" bindtap="onConfirm">完成</view>
+    </view>
+  </view>
+</t-popup>

+ 8 - 0
miniprogram/module/order/components/refund-reason-popup/refund-reason-popup.json

@@ -0,0 +1,8 @@
+{
+  "component": true,
+  "usingComponents": {
+    "t-popup": "tdesign-miniprogram/popup/popup",
+    "t-radio": "tdesign-miniprogram/radio/radio",
+    "t-radio-group": "tdesign-miniprogram/radio-group/radio-group"
+  }
+}

+ 72 - 0
miniprogram/module/order/components/refund-reason-popup/refund-reason-popup.scss

@@ -0,0 +1,72 @@
+.refund-reason-popup {
+  height: 70vh;
+  min-height: 560rpx;
+  background: #fff;
+  border-top-left-radius: 32rpx;
+  border-top-right-radius: 32rpx;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+
+.refund-reason-popup__header {
+  height: 100rpx;
+  position: relative;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-weight: 600;
+  font-size: 34rpx;
+  color: #1f1f1f;
+  background: #f2f2f2;
+}
+
+.refund-reason-popup__close {
+  position: absolute;
+  right: 24rpx;
+  width: 72rpx;
+  height: 116rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 48rpx;
+  color: #444;
+}
+
+.refund-reason-popup__header-spacer {
+  width: 72rpx;
+  height: 1px;
+}
+
+.refund-reason-popup__content {
+  flex: 1;
+  overflow: auto;
+  background: #fff;
+  padding: 10rpx 0;
+  box-sizing: border-box;
+}
+
+.custom-radio {
+  --td-radio-icon-checked-color: #2f6df6;
+  font-size: 32rpx !important;
+  padding: 24rpx 32rpx;
+}
+
+.refund-reason-popup__footer {
+  flex-shrink: 0;
+  padding: 20rpx 32rpx 32rpx;
+  background: #fff;
+  box-shadow: 0 -2rpx 12rpx rgba(0, 0, 0, 0.06);
+}
+
+.refund-reason-popup__next-btn {
+  width: 100%;
+  height: 88rpx;
+  line-height: 88rpx;
+  border-radius: 44rpx;
+  background: linear-gradient(135deg, #2f6df6 0%, #1f5de8 100%);
+  color: #fff;
+  text-align: center;
+  font-size: 30rpx;
+  font-weight: 500;
+}

+ 65 - 0
miniprogram/module/order/components/refund-reason-popup/refund-reason-popup.ts

@@ -0,0 +1,65 @@
+import { Get } from "../../../../lib/request/method";
+Component({
+  properties: {
+    visible: { type: Boolean, value: false },
+    initialReason: { type: String, value: "" },
+  },
+  data: {
+    selectedReason: "",
+    reasons: [] as Array<{ label: string; value: string }>,
+  },
+  observers: {
+    visible(v: boolean) {
+      if (v) {
+        this.setData({ selectedReason: this.data.initialReason || "" });
+      }
+    },
+  },
+  lifetimes: {
+    attached() {
+      this.fetchReasons();
+    },
+  },
+  methods: {
+    async fetchReasons() {
+      try {
+        const res = await Get<any[]>("/dict/getDicts");
+        const dict = (res as any)?.data?.find(
+          (item: any) => item.dictType === "aftersale_reason"
+        );
+        if (dict?.items?.length) {
+          this.setData({
+            reasons: dict.items.map((item: any) => ({
+              label: item.dictLabel,
+              value: item.dictValue,
+            })),
+          });
+        }
+      } catch {
+        wx.showToast({ title: "获取退款原因失败", icon: "none" });
+      }
+    },
+    onPopupVisibleChange(e: WechatMiniprogram.CustomEvent<{ visible: boolean }>) {
+      if (!e?.detail?.visible) {
+        this.triggerEvent("close");
+      }
+    },
+    onClose() {
+      this.triggerEvent("close");
+    },
+    onSelectReason(e: any) {
+      const reason = e.detail?.value;
+      if (reason) {
+        this.setData({ selectedReason: reason });
+      }
+    },
+    onNext() {
+      const reason = this.data.selectedReason;
+      if (!reason) {
+        wx.showToast({ title: "请选择退款原因", icon: "none" });
+        return;
+      }
+      this.triggerEvent("next", { reason });
+    },
+  },
+});

+ 35 - 0
miniprogram/module/order/components/refund-reason-popup/refund-reason-popup.wxml

@@ -0,0 +1,35 @@
+<t-popup
+  placement="bottom"
+  visible="{{visible}}"
+  show-overlay="{{true}}"
+  close-on-overlay-click="{{true}}"
+  bind:visible-change="onPopupVisibleChange"
+  z-index="12000"
+  style="--td-popup-z-index: 12000;"
+>
+  <view class="refund-reason-popup">
+    <view class="refund-reason-popup__header">
+      <view class="title">选择退款原因</view>
+      <text class="refund-reason-popup__close" catchtap="onClose">×</text>
+      <view class="refund-reason-popup__header-spacer" />
+    </view>
+
+    <scroll-view class="refund-reason-popup__content" scroll-y>
+      <t-radio-group value="{{selectedReason}}" bind:change="onSelectReason">
+        <t-radio
+          wx:for="{{reasons}}"
+          wx:key="value"
+          value="{{item.value}}"
+          label="{{item.label}}"
+          placement="right"
+          icon="circle"
+          class="custom-radio"
+        />
+      </t-radio-group>
+    </scroll-view>
+
+    <view class="refund-reason-popup__footer">
+      <view class="refund-reason-popup__next-btn" bindtap="onNext">下一步</view>
+    </view>
+  </view>
+</t-popup>

+ 24 - 0
miniprogram/module/order/model/evaluate.model.ts

@@ -0,0 +1,24 @@
+export interface EvaluateModel {
+  patientConditioningRecordId: string; //	患者调理记录ID
+  patientConditioningProgramId: string; //患者调理方案ID
+  lineId: string; //调理任务ID
+  conditioningProgramId: number; //调理方案ID
+  complianceScore: number;  //相符度评分
+  qualityScore: number;  //质量评分
+  attitudeScore: number;  //态度评分
+  environmentScore: number;  //环境评分
+  depict: string;  //评价描述
+  imageVideos: string[];  //评价图片/视频
+  createTime: string;  //创建时间
+}
+
+export interface OfflineEvaluateModel {
+  lineId: number,
+  patientConditioningRecordId: number;
+  patientConditioningProgramId: number;
+  operateTime: string;
+  operateBy: string;
+  conditioningProgramSupplierName: string;
+  image: string;
+  name: string;
+}

+ 1 - 3
miniprogram/module/order/pages/appointment-success/appointment-success.json

@@ -3,8 +3,6 @@
   "component": true,
   "usingComponents": {
     "t-navbar": "tdesign-miniprogram/navbar/navbar",
-    "van-calendar": "@vant/weapp/calendar/index",
-    "van-cell": "@vant/weapp/cell/index",
-    "van-icon": "@vant/weapp/icon/index"
+    "t-icon": "tdesign-miniprogram/icon/icon"
   }
 }

+ 1 - 4
miniprogram/module/order/pages/appointment-success/appointment-success.ts

@@ -11,19 +11,16 @@ Page({
   },
   onLoad(options: any) {
     if(options.goodsInfo){
-      const goodsInfo = JSON.parse(options.goodsInfo);
-      console.log(goodsInfo, "goodsInfo")
+      const goodsInfo = JSON.parse(decodeURIComponent(options.goodsInfo));
       this.setData({
         appointmentInfo: goodsInfo,
       });
     }
-    
   },
   // 返回非药物治疗页面
   onReturn() {
     // 返回到非药物治疗页面,传递预约成功参数
     const { appointmentInfo } = this.data;
-    console.log(appointmentInfo, "appointmentInfo")
     wx.redirectTo({
       url: `/module/care/pages/offlineTreatment/offlineTreatment?id=${appointmentInfo?.id}`,
     });

+ 2 - 3
miniprogram/module/order/pages/appointment/appointment.json

@@ -4,7 +4,6 @@
   "usingComponents": {
     "t-navbar": "tdesign-miniprogram/navbar/navbar",
     "t-icon": "tdesign-miniprogram/icon/icon",
-    "t-calendar": "tdesign-miniprogram/calendar/calendar",
-    "van-calendar": "@vant/weapp/calendar/index"
+    "t-calendar": "tdesign-miniprogram/calendar/calendar"
   }
-}
+}

+ 49 - 40
miniprogram/module/order/pages/appointment/appointment.scss

@@ -45,7 +45,6 @@
 .service-info {
   flex: 1;
   display: flex;
-  // flex-direction: column;
   align-items: center;
   justify-content: space-between;
   gap: 12rpx;
@@ -59,14 +58,13 @@
 
 .service-duration {
   font-size: 28rpx;
-  color: #666;
+  color: #999;
 }
 
 // 时间选择卡片
 .time-selection-card {
   background: #fff;
   margin: 0 20rpx 20rpx 20rpx;
-  // padding: 32rpx;
   padding: 20rpx;
   border-radius: 16rpx;
   box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
@@ -85,7 +83,7 @@
 .time-selection-title {
   font-size: 30rpx;
   font-weight: 500;
-  color: black;
+  color: #333;
   text-align: center;
   width: 100%;
 }
@@ -103,25 +101,23 @@
 .date-selector {
   display: flex;
   gap: 12rpx;
-  margin-bottom: 32rpx;
+  margin: 24rpx 0 32rpx 0;
   overflow-x: auto;
   padding-bottom: 8rpx;
   align-items: center;
-  justify-content: space-between;
 }
 
 .date-item {
-  flex-shrink: 0;
+  flex: 1;
+  min-width: 0;
   display: flex;
   flex-direction: column;
   align-items: center;
   padding: 16rpx 18rpx;
   border-radius: 12rpx;
-  // background: #f7f8fa;
   background: white;
   border: 2rpx solid transparent;
   transition: all 0.3s ease;
-  // min-width: 100rpx;
 
   &:active {
     transform: scale(0.95);
@@ -129,13 +125,14 @@
 }
 
 .date-item--selected {
-  // 选中状态样式在子元素中定义
-  position: relative;
+  background: #f0faf0;
+  border-color: #4caf50;
+  border-radius: 12rpx;
 }
 
 .date-label {
   font-size: 24rpx;
-  color: #666;
+  color: #999;
   margin-bottom: 6rpx;
 }
 
@@ -155,44 +152,34 @@
   font-weight: 600;
 }
 
-// 时间选择网格 - 5行5列表格
+// 时间选择网格 - 5列
+// 用背景色+gap实现网格线,避免border导致rpx舍入溢出
 .time-grid {
   display: flex;
   flex-wrap: wrap;
-  border: 1rpx solid #e0e0e0;
-  border-radius: 0;
-  background: #fff;
+  background: #e8e8e8;
+  gap: 1rpx;
   width: 100%;
   box-sizing: border-box;
+  border: 1rpx solid #e8e8e8;
+  border-bottom: none;
 }
 
 .time-slot {
-  width: 20%;
+  width: calc((100% - 4rpx) / 5);
   text-align: center;
-  font-size: 28rpx;
-  color: #000;
+  font-size: 26rpx;
+  color: #333;
   background: #fff;
-  border-right: 1rpx solid #e0e0e0;
-  border-bottom: 1rpx solid #e0e0e0;
+  border-bottom: 1rpx solid #e8e8e8;
   border-radius: 0;
   transition: all 0.3s ease;
   display: flex;
   align-items: center;
   justify-content: center;
-  min-height: 88rpx;
+  min-height: 96rpx;
   box-sizing: border-box;
   padding: 0;
-  flex-shrink: 0;
-
-  // 每行最后一个去掉右边框
-  &:nth-child(5n) {
-    border-right: none;
-  }
-
-  // 最后一行去掉下边框
-  &:nth-child(n+21) {
-    border-bottom: none;
-  }
 
   &:active:not(.time-slot--disabled):not(.time-slot--selected) {
     background: #fafafa;
@@ -201,7 +188,6 @@
 
 .time-slot--selected {
   background: #1d6ff6 !important;
-  border-color: #1d6ff6 !important;
   color: #fff !important;
   font-weight: 500;
   z-index: 1;
@@ -210,7 +196,6 @@
 
 .time-slot--disabled {
   background: #fff;
-  border-color: #e0e0e0;
   color: #ccc;
   opacity: 0.6;
 }
@@ -228,7 +213,7 @@
   right: 0;
   width: 100%;
   background: #fff;
-  padding: 20rpx;
+  padding: 20rpx 20rpx calc(20rpx + env(safe-area-inset-bottom));
   box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.08);
   z-index: 100;
   box-sizing: border-box;
@@ -239,17 +224,41 @@
   height: 88rpx;
   line-height: 88rpx;
   text-align: center;
-  background: linear-gradient(135deg, #1d6ff6 0%, #4a90ff 100%);
+  background: #1d6ff6;
   color: #fff;
   font-size: 32rpx;
   font-weight: 600;
   border-radius: 44rpx;
-  box-shadow: 0 4rpx 12rpx rgba(29, 111, 246, 0.3);
   transition: all 0.3s ease;
 
   &:active {
     opacity: 0.9;
     transform: scale(0.98);
-    box-shadow: 0 2rpx 8rpx rgba(29, 111, 246, 0.4);
   }
-}
+}
+
+// ========================================
+// TDesign Calendar Skyline 兼容性覆盖
+// Skyline 不支持 CSS Grid,用 flexbox 替代
+// 利用组件 styleIsolation: apply-shared 注入样式
+// ========================================
+.t-calendar__days {
+  display: flex !important;
+  flex-wrap: wrap !important;
+}
+
+.t-calendar__days-item {
+  width: 14.2857% !important;
+  box-sizing: border-box !important;
+  text-align: center !important;
+}
+
+.t-calendar__dates {
+  display: flex !important;
+  flex-wrap: wrap !important;
+}
+
+.t-calendar__dates-item {
+  width: 14.2857% !important;
+  box-sizing: border-box !important;
+}

+ 146 - 63
miniprogram/module/order/pages/appointment/appointment.ts

@@ -1,7 +1,7 @@
 import PageContainerBehavior from "../../../../core/behavior/page-container.behavior";
 import DictionariesBehavior from "../../../../core/behavior/dictionaries.behavior";
 import tickleBehavior from "../../../../core/behavior/tickle.behavior";
-import { patientOfflineTreatmentAppointmentMethod } from "../../request";
+import { patientOfflineTreatmentAppointmentMethod ,updateAppointmentMethod} from "../../request";
 // module/order/pages/appointment/appointment.ts
 Page({
   behaviors: [PageContainerBehavior, DictionariesBehavior, tickleBehavior],
@@ -23,17 +23,56 @@ Page({
     show: false,
     date: '',
     offlineId: "",
+    // 是否为修改预约模式,以及原始预约时间(用于高亮和对比是否有改动)
+    isEditMode: false,
+    originalAppointmentTime: "",
   },
   onLoad(options: any) {
-    // 初始化日期列表(从今天开始5天)
-    this.initDateList();
-    if (options.goodsInfo) {
-      const goodsInfo = JSON.parse(options.goodsInfo);
+    const goodsInfoFromStorage = wx.getStorageSync('goodsInfo');
+
+    let parsedGoodsInfo: any = null;
+    if ( goodsInfoFromStorage) {
+      try {
+        parsedGoodsInfo = JSON.parse(goodsInfoFromStorage || '{}');
+      } catch (e) {
+        console.error("解析 goodsInfo 失败", e);
+      }
+    }
+
+    const originalAppointmentTime =
+      (options.arrangeTime && decodeURIComponent(options.arrangeTime)) ||
+      parsedGoodsInfo?.arrangeTime ||
+      parsedGoodsInfo?.appointmentTime ||
+      "";
+    const isEditMode = options.mode === "edit" || !!originalAppointmentTime;
+
+    // 如果有原始预约时间,则围绕该日期初始化列表;否则从今天开始 5 天
+    if (originalAppointmentTime) {
+      const [datePart] = originalAppointmentTime.split(" ");
+      const baseDate = datePart ? new Date(datePart) : new Date();
+      this.initDateList(baseDate);
+    } else {
+      this.initDateList();
+    }
+
+    if (parsedGoodsInfo) {
+      this.setData({
+        goodsInfo: parsedGoodsInfo,
+        offlineId: parsedGoodsInfo.offlineId || "",
+        isEditMode,
+        originalAppointmentTime,
+      });
+    } else {
       this.setData({
-        goodsInfo: goodsInfo,
-        offlineId: goodsInfo.offlineId,
+        isEditMode,
+        originalAppointmentTime,
       });
     }
+
+    // 修改预约场景:根据原始预约时间高亮默认选中项
+    if (originalAppointmentTime) {
+      this.highlightOriginalAppointmentTime(originalAppointmentTime);
+    }
   },
   // 初始化日期列表
   initDateList(startDate?: Date) {
@@ -89,7 +128,8 @@ Page({
     this.initTimeSlots(dateList[0].date);
   },
   // 初始化时间列表 - 固定8:00-20:00
-  initTimeSlots(selectedDate?: string) {
+  // 可选传入 preSelectedTime,在初始化时高亮该时间段
+  initTimeSlots(selectedDate?: string, preSelectedTime?: string) {
     const timeSlots: Array<{
       time: string;
       isSelected: boolean;
@@ -123,7 +163,7 @@ Page({
 
         timeSlots.push({
           time: timeStr,
-          isSelected: false,
+          isSelected: preSelectedTime ? timeStr === preSelectedTime : false,
           isDisabled,
         });
       }
@@ -131,6 +171,37 @@ Page({
 
     this.setData({ timeSlots });
   },
+  // 根据原始预约时间高亮日期和时间
+  highlightOriginalAppointmentTime(appointmentTime: string) {
+    if (!appointmentTime) return;
+    const [rawDatePart, rawTimePart] = appointmentTime.split(" ");
+    if (!rawDatePart || !rawTimePart) return;
+
+    // 兼容不同格式:2025-03-13 / 2025/03/13 以及带秒的时间 14:30:00
+    const datePart = rawDatePart.replace(/\//g, "-").substring(0, 10);
+    const timePart = rawTimePart.substring(0, 5); // 只取 HH:mm
+
+    // 高亮日期
+    const dateList = this.data.dateList.map((item) => ({
+      ...item,
+      isSelected: item.date === datePart,
+    }));
+
+    // 如果当前 5 天中没有该日期,则直接返回(保持已有逻辑);更复杂的滚动可后续再优化
+    const hasTargetDate = dateList.some((item) => item.isSelected);
+    if (!hasTargetDate) {
+      return;
+    }
+
+    this.setData({
+      dateList,
+      selectedDate: datePart,
+      selectedTime: timePart,
+    });
+
+    // 按该日期重新初始化时间段,并选中对应时间
+    this.initTimeSlots(datePart, timePart);
+  },
   // 选择日期
   onDateSelect(e: any) {
     const index = e.currentTarget.dataset.index;
@@ -190,11 +261,28 @@ Page({
       });
       return;
     }
-    console.log(this.data.selectedDate, "this.data.selectedDate", this.data.selectedTime, "this.data.selectedTime", this.data.offlineId, "this.data.offlineId")
 
     const appointmentTime = `${this.data.selectedDate} ${this.data.selectedTime}`;
-    console.log(appointmentTime, "appointmentTime");
-    console.log(this.data.offlineId, "this.data.offlineId");
+
+    // 归一化原始预约时间为 "YYYY-MM-DD HH:mm" 用于对比
+    let normalizedOriginal = "";
+    if (this.data.originalAppointmentTime) {
+      const [rawDatePart, rawTimePart] = this.data.originalAppointmentTime.split(" ");
+      if (rawDatePart && rawTimePart) {
+        const datePart = rawDatePart.replace(/\//g, "-").substring(0, 10);
+        const timePart = rawTimePart.substring(0, 5); // HH:mm
+        normalizedOriginal = `${datePart} ${timePart}`;
+      }
+    }
+
+    // 修改预约模式下,如果时间未变化,则提示修改失败,不调接口
+    if (this.data.isEditMode && normalizedOriginal && appointmentTime === normalizedOriginal) {
+      wx.showToast({
+        title: "预约时间未修改",
+        icon: "none",
+      });
+      return;
+    }
     this.setData({
       goodsInfo: {
         ...this.data.goodsInfo,
@@ -202,14 +290,21 @@ Page({
       },
     });
     try {
-      await patientOfflineTreatmentAppointmentMethod(this.data.offlineId, appointmentTime);
+      if (this.data.isEditMode) {
+        await updateAppointmentMethod(this.data.offlineId, appointmentTime);
+      } else {
+        await patientOfflineTreatmentAppointmentMethod(
+          this.data.offlineId,
+          appointmentTime
+        );
+      }
       wx.showToast({
         title: '预约成功',
         icon: 'success',
       });
       setTimeout(() => {
         wx.redirectTo({
-          url: `/module/order/pages/appointment-success/appointment-success?goodsInfo=${JSON.stringify(this.data.goodsInfo)}`,
+          url: `/module/order/pages/appointment-success/appointment-success?goodsInfo=${encodeURIComponent(JSON.stringify(this.data.goodsInfo))}`,
         });
       }, 1000);
 
@@ -218,13 +313,11 @@ Page({
         title: error.errMsg || "预约失败",
         icon: "none",
       });
-      console.log(error, "error")
     }
   },
 
   // 打开日历弹窗
   onOpenCalendar() {
-    console.log('onOpenCalendar');
     this.setData({ show: true });
   },
   // 关闭日历弹窗
@@ -236,56 +329,46 @@ Page({
     date = new Date(date);
     return `${date.getMonth() + 1}/${date.getDate()}`;
   },
-  // 确认选择日期
-  onConfirm(event: any) {
-    const selectedDate = event.detail;
-    console.log('选择的日期:', selectedDate);
-
-    if (selectedDate) {
-      // 解析日期
-      const dateObj = new Date(selectedDate);
-      dateObj.setHours(0, 0, 0, 0);
-
-      // 格式化日期字符串用于比较
-      const dateValue = `${dateObj.getFullYear()}-${String(dateObj.getMonth() + 1).padStart(2, '0')}-${String(dateObj.getDate()).padStart(2, '0')}`;
-
-      // 检查选择的日期是否在当前展示的5天中
-      const existingDateIndex = this.data.dateList.findIndex(item => item.date === dateValue);
-
-      if (existingDateIndex !== -1) {
-        // 如果选择的日期在已展示的5天中,只更新选中状态
-        const dateList = this.data.dateList.map((item, index) => ({
-          ...item,
-          isSelected: index === existingDateIndex,
-        }));
-
-        // 重新初始化时间列表,根据选中的日期禁用已过去的时间
-        this.initTimeSlots(dateValue);
-
-        // 清空已选时间
-        this.setData({
-          show: false,
-          dateList,
-          selectedDate: dateValue,
-          date: this.formatDate(selectedDate),
-          selectedTime: "",
-        });
-      } else {
-        // 如果选择的日期不在已展示的5天中,重新生成日期列表
-        this.initDateList(dateObj);
+  // 日历弹窗选择日期(同时监听 change 和 select,双保险)
+  _calendarLock: false,
+  onCalendarSelect(event: any) {
+    // 防止 change 和 select 同时触发导致重复执行
+    if ((this as any)._calendarLock) return;
+    (this as any)._calendarLock = true;
+    setTimeout(() => { (this as any)._calendarLock = false; }, 300);
 
-        // 重新初始化时间列表,根据选中的日期禁用已过去的时间
-        this.initTimeSlots(dateValue);
+    const timestamp = event.detail.value;
+    if (!timestamp) return;
 
-        // 清空已选时间
-        this.setData({
-          show: false,
-          date: this.formatDate(selectedDate),
-          selectedTime: "",
-        });
-      }
+    const dateObj = new Date(timestamp);
+    dateObj.setHours(0, 0, 0, 0);
+    const dateValue = `${dateObj.getFullYear()}-${String(dateObj.getMonth() + 1).padStart(2, '0')}-${String(dateObj.getDate()).padStart(2, '0')}`;
+
+    // 关闭弹窗
+    this.setData({ show: false });
+
+    // 检查选择的日期是否在当前展示的5天中
+    const existingDateIndex = this.data.dateList.findIndex(item => item.date === dateValue);
+
+    if (existingDateIndex !== -1) {
+      // 日期在已展示的5天中,更新选中状态
+      const dateList = this.data.dateList.map((item, index) => ({
+        ...item,
+        isSelected: index === existingDateIndex,
+      }));
+      this.initTimeSlots(dateValue);
+      this.setData({
+        dateList,
+        selectedDate: dateValue,
+        selectedTime: "",
+      });
     } else {
-      this.setData({ show: false });
+      // 日期不在已展示的5天中,重新生成日期列表
+      this.initDateList(new Date(dateObj.getTime())); // 传副本,避免 initDateList 修改原对象
+      this.setData({
+        selectedDate: dateValue,
+        selectedTime: "",
+      });
     }
   },
 });

+ 2 - 2
miniprogram/module/order/pages/appointment/appointment.wxml

@@ -44,10 +44,10 @@
 </scroll-view>
 
 <!-- 提交按钮 -->
-<view class="submit-footer" style="padding-bottom: {{container.safeBottomOffset}}px;">
+<view class="submit-footer">
   <view class="submit-btn" bindtap="onSubmit">
     提交预约
   </view>
 </view>
 <!-- 日期选择弹窗 -->
-<van-calendar show="{{ show }}" show-title title="日期选择" bind:close="onClose" bind:confirm="onConfirm" color="#1d6ff6" />
+<t-calendar visible="{{ show }}" title="日期选择" bind:close="onClose" bind:confirm="onCalendarSelect" />

+ 4 - 4
miniprogram/module/order/pages/confirme-order/confirme-order.scss

@@ -320,10 +320,10 @@
     }
   }
 
-  &.address-card--disabled {
-    opacity: 0.9;
-    pointer-events: none;
-  }
+  // &.address-card--disabled {
+  //   opacity: 0.9;
+  //   pointer-events: none;
+  // }
 }
 
 .address-content {

+ 1 - 1
miniprogram/module/order/pages/confirme-order/confirme-order.wxml

@@ -3,7 +3,7 @@
 <scroll-view class="page-scroll__container bottom" type="list" scroll-y style="{{containerStyle}}">
   <view class="info-box {{ showDetail ? ' ' : 'show-bttom' }}">
     <!-- 收货人信息卡片 -->
-    <view class="address-card address-card--disabled" bindtap="changeAddress">
+    <view class="address-card" bindtap="changeAddress">
       <view class="delivery-address" wx:if="{{showDetail}}">
         请选择配送地址
       </view>

+ 8 - 0
miniprogram/module/order/pages/goods-evaluate/goods-evaluate.json

@@ -0,0 +1,8 @@
+{
+  "renderer": "skyline",
+  "usingComponents": {
+    "t-navbar": "tdesign-miniprogram/navbar/navbar",
+    "t-icon": "tdesign-miniprogram/icon/icon",
+    "t-rate": "tdesign-miniprogram/rate/rate"
+  }
+}

+ 225 - 0
miniprogram/module/order/pages/goods-evaluate/goods-evaluate.scss

@@ -0,0 +1,225 @@
+@import "../../../../themes/page.scss";
+
+.page-scroll__container {
+  flex: 0 1 auto;
+  height: var(--page-container-safeHeight, 100vh);
+  background: #f7f8fa;
+  padding-bottom: 140rpx;
+}
+
+.evaluate-container {
+  background: #fff;
+  margin: 24rpx;
+  border-radius: 16rpx;
+  padding: 32rpx;
+}
+
+.product-card {
+  display: flex;
+  align-items: center;
+  padding-bottom: 32rpx;
+  margin-bottom: 24rpx;
+  border-bottom: 1rpx solid #eee;
+}
+
+.product-img {
+  width: 120rpx;
+  height: 120rpx;
+  border-radius: 12rpx;
+  background: #f5f5f5;
+  flex-shrink: 0;
+}
+
+.product-info {
+  flex: 1;
+  margin-left: 24rpx;
+  min-width: 0;
+  display: flex;
+  flex-direction: column;
+  gap: 8rpx;
+}
+
+.product-name {
+  font-size: 30rpx;
+  color: #333;
+  font-weight: 500;
+}
+
+.product-spec {
+  font-size: 26rpx;
+  color: #999;
+}
+
+.product-close {
+  flex-shrink: 0;
+  padding: 16rpx;
+}
+
+.rate-row {
+  display: flex;
+  align-items: center;
+  // justify-content: space-between;
+  margin-bottom: 32rpx;
+}
+
+.rate-label {
+  font-size: 30rpx;
+  color: #333;
+  margin-right: 20px;
+}
+
+.rate-wrap {
+  display: flex;
+  align-items: center;
+  gap: 16rpx;
+}
+
+.rate-score {
+  font-size: 26rpx;
+  color: #999;
+  min-width: 48rpx;
+  margin-left: 20px;
+}
+
+.comment-section {
+  position: relative;
+  margin-bottom: 32rpx;
+}
+
+.comment-input {
+  width: 100%;
+  min-height: 200rpx;
+  padding: 24rpx;
+  font-size: 28rpx;
+  color: #333;
+  background: #f7f8fa;
+  border-radius: 12rpx;
+  box-sizing: border-box;
+}
+
+.comment-placeholder {
+  color: #999;
+}
+.product-name-row {
+  display: flex;
+  align-items: center;
+  flex-wrap: wrap;
+  gap: 8rpx;
+}
+.product-divider {
+  font-size: 28rpx;
+  color: #dcdcdc;
+  margin: 0 30rpx;
+}
+
+.comment-count {
+  position: absolute;
+  top: 24rpx;
+  right: 24rpx;
+  font-size: 24rpx;
+  color: #999;
+}
+
+.upload-section {
+  margin-bottom: 24rpx;
+}
+
+/* 一行 4 个,文件之间 24rpx 缝隙 */
+.media-grid {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 24rpx;
+}
+
+.media-item {
+  position: relative;
+  width: 140rpx;
+  height: 140rpx;
+  flex-shrink: 0;
+  border-radius: 12rpx;
+  overflow: hidden;
+  background: #f5f5f5;
+  margin-bottom: 10px;
+}
+
+.upload-trigger {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  width: 142rpx;
+  height: 142rpx;
+  flex-shrink: 0;
+  background: #f7f8fa;
+  border-radius: 12rpx;
+  border: 2rpx dashed #ddd;
+}
+
+.upload-label {
+  font-size: 24rpx;
+  color: #999;
+  margin-top: 12rpx;
+}
+
+.media-thumb {
+  width: 100%;
+  height: 100%;
+  display: block;
+}
+
+.media-item-video {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  .media-thumb {
+    display: block;
+  }
+}
+
+.media-item-play {
+  position: absolute;
+  left: 50%;
+  top: 50%;
+  transform: translate(-50%, -50%);
+  pointer-events: none;
+}
+
+.media-delete {
+  position: absolute;
+  top: 0;
+  right: 0;
+  width: 48rpx;
+  height: 48rpx;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border-radius: 0 12rpx 0 12rpx;
+}
+
+.publish-footer {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  background: #fff;
+  padding: 20rpx 32rpx;
+  box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.06);
+  z-index: 100;
+}
+
+.publish-btn {
+  height: 88rpx;
+  line-height: 88rpx;
+  text-align: center;
+  font-size: 32rpx;
+  font-weight: 500;
+  color: #fff;
+  background: #1976d2;
+  border-radius: 44rpx;
+}
+
+.publish-btn.disabled {
+  background: #e8e8e8;
+  color: #999;
+}

+ 205 - 0
miniprogram/module/order/pages/goods-evaluate/goods-evaluate.ts

@@ -0,0 +1,205 @@
+import PageContainerBehavior from "../../../../core/behavior/page-container.behavior";
+import { EvaluateModel } from "../../model/evaluate.model";
+import { evaluateOrderGoodsMethod } from "../../request";
+import { upload } from "../../../../lib/request/upload";
+
+function withMediaType(url: string, type: "image" | "video") {
+  if (!url) return url;
+  const hasQuery = url.includes("?");
+  const hasHash = url.includes("#");
+  if (hasHash) {
+    const [base, hash] = url.split("#");
+    return `${base}${hasQuery ? "&" : "?"}type=${type}#${hash ?? ""}`;
+  }
+  return `${url}${hasQuery ? "&" : "?"}type=${type}`;
+}
+Page({
+  behaviors: [PageContainerBehavior],
+  data: {
+    orderId: "",
+    product: {} as { name: string; image: string; description?: string, patientConditioningRecordId?: number, patientConditioningProgramId?: number },
+    score: 0,
+    rateColor: "#F7BA2A",
+    content: "",
+    evaluateInfo: {} as EvaluateModel,
+    mediaList: [] as { path: string; type: "image" | "video" }[],
+    canPublish: false,
+    videoFullscreen: false,
+  },
+  onLoad(options: Record<string, string>) {
+    console.log(options, "options===");
+    // const orderId = options.orderId || options.id || "";
+    let product: { name: string; image: string; description?: string, patientConditioningRecordId?: number, patientConditioningProgramId?: number } = {
+      name: "",
+      image: "",
+      description: "",
+      patientConditioningRecordId: 0,
+      patientConditioningProgramId: 0,
+    };
+    if (options.goodsInfo) {
+      const goods = JSON.parse(decodeURIComponent(options.goodsInfo));
+      console.log(goods, "goods===");
+      product = {
+        name: goods.name || "",
+        image: goods.image || "",
+        description: goods.description || "",
+        patientConditioningRecordId: goods.patientConditioningRecordId || 0,
+        patientConditioningProgramId: goods.id || 0,
+      };
+    }
+    this.setData({
+      // orderId,
+      product,
+      canPublish: this._checkCanPublish(0, ""),
+    });
+  },
+  _checkCanPublish(score: number, _content: string): boolean {
+    return score > 0;
+  },
+  onScoreChange(e: WechatMiniprogram.CustomEvent<{ value: number }>) {
+    const score = e.detail?.value ?? 0;
+    this.setData({
+      score,
+      canPublish: this._checkCanPublish(score, this.data.content),
+    });
+  },
+  onContentInput(e: WechatMiniprogram.Input) {
+    const content = e.detail?.value ?? "";
+    this.setData({
+      content,
+      canPublish: this._checkCanPublish(this.data.score, content),
+    });
+  },
+  onChooseMedia() {
+    const current = this.data.mediaList.length;
+    if (current >= 9) {
+      wx.showToast({ title: "图片和视频总数不能超过9个", icon: "none" });
+      return;
+    }
+    const remain = 9 - current;
+    wx.chooseMedia({
+      count: remain,
+      mediaType: ["image", "video"],
+      sourceType: ["album", "camera"],
+      maxDuration: 30,
+      camera: "back",
+      success: (res) => {
+        const list = res.tempFiles.map((f) => ({
+          path: f.tempFilePath,
+          type: (f.fileType === "video" ? "video" : "image") as "image" | "video",
+        }));
+        const oldLength = this.data.mediaList.length;
+        const next = [...this.data.mediaList, ...list].slice(0, 9);
+        this.setData({ mediaList: next });
+
+        // 选取本地媒体后,调用上传接口上传到服务器
+        list.forEach((item, idx) => {
+          const index = oldLength + idx;
+          upload<string, any>({
+            params: { name: "file", file: item.path },
+            transform({ data }: any): string {
+              return (data as any)?.url || (data as any);
+            },
+          }).then(
+            (url) => {
+              const key = `mediaList[${index}].path`;
+              this.setData({ [key]: url });
+            },
+            (error: any) => {
+              wx.showToast({
+                title: error?.errMsg || "上传失败",
+                icon: "none",
+              });
+              const mediaList = this.data.mediaList.filter((_, i) => i !== index);
+              this.setData({ mediaList });
+            }
+          );
+        });
+      },
+    });
+  },
+  onRemoveMedia(e: WechatMiniprogram.TouchEvent) {
+    const index = e.currentTarget.dataset.index as number;
+    const mediaList = this.data.mediaList.filter((_, i) => i !== index);
+    this.setData({ mediaList });
+  },
+  onPreviewImage(e: WechatMiniprogram.TouchEvent) {
+    const url = e.currentTarget.dataset.url as string;
+    const urls = this.data.mediaList.filter((m) => m.type === "image").map((m) => m.path);
+    if (url && urls.length) {
+      wx.previewImage({ current: url, urls });
+    }
+  },
+  onPreviewVideo(e: WechatMiniprogram.TouchEvent) {
+    const index = e.currentTarget.dataset.index as number;
+    const videoContext = wx.createVideoContext("goods-video-" + index, this);
+    videoContext.requestFullScreen({});
+  },
+  /** 点击图片/视频:原生全屏预览(类似 test 页) */
+  onPreviewMedia(e: WechatMiniprogram.TouchEvent) {
+    const index = e.currentTarget.dataset.index as number;
+    const { mediaList } = this.data;
+    if (index < 0 || index >= mediaList.length) return;
+
+    const sources = mediaList.map((m) => ({
+      url: m.path,
+      type: m.type as "image" | "video",
+    }));
+
+    const previewMedia = (wx as any).previewMedia as
+      | ((option: { sources: { url: string; type: "image" | "video" }[]; current: number }) => void)
+      | undefined;
+
+    if (typeof previewMedia === "function") {
+      previewMedia({ sources, current: index });
+      return;
+    }
+
+    // 低版本兜底:图片用 previewImage,视频用 requestFullScreen
+    const current = mediaList[index];
+    if (current.type === "image") {
+      const urls = mediaList.filter((m) => m.type === "image").map((m) => m.path);
+      wx.previewImage({ current: current.path, urls });
+      return;
+    }
+    const ctx = wx.createVideoContext("goods-video-" + index, this);
+    ctx.requestFullScreen({});
+  },
+  onVideoFullscreenChange(e: WechatMiniprogram.VideoFullScreenChange) {
+    const fullScreen = !!(e.detail && e.detail.fullScreen);
+    this.setData({ videoFullscreen: fullScreen });
+  },
+  onRemoveProduct() {
+    this.setData({
+      product: { name: "", image: "", description: "", patientConditioningRecordId: 0, patientConditioningProgramId: 0 },
+    });
+  },
+  async onPublish() {
+    if (!this.data.canPublish) {
+      wx.showToast({ title: "请先完成评分", icon: "none" });
+      return;
+    }
+
+    console.log(this.data.product,"90000")
+    const data = {
+      patientConditioningProgramId: this.data.product.patientConditioningProgramId,
+      patientConditioningRecordId: this.data.product.patientConditioningRecordId,
+      complianceScore: this.data.score,
+      depict: this.data.content,
+      imageVideos: this.data.mediaList.map((m) => withMediaType(m.path, m.type)),
+    };
+    console.log(data,"评价数据")
+    try {
+      wx.showLoading({ title: "发布中..." });
+      await evaluateOrderGoodsMethod(data);
+      wx.hideLoading();
+      wx.showToast({ title: "发布成功", icon: "success" });
+      wx.redirectTo({
+        url: `/module/article/pages/order-list/order-list`,
+      });
+    } catch (error: any) {
+      wx.hideLoading();
+      wx.showToast({ title: error.errMsg || "发布失败", icon: "none" });
+    }
+  },
+});

+ 72 - 0
miniprogram/module/order/pages/goods-evaluate/goods-evaluate.wxml

@@ -0,0 +1,72 @@
+<!--module/order/pages/evaluate/evaluate.wxml 评价页-->
+<t-navbar wx:if="{{!videoFullscreen}}" title="评价" left-arrow />
+<scroll-view class="page-scroll__container" type="list" scroll-y style="{{containerStyle}}">
+  <view class="evaluate-container">
+    <!-- 商品信息 -->
+    <view class="product-card" wx:if="{{product.name}}">
+      <image class="product-img" src="{{product.image}}" mode="aspectFill" />
+      <view class="product-info">
+       <view class="product-name-row">
+        <text class="product-name">{{product.name}}</text>
+        <text class="product-divider" wx:if="{{product.description}}">|</text>
+        <text class="product-spec" wx:if="{{product.description}}">{{product.description}}</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 描述相符 评分(半星) -->
+    <view class="rate-row">
+      <text class="rate-label">描述相符</text>
+      <view class="rate-wrap">
+        <t-rate
+          value="{{score}}"
+          count="{{5}}"
+          color="{{rateColor}}"
+          show-text="{{false}}"
+          placement=""
+          bind:change="onScoreChange"
+        />
+        <text class="rate-score">{{score || '0'}}分</text>
+      </view>
+    </view>
+
+    <!-- 评价输入 -->
+    <view class="comment-section">
+      <textarea
+        class="comment-input"
+        placeholder="展开说说对商品的想法吧"
+        placeholder-class="comment-placeholder"
+        value="{{content}}"
+        bindinput="onContentInput"
+        maxlength="{{200}}"
+        show-confirm-bar="{{false}}"
+      />
+      <view class="comment-count" wx:if="{{content.length > 0}}">{{content.length}}/200</view>
+    </view>
+
+    <!-- 图/视频 上传(图片+视频总数不超过9个,添加按钮在最后,一行4个) -->
+    <view class="upload-section">
+      <view class="media-grid">
+        <view class="media-item" wx:for="{{mediaList}}" wx:key="path">
+          <image wx:if="{{item.type === 'image'}}" class="media-thumb" src="{{item.path}}" mode="aspectFill" bindtap="onPreviewMedia" data-index="{{index}}" />
+          <view wx:else class="media-item-video" bindtap="onPreviewMedia" data-index="{{index}}">
+            <video id="goods-video-{{index}}" class="media-thumb" src="{{item.path}}" show-center-play-btn="{{true}}" object-fit="cover" controls="{{true}}" bindfullscreenchange="onVideoFullscreenChange" />
+            <view class="media-item-play"><t-icon name="play-circle-filled" size="56rpx" color="rgba(255,255,255,0.95)" /></view>
+          </view>
+          <view class="media-delete" catchtap="onRemoveMedia" data-index="{{index}}">
+            <t-icon name="close" size="32rpx" color="#fff" />
+          </view>
+        </view>
+        <view class="upload-trigger" wx:if="{{mediaList.length < 9}}" bindtap="onChooseMedia">
+          <t-icon name="add" size="48rpx" color="#999" />
+          <text class="upload-label">图/视频</text>
+        </view>
+      </view>
+    </view>
+  </view>
+</scroll-view>
+
+<!-- 发布按钮(视频全屏时隐藏,避免遮挡进度条) -->
+<view class="publish-footer" wx:if="{{!videoFullscreen}}" style="padding-bottom: {{container.safeBottomOffset}}px;">
+  <view class="publish-btn {{canPublish ? '' : 'disabled'}}" bindtap="onPublish">发布</view>
+</view>

+ 8 - 0
miniprogram/module/order/pages/goods-evaluateDetail/goods-evaluateDetail.json

@@ -0,0 +1,8 @@
+{
+  "renderer": "skyline",
+  "usingComponents": {
+    "t-navbar": "tdesign-miniprogram/navbar/navbar",
+    "t-icon": "tdesign-miniprogram/icon/icon",
+    "t-rate": "tdesign-miniprogram/rate/rate"
+  }
+}

+ 349 - 0
miniprogram/module/order/pages/goods-evaluateDetail/goods-evaluateDetail.scss

@@ -0,0 +1,349 @@
+@import "../../../../themes/page.scss";
+
+.page-scroll__container {
+  flex: 0 1 auto;
+  height: var(--page-container-safeHeight, 100vh);
+  background: #f7f8fa;
+  padding-bottom: 140rpx;
+}
+
+.evaluate-container {
+  background: #fff;
+  margin: 24rpx;
+  border-radius: 16rpx;
+  padding: 32rpx;
+}
+
+.product-card {
+  position: relative;
+  display: flex;
+  align-items: center;
+  padding-bottom: 32rpx;
+  margin-bottom: 24rpx;
+  border-bottom: 1rpx solid #eee;
+}
+
+.product-img {
+  width: 120rpx;
+  height: 120rpx;
+  border-radius: 12rpx;
+  background: #f5f5f5;
+  flex-shrink: 0;
+}
+
+.product-info {
+  flex: 1;
+  margin-left: 24rpx;
+  min-width: 0;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+}
+
+.product-name-row {
+  display: flex;
+  align-items: center;
+  flex-wrap: wrap;
+  gap: 8rpx;
+}
+
+.product-name {
+  font-size: 30rpx;
+  color: #333;
+  font-weight: 500;
+}
+
+.product-divider {
+  font-size: 28rpx;
+  color: #dcdcdc;
+  margin: 0 30rpx;
+}
+
+.product-spec {
+  font-size: 26rpx;
+  color: #999;
+}
+
+.product-close {
+  position: absolute;
+  top: 0;
+  right: 0;
+  padding: 16rpx;
+  z-index: 1;
+}
+
+.rate-row {
+  display: flex;
+  align-items: center;
+  // justify-content: space-between;
+  margin-bottom: 32rpx;
+}
+
+.rate-label {
+  font-size: 30rpx;
+  color: #333;
+  margin-right: 20px;
+}
+
+.rate-wrap {
+  display: flex;
+  align-items: center;
+  gap: 16rpx;
+}
+
+.rate-score {
+  font-size: 28rpx;
+  color: #333;
+  min-width: 56rpx;
+  margin-left: 20px;
+}
+
+.comment-section {
+  position: relative;
+  margin-bottom: 32rpx;
+}
+
+.comment-display {
+  width: 100%;
+  min-height: 120rpx;
+  padding: 24rpx;
+  font-size: 28rpx;
+  color: #333;
+  background: #f7f8fa;
+  border-radius: 12rpx;
+  box-sizing: border-box;
+  line-height: 1.5;
+  white-space: pre-wrap;
+  word-break: break-all;
+}
+
+.comment-input {
+  width: 100%;
+  min-height: 200rpx;
+  padding: 24rpx;
+  font-size: 28rpx;
+  color: #333;
+  background: #f7f8fa;
+  border-radius: 12rpx;
+  box-sizing: border-box;
+}
+
+.comment-placeholder {
+  color: #999;
+}
+
+.comment-count {
+  position: absolute;
+  top: 24rpx;
+  right: 24rpx;
+  font-size: 24rpx;
+  color: #999;
+}
+
+.upload-section {
+  margin-bottom: 24rpx;
+}
+
+.upload-section .empty-media {
+  font-size: 26rpx;
+  color: #999;
+  margin-top: 8rpx;
+  display: block;
+}
+
+.section-label {
+  font-size: 28rpx;
+  color: #666;
+  margin-bottom: 16rpx;
+}
+
+.upload-row {
+  display: flex;
+  gap: 20rpx;
+  flex-wrap: wrap;
+}
+
+.upload-trigger {
+  display: inline-flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  width: 160rpx;
+  height: 160rpx;
+  border-radius: 12rpx;
+  border: 2rpx dashed #ddd;
+}
+
+.upload-trigger--image {
+  background: #f2f2f2;
+}
+
+.upload-trigger--video {
+  background: #4a4a4a;
+  border-color: #333;
+}
+
+.upload-label {
+  font-size: 26rpx;
+  color: #999;
+  margin-top: 12rpx;
+}
+
+.upload-label--light {
+  color: rgba(255, 255, 255, 0.9);
+}
+
+.upload-trigger .t-icon {
+  display: block;
+}
+
+/* 一行 4 个,与评价页一致 */
+.media-grid {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 24rpx;
+}
+
+.media-list--readonly .media-item {
+  .media-delete {
+    display: none;
+  }
+}
+
+.media-item {
+  position: relative;
+  width: 140rpx;
+  height: 140rpx;
+  flex-shrink: 0;
+  border-radius: 12rpx;
+  overflow: hidden;
+  background: #f5f5f5;
+  margin-bottom: 10px;
+}
+
+.media-thumb {
+  width: 100%;
+  height: 100%;
+  display: block;
+}
+
+.media-item-video {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  .media-thumb {
+    display: block;
+  }
+}
+
+.media-item-play {
+  position: absolute;
+  left: 50%;
+  top: 50%;
+  transform: translate(-50%, -50%);
+  pointer-events: none;
+}
+
+.media-delete {
+  position: absolute;
+  top: 0;
+  right: 0;
+  width: 48rpx;
+  height: 48rpx;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border-radius: 0 12rpx 0 12rpx;
+}
+
+.publish-footer {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  background: #fff;
+  padding: 20rpx 32rpx;
+  box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.06);
+  z-index: 100;
+}
+
+.publish-btn {
+  height: 88rpx;
+  line-height: 88rpx;
+  text-align: center;
+  font-size: 32rpx;
+  font-weight: 500;
+  color: #fff;
+  background: #1976d2;
+  border-radius: 44rpx;
+}
+
+.publish-btn.disabled {
+  background: #e8e8e8;
+  color: #999;
+}
+
+/* 全屏轮播层:图片+视频一起,轮播到视频自动播放 */
+.media-carousel-overlay {
+  position: fixed;
+  left: 0;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 1000;
+  background: #000;
+}
+
+.media-carousel-swiper {
+  width: 100%;
+  height: 100%;
+}
+
+.media-carousel-item {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.media-carousel-image-wrap {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.media-carousel-image {
+  width: 100%;
+  height: 100%;
+  display: block;
+  object-position: center;
+}
+
+.media-carousel-video-wrap {
+  width: 100%;
+  height: 100%;
+}
+
+.media-carousel-video {
+  width: 100%;
+  height: 100%;
+  display: block;
+}
+
+.media-carousel-close {
+  position: absolute;
+  top: calc(var(--status-bar-height, 0px) + 24rpx);
+  right: 24rpx;
+  width: 80rpx;
+  height: 80rpx;
+  border-radius: 50%;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 1001;
+}

+ 164 - 0
miniprogram/module/order/pages/goods-evaluateDetail/goods-evaluateDetail.ts

@@ -0,0 +1,164 @@
+import PageContainerBehavior from "../../../../core/behavior/page-container.behavior";
+import { getOrderGoodsEvaluationMethod } from "../../request";
+import { EvaluateModel } from "../../model/evaluate.model";
+
+function parseMediaType(url: string): "image" | "video" {
+  if (!url) return "image";
+  const match = /[?&]type=(image|video)\b/i.exec(url);
+  if (match) return match[1].toLowerCase() as "image" | "video";
+  const lower = url.toLowerCase();
+  if (/\.(mp4|mov|avi|rmvb|flv|mkv|webm)$/.test(lower)) return "video";
+  return "image";
+}
+
+Page({
+  behaviors: [PageContainerBehavior],
+  data: {
+    orderId: "",
+    product: {} as { id: number; name: string; image: string; description?: string },
+    evaluateInfo: {} as EvaluateModel,
+    // 只读展示用
+    score: 0,
+    rateColor: "#F7BA2A",
+    content: "",
+    mediaList: [] as { path: string; type: "image" | "video" }[],
+    videoFullscreen: false,
+    showMediaCarousel: false,
+    mediaCarouselCurrent: 0,
+  },
+  async onLoad(options: Record<string, string>) {
+    console.log(options, "options===");
+
+
+    let product: { id: number; name: string; image: string; description?: string } = {
+      id: 0,
+      name: "",
+      image: "",
+      description: "",
+    };
+    console.log(options, "options.goodsInfo===");
+    if (options.goodsInfo) {
+      try {
+        const goods = JSON.parse(decodeURIComponent(options.goodsInfo));
+        console.log(goods, "goods===");
+        product = {
+          id: goods.id || 0,
+          name: goods.name || "",
+          image: goods.image || "",
+          description: goods.description || "",
+        };
+      } catch (_) { }
+    }
+    this.setData({ product });
+    console.log(product, "product===");
+
+    if (product.id) {
+      try {
+        const res = await getOrderGoodsEvaluationMethod("1", product.id);
+        if (!res && !res.data) return;
+        console.log(res, "res===");
+        const mediaList: { path: string; type: "image" | "video" }[] = [];
+        if (Array.isArray(res.data.imageVideos)) {
+          res.data.imageVideos.forEach((url: string) => {
+            if (!url) return;
+            mediaList.push({
+              path: url,
+              type: parseMediaType(url),
+            });
+          });
+        }
+        this.setData({
+          evaluateInfo: res.data,
+          score: Number(res.data.complianceScore ?? 0),
+          content: res.data.depict || "",
+          mediaList,
+        });
+
+      } catch (error: any) {
+        console.log(error, "error===");
+        wx.showToast({
+          title: error?.errMsg || "获取评价详情失败",
+          icon: "none",
+        });
+      }
+    }
+  },
+  onBack() {
+    wx.navigateBack();
+  },
+  // 图片预览(九宫格内点击)
+  onPreviewImage(e: WechatMiniprogram.TouchEvent) {
+    const url = e.currentTarget.dataset.url as string;
+    const urls = this.data.mediaList
+      .filter((m) => m.type === "image")
+      .map((m) => m.path);
+    if (url && urls.length) {
+      wx.previewImage({ current: url, urls });
+    }
+  },
+  // 视频预览(九宫格内点击,先全屏)
+  onPreviewVideo(e: WechatMiniprogram.TouchEvent) {
+    const index = e.currentTarget.dataset.index as number;
+    const ctx = wx.createVideoContext("goods-detail-video-" + index, this);
+    ctx.requestFullScreen({});
+  },
+  /** 点击图片/视频:原生全屏预览(类似 test 页) */
+  onPreviewMedia(e: WechatMiniprogram.TouchEvent) {
+    const index = e.currentTarget.dataset.index as number;
+    const { mediaList } = this.data;
+    if (index < 0 || index >= mediaList.length) return;
+
+    const sources = mediaList.map((m) => ({
+      url: m.path,
+      type: m.type as "image" | "video",
+    }));
+
+    const previewMedia = (wx as any).previewMedia as
+      | ((option: { sources: { url: string; type: "image" | "video" }[]; current: number }) => void)
+      | undefined;
+
+    if (typeof previewMedia === "function") {
+      previewMedia({ sources, current: index });
+      return;
+    }
+
+    // 低版本兜底:图片用 previewImage,视频用 requestFullScreen
+    const current = mediaList[index];
+    if (current.type === "image") {
+      const urls = mediaList.filter((m) => m.type === "image").map((m) => m.path);
+      wx.previewImage({ current: current.path, urls });
+      return;
+    }
+    const ctx = wx.createVideoContext("goods-detail-video-" + index, this);
+    ctx.requestFullScreen({});
+  },
+  /** 轮播切换:暂停所有视频,若当前项是视频则播放 */
+  onMediaCarouselChange(e: WechatMiniprogram.SwiperChange) {
+    const current = e.detail?.current ?? 0;
+    this.setData({ mediaCarouselCurrent: current });
+    this._pauseAllCarouselVideos();
+    this._playVideoAtCarouselIndex(current);
+  },
+  onCloseMediaCarousel() {
+    this._pauseAllCarouselVideos();
+    this.setData({ showMediaCarousel: false });
+  },
+  _playVideoAtCarouselIndex(index: number) {
+    const list = this.data.mediaList;
+    if (index < 0 || index >= list.length || list[index].type !== "video") return;
+    const ctx = wx.createVideoContext("preview-video-" + index, this);
+    ctx.play();
+  },
+  _pauseAllCarouselVideos() {
+    this.data.mediaList.forEach((item, i) => {
+      if (item.type === "video") {
+        const ctx = wx.createVideoContext("preview-video-" + i, this);
+        ctx.pause();
+      }
+    });
+  },
+  onVideoFullscreenChange(e: WechatMiniprogram.VideoFullScreenChange) {
+    const fullScreen = !!(e.detail && e.detail.fullScreen);
+    this.setData({ videoFullscreen: fullScreen });
+  },
+});

+ 57 - 0
miniprogram/module/order/pages/goods-evaluateDetail/goods-evaluateDetail.wxml

@@ -0,0 +1,57 @@
+<!--module/order/pages/goods-evaluateDetail/goods-evaluateDetail.wxml 评价详情(只读)-->
+<t-navbar wx:if="{{!videoFullscreen}}" title="评价" left-arrow />
+<scroll-view class="page-scroll__container" type="list" scroll-y style="{{containerStyle}}">
+  <view class="evaluate-container">
+    <!-- 商品信息:仅展示 -->
+    <view class="product-card" wx:if="{{product.name}}">
+      <image class="product-img" src="{{product.image}}" mode="aspectFill" />
+      <view class="product-info">
+        <view class="product-name-row">
+          <text class="product-name">{{product.name}}</text>
+          <text class="product-divider" wx:if="{{product.description}}">|</text>
+          <text class="product-spec" wx:if="{{product.description}}">{{product.description}}</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 描述相符 评分(只读展示) -->
+    <view class="rate-row">
+      <text class="rate-label">描述相符</text>
+      <view class="rate-wrap">
+        <t-rate
+          value="{{score}}"
+          count="{{5}}"
+          color="{{rateColor}}"
+          show-text="{{false}}"
+          disabled="{{true}}"
+        />
+        <text class="rate-score">{{score || '0'}}分</text>
+      </view>
+    </view>
+
+    <!-- 评价内容:只读展示 -->
+    <view class="comment-section">
+      <view class="comment-display">{{content || '暂无评价内容'}}</view>
+    </view>
+
+    <!-- 图片/视频:一行4个,可点击预览,视频全屏时隐藏导航和底部 -->
+    <view class="upload-section">
+      <view class="media-grid media-list--readonly" wx:if="{{mediaList.length > 0}}">
+        <view class="media-item" wx:for="{{mediaList}}" wx:key="path">
+          <image wx:if="{{item.type === 'image'}}" class="media-thumb" src="{{item.path}}" mode="aspectFill" bindtap="onPreviewMedia" data-index="{{index}}" />
+          <view wx:else class="media-item-video" bindtap="onPreviewMedia" data-index="{{index}}">
+            <video id="goods-detail-video-{{index}}" class="media-thumb" src="{{item.path}}" show-center-play-btn="{{true}}" object-fit="cover" controls="{{true}}" bindfullscreenchange="onVideoFullscreenChange" />
+            <view class="media-item-play"><t-icon name="play-circle-filled" size="56rpx" color="rgba(255,255,255,0.95)" /></view>
+          </view>
+        </view>
+      </view>
+    </view>
+  </view>
+</scroll-view>
+
+<!-- 底部返回(视频全屏时隐藏,避免遮挡) -->
+<view class="publish-footer" wx:if="{{!videoFullscreen}}" style="padding-bottom: {{container.safeBottomOffset}}px;">
+  <view class="publish-btn" bindtap="onBack">返回</view>
+</view>
+
+

+ 10 - 0
miniprogram/module/order/pages/logistics-detail/logistics-detail.json

@@ -0,0 +1,10 @@
+{
+  "renderer": "skyline",
+  "component": true,
+  "usingComponents": {
+    "t-navbar": "tdesign-miniprogram/navbar/navbar",
+    "t-icon": "tdesign-miniprogram/icon/icon",
+    "t-tag": "tdesign-miniprogram/tag/tag",
+    "t-image": "tdesign-miniprogram/image/image"
+  }
+}

+ 395 - 0
miniprogram/module/order/pages/logistics-detail/logistics-detail.scss

@@ -0,0 +1,395 @@
+@import "../../../../themes/t.cell.scss";
+@import "../../../../themes/page.scss";
+@import "../../assets/iconfont/iconfont.wxss";
+
+/* module/order/pages/logistics-detail/logistics-detail.scss */
+.page-scroll__container {
+  flex: 0 1 auto;
+  height: var(--page-container-safeHeight, 100vh);
+  background: #f5f5f5;
+  // padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
+  box-sizing: border-box;
+  // padding-top: 10px;
+}
+
+.logistics-page {
+  padding-bottom: 32rpx;
+}
+
+.map-header {
+  height: 320rpx;
+  background: linear-gradient(180deg, #dde6f0 0%, #e8edf4 45%, #f0f2f5 100%);
+  position: relative;
+  overflow: hidden;
+  display: flex;
+  flex-direction: column;
+}
+
+.map-header__map {
+  width: 100%;
+  flex: 1;
+  min-height: 264rpx;
+}
+
+.map-header__fallback {
+  flex: 1;
+  min-height: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 0 32rpx;
+}
+
+.map-header__fallback-text {
+  font-size: 28rpx;
+  color: rgba(0, 0, 0, 0.45);
+}
+
+.map-header__inner {
+  position: absolute;
+  inset: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  opacity: 0.85;
+  pointer-events: none;
+}
+
+.map-header__inner::before {
+  content: "";
+  position: absolute;
+  left: 10%;
+  right: 10%;
+  top: 38%;
+  height: 2rpx;
+  background: rgba(255, 255, 255, 0.7);
+  transform: rotate(-8deg);
+}
+
+.map-header__inner::after {
+  content: "";
+  position: absolute;
+  left: 20%;
+  right: 15%;
+  top: 55%;
+  height: 2rpx;
+  background: rgba(255, 255, 255, 0.5);
+  transform: rotate(6deg);
+}
+
+.map-header__city {
+  font-size: 28rpx;
+  color: rgba(0, 0, 0, 0.35);
+  font-weight: 500;
+}
+
+.logistics-card {
+  padding: 32rpx 28rpx 28rpx;
+  background: #fff;
+  box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.06);
+  position: relative;
+  z-index: 1;
+}
+
+.carrier-row {
+  display: flex;
+  align-items: center;
+  margin-bottom: 28rpx;
+}
+
+.carrier-row__logo {
+  width: 50rpx;
+  height: 50rpx;
+  border-radius: 50%;
+  background: #1e6ee8;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+}
+
+.carrier-row__logo-text {
+  font-size: 22rpx;
+  color: #fff;
+  font-weight: 600;
+}
+
+.carrier-row__info {
+  flex: 1;
+  min-width: 0;
+  margin-left: 20rpx;
+  display: flex;
+  gap: 8rpx;
+  align-items: center;
+}
+
+.carrier-row__name {
+  font-size: 24rpx;
+  font-weight: 600;
+  color: #888;
+  margin-right: 3px;
+}
+
+.carrier-row__no {
+  font-size: 24rpx;
+  color: #888;
+}
+
+.carrier-row__actions {
+  display: flex;
+  align-items: center;
+  flex-shrink: 0;
+  color: #888;
+}
+
+.carrier-row__action {
+  font-size: 26rpx;
+  padding: 8rpx 12rpx;
+}
+
+.carrier-row__divider {
+  width: 1rpx;
+  height: 28rpx;
+  background: #e5e5e5;
+  margin: 0 4rpx;
+}
+
+.trace-list {
+  position: relative;
+}
+
+.trace-list__axis {
+  position: absolute;
+  left: 16rpx;
+  top: 22rpx;
+  bottom: 26rpx;
+  width: 0;
+  border-left: 2rpx dashed #d8d8d8;
+}
+
+.trace-item {
+  position: relative;
+  padding-left: 48rpx;
+  padding-bottom: 8rpx;
+}
+
+.trace-item--current {
+  padding-bottom: 16rpx;
+}
+
+.trace-item--expand {
+  padding-bottom: 0;
+}
+
+.trace-item__dot {
+  position: absolute;
+  left: 16rpx;
+  top: 10rpx;
+  width: 16rpx;
+  height: 16rpx;
+  border-radius: 50%;
+  transform: translateX(-50%);
+  z-index: 1;
+}
+
+.trace-item__dot--active {
+  background: #ff6b00;
+  box-shadow: 0 0 0 6rpx rgba(255, 107, 0, 0.22);
+}
+
+.trace-item__dot--grey {
+  background: #c8c8c8;
+}
+
+.trace-item__dot--hollow {
+  width: 14rpx;
+  height: 14rpx;
+  top: 12rpx;
+  border: 2rpx solid #bbb;
+  background: #fff;
+}
+
+.trace-item__body--inline {
+  display: flex;
+  align-items: center;
+  gap: 8rpx;
+}
+
+.trace-item__title-row {
+  display: flex;
+  align-items: baseline;
+  flex-wrap: wrap;
+  gap: 16rpx;
+  margin-bottom: 12rpx;
+}
+
+.trace-item__status {
+  font-size: 28rpx;
+  font-weight: 600;
+  color: #333;
+}
+
+.trace-item__status--accent {
+  font-size: 32rpx;
+  font-weight: 700;
+  color: #ff6b00;
+}
+
+.trace-item__time {
+  font-size: 24rpx;
+  color: #999;
+}
+
+.trace-item__time--accent {
+  font-size: 26rpx;
+  color: #ff6b00;
+}
+
+.trace-item__desc {
+  font-size: 26rpx;
+  color: #888;
+  line-height: 1.55;
+  display: block;
+}
+
+.trace-item__desc--muted {
+  color: #888;
+}
+
+.trace-expand__text {
+  font-size: 26rpx;
+  color: #999;
+}
+
+.logistics-divider {
+  height: 1rpx;
+  background: #eee;
+  margin: 28rpx 0 24rpx;
+}
+
+.addr-block {
+  display: flex;
+  align-items: flex-start;
+  gap: 12rpx;
+}
+
+.addr-block__icon {
+  flex-shrink: 0;
+  margin-top: 6rpx;
+}
+
+.addr-block__main {
+  flex: 1;
+  min-width: 0;
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
+  margin-left: 5px;
+}
+
+.addr-block__address {
+  font-size: 28rpx;
+  font-weight: 600;
+  color: #333;
+  line-height: 1.45;
+  width: 100%;
+  margin-bottom: 10rpx;
+}
+
+.addr-block__contact {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  gap: 12rpx 16rpx;
+  width: 100%;
+  color: #888;
+  font-size: 22rpx;
+}
+
+.address-name {
+  font-weight: 500;
+  line-height: 1.4;
+}
+
+.address-phone {
+  font-weight: 400;
+  line-height: 1.4;
+}
+
+.addr-block__tag {
+  font-size: 18rpx !important;
+  color: #888 !important;
+  padding: 0rpx 2rpx !important;
+}
+
+.addr-block__tag--orange {
+  color: #ff6b00 !important;
+  border-color: rgba(255, 107, 0, 0.45) !important;
+}
+
+
+.addr-block__virtual-arrow {
+  margin-left: -4rpx;
+}
+
+.goods-section {
+  margin: 24rpx 0rpx 0;
+  padding: 8rpx 24rpx 24rpx;
+  background: #fff;
+}
+
+.goods-item {
+  display: flex;
+  align-items: flex-start;
+  padding: 24rpx 0;
+  border-bottom: 1rpx solid #f0f0f0;
+}
+
+.goods-item:last-child {
+  border-bottom: none;
+}
+
+.goods-item__img {
+  flex-shrink: 0;
+  margin-right: 20rpx;
+}
+
+.goods-item__main {
+  flex: 1;
+  min-width: 0;
+  display: flex;
+  flex-direction: column;
+  gap: 10rpx;
+}
+
+.goods-item__title {
+  font-size: 28rpx;
+  color: #333;
+  margin-bottom: 10px;
+}
+
+.goods-item__spec {
+  font-size: 24rpx;
+  color: #999;
+}
+
+.goods-item__price-col {
+  flex-shrink: 0;
+  text-align: right;
+  display: flex;
+  flex-direction: column;
+  gap: 12rpx;
+  align-items: flex-end;
+}
+
+.goods-item__price {
+  font-size: 28rpx;
+  color: #333;
+  font-weight: 600;
+  margin-bottom: 10px;
+}
+
+.goods-item__qty {
+  font-size: 24rpx;
+  color: #999;
+}

+ 188 - 0
miniprogram/module/order/pages/logistics-detail/logistics-detail.ts

@@ -0,0 +1,188 @@
+import PageContainerBehavior from "../../../../core/behavior/page-container.behavior";
+import DictionariesBehavior from "../../../../core/behavior/dictionaries.behavior";
+import tickleBehavior from "../../../../core/behavior/tickle.behavior";
+import { getLogisticsMethod } from "../../request"
+interface TraceItem {
+  status: string;
+  time: string;
+  desc: string;
+  timeKey?: string;
+}
+
+interface LogisticsTrack {
+  time?: string;
+  context?: string;
+  areaName?: string;
+  status?: string;
+  location?: string;
+  areaCode?: string | null;
+  areaCenter?: any;
+  areaPinYin?: string | null;
+  statusCode?: any;
+}
+
+interface MapPoint {
+  latitude: number;
+  longitude: number;
+}
+
+Page({
+  behaviors: [PageContainerBehavior, DictionariesBehavior, tickleBehavior],
+  async onLoad(options: any) {
+    const id = options?.id;
+    if (id) {
+      try {
+        const res = await getLogisticsMethod(id as any);
+        const tracks: LogisticsTrack[] = res?.data?.tracks ?? [];
+        const [latestTrack, ...historyTracks] = tracks;
+        this.setData({
+          showAllTrace: false,
+          latestTrace: latestTrack ? this.trackToTraceItem(latestTrack) : ({ status: "", time: "", desc: "" } as TraceItem),
+          traceHistory: historyTracks.map((t) => this.trackToTraceItem(t)),
+        });
+        console.log(tracks, "tracks")
+        // 顶部地图背景:只使用 tracks.areaCenter 画轨迹
+        this.applyMapFromLogistics(tracks);
+      } catch (e: any) {
+        this.setData({
+          showAllTrace: false,
+          latestTrace: { status: "", time: "", desc: "" } as TraceItem,
+          traceHistory: [],
+        });
+      }
+    }
+    if (options.goods) {
+      const goosInfo = JSON.parse(options.goods);
+      const expressType = goosInfo.expressType
+      this.setData({
+        deliveryAddress: goosInfo.address,
+        recipientName: goosInfo.liaison,
+        recipientPhone: goosInfo.phone,
+        expressType,
+        carrierName: goosInfo.expressType,
+        trackingNo: goosInfo.expressNo,
+        goosInfo
+      });
+    }
+  },
+  data: {
+    // 物流start
+    hasMapPosition: false,
+    latitude: 0,
+    longitude: 0,
+    mapScale: 14,
+    markers: [] as any[],
+    polyline: [] as any[],
+    includePoints: [] as MapPoint[],
+    // end
+    carrierName: "",
+    expressType: "",
+    trackingNo: "",
+    courierPhone: "",
+    showAllTrace: false,
+    latestTrace: {
+      status: "",
+      time: "",
+      desc: "",
+    } as TraceItem,
+    traceHistory: [
+    ] as TraceItem[],
+    deliveryAddress: "",
+    recipientName: "",
+    recipientPhone: "",
+    goosInfo: {}
+  },
+  // --start
+  buildRoutePointsFromTracks(tracks: LogisticsTrack[]): MapPoint[] {
+    if (!Array.isArray(tracks) || tracks.length === 0) return [];
+    const pts: MapPoint[] = [];
+    for (const t of [...tracks].reverse()) {
+      if (!t?.areaCenter) continue;
+      const [lng, lat] = t.areaCenter.split(",").map((s: string) => Number(s.trim()));
+      if (!Number.isNaN(lng) && !Number.isNaN(lat)) {
+        pts.push({ latitude: lat, longitude: lng });
+      }
+    }
+    return pts;
+  },
+
+  applyMapFromLogistics(tracks: LogisticsTrack[]) {
+    // 把每个点转为经纬度
+    const routePoints = this.buildRoutePointsFromTracks(tracks || []);
+    // 多点的情况
+    if (routePoints.length >= 2) {
+      // 第一个点的经纬度
+      const first = routePoints[0];
+      // 最后一个点的经纬度
+      const last = routePoints[routePoints.length - 1];
+      this.setData({
+        // 设置地图的显示状态
+        hasMapPosition: true,
+        // 设置地图的经纬度
+        latitude: (first.latitude + last.latitude) / 2,
+        longitude: (first.longitude + last.longitude) / 2,
+        // 设置地图的缩放级别
+        mapScale: 11,
+        // 设置轨迹点
+        markers: [
+          { id: 1, latitude: first.latitude, longitude: first.longitude, width: 26, height: 26 },
+          { id: 2, latitude: last.latitude, longitude: last.longitude, width: 26, height: 26 },
+        ],
+        // 设置轨迹线
+        polyline: [
+          {
+            points: routePoints,
+            color: "#FF6B00AA",
+            width: 4,
+          },
+        ],
+        // 设置轨迹点
+        includePoints: routePoints,
+      });
+      return;
+    }
+    // 单点的情况
+    if (routePoints.length === 1) {
+      const only = routePoints[0];
+      this.setData({
+        hasMapPosition: true,
+        latitude: only.latitude,
+        longitude: only.longitude,
+        mapScale: 14,
+        markers: [{ id: 1, latitude: only.latitude, longitude: only.longitude, width: 28, height: 28 }],
+        polyline: [],
+        includePoints: [only],
+      });
+      return;
+    }
+    // 无轨迹点的情况
+    this.setData({
+      hasMapPosition: false,
+      markers: [],
+      polyline: [],
+      includePoints: [],
+    });
+  },
+  // end
+  trackToTraceItem(track: LogisticsTrack): TraceItem {
+    const time = String(track?.time ?? "");
+    return {
+      status: String(track?.status ?? ""),
+      time,
+      desc: String(track?.context ?? ""),
+      timeKey: time,
+    };
+  },
+  toggleTraceExpand() {
+    this.setData({ showAllTrace: !this.data.showAllTrace });
+  },
+  onCopyTracking() {
+    const no = this.data.trackingNo;
+    wx.setClipboardData({
+      data: no,
+      success: () => {
+        wx.showToast({ title: "已复制运单号", icon: "none" });
+      },
+    });
+  },
+});

+ 100 - 0
miniprogram/module/order/pages/logistics-detail/logistics-detail.wxml

@@ -0,0 +1,100 @@
+<!-- module/order/pages/logistics-detail/logistics-detail.wxml -->
+<t-navbar title="物流详情" left-arrow />
+<scroll-view class="page-scroll__container" type="list" scroll-y style="{{containerStyle}}">
+  <view class="logistics-page">
+    <view class="map-header">
+      <block wx:if="{{hasMapPosition}}">
+        <map class="map-header__map" latitude="{{latitude}}" longitude="{{longitude}}" scale="{{mapScale}}" markers="{{markers}}" polyline="{{polyline}}" include-points="{{includePoints}}" enable-scroll="{{true}}" enable-zoom="{{true}}" />
+      </block>
+      <block wx:else>
+        <view class="map-header__inner">
+          <text class="map-header__city">暂无物流轨迹地图</text>
+        </view>
+      </block>
+    </view>
+
+    <view class="logistics-card">
+      <view class="carrier-row">
+        <!-- <i class="iconfont {{carrierIcon || 'icon-zhongtong'}}" style="font-size:15px"></i> -->
+        <view class="carrier-row__info">
+          <text class="carrier-row__name">{{carrierName}}</text>
+          <text class="carrier-row__no">{{trackingNo}}</text>
+        </view>
+        <view class="carrier-row__actions">
+          <text class="carrier-row__action" bindtap="onCopyTracking">复制</text>
+          <!-- <view class="carrier-row__divider" />
+          <text class="carrier-row__action" bindtap="onCallCourier">打电话</text> -->
+        </view>
+      </view>
+
+      <view class="trace-list">
+        <view class="trace-list__axis" />
+
+        <view class="trace-item trace-item--current">
+          <view class="trace-item__dot trace-item__dot--active" />
+          <view class="trace-item__body">
+            <view class="trace-item__title-row">
+              <text class="trace-item__status trace-item__status--accent">{{latestTrace.status}}</text>
+              <text class="trace-item__time trace-item__time--accent">{{latestTrace.time}}</text>
+            </view>
+            <text class="trace-item__desc trace-item__desc--muted">{{latestTrace.desc}}</text>
+          </view>
+        </view>
+
+        <block wx:if="{{showAllTrace}}">
+          <view class="trace-item" wx:for="{{traceHistory}}" wx:key="timeKey">
+            <view class="trace-item__dot trace-item__dot--grey" />
+            <view class="trace-item__body">
+              <view class="trace-item__title-row">
+                <text class="trace-item__status">{{item.status}}</text>
+                <text class="trace-item__time">{{item.time}}</text>
+              </view>
+              <text class="trace-item__desc">{{item.desc}}</text>
+            </view>
+          </view>
+        </block>
+
+        <view class="trace-item trace-item--expand" bindtap="toggleTraceExpand">
+          <view class="trace-item__dot trace-item__dot--hollow" />
+          <view class="trace-item__body trace-item__body--inline">
+            <text class="trace-expand__text">{{showAllTrace ? '收起物流明细' : '查看更多物流明细'}}</text>
+            <t-icon name="{{showAllTrace ? 'chevron-up' : 'chevron-down'}}" size="36rpx" color="#999" />
+          </view>
+        </view>
+      </view>
+
+      <view class="logistics-divider" />
+
+      <view class="addr-block">
+        <t-icon name="location" size="30rpx" color="#333" class="addr-block__icon" />
+        <view class="addr-block__main">
+          <text class="addr-block__address">{{deliveryAddress}}</text>
+          <view class="addr-block__contact">
+            <text class="address-name">{{recipientName}}</text>
+            <text class="address-phone">{{recipientPhone}}</text>
+            <!-- <t-tag size="small" variant="outline" theme="default" class="addr-block__tag">号码保护中</t-tag>
+            <view class="addr-block__virtual" bindtap="onVirtualNumberTip">
+              <t-tag size="small" variant="outline" theme="warning" class="addr-block__tag addr-block__tag--orange">
+                取件出示虚拟号
+              </t-tag>
+            </view> -->
+          </view>
+        </view>
+      </view>
+    </view>
+
+    <view class="goods-section">
+      <view class="goods-item">
+        <t-image src="{{goosInfo.image}}" width="144rpx" height="144rpx" shape="round" mode="aspectFill" class="goods-item__img" />
+        <view class="goods-item__main">
+          <text class="goods-item__title">{{goosInfo.name}}</text>
+          <text class="goods-item__spec">{{goosInfo.description}}</text>
+        </view>
+        <view class="goods-item__price-col">
+          <text class="goods-item__price">¥{{goosInfo.price}}</text>
+          <text class="goods-item__qty">x{{goosInfo.quantity}}</text>
+        </view>
+      </view>
+    </view>
+  </view>
+</scroll-view>

+ 6 - 0
miniprogram/module/order/pages/negotiation-history/negotiation-history.json

@@ -0,0 +1,6 @@
+{
+  "navigationBarTitleText": "协商历史",
+  "usingComponents": {
+    "t-navbar": "tdesign-miniprogram/navbar/navbar"
+  }
+}

+ 150 - 0
miniprogram/module/order/pages/negotiation-history/negotiation-history.scss

@@ -0,0 +1,150 @@
+page {
+  background-color: #f7f8fa;
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+}
+
+.page-container {
+  flex: 1;
+}
+
+.history-list {
+  padding-bottom: 40rpx;
+}
+
+.history-item {
+  background-color: #fff;
+  padding: 32rpx;
+  position: relative;
+  margin-bottom: 20rpx;
+  
+  &:last-child {
+    margin-bottom: 0;
+  }
+}
+
+/* Header */
+.item-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 20rpx;
+}
+
+.avatar {
+  width: 72rpx;
+  height: 72rpx;
+  border-radius: 50%;
+  margin-right: 20rpx;
+  flex-shrink: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 22rpx;
+  font-weight: 500;
+  color: #fff;
+}
+
+.avatar--user {
+  background-color: #1d6ff6;
+}
+
+.avatar--platform {
+  background-color: #ff8c00;
+}
+
+.user-info {
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+}
+
+.user-name {
+  font-size: 30rpx;
+  color: #333;
+  font-weight: 500;
+  margin-bottom: 8rpx;
+}
+
+.action-time {
+  font-size: 24rpx;
+  color: #999;
+}
+
+/* Content */
+.item-content {
+  padding-left: 92rpx; /* Align with text, after avatar (72 + 20) */
+  margin-top: -10rpx;
+}
+
+/* Platform Styles */
+.platform-line {
+  font-size: 30rpx;
+  color: #333;
+  line-height: 1.6;
+  margin-bottom: 12rpx;
+  font-weight: 500;
+  
+  &:last-child {
+    margin-bottom: 0;
+  }
+}
+
+/* User Styles */
+.action-title {
+  font-size: 30rpx;
+  color: #333;
+  font-weight: 500;
+  margin-bottom: 16rpx;
+}
+
+.detail-line {
+  font-size: 28rpx;
+  line-height: 1.6;
+  margin-bottom: 6rpx;
+  display: flex;
+}
+
+.detail-label {
+  color: #333;
+  flex-shrink: 0;
+}
+
+.detail-value {
+  color: #333;
+  flex: 1;
+  word-break: break-all;
+}
+
+/* Image Grid */
+.image-grid {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 16rpx;
+  margin-top: 24rpx;
+}
+
+.grid-img-wrap {
+  width: 120rpx;
+  height: 120rpx;
+  border-radius: 8rpx;
+  overflow: hidden;
+  background-color: #f5f5f5;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.grid-img {
+  width: 100%;
+  height: 100%;
+}
+
+.grid-placeholder {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: #e8e8e8;
+}

+ 73 - 0
miniprogram/module/order/pages/negotiation-history/negotiation-history.ts

@@ -0,0 +1,73 @@
+import { getAfterSaleNegotiationHistoryMethod } from "../../request";
+
+Page({
+  data: {
+    historyList: [] as Array<any>,
+  },
+
+  onLoad(options: any) {
+    if (options.id) {
+      this.loadHistory(options.id);
+    }
+  },
+
+  async loadHistory(id: string) {
+    wx.showLoading({ title: '加载中' });
+    try {
+      const res = await getAfterSaleNegotiationHistoryMethod(Number(id));
+      const list = (res as any)?.data || [];
+      const historyList = list.map((item: any) => {
+        if (item.progress === '0') {
+          // 发起售后 - 用户操作
+          const content = item.aftersaleContent || {};
+          const details: Array<{ label: string; value: string }> = [];
+          if (content.receiptStatusStr) details.push({ label: '商品状态', value: content.receiptStatusStr });
+          const receiptMap: Record<string, string> = { '0': '待发货', '1': '已发货', '2': '已收货' };
+          if (content.receiptStatus !== undefined && content.receiptStatus !== null) {
+            details.push({ label: '收货状态', value: receiptMap[String(content.receiptStatus)] || '未知' });
+          }
+          if (content.applyAmount) details.push({ label: '退款金额', value: `¥${content.applyAmount}` });
+          if (content.reason) details.push({ label: '退款原因', value: content.reason });
+          if (content.remark) details.push({ label: '退款说明', value: content.remark });
+          return {
+            type: 'user',
+            avatar: '',
+            name: item.createBy || '用户',
+            time: item.createTime || '',
+            actionTitle: '发起了退款申请',
+            details,
+            images: content.voucherImgs || [],
+          };
+        } else {
+          // 平台操作
+          const lines: string[] = [];
+          if (item.title) lines.push(`${item.title}`);
+          if (item.content) lines.push(`${item.content}`);
+          return {
+            type: 'platform',
+            avatar: '',
+            name: '平台',
+            time: item.createTime || '',
+            lines,
+          };
+        }
+      });
+      this.setData({ historyList });
+    } catch (error: any) {
+      wx.showToast({ title: error.errMsg || '获取协商历史失败', icon: 'none' });
+    }
+    wx.hideLoading();
+  },
+
+  previewImage(e: any) {
+    const currentUrl = e.currentTarget.dataset.url;
+    const images = e.currentTarget.dataset.images;
+    wx.previewImage({
+      current: currentUrl,
+      urls: images,
+      fail: (err) => {
+        console.error('预览图片失败', err);
+      },
+    });
+  }
+});

+ 46 - 0
miniprogram/module/order/pages/negotiation-history/negotiation-history.wxml

@@ -0,0 +1,46 @@
+<t-navbar title="协商历史" left-arrow />
+
+<scroll-view class="page-container" scroll-y>
+  <view class="history-list">
+    <view class="history-item" wx:for="{{historyList}}" wx:key="index">
+      
+      <!-- 顶部信息 -->
+      <view class="item-header">
+        <view class="avatar {{item.type === 'platform' ? 'avatar--platform' : 'avatar--user'}}">
+          <text wx:if="{{item.type === 'platform'}}">平台</text>
+          <text wx:else>{{item.name[0] || '用'}}</text>
+        </view>
+        <view class="user-info">
+          <view class="user-name">{{item.name}}</view>
+          <view class="action-time">{{item.time}}</view>
+        </view>
+      </view>
+
+      <!-- 内容区 -->
+      <view class="item-content">
+        <!-- 平台消息 -->
+        <block wx:if="{{item.type === 'platform'}}">
+          <view class="platform-line" wx:for="{{item.lines}}" wx:for-item="line" wx:key="*this">{{line}}</view>
+        </block>
+
+        <!-- 用户行为 -->
+        <block wx:elif="{{item.type === 'user'}}">
+          <view class="action-title">{{item.actionTitle}}</view>
+          
+          <view class="detail-line" wx:for="{{item.details}}" wx:for-item="detail" wx:key="label">
+            <text class="detail-label">{{detail.label}}:</text>
+            <text class="detail-value">{{detail.value}}</text>
+          </view>
+          
+          <!-- 图片矩阵 -->
+          <view class="image-grid" wx:if="{{item.images && item.images.length > 0}}">
+            <view class="grid-img-wrap" wx:for="{{item.images}}" wx:for-item="img" wx:key="index" bind:tap="previewImage" data-url="{{img}}" data-outer-index="{{index}}" data-images="{{item.images}}">
+              <image class="grid-img" src="{{img}}" mode="aspectFill"></image>
+            </view>
+          </view>
+        </block>
+      </view>
+
+    </view>
+  </view>
+</scroll-view>

+ 8 - 0
miniprogram/module/order/pages/offline-evaluate/offline-evaluate.json

@@ -0,0 +1,8 @@
+{
+  "renderer": "skyline",
+  "usingComponents": {
+    "t-navbar": "tdesign-miniprogram/navbar/navbar",
+    "t-icon": "tdesign-miniprogram/icon/icon",
+    "t-rate": "tdesign-miniprogram/rate/rate"
+  }
+}

+ 238 - 0
miniprogram/module/order/pages/offline-evaluate/offline-evaluate.scss

@@ -0,0 +1,238 @@
+@import "../../../../themes/page.scss";
+
+.page-scroll__container {
+  flex: 0 1 auto;
+  height: var(--page-container-safeHeight, 100vh);
+  background: #f7f8fa;
+  padding-bottom: 140rpx;
+}
+
+.evaluate-container {
+  background: #fff;
+  margin: 24rpx;
+  border-radius: 16rpx;
+  padding: 32rpx;
+}
+
+.service-card {
+  position: relative;
+  display: flex;
+  align-items: flex-start;
+  padding-bottom: 32rpx;
+  margin-bottom: 24rpx;
+  border-bottom: 1rpx solid #eee;
+}
+
+.service-img {
+  width: 120rpx;
+  height: 120rpx;
+  border-radius: 12rpx;
+  background: #f5f5f5;
+  flex-shrink: 0;
+}
+
+.service-info {
+  flex: 1;
+  margin-left: 24rpx;
+  min-width: 0;
+  display: flex;
+  flex-direction: column;
+  gap: 12rpx;
+}
+
+.service-name-row {
+  display: flex;
+  align-items: center;
+  flex-wrap: wrap;
+}
+
+.service-name {
+  font-size: 30rpx;
+  color: #333;
+  font-weight: 500;
+}
+
+.service-divider {
+  font-size: 28rpx;
+  color: #dcdcdc;
+  margin: 0 10rpx;
+}
+
+.service-date,
+.service-time {
+  font-size: 26rpx;
+  color: #666;
+}
+
+.service-row {
+  font-size: 26rpx;
+  color: #666;
+}
+
+.service-close {
+  position: absolute;
+  top: 0;
+  right: 0;
+  padding: 16rpx;
+  z-index: 1;
+}
+
+.rate-row {
+  display: flex;
+  align-items: center;
+  // justify-content: space-between;
+  margin-bottom: 28rpx;
+}
+
+.rate-label {
+  font-size: 30rpx;
+  color: #333;
+  min-width: 140rpx;
+  margin-right: 10px;
+}
+
+.rate-wrap {
+  display: flex;
+  align-items: center;
+  gap: 16rpx;
+}
+
+.rate-score {
+  font-size: 28rpx;
+  color: #333;
+  min-width: 56rpx;
+  margin-left: 20px;
+}
+
+.comment-section {
+  position: relative;
+  margin-bottom: 32rpx;
+}
+
+.comment-input {
+  width: 100%;
+  min-height: 200rpx;
+  padding: 24rpx;
+  font-size: 28rpx;
+  color: #333;
+  background: #f7f8fa;
+  border-radius: 12rpx;
+  box-sizing: border-box;
+}
+
+.comment-placeholder {
+  color: #999;
+}
+
+.comment-count {
+  position: absolute;
+  top: 24rpx;
+  right: 24rpx;
+  font-size: 24rpx;
+  color: #999;
+}
+
+.upload-section {
+  margin-bottom: 24rpx;
+}
+
+/* 一行 4 个,文件之间 24rpx 缝隙,与 goods-evaluate 一致 */
+.media-grid {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 24rpx;
+}
+
+.media-item {
+  position: relative;
+  width: 142rpx;
+  height: 142rpx;
+  flex-shrink: 0;
+  border-radius: 12rpx;
+  overflow: hidden;
+  background: #f5f5f5;
+  margin-right: 5px;
+  margin-bottom: 10px;
+}
+
+.upload-trigger {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  width: 142rpx;
+  height: 142rpx;
+  flex-shrink: 0;
+  background: #f7f8fa;
+  border-radius: 12rpx;
+  border: 2rpx dashed #ddd;
+}
+
+.upload-label {
+  font-size: 24rpx;
+  color: #999;
+  margin-top: 12rpx;
+}
+
+.media-thumb {
+  width: 100%;
+  height: 100%;
+  display: block;
+}
+
+.media-item-video {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  .media-thumb {
+    display: block;
+  }
+}
+
+.media-item-play {
+  position: absolute;
+  left: 50%;
+  top: 50%;
+  transform: translate(-50%, -50%);
+  pointer-events: none;
+}
+
+.media-delete {
+  position: absolute;
+  top: 0;
+  right: 0;
+  width: 48rpx;
+  height: 48rpx;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border-radius: 0 12rpx 0 12rpx;
+}
+
+.publish-footer {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  background: #fff;
+  padding: 20rpx 32rpx;
+  box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.06);
+  z-index: 100;
+}
+
+.publish-btn {
+  height: 88rpx;
+  line-height: 88rpx;
+  text-align: center;
+  font-size: 32rpx;
+  font-weight: 500;
+  color: #fff;
+  background: #1976d2;
+  border-radius: 44rpx;
+}
+
+.publish-btn.disabled {
+  background: #e8e8e8;
+  color: #999;
+}

+ 218 - 0
miniprogram/module/order/pages/offline-evaluate/offline-evaluate.ts

@@ -0,0 +1,218 @@
+import PageContainerBehavior from "../../../../core/behavior/page-container.behavior";
+import { evaluateOrderGoodsMethod } from "../../request";
+import { upload } from "../../../../lib/request/upload";
+import { OfflineEvaluateModel } from "../../model/evaluate.model";
+function withMediaType(url: string, type: "image" | "video") {
+  if (!url) return url;
+  const hasQuery = url.includes("?");
+  const hasHash = url.includes("#");
+  if (hasHash) {
+    const [base, hash] = url.split("#");
+    return `${base}${hasQuery ? "&" : "?"}type=${type}#${hash ?? ""}`;
+  }
+  return `${url}${hasQuery ? "&" : "?"}type=${type}`;
+}
+Page({
+  behaviors: [PageContainerBehavior],
+  data: {
+    orderId: "",
+    service: {} as OfflineEvaluateModel,
+    scoreServiceQuality: 0,
+    scoreAttitude: 0,
+    scoreEnvironment: 0,
+    rateColor: "#F7BA2A",
+    content: "",
+    mediaList: [] as { path: string; type: "image" | "video" }[],
+    canPublish: false,
+    videoFullscreen: false,
+  },
+
+  onLoad(options: Record<string, string>) {
+    let service = {} as OfflineEvaluateModel;
+    if (options.goodsInfo) {
+      const goods = JSON.parse(decodeURIComponent(options.goodsInfo));
+      service = {
+        lineId: goods.id || 0,
+        patientConditioningRecordId: goods.patientConditioningRecordId || 0,
+        patientConditioningProgramId: goods.patientConditioningProgramId || 0,
+        operateTime: goods.operateTime || "",
+        operateBy: goods.operateBy || "",
+        conditioningProgramSupplierName: goods.conditioningProgramSupplierName || "",
+        image: goods.conditioningProgramPhoto || "",
+        name: goods.conditioningProgramName || "",
+      };
+      console.log(service, "service===");
+    }
+    this.setData({
+      service,
+      canPublish: this._checkCanPublish(0, 0, 0),
+    });
+  },
+
+  _checkCanPublish(
+    quality: number,
+    attitude: number,
+    environment: number
+  ): boolean {
+    return quality > 0 && attitude > 0 && environment > 0;
+  },
+  _updateCanPublish() {
+    const { scoreServiceQuality, scoreAttitude, scoreEnvironment } = this.data;
+    this.setData({
+      canPublish: this._checkCanPublish(
+        scoreServiceQuality,
+        scoreAttitude,
+        scoreEnvironment
+      ),
+    });
+  },
+  onScoreServiceQuality(e: WechatMiniprogram.CustomEvent<{ value: number }>) {
+    const scoreServiceQuality = e.detail?.value ?? 0;
+    this.setData({ scoreServiceQuality }, () => this._updateCanPublish());
+  },
+  onScoreAttitude(e: WechatMiniprogram.CustomEvent<{ value: number }>) {
+    const scoreAttitude = e.detail?.value ?? 0;
+    this.setData({ scoreAttitude }, () => this._updateCanPublish());
+  },
+  onScoreEnvironment(e: WechatMiniprogram.CustomEvent<{ value: number }>) {
+    const scoreEnvironment = e.detail?.value ?? 0;
+    this.setData({ scoreEnvironment }, () => this._updateCanPublish());
+  },
+  onContentInput(e: WechatMiniprogram.Input) {
+    const content = e.detail?.value ?? "";
+    this.setData({ content });
+  },
+  onChooseMedia() {
+    const current = this.data.mediaList.length;
+    if (current >= 9) {
+      wx.showToast({ title: "图片和视频总数不能超过9个", icon: "none" });
+      return;
+    }
+    const remain = 9 - current;
+    wx.chooseMedia({
+      count: remain,
+      mediaType: ["image", "video"],
+      sourceType: ["album", "camera"],
+      maxDuration: 30,
+      camera: "back",
+      success: (res) => {
+        const list = res.tempFiles.map((f) => ({
+          path: f.tempFilePath,
+          type: (f.fileType === "video" ? "video" : "image") as "image" | "video",
+        }));
+        const oldLength = this.data.mediaList.length;
+        const next = [...this.data.mediaList, ...list].slice(0, 9);
+        this.setData({ mediaList: next });
+
+        // 选取本地媒体后,调用上传接口上传到服务器
+        list.forEach((item, idx) => {
+          const index = oldLength + idx;
+          upload<string, any>({
+            params: { name: "file", file: item.path },
+            transform({ data }: any): string {
+              return (data as any)?.url || (data as any);
+            },
+          }).then(
+            (url) => {
+              const key = `mediaList[${index}].path`;
+              this.setData({ [key]: url });
+            },
+            (error: any) => {
+              wx.showToast({
+                title: error?.errMsg || "上传失败",
+                icon: "none",
+              });
+              const mediaList = this.data.mediaList.filter((_, i) => i !== index);
+              this.setData({ mediaList });
+            }
+          );
+        });
+      },
+    });
+  },
+  onRemoveMedia(e: WechatMiniprogram.TouchEvent) {
+    const index = e.currentTarget.dataset.index as number;
+    const mediaList = this.data.mediaList.filter((_, i) => i !== index);
+    this.setData({ mediaList });
+  },
+  onPreviewImage(e: WechatMiniprogram.TouchEvent) {
+    const url = e.currentTarget.dataset.url as string;
+    const urls = this.data.mediaList.filter((m) => m.type === "image").map((m) => m.path);
+    if (url && urls.length) {
+      wx.previewImage({ current: url, urls });
+    }
+  },
+  onPreviewVideo(e: WechatMiniprogram.TouchEvent) {
+    const index = e.currentTarget.dataset.index as number;
+    const videoContext = wx.createVideoContext("offline-video-" + index, this);
+    videoContext.requestFullScreen({});
+  },
+  /** 点击图片/视频:原生全屏预览(类似 test 页) */
+  onPreviewMedia(e: WechatMiniprogram.TouchEvent) {
+    const index = e.currentTarget.dataset.index as number;
+    const { mediaList } = this.data;
+    if (index < 0 || index >= mediaList.length) return;
+
+    const sources = mediaList.map((m) => ({
+      url: m.path,
+      type: m.type as "image" | "video",
+    }));
+
+    const previewMedia = (wx as any).previewMedia as
+      | ((option: { sources: { url: string; type: "image" | "video" }[]; current: number }) => void)
+      | undefined;
+
+    if (typeof previewMedia === "function") {
+      previewMedia({ sources, current: index });
+      return;
+    }
+
+    // 低版本兜底:图片用 previewImage,视频用 requestFullScreen
+    const current = mediaList[index];
+    if (current.type === "image") {
+      const urls = mediaList.filter((m) => m.type === "image").map((m) => m.path);
+      wx.previewImage({ current: current.path, urls });
+      return;
+    }
+    const ctx = wx.createVideoContext("offline-video-" + index, this);
+    ctx.requestFullScreen({});
+  },
+  onVideoFullscreenChange(e: WechatMiniprogram.VideoFullScreenChange) {
+    const fullScreen = !!(e.detail && e.detail.fullScreen);
+    this.setData({ videoFullscreen: fullScreen });
+  },
+  onRemoveService() {
+    this.setData({
+      service: {} as OfflineEvaluateModel,
+    });
+  },
+  async onPublish() {
+    if (!this.data.canPublish) {
+      wx.showToast({ title: "请完成三项评分", icon: "none" });
+      return;
+    }
+    try {
+      const data = {
+        lineId: this.data.service?.lineId,
+        patientConditioningRecordId: this.data.service?.patientConditioningRecordId,
+        patientConditioningProgramId: this.data.service?.patientConditioningProgramId,
+        complianceScore: this.data.scoreServiceQuality,
+        qualityScore: this.data.scoreServiceQuality,
+        attitudeScore: this.data.scoreAttitude,
+        environmentScore: this.data.scoreEnvironment,
+        depict: this.data.content,
+        imageVideos: this.data.mediaList.map((m) => withMediaType(m.path, m.type)),
+      };
+      wx.showLoading({ title: "发布中..." });
+      await evaluateOrderGoodsMethod(data);
+      wx.hideLoading();
+      wx.showToast({ title: "发布成功", icon: "success" });
+      wx.redirectTo({
+        url: `/module/care/pages/care/verifyRecord?id=${this.data.service?.patientConditioningProgramId}`,
+      });
+    } catch (error: any) {
+      wx.hideLoading();
+      wx.showToast({ title: error.errMsg || "发布失败", icon: "none" });
+    }
+  },
+});

+ 79 - 0
miniprogram/module/order/pages/offline-evaluate/offline-evaluate.wxml

@@ -0,0 +1,79 @@
+<!--module/order/pages/offline-evaluate/offline-evaluate.wxml 线下服务评价-->
+<t-navbar wx:if="{{!videoFullscreen}}" title="评价" left-arrow />
+<scroll-view class="page-scroll__container" type="list" scroll-y style="{{containerStyle}}">
+  <view class="evaluate-container">
+    <!-- 服务信息卡片 -->
+    <view class="service-card">
+      <image class="service-img" src="{{service.image}}" mode="aspectFill" />
+      <view class="service-info">
+        <view class="service-name-row">
+          <text class="service-name">{{service.name}}</text>
+          <text class="service-divider">|</text>
+          <text class="service-date">{{service.operateTime}}</text>
+        </view>
+        <view class="service-row" wx:if="{{service.operateBy}}">操作人: {{service.operateBy}}</view>
+        <view class="service-row" wx:if="{{service.conditioningProgramSupplierName}}">机构: {{service.conditioningProgramSupplierName}}</view>
+      </view>
+    </view>
+
+    <!-- 服务质量 评分 -->
+    <view class="rate-row">
+      <text class="rate-label">服务质量</text>
+      <view class="rate-wrap">
+        <t-rate value="{{scoreServiceQuality}}" count="{{5}}" color="{{rateColor}}" placement="" bind:change="onScoreServiceQuality" />
+        <text class="rate-score">{{scoreServiceQuality || '0'}}分</text>
+      </view>
+    </view>
+
+    <!-- 服务态度 评分 -->
+    <view class="rate-row">
+      <text class="rate-label">服务态度</text>
+      <view class="rate-wrap">
+        <t-rate value="{{scoreAttitude}}" count="{{5}}" color="{{rateColor}}" placement="" bind:change="onScoreAttitude" />
+        <text class="rate-score">{{scoreAttitude || '0'}}分</text>
+      </view>
+    </view>
+
+    <!-- 环境 评分 -->
+    <view class="rate-row">
+      <text class="rate-label">环 境</text>
+      <view class="rate-wrap">
+        <t-rate value="{{scoreEnvironment}}" count="{{5}}" color="{{rateColor}}" placement="" bind:change="onScoreEnvironment" />
+        <text class="rate-score">{{scoreEnvironment || '0'}}分</text>
+      </view>
+    </view>
+
+    <!-- 评价输入 -->
+    <view class="comment-section">
+      <textarea class="comment-input" placeholder="展开说说对商品的想法吧" placeholder-class="comment-placeholder" value="{{content}}" bindinput="onContentInput" maxlength="{{200}}" show-confirm-bar="{{false}}" />
+      <view class="comment-count" wx:if="{{content.length > 0}}">{{content.length}}/200</view>
+    </view>
+
+    <!-- 图/视频 上传(图片+视频总数不超过9个,添加按钮在最后,一行4个) -->
+    <view class="upload-section">
+      <view class="media-grid">
+        <view class="media-item" wx:for="{{mediaList}}" wx:key="path">
+          <image wx:if="{{item.type === 'image'}}" class="media-thumb" src="{{item.path}}" mode="aspectFill" bindtap="onPreviewMedia" data-index="{{index}}" />
+          <view wx:else class="media-item-video" bindtap="onPreviewMedia" data-index="{{index}}">
+            <video id="offline-video-{{index}}" class="media-thumb" src="{{item.path}}" show-center-play-btn="{{true}}" object-fit="cover" controls="{{true}}" bindfullscreenchange="onVideoFullscreenChange" />
+            <view class="media-item-play">
+              <t-icon name="play-circle-filled" size="56rpx" color="rgba(255,255,255,0.95)" />
+            </view>
+          </view>
+          <view class="media-delete" catchtap="onRemoveMedia" data-index="{{index}}">
+            <t-icon name="close" size="32rpx" color="#fff" />
+          </view>
+        </view>
+        <view class="upload-trigger" wx:if="{{mediaList.length < 9}}" bindtap="onChooseMedia">
+          <t-icon name="add" size="48rpx" color="#999" />
+          <text class="upload-label">图/视频</text>
+        </view>
+      </view>
+    </view>
+  </view>
+</scroll-view>
+
+<!-- 发布按钮(视频全屏时隐藏,避免遮挡进度条) -->
+<view class="publish-footer" wx:if="{{!videoFullscreen}}" style="padding-bottom: {{container.safeBottomOffset}}px;">
+  <view class="publish-btn {{canPublish ? '' : 'disabled'}}" bindtap="onPublish">发布</view>
+</view>

+ 8 - 0
miniprogram/module/order/pages/offline-evaluateDetail/offline-evaluateDetail.json

@@ -0,0 +1,8 @@
+{
+  "renderer": "skyline",
+  "usingComponents": {
+    "t-navbar": "tdesign-miniprogram/navbar/navbar",
+    "t-icon": "tdesign-miniprogram/icon/icon",
+    "t-rate": "tdesign-miniprogram/rate/rate"
+  }
+}

+ 412 - 0
miniprogram/module/order/pages/offline-evaluateDetail/offline-evaluateDetail.scss

@@ -0,0 +1,412 @@
+@import "../../../../themes/page.scss";
+
+.page-scroll__container {
+  flex: 0 1 auto;
+  height: var(--page-container-safeHeight, 100vh);
+  background: #f7f8fa;
+  padding-bottom: 140rpx;
+}
+
+.evaluate-container {
+  background: #fff;
+  margin: 24rpx;
+  border-radius: 16rpx;
+  padding: 32rpx;
+}
+
+.evaluate-container--readonly {
+  user-select: none;
+  -webkit-user-select: none;
+}
+.service-card {
+  position: relative;
+  display: flex;
+  align-items: flex-start;
+  padding-bottom: 32rpx;
+  margin-bottom: 24rpx;
+  border-bottom: 1rpx solid #eee;
+}
+
+.service-img {
+  width: 120rpx;
+  height: 120rpx;
+  border-radius: 12rpx;
+  background: #f5f5f5;
+  flex-shrink: 0;
+}
+
+.service-info {
+  flex: 1;
+  margin-left: 24rpx;
+  min-width: 0;
+  display: flex;
+  flex-direction: column;
+  gap: 12rpx;
+}
+
+.service-name-row {
+  display: flex;
+  align-items: center;
+  flex-wrap: wrap;
+  margin-bottom: 0px;
+}
+
+.service-name {
+  font-size: 30rpx;
+  color: #333;
+  font-weight: 500;
+}
+
+.service-divider {
+  font-size: 28rpx;
+  color: #dcdcdc;
+  margin: 0 10rpx;
+}
+
+.service-date,
+.service-time {
+  font-size: 26rpx;
+  color: #666;
+  margin-right: 10px;
+}
+
+.service-row {
+  font-size: 26rpx;
+  color: #666;
+  margin-bottom: 0px;
+}
+
+.product-card {
+  position: relative;
+  display: flex;
+  align-items: center;
+  padding-bottom: 32rpx;
+  margin-bottom: 24rpx;
+  border-bottom: 1rpx solid #eee;
+}
+
+.product-img {
+  width: 120rpx;
+  height: 120rpx;
+  border-radius: 12rpx;
+  background: #f5f5f5;
+  flex-shrink: 0;
+}
+
+.product-info {
+  flex: 1;
+  margin-left: 24rpx;
+  min-width: 0;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+}
+
+.product-name-row {
+  display: flex;
+  align-items: center;
+  flex-wrap: wrap;
+  gap: 8rpx;
+}
+
+.product-name {
+  font-size: 30rpx;
+  color: #333;
+  font-weight: 500;
+}
+
+.product-divider {
+  font-size: 28rpx;
+  color: #dcdcdc;
+  margin: 0 30rpx;
+}
+
+.product-spec {
+  font-size: 26rpx;
+  color: #999;
+}
+
+.product-close {
+  position: absolute;
+  top: 0;
+  right: 0;
+  padding: 16rpx;
+  z-index: 1;
+}
+
+.rate-row {
+  display: flex;
+  align-items: center;
+  // justify-content: space-between;
+  margin-bottom: 32rpx;
+}
+
+.rate-label {
+  font-size: 30rpx;
+  color: #333;
+  margin-right: 20px;
+  min-width: 60px;
+}
+
+.rate-wrap {
+  display: flex;
+  align-items: center;
+  gap: 16rpx;
+}
+
+.rate-score {
+  font-size: 28rpx;
+  color: #333;
+  min-width: 56rpx;
+  margin-left: 20px;
+}
+
+.comment-section {
+  position: relative;
+  margin-bottom: 32rpx;
+}
+
+.comment-display {
+  width: 100%;
+  min-height: 120rpx;
+  padding: 24rpx;
+  font-size: 28rpx;
+  color: #333;
+  background: #f7f8fa;
+  border-radius: 12rpx;
+  box-sizing: border-box;
+  line-height: 1.5;
+  white-space: pre-wrap;
+  word-break: break-all;
+}
+
+.comment-input {
+  width: 100%;
+  min-height: 200rpx;
+  padding: 24rpx;
+  font-size: 28rpx;
+  color: #333;
+  background: #f7f8fa;
+  border-radius: 12rpx;
+  box-sizing: border-box;
+}
+
+.comment-placeholder {
+  color: #999;
+}
+
+.comment-count {
+  position: absolute;
+  top: 24rpx;
+  right: 24rpx;
+  font-size: 24rpx;
+  color: #999;
+}
+
+.upload-section {
+  margin-bottom: 24rpx;
+}
+
+.upload-section .empty-media {
+  font-size: 26rpx;
+  color: #999;
+  margin-top: 8rpx;
+  display: block;
+}
+
+.section-label {
+  font-size: 28rpx;
+  color: #666;
+  margin-bottom: 16rpx;
+}
+
+.upload-row {
+  display: flex;
+  gap: 20rpx;
+  flex-wrap: wrap;
+}
+
+.upload-trigger {
+  display: inline-flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  width: 160rpx;
+  height: 160rpx;
+  border-radius: 12rpx;
+  border: 2rpx dashed #ddd;
+}
+
+.upload-trigger--image {
+  background: #f2f2f2;
+}
+
+.upload-trigger--video {
+  background: #4a4a4a;
+  border-color: #333;
+}
+
+.upload-label {
+  font-size: 26rpx;
+  color: #999;
+  margin-top: 12rpx;
+}
+
+.upload-label--light {
+  color: rgba(255, 255, 255, 0.9);
+}
+
+.upload-trigger .t-icon {
+  display: block;
+}
+
+/* 一行 4 个,与 goods-evaluateDetail 一致 */
+.media-grid {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 24rpx;
+}
+
+.media-list--readonly .media-item {
+  .media-delete {
+    display: none;
+  }
+}
+
+.media-item {
+  position: relative;
+  width: 140rpx;
+  height: 140rpx;
+  flex-shrink: 0;
+  border-radius: 12rpx;
+  overflow: hidden;
+  background: #f5f5f5;
+  margin-bottom: 10px;
+}
+
+.media-thumb {
+  width: 100%;
+  height: 100%;
+  display: block;
+}
+
+.media-item-video {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  .media-thumb {
+    display: block;
+  }
+}
+
+.media-item-play {
+  position: absolute;
+  left: 50%;
+  top: 50%;
+  transform: translate(-50%, -50%);
+  pointer-events: none;
+}
+
+.media-delete {
+  position: absolute;
+  top: 0;
+  right: 0;
+  width: 48rpx;
+  height: 48rpx;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border-radius: 0 12rpx 0 12rpx;
+}
+
+.publish-footer {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  background: #fff;
+  padding: 20rpx 32rpx;
+  box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.06);
+  z-index: 100;
+}
+
+.publish-btn {
+  height: 88rpx;
+  line-height: 88rpx;
+  text-align: center;
+  font-size: 32rpx;
+  font-weight: 500;
+  color: #fff;
+  background: #1976d2;
+  border-radius: 44rpx;
+}
+
+.publish-btn.disabled {
+  background: #e8e8e8;
+  color: #999;
+}
+
+/* 全屏轮播层:图片+视频一起,轮播到视频自动播放 */
+.media-carousel-overlay {
+  position: fixed;
+  left: 0;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 1000;
+  background: #000;
+}
+
+.media-carousel-swiper {
+  width: 100%;
+  height: 100%;
+}
+
+.media-carousel-item {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.media-carousel-image-wrap {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.media-carousel-image {
+  width: 100%;
+  height: 100%;
+  display: block;
+  object-position: center;
+}
+
+.media-carousel-video-wrap {
+  width: 100%;
+  height: 100%;
+}
+
+.media-carousel-video {
+  width: 100%;
+  height: 100%;
+  display: block;
+}
+
+.media-carousel-close {
+  position: absolute;
+  top: calc(var(--status-bar-height, 0px) + 24rpx);
+  right: 24rpx;
+  width: 80rpx;
+  height: 80rpx;
+  border-radius: 50%;
+  background: rgba(0, 0, 0, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 1001;
+}

+ 125 - 0
miniprogram/module/order/pages/offline-evaluateDetail/offline-evaluateDetail.ts

@@ -0,0 +1,125 @@
+import PageContainerBehavior from "../../../../core/behavior/page-container.behavior";
+import { OfflineEvaluateModel } from "../../model/evaluate.model";
+import { getOrderGoodsEvaluationMethod } from "../../request";
+function parseMediaType(url: string): "image" | "video" {
+  if (!url) return "image";
+  const match = /[?&]type=(image|video)\b/i.exec(url);
+  if (match) return match[1].toLowerCase() as "image" | "video";
+  const lower = url.toLowerCase();
+  if (/\.(mp4|mov|avi|rmvb|flv|mkv|webm)$/.test(lower)) return "video";
+  return "image";
+}
+Page({
+  behaviors: [PageContainerBehavior],
+  data: {
+    orderId: "",
+    service: {} as OfflineEvaluateModel,
+    scoreServiceQuality: 0,
+    scoreAttitude: 0,
+    scoreEnvironment: 0,
+    rateColor: "#F7BA2A",
+    content: "",
+    mediaList: [] as { path: string; type: "image" | "video" }[],
+    videoFullscreen: false,
+  },
+  async onLoad(options: Record<string, string>) {
+    let service = {} as OfflineEvaluateModel;
+    if (options.goodsInfo) {
+      const goods = JSON.parse(decodeURIComponent(options.goodsInfo));
+      service = {
+        lineId: goods.id || 0,
+        patientConditioningRecordId: goods.patientConditioningRecordId || 0,
+        patientConditioningProgramId: goods.patientConditioningProgramId || 0,
+        operateTime: goods.operateTime || "",
+        operateBy: goods.operateBy || "",
+        conditioningProgramSupplierName: goods.conditioningProgramSupplierName || "",
+        image: goods.conditioningProgramPhoto || "",
+        name: goods.conditioningProgramName
+      };
+    }
+    this.setData({
+      service,
+    });
+    if (service.patientConditioningRecordId) {
+      try {
+        const res = await getOrderGoodsEvaluationMethod("2", service.lineId);
+        const mediaList: { path: string; type: "image" | "video" }[] = [];
+        if (res && res.data) {
+          if (Array.isArray(res.data.imageVideos)) {
+            res.data.imageVideos.forEach((url: string) => {
+              if (!url) return;
+              mediaList.push({
+                path: url,
+                type: parseMediaType(url),
+              });
+            });
+          }
+          this.setData({
+            evaluateInfo: res.data,
+            scoreServiceQuality: Number(res.data.complianceScore ?? 0),
+            scoreAttitude: Number(res.data.attitudeScore ?? 0),
+            scoreEnvironment: Number(res.data.environmentScore ?? 0),
+            content: res.data.depict || "",
+            mediaList,
+          });
+        }
+      } catch (error: any) {
+        wx.showToast({
+          title: error?.errMsg || "获取评价详情失败",
+          icon: "none",
+        });
+      }
+    }
+  },
+  onBack() {
+    wx.navigateBack();
+  },
+  onPreviewImage(e: WechatMiniprogram.TouchEvent) {
+    const url = e.currentTarget.dataset.url as string;
+    const urls = this.data.mediaList
+      .filter((m) => m.type === "image")
+      .map((m) => m.path);
+    if (url && urls.length) {
+      wx.previewImage({ current: url, urls });
+    }
+  },
+  onPreviewVideo(e: WechatMiniprogram.TouchEvent) {
+    const index = e.currentTarget.dataset.index as number;
+    const ctx = wx.createVideoContext("offline-detail-video-" + index, this);
+    ctx.requestFullScreen({});
+  },
+  /** 点击图片/视频:原生全屏预览(类似 test 页) */
+  onPreviewMedia(e: WechatMiniprogram.TouchEvent) {
+    const index = e.currentTarget.dataset.index as number;
+    const { mediaList } = this.data;
+    if (index < 0 || index >= mediaList.length) return;
+
+    const sources = mediaList.map((m) => ({
+      url: m.path,
+      type: m.type as "image" | "video",
+    }));
+
+    const previewMedia = (wx as any).previewMedia as
+      | ((option: { sources: { url: string; type: "image" | "video" }[]; current: number }) => void)
+      | undefined;
+
+    if (typeof previewMedia === "function") {
+      previewMedia({ sources, current: index });
+      return;
+    }
+
+    // 低版本兜底:图片用 previewImage,视频用 requestFullScreen
+    const current = mediaList[index];
+    if (current.type === "image") {
+      const urls = mediaList.filter((m) => m.type === "image").map((m) => m.path);
+      wx.previewImage({ current: current.path, urls });
+      return;
+    }
+    const ctx = wx.createVideoContext("offline-detail-video-" + index, this);
+    ctx.requestFullScreen({});
+  },
+  onVideoFullscreenChange(e: WechatMiniprogram.VideoFullScreenChange) {
+    const fullScreen = !!(e.detail && e.detail.fullScreen);
+    this.setData({ videoFullscreen: fullScreen });
+  },
+});

+ 71 - 0
miniprogram/module/order/pages/offline-evaluateDetail/offline-evaluateDetail.wxml

@@ -0,0 +1,71 @@
+<!-- 线下评价详情:整页只读,不允许用户修改,仅支持查看与返回 -->
+<t-navbar wx:if="{{!videoFullscreen}}" title="评价" left-arrow />
+<scroll-view class="page-scroll__container" type="list" scroll-y style="{{containerStyle}}">
+  <view class="evaluate-container evaluate-container--readonly">
+    <!-- 商品信息 -->
+    <view class="service-card">
+      <image class="service-img" src="{{service.image}}" mode="aspectFill" />
+      <view class="service-info">
+        <view class="service-name-row">
+          <text class="service-name">{{service.name}}</text>
+          <text class="service-divider">|</text>
+          <text class="service-date">{{service.operateTime}}</text>
+        </view>
+        <view class="service-row" wx:if="{{service.operateBy}}">操作人: {{service.operateBy}}</view>
+        <view class="service-row" wx:if="{{service.conditioningProgramSupplierName}}">机构: {{service.conditioningProgramSupplierName}}</view>
+      </view>
+    </view>
+
+    <!-- 服务质量 评分-->
+    <view class="rate-row">
+      <text class="rate-label">服务质量</text>
+      <view class="rate-wrap">
+        <t-rate value="{{scoreServiceQuality}}" count="{{5}}" color="{{rateColor}}" placement="" disabled="{{true}}" />
+        <text class="rate-score">{{scoreServiceQuality || '0'}}分</text>
+      </view>
+    </view>
+
+    <!-- 服务态度 评分 -->
+    <view class="rate-row">
+      <text class="rate-label">服务态度</text>
+      <view class="rate-wrap">
+        <t-rate value="{{scoreAttitude}}" count="{{5}}" color="{{rateColor}}" placement="" disabled="{{true}}" />
+        <text class="rate-score">{{scoreAttitude || '0'}}分</text>
+      </view>
+    </view>
+
+    <!-- 环境 评分 -->
+    <view class="rate-row">
+      <text class="rate-label">环 境</text>
+      <view class="rate-wrap">
+        <t-rate value="{{scoreEnvironment}}" count="{{5}}" color="{{rateColor}}" placement="" disabled="{{true}}" />
+        <text class="rate-score">{{scoreEnvironment || '0'}}分</text>
+      </view>
+    </view>
+
+    <!-- 评价内容:只读展示 -->
+    <view class="comment-section">
+      <view class="comment-display">{{content || '暂无评价内容'}}</view>
+    </view>
+
+    <!-- 图片/视频:一行4个,可点击预览,视频全屏时隐藏导航和底部 -->
+    <view class="upload-section">
+      <view class="media-grid media-list--readonly" wx:if="{{mediaList.length > 0}}">
+        <view class="media-item" wx:for="{{mediaList}}" wx:key="path">
+          <image wx:if="{{item.type === 'image'}}" class="media-thumb" src="{{item.path}}" mode="aspectFill" bindtap="onPreviewMedia" data-index="{{index}}" />
+          <view wx:else class="media-item-video" bindtap="onPreviewMedia" data-index="{{index}}">
+            <video id="offline-detail-video-{{index}}" class="media-thumb" src="{{item.path}}" show-center-play-btn="{{true}}" object-fit="cover" controls="{{true}}" bindfullscreenchange="onVideoFullscreenChange" />
+            <view class="media-item-play">
+              <t-icon name="play-circle-filled" size="56rpx" color="rgba(255,255,255,0.95)" />
+            </view>
+          </view>
+        </view>
+      </view>
+    </view>
+  </view>
+</scroll-view>
+
+<!-- 底部返回(视频全屏时隐藏,避免遮挡) -->
+<view class="publish-footer" wx:if="{{!videoFullscreen}}" style="padding-bottom: {{container.safeBottomOffset}}px;">
+  <view class="publish-btn" bindtap="onBack">返回</view>
+</view>

+ 1 - 1
miniprogram/module/order/pages/order-detail/order-detail.wxml

@@ -1,4 +1,4 @@
-<!--module/order/pages/select-goods/select-goods.wxml-->
+<!--module/order/pages/order-detail/order-detail.wxml-->
 <t-navbar title="{{title}}" left-arrow />
 <scroll-view class="page-scroll__container {{orderStatus==='pending'?'':'bottom'}}" type="list" scroll-y style="{{containerStyle}}">
   <view class="info-box {{ showDetail ? 'show-bttom' : '' }}">

+ 5 - 2
miniprogram/module/order/pages/other-detail/other-detail.json

@@ -1,8 +1,11 @@
 {
   "renderer": "skyline",
-  "component": true,
   "usingComponents": {
     "t-navbar": "tdesign-miniprogram/navbar/navbar",
-    "t-icon": "tdesign-miniprogram/icon/icon"
+    "t-icon": "tdesign-miniprogram/icon/icon",
+    "after-sale-type-popup": "../../components/after-sale-type-popup/after-sale-type-popup",
+    "refund-reason-popup": "../../components/refund-reason-popup/refund-reason-popup",
+    "refund-confirm-popup": "../../components/refund-confirm-popup/refund-confirm-popup",
+    "refund-proof-popup": "../../components/refund-proof-popup/refund-proof-popup"
   }
 }

+ 41 - 1
miniprogram/module/order/pages/other-detail/other-detail.scss

@@ -63,6 +63,7 @@
   margin-bottom: 0;
   text-align: center;
 }
+
 .offline-title {
   padding-left: 32rpx;
   font-size: 32rpx;
@@ -74,6 +75,7 @@
   z-index: 10;
   text-align: left;
 }
+
 .goods-item-wrapper {
   background: #fff;
 
@@ -259,6 +261,44 @@
   }
 }
 
+// 申请售后 + 评价 按钮行(图示样式:左灰底、右白底蓝框)
+.action-btns {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: flex-end;
+  margin-top: 16rpx;
+}
+
+.btn-aftersale {
+  padding: 12rpx 28rpx;
+  border-radius: 12rpx;
+  font-size: 26rpx;
+  text-align: center;
+  background-color: #e8e8e8;
+  color: #666;
+}
+
+.btn-aftersale-status {
+  padding: 12rpx 10rpx;
+  border-radius: 12rpx;
+  font-size: 26rpx;
+  text-align: center;
+  color: #fa5151;
+  font-weight: 600;
+}
+
+.btn-review {
+  padding: 12rpx 28rpx;
+  border-radius: 12rpx;
+  font-size: 26rpx;
+  text-align: center;
+  background-color: #fff;
+  color: #1976d2;
+  border: 1px solid #1976d2;
+  margin-left: 20rpx;
+}
+
 .quantity-selector {
   display: flex;
   align-items: center;
@@ -672,4 +712,4 @@
 .remark-count-text {
   font-size: 24rpx;
   color: #999;
-}
+}

+ 226 - 55
miniprogram/module/order/pages/other-detail/other-detail.ts

@@ -4,7 +4,7 @@ import { handleWeChatPayment } from "../../../../utils/util";
 import {
   getTickleContext,
 } from "../../../../core/behavior/tickle.behavior";
-import { getOrderDetailMethod, orderPayMethod } from "../../request";
+import { getOrderDetailMethod, orderPayMethod, applyAfterSaleMethod } from "../../request";
 // module/order/pages/select-goods/other-detail.ts
 Page({
   behaviors: [PageContainerBehavior, DictionariesBehavior],
@@ -37,6 +37,29 @@ Page({
     showDetail: false,
     isPaymentLoading: false,
     orderStatus: "", // 订单状态:pending, paid, closed, completed
+    // 售后半屏弹窗(组件 after-sale-type-popup)
+    afterSalePopupVisible: false,
+    refundReasonPopupVisible: false,
+    refundConfirmPopupVisible: false,
+    refundProofPopupVisible: false,
+    refundSubmitSuccessPopupVisible: false,
+    selectedAfterSaleType: "",
+    selectedRefundReason: "",
+    refundStatus: "",
+    refundMaxAmount: 0,
+    refundAmount: 0,
+    refundDesc: "",
+    refundProofImages: [] as string[],
+    afterSaleSiteOption: {
+      name: "",
+      price: 0,
+      meta1: "",
+      meta2: "",
+      image: "",
+      patientConditioningProgramId: "",
+      patientConditioningRecordId: "",
+      sellType: "",
+    },
   },
   onLoad(options: any) {
     const remark = (this.data.orderDetail as any)?.remark || '';
@@ -77,6 +100,25 @@ Page({
         break;
     }
   },
+  // 查看物流详情
+  goLogistics(e: any) {
+    const { id } = e.currentTarget.dataset.goods;
+    const { liaison, phone, provinceName, cityName, areaName, detailAddress } = this.data.orderDetail;
+    let address = `${provinceName}${cityName ? `${cityName}` : ""
+      }${areaName ? `${areaName}` : ""}${detailAddress ? `${detailAddress}` : ""
+      }`;
+    const goods = JSON.stringify({ ...e.currentTarget.dataset.goods, liaison, phone, address });
+    if (id) {
+      wx.navigateTo({
+        url: `/module/order/pages/logistics-detail/logistics-detail?id=${id}&goods=${goods}`,
+      });
+    } else {
+      wx.showToast({
+        title: "无参数id",
+        icon: "none",
+      })
+    }
+  },
   // 订单详情
   async load(id: string) {
     wx.showLoading({ title: "加载中" });
@@ -119,11 +161,11 @@ Page({
           if (!items || !Array.isArray(items)) {
             return [];
           }
-          return items.map((item: any) => {
+          return items.map((item: any, index: number) => {
             // 0是一口价 1按穴位/经络次数计费
             const pricingType = item?.conditioningProgramDetail?.pricingType;
             return {
-              id: item.id || '',
+              id: item.id || '', //患者调理方案id
               patientConditioningRecordId: item?.patientConditioningRecordId || '',//调理记录id
               name: item.conditioningProgramName || '',
               description: (() => {
@@ -139,15 +181,21 @@ Page({
               receiptType: item?.receiptType || '', //收货类型 0-快递 1-线下取货
               expressType: item?.expressType || '', //快递类型 0-邮政速递 1-顺丰速运 2-京东快递 3-中通快递 4-圆通速递 5-申通快递 6-韵达快递 7-极兔速递
               expressNo: item?.expressNo || '', //快递单号
-              expressTypeName: this.getExpressTypeName(item?.expressType), //快递公司名称
-              expressTypeIcon: this.getExpressTypeIcon(item?.expressType), //快递公司图标
+              expressTypeName: item?.expressType, //快递公司名称
+              // expressTypeIcon: this.getExpressTypeIcon(item?.expressType), //快递公司图标
               //end实体商品所用到的字段
               progress: item?.progress || '', //进度 0-进行中 1-已完成 2-未开始 3-已取消 这个字段用于线下服务商品以及线上权益商品的进度状态
               image: item.conditioningProgramPhoto || '',
               price: item?.unitPrice || 0,
+              totalPrice: item?.totalPrice || 0,
               quantity: item?.totalMeasure || 0,
-              statusClass: this.getStatusClass(item?.sellType, item?.progress, item?.receiptStatus),
-              statusText: this.getGoodsStatusText(item?.sellType, item?.progress, item?.receiptStatus),
+              statusClass: this.getStatusClass(item?.sellType, item?.progress, item?.receiptStatus, item.expressStatus),
+              statusText: this.getGoodsStatusText(item?.sellType, item?.progress, item?.receiptStatus, item.expressStatus),
+              isCanEvaluate: item?.isCanEvaluate, //是否可以评价 false-否 true-是
+              evaluateTime: item?.evaluateTime, //有评价时间就是已评价,是空就是未评价
+              isShowAftersaleButton: item?.isShowAftersaleButton || false,
+              aftersaleProgressStr: item?.aftersaleProgressStr || '',
+              aftersaleProgress: item?.aftersaleProgress,
             }
           });
         };
@@ -178,7 +226,7 @@ Page({
       }
     } catch (error: any) {
       wx.showToast({
-        title: error.errMsg,
+        title: error.errMsg || "获取订单详情失败",
         icon: "none",
       });
     }
@@ -205,7 +253,7 @@ Page({
       const payResult = await orderPayMethod(this.data.id, (this.data.orderDetail as any)?.remark);
       if (payResult && payResult.data) {
         const paymentParams = payResult.data;
-        handleWeChatPayment(paymentParams, (res: any) => {
+        handleWeChatPayment(paymentParams, (_res: any) => {
           // 支付成功,跳转到成功页面
           wx.redirectTo({
             url: "/module/article/pages/success-page/success-page?title=订单支付成功",
@@ -213,13 +261,9 @@ Page({
         }, (error: any) => {
           this.setData({ isPaymentLoading: false });
           if (error?.errMsg === 'requestPayment:fail cancel') {
-           // 支付取消跳到支付订单页面
+            // 支付取消跳到支付订单页面
             wx.navigateBack({ delta: 1 });
           }
-          // wx.showToast({
-          //   title: error.errMsg,
-          //   icon: "none",
-          // });
         });
       } else {
         wx.showToast({
@@ -253,6 +297,158 @@ Page({
     });
   },
 
+  preventTap() {
+    // 仅用于 catchtap 阻止冒泡,避免触发父级 goAppointment
+  },
+  onApplyAfterSale(e: any) {
+    const { goods } = e.currentTarget.dataset;
+    this.setData({
+      afterSaleSiteOption: {
+        image: goods.image || '',
+        name: goods.name,
+        price: goods.price,
+        totalPrice: goods.totalPrice,
+        meta1: goods.description || '',
+        meta2: `x${goods.quantity}`,
+        patientConditioningProgramId: goods.id || '',
+        patientConditioningRecordId: goods.patientConditioningRecordId || '',
+        sellType: goods.sellType || '',
+      },
+      refundMaxAmount: goods.totalPrice || 0,
+      refundAmount: goods.totalPrice || 0,
+      selectedAfterSaleType: "",
+      selectedRefundReason: "",
+      refundDesc: "",
+      refundProofImages: [],
+      afterSalePopupVisible: true,
+    });
+  },
+
+  // 点击退款状态,跳转到售后详情页
+  onAftersaleProgress(e: any) {
+    const { goods } = e.currentTarget.dataset;
+    wx.navigateTo({
+      url: `/module/order/pages/refund-processing/refund-processing?id=${goods.id}&recordId=${goods.patientConditioningRecordId || ''}`,
+    });
+  },
+
+  onAfterSalePopupClose() {
+    this.setData({ afterSalePopupVisible: false });
+  },
+
+  onAfterSaleNext(e: WechatMiniprogram.CustomEvent<{ selectedKey: string }>) {
+    const key = e.detail?.selectedKey;
+    if (!key) {
+      wx.showToast({ title: "请选择售后类型", icon: "none" });
+      return;
+    }
+    this.setData({
+      selectedAfterSaleType: key,
+      afterSalePopupVisible: false,
+      refundReasonPopupVisible: true,
+    });
+  },
+
+  onRefundReasonPopupClose() {
+    this.setData({ refundReasonPopupVisible: false });
+  },
+
+  onRefundReasonNext(e: WechatMiniprogram.CustomEvent<{ reason: string }>) {
+    const reason = e.detail?.reason;
+    if (!reason) {
+      wx.showToast({ title: "请选择退款原因", icon: "none" });
+      return;
+    }
+    this.setData({
+      selectedRefundReason: reason,
+      refundReasonPopupVisible: false,
+      refundConfirmPopupVisible: true,
+      refundAmount: this.data.refundMaxAmount,
+    });
+  },
+
+  onRefundConfirmClose() {
+    this.setData({ refundConfirmPopupVisible: false });
+  },
+
+  onChangeRefundStatus(e: WechatMiniprogram.CustomEvent<{ status: string }>) {
+    this.setData({ refundStatus: e.detail?.status || "" });
+  },
+
+  onOpenRefundReasonAgain() {
+    this.setData({ refundReasonPopupVisible: true });
+  },
+
+  onRefundAmountConfirm(e: WechatMiniprogram.CustomEvent<{ amount: number }>) {
+    const amount = Number(e.detail?.amount || 0);
+    this.setData({ refundAmount: amount });
+  },
+
+  onOpenRefundProofEditor() {
+    this.setData({ refundProofPopupVisible: true });
+  },
+
+  onRefundProofPopupClose() {
+    this.setData({ refundProofPopupVisible: false });
+  },
+
+  onRefundProofConfirm(e: WechatMiniprogram.CustomEvent<{ desc: string; images: string[] }>) {
+    this.setData({
+      refundDesc: e.detail?.desc || "",
+      refundProofImages: e.detail?.images || [],
+      refundProofPopupVisible: false,
+    });
+  },
+
+  async onSubmitRefundApply() {
+    if (!this.data.selectedRefundReason) {
+      wx.showToast({ title: "请选择退款原因", icon: "none" });
+      return;
+    }
+    if (!this.data.refundProofImages.length && !this.data.refundDesc.trim()) {
+      wx.showToast({ title: "请上传凭证或填写描述", icon: "none" });
+      return;
+    }
+    wx.showLoading({ title: "提交中" });
+    const { afterSaleSiteOption, selectedAfterSaleType, refundStatus, selectedRefundReason, refundAmount, refundProofImages, refundDesc } = this.data;
+    const params = {
+      patientConditioningRecordId: (afterSaleSiteOption as any).patientConditioningRecordId,
+      patientConditioningProgramId: (afterSaleSiteOption as any).patientConditioningProgramId,
+      type: selectedAfterSaleType,
+      ...((afterSaleSiteOption as any).sellType === '1' ? { receiptStatus: refundStatus } : {}),
+      reason: selectedRefundReason,
+      applyAmount: refundAmount,
+      voucherImgs: refundProofImages,
+      remark: refundDesc,
+    };
+    console.log(params, "参数111")
+    try {
+      await applyAfterSaleMethod(params);
+      this.setData({ refundConfirmPopupVisible: false });
+      wx.navigateTo({
+        url: "/module/order/pages/refund-success/refund-success",
+      });
+    } catch (error: any) {
+      wx.showToast({ title: error.errMsg || "提交失败", icon: "none" });
+    }
+    wx.hideLoading();
+  },
+  onReview(e: WechatMiniprogram.TouchEvent) {
+    console.log(e.currentTarget.dataset);
+    const { evaluateTime } = e.currentTarget.dataset.goods;
+    if (evaluateTime) {
+      // 单商品评价详情
+      wx.navigateTo({
+        url: `/module/order/pages/goods-evaluateDetail/goods-evaluateDetail?goodsInfo=${encodeURIComponent(JSON.stringify(e.currentTarget.dataset.goods))}`,
+      });
+
+    } else {
+      // 去单商品评价页面
+      wx.navigateTo({
+        url: `/module/order/pages/goods-evaluate/goods-evaluate?goodsInfo=${encodeURIComponent(JSON.stringify(e.currentTarget.dataset.goods))}`,
+      });
+    }
+  },
   // 复制订单号
   copyOrderNo(e: any) {
     const orderNo = e.currentTarget.dataset.orderno;
@@ -275,9 +471,9 @@ Page({
   // sellType: 商品类型 1-实体商品 2-线下服务 3-线上权益
   // progress: 进度状态(用于线下服务和线上权益):0-进行中 1-已完成 2-未开始
   // receiptStatus: 收货状态(用于实体商品):0-待发货 1-已发货 2-已收货
-  getStatusClass(sellType?: string | number, progress?: string | number, receiptStatus?: string | number): string {
+  getStatusClass(sellType?: string | number, progress?: string | number, receiptStatus?: string | number, expressStatus?: string): string {
     const type = String(sellType || '');
-    // 实体商品 (sellType === 1) 使用 receiptStatus(三种状态)
+    // 实体商品 (sellType === 1) 使用 receiptStatus(三种状态)实体商品增加运输中
     if (type === "1") {
       const status = String(receiptStatus || '');
       if (status === "0") {
@@ -286,6 +482,8 @@ Page({
         return "status-received"; // 已发货
       } else if (status === "2") {
         return "status-completed"; // 已收货
+      } else {
+        return "status-received"
       }
     }
     // 线下服务 (sellType === 2) 和线上权益 (sellType === 3) 使用 progress(三种状态)
@@ -303,17 +501,21 @@ Page({
   },
 
   // 获取商品状态文本
-  getGoodsStatusText(sellType?: string | number, progress?: string | number, receiptStatus?: string | number): string {
+  getGoodsStatusText(sellType?: string | number, progress?: string | number, receiptStatus?: string | number, expressStatus?: string): string {
     const type = String(sellType || '');
-    // 实体商品 (sellType === 1) 使用 receiptStatus(三种状态)
+    // 实体商品 (sellType === 1) 使用 receiptStatus(三种状态)新增expressStatus运输状态
     if (type === "1") {
       const status = String(receiptStatus || '');
-      if (status === "0") {
-        return "待发货";
-      } else if (status === "1") {
-        return "已发货";
-      } else if (status === "2") {
-        return "已收货";
+      if (expressStatus) {
+        return expressStatus
+      } else {
+        if (status === "0") {
+          return "待发货";
+        } else if (status === "1") {
+          return "已发货";
+        } else if (status === "2") {
+          return "已收货";
+        }
       }
     }
     // 线下服务 (sellType === 2) 和线上权益 (sellType === 3) 使用 progress(三种状态)
@@ -329,37 +531,6 @@ Page({
     }
     return "";
   },
-
-  // 获取快递公司名称
-  getExpressTypeName(expressType?: string | number): string {
-    const expressTypeMap: Record<string, string> = {
-      "0": "邮政速递",
-      "1": "顺丰速运",
-      "2": "京东快递",
-      "3": "中通快递",
-      "4": "圆通速递",
-      "5": "申通快递",
-      "6": "韵达快递",
-      "7": "极兔速递",
-    };
-    return expressTypeMap[String(expressType || '')] || "";
-  },
-
-  // 获取快递公司图标
-  getExpressTypeIcon(expressType?: string | number): string {
-    const expressIconMap: Record<string, string> = {
-      "0": "icon-youzhengkuaidi", // 邮政速递
-      "1": "icon-shunfengkuaidi", // 顺丰速运
-      "2": "icon-jingdong", // 京东快递
-      "3": "icon-zhongtong", // 中通快递
-      "4": "icon-yuantongkuaidi", // 圆通速递
-      "5": "icon-shentong", // 申通快递
-      "6": "icon-yunda", // 韵达快递
-      "7": "icon-jitukuaidi", // 极兔速递
-    };
-    return expressIconMap[String(expressType || '')] || "";
-  },
-
   // 获取订单状态文本
   getStatusText(status: string | number): string {
     const statusTextMap: Record<string, string> = {

+ 74 - 11
miniprogram/module/order/pages/other-detail/other-detail.wxml

@@ -1,4 +1,4 @@
-<!--module/order/pages/select-goods/select-goods.wxml-->
+<!--module/order/pages/other-detail/other-detail.wxml-->
 <t-navbar title="{{title}}" left-arrow />
 <scroll-view class="page-scroll__container" type="list" scroll-y style="{{containerStyle}}">
   <view class="info-box">
@@ -11,18 +11,20 @@
         <!-- 线下取货小标题:当遇到第一个线下取货商品时显示 -->
         <view class="offline-title" wx:if="{{goods.receiptType === '1' && (goodsIndex === 0 || sellTypeFirstItems[goodsIndex - 1].receiptType === '0')}}">线下取货</view>
         <!-- 快递信息 -->
-        <view class="express-info" wx:if="{{goods.receiptType === '0' && goods.expressTypeName && goods.expressNo && goods.receiptStatus === '1'}}">
+        <view bindtap="{{goods.receiptStatus !== '0' ? 'goLogistics' : ''}}" data-goods="{{goods}}">
+        <view class="express-info" wx:if="{{goods.receiptType === '0'  && goods.receiptStatus === '1'}}">
           <view class="express-text">
             <view style="font-weight:600;margin-right: 10rpx;display: flex;align-items: center;">
-             <text style="font-size: 25rpx;"> 快件{{goodsIndex + 1}}</text>
-              <i class="iconfont {{goods.expressTypeIcon}}" style="font-size:15px" wx:if="{{goods.expressTypeIcon}}"></i>
+              <text style="font-size: 25rpx;"> 快件{{goodsIndex + 1}}</text>
+              <!-- <i class="iconfont {{goods.expressTypeIcon}}" style="font-size:15px" wx:if="{{goods.expressTypeIcon}}"></i> -->
             </view>
             <view style="color:#9b9797;font-size: 25rpx;">{{goods.expressTypeName}} {{goods.expressNo}}</view>
           </view>
-           <t-icon name="chevron-right" color="#000" size="40rpx" slot="note" />
+          <t-icon name="chevron-right" color="#000" size="40rpx" slot="note" />
         </view>
-        <view>
           <view class="goods-status {{goods.statusClass}}" wx:if="{{goods.statusText && orderStatus !== 'closed'}}">{{goods.statusText}}</view>
+          </view>
+        <view>
           <view class="goods-item">
             <image class="goods-image" src="{{goods.image}}" mode="aspectFill" wx:if="{{goods.image}}" />
             <view class="service-package-placeholder" wx:else>
@@ -41,6 +43,12 @@
               <view class="confirm-receipt-btn" wx:if="{{goods.receiptStatus === '1'}}" bindtap="onConfirmReceipt" data-patientConditioningRecordId="{{goods.patientConditioningRecordId}}" data-index="{{goodsIndex}}">
                 确认收货
               </view>
+              <!-- 申请售后 + 退款状态 + 评价 -->
+              <view class="action-btns">
+                <view class="btn-aftersale" wx:if="{{goods.isShowAftersaleButton}}" catchtap="onApplyAfterSale" data-goods="{{goods}}">申请售后</view>
+                <view class="btn-aftersale-status" wx:elif="{{goods.aftersaleProgressStr}}" catchtap="onAftersaleProgress" data-goods="{{goods}}">{{goods.aftersaleProgressStr}}</view>
+                <view class="btn-review" catchtap="onReview" data-goods="{{goods}}" wx:if="{{goods.isCanEvaluate}}">{{goods.evaluateTime?"已评价":"评价"}}</view>
+              </view>
             </view>
           </view>
         </view>
@@ -67,6 +75,12 @@
                 <view class="goods-desc" wx:if="{{goods.description}}">{{goods.description}}</view>
                 <view class="quantity-text">x{{goods.quantity}}</view>
               </view>
+              <!-- 申请售后 + 退款状态 + 评价 -->
+              <view class="action-btns" catchtap="preventTap">
+                <view class="btn-aftersale" wx:if="{{goods.isShowAftersaleButton}}" catchtap="onApplyAfterSale" data-goods="{{goods}}">申请售后</view>
+                <view class="btn-aftersale-status" wx:elif="{{goods.aftersaleProgressStr}}" catchtap="onAftersaleProgress" data-goods="{{goods}}">{{goods.aftersaleProgressStr}}</view>
+                <view class="btn-review" catchtap="onReview" data-goods="{{goods}}" wx:if="{{goods.isCanEvaluate}}">{{goods.evaluateTime?"已评价":"评价"}}</view>
+              </view>
             </view>
           </view>
         </view>
@@ -93,6 +107,12 @@
                 <view class="goods-desc" wx:if="{{goods.description}}">{{goods.description}}</view>
                 <view class="quantity-text">x{{goods.quantity}}</view>
               </view>
+              <!-- 申请售后 + 退款状态 + 评价 -->
+              <view class="action-btns">
+                <view class="btn-aftersale" wx:if="{{goods.isShowAftersaleButton}}" catchtap="onApplyAfterSale" data-goods="{{goods}}">申请售后</view>
+                <view class="btn-aftersale-status" wx:elif="{{goods.aftersaleProgressStr}}" catchtap="onAftersaleProgress" data-goods="{{goods}}">{{goods.aftersaleProgressStr}}</view>
+                <view class="btn-review" catchtap="onReview" data-goods="{{goods}}" wx:if="{{goods.isCanEvaluate}}">{{goods.evaluateTime?"已评价":"评价"}}</view>
+              </view>
             </view>
           </view>
         </view>
@@ -143,22 +163,22 @@
         <text class="info-label">创建时间</text>
         <text class="info-value">{{orderDetail.operateTime || ''}}</text>
       </view>
-       <view class="info-divider"></view>
+      <view class="info-divider"></view>
       <view class="info-item" wx:if="{{orderStatus!=='closed'}}">
         <text class="info-label">付款时间</text>
         <text class="info-value">{{orderDetail.payTime || ''}}</text>
       </view>
-       <view class="info-divider" wx:if="{{orderStatus!=='closed'}}"></view>
+      <view class="info-divider" wx:if="{{orderStatus!=='closed'}}"></view>
       <view class="info-item" wx:if="{{orderStatus==='completed'}}">
         <text class="info-label">成交时间</text>
         <text class="info-value">{{orderDetail.finishTime || ''}}</text>
       </view>
-       <view class="info-divider" wx:if="{{orderStatus==='completed'}}"></view>
+      <view class="info-divider" wx:if="{{orderStatus==='completed'}}"></view>
       <view class="info-item" wx:if="{{orderStatus==='closed'}}">
         <text class="info-label">关闭时间</text>
         <text class="info-value">{{orderDetail.cancelTime || ''}}</text>
       </view>
-       <view class="info-divider" wx:if="{{orderStatus==='closed'}}"></view>
+      <view class="info-divider" wx:if="{{orderStatus==='closed'}}"></view>
       <view class="info-item" wx:if="{{orderStatus!=='closed'}}">
         <text class="info-label">微信交易号</text>
         <text class="info-value">{{orderDetail.payTransactionNo || ''}}</text>
@@ -190,4 +210,47 @@
   </view>
 
 
-</scroll-view>
+</scroll-view>
+
+<after-sale-type-popup
+  visible="{{afterSalePopupVisible}}"
+  site-option="{{afterSaleSiteOption}}"
+  bind:close="onAfterSalePopupClose"
+  bind:next="onAfterSaleNext"
+/>
+
+<refund-reason-popup
+  visible="{{refundReasonPopupVisible}}"
+  initial-reason="{{selectedRefundReason}}"
+  bind:close="onRefundReasonPopupClose"
+  bind:next="onRefundReasonNext"
+/>
+
+<refund-confirm-popup
+  visible="{{refundConfirmPopupVisible}}"
+  goods-name="{{afterSaleSiteOption.name}}"
+  goods-image="{{afterSaleSiteOption.image}}"
+  goods-price="{{afterSaleSiteOption.price}}"
+  goods-meta1="{{afterSaleSiteOption.meta1}}"
+  goods-meta2="{{afterSaleSiteOption.meta2}}"
+  refund-reason="{{selectedRefundReason}}"
+  refund-status="{{refundStatus}}"
+  refund-amount="{{refundAmount}}"
+  max-amount="{{refundMaxAmount}}"
+  proof-images="{{refundProofImages}}"
+  show-receipt-status="{{afterSaleSiteOption.sellType === '1'}}"
+  bind:close="onRefundConfirmClose"
+  bind:changeStatus="onChangeRefundStatus"
+  bind:editReason="onOpenRefundReasonAgain"
+  bind:editProof="onOpenRefundProofEditor"
+  bind:amountConfirm="onRefundAmountConfirm"
+  bind:submit="onSubmitRefundApply"
+/>
+
+<refund-proof-popup
+  visible="{{refundProofPopupVisible}}"
+  desc="{{refundDesc}}"
+  images="{{refundProofImages}}"
+  bind:close="onRefundProofPopupClose"
+  bind:confirm="onRefundProofConfirm"
+/>

+ 12 - 0
miniprogram/module/order/pages/refund-processing/refund-processing.json

@@ -0,0 +1,12 @@
+{
+  "navigationBarTitleText": "商家处理",
+  "usingComponents": {
+    "t-navbar": "tdesign-miniprogram/navbar/navbar",
+    "t-icon": "tdesign-miniprogram/icon/icon",
+    "t-button": "tdesign-miniprogram/button/button",
+    "after-sale-type-popup": "../../components/after-sale-type-popup/after-sale-type-popup",
+    "refund-reason-popup": "../../components/refund-reason-popup/refund-reason-popup",
+    "refund-confirm-popup": "../../components/refund-confirm-popup/refund-confirm-popup",
+    "refund-proof-popup": "../../components/refund-proof-popup/refund-proof-popup"
+  }
+}

+ 314 - 0
miniprogram/module/order/pages/refund-processing/refund-processing.scss

@@ -0,0 +1,314 @@
+page {
+  background-color: #f7f8fa;
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+}
+
+.page-container {
+  flex: 1;
+  padding-bottom: 120rpx;
+  /* footer padding */
+}
+
+/* Header Section */
+.header-section {
+  background-color: #fff;
+  display: flex;
+  flex-direction: column;
+
+
+  &.header-processing,
+  &.header-revoked,
+  &.header-completed {
+    padding: 80rpx 40rpx 60rpx;
+    align-items: center;
+  }
+
+  &.header-approved,
+  &.header-rejected {
+    padding: 40rpx 40rpx 60rpx;
+    align-items: flex-start;
+  }
+}
+
+.status-title {
+  font-size: 52rpx;
+  font-weight: 600;
+  color: #333;
+  margin-bottom: 20rpx;
+
+  &.completed-title {
+    font-size: 64rpx;
+    margin-bottom: 60rpx;
+  }
+}
+
+/* Refund Success Card */
+.refund-card {
+  width: 100%;
+  padding: 30rpx 0;
+  border-top: 1rpx solid #eee;
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-start;
+  margin-top: 20rpx;
+}
+
+.refund-dest-name {
+  font-size: 32rpx;
+  color: #333;
+  font-weight: 500;
+  margin-bottom: 8rpx;
+  text-align: left;
+}
+
+.refund-dest-account {
+  font-size: 26rpx;
+  color: #999;
+  text-align: left;
+}
+
+.refund-amount-text {
+  font-size: 36rpx;
+  color: #333;
+  font-weight: 600;
+}
+
+.status-sub {
+  font-size: 28rpx;
+  color: #666;
+  margin-bottom: 40rpx;
+}
+
+.countdown-container {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.time-block {
+  width: 90rpx;
+  height: 90rpx;
+  background-color: #1d6ff6;
+  color: #fff;
+  font-size: 40rpx;
+  font-weight: 600;
+  border-radius: 8rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.time-colon {
+  font-size: 40rpx;
+  color: #666;
+  margin: 0 16rpx;
+  font-weight: 600;
+}
+
+/* Approved/Rejected Common Styles */
+.approved-title {
+  font-size: 40rpx;
+  font-weight: 600;
+  color: #333;
+  line-height: 1.4;
+  margin-bottom: 16rpx;
+  text-align: left;
+}
+
+.approved-time {
+  font-size: 28rpx;
+  color: #999;
+  margin-bottom: 40rpx;
+}
+
+.approved-content {
+  width: 100%;
+}
+
+.content-row {
+  font-size: 28rpx;
+  color: #333;
+  line-height: 1.6;
+  margin-bottom: 16rpx;
+}
+
+/* Detail Section */
+.detail-section {
+  background-color: #fff;
+  margin-top: 20rpx;
+  padding: 32rpx;
+  border-radius: 16rpx 16rpx 0 0;
+}
+
+.goods-info {
+  display: flex;
+  // align-items: center;
+  margin-bottom: 32rpx;
+}
+
+.goods-img {
+  width: 120rpx;
+  height: 120rpx;
+  border-radius: 8rpx;
+  background-color: #f5f5f5;
+}
+
+.service-package-placeholder {
+  width: 120rpx;
+  height: 120rpx;
+  border-radius: 8rpx;
+  background-color: #e8e8e8;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.goods-desc {
+  margin-left: 20rpx;
+  flex: 1;
+}
+
+.goods-title {
+  font-size: 30rpx;
+  color: #333;
+  margin-bottom: 12rpx;
+}
+
+.goods-meta {
+  font-size: 24rpx;
+  color: #999;
+}
+
+.divider {
+  width: 100%;
+  height: 1rpx;
+  background-color: #eee;
+  margin-bottom: 32rpx;
+}
+
+/* List Items */
+.info-list {
+  display: flex;
+  flex-direction: column;
+  gap: 24rpx;
+}
+
+.info-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  font-size: 28rpx;
+}
+
+.info-label {
+  color: #666;
+}
+
+.info-value {
+  color: #333;
+}
+
+.info-value-group {
+  display: flex;
+  align-items: center;
+}
+
+.clickable-text {
+  color: #333;
+  font-weight: 600;
+}
+
+.copy-btn {
+  color: #333;
+  font-weight: 600;
+  margin-left: 16rpx;
+}
+
+/* Service Action */
+.service-action {
+  display: inline-flex;
+  flex-direction: column;
+  align-items: center;
+  margin-top: 40rpx;
+}
+
+.service-text {
+  font-size: 24rpx;
+  color: #333;
+  margin-top: 8rpx;
+}
+
+/* Bottom Bar */
+.bottom-bar {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  background-color: #fff;
+  padding: 20rpx 32rpx 32rpx;
+  box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05);
+
+  &.bottom-bar-rejected {
+    padding: 20rpx 32rpx 32rpx;
+    background-color: transparent;
+    box-shadow: none;
+  }
+}
+
+.revoke-btn {
+  background-color: #1d6ff6;
+  color: #fff;
+  font-size: 32rpx;
+  font-weight: 500;
+  border-radius: 44rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  height: 88rpx;
+  width: 100%;
+}
+
+.revoke-btn::after {
+  border: none;
+}
+
+/* Dual Button Group - Pill Shaped */
+.dual-btn-group {
+  display: flex;
+  align-items: center;
+  width: 100%;
+  height: 100rpx;
+  background-color: #1d6ff6;
+  border-radius: 50rpx;
+  /* Makes it pill shaped */
+  overflow: hidden;
+  box-shadow: 0 4rpx 20rpx rgba(29, 111, 246, 0.2);
+}
+
+.dual-btn {
+  flex: 1;
+  height: 100%;
+  background-color: transparent;
+  color: #fff;
+  font-size: 32rpx;
+  font-weight: 600;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border-radius: 0;
+  padding: 0;
+  margin: 0;
+  line-height: normal;
+
+  &::after {
+    border: none;
+  }
+}
+
+.btn-divider {
+  width: 1rpx;
+  height: 40rpx;
+  background-color: rgba(255, 255, 255, 0.5);
+}

+ 330 - 0
miniprogram/module/order/pages/refund-processing/refund-processing.ts

@@ -0,0 +1,330 @@
+import { getAfterSaleDetailMethod, cancelAfterSaleMethod, applyAfterSaleMethod, updateAfterSaleMethod } from "../../request";
+
+// progress 数字转页面状态
+const progressToState: Record<string, string> = {
+  '0': 'processing',
+  '1': 'revoked',
+  '2': 'rejected',
+  '3': 'approved',
+  '4': 'completed',
+};
+
+Page({
+  data: {
+    id: '',
+    recordId: '',
+    aftersaleId: '',
+    pageTitle: '商家处理',
+    refundState: '',
+    days: '00',
+    hours: '00',
+    minutes: '00',
+    seconds: '00',
+    goods: {
+      name: "",
+      meta1: "",
+      price: "",
+      image: "",
+    },
+    refundDetail: {
+      reason: "",
+      amount: "",
+      refundAmount: "",
+      finishTime: "",
+      applyTime: "",
+      refundNo: "",
+      updateTime: ""
+    },
+    // 协商历史标题和内容
+    negotiateTitle: "",
+    negotiateContent: "",
+    // 拒绝状态处理截止时间
+    handleEndTime: "",
+    refundDestination: {
+      type: '退回微信',
+      account: '',
+    },
+    // 弹窗状态
+    refundReasonPopupVisible: false,
+    refundConfirmPopupVisible: false,
+    refundProofPopupVisible: false,
+    refundStatus: '',
+    refundAction: '', // 'modify' 修改申请 | 'reApply' 再次申请
+    refundMaxAmount: '0',
+    refundProofImages: [] as string[],
+    refundDesc: '',
+    // 接口提交所需原始字段
+    rawDetail: {} as any,
+  },
+
+  timer: null as any,
+
+  onLoad(options: any) {
+    if (options.id) {
+      this.setData({ id: options.id, recordId: options.recordId || '' });
+      this.loadDetail(options.id);
+    }
+  },
+
+  onShow() {
+    if (this.data.id && this.data.refundState) {
+      this.loadDetail(this.data.id);
+    }
+  },
+
+  async loadDetail(id: string) {
+    wx.showLoading({ title: '加载中' });
+    try {
+      const res = await getAfterSaleDetailMethod(id);
+      const detail = (res as any)?.data;
+      if (!detail) return;
+
+      const refundState = progressToState[String(detail.progress)] || 'processing';
+      console.log(detail, "888")
+      this.setData({
+        rawDetail: detail,
+        aftersaleId: detail.id || '',
+        refundState,
+        goods: {
+          name: detail.conditioningProgramName || '',
+          meta1: detail.convertDose ? `${detail.convertDose}${detail.convertUnit || '次'}` : '',
+          price: String(detail.unitPrice || 0),
+          image: detail.conditioningProgramPhoto || '',
+        },
+        refundDetail: {
+          reason: detail.reason || '',
+          amount: String(detail.totalPrice || 0),
+          refundAmount: String(detail.refundAmount || 0),
+          finishTime: detail.finishTime || '',
+          applyTime: detail.applyTime || '',
+          refundNo: detail.ref || '',
+          updateTime: detail.updateTime || ''
+        },
+        negotiateTitle: detail.title || '',
+        negotiateContent: (() => {
+          const c = detail.content || '';
+          try { if (c && JSON.parse(c)) return ''; } catch (e) { /* 不是JSON,保留原文 */ }
+          return c;
+        })(),
+        handleEndTime: detail.handleEndTime || '',
+        refundMaxAmount: String(detail.totalPrice || 0),
+        refundProofImages: detail.voucherImgs || [],
+        refundDesc: detail.remark || '',
+      });
+
+      this.updatePageTitle();
+      if (refundState === 'processing' || refundState === 'rejected') {
+        this.startCountdown();
+      }
+    } catch (error: any) {
+      wx.showToast({ title: error.errMsg || '获取详情失败', icon: 'none' });
+    }
+    wx.hideLoading();
+  },
+
+  updatePageTitle() {
+    let title = '商家处理';
+    if (this.data.refundState === 'revoked') {
+      title = '撤销申请';
+    } else if (this.data.refundState === 'completed') {
+      title = '退款完成';
+    }
+    this.setData({ pageTitle: title });
+  },
+
+  onUnload() {
+    if (this.timer) {
+      clearInterval(this.timer);
+    }
+  },
+
+  startCountdown() {
+    if (this.timer) clearInterval(this.timer);
+
+    const setTime = (diff: number) => {
+      if (diff <= 0) {
+        this.setData({ days: '00', hours: '00', minutes: '00', seconds: '00' });
+        if (this.timer) {
+          clearInterval(this.timer);
+          this.timer = null;
+        }
+        // 拒绝状态倒计时结束,清除截止时间,触发按钮切换
+        if (this.data.refundState === 'rejected' && this.data.handleEndTime) {
+          this.setData({ handleEndTime: '' });
+        }
+        return;
+      }
+      const d = Math.floor(diff / (1000 * 60 * 60 * 24));
+      const h = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
+      const m = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
+      const s = Math.floor((diff % (1000 * 60)) / 1000);
+      this.setData({
+        days: d < 10 ? '0' + d : String(d),
+        hours: h < 10 ? '0' + h : String(h),
+        minutes: m < 10 ? '0' + m : String(m),
+        seconds: s < 10 ? '0' + s : String(s),
+      });
+    };
+
+    if (this.data.refundState === 'rejected' && this.data.handleEndTime) {
+      // rejected:倒计时到处理截止时间
+      const target = new Date(this.data.handleEndTime.replace(/-/g, '/')).getTime();
+      const update = () => setTime(target - new Date().getTime());
+      update();
+      this.timer = setInterval(update, 1000);
+    } else {
+      // processing:已过去的时间
+      const applyTime = new Date(this.data.refundDetail.applyTime.replace(/-/g, '/')).getTime();
+      const update = () => setTime(new Date().getTime() - applyTime);
+      update();
+      this.timer = setInterval(update, 1000);
+    }
+  },
+
+  // 修改申请:打开确认弹窗
+  onModify() {
+    this.setData({ refundAction: 'modify', refundConfirmPopupVisible: true });
+  },
+
+  onRefundConfirmClose() {
+    this.setData({ refundConfirmPopupVisible: false });
+  },
+
+  onChangeRefundStatus(e: any) {
+    this.setData({ refundStatus: e.detail.status });
+  },
+
+  onOpenRefundReasonAgain() {
+    this.setData({
+      refundConfirmPopupVisible: false,
+      refundReasonPopupVisible: true
+    });
+  },
+
+  onOpenRefundProofEditor() {
+    this.setData({
+      refundConfirmPopupVisible: false,
+      refundProofPopupVisible: true
+    });
+  },
+
+  onRefundAmountConfirm(e: any) {
+    this.setData({
+      ['refundDetail.amount']: e.detail.amount
+    });
+  },
+
+  async onSubmitRefundApply() {
+    if (!this.data.refundProofImages.length && !this.data.refundDesc.trim()) {
+      wx.showToast({ title: '请上传凭证或填写描述', icon: 'none' });
+      return;
+    }
+    wx.showLoading({ title: '正在提交' });
+    const { rawDetail, refundStatus, refundDetail, refundProofImages, refundDesc, recordId, id, aftersaleId, refundAction } = this.data;
+    const params = {
+      patientConditioningRecordId: recordId,
+      patientConditioningProgramId: id,
+      type: rawDetail.type,
+      ...(rawDetail.sellType === '1' ? { receiptStatus: refundStatus || rawDetail.receiptStatus } : {}),
+      reason: refundDetail.reason,
+      applyAmount: Number(refundDetail.amount) || 0,
+      voucherImgs: refundProofImages,
+      remark: refundDesc,
+    };
+    try {
+      if (refundAction === 'modify') {
+        await updateAfterSaleMethod(Number(aftersaleId), params);
+      } else {
+        await applyAfterSaleMethod(params);
+      }
+      this.setData({ refundConfirmPopupVisible: false });
+      wx.navigateTo({
+        url: '/module/order/pages/refund-success/refund-success'
+      });
+    } catch (error: any) {
+      wx.showToast({ title: error.errMsg || '提交失败', icon: 'none' });
+    }
+    wx.hideLoading();
+  },
+
+  onRefundReasonPopupClose() {
+    this.setData({ refundReasonPopupVisible: false });
+  },
+
+  onRefundReasonNext(e: any) {
+    this.setData({
+      ['refundDetail.reason']: e.detail.reason,
+      refundReasonPopupVisible: false,
+      refundConfirmPopupVisible: true
+    });
+  },
+
+  onRefundProofPopupClose() {
+    this.setData({ refundProofPopupVisible: false, refundConfirmPopupVisible: true });
+  },
+
+  onRefundProofSubmit(e: any) {
+    this.setData({
+      refundProofImages: e.detail.images,
+      refundDesc: e.detail.desc || '',
+      refundProofPopupVisible: false,
+      refundConfirmPopupVisible: true
+    });
+  },
+
+  onViewHistory() {
+    wx.navigateTo({
+      url: `/module/order/pages/negotiation-history/negotiation-history?id=${this.data.aftersaleId}`
+    });
+  },
+
+  onCopyRefundNo() {
+    wx.setClipboardData({
+      data: this.data.refundDetail.refundNo,
+      success: () => {
+        wx.showToast({ title: '已复制', icon: 'none' });
+      }
+    });
+  },
+
+  onCallService() {
+    wx.makePhoneCall({
+      phoneNumber: '400-123-4567',
+      fail: () => { }
+    });
+  },
+
+  onReApply() {
+    const originalPrice = String(this.data.rawDetail.totalPrice || 0);
+    this.setData({
+      refundAction: 'reApply',
+      refundDetail: { ...this.data.refundDetail, amount: originalPrice },
+      refundMaxAmount: originalPrice,
+      refundConfirmPopupVisible: true,
+    });
+  },
+
+  onRevoke() {
+    wx.showModal({
+      title: '撤销退款申请',
+      content: '撤销退款申请后,可以再次发起退款申请(总共可发起2次)',
+      confirmText: '确定撤销',
+      cancelText: '暂不撤销',
+      success: async (res) => {
+        if (res.confirm) {
+          try {
+            await cancelAfterSaleMethod(Number(this.data.aftersaleId));
+            wx.showToast({ title: '已撤销', icon: 'success' });
+            setTimeout(() => {
+              wx.redirectTo({
+                url: '/module/article/pages/success-page/success-page?title=撤销退款申请成功'
+              });
+            }, 1500);
+          } catch (error: any) {
+            wx.showToast({ title: error.errMsg || '撤销失败', icon: 'none' });
+          }
+        }
+      }
+    });
+  }
+});

Some files were not shown because too many files changed in this diff