浏览代码

Merge branch 'master' of http://47.107.245.0:3000/xiongxing/kyj-yanglao-web-new

xiongxing 3 小时之前
父节点
当前提交
ef8808275f

+ 40 - 0
.workbuddy/memory/2026-10-08.md

@@ -0,0 +1,40 @@
+# 2026-10-08 工作日志
+
+## 合同「四大费用」名称改为可编辑
+
+用户要求:`ContractBody.vue` 与 `ContractBodyHubei.vue` 两处「四大费用」(月度费用列表)行的费用名称要能修改,默认值就是当前系统名称。
+
+### 实现方式
+不改后端字段、不动接口契约 —— 在费用项对象上新增一个 `customName` 覆盖字段:
+
+- 新增 `getExpenseName(item)`:有 `customName` 就用它,否则回落到 `displayExpenseName(item)` 去掉末尾「¥」(「¥」由模板统一输出,避免重复)。
+- 新增 `setExpenseName(item, val)`:写入 `customName`;**若改回默认值或清空,则把 `customName` 置为 `''`**,让显示继续走系统默认名 —— 这样合同 JSON 里不会堆积与默认值重复的脏数据。
+- 模板把原来的纯文本 `{{ displayExpenseName(item) }}:` 换成 `<FieldItem :model-value="getExpenseName(item)" @update:model-value="..." />¥:`。
+  注意:模板里用了带 TS 类型的箭头函数 `(val: any) => setExpenseName(item, val)`,Vite 能正确编译(已实测)。
+
+### 关键上下文(改这块前必看)
+- `monthlyExpenses` 整体被 `JSON.stringify` 进 `contractInfo` 存后端(`ContractBody.vue:597`),所以 `customName` 天然随数组持久化,**无需后端改字段**。回读走 `Object.assign(contractForm, parsedData)`。
+- 已有 `isManual` 分支(点「+ 添加费用」加的行)本来就是名称可编辑的,本次只补齐系统项(`isManual` 为 false 的四大费用)。
+- `addMonthlyExpense` / `removeMonthlyExpense` 在两个文件里各有一份同名实现,改动时注意两文件同步。
+
+### 验证
+- 两个组件 `@vue/compiler-sfc` 完整编译(template + script):`ALL COMPILE OK`。
+- Vite dev server 逐个 transform:均 `HTTP 200`(约 340KB),无 transform error。
+- 7 个行为场景实跑通过:默认名回退、复合名(`四大费用-床位费(四人间)`)、自定义生效、改回默认值清空 `customName`、清空输入不显示空白、两侧空格读取 trim、JSON 往返后自定义名保留。
+
+## 追加:「一次性收取的费用」名称同样改为可编辑
+
+用户继续要求把 `(2)一次性收取的费用`(`oneTimeExpensesList`,如「押金(预备金10000)¥」)的名称也改成可编辑。复用上一节的 `getExpenseName` / `setExpenseName`,两文件模板各改一处:
+- `ContractBody.vue` oneTime 区块、`ContractBodyHubei.vue` oneTime 区块
+- 原来:`{{ numberToCircle(index + 1) }}{{ displayExpenseName(item) }}`
+- 现在:`{{ numberToCircle(index + 1) }}<FieldItem :model-value="getExpenseName(item)" ... @update:model-value="(val:any)=>setExpenseName(item,val)" />¥`
+
+###⚠️ 押金判定不受改名影响(重要设计点)
+`isDepositFee(item)` 判据是 `item.isDeposit === 1 || itemCategoryName 含「押金」 || itemName 含「押金」`,
+**读的是原始字段、不是 `customName`** —— 这是有意为之:押金行对应的「全额退还 + 计息/不计息」条款是合同法务语义,
+必须锚定在系统费用项本体上,不能因为用户改了显示名就丢失或误触发。实测:押金改名成「履约保证金」后条款仍在;
+非押金项改名成含「押金」字样也不会误触发。改这块时**不要**把 `isDepositFee` 改成读 `customName`。
+
+### 验证
+- 两组件 SFC 完整编译 + Vite transform 均 `HTTP 200`/无错。
+- 实跑确认:押金默认名正确、改名后条款保留、JSON 往返后自定义名保留、误触发防护有效。

+ 35 - 2
src/views/elderly/contracts/components/ContractBody.vue

@@ -784,6 +784,24 @@ const displayExpenseName = (item: any): string => {
   return label ? `${label}¥` : ''
 }
 
+/**
+ * 月度费用名称(四大费用等系统项)的可编辑取值。
+ * 默认沿用系统费用名称(displayExpenseName 去掉末尾「¥」,「¥」由模板统一输出),
+ * 用户改过后写入 item.customName,随 contractInfo 一并持久化。
+ */
+const getExpenseName = (item: any): string => {
+  const custom = (item?.customName ?? '').toString().trim()
+  if (custom) return custom
+  return displayExpenseName(item).replace(/¥$/, '')
+}
+
+/** 写入自定义费用名称;清空或改回默认值时回落为'',让显示继续走系统默认名 */
+const setExpenseName = (item: any, val: any) => {
+  if (!item) return
+  const next = (val ?? '').toString()
+  item.customName = next.trim() === getExpenseName({ ...item, customName: '' }) ? '' : next
+}
+
 const bedList = ref<{ chargeName: string; id: string; price: number }[]>([])
 
 const loadBedTypes = async () => {
@@ -1426,7 +1444,14 @@ watch(
             />¥:
           </template>
           <template v-else>
-            {{ displayExpenseName(item) }}:
+            <FieldItem
+              :model-value="getExpenseName(item)"
+              :is-text-mode="isTextMode"
+              width="220px"
+              placeholder="请输入费用名称"
+              no-underline
+              @update:model-value="(val: any) => setExpenseName(item, val)"
+            />¥:
           </template>
           <FieldItem
             v-model="item.actualAmount"
@@ -1499,7 +1524,15 @@ watch(
 
       <div class="page-content">
         <div v-for="(item, index) in oneTimeExpensesList" :key="item.id" class="content-text">
-          &nbsp;&nbsp;&nbsp;&nbsp;{{ numberToCircle(index + 1) }}{{ displayExpenseName(item) }}
+          &nbsp;&nbsp;&nbsp;&nbsp;{{ numberToCircle(index + 1)
+          }}<FieldItem
+            :model-value="getExpenseName(item)"
+            :is-text-mode="isTextMode"
+            width="200px"
+            placeholder="请输入费用名称"
+            no-underline
+            @update:model-value="(val: any) => setExpenseName(item, val)"
+          />¥
           <FieldItem
             v-model="item.actualAmount"
             :is-text-mode="isTextMode"

+ 35 - 2
src/views/elderly/contracts/components/ContractBodyHubei.vue

@@ -848,6 +848,24 @@ const displayExpenseName = (item: any): string => {
   return label ? `${label}¥` : ''
 }
 
+/**
+ * 月度费用名称(四大费用等系统项)的可编辑取值。
+ * 默认沿用系统费用名称(displayExpenseName 去掉末尾「¥」,「¥」由模板统一输出),
+ * 用户改过后写入 item.customName,随 contractInfo 一并持久化。
+ */
+const getExpenseName = (item: any): string => {
+  const custom = (item?.customName ?? '').toString().trim()
+  if (custom) return custom
+  return displayExpenseName(item).replace(/¥$/, '')
+}
+
+/** 写入自定义费用名称;清空或改回默认值时回落为'',让显示继续走系统默认名 */
+const setExpenseName = (item: any, val: any) => {
+  if (!item) return
+  const next = (val ?? '').toString()
+  item.customName = next.trim() === getExpenseName({ ...item, customName: '' }) ? '' : next
+}
+
 const bedList = ref<{ chargeName: string; id: string; price: number }[]>([])
 
 const loadBedTypes = async () => {
@@ -1482,7 +1500,14 @@ watch(
             />¥:
           </template>
           <template v-else>
-            {{ displayExpenseName(item) }}:
+            <FieldItem
+              :model-value="getExpenseName(item)"
+              :is-text-mode="isTextMode"
+              width="220px"
+              placeholder="请输入费用名称"
+              no-underline
+              @update:model-value="(val: any) => setExpenseName(item, val)"
+            />¥:
           </template>
           <FieldItem
             v-model="item.actualAmount"
@@ -1553,7 +1578,15 @@ watch(
         </p>
         <p class="content-text"> &nbsp;&nbsp;&nbsp;&nbsp;(2)一次性收取的费用(如有请填写)。 </p>
         <div v-for="(item, index) in oneTimeExpensesList" :key="item.id" class="content-text">
-          &nbsp;&nbsp;&nbsp;&nbsp;{{ numberToCircle(index + 1) }}{{ displayExpenseName(item) }}
+          &nbsp;&nbsp;&nbsp;&nbsp;{{ numberToCircle(index + 1)
+          }}<FieldItem
+            :model-value="getExpenseName(item)"
+            :is-text-mode="isTextMode"
+            width="200px"
+            placeholder="请输入费用名称"
+            no-underline
+            @update:model-value="(val: any) => setExpenseName(item, val)"
+          />¥
           <FieldItem
             v-model="item.actualAmount"
             :is-text-mode="isTextMode"