6c11a8363937ec701072aedd7abe26bd492f1159..5fbc4feb28dfc709652cbf8dc74c7f5751116d75
4 天以前 WXL (wul)
测试完成
5fbc4f 对比 | 目录
4 天以前 WXL (wul)
测试完成
cf6a79 对比 | 目录
4 天以前 WXL (wul)
测试完成
cfa5a3 对比 | 目录
已修改8个文件
已添加3个文件
3162 ■■■■■ 文件已修改
dist.zip 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/record/detailpage/index.vue 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/knowledge/Medication/index.vue 347 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/outsideChainwtnew copy.vue 849 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/outsideChainwtnew.vue 976 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/SignAcontract/Review.vue 469 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/SignAcontract/index.vue 265 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/patient/index.vue 84 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/patient/profile/index.vue 157 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vue.config.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
wuxi.zip 补丁 | 查看 | 原始文档 | blame | 历史
dist.zip
Binary files differ
src/views/followvisit/record/detailpage/index.vue
@@ -171,7 +171,7 @@
        </el-table>
      </div>
    </div>
    <div :class="form.serviceType == 2 ? 'Followuserinfo' : 'Followuserinfos'">
    <div :class="form.serviceType ? 'Followuserinfo' : 'Followuserinfos'">
      <div>
        <el-form ref="form" :model="form" label-width="120px">
          <div class="headline">
@@ -228,7 +228,7 @@
        </el-form>
      </div>
    </div>
    <div v-if="form.serviceType == 2">
    <div>
      <el-tabs v-model="activeName" type="border-card">
        <el-tab-pane name="wj">
          <span class="mulsz" slot="label"
@@ -663,6 +663,7 @@
          subId: this.id,
          taskid: this.taskid,
          scriptid: item.id,
          sendstate: 6,
          templatequestionnum: item.scriptno,
          questiontext: item.scriptContent,
        };
@@ -713,6 +714,7 @@
        param1: this.taskid,
        param2: this.patid,
        subId: this.id,
        sendstate: 6,
      };
      addPersonVoices(obj).then((res) => {
@@ -753,7 +755,7 @@
      let objson = {};
      getTaskservelist({
        patid: this.patid,
        subId: this.id,
        subid: this.id,
      }).then((res) => {
        if (res.code == 200) {
          objson = res.rows[0].serviceSubtaskList[0];
@@ -771,12 +773,13 @@
      let objson = {};
      getTaskservelist({
        patid: this.patid,
        subId: this.id,
        subid: this.id,
      }).then((res) => {
        if (res.code == 200) {
          objson = res.rows[0].serviceSubtaskList[0];
          objson.remark = this.form.remark;
          if (sendstate) objson.sendstate = sendstate;
          objson.sendstate = 6;
          Editsingletaskson(objson).then((res) => {
            if (res.code) {
              this.$modal.msgSuccess("服务修改成功");
src/views/knowledge/Medication/index.vue
@@ -398,175 +398,184 @@
      total: 0,
      // ç”¨è¯è®°å½•数据
medicationList: [
  {
    id: 1,
    patientId: 'P1001',
    patientName: '张清扬',
    patientAge: 65,
    patientGender: '1',
    patientNo: '20241209001',
    allergyHistory: '青霉素过敏',
    drugId: 'D1001',
    drugName: '阿司匹林肠溶片',
    drugSpecification: '100mg*30片/盒',
    dosage: 1,
    dosageUnit: '片',
    frequency: '1',
    usageMethod: '口服',
    startTime: '2024-12-01 08:00:00',
    endTime: '',
    durationType: '1',
    instructions: '饭后服用,注意胃肠道反应。长期服用需定期检查血常规。',
    medicationStatus: '1',
    prescribingDoctor: '李成白',
    stopReason: '',
    stopDescription: ''
  },
  {
    id: 2,
    patientId: 'P1001',
    patientName: '张清扬',
    patientAge: 65,
    patientGender: '1',
    patientNo: '20241209001',
    allergyHistory: '青霉素过敏',
    drugId: 'D1002',
    drugName: '阿托伐他汀钙片',
    drugSpecification: '20mg*7片/盒',
    dosage: 1,
    dosageUnit: '片',
    frequency: '1',
    usageMethod: '口服',
    startTime: '2024-11-20 20:00:00',
    endTime: '',
    durationType: '1',
    instructions: '每晚睡前服用。注意监测肝功能,如有肌肉酸痛请及时就医。',
    medicationStatus: '1',
    prescribingDoctor: '李成白',
    stopReason: '',
    stopDescription: ''
  },
  {
    id: 3,
    patientId: 'P2001',
    patientName: '王芳',
    patientAge: 33,
    patientGender: '0',
    patientNo: '20241115002',
    allergyHistory: '无',
    drugId: 'D1003',
    drugName: '泼尼松片',
    drugSpecification: '5mg*100片/瓶',
    dosage: 2,
    dosageUnit: '片',
    frequency: '3',
    usageMethod: '口服',
    startTime: '2024-12-05 09:00:00',
    endTime: '2024-12-20 09:00:00',
    durationType: '2',
    instructions: '早、中、晚餐后服用。需严格遵医嘱逐渐减量,不可突然停药。',
    medicationStatus: '0',
    prescribingDoctor: '刘翊惠',
    stopReason: '疗程结束',
    stopDescription: '标准疗程用药完毕,血小板计数已恢复正常范围。'
  },
  {
    id: 4,
    patientId: 'P2001',
    patientName: '王芳',
    patientAge: 33,
    patientGender: '0',
    patientNo: '20241115002',
    allergyHistory: '无',
    drugId: 'D1004',
    drugName: '多糖铁复合物胶囊',
    drugSpecification: '150mg*10粒/盒',
    dosage: 1,
    dosageUnit: '粒',
    frequency: '2',
    usageMethod: '口服',
    startTime: '2024-12-05 09:00:00',
    endTime: '',
    durationType: '1',
    instructions: '餐后服用,可减轻胃肠道刺激。服药后可能出现黑便,属正常现象。',
    medicationStatus: '1',
    prescribingDoctor: '刘翊惠',
    stopReason: '',
    stopDescription: ''
  },
  {
    id: 5,
    patientId: 'P3001',
    patientName: '李伟',
    patientAge: 58,
    patientGender: '1',
    patientNo: '20241022005',
    allergyHistory: '磺胺类药物过敏',
    drugId: 'D1005',
    drugName: '盐酸二甲双胍片',
    drugSpecification: '0.5g*20片/板',
    dosage: 1,
    dosageUnit: '片',
    frequency: '2',
    usageMethod: '口服',
    startTime: '2024-10-22 08:00:00',
    endTime: '',
    durationType: '1',
    instructions: '随餐或餐后立即服用,以减少胃肠道不适。',
    medicationStatus: '1',
    prescribingDoctor: '张孟涵',
    stopReason: '',
    stopDescription: ''
  },
  {
    id: 6,
    patientId: 'P4001',
    patientName: '赵磊',
    patientAge: 70,
    patientGender: '1',
    patientNo: '20241202011',
    allergyHistory: '海鲜过敏',
    drugId: 'D1006',
    drugName: '呋塞米片',
    drugSpecification: '20mg*100片/瓶',
    dosage: 1,
    dosageUnit: '片',
    frequency: '1',
    usageMethod: '口服',
    startTime: '2024-12-02 07:00:00',
    endTime: '',
    durationType: '1',
    instructions: '晨起服用,避免夜间多次起夜。注意监测电解质水平。',
    medicationStatus: '1',
    prescribingDoctor: '吴思翰',
    stopReason: '',
    stopDescription: ''
  },
  {
    id: 7,
    patientId: 'P5001',
    patientName: '周华',
    patientAge: 52,
    patientGender: '0',
    patientNo: '20241128009',
    allergyHistory: '无',
    drugId: 'D1007',
    drugName: '硝苯地平控释片',
    drugSpecification: '30mg*7片/盒',
    dosage: 1,
    dosageUnit: '片',
    frequency: '1',
    usageMethod: '口服',
    startTime: '2024-11-28 08:00:00',
    endTime: '',
    durationType: '1',
    instructions: '整片吞服,不可嚼碎或掰开。',
    medicationStatus: '1',
    prescribingDoctor: '陈政倩',
    stopReason: '',
    stopDescription: ''
  }
],
        {
          id: 1,
          patientId: 'C1001',
          patientName: '李俊宝',
          patientAge: 2,
          ageGroup: '婴幼儿(0-3岁)',
          patientGender: '1',
          patientNo: 'P202412001',
          allergyHistory: '青霉素过敏、鸡蛋过敏',
          drugId: 'D1001',
          drugName: '布洛芬混悬滴剂',
          drugSpecification: '100mg:5ml*15ml/瓶',
          drugCategory: '解热镇痛药',
          dosage: 2.5,
          dosageUnit: 'ml',
          frequency: '3',
          usageMethod: '口服',
          startTime: '2024-12-10 08:00:00',
          endTime: '',
          durationType: '1',
          instructions: '用于发热,体重10kg,按体重计算剂量。饭后服用,注意观察有无胃肠道反应。',
          medicationStatus: '1',
          prescribingDoctor: '王医生',
          guardianName: '李爸爸'
        },
        {
          id: 2,
          patientId: 'C1002',
          patientName: '张萌',
          patientAge: 4,
          ageGroup: '学龄前(3-6岁)',
          patientGender: '0',
          patientNo: 'P202411002',
          allergyHistory: '无',
          drugId: 'D1002',
          drugName: '阿莫西林颗粒',
          drugSpecification: '125mg*12袋/盒',
          drugCategory: '抗生素',
          dosage: 1,
          dosageUnit: '袋',
          frequency: '3',
          usageMethod: '口服',
          startTime: '2024-12-08 09:00:00',
          endTime: '2024-12-18 09:00:00',
          durationType: '2',
          instructions: '治疗急性中耳炎,需连续服用10天,不可自行停药。注意观察有无过敏反应。',
          medicationStatus: '0',
          prescribingDoctor: '刘医生',
          guardianName: '张妈妈',
          stopReason: '疗程结束',
          stopDescription: '完成10天抗生素疗程,中耳炎症状完全缓解。'
        },
        {
          id: 3,
          patientId: 'C1003',
          patientName: '王青宇',
          patientAge: 8,
          ageGroup: '学龄期(7-12岁)',
          patientGender: '1',
          patientNo: 'P202412003',
          allergyHistory: '海鲜过敏',
          drugId: 'D1003',
          drugName: '孟鲁司特钠咀嚼片',
          drugSpecification: '5mg*5片/盒',
          drugCategory: '抗过敏药',
          dosage: 1,
          dosageUnit: '片',
          frequency: '1',
          usageMethod: '口服',
          startTime: '2024-11-15 20:00:00',
          endTime: '',
          durationType: '1',
          instructions: '每晚睡前咀嚼服用,用于控制哮喘症状。注意监测身高体重变化。',
          medicationStatus: '1',
          prescribingDoctor: '陈医生',
          guardianName: '王妈妈'
        },
        {
          id: 4,
          patientId: 'C1004',
          patientName: '刘驰欣',
          patientAge: 14,
          ageGroup: '青少年(13-18岁)',
          patientGender: '0',
          patientNo: 'P202410004',
          allergyHistory: '磺胺类药物过敏',
          drugId: 'D1004',
          drugName: '异维A酸软胶囊',
          drugSpecification: '10mg*20粒/盒',
          drugCategory: '皮肤科用药',
          dosage: 1,
          dosageUnit: '粒',
          frequency: '2',
          usageMethod: '口服',
          startTime: '2024-10-20 08:00:00',
          endTime: '2025-01-20 08:00:00',
          durationType: '2',
          instructions: '治疗重度痤疮,需定期检查肝功能。服药期间及停药后1个月内避免怀孕。',
          medicationStatus: '1',
          prescribingDoctor: '赵医生',
          guardianName: '刘爸爸'
        },
        {
          id: 5,
          patientId: 'C1005',
          patientName: '陈浩',
          patientAge: 1,
          ageGroup: '婴幼儿(0-3岁)',
          patientGender: '1',
          patientNo: 'P202412005',
          allergyHistory: '牛奶蛋白过敏',
          drugId: 'D1005',
          drugName: '盐酸西替利嗪滴剂',
          drugSpecification: '10mg:1ml*5ml/瓶',
          drugCategory: '抗过敏药',
          dosage: 0.25,
          dosageUnit: 'ml',
          frequency: '1',
          usageMethod: '口服',
          startTime: '2024-12-05 08:00:00',
          endTime: '',
          durationType: '1',
          instructions: '用于过敏性鼻炎,体重8kg,按体重精确计算剂量。注意观察有无嗜睡副作用。',
          medicationStatus: '1',
          prescribingDoctor: '孙医生',
          guardianName: '陈妈妈'
        },
        {
          id: 6,
          patientId: 'C1006',
          patientName: '杨芷悦',
          patientAge: 16,
          ageGroup: '青少年(13-18岁)',
          patientGender: '0',
          patientNo: 'P202409006',
          allergyHistory: '无',
          drugId: 'D1006',
          drugName: '左甲状腺素钠片',
          drugSpecification: '50μg*100片/盒',
          drugCategory: '内分泌用药',
          dosage: 1,
          dosageUnit: '片',
          frequency: '1',
          usageMethod: '口服',
          startTime: '2024-09-01 07:00:00',
          endTime: '',
          durationType: '1',
          instructions: '治疗甲状腺功能减退症,每日清晨空腹服用,需终身服药。定期复查甲状腺功能。',
          medicationStatus: '1',
          prescribingDoctor: '周医生',
          guardianName: '杨爸爸'
        },
        {
          id: 7,
          patientId: 'C1007',
          patientName: '黄铭轩',
          patientAge: 5,
          ageGroup: '学龄前(3-6岁)',
          patientGender: '1',
          patientNo: 'P202412007',
          allergyHistory: '花粉过敏',
          drugId: 'D1007',
          drugName: '沙丁胺醇气雾剂',
          drugSpecification: '100μg*200揿/瓶',
          drugCategory: '平喘药',
          dosage: 1,
          dosageUnit: '揿',
          frequency: '0',
          usageMethod: '吸入',
          startTime: '2024-11-20 08:00:00',
          endTime: '',
          durationType: '1',
          instructions: '用于哮喘急性发作时缓解症状,按需使用。教导正确吸入方法。',
          medicationStatus: '1',
          prescribingDoctor: '吴医生',
          guardianName: '黄妈妈'
        }
      ],
      // åˆ†é…ç”¨è¯å¼¹å‡ºå±‚
      assignOpen: false,
      assignTitle: "",
src/views/outsideChainwtnew copy.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,849 @@
<template>
  <div class="questionnaire-optimized">
    <div class="content-wrapper" v-if="!accomplish">
      <div class="questionnaire-container">
        <!-- é—®å·æ ‡é¢˜åŒºåŸŸ -->
        <div class="questionnaire-header">
          <h1 class="questionnaire-title">
            {{ taskname ? taskname : "问卷" }}
          </h1>
          <div class="questionnaire-description">
            {{
              kcb
                ? kcb
                : "亲爱的患者-家属,我们是医院的医护人员,为了更好地了解您的康复情况,请您抽一点宝贵时间,完成这份随访问卷。"
            }}
          </div>
        </div>
        <el-divider class="custom-divider"></el-divider>
        <!-- é—®å·é¢˜ç›®åŒºåŸŸ -->
        <div class="questions-section">
          <div
            class="question-item"
            v-for="(item, index) in visibleQuestions"
            :key="item.id"
            :class="{
              'has-warning':
                item.prompt &&
                item.scriptResult &&
                (item.scriptType !== 2 || item.scriptResult.length > 0),
            }"
          >
            <!-- é¢˜ç›®é¢˜å¹² -->
            <div class="question-stem">
              <span class="question-number"
                >{{ getVisibleQuestionIndex(index) }}.</span
              >
              <span class="question-text">{{ item.scriptContent }}</span>
              <span class="question-type-tag">
                {{
                  item.scriptType === 1
                    ? "[单选]"
                    : item.scriptType === 2
                    ? "[多选]"
                    : "[问答]"
                }}
              </span>
            </div>
            <!-- å•选题目 -->
            <div
              class="question-options"
              v-if="item.scriptType == 1 && !item.ishide"
            >
              <el-radio-group class="options-group" v-model="item.scriptResult">
                <el-radio
                  v-for="(
                    option, optionIndex
                  ) in item.svyTaskTemplateTargetoptions"
                  :key="optionIndex"
                  :label="option.optioncontent"
                  :class="{
                    'abnormal-option':
                      option.isabnormal &&
                      item.scriptResult == option.optioncontent,
                  }"
                  @click.native.prevent="
                    handleRadioToggle(
                      item,
                      index,
                      item.svyTaskTemplateTargetoptions,
                      option.optioncontent
                    )
                  "
                  class="option-radio"
                >
                  <span class="option-text">{{ option.optioncontent }}</span>
                </el-radio>
              </el-radio-group>
            </div>
            <!-- å¤šé€‰é¢˜ç›® -->
            <div class="question-options" v-if="item.scriptType == 2">
              <el-checkbox-group
                class="options-group"
                v-model="item.scriptResult"
              >
                <el-checkbox
                  v-for="(
                    option, optionIndex
                  ) in item.svyTaskTemplateTargetoptions"
                  :key="optionIndex"
                  :label="option.optioncontent"
                  :class="{
                    'abnormal-option': option.isabnormal,
                  }"
                  @change="$forceUpdate()"
                  class="option-checkbox"
                >
                  <span class="option-text">{{ option.optioncontent }}</span>
                </el-checkbox>
              </el-checkbox-group>
            </div>
            <!-- å¡«ç©ºé¢˜ç›® -->
            <div class="question-input" v-if="item.scriptType == 4">
              <el-input
                type="textarea"
                :rows="3"
                placeholder="请输入您的回答"
                v-model="item.scriptResult"
                clearable
                class="answer-textarea"
              ></el-input>
            </div>
            <!-- æç¤ºä¿¡æ¯ -->
            <div
              class="question-warning"
              v-show="
                item.prompt &&
                item.scriptResult &&
                (item.scriptType !== 2 || item.scriptResult.length > 0)
              "
            >
              <el-alert
                :title="item.prompt"
                type="warning"
                :closable="false"
                class="warning-alert"
              ></el-alert>
            </div>
          </div>
        </div>
        <!-- æäº¤æŒ‰é’® -->
        <div class="submit-section">
          <el-button type="primary" @click="cache(true)" class="submit-button">
            æäº¤é—®å·
          </el-button>
        </div>
      </div>
    </div>
    <!-- å®Œæˆé¡µé¢ -->
    <div class="completion-page" v-else>
      <div class="completion-content">
        <div class="completion-icon">✓</div>
        <h2 class="completion-title">感谢您的配合!</h2>
        <p class="completion-message">
          {{
            jsy
              ? jsy
              : "生活上要劳逸结合,注意休息和营养,适当锻炼,戒烟限酒,保持心情舒畅,定期复诊。那本次回访就到这里,祝您身体健康!"
          }}
        </p>
      </div>
    </div>
  </div>
</template>
<script>
import {
  getExternalfollowup,
  getCachequestionnaire,
  Cachequestionnaire,
  Submitaquestionnaire,
  geturlinfo,
} from "@/api/AiCentre/index";
import JSEncrypt from "jsencrypt";
export default {
  data() {
    return {
      taskid: 355,
      patid: 265823,
      kcb: "",
      excep: 0,
      isabnormal: 0,
      taskname: "",
      questionList: [],
      jsy: null,
      dialogVisible: false,
      Endornot: true,
      accomplish: false,
      // å‰ç«¯å…¬é’¥
      publicKey:
        "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKR0yHv0rbJWQE+Sc7/FwpW66qMd9qX2k6z+SDgkSdxWh/1GbBoAP7bDQQRF6vXmoKsD2ya42H6XRLSDXAoayuMCAwEAAQ== ",
      // åŽç«¯ç§é’¥
      privateKey:
        " MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEAtDOpbUQhcEoYy77agRhIHmAzs7H+KHJhN56gTTI9fWq23j77nI055MFV3oQQziIrNUTNaPpEQhZXBpI0+f9K9QIDAQABAkB3n0fcWfrcoMN/FU3VnrnZOEF6CzFNxkgU9P8y36QECWKZ9JhYQkNpKrMC9oXlN3VSaRigV7B+L/I/a0Rs1W+tAiEA4jx7xcXJ4y4BNwAmVHt6NNiEkzIwWnwC/0qsEu8NsOsCIQDL6MMn1D2uznC6OuOWpxDCkBh1JL1NzZTZeH2G+hj7nwIgKGAC9tjFnvWm4dn0/T7MIIJDpsFeP8fCAS2iZ/6hwuECIAS/eLvWr1EAsZNEh8QcQ8GkBU3E+ztyjAK8UX/xFt/VAiBf79/1tDErX4/DChecM8w3c3DhbBcjuE3fHZn7p6/UKg==",
      formData: {
        question1: "",
        question2: "",
        question3: "",
      },
    };
  },
  mounted() {
    // window.addEventListener("beforeunload", this.cache);
  },
  beforeDestroy() {
    // window.removeEventListener("beforeunload", this.cache);
  },
  created() {
    this.geturlinfo();
  },
  computed: {
    // è®¡ç®—属性:获取所有可见的题目
    visibleQuestions() {
      if (!Array.isArray(this.questionList)) {
        return [];
      }
      return this.questionList.filter((question) => !question.ishide);
    },
  },
  methods: {
    // è§£æžurlid
    geturlinfo() {
      // let url = window.location.href;
      let url = this.$route.query.p;
      console.log(url, "url");
      // let url = 'http://218.108.11.22:8093/sf/003';
      // let urlid = this.extractLastSegmentFromUrl(url);
      geturlinfo(url).then((res) => {
        if (res.code == 200) {
          this.getQuestionnaire(
            res.data.param1,
            res.data.param2,
            res.data.param3,
            res.data.param5
          );
        }
      });
    },
    //     extractLastSegmentFromUrl(url) {
    //     // æ‰¾åˆ°æœ€åŽä¸€ä¸ª'/'的位置
    //     const lastSlashIndex = url.lastIndexOf('/');
    //     // å¦‚果找到了'/',截取其后的所有字符
    //     if (lastSlashIndex !== -1) {
    //         return url.substring(lastSlashIndex + 1);
    //     }
    //     // å¦‚果没有找到'/',返回空字符串
    //     return '';
    // },
    // èŽ·å–æ•°æ®
    getQuestionnaire(param1, param2, param3) {
      this.taskid = decodeURIComponent(param1);
      this.patid = decodeURIComponent(param2);
      this.taskname = decodeURIComponent(param3);
      // let taskid =
      //   "OFp7tn/B6x7IzKJetvGWHdSWBj7msRlnlj6am9dyuHTH6sEt4uBbVCUXs5kcF/e4O2W6vqHf2Bz9K3/evbYDmw==";
      // let patid =
      //   "CVk0j8O86AeCqhV5WPsBBYDg9fec0wDoDlP9imYK4wDBNIkxywZzMJEGlPagOxnq6qr2WYZo0U8MUGWRGnq8ZA==";
      // this.taskid = this.decrypt(taskid);
      // this.patid = this.decrypt(patid);
      // let taskids = this.encrypt(this.taskid);
      // let patids = this.encrypt(this.patid);
      // å…ˆå–缓存
      getCachequestionnaire({ param1: this.taskid, param2: this.patid }).then(
        (res) => {
          if (res.code == 200) {
            this.questionList = res.data.result;
            this.accomplish = res.data.submit;
            if (this.questionList[0]) {
              this.questionList.forEach((item) => {
                if (item.scriptResult && item.scriptType != 2) {
                  item.scriptResult = JSON.parse(item.scriptResult);
                } else if (item.scriptResult && item.scriptType == 2) {
                  item.scriptResult = item.scriptResult.split("&");
                }
              });
              return;
            } else {
              this.getExternalfollowup();
            }
          } else {
            this.getExternalfollowup();
          }
        }
      );
    },
    // èŽ·å–æ•°æ®
    getExternalfollowup() {
      getExternalfollowup({ param1: this.taskid, param2: this.patid }).then(
        (res) => {
          if (res.code == 200) {
            this.questionList = res.data.script;
            this.jsy = res.data.jsy;
            this.kcb = res.data.kcb;
            // å¤„理题目收集结果格式
            this.questionList.forEach((item) => {
              item.nextScriptno = Number(item.nextScriptno);
              if (item.scriptType == 2) {
                item.scriptResult = [];
              }
            });
          }
        }
      );
    },
    // åŠ å¯†å‡½æ•°
    encrypt(txt) {
      const encryptor = new JSEncrypt();
      encryptor.setPublicKey(this.publicKey); // è®¾ç½®å…¬é’¥
      return encryptor.encrypt(txt); // å¯¹æ•°æ®è¿›è¡ŒåР坆
    },
    // è§£å¯†å‡½æ•°
    decrypt(txt) {
      const encryptor = new JSEncrypt();
      encryptor.setPrivateKey(this.privateKey); // è®¾ç½®ç§é’¥
      return encryptor.decrypt(txt); // å¯¹æ•°æ®è¿›è¡Œè§£å¯†
    },
    // æäº¤
    submitForm() {
      // æäº¤è¡¨å•逻辑
      let form = {
        param1: this.taskid,
        param2: this.patid,
        excep: this.excep,
        isabnormal: this.isabnormal,
        serviceSubtaskDetailList: [],
      };
      console.log(form.isabnormal, "formisabnormal");
      const arr = structuredClone(this.questionList);
      // arr.forEach((item) => {
      //   item.asrtext = JSON.stringify(item.scriptResult);
      //   if (item.scriptType == 2 && item.scriptResult[0]) {
      //     item.scriptResult = item.scriptResult.join("&");
      //   }
      // });
      form.serviceSubtaskDetailList = arr;
      Submitaquestionnaire(form).then((res) => {
        if (res.code == 200) {
          if (this.jsy) {
            this.dialogVisible = true;
          }
          this.accomplish = true;
          this.$modal.msgSuccess("提交成功");
        }
      });
    },
    // ç¼“å­˜
    cache(subm) {
      console.log("进入缓存");
      let form = {
        param1: this.taskid,
        param2: this.patid,
        svyTaskTemplateScriptVOS: [],
      };
      const arr = structuredClone(this.questionList);
      arr.forEach((item) => {
        item.ishide = item.ishide ? 1 : 0;
      });
      arr.forEach((item, index) => {
        if (item.scriptType == 1 && item.scriptResult) {
          var obj = item.svyTaskTemplateTargetoptions.find(
            (items) => items.optioncontent == item.scriptResult
          );
          console.log(item);
          console.log(obj, "obj");
          if (obj.sendTaskid) {
            item.sendTaskname = obj.sendTaskname;
            item.sendTaskid = obj.sendTaskid;
            console.log(String(obj.sendTaskid).split(","));
            item.sendTaskids = String(obj.sendTaskid).split(",");
          }
          if (obj) {
            form.excep = obj.isabnormal;
            form.isabnormal = obj.isabnormal;
            if (this.isabnormal != 1 && obj.isabnormal) {
              this.excep = obj.isabnormal;
              this.isabnormal = obj.isabnormal;
            }
            console.log(obj.isabnormal);
          }
        }
      });
      arr.forEach((item) => {
        if (item.scriptType == 2 && item.scriptResult[0]) {
          item.scriptResult = item.scriptResult.join("&");
        } else if (item.scriptType != 2 && item.scriptResult) {
          item.scriptResult = JSON.stringify(item.scriptResult);
        }
      });
      form.svyTaskTemplateScriptVOS = arr;
      form.type = 2;
      Cachequestionnaire(form).then((res) => {
        if (res.code == 200) {
          if (subm) {
            this.submitForm();
          }
        }
      });
    },
    // èŽ·å–å¯è§é¢˜ç›®çš„æ­£ç¡®åºå·ï¼ˆè§£å†³è·³é¢˜åŽåºå·ä¸è¿žç»­çš„é—®é¢˜ï¼‰
    getVisibleQuestionIndex(index) {
      return index + 1;
    },
    // æ–°å¢žçš„切换选中/取消选中方法
    handleRadioToggle(questionItem, index, options, optionValue) {
      // ä¿å­˜å½“前状态以便后续比较
      const previousState = JSON.parse(JSON.stringify(this.questionList));
      // åŽŸæœ‰çš„å¤„ç†é€»è¾‘
      if (questionItem.scriptResult === optionValue) {
        questionItem.scriptResult = "";
        questionItem.isabnormal = 0;
        questionItem.showAppendInput = false;
      } else {
        questionItem.scriptResult = optionValue;
        this.handleOptionChange(optionValue, index, options, questionItem);
      }
      // å¤„理完成后,确保重新计算可见题目的序号
      this.$forceUpdate();
    },
    // åœ¨methods部分,修改handleOptionChange方法:
    handleOptionChange(selectedOption, questionIndex, options, a) {
      console.log(selectedOption, questionIndex, options, a, "888");
      if (document.activeElement) {
        document.activeElement.blur();
      }
      // æ‰¾åˆ°è¢«é€‰ä¸­çš„选项对象
      const selectedOptionObj = options.find(
        (item) => item.optioncontent == selectedOption
      );
      if (selectedOptionObj) {
        this.questionList[questionIndex].nextScriptno =
          selectedOptionObj.nextQuestion;
        this.questionList[questionIndex].score = selectedOptionObj.score;
        this.questionList[questionIndex].prompt = selectedOptionObj.prompt;
      }
      // å¤„理异常状态高亮
      this.questionList[questionIndex].isabnormal =
        selectedOptionObj.isabnormal;
      // å¤„理附加输入框显示
      this.questionList[questionIndex].showAppendInput =
        selectedOptionObj.appendflag == 1;
      console.log(this.questionList);
      // if (!this.questionList[questionIndex].showAppendInput) {
      //   this.questionList[questionIndex].answerps = ""; // æ¸…除附加信息
      // }
      // ä¿å­˜å½“前题目之前已经隐藏的题目状态
      const previouslyHiddenBeforeCurrent = this.questionList
        .slice(0, questionIndex)
        .map((item, index) => (item.ishide ? index : -1))
        .filter((index) => index !== -1);
      // ä¿å­˜ä¹‹å‰å› nextQuestion=0而隐藏的题目范围
      const previouslyHiddenByEnd = this.questionList
        .map((item, index) => (item.hiddenByEnd ? index : -1))
        .filter((index) => index !== -1);
      // å¦‚æžœbranchFlag为1,处理题目跳转
      if (a.branchFlag == 1) {
        if (selectedOptionObj.nextQuestion == 0) {
          // ç»“束问答 - éšè—åŽé¢æ‰€æœ‰é¢˜ç›®å¹¶æ ‡è®°
          this.questionList = this.questionList.map((item, index) => ({
            ...item,
            ishide: index > questionIndex,
            hiddenByEnd: index > questionIndex, // æ ‡è®°è¿™äº›é¢˜ç›®æ˜¯è¢«ç»“束问答隐藏的
          }));
        } else {
          // æ­£å¸¸è·³è½¬é€»è¾‘
          const nextQuestionIndex = selectedOptionObj.nextQuestion - 1;
          console.log(nextQuestionIndex, 4);
          console.log(selectedOptionObj);
          this.questionList = this.questionList.map((item, index) => {
            // ä¿ç•™å½“前题目之前的隐藏状态
            if (index < questionIndex) {
              return {
                ...item,
                ishide: previouslyHiddenBeforeCurrent.includes(index),
                hiddenByEnd: false, // æ¸…除结束标记
              };
            }
            // å½“前题目总是可见
            if (index === questionIndex) {
              return { ...item, ishide: 0, hiddenByEnd: false };
            }
            // æ˜¾ç¤ºç›®æ ‡ä¸‹ä¸€é¢˜
            if (index === nextQuestionIndex) {
              return { ...item, ishide: 0, hiddenByEnd: false };
            }
            // å¦‚果是之前被结束问答隐藏的题目,现在应该恢复显示
            if (item.hiddenByEnd) {
              return { ...item, ishide: 0, hiddenByEnd: false };
            }
            // éšè—å½“前题和目标题之间的题目
            if (index > questionIndex && index < nextQuestionIndex) {
              return { ...item, ishide: 1, hiddenByEnd: false };
            }
            // å…¶ä»–情况保持原状
            return item;
          });
        }
      } else {
        // å¦‚果没有跳转,只需确保下一题可见
        this.questionList = this.questionList.map((item, index) => ({
          ...item,
          ishide: index === questionIndex + 1 ? 0 : item.ishide,
          hiddenByEnd: index === questionIndex + 1 ? false : item.hiddenByEnd,
        }));
      }
      // åœ¨å¤„理完题目显示/隐藏后,强制更新视图以确保序号正确
      this.$nextTick(() => {
        this.$forceUpdate();
      });
    },
    // å¤„理单选选项
    // handleOptionChange(selectedvalue, index, arr) {
    //   // æŸ¥æ‰¾é€‰ä¸­çš„选项对象
    //   const selectedOption = arr.svyTaskTemplateTargetoptions.find(
    //     (option) => option.optioncontent == selectedvalue
    //   );
    //   if (selectedOption) {
    //     this.questionList[index].nextScriptno = selectedOption.nextQuestion;
    //     this.questionList[index].score = selectedOption.score;
    //     this.questionList[index].prompt = selectedOption.prompt;
    //   }
    // },
    // å¤„理多选选项
    // updateScore(selectedvalues, index, arr) {
    //   // ï¿½ï¿½åŠ åˆ†æ•°
    //   let score = 0;
    //   selectedvalues.forEach((value) => {
    //     const selectedOption = arr.svyTaskTemplateTargetoptions.find(
    //       (option) => option.optioncontent == value
    //     );
    //     if (selectedOption) {
    //       score += Number(selectedOption.score);
    //     }
    //   });
    //   this.questionList[index].score = score;
    // },
  },
};
</script>
<style lang="scss" scoped>
.questionnaire-optimized {
  min-height: 100vh;
  background: linear-gradient(135deg, #f5f7fa 0%, #e4edf5 100%);
  padding: 20px 0;
  font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
}
.content-wrapper {
  max-width: 800px;
  margin: 0 auto;
  padding: 0 15px;
}
.questionnaire-container {
  background: #ffffff;
  border-radius: 12px;
  box-shadow: 0 5px 20px rgba(0, 0, 0, 0.08);
  padding: 30px;
  margin-bottom: 30px;
}
.questionnaire-header {
  text-align: center;
  margin-bottom: 25px;
}
.questionnaire-title {
  color: #175997;
  font-size: 28px;
  font-weight: 700;
  margin-bottom: 15px;
  line-height: 1.3;
}
.questionnaire-description {
  font-size: 18px;
  color: #5a6c84;
  line-height: 1.6;
  max-width: 700px;
  margin: 0 auto;
}
.custom-divider {
  margin: 25px 0;
  background-color: #eaeef2;
}
.questions-section {
  margin-bottom: 40px;
}
.question-item {
  margin-bottom: 35px;
  padding: 20px;
  border-radius: 8px;
  border: 1px solid #eaeef2;
  transition: all 0.3s ease;
  &:hover {
    border-color: #d1e0f0;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
  }
  &.has-warning {
    border-left: 4px solid #e6a23c;
  }
}
.question-stem {
  display: flex;
  align-items: flex-start;
  margin-bottom: 20px;
  font-size: 18px;
}
.question-number {
  font-weight: 600;
  color: #175997;
  margin-right: 8px;
  min-width: 24px;
}
.question-text {
  flex: 1;
  line-height: 1.5;
  color: #2c3e50;
  font-weight: 500;
}
.question-type-tag {
  color: #3ba2f7;
  font-size: 14px;
  margin-left: 10px;
  font-weight: 500;
}
.question-options {
  margin: 15px 0;
}
.options-group {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.option-radio,
.option-checkbox {
  margin: 0;
  padding: 12px 15px;
  border-radius: 6px;
  border: 1px solid #e0e0e0;
  transition: all 0.2s;
  &:hover {
    border-color: #c0c4cc;
    background-color: #f8fafc;
  }
}
:deep(.option-radio .el-radio__label),
:deep(.option-checkbox .el-checkbox__label) {
  display: flex;
  align-items: center;
  font-size: 16px;
}
.option-text {
  margin-right: 5px;
}
.abnormal-indicator {
  color: #f56c6c;
  font-weight: bold;
}
.abnormal-option {
  :deep(.el-radio__inner) {
    border-color: #f56c6c;
  }
  :deep(.el-checkbox__inner) {
    border-color: #f56c6c;
  }
}
.question-input {
  margin: 15px 0;
}
.answer-textarea {
  :deep(.el-textarea__inner) {
    font-size: 16px;
    line-height: 1.5;
  }
}
.question-warning {
  margin-top: 15px;
}
.warning-alert {
  :deep(.el-alert__title) {
    font-size: 15px;
    line-height: 1.4;
  }
}
.submit-section {
  text-align: center;
  padding: 20px 0 10px;
}
.submit-button {
  width: 100%;
  max-width: 300px;
  height: 50px;
  font-size: 18px;
  font-weight: 500;
  border-radius: 8px;
  background: linear-gradient(135deg, #175997 0%, #2a77c9 100%);
  border: none;
  box-shadow: 0 4px 12px rgba(23, 89, 151, 0.3);
  transition: all 0.3s;
  &:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 16px rgba(23, 89, 151, 0.4);
  }
  &:active {
    transform: translateY(0);
  }
}
.completion-page {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 70vh;
  padding: 20px;
}
.completion-content {
  text-align: center;
  max-width: 600px;
  padding: 40px;
  background: #ffffff;
  border-radius: 12px;
  box-shadow: 0 5px 20px rgba(0, 0, 0, 0.08);
}
.completion-icon {
  font-size: 80px;
  color: #52c41a;
  margin-bottom: 20px;
}
.completion-title {
  color: #175997;
  font-size: 32px;
  font-weight: 700;
  margin-bottom: 20px;
}
.completion-message {
  font-size: 18px;
  color: #5a6c84;
  line-height: 1.6;
}
/* å“åº”式设计 */
@media (max-width: 768px) {
  .content-wrapper {
    padding: 0 10px;
  }
  .questionnaire-container {
    padding: 20px 15px;
  }
  .questionnaire-title {
    font-size: 24px;
  }
  .questionnaire-description {
    font-size: 16px;
  }
  .question-stem {
    font-size: 16px;
    flex-direction: column;
    align-items: flex-start;
  }
  .question-type-tag {
    margin-left: 0;
    margin-top: 5px;
  }
  .question-item {
    padding: 15px;
  }
  .completion-content {
    padding: 30px 20px;
  }
  .completion-title {
    font-size: 28px;
  }
  .completion-message {
    font-size: 16px;
  }
}
@media (max-width: 480px) {
  .questionnaire-title {
    font-size: 22px;
  }
  .completion-title {
    font-size: 24px;
  }
  .completion-icon {
    font-size: 60px;
  }
}
</style>
src/views/outsideChainwtnew.vue
@@ -1,146 +1,162 @@
<template>
  <div class="questionnaire">
    <div class="CONTENT" v-if="!accomplish">
      <div class="preview-left">
        <div class="toptitle">
          <div class="title">{{ taskname ? taskname : "问卷" }}</div>
          <div style="font-size: 22px; margin-bottom: 20px; line-height: 1.5">
  <div class="questionnaire-optimized">
    <div class="content-wrapper" v-if="!accomplish">
      <div class="questionnaire-container">
        <!-- é—®å·æ ‡é¢˜åŒºåŸŸ -->
        <div class="questionnaire-header">
          <h1 class="questionnaire-title">
            {{ taskname ? taskname : "问卷" }}
          </h1>
          <div class="questionnaire-description">
            {{
              kcb
                ? kcb
                : "亲爱的患者-家属,您好!我们是无锡儿童医院的医护人员,为了更好地了解您的康复情况,请您抽一点宝贵时间,完成这份随访问卷。"
                : "亲爱的患者-家属,我们是医院的医护人员,为了更好地了解您的康复情况,请您抽一点宝贵时间,完成这份随访问卷。"
            }}
            <!-- äº²çˆ±çš„æ‚£è€…/家属您好,为了更好的了解您出院后的康复情况,给您适当及时的健康指导,请您抽一点宝贵时间,完成这份出院随访问卷调查。 -->
          </div>
        </div>
        <el-divider></el-divider>
        <!-- å•选 -->
        <div
          class="topic-dev"
          v-for="(item, index) in questionList"
          :key="item.aaa"
        >
          <div class="scriptTopic-dev" :key="index" v-if="item.scriptType == 1">
            <div class="dev-text">
              {{ index + 1 }}、<span style="line-height: 1.5"
                >{{ item.scriptContent }}
                <span style="color: #3ba2f7">[单选]</span></span
        <el-divider class="custom-divider"></el-divider>
        <!-- é—®å·é¢˜ç›®åŒºåŸŸ -->
        <div class="questions-section">
          <div
            class="question-item"
            v-for="(item, index) in visibleQuestions"
            :key="item.id"
            :class="{
              'has-warning':
                item.prompt &&
                item.scriptResult &&
                (item.scriptType !== 2 || item.scriptResult.length > 0),
            }"
          >
            <!-- é¢˜ç›®é¢˜å¹² -->
            <div class="question-stem">
              <span class="question-number"
                >{{ getVisibleQuestionIndex(index) }}.</span
              >
              <span class="question-text">{{ item.scriptContent }}</span>
              <span class="question-type-tag">
                {{
                  item.scriptType == 1
                    ? "[单选]"
                    : item.scriptType == 2
                    ? "[多选]"
                    : "[问答]"
                }}
              </span>
            </div>
            <div class="dev-xx">
              <el-radio-group
                class="custom-radio"
                v-model="item.scriptResult"
                @change="handleOptionChange($event, index, item)"
              >
            <!-- å•选题目 -->
            <div
              class="question-options"
              v-if="item.scriptType == 1 && !item.ishide"
            >
              <el-radio-group class="options-group" v-model="item.scriptResult">
                <el-radio
                  border
                  v-for="(items, index) in item.svyLibTemplateTargetoptions"
                  :class="
                    items.isabnormal && item.scriptResult == items.optioncontent
                      ? 'red-star'
                      : ''
                  v-for="(
                    option, optionIndex
                  ) in item.svyLibTemplateTargetoptions"
                  :key="optionIndex"
                  :label="option.optioncontent"
                  :class="{
                    'abnormal-option':
                      option.isabnormal &&
                      item.scriptResult == option.optioncontent,
                  }"
                  @click.native.prevent="
                    handleRadioToggle(
                      item,
                      index,
                      item.svyLibTemplateTargetoptions,
                      option.optioncontent
                    )
                  "
                  :key="index"
                  :label="items.optioncontent"
                  >{{ items.optioncontent }}</el-radio
                  class="option-radio"
                >
                  <span class="option-text">{{ option.optioncontent }}</span>
                </el-radio>
              </el-radio-group>
            </div>
            <div v-show="item.prompt">
              <el-alert :title="item.prompt" type="warning"> </el-alert>
            </div>
          </div>
          <!-- å¤šé€‰ -->
          <div class="scriptTopic-dev" :key="index" v-if="item.scriptType == 2">
            <div class="dev-text">
              {{ index + 1 }}、<span style="line-height: 1.5"
                >{{ item.scriptContent }}
                <span style="color: #3ba2f7">[多选]</span></span
              >
            </div>
            <div class="dev-xx">
            <!-- å¤šé€‰é¢˜ç›® -->
            <div class="question-options" v-if="item.scriptType == 2">
              <el-checkbox-group
                class="custom-radio"
                class="options-group"
                v-model="item.scriptResult"
              >
                <el-checkbox
                  border
                  v-for="(
                    option, optionIndex
                  ) in item.svyLibTemplateTargetoptions"
                  :key="optionIndex"
                  :label="option.optioncontent"
                  :class="{
                    'abnormal-option': option.isabnormal,
                  }"
                  @change="$forceUpdate()"
                  v-for="(items, indexs) in item.svyLibTemplateTargetoptions"
                  :key="indexs"
                  :label="items.optioncontent"
                  class="option-checkbox"
                >
                  {{ items.optioncontent }}
                  <span class="option-text">{{ option.optioncontent }}</span>
                </el-checkbox>
              </el-checkbox-group>
            </div>
            <!-- :class="items.isabnormal ? 'red-star' : ''" -->
            <div v-show="item.prompt && item.scriptResult[0]">
              <el-alert :title="item.prompt" type="warning"> </el-alert>
            </div>
          </div>
          <!-- å¡«ç©º -->
          <div class="scriptTopic-dev" :key="index" v-if="item.scriptType == 4">
            <div class="dev-text">
              {{ index + 1 }}、<span style="line-height: 1.5"
                >{{ item.scriptContent
                }}<span style="color: #3ba2f7">[问答]</span></span
              >
            </div>
            <div class="dev-xx">
            <!-- å¡«ç©ºé¢˜ç›® -->
            <div class="question-input" v-if="item.scriptType == 4">
              <el-input
                type="textarea"
                :rows="3"
                placeholder="请输入答案"
                placeholder="请输入您的回答"
                v-model="item.scriptResult"
                clearable
              >
              </el-input>
                class="answer-textarea"
              ></el-input>
            </div>
            <!-- æç¤ºä¿¡æ¯ -->
            <div
              class="question-warning"
              v-show="
                item.prompt &&
                item.scriptResult &&
                (item.scriptType !== 2 || item.scriptResult.length > 0)
              "
            >
              <el-alert
                :title="item.prompt"
                type="warning"
                :closable="false"
                class="warning-alert"
              ></el-alert>
            </div>
          </div>
        </div>
        <div class="bottom-fixed">
          <el-button
            type="primary"
            style="width: 80%; font-size: 20px"
            @click="cache(true)"
            >提交问卷</el-button
          >
        <!-- æäº¤æŒ‰é’® -->
        <div class="submit-section">
          <el-button type="primary" @click="cache(true)" class="submit-button">
            æäº¤é—®å·
          </el-button>
        </div>
      </div>
    </div>
    <div class="CONTENT" v-else>
      <div class="preview-lefts">
        <div
          style="
            text-align: center;
            padding-top: 50px;
            font-size: 24px;
            color: #175997;
            font-weight: 600;
            margin-bottom: 10px;
          "
        >
          æ„Ÿè°¢æ‚¨çš„配合!
        </div>
        <div style="font-size: 20px">
    <!-- å®Œæˆé¡µé¢ -->
    <div class="completion-page" v-else>
      <div class="completion-content">
        <div class="completion-icon">✓</div>
        <h2 class="completion-title">感谢您的配合!</h2>
        <p class="completion-message">
          {{
            jsy
              ? jsy
              : "感谢您参与本次随访。您的反馈帮助我们更好地了解宝宝的适应情况,并为您提供更精准的护理建议与健康指导。祝您和宝宝健康快乐"
              : "生活上要劳逸结合,注意休息和营养,适当锻炼,戒烟限酒,保持心情舒畅,定期复诊。那本次回访就到这里,祝您身体健康!"
          }}
        </div>
        </p>
      </div>
    </div>
    <!-- <el-dialog
      :visible.sync="dialogVisible"
      width="50%"
    >
      <div>
        <el-alert :title="jsy" type="success"> </el-alert>
      </div>
    </el-dialog> -->
  </div>
</template>
@@ -159,94 +175,10 @@
      taskid: 355,
      patid: 265823,
      kcb: "",
      excep: "",
      questionList: [
        // {
        //   scriptType: 1,
        //   scriptContent: "您的年龄范围是?",
        //   scriptResult: null,
        //   svyLibTemplateTargetoptions: [
        //     { optioncontent: "18-25", value: "18-25", isabnormal: true },
        //     { optioncontent: "26-35", value: "26-35" },
        //     { optioncontent: "36-45", value: "36-45" },
        //     { optioncontent: "46-55", value: "46-55" },
        //     { optioncontent: "56+", value: "56+" },
        //   ],
        //   required: true,
        // },
        // {
        //   scriptType: 1,
        //   scriptContent: "您的职业是什么?",
        //   scriptResult: null,
        //   svyLibTemplateTargetoptions: [
        //     { optioncontent: "学生", value: "student" },
        //     { optioncontent: "教师", value: "teacher" },
        //     { optioncontent: "工程师", value: "engineer" },
        //     { optioncontent: "医生", value: "doctor" },
        //     { optioncontent: "其他", value: "other" },
        //   ],
        //   required: false,
        // },
        // {
        //   scriptType: 2,
        //   scriptContent: "您感兴趣的活动有哪些?",
        //   scriptResult: [],
        //   svyLibTemplateTargetoptions: [
        //     { optioncontent: "旅游", value: "travel", isabnormal: true },
        //     { optioncontent: "阅读", value: "reading", isabnormal: true },
        //     { optioncontent: "运动", value: "sports", isabnormal: true },
        //     { optioncontent: "音乐", value: "music" },
        //     { optioncontent: "电影", value: "movies" },
        //   ],
        //   required: false,
        // },
        // {
        //   scriptType: 1,
        //   scriptContent: "您的职业是什么?",
        //   scriptResult: null,
        //   svyLibTemplateTargetoptions: [
        //     { optioncontent: "学生", value: "student" },
        //     { optioncontent: "教师", value: "teacher" },
        //     { optioncontent: "工程师", value: "engineer" },
        //     { optioncontent: "医生", value: "doctor" },
        //     { optioncontent: "其他", value: "other" },
        //   ],
        //   required: false,
        // },
        // {
        //   scriptType: 1,
        //   scriptContent: "您的职业是什么?",
        //   scriptResult: null,
        //   svyLibTemplateTargetoptions: [
        //     { optioncontent: "学生", value: "student" },
        //     { optioncontent: "教师", value: "teacher" },
        //     { optioncontent: "工程师", value: "engineer", isabnormal: true },
        //     { optioncontent: "医生", value: "doctor" },
        //     { optioncontent: "其他", value: "other" },
        //   ],
        //   required: false,
        // },
        // {
        //   scriptType: 1,
        //   scriptContent: "您的职业是什么?",
        //   scriptResult: null,
        //   svyLibTemplateTargetoptions: [
        //     { optioncontent: "学生", value: "student" },
        //     { optioncontent: "教师", value: "teacher" },
        //     { optioncontent: "工程师", value: "engineer" },
        //     { optioncontent: "医生", value: "doctor" },
        //     { optioncontent: "其他", value: "other" },
        //   ],
        //   required: false,
        // },
        // {
        //   scriptType: 4,
        //   scriptContent: "您的姓名是什么?",
        //   scriptResult: "name",
        //   required: true,
        //   scriptResult: null,
        // },
      ],
      excep: 0,
      isabnormal: 0,
      taskname: "",
      questionList: [],
      jsy: null,
      dialogVisible: false,
      Endornot: true,
@@ -265,45 +197,61 @@
    };
  },
  mounted() {
    window.addEventListener("beforeunload", this.cache);
    // window.addEventListener("beforeunload", this.cache);
  },
  beforeDestroy() {
    window.removeEventListener("beforeunload", this.cache);
    // window.removeEventListener("beforeunload", this.cache);
  },
  created() {
    this.geturlinfo();
  },
  computed: {
    // è®¡ç®—属性:获取所有可见的题目
    visibleQuestions() {
      if (!Array.isArray(this.questionList)) {
        return [];
      }
      return this.questionList.filter((question) => !question.ishide);
    },
  },
  methods: {
    // è§£æžurlid
    geturlinfo() {
       // let url = window.location.href;
       let url = this.$route.query.p;
       console.log(url,"url");
      // let url = window.location.href;
      let url = this.$route.query.p;
      console.log(url, "url");
      // let url = 'http://218.108.11.22:8093/sf/003';
      // let urlid = this.extractLastSegmentFromUrl(url);
      geturlinfo( url ).then((res) => {
       if (res.code==200) {
         this.getQuestionnaire(res.data.param1,res.data.param2,res.data.param3,res.data.param5,)
       }
      geturlinfo(url).then((res) => {
        if (res.code == 200) {
          this.getQuestionnaire(
            res.data.param1,
            res.data.param2,
            res.data.param3,
            res.data.param5
          );
        }
      });
    },
//     extractLastSegmentFromUrl(url) {
//     // æ‰¾åˆ°æœ€åŽä¸€ä¸ª'/'的位置
//     const lastSlashIndex = url.lastIndexOf('/');
//     // å¦‚果找到了'/',截取其后的所有字符
//     if (lastSlashIndex !== -1) {
//         return url.substring(lastSlashIndex + 1);
//     }
//     // å¦‚果没有找到'/',返回空字符串
//     return '';
// },
    //     extractLastSegmentFromUrl(url) {
    //     // æ‰¾åˆ°æœ€åŽä¸€ä¸ª'/'的位置
    //     const lastSlashIndex = url.lastIndexOf('/');
    //     // å¦‚果找到了'/',截取其后的所有字符
    //     if (lastSlashIndex !== -1) {
    //         return url.substring(lastSlashIndex + 1);
    //     }
    //     // å¦‚果没有找到'/',返回空字符串
    //     return '';
    // },
    // èŽ·å–æ•°æ®
    getQuestionnaire(param1,param2,param3) {
      this.taskid = decodeURIComponent(param1);
      this.patid = decodeURIComponent(param2);
      this.taskname = decodeURIComponent(param3);
    getQuestionnaire(param1, param2, param3) {
      console.log(param1,'param1');
      this.taskid = param1;
      this.patid = param2;
      this.taskname = param3;
      // let taskid =
      //   "OFp7tn/B6x7IzKJetvGWHdSWBj7msRlnlj6am9dyuHTH6sEt4uBbVCUXs5kcF/e4O2W6vqHf2Bz9K3/evbYDmw==";
      // let patid =
@@ -316,7 +264,7 @@
      getCachequestionnaire({ param1: this.taskid, param2: this.patid }).then(
        (res) => {
          if (res.code == 200) {
            this.questionList = res.data;
            this.questionList = res.data.result;
            this.accomplish = res.data.submit;
            if (this.questionList[0]) {
              this.questionList.forEach((item) => {
@@ -351,6 +299,8 @@
                item.scriptResult = [];
              }
            });
            console.log(this.questionList,'this.questionList');
          }
        }
      );
@@ -374,8 +324,11 @@
        param1: this.taskid,
        param2: this.patid,
        excep: this.excep,
        isabnormal: this.isabnormal,
        serviceSubtaskDetailList: [],
      };
      console.log(form.isabnormal, "formisabnormal");
      const arr = structuredClone(this.questionList);
      // arr.forEach((item) => {
      //   item.asrtext = JSON.stringify(item.scriptResult);
@@ -403,18 +356,31 @@
        svyLibTemplateScriptVOS: [],
      };
      const arr = structuredClone(this.questionList);
      console.log(arr, "srr");
      arr.forEach((item) => {
        item.ishide = item.ishide ? 1 : 0;
      });
      arr.forEach((item, index) => {
        var obj = item.svyLibTemplateTargetoptions.find(
          (items) => items.optioncontent == item.scriptResult
        );
        console.log(obj,'obj');
        if (item.scriptType == 1 && item.scriptResult) {
          var obj = item.svyLibTemplateTargetoptions.find(
            (items) => items.optioncontent == item.scriptResult
          );
          console.log(item);
        if (obj) {
          if (obj.isabnormal) {
          console.log(obj, "obj");
          if (obj.sendTaskid) {
            item.sendTaskname = obj.sendTaskname;
            item.sendTaskid = obj.sendTaskid;
            console.log(String(obj.sendTaskid).split(","));
            item.sendTaskids = String(obj.sendTaskid).split(",");
          }
          if (obj) {
            form.excep = obj.isabnormal;
            form.isabnormal = obj.isabnormal;
            if (this.isabnormal != 1 && obj.isabnormal) {
              this.excep = obj.isabnormal;
              this.isabnormal = obj.isabnormal;
            }
            console.log(obj.isabnormal);
            form.excep = 1;
            this.excep = 1;
          }
        }
      });
@@ -436,19 +402,145 @@
        }
      });
    },
    // å¤„理单选选项
    handleOptionChange(selectedvalue, index, arr) {
      // æŸ¥æ‰¾é€‰ä¸­çš„选项对象
      const selectedOption = arr.svyLibTemplateTargetoptions.find(
        (option) => option.optioncontent == selectedvalue
      );
      if (selectedOption) {
        // å°†é€‰ä¸­çš„选项对象的 id èµ‹å€¼ç»™ obj.sonId
        this.questionList[index].nextScriptno = selectedOption.nextQuestion;
        this.questionList[index].score = selectedOption.score;
        this.questionList[index].prompt = selectedOption.prompt;
      }
    // èŽ·å–å¯è§é¢˜ç›®çš„æ­£ç¡®åºå·ï¼ˆè§£å†³è·³é¢˜åŽåºå·ä¸è¿žç»­çš„é—®é¢˜ï¼‰
    getVisibleQuestionIndex(index) {
      return index + 1;
    },
    // æ–°å¢žçš„切换选中/取消选中方法
    handleRadioToggle(questionItem, index, options, optionValue) {
      // ä¿å­˜å½“前状态以便后续比较
      const previousState = JSON.parse(JSON.stringify(this.questionList));
      // åŽŸæœ‰çš„å¤„ç†é€»è¾‘
      if (questionItem.scriptResult === optionValue) {
        questionItem.scriptResult = "";
        questionItem.isabnormal = 0;
        questionItem.showAppendInput = false;
      } else {
        questionItem.scriptResult = optionValue;
        this.handleOptionChange(optionValue, index, options, questionItem);
      }
      // å¤„理完成后,确保重新计算可见题目的序号
      this.$forceUpdate();
    },
    // åœ¨methods部分,修改handleOptionChange方法:
    handleOptionChange(selectedOption, questionIndex, options, a) {
      console.log(selectedOption, questionIndex, options, a, "888");
      if (document.activeElement) {
        document.activeElement.blur();
      }
      // æ‰¾åˆ°è¢«é€‰ä¸­çš„选项对象
      const selectedOptionObj = options.find(
        (item) => item.optioncontent == selectedOption
      );
      if (selectedOptionObj) {
        this.questionList[questionIndex].nextScriptno =
          selectedOptionObj.nextQuestion;
        this.questionList[questionIndex].score = selectedOptionObj.score;
        this.questionList[questionIndex].prompt = selectedOptionObj.prompt;
      }
      // å¤„理异常状态高亮
      this.questionList[questionIndex].isabnormal =
        selectedOptionObj.isabnormal;
      // å¤„理附加输入框显示
      this.questionList[questionIndex].showAppendInput =
        selectedOptionObj.appendflag == 1;
      console.log(this.questionList);
      // if (!this.questionList[questionIndex].showAppendInput) {
      //   this.questionList[questionIndex].answerps = ""; // æ¸…除附加信息
      // }
      // ä¿å­˜å½“前题目之前已经隐藏的题目状态
      const previouslyHiddenBeforeCurrent = this.questionList
        .slice(0, questionIndex)
        .map((item, index) => (item.ishide ? index : -1))
        .filter((index) => index !== -1);
      // ä¿å­˜ä¹‹å‰å› nextQuestion=0而隐藏的题目范围
      const previouslyHiddenByEnd = this.questionList
        .map((item, index) => (item.hiddenByEnd ? index : -1))
        .filter((index) => index !== -1);
      // å¦‚æžœbranchFlag为1,处理题目跳转
      if (a.branchFlag == 1) {
        if (selectedOptionObj.nextQuestion == 0) {
          // ç»“束问答 - éšè—åŽé¢æ‰€æœ‰é¢˜ç›®å¹¶æ ‡è®°
          this.questionList = this.questionList.map((item, index) => ({
            ...item,
            ishide: index > questionIndex,
            hiddenByEnd: index > questionIndex, // æ ‡è®°è¿™äº›é¢˜ç›®æ˜¯è¢«ç»“束问答隐藏的
          }));
        } else {
          // æ­£å¸¸è·³è½¬é€»è¾‘
          const nextQuestionIndex = selectedOptionObj.nextQuestion - 1;
          console.log(nextQuestionIndex, 4);
          console.log(selectedOptionObj);
          this.questionList = this.questionList.map((item, index) => {
            // ä¿ç•™å½“前题目之前的隐藏状态
            if (index < questionIndex) {
              return {
                ...item,
                ishide: previouslyHiddenBeforeCurrent.includes(index),
                hiddenByEnd: false, // æ¸…除结束标记
              };
            }
            // å½“前题目总是可见
            if (index === questionIndex) {
              return { ...item, ishide: 0, hiddenByEnd: false };
            }
            // æ˜¾ç¤ºç›®æ ‡ä¸‹ä¸€é¢˜
            if (index === nextQuestionIndex) {
              return { ...item, ishide: 0, hiddenByEnd: false };
            }
            // å¦‚果是之前被结束问答隐藏的题目,现在应该恢复显示
            if (item.hiddenByEnd) {
              return { ...item, ishide: 0, hiddenByEnd: false };
            }
            // éšè—å½“前题和目标题之间的题目
            if (index > questionIndex && index < nextQuestionIndex) {
              return { ...item, ishide: 1, hiddenByEnd: false };
            }
            // å…¶ä»–情况保持原状
            return item;
          });
        }
      } else {
        // å¦‚果没有跳转,只需确保下一题可见
        this.questionList = this.questionList.map((item, index) => ({
          ...item,
          ishide: index === questionIndex + 1 ? 0 : item.ishide,
          hiddenByEnd: index === questionIndex + 1 ? false : item.hiddenByEnd,
        }));
      }
      // åœ¨å¤„理完题目显示/隐藏后,强制更新视图以确保序号正确
      this.$nextTick(() => {
        this.$forceUpdate();
      });
    },
    // å¤„理单选选项
    // handleOptionChange(selectedvalue, index, arr) {
    //   // æŸ¥æ‰¾é€‰ä¸­çš„选项对象
    //   const selectedOption = arr.svyLibTemplateTargetoptions.find(
    //     (option) => option.optioncontent == selectedvalue
    //   );
    //   if (selectedOption) {
    //     this.questionList[index].nextScriptno = selectedOption.nextQuestion;
    //     this.questionList[index].score = selectedOption.score;
    //     this.questionList[index].prompt = selectedOption.prompt;
    //   }
    // },
    // å¤„理多选选项
    // updateScore(selectedvalues, index, arr) {
    //   // ï¿½ï¿½åŠ åˆ†æ•°
@@ -468,158 +560,294 @@
</script>
<style lang="scss" scoped>
.questionnaire {
  // background-image: url("../assets/images/chainbackground.jpg");
  background-color: #f9f9fb;
  background-size: cover;
  background-attachment: fixed; /* ä¿æŒèƒŒæ™¯å›ºå®š */
  background-position: center;
  font-family: Arial, sans-serif;
.questionnaire-optimized {
  min-height: 100vh;
  margin: 0;
  padding: 0;
  .CONTENT {
    .title {
      color: #3769f3;
      font-size: 22px;
      font-weight: bold;
      margin-bottom: 20px;
      text-align: center;
    }
  }
}
.preview-left {
  margin: 10px;
  margin-bottom: 60px;
  background-color: #fff;
  border-radius: 5px;
  //   margin: 20px;
  padding: 10px;
  height: 100%;
  // background: #ffff;
  border: 1px solid #dcdfe6;
  -webkit-box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.12),
    0 0 6px 0 rgba(0, 0, 0, 0.04);
  .topic-dev {
    margin-bottom: 25px;
    font-size: 20px !important;
    .dev-text {
      margin-bottom: 10px;
    }
  }
}
.preview-lefts {
  margin: 10px;
  background-color: #fff;
  border-radius: 5px;
  //   margin: 20px;
  padding: 10px;
  height: 95vh; // background: #ffff;
  border: 1px solid #dcdfe6;
  -webkit-box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.12),
    0 0 6px 0 rgba(0, 0, 0, 0.04);
  .topic-dev {
    margin-bottom: 25px;
    font-size: 20px !important;
    .dev-text {
      margin-bottom: 10px;
    }
  }
  background: linear-gradient(135deg, #f5f7fa 0%, #e4edf5 100%);
  padding: 20px 0;
  font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
}
.red-star {
  ::v-deep.el-radio__label {
    position: relative;
    padding-right: 10px; /* æ ¹æ®éœ€è¦è°ƒæ•´ */
  }
  ::v-deep.el-radio__label::after {
    content: "*";
    color: red;
    position: absolute;
    right: -5px; /* æ ¹æ®éœ€è¦è°ƒæ•´ */
    top: 0;
  }
  ::v-deep.el-checkbox__label {
    position: relative;
    padding-right: 10px; /* æ ¹æ®éœ€è¦è°ƒæ•´ */
  }
  ::v-deep.el-checkbox__label::after {
    content: "*";
    color: red;
    position: absolute;
    right: -5px; /* æ ¹æ®éœ€è¦è°ƒæ•´ */
    top: 0;
  }
}
::v-deep.el-checkbox-group {
  font-size: 0;
  display: flex;
  flex-direction: column;
  margin: 5px 0;
}
::v-deep.el-checkbox.is-bordered + .el-checkbox.is-bordered {
  margin-left: 0;
.content-wrapper {
  max-width: 800px;
  margin: 0 auto;
  padding: 0 15px;
}
::v-deep.el-radio-group {
  display: flex;
  flex-direction: column;
  margin: 5px 0;
}
::v-deep.el-radio.is-bordered + .el-radio.is-bordered {
  /* margin-left: 10px; */
  margin-left: 0;
}
::v-deep.custom-radio .el-radio {
  margin: 2px 0;
.questionnaire-container {
  background: #ffffff;
  border-radius: 12px;
  box-shadow: 0 5px 20px rgba(0, 0, 0, 0.08);
  padding: 30px;
  margin-bottom: 30px;
}
.radio-option {
  flex: none; /* ä¸è®©é€‰é¡¹è‡ªåŠ¨å¡«å……ç©ºé—´ */
  white-space: nowrap; /* é˜²æ­¢é€‰é¡¹æ–‡æœ¬æ¢è¡Œ */
  margin: 0 10px; /* è®¾ç½®é€‰é¡¹å·¦å³çš„é—´éš” */
  font-size: 20px; /* å¢žå¤§å­—体大小 */
}
.el-radio__label {
  font-size: 20px; /* å¢žå¤§æ ‡ç­¾æ–‡å­—大小 */
}
.toptitle {
}
.bottom-fixed {
  position: fixed;
  bottom: 0;
  left: 0;
  width: 100%;
.questionnaire-header {
  text-align: center;
  padding: 10px 0; /* æ ¹æ®éœ€è¦è°ƒæ•´å†…边距 */
  background: #fff; /* æ ¹æ®éœ€è¦è°ƒæ•´èƒŒæ™¯é¢œè‰² */
  box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1); /* å¯é€‰çš„阴影效果 */
  z-index: 1000; /* ç¡®ä¿æŒ‰é’®åœ¨é¡µé¢æœ€ä¸Šå±‚ */
  margin-bottom: 25px;
}
::v-deep.el-alert--warning.is-light {
  background-color: #fbf9f3;
  color: #ffba00;
.questionnaire-title {
  color: #175997;
  font-size: 28px;
  font-weight: 700;
  margin-bottom: 15px;
  line-height: 1.3;
}
::v-deep {
  .el-alert__title {
    font-size: 20px;
    line-height: 18px;
.questionnaire-description {
  font-size: 18px;
  color: #5a6c84;
  line-height: 1.6;
  max-width: 700px;
  margin: 0 auto;
}
.custom-divider {
  margin: 25px 0;
  background-color: #eaeef2;
}
.questions-section {
  margin-bottom: 40px;
}
.question-item {
  margin-bottom: 35px;
  padding: 20px;
  border-radius: 8px;
  border: 1px solid #eaeef2;
  transition: all 0.3s ease;
  &:hover {
    border-color: #d1e0f0;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
  }
  &.has-warning {
    border-left: 4px solid #e6a23c;
  }
}
::v-deep.el-radio--medium.is-bordered .el-radio__label {
  font-size: 20px;
.question-stem {
  display: flex;
  align-items: flex-start;
  margin-bottom: 20px;
  font-size: 18px;
}
::v-deep.el-radio--medium.is-bordered {
  padding: 5px 20px 0px 10px;
  border-radius: 4px;
  height: 36px;
.question-number {
  font-weight: 600;
  color: #175997;
  margin-right: 8px;
  min-width: 24px;
}
::v-deep.el-checkbox.is-bordered.el-checkbox--medium .el-checkbox__label {
  line-height: 17px;
  font-size: 20px;
.question-text {
  flex: 1;
  line-height: 1.5;
  color: #2c3e50;
  font-weight: 500;
}
::v-deep.el-checkbox {
  margin-right: 0px;
.question-type-tag {
  color: #3ba2f7;
  font-size: 14px;
  margin-left: 10px;
  font-weight: 500;
}
.question-options {
  margin: 15px 0;
}
.options-group {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.option-radio,
.option-checkbox {
  margin: 0;
  padding: 12px 15px;
  border-radius: 6px;
  border: 1px solid #e0e0e0;
  transition: all 0.2s;
  &:hover {
    border-color: #c0c4cc;
    background-color: #f8fafc;
  }
}
:deep(.option-radio .el-radio__label),
:deep(.option-checkbox .el-checkbox__label) {
  display: flex;
  align-items: center;
  font-size: 16px;
}
.option-text {
  margin-right: 5px;
}
.abnormal-indicator {
  color: #f56c6c;
  font-weight: bold;
}
.abnormal-option {
  :deep(.el-radio__inner) {
    border-color: #f56c6c;
  }
  :deep(.el-checkbox__inner) {
    border-color: #f56c6c;
  }
}
.question-input {
  margin: 15px 0;
}
.answer-textarea {
  :deep(.el-textarea__inner) {
    font-size: 16px;
    line-height: 1.5;
  }
}
.question-warning {
  margin-top: 15px;
}
.warning-alert {
  :deep(.el-alert__title) {
    font-size: 15px;
    line-height: 1.4;
  }
}
.submit-section {
  text-align: center;
  padding: 20px 0 10px;
}
.submit-button {
  width: 100%;
  max-width: 300px;
  height: 50px;
  font-size: 18px;
  font-weight: 500;
  border-radius: 8px;
  background: linear-gradient(135deg, #175997 0%, #2a77c9 100%);
  border: none;
  box-shadow: 0 4px 12px rgba(23, 89, 151, 0.3);
  transition: all 0.3s;
  &:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 16px rgba(23, 89, 151, 0.4);
  }
  &:active {
    transform: translateY(0);
  }
}
.completion-page {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 70vh;
  padding: 20px;
}
.completion-content {
  text-align: center;
  max-width: 600px;
  padding: 40px;
  background: #ffffff;
  border-radius: 12px;
  box-shadow: 0 5px 20px rgba(0, 0, 0, 0.08);
}
.completion-icon {
  font-size: 80px;
  color: #52c41a;
  margin-bottom: 20px;
}
.completion-title {
  color: #175997;
  font-size: 32px;
  font-weight: 700;
  margin-bottom: 20px;
}
.completion-message {
  font-size: 18px;
  color: #5a6c84;
  line-height: 1.6;
}
/* å“åº”式设计 */
@media (max-width: 768px) {
  .content-wrapper {
    padding: 0 10px;
  }
  .questionnaire-container {
    padding: 20px 15px;
  }
  .questionnaire-title {
    font-size: 24px;
  }
  .questionnaire-description {
    font-size: 16px;
  }
  .question-stem {
    font-size: 16px;
    flex-direction: column;
    align-items: flex-start;
  }
  .question-type-tag {
    margin-left: 0;
    margin-top: 5px;
  }
  .question-item {
    padding: 15px;
  }
  .completion-content {
    padding: 30px 20px;
  }
  .completion-title {
    font-size: 28px;
  }
  .completion-message {
    font-size: 16px;
  }
}
@media (max-width: 480px) {
  .questionnaire-title {
    font-size: 22px;
  }
  .completion-title {
    font-size: 24px;
  }
  .completion-icon {
    font-size: 60px;
  }
}
</style>
src/views/patient/SignAcontract/Review.vue
@@ -5,10 +5,10 @@
      <el-form :model="queryParams" ref="queryForm" :inline="true" label-width="100px">
        <el-row :gutter="20">
          <el-col :span="6">
            <el-form-item label="患者姓名" prop="patientName">
            <el-form-item label="儿童姓名" prop="patientName">
              <el-input
                v-model="queryParams.patientName"
                placeholder="请输入患者姓名"
                placeholder="请输入儿童姓名"
                clearable
                @keyup.enter="handleQuery"
              />
@@ -81,12 +81,15 @@
        style="width: 100%"
        :default-sort="{prop: 'applyTime', order: 'descending'}"
      >
        <el-table-column label="患者信息" min-width="200" fixed>
        <el-table-column label="儿童信息" min-width="200" fixed>
          <template slot-scope="scope">
            <div class="patient-info">
              <div class="patient-name">{{ scope.row.patientName }}</div>
              <div class="patient-detail">
                {{ scope.row.gender }} | {{ scope.row.age }}岁 | {{ scope.row.phone }}
                <div style="color: #909399; font-size: 12px; margin-top: 2px;">
                  {{ scope.row.applicableAge }}
                </div>
              </div>
            </div>
          </template>
@@ -110,7 +113,7 @@
        </el-table-column>
        <el-table-column label="申请时间" prop="applyTime" width="160" sortable />
        <el-table-column label="期望医生" prop="expectDoctor" width="120" />
        <el-table-column label="特殊要求" min-width="200">
        <el-table-column label="监护人要求" min-width="200">
          <template slot-scope="scope">
            <span v-if="scope.row.specialRequirements" :title="scope.row.specialRequirements">
              {{ scope.row.specialRequirements.substring(0, 30) }}...
@@ -187,7 +190,7 @@
            type="textarea"
            :rows="3"
            v-model="reviewForm.rejectReason"
            placeholder="请输入驳回的具体原因,便于患者了解情况"
            placeholder="请输入驳回的具体原因,便于监护人了解情况"
            maxlength="200"
            show-word-limit
          />
@@ -255,18 +258,44 @@
        rejected: 0
      },
      availableDoctors: [
        { id: '1', name: '王医生', department: '内科' },
        { id: '2', name: '李医生', department: '老年科' },
        { id: '3', name: '张医生', department: '妇产科' },
        { id: '4', name: '刘医生', department: '儿科' },
        { id: '1', name: '王医生', department: '儿科' },
        { id: '2', name: '李医生', department: '儿童保健科' },
        { id: '3', name: '张医生', department: '儿科' },
        { id: '4', name: '刘医生', department: '儿童营养科' },
        { id: '5', name: '陈医生', department: '全科' }
      ],
      // å„¿ç«¥æœåŠ¡å¥—é¤é…ç½®
      servicePackages: {
        '1': { name: '基础健康管理包', color: 'info' },
        '2': { name: '慢性病管理包', color: 'success' },
        '3': { name: '老年人健康包', color: 'warning' },
        '4': { name: '孕产妇保健包', color: 'danger' },
        '5': { name: '儿童保健包', color: 'primary' }
        '1': {
          name: '婴幼儿健康基础包',
          color: 'primary',
          applicableAge: '0-3岁'
        },
        '2': {
          name: '学龄前儿童健康包',
          color: 'success',
          applicableAge: '3-6岁'
        },
        '3': {
          name: '学龄儿童综合健康包',
          color: 'warning',
          applicableAge: '7-12岁'
        },
        '4': {
          name: '青少年健康支持包',
          color: 'danger',
          applicableAge: '13-18岁'
        },
        '5': {
          name: '儿童营养与生长发育增值包',
          color: 'info',
          applicableAge: '3-18岁'
        },
        '6': {
          name: '儿童中医特色保健包',
          color: 'primary',
          applicableAge: '0-6岁'
        }
      },
      rules: {
        rejectReason: [
@@ -283,111 +312,128 @@
    this.calculateStats()
  },
  methods: {
//优化后的模拟数据生成方法
generateMockData() {
  const mockData = []
    // ç”Ÿæˆæ¨¡æ‹Ÿæ•°æ®
    generateMockData() {
      const mockData = []
  // ä½¿ç”¨æ‚¨æä¾›çš„真实姓名列表
  const patientNames = [
    '李肇芬', '卢木仲', '李成白', '方兆玉', '刘翊惠', '丁汉臻', '吴佳瑞', '舒绿珮',
    '周白芷', '张姿妤', '张虹伦', '周琼玟', '倪怡芳', '郭贵妃', '杨佩芳', '黄文旺',
    '黄盛玫', '郑丽青', '许智云', '张孟涵', '李小爱', '王恩龙', '朱政廷', '邓诗涵',
    '陈政倩', '吴俊伯', '阮馨学', '翁惠珠', '吴思翰', '林佩玲'
  ]
      // ä½¿ç”¨å„¿ç«¥å§“名列表
      const patientNames = [
        '李小宝', '张小明', '王雨欣', '刘浩然', '陈思琪', '杨宇航', '黄诗涵', '赵天宇',
        '周小萌', '吴俊杰', '郑雅雯', '孙沐辰', '朱雨萱', '马浩宇', '胡可馨', '林俊熙',
        '郭子轩', '何欣怡', '高天佑', '梁静怡', '罗浩然', '宋雨泽', '唐语嫣', '许博文',
        '谢欣妍', '冯子默', '董雨桐', '萧天乐', '曹心怡', '袁嘉豪'
      ]
  const specialReqs = [
    '希望医生能定期上门检查',
    '需要周末时间段的服务',
    '对药物有过敏史,需特别注意',
    '行动不便,需要上门服务',
    '无特殊要求',
    '需要英语服务支持',
    '有高血压病史,需重点关注',
    '需要定期血糖监测服务',
    '希望有固定的家庭医生',
    '需要心理疏导服务'
  ]
      const specialReqs = [
        '希望医生能定期上门检查',
        '需要周末时间段的服务',
        '对药物有过敏史,需特别注意',
        '行动不便,需要上门服务',
        '无特殊要求',
        '需要英语服务支持',
        '有哮喘病史,需重点关注',
        '需要定期生长发育监测',
        '希望有固定的儿科医生',
        '需要疫苗接种提醒服务'
      ]
  const rejectReasons = [
    '资料不完整,请补充健康档案',
    '不符合当前签约条件',
    '选择的医生档期已满',
    '服务套餐与病情不匹配',
    '年龄不符合套餐要求',
    '请补充完整的病史资料'
  ]
      const rejectReasons = [
        '监护人资料不完整,请补充身份证明',
        '儿童年龄不符合套餐要求',
        '选择的医生专长与儿童需求不匹配',
        '服务套餐与儿童健康状况不匹配',
        '请补充完整的儿童健康档案',
        '疫苗接种记录不完整'
      ]
  // ç”Ÿæˆçº¦30条数据(与姓名数量匹配)
  for (let i = 0; i < patientNames.length; i++) {
    const packageId = (i % 5) + 1 + ''
    const reviewStatus = i % 3 // 0:待审核, 1:通过, 2:驳回
    const applyDate = this.generateRandomDate('2024-10-01', '2024-12-08')
      for (let i = 0; i < patientNames.length; i++) {
        const packageId = (i % 6) + 1 + ''
        const packageInfo = this.servicePackages[packageId]
        const reviewStatus = i % 3 // 0:待审核, 1:通过, 2:驳回
        const applyDate = this.generateRandomDate('2024-10-01', '2024-12-08')
    // ç”Ÿæˆæ›´çœŸå®žçš„电话号码
    const phonePrefix = ['138', '139', '150', '151', '152', '186', '187', '188']
    const phone = `${phonePrefix[i % phonePrefix.length]}${this.padNumber(1000 + i * 37, 4)}${this.padNumber(i % 100, 2)}`
        // æ ¹æ®å¥—餐适用年龄生成合理的实际年龄
        let age
        switch(packageInfo.applicableAge) {
          case '0-3岁':
            age = Math.floor(Math.random() * 3) + 1
            break
          case '3-6岁':
            age = Math.floor(Math.random() * 3) + 3
            break
          case '7-12岁':
            age = Math.floor(Math.random() * 6) + 7
            break
          case '13-18岁':
            age = Math.floor(Math.random() * 6) + 13
            break
          default:
            age = Math.floor(Math.random() * 18) + 1
        }
    // ç”Ÿæˆåˆç†çš„年龄(0-80岁)
    const age = i % 80
    const gender = i % 2 === 0 ? '男' : '女'
        // ç”Ÿæˆç›‘护人电话号码
        const phonePrefix = ['138', '139', '150', '151', '152', '186', '187', '188']
        const phone = `${phonePrefix[i % phonePrefix.length]}${this.padNumber(1000 + i * 37, 4)}${this.padNumber(i % 100, 2)}`
    mockData.push({
      id: `A${2024000 + i}`,
      patientName: patientNames[i],
      gender: gender,
      age: age,
      phone: phone,
      servicePackageId: packageId,
      servicePackage: this.servicePackages[packageId].name,
      services: this.getServicesByPackage(packageId),
      contractPeriod: [1, 2][i % 2],
      applyTime: `${applyDate} ${this.padNumber(8 + (i % 10), 2)}:${this.padNumber(i % 60, 2)}:${this.padNumber(i % 60, 2)}`,
      expectDoctor: ['王医生', '李医生', '张医生', '刘医生', '陈医生'][i % 5],
      specialRequirements: specialReqs[i % specialReqs.length],
      reviewStatus: reviewStatus,
      reviewer: reviewStatus !== 0 ? ['管理员', '系统管理员', '审核专员'][i % 3] : '',
      reviewTime: reviewStatus !== 0 ?
        `${this.addDays(applyDate, 1 + (i % 3))} 14:${this.padNumber(i % 60, 2)}:00` : '',
      rejectReason: reviewStatus === 2 ? rejectReasons[i % rejectReasons.length] : ''
    })
  }
        mockData.push({
          id: `A${2024000 + i}`,
          patientName: patientNames[i],
          gender: i % 2 === 0 ? '男' : '女',
          age: age,
          phone: phone,
          servicePackageId: packageId,
          servicePackage: packageInfo.name,
          services: this.getServicesByPackage(packageId),
          contractPeriod: [1, 2][i % 2],
          applicableAge: packageInfo.applicableAge,
          applyTime: `${applyDate} ${this.padNumber(8 + (i % 10), 2)}:${this.padNumber(i % 60, 2)}:${this.padNumber(i % 60, 2)}`,
          expectDoctor: ['王医生', '李医生', '张医生', '刘医生', '陈医生'][i % 5],
          specialRequirements: specialReqs[i % specialReqs.length],
          reviewStatus: reviewStatus,
          reviewer: reviewStatus !== 0 ? ['管理员', '系统管理员', '审核专员'][i % 3] : '',
          reviewTime: reviewStatus !== 0 ?
            `${this.addDays(applyDate, 1 + (i % 3))} 14:${this.padNumber(i % 60, 2)}:00` : '',
          rejectReason: reviewStatus === 2 ? rejectReasons[i % rejectReasons.length] : ''
        })
      }
  return mockData
},
      return mockData
    },
// æ ¹æ®å¥—餐获取服务列表
getServicesByPackage(packageId) {
  const servicesMap = {
    '1': ['年度健康评估', '在线健康咨询', '健康档案管理', '定期健康提醒'],
    '2': ['专属医生服务', '用药指导管理', '定期随访监测', '个性化康复计划', '紧急医疗咨询'],
    '3': ['跌倒风险评估', '康复训练指导', '用药安全管理', '定期上门访视', '紧急联系服务', '心理健康关怀'],
    '4': ['孕期健康管理', '产后康复指导', '新生儿护理咨询', '营养膳食建议', '心理情绪支持'],
    '5': ['生长发育监测', '疫苗接种管理', '常见病防治', '营养指导', '早期教育咨询']
  }
  return servicesMap[packageId] || []
},
    // æ ¹æ®å¥—餐获取服务列表
    getServicesByPackage(packageId) {
      const servicesMap = {
        '1': ['新生儿家庭访视', '定期体格检查与发育评估', '血常规检测', '听力筛查', '喂养与护理指导', '预防接种服务', '中医保健指导'],
        '2': ['生长发育评估', '视力筛查与口腔保健', '血常规检查', '合理膳食与行为指导', '疾病预防与健康干预', '中医饮食调养指导'],
        '3': ['年度健康检查', '心理行为发育评估', '科学用眼与口腔保健', '合理膳食指导', '健康生活方式干预', '专家转诊绿色通道'],
        '4': ['青春期健康教育', '年度健康评估', '心理健康支持', '健康风险行为干预', '个性化健康方案', '优先预约检查服务'],
        '5': ['微量元素测定', '骨密度检测', '个性化膳食方案', '生长发育专项评估', '运动处方指导', '定期营养监测'],
        '6': ['中医体质辨识', '三伏贴服务', '小儿推拿', '耳穴治疗', '防感香囊', '食疗指导']
      }
      return servicesMap[packageId] || []
    },
// è¾…助方法:生成随机日期
generateRandomDate(start, end) {
  const startDate = new Date(start).getTime()
  const endDate = new Date(end).getTime()
  const randomTime = startDate + Math.random() * (endDate - startDate)
  return new Date(randomTime).toISOString().split('T')[0]
},
    // è¾…助方法:生成随机日期
    generateRandomDate(start, end) {
      const startDate = new Date(start).getTime()
      const endDate = new Date(end).getTime()
      const randomTime = startDate + Math.random() * (endDate - startDate)
      return new Date(randomTime).toISOString().split('T')[0]
    },
// è¾…助方法:添加天数
addDays(date, days) {
  const result = new Date(date)
  result.setDate(result.getDate() + days)
  return result.toISOString().split('T')[0]
},
    // è¾…助方法:添加天数
    addDays(date, days) {
      const result = new Date(date)
      result.setDate(result.getDate() + days)
      return result.toISOString().split('T')[0]
    },
// è¾…助方法:数字补零
padNumber(num, length) {
  return num.toString().padStart(length, '0')
},
    // è¾…助方法:数字补零
    padNumber(num, length) {
      return num.toString().padStart(length, '0')
    },
    // èŽ·å–å®¡æ ¸åˆ—è¡¨ [1](@ref)
    // èŽ·å–å®¡æ ¸åˆ—è¡¨
    async getList() {
      this.loading = true
      try {
@@ -435,7 +481,7 @@
      this.stats.rejected = allData.filter(item => item.reviewStatus === 2).length
    },
    // èŽ·å–å®¡æ ¸çŠ¶æ€æ–‡æœ¬ [2](@ref)
    // èŽ·å–å®¡æ ¸çŠ¶æ€æ–‡æœ¬
    getReviewStatusText(status) {
      const statusMap = { 0: '待审核', 1: '审核通过', 2: '审核驳回' }
      return statusMap[status] || '未知'
@@ -449,11 +495,14 @@
    // èŽ·å–å¥—é¤ç±»åž‹
    getPackageType(packageId) {
      const typeMap = { '1': 'info', '2': 'success', '3': 'warning', '4': 'danger', '5': 'primary' }
      const typeMap = {
        '1': 'primary', '2': 'success', '3': 'warning',
        '4': 'danger', '5': 'info', '6': 'primary'
      }
      return typeMap[packageId] || 'info'
    },
    // æœç´¢æ“ä½œ [1](@ref)
    // æœç´¢æ“ä½œ
    handleQuery() {
      this.queryParams.pageNum = 1
      this.getList()
@@ -471,7 +520,7 @@
      this.handleQuery()
    },
    // å¤„理审核操作 [2](@ref)
    // å¤„理审核操作
    handleReview(row, status) {
      this.currentRow = row
      this.reviewForm = {
@@ -483,7 +532,6 @@
      }
      this.reviewDialogVisible = true
      // æ¸…除表单验证
      this.$nextTick(() => {
        if (this.$refs.reviewFormRef) {
          this.$refs.reviewFormRef.clearValidate()
@@ -491,10 +539,9 @@
      })
    },
    // æäº¤å®¡æ ¸ [2,6](@ref)
    // æäº¤å®¡æ ¸
    async submitReview() {
      try {
        // è¡¨å•验证
        if (this.reviewForm.reviewStatus === 2) {
          if (!this.reviewForm.rejectReason) {
            this.$message.error('请填写驳回原因')
@@ -507,10 +554,8 @@
          return
        }
        // æ¨¡æ‹ŸAPI调用
        await new Promise(resolve => setTimeout(resolve, 1000))
        // æ›´æ–°å½“前行的状态
        const currentIndex = this.reviewList.findIndex(item => item.id === this.currentRow.id)
        if (currentIndex !== -1) {
          this.reviewList[currentIndex].reviewStatus = this.reviewForm.reviewStatus
@@ -518,7 +563,6 @@
          this.reviewList[currentIndex].reviewTime = new Date().toLocaleString()
          this.reviewList[currentIndex].rejectReason = this.reviewForm.rejectReason
          // å¦‚果审核通过,分配医生
          if (this.reviewForm.reviewStatus === 1) {
            const doctor = this.availableDoctors.find(d => d.id === this.reviewForm.assignDoctor)
            this.reviewList[currentIndex].expectDoctor = doctor ? doctor.name : this.reviewList[currentIndex].expectDoctor
@@ -527,7 +571,7 @@
        this.$message.success(this.reviewForm.reviewStatus === 1 ? '审核通过成功' : '审核驳回成功')
        this.reviewDialogVisible = false
        this.calculateStats() // é‡æ–°è®¡ç®—统计信息
        this.calculateStats()
      } catch (error) {
        console.error('审核操作失败:', error)
        this.$message.error('审核操作失败')
@@ -536,54 +580,10 @@
    // æŸ¥çœ‹è¯¦æƒ…
    handleView(row) {
      this.$message.info(`查看患者 ${row.patientName} çš„申请详情`)
      // å®žé™…开发中跳转到详情页
      // this.$router.push({ path: '/patient/contract/apply-detail', query: { id: row.id } })
    },
    // æ‰¹é‡å®¡æ ¸é€šè¿‡
    handleBatchApprove() {
      const pendingItems = this.reviewList.filter(item => item.reviewStatus === 0)
      if (pendingItems.length === 0) {
        this.$message.warning('没有待审核的申请')
        return
      }
      this.$confirm(`确定要批量通过 ${pendingItems.length} ä¸ªå¾…审核申请吗?`, '批量审核', {
        type: 'warning'
      }).then(async () => {
        try {
          this.loading = true
          // æ¨¡æ‹Ÿæ‰¹é‡å®¡æ ¸API调用
          await new Promise(resolve => setTimeout(resolve, 2000))
          // æ›´æ–°æ‰€æœ‰å¾…审核项的状态
          pendingItems.forEach(item => {
            item.reviewStatus = 1
            item.reviewer = '当前用户'
            item.reviewTime = new Date().toLocaleString()
          })
          this.$message.success(`批量审核通过成功,共处理 ${pendingItems.length} ä¸ªç”³è¯·`)
          this.calculateStats()
        } catch (error) {
          this.$message.error('批量审核失败')
        } finally {
          this.loading = false
        }
      })
    },
    // å¯¼å‡ºå®¡æ ¸æ•°æ®
    handleExport() {
      const exportData = this.generateMockData()
      // å®žé™…开发中这里应该调用导出API
      console.log('导出数据:', exportData)
      this.$message.info('导出功能开发中,数据已打印到控制台')
      this.$message.info(`查看儿童 ${row.patientName} çš„申请详情`)
    }
  },
  watch: {
    // ç›‘听审核状态变化,动态设置验证规则 [6](@ref)
    'reviewForm.reviewStatus': function(newVal) {
      this.$nextTick(() => {
        if (this.$refs.reviewFormRef) {
@@ -638,53 +638,11 @@
    .patient-detail {
      font-size: 12px;
      color: #909399;
      line-height: 1.4;
    }
  }
  // å®¡æ ¸çŠ¶æ€æ ·å¼
  .review-status {
    padding: 4px 8px;
    border-radius: 4px;
    font-size: 12px;
    font-weight: 500;
    &.status-pending {
      background: #fdf6ec;
      color: #e6a23c;
    }
    &.status-approved {
      background: #f0f9e8;
      color: #67c23a;
    }
    &.status-rejected {
      background: #fef0f0;
      color: #f56c6c;
    }
  }
  // ç‰¹æ®Šè¦æ±‚文本样式
  .special-requirements {
    max-width: 200px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    &:hover {
      white-space: normal;
      overflow: visible;
      background: white;
      box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
      padding: 8px;
      border-radius: 4px;
      position: absolute;
      z-index: 1000;
      max-width: 300px;
    }
  }
  // è¡¨æ ¼æ ·å¼
  // è¡¨æ ¼æ ·å¼ä¼˜åŒ–
  ::v-deep .el-table {
    .el-table__header-wrapper {
      th {
@@ -714,95 +672,6 @@
    }
  }
  // å®¡æ ¸å¯¹è¯æ¡†æ ·å¼
  .review-dialog {
    ::v-deep .el-dialog {
      border-radius: 8px;
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    }
    .dialog-footer {
      text-align: right;
      margin-top: 20px;
    }
  }
  // æ“ä½œæŒ‰é’®æ ·å¼
  .action-buttons {
    display: flex;
    gap: 8px;
    .el-button {
      padding: 7px 12px;
      border-radius: 4px;
      font-size: 12px;
      &.approve-btn {
        background: #67c23a;
        border-color: #67c23a;
        color: white;
        &:hover {
          background: #5daf34;
          border-color: #5daf34;
        }
      }
      &.reject-btn {
        background: #f56c6c;
        border-color: #f56c6c;
        color: white;
        &:hover {
          background: #e65c5c;
          border-color: #e65c5c;
        }
      }
      &.detail-btn {
        color: #409eff;
        border-color: #409eff;
        &:hover {
          background: #ecf5ff;
        }
      }
    }
  }
  // æ‰¹é‡æ“ä½œæ 
  .batch-actions {
    background: #ecf5ff;
    padding: 12px 20px;
    margin-bottom: 16px;
    border-radius: 4px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    .batch-info {
      color: #409eff;
      font-size: 14px;
    }
  }
  // ç©ºçŠ¶æ€æ ·å¼
  .empty-state {
    text-align: center;
    padding: 40px 20px;
    color: #909399;
    .empty-icon {
      font-size: 48px;
      margin-bottom: 16px;
      opacity: 0.5;
    }
    .empty-text {
      font-size: 14px;
    }
  }
  // å“åº”式设计
  @media (max-width: 768px) {
    padding: 10px;
@@ -811,16 +680,6 @@
      .el-col {
        margin-bottom: 10px;
      }
    }
    .action-buttons {
      flex-direction: column;
    }
    .batch-actions {
      flex-direction: column;
      gap: 10px;
      align-items: stretch;
    }
  }
}
src/views/patient/SignAcontract/index.vue
@@ -27,11 +27,12 @@
          <el-col :span="6">
            <el-form-item label="服务套餐" prop="servicePackage">
              <el-select v-model="queryParams.servicePackage" placeholder="请选择套餐" clearable>
                <el-option label="基础健康管理包" value="1" />
                <el-option label="慢性病管理包" value="2" />
                <el-option label="老年人健康包" value="3" />
                <el-option label="孕产妇保健包" value="4" />
                <el-option label="儿童保健包" value="5" />
                <el-option
                  v-for="pkg in packageOptions"
                  :key="pkg.id"
                  :label="pkg.name"
                  :value="pkg.id"
                />
              </el-select>
            </el-form-item>
          </el-col>
@@ -51,7 +52,7 @@
        <el-card class="stats-card" shadow="hover">
          <div class="stats-content">
            <div class="stats-number">{{ stats.total }}</div>
            <div class="stats-label">总签约患者</div>
            <div class="stats-label">总签约儿童</div>
          </div>
        </el-card>
      </el-col>
@@ -91,12 +92,15 @@
        :default-sort="{prop: 'signDate', order: 'descending'}"
      >
        <el-table-column type="selection" width="55" />
        <el-table-column label="患者信息" min-width="180" fixed>
        <el-table-column label="儿童信息" min-width="180" fixed>
          <template slot-scope="scope">
            <div class="patient-info">
              <div class="patient-name">{{ scope.row.patientName }}</div>
              <div class="patient-detail">
                {{ scope.row.gender }} | {{ scope.row.age }}岁 | {{ scope.row.phone }}
                <div style="color: #909399; font-size: 12px; margin-top: 2px;">
                  {{ scope.row.applicableAge }}
                </div>
              </div>
            </div>
          </template>
@@ -190,118 +194,161 @@
      },
      total: 0,
      patientList: [],
      // æœåŠ¡å¥—é¤é…ç½®
      // å„¿ç«¥æœåŠ¡å¥—é¤é…ç½® [1,3,5](@ref)
      servicePackages: {
        '1': {
          name: '基础健康管理包',
          services: ['年度健康评估', '在线健康咨询', '健康档案管理', '定期健康提醒'],
          name: '婴幼儿健康基础包',
          description: '为0-3岁婴幼儿提供全面的健康监测与发育指导',
          services: ['新生儿家庭访视', '定期体格检查与发育评估', '血常规检测', '听力筛查', '喂养与护理指导', '预防接种服务', '中医保健指导'],
          price: 0,
          color: 'info'
          color: 'primary',
          applicableAge: '0-3岁'
        },
        '2': {
          name: '慢性病管理包',
          services: ['专属医生服务', '用药指导管理', '定期随访监测', '个性化康复计划', '紧急医疗咨询'],
          price: 299,
          color: 'success'
          name: '学龄前儿童健康包',
          description: '关注3-6岁儿童生长发育、常见病预防及习惯养成',
          services: ['生长发育评估', '视力筛查与口腔保健', '血常规检查', '合理膳食与行为指导', '疾病预防与健康干预', '中医饮食调养指导'],
          price: 0,
          color: 'success',
          applicableAge: '3-6岁'
        },
        '3': {
          name: '老年人健康包',
          services: ['跌倒风险评估', '康复训练指导', '用药安全管理', '定期上门访视', '紧急联系服务', '心理健康关怀'],
          price: 499,
          color: 'warning'
          name: '学龄儿童综合健康包',
          description: '为7-12岁儿童提供学习期健康保障与发展支持',
          services: ['年度健康检查', '心理行为发育评估', '科学用眼与口腔保健', '合理膳食指导', '健康生活方式干预', '专家转诊绿色通道'],
          price: 0,
          color: 'warning',
          applicableAge: '7-12岁'
        },
        '4': {
          name: '孕产妇保健包',
          services: ['孕期健康管理', '产后康复指导', '新生儿护理咨询', '营养膳食建议', '心理情绪支持'],
          price: 399,
          color: 'danger'
          name: '青少年健康支持包',
          description: '针对13-18岁青少年青春期特点的健康管理',
          services: ['青春期健康教育', '年度健康评估', '心理健康支持', '健康风险行为干预', '个性化健康方案', '优先预约检查服务'],
          price: 0,
          color: 'danger',
          applicableAge: '13-18岁'
        },
        '5': {
          name: '儿童保健包',
          services: ['生长发育监测', '疫苗接种管理', '常见病防治', '营养指导', '早期教育咨询'],
          price: 199,
          color: 'primary'
          name: '儿童营养与生长发育增值包',
          description: '针对肥胖、营养不良等问题的专项管理',
          services: ['微量元素测定', '骨密度检测', '个性化膳食方案', '生长发育专项评估', '运动处方指导', '定期营养监测'],
          price: 150,
          color: 'info',
          applicableAge: '3-18岁'
        },
        '6': {
          name: '儿童中医特色保健包',
          description: '运用中医药方法增强儿童体质',
          services: ['中医体质辨识', '三伏贴服务', '小儿推拿', '耳穴治疗', '防感香囊', '食疗指导'],
          price: 200,
          color: 'primary',
          applicableAge: '0-6岁'
        }
      }
      },
      packageOptions: []
    }
  },
  created() {
    this.initPackageOptions()
    this.getList()
    this.calculateStats()
  },
  methods: {
    // ç”Ÿæˆæ›´çœŸå®žçš„æ¨¡æ‹Ÿæ•°æ®
   // ç²¾ç®€åŽçš„æ¨¡æ‹Ÿæ•°æ®ç”Ÿæˆæ–¹æ³•
// ä¼˜åŒ–后的模拟数据生成方法
generateMockData() {
  const mockData = []
    // åˆå§‹åŒ–套餐选项
    initPackageOptions() {
      this.packageOptions = Object.keys(this.servicePackages).map(key => ({
        id: key,
        name: this.servicePackages[key].name
      }))
    },
  // ä½¿ç”¨æ‚¨æä¾›çš„真实姓名列表
  const patientNames = [
    '李肇芬', '卢木仲', '李成白', '方兆玉', '刘翊惠', '丁汉臻', '吴佳瑞', '舒绿珮',
    '周白芷', '张姿妤', '张虹伦', '周琼玟', '倪怡芳', '郭贵妃', '杨佩芳', '黄文旺',
    '黄盛玫', '郑丽青', '许智云', '张孟涵', '李小爱', '王恩龙', '朱政廷', '邓诗涵',
    '陈政倩', '吴俊伯', '阮馨学', '翁惠珠', '吴思翰', '林佩玲'
  ]
    // ç”Ÿæˆæ¨¡æ‹Ÿæ•°æ®
    generateMockData() {
      const mockData = []
  const doctors = ['王医生', '李医生', '张医生', '刘医生', '陈医生']
  const cities = ['北京市', '上海市', '广州市', '深圳市', '杭州市', '南京市', '成都市']
  const areas = ['朝阳区', '海淀区', '浦东新区', '黄浦区', '天河区', '福田区', '西湖区']
      // ä½¿ç”¨å„¿ç«¥å§“名列表
      const patientNames = [
        '李小宝', '张小明', '王雨欣', '刘浩然', '陈思琪', '杨宇航', '黄诗涵', '赵天宇',
        '周小萌', '吴俊杰', '郑雅雯', '孙沐辰', '朱雨萱', '马浩宇', '胡可馨', '林俊熙',
        '郭子轩', '何欣怡', '高天佑', '梁静怡', '罗浩然', '宋雨泽', '唐语嫣', '许博文',
        '谢欣妍', '冯子默', '董雨桐', '萧天乐', '曹心怡', '袁嘉豪'
      ]
  // ç”Ÿæˆçº¦20条数据
  for (let i = 0; i < patientNames.length; i++) {
    const packageId = (i % 5) + 1 + ''
    const packageInfo = this.servicePackages[packageId]
      const doctors = ['王医生', '李医生', '张医生', '刘医生', '陈医生']
      const cities = ['北京市', '上海市', '广州市', '深圳市', '杭州市', '南京市', '成都市']
      const areas = ['朝阳区', '海淀区', '浦东新区', '黄浦区', '天河区', '福田区', '西湖区']
    // ç”Ÿæˆæ›´åˆç†çš„签约时间(过去1年内)
    const signDate = this.generateRandomDate('2023-12-01', '2024-11-30')
    const contractPeriod = [1, 2][i % 2] // 1年或2年合同
    const expireDate = this.addYears(signDate, contractPeriod)
    const remainingDays = this.calculateRemainingDays(expireDate)
    const contractStatus = this.getContractStatus(expireDate, remainingDays)
      for (let i = 0; i < patientNames.length; i++) {
        const packageId = (i % 6) + 1 + ''
        const packageInfo = this.servicePackages[packageId]
    // ç”Ÿæˆæ›´çœŸå®žçš„电话号码和身份证号
    const phonePrefix = ['138', '139', '150', '151', '152', '186', '187', '188']
    const phone = `${phonePrefix[i % phonePrefix.length]}${this.padNumber(1000 + i * 37, 4)}${this.padNumber(i % 100, 2)}`
        // æ ¹æ®å¥—餐适用年龄生成合理的实际年龄
        let age
        switch(packageInfo.applicableAge) {
          case '0-3岁':
            age = Math.floor(Math.random() * 3) + 1
            break
          case '3-6岁':
            age = Math.floor(Math.random() * 3) + 3
            break
          case '7-12岁':
            age = Math.floor(Math.random() * 6) + 7
            break
          case '13-18岁':
            age = Math.floor(Math.random() * 6) + 13
            break
          default:
            age = Math.floor(Math.random() * 18) + 1
        }
    // ç”Ÿæˆåˆç†çš„年龄(20-80岁)
    const age = 20 + (i % 60)
    const birthYear = new Date().getFullYear() - age
    const idCard = `11010${birthYear}${this.padNumber(1 + (i % 12), 2)}${this.padNumber(1 + (i % 28), 2)}${this.padNumber(i % 1000, 3)}X`
        // ç”Ÿæˆç­¾çº¦æ—¶é—´ï¼ˆè¿‡åŽ»1年内)
        const signDate = this.generateRandomDate('2023-12-01', '2024-11-30')
        const contractPeriod = [1, 2][i % 2] // 1年或2年合同
        const expireDate = this.addYears(signDate, contractPeriod)
        const remainingDays = this.calculateRemainingDays(expireDate)
        const contractStatus = this.getContractStatus(expireDate, remainingDays)
    mockData.push({
      id: `P${2024000 + i}`,
      patientName: patientNames[i],
      gender: i % 2 === 0 ? '男' : '女',
      age: age,
      phone: phone,
      idCard: idCard,
      doctorName: doctors[i % doctors.length],
      servicePackageId: packageId,
      servicePackage: packageInfo.name,
      services: packageInfo.services,
      contractPeriod: contractPeriod,
      signDate: signDate,
      expireDate: expireDate,
      remainingDays: remainingDays,
      contractStatus: contractStatus,
      address: `${cities[i % cities.length]}${areas[i % areas.length]}${this.generateStreet(i)}` // æ–°å¢žåœ°å€å­—段
    })
  }
        // ç”Ÿæˆç”µè¯å·ç ï¼ˆä½¿ç”¨å®¶é•¿ç”µè¯ï¼‰
        const phonePrefix = ['138', '139', '150', '151', '152', '186', '187', '188']
        const phone = `${phonePrefix[i % phonePrefix.length]}${this.padNumber(1000 + i * 37, 4)}${this.padNumber(i % 100, 2)}`
  return mockData
},
        // ç”Ÿæˆå„¿ç«¥èº«ä»½è¯å·
        const birthYear = new Date().getFullYear() - age
        const idCard = `11010${birthYear}${this.padNumber(1 + (i % 12), 2)}${this.padNumber(1 + (i % 28), 2)}${this.padNumber(i % 1000, 3)}X`
// æ–°å¢žè¾…助方法:生成街道地址
generateStreet(index) {
  const streets = [
    '中山路123号', '人民路456号', '解放路789号', '建设路101号', '和平路202号',
    '新华路303号', '光明路404号', '幸福路505号', '团结路606号', '文明路707号'
  ]
  return streets[index % streets.length]
},
        mockData.push({
          id: `C${2024000 + i}`,
          patientName: patientNames[i],
          gender: i % 2 === 0 ? '男' : '女',
          age: age,
          phone: phone,
          idCard: idCard,
          doctorName: doctors[i % doctors.length],
          servicePackageId: packageId,
          servicePackage: packageInfo.name,
          services: packageInfo.services,
          contractPeriod: contractPeriod,
          signDate: signDate,
          expireDate: expireDate,
          remainingDays: remainingDays,
          contractStatus: contractStatus,
          applicableAge: packageInfo.applicableAge,
          address: `${cities[i % cities.length]}${areas[i % areas.length]}${this.generateStreet(i)}`
        })
      }
    // è¾…助方法
      return mockData
    },
    // è¾…助方法保持不变
    generateStreet(index) {
      const streets = [
        '中山路123号', '人民路456号', '解放路789号', '建设路101号', '和平路202号',
        '新华路303号', '光明路404号', '幸福路505号', '团结路606号', '文明路707号'
      ]
      return streets[index % streets.length]
    },
    generateRandomDate(start, end) {
      const startDate = new Date(start).getTime()
      const endDate = new Date(end).getTime()
@@ -336,11 +383,10 @@
    async getList() {
      this.loading = true
      try {
        // æ¨¡æ‹ŸAPI调用延迟
        await new Promise(resolve => setTimeout(resolve, 500))
        const allData = this.generateMockData()
        // ç®€å•的本地筛选
        // ç­›é€‰é€»è¾‘
        let filteredData = allData.filter(item => {
          if (this.queryParams.patientName &&
              !item.patientName.includes(this.queryParams.patientName)) {
@@ -393,7 +439,10 @@
    },
    getPackageType(packageId) {
      const typeMap = { '1': 'info', '2': 'success', '3': 'warning', '4': 'danger', '5': 'primary' }
      const typeMap = {
        '1': 'primary', '2': 'success', '3': 'warning',
        '4': 'danger', '5': 'info', '6': 'primary'
      }
      return typeMap[packageId] || 'info'
    },
@@ -425,13 +474,12 @@
    },
    handleView(row) {
      this.$message.info(`查看患者 ${row.patientName} çš„详情`)
      this.$message.info(`查看儿童 ${row.patientName} çš„详情`)
      // å®žé™…开发中跳转到详情页
      // this.$router.push({ path: '/patient/contract/detail', query: { id: row.id } })
    },
    handleRenew(row) {
      this.$confirm(`确定要为患者 ${row.patientName} åŠžç†ç»­çº¦å—ï¼Ÿ`, '提示', {
      this.$confirm(`确定要为儿童 ${row.patientName} åŠžç†ç»­çº¦å—ï¼Ÿ`, '提示', {
        type: 'warning'
      }).then(() => {
        this.$message.success('续约操作成功')
@@ -458,6 +506,10 @@
  .search-card {
    margin-bottom: 20px;
    ::v-deep .el-form-item {
      margin-bottom: 0;
    }
  }
  .stats-row {
@@ -487,11 +539,13 @@
    .patient-name {
      font-weight: 600;
      margin-bottom: 4px;
      color: #2c3e50;
    }
    .patient-detail {
      font-size: 12px;
      color: #666;
      line-height: 1.4;
    }
  }
@@ -509,4 +563,23 @@
    color: #67C23A;
  }
}
// å“åº”式设计
@media (max-width: 768px) {
  .signed-patient-page {
    padding: 10px;
    .search-card {
      ::v-deep .el-col {
        margin-bottom: 10px;
      }
    }
    .stats-row {
      .el-col {
        margin-bottom: 10px;
      }
    }
  }
}
</style>
src/views/patient/patient/index.vue
@@ -765,8 +765,8 @@
          v-model="tempContractData.servicePackage"
          style="width: 100%"
        >
          <el-row :gutter="16">
            <el-col :span="8" v-for="pkg in servicePackages" :key="pkg.id">
          <el-row :gutter="10">
            <el-col :span="7" v-for="pkg in servicePackages" :key="pkg.id">
              <el-card
                :class="[
                  'package-card',
@@ -978,25 +978,81 @@
      servicePackages: [
        {
          id: 1,
          name: "基础健康管理包",
          description: "包含定期健康评估、基本咨询",
          name: "婴幼儿健康基础包",
          description: "为0-3岁婴幼儿提供全面的健康监测与发育指导",
          price: 0,
          features: ["年度健康评估", "在线咨询"],
          features: [
            "新生儿家庭访视",
            "定期体格检查与发育评估",
            "血常规检测",
            "听力筛查",
            "喂养与护理指导",
            "预防接种服务",
            "中医保健指导(如摩腹、捏脊)",
          ],
          applicableAge: "0-3岁",
        },
        {
          id: 2,
          name: "慢性病管理包",
          description: "专为慢性病患者设计",
          price: 299,
          features: ["专属医生", "用药提醒", "定期随访"],
          name: "学龄前儿童健康包",
          description: "关注3-6岁儿童生长发育、常见病预防及习惯养成",
          price: 0,
          features: [
            "生长发育评估",
            "视力筛查与口腔保健",
            "血常规检查",
            "合理膳食与行为指导",
            "疾病预防与健康干预",
            "中医饮食调养指导",
          ],
          applicableAge: "3-6岁",
        },
        {
          id: 3,
          name: "老年人健康包",
          description: "关注老年人健康问题",
          price: 499,
          features: ["跌倒风险评估", "康复指导", "紧急联系"],
          name: "学龄儿童综合健康包",
          description: "为7-12岁儿童提供学习期健康保障与发展支持",
          price: 0,
          features: [
            "年度健康检查",
            "心理行为发育评估",
            "科学用眼与口腔保健",
            "合理膳食指导",
            "健康生活方式干预",
            "专家转诊绿色通道",
          ],
          applicableAge: "7-12岁",
        },
        {
          id: 4,
          name: "青少年健康支持包",
          description: "针对13-18岁青少年青春期特点的健康管理",
          price: 0,
          features: [
            "青春期健康教育",
            "年度健康评估",
            "心理健康支持",
            "健康风险行为干预",
            "个性化健康方案",
            "优先预约检查服务",
          ],
          applicableAge: "13-18岁",
        },
        {
          id: 5,
          name: "儿童营养与生长发育增值包",
          description: "针对肥胖、营养不良等问题的专项管理",
          price: 150,
          features: [
            "微量元素测定",
            "骨密度检测",
            "个性化膳食方案",
            "生长发育专项评估",
            "运动处方指导",
            "定期营养监测",
          ],
          applicableAge: "3-18岁",
        },
      ],
      taskoptions: [
        // {
@@ -1588,7 +1644,7 @@
  }
}
.package-card.active {
  border-color: #409EFF;
  border-color: #409eff;
  background-color: #f0f9ff;
}
src/views/patient/patient/profile/index.vue
@@ -64,36 +64,13 @@
        >
        <el-tabs v-model="sontwoactiveName">
          <!-- <el-tab-pane name="blood"
            ><span class="mulsz" slot="label"
              ><i class="el-icon-s-operation"></i> è¡€åŽ‹</span
            ></el-tab-pane
          >
          <el-tab-pane name="glucose"
            ><span class="mulsz" slot="label"
              ><i class="el-icon-odometer"></i>血糖
            </span></el-tab-pane
          > -->
          <el-tab-pane name="weight"
            ><span class="mulsz" slot="label"
              ><i class="el-icon-s-data"></i>身长头围趋势
            </span></el-tab-pane
          >
          <!-- <el-tab-pane name="heartrate"
            ><span class="mulsz" slot="label"
              ><i class="el-icon-s-operation"></i>心率
            </span></el-tab-pane
          >
          <el-tab-pane name="bloodoxygen"
            ><span class="mulsz" slot="label"
              ><i class="el-icon-s-data"></i>血氧
            </span></el-tab-pane
          >
          <el-tab-pane name="animalheat"
            ><span class="mulsz" slot="label"
              ><i class="el-icon-s-opportunity"></i>体温
            </span></el-tab-pane
          > -->
        </el-tabs>
      </el-tab-pane>
      <el-tab-pane name="serve">
@@ -1020,40 +997,7 @@
    </div>
    <!-- å¥åº·ç›‘测 -->
    <div class="medical-record" v-show="activeName == 'monitor'">
      <!-- <div v-show="sontwoactiveName == 'blood'" style="display: flex">
        <div
          id="xyeCharts"
          class="sontwoactiveName"
          style="width: 880px; height: 560px"
        ></div>
        <div style="width: 400px">
          <el-card class="box-card">
            <SFtable
              :currentList="tableDatalist"
              :tableLabel="tableLabelxy"
              :center="false"
              :multiplechoice="false"
            />
          </el-card>
        </div>
      </div> -->
      <!-- <div v-show="sontwoactiveName == 'glucose'" style="display: flex">
        <div
          id="xteCharts"
          class="sontwoactiveName"
          style="width: 880px; height: 560px"
        ></div>
        <div style="width: 400px">
          <el-card class="box-card">
            <SFtable
              :currentList="tableDatalist"
              :tableLabel="tableLabelxt"
              :center="false"
              :multiplechoice="false"
            />
          </el-card>
        </div>
      </div> -->
      <div v-show="sontwoactiveName == 'weight'" style="display: flex">
        <div
          id="tzeCharts"
@@ -1075,14 +1019,7 @@
                  <span v-if="!scope.row.editing">{{
                    scope.row.generatedTime
                  }}</span>
                  <!-- <el-date-picker
                    v-else
                    v-model="scope.row.generatedTime"
                    type="date"
                    placeholder="选择日期"
                    value-format="yyyy-MM-dd"
                    size="small"
                  ></el-date-picker> -->
                  <el-date-picker
                    v-else
                    v-model="scope.row.generatedTime"
@@ -1181,57 +1118,7 @@
          </el-card>
        </div>
      </div>
      <!-- <div v-show="sontwoactiveName == 'heartrate'" style="display: flex">
        <div
          id="xleCharts"
          class="sontwoactiveName"
          style="width: 880px; height: 560px"
        ></div>
        <div style="width: 400px">
          <el-card class="box-card">
            <SFtable
              :currentList="tableDatalist"
              :tableLabel="tableLabelxl"
              :center="false"
              :multiplechoice="false"
            />
          </el-card>
        </div>
      </div> -->
      <!-- <div v-show="sontwoactiveName == 'bloodoxygen'" style="display: flex">
        <div
          id="xueyangeCharts"
          class="sontwoactiveName"
          style="width: 880px; height: 560px"
        ></div>
        <div style="width: 400px">
          <el-card class="box-card">
            <SFtable
              :currentList="tableDatalist"
              :tableLabel="tableLabelxueyang"
              :center="false"
              :multiplechoice="false"
            />
          </el-card>
        </div>
      </div> -->
      <!-- <div v-show="sontwoactiveName == 'animalheat'" style="display: flex">
        <div
          id="tweCharts"
          class="sontwoactiveName"
          style="width: 880px; height: 560px"
        ></div>
        <div style="width: 400px">
          <el-card class="box-card">
            <SFtable
              :currentList="tableDatalist"
              :tableLabel="tableLabeltw"
              :center="false"
              :multiplechoice="false"
            />
          </el-card>
        </div>
      </div> -->
    </div>
    <!-- ç­›æŸ¥æ‘˜è¦ç¼–辑对话框 -->
    <el-dialog
@@ -1426,12 +1313,7 @@
          </el-input>
        </el-form-item>
        <el-form-item label="记录日期" prop="generatedTime">
          <!-- <el-date-picker
            v-model="borninfoform.generatedTime"
            type="date"
            placeholder="选择日期"
          >
          </el-date-picker> -->
          <el-date-picker
            v-model="borninfoform.generatedTime"
            value-format="yyyy-MM-dd HH:mm:ss"
@@ -1587,32 +1469,7 @@
        ],
      },
      record: [
        {
          name: "宣教任务",
          serviceType: "4",
          templatename: "管饲宣教一期",
          createTime: "2024-11-10",
          finishtime: "2024-11-12",
          createBy: "王政",
          drname: "刘明",
          nurseName: "张淑琴",
          excep: "0",
          deptname: "呼吸科",
          leavehospitaldistrictname: "五病区",
        },
        {
          name: "随访任务",
          serviceType: "2",
          templatename: "心血管随访一期",
          createTime: "2024-11-11",
          finishtime: "2024-11-14",
          createBy: "章程",
          drname: "刘明",
          nurseName: "李丽",
          excep: "0",
          deptname: "呼吸科",
          leavehospitaldistrictname: "五病区",
        },
        // {
        //   name: "问卷调查",
        // },
vue.config.js
@@ -38,8 +38,8 @@
        // target: `http://192.168.168.60:8095`,
        // target: `http://192.168.144.34:8095`,
        // target: `http://220.66.136.101:8095`,
        target: `http://192.168.100.10:8099`,
        // target:`http://localhost:8095`,
        // target: `http://192.168.100.10:8099`,
        target:`http://localhost:8095`,
        changeOrigin: true,
        pathRewrite: {
          ['^' + process.env.VUE_APP_BASE_API]: ''
wuxi.zip
Binary files differ