feat(customer-archive): 优化客商档案弹窗基本信息分组UI

- 将单一el-row拆分为工商信息、联系信息、财务/信用信息、其他信息四个分组
- 每个分组外层使用灰色背景块,内层字段区为白底卡片,提升视觉层次感
- 字段顺序重新调整,保留全部字段并优化布局,联系信息三项均分一行
- 新增.archive-form__group样式,定义背景色、圆角、内外边距及阴影效果
- 复用全局.dialog-section-title样式作为分组标题样式,保持风格统一
- 确保表单校验正常,vue/compiler-sfc解析无错误,标签闭合平衡
- 在项目长期记忆新增设计约定,明确弹窗字段分组的UI方案及理由
This commit is contained in:
2026-08-05 18:05:07 +08:00
parent f6451643e8
commit 4d0cc2d595
3 changed files with 266 additions and 193 deletions

View File

@@ -18,3 +18,13 @@
- 移除「第N行」前缀
- 失败原因编号从 `A. B. C.` 改为 `1. 2. 3.`
- 备注超长兜底提示也同步使用数字编号。
## UI 优化客商档案弹窗「基本信息」分组vehicle/customer-archive.vue
- 背景:原弹窗「基本信息」是一整块 `el-row` 平铺 20 个字段,信息密度过高。
- 方案(与用户确认):采用「分区标题平铺 + 浅色背景块」而非嵌套卡片(契合 AGENTS.md 去卡片化哲学;全局 `.dialog-section-title` 已自带 4px 主色竖条)。
- 改动:
- 模板:将单一 `el-row` 拆分为 4 个 `.archive-form__group`:工商信息 / 联系信息(3项 span=8) / 财务·信用信息 / 其他信息(备注 span=24);字段顺序重组但全部保留,地址、备注保持 span=24。
- 样式:新增 `.archive-form__group`#fafafa 背景、8px 圆角、16px/20px 内边距、组间距 16px子标题复用 `.dialog-section-title`14px/500
- 校验:@vue/compiler-sfc 解析 0 错误;标签闭合平衡。
-此前讨论中提及的「Tab 内容加内边距」「材料区标题竖条」未纳入本次(材料区已用 dialog-section-title如需可后续追加。

View File

@@ -0,0 +1,16 @@
# 项目长期记忆tms-erp-web-ws / Saber3 客商模块)
## 设计约定(经用户确认的决策)
- 弹窗内多字段表单分组:**「灰色分组块 + 白色内层卡片」双色方案**(用户最终确认)。
- 外层 `.archive-form__group`:灰底 `#f0f2f5` + 8px 圆角 + `16px 20px` 内边距,分组标题(`.dialog-section-title`,自带 4px 主色竖条)浮于灰底上。
- 内层字段区:直接把分组内唯一的 `> .el-row` 设为白底(`#fff` + 6px 圆角 + 极淡阴影 `0 1px 2px rgba(0,0,0,.04)` + `18px 16px 4px` 内边距),形成「白卡片浮于灰底」的层次,而非逐个输入框强制白底。
- 理由:契合 AGENTS.md「去卡片化」哲学不嵌套 `el-card`),同时用户明确要灰底+白内容对比,全局 `.dialog-section-title` 直接复用即可。
- 适用:新增/编辑弹窗中字段超过 ~10 个、需按业务语义分组时。
- 联系信息等仅 3 项的分组,可用 `el-col :span="8"` 让三项均分一行,避免 4 列网格留白。
## 关键文件
- 客商档案弹窗:`src/views/vehicle/customer-archive.vue`(手写 `el-form`,非 Avue option 驱动;分组结构见 `.archive-form__group`)。
- 全局弹窗分区标题规范:`src/styles/element-ui.scss``.dialog-section-title`
## 环境注意
- Vite 代理跨域Saber3 axios 开 `withCredentials=true`,直连绝对地址后端须让 CORS `Allow-Origin` 为具体域名(不能用 `*`),否则浏览器拒绝。改 `.env`/`vite.config.mjs` 必须重启 dev。

View File

@@ -143,201 +143,222 @@
class="archive-form"
>
<div class="section-title dialog-section-title">基本信息</div>
<el-row :gutter="18">
<el-col :span="6">
<el-form-item label="客商编号" prop="customerCode">
<el-input v-model="archiveForm.customerCode" disabled placeholder="保存后自动生成" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="客商性质" prop="customerNature">
<el-select v-model="archiveForm.customerNature" filterable clearable>
<el-option
v-for="item in customerNatureOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="统一社会信用代码" prop="unifiedCreditCode">
<el-input
v-model="archiveForm.unifiedCreditCode"
maxlength="18"
show-word-limit
@input="
archiveForm.unifiedCreditCode = String(
archiveForm.unifiedCreditCode || ''
).toUpperCase()
"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="客商名称" prop="fullName">
<el-input v-model="archiveForm.fullName" maxlength="100" show-word-limit />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="客商类型" prop="customerType">
<el-select v-model="archiveForm.customerType" filterable clearable>
<el-option
v-for="item in customerTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="注册资金(万元)" prop="registeredCapital">
<el-input
v-model="archiveForm.registeredCapital"
maxlength="18"
@input="value => handleDecimalInput('registeredCapital', value)"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="企业法人" prop="legalPerson">
<el-input v-model="archiveForm.legalPerson" maxlength="10" show-word-limit />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="经营范围" prop="businessScope">
<el-select v-model="archiveForm.businessScope" multiple filterable clearable>
<el-option
v-for="item in businessScopeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="客商负责人" prop="principal">
<el-input v-model="archiveForm.principal" maxlength="10" show-word-limit />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="联系电话" prop="contactPhone">
<el-input v-model="archiveForm.contactPhone" maxlength="20" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="所属组织" prop="deptName">
<el-tree-select
v-model="archiveForm.deptId"
:data="deptTree"
:props="{ label: 'label', value: 'value', children: 'children' }"
multiple
node-key="value"
check-strictly
filterable
clearable
:render-after-expand="false"
@change="onDeptChange"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="营业期限" prop="businessEndDate" required>
<div class="business-term-field">
<el-date-picker
v-model="archiveForm.businessEndDate"
type="date"
value-format="YYYY-MM-DD"
:disabled="archiveForm.businessTermType === '长期'"
@change="handleBusinessEndDateChange"
/>
<el-checkbox
:model-value="archiveForm.businessTermType === '长期'"
@change="handleNoFixedTermChange"
>
无固定期限
</el-checkbox>
</div>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="开票税点" prop="invoiceTaxRate">
<el-input
v-model="archiveForm.invoiceTaxRate"
maxlength="5"
@input="value => handleDecimalInput('invoiceTaxRate', value, 13)"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="助记码" prop="mnemonicCode">
<el-input v-model="archiveForm.mnemonicCode" maxlength="15" show-word-limit />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="客户简称" prop="shortName">
<el-input v-model="archiveForm.shortName" maxlength="20" show-word-limit />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="客户等级" prop="customerLevel">
<el-input v-model="archiveForm.customerLevel" disabled />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="最大资金使用额度(万元)" prop="maxCreditLimit">
<el-input
v-model="archiveForm.maxCreditLimit"
maxlength="18"
disabled
@input="value => handleDecimalInput('maxCreditLimit', value)"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="申请总资金使用额度(万元)" prop="applyCreditLimit">
<el-input
v-model="archiveForm.applyCreditLimit"
maxlength="18"
@input="value => handleDecimalInput('applyCreditLimit', value)"
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="地址" prop="registeredDetailAddress">
<div class="archive-form__address">
<el-cascader
ref="registeredRegionCascader"
v-model="archiveForm.registeredRegionPath"
:options="regionOptions"
:props="regionCascaderProps"
:placeholder="archiveForm.registeredRegionName || '请选择省市区'"
clearable
filterable
@change="handleRegisteredRegionChange"
/>
<div class="archive-form__group">
<div class="dialog-section-title archive-form__group-title">工商信息</div>
<el-row :gutter="18">
<el-col :span="6">
<el-form-item label="客商编号" prop="customerCode">
<el-input v-model="archiveForm.customerCode" disabled placeholder="保存后自动生成" />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="客商性质" prop="customerNature">
<el-select v-model="archiveForm.customerNature" filterable clearable>
<el-option
v-for="item in customerNatureOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="统一社会信用代码" prop="unifiedCreditCode">
<el-input
v-model="archiveForm.registeredDetailAddress"
maxlength="255"
placeholder="请输入详细地址"
v-model="archiveForm.unifiedCreditCode"
maxlength="18"
show-word-limit
@input="
archiveForm.unifiedCreditCode = String(
archiveForm.unifiedCreditCode || ''
).toUpperCase()
"
/>
</div>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input
v-model="archiveForm.remark"
type="textarea"
maxlength="500"
show-word-limit
/>
</el-form-item>
</el-col>
</el-row>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="客商名称" prop="fullName">
<el-input v-model="archiveForm.fullName" maxlength="100" show-word-limit />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="客商类型" prop="customerType">
<el-select v-model="archiveForm.customerType" filterable clearable>
<el-option
v-for="item in customerTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="企业法人" prop="legalPerson">
<el-input v-model="archiveForm.legalPerson" maxlength="10" show-word-limit />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="经营范围" prop="businessScope">
<el-select v-model="archiveForm.businessScope" multiple filterable clearable>
<el-option
v-for="item in businessScopeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="营业期限" prop="businessEndDate" required>
<div class="business-term-field">
<el-date-picker
v-model="archiveForm.businessEndDate"
type="date"
value-format="YYYY-MM-DD"
:disabled="archiveForm.businessTermType === '长期'"
@change="handleBusinessEndDateChange"
/>
<el-checkbox
:model-value="archiveForm.businessTermType === '长期'"
@change="handleNoFixedTermChange"
>
无固定期限
</el-checkbox>
</div>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="助记码" prop="mnemonicCode">
<el-input v-model="archiveForm.mnemonicCode" maxlength="15" show-word-limit />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="客户简称" prop="shortName">
<el-input v-model="archiveForm.shortName" maxlength="20" show-word-limit />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="地址" prop="registeredDetailAddress">
<div class="archive-form__address">
<el-cascader
ref="registeredRegionCascader"
v-model="archiveForm.registeredRegionPath"
:options="regionOptions"
:props="regionCascaderProps"
:placeholder="archiveForm.registeredRegionName || '请选择省市区'"
clearable
filterable
@change="handleRegisteredRegionChange"
/>
<el-input
v-model="archiveForm.registeredDetailAddress"
maxlength="255"
placeholder="请输入详细地址"
/>
</div>
</el-form-item>
</el-col>
</el-row>
</div>
<div class="archive-form__group">
<div class="dialog-section-title archive-form__group-title">联系信息</div>
<el-row :gutter="18">
<el-col :span="8">
<el-form-item label="客商负责人" prop="principal">
<el-input v-model="archiveForm.principal" maxlength="10" show-word-limit />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="联系电话" prop="contactPhone">
<el-input v-model="archiveForm.contactPhone" maxlength="20" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="所属组织" prop="deptName">
<el-tree-select
v-model="archiveForm.deptId"
:data="deptTree"
:props="{ label: 'label', value: 'value', children: 'children' }"
multiple
node-key="value"
check-strictly
filterable
clearable
:render-after-expand="false"
@change="onDeptChange"
/>
</el-form-item>
</el-col>
</el-row>
</div>
<div class="archive-form__group">
<div class="dialog-section-title archive-form__group-title">财务/信用信息</div>
<el-row :gutter="18">
<el-col :span="6">
<el-form-item label="注册资金(万元)" prop="registeredCapital">
<el-input
v-model="archiveForm.registeredCapital"
maxlength="18"
@input="value => handleDecimalInput('registeredCapital', value)"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="开票税点" prop="invoiceTaxRate">
<el-input
v-model="archiveForm.invoiceTaxRate"
maxlength="5"
@input="value => handleDecimalInput('invoiceTaxRate', value, 13)"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="客户等级" prop="customerLevel">
<el-input v-model="archiveForm.customerLevel" disabled />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="最大资金使用额度(万元)" prop="maxCreditLimit">
<el-input
v-model="archiveForm.maxCreditLimit"
maxlength="18"
disabled
@input="value => handleDecimalInput('maxCreditLimit', value)"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="申请总资金使用额度(万元)" prop="applyCreditLimit">
<el-input
v-model="archiveForm.applyCreditLimit"
maxlength="18"
@input="value => handleDecimalInput('applyCreditLimit', value)"
/>
</el-form-item>
</el-col>
</el-row>
</div>
<div class="archive-form__group">
<div class="dialog-section-title archive-form__group-title">其他信息</div>
<el-row :gutter="18">
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input
v-model="archiveForm.remark"
type="textarea"
maxlength="500"
show-word-limit
/>
</el-form-item>
</el-col>
</el-row>
</div>
</el-form>
<el-tabs v-model="activeTab" class="archive-tabs">
@@ -3279,6 +3300,29 @@ export default {
}
}
.archive-form__group {
padding: 16px 20px;
margin-bottom: 16px;
background-color: #f0f2f5;
border-radius: 8px;
.dialog-section-title {
margin: 0 0 12px;
font-size: 14px;
font-weight: 500;
color: #303133;
}
// 内层白色卡片:字段区域白底,浮于灰色分组块之上
> .el-row {
margin: 0;
padding: 18px 16px 4px;
background-color: #fff;
border-radius: 6px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
}
}
.business-term-field,
.archive-form__address {
display: flex;
@@ -3624,6 +3668,9 @@ export default {
width: 100%;
}
.archive-dialog{
background-color: #ff0000;
}
:deep(.archive-dialog .el-dialog__body) {
max-height: 72vh;
overflow: auto;