WXL (wul)
17 小时以前 a9d2b856e0f6be6475319c2dc36bf405c2f908fc
测试完成
已删除4个文件
已重命名1个文件
已修改35个文件
已添加9个文件
5926 ■■■■ 文件已修改
src/api/AiCentre/satisfaction.js 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Satisfaction/sfstatistics/components/ChartsPanel.vue 620 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Satisfaction/sfstatistics/components/DataOverview.vue 1170 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Satisfaction/sfstatistics/components/FollowupStatistics.vue 36 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/Satisfaction/sfstatistics/components/SatisfactionStatistics.vue 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/Continue/index.vue 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/HistoricalFollow/index.vue 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/OutpatientAgain/index.vue 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/Tracking/index.vue 37 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/again/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/beHospitalized/followUp.vue 33 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/complaint/index.vue 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/discharge/index.vue 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/discharge/outpatientService.vue 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/mzsatisfaction/index.vue 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/operation/index.vue 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/record/detailpage/index.vue 104 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/record/index.vue 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/technology/index.vue 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/zbAgain/index.vue 35 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/followvisit/zysatisfaction/index.vue 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/login.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/loginSSO.vue 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/archives/childHealth/detail.vue 153 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/archives/components/ArchiveDialog.vue 276 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/archives/components/ArchiveList.vue 148 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/archives/components/OutpatientPicker.vue 145 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/archives/components/ServiceRecords.vue 344 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/archives/highRisk/index.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/archives/learning/index.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/archives/maternalHealth/detail.vue 726 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/archives/menopause/index.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/archives/nutrition/index.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/archives/pregnancy/index.vue 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/archives/types.js 187 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/patient/behospitalized.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/patient/patient/profile/index.vue 723 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/repositoryai/intention/index.vue 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sfstatistics/percentage/components/ChartDialog.vue 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sfstatistics/percentage/components/CombinedFollowUp.vue 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sfstatistics/percentage/components/FirstFollowUp.vue 296 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sfstatistics/percentage/components/SecondFollowUp.vue 287 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/sfstatistics/percentage/index.vue 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vue.config.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
随访外链通用9.12.zip 补丁 | 查看 | 原始文档 | blame | 历史
随访通用.zip 补丁 | 查看 | 原始文档 | blame | 历史
随访通用9.10.zip 补丁 | 查看 | 原始文档 | blame | 历史
随访通用9.11.zip 补丁 | 查看 | 原始文档 | blame | 历史
随访通用9.22.zip 补丁 | 查看 | 原始文档 | blame | 历史
src/api/AiCentre/satisfaction.js
@@ -8,8 +8,46 @@
    data: data
  });
}
// å„项服务满意度数据统计
export function sltdMydTotalByScore(data) {
  return request({
    url: "/smartor/serviceSubtaskDetail/sltdMydTotalByScore",
    method: "post",
    data: data
  });
}
// å„项服务满意度数据统计
export function mzMydScoreRank(data) {
  return request({
    url: "/smartor/serviceSubtaskDetail/mzMydScoreRank",
    method: "post",
    data: data
  });
}
// å„科室/病区满意度评分对比
export function statQuestionOptionByKsOrBq(data) {
  return request({
    url: "/smartor/serviceSubtaskDetail/statQuestionOptionByKsOrBq",
    method: "post",
    data: data
  });
}
// è¯„分趋势
export function mydTrend(data) {
  return request({
    url: "/smartor/serviceSubtaskDetail/mydTrend",
    method: "post",
    data: data
  });
}
// çœç«‹åŒå¾·æ‚£è€…满意度维护(按维度/院区统计)
export function sltdMydTotalByDimension(data) {
  return request({
    url: "/smartor/serviceSubtaskDetail/sltdMydTotalByDimension",
    method: "post",
    data: data
  });
}
// ç¼“存问卷
export function saveMYDQuestionAnswer(data) {
src/views/Satisfaction/sfstatistics/components/ChartsPanel.vue
@@ -17,8 +17,14 @@
        <span class="filter-label">区间类型:</span>
        <el-radio-group v-model="queryParams.interval" size="small" @change="refreshAll">
          <el-radio-button label="day">按天</el-radio-button>
          <el-radio-button label="week">按周</el-radio-button>
          <el-radio-button label="month">按月</el-radio-button>
          <el-radio-button label="year">按年</el-radio-button>
        </el-radio-group>
        <span class="filter-label">统计维度:</span>
        <el-radio-group v-model="queryParams.dimension" size="small" @change="onDimensionChange">
          <el-radio-button label="ward">按病区统计</el-radio-button>
          <el-radio-button label="dept">按科室统计</el-radio-button>
        </el-radio-group>
        <el-button type="primary" size="small" icon="el-icon-search" @click="refreshAll">查询</el-button>
        <el-button size="small" icon="el-icon-refresh" @click="handleReset">重置</el-button>
@@ -31,10 +37,13 @@
      <el-col :span="12">
        <el-card shadow="never" class="chart-card">
          <div slot="header" class="card-header">
            <span>时间维度 Â· é—¨è¯Šæ‚£è€…满意度趋势</span>
            <span>患者满意度综合得分趋势</span>
            <el-tag type="success" size="small">环比 {{ trendMoM }}</el-tag>
          </div>
          <div ref="chart1" class="chart-box"></div>
          <div class="chart-wrap" v-loading="trendLoading" element-loading-text="趋势加载中...">
            <div ref="chart1" class="chart-box"></div>
            <div v-if="!trendLoading && !trend.buckets.length" class="chart-empty">暂无趋势数据</div>
          </div>
        </el-card>
      </el-col>
@@ -44,7 +53,10 @@
          <div slot="header" class="card-header">
            <span>分类维度 Â· å„指标{{ intervalLabel }}对比</span>
          </div>
          <div ref="chart2" class="chart-box"></div>
          <div class="chart-wrap" v-loading="categoryLoading" element-loading-text="分类维度加载中...">
            <div ref="chart2" class="chart-box"></div>
            <div v-if="!categoryLoading && !categoryCompare.categories.length" class="chart-empty">暂无分类维度数据</div>
          </div>
        </el-card>
      </el-col>
    </el-row>
@@ -56,25 +68,26 @@
          <div slot="header" class="card-header">
            <span>单题 Â· é€‰é¡¹ç»Ÿè®¡</span>
            <div class="header-right">
              <el-select v-model="singleQuestionId" size="small" class="sel sel-lg" @change="refreshAll">
                <el-option v-for="q in questionOptions" :key="q.id" :label="q.title" :value="q.id" />
              <el-select v-model="singleQuestionId" size="small" class="sel sel-lg" filterable remote clearable
                :remote-method="searchQuestions" :loading="questionLoading" placeholder="请输入问题内容搜索" @change="onSingleQuestionChange">
                <el-option v-for="q in questionOptions" :key="q.id" :label="q.scriptContent || q.title" :value="q.id" />
              </el-select>
              <el-select v-model="singleDeptWards" multiple collapse-tags size="small" class="sel" clearable filterable placeholder="全部科室/病区" @change="refreshAll">
                <el-option-group label="科室">
              <el-select v-model="singleDeptWards" multiple collapse-tags size="small" class="sel" clearable filterable :placeholder="queryParams.dimension === 'dept' ? '全部科室' : '全部病区'" @change="refreshAll">
                <template v-if="queryParams.dimension === 'dept'">
                  <el-option v-for="d in deptOptions" :key="'dept:' + d" :label="d" :value="'dept:' + d" />
                </el-option-group>
                <el-option-group label="病区">
                </template>
                <template v-else>
                  <el-option v-for="w in wardOptions" :key="'ward:' + w" :label="w" :value="'ward:' + w" />
                </el-option-group>
                </template>
              </el-select>
            </div>
          </div>
          <el-table :data="singleQuestionData" border size="small">
          <el-table :data="singleQuestionData" border size="small" v-loading="singleLoading" element-loading-text="选项统计加载中...">
            <el-table-column prop="option" label="选项" />
            <el-table-column prop="count" label="合计数量" width="120" align="center" />
            <el-table-column prop="rate" label="占比比例" width="120" align="center" />
          </el-table>
          <div class="table-footer">本题有效填写人次:<b>{{ singleQuestionMeta.total }}</b> 综合满意度:<b>{{ singleQuestionMeta.score }}</b></div>
          <div class="table-footer">本题有效填写人次:<b>{{ singleQuestionMeta.total }}</b> 综合得分:<b>{{ singleQuestionMeta.score }}</b></div>
        </el-card>
      </el-col>
@@ -84,7 +97,10 @@
          <div slot="header" class="card-header">
            <span>院区维度 Â· å„院区{{ intervalLabel }}对比</span>
          </div>
          <div ref="chart4" class="chart-box"></div>
          <div class="chart-wrap" v-loading="hospitalLoading" element-loading-text="院区维度加载中...">
            <div ref="chart4" class="chart-box"></div>
            <div v-if="!hospitalLoading && !hospitalCompare.hospitals.length" class="chart-empty">暂无院区维度数据</div>
          </div>
        </el-card>
      </el-col>
    </el-row>
@@ -105,10 +121,11 @@
              </el-select>
            </div>
          </div>
          <el-table :data="rankRows" border size="small" max-height="400">
            <el-table-column type="index" label="排名" width="60" align="center" />
          <el-table :data="rankRows" border size="small" max-height="400" v-loading="rankLoading" element-loading-text="排名加载中...">
            <el-table-column prop="rank" label="排名" width="60" align="center" />
            <el-table-column prop="name" :label="rankDimLabel" />
            <el-table-column prop="score" label="综合得分" width="120" align="center" />
            <el-table-column prop="itemCount" label="项数" width="100" align="center" />
          </el-table>
        </el-card>
      </el-col>
@@ -128,10 +145,11 @@
              </el-select>
            </div>
          </div>
          <el-table :data="crossRows" border size="small" max-height="400">
            <el-table-column type="index" label="排名" width="60" align="center" />
          <el-table :data="crossRows" border size="small" max-height="400" v-loading="crossLoading" element-loading-text="排名加载中...">
            <el-table-column prop="rank" label="排名" width="60" align="center" />
            <el-table-column prop="name" :label="crossDimLabel" />
            <el-table-column prop="score" label="综合得分" width="120" align="center" />
            <el-table-column prop="itemCount" label="项数" width="100" align="center" />
          </el-table>
        </el-card>
      </el-col>
@@ -141,54 +159,29 @@
<script>
import * as echarts from 'echarts';
import { mydTrend, sltdMydTotalByDimension, sltdMydTotalByScore ,mzMydScoreRank} from "@/api/AiCentre/satisfaction";
import { getissuelist } from "@/api/AiCentre/questionnaire";
// å•题选项
const QUESTION_OPTIONS = ['非常满意', '满意', '一般', '不满意'];
// å¯é€‰é¢˜ç›®
const QUESTIONS = [
  { id: 22, title: '第22题:抱怨和不满能否及时得到回应?' },
  { id: 1, title: '第1题:您对我院工作人员的服务质量满意不满意?' },
  { id: 5, title: '第5题:您对医院服务设施的总体印象如何?' },
  { id: 8, title: '第8题:医生对您是否尊重?' },
  { id: 12, title: '第12题:护士对您是否尊重?' },
  { id: 15, title: '第15题:您对预约挂号是否满意?' },
];
const DEPTS = ['保健科', '介入治疗科', '专科护士门诊', '康复医学科', '皮肤科', '内分泌科', '消化内科', '肾内科', '呼吸内科', '心血管内科', '神经内科', '血液科', '风湿免疫科', '肿瘤内科', '感染科', '老年科', '普外科', '骨科', '泌尿外科', '妇产科'];
const WARDS = ['一病区', '二病区', '三病区', '四病区', '五病区', '六病区', '七病区', '八病区'];
// ç¡®å®šæ€§ä¼ªéšæœº
function hashString(str) {
  let h = 2166136261;
  for (let i = 0; i < str.length; i++) {
    h ^= str.charCodeAt(i);
    h = Math.imul(h, 16777619);
  }
  return h >>> 0;
}
function mulberry32(a) {
  return function () {
    a |= 0;
    a = (a + 0x6d2b79f5) | 0;
    let t = Math.imul(a ^ (a >>> 15), 1 | a);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}
export default {
  name: 'ChartsPanel',
  dicts: ['dimensionality_type'],
  data() {
    return {
      queryParams: {
        dateRange: [],
        interval: 'month',
        dimension: 'ward', // ç»Ÿè®¡ç»´åº¦ï¼šward=按病区统计 dept=按科室统计
      },
      deptOptions: DEPTS,
      wardOptions: WARDS,
      questionOptions: QUESTIONS,
      questionOptions: [],
      // å•题筛选
      singleQuestionId: 22,
      singleQuestionId: null,
      singleQuestionContent: '',
      singleDeptWards: [],
      // æŽ’名维度筛选
@@ -198,22 +191,44 @@
      crossDeptWards: [],
      // å›¾è¡¨æ•°æ®
      trendLoading: false,
      categoryLoading: false,
      hospitalLoading: false,
      trend: { buckets: [], data: [], seriesName: '门诊患者满意度' },
      categoryCompare: { categories: [], seriesNames: [], series1: [], series2: [] },
      hospitalCompare: { hospitals: [], seriesNames: [], series1: [], series2: [] },
      // å•题 / æŽ’名 / äº¤å‰è¡¨åŠ è½½çŠ¶æ€
      singleLoading: false,
      questionLoading: false,
      rankLoading: false,
      crossLoading: false,
      // è¡¨æ ¼æ•°æ®
      singleQuestionData: [],
      singleQuestionMeta: { total: '0', score: '0.00%' },
      deptRankData: [],
      wardRankData: [],
      crossDeptData: [],
      crossWardData: [],
      singleQuestionMeta: { total: '0', score: '0.00' },
      rankData: [],
      crossData: [],
    };
  },
  computed: {
    // ç§‘室/病区编码:优先取当前用户所属,无则兜底名称本身作为编码
    deptCodeList() {
      const list = this.$store.getters.belongDepts || [];
      return list.length ? list : DEPTS.map((d) => ({ deptName: d, deptCode: d }));
    },
    wardCodeList() {
      const list = this.$store.getters.belongWards || [];
      return list.length ? list : WARDS.map((w) => ({ districtName: w, districtCode: w }));
    },
    defaultDeptCodes() {
      return Array.from(new Set(this.deptCodeList.map((d) => d.deptCode)));
    },
    defaultWardCodes() {
      return Array.from(new Set(this.wardCodeList.map((w) => w.districtCode)));
    },
    intervalLabel() {
      return { day: '按天', month: '按月', year: '按年' }[this.queryParams.interval] || '按月';
      return { day: '按天', week: '按周', month: '按月', year: '按年' }[this.queryParams.interval] || '按月';
    },
    trendMoM() {
      const d = this.trend.data;
@@ -237,14 +252,10 @@
      return this.crossDimension === 'dept' ? this.deptOptions : this.wardOptions;
    },
    rankRows() {
      const list = this.rankDimension === 'dept' ? this.deptRankData : this.wardRankData;
      if (!this.rankDeptWards.length) return list;
      return list.filter((r) => this.rankDeptWards.includes(r.name));
      return this.rankData;
    },
    crossRows() {
      const list = this.crossDimension === 'dept' ? this.crossDeptData : this.crossWardData;
      if (!this.crossDeptWards.length) return list;
      return list.filter((r) => this.crossDeptWards.includes(r.name));
      return this.crossData;
    },
  },
  mounted() {
@@ -253,7 +264,26 @@
        this.queryParams.dateRange = this.defaultRange();
      }
      this.initCharts();
      // åˆå§‹åŒ–即显示各数据块的加载状态,避免先闪现“暂无数据”
      this.trendLoading = true;
      this.categoryLoading = true;
      this.hospitalLoading = true;
      this.singleLoading = true;
      this.rankLoading = true;
      this.crossLoading = true;
      // æ•°æ®å—与题目列表并行加载:数据块不依赖题目列表,无需串行等待
      this.refreshAll();
      this.loadQuestions('').then(() => {
        if (this.singleQuestionContent) {
          // å•题选项统计 / å•题交叉排名依赖问题文本,题目加载完成后补拉一次
          this.fetchSingleQuestion();
          this.fetchCross();
        } else {
          // é¢˜ç›®åˆ—表为空或加载失败,结束单题相关加载状态
          this.singleLoading = false;
          this.crossLoading = false;
        }
      });
    });
  },
  methods: {
@@ -269,9 +299,163 @@
        if (c) c.resize();
      });
    },
    // ç»´åº¦ç¼–码 â†’ å­—典名称(dimensionality_type)
    getDimensionLabel(code) {
      const dict = this.dict && this.dict.type && this.dict.type.dimensionality_type;
      if (dict) {
        const hit = dict.find((item) => String(item.value) === String(code));
        if (hit) return hit.label;
      }
      return code == null ? '' : String(code);
    },
    // å¯é€‰é¢˜ç›®ï¼šè¿œç¨‹æ¨¡ç³Šæœç´¢ï¼ˆæŒ‰é—®é¢˜å†…容 scriptContent)
    searchQuestions(keyword) {
      this.loadQuestions(keyword || '');
    },
    // åŠ è½½å¯é€‰é¢˜ç›®ï¼ˆå‚è€ƒé—®é¢˜åˆ—è¡¨é¡µ getissuelist)
    async loadQuestions(keyword) {
      this.questionLoading = true;
      try {
        const res = await getissuelist({
          pageNum: 1,
          pageSize: 50,
          isavailable: '',
          scriptContent: keyword || '',
        });
        const rows = (res && res.rows) || [];
        this.questionOptions = rows.map((q) => ({
          id: q.id,
          scriptContent: q.scriptContent || q.title || '',
        }));
        // æ— å…³é”®å­—时默认选中第一题
        if (!keyword && this.questionOptions.length && !this.singleQuestionContent) {
          const first = this.questionOptions[0];
          this.singleQuestionId = first.id;
          this.singleQuestionContent = first.scriptContent;
        }
      } catch (e) {
        this.questionOptions = [];
      } finally {
        this.questionLoading = false;
      }
    },
    onSingleQuestionChange(val) {
      const q = this.questionOptions.find((o) => String(o.id) === String(val));
      this.singleQuestionContent = q ? q.scriptContent : '';
      this.refreshAll();
    },
    // ç§‘室/病区名称 â†’ ç¼–码
    mapDeptNames(names) {
      return names.map((n) => {
        const hit = this.deptCodeList.find((d) => d.deptName === n);
        return hit ? hit.deptCode : n;
      });
    },
    mapWardNames(names) {
      return names.map((n) => {
        const hit = this.wardCodeList.find((w) => w.districtName === n);
        return hit ? hit.districtCode : n;
      });
    },
    // ç»¼åˆå¾—分(真实口径):Σ(选项分值 Ã— å¡«æŠ¥é‡) / Î£(填报量)
    scoreOfOptions(options) {
      let num = 0;
      let den = 0;
      (options || []).forEach((o) => {
        const c = Number(o && o.count) || 0;
        const s = Number(o && o.score);
        if (isNaN(s)) return;
        num += s * c;
        den += c;
      });
      if (!den) return '0.00';
      return (num / den).toFixed(2);
    },
    // è§£æžæ¯”例字符串:"14.29%"、"0"、"0.5" â†’ æ•°å€¼ï¼ˆç™¾åˆ†æ¯”);无效返回 null
    parsePercent(value) {
      if (value == null || value === '') return null;
      const n = Number(String(value).replace(/%/g, '').trim());
      return isNaN(n) ? null : n;
    },
    // å•题返回 List<QuestionResultDTO> â†’ { options, total }
    normalizeSingleQuestion(list) {
      const arr = Array.isArray(list) ? list : (list && (list.list || list.rows)) || [];
      const options = [];
      let total = 0;
      arr.forEach((item) => {
        const details = Array.isArray(item && item.subtaskDetailRatioExportList)
          ? item.subtaskDetailRatioExportList
          : [];
        details.forEach((d) => {
          const option = (d && (d.optionresult || d.targetvalue || d.optioncontent || d.questiontext)) || '';
          const count = Number(d && d.count) || 0;
          const rawScore = Number(d && d.score);
          const ratio = this.parsePercent(d && d.ratio);
          options.push({ option, count, score: isNaN(rawScore) ? null : rawScore, ratio });
          total += count;
        });
      });
      return { options, total };
    },
    // å•题选项统计(真实接口 /smartor/serviceSubtaskDetail/sltdMydTotalByScore)
    async fetchSingleQuestion() {
      const range = this.queryParams.dateRange || [];
      if (range.length !== 2 || !range[0] || !range[1] || !this.singleQuestionContent) {
        this.singleQuestionData = [];
        this.singleQuestionMeta = { total: '0', score: '0.00' };
        return;
      }
      this.singleLoading = true;
      try {
        const payload = {
          deptOrDistrict: '1',
          serviceTypeList: ['6', '14'],
          startFinishTime: range[0],
          endFinishTime: range[1],
          questionContent: this.singleQuestionContent,
        };
        const sel = this.singleDeptWards || [];
        if (this.queryParams.dimension === 'dept') {
          const names = sel.map((s) => String(s).replace(/^dept:/, ''));
          payload.leaveldeptcodes = names.length ? this.mapDeptNames(names) : this.defaultDeptCodes;
        } else {
          const names = sel.map((s) => String(s).replace(/^ward:/, ''));
          payload.leavehospitaldistrictcodes = names.length ? this.mapWardNames(names) : this.defaultWardCodes;
        }
        const res = await sltdMydTotalByScore(payload);
        console.log('[sltdMydTotalByScore å•题] code=', res && res.code, 'data=', JSON.stringify(res && res.data).slice(0, 1500));
        const list = res && res.code === 200 ? res.data : [];
        const { options, total } = this.normalizeSingleQuestion(list);
        this.singleQuestionData = options.map((o) => ({
          option: o.option,
          count: o.count,
          rate: o.ratio != null
            ? o.ratio.toFixed(2) + '%'
            : (total ? ((o.count / total) * 100).toFixed(2) + '%' : '0.00%'),
        }));
        this.singleQuestionMeta = {
          total: total.toLocaleString(),
          score: this.scoreOfOptions(options),
        };
        if (!options.length) this.notify('当前条件下暂无该题的选项统计数据', 'warning');
      } catch (e) {
        this.singleQuestionData = [];
        this.singleQuestionMeta = { total: '0', score: '0.00' };
        this.notify('单题选项统计数据请求失败,请稍后重试', 'error');
      } finally {
        this.singleLoading = false;
      }
    },
    handleReset() {
      this.queryParams.dateRange = this.defaultRange();
      this.queryParams.interval = 'month';
      this.queryParams.dimension = 'ward';
      this.singleDeptWards = [];
      this.refreshAll();
    },
    // åˆ‡æ¢å…¨å±€ç»Ÿè®¡ç»´åº¦æ—¶æ¸…空单题筛选,保证科室/病区互斥
    onDimensionChange() {
      this.singleDeptWards = [];
      this.refreshAll();
    },
    onRankDimensionChange() {
@@ -285,7 +469,7 @@
    defaultRange() {
      const end = new Date();
      const start = new Date();
      start.setDate(start.getDate() - 29);
      start.setDate(start.getDate() - 91);
      const f = (d) => {
        const y = d.getFullYear();
        const m = String(d.getMonth() + 1).padStart(2, '0');
@@ -298,6 +482,7 @@
    intervalConfig() {
      const buckets = this.genBuckets(this.queryParams.interval);
      if (this.queryParams.interval === 'day') return { buckets, seriesNames: ['昨日', '今日'] };
      if (this.queryParams.interval === 'week') return { buckets, seriesNames: ['上周', '本周'] };
      if (this.queryParams.interval === 'year') return { buckets, seriesNames: ['去年', '今年'] };
      return { buckets, seriesNames: ['上月', '本月'] };
    },
@@ -312,6 +497,12 @@
          d.setDate(end.getDate() - i);
          buckets.push(`${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`);
        }
      } else if (type === 'week') {
        for (let i = 5; i >= 0; i--) {
          const d = new Date(end);
          d.setDate(end.getDate() - i * 7);
          buckets.push(`${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} å‘¨`);
        }
      } else if (type === 'year') {
        for (let i = 3; i >= 0; i--) buckets.push(String(end.getFullYear() - i));
      } else {
@@ -322,83 +513,208 @@
      }
      return buckets;
    },
    // åˆ·æ–°å…¨éƒ¨æ•°æ®ï¼ˆæ¨¡æ‹Ÿï¼ŒæŽ¥å£å°±ç»ªåŽæ›¿æ¢ä¸ºçœŸå®žè¯·æ±‚)
    refreshAll() {
      const baseChart = JSON.stringify(this.queryParams);
      const baseTime = JSON.stringify({ dateRange: this.queryParams.dateRange });
      // å›¾è¡¨ï¼šåŒºé—´ç±»åž‹ + æ—¶é—´èŒƒå›´
      this.genChartData(mulberry32(hashString(baseChart + 'charts')));
      // å•题表:时间范围 + é¢˜ç›® + ç§‘室/病区
      this.genSingleData(mulberry32(hashString(baseTime + JSON.stringify({ sq: this.singleQuestionId, sdw: this.singleDeptWards }))));
      // ç§‘室/病区排名:时间范围 + ç»´åº¦ + é€‰æ‹©
      this.genRankData(mulberry32(hashString(baseTime + JSON.stringify({ rd: this.rankDimension, rdw: this.rankDeptWards }))));
      // å•题×科室/病区排名:时间范围 + ç»´åº¦ + é€‰æ‹© + é¢˜ç›®
      this.genCrossData(mulberry32(hashString(baseTime + JSON.stringify({ cd: this.crossDimension, cdw: this.crossDeptWards, sq: this.singleQuestionId }))));
    // åˆ·æ–°å…¨éƒ¨æ•°æ®ï¼ˆè¶‹åŠ¿ã€åˆ†ç±»ç»´åº¦ã€é™¢åŒºç»´åº¦ã€å•é¢˜ã€æŽ’åã€äº¤å‰å‡ä¸ºçœŸå®žæŽ¥å£ï¼‰
    async refreshAll() {
      // å…­ä¸ªæ•°æ®å—并行拉取,各区块同时显示各自的加载状态
      await Promise.all([
        this.fetchTrend(),
        this.fetchCategoryCompare(),
        this.fetchHospitalCompare(),
        this.fetchSingleQuestion(),
        this.fetchRank(),
        this.fetchCross(),
      ]);
      this.renderCharts();
    },
    genChartData(rnd) {
      const jitter = (lo, hi) => lo + rnd() * (hi - lo);
      const cfg = this.intervalConfig();
      const buckets = cfg.buckets;
    // æ»¡æ„åº¦è¶‹åŠ¿ï¼ˆçœŸå®žæŽ¥å£ï¼‰ï¼šdata = [{ period, totalScore }]
    async fetchTrend() {
      const range = this.queryParams.dateRange || [];
      if (range.length !== 2 || !range[0] || !range[1]) {
        this.trend = { buckets: [], data: [], seriesName: '门诊患者满意度' };
        return;
      }
      this.trend = {
        buckets,
        seriesName: '门诊患者满意度',
        data: buckets.map(() => Math.round(jitter(86, 90) * 100) / 100),
      };
      const categories = ['隐私保护', '环境与标识', '医务人员回应', '医生沟通', '挂号体验', '护士沟通'];
      this.categoryCompare = {
        categories,
        seriesNames: cfg.seriesNames,
        series1: categories.map(() => Math.round(jitter(82, 88) * 100) / 100),
        series2: categories.map(() => Math.round(jitter(83, 89) * 100) / 100),
      };
      const hospitals = ['院区一', '院区二', '院区三'];
      this.hospitalCompare = {
        hospitals,
        seriesNames: cfg.seriesNames,
        series1: hospitals.map(() => Math.round(jitter(85, 89) * 100) / 100),
        series2: hospitals.map(() => Math.round(jitter(86, 90) * 100) / 100),
      this.trendLoading = true;
      try {
        const res = await mydTrend({
          startFinishTime: range[0],
          endFinishTime: range[1],
          type: this.queryParams.interval, // day / week / month / year
        });
        const list = res && res.code === 200 && Array.isArray(res.data) ? res.data : [];
        this.trend = {
          seriesName: '门诊患者满意度',
          buckets: list.map((i) => String((i && i.period) || '')),
          data: list.map((i) => {
            const n = Number(i && i.totalScore);
            return isNaN(n) ? 0 : Math.round(n * 100) / 100;
          }),
        };
        if (!list.length) this.notify('当前条件下暂无趋势数据', 'warning');
      } catch (e) {
        this.trend = { buckets: [], data: [], seriesName: '门诊患者满意度' };
        this.notify('趋势数据请求失败,请稍后重试', 'error');
      } finally {
        this.trendLoading = false;
      }
    },
    notify(msg, type) {
      const fn = { warning: 'warning', error: 'error', success: 'success' }[type] || 'info';
      if (this.$message && this.$message[fn]) this.$message[fn](msg);
    },
    // åˆ†ç±»ç»´åº¦å¯¹æ¯”(真实接口):dimensionType=1,data = [{ period, dimension, totalScore }]
    async fetchCategoryCompare() {
      const range = this.queryParams.dateRange || [];
      if (range.length !== 2 || !range[0] || !range[1]) {
        this.categoryCompare = { categories: [], seriesNames: [], series1: [], series2: [] };
        return;
      }
      this.categoryLoading = true;
      try {
        const res = await sltdMydTotalByDimension({
          startFinishTime: range[0],
          endFinishTime: range[1],
          type: this.queryParams.interval, // day / week / month / year
          dimensionType: "1", // 1=按满意度维度统计
        });
        // æŽ’查用:打印接口原始返回,便于核对字段名(确认无误后可删除)
        console.log("[sltdMydTotalByDimension dimensionType=1] code=", res && res.code, "data=", JSON.stringify(res && res.data).slice(0, 2000));
        const list = res && res.code === 200 && Array.isArray(res.data) ? res.data : [];
        // ç»´åº¦ç¼–码 â†’ å­—典名称,并过滤两期均无数据的维度
        const d = this.buildCompareData(list, (code) => this.getDimensionLabel(code));
        this.categoryCompare = { categories: d.labels, seriesNames: d.seriesNames, series1: d.series1, series2: d.series2 };
        if (!list.length) this.notify('当前条件下暂无分类维度数据', 'warning');
      } catch (e) {
        this.categoryCompare = { categories: [], seriesNames: [], series1: [], series2: [] };
        this.notify('分类维度数据请求失败,请稍后重试', 'error');
      } finally {
        this.categoryLoading = false;
      }
    },
    // é™¢åŒºç»´åº¦å¯¹æ¯”(真实接口):dimensionType=2,data = [{ period, dimension(院区名), totalScore }]
    async fetchHospitalCompare() {
      const range = this.queryParams.dateRange || [];
      if (range.length !== 2 || !range[0] || !range[1]) {
        this.hospitalCompare = { hospitals: [], seriesNames: [], series1: [], series2: [] };
        return;
      }
      this.hospitalLoading = true;
      try {
        const res = await sltdMydTotalByDimension({
          startFinishTime: range[0],
          endFinishTime: range[1],
          type: this.queryParams.interval, // day / week / month / year
          dimensionType: "2", // 2=按院区统计
        });
        // æŽ’查用:打印接口原始返回,便于核对字段名(确认无误后可删除)
        console.log("[sltdMydTotalByDimension dimensionType=2] code=", res && res.code, "data=", JSON.stringify(res && res.data).slice(0, 2000));
        const list = res && res.code === 200 && Array.isArray(res.data) ? res.data : [];
        const d = this.buildCompareData(list);
        this.hospitalCompare = { hospitals: d.labels, seriesNames: d.seriesNames, series1: d.series1, series2: d.series2 };
        if (!list.length) this.notify('当前条件下暂无院区维度数据', 'warning');
      } catch (e) {
        this.hospitalCompare = { hospitals: [], seriesNames: [], series1: [], series2: [] };
        this.notify('院区维度数据请求失败,请稍后重试', 'error');
      } finally {
        this.hospitalLoading = false;
      }
    },
    // å°† [{ period, dimension, totalScore }] ç»„装为分组柱状图数据(上期/本期两组对比)
    // labelFn:可选,用于把维度编码映射为字典名称(分类维度图传入)
    buildCompareData(list, labelFn) {
      const labels = [];
      const byCat = {};
      list.forEach((i) => {
        // ç»´åº¦ç¼–码(dimensionType=1)或院区名称(dimensionType=2);若后端字段命名不同,兜底取 period
        const key = (i && (i.dimension || i.campusName || i.campusid)) || (i && i.period) || '';
        if (!key) return;
        if (!byCat[key]) { byCat[key] = {}; labels.push(key); }
        const n = Number(i.totalScore);
        byCat[key][i.period] = isNaN(n) ? 0 : Math.round(n * 100) / 100;
      });
      // åŽç«¯æŒ‰æ—¶é—´å‡åºè¿”回,取最后两个周期作为“上期 / æœ¬æœŸâ€
      const periods = [...new Set(list.map((i) => i && i.period).filter(Boolean))];
      const cur = periods[periods.length - 1];
      const prev = periods[periods.length - 2];
      const val = (cat, p) => (p != null && byCat[cat][p] != null ? byCat[cat][p] : 0);
      // è¿‡æ»¤ä¸¤æœŸå‡æ— æ•°æ®çš„维度
      const cats = labels.filter((c) => val(c, prev) !== 0 || val(c, cur) !== 0);
      const display = labelFn ? cats.map((c) => labelFn(c)) : cats;
      return {
        labels: display,
        seriesNames: this.intervalConfig().seriesNames,
        series1: cats.map((c) => val(c, prev)),
        series2: cats.map((c) => val(c, cur)),
      };
    },
    genSingleData(rnd) {
      const jitter = (lo, hi) => lo + rnd() * (hi - lo);
      const scopeFactor = this.singleDeptWards.length ? 0.18 : 1;
      const total = Math.round(jitter(20000, 30000) * scopeFactor);
      const weights = [0.62, 0.27, 0.07, 0.04];
      const counts = weights.map((w) => Math.max(0, Math.round(total * w * jitter(0.92, 1.08))));
      const good = counts[0] + counts[1];
      this.singleQuestionData = QUESTION_OPTIONS.map((opt, i) => ({
        option: opt,
        count: counts[i],
        rate: ((counts[i] / total) * 100).toFixed(2) + '%',
    // ç§‘室/病区维度 Â· é—¨è¯Šæ‚£è€…满意度得分排名(真实接口 /mzMydScoreRank,不带问题文本)
    async fetchRank() {
      const range = this.queryParams.dateRange || [];
      if (range.length !== 2 || !range[0] || !range[1]) {
        this.rankData = [];
        return;
      }
      this.rankLoading = true;
      try {
        const res = await mzMydScoreRank(this.buildRankPayload(this.rankDimension, this.rankDeptWards, ''));
        this.rankData = this.normalizeRank(res);
        if (!this.rankData.length) this.notify('当前条件下暂无排名数据', 'warning');
      } catch (e) {
        this.rankData = [];
        this.notify('排名数据请求失败,请稍后重试', 'error');
      } finally {
        this.rankLoading = false;
      }
    },
    // å•题×科室/病区 Â· å¾—分排名(真实接口 /mzMydScoreRank,带问题文本)
    async fetchCross() {
      const range = this.queryParams.dateRange || [];
      if (range.length !== 2 || !range[0] || !range[1] || !this.singleQuestionContent) {
        this.crossData = [];
        return;
      }
      this.crossLoading = true;
      try {
        const res = await mzMydScoreRank(this.buildRankPayload(this.crossDimension, this.crossDeptWards, this.singleQuestionContent));
        this.crossData = this.normalizeRank(res);
        if (!this.crossData.length) this.notify('当前条件下暂无排名数据', 'warning');
      } catch (e) {
        this.crossData = [];
        this.notify('排名数据请求失败,请稍后重试', 'error');
      } finally {
        this.crossLoading = false;
      }
    },
    // ç»„装排名接口请求体(dimensionType/serviceType/deptCodes/districtCodes/时间/questionText)
    buildRankPayload(dimension, selectedNames, questionText) {
      const range = this.queryParams.dateRange || [];
      const payload = {
        dimensionType: dimension === 'dept' ? '1' : '2',
        serviceTypes: ['6', '14'],
        startFinishTime: range[0],
        endFinishTime: range[1],
      };
      if (questionText) payload.questionText = questionText;
      if (selectedNames && selectedNames.length) {
        if (dimension === 'dept') {
          payload.deptCodes = this.mapDeptNames(selectedNames);
        } else {
          payload.districtCodes = this.mapWardNames(selectedNames);
        }
      }
      return payload;
    },
    // å½’一化排名返回 List<MzMydScoreRankDTO> â†’ [{ rank, code, name, score, itemCount }]
    normalizeRank(res) {
      const list = res && res.code === 200 && Array.isArray(res.data) ? res.data : [];
      return list.map((item) => ({
        rank: item.rank,
        code: item.code,
        name: item.name,
        score: item.score,
        itemCount: item.itemCount,
      }));
      this.singleQuestionMeta = {
        total: total.toLocaleString(),
        score: ((good / total) * 100).toFixed(2) + '%',
      };
    },
    genRankData(rnd) {
      const jitter = (lo, hi) => lo + rnd() * (hi - lo);
      this.deptRankData = DEPTS
        .map((d) => ({ name: d, score: jitter(70, 85).toFixed(2) }))
        .sort((a, b) => Number(b.score) - Number(a.score));
      this.wardRankData = WARDS
        .map((w) => ({ name: w, score: jitter(70, 85).toFixed(2) }))
        .sort((a, b) => Number(b.score) - Number(a.score));
    },
    genCrossData(rnd) {
      const jitter = (lo, hi) => lo + rnd() * (hi - lo);
      this.crossDeptData = DEPTS
        .map((d) => ({ name: d, score: jitter(70, 85).toFixed(2) }))
        .sort((a, b) => Number(b.score) - Number(a.score));
      this.crossWardData = WARDS
        .map((w) => ({ name: w, score: jitter(70, 85).toFixed(2) }))
        .sort((a, b) => Number(b.score) - Number(a.score));
    },
    renderCharts() {
      const c1 = echarts.getInstanceByDom(this.$refs.chart1);
@@ -433,22 +749,46 @@
      });
      // 2. åˆ†ç»„柱状图 - åˆ†ç±»ç»´åº¦
      c2.setOption({
      // åˆ†ç±»è¾ƒå¤šï¼ˆå¦‚十几个满意度维度)时底部名称会重叠,这里按数量动态适配:
      //   â‰¤6 ä¸ªï¼šæ­£å¸¸æ°´å¹³æ ‡ç­¾ï¼›
      //   7~10 ä¸ªï¼šx è½´æ ‡ç­¾å€¾æ–œ 35° + åº•部留白加大 + æŸ±å­å˜ç»†ï¼›
      //   >10 ä¸ªï¼šå†å¯ç”¨æ¨ªå‘缩放(初始显示约 8 ä¸ªåˆ†ç±»ï¼Œå¯æ‹–动/滚动查看其余)。
      const catCategories = this.categoryCompare.categories || [];
      const catN = catCategories.length;
      const catRotate = catN > 6 ? 35 : 0;
      const catEnableZoom = catN > 10;
      const catBarWidth = catN > 10 ? 18 : catN > 6 ? 20 : 22;
      const catLegend = catEnableZoom
        ? { data: this.categoryCompare.seriesNames, top: 4, textStyle: { color: '#666', fontSize: 12 } }
        : { data: this.categoryCompare.seriesNames, bottom: 8, textStyle: { color: '#666', fontSize: 12 } };
      const catGrid = catEnableZoom
        ? { top: 36, right: 30, bottom: 80, left: 85 }
        : { top: 48, right: 30, bottom: catRotate ? 100 : 60, left: 85 };
      const catOption = {
        color: ['#6DC8EC', '#5B8FF9'],
        grid: barGrid,
        grid: catGrid,
        tooltip: { ...baseTooltip, trigger: 'axis', axisPointer: { type: 'shadow', shadowStyle: { color: 'rgba(0,0,0,0.03)' } } },
        legend: { data: this.categoryCompare.seriesNames, bottom: 8, textStyle: { color: '#666', fontSize: 12 } },
        xAxis: [{ ...baseXAxis, data: this.categoryCompare.categories, axisLabel: { ...baseXAxis.axisLabel, interval: 0 } }],
        legend: catLegend,
        xAxis: [{ ...baseXAxis, data: catCategories, axisLabel: { ...baseXAxis.axisLabel, interval: 0, rotate: catRotate } }],
        yAxis: [{ ...baseYAxis, name: '得分', nameTextStyle: { color: '#999', fontSize: 11 } }],
        series: [
          { name: this.categoryCompare.seriesNames[0], type: 'bar', barWidth: 22, barGap: '30%',
          { name: this.categoryCompare.seriesNames[0], type: 'bar', barWidth: catBarWidth, barGap: '30%',
            itemStyle: { borderRadius: [6, 6, 0, 0], color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: '#6DC8EC' }, { offset: 1, color: '#48B0D5' }]) },
            data: this.categoryCompare.series1 },
          { name: this.categoryCompare.seriesNames[1], type: 'bar', barWidth: 22,
          { name: this.categoryCompare.seriesNames[1], type: 'bar', barWidth: catBarWidth,
            itemStyle: { borderRadius: [6, 6, 0, 0], color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: '#5B8FF9' }, { offset: 1, color: '#3D6FDB' }]) },
            data: this.categoryCompare.series2 },
        ],
      });
      };
      if (catEnableZoom) {
        // åˆå§‹åªå±•示前 8 ä¸ªåˆ†ç±»å®½åº¦ï¼Œé¿å…æŸ±å­æŒ¤æˆä¸€å›¢ï¼Œç”¨æˆ·å¯æ¨ªå‘滚动/拖动查看其余
        const zoomEnd = catN > 0 ? Math.min(100, Math.round((8 / catN) * 100)) : 100;
        catOption.dataZoom = [
          { type: 'inside', start: 0, end: zoomEnd },
          { type: 'slider', height: 16, bottom: 4, start: 0, end: zoomEnd },
        ];
      }
      c2.setOption(catOption);
      // 4. é™¢åŒºç»´åº¦ - åˆ†ç»„柱状图
      c4.setOption({
@@ -527,6 +867,8 @@
.filter-bar { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; .filter-label { font-size: 13px; color: #606266; } .filter-date { width: 260px; } }
.chart-card { margin-bottom: 0; }
.chart-box { width: 100%; height: 360px; }
.chart-wrap { position: relative; width: 100%; }
.chart-empty { position: absolute; top: 0; left: 0; right: 0; bottom: 0; display: flex; align-items: center; justify-content: center; background: #fff; color: #909399; font-size: 14px; }
.card-header { display: flex; justify-content: space-between; align-items: center; span { font-size: 15px; font-weight: 600; color: #303133; } }
.header-right { display: flex; align-items: center; gap: 10px; .sel { width: 140px; } .sel-lg { width: 240px; } }
.table-footer { text-align: right; padding: 10px 0 0; font-size: 13px; color: #606266; }
src/views/Satisfaction/sfstatistics/components/DataOverview.vue
@@ -3,7 +3,7 @@
    <!-- é¡¶éƒ¨ç­›é€‰ï¼šæ—¶é—´èŒƒå›´ + ç§‘室/病区,控制本页所有数据 -->
    <el-card shadow="never" class="overview-card filter-card">
      <el-form inline class="filter-form">
        <el-form-item label="时间范围">
        <el-form-item label="出院日期">
          <el-date-picker
            v-model="queryParams.dateRange"
            type="daterange"
@@ -14,49 +14,117 @@
            size="small"
          />
        </el-form-item>
        <el-form-item label="科室/病区">
        <el-form-item label="统计维度">
          <el-radio-group
            v-model="queryParams.statMode"
            size="small"
            @change="onStatModeChange"
          >
            <el-radio-button label="ward">按病区统计</el-radio-button>
            <el-radio-button label="dept">按科室统计</el-radio-button>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="科室" v-if="queryParams.statMode === 'dept'">
          <el-select
            v-model="queryParams.deptWard"
            v-model="queryParams.deptCodes"
            multiple
            collapse-tags
            clearable
            filterable
            placeholder="全部"
            placeholder="全部科室"
            size="small"
            class="filter-select"
          >
            <el-option-group label="科室">
              <el-option
                v-for="d in deptOptions"
                :key="'dept:' + d"
                :label="d"
                :value="'dept:' + d"
              />
            </el-option-group>
            <el-option-group label="病区">
              <el-option
                v-for="w in wardOptions"
                :key="'ward:' + w"
                :label="w"
                :value="'ward:' + w"
              />
            </el-option-group>
            <el-option
              v-for="d in deptOptions"
              :key="d.value"
              :label="d.label"
              :value="d.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="病区" v-else>
          <el-select
            v-model="queryParams.wardCodes"
            multiple
            collapse-tags
            clearable
            filterable
            placeholder="全部病区"
            size="small"
            class="filter-select"
          >
            <el-option
              v-for="w in wardOptions"
              :key="w.value"
              :label="w.label"
              :value="w.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="服务类型">
          <el-select
            v-model="queryParams.serviceTypeList"
            multiple
            collapse-tags
            clearable
            filterable
            placeholder="全部类型"
            size="small"
            class="filter-select"
          >
            <el-option
              v-for="s in serviceTypeOptions"
              :key="s.value"
              :label="s.label"
              :value="s.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" size="small" icon="el-icon-search" @click="fetchData">查询</el-button>
          <el-button size="small" icon="el-icon-refresh" @click="handleReset">重置</el-button>
          <el-button
            type="primary"
            size="small"
            icon="el-icon-search"
            :loading="loading"
            @click="fetchData"
            >查询</el-button
          >
          <el-button size="small" icon="el-icon-refresh" @click="handleReset"
            >重置</el-button
          >
        </el-form-item>
      </el-form>
    </el-card>
    <!-- è¡¨æ ¼ä¸€ï¼šå„服务项目满意度统计(固定五项选项的题目) -->
    <!-- è¡¨æ ¼ä¸€ï¼šå„服务项目满意度统计(固定六项选项的题目) -->
    <el-card shadow="never" class="overview-card">
      <div slot="header" class="card-header">
        <span class="card-title">各服务项目满意度统计</span>
        <el-button type="warning" size="small" icon="el-icon-download" @click="exportTable('table1')">导出Excel</el-button>
        <el-button
          type="warning"
          size="small"
          icon="el-icon-download"
          @click="exportTable('table1')"
          >导出Excel</el-button
        >
      </div>
      <el-table id="table1" :data="serviceSatisfaction" border size="small" :header-cell-style="{ background: '#f5f7fa', color: '#303133' }">
        <el-table-column prop="category" label="分类/题目" min-width="220" fixed="left" />
        <el-table-column label="非常满意" align="center">
      <el-table
        id="table1"
        v-loading="loading"
        element-loading-text="统计中..."
        :data="serviceSatisfaction"
        border
        size="small"
        :header-cell-style="{ background: '#f5f7fa', color: '#303133' }"
      >
        <el-table-column
          prop="category"
          label="分类/题目"
          min-width="220"
          fixed="left"
        />
        <el-table-column label="很满意" align="center">
          <el-table-column prop="veryGoodCount" label="数量" width="90" />
          <el-table-column prop="veryGoodRate" label="比例" width="90" />
        </el-table-column>
@@ -72,11 +140,21 @@
          <el-table-column prop="badCount" label="数量" width="90" />
          <el-table-column prop="badRate" label="比例" width="90" />
        </el-table-column>
        <el-table-column label="不适用" align="center">
        <el-table-column label="很不满意" align="center">
          <el-table-column prop="veryBadCount" label="数量" width="90" />
          <el-table-column prop="veryBadRate" label="比例" width="90" />
        </el-table-column>
        <el-table-column label="未经历" align="center">
          <el-table-column prop="naCount" label="数量" width="90" />
          <el-table-column prop="naRate" label="比例" width="90" />
        </el-table-column>
        <el-table-column prop="score" label="综合得分" width="100" align="center" />
        <el-table-column
          prop="score"
          label="综合得分"
          width="100"
          align="center"
          fixed="right"
        />
      </el-table>
    </el-card>
@@ -84,17 +162,59 @@
    <el-card shadow="never" class="overview-card">
      <div slot="header" class="card-header">
        <span class="card-title">单选题/多选题统计</span>
        <el-button type="warning" size="small" icon="el-icon-download" @click="exportTable('table2')">导出Excel</el-button>
        <div class="header-right">
          <el-input
            v-model="questionSearch"
            size="small"
            clearable
            prefix-icon="el-icon-search"
            placeholder="按题目模糊查询"
            class="search-input"
          />
          <el-button
            type="warning"
            size="small"
            icon="el-icon-download"
            @click="exportTable('table2')"
            >导出Excel</el-button
          >
        </div>
      </div>
      <div id="table2">
        <div class="question-block" v-for="(q, qi) in questionStats" :key="qi">
          <h4 class="q-title">{{ q.title }}</h4>
          <el-table :data="q.options" border size="small" :header-cell-style="{ background: '#f5f7fa', color: '#303133' }">
            <el-table-column prop="option" label="选项" min-width="180" />
            <el-table-column prop="count" label="数量" width="100" align="center" />
            <el-table-column prop="rate" label="比例" width="100" align="center" />
          </el-table>
          <div v-if="q.total" class="q-footer">本题有效填写人次:<b>{{ q.total }}</b></div>
      <div id="table2" v-loading="loading" element-loading-text="统计中...">
        <div class="table2-scroll">
          <div
            class="question-block"
            v-for="(q, qi) in filteredQuestionStats"
            :key="qi"
          >
            <h4 class="q-title">{{ q.title }}</h4>
            <el-table
              :data="q.options"
              border
              size="small"
              :header-cell-style="{ background: '#f5f7fa', color: '#303133' }"
            >
              <el-table-column prop="option" label="选项" min-width="180" />
              <el-table-column
                prop="count"
                label="数量"
                width="100"
                align="center"
              />
              <el-table-column
                prop="rate"
                label="比例"
                width="100"
                align="center"
              />
            </el-table>
            <div v-if="q.total" class="q-footer">
              æœ¬é¢˜å¡«æŠ¥æ€»é‡ï¼š<b>{{ q.total }}</b>
            </div>
          </div>
          <div v-if="!loading && !filteredQuestionStats.length" class="empty-tip">
            {{ questionStats.length ? "未找到匹配的题目" : "暂无单选/多选题统计数据" }}
          </div>
        </div>
      </div>
    </el-card>
@@ -104,149 +224,336 @@
      <div slot="header" class="card-header">
        <span class="card-title">各科室/病区满意度评分对比</span>
        <div class="header-right">
          <el-radio-group v-model="statDimension" size="small" @change="onStatDimensionChange">
          <el-radio-group
            v-model="statDimension"
            size="small"
            @change="onStatDimensionChange"
          >
            <el-radio-button label="dept">按科室</el-radio-button>
            <el-radio-button label="ward">按病区</el-radio-button>
          </el-radio-group>
          <el-select v-model="statDeptWard" size="small" class="stat-select" placeholder="沿用顶部条件">
          <el-select
            v-model="statDeptWard"
            size="small"
            class="stat-select"
            placeholder="沿用顶部条件"
          >
            <el-option label="沿用顶部条件" value="inherit" />
            <template v-if="statDimension === 'dept'">
              <el-option v-for="d in deptOptions" :key="'dept:' + d" :label="d" :value="'dept:' + d" />
              <el-option
                v-for="d in deptOptions"
                :key="'dept:' + d.value"
                :label="d.label"
                :value="'dept:' + d.value"
              />
            </template>
            <template v-else>
              <el-option v-for="w in wardOptions" :key="'ward:' + w" :label="w" :value="'ward:' + w" />
              <el-option
                v-for="w in wardOptions"
                :key="'ward:' + w.value"
                :label="w.label"
                :value="'ward:' + w.value"
              />
            </template>
          </el-select>
          <el-button type="warning" size="small" icon="el-icon-download" @click="exportTable('table3')">导出Excel</el-button>
          <el-button
            type="warning"
            size="small"
            icon="el-icon-download"
            @click="exportTable('table3')"
            >导出Excel</el-button
          >
        </div>
      </div>
      <el-table id="table3" :data="table3Rows" border size="small" :header-cell-style="{ background: '#f5f7fa', color: '#303133' }">
        <el-table-column prop="name" :label="statDimension === 'dept' ? '科室' : '病区'" fixed="left" align="center" />
        <el-table-column label="第1题" align="center">
          <el-table-column prop="q1VeryGood" label="非常满意" width="100" />
          <el-table-column prop="q1Good" label="满意" width="90" />
          <el-table-column prop="q1Normal" label="一般" width="90" />
          <el-table-column prop="q1Bad" label="不满意" width="90" />
          <el-table-column prop="q1Score" label="得分" width="100" />
      <el-table
        id="table3"
        v-loading="loading"
        element-loading-text="统计中..."
        :data="table3Rows"
        border
        size="small"
        max-height="500"
        :header-cell-style="{ background: '#f5f7fa', color: '#303133' }"
      >
        <el-table-column
          prop="name"
          :label="statDimension === 'dept' ? '科室' : '病区'"
          fixed="left"
          align="center"
        />
        <el-table-column
          v-for="q in table3Questions"
          :key="q.title"
          :label="q.title"
          align="center"
        >
          <el-table-column
            v-for="opt in q.options"
            :key="opt"
            :label="opt"
            width="100"
          >
            <template slot-scope="{ row }">
              {{
                (row.cells[q.title] &&
                  row.cells[q.title][opt] &&
                  row.cells[q.title][opt].count) ||
                  0
              }}
            </template>
          </el-table-column>
          <el-table-column label="得分" width="90">
            <template slot-scope="{ row }">
              {{ (row.cells[q.title] && row.cells[q.title]._score) || "0.00" }}
            </template>
          </el-table-column>
        </el-table-column>
        <el-table-column label="第2题" align="center">
          <el-table-column prop="q2VeryGood" label="非常满意" width="100" />
          <el-table-column prop="q2Good" label="满意" width="90" />
          <el-table-column prop="q2Normal" label="一般" width="90" />
          <el-table-column prop="q2Bad" label="不满意" width="90" />
          <el-table-column prop="q2Score" label="得分" width="100" />
        <el-table-column label="综合得分" align="center">
          <template slot-scope="{ row }">
            {{ row.totalScore }}
          </template>
        </el-table-column>
        <el-table-column prop="totalScore" label="综合得分" align="center" />
      </el-table>
    </el-card>
  </div>
</template>
<script>
import XLSX from 'xlsx';
import { sltdMydTotalByScore,statQuestionOptionByKsOrBq } from "@/api/AiCentre/satisfaction";
import XLSX from "xlsx";
// æ»¡æ„åº¦é¢˜çš„固定选项(顺序即表格列顺序)
const FIXED_OPTIONS = ['非常满意', '满意', '一般', '不满意', '不适用'];
const DEPTS = ['内分泌科', '皮肤科', '肿瘤内科', '消化内科', '肾内科'];
const WARDS = ['一病区', '二病区', '三病区', '四病区', '五病区'];
// æ»¡æ„åº¦é¢˜çš„固定选项(顺序即表格列顺序)——这些选项的题目进表一,其余进表二
const FIXED_OPTIONS = ["很满意", "满意", "一般", "不满意", "很不满意", "未经历"];
// æ— åˆ†å€¼é€‰é¡¹ï¼ˆå¦‚"未经历")是否计入综合得分分母:默认 false,避免拉低得分
const NO_SCORE_IN_DENOMINATOR = false;
// æ»¡æ„åº¦é¢˜ï¼ˆå›ºå®šäº”项选项,counts é¡ºåºå¯¹åº” FIXED_OPTIONS)
const SATISFACTION_BASE = [
  ['诊疗过程中,医务人员是否注意保护您的隐私?', [3219, 1351, 142, 33, 0]],
  ['您对医院服务设施的总体印象如何?', [3110, 1369, 217, 49, 0]],
  ['医院的厕所是否清洁无异味?', [2973, 1504, 235, 33, 0]],
  ['医院内的路标和指示是否明确?', [3234, 1289, 196, 26, 0]],
  ['医院的空间布局是否便利?', [3037, 1460, 209, 39, 0]],
  ['看病期间,您的抱怨和不满能否及时得到回应?', [3019, 1317, 294, 115, 0]],
  ['医生是否用您听得懂的方式解释问题?', [3402, 967, 254, 122, 0]],
  ['医生对您是否尊重?', [3495, 897, 249, 104, 0]],
  ['医生是否仔细倾听您讲话?', [3422, 937, 251, 135, 0]],
  ['对挂号服务人员服务态度是否满意', [3458, 1205, 52, 30, 0]],
  ['您对预约挂号是否满意?', [3527, 1088, 72, 58, 0]],
  ['护士对您是否尊重?', [3420, 1184, 113, 28, 0]],
  ['护士是否用您听得懂的方式解释问题?', [3346, 1250, 122, 27, 0]],
  ['护士是否仔细倾听您讲话?', [3378, 1205, 134, 28, 0]],
// æœåŠ¡ç±»åž‹å­—å…¸ï¼ˆæŽ¥å£ serviceType)
const SERVICE_TYPES = [
  { value: "1", label: "监测评估" },
  { value: "2", label: "出院随访" },
  { value: "3", label: "门诊随访" },
  { value: "4", label: "宣教关怀" },
  { value: "5", label: "复诊通知" },
  { value: "6", label: "满意度调查" },
  { value: "7", label: "患者报告" },
  { value: "8", label: "其他通知" },
  { value: "9", label: "体检随访" },
  { value: "10", label: "医技随访" },
  { value: "11", label: "影像专科随访" },
  { value: "12", label: "心电专科随访" },
  { value: "13", label: "专科随访" },
];
// å…¶ä½™é¢˜ç›®ï¼ˆå•选/多选,非固定满意度选项)
const OTHER_BASE = [
  {
    title: '您对我院工作人员的服务质量满意不满意?【分值单选题】',
    type: 'single',
    options: [['非常满意', 1084], ['满意', 1959], ['一般', 167], ['不满意', 153]],
  },
  {
    title: '您对哪部分服务不满意?【多选题】',
    type: 'multiple',
    total: 408,
    options: [['医生诊断和诊疗', 131], ['护理服务', 47], ['其他医务人员技术水平', 30], ['工人', 72]],
  },
  {
    title: '医生诊断和治疗中哪些服务质量不满意?【多选题】',
    type: 'multiple',
    total: 131,
    options: [['医生诊断错误', 23], ['对治疗效果不满意', 72], ['其他', 54]],
  },
];
// æœåŠ¡ç±»åž‹é»˜è®¤é€‰ä¸­é¡¹ï¼ˆæŒ‰ label åŒ¹é…ï¼Œé€‚配不同字典的 value å·®å¼‚)
const DEFAULT_SERVICE_LABELS = ["住院满意度", "门诊满意度"];
// ç»Ÿä¸€æŽ¥å£è¿”回的题目(模拟):固定五项选项 => è¡¨ä¸€ï¼Œå…¶ä½™ => è¡¨äºŒ
const RAW_QUESTIONS = [
  ...SATISFACTION_BASE.map(([title, counts]) => ({
    title,
    type: 'single',
    options: FIXED_OPTIONS.map((opt, i) => ({ option: opt, count: counts[i] })),
  })),
  ...OTHER_BASE.map((q) => ({
    title: q.title,
    type: q.type,
    total: q.total,
    options: q.options.map(([option, count]) => ({ option, count })),
  })),
];
// å…œåº•:store ä¸­æ— ç§‘室/病区数据时使用(表三对比 + ç­›é€‰ä¸‹æ‹‰ï¼‰
const DEPTS = ["内分泌科", "皮肤科", "肿瘤内科", "消化内科", "肾内科"];
const WARDS = ["一病区", "二病区", "三病区", "四病区", "五病区"];
// ç¡®å®šæ€§ä¼ªéšæœºï¼ˆä¾¿äºŽç­›é€‰æ¡ä»¶å›ºå®šæ—¶ç»“果稳定)
function hashString(str) {
  let h = 2166136261;
  for (let i = 0; i < str.length; i++) {
    h ^= str.charCodeAt(i);
    h = Math.imul(h, 16777619);
  }
  return h >>> 0;
}
function mulberry32(a) {
  return function () {
    a |= 0;
    a = (a + 0x6d2b79f5) | 0;
    let t = Math.imul(a ^ (a >>> 15), 1 | a);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
/**
 * æŽ¥å£æ•°æ® â†’ é¡µé¢å†…部题目结构
 * å…¥å‚:接口 data(List<QuestionResultDTO>)
 * å‡ºå‚:[{ title, type, total, options: [{ option, count, score }] }]
 * åŒä¸€é¢˜ç›®å¯èƒ½åˆ†æ•£åœ¨å¤šä¸ªä»»åŠ¡ï¼ˆtaskname)里,这里按「题目 + é€‰é¡¹ã€åˆå¹¶ç´¯åŠ æ•°é‡
 */
function normalizeQuestions(list) {
  const map = new Map();
  // å…¥å‚可能是数组 / {list} / {rows} / å•对象,统一规整成数组
  const arr = Array.isArray(list)
    ? list
    : (list &&
      (Array.isArray(list.list)
        ? list.list
        : Array.isArray(list.rows)
        ? list.rows
        : [list])) ||
      [];
  // é¢˜ç›®æ–‡æœ¬å­—段候选(本系统题目内容统一叫 scriptContent,兼容历史命名)
  const pickTitle = (item) => {
    const keys = [
      "scriptContent",
      "questionText",
      "qeustionText",
      "questiontext",
      "scriptcontent",
      "title",
      "name",
      "questionName",
    ];
    for (const k of keys) {
      if (item && item[k] != null && String(item[k]).trim()) {
        return String(item[k]).trim();
      }
    }
    return "";
  };
  // é€‰é¡¹æ˜Žç»†åˆ—表字段候选
  const pickDetails = (item) => {
    const keys = [
      "subtaskDetailRatioExportList",
      "details",
      "optionList",
      "options",
      "optionResultList",
      "subtaskDetailRatioExportlist",
      "list",
    ];
    for (const k of keys) {
      if (item && Array.isArray(item[k])) return item[k];
    }
    return [];
  };
  // é€‰é¡¹æ–‡æœ¬å­—段候选
  const pickOption = (d) => {
    const keys = [
      "optionresult",
      "optioncontent",
      "optionText",
      "targetvalue",
      "optionName",
      "name",
      "option",
      "content",
    ];
    for (const k of keys) {
      if (d && d[k] != null && String(d[k]).trim()) {
        return String(d[k]).trim();
      }
    }
    return "";
  };
  // æ•°é‡å­—段候选
  const pickCount = (d) => {
    const keys = ["count", "chosenQuantity", "num", "total", "chosenquantity"];
    for (const k of keys) {
      if (d && d[k] != null && !isNaN(Number(d[k]))) {
        return Number(d[k]) || 0;
      }
    }
    return 0;
  };
  arr.forEach((item) => {
    if (!item || typeof item !== "object") return;
    const title = pickTitle(item) || "未命名题目";
    const details = pickDetails(item);
    let node = map.get(title);
    if (!node) {
      node = { title, type: "", options: new Map() };
      map.set(title, node);
    }
    details.forEach((d) => {
      const option = pickOption(d);
      if (!option) return;
      const count = pickCount(d);
      const rawScore = Number(d && d.score);
      const score = isNaN(rawScore) ? null : rawScore;
      // é¢˜åž‹ï¼šä¼˜å…ˆ scriptType(1单选 2多选 4问答),其次 valueType(1单选 2多选 3问答 4填空 5其它)
      const typeVal = String(
        (item && item.scriptType) ||
          (d && d.scriptType) ||
          (d && d.valueType) ||
          (item && item.valueType) ||
          ""
      );
      if (!node.type) {
        node.type =
          typeVal === "2" ? "multiple" : typeVal === "1" ? "single" : "qa";
      }
      const old = node.options.get(option);
      if (old) {
        old.count += count;
        if (old.score == null) old.score = score;
      } else {
        node.options.set(option, { option, count, score });
      }
    });
  });
  return Array.from(map.values()).map((n) => {
    const options = Array.from(n.options.values());
    const item = { title: n.title, type: n.type, options };
    // å¤šé€‰é¢˜ï¼šç”¨å„选项数量之和作为填报总量(接口未单独提供人次)
    if (n.type === "multiple")
      item.total = options.reduce((s, o) => s + o.count, 0);
    return item;
  });
}
export default {
  name: 'DataOverview',
  name: "DataOverview",
  data() {
    return {
      // é¡¶éƒ¨å…¨å±€ç­›é€‰
      queryParams: {
        dateRange: [],
        deptWard: '',
        deptCodes: [], // ç§‘室编码集合 => leaveldeptcodes
        wardCodes: [], // å‡ºé™¢ç—…区编码集合 => leavehospitaldistrictcodes(接口必填)
        serviceTypeList: [], // æœåŠ¡ç±»åž‹å¤šé€‰ï¼Œæäº¤æ—¶ç”¨é€—å·æ‹¼æŽ¥
        deptOrDistrict: "1", // ç§‘室/病区组合方式,默认 1:离院病区 and ç¦»é™¢ç§‘室
        statMode: "ward", // ç»Ÿè®¡ç»´åº¦ï¼šward=按病区统计 dept=按科室统计
      },
      deptOptions: DEPTS,
      wardOptions: WARDS,
      SERVICE_TYPES,
      loading: false,
      // è¡¨äºŒï¼šé¢˜ç›®æ¨¡ç³ŠæŸ¥è¯¢å…³é”®å­—(纯前端过滤,不请求后端)
      questionSearch: "",
      // è¡¨æ ¼ä¸‰ï¼šç»Ÿè®¡ç»´åº¦ + ç‹¬ç«‹ç§‘室/病区条件('inherit' = æ²¿ç”¨é¡¶éƒ¨ï¼‰
      statDimension: 'dept',
      statDeptWard: 'inherit',
      statDimension: "ward",
      statDeptWard: "inherit",
      // ç»Ÿä¸€æŽ¥å£è¿”回的数据(模拟)
      // æŽ¥å£è¿”回的原始数据(已归一化的题目列表)
      questionList: [],
      deptComparison: [],
      wardComparison: [],
      // è¡¨ä¸‰ï¼šç§‘室/病区评分对比(rows=行数据,questions=动态列)
      deptComparison: { rows: [], questions: [] },
      wardComparison: { rows: [], questions: [] },
    };
  },
  computed: {
    // å›ºå®šäº”项选项的题目 => è¡¨ä¸€
    // ç§‘室选项:优先取当前用户所属科室,无则兜底
    deptOptions() {
      const list = this.$store.getters.belongDepts || [];
      return list.length
        ? list.map((d) => ({ label: d.deptName, value: d.deptCode }))
        : DEPTS.map((d) => ({ label: d, value: d }));
    },
    // ç—…区选项:优先取当前用户所属病区,无则兜底
    wardOptions() {
      const list = this.$store.getters.belongWards || [];
      return list.length
        ? list.map((w) => ({ label: w.districtName, value: w.districtCode }))
        : WARDS.map((w) => ({ label: w, value: w }));
    },
    // æœåŠ¡ç±»åž‹ä¸‹æ‹‰ï¼šå– store çš„随访类型字典 tasktypes,取不到时兜底本地 SERVICE_TYPES
    serviceTypeOptions() {
      const toOptions = (list) =>
        (Array.isArray(list) ? list : [])
          .map((i) => ({
            label: String(i.label || "").trim(),
            value: String(i.value),
          }))
          .filter((i) => i.label && i.value && i.value !== "undefined");
      const taskTypes = toOptions(this.$store.getters.tasktypes);
      return taskTypes.length ? taskTypes : SERVICE_TYPES;
    },
    // æœªé€‰ç—…区时的默认查询范围:全部病区编码
    defaultWardCodes() {
      return Array.from(new Set(this.wardOptions.map((w) => w.value)));
    },
    // æœªé€‰ç§‘室时的默认查询范围:全部科室编码
    defaultDeptCodes() {
      return Array.from(new Set(this.deptOptions.map((d) => d.value)));
    },
    // å›ºå®šå…­é¡¹é€‰é¡¹çš„题目 => è¡¨ä¸€
    satisfactionQuestions() {
      return this.questionList.filter((q) => this.isSatisfactionQuestion(q));
    },
@@ -256,54 +563,67 @@
    },
    // è¡¨ä¸€ï¼šæ»¡æ„åº¦ç»Ÿè®¡ï¼ˆå«æ€»è®¡è¡Œï¼‰
    serviceSatisfaction() {
      // å–指定选项的 { count, score },未返回则该项为 0
      const pick = (q, name) => {
        const hit = q.options.find((o) => o.option === name);
        return hit
          ? { count: Number(hit.count) || 0, score: hit.score }
          : { count: 0, score: null };
      };
      const NAMES = FIXED_OPTIONS;
      const rows = this.satisfactionQuestions.map((q) => {
        const m = {};
        q.options.forEach((o) => (m[o.option] = o.count));
        const veryGood = m['非常满意'] || 0;
        const good = m['满意'] || 0;
        const normal = m['一般'] || 0;
        const bad = m['不满意'] || 0;
        const na = m['不适用'] || 0;
        const total = veryGood + good + normal + bad + na;
        const veryGood = pick(q, "很满意");
        const good = pick(q, "满意");
        const normal = pick(q, "一般");
        const bad = pick(q, "不满意");
        const veryBad = pick(q, "很不满意");
        const na = pick(q, "未经历");
        const items = [veryGood, good, normal, bad, veryBad, na];
        const total = items.reduce((s, it) => s + it.count, 0);
        return {
          category: q.title,
          veryGoodCount: veryGood,
          veryGoodRate: this.rateOf(veryGood, total),
          goodCount: good,
          goodRate: this.rateOf(good, total),
          normalCount: normal,
          normalRate: this.rateOf(normal, total),
          badCount: bad,
          badRate: this.rateOf(bad, total),
          naCount: na,
          naRate: this.rateOf(na, total),
          score: this.scoreOf([veryGood, good, normal, bad, na]),
          veryGoodCount: veryGood.count,
          veryGoodRate: this.rateOf(veryGood.count, total),
          goodCount: good.count,
          goodRate: this.rateOf(good.count, total),
          normalCount: normal.count,
          normalRate: this.rateOf(normal.count, total),
          badCount: bad.count,
          badRate: this.rateOf(bad.count, total),
          veryBadCount: veryBad.count,
          veryBadRate: this.rateOf(veryBad.count, total),
          naCount: na.count,
          naRate: this.rateOf(na.count, total),
          score: this.scoreOfItems(items),
        };
      });
      // æ€»è®¡è¡Œ
      const sum = { veryGood: 0, good: 0, normal: 0, bad: 0, na: 0 };
      rows.forEach((r) => {
        sum.veryGood += r.veryGoodCount;
        sum.good += r.goodCount;
        sum.normal += r.normalCount;
        sum.bad += r.badCount;
        sum.na += r.naCount;
      // æ€»è®¡è¡Œï¼šå„题同选项数量累加,分值沿用该选项分值
      const sum = NAMES.map(() => ({ count: 0, score: null }));
      this.satisfactionQuestions.forEach((q) => {
        NAMES.forEach((name, i) => {
          const it = pick(q, name);
          sum[i].count += it.count;
          if (sum[i].score == null) sum[i].score = it.score;
        });
      });
      const total = sum.veryGood + sum.good + sum.normal + sum.bad + sum.na;
      const total = sum.reduce((s, it) => s + it.count, 0);
      rows.push({
        category: '总计',
        veryGoodCount: sum.veryGood,
        veryGoodRate: this.rateOf(sum.veryGood, total),
        goodCount: sum.good,
        goodRate: this.rateOf(sum.good, total),
        normalCount: sum.normal,
        normalRate: this.rateOf(sum.normal, total),
        badCount: sum.bad,
        badRate: this.rateOf(sum.bad, total),
        naCount: sum.na,
        naRate: this.rateOf(sum.na, total),
        score: this.scoreOf([sum.veryGood, sum.good, sum.normal, sum.bad, sum.na]),
        category: "总计",
        veryGoodCount: sum[0].count,
        veryGoodRate: this.rateOf(sum[0].count, total),
        goodCount: sum[1].count,
        goodRate: this.rateOf(sum[1].count, total),
        normalCount: sum[2].count,
        normalRate: this.rateOf(sum[2].count, total),
        badCount: sum[3].count,
        badRate: this.rateOf(sum[3].count, total),
        veryBadCount: sum[4].count,
        veryBadRate: this.rateOf(sum[4].count, total),
        naCount: sum[5].count,
        naRate: this.rateOf(sum[5].count, total),
        score: this.scoreOfItems(sum),
      });
      return rows;
    },
@@ -322,138 +642,359 @@
        };
      });
    },
    // è¡¨äºŒï¼šæŒ‰é¢˜ç›®æ¨¡ç³ŠæŸ¥è¯¢ï¼ˆçº¯å‰ç«¯è¿‡æ»¤ï¼Œä¸è¯·æ±‚后端)
    filteredQuestionStats() {
      const kw = (this.questionSearch || "").trim().toLowerCase();
      if (!kw) return this.questionStats;
      return this.questionStats.filter((q) =>
        (q.title || "").toLowerCase().indexOf(kw) > -1
      );
    },
    // è¡¨ä¸‰ï¼šæŒ‰ç»´åº¦ + ç‹¬ç«‹æ¡ä»¶è¿‡æ»¤åŽçš„行
    table3Rows() {
      const list = this.statDimension === 'dept' ? this.deptComparison : this.wardComparison;
      const f = this.statFilter();
      if (!f) return list;
      const [type, name] = f.split(':');
      if (type !== this.statDimension) return list;
      return list.filter((r) => r.name === name);
      const comp =
        this.statDimension === "dept"
          ? this.deptComparison
          : this.wardComparison;
      const list = (comp && comp.rows) || [];
      const names = this.statFilterNames();
      if (!names || !names.length) return list;
      return list.filter((r) => names.indexOf(r.name) > -1);
    },
    // è¡¨ä¸‰ï¼šå½“前维度下的题目列(动态列)
    table3Questions() {
      const comp =
        this.statDimension === "dept"
          ? this.deptComparison
          : this.wardComparison;
      return (comp && comp.questions) || [];
    },
  },
  mounted() {
  created() {
    if (!this.queryParams.dateRange || !this.queryParams.dateRange.length) {
      this.queryParams.dateRange = this.defaultRange();
    }
    this.queryParams.serviceTypeList = this.defaultServiceTypes();
  },
  mounted() {
    this.fetchData();
  },
  methods: {
    // è¡¨ä¸‰ç‹¬ç«‹æ¡ä»¶ï¼š'inherit' æ—¶æ²¿ç”¨é¡¶éƒ¨æ¡ä»¶
    statFilter() {
      const v = this.statDeptWard === 'inherit' ? this.queryParams.deptWard : this.statDeptWard;
      return v || '';
    // é»˜è®¤é€‰ä¸­çš„æœåŠ¡ç±»åž‹ï¼ˆæŒ‰ label åŒ¹é…ï¼Œå–不到就返回空数组 = ä¸é™ï¼‰
    defaultServiceTypes() {
      const hit = DEFAULT_SERVICE_LABELS.map((label) => {
        const item = this.serviceTypeOptions.find((o) => o.label === label);
        return item ? item.value : null;
      }).filter((v) => v != null);
      return Array.from(new Set(hit));
    },
    // è¡¨ä¸‰ç‹¬ç«‹æ¡ä»¶ï¼š'inherit' æ—¶æ²¿ç”¨é¡¶éƒ¨æ‰€é€‰ï¼›è¿”回选中的名称数组(接口只回名称),空数组表示不限
    statFilterNames() {
      const opts =
        this.statDimension === "dept" ? this.deptOptions : this.wardOptions;
      const nameOf = (code) => {
        const hit = opts.find((o) => String(o.value) === String(code));
        return hit ? hit.label : null;
      };
      if (this.statDeptWard === "inherit") {
        const codes =
          this.statDimension === "dept"
            ? this.queryParams.deptCodes
            : this.queryParams.wardCodes;
        return (codes || []).map(nameOf).filter(Boolean);
      }
      // åˆ‡æ¢ç»´åº¦åŽï¼Œè‹¥å·²é€‰æ¡ä»¶ä¸å±žäºŽå½“前维度则视为不限
      if (this.statDeptWard.startsWith(this.statDimension + ":")) {
        const name = nameOf(this.statDeptWard.slice(this.statDimension.length + 1));
        return name ? [name] : [];
      }
      return [];
    },
    onStatDimensionChange() {
      // åˆ‡æ¢ç»´åº¦åŽï¼Œè‹¥å½“前已选条件不属于新维度,则恢复“沿用顶部”
      if (this.statDeptWard !== 'inherit' && !this.statDeptWard.startsWith(this.statDimension + ':')) {
        this.statDeptWard = 'inherit';
      if (
        this.statDeptWard !== "inherit" &&
        !this.statDeptWard.startsWith(this.statDimension + ":")
      ) {
        this.statDeptWard = "inherit";
      }
    },
    handleReset() {
      this.queryParams.dateRange = this.defaultRange();
      this.queryParams.deptWard = '';
      this.queryParams.deptCodes = [];
      this.queryParams.wardCodes = [];
      this.queryParams.statMode = "ward";
      this.statDimension = "ward";
      this.statDeptWard = "inherit";
      this.queryParams.serviceTypeList = this.defaultServiceTypes();
      this.fetchData();
    },
    // åˆ¤æ–­é¢˜ç›®é€‰é¡¹æ˜¯å¦ä¸ºå›ºå®šæ»¡æ„åº¦äº”项
    // åˆ‡æ¢ç»Ÿè®¡ç»´åº¦ï¼ˆç§‘室/病区)时清空另一维度的选择,保证互斥
    onStatModeChange() {
      if (this.queryParams.statMode === "dept") {
        this.queryParams.wardCodes = [];
      } else {
        this.queryParams.deptCodes = [];
      }
      // å•向同步到「各科室/病区满意度评分对比」表:顶部 â†’ è¡¨ä¸‰
      this.statDimension = this.queryParams.statMode;
      this.onStatDimensionChange();
    },
    // åˆ¤æ–­é¢˜ç›®æ˜¯å¦ä¸ºæ»¡æ„åº¦å…­é¡¹é€‰é¡¹é¢˜ï¼šæ‰€æœ‰é€‰é¡¹éƒ½è½åœ¨å›ºå®šå…­é¡¹å†…即归表一
    isSatisfactionQuestion(q) {
      if (q.options.length !== FIXED_OPTIONS.length) return false;
      const set = q.options.map((o) => o.option);
      return FIXED_OPTIONS.every((o) => set.includes(o));
      if (!q.options || !q.options.length) return false;
      return q.options.every((o) => FIXED_OPTIONS.includes(o.option));
    },
    questionTotal(q) {
      if (q.total != null) return q.total;
      return q.options.reduce((s, o) => s + o.count, 0);
    },
    rateOf(count, total) {
      if (!total) return '0.00%';
      return ((count / total) * 100).toFixed(2) + '%';
      if (!total) return "0.00%";
      return ((count / total) * 100).toFixed(2) + "%";
    },
    // ç»¼åˆå¾—分:非常满意5分、满意4分、一般3分、不满意2分,换算百分制(不适用不计入)——占位公式,接口就绪后可按后端口径替换
    scoreOf(counts) {
      const [a, b, c, d] = counts;
      const total = a + b + c + d;
      if (!total) return '0.00';
      return (((a * 5 + b * 4 + c * 3 + d * 2) / total) * 20).toFixed(2);
    /**
     * ç»¼åˆå¾—分(真实口径):Σ(各选项分值 Ã— å¡«æŠ¥é‡) / Î£(填报量)
     * items: [{ count, score }],score å–接口返回的 score å­—段
     * æ— åˆ†å€¼é€‰é¡¹ï¼ˆå¦‚"未经历")默认不计入分子与分母,避免拉低得分
     */
    scoreOfItems(items) {
      let numerator = 0;
      let denominator = 0;
      items.forEach((it) => {
        const count = Number(it && it.count) || 0;
        const score = Number(it && it.score);
        const hasScore = !isNaN(score);
        if (!hasScore && !NO_SCORE_IN_DENOMINATOR) return;
        numerator += (hasScore ? score : 0) * count;
        denominator += count;
      });
      if (!denominator) return "0.00";
      return (numerator / denominator).toFixed(2);
    },
    defaultRange() {
      const end = new Date();
      const start = new Date();
      start.setDate(start.getDate() - 29);
      start.setDate(start.getDate() - 91);
      const f = (d) => {
        const y = d.getFullYear();
        const m = String(d.getMonth() + 1).padStart(2, '0');
        const day = String(d.getDate()).padStart(2, '0');
        const m = String(d.getMonth() + 1).padStart(2, "0");
        const day = String(d.getDate()).padStart(2, "0");
        return `${y}-${m}-${day}`;
      };
      return [f(start), f(end)];
    },
    dateRangeDays(range) {
      if (Array.isArray(range) && range.length === 2 && range[0] && range[1]) {
        const ms = new Date(range[1]).getTime() - new Date(range[0]).getTime();
        return Math.max(1, Math.round(ms / 86400000));
    // æç¤ºä¿¡æ¯ï¼ˆå…¼å®¹ $modal ä¸Ž $message)
    notify(msg, type) {
      const map = {
        warning: ["msgWarning", "warning"],
        error: ["msgError", "error"],
        success: ["msgSuccess", "success"],
      };
      const [modalFn, msgFn] = map[type] || map.success;
      if (this.$modal && this.$modal[modalFn]) {
        this.$modal[modalFn](msg);
      } else if (this.$message && this.$message[msgFn]) {
        this.$message[msgFn](msg);
      }
      return 30;
    },
    // æ‹‰å–数据(当前为模拟数据,接口就绪后替换为真实请求)
    fetchData() {
      const data = this.mockFetch(this.queryParams);
      this.questionList = data.questionList;
      this.deptComparison = data.deptComparison;
      this.wardComparison = data.wardComparison;
    },
    mockFetch(params) {
      const seed = hashString(JSON.stringify(params));
      const rnd = mulberry32(seed);
      const days = this.dateRangeDays(params.dateRange);
      const timeFactor = Math.max(0.05, days / 30);
      const scopeFactor = params.deptWard ? 0.18 : 1;
      const jitter = () => 0.9 + rnd() * 0.2;
    // æ‹‰å–满意度统计数据(接口:/smartor/serviceSubtaskDetail/sltdMydTotalByScore)
    async fetchData() {
      const range = this.queryParams.dateRange || [];
      if (range.length !== 2 || !range[0] || !range[1]) {
        this.notify("请先选择出院日期范围", "warning");
        return;
      }
      const questionList = RAW_QUESTIONS.map((q) => {
        const options = q.options.map((o) => ({
          option: o.option,
          count: Math.round(o.count * timeFactor * scopeFactor * jitter()),
        }));
        const item = { title: q.title, type: q.type, options };
        if (q.type === 'multiple' && q.total != null) {
          item.total = Math.round(q.total * timeFactor * scopeFactor);
      // ç—…区 => leavehospitaldistrictcodes,科室 => leaveldeptcodes
      // ç§‘室/病区互斥:按病区只传 leavehospitaldistrictcodes,按科室只传 leaveldeptcodes
      const wards = this.queryParams.wardCodes || [];
      const depts = this.queryParams.deptCodes || [];
      const payload = {
        deptOrDistrict: this.queryParams.deptOrDistrict || "1",
        startFinishTime: range[0],
        endFinishTime: range[1],
      };
      if (this.queryParams.statMode === "dept") {
        payload.leaveldeptcodes = depts.length
          ? Array.from(new Set(depts))
          : this.defaultDeptCodes;
      } else {
        payload.leavehospitaldistrictcodes = wards.length
          ? Array.from(new Set(wards))
          : this.defaultWardCodes;
      }
      // æœåŠ¡ç±»åž‹å¤šé€‰ï¼šé€—å·åˆ†éš”ï¼ˆæŽ¥å£å­—æ®µä¸º serviceType å­—符串)
      const serviceTypes = this.queryParams.serviceTypeList || [];
      if (serviceTypes.length) payload.serviceType = serviceTypes.join(",");
      payload.serviceTypeList = serviceTypes;
      this.loading = true;
      try {
        const res = await sltdMydTotalByScore(payload);
        // æŽ’查用:打印接口原始返回,便于核对字段名(确认无误后可删除)
        console.log(
          "[sltdMydTotalByScore] code=",
          res && res.code,
          "data=",
          JSON.stringify(res && res.data).slice(0, 2000)
        );
        if (res && res.code === 200) {
          this.questionList = normalizeQuestions(res.data);
          if (!this.questionList.length)
            this.notify("当前条件下暂无统计数据", "warning");
        } else {
          this.questionList = [];
          this.notify((res && res.msg) || "统计数据获取失败", "error");
        }
        return item;
        // è¡¨ä¸‰ï¼šå„科室/病区满意度评分对比(真实接口,内部自行处理错误)
        await this.fetchComparison();
      } catch (e) {
        this.questionList = [];
        this.notify("统计数据请求失败,请稍后重试", "error");
      } finally {
        this.loading = false;
      }
    },
    // æ‹‰å–各科室/病区满意度评分对比(接口:/smartor/serviceSubtaskDetail/statQuestionOptionByKsOrBq)
    async fetchComparison() {
      const range = this.queryParams.dateRange || [];
      if (range.length !== 2 || !range[0] || !range[1]) {
        this.deptComparison = { rows: [], questions: [] };
        this.wardComparison = { rows: [], questions: [] };
        return;
      }
      const base = {
        deptOrDistrict: this.queryParams.deptOrDistrict || "1",
        startFinishTime: range[0],
        endFinishTime: range[1],
      };
      try {
        // æŒ‰ç§‘室统计(leaveldeptcodes ä¼˜å…ˆï¼‰ä¸ŽæŒ‰ç—…区统计(leavehospitaldistrictcodes)各查一次
        const [deptRes, wardRes] = await Promise.all([
          statQuestionOptionByKsOrBq({ ...base, leaveldeptcodes: this.defaultDeptCodes }),
          statQuestionOptionByKsOrBq({ ...base, leavehospitaldistrictcodes: this.defaultWardCodes }),
        ]);
        // æŽ’查用:打印接口原始返回,便于核对两级嵌套 Map ç»“构(确认无误后可删除)
        console.log(
          "[statQuestionOptionByKsOrBq] dept code=",
          deptRes && deptRes.code,
          "data=",
          JSON.stringify(deptRes && deptRes.data).slice(0, 2000),
          "| ward code=",
          wardRes && wardRes.code,
          "data=",
          JSON.stringify(wardRes && wardRes.data).slice(0, 2000)
        );
        this.deptComparison = this.buildComparison(
          deptRes && deptRes.code === 200 ? deptRes.data : {},
          "dept"
        );
        this.wardComparison = this.buildComparison(
          wardRes && wardRes.code === 200 ? wardRes.data : {},
          "ward"
        );
      } catch (e) {
        this.deptComparison = { rows: [], questions: [] };
        this.wardComparison = { rows: [], questions: [] };
        console.error("获取科室/病区评分对比失败:", e);
      }
    },
    // ç»„装为表三所需的行/列结构:行=科室/病区,列=题目×选项 + å¾—分 + ç»¼åˆå¾—分
    // æ–°æŽ¥å£è¿”回两级嵌套 Map:{ ç§‘室/病区名称: { é¢˜ç›®å†…容: [QuestionCountKsOrBqDTO] } }
    // å…¼å®¹æ—§æ‰å¹³æ•°ç»„:[{ questionText, deptname, leavehospitaldistrictname, targetvalue, answerCount, singleScore }]
    buildComparison(data, mode) {
      const questions = [];
      const qMap = new Map();
      const rowMap = new Map();
      // å°†ä¸€æ¡ (名称, é¢˜ç›®, é€‰é¡¹, æ•°é‡, å¾—分) æ˜Žç»†ç´¯åŠ è¿›è¡Œ/列结构
      const collect = (name, title, option, count, single) => {
        if (!title || !name) return;
        let q = qMap.get(title);
        if (!q) {
          q = { title, options: [] };
          qMap.set(title, q);
          questions.push(q);
        }
        if (option && q.options.indexOf(option) === -1) q.options.push(option);
        let row = rowMap.get(name);
        if (!row) {
          row = { name, code: this.codeFor(mode, name), cells: {} };
          rowMap.set(name, row);
        }
        if (!row.cells[title]) row.cells[title] = {};
        const cell = row.cells[title][option] || { count: 0, score: 0 };
        cell.count += count;
        cell.score += single;
        row.cells[title][option] = cell;
      };
      if (Array.isArray(data)) {
        // æ—§ç»“构:扁平数组
        data.forEach((it) => {
          const title = it.questionText || it.qeustionText || "";
          const name =
            mode === "dept"
              ? it.deptname || ""
              : it.leavehospitaldistrictname || "";
          const option = it.targetvalue || it.asrtext || "";
          collect(name, title, option, Number(it.answerCount) || 0, Number(it.singleScore) || 0);
        });
      } else if (data && typeof data === "object") {
        // æ–°ç»“构:两级嵌套 Map,第一层 key=科室/病区名称,第二层 key=题目,value=选项明细列表
        Object.keys(data).forEach((name) => {
          const questionMap = data[name] || {};
          Object.keys(questionMap).forEach((title) => {
            const list = Array.isArray(questionMap[title]) ? questionMap[title] : [];
            list.forEach((it) => {
              const option = it.targetvalue || it.asrtext || "";
              collect(name, title, option, Number(it.answerCount) || 0, Number(it.singleScore) || 0);
            });
          });
        });
      }
      const rows = Array.from(rowMap.values());
      rows.forEach((row) => {
        let totalCount = 0;
        let totalScore = 0;
        questions.forEach((q) => {
          const cells = row.cells[q.title] || {};
          let qCount = 0;
          let qScore = 0;
          q.options.forEach((opt) => {
            const c = cells[opt];
            if (c) {
              qCount += c.count;
              qScore += c.score;
            }
          });
          // è¯¥é¢˜ç›®åŠ æƒå¹³å‡åˆ†ï¼šÎ£ singleScore / Î£ answerCount
          cells._score = qCount ? (qScore / qCount).toFixed(2) : "0.00";
          totalCount += qCount;
          totalScore += qScore;
          row.cells[q.title] = cells;
        });
        row.totalScore = totalCount ? (totalScore / totalCount).toFixed(2) : "0.00";
      });
      const deptComparison = DEPTS.map((d) => this.genCompareRow(d, rnd));
      const wardComparison = WARDS.map((w) => this.genCompareRow(w, rnd));
      return { questionList, deptComparison, wardComparison };
      return { rows, questions };
    },
    genCompareRow(name, rnd) {
      const r = (lo, hi) => Math.round(lo + rnd() * (hi - lo));
      const q1VeryGood = r(20, 180);
      const q1Good = r(5, 55);
      const q1Normal = r(0, 4);
      const q1Bad = r(0, 3);
      const q2VeryGood = r(18, 175);
      const q2Good = r(5, 55);
      const q2Normal = r(0, 4);
      const q2Bad = r(0, 3);
      return {
        name,
        q1VeryGood, q1Good, q1Normal, q1Bad,
        q1Score: this.scoreOf([q1VeryGood, q1Good, q1Normal, q1Bad]),
        q2VeryGood, q2Good, q2Normal, q2Bad,
        q2Score: this.scoreOf([q2VeryGood, q2Good, q2Normal, q2Bad]),
        totalScore: this.scoreOf([
          q1VeryGood + q2VeryGood,
          q1Good + q2Good,
          q1Normal + q2Normal,
          q1Bad + q2Bad,
        ]),
      };
    // åç§° â†’ ç¼–码(用于表三按顶部所选编码过滤)
    codeFor(mode, name) {
      const opts = mode === "dept" ? this.deptOptions : this.wardOptions;
      const hit = opts.find((o) => String(o.label) === String(name));
      return hit ? hit.value : name;
    },
    exportTable(tableId) {
      const tableNames = { table1: '各服务项目满意度统计', table2: '单选题多选题统计', table3: '各科室满意度评分对比' };
      const name = tableNames[tableId] || '统计数据';
      const tableNames = {
        table1: "各服务项目满意度统计",
        table2: "单选题多选题统计",
        table3: "各科室满意度评分对比",
      };
      const name = tableNames[tableId] || "统计数据";
      const table = document.getElementById(tableId);
      if (!table) return;
@@ -461,10 +1002,10 @@
        // ä»Žè¡¨æ ¼æå–数据构建带样式的 Excel
        const wb = XLSX.utils.book_new();
        const data = [];
        table.querySelectorAll('tr').forEach((tr) => {
        table.querySelectorAll("tr").forEach((tr) => {
          const row = [];
          tr.querySelectorAll('th,td').forEach((td) => {
            row.push((td.innerText || '').trim());
          tr.querySelectorAll("th,td").forEach((td) => {
            row.push((td.innerText || "").trim());
          });
          if (row.length) data.push(row);
        });
@@ -472,22 +1013,24 @@
        const ws = XLSX.utils.aoa_to_sheet(data);
        // è®¾ç½®åˆ—宽
        const colWidths = data[0] ? data[0].map(() => ({ wch: 16 })) : [];
        ws['!cols'] = colWidths;
        ws["!cols"] = colWidths;
        XLSX.utils.book_append_sheet(wb, ws, 'Sheet1');
        XLSX.writeFile(wb, name + '.xlsx');
        XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
        XLSX.writeFile(wb, name + ".xlsx");
      } catch (e) {
        // fallback: CSV
        let csv = '';
        table.querySelectorAll('tr').forEach((tr) => {
        let csv = "";
        table.querySelectorAll("tr").forEach((tr) => {
          const row = [];
          tr.querySelectorAll('th,td').forEach((td) => row.push('"' + (td.innerText || '').replace(/"/g, '""') + '"'));
          csv += row.join(',') + '\n';
          tr.querySelectorAll("th,td").forEach((td) =>
            row.push('"' + (td.innerText || "").replace(/"/g, '""') + '"')
          );
          csv += row.join(",") + "\n";
        });
        const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
        const link = document.createElement('a');
        const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
        const link = document.createElement("a");
        link.href = URL.createObjectURL(blob);
        link.download = name + '.csv';
        link.download = name + ".csv";
        link.click();
      }
    },
@@ -496,13 +1039,66 @@
</script>
<style lang="scss" scoped>
.data-overview { .overview-card { margin-bottom: 20px; } }
.filter-card { .filter-form { margin-bottom: -10px; } .filter-select { width: 200px; } }
.card-header { display: flex; justify-content: space-between; align-items: center; }
.card-title { font-size: 16px; font-weight: 600; color: #303133; }
.header-right { display: flex; align-items: center; .stat-select { width: 180px; margin: 0 12px; } }
.question-block { margin-bottom: 24px;
  .q-title { margin: 0 0 12px 0; font-size: 14px; color: #606266; }
  .q-footer { margin-top: 8px; font-size: 13px; color: #909399; text-align: right; }
.data-overview {
  .overview-card {
    margin-bottom: 20px;
  }
}
.filter-card {
  .filter-form {
    margin-bottom: -10px;
  }
  .filter-select {
    width: 220px;
  }
  .filter-select-sm {
    width: 150px;
  }
}
.empty-tip {
  padding: 40px 0;
  text-align: center;
  color: #909399;
  font-size: 13px;
}
.table2-scroll {
  max-height: 560px;
  overflow-y: auto;
}
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.card-title {
  font-size: 16px;
  font-weight: 600;
  color: #303133;
}
.header-right {
  display: flex;
  align-items: center;
  .stat-select {
    width: 180px;
    margin: 0 12px;
  }
  .search-input {
    width: 200px;
    margin-right: 12px;
  }
}
.question-block {
  margin-bottom: 24px;
  .q-title {
    margin: 0 0 12px 0;
    font-size: 14px;
    color: #606266;
  }
  .q-footer {
    margin-top: 8px;
    font-size: 13px;
    color: #909399;
    text-align: right;
  }
}
</style>
src/views/Satisfaction/sfstatistics/components/FollowupStatistics.vue
@@ -354,7 +354,7 @@
        statisticaltype: 1,
        leavehospitaldistrictcodes: ["all"],
        deptcodes: [],
        serviceType: [],
        serviceType: [2],
        dateRange: [],
        pageNum: 1,
        pageSize: 20,
@@ -374,23 +374,23 @@
      // æœåŠ¡ç±»åž‹é€‰é¡¹
      options: [
        {
          value: 6,
          label: "住院满意度",
          raw: {
            cssClass: "",
            listClass: "",
          },
        },
        // {
        //   value: 6,
        //   label: "住院满意度",
        //   raw: {
        //     cssClass: "",
        //     listClass: "",
        //   },
        // },
        {
          value: 14,
          label: "门诊满意度",
          raw: {
            cssClass: "",
            listClass: "",
          },
        },
        // {
        //   value: 14,
        //   label: "门诊满意度",
        //   raw: {
        //     cssClass: "",
        //     listClass: "",
        //   },
        // },
      ],
      // è¡¨æ ¼æ•°æ®
@@ -475,7 +475,7 @@
    // èŽ·å–ç§‘å®¤æ ‘
    getDeptTree() {
      // èŽ·å–æœåŠ¡ç±»åž‹
      // this.options = this.$store.getters.tasktypes || [];
      this.options = this.$store.getters.tasktypes || [];
      // èŽ·å–ç§‘å®¤åˆ—è¡¨
      this.flatArraydept = (this.$store.getters.belongDepts || []).map(
src/views/Satisfaction/sfstatistics/components/SatisfactionStatistics.vue
@@ -141,6 +141,8 @@
          <div
            id="satisfactionBarChart"
            v-loading="loading"
            element-loading-text="统计加载中..."
            style="width: 100%; height: 400px"
          ></div>
        </div>
@@ -858,6 +860,12 @@
      }
      return this.wardList.map((ward) => ward.value);
    },
    // é—®é¢˜ç»´åº¦é›†åˆï¼šä»Ž dimensionality_type å­—典取维度编码,查维度时必传
    dimensionList() {
      const dict = this.dict && this.dict.type && this.dict.type.dimensionality_type;
      return dict ? dict.map((d) => String(d.value)) : [];
    },
  },
  mounted() {
@@ -978,6 +986,9 @@
        deptcodes: this.deptCodes, // ä½¿ç”¨è®¡ç®—属性
        hospitaldistrictcodes: this.hospitalDistrictCodes, // ä½¿ç”¨è®¡ç®—属性
        templateid: this.templateId,
        questionType: this.queryParams.type || null,
        serviceTypes: this.defaultServiceTypes,
        dimensionList: this.dimensionList, // é—®é¢˜ç»´åº¦é›†åˆï¼ŒæŸ¥ç»´åº¦æ—¶å¿…ä¼ 
      };
      const response = await satisfactionGraphDimension(params);
@@ -1153,14 +1164,14 @@
        return {
          scriptContent: item.scriptContent || "",
          scriptType: 1,
          answerPerson: item.answerPerson || 0,
          noAnswerPerson: item.noAnswerPerson || 0,
          answerCount: item.answerPerson || 0,
          averageScore: item.averageScore || 0,
          maxScore: item.maxScore || 0,
          minScore: item.minScore || 0,
          answerRate: item.answerRate || 0,
          totalCount: (item.answerPerson || 0) + (item.noAnswerPerson || 0),
          answerPerson: Number(item.answerPerson) || 0,
          noAnswerPerson: Number(item.noAnswerPerson) || 0,
          answerCount: Number(item.answerPerson) || 0,
          averageScore: Number(item.averageScore) || 0,
          maxScore: Number(item.maxScore) || 0,
          minScore: Number(item.minScore) || 0,
          answerRate: Number(item.answerRate) || 0,
          totalCount: (Number(item.answerPerson) || 0) + (Number(item.noAnswerPerson) || 0),
          options: options,
        };
      });
@@ -1189,6 +1200,7 @@
          templateid: this.templateId,
          questionType: this.queryParams.type || null,
          serviceTypes: this.defaultServiceTypes,
          dimensionList: this.dimensionList, // é—®é¢˜ç»´åº¦é›†åˆï¼ŒæŸ¥ç»´åº¦æ—¶å¿…ä¼ 
        };
        const response = await statisticsByDimension(params);
@@ -1238,14 +1250,14 @@
          dimension: item.dimension || "0", // ç»´åº¦ä»£ç 
          dimensionName: this.getDimensionLabel(item.dimension || "0"), // ç»´åº¦åç§°
          scriptType: 1,
          answerPerson: item.answerPerson || 0,
          noAnswerPerson: item.noAnswerPerson || 0,
          answerCount: item.answerPerson || 0,
          averageScore: item.averageScore || 0,
          maxScore: item.maxScore || 0,
          minScore: item.minScore || 0,
          answerRate: item.answerRate || 0,
          totalCount: (item.answerPerson || 0) + (item.noAnswerPerson || 0),
          answerPerson: Number(item.answerPerson) || 0,
          noAnswerPerson: Number(item.noAnswerPerson) || 0,
          answerCount: Number(item.answerPerson) || 0,
          averageScore: Number(item.averageScore) || 0,
          maxScore: Number(item.maxScore) || 0,
          minScore: Number(item.minScore) || 0,
          answerRate: Number(item.answerRate) || 0,
          totalCount: (Number(item.answerPerson) || 0) + (Number(item.noAnswerPerson) || 0),
          options: options,
        };
      });
src/views/followvisit/Continue/index.vue
@@ -1461,8 +1461,10 @@
      this.getList(1);
    },
    onthatday() {
      this.topqueryParams.startSendDateTime = this.getCurrentDate();
      this.topqueryParams.endSendDateTime = this.getCurrentDate();
      this.topqueryParams.sendstateView = 1; // å¾…随访
      this.topqueryParams.sendstate = null;
      this.topqueryParams.startSendDateTime = null;
      this.topqueryParams.endSendDateTime = this.getCurrentDate(); // å¼€å§‹éšè®¿æ—¥æœŸ <= ä»Šå¤©
      this.getList(1);
    },
    getCurrentDate() {
@@ -1470,13 +1472,12 @@
      return now.toISOString().slice(0, 10); // æˆªå–前10个字符,即 YYYY-MM-DD
    },
    buidegetTasklist(type) {
      if (this.topqueryParams.searchscope == 3) {
        this.topqueryParams.leaveldeptcodes = store.getters.belongDepts.map(
          (obj) => obj.deptCode
        );
        this.topqueryParams.leavehospitaldistrictcodes =
          store.getters.belongWards.map((obj) => obj.districtCode);
      }
      // ç”¨æˆ·æƒé™ä¸‹æ‰€æœ‰å¾…随访任务:传递全部科室和病区
      this.topqueryParams.leaveldeptcodes = store.getters.belongDepts.map(
        (obj) => obj.deptCode
      );
      this.topqueryParams.leavehospitaldistrictcodes =
        store.getters.belongWards.map((obj) => obj.districtCode);
      // æŽ¥å—异常跳转
      if (this.errtype) {
        this.topqueryParams.leavehospitaldistrictcodes.push(
@@ -1488,7 +1489,7 @@
        pageSize: 10,
        leavehospitaldistrictcodes:
          this.topqueryParams.leavehospitaldistrictcodes,
        sendstates: [2, 3],
        sendstateView: 1,
        leaveldeptcodes: this.topqueryParams.leaveldeptcodes,
      };
      buidegetTasklist(obj).then((response) => {
src/views/followvisit/HistoricalFollow/index.vue
@@ -885,13 +885,12 @@
      });
    },
    buidegetTasklist(type) {
      if (this.topqueryParams.searchscope == 3) {
        this.topqueryParams.leaveldeptcodes = store.getters.belongDepts.map(
          (obj) => obj.deptCode
        );
        this.topqueryParams.leavehospitaldistrictcodes =
          store.getters.belongWards.map((obj) => obj.districtCode);
      }
      // ç”¨æˆ·æƒé™ä¸‹æ‰€æœ‰å¾…随访任务:传递全部科室和病区
      this.topqueryParams.leaveldeptcodes = store.getters.belongDepts.map(
        (obj) => obj.deptCode
      );
      this.topqueryParams.leavehospitaldistrictcodes =
        store.getters.belongWards.map((obj) => obj.districtCode);
      // æŽ¥å—异常跳转
      if (this.errtype) {
        this.topqueryParams.leavehospitaldistrictcodes.push(
@@ -903,7 +902,7 @@
        pageSize: 10,
        leavehospitaldistrictcodes:
          this.topqueryParams.leavehospitaldistrictcodes,
        sendstates: [2, 3],
        sendstateView: 1,
        leaveldeptcodes: this.topqueryParams.leaveldeptcodes,
      };
      buidegetTasklist(obj).then((response) => {
@@ -1101,8 +1100,10 @@
      });
    },
    onthatday() {
      this.topqueryParams.startSendDateTime = this.getCurrentDate();
      this.topqueryParams.endSendDateTime = this.getCurrentDate();
      this.topqueryParams.sendstateView = 1; // å¾…随访
      this.topqueryParams.sendstate = null;
      this.topqueryParams.startSendDateTime = null;
      this.topqueryParams.endSendDateTime = this.getCurrentDate(); // å¼€å§‹éšè®¿æ—¥æœŸ <= ä»Šå¤©
      this.getList(1);
    },
    getCurrentDate() {
src/views/followvisit/OutpatientAgain/index.vue
@@ -173,7 +173,7 @@
            </div>
          </div>
        </el-col>
        <el-col :span="1.5">
        <!-- <el-col :span="1.5">
          <div class="documentf">
            <div class="document">
              <el-button
@@ -185,7 +185,7 @@
              >
            </div>
          </div>
        </el-col>
        </el-col> -->
        <el-col :span="1.5">
          <div class="documentf">
            <div class="document">
@@ -1015,7 +1015,7 @@
      topqueryParams: {
        pageNum: 1,
        pageSize: 10,
        sendstateView:2,
        sendstateView:1,
        sort: localStorage.getItem("orgname") == "丽水市中医院" ? 8 : 2, //0 æœåŠ¡æ—¶é—´(正序)    1 æœåŠ¡æ—¶é—´(倒序)   2 å‘送时间(正序)    3 å‘送时间(倒序)  7应随访日期(倒序) åº”随访日期(正序)
        serviceType: 3,
        searchscope: 3,
@@ -1230,8 +1230,10 @@
      this.getList(1);
    },
    onthatday() {
      this.topqueryParams.startSendDateTime = this.getCurrentDate();
      this.topqueryParams.endSendDateTime = this.getCurrentDate();
      this.topqueryParams.sendstateView = 1; // å¾…随访
      this.topqueryParams.sendstate = null;
      this.topqueryParams.startSendDateTime = null;
      this.topqueryParams.endSendDateTime = this.getCurrentDate(); // å¼€å§‹éšè®¿æ—¥æœŸ <= ä»Šå¤©
      this.getList(1);
    },
    getCurrentDate() {
@@ -1239,16 +1241,12 @@
      return now.toISOString().slice(0, 10); // æˆªå–前10个字符,即 YYYY-MM-DD
    },
    buidegetTasklist(type) {
      if (this.topqueryParams.searchscope == 3) {
        this.topqueryParams.visitDeptCodes = store.getters.belongDepts.map(
          (obj) => obj.deptCode
        );
        //   this.topqueryParams.leaveldeptcodes = store.getters.belongDepts.map(
        //   (obj) => obj.deptCode
        // );
        this.topqueryParams.leavehospitaldistrictcodes =
          store.getters.belongWards.map((obj) => obj.districtCode);
      }
      // ç”¨æˆ·æƒé™ä¸‹æ‰€æœ‰å¾…随访任务:传递全部科室和病区
      this.topqueryParams.visitDeptCodes = store.getters.belongDepts.map(
        (obj) => obj.deptCode
      );
      this.topqueryParams.leavehospitaldistrictcodes =
        store.getters.belongWards.map((obj) => obj.districtCode);
      // æŽ¥å—异常跳转
      if (this.errtype) {
        this.topqueryParams.leavehospitaldistrictcodes.push(
@@ -1260,7 +1258,7 @@
        pageSize: 10,
        leavehospitaldistrictcodes:
          this.topqueryParams.leavehospitaldistrictcodes,
        sendstates: [2, 3],
        sendstateView: 1,
        visitDeptCodes: this.topqueryParams.visitDeptCodes,
        // leaveldeptcodes: this.topqueryParams.leaveldeptcodes,
      };
@@ -1432,10 +1430,12 @@
    resetQuery() {
      this.dateRange = [];
      this.dateRangefs = [];
      this.serviceStatusValue = 10;
      this.topqueryParams = {
        pageNum: 1,
        pageSize: 10,
        sendstateView:2,
        sendstateView:1,
        sort: 2, //0 æœåŠ¡æ—¶é—´(正序)    1 æœåŠ¡æ—¶é—´(倒序)   2 å‘送时间(正序)    3 å‘送时间(倒序)
        serviceType: 3,
        searchscope: 3,
src/views/followvisit/Tracking/index.vue
@@ -168,7 +168,7 @@
            </div>
          </div>
        </el-col>
        <el-col :span="1.5">
        <!-- <el-col :span="1.5">
          <div class="documentf">
            <div class="document">
              <el-button
@@ -180,7 +180,7 @@
              >
            </div>
          </div>
        </el-col>
        </el-col> -->
        <el-col :span="1.5">
          <div class="documentf">
            <div class="document">
@@ -1007,7 +1007,7 @@
      topqueryParams: {
        pageNum: 1,
        pageSize: 10,
        sendstateView:2,
        sendstateView:1,
        sort: localStorage.getItem("orgname") == "丽水市中医院" ? 8 : 2, //0 å»¶ç»­æŠ¤ç†æ—¶é—´(正序)    1 å»¶ç»­æŠ¤ç†æ—¶é—´(倒序)   2 å‘送时间(正序)    3 å‘送时间(倒序)  7应随访日期(倒序) åº”随访日期(正序)
        serviceType: 3,
        searchscope: 3,
@@ -1222,8 +1222,10 @@
      this.getList(1);
    },
    onthatday() {
      this.topqueryParams.startSendDateTime = this.getCurrentDate();
      this.topqueryParams.endSendDateTime = this.getCurrentDate();
      this.topqueryParams.sendstateView = 1; // å¾…随访
      this.topqueryParams.sendstate = null;
      this.topqueryParams.startSendDateTime = null;
      this.topqueryParams.endSendDateTime = this.getCurrentDate(); // å¼€å§‹éšè®¿æ—¥æœŸ <= ä»Šå¤©
      this.getList(1);
    },
    getCurrentDate() {
@@ -1231,16 +1233,15 @@
      return now.toISOString().slice(0, 10); // æˆªå–前10个字符,即 YYYY-MM-DD
    },
    buidegetTasklist(type) {
      if (this.topqueryParams.searchscope == 3) {
        this.topqueryParams.visitDeptCodes = store.getters.belongDepts.map(
          (obj) => obj.deptCode
        );
          this.topqueryParams.leaveldeptcodes = store.getters.belongDepts.map(
          (obj) => obj.deptCode
        );
        this.topqueryParams.leavehospitaldistrictcodes =
          store.getters.belongWards.map((obj) => obj.districtCode);
      }
      // ç”¨æˆ·æƒé™ä¸‹æ‰€æœ‰å¾…随访任务:传递全部科室和病区
      this.topqueryParams.visitDeptCodes = store.getters.belongDepts.map(
        (obj) => obj.deptCode
      );
      this.topqueryParams.leaveldeptcodes = store.getters.belongDepts.map(
        (obj) => obj.deptCode
      );
      this.topqueryParams.leavehospitaldistrictcodes =
        store.getters.belongWards.map((obj) => obj.districtCode);
      // æŽ¥å—异常跳转
      if (this.errtype) {
        this.topqueryParams.leavehospitaldistrictcodes.push(
@@ -1252,7 +1253,7 @@
        pageSize: 10,
        leavehospitaldistrictcodes:
          this.topqueryParams.leavehospitaldistrictcodes,
        sendstates: [2, 3],
        sendstateView: 1,
        visitDeptCodes: this.topqueryParams.visitDeptCodes,
        leaveldeptcodes: this.topqueryParams.leaveldeptcodes,
      };
@@ -1424,10 +1425,12 @@
    resetQuery() {
      this.dateRange = [];
      this.dateRangefs = [];
      this.serviceStatusValue = 10;
      this.topqueryParams = {
        pageNum: 1,
        pageSize: 10,
        sendstateView:2,
        sendstateView:1,
        sort: 2, //0 å»¶ç»­æŠ¤ç†æ—¶é—´(正序)    1 å»¶ç»­æŠ¤ç†æ—¶é—´(倒序)   2 å‘送时间(正序)    3 å‘送时间(倒序)
        serviceType: 3,
        searchscope: 3,
src/views/followvisit/again/index.vue
@@ -173,7 +173,7 @@
            </div>
          </div>
        </el-col>
        <el-col :span="1.5">
        <!-- <el-col :span="1.5">
          <div class="documentf">
            <div class="document">
              <el-button
@@ -185,7 +185,7 @@
              >
            </div>
          </div>
        </el-col>
        </el-col> -->
        <el-col :span="1.5">
          <div class="documentf">
            <div class="document">
src/views/followvisit/beHospitalized/followUp.vue
@@ -1263,12 +1263,6 @@
        },
      ],
      loading: false,
      serviceStatusValue: 10,
      cascaderProps: {
        expandTrigger: "hover", // æ‚¬åœå±•å¼€
        checkStrictly: true, // âœ… å…³é”®ï¼šå…è®¸é€‰ä¸­ä»»æ„ä¸€çº§
        emitPath: false, // âœ… åªè¿”回选中的值,不返回路径数组
      },
      // éšè®¿çŠ¶æ€çº§è”é€‰æ‹©å™¨
      serviceStatusOptions: [
        {
@@ -1418,7 +1412,7 @@
      topqueryParams: {
        pageNum: 1,
        pageSize: 10,
        sendstateView:2,
        sendstateView:1,
        sort: localStorage.getItem("orgname") == "丽水市中医院" ? 8 : 2, //0 å‡ºé™¢æ—¶é—´(正序)    1 å‡ºé™¢æ—¶é—´(倒序)   2 å‘送时间(正序)    3 å‘送时间(倒序)  7应随访日期(倒序) åº”随访日期(正序)
        serviceType: 18,
        searchscope: 3,
@@ -1719,8 +1713,10 @@
      this.getList(1);
    },
    onthatday() {
      this.topqueryParams.startSendDateTime = this.getCurrentDate();
      this.topqueryParams.endSendDateTime = this.getCurrentDate();
      this.topqueryParams.sendstateView = 1; // å¾…随访
      this.topqueryParams.sendstate = null;
      this.topqueryParams.startSendDateTime = null;
      this.topqueryParams.endSendDateTime = this.getCurrentDate(); // å¼€å§‹éšè®¿æ—¥æœŸ <= ä»Šå¤©
      this.getList(1);
    },
    getCurrentDate() {
@@ -1728,13 +1724,12 @@
      return now.toISOString().slice(0, 10); // æˆªå–前10个字符,即 YYYY-MM-DD
    },
    buidegetTasklist(type) {
      if (this.topqueryParams.searchscope == 3) {
        this.topqueryParams.leaveldeptcodes = store.getters.belongDepts.map(
          (obj) => obj.deptCode
        );
        this.topqueryParams.leavehospitaldistrictcodes =
          store.getters.belongWards.map((obj) => obj.districtCode);
      }
      // ç”¨æˆ·æƒé™ä¸‹æ‰€æœ‰å¾…随访任务:传递全部科室和病区
      this.topqueryParams.leaveldeptcodes = store.getters.belongDepts.map(
        (obj) => obj.deptCode
      );
      this.topqueryParams.leavehospitaldistrictcodes =
        store.getters.belongWards.map((obj) => obj.districtCode);
      // æŽ¥å—异常跳转
      if (this.errtype) {
        this.topqueryParams.leavehospitaldistrictcodes.push(
@@ -1746,7 +1741,7 @@
        pageSize: 10,
        leavehospitaldistrictcodes:
          this.topqueryParams.leavehospitaldistrictcodes,
        sendstates: [2, 3],
        sendstateView: 1,
        leaveldeptcodes: this.topqueryParams.leaveldeptcodes,
      };
      buidegetTasklist(obj).then((response) => {
@@ -1912,10 +1907,12 @@
    resetQuery() {
      this.dateRange = [];
      this.dateRangefs = [];
      this.serviceStatusValue = 10;
      this.topqueryParams = {
        pageNum: 1,
        pageSize: 10,
        sendstateView:2,
        sendstateView:1,
        sort: 2, //0 å‡ºé™¢æ—¶é—´(正序)    1 å‡ºé™¢æ—¶é—´(倒序)   2 å‘送时间(正序)    3 å‘送时间(倒序)
        serviceType: 18,
        searchscope: 3,
src/views/followvisit/complaint/index.vue
@@ -181,7 +181,7 @@
            </div>
          </div>
        </el-col>
        <el-col :span="1.5">
        <!-- <el-col :span="1.5">
          <div class="documentf">
            <div class="document">
              <el-button
@@ -193,7 +193,7 @@
              >
            </div>
          </div>
        </el-col>
        </el-col> -->
        <el-col :span="1.5">
          <div class="documentf">
            <div class="document">
@@ -1357,8 +1357,10 @@
      this.getList(1);
    },
    onthatday() {
      this.topqueryParams.startSendDateTime = this.getCurrentDate();
      this.topqueryParams.endSendDateTime = this.getCurrentDate();
      this.topqueryParams.sendstateView = 1; // å¾…随访
      this.topqueryParams.sendstate = null;
      this.topqueryParams.startSendDateTime = null;
      this.topqueryParams.endSendDateTime = this.getCurrentDate(); // å¼€å§‹éšè®¿æ—¥æœŸ <= ä»Šå¤©
      this.getList(1);
    },
    getCurrentDate() {
@@ -1366,13 +1368,12 @@
      return now.toISOString().slice(0, 10); // æˆªå–前10个字符,即 YYYY-MM-DD
    },
    buidegetTasklist(type) {
      if (this.topqueryParams.searchscope == 3) {
        this.topqueryParams.leaveldeptcodes = store.getters.belongDepts.map(
          (obj) => obj.deptCode
        );
        this.topqueryParams.leavehospitaldistrictcodes =
          store.getters.belongWards.map((obj) => obj.districtCode);
      }
      // ç”¨æˆ·æƒé™ä¸‹æ‰€æœ‰å¾…随访任务:传递全部科室和病区
      this.topqueryParams.leaveldeptcodes = store.getters.belongDepts.map(
        (obj) => obj.deptCode
      );
      this.topqueryParams.leavehospitaldistrictcodes =
        store.getters.belongWards.map((obj) => obj.districtCode);
      // æŽ¥å—异常跳转
      if (this.errtype) {
        this.topqueryParams.leavehospitaldistrictcodes.push(
@@ -1384,7 +1385,7 @@
        pageSize: 10,
        leavehospitaldistrictcodes:
          this.topqueryParams.leavehospitaldistrictcodes,
        sendstates: [2, 3],
        sendstateView: 1,
        leaveldeptcodes: this.topqueryParams.leaveldeptcodes,
      };
      buidegetTasklist(obj).then((response) => {
src/views/followvisit/discharge/index.vue
@@ -244,7 +244,7 @@
          </div>
        </el-col>
        <div class="color-legend">
          <span class="legend-item" v-if="orgname == '省立同德翠苑院区'">
          <span class="legend-item" v-if="orgname == '南华大学附属第一医院'">
            <span class="legend-block legend-blue"></span>未到随访时间
          </span>
          <span class="legend-item">
@@ -1511,6 +1511,8 @@
        value: dept.districtCode,
      };
    });
    // å…ç™»ç›´è¾¾ï¼šæ ¹æ®ç§‘室/病区编码预填患者范围
    this.applyFollowUpScope();
    if (this.errtype == 1) {
      this.toleadExport(2);
    } else if (this.errtype == 2) {
@@ -1725,8 +1727,10 @@
    onthatday() {
      this.resetQuerykj();
      this.topqueryParams.startSendDateTime = this.getCurrentDate();
      this.topqueryParams.endSendDateTime = this.getCurrentDate();
      this.topqueryParams.sendstateView = 1; // å¾…随访
      this.topqueryParams.sendstate = null;
      this.topqueryParams.startSendDateTime = null;
      this.topqueryParams.endSendDateTime = this.getCurrentDate(); // å¼€å§‹éšè®¿æ—¥æœŸ <= ä»Šå¤©
      this.getList(1);
    },
    getCurrentDate() {
@@ -1735,10 +1739,12 @@
    },
    buidegetTasklist(type) {
      if (type) {
        this.resetQuerykj();
      }
      this.applyDeptWardScope();
      // ç”¨æˆ·æƒé™ä¸‹æ‰€æœ‰å¾…随访任务:传递全部科室和病区
      this.topqueryParams.leaveldeptcodes = store.getters.belongDepts.map(
        (obj) => obj.deptCode
      );
      this.topqueryParams.leavehospitaldistrictcodes =
        store.getters.belongWards.map((obj) => obj.districtCode);
      // æŽ¥å—异常跳转
      if (this.errtype) {
        this.topqueryParams.leavehospitaldistrictcodes.push(
@@ -1750,7 +1756,7 @@
        pageSize: 10,
        leavehospitaldistrictcodes:
          this.topqueryParams.leavehospitaldistrictcodes,
        sendstates: [2, 3],
        sendstateView: 1,
        leaveldeptcodes: this.topqueryParams.leaveldeptcodes,
      };
      buidegetTasklist(obj).then((response) => {
@@ -1847,6 +1853,7 @@
    /** æœç´¢æŒ‰é’®æ“ä½œ */
    handleQuery(refresh) {
      this.applyDeptWardScope();
      this.clearAllSelection();
      this.topqueryParams.pageNum = 1;
      // åˆ¤æ–­æ˜¯ä¸æ˜¯å·¥ä½œå°å¿«æ·æŸ¥è¯¢
      if (this.errtype != 2) {
@@ -1857,6 +1864,17 @@
      }
      this.getList(refresh);
    },
    // å…ç™»ç›´è¾¾ï¼šæ ¹æ®è·¯ç”±ä¸­çš„科室/病区编码预填患者范围
    applyFollowUpScope() {
      const deptCode = this.$route.query.followUpDeptCode;
      const wardCode = this.$route.query.followUpWardCode;
      if (!deptCode && !wardCode) return;
      const scope = [];
      if (deptCode) scope.push([1, deptCode]);
      if (wardCode) scope.push([2, wardCode]);
      this.topqueryParams.scopetype = scope;
      this.lastScopeType = deptCode ? 1 : 2;
    },
    // æ‚£è€…范围处理:从多选级联 scopetype è§£æžç§‘室/病区
    applyDeptWardScope() {
      const value = this.topqueryParams.scopetype || [];
src/views/followvisit/discharge/outpatientService.vue
@@ -185,7 +185,7 @@
            </div>
          </div>
        </el-col>
        <el-col :span="1.5">
        <!-- <el-col :span="1.5">
          <div class="documentf">
            <div class="document">
              <el-button
@@ -197,7 +197,7 @@
              >
            </div>
          </div>
        </el-col>
        </el-col> -->
        <el-col :span="1.5">
          <div class="documentf">
            <div class="document">
@@ -1227,8 +1227,10 @@
      this.getList(1);
    },
    onthatday() {
      this.topqueryParams.startSendDateTime = this.getCurrentDate();
      this.topqueryParams.endSendDateTime = this.getCurrentDate();
      this.topqueryParams.sendstateView = 1; // å¾…随访
      this.topqueryParams.sendstate = null;
      this.topqueryParams.startSendDateTime = null;
      this.topqueryParams.endSendDateTime = this.getCurrentDate(); // å¼€å§‹éšè®¿æ—¥æœŸ <= ä»Šå¤©
      this.getList(1);
    },
    getCurrentDate() {
@@ -1236,13 +1238,12 @@
      return now.toISOString().slice(0, 10); // æˆªå–前10个字符,即 YYYY-MM-DD
    },
    buidegetTasklist(type) {
      if (this.topqueryParams.searchscope == 3) {
        this.topqueryParams.leaveldeptcodes = store.getters.belongDepts.map(
          (obj) => obj.deptCode
        );
        this.topqueryParams.leavehospitaldistrictcodes =
          store.getters.belongWards.map((obj) => obj.districtCode);
      }
      // ç”¨æˆ·æƒé™ä¸‹æ‰€æœ‰å¾…随访任务:传递全部科室和病区
      this.topqueryParams.leaveldeptcodes = store.getters.belongDepts.map(
        (obj) => obj.deptCode
      );
      this.topqueryParams.leavehospitaldistrictcodes =
        store.getters.belongWards.map((obj) => obj.districtCode);
      // æŽ¥å—异常跳转
      if (this.errtype) {
        this.topqueryParams.leavehospitaldistrictcodes.push(
@@ -1254,7 +1255,7 @@
        pageSize: 10,
        leavehospitaldistrictcodes:
          this.topqueryParams.leavehospitaldistrictcodes,
        sendstates: [2, 3],
        sendstateView: 1,
        leaveldeptcodes: this.topqueryParams.leaveldeptcodes,
      };
      buidegetTasklist(obj).then((response) => {
@@ -1418,10 +1419,12 @@
    resetQuery() {
      this.dateRange = [];
      this.dateRangefs = [];
      this.serviceStatusValue = 10;
      this.topqueryParams = {
        pageNum: 1,
        pageSize: 10,
        sendstateView:2,
        sendstateView:1,
        sort: 2, //0 å‡ºé™¢æ—¶é—´(正序)    1 å‡ºé™¢æ—¶é—´(倒序)   2 å‘送时间(正序)    3 å‘送时间(倒序)
        serviceType: 3,
        searchscope: 3,
src/views/followvisit/mzsatisfaction/index.vue
@@ -173,7 +173,7 @@
            </div>
          </div>
        </el-col>
        <el-col :span="1.5">
        <!-- <el-col :span="1.5">
          <div class="documentf">
            <div class="document">
              <el-button
@@ -185,7 +185,7 @@
              >
            </div>
          </div>
        </el-col>
        </el-col> -->
        <el-col :span="1.5">
          <div class="documentf">
            <div class="document">
@@ -1341,8 +1341,10 @@
      this.getList(1);
    },
    onthatday() {
      this.topqueryParams.startSendDateTime = this.getCurrentDate();
      this.topqueryParams.endSendDateTime = this.getCurrentDate();
      this.topqueryParams.sendstateView = 1; // å¾…随访
      this.topqueryParams.sendstate = null;
      this.topqueryParams.startSendDateTime = null;
      this.topqueryParams.endSendDateTime = this.getCurrentDate(); // å¼€å§‹éšè®¿æ—¥æœŸ <= ä»Šå¤©
      this.getList(1);
    },
    getCurrentDate() {
@@ -1350,13 +1352,12 @@
      return now.toISOString().slice(0, 10); // æˆªå–前10个字符,即 YYYY-MM-DD
    },
    buidegetTasklist(type) {
      if (this.topqueryParams.searchscope == 3) {
        this.topqueryParams.leaveldeptcodes = store.getters.belongDepts.map(
          (obj) => obj.deptCode
        );
        this.topqueryParams.leavehospitaldistrictcodes =
          store.getters.belongWards.map((obj) => obj.districtCode);
      }
      // ç”¨æˆ·æƒé™ä¸‹æ‰€æœ‰å¾…随访任务:传递全部科室和病区
      this.topqueryParams.leaveldeptcodes = store.getters.belongDepts.map(
        (obj) => obj.deptCode
      );
      this.topqueryParams.leavehospitaldistrictcodes =
        store.getters.belongWards.map((obj) => obj.districtCode);
      // æŽ¥å—异常跳转
      if (this.errtype) {
        this.topqueryParams.leavehospitaldistrictcodes.push(
@@ -1368,7 +1369,7 @@
        pageSize: 10,
        leavehospitaldistrictcodes:
          this.topqueryParams.leavehospitaldistrictcodes,
        sendstates: [2, 3],
        sendstateView: 1,
        leaveldeptcodes: this.topqueryParams.leaveldeptcodes,
      };
      buidegetTasklist(obj).then((response) => {
src/views/followvisit/operation/index.vue
@@ -1345,7 +1345,7 @@
      topqueryParams: {
        pageNum: 1,
        pageSize: 10,
        sendstateView:2,
        sendstateView:1,
        sort: localStorage.getItem("orgname") == "丽水市中医院" ? 8 : 2, //0 æ‰‹æœ¯å®Œæˆæ—¶é—´(正序)    1 æ‰‹æœ¯å®Œæˆæ—¶é—´(倒序)   2 å‘送时间(正序)    3 å‘送时间(倒序)  7应随访日期(倒序) åº”随访日期(正序)
        serviceType: 19,
        searchscope: 3,
@@ -1642,8 +1642,10 @@
      this.getList(1);
    },
    onthatday() {
      this.topqueryParams.startSendDateTime = this.getCurrentDate();
      this.topqueryParams.endSendDateTime = this.getCurrentDate();
      this.topqueryParams.sendstateView = 1; // å¾…随访
      this.topqueryParams.sendstate = null;
      this.topqueryParams.startSendDateTime = null;
      this.topqueryParams.endSendDateTime = this.getCurrentDate(); // å¼€å§‹éšè®¿æ—¥æœŸ <= ä»Šå¤©
      this.getList(1);
    },
    getCurrentDate() {
@@ -1651,13 +1653,12 @@
      return now.toISOString().slice(0, 10); // æˆªå–前10个字符,即 YYYY-MM-DD
    },
    buidegetTasklist(type) {
      if (this.topqueryParams.searchscope == 3) {
        this.topqueryParams.leaveldeptcodes = store.getters.belongDepts.map(
          (obj) => obj.deptCode
        );
        this.topqueryParams.leavehospitaldistrictcodes =
          store.getters.belongWards.map((obj) => obj.districtCode);
      }
      // ç”¨æˆ·æƒé™ä¸‹æ‰€æœ‰å¾…随访任务:传递全部科室和病区
      this.topqueryParams.leaveldeptcodes = store.getters.belongDepts.map(
        (obj) => obj.deptCode
      );
      this.topqueryParams.leavehospitaldistrictcodes =
        store.getters.belongWards.map((obj) => obj.districtCode);
      // æŽ¥å—异常跳转
      if (this.errtype) {
        this.topqueryParams.leavehospitaldistrictcodes.push(
@@ -1669,7 +1670,7 @@
        pageSize: 10,
        leavehospitaldistrictcodes:
          this.topqueryParams.leavehospitaldistrictcodes,
        sendstates: [2, 3],
        sendstateView: 1,
        leaveldeptcodes: this.topqueryParams.leaveldeptcodes,
      };
      buidegetTasklist(obj).then((response) => {
@@ -1831,10 +1832,12 @@
    resetQuery() {
      this.dateRange = [];
      this.dateRangefs = [];
      this.serviceStatusValue = 10;
      this.topqueryParams = {
        pageNum: 1,
        pageSize: 10,
        sendstateView:2,
        sendstateView:1,
        sort: 2, //0 æ‰‹æœ¯å®Œæˆæ—¶é—´(正序)    1 æ‰‹æœ¯å®Œæˆæ—¶é—´(倒序)   2 å‘送时间(正序)    3 å‘送时间(倒序)
        serviceType: 19,
        searchscope: 3,
src/views/followvisit/record/detailpage/index.vue
@@ -336,8 +336,8 @@
            v-if="
              (orgname == '南华大学附属第一医院' ||
                orgname == '丽水市第二人民医院' ||
                orgname == '缙云县人民医院'||
                orgname == '丽水市妇幼保健院'||
                orgname == '缙云县人民医院' ||
                orgname == '丽水市妇幼保健院' ||
                orgname == '松阳县中医医院') &&
              !Voicetype
            "
@@ -999,16 +999,29 @@
          <div class="collapse-section">
            <el-collapse v-model="collapseActive" accordion>
              <el-collapse-item title="患者档案信息" name="patient">
                <el-form ref="userform2" :model="userform" label-width="90px" size="small">
                <el-form
                  ref="userform2"
                  :model="userform"
                  label-width="90px"
                  size="small"
                >
                  <el-row :gutter="16">
                    <el-col :span="12">
                      <el-form-item label="患者姓名">
                        <el-input v-model="userform.name" placeholder="请输入姓名" maxlength="30"></el-input>
                        <el-input
                          v-model="userform.name"
                          placeholder="请输入姓名"
                          maxlength="30"
                        ></el-input>
                      </el-form-item>
                    </el-col>
                    <el-col :span="12">
                      <el-form-item label="性别">
                        <el-select v-model="userform.sex" placeholder="请选择" style="width: 100%">
                        <el-select
                          v-model="userform.sex"
                          placeholder="请选择"
                          style="width: 100%"
                        >
                          <el-option label="男" :value="1"></el-option>
                          <el-option label="女" :value="2"></el-option>
                        </el-select>
@@ -1018,53 +1031,78 @@
                  <el-row :gutter="16">
                    <el-col :span="12">
                      <el-form-item label="年龄">
                        <el-input v-model="userform.age" placeholder="请输入年龄" maxlength="20"></el-input>
                        <el-input
                          v-model="userform.age"
                          placeholder="请输入年龄"
                          maxlength="20"
                        ></el-input>
                      </el-form-item>
                    </el-col>
                    <el-col :span="12">
                      <el-form-item label="联系方式">
                        <el-input v-model="userform.telcode" placeholder="请输入联系方式" maxlength="20"></el-input>
                        <el-input
                          v-model="userform.telcode"
                          placeholder="请输入联系方式"
                          maxlength="20"
                        ></el-input>
                      </el-form-item>
                    </el-col>
                  </el-row>
                  <el-row :gutter="16">
                    <el-col :span="24">
                      <el-form-item label="亲属电话">
                        <el-input v-model="userform.relativetelcode" placeholder="请输入亲属联系方式" maxlength="20"></el-input>
                        <el-input
                          v-model="userform.relativetelcode"
                          placeholder="请输入亲属联系方式"
                          maxlength="20"
                        ></el-input>
                      </el-form-item>
                    </el-col>
                  </el-row>
                  <el-row :gutter="16">
                    <el-col :span="24">
                      <el-form-item label="诊断名称">
                        <el-input v-model="form.leavediagname" placeholder="请输入诊断" maxlength="50"></el-input>
                        <el-input
                          v-model="form.leavediagname"
                          placeholder="请输入诊断"
                          maxlength="50"
                        ></el-input>
                      </el-form-item>
                    </el-col>
                  </el-row>
                  <el-row :gutter="16">
                    <el-col :span="24">
                      <el-form-item label="出生地">
                        <el-input v-model="userform.birthplace" placeholder="国、省、地市、区县、街道等" maxlength="50"></el-input>
                        <el-input
                          v-model="userform.birthplace"
                          placeholder="国、省、地市、区县、街道等"
                          maxlength="50"
                        ></el-input>
                      </el-form-item>
                    </el-col>
                  </el-row>
                  <el-row :gutter="16">
                    <el-col :span="24">
                      <el-form-item label="居住地">
                        <el-input v-model="userform.placeOfResidence" placeholder="国、省、地市、区县、街道等" maxlength="50"></el-input>
                        <el-input
                          v-model="userform.placeOfResidence"
                          placeholder="国、省、地市、区县、街道等"
                          maxlength="50"
                        ></el-input>
                      </el-form-item>
                    </el-col>
                  </el-row>
                </el-form>
              </el-collapse-item>
              <el-collapse-item title="投诉建议" name="advice" v-if="orgname=='缙云县人民医院'">
              <el-collapse-item
                title="投诉建议"
                name="advice"
                v-if="orgname == '缙云县人民医院'"
              >
                <AdviceList :subid="taskid" :taskid="id" :patid="patid" />
              </el-collapse-item>
            </el-collapse>
          </div>
        </div>
      </div>
    </div>
@@ -1670,7 +1708,7 @@
    },
    // æ˜¯å¦ä»¥é—®å·å½¢å¼å±•示(非语音服务,或语音服务但无完整语音数据需手动填报)
    showQuestionnaireForm() {
      return !this.Voicetype ;
      return !this.Voicetype;
    },
    // æ˜¯å¦ä¸ºç®¡ç†å‘˜è§’色
    isAdmin() {
@@ -2046,13 +2084,12 @@
                .map((obj) => obj.targetvalue)
                .join("&");
            }
            console.log(item.targetvalue,'item.targetvalue');
            console.log(item.targetvalue, "item.targetvalue");
            if (item.targetvalue) {
              item.scriptResult = item.targetvalue.split("&");
            }
            console.log(item.scriptResult);
          });
        }
      });
@@ -2118,8 +2155,11 @@
          this.$modal.msgSuccess("服务保存成功");
          this.Editsingletasksonyic(this.serviceStates);
          const orgName = localStorage.getItem("orgname");
          if (this.form.isVisitAgain != 1 || this.visitAgain == 1) {
          if (
            this.form.isVisitAgain != 1 ||
            orgName == "丽水市中医院" ||
            this.visitAgain == 1
          ) {
            this.Torouter();
            return;
          }
@@ -2407,7 +2447,7 @@
      savequestiondetail(obj).then((res) => {
        if (res.code == 200) {
          this.$modal.msgSuccess("服务保存成功");
          // this.Torouter();
          if (
            this.form.isVisitAgain != 1 ||
            orgName == "丽水市中医院" ||
@@ -2418,6 +2458,7 @@
            this.Torouter();
            return;
          }
          this.$modal
            .confirm(
              '任务保存成功是否针对患者:"' +
@@ -2750,6 +2791,7 @@
        // åŒæ—¶é‡ç½®ä¸Žé€‰é¡¹ç›¸å…³çš„状态
        // questionItem.isabnormal = false;
        questionItem.showAppendInput = false;
        questionItem.score = null; // å–消选中后清空题目分数
        // æ³¨æ„ï¼šå–消选中时,我们通常不希望触发题目跳转逻辑,所以直接返回
        // å¦‚果需要,可以在这里添加取消选中后的特定逻辑,例如重置题目序列
      } else {
@@ -2787,6 +2829,9 @@
      // å¤„理异常状态高亮
      this.tableDatatop[questionIndex].isabnormal =
        selectedOptionObj.isabnormal;
      // å¤„理分数:把选中选项的 score å¸¦åˆ°é¢˜ç›® score ä¸Š
      this.tableDatatop[questionIndex].score = selectedOptionObj.score;
      // å¤„理附加输入框显示
@@ -2881,6 +2926,10 @@
          if (obj.isabnormal) {
            this.tableDatatop[index].isabnormal = obj.isabnormal;
          }
          // å›žæ˜¾å•选题目分数:把已选中选项的 score å¸¦åˆ°é¢˜ç›® score ä¸Š
          if (obj.score !== undefined && obj.score !== null) {
            this.tableDatatop[index].score = obj.score;
          }
          this.$forceUpdate();
        }
      });
@@ -2952,7 +3001,18 @@
      return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
    },
    updateScore(a, b, c) {},
    // å¤šé€‰ï¼šæŠŠé€‰ä¸­é€‰é¡¹çš„分数累加到题目 score ä¸Š
    updateScore(selectedValues, questionIndex, question) {
      const options = question.svyTaskTemplateTargetoptions || [];
      const score = options.reduce((sum, opt) => {
        if (selectedValues && selectedValues.includes(opt.optioncontent)) {
          return sum + (Number(opt.score) || 0);
        }
        return sum;
      }, 0);
      question.score = score;
      this.$forceUpdate();
    },
    // åˆå¹¶ä¿®æ”¹ç›¸å…³=============================
    toggleMergeMode() {
      this.isMergeMode = !this.isMergeMode;
src/views/followvisit/record/index.vue
@@ -1201,13 +1201,12 @@
      return `${year}-${month}-${day}`;
    },
    buidegetTasklist(type) {
      if (this.topqueryParams.searchscope == 3) {
        this.topqueryParams.leaveldeptcodes = store.getters.belongDepts.map(
          (obj) => obj.deptCode
        );
        this.topqueryParams.leavehospitaldistrictcodes =
          store.getters.belongWards.map((obj) => obj.districtCode);
      }
      // ç”¨æˆ·æƒé™ä¸‹æ‰€æœ‰å¾…随访任务:传递全部科室和病区
      this.topqueryParams.leaveldeptcodes = store.getters.belongDepts.map(
        (obj) => obj.deptCode
      );
      this.topqueryParams.leavehospitaldistrictcodes =
        store.getters.belongWards.map((obj) => obj.districtCode);
      // æŽ¥å—异常跳转
      if (this.errtype) {
        this.topqueryParams.leavehospitaldistrictcodes.push(
@@ -1219,7 +1218,7 @@
        pageSize: 10,
        leavehospitaldistrictcodes:
          this.topqueryParams.leavehospitaldistrictcodes,
        sendstates: [2, 3],
        sendstateView: 1,
        leaveldeptcodes: this.topqueryParams.leaveldeptcodes,
      };
      buidegetTasklist(obj).then((response) => {
@@ -1418,8 +1417,10 @@
      });
    },
    onthatday() {
      this.topqueryParams.startSendDateTime = this.getCurrentDate();
      this.topqueryParams.endSendDateTime = this.getCurrentDate();
      this.topqueryParams.sendstateView = 1; // å¾…随访
      this.topqueryParams.sendstate = null;
      this.topqueryParams.startSendDateTime = null;
      this.topqueryParams.endSendDateTime = this.getCurrentDate(); // å¼€å§‹éšè®¿æ—¥æœŸ <= ä»Šå¤©
      this.getList(1);
    },
    getCurrentDate() {
src/views/followvisit/technology/index.vue
@@ -1037,13 +1037,12 @@
      });
    },
    buidegetTasklist(type) {
      if (this.topqueryParams.searchscope == 3) {
        this.topqueryParams.leaveldeptcodes = store.getters.belongDepts.map(
          (obj) => obj.deptCode
        );
        this.topqueryParams.leavehospitaldistrictcodes =
          store.getters.belongWards.map((obj) => obj.districtCode);
      }
      // ç”¨æˆ·æƒé™ä¸‹æ‰€æœ‰å¾…随访任务:传递全部科室和病区
      this.topqueryParams.leaveldeptcodes = store.getters.belongDepts.map(
        (obj) => obj.deptCode
      );
      this.topqueryParams.leavehospitaldistrictcodes =
        store.getters.belongWards.map((obj) => obj.districtCode);
      // æŽ¥å—异常跳转
      if (this.errtype) {
        this.topqueryParams.leavehospitaldistrictcodes.push(
@@ -1055,7 +1054,7 @@
        pageSize: 10,
        leavehospitaldistrictcodes:
          this.topqueryParams.leavehospitaldistrictcodes,
        sendstates: [2, 3],
        sendstateView: 1,
        leaveldeptcodes: this.topqueryParams.leaveldeptcodes,
      };
      buidegetTasklist(obj).then((response) => {
src/views/followvisit/zbAgain/index.vue
@@ -173,7 +173,7 @@
            </div>
          </div>
        </el-col>
        <el-col :span="1.5">
        <!-- <el-col :span="1.5">
          <div class="documentf">
            <div class="document">
              <el-button
@@ -185,7 +185,7 @@
              >
            </div>
          </div>
        </el-col>
        </el-col> -->
        <el-col :span="1.5">
          <div class="documentf">
            <div class="document">
@@ -1005,7 +1005,7 @@
      topqueryParams: {
        pageNum: 1,
        pageSize: 10,
        sendstateView:2,
        sendstateView:1,
        sort: localStorage.getItem("orgname") == "丽水市中医院" ? 8 : 2, //0 å‡ºé™¢æ—¶é—´(正序)    1 å‡ºé™¢æ—¶é—´(倒序)   2 å‘送时间(正序)    3 å‘送时间(倒序)  7应随访日期(倒序) åº”随访日期(正序)
        serviceType: 13,
        searchscope: 3,
@@ -1220,8 +1220,10 @@
      this.getList(1);
    },
    onthatday() {
      this.topqueryParams.startSendDateTime = this.getCurrentDate();
      this.topqueryParams.endSendDateTime = this.getCurrentDate();
      this.topqueryParams.sendstateView = 1; // å¾…随访
      this.topqueryParams.sendstate = null;
      this.topqueryParams.startSendDateTime = null;
      this.topqueryParams.endSendDateTime = this.getCurrentDate(); // å¼€å§‹éšè®¿æ—¥æœŸ <= ä»Šå¤©
      this.getList(1);
    },
    getCurrentDate() {
@@ -1229,16 +1231,15 @@
      return now.toISOString().slice(0, 10); // æˆªå–前10个字符,即 YYYY-MM-DD
    },
    buidegetTasklist(type) {
      if (this.topqueryParams.searchscope == 3) {
        this.topqueryParams.visitDeptCodes = store.getters.belongDepts.map(
          (obj) => obj.deptCode
        );
          this.topqueryParams.leaveldeptcodes = store.getters.belongDepts.map(
          (obj) => obj.deptCode
        );
        this.topqueryParams.leavehospitaldistrictcodes =
          store.getters.belongWards.map((obj) => obj.districtCode);
      }
      // ç”¨æˆ·æƒé™ä¸‹æ‰€æœ‰å¾…随访任务:传递全部科室和病区
      this.topqueryParams.visitDeptCodes = store.getters.belongDepts.map(
        (obj) => obj.deptCode
      );
      this.topqueryParams.leaveldeptcodes = store.getters.belongDepts.map(
        (obj) => obj.deptCode
      );
      this.topqueryParams.leavehospitaldistrictcodes =
        store.getters.belongWards.map((obj) => obj.districtCode);
      // æŽ¥å—异常跳转
      if (this.errtype) {
        this.topqueryParams.leavehospitaldistrictcodes.push(
@@ -1250,7 +1251,7 @@
        pageSize: 10,
        leavehospitaldistrictcodes:
          this.topqueryParams.leavehospitaldistrictcodes,
        sendstates: [2, 3],
        sendstateView: 1,
        visitDeptCodes: this.topqueryParams.visitDeptCodes,
        leaveldeptcodes: this.topqueryParams.leaveldeptcodes,
      };
@@ -1425,7 +1426,7 @@
      this.topqueryParams = {
        pageNum: 1,
        pageSize: 10,
        sendstateView:2,
        sendstateView:1,
        sort: 2, //0 å‡ºé™¢æ—¶é—´(正序)    1 å‡ºé™¢æ—¶é—´(倒序)   2 å‘送时间(正序)    3 å‘送时间(倒序)
        serviceType: 13,
        searchscope: 3,
src/views/followvisit/zysatisfaction/index.vue
@@ -184,7 +184,7 @@
            </div>
          </div>
        </el-col>
        <el-col :span="1.5">
        <!-- <el-col :span="1.5">
          <div class="documentf">
            <div class="document">
              <el-button
@@ -196,7 +196,7 @@
              >
            </div>
          </div>
        </el-col>
        </el-col> -->
        <el-col :span="1.5">
          <div class="documentf">
            <div class="document">
@@ -1359,13 +1359,12 @@
      return now.toISOString().slice(0, 10); // æˆªå–前10个字符,即 YYYY-MM-DD
    },
    buidegetTasklist(type) {
      if (this.topqueryParams.searchscope == 3) {
        this.topqueryParams.leaveldeptcodes = store.getters.belongDepts.map(
          (obj) => obj.deptCode
        );
        this.topqueryParams.leavehospitaldistrictcodes =
          store.getters.belongWards.map((obj) => obj.districtCode);
      }
      // ç”¨æˆ·æƒé™ä¸‹æ‰€æœ‰å¾…随访任务:传递全部科室和病区
      this.topqueryParams.leaveldeptcodes = store.getters.belongDepts.map(
        (obj) => obj.deptCode
      );
      this.topqueryParams.leavehospitaldistrictcodes =
        store.getters.belongWards.map((obj) => obj.districtCode);
      // æŽ¥å—异常跳转
      if (this.errtype) {
        this.topqueryParams.leavehospitaldistrictcodes.push(
@@ -1377,7 +1376,7 @@
        pageSize: 10,
        leavehospitaldistrictcodes:
          this.topqueryParams.leavehospitaldistrictcodes,
        sendstates: [2, 3],
        sendstateView: 1,
        leaveldeptcodes: this.topqueryParams.leaveldeptcodes,
      };
      buidegetTasklist(obj).then((response) => {
src/views/login.vue
@@ -13,6 +13,7 @@
          type="text"
          auto-complete="off"
          placeholder="账号"
          @blur="handleUsernameBlur"
        >
          <svg-icon
            slot="prefix"
@@ -184,6 +185,17 @@
        }
      });
    },
    // è¾“入完账号后,检索可选机构是否包含“南华附一”,若有则自动填充密码
    handleUsernameBlur() {
      if (!this.loginForm.username) return;
      const hasNanhua = this.options.some((item) => {
        const label = item.label || "";
        return label.includes("南华") && label.includes("附一");
      });
      if (hasNanhua) {
        this.loginForm.password = "nhfy@0230";
      }
    },
    getCookie() {
      const username = Cookies.get("username");
      const password = Cookies.get("password");
src/views/loginSSO.vue
@@ -162,6 +162,8 @@
          id,
          Voicetype,
          visitCount,
          followUpDeptCode,
          followUpWardCode,
        } = params;
        console.log(
          token,
@@ -171,6 +173,8 @@
          deptCode,
          redirect,
          campusid,
             followUpDeptCode,
          followUpWardCode,
          "参数日志"
        );
@@ -229,8 +233,16 @@
            visitCount: visitCount || "",
          };
          console.log("使用taskid跳转到详情页:", redirectPath, redirectQuery);
        } else if (followUpDeptCode || followUpWardCode) {
          // 7.2 æºå¸¦ç§‘室/病区编码,跳转到出院随访列表并按科室/病区查询
          redirectPath = "/followvisit/discharge";
          redirectQuery = {
            followUpDeptCode: followUpDeptCode || "",
            followUpWardCode: followUpWardCode || "",
          };
          console.log("使用科室/病区编码跳转到出院随访列表:", redirectPath, redirectQuery);
        } else if (redirect) {
          // 7.2 å¦‚果传入的redirect存在,使用传入的重定向路径
          // 7.3 å¦‚果传入的redirect存在,使用传入的重定向路径
          try {
            redirectPath = decodeURIComponent(redirect);
            console.log("使用传入的重定向路径:", redirectPath);
@@ -239,7 +251,7 @@
            redirectPath = this.getDefaultRedirectPath();
          }
        } else {
          // 7.3 ä½¿ç”¨é»˜è®¤è·¯å¾„
          // 7.4 ä½¿ç”¨é»˜è®¤è·¯å¾„
          redirectPath = this.getDefaultRedirectPath();
        }
src/views/patient/archives/childHealth/detail.vue
@@ -3,9 +3,18 @@
    <template v-if="archive">
      <div class="action-bar">
        <el-button @click="$router.go(-1)" icon="el-icon-back">返回</el-button>
        <el-button :type="editing ? 'success' : 'primary'" :icon="editing ? 'el-icon-check' : 'el-icon-edit'" @click="toggleEdit">{{ editing ? '保存' : '编辑' }}</el-button>
        <el-tag :type="subTypeTag(archive.subType)" style="margin-left:12px">{{ archive.subTypeLabel }}</el-tag>
        <el-button :type="editing ? 'success' : 'primary'" :icon="editing ? 'el-icon-check' : 'el-icon-edit'" @click="toggleEdit">
          {{ editing ? '保存' : '编辑' }}
        </el-button>
        <el-button v-if="archive.closeStatus != 1" type="danger" plain icon="el-icon-circle-close" @click="handleCloseCase">结案</el-button>
        <el-tag :type="archive.closeStatus == 1 ? 'info' : 'success'" style="margin-left:12px">
          {{ archive.closeStatus == 1 ? '已结案' : '未结案' }}
        </el-tag>
        <el-tag :type="subTypeTag(archive.subType)" style="margin-left:8px">{{ archive.subTypeLabel }}</el-tag>
      </div>
      <el-tabs v-model="mainTab" type="border-card">
        <el-tab-pane label="档案详情" name="detail">
      <!-- æ‚£è€…基本信息 -->
      <el-card class="info-card" shadow="never">
@@ -19,7 +28,6 @@
        <el-row :gutter="20" style="margin-top:10px">
          <el-col :span="6"><InfoField label="科室" :value="archive.deptname" /></el-col>
          <el-col :span="6"><InfoField label="诊断" :value="archive.diagname" /></el-col>
          <el-col :span="6"><InfoField label="主治医生" :value="archive.drname" /></el-col>
          <el-col :span="6"><InfoField label="来源" :value="sourceLabel" /></el-col>
        </el-row>
      </el-card>
@@ -27,43 +35,54 @@
      <!-- å®¶é•¿ä¿¡æ¯ -->
      <el-card class="info-card" shadow="never" style="margin-top:14px">
        <div slot="header" class="card-header"><span>家长/监护人信息</span></div>
        <el-row :gutter="20">
          <el-col :span="6">
            <InfoField label="家长姓名" :value="parentInfo.parentName" :editing="editing" v-model="parentInfo.parentName" />
          </el-col>
          <el-col :span="6">
            <InfoField label="与儿童关系" :value="relationLabel" :editing="editing" type="select" :options="relationOptions" @change="v => parentInfo.relation = v" />
          </el-col>
          <el-col :span="6">
            <InfoField label="联系电话" :value="parentInfo.parentPhone" :editing="editing" v-model="parentInfo.parentPhone" />
          </el-col>
          <el-col :span="6">
            <InfoField label="身份证号" :value="parentInfo.parentIdCard" :editing="editing" v-model="parentInfo.parentIdCard" />
          </el-col>
        </el-row>
        <el-row :gutter="20" style="margin-top:10px">
          <el-col :span="12">
            <InfoField label="联系地址" :value="parentInfo.address" :editing="editing" v-model="parentInfo.address" />
          </el-col>
        </el-row>
        <el-form :model="parentInfo" label-width="110px" size="small" :disabled="!editing">
          <el-row :gutter="20">
            <el-col :span="8"><el-form-item label="家长姓名"><el-input v-model="parentInfo.parentName" class="w-sm" /></el-form-item></el-col>
            <el-col :span="8"><el-form-item label="与儿童关系">
              <el-select v-model="parentInfo.relation" class="w-sm">
                <el-option v-for="o in relationOptions" :key="o.value" :label="o.label" :value="o.value" />
              </el-select>
            </el-form-item></el-col>
            <el-col :span="8"><el-form-item label="联系电话"><el-input v-model="parentInfo.parentPhone" class="w-sm" /></el-form-item></el-col>
          </el-row>
          <el-row :gutter="20">
            <el-col :span="8"><el-form-item label="身份证号"><el-input v-model="parentInfo.parentIdCard" class="w-md" /></el-form-item></el-col>
            <el-col :span="16"><el-form-item label="联系地址"><el-input v-model="parentInfo.address" class="w-full" /></el-form-item></el-col>
          </el-row>
        </el-form>
      </el-card>
      <!-- æ¡£æ¡ˆåŸºæœ¬ä¿¡æ¯ -->
      <!-- æ¡£æ¡ˆä¿¡æ¯ï¼ˆå¯ç¼–辑) -->
      <el-card class="info-card" shadow="never" style="margin-top:14px">
        <div slot="header" class="card-header">
          <span>档案基本信息</span>
          <span>档案信息</span>
          <span class="header-sub">{{ archive.createTime ? archive.createTime.substring(0,10) : '-' }} å»ºæ¡£</span>
        </div>
        <el-row :gutter="20">
          <el-col :span="8"><InfoField label="建档类型" :value="archive.subTypeLabel" /></el-col>
          <el-col :span="8"><InfoField label="建档医生" :value="archive.archiveForm.doctorName" :editing="editing" v-model="archive.archiveForm.doctorName" /></el-col>
          <el-col :span="8"><InfoField label="建档时间" :value="archive.createTime" /></el-col>
        </el-row>
        <el-form :model="archive" label-width="110px" size="small" :disabled="!editing">
          <el-row :gutter="20">
            <el-col :span="8"><el-form-item label="出生日期"><el-date-picker v-model="archive.birthday" value-format="yyyy-MM-dd" class="w-md" /></el-form-item></el-col>
            <el-col :span="8"><el-form-item label="出生体重"><el-input v-model="archive.birthWeight" class="w-xs" /></el-form-item></el-col>
            <el-col :span="8"><el-form-item label="出生孕周"><el-input v-model="archive.gestationalWeek" class="w-xs" /></el-form-item></el-col>
          </el-row>
          <el-row :gutter="20">
            <el-col :span="8"><el-form-item label="诊断日期"><el-date-picker v-model="archive.diagnosisDate" value-format="yyyy-MM-dd" class="w-md" /></el-form-item></el-col>
            <el-col :span="8"><el-form-item label="主治医生"><el-input v-model="archive.attendingDoctor" class="w-sm" /></el-form-item></el-col>
            <el-col :span="8"><el-form-item label="经管医生"><el-input v-model="archive.managingDoctor" class="w-sm" /></el-form-item></el-col>
          </el-row>
          <el-row :gutter="20">
            <el-col :span="8"><el-form-item label="登记人员"><el-input v-model="archive.registrant" class="w-sm" /></el-form-item></el-col>
          </el-row>
        </el-form>
      </el-card>
      <!-- é«˜å±å„¿ä¸“案 â†’ å¯ç¼–辑表格 -->
      <el-card v-if="archive.subType === 'highRisk'" class="info-card" shadow="never" style="margin-top:14px">
        <div slot="header" class="card-header"><span>高危儿专案管理</span></div>
        <el-form :model="archive" label-width="110px" size="small" :disabled="!editing" inline>
          <el-form-item label="高危儿分类"><el-input v-model="archive.highRiskCategory" class="w-md" /></el-form-item>
          <el-form-item label="高危儿名称"><el-input v-model="archive.highRiskName" class="w-lg" /></el-form-item>
          <el-form-item label="拟随访日期"><el-date-picker v-model="archive.followupDate" value-format="yyyy-MM-dd" class="w-md" /></el-form-item>
        </el-form>
        <el-tabs v-model="activeTab" type="border-card">
          <el-tab-pane label="生长发育监测" name="growth">
            <EditableTable :data.sync="growthData" :editing="editing" :columns="growthColumns" />
@@ -80,6 +99,9 @@
      <!-- è¥å…»æ€§ç–¾ç—…专案 â†’ å¯ç¼–辑表格 -->
      <el-card v-if="archive.subType === 'nutrition'" class="info-card" shadow="never" style="margin-top:14px">
        <div slot="header" class="card-header"><span>营养性疾病专案管理</span></div>
        <el-form :model="archive" label-width="130px" size="small" :disabled="!editing" inline>
          <el-form-item label="营养性疾病名称"><el-input v-model="archive.nutritionName" class="w-lg" /></el-form-item>
        </el-form>
        <el-tabs v-model="activeTab" type="border-card">
          <el-tab-pane label="体格测量" name="measure">
            <EditableTable :data.sync="measureData" :editing="editing" :columns="measureColumns" />
@@ -96,23 +118,26 @@
      <!-- å­¦ä¹ å›°éš¾ä¸“案 â†’ å•一表单 -->
      <el-card v-if="archive.subType === 'learning'" class="info-card" shadow="never" style="margin-top:14px">
        <div slot="header" class="card-header"><span>学习困难专案管理</span></div>
        <el-form :model="archive" label-width="110px" size="small" :disabled="!editing" inline>
          <el-form-item label="预约情况"><el-input v-model="archive.appointmentStatus" class="w-lg" /></el-form-item>
        </el-form>
        <el-form :model="form" label-width="130px" size="small" :disabled="!editing">
          <el-divider content-position="left">学习能力评估</el-divider>
          <el-row :gutter="20">
            <el-col :span="8"><el-form-item label="评估日期"><el-date-picker v-model="form.assessDate" value-format="yyyy-MM-dd" style="width:100%" /></el-form-item></el-col>
            <el-col :span="8"><el-form-item label="测评工具"><el-input v-model="form.testName" /></el-form-item></el-col>
            <el-col :span="8"><el-form-item label="评估日期"><el-date-picker v-model="form.assessDate" value-format="yyyy-MM-dd" class="w-md" /></el-form-item></el-col>
            <el-col :span="8"><el-form-item label="测评工具"><el-input v-model="form.testName" class="w-sm" /></el-form-item></el-col>
            <el-col :span="8"><el-form-item label="智商(IQ)"><el-input-number v-model="form.iq" :min="40" :max="160" /></el-form-item></el-col>
          </el-row>
          <el-row :gutter="20">
            <el-col :span="8"><el-form-item label="阅读能力"><el-select v-model="form.reading" style="width:100%"><el-option v-for="o in gradeOptions" :key="o" :label="o" :value="o" /></el-select></el-form-item></el-col>
            <el-col :span="8"><el-form-item label="书写能力"><el-select v-model="form.writing" style="width:100%"><el-option v-for="o in gradeOptions" :key="o" :label="o" :value="o" /></el-select></el-form-item></el-col>
            <el-col :span="8"><el-form-item label="计算能力"><el-select v-model="form.math" style="width:100%"><el-option v-for="o in gradeOptions" :key="o" :label="o" :value="o" /></el-select></el-form-item></el-col>
            <el-col :span="8"><el-form-item label="阅读能力"><el-select v-model="form.reading" class="w-sm"><el-option v-for="o in gradeOptions" :key="o" :label="o" :value="o" /></el-select></el-form-item></el-col>
            <el-col :span="8"><el-form-item label="书写能力"><el-select v-model="form.writing" class="w-sm"><el-option v-for="o in gradeOptions" :key="o" :label="o" :value="o" /></el-select></el-form-item></el-col>
            <el-col :span="8"><el-form-item label="计算能力"><el-select v-model="form.math" class="w-sm"><el-option v-for="o in gradeOptions" :key="o" :label="o" :value="o" /></el-select></el-form-item></el-col>
          </el-row>
          <el-form-item label="综合结论"><el-input v-model="form.result" type="textarea" :rows="2" /></el-form-item>
          <el-divider content-position="left">行为评估</el-divider>
          <el-row :gutter="20">
            <el-col :span="8"><el-form-item label="评估日期"><el-date-picker v-model="form.behaviorDate" value-format="yyyy-MM-dd" style="width:100%" /></el-form-item></el-col>
            <el-col :span="8"><el-form-item label="评估日期"><el-date-picker v-model="form.behaviorDate" value-format="yyyy-MM-dd" class="w-md" /></el-form-item></el-col>
            <el-col :span="4"><el-form-item label="注意力"><el-input-number v-model="form.attention" :min="0" :max="36" /></el-form-item></el-col>
            <el-col :span="4"><el-form-item label="多动"><el-input-number v-model="form.hyperactivity" :min="0" :max="36" /></el-form-item></el-col>
            <el-col :span="4"><el-form-item label="冲动"><el-input-number v-model="form.impulsivity" :min="0" :max="36" /></el-form-item></el-col>
@@ -120,22 +145,29 @@
          </el-row>
          <el-row :gutter="20">
            <el-col :span="8"><el-form-item label="总分"><el-input-number v-model="form.totalScore" disabled /></el-form-item></el-col>
            <el-col :span="8"><el-form-item label="评价"><el-select v-model="form.evaluation" style="width:100%"><el-option label="正常范围" value="正常范围" /><el-option label="轻度异常" value="轻度异常" /><el-option label="中度异常" value="中度异常" /></el-select></el-form-item></el-col>
            <el-col :span="8"><el-form-item label="评价"><el-select v-model="form.evaluation" class="w-sm"><el-option label="正常范围" value="正常范围" /><el-option label="轻度异常" value="轻度异常" /><el-option label="中度异常" value="中度异常" /></el-select></el-form-item></el-col>
          </el-row>
          <el-divider content-position="left">干预训练记录</el-divider>
          <el-row :gutter="20">
            <el-col :span="8"><el-form-item label="训练日期"><el-date-picker v-model="form.trainDate" value-format="yyyy-MM-dd" style="width:100%" /></el-form-item></el-col>
            <el-col :span="8"><el-form-item label="训练类型"><el-input v-model="form.trainingType" /></el-form-item></el-col>
            <el-col :span="8"><el-form-item label="训练日期"><el-date-picker v-model="form.trainDate" value-format="yyyy-MM-dd" class="w-md" /></el-form-item></el-col>
            <el-col :span="8"><el-form-item label="训练类型"><el-input v-model="form.trainingType" class="w-sm" /></el-form-item></el-col>
            <el-col :span="8"><el-form-item label="时长(分钟)"><el-input-number v-model="form.duration" :min="10" :max="120" /></el-form-item></el-col>
          </el-row>
          <el-form-item label="训练内容"><el-input v-model="form.trainContent" type="textarea" :rows="2" /></el-form-item>
          <el-row :gutter="20">
            <el-col :span="8"><el-form-item label="训练师"><el-input v-model="form.trainer" /></el-form-item></el-col>
            <el-col :span="8"><el-form-item label="进展"><el-select v-model="form.progress" style="width:100%"><el-option label="良好" value="良好" /><el-option label="一般" value="一般" /><el-option label="缓慢" value="缓慢" /></el-select></el-form-item></el-col>
            <el-col :span="8"><el-form-item label="训练师"><el-input v-model="form.trainer" class="w-sm" /></el-form-item></el-col>
            <el-col :span="8"><el-form-item label="进展"><el-select v-model="form.progress" class="w-sm"><el-option label="良好" value="良好" /><el-option label="一般" value="一般" /><el-option label="缓慢" value="缓慢" /></el-select></el-form-item></el-col>
          </el-row>
        </el-form>
      </el-card>
        </el-tab-pane>
        <el-tab-pane label="服务记录" name="service">
          <ServiceRecords :patid="archive.patid" />
        </el-tab-pane>
      </el-tabs>
    </template>
    <div v-else class="empty-wrap">档案不存在或已删除</div>
  </div>
@@ -144,13 +176,15 @@
<script>
import EditableTable from "../components/EditableTable.vue";
import InfoField from "./InfoField.vue";
import ServiceRecords from "../components/ServiceRecords.vue";
import { loadArchives, saveArchives } from "../types";
export default {
  name: "ChildHealthArchiveDetail",
  components: { EditableTable, InfoField },
  components: { EditableTable, InfoField, ServiceRecords },
  data() {
    return {
      loading: false, archive: null, editing: false, activeTab: "growth",
      loading: false, archive: null, editing: true, activeTab: "growth", mainTab: "detail",
      relationOptions: [
        { label:"父亲", value:"father" },{ label:"母亲", value:"mother" },{ label:"祖父", value:"grandfather" },{ label:"祖母", value:"grandmother" },{ label:"其他", value:"other" },
      ],
@@ -203,21 +237,17 @@
      const map = { standalone:"独立建档", outpatient:"门诊记录", inpatient:"在院记录" };
      return map[this.archive?.sourcePage] || this.archive?.sourcePage || "-";
    },
    relationLabel() {
      const r = this.relationOptions.find(o => o.value === (this.archive?.parentInfo?.relation));
      return r ? r.label : "-";
    },
  },
  created() {
    const id = this.$route.query.id;
    if (id) this.loadArchive(id);
    if (id) this.loadArchive(id, this.$route.query.edit == 1);
  },
  methods: {
    loadArchive(id) {
    loadArchive(id, autoEdit) {
      this.loading = true;
      const all = JSON.parse(localStorage.getItem("patientArchives") || "[]");
      this.archive = all.find(item => item.id === id) || null;
      this.archive = loadArchives().find((item) => item.id === id) || null;
      if (this.archive) {
        if (!this.archive.parentInfo) this.archive.parentInfo = {};
        if (this.archive.subType === 'highRisk') {
          this.activeTab = "growth";
          this.growthData = this.archive.growthData || getDefaultGrowthData();
@@ -232,6 +262,7 @@
          Object.assign(this.form, this.archive.archiveForm.learningData);
        }
      }
      if (autoEdit) this.editing = true;
      this.loading = false;
    },
    toggleEdit() {
@@ -251,13 +282,24 @@
          this.archive.planData = JSON.parse(JSON.stringify(this.planData));
        }
        this.archive.updateTime = new Date().toISOString().replace("T"," ").substring(0,19);
        const all = JSON.parse(localStorage.getItem("patientArchives") || "[]");
        const all = loadArchives();
        const idx = all.findIndex(item => item.id === this.archive.id);
        if (idx >= 0) all[idx] = JSON.parse(JSON.stringify(this.archive));
        localStorage.setItem("patientArchives", JSON.stringify(all));
        saveArchives(all);
        this.$message.success("保存成功");
      }
      this.editing = !this.editing;
    },
    handleCloseCase() {
      this.$modal.confirm("确认将该档案结案?").then(() => {
        this.archive.closeStatus = 1;
        this.archive.updateTime = new Date().toISOString().replace("T"," ").substring(0,19);
        const all = loadArchives();
        const idx = all.findIndex(item => item.id === this.archive.id);
        if (idx >= 0) all[idx] = JSON.parse(JSON.stringify(this.archive));
        saveArchives(all);
        this.$message.success("已结案");
      }).catch(() => {});
    },
    subTypeTag(type) { const m={highRisk:"danger",nutrition:"warning",learning:"info"}; return m[type]||""; },
  },
@@ -297,6 +339,13 @@
</script>
<style lang="scss" scoped>
// è¾“入框宽度(按字段长短区分)
.w-xs { width: 90px; }
.w-sm { width: 150px; }
.w-md { width: 200px; }
.w-lg { width: 280px; }
.w-full { width: 100%; }
.action-bar { margin-bottom: 16px; }
.info-card { margin-bottom: 0; }
.card-header { display: flex; justify-content: space-between; align-items: center; font-size: 16px; font-weight: 600; }
src/views/patient/archives/components/ArchiveDialog.vue
@@ -1,15 +1,14 @@
<template>
  <el-dialog
    :title="dialogTitle"
    :title="standalone ? '新建建档' : '选择建档类型'"
    :visible.sync="dialogVisible"
    width="750px"
    width="640px"
    :close-on-click-modal="false"
    append-to-body
    @close="handleClose"
    top="3vh"
  >
    <!-- å·²é€‰æ‚£è€…预览(关联建档模式) -->
    <div class="archive-section" v-if="selectedPatients.length > 0">
    <div class="archive-section" v-if="!standalone && selectedPatients.length > 0">
      <div class="section-title">已选患者({{ selectedPatients.length }}人)</div>
      <el-table :data="selectedPatients" border size="small" max-height="180">
        <el-table-column prop="patname" label="姓名" width="80" align="center" />
@@ -52,186 +51,24 @@
      <el-divider />
    </div>
    <!-- å„¿ä¿ç§‘:家长信息(必填) -->
    <div class="archive-section" v-if="mainCategory === 'child'">
      <div class="section-title">家长/监护人信息</div>
      <el-form :model="parentInfo" label-width="100px" size="small" inline>
        <el-form-item label="家长姓名" required>
          <el-input v-model="parentInfo.parentName" placeholder="请输入" style="width:150px" />
        </el-form-item>
        <el-form-item label="与儿童关系">
          <el-select v-model="parentInfo.relation" style="width:120px">
            <el-option label="父亲" value="father" />
            <el-option label="母亲" value="mother" />
            <el-option label="祖父" value="grandfather" />
            <el-option label="祖母" value="grandmother" />
            <el-option label="其他" value="other" />
          </el-select>
        </el-form-item>
        <el-form-item label="联系电话" required>
          <el-input v-model="parentInfo.parentPhone" placeholder="联系电话" style="width:150px" />
        </el-form-item>
        <el-form-item label="身份证号">
          <el-input v-model="parentInfo.parentIdCard" placeholder="身份证号" style="width:200px" />
        </el-form-item>
        <el-form-item label="联系地址">
          <el-input v-model="parentInfo.address" placeholder="联系地址" style="width:300px" />
        </el-form-item>
      </el-form>
      <el-divider />
    </div>
    <!-- å»ºæ¡£ä¸»åˆ†ç±» -->
    <!-- ä¸“案类型选择 -->
    <div class="archive-section">
      <div class="section-title">选择建档类型</div>
      <el-radio-group v-model="mainCategory" size="medium" @change="onMainCategoryChange">
        <el-radio-button label="child">儿保科建档</el-radio-button>
        <el-radio-button label="maternal">妇保科建档</el-radio-button>
      </el-radio-group>
    </div>
    <!-- å­ç±»åž‹ -->
    <div class="archive-section" v-if="mainCategory">
      <div class="section-title">选择专案类型</div>
      <el-radio-group v-model="subType" size="small" @change="onSubTypeChange">
        <template v-if="mainCategory === 'child'">
          <el-radio-button label="highRisk">高危儿专案管理</el-radio-button>
          <el-radio-button label="nutrition">营养性疾病专案管理</el-radio-button>
          <el-radio-button label="learning">学习困难专案管理</el-radio-button>
        </template>
        <template v-else>
          <el-radio-button label="pregnancy">孕妇随访</el-radio-button>
          <el-radio-button label="menopause">更年期随访</el-radio-button>
        </template>
      <el-radio-group v-model="subType" size="medium">
        <el-radio-button v-for="t in typeOptions" :key="t.key" :label="t.key">{{ t.entryLabel }}</el-radio-button>
      </el-radio-group>
    </div>
    <!-- å»ºæ¡£ä¸“属字段 -->
    <div class="archive-section" v-if="subType">
      <div class="section-title">建档信息</div>
      <el-form :model="archiveForm" label-width="120px" size="small">
        <!-- å„¿ä¿ç§‘ - é«˜å±å„¿ -->
        <template v-if="mainCategory === 'child' && subType === 'highRisk'">
          <el-form-item label="高危因素">
            <el-select v-model="archiveForm.riskFactor" multiple placeholder="可多选" style="width:100%">
              <el-option label="早产/低体重" value="preterm" />
              <el-option label="窒息史" value="asphyxia" />
              <el-option label="颅内出血" value="ich" />
              <el-option label="高胆红素血症" value="hyperbilirubinemia" />
              <el-option label="宫内感染" value="intrauterine" />
              <el-option label="先天畸形" value="malformation" />
              <el-option label="其他" value="other" />
            </el-select>
          </el-form-item>
          <el-form-item label="出生体重(g)">
            <el-input-number v-model="archiveForm.birthWeight" :min="500" :max="6000" />
          </el-form-item>
          <el-form-item label="出生孕周">
            <el-input-number v-model="archiveForm.gestationalWeek" :min="22" :max="42" :precision="1" /> å‘¨
          </el-form-item>
          <el-form-item label="建档医生">
            <el-input v-model="archiveForm.doctorName" placeholder="请输入建档医生" />
          </el-form-item>
          <el-form-item label="备注">
            <el-input v-model="archiveForm.remark" type="textarea" :rows="2" />
          </el-form-item>
        </template>
        <!-- å„¿ä¿ç§‘ - è¥å…»æ€§ç–¾ç—… -->
        <template v-if="mainCategory === 'child' && subType === 'nutrition'">
          <el-form-item label="营养诊断" required>
            <el-select v-model="archiveForm.nutritionDiagnosis" placeholder="请选择" style="width:100%">
              <el-option label="营养不良" value="malnutrition" />
              <el-option label="超重" value="overweight" />
              <el-option label="肥胖" value="obesity" />
              <el-option label="微量元素缺乏" value="micronutrient" />
              <el-option label="喂养困难" value="feeding" />
            </el-select>
          </el-form-item>
          <el-form-item label="建档医生">
            <el-input v-model="archiveForm.doctorName" placeholder="请输入建档医生" />
          </el-form-item>
          <el-form-item label="备注">
            <el-input v-model="archiveForm.remark" type="textarea" :rows="2" />
          </el-form-item>
        </template>
        <!-- å„¿ä¿ç§‘ - å­¦ä¹ å›°éš¾ -->
        <template v-if="mainCategory === 'child' && subType === 'learning'">
          <el-form-item label="学习问题" required>
            <el-select v-model="archiveForm.learningIssue" placeholder="请选择" style="width:100%">
              <el-option label="阅读障碍" value="dyslexia" />
              <el-option label="书写障碍" value="dysgraphia" />
              <el-option label="计算障碍" value="dyscalculia" />
              <el-option label="注意力缺陷" value="adhd" />
              <el-option label="混合型" value="mixed" />
            </el-select>
          </el-form-item>
          <el-form-item label="建档医生">
            <el-input v-model="archiveForm.doctorName" placeholder="请输入建档医生" />
          </el-form-item>
          <el-form-item label="备注">
            <el-input v-model="archiveForm.remark" type="textarea" :rows="2" />
          </el-form-item>
        </template>
        <!-- å¦‡ä¿ç§‘ - å­•妇随访 -->
        <template v-if="mainCategory === 'maternal' && subType === 'pregnancy'">
          <el-form-item label="末次月经">
            <el-date-picker v-model="archiveForm.lmp" type="date" value-format="yyyy-MM-dd" placeholder="选择日期" style="width:100%" />
          </el-form-item>
          <el-row :gutter="12">
            <el-col :span="8"><el-form-item label="孕周"><el-input-number v-model="archiveForm.gestationalWeek" :min="1" :max="42" :precision="1" /> å‘¨</el-form-item></el-col>
            <el-col :span="8"><el-form-item label="孕次"><el-input-number v-model="archiveForm.gravidity" :min="1" :max="20" /></el-form-item></el-col>
            <el-col :span="8"><el-form-item label="产次"><el-input-number v-model="archiveForm.parity" :min="0" :max="20" /></el-form-item></el-col>
          </el-row>
          <el-form-item label="预产期">
            <el-date-picker v-model="archiveForm.edd" type="date" value-format="yyyy-MM-dd" placeholder="选择预产期" style="width:100%" />
          </el-form-item>
          <el-form-item label="高危妊娠">
            <el-switch v-model="archiveForm.highRiskPregnancy" active-text="是" inactive-text="否" />
          </el-form-item>
          <el-form-item label="建档医生">
            <el-input v-model="archiveForm.doctorName" placeholder="请输入建档医生" />
          </el-form-item>
          <el-form-item label="备注">
            <el-input v-model="archiveForm.remark" type="textarea" :rows="2" />
          </el-form-item>
        </template>
        <!-- å¦‡ä¿ç§‘ - æ›´å¹´æœŸéšè®¿ -->
        <template v-if="mainCategory === 'maternal' && subType === 'menopause'">
          <el-form-item label="月经状态">
            <el-select v-model="archiveForm.menstrualStatus" placeholder="请选择" style="width:100%">
              <el-option label="月经紊乱" value="irregular" />
              <el-option label="闭经(<12个月)" value="amenorrhea_short" />
              <el-option label="绝经(≥12个月)" value="menopause" />
              <el-option label="人工绝经" value="surgical" />
            </el-select>
          </el-form-item>
          <el-form-item label="末次月经">
            <el-date-picker v-model="archiveForm.lmp" type="date" value-format="yyyy-MM-dd" placeholder="选择日期" style="width:100%" />
          </el-form-item>
          <el-form-item label="建档医生">
            <el-input v-model="archiveForm.doctorName" placeholder="请输入建档医生" />
          </el-form-item>
          <el-form-item label="备注">
            <el-input v-model="archiveForm.remark" type="textarea" :rows="2" />
          </el-form-item>
        </template>
      </el-form>
    </div>
    <div slot="footer" class="dialog-footer">
      <el-button @click="dialogVisible = false">取 æ¶ˆ</el-button>
      <el-button type="primary" :loading="submitting" @click="handleSubmit" :disabled="!canSubmit">
        ç¡®è®¤å»ºæ¡£
      </el-button>
      <el-button type="primary" :loading="submitting" @click="handleSubmit" :disabled="!canSubmit">确认建档</el-button>
    </div>
  </el-dialog>
</template>
<script>
import { ARCHIVE_TYPES, loadArchives, saveArchives, buildArchiveRecord } from "../types";
export default {
  name: "ArchiveDialog",
  props: {
@@ -243,33 +80,20 @@
  data() {
    return {
      dialogVisible: false,
      mainCategory: "",
      subType: "",
      submitting: false,
      standalonePatient: { patname: "", sex: 2, age: "", telcode: "", deptname: "", diagname: "" },
      parentInfo: { parentName: "", relation: "", parentPhone: "", parentIdCard: "", address: "" },
      archiveForm: {
        riskFactor: [], birthWeight: null, gestationalWeek: null,
        nutritionDiagnosis: "", learningIssue: "",
        gravidity: 1, parity: 0, edd: "", lmp: "",
        highRiskPregnancy: false, menstrualStatus: "",
        doctorName: "", remark: "",
      },
    };
  },
  computed: {
    dialogTitle() {
      return this.standalone ? "新建建档" : "患者建档";
    },
    typeOptions() { return ARCHIVE_TYPES; },
    canSubmit() {
      if (!this.subType) return false;
      if (this.mainCategory === 'child' && !this.parentInfo.parentName) return false;
      if (this.standalone && !this.standalonePatient.patname) return false;
      if (!this.standalone && this.selectedPatients.length === 0) return false;
      return true;
    },
    selectedPatients() {
      return this.patients || [];
    },
    selectedPatients() { return this.patients || []; },
  },
  watch: {
    visible(val) {
@@ -282,83 +106,31 @@
  },
  methods: {
    resetForm() {
      this.mainCategory = "";
      this.subType = "";
      this.standalonePatient = { patname: "", sex: 2, age: "", telcode: "", deptname: "", diagname: "" };
      this.parentInfo = { parentName: "", relation: "", parentPhone: "", parentIdCard: "", address: "" };
      this.archiveForm = {
        riskFactor: [], birthWeight: null, gestationalWeek: null,
        nutritionDiagnosis: "", learningIssue: "",
        gravidity: 1, parity: 0, edd: "", lmp: "",
        highRiskPregnancy: false, menstrualStatus: "",
        doctorName: "", remark: "",
      };
    },
    onMainCategoryChange() { this.subType = ""; },
    onSubTypeChange() {},
    getSubTypeLabel() {
      const map = { highRisk: "高危儿专案管理", nutrition: "营养性疾病专案管理", learning: "学习困难专案管理", pregnancy: "孕妇随访", menopause: "更年期随访" };
      return map[this.subType] || "";
    },
    getMainCategoryLabel() {
      return this.mainCategory === "child" ? "儿保科建档" : "妇保科建档";
    },
    handleSubmit() {
      if (!this.subType) { this.$message.warning("请选择专案类型"); return; }
      if (this.standalone && !this.standalonePatient.patname) { this.$message.warning("请输入姓名"); return; }
      if (!this.standalone && this.selectedPatients.length === 0) { this.$message.warning("请选择患者"); return; }
      this.submitting = true;
      const archives = JSON.parse(localStorage.getItem("patientArchives") || "[]");
      const now = new Date().toISOString().replace("T", " ").substring(0, 19);
      // ç¡®å®šæ‚£è€…列表
      const patientList = this.standalone
        ? [{ ...this.standalonePatient, patid: "sa_" + Date.now() }]
        : this.selectedPatients;
      patientList.forEach((patient) => {
        archives.push({
          id: "arc_" + Date.now() + "_" + Math.random().toString(36).substr(2, 6),
          patientId: patient.patid || patient.id,
          patname: patient.patname,
          sex: patient.sex,
          age: patient.age,
          telcode: patient.telcode || patient.phone || "",
          deptname: patient.deptname || "",
          diagname: patient.diagname || "",
          inhospno: patient.inhospno || "",
          outhospno: patient.outhospno || "",
          bedNo: patient.bedNo || "",
          drname: patient.drname || "",
          nurseName: patient.nurseName || "",
          starttime: patient.starttime || patient.admitdate || "",
          mainCategory: this.mainCategory,
          mainCategoryLabel: this.getMainCategoryLabel(),
          subType: this.subType,
          subTypeLabel: this.getSubTypeLabel(),
          archiveForm: JSON.parse(JSON.stringify(this.archiveForm)),
          parentInfo: this.mainCategory === 'child' ? JSON.parse(JSON.stringify(this.parentInfo)) : {},
          sourcePage: this.standalone ? "standalone" : this.sourcePage,
          status: "active",
          createTime: now,
          updateTime: now,
        });
      });
      localStorage.setItem("patientArchives", JSON.stringify(archives));
      const patientList = this.standalone ? [this.standalonePatient] : this.selectedPatients;
      const sourcePage = this.standalone ? "standalone" : this.sourcePage;
      const records = patientList.map((p) => buildArchiveRecord(p, this.subType, sourcePage));
      saveArchives([...loadArchives(), ...records]);
      this.submitting = false;
      this.dialogVisible = false;
      this.$message.success(`成功创建 ${patientList.length} æ¡å»ºæ¡£è®°å½•`);
      this.$message.success(`成功创建 ${records.length} æ¡å»ºæ¡£è®°å½•`);
      this.$emit("created");
      // è·³è½¬
      const routePath = this.mainCategory === "child"
        ? "/filing/childHealth"
        : "/filing/maternalHealth";
      this.$router.push({ path: routePath });
      // è¿›å…¥è¯¦æƒ…页(编辑态)
      const first = records[0];
      const type = ARCHIVE_TYPES.find((t) => t.key === this.subType);
      if (first && type) {
        this.$router.push({ path: type.detailRoute, query: { id: first.id, edit: 1 } });
      }
    },
    handleClose() { this.resetForm(); },
  },
src/views/patient/archives/components/ArchiveList.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,148 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="80px">
      <el-form-item label="姓名">
        <el-input v-model="queryParams.patname" placeholder="请输入姓名" clearable @keyup.enter.native="handleQuery" style="width:180px" />
      </el-form-item>
      <el-form-item label="诊断">
        <el-input v-model="queryParams.diagname" placeholder="请输入诊断" clearable @keyup.enter.native="handleQuery" style="width:180px" />
      </el-form-item>
      <el-form-item label="建档日期">
        <el-date-picker v-model="dateRange" style="width:240px" value-format="yyyy-MM-dd" type="daterange"
          range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" icon="el-icon-search" size="medium" @click="handleQuery">搜索</el-button>
        <el-button icon="el-icon-refresh" size="medium" @click="resetQuery">重置</el-button>
      </el-form-item>
    </el-form>
    <el-row :gutter="10" class="mb8">
      <el-col :span="1.5">
        <el-button type="primary" plain icon="el-icon-plus" size="medium" @click="handleCreate">新建建档</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button type="danger" plain icon="el-icon-delete" size="medium" :disabled="multiple" @click="handleDelete">删除</el-button>
      </el-col>
      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
    </el-row>
    <el-table v-loading="loading" :data="displayList" @selection-change="handleSelectionChange">
      <el-table-column type="selection" width="50" align="center" />
      <el-table-column
        v-for="col in typeConfig.columns"
        :key="col.prop"
        :label="col.label"
        align="center"
        show-overflow-tooltip
      >
        <template slot-scope="scope">
          <el-tag v-if="col.type === 'closeStatus'" :type="scope.row.closeStatus == 1 ? 'info' : 'success'" size="mini">
            {{ cellValue(scope.row, col) }}
          </el-tag>
          <span v-else>{{ cellValue(scope.row, col) }}</span>
        </template>
      </el-table-column>
      <el-table-column label="操作" align="center" fixed="right" width="120">
        <template slot-scope="scope">
          <el-button size="medium" type="text" @click="goDetail(scope.row)">
            <span class="button-textsc"><i class="el-icon-zoom-in" />查看</span>
          </el-button>
          <el-button size="medium" type="text" @click="handleSingleDelete(scope.row)">
            <span style="color:#f56c6c"><i class="el-icon-delete" />删除</span>
          </el-button>
        </template>
      </el-table-column>
    </el-table>
    <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize"
      @pagination="getList" />
    <OutpatientPicker :visible.sync="pickerVisible" :type="type" @picked="onPicked" />
  </div>
</template>
<script>
import OutpatientPicker from "./OutpatientPicker.vue";
import { getTypeByKey, loadArchives, saveArchives } from "../types";
export default {
  name: "ArchiveList",
  components: { OutpatientPicker },
  props: {
    type: { type: String, default: "" },
  },
  data() {
    return {
      loading: false, showSearch: true, multiple: true, ids: [], dateRange: [],
      total: 0, allList: [], displayList: [], pickerVisible: false,
      queryParams: { pageNum: 1, pageSize: 10, patname: "", diagname: "" },
    };
  },
  computed: {
    typeConfig() {
      return getTypeByKey(this.type) || { columns: [], detailRoute: "/filing/maternalDetail" };
    },
  },
  created() { this.getList(); },
  methods: {
    getList() {
      this.loading = true;
      const all = loadArchives().filter((item) => item.subType === this.type);
      let filtered = [...all];
      if (this.queryParams.patname) filtered = filtered.filter((i) => i.patname && i.patname.includes(this.queryParams.patname));
      if (this.queryParams.diagname) filtered = filtered.filter((i) => i.diagname && i.diagname.includes(this.queryParams.diagname));
      if (this.dateRange && this.dateRange.length === 2) {
        filtered = filtered.filter((i) => {
          const d = (i.createTime || "").substring(0, 10);
          return d >= this.dateRange[0] && d <= this.dateRange[1];
        });
      }
      this.total = filtered.length;
      const start = (this.queryParams.pageNum - 1) * this.queryParams.pageSize;
      this.displayList = filtered.slice(start, start + this.queryParams.pageSize);
      this.loading = false;
    },
    handleQuery() { this.queryParams.pageNum = 1; this.getList(); },
    resetQuery() {
      this.dateRange = [];
      this.queryParams = { pageNum: 1, pageSize: 10, patname: "", diagname: "" };
      this.getList();
    },
    handleSelectionChange(s) { this.ids = s.map((i) => i.id); this.multiple = !s.length; },
    handleDelete() {
      if (this.ids.length === 0) { this.$modal.msgWarning("请至少选择一条"); return; }
      this.$modal.confirm("确认删除选中档案?").then(() => {
        const remaining = loadArchives().filter((item) => !this.ids.includes(item.id));
        saveArchives(remaining);
        this.$modal.msgSuccess("删除成功");
        this.getList();
      }).catch(() => {});
    },
    handleSingleDelete(row) { this.ids = [row.id]; this.handleDelete(); },
    handleCreate() { this.pickerVisible = true; },
    onPicked(record) {
      this.getList();
      this.$router.push({ path: this.typeConfig.detailRoute, query: { id: record.id, edit: 1 } });
    },
    goDetail(row) {
      this.$router.push({ path: this.typeConfig.detailRoute, query: { id: row.id } });
    },
    getProp(obj, path) {
      if (!path) return "";
      return path.split(".").reduce((o, k) => (o == null ? o : o[k]), obj);
    },
    cellValue(row, col) {
      const val = this.getProp(row, col.prop);
      if (col.type === "sex") return val == 1 ? "男" : "女";
      if (col.type === "date") return val ? String(val).substring(0, 10) : "-";
      if (col.type === "closeStatus") return val == 1 ? "已结案" : "未结案";
      return val || "-";
    },
  },
};
</script>
<style lang="scss" scoped>
.button-textsc { color: #3664d9; }
</style>
src/views/patient/archives/components/OutpatientPicker.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,145 @@
<template>
  <el-dialog
    title="选择门诊记录"
    :visible.sync="dialogVisible"
    width="85%"
    append-to-body
    top="4vh"
    :close-on-click-modal="false"
    @close="handleClose"
  >
    <el-form :inline="true" size="small" label-width="56px">
      <el-form-item label="姓名">
        <el-input v-model="queryParams.patname" placeholder="请输入姓名" clearable style="width:160px" @keyup.enter.native="handleQuery" />
      </el-form-item>
      <el-form-item label="诊断">
        <el-input v-model="queryParams.diagname" placeholder="请输入诊断" clearable style="width:160px" @keyup.enter.native="handleQuery" />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
        <el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
      </el-form-item>
    </el-form>
    <el-table
      v-loading="loading"
      :data="list"
      border
      highlight-current-row
      style="width: 100%"
      @current-change="handleCurrentChange"
    >
      <el-table-column label="就诊时间" align="center" width="140">
        <template slot-scope="scope">{{ formatTime(scope.row.admitdate) }}</template>
      </el-table-column>
      <el-table-column label="病案号" align="center" prop="outhospno" width="110" />
      <el-table-column label="姓名" align="center" prop="patname" width="100" />
      <el-table-column label="性别" align="center" width="60">
        <template slot-scope="scope">{{ scope.row.sex == 1 ? "男" : "女" }}</template>
      </el-table-column>
      <el-table-column label="年龄" align="center" prop="age" width="60" />
      <el-table-column label="联系电话" align="center" prop="telcode" width="120" />
      <el-table-column label="诊断" align="center" prop="diagname" show-overflow-tooltip />
      <el-table-column label="就诊科室" align="center" prop="deptname" show-overflow-tooltip />
      <el-table-column label="接诊医生" align="center" prop="drname" width="100" />
    </el-table>
    <pagination
      v-show="total > 0"
      :total="total"
      :page.sync="queryParams.pageNum"
      :limit.sync="queryParams.pageSize"
      @pagination="getList"
    />
    <div slot="footer" class="dialog-footer">
      <el-button @click="dialogVisible = false">取 æ¶ˆ</el-button>
      <el-button type="primary" :disabled="!currentRow" @click="handleConfirm">确定</el-button>
    </div>
  </el-dialog>
</template>
<script>
import { listPatouthosp } from "@/api/smartor/patouthosp";
import { loadArchives, saveArchives, buildArchiveRecord } from "../types";
import store from "@/store";
export default {
  name: "OutpatientPicker",
  props: {
    visible: { type: Boolean, default: false },
    type: { type: String, default: "" },
    sourcePage: { type: String, default: "outpatient" },
  },
  data() {
    return {
      dialogVisible: false,
      loading: false,
      list: [],
      total: 0,
      currentRow: null,
      queryParams: { pageNum: 1, pageSize: 10, patname: "", diagname: "", searchscope: 3, deptcodes: [] },
    };
  },
  watch: {
    visible(val) {
      this.dialogVisible = val;
      if (val) {
        this.currentRow = null;
        this.getList();
      }
    },
    dialogVisible(val) {
      if (!val) this.$emit("update:visible", false);
    },
  },
  methods: {
    getList() {
      this.loading = true;
      if (this.queryParams.searchscope == 3) {
        this.queryParams.deptcodes = store.getters.belongDepts.map((obj) => obj.deptCode);
      }
      listPatouthosp(this.queryParams)
        .then((response) => {
          this.list = response.rows || [];
          this.total = response.total || 0;
        })
        .finally(() => { this.loading = false; });
    },
    handleQuery() {
      this.queryParams.pageNum = 1;
      this.getList();
    },
    resetQuery() {
      this.queryParams = { pageNum: 1, pageSize: 10, patname: "", diagname: "", searchscope: 3, deptcodes: [] };
      this.currentRow = null;
      this.getList();
    },
    handleCurrentChange(row) {
      this.currentRow = row;
    },
    formatTime(time) {
      if (!time) return "-";
      return String(time).substring(0, 19).replace("T", " ");
    },
    handleConfirm() {
      if (!this.currentRow) return;
      const record = buildArchiveRecord(this.currentRow, this.type, this.sourcePage);
      const all = loadArchives();
      all.push(record);
      saveArchives(all);
      this.dialogVisible = false;
      this.$emit("picked", record);
    },
    handleClose() {
      this.currentRow = null;
    },
  },
};
</script>
<style lang="scss" scoped>
.dialog-footer {
  text-align: right;
}
</style>
src/views/patient/archives/components/ServiceRecords.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,344 @@
<template>
  <el-card class="service-records" shadow="never" style="margin-top: 14px">
    <div slot="header" class="card-header"><span>服务记录</span></div>
    <div v-loading="loading">
      <div v-if="record.length === 0" class="empty-mini">暂无服务记录</div>
      <div v-else class="service-record">
        <div class="service-item" v-for="(item, idx) in record" :key="idx">
          <el-card shadow="never" class="service-card">
            <!-- æ ‡é¢˜è¡Œï¼šæœåŠ¡ç±»åž‹ + ä»»åŠ¡åç§° + éšè®¿çŠ¶æ€ + å¼‚常状态 -->
            <div class="service-card__header">
              <div class="service-card__title">
                <span class="service-badge">{{ typeName(item.serviceType) }}</span>
                <span class="service-name">{{ item.taskName || item.templatename || "-" }}</span>
              </div>
              <div class="service-card__tags">
                <el-tag v-if="item.sendstateView == 1" type="primary" size="small" effect="dark">待随访</el-tag>
                <el-tag v-else-if="item.sendstateView == 2" type="success" size="small" effect="dark">已完成</el-tag>
                <el-tag v-else-if="item.sendstateView == 3" type="warning" size="small" effect="dark">无需随访</el-tag>
                <span class="service-status" :class="item.excep == 0 ? 'is-ok' : 'is-bad'">
                  {{ item.excep == 0 ? "服务正常" : item.excep == 2 ? "警告" : "异常" }}
                </span>
              </div>
            </div>
            <!-- æ—¶é—´çº¿ï¼šå‡ºé™¢ â†’ åº”访 â†’ å®Œæˆ -->
            <div class="service-card__timeline">
              <span><em>出院</em>{{ fmtTime(item.endtime) }}</span>
              <i>→</i>
              <span><em>应访</em>{{ fmtTime(item.visitTime) }}</span>
              <i>→</i>
              <span><em>完成</em>{{ fmtTime(item.finishtime) }}</span>
            </div>
            <!-- ä¸‰æ ä¸»ä½“ -->
            <div class="service-card__columns">
              <!-- â‘  æ‚£è€…信息 -->
              <div class="svc-block svc-block--blue">
                <div class="svc-block__title"><i class="el-icon-user-solid"></i>患者信息</div>
                <div class="svc-block__row">
                  <span class="svc-name">{{ item.sendname || "-" }}</span>
                  <span class="svc-sub">{{ sexName(item.sex) }}</span>
                  <span class="svc-sub">{{ item.phone || "-" }}</span>
                  <span class="svc-sub" v-if="item.endtime">出院{{ endDay(item.endtime) }}天</span>
                </div>
                <div class="svc-block__row svc-sub" v-if="item.sfzh">身份证:{{ item.sfzh }}</div>
                <div class="svc-block__row" v-if="item.leavediagname"><span class="svc-diag">诊断</span>{{ item.leavediagname }}</div>
              </div>
              <!-- â‘¡ ç§‘室病区 Â· åŒ»æŠ¤ -->
              <div class="svc-block svc-block--green">
                <div class="svc-block__title"><i class="el-icon-office-building"></i>科室病区 Â· åŒ»æŠ¤</div>
                <div class="svc-block__row">科室:{{ item.deptname || "-" }} / ç—…区:{{ item.leavehospitaldistrictname || "-" }}</div>
                <div class="svc-block__row">
                  ä¸»æ²»ï¼š{{ item.drname || "-" }}
                  <span class="svc-sub">经管:{{ item.managementDoctor || "-" }}</span>
                  <span class="svc-sub">护士:{{ item.nurseName || "-" }}</span>
                </div>
                <div class="svc-block__row svc-sub">随访人员:{{ item.updateBy || "-" }}</div>
                <div class="svc-block__row svc-sub">负责人:{{ item.createBy || "-" }}</div>
              </div>
              <!-- â‘¢ æ‰§è¡Œè¯¦æƒ… -->
              <div class="svc-block svc-block--gray">
                <div class="svc-block__title"><i class="el-icon-s-management"></i>执行详情</div>
                <div class="svc-meta">
                  <span v-if="item.sendstate || item.sendstate === 0"><em>状态</em>{{ sendStateName(item.sendstate) }}</span>
                  <span v-if="preach(item.preachform)"><em>执行</em>{{ preach(item.preachform) }}</span>
                  <span v-if="item.templatename"><em>模板</em>{{ item.templatename }}</span>
                  <span v-if="item.suggest !== undefined && item.suggest !== null && item.suggest !== ''"><em>意见</em><dict-tag :options="dict.type.sys_suggest" :value="item.suggest" /></span>
                  <span v-if="item.remark"><em>说明</em>{{ item.remark }}</span>
                  <span v-if="item.createTime"><em>创建</em>{{ fmtTime(item.createTime) }}</span>
                </div>
              </div>
            </div>
          </el-card>
        </div>
      </div>
    </div>
  </el-card>
</template>
<script>
import { getTaskservelist } from "@/api/AiCentre/index";
export default {
  name: "ServiceRecords",
  dicts: ["sys_suggest"],
  props: {
    patid: { default: "" },
  },
  data() {
    return { loading: false, record: [] };
  },
  watch: {
    patid: { immediate: true, handler(v) { if (v) this.load(v); } },
  },
  methods: {
    load(patid) {
      this.loading = true;
      getTaskservelist({ patid })
        .then((res) => {
          if (res.code === 200 && res.rows && res.rows[0]) {
            this.record = res.rows[0].serviceSubtaskList || [];
          } else {
            this.record = [];
          }
        })
        .catch(() => { this.record = []; })
        .finally(() => { this.loading = false; });
    },
    typeName(t) {
      if (t == 1) return "心电随访";
      if (t == 2) return "出院随访";
      if (t == 3) return "影像随访";
      if (t == 4) return "宣教关怀";
      return "其他服务";
    },
    sexName(s) {
      if (s == 1) return "男";
      if (s == 2) return "女";
      return "-";
    },
    sendStateName(s) {
      const map = { 1: "已领取", 2: "待随访", 3: "已发送", 4: "不执行", 5: "失败", 6: "已完成", 7: "超时" };
      return map[s] || "-";
    },
    fmtTime(t) {
      if (!t) return "-";
      const s = String(t).replace("T", " ");
      return s.length > 16 ? s.substring(0, 16) : s.substring(0, 10);
    },
    endDay(t) {
      if (!t) return "-";
      const d = new Date(String(t).replace("T", " ").replace(/-/g, "/"));
      const diff = Date.now() - d.getTime();
      return Math.round(diff / (1000 * 3600 * 24));
    },
    preach(v) {
      if (!v) return "";
      return Array.isArray(v) ? v.join("、") : String(v);
    },
  },
};
</script>
<style lang="scss" scoped>
$primary: #3f6ef0;
.service-records {
  .card-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-size: 16px;
    font-weight: 600;
  }
}
.empty-mini {
  padding: 30px 0;
  text-align: center;
  color: #c0c4cc;
  font-size: 14px;
}
.service-record {
  .service-item {
    margin-bottom: 16px;
    &:last-child {
      margin-bottom: 0;
    }
  }
  .service-card {
    border-radius: 12px;
    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
    ::v-deep .el-card__body {
      padding: 20px 24px;
    }
  }
  .service-card__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding-bottom: 14px;
    border-bottom: 1px dashed #eef0f4;
  }
  .service-card__title {
    display: flex;
    align-items: center;
    gap: 12px;
    min-width: 0;
  }
  .service-card__tags {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
  }
  .service-badge {
    display: inline-block;
    padding: 4px 12px;
    font-size: 13px;
    font-weight: 600;
    color: $primary;
    background: #eef4ff;
    border-radius: 6px;
    white-space: nowrap;
  }
  .service-name {
    font-size: 16px;
    font-weight: 600;
    color: #303133;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .service-status {
    padding: 4px 12px;
    font-size: 13px;
    border-radius: 999px;
    &.is-ok {
      color: #12a182;
      background: #e8f7f1;
    }
    &.is-bad {
      color: #e0564c;
      background: #fdeeee;
    }
  }
  .service-card__timeline {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    padding: 12px 0;
    font-size: 13px;
    color: #606266;
    em {
      font-style: normal;
      color: #909399;
      margin-right: 2px;
    }
    i {
      color: #c0c4cc;
      font-style: normal;
    }
  }
  .service-card__columns {
    display: flex;
    gap: 12px;
  }
  .svc-block {
    flex: 1;
    min-width: 0;
    border-radius: 6px;
    padding: 10px 12px;
    font-size: 13px;
    &--blue {
      background: #ecf5ff;
      .svc-block__title { color: #409eff; }
    }
    &--green {
      background: #edf7f0;
      .svc-block__title { color: #67c23a; }
    }
    &--gray {
      background: #f5f7fa;
      .svc-block__title { color: #909399; }
    }
  }
  .svc-block__title {
    font-size: 12px;
    font-weight: 600;
    margin-bottom: 6px;
    i { margin-right: 4px; }
  }
  .svc-block__row {
    color: #303133;
    line-height: 1.7;
    font-size: 13px;
    word-break: break-all;
  }
  .svc-sub {
    color: #909399;
    font-size: 12px;
    margin-left: 8px;
  }
  .svc-name {
    color: #409eff;
    font-weight: 600;
    cursor: default;
  }
  .svc-diag {
    display: inline-block;
    font-size: 11px;
    color: #e6a23c;
    background: #fdf6ec;
    padding: 1px 6px;
    border-radius: 3px;
    margin-right: 6px;
  }
  .svc-meta {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 12px;
    font-size: 12px;
    color: #606266;
    span { white-space: normal; }
    em {
      font-style: normal;
      color: #909399;
      margin-right: 2px;
    }
  }
}
</style>
src/views/patient/archives/highRisk/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
<template>
  <ArchiveList type="highRisk" />
</template>
<script>
import ArchiveList from "../components/ArchiveList.vue";
export default {
  name: "HighRiskArchiveList",
  components: { ArchiveList },
};
</script>
src/views/patient/archives/learning/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
<template>
  <ArchiveList type="learning" />
</template>
<script>
import ArchiveList from "../components/ArchiveList.vue";
export default {
  name: "LearningArchiveList",
  components: { ArchiveList },
};
</script>
src/views/patient/archives/maternalHealth/detail.vue
@@ -3,253 +3,359 @@
    <template v-if="archive">
      <div class="action-bar">
        <el-button @click="$router.go(-1)" icon="el-icon-back">返回</el-button>
        <el-button :type="editing ? 'success' : 'primary'" :icon="editing ? 'el-icon-check' : 'el-icon-edit'" @click="toggleEdit">{{ editing ? '保存' : '编辑' }}</el-button>
        <el-tag :type="archive.subType === 'pregnancy' ? 'success' : 'warning'" style="margin-left:12px">{{ archive.subTypeLabel }}</el-tag>
        <el-button :type="editing ? 'success' : 'primary'" :icon="editing ? 'el-icon-check' : 'el-icon-edit'" @click="toggleEdit">
          {{ editing ? '保存' : '编辑' }}
        </el-button>
        <el-button v-if="archive.closeStatus != 1" type="danger" plain icon="el-icon-circle-close" @click="handleCloseCase">结案</el-button>
        <el-tag :type="archive.closeStatus == 1 ? 'info' : 'success'" style="margin-left:12px">
          {{ archive.closeStatus == 1 ? '已结案' : '未结案' }}
        </el-tag>
        <el-tag :type="archive.subType === 'pregnancy' ? 'success' : 'warning'" style="margin-left:8px">{{ archive.subTypeLabel }}</el-tag>
      </div>
      <!-- æ‚£è€…基本信息 -->
      <el-card class="info-card" shadow="never">
        <div slot="header" class="card-header"><span>患者基本信息</span></div>
        <el-row :gutter="20">
          <el-col :span="6"><InfoField label="姓名" :value="archive.patname" /></el-col>
          <el-col :span="6"><InfoField label="性别" :value="archive.sex == 1 ? '男' : '女'" /></el-col>
          <el-col :span="6"><InfoField label="年龄" :value="archive.age" /></el-col>
          <el-col :span="6"><InfoField label="电话" :value="archive.telcode" /></el-col>
        </el-row>
        <el-row :gutter="20" style="margin-top:10px">
          <el-col :span="6"><InfoField label="科室" :value="archive.deptname" /></el-col>
          <el-col :span="6"><InfoField label="诊断" :value="archive.diagname" /></el-col>
          <el-col :span="6"><InfoField label="主治医生" :value="archive.drname" /></el-col>
          <el-col :span="6"><InfoField label="来源" :value="sourceLabel" /></el-col>
        </el-row>
      </el-card>
      <el-tabs v-model="mainTab" type="border-card">
        <el-tab-pane label="档案详情" name="detail">
          <!-- æ‚£è€…基本信息 -->
          <el-card class="info-card" shadow="never">
            <div slot="header" class="card-header"><span>患者基本信息</span></div>
            <el-row :gutter="20">
              <el-col :span="6"><InfoField label="姓名" :value="archive.patname" /></el-col>
              <el-col :span="6"><InfoField label="性别" :value="archive.sex == 1 ? '男' : '女'" /></el-col>
              <el-col :span="6"><InfoField label="年龄" :value="archive.age" /></el-col>
              <el-col :span="6"><InfoField label="联系电话" :value="archive.telcode" /></el-col>
            </el-row>
            <el-row :gutter="20" style="margin-top:10px">
              <el-col :span="6"><InfoField label="科室" :value="archive.deptname" /></el-col>
              <el-col :span="6"><InfoField label="诊断" :value="archive.diagname" /></el-col>
              <el-col :span="6"><InfoField label="来源" :value="sourceLabel" /></el-col>
            </el-row>
          </el-card>
      <!-- æ¡£æ¡ˆåŸºæœ¬ä¿¡æ¯ -->
      <el-card class="info-card" shadow="never" style="margin-top:14px">
        <div slot="header" class="card-header">
          <span>档案基本信息</span>
          <span class="header-sub">{{ archive.createTime ? archive.createTime.substring(0,10) : '-' }} å»ºæ¡£</span>
        </div>
        <el-row :gutter="20">
          <el-col :span="8"><InfoField label="建档类型" :value="archive.subTypeLabel" /></el-col>
          <el-col :span="8"><InfoField label="建档医生" :value="archive.archiveForm.doctorName" :editing="editing" v-model="archive.archiveForm.doctorName" /></el-col>
          <el-col :span="8"><InfoField label="建档时间" :value="archive.createTime" /></el-col>
        </el-row>
        <!-- å­•妇专属 -->
        <el-row v-if="archive.subType === 'pregnancy'" :gutter="20" style="margin-top:10px">
          <el-col :span="6"><InfoField label="孕周" :value="archive.archiveForm.gestationalWeek" :editing="editing" v-model="archive.archiveForm.gestationalWeek" type="number" /></el-col>
          <el-col :span="6"><InfoField label="孕次/产次" :value="'G'+(archive.archiveForm.gravidity||1)+'P'+(archive.archiveForm.parity||0)" /></el-col>
          <el-col :span="6"><InfoField label="预产期" :value="archive.archiveForm.edd" /></el-col>
          <el-col :span="6"><InfoField label="高危妊娠" :value="archive.archiveForm.highRiskPregnancy ? '是' : '否'" /></el-col>
        </el-row>
        <el-row v-if="archive.subType === 'menopause'" :gutter="20" style="margin-top:10px">
          <el-col :span="8"><InfoField label="月经状态" :value="menstrualLabel" /></el-col>
          <el-col :span="8"><InfoField label="末次月经" :value="archive.archiveForm.lmp" /></el-col>
        </el-row>
      </el-card>
      <!-- ===== å­•妇随访:3个表单 ===== -->
      <template v-if="archive.subType === 'pregnancy'">
        <el-card class="info-card" shadow="never" style="margin-top:14px">
          <div slot="header" class="card-header"><span>孕妇随访管理</span></div>
          <el-tabs v-model="activeTab" type="border-card">
            <!-- 1. å­•妇随访系统问题 -->
            <el-tab-pane label="孕妇随访系统问题" name="sysQ">
              <el-form :model="form.pregSysQ" label-width="150px" size="small" :disabled="!editing">
          <!-- ===== å­•妇随访:档案信息 + é¦–诊信息 + å¦Šå¨ æƒ…况 åˆå¹¶åŒºå— ===== -->
          <el-card v-if="archive.subType === 'pregnancy'" class="info-card" shadow="never" style="margin-top:14px">
            <div class="sub-section">
              <div class="sub-title">档案信息</div>
              <el-form :model="archive" label-width="110px" size="small" :disabled="!editing">
                <el-row :gutter="20">
                  <el-col :span="8"><el-form-item label="填写日期"><el-date-picker v-model="form.pregSysQ.date" value-format="yyyy-MM-dd" style="width:100%" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="孕周"><el-input-number v-model="form.pregSysQ.gestWeek" :min="1" :max="42" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="身份证号"><el-input v-model="archive.idcardno" class="w-md" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="出生日期"><el-date-picker v-model="archive.birthday" value-format="yyyy-MM-dd" class="w-md" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="诊断日期"><el-date-picker v-model="archive.diagnosisDate" value-format="yyyy-MM-dd" class="w-md" /></el-form-item></el-col>
                </el-row>
                <el-row :gutter="20">
                  <el-col :span="8"><el-form-item label="体重(kg)"><el-input-number v-model="form.pregSysQ.weight" :precision="1" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="血压"><el-input v-model="form.pregSysQ.bloodPressure" placeholder="收缩压/舒张压" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="宫高(cm)"><el-input-number v-model="form.pregSysQ.fundalHeight" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="主治医生"><el-input v-model="archive.attendingDoctor" class="w-sm" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="经管医生"><el-input v-model="archive.managingDoctor" class="w-sm" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="登记人员"><el-input v-model="archive.registrant" class="w-sm" /></el-form-item></el-col>
                </el-row>
                <el-row :gutter="20">
                  <el-col :span="8"><el-form-item label="胎心(次/分)"><el-input-number v-model="form.pregSysQ.fetalHeart" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="水肿"><el-select v-model="form.pregSysQ.edema" style="width:100%"><el-option label="无" value="无" /><el-option label="+" value="+" /><el-option label="++" value="++" /><el-option label="+++" value="+++" /></el-select></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="尿蛋白"><el-select v-model="form.pregSysQ.urineProtein" style="width:100%"><el-option label="-" value="-" /><el-option label="±" value="±" /><el-option label="+" value="+" /><el-option label="++" value="++" /></el-select></el-form-item></el-col>
                </el-row>
                <el-form-item label="自觉症状"><el-input v-model="form.pregSysQ.symptoms" type="textarea" :rows="2" placeholder="头晕/眼花/胸闷/腹痛等" /></el-form-item>
                <el-form-item label="处理意见"><el-input v-model="form.pregSysQ.advice" type="textarea" :rows="2" /></el-form-item>
                <el-form-item label="下次随访日期"><el-date-picker v-model="form.pregSysQ.nextVisit" value-format="yyyy-MM-dd" style="width:200px" /></el-form-item>
                <el-form-item label="随访医生"><el-input v-model="form.pregSysQ.doctor" style="width:200px" /></el-form-item>
              </el-form>
            </el-tab-pane>
            </div>
            <!-- 2. å­•妇首诊信息登记表 -->
            <el-tab-pane label="丽水妇幼保健院孕妇首诊信息登记表" name="firstVisit">
              <el-form :model="form.pregFirst" label-width="160px" size="small" :disabled="!editing">
                <el-divider content-position="left">基本信息</el-divider>
            <div class="sub-section">
              <div class="sub-title">孕妇首诊信息登记</div>
              <el-form :model="pregForm.firstVisit" label-width="120px" size="small" :disabled="!editing">
                <el-row :gutter="20">
                  <el-col :span="8"><el-form-item label="建档日期"><el-date-picker v-model="form.pregFirst.regDate" value-format="yyyy-MM-dd" style="width:100%" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="丈夫姓名"><el-input v-model="form.pregFirst.husbandName" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="丈夫电话"><el-input v-model="form.pregFirst.husbandPhone" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="就诊号"><el-input v-model="pregForm.firstVisit.visitNo" class="w-sm" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="本人联系方式"><el-input v-model="pregForm.firstVisit.selfPhone" class="w-sm" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="病区姓名"><el-input v-model="pregForm.firstVisit.wardName" class="w-sm" /></el-form-item></el-col>
                </el-row>
                <el-row :gutter="20">
                  <el-col :span="8"><el-form-item label="末次月经"><el-date-picker v-model="form.pregFirst.lmp" value-format="yyyy-MM-dd" style="width:100%" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="预产期"><el-date-picker v-model="form.pregFirst.edd" value-format="yyyy-MM-dd" style="width:100%" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="孕次/产次"><el-input v-model="form.pregFirst.gp" placeholder="G_P_" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="联系人联系方式"><el-input v-model="pregForm.firstVisit.contactPhone" class="w-sm" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="身份证号"><el-input v-model="pregForm.firstVisit.idcardno" class="w-md" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="首诊医生"><el-input v-model="pregForm.firstVisit.firstDoctor" class="w-sm" /></el-form-item></el-col>
                </el-row>
                <el-divider content-position="left">既往史</el-divider>
                <el-row :gutter="20">
                  <el-col :span="8"><el-form-item label="剖宫产史"><el-select v-model="form.pregFirst.csHistory" style="width:100%"><el-option label="无" :value="0" /><el-option label="有" :value="1" /></el-select></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="流产史"><el-select v-model="form.pregFirst.abortionHistory" style="width:100%"><el-option label="无" :value="0" /><el-option label="有" :value="1" /></el-select></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="疾病史"><el-input v-model="form.pregFirst.diseaseHistory" placeholder="高血压/糖尿病/甲状腺等" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="诊断名称"><el-input v-model="pregForm.firstVisit.diagname" class="w-full" /></el-form-item></el-col>
                  <el-col :span="16"><el-form-item label="居住地址"><el-input v-model="pregForm.firstVisit.address" class="w-full" /></el-form-item></el-col>
                </el-row>
                <el-divider content-position="left">本次妊娠情况</el-divider>
                <el-row :gutter="20">
                  <el-col :span="8"><el-form-item label="身高(cm)"><el-input-number v-model="form.pregFirst.height" :precision="1" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="孕前体重(kg)"><el-input-number v-model="form.pregFirst.preWeight" :precision="1" /></el-form-item></el-col>
                </el-row>
                <el-form-item label="高危因素评估"><el-input v-model="form.pregFirst.riskAssessment" type="textarea" :rows="2" /></el-form-item>
                <el-form-item label="建册医生"><el-input v-model="form.pregFirst.doctor" style="width:200px" /></el-form-item>
              </el-form>
            </el-tab-pane>
            </div>
            <!-- 3. æ—©å­•随访登记表 -->
            <el-tab-pane label="丽水妇幼保健院早孕随访登记表" name="earlyFollow">
              <el-form :model="form.pregEarly" label-width="150px" size="small" :disabled="!editing">
                <el-row :gutter="20">
                  <el-col :span="8"><el-form-item label="随访日期"><el-date-picker v-model="form.pregEarly.date" value-format="yyyy-MM-dd" style="width:100%" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="孕周"><el-input-number v-model="form.pregEarly.gestWeek" :min="1" :max="14" /></el-form-item></el-col>
            <div class="sub-section">
              <div class="sub-title">妊娠情况</div>
              <el-form label-width="0" size="small" :disabled="!editing">
                <el-row :gutter="24" class="preg-cascade">
                  <el-col :span="8">
                    <div class="cascade-col">
                      <div class="cascade-label">妊娠情况</div>
                      <el-radio-group v-model="pregForm.pregnancy.status" @change="onPregnancyStatusChange">
                        <el-radio v-for="(opt, key) in PREGNANCY_OPTIONS" :key="key" :label="key">{{ opt.label }}</el-radio>
                      </el-radio-group>
                    </div>
                  </el-col>
                  <el-col :span="8">
                    <div class="cascade-col">
                      <div class="cascade-label">具体情况</div>
                      <el-checkbox-group v-model="pregForm.pregnancy.details" :disabled="!pregForm.pregnancy.status">
                        <el-checkbox v-for="d in pregnancySubOptions" :key="d" :label="d">{{ d }}</el-checkbox>
                      </el-checkbox-group>
                      <div v-if="!pregForm.pregnancy.status" class="cascade-empty">请先选择妊娠情况</div>
                    </div>
                  </el-col>
                  <el-col :span="8">
                    <div class="cascade-col">
                      <div class="cascade-label">处理建议</div>
                      <el-checkbox-group v-model="pregForm.pregnancy.hint" :disabled="!pregForm.pregnancy.status">
                        <el-checkbox v-for="h in pregnancyHintOptions" :key="h" :label="h">{{ h }}</el-checkbox>
                      </el-checkbox-group>
                      <div v-if="!pregForm.pregnancy.status" class="cascade-empty">请先选择妊娠情况</div>
                    </div>
                  </el-col>
                </el-row>
                <el-row :gutter="20">
                  <el-col :span="8"><el-form-item label="早孕反应"><el-select v-model="form.pregEarly.morningSickness" style="width:100%"><el-option label="无" value="无" /><el-option label="轻度" value="轻度" /><el-option label="中度" value="中度" /><el-option label="重度" value="重度" /></el-select></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="阴道出血"><el-select v-model="form.pregEarly.bleeding" style="width:100%"><el-option label="无" value="无" /><el-option label="有" value="有" /></el-select></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="腹痛"><el-select v-model="form.pregEarly.abdominalPain" style="width:100%"><el-option label="无" value="无" /><el-option label="有" value="有" /></el-select></el-form-item></el-col>
                </el-row>
                <el-row :gutter="20">
                  <el-col :span="8"><el-form-item label="血常规"><el-input v-model="form.pregEarly.bloodRoutine" placeholder="Hb/PLT等" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="尿常规"><el-input v-model="form.pregEarly.urineRoutine" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="B超检查"><el-input v-model="form.pregEarly.ultrasound" placeholder="CRL/NT等" /></el-form-item></el-col>
                </el-row>
                <el-form-item label="叶酸补充"><el-select v-model="form.pregEarly.folicAcid" style="width:200px"><el-option label="规律服用" value="规律服用" /><el-option label="偶尔服用" value="偶尔服用" /><el-option label="未服用" value="未服用" /></el-select></el-form-item>
                <el-form-item label="指导意见"><el-input v-model="form.pregEarly.advice" type="textarea" :rows="2" /></el-form-item>
                <el-form-item label="随访医生"><el-input v-model="form.pregEarly.doctor" style="width:200px" /></el-form-item>
              </el-form>
            </el-tab-pane>
          </el-tabs>
        </el-card>
      </template>
            </div>
          </el-card>
      <!-- ===== æ›´å¹´æœŸéšè®¿ï¼š6个表单 ===== -->
      <template v-if="archive.subType === 'menopause'">
        <el-card class="info-card" shadow="never" style="margin-top:14px">
          <div slot="header" class="card-header"><span>更年期随访管理</span></div>
          <el-tabs v-model="activeTab" type="border-card">
            <!-- 1. æ›´å¹´æœŸéšè®¿ç³»ç»Ÿé—®é¢˜è¡¨ -->
            <el-tab-pane label="更年期随访系统问题表" name="sysQ">
              <el-form :model="form.menoSysQ" label-width="150px" size="small" :disabled="!editing">
                <el-row :gutter="20">
                  <el-col :span="8"><el-form-item label="填写日期"><el-date-picker v-model="form.menoSysQ.date" value-format="yyyy-MM-dd" style="width:100%" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="年龄"><el-input-number v-model="form.menoSysQ.age" /></el-form-item></el-col>
                </el-row>
                <el-row :gutter="20">
                  <el-col :span="8"><el-form-item label="月经情况"><el-input v-model="form.menoSysQ.menstruation" placeholder="周期/经期/经量" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="末次月经"><el-date-picker v-model="form.menoSysQ.lmp" value-format="yyyy-MM-dd" style="width:100%" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="绝经年龄"><el-input-number v-model="form.menoSysQ.menopauseAge" /></el-form-item></el-col>
                </el-row>
                <el-row :gutter="20">
                  <el-col :span="6"><el-form-item label="潮热出汗"><el-select v-model="form.menoSysQ.hotFlash" style="width:100%"><el-option :value="0" label="无" /><el-option :value="1" label="è½»" /><el-option :value="2" label="中" /><el-option :value="3" label="重" /></el-select></el-form-item></el-col>
                  <el-col :span="6"><el-form-item label="失眠"><el-select v-model="form.menoSysQ.insomnia" style="width:100%"><el-option :value="0" label="无" /><el-option :value="1" label="è½»" /><el-option :value="2" label="中" /><el-option :value="3" label="重" /></el-select></el-form-item></el-col>
                  <el-col :span="6"><el-form-item label="情绪波动"><el-select v-model="form.menoSysQ.mood" style="width:100%"><el-option :value="0" label="无" /><el-option :value="1" label="è½»" /><el-option :value="2" label="中" /><el-option :value="3" label="重" /></el-select></el-form-item></el-col>
                  <el-col :span="6"><el-form-item label="疲乏"><el-select v-model="form.menoSysQ.fatigue" style="width:100%"><el-option :value="0" label="无" /><el-option :value="1" label="è½»" /><el-option :value="2" label="中" /><el-option :value="3" label="重" /></el-select></el-form-item></el-col>
                </el-row>
                <el-row :gutter="20">
                  <el-col :span="6"><el-form-item label="关节痛"><el-select v-model="form.menoSysQ.jointPain" style="width:100%"><el-option :value="0" label="无" /><el-option :value="1" label="è½»" /><el-option :value="2" label="中" /><el-option :value="3" label="重" /></el-select></el-form-item></el-col>
                  <el-col :span="6"><el-form-item label="心悸"><el-select v-model="form.menoSysQ.palpitation" style="width:100%"><el-option :value="0" label="无" /><el-option :value="1" label="è½»" /><el-option :value="2" label="中" /><el-option :value="3" label="重" /></el-select></el-form-item></el-col>
                  <el-col :span="6"><el-form-item label="阴道干涩"><el-select v-model="form.menoSysQ.dryness" style="width:100%"><el-option :value="0" label="无" /><el-option :value="1" label="è½»" /><el-option :value="2" label="中" /><el-option :value="3" label="重" /></el-select></el-form-item></el-col>
                  <el-col :span="6"><el-form-item label="性欲减退"><el-select v-model="form.menoSysQ.libido" style="width:100%"><el-option :value="0" label="无" /><el-option :value="1" label="è½»" /><el-option :value="2" label="中" /><el-option :value="3" label="重" /></el-select></el-form-item></el-col>
                </el-row>
                <el-form-item label="其他症状"><el-input v-model="form.menoSysQ.otherSymptoms" type="textarea" :rows="2" /></el-form-item>
                <el-form-item label="处理意见"><el-input v-model="form.menoSysQ.advice" type="textarea" :rows="2" /></el-form-item>
                <el-form-item label="医生"><el-input v-model="form.menoSysQ.doctor" style="width:200px" /></el-form-item>
              </el-form>
            </el-tab-pane>
          <!-- æ˜¯å¦å»ºå†Œï¼ˆå­•妇随访) -->
          <el-card v-if="archive.subType === 'pregnancy'" class="info-card" shadow="never" style="margin-top:14px">
            <div slot="header" class="card-header"><span>是否建册</span></div>
            <el-form :model="pregForm.registration" label-width="120px" size="small" :disabled="!editing">
              <el-row :gutter="20">
                <el-col :span="8"><el-form-item label="是否建册">
                  <el-radio-group v-model="pregForm.registration.isRegistered">
                    <el-radio label="是">是</el-radio><el-radio label="否">否</el-radio>
                  </el-radio-group>
                </el-form-item></el-col>
                <el-col :span="8"><el-form-item label="预产期"><el-date-picker v-model="pregForm.registration.edd" value-format="yyyy-MM-dd" class="w-md" /></el-form-item></el-col>
                <el-col :span="8"><el-form-item label="记录者"><el-input v-model="pregForm.registration.recorder" class="w-sm" /></el-form-item></el-col>
              </el-row>
              <el-form-item v-if="pregForm.registration.isRegistered === '否'" label="未建档原因">
                <el-input v-model="pregForm.registration.unregisteredReason" type="textarea" :rows="2" class="w-lg" />
              </el-form-item>
            </el-form>
          </el-card>
            <!-- 2. MHT首诊病历表 -->
            <el-tab-pane label="MHT首诊病历表" name="mhtFirst">
              <el-form :model="form.mhtFirst" label-width="160px" size="small" :disabled="!editing">
                <el-row :gutter="20">
                  <el-col :span="8"><el-form-item label="就诊日期"><el-date-picker v-model="form.mhtFirst.date" value-format="yyyy-MM-dd" style="width:100%" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="主诉"><el-input v-model="form.mhtFirst.chiefComplaint" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="绝经年限"><el-input-number v-model="form.mhtFirst.menopauseYears" :precision="1" /></el-form-item></el-col>
                </el-row>
                <el-row :gutter="20">
                  <el-col :span="8"><el-form-item label="身高(cm)"><el-input-number v-model="form.mhtFirst.height" :precision="1" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="体重(kg)"><el-input-number v-model="form.mhtFirst.weight" :precision="1" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="BMI"><el-input-number v-model="form.mhtFirst.bmi" :precision="1" disabled /></el-form-item></el-col>
                </el-row>
                <el-row :gutter="20">
                  <el-col :span="8"><el-form-item label="血压"><el-input v-model="form.mhtFirst.bloodPressure" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="血糖"><el-input-number v-model="form.mhtFirst.glucose" :precision="1" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="血脂"><el-input v-model="form.mhtFirst.lipid" placeholder="TC/TG/LDL/HDL" /></el-form-item></el-col>
                </el-row>
                <el-form-item label="既往史"><el-input v-model="form.mhtFirst.pastHistory" type="textarea" :rows="2" placeholder="高血压/糖尿病/血栓/乳腺癌/子宫肌瘤等" /></el-form-item>
                <el-form-item label="妇科检查"><el-input v-model="form.mhtFirst.gynExam" type="textarea" :rows="2" /></el-form-item>
                <el-form-item label="乳腺检查"><el-input v-model="form.mhtFirst.breastExam" type="textarea" :rows="2" /></el-form-item>
                <el-form-item label="辅助检查"><el-input v-model="form.mhtFirst.auxExam" type="textarea" :rows="2" placeholder="B超/骨密度/激素水平等" /></el-form-item>
                <el-form-item label="MHT方案"><el-input v-model="form.mhtFirst.mhtPlan" type="textarea" :rows="2" placeholder="药物名称/剂量/用法/疗程" /></el-form-item>
                <el-form-item label="医生"><el-input v-model="form.mhtFirst.doctor" style="width:200px" /></el-form-item>
              </el-form>
            </el-tab-pane>
          <!-- ===== å›´ç»ç»æœŸMHI:档案信息(含随访周期次数) ===== -->
          <el-card v-if="archive.subType === 'menopause'" class="info-card" shadow="never" style="margin-top:14px">
            <div slot="header" class="card-header">
              <span>档案信息</span>
              <span class="header-sub">{{ archive.createTime ? archive.createTime.substring(0,10) : '-' }} å»ºæ¡£</span>
            </div>
            <el-form :model="archive" label-width="110px" size="small" :disabled="!editing">
              <el-row :gutter="20">
                <el-col :span="8"><el-form-item label="身份证号"><el-input v-model="archive.idcardno" class="w-md" /></el-form-item></el-col>
                <el-col :span="8"><el-form-item label="出生日期"><el-date-picker v-model="archive.birthday" value-format="yyyy-MM-dd" class="w-md" /></el-form-item></el-col>
                <el-col :span="8"><el-form-item label="诊断日期"><el-date-picker v-model="archive.diagnosisDate" value-format="yyyy-MM-dd" class="w-md" /></el-form-item></el-col>
              </el-row>
              <el-row :gutter="20">
                <el-col :span="8"><el-form-item label="主治医生"><el-input v-model="archive.attendingDoctor" class="w-sm" /></el-form-item></el-col>
                <el-col :span="8"><el-form-item label="经管医生"><el-input v-model="archive.managingDoctor" class="w-sm" /></el-form-item></el-col>
                <el-col :span="8"><el-form-item label="登记人员"><el-input v-model="archive.registrant" class="w-sm" /></el-form-item></el-col>
              </el-row>
              <el-row :gutter="20">
                <el-col :span="8"><el-form-item label="随访周期(次数)"><el-input v-model="archive.followupCycles" class="w-xs" /></el-form-item></el-col>
              </el-row>
            </el-form>
          </el-card>
            <!-- 3. MHT复诊病历表 -->
            <el-tab-pane label="MHT复诊病历表" name="mhtFollow">
              <el-form :model="form.mhtFollow" label-width="160px" size="small" :disabled="!editing">
          <!-- ===== å­•妇随访:早孕随访表 + è¯„分表(选中「经咨询指导后继续妊娠并在产科建档」后出现) ===== -->
          <template v-if="archive.subType === 'pregnancy' && showFollowUp">
            <el-card class="info-card" shadow="never" style="margin-top:14px">
              <div slot="header" class="card-header"><span>早孕随访表</span></div>
              <el-form :model="pregForm.earlyFollow" label-width="140px" size="small" :disabled="!editing">
                <el-row :gutter="20">
                  <el-col :span="8"><el-form-item label="复诊日期"><el-date-picker v-model="form.mhtFollow.date" value-format="yyyy-MM-dd" style="width:100%" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="用药依从性"><el-select v-model="form.mhtFollow.compliance" style="width:100%"><el-option label="良好" value="良好" /><el-option label="一般" value="一般" /><el-option label="å·®" value="å·®" /></el-select></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="姓名"><el-input v-model="pregForm.earlyFollow.name" class="w-sm" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="年龄"><el-input v-model="pregForm.earlyFollow.age" class="w-xs" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="就诊号"><el-input v-model="pregForm.earlyFollow.visitNo" class="w-sm" /></el-form-item></el-col>
                </el-row>
                <el-row :gutter="20">
                  <el-col :span="8"><el-form-item label="症状改善"><el-select v-model="form.mhtFollow.symptomImprove" style="width:100%"><el-option label="明显" value="明显" /><el-option label="部分" value="部分" /><el-option label="无变化" value="无变化" /></el-select></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="不良反应"><el-input v-model="form.mhtFollow.sideEffect" placeholder="如乳房胀痛/阴道出血等" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="阴道出血"><el-select v-model="form.mhtFollow.bleeding" style="width:100%"><el-option label="无" value="无" /><el-option label="点滴" value="点滴" /><el-option label="少量" value="少量" /><el-option label="中量" value="中量" /></el-select></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="联系方式(微信)"><el-input v-model="pregForm.earlyFollow.wechat" class="w-sm" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="户籍"><el-input v-model="pregForm.earlyFollow.household" class="w-sm" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="孕产史"><el-input v-model="pregForm.earlyFollow.gravidaPara" placeholder="G_P_" class="w-xs" /></el-form-item></el-col>
                </el-row>
                <el-row :gutter="20">
                  <el-col :span="8"><el-form-item label="体重(kg)"><el-input-number v-model="form.mhtFollow.weight" :precision="1" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="血压"><el-input v-model="form.mhtFollow.bloodPressure" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="婚姻状况">
                    <el-select v-model="pregForm.earlyFollow.maritalStatus" class="w-sm">
                      <el-option label="未婚" value="未婚" /><el-option label="已婚" value="已婚" /><el-option label="不详" value="不详" />
                    </el-select>
                  </el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="建表日期"><el-date-picker v-model="pregForm.earlyFollow.buildDate" value-format="yyyy-MM-dd" class="w-md" /></el-form-item></el-col>
                  <el-col :span="8"><el-form-item label="预产期"><el-date-picker v-model="pregForm.earlyFollow.edd" value-format="yyyy-MM-dd" class="w-md" /></el-form-item></el-col>
                </el-row>
                <el-form-item label="复查结果"><el-input v-model="form.mhtFollow.reExamResult" type="textarea" :rows="2" placeholder="B超/乳腺/激素等检查结果" /></el-form-item>
                <el-form-item label="方案调整"><el-input v-model="form.mhtFollow.planAdjust" type="textarea" :rows="2" /></el-form-item>
                <el-form-item label="下次复诊"><el-date-picker v-model="form.mhtFollow.nextVisit" value-format="yyyy-MM-dd" style="width:200px" /></el-form-item>
                <el-form-item label="医生"><el-input v-model="form.mhtFollow.doctor" style="width:200px" /></el-form-item>
                <el-form-item label="计划流产主要原因(可多选)">
                  <el-checkbox-group v-model="pregForm.earlyFollow.abortionReasons">
                    <el-checkbox label="已有多孩" /><el-checkbox label="经济因素" /><el-checkbox label="药物暴露" />
                    <el-checkbox label="未达结婚年龄" /><el-checkbox label="身体因素" /><el-checkbox label="家庭支持不足" />
                    <el-checkbox label="自身不愿生育" /><el-checkbox label="其他" />
                  </el-checkbox-group>
                  <el-input v-if="pregForm.earlyFollow.abortionReasons.includes('其他')" v-model="pregForm.earlyFollow.otherReason" placeholder="其他原因" class="w-md" style="margin-top:8px" />
                </el-form-item>
                <el-form-item label="继续妊娠">
                  <el-radio-group v-model="pregForm.earlyFollow.continuePregnancy">
                    <el-radio label="是">是</el-radio><el-radio label="否">否</el-radio>
                  </el-radio-group>
                </el-form-item>
                <el-row :gutter="20">
                  <el-col :span="16"><el-form-item label="早孕建册单位"><el-input v-model="pregForm.earlyFollow.registrationUnit" class="w-full" /></el-form-item></el-col>
                </el-row>
              </el-form>
            </el-tab-pane>
            <!-- 4. æ”¹è‰¯Kupperman评分标准表 -->
            <el-tab-pane label="改良Kupperman评分标准表" name="kupperman">
              <el-form :model="form.kupperman" label-width="160px" size="small" :disabled="!editing">
                <el-row :gutter="20">
                  <el-col :span="8"><el-form-item label="评估日期"><el-date-picker v-model="form.kupperman.date" value-format="yyyy-MM-dd" style="width:100%" /></el-form-item></el-col>
                </el-row>
                <KuppermanScoreTable v-model="form.kupperman" :editing="editing" />
              </el-form>
            </el-tab-pane>
              <div class="sub-section">
                <div class="sub-title">辅导内容</div>
                <el-table :data="pregForm.counseling" border size="small">
                  <el-table-column label="辅导次数" width="120" align="center">
                    <template slot-scope="scope">{{ ['第一次辅导','第二次辅导','第三次辅导','第四次辅导'][scope.$index] }}</template>
                  </el-table-column>
                  <el-table-column label="辅导日期" width="160" align="center">
                    <template slot-scope="scope">
                      <el-date-picker v-if="editing" v-model="scope.row.date" value-format="yyyy-MM-dd" size="small" style="width:100%" />
                      <span v-else>{{ scope.row.date || '-' }}</span>
                    </template>
                  </el-table-column>
                  <el-table-column label="辅导内容" align="center">
                    <template slot-scope="scope">
                      <el-input v-if="editing" v-model="scope.row.content" size="small" />
                      <span v-else>{{ scope.row.content || '-' }}</span>
                    </template>
                  </el-table-column>
                  <el-table-column label="辅导人员" width="140" align="center">
                    <template slot-scope="scope">
                      <el-input v-if="editing" v-model="scope.row.person" size="small" />
                      <span v-else>{{ scope.row.person || '-' }}</span>
                    </template>
                  </el-table-column>
                </el-table>
              </div>
            <!-- 5. æ›´å¹´æœŸè¯„分量表(MRS) -->
            <el-tab-pane label="更年期评分量表(MRS)" name="mrs">
              <el-form :model="form.mrs" label-width="160px" size="small" :disabled="!editing">
                <el-row :gutter="20">
                  <el-col :span="8"><el-form-item label="评估日期"><el-date-picker v-model="form.mrs.date" value-format="yyyy-MM-dd" style="width:100%" /></el-form-item></el-col>
                </el-row>
                <MRSScoreTable v-model="form.mrs" :editing="editing" />
              </el-form>
            </el-tab-pane>
              <div class="sub-section">
                <div class="sub-title">评分表</div>
                <el-form :model="pregForm.score" label-width="140px" size="small" :disabled="!editing" inline>
                  <el-form-item label="评分总分"><el-input-number v-model="pregForm.score.total" :controls="false" style="width:140px" /></el-form-item>
                  <el-form-item label="评估结果"><el-input v-model="pregForm.score.result" class="w-lg" /></el-form-item>
                </el-form>
              </div>
            </el-card>
            <!-- 6. åŒ¹å…¹å ¡ç¡çœ è´¨é‡è¯„分量表(PSQI) -->
            <el-tab-pane label="匹兹堡睡眠质量评分量表(PSQI)" name="psqi">
              <el-form :model="form.psqi" label-width="160px" size="small" :disabled="!editing">
                <el-row :gutter="20">
                  <el-col :span="8"><el-form-item label="评估日期"><el-date-picker v-model="form.psqi.date" value-format="yyyy-MM-dd" style="width:100%" /></el-form-item></el-col>
                </el-row>
                <PSQIScoreTable v-model="form.psqi" :editing="editing" />
              </el-form>
            </el-tab-pane>
          </el-tabs>
        </el-card>
      </template>
            <!-- éšè®¿å‘¨æœŸ -->
            <el-card class="info-card" shadow="never" style="margin-top:14px">
              <div slot="header" class="card-header"><span>随访周期</span></div>
              <EditableTable :data.sync="pregForm.followupCycle" :editing="editing" :columns="followupColumns" />
            </el-card>
            <!-- å½“前患者随访记录表 -->
            <el-card class="info-card" shadow="never" style="margin-top:14px">
              <div slot="header" class="card-header"><span>当前患者随访记录表</span></div>
              <el-table :data="currentFollowRecords" border size="small">
                <el-table-column type="index" label="序号" width="60" align="center" />
                <el-table-column prop="planDate" label="拟随访日期" width="130" align="center" />
                <el-table-column prop="status" label="状况" width="100" align="center" />
                <el-table-column prop="method" label="随访方式" width="110" align="center" />
                <el-table-column prop="situation" label="随访情况" align="center" show-overflow-tooltip />
                <el-table-column prop="followDate" label="随访日期" width="130" align="center" />
                <el-table-column prop="staff" label="随访人员" width="110" align="center" />
              </el-table>
            </el-card>
          </template>
          <!-- ===== å›´ç»ç»æœŸMHI:6 ä¸ªé‡è¡¨ tab ===== -->
          <template v-if="archive.subType === 'menopause'">
            <el-card class="info-card" shadow="never" style="margin-top:14px">
              <div slot="header" class="card-header"><span>围绝经期MHI管理</span></div>
              <el-tabs v-model="activeTab" type="border-card">
                <el-tab-pane label="更年期随访系统问题表" name="sysQ">
                  <el-form :model="form.menoSysQ" label-width="150px" size="small" :disabled="!editing">
                    <el-row :gutter="20">
                      <el-col :span="8"><el-form-item label="填写日期"><el-date-picker v-model="form.menoSysQ.date" value-format="yyyy-MM-dd" class="w-md" /></el-form-item></el-col>
                      <el-col :span="8"><el-form-item label="年龄"><el-input-number v-model="form.menoSysQ.age" /></el-form-item></el-col>
                    </el-row>
                    <el-row :gutter="20">
                      <el-col :span="8"><el-form-item label="月经情况"><el-input v-model="form.menoSysQ.menstruation" placeholder="周期/经期/经量" class="w-md" /></el-form-item></el-col>
                      <el-col :span="8"><el-form-item label="末次月经"><el-date-picker v-model="form.menoSysQ.lmp" value-format="yyyy-MM-dd" class="w-md" /></el-form-item></el-col>
                      <el-col :span="8"><el-form-item label="绝经年龄"><el-input-number v-model="form.menoSysQ.menopauseAge" /></el-form-item></el-col>
                    </el-row>
                    <el-row :gutter="20">
                      <el-col :span="6"><el-form-item label="潮热出汗"><el-select v-model="form.menoSysQ.hotFlash" style="width:100%"><el-option :value="0" label="无" /><el-option :value="1" label="è½»" /><el-option :value="2" label="中" /><el-option :value="3" label="重" /></el-select></el-form-item></el-col>
                      <el-col :span="6"><el-form-item label="失眠"><el-select v-model="form.menoSysQ.insomnia" style="width:100%"><el-option :value="0" label="无" /><el-option :value="1" label="è½»" /><el-option :value="2" label="中" /><el-option :value="3" label="重" /></el-select></el-form-item></el-col>
                      <el-col :span="6"><el-form-item label="情绪波动"><el-select v-model="form.menoSysQ.mood" style="width:100%"><el-option :value="0" label="无" /><el-option :value="1" label="è½»" /><el-option :value="2" label="中" /><el-option :value="3" label="重" /></el-select></el-form-item></el-col>
                      <el-col :span="6"><el-form-item label="疲乏"><el-select v-model="form.menoSysQ.fatigue" style="width:100%"><el-option :value="0" label="无" /><el-option :value="1" label="è½»" /><el-option :value="2" label="中" /><el-option :value="3" label="重" /></el-select></el-form-item></el-col>
                    </el-row>
                    <el-row :gutter="20">
                      <el-col :span="6"><el-form-item label="关节痛"><el-select v-model="form.menoSysQ.jointPain" style="width:100%"><el-option :value="0" label="无" /><el-option :value="1" label="è½»" /><el-option :value="2" label="中" /><el-option :value="3" label="重" /></el-select></el-form-item></el-col>
                      <el-col :span="6"><el-form-item label="心悸"><el-select v-model="form.menoSysQ.palpitation" style="width:100%"><el-option :value="0" label="无" /><el-option :value="1" label="è½»" /><el-option :value="2" label="中" /><el-option :value="3" label="重" /></el-select></el-form-item></el-col>
                      <el-col :span="6"><el-form-item label="阴道干涩"><el-select v-model="form.menoSysQ.dryness" style="width:100%"><el-option :value="0" label="无" /><el-option :value="1" label="è½»" /><el-option :value="2" label="中" /><el-option :value="3" label="重" /></el-select></el-form-item></el-col>
                      <el-col :span="6"><el-form-item label="性欲减退"><el-select v-model="form.menoSysQ.libido" style="width:100%"><el-option :value="0" label="无" /><el-option :value="1" label="è½»" /><el-option :value="2" label="中" /><el-option :value="3" label="重" /></el-select></el-form-item></el-col>
                    </el-row>
                    <el-form-item label="其他症状"><el-input v-model="form.menoSysQ.otherSymptoms" type="textarea" :rows="2" /></el-form-item>
                    <el-form-item label="处理意见"><el-input v-model="form.menoSysQ.advice" type="textarea" :rows="2" /></el-form-item>
                    <el-form-item label="医生"><el-input v-model="form.menoSysQ.doctor" class="w-sm" /></el-form-item>
                  </el-form>
                </el-tab-pane>
                <el-tab-pane label="MHT首诊病历表" name="mhtFirst">
                  <el-form :model="form.mhtFirst" label-width="160px" size="small" :disabled="!editing">
                    <el-row :gutter="20">
                      <el-col :span="8"><el-form-item label="就诊日期"><el-date-picker v-model="form.mhtFirst.date" value-format="yyyy-MM-dd" class="w-md" /></el-form-item></el-col>
                      <el-col :span="8"><el-form-item label="主诉"><el-input v-model="form.mhtFirst.chiefComplaint" class="w-sm" /></el-form-item></el-col>
                      <el-col :span="8"><el-form-item label="绝经年限"><el-input-number v-model="form.mhtFirst.menopauseYears" :precision="1" /></el-form-item></el-col>
                    </el-row>
                    <el-row :gutter="20">
                      <el-col :span="8"><el-form-item label="身高(cm)"><el-input-number v-model="form.mhtFirst.height" :precision="1" /></el-form-item></el-col>
                      <el-col :span="8"><el-form-item label="体重(kg)"><el-input-number v-model="form.mhtFirst.weight" :precision="1" /></el-form-item></el-col>
                      <el-col :span="8"><el-form-item label="BMI"><el-input-number v-model="form.mhtFirst.bmi" :precision="1" disabled /></el-form-item></el-col>
                    </el-row>
                    <el-row :gutter="20">
                      <el-col :span="8"><el-form-item label="血压"><el-input v-model="form.mhtFirst.bloodPressure" class="w-xs" /></el-form-item></el-col>
                      <el-col :span="8"><el-form-item label="血糖"><el-input-number v-model="form.mhtFirst.glucose" :precision="1" /></el-form-item></el-col>
                      <el-col :span="8"><el-form-item label="血脂"><el-input v-model="form.mhtFirst.lipid" placeholder="TC/TG/LDL/HDL" class="w-md" /></el-form-item></el-col>
                    </el-row>
                    <el-form-item label="既往史"><el-input v-model="form.mhtFirst.pastHistory" type="textarea" :rows="2" placeholder="高血压/糖尿病/血栓/乳腺癌/子宫肌瘤等" /></el-form-item>
                    <el-form-item label="妇科检查"><el-input v-model="form.mhtFirst.gynExam" type="textarea" :rows="2" /></el-form-item>
                    <el-form-item label="乳腺检查"><el-input v-model="form.mhtFirst.breastExam" type="textarea" :rows="2" /></el-form-item>
                    <el-form-item label="辅助检查"><el-input v-model="form.mhtFirst.auxExam" type="textarea" :rows="2" placeholder="B超/骨密度/激素水平等" /></el-form-item>
                    <el-form-item label="MHT方案"><el-input v-model="form.mhtFirst.mhtPlan" type="textarea" :rows="2" placeholder="药物名称/剂量/用法/疗程" /></el-form-item>
                    <el-form-item label="医生"><el-input v-model="form.mhtFirst.doctor" class="w-sm" /></el-form-item>
                  </el-form>
                </el-tab-pane>
                <el-tab-pane label="MHT复诊病历表" name="mhtFollow">
                  <el-form :model="form.mhtFollow" label-width="160px" size="small" :disabled="!editing">
                    <el-row :gutter="20">
                      <el-col :span="8"><el-form-item label="复诊日期"><el-date-picker v-model="form.mhtFollow.date" value-format="yyyy-MM-dd" class="w-md" /></el-form-item></el-col>
                      <el-col :span="8"><el-form-item label="用药依从性"><el-select v-model="form.mhtFollow.compliance" style="width:100%"><el-option label="良好" value="良好" /><el-option label="一般" value="一般" /><el-option label="å·®" value="å·®" /></el-select></el-form-item></el-col>
                    </el-row>
                    <el-row :gutter="20">
                      <el-col :span="8"><el-form-item label="症状改善"><el-select v-model="form.mhtFollow.symptomImprove" style="width:100%"><el-option label="明显" value="明显" /><el-option label="部分" value="部分" /><el-option label="无变化" value="无变化" /></el-select></el-form-item></el-col>
                      <el-col :span="8"><el-form-item label="不良反应"><el-input v-model="form.mhtFollow.sideEffect" placeholder="如乳房胀痛/阴道出血等" class="w-md" /></el-form-item></el-col>
                      <el-col :span="8"><el-form-item label="阴道出血"><el-select v-model="form.mhtFollow.bleeding" style="width:100%"><el-option label="无" value="无" /><el-option label="点滴" value="点滴" /><el-option label="少量" value="少量" /><el-option label="中量" value="中量" /></el-select></el-form-item></el-col>
                    </el-row>
                    <el-row :gutter="20">
                      <el-col :span="8"><el-form-item label="体重(kg)"><el-input-number v-model="form.mhtFollow.weight" :precision="1" /></el-form-item></el-col>
                      <el-col :span="8"><el-form-item label="血压"><el-input v-model="form.mhtFollow.bloodPressure" class="w-xs" /></el-form-item></el-col>
                    </el-row>
                    <el-form-item label="复查结果"><el-input v-model="form.mhtFollow.reExamResult" type="textarea" :rows="2" placeholder="B超/乳腺/激素等检查结果" /></el-form-item>
                    <el-form-item label="方案调整"><el-input v-model="form.mhtFollow.planAdjust" type="textarea" :rows="2" /></el-form-item>
                    <el-form-item label="下次复诊"><el-date-picker v-model="form.mhtFollow.nextVisit" value-format="yyyy-MM-dd" class="w-md" /></el-form-item>
                    <el-form-item label="医生"><el-input v-model="form.mhtFollow.doctor" class="w-sm" /></el-form-item>
                  </el-form>
                </el-tab-pane>
                <el-tab-pane label="改良Kupperman评分标准表" name="kupperman">
                  <el-form :model="form.kupperman" label-width="160px" size="small" :disabled="!editing">
                    <el-row :gutter="20"><el-col :span="8"><el-form-item label="评估日期"><el-date-picker v-model="form.kupperman.date" value-format="yyyy-MM-dd" class="w-md" /></el-form-item></el-col></el-row>
                    <KuppermanScoreTable v-model="form.kupperman" :editing="editing" />
                  </el-form>
                </el-tab-pane>
                <el-tab-pane label="更年期评分量表(MRS)" name="mrs">
                  <el-form :model="form.mrs" label-width="160px" size="small" :disabled="!editing">
                    <el-row :gutter="20"><el-col :span="8"><el-form-item label="评估日期"><el-date-picker v-model="form.mrs.date" value-format="yyyy-MM-dd" class="w-md" /></el-form-item></el-col></el-row>
                    <MRSScoreTable v-model="form.mrs" :editing="editing" />
                  </el-form>
                </el-tab-pane>
                <el-tab-pane label="匹兹堡睡眠质量评分量表(PSQI)" name="psqi">
                  <el-form :model="form.psqi" label-width="160px" size="small" :disabled="!editing">
                    <el-row :gutter="20"><el-col :span="8"><el-form-item label="评估日期"><el-date-picker v-model="form.psqi.date" value-format="yyyy-MM-dd" class="w-md" /></el-form-item></el-col></el-row>
                    <PSQIScoreTable v-model="form.psqi" :editing="editing" />
                  </el-form>
                </el-tab-pane>
              </el-tabs>
            </el-card>
          </template>
        </el-tab-pane>
        <el-tab-pane label="服务记录" name="service">
          <ServiceRecords :patid="archive.patid" />
        </el-tab-pane>
      </el-tabs>
    </template>
    <div v-else class="empty-wrap">档案不存在或已删除</div>
  </div>
@@ -257,20 +363,48 @@
<script>
import InfoField from "../childHealth/InfoField.vue";
import EditableTable from "../components/EditableTable.vue";
import ServiceRecords from "../components/ServiceRecords.vue";
import KuppermanScoreTable from "./KuppermanScoreTable.vue";
import MRSScoreTable from "./MRSScoreTable.vue";
import PSQIScoreTable from "./PSQIScoreTable.vue";
import { loadArchives, saveArchives } from "../types";
const PREGNANCY_OPTIONS = {
  planBirth: { label: "首诊拟生育", details: ["先兆流产"], hints: ["医学处置后继续妊娠并在产科建档"] },
  earlyAbnormal: { label: "首诊早孕异常", details: ["自然流产", "胚胎停育"], hints: ["为女性再次妊娠提供备孕指导"] },
  planAbortion: { label: "首诊拟人工流产", details: ["药物流产", "手术流产"], hints: ["经咨询指导后继续妊娠并在产科建档"] },
};
export default {
  name: "MaternalHealthArchiveDetail",
  components: { InfoField, KuppermanScoreTable, MRSScoreTable, PSQIScoreTable },
  components: { InfoField, EditableTable, ServiceRecords, KuppermanScoreTable, MRSScoreTable, PSQIScoreTable },
  data() {
    return {
      loading: false, archive: null, editing: false, activeTab: "sysQ",
      PREGNANCY_OPTIONS,
      loading: false, archive: null, editing: true, activeTab: "sysQ", mainTab: "detail",
      followupColumns: [
        { prop: "seq", label: "序号", width: 60 },
        { prop: "planDate", label: "拟随访日期", width: 130, type: "date" },
        { prop: "status", label: "状况", width: 100, type: "select", options: ["已完成", "未完成"] },
        { prop: "method", label: "随访方式", width: 110 },
        { prop: "situation", label: "随访情况", minWidth: 160 },
        { prop: "followDate", label: "随访日期", width: 130, type: "date" },
        { prop: "staff", label: "随访人员", width: 110 },
      ],
      pregForm: {
        firstVisit: { visitNo: "", selfPhone: "", wardName: "", contactPhone: "", idcardno: "", address: "", diagname: "", firstDoctor: "" },
        pregnancy: { status: "", details: [], hint: [] },
        earlyFollow: { name: "", age: "", visitNo: "", wechat: "", household: "", gravidaPara: "", maritalStatus: "", buildDate: "", abortionReasons: [], otherReason: "", continuePregnancy: "", edd: "", registrationUnit: "" },
        counseling: [
          { date: "", content: "", person: "" }, { date: "", content: "", person: "" },
          { date: "", content: "", person: "" }, { date: "", content: "", person: "" },
        ],
        score: { total: null, result: "" },
        registration: { isRegistered: "", unregisteredReason: "", edd: "", recorder: "" },
        followupCycle: [],
      },
      form: {
        pregSysQ: { date:"", gestWeek:null, weight:null, bloodPressure:"", fundalHeight:null, fetalHeart:null, edema:"无", urineProtein:"-", symptoms:"", advice:"", nextVisit:"", doctor:"" },
        pregFirst: { regDate:"", husbandName:"", husbandPhone:"", lmp:"", edd:"", gp:"", csHistory:0, abortionHistory:0, diseaseHistory:"", height:null, preWeight:null, riskAssessment:"", doctor:"" },
        pregEarly: { date:"", gestWeek:null, morningSickness:"无", bleeding:"无", abdominalPain:"无", bloodRoutine:"", urineRoutine:"", ultrasound:"", folicAcid:"规律服用", advice:"", doctor:"" },
        menoSysQ: { date:"", age:null, menstruation:"", lmp:"", menopauseAge:null, hotFlash:0, insomnia:0, mood:0, fatigue:0, jointPain:0, palpitation:0, dryness:0, libido:0, otherSymptoms:"", advice:"", doctor:"" },
        mhtFirst: { date:"", chiefComplaint:"", menopauseYears:null, height:null, weight:null, bmi:null, bloodPressure:"", glucose:null, lipid:"", pastHistory:"", gynExam:"", breastExam:"", auxExam:"", mhtPlan:"", doctor:"" },
        mhtFollow: { date:"", compliance:"良好", symptomImprove:"部分", sideEffect:"", bleeding:"无", weight:null, bloodPressure:"", reExamResult:"", planAdjust:"", nextVisit:"", doctor:"" },
@@ -282,48 +416,168 @@
  },
  computed: {
    sourceLabel() { const m={standalone:"独立建档",outpatient:"门诊记录",inpatient:"在院记录"}; return m[this.archive?.sourcePage]||this.archive?.sourcePage||"-"; },
    menstrualLabel() { const m={irregular:"月经紊乱",amenorrhea_short:"闭经(<12个月)",menopause:"绝经(≥12个月)",surgical:"人工绝经"}; return m[this.archive?.archiveForm?.menstrualStatus]||"-"; },
    pregnancySubOptions() {
      const opt = PREGNANCY_OPTIONS[this.pregForm.pregnancy.status];
      return opt ? opt.details : [];
    },
    pregnancyHintOptions() {
      const opt = PREGNANCY_OPTIONS[this.pregForm.pregnancy.status];
      return opt ? opt.hints : [];
    },
    showFollowUp() {
      return this.pregForm.pregnancy.status === "planAbortion" &&
        this.pregForm.pregnancy.hint.includes("经咨询指导后继续妊娠并在产科建档");
    },
    currentFollowRecords() {
      return (this.pregForm.followupCycle || []).filter((r) => r.status === "已完成");
    },
  },
  created() {
    const id = this.$route.query.id;
    if (id) this.loadArchive(id);
    if (id) this.loadArchive(id, this.$route.query.edit == 1);
  },
  methods: {
    loadArchive(id) {
    loadArchive(id, autoEdit) {
      this.loading = true;
      const all = JSON.parse(localStorage.getItem("patientArchives") || "[]");
      this.archive = all.find(item => item.id === id) || null;
      this.archive = loadArchives().find((item) => item.id === id) || null;
      if (this.archive) {
        if (this.archive.subType === 'pregnancy') this.activeTab = "sysQ";
        else if (this.archive.subType === 'menopause') this.activeTab = "sysQ";
        if (this.archive.formData) {
          Object.keys(this.form).forEach(k => {
            if (this.archive.formData[k]) Object.assign(this.form[k], this.archive.formData[k]);
          });
        if (this.archive.subType === "pregnancy") {
          if (this.archive.pregFormData) {
            const saved = JSON.parse(JSON.stringify(this.archive.pregFormData));
            this.pregForm = Object.assign({}, this.pregForm, saved);
            if (!this.pregForm.pregnancy) this.pregForm.pregnancy = { status: "", details: [], hint: [] };
          }
        } else if (this.archive.subType === "menopause") {
          if (this.archive.formData) {
            Object.keys(this.form).forEach((k) => {
              if (this.archive.formData[k]) Object.assign(this.form[k], this.archive.formData[k]);
            });
          }
        }
      }
      if (autoEdit) this.editing = true;
      this.loading = false;
    },
    onPregnancyStatusChange() {
      this.pregForm.pregnancy.details = [];
      this.pregForm.pregnancy.hint = [];
      const opt = PREGNANCY_OPTIONS[this.pregForm.pregnancy.status];
      if (this.archive) this.archive.pregnancyStatus = opt ? opt.label : "";
    },
    toggleEdit() {
      if (this.editing) {
        this.archive.formData = JSON.parse(JSON.stringify(this.form));
        this.archive.updateTime = new Date().toISOString().replace("T"," ").substring(0,19);
        const all = JSON.parse(localStorage.getItem("patientArchives") || "[]");
        const idx = all.findIndex(item => item.id === this.archive.id);
        if (this.archive.subType === "pregnancy") this.archive.pregFormData = JSON.parse(JSON.stringify(this.pregForm));
        if (this.archive.subType === "menopause") this.archive.formData = JSON.parse(JSON.stringify(this.form));
        this.archive.updateTime = new Date().toISOString().replace("T", " ").substring(0, 19);
        const all = loadArchives();
        const idx = all.findIndex((item) => item.id === this.archive.id);
        if (idx >= 0) all[idx] = JSON.parse(JSON.stringify(this.archive));
        localStorage.setItem("patientArchives", JSON.stringify(all));
        saveArchives(all);
        this.$message.success("保存成功");
      }
      this.editing = !this.editing;
    },
    handleCloseCase() {
      this.$modal.confirm("确认将该档案结案?结案后可在列表页查看结案状态。").then(() => {
        this.archive.closeStatus = 1;
        this.archive.updateTime = new Date().toISOString().replace("T", " ").substring(0, 19);
        const all = loadArchives();
        const idx = all.findIndex((item) => item.id === this.archive.id);
        if (idx >= 0) all[idx] = JSON.parse(JSON.stringify(this.archive));
        saveArchives(all);
        this.$message.success("已结案");
      }).catch(() => {});
    },
  },
};
</script>
<style lang="scss" scoped>
$primary: #3f6ef0;
// è¾“入框宽度(按字段长短区分)
.w-xs { width: 90px; }
.w-sm { width: 150px; }
.w-md { width: 200px; }
.w-lg { width: 280px; }
.w-full { width: 100%; }
.action-bar { margin-bottom: 16px; }
.info-card { margin-bottom: 0; }
.card-header { display: flex; justify-content: space-between; align-items: center; font-size: 16px; font-weight: 600; }
.header-sub { font-size: 13px; color: #909399; font-weight: normal; }
.empty-wrap { text-align: center; padding: 100px 0; color: #c0c4cc; font-size: 16px; }
// è“è‰²åˆ†å‰²çº¿ + å°æ ‡é¢˜
.sub-section {
  & + .sub-section {
    margin-top: 24px;
  }
}
.sub-title {
  position: relative;
  padding-left: 12px;
  margin-bottom: 16px;
  font-size: 15px;
  font-weight: 600;
  color: #303133;
  line-height: 1.2;
  &::before {
    content: "";
    position: absolute;
    left: 0;
    top: 1px;
    bottom: 1px;
    width: 3px;
    border-radius: 2px;
    background: $primary;
  }
  &::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: -7px;
    border-bottom: 1px solid #dbe5ff;
  }
}
// å¦Šå¨ æƒ…况 ä¸‰çº§å‹¾é€‰
.preg-cascade {
  .cascade-col {
    padding: 14px 16px;
    min-height: 120px;
    border: 1px solid #e6ecfb;
    border-radius: 8px;
    background: #fafbff;
    .el-radio-group,
    .el-checkbox-group {
      display: flex;
      flex-direction: column;
      align-items: flex-start;
      .el-radio,
      .el-checkbox {
        margin-right: 0;
        margin-bottom: 8px;
      }
    }
  }
  .cascade-label {
    margin-bottom: 10px;
    font-size: 13px;
    font-weight: 600;
    color: $primary;
  }
  .cascade-empty {
    padding: 10px 0;
    font-size: 12px;
    color: #c0c4cc;
  }
}
</style>
src/views/patient/archives/menopause/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
<template>
  <ArchiveList type="menopause" />
</template>
<script>
import ArchiveList from "../components/ArchiveList.vue";
export default {
  name: "MenopauseArchiveList",
  components: { ArchiveList },
};
</script>
src/views/patient/archives/nutrition/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
<template>
  <ArchiveList type="nutrition" />
</template>
<script>
import ArchiveList from "../components/ArchiveList.vue";
export default {
  name: "NutritionArchiveList",
  components: { ArchiveList },
};
</script>
src/views/patient/archives/pregnancy/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
<template>
  <ArchiveList type="pregnancy" />
</template>
<script>
import ArchiveList from "../components/ArchiveList.vue";
export default {
  name: "PregnancyArchiveList",
  components: { ArchiveList },
};
</script>
src/views/patient/archives/types.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,187 @@
// å¦‡ä¿/儿保 5 ç§ç‰¹æ®Šæ‚£è€…专案注册表(单一数据源)
// category: maternal=妇保科 / child=儿保科
// entryLabel: å…¥å£ï¼ˆé—¨è¯Šè®°å½•页选专案)按钮名
// label:      åˆ—表页/详情页标题
// columns:    åˆ—表页列定义 { label, prop, type }
//   type å–值:text(默认) | sex | date | closeStatus
export const ARCHIVE_TYPES = [
  {
    key: "pregnancy",
    category: "maternal",
    entryLabel: "早孕关爱",
    label: "孕妇随访",
    subTypeLabel: "孕妇随访",
    route: "/filing/pregnancy",
    detailRoute: "/filing/maternalDetail",
    columns: [
      { label: "姓名", prop: "patname" },
      { label: "出生日期", prop: "birthday", type: "date" },
      { label: "身份证号", prop: "idcardno" },
      { label: "联系电话", prop: "telcode" },
      { label: "诊断日期", prop: "diagnosisDate", type: "date" },
      { label: "建档日期", prop: "createTime", type: "date" },
      { label: "诊断", prop: "diagname" },
      { label: "妊娠情况", prop: "pregnancyStatus" },
      { label: "主治医生", prop: "attendingDoctor" },
      { label: "经管医生", prop: "managingDoctor" },
      { label: "结案状态", prop: "closeStatus", type: "closeStatus" },
      { label: "登记人员", prop: "registrant" },
    ],
  },
  {
    key: "menopause",
    category: "maternal",
    entryLabel: "围绝经期MHI",
    label: "围绝经期MHI",
    subTypeLabel: "围绝经期MHI",
    route: "/filing/menopause",
    detailRoute: "/filing/maternalDetail",
    columns: [
      { label: "姓名", prop: "patname" },
      { label: "出生日期", prop: "birthday", type: "date" },
      { label: "身份证号", prop: "idcardno" },
      { label: "联系电话", prop: "telcode" },
      { label: "诊断日期", prop: "diagnosisDate", type: "date" },
      { label: "建档日期", prop: "createTime", type: "date" },
      { label: "诊断", prop: "diagname" },
      { label: "随访周期(次数)", prop: "followupCycles" },
      { label: "主治医生", prop: "attendingDoctor" },
      { label: "经管医生", prop: "managingDoctor" },
      { label: "结案状态", prop: "closeStatus", type: "closeStatus" },
      { label: "登记人员", prop: "registrant" },
    ],
  },
  {
    key: "highRisk",
    category: "child",
    entryLabel: "高危儿专案",
    label: "高危儿专案",
    subTypeLabel: "高危儿专案管理",
    route: "/filing/highRisk",
    detailRoute: "/filing/childDetail",
    columns: [
      { label: "儿童姓名", prop: "patname" },
      { label: "家长姓名", prop: "parentInfo.parentName" },
      { label: "拟随访日期", prop: "followupDate", type: "date" },
      { label: "联系电话", prop: "telcode" },
      { label: "诊断日期", prop: "diagnosisDate", type: "date" },
      { label: "建档日期", prop: "createTime", type: "date" },
      { label: "出生孕周", prop: "gestationalWeek" },
      { label: "性别", prop: "sex", type: "sex" },
      { label: "出生日期", prop: "birthday", type: "date" },
      { label: "出生体重", prop: "birthWeight" },
      { label: "高危儿分类", prop: "highRiskCategory" },
      { label: "高危儿名称", prop: "highRiskName" },
      { label: "登记人员", prop: "registrant" },
    ],
  },
  {
    key: "nutrition",
    category: "child",
    entryLabel: "营养性疾病",
    label: "营养性疾病",
    subTypeLabel: "营养性疾病专案管理",
    route: "/filing/nutrition",
    detailRoute: "/filing/childDetail",
    columns: [
      { label: "儿童姓名", prop: "patname" },
      { label: "家长姓名", prop: "parentInfo.parentName" },
      { label: "出生日期", prop: "birthday", type: "date" },
      { label: "联系电话", prop: "telcode" },
      { label: "诊断日期", prop: "diagnosisDate", type: "date" },
      { label: "建档日期", prop: "createTime", type: "date" },
      { label: "出生孕周", prop: "gestationalWeek" },
      { label: "性别", prop: "sex", type: "sex" },
      { label: "出生体重", prop: "birthWeight" },
      { label: "营养性疾病名称", prop: "nutritionName" },
      { label: "登记人员", prop: "registrant" },
    ],
  },
  {
    key: "learning",
    category: "child",
    entryLabel: "学习困难",
    label: "学习困难",
    subTypeLabel: "学习困难专案管理",
    route: "/filing/learning",
    detailRoute: "/filing/childDetail",
    columns: [
      { label: "儿童姓名", prop: "patname" },
      { label: "家长姓名", prop: "parentInfo.parentName" },
      { label: "出生日期", prop: "birthday", type: "date" },
      { label: "联系电话", prop: "telcode" },
      { label: "诊断日期", prop: "diagnosisDate", type: "date" },
      { label: "建档日期", prop: "createTime", type: "date" },
      { label: "性别", prop: "sex", type: "sex" },
      { label: "预约情况", prop: "appointmentStatus" },
    ],
  },
];
export function getTypeByKey(key) {
  return ARCHIVE_TYPES.find((t) => t.key === key);
}
// è¯»å– localStorage æ¡£æ¡ˆåˆ—表
export function loadArchives() {
  try {
    return JSON.parse(localStorage.getItem("patientArchives") || "[]");
  } catch (e) {
    return [];
  }
}
export function saveArchives(list) {
  localStorage.setItem("patientArchives", JSON.stringify(list));
}
// ç”±ä¸€æ¡æ‚£è€…记录(门诊/住院/独立录入)生成某专案的档案草稿
export function buildArchiveRecord(patient, typeKey, sourcePage) {
  const type = getTypeByKey(typeKey);
  const now = new Date().toISOString().replace("T", " ").substring(0, 19);
  const patid = patient.patid || patient.id || "sa_" + Date.now();
  return {
    id: "arc_" + Date.now() + "_" + Math.random().toString(36).substr(2, 6),
    patientId: patid,
    patid: patid,
    patname: patient.patname || patient.name || "",
    sex: patient.sex,
    age: patient.age,
    telcode: patient.telcode || patient.phone || "",
    deptname: patient.deptname || "",
    diagname: patient.diagname || "",
    outhospno: patient.outhospno || "",
    inhospno: patient.inhospno || "",
    bedNo: patient.bedNo || "",
    drname: patient.drname || "",
    nurseName: patient.nurseName || "",
    starttime: patient.starttime || patient.admitdate || "",
    mainCategory: type.category,
    mainCategoryLabel: type.category === "child" ? "儿保科建档" : "妇保科建档",
    subType: type.key,
    subTypeLabel: type.subTypeLabel,
    archiveForm: { doctorName: "" },
    parentInfo: {},
    sourcePage: sourcePage || "outpatient",
    status: "active",
    closeStatus: 0,
    // åˆ—表页新增字段
    idcardno: patient.idcardno || "",
    birthday: patient.birthday || patient.goday || "",
    birthWeight: "",
    gestationalWeek: "",
    diagnosisDate: patient.diagnosisDate || patient.admitdate || "",
    attendingDoctor: patient.drname || "",
    managingDoctor: "",
    registrant: localStorage.getItem("YongHuXM") || "",
    pregnancyStatus: "",
    followupCycles: "",
    highRiskCategory: "",
    highRiskName: "",
    nutritionName: "",
    appointmentStatus: "",
    followupDate: "",
    createTime: now,
    updateTime: now,
  };
}
src/views/patient/patient/behospitalized.vue
@@ -111,7 +111,7 @@
              æ·»åŠ å»¶ç»­æŠ¤ç†ä»»åŠ¡
            </el-button>
          </el-col>
          <el-col :span="1.5">
          <!-- <el-col :span="1.5">
            <el-button
              type="success"
              plain
@@ -122,7 +122,7 @@
            >
              æ‚£è€…建档</el-button
            >
          </el-col>
          </el-col> -->
          <el-col :span="1.5">
            <el-button
              type="danger"
src/views/patient/patient/profile/index.vue
@@ -1,35 +1,23 @@
<template>
  <div class="app-container">
    <!-- å¤´éƒ¨ç›’子 -->
    <div class="personages">
      <el-row :gutter="20">
        <el-col :span="18">
          <div class="headportrait">
            <div class="text-center">
              <img
                v-imgError="require('@/assets/images/shoushuzhudaoyisheng.png')"
                src=""
              />
            </div>
            <div style="margin-left: 10px">{{ userform.name }}</div>
            <div style="margin-left: 10px" class="text-title">
              {{ userform.sex == 1 ? "男" : "女" }}
            </div>
            <div style="margin-left: 10px" class="text-title">
              {{ userform.age }}岁
            </div>
            <div style="margin-left: 10px" class="text-title">
              {{ userform.birthdate }}
            </div>
            <div style="margin-left: 10px" class="text-title">
              {{ userform.nation }}
            </div>
            <div style="margin-left: 10px" class="text-title">
              {{ userform.birthplace }}
            </div>
          </div></el-col
        >
      </el-row>
    <!-- æ‚£è€…信息头部 -->
    <div class="profile-header">
      <div class="profile-header__avatar">
        <img
          v-imgError="require('@/assets/images/shoushuzhudaoyisheng.png')"
          src=""
        />
      </div>
      <div class="profile-header__info">
        <div class="profile-header__name">{{ userform.name }}</div>
        <div class="profile-header__chips">
          <span class="chip">{{ userform.sex == 1 ? "男" : "女" }}</span>
          <span class="chip">{{ userform.age }}岁</span>
          <span class="chip">{{ userform.birthdate }}</span>
          <span class="chip">{{ userform.nation }}</span>
          <span class="chip">{{ userform.birthplace }}</span>
        </div>
      </div>
    </div>
    <el-tabs type="border-card" v-model="activeName" @tab-click="handleClick">
@@ -101,67 +89,45 @@
    <!-- å¥åº·æ¡£æ¡ˆæ¨¡å— -->
    <div class="information-content" v-if="activeName == 'health'">
      <div class="top-message">
        <div class="headlines">
          <div>基础信息</div>
          <el-button type="primary" @click="savefile">保存患者档案</el-button>
      <div class="section-card">
        <div class="section-card__header">
          <span class="section-title">基础信息</span>
          <el-button type="primary" size="small" @click="savefile">保存患者档案</el-button>
        </div>
        <div class="detailed">
        <div class="section-card__body">
          <el-form
            ref="userform"
            :model="userform"
            :rules="rules"
            label-width="150px"
            label-width="110px"
            class="profile-form"
          >
            <el-row :gutter="20">
            <el-row :gutter="24">
              <el-col :span="12">
                <el-form-item label="患者姓名" prop="name">
                  <el-input
                    v-model="userform.name"
                    placeholder="请输入姓名"
                    maxlength="30"
                  ></el-input> </el-form-item
              ></el-col>
            </el-row>
            <el-row >
              <el-col :span="12"
                ><el-form-item label="联系方式" prop="telcode">
                  <el-input
                    v-model="userform.telcode"
                    placeholder="请输入联系方式"
                    maxlength="30"
                  /> </el-form-item
              ></el-col>
                  <el-input v-model="userform.name" placeholder="请输入姓名" maxlength="30"></el-input>
                </el-form-item>
              </el-col>
              <el-col :span="12">
                <el-form-item label="亲属联系方式"  prop="name">
                  <el-input
                    v-model="userform.telcodewx"
                    placeholder="请输入姓名"
                    maxlength="30"
                  ></el-input> </el-form-item
              ></el-col>
            </el-row>
            <el-row :gutter="20">
                <el-form-item label="联系方式" prop="telcode">
                  <el-input v-model="userform.telcode" placeholder="请输入联系方式" maxlength="30"></el-input>
                </el-form-item>
              </el-col>
              <el-col :span="12">
                <el-form-item label="亲属联系方式" prop="name">
                  <el-input v-model="userform.telcodewx" placeholder="请输入姓名" maxlength="30"></el-input>
                </el-form-item>
              </el-col>
              <el-col :span="24">
                <el-form-item label="出生地" prop="birthplace">
                  <el-input
                    v-model="userform.birthplace"
                    placeholder="国、省、地市、区县、街道等详细信息"
                    maxlength="50"
                  /> </el-form-item
              ></el-col>
            </el-row>
            <el-row :gutter="20">
              <el-col :span="24"
                ><el-form-item label="居住地" prop="placeOfResidence">
                  <el-input
                    v-model="userform.placeOfResidence"
                    placeholder="国、省、地市、区县、街道等详细信息"
                    maxlength="50"
                  /> </el-form-item
              ></el-col>
            </el-row>
            <el-row :gutter="20">
                  <el-input v-model="userform.birthplace" placeholder="国、省、地市、区县、街道等详细信息" maxlength="50"></el-input>
                </el-form-item>
              </el-col>
              <el-col :span="24">
                <el-form-item label="居住地" prop="placeOfResidence">
                  <el-input v-model="userform.placeOfResidence" placeholder="国、省、地市、区县、街道等详细信息" maxlength="50"></el-input>
                </el-form-item>
              </el-col>
              <el-col :span="24">
                <el-form-item label="标签" prop="desc">
                  <div class="xinz-inf">
@@ -193,7 +159,6 @@
                      >
                      </el-option>
                    </el-select>
                    <el-button
                      v-else
                      class="button-new-tag"
@@ -208,56 +173,36 @@
          </el-form>
        </div>
      </div>
      <!-- <div class="top-message">
        <div class="headline">病史</div>
        <div class="detailed">
          <el-form :model="form" label-width="100px">
            <el-row>
      <div class="section-card">
        <div class="section-card__header">
          <span class="section-title">病史</span>
        </div>
        <div class="section-card__body">
          <el-form :model="form" label-width="110px" class="profile-form">
            <el-row :gutter="24">
              <el-col :span="8">
                <el-form-item label="过往疾病" prop="name">
                  <el-input
                    v-model="form.pastIllnesses"
                    placeholder="请输入"
                    maxlength="30"
                  />
                  <el-input v-model="form.pastIllnesses" placeholder="请输入" maxlength="30" />
                </el-form-item>
              </el-col>
              <el-col :span="8">
                <el-form-item label="药物过敏" prop="sex">
                  <el-input
                    v-model="form.drugAllergy"
                    placeholder="请输入具体药物/无"
                    maxlength="30"
                  />
                  <el-input v-model="form.drugAllergy" placeholder="请输入具体药物/无" maxlength="30" />
                </el-form-item>
              </el-col>
              <el-col :span="8">
                <el-form-item label="家族病史" prop="age">
                  <el-input
                    v-model="form.familyHistory"
                    placeholder="请输入具体疾病/无"
                    maxlength="30"
                  />
                  <el-input v-model="form.familyHistory" placeholder="请输入具体疾病/无" maxlength="30" />
                </el-form-item>
              </el-col>
            </el-row>
            <el-row>
              <el-col :span="8">
                <el-form-item label="手术史" prop="name">
                  <el-input
                    v-model="form.surgicalHistory"
                    placeholder="请输入手术/无"
                    maxlength="30"
                  />
                  <el-input v-model="form.surgicalHistory" placeholder="请输入手术/无" maxlength="30" />
                </el-form-item>
              </el-col>
              <el-col :span="8">
                <el-form-item label="生育史" prop="age">
                  <el-input
                    v-model="form.reproductiveHistory"
                    placeholder="请输入胎数/无"
                    maxlength="30"
                  />
                  <el-input v-model="form.reproductiveHistory" placeholder="请输入胎数/无" maxlength="30" />
                </el-form-item>
              </el-col>
              <el-col :span="8">
@@ -268,8 +213,6 @@
                  </el-radio-group>
                </el-form-item>
              </el-col>
            </el-row>
            <el-row>
              <el-col :span="8">
                <el-form-item label="吸烟情况" prop="smoking">
                  <el-radio-group v-model="form.smoking">
@@ -294,8 +237,6 @@
                  </el-radio-group>
                </el-form-item>
              </el-col>
            </el-row>
            <el-row>
              <el-col :span="12">
                <el-form-item label="饮食情况" prop="diet">
                  <el-radio-group v-model="form.diet">
@@ -303,8 +244,9 @@
                    <el-radio label="2">正常饮食</el-radio>
                    <el-radio label="3">食欲不振</el-radio>
                  </el-radio-group>
                </el-form-item> </el-col
              ><el-col :span="12">
                </el-form-item>
              </el-col>
              <el-col :span="12">
                <el-form-item label="心理情况" prop="psychology">
                  <el-radio-group v-model="form.psychology">
                    <el-radio label="1">心情愉悦</el-radio>
@@ -316,18 +258,20 @@
            </el-row>
          </el-form>
        </div>
      </div> -->
      <!-- è”系电话 -->
      <div class="bottom-message">
        <div class="headline">
          äº²å±žå·ç ç»´æŠ¤<span style="margin-right: 60px">&nbsp;&nbsp;</span>
          <el-button type="primary" @click="addcompiletb">+新增</el-button>
      </div>
      <!-- äº²å±žå·ç ç»´æŠ¤ -->
      <div class="section-card">
        <div class="section-card__header">
          <span class="section-title">亲属号码维护</span>
          <el-button type="primary" size="small" icon="el-icon-plus" @click="addcompiletb">新增</el-button>
        </div>
        <div class="Table-screen">
        <div class="section-card__body">
          <el-table
            :data="tableData"
            style="width: 100%"
            :row-class-name="tableRowClassName"
            stripe
            border
          >
            <el-table-column prop="id" label="序号" width="180">
            </el-table-column>
@@ -355,7 +299,6 @@
                  size="small"
                  >编辑</el-button
                >
                <el-button @click="deltb(scope.row)" type="danger" size="small"
                  >删除</el-button
                >
@@ -369,7 +312,7 @@
    <div class="medical-record" v-else-if="activeName == 'medical'">
      <!-- é—¨è¯Š -->
      <div v-if="sonactiveName == 'outpatient'">
        <el-table :data="serviceData" style="width: 100%">
        <el-table :data="serviceData" style="width: 100%" stripe border>
          <el-table-column
            label="就诊时间"
            align="center"
@@ -458,7 +401,7 @@
      </div>
      <!-- ä½é™¢ -->
      <div v-if="sonactiveName == 'inhospital'">
        <el-table :data="serviceDatary" style="width: 100%">
        <el-table :data="serviceDatary" style="width: 100%" stripe border>
          <el-table-column
            label="住院号"
            align="center"
@@ -535,7 +478,7 @@
      </div>
      <!-- å‡ºé™¢ -->
      <div v-if="sonactiveName == 'checkout'">
        <el-table :data="serviceDatacy" style="width: 100%">
        <el-table :data="serviceDatacy" style="width: 100%" stripe border>
          <el-table-column
            label="住院号"
            align="center"
@@ -614,91 +557,7 @@
    </div>
    <!-- æœåŠ¡è®°å½• -->
    <div
      class="medical-recordc"
      style="margin-top: 20px; font-size: 20px !important"
      v-else-if="activeName == 'serve'"
    >
      <div style="margin-bottom: 15px" v-for="item in record">
        <el-card class="box-card">
          <div
            style="
              font-size: 22px;
              padding-left: 5px;
              margin: 15px 0;
              border-left: 4px solid rgb(190, 65, 134);
            "
          >
            <span v-if="item.serviceType == 2">出院随访</span>
            <span v-if="item.serviceType == 1">心电随访</span>
            <span v-if="item.serviceType == 3">影像随访</span>
            <span v-if="item.serviceType == 4">宣教关怀</span>
          </div>
          <el-divider></el-divider>
          <div style="margin-top: 10px">
            æœåŠ¡åç§°ï¼š<span style="color: #2775b6">{{
              item.templatename
            }}</span>
          </div>
          <div style="margin-top: 10px">
            åˆ›å»ºæ—¶é—´ï¼š<span style="color: #2775b6">{{ item.createTime }}</span>
          </div>
          <div style="margin-top: 10px">
            å®Œæˆæ—¶é—´ï¼š<span style="color: #2775b6">{{ item.finishtime }}</span>
          </div>
          <div style="margin-top: 10px">
            <el-row :gutter="20">
              <el-col :span="12"
                >科室:
                <span style="color: #2775b6">{{ item.deptname }}</span></el-col
              >
              <el-col :span="12"
                >病区:
                <span style="color: #2775b6">{{
                  item.leavehospitaldistrictname
                }}</span></el-col
              >
            </el-row>
          </div>
          <div
            style="
              font-size: 20px;
              padding-left: 5px;
              margin: 15px 0;
              border-left: 4px solid rgb(65, 161, 190);
            "
          >
            äººå·¥è®°å½•
          </div>
          <div style="margin-top: 10px">
            <el-row :gutter="20">
              <el-col :span="6"
                >负责人:
                <span style="color: #2775b6">{{ item.createBy }}</span></el-col
              >
              <el-col :span="6"
                >主治医生:
                <span style="color: #2775b6">{{ item.drname }}</span></el-col
              >
              <el-col :span="6"
                >责任护士
                <span style="color: #2775b6">{{ item.nurseName }}</span></el-col
              >
            </el-row>
          </div>
          <div style="margin-top: 10px">
            ç»“果状态:
            <span style="color: #2775b6" v-if="item.excep == 0">服务正常</span>
            <span style="color: #b55e54" v-if="item.excep == 1">异常</span>
          </div>
          <!-- <div style="margin-top: 10px">
            å¤‡æ³¨ï¼š
            <span style="color: #2775b6">提醒注意饮食健康</span>
          </div> -->
        </el-card>
      </div>
    </div>
    <ServiceRecords v-else-if="activeName == 'serve'" :patid="id" />
    <!-- å¥åº·ç›‘测 -->
    <!-- <div class="medical-record" v-show="activeName == 'monitor'">
      <div v-show="sontwoactiveName == 'blood'" style="display: flex">
@@ -846,13 +705,14 @@
  Patientclinic,
} from "@/api/patient/homepage";
import { getsearchrResults, getTaskservelist } from "@/api/AiCentre/index";
import { getsearchrResults } from "@/api/AiCentre/index";
import ServiceRecords from "@/views/patient/archives/components/ServiceRecords.vue";
import { listPatouthosp } from "@/api/smartor/patouthosp";
import { listpatient } from "@/api/patient/record";
export default {
  name: "Profile",
  components: { userAvatar, userInfo, resetPwd, SFtable },
  components: { userAvatar, userInfo, resetPwd, SFtable, ServiceRecords },
  data() {
    return {
      userform: {},
@@ -1233,24 +1093,11 @@
    handleClick(tab, event) {
      if (tab.index == "1") {
        this.getList(1);
      } else if (tab.index == "2") {
        this.handleClickfw();
      }
    },
    handleClickson(tab, event) {
      console.log(tab, "son");
      this.getList(tab.index);
    },
    // æŸ¥è¯¢æœåŠ¡è®°å½•
    handleClickfw() {
      getTaskservelist({
        patid: this.id,
      }).then((res) => {
        if (res.code == 200) {
          this.record = res.rows[0].serviceSubtaskList;
        }
      });
    },
    // è”系方式新增
    Addanumber() {},
@@ -1780,134 +1627,354 @@
</script>
<style lang="scss" scoped>
.personages {
  height: 88px;
  width: 100%;
  background-color: #5e86f9;
  border-radius: 5px;
  padding: 20px;
  margin-bottom: 10px;
  line-height: 48px;
  font-size: 25px;
  font-weight: 600;
  color: #ffffff;
  .headportrait {
    display: flex;
    .text-center {
      width: 55px;
      height: 55px;
      margin-right: 10px;
      img {
        width: 100%;
        height: 100%;
        border-radius: 50%;
      }
$primary: #3f6ef0;
// ===== æ‚£è€…信息头部 =====
.profile-header {
  position: relative;
  display: flex;
  align-items: center;
  padding: 26px 32px;
  margin-bottom: 16px;
  border-radius: 14px;
  overflow: hidden;
  color: #fff;
  background: linear-gradient(120deg, #3f6ef0 0%, #5b8cff 55%, #78a9ff 100%);
  box-shadow: 0 8px 24px rgba(63, 110, 240, 0.26);
  &::before {
    content: "";
    position: absolute;
    right: -70px;
    top: -90px;
    width: 280px;
    height: 280px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.09);
  }
  &::after {
    content: "";
    position: absolute;
    right: 90px;
    bottom: -130px;
    width: 230px;
    height: 230px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.06);
  }
  &__avatar {
    position: relative;
    z-index: 1;
    width: 76px;
    height: 76px;
    flex-shrink: 0;
    border-radius: 50%;
    padding: 3px;
    background: rgba(255, 255, 255, 0.4);
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18);
    img {
      width: 100%;
      height: 100%;
      border-radius: 50%;
      object-fit: cover;
      background: #eef2fb;
    }
    .text-title {
      font-size: 20px;
  }
  &__info {
    position: relative;
    z-index: 1;
    margin-left: 22px;
    min-width: 0;
  }
  &__name {
    font-size: 24px;
    font-weight: 700;
    letter-spacing: 1px;
    line-height: 1.3;
  }
  &__chips {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin-top: 12px;
    .chip {
      padding: 4px 14px;
      font-size: 13px;
      font-weight: 500;
      color: #ecfcfc;
      border-radius: 999px;
      background: rgba(255, 255, 255, 0.2);
      border: 1px solid rgba(255, 255, 255, 0.3);
      color: #f5f8ff;
      line-height: 1.5;
    }
  }
}
.xinz-inf {
  font-size: 18px;
  // white-space: nowrap;
// ===== Tabs =====
::v-deep .el-tabs--border-card {
  border: none;
  border-radius: 12px;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
  overflow: hidden;
  text-overflow: ellipsis;
  line-height: 48px;
  > .el-tabs__header {
    margin: 0;
    background: #fff;
    border-bottom: 1px solid #eef0f4;
  .el-tag + .el-tag {
    margin-left: 10px;
    .el-tabs__nav {
      border: none;
      .el-tabs__item {
        height: 54px;
        line-height: 54px;
        font-size: 15px;
        color: #606266;
        border: none !important;
        .mulsz {
          font-size: 15px;
        }
        &.is-active {
          color: $primary;
          font-weight: 600;
          background: #f3f7ff;
          box-shadow: inset 0 -3px 0 $primary;
        }
      }
    }
  }
  > .el-tabs__content {
    padding: 0;
  }
}
// ===== å¥åº·æ¡£æ¡ˆå¡ç‰‡ =====
.information-content {
  margin-top: 16px;
}
.section-card {
  margin-bottom: 16px;
  background: #fff;
  border-radius: 12px;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
  overflow: hidden;
  &__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 16px 24px;
    border-bottom: 1px solid #f0f2f5;
    .section-title {
      position: relative;
      padding-left: 12px;
      font-size: 16px;
      font-weight: 600;
      color: #303133;
      &::before {
        content: "";
        position: absolute;
        left: 0;
        top: 50%;
        transform: translateY(-50%);
        width: 4px;
        height: 16px;
        border-radius: 2px;
        background: $primary;
      }
    }
  }
  &__body {
    padding: 20px 24px 4px;
  }
}
// è¡¨å•
.profile-form {
  .el-form-item {
    margin-bottom: 18px;
  }
  ::v-deep .el-form-item__label {
    color: #606266;
    font-weight: 500;
  }
  ::v-deep .el-input__inner {
    border-radius: 6px;
  }
}
// æ ‡ç­¾
.xinz-inf {
  line-height: 40px;
  .el-tag {
    margin-bottom: 6px;
    border-radius: 4px;
    & + .el-tag {
      margin-left: 8px;
    }
  }
  .button-new-tag {
    margin-left: 10px;
    margin-left: 8px;
    height: 32px;
    line-height: 30px;
    padding-top: 0;
    padding-bottom: 0;
  }
  .input-new-tag {
    width: 90px;
    margin-left: 10px;
    margin-left: 8px;
    vertical-align: bottom;
  }
}
.el-tabs--border-card {
  // font-size: 25px;
  .mulsz {
    font-size: 25px;
    margin-top: 20px;
// ===== åŒ»ç–—档案表格 =====
.medical-record {
  margin-top: 16px;
  padding: 24px;
  background: #fff;
  border-radius: 12px;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
  ::v-deep .el-table {
    th.el-table__cell {
      background: #f5f7fb;
      color: #303133;
      font-weight: 600;
    }
    .el-table__row td {
      padding: 10px 0;
    }
    .el-table__row--striped td {
      background: #fafbfd;
    }
    .el-table__row:hover > td {
      background: #f3f7ff !important;
    }
  }
}
.information-content {
  margin-top: 20px;
  background: #ffffff;
  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);
  .top-message {
    margin-top: 25px;
    margin-left: 100px;
    .detailed {
      width: 88%;
      border-radius: 8px;
      padding: 30px;
      margin-bottom: 30px;
      background-color: #ddf0f8;
      .bg-purple {
        margin-bottom: 20px;
      }
      .spanvalue {
        display: inline-block;
        min-width: 200px;
        border-bottom: 1px solid rgb(172, 172, 172);
      }
// å¡ç‰‡å†…表格(亲属号码)
.section-card__body {
  ::v-deep .el-table {
    th.el-table__cell {
      background: #f5f7fb;
      color: #303133;
      font-weight: 600;
    }
  }
}
// ===== æœåŠ¡è®°å½• =====
.service-record {
  margin-top: 16px;
  .service-item {
    margin-bottom: 16px;
  }
  .service-card {
    border-radius: 12px;
    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
    ::v-deep .el-card__body {
      padding: 20px 24px;
    }
  }
  .headline {
    font-size: 25px;
    padding-left: 5px;
    margin-bottom: 10px;
    border-left: 8px solid rgb(65, 161, 190);
  }
  .headlines {
    font-size: 25px;
  .service-card__header {
    display: flex;
    width: 88%;
    align-items: center;
    justify-content: space-between;
    padding-left: 5px;
    margin-bottom: 10px;
    border-left: 8px solid rgb(65, 161, 190);
    padding-bottom: 14px;
    border-bottom: 1px dashed #eef0f4;
  }
  .bottom-message {
    margin-bottom: 25px;
    margin-left: 100px;
    .Table-screen {
      width: 88%;
      padding: 30px;
      margin-bottom: 30px;
      border-radius: 8px;
      background-color: #ddf0f8;
      font-size: 20px !important;
  .service-card__title {
    display: flex;
    align-items: center;
    gap: 12px;
  }
  .service-badge {
    display: inline-block;
    padding: 4px 12px;
    font-size: 13px;
    font-weight: 600;
    color: $primary;
    background: #eef4ff;
    border-radius: 6px;
  }
  .service-name {
    font-size: 16px;
    font-weight: 600;
    color: #303133;
  }
  .service-status {
    padding: 4px 12px;
    font-size: 13px;
    border-radius: 999px;
    &.is-ok {
      color: #12a182;
      background: #e8f7f1;
    }
    &.is-bad {
      color: #e0564c;
      background: #fdeeee;
    }
  }
  .service-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 18px 24px;
    padding-top: 16px;
  }
  .service-cell {
    display: flex;
    flex-direction: column;
    gap: 4px;
    .s-label {
      font-size: 12px;
      color: #909399;
    }
    .s-value {
      font-size: 14px;
      color: #303133;
    }
  }
}
.medical-record {
  margin-top: 20px;
  margin: 20px;
  padding: 30px;
  background: #ffffff;
  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);
}
.sontwoactiveName {
  margin: 0 auto;
}
::v-deep.el-table .warning-row {
  background: oldlace;
// é»˜è®¤è”系方式高亮行
::v-deep .el-table .warning-row {
  background: #fff7e6;
}
</style>
src/views/repositoryai/intention/index.vue
@@ -152,7 +152,6 @@
                  icon="el-icon-plus"
                  size="medium"
                  @click="handleAdd"
                  >新增</el-button
                >
              </el-col>
@@ -165,7 +164,6 @@
                  size="medium"
                  :disabled="multiple"
                  @click="handleDelete"
                  >删除</el-button
                >
              </el-col>
@@ -218,7 +216,6 @@
                </template>
              </el-table-column>
              <!-- <el-table-column
                label="正常值范围"
                align="center"
@@ -230,7 +227,7 @@
                  <span>{{ scope.row.warnup }}~{{ scope.row.warndown }}</span>
                </template>
              </el-table-column> -->
             <el-table-column
              <el-table-column
                label="适用场景"
                align="center"
                key="suitWayList"
@@ -253,7 +250,6 @@
                    size="medium"
                    type="text"
                    @click="illnessUpdate(scope.row)"
                    ><span class="button-textxg"
                      ><i class="el-icon-date"></i>详情</span
                    ></el-button
@@ -290,7 +286,6 @@
                    size="medium"
                    type="text"
                    @click="handleUpdate(scope.row)"
                    ><span class="button-textxg"
                      ><i class="el-icon-edit"></i>修改</span
                    ></el-button
@@ -308,7 +303,6 @@
                    size="medium"
                    type="text"
                    @click="handleDelete(scope.row)"
                    ><span class="button-textsc"
                      ><i class="el-icon-delete"></i>删除</span
                    ></el-button
@@ -351,14 +345,14 @@
                  <el-input
                    v-model="indexform.targetname"
                    placeholder="请输入指标名称"
                    maxlength="40"
                    maxlength="100"
                  />
                </el-form-item>
              </el-col>
              <el-col :span="12">
                <el-form-item label="指标分类" prop="assortid">
                  <el-select
                  style="width: 300px;"
                    style="width: 300px"
                    v-model="indexform.assortid"
                    size="medium"
                    filterable
@@ -379,18 +373,19 @@
                    </el-option-group>
                  </el-select>
                </el-form-item>
              </el-col>
            </el-row>
            <el-row v-if="!measurement">
              <el-col :span="24">
                <el-form-item label="指标描述" prop="targetdesc">
                  <el-input
                    type="textarea"
                    :rows="2"
                    v-model="indexform.targetdesc"
                    placeholder="请输入指标描述"
                    id="targetdesc"
                    @focus="currentInputId = 'targetdesc'"
                    maxlength="40"
                    maxlength="400"
                  />
                </el-form-item> </el-col
            ></el-row>
@@ -1293,7 +1288,7 @@
          targetregex: "",
          targetregex2: "",
          isoperation: 1,
          isabnormal:0,
          isabnormal: 0,
          picturePath: "",
          dynamiccruxs: [],
          nodynamiccruxs: [],
src/views/sfstatistics/percentage/components/ChartDialog.vue
@@ -210,6 +210,28 @@
          { value: careData.inProgress, name: "护理进行中" },
          { value: careData.notStarted, name: "护理未开始" },
        ];
      } else if (this.activeTab === "combined") {
        legendData = ["待随访", "随访成功", "随访失败"];
        const followUpData = {
          pending: 0,
          success: 0,
          fail: 0,
        };
        this.data.forEach((item) => {
          followUpData.pending +=
            (item.pendingFollowUp || 0) + (item.pendingFollowUpAgain || 0);
          followUpData.success +=
            (item.followUpSuccess || 0) + (item.followUpSuccessAgain || 0);
          followUpData.fail +=
            (item.followUpFail || 0) + (item.followUpFailAgain || 0);
        });
        seriesData = [
          { value: followUpData.pending, name: "待随访" },
          { value: followUpData.success, name: "随访成功" },
          { value: followUpData.fail, name: "随访失败" },
        ];
      }
      return { legendData, seriesData };
@@ -536,6 +558,77 @@
            },
          },
        ];
      } else if (this.activeTab === "combined") {
        title = "综合随访";
        yAxisName1 = "人次";
        legendData = ["出院人次", "首次完成", "再次完成", "随访率(%)", "推送率(%)"];
        colors = ["#5470C6", "#91CC75", "#FAC858", "#EE6666", "#9A60B4"];
        const dischargeData = this.data.map((item) => item.dischargeCount || 0);
        const firstSuccessData = this.data.map(
          (item) => item.followUpSuccess || 0
        );
        const againSuccessData = this.data.map(
          (item) => item.followUpSuccessAgain || 0
        );
        const followUpRateData = this.data.map((item) => {
          const success = Number(item.followUpSuccess) || 0;
          const nonFollowUp = Number(item.nonFollowUp) || 0;
          const followUpNeeded = Number(item.followUpNeeded) || 0;
          const denominator = followUpNeeded + nonFollowUp;
          return denominator > 0
            ? Number((((success + nonFollowUp) / denominator) * 100).toFixed(2))
            : 0;
        });
        const pushRateData = this.data.map((item) => {
          const successAgain = Number(item.followUpSuccessAgain) || 0;
          const nonFollowUp = Number(item.nonFollowUp) || 0;
          const followUpNeeded = Number(item.followUpNeeded) || 0;
          const denominator = followUpNeeded + nonFollowUp;
          return denominator > 0
            ? Number(((successAgain / denominator) * 100).toFixed(2))
            : 0;
        });
        series = [
          {
            name: "出院人次",
            type: "bar",
            barWidth: "25%",
            data: dischargeData,
            itemStyle: { borderRadius: [4, 4, 0, 0] },
          },
          {
            name: "首次完成",
            type: "bar",
            barWidth: "25%",
            data: firstSuccessData,
            itemStyle: { borderRadius: [4, 4, 0, 0] },
          },
          {
            name: "再次完成",
            type: "bar",
            barWidth: "25%",
            data: againSuccessData,
            itemStyle: { borderRadius: [4, 4, 0, 0] },
          },
          {
            name: "随访率(%)",
            type: "line",
            yAxisIndex: 1,
            data: followUpRateData,
            symbolSize: 8,
            lineStyle: { width: 3 },
          },
          {
            name: "推送率(%)",
            type: "line",
            yAxisIndex: 1,
            data: pushRateData,
            symbolSize: 8,
            lineStyle: { width: 3, type: "dotted" },
          },
        ];
      }
      return {
src/views/sfstatistics/percentage/components/CombinedFollowUp.vue
@@ -582,6 +582,18 @@
    },
    // ===================== å¯¼å‡º =====================
    getServiceTypeLabel() {
      const val = this.queryParams.serviceType;
      if (val == null) return "";
      const values = Array.isArray(val) ? val : [val];
      const list =
        this.options && this.options.length ? this.options : this.tasktypes;
      const labels = values
        .map((v) => (list.find((o) => o.value === v) || {}).label)
        .filter(Boolean);
      return labels.join("/");
    },
    async exportTable() {
      try {
        let dateRangeString = "";
@@ -603,7 +615,9 @@
          dateRangeString = isLishuiHospital ? `${now.getFullYear()}å¹´${now.getMonth() + 1}月` : `${now.getMonth() + 1}月`;
        }
        const excelName = `综合随访统计表_${dateRangeString}.xlsx`;
        const serviceTypeLabel = this.getServiceTypeLabel();
        const serviceTypeSuffix = serviceTypeLabel ? `_${serviceTypeLabel}` : "";
        const excelName = `综合随访统计表${serviceTypeSuffix}_${dateRangeString}.xlsx`;
        if (!this.tableData || this.tableData.length === 0) {
          this.$message.warning("暂无数据可导出");
          return false;
@@ -611,7 +625,7 @@
        const workbook = new ExcelJS.Workbook();
        const worksheet = workbook.addWorksheet("综合统计");
        this.buildExportSheet(worksheet, dateRangeString);
        this.buildExportSheet(worksheet, dateRangeString, serviceTypeSuffix);
        const buffer = await workbook.xlsx.writeBuffer();
        const blob = new Blob([buffer], {
          type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
@@ -626,7 +640,7 @@
      }
    },
    buildExportSheet(worksheet, dateRangeString) {
    buildExportSheet(worksheet, dateRangeString, serviceTypeSuffix = "") {
      // å®šä¹‰æ ·å¼
      const titleStyle = {
        font: { name: "微软雅黑", size: 16, bold: true },
@@ -653,7 +667,7 @@
      // æ ‡é¢˜è¡Œ
      worksheet.mergeCells(1, 1, 1, 20);
      worksheet.getCell(1, 1).value = `综合随访统计表_${dateRangeString}`;
      worksheet.getCell(1, 1).value = `综合随访统计表${serviceTypeSuffix}_${dateRangeString}`;
      worksheet.getCell(1, 1).style = titleStyle;
      worksheet.getRow(1).height = 35;
src/views/sfstatistics/percentage/components/FirstFollowUp.vue
@@ -590,6 +590,223 @@
            </template>
          </el-table-column>
          <el-table-column
            v-if="isTargetHospital"
            label="人工"
            align="center"
          >
            <el-table-column
              label="发送完成"
              align="center"
              key="manualSuccess"
              prop="manualSuccess"
              :render-header="Tooltipfswc"
            >
              <template slot-scope="scope">
                <el-button
                  size="medium"
                  type="text"
                  @click="
                    handleViewDetails(
                      scope.row,
                      'manualSuccessInfo',
                      '发送完成人工随访列表'
                    )
                  "
                >
                  <span class="button-zx">{{ scope.row.manualSuccess }}</span>
                </el-button>
              </template>
            </el-table-column>
            <el-table-column
              label="最终完成"
              align="center"
              key="manualAgainSuccess"
              prop="manualAgainSuccess"
              :render-header="Tooltipzzwc"
            >
              <template slot-scope="scope">
                <el-button
                  size="medium"
                  type="text"
                  @click="
                    handleViewDetails(
                      scope.row,
                      'manualAgainSuccessInfo',
                      '最终完成人工随访列表'
                    )
                  "
                >
                  <span class="button-zx">{{
                    scope.row.manualAgainSuccess
                  }}</span>
                </el-button>
              </template>
            </el-table-column>
          </el-table-column>
          <el-table-column
            v-if="isTargetHospital"
            label="语音"
            align="center"
          >
            <el-table-column
              label="发送完成"
              align="center"
              key="voiceSuccess"
              prop="voiceSuccess"
              :render-header="Tooltipfswc"
            >
              <template slot-scope="scope">
                <el-button
                  size="medium"
                  type="text"
                  @click="
                    handleViewDetails(
                      scope.row,
                      'voiceSuccessInfo',
                      '发送完成语音随访列表'
                    )
                  "
                >
                  <span class="button-zx">{{ scope.row.voiceSuccess }}</span>
                </el-button>
              </template>
            </el-table-column>
            <el-table-column
              label="最终完成"
              align="center"
              key="voiceAgainSuccess"
              prop="voiceAgainSuccess"
              :render-header="Tooltipzzwc"
            >
              <template slot-scope="scope">
                <el-button
                  size="medium"
                  type="text"
                  @click="
                    handleViewDetails(
                      scope.row,
                      'voiceAgainSuccessInfo',
                      '最终完成语音随访列表'
                    )
                  "
                >
                  <span class="button-zx">{{
                    scope.row.voiceAgainSuccess
                  }}</span>
                </el-button>
              </template>
            </el-table-column>
          </el-table-column>
          <el-table-column
            v-if="isTargetHospital"
            label="短信"
            align="center"
          >
            <el-table-column
              label="发送完成"
              align="center"
              key="smsSuccess"
              prop="smsSuccess"
              :render-header="Tooltipfswc"
            >
              <template slot-scope="scope">
                <el-button
                  size="medium"
                  type="text"
                  @click="
                    handleViewDetails(
                      scope.row,
                      'smsSuccessInfo',
                      '发送完成短信随访列表'
                    )
                  "
                >
                  <span class="button-zx">{{ scope.row.smsSuccess }}</span>
                </el-button>
              </template>
            </el-table-column>
            <el-table-column
              label="最终完成"
              align="center"
              key="smsAgainSuccess"
              prop="smsAgainSuccess"
              :render-header="Tooltipzzwc"
            >
              <template slot-scope="scope">
                <el-button
                  size="medium"
                  type="text"
                  @click="
                    handleViewDetails(
                      scope.row,
                      'smsAgainSuccessInfo',
                      '最终完成短信随访列表'
                    )
                  "
                >
                  <span class="button-zx">{{
                    scope.row.smsAgainSuccess
                  }}</span>
                </el-button>
              </template>
            </el-table-column>
          </el-table-column>
          <el-table-column
            v-if="isTargetHospital"
            label="微信"
            align="center"
          >
            <el-table-column
              label="发送完成"
              align="center"
              key="weChatSuccess"
              prop="weChatSuccess"
              :render-header="Tooltipfswc"
            >
              <template slot-scope="scope">
                <el-button
                  size="medium"
                  type="text"
                  @click="
                    handleViewDetails(
                      scope.row,
                      'wechatSuccessInfo',
                      '发送完成微信随访列表'
                    )
                  "
                >
                  <span class="button-zx">{{ scope.row.weChatSuccess }}</span>
                </el-button>
              </template>
            </el-table-column>
            <el-table-column
              label="最终完成"
              align="center"
              key="weChatAgainSuccess"
              prop="weChatAgainSuccess"
              :render-header="Tooltipzzwc"
            >
              <template slot-scope="scope">
                <el-button
                  size="medium"
                  type="text"
                  @click="
                    handleViewDetails(
                      scope.row,
                      'wechatAgainSuccessInfo',
                      '最终完成微信随访列表'
                    )
                  "
                >
                  <span class="button-zx">{{
                    scope.row.weChatAgainSuccess
                  }}</span>
                </el-button>
              </template>
            </el-table-column>
          </el-table-column>
          <el-table-column
            v-if="!isTargetHospital"
            label="人工"
            align="center"
            key="manual"
@@ -607,7 +824,13 @@
              </el-button>
            </template>
          </el-table-column>
          <el-table-column label="语音" align="center" key="voice" prop="voice">
          <el-table-column
            v-if="!isTargetHospital"
            label="语音"
            align="center"
            key="voice"
            prop="voice"
          >
            <template slot-scope="scope">
              <el-button
                size="medium"
@@ -620,7 +843,13 @@
              </el-button>
            </template>
          </el-table-column>
          <el-table-column label="短信" align="center" key="sms" prop="sms">
          <el-table-column
            v-if="!isTargetHospital"
            label="短信"
            align="center"
            key="sms"
            prop="sms"
          >
            <template slot-scope="scope">
              <el-button
                size="medium"
@@ -632,6 +861,7 @@
            </template>
          </el-table-column>
          <el-table-column
            v-if="!isTargetHospital"
            label="微信"
            align="center"
            key="weChat"
@@ -748,6 +978,14 @@
      patientqueryParams: { pn: 1, ps: 10 },
      tasktypes: store.getters.tasktypes,
    };
  },
  computed: {
    isTargetHospital() {
      return (
        this.orgname == "第一人民医院湖滨院区" ||
        this.orgname == "第一人民医院吴山院区"
      );
    },
  },
  methods: {
    loadData() {
@@ -982,6 +1220,32 @@
        [h("span", column.label)]
      );
    },
    Tooltipfswc(h, { column }) {
      return h(
        "el-tooltip",
        {
          props: {
            content: "首次发送患者即填报成功的数量",
            placement: "top",
            effect: "dark",
          },
        },
        [h("span", column.label)]
      );
    },
    Tooltipzzwc(h, { column }) {
      return h(
        "el-tooltip",
        {
          props: {
            content: "首次未完成二次或三次补偿推送本方式的完成次数",
            placement: "top",
            effect: "dark",
          },
        },
        [h("span", column.label)]
      );
    },
    getInnerSummaries(param) {
      const { columns, data } = param;
      const sums = [];
@@ -1207,19 +1471,21 @@
          }
        }
        // æ ¹æ® serviceType ç”Ÿæˆéšè®¿ç±»åž‹åç§°
        // æ ¹æ® serviceType ç”Ÿæˆéšè®¿ç±»åž‹åç§°ï¼ˆå…¼å®¹å•选/多选)
        const serviceTypes = Array.isArray(this.queryParams.serviceType)
          ? this.queryParams.serviceType
          : this.queryParams.serviceType
          ? [this.queryParams.serviceType]
          : [];
        let serviceTypeName = "首次出院随访"; // æ–‡ä»¶åä½¿ç”¨çš„名称
        let sheetTypeName = "首次随访"; // å·¥ä½œè¡¨ä½¿ç”¨çš„名称(简化版)
        if (
          this.queryParams.serviceType &&
          Array.isArray(this.queryParams.serviceType) &&
          this.queryParams.serviceType.length > 0
        ) {
        if (serviceTypes.length > 0) {
          // ä½¿ç”¨ props ä¸­çš„ options(即 tasktypes)
          if (this.options && Array.isArray(this.options)) {
            const matchedTypes = this.options.filter((task) =>
              this.queryParams.serviceType.includes(task.value)
              serviceTypes.includes(task.value)
            );
            console.log(matchedTypes, "matchedTypes");
@@ -1232,14 +1498,14 @@
              serviceTypeName = `首次${typeNames.join("/")}`;
              // å·¥ä½œè¡¨åç®€åŒ–(取第一个+“等”)
              sheetTypeName = `首次${typeNames[0]}等`;
            } else if (this.queryParams.serviceType.length > 0) {
            } else {
              // æ— åŒ¹é…æ—¶ä½¿ç”¨åŽŸå§‹å€¼
              const typeStr = this.queryParams.serviceType.join("/");
              const typeStr = serviceTypes.join("/");
              serviceTypeName = `首次${typeStr}`;
              sheetTypeName = "首次随访";
            }
          } else if (this.queryParams.serviceType.length > 0) {
            const typeStr = this.queryParams.serviceType.join("/");
          } else {
            const typeStr = serviceTypes.join("/");
            serviceTypeName = `首次${typeStr}`;
            sheetTypeName = "首次随访";
          }
@@ -1800,7 +2066,9 @@
      console.log(1);
      this.patientqueryParams.serviceTypes = queryParams.serviceType
        ? queryParams.serviceType.join(",")
        ? Array.isArray(queryParams.serviceType)
          ? queryParams.serviceType.join(",")
          : String(queryParams.serviceType)
        : null;
      return selectTimelyRate(this.patientqueryParams);
src/views/sfstatistics/percentage/components/SecondFollowUp.vue
@@ -554,6 +554,223 @@
            :render-header="Tooltipsfl"
          />
          <el-table-column
            v-if="isTargetHospital"
            label="人工"
            align="center"
          >
            <el-table-column
              label="发送完成"
              align="center"
              key="manualSuccess"
              prop="manualSuccess"
              :render-header="Tooltipfswc"
            >
              <template slot-scope="scope">
                <el-button
                  size="medium"
                  type="text"
                  @click="
                    handleViewDetails(
                      scope.row,
                      'manualSuccessInfo',
                      '发送完成人工随访列表'
                    )
                  "
                >
                  <span class="button-zx">{{ scope.row.manualSuccess }}</span>
                </el-button>
              </template>
            </el-table-column>
            <el-table-column
              label="最终完成"
              align="center"
              key="manualAgainSuccess"
              prop="manualAgainSuccess"
              :render-header="Tooltipzzwc"
            >
              <template slot-scope="scope">
                <el-button
                  size="medium"
                  type="text"
                  @click="
                    handleViewDetails(
                      scope.row,
                      'manualAgainSuccessInfo',
                      '最终完成人工随访列表'
                    )
                  "
                >
                  <span class="button-zx">{{
                    scope.row.manualAgainSuccess
                  }}</span>
                </el-button>
              </template>
            </el-table-column>
          </el-table-column>
          <el-table-column
            v-if="isTargetHospital"
            label="语音"
            align="center"
          >
            <el-table-column
              label="发送完成"
              align="center"
              key="voiceSuccess"
              prop="voiceSuccess"
              :render-header="Tooltipfswc"
            >
              <template slot-scope="scope">
                <el-button
                  size="medium"
                  type="text"
                  @click="
                    handleViewDetails(
                      scope.row,
                      'voiceSuccessInfo',
                      '发送完成语音随访列表'
                    )
                  "
                >
                  <span class="button-zx">{{ scope.row.voiceSuccess }}</span>
                </el-button>
              </template>
            </el-table-column>
            <el-table-column
              label="最终完成"
              align="center"
              key="voiceAgainSuccess"
              prop="voiceAgainSuccess"
              :render-header="Tooltipzzwc"
            >
              <template slot-scope="scope">
                <el-button
                  size="medium"
                  type="text"
                  @click="
                    handleViewDetails(
                      scope.row,
                      'voiceAgainSuccessInfo',
                      '最终完成语音随访列表'
                    )
                  "
                >
                  <span class="button-zx">{{
                    scope.row.voiceAgainSuccess
                  }}</span>
                </el-button>
              </template>
            </el-table-column>
          </el-table-column>
          <el-table-column
            v-if="isTargetHospital"
            label="短信"
            align="center"
          >
            <el-table-column
              label="发送完成"
              align="center"
              key="smsSuccess"
              prop="smsSuccess"
              :render-header="Tooltipfswc"
            >
              <template slot-scope="scope">
                <el-button
                  size="medium"
                  type="text"
                  @click="
                    handleViewDetails(
                      scope.row,
                      'smsSuccessInfo',
                      '发送完成短信随访列表'
                    )
                  "
                >
                  <span class="button-zx">{{ scope.row.smsSuccess }}</span>
                </el-button>
              </template>
            </el-table-column>
            <el-table-column
              label="最终完成"
              align="center"
              key="smsAgainSuccess"
              prop="smsAgainSuccess"
              :render-header="Tooltipzzwc"
            >
              <template slot-scope="scope">
                <el-button
                  size="medium"
                  type="text"
                  @click="
                    handleViewDetails(
                      scope.row,
                      'smsAgainSuccessInfo',
                      '最终完成短信随访列表'
                    )
                  "
                >
                  <span class="button-zx">{{
                    scope.row.smsAgainSuccess
                  }}</span>
                </el-button>
              </template>
            </el-table-column>
          </el-table-column>
          <el-table-column
            v-if="isTargetHospital"
            label="微信"
            align="center"
          >
            <el-table-column
              label="发送完成"
              align="center"
              key="weChatSuccess"
              prop="weChatSuccess"
              :render-header="Tooltipfswc"
            >
              <template slot-scope="scope">
                <el-button
                  size="medium"
                  type="text"
                  @click="
                    handleViewDetails(
                      scope.row,
                      'wechatSuccessInfo',
                      '发送完成微信随访列表'
                    )
                  "
                >
                  <span class="button-zx">{{ scope.row.weChatSuccess }}</span>
                </el-button>
              </template>
            </el-table-column>
            <el-table-column
              label="最终完成"
              align="center"
              key="weChatAgainSuccess"
              prop="weChatAgainSuccess"
              :render-header="Tooltipzzwc"
            >
              <template slot-scope="scope">
                <el-button
                  size="medium"
                  type="text"
                  @click="
                    handleViewDetails(
                      scope.row,
                      'wechatAgainSuccessInfo',
                      '最终完成微信随访列表'
                    )
                  "
                >
                  <span class="button-zx">{{
                    scope.row.weChatAgainSuccess
                  }}</span>
                </el-button>
              </template>
            </el-table-column>
          </el-table-column>
          <el-table-column
            v-if="!isTargetHospital"
            label="人工"
            align="center"
            key="manualAgain"
@@ -576,6 +793,7 @@
            </template>
          </el-table-column>
          <el-table-column
            v-if="!isTargetHospital"
            label="语音"
            align="center"
            key="voiceAgain"
@@ -594,6 +812,7 @@
            </template>
          </el-table-column>
          <el-table-column
            v-if="!isTargetHospital"
            label="短信"
            align="center"
            key="smsAgain"
@@ -616,6 +835,7 @@
            </template>
          </el-table-column>
          <el-table-column
            v-if="!isTargetHospital"
            label="微信"
            align="center"
            key="weChatAgain"
@@ -682,6 +902,14 @@
      tasktypes: store.getters.tasktypes,
    };
  },
  computed: {
    isTargetHospital() {
      return (
        this.orgname == "第一人民医院湖滨院区" ||
        this.orgname == "第一人民医院吴山院区"
      );
    },
  },
  methods: {
    loadData() {
      this.loading = true;
@@ -746,6 +974,32 @@
        {
          props: {
            content: "(随访成功+首次随访失败)/首次应随访",
            placement: "top",
            effect: "dark",
          },
        },
        [h("span", column.label)]
      );
    },
    Tooltipfswc(h, { column }) {
      return h(
        "el-tooltip",
        {
          props: {
            content: "首次发送患者即填报成功的数量",
            placement: "top",
            effect: "dark",
          },
        },
        [h("span", column.label)]
      );
    },
    Tooltipzzwc(h, { column }) {
      return h(
        "el-tooltip",
        {
          props: {
            content: "首次未完成二次或三次补偿推送本方式的完成次数",
            placement: "top",
            effect: "dark",
          },
@@ -1121,20 +1375,21 @@
          }
        }
        // æ ¹æ® serviceType ç”Ÿæˆéšè®¿ç±»åž‹åç§°
        // æ ¹æ® serviceType ç”Ÿæˆéšè®¿ç±»åž‹åç§°ï¼ˆå…¼å®¹å•选/多选)
        const serviceTypes = Array.isArray(this.queryParams.serviceType)
          ? this.queryParams.serviceType
          : this.queryParams.serviceType
          ? [this.queryParams.serviceType]
          : [];
        let serviceTypeName = "出院随访"; // æ–‡ä»¶åä½¿ç”¨çš„名称
        let sheetTypeName = "再次随访"; // å·¥ä½œè¡¨ä½¿ç”¨çš„名称(简化版)
        console.log(this.queryParams.serviceType);
        if (
          this.queryParams.serviceType &&
          Array.isArray(this.queryParams.serviceType) &&
          this.queryParams.serviceType.length > 0
        ) {
        if (serviceTypes.length > 0) {
          if (this.tasktypes && Array.isArray(this.tasktypes)) {
            // è¿‡æ»¤å‡ºåŒ¹é…çš„随访类型
            const matchedTypes = this.tasktypes.filter((task) =>
              this.queryParams.serviceType.includes(task.value)
              serviceTypes.includes(task.value)
            );
            if (matchedTypes.length === 1) {
@@ -1150,22 +1405,16 @@
              serviceTypeName = typeNames.join("/");
              // å·¥ä½œè¡¨åï¼šä½¿ç”¨ç¬¬ä¸€ä¸ªç±»åž‹æˆ–简化名称
              if (matchedTypes.length <= 2) {
                // å¦‚果只有2个类型,都显示
                sheetTypeName = `${typeNames[0]}等`;
              } else {
                // å¦‚果超过2个类型,只显示第一个
                sheetTypeName = `${typeNames[0]}等`;
              }
            } else if (this.queryParams.serviceType.length > 0) {
              sheetTypeName = `${typeNames[0]}等`;
            } else {
              // å¦‚果没有匹配的,使用原始值
              const typeStr = this.queryParams.serviceType.join("/");
              const typeStr = serviceTypes.join("/");
              serviceTypeName = typeStr;
              sheetTypeName = "再次随访";
            }
          } else if (this.queryParams.serviceType.length > 0) {
          } else {
            // å¦‚果没有 tasktypes,使用原始值
            const typeStr = this.queryParams.serviceType.join("/");
            const typeStr = serviceTypes.join("/");
            serviceTypeName = typeStr;
            sheetTypeName = "再次随访";
          }
src/views/sfstatistics/percentage/index.vue
@@ -58,8 +58,7 @@
        <el-form-item label="服务类型" prop="userName">
          <el-select
            v-model="queryParams.serviceType"
            multiple
            v-model="serviceTypeSingle"
            placeholder="请选择"
          >
            <el-option
@@ -319,6 +318,18 @@
      },
    };
  },
  computed: {
    // æœåŠ¡ç±»åž‹å•é€‰ï¼šUI ç»‘定单值,内部仍以数组形式传给后端
    serviceTypeSingle: {
      get() {
        const st = this.queryParams.serviceType;
        return Array.isArray(st) ? st[0] : st;
      },
      set(val) {
        this.queryParams.serviceType = val == null || val === "" ? [] : [val];
      },
    },
  },
  created() {
    this.getDeptTree();
    this.loadCurrentTabData();
vue.config.js
@@ -36,11 +36,11 @@
      // detail: https://cli.vuejs.org/config/#devserver-proxy
      [process.env.VUE_APP_BASE_API]: {
        // target: `https://www.health-y.cn/lssf`,
        // target: `http://192.168.100.10:8096`,
        target: `http://192.168.100.10:8096`,
        // target: `http://192.168.100.10:8094`,//省立同德
        // target: `http://192.168.100.10:8095`,//新华
        // target: `http://192.168.100.10:8098`,//市一
        target:`http://localhost:8095`,
        // target:`http://localhost:8095`,
        // target:`http://35z1t16164.qicp.vip`,
        // target: `http://192.168.100.172:8095`,
        // target: `http://192.168.100.10:8089`,//南华
Ëæ·ÃÍâÁ´Í¨ÓÃ9.12.zip
Binary files differ
Ëæ·ÃͨÓÃ.zip
Binary files differ
Ëæ·ÃͨÓÃ9.10.zip
Binary files differ
Ëæ·ÃͨÓÃ9.11.zip
Binary files differ
Ëæ·ÃͨÓÃ9.22.zip
Binary files differ