feat(contract-manage): 将合同详情由弹窗改为独立页面
- 新增路由 /business/contract-manage/detail,详情页共享合同管理组件 - 列表、表单、详情三分支用 isFormPage 和 isDetailPage 控制 - openDetail 方法改为跳转详情路由,initDetailPage 获取详情数据 - 新增 resetDetailState 和 closeDetailPage 方法,复用关闭逻辑 - 详情页去除弹窗高度限制,样式改为页面自滚动 - 详情页底栏使用固定位置关闭按钮,保持页面布局一致 - watch 和 created 中添加对 isDetailPage 的初始化调用和监听
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
# 2026-09-11
|
||||
|
||||
## 上传证件区域固定宽度导致跨机器错位(transportCapacity)
|
||||
- 现象:driver.vue 新增弹窗的身份证/大头照上传框在本机对齐,他人电脑不对齐。
|
||||
- 根因:`--large` 上传框写死 `width:240px`(ship 写 248px),而弹窗 `width:90%` + `el-col :span=6` 列宽是弹性的、上方输入框 `width:100%`;列宽随分辨率 / Windows 系统缩放 / 浏览器缩放变化,240px 只在特定宽度下恰好等于列宽。叠加 `labelWidth:auto` 靠 JS 实测字体(苹方 vs 微软雅黑)宽度,label 列宽也不同 → 起始位置漂移。
|
||||
- 修复:三个文件改为 `width:100%; max-width:240px; flex:none`(ship 保留 248px 上限),窄屏跟随列宽、宽屏保持比例上限,左边缘恒与输入框对齐。
|
||||
- `src/views/transportCapacity/driver.vue`(.driver-uploader--large)
|
||||
- `src/views/transportCapacity/vehicle.vue`(.vehicle-uploader--large)
|
||||
- `src/views/transportCapacity/ship.vue`(.ship-uploader--large)
|
||||
- 用 @vue/compiler-sfc(脚本放项目根目录)验证三个 SFC 编译通过。
|
||||
- 教训:本项目所有「固定 px 宽度的上传/控件」都要配 `max-width` + `width:100%`,否则高分屏/低分屏下必然错位。
|
||||
|
||||
## 合同详情由弹窗改为独立页面(contract-manage)
|
||||
- 路由:新增 `/business/contract-manage/detail`(`src/router/views/index.js`,name『合同详情』,meta `keepAlive:false` + `activeMenu:'/business/contract-manage'`),组件仍复用 `contract-manage.vue`(与 form 页同一套路)。
|
||||
- 页面内三分支:`v-if="!isFormPage && !isDetailPage"`(列表)/ `v-else-if="isFormPage"`(表单)/ `v-else`(详情)。**v-else 必须紧邻 form 分支的 `</template>`**,中间不能插其它组件,否则 Vue 编译报「v-else 无相邻 v-if」——弹窗类组件(billing-plan-editor / 附件预览 / 变更记录详情 / 流程)放在三个分支之后。
|
||||
- 关键改动:`openDetail` 改 `router.push({ path:'/business/contract-manage/detail', query:{ id, name } })`;新增 `initDetailPage()`(按 query.id 拉详情,`resetDetailState()` 先清空)、`closeDetailPage()`(closeTag + 回列表)、`isDetailPage` / `detailPageTitle` computed;`created` 与 `$route.fullPath` watch 都要加 `else if (this.isDetailPage)`。
|
||||
- 样式:`.contract-manage-detail--page { max-height:none; overflow:visible }`,去掉弹窗的 74vh 限制;底栏复用 `.contract-manage-page__footer`(fixed 浮动),只读详情页按钮保留「关闭」。
|
||||
- 校验方式:dev server 已运行时 `curl "http://localhost:2889/src/views/business/contract-manage.vue"` 返回 200 即 Vite 编译通过(比整包 build 快)。
|
||||
|
||||
## 只给「查看弹窗」加宽 label(temporary-credit-limit)
|
||||
- 场景:详情页长 label(「剩余项目资金使用额度(万元)」)换行超 2 行撑坏详情表格,但又不能影响新增/编辑页。
|
||||
- **关键发现:Avue 的 `avue-form` 在 detail 模式下根节点会挂 `.avue--detail` 类**(源码 `node_modules/@smallwei/avue/lib/packages/element-plus/form/index3.js` 中 `class: normalizeClass([b(), { 'avue--detail': isDetail }])`)。所以「只针对详情态」的选择器就是:
|
||||
```scss
|
||||
.temporary-credit-limit-dialog .avue--detail .el-form-item__label { width:210px !important; flex:0 0 210px !important; }
|
||||
```
|
||||
新增/编辑弹窗无 `.avue--detail`,天然不受影响,不需要在 option 里做 mode 判断。
|
||||
- 校验样式块编译:dev server 请求 `?vue&type=style&index=1&lang.scss`(**lang 必须写 scss,写 css 会 500**),grep 规则确认产物。
|
||||
- 结论备忘:长 label 优先「加宽 + 换行 ≤2 行」,不要用单行省略 + tooltip —— 详情是纯阅读场景,hover 才能看全字段名的可发现性差。
|
||||
@@ -30,8 +30,13 @@
|
||||
- Avue 内置弹窗 footer 真实 class 是 `.avue-dialog__footer`,须 `position:sticky; bottom:0; flex:none; background:#fff` 贴底。
|
||||
|
||||
### 上传证件区域尺寸(transportCapacity)
|
||||
- 身份证/行驶证/道路运输证等固定 **240×151px**(1.586:1)。
|
||||
- scoped 覆盖 `.xxx-uploader--large { width:240px }` 与 `--large .el-upload { width:100%; height:151px }`。
|
||||
- 身份证/行驶证/道路运输证等目标比例 **240×151px**(1.586:1),ship 为 248px 上限。
|
||||
- ⚠️ **不能写死 `width:240px`**:弹窗 `width:90%` + 列 `el-col :span=6` 是弹性的,固定 px 只在特定分辨率/系统缩放下恰好等于列宽(本机对齐、他人错位)。统一写法:
|
||||
```scss
|
||||
.xxx-uploader--large { display:block; width:100%; max-width:240px; flex:none; margin:0; }
|
||||
.xxx-uploader--large .el-upload { width:100%; height:151px; }
|
||||
```
|
||||
2026-09-11 已修正 driver / vehicle / ship 三处。
|
||||
|
||||
### 上传图片点击放大预览
|
||||
- `src/components/image-upload-field/main.vue` 用 `<el-image preview-src-list fit="contain" preview-teleported :z-index="3000" @click.stop>`,阻止冒泡到 el-upload。
|
||||
|
||||
@@ -287,6 +287,18 @@ export default [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/business/contract-manage/detail',
|
||||
component: Layout,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: '合同详情',
|
||||
meta: { keepAlive: false, activeMenu: '/business/contract-manage' },
|
||||
component: () => import('@/views/business/contract-manage.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/business/contract-manage/change',
|
||||
component: Layout,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<template>
|
||||
<basic-container :class="['contract-manage-page', { 'contract-manage-page--form': isFormPage }]">
|
||||
<template v-if="!isFormPage">
|
||||
<basic-container
|
||||
:class="['contract-manage-page', { 'contract-manage-page--form': isFormPage || isDetailPage }]"
|
||||
>
|
||||
<template v-if="!isFormPage && !isDetailPage">
|
||||
<avue-crud
|
||||
ref="crud"
|
||||
v-model:page="page"
|
||||
@@ -96,7 +98,7 @@
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<template v-else-if="isFormPage">
|
||||
<div class="archive-page-form__title">{{ formPageTitle }}</div>
|
||||
|
||||
<el-form
|
||||
@@ -558,65 +560,13 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<billing-plan-editor
|
||||
ref="billingPlanEditor"
|
||||
v-model="billingPlanBox"
|
||||
:value="billingPlanForm"
|
||||
:index="billingPlanIndex"
|
||||
@save="saveBillingPlan"
|
||||
/>
|
||||
<template v-else>
|
||||
<div class="archive-page-form__title">{{ detailPageTitle }}</div>
|
||||
|
||||
<el-dialog
|
||||
v-model="attachmentDocumentPreviewVisible"
|
||||
:title="attachmentPreviewFile.name || '附件预览'"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
width="90%"
|
||||
top="4vh"
|
||||
<div
|
||||
v-loading="detailLoading"
|
||||
class="contract-manage-form contract-manage-detail contract-manage-detail--page"
|
||||
>
|
||||
<pdf-preview
|
||||
v-if="
|
||||
attachmentDocumentPreviewVisible &&
|
||||
attachmentPreviewFile.url &&
|
||||
attachmentPreviewFile.isPdf
|
||||
"
|
||||
:source="attachmentPreviewFile.url"
|
||||
@error="handleAttachmentPreviewError"
|
||||
/>
|
||||
<open-file-viewer
|
||||
v-else-if="attachmentDocumentPreviewVisible && attachmentPreviewFile.url"
|
||||
:file="attachmentPreviewFile.url"
|
||||
:file-name="attachmentPreviewFile.name"
|
||||
:mime-type="attachmentPreviewFile.mimeType"
|
||||
width="100%"
|
||||
height="72vh"
|
||||
fit="contain"
|
||||
theme="auto"
|
||||
locale="zh-CN"
|
||||
:toolbar="attachmentViewerToolbar"
|
||||
:plugins="attachmentViewerPlugins"
|
||||
@unsupported="handleAttachmentPreviewUnsupported"
|
||||
@error="handleAttachmentPreviewError"
|
||||
/>
|
||||
</el-dialog>
|
||||
|
||||
<el-image-viewer
|
||||
v-if="attachmentImagePreviewVisible"
|
||||
:url-list="attachmentImagePreviewUrls"
|
||||
:initial-index="attachmentImagePreviewIndex"
|
||||
@close="attachmentImagePreviewVisible = false"
|
||||
/>
|
||||
|
||||
<el-dialog
|
||||
v-model="detailBox"
|
||||
title="合同详情"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
class="contract-manage-detail-dialog"
|
||||
width="92%"
|
||||
top="4vh"
|
||||
>
|
||||
<div v-loading="detailLoading" class="contract-manage-form contract-manage-detail">
|
||||
<section class="contract-manage-form__section contract-manage-form__section--panel">
|
||||
<div class="dialog-section-title">基本信息</div>
|
||||
<el-descriptions :column="3" class="contract-manage-detail__descriptions">
|
||||
@@ -819,11 +769,61 @@
|
||||
</el-table>
|
||||
</section>
|
||||
</div>
|
||||
<template #footer
|
||||
><el-button type="primary" @click="detailBox = false">关闭</el-button></template
|
||||
|
||||
<div class="contract-manage-page__footer">
|
||||
<el-button type="primary" @click="closeDetailPage">关闭</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<billing-plan-editor
|
||||
ref="billingPlanEditor"
|
||||
v-model="billingPlanBox"
|
||||
:value="billingPlanForm"
|
||||
:index="billingPlanIndex"
|
||||
@save="saveBillingPlan"
|
||||
/>
|
||||
|
||||
<el-dialog
|
||||
v-model="attachmentDocumentPreviewVisible"
|
||||
:title="attachmentPreviewFile.name || '附件预览'"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
width="90%"
|
||||
top="4vh"
|
||||
>
|
||||
<pdf-preview
|
||||
v-if="
|
||||
attachmentDocumentPreviewVisible &&
|
||||
attachmentPreviewFile.url &&
|
||||
attachmentPreviewFile.isPdf
|
||||
"
|
||||
:source="attachmentPreviewFile.url"
|
||||
@error="handleAttachmentPreviewError"
|
||||
/>
|
||||
<open-file-viewer
|
||||
v-else-if="attachmentDocumentPreviewVisible && attachmentPreviewFile.url"
|
||||
:file="attachmentPreviewFile.url"
|
||||
:file-name="attachmentPreviewFile.name"
|
||||
:mime-type="attachmentPreviewFile.mimeType"
|
||||
width="100%"
|
||||
height="72vh"
|
||||
fit="contain"
|
||||
theme="auto"
|
||||
locale="zh-CN"
|
||||
:toolbar="attachmentViewerToolbar"
|
||||
:plugins="attachmentViewerPlugins"
|
||||
@unsupported="handleAttachmentPreviewUnsupported"
|
||||
@error="handleAttachmentPreviewError"
|
||||
/>
|
||||
</el-dialog>
|
||||
|
||||
<el-image-viewer
|
||||
v-if="attachmentImagePreviewVisible"
|
||||
:url-list="attachmentImagePreviewUrls"
|
||||
:initial-index="attachmentImagePreviewIndex"
|
||||
@close="attachmentImagePreviewVisible = false"
|
||||
/>
|
||||
|
||||
<el-dialog
|
||||
v-model="detailChangeRecordVisible"
|
||||
title="变更记录详情"
|
||||
@@ -1313,7 +1313,6 @@ export default {
|
||||
changeRecordRows: [],
|
||||
settlementTypeOptions: ['月结', '日结', '周结', '半月结', '固定天数周期结算'],
|
||||
billCycleTypeOptions: ['固定截单日', '自然月'],
|
||||
detailBox: false,
|
||||
detailLoading: false,
|
||||
detailRow: {},
|
||||
detailContractFileRows: [],
|
||||
@@ -1352,6 +1351,9 @@ export default {
|
||||
isFormPage() {
|
||||
return this.$route.path === '/business/contract-manage/form';
|
||||
},
|
||||
isDetailPage() {
|
||||
return this.$route.path === '/business/contract-manage/detail';
|
||||
},
|
||||
formMode() {
|
||||
return this.$route.query.mode === 'edit' ? 'edit' : 'add';
|
||||
},
|
||||
@@ -1362,6 +1364,9 @@ export default {
|
||||
formPageTitle() {
|
||||
return this.$route.query.name || `${this.formMode === 'edit' ? '编辑' : '新增'}合同管理`;
|
||||
},
|
||||
detailPageTitle() {
|
||||
return this.$route.query.name || '合同详情';
|
||||
},
|
||||
ids() {
|
||||
return this.selectionList.map(item => item.id).join(',');
|
||||
},
|
||||
@@ -1461,6 +1466,7 @@ export default {
|
||||
watch: {
|
||||
'$route.fullPath'() {
|
||||
if (this.isFormPage) this.initFormPage();
|
||||
else if (this.isDetailPage) this.initDetailPage();
|
||||
},
|
||||
settlementConfigTab(tab, oldTab) {
|
||||
if (tab === oldTab) return;
|
||||
@@ -1476,6 +1482,7 @@ export default {
|
||||
this.loadSettlementDictionaries();
|
||||
this.loadOrganizationOptions();
|
||||
if (this.isFormPage) this.initFormPage();
|
||||
else if (this.isDetailPage) this.initDetailPage();
|
||||
},
|
||||
methods: {
|
||||
buildTableOption() {
|
||||
@@ -2204,19 +2211,42 @@ export default {
|
||||
if (Number(nextValue) > 100) nextValue = '100';
|
||||
row.ratioLimit = nextValue;
|
||||
},
|
||||
// 合同详情:与新增/编辑一致,跳转独立页面(/business/contract-manage/detail)
|
||||
openDetail(row) {
|
||||
this.detailBox = true;
|
||||
this.$router.push({
|
||||
path: '/business/contract-manage/detail',
|
||||
query: { id: row.id, name: row.contractName ? `合同详情-${row.contractName}` : '合同详情' },
|
||||
});
|
||||
},
|
||||
initDetailPage() {
|
||||
this.resetDetailState();
|
||||
const id = this.$route.query.id;
|
||||
if (!id) return;
|
||||
this.detailLoading = true;
|
||||
this.applyDetailState(row);
|
||||
this.api
|
||||
.getDetail(row.id)
|
||||
.getDetail(id)
|
||||
.then(res => {
|
||||
this.applyDetailState(res.data?.data || row);
|
||||
this.applyDetailState(res.data?.data || {});
|
||||
})
|
||||
.finally(() => {
|
||||
this.detailLoading = false;
|
||||
});
|
||||
},
|
||||
resetDetailState() {
|
||||
this.detailRow = {};
|
||||
this.detailContractFileRows = [];
|
||||
this.detailAttachmentRows = [];
|
||||
this.detailBillingPlanRows = [];
|
||||
this.detailPaymentRatioRows = [];
|
||||
this.detailChangeRecordRows = [];
|
||||
this.detailPreSettlementRuleForm = defaultSettlementRule();
|
||||
this.detailFormalSettlementRuleForm = defaultSettlementRule();
|
||||
this.detailSettlementConfigTab = 'pre';
|
||||
},
|
||||
closeDetailPage() {
|
||||
this.$router.$avueRouter?.closeTag?.();
|
||||
this.$router.push('/business/contract-manage');
|
||||
},
|
||||
applyDetailState(detail = {}) {
|
||||
this.detailRow = {
|
||||
...detail,
|
||||
@@ -2895,6 +2925,12 @@ export default {
|
||||
max-height: 74vh;
|
||||
overflow-y: auto;
|
||||
|
||||
// 独立详情页面:不再受弹窗高度限制,交给页面自身滚动
|
||||
&--page {
|
||||
max-height: none;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
&__descriptions {
|
||||
:deep(.el-descriptions__label) {
|
||||
display: inline-block;
|
||||
|
||||
@@ -288,7 +288,7 @@
|
||||
<el-form-item prop="fundLimit" class="project-apply-form__tip-label">
|
||||
<template #label>
|
||||
<span>项目资金使用额度</span>
|
||||
<el-tooltip content="实际业务回款周期内付款" placement="top">
|
||||
<el-tooltip content="实际业务回款周期内付款额度" placement="top">
|
||||
<el-icon class="project-apply-form__label-tip"><QuestionFilled /></el-icon>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
|
||||
@@ -763,6 +763,7 @@ export default {
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 12px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
&__attachment-upload {
|
||||
@@ -840,6 +841,13 @@ export default {
|
||||
.temporary-credit-limit-dialog .business-crud-page__detail-content .el-descriptions__label {
|
||||
width: 180px !important;
|
||||
}
|
||||
|
||||
// 仅查看(detail)态加宽 label 列:长字段名(如「剩余项目资金使用额度(万元)」)收敛为最多两行,
|
||||
// 避免撑坏详情表格。新增 / 编辑弹窗的 avue-form 无 .avue--detail 类,不受影响
|
||||
.temporary-credit-limit-dialog .avue--detail .el-form-item__label {
|
||||
width: 148px !important;
|
||||
flex: 0 0 148px !important;
|
||||
}
|
||||
.el-form-item--default .el-form-item__label {
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
@@ -1829,10 +1829,13 @@ export default {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// 大尺寸模式:缩成 1.586:1 比例,左对齐(不再 100% 占栏)
|
||||
// 大尺寸模式:跟随列宽自适应(最大 240px,约 1.586:1 证件比例),
|
||||
// 保证不同分辨率 / 系统缩放下都与上方输入框左右对齐
|
||||
:deep(.driver-uploader--large) {
|
||||
display: block;
|
||||
width: 240px;
|
||||
width: 100%;
|
||||
max-width: 240px;
|
||||
flex: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1349,9 +1349,13 @@ export default {
|
||||
height: 151px;
|
||||
}
|
||||
|
||||
// 大尺寸模式:跟随列宽自适应(最大 248px),
|
||||
// 保证不同分辨率 / 系统缩放下都与上方输入框左右对齐
|
||||
:deep(.ship-uploader--large) {
|
||||
display: block;
|
||||
width: 248px;
|
||||
width: 100%;
|
||||
max-width: 248px;
|
||||
flex: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1741,10 +1741,13 @@ export default {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// 大尺寸模式:缩成 1.586:1 比例,左对齐(不再 100% 占栏)
|
||||
// 大尺寸模式:跟随列宽自适应(最大 240px,约 1.586:1 证件比例),
|
||||
// 保证不同分辨率 / 系统缩放下都与上方输入框左右对齐
|
||||
:deep(.vehicle-uploader--large) {
|
||||
display: block;
|
||||
width: 240px;
|
||||
width: 100%;
|
||||
max-width: 240px;
|
||||
flex: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
+13
-13
@@ -47,26 +47,26 @@ export default ({ mode, command }) => {
|
||||
__VUE_I18N_LEGACY_API__: true,
|
||||
__INTLIFY_PROD_DEVTOOLS__: false,
|
||||
},
|
||||
server: {
|
||||
port: 2888,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost',
|
||||
//target: 'https://saber3.bladex.cn/api',
|
||||
changeOrigin: true,
|
||||
rewrite: path => path.replace(/^\/api/, ''),
|
||||
},
|
||||
},
|
||||
},
|
||||
// server: {
|
||||
// port: 2889,
|
||||
// port: 2888,
|
||||
// proxy: {
|
||||
// '/api': {
|
||||
// target: 'http://172.16.203.228:8000',
|
||||
// target: 'http://localhost',
|
||||
// //target: 'https://saber3.bladex.cn/api',
|
||||
// changeOrigin: true,
|
||||
// rewrite: path => path.replace(/^\/api/, ''),
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
server: {
|
||||
port: 2889,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://172.16.203.228:8000',
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'~': resolve(__dirname, './'),
|
||||
|
||||
Reference in New Issue
Block a user