Files
tms-erp-web/src/views/transit/risk-disposal.vue
T
gxwebsoft 8a78f4d01a fix(business-crud-page): 修复合同名称列左对齐问题
- 在 business-crud-page.vue 的 contractName 插槽中新增包裹 div,强制内容左对齐
- 新增 scoped 样式 .business-crud-page__contract-name,设置 text-align: left
- contract-manage.js 保留 contractName 列 align 和 headerAlign 配置,作为兜底
- 调整合同名称列最小宽度和合同管理页面菜单宽度
- 修复车辆信用评分量化页面部分文本居中样式移除
2026-08-20 17:08:30 +08:00

505 lines
14 KiB
Vue

<template>
<basic-container class="risk-disposal-page">
<section class="risk-disposal-page__search">
<el-form :model="query" label-position="right" label-width="160px" @submit.prevent>
<div class="risk-disposal-page__search-grid">
<el-form-item v-for="field in searchFields" :key="field.prop" :label="field.label">
<el-select
v-if="field.type === 'select'"
v-model="query[field.prop]"
clearable
:placeholder="field.placeholder"
>
<el-option
v-for="item in field.options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-input
v-else
v-model="query[field.prop]"
clearable
:placeholder="field.placeholder || '请输入'"
/>
</el-form-item>
<div class="risk-disposal-page__search-actions">
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button @click="handleReset">重置</el-button>
</div>
</div>
</el-form>
</section>
<section class="risk-disposal-page__table-panel">
<div class="risk-disposal-page__toolbar">
<div class="risk-disposal-page__toolbar-left">
<el-button type="primary" plain @click="openBatchDialog('process')">批量处理</el-button>
<el-button type="primary" plain @click="openBatchDialog('ignore')">批量忽略</el-button>
</div>
<div class="risk-disposal-page__toolbar-right">
<el-tooltip content="刷新" placement="top">
<el-button :icon="Refresh" text @click="loadTable" />
</el-tooltip>
<el-tooltip content="表格密度" placement="top">
<el-button :icon="Rank" text @click="noop" />
</el-tooltip>
<el-tooltip content="列设置" placement="top">
<el-button :icon="Setting" text @click="noop" />
</el-tooltip>
</div>
</div>
<el-table
v-loading="loading"
:data="rows"
border
class="risk-disposal-page__table"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="64" fixed="left" align="center" />
<el-table-column type="index" label="序号" width="96" fixed="left" align="center" />
<el-table-column
v-for="column in tableColumns"
:key="column.prop"
:prop="column.prop"
:label="column.label"
:min-width="column.minWidth"
align="center"
show-overflow-tooltip
>
<template #default="{ row }">
<el-link v-if="column.link && row[column.prop]" type="primary" @click="openWaybill(row)">
{{ row[column.prop] }}
</el-link>
<el-tag v-else-if="column.tag" :type="tagType(row, column.prop)" effect="plain">
{{ row[column.prop] || '-' }}
</el-tag>
<span v-else>{{ row[column.prop] || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="190" fixed="right" align="center">
<template #default="{ row }">
<div class="risk-disposal-page__actions">
<template v-if="row.disposalStatus === 'pending'">
<el-link type="primary" @click="openDisposal(row, 'process')">处理</el-link>
<el-link type="primary" @click="openDisposal(row, 'ignore')">忽略</el-link>
</template>
<el-link v-else type="primary" @click="openDetail(row)">查看</el-link>
</div>
</template>
</el-table-column>
</el-table>
<div class="risk-disposal-page__pagination">
<el-pagination
v-model:current-page="page.current"
v-model:page-size="page.size"
:total="page.total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
@current-change="loadTable"
@size-change="handleSizeChange"
/>
</div>
</section>
<el-dialog
v-model="dialog.visible"
:title="dialogTitle"
width="1100px"
append-to-body
destroy-on-close
class="risk-disposal-dialog"
>
<div v-loading="dialog.loading" class="risk-disposal-page__dialog">
<div v-if="!dialog.batch" class="risk-disposal-page__risk-card">
<div
v-for="field in detailFields"
:key="field.prop"
:class="{ 'is-wide': field.span === 3 }"
class="risk-disposal-page__risk-item"
>
<div class="risk-disposal-page__risk-label">{{ field.label }}</div>
<div class="risk-disposal-page__risk-value">
<el-link v-if="field.link && detail[field.prop]" type="primary" @click="openWaybill(detail)">
{{ detail[field.prop] }}
</el-link>
<el-tag v-else-if="field.tag" :type="tagType(detail, field.prop)" effect="plain">
{{ detail[field.prop] || '-' }}
</el-tag>
<span v-else>{{ detail[field.prop] || '-' }}</span>
</div>
</div>
</div>
<el-form
ref="disposeFormRef"
:model="disposeForm"
:rules="dialog.readonly ? {} : disposeRules"
label-position="right"
label-width="auto"
class="risk-disposal-page__dispose-form"
>
<el-form-item label="处理方式" prop="disposalMethod" required>
<el-radio-group v-model="disposeForm.disposalMethod" :disabled="dialog.readonly">
<el-radio
v-for="item in disposalMethodOptions"
:key="item.value"
:label="item.value"
>
{{ item.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="处置说明" prop="disposalRemark">
<el-input
v-model="disposeForm.disposalRemark"
type="textarea"
maxlength="500"
:rows="4"
:disabled="dialog.readonly"
/>
</el-form-item>
</el-form>
</div>
<template #footer>
<el-button @click="dialog.visible = false">关闭</el-button>
<el-button
v-if="!dialog.readonly"
type="primary"
:loading="dialog.submitting"
@click="submitDisposal"
>
确认
</el-button>
</template>
</el-dialog>
</basic-container>
</template>
<script>
import { Rank, Refresh, Setting } from '@element-plus/icons-vue';
import {
detailFields,
disposalMethodOptions,
disposalStatusOptions,
riskLevelOptions,
searchFields,
tableColumns,
} from '@/option/transit/risk-disposal';
import * as api from '@/api/transit/risk-disposal';
export default {
data() {
return {
Rank,
Refresh,
Setting,
searchFields,
tableColumns,
detailFields,
disposalMethodOptions,
loading: false,
query: {},
rows: [],
selection: [],
page: { current: 1, size: 10, total: 0 },
dialog: {
visible: false,
batch: false,
readonly: false,
loading: false,
submitting: false,
method: 'process',
},
detail: {},
disposeForm: {
disposalMethod: 'process',
disposalRemark: '',
},
disposeRules: {
disposalMethod: [{ required: true, message: '请选择处理方式', trigger: 'change' }],
disposalRemark: [
{ max: 500, message: '处置说明不能超过500个字符', trigger: 'blur' },
],
},
};
},
computed: {
dialogTitle() {
if (this.dialog.readonly) return '风险详情';
return '风险处置';
},
},
mounted() {
this.loadTable();
},
methods: {
async loadTable() {
this.loading = true;
try {
const res = await api.getList(this.page.current, this.page.size, this.buildQuery());
const data = this.unwrapPage(res);
this.rows = (data.records || []).map(this.decorateRow);
this.page.total = data.total || 0;
} finally {
this.loading = false;
}
},
buildQuery() {
return { ...this.query };
},
handleSearch() {
this.page.current = 1;
this.loadTable();
},
handleReset() {
this.query = {};
this.handleSearch();
},
handleSizeChange() {
this.page.current = 1;
this.loadTable();
},
handleSelectionChange(selection) {
this.selection = selection;
},
async openDisposal(row, method) {
this.dialog = {
visible: true,
batch: false,
readonly: false,
loading: true,
submitting: false,
method,
};
this.disposeForm = { disposalMethod: method, disposalRemark: '' };
try {
const res = await api.getDetail(row.id);
this.detail = this.decorateRow(res.data?.data || res.data || res || row);
} finally {
this.dialog.loading = false;
}
},
async openDetail(row) {
this.dialog = {
visible: true,
batch: false,
readonly: true,
loading: true,
submitting: false,
method: row.disposalMethod || 'process',
};
try {
const res = await api.getDetail(row.id);
this.detail = this.decorateRow(res.data?.data || res.data || res || row);
this.disposeForm = {
disposalMethod: this.detail.disposalMethod || this.detail.processMethod || 'process',
disposalRemark: this.detail.disposalRemark || this.detail.processRemark || '',
};
} finally {
this.dialog.loading = false;
}
},
openBatchDialog(method) {
const pendingRows = this.selection.filter(row => row.disposalStatus === 'pending');
if (!pendingRows.length) {
this.$message.warning('请选择待处理风险');
return;
}
this.dialog = {
visible: true,
batch: true,
readonly: false,
loading: false,
submitting: false,
method,
};
this.detail = {};
this.disposeForm = { disposalMethod: method, disposalRemark: '' };
},
async submitDisposal() {
await this.$refs.disposeFormRef.validate();
this.dialog.submitting = true;
try {
const payload = {
disposalMethod: this.disposeForm.disposalMethod,
disposalRemark: this.disposeForm.disposalRemark,
};
if (this.dialog.batch) {
await api.batchDispose({
...payload,
ids: this.selection
.filter(row => row.disposalStatus === 'pending')
.map(row => row.id),
});
} else {
await api.dispose({ ...payload, id: this.detail.id });
}
this.$message.success(this.disposeForm.disposalMethod === 'ignore' ? '忽略成功' : '处理成功');
this.dialog.visible = false;
this.loadTable();
} finally {
this.dialog.submitting = false;
}
},
openWaybill(row) {
if (row.waybillId) {
this.$router.push({ path: '/business/waybill-manage', query: { detailId: row.waybillId } });
}
},
decorateRow(row) {
return {
...row,
riskLevelName: this.optionLabel(riskLevelOptions, row.riskLevel) || row.riskLevelName,
disposalStatusName:
this.optionLabel(disposalStatusOptions, row.disposalStatus) || row.disposalStatusName,
};
},
optionLabel(options, value) {
return options.find(item => item.value === value)?.label || '';
},
tagType(row, prop) {
const options = prop === 'riskLevelName' ? riskLevelOptions : disposalStatusOptions;
const sourceProp = prop === 'riskLevelName' ? 'riskLevel' : 'disposalStatus';
return options.find(item => item.value === row[sourceProp])?.tagType || '';
},
unwrapPage(res) {
const data = res.data?.data || res.data || res || {};
return data.records ? data : { records: [], total: 0 };
},
noop() {},
},
};
</script>
<style scoped lang="scss">
.risk-disposal-page {
color: #303133;
}
.risk-disposal-page__search {
margin-bottom: 8px;
padding: 12px 12px 4px;
background: #fff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
}
.risk-disposal-page__search-grid {
display: grid;
grid-template-columns: repeat(4, minmax(260px, 1fr));
gap: 8px 36px;
:deep(.el-form-item) {
margin-bottom: 8px;
}
:deep(.el-input),
:deep(.el-select) {
width: 100%;
}
}
.risk-disposal-page__search-actions {
grid-column: 1 / -1;
display: flex;
justify-content: flex-end;
gap: 8px;
}
.risk-disposal-page__table-panel {
background: #fff;
}
.risk-disposal-page__toolbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 0;
}
.risk-disposal-page__toolbar-left,
.risk-disposal-page__toolbar-right,
.risk-disposal-page__actions {
display: flex;
align-items: center;
gap: 8px;
}
.risk-disposal-page__table {
--el-table-border-color: #eff1f7;
:deep(th.el-table__cell) {
background: #f5f7fa;
}
:deep(.el-table__row:nth-child(even) td.el-table__cell) {
background: #fafafa;
}
}
.risk-disposal-page__pagination {
display: flex;
justify-content: flex-end;
padding: 16px 0;
}
.risk-disposal-page__dialog {
padding: 4px 36px 0;
}
.risk-disposal-page__risk-card {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 20px 48px;
margin: 0 0 72px;
padding: 14px 42px 12px;
border: 1px solid #ebeef5;
border-radius: 8px;
}
.risk-disposal-page__risk-item.is-wide {
grid-column: 1 / -1;
padding-top: 12px;
border-top: 1px solid #dcdfe6;
}
.risk-disposal-page__risk-label {
margin-bottom: 8px;
color: #909399;
font-weight: 600;
}
.risk-disposal-page__risk-value {
min-height: 24px;
color: #303133;
font-size: 16px;
}
.risk-disposal-page__dispose-form {
:deep(.el-form-item) {
margin-bottom: 28px;
}
:deep(.el-textarea) {
max-width: 800px;
}
}
@media screen and (max-width: 1200px) {
.risk-disposal-page__search-grid,
.risk-disposal-page__risk-card {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media screen and (max-width: 768px) {
.risk-disposal-page__search-grid,
.risk-disposal-page__risk-card {
grid-template-columns: 1fr;
}
}
</style>