✨ 合同结算币种联动计费与运单计价单位,并完善合同与派车交互
结算币种驱动计费单价单位及运单/计划/总单计价单位;同步合同编号预览、部门前缀配置、派车备注与查看弹窗等交互优化。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -90,3 +90,11 @@ export const expireStats = params =>
|
||||
method: 'get',
|
||||
params,
|
||||
});
|
||||
|
||||
/** 按合同负责部门预览下一合同编号 */
|
||||
export const nextContractNo = organizationId =>
|
||||
request({
|
||||
url: `${baseUrl}/next-contract-no`,
|
||||
method: 'get',
|
||||
params: { organizationId },
|
||||
});
|
||||
|
||||
@@ -117,6 +117,7 @@ export const contractCategoryOptions = [
|
||||
export const signTypeOptions = [
|
||||
{ label: '新签', value: '新签' },
|
||||
{ label: '续签', value: '续签' },
|
||||
{ label: '补充协议', value: '补充协议' },
|
||||
];
|
||||
|
||||
export const contractStageOptions = [
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
contractStageOptions,
|
||||
createCrudOption,
|
||||
effectiveTypeOptions,
|
||||
nonNegativeRule,
|
||||
selectRule,
|
||||
signTypeOptions,
|
||||
textRule,
|
||||
@@ -61,13 +62,14 @@ export const config = {
|
||||
['contractNo', '合同编号'],
|
||||
['contractName', '合同名称'],
|
||||
['projectName', '所属项目'],
|
||||
['organizationName', '所属组织'],
|
||||
['organizationName', '合同负责部门'],
|
||||
['contractCategory', '合同类别'],
|
||||
['signType', '签约类型'],
|
||||
['partyA', '甲方'],
|
||||
['partyB', '乙方'],
|
||||
['handlerUserName', '经办人'],
|
||||
['signDate', '签订日期'],
|
||||
['applyDate', '申请日期'],
|
||||
['contractFormat', '合同格式'],
|
||||
['legalSealFlag', '是否需要加盖法人章'],
|
||||
['copyCount', '一式(份)'],
|
||||
@@ -76,7 +78,8 @@ export const config = {
|
||||
['templateFlag', '是否范本'],
|
||||
['originalContractNo', '原件合同编号'],
|
||||
['electronicSealFlag', '是否电子章'],
|
||||
['remark', '备注', 2],
|
||||
['mainContent', '合同主要内容', 2],
|
||||
['remark', '其他事项说明', 2],
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -294,17 +297,17 @@ export const option = createCrudOption([
|
||||
display: false,
|
||||
},
|
||||
{
|
||||
label: '所属组织',
|
||||
label: '合同负责部门',
|
||||
prop: 'organizationName',
|
||||
search: true,
|
||||
searchOrder: 3,
|
||||
searchslot: true,
|
||||
searchPlaceholder: '请选择 所属组织',
|
||||
searchPlaceholder: '请选择 合同负责部门',
|
||||
formslot: true,
|
||||
order: 120,
|
||||
dicData: [],
|
||||
minWidth: 150,
|
||||
rules: selectRule('所属组织'),
|
||||
rules: selectRule('合同负责部门'),
|
||||
},
|
||||
{
|
||||
label: '合同类别',
|
||||
@@ -435,6 +438,17 @@ export const option = createCrudOption([
|
||||
minWidth: 130,
|
||||
rules: [{ required: true, message: '请选择签订日期', trigger: 'change' }],
|
||||
},
|
||||
{
|
||||
label: '申请日期',
|
||||
prop: 'applyDate',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
sortable: true,
|
||||
minWidth: 130,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '结算类型',
|
||||
prop: 'settlementMode',
|
||||
@@ -468,11 +482,10 @@ export const option = createCrudOption([
|
||||
{ label: '否', value: 0 },
|
||||
{ label: '是', value: 1 },
|
||||
],
|
||||
value: 0,
|
||||
clearable: true,
|
||||
hide: true,
|
||||
order: 70,
|
||||
minWidth: 100,
|
||||
rules: selectRule('是否需要加盖法人章'),
|
||||
},
|
||||
{
|
||||
label: '一式(份)',
|
||||
@@ -500,6 +513,10 @@ export const option = createCrudOption([
|
||||
hide: true,
|
||||
order: 45,
|
||||
minWidth: 130,
|
||||
rules: [
|
||||
{ required: true, message: '请输入合同金额', trigger: 'blur' },
|
||||
nonNegativeRule('合同金额'),
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '是否范本',
|
||||
@@ -617,7 +634,19 @@ export const option = createCrudOption([
|
||||
minWidth: 110,
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
label: '合同主要内容',
|
||||
prop: 'mainContent',
|
||||
type: 'textarea',
|
||||
span: 24,
|
||||
minRows: 4,
|
||||
hide: true,
|
||||
order: 41,
|
||||
maxlength: 1000,
|
||||
showWordLimit: true,
|
||||
rules: textRule('合同主要内容', 1000),
|
||||
},
|
||||
{
|
||||
label: '其他事项说明',
|
||||
prop: 'remark',
|
||||
type: 'textarea',
|
||||
span: 24,
|
||||
@@ -626,7 +655,7 @@ export const option = createCrudOption([
|
||||
order: 40,
|
||||
maxlength: 2000,
|
||||
showWordLimit: true,
|
||||
rules: textRule('备注', 2000),
|
||||
rules: textRule('其他事项说明', 2000),
|
||||
},
|
||||
{
|
||||
label: '',
|
||||
|
||||
@@ -101,6 +101,7 @@ export const option = {
|
||||
minWidth: 120,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '当前节点',
|
||||
@@ -108,6 +109,7 @@ export const option = {
|
||||
minWidth: 120,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '当前处理人',
|
||||
@@ -115,6 +117,7 @@ export const option = {
|
||||
minWidth: 130,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '创建人',
|
||||
@@ -122,6 +125,7 @@ export const option = {
|
||||
minWidth: 120,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
@@ -132,6 +136,7 @@ export const option = {
|
||||
minWidth: 170,
|
||||
addDisplay: false,
|
||||
editDisplay: false,
|
||||
viewDisplay: false,
|
||||
},
|
||||
// ========== 新增/编辑表单专用(不进表格)==========
|
||||
{
|
||||
@@ -165,6 +170,7 @@ export const option = {
|
||||
type: 'textarea',
|
||||
hide: true,
|
||||
span: 24,
|
||||
minRows: 2,
|
||||
maxlength: 200,
|
||||
showWordLimit: true,
|
||||
rules: [{ max: 200, message: '备注不能超过200个字', trigger: 'blur' }],
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
const CURRENCY_NAME_MAP = {
|
||||
CNY: '人民币',
|
||||
RMB: '人民币',
|
||||
USD: '美元',
|
||||
EUR: '欧元',
|
||||
JPY: '日元',
|
||||
GBP: '英镑',
|
||||
};
|
||||
|
||||
const CURRENCY_UNIT_MAP = {
|
||||
CNY: '元',
|
||||
RMB: '元',
|
||||
USD: '美元',
|
||||
EUR: '欧元',
|
||||
JPY: '日元',
|
||||
GBP: '英镑',
|
||||
};
|
||||
|
||||
const CURRENCY_NAME_TO_UNIT = {
|
||||
人民币: '元',
|
||||
美元: '美元',
|
||||
欧元: '欧元',
|
||||
日元: '日元',
|
||||
英镑: '英镑',
|
||||
};
|
||||
|
||||
/**
|
||||
* 归一化币种代码(CNY → RMB)
|
||||
*/
|
||||
export function normalizeCurrencyCode(value, fallback = 'RMB') {
|
||||
const raw =
|
||||
typeof value === 'object'
|
||||
? value?.dictKey || value?.value || value?.code || value?.dictValue || ''
|
||||
: value;
|
||||
const normalized = String(raw || fallback)
|
||||
.trim()
|
||||
.toUpperCase();
|
||||
const code = normalized.includes(' - ') ? normalized.split(' - ')[0].trim() : normalized;
|
||||
if (!code) return fallback;
|
||||
return code === 'CNY' ? 'RMB' : code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 币种中文名称
|
||||
*/
|
||||
export function currencyName(value) {
|
||||
const text = String(value || '');
|
||||
if (text.includes(' - ')) return text.slice(text.indexOf(' - ') + 3);
|
||||
return CURRENCY_NAME_MAP[text.toUpperCase()] || text;
|
||||
}
|
||||
|
||||
/**
|
||||
* 金额单位文案:RMB/CNY → 元,USD → 美元
|
||||
*/
|
||||
export function currencyUnit(value) {
|
||||
const text = String(value || '');
|
||||
let code = text;
|
||||
if (text.includes(' - ')) {
|
||||
code = text.split(' - ')[0].trim();
|
||||
}
|
||||
const upperCode = code.toUpperCase();
|
||||
if (CURRENCY_UNIT_MAP[upperCode]) {
|
||||
return CURRENCY_UNIT_MAP[upperCode];
|
||||
}
|
||||
if (CURRENCY_NAME_TO_UNIT[code]) {
|
||||
return CURRENCY_NAME_TO_UNIT[code];
|
||||
}
|
||||
return text || '元';
|
||||
}
|
||||
|
||||
/**
|
||||
* 将计价单位前缀替换为当前币种单位,如 元/吨 → 美元/吨
|
||||
*/
|
||||
export function remapPriceUnitCurrency(priceUnit, currency) {
|
||||
const text = String(priceUnit || '').trim();
|
||||
if (!text || !text.includes('/')) return text;
|
||||
const suffix = text.slice(text.indexOf('/'));
|
||||
return `${currencyUnit(currency)}${suffix}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按数量单位生成默认计价单位
|
||||
*/
|
||||
export function resolvePriceUnitByQuantityUnit(quantityUnit, currency = 'RMB') {
|
||||
const unit = currencyUnit(currency);
|
||||
return (
|
||||
{
|
||||
吨: `${unit}/吨`,
|
||||
方: `${unit}/立方`,
|
||||
件: `${unit}/件`,
|
||||
}[String(quantityUnit || '').trim()] || ''
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字典中的计价单位选项映射到当前币种
|
||||
*/
|
||||
export function mapFeeUnitOptions(options = [], currency = 'RMB') {
|
||||
const unit = currencyUnit(currency);
|
||||
return (options || []).map(item => {
|
||||
const label = String(item.label || item.value || '');
|
||||
const value = String(item.value || item.label || '');
|
||||
return {
|
||||
...item,
|
||||
label: label.replace(/^元/, unit).replace(/^人民币/, unit),
|
||||
value: value.replace(/^元/, unit).replace(/^人民币/, unit),
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -171,7 +171,7 @@
|
||||
:label="item.label"
|
||||
:value="item.value" /></el-select></template
|
||||
></el-table-column>
|
||||
<el-table-column label="单价(元)" width="180"
|
||||
<el-table-column :label="unitPriceColumnLabel" width="180"
|
||||
><template #default="{ row }"
|
||||
><div v-if="readonly && usesRangeUnitPrice(row)" class="limit-list">
|
||||
<span v-for="(item, rangeIndex) in getRanges(row)" :key="rangeIndex">{{
|
||||
@@ -379,6 +379,7 @@ import { getLazyTree as getRegionLazyTree } from '@/api/base/region';
|
||||
import { InfoFilled } from '@element-plus/icons-vue';
|
||||
import { getDictionary } from '@/api/system/dictbiz';
|
||||
import SectionCard from '@/components/section-card/main.vue';
|
||||
import { currencyUnit } from '@/utils/currency';
|
||||
|
||||
const clone = value => JSON.parse(JSON.stringify(value));
|
||||
/** 计费要素与计量单位维度的对应关系(这些要素可选计费单位) */
|
||||
@@ -431,6 +432,7 @@ export default {
|
||||
value: { type: Object, default: () => ({}) },
|
||||
index: { type: Number, default: -1 },
|
||||
readonly: Boolean,
|
||||
settlementCurrency: { type: String, default: 'RMB' },
|
||||
},
|
||||
emits: ['update:modelValue', 'save'],
|
||||
data() {
|
||||
@@ -500,6 +502,9 @@ export default {
|
||||
emitPath: true,
|
||||
};
|
||||
},
|
||||
unitPriceColumnLabel() {
|
||||
return `单价(${currencyUnit(this.settlementCurrency)})`;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.loadDictionaries();
|
||||
|
||||
@@ -173,13 +173,41 @@ import { downloadFileByUrl } from '@/utils/util';
|
||||
import { getDictionary as getBizDictionary } from '@/api/system/dictbiz';
|
||||
|
||||
const CONTRACT_ATTACHMENT_TYPE_OTHER = '其他文件';
|
||||
/** 用印材料历史取值兼容别名 */
|
||||
export const CONTRACT_SEAL_MATERIAL_LOCATION = '用印材料';
|
||||
export const CONTRACT_SEAL_MATERIAL_TYPE_ALIASES = {
|
||||
合同文件: CONTRACT_SEAL_MATERIAL_LOCATION,
|
||||
};
|
||||
/** 佐证材料附件位置(历史:其它附件) */
|
||||
export const CONTRACT_SUPPORTING_ATTACHMENT_LOCATION = '附件(佐证材料等)';
|
||||
export const CONTRACT_ATTACHMENT_TYPE_DICT = 'contract_attachment_type';
|
||||
export const CONTRACT_SUPPORTING_ATTACHMENT_TYPE_DICT = 'contract_supporting_attachment_type';
|
||||
export const isContractSupportingAttachmentLocation = location => {
|
||||
const text = String(location || '');
|
||||
return (
|
||||
text === CONTRACT_SUPPORTING_ATTACHMENT_LOCATION ||
|
||||
text === '其它附件' ||
|
||||
text === '其他附件'
|
||||
);
|
||||
};
|
||||
export const resolveAttachmentTypeDictCode = (location, fallback = CONTRACT_ATTACHMENT_TYPE_DICT) =>
|
||||
isContractSupportingAttachmentLocation(location)
|
||||
? CONTRACT_SUPPORTING_ATTACHMENT_TYPE_DICT
|
||||
: fallback || CONTRACT_ATTACHMENT_TYPE_DICT;
|
||||
export const defaultContractAttachmentTypeOptions = [
|
||||
{ label: '合同文件', value: '合同文件' },
|
||||
{ label: '用印材料', value: '用印材料' },
|
||||
{ label: '双章归档文件', value: '双章归档文件' },
|
||||
{ label: '其他文件', value: CONTRACT_ATTACHMENT_TYPE_OTHER },
|
||||
];
|
||||
/** @deprecated 兼容旧引用,实际选项由业务字典 contract_attachment_type 动态加载 */
|
||||
export const defaultContractSupportingAttachmentTypeOptions = [
|
||||
{ label: '其他文件', value: CONTRACT_ATTACHMENT_TYPE_OTHER },
|
||||
];
|
||||
/** @deprecated 兼容旧引用,实际选项由业务字典动态加载 */
|
||||
export const contractAttachmentTypeOptions = defaultContractAttachmentTypeOptions;
|
||||
export const isContractSealMaterialLocation = location => {
|
||||
const text = String(location || '');
|
||||
return text === CONTRACT_SEAL_MATERIAL_LOCATION || text === '合同文件';
|
||||
};
|
||||
export const attachmentFileTypes = [
|
||||
'pdf',
|
||||
'bmp',
|
||||
@@ -234,8 +262,11 @@ const isOtherAttachmentTypeOption = item => {
|
||||
const attachmentTypeKeywords = fileType => {
|
||||
const text = String(fileType || '');
|
||||
if (text.includes('双章')) return ['双章归档文件', '双章归档', '双章'];
|
||||
if (text.includes('合同') && !text.includes('其他') && !text.includes('其它')) {
|
||||
return ['合同文件', '合同'];
|
||||
if (
|
||||
text.includes('用印') ||
|
||||
(text.includes('合同') && !text.includes('其他') && !text.includes('其它'))
|
||||
) {
|
||||
return ['用印材料', '合同文件', '用印', '合同'];
|
||||
}
|
||||
if (isOtherAttachmentTypeOption({ label: text, value: text })) return [];
|
||||
return text ? [text] : [];
|
||||
@@ -254,8 +285,13 @@ export const resolveContractAttachmentType = (
|
||||
const list = options.length ? options : defaultContractAttachmentTypeOptions;
|
||||
const otherType = resolveOtherAttachmentType(list);
|
||||
const values = list.map(item => item.value);
|
||||
if (values.includes(row.fileType)) return row.fileType;
|
||||
const matchedByLabel = list.find(item => String(item.label) === String(row.fileType));
|
||||
const rawFileType = String(row.fileType || '');
|
||||
const aliasedFileType = CONTRACT_SEAL_MATERIAL_TYPE_ALIASES[rawFileType] || rawFileType;
|
||||
if (values.includes(aliasedFileType)) return aliasedFileType;
|
||||
if (values.includes(rawFileType)) return rawFileType;
|
||||
const matchedByLabel = list.find(
|
||||
item => String(item.label) === aliasedFileType || String(item.label) === rawFileType
|
||||
);
|
||||
if (matchedByLabel) return matchedByLabel.value;
|
||||
const fileName = normalizeAttachmentTypeText(attachmentName(row));
|
||||
if (!fileName) return otherType;
|
||||
@@ -283,17 +319,32 @@ export const normalizeContractFileRows = (rows, options = defaultContractAttachm
|
||||
description: row.description || '',
|
||||
}));
|
||||
|
||||
const mapDictOptions = response => {
|
||||
const mapDictOptions = (response, dictCode = CONTRACT_ATTACHMENT_TYPE_DICT) => {
|
||||
const data = response?.data?.data || response?.data || [];
|
||||
const records = Array.isArray(data) ? data : data.records || [];
|
||||
const applySealAlias = dictCode === CONTRACT_ATTACHMENT_TYPE_DICT;
|
||||
return records
|
||||
.map(item => ({
|
||||
label: item.dictValue,
|
||||
value: item.dictKey,
|
||||
}))
|
||||
.map(item => {
|
||||
const rawLabel = String(item.dictValue || '');
|
||||
const rawValue = String(item.dictKey || '');
|
||||
const label = applySealAlias
|
||||
? CONTRACT_SEAL_MATERIAL_TYPE_ALIASES[rawLabel] ||
|
||||
(rawLabel === '合同文件' ? CONTRACT_SEAL_MATERIAL_LOCATION : rawLabel)
|
||||
: rawLabel;
|
||||
const value = applySealAlias
|
||||
? CONTRACT_SEAL_MATERIAL_TYPE_ALIASES[rawValue] ||
|
||||
(rawValue === '合同文件' ? CONTRACT_SEAL_MATERIAL_LOCATION : rawValue)
|
||||
: rawValue;
|
||||
return { label, value };
|
||||
})
|
||||
.filter(item => item.label && item.value);
|
||||
};
|
||||
|
||||
const defaultOptionsByDictCode = dictCode =>
|
||||
dictCode === CONTRACT_SUPPORTING_ATTACHMENT_TYPE_DICT
|
||||
? [...defaultContractSupportingAttachmentTypeOptions]
|
||||
: [...defaultContractAttachmentTypeOptions];
|
||||
|
||||
export default {
|
||||
name: 'ContractAttachmentSection',
|
||||
components: { UploadFilled },
|
||||
@@ -307,8 +358,13 @@ export default {
|
||||
markApprovedOnUpload: Boolean,
|
||||
useUploadDialog: Boolean,
|
||||
dialogOnly: Boolean,
|
||||
attachmentLocation: { type: String, default: '合同文件' },
|
||||
attachmentLocation: { type: String, default: CONTRACT_SEAL_MATERIAL_LOCATION },
|
||||
attachmentLocationOptions: { type: Array, default: null },
|
||||
/** 附件类型业务字典 code,默认用印材料字典;佐证材料传 contract_supporting_attachment_type */
|
||||
attachmentTypeDictCode: {
|
||||
type: String,
|
||||
default: CONTRACT_ATTACHMENT_TYPE_DICT,
|
||||
},
|
||||
preview: { type: Function, default: null },
|
||||
},
|
||||
emits: ['update:rows', 'upload-to-location', 'upload-confirmed'],
|
||||
@@ -317,8 +373,9 @@ export default {
|
||||
selected: [],
|
||||
attachmentFileTypes,
|
||||
contractAttachmentTypeOptions: [...defaultContractAttachmentTypeOptions],
|
||||
activeAttachmentTypeDictCode: CONTRACT_ATTACHMENT_TYPE_DICT,
|
||||
uploadDialogVisible: false,
|
||||
uploadDialogLocation: '合同文件',
|
||||
uploadDialogLocation: CONTRACT_SEAL_MATERIAL_LOCATION,
|
||||
uploadDialogType: resolveOtherAttachmentType(defaultContractAttachmentTypeOptions),
|
||||
uploadDialogFiles: [],
|
||||
uploadDialogFileList: [],
|
||||
@@ -337,48 +394,91 @@ export default {
|
||||
defaultAttachmentType() {
|
||||
return resolveOtherAttachmentType(this.contractAttachmentTypeOptions);
|
||||
},
|
||||
resolvedAttachmentTypeDictCode() {
|
||||
return this.attachmentTypeDictCode || CONTRACT_ATTACHMENT_TYPE_DICT;
|
||||
},
|
||||
resolvedLocationOptions() {
|
||||
const options = (this.attachmentLocationOptions || []).filter(Boolean);
|
||||
if (options.length) return options;
|
||||
return ['合同文件', '其它附件'];
|
||||
if (options.length) {
|
||||
return options.map(item => {
|
||||
if (item === '合同文件') return CONTRACT_SEAL_MATERIAL_LOCATION;
|
||||
if (item === '其它附件' || item === '其他附件') {
|
||||
return CONTRACT_SUPPORTING_ATTACHMENT_LOCATION;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
}
|
||||
return [CONTRACT_SEAL_MATERIAL_LOCATION, CONTRACT_SUPPORTING_ATTACHMENT_LOCATION];
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
attachmentTypeDictCode: {
|
||||
immediate: false,
|
||||
handler() {
|
||||
this.loadAttachmentTypeOptions(this.resolvedAttachmentTypeDictCode, true);
|
||||
},
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.loadAttachmentTypeOptions();
|
||||
this.loadAttachmentTypeOptions(this.resolvedAttachmentTypeDictCode, true);
|
||||
},
|
||||
methods: {
|
||||
attachmentName,
|
||||
loadAttachmentTypeOptions() {
|
||||
return getBizDictionary({ code: 'contract_attachment_type' })
|
||||
currentUploadUserName() {
|
||||
const raw = this.$store.getters.userInfo;
|
||||
const userInfo = raw && !Array.isArray(raw) ? raw : {};
|
||||
// 与顶部栏一致优先 real_name;用印材料 / 佐证材料共用
|
||||
return (
|
||||
userInfo.real_name ||
|
||||
userInfo.realName ||
|
||||
userInfo.nick_name ||
|
||||
userInfo.nickName ||
|
||||
userInfo.user_name ||
|
||||
userInfo.userName ||
|
||||
userInfo.account ||
|
||||
''
|
||||
);
|
||||
},
|
||||
loadAttachmentTypeOptions(dictCode = this.resolvedAttachmentTypeDictCode, alignRows = false) {
|
||||
const code = dictCode || CONTRACT_ATTACHMENT_TYPE_DICT;
|
||||
this.activeAttachmentTypeDictCode = code;
|
||||
const fallbackOptions = defaultOptionsByDictCode(code);
|
||||
return getBizDictionary({ code })
|
||||
.then(res => {
|
||||
const options = mapDictOptions(res);
|
||||
// 切换字典期间若已发起更新请求,忽略过期响应
|
||||
if (this.activeAttachmentTypeDictCode !== code) return;
|
||||
const options = mapDictOptions(res, code);
|
||||
if (options.length) {
|
||||
// 字典缺少「其他文件」时补上,保证未匹配文件名可默认选中
|
||||
this.contractAttachmentTypeOptions = options.some(isOtherAttachmentTypeOption)
|
||||
? options
|
||||
: [...options, { label: '其他文件', value: CONTRACT_ATTACHMENT_TYPE_OTHER }];
|
||||
if (
|
||||
!this.contractAttachmentTypeOptions.some(
|
||||
item => String(item.value) === String(this.uploadDialogType)
|
||||
)
|
||||
) {
|
||||
this.uploadDialogType = this.defaultAttachmentType;
|
||||
}
|
||||
// 字典加载后,把已有行的中文类型名对齐到 dictKey,保证下拉可选中
|
||||
if (this.attachmentType && (this.rows || []).length) {
|
||||
const aligned = (this.rows || []).map(row => ({
|
||||
...row,
|
||||
fileType: resolveContractAttachmentType(row, this.contractAttachmentTypeOptions),
|
||||
}));
|
||||
const changed = aligned.some(
|
||||
(row, index) => row.fileType !== this.rows[index]?.fileType
|
||||
);
|
||||
if (changed) this.update(aligned);
|
||||
}
|
||||
} else {
|
||||
this.contractAttachmentTypeOptions = fallbackOptions;
|
||||
}
|
||||
if (
|
||||
!this.contractAttachmentTypeOptions.some(
|
||||
item => String(item.value) === String(this.uploadDialogType)
|
||||
)
|
||||
) {
|
||||
this.uploadDialogType = this.defaultAttachmentType;
|
||||
}
|
||||
// 字典加载后,把已有行的中文类型名对齐到 dictKey,保证下拉可选中
|
||||
if (alignRows && this.attachmentType && (this.rows || []).length) {
|
||||
const aligned = (this.rows || []).map(row => ({
|
||||
...row,
|
||||
fileType: resolveContractAttachmentType(row, this.contractAttachmentTypeOptions),
|
||||
}));
|
||||
const changed = aligned.some(
|
||||
(row, index) => row.fileType !== this.rows[index]?.fileType
|
||||
);
|
||||
if (changed) this.update(aligned);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// 字典加载失败时保留默认选项
|
||||
if (this.activeAttachmentTypeDictCode !== code) return;
|
||||
this.contractAttachmentTypeOptions = fallbackOptions;
|
||||
this.uploadDialogType = this.defaultAttachmentType;
|
||||
});
|
||||
},
|
||||
isRowLocked(row) {
|
||||
@@ -388,13 +488,14 @@ export default {
|
||||
this.$emit('update:rows', Array.isArray(rows) ? rows.map(item => ({ ...item })) : []);
|
||||
},
|
||||
enrichUploadedRow(row, existing) {
|
||||
const uploadUserName = this.$store.getters.userInfo?.realName || '';
|
||||
const uploadUserName = this.currentUploadUserName();
|
||||
const uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||
const next = {
|
||||
...existing,
|
||||
...row,
|
||||
description: row.description || existing?.description || '',
|
||||
uploadUserName: existing?.uploadUserName || row.uploadUserName || uploadUserName,
|
||||
// 新上传行始终写入当前用户;已有行保留原上传人
|
||||
uploadUserName: existing?.uploadUserName || uploadUserName || row.uploadUserName || '',
|
||||
uploadTime: existing?.uploadTime || row.uploadTime || uploadTime,
|
||||
approved: existing?.approved || row.approved || false,
|
||||
};
|
||||
@@ -434,20 +535,38 @@ export default {
|
||||
resolveTypeByLocation(location) {
|
||||
const options = this.contractAttachmentTypeOptions || [];
|
||||
const locationText = String(location || '');
|
||||
if (locationText.includes('其它') || locationText.includes('其他')) {
|
||||
if (
|
||||
isContractSupportingAttachmentLocation(locationText) ||
|
||||
locationText.includes('佐证') ||
|
||||
locationText.includes('其它') ||
|
||||
locationText.includes('其他')
|
||||
) {
|
||||
return this.defaultAttachmentType;
|
||||
}
|
||||
const matched = options.find(
|
||||
item =>
|
||||
String(item.value) === locationText ||
|
||||
String(item.label) === locationText ||
|
||||
String(item.value).includes('用印') ||
|
||||
String(item.label).includes('用印') ||
|
||||
String(item.value).includes('合同') ||
|
||||
String(item.label).includes('合同')
|
||||
);
|
||||
return matched?.value || options[0]?.value || this.defaultAttachmentType;
|
||||
},
|
||||
syncUploadDialogTypeByLocation() {
|
||||
this.uploadDialogType = this.resolveTypeByLocation(this.uploadDialogLocation);
|
||||
const dictCode = resolveAttachmentTypeDictCode(
|
||||
this.uploadDialogLocation,
|
||||
this.resolvedAttachmentTypeDictCode
|
||||
);
|
||||
const applyType = () => {
|
||||
this.uploadDialogType = this.resolveTypeByLocation(this.uploadDialogLocation);
|
||||
};
|
||||
if (dictCode !== this.activeAttachmentTypeDictCode) {
|
||||
this.loadAttachmentTypeOptions(dictCode, false).finally(applyType);
|
||||
return;
|
||||
}
|
||||
applyType();
|
||||
},
|
||||
openUploadDialog() {
|
||||
const options = this.resolvedLocationOptions;
|
||||
@@ -544,13 +663,13 @@ export default {
|
||||
this.$message.warning('请选择附件类型');
|
||||
return;
|
||||
}
|
||||
const uploadUserName = this.$store.getters.userInfo?.realName || '';
|
||||
const uploadUserName = this.currentUploadUserName();
|
||||
const uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||
const appended = this.uploadDialogFiles.map(row => ({
|
||||
...row,
|
||||
fileType: this.uploadDialogType,
|
||||
description: row.description || '',
|
||||
uploadUserName: row.uploadUserName || uploadUserName,
|
||||
uploadUserName: uploadUserName || row.uploadUserName || '',
|
||||
uploadTime: row.uploadTime || uploadTime,
|
||||
...(this.markApprovedOnUpload ? { approved: true } : {}),
|
||||
}));
|
||||
|
||||
@@ -266,7 +266,7 @@
|
||||
<el-col :span="6">
|
||||
<el-form-item label="单价">
|
||||
<el-input :model-value="freightGroupUnitPrice(group)" inputmode="decimal" placeholder="请输入" @input="value => handleFreightGroupUnitPriceInput(group, value)" @clear="() => handleFreightGroupUnitPriceInput(group, '')">
|
||||
<template #append><el-select :model-value="freightGroupPriceUnit(group)" class="freight-unit-select" @change="value => handleFreightGroupPriceUnitChange(group, value)"><el-option label="元/吨" value="元/吨" /><el-option label="元/件" value="元/件" /><el-option label="元/方" value="元/方" /></el-select></template>
|
||||
<template #append><el-select :model-value="freightGroupPriceUnit(group)" class="freight-unit-select" @change="value => handleFreightGroupPriceUnitChange(group, value)"><el-option v-for="item in priceUnitOptions(route)" :key="item.value" :label="item.label" :value="item.value" /></el-select></template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -318,6 +318,11 @@
|
||||
import * as api from '@/api/business/master-order';
|
||||
import { fetchDriversByCarrierOrganizations } from '@/utils/carrier-org-resource';
|
||||
import { formatContractOptionLabel } from '@/utils/contract';
|
||||
import {
|
||||
currencyUnit,
|
||||
remapPriceUnitCurrency,
|
||||
resolvePriceUnitByQuantityUnit,
|
||||
} from '@/utils/currency';
|
||||
import { loadMeasurementUnitOptions } from '@/utils/measurement-unit';
|
||||
import { isMobile } from '@/utils/validate';
|
||||
|
||||
@@ -429,8 +434,10 @@ export default {
|
||||
},
|
||||
async loadContractCurrency() {
|
||||
const directCurrency = this.master?.settlementCurrency || this.master?.settlementCurrencyCode || this.master?.currency;
|
||||
const previousCurrency = this.master?.settlementCurrency;
|
||||
if (!this.master?.projectId || !this.master?.contractId) {
|
||||
this.master.settlementCurrency = this.normalizeCurrencyValue(directCurrency || 'RMB');
|
||||
this.syncRouteCurrency(previousCurrency, this.master.settlementCurrency);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -444,6 +451,20 @@ export default {
|
||||
this.master.customerContractPartyB = '';
|
||||
this.master.settlementCurrency = 'RMB';
|
||||
}
|
||||
this.syncRouteCurrency(previousCurrency, this.master.settlementCurrency);
|
||||
},
|
||||
syncRouteCurrency(_previousCurrency, nextCurrency) {
|
||||
const currency = this.normalizeCurrencyValue(nextCurrency || 'RMB');
|
||||
(this.routes || []).forEach(route => {
|
||||
route.currency = currency;
|
||||
(route.freightItems || []).forEach(item => {
|
||||
if (item.priceUnit) {
|
||||
item.priceUnit = remapPriceUnitCurrency(item.priceUnit, currency);
|
||||
} else if (item.quantityUnit) {
|
||||
item.priceUnit = resolvePriceUnitByQuantityUnit(item.quantityUnit, currency);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
dispatchRouteNodes() {
|
||||
const routes = Array.isArray(this.master?.routes) ? this.master.routes : [];
|
||||
@@ -618,8 +639,23 @@ export default {
|
||||
const prices = (group.rows || []).map(item => String(item.unitPrice ?? '').trim()).filter(Boolean);
|
||||
return !prices.length || prices.some(price => price !== prices[0]) ? '' : prices[0];
|
||||
},
|
||||
freightGroupPriceUnit(group = {}) { return group.rows?.[0]?.priceUnit || (group.quantityUnit ? `元/${group.quantityUnit}` : ''); },
|
||||
freightGroupPriceUnit(group = {}) {
|
||||
if (group.rows?.[0]?.priceUnit) return group.rows[0].priceUnit;
|
||||
if (!group.quantityUnit) return '';
|
||||
return (
|
||||
resolvePriceUnitByQuantityUnit(group.quantityUnit, this.master?.settlementCurrency || 'RMB') ||
|
||||
`${currencyUnit(this.master?.settlementCurrency || 'RMB')}/${group.quantityUnit}`
|
||||
);
|
||||
},
|
||||
freightGroupAmount(group = {}) { return (group.rows || []).reduce((sum, item) => sum + this.freightAmount(item), 0); },
|
||||
priceUnitOptions(route = {}) {
|
||||
const unit = currencyUnit(route.currency || this.master?.settlementCurrency || 'RMB');
|
||||
return [
|
||||
{ label: `${unit}/吨`, value: `${unit}/吨` },
|
||||
{ label: `${unit}/件`, value: `${unit}/件` },
|
||||
{ label: `${unit}/方`, value: `${unit}/方` },
|
||||
];
|
||||
},
|
||||
handleFreightGroupUnitPriceInput(group, value) {
|
||||
const [integer = '', decimal = ''] = String(value || '').replace(/[^\d.]/g, '').split('.');
|
||||
const normalized = decimal || String(value || '').includes('.') ? `${integer}.${decimal.slice(0, 2)}` : integer;
|
||||
@@ -629,6 +665,10 @@ export default {
|
||||
handleFreightGroupQuantityUnitChange(route, group, value) {
|
||||
(group.rows || []).forEach(item => {
|
||||
item.quantityUnit = value || '';
|
||||
item.priceUnit = resolvePriceUnitByQuantityUnit(
|
||||
value,
|
||||
route.currency || this.master?.settlementCurrency || 'RMB'
|
||||
) || item.priceUnit;
|
||||
const goods = (route.goods || []).find(row => String(row.sourceIndex) === String(item.sourceIndex));
|
||||
if (goods) goods.quantityUnit = value || '';
|
||||
});
|
||||
@@ -651,7 +691,7 @@ export default {
|
||||
})),
|
||||
});
|
||||
},
|
||||
currencyLabel(value) { return ({ CNY: '元', RMB: '元', USD: '美元', EUR: '欧元' })[value] || value || '元'; },
|
||||
currencyLabel(value) { return currencyUnit(value); },
|
||||
quantityNumber(value) { return Number(value || 0); },
|
||||
goodsKey(goods = {}, includeUnit = true) {
|
||||
return [
|
||||
@@ -841,6 +881,7 @@ export default {
|
||||
removeGoods(route, index) { route.goods.splice(index, 1); this.syncFreightItems(route); },
|
||||
syncFreightItems(route) {
|
||||
const oldItems = route.freightItems || [];
|
||||
const currency = route.currency || this.master?.settlementCurrency || 'RMB';
|
||||
route.freightItems = (route.goods || [])
|
||||
.filter(item => item.sourceIndex !== undefined && item.sourceIndex !== '')
|
||||
.map(item => {
|
||||
@@ -852,7 +893,10 @@ export default {
|
||||
quantity: item.dispatchQuantity || '',
|
||||
quantityUnit: item.quantityUnit || '',
|
||||
unitPrice: old.unitPrice || '',
|
||||
priceUnit: old.priceUnit || `元/${item.quantityUnit || '吨'}`,
|
||||
priceUnit:
|
||||
old.priceUnit ||
|
||||
resolvePriceUnitByQuantityUnit(item.quantityUnit || '吨', currency) ||
|
||||
`${currencyUnit(currency)}/${item.quantityUnit || '吨'}`,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1744,11 +1744,11 @@
|
||||
><el-select
|
||||
:model-value="dispatchFreightGroupPriceUnit(group)"
|
||||
class="transport-plan-page__dispatch-unit-select"
|
||||
placeholder="元/吨"
|
||||
:placeholder="defaultPriceUnit"
|
||||
@change="value => handleDispatchFreightGroupPriceUnitChange(group, value)"
|
||||
@visible-change="visible => visible && loadTaskFeeUnitOptions()"
|
||||
><el-option
|
||||
v-for="item in taskFeeUnitOptions"
|
||||
v-for="item in taskFeeUnitSelectOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value" /></el-select
|
||||
@@ -1969,17 +1969,17 @@
|
||||
<el-select
|
||||
v-model="dispatchItemForm.priceUnit"
|
||||
class="transport-plan-page__dispatch-unit-select"
|
||||
placeholder="元/吨"
|
||||
:placeholder="defaultPriceUnit"
|
||||
:loading="taskFeeUnitLoading"
|
||||
@visible-change="visible => visible && loadTaskFeeUnitOptions()"
|
||||
>
|
||||
<el-option
|
||||
v-if="!taskFeeUnitOptions.some(item => item.value === '元/吨')"
|
||||
label="元/吨"
|
||||
value="元/吨"
|
||||
v-if="!taskFeeUnitSelectOptions.some(item => item.value === defaultPriceUnit)"
|
||||
:label="defaultPriceUnit"
|
||||
:value="defaultPriceUnit"
|
||||
/>
|
||||
<el-option
|
||||
v-for="item in taskFeeUnitOptions"
|
||||
v-for="item in taskFeeUnitSelectOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
@@ -2741,6 +2741,12 @@ import { addressTypeOptions } from '@/option/base/common-address';
|
||||
import { packageOptions } from '@/option/business/common';
|
||||
import { formatUpdateUserName } from '@/utils/audit';
|
||||
import { getToken, isAdministrator } from '@/utils/auth';
|
||||
import {
|
||||
currencyUnit,
|
||||
mapFeeUnitOptions,
|
||||
remapPriceUnitCurrency,
|
||||
resolvePriceUnitByQuantityUnit,
|
||||
} from '@/utils/currency';
|
||||
import { formatContractOptionLabel } from '@/utils/contract';
|
||||
import { normalizeMapSearchResults, searchAmapPlaceText } from '@/utils/map-search';
|
||||
import { loadMeasurementUnitOptions } from '@/utils/measurement-unit';
|
||||
@@ -3612,6 +3618,22 @@ export default {
|
||||
const value = this.dispatchItemForm.freightCurrency || 'RMB';
|
||||
return this.currencyRemark(value);
|
||||
},
|
||||
taskFeeUnitSelectOptions() {
|
||||
const currency =
|
||||
this.dispatchItemForm.freightCurrency ||
|
||||
this.dispatchContractCurrency ||
|
||||
this.form.settlementCurrency ||
|
||||
'RMB';
|
||||
return mapFeeUnitOptions(this.taskFeeUnitOptions, currency);
|
||||
},
|
||||
defaultPriceUnit() {
|
||||
return `${currencyUnit(
|
||||
this.dispatchItemForm.freightCurrency ||
|
||||
this.dispatchContractCurrency ||
|
||||
this.form.settlementCurrency ||
|
||||
'RMB'
|
||||
)}/吨`;
|
||||
},
|
||||
isAdmin() {
|
||||
return isAdministrator(this.userInfo);
|
||||
},
|
||||
@@ -4806,19 +4828,49 @@ export default {
|
||||
contract.customerNames ||
|
||||
this.form.customerName ||
|
||||
'';
|
||||
const nextCurrency = this.getContractCurrency(contract);
|
||||
this.remapTransportPlanPriceUnits(nextCurrency);
|
||||
this.$refs.crud?.validateField?.('contractName');
|
||||
},
|
||||
remapTransportPlanPriceUnits(currency) {
|
||||
if (this.form.priceUnit) {
|
||||
this.form.priceUnit = remapPriceUnitCurrency(this.form.priceUnit, currency);
|
||||
}
|
||||
if (this.dispatchItemForm.priceUnit) {
|
||||
this.dispatchItemForm.priceUnit = remapPriceUnitCurrency(
|
||||
this.dispatchItemForm.priceUnit,
|
||||
currency
|
||||
);
|
||||
}
|
||||
this.dispatchItemForm.freightCurrency = currency;
|
||||
(this.dispatchItemCargoRows || []).forEach(cargo => {
|
||||
if (cargo.priceUnit) {
|
||||
cargo.priceUnit = remapPriceUnitCurrency(cargo.priceUnit, currency);
|
||||
}
|
||||
});
|
||||
(this.transportCargoRows || []).forEach(cargo => {
|
||||
if (cargo.priceUnit) {
|
||||
cargo.priceUnit = remapPriceUnitCurrency(cargo.priceUnit, currency);
|
||||
}
|
||||
});
|
||||
},
|
||||
loadDispatchContractCurrency(plan = {}) {
|
||||
const findContract = () =>
|
||||
this.contractOptions.find(item => String(item.id) === String(plan.contractId)) ||
|
||||
this.contractOptions.find(item => item.contractName === plan.contractName);
|
||||
const applyCurrency = contract => {
|
||||
const previousCurrency = this.dispatchContractCurrency;
|
||||
this.dispatchContractCurrency = this.getContractCurrency(
|
||||
contract &&
|
||||
(contract.settlementCurrency || contract.settlementCurrencyCode || contract.currency)
|
||||
? contract
|
||||
: { settlementCurrency: plan.settlementCurrency }
|
||||
);
|
||||
if (previousCurrency && previousCurrency !== this.dispatchContractCurrency) {
|
||||
this.remapTransportPlanPriceUnits(this.dispatchContractCurrency);
|
||||
} else {
|
||||
this.dispatchItemForm.freightCurrency = this.dispatchContractCurrency;
|
||||
}
|
||||
return this.dispatchContractCurrency;
|
||||
};
|
||||
const current = findContract();
|
||||
@@ -5217,7 +5269,7 @@ export default {
|
||||
},
|
||||
addTransportCargoRow(index = -1, row = {}) {
|
||||
const nextRow = this.normalizeTransportCargoRow({
|
||||
priceUnit: this.form.priceUnit || '元/吨',
|
||||
priceUnit: this.form.priceUnit || this.defaultPriceUnit,
|
||||
...row,
|
||||
});
|
||||
if (
|
||||
@@ -7680,7 +7732,10 @@ export default {
|
||||
this.dispatchItemCargoRows = this.dispatchItemCargoRows.map((cargo, itemIndex) => ({
|
||||
...cargo,
|
||||
unitPrice: cargo.unitPrice || freightInfo.freightItems[itemIndex]?.unitPrice || '',
|
||||
priceUnit: cargo.priceUnit || freightInfo.freightItems[itemIndex]?.priceUnit || '元/吨',
|
||||
priceUnit:
|
||||
cargo.priceUnit ||
|
||||
freightInfo.freightItems[itemIndex]?.priceUnit ||
|
||||
this.defaultPriceUnit,
|
||||
freightAmount:
|
||||
cargo.freightAmount || freightInfo.freightItems[itemIndex]?.freightAmount || '',
|
||||
}));
|
||||
@@ -7929,7 +7984,7 @@ export default {
|
||||
return value;
|
||||
},
|
||||
addDispatchCargoRow(index = -1) {
|
||||
const nextRow = this.normalizeTransportCargoRow({ priceUnit: '元/吨' });
|
||||
const nextRow = this.normalizeTransportCargoRow({ priceUnit: this.defaultPriceUnit });
|
||||
if (index > -1) this.dispatchItemCargoRows.splice(index + 1, 0, nextRow);
|
||||
else this.dispatchItemCargoRows.push(nextRow);
|
||||
},
|
||||
@@ -7973,7 +8028,14 @@ export default {
|
||||
return prices[0];
|
||||
},
|
||||
dispatchFreightGroupPriceUnit(group = {}) {
|
||||
return group.rows?.[0]?.priceUnit || (group.quantityUnit ? `元/${group.quantityUnit}` : '');
|
||||
if (group.rows?.[0]?.priceUnit) return group.rows[0].priceUnit;
|
||||
if (!group.quantityUnit) return '';
|
||||
return (
|
||||
resolvePriceUnitByQuantityUnit(
|
||||
group.quantityUnit,
|
||||
this.dispatchItemForm.freightCurrency || this.dispatchContractCurrency || 'RMB'
|
||||
) || `${currencyUnit(this.dispatchContractCurrency || 'RMB')}/${group.quantityUnit}`
|
||||
);
|
||||
},
|
||||
dispatchFreightGroupAmount(group = {}) {
|
||||
const amount = (group.rows || []).reduce(
|
||||
|
||||
@@ -270,15 +270,22 @@
|
||||
prop="mileage"
|
||||
label="里程(km)"
|
||||
width="100"
|
||||
/><el-table-column prop="unitPrice" label="单价" width="90" /><el-table-column
|
||||
prop="freight"
|
||||
label="运费"
|
||||
width="100"
|
||||
/><el-table-column prop="otherFeeTotal" label="其他费用合计" width="120" /><el-table-column
|
||||
prop="freightTotal"
|
||||
label="运费合计"
|
||||
width="100"
|
||||
/><el-table-column
|
||||
>
|
||||
<template #default="{ row }">{{ formatOptionalNumber(row.mileage) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="unitPrice" label="单价" width="90">
|
||||
<template #default="{ row }">{{ formatOptionalNumber(row.unitPrice) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="freight" label="运费" width="100">
|
||||
<template #default="{ row }">{{ formatOptionalNumber(row.freight) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="otherFeeTotal" label="其他费用合计" width="120">
|
||||
<template #default="{ row }">{{ formatOptionalNumber(row.otherFeeTotal) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="freightTotal" label="运费合计" width="100">
|
||||
<template #default="{ row }">{{ formatOptionalNumber(row.freightTotal) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="actualStartDate"
|
||||
label="实际发货时间"
|
||||
width="160"
|
||||
@@ -1107,7 +1114,7 @@ const loadDetails = async () => {
|
||||
current: detailPage.current,
|
||||
size: detailPage.size,
|
||||
});
|
||||
details.value = res.data?.data?.records || [];
|
||||
details.value = (res.data?.data?.records || []).map(normalizeDetailRow);
|
||||
detailPage.total = res.data?.data?.total || 0;
|
||||
};
|
||||
const openCreate = () => prepareCreateForm(true);
|
||||
@@ -1156,7 +1163,7 @@ const loadBatchRows = async batchId => {
|
||||
if (!batchId) return;
|
||||
const res = await api.getImportDetails({ batchId, current: 1, size: 9999 });
|
||||
rows.value = extractRecords(res).map(({ id, ...item }, index) => ({
|
||||
...item,
|
||||
...normalizeDetailRow(item),
|
||||
_key: `${id || 'row'}-${index}`,
|
||||
batchNo: item.batchNo || form.batchNo,
|
||||
}));
|
||||
@@ -1300,10 +1307,22 @@ const carrierChange = value => {
|
||||
};
|
||||
const firstNotEmpty = (...values) => values.find(value => String(value ?? '').trim()) ?? '';
|
||||
const OPTIONAL_NUMBER_EMPTY_SENTINELS = [-1];
|
||||
const OPTIONAL_NUMBER_PROPS = ['mileage', 'unitPrice', 'freight', 'otherFeeTotal', 'freightTotal'];
|
||||
const normalizeOptionalImportNumber = (value, emptySentinels = OPTIONAL_NUMBER_EMPTY_SENTINELS) => {
|
||||
if (!String(value ?? '').trim()) return null;
|
||||
return emptySentinels.some(sentinel => Number(value) === sentinel) ? null : value;
|
||||
};
|
||||
const formatOptionalNumber = value => {
|
||||
const normalized = normalizeOptionalImportNumber(value);
|
||||
return normalized === null || normalized === undefined ? '' : normalized;
|
||||
};
|
||||
const normalizeDetailRow = row => {
|
||||
const next = { ...(row || {}) };
|
||||
OPTIONAL_NUMBER_PROPS.forEach(prop => {
|
||||
next[prop] = formatOptionalNumber(next[prop]);
|
||||
});
|
||||
return next;
|
||||
};
|
||||
const normalizeImportRow = row => ({
|
||||
...row,
|
||||
mileage: normalizeOptionalImportNumber(
|
||||
|
||||
@@ -1083,7 +1083,7 @@
|
||||
@visible-change="visible => visible && loadTaskFeeUnitOptions()"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in taskFeeUnitOptions"
|
||||
v-for="item in taskFeeUnitSelectOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
@@ -1416,7 +1416,7 @@
|
||||
@visible-change="visible => visible && loadTaskFeeUnitOptions()"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in taskFeeUnitOptions"
|
||||
v-for="item in taskFeeUnitSelectOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
@@ -3642,6 +3642,12 @@ import { addressTypeOptions } from '@/option/base/common-address';
|
||||
import { packageOptions } from '@/option/business/common';
|
||||
import { formatUpdateUserName } from '@/utils/audit';
|
||||
import { getToken, isAdministrator } from '@/utils/auth';
|
||||
import {
|
||||
currencyUnit,
|
||||
mapFeeUnitOptions,
|
||||
remapPriceUnitCurrency,
|
||||
resolvePriceUnitByQuantityUnit,
|
||||
} from '@/utils/currency';
|
||||
import { formatContractOptionLabel } from '@/utils/contract';
|
||||
import { wgs84ToGcj02 } from '@/utils/coordinate';
|
||||
import { openImportDialog } from '@/utils/import-excel';
|
||||
@@ -4185,6 +4191,8 @@ export default {
|
||||
},
|
||||
selectionList: [],
|
||||
attachmentUploadMode: false,
|
||||
/** 复制来源运单业务状态(表单字段 addDisplay:false,提交时需显式带回) */
|
||||
copySourceBusinessStatus: '',
|
||||
standaloneFormKey: '',
|
||||
// 实例所属路由的路径快照:用于判断当前 $route 是否还是「本实例自己的路由」。
|
||||
// 必须在 data 里初始化,不能放 created —— Vue 的 Options API 执行顺序是
|
||||
@@ -4581,6 +4589,12 @@ export default {
|
||||
if (!currency) return '';
|
||||
return this.currencyRemark(currency);
|
||||
},
|
||||
taskFeeUnitSelectOptions() {
|
||||
return mapFeeUnitOptions(this.taskFeeUnitOptions, this.taskFreightCurrency || 'RMB');
|
||||
},
|
||||
defaultPriceUnit() {
|
||||
return `${currencyUnit(this.taskFreightCurrency || 'RMB')}/吨`;
|
||||
},
|
||||
isAdmin() {
|
||||
return isAdministrator(this.userInfo);
|
||||
},
|
||||
@@ -5175,7 +5189,7 @@ export default {
|
||||
quantity: goods.quantity ?? goods.cargoQuantity ?? goods.goodsQuantity ?? '',
|
||||
quantityUnit: goods.quantityUnit || goods.goodsQuantityUnit || goods.unit || '吨',
|
||||
unitPrice: goods.unitPrice,
|
||||
priceUnit: goods.priceUnit || '元/吨',
|
||||
priceUnit: goods.priceUnit || this.defaultPriceUnit,
|
||||
});
|
||||
},
|
||||
canOperation(operation, row) {
|
||||
@@ -6477,6 +6491,18 @@ export default {
|
||||
if (isAddMode) {
|
||||
delete submitRow.id;
|
||||
delete submitRow.waybillNo;
|
||||
// 复制提交:状态列未进表单,Avue 可能不带回;显式保留原运单业务状态(含已完成)
|
||||
const copyId = this.$route.query.copyId || '';
|
||||
if (copyId && !saveAsDraft) {
|
||||
const copiedStatus =
|
||||
this.form?.businessStatus ||
|
||||
this.copySourceBusinessStatus ||
|
||||
submitRow.businessStatus ||
|
||||
'';
|
||||
if (copiedStatus) {
|
||||
submitRow.businessStatus = copiedStatus;
|
||||
}
|
||||
}
|
||||
}
|
||||
const skipValidation = saveAsDraft && this.config.skipDraftValidation === true;
|
||||
if (
|
||||
@@ -6596,7 +6622,29 @@ export default {
|
||||
selected?.currency ||
|
||||
this.form.settlementCurrency ||
|
||||
'RMB';
|
||||
this.taskFreightCurrency = this.normalizeFreightCurrency(value);
|
||||
const nextCurrency = this.normalizeFreightCurrency(value);
|
||||
const previousCurrency = this.taskFreightCurrency;
|
||||
this.taskFreightCurrency = nextCurrency;
|
||||
if (previousCurrency && previousCurrency !== nextCurrency) {
|
||||
this.remapWaybillPriceUnits(nextCurrency);
|
||||
}
|
||||
},
|
||||
remapWaybillPriceUnits(currency) {
|
||||
if (this.form.priceUnit) {
|
||||
this.form.priceUnit = remapPriceUnitCurrency(this.form.priceUnit, currency);
|
||||
}
|
||||
(this.transportCargoRows || []).forEach(cargo => {
|
||||
if (cargo.priceUnit) {
|
||||
cargo.priceUnit = remapPriceUnitCurrency(cargo.priceUnit, currency);
|
||||
}
|
||||
});
|
||||
(this.taskFreightGroups || []).forEach(group => {
|
||||
(group.rows || []).forEach(row => {
|
||||
if (row.priceUnit) {
|
||||
row.priceUnit = remapPriceUnitCurrency(row.priceUnit, currency);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
loadFreightCurrencyOptions() {
|
||||
if (this.freightCurrencyOptions.length || this.freightCurrencyLoading) {
|
||||
@@ -6880,6 +6928,8 @@ export default {
|
||||
if (type === 'add' && copyId) {
|
||||
this.api.getDetail(copyId).then(res => {
|
||||
const sourceData = res.data.data || {};
|
||||
// 原运单业务状态(如 completed)提交时需原样带回并生成应收应付
|
||||
this.copySourceBusinessStatus = sourceData.businessStatus || '';
|
||||
// 清除不应该复制的字段(业务状态与原运单保持一致)
|
||||
delete sourceData.id;
|
||||
delete sourceData.code;
|
||||
@@ -6901,14 +6951,18 @@ export default {
|
||||
delete sourceData.updateUserName;
|
||||
|
||||
this.applyFormDetail(sourceData);
|
||||
// 标记为手工创建;业务状态沿用原运单
|
||||
// 标记为手工创建;业务状态沿用原运单(含已完成)
|
||||
this.form.dataSource = '手工创建';
|
||||
if (this.copySourceBusinessStatus) {
|
||||
this.form.businessStatus = this.copySourceBusinessStatus;
|
||||
}
|
||||
done();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (type === 'add') {
|
||||
this.copySourceBusinessStatus = '';
|
||||
this.form = {
|
||||
...(this.config.defaultForm || {}),
|
||||
};
|
||||
@@ -7382,7 +7436,7 @@ export default {
|
||||
if (this.isSentinelMinusOne(this.form[prop])) this.form[prop] = '';
|
||||
});
|
||||
this.form.priceUnit =
|
||||
this.form.priceUnit || taskInfo.priceUnit || goodsInfo.priceUnit || '元/吨';
|
||||
this.form.priceUnit || taskInfo.priceUnit || goodsInfo.priceUnit || this.defaultPriceUnit;
|
||||
this.form.otherFeeTotal = this.normalizeOptionalAmount(
|
||||
this.form.otherFeeTotal || taskInfo.otherFeeTotal || goodsInfo.otherFeeTotal || ''
|
||||
);
|
||||
@@ -8095,7 +8149,7 @@ export default {
|
||||
if (index === 0 && !cargo.unitPrice && this.form.unitPrice) {
|
||||
cargo.unitPrice = this.form.unitPrice;
|
||||
}
|
||||
cargo.priceUnit = cargo.priceUnit || this.form.priceUnit || '元/吨';
|
||||
cargo.priceUnit = cargo.priceUnit || this.form.priceUnit || this.defaultPriceUnit;
|
||||
if (this.isBillingFieldEmpty(cargo.freightAmount)) {
|
||||
cargo.freightAmount = this.normalizeOptionalAmount(
|
||||
freightItems[index]?.freightAmount ?? ''
|
||||
@@ -8477,7 +8531,7 @@ export default {
|
||||
},
|
||||
addTransportCargoRow(index = -1, row = {}) {
|
||||
const nextRow = this.normalizeTransportCargoRow({
|
||||
priceUnit: this.form.priceUnit || '元/吨',
|
||||
priceUnit: this.form.priceUnit || this.defaultPriceUnit,
|
||||
...row,
|
||||
});
|
||||
if (
|
||||
@@ -8524,12 +8578,9 @@ export default {
|
||||
this.syncTransportCargoJson();
|
||||
},
|
||||
resolvePriceUnitByQuantityUnit(quantityUnit) {
|
||||
return (
|
||||
{
|
||||
吨: '元/吨',
|
||||
方: '元/立方',
|
||||
件: '元/件',
|
||||
}[String(quantityUnit || '').trim()] || ''
|
||||
return resolvePriceUnitByQuantityUnit(
|
||||
quantityUnit,
|
||||
this.taskFreightCurrency || this.form.settlementCurrency || 'RMB'
|
||||
);
|
||||
},
|
||||
handleTransportCargoQuantityUnitChange(quantityUnit) {
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
<el-form-item label="项目">
|
||||
<el-input v-model="form.projectName" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="所属组织">
|
||||
<el-form-item label="合同负责部门">
|
||||
<el-input v-model="form.organizationName" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="甲方">
|
||||
@@ -63,6 +63,15 @@
|
||||
disabled
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="申请日期">
|
||||
<el-date-picker
|
||||
v-model="form.applyDate"
|
||||
type="date"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
disabled
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="合同期限">
|
||||
<div class="contract-basic-section__date-range">
|
||||
<el-date-picker
|
||||
@@ -91,7 +100,7 @@
|
||||
<template #suffix>天</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="合同金额">
|
||||
<el-form-item label="合同金额" prop="contractAmount">
|
||||
<el-input
|
||||
:model-value="contractAmountDisplay"
|
||||
placeholder="请输入"
|
||||
@@ -154,7 +163,7 @@
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否需要加盖法人章">
|
||||
<el-select v-model="form.legalSealFlag" placeholder="请选择">
|
||||
<el-select v-model="form.legalSealFlag" placeholder="请选择" clearable>
|
||||
<el-option label="否" :value="0" />
|
||||
<el-option label="是" :value="1" />
|
||||
</el-select>
|
||||
@@ -173,20 +182,30 @@
|
||||
<el-input v-model="form.handlerUserName" disabled />
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item label="备注" class="contract-basic-section__remark">
|
||||
<el-form-item label="合同主要内容" class="contract-basic-section__remark">
|
||||
<el-input
|
||||
v-model="form.mainContent"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
maxlength="1000"
|
||||
show-word-limit
|
||||
placeholder="请输入合同主要内容"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="其他事项说明" class="contract-basic-section__remark">
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
placeholder="请输入备注"
|
||||
placeholder="请输入其他事项说明"
|
||||
/>
|
||||
</el-form-item>
|
||||
</section>
|
||||
|
||||
<contract-attachment-section
|
||||
title="合同文件"
|
||||
title="用印材料"
|
||||
description
|
||||
attachment-type
|
||||
:attachment-location-options="changeAttachmentLocationOptions"
|
||||
@@ -251,13 +270,20 @@
|
||||
<el-button type="primary" plain @click="addPaymentRatioRow">添加</el-button>
|
||||
</section>
|
||||
|
||||
<billing-plan-editor v-model="planDialogVisible" :value="planEditor" :index="planEditorIndex" @save="savePlan" />
|
||||
<billing-plan-editor
|
||||
v-model="planDialogVisible"
|
||||
:value="planEditor"
|
||||
:index="planEditorIndex"
|
||||
:settlement-currency="form.settlementCurrency"
|
||||
@save="savePlan"
|
||||
/>
|
||||
|
||||
<contract-attachment-section
|
||||
title="其它附件"
|
||||
title="附件(佐证材料等)"
|
||||
description
|
||||
attachment-type
|
||||
attachment-location="其它附件"
|
||||
attachment-location="附件(佐证材料等)"
|
||||
attachment-type-dict-code="contract_supporting_attachment_type"
|
||||
:attachment-location-options="changeAttachmentLocationOptions"
|
||||
:rows="attachments"
|
||||
:preview="previewAttachment"
|
||||
@@ -308,6 +334,8 @@ import * as api from '@/api/business/contract-manage';
|
||||
import { submitMkApprovalFlow } from '@/utils/mk-approval';
|
||||
import BillingPlanEditor from './components/billing-plan-editor.vue';
|
||||
import ContractAttachmentSection, {
|
||||
isContractSealMaterialLocation,
|
||||
isContractSupportingAttachmentLocation,
|
||||
normalizeContractFileRows,
|
||||
} from './components/contract-attachment-section.vue';
|
||||
import { getDictionary as getSystemDictionary } from '@/api/system/dict';
|
||||
@@ -424,6 +452,21 @@ export default {
|
||||
],
|
||||
rules: {
|
||||
contractName: [{ required: true, message: '请输入合同名称', trigger: 'blur' }],
|
||||
contractAmount: [
|
||||
{ required: true, message: '请输入合同金额', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
return callback(new Error('请输入合同金额'));
|
||||
}
|
||||
if (Number(value) < 0) {
|
||||
return callback(new Error('合同金额不能小于0'));
|
||||
}
|
||||
callback();
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
changeReason: [{ max: 500, message: '变更原因不能超过500个字符', trigger: 'blur' }],
|
||||
},
|
||||
};
|
||||
@@ -433,7 +476,7 @@ export default {
|
||||
return this.$route.query.name || '合同变更';
|
||||
},
|
||||
changeAttachmentLocationOptions() {
|
||||
return ['合同文件', '其它附件', '变更材料'];
|
||||
return ['用印材料', '附件(佐证材料等)', '变更材料'];
|
||||
},
|
||||
contractAmountDisplay() {
|
||||
const value = this.form.contractAmount;
|
||||
@@ -601,7 +644,7 @@ export default {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
// 审核通过后上一次的变更材料归集到「其它附件」,按文件地址/文件名去重,避免重复展示
|
||||
// 审核通过后上一次的变更材料归集到「附件(佐证材料等)」,按文件地址/文件名去重,避免重复展示
|
||||
mergeChangeAttachments(attachments = [], changeAttachments = []) {
|
||||
const key = item => this.attachmentUrl(item) || this.attachmentName(item);
|
||||
const exists = new Set(attachments.map(key));
|
||||
@@ -655,11 +698,11 @@ export default {
|
||||
previewAttachment(row, rows = this.attachments) { const url = this.attachmentUrl(row); if (!url) { this.$message.warning('附件地址为空,无法预览'); return; } if (this.isAttachmentImage(row)) { this.imagePreviewUrls = (rows || []).filter(item => this.isAttachmentImage(item) && this.attachmentUrl(item)).map(item => this.attachmentUrl(item)); this.imagePreviewIndex = Math.max(this.imagePreviewUrls.indexOf(url), 0); this.imagePreviewVisible = true; return; } this.previewFile = { name: this.attachmentName(row), url, mimeType: row.mimeType || row.contentType || '' }; this.documentPreviewVisible = true; },
|
||||
handleAttachmentUploadToLocation({ location, rows = [] }) {
|
||||
if (!rows.length) return;
|
||||
if (location === '合同文件') {
|
||||
if (isContractSealMaterialLocation(location)) {
|
||||
this.contractFileRows = [...(this.contractFileRows || []), ...rows];
|
||||
return;
|
||||
}
|
||||
if (location === '其它附件') {
|
||||
if (isContractSupportingAttachmentLocation(location)) {
|
||||
this.attachments = [...(this.attachments || []), ...rows];
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@
|
||||
<el-descriptions-item label="甲方">{{ detailValue('partyA') }}</el-descriptions-item>
|
||||
<el-descriptions-item label="乙方">{{ detailValue('partyB') }}</el-descriptions-item>
|
||||
<el-descriptions-item label="合同期限">{{ detailContractPeriod }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所属组织">{{
|
||||
<el-descriptions-item label="合同负责部门">{{
|
||||
detailValue('organizationName')
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="经办人">{{
|
||||
@@ -140,6 +140,9 @@
|
||||
<el-descriptions-item label="签订日期">{{
|
||||
detailValue('signDate')
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请日期">{{
|
||||
detailValue('applyDate')
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="合同格式">{{
|
||||
detailValue('contractFormat')
|
||||
}}</el-descriptions-item>
|
||||
@@ -171,14 +174,17 @@
|
||||
<el-descriptions-item label="是否电子章">
|
||||
{{ detailElectronicSealText }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="备注" :span="3">{{
|
||||
<el-descriptions-item label="合同主要内容" :span="3">{{
|
||||
detailValue('mainContent')
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="其他事项说明" :span="3">{{
|
||||
detailValue('remark')
|
||||
}}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</section>
|
||||
|
||||
<contract-attachment-section
|
||||
title="合同文件"
|
||||
title="用印材料"
|
||||
description
|
||||
attachment-type
|
||||
readonly
|
||||
@@ -294,7 +300,7 @@
|
||||
</section>
|
||||
|
||||
<contract-attachment-section
|
||||
title="其它附件"
|
||||
title="附件(佐证材料等)"
|
||||
description
|
||||
readonly
|
||||
:rows="detailAttachmentRows"
|
||||
@@ -377,8 +383,14 @@
|
||||
<el-form-item label="合同编号">
|
||||
<el-input
|
||||
v-model="form.contractNo"
|
||||
:disabled="formMode === 'edit'"
|
||||
placeholder="不填写,则系统自动生成"
|
||||
disabled
|
||||
:placeholder="
|
||||
formMode === 'add'
|
||||
? form.organizationId
|
||||
? '系统按部门规则自动生成'
|
||||
: '请先选择合同负责部门'
|
||||
: ''
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="合同名称" prop="contractName">
|
||||
@@ -417,7 +429,7 @@
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="所属组织" prop="organizationName">
|
||||
<el-form-item label="合同负责部门" prop="organizationName">
|
||||
<el-cascader
|
||||
v-model="organizationCascaderValue"
|
||||
:options="organizationOptions"
|
||||
@@ -471,6 +483,16 @@
|
||||
placeholder="请选择签订日期"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="申请日期">
|
||||
<el-date-picker
|
||||
v-model="form.applyDate"
|
||||
type="date"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="提交正式合同时自动填充"
|
||||
disabled
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="合同期限" prop="startDate">
|
||||
<div class="contract-manage-form__date-range">
|
||||
<el-date-picker
|
||||
@@ -497,7 +519,7 @@
|
||||
@input="value => integerInput('paymentDays', value)"
|
||||
><template #suffix>天</template></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="合同金额">
|
||||
<el-form-item label="合同金额" prop="contractAmount">
|
||||
<el-input
|
||||
:model-value="contractAmountDisplay"
|
||||
placeholder="请输入"
|
||||
@@ -557,7 +579,7 @@
|
||||
>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否需要加盖法人章" prop="legalSealFlag">
|
||||
<el-select v-model="form.legalSealFlag" placeholder="请选择">
|
||||
<el-select v-model="form.legalSealFlag" placeholder="请选择" clearable>
|
||||
<el-option label="否" :value="0" />
|
||||
<el-option label="是" :value="1" />
|
||||
</el-select>
|
||||
@@ -576,20 +598,30 @@
|
||||
<el-input v-model="form.handlerUserName" disabled />
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item label="备注" prop="remark" class="contract-manage-form__remark">
|
||||
<el-form-item label="合同主要内容" prop="mainContent" class="contract-manage-form__remark">
|
||||
<el-input
|
||||
v-model="form.mainContent"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
maxlength="1000"
|
||||
show-word-limit
|
||||
placeholder="请输入合同主要内容"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="其他事项说明" prop="remark" class="contract-manage-form__remark">
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
placeholder="请输入备注"
|
||||
placeholder="请输入其他事项说明"
|
||||
/>
|
||||
</el-form-item>
|
||||
</section>
|
||||
|
||||
<contract-attachment-section
|
||||
title="合同文件"
|
||||
title="用印材料"
|
||||
description
|
||||
attachment-type
|
||||
:attachment-location-options="contractAttachmentLocationOptions"
|
||||
@@ -809,10 +841,11 @@
|
||||
</section>
|
||||
|
||||
<contract-attachment-section
|
||||
title="其它附件"
|
||||
title="附件(佐证材料等)"
|
||||
description
|
||||
attachment-type
|
||||
attachment-location="其它附件"
|
||||
attachment-location="附件(佐证材料等)"
|
||||
attachment-type-dict-code="contract_supporting_attachment_type"
|
||||
:attachment-location-options="contractAttachmentLocationOptions"
|
||||
:readonly="isAttachmentUploadMode"
|
||||
:rows="attachmentRows"
|
||||
@@ -920,18 +953,19 @@
|
||||
v-model="billingPlanBox"
|
||||
:value="billingPlanForm"
|
||||
:index="billingPlanIndex"
|
||||
:settlement-currency="form.settlementCurrency"
|
||||
@save="saveBillingPlan"
|
||||
/>
|
||||
|
||||
<contract-attachment-section
|
||||
ref="listAttachmentUploader"
|
||||
dialog-only
|
||||
title="合同文件"
|
||||
title="用印材料"
|
||||
description
|
||||
attachment-type
|
||||
use-upload-dialog
|
||||
mark-approved-on-upload
|
||||
:attachment-location-options="['合同文件', '其它附件']"
|
||||
:attachment-location-options="['用印材料', '附件(佐证材料等)']"
|
||||
:rows="listUploadContractFileRows"
|
||||
@update:rows="listUploadContractFileRows = $event"
|
||||
@upload-to-location="handleListAttachmentUploadToLocation"
|
||||
@@ -988,6 +1022,7 @@
|
||||
v-model="detailBillingPlanBox"
|
||||
:value="detailBillingPlanForm"
|
||||
:index="detailBillingPlanIndex"
|
||||
:settlement-currency="detail.settlementCurrency || form.settlementCurrency"
|
||||
readonly
|
||||
/>
|
||||
|
||||
@@ -1026,6 +1061,8 @@ import ChangeRecordDetailDialog from '@/components/change-record-detail-dialog/m
|
||||
import ContractAttachmentSection, {
|
||||
attachmentName as sharedAttachmentName,
|
||||
attachmentUrl as sharedAttachmentUrl,
|
||||
isContractSealMaterialLocation,
|
||||
isContractSupportingAttachmentLocation,
|
||||
normalizeContractFileRows,
|
||||
} from './components/contract-attachment-section.vue';
|
||||
import PdfPreview from '@/components/pdf-preview/main.vue';
|
||||
@@ -1093,8 +1130,9 @@ const defaultForm = () => ({
|
||||
partyB: '',
|
||||
handlerUserName: '',
|
||||
signDate: '',
|
||||
applyDate: '',
|
||||
contractFormat: '',
|
||||
legalSealFlag: 0,
|
||||
legalSealFlag: null,
|
||||
copyCount: '',
|
||||
settlementCurrency: '',
|
||||
invoiceCycle: '',
|
||||
@@ -1110,6 +1148,7 @@ const defaultForm = () => ({
|
||||
effectiveType: '',
|
||||
feeGenerationMode: 'system',
|
||||
archiveStatus: '未归档',
|
||||
mainContent: '',
|
||||
remark: '',
|
||||
});
|
||||
// 复制新增:仅保留可编辑字段与明细,剔除主键、编号、审批与流程信息
|
||||
@@ -1121,6 +1160,7 @@ const createCopiedDetail = (detail = {}) => {
|
||||
return {
|
||||
...form,
|
||||
contractNo: '',
|
||||
applyDate: '',
|
||||
archiveStatus: '未归档',
|
||||
feeGenerationMode: detail.feeGenerationMode || '',
|
||||
billingEnabled: detail.billingEnabled,
|
||||
@@ -1215,13 +1255,12 @@ export default {
|
||||
signType: [{ required: true, message: '请选择签约类型', trigger: 'change' }],
|
||||
settlementCurrency: [{ required: true, message: '请选择结算币种', trigger: 'change' }],
|
||||
projectName: [{ required: true, message: '请选择所属项目', trigger: 'change' }],
|
||||
organizationName: [{ required: true, message: '请选择所属组织', trigger: 'change' }],
|
||||
organizationName: [{ required: true, message: '请选择合同负责部门', trigger: 'change' }],
|
||||
partyA: [{ required: true, message: '请选择甲方', trigger: 'change' }],
|
||||
partyB: [{ required: true, message: '请选择乙方', trigger: 'change' }],
|
||||
signDate: [{ required: true, message: '请选择签订日期', trigger: 'change' }],
|
||||
settlementMode: [{ required: true, message: '请选择结算方式', trigger: 'change' }],
|
||||
contractFormat: [{ required: true, message: '请选择合同格式', trigger: 'change' }],
|
||||
legalSealFlag: [{ required: true, message: '请选择是否需要加盖法人章', trigger: 'change' }],
|
||||
invoiceCycle: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
@@ -1236,6 +1275,22 @@ export default {
|
||||
],
|
||||
startDate: [{ required: true, message: '请选择开始日期', trigger: 'change' }],
|
||||
endDate: [{ required: true, message: '请选择结束日期', trigger: 'change' }],
|
||||
contractAmount: [
|
||||
{ required: true, message: '请输入合同金额', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
return callback(new Error('请输入合同金额'));
|
||||
}
|
||||
if (Number(value) < 0) {
|
||||
return callback(new Error('合同金额不能小于0'));
|
||||
}
|
||||
callback();
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
mainContent: [{ max: 1000, message: '合同主要内容不能超过1000个字符', trigger: 'blur' }],
|
||||
},
|
||||
submitAction: '',
|
||||
projectOptions: [],
|
||||
@@ -1345,8 +1400,8 @@ export default {
|
||||
return this.$route.query.attachmentUpload === '1';
|
||||
},
|
||||
contractAttachmentLocationOptions() {
|
||||
if (this.isAttachmentUploadMode) return ['合同文件'];
|
||||
return ['合同文件', '其它附件'];
|
||||
if (this.isAttachmentUploadMode) return ['用印材料'];
|
||||
return ['用印材料', '附件(佐证材料等)'];
|
||||
},
|
||||
contractAmountDisplay() {
|
||||
const value = this.form.contractAmount;
|
||||
@@ -1413,7 +1468,7 @@ export default {
|
||||
expandTrigger: 'click',
|
||||
};
|
||||
},
|
||||
// 所属组织级联:selectedOrganizationId 存部门 id,级联控件用根到节点的 id 路径,
|
||||
// 合同负责部门级联:selectedOrganizationId 存部门 id,级联控件用根到节点的 id 路径,
|
||||
// 两者在此双向转换;依赖 organizationOptions,树加载完成后回显自动刷新
|
||||
organizationCascaderValue: {
|
||||
get() {
|
||||
@@ -1775,6 +1830,7 @@ export default {
|
||||
this.applyDetail(createCopiedDetail(detail));
|
||||
this.form.handlerUserName =
|
||||
this.userInfo?.realName || this.userInfo?.userName || this.userInfo?.account || '';
|
||||
this.refreshPreviewContractNo();
|
||||
this.$nextTick(() => this.$refs.contractForm?.clearValidate());
|
||||
},
|
||||
closeForm() {
|
||||
@@ -1789,9 +1845,10 @@ export default {
|
||||
['contractName', '合同名称'],
|
||||
['contractCategory', '合同类型'],
|
||||
['projectName', '所属项目'],
|
||||
['organizationName', '所属组织'],
|
||||
['organizationName', '合同负责部门'],
|
||||
['partyA', '甲方'],
|
||||
['partyB', '乙方'],
|
||||
['contractAmount', '合同金额'],
|
||||
];
|
||||
const empty = required.find(([prop]) => {
|
||||
const value = this.form[prop];
|
||||
@@ -1799,10 +1856,16 @@ export default {
|
||||
});
|
||||
if (empty) {
|
||||
this.$message.warning(
|
||||
empty[0] === 'contractName' ? `请输入${empty[1]}` : `请选择${empty[1]}`
|
||||
['contractName', 'contractAmount'].includes(empty[0])
|
||||
? `请输入${empty[1]}`
|
||||
: `请选择${empty[1]}`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (Number(this.form.contractAmount) < 0) {
|
||||
this.$message.warning('合同金额不能小于0');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (!this.form.contractName?.trim()) {
|
||||
@@ -1825,7 +1888,7 @@ export default {
|
||||
if (action !== 'formal' && action !== 'edit') return true;
|
||||
const required = [
|
||||
['projectName', '所属项目'],
|
||||
['organizationName', '所属组织'],
|
||||
['organizationName', '合同负责部门'],
|
||||
['partyA', '甲方'],
|
||||
['partyB', '乙方'],
|
||||
['startDate', '合同开始日期'],
|
||||
@@ -1833,10 +1896,20 @@ export default {
|
||||
['signDate', '签订日期'],
|
||||
['settlementMode', '结算方式'],
|
||||
['contractFormat', '合同格式'],
|
||||
['contractAmount', '合同金额'],
|
||||
];
|
||||
const empty = required.find(([prop]) => !this.form[prop]);
|
||||
const empty = required.find(([prop]) => {
|
||||
const value = this.form[prop];
|
||||
return value === undefined || value === null || String(value).trim() === '';
|
||||
});
|
||||
if (empty) {
|
||||
this.$message.warning(`请选择或输入${empty[1]}`);
|
||||
this.$message.warning(
|
||||
empty[0] === 'contractAmount' ? `请输入${empty[1]}` : `请选择或输入${empty[1]}`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (Number(this.form.contractAmount) < 0) {
|
||||
this.$message.warning('合同金额不能小于0');
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
@@ -2026,11 +2099,11 @@ export default {
|
||||
},
|
||||
handleListAttachmentUploadToLocation({ location, rows = [] }) {
|
||||
if (!rows.length) return;
|
||||
if (location === '合同文件') {
|
||||
if (isContractSealMaterialLocation(location)) {
|
||||
this.listUploadContractFileRows = [...(this.listUploadContractFileRows || []), ...rows];
|
||||
return;
|
||||
}
|
||||
if (location === '其它附件') {
|
||||
if (isContractSupportingAttachmentLocation(location)) {
|
||||
this.listUploadAttachmentRows = [...(this.listUploadAttachmentRows || []), ...rows];
|
||||
}
|
||||
},
|
||||
@@ -2150,6 +2223,9 @@ export default {
|
||||
column.renderSearch = scope => this.renderOrganizationSearch(scope);
|
||||
}
|
||||
this.syncCurrentOrganizationOption();
|
||||
if (this.isFormPage && this.formMode === 'add' && this.form.organizationId) {
|
||||
this.refreshPreviewContractNo();
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.organizationLoading = false;
|
||||
@@ -2212,6 +2288,29 @@ export default {
|
||||
}
|
||||
: { organizationId: '', organizationName: '' }
|
||||
);
|
||||
if (this.formMode === 'add') {
|
||||
this.refreshPreviewContractNo();
|
||||
}
|
||||
},
|
||||
refreshPreviewContractNo() {
|
||||
if (this.formMode !== 'add') return;
|
||||
const organizationId = this.form.organizationId;
|
||||
if (!organizationId) {
|
||||
this.form.contractNo = '';
|
||||
return;
|
||||
}
|
||||
const requestId = (this.contractNoPreviewRequestId =
|
||||
(this.contractNoPreviewRequestId || 0) + 1);
|
||||
api
|
||||
.nextContractNo(organizationId)
|
||||
.then(res => {
|
||||
if (requestId !== this.contractNoPreviewRequestId) return;
|
||||
this.form.contractNo = res.data?.data || '';
|
||||
})
|
||||
.catch(() => {
|
||||
if (requestId !== this.contractNoPreviewRequestId) return;
|
||||
this.form.contractNo = '';
|
||||
});
|
||||
},
|
||||
syncCurrentOrganizationOption() {
|
||||
if (!this.form.organizationId || !this.form.organizationName) return;
|
||||
@@ -2225,7 +2324,7 @@ export default {
|
||||
this.organizationFlatOptions.unshift(item);
|
||||
},
|
||||
renderOrganizationSearch(scope) {
|
||||
// 搜索区所属组织:单选级联(与新增/编辑表单一致)
|
||||
// 搜索区合同负责部门:单选级联(与新增/编辑表单一致)
|
||||
// 级联返回 id 路径数组,提交时由 normalizeSearch 取末级 id 转组织名称
|
||||
return h(ElCascader, {
|
||||
modelValue: Array.isArray(scope.row?.organizationName) ? scope.row.organizationName : [],
|
||||
@@ -2244,7 +2343,7 @@ export default {
|
||||
filterable: true,
|
||||
clearable: true,
|
||||
style: 'width: 100%',
|
||||
placeholder: '请选择 所属组织',
|
||||
placeholder: '请选择 合同负责部门',
|
||||
});
|
||||
},
|
||||
integerInput(prop, value) {
|
||||
@@ -2289,11 +2388,11 @@ export default {
|
||||
},
|
||||
handleAttachmentUploadToLocation({ location, rows = [] }) {
|
||||
if (!rows.length) return;
|
||||
if (location === '合同文件') {
|
||||
if (isContractSealMaterialLocation(location)) {
|
||||
this.contractFileRows = [...(this.contractFileRows || []), ...rows];
|
||||
return;
|
||||
}
|
||||
if (location === '其它附件') {
|
||||
if (isContractSupportingAttachmentLocation(location)) {
|
||||
this.attachmentRows = [...(this.attachmentRows || []), ...rows];
|
||||
}
|
||||
},
|
||||
@@ -2654,15 +2753,16 @@ export default {
|
||||
templateFlag: '是否范本',
|
||||
originalContractNo: '原件合同编号',
|
||||
electronicSealFlag: '是否电子章',
|
||||
remark: '备注',
|
||||
mainContent: '合同主要内容',
|
||||
remark: '其他事项说明',
|
||||
feeGenerationMode: '费用生成模式',
|
||||
billingPlanJson: '计费方案',
|
||||
settlementRuleJson: '结算单规则',
|
||||
preSettlementConfigJson: '预结算配置',
|
||||
formalSettlementConfigJson: '正式结算配置',
|
||||
paymentRatioJson: '付款比例设置',
|
||||
contractFileJson: '合同文件',
|
||||
attachmentsJson: '其它附件',
|
||||
contractFileJson: '用印材料',
|
||||
attachmentsJson: '附件(佐证材料等)',
|
||||
changeAttachmentsJson: '变更材料',
|
||||
};
|
||||
return labels[field] || field;
|
||||
|
||||
@@ -357,6 +357,33 @@ export default {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '合同编码前缀',
|
||||
prop: 'contractCodePrefix',
|
||||
minWidth: 160,
|
||||
order: 62,
|
||||
maxlength: 64,
|
||||
showWordLimit: true,
|
||||
placeholder: '如 GWZH成都',
|
||||
tip: '合同编号规则:前缀(年份)三位流水号,流水按本部门独立编号',
|
||||
rules: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
const prefix = String(value || '').trim();
|
||||
if (!prefix) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
if (prefix.length > 64) {
|
||||
callback(new Error('合同编码前缀不能超过64个字符'));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: '组织简称',
|
||||
prop: 'shortName',
|
||||
@@ -995,6 +1022,7 @@ export default {
|
||||
row.tenantId = row.tenantId || this.userInfo.tenantId || website.tenantId;
|
||||
row.deptCode = String(row.deptCode || '').trim();
|
||||
row.kingdeeDeptCode = String(row.kingdeeDeptCode || '').trim();
|
||||
row.contractCodePrefix = String(row.contractCodePrefix || '').trim();
|
||||
row.leaderId = func.join(row.leaderId);
|
||||
this.normalizeTopParent(row);
|
||||
if (!this.prepareDeptSubmitRow(row)) {
|
||||
@@ -1030,6 +1058,7 @@ export default {
|
||||
row.tenantId = row.tenantId || this.userInfo.tenantId || website.tenantId;
|
||||
row.deptCode = String(row.deptCode || '').trim();
|
||||
row.kingdeeDeptCode = String(row.kingdeeDeptCode || '').trim();
|
||||
row.contractCodePrefix = String(row.contractCodePrefix || '').trim();
|
||||
row.leaderId = func.join(row.leaderId);
|
||||
this.normalizeTopParent(row);
|
||||
if (!this.prepareDeptSubmitRow(row)) {
|
||||
|
||||
@@ -428,4 +428,43 @@ export default {
|
||||
.vehicle-dispatch-page :deep(.vehicle-dispatch-form-control) {
|
||||
width: 100%;
|
||||
}
|
||||
/* 查看弹窗保持与新增/编辑相同的表单样式,仅禁用控件 */
|
||||
.vehicle-dispatch-page :deep(.avue--detail .el-form-item) {
|
||||
background-color: transparent;
|
||||
}
|
||||
.vehicle-dispatch-page :deep(.avue--detail .el-input.is-disabled .el-input__wrapper) {
|
||||
box-shadow: 0 0 0 1px var(--el-disabled-border-color) inset;
|
||||
background-color: var(--el-disabled-bg-color);
|
||||
}
|
||||
.vehicle-dispatch-page :deep(.avue--detail .el-input.is-disabled .el-input__inner),
|
||||
.vehicle-dispatch-page :deep(.avue--detail .el-textarea.is-disabled .el-textarea__inner),
|
||||
.vehicle-dispatch-page :deep(.avue--detail .el-range-editor.is-disabled),
|
||||
.vehicle-dispatch-page :deep(.avue--detail .el-range-editor.is-disabled input) {
|
||||
color: var(--el-disabled-text-color);
|
||||
background-color: var(--el-disabled-bg-color);
|
||||
padding-left: 11px;
|
||||
box-shadow: 0 0 0 1px var(--el-disabled-border-color) inset;
|
||||
-webkit-text-fill-color: var(--el-disabled-text-color);
|
||||
}
|
||||
.vehicle-dispatch-page :deep(.avue--detail .el-select__wrapper.is-disabled) {
|
||||
background-color: var(--el-disabled-bg-color);
|
||||
box-shadow: 0 0 0 1px var(--el-disabled-border-color) inset !important;
|
||||
}
|
||||
.vehicle-dispatch-page :deep(.avue--detail .el-select__suffix),
|
||||
.vehicle-dispatch-page :deep(.avue--detail .el-input__suffix),
|
||||
.vehicle-dispatch-page :deep(.avue--detail .el-input__prefix) {
|
||||
display: inline-flex;
|
||||
}
|
||||
.vehicle-dispatch-page :deep(.avue--detail .el-input__inner),
|
||||
.vehicle-dispatch-page :deep(.avue--detail .el-textarea__inner) {
|
||||
border: none;
|
||||
}
|
||||
.vehicle-dispatch-page :deep(.avue--detail .el-cascader) {
|
||||
width: 100%;
|
||||
}
|
||||
.vehicle-dispatch-page :deep(.avue--detail .el-cascader .el-input.is-disabled .el-input__wrapper),
|
||||
.vehicle-dispatch-page :deep(.avue--detail .el-autocomplete .el-input.is-disabled .el-input__wrapper) {
|
||||
box-shadow: 0 0 0 1px var(--el-disabled-border-color) inset;
|
||||
background-color: var(--el-disabled-bg-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -985,7 +985,7 @@ export default {
|
||||
}
|
||||
this.applyDrivingLicenseValidity(inspectionValidity);
|
||||
if (registrationDate) {
|
||||
this.vehicleForm.registrationDate = this.normalizeDate(registrationDate);
|
||||
this.vehicleForm.registrationDate = this.normalizeDate(registrationDate, 'first');
|
||||
}
|
||||
if (registrationNo) {
|
||||
this.vehicleForm.registrationNo = String(registrationNo).replace(/\s/g, '');
|
||||
@@ -1000,6 +1000,7 @@ export default {
|
||||
'vehicleType',
|
||||
'drivingLicenseNo',
|
||||
'drivingLicenseEndDate',
|
||||
'registrationDate',
|
||||
'registrationNo',
|
||||
'approvedLoadKg',
|
||||
'outerLength',
|
||||
@@ -1057,9 +1058,9 @@ export default {
|
||||
this.vehicleForm.roadTransportCertEndDate = '';
|
||||
return;
|
||||
}
|
||||
const dateList = value.match(/\d{4}[-/.年]\d{1,2}[-/.月]\d{1,2}日?/g) || [];
|
||||
if (dateList.length) {
|
||||
this.vehicleForm.roadTransportCertEndDate = this.normalizeDate(dateList[dateList.length - 1]);
|
||||
const endDate = this.normalizeDate(value, 'last');
|
||||
if (endDate) {
|
||||
this.vehicleForm.roadTransportCertEndDate = endDate;
|
||||
this.vehicleForm.roadTransportCertLongTerm = 0;
|
||||
}
|
||||
},
|
||||
@@ -1071,9 +1072,9 @@ export default {
|
||||
this.vehicleForm.drivingLicenseEndDate = '';
|
||||
return;
|
||||
}
|
||||
const dateList = value.match(/\d{4}[-/.年]\d{1,2}[-/.月]\d{1,2}日?/g) || [];
|
||||
if (dateList.length) {
|
||||
this.vehicleForm.drivingLicenseEndDate = this.normalizeDate(dateList[dateList.length - 1]);
|
||||
const endDate = this.normalizeDate(value, 'last');
|
||||
if (endDate) {
|
||||
this.vehicleForm.drivingLicenseEndDate = endDate;
|
||||
this.vehicleForm.drivingLicenseLongTerm = 0;
|
||||
}
|
||||
},
|
||||
@@ -1150,11 +1151,11 @@ export default {
|
||||
this.vehicleForm.drivingLicenseNo = drivingLicenseNo;
|
||||
}
|
||||
if (drivingLicenseEndDate) {
|
||||
this.vehicleForm.drivingLicenseEndDate = this.normalizeDate(drivingLicenseEndDate);
|
||||
this.vehicleForm.drivingLicenseEndDate = this.normalizeDate(drivingLicenseEndDate, 'last');
|
||||
this.vehicleForm.drivingLicenseLongTerm = 0;
|
||||
}
|
||||
if (registrationDate) {
|
||||
this.vehicleForm.registrationDate = this.normalizeDate(registrationDate);
|
||||
this.vehicleForm.registrationDate = this.normalizeDate(registrationDate, 'first');
|
||||
}
|
||||
if (registrationNo) {
|
||||
this.vehicleForm.registrationNo = registrationNo;
|
||||
@@ -1165,18 +1166,60 @@ export default {
|
||||
'vehicleType',
|
||||
'drivingLicenseNo',
|
||||
'drivingLicenseEndDate',
|
||||
'registrationDate',
|
||||
'registrationNo',
|
||||
].forEach(prop => {
|
||||
this.$refs.vehicleForm?.validateField(prop);
|
||||
});
|
||||
});
|
||||
},
|
||||
normalizeDate(value = '') {
|
||||
/**
|
||||
* OCR 日期常为区间(如 2022-08-17至2024-01-05)或含无效日(如 01-65)。
|
||||
* 提取可解析的合法日期;prefer=first 取登记类起始日,prefer=last 取有效期截止日。
|
||||
*/
|
||||
normalizeDate(value = '', prefer = 'first') {
|
||||
const dateValue = String(value || '').trim();
|
||||
const match = dateValue.match(/^(\d{4})[-/.年](\d{1,2})[-/.月](\d{1,2})日?$/);
|
||||
if (!match) return dateValue;
|
||||
const [, year, month, day] = match;
|
||||
return `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`;
|
||||
if (!dateValue) return '';
|
||||
const segments = dateValue.match(/\d{4}[-/.年]\d{1,2}[-/.月]\d{1,2}日?/g) || [];
|
||||
const sourceList = segments.length ? segments : [dateValue];
|
||||
const candidates = sourceList
|
||||
.map(item => {
|
||||
const match = String(item)
|
||||
.trim()
|
||||
.match(/^(\d{4})[-/.年](\d{1,2})[-/.月](\d{1,2})日?$/);
|
||||
if (!match) return '';
|
||||
const year = Number(match[1]);
|
||||
const month = Number(match[2]);
|
||||
const day = Number(match[3]);
|
||||
const date = new Date(year, month - 1, day);
|
||||
if (
|
||||
date.getFullYear() !== year ||
|
||||
date.getMonth() !== month - 1 ||
|
||||
date.getDate() !== day
|
||||
) {
|
||||
return '';
|
||||
}
|
||||
return `${String(year).padStart(4, '0')}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
||||
})
|
||||
.filter(Boolean);
|
||||
if (!candidates.length) return '';
|
||||
return prefer === 'last' ? candidates[candidates.length - 1] : candidates[0];
|
||||
},
|
||||
normalizeVehicleDateFields(form = {}) {
|
||||
const dateFields = [
|
||||
['compulsoryScrapDate', 'first'],
|
||||
['drivingLicenseStartDate', 'first'],
|
||||
['drivingLicenseEndDate', 'last'],
|
||||
['roadTransportCertStartDate', 'first'],
|
||||
['roadTransportCertEndDate', 'last'],
|
||||
['annualReviewEndDate', 'last'],
|
||||
['registrationDate', 'first'],
|
||||
];
|
||||
dateFields.forEach(([prop, prefer]) => {
|
||||
if (form[prop] === undefined || form[prop] === null || form[prop] === '') return;
|
||||
form[prop] = this.normalizeDate(form[prop], prefer);
|
||||
});
|
||||
return form;
|
||||
},
|
||||
normalizeVehicleType(value = '') {
|
||||
const vehicleType = String(value || '').trim();
|
||||
@@ -1207,12 +1250,14 @@ export default {
|
||||
},
|
||||
handleSubmit() {
|
||||
this.syncPlateNo();
|
||||
this.normalizeVehicleDateFields(this.vehicleForm);
|
||||
this.$refs.vehicleForm.validate(valid => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
this.submitLoading = true;
|
||||
submit(this.vehicleForm)
|
||||
const payload = this.normalizeVehicleDateFields({ ...this.vehicleForm });
|
||||
submit(payload)
|
||||
.then(() => {
|
||||
this.$message.success('操作成功');
|
||||
this.vehicleBox = false;
|
||||
|
||||
Reference in New Issue
Block a user