feat(driver): 优化驾驶证信息必填规则和表单placeholder统一

- 驾驶证有效期必填加红星,长期有效去除必填红星,保持校验逻辑不变
- 统一司机弹窗所有输入框和选择框的placeholder为“请输入”或“请选择”,去除字段名
- 保留复合操作、日期范围起止及默认值说明的特例placeholder不变
- 新增3处自动生成结算单开关后悬浮问号图标和提示,增强用户理解
- customer-archive中新增customerKindOptions和映射,用于客户类别选择
- 提升archive-form label与控件垂直居中样式至全局,修正多页标签顶置问题
This commit is contained in:
2026-09-09 17:35:19 +08:00
parent 14c6ad0a31
commit 716ce43862
7 changed files with 110 additions and 23 deletions
+35
View File
@@ -150,6 +150,7 @@
- 注意:此前日志记录的「3 列 span=8」方案已被后续改动覆盖为 span=6,本次以当前文件实际状态为准。 - 注意:此前日志记录的「3 列 span=8」方案已被后续改动覆盖为 span=6,本次以当前文件实际状态为准。
## 客商档案:客商类型→客商分类 + 新增客商类型(customer-archive.vue ## 客商档案:客商类型→客商分类 + 新增客商类型(customer-archive.vue
- ⚠️ 补救(17:34):data 里的 `customerKindOptions`/`customerKindMap` 定义一度丢失(模板 v-for 空 → 下拉显示「无数据」,主人截图反馈),已补回 datacustomerTypeOptions 之后)。其余 8 处改动经 grep 确认完好。
- 需求:原「客商类型」(prop `customerType`,字典 customer_type:客户/承运商,多选)改名「客商分类」;另新增「客商类型」字段,取值为 外部客商 / 内部组织。 - 需求:原「客商类型」(prop `customerType`,字典 customer_type:客户/承运商,多选)改名「客商分类」;另新增「客商类型」字段,取值为 外部客商 / 内部组织。
- 约定:prop `customerType` 不动(其它页面 loading-manage / dept / business-crud-page 都以 `customerType:'承运商'` 作为后端查询参数),只改中文标签。 - 约定:prop `customerType` 不动(其它页面 loading-manage / dept / business-crud-page 都以 `customerType:'承运商'` 作为后端查询参数),只改中文标签。
- 新增字段 prop = **`customerKind`**(前端自定义命名,后端尚无对应字段,需后端在客商表加列并透传),值 `external`(外部客商) / `internal`(内部组织),单选下拉,默认 `external` - 新增字段 prop = **`customerKind`**(前端自定义命名,后端尚无对应字段,需后端在客商表加列并透传),值 `external`(外部客商) / `internal`(内部组织),单选下拉,默认 `external`
@@ -168,3 +169,37 @@
- 症状:driver.vue 司机弹窗「驾驶证主页/副页」「封面页/内容页」等证件上传项(el-form-item 内容高 151pxlabel 文字贴顶,普通 32px 控件行看不出来。 - 症状:driver.vue 司机弹窗「驾驶证主页/副页」「封面页/内容页」等证件上传项(el-form-item 内容高 151pxlabel 文字贴顶,普通 32px 控件行看不出来。
- 根因:`archive-form` class 被 driver / vehicle / ship / credit-score-quantification / loading-manage / waybill-import-dialog 等 8+ 页面复用,但「label 与控件垂直居中」规则 `.archive-form :deep(.el-form-item){align-items:center}` 锁在 customer-archive.vue 的 scoped 样式里,其它页面拿不到;flex 容器默认 stretchlabel(32px) 在高控件行内贴顶。 - 根因:`archive-form` class 被 driver / vehicle / ship / credit-score-quantification / loading-manage / waybill-import-dialog 等 8+ 页面复用,但「label 与控件垂直居中」规则 `.archive-form :deep(.el-form-item){align-items:center}` 锁在 customer-archive.vue 的 scoped 样式里,其它页面拿不到;flex 容器默认 stretchlabel(32px) 在高控件行内贴顶。
- 修复:规则提升到 `src/styles/element-ui.scss` 全局(紧跟 is-required 星号配套规则之后):`.archive-form .el-form-item { align-items:center }`。全站 archive-form 表单一并生效,label 与控件垂直居中。SCSS 编译通过。 - 修复:规则提升到 `src/styles/element-ui.scss` 全局(紧跟 is-required 星号配套规则之后):`.archive-form .el-form-item { align-items:center }`。全站 archive-form 表单一并生效,label 与控件垂直居中。SCSS 编译通过。
## 司机弹窗「驾驶证信息」:有效期必填、长期有效不必填(transportCapacity/driver.vue
- 需求:驾驶证信息区「有效期」(起/止,prop `drivingLicenseEndDate`)要必填(显示红*),「长期有效」复选框(prop `drivingLicenseLongTerm`)不必填(去掉红*)。
- 改动(formRules 两处):
1. `drivingLicenseEndDate` 规则加 `required: true``{ required: true, validator: this.validateDrivingLicenseEndDate, trigger: 'change' }`)——仅用于渲染红*,实际校验仍走自定义 validator。
2. 删除 `drivingLicenseLongTerm``{ required: true, message: '请选择驾驶证是否长期有效' }` 规则块。
- 关键机制(Element Plus 2.14 + async-validator 4.2.5 已核实):
- 红* 由 `form-item``isRequired = normalizedRules.some(rule => rule.required)` 决定,只要规则对象带 `required` 就显示。
- async-validator 的 `rule.validate` 中,若存在 `rule.validator`,直接走自定义 validator 并 return**不会**再执行 required 空值检查 → 勾「长期有效」时止日被清空/disabled 也不会误报必填。
- 校验逻辑保持原样:起始日期恒必填;`longTerm !== 1` 时止日必填。
- 未同步:「从业资格证信息」区仍是「有效期无红* + 长期有效有红*」的旧形态(待主人确认是否一并统一)。
## 司机弹窗 placeholder 统一为「请输入/请选择」(transportCapacity/driver.vue
- 需求:新增/修改司机弹窗输入框 placeholder 去掉后面的字段名称,只保留「请输入」「请选择」。
- 改动:18 处 placeholder 简化(7 处 → `请输入`10 处 → `请选择`1 处动态 cascader `driverForm.addressRegion || '请选择省市区'``|| '请选择'`)。
- **保留 4 处**(主人明确确认):
1. 驾驶车辆 `请输入或选择车辆` —— 复合操作(既可输入又可点按钮选车),去掉会丢语义。
2. 日期范围 `起` / `止` —— 起止标记,非字段提示。
3. 资格证号 `默认身份证号` —— 默认值说明,非「请输入」格式。
- 未动:表单**校验错误提示 message 仍带字段名**(如「请选择准驾车型」),因报错需指明具体字段,与 placeholder 定位不同;主人未要求,暂保持。
- 做法:Python 脚本按完整 placeholder 字符串精确替换(18 规则全命中,未匹配即 exit 1 中断),比逐个 Edit 高效且不会误伤。
- 校验:`@vue/compiler-sfc` parse + compileTemplate + compileScript 三项全通过。注意脚本必须放在**项目根目录**下运行(放 /tmp 解析不到 node_modules 里的 @vue/compiler-sfc)。
- ⚠️ git diff 显示的 `drivingLicenseLongTerm` 校验规则变动是**本次之前工作区已有的未提交改动**(见上一条日志),非本次引入,勿混淆。
## 「自动生成结算单」开关加 ? 悬浮提示(3 处)
- 需求:开关「开启」后面加问号图标,hover 提示「开启时,系统根据配置规则归集运单,定时生成结算单」。
- 改动文件(同一文案,为保持全站一致):
1. `src/views/business/components/business-crud-page.vue`1884 行附近,`.business-crud-page__settlement-switch`
2. `src/views/business/contract-manage.vue`357 行附近,`.contract-manage-form__settlement-switch`
3. `src/views/business/contract-manage-change.vue`55 行,`.settlement-switch`,原为单行长标签已拆行)
- 写法:`<el-radio :label="1">开启</el-radio>` 后插 `<el-tooltip content="..." placement="top"><el-icon class="settlement-switch-tip"><QuestionFilled /></el-icon></el-tooltip>`,再接「关闭」radio。
- 样式:`.settlement-switch-tip { margin:0 4px; color:#a8abb2; cursor:help; font-size:14px }`,各文件 scoped 样式内一份(contract-manage 写在 `&__settlement-switch` 内)。
- 注意:图标走 main.js 全局注册(@element-plus/icons-vue 全量注册),无需 import;写法与 `project-apply.vue``__label-tip` 一致。
- 这三个大文件本身已存在 prettier 格式问题(改动前 git HEAD 版本同样 fail),非本次引入。
+11
View File
@@ -2,6 +2,17 @@
## 设计约定(已确认) ## 设计约定(已确认)
### 表单 placeholder 统一为「请输入 / 请选择」(不带字段名)
- **规范**:输入框 placeholder 只写动作词 `请输入` / `请选择`,**后面不跟字段名称**(label 已说明是什么字段,重复无意义)。
- 已生效页面:waybill-manage、transport-plan、shipping-template、temporary-credit-limit(搜索栏 + 弹窗表单)、**driver.vue**2026-09-09 补齐 18 处)。
- **3 类例外必须保留,不要误简化**:
1. 复合操作:如 `请输入或选择车辆`(既可键盘输入又可点按钮弹窗选车),丢掉会损失语义。
2. 日期区间的 `起` / `止`:是起止标记,不是字段提示。
3. 默认值说明:如资格证号 `默认身份证号`、车牌号 `如:京A12345`,属于示例/默认值提示,非「请输入」格式。
- **校验错误提示 message 保持带字段名**(如「请选择准驾车型」「请输入档案编号」),与 placeholder 定位不同——报错必须指明具体哪个字段,不能只说「请选择」。
- Avue 配置化表单:在 option 各列加 `placeholder`;搜索栏可用通用工具 `withSearchPlaceholders([...])`(见 temporary-credit-limit.js)。
- 批量改法:Python 脚本按**完整 placeholder 字符串**做精确替换,每规则校验 `count > 0` 否则中断,避免误伤;改完用 `@vue/compiler-sfc` 的 parse + compileTemplate + compileScript 验证(脚本须放项目根目录下跑,放 /tmp 解析不到 node_modules)。
### 弹窗分组:统一用全局 `<section-card>` ### 弹窗分组:统一用全局 `<section-card>`
- 弹窗 body 背景由 `src/styles/element-ui.scss` 全局 `.el-dialog__body { background:#f5f6fa; padding:16px }` 统一灰底。 - 弹窗 body 背景由 `src/styles/element-ui.scss` 全局 `.el-dialog__body { background:#f5f6fa; padding:16px }` 统一灰底。
- 每组 = 白底卡片:4px 主色竖条 + 8px 间距 + 圆角 6px + 淡阴影。组件全局注册 `<section-card title="...">`,支持 `#title` / `#extra` slot。 - 每组 = 白底卡片:4px 主色竖条 + 8px 间距 + 圆角 6px + 淡阴影。组件全局注册 `<section-card title="...">`,支持 `#title` / `#extra` slot。
@@ -1883,6 +1883,12 @@
<span>自动生成结算单</span> <span>自动生成结算单</span>
<el-radio-group v-model="settlementRuleForm.autoGenerate" :disabled="dialogReadonly"> <el-radio-group v-model="settlementRuleForm.autoGenerate" :disabled="dialogReadonly">
<el-radio :label="1">开启</el-radio> <el-radio :label="1">开启</el-radio>
<el-tooltip
content="开启时,系统根据配置规则归集运单,定时生成结算单"
placement="top"
>
<el-icon class="settlement-switch-tip"><QuestionFilled /></el-icon>
</el-tooltip>
<el-radio :label="0">关闭</el-radio> <el-radio :label="0">关闭</el-radio>
</el-radio-group> </el-radio-group>
</div> </div>
@@ -14453,6 +14459,13 @@ export default {
color: #303133; color: #303133;
} }
.settlement-switch-tip {
margin: 0 4px;
color: #a8abb2;
cursor: help;
font-size: 14px;
}
&__module-form { &__module-form {
width: 100%; width: 100%;
+11 -1
View File
@@ -52,7 +52,16 @@
<el-tab-pane label="预结算配置" name="pre" /> <el-tab-pane label="预结算配置" name="pre" />
<el-tab-pane label="正式结算配置" name="formal" /> <el-tab-pane label="正式结算配置" name="formal" />
</el-tabs> </el-tabs>
<div class="settlement-switch"><span>自动生成结算单</span><el-radio-group v-model="settlementRule.autoGenerate"><el-radio :label="1">开启</el-radio><el-radio :label="0">关闭</el-radio></el-radio-group></div> <div class="settlement-switch">
<span>自动生成结算单</span>
<el-radio-group v-model="settlementRule.autoGenerate">
<el-radio :label="1">开启</el-radio>
<el-tooltip content="开启时,系统根据配置规则归集运单,定时生成结算单" placement="top">
<el-icon class="settlement-switch-tip"><QuestionFilled /></el-icon>
</el-tooltip>
<el-radio :label="0">关闭</el-radio>
</el-radio-group>
</div>
<el-form v-if="settlementRule.autoGenerate === 1" :model="settlementRule" label-position="right" label-width="auto" class="settlement-form"> <el-form v-if="settlementRule.autoGenerate === 1" :model="settlementRule" label-position="right" label-width="auto" class="settlement-form">
<el-form-item label="账单起始日期" required><el-date-picker v-model="settlementRule.billStartDate" type="date" placeholder="请选择账单起始日期" format="YYYY-MM-DD" value-format="YYYY-MM-DD" /></el-form-item> <el-form-item label="账单起始日期" required><el-date-picker v-model="settlementRule.billStartDate" type="date" placeholder="请选择账单起始日期" format="YYYY-MM-DD" value-format="YYYY-MM-DD" /></el-form-item>
<el-form-item label="结算类型" required><el-select v-model="settlementRule.settlementType" placeholder="请选择结算类型" @change="handleSettlementTypeChange"><el-option v-for="item in settlementTypeOptions" :key="item" :label="item" :value="item" /></el-select></el-form-item> <el-form-item label="结算类型" required><el-select v-model="settlementRule.settlementType" placeholder="请选择结算类型" @change="handleSettlementTypeChange"><el-option v-for="item in settlementTypeOptions" :key="item" :label="item" :value="item" /></el-select></el-form-item>
@@ -211,6 +220,7 @@ export default {
.attachment-upload .vehicle-attachment-upload { width: auto; } .attachment-upload .vehicle-attachment-upload { width: auto; }
.attachment-upload .el-upload { display: inline-flex; } .attachment-upload .el-upload { display: inline-flex; }
.settlement-switch { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; } .settlement-switch { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; }
.settlement-switch-tip { margin: 0 4px; color: #a8abb2; cursor: help; font-size: 14px; }
.settlement-form { display: grid; grid-template-columns: repeat(3, minmax(240px, 1fr)); gap: 8px 28px; } .settlement-form { display: grid; grid-template-columns: repeat(3, minmax(240px, 1fr)); gap: 8px 28px; }
.change-reason-section { margin: 20px 0; } .change-reason-section { margin: 20px 0; }
.page-footer { .page-footer {
+13
View File
@@ -356,6 +356,12 @@
<span>自动生成结算单</span> <span>自动生成结算单</span>
<el-radio-group v-model="settlementRuleForm.autoGenerate"> <el-radio-group v-model="settlementRuleForm.autoGenerate">
<el-radio :label="1">开启</el-radio> <el-radio :label="1">开启</el-radio>
<el-tooltip
content="开启时系统根据配置规则归集运单定时生成结算单"
placement="top"
>
<el-icon class="settlement-switch-tip"><QuestionFilled /></el-icon>
</el-tooltip>
<el-radio :label="0">关闭</el-radio> <el-radio :label="0">关闭</el-radio>
</el-radio-group> </el-radio-group>
</div> </div>
@@ -2758,6 +2764,13 @@ export default {
align-items: center; align-items: center;
gap: 20px; gap: 20px;
margin-bottom: 16px; margin-bottom: 16px;
.settlement-switch-tip {
margin: 0 4px;
color: #a8abb2;
cursor: help;
font-size: 14px;
}
} }
&__settlement-grid { &__settlement-grid {
+19 -22
View File
@@ -131,7 +131,7 @@
v-model="driverForm.driverName" v-model="driverForm.driverName"
maxlength="10" maxlength="10"
show-word-limit show-word-limit
placeholder="请输入司机姓名" placeholder="请输入"
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
@@ -141,7 +141,7 @@
v-model="driverForm.idCardNo" v-model="driverForm.idCardNo"
maxlength="18" maxlength="18"
show-word-limit show-word-limit
placeholder="请输入身份证号" placeholder="请输入"
@input="driverForm.idCardNo = String(driverForm.idCardNo || '').toUpperCase()" @input="driverForm.idCardNo = String(driverForm.idCardNo || '').toUpperCase()"
/> />
</el-form-item> </el-form-item>
@@ -152,13 +152,13 @@
v-model="driverForm.birthday" v-model="driverForm.birthday"
type="date" type="date"
value-format="YYYY-MM-DD" value-format="YYYY-MM-DD"
placeholder="请选择出生年月" placeholder="请选择"
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<el-form-item label="性别" prop="gender"> <el-form-item label="性别" prop="gender">
<el-select v-model="driverForm.gender" clearable placeholder="请选择性别"> <el-select v-model="driverForm.gender" clearable placeholder="请选择">
<el-option label="男" value="男" /> <el-option label="男" value="男" />
<el-option label="女" value="女" /> <el-option label="女" value="女" />
</el-select> </el-select>
@@ -166,7 +166,7 @@
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<el-form-item label="民族" prop="nation"> <el-form-item label="民族" prop="nation">
<el-select v-model="driverForm.nation" clearable filterable placeholder="请选择民族"> <el-select v-model="driverForm.nation" clearable filterable placeholder="请选择">
<el-option <el-option
v-for="item in nationOptions" v-for="item in nationOptions"
:key="item" :key="item"
@@ -182,7 +182,7 @@
v-model="driverForm.education" v-model="driverForm.education"
clearable clearable
filterable filterable
placeholder="请选择学历" placeholder="请选择"
> >
<el-option <el-option
v-for="item in educationOptions" v-for="item in educationOptions"
@@ -200,7 +200,7 @@
v-model="driverForm.addressRegionPath" v-model="driverForm.addressRegionPath"
:options="regionOptions" :options="regionOptions"
:props="regionCascaderProps" :props="regionCascaderProps"
:placeholder="driverForm.addressRegion || '请选择省市区'" :placeholder="driverForm.addressRegion || '请选择'"
clearable clearable
filterable filterable
@change="handleAddressRegionChange" @change="handleAddressRegionChange"
@@ -213,7 +213,7 @@
v-model="driverForm.address" v-model="driverForm.address"
maxlength="200" maxlength="200"
show-word-limit show-word-limit
placeholder="请输入详细地址" placeholder="请输入"
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
@@ -297,7 +297,7 @@
filterable filterable
allow-create allow-create
default-first-option default-first-option
placeholder="请选择准驾车型" placeholder="请选择"
> >
<el-option <el-option
v-for="item in drivingTypeOptions" v-for="item in drivingTypeOptions"
@@ -313,7 +313,7 @@
<el-input <el-input
v-model="driverForm.drivingLicenseNo" v-model="driverForm.drivingLicenseNo"
maxlength="50" maxlength="50"
placeholder="请输入档案编号" placeholder="请输入"
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
@@ -389,7 +389,7 @@
v-model="driverForm.qualificationType" v-model="driverForm.qualificationType"
clearable clearable
filterable filterable
placeholder="请选择从业资格认证类型" placeholder="请选择"
> >
<el-option <el-option
v-for="item in qualificationTypeOptions" v-for="item in qualificationTypeOptions"
@@ -415,7 +415,7 @@
v-model="driverForm.qualificationEndDate" v-model="driverForm.qualificationEndDate"
type="date" type="date"
value-format="YYYY-MM-DD" value-format="YYYY-MM-DD"
placeholder="请选择有效期" placeholder="请选择"
:disabled="driverForm.qualificationLongTerm === 1" :disabled="driverForm.qualificationLongTerm === 1"
@change="validateFormField('qualificationEndDate')" @change="validateFormField('qualificationEndDate')"
/> />
@@ -464,7 +464,7 @@
<el-row :gutter="18"> <el-row :gutter="18">
<el-col :span="6"> <el-col :span="6">
<el-form-item label="司机类型" prop="driverType"> <el-form-item label="司机类型" prop="driverType">
<el-select v-model="driverForm.driverType" filterable placeholder="请选择司机类型"> <el-select v-model="driverForm.driverType" filterable placeholder="请选择">
<el-option label="自有" value="自有" /> <el-option label="自有" value="自有" />
<el-option label="外协" value="外协" /> <el-option label="外协" value="外协" />
<el-option label="承运管理员" value="承运管理员" /> <el-option label="承运管理员" value="承运管理员" />
@@ -476,7 +476,7 @@
<el-input <el-input
v-model="driverForm.mobile" v-model="driverForm.mobile"
maxlength="20" maxlength="20"
placeholder="请输入手机号" placeholder="请输入"
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
@@ -485,7 +485,7 @@
<el-input <el-input
v-model="driverForm.emergencyContactName" v-model="driverForm.emergencyContactName"
maxlength="20" maxlength="20"
placeholder="请输入紧急联系人姓名" placeholder="请输入"
@input="driverForm.emergencyContactName = removeDigits($event)" @input="driverForm.emergencyContactName = removeDigits($event)"
/> />
</el-form-item> </el-form-item>
@@ -495,7 +495,7 @@
<el-input <el-input
v-model="driverForm.emergencyContactMobile" v-model="driverForm.emergencyContactMobile"
maxlength="20" maxlength="20"
placeholder="请输入紧急联系人手机号" placeholder="请输入"
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
@@ -507,7 +507,7 @@
filterable filterable
allow-create allow-create
default-first-option default-first-option
placeholder="请选择与联系人关系" placeholder="请选择"
> >
<el-option <el-option
v-for="item in relationOptions" v-for="item in relationOptions"
@@ -526,7 +526,7 @@
:props="organizationCascaderProps" :props="organizationCascaderProps"
filterable filterable
clearable clearable
placeholder="请选择 所属组织" placeholder="请选择"
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
@@ -813,10 +813,7 @@ export default {
drivingType: [{ required: true, message: '请选择准驾车型', trigger: 'change' }], drivingType: [{ required: true, message: '请选择准驾车型', trigger: 'change' }],
drivingLicenseNo: [{ required: true, message: '请输入档案编号', trigger: 'blur' }], drivingLicenseNo: [{ required: true, message: '请输入档案编号', trigger: 'blur' }],
drivingLicenseEndDate: [ drivingLicenseEndDate: [
{ validator: this.validateDrivingLicenseEndDate, trigger: 'change' }, { required: true, validator: this.validateDrivingLicenseEndDate, trigger: 'change' },
],
drivingLicenseLongTerm: [
{ required: true, message: '请选择驾驶证是否长期有效', trigger: 'change' },
], ],
qualificationType: [ qualificationType: [
{ required: true, message: '请选择从业资格认证类型', trigger: 'change' }, { required: true, message: '请选择从业资格认证类型', trigger: 'change' },
+8
View File
@@ -1645,6 +1645,14 @@ export default {
regionOptions: [], regionOptions: [],
customerNatureOptions: [], customerNatureOptions: [],
customerTypeOptions: [], customerTypeOptions: [],
customerKindOptions: [
{ label: '外部客商', value: 'external' },
{ label: '内部组织', value: 'internal' },
],
customerKindMap: {
external: '外部客商',
internal: '内部组织',
},
businessScopeOptions: [], businessScopeOptions: [],
qualificationTypeOptions: [], qualificationTypeOptions: [],
creditLevelOptions: ['A', 'B', 'C', 'D', 'E', 'F'].map(item => ({ creditLevelOptions: ['A', 'B', 'C', 'D', 'E', 'F'].map(item => ({