From e9f35f9782f3d99d742c294fe503fc1294f203c4 Mon Sep 17 00:00:00 2001
From: WXL (wul) <wl_5969728@163.com>
Date: 星期一, 14 九月 2026 11:10:31 +0800
Subject: [PATCH] 测试完成

---
 src/views/patient/dedicated/tubeFeed/index.vue                  |  391 ++++++++++++
 随访外链通用9.12.zip                                                  |    0 
 src/views/Satisfaction/sfstatistics/components/ChartsPanel.vue  |  443 +++++++++++--
 src/views/patient/dedicated/heart/index.vue                     |  388 ++++++++++++
 src/views/Satisfaction/sfstatistics/components/DataOverview.vue |  467 ++++++++++++--
 src/views/repositoryai/templateku/configurat/index.vue          |    4 
 随访通用 9.14.zip                                                   |    0 
 src/views/outsideChainnew.vue                                   |   73 ++
 src/views/followvisit/discharge/index.vue                       |   74 ++
 src/views/outsideChainwtnew.vue                                 |   70 ++
 10 files changed, 1,737 insertions(+), 173 deletions(-)

diff --git a/src/views/Satisfaction/sfstatistics/components/ChartsPanel.vue b/src/views/Satisfaction/sfstatistics/components/ChartsPanel.vue
index c8a13c6..e0ce533 100644
--- a/src/views/Satisfaction/sfstatistics/components/ChartsPanel.vue
+++ b/src/views/Satisfaction/sfstatistics/components/ChartsPanel.vue
@@ -1,7 +1,28 @@
 <template>
   <div class="charts-panel" ref="pdfTarget">
-    <!-- PDF瀵煎嚭鎸夐挳 -->
+    <!-- 椤堕儴绛涢�夛細鏃堕棿鍖洪棿 + 鍖洪棿绫诲瀷 + PDF瀵煎嚭 -->
     <div class="export-bar">
+      <div class="filter-bar">
+        <span class="filter-label">鏃堕棿鑼冨洿锛�</span>
+        <el-date-picker
+          v-model="queryParams.dateRange"
+          type="daterange"
+          range-separator="鑷�"
+          start-placeholder="寮�濮嬫棩鏈�"
+          end-placeholder="缁撴潫鏃ユ湡"
+          value-format="yyyy-MM-dd"
+          size="small"
+          class="filter-date"
+        />
+        <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="month">鎸夋湀</el-radio-button>
+          <el-radio-button label="year">鎸夊勾</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>
+      </div>
       <el-button type="danger" size="small" icon="el-icon-download" @click="exportPDF">瀵煎嚭PDF</el-button>
     </div>
 
@@ -11,7 +32,7 @@
         <el-card shadow="never" class="chart-card">
           <div slot="header" class="card-header">
             <span>鏃堕棿缁村害 路 闂ㄨ瘖鎮h�呮弧鎰忓害瓒嬪娍</span>
-            <el-tag type="success" size="small">鐜瘮 +0.97</el-tag>
+            <el-tag type="success" size="small">鐜瘮 {{ trendMoM }}</el-tag>
           </div>
           <div ref="chart1" class="chart-box"></div>
         </el-card>
@@ -21,7 +42,7 @@
       <el-col :span="12">
         <el-card shadow="never" class="chart-card">
           <div slot="header" class="card-header">
-            <span>鍒嗙被缁村害 路 鍚勬寚鏍囧勾搴﹀姣�</span>
+            <span>鍒嗙被缁村害 路 鍚勬寚鏍噞{ intervalLabel }}瀵规瘮</span>
           </div>
           <div ref="chart2" class="chart-box"></div>
         </el-card>
@@ -29,18 +50,31 @@
     </el-row>
 
     <el-row :gutter="20" style="margin-top: 20px">
-      <!-- 3. 鍗曢閫夐」 - 鏁版嵁琛ㄦ牸 -->
+      <!-- 3. 鍗曢閫夐」 - 鏁版嵁琛ㄦ牸锛堝彲閫夋嫨棰樼洰銆佺瀹�/鐥呭尯锛� -->
       <el-col :span="12">
         <el-card shadow="never" class="chart-card">
           <div slot="header" class="card-header">
-            <span>鍗曢 路 绗�22棰橈細鎶辨�ㄥ拰涓嶆弧鑳藉惁鍙婃椂寰楀埌鍥炲簲锛�</span>
+            <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>
+              <el-select v-model="singleDeptWards" multiple collapse-tags size="small" class="sel" clearable filterable placeholder="鍏ㄩ儴绉戝/鐥呭尯" @change="refreshAll">
+                <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-select>
+            </div>
           </div>
           <el-table :data="singleQuestionData" border size="small">
             <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>26,233</b>銆�缁煎悎婊℃剰搴︼細<b>83.20%</b></div>
+          <div class="table-footer">鏈鏈夋晥濉啓浜烘锛�<b>{{ singleQuestionMeta.total }}</b>銆�缁煎悎婊℃剰搴︼細<b>{{ singleQuestionMeta.score }}</b></div>
         </el-card>
       </el-col>
 
@@ -48,7 +82,7 @@
       <el-col :span="12">
         <el-card shadow="never" class="chart-card">
           <div slot="header" class="card-header">
-            <span>闄㈠尯缁村害 路 鍚勯櫌鍖哄勾搴﹀姣�</span>
+            <span>闄㈠尯缁村害 路 鍚勯櫌鍖簕{ intervalLabel }}瀵规瘮</span>
           </div>
           <div ref="chart4" class="chart-box"></div>
         </el-card>
@@ -56,29 +90,47 @@
     </el-row>
 
     <el-row :gutter="20" style="margin-top: 20px">
-      <!-- 5. 绉戝缁村害 - 鎺掑悕琛ㄦ牸 -->
+      <!-- 5. 绉戝/鐥呭尯缁村害 - 鎺掑悕琛ㄦ牸 -->
       <el-col :span="12">
         <el-card shadow="never" class="chart-card">
           <div slot="header" class="card-header">
-            <span>绉戝缁村害 路 闂ㄨ瘖鎮h�呮弧鎰忓害寰楀垎鎺掑悕</span>
+            <span>{{ rankDimLabel }}缁村害 路 闂ㄨ瘖鎮h�呮弧鎰忓害寰楀垎鎺掑悕</span>
+            <div class="header-right">
+              <el-radio-group v-model="rankDimension" size="mini" @change="onRankDimensionChange">
+                <el-radio-button label="dept">鎸夌瀹�</el-radio-button>
+                <el-radio-button label="ward">鎸夌梾鍖�</el-radio-button>
+              </el-radio-group>
+              <el-select v-model="rankDeptWards" multiple collapse-tags size="small" class="sel" clearable filterable :placeholder="'鍏ㄩ儴' + rankDimLabel" @change="refreshAll">
+                <el-option v-for="name in rankOptions" :key="name" :label="name" :value="name" />
+              </el-select>
+            </div>
           </div>
-          <el-table :data="deptRankData" border size="small">
+          <el-table :data="rankRows" border size="small" max-height="400">
             <el-table-column type="index" label="鎺掑悕" width="60" align="center" />
-            <el-table-column prop="dept" label="绉戝" />
+            <el-table-column prop="name" :label="rankDimLabel" />
             <el-table-column prop="score" label="缁煎悎寰楀垎" width="120" align="center" />
           </el-table>
         </el-card>
       </el-col>
 
-      <!-- 6. 鍗曢绉戝 - 浜ゅ弶鍒嗘瀽琛ㄦ牸 -->
+      <!-- 6. 鍗曢脳绉戝/鐥呭尯 - 浜ゅ弶鎺掑悕琛ㄦ牸 -->
       <el-col :span="12">
         <el-card shadow="never" class="chart-card">
           <div slot="header" class="card-header">
-            <span>鍗曢脳绉戝 路 绗�22棰樺悇绉戝寰楀垎</span>
+            <span>鍗曢脳{{ crossDimLabel }} 路 寰楀垎鎺掑悕</span>
+            <div class="header-right">
+              <el-radio-group v-model="crossDimension" size="mini" @change="onCrossDimensionChange">
+                <el-radio-button label="dept">鎸夌瀹�</el-radio-button>
+                <el-radio-button label="ward">鎸夌梾鍖�</el-radio-button>
+              </el-radio-group>
+              <el-select v-model="crossDeptWards" multiple collapse-tags size="small" class="sel" clearable filterable :placeholder="'鍏ㄩ儴' + crossDimLabel" @change="refreshAll">
+                <el-option v-for="name in crossOptions" :key="name" :label="name" :value="name" />
+              </el-select>
+            </div>
           </div>
-          <el-table :data="crossDeptData" border size="small" max-height="400">
+          <el-table :data="crossRows" border size="small" max-height="400">
             <el-table-column type="index" label="鎺掑悕" width="60" align="center" />
-            <el-table-column prop="dept" label="绉戝" />
+            <el-table-column prop="name" :label="crossDimLabel" />
             <el-table-column prop="score" label="缁煎悎寰楀垎" width="120" align="center" />
           </el-table>
         </el-card>
@@ -90,113 +142,336 @@
 <script>
 import * as echarts from 'echarts';
 
+// 鍗曢閫夐」
+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",
+  name: 'ChartsPanel',
   data() {
     return {
-      singleQuestionData: [
-        { option: "闈炲父婊℃剰", count: 16513, rate: "62.95%" },
-        { option: "婊℃剰", count: 7088, rate: "27.02%" },
-        { option: "涓�鑸�", count: 1760, rate: "6.71%" },
-        { option: "涓嶆弧鎰�", count: 872, rate: "3.32%" },
-      ],
-      deptRankData: [
-        { dept: "淇濆仴绉�", score: "81.54" }, { dept: "浠嬪叆娌荤枟绉�", score: "80.72" },
-        { dept: "涓撶鎶ゅ+闂ㄨ瘖", score: "79.27" }, { dept: "搴峰鍖诲绉�", score: "78.65" },
-        { dept: "鐨偆绉�", score: "78.12" }, { dept: "鍐呭垎娉岀", score: "77.89" },
-        { dept: "娑堝寲鍐呯", score: "77.34" }, { dept: "鑲惧唴绉�", score: "76.50" },
-        { dept: "鍛煎惛鍐呯", score: "75.88" }, { dept: "蹇冭绠″唴绉�", score: "75.21" },
-      ],
-      crossDeptData: [
-        { dept: "淇濆仴绉�", score: "81.24" }, { dept: "浠嬪叆娌荤枟绉�", score: "81.22" },
-        { dept: "涓撶鎶ゅ+闂ㄨ瘖", score: "80.15" }, { dept: "搴峰鍖诲绉�", score: "79.88" },
-        { dept: "鐨偆绉�", score: "79.01" }, { dept: "鍐呭垎娉岀", score: "78.45" },
-        { dept: "娑堝寲鍐呯", score: "77.92" }, { dept: "鑲惧唴绉�", score: "77.13" },
-        { dept: "鍛煎惛鍐呯", score: "76.80" }, { dept: "蹇冭绠″唴绉�", score: "76.05" },
-        { dept: "绁炵粡鍐呯", score: "75.62" }, { dept: "琛�娑茬", score: "75.01" },
-        { dept: "椋庢箍鍏嶇柅绉�", score: "74.38" }, { dept: "鑲跨槫鍐呯", score: "73.85" },
-        { dept: "鎰熸煋绉�", score: "73.12" }, { dept: "鑰佸勾绉�", score: "72.56" },
-        { dept: "鏅绉�", score: "71.90" }, { dept: "楠ㄧ", score: "71.23" },
-        { dept: "娉屽翱澶栫", score: "70.55" }, { dept: "濡囦骇绉�", score: "69.88" },
-      ],
+      queryParams: {
+        dateRange: [],
+        interval: 'month',
+      },
+      deptOptions: DEPTS,
+      wardOptions: WARDS,
+      questionOptions: QUESTIONS,
+
+      // 鍗曢绛涢��
+      singleQuestionId: 22,
+      singleDeptWards: [],
+
+      // 鎺掑悕缁村害绛涢��
+      rankDimension: 'dept',
+      rankDeptWards: [],
+      crossDimension: 'dept',
+      crossDeptWards: [],
+
+      // 鍥捐〃鏁版嵁
+      trend: { buckets: [], data: [], seriesName: '闂ㄨ瘖鎮h�呮弧鎰忓害' },
+      categoryCompare: { categories: [], seriesNames: [], series1: [], series2: [] },
+      hospitalCompare: { hospitals: [], seriesNames: [], series1: [], series2: [] },
+
+      // 琛ㄦ牸鏁版嵁
+      singleQuestionData: [],
+      singleQuestionMeta: { total: '0', score: '0.00%' },
+      deptRankData: [],
+      wardRankData: [],
+      crossDeptData: [],
+      crossWardData: [],
     };
   },
-  mounted() { this.$nextTick(() => { this.initCharts(); }); },
+  computed: {
+    intervalLabel() {
+      return { day: '鎸夊ぉ', month: '鎸夋湀', year: '鎸夊勾' }[this.queryParams.interval] || '鎸夋湀';
+    },
+    trendMoM() {
+      const d = this.trend.data;
+      if (d.length < 2) return '0.00';
+      const diff = d[d.length - 1] - d[d.length - 2];
+      return (diff >= 0 ? '+' : '') + diff.toFixed(2);
+    },
+    currentSingleQuestion() {
+      return this.questionOptions.find((q) => q.id === this.singleQuestionId) || {};
+    },
+    rankDimLabel() {
+      return this.rankDimension === 'dept' ? '绉戝' : '鐥呭尯';
+    },
+    crossDimLabel() {
+      return this.crossDimension === 'dept' ? '绉戝' : '鐥呭尯';
+    },
+    rankOptions() {
+      return this.rankDimension === 'dept' ? this.deptOptions : this.wardOptions;
+    },
+    crossOptions() {
+      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));
+    },
+    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));
+    },
+  },
+  mounted() {
+    this.$nextTick(() => {
+      if (!this.queryParams.dateRange || !this.queryParams.dateRange.length) {
+        this.queryParams.dateRange = this.defaultRange();
+      }
+      this.initCharts();
+      this.refreshAll();
+    });
+  },
   methods: {
     initCharts() {
-      const quarters = ['绗竴瀛e害', '绗簩瀛e害', '绗笁瀛e害', '绗洓瀛e害'];
+      echarts.init(this.$refs.chart1);
+      echarts.init(this.$refs.chart2);
+      echarts.init(this.$refs.chart4);
+      window.addEventListener('resize', this.handleResize);
+    },
+    handleResize() {
+      ['chart1', 'chart2', 'chart4'].forEach((ref) => {
+        const c = echarts.getInstanceByDom(this.$refs[ref]);
+        if (c) c.resize();
+      });
+    },
+    handleReset() {
+      this.queryParams.dateRange = this.defaultRange();
+      this.queryParams.interval = 'month';
+      this.refreshAll();
+    },
+    onRankDimensionChange() {
+      this.rankDeptWards = [];
+      this.refreshAll();
+    },
+    onCrossDimensionChange() {
+      this.crossDeptWards = [];
+      this.refreshAll();
+    },
+    defaultRange() {
+      const end = new Date();
+      const start = new Date();
+      start.setDate(start.getDate() - 29);
+      const f = (d) => {
+        const y = d.getFullYear();
+        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)];
+    },
+    // 鍖洪棿绫诲瀷 => 鏃堕棿妗� + 鏌辩姸鍥惧姣斿簭鍒楀悕
+    intervalConfig() {
+      const buckets = this.genBuckets(this.queryParams.interval);
+      if (this.queryParams.interval === 'day') return { buckets, seriesNames: ['鏄ㄦ棩', '浠婃棩'] };
+      if (this.queryParams.interval === 'year') return { buckets, seriesNames: ['鍘诲勾', '浠婂勾'] };
+      return { buckets, seriesNames: ['涓婃湀', '鏈湀'] };
+    },
+    genBuckets(type) {
+      const end = this.queryParams.dateRange && this.queryParams.dateRange[1]
+        ? new Date(this.queryParams.dateRange[1])
+        : new Date();
+      const buckets = [];
+      if (type === 'day') {
+        for (let i = 14; i >= 0; i--) {
+          const d = new Date(end);
+          d.setDate(end.getDate() - i);
+          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 {
+        for (let i = 5; i >= 0; i--) {
+          const d = new Date(end.getFullYear(), end.getMonth() - i, 1);
+          buckets.push(`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`);
+        }
+      }
+      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 }))));
+
+      this.renderCharts();
+    },
+    genChartData(rnd) {
+      const jitter = (lo, hi) => lo + rnd() * (hi - lo);
+      const cfg = this.intervalConfig();
+      const buckets = cfg.buckets;
+
+      this.trend = {
+        buckets,
+        seriesName: '闂ㄨ瘖鎮h�呮弧鎰忓害',
+        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 = ['闄㈠尯涓�', '闄㈠尯浜�', '闄㈠尯涓�'];
-      const baseGrid = { top: 20, right: 30, bottom: 30, left: 50 };
+      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),
+      };
+    },
+    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) + '%',
+      }));
+      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);
+      const c2 = echarts.getInstanceByDom(this.$refs.chart2);
+      const c4 = echarts.getInstanceByDom(this.$refs.chart4);
+      if (!c1 || !c2 || !c4) return;
+
+      const lineGrid = { top: 32, right: 30, bottom: 32, left: 85 };
+      const barGrid = { top: 48, right: 30, bottom: 60, left: 85 };
       const baseTooltip = { backgroundColor: 'rgba(255,255,255,0.95)', borderColor: '#e8e8e8', textStyle: { color: '#333', fontSize: 13 }, extraCssText: 'box-shadow: 0 2px 12px rgba(0,0,0,0.1); border-radius: 6px; padding: 10px 14px;' };
       const baseXAxis = { axisLine: { lineStyle: { color: '#e0e0e0' } }, axisTick: { show: false }, axisLabel: { color: '#666', fontSize: 12 } };
       const baseYAxis = { splitLine: { lineStyle: { color: '#f0f0f0', type: 'dashed' } }, axisLine: { show: false }, axisTick: { show: false }, axisLabel: { color: '#999', fontSize: 11 } };
+      const areaGrad = new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+        { offset: 0, color: 'rgba(91,143,249,0.25)' },
+        { offset: 1, color: 'rgba(91,143,249,0.02)' },
+      ]);
 
       // 1. 鎶樼嚎鍥� - 鏃堕棿缁村害
-      const c1 = echarts.init(this.$refs.chart1);
       c1.setOption({
         color: ['#5B8FF9'],
-        grid: { ...baseGrid, top: 25 },
+        grid: lineGrid,
         tooltip: { ...baseTooltip, trigger: 'axis' },
-        xAxis: [{ ...baseXAxis, data: quarters, boundaryGap: false }],
-        yAxis: [{ ...baseYAxis, min: 86, max: 91, name: '婊℃剰搴﹀緱鍒�', nameTextStyle: { color: '#999', fontSize: 11 } }],
+        xAxis: [{ ...baseXAxis, data: this.trend.buckets, boundaryGap: false }],
+        yAxis: [{ ...baseYAxis, name: '婊℃剰搴﹀緱鍒�', nameTextStyle: { color: '#999', fontSize: 11 } }],
         series: [{
-          name: '闂ㄨ瘖鎮h�呮弧鎰忓害', type: 'line', smooth: true, symbol: 'circle', symbolSize: 8,
-          data: [88.20, 87.32, 88.50, 89.17],
+          name: this.trend.seriesName, type: 'line', smooth: true, symbol: 'circle', symbolSize: 8,
+          data: this.trend.data,
           lineStyle: { width: 3, shadowBlur: 8, shadowColor: 'rgba(91,143,249,0.3)' },
           itemStyle: { color: '#5B8FF9' },
-          areaStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-            { offset: 0, color: 'rgba(91,143,249,0.25)' }, { offset: 1, color: 'rgba(91,143,249,0.02)' }
-          ])},
+          areaStyle: { color: areaGrad },
         }],
       });
 
       // 2. 鍒嗙粍鏌辩姸鍥� - 鍒嗙被缁村害
-      const c2 = echarts.init(this.$refs.chart2);
       c2.setOption({
         color: ['#6DC8EC', '#5B8FF9'],
-        grid: baseGrid,
+        grid: barGrid,
         tooltip: { ...baseTooltip, trigger: 'axis', axisPointer: { type: 'shadow', shadowStyle: { color: 'rgba(0,0,0,0.03)' } } },
-        legend: { data: ['2023骞�', '2024骞�'], bottom: 0, textStyle: { color: '#666', fontSize: 12 } },
-        xAxis: [{ ...baseXAxis, data: categories, axisLabel: { ...baseXAxis.axisLabel, interval: 0 } }],
+        legend: { data: this.categoryCompare.seriesNames, bottom: 8, textStyle: { color: '#666', fontSize: 12 } },
+        xAxis: [{ ...baseXAxis, data: this.categoryCompare.categories, axisLabel: { ...baseXAxis.axisLabel, interval: 0 } }],
         yAxis: [{ ...baseYAxis, name: '寰楀垎', nameTextStyle: { color: '#999', fontSize: 11 } }],
         series: [
-          { name: '2023骞�', type: 'bar', barWidth: 22, barGap: '30%',
+          { name: this.categoryCompare.seriesNames[0], type: 'bar', barWidth: 22, 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: [86.34, 85.12, 83.45, 86.22, 88.55, 87.33] },
-          { name: '2024骞�', type: 'bar', barWidth: 22,
+            data: this.categoryCompare.series1 },
+          { name: this.categoryCompare.seriesNames[1], type: 'bar', barWidth: 22,
             itemStyle: { borderRadius: [6, 6, 0, 0], color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: '#5B8FF9' }, { offset: 1, color: '#3D6FDB' }]) },
-            data: [88.06, 84.57, 82.88, 87.77, 88.12, 87.00] },
+            data: this.categoryCompare.series2 },
         ],
       });
 
       // 4. 闄㈠尯缁村害 - 鍒嗙粍鏌辩姸鍥�
-      const c4 = echarts.init(this.$refs.chart4);
       c4.setOption({
         color: ['#6DC8EC', '#5B8FF9'],
-        grid: baseGrid,
+        grid: barGrid,
         tooltip: { ...baseTooltip, trigger: 'axis', axisPointer: { type: 'shadow' } },
-        legend: { data: ['2023骞�', '2024骞�'], bottom: 0, textStyle: { color: '#666', fontSize: 12 } },
-        xAxis: [{ ...baseXAxis, data: hospitals }],
+        legend: { data: this.hospitalCompare.seriesNames, bottom: 8, textStyle: { color: '#666', fontSize: 12 } },
+        xAxis: [{ ...baseXAxis, data: this.hospitalCompare.hospitals }],
         yAxis: [{ ...baseYAxis, name: '婊℃剰搴﹀緱鍒�', nameTextStyle: { color: '#999', fontSize: 11 } }],
         series: [
-          { name: '2023骞�', type: 'bar', barWidth: 24, barGap: '30%',
+          { name: this.hospitalCompare.seriesNames[0], type: 'bar', barWidth: 24, 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: [86.20, 87.69, 85.55],
-            label: { show: true, position: 'top', color: '#666', fontSize: 12, fontWeight: 500, formatter: p => p.value.toFixed(2) } },
-          { name: '2024骞�', type: 'bar', barWidth: 24,
+            data: this.hospitalCompare.series1,
+            label: { show: true, position: 'top', color: '#666', fontSize: 12, fontWeight: 500, formatter: (p) => p.value.toFixed(2) } },
+          { name: this.hospitalCompare.seriesNames[1], type: 'bar', barWidth: 24,
             itemStyle: { borderRadius: [6, 6, 0, 0], color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: '#5B8FF9' }, { offset: 1, color: '#3D6FDB' }]) },
-            data: [88.15, 88.14, 85.88],
-            label: { show: true, position: 'top', color: '#666', fontSize: 12, fontWeight: 500, formatter: p => p.value.toFixed(2) } },
+            data: this.hospitalCompare.series2,
+            label: { show: true, position: 'top', color: '#666', fontSize: 12, fontWeight: 500, formatter: (p) => p.value.toFixed(2) } },
         ],
       });
-
-      // 鍝嶅簲寮�
-      window.addEventListener('resize', () => { c1.resize(); c2.resize(); c4.resize(); });
     },
 
     async exportPDF() {
-      // 鍏堝皢 ECharts 瀹炰緥杞负鍥剧墖 dataURL
       const chartRefs = [
         { ref: this.$refs.chart1, id: 'chart1' },
         { ref: this.$refs.chart2, id: 'chart2' },
@@ -212,19 +487,7 @@
         }
       }
 
-      // 鍏嬮殕鍐呭骞舵浛鎹㈠浘琛ㄤ负鍥剧墖
       const printContent = this.$refs.pdfTarget.cloneNode(true);
-      for (const { ref, id } of chartRefs) {
-        if (chartImages[id] && ref) {
-          const boxes = printContent.querySelectorAll('.chart-box');
-          for (const box of boxes) {
-            if (box.innerHTML.includes(id) || boxes.length === chartRefs.length) {
-              box.innerHTML = `<img src="${chartImages[id]}" style="width:100%;height:auto;" />`;
-            }
-          }
-        }
-      }
-      // 鏇村彲闈犵殑鏂瑰紡锛氭寜浣嶇疆鍖归厤
       const chartBoxes = printContent.querySelectorAll('.chart-box');
       const imgKeys = Object.keys(chartImages);
       chartBoxes.forEach((box, i) => {
@@ -246,7 +509,7 @@
           th, td { border: 1px solid #d9d9d9; padding: 8px 12px; text-align: center; font-size: 12px; }
           th { background: #f0f2f5; font-weight: 600; }
           .table-footer { text-align: right; padding: 8px 0; font-size: 13px; color: #606266; }
-          .export-bar, .el-tag { display: none; }
+          .export-bar, .filter-bar, .el-tag, .el-select, .el-radio-group { display: none; }
           @media print { @page { size: A4; margin: 10mm; } }
         </style></head><body>
         ${printContent.innerHTML}
@@ -260,9 +523,11 @@
 </script>
 
 <style lang="scss" scoped>
-.export-bar { text-align: right; margin-bottom: 16px; }
+.export-bar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
+.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; }
 .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; }
 </style>
diff --git a/src/views/Satisfaction/sfstatistics/components/DataOverview.vue b/src/views/Satisfaction/sfstatistics/components/DataOverview.vue
index 18b5142..1987125 100644
--- a/src/views/Satisfaction/sfstatistics/components/DataOverview.vue
+++ b/src/views/Satisfaction/sfstatistics/components/DataOverview.vue
@@ -1,6 +1,54 @@
 <template>
   <div class="data-overview">
-    <!-- 琛ㄦ牸涓�锛氬悇鏈嶅姟椤圭洰婊℃剰搴︾粺璁� -->
+    <!-- 椤堕儴绛涢�夛細鏃堕棿鑼冨洿 + 绉戝/鐥呭尯锛屾帶鍒舵湰椤垫墍鏈夋暟鎹� -->
+    <el-card shadow="never" class="overview-card filter-card">
+      <el-form inline class="filter-form">
+        <el-form-item label="鏃堕棿鑼冨洿">
+          <el-date-picker
+            v-model="queryParams.dateRange"
+            type="daterange"
+            range-separator="鑷�"
+            start-placeholder="寮�濮嬫棩鏈�"
+            end-placeholder="缁撴潫鏃ユ湡"
+            value-format="yyyy-MM-dd"
+            size="small"
+          />
+        </el-form-item>
+        <el-form-item label="绉戝/鐥呭尯">
+          <el-select
+            v-model="queryParams.deptWard"
+            clearable
+            filterable
+            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-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-form-item>
+      </el-form>
+    </el-card>
+
+    <!-- 琛ㄦ牸涓�锛氬悇鏈嶅姟椤圭洰婊℃剰搴︾粺璁★紙鍥哄畾浜旈」閫夐」鐨勯鐩級 -->
     <el-card shadow="never" class="overview-card">
       <div slot="header" class="card-header">
         <span class="card-title">鍚勬湇鍔¢」鐩弧鎰忓害缁熻</span>
@@ -32,31 +80,48 @@
       </el-table>
     </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('table2')">瀵煎嚭Excel</el-button>
       </div>
-      <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">
+        <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>
       </div>
     </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('table3')">瀵煎嚭Excel</el-button>
+        <span class="card-title">鍚勭瀹�/鐥呭尯婊℃剰搴﹁瘎鍒嗗姣�</span>
+        <div class="header-right">
+          <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-option label="娌跨敤椤堕儴鏉′欢" value="inherit" />
+            <template v-if="statDimension === 'dept'">
+              <el-option v-for="d in deptOptions" :key="'dept:' + d" :label="d" :value="'dept:' + d" />
+            </template>
+            <template v-else>
+              <el-option v-for="w in wardOptions" :key="'ward:' + w" :label="w" :value="'ward:' + w" />
+            </template>
+          </el-select>
+          <el-button type="warning" size="small" icon="el-icon-download" @click="exportTable('table3')">瀵煎嚭Excel</el-button>
+        </div>
       </div>
-      <el-table id="table3" :data="deptComparison" border size="small" :header-cell-style="{ background: '#f5f7fa', color: '#303133' }">
-        <el-table-column prop="dept" label="绉戝"fixed="left" align="center" />
+      <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" />
@@ -71,7 +136,7 @@
           <el-table-column prop="q2Bad" label="涓嶆弧鎰�" width="90" />
           <el-table-column prop="q2Score" label="寰楀垎" width="100" />
         </el-table-column>
-        <el-table-column prop="totalScore" label="缁煎悎寰楀垎"  align="center" />
+        <el-table-column prop="totalScore" label="缁煎悎寰楀垎" align="center" />
       </el-table>
     </el-card>
   </div>
@@ -80,68 +145,312 @@
 <script>
 import XLSX from 'xlsx';
 
+// 婊℃剰搴﹂鐨勫浐瀹氶�夐」锛堥『搴忓嵆琛ㄦ牸鍒楅『搴忥級
+const FIXED_OPTIONS = ['闈炲父婊℃剰', '婊℃剰', '涓�鑸�', '涓嶆弧鎰�', '涓嶉�傜敤'];
+const DEPTS = ['鍐呭垎娉岀', '鐨偆绉�', '鑲跨槫鍐呯', '娑堝寲鍐呯', '鑲惧唴绉�'];
+const WARDS = ['涓�鐥呭尯', '浜岀梾鍖�', '涓夌梾鍖�', '鍥涚梾鍖�', '浜旂梾鍖�'];
+
+// 婊℃剰搴﹂锛堝浐瀹氫簲椤归�夐」锛宑ounts 椤哄簭瀵瑰簲 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]],
+  ['鍖荤敓鏄惁鐢ㄦ偍鍚緱鎳傜殑鏂瑰紡瑙i噴闂锛�', [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]],
+  ['鎶ゅ+鏄惁鐢ㄦ偍鍚緱鎳傜殑鏂瑰紡瑙i噴闂锛�', [3346, 1250, 122, 27, 0]],
+  ['鎶ゅ+鏄惁浠旂粏鍊惧惉鎮ㄨ璇濓紵', [3378, 1205, 134, 28, 0]],
+];
+
+// 鍏朵綑棰樼洰锛堝崟閫�/澶氶�夛紝闈炲浐瀹氭弧鎰忓害閫夐」锛�
+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]],
+  },
+];
+
+// 缁熶竴鎺ュ彛杩斿洖鐨勯鐩紙妯℃嫙锛夛細鍥哄畾浜旈」閫夐」 => 琛ㄤ竴锛屽叾浣� => 琛ㄤ簩
+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 })),
+  })),
+];
+
+// 纭畾鎬т吉闅忔満锛堜究浜庣瓫閫夋潯浠跺浐瀹氭椂缁撴灉绋冲畾锛�
+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: "DataOverview",
+  name: 'DataOverview',
   data() {
     return {
-      serviceSatisfaction: [
-        { category: "璇婄枟杩囩▼涓紝鍖诲姟浜哄憳鏄惁娉ㄦ剰淇濇姢鎮ㄧ殑闅愮锛�", veryGoodCount: 3219, veryGoodRate: "67.84%", goodCount: 1351, goodRate: "28.47%", normalCount: 142, normalRate: "2.99%", badCount: 33, badRate: "0.70%", naCount: 0, naRate: "0.00%", score: "87.82" },
-        { category: "鎮ㄥ鍖婚櫌鏈嶅姟璁炬柦鐨勬�讳綋鍗拌薄濡備綍锛�", veryGoodCount: 3110, veryGoodRate: "65.54%", goodCount: 1369, goodRate: "28.85%", normalCount: 217, normalRate: "4.57%", badCount: 49, badRate: "1.03%", naCount: 0, naRate: "0.00%", score: "86.30" },
-        { category: "鍖婚櫌鐨勫帟鎵�鏄惁娓呮磥鏃犲紓鍛筹紵", veryGoodCount: 2973, veryGoodRate: "62.66%", goodCount: 1504, goodRate: "31.70%", normalCount: 235, normalRate: "4.95%", badCount: 33, badRate: "0.70%", naCount: 0, naRate: "0.00%", score: "85.44" },
-        { category: "鍖婚櫌鍐呯殑璺爣鍜屾寚绀烘槸鍚︽槑纭紵", veryGoodCount: 3234, veryGoodRate: "68.16%", goodCount: 1289, goodRate: "27.17%", normalCount: 196, normalRate: "4.13%", badCount: 26, badRate: "0.55%", naCount: 0, naRate: "0.00%", score: "87.64" },
-        { category: "鍖婚櫌鐨勭┖闂村竷灞�鏄惁渚垮埄锛�", veryGoodCount: 3037, veryGoodRate: "64.00%", goodCount: 1460, goodRate: "30.77%", normalCount: 209, normalRate: "4.40%", badCount: 39, badRate: "0.82%", naCount: 0, naRate: "0.00%", score: "85.99" },
-        { category: "鐪嬬梾鏈熼棿锛屾偍鐨勬姳鎬ㄥ拰涓嶆弧鑳藉惁鍙婃椂寰楀埌鍥炲簲锛�", veryGoodCount: 3019, veryGoodRate: "63.62%", goodCount: 1317, goodRate: "27.76%", normalCount: 294, normalRate: "6.20%", badCount: 115, badRate: "2.42%", naCount: 0, naRate: "0.00%", score: "84.19" },
-        { category: "鍖荤敓鏄惁鐢ㄦ偍鍚緱鎳傜殑鏂瑰紡瑙i噴闂锛�", veryGoodCount: 3402, veryGoodRate: "71.70%", goodCount: 967, goodRate: "20.38%", normalCount: 254, normalRate: "5.35%", badCount: 122, badRate: "2.57%", naCount: 0, naRate: "0.00%", score: "87.07" },
-        { category: "鍖荤敓瀵规偍鏄惁灏婇噸锛�", veryGoodCount: 3495, veryGoodRate: "73.66%", goodCount: 897, goodRate: "18.90%", normalCount: 249, normalRate: "5.25%", badCount: 104, badRate: "2.19%", naCount: 0, naRate: "0.00%", score: "88.01" },
-        { category: "鍖荤敓鏄惁浠旂粏鍊惧惉鎮ㄨ璇濓紵", veryGoodCount: 3422, veryGoodRate: "72.12%", goodCount: 937, goodRate: "19.75%", normalCount: 251, normalRate: "5.29%", badCount: 135, badRate: "2.85%", naCount: 0, naRate: "0.00%", score: "87.05" },
-        { category: "瀵规寕鍙锋湇鍔′汉鍛樻湇鍔℃�佸害鏄惁婊℃剰", veryGoodCount: 3458, veryGoodRate: "72.88%", goodCount: 1205, goodRate: "25.40%", normalCount: 52, normalRate: "1.10%", badCount: 30, badRate: "0.63%", naCount: 0, naRate: "0.00%", score: "90.17" },
-        { category: "鎮ㄥ棰勭害鎸傚彿鏄惁婊℃剰锛�", veryGoodCount: 3527, veryGoodRate: "74.33%", goodCount: 1088, goodRate: "22.93%", normalCount: 72, normalRate: "1.52%", badCount: 58, badRate: "1.22%", naCount: 0, naRate: "0.00%", score: "90.12" },
-        { category: "鎶ゅ+瀵规偍鏄惁灏婇噸锛�", veryGoodCount: 3420, veryGoodRate: "72.08%", goodCount: 1184, goodRate: "24.95%", normalCount: 113, normalRate: "2.38%", badCount: 28, badRate: "0.59%", naCount: 0, naRate: "0.00%", score: "89.50" },
-        { category: "鎶ゅ+鏄惁鐢ㄦ偍鍚緱鎳傜殑鏂瑰紡瑙i噴闂锛�", veryGoodCount: 3346, veryGoodRate: "70.52%", goodCount: 1250, goodRate: "26.34%", normalCount: 122, normalRate: "2.57%", badCount: 27, badRate: "0.57%", naCount: 0, naRate: "0.00%", score: "88.94" },
-        { category: "鎶ゅ+鏄惁浠旂粏鍊惧惉鎮ㄨ璇濓紵", veryGoodCount: 3378, veryGoodRate: "71.19%", goodCount: 1205, goodRate: "25.40%", normalCount: 134, normalRate: "2.82%", badCount: 28, badRate: "0.59%", naCount: 0, naRate: "0.00%", score: "89.06" },
-        { category: "鎬昏", veryGoodCount: 54863, veryGoodRate: "69.02%", goodCount: 20800, goodRate: "26.17%", normalCount: 2920, normalRate: "3.67%", badCount: 908, badRate: "1.14%", naCount: 0, naRate: "0.00%", score: "87.66" },
-      ],
-      questionStats: [
-        {
-          title: "绗�1棰橈細鎮ㄥ鎴戦櫌宸ヤ綔浜哄憳鐨勬湇鍔¤川閲忔弧鎰忎笉婊℃剰锛熴�愬垎鍊煎崟閫夐銆�- 缁煎悎寰楀垎",
-          options: [
-            { option: "闈炲父婊℃剰", count: 1084, rate: "75.66%" },
-            { option: "婊℃剰", count: 1959, rate: "20.92%" },
-            { option: "涓�鑸�", count: 167, rate: "1.78%" },
-            { option: "涓嶆弧鎰�", count: 153, rate: "1.63%" },
-          ],
-          total: 3363
-        },
-        {
-          title: "绗�2棰橈細鎮ㄥ鍝儴鍒嗘湇鍔′笉婊℃剰锛熴�愬閫夐銆�",
-          options: [
-            { option: "鍖荤敓璇婃柇鍜岃瘖鐤�", count: 131, rate: "32.11%" },
-            { option: "鎶ょ悊鏈嶅姟", count: 47, rate: "11.52%" },
-            { option: "鍏朵粬鍖诲姟浜哄憳鎶�鏈按骞�", count: 30, rate: "7.33%" },
-            { option: "宸ヤ汉", count: 72, rate: "17.65%" },
-          ],
-          total: 408
-        },
-        {
-          title: "绗�3棰橈細鍖荤敓璇婃柇鍜屾不鐤椾腑鍝簺鏈嶅姟璐ㄩ噺涓嶆弧鎰忥紵銆愬閫夐銆�",
-          options: [
-            { option: "鍖荤敓璇婃柇閿欒", count: 23, rate: "15.44%" },
-            { option: "瀵规不鐤楁晥鏋滀笉婊℃剰", count: 72, rate: "48.32%" },
-            { option: "鍏朵粬", count: 54, rate: "36.24%" },
-          ],
-          total: 131
-        },
-      ],
-      deptComparison: [
-        { dept: "鍐呭垎娉岀", q1VeryGood: 103, q1Good: 34, q1Normal: 0, q1Bad: 0, q1Score: "377.00", q2VeryGood: 101, q2Good: 35, q2Normal: 1, q2Bad: 0, q2Score: "374.00", totalScore: "75.25" },
-        { dept: "鐨偆绉�", q1VeryGood: 112, q1Good: 25, q1Normal: 2, q1Bad: 3, q1Score: "388.00", q2VeryGood: 111, q2Good: 27, q2Normal: 3, q2Bad: 1, q2Score: "390.00", totalScore: "76.12" },
-        { dept: "鑲跨槫鍐呯", q1VeryGood: 24, q1Good: 12, q1Normal: 2, q1Bad: 0, q1Score: "98.00", q2VeryGood: 25, q2Good: 12, q2Normal: 1, q2Bad: 0, q2Score: "100.00", totalScore: "70.22" },
-        { dept: "娑堝寲鍐呯", q1VeryGood: 180, q1Good: 52, q1Normal: 1, q1Bad: 0, q1Score: "645.00", q2VeryGood: 168, q2Good: 62, q2Normal: 3, q2Bad: 0, q2Score: "631.00", totalScore: "73.56" },
-        { dept: "鑲惧唴绉�", q1VeryGood: 21, q1Good: 4, q1Normal: 0, q1Bad: 0, q1Score: "71.00", q2VeryGood: 19, q2Good: 6, q2Normal: 0, q2Bad: 0, q2Score: "65.00", totalScore: "68.50" },
-      ],
+      // 椤堕儴鍏ㄥ眬绛涢��
+      queryParams: {
+        dateRange: [],
+        deptWard: '',
+      },
+      deptOptions: DEPTS,
+      wardOptions: WARDS,
+
+      // 琛ㄦ牸涓夛細缁熻缁村害 + 鐙珛绉戝/鐥呭尯鏉′欢锛�'inherit' = 娌跨敤椤堕儴锛�
+      statDimension: 'dept',
+      statDeptWard: 'inherit',
+
+      // 缁熶竴鎺ュ彛杩斿洖鐨勬暟鎹紙妯℃嫙锛�
+      questionList: [],
+      deptComparison: [],
+      wardComparison: [],
     };
   },
+  computed: {
+    // 鍥哄畾浜旈」閫夐」鐨勯鐩� => 琛ㄤ竴
+    satisfactionQuestions() {
+      return this.questionList.filter((q) => this.isSatisfactionQuestion(q));
+    },
+    // 鍏朵綑棰樼洰 => 琛ㄤ簩
+    otherQuestions() {
+      return this.questionList.filter((q) => !this.isSatisfactionQuestion(q));
+    },
+    // 琛ㄤ竴锛氭弧鎰忓害缁熻锛堝惈鎬昏琛岋級
+    serviceSatisfaction() {
+      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;
+        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]),
+        };
+      });
+
+      // 鎬昏琛�
+      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 total = sum.veryGood + sum.good + sum.normal + sum.bad + sum.na;
+      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]),
+      });
+      return rows;
+    },
+    // 琛ㄤ簩锛氬崟閫�/澶氶�夌粺璁�
+    questionStats() {
+      return this.otherQuestions.map((q) => {
+        const total = this.questionTotal(q);
+        return {
+          title: q.title,
+          total,
+          options: q.options.map((o) => ({
+            option: o.option,
+            count: o.count,
+            rate: this.rateOf(o.count, total),
+          })),
+        };
+      });
+    },
+    // 琛ㄤ笁锛氭寜缁村害 + 鐙珛鏉′欢杩囨护鍚庣殑琛�
+    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);
+    },
+  },
+  mounted() {
+    if (!this.queryParams.dateRange || !this.queryParams.dateRange.length) {
+      this.queryParams.dateRange = this.defaultRange();
+    }
+    this.fetchData();
+  },
   methods: {
+    // 琛ㄤ笁鐙珛鏉′欢锛�'inherit' 鏃舵部鐢ㄩ《閮ㄦ潯浠�
+    statFilter() {
+      const v = this.statDeptWard === 'inherit' ? this.queryParams.deptWard : this.statDeptWard;
+      return v || '';
+    },
+    onStatDimensionChange() {
+      // 鍒囨崲缁村害鍚庯紝鑻ュ綋鍓嶅凡閫夋潯浠朵笉灞炰簬鏂扮淮搴︼紝鍒欐仮澶嶁�滄部鐢ㄩ《閮ㄢ��
+      if (this.statDeptWard !== 'inherit' && !this.statDeptWard.startsWith(this.statDimension + ':')) {
+        this.statDeptWard = 'inherit';
+      }
+    },
+    handleReset() {
+      this.queryParams.dateRange = this.defaultRange();
+      this.queryParams.deptWard = '';
+      this.fetchData();
+    },
+    // 鍒ゆ柇棰樼洰閫夐」鏄惁涓哄浐瀹氭弧鎰忓害浜旈」
+    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));
+    },
+    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) + '%';
+    },
+    // 缁煎悎寰楀垎锛氶潪甯告弧鎰�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);
+    },
+    defaultRange() {
+      const end = new Date();
+      const start = new Date();
+      start.setDate(start.getDate() - 29);
+      const f = (d) => {
+        const y = d.getFullYear();
+        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));
+      }
+      return 30;
+    },
+    // 鎷夊彇鏁版嵁锛堝綋鍓嶄负妯℃嫙鏁版嵁锛屾帴鍙e氨缁悗鏇挎崲涓虹湡瀹炶姹傦級
+    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;
+
+      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);
+        }
+        return item;
+      });
+
+      const deptComparison = DEPTS.map((d) => this.genCompareRow(d, rnd));
+      const wardComparison = WARDS.map((w) => this.genCompareRow(w, rnd));
+
+      return { questionList, deptComparison, wardComparison };
+    },
+    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,
+        ]),
+      };
+    },
     exportTable(tableId) {
       const tableNames = { table1: '鍚勬湇鍔¢」鐩弧鎰忓害缁熻', table2: '鍗曢�夐澶氶�夐缁熻', table3: '鍚勭瀹ゆ弧鎰忓害璇勫垎瀵规瘮' };
       const name = tableNames[tableId] || '缁熻鏁版嵁';
@@ -152,9 +461,9 @@
         // 浠庤〃鏍兼彁鍙栨暟鎹瀯寤哄甫鏍峰紡鐨� 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 => {
+          tr.querySelectorAll('th,td').forEach((td) => {
             row.push((td.innerText || '').trim());
           });
           if (row.length) data.push(row);
@@ -170,14 +479,16 @@
       } catch (e) {
         // fallback: CSV
         let csv = '锘�';
-        table.querySelectorAll('tr').forEach(tr => {
+        table.querySelectorAll('tr').forEach((tr) => {
           const row = [];
-          tr.querySelectorAll('th,td').forEach(td => row.push('"' + (td.innerText || '').replace(/"/g, '""') + '"'));
+          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');
-        link.href = URL.createObjectURL(blob); link.download = name + '.csv'; link.click();
+        link.href = URL.createObjectURL(blob);
+        link.download = name + '.csv';
+        link.click();
       }
     },
   },
@@ -186,8 +497,10 @@
 
 <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; }
diff --git a/src/views/followvisit/discharge/index.vue b/src/views/followvisit/discharge/index.vue
index da3ba46..7b355eb 100644
--- a/src/views/followvisit/discharge/index.vue
+++ b/src/views/followvisit/discharge/index.vue
@@ -243,6 +243,17 @@
             </div>
           </div>
         </el-col>
+        <div class="color-legend">
+          <span class="legend-item" v-if="orgname == '鐪佺珛鍚屽痉缈犺嫅闄㈠尯'">
+            <span class="legend-block legend-blue"></span>鏈埌闅忚鏃堕棿
+          </span>
+          <span class="legend-item">
+            <span class="legend-block legend-red"></span>濉姤鍐呭瀛樺湪寮傚父
+          </span>
+          <span class="legend-item">
+            <span class="legend-block legend-yellow"></span>濉姤鍐呭瀛樺湪璀﹀憡
+          </span>
+        </div>
       </el-row>
       <div class="selected-info">
         宸查�変腑
@@ -2309,14 +2320,41 @@
         this.getList();
       });
     },
-    // 寮傚父鍒楁覆鏌�
+    // 琛岄鑹叉覆鏌擄細钃濊壊(鏈埌闅忚鏃堕棿) / 绾㈣壊(寮傚父) / 榛勮壊(璀﹀憡)
     tableRowClassName({ row, rowIndex }) {
+      // 褰撳墠鏃堕棿灏忎簬搴旈殢璁挎椂闂翠笖浠诲姟鐘舵�佷负寰呴殢璁� 鈫� 鏁磋钃濊壊锛堝崡鍗庡ぇ瀛﹂檮灞炵涓�鍖婚櫌涓嶇敓鏁堬級
+      if (
+        this.orgname == "鍗楀崕澶у闄勫睘绗竴鍖婚櫌" &&
+        row.sendstate == 2 &&
+        this.isBeforeVisitTime(row.visitTime)
+      ) {
+        return "blue-row";
+      }
       if (row.excep == 1) {
         return "warning-row";
       } else if (row.excep == 2) {
         return "remind-row";
       }
       return "";
+    },
+    // 鍒ゆ柇褰撳墠鏃堕棿鏄惁鏃╀簬搴旈殢璁挎椂闂达紙鎸夆�滃ぉ鈥濇瘮杈冿級
+    isBeforeVisitTime(visitTime) {
+      if (!visitTime) return false;
+      let date;
+      if (typeof visitTime === "number") {
+        date = new Date(visitTime);
+      } else {
+        date = new Date(String(visitTime).replace(/-/g, "/"));
+      }
+      if (isNaN(date.getTime())) return false;
+      const now = new Date();
+      const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
+      const visitDay = new Date(
+        date.getFullYear(),
+        date.getMonth(),
+        date.getDate()
+      );
+      return today.getTime() < visitDay.getTime();
     },
     restoreSelection() {
       if (!this.$refs.userform) {
@@ -2549,12 +2587,46 @@
 ::v-deep.el-table .remind-row {
   background: #fcf5aa;
 }
+::v-deep.el-table .blue-row {
+  background: #c6e2ff;
+}
 
 .documentf {
   display: flex;
   justify-content: flex-end;
 }
 
+.color-legend {
+  float: right;
+  display: flex;
+  align-items: center;
+  height: 40px;
+  gap: 16px;
+  margin-right: 5px;
+}
+.legend-item {
+  display: inline-flex;
+  align-items: center;
+  font-size: 16px;
+  color: #666;
+}
+.legend-block {
+  display: inline-block;
+  width: 14px;
+  height: 14px;
+  border-radius: 3px;
+  margin-right: 6px;
+}
+.legend-blue {
+  background: #9cc9fa;
+}
+.legend-red {
+  background: #f1aaaa;
+}
+.legend-yellow {
+  background: #faf18d;
+}
+
 .download {
   text-align: center;
 
diff --git a/src/views/outsideChainnew.vue b/src/views/outsideChainnew.vue
index 1132d17..3feadf1 100644
--- a/src/views/outsideChainnew.vue
+++ b/src/views/outsideChainnew.vue
@@ -22,9 +22,15 @@
           v-for="(item, index) in questionList"
           :key="item.aaa"
         >
-          <div class="scriptTopic-dev" :key="index" v-if="item.scriptType == 1">
+          <div
+            class="scriptTopic-dev"
+            :key="index"
+            v-if="item.scriptType == 1"
+            :class="{ 'required-error': item.requiredError }"
+          >
             <div class="dev-text">
               {{ index + 1 }}銆�<span style="line-height: 1.5"
+                ><span v-if="isRequired(item)" class="required-star">*</span
                 >{{ item.scriptContent }}
                 <span style="color: #3ba2f7">[鍗曢�塢</span></span
               >
@@ -54,9 +60,15 @@
             </div>
           </div>
           <!-- 澶氶�� -->
-          <div class="scriptTopic-dev" :key="index" v-if="item.scriptType == 2">
+          <div
+            class="scriptTopic-dev"
+            :key="index"
+            v-if="item.scriptType == 2"
+            :class="{ 'required-error': item.requiredError }"
+          >
             <div class="dev-text">
               {{ index + 1 }}銆�<span style="line-height: 1.5"
+                ><span v-if="isRequired(item)" class="required-star">*</span
                 >{{ item.scriptContent }}
                 <span style="color: #3ba2f7">[澶氶�塢</span></span
               >
@@ -88,9 +100,11 @@
             class="scriptTopic-dev"
             :key="index"
             v-if="item.scriptType == 4 || item.scriptType == 3"
+            :class="{ 'required-error': item.requiredError }"
           >
             <div class="dev-text">
               {{ index + 1 }}銆�<span style="line-height: 1.5"
+                ><span v-if="isRequired(item)" class="required-star">*</span
                 >{{ item.scriptContent
                 }}<span style="color: #3ba2f7">[闂瓟]</span></span
               >
@@ -421,6 +435,10 @@
     // 缂撳瓨
     cache(subm) {
       console.log("杩涘叆缂撳瓨");
+      // 鎻愪氦鍦烘櫙锛氬厛鍋氬繀濉」鏍¢獙
+      if (subm === true && !this.validateRequired()) {
+        return;
+      }
       let form = {
         param1: this.taskid,
         param2: this.patid,
@@ -459,6 +477,45 @@
           }
         }
       });
+    },
+    // 鏄惁蹇呭~锛�1蹇呭~ 2涓嶅繀濉級
+    isRequired(item) {
+      return item.ismandatory == 1 || item.ismandatory == "1";
+    },
+    // 鍒ゆ柇棰樼洰鏄惁鏈~鍐�
+    isQuestionEmpty(item) {
+      const val = item.questionResult;
+      if (item.scriptType == 2) {
+        return !val || (Array.isArray(val) && val.length === 0);
+      }
+      return val === undefined || val === null || val === "";
+    },
+    // 淇濆瓨鍓嶅繀濉」鏍¢獙
+    validateRequired() {
+      const list = this.questionList || [];
+      list.forEach((item) => {
+        if (item.requiredError) this.$set(item, "requiredError", false);
+      });
+      const unfilled = [];
+      list.forEach((item) => {
+        if (!this.isRequired(item)) return;
+        if (this.isQuestionEmpty(item)) unfilled.push(item);
+      });
+      if (unfilled.length > 0) {
+        unfilled.forEach((item) => this.$set(item, "requiredError", true));
+        this.$modal.msgWarning(
+          `杩樻湁 ${unfilled.length} 閬撳繀濉鏈~鍐欙紝璇疯ˉ鍏呭悗鍐嶆彁浜
+        );
+        this.$nextTick(() => {
+          const idx = this.questionList.indexOf(unfilled[0]);
+          const els = this.$el.querySelectorAll(".scriptTopic-dev");
+          if (els[idx]) {
+            els[idx].scrollIntoView({ behavior: "smooth", block: "center" });
+          }
+        });
+        return false;
+      }
+      return true;
     },
     // 澶勭悊鍗曢�夐�夐」
     handleOptionChange(selectedvalue, index, arr) {
@@ -610,6 +667,18 @@
 }
 .toptitle {
 }
+.required-star {
+  color: #f56c6c;
+  margin-right: 2px;
+  font-weight: 600;
+}
+.scriptTopic-dev.required-error {
+  border: 1px solid #f56c6c;
+  border-left: 4px solid #f56c6c;
+  background: #fef0f0;
+  border-radius: 4px;
+  padding: 8px;
+}
 .bottom-fixed {
   position: fixed;
   bottom: 0;
diff --git a/src/views/outsideChainwtnew.vue b/src/views/outsideChainwtnew.vue
index 5dd3cb3..f37bec9 100644
--- a/src/views/outsideChainwtnew.vue
+++ b/src/views/outsideChainwtnew.vue
@@ -64,6 +64,7 @@
                       item.prompt &&
                       item.scriptResult &&
                       (item.scriptType !== 2 || item.scriptResult.length > 0),
+                    'required-error': item.requiredError,
                   }"
                 >
                   <!-- 棰樼洰棰樺共 -->
@@ -71,7 +72,11 @@
                     <span class="question-number"
                       >{{ getQuestionNumber(group, index) }}.</span
                     >
-                    <span class="question-text">{{ item.scriptContent }}</span>
+                    <span class="question-text"
+                      ><span v-if="isRequired(item)" class="required-star"
+                        >*</span
+                      >{{ item.scriptContent }}</span
+                    >
                     <span class="question-type-tag">
                       {{
                         item.scriptType == 1
@@ -201,6 +206,7 @@
                   item.prompt &&
                   item.scriptResult &&
                   (item.scriptType !== 2 || item.scriptResult.length > 0),
+                'required-error': item.requiredError,
               }"
             >
               <!-- 棰樼洰棰樺共 -->
@@ -208,7 +214,11 @@
                 <span class="question-number"
                   >{{ getVisibleQuestionIndex(index) }}.</span
                 >
-                <span class="question-text">{{ item.scriptContent }}</span>
+                <span class="question-text"
+                  ><span v-if="isRequired(item)" class="required-star"
+                    >*</span
+                  >{{ item.scriptContent }}</span
+                >
                 <span class="question-type-tag">
                   {{
                     item.scriptType == 1
@@ -695,6 +705,10 @@
     // 缂撳瓨
     cache(subm) {
       console.log("杩涘叆缂撳瓨");
+      // 鎻愪氦鍦烘櫙锛氬厛鍋氬繀濉」鏍¢獙
+      if (subm === true && !this.validateRequired()) {
+        return;
+      }
       // 鎻愪氦鍦烘櫙锛氬紑鍚姞杞界姸鎬侊紝闃叉閲嶅鐐瑰嚮
       if (subm === true) {
         this.submitting = true;
@@ -764,6 +778,46 @@
             this.$modal.msgError("鎻愪氦澶辫触锛岃閲嶈瘯");
           }
         });
+    },
+    // 鏄惁蹇呭~锛�1蹇呭~ 2涓嶅繀濉級
+    isRequired(item) {
+      return item.ismandatory == 1 || item.ismandatory == "1";
+    },
+    // 鍒ゆ柇棰樼洰鏄惁鏈~鍐�
+    isQuestionEmpty(item) {
+      const val = item.scriptResult;
+      if (item.scriptType == 2) {
+        return !val || (Array.isArray(val) && val.length === 0);
+      }
+      return val === undefined || val === null || val === "";
+    },
+    // 淇濆瓨鍓嶅繀濉」鏍¢獙
+    validateRequired() {
+      const list = this.questionList || [];
+      list.forEach((item) => {
+        if (item.requiredError) this.$set(item, "requiredError", false);
+      });
+      const unfilled = [];
+      list.forEach((item) => {
+        if (item.ishide) return;
+        if (!this.isRequired(item)) return;
+        if (this.isQuestionEmpty(item)) unfilled.push(item);
+      });
+      if (unfilled.length > 0) {
+        unfilled.forEach((item) => this.$set(item, "requiredError", true));
+        this.$modal.msgWarning(
+          `杩樻湁 ${unfilled.length} 閬撳繀濉鏈~鍐欙紝璇疯ˉ鍏呭悗鍐嶆彁浜
+        );
+        this.$nextTick(() => {
+          const idx = this.visibleQuestions.indexOf(unfilled[0]);
+          const els = this.$el.querySelectorAll(".question-item");
+          if (els[idx]) {
+            els[idx].scrollIntoView({ behavior: "smooth", block: "center" });
+          }
+        });
+        return false;
+      }
+      return true;
     },
     // 鑾峰彇鍙棰樼洰鐨勬纭簭鍙凤紙瑙e喅璺抽鍚庡簭鍙蜂笉杩炵画鐨勯棶棰橈級
     getVisibleQuestionIndex(index) {
@@ -1041,6 +1095,12 @@
     border-left: 4px solid #e6a23c;
   }
 
+  &.required-error {
+    border-color: #f56c6c;
+    border-left: 4px solid #f56c6c;
+    background: #fef0f0;
+  }
+
   &:last-child {
     margin-bottom: 0;
   }
@@ -1074,6 +1134,12 @@
   min-width: 16px;
 }
 
+.required-star {
+  color: #f56c6c;
+  margin-right: 2px;
+  font-weight: 600;
+}
+
 // .question-text {
 //   line-height: 1.5;
 //   color: #2c3e50;
diff --git a/src/views/patient/dedicated/heart/index.vue b/src/views/patient/dedicated/heart/index.vue
new file mode 100644
index 0000000..092e749
--- /dev/null
+++ b/src/views/patient/dedicated/heart/index.vue
@@ -0,0 +1,388 @@
+<template>
+  <div class="app-container">
+    <!-- 鏌ヨ鏍� -->
+    <el-form :inline="true" :model="queryParams" size="small" class="query-form">
+      <el-form-item label="鎮h�呭鍚�">
+        <el-input v-model="queryParams.name" placeholder="璇疯緭鍏ュ鍚�" clearable @keyup.enter.native="handleQuery" />
+      </el-form-item>
+      <el-form-item label="涓绘不鍖荤敓">
+        <el-input v-model="queryParams.drname" placeholder="璇疯緭鍏ュ尰鐢熷鍚�" clearable @keyup.enter.native="handleQuery" />
+      </el-form-item>
+      <el-form-item label="绉戝/鐥呭尯">
+        <el-select v-model="queryParams.deptWard" placeholder="鍏ㄩ儴" clearable filterable>
+          <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-select>
+      </el-form-item>
+      <el-form-item label="蹇冨姛鑳藉垎绾�">
+        <el-select v-model="queryParams.nyhaClass" placeholder="鍏ㄩ儴" clearable>
+          <el-option v-for="g in NYHA_CLASSES" :key="g" :label="g" :value="g" />
+        </el-select>
+      </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-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button type="primary" icon="el-icon-plus" size="mini" @click="openAddDialog">娣诲姞鎮h��</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="danger" icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete">鍒犻櫎</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="warning" icon="el-icon-download" size="mini" @click="handleExport">瀵煎嚭</el-button>
+      </el-col>
+    </el-row>
+
+    <!-- 鍒楄〃 -->
+    <el-table :data="currentPageData" v-loading="loading" border @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="50" align="center" />
+      <el-table-column label="濮撳悕" prop="name" min-width="90" align="center" fixed="left" />
+      <el-table-column label="鎬у埆" prop="sex" width="60" align="center" />
+      <el-table-column label="骞撮緞" prop="age" width="70" align="center" />
+      <el-table-column label="鑱旂郴鐢佃瘽" prop="telcode" width="120" align="center" />
+      <el-table-column label="绉戝" prop="deptname" min-width="110" align="center" show-overflow-tooltip />
+      <el-table-column label="鐥呭尯" prop="wardname" min-width="90" align="center" />
+      <el-table-column label="涓绘不鍖荤敓" prop="drname" width="90" align="center" />
+      <el-table-column label="蹇冭剰璇婃柇" prop="heartDiagnosis" min-width="120" align="center" show-overflow-tooltip />
+      <el-table-column label="蹇冨姛鑳藉垎绾�" prop="nyhaClass" width="100" align="center">
+        <template slot-scope="scope">
+          <el-tag :type="nyhaTagType(scope.row.nyhaClass)" size="small">{{ scope.row.nyhaClass }}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="蹇冪巼(娆�/鍒�)" prop="heartRate" width="100" align="center" />
+      <el-table-column label="琛�鍘�(mmHg)" prop="bloodPressure" width="120" align="center" />
+      <el-table-column label="闅忚璁″垝" prop="followPlan" width="100" align="center" />
+      <el-table-column label="鏉ユ簮" prop="source" width="90" align="center" />
+      <el-table-column label="鍔犲叆鏃堕棿" prop="addTime" width="150" align="center" />
+      <el-table-column label="鎿嶄綔" width="120" align="center" fixed="right">
+        <template slot-scope="scope">
+          <el-button type="text" size="mini" icon="el-icon-view" @click="openDetail(scope.row)">璇︽儏</el-button>
+          <el-button type="text" size="mini" icon="el-icon-close" class="danger-text" @click="removeRow(scope.row)">绉婚櫎</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 鍒嗛〉 -->
+    <el-pagination
+      class="pagination-wrap"
+      background
+      layout="total, sizes, prev, pager, next, jumper"
+      :total="filteredList.length"
+      :current-page.sync="pageNum"
+      :page-size.sync="pageSize"
+      :page-sizes="[10, 20, 50]"
+    />
+
+    <!-- 娣诲姞鎮h�呭脊绐� -->
+    <el-dialog title="娣诲姞涓撶梾鎮h��" :visible.sync="addVisible" width="80%" top="6vh">
+      <el-tabs v-model="activeSource" @tab-click="onSourceTabChange">
+        <el-tab-pane label="鎮h�呮。妗�" name="archive" />
+        <el-tab-pane label="鍑洪櫌璁板綍" name="hospital" />
+        <el-tab-pane label="闂ㄨ瘖璁板綍" name="outpatient" />
+        <el-tab-pane label="鍦ㄩ櫌璁板綍" name="inpatient" />
+      </el-tabs>
+      <el-form :inline="true" size="small">
+        <el-form-item label="濮撳悕/璇婃柇">
+          <el-input v-model="poolKeyword" placeholder="杈撳叆濮撳悕鎴栬瘖鏂叧閿瓧" clearable style="width: 220px" @input="handlePoolSearch" />
+        </el-form-item>
+        <span class="pool-tip">鍏� {{ filteredPool.length }} 鏉★紝宸查�� {{ addSelected.length }} 鏉�</span>
+      </el-form>
+      <el-table ref="addTable" :data="filteredPool" border size="small" max-height="420" @selection-change="onAddSelectionChange">
+        <el-table-column type="selection" width="50" align="center" />
+        <el-table-column label="濮撳悕" prop="name" width="90" align="center" />
+        <el-table-column label="鎬у埆" prop="sex" width="60" align="center" />
+        <el-table-column label="骞撮緞" prop="age" width="70" align="center" />
+        <el-table-column label="鑱旂郴鐢佃瘽" prop="telcode" width="120" align="center" />
+        <el-table-column label="绉戝" prop="deptname" min-width="110" align="center" show-overflow-tooltip />
+        <el-table-column label="鐥呭尯" prop="wardname" min-width="90" align="center" />
+        <el-table-column label="璇婃柇" prop="diagname" min-width="130" align="center" show-overflow-tooltip />
+        <el-table-column label="鍖荤敓" prop="drname" width="90" align="center" />
+        <el-table-column label="鏃ユ湡" prop="sourceDate" width="110" align="center" />
+      </el-table>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="addVisible = false">鍙� 娑�</el-button>
+        <el-button type="primary" @click="confirmAdd">纭畾娣诲姞</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 璇︽儏寮圭獥 -->
+    <el-dialog title="涓撶梾鎮h�呰鎯�" :visible.sync="detailVisible" width="720px">
+      <el-descriptions :column="2" border size="medium" v-if="detailRow">
+        <el-descriptions-item label="濮撳悕">{{ detailRow.name }}</el-descriptions-item>
+        <el-descriptions-item label="鎬у埆">{{ detailRow.sex }}</el-descriptions-item>
+        <el-descriptions-item label="骞撮緞">{{ detailRow.age }}</el-descriptions-item>
+        <el-descriptions-item label="鑱旂郴鐢佃瘽">{{ detailRow.telcode }}</el-descriptions-item>
+        <el-descriptions-item label="绉戝">{{ detailRow.deptname }}</el-descriptions-item>
+        <el-descriptions-item label="鐥呭尯">{{ detailRow.wardname || '鈥�' }}</el-descriptions-item>
+        <el-descriptions-item label="涓绘不鍖荤敓">{{ detailRow.drname }}</el-descriptions-item>
+        <el-descriptions-item label="蹇冭剰璇婃柇">{{ detailRow.heartDiagnosis }}</el-descriptions-item>
+        <el-descriptions-item label="蹇冨姛鑳藉垎绾�">{{ detailRow.nyhaClass }}</el-descriptions-item>
+        <el-descriptions-item label="蹇冪巼">{{ detailRow.heartRate }} 娆�/鍒�</el-descriptions-item>
+        <el-descriptions-item label="琛�鍘�">{{ detailRow.bloodPressure }} mmHg</el-descriptions-item>
+        <el-descriptions-item label="闅忚璁″垝">{{ detailRow.followPlan }}</el-descriptions-item>
+        <el-descriptions-item label="鎮h�呮潵婧�">{{ detailRow.source }}</el-descriptions-item>
+        <el-descriptions-item label="鍔犲叆鏃堕棿">{{ detailRow.addTime }}</el-descriptions-item>
+      </el-descriptions>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+// 蹇冭剰涓撶梾鎮h�� 鈥斺�� 妯℃嫙鏁版嵁锛屾帴鍙e氨缁悗鏇挎崲 getList / confirmAdd 涓殑鏈湴鏁版嵁閫昏緫
+const PERSONS = [
+  { name: '寮犱紵', sex: '鐢�', age: 68 },
+  { name: '鏉庡', sex: '濂�', age: 55 },
+  { name: '鐜嬪己', sex: '鐢�', age: 72 },
+  { name: '鍒樻晱', sex: '濂�', age: 61 },
+  { name: '闄堥潤', sex: '濂�', age: 66 },
+  { name: '鏉ㄥ啗', sex: '鐢�', age: 59 },
+  { name: '璧典附', sex: '濂�', age: 70 },
+  { name: '榛勫媷', sex: '鐢�', age: 64 },
+  { name: '鍛ㄨ壋', sex: '濂�', age: 58 },
+  { name: '鍚村垰', sex: '鐢�', age: 75 },
+];
+const HEART_DIAGNOSES = ['鍐犲績鐥�', '蹇冨姏琛扮', '楂樿鍘�', '蹇冩埧棰ゅ姩', '蹇冭倢姊楁', '蹇冨緥澶卞父', '蹇冭剰鐡h啘鐥�', '鎵╁紶鍨嬪績鑲岀梾', '鑲ュ帤鍨嬪績鑲岀梾', '鎬ユ�у啝鑴夌患鍚堝緛'];
+const DEPTS = ['蹇冭绠″唴绉�', '鑰佸勾绉�', '閲嶇棁鍖诲绉�', '搴峰鍖诲绉�', '蹇冨绉�', '鍐呭垎娉岀', '绁炵粡鍐呯', '鍛煎惛鍐呯'];
+const WARDS = ['涓�鐥呭尯', '浜岀梾鍖�', '涓夌梾鍖�', '鍥涚梾鍖�', '浜旂梾鍖�'];
+const DOCTORS = ['鐜嬩富浠�', '鏉庡尰鐢�', '寮犲尰鐢�', '鍒樺尰鐢�', '闄堝尰鐢�'];
+const NYHA_CLASSES = ['鈪犵骇', '鈪$骇', '鈪㈢骇', '鈪g骇'];
+const FOLLOW_PLANS = ['姣忓懆', '姣忎袱鍛�', '姣忔湀', '姣忓搴�'];
+const SOURCE_LABEL = { archive: '鎮h�呮。妗�', hospital: '鍑洪櫌璁板綍', outpatient: '闂ㄨ瘖璁板綍', inpatient: '鍦ㄩ櫌璁板綍' };
+
+export default {
+  name: 'HeartDedicated',
+  data() {
+    return {
+      loading: false,
+      // 涓撶梾鎮h�呭垪琛紙宸插姞鍏ワ級
+      patientList: [],
+      ids: [],
+      single: true,
+      multiple: true,
+      // 鏌ヨ鏉′欢
+      queryParams: { name: '', drname: '', deptWard: '', nyhaClass: '' },
+      pageNum: 1,
+      pageSize: 10,
+      deptOptions: DEPTS,
+      wardOptions: WARDS,
+      NYHA_CLASSES,
+      // 娣诲姞寮圭獥
+      addVisible: false,
+      activeSource: 'archive',
+      poolKeyword: '',
+      addSelected: [],
+      mockPool: { archive: [], hospital: [], outpatient: [], inpatient: [] },
+      // 璇︽儏寮圭獥
+      detailVisible: false,
+      detailRow: null,
+    };
+  },
+  computed: {
+    filteredList() {
+      const { name, drname, deptWard, nyhaClass } = this.queryParams;
+      return this.patientList.filter((r) => {
+        if (name && !r.name.includes(name)) return false;
+        if (drname && !r.drname.includes(drname)) return false;
+        if (deptWard) {
+          const label = deptWard.split(':')[1];
+          if (label && r.deptname !== label && r.wardname !== label) return false;
+        }
+        if (nyhaClass && r.nyhaClass !== nyhaClass) return false;
+        return true;
+      });
+    },
+    currentPageData() {
+      const start = (this.pageNum - 1) * this.pageSize;
+      return this.filteredList.slice(start, start + this.pageSize);
+    },
+    filteredPool() {
+      const kw = this.poolKeyword.trim();
+      const list = this.mockPool[this.activeSource] || [];
+      if (!kw) return list;
+      return list.filter((r) => r.name.includes(kw) || r.diagname.includes(kw));
+    },
+  },
+  created() {
+    this.buildPool();
+    this.buildInitialList();
+  },
+  methods: {
+    // 鐢熸垚鍚勬潵婧愮殑妯℃嫙鎮h�呮睜
+    buildPool() {
+      const mk = (idx, source, noPrefix) => {
+        const p = PERSONS[idx % PERSONS.length];
+        const isWard = source === 'hospital' || source === 'inpatient';
+        return {
+          id: noPrefix + (idx + 1),
+          name: p.name,
+          sex: p.sex,
+          age: p.age,
+          telcode: '139' + (10000000 + idx * 1111111),
+          deptname: DEPTS[idx % DEPTS.length],
+          wardname: isWard ? WARDS[idx % WARDS.length] : '',
+          drname: DOCTORS[idx % DOCTORS.length],
+          diagname: HEART_DIAGNOSES[idx % HEART_DIAGNOSES.length],
+          sourceDate: this.recentDate(idx * 3 + (source === 'outpatient' ? 2 : 5)),
+        };
+      };
+      this.mockPool = {
+        archive: PERSONS.map((_, i) => mk(i, 'archive', 'da')),
+        hospital: PERSONS.map((_, i) => mk(i, 'hospital', 'zy')),
+        outpatient: PERSONS.map((_, i) => mk(i, 'outpatient', 'mz')),
+        inpatient: PERSONS.map((_, i) => mk(i, 'inpatient', 'zy')),
+      };
+    },
+    // 棰勭疆鑻ュ共宸插姞鍏ョ殑鎮h�咃紝淇濊瘉棣栧睆鏈夋暟鎹�
+    buildInitialList() {
+      const take = (source, n) =>
+        this.mockPool[source].slice(0, n).map((row) => this.toDedicated(row, source));
+      this.patientList = [
+        ...take('hospital', 3),
+        ...take('inpatient', 2),
+        ...take('archive', 2),
+        ...take('outpatient', 1),
+      ];
+    },
+    // 姹犳偅鑰� => 涓撶梾鎮h�咃紙琛ュ厖蹇冭剰鐩稿叧瀛楁锛�
+    toDedicated(row, source) {
+      const n = Number(String(row.id).replace(/\D/g, '')) || 0;
+      const nyhaClass = NYHA_CLASSES[n % NYHA_CLASSES.length];
+      const heartRate = 58 + (n % 5) * 8;
+      const high = 118 + (n % 4) * 8;
+      const low = 72 + (n % 3) * 4;
+      return {
+        id: row.id,
+        name: row.name,
+        sex: row.sex,
+        age: row.age,
+        telcode: row.telcode,
+        deptname: row.deptname,
+        wardname: row.wardname,
+        drname: row.drname,
+        heartDiagnosis: row.diagname,
+        nyhaClass,
+        heartRate,
+        bloodPressure: `${high}/${low}`,
+        followPlan: FOLLOW_PLANS[n % FOLLOW_PLANS.length],
+        source: SOURCE_LABEL[source] || source,
+        addTime: this.now(),
+      };
+    },
+    nyhaTagType(grade) {
+      const map = { '鈪犵骇': 'success', '鈪$骇': 'primary', '鈪㈢骇': 'warning', '鈪g骇': 'danger' };
+      return map[grade] || 'info';
+    },
+    handleQuery() {
+      this.pageNum = 1;
+    },
+    resetQuery() {
+      this.queryParams = { name: '', drname: '', deptWard: '', nyhaClass: '' };
+      this.pageNum = 1;
+    },
+    handleSelectionChange(selection) {
+      this.ids = selection.map((item) => item.id);
+      this.single = selection.length !== 1;
+      this.multiple = !selection.length;
+    },
+    openAddDialog() {
+      this.addSelected = [];
+      this.poolKeyword = '';
+      this.activeSource = 'archive';
+      this.addVisible = true;
+      this.$nextTick(() => this.$refs.addTable && this.$refs.addTable.clearSelection());
+    },
+    onSourceTabChange() {
+      this.addSelected = [];
+      this.$nextTick(() => this.$refs.addTable && this.$refs.addTable.clearSelection());
+    },
+    handlePoolSearch() {
+      // 鏈湴杩囨护锛岀敱 filteredPool 璁$畻灞炴�у畬鎴�
+    },
+    onAddSelectionChange(selection) {
+      this.addSelected = selection;
+    },
+    confirmAdd() {
+      if (!this.addSelected.length) {
+        this.$modal.msgWarning('璇峰厛閫夋嫨瑕佹坊鍔犵殑鎮h��');
+        return;
+      }
+      const existing = new Set(this.patientList.map((r) => r.id));
+      const toAdd = this.addSelected
+        .filter((row) => !existing.has(row.id))
+        .map((row) => this.toDedicated(row, this.activeSource));
+      this.patientList = [...toAdd, ...this.patientList];
+      this.addVisible = false;
+      this.$modal.msgSuccess(`鎴愬姛娣诲姞 ${toAdd.length} 浣嶆偅鑰卄);
+    },
+    removeRow(row) {
+      this.$modal
+        .confirm(`鏄惁纭灏嗘偅鑰�"${row.name}"绉诲嚭蹇冭剰涓撶梾鍒楄〃锛焋)
+        .then(() => {
+          this.patientList = this.patientList.filter((r) => r.id !== row.id);
+          this.$modal.msgSuccess('绉婚櫎鎴愬姛');
+        })
+        .catch(() => {});
+    },
+    handleDelete() {
+      this.$modal
+        .confirm(`鏄惁纭鍒犻櫎閫変腑鐨� ${this.ids.length} 浣嶆偅鑰咃紵`)
+        .then(() => {
+          this.patientList = this.patientList.filter((r) => !this.ids.includes(r.id));
+          this.$modal.msgSuccess('鍒犻櫎鎴愬姛');
+        })
+        .catch(() => {});
+    },
+    openDetail(row) {
+      this.detailRow = row;
+      this.detailVisible = true;
+    },
+    handleExport() {
+      const rows = this.filteredList;
+      if (!rows.length) {
+        this.$modal.msgWarning('鏆傛棤鏁版嵁鍙鍑�');
+        return;
+      }
+      const head = ['濮撳悕', '鎬у埆', '骞撮緞', '鑱旂郴鐢佃瘽', '绉戝', '鐥呭尯', '涓绘不鍖荤敓', '蹇冭剰璇婃柇', '蹇冨姛鑳藉垎绾�', '蹇冪巼(娆�/鍒�)', '琛�鍘�(mmHg)', '闅忚璁″垝', '鏉ユ簮', '鍔犲叆鏃堕棿'];
+      const keys = ['name', 'sex', 'age', 'telcode', 'deptname', 'wardname', 'drname', 'heartDiagnosis', 'nyhaClass', 'heartRate', 'bloodPressure', 'followPlan', 'source', 'addTime'];
+      const csv = [head.join(',')]
+        .concat(rows.map((r) => keys.map((k) => `"${(r[k] ?? '').toString().replace(/"/g, '""')}"`).join(',')))
+        .join('\n');
+      const blob = new Blob(['锘�' + csv], { type: 'text/csv;charset=utf-8;' });
+      const link = document.createElement('a');
+      link.href = URL.createObjectURL(blob);
+      link.download = `蹇冭剰涓撶梾鎮h�卂${Date.now()}.csv`;
+      link.click();
+      URL.revokeObjectURL(link.href);
+    },
+    recentDate(daysAgo) {
+      const d = new Date();
+      d.setDate(d.getDate() - daysAgo);
+      const mm = String(d.getMonth() + 1).padStart(2, '0');
+      const dd = String(d.getDate()).padStart(2, '0');
+      return `${d.getFullYear()}-${mm}-${dd}`;
+    },
+    now() {
+      const d = new Date();
+      const pad = (x) => String(x).padStart(2, '0');
+      return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.query-form { margin-bottom: 4px; }
+.mb8 { margin-bottom: 8px; }
+.pagination-wrap { margin-top: 16px; text-align: right; }
+.pool-tip { font-size: 12px; color: #909399; margin-left: 8px; }
+.danger-text { color: #f56c6c; }
+</style>
diff --git a/src/views/patient/dedicated/tubeFeed/index.vue b/src/views/patient/dedicated/tubeFeed/index.vue
new file mode 100644
index 0000000..e9e2b8a
--- /dev/null
+++ b/src/views/patient/dedicated/tubeFeed/index.vue
@@ -0,0 +1,391 @@
+<template>
+  <div class="app-container">
+    <!-- 鏌ヨ鏍� -->
+    <el-form :inline="true" :model="queryParams" size="small" class="query-form">
+      <el-form-item label="鎮h�呭鍚�">
+        <el-input v-model="queryParams.name" placeholder="璇疯緭鍏ュ鍚�" clearable @keyup.enter.native="handleQuery" />
+      </el-form-item>
+      <el-form-item label="涓绘不鍖荤敓">
+        <el-input v-model="queryParams.drname" placeholder="璇疯緭鍏ュ尰鐢熷鍚�" clearable @keyup.enter.native="handleQuery" />
+      </el-form-item>
+      <el-form-item label="绉戝/鐥呭尯">
+        <el-select v-model="queryParams.deptWard" placeholder="鍏ㄩ儴" clearable filterable>
+          <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-select>
+      </el-form-item>
+      <el-form-item label="绠¢ゲ鏂瑰紡">
+        <el-select v-model="queryParams.feedType" placeholder="鍏ㄩ儴" clearable>
+          <el-option v-for="t in TUBE_FEED_TYPES" :key="t" :label="t" :value="t" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="绠¢ゲ鐘舵��">
+        <el-select v-model="queryParams.feedStatus" placeholder="鍏ㄩ儴" clearable>
+          <el-option v-for="s in TUBE_STATUS" :key="s" :label="s" :value="s" />
+        </el-select>
+      </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-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button type="primary" icon="el-icon-plus" size="mini" @click="openAddDialog">娣诲姞鎮h��</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="danger" icon="el-icon-delete" size="mini" :disabled="multiple" @click="handleDelete">鍒犻櫎</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button type="warning" icon="el-icon-download" size="mini" @click="handleExport">瀵煎嚭</el-button>
+      </el-col>
+    </el-row>
+
+    <!-- 鍒楄〃 -->
+    <el-table :data="currentPageData" v-loading="loading" border @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="50" align="center" />
+      <el-table-column label="濮撳悕" prop="name" min-width="90" align="center" fixed="left" />
+      <el-table-column label="鎬у埆" prop="sex" width="60" align="center" />
+      <el-table-column label="骞撮緞" prop="age" width="70" align="center" />
+      <el-table-column label="鑱旂郴鐢佃瘽" prop="telcode" width="120" align="center" />
+      <el-table-column label="绉戝" prop="deptname" min-width="110" align="center" show-overflow-tooltip />
+      <el-table-column label="鐥呭尯" prop="wardname" min-width="90" align="center" />
+      <el-table-column label="涓绘不鍖荤敓" prop="drname" width="90" align="center" />
+      <el-table-column label="璇婃柇" prop="diagname" min-width="120" align="center" show-overflow-tooltip />
+      <el-table-column label="绠¢ゲ鏂瑰紡" prop="feedType" width="100" align="center" />
+      <el-table-column label="绠¢ゲ寮�濮嬫棩鏈�" prop="feedStartDate" width="120" align="center" />
+      <el-table-column label="钀ュ吇鏂规" prop="nutrition" min-width="140" align="center" show-overflow-tooltip />
+      <el-table-column label="姣忔棩鑳介噺(kcal)" prop="energy" width="110" align="center" />
+      <el-table-column label="绠¢ゲ鐘舵��" prop="feedStatus" width="90" align="center">
+        <template slot-scope="scope">
+          <el-tag :type="scope.row.feedStatus === '宸插仠鐢�' ? 'info' : 'success'" size="small">{{ scope.row.feedStatus }}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="鏉ユ簮" prop="source" width="90" align="center" />
+      <el-table-column label="鍔犲叆鏃堕棿" prop="addTime" width="150" align="center" />
+      <el-table-column label="鎿嶄綔" width="120" align="center" fixed="right">
+        <template slot-scope="scope">
+          <el-button type="text" size="mini" icon="el-icon-view" @click="openDetail(scope.row)">璇︽儏</el-button>
+          <el-button type="text" size="mini" icon="el-icon-close" class="danger-text" @click="removeRow(scope.row)">绉婚櫎</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 鍒嗛〉 -->
+    <el-pagination
+      class="pagination-wrap"
+      background
+      layout="total, sizes, prev, pager, next, jumper"
+      :total="filteredList.length"
+      :current-page.sync="pageNum"
+      :page-size.sync="pageSize"
+      :page-sizes="[10, 20, 50]"
+    />
+
+    <!-- 娣诲姞鎮h�呭脊绐� -->
+    <el-dialog title="娣诲姞涓撶梾鎮h��" :visible.sync="addVisible" width="80%" top="6vh">
+      <el-tabs v-model="activeSource" @tab-click="onSourceTabChange">
+        <el-tab-pane label="鎮h�呮。妗�" name="archive" />
+        <el-tab-pane label="鍑洪櫌璁板綍" name="hospital" />
+        <el-tab-pane label="闂ㄨ瘖璁板綍" name="outpatient" />
+        <el-tab-pane label="鍦ㄩ櫌璁板綍" name="inpatient" />
+      </el-tabs>
+      <el-form :inline="true" size="small">
+        <el-form-item label="濮撳悕/璇婃柇">
+          <el-input v-model="poolKeyword" placeholder="杈撳叆濮撳悕鎴栬瘖鏂叧閿瓧" clearable style="width: 220px" @input="handlePoolSearch" />
+        </el-form-item>
+        <span class="pool-tip">鍏� {{ filteredPool.length }} 鏉★紝宸查�� {{ addSelected.length }} 鏉�</span>
+      </el-form>
+      <el-table ref="addTable" :data="filteredPool" border size="small" max-height="420" @selection-change="onAddSelectionChange">
+        <el-table-column type="selection" width="50" align="center" />
+        <el-table-column label="濮撳悕" prop="name" width="90" align="center" />
+        <el-table-column label="鎬у埆" prop="sex" width="60" align="center" />
+        <el-table-column label="骞撮緞" prop="age" width="70" align="center" />
+        <el-table-column label="鑱旂郴鐢佃瘽" prop="telcode" width="120" align="center" />
+        <el-table-column label="绉戝" prop="deptname" min-width="110" align="center" show-overflow-tooltip />
+        <el-table-column label="鐥呭尯" prop="wardname" min-width="90" align="center" />
+        <el-table-column label="璇婃柇" prop="diagname" min-width="130" align="center" show-overflow-tooltip />
+        <el-table-column label="鍖荤敓" prop="drname" width="90" align="center" />
+        <el-table-column label="鏃ユ湡" prop="sourceDate" width="110" align="center" />
+      </el-table>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="addVisible = false">鍙� 娑�</el-button>
+        <el-button type="primary" @click="confirmAdd">纭畾娣诲姞</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 璇︽儏寮圭獥 -->
+    <el-dialog title="涓撶梾鎮h�呰鎯�" :visible.sync="detailVisible" width="720px">
+      <el-descriptions :column="2" border size="medium" v-if="detailRow">
+        <el-descriptions-item label="濮撳悕">{{ detailRow.name }}</el-descriptions-item>
+        <el-descriptions-item label="鎬у埆">{{ detailRow.sex }}</el-descriptions-item>
+        <el-descriptions-item label="骞撮緞">{{ detailRow.age }}</el-descriptions-item>
+        <el-descriptions-item label="鑱旂郴鐢佃瘽">{{ detailRow.telcode }}</el-descriptions-item>
+        <el-descriptions-item label="绉戝">{{ detailRow.deptname }}</el-descriptions-item>
+        <el-descriptions-item label="鐥呭尯">{{ detailRow.wardname || '鈥�' }}</el-descriptions-item>
+        <el-descriptions-item label="涓绘不鍖荤敓">{{ detailRow.drname }}</el-descriptions-item>
+        <el-descriptions-item label="璇婃柇">{{ detailRow.diagname }}</el-descriptions-item>
+        <el-descriptions-item label="绠¢ゲ鏂瑰紡">{{ detailRow.feedType }}</el-descriptions-item>
+        <el-descriptions-item label="绠¢ゲ寮�濮嬫棩鏈�">{{ detailRow.feedStartDate }}</el-descriptions-item>
+        <el-descriptions-item label="钀ュ吇鏂规">{{ detailRow.nutrition }}</el-descriptions-item>
+        <el-descriptions-item label="姣忔棩鑳介噺">{{ detailRow.energy }} kcal</el-descriptions-item>
+        <el-descriptions-item label="绠¢ゲ鐘舵��">{{ detailRow.feedStatus }}</el-descriptions-item>
+        <el-descriptions-item label="鎮h�呮潵婧�">{{ detailRow.source }}</el-descriptions-item>
+        <el-descriptions-item label="鍔犲叆鏃堕棿">{{ detailRow.addTime }}</el-descriptions-item>
+      </el-descriptions>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+// 绠¢ゲ涓撶梾鎮h�� 鈥斺�� 妯℃嫙鏁版嵁锛屾帴鍙e氨缁悗鏇挎崲 getList / confirmAdd 涓殑鏈湴鏁版嵁閫昏緫
+const PERSONS = [
+  { name: '寮犱紵', sex: '鐢�', age: 68 },
+  { name: '鏉庡', sex: '濂�', age: 55 },
+  { name: '鐜嬪己', sex: '鐢�', age: 72 },
+  { name: '鍒樻晱', sex: '濂�', age: 61 },
+  { name: '闄堥潤', sex: '濂�', age: 66 },
+  { name: '鏉ㄥ啗', sex: '鐢�', age: 59 },
+  { name: '璧典附', sex: '濂�', age: 70 },
+  { name: '榛勫媷', sex: '鐢�', age: 64 },
+  { name: '鍛ㄨ壋', sex: '濂�', age: 58 },
+  { name: '鍚村垰', sex: '鐢�', age: 75 },
+];
+const TUBE_DIAGNOSES = ['鑴戝崚涓�', '鍚炲捊鍥伴毦', '椋熺鐧屾湳鍚�', '鏄忚糠', '閲嶇棁鑲虹値', '杩愬姩绁炵粡鍏冪梾', '棰呰剳鎹熶激', '甯曢噾妫梾', '鑴戝嚭琛�鎭㈠鏈�', '鑲岃悗缂╀晶绱㈢‖鍖�'];
+const DEPTS = ['绁炵粡鍐呯', '搴峰鍖诲绉�', '鑰佸勾绉�', '鍛煎惛鍐呯', '鏅绉�', '閲嶇棁鍖诲绉�', '鍐呭垎娉岀', '娑堝寲鍐呯'];
+const WARDS = ['涓�鐥呭尯', '浜岀梾鍖�', '涓夌梾鍖�', '鍥涚梾鍖�', '浜旂梾鍖�'];
+const DOCTORS = ['鐜嬩富浠�', '鏉庡尰鐢�', '寮犲尰鐢�', '鍒樺尰鐢�', '闄堝尰鐢�'];
+const TUBE_FEED_TYPES = ['榧昏儍绠�', '鑳冮�犵槝绠�', '绌鸿偁閫犵槝绠�', '榧昏偁绠�'];
+const TUBE_STATUS = ['绠¢ゲ涓�', '闂存瓏绠¢ゲ', '宸插仠鐢�'];
+const NUTRITIONS = ['鑲犲唴钀ュ吇涔冲墏(TPF)', '鑲犲唴钀ュ吇娣锋偓娑�(TPF-D)', '鑷埗鍖�娴嗚喅', '鐭偨鍨嬭惀鍏绘恫'];
+const SOURCE_LABEL = { archive: '鎮h�呮。妗�', hospital: '鍑洪櫌璁板綍', outpatient: '闂ㄨ瘖璁板綍', inpatient: '鍦ㄩ櫌璁板綍' };
+
+export default {
+  name: 'TubeFeedDedicated',
+  data() {
+    return {
+      loading: false,
+      // 涓撶梾鎮h�呭垪琛紙宸插姞鍏ワ級
+      patientList: [],
+      ids: [],
+      single: true,
+      multiple: true,
+      // 鏌ヨ鏉′欢
+      queryParams: { name: '', drname: '', deptWard: '', feedType: '', feedStatus: '' },
+      pageNum: 1,
+      pageSize: 10,
+      deptOptions: DEPTS,
+      wardOptions: WARDS,
+      TUBE_FEED_TYPES,
+      TUBE_STATUS,
+      // 娣诲姞寮圭獥
+      addVisible: false,
+      activeSource: 'archive',
+      poolKeyword: '',
+      addSelected: [],
+      mockPool: { archive: [], hospital: [], outpatient: [], inpatient: [] },
+      // 璇︽儏寮圭獥
+      detailVisible: false,
+      detailRow: null,
+    };
+  },
+  computed: {
+    filteredList() {
+      const { name, drname, deptWard, feedType, feedStatus } = this.queryParams;
+      return this.patientList.filter((r) => {
+        if (name && !r.name.includes(name)) return false;
+        if (drname && !r.drname.includes(drname)) return false;
+        if (deptWard) {
+          const label = deptWard.split(':')[1];
+          if (label && r.deptname !== label && r.wardname !== label) return false;
+        }
+        if (feedType && r.feedType !== feedType) return false;
+        if (feedStatus && r.feedStatus !== feedStatus) return false;
+        return true;
+      });
+    },
+    currentPageData() {
+      const start = (this.pageNum - 1) * this.pageSize;
+      return this.filteredList.slice(start, start + this.pageSize);
+    },
+    filteredPool() {
+      const kw = this.poolKeyword.trim();
+      const list = this.mockPool[this.activeSource] || [];
+      if (!kw) return list;
+      return list.filter((r) => r.name.includes(kw) || r.diagname.includes(kw));
+    },
+  },
+  created() {
+    this.buildPool();
+    this.buildInitialList();
+  },
+  methods: {
+    // 鐢熸垚鍚勬潵婧愮殑妯℃嫙鎮h�呮睜
+    buildPool() {
+      const mk = (idx, source, noPrefix) => {
+        const p = PERSONS[idx % PERSONS.length];
+        const isWard = source === 'hospital' || source === 'inpatient';
+        return {
+          id: noPrefix + (idx + 1),
+          name: p.name,
+          sex: p.sex,
+          age: p.age,
+          telcode: '138' + (10000000 + idx * 1111111),
+          deptname: DEPTS[idx % DEPTS.length],
+          wardname: isWard ? WARDS[idx % WARDS.length] : '',
+          drname: DOCTORS[idx % DOCTORS.length],
+          diagname: TUBE_DIAGNOSES[idx % TUBE_DIAGNOSES.length],
+          sourceDate: this.recentDate(idx * 3 + (source === 'outpatient' ? 2 : 5)),
+        };
+      };
+      this.mockPool = {
+        archive: PERSONS.map((_, i) => mk(i, 'archive', 'da')),
+        hospital: PERSONS.map((_, i) => mk(i, 'hospital', 'zy')),
+        outpatient: PERSONS.map((_, i) => mk(i, 'outpatient', 'mz')),
+        inpatient: PERSONS.map((_, i) => mk(i, 'inpatient', 'zy')),
+      };
+    },
+    // 棰勭疆鑻ュ共宸插姞鍏ョ殑鎮h�咃紝淇濊瘉棣栧睆鏈夋暟鎹�
+    buildInitialList() {
+      const take = (source, n) =>
+        this.mockPool[source].slice(0, n).map((row) => this.toDedicated(row, source));
+      this.patientList = [
+        ...take('hospital', 3),
+        ...take('inpatient', 2),
+        ...take('archive', 2),
+        ...take('outpatient', 1),
+      ];
+    },
+    // 姹犳偅鑰� => 涓撶梾鎮h�咃紙琛ュ厖绠¢ゲ鐩稿叧瀛楁锛�
+    toDedicated(row, source) {
+      const n = Number(String(row.id).replace(/\D/g, '')) || 0;
+      return {
+        id: row.id,
+        name: row.name,
+        sex: row.sex,
+        age: row.age,
+        telcode: row.telcode,
+        deptname: row.deptname,
+        wardname: row.wardname,
+        drname: row.drname,
+        diagname: row.diagname,
+        feedType: TUBE_FEED_TYPES[n % TUBE_FEED_TYPES.length],
+        feedStartDate: this.recentDate(n % 10),
+        nutrition: NUTRITIONS[n % NUTRITIONS.length],
+        energy: 1500 + (n % 5) * 100,
+        feedStatus: TUBE_STATUS[n % 2],
+        source: SOURCE_LABEL[source] || source,
+        addTime: this.now(),
+      };
+    },
+    handleQuery() {
+      this.pageNum = 1;
+    },
+    resetQuery() {
+      this.queryParams = { name: '', drname: '', deptWard: '', feedType: '', feedStatus: '' };
+      this.pageNum = 1;
+    },
+    handleSelectionChange(selection) {
+      this.ids = selection.map((item) => item.id);
+      this.single = selection.length !== 1;
+      this.multiple = !selection.length;
+    },
+    openAddDialog() {
+      this.addSelected = [];
+      this.poolKeyword = '';
+      this.activeSource = 'archive';
+      this.addVisible = true;
+      this.$nextTick(() => this.$refs.addTable && this.$refs.addTable.clearSelection());
+    },
+    onSourceTabChange() {
+      this.addSelected = [];
+      this.$nextTick(() => this.$refs.addTable && this.$refs.addTable.clearSelection());
+    },
+    handlePoolSearch() {
+      // 鏈湴杩囨护锛岀敱 filteredPool 璁$畻灞炴�у畬鎴�
+    },
+    onAddSelectionChange(selection) {
+      this.addSelected = selection;
+    },
+    confirmAdd() {
+      if (!this.addSelected.length) {
+        this.$modal.msgWarning('璇峰厛閫夋嫨瑕佹坊鍔犵殑鎮h��');
+        return;
+      }
+      const existing = new Set(this.patientList.map((r) => r.id));
+      const toAdd = this.addSelected
+        .filter((row) => !existing.has(row.id))
+        .map((row) => this.toDedicated(row, this.activeSource));
+      this.patientList = [...toAdd, ...this.patientList];
+      this.addVisible = false;
+      this.$modal.msgSuccess(`鎴愬姛娣诲姞 ${toAdd.length} 浣嶆偅鑰卄);
+    },
+    removeRow(row) {
+      this.$modal
+        .confirm(`鏄惁纭灏嗘偅鑰�"${row.name}"绉诲嚭绠¢ゲ涓撶梾鍒楄〃锛焋)
+        .then(() => {
+          this.patientList = this.patientList.filter((r) => r.id !== row.id);
+          this.$modal.msgSuccess('绉婚櫎鎴愬姛');
+        })
+        .catch(() => {});
+    },
+    handleDelete() {
+      this.$modal
+        .confirm(`鏄惁纭鍒犻櫎閫変腑鐨� ${this.ids.length} 浣嶆偅鑰咃紵`)
+        .then(() => {
+          this.patientList = this.patientList.filter((r) => !this.ids.includes(r.id));
+          this.$modal.msgSuccess('鍒犻櫎鎴愬姛');
+        })
+        .catch(() => {});
+    },
+    openDetail(row) {
+      this.detailRow = row;
+      this.detailVisible = true;
+    },
+    handleExport() {
+      const rows = this.filteredList;
+      if (!rows.length) {
+        this.$modal.msgWarning('鏆傛棤鏁版嵁鍙鍑�');
+        return;
+      }
+      const head = ['濮撳悕', '鎬у埆', '骞撮緞', '鑱旂郴鐢佃瘽', '绉戝', '鐥呭尯', '涓绘不鍖荤敓', '璇婃柇', '绠¢ゲ鏂瑰紡', '绠¢ゲ寮�濮嬫棩鏈�', '钀ュ吇鏂规', '姣忔棩鑳介噺(kcal)', '绠¢ゲ鐘舵��', '鏉ユ簮', '鍔犲叆鏃堕棿'];
+      const keys = ['name', 'sex', 'age', 'telcode', 'deptname', 'wardname', 'drname', 'diagname', 'feedType', 'feedStartDate', 'nutrition', 'energy', 'feedStatus', 'source', 'addTime'];
+      const csv = [head.join(',')]
+        .concat(rows.map((r) => keys.map((k) => `"${(r[k] ?? '').toString().replace(/"/g, '""')}"`).join(',')))
+        .join('\n');
+      const blob = new Blob(['锘�' + csv], { type: 'text/csv;charset=utf-8;' });
+      const link = document.createElement('a');
+      link.href = URL.createObjectURL(blob);
+      link.download = `绠¢ゲ涓撶梾鎮h�卂${Date.now()}.csv`;
+      link.click();
+      URL.revokeObjectURL(link.href);
+    },
+    recentDate(daysAgo) {
+      const d = new Date();
+      d.setDate(d.getDate() - daysAgo);
+      const mm = String(d.getMonth() + 1).padStart(2, '0');
+      const dd = String(d.getDate()).padStart(2, '0');
+      return `${d.getFullYear()}-${mm}-${dd}`;
+    },
+    now() {
+      const d = new Date();
+      const pad = (x) => String(x).padStart(2, '0');
+      return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
+    },
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.query-form { margin-bottom: 4px; }
+.mb8 { margin-bottom: 8px; }
+.pagination-wrap { margin-top: 16px; text-align: right; }
+.pool-tip { font-size: 12px; color: #909399; margin-left: 8px; }
+.danger-text { color: #f56c6c; }
+</style>
diff --git a/src/views/repositoryai/templateku/configurat/index.vue b/src/views/repositoryai/templateku/configurat/index.vue
index a34f016..c6c0700 100644
--- a/src/views/repositoryai/templateku/configurat/index.vue
+++ b/src/views/repositoryai/templateku/configurat/index.vue
@@ -533,7 +533,7 @@
                   <el-col :span="12"
                     ><el-form-item label="鏄惁蹇呭~">
                       <el-select
-                        v-model="topicobj.isMust"
+                        v-model="topicobj.ismandatory"
                         placeholder="榛樿蹇呭~"
                       >
                         <el-option
@@ -1086,7 +1086,7 @@
                     <el-col :span="12"
                       ><el-form-item label="鏄惁蹇呭~">
                         <el-select
-                          v-model="indexform.isMust"
+                          v-model="indexform.ismandatory"
                           placeholder="榛樿蹇呭~"
                         >
                           <el-option
diff --git "a/\351\232\217\350\256\277\345\244\226\351\223\276\351\200\232\347\224\2509.12.zip" "b/\351\232\217\350\256\277\345\244\226\351\223\276\351\200\232\347\224\2509.12.zip"
new file mode 100644
index 0000000..e05720c
--- /dev/null
+++ "b/\351\232\217\350\256\277\345\244\226\351\223\276\351\200\232\347\224\2509.12.zip"
Binary files differ
diff --git "a/\351\232\217\350\256\277\351\200\232\347\224\250 9.14.zip" "b/\351\232\217\350\256\277\351\200\232\347\224\250 9.14.zip"
new file mode 100644
index 0000000..87b3f17
--- /dev/null
+++ "b/\351\232\217\350\256\277\351\200\232\347\224\250 9.14.zip"
Binary files differ

--
Gitblit v1.9.3