2 Commits

Author SHA1 Message Date
gxwebsoft 006dc95bf0 Merge remote-tracking branch 'origin/master' 2026-09-09 15:11:56 +08:00
gxwebsoft d187da68d2 fix(contract-manage): 优化合同管理页面布局及字典数据读取
- 合同文件「批量下载」按钮调整为与标题同排且垂直居中
- 修改内联组件render函数结构及相关样式,统一合同文件和其它附件布局
- 合同格式下拉改为动态读取业务字典contractFormat,保留静态兜底
- 合同类别contractCategory下拉改为业务字典读取,替代硬编码选项
- 表单增加所属组织、签订日期、回款账期、开票周期、结算币种等字段
- 调整字段顺序,完善表单输入限制和控件属性
- 替换部分按钮类型,优化用户交互体验
- 修复detailValue字典映射,显示合同格式标签
- 修正样式`:deep`选择器,解决scoped样式对子组件无效问题
2026-09-09 15:11:53 +08:00
2 changed files with 113 additions and 55 deletions
+28
View File
@@ -55,3 +55,31 @@
- api 新增 `getFundRiskStats` → GET /blade-transport/project-apply/fund-risk-stats。 - api 新增 `getFundRiskStats` → GET /blade-transport/project-apply/fund-risk-stats。
- 后端契约(未改,待确认数据来源):ProjectApplyVO 补齐 `fundUseRisk`(high/medium/none)、`fundUseRiskName``fundUseRate``usedFundLimit`/`remainingFundLimit`;列表 selectProjectApplyPage 填充每行风险 + buildQuery 支持 `fundUseRisk` 过滤;新增 `/fund-risk-stats` 返回 {high,medium,none}。 - 后端契约(未改,待确认数据来源):ProjectApplyVO 补齐 `fundUseRisk`(high/medium/none)、`fundUseRiskName``fundUseRate``usedFundLimit`/`remainingFundLimit`;列表 selectProjectApplyPage 填充每行风险 + buildQuery 支持 `fundUseRisk` 过滤;新增 `/fund-risk-stats` 返回 {high,medium,none}。
- ⚠️ 关键阻塞:系统里项目与任何费用表(运输计划/运单/账单/客商)均无关联字段,`usedFundLimit`(已使用资金) 无真实取数来源;临时额度模块的 project-apply/list 返回里 usedFundLimit 实际为空。需业务方确认「已使用资金」口径后才能算风险等级,否则列表风险列全空、统计为 0。 - ⚠️ 关键阻塞:系统里项目与任何费用表(运输计划/运单/账单/客商)均无关联字段,`usedFundLimit`(已使用资金) 无真实取数来源;临时额度模块的 project-apply/list 返回里 usedFundLimit 实际为空。需业务方确认「已使用资金」口径后才能算风险等级,否则列表风险列全空、统计为 0。
## 合同文件「批量下载」按钮与标题同排(contract-manage.vue 内联 AttachmentSection
- 需求:编辑页「合同文件」分组里「批量下载」按钮目前独立一行、靠右偏上,高度与标题区错位;要放到「合同文件」标题右侧同排、垂直居中。
- 修复:`const AttachmentSection = defineComponent(...)` 内联组件 render() —— 原结构是 `div.dialog-section-title`(标题) 与 `div.contract-manage-form__attachment-head`(批量下载按钮) 两个并列 div;改为把标题与按钮放进同一个 `contract-manage-form__attachment-head` flex 容器(标题左、按钮右)。
- 样式:`:deep(.contract-manage-form__attachment-head)``justify-content:flex-end` 改为 `space-between` + `align-items:center`,并 `.dialog-section-title { margin-bottom:0 }` 抵消标题自身 20px 下边距(2674 行 `:deep(.dialog-section-title)` 会命中子组件渲染的标题)。
- 注意:AttachmentSection 用 render 函数产出 DOM,不带当前组件 scoped data 属性,因此样式必须走 `:deep()`scoped 的 `&__billing-head, &__attachment-head`(2723) 只作用于当前组件模板内(计费信息),对子组件 DOM 无效。
- 影响:4 处使用(编辑/详情 × 合同文件/其它附件)布局统一变为「标题 + 批量下载」同排。
- 主人二次反馈「下面的其它附件也一样」——确认 L463(编辑/其它附件 description) / L765(详情/其它附件 description+readonly) 与 L313(编辑/合同文件) / L671(详情/合同文件) 都走同一份 render+样式,无需新代码;指挥浏览器 Cmd+Shift+R 硬刷即可看到效果(render 函数组件的 :deep() 样式改 HMR 经常不传播)。
## 合同格式下拉改读业务字典 contractFormatcontract-manage.vue
- 需求:新增/编辑合同管理页「合同格式」下拉由硬编码改为读取业务字典 `contractFormat`
- 原实现:`option/business/contract-manage.js``contractFormat``dicData` 硬编码 `[{label:'电子合同',value:'电子合同'},{label:'纸质合同',value:'纸质合同'}]`view 里 `contractFormatOptions` computed 直接 `columnOptions('contractFormat')` 读 column.dicData。
- 修复(4 处):
1. data 加 `contractFormatDictOptions: []`(与 settlementModeDictOptions 并列)。
2. computed `contractFormatOptions` 改为优先 `contractFormatDictOptions`,为空回退 `columnOptions('contractFormat')`(保留静态兜底)。
3. `loadSettlementDictionaries``Promise.all` 追加 `getBizDictionary({ code: 'contractFormat' })`,用 `records()` 映射 `{label:dictValue,value:dictKey}` 填充 `contractFormatDictOptions`;再**条件覆盖** column.dicData(仅当字典非空时),避免字典未配时清掉静态兜底。
4. `detailValue` 的字典映射分支加入 `prop === 'contractFormat'`,详情页「合同格式」也走 `displayStatus` 显示 label。
- 后端契约:`getBizDictionary``GET /blade-system/dict-biz/dictionary?code=contractFormat`,返回 `List<DictBiz>{code,dictKey,dictValue,remark}`DictBizController#dictionary 按 code 精确匹配)。字典 code 按用户口述用驼峰 `contractFormat`(现有 settle_method 为下划线,若后台实际是 `contract_format` 需调整 code)。
- 参考模式:与 `settlementMode`settle_method)完全一致。
## 合同类别 contractCategory 也读业务字典(contract-manage.vue,续上)
- 需求:模板里 label 实为「合同类型」(prop `contractCategory`),下拉由硬编码 `common.js#contractCategoryOptions`(客户合同/承运商合同/补充协议,value 中文)改为读业务字典 `contractCategory`
- 修复(3 处,与 contractFormat 同模式):
1. data 加 `contractCategoryDictOptions: []`
2. computed `contractCategoryOptions` 改为优先 `contractCategoryDictOptions`,空回退 `columnOptions('contractCategory')`(静态兜底)。
3. `loadSettlementDictionaries``Promise.all` 追加 `getBizDictionary({ code: 'contractCategory' })`,映射填充后**条件覆盖** `contractCategory` 列 dicData(仅非空时)。
- 列表页列显示/搜索下拉、详情页(detailValue 已有 `prop==='contractCategory'` 分支走 displayStatus)均因 column.dicData 覆盖而自动跟随业务字典。
- 注意:`config.defaultForm.contractCategory: '客户合同'`option/contract-manage.js)为硬编码中文默认值,若业务字典 dictKey 非中文需同步调整。
+85 -55
View File
@@ -163,6 +163,17 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="所属组织" prop="organizationName">
<el-cascader
v-model="organizationCascaderValue"
:options="organizationOptions"
:props="organizationCascaderProps"
placeholder="请选择"
clearable
filterable
@visible-change="visible => visible && loadOrganizationOptions()"
/>
</el-form-item>
<el-form-item label="甲方" prop="partyA"> <el-form-item label="甲方" prop="partyA">
<el-select <el-select
v-model="form.partyA" v-model="form.partyA"
@@ -197,6 +208,15 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="签订日期" prop="signDate">
<el-date-picker
v-model="form.signDate"
type="date"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
placeholder="请选择签订日期"
/>
</el-form-item>
<el-form-item label="合同期限" prop="startDate"> <el-form-item label="合同期限" prop="startDate">
<div class="contract-manage-form__date-range"> <div class="contract-manage-form__date-range">
<el-date-picker <el-date-picker
@@ -216,28 +236,29 @@
/> />
</div> </div>
</el-form-item> </el-form-item>
<el-form-item label="所属组织" prop="organizationName"> <el-form-item label="回款账期">
<el-cascader <el-input
v-model="organizationCascaderValue" v-model="form.paymentDays"
:options="organizationOptions" placeholder="请输入"
:props="organizationCascaderProps" @input="value => integerInput('paymentDays', value)"
placeholder="请选择" ><template #suffix>天</template></el-input>
clearable
filterable
@visible-change="visible => visible && loadOrganizationOptions()"
/>
</el-form-item> </el-form-item>
<el-form-item label="经办人"> <el-form-item label="开票周期">
<el-input v-model="form.handlerUserName" disabled /> <el-input
v-model="form.invoiceCycle"
placeholder="请输入"
@input="value => positiveIntegerInput('invoiceCycle', value)"
><template #suffix>天</template></el-input>
</el-form-item> </el-form-item>
<el-form-item label="签订日期" prop="signDate"> <el-form-item label="结算币种" prop="settlementCurrency">
<el-date-picker <el-select v-model="form.settlementCurrency" placeholder="请选择结算币种" clearable>
v-model="form.signDate" <el-option
type="date" v-for="item in settlementCurrencyOptions"
format="YYYY-MM-DD" :key="item.value"
value-format="YYYY-MM-DD" :label="item.label"
placeholder="请选择签订日期" :value="item.value"
/> />
</el-select>
</el-form-item> </el-form-item>
<el-form-item label="合同格式" prop="contractFormat"> <el-form-item label="合同格式" prop="contractFormat">
<el-select v-model="form.contractFormat" placeholder="请选择合同格式"> <el-select v-model="form.contractFormat" placeholder="请选择合同格式">
@@ -249,28 +270,18 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="是否需要加盖法人章" prop="legalSealFlag">
<el-select v-model="form.legalSealFlag" placeholder="请选择">
<el-option label="" :value="0" />
<el-option label="" :value="1" />
</el-select>
</el-form-item>
<el-form-item label="一式"> <el-form-item label="一式">
<el-input <el-input
v-model="form.copyCount" v-model="form.copyCount"
placeholder="请输入" placeholder="请输入"
@input="value => integerInput('copyCount', value)" @input="value => integerInput('copyCount', value)"
><template #suffix>份</template></el-input ><template #suffix>份</template></el-input
> >
</el-form-item> </el-form-item>
<el-form-item label="结算币种" prop="settlementCurrency"> <el-form-item label="是否需要加盖法人章" prop="legalSealFlag">
<el-select v-model="form.settlementCurrency" placeholder="请选择结算币种" clearable> <el-select v-model="form.legalSealFlag" placeholder="请选择">
<el-option <el-option label="" :value="0" />
v-for="item in settlementCurrencyOptions" <el-option label="" :value="1" />
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="结算方式" prop="settlementMode"> <el-form-item label="结算方式" prop="settlementMode">
@@ -283,19 +294,8 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="开票周期"> <el-form-item label="经办人">
<el-input <el-input v-model="form.handlerUserName" disabled />
v-model="form.invoiceCycle"
placeholder="请输入"
@input="value => positiveIntegerInput('invoiceCycle', value)"
><template #suffix>天</template></el-input>
</el-form-item>
<el-form-item label="回款账期">
<el-input
v-model="form.paymentDays"
placeholder="请输入"
@input="value => integerInput('paymentDays', value)"
><template #suffix>天</template></el-input>
</el-form-item> </el-form-item>
</div> </div>
<el-form-item label="备注" prop="remark" class="contract-manage-form__remark"> <el-form-item label="备注" prop="remark" class="contract-manage-form__remark">
@@ -431,7 +431,7 @@
<el-icon style="margin-left: 4px; color: #909399; cursor: pointer"><el-icon-question-filled /></el-icon> <el-icon style="margin-left: 4px; color: #909399; cursor: pointer"><el-icon-question-filled /></el-icon>
</el-tooltip> </el-tooltip>
</div> </div>
<el-button type="primary" plain @click="addPaymentRatio">添加</el-button> <el-button type="primary" @click="addPaymentRatio">添加</el-button>
</div> </div>
<el-table :data="paymentRatioRows" border> <el-table :data="paymentRatioRows" border>
<el-table-column type="index" label="序号" width="80" align="center" /> <el-table-column type="index" label="序号" width="80" align="center" />
@@ -1170,8 +1170,8 @@ const AttachmentSection = defineComponent({
class: 'contract-manage-form__section contract-manage-form__section--panel', class: 'contract-manage-form__section contract-manage-form__section--panel',
}, },
[ [
h('div', { class: 'dialog-section-title' }, this.title),
h('div', { class: 'contract-manage-form__attachment-head' }, [ h('div', { class: 'contract-manage-form__attachment-head' }, [
h('div', { class: 'dialog-section-title' }, this.title),
h( h(
ElButton, ElButton,
{ type: 'primary', disabled: !this.rows.length, onClick: this.batchDownload }, { type: 'primary', disabled: !this.rows.length, onClick: this.batchDownload },
@@ -1270,6 +1270,8 @@ export default {
selectedProjectId: '', selectedProjectId: '',
settlementCurrencyOptions: [], settlementCurrencyOptions: [],
settlementModeDictOptions: [], settlementModeDictOptions: [],
contractFormatDictOptions: [],
contractCategoryDictOptions: [],
contractPartyAOptions: [], contractPartyAOptions: [],
contractPartyBOptions: [], contractPartyBOptions: [],
contractPartyLoading: false, contractPartyLoading: false,
@@ -1358,7 +1360,9 @@ export default {
return this.selectionList.map(item => item.id).join(','); return this.selectionList.map(item => item.id).join(',');
}, },
contractCategoryOptions() { contractCategoryOptions() {
return this.columnOptions('contractCategory'); return this.contractCategoryDictOptions.length
? this.contractCategoryDictOptions
: this.columnOptions('contractCategory');
}, },
signTypeOptions() { signTypeOptions() {
return this.columnOptions('signType'); return this.columnOptions('signType');
@@ -1372,7 +1376,9 @@ export default {
: this.columnOptions('settlementMode'); : this.columnOptions('settlementMode');
}, },
contractFormatOptions() { contractFormatOptions() {
return this.columnOptions('contractFormat'); return this.contractFormatDictOptions.length
? this.contractFormatDictOptions
: this.columnOptions('contractFormat');
}, },
organizationCascaderProps() { organizationCascaderProps() {
return { return {
@@ -1822,7 +1828,9 @@ export default {
Promise.all([ Promise.all([
getSystemDictionary({ code: 'currency_type' }), getSystemDictionary({ code: 'currency_type' }),
getBizDictionary({ code: 'settle_method' }), getBizDictionary({ code: 'settle_method' }),
]).then(([currencyRes, methodRes]) => { getBizDictionary({ code: 'contractFormat' }),
getBizDictionary({ code: 'contractCategory' }),
]).then(([currencyRes, methodRes, formatRes, categoryRes]) => {
const records = response => { const records = response => {
const data = response?.data?.data || response?.data || []; const data = response?.data?.data || response?.data || [];
return Array.isArray(data) ? data : data.records || []; return Array.isArray(data) ? data : data.records || [];
@@ -1835,10 +1843,26 @@ export default {
label: item.dictValue, label: item.dictValue,
value: item.dictKey, value: item.dictKey,
})); }));
this.contractFormatDictOptions = records(formatRes).map(item => ({
label: item.dictValue,
value: item.dictKey,
}));
this.contractCategoryDictOptions = records(categoryRes).map(item => ({
label: item.dictValue,
value: item.dictKey,
}));
['settlementMode'].forEach(prop => { ['settlementMode'].forEach(prop => {
const column = this.findColumn(this.tableOption.column, prop); const column = this.findColumn(this.tableOption.column, prop);
if (column) column.dicData = this.settlementModeDictOptions; if (column) column.dicData = this.settlementModeDictOptions;
}); });
const formatColumn = this.findColumn(this.tableOption.column, 'contractFormat');
if (formatColumn && this.contractFormatDictOptions.length) {
formatColumn.dicData = this.contractFormatDictOptions;
}
const categoryColumn = this.findColumn(this.tableOption.column, 'contractCategory');
if (categoryColumn && this.contractCategoryDictOptions.length) {
categoryColumn.dicData = this.contractCategoryDictOptions;
}
}); });
}, },
syncCurrentProjectOption() { syncCurrentProjectOption() {
@@ -2428,7 +2452,8 @@ export default {
prop === 'approvalStatus' || prop === 'approvalStatus' ||
prop === 'contractCategory' || prop === 'contractCategory' ||
prop === 'signType' || prop === 'signType' ||
prop === 'effectiveType' prop === 'effectiveType' ||
prop === 'contractFormat'
) { ) {
return this.displayStatus(this.detailRow, prop); return this.displayStatus(this.detailRow, prop);
} }
@@ -2819,8 +2844,13 @@ export default {
:deep(.contract-manage-form__attachment-head) { :deep(.contract-manage-form__attachment-head) {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-end; justify-content: space-between;
margin-bottom: 16px; margin-bottom: 16px;
// 标题与「批量下载」按钮同排、垂直居中,消除标题自身下边距
.dialog-section-title {
margin-bottom: 0;
}
} }
:deep(.contract-manage-form__attachment-upload) { :deep(.contract-manage-form__attachment-upload) {