Jelajahi Sumber

Merge branch 'develop' into project/CD.510100

# Conflicts:
#	miniprogram/app.config.ts
#	miniprogram/app.json
cc12458 3 bulan lalu
induk
melakukan
084a2215e7
100 mengubah file dengan 3312 tambahan dan 447 penghapusan
  1. 1 0
      .cloudbase/container/debug.json
  2. 0 79
      .idea/workspace.xml
  3. 5 2
      miniprogram/app.config.ts
  4. 41 8
      miniprogram/app.json
  5. 11 7
      miniprogram/app.ts
  6. TEMPAT SAMPAH
      miniprogram/assets/bg/bg_dialog@2x.png
  7. TEMPAT SAMPAH
      miniprogram/assets/bg/bg_home.png
  8. TEMPAT SAMPAH
      miniprogram/assets/bg/bg_user@2x.png
  9. TEMPAT SAMPAH
      miniprogram/assets/bg/health-file.bg.png
  10. TEMPAT SAMPAH
      miniprogram/assets/bg/pic_body@2x.png
  11. TEMPAT SAMPAH
      miniprogram/assets/icon/all@3x.png
  12. TEMPAT SAMPAH
      miniprogram/assets/icon/deal@3x.png
  13. TEMPAT SAMPAH
      miniprogram/assets/icon/delivery@3x.png
  14. TEMPAT SAMPAH
      miniprogram/assets/icon/icon_footprint@3x.png
  15. TEMPAT SAMPAH
      miniprogram/assets/icon/obligation@3x.png
  16. 7 2
      miniprogram/components/tabbar/tabbar.ts
  17. 1 1
      miniprogram/components/tabbar/tabbar.wxml
  18. 24 0
      miniprogram/i18n/behavior.ts
  19. 15 0
      miniprogram/i18n/install.ts
  20. 45 0
      miniprogram/i18n/tool.ts
  21. 4 2
      miniprogram/lib/request/create.ts
  22. TEMPAT SAMPAH
      miniprogram/module/article/assets/servicePag.png
  23. 5 0
      miniprogram/module/article/components/education-card/education-card.json
  24. 58 0
      miniprogram/module/article/components/education-card/education-card.scss
  25. 42 0
      miniprogram/module/article/components/education-card/education-card.ts
  26. 12 0
      miniprogram/module/article/components/education-card/education-card.wxml
  27. 9 5
      miniprogram/module/article/components/science-card/science-card.scss
  28. 33 3
      miniprogram/module/article/components/science-card/science-card.ts
  29. 2 1
      miniprogram/module/article/components/science-card/science-card.wxml
  30. 5 0
      miniprogram/module/article/components/waterfall-card/waterfall-card.json
  31. 70 0
      miniprogram/module/article/components/waterfall-card/waterfall-card.scss
  32. 34 0
      miniprogram/module/article/components/waterfall-card/waterfall-card.ts
  33. 13 0
      miniprogram/module/article/components/waterfall-card/waterfall-card.wxml
  34. 2 1
      miniprogram/module/article/pages/confirm-receiving/confirm-receiving.json
  35. 17 6
      miniprogram/module/article/pages/confirm-receiving/confirm-receiving.scss
  36. 78 58
      miniprogram/module/article/pages/confirm-receiving/confirm-receiving.ts
  37. 10 18
      miniprogram/module/article/pages/confirm-receiving/confirm-receiving.wxml
  38. 7 2
      miniprogram/module/article/pages/diet-list/diet-list.ts
  39. 7 0
      miniprogram/module/article/pages/education-list/education-list.json
  40. 18 0
      miniprogram/module/article/pages/education-list/education-list.scss
  41. 83 0
      miniprogram/module/article/pages/education-list/education-list.ts
  42. 14 0
      miniprogram/module/article/pages/education-list/education-list.wxml
  43. 1 1
      miniprogram/module/article/pages/foot-print/foot-print.scss
  44. 20 10
      miniprogram/module/article/pages/foot-print/foot-print.ts
  45. 37 56
      miniprogram/module/article/pages/manage-address/manage-address.ts
  46. 0 1
      miniprogram/module/article/pages/manage-address/manage-address.wxml
  47. 1 0
      miniprogram/module/article/pages/order-detail/order-detail.ts
  48. 124 0
      miniprogram/module/article/pages/order-list/order-list.scss
  49. 103 43
      miniprogram/module/article/pages/order-list/order-list.ts
  50. 53 10
      miniprogram/module/article/pages/order-list/order-list.wxml
  51. 2 0
      miniprogram/module/article/pages/science-info/science-info.ts
  52. 1 1
      miniprogram/module/article/pages/science-info/science-info.wxml
  53. 10 3
      miniprogram/module/article/pages/science-list/science-list.ts
  54. 1 1
      miniprogram/module/article/pages/science-list/science-list.wxml
  55. 1 1
      miniprogram/module/article/pages/success-page/success-page.scss
  56. 4 1
      miniprogram/module/article/pages/success-page/success-page.ts
  57. 33 0
      miniprogram/module/article/request.ts
  58. 4 3
      miniprogram/module/article/searc-list.scss
  59. 1 1
      miniprogram/module/care/pages/care/verifyRecord.json
  60. 89 0
      miniprogram/module/care/pages/care/verifyRecord.scss
  61. 39 8
      miniprogram/module/care/pages/care/verifyRecord.ts
  62. 51 2
      miniprogram/module/care/pages/care/verifyRecord.wxml
  63. 1 1
      miniprogram/module/care/pages/careDetail/careDetail.json
  64. 10 0
      miniprogram/module/care/pages/careDetail/careDetail.ts
  65. 6 6
      miniprogram/module/care/pages/careDetail/careDetail.wxml
  66. 1 1
      miniprogram/module/care/pages/offlineTreatment/offlineTreatment.json
  67. 54 15
      miniprogram/module/care/pages/offlineTreatment/offlineTreatment.scss
  68. 75 5
      miniprogram/module/care/pages/offlineTreatment/offlineTreatment.ts
  69. 30 21
      miniprogram/module/care/pages/offlineTreatment/offlineTreatment.wxml
  70. 1 1
      miniprogram/module/care/pages/reportRecord/reportRecord.json
  71. 1 1
      miniprogram/module/care/request.ts
  72. 4 0
      miniprogram/module/chats/common.scss
  73. 3 0
      miniprogram/module/chats/components/guide/guide.scss
  74. 53 18
      miniprogram/module/chats/components/guide/guide.ts
  75. 17 4
      miniprogram/module/chats/components/guide/guide.wxml
  76. 12 0
      miniprogram/module/chats/components/message-consult/message-consult.json
  77. 272 0
      miniprogram/module/chats/components/message-consult/message-consult.scss
  78. 634 0
      miniprogram/module/chats/components/message-consult/message-consult.ts
  79. 86 0
      miniprogram/module/chats/components/message-consult/message-consult.wxml
  80. 7 2
      miniprogram/module/chats/components/message-follow/message-follow.ts
  81. 3 3
      miniprogram/module/chats/components/message-follow/message-follow.wxml
  82. 7 1
      miniprogram/module/chats/components/message-report/message-report.ts
  83. 4 4
      miniprogram/module/chats/components/message-report/message-report.wxml
  84. 2 1
      miniprogram/module/chats/components/questionnaire/questionnaire.json
  85. 70 7
      miniprogram/module/chats/components/questionnaire/questionnaire.ts
  86. 11 2
      miniprogram/module/chats/components/questionnaire/questionnaire.wxml
  87. 4 0
      miniprogram/module/chats/pages/analysis/analysis.json
  88. 1 0
      miniprogram/module/chats/pages/analysis/analysis.scss
  89. 66 0
      miniprogram/module/chats/pages/analysis/analysis.ts
  90. 4 0
      miniprogram/module/chats/pages/analysis/analysis.wxml
  91. 8 0
      miniprogram/module/chats/pages/consultation-record/consultation-record.json
  92. 151 0
      miniprogram/module/chats/pages/consultation-record/consultation-record.scss
  93. 130 0
      miniprogram/module/chats/pages/consultation-record/consultation-record.ts
  94. 78 0
      miniprogram/module/chats/pages/consultation-record/consultation-record.wxml
  95. 23 14
      miniprogram/module/chats/pages/index/index.ts
  96. 4 3
      miniprogram/module/chats/pages/index/index.wxml
  97. 105 0
      miniprogram/module/chats/towxml/audio-player/Audio.js
  98. 107 0
      miniprogram/module/chats/towxml/audio-player/audio-player.js
  99. 5 0
      miniprogram/module/chats/towxml/audio-player/audio-player.json
  100. 14 0
      miniprogram/module/chats/towxml/audio-player/audio-player.wxml

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

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

+ 0 - 79
.idea/workspace.xml

@@ -1,79 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project version="4">
-  <component name="AutoImportSettings">
-    <option name="autoReloadType" value="SELECTIVE" />
-  </component>
-  <component name="ChangeListManager">
-    <list default="true" id="ade0a8c5-0639-4f05-956a-aae1181b5d18" name="更改" comment="">
-      <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/miniprogram/app.config.ts" beforeDir="false" afterPath="$PROJECT_DIR$/miniprogram/app.config.ts" afterDir="false" />
-    </list>
-    <option name="SHOW_DIALOG" value="false" />
-    <option name="HIGHLIGHT_CONFLICTS" value="true" />
-    <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
-    <option name="LAST_RESOLUTION" value="IGNORE" />
-  </component>
-  <component name="Git.Settings">
-    <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
-  </component>
-  <component name="ProjectColorInfo">{
-  &quot;associatedIndex&quot;: 6
-}</component>
-  <component name="ProjectId" id="2y83hfQWdk0KL1ry1ltAyuUZcIM" />
-  <component name="ProjectViewState">
-    <option name="hideEmptyMiddlePackages" value="true" />
-    <option name="showLibraryContents" value="true" />
-  </component>
-  <component name="PropertiesComponent"><![CDATA[{
-  "keyToString": {
-    "ModuleVcsDetector.initialDetectionPerformed": "true",
-    "RunOnceActivity.ShowReadmeOnStart": "true",
-    "RunOnceActivity.git.unshallow": "true",
-    "git-widget-placeholder": "release/2.0.1",
-    "junie.onboarding.icon.badge.shown": "true",
-    "last_opened_file_path": "/Users/zhangtiantian/Desktop/six-health.applet",
-    "node.js.detected.package.eslint": "true",
-    "node.js.detected.package.tslint": "true",
-    "node.js.selected.package.eslint": "(autodetect)",
-    "node.js.selected.package.tslint": "(autodetect)",
-    "nodejs_package_manager_path": "npm",
-    "ts.external.directory.path": "/Applications/WebStorm.app/Contents/plugins/javascript-plugin/jsLanguageServicesImpl/external",
-    "vue.rearranger.settings.migration": "true"
-  }
-}]]></component>
-  <component name="SharedIndexes">
-    <attachedChunks>
-      <set>
-        <option value="bundled-js-predefined-d6986cc7102b-6a121458b545-JavaScript-WS-251.25410.117" />
-      </set>
-    </attachedChunks>
-  </component>
-  <component name="TaskManager">
-    <task active="true" id="Default" summary="默认任务">
-      <changelist id="ade0a8c5-0639-4f05-956a-aae1181b5d18" name="更改" comment="" />
-      <created>1749202493163</created>
-      <option name="number" value="Default" />
-      <option name="presentableId" value="Default" />
-      <updated>1749202493163</updated>
-      <workItem from="1749202494220" duration="720000" />
-      <workItem from="1749523832830" duration="2478000" />
-      <workItem from="1754882475936" duration="7606000" />
-    </task>
-    <servers />
-  </component>
-  <component name="TypeScriptGeneratedFilesManager">
-    <option name="version" value="3" />
-    <option name="exactExcludedFiles">
-      <list>
-        <option value="$PROJECT_DIR$/miniprogram/module/care/pages/careDetail/careDetail.js" />
-        <option value="$PROJECT_DIR$/miniprogram/module/charts/compoments/record-index/record-index.js" />
-        <option value="$PROJECT_DIR$/miniprogram/module/charts/compoments/record-index/chalk.theme.js" />
-        <option value="$PROJECT_DIR$/miniprogram/module/charts/record-care/record-care.js" />
-        <option value="$PROJECT_DIR$/miniprogram/module/charts/record-care/record-care.js" />
-      </list>
-    </option>
-  </component>
-  <component name="UnknownFeatures">
-    <option featureType="com.intellij.fileTypeFactory" implementationName="*.wxml" />
-  </component>
-</project>

+ 5 - 2
miniprogram/app.config.ts

@@ -3,10 +3,13 @@ import { getAccountInfoSync } from "./lib/wx/open-api";
 const miniProgram = getAccountInfoSync();
 const env = miniProgram.envVersion;
 let host = "xingxinsaas.com";
-if (env === "trial" || env === 'develop') {
-  // host = "test.hzliuzhi.com";
+if (env === "trial" || env === "develop") {
+  host = "test.hzliuzhi.com";
 }
+export const HOST = host;
 export const Base_URL = `https://${host}/manager/fdhb-mobile`;
 export const Upload_URL = `https://${host}/manager/file`;
+export const Locale_URL = `https://${host}/admin/i18n`
+export const ENV = env;
 
 

+ 41 - 8
miniprogram/app.json

@@ -1,10 +1,17 @@
 {
-  "pages": ["pages/home/home", "pages/mine/mine"],
+  "pages": [
+    "pages/home/home",
+    "pages/mine/mine"
+  ],
   "subpackages": [
     {
       "name": "chats",
       "root": "module/chats",
-      "pages": ["pages/index/index"]
+      "pages": [
+        "pages/index/index",
+        "pages/analysis/analysis",
+        "pages/consultation-record/consultation-record"
+      ]
     },
     {
       "name": "health",
@@ -26,6 +33,7 @@
         "pages/diet-list/diet-list",
         "pages/diet-info/diet-info",
         "pages/science-list/science-list",
+        "pages/education-list/education-list",
         "pages/science-info/science-info",
         "pages/manage-address/manage-address",
         "pages/add-address/add-address",
@@ -52,12 +60,21 @@
     {
       "name": "charts",
       "root": "module/charts",
-      "pages": ["record-index/record-index"]
+      "pages": [
+        "record-index/record-index"
+      ]
     },
     {
       "name": "follow",
       "root": "module/follow",
-      "pages": ["pages/evaluation/report"]
+      "pages": [
+        "pages/evaluation/report"
+      ]
+    },
+    {
+      "name": "satisfaction",
+      "root": "module/satisfaction",
+      "pages": ["pages/questionnaire/questionnaire"]
     },
     {
       "name": "care",
@@ -68,11 +85,25 @@
         "pages/careDetail/careDetail",
         "pages/reportRecord/reportRecord"
       ]
+    },
+    {
+      "name": "order",
+      "root": "module/order",
+      "pages": [
+        "pages/select-goods/select-goods",
+        "pages/order-detail/order-detail",
+        "pages/other-detail/other-detail",
+        "pages/confirme-order/confirme-order",
+        "pages/appointment/appointment",
+        "pages/appointment-success/appointment-success"
+      ]
     }
   ],
   "preloadRule": {
     "pages/home/home": {
-      "packages": ["chats"]
+      "packages": [
+        "chats"
+      ]
     }
   },
   "window": {
@@ -84,7 +115,6 @@
     "backgroundColorTop": "#ffffff",
     "backgroundColorBottom": "#ffffff"
   },
-
   "rendererOptions": {
     "skyline": {
       "defaultDisplayBlock": true,
@@ -103,10 +133,13 @@
     "popup-user-auth": "./components/popup-user-auth/popup-user-auth",
     "popup-privacy": "./components/popup-privacy/popup-privacy"
   },
-  "requiredPrivateInfos": ["chooseAddress"],
+  "requiredPrivateInfos": [
+    "getFuzzyLocation",
+    "chooseAddress"
+  ],
   "permission": {
     "scope.userFuzzyLocation": {
       "desc": "你的位置信息将用于小程序推荐"
     }
   }
-}
+}

+ 11 - 7
miniprogram/app.ts

@@ -1,21 +1,25 @@
-import { login } from "./lib/logic"
+import { login } from "./lib/logic";
 import { appUpdate } from "./lib/wx/update";
 import { useRouteQuery } from "./utils/route-query";
-// import { Get } from "./lib/request/method";
+import i18n from "./i18n/install";
 
 // app.ts
 App<IAppOption>({
   globalData: {
-    doctorId: '',
-    patientId: '',
+    doctorId: "",
+    patientId: "",
     dictionaries: [],
   },
+  i18n: null,
   onLaunch(options: WechatMiniprogram.App.LaunchShowOption) {
+    i18n().then((value) => this.i18n = value);
     appUpdate(true);
-    const query = useRouteQuery(options.query.scene);
+    const scene = options.query.scene;
+    const query = useRouteQuery(scene);
+    wx.setStorageSync("scene", scene);
     const doctorId = query.ys;
     this.globalData.doctorId = doctorId;
-    wx.setStorageSync('doctorId', doctorId);
+    wx.setStorageSync("doctorId", doctorId);
     login();
   },
-})
+});

TEMPAT SAMPAH
miniprogram/assets/bg/bg_dialog@2x.png


TEMPAT SAMPAH
miniprogram/assets/bg/bg_home.png


TEMPAT SAMPAH
miniprogram/assets/bg/bg_user@2x.png


TEMPAT SAMPAH
miniprogram/assets/bg/health-file.bg.png


TEMPAT SAMPAH
miniprogram/assets/bg/pic_body@2x.png


TEMPAT SAMPAH
miniprogram/assets/icon/all@3x.png


TEMPAT SAMPAH
miniprogram/assets/icon/deal@3x.png


TEMPAT SAMPAH
miniprogram/assets/icon/delivery@3x.png


TEMPAT SAMPAH
miniprogram/assets/icon/icon_footprint@3x.png


TEMPAT SAMPAH
miniprogram/assets/icon/obligation@3x.png


+ 7 - 2
miniprogram/components/tabbar/tabbar.ts

@@ -1,7 +1,9 @@
+import I18nBehavior from "../../i18n/behavior";
 import props from "../../miniprogram_npm/tdesign-miniprogram/action-sheet/props";
 import Tabbar from "../../miniprogram_npm/tdesign-miniprogram/tab-bar/tab-bar";
 
 Component({
+  behaviors: [I18nBehavior],
   data: {
     tabbarHeight: 0,
     pageHeight: "100vh",
@@ -15,7 +17,7 @@ Component({
       },
       {
         value: "/module/chats/pages/index/index",
-        label: "健康管家",
+        label: "",
         icon: "app",
         path: "/module/chats/pages/index/index",
       },
@@ -26,6 +28,9 @@ Component({
         path: "/pages/mine/mine",
       },
     ],
+    i18n: {
+      common: { title: '生活助理' }
+    }
   },
   properties: {
     tabbarValue: {
@@ -63,7 +68,7 @@ Component({
       });
       wx.setStorageSync("isAnalysis", 3);
     },
-    onChange(e:any) {
+    onChange(e: any) {
       this.setData({
         value: e.detail.value,
       });

+ 1 - 1
miniprogram/components/tabbar/tabbar.wxml

@@ -1,5 +1,5 @@
 <t-tab-bar t-class="my-tabbar" value="{{value}}" bindchange="onChange" theme="tag" split="{{false}}">
   <t-tab-bar-item wx:for="{{list}}" wx:key="value" value="{{item.value}}" icon="{{item.icon}}" data-id="{{item.id}}">
-    {{item.label}}
+    {{item.label || i18n.common.title}}
   </t-tab-bar-item>
 </t-tab-bar>

+ 24 - 0
miniprogram/i18n/behavior.ts

@@ -0,0 +1,24 @@
+import request from './install';
+import { PREFIX, formatRecord, formatData } from './tool';
+export default Behavior({
+  lifetimes: {
+    created() { this._i18n_init(false); },
+  },
+  pageLifetimes: {
+    created() { this._i18n_init(true); },
+  },
+  methods: {
+    async _i18n_init(page?: boolean) {
+      const [gather, reset] = formatRecord(this.data[PREFIX]);
+      const app = getApp({ allowDefault: true });
+      if (app.i18n == null) {
+        if (reset) this.setData(reset);
+        app.i18n = await request();
+      }
+      if (app.i18n != null) {
+        const i18n = formatData(gather, (key) => app.i18n?.[key]);
+        if (i18n) this.setData(i18n);
+      } else this.setData(formatData(gather));
+    },
+  }
+});

+ 15 - 0
miniprogram/i18n/install.ts

@@ -0,0 +1,15 @@
+import { Locale_URL, ENV } from '../app.config';
+import { Get } from '../lib/request/method';
+
+import { flatRecordKeys } from './tool';
+
+let cache = null;
+
+export default async function request(url?: string) {
+  try {
+    const { locales } = await Get(url ?? `${Locale_URL}/applet.${ENV}.json`, { notTransform: true, shareRequest: true });
+    return cache = flatRecordKeys(locales), cache;
+  } catch (error) {
+    return null;
+  }
+}

+ 45 - 0
miniprogram/i18n/tool.ts

@@ -0,0 +1,45 @@
+export const PREFIX = 'i18n';
+export function flatRecordKeys(
+  record: Record<string, any>,
+  parent: string[] = [],
+  result: Record<string, string> = {}
+) {
+  if (record && typeof record === 'object') {
+    for (const [key, value] of Object.entries(record)) {
+      if (Array.isArray(value)) value.forEach((v, i) => {
+        const keys = [...parent, `${key}[${i}]`];
+        flatRecordKeys(v, keys, result);
+      }); else {
+        const keys = [...parent, key];
+        flatRecordKeys(value, keys, result);
+      }
+    }
+  } else if (['string', 'boolean', 'number'].includes(typeof record)) result[parent.join('.')] = record;
+  return result;
+}
+
+type FormatData = { key: string; value: string; }[];
+
+export function formatRecord(record: Record<string, any> | string[]) {
+  if (Array.isArray(record)) return [record.map(key => ({ key, value: '' })), null] as const;
+
+  let reset: FormatData = [];
+  const gather: FormatData = [];
+
+  const result = flatRecordKeys(record);
+  for (let [key, value] of Object.entries(result)) {
+    if (typeof value === 'string') {
+      value = value?.replace(/^_/, '').replace(/\\_/, '_') ?? ''
+      if (value) reset.push({ key, value });
+    }
+    gather.push({ key, value });
+  }
+  return [gather, formatData(reset)] as const;
+}
+
+export function formatData(data?: FormatData, get?: (key: string) => string) {
+  return data?.reduce((set, { key, value }) => {
+    const _key = PREFIX ? `${PREFIX}.${key}` : key;
+    return (set[_key] = get?.(key) ?? value ?? '', set);
+  }, {} as Record<string, string>) ?? null;
+}

+ 4 - 2
miniprogram/lib/request/create.ts

@@ -4,8 +4,8 @@ import { getAccountInfoSync } from "../wx/open-api";
 
 const shareRequestCache = new Map<string, IRequestData<any>>();
 
-const miniProgram = getAccountInfoSync();
 
+const miniProgram = getAccountInfoSync();
 export function createRequest(option: IRequestCreateConfig) {
   const { baseURL } = option;
 
@@ -16,6 +16,7 @@ export function createRequest(option: IRequestCreateConfig) {
       params,
       header,
       meta,
+      notTransform = false,
       transform = (params: any) => params,
       shareRequest,
       ..._config
@@ -44,10 +45,10 @@ export function createRequest(option: IRequestCreateConfig) {
     header["patientId"] = wx.getStorageSync("patientId") ?? "";
     // header['patientId'] = '783';
     header["doctorId"] = wx.getStorageSync("doctorId") ?? "";
+    header["scene"] = wx.getStorageSync("scene") ?? "";
     header["appId"] = miniProgram.appId ?? "";
     header["version"] = miniProgram.version ?? "";
     header["env"] = miniProgram.envVersion ?? "";
-
     const promise = _request<IRequestData<T>>({
       url: /https?\:\/\//.test(url) ? url : `${baseURL}${url}`,
       header,
@@ -55,6 +56,7 @@ export function createRequest(option: IRequestCreateConfig) {
       ..._config,
     }).then((response) => {
       if (response.statusCode === 200) {
+        if (notTransform) return response.data;
         if (response.data.code === 200 && response.data.success !== false) {
           const data =
             Array.isArray(response.data.rows) && response.data.total != null

TEMPAT SAMPAH
miniprogram/module/article/assets/servicePag.png


+ 5 - 0
miniprogram/module/article/components/education-card/education-card.json

@@ -0,0 +1,5 @@
+{
+  "renderer": "skyline",
+  "component": true,
+  "usingComponents": {}
+}

+ 58 - 0
miniprogram/module/article/components/education-card/education-card.scss

@@ -0,0 +1,58 @@
+/* module/article/components/science-card/science-card.wxss */
+.list-card-item {
+  position: relative;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  min-height: 125px;
+  padding: 10px 0;
+  box-sizing: border-box;
+  padding-left: 10px;
+
+  .cover {
+    flex: none;
+    width: 100px;
+    height: 100px;
+    margin-right: 16px;
+    border-radius: 12px;
+  }
+
+  .content {
+    flex: auto;
+    display: flex;
+    flex-direction: column;
+    height: 100px;
+    justify-content: space-evenly;
+  }
+
+  .title {
+    font-size: 16px;
+    // color: #fff;
+    color: #191919;
+    font-weight: bold;
+    // margin-bottom: 10px;
+  }
+
+  .description {
+    font-size: 14px;
+    color: #A2AFB2;
+
+    &-2 {
+      font-size: 14px;
+      // color: #34A76B;
+      // color: #8C8C8C;
+      color: #333333;
+    }
+  }
+
+  .border-gradient {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 1px;
+    transform: translateY(-1px);
+    // background: linear-gradient(90deg, rgba(15, 34, 38, 0) 0%, var(--primary-color, #E7E7E7) 50%, rgba(15, 34, 38, 0) 100%);
+    background: linear-gradient(90deg, rgba(15, 34, 38, 0) 0%, #E7E7E7 50%, rgba(15, 34, 38, 0) 100%);
+  }
+}

+ 42 - 0
miniprogram/module/article/components/education-card/education-card.ts

@@ -0,0 +1,42 @@
+// module/article/components/science-card/science-card.ts
+// import { HOST } from "../../../../app.config";
+import { Get } from "../../../../lib/request/method";
+Component({
+  properties: {
+    item: { type: Object, value: {} },
+    index: { type: Number, value: 0 },
+    replace: { type: Boolean, value: false },
+  },
+  data: {},
+  methods: {
+    async toInfoPage(event: WechatMiniprogram.TouchEvent) {
+      const id = event.currentTarget.id;
+      if (this.data.replace) {
+        wx.redirectTo({
+          url: `/module/article/pages/science-info/science-info?id=${id}`,
+        });
+      } else {
+        const id = event.currentTarget.id;
+        // const url = `https://${HOST}/admin/editor/preview.html?popularScienceArticleId=${id}`;
+        try {
+          const res = await Get(`/psarticle/clickPsaNotice`, {
+            params: { noticeSendRecordId: id },
+          });
+          const url = res?.data;
+          const item = {
+            url,
+          };
+          wx.navigateTo({
+            url: `/module/article/pages/science-info/science-info`,
+          }).then((res) => {
+            res.eventChannel.emit("load", item);
+          });
+        } catch (error) {
+          console.log(error);
+        }
+        //   wx.navigateTo({ url: `/module/article/pages/science-info/science-info?id=${id}` })
+        //     .then(res => { res.eventChannel.emit('load', this.data.item) })
+      }
+    },
+  },
+});

+ 12 - 0
miniprogram/module/article/components/education-card/education-card.wxml

@@ -0,0 +1,12 @@
+<!--module/article/components/science-card/science-card.wxml-->
+<view class="list-card-item" id="{{item.noticeSendRecordId}}" bind:tap="toInfoPage">
+  <view class="border-gradient" wx:if="{{index}}">
+    <!--边框-->
+  </view>
+  <!--<image class="cover" src="{{item.briefImg}}" mode="aspectFill" fade-in /> -->
+  <view class="content">
+    <text class="title" max-lines="3" overflow="ellipsis">{{item.noticeName}}</text>
+     <text class="description-2" max-lines="1" overflow="ellipsis" wx:if="{{item.createBy}}">推送医生:{{item.createBy}}</text>
+    <text class="description-2" max-lines="1" overflow="ellipsis" wx:if="{{item.sendTime}}">推送时间:{{item.sendTime}}</text>
+  </view>
+</view>

+ 9 - 5
miniprogram/module/article/components/science-card/science-card.scss

@@ -5,8 +5,9 @@
   flex-direction: row;
   align-items: center;
   min-height: 125px;
-  padding: 12px 0;
+  padding: 10px 0;
   box-sizing: border-box;
+  padding-left: 10px;
 
   .cover {
     flex: none;
@@ -27,7 +28,9 @@
   .title {
     font-size: 16px;
     // color: #fff;
-    color:#191919 ;
+    color: #191919;
+    font-weight: bold;
+    margin-bottom: 10px;
   }
 
   .description {
@@ -35,9 +38,10 @@
     color: #A2AFB2;
 
     &-2 {
-      font-size: 12px;
+      font-size: 14px;
       // color: #34A76B;
-      color: #8C8C8C;
+      // color: #8C8C8C;
+      color: #333333;
     }
   }
 
@@ -49,6 +53,6 @@
     height: 1px;
     transform: translateY(-1px);
     // background: linear-gradient(90deg, rgba(15, 34, 38, 0) 0%, var(--primary-color, #E7E7E7) 50%, rgba(15, 34, 38, 0) 100%);
-    background: linear-gradient(90deg, rgba(15, 34, 38, 0) 0%,  #E7E7E7 50%, rgba(15, 34, 38, 0) 100%);
+    background: linear-gradient(90deg, rgba(15, 34, 38, 0) 0%, #E7E7E7 50%, rgba(15, 34, 38, 0) 100%);
   }
 }

+ 33 - 3
miniprogram/module/article/components/science-card/science-card.ts

@@ -1,4 +1,6 @@
 // module/article/components/science-card/science-card.ts
+// import { HOST } from "../../../../app.config";
+import { Get } from "../../../../lib/request/method";
 Component({
   properties: {
     item: { type: Object, value: {} },
@@ -7,13 +9,41 @@ Component({
   },
   data: {},
   methods: {
-    toInfoPage(event: WechatMiniprogram.TouchEvent) {
+    async toInfoPage(event: WechatMiniprogram.TouchEvent) {
       const id = event.currentTarget.id;
       if (this.data.replace) {
         wx.redirectTo({ url: `/module/article/pages/science-info/science-info?id=${id}` });
       } else {
-        wx.navigateTo({ url: `/module/article/pages/science-info/science-info?id=${id}` })
-          .then(res => { res.eventChannel.emit('load', this.data.item) })
+        const id = event.currentTarget.id;
+        // const url = `https://${HOST}/admin/editor/preview.html?popularScienceArticleId=${id}`;
+      
+        try {
+          const res = await Get(`/psarticle/clickPsarticle`, {
+            params: { popularScienceArticleId: id },
+          });
+          const url = res?.data;
+          const item = {
+            url,
+          };
+          wx.navigateTo({
+            url: `/module/article/pages/science-info/science-info`,
+          }).then((res) => {
+            res.eventChannel.emit("load", item);
+          });
+        } catch (error) {
+          console.log(error);
+        }
+        // const url = `https://${HOST}/admin/editor/preview.html?popularScienceArticleId=${id}`;
+        // const item = {
+        //   url,
+        // };
+        // wx.navigateTo({
+        //   url: `/module/article/pages/science-info/science-info?url=${url}`,
+        // }).then((res) => {
+        //   res.eventChannel.emit("load", item);
+        // });
+      //   wx.navigateTo({ url: `/module/article/pages/science-info/science-info?id=${id}` })
+      //     .then(res => { res.eventChannel.emit('load', this.data.item) })
       }
     }
   }

+ 2 - 1
miniprogram/module/article/components/science-card/science-card.wxml

@@ -6,6 +6,7 @@
   <image class="cover" src="{{item.briefImg}}" mode="aspectFill" fade-in />
   <view class="content">
     <text class="title" max-lines="3" overflow="ellipsis">{{item.title}}</text>
-    <text class="description-2" max-lines="1" overflow="ellipsis" wx:if="{{item.readVolume}}">{{item.readVolume}}人阅读</text>
+     <text class="description-2" max-lines="1" overflow="ellipsis" wx:if="{{item.pushTime}}">发布时间:{{item.pushTime}}</text>
+    <text class="description-2" max-lines="1" overflow="ellipsis" wx:if="{{item.readVolume}}">阅读量:{{item.readVolume}}</text>
   </view>
 </view>

+ 5 - 0
miniprogram/module/article/components/waterfall-card/waterfall-card.json

@@ -0,0 +1,5 @@
+{
+  "renderer": "skyline",
+  "component": true,
+  "usingComponents": {}
+}

+ 70 - 0
miniprogram/module/article/components/waterfall-card/waterfall-card.scss

@@ -0,0 +1,70 @@
+/* module/article/components/waterfall-card/waterfall-card.wxss */
+.waterfall-card-item {
+  display: flex;
+  flex-direction: column;
+  background: #fff;
+  border-radius: 12px;
+  overflow: hidden;
+  margin-bottom: 0;
+  box-sizing: border-box;
+  width: 98%;
+
+  .cover-wrapper {
+    width: 100%;
+    min-height: 120px;
+    max-height: 260px;
+    overflow: hidden;
+    border-radius: 12px 12px 0 0;
+    position: relative;
+  }
+
+  .cover {
+    width: 100%;
+    height: auto;
+    min-height: 120px;
+    max-height: 260px;
+    display: block;
+    /* 图片等宽,高度自适应,最小高度120px,最大高度260px,保持原始宽高比,实现不等高效果 */
+  }
+
+  .content {
+    display: flex;
+    flex-direction: column;
+    padding: 12px;
+    box-sizing: border-box;
+    flex: 1;
+  }
+
+  .title {
+    font-size: 15px;
+    color: #191919;
+    line-height: 1.5;
+    margin-bottom: 5px;
+    word-break: break-all;
+    display: -webkit-box;
+    -webkit-box-orient: vertical;
+    -webkit-line-clamp: 3;
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
+
+  .meta-info {
+    display: flex;
+    flex-direction: column;
+    gap: 4px;
+    font-size: 12px;
+    color: #8C8C8C;
+    margin-top: auto;
+  }
+
+  .date {
+    font-size: 12px;
+    color: #8C8C8C;
+    margin-bottom: 5px;
+  }
+
+  .read-count {
+    font-size: 12px;
+    color: #8C8C8C;
+  }
+}

+ 34 - 0
miniprogram/module/article/components/waterfall-card/waterfall-card.ts

@@ -0,0 +1,34 @@
+// module/article/components/waterfall-card/waterfall-card.ts
+// import { HOST } from "../../../../app.config";
+import { Get } from "../../../../lib/request/method";
+Component({
+  properties: {
+    item: { type: Object, value: {} },
+    index: { type: Number, value: 0 },
+    replace: { type: Boolean, value: false },
+  },
+  data: {},
+  methods: {
+    async toInfoPage(event: WechatMiniprogram.TouchEvent) {
+      const id = event.currentTarget.id;
+      // const url = `https://${HOST}/admin/editor/preview.html?popularScienceArticleId=${id}`;
+    
+      try {
+        const res = await Get(`/psarticle/clickPsarticle`, {
+          params: { popularScienceArticleId: id },
+        });
+        const url = res?.data;
+        const item = {
+          url,
+        };
+        wx.navigateTo({
+          url: `/module/article/pages/science-info/science-info`,
+        }).then((res) => {
+          res.eventChannel.emit("load", item);
+        });
+      } catch (error) {
+        console.log(error);
+      }
+    },
+  },
+});

+ 13 - 0
miniprogram/module/article/components/waterfall-card/waterfall-card.wxml

@@ -0,0 +1,13 @@
+<!--module/article/components/waterfall-card/waterfall-card.wxml-->
+<view class="waterfall-card-item" id="{{item.id}}" bind:tap="toInfoPage">
+  <view class="cover-wrapper">
+    <image class="cover" src="{{item.briefImg}}" mode="widthFix" fade-in lazy-load />
+  </view>
+  <view class="content">
+    <text class="title">{{item.title}}</text>
+    <view class="meta-info">
+      <text class="date" wx:if="{{item.pushTime}}">{{item.pushTime}}</text>
+      <text class="read-count">阅读量 {{item.readVolume}}</text>
+    </view>
+  </view>
+</view>

+ 2 - 1
miniprogram/module/article/pages/confirm-receiving/confirm-receiving.json

@@ -4,6 +4,7 @@
   "usingComponents": {
     "t-button": "tdesign-miniprogram/button/button",
     "t-navbar": "tdesign-miniprogram/navbar/navbar",
-    "t-icon": "tdesign-miniprogram/icon/icon"
+    "t-icon": "tdesign-miniprogram/icon/icon",
+    "t-checkbox": "tdesign-miniprogram/checkbox/checkbox"
   }
 }

+ 17 - 6
miniprogram/module/article/pages/confirm-receiving/confirm-receiving.scss

@@ -1,10 +1,11 @@
 @import "../../../../themes/page.scss";
 @import "../../searc-list.scss";
-/* module/diet/pages/diet-info/diet-info.wxss */
+/* module/article/pages/confirm-receiving/confirm-receiving.scss */
 
 
 .confirm-receive-container {
-  background: #f7f8fa;
+  // background: #f7f8fa;
+  background: white;
   display: flex;
   flex-direction: column;
   align-items: stretch;
@@ -17,23 +18,33 @@
   display: flex;
   background: #fff;
   border-radius: 20rpx;
-  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
+  // box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
   margin: 0 24rpx 24rpx 24rpx;
   padding: 24rpx;
-  align-items: flex-start;
+  align-items: center;
   gap: 24rpx;
 }
 .goods-img {
-  width: 120rpx;
-  height: 120rpx;
+  width: 140rpx;
+  height: 140rpx;
   border-radius: 12rpx;
   object-fit: cover;
   margin-right: 16rpx;
+
 }
 .goods-info {
   flex: 1;
   display: flex;
   flex-direction: column;
+  justify-content: space-between;
+  margin-left: 10rpx;
+  min-height: 115rpx;
+}
+
+.goods-checkbox {
+  flex-shrink: 0;
+  display: flex;
+  align-items: center;
   justify-content: center;
 }
 .goods-title {

+ 78 - 58
miniprogram/module/article/pages/confirm-receiving/confirm-receiving.ts

@@ -1,74 +1,94 @@
 import PageContainerBehavior from "../../../../core/behavior/page-container.behavior";
-import tickleBehavior, { getTickleContext } from "../../../../core/behavior/tickle.behavior";
-import { getOrderDetailMethod, orderConfirmMethod } from "../../request";
-// module/diet/pages/delivery-address/delivery-address.ts
+import tickleBehavior from "../../../../core/behavior/tickle.behavior";
+import { orderConfirmReceiptMethod } from "../../request";
+// module/article/pages/confirm-receiving/confirm-receiving.ts
 Page({
   behaviors: [
     PageContainerBehavior,
     tickleBehavior,
   ],
-  async onLoad(options:any) {
-    console.log("onLoad",options)
-    if(options.orderId){
-      this.setData({ id: options.orderId });
-      await this.load(options.orderId);
+  data: {
+    patientConditioningRecordId: '',
+    orderId: '',
+    goodsList: [] as Array<{
+      id: number;
+      name: string;
+      description: string;
+      image: string;
+      checked: boolean;
+    }>
+  },
+  async onLoad(options: any) {
+    console.log("onLoad", options)
+    if (options.patientConditioningRecordId) {
+      this.setData({ patientConditioningRecordId: options.patientConditioningRecordId });
+    }
+    if (options.goodsList) {
+      const goodsList = JSON.parse(decodeURIComponent(options.goodsList));
+      // 为每个商品添加 checked 属性,默认全部选中
+      const goodsListWithChecked = goodsList.map((item: any) => ({
+        ...item,
+        checked: true
+      }));
+      this.setData({ goodsList: goodsListWithChecked });
     }
-  
+    console.log("goodsList", this.data.goodsList)
   },
   properties: {
   },
-  data: {
-    id: '',
-    goodsList: []
+  copyExpressNo(e: any) {
+    wx.setClipboardData({
+      data: e.currentTarget.dataset.no,
+      success: () => wx.showToast({ title: '已复制', icon: 'none' })
+    });
+  },
+  onCancel() {
+    // 关闭弹窗或返回
+    wx.navigateBack();
   },
-    async load(id:string) {
-      wx.showLoading({ title: '加载中' });
-      try {
-        const res = await getOrderDetailMethod(id);
-        console.log("res",res)  
-        if(res && res.data && res.data.items && res.data.items.length > 0){
-          this.setData({ goodsList: res.data.items });
-        }else{
-          this.setData({ goodsList: [] });
+  // 商品复选框变化
+  onGoodsCheckChange(e: any) {
+    const { index } = e.currentTarget.dataset;
+    const checked = e.detail.checked;
+    const goodsList = this.data.goodsList;
+    goodsList[index].checked = checked;
+    this.setData({ goodsList });
+  },
+  // 确认收货逻辑
+  async onConfirmReceiving() {
+    // 获取选中的商品id数组
+    const selectedIds = this.data.goodsList
+      .filter((item: any) => item.checked)
+      .map((item: any) => item.id);
+
+    if (selectedIds.length === 0) {
+      wx.showToast({ title: '请至少选择一个商品', icon: 'none' });
+      return;
+    }
+    console.log(selectedIds, "selectedIds======选中的商品id")
+    let that = this;
+    wx.showModal({
+      title: '提示',
+      content: '确认收货后,订单将无法修改,请确认无误后再进行操作',
+      success: (res) => {
+        if (res.confirm) {
+          that.confirmReceiving(selectedIds);
         }
-        console.log("goodsList",this.data.goodsList)
-      } catch (error:any) {
-        getTickleContext.call(this).showWarnMessage(error.errMsg);
       }
-      wx.hideLoading();
-    },
-    copyExpressNo(e:any) {
-      wx.setClipboardData({
-        data: e.currentTarget.dataset.no,
-        success: () => wx.showToast({ title: '已复制', icon: 'none' })
-      });
-    },
-    onCancel() {
-      // 关闭弹窗或返回
-      wx.navigateBack();
-    },
-    // 确认收货逻辑
-    async onConfirmReceiving() {
-      let that = this;
-      wx.showModal({
-        title: '提示',
-        content: '确认收货后,订单将无法修改,请确认无误后再进行操作',
-        success: (res) => {
-          if (res.confirm) {
-            that.confirmReceiving();
-          }
-        }
-      });
-    },
-    async confirmReceiving() {
-      try {
-        await orderConfirmMethod(this.data.id);
-        // wx.showToast({ title: '确认收货成功', icon: 'success' });
-        wx.navigateTo({
-          url: "/module/article/pages/success-page/success-page?title=确认收货成功",
+    });
+  },
+  async confirmReceiving(selectedIds: number[]) {
+    try {
+      await orderConfirmReceiptMethod(this.data.patientConditioningRecordId, selectedIds);
+      wx.showToast({ title: '收货成功', icon: 'success' });
+      setTimeout(() => {
+        wx.redirectTo({
+          url: "/module/article/pages/success-page/success-page?title=收货成功",
         });
-      } catch (error:any) {
-        getTickleContext.call(this).showWarnMessage(error.errMsg);
-      }
+      }, 1000);
+
+    } catch (error: any) {
+      wx.showToast({ title: error.errMsg || '收货失败', icon: 'none' });
     }
+  }
 })

+ 10 - 18
miniprogram/module/article/pages/confirm-receiving/confirm-receiving.wxml

@@ -1,25 +1,17 @@
-<!--module/diet/pages/delivery-address/delivery-address.wxml-->
+<!--module/article/pages/confirm-receiving/confirm-receiving.wxml-->
 <t-navbar title="确认收货" left-arrow />
 <scroll-view class="page-scroll__container" type="list" scroll-y style="{{containerStyle}}">
 <view class="confirm-receive-container">
   <!-- 商品物流卡片列表 -->
   <view class="order-goods-list">
-    <view class="goods-card" wx:for="{{goodsList}}" wx:key="id" wx:if="{{item.conditioningProgramDetail.isDelivery==='Y'}}">
-      <image class="goods-img" src="{{item.conditioningProgramDetail.photo}}" mode="aspectFill" />
+    <view class="goods-card" wx:for="{{goodsList}}" wx:key="id" wx:for-index="index">
+      <image class="goods-img" src="{{item.image}}" mode="aspectFill" />
       <view class="goods-info">
-        <view class="goods-title">{{item.conditioningProgramDetail.name}}</view>
-        <view class="goods-count">× {{item.totalMeasure}}</view>
-        <view class="goods-express" wx:if="{{false}}">
-          <text class="express-label">快递:</text>
-          <block wx:if="{{item.expressNo}}">
-            <text class="express-no" bindtap="copyExpressNo" data-no="{{item.expressNo}}">
-              {{item.expressNo}}
-            </text>
-          </block>
-          <block wx:else>
-            <text class="express-none">暂无物流信息</text>
-          </block>
-        </view>
+        <view class="goods-title">{{item.name}}</view>
+        <view class="goods-count">× {{item.description}}</view>
+      </view>
+      <view class="goods-checkbox">
+        <t-checkbox checked="{{item.checked}}" bind:change="onGoodsCheckChange" data-index="{{index}}" style="padding:0" />
       </view>
     </view>
   </view>
@@ -32,8 +24,8 @@
 
   <!-- 底部按钮 -->
   <view class="confirm-footer">
-    <t-button theme="default" class="footer-btn" bindtap="onCancel">取消</t-button>
-    <t-button theme="primary" class="footer-btn" bindtap="onConfirmReceiving">确</t-button>
+    <!-- <t-button theme="default" class="footer-btn" bindtap="onCancel">取消</t-button> -->
+    <t-button theme="primary" class="footer-btn" bindtap="onConfirmReceiving">确</t-button>
   </view>
 </view>
 </scroll-view>

+ 7 - 2
miniprogram/module/article/pages/diet-list/diet-list.ts

@@ -1,9 +1,11 @@
 import PageContainerBehavior from "../../../../core/behavior/page-container.behavior";
+import I18nBehavior from "../../../../i18n/behavior";
 import tickleBehavior, { getTickleContext } from "../../../../core/behavior/tickle.behavior";
 import { getDietListMethod } from "../../request";
 // module/diet/pages/diet-list/diet-list.ts
 Component({
   behaviors: [
+    I18nBehavior,
     PageContainerBehavior,
     tickleBehavior,
   ],
@@ -17,6 +19,9 @@ Component({
     classify: { type: String, value: '' },
   },
   data: {
+    i18n: {
+      home: { tea: '茶', tonic: "菜谱" }
+    },
     keyword: '',
     searchInputProps: {
       placeholderStyle: 'color: #929292;',
@@ -30,8 +35,8 @@ Component({
     page: 1, size: 20, total: 0, loading: false, isLastPage: false,
   },
   observers: {
-    'classify'(value) {
-      const ref = { tonic: '药膳查询', tea: '中医茶饮' } as any;
+    'classify,i18n.home'(value, i18n) {
+      const ref = { tonic: i18n.tonic, tea: i18n.tea } as any;
       this.setData({ title: ref[value] || '' })
     },
     'page,size,total'(page, size, total) {

+ 7 - 0
miniprogram/module/article/pages/education-list/education-list.json

@@ -0,0 +1,7 @@
+{
+  "renderer": "skyline",
+  "component": true,
+  "usingComponents": {
+    "education-card": "../../components/education-card/education-card"
+  }
+}

+ 18 - 0
miniprogram/module/article/pages/education-list/education-list.scss

@@ -0,0 +1,18 @@
+@import '../../../../themes/page.scss';
+@import '../../searc-list.scss';
+
+/* module/diet/pages/science-list/science-list.wxss */
+.list-wrapper {
+  position: relative;
+  
+  &::after {
+    content: '';
+    position: fixed;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    height: 40px;
+    background: linear-gradient(to bottom, rgba(29, 111, 246, 0), rgba(29, 111, 246, 0.1));
+    pointer-events: none;
+  }
+}

+ 83 - 0
miniprogram/module/article/pages/education-list/education-list.ts

@@ -0,0 +1,83 @@
+import PageContainerBehavior from "../../../../core/behavior/page-container.behavior";
+import tickleBehavior, { getTickleContext } from "../../../../core/behavior/tickle.behavior";
+import { getScienceNoticeListMethod } from "../../request";
+// module/diet/pages/science-list/science-list.ts
+Component({
+  behaviors: [
+    PageContainerBehavior,
+    tickleBehavior,
+  ],
+  lifetimes: {
+    attached() {
+      wx.showLoading({ title: '加载中' });
+      this.load();
+    }
+  },
+  properties: {
+    classify: { type: String, value: '' },
+  },
+  data: {
+    keyword: '',
+    searchInputProps: {
+      placeholderStyle: 'color: #929292;',
+      // cursorColor: '#1D6FF6',
+      // iconColor: '#1D6FF6',
+      cursorColor: '#929292',
+      iconColor: '#929292',
+      focus: false,
+      placeholder: '输入搜索名称',
+    },
+    title: '',
+    dataset: [] as AnyObject[],
+    page: 1, size: 20, total: 0, loading: false, isLastPage: false,
+  },
+  observers: {
+    'classify'(value) {
+      const ref = { tonic: '药膳查询', tea: '中医茶饮' } as any;
+      this.setData({ title: ref[value] || '' })
+    },
+    'page,size,total'(page, size, total) {
+      this.setData({ isLastPage: page * size >= total })
+    }
+  },
+  methods: {
+    async load() {
+      try {
+        const { data, total } = await getScienceNoticeListMethod(1, this.data.size, {
+          keyword: this.data.keyword,
+        });
+        this.setData({ dataset: data, total });
+      } catch (error) {
+        getTickleContext.call(this).showWarnMessage(error.errMsg);
+      }
+      wx.hideLoading();
+    },
+    async loadMore() {
+      if (this.data.isLastPage || this.data.loading) return;
+      this.setData({ loading: true });
+      try {
+        const _page = this.data.page + 1;
+        const { data, total } = await getScienceNoticeListMethod(_page, this.data.size, {
+          keyword: this.data.keyword,
+        });
+        this.setData({ dataset: [...this.data.dataset, ...data], total, page: _page });
+      } catch (error) {
+        getTickleContext.call(this).showWarnMessage(error.errMsg);
+      }
+      this.setData({ loading: false });
+    },
+    onConfirmSearchInput(event: WechatMiniprogram.InputConfirm) {
+      const _keyword = this.data.keyword;
+      this.setData({ keyword: event.detail.value });
+      wx.showLoading({ title: '搜索中' });
+      this.load().catch(() => { this.setData({ keyword: _keyword }) });
+    },
+    toInfoPage(event: WechatMiniprogram.TouchEvent) {
+      const id = event.currentTarget.id;
+      wx.navigateTo({ url: `/module/article/pages/science-info/science-info?id=${id}` })
+        .then(res => {
+          res.eventChannel.emit('load', this.data.dataset.find(item => item.id.toString() === id))
+        })
+    }
+  }
+})

+ 14 - 0
miniprogram/module/article/pages/education-list/education-list.wxml

@@ -0,0 +1,14 @@
+<!--module/diet/pages/science-list/science-list.wxml-->
+<t-navbar title="宣教通知" left-arrow />
+<view style="flex: auto;overflow: hidden;">
+  <scroll-view class="list-wrapper scrollable" scroll-y type="custom" enhanced bind:scrolltolower="loadMore">
+    <list-builder list="{{dataset}}" child-height="125">
+      <education-card slot:item slot:index item="{{item}}" index="{{index}}"></education-card>
+    </list-builder>
+    <view class="tips" wx:if="{{loading}}">加载中...</view>
+    <view class="tips" wx:elif="{{isLastPage}}">没有更多的了</view>
+  </scroll-view>
+</view>
+
+<view style="height: {{container.safeBottomOffset}}px;flex: none;"></view>
+<t-message id="{{$messageId}}" />

+ 1 - 1
miniprogram/module/article/pages/foot-print/foot-print.scss

@@ -12,7 +12,7 @@
   align-items: flex-start;
   background: #fff;
   border-radius: 16rpx;
-  margin: 20rpx 24rpx 0 24rpx;
+  margin: 10rpx 14rpx 0 24rpx;
   padding: 20rpx 0;
   box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.03);
   gap: 20rpx;

+ 20 - 10
miniprogram/module/article/pages/foot-print/foot-print.ts

@@ -3,6 +3,7 @@ import tickleBehavior, {
   getTickleContext,
 } from "../../../../core/behavior/tickle.behavior";
 import { getFootPrintListMethod } from "../../request";
+import { Get } from "../../../../lib/request/method";
 Page({
   behaviors: [PageContainerBehavior, tickleBehavior],
   lifetimes: {
@@ -10,20 +11,30 @@ Page({
   },
   properties: {},
   data: {
-    articleList: [],
+    articleList: [] as any[],
   },
-  goDetail(e: any) {
+  async goDetail(e: any) {
     const type = e.currentTarget.dataset.type;
     const item = e.currentTarget.dataset.item;
-    // type 1 中医科普 2 药膳 4 茶饮
+    // type ,1-健康宣教 2-药膳 3-膳食 4-茶饮
     // 药膳详情
-    console.log(type, "详情");
     if (type === "1") {
-      wx.navigateTo({
-        url: `/module/article/pages/science-info/science-info?id=${item.sourceId}`,
-      }).then((res) => {
-        res.eventChannel.emit("load", item);
-      });
+      try {
+        const res: any = await Get(`/psarticle/clickPsarticle`, {
+          params: { popularScienceArticleId: item.sourceId },
+        });
+        const url = res?.data;
+        const detailPayload = {
+          url,
+        };
+        wx.navigateTo({
+          url: `/module/article/pages/science-info/science-info`,
+        }).then((res) => {
+          res.eventChannel.emit("load", detailPayload);
+        });
+      } catch (error) {
+        console.log(error);
+      }
     } else if (type === "2" || type === "4") {
       wx.navigateTo({
         url: `/module/article/pages/diet-info/diet-info?id=${item.sourceId}`,
@@ -37,7 +48,6 @@ Page({
     try {
       const res = await getFootPrintListMethod();
       const list = res?.data?.data ?? [];
-      console.log(list, "list",res.data.data);
       this.setData({ articleList: Array.isArray(list) ? list : [] });
     } catch (error: any) {
       getTickleContext.call(this).showWarnMessage(error.errMsg);

+ 37 - 56
miniprogram/module/article/pages/manage-address/manage-address.ts

@@ -40,29 +40,36 @@ Page({
       liaison: address.liaison,
       phone: address.phone,
     };
-    console.log(id, data, "更新订单地址");
-    const res = await orderPayAddressMethod(id, data);
-    console.log(res, "更新订单地址");
+    await orderPayAddressMethod(id, data);
   },
   // 选择地址
   async selectAddress(e: any) {
     const item = e.currentTarget.dataset.item;
     const type = wx.getStorageSync("type");
     const orderId = wx.getStorageSync("orderId");
-    console.log(type, "页面切换type", item);
     const pages = getCurrentPages();
-    console.log(pages, "pages");
+
+
+    const findTargetPageIndex = (targetRoute: string) => {
+      for (let i = pages.length - 1; i >= 0; i--) {
+        if (pages[i].route === targetRoute) {
+          return pages.length - 1 - i; // 需要返回的层级数
+        }
+      }
+      return -1;
+    };
+
     if (type === "orderDetail") {
       // 订单详情 切换地址
-      const orderPage = pages.find(
-        (page) =>
-          page.route === "module/article/pages/order-detail/order-detail"
-      );
-      console.log(orderPage, "orderPage");
       try {
         await this.updataOrderAddress(orderId, item);
-        if (orderPage) {
-          // orderPage.setData({ selectedAddress: item });
+        const targetRoute = "module/order/pages/order-detail/order-detail";
+        const delta = findTargetPageIndex(targetRoute);
+        if (delta > 0) {
+          // 返回到目标页面
+          wx.navigateBack({ delta });
+        } else {
+          // 如果找不到目标页面,返回到上一页
           wx.navigateBack();
         }
       } catch (error: any) {
@@ -70,55 +77,33 @@ Page({
       }
     } else if (type === "orderList") {
       // 订单列表 切换地址
-      const findPage = pages.find(
-        (page) => page.route === "module/article/pages/order-list/order-list"
-      );
-      console.log(findPage, "orderPage", orderId,item);
       try {
         await this.updataOrderAddress(orderId, item);
-        if (findPage) {
-          // orderPage.setData({ selectedAddress: item });
-          console.log("orderPage8888");
-          // wx.navigateBack({
-            // delta: 1,
-            // fail: () => {
-            //   console.log("orderPage9999");
-              wx.redirectTo({
-                url: "/"+findPage.route+"?tab=pending",
-              });
-            // },
-          // });
-        
+        const targetRoute = "module/article/pages/order-list/order-list";
+        const delta = findTargetPageIndex(targetRoute);
+        if (delta > 0) {
+          wx.navigateBack({ delta });
+        } else {
+          wx.navigateBack();
         }
       } catch (error: any) {
         getTickleContext.call(this).showWarnMessage(error.errMsg);
       }
+    } else if (type === "confirmeOrder") {
+      wx.setStorageSync('selectedAddress', item);
+      // 确认订单页面 切换地址
+      const confirmeOrderPage = pages.find(
+        (page: any) =>
+          page.route === "module/order/pages/confirme-order/confirme-order"
+      );
+      if (confirmeOrderPage) {
+        wx.redirectTo({
+          url: "/" + confirmeOrderPage.route + "?isDefault=1",
+        });
+      }
     } else {
       // 其他页面 切换地址
-      console.log("其他页面");
     }
-    // 获取上一页的数据
-    // const pages = getCurrentPages();
-    // if (pages.length > 1) {
-    //   const prevPage = pages[pages.length - 2];
-    //   console.log(prevPage.route, "上一页");
-    //   // 订单详情 切换地址
-    //   if (prevPage.route === "module/article/pages/order-detail/order-detail") {
-    //     // 设置上一页的数据
-    //     prevPage.setData({ selectedAddress: item });
-    //     wx.navigateBack();
-    //     // 订单列表切换地址
-    //   } else if (
-    //     prevPage.route === "module/article/pages/order-list/order-list"
-    //   ) {
-    //     prevPage.setData({
-    //       selectedAddress: e.currentTarget.dataset.address,
-    //     });
-    //     wx.navigateBack();
-    //   } else {
-    //     console.log("其他页面不返回");
-    //   }
-    //   }
   },
   // 搜索
   onSearchChange(e: any) {
@@ -160,7 +145,6 @@ Page({
     try {
       // 获取所有的地址列表
       const res = await getAddressListMethod("", "");
-      console.log(res, "res====");
       if (res && res.length > 0) {
         res.forEach((item: any) => {
           item.fullAddress = `${item.provinceName} ${item.cityName} ${item.areaName} ${item.detailAddress}`;
@@ -172,7 +156,6 @@ Page({
           allAddressList: res, // 原始
         });
       } else {
-        console.log(111);
         this.setData({
           noResult: true,
           addressList: [],
@@ -188,7 +171,6 @@ Page({
   onImportWechatAddress() {
     wx.chooseAddress({
       success: (res) => {
-        console.log(res, "导入微信地址");
         // 拼接参数
         const params = encodeURIComponent(
           JSON.stringify({
@@ -202,7 +184,6 @@ Page({
             detailAddress: res.detailInfoNew,
           })
         );
-        console.log(params, "微信地址的拼接");
         wx.navigateTo({
           url: `/module/article/pages/add-address/add-address?imported=${params}`,
         });

+ 0 - 1
miniprogram/module/article/pages/manage-address/manage-address.wxml

@@ -1,6 +1,5 @@
 <!-- 导航栏 -->
 <t-navbar title="地址管理" left-arrow />
-
 <!-- 搜索框 -->
 <view class="search-bar">
   <t-input

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

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

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

@@ -228,4 +228,128 @@
 
 .name {
   margin-right: 20rpx;
+}
+
+/* 服务包列表样式 */
+.service-packages {
+  margin-bottom: 24rpx;
+}
+
+.service-package-item {
+  display: flex;
+  align-items: flex-start;
+  margin-bottom: 20rpx;
+  padding-bottom: 20rpx;
+  
+  &:not(:last-child) {
+    // border-bottom: 1px solid #f0f0f0;
+  }
+  
+  &:last-child {
+    margin-bottom: 0;
+    padding-bottom: 0;
+  }
+}
+
+.service-package-img {
+  width: 120rpx;
+  height: 120rpx;
+  border-radius: 8rpx;
+  margin-right: 20rpx;
+  flex-shrink: 0;
+  background-color: #f5f5f5;
+}
+
+.service-package-placeholder {
+  width: 120rpx;
+  height: 120rpx;
+  border-radius: 8rpx;
+  margin-right: 20rpx;
+  background-color: #f5f5f5;
+  border: 1px solid #e8e8e8;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+}
+.servicepag-des{
+  display: flex;
+    align-items: center;
+    justify-content: space-between;
+}
+.placeholder-icon {
+  font-size: 40rpx;
+  opacity: 0.3;
+}
+
+.service-package-info {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  justify-content: flex-start;
+  min-height: 120rpx;
+}
+
+.service-package-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-start;
+  margin-bottom: 15rpx;
+  width: 100%;
+}
+
+.service-package-name {
+  font-size: 28rpx;
+  font-weight: 500;
+  color: #333;
+  line-height: 1.4;
+  flex: 1;
+}
+
+.service-package-price {
+  font-size: 28rpx;
+  color: #333;
+  font-weight: 500;
+  margin-left: 16rpx;
+  white-space: nowrap;
+}
+
+.service-package-detail {
+  font-size: 24rpx;
+  color: #666;
+  margin-bottom: 8rpx;
+  line-height: 1.5;
+}
+
+.service-package-quantity {
+  font-size: 24rpx;
+  color: #666;
+  text-align: right;
+  line-height: 1.5;
+}
+
+/* 展开/收起按钮 */
+.expand-toggle {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 16rpx 0;
+  color: #1890ff;
+  font-size: 26rpx;
+  cursor: pointer;
+  user-select: none;
+}
+
+.expand-text {
+  margin-right: 8rpx;
+}
+
+.expand-icon {
+  font-size: 20rpx;
+  transition: transform 0.3s ease;
+  transform: rotate(0deg);
+  
+  &.expanded {
+    transform: rotate(180deg);
+  }
 }

+ 103 - 43
miniprogram/module/article/pages/order-list/order-list.ts

@@ -2,16 +2,28 @@ import PageContainerBehavior from "../../../../core/behavior/page-container.beha
 import tickleBehavior, {
   getTickleContext,
 } from "../../../../core/behavior/tickle.behavior";
+import { handleWeChatPayment } from "../../../../utils/util";
 import {
   orderListMethod,
   orderCancelMethod,
   orderPayMethod,
 } from "../../request";
+import I18nBehavior from "../../../../i18n/behavior";
+
 // module/article/pages/order-list/order-list.ts
+const i18n = {
+  orderText: {
+    mineOrder: "我的服务",
+    paying: "待确认",
+    paid: "已确认",
+    paySuccess: "已完成",
+  },
+};
 Page({
-  behaviors: [PageContainerBehavior, tickleBehavior],
+  behaviors: [PageContainerBehavior, tickleBehavior,I18nBehavior],
   properties: {},
   data: {
+    i18n,
     showConfirm: false,
     currentTab: "all", // 当前选中的标签
     orders: [],
@@ -20,7 +32,8 @@ Page({
     id: "",
     statusObj: {
       0: "待付款",
-      6: "待收货",
+      // 6: "待收货",
+      6: "已付款",
       345: "交易成功",
       2: "交易关闭",
     },
@@ -35,15 +48,15 @@ Page({
     payingId: '',
     showAddress: false,
     // 节流控制
-    throttleTimers: {} as Record<string, boolean>, // 存储各个按钮的节流定时器
+    throttleTimers: {} as Record<string, boolean>,
+    expandedItems: {} as Record<string, boolean>,
   },
   computed: {},
-  // 节流函数 - 防止短时间内重复点击
+  // 节流 - 防止短时间内重复点击
   throttle(func: Function, delay: number = 1000, key: string = "default") {
     return (...args: any[]) => {
       // 如果该按钮正在节流中,直接返回
       if (this.data.throttleTimers[key]) {
-        console.log(`按钮 ${key} 正在节流中,忽略点击`);
         return;
       }
 
@@ -67,26 +80,15 @@ Page({
   },
   onLoad(options: any) {
     // 读取 tab 参数,默认为 all
-    console.log(options, "options");
     const tab = options?.tab || "all";
     this.setData({ currentTab: tab });
-    // this.filterOrdersList(tab);
   },
   onShow() {
     this.filterOrdersList(this.data.currentTab);
-    // 读取地址
-    // if (JSON.stringify(this.data.selectedAddress) !== "{}") {
-    //   this.setData({
-    //     name: this.data.selectedAddress?.liaison,
-    //     phone: this.data.selectedAddress?.phone,
-    //     address: this.data.selectedAddress?.fullAddress,
-    //   });
-    // }
   },
   // 切换tab
   onTabChange(event: any) {
     const type = event.detail.value;
-    console.log(type, "传来的type");
     this.setData({ currentTab: type });
     this.filterOrdersList(type);
   },
@@ -119,14 +121,39 @@ Page({
         } else {
           item.showAddress = true;
         }
+        if (item.items && Array.isArray(item.items)) {
+          item.items = item.items.map((subItem: any) => {
+            return {
+              id: subItem.id || '',
+              name: subItem.conditioningProgramName || '',
+              description: (() => {
+                const dose = subItem?.convertDose ?? '1';
+                const unit = subItem?.convertUnit ?? '次';
+                return `${dose} ${unit}`;
+              })(),
+              photo: subItem.conditioningProgramPhoto || '',
+              price: subItem.unitPrice || 0,
+              quantity: subItem?.totalMeasure || 0,
+            }
+          });
+        }
+      });
+      // 批量初始化展开状态为false(默认折叠)
+      const expandedItems: Record<string, boolean> = {};
+      res.data.forEach((item: any) => {
+        if (!this.data.expandedItems[item.id]) {
+          expandedItems[item.id] = false;
+        }
       });
+      if (Object.keys(expandedItems).length > 0) {
+        this.setData({ expandedItems: { ...this.data.expandedItems, ...expandedItems } });
+      }
       this.setData({ orders: res.data });
     }
   },
   // 过滤订单l列表
   filterOrdersList(type: any) {
     // 根据当前选中的标签过滤订单
-    console.log(type, "根据传来的type获取不同状态下的列表");
     switch (type) {
       // 全部订单
       case "all":
@@ -136,14 +163,18 @@ Page({
       case "pending":
         this.getOrderList("0");
         break;
-      // 待收货订单
-      case "received":
+      // 已付款订单
+      case "paid":
         this.getOrderList("6");
         break;
       // 交易完成订单
       case "completed":
         this.getOrderList("345");
         break;
+      // 交易关闭订单
+      case "closed":
+        this.getOrderList("2");
+        break;
       default:
         break;
     }
@@ -156,13 +187,36 @@ Page({
         if (this.data.payingId) return; // 防重复
         this.setData({ paying: true, payingId: orderId });
         try {
-          await orderPayMethod(orderId);
-          wx.navigateTo({
-            url: "/module/article/pages/success-page/success-page?title=订单支付成功",
-          });
+          // 调用支付接口
+          const payResult = await orderPayMethod(orderId);
+          if (payResult && payResult.data) {
+            const paymentParams = payResult.data;
+            handleWeChatPayment(paymentParams, (res: any) => {
+              // 支付成功,跳转到成功页面
+              wx.redirectTo({
+                url: "/module/article/pages/success-page/success-page?title=订单支付成功",
+              });
+            }, (error: any) => {
+              this.setData({ paying: false, payingId: '' });
+              if (error.errMsg === 'requestPayment:fail cancel') {
+                wx.showToast({
+                  title: "支付已取消",
+                  icon: "none",
+                });
+              } else {
+                wx.showToast({
+                  title: error.errMsg || "支付失败,请重试",
+                  icon: "none",
+                });
+              }
+            });
+          } else {
+            wx.showToast({
+              title: payResult.errMsg,
+              icon: "none",
+            });
+          }
         } catch (error: any) {
-          this.setData({ paying: false, payingId: '' });
-          // getTickleContext.call(this).showWarnMessage(error.errMsg);
           wx.showToast({
             title: error.errMsg,
             icon: "none",
@@ -179,15 +233,10 @@ Page({
   onSeeLogistics: function (e: any) {
     return this.throttle(
       (event: any) => {
-        console.log(event, "查看物流");
         wx.showToast({
           title: "暂未开通",
           icon: "none",
         });
-        // const id = event.currentTarget.dataset.id;
-        // wx.navigateTo({
-        //   url: `/module/article/pages/see-logistics/see-logistics?id=${id}`,
-        // });
       },
       2000,
       "logistics"
@@ -199,8 +248,7 @@ Page({
       (event: any) => {
         const orderStatus = event.currentTarget.dataset.status;
         const id = event.currentTarget.dataset.id;
-        console.log(orderStatus, "切换地址");
-        // 根据订单状态判断是否可以切换地址. 待支付状态下可以切换地址
+        // 根据订单状态判断是否可以切换地址. 待付款状态下可以切换地址
         if (orderStatus === "0") {
           wx.navigateTo({
             url:
@@ -252,7 +300,6 @@ Page({
   onConfirmReceiving: function (e: any) {
     return this.throttle(
       (event: any) => {
-        console.log(event, "确认收货");
         const orderId = event.currentTarget.dataset.id;
         wx.navigateTo({
           url: `/module/article/pages/confirm-receiving/confirm-receiving?orderId=${orderId}`,
@@ -284,16 +331,29 @@ Page({
   // 订单详情
   onOrderDetail(e: any) {
     const id = e.currentTarget.dataset.id;
-    // const status = e.currentTarget.dataset.status;
-    wx.navigateTo({
-      url: `/module/article/pages/order-detail/order-detail?id=${id}`,
-    });
+    const status = e.currentTarget.dataset.status;
+    if (status === '0') {
+      wx.navigateTo({
+        url: `/module/order/pages/order-detail/order-detail?id=${id}&status=${status}`,
+      });
+    } else {
+      wx.navigateTo({
+        url: `/module/order/pages/other-detail/other-detail?id=${id}&status=${status}`,
+      });
+    }
   },
   //回到我的页面
-goMine() {
-  console.log("goMine");
-  wx.redirectTo({
-    url: "/pages/mine/mine",
-  });
-}
+  goMine() {
+    wx.redirectTo({
+      url: "/pages/mine/mine",
+    });
+  },
+  // 切换服务包展开/收起
+  toggleServicePackages(e: any) {
+    const orderId = e.currentTarget.dataset.id;
+    const currentExpanded = this.data.expandedItems[orderId] || false;
+    this.setData({
+      [`expandedItems[${orderId}]`]: !currentExpanded,
+    });
+  },
 });

+ 53 - 10
miniprogram/module/article/pages/order-list/order-list.wxml

@@ -1,18 +1,19 @@
 <!--module/diet/pages/delivery-address/delivery-address.wxml-->
-<t-navbar title="我的订单" left-arrow bindtap="goMine" />
+<t-navbar title="{{i18n.orderText.mineOrder}}" left-arrow bindtap="goMine" />
 <t-tabs value="{{currentTab}}" bindchange="onTabChange">
   <t-tab-panel label="全部" value="all" />
-  <t-tab-panel label="待付款" value="pending" />
-  <t-tab-panel label="待收货" value="received" />
-  <t-tab-panel label="交易成功" value="completed" />
+  <t-tab-panel label="{{i18n.orderText.paying}}" value="pending" />
+  <t-tab-panel label="{{i18n.orderText.paid}}" value="paid" />
+  <t-tab-panel label="{{i18n.orderText.paySuccess}}" value="completed" />
+    <!-- <t-tab-panel label="交易关闭" value=closed" /> -->
 </t-tabs>
 
 <scroll-view class="page-scroll__container" scroll-y>
   <block wx:for="{{orders}}" wx:key="id">
-    <view class="order-card" bindtap="onOrderDetail" data-id="{{item.id}}" data-status="{{item.status}}">
+    <view class="order-card" bindtap="onOrderDetail" data-id="{{item.id}}" data-status="{{item.orderStatus}}">
       <view class="order-header">
         <view>
-          <text wx:if="{{false}}">订单编号:{{item.orderId}}</text>
+          <text wx:if="{{item.orderNo}}">订单编号:{{item.orderNo}}</text>
         </view>
         <text class="order-status {{statusClassObj[item.orderStatus]}}">{{statusObj[item.orderStatus]}}</text>
       </view>
@@ -31,6 +32,7 @@
       </view>
       <view class="order-body">
         <image src="{{item.photo}}" class="order-img" wx:if="{{item.photo}}" />
+        <image src="../../assets/servicePag.png" class="order-img" wx:else />
         <view class="order-info">
           <text class="order-title">{{item.conditioningWrapName || ''}}</text>
           <text class="order-doctor" wx:if="{{item.operateBy}}">开具医生:<text style="color:black">{{item.operateBy}}</text></text>
@@ -39,6 +41,47 @@
       </view>
 
       <view class="line"></view>
+      <!-- 服务包列表 -->
+      <view class="service-packages">
+        <view 
+          class="service-package-item" 
+          wx:for="{{item.items}}" 
+          wx:key="id"
+          wx:for-item="package"
+          wx:for-index="packageIndex"
+          wx:if="{{packageIndex < 2 || expandedItems[item.id]}}"
+        >
+          <image 
+            src="{{package.photo}}" 
+            class="service-package-img" 
+            wx:if="{{package.photo}}" 
+            mode="aspectFill"
+          />
+          <view class="service-package-placeholder" wx:else>
+            <text class="placeholder-icon">📦</text>
+          </view>
+          <view class="service-package-info">
+            <view class="service-package-header">
+              <text class="service-package-name">{{package.name}}</text>
+              <text class="service-package-price" wx:if="{{package.price > 0}}">¥{{package.price}}</text>
+            </view>
+            <view class="servicepag-des">
+            <text class="service-package-detail" wx:if="{{package.description}}">{{package.description}}</text>
+            <text class="service-package-quantity" wx:if="{{package.quantity > 0}}">x{{package.quantity}}</text>
+            </view>
+          </view>
+        </view>
+        <!-- 展开/收起按钮 -->
+        <view 
+          class="expand-toggle" 
+          wx:if="{{item.items && item.items.length > 2}}"
+          catchtap="toggleServicePackages"
+          data-id="{{item.id}}"
+        >
+          <text class="expand-text">{{expandedItems[item.id] ? '收起' : '展开'}}</text>
+          <text class="expand-icon {{expandedItems[item.id] ? 'expanded' : ''}}">▼</text>
+        </view>
+      </view>
       <view class="order-footer">
         <view class="order-price">订单金额:<text class="order-amount">¥{{item.cost}}</text></view>
 
@@ -50,12 +93,12 @@
           <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>
-          <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}}">
+          <!-- <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}}">
             查看物流
-          </t-button>
-          <t-button wx:if="{{item.orderStatus==='6'}}" size="small" type="default" class="outline-btn" bindtap="onConfirmReceiving" data-id="{{item.id}}" disabled="{{throttleTimers.confirmReceiving}}">
+          </t-button> -->
+          <!-- <t-button wx:if="{{item.orderStatus==='6'}}" size="small" type="default" class="outline-btn" bindtap="onConfirmReceiving" data-id="{{item.id}}" disabled="{{throttleTimers.confirmReceiving}}">
             确认收货
-          </t-button>
+          </t-button> -->
 
         </view>
       </view>

+ 2 - 0
miniprogram/module/article/pages/science-info/science-info.ts

@@ -8,8 +8,10 @@ Component({
   behaviors: [PageContainerBehavior, tickleBehavior],
   lifetimes: {
     attached() {
+      console.log(this.data, "this.data===获取宣教传来的参数111");
       const channel = this.getOpenerEventChannel();
       channel.on?.("load", (data: AnyObject) => {
+        console.log(data, "data===获取宣教传来的参数");
         // 支持直接传入外链 URL,直接使用 web-view 打开 新加的
         if (data?.url) {
           this.setData({

+ 1 - 1
miniprogram/module/article/pages/science-info/science-info.wxml

@@ -6,7 +6,7 @@
     <web-view src="{{dataset.content}}"></web-view>
   </block>
   <block wx:else>
-    <t-navbar title="中医科普" left-arrow />
+    <t-navbar title="健康宣教" left-arrow />
     <scroll-view class="page-scroll__container" type="list" scroll-y style="{{containerStyle}}">
       <block wx:if="{{dataset.briefImg}}">
         <image src="{{dataset.briefImg}}" mode="widthFix" style="width: 100%;"></image>

+ 10 - 3
miniprogram/module/article/pages/science-list/science-list.ts

@@ -1,9 +1,11 @@
 import PageContainerBehavior from "../../../../core/behavior/page-container.behavior";
+import I18nBehavior from "../../../../i18n/behavior";
 import tickleBehavior, { getTickleContext } from "../../../../core/behavior/tickle.behavior";
 import { getScienceListMethod } from "../../request";
 // module/diet/pages/science-list/science-list.ts
 Component({
   behaviors: [
+    I18nBehavior,
     PageContainerBehavior,
     tickleBehavior,
   ],
@@ -17,13 +19,18 @@ Component({
     classify: { type: String, value: '' },
   },
   data: {
+    i18n: {
+      healthTeach: { title: '分享文章' },
+    },
     keyword: '',
     searchInputProps: {
       placeholderStyle: 'color: #929292;',
-      cursorColor: '#1D6FF6',
-      iconColor: '#1D6FF6',
+      // cursorColor: '#1D6FF6',
+      // iconColor: '#1D6FF6',
+      cursorColor: '#929292',
+      iconColor: '#929292',
       focus: false,
-      placeholder: '输入搜索名称',
+      placeholder: '搜索名称',
     },
     title: '',
     dataset: [] as AnyObject[],

+ 1 - 1
miniprogram/module/article/pages/science-list/science-list.wxml

@@ -1,5 +1,5 @@
 <!--module/diet/pages/science-list/science-list.wxml-->
-<t-navbar title="中医科普" left-arrow />
+<t-navbar title="{{i18n.healthTeach.title}}" left-arrow />
 <view class="search-wrapper" bindtap="onTapSearchInput">
   <icon class="search-icon" data-button="focus" type="search" size="14" color="{{searchInputProps.iconColor}}"></icon>
   <input class="search-input" value="{{keyword}}" focus="{{searchInputProps.focus}}" placeholder="{{searchInputProps.placeholder}}" placeholder-style="{{searchInputProps.placeholderStyle}}" cursor-color="{{searchInputProps.cursorColor}}" confirm-type="search" bindconfirm="onConfirmSearchInput" />

+ 1 - 1
miniprogram/module/article/pages/success-page/success-page.scss

@@ -1,6 +1,6 @@
 @import "../../../../themes/page.scss";
 @import "../../searc-list.scss";
-/* module/diet/pages/diet-info/diet-info.wxss */
+/* module/diet/pages/success-page/success-page.wxss */
 
 .page-scroll__container {
   background: white;

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

@@ -24,7 +24,10 @@ Page({
     }
   },
     onBackOrderList() {
-      wx.redirectTo({ url: '/module/article/pages/order-list/order-list' });
+      // wx.redirectTo({ url: '/module/article/pages/order-list/order-list' });
+      // 先到「我的」再自动进订单列表,这样订单列表返回时栈上是「我的」
+      wx.setStorageSync("fromSuccessToOrderList", "1");
+      wx.redirectTo({ url: "/pages/mine/mine" });
     },
     async load() {
       wx.showLoading({ title: '加载中' });

+ 33 - 0
miniprogram/module/article/request.ts

@@ -57,6 +57,31 @@ export function getScienceListMethod(
     }
   );
 }
+// 宣教通知
+export function getScienceNoticeListMethod(
+  page: number,
+  size: number,
+  query?: { keyword?: string }
+) {
+  return Post(
+    `/psarticle/pagePsaNotice?pageNum=${page}&pageSize=${size}&keyword=${query?.keyword}`,
+    void 0,
+    {
+      transform({ data }: AnyObject) {
+        return {
+          total: data.total,
+          page: data?.current ?? page,
+          size: data?.size ?? size,
+          data: data.data.map(
+            (item: AnyObject) => (
+              (item.id = item.popularScienceArticleId), item
+            )
+          ),
+        };
+      },
+    }
+  );
+}
 
 export function getScienceMethod(id: string) {
   return Get(`/psarticle/getPsarticleDetailById`, {
@@ -163,6 +188,14 @@ export function orderConfirmMethod(id: string) {
     },
   });
 }
+//新的确认收货接口
+export function orderConfirmReceiptMethod(patientConditioningRecordId: string,integers:number[]) {
+  return Post(`/patientCrManage/confirmReceipt/${patientConditioningRecordId}`, integers,{
+    transform({ data }: AnyObject) {
+      return data;
+    },
+  });
+}
 // 获取足迹列表
 export function getFootPrintListMethod() {
   return Post(`/patientInfoManage/getMyFootprint`, {

+ 4 - 3
miniprogram/module/article/searc-list.scss

@@ -2,10 +2,11 @@
   display: flex;
   align-items: center;
   margin: 12px;
-  padding: 4px 8px;
+  padding: 6px 8px;
   // border: 1px solid #34A76B;
-  border: 1px solid #1D6FF6;
-  border-radius: 8px;
+  // border: 1px solid #1D6FF6;
+  border-radius: 30px;
+  background-color: #f7f7f7;
 
   .search-icon {
     flex: none;

+ 1 - 1
miniprogram/module/care/pages/care/verifyRecord.json

@@ -3,7 +3,7 @@
   "navigationBarTextStyle": "white",
   "navigationBarBackgroundColor": "#0f2226",
   "backgroundColor": "#0f2226",
-  "backgroundColorContent": "#0f2226",
+  "backgroundColorContent": "#ffffff",
   "backgroundColorTop": "#0f2226",
   "backgroundColorBottom": "#0f2226",
   "component": true,

+ 89 - 0
miniprogram/module/care/pages/care/verifyRecord.scss

@@ -70,3 +70,92 @@
     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-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: 100rpx;
+}
+
+.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;
+}

+ 39 - 8
miniprogram/module/care/pages/care/verifyRecord.ts

@@ -1,10 +1,31 @@
-import {  Post } from "../../../../lib/request/method";
+import { Post } from "../../../../lib/request/method";
 Page({
-  onLoad(options: { id: number }) {
+  data: {
+    id: "",
+    recordList: [],
+    showAppointmentSuccess: true,
+    qrCodeUrl: "",
+    appointmentInfo: {
+      operateTime: '',
+      operateDuration: '',
+      conditioningProgramSupplierName: '',
+      cpSupplierDetailAddress: '',
+      cpSupplierPhone: '',
+      cpSupplierQrCode: '',
+    },
+    showAppointmentInfo: false,
+  },
+  onLoad(options: any) {
     if (options.id) {
       this.getVerifyRecordList(options.id);
     }
   },
+  // 生成二维码
+  generateQRCode(qrCodeUrl: any) {
+    this.setData({
+      qrCodeUrl
+    });
+  },
   // 获取核销记录列表
   async getVerifyRecordList(id: number) {
     try {
@@ -17,9 +38,22 @@ Page({
           },
         }
       );
-      if (res && res.length > 0) {
+      if (res && res.patientConditioningSwagItemOffline && JSON.stringify(res.patientConditioningSwagItemOffline) !== '{}') {
         this.setData({
-          recordList: res,
+          showAppointmentInfo: true,
+        });
+        this.setData({
+          appointmentInfo: res.patientConditioningSwagItemOffline,
+        });
+        this.generateQRCode(this.data.appointmentInfo.cpSupplierQrCode);
+      } else {
+        this.setData({
+          showAppointmentInfo: false,
+        });
+      }
+      if (res && res.patientConditioningSwagItemOfflines && res.patientConditioningSwagItemOfflines.length > 0) {
+        this.setData({
+          recordList: res.patientConditioningSwagItemOfflines,
         });
       } else {
         this.setData({
@@ -30,8 +64,5 @@ Page({
       console.error("核销记录列表", error);
     }
   },
-  data: {
-    id: "",
-    recordList: [],
-  },
+
 });

+ 51 - 2
miniprogram/module/care/pages/care/verifyRecord.wxml

@@ -1,6 +1,49 @@
 <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>
 <view class="verify-record-page" wx:if="{{recordList.length>0}}">
   <block wx:for="{{recordList}}" wx:key="id">
     <view class="record-card">
@@ -12,6 +55,12 @@
         <text class="label">操作人:</text>
         <text style="color:black">{{item.operateBy}}</text>
       </view>
+       <!-- 新增加的一列--机构 -->
+        <view class="record-row" wx:if="{{item.operateBy}}">
+        <text class="label">机构:</text>
+        <text style="color:black">{{item.operateBy}}</text>
+      </view>
+       <!-- end -->
       <view class="record-row" wx:if="{{item.feedback}}">
         <text class="label">治疗备注:</text>
         <text style="color:black">{{item.feedback}}</text>
@@ -28,6 +77,6 @@
   </block>
 </view>
  <view wx:else class="empty-state">
-    <text class="empty-text">暂无数据</text>
+    <text class="empty-text">暂无核销数据</text>
   </view>
 </scroll-view>

+ 1 - 1
miniprogram/module/care/pages/careDetail/careDetail.json

@@ -3,7 +3,7 @@
   "navigationBarTextStyle": "white",
   "navigationBarBackgroundColor": "#0f2226",
   "backgroundColor": "#0f2226",
-  "backgroundColorContent": "#0f2226",
+  "backgroundColorContent": "#ffffff",
   "backgroundColorTop": "#0f2226",
   "backgroundColorBottom": "#0f2226",
   "component": true,

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

@@ -98,6 +98,16 @@ Page({
 
     return processedReports;
   },
+//健康咨询记录
+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))}`,
+    });
+  }
+},
 
   onCardRecord(e: any) {
     const id = e.currentTarget.dataset.id;

+ 6 - 6
miniprogram/module/care/pages/careDetail/careDetail.wxml

@@ -55,7 +55,7 @@
           </view>
           <view wx:for="{{careDetail.items}}" wx:key="id">
             <!-- 线下操作-->
-            <view class="scheme-card" wx:if="{{item.conditioningProgramDetail.isOffline && item.conditioningProgramDetail.isOffline==='Y'}}">
+            <view class="scheme-card" wx:if="{{item.conditioningProgramDetail.sellType && item.conditioningProgramDetail.sellType==='2'}}">
               <view class="scheme-title" wx:if="{{item.conditioningProgramDetail && item.conditioningProgramDetail.name && item.conditioningProgramDetail.name!=='null'}}">{{item.conditioningProgramDetail.name}}</view>
               <view class="divider"></view>
               <view class="carousel-box" wx:if="{{item.carouselMediaList && item.carouselMediaList.length > 0}}">
@@ -78,7 +78,7 @@
                     <text style="color:black">{{item.totalMeasure}}次</text>
                   </text>
                 </text>
-                <text class="link" bindtap="onRecord" data-id="{{item.id}}" wx:if="{{item.conditioningProgramDetail.isOffline && item.conditioningProgramDetail.isOffline==='Y'}}">核销记录</text>
+                <text class="link" bindtap="onRecord" data-id="{{item.id}}" wx:if="{{item.conditioningProgramDetail.sellType && item.conditioningProgramDetail.sellType==='2'}}">核销记录</text>
               </view>
               <view class="scheme-row">
                 <text class="text-gray">频次:</text>
@@ -97,7 +97,7 @@
               </view>
             </view>
             <!-- 线上操作 -->
-            <view class="scheme-card" wx:if="{{item.conditioningProgramDetail.isOffline && item.conditioningProgramDetail.isOffline==='N'}}">
+            <view class="scheme-card" wx:if="{{item.conditioningProgramDetail.sellType && item.conditioningProgramDetail.sellType!=='2'}}">
               <view class="scheme-title">{{item.conditioningProgramDetail.name}}</view>
               <view class="divider"></view>
               <view class="carousel-box" wx:if="{{item.carouselMediaList && item.carouselMediaList.length > 0}}">
@@ -112,7 +112,7 @@
               </view>
               <view class="scheme-row light">
                 <text class="text-gray">数量:</text>
-                <text>{{item.totalMeasure}}{{item.conditioningProgramDetail.cpFixedPricingRule.convertUnit}}</text>
+                <text>{{item.totalMeasure}}{{item.pricingUnit}}</text>
 
               </view>
               <view class="scheme-row between">
@@ -120,7 +120,7 @@
                   <text class="label between">频次:</text>
                   <text>每{{item.frequencyType}}天{{item.frequencyMeasure}}{{item.conditioningProgramDetail.cpFixedPricingRule.convertUnit}}</text>
                 </text>
-                <view class="link" bindtap="onCardRecord" data-id="{{item.id}}" wx:if="{{item.conditioningProgramDetail.isOffline && item.conditioningProgramDetail.isOffline==='N'}}">打卡记录</view>
+                <view class="link" bindtap="onCardRecord" data-id="{{item.id}}" wx:if="{{item.conditioningProgramDetail.sellType && item.conditioningProgramDetail.sellType!=='2'}}">打卡记录</view>
 
               </view>
               <view class="desc-row" wx:if="{{item.remark}}">
@@ -178,7 +178,7 @@
                     <text style="color:black" wx:else>{{item.totalMeasure}}次</text>
                   </text>
                 </view>
-                <view class="link" bindtap="onReport" data-id="{{item.id}}" wx:if="{{false}}">
+                <view class="link" bindtap="onChatRecord" data-item="{{item.consults}}" wx:if="{{item.consults && item.consults.length>0}}">
                   咨询记录</view>
               </view>
               <view class="scheme-row">

+ 1 - 1
miniprogram/module/care/pages/offlineTreatment/offlineTreatment.json

@@ -3,7 +3,7 @@
   "navigationBarTextStyle": "white",
   "navigationBarBackgroundColor": "#0f2226",
   "backgroundColor": "#0f2226",
-  "backgroundColorContent": "#0f2226",
+  "backgroundColorContent": "#ffffff",
   "backgroundColorTop": "#0f2226",
   "backgroundColorBottom": "#0f2226",
   "component": true,

+ 54 - 15
miniprogram/module/care/pages/offlineTreatment/offlineTreatment.scss

@@ -1,20 +1,15 @@
 .page-container {
   background-color: #f3f3f3;
   padding-top: 10px;
-  height: calc(100vh - 44px);
+  // height: calc(100vh - 44px);
+  height: calc(100vh - 180rpx); // 减去 navbar 和 tabs 的高度(约 180rpx)
+  flex: 1;
+  overflow-y: hidden;
   box-sizing: border-box;
-  -webkit-overflow-scrolling: touch;
+  // box-sizing: border-box;
+  // -webkit-overflow-scrolling: touch;
   /* 兼容底部安全区,增加额外内边距防遮挡 */
-  padding-bottom: calc(env(safe-area-inset-bottom) + 24rpx);
-}
-
-.offline-treatment-page {
-  background: #f6f7fa;
-  min-height: 100vh;
-}
-
-.page-container {
-  padding: 24rpx 0;
+  // padding-bottom: calc(env(safe-area-inset-bottom) + 24rpx);
 }
 
 .treatment-card {
@@ -51,10 +46,16 @@
   align-items: center;
   background: #fff;
   border-radius: 16rpx;
-  margin: 0 24rpx 32rpx 24rpx;
+  margin: 0 15rpx 20rpx 15rpx;
   padding: 24rpx 54rpx 18rpx 24rpx;
   box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.03);
   font-size: 28rpx;
+  transition: background-color 0.3s ease;
+}
+
+.treatment-detail-card--highlighted {
+  background: #f9e8f6;
+  border: 1rpx solid #f9e8f6;
 }
 
 .row {
@@ -77,13 +78,16 @@
   font-size: 28rpx;
   font-weight: 500;
   margin-bottom: 20rpx;
+  text-align: center;
 }
 
 .status.done {
   color: #43A047;
   margin-left: 16rpx;
 }
-
+.center{
+  text-align: center;
+}
 .count {
   margin-left: 16rpx;
   color: #222;
@@ -100,5 +104,40 @@
 
 /* 底部安全区占位,确保列表最后一项不被遮挡 */
 .safe-bottom-spacer {
-  height: calc(env(safe-area-inset-bottom) + 24rpx);
+  height: calc(env(safe-area-inset-bottom) + 48rpx);
+  min-height: 48rpx;
+  flex: none;
+}
+
+/* 预约相关样式 */
+.appointment-action {
+  margin-top: 100rpx;
+  display: flex;
+  justify-content: flex-end;
+}
+
+.appointment-btn {
+  padding: 12rpx 32rpx;
+  background: linear-gradient(135deg, #1d6ff6 0%, #4a90ff 100%);
+  color: #fff;
+  font-size: 26rpx;
+  font-weight: 500;
+  border-radius: 20rpx;
+  box-shadow: 0 2rpx 8rpx rgba(29, 111, 246, 0.3);
+  transition: all 0.3s ease;
+  
+  &:active {
+    opacity: 0.9;
+    transform: scale(0.98);
+  }
+}
+
+.appointment-tag {
+  padding: 12rpx 32rpx;
+  background: #fff;
+  color: #f44336;
+  font-size: 24rpx;
+  font-weight: 500;
+  border: 1rpx solid #f44336;
+  border-radius: 20rpx;
 }

+ 75 - 5
miniprogram/module/care/pages/offlineTreatment/offlineTreatment.ts

@@ -1,13 +1,28 @@
+import I18nBehavior from "../../../../i18n/behavior";
 import { getOfflineTreatmentListMethod } from "../../request";
 
 Page({
+  behaviors: [I18nBehavior],
   data: {
+    i18n: {
+      offlineTreatment: { title: '线下', ing: '进行中', finish: '完成' },
+    },
     currentTab: "",
     treatmentList: [],
     isLoading: false,
+    treatmentId: "",
   },
-  onLoad() {
-    this.getOfflineTreatmentList("");
+  async onLoad(options: any) {
+    console.log(options, '传过来的参数')
+    if (options.id) {
+      this.setData({
+        treatmentId: options.id,
+      });
+    }
+    // await this.getOfflineTreatmentList("");
+  },
+  async onShow() {
+    await this.getOfflineTreatmentList("");
   },
   // 跳转到核销记录
   onRecord(e: any) {
@@ -18,6 +33,38 @@ Page({
       });
     }
   },
+  // 去预约
+  goAppointment(e: any) {
+
+    const { item } = e.currentTarget.dataset;
+    const { offlineId, conditioningProgramName, offlineDuration, itemImgSecond, estimatedStartDate } = item;
+    const goodsInfo = {
+      name: conditioningProgramName || '',
+      duration: offlineDuration || 0,
+      image: itemImgSecond || '',
+      offlineId: offlineId || '',
+      serviceTime: estimatedStartDate || '',
+    }
+    console.log(goodsInfo, "goodsInfo======去预约页面")
+    if (offlineId) {
+      // 去预约页面
+      wx.navigateTo({
+        url: `/module/order/pages/appointment/appointment?goodsInfo=${JSON.stringify(goodsInfo)}`,
+      });
+    } else {
+      wx.showToast({
+        title: "线下服务ID不能为空",
+        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({
@@ -32,10 +79,33 @@ Page({
     this.setData({ isLoading: true });
     try {
       const res = await getOfflineTreatmentListMethod(progress);
-      const list = res && res.data ? res.data : [];
-      // 只有在数据加载完成后才更新列表
-      this.setData({ treatmentList: list, isLoading: false });
+      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,
+          isHighlighted: item.id === treatmentId || String(item.id) === String(treatmentId),
+        }));
+        // 将高亮的项目移到最前面
+        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 {
+        // 只有在数据加载完成后才更新列表
+        this.setData({ treatmentList: list, isLoading: false });
+      }
     } catch (error: any) {
+
+      console.error('获取数据失败:', error);
+      // 只有在真正出错时才显示错误提示
       wx.showToast({
         title: error.errMsg || "获取数据失败",
         icon: "none",

+ 30 - 21
miniprogram/module/care/pages/offlineTreatment/offlineTreatment.wxml

@@ -1,57 +1,66 @@
-<t-navbar title="线下非药物治疗" left-arrow />
-<view class="offline-treatment-page">
+<t-navbar title="{{i18n.offlineTreatment.title}}" left-arrow />
   <t-tabs value="{{currentTab}}" bind:change="onTabChange">
     <t-tab-panel label="全部" value=""></t-tab-panel>
-    <t-tab-panel label="治疗中" value="0"></t-tab-panel>
-    <t-tab-panel label="治疗完成" value="1"></t-tab-panel>
+    <t-tab-panel label="{{i18n.offlineTreatment.ing}}" value="0"></t-tab-panel>
+    <t-tab-panel label="{{i18n.offlineTreatment.finish}}" value="1"></t-tab-panel>
   </t-tabs>
   <scroll-view class="page-container" scroll-y>
     <view wx:if="{{isLoading}}">
       <t-loading theme="spinner" text="加载中..." size="24" />
     </view>
-    <view wx:elif="{{treatmentList.length>0}}"> 
+   <view wx:elif="{{treatmentList.length>0}}">
       <block wx:for="{{treatmentList}}" wx:key="id">
-        <view class="treatment-detail-card" bindtap="onRecord" data-id="{{item.id}}">
+        <view class="treatment-detail-card {{item.isHighlighted ? 'treatment-detail-card--highlighted' : ''}}" bindtap="onRecord" data-id="{{item.id}}">
           <view>
-            <view class="row between">
+            <view class="row between" wx:if="{{item.conditioningProgramName}}">
               <text class="label bold">项目:{{item.conditioningProgramName}}</text>
             </view>
-            <view class="row">
+            <view class="row" wx:if="{{item.operateBy}}">
               <text class="sub-label">开具人:</text>
               {{item.operateBy}}
             </view>
-            <view class="row">
+            <view class="row" wx:if="{{item.estimatedStartDate}}">
               <text class="sub-label">开始时间:</text>
               {{item.estimatedStartDate}}
             </view>
             <view class="row">
               <text class="sub-label">频次:</text>
-              每{{item.frequencyType}}天{{item.frequencyMeasure}}{{item.convertUnit}}
+              每{{item.frequencyType}}天{{item.frequencyMeasure}}{{item.convertUnit?item.convertUnit: '次'}}
             </view>
-            <view class="row" wx:if="{{item.arrangeDate}}">
+            <view class="row">
               <text class="sub-label">下次时间:</text>
-              {{item.arrangeDate}}
+              {{item.arrangeDate?item.arrangeDate: '-'}} {{item.arrangeTime ?item.arrangeTime: ''}}{{item.applyTime?'(已预约)':''}}
             </view>
-            <view class="row" wx:if="{{item.conditioningProgramSupplierName}}">
+            <view class="row" wx:if="{{item.pieTime}}">
               <text class="sub-label">机构:</text>
-              {{item.conditioningProgramSupplierName}}
+              {{item.conditioningProgramSupplierName || '-'}}
             </view>
             <view class="divider"></view>
           </view>
-          <view style="margin-top:-80px">
+          <view>
             <view class="status" style="color:{{item.status === 'doing' ? '#F44336' : '#43A047'}}">
               {{item.progress === '0' ? '进行中' : '已完成'}}
             </view>
-            <view>{{item.finishCount}}/{{item.totalMeasure}}</view>
+            <view class="center">{{item.finishCount}}/{{item.totalMeasure}}</view>
+            <!-- 预约按钮/标签 -->
+            <view class="appointment-action" wx:if="{{item.progress === '0'}}">
+              <!-- 已预约:显示标签 -->
+              <view class="appointment-tag" wx:if="{{item.applyTime}}">
+                已预约
+              </view>
+              <!-- 未预约:显示按钮 -->
+              <view class="appointment-btn" wx:else catchtap="goAppointment" data-id="{{item.id}}"
+               data-item="{{item}}">
+                去预约
+              </view>
+            </view>
           </view>
         </view>
       </block>
-      <!-- 底部安全区占位 -->
-      <view class="safe-bottom-spacer"></view>
     </view>
     <view wx:elif="{{!isLoading && treatmentList.length === 0}}">
       <t-empty icon="info-circle-filled" description="暂无数据" />
     </view>
-
-  </scroll-view>
-</view>
+    <!-- 底部安全区占位 -->
+    <view class="safe-bottom-spacer"></view>
+  </scroll-view>

+ 1 - 1
miniprogram/module/care/pages/reportRecord/reportRecord.json

@@ -3,7 +3,7 @@
   "navigationBarTextStyle": "white",
   "navigationBarBackgroundColor": "#0f2226",
   "backgroundColor": "#0f2226",
-  "backgroundColorContent": "#0f2226",
+  "backgroundColorContent": "#ffffff",
   "backgroundColorTop": "#0f2226",
   "backgroundColorBottom": "#0f2226",
   "component": true,

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

@@ -15,7 +15,7 @@ export function healthReportListMethod() {
 }
 // 线下非药物治疗
 export function getOfflineTreatmentListMethod(progress: string) {
-  return Post(`/patientCrManage/pagePofflineCp`, {progress}, {
+  return Post(`/patientCrManage/pagePofflineCp?pageNum=1&pageSize=1000000000`, { progress }, {
     transform({ data }: AnyObject) {
       return data;
     }

+ 4 - 0
miniprogram/module/chats/common.scss

@@ -33,7 +33,10 @@
     .chat-card__content {
       // border-color: #38FF6E;
       border-radius: 10px 0px 10px 10px;
+      max-width: calc(90% - var(--avatar-size));
     }
+
+
   }
 
 
@@ -50,6 +53,7 @@
 
   &__content {
     max-width: calc(80% - $size);
+    // max-width: calc(90% - var(--avatar-size));
     // background-color: rgba(255, 255, 255, 0.04);
     background-color: white;
     border-radius: 0px 10px 10px 10px;

+ 3 - 0
miniprogram/module/chats/components/guide/guide.scss

@@ -2,6 +2,9 @@
 @import '../../common.scss';
 
 /* module/chats/components/guide/guide.wxss */
+.chat-card__content{
+  border-radius: 0 10px 10px 10px 10px !important;
+}
 .steward-wrapper {
   // --primary-color: #38FF6E;
   --primary-color: #1D6FF6;

+ 53 - 18
miniprogram/module/chats/components/guide/guide.ts

@@ -2,10 +2,12 @@
 import { getPatients } from "../../../../pages/home/request";
 import { toCertificationPage } from "../../../../pages/home/router";
 import { Post } from "../../../../lib/request/method";
+import I18nBehavior from "../../../../i18n/behavior";
 
-let next: 'handleA' | 'handleB' | 'handleC';
+let next: "handleA" | "handleB" | "handleC";
 
 Component({
+  behaviors: [I18nBehavior],
   lifetimes: {
     attached(this: any) {
       const showGuideActive = wx.getStorageSync("showGuideActive");
@@ -23,9 +25,9 @@ Component({
       // 获取评估剩余次数
       this.setAnalysisCount().then(() => {
         if (next) {
-          const patientId = wx.getStorageSync('patientId');
+          const patientId = wx.getStorageSync("patientId");
           if (patientId) this[next]();
-          else wx.showToast({ title: '请先完善信息!', icon: 'none' });
+          else wx.showToast({ title: "请先完善信息!", icon: "none" });
           next = undefined;
         }
       });
@@ -36,6 +38,14 @@ Component({
     active: { type: Boolean, value: false },
   },
   data: {
+    i18n: {
+      chats: { 
+        analysis: '测评',
+        healthData: '个人信息管理',
+        healthIndex: '数据信息管理',
+        consult: '',
+      },
+    },
     analysisCount: 0,
     result: [] as string[],
     result2: false,
@@ -51,7 +61,8 @@ Component({
           {},
           {
             transform({ data }: any) {
-              if (data?.usedCou === 0 && data.residuedCou === 0) data.residuedCou = 3;
+              if (data?.usedCou === 0 && data.residuedCou === 0)
+                data.residuedCou = 3;
               return data?.residuedCou;
             },
           }
@@ -65,15 +76,19 @@ Component({
       const { patient } = await getPatients(/*this.data.patientId*/);
       if (!patient) {
         await toCertificationPage();
-        next = 'handleA';
-      }
-      else {
+        next = "handleA";
+      } else {
         if (this.data.active) {
-          this.triggerEvent("next", {
-            component: "questionnaire",
-            scroll: true,
-          });
+          // 设置 isAnalysis 为 3(健康分析/对话管家)
+          wx.setStorageSync("isAnalysis", 3);
+          // 先触发 nextType 更新 messageType,然后触发 next 切换组件
           this.triggerEvent("nextType", { MessageType: 2 });
+          setTimeout(() => {
+            this.triggerEvent("next", {
+              component: "questionnaire",
+              scroll: true,
+            });
+          }, 100);
         }
       }
     },
@@ -81,9 +96,8 @@ Component({
       const { patient } = await getPatients(/*this.data.patientId*/);
       if (!patient) {
         await toCertificationPage();
-        next = 'handleB';
-      }
-      else {
+        next = "handleB";
+      } else {
         if (this.data.active)
           wx.navigateTo({
             url: "/module/health/pages/status/status",
@@ -93,11 +107,10 @@ Component({
     },
     async handleC() {
       const { patient } = await getPatients(/*this.data.patientId*/);
-      if (!patient){
+      if (!patient) {
         await toCertificationPage();
-        next = 'handleC';
-      }
-      else {
+        next = "handleC";
+      } else {
         if (this.data.active)
           wx.navigateTo({
             url: "/module/user/pages/user-edit/user-edit",
@@ -116,7 +129,29 @@ Component({
         wx.redirectTo({ url: "/pages/home/home" });
       }
     },
+    // 在线咨询
+    async handleE() {
+      const { patient } = await getPatients(/*this.data.patientId*/);
+      if (!patient) await toCertificationPage();
+      else {
+        if (this.data.active) {
+          // 清除之前的咨询结束状态,开始新咨询
+          wx.setStorageSync("consultEnded", false);
+          wx.setStorageSync("lastConsultTime", Date.now());
 
+          wx.setStorageSync("isAnalysis", 5);
+          // 先触发 nextType 更新 messageType,然后立即触发 next 切换组件
+          // 由于 nextType 事件处理是同步的,所以可以立即触发 next
+          this.triggerEvent("nextType", { MessageType: 3 });
+          setTimeout(() => {
+            this.triggerEvent("next", {
+              component: "questionnaire",
+              scroll: true,
+            });
+          }, 100);
+        }
+      }
+    },
     _update(result: string[]) {
       if (result.length) {
         this.setData({ result });

+ 17 - 4
miniprogram/module/chats/components/guide/guide.wxml

@@ -18,7 +18,7 @@
       t-class="cell-border-gradient {{_.getClassName(active)}}" 
       t-class-hover="custom-cell-hover"
       t-class-title="health-analysis-title"
-      title="1、健康分析(剩余{{analysisCount}}次)" 
+      title="1、{{i18n.chats.analysis}}(剩余{{analysisCount}}次)" 
       hover="{{active}}" 
       bind:tap="handleA"
       style="--td-cell-hover-color: #F5F5F5;"
@@ -29,7 +29,7 @@
       t-class="cell-border-gradient {{_.getClassName(active)}}" 
       t-class-hover="custom-cell-hover"
        t-class-title="health-analysis-title"
-      title="2、指标信息管理" 
+      title="2、{{i18n.chats.healthIndex}}" 
       hover="{{active}}" 
       bind:tap="handleB"
       style="--td-cell-hover-color: #F5F5F5;"
@@ -37,16 +37,29 @@
       <t-icon name="chevron-right" color="#CCCCCC" size="40rpx" slot="note" />
     </t-cell>
     <t-cell 
-      t-class="cell-border-gradient no-border {{_.getClassName(active)}}" 
+      t-class="cell-border-gradient {{_.getClassName(active)}}" 
       t-class-hover="custom-cell-hover"
        t-class-title="health-analysis-title"
-      title="3、健康信息管理" 
+      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 
+      wx:if="{{i18n.chats.consult}}"
+      t-class="cell-border-gradient no-border {{_.getClassName(active)}}" 
+      t-class-hover="custom-cell-hover"
+       t-class-title="health-analysis-title"
+      title="4、{{i18n.chats.consult}}" 
+      hover="{{active}}" 
+      bind:tap="handleE"
+      style="--td-cell-hover-color: #F5F5F5;"
+    >
+      <t-icon name="chevron-right" color="#CCCCCC" size="40rpx" slot="note" />
+    </t-cell>
+    
           <!-- 
   <t-cell 
       t-class="{{_.getClassName(active)}}" 

+ 12 - 0
miniprogram/module/chats/components/message-consult/message-consult.json

@@ -0,0 +1,12 @@
+{
+  "renderer": "skyline",
+  "component": true,
+  "pureDataPattern": "^_",
+  "usingComponents": {
+    "t-cell": "tdesign-miniprogram/cell/cell",
+    "t-icon": "tdesign-miniprogram/icon/icon",
+    "user-avatar": "../../../../components/user-avatar/user-avatar",
+    "towxml": "../../towxml/towxml"
+  }
+}
+

+ 272 - 0
miniprogram/module/chats/components/message-consult/message-consult.scss

@@ -0,0 +1,272 @@
+@import "../../../../themes/t.cell.scss";
+@import "../../common.scss";
+
+.consult-wrapper {
+  padding-bottom: 180rpx;
+
+  &.consult-ended {
+    padding-bottom: 0;
+  }
+}
+
+.message-content {
+  display: flex;
+  justify-content: center;
+  align-items: flex-start;
+  font-size: 28rpx;
+  color: #333;
+
+  .markdown-content {
+    width: 100%;
+    word-wrap: break-word;
+    word-break: break-all;
+    white-space: normal;
+    line-height: 1.5;
+    font-size: 28rpx;
+    display: block;
+
+    strong,
+    b {
+      display: inline !important;
+      font-weight: bold !important;
+      white-space: normal !important;
+      margin: 0 !important;
+      padding: 0 !important;
+      line-height: inherit !important;
+      vertical-align: baseline;
+    }
+
+    em,
+    i {
+      display: inline !important;
+      font-style: italic !important;
+      white-space: normal !important;
+      margin: 0 !important;
+      padding: 0 !important;
+      line-height: inherit !important;
+      vertical-align: baseline;
+    }
+
+    // 代码块样式
+    pre {
+      background-color: #f5f5f5;
+      padding: 16rpx;
+      border-radius: 8rpx;
+      overflow-x: auto;
+      margin: 16rpx 0;
+      display: block;
+    }
+
+    code {
+      background-color: #f5f5f5;
+      padding: 4rpx 8rpx;
+      border-radius: 4rpx;
+      font-family: "Courier New", monospace;
+      font-size: 24rpx;
+      display: inline;
+    }
+
+    pre code {
+      background-color: transparent;
+      padding: 0;
+      display: block;
+    }
+
+    ul,
+    ol {
+      margin: 16rpx 0;
+      padding-left: 40rpx;
+      display: block;
+    }
+
+    li {
+      margin: 8rpx 0;
+      list-style-type: disc;
+      display: list-item;
+
+      strong,
+      b {
+        display: inline !important;
+      }
+    }
+
+    p {
+      margin: 8rpx 0;
+      display: block;
+      line-height: 1.5;
+
+      strong,
+      b {
+        display: inline !important;
+      }
+
+      &:has(strong),
+      &:has(b) {
+        strong,
+        b {
+          display: inline !important;
+          white-space: normal !important;
+        }
+      }
+    }
+
+    br {
+      line-height: 1.5;
+    }
+
+    span {
+      display: inline !important;
+      white-space: normal !important;
+    }
+  }
+}
+
+.message-image {
+  width: 200rpx;
+  height: 200rpx;
+  border-radius: 16rpx;
+  display: block;
+}
+
+.input-panel {
+  position: fixed;
+  left: 0;
+  right: 0;
+  display: flex;
+  flex-direction: column;
+  align-items: stretch;
+  padding: 16rpx 24rpx;
+  box-sizing: border-box;
+  z-index: 10;
+  transition: bottom 0.25s cubic-bezier(0.25, 0.46, 0.45, 0.94);
+  will-change: bottom;
+}
+.chat-card {
+  --avatar-size: 42px;
+
+  .image-message,
+  .text-message {
+    max-width: calc(90% - var(--avatar-size)) !important;
+    border-radius: 0px 10px 10px 10px !important;
+    background-color: white;
+    overflow: hidden;
+  }
+
+  &.right {
+    .image-message,
+    .text-message {
+      border-radius: 10px 0px 10px 10px !important;
+    }
+  }
+}
+.action-bar {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  margin-bottom: 12rpx;
+  gap: 24rpx;
+}
+
+.action-btn {
+  display: inline-flex;
+  align-items: center;
+  padding: 0 24rpx;
+  height: 80rpx;
+  line-height: 80rpx;
+  border-radius: 12rpx;
+  font-size: 30rpx;
+}
+
+.action-btn.secondary {
+  color: black;
+}
+
+.action-btn.danger {
+  color: #1d6ff6;
+}
+
+.action-text {
+  margin-left: 8rpx;
+}
+
+.input-row {
+  display: flex;
+  align-items: flex-end;
+  gap: 16rpx;
+}
+
+.text-input {
+  flex: 1;
+  width: 60% !important;
+  min-height: 80rpx;
+  max-height: 200rpx;
+  padding: 12rpx 20rpx;
+  background: #ffffff;
+  border-radius: 12rpx;
+  border: 1rpx solid #e6e6e6;
+  color: #333333;
+  font-size: 28rpx;
+  line-height: 1.9;
+  box-sizing: border-box;
+  overflow-y: scroll !important;
+  overflow-x: hidden;
+  -webkit-overflow-scrolling: touch;
+  transition: height 0.2s ease-out;
+  word-wrap: break-word;
+  word-break: break-all;
+  scroll-behavior: smooth;
+}
+
+.send-btn {
+  padding: 0 24rpx;
+  height: 80rpx;
+  line-height: 80rpx;
+  background: #1d6ff6;
+  color: #ffffff;
+  border-radius: 12rpx;
+  font-size: 28rpx;
+  flex-shrink: 0;
+}
+
+.media-btn {
+  display: inline-flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  background: #ffffff;
+  border-radius: 8rpx;
+  padding: 0 20rpx;
+  flex-shrink: 0;
+  height: 80rpx;
+  margin: 0 5rpx;
+}
+
+.media-text {
+  color: #1d6ff6;
+  font-size: 24rpx;
+  margin-top: 2rpx;
+}
+
+/* 系统消息样式 */
+.system-wrapper {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  margin: 6px 0;
+
+  .title {
+    padding: 4px 12px;
+    font-size: 12px;
+    color: #999999;
+    border-radius: 5px;
+  }
+
+  .date {
+    font-size: 12px;
+    color: #999;
+    margin-bottom: 6px;
+  }
+}
+.chat-box {
+  padding: 15rpx 0 15rpx 30rpx !important;
+}

+ 634 - 0
miniprogram/module/chats/components/message-consult/message-consult.ts

@@ -0,0 +1,634 @@
+interface ConsultMessage {
+  id: string; //	咨询记录详情ID
+  consultRecordId: number; //咨询记录ID
+  sender: "user" | "agent" | "human" | "system";
+  sendType: string; //	发送类型 1-患者 2-医生 3-系统 4-AI
+  messageType: "1" | "2"; // 1文本 2 图片
+  messageContent?: string; // 消息内容
+  sendTime?: string; // 发送消息的时间
+}
+import I18nBehavior from "../../../../i18n/behavior";
+import { Post } from "../../../../lib/request/method";
+import { upload } from "../../../../lib/request/upload";
+import dayjs from "dayjs";
+
+const sendTypeMap: Record<string, "user" | "agent" | "human" | "system"> = {
+  "1": "user", // 患者
+  "2": "human", // 医生
+  "3": "system", // 系统
+  "4": "agent", // AI
+};
+
+// 计算底部安全区位置(rpx)
+function calculateSafeBottomRpx(): number {
+  const systemInfo = wx.getSystemInfoSync();
+  // 获取窗口的高度
+  const windowHeight = systemInfo.windowHeight;
+  // 获取安全区底部的高度
+  const safeAreaBottom = systemInfo.safeArea?.bottom ?? windowHeight;
+  const safeBottom = windowHeight - safeAreaBottom;
+  //将px转为rpx
+  return (750 / systemInfo.windowWidth) * safeBottom;
+}
+
+function isConsultEndMessage(msg: ConsultMessage): boolean {
+  if (msg.sendType !== "3" || !msg.messageContent) {
+    return false;
+  }
+  const content = msg.messageContent;
+  const isReminder =
+    content.includes("30分钟") &&
+    (content.includes("5分钟后") || content.includes("将在"));
+  const isRealEnd = content.includes("咨询结束") && !isReminder;
+  return isRealEnd;
+}
+
+// 获取的聊天消息为ConsultMessage格式                                           
+function transformMessage(item: AnyObject): ConsultMessage {
+  const sender = sendTypeMap[item.sendType];
+  return {
+    id: `msg-${item.id}`,
+    consultRecordId: item.consultRecordId,
+    sender,
+    sendTime: item.sendTime || "",
+    sendType: item.sendType,
+    messageType: item.messageType as "1" | "2",
+    messageContent: item.messageContent || "",
+  };
+}
+
+Component({
+  behaviors: [I18nBehavior],
+  properties: {},
+  data: {
+    title: '',
+    i18n: {
+      consultChat: { _: '聊天' }
+    },
+    messages: [] as ConsultMessage[],
+    inputText: "",
+    inputFocus: true,
+    inputBoxBottom: 0,
+    baseInputBottom: 0,
+    keepFocus: true,
+    _keyboardHeight: 0, // 当前键盘高度
+    isTransferredToHuman: false, // 是否已转人工
+    consultEnded: false, // 是否已结束咨询
+    _pollTimer: 0 as any, // 5秒轮询最新消息定时器
+    textareaHeight: 80, // textarea 高度(rpx),初始值与 min-height 一致
+  },
+  observers: {
+    'i18n.consultChat._'(this: any, title: string) {
+      this.setData({ title });
+    },
+  },
+  lifetimes: {
+    async attached() {
+      const safeBottomRpx = calculateSafeBottomRpx();
+      const tabBarHeight = 100; // rpx
+      const baseBottom = safeBottomRpx + tabBarHeight;
+      // 获取咨询中的id
+      const consultId = wx.getStorageSync("consultId");
+      let messages: ConsultMessage[] = [];
+
+      if (consultId) {
+        try {
+          // 获取所有消息的数据
+          const res = await Post(`/consultManage/getAllMsgs/${consultId}`);
+          if (res.data && res.data.length > 0) {
+            messages = res.data.map((item: AnyObject) => {
+              const msg = transformMessage(item);
+              return msg;
+            });
+          }
+        } catch (error: any) {
+          wx.showToast({
+            title: error?.errMsg || "获取历史消息失败",
+            icon: "none",
+          });
+        }
+      }
+
+      const hasEndMessage = messages.some((msg: ConsultMessage) =>
+        isConsultEndMessage(msg)
+      );
+
+      const consultEnded = hasEndMessage || wx.getStorageSync("consultEnded");
+      this.setData({
+        baseInputBottom: baseBottom,
+        inputBoxBottom: baseBottom,
+        messages,
+        consultEnded: !!consultEnded,
+      });
+      this.triggerEvent("boxBottom", { inputBoxBottom: baseBottom });
+      const kbHandler = (res: any) => {
+        const height = res?.height ?? 0;
+
+        // 键盘收起时,直接更新位置
+        if (height === 0) {
+          this._updateInputPosition(0);
+          return;
+        }
+
+        if (this.data.inputFocus || this.data.keepFocus) {
+          this._updateInputPosition(height);
+        }
+      };
+
+      wx.onKeyboardHeightChange?.(kbHandler);
+      (this as any)._kbHandler = kbHandler;
+
+      this._ensureFocus();
+
+      const systemInfo = wx.getSystemInfoSync();
+      const isHarmonyOS =
+        systemInfo.system &&
+        systemInfo.system.toLowerCase().includes("harmony");
+      const delayTime = isHarmonyOS ? 300 : 0;
+
+      if (delayTime > 0) {
+        setTimeout(() => {
+          if (this.data._keyboardHeight > 0 && this.data.keepFocus) {
+            this._updateInputPosition(this.data._keyboardHeight);
+          }
+        }, delayTime);
+      }
+
+      // 如果咨询未结束,启动轮询最新消息
+      if (!consultEnded) {
+        this._startPolling();
+      }
+    },
+    detached() {
+      // 清理监听
+      if ((this as any)._kbHandler) {
+        wx.offKeyboardHeightChange?.((this as any)._kbHandler);
+      }
+      this._stopPolling();
+    },
+  },
+  methods: {
+    _scrollToBottom() {
+      this.triggerEvent("scroll", { id: "bottom" });
+    },
+    _hideKeyboardAndUpdatePosition() {
+      wx.hideKeyboard?.();
+      this.setData({
+        inputFocus: false,
+        keepFocus: false,
+      });
+      this._updateInputPosition(0);
+    },
+    _updateInputPosition(keyboardHeight: number) {
+      const systemInfo = wx.getSystemInfoSync();
+      const rpx2px = systemInfo.windowWidth / 750;
+
+      const keyboardHeightRpx =
+        keyboardHeight > 0 ? keyboardHeight / rpx2px : 0;
+
+      const nextBottom =
+        keyboardHeight > 0 ? keyboardHeightRpx : this.data.baseInputBottom;
+
+      // 避免重复更新相同位置(容差1rpx)
+      if (Math.abs(nextBottom - this.data.inputBoxBottom) < 1) {
+        if (keyboardHeight !== this.data._keyboardHeight) {
+          this.setData({ _keyboardHeight: keyboardHeight });
+        }
+        return;
+      }
+
+      this.setData({
+        inputBoxBottom: nextBottom,
+        _keyboardHeight: keyboardHeight,
+      });
+
+      this.triggerEvent("boxBottom", { inputBoxBottom: nextBottom });
+
+      if (keyboardHeight > 0) {
+        this.triggerEvent("scroll", { id: "bottom" });
+      }
+    },
+    _ensureFocus() {
+      if (!this.data.keepFocus) return;
+      this.setData({ inputFocus: false });
+      wx.nextTick?.(() => {
+        setTimeout(() => {
+          if (this.data.keepFocus) {
+            this.setData({ inputFocus: true });
+            setTimeout(() => {
+              if (this.data._keyboardHeight > 0 && this.data.inputFocus) {
+                this._updateInputPosition(this.data._keyboardHeight);
+              }
+            }, 150);
+          }
+        }, 120);
+      });
+    },
+    tapPanel() {
+      if (!this.data.inputFocus && this.data.keepFocus) {
+        this._ensureFocus();
+      }
+    },
+    endConsult() {
+      // 收起键盘并更新位置
+      this._hideKeyboardAndUpdatePosition();
+      wx.showModal({
+        title: "",
+        content: `确定要结束本次?${this.data.title}`,
+        cancelText: `继续${this.data.title}`,
+        confirmText: "结束",
+      }).then((res: any) => {
+        if (res.confirm) {
+          // 确认结束
+          this._endConsult();
+        } else {
+          // 继续咨询,恢复聚焦
+          this.setData({ keepFocus: true });
+          this._ensureFocus();
+        }
+      });
+    },
+    async _endConsult() {
+      const endDate = dayjs().format("MM-DD HH:mm:ss");
+
+      const consultId = wx.getStorageSync("consultId");
+      this._appendMessage({
+        id: `end-time-${Date.now()}`,
+        consultRecordId: consultId || 0,
+        sender: "system",
+        sendType: "3",
+        messageType: "1",
+        messageContent: `${this.data.title}结束`,
+        sendTime: endDate,
+      });
+
+      // 调用结束咨询接口
+      if (consultId) {
+        try {
+          await Post(`/consultManage/end/${consultId}`);
+        } catch (error: any) {
+          wx.showToast({
+            title: error?.errMsg || `结束${this.data.title}失败`,
+            icon: "none",
+          });
+        }
+      }
+
+      // 设置结束状态
+      this.setData({ consultEnded: true });
+
+      wx.setStorageSync("consultEnded", true);
+      wx.removeStorageSync("consultId");
+      this._stopPolling();
+
+      // 收起键盘
+      wx.hideKeyboard?.();
+
+      // 重置底部位置为正常值(tabbar 高度 + 安全区高度)
+      const safeBottomRpx = calculateSafeBottomRpx();
+      const tabBarHeight = 100; // rpx
+      const normalBottom = tabBarHeight + safeBottomRpx;
+
+      this.triggerEvent("boxBottom", { inputBoxBottom: normalBottom });
+
+      // 通知父组件显示guide菜单组件
+      this.triggerEvent("consultEvent", { type: "end" });
+
+      // 滚动到底部
+      this._scrollToBottom();
+    },
+    handleInput(event: any) {
+      const value = event.detail.value;
+      this.setData({ inputText: value });
+
+      // 内容为空时,立即重置为最小高度
+      if (!value || value.trim() === "") {
+        if (this.data.textareaHeight !== 80) {
+          this.setData({ textareaHeight: 80 });
+        }
+      }
+    },
+    onLineChange(event: any) {
+      const minHeight = 80; // 最小高度(rpx)
+      const maxHeight = 200; // 最大高度(rpx)
+      const lineCount = event.detail.lineCount || 1;
+
+      // 如果输入框为空,直接设置为最小高度
+      if (!this.data.inputText || this.data.inputText.trim() === "") {
+        if (this.data.textareaHeight !== minHeight) {
+          this.setData({ textareaHeight: minHeight });
+        }
+        return;
+      }
+
+      if (lineCount === 1) {
+        if (this.data.textareaHeight !== minHeight) {
+          this.setData({ textareaHeight: minHeight });
+        }
+        return;
+      }
+
+      const lineHeight = 53; // 每行高度(rpx)
+      const padding = 24;
+
+      const calculatedHeight = lineCount * lineHeight + padding;
+      const finalHeight = Math.max(
+        minHeight,
+        Math.min(maxHeight, calculatedHeight)
+      );
+
+      if (Math.abs(this.data.textareaHeight - finalHeight) > 3) {
+        this.setData({ textareaHeight: finalHeight });
+      }
+    },
+    onInputFocus(event: any) {
+      const keyboardHeight = event.detail.height ?? 0;
+
+      // 设置 focus 状态
+      this.setData({
+        inputFocus: true,
+        keepFocus: true,
+      });
+
+      if (keyboardHeight > 0) {
+        this._updateInputPosition(keyboardHeight);
+      } else {
+        if (this.data._keyboardHeight > 0) {
+          // 延迟一点时间,等待键盘完全弹起后再更新
+          setTimeout(() => {
+            if (this.data.inputFocus && this.data._keyboardHeight > 0) {
+              this._updateInputPosition(this.data._keyboardHeight);
+            }
+          }, 100);
+        }
+      }
+    },
+    onInputBlur() {
+      // 设置 focus 状态
+      this.setData({
+        inputFocus: false,
+      });
+
+      setTimeout(() => {
+        if (!this.data.inputFocus) {
+          this._updateInputPosition(0);
+        }
+      }, 100);
+    },
+
+    // 启动轮询最新消息
+    _startPolling() {
+      if (this.data.consultEnded) return;
+      this._stopPolling();
+      const timer = setInterval(() => {
+        if (this.data.consultEnded) {
+          this._stopPolling();
+          return;
+        }
+        this._getLatestMessages();
+      }, 5000);
+      this.setData({ _pollTimer: timer });
+    },
+    _stopPolling() {
+      if (this.data._pollTimer) {
+        clearInterval(this.data._pollTimer);
+        this.setData({ _pollTimer: 0 });
+      }
+    },
+    // 获取最新消息
+    async _getLatestMessages() {
+      const consultId = wx.getStorageSync("consultId");
+      if (!consultId) return;
+      try {
+        // 获取最新消息
+        const res = await Post(`/consultManage/getLatestMsgs/${consultId}`);
+        if (res.data && Array.isArray(res.data) && res.data.length > 0) {
+          const newMessages = res.data.map((item: AnyObject) => {
+            const msg = transformMessage(item);
+            return msg;
+          });
+          const allMessages = [...this.data.messages, ...newMessages];
+          this.setData({ messages: allMessages });
+
+          const hasEndMessage = newMessages.some((msg: ConsultMessage) =>
+            isConsultEndMessage(msg)
+          );
+
+          if (hasEndMessage && !this.data.consultEnded) {
+            this.setData({ consultEnded: true });
+            wx.setStorageSync("consultEnded", true);
+            this._stopPolling();
+
+            // 收起键盘
+            wx.hideKeyboard?.();
+
+            const safeBottomRpx = calculateSafeBottomRpx();
+            const tabBarHeight = 100; // rpx
+            const normalBottom = tabBarHeight + safeBottomRpx;
+
+            this.triggerEvent("boxBottom", { inputBoxBottom: normalBottom });
+
+            // 通知父组件显示guide菜单组件
+            this.triggerEvent("consultEvent", { type: "end" });
+          }
+
+          this._scrollToBottom();
+        }
+      } catch (error: any) {
+        wx.showToast({
+          title: error?.errMsg || "获取最新消息失败",
+          icon: "none",
+        });
+      }
+    },
+    // 发送消息到后端
+    async _sendMessage(messageType: "1" | "2", messageContent: string) {
+      const consultId = wx.getStorageSync("consultId");
+      if (!consultId) {
+        wx.showToast({ title: `${this.data.title}ID不存在`, icon: "none" });
+        return;
+      }
+      try {
+        await Post(`/consultManage/sendConsultMsg`, {
+          consultRecordId: consultId,
+          messageType,
+          messageContent,
+        }).then(() => {
+          this._stopPolling();
+          this._getLatestMessages();
+          setTimeout(() => {
+            if (!this.data.consultEnded) {
+              this._startPolling();
+            }
+          }, 5000);
+        });
+      } catch (error: any) {
+        wx.showToast({
+          title: error?.errMsg || "发送失败,请重试",
+          icon: "none",
+        });
+      }
+    },
+    async sendText() {
+      const text = this.data.inputText.trim();
+      if (!text) {
+        wx.showToast({ title: "发送内容不能为空", icon: "none" });
+        return;
+      }
+      const consultId = wx.getStorageSync("consultId");
+      // 先添加用户消息到界面
+      const messageId = `user-text-${Date.now()}`;
+      this._appendMessage({
+        id: messageId,
+        consultRecordId: consultId || 0,
+        sender: "user",
+        sendType: "1",
+        messageType: "1",
+        messageContent: text,
+      });
+      this._scrollToBottom();
+      // 发送信息
+      this._sendMessage("1", text);
+
+      // 保存当前键盘状态
+      const wasFocused = this.data.inputFocus;
+      const currentKeyboardHeight = this.data._keyboardHeight;
+
+      this.setData({
+        inputText: "",
+        textareaHeight: 80,
+      });
+
+      if (!wasFocused) {
+        this.setData({
+          inputFocus: false,
+          keepFocus: false,
+        });
+      } else {
+        if (currentKeyboardHeight > 0) {
+          this._updateInputPosition(currentKeyboardHeight);
+        }
+      }
+    },
+    async chooseImage() {
+      const wasFocused = this.data.inputFocus;
+      const currentKeyboardHeight = this.data._keyboardHeight;
+
+      this._hideKeyboardAndUpdatePosition();
+
+      try {
+        const res = await wx.chooseMedia({
+          count: 1,
+          mediaType: ["image"],
+          sourceType: ["album", "camera"],
+        });
+        const files = res.tempFiles ?? [];
+        const file = files[0];
+        if (!file?.tempFilePath) return;
+        const imagePath = file.tempFilePath;
+        // 直接发送图片
+        await this._sendImageMessage(imagePath);
+
+        if (wasFocused) {
+          wx.nextTick?.(() => {
+            setTimeout(() => {
+              this.setData({
+                inputFocus: true,
+                keepFocus: true,
+              });
+              // 恢复键盘位置
+              if (currentKeyboardHeight > 0) {
+                this._updateInputPosition(currentKeyboardHeight);
+              }
+            }, 200);
+          });
+        }
+      } catch (error: any) {
+        if (error.errMsg && !error.errMsg.includes("cancel")) {
+          console.error("选择图片失败", error);
+        }
+
+        if (wasFocused) {
+          wx.nextTick?.(() => {
+            setTimeout(() => {
+              this.setData({
+                inputFocus: true,
+                keepFocus: true,
+              });
+              // 恢复键盘位置
+              if (currentKeyboardHeight > 0) {
+                this._updateInputPosition(currentKeyboardHeight);
+              }
+            }, 200);
+          });
+        }
+      }
+    },
+    // 发送图片
+    async _sendImageMessage(imagePath: string) {
+      const consultId = wx.getStorageSync("consultId");
+      try {
+        const messageId = `user-image-${Date.now()}`;
+        this._appendMessage({
+          id: messageId,
+          consultRecordId: consultId || 0,
+          sender: "user",
+          sendType: "1",
+          messageType: "2",
+          messageContent: imagePath,
+        });
+
+        // 上传图片
+        const imageUrl = await upload({
+          params: { name: "file", file: imagePath },
+          transform({ data }: any) {
+            return data?.url || data;
+          },
+        });
+
+        // 发送图片消息
+        await this._sendMessage("2", imageUrl);
+
+        // 更新消息中的图片URL
+        const messages = this.data.messages;
+        const messageIndex = messages.findIndex(
+          (msg: ConsultMessage) => msg.id === messageId
+        );
+        if (messageIndex !== -1) {
+          messages[messageIndex].messageContent = imageUrl;
+          this.setData({ messages });
+        }
+        this._scrollToBottom();
+      } catch (error: any) {
+        wx.showToast({
+          title: error?.errMsg || "图片上传失败",
+          icon: "none",
+        });
+      }
+    },
+    // 预览图片
+    previewImage(e: any) {
+      const currentUrl = e.currentTarget.dataset.url;
+      const urls = this.data.messages
+        .filter(
+          (msg: ConsultMessage) => msg.messageType === "2" && msg.messageContent
+        )
+        .map((msg: ConsultMessage) => msg.messageContent!);
+      wx.previewImage({
+        current: currentUrl,
+        urls: urls.length > 0 ? urls : [currentUrl],
+        fail: (err) => {
+          wx.showToast({
+            title: err?.errMsg || "预览图片失败",
+            icon: "none",
+          });
+        },
+      });
+    },
+    _appendMessage(message: ConsultMessage) {
+      // 把获取的最新的消息追加到所有消息后面
+      const messages = [...this.data.messages, message];
+      this.setData({ messages });
+    },
+  },
+});

+ 86 - 0
miniprogram/module/chats/components/message-consult/message-consult.wxml

@@ -0,0 +1,86 @@
+<wxs module="_">
+  module.exports.isUser = function (sender) {
+    return sender === 'user'
+  }
+  module.exports.isHuman = function (sender) {
+    return sender === 'human'
+  }
+  module.exports.isSystem = function (sender) {
+    return sender === 'system'
+  }
+  module.exports.isAgent = function (sender) {
+    return sender === 'agent'
+  }
+  // 判断是否需要 Markdown 渲染(AI 和医生的消息)
+  module.exports.needMarkdown = function (sender) {
+    return sender === 'agent' || sender === 'human'
+  }
+</wxs>
+<!--module/chats/components/message-consult/message-consult.wxml-->
+<view class="consult-wrapper {{consultEnded ? 'consult-ended' : ''}}">
+  <block wx:for="{{messages}}" wx:key="id">
+    <!-- 系统消息样式 -->
+    <view wx:if="{{_.isSystem(item.sender)}}" class="system-wrapper">
+      <view class="date">{{item.sendTime}}</view>
+      <view class="title">{{item.messageContent}}</view>
+    </view>
+    <!-- 普通消息 -->
+    <view wx:else class="chat-card {{_.isUser(item.sender) ? 'right' : 'left'}}">
+      <view class="chat-card__avatar ">
+        <user-avatar wx:if="{{_.isUser(item.sender)}}"></user-avatar>
+        <user-avatar wx:elif="{{_.isHuman(item.sender)}}"></user-avatar>
+        <image wx:else src="../../assets/robot.png" mode="aspectFill" />
+      </view>
+      <view class="{{item.messageType === '2' ? 'image-message' : 'text-message'}}">
+        <block wx:if="{{item.messageType === '1'}}">
+          <t-cell t-class="cell-border-gradient" bordered="{{false}}" class="chat-box">
+            <view slot="title" class="message-content">
+              <!-- 如果是 AI 或医生的消息,使用 towxml 直接渲染 Markdown -->
+              <block wx:if="{{_.needMarkdown(item.sender)}}">
+                <towxml markdown="{{item.messageContent}}" theme="light" />
+              </block>
+              <!-- 其他消息使用普通文本显示 -->
+              <text wx:else>{{item.messageContent}}</text>
+            </view>
+          </t-cell>
+        </block>
+        <block wx:elif="{{item.messageType === '2'}}">
+          <image class="message-image" src="{{item.messageContent}}"
+           mode="aspectFill" show-menu-by-longpress="true"
+           bind:tap="previewImage" 
+           data-url="{{item.messageContent}}" />
+        </block>
+      </view>
+    </view>
+  </block>
+</view>
+
+<view class="input-panel" style="bottom: {{inputBoxBottom}}rpx;" bind:tap="tapPanel" wx:if="{{!consultEnded}}">
+  <view class="action-bar">
+    <view class="action-btn danger" bind:tap="endConsult">结束{{i18n.consultChat._}}</view>
+  </view>
+  <view class="input-row">
+    <textarea
+      class="text-input"
+      placeholder="请输入内容"
+      bind:input="handleInput"
+      bind:linechange="onLineChange"
+      value="{{inputText}}"
+      confirm-type="send"
+      bindconfirm="sendText"
+      adjust-position="{{false}}"
+      bind:focus="onInputFocus"
+      bind:blur="onInputBlur"
+      focus="{{inputFocus}}"
+      maxlength="{{2000}}"
+      show-confirm-bar="{{false}}"
+      style="height: {{textareaHeight}}rpx;"
+    />
+    <view class="media-btn" bind:tap="chooseImage">
+      <t-icon name="camera" size="44rpx" color="#1D6FF6" />
+      <text class="media-text">图片</text>
+    </view>
+    <view class="send-btn" bind:tap="sendText">发送</view>
+  </view>
+</view>
+

+ 7 - 2
miniprogram/module/chats/components/message-follow/message-follow.ts

@@ -1,6 +1,7 @@
 // module/chats/components/message-text/message-text.ts
+import I18nBehavior from "../../../../i18n/behavior";
 Component({
-
+  behaviors: [I18nBehavior],
   /**
    * 组件的属性列表
    */
@@ -13,7 +14,11 @@ Component({
    * 组件的初始数据
    */
   data: {
-
+    i18n: {
+      report: { title: '评估' },
+      analysis: { title: '结果' },
+      follow: { tips: '操作完成' },
+    },
   },
 
   /**

+ 3 - 3
miniprogram/module/chats/components/message-follow/message-follow.wxml

@@ -5,7 +5,7 @@
   </view>
 
   <view class="chat-card__content">
-   <t-cell t-class="cell-border-gradient" title="随访完成,感谢您的配合,祝您早日恢复!"></t-cell>
+   <t-cell t-class="cell-border-gradient" title="{{i18n.follow.tips}}"></t-cell>
   </view>
 </view>
 
@@ -14,11 +14,11 @@
     <image src="../../assets/robot.png" mode="aspectFill" />
   </view>
   <view class="chat-card__content">
-    <t-cell t-class="cell-border-gradient" title="查看舌面象分析报告">
+    <t-cell t-class="cell-border-gradient" title="查看{{i18n.analysis.title}}">
     </t-cell>
     <view class="chat-card__handle">
       <view class="item health-report" bind:tap="seeReport">
-      查看健康分析报告
+        查看{{i18n.report.title}}
         <image src="../../assets/button-report.bg.png" mode="aspectFit" />
       </view>
     </view>

+ 7 - 1
miniprogram/module/chats/components/message-report/message-report.ts

@@ -1,9 +1,15 @@
+import I18nBehavior from "../../../../i18n/behavior";
 // module/chats/components/message-report/message-report.ts
 Component({
+  behaviors: [I18nBehavior],
   properties: {
     payload: { type: Object, value: { title: '', url: '' } }
   },
-  data: {},
+  data: {
+    i18n: {
+      report: { title: '报告' },
+    }
+  },
   methods: {
     handle() {
       wx.navigateTo({ url: this.data.payload.url })

+ 4 - 4
miniprogram/module/chats/components/message-report/message-report.wxml

@@ -8,10 +8,10 @@
     </t-cell>
     <view class="chat-card__handle report-handle">
       <view class="item" bind:tap="handle">
-   <view class="report-box">
-<view><image src="../../../../assets/bg/icon_file@3x.png" class="report-img" /> </view>
-<view class="text-box">健康分析报告</view>
-</view>
+        <view class="report-box">
+          <view><image src="../../../../assets/bg/icon_file@3x.png" class="report-img" /></view>
+          <view class="text-box">{{i18n.report.title}}</view>
+        </view>
       </view>
     </view>
   </view>

+ 2 - 1
miniprogram/module/chats/components/questionnaire/questionnaire.json

@@ -13,6 +13,7 @@
     "message-report": "../message-report/message-report",
     "message-again":"../message-again/message-again",
     "message-follow":"../message-follow/message-follow",
-    "message-count":"../message-count/message-count"
+    "message-count":"../message-count/message-count",
+    "message-consult":"../message-consult/message-consult"
   }
 }

+ 70 - 7
miniprogram/module/chats/components/questionnaire/questionnaire.ts

@@ -1,4 +1,5 @@
 import dayjs from "dayjs";
+import I18nBehavior from "../../../../i18n/behavior";
 import { Post } from "../../../../lib/request/method";
 
 // module/chats/components/questionnaire/questionnaire.ts
@@ -13,7 +14,8 @@ interface Message {
     | "report"
     | "again"
     | "follow"
-    | "count";
+    | "count"
+    | "consult";
 
   payload: AnyObject;
 }
@@ -29,11 +31,13 @@ interface MessageType {
 }
 
 Component({
+  behaviors: [I18nBehavior],
   lifetimes: {
     attached: function () {
       let isAnalysis: number;
       isAnalysis = wx.getStorageSync("isAnalysis");
-      if (isAnalysis === 3 || isAnalysis === 4) {
+      console.log("isAnalysis", isAnalysis);
+      if (isAnalysis === 3 || isAnalysis === 4 || isAnalysis === 5) {
         // 对话管家
         this._start();
       } else if (isAnalysis === 2) {
@@ -57,6 +61,9 @@ Component({
    * 组件的初始数据
    */
   data: {
+    i18n: {
+      chats: { report: '' },
+    },
     inputBoxBottom: 0,
     messages: {} as Record<number, Message>,
     lastId: "",
@@ -69,6 +76,7 @@ Component({
     _timestamp: Date.now(),
     // 防止 _next 被并发/重复触发(例如 classify === 'tongue' 时)
     _requesting: false,
+    consultStarted: false,
   },
   observers: {
     "messages.**"(messages) {
@@ -85,7 +93,9 @@ 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,
       });
@@ -98,6 +108,9 @@ Component({
         Object.assign(questions[index], event.detail);
         this.setData({ "_next.questions": questions });
         this._next();
+      } else if (isAnalysis === 5) {
+        // 在线咨询暂不需要在问卷组件中处理回答
+        return;
       } else {
         // 随访
         this._createMessage({
@@ -108,6 +121,19 @@ Component({
       }
     },
     async _start() {
+      const isAnalysis = wx.getStorageSync("isAnalysis");
+      // 在线咨询:isAnalysis === 5 且 messageType === 3
+      if (isAnalysis === 5 && this.data.messageType === 3) {
+        this.setData({
+          [`_next.classify`]: "",
+          [`_next.dialogId`]: "",
+          [`_next.questions`]: [],
+          _timestamp: Date.now(),
+          consultStarted: false,
+        });
+        this._next();
+        return;
+      }
       try {
         // 获取剩余次数
         const count = await Post(
@@ -157,6 +183,7 @@ Component({
         [`_next.classify`]: "",
         [`_next.dialogId`]: "",
         [`_next.questions`]: [],
+        consultStarted: false,
       });
       // 对于 isAnalysis === 4 的情况,不创建新的 guide 组件,直接触发滚动
       // 对于 isAnalysis === 3 的情况,仍然创建 guide 组件显示三个业务选项
@@ -201,13 +228,40 @@ Component({
         }
       }
       try {
-        // messageType 1 是随访。messageType 2 是健康评估和对话管家
+        // 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()}`,
             type: "again",
             payload: { title: "" },
           });
+        } else if (this.data.messageType === 3) {
+          // 在线咨询
+          // 开始咨询,如果咨询已经开始,则不重新开始
+          if (!this.data.consultStarted) {
+            try {
+              // 开始咨询  获取咨询id
+              const res = await Post(`/consultManage/start`);
+              if (res.data) {
+                // 存储咨询中的id
+                wx.setStorageSync("consultId", res.data);
+                this._createMessage({
+                  id: `consult.${Date.now()}`,
+                  type: "consult",
+                  payload: { title: "咨询开始" },
+                });
+                this.setData({ consultStarted: true });
+                this.triggerEvent("to");
+              }
+            } catch (error: any) {
+              console.error("咨询开始失败", error);
+              wx.showToast({
+                title: error.errMsg ?? `咨询开始失败,请重试!`,
+                icon: "none",
+              });
+            }
+          }
         } else if (this.data.messageType === 2) {
           let data: any = {};
           const res = await Post(
@@ -217,7 +271,7 @@ Component({
           data = res.data;
 
           data.nextQuestions?.forEach((question: any, index: number) => {
-            // isAnalysis 2是随访  3健康管家 4健康评估
+            // isAnalysis 2是随访  3健康管家 4健康评估 5在线咨询
             if (isAnalysis === 2) {
               // 随访
               if (question.css === "tongue") {
@@ -291,9 +345,8 @@ Component({
                 id: "report",
                 type: "report",
                 payload: {
-                  title: `本次问答已结束,历时${
-                    diff || 1
-                  }分钟,非常感谢您的配合!请查看您本次的健康评估情况。`,
+                  title: `本次问答已结束,历时${diff || 1
+                    }分钟,非常感谢您的配合!${this.data.i18n.chats.report}`,
                   url: `/module/health/pages/report/report?id=${data.healthAnalysisReportId}`,
                 },
               });
@@ -356,5 +409,15 @@ Component({
         [`messages.${index - 1}`]: body,
       });
     },
+    // 处理咨询事件
+    handleConsultEvent(event: { detail: { type: string } }) {
+      if (event.detail.type === "end") {
+        // 结束咨询时,通知父组件显示guide菜单组件
+        this.triggerEvent("next", { component: "guide", scroll: true });
+        setTimeout(() => {
+          this.triggerEvent("to");
+        }, 100);
+      }
+    },
   },
 });

+ 11 - 2
miniprogram/module/chats/components/questionnaire/questionnaire.wxml

@@ -5,15 +5,24 @@
   module.exports.show = function (message, type) {
     return message && message.type === type
   }
+   module.exports.returnBottomId = function (index) {
+    return "bottom-" + index;
+  }
 </wxs>
 <!--module/chats/components/questionnaire/questionnaire.wxml-->
-<block wx:for="{{messages}}" wx:key="id">
+<block wx:for="{{messages}}" wx:index="{{index}}" wx:key="id">
   <message-again wx:if="{{_.show(item,'again')}}" bind:nextType="nextType" workId="{{workId}}" bind:boxBottom="boxBottom" bind:scroll="scroll"/>
   <message-count wx:elif="{{_.show(item,'count')}}" payload="{{item.payload}}" id="{{item.id}}" active="{{_.active(lastId,item.id)}}" bind:next="handle" /> 
-  <message-analysis wx:elif="{{_.show(item,'analysis')}}" active="{{_.active(lastId,item.id)}}" id="{{item.id}}" payload="{{item.payload}}" bind:next="handle" messageType="{{messageType}}" />
+  <message-analysis wx:elif="{{_.show(item,'analysis')}}" active="{{_.active(lastId,item.id)}}" id="{{item.id}}"
+   payload="{{item.payload}}" 
+  bind:next="handle"
+   messageType="{{messageType}}" />
+  <message-consult wx:elif="{{_.show(item,'consult')}}" id="{{item.id}}" active="{{_.active(lastId,item.id)}}" bind:boxBottom="boxBottom" bind:scroll="scroll" bind:consultEvent="handleConsultEvent" />
   <message-follow wx:elif="{{_.show(item,'follow')}}" bind:nextType="nextType" workId="{{workId}}" bind:boxBottom="boxBottom" />
   <message-select wx:elif="{{_.show(item,'select')}}" active="{{_.active(lastId,item.id)}}" id="{{item.id}}" payload="{{item.payload}}" bind:next="handle" />
   <message-text wx:elif="{{_.show(item,'text')}}" active="{{_.active(lastId,item.id)}}" id="{{item.id}}" payload="{{item.payload}}" bind:next="handle" />
   <message-report wx:elif="{{_.show(item,'report')}}" active="{{_.active(lastId,item.id)}}" id="{{item.id}}" payload="{{item.payload}}" bind:next="handle" />
   <message-system wx:elif="{{_.show(item,'system')}}" active="{{_.active(lastId,item.id)}}" id="{{item.id}}" payload="{{item.payload}}" />
+  <view id="{{_.returnBottomId(index)}}" style="height: 10rpx;"></view>
+  
 </block>

+ 4 - 0
miniprogram/module/chats/pages/analysis/analysis.json

@@ -0,0 +1,4 @@
+{
+  "navigationBarTitleText": "分析",
+  "usingComponents": {}
+}

+ 1 - 0
miniprogram/module/chats/pages/analysis/analysis.scss

@@ -0,0 +1 @@
+/* module/chats/pages/analysis/analysis.wxss */

+ 66 - 0
miniprogram/module/chats/pages/analysis/analysis.ts

@@ -0,0 +1,66 @@
+// module/chats/pages/analysis/analysis.ts
+Page({
+
+  /**
+   * 页面的初始数据
+   */
+  data: {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面加载
+   */
+  onLoad() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面初次渲染完成
+   */
+  onReady() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面显示
+   */
+  onShow() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面隐藏
+   */
+  onHide() {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面卸载
+   */
+  onUnload() {
+
+  },
+
+  /**
+   * 页面相关事件处理函数--监听用户下拉动作
+   */
+  onPullDownRefresh() {
+
+  },
+
+  /**
+   * 页面上拉触底事件的处理函数
+   */
+  onReachBottom() {
+
+  },
+
+  /**
+   * 用户点击右上角分享
+   */
+  onShareAppMessage() {
+
+  }
+})

+ 4 - 0
miniprogram/module/chats/pages/analysis/analysis.wxml

@@ -0,0 +1,4 @@
+<!--module/chats/pages/analysis/analysis.wxml-->
+<view class="analysis-container">
+  <view class="placeholder"></view>
+</view>

+ 8 - 0
miniprogram/module/chats/pages/consultation-record/consultation-record.json

@@ -0,0 +1,8 @@
+{
+  "renderer": "skyline",
+  "component": true,
+  "usingComponents": {
+    "t-navbar": "tdesign-miniprogram/navbar/navbar",
+    "t-avatar": "tdesign-miniprogram/avatar/avatar"
+  }
+}

+ 151 - 0
miniprogram/module/chats/pages/consultation-record/consultation-record.scss

@@ -0,0 +1,151 @@
+@import "../../../../themes/page.scss";
+
+/* module/chats/pages/consultation-record/consultation-record.wxss */
+
+.page-scroll__container {
+  box-sizing: border-box;
+  overflow-y: auto;
+  padding-top: 10px;
+  background-color: #f5f5f5;
+}
+
+.consultation-container {
+  padding: 40rpx 30rpx;
+  background-color: #ffffff;
+}
+
+.consultation-session {
+  margin-bottom: 60rpx;
+
+  &:last-child {
+    margin-bottom: 0;
+  }
+}
+
+.timestamp {
+  text-align: center;
+  font-size: 24rpx;
+  color: #999999;
+  margin: 0rpx 0 10rpx;
+}
+
+.status-message {
+  text-align: center;
+  font-size: 24rpx;
+  color: #999999;
+  margin: 15rpx 0 15rpx 0;
+}
+
+.messages-list {
+  display: flex;
+  flex-direction: column;
+  gap: 30rpx;
+}
+
+.message-item {
+  display: flex;
+  align-items: flex-start;
+  margin: 20rpx 0 20rpx 0;
+
+  &--agent {
+    justify-content: flex-start;
+  }
+
+  &--user {
+    justify-content: flex-end;
+  }
+}
+
+.message-avatar {
+  flex-shrink: 0;
+}
+
+.message-bubble {
+  max-width: 500rpx;
+  padding: 20rpx 24rpx;
+  border-radius: 12rpx;
+  position: relative;
+  word-wrap: break-word;
+  margin-left: 20rpx;
+
+  &--agent {
+    background-color: #ffffff;
+    border: 1rpx solid #e5e5e5;
+
+    // 三角形尾巴(指向左侧头像)
+    &::before {
+      content: "";
+      position: absolute;
+      left: -10rpx;
+      top: 16rpx;
+      width: 0;
+      height: 0;
+      border-top: 10rpx solid transparent;
+      border-bottom: 10rpx solid transparent;
+      border-right: 10rpx solid #ffffff;
+      z-index: 2;
+    }
+
+    // 三角形边框
+    &::after {
+      content: "";
+      position: absolute;
+      left: -11rpx;
+      top: 16rpx;
+      width: 0;
+      height: 0;
+      border-top: 10rpx solid transparent;
+      border-bottom: 10rpx solid transparent;
+      border-right: 10rpx solid #e5e5e5;
+      z-index: 1;
+    }
+  }
+
+  &--user {
+    background-color: #409eff;
+    margin-right: 20rpx;
+    // 三角形尾巴(指向右侧头像)
+    &::before {
+      content: "";
+      position: absolute;
+      right: -10rpx;
+      top: 16rpx;
+      width: 0;
+      height: 0;
+      border-top: 10rpx solid transparent;
+      border-bottom: 10rpx solid transparent;
+      border-left: 10rpx solid #409eff;
+    }
+  }
+}
+
+.message-text {
+  font-size: 28rpx;
+  color: #000000;
+  line-height: 1.5;
+  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;
+  background: transparent !important;
+  display: block;
+  border-radius: 8rpx;
+}

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

@@ -0,0 +1,130 @@
+import PageContainerBehavior from "../../../../core/behavior/page-container.behavior";
+import I18nBehavior from "../../../../i18n/behavior";
+import { Post } from "../../../../lib/request/method";
+// module/chats/pages/consultation-record/consultation-record.ts
+
+Page({
+  behaviors: [I18nBehavior, PageContainerBehavior],
+  properties: {},
+  data: {
+    i18n: {
+      consultChat: { _: '聊天', title: '记录' },
+    },
+    //有item传过来不分页
+    //是否分页
+    isPage: false,
+    page: 1,
+    pageSize: 2,
+    hasMore: true,
+    isLoading: false,
+    consultationList: [] as Array<{
+      id: number;
+      startTime: string;
+      endTime: string;
+      manualTime: string; //转人工的时间
+      items: Array<{
+        id: number; //咨询记录id
+        consultRecordId: number; //咨询记录id
+        messageContent: string;
+        sendTime: string; //发送时间
+        sendType: string; //	发送类型 1-患者 2-医生 3-系统 4-AI
+        messageType: string; //	消息类型 1-文本 2-图片
+        isRead: string;
+      }>;
+    }>,
+  },
+  observers: {},
+  onLoad(options: any) {
+    console.log(options, "咨询记录数据");
+    if (options.item) {
+      const item = JSON.parse(decodeURIComponent(options.item));
+      this.setData({
+        consultationList: item,
+        isPage: false,
+      });
+    } else {
+      this.setData({
+        isPage: true,
+      });
+      this.loadData();
+    }
+  },
+  // 获取聊天记录的数据
+  async loadData() {
+    if (this.data.isLoading || !this.data.hasMore) return;
+    this.setData({ isLoading: true });
+    try {
+      const res = await Post(
+        `/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) {
+        // 追加新数据到现有列表
+        const newList =
+          this.data.page === 1
+            ? res.data.data
+            : [...this.data.consultationList, ...res.data.data];
+
+        // 判断是否还有更多数据
+        // 方式1: 如果有 total 字段,通过比较已加载数量和总数判断
+        // 方式2: 如果返回的数据长度小于 pageSize,说明没有更多数据了
+        let hasMoreData = false;
+        if (res.data.total !== undefined && res.data.total !== null) {
+          hasMoreData = newList.length < res.data.total;
+        } else {
+          // 如果返回的数据长度等于 pageSize,可能还有更多数据
+          hasMoreData = res.data.data.length >= this.data.pageSize;
+        }
+
+        this.setData({
+          consultationList: newList,
+          page: this.data.page + 1,
+          hasMore: hasMoreData,
+          isLoading: false,
+        });
+      } else {
+        // 没有数据时,第一页清空列表,后续页保留已有数据
+        this.setData({
+          ...(this.data.page === 1 && { consultationList: [] }),
+          hasMore: false,
+          isLoading: false,
+        });
+      }
+    } catch (error) {
+      console.error("加载咨询记录失败", error);
+      this.setData({
+        isLoading: false,
+      });
+    }
+  },
+
+  onScrollToLower() {
+    console.log("onScrollToLower");
+    if (this.data.isPage) {
+      this.loadData();
+    }
+  },
+  //预览图片
+  previewImage(e: any) {
+    const currentUrl = e.currentTarget.dataset.url;
+    // 获取所有咨询记录中所有图片消息的 URL 列表
+    const urls: string[] = [];
+    this.data?.consultationList?.forEach((consultation) => {
+      consultation?.items?.forEach((item) => {
+        if (item?.messageType === "2" && item?.messageContent) {
+          urls.push(item?.messageContent);
+        }
+      });
+    });
+
+    wx.previewImage({
+      current: currentUrl, // 当前显示图片的链接
+      urls: urls.length > 0 ? urls : [currentUrl], // 需要预览的图片链接列表
+      fail: (err) => {
+        console.error("预览图片失败", err);
+      },
+    });
+  },
+});

+ 78 - 0
miniprogram/module/chats/pages/consultation-record/consultation-record.wxml

@@ -0,0 +1,78 @@
+<!--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 
+  style="height: calc(100vh - 180rpx);"
+  bindscrolltolower="onScrollToLower"
+  lower-threshold="{{50}}"
+>
+  <view class="consultation-container">
+    <block wx:for="{{consultationList}}" wx:key="id">
+      <!-- 开始咨询 -->
+      <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">
+            <!-- 系统消息 -->
+            <view wx:if="{{message.sendType === '3'}}" class="message-item message-item--system">
+              <view class="message-bubble message-bubble--system">
+                  <view class="timestamp">{{item.startTime}}</view>
+                <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':''}}">
+                <!-- 文本消息 -->
+                <block wx:if="{{message.messageType === '1'}}">
+                  <text class="message-text message-text--user">{{message.messageContent}}</text>
+                </block>
+                <!-- 图片消息 -->
+                <block wx:elif="{{message.messageType === '2'}}">
+                  <image class="message-image" src="{{message.messageContent}}" mode="aspectFill" show-menu-by-longpress="true" bind:tap="previewImage" data-url="{{message.messageContent}}" />
+                </block>
+              </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':''}}">
+                <!-- 文本消息 -->
+                <block wx:if="{{message.messageType === '1'}}">
+                  <text class="message-text">{{message.messageContent}}</text>
+                </block>
+                <!-- 图片消息 -->
+                <block wx:elif="{{message.messageType === '2'}}">
+                  <image class="message-image" src="{{message.messageContent}}" mode="aspectFill" show-menu-by-longpress="true" bind:tap="previewImage" data-url="{{message.messageContent}}" />
+                </block>
+              </view>
+            </view>
+          </block>
+        </view>
+        
+        <!-- 咨询结束 -->
+        <view class="timestamp">{{item.endTime}}</view>
+        <view class="status-message">{{i18n.consultChat._}}结束</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>
+      <view wx:elif="{{!hasMore}}">没有更多数据了</view>
+    </view>
+  </view>
+</scroll-view>
+
+  

+ 23 - 14
miniprogram/module/chats/pages/index/index.ts

@@ -1,5 +1,6 @@
 import dayjs from "dayjs";
 import PageContainerBehavior from "../../../../core/behavior/page-container.behavior";
+import I18nBehavior from "../../../../i18n/behavior";
 
 // module/chats/pages/index/index.ts
 interface ScrollIntoViewEvent {
@@ -23,7 +24,7 @@ function getScrollcontext(this: any) {
 }
 
 Component({
-  behaviors: [PageContainerBehavior],
+  behaviors: [I18nBehavior, PageContainerBehavior],
   lifetimes: {
     attached() {
       this.setData({
@@ -42,7 +43,7 @@ Component({
       const safeBottom = windowHeight - safeAreaBottom; // 安全区底部距离(px)
       const safeBottomRpx = (750 / systemInfo.windowWidth) * safeBottom; // rpx
       this.setData({
-        paddingBottom: this.data.paddingBottom+safeBottomRpx,
+        paddingBottom: this.data.paddingBottom + safeBottomRpx,
       });
     },
     ready() {
@@ -61,13 +62,16 @@ Component({
     id: { type: Number, value: 0 },
   },
   data: {
+    i18n: {
+      chats: { title: '聊天', analysis: '测评' },
+    },
     date: "",
     messages: {} as Record<number, Message>,
     lastId: "",
     inputBoxBottom: 0,
     tabbarValue: "/module/chats/pages/index/index",
     analysisCount: 0,
-    paddingBottom: 100,  // 底部tabbar高度是100
+    paddingBottom: 100, // 底部tabbar高度是100
   },
   observers: {
     "messages.**"(messages) {
@@ -77,10 +81,11 @@ Component({
   },
   methods: {
     boxBottom(event: boxBottom) {
+      // 直接使用子组件传递的 inputBoxBottom 值
       this.setData({
-        inputBoxBottom: event.detail.inputBoxBottom + 60,
+        inputBoxBottom: event.detail.inputBoxBottom,
       });
-      // const bottom=this.data.paddingBottom;
+      // 更新 scroll-view 的 padding-bottom,确保内容不被输入框遮挡
       this.setData({
         paddingBottom: event.detail.inputBoxBottom,
       });
@@ -108,21 +113,25 @@ Component({
     scrollIntoView(event?: ScrollIntoViewEvent) {
       clearTimeout(getScrollcontext.call(this).timer);
       const id = event?.detail ?? this.data.lastId;
-      const doScroll = (scroll: WechatMiniprogram.ScrollViewContext | undefined) => {
+      const doScroll = (
+        scroll: WechatMiniprogram.ScrollViewContext | undefined
+      ) => {
         if (!scroll) return;
         if (id === "bottom") {
-          scroll.scrollTo({ top: Number.MAX_SAFE_INTEGER, animated: true });
+          // 使用 scrollIntoView 滚动到 #bottom 元素,实现平滑滚动
+          scroll.scrollIntoView(`#bottom`, { alignment: "end" });
         } else if (id) {
           scroll.scrollIntoView(`#${id}`, { alignment: "end" });
         }
         // 再补一次,处理内容异步渲染晚于滚动触发的情况
-        setTimeout(() => {
-          if (id === "bottom") {
-            scroll.scrollTo({ top: Number.MAX_SAFE_INTEGER, animated: true });
-          } else if (id) {
-            scroll.scrollIntoView(`#${id}`, { alignment: "end" });
-          }
-        }, 200);
+        // setTimeout(() => {
+        //   if (id === "bottom") {
+        //     // 使用 scrollIntoView 滚动到 #bottom 元素,实现平滑滚动
+        //     scroll.scrollIntoView(`#bottom`, { alignment: "end" });
+        //   } else if (id) {
+        //     scroll.scrollIntoView(`#${id}`, { alignment: "end" });
+        //   }
+        // }, 200);
       };
 
       getScrollcontext.call(this).timer = setTimeout(() => {

+ 4 - 3
miniprogram/module/chats/pages/index/index.wxml

@@ -6,9 +6,10 @@
     return message && message.component === component;
   }
 </wxs>
+
 <!--module/chats/pages/index/index.wxml-->
 <view class="chats-container">
-  <t-navbar title="对话管家" />
+  <t-navbar title="{{i18n.chats.title}}" />
   <scroll-view 
     id="scrollview" 
     class="page-scroll__container" 
@@ -22,10 +23,10 @@
   >
     <view class="system-wrapper">
       <view class="date">{{date}}</view>
-      <view class="title">对话管家 已进入聊天</view>
+      <view class="title">{{i18n.chats.title}} 已进入聊天</view>
     </view>
     
-    <block wx:for="{{messages}}" wx:key="id">
+    <block wx:for="{{messages}}" wx:index="{{index}}" wx:key="id">
       <chat-guide wx:if="{{(_.show(item, 'guide') && isShowGuide)}}" id="{{item.id}}" active="{{_.active(lastId, item.id)}}" bind:next="handle" bind:to="scrollIntoView" bind:nextType="nextType" bind:getCount="getCount" analysisCount="{{analysisCount}}" />
       <chat-questionnaire wx:if="{{_.show(item, 'questionnaire') && messageType}}" id="{{item.id}}" active="{{_.active(lastId, item.id)}}" bind:next="handle" 
       bind:to="scrollIntoView" bind:nextType="nextType" messageType="{{messageType}}" workId="{{id}}" bind:boxBottom="boxBottom" />

+ 105 - 0
miniprogram/module/chats/towxml/audio-player/Audio.js

@@ -0,0 +1,105 @@
+const fillIn = val => `${val < 10 ? '0' : ''}${val}`,
+    formatTime = _time => {
+        let time = Math.round(_time);
+        let second = Math.round(time % 60),
+            minute = Math.floor(time / 60 % 60),
+            hour = Math.floor(time / 60 / 60);
+        return `${fillIn(hour)}:${fillIn(minute)}:${fillIn(second)}`;
+    };
+
+class Audio{
+    constructor(obj){
+        const _ts = this,
+            option = _ts.option = obj.attrs;
+
+        _ts.loop = option.loop === 'true',
+        _ts.autoplay = option.autoplay === 'true';
+        _ts.create();
+        _ts.index = 0;
+
+        
+    }
+    create(){
+        const _ts = this,
+            option = _ts.option;
+        let audio = _ts.audio = wx.createInnerAudioContext();
+        audio.src = option.src;
+
+        // 说明可以播放了
+        audio.onCanplay(function(){
+            if(_ts.autoplay && !_ts.index){
+                _ts.play();
+            };
+            if(!_ts.autoplay && !_ts.index){
+                _ts.eventCanplay();
+            };
+        });
+
+        // 更新时间
+        audio.onTimeUpdate(function(){
+            //_ts.status = 'update';
+            _ts.duration = audio.duration;
+            _ts.currentTime = audio.currentTime;
+
+            // 定义播放结束
+            if(_ts.duration - _ts.currentTime < 0.5){
+                _ts.index++;
+                if(_ts.loop){
+                    audio.stop();
+                }else{
+                    _ts.stop();
+                };
+                audio.seek(0);
+            };
+            _ts.eventTimeUpdate(formatTime(_ts.duration),formatTime(_ts.currentTime));
+        });
+
+        // 
+        audio.onSeeked(function(){
+            if(_ts.loop){
+                _ts.play();
+            };
+        });
+
+
+
+    }
+    // 播放
+    play(){
+        const _ts = this;
+        _ts.status = 'play';
+        _ts.audio.play();
+        _ts.eventPlay();
+    }
+    // 暂停
+    pause(){
+        const _ts = this;
+        _ts.status = 'pause';
+        _ts.audio.pause();
+        _ts.eventPause();
+    }
+    // 停止
+    stop(){
+        const _ts = this;
+        _ts.status = 'stop';
+        _ts.audio.stop();
+        _ts.eventStop();
+    }
+    // 销毁
+    destroy(){
+        const _ts = this;
+        _ts.stop();
+        _ts.audio.destroy();
+    }
+    eventCanplay(){}
+    eventTimeUpdate(){}
+    eventEnded(){}
+    eventError(){}
+    eventPause(){}
+    eventPlay(){}
+    eventSeeked(){}
+    eventSeeking(){}
+    eventStop(){}
+    eventWaiting(){}
+};
+module.exports = Audio;

+ 107 - 0
miniprogram/module/chats/towxml/audio-player/audio-player.js

@@ -0,0 +1,107 @@
+const Audio = require('./Audio');
+Component({
+	options: {
+		styleIsolation: 'shared'
+	},
+	properties: {
+		data: {
+			type: Object,
+			value: {}
+		}
+	},
+	lifetimes:{
+		// 页面生命周期
+		attached:function(){
+			const _ts = this,
+				audio = _ts.audio = new Audio(this.data.data);
+
+			audio.eventPlay = function(){
+				_ts.setData({tips:{state:'h2w__audio--play',text:'Playing'}});
+			};
+			audio.eventCanplay = function(){
+				_ts.setData({tips:{state:'h2w__audio--readyed',text:'Readyed'}});
+			};
+			audio.eventTimeUpdate = function(duration,currentTime){
+				_ts.setData({time:{currentTime:currentTime,duration:duration,schedule:Math.round(_ts.audio.currentTime) / Math.round(_ts.audio.duration) * 100 + '%'}});
+			};
+			audio.eventPause = function(){
+				_ts.setData({tips:{state:'h2w__audio--pause',text:'Pause'}});
+			};
+			audio.eventStop = function(){
+				_ts.setData({tips:{state:'h2w__audio--end',text:'End'}});
+			};
+
+
+
+
+
+			// // 更新播放状态
+			// _ts.audio.onTimeUpdate = function(duration,currentTime){
+			// 	_ts.setData({
+			// 		playerData:{
+			// 			state:'h2w__audio--play',
+			// 			tips:'Playing',
+			// 			currentTime:currentTime,
+			// 			duration:duration,
+			// 			schedule:_ts.audio.currentTime / _ts.audio.duration * 100 + '%'
+			// 		}
+			// 	});
+			// };
+
+			// _ts.audio.onPause = function(){
+			// 	_ts.setData({playerData:{state:'h2w__audio--pause',tips:'Pause'}});
+			// };
+
+			// _ts.audio.onCanplay = function(){
+			// 	_ts.setData({playerData:{state:'h2w__audio--readyed',tips:'Readyed'}});
+			// };
+
+			// _ts.audio.onError = function(){
+			// 	_ts.setData({playerData:{state:'h2w__audio--error',tips:'Error'}});
+			// };
+
+			// _ts.audio.onEnded = ()=>{
+			// 	_ts.setData({playerData:{state:'h2w__audio--end',tips:'End'}});
+			// };
+
+		},
+		moved:function(){
+			const _ts = this;
+			_ts.audio.stop();
+			_ts.audio.destroy();
+		},
+		detached:()=>{
+			const _ts = this;
+			_ts.audio.stop();
+			_ts.audio.destroy();
+		},
+	},
+	data: {
+		tips:{
+			state:'',
+			text:'--'
+		},
+		time: {
+			currentTime:'00:00:00',
+			duration:'00:00:00',
+			schedule:'0%'
+		}
+	},
+	methods: {
+		playAndPause: function () {
+			const _ts = this,
+				audio = _ts.audio;
+
+			// console.log(audio);
+
+			audio.isTouch = true;
+			if(audio.status === 'update' || audio.status === 'play'){
+				// console.log('pause');
+				audio.pause();
+			}else{
+				// console.log('play');
+				audio.play();
+			};
+		}
+	}
+})

+ 5 - 0
miniprogram/module/chats/towxml/audio-player/audio-player.json

@@ -0,0 +1,5 @@
+{
+  "component": true,
+  "usingComponents": {
+  }
+}

+ 14 - 0
miniprogram/module/chats/towxml/audio-player/audio-player.wxml

@@ -0,0 +1,14 @@
+<view class="h2w__audio {{tips.state || 'h2w__audio--loading'}}" bind:tap="playAndPause">
+    <view class="h2w__audioIcon"></view>
+    <view class="h2w__audioCover">
+        <image class="h2w__audioLoading" src="loading.svg"></image>
+        <image class="h2w__audioCoverImg" src="{{data.attrs.poster}}"></image>
+    </view>
+    <view class="h2w__audioInfo">
+        <view class="h2w__audioTips">{{tips.text || 'Error'}}</view>
+        <view class="h2w__audioSchedule" style="width:{{time.schedule}};"></view>
+        <view class="h2w__audioTitle">{{data.attrs.name}}</view>
+        <view class="h2w__audioAuthor">{{data.attrs.author}}</view>
+        <view class="h2w__audioTime">{{time.currentTime || '00:00:00'}} / {{time.duration || '00:00:00'}}</view>
+    </view>
+</view>

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini