Jelajahi Sumber

首页、患者信息、健康分析

cc12458 1 tahun lalu
induk
melakukan
744f574a71
55 mengubah file dengan 2126 tambahan dan 0 penghapusan
  1. TEMPAT SAMPAH
      miniprogram/module/chats/assets/button-1.bg.png
  2. TEMPAT SAMPAH
      miniprogram/module/chats/assets/button-report.bg.png
  3. TEMPAT SAMPAH
      miniprogram/module/chats/assets/face-1.png
  4. TEMPAT SAMPAH
      miniprogram/module/chats/assets/finger.png
  5. TEMPAT SAMPAH
      miniprogram/module/chats/assets/robot.png
  6. TEMPAT SAMPAH
      miniprogram/module/chats/assets/tongue-1.png
  7. TEMPAT SAMPAH
      miniprogram/module/chats/assets/tongue-2.png
  8. TEMPAT SAMPAH
      miniprogram/module/chats/assets/upload-picture.bg.png
  9. 97 0
      miniprogram/module/chats/common.scss
  10. 8 0
      miniprogram/module/chats/components/guide/guide.json
  11. 25 0
      miniprogram/module/chats/components/guide/guide.scss
  12. 43 0
      miniprogram/module/chats/components/guide/guide.ts
  13. 68 0
      miniprogram/module/chats/components/guide/guide.wxml
  14. 10 0
      miniprogram/module/chats/components/message-analysis/message-analysis.json
  15. 37 0
      miniprogram/module/chats/components/message-analysis/message-analysis.scss
  16. 53 0
      miniprogram/module/chats/components/message-analysis/message-analysis.ts
  17. 42 0
      miniprogram/module/chats/components/message-analysis/message-analysis.wxml
  18. 8 0
      miniprogram/module/chats/components/message-report/message-report.json
  19. 8 0
      miniprogram/module/chats/components/message-report/message-report.scss
  20. 14 0
      miniprogram/module/chats/components/message-report/message-report.ts
  21. 15 0
      miniprogram/module/chats/components/message-report/message-report.wxml
  22. 12 0
      miniprogram/module/chats/components/message-select/message-select.json
  23. 85 0
      miniprogram/module/chats/components/message-select/message-select.scss
  24. 115 0
      miniprogram/module/chats/components/message-select/message-select.ts
  25. 80 0
      miniprogram/module/chats/components/message-select/message-select.wxml
  26. 6 0
      miniprogram/module/chats/components/message-system/message-system.json
  27. 21 0
      miniprogram/module/chats/components/message-system/message-system.scss
  28. 15 0
      miniprogram/module/chats/components/message-system/message-system.ts
  29. 6 0
      miniprogram/module/chats/components/message-system/message-system.wxml
  30. 10 0
      miniprogram/module/chats/components/message-text/message-text.json
  31. 4 0
      miniprogram/module/chats/components/message-text/message-text.scss
  32. 24 0
      miniprogram/module/chats/components/message-text/message-text.ts
  33. 9 0
      miniprogram/module/chats/components/message-text/message-text.wxml
  34. 15 0
      miniprogram/module/chats/components/questionnaire/questionnaire.json
  35. 58 0
      miniprogram/module/chats/components/questionnaire/questionnaire.scss
  36. 138 0
      miniprogram/module/chats/components/questionnaire/questionnaire.ts
  37. 16 0
      miniprogram/module/chats/components/questionnaire/questionnaire.wxml
  38. 11 0
      miniprogram/module/chats/pages/analysis/analysis.json
  39. 52 0
      miniprogram/module/chats/pages/analysis/analysis.scss
  40. 93 0
      miniprogram/module/chats/pages/analysis/analysis.ts
  41. 41 0
      miniprogram/module/chats/pages/analysis/analysis.wxml
  42. 8 0
      miniprogram/module/chats/pages/index/index.json
  43. 7 0
      miniprogram/module/chats/pages/index/index.scss
  44. 66 0
      miniprogram/module/chats/pages/index/index.ts
  45. 16 0
      miniprogram/module/chats/pages/index/index.wxml
  46. 6 0
      miniprogram/pages/home/body-model/body-model.json
  47. 127 0
      miniprogram/pages/home/body-model/body-model.scss
  48. 52 0
      miniprogram/pages/home/body-model/body-model.ts
  49. 48 0
      miniprogram/pages/home/body-model/body-model.wxml
  50. 14 0
      miniprogram/pages/home/home.json
  51. 182 0
      miniprogram/pages/home/home.scss
  52. 207 0
      miniprogram/pages/home/home.ts
  53. 100 0
      miniprogram/pages/home/home.wxml
  54. 32 0
      miniprogram/pages/home/request.ts
  55. 22 0
      miniprogram/pages/home/router.ts

TEMPAT SAMPAH
miniprogram/module/chats/assets/button-1.bg.png


TEMPAT SAMPAH
miniprogram/module/chats/assets/button-report.bg.png


TEMPAT SAMPAH
miniprogram/module/chats/assets/face-1.png


TEMPAT SAMPAH
miniprogram/module/chats/assets/finger.png


TEMPAT SAMPAH
miniprogram/module/chats/assets/robot.png


TEMPAT SAMPAH
miniprogram/module/chats/assets/tongue-1.png


TEMPAT SAMPAH
miniprogram/module/chats/assets/tongue-2.png


TEMPAT SAMPAH
miniprogram/module/chats/assets/upload-picture.bg.png


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

@@ -0,0 +1,97 @@
+.chat-card {
+  padding: 12px;
+  display: flex;
+  justify-content: flex-start;
+  $size: 42px;
+  --avatar-size: 42px;
+
+  &.left {
+    flex-direction: row;
+
+    .chat-card__avatar {
+      margin-right: 8px;
+
+      image {
+        width: 90%;
+        height: 90%;
+      }
+    }
+
+    .chat-card__content {
+      width: calc(90% - var(--avatar-size));
+      border-color: #1B4F34;
+    }
+  }
+
+  &.right {
+    flex-direction: row-reverse;
+
+    .chat-card__avatar {
+      margin-left: 8px;
+    }
+
+    .chat-card__content {
+      border-color: #38FF6E;
+    }
+  }
+
+
+
+  &__avatar {
+    flex: none;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    width: $size;
+    height: $size;
+    border-radius: 50%;
+  }
+
+  &__content {
+    max-width: calc(80% - $size);
+    background-color: rgba(255, 255, 255, 0.04);
+    border-radius: 8px;
+    border-width: 1px;
+    border-style: solid;
+    overflow: hidden;
+  }
+
+
+  &__handle {
+    display: flex;
+    flex-direction: row;
+    justify-content: space-evenly;
+    margin: 12px 0;
+
+    &.disabled {
+      opacity: 0.5;
+    }
+
+    .item {
+      position: relative;
+      width: 190 * 0.5px;
+      height: 54 * 0.5px;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      text-align: center;
+      font-size: 12px;
+      color: var(--primary-color);
+
+      image {
+        position: absolute;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
+      }
+    }
+
+  }
+}
+
+.loading {
+  --td-loading-color: #38FF6E;
+  align-self: center;
+  margin: 0 8px;
+}

+ 8 - 0
miniprogram/module/chats/components/guide/guide.json

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

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

@@ -0,0 +1,25 @@
+@import '../../../../themes/t.cell.scss';
+@import '../../common.scss';
+
+/* module/chats/components/guide/guide.wxss */
+.steward-wrapper {
+  --primary-color: #38FF6E;
+  --td-cell-title-color: var(--primary-color);
+}
+
+.title {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  color: #fff;
+
+  .icon {
+    margin-left: 4px;
+    width: 12px;
+    height: 16px;
+  }
+}
+
+.disabled {
+  opacity: 0.5 !important;
+}

+ 43 - 0
miniprogram/module/chats/components/guide/guide.ts

@@ -0,0 +1,43 @@
+// module/chats/components/guide/guide.ts
+Component({
+  lifetimes: {},
+  properties: {
+    id: { type: String, value: '' },
+    active: { type: Boolean, value: false },
+  },
+  data: {
+    result: [] as string[],
+    result2: false,
+  },
+  methods: {
+    handleA() {
+      if (this.data.active) this.triggerEvent('next', { component: 'questionnaire', scroll: true });
+    },
+    handleB() {
+      if (this.data.active) wx.navigateTo({
+        url: '/module/health/pages/status/status',
+        events: { update: this._update.bind(this) }
+      });
+    },
+    handleC() {
+      if (this.data.active) wx.navigateTo({
+        url: '/module/user/pages/user-edit/user-edit',
+        events: {
+          update2: () => {
+            this.setData({ result2: true });
+            this.triggerEvent('next', { component: 'guide', scroll: true });
+          }
+        }
+      });
+    },
+
+    _update(result: string[]) {
+      if (result.length) {
+        this.setData({ result });
+        this.triggerEvent('next', { component: 'guide', scroll: true });
+      } else {
+        setTimeout(() => wx.showToast({ title: `没有更改项`, icon: 'none' }), 300)
+      }
+    }
+  }
+})

+ 68 - 0
miniprogram/module/chats/components/guide/guide.wxml

@@ -0,0 +1,68 @@
+<wxs module="_">
+  module.exports.getClassName = function (active) {
+    return active ? '' : 'disabled';
+  }
+</wxs>
+<!--module/chats/components/guide/guide.wxml-->
+<view class="chat-card left steward-wrapper">
+  <view class="chat-card__avatar ">
+    <image src="../../assets/robot.png" mode="aspectFill" />
+  </view>
+  <view class="chat-card__content">
+    <t-cell t-class="cell-border-gradient">
+      <view slot="title" class="title">
+        <text>请点击以下内容</text>
+        <image class="icon" src="../../assets/finger.png" mode="aspectFit" />
+      </view>
+    </t-cell>
+    <t-cell t-class="cell-border-gradient {{_.getClassName(active)}}" title="1、健康分析" hover="{{active}}" bind:tap="handleA"></t-cell>
+    <t-cell t-class="cell-border-gradient {{_.getClassName(active)}}" title="2、指标信息管理" hover="{{active}}" bind:tap="handleB"></t-cell>
+    <t-cell t-class="{{_.getClassName(active)}}" title="3、健康信息管理" bordered="{{false}}" hover="{{active}}" bind:tap="handleC"></t-cell>
+  </view>
+</view>
+
+<block wx:if="{{result.length}}">
+  <view class="chat-card right">
+    <view class="chat-card__avatar ">
+      <user-avatar></user-avatar>
+    </view>
+    <view class="chat-card__content">
+      <t-cell bordered="{{false}}">
+        <view slot="title">
+          <div wx:for="{{result}}" wx:key="*this">{{item}}</div>
+        </view>
+      </t-cell>
+    </view>
+  </view>
+  <view class="chat-card left">
+    <view class="chat-card__avatar ">
+      <image src="../../assets/robot.png" mode="aspectFill" />
+    </view>
+    <view class="chat-card__content">
+      <t-cell bordered="{{false}}" title="好的,我们已更新了相关指标数据。"></t-cell>
+    </view>
+  </view>
+</block>
+
+<block wx:if="{{result2}}">
+  <!-- <view class="chat-card right">
+    <view class="chat-card__avatar ">
+      <user-avatar></user-avatar>
+    </view>
+    <view class="chat-card__content">
+      <t-cell bordered="{{false}}">
+        <view slot="title">
+          <div wx:for="{{result}}" wx:key="*this">{{item}}</div>
+        </view>
+      </t-cell>
+    </view>
+  </view> -->
+  <view class="chat-card left">
+    <view class="chat-card__avatar ">
+      <image src="../../assets/robot.png" mode="aspectFill" />
+    </view>
+    <view class="chat-card__content">
+      <t-cell bordered="{{false}}" title="好的,我们已更新了您的健康信息相关数据。"></t-cell>
+    </view>
+  </view>
+</block>

+ 10 - 0
miniprogram/module/chats/components/message-analysis/message-analysis.json

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

+ 37 - 0
miniprogram/module/chats/components/message-analysis/message-analysis.scss

@@ -0,0 +1,37 @@
+@import '../../../../themes/t.cell.scss';
+@import '../../common.scss';
+
+/* module/chats/components/message-analysis/message-analysis.wxss */
+.tips {
+  font-size: 12px;
+  color: #ff611b;
+  padding: 12px var(--td-cell-horizontal-padding, 32rpx) 0;
+}
+
+.gallery-wrapper {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-evenly;
+  margin: 12px 0;
+
+  .container {
+    width: 30%;
+    text-align: center;
+    color: var(--td-text-color-secondarycontainer, #929292);
+
+    image {
+      width: 100%;
+      height: 75px;
+    }
+
+    text {
+      margin-top: 8px;
+    }
+  }
+}
+
+.source {
+  .container {
+    width: 96px !important;
+  }
+}

+ 53 - 0
miniprogram/module/chats/components/message-analysis/message-analysis.ts

@@ -0,0 +1,53 @@
+interface Gallery {
+  label?: string;
+  src: string;
+}
+
+// module/chats/components/message-analysis/message-analysis.ts
+
+const defaultGallery = {
+  'tongueImgUrl': '',
+  'tongueBackImgUrl': '',
+  'faceImgUrl': '',
+}
+
+Component({
+  properties: {
+    payload: { type: Object, value: { title: '', description: '' } },
+    active: { type: Boolean, value: false },
+  },
+  data: {
+    examples: [
+      { label: '舌面举例', src: '../../assets/tongue-1.png' },
+      { label: '舌下举例', src: '../../assets/tongue-2.png' },
+      { label: '面部举例', src: '../../assets/face-1.png' },
+    ] as Gallery[],
+    source: [] as Gallery[],
+  },
+
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+    onConfirm() {
+      if (this.data.source.length) return;
+      wx.navigateTo({
+        url: '/module/chats/pages/analysis/analysis',
+        events: { update: (data: any[]) => this._update(data) }
+      });
+    },
+    onCancel() {
+      this.triggerEvent('next', defaultGallery);
+    },
+
+    _update(source: (Gallery & { target: string; })[]) {
+      this.setData({ source });
+      const data = {} as AnyObject;
+      for (const item of source) { data[item.target] = item.src; }
+      this.triggerEvent('next', {
+        ...defaultGallery,
+        ...data,
+      });
+    }
+  }
+})

+ 42 - 0
miniprogram/module/chats/components/message-analysis/message-analysis.wxml

@@ -0,0 +1,42 @@
+<!--module/chats/components/message-analysis/message-analysis.wxml-->
+<view class="chat-card left">
+  <view class="chat-card__avatar ">
+    <image src="../../assets/robot.png" mode="aspectFill" />
+  </view>
+  <view class="chat-card__content">
+    <t-cell t-class="cell-border-gradient" title="{{payload.title}}" description="{{payload.description}}">
+    </t-cell>
+    <view class="tips">注意下面是舌面、舌下、面部的拍摄标准哦!</view>
+    <view class="gallery-wrapper">
+      <view class="container" wx:for="{{examples}}" wx:key="src">
+        <image src="{{item.src}}" mode="aspectFit" />
+        <text>{{item.label}}</text>
+      </view>
+    </view>
+
+    <view class="chat-card__handle {{active ? '' : 'disabled'}}">
+      <!-- <view class="item" bind:tap="onCancel">
+        <image src="../../assets/button-1.bg.png" mode="aspectFit" />
+        <view>算了,不传</view>
+      </view> -->
+      <view class="item" bind:tap="onConfirm">
+        <image src="../../assets/button-1.bg.png" mode="aspectFit" />
+        <view>点击上传</view>
+      </view>
+    </view>
+  </view>
+</view>
+
+<view class="chat-card right" wx:if="{{source.length}}">
+  <view class="chat-card__avatar ">
+    <user-avatar></user-avatar>
+  </view>
+  <view class="chat-card__content">
+    <view class="gallery-wrapper source">
+      <view class="container" wx:for="{{source}}" wx:key="src">
+        <image src="{{item.src}}" mode="aspectFit" />
+      </view>
+    </view>
+  </view>
+  <t-loading wx:if="{{active}}" t-class="loading" theme="spinner" size="40rpx" class="wrapper" />
+</view>

+ 8 - 0
miniprogram/module/chats/components/message-report/message-report.json

@@ -0,0 +1,8 @@
+{
+  "renderer": "skyline",
+  "component": true,
+  "pureDataPattern": "^_",
+  "usingComponents": {
+    "t-cell": "tdesign-miniprogram/cell/cell"
+  }
+}

+ 8 - 0
miniprogram/module/chats/components/message-report/message-report.scss

@@ -0,0 +1,8 @@
+@import '../../../../themes/t.cell.scss';
+@import '../../common.scss';
+/* module/chats/components/message-report/message-report.wxss */
+
+.chat-card__handle .item {
+  width: 280px * 0.5;
+  height: 52px * 0.5;
+}

+ 14 - 0
miniprogram/module/chats/components/message-report/message-report.ts

@@ -0,0 +1,14 @@
+// module/chats/components/message-report/message-report.ts
+Component({
+  properties: {
+    payload: { type: Object, value: { title: '', url: '' } }
+  },
+  data: {},
+  methods: {
+    handle() {
+      console.log(this.data.payload);
+      
+      wx.navigateTo({ url: this.data.payload.url })
+    }
+  }
+})

+ 15 - 0
miniprogram/module/chats/components/message-report/message-report.wxml

@@ -0,0 +1,15 @@
+<!--module/chats/components/message-report/message-report.wxml-->
+<view class="chat-card left">
+  <view class="chat-card__avatar ">
+    <image src="../../assets/robot.png" mode="aspectFill" />
+  </view>
+  <view class="chat-card__content">
+    <t-cell t-class="cell-border-gradient" title="{{payload.title}}" description="{{payload.description}}">
+    </t-cell>
+    <view class="chat-card__handle">
+      <view class="item" bind:tap="handle">
+        <image src="../../assets/button-report.bg.png" mode="aspectFit" />
+      </view>
+    </view>
+  </view>
+</view>

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

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

+ 85 - 0
miniprogram/module/chats/components/message-select/message-select.scss

@@ -0,0 +1,85 @@
+@import '../../../../themes/t.cell.scss';
+@import '../../common.scss';
+
+.form-picker {
+  &__header {
+    display: flex;
+    align-items: center;
+    height: 116rpx;
+
+    .title {
+      flex: 1;
+      text-align: center;
+      font-weight: 600;
+      font-size: 36rpx;
+      color: var(--td-text-color-primary);
+    }
+
+    .btn {
+      font-size: 32rpx;
+      padding: 32rpx;
+
+      &--cancel {
+        color: var(--td-text-color-secondary);
+      }
+
+      &--confirm {
+        color: var(--primary-color);
+      }
+    }
+  }
+
+  &__content {
+    padding: 0 12px;
+    box-sizing: border-box;
+  }
+}
+
+/* module/chats/components/message-select/message-select.wxss */
+
+.card {
+  position: relative;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  padding: 4px;
+  font-size: 28rpx;
+  text-align: center;
+  border-radius: 12rpx;
+  overflow: hidden;
+  box-sizing: border-box;
+  background-color: var(--td-bg-color-secondarycontainer);
+  border: 1px solid var(--td-bg-color-container, #fff);
+
+  &--active {
+    border-color: var(--td-bg-color-container, #9ea1a5);
+
+    &::after {
+      content: '';
+      display: block;
+      position: absolute;
+      left: 0;
+      top: 0;
+      width: 0;
+      height: 0;
+      border-width: 28px 28px 28px 0;
+      border-style: solid;
+      border-color: var(--td-bg-color-container) transparent transparent transparent;
+      border: 14px solid var(--td-bg-color-container, #0052d9);
+      border-bottom-color: transparent;
+      border-right-color: transparent;
+    }
+  }
+
+  &--disabled {
+    opacity: 0.5;
+  }
+
+  &__icon {
+    color: var(--primary-color, #fff);
+    position: absolute;
+    left: 1.5px;
+    top: 1.5px;
+    z-index: 1;
+  }
+}

+ 115 - 0
miniprogram/module/chats/components/message-select/message-select.ts

@@ -0,0 +1,115 @@
+// module/chats/components/message-select/message-select.ts
+interface Option {
+  id: string;
+  name: string;
+  checked?: boolean;
+  options: Option[] | null;
+}
+interface HandleEvent {
+  mark: {
+    type: 'sub' | 'options';
+    index: number;
+    item: Option;
+  }
+}
+
+Component({
+  properties: {
+    payload: { type: Object, value: { title: '', multiple: false, options: [] }, result: '' },
+    active: { type: Boolean, value: false },
+  },
+  data: {
+    options: [] as Option[],
+    subOptions: [] as Option[],
+    multiple: false,
+    itemHeight: 48,
+
+    result: '',
+  },
+  observers: {
+    'payload.options'(options) {
+      this.setData({ options });
+    }
+  },
+  methods: {
+    async handle(event: HandleEvent) {
+      console.log(this.data.active);
+
+      const { mark: { type, item, index } } = event;
+      if (!item || !this.data.active) return;
+
+      switch (type) {
+        case 'options':
+          const options = this._handle(this.data.options, item, index, this.data.payload.multiple);
+          this.setData({ options });
+          break;
+        case 'sub':
+          const subOptions = this._handle(this.data.subOptions, item, index, this.data.multiple);
+          this.setData({ subOptions });
+          break;
+      }
+
+      console.log(this.data);
+
+    },
+    _handle(options: Option[], item: Option, index: number, multiple?: boolean) {
+      const checked = !item.checked;
+      if (checked) {
+        const fn = () => {
+          if (multiple) {
+            options[index].checked = checked;
+          } else {
+            options.forEach(option => { option.checked = option.id === item.id; })
+          }
+          return options;
+        }
+        if (item.options?.filter(option => !(<any>option)?.hide).length) {
+          this.setData({
+            subTitle: item.name,
+            subOptions: item.options,
+            multiple: (<any>item).css === 'checkbox'
+          })
+          this.onCancel = () => {
+            this.setData({ subOptions: [] })
+          }
+          this.onConfirm = () => {
+            if (!this.data.subOptions.some(option => option.checked)) {
+              wx.showToast({ title: '请至少选择一项', icon: 'error' })
+            } else {
+              const options = fn();
+              options[index].options = this.data.subOptions;
+              this.setData({ subOptions: [], options })
+            } 
+          }
+        } else {
+          return fn();
+        }
+      } else {
+        options[index].checked = !item.checked;
+      }
+      return options;
+    },
+    onCancel() { },
+    onConfirm() { },
+    onSubmit() {
+      if (!this.data.options.some(option => option.checked)) {
+        wx.showToast({ title: '请至少选择一项', icon: 'error' })
+      } else {
+        console.log(this.data.options);
+        const result = this.data.options
+          .filter((item: any) => item?.checked && !item?.hide)
+          .map((option: any) => {
+            const sub = option.options
+              ?.filter((item: any) => item?.checked && !item?.hide)
+              .map((item: any) => item.name)
+              .join(', ');
+            return [option.name, sub].filter(Boolean).join(': ')
+          })
+          .join('; ');
+        this.setData({ result })
+
+        this.triggerEvent('next', { options: this.data.options });
+      }
+    }
+  }
+})

+ 80 - 0
miniprogram/module/chats/components/message-select/message-select.wxml

@@ -0,0 +1,80 @@
+<wxs module="_">
+  module.exports.getClassName = function (option) {
+    if (!option) return '';
+    if (option.disabled) return 'card--disabled';
+    if (option.checked) return 'card--active';
+  }
+  module.exports.maxHeight = function (options, height, maxHeight) {
+    var rows = Math.ceil(options.filter(function (option) { return !option.hide }).length / 3);
+    return Math.min(rows * (height + 8), maxHeight || 350);
+  }
+  module.exports.subName = function (option) {
+    if (!option.options) return '';
+    var options = option.options
+      .filter(function (option) { return option.checked; })
+      .map(function (option) { return option.name; })
+    if (options.length) return ':' + options.join(' ');
+    return ''
+  }
+</wxs>
+<!--module/chats/components/message-select/message-select.wxml-->
+<view class="chat-card left">
+  <view class="chat-card__avatar ">
+    <image src="../../assets/robot.png" mode="aspectFill" />
+  </view>
+  <view class="chat-card__content">
+    <t-cell t-class="cell-border-gradient" title="{{payload.title}}"></t-cell>
+    <scroll-view class="options-wrapper" type="custom" scroll-y style="height: {{_.maxHeight(options, itemHeight)}}px;" mark:type="options" bind:tap="handle">
+      <grid-builder list="{{options}}" cross-axis-count="3" cross-axis-gap="8" main-axis-gap="8" padding="{{[4,4,4,4]}}">
+        <view slot:item slot:index class="card {{_.getClassName(item)}}" style="height: {{itemHeight}}px;" mark:item="{{item}}" mark:index="{{index}}">
+          <t-icon wx:if="{{item.checked}}" name="check" t-class="card__icon" ariaHidden="{{true}}" />
+          <text overflow="ellipsis" max-lines="2">{{item.name}}{{_.subName(item)}}</text>
+        </view>
+      </grid-builder>
+    </scroll-view>
+    <view class="chat-card__handle {{active ? '' : 'disabled'}}">
+      <view class="item" bind:tap="onSubmit">
+        <image src="../../assets/button-1.bg.png" mode="aspectFit" />
+        <view>提交</view>
+      </view>
+    </view>
+  </view>
+</view>
+
+<view class="chat-card right" wx:if="{{result}}">
+  <view class="chat-card__avatar ">
+    <user-avatar></user-avatar>
+  </view>
+  <view class="chat-card__content">
+    <t-cell title="{{result}}" bordered="{{false}}"></t-cell>
+  </view>
+  <t-loading wx:if="{{active}}" t-class="loading" theme="spinner" size="40rpx" class="wrapper" />
+</view>
+
+<t-popup wx:if="{{subOptions.length}}" t-class="form-picker__inner" visible="{{true}}" bind:visible-change="onCancel" placement="bottom" close-on-overlay-click="{{false}}">
+  <view class="form-picker__header">
+    <view class="btn btn--cancel" aria-role="button" bind:tap="onCancel">取消</view>
+    <view class="title">{{subTitle}}</view>
+    <view class="btn btn--confirm" aria-role="button" bind:tap="onConfirm">确定</view>
+  </view>
+  <view class="form-picker__content">
+    <scroll-view class="options-wrapper" type="custom" scroll-y style="height: {{_.maxHeight(subOptions, itemHeight)}}px;" mark:type="sub" bind:tap="handle">
+      <grid-builder list="{{subOptions}}" cross-axis-count="3" cross-axis-gap="8" main-axis-gap="8" padding="{{[4,4,4,4]}}">
+        <view slot:item slot:index class="card {{_.getClassName(item)}}" style="height: {{itemHeight}}px;" mark:item="{{item}}" mark:index="{{index}}">
+          <t-icon wx:if="{{item.checked}}" name="check" t-class="card__icon" ariaHidden="{{true}}" />
+          <text overflow="ellipsis" max-lines="2">{{item.name}}{{_.subName(item)}}</text>
+        </view>
+      </grid-builder>
+    </scroll-view>
+
+
+    <!-- <scroll-view type="list" scroll-y style="{{_.maxHeight(subOptions)}}">
+      <view class="options-wrapper" mark:type="sub" bind:tap="handle">
+        <view wx:for="{{subOptions}}" wx:key="id" class="card {{_.getClassName(item)}}" style="height: {{itemHeight}}px;" mark:item="{{item}}" mark:index="{{index}}">
+          <t-icon wx:if="{{item.checked}}" name="check" t-class="card__icon" ariaHidden="{{true}}" />
+          <text overflow="ellipsis" max-lines="3">{{item.name}}</text>
+        </view>
+      </view>
+    </scroll-view> -->
+  </view>
+</t-popup>

+ 6 - 0
miniprogram/module/chats/components/message-system/message-system.json

@@ -0,0 +1,6 @@
+{
+  "renderer": "skyline",
+  "component": true,
+  "pureDataPattern": "^_",
+  "usingComponents": {}
+}

+ 21 - 0
miniprogram/module/chats/components/message-system/message-system.scss

@@ -0,0 +1,21 @@
+/* module/chats/components/message-system/message-system.wxss */
+.system-wrapper {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  margin: 6px 0;
+
+  .title {
+    padding: 4px 12px;
+    font-size: 12px;
+    color: #7F96A7;
+    background-color: #1B4F34;
+    border-radius: 5px;
+  }
+
+  .date {
+    font-size: 12px;
+    color: #999;
+    margin-bottom: 6px;
+  }
+}

+ 15 - 0
miniprogram/module/chats/components/message-system/message-system.ts

@@ -0,0 +1,15 @@
+import dayjs from "dayjs";
+// module/chats/components/message-system/message-system.ts
+Component({
+  properties: {
+    payload: { type: Object, value: { title: '', date: '' } }
+  },
+  observers: {
+    'payload.date'(value) {
+      if (value) this.setData({ date: dayjs(value).format('MM-DD HH:mm:ss') })
+    }
+  },
+  data: {
+    date:''
+  }
+})

+ 6 - 0
miniprogram/module/chats/components/message-system/message-system.wxml

@@ -0,0 +1,6 @@
+<!--module/chats/components/message-system/message-system.wxml-->
+
+<view class="system-wrapper">
+  <view class="date">{{date}}</view>
+  <view class="title">{{payload.title}}</view>
+</view>

+ 10 - 0
miniprogram/module/chats/components/message-text/message-text.json

@@ -0,0 +1,10 @@
+{
+  "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"
+  }
+}

+ 4 - 0
miniprogram/module/chats/components/message-text/message-text.scss

@@ -0,0 +1,4 @@
+@import '../../../../themes/t.cell.scss';
+@import '../../common.scss';
+
+/* module/chats/components/message-text/message-text.wxss */

+ 24 - 0
miniprogram/module/chats/components/message-text/message-text.ts

@@ -0,0 +1,24 @@
+// module/chats/components/message-text/message-text.ts
+Component({
+
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+    payload: { type: Object, value: { title: '' } }
+  },
+
+  /**
+   * 组件的初始数据
+   */
+  data: {
+
+  },
+
+  /**
+   * 组件的方法列表
+   */
+  methods: {
+
+  }
+})

+ 9 - 0
miniprogram/module/chats/components/message-text/message-text.wxml

@@ -0,0 +1,9 @@
+<!--module/chats/components/message-text/message-text.wxml-->
+<view class="chat-card left">
+  <view class="chat-card__avatar ">
+    <image src="../../assets/robot.png" mode="aspectFill" />
+  </view>
+  <view class="chat-card__content">
+    <t-cell t-class="cell-border-gradient" title="{{payload.title}}"></t-cell>
+  </view>
+</view>

+ 15 - 0
miniprogram/module/chats/components/questionnaire/questionnaire.json

@@ -0,0 +1,15 @@
+{
+  "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",
+    "message-analysis": "../message-analysis/message-analysis",
+    "message-select": "../message-select/message-select",
+    "message-system": "../message-system/message-system",
+    "message-text": "../message-text/message-text",
+    "message-report": "../message-report/message-report"
+  }
+}

+ 58 - 0
miniprogram/module/chats/components/questionnaire/questionnaire.scss

@@ -0,0 +1,58 @@
+@import '../../../../themes/t.cell.scss';
+@import '../../common.scss';
+
+/* module/chats/components/questionnaire/questionnaire.wxss */
+.options-wrapper {
+  display: flex;
+  flex-direction: row;
+  
+  .item {
+    flex: 1 1 auto;
+    margin: 8px;
+  }
+  .item {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    padding: 4px;
+    font-size: 28rpx;
+    text-align: center;
+    border-radius: 12rpx;
+    overflow: hidden;
+    box-sizing: border-box;
+    background-color: var(--td-bg-color-secondarycontainer);
+    border: 1px solid var(--td-bg-color-container, #fff);
+  
+    &--active {
+      border-color: var(--td-bg-color-container, #9ea1a5);
+  
+      &::after {
+        content: '';
+        display: block;
+        position: absolute;
+        left: 0;
+        top: 0;
+        width: 0;
+        height: 0;
+        border-width: 28px 28px 28px 0;
+        border-style: solid;
+        border-color: var(--td-bg-color-container) transparent transparent transparent;
+        border: 14px solid var(--td-bg-color-container, #0052d9);
+        border-bottom-color: transparent;
+        border-right-color: transparent;
+      }
+    }
+  
+    &--disabled {
+      opacity: 0.5;
+    }
+  
+    &__icon {
+      color: var(--primary-color, #fff);
+      position: absolute;
+      left: 1.5px;
+      top: 1.5px;
+      z-index: 1;
+    }
+  }
+}

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

@@ -0,0 +1,138 @@
+import dayjs from "dayjs";
+import { Post } from "../../../../lib/request/method";
+
+// module/chats/components/questionnaire/questionnaire.ts
+
+interface Message {
+  id: string;
+  type: 'system' | 'analysis' | 'select' | 'text' | 'report';
+  payload: AnyObject;
+}
+
+interface HandleEvent {
+  target: { id: string; };
+  detail: AnyObject;
+  type: 'next';
+}
+
+Component({
+  lifetimes: {
+    attached() { this._start(); }
+  },
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+
+  },
+
+  /**
+   * 组件的初始数据
+   */
+  data: {
+    messages: {} as Record<number, Message>,
+    lastId: '',
+    _next: {
+      dialogId: "",
+      questions: []
+    } as AnyObject,
+
+    _timestamp: Date.now(),
+  },
+  observers: {
+    'messages.**'(messages) {
+      const message = Object.values(messages).pop() as Message;
+      this.setData({ lastId: message?.id });
+    }
+  },
+  methods: {
+    handle(event: HandleEvent) {
+      const index = event.target.id.split('.').pop() ?? 0;
+      console.log(index, event.target.id);
+
+
+      const questions = this.data._next.questions;
+      Object.assign(questions[index], event.detail);
+      this.setData({ '_next.questions': questions })
+      console.log(this.data._next);
+
+      this._next()
+    },
+    _start() {
+      this._createMessage({
+        id: `start`, type: 'system',
+        payload: { title: '对话管家 已进入聊天', date: this.data._timestamp },
+      }, {
+        [`_next.dialogId`]: '',
+        [`_next.questions`]: [],
+      })
+      this._next();
+    },
+    _end() {
+      this._createMessage({
+        id: `end`, type: 'system',
+        payload: { title: '对话管家 已结束聊天', date: Date.now() },
+      }, {
+        [`_next.dialogId`]: '',
+        [`_next.questions`]: [],
+      })
+      this.triggerEvent('next', { component: 'guide', scroll: true });
+    },
+    async _next() {
+      const { data } = await Post(`/dialogueManage/dialog`, this.data._next);
+      data.nextQuestions?.forEach((question: any, index: number) => {
+        if (question.css === 'tongue') {
+          this._createMessage({
+            id: `${question.classify}.${question.id}.${index}`, type: 'analysis',
+            payload: { title: question.title }
+          });
+        } else if (question.css === 'text') {
+          this._createMessage({
+            id: `${question.classify}.${question.id}.${index}`, type: 'text',
+            payload: { title: question.content }
+          });
+        } else if (['select', 'checkbox'].includes(question.css)) {
+          this._createMessage({
+            id: `${question.classify}.${question.id}.${index}`, type: 'select',
+            payload: {
+              title: question.title,
+              options: question.options,
+              multiple: question.css === 'checkbox',
+            }
+          })
+        } else if (question.over) {
+          return this._end();
+        }
+      });
+      if (data.classify === 'report') {
+        const diff = dayjs().diff(this.data._timestamp, 'm');
+        this._createMessage({
+          id: 'report', type: 'report',
+          payload: {
+            title: `本次问答已结束,历史${diff || 1}分钟,非常感谢您的配合!请查看您本次的健康评估情况。`,
+            url: `/module/health/pages/report/report?id=${data.healthAnalysisReportId}`
+          }
+        })
+      }
+      if (data.over) return this._end();
+
+      this.setData({
+        [`_next.dialogId`]: data.dialogId,
+        [`_next.questions`]: data.nextQuestions,
+      })
+      this.triggerEvent('to');
+    },
+
+    _createMessage(body: Message, data?: Record<string, any>) {
+      const messages = this.data.messages;
+      const index = Object.keys(messages).length;
+      this.setData({
+        [`messages.${index}`]: body,
+        ...data,
+      })
+
+      console.log(this.data.messages);
+
+    }
+  }
+})

+ 16 - 0
miniprogram/module/chats/components/questionnaire/questionnaire.wxml

@@ -0,0 +1,16 @@
+<wxs module="_">
+  module.exports.active = function (lastId, id) {
+    return lastId === id;
+  }
+  module.exports.show = function (message, type) {
+    return message && message.type === type
+  }
+</wxs>
+<!--module/chats/components/questionnaire/questionnaire.wxml-->
+<block wx:for="{{messages}}" wx:key="id">
+  <message-system wx:if="{{_.show(item,'system')}}" active="{{_.active(lastId,item.id)}}" id="{{item.id}}" payload="{{item.payload}}" bind:next="handle" />
+  <message-analysis wx:if="{{_.show(item,'analysis')}}" active="{{_.active(lastId,item.id)}}" id="{{item.id}}" payload="{{item.payload}}" bind:next="handle" />
+  <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" />
+</block>

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

@@ -0,0 +1,11 @@
+{
+  "renderer": "skyline",
+  "component": true,
+  "pureDataPattern": "^_",
+  "usingComponents": {
+    "t-cell": "tdesign-miniprogram/cell/cell",
+    "t-icon": "tdesign-miniprogram/icon/icon",
+    "t-loading": "tdesign-miniprogram/loading/loading",
+    "form-button": "../../../../components/button/button"
+  }
+}

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

@@ -0,0 +1,52 @@
+@import '../../../../themes/page.scss';
+
+/* module/chats/pages/analysis/analysis.wxss */
+.page-scroll__container {
+  flex: 0 1 auto;
+  height: var(--page-container-safeHeight, 100vh);
+
+  padding-top: 12px;
+}
+
+.upload-card {
+  margin: 0 12px;
+  padding: 12px;
+  border: 1px solid var(--td-bg-color-secondarycontainer, #1B4F34);
+  border-radius: 8px;
+  background-color: rgba(255, 255, 255, 0.04);
+
+  &__header {
+    display: flex;
+    flex-direction: row;
+  }
+
+  &__content {
+    margin-top: 24px;
+
+    .row {
+      display: flex;
+      flex-direction: row;
+      align-items: center;
+      margin: 12px 0;
+    }
+
+    .col {
+      flex: 1 1 50%;
+      text-align: center;
+
+      display: flex;
+      flex-direction: row;
+      justify-content: center;
+      align-items: center;
+    }
+
+    .example {
+      height: 140px;
+    }
+
+    .picture {
+      width: 126px;
+      height: 126px;
+    }
+  }
+}

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

@@ -0,0 +1,93 @@
+import PageContainerBehavior from "../../../../core/behavior/page-container.behavior";
+import { upload } from "../../../../lib/request/upload";
+
+// module/chats/pages/analysis/analysis.ts
+
+Component({
+  behaviors: [PageContainerBehavior],
+  options: {},
+  properties: {},
+  data: {
+    uploadList: [
+      { target: 'tongueImgUrl', required: true, label: '舌面图', src: '../../assets/tongue-1.png' },
+      { target: 'tongueBackImgUrl', required: true, label: '舌下图', src: '../../assets/tongue-2.png' },
+      { target: 'faceImgUrl', required: false, label: '面部图', src: '../../assets/face-1.png' },
+    ],
+    thumbnail: [] as string[],
+    original: [
+      // 'http://121.43.162.141:9300/statics/2024/07/13/1_20240713152314A029.JPG',
+      // 'http://121.43.162.141:9300/statics/2024/07/13/2_20240713152711A030.JPG',
+      // 'http://121.43.162.141:9300/statics/2024/07/13/3_20240713152819A031.png',
+    ] as string[],
+    status: [false, false, false],
+    _queue: {} as AnyObject,
+  },
+  methods: {
+    handle(event: WechatMiniprogram.TouchEvent) {
+      const { handle, index } = event.mark as AnyObject;
+      console.log(handle, index, handle === 'upload:delete', event.mark);
+      switch (handle) {
+        case 'preview':
+          break;
+        case 'upload':
+          this._chooseMedia(index).then(src => this._uploadMedia(index, src))
+          break;
+        case 'upload:delete':
+          this._deleteMedia(index);
+          break;
+      }
+    },
+    _chooseMedia(index: number) {
+      return wx.chooseMedia({ count: 1, mediaType: ['image'], camera: 'front' })
+        .then(res => {
+          const src = res.tempFiles[0].tempFilePath;
+          this.setData({ [`thumbnail.${index}`]: src });
+          return src;
+        })
+        .catch(({ errMsg }) => {
+          const message = (<AnyObject>{ 'chooseMedia:fail cancel': '取消上传' })[errMsg] ?? errMsg;
+          wx.showToast({ title: message, icon: 'none' });
+          throw { errMsg };
+        })
+    },
+    _deleteMedia(index: number) {
+      this.setData({
+        [`thumbnail.${index}`]: '',
+        [`original.${index}`]: '',
+      })
+    },
+    _uploadMedia(index: number, src?: string) {
+      src ??= this.data.thumbnail[index];
+      upload({
+        params: { name: 'file', file: src! },
+        transform({ data }) { return (<any>data).url; }
+      }).then(src => {
+        this.setData({ [`original.${index}`]: src })
+      }, () => {
+        wx.showToast({ title: '上传失败', icon: 'error' })
+        this.setData({
+          [`thumbnail.${index}`]: '',
+          [`original.${index}`]: '',
+        })
+      })
+    },
+    onSubmit() {
+      const data = [];
+      for (let index = 0; index < this.data.uploadList.length; index++) {
+        const item = this.data.uploadList[index];
+        if (item.required) {
+          if (this.data._queue[index]) {
+            wx.showToast({ title: `请等待${item.label}上传完成`, icon: 'loading' });
+            return;
+          } else if (!this.data.original[index]) {
+            wx.showToast({ title: `请上传${item.label}`, icon: 'none' });
+            return;
+          }
+        }
+        if (this.data.original[index]) data.push({ target: item.target, src: this.data.original[index], })
+      }
+      this.getOpenerEventChannel().emit('update', data)
+      wx.navigateBack()
+    }
+  }
+})

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

@@ -0,0 +1,41 @@
+<!--module/chats/pages/analysis/analysis.wxml-->
+<t-navbar title="舌面象上传" left-arrow />
+<scroll-view class="page-scroll__container" type="list" scroll-y style="{{containerStyle}}">
+  <view class="upload-card" wx:for="{{uploadList}}" wx:key="{{item.target}}" mark:index="{{index}}" mark:target="{{item.target}}" bind:tap="handle">
+    <view class="upload-card__header">
+      <text>{{item.label}}上传</text>
+      <text wx:if="{{item.required}}" style="color: #ff611b;">(必传)</text>
+    </view>
+    <view class="upload-card__content">
+      <view class="row">
+        <image class="col example" mark:handle="preview" src="{{item.src}}" mode="aspectFit" />
+        <image wx:if="{{thumbnail[index]}}" class="col picture" mark:handle="preview:thumbnail" src="{{thumbnail[index]}}" mode="aspectFit"></image>
+        <!-- <image wx:elif="{{original[index]}}" class="col picture" mark:handle="preview:original" src="{{original[index]}}" mode="aspectFit"></image> -->
+        <image wx:else class="col picture" mark:handle="upload" src="../../assets/upload-picture.bg.png" mode="aspectFit" />
+      </view>
+      <view class="row">
+        <view class="col">示例</view>
+        <view class="col">
+          <text>(上传)</text>
+          <block wx:if="{{thumbnail[index]}}">
+            <t-icon wx:if="{{original[index]}}" style="color: #ff611b;" name="delete-1" mark:handle="upload:delete" />
+            <t-loading wx:else="" theme="spinner" size="40rpx" class="wrapper" />
+          </block>
+        </view>
+      </view>
+    </view>
+  </view>
+
+  <t-cell title="拍摄注意事项" required bordered="{{false}}">
+    <view slot="description">
+      <view style="margin-top: 8px;">光线:</view>
+      <view>白天充足、柔和的自然光线下效果最佳,避免背光、偏暗、曝光。</view>
+      <view style="margin-top: 8px;">禁忌:</view>
+      <view>不要在食用有色饮食或药物后、有色光源下、早晨起床时、饭后半小时内拍摄舌象。</view>
+    </view>
+  </t-cell>
+
+  <form bind:submit="onSubmit">
+    <form-button block index="2"></form-button>
+  </form>
+</scroll-view>

+ 8 - 0
miniprogram/module/chats/pages/index/index.json

@@ -0,0 +1,8 @@
+{
+  "renderer": "skyline",
+  "component": true,
+  "usingComponents": {
+    "chat-guide": "../../components/guide/guide",
+    "chat-questionnaire": "../../components/questionnaire/questionnaire"
+  }
+}

+ 7 - 0
miniprogram/module/chats/pages/index/index.scss

@@ -0,0 +1,7 @@
+@import '../../../../themes/page.scss';
+
+/* module/chats/pages/index/index.wxss */
+
+.chat {
+  padding: 24px 12px;
+}

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

@@ -0,0 +1,66 @@
+import PageContainerBehavior from "../../../../core/behavior/page-container.behavior";
+
+// module/chats/pages/index/index.ts
+interface ScrollIntoViewEvent {
+  detail: string;
+}
+interface HandleEvent {
+  detail: { component: 'guide' | 'questionnaire', scroll?: boolean }
+}
+interface Message extends Omit<HandleEvent['detail'], 'scroll'> {
+  id: string;
+}
+
+function getScrollcontext(this: any) {
+  return this as {
+    scroll: WechatMiniprogram.ScrollViewContext,
+    timer: number;
+  }
+}
+
+Component({
+  behaviors: [PageContainerBehavior],
+  lifetimes: {
+    attached() {
+      const component = this.data.component as 'guide' | 'questionnaire'
+      this.handle({ detail: { component, scroll: true } });
+    },
+    ready() {
+      wx.createSelectorQuery().select('#scrollview').node().exec((res) => {
+        getScrollcontext.call(this).scroll = res[0].node;
+      })
+    }
+  },
+  properties: {
+    component: { type: String, value: 'guide' }
+  },
+  data: {
+    messages: {} as Record<number, Message>,
+    lastId: '',
+  },
+  observers: {
+    'messages.**'(messages) {
+      const message = Object.values(messages).pop() as Message;
+      this.setData({ lastId: message?.id });
+    }
+  },
+  methods: {
+    handle(event: HandleEvent) {
+      const index = Object.keys(this.data.messages).length;
+      this.setData({
+        [`messages.${index}`]: <Message>{
+          id: `${this.is.replace(/\//g, '_')}-${index}`,
+          component: event.detail?.component,
+        }
+      });
+      if (event.detail?.scroll) this.scrollIntoView()
+    },
+    scrollIntoView(event?: ScrollIntoViewEvent) {
+      clearTimeout(getScrollcontext.call(this).timer);
+      const id = event?.detail ?? this.data.lastId;
+      getScrollcontext.call(this).timer = setTimeout(() => {
+        getScrollcontext.call(this).scroll?.scrollIntoView(`#${id}`, { alignment: 'end' });
+      }, 300)
+    },
+  }
+})

+ 16 - 0
miniprogram/module/chats/pages/index/index.wxml

@@ -0,0 +1,16 @@
+<wxs module="_">
+  module.exports.active = function (lastId, id) {
+    return lastId === id;
+  }
+  module.exports.show = function (message, component) {
+    return message && message.component === component;
+  }
+</wxs>
+<!--module/chats/pages/index/index.wxml-->
+<t-navbar title="对话管家" left-arrow />
+<scroll-view id="scrollview" class="page-scroll__container" style="{{containerStyle}}" type="list" scroll-y enhanced="{{true}}" enable-passive scroll-into-view="{{lastId}}" scroll-into-view-alignment="end">
+  <block wx:for="{{messages}}" wx:key="id">
+    <chat-guide wx:if="{{_.show(item, 'guide')}}" id="{{item.id}}" active="{{_.active(lastId, item.id)}}" bind:next="handle" bind:to="scrollIntoView" />
+    <chat-questionnaire wx:if="{{_.show(item, 'questionnaire')}}" id="{{item.id}}" active="{{_.active(lastId, item.id)}}" bind:next="handle" bind:to="scrollIntoView" />
+  </block>
+</scroll-view>

+ 6 - 0
miniprogram/pages/home/body-model/body-model.json

@@ -0,0 +1,6 @@
+{
+  "component": true,
+  "usingComponents": {
+    "form-button": "../../../components/button/button"
+  }
+}

+ 127 - 0
miniprogram/pages/home/body-model/body-model.scss

@@ -0,0 +1,127 @@
+/* pages/home/body-model/body-model.wxss */
+.body-model-wrapper {
+  position: relative;
+  box-sizing: border-box;
+  --button-line-1: 10px;
+
+  .container-bg-image {
+    position: static;
+    margin: auto;
+  }
+
+  .container {
+    position: absolute;
+    top: 0;
+    left: 0;
+  }
+}
+
+.index-wrapper {
+  position: absolute;
+  top: 60px;
+  left: 50%;
+  transform: translateX(-55%);
+  max-width: 80px;
+  max-height: 150px;
+  box-shadow: inset 0px 1px 18px 7px rgba(161, 19, 57, 0.55);
+  border: 1px solid #BD1D34;
+  font-size: 12px;
+}
+
+.frame-wrapper {
+  position: relative;
+  width: 110px;
+  height: 64px;
+  box-sizing: border-box;
+
+  image {
+    width: 100%;
+    height: 100%;
+  }
+
+  .content {
+    position: absolute;
+    top: 0;
+    left: 0;
+    display: flex;
+    flex-direction: column;
+    justify-content: space-evenly;
+    align-items: center;
+    width: 100%;
+    height: 100%;
+    font-size: 12px;
+    box-sizing: border-box;
+  }
+}
+
+.card {
+  position: absolute;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  $border: 1px solid #38FF6E;
+
+  .line {
+    box-sizing: border-box;
+  }
+
+  &.LT,
+  &.LB {
+    transform: translateX(-100%);
+  }
+
+  &.LT {
+    .line {
+      border-top: $border;
+      border-right: $border;
+      width: 32px;
+      height: 16px;
+    }
+
+    .content text {
+      width: 90%;
+    }
+  }
+
+  &.LB {
+    flex-direction: column;
+
+    .line {
+      width: 100px;
+      height: 16px;
+      border-left: $border;
+      border-bottom: $border;
+      transform: translateX(50%);
+    }
+  }
+
+
+  &.RT {
+    .line-wrapper {
+      display: flex;
+      flex-direction: row;
+    }
+
+    .line {
+      width: 20px;
+      height: 16px;
+
+      &-1 {
+        border-bottom: $border;
+      }
+
+      &-2 {
+        border-top: $border;
+        border-left: $border;
+      }
+    }
+  }
+
+  &.RB {
+    .line {
+      border-bottom: $border;
+      width: 60px;
+      height: 2px;
+    }
+  }
+}

+ 52 - 0
miniprogram/pages/home/body-model/body-model.ts

@@ -0,0 +1,52 @@
+// pages/home/body-model/body-model.ts
+const Size = [542, 568];
+const Scale = 0.78
+interface Rect {
+  width: number;
+  height: number;
+}
+Component({
+  lifetimes: {
+    attached() { setTimeout(() => this._getContainerRef(), 300); }
+  },
+  properties: {
+    dataset: { type: Object },
+  },
+  data: {
+    container: { width: 0, height: 0 },
+    model: { width: 0, height: 0 },
+  },
+  observers: {
+    'container.**'(container) { this._positioning(container); }
+  },
+  methods: {
+    onBodyModel(event: WechatMiniprogram.TouchEvent) {
+      const position = event.mark?.position
+      if (position) this.triggerEvent('position', { position });
+    },
+    _getContainerRef() {
+      this.createSelectorQuery().select('.body-model-wrapper').boundingClientRect().exec(res => {
+        const { width: cw } = res[0];
+        const mw = Math.round(cw * Scale);
+        const ch = Math.round(mw * Size[1] / Size[0]);
+        this.setData({
+          'container.width': cw,
+          'container.height': ch,
+          'model.width': mw,
+          'model.height': ch,
+        })
+      })
+    },
+    _positioning(container: Rect) {
+      const halfW = container.width / 2;
+      const halfH = container.height / 2;
+
+      this.setData({
+        LT: { left: `${halfW - 60 * Scale}px`, top: `${25 * Scale}px` },
+        LB: { left: `${halfW - 100 * Scale}px`, top: `${halfH + 10 * Scale}px` },
+        RT: { left: `${halfW + 50 * Scale}px`, top: `${30 * Scale}px` },
+        RB: { left: `${halfW + 30 * Scale}px`, top: `${halfH + 5 * Scale}px` },
+      })
+    }
+  }
+})

+ 48 - 0
miniprogram/pages/home/body-model/body-model.wxml

@@ -0,0 +1,48 @@
+<!--pages/home/body-model/body-model.wxml-->
+<view class="body-model-wrapper" style="height: {{container.height}}px;" bind:tap="onBodyModel">
+  <image wx:if="{{model.width}}" style="width: {{model.width}}px;height: {{model.height}}px;" class="container-bg-image" src="../../../assets/bg/body-model.bg.png" mode="widthFix" />
+  <view wx:if="{{dataset}}" class="container" style="width: {{container.width}}px;height: {{container.height}}px;">
+    <view class="card LT" style="top:{{LT.top}};left: {{LT.left}};" mark:position="LT">
+      <view class="frame-wrapper">
+        <image src="../../../assets/bg/body-model-frame.bg.png" mode="aspectFit" />
+        <view class="content">
+          <form-button index="1">{{dataset.willillStateName}}</form-button>
+          <text max-lines="1" overflow="ellipsis">程度:{{dataset.willillDegreeName}}</text>
+          <text max-lines="1" overflow="ellipsis">类型:{{dataset.willillFunctionName}}</text>
+        </view>
+      </view>
+      <view class="line"></view>
+    </view>
+    <view class="card RT" style="top:{{RT.top}};left: {{RT.left}};" mark:position="RT">
+      <view class="line-wrapper">
+        <view class="line line-1"></view>
+        <view class="line line-2"></view>
+      </view>
+      <view class="frame-wrapper">
+        <image src="../../../assets/bg/body-model-frame.bg.png" mode="aspectFit" />
+        <view class="content">
+          <view>体质</view>
+          <form-button index="2">{{dataset.constitutionGroupName}}</form-button>
+        </view>
+      </view>
+    </view>
+    <view class="card RB" style="top:{{RB.top}};left: {{RB.left}};" mark:position="RB">
+      <view class="line"></view>
+      <view class="frame-wrapper">
+        <image src="../../../assets/bg/body-model-frame.bg.png" mode="aspectFit" />
+        <view class="content">
+          <view>中医症型</view>
+          <form-button index="2">{{dataset.diagnoseSyndromeSummary}}</form-button>
+        </view>
+      </view>
+    </view>
+
+    <view class="card LB" style="top:{{LB.top}};left: {{LB.left}};" mark:position="LB">
+      <form-button index="1">健康分析报告</form-button>
+      <view class="line"></view>
+    </view>
+  </view>
+  <view class="index-wrapper" mark:position="CT">
+    <slot></slot>
+  </view>
+</view>

+ 14 - 0
miniprogram/pages/home/home.json

@@ -0,0 +1,14 @@
+{
+  "renderer": "skyline",
+  "component": true,
+  "pureDataPattern": "^_",
+  "usingComponents": {
+    "t-cell": "tdesign-miniprogram/cell/cell",
+    "form-button": "../../components/button/button",
+    "body-model": "./body-model/body-model",
+    "report-health-scheme": "../../module/health/components/report-health-scheme/report-health-scheme"
+  },
+  "componentPlaceholder": {
+    "report-health-scheme": "view"
+  }
+}

+ 182 - 0
miniprogram/pages/home/home.scss

@@ -0,0 +1,182 @@
+@import "../../themes/page.scss";
+@import "../../themes/draggable-sheet.scss";
+@import "../../themes/t.cell.scss";
+
+.container-bg-image {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+}
+
+/* pages/home/home.wxss */
+
+$scale: 0.5;
+
+.header-container {
+  padding: 12px;
+  display: flex;
+  flex-direction: row;
+  box-sizing: border-box;
+}
+
+.user-info-wrapper {
+  position: relative;
+  display: flex;
+  flex-direction: row;
+  // justify-content: center;
+  align-items: center;
+  padding: 0 8px;
+  width: 254px * $scale;
+  height: 66px * $scale;
+  box-sizing: border-box;
+
+  .user {
+    &-icon {
+      flex: none;
+      margin-left: 4px;
+      width: 12px * $scale;
+      height: 16px * $scale;
+    }
+
+    &-select-icon {
+      flex: none;
+      margin-left: 4px;
+      width: 8px;
+      height: 4px;
+    }
+
+    &-name {
+      flex: none;
+      margin-left: 4px;
+      font-size: 14px;
+      color: global.$primary-color;
+    }
+
+    &-age {
+      flex: none;
+      margin-left: 4px;
+      font-size: 8px;
+    }
+
+    &-description {
+      flex: auto;
+      margin-left: 4px;
+      font-size: 8px;
+    }
+  }
+}
+
+
+
+.banner-wrapper {
+  padding-top: 12px;
+
+  .container-bg-image {
+    position: static;
+    margin: auto;
+    width: calc(100% - 24px);
+  }
+}
+
+.health-scheme-wrapper {
+  padding: 0 12px 12px 12px;
+}
+
+.draggable-sheet-wrapper {
+
+  .card-wrapper {
+    --td-cell-vertical-padding: 12px;
+    --page-container-bleeding: 7px;
+    $gap: 6px;
+    margin: $gap var(--page-container-bleeding, 0);
+    border-radius: $gap;
+    box-shadow: inset 0px 0px $gap * 3 0px rgba(52, 167, 107, 0.38);
+
+    .card-header {
+      --td-cell-title-color: var(--primary-color);
+      --td-loading-color: var(--primary-color);
+      background-color: transparent;
+
+      &__title {
+        display: flex;
+        flex-direction: row;
+        align-items: center;
+
+        .icon {
+          margin-left: 8px;
+          margin-top: 2px;
+          height: 20px;
+        }
+      }
+
+      .loading {
+        margin-left: 8px;
+      }
+    }
+
+    .card-body {
+      padding: var(--td-cell-vertical-padding, 32rpx) var(--td-cell-horizontal-padding, 32rpx);
+      background-color: transparent;
+
+      .row+.row {
+        margin-top: 8px;
+      }
+    }
+  }
+
+  .bottom-wrapper {
+    display: flex;
+    justify-content: center;
+    padding: 6px 0 24px;
+  }
+
+
+  .primary {
+    color: var(--primary-color, #38FF6E);
+  }
+
+  .row+.row {
+    margin-top: 8px;
+  }
+
+  .picture-wrapper {
+    display: flex;
+    flex-direction: row;
+    justify-content: center;
+
+    image {
+      margin: 12px;
+      width: 128px;
+      height: 128px;
+    }
+  }
+}
+
+.fab-wrapper {
+  position: absolute;
+  top: 50%;
+  right: 0;
+
+  .fab-main {
+    width: 72px;
+    height: 72px;
+    transform: translateY(6px);
+    // z-index: 2;
+  }
+
+  .fab {
+    position: absolute;
+    top: 6px;
+    right: 6px;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    width: 60px;
+    height: 60px;
+    background-color: var(--td-bg-color-container);
+    border-radius: 50%;
+  }
+}

+ 207 - 0
miniprogram/pages/home/home.ts

@@ -0,0 +1,207 @@
+import PageContainerBehavior from "../../core/behavior/page-container.behavior";
+import { DraggableSheetBehavior, getDraggableSheetContext } from "../../core/behavior/draggableSheet.behavior";
+import { login } from "../../lib/logic";
+import { Post } from "../../lib/request/method";
+
+const { shared, Easing, timing } = wx.worklet
+const offset = shared(0);
+const styleIds = [];
+
+// pages/home/home.ts
+import { getPatients, healthReportMethod, healthIndexMethod } from "./request";
+import { toCertificationPage } from "./router";
+Component({
+  behaviors: [
+    PageContainerBehavior,
+    DraggableSheetBehavior('.draggable-sheet-wrapper'),
+  ],
+  lifetimes: {
+    attached() { this.initFabAnimated(); }
+  },
+  pageLifetimes: {
+    show() { this.load(); }
+  },
+  properties: {
+    patientId: { type: String, value: '' },
+    next: { type: String, value: '' },
+  },
+  data: {
+    patients: [] as (App.Patient.Model & { isDefault: 'Y' | 'N' })[],
+    patient: null as App.Patient.Model | null,
+
+    healthId: '',
+    healthReport: { data: null, message: '' },
+    healthIndex: { data: [], message: '' },
+
+    position: {} as AnyObject,
+  },
+
+  observers: {
+    'patient'(model: { patientId: string }) {
+      wx.setStorageSync('patientId', model.patientId);
+      this._getHealthReport();
+      this._getAbnormalHealthIndex();
+    }
+  },
+  methods: {
+    async load() {
+      await login();
+      wx.showLoading({ title: '加载中' });
+      const { patient } = await getPatients(this.data.patientId);
+      if (!patient) toCertificationPage();
+      else this.setData({ patient });
+    },
+
+    async _getHealthReport() {
+      wx.showLoading({ title: '加载中' });
+      this.setData({ 'healthReport.loading': true, })
+      try {
+        const data = await healthReportMethod();
+        this.setData({
+          'healthReport.data': data,
+          'healthReport.loading': false,
+        });
+      } catch (error) {
+        this.setData({
+          'healthReport.data': [],
+          'healthReport.loading': false,
+          'healthReport.message': error.errMsg,
+        });
+      }
+      wx.hideLoading();
+    },
+    async _getAbnormalHealthIndex() {
+      this.setData({ 'healthIndex.loading': true, })
+      try {
+        const data = await healthIndexMethod();
+        this.setData({
+          'healthIndex.data': data.map((item: AnyObject) => item.abnormalDesc).filter(Boolean),
+          'healthIndex.loading': false,
+        });
+      } catch (error) {
+        this.setData({
+          'healthIndex.data': [],
+          'healthIndex.loading': false,
+          'healthIndex.message': error.errMsg,
+        });
+      }
+    },
+    onBodyModel(event: WechatMiniprogram.TouchEvent) {
+      if (event.detail?.position === 'LB') { this.toReportPage(); }
+      else if (event.detail?.position === 'LT') {
+        const report = this.data.healthReport.data as unknown as AnyObject;
+        this.setData({
+          position: {
+            LT: ['willillState', 'willillDegree', 'willillSocial', 'willillFunction'].map(key => {
+              const title = report[`${key}Name`]
+              const description = report[`${key}Description`]
+              return title || description ? { title, description } : null
+            }).filter(Boolean)
+          },
+        });
+        this.showDraggableSheet();
+      }
+      else if (event.detail?.position === 'RT') {
+        const report = this.data.healthReport.data as unknown as AnyObject;
+        const get = (key: string) => ({ [key]: report[key] })
+        this.setData({
+          position: {
+            RT: {
+              ...get('constitutionGroupName'),
+              ...get('constitutionGroupDefinition'),
+              ...get('faceImg'),
+              ...get('faceAnalysisResult'),
+              ...get('tongueAnalysisResult'),
+              ...get('upImg'),
+              ...get('downImg'),
+            }
+          },
+        });
+        this.showDraggableSheet();
+      }
+      else if (event.detail?.position === 'RB') {
+        const report = this.data.healthReport.data as unknown as AnyObject;
+        const get = (key: string) => ({ [key]: report[key] })
+        this.setData({
+          position: {
+            RB: {
+              ...get('diagnoseSyndromeSummary'),
+              ...get('diagnoseSyndromes'),
+              ...get('factorItemSummary'),
+              ...get('factorItems'),
+            }
+          },
+        });
+        this.showDraggableSheet();
+      }
+      else if (event.detail?.position === 'CT') {
+        wx.showModal({
+          title: '预警信息',
+          content: this.data.healthIndex.data.map((item, index) => `${index + 1}、${item}`).join(''),
+          showCancel: false,
+        })
+      }
+    },
+
+    initFabAnimated() {
+      (<any>this).applyAnimatedStyle('.fab-1', () => {
+        'worklet'
+        return { transform: `translateY(${-offset.value}px)` };
+      });
+      (<any>this).applyAnimatedStyle('.fab-2', () => {
+        'worklet'
+        return { transform: `translateX(${-offset.value}px) translateY(${-offset.value / 2}px)` };
+      });
+      (<any>this).applyAnimatedStyle('.fab-3', () => {
+        'worklet'
+        return { transform: `translateX(${-offset.value}px) translateY(${offset.value / 2}px)` };
+      });
+      (<any>this).applyAnimatedStyle('.fab-4', () => {
+        'worklet'
+        return { transform: `translateY(${offset.value}px)` };
+      });
+    },
+    onFabTap() {
+      const value = Math.abs(offset.value - 72);
+      offset.value = timing(value, { duration: 300, easing: (<any>Easing).linear }, () => {
+        'worklet'
+        if (offset.value > 0) offset.value = 72
+      })
+    },
+    toChatsPage() {
+      wx.navigateTo({ url: `/module/chats/pages/index/index` })
+    },
+    toHealthPage() {
+      wx.navigateTo({ url: `/module/health/pages/home/home` })
+    },
+    toSchemePage() {
+      const id = this.data.healthId;
+      if (id) wx.navigateTo({ url: `/module/health/pages/scheme/scheme?id=${id}` })
+      else wx.showToast({ title: '暂无调理方案', icon: 'none' });
+    },
+    toReportPage() {
+      const id = this.data.healthId;
+      if (id) wx.navigateTo({ url: `/module/health/pages/report/report?id=${id}` })
+      else wx.showToast({ title: '暂无分析报告', icon: 'none' });
+    },
+    showDraggableSheet() {
+      getDraggableSheetContext.call(this).scrollTo({
+        size: 0.5,
+        pixels: 600,
+        animated: true,
+        duration: 300,
+        easingFunction: 'ease'
+      });
+    },
+    hideDraggableSheet() {
+      getDraggableSheetContext.call(this).scrollTo({
+        size: 0,
+        pixels: 600,
+        animated: true,
+        duration: 300,
+        easingFunction: 'ease'
+      });
+      this.setData({ position: {} })
+    }
+  },
+})

+ 100 - 0
miniprogram/pages/home/home.wxml

@@ -0,0 +1,100 @@
+<!--pages/home/home.wxml-->
+<t-navbar title="云管家" />
+<scroll-view class="page-scroll__container" type="list" scroll-y style="{{containerStyle}}">
+  <view class="header-container">
+    <view class="user-info-wrapper" wx:if="{{patient}}">
+      <image class="container-bg-image" src="../../assets/bg/user-info.bg.png" mode="aspectFit" />
+      <block>
+        <image class="user-icon" src="../../assets/icon/gender-{{patient.sex}}.icon.png" mode="aspectFit" />
+        <text class="user-name">{{patient.name}}</text>
+        <text class="user-age">{{patient.age}}岁</text>
+        <text wx:if="{{false}}" class="user-description" overflow="fade" max-lines="1"></text>
+        <image wx:if="{{patients.length > 1}}" class="user-select-icon" src="../../assets/icon/arrows-down.icon.png" mode="aspectFit" />
+      </block>
+    </view>
+  </view>
+  <body-model dataset="{{healthReport.data}}" bind:position="onBodyModel">
+    <view wx:for="{{healthIndex.data}}" wx:key="*this">
+      <text max-lines="1" overflow="ellipsis">{{item}}</text>
+    </view>
+  </body-model>
+  <view class="banner-wrapper">
+    <image class="container-bg-image" src="../../assets/bg/home-banner.bg.png" mode="widthFix" bind:tap="toChatsPage"></image>
+  </view>
+  <view class="health-scheme-wrapper">
+    <report-health-scheme dataset="{{healthReport.data.conditProgram}}" mark:id="{{healthId}}" bind:tap="toSchemePage"></report-health-scheme>
+  </view>
+</scroll-view>
+
+<view class="fab-wrapper" bind:tap="onFabTap">
+  <view class="fab fab-1" bind:tap="toHealthPage"><text>健康</text><text>档案</text></view>
+  <view class="fab fab-2"><text>中医</text><text>科普</text></view>
+  <view class="fab fab-3"><text>中医</text><text>茶饮</text></view>
+  <view class="fab fab-4"><text>药膳</text><text>查询</text></view>
+  <image class="fab-main" src="../../assets/icon/fab.png" mode="aspectFill" />
+</view>
+
+<draggable-sheet class="draggable-sheet-wrapper" style="height: {{container.height}}px;" initial-child-size="0" min-child-size="0" max-child-size="1" snap="{{true}}" snap-sizes="{{[0.5]}}">
+  <scroll-view class="scrollable draggable-sheet-container" type="custom" scroll-y associative-container="draggable-sheet" show-scrollbar="{{false}}" bounces="{{false}}">
+    <sticky-section>
+      <sticky-header>
+        <view class="draggable-sheet-bar">
+          <view class="indicator" />
+        </view>
+      </sticky-header>
+      <block wx:if="{{position.LT}}">
+        <view class="card-wrapper" wx:for="{{position.LT}}" wx:key="*this">
+          <t-cell wx:if="{{item.title}}" t-class="card-header cell-border-gradient" title="{{item.title}}" />
+          <view wx:if="{{item.description}}" class="card-body">{{item.description}}</view>
+        </view>
+      </block>
+      <block wx:if="{{position.RB}}">
+        <view class="card-wrapper">
+          <t-cell t-class="card-header cell-border-gradient" title="中医证型解释" description="{{position.RB.diagnoseSyndromeSummary}}" />
+          <view class="card-body">
+            <view class="row" wx:for="{{position.RB.diagnoseSyndromes}}" wx:key="*this">
+              <view class="primary">{{item.diagnoseSyndromeName}}</view>
+              <view style="margin-top: 4px;">{{item.diagnoseSyndromeAnalysis}}</view>
+            </view>
+          </view>
+        </view>
+        <view class="card-wrapper">
+          <t-cell t-class="card-header cell-border-gradient" title="中医证素解释" description="{{position.RB.factorItemSummary}}" />
+          <view class="card-body">
+            <view class="row" wx:for="{{position.RB.factorItems}}" wx:key="*this">
+              <view class="primary">{{item.factorItemName}}</view>
+              <view style="margin-top: 4px;">{{item.factorItemDescription}}</view>
+            </view>
+          </view>
+        </view>
+      </block>
+      <block wx:if="{{position.RT}}">
+        <view class="card-wrapper">
+          <t-cell t-class="card-header cell-border-gradient" title="{{position.RT.constitutionGroupName}}" />
+          <view class="card-body">
+            <span class="row">
+              <text>{{position.RT.constitutionGroupName}}是</text>
+              <text>{{position.RT.constitutionGroupDefinition}}</text>
+            </span>
+          </view>
+        </view>
+        <view class="card-wrapper">
+          <t-cell t-class="card-header cell-border-gradient" title="舌象解释" description="{{position.RT.tongueAnalysisResult}}" />
+          <view class="picture-wrapper">
+            <image src="{{position.RT.upImg}}" mode="aspectFit" />
+            <image src="{{position.RT.downImg}}" mode="aspectFit" />
+          </view>
+        </view>
+        <view class="card-wrapper">
+          <t-cell t-class="card-header cell-border-gradient" title="面象解释" description="{{position.RT.faceAnalysisResult}}" />
+          <view class="picture-wrapper">
+            <image src="{{position.RT.faceImg}}" mode="aspectFit" />
+          </view>
+        </view>
+      </block>
+      <view class="bottom-wrapper">
+        <form-button index="1" bind:tap="hideDraggableSheet">确定</form-button>
+      </view>
+    </sticky-section>
+  </scroll-view>
+</draggable-sheet>

+ 32 - 0
miniprogram/pages/home/request.ts

@@ -0,0 +1,32 @@
+import { Post } from "../../lib/request/method";
+
+export function getPatients(id?: string) {
+  id ??= wx.getStorageSync('patientId')
+  const transform = ({ data }: AnyObject) => {
+    const patient = void 0
+      ?? data.find((item: AnyObject) => item.patientId == id)
+      ?? data.find((item: AnyObject) => item.isDefault?.toUpperCase() === 'Y')
+      ?? data[0];
+
+    return { patient, patients: data }
+  }
+  return Post('/mobileAccountManage/getPatsByAid', {}, { transform })
+}
+
+export function healthReportMethod() {
+  const transform = ({ data }: AnyObject) => {
+    if (Array.isArray(data?.conditProgram?.types)) {
+      data.conditProgram.types = data.conditProgram.types.map((item: AnyObject) => (item.summary = item.summary?.replace(/null/g, '') || '', item))
+    }
+    return data;
+  };
+
+  return Post(`/analysisManage/getLastHealRepDetail`, {}, { transform })
+}
+
+export function healthIndexMethod() {
+  const transform = ({ data }: AnyObject) => {
+    return Array.isArray(data) ? data.map(item => ({ ...item, ...item.patientQuotaRecordDTOS?.slice(-1)[0] })) : [];
+  };
+  return Post(`/patientQuota/getCurQuoval`, {}, { transform })
+}

+ 22 - 0
miniprogram/pages/home/router.ts

@@ -0,0 +1,22 @@
+import { registerDataValue } from "XrFrame/xrFrameSystem";
+
+export function toCertificationPage() {
+  const navigate = wx.navigateTo({
+    url: `/module/user/pages/user-certification/user-certification`,
+    events: {
+      update: (data: { patientId: string }) => {
+        if (data.patientId) {
+          wx.setStorageSync('patientId', data.patientId);
+          toChats('questionnaire')
+        }
+      }
+    }
+  })
+  navigate.then(res => { res.eventChannel.emit('navigateBack', { hide: true }); })
+  return navigate;
+}
+
+export function toChats(component: 'guide' | 'questionnaire') {
+  const navigate = wx.navigateTo({ url: `/module/chats/pages/index/index?component=${component}` })
+  return navigate;
+}