Merge branch 'developer' of ssh://code.cyweb.top:30033/scj/zhxy/v3/cloud-ui into developer

This commit is contained in:
zhoutianchi
2026-02-03 18:04:39 +08:00
4 changed files with 593 additions and 91 deletions

View File

@@ -211,10 +211,33 @@
upload-file-url="/purchase/purchasingfiles/upload" />
</el-form-item>
<!-- 询价 -->
<el-form-item
v-if="isInquiryPurchaseType"
label="询价模板"
prop="inquiryTemplate"
class="mb20">
<el-button
type="primary"
link
icon="Download"
@click="downloadTemplate('inquiry')"
class="mb10">
下载部门采购询价模版模版
</el-button>
<upload-file
v-model="dataForm.inquiryTemplate"
:limit="5"
:file-type="['doc', 'docx', 'pdf']"
:data="{ fileType: FILE_TYPE_MAP.inquiryTemplate }"
upload-file-url="/purchase/purchasingfiles/upload" />
</el-form-item>
<!-- 委托采购中心 -->
<template v-if="isPurchaseType(PURCHASE_TYPE_IDS.ENTRUST_CENTER)">
<el-form-item label="委托采购中心方式" prop="entrustCenterType" class="mb20">
<el-radio-group v-model="dataForm.entrustCenterType">
<!-- 由系统根据品目末级节点标记自动判断不允许用户手动选择 -->
<el-radio-group v-model="dataForm.entrustCenterType" disabled>
<el-radio label="service_online">服务类网上商城</el-radio>
<el-radio label="other">其他方式</el-radio>
</el-radio-group>
@@ -224,12 +247,12 @@
<template v-if="dataForm.entrustCenterType === 'service_online' && categoryCodePath && categoryCodePath.length > 0 && categoryCodePath[0] === 'C'">
<el-form-item label="是否有供应商" prop="hasSupplier" class="mb20">
<el-radio-group v-model="dataForm.hasSupplier">
<el-radio label="yes"></el-radio>
<el-radio label="no"></el-radio>
<el-radio label="1"></el-radio>
<el-radio label="0"></el-radio>
</el-radio-group>
</el-form-item>
<!-- 有供应商显示供应商名称输入框 -->
<template v-if="dataForm.hasSupplier === 'yes'">
<template v-if="dataForm.hasSupplier === '1'">
<el-form-item
label="供应商名称"
prop="suppliers"
@@ -776,6 +799,7 @@ const dataForm = reactive({
businessNegotiationTable: '',
marketPurchaseMinutes: '',
onlineMallMaterials: '',
inquiryTemplate: '', // 询价模板
entrustCenterType: '',
hasSupplier: '',
suppliers: '', // 供应商名称(逗号或分号分隔)
@@ -831,10 +855,11 @@ const PURCHASE_TYPE_IDS = {
};
// 文件类型映射(对应数据库 file_type 字段)
// 10:商务洽谈纪要 20:市场采购纪要 30:网上商城采购相关材料 40:可行性论证报告 50:会议记录 60:其他材料 70:单一来源专家论证表 80:进口产品申请表 90:进口产品专家论证表 100:政府采购意向表 110:履约验收单
// 10:商务洽谈纪要 20:市场采购纪要 30:网上商城采购相关材料 40:可行性论证报告 50:会议记录 60:其他材料 70:单一来源专家论证表 80:进口产品申请表 90:进口产品专家论证表 100:政府采购意向表 110:履约验收单 120:采购需求表 130:采购文件
const FILE_TYPE_MAP: Record<string, string> = {
businessNegotiationTable: '10', // 商务洽谈纪要
marketPurchaseMinutes: '20', // 市场采购纪要
inquiryTemplate: '20', // 询价模板(归类到市场采购纪要)
onlineMallMaterials: '30', // 网上商城采购相关材料
feasibilityReport: '40', // 可行性论证报告
meetingMinutes: '50', // 会议记录
@@ -845,16 +870,16 @@ const FILE_TYPE_MAP: Record<string, string> = {
singleSourceProof: '70', // 单一来源专家论证表
importApplication: '80', // 进口产品申请表
governmentPurchaseIntent: '100', // 政府采购意向表
// 需求文件相关(暂时使用默认值,可根据实际需求调整
serviceDirectSelect: '30', // 服务商城项目需求模板(直选)- 归类到网上商城采购相关材料
serviceInviteSelect: '30', // 服务商城项目需求模板(邀请比选)
servicePublicSelect: '30', // 服务商城项目需求模板(公开比选)
servicePublicSelectAuto: '30', // 服务商城项目需求模板(公开比选-自动)
purchaseRequirementTemplate: '30', // 采购需求填报模板
purchaseRequirement: '30', // 采购需求填报模板
serviceInviteSelectSchool: '30', // 服务商城项目需求模板(邀请比选-学校)
servicePublicSelectSchoolAuto: '30', // 服务商城项目需求模板(公开比选-学校-自动)
servicePublicSelectSchool: '30', // 服务商城项目需求模板(公开比选-学校)
// 需求文件相关 - 所有需求模板都应该是120采购需求表
serviceDirectSelect: '120', // 服务商城项目需求模板(直选)- 采购需求表
serviceInviteSelect: '120', // 服务商城项目需求模板(邀请比选)- 采购需求表
servicePublicSelect: '120', // 服务商城项目需求模板(公开比选)- 采购需求表
servicePublicSelectAuto: '120', // 服务商城项目需求模板(公开比选-自动)- 采购需求表
purchaseRequirementTemplate: '120', // 采购需求填报模板 - 采购需求表
purchaseRequirement: '120', // 采购需求填报模板 - 采购需求表
serviceInviteSelectSchool: '120', // 服务商城项目需求模板(邀请比选-学校)- 采购需求表
servicePublicSelectSchoolAuto: '120', // 服务商城项目需求模板(公开比选-学校-自动)- 采购需求表
servicePublicSelectSchool: '120', // 服务商城项目需求模板(公开比选-学校)- 采购需求表
};
// 辅助函数:判断当前采购方式是否为指定类型(通过 id 或 value 匹配)
@@ -871,6 +896,17 @@ const isPurchaseType = (purchaseTypeId: string) => {
return dataForm.purchaseType === purchaseTypeId;
};
// 辅助函数:判断当前采购方式是否为"询价"(通过 label 匹配)
const isInquiryPurchaseType = computed(() => {
if (!dataForm.purchaseType) return false;
const item = purchaseTypeDeptList.value.find(item => item.value === dataForm.purchaseType);
if (item) {
const label = item.label || item.dictLabel || item.name || '';
return label.includes('询价');
}
return false;
});
// 辅助函数:判断特殊情况是否为指定类型(通过 id 或 value 匹配)
const isSpecialType = (specialIdOrValue: string) => {
if (!dataForm.isSpecial) return false;
@@ -998,6 +1034,53 @@ const isSpecialServiceCategory = computed(() => {
return Number(category.isMallService) === 1 || Number(category.isProjectService) === 1;
});
// 委托采购中心方式自动判断:
// - 服务类:若末级节点 isMallService=0 且 isMallProject=0则选“其他方式”否则选“服务类网上商城”
// - 非服务类:默认选“其他方式”
const calcEntrustCenterType = (): 'service_online' | 'other' | '' => {
if (!isPurchaseType(PURCHASE_TYPE_IDS.ENTRUST_CENTER)) return '';
if (!dataForm.categoryCode) return '';
const category = getCategoryInfo();
if (!category) return '';
// 兼容字段:接口可能为 isMallProject也可能历史字段为 isProjectService
const mallService = Number(category.isMallService ?? 0);
const mallProject = Number(category.isMallProject ?? category.isProjectService ?? 0);
if (isServiceCategory.value) {
return mallService === 0 && mallProject === 0 ? 'other' : 'service_online';
}
return 'other';
};
// 监听品目/采购方式变化,自动设置委托采购中心方式,并清理无关字段
watch(
[() => dataForm.purchaseType, () => dataForm.categoryCode, () => categoryTreeData.value],
() => {
const nextType = calcEntrustCenterType();
if (!nextType) return;
const prevType = dataForm.entrustCenterType as any;
if (prevType === nextType) return;
dataForm.entrustCenterType = nextType;
// 切换时清理不相关字段,避免脏数据
if (nextType === 'other') {
dataForm.hasSupplier = '';
dataForm.suppliers = '';
dataForm.serviceDirectSelect = '';
dataForm.serviceInviteSelect = '';
dataForm.servicePublicSelect = '';
dataForm.servicePublicSelectAuto = '';
} else if (nextType === 'service_online') {
dataForm.purchaseRequirementTemplate = '';
}
},
{ immediate: true }
);
// 判断是否自动选择网上商城采购方式5万<=金额<40万服务类目特殊服务类目
const isAutoSelectPurchaseType = computed(() => {
if (!dataForm.budget) return false;
@@ -1085,6 +1168,7 @@ const downloadTemplate = async (type: string) => {
const templateMap: Record<string, { fileName: string, displayName: string }> = {
'business_negotiation': { fileName: '商务洽谈表.xlsx', displayName: '商务洽谈表.xlsx' },
'market_purchase_minutes': { fileName: '市场采购纪要.xlsx', displayName: '市场采购纪要.xlsx' },
'inquiry': { fileName: '部门采购询价模版.docx', displayName: '部门采购询价模版.docx' },
'direct_select': { fileName: '服务商城项目需求模板(直选).doc', displayName: '服务商城项目需求模板(直选).doc' },
'public_select': { fileName: '服务商城项目需求模板(公开比选).doc', displayName: '服务商城项目需求模板(公开比选).doc' },
'invite_select': { fileName: '服务商城项目需求模板(邀请比选).doc', displayName: '服务商城项目需求模板(邀请比选).doc' },
@@ -1437,50 +1521,52 @@ const handleSchoolLeaderChange = (value: string) => {
};
// 处理文件ID字符串转数组
// 从上传返回的URL中提取文件ID拼成数组格式["id1", "id2"]
// 从上传返回的URL或ID中提取文件ID拼成数组格式["id1", "id2"]
// 上传接口返回的id会保存在URL的id参数中或者直接是32位十六进制字符串
const getFileIdsArray = (fileIds: string | string[]): string[] => {
if (!fileIds) return [];
if (Array.isArray(fileIds)) return fileIds;
const urls = fileIds.split(',').filter(url => url.trim());
const items = fileIds.split(',').filter(item => item.trim());
const ids: string[] = [];
urls.forEach(url => {
items.forEach(item => {
const trimmed = item.trim();
// 首先检查是否是直接的ID格式32位十六进制字符串
if (/^[a-f0-9]{32}$/i.test(trimmed)) {
ids.push(trimmed);
return;
}
// 如果不是ID格式尝试从URL中提取id参数
try {
// 尝试解析为URL
const urlObj = new URL(url, window.location.origin);
const urlObj = new URL(trimmed, window.location.origin);
// 优先从URL参数中获取id
let id = urlObj.searchParams.get('id');
// 如果没有id参数尝试从路径中提取可能是直接的文件ID
if (!id) {
const pathParts = urlObj.pathname.split('/').filter(p => p);
// 检查最后一个路径段是否是32位十六进制字符串文件ID格式
const lastPart = pathParts[pathParts.length - 1];
if (lastPart && /^[a-f0-9]{32}$/i.test(lastPart)) {
id = lastPart;
} else if (lastPart) {
id = lastPart;
}
}
if (id) {
ids.push(id);
} else {
// 如果URL解析失败检查原始字符串是否是ID格式
if (/^[a-f0-9]{32}$/i.test(url.trim())) {
ids.push(url.trim());
} else {
ids.push(url);
}
// 如果无法提取ID使用原始字符串可能是URL
// 但这种情况不应该发生因为上传接口应该返回id
console.warn('无法从URL中提取文件ID:', trimmed);
ids.push(trimmed);
}
} catch {
// URL解析失败检查是否是直接的ID格式32位十六进制
if (/^[a-f0-9]{32}$/i.test(url.trim())) {
ids.push(url.trim());
// URL解析失败如果原始字符串是ID格式则使用否则忽略
if (/^[a-f0-9]{32}$/i.test(trimmed)) {
ids.push(trimmed);
} else {
// 否则直接使用原始字符串
ids.push(url);
console.warn('无法解析文件标识:', trimmed);
}
}
});
@@ -1504,9 +1590,9 @@ const handleSubmit = async () => {
...dataForm,
};
// 处理所有文件字段
// 处理所有文件字段 - 收集所有文件ID到fileIds数组中
const fileFields = [
'businessNegotiationTable', 'marketPurchaseMinutes', 'onlineMallMaterials',
'businessNegotiationTable', 'marketPurchaseMinutes', 'onlineMallMaterials', 'inquiryTemplate',
'serviceDirectSelect', 'servicePublicSelect', 'purchaseRequirementTemplate',
'serviceInviteSelect', 'servicePublicSelectAuto', 'purchaseRequirement',
'meetingMinutes', 'feasibilityReport', 'meetingMinutesUrgent',
@@ -1515,11 +1601,27 @@ const handleSubmit = async () => {
'servicePublicSelectSchoolAuto', 'otherMaterials'
];
// 收集所有文件ID到一个数组中
const allFileIds: string[] = [];
fileFields.forEach(field => {
if (submitData[field]) {
submitData[field] = getFileIdsArray(submitData[field]);
const ids = getFileIdsArray(submitData[field]);
console.log(`字段 ${field} 的文件ID:`, ids);
// 收集到总数组中
allFileIds.push(...ids);
// 清空原字段,不再单独传递
delete submitData[field];
}
});
// 将所有文件ID统一放到fileIds字段中
if (allFileIds.length > 0) {
submitData.fileIds = allFileIds;
console.log('所有文件ID (fileIds):', allFileIds);
}
console.log('提交数据:', submitData);
await addObj(submitData);
useMessage().success('提交成功');
@@ -1555,8 +1657,9 @@ const handleTempStore = async () => {
...dataForm,
};
// 处理所有文件字段 - 收集所有文件ID到fileIds数组中
const fileFields = [
'businessNegotiationTable', 'marketPurchaseMinutes', 'onlineMallMaterials',
'businessNegotiationTable', 'marketPurchaseMinutes', 'onlineMallMaterials', 'inquiryTemplate',
'serviceDirectSelect', 'servicePublicSelect', 'purchaseRequirementTemplate',
'serviceInviteSelect', 'servicePublicSelectAuto', 'purchaseRequirement',
'meetingMinutes', 'feasibilityReport', 'meetingMinutesUrgent',
@@ -1565,11 +1668,23 @@ const handleTempStore = async () => {
'servicePublicSelectSchoolAuto', 'otherMaterials'
];
// 收集所有文件ID到一个数组中
const allFileIds: string[] = [];
fileFields.forEach(field => {
if (submitData[field]) {
submitData[field] = getFileIdsArray(submitData[field]);
const ids = getFileIdsArray(submitData[field]);
// 收集到总数组中
allFileIds.push(...ids);
// 清空原字段,不再单独传递
delete submitData[field];
}
});
// 将所有文件ID统一放到fileIds字段中
if (allFileIds.length > 0) {
submitData.fileIds = allFileIds;
}
await tempStore(submitData);
useMessage().success('暂存成功');