Forráskód Böngészése

修复入院评估问题2

chenjun 3 hete
szülő
commit
4de8fc17fb

+ 113 - 0
.workbuddy/memory/2026-09-16.md

@@ -0,0 +1,113 @@
+# 2026-09-16 工作日志
+
+## 能力评估新增「评估编号」自定义输入 + 自动生成
+
+### 需求
+评估弹窗第一步(评估基本信息)中,在「长者姓名」上方新增一行「评估编号」:
+- 可手动输入
+- 新增时默认自动生成
+- 打印预览(Print.vue)已支持 `evaluationCode`,需保证数据落库后正常回显
+
+### 改动文件
+
+1. **`src/views/elderly/assess/types.ts`**
+   - 在 `syntheticAbilityFormType.dataForm` 中增加 `evaluationCode?: string`。
+
+2. **`src/views/elderly/assess/synthetic-ability/Form.vue`**
+   - 默认表单对象增加 `evaluationCode: ''`。
+   - 模板在「长者姓名」上方新增一行「评估编号」`el-input`(`maxlength="64"`)。
+   - 新增 `generateEvaluationCode()`:格式 `NLPG + YYYYMMDD + 4位随机数`(与打印预览已有示例一致)。
+   - `open(tId, id)`:无 `id`(新增)时自动生成默认编号;有 `id`(编辑)时由后端 `res.evaluationCode` 回填。
+   - `handleChangeEvaluationAgency` 切换评估机构时保留 `evaluationCode`,避免编号被清空。
+
+## 能力评估:修复评估日期不回显 + 评估编号必填
+
+### 问题
+- 编辑已保存记录时,「评估日期」字段为空(后端返回的日期格式可能带时间或时间戳,`el-date-picker` 无法直接匹配)。
+- 「评估编号」需要必填校验。
+
+### 改动文件
+
+**`src/views/elderly/assess/synthetic-ability/Form.vue`**
+- 引入 `dayjs`,新增 `formatDateYMD()` helper,把后端返回的日期统一格式化为 `YYYY-MM-DD`。
+- 编辑回填 `evaluatorDate` 时走 `formatDateYMD()`,确保 `el-date-picker` 能正确显示。
+- `dataRule` 增加 `evaluationCode` 必填规则(`trigger: 'blur'`)。
+- `handleChangeEvaluationAgency` 切换评估机构时额外保留 `evaluatorDate`,避免日期被重置。
+
+### 验证
+- 全量 `vue-tsc --noEmit`:本次改动未引入新的类型错误;报出的错误均为 `synthetic-ability` 目录下既有历史问题(`ref({})` 无显式类型、FCForm 的 `never` 推断等),与本次改动无关。
+
+## 能力评估:评估员签名改为可编辑 + 日期取评估基准日期
+
+### 需求
+1. 打印页「评估员签名」一行上下边距加高。
+2. 打印页「日期」取值由 `createDate` 改为「评估基准日期」`evaluatorDate`。
+3. 第五步「评估报告」新增「评估员签名」输入框,可编辑,默认当前账号昵称;清空后打印时显示为空。
+
+### 改动文件
+
+1. **`src/views/elderly/assess/types.ts`**
+   - `syntheticAbilityFormType.dataForm` 与 `syntheticAbilityDetailType` 增加 `evaluator: string`。
+
+2. **`src/views/elderly/assess/synthetic-ability/Form.vue`**
+   - 默认表单对象增加 `evaluator: nickname`(当前账号昵称)。
+   - `open(id)` 编辑回填:后端有 `res.evaluator` 则回填,否则默认 `nickname`。
+   - `handleChangeEvaluationAgency` 切换机构时保留 `evaluator`。
+   - `<Result>` 组件调用改为 `v-model:evaluator="dataForm.evaluator"`。
+   - `submitForm('1')` 移除旧的 `base64Str` 签名逻辑,直接提交 `dataForm.evaluator`。
+
+3. **`src/views/elderly/assess/synthetic-ability/components/Result.vue`**
+   - 新增 prop `evaluator` 与 `emit('update:evaluator')`。
+   - 新增 `evaluatorSign` ref,通过 `watch` 接收回填值。
+   - 在本院评估(type=0)与广州标准(type=1)两种报告底部各增加一行「评估员签名」输入框。
+
+4. **`src/views/elderly/assess/synthetic-ability/Print.vue`**
+   - 新增 `evaluatorDateList` computed,从 `dataForm.evaluatorDate` 拆出年月日。
+   - 两套打印签名模板都把签名值改为 `dataForm.evaluator`,日期改为 `evaluatorDateList`。
+   - 评估员签名行 `style="padding: 16px 0;"`,家属签名行保持不变。
+
+### 验证
+- 全量 `vue-tsc --noEmit` 跑完:本次改动前相关文件报错 234 条,改动后降至 231 条;新增的唯一可见报错是 `Print.vue` 中 `dataForm.evaluator` 访问(源于 `const dataForm = ref({})` 无显式类型,同文件已有大量同类历史报错),其余为既有历史问题,与本次业务改动无关。
+
+## 能力评估:修复「信息提供者姓名」保存后回显为登录账号的问题
+
+### 问题
+编辑「信息提供者姓名」后保存,再次打开回显仍显示当前登录账号昵称,无法修改。
+
+### 根因
+`Form.vue` 的 `submitForm` 中构建提交参数时,硬编码了 `informationProviderName: nickname`,覆盖了用户在表单中输入的值。
+
+### 改动文件
+
+**`src/views/elderly/assess/synthetic-ability/Form.vue`**
+- 移除 `obj` 中的 `informationProviderName: nickname` 硬编码,让 `...dataForm.value` 中的用户输入值正常提交。
+
+### 验证
+- 再次编辑保存后,`informationProviderName` 应正确落库并按用户输入值回显。
+
+## 能力评估:修复列表「评估员姓名」列错取「信息提供者姓名」的问题
+
+### 问题
+能力评估列表中「评估员姓名」列显示的是 `informationProviderName`(信息提供者姓名)的值,导致列表与表单字段含义不一致。
+
+### 根因
+`src/views/elderly/assess/column.ts` 中「评估员姓名」列的 `field` 错配为 `informationProviderName`。
+
+### 改动文件
+
+**`src/views/elderly/assess/column.ts`**
+- 将「评估员姓名」列的 `field` 从 `informationProviderName` 改为 `evaluator`,与表单中新增的「评估员签名」字段保持一致。
+
+### 验证
+- 列表刷新后,「评估员姓名」列应显示 `evaluator` 字段的值(默认当前账号昵称,可在第五步评估报告修改)。
+
+## 能力评估:打印页评估员签名横线加长
+
+### 需求
+打印页「评估员签名」处的横线太短,需要加长,与下方「家属签名」横线长度保持一致。
+
+### 改动文件
+
+**`src/views/elderly/assess/synthetic-ability/Print.vue`**
+- 将两套打印模板(`templateType == 8` 与 `templateType != 8`)中的 `评估员签名______{{ dataForm.evaluator }}______` 改为 `评估员签名____________________{{ dataForm.evaluator }}____________________`。
+- 横线长度与「家属签名____________________」对齐,有签名值时内容居中显示在横线之上。

+ 1 - 1
src/views/elderly/assess/column.ts

@@ -67,7 +67,7 @@ export const SyntheticAbilityColumns = reactive([
 
   {
     label: '评估员姓名',
-    field: 'informationProviderName'
+    field: 'evaluator'
   },
   // {
   //   label: '家属签名状态',

+ 42 - 7
src/views/elderly/assess/synthetic-ability/Form.vue

@@ -74,6 +74,13 @@
                   }}</el-button>
                 </el-form-item>
               </template>
+              <el-row :gutter="30">
+                <el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
+                  <el-form-item label="评估编号" prop="evaluationCode">
+                    <el-input v-model="dataForm.evaluationCode" placeholder="请输入评估编号" maxlength="64" />
+                  </el-form-item>
+                </el-col>
+              </el-row>
               <el-row :gutter="30">
                 <el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
                   <el-form-item label="长者姓名" prop="elderId">
@@ -653,7 +660,7 @@
           <div class="fifth" v-if="showViews()?active==4:active == 2">
             <div class="info-title">评估报告</div>
             <div class="info-wrap">
-              <Result :nurseLevelName="dataForm.nurseLevelName" :recommendedNursingLevel="dataForm.recommendedNursingLevel" :type="showViews2()" ref="resultRef" :templates="fcFormRefs" :nurseLevelList="nurseLevelList" :syntheticAbilityId="dataForm.id" :status="dataForm.status"/>
+              <Result :nurseLevelName="dataForm.nurseLevelName" :recommendedNursingLevel="dataForm.recommendedNursingLevel" v-model:evaluator="dataForm.evaluator" :type="showViews2()" ref="resultRef" :templates="fcFormRefs" :nurseLevelList="nurseLevelList" :syntheticAbilityId="dataForm.id" :status="dataForm.status"/>
             </div>
           </div>
         </el-form>
@@ -689,6 +696,7 @@
 
 <script lang="ts" setup>
 import { computed, ref } from 'vue'
+import dayjs from 'dayjs'
 import { DICT_TYPE, getIntDictOptions, getStrDictOptions, getDictOptions } from '@/utils/dict'
 import {
   getEvaluationProcessById,
@@ -723,6 +731,7 @@ const stateForm = reactive<syntheticAbilityFormType>({
     evaluationAgency: '1', // 1本院评估, 2第三方评估
     thirdPartyName: '', // 第三方名称
     evaluateReasons: '3', //评估原因,默认常规评估
+    evaluationCode: '', // 评估编号
     questionnaireName: '', // 能力评估表
     questionnaireId: '', // 评估表id
     questionnaireType: '', // 评估类型
@@ -738,6 +747,7 @@ const stateForm = reactive<syntheticAbilityFormType>({
     liveSituation: '',
     economySource: '',
     medicalInsurance: '',
+    evaluator: nickname, // 评估员签名,默认当前账号
     idCard: '', // 证件号码
     elderSex: undefined, // 性别
     birthday: undefined, // 出生日期
@@ -782,6 +792,7 @@ const stateForm = reactive<syntheticAbilityFormType>({
     contactPhone: ''
   },
   dataRule: {
+    evaluationCode: [{ required: true, message: '评估编号不能为空', trigger: 'blur' }],
     elderId: [{ required: true, message: '长者姓名不能为空', trigger: 'change' }],
     evaluatorDate: [{ required: true, message: '评估日期不能为空', trigger: 'change' }],
     evaluateReasons: [{ required: true, message: '评估原因不能为空', trigger: 'change' }],
@@ -954,9 +965,30 @@ const resetCheckBox = (arg, type, input1? : string, input2?: string) => {
   }
 }
 
+/** 把后端返回的日期统一格式化为 YYYY-MM-DD,兼容字符串、时间戳、Date */
+const formatDateYMD = (raw: any): string => {
+  if (!raw) return ''
+  const d = dayjs(raw)
+  return d.isValid() ? d.format('YYYY-MM-DD') : ''
+}
+
+/** 生成默认评估编号:NLPG + YYYYMMDD + 4位随机数 */
+const generateEvaluationCode = () => {
+  const now = new Date()
+  const dateStr = now.getFullYear().toString() +
+    String(now.getMonth() + 1).padStart(2, '0') +
+    String(now.getDate()).padStart(2, '0')
+  const random = Math.floor(Math.random() * 10000).toString().padStart(4, '0')
+  return `NLPG${dateStr}${random}`
+}
+
 /** 打开弹窗 */
 const open = async (tId, id) => {
   resetForm()
+  // 新增时自动生成默认评估编号;编辑时由后端数据回填
+  if (!id) {
+    dataForm.value.evaluationCode = generateEvaluationCode()
+  }
   // 意外事件默认选中"无"
   const accidentNone = getStrDictOptions(DICT_TYPE.ACCIDENT_ARR).find((d) => d.label === '无')
   if (accidentNone) {
@@ -1014,7 +1046,7 @@ const open = async (tId, id) => {
         elderId: Number(res.elderId),
         evaluationAgency: res.evaluationAgency ? res.evaluationAgency : '1', // 1本院评估, 2第三方评估
         // 修改时保留原评估日期;仅新建/草稿(status==0 且无日期)才默认今天
-        evaluatorDate: res.evaluatorDate ? res.evaluatorDate : new Date().toISOString().substr(0, 10),
+        evaluatorDate: formatDateYMD(res.evaluatorDate) || new Date().toISOString().substr(0, 10),
         diseaseDiagnosis: res.diseaseDiagnosis ? str2arr(res.diseaseDiagnosis, 'diseaseDiagnosisInput') : [],
         medicationSituation: parseArr(res.medicationSituation),
         absenceOfTeeth: parseArr(res.absenceOfTeeth),
@@ -1038,6 +1070,8 @@ const open = async (tId, id) => {
         liveSituation: res.elder.liveSituation,
         economySource: res.elder.economySource,
         medicalInsurance: res.elder.medicalInsurance,
+        // 评估员签名:后端有值则回填,否则默认当前账号昵称
+        evaluator: res.evaluator || nickname,
       }
 
       if(res.motionOfJoint && res.motionOfJoint.indexOf('-') > -1){
@@ -1118,9 +1152,7 @@ const submitForm = async (type: string) => {
   const params = await validateAllForm()
   const allNum = params.syntheticAbilityInfoList.reduce((pre, cur)=> pre + cur.score, 0 )
   const obj = {
-    evaluator: '',
     ...dataForm.value,
-    informationProviderName:nickname,
     processSequenceList: undefined,
     syntheticAbilityInfoList: params.syntheticAbilityInfoList,
     totalScore: allNum,
@@ -1144,9 +1176,9 @@ const submitForm = async (type: string) => {
     formLoading.value = true
     // TODO 看要不要调用保存的接口
     try {
-      if(resultRef.value && resultRef.value.base64Str){
-        obj.evaluator = resultRef.value.base64Str
-      }
+      // 评估员签名:dataForm.evaluator 已通过 v-model 与 Result 输入框同步;
+      // 若用户清空输入框,则打印时展示为空
+      obj.evaluator = dataForm.value.evaluator || ''
       // 修改时保留原记录状态(status),避免把已签字(status=2)等记录误改回 1;
       // 初次评估(status==0)保存后自然变为 1
       obj.status = dataForm.value.status || 1
@@ -1333,6 +1365,8 @@ const handleChangeEvaluationAgency = (type: string) => {
   let tempDate = JSON.parse(JSON.stringify(dataForm.value))
   resetForm()
   dataForm.value.evaluationAgency = type
+  dataForm.value.evaluationCode = tempDate.evaluationCode
+  dataForm.value.evaluatorDate = tempDate.evaluatorDate
   dataForm.value.elderId = tempDate.elderId
   dataForm.value.papersType = tempDate.papersType
   dataForm.value.idCard = tempDate.idCard
@@ -1347,6 +1381,7 @@ const handleChangeEvaluationAgency = (type: string) => {
   dataForm.value.liveSituation = tempDate.liveSituation
   dataForm.value.economySource = tempDate.economySource
   dataForm.value.medicalInsurance = tempDate.medicalInsurance
+  dataForm.value.evaluator = tempDate.evaluator
   dataForm.value.evaluateReasons = tempDate.evaluateReasons
   dataForm.value.questionnaireId = tempDate.questionnaireId
   dataForm.value.questionnaireName = tempDate.questionnaireName

+ 15 - 10
src/views/elderly/assess/synthetic-ability/Print.vue

@@ -382,12 +382,12 @@
               <tr>
                 <td colspan="3">
                   <div class="flex-column">
-                    <div class="flex">
-                      <span>评估员签名______{{ dataForm.informationProviderName }}______</span>
+                    <div class="flex" style="padding: 16px 0;">
+                      <span>评估员签名____________________{{ dataForm.evaluator }}____________________</span>
                       <span
-                        >日期___{{ dateList.length > 0 ? dateList[0] : '' }}___年___{{
-                          dateList.length > 0 ? dateList[1] : ''
-                        }}___月___{{ dateList.length > 0 ? dateList[2] : '' }}___日</span
+                        >日期___{{ evaluatorDateList[0] }}___年___{{
+                          evaluatorDateList[1]
+                        }}___月___{{ evaluatorDateList[2] }}___日</span
                       >
                     </div>
                     <!--                    <div class="flex">-->
@@ -505,12 +505,12 @@
           <tr>
             <td colspan="3">
               <div class="flex-column">
-                <div class="flex">
-                  <span>评估员签名______{{ dataForm.informationProviderName }}______</span>
+                <div class="flex" style="padding: 16px 0;">
+                  <span>评估员签名____________________{{ dataForm.evaluator }}____________________</span>
                   <span
-                    >日期___{{ dateList.length > 0 ? dateList[0] : '' }}___年___{{
-                      dateList.length > 0 ? dateList[1] : ''
-                    }}___月___{{ dateList.length > 0 ? dateList[2] : '' }}___日</span
+                    >日期___{{ evaluatorDateList[0] }}___年___{{
+                      evaluatorDateList[1]
+                    }}___月___{{ evaluatorDateList[2] }}___日</span
                   >
                 </div>
                 <div class="flex">
@@ -667,6 +667,11 @@ const dataForm = ref({}) // 表单字段
 const loading = ref(false) // 表单的加载中
 const temArr = ref([])
 const dateList = ref([])
+// 评估员签名一行的日期取「评估基准日期」
+const evaluatorDateList = computed(() => {
+  const d = (dataForm.value as any).evaluatorDate ? formatDate((dataForm.value as any).evaluatorDate, 'YYYY-MM-DD') : ''
+  return d ? d.split('-') : ['', '', '']
+})
 const descArr = ref([
   {
     label: '能力完好',

+ 25 - 0
src/views/elderly/assess/synthetic-ability/components/Result.vue

@@ -197,6 +197,10 @@
     <!--          </template>-->
     <!--        </div>-->
     <!--      </div>-->
+    <div class="left">评估员签名</div>
+    <div class="right">
+      <TgInput v-model="evaluatorSign" placeholder="请输入评估员签名" />
+    </div>
   </div>
   <div v-if="props.type == 1" class="result-page">
     <div class="left">
@@ -290,6 +294,10 @@
         props.templates[0].totalRecordList[7].value
       }}分)
     </div>
+    <div class="left">评估员签名</div>
+    <div class="right">
+      <TgInput v-model="evaluatorSign" placeholder="请输入评估员签名" />
+    </div>
   </div>
 </template>
 <script setup lang="ts">
@@ -338,10 +346,17 @@ const props = defineProps({
   recommendedNursingLevel: {
     type: [String, Number],
     default: ''
+  },
+  evaluator: {
+    type: String,
+    default: ''
   }
 })
 
+const emit = defineEmits(['update:evaluator'])
+
 const nursingLevel = ref<string | number>('')
+const evaluatorSign = ref('')
 
 watch(() => props.recommendedNursingLevel, (val) => {
   if (val === '' || val == null) return
@@ -351,6 +366,16 @@ watch(() => props.recommendedNursingLevel, (val) => {
   nursingLevel.value = matched ? (matched as any).id : val
 }, { immediate: true })
 
+watch(() => props.evaluator, (val) => {
+  if (val !== undefined && val !== null) {
+    evaluatorSign.value = String(val)
+  }
+}, { immediate: true })
+
+watch(evaluatorSign, (val) => {
+  emit('update:evaluator', val)
+})
+
 const totalNum = computed(() => {
   let num = 0
   props.templates.map((item) => {

+ 8 - 5
src/views/elderly/assess/types.ts

@@ -27,6 +27,7 @@ export interface syntheticAbilityFormType {
     evaluationAgency: string // 1本院评估, 2第三方评估
     thirdPartyName: undefined | string // 第三方名称
     evaluateReasons: undefined | string //评估原因
+    evaluationCode: undefined | string // 评估编号
     questionnaireName: undefined | string // 能力评估表
     questionnaireId: undefined | string // 评估表id
     questionnaireType: undefined | string // 评估表id
@@ -77,6 +78,7 @@ export interface syntheticAbilityFormType {
     relation: string
     contactName: string
     contactPhone: string
+    evaluator: string // 评估员签名
     status: number
     nurseLevelName: string
   }
@@ -167,11 +169,12 @@ export interface syntheticAbilityDetailType {
   liveSituation: string
   economySource: string
   medicalInsurance: string
-  informationProviderName: string
-  relation: string
-  contactName: string
-  contactPhone: string
-  status: number
+    informationProviderName: string
+    relation: string
+    contactName: string
+    contactPhone: string
+    evaluator: string // 评估员签名
+    status: number
 }
 
 export interface syntheticAbilityDetailTitleType {

+ 111 - 166
src/views/operation-analysis/overview/campus-data.ts

@@ -1,7 +1,6 @@
 import request from '@/config/axios'
 import { getBuildList, getBuildData } from '@/api/system/badManage'
 import { getElderlyPage, getWeeklyOperationSummary } from '@/api/elderly/elder/elderly-Info'
-import { getInstitutionDashboard } from '@/api/login'
 import { getTenantId } from '@/utils/auth'
 import { useUserStore } from '@/store/modules/user'
 
@@ -173,91 +172,42 @@ export const mergeNameCount = (list: NameCountItem[]): NameCountItem[] => {
     .sort((a, b) => b.count - a.count)
 }
 
-/**
- * 运营总览动态指标聚合接口开关
- * 接口需求见同目录《运营总览-后端接口需求.md》;后端开发完成并联调通过后置为 true
- */
-export const OVERVIEW_DYNAMICS_API_READY = false
-
-/** 运营总览动态指标(新聚合接口返回,结构对齐工作台 getInstitutionDashboard,口径见接口文档) */
-export interface OverviewDynamics {
-  /** 今日实际完成入住办理人数(不含预订、审批中) */
-  todayCheckInCount: number | null
-  /** 今日完成退院人数 */
-  todayCheckOutCount: number | null
-  /** 当前请假在外未返院人数 */
-  leaveOutCount: number | null
-  /** 当天请假人数 */
-  todayLeaveCount: number | null
-  /** 在院长者护理等级分布(已按同名合并) */
-  nursingLevels: NameCountItem[]
-  /** 运营周期(周/月)汇总 */
-  weekly: Record<string, any> | null
+/** 近 12 个月月份项(yyyy-MM + 展示标签),按时间升序 */
+export interface MonthCountItem {
+  /** yyyy-MM */
+  month: string
+  /** 展示标签 M月 */
+  label: string
+  count: number
 }
 
 const pad2 = (n: number) => String(n).padStart(2, '0')
 
-const formatDay = (date: Date) =>
-  `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`
-
 /**
- * 在院人数:长者档案在院状态(inStatus=1)总数,按所选院区过滤
+ * 生成近 months 个月(含当月)的月份骨架,用于退住趋势零填充
  */
-export const fetchInHospitalCount = async (tenantIds: number[]): Promise<number | null> => {
-  try {
-    const res: any = await getElderlyPage({
-      tenantIds,
-      inStatus: 1,
-      pageNo: 1,
-      pageSize: 1,
-      orgType: 1
-    })
-    return Number(res?.total) || 0
-  } catch {
-    return null
-  }
-}
-
-/**
- * 今日退住人数:退住流程已完成(status=2)且退住日期为今天
- * 过渡期使用业务列表接口兜底,后续由动态聚合接口统一返回
- */
-export const fetchTodayCheckOutCount = async (tenantIds: number[]): Promise<number | null> => {
-  try {
-    const day = formatDay(new Date())
-    const res: any = await request.get({
-      url: '/bpm/process-instance/elderly-retreat-page',
-      params: { tenantIds, retreatDate: [day, day], status: 2, pageNo: 1, pageSize: 1 }
-    })
-    return Number(res?.total) || 0
-  } catch {
-    return null
-  }
-}
-
-/**
- * 轮候待住人数:入住申请中处于轮候(排队等床)状态的人数,按所选院区过滤
- */
-export const fetchCheckInWaitCount = async (tenantIds: number[]): Promise<number | null> => {
-  try {
-    const res: any = await request.get({
-      url: '/bpm/process-instance/check-in-wait-page',
-      params: { tenantIds, pageNo: 1, pageSize: 1 }
+const buildRecentMonths = (months: number): MonthCountItem[] => {
+  const now = new Date()
+  const list: MonthCountItem[] = []
+  for (let i = months - 1; i >= 0; i--) {
+    const d = new Date(now.getFullYear(), now.getMonth() - i, 1)
+    list.push({
+      month: `${d.getFullYear()}-${pad2(d.getMonth() + 1)}`,
+      label: `${d.getMonth() + 1}月`,
+      count: 0
     })
-    return Number(res?.total) || 0
-  } catch {
-    return null
   }
+  return list
 }
 
 /**
- * 预订在途人数:长者档案状态为预订(inStatus=0,尚未实际入住)的人数
+ * 在院人数:长者档案在院状态(inStatus=1)总数,按所选院区过滤
  */
-export const fetchReservedCount = async (tenantIds: number[]): Promise<number | null> => {
+export const fetchInHospitalCount = async (tenantIds: number[]): Promise<number | null> => {
   try {
     const res: any = await getElderlyPage({
       tenantIds,
-      inStatus: 0,
+      inStatus: 1,
       pageNo: 1,
       pageSize: 1,
       orgType: 1
@@ -268,59 +218,6 @@ export const fetchReservedCount = async (tenantIds: number[]): Promise<number |
   }
 }
 
-export interface MonthCountItem {
-  /** yyyy-MM */
-  month: string
-  /** 展示标签 M月 */
-  label: string
-  count: number
-}
-
-/**
- * 近 N 个月已完成退住人数趋势(退住流程 status=2,按退住日期归属月份)
- * 逐月查询 total,避免大分页拉取明细
- */
-export const fetchMonthlyCheckOutTrend = async (
-  tenantIds: number[],
-  months = 12
-): Promise<MonthCountItem[]> => {
-  const now = new Date()
-  const list: { month: string; label: string; first: string; last: string }[] = []
-  for (let i = months - 1; i >= 0; i--) {
-    const d = new Date(now.getFullYear(), now.getMonth() - i, 1)
-    const y = d.getFullYear()
-    const m = d.getMonth()
-    const first = `${y}-${pad2(m + 1)}-01`
-    const lastDay = new Date(y, m + 1, 0).getDate()
-    const last = `${y}-${pad2(m + 1)}-${pad2(lastDay)}`
-    list.push({ month: `${y}-${pad2(m + 1)}`, label: `${m + 1}月`, first, last })
-  }
-
-  const counts = await runWithConcurrency(list, 4, async (item) => {
-    try {
-      const res: any = await request.get({
-        url: '/bpm/process-instance/elderly-retreat-page',
-        params: {
-          tenantIds,
-          retreatDate: [item.first, item.last],
-          status: 2,
-          pageNo: 1,
-          pageSize: 1
-        }
-      })
-      return Number(res?.total) || 0
-    } catch {
-      return 0
-    }
-  })
-
-  return list.map((item, index) => ({
-    month: item.month,
-    label: item.label,
-    count: counts[index]
-  }))
-}
-
 /**
  * 逐院区查询在院人数(长者档案人员口径,区别于楼栋"在住床位"口径)
  * 返回 tenantId -> 在院人数(查询失败院区为 null)
@@ -336,11 +233,8 @@ export const fetchCampusInHospitalMap = async (
 
 /**
  * 是否为"只查看登录机构自身"的单院区场景。
- * 该场景下工作台现网两个接口返回的就是本院数据,可直接聚合使用:
- * - getInstitutionDashboard:院内动态 + 护理情况
- * - getWeeklyOperationSummary:运营周期汇总(本就只按当前登录机构返回)
- * 集团账号不满足(getTenantId 为集团 id,即使只选一个院区,两接口仍返回集团全量),
- * 必须等待多院区聚合接口。
+ * 运营周期汇总接口 getWeeklyOperationSummary 只按当前登录机构返回:
+ * 机构账号查看自身时口径正确可直接使用;集团账号(getTenantId 为集团 id)不满足。
  */
 const isOwnSingleCampus = (tenantIds: number[]): boolean => {
   if (!tenantIds || tenantIds.length !== 1) return false
@@ -348,54 +242,105 @@ const isOwnSingleCampus = (tenantIds: number[]): boolean => {
   return !!loginTenantId && loginTenantId === tenantIds[0]
 }
 
-/**
- * 将工作台看板数据(现网接口或新聚合接口同构返回)统一解析为 OverviewDynamics。
- * hospitalDynamics 为固定名称的 {name,count} 数组,按 name 取值;
- * nursingSituation 前端再做一次同名合并兜底(现网同机构内也可能出现重名)。
- */
-const parseOverviewDynamics = (dash: any, weekly: Record<string, any> | null): OverviewDynamics => {
-  const dynamics: NameCountItem[] = Array.isArray(dash?.hospitalDynamics)
-    ? dash.hospitalDynamics
-    : []
-  const pickCount = (name: string): number | null => {
-    const hit = dynamics.find((item) => item.name === name)
-    return hit ? Number(hit.count) || 0 : null
-  }
-  return {
-    todayCheckInCount: pickCount('新收人数'),
-    todayCheckOutCount: pickCount('退院人数'),
-    leaveOutCount: pickCount('请假人数'),
-    todayLeaveCount: pickCount('当天请假人数'),
-    nursingLevels: Array.isArray(dash?.nursingSituation) ? dash.nursingSituation : [],
-    weekly: weekly ?? null
-  }
+/** 后端 dynamics 接口单个机构的原始返回 */
+interface OverviewDynamicsCampusVO {
+  tenantId: number
+  tenantName: string
+  newCheckInCount?: number
+  checkOutCount?: number
+  askLeaveCount?: number
+  todayAskLeaveCount?: number
+  waitingCount?: number
+  reservationCount?: number
+  retreatTrend?: { month: string; count: number }[]
+  nurseLevelDistribution?: { nurseLevelId?: number; nurseLevelName?: string; count?: number }[]
+}
+
+/** 运营总览动态指标(多院区已在前端聚合求和) */
+export interface OverviewDynamics {
+  /** 今日实际完成入住办理人数 */
+  todayCheckInCount: number
+  /** 今日完成退院人数 */
+  todayCheckOutCount: number
+  /** 当前请假在外未返院人数(含往日请假未归) */
+  leaveOutCount: number
+  /** 当天请假人数(当天离院) */
+  todayLeaveCount: number
+  /** 轮候待住人数 */
+  waitingCount: number
+  /** 预订在途人数 */
+  reservationCount: number
+  /** 近 12 个月已完成退住趋势(多院区按月求和) */
+  checkoutTrend: MonthCountItem[]
+  /** 在院长者护理等级分布(跨院区按同名合并,降序) */
+  nursingLevels: NameCountItem[]
+  /** 运营周期(周/月)汇总;仅登录机构查看自身时有值,多院区待后端扩展 */
+  weekly: Record<string, any> | null
 }
 
 /**
- * 动态指标聚合(今日新收/请假/当天请假/护理等级/周期汇总)
- * - 机构账号查看自身:直接复用工作台现网两个接口(数据真实可得)
- * - 多院区/集团账号:依赖后端新聚合接口,未上线返回 null,页面显示 "-",严禁用其他口径数据填充
+ * 运营总览动态指标聚合:GET /operation-analysis/overview/dynamics
+ * 后端按机构分组返回(每机构一项),这里做跨院区聚合:
+ * - 数值指标求和;
+ * - retreatTrend 按月求和并按近 12 个月骨架零填充;
+ * - nurseLevelDistribution 按护理等级**名称**跨院区同名合并
+ *   (各院区等级字典独立、id 不通用,实测同名"一级护理"存在多个不同 id)。
+ *
+ * 注意:该接口不传 tenantIds 时返回的机构集合大于账号菜单可见范围(实测 29 家),
+ * 因此必须始终显式传入选区 tenantIds。
  */
 export const fetchOverviewDynamics = async (
   tenantIds: number[]
 ): Promise<OverviewDynamics | null> => {
+  if (!tenantIds || tenantIds.length === 0) return null
   try {
-    // 场景 1:登录机构只查看自身,现网接口口径正确
+    const list: OverviewDynamicsCampusVO[] = await request.get({
+      url: '/operation-analysis/overview/dynamics',
+      params: { tenantIds }
+    })
+    if (!Array.isArray(list)) return null
+
+    const sum = (key: keyof OverviewDynamicsCampusVO) =>
+      list.reduce((acc, item) => acc + (Number(item[key]) || 0), 0)
+
+    // 退住趋势:按月合并
+    const trend = buildRecentMonths(12)
+    const monthMap = new Map(trend.map((item) => [item.month, item]))
+    list.forEach((item) => {
+      ;(item.retreatTrend || []).forEach((row) => {
+        const target = monthMap.get(row.month)
+        if (target) target.count += Number(row.count) || 0
+      })
+    })
+
+    // 护理等级:各院区条目按同名合并
+    const nursingLevels = mergeNameCount(
+      list.flatMap((item) =>
+        (item.nurseLevelDistribution || []).map((row) => ({
+          name: row.nurseLevelName || '未设置',
+          count: Number(row.count) || 0
+        }))
+      )
+    )
+
+    // 运营周期汇总:仅单机构自身场景取现网接口;多院区后端尚未提供
+    let weekly: Record<string, any> | null = null
     if (isOwnSingleCampus(tenantIds)) {
-      const [dash, weeklyRes] = await Promise.all([
-        getInstitutionDashboard(),
-        getWeeklyOperationSummary()
-      ])
-      return parseOverviewDynamics(dash, weeklyRes?.data ?? weeklyRes ?? null)
+      const weeklyRes: any = await getWeeklyOperationSummary()
+      weekly = weeklyRes?.data ?? weeklyRes ?? null
     }
 
-    // 场景 2:多院区范围,必须走后端聚合接口
-    if (!OVERVIEW_DYNAMICS_API_READY) return null
-    const data: any = await request.get({
-      url: '/operation-analysis/overview/dynamics',
-      params: { tenantIds, orgType: 1 }
-    })
-    return parseOverviewDynamics(data, data?.weeklyOperation ?? null)
+    return {
+      todayCheckInCount: sum('newCheckInCount'),
+      todayCheckOutCount: sum('checkOutCount'),
+      leaveOutCount: sum('askLeaveCount'),
+      todayLeaveCount: sum('todayAskLeaveCount'),
+      waitingCount: sum('waitingCount'),
+      reservationCount: sum('reservationCount'),
+      checkoutTrend: trend,
+      nursingLevels,
+      weekly
+    }
   } catch {
     return null
   }

+ 63 - 65
src/views/operation-analysis/overview/dashboard/index.vue

@@ -12,14 +12,14 @@
         </div>
       </div>
 
-      <!-- 动态聚合接口未上线提示 -->
+      <!-- 动态指标加载失败提示 -->
       <el-alert
         v-if="!dynamicsReady"
         type="warning"
         :closable="false"
         show-icon
         class="api-tip"
-        title="今日新收、请假人数、当天请假、护理等级分布及运营周期汇总依赖后端聚合接口,接口上线前暂显示“-”;床位、在院人数、今日退住、轮候待住、预订在途及退住趋势为所选院区实时统计数据。"
+        title="今日新收、今日退院、请假人数、当天请假、轮候待住、预订在途、退住趋势及护理等级分布加载失败,请稍后刷新重试;床位与在院人数不受影响。"
       />
 
       <!-- 实时动态 KPI -->
@@ -50,7 +50,7 @@
           <span class="period-tip">按所选院区合计口径统计</span>
         </div>
         <div v-if="!hasWeekly" class="period-empty">
-          暂无数据:运营周期汇总依赖后端聚合接口,接口上线后展示
+          暂无数据:多院区运营周期汇总需后端在聚合接口中补充,当前仅登录机构查看本院时展示
         </div>
         <el-row v-else :gutter="10">
           <el-col :xs="8" :sm="8" :md="4" :lg="4">
@@ -116,7 +116,13 @@
       <el-col :xs="24" :lg="9">
         <ContentWrap>
           <div class="panel-title">{{ campusOptions.length }} 家院区床位结构</div>
-          <Echart :options="bedStructureOption" height="360px" />
+          <div class="donut-wrap">
+            <Echart :options="bedStructureOption" height="360px" />
+            <div class="donut-center">
+              <div class="donut-total">{{ bedSummary.total || '-' }}</div>
+              <div class="donut-label">总床位(张)</div>
+            </div>
+          </div>
         </ContentWrap>
       </el-col>
     </el-row>
@@ -126,11 +132,15 @@
         <ContentWrap>
           <div class="panel-title">
             近 12 个月已完成退住趋势
-            <span class="title-tip">
-              合计 {{ checkoutTotal }} 人,按退住日期统计,口径同"今日退院"(审批完成)
-            </span>
+            <span v-if="dynamicsReady" class="title-tip">合计 {{ checkoutTotal }} 人</span>
           </div>
-          <Echart :options="checkoutTrendOption" height="300px" />
+          <Echart v-if="dynamicsReady" :options="checkoutTrendOption" height="300px" />
+          <el-empty
+            v-else
+            description="暂无数据:动态指标加载失败"
+            :image-size="90"
+            class="chart-empty"
+          />
         </ContentWrap>
       </el-col>
     </el-row>
@@ -145,7 +155,7 @@
           <Echart v-if="careLevels.length" :options="careLevelOption" height="380px" />
           <el-empty
             v-else
-            description="暂无数据:依赖后端聚合接口"
+            :description="dynamicsReady ? '所选院区暂无护理等级数据' : '暂无数据:动态指标加载失败'"
             :image-size="90"
             class="chart-empty"
           />
@@ -167,15 +177,9 @@ import Echart from '@/components/Echart/src/Echart.vue'
 import {
   fetchCampusBedStats,
   fetchInHospitalCount,
-  fetchTodayCheckOutCount,
   fetchOverviewDynamics,
-  fetchCheckInWaitCount,
-  fetchReservedCount,
-  fetchMonthlyCheckOutTrend,
   getCampusOptions,
-  mergeNameCount,
   type CampusBedStat,
-  type MonthCountItem,
   type NameCountItem,
   type OverviewDynamics
 } from '../campus-data'
@@ -189,24 +193,17 @@ const updateTime = ref('')
 
 /** 在院人数(档案在院状态) */
 const inHospitalCount = ref<number | null>(null)
-/** 今日退住人数(退住流程已完成) */
-const todayCheckOutCount = ref<number | null>(null)
-/** 轮候待住人数(入住申请排队等床) */
-const waitCount = ref<number | null>(null)
-/** 预订在途人数(档案预订状态,未实际入住) */
-const reservedCount = ref<number | null>(null)
-/** 近 12 个月退住趋势 */
-const checkoutTrend = ref<MonthCountItem[]>([])
-/** 后端聚合动态指标(今日新收/请假/护理等级/周期汇总),接口未上线时为 null */
+/** 后端聚合动态指标(新收/退院/请假/轮候/预订/退住趋势/护理等级/周期汇总),加载失败为 null */
 const dynamics = ref<OverviewDynamics | null>(null)
-const careLevels = ref<NameCountItem[]>([])
-const weekly = ref<Record<string, any>>({})
 const campusStats = ref<CampusBedStat[]>([])
 
 const campusOptions = getCampusOptions()
 
 const dynamicsReady = computed(() => dynamics.value !== null)
 const hasWeekly = computed(() => dynamics.value?.weekly != null)
+const weekly = computed<Record<string, any>>(() => dynamics.value?.weekly ?? {})
+const careLevels = computed<NameCountItem[]>(() => dynamics.value?.nursingLevels ?? [])
+const checkoutTrend = computed(() => dynamics.value?.checkoutTrend ?? [])
 
 const fmtCount = (val: number | null | undefined) => (val === null || val === undefined ? '-' : val)
 
@@ -234,7 +231,7 @@ const kpiCards = computed(() => [
   },
   {
     label: '今日退院',
-    value: fmtCount(todayCheckOutCount.value),
+    value: fmtCount(dynamics.value?.todayCheckOutCount),
     unit: '人',
     icon: 'ep:minus',
     theme: 'orange'
@@ -255,14 +252,14 @@ const kpiCards = computed(() => [
   },
   {
     label: '轮候待住',
-    value: fmtCount(waitCount.value),
+    value: fmtCount(dynamics.value?.waitingCount),
     unit: '人',
     icon: 'ep:bell',
     theme: 'red'
   },
   {
     label: '预订在途',
-    value: fmtCount(reservedCount.value),
+    value: fmtCount(dynamics.value?.reservationCount),
     unit: '人',
     icon: 'ep:document',
     theme: 'pink'
@@ -377,29 +374,7 @@ const bedStructureOption = computed<any>(() => ({
         { value: bedSummary.value.temp, name: '临时占用', itemStyle: { color: '#e6a23c' } }
       ]
     }
-  ],
-  graphic: bedSummary.value.total
-    ? [
-        {
-          type: 'text',
-          left: 'center',
-          top: '40%',
-          style: {
-            text: `${bedSummary.value.total}`,
-            fontSize: 26,
-            fontWeight: 700,
-            fill: '#303133',
-            textAlign: 'center'
-          }
-        },
-        {
-          type: 'text',
-          left: 'center',
-          top: '52%',
-          style: { text: '总床位(张)', fontSize: 12, fill: '#909399', textAlign: 'center' }
-        }
-      ]
-    : []
+  ]
 }))
 
 const checkoutTotal = computed(() => checkoutTrend.value.reduce((sum, item) => sum + item.count, 0))
@@ -416,7 +391,7 @@ const checkoutTrendOption = computed<any>(() => {
         return `${item.month}<br/>${params[0].marker}已完成退住:${params[0].value} 人`
       }
     },
-    grid: { left: 45, right: 70, top: 40, bottom: 30 },
+    grid: { left: 45, right: 25, top: 40, bottom: 30 },
     xAxis: {
       type: 'category',
       data: data.map((item) => item.label),
@@ -450,7 +425,11 @@ const checkoutTrendOption = computed<any>(() => {
         markLine: {
           symbol: 'none',
           lineStyle: { color: '#f56c6c', type: 'dashed' },
-          label: { formatter: '月均 {c} 人', color: '#f56c6c', position: 'end' },
+          label: {
+            formatter: '月均 {c} 人',
+            color: '#f56c6c',
+            position: 'insideEndTop'
+          },
           data: [{ type: 'average', name: '月均' }]
         },
         data: data.map((item) => item.count)
@@ -562,25 +541,18 @@ const loadAll = async () => {
   loading.value = true
   try {
     const tenantIds = campusOptions.map((item) => item.value)
-    const [bedStats, inHospital, checkOut, waiting, reserved, trend, dyn] = await Promise.all([
+    const [bedStats, inHospital, dyn] = await Promise.all([
       fetchCampusBedStats(tenantIds),
       fetchInHospitalCount(tenantIds),
-      fetchTodayCheckOutCount(tenantIds),
-      fetchCheckInWaitCount(tenantIds),
-      fetchReservedCount(tenantIds),
-      fetchMonthlyCheckOutTrend(tenantIds, 12),
       fetchOverviewDynamics(tenantIds)
     ])
 
     campusStats.value = bedStats
     inHospitalCount.value = inHospital
-    todayCheckOutCount.value = checkOut
-    waitCount.value = waiting
-    reservedCount.value = reserved
-    checkoutTrend.value = trend
     dynamics.value = dyn
-    careLevels.value = dyn ? mergeNameCount(dyn.nursingLevels) : []
-    weekly.value = dyn?.weekly ?? {}
+    if (!dyn) {
+      message.warning('今日动态、退住趋势及护理等级数据加载失败,床位与在院人数为实时统计数据')
+    }
     updateTime.value = formatTime(new Date())
   } catch (e) {
     message.error('运营数据加载失败,请稍后重试')
@@ -794,6 +766,32 @@ onMounted(() => {
     height: 380px;
   }
 
+  .donut-wrap {
+    position: relative;
+  }
+
+  .donut-center {
+    position: absolute;
+    top: 46%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    text-align: center;
+    pointer-events: none;
+  }
+
+  .donut-total {
+    font-size: 26px;
+    font-weight: 700;
+    color: #303133;
+    line-height: 1.2;
+  }
+
+  .donut-label {
+    margin-top: 2px;
+    font-size: 12px;
+    color: #909399;
+  }
+
   .panel-title {
     position: relative;
     padding-left: 10px;

+ 86 - 130
src/views/operation-analysis/overview/运营总览-后端接口需求.md

@@ -25,149 +25,105 @@
 - 数组参数序列化形式:`tenantIds[0]=196&tenantIds[1]=197`(项目 axios 已用 qs allowDots);
 - 返回结构沿用 `{ code: 0, data: ..., msg: '' }`。
 
-前端已用**现有接口**实现、无需新开发的指标(2026-09-15 实测通过):
+前端已用**现有接口**实现、无需新开发的指标(2026-09-16 实测通过):
 总床位/在住床位/空闲/临时占用(楼栋统计聚合)、在院人数与各院区在院人数(`elderlyInfo/findPage`
-`inStatus=1`)、今日退住与近 12 个月退住趋势(`bpm/process-instance/elderly-retreat-page`
-`status=2` 按纯日期区间)、轮候待住人数(`bpm/process-instance/check-in-wait-page`,
-15 院区实测 275 人)、预订在途人数(`findPage` `inStatus=0`,实测 60 人)。
-**机构账号查看自身院区时**(所选 `tenantIds` 仅含当前登录租户),院内动态、护理情况、
-运营周期汇总也已直接复用工作台现网的 `getInstitutionDashboard` 与
-`getWeeklyOperationSummary`(这两个接口本就按当前登录机构返回,单机构口径正确)。
-下文新接口**仅需解决集团账号 / 多院区范围**的聚合。
+`inStatus=1`)。
+**今日新收/退院/请假/当天请假/轮候/预订/退住趋势/护理等级**已全部切换到 V1 聚合接口
+`GET /operation-analysis/overview/dynamics`(单机构与多院区统一走该接口,详见第二节)。
+**运营周期汇总**目前仅机构账号自身可查(复用现网 `getWeeklyOperationSummary`),
+多院区版仍待后端在该接口 V2 补充。
 
 ---
 
-## 二、【必需】接口 1:运营总览动态指标聚合(仅集团 / 多院区场景)
+## 二、接口 1:运营总览动态指标聚合(✅ V1 已上线 2026-09-16)
 
 ### `GET /operation-analysis/overview/dynamics`
 
-> 机构账号查看自身时前端已直连现网两个接口、数据正常,**本接口只需服务集团账号的
-> 多院区聚合**。响应结构刻意与现网接口保持一致,前端两套来源共用同一套解析逻辑。
+**按机构分组返回数组**,每个机构一项;前端始终显式传入选区 `tenantIds` 并做跨机构聚合。
 
-**本质:把工作台首页"院内统计"区块的两个现有接口做成支持院区范围的聚合版**,
-响应结构、指标名称、统计口径与现网保持一致,仅数据范围改为按 `tenantIds` 过滤:
-
-| 区块 | 现网接口(工作台首页) | 现网问题 |
-| --- | --- | --- |
-| 院内动态 + 护理情况 | `GET /elderlyInfo/getInstitutionDashboard` | 忽略 `tenantIds`,恒返回集团全量;护理情况跨院区**逐条拼接、同名不合并** |
-| 运营周期汇总 | `GET /elderlyInfo/getWeeklyOperationSummary` | 不支持院区参数;集团账号返回的是 102 人小机构数据,非 15 院区合计 |
-
-前端工作台渲染代码(`src/views/Home/Index.vue`)直接遍历 `hospitalDynamics`、
-`nursingSituation` 两个 `{name, count}` 数组。新接口沿用该结构,
-**后端可最大程度复用现有查询逻辑,只需加租户范围过滤与同名合并**。
+> ⚠️ 实测注意(2026-09-16):**不传 `tenantIds` 时返回 29 家机构**,大于该账号菜单
+> 可见的 15 家(含 212/213/215/217/218/221/222/223/224/226/227 等)。
+> 即默认范围不是"菜单可见院区",前端因此**永远显式传 tenantIds**;
+> 若后端能改为按当前账号数据权限过滤会更稳妥。
 
 ### 请求参数(Query)
 
 | 参数 | 类型 | 必填 | 说明 |
 | --- | --- | --- | --- |
-| tenantIds | Long[] | 否 | 院区租户 ID 集合;空 = 当前账号可见全部院区 |
-| orgType | Integer | 否 | 固定传 1 |
+| tenantIds | Long[] | 是(前端必传) | 机构 ID 数组(租户 ID),序列化 `tenantIds[0]=..&tenantIds[1]=..` |
 
-### 响应字段(data)
+### 响应(data 为数组,每个机构一项)
 
 | 字段 | 类型 | 说明 |
 | --- | --- | --- |
-| hospitalDynamics | Array<{name:String,count:Integer}> | **院内动态**,固定 6 项,**名称与顺序与现网完全一致**,见下表 |
-| nursingSituation | Array<{name:String,count:Integer}> | **护理情况**,在院长者按护理等级名称分组计数。⚠️ 必须**同名合并后再返回**(现网接口未合并,问题见下) |
-| weeklyOperation | Object | **运营周期汇总**,字段见下节,范围为所选院区合计 |
-
-### hospitalDynamics 固定项(name 不可改名,前端按 name 取值)
-
-| name | 口径 |
-| --- | --- |
-| 总床位数 | 所选院区床位总数(与接口 2 合计一致) |
-| 在院人数 | 统计时刻在院长者人数(`inStatus=1`) |
-| 新收人数 | 统计日 00:00 至当前实际完成入住办理的人数;**不含**预订(`inStatus=0`)、审批中、已撤销。是否含试住/预订转入住见文末待确认口径 |
-| 请假人数 | 统计时刻仍请假外出、未返院的在院长者人数 |
-| 当天请假人数 | 统计日当天处于请假外出状态的人数(含跨日未返)。与"请假人数"的业务边界见文末待确认口径 |
-| 退院人数 | 统计日 00:00 至当前退住审批完成(`status=2`)的人数,按退住日期 = 统计日 |
-
-### nursingSituation 聚合要求(重点)
-
-现网 `getInstitutionDashboard` 在集团账号下 2026-09-15 实测返回 **116 条、合计 3439**
-(恰等于集团全量在院人数),但**只是把各院区结果逐条拼接,同名等级没有合并**,例如
-"二级护理"出现 6 次以上、"三级护理"出现 5 次以上;且各院区护理等级字典不统一,
-同时存在"一级护理 / 一级A护理 / 一级等级 / 自理 / 轻度失能 / 能力完好 / 介护护理+ /
-特困-全护理 / 托养"等不同体系的名称。
-
-新接口要求:
-
-1. 按所选院区汇总后 `GROUP BY 护理等级名称`,**同名合并计数**,按 count 降序;
-2. 等级为空的归入固定名称"未设置";
-3. 合并后合计数应等于 `hospitalDynamics` 中"在院人数";
-4. 各院区字典不统一属数据治理问题,本次不要求统一字典,仅做同名合并;
-   若业务方后续统一字典,接口无需改动。
-
-### weeklyOperation 字段
-
-沿用现有 `GET /elderlyInfo/getWeeklyOperationSummary` 的字段(字段名已与现网核实一致,
-现网集团账号实测返回 `institutionName=颐年集团, periodBeginCount=102, periodEndCount=102,
-periodEndEmptyBedCount=5, 其余=0`,明显不是 15 院区合计),范围改为所选院区合计,
-并建议补充周期日期字段:
-
-| 字段 | 类型 | 现网已有 | 说明 |
-| --- | --- | --- | --- |
-| institutionName | String | 是 | 范围名称,如"颐年集团(15 家院区)" |
-| periodBegin | String | 否(建议新增) | 本周期开始日期 yyyy-MM-dd |
-| periodEnd | String | 否(建议新增) | 本周期结束日期 yyyy-MM-dd |
-| periodBeginCount | Integer | 是 | 本周期初在院人数 |
-| periodEndCount | Integer | 是 | 本周期末在院人数 |
-| weeklyCheckInCount | Integer | 是 | 本周入住人数 |
-| weeklyCheckOutCount | Integer | 是 | 本周退住人数 |
-| weeklyNetGrowthCount | Integer | 是 | 本周净增长 |
-| monthlyCheckInCount | Integer | 是 | 本月入住人数 |
-| monthlyCheckOutCount | Integer | 是 | 本月退住人数 |
-| monthlyNetGrowthCount | Integer | 是 | 本月净增长 |
-| periodEndEmptyBedCount | Integer | 是 | 期末空余床位数(与接口 2 合计一致) |
-
-### 响应样例(结构与现网接口一致,数值为示例)
-
-```json
-{
-  "code": 0,
-  "data": {
-    "hospitalDynamics": [
-      { "name": "总床位数", "count": 4325 },
-      { "name": "在院人数", "count": 3011 },
-      { "name": "新收人数", "count": 0 },
-      { "name": "请假人数", "count": 230 },
-      { "name": "当天请假人数", "count": 10 },
-      { "name": "退院人数", "count": 0 }
-    ],
-    "nursingSituation": [
-      { "name": "二级护理", "count": 730 },
-      { "name": "三级护理", "count": 620 },
-      { "name": "一级A护理", "count": 210 }
-    ],
-    "weeklyOperation": {
-      "institutionName": "颐年集团(15 家院区)",
-      "periodBegin": "2026-09-08",
-      "periodEnd": "2026-09-14",
-      "periodBeginCount": 3000,
-      "weeklyCheckInCount": 9,
-      "weeklyCheckOutCount": 5,
-      "periodEndCount": 3011,
-      "weeklyNetGrowthCount": 4,
-      "monthlyCheckInCount": 42,
-      "monthlyCheckOutCount": 25,
-      "monthlyNetGrowthCount": 17,
-      "periodEndEmptyBedCount": 1277
-    }
-  },
-  "msg": ""
-}
-```
+| tenantId | Integer | 机构 ID(租户 ID) |
+| tenantName | String | 机构名称 |
+| newCheckInCount | Integer | 今日新收 |
+| checkOutCount | Integer | 今日退院 |
+| askLeaveCount | Integer | 请假人数(截至当前仍在请假中,含往日请假未归) |
+| todayAskLeaveCount | Integer | 当天请假人数(当天离院人数) |
+| waitingCount | Integer | 轮候待住 |
+| reservationCount | Integer | 预订在途 |
+| retreatTrend | Array<{month:String,count:Integer}> | 近 12 个月已完成退住趋势(含当月,无数据补 0,month=yyyy-MM) |
+| nurseLevelDistribution | Array<{nurseLevelId:Integer,nurseLevelName:String,count:Integer}> | 在住长者护理等级分布 |
+
+### 前端聚合规则(已实现,`campus-data.ts` fetchOverviewDynamics)
+
+1. 6 个数值指标(newCheckInCount / checkOutCount / askLeaveCount /
+   todayAskLeaveCount / waitingCount / reservationCount)跨机构**求和**;
+2. `retreatTrend` 按 month 跨机构求和,并以浏览器端近 12 个月骨架做零填充;
+3. `nurseLevelDistribution` 按 **nurseLevelName 同名合并**后降序——
+   实测各院区等级字典独立、id 不通用(同名"一级护理"存在 id=95 与 id=116 两个不同 ID,
+   分属不同院区),按 id 合并会出现重名条目,故前端按名称合并;空名称归"未设置";
+4. 15 院区聚合实测(2026-09-16):今日新收 3、退院 0、请假 222、当天请假 1、
+   轮候 55、预订 0;退住趋势合计 860(2025-10 至 2026-09 依次
+   81/69/55/84/72/134/73/66/73/68/62/23);护理等级合计 3014,与实时在院 3015 差 1
+   (统计时点差异,可接受)。
+5. 性能:15 院区请求约 3.3 秒,可接受;后续若院区增多建议后端直接返回汇总行。
+
+> 口径差异说明(以前端新接口数据为准,旧的前端临时算法已废弃):
+> - 轮候待住:新接口 15 院区为 55 人;旧前端用 `check-in-wait-page` 总数得 275 人
+>   (含非"排队等床"状态);
+> - 预订在途:新接口为 0;旧前端用 `findPage inStatus=0` 得 60 人(含其他预订状态);
+> - 退住趋势:新接口 2025 年 10-12 月及 2026 年 1-2 月数字高于旧前端按
+>   `elderly-retreat-page` 逐月 total 的结果,近 6 个月两口径一致。
+>   推测旧列表接口存在历史数据过滤条件差异,请后端确认趋势使用的日期字段
+>   (退住日期 vs 流程完成时间)及状态范围。
+
+### 与工作台现网看板的字段对应
+
+| 驾驶舱指标 | 新接口字段(V1) | 工作台旧接口字段 |
+| --- | --- | --- |
+| 今日新收 | newCheckInCount | hospitalDynamics[新收人数] |
+| 今日退院 | checkOutCount | hospitalDynamics[退院人数] |
+| 请假人数 | askLeaveCount | hospitalDynamics[请假人数] |
+| 当天请假人数 | todayAskLeaveCount | hospitalDynamics[当天请假人数] |
+| 轮候待住 | waitingCount | 旧看板无 |
+| 预订在途 | reservationCount | 旧看板无 |
+| 退住趋势 | retreatTrend | 旧看板无(前端曾逐月查询,已废弃) |
+| 护理等级 | nurseLevelDistribution | nursingSituation(旧接口跨院区不合并) |
+| 总床位 / 在院人数 | 不含(仍走楼栋统计 / findPage) | hospitalDynamics[总床位数/在院人数] |
+
+### 仍缺:运营周期汇总(请在 V2 补充)
+
+V1 **未包含** `weeklyOperation`。多院区场景下驾驶舱"运营周期汇总"卡片继续显示空态;
+机构账号查看自身时,前端另调现网 `getWeeklyOperationSummary`(本就按登录机构返回)展示。
+请在该接口每个机构项内(或新增汇总字段)补充以下字段,前端即可开放多院区周期卡:
 
-### 现网接口原始抓包(2026-09-15 集团账号,供对照)
+| 字段 | 类型 | 说明 |
+| --- | --- | --- |
+| institutionName | String | 范围名称 |
+| periodBegin / periodEnd | String | 周期起止 yyyy-MM-dd(建议新增) |
+| periodBeginCount / periodEndCount | Integer | 周期初/末在院人数 |
+| weeklyCheckInCount / weeklyCheckOutCount / weeklyNetGrowthCount | Integer | 本周入住/退住/净增 |
+| monthlyCheckInCount / monthlyCheckOutCount / monthlyNetGrowthCount | Integer | 本月入住/退住/净增 |
+| periodEndEmptyBedCount | Integer | 期末空余床位数(与床位统计合计一致) |
 
-`getInstitutionDashboard`:总床位数 4829、在院人数 3439、新收人数 0、请假人数 264、
-当天请假人数 0、退院人数 6;`nursingSituation` 116 条合计 3439(未同名合并)。
+### 现网旧接口原始抓包(2026-09-15 集团账号,供对照)
 
-> 前端对接说明(`campus-data.ts` 中 `fetchOverviewDynamics`,两种来源共用同一解析函数):
-> ① 机构账号只看自身 → 已直接调用 `getInstitutionDashboard` + `getWeeklyOperationSummary`,
-> 从 `hospitalDynamics` 按 name 取值,`nursingSituation` 前端再做一次同名合并兜底;
-> ② 集团/多院区 → 接口上线验证通过后将 `OVERVIEW_DYNAMICS_API_READY` 置为 `true` 即生效;
-> 上线前多院区场景相关卡片显示 "-",**不会**用集团全量数据兜底。
+`getInstitutionDashboard`:总床位数 4829、在院人数 3439、新收 0、请假 264、当天请假 0、
+退院 6;`nursingSituation` 116 条合计 3439(未同名合并)。`getWeeklyOperationSummary`
+集团账号返回 102 人口径(非 15 院区合计)。
 
 ---
 
@@ -227,15 +183,15 @@ periodEndEmptyBedCount=5, 其余=0`,明显不是 15 院区合计),范围
 ### `GET /operation-analysis/overview/check-trend`
 
 驾驶舱需要展示近 N 个月(默认 12)的**入住人数、退住人数、净增、月末在院人数与入住率趋势**。
-目前:
+目前(2026-09-16 更新):
 
-- 退住趋势前端可通过 `bpm/process-instance/elderly-retreat-page`(`status=2` +
-  `retreatDate[0]/[1]=yyyy-MM-dd`,纯日期格式)**逐月查 total 临时聚合**,当前已如此实现,
-  但每月 1 次请求(12 次),入住趋势无法同样实现——
-  `check-in-page` 多院区或带 `checkInTime` 参数均返回 500(见问题清单 #3);
-- 历史入住率无任何数据源(`build/getBuildFloorStatistics` 持续 500,见问题清单 #2)。
+- ✅ **退住趋势已由接口 1 V1 的 `retreatTrend` 提供**(驾驶舱已接入,旧的逐月
+  `elderly-retreat-page` 12 次请求方案已废弃);
+- ❌ 入住趋势仍无数据源——`check-in-page` 多院区或带 `checkInTime` 参数均返回 500
+  (见问题清单 #3);
+- ❌ 历史入住率无任何数据源(`build/getBuildFloorStatistics` 持续 500,见问题清单 #2)。
 
-故需后端提供一次聚合,提供后前端将把退住趋势的 12 次逐月请求一并替换掉。
+本接口范围因此缩小为:入住趋势 + 净增(可结合已落地的退住数)+ 月末在院与入住率。
 
 ### 请求参数
 

+ 78 - 78
tsc_out.txt

@@ -1583,31 +1583,31 @@ src/views/elderly/assess/synthetic-ability/components/Result.vue(6,30): error TS
 src/views/elderly/assess/synthetic-ability/components/Result.vue(7,18): error TS18046: 'item' is of type 'unknown'.
 src/views/elderly/assess/synthetic-ability/components/Result.vue(51,23): error TS18046: 'n' is of type 'unknown'.
 src/views/elderly/assess/synthetic-ability/components/Result.vue(52,23): error TS18046: 'n' is of type 'unknown'.
-src/views/elderly/assess/synthetic-ability/components/Result.vue(208,10): error TS2571: Object is of type 'unknown'.
-src/views/elderly/assess/synthetic-ability/components/Result.vue(209,9): error TS2571: Object is of type 'unknown'.
-src/views/elderly/assess/synthetic-ability/components/Result.vue(219,10): error TS2571: Object is of type 'unknown'.
-src/views/elderly/assess/synthetic-ability/components/Result.vue(232,21): error TS18046: 'n' is of type 'unknown'.
-src/views/elderly/assess/synthetic-ability/components/Result.vue(233,21): error TS18046: 'n' is of type 'unknown'.
-src/views/elderly/assess/synthetic-ability/components/Result.vue(245,10): error TS2571: Object is of type 'unknown'.
-src/views/elderly/assess/synthetic-ability/components/Result.vue(246,9): error TS2571: Object is of type 'unknown'.
-src/views/elderly/assess/synthetic-ability/components/Result.vue(256,10): error TS2571: Object is of type 'unknown'.
-src/views/elderly/assess/synthetic-ability/components/Result.vue(257,9): error TS2571: Object is of type 'unknown'.
-src/views/elderly/assess/synthetic-ability/components/Result.vue(267,10): error TS2571: Object is of type 'unknown'.
-src/views/elderly/assess/synthetic-ability/components/Result.vue(268,9): error TS2571: Object is of type 'unknown'.
-src/views/elderly/assess/synthetic-ability/components/Result.vue(278,10): error TS2571: Object is of type 'unknown'.
-src/views/elderly/assess/synthetic-ability/components/Result.vue(279,9): error TS2571: Object is of type 'unknown'.
-src/views/elderly/assess/synthetic-ability/components/Result.vue(289,10): error TS2571: Object is of type 'unknown'.
-src/views/elderly/assess/synthetic-ability/components/Result.vue(290,9): error TS2571: Object is of type 'unknown'.
-src/views/elderly/assess/synthetic-ability/components/Result.vue(297,1): error TS6133: 'getSyntheticAbilityName' is declared but its value is never read.
-src/views/elderly/assess/synthetic-ability/components/Result.vue(298,1): error TS6133: 'Qrcode' is declared but its value is never read.
-src/views/elderly/assess/synthetic-ability/components/Result.vue(299,1): error TS6192: All imports in import declaration are unused.
-src/views/elderly/assess/synthetic-ability/components/Result.vue(304,1): error TS6133: 'getTenantId' is declared but its value is never read.
-src/views/elderly/assess/synthetic-ability/components/Result.vue(311,7): error TS6133: 'message' is declared but its value is never read.
-src/views/elderly/assess/synthetic-ability/components/Result.vue(357,19): error TS18046: 'item' is of type 'unknown'.
-src/views/elderly/assess/synthetic-ability/components/Result.vue(364,7): error TS6133: 'qrUrl' is declared but its value is never read.
-src/views/elderly/assess/synthetic-ability/components/Result.vue(365,7): error TS6133: 'randomStr' is declared but its value is never read.
-src/views/elderly/assess/synthetic-ability/components/Result.vue(366,7): error TS6133: 'elderSyntheticAbilityId' is declared but its value is never read.
-src/views/elderly/assess/synthetic-ability/components/Result.vue(389,7): error TS6133: 'getSign' is declared but its value is never read.
+src/views/elderly/assess/synthetic-ability/components/Result.vue(212,10): error TS2571: Object is of type 'unknown'.
+src/views/elderly/assess/synthetic-ability/components/Result.vue(213,9): error TS2571: Object is of type 'unknown'.
+src/views/elderly/assess/synthetic-ability/components/Result.vue(223,10): error TS2571: Object is of type 'unknown'.
+src/views/elderly/assess/synthetic-ability/components/Result.vue(236,21): error TS18046: 'n' is of type 'unknown'.
+src/views/elderly/assess/synthetic-ability/components/Result.vue(237,21): error TS18046: 'n' is of type 'unknown'.
+src/views/elderly/assess/synthetic-ability/components/Result.vue(249,10): error TS2571: Object is of type 'unknown'.
+src/views/elderly/assess/synthetic-ability/components/Result.vue(250,9): error TS2571: Object is of type 'unknown'.
+src/views/elderly/assess/synthetic-ability/components/Result.vue(260,10): error TS2571: Object is of type 'unknown'.
+src/views/elderly/assess/synthetic-ability/components/Result.vue(261,9): error TS2571: Object is of type 'unknown'.
+src/views/elderly/assess/synthetic-ability/components/Result.vue(271,10): error TS2571: Object is of type 'unknown'.
+src/views/elderly/assess/synthetic-ability/components/Result.vue(272,9): error TS2571: Object is of type 'unknown'.
+src/views/elderly/assess/synthetic-ability/components/Result.vue(282,10): error TS2571: Object is of type 'unknown'.
+src/views/elderly/assess/synthetic-ability/components/Result.vue(283,9): error TS2571: Object is of type 'unknown'.
+src/views/elderly/assess/synthetic-ability/components/Result.vue(293,10): error TS2571: Object is of type 'unknown'.
+src/views/elderly/assess/synthetic-ability/components/Result.vue(294,9): error TS2571: Object is of type 'unknown'.
+src/views/elderly/assess/synthetic-ability/components/Result.vue(305,1): error TS6133: 'getSyntheticAbilityName' is declared but its value is never read.
+src/views/elderly/assess/synthetic-ability/components/Result.vue(306,1): error TS6133: 'Qrcode' is declared but its value is never read.
+src/views/elderly/assess/synthetic-ability/components/Result.vue(307,1): error TS6192: All imports in import declaration are unused.
+src/views/elderly/assess/synthetic-ability/components/Result.vue(312,1): error TS6133: 'getTenantId' is declared but its value is never read.
+src/views/elderly/assess/synthetic-ability/components/Result.vue(319,7): error TS6133: 'message' is declared but its value is never read.
+src/views/elderly/assess/synthetic-ability/components/Result.vue(382,19): error TS18046: 'item' is of type 'unknown'.
+src/views/elderly/assess/synthetic-ability/components/Result.vue(389,7): error TS6133: 'qrUrl' is declared but its value is never read.
+src/views/elderly/assess/synthetic-ability/components/Result.vue(390,7): error TS6133: 'randomStr' is declared but its value is never read.
+src/views/elderly/assess/synthetic-ability/components/Result.vue(391,7): error TS6133: 'elderSyntheticAbilityId' is declared but its value is never read.
+src/views/elderly/assess/synthetic-ability/components/Result.vue(414,7): error TS6133: 'getSign' is declared but its value is never read.
 src/views/elderly/assess/synthetic-ability/Detail.vue(394,10): error TS6133: 'syntheticAbilityDetailType' is declared but its value is never read.
 src/views/elderly/assess/synthetic-ability/Detail.vue(398,1): error TS6133: 'getSyntheticAbilityName' is declared but its value is never read.
 src/views/elderly/assess/synthetic-ability/Detail.vue(399,1): error TS6133: 'syntheticAbilityRecord' is declared but its value is never read.
@@ -1616,43 +1616,43 @@ src/views/elderly/assess/synthetic-ability/Detail.vue(553,25): error TS2339: Pro
 src/views/elderly/assess/synthetic-ability/Detail.vue(555,18): error TS2345: Argument of type 'string' is not assignable to parameter of type 'never'.
 src/views/elderly/assess/synthetic-ability/Detail.vue(657,7): error TS6133: 'extractNumbers' is declared but its value is never read.
 src/views/elderly/assess/synthetic-ability/Form.vue(59,67): error TS2367: This comparison appears to be unintentional because the types 'string | undefined' and 'number' have no overlap.
-src/views/elderly/assess/synthetic-ability/Form.vue(441,129): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
-src/views/elderly/assess/synthetic-ability/Form.vue(650,60): error TS2322: Type '{ evaluationProcessId: number; id: number; sort: number; templateId: number; templateName: string; template: { id: number; channelId: number; templateName: string; templateProperties: string; configProperties: string; status: number; }[]; }' is not assignable to type 'processSequenceListType[] | templateType[] | undefined'.
-src/views/elderly/assess/synthetic-ability/Form.vue(691,10): error TS6133: 'computed' is declared but its value is never read.
-src/views/elderly/assess/synthetic-ability/Form.vue(719,3): error TS2741: Property 'nurseLevelName' is missing in type '{ tenantId: undefined; id: string; evaluationAgency: string; thirdPartyName: string; evaluateReasons: string; questionnaireName: string; questionnaireId: string; questionnaireType: string; ... 47 more ...; contactPhone: string; }' but required in type '{ tenantId: number | undefined; id: string | undefined; evaluationAgency: string; thirdPartyName: string | undefined; evaluateReasons: string | undefined; questionnaireName: string | undefined; ... 50 more ...; nurseLevelName: string; }'.
-src/views/elderly/assess/synthetic-ability/Form.vue(913,24): error TS2531: Object is possibly 'null'.
-src/views/elderly/assess/synthetic-ability/Form.vue(913,24): error TS2531: Object is possibly 'null'.
-src/views/elderly/assess/synthetic-ability/Form.vue(929,16): error TS2345: Argument of type 'string' is not assignable to parameter of type 'never'.
-src/views/elderly/assess/synthetic-ability/Form.vue(944,6): error TS2367: This comparison appears to be unintentional because the types 'never[]' and 'string' have no overlap.
-src/views/elderly/assess/synthetic-ability/Form.vue(972,24): error TS2531: Object is possibly 'null'.
-src/views/elderly/assess/synthetic-ability/Form.vue(972,24): error TS2531: Object is possibly 'null'.
-src/views/elderly/assess/synthetic-ability/Form.vue(1059,23): error TS2345: Argument of type 'string' is not assignable to parameter of type 'never'.
-src/views/elderly/assess/synthetic-ability/Form.vue(1061,23): error TS2345: Argument of type 'string' is not assignable to parameter of type 'never'.
-src/views/elderly/assess/synthetic-ability/Form.vue(1086,62): error TS2339: Property 'templateName' does not exist on type '{ id: number; channelId: number; templateName: string; templateProperties: string; configProperties: string; status: number; }[]'.
-src/views/elderly/assess/synthetic-ability/Form.vue(1090,27): error TS2339: Property 'templateProperties' does not exist on type '{ id: number; channelId: number; templateName: string; templateProperties: string; configProperties: string; status: number; }[]'.
-src/views/elderly/assess/synthetic-ability/Form.vue(1096,16): error TS2339: Property 'init' does not exist on type 'HTMLElement'.
-src/views/elderly/assess/synthetic-ability/Form.vue(1119,18): error TS18046: 'params' is of type 'unknown'.
-src/views/elderly/assess/synthetic-ability/Form.vue(1125,31): error TS18046: 'params' is of type 'unknown'.
-src/views/elderly/assess/synthetic-ability/Form.vue(1142,9): error TS18046: 'params' is of type 'unknown'.
-src/views/elderly/assess/synthetic-ability/Form.vue(1195,9): error TS18046: 'params' is of type 'unknown'.
-src/views/elderly/assess/synthetic-ability/Form.vue(1216,46): error TS2339: Property 'totalRecordList' does not exist on type 'HTMLElement'.
-src/views/elderly/assess/synthetic-ability/Form.vue(1232,45): error TS2339: Property 'propertiesName' does not exist on type 'number[]'.
-src/views/elderly/assess/synthetic-ability/Form.vue(1246,61): error TS2339: Property 'fApi' does not exist on type 'HTMLElement'.
-src/views/elderly/assess/synthetic-ability/Form.vue(1250,43): error TS2345: Argument of type '{ evaluationName: string; evaluationContent: any; score: any; }' is not assignable to parameter of type 'never'.
-src/views/elderly/assess/synthetic-ability/Form.vue(1252,39): error TS2339: Property 'fApi' does not exist on type 'HTMLElement'.
-src/views/elderly/assess/synthetic-ability/Form.vue(1253,27): error TS2339: Property 'totalRecord' does not exist on type 'HTMLElement'.
-src/views/elderly/assess/synthetic-ability/Form.vue(1305,24): error TS2531: Object is possibly 'null'.
-src/views/elderly/assess/synthetic-ability/Form.vue(1305,24): error TS2531: Object is possibly 'null'.
-src/views/elderly/assess/synthetic-ability/Form.vue(1306,5): error TS2531: Object is possibly 'null'.
-src/views/elderly/assess/synthetic-ability/Form.vue(1306,5): error TS2531: Object is possibly 'null'.
-src/views/elderly/assess/synthetic-ability/Form.vue(1318,10): error TS18046: 'params' is of type 'unknown'.
-src/views/elderly/assess/synthetic-ability/Form.vue(1327,24): error TS2531: Object is possibly 'null'.
-src/views/elderly/assess/synthetic-ability/Form.vue(1327,24): error TS2531: Object is possibly 'null'.
-src/views/elderly/assess/synthetic-ability/Form.vue(1328,5): error TS2531: Object is possibly 'null'.
-src/views/elderly/assess/synthetic-ability/Form.vue(1328,5): error TS2531: Object is possibly 'null'.
-src/views/elderly/assess/synthetic-ability/Form.vue(1355,24): error TS2531: Object is possibly 'null'.
-src/views/elderly/assess/synthetic-ability/Form.vue(1355,24): error TS2531: Object is possibly 'null'.
-src/views/elderly/assess/synthetic-ability/Form.vue(1415,12): error TS2339: Property 'init' does not exist on type 'HTMLElement'.
+src/views/elderly/assess/synthetic-ability/Form.vue(448,129): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
+src/views/elderly/assess/synthetic-ability/Form.vue(657,60): error TS2322: Type '{ evaluationProcessId: number; id: number; sort: number; templateId: number; templateName: string; template: { id: number; channelId: number; templateName: string; templateProperties: string; configProperties: string; status: number; }[]; }' is not assignable to type 'processSequenceListType[] | templateType[] | undefined'.
+src/views/elderly/assess/synthetic-ability/Form.vue(698,10): error TS6133: 'computed' is declared but its value is never read.
+src/views/elderly/assess/synthetic-ability/Form.vue(727,3): error TS2741: Property 'nurseLevelName' is missing in type '{ tenantId: undefined; id: string; evaluationAgency: string; thirdPartyName: string; evaluateReasons: string; evaluationCode: string; questionnaireName: string; questionnaireId: string; questionnaireType: string; ... 48 more ...; contactPhone: string; }' but required in type '{ tenantId: number | undefined; id: string | undefined; evaluationAgency: string; thirdPartyName: string | undefined; evaluateReasons: string | undefined; evaluationCode: string | undefined; ... 52 more ...; nurseLevelName: string; }'.
+src/views/elderly/assess/synthetic-ability/Form.vue(924,24): error TS2531: Object is possibly 'null'.
+src/views/elderly/assess/synthetic-ability/Form.vue(924,24): error TS2531: Object is possibly 'null'.
+src/views/elderly/assess/synthetic-ability/Form.vue(940,16): error TS2345: Argument of type 'string' is not assignable to parameter of type 'never'.
+src/views/elderly/assess/synthetic-ability/Form.vue(955,6): error TS2367: This comparison appears to be unintentional because the types 'never[]' and 'string' have no overlap.
+src/views/elderly/assess/synthetic-ability/Form.vue(1004,24): error TS2531: Object is possibly 'null'.
+src/views/elderly/assess/synthetic-ability/Form.vue(1004,24): error TS2531: Object is possibly 'null'.
+src/views/elderly/assess/synthetic-ability/Form.vue(1093,23): error TS2345: Argument of type 'string' is not assignable to parameter of type 'never'.
+src/views/elderly/assess/synthetic-ability/Form.vue(1095,23): error TS2345: Argument of type 'string' is not assignable to parameter of type 'never'.
+src/views/elderly/assess/synthetic-ability/Form.vue(1120,62): error TS2339: Property 'templateName' does not exist on type '{ id: number; channelId: number; templateName: string; templateProperties: string; configProperties: string; status: number; }[]'.
+src/views/elderly/assess/synthetic-ability/Form.vue(1124,27): error TS2339: Property 'templateProperties' does not exist on type '{ id: number; channelId: number; templateName: string; templateProperties: string; configProperties: string; status: number; }[]'.
+src/views/elderly/assess/synthetic-ability/Form.vue(1130,16): error TS2339: Property 'init' does not exist on type 'HTMLElement'.
+src/views/elderly/assess/synthetic-ability/Form.vue(1153,18): error TS18046: 'params' is of type 'unknown'.
+src/views/elderly/assess/synthetic-ability/Form.vue(1158,31): error TS18046: 'params' is of type 'unknown'.
+src/views/elderly/assess/synthetic-ability/Form.vue(1175,9): error TS18046: 'params' is of type 'unknown'.
+src/views/elderly/assess/synthetic-ability/Form.vue(1228,9): error TS18046: 'params' is of type 'unknown'.
+src/views/elderly/assess/synthetic-ability/Form.vue(1249,46): error TS2339: Property 'totalRecordList' does not exist on type 'HTMLElement'.
+src/views/elderly/assess/synthetic-ability/Form.vue(1265,45): error TS2339: Property 'propertiesName' does not exist on type 'number[]'.
+src/views/elderly/assess/synthetic-ability/Form.vue(1279,61): error TS2339: Property 'fApi' does not exist on type 'HTMLElement'.
+src/views/elderly/assess/synthetic-ability/Form.vue(1283,43): error TS2345: Argument of type '{ evaluationName: string; evaluationContent: any; score: any; }' is not assignable to parameter of type 'never'.
+src/views/elderly/assess/synthetic-ability/Form.vue(1285,39): error TS2339: Property 'fApi' does not exist on type 'HTMLElement'.
+src/views/elderly/assess/synthetic-ability/Form.vue(1286,27): error TS2339: Property 'totalRecord' does not exist on type 'HTMLElement'.
+src/views/elderly/assess/synthetic-ability/Form.vue(1338,24): error TS2531: Object is possibly 'null'.
+src/views/elderly/assess/synthetic-ability/Form.vue(1338,24): error TS2531: Object is possibly 'null'.
+src/views/elderly/assess/synthetic-ability/Form.vue(1339,5): error TS2531: Object is possibly 'null'.
+src/views/elderly/assess/synthetic-ability/Form.vue(1339,5): error TS2531: Object is possibly 'null'.
+src/views/elderly/assess/synthetic-ability/Form.vue(1351,10): error TS18046: 'params' is of type 'unknown'.
+src/views/elderly/assess/synthetic-ability/Form.vue(1360,24): error TS2531: Object is possibly 'null'.
+src/views/elderly/assess/synthetic-ability/Form.vue(1360,24): error TS2531: Object is possibly 'null'.
+src/views/elderly/assess/synthetic-ability/Form.vue(1361,5): error TS2531: Object is possibly 'null'.
+src/views/elderly/assess/synthetic-ability/Form.vue(1361,5): error TS2531: Object is possibly 'null'.
+src/views/elderly/assess/synthetic-ability/Form.vue(1391,24): error TS2531: Object is possibly 'null'.
+src/views/elderly/assess/synthetic-ability/Form.vue(1391,24): error TS2531: Object is possibly 'null'.
+src/views/elderly/assess/synthetic-ability/Form.vue(1451,12): error TS2339: Property 'init' does not exist on type 'HTMLElement'.
 src/views/elderly/assess/synthetic-ability/index.vue(126,7): error TS6133: 't' is declared but its value is never read.
 src/views/elderly/assess/synthetic-ability/index.vue(186,7): error TS6133: 'handleSign' is declared but its value is never read.
 src/views/elderly/assess/synthetic-ability/index.vue(218,7): error TS2322: Type 'string' is not assignable to type 'undefined'.
@@ -1729,7 +1729,7 @@ src/views/elderly/assess/synthetic-ability/Print.vue(364,53): error TS2339: Prop
 src/views/elderly/assess/synthetic-ability/Print.vue(370,57): error TS2339: Property 'dsc' does not exist on type 'never'.
 src/views/elderly/assess/synthetic-ability/Print.vue(375,54): error TS2339: Property 'dsc' does not exist on type 'never'.
 src/views/elderly/assess/synthetic-ability/Print.vue(376,53): error TS2339: Property 'value' does not exist on type 'never'.
-src/views/elderly/assess/synthetic-ability/Print.vue(386,52): error TS2339: Property 'informationProviderName' does not exist on type '{}'.
+src/views/elderly/assess/synthetic-ability/Print.vue(386,52): error TS2339: Property 'evaluator' does not exist on type '{}'.
 src/views/elderly/assess/synthetic-ability/Print.vue(406,33): error TS6133: 'index' is declared but its value is never read.
 src/views/elderly/assess/synthetic-ability/Print.vue(406,52): error TS2339: Property 'syntheticAbilityInfoList' does not exist on type '{}'.
 src/views/elderly/assess/synthetic-ability/Print.vue(431,40): error TS2339: Property 'evaluationAgency' does not exist on type '{}'.
@@ -1738,23 +1738,23 @@ src/views/elderly/assess/synthetic-ability/Print.vue(443,60): error TS2339: Prop
 src/views/elderly/assess/synthetic-ability/Print.vue(491,34): error TS2339: Property 'processSequenceList' does not exist on type '{}'.
 src/views/elderly/assess/synthetic-ability/Print.vue(493,34): error TS2339: Property 'totalScore' does not exist on type '{}'.
 src/views/elderly/assess/synthetic-ability/Print.vue(496,38): error TS2339: Property 'totalScore' does not exist on type '{}'.
-src/views/elderly/assess/synthetic-ability/Print.vue(509,48): error TS2339: Property 'informationProviderName' does not exist on type '{}'.
+src/views/elderly/assess/synthetic-ability/Print.vue(509,48): error TS2339: Property 'evaluator' does not exist on type '{}'.
 src/views/elderly/assess/synthetic-ability/Print.vue(662,7): error TS6133: 'message' is declared but its value is never read.
 src/views/elderly/assess/synthetic-ability/Print.vue(663,7): error TS6133: 't' is declared but its value is never read.
-src/views/elderly/assess/synthetic-ability/Print.vue(698,5): error TS6133: 'syntheticAbilityInfoListTemp' is declared but its value is never read.
-src/views/elderly/assess/synthetic-ability/Print.vue(761,7): error TS6133: 'cellNum' is declared but its value is never read.
-src/views/elderly/assess/synthetic-ability/Print.vue(814,9): error TS2322: Type 'string[]' is not assignable to type 'never[]'.
-src/views/elderly/assess/synthetic-ability/Print.vue(822,13): error TS2339: Property 'name' does not exist on type '{ list: never[]; templateList: never[]; }'.
-src/views/elderly/assess/synthetic-ability/Print.vue(823,13): error TS2339: Property 'id' does not exist on type '{ list: never[]; templateList: never[]; }'.
-src/views/elderly/assess/synthetic-ability/Print.vue(824,13): error TS2339: Property 'total' does not exist on type '{ list: never[]; templateList: never[]; }'.
-src/views/elderly/assess/synthetic-ability/Print.vue(827,27): error TS2345: Argument of type '{ value: any; title: any; options: any; info: any; type: any; }' is not assignable to parameter of type 'never'.
-src/views/elderly/assess/synthetic-ability/Print.vue(834,17): error TS2339: Property 'total' does not exist on type '{ list: never[]; templateList: never[]; }'.
-src/views/elderly/assess/synthetic-ability/Print.vue(837,27): error TS2345: Argument of type '{ list: never[]; templateList: never[]; }' is not assignable to parameter of type 'never'.
-src/views/elderly/assess/synthetic-ability/Print.vue(840,33): error TS2339: Property 'templateList' does not exist on type 'never'.
-src/views/elderly/assess/synthetic-ability/Print.vue(846,49): error TS2339: Property 'push' does not exist on type 'never'.
-src/views/elderly/assess/synthetic-ability/Print.vue(851,51): error TS2339: Property 'result' does not exist on type '{}'.
-src/views/elderly/assess/synthetic-ability/Print.vue(872,19): error TS2345: Argument of type 'never[]' is not assignable to parameter of type 'never'.
-src/views/elderly/assess/synthetic-ability/Print.vue(875,19): error TS2345: Argument of type 'any' is not assignable to parameter of type 'never'.
+src/views/elderly/assess/synthetic-ability/Print.vue(703,5): error TS6133: 'syntheticAbilityInfoListTemp' is declared but its value is never read.
+src/views/elderly/assess/synthetic-ability/Print.vue(766,7): error TS6133: 'cellNum' is declared but its value is never read.
+src/views/elderly/assess/synthetic-ability/Print.vue(819,9): error TS2322: Type 'string[]' is not assignable to type 'never[]'.
+src/views/elderly/assess/synthetic-ability/Print.vue(827,13): error TS2339: Property 'name' does not exist on type '{ list: never[]; templateList: never[]; }'.
+src/views/elderly/assess/synthetic-ability/Print.vue(828,13): error TS2339: Property 'id' does not exist on type '{ list: never[]; templateList: never[]; }'.
+src/views/elderly/assess/synthetic-ability/Print.vue(829,13): error TS2339: Property 'total' does not exist on type '{ list: never[]; templateList: never[]; }'.
+src/views/elderly/assess/synthetic-ability/Print.vue(832,27): error TS2345: Argument of type '{ value: any; title: any; options: any; info: any; type: any; }' is not assignable to parameter of type 'never'.
+src/views/elderly/assess/synthetic-ability/Print.vue(839,17): error TS2339: Property 'total' does not exist on type '{ list: never[]; templateList: never[]; }'.
+src/views/elderly/assess/synthetic-ability/Print.vue(842,27): error TS2345: Argument of type '{ list: never[]; templateList: never[]; }' is not assignable to parameter of type 'never'.
+src/views/elderly/assess/synthetic-ability/Print.vue(845,33): error TS2339: Property 'templateList' does not exist on type 'never'.
+src/views/elderly/assess/synthetic-ability/Print.vue(851,49): error TS2339: Property 'push' does not exist on type 'never'.
+src/views/elderly/assess/synthetic-ability/Print.vue(856,51): error TS2339: Property 'result' does not exist on type '{}'.
+src/views/elderly/assess/synthetic-ability/Print.vue(877,19): error TS2345: Argument of type 'never[]' is not assignable to parameter of type 'never'.
+src/views/elderly/assess/synthetic-ability/Print.vue(880,19): error TS2345: Argument of type 'any' is not assignable to parameter of type 'never'.
 src/views/elderly/assess/synthetic-ability/Sign.vue(98,11): error TS6133: 'res' is declared but its value is never read.
 src/views/elderly/assess/vital-sign/Form.vue(196,26): error TS6133: 'id' is declared but its value is never read.
 src/views/elderly/assess/vital-sign/Form.vue(196,30): error TS6133: 'detail' is declared but its value is never read.