diff --git a/src/api/professional/professionalfile.ts b/src/api/professional/professionalfile.ts
new file mode 100644
index 0000000..3c5d078
--- /dev/null
+++ b/src/api/professional/professionalfile.ts
@@ -0,0 +1,10 @@
+import request from '/@/utils/request';
+
+
+export const exportTeacherInfoBySelf = (data?: any) => {
+ return request({
+ url: '/professional/file/exportTeacherInfoBySelf',
+ method: 'post',
+ data: data,
+ });
+};
\ No newline at end of file
diff --git a/src/components/Upload/index.vue b/src/components/Upload/index.vue
index d5817ff..4540b95 100644
--- a/src/components/Upload/index.vue
+++ b/src/components/Upload/index.vue
@@ -161,6 +161,7 @@ interface FileItem {
name?: string;
url?: string;
uid?: number;
+ id?: string; // 文件ID
}
interface UploadFileItem {
@@ -170,6 +171,7 @@ interface UploadFileItem {
fileSize: number;
fileName: string;
fileType: string;
+ id?: string; // 文件ID
}
const props = defineProps({
@@ -270,14 +272,74 @@ const handleBeforeUpload = (file: File) => {
// 上传成功回调
function handleUploadSuccess(res: any, file: any) {
if (res.code === 0) {
+ // 调试:打印完整的响应数据
+ console.log('上传成功响应数据:', res);
+ console.log('res.data:', res.data);
+
+ // 获取文件ID - 尝试多种可能的字段名
+ let fileId = res.data?.id || res.data?.fileId || res.data?.file_id || null;
+ let fileUrl = res.data?.url || res.data?.fileUrl || '';
+
+ // 如果fileId不存在,尝试从fileName中提取(fileName可能是ID)
+ if (!fileId && res.data?.fileName) {
+ const fileName = res.data.fileName;
+ // 检查fileName是否是32位十六进制字符串(文件ID格式)
+ if (/^[a-f0-9]{32}$/i.test(fileName)) {
+ fileId = fileName;
+ }
+ }
+
+ // 如果fileId仍然不存在,尝试从URL中提取
+ if (!fileId && fileUrl) {
+ try {
+ const urlObj = new URL(fileUrl, window.location.origin);
+ fileId = urlObj.searchParams.get('id');
+ // 如果URL参数中没有id,检查路径中是否有32位十六进制字符串
+ if (!fileId) {
+ const pathParts = urlObj.pathname.split('/').filter(p => p);
+ const lastPart = pathParts[pathParts.length - 1];
+ if (lastPart && /^[a-f0-9]{32}$/i.test(lastPart)) {
+ fileId = lastPart;
+ }
+ }
+ } catch (e) {
+ // URL解析失败,忽略
+ }
+ }
+
+ // 如果仍然没有fileId,尝试从整个res.data中查找可能的ID字段
+ if (!fileId && res.data) {
+ // 遍历res.data的所有属性,查找32位十六进制字符串
+ for (const key in res.data) {
+ const value = res.data[key];
+ if (typeof value === 'string' && /^[a-f0-9]{32}$/i.test(value)) {
+ fileId = value;
+ break;
+ }
+ }
+ }
+
+ console.log('提取的文件ID:', fileId);
+
+ // 构建URL,如果存在id则添加到URL参数中(用于兼容性)
+ if (fileId && fileUrl) {
+ // 如果URL中已经有参数,使用&,否则使用?
+ fileUrl = fileUrl.includes('?') ? `${fileUrl}&id=${fileId}` : `${fileUrl}?id=${fileId}`;
+ }
+ fileUrl = `${fileUrl}&originalFileName=${file.name}`;
+
uploadList.value.push({
name: file.name,
- url: `${res.data?.url}&originalFileName=${file.name}`,
+ url: fileUrl,
fileUrl: res.data?.fileName,
fileSize: file.size,
fileName: file.name,
fileType: file.raw.type,
+ id: fileId, // 保存文件ID(优先使用)
});
+
+ console.log('保存的文件对象:', uploadList.value[uploadList.value.length - 1]);
+
uploadedSuccessfully();
} else {
number.value--;
@@ -298,10 +360,12 @@ const uploadedSuccessfully = () => {
}
};
-const handleRemove = (file: { name: string }) => {
- fileList.value = fileList.value.filter((f) => f.name !== file.name);
- emit('update:modelValue', listToString(fileList.value));
- emit('change', listToString(fileList.value), fileList.value);
+const handleRemove = (file: { name?: string }) => {
+ if (file.name) {
+ fileList.value = fileList.value.filter((f) => f.name !== file.name);
+ emit('update:modelValue', listToString(fileList.value));
+ emit('change', listToString(fileList.value), fileList.value);
+ }
};
const handlePreview = (file: any) => {
@@ -319,12 +383,21 @@ const handleExceed = () => {
* @param separator 分隔符,默认为逗号。
* @returns {string} 返回转换后的字符串。
*/
+/**
+ * 将对象数组转为字符串,以逗号分隔。
+ * 如果文件有id,优先使用id,否则使用url。
+ * @param list 待转换的对象数组。
+ * @param separator 分隔符,默认为逗号。
+ * @returns {string} 返回转换后的字符串(ID或URL,用逗号分隔)。
+ */
const listToString = (list: FileItem[], separator = ','): string => {
let strs = '';
separator = separator || ',';
for (let i in list) {
- if (list[i].url) {
- strs += list[i].url + separator;
+ // 优先使用id,如果没有id则使用url
+ const value = list[i].id || list[i].url;
+ if (value) {
+ strs += value + separator;
}
}
return strs !== '' ? strs.substr(0, strs.length - 1) : '';
@@ -347,7 +420,28 @@ watch(
// 然后将数组转为对象数组
fileList.value = list.map((item: any) => {
if (typeof item === 'string') {
- item = { name: other.getQueryString(item, 'originalFileName') || other.getQueryString(item, 'fileName'), url: item };
+ // 检查是否是32位十六进制字符串(文件ID格式)
+ const isId = /^[a-f0-9]{32}$/i.test(item.trim());
+ if (isId) {
+ // 如果是ID格式,直接使用
+ item = { id: item.trim(), name: '', url: '' };
+ } else {
+ // 如果是URL,尝试从URL中提取id
+ try {
+ const urlObj = new URL(item, window.location.origin);
+ const id = urlObj.searchParams.get('id');
+ item = {
+ name: other.getQueryString(item, 'originalFileName') || other.getQueryString(item, 'fileName'),
+ url: item,
+ id: id || undefined
+ };
+ } catch {
+ item = {
+ name: other.getQueryString(item, 'originalFileName') || other.getQueryString(item, 'fileName'),
+ url: item
+ };
+ }
+ }
}
item.uid = item.uid || new Date().getTime() + temp++;
return item as FileItem;
diff --git a/src/views/finance/purchasingrequisition/add.vue b/src/views/finance/purchasingrequisition/add.vue
index 02651f1..31d6c3f 100644
--- a/src/views/finance/purchasingrequisition/add.vue
+++ b/src/views/finance/purchasingrequisition/add.vue
@@ -21,12 +21,13 @@
ref="formRef"
:model="dataForm"
:rules="dataRules"
- label-width="140px">
+ label-width="120px"
+ class="compact-form">
-
-
+
+
-
+
-
-
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
+
-
-
+
+
-
-
部门自行采购
-
+
+
部门自行采购
+
-
+
+ class="mb16">
+ class="mb16">
+ class="mb16">
+
+
+
+ 下载《部门采购询价模版》模版
+
+
+
+
-
-
+
+
+
服务类网上商城
其他方式
@@ -222,22 +258,22 @@
-
+
- 有
- 无
+ 有
+ 无
-
+
+ class="mb16">
@@ -248,7 +284,7 @@
+ class="mb16">
+ class="mb16">
+ class="mb16">
-
+
- 有
- 无
+ 有
+ 无
-
+
+ class="mb16">
+ class="mb16">
+ class="mb16">
-
+
-
-
学校统一采购
-
-
-
-
-
-
-
-
-
-
+
+
学校统一采购
+
+
+
+
+
+ {{ item.label }}
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
- 下载《项目可行性论证报告模板.doc》
-
-
-
-
-
-
+
+
+
+
+ 下载《项目可行性论证报告模板.doc》
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
-
-
- 下载《单一来源论专家证附件.docx》
-
-
-
-
-
-
+
+
+
+
+ 下载《单一来源论专家证附件.docx》
+
+
+
+
+
+
+
+
+
+
-
-
- 下载《进口产品申请及专家论证意见表.doc》
-
-
-
-
-
-
+
+
+
+
+ 下载《进口产品申请及专家论证意见表.doc》
+
+
+
+
+
+
+
+
+
+
-
-
- 有
- 无
-
-
-
-
-
-
- 请输入三家供应商名称,用逗号或分号分隔
-
-
+
+
+
+
+ 有
+ 无
+
+
+
+
+
+
+
+
+ 请输入三家供应商名称,用逗号或分号分隔
+
+
+
+
-
+
@@ -668,38 +745,45 @@
-
-
-
-
+
+
+
+
+
+
+
-
-
-
- 支持上传zip格式的压缩包文件
-
+
+
+
+
+ 支持上传zip格式的压缩包文件
+
+
+
-
+
@@ -776,6 +860,7 @@ const dataForm = reactive({
businessNegotiationTable: '',
marketPurchaseMinutes: '',
onlineMallMaterials: '',
+ inquiryTemplate: '', // 询价模板
entrustCenterType: '',
hasSupplier: '',
suppliers: '', // 供应商名称(逗号或分号分隔)
@@ -822,6 +907,10 @@ const businessDeptList = ref([]);
const schoolLeaderList = ref([]);
const loading = ref(false);
+// 预算金额输入相关
+const budgetUnit = ref<'yuan' | 'thousand' | 'wan'>('yuan');
+const budgetInputValue = ref(null);
+
// 采购方式ID常量
const PURCHASE_TYPE_IDS = {
BUSINESS_NEGOTIATION: '77b429c146fc9e12ba4c5573da19ad70', // 商务洽谈
@@ -831,10 +920,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 = {
businessNegotiationTable: '10', // 商务洽谈纪要
marketPurchaseMinutes: '20', // 市场采购纪要
+ inquiryTemplate: '20', // 询价模板(归类到市场采购纪要)
onlineMallMaterials: '30', // 网上商城采购相关材料
feasibilityReport: '40', // 可行性论证报告
meetingMinutes: '50', // 会议记录
@@ -845,16 +935,16 @@ const FILE_TYPE_MAP: Record = {
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 +961,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 +1099,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 +1233,7 @@ const downloadTemplate = async (type: string) => {
const templateMap: Record = {
'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 +1586,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 +1655,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 +1666,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('提交成功');
@@ -1533,7 +1700,11 @@ const handleSubmit = async () => {
router.push('/finance/purchasingrequisition');
}
} catch (err: any) {
- useMessage().error(err.msg || '提交失败');
+ // 全局拦截器已经显示了错误提示,这里不需要再次显示
+ // 只有当错误没有 msg 时才显示默认错误提示
+ if (!err?.msg) {
+ useMessage().error('提交失败');
+ }
} finally {
loading.value = false;
}
@@ -1555,8 +1726,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 +1737,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('暂存成功');
@@ -1583,7 +1767,11 @@ const handleTempStore = async () => {
router.push('/finance/purchasingrequisition');
}
} catch (err: any) {
- useMessage().error(err.msg || '暂存失败');
+ // 全局拦截器已经显示了错误提示,这里不需要再次显示
+ // 只有当错误没有 msg 时才显示默认错误提示
+ if (!err?.msg) {
+ useMessage().error('暂存失败');
+ }
} finally {
loading.value = false;
}
@@ -1603,6 +1791,83 @@ const setCategoryCodePath = () => {
}
};
+// 预算金额单位转换
+const handleBudgetChange = (value: number | null) => {
+ if (value === null || value === undefined) {
+ dataForm.budget = null;
+ return;
+ }
+
+ // 根据单位转换为元
+ switch (budgetUnit.value) {
+ case 'yuan':
+ dataForm.budget = value;
+ break;
+ case 'thousand':
+ dataForm.budget = value * 1000;
+ break;
+ case 'wan':
+ dataForm.budget = value * 10000;
+ break;
+ }
+};
+
+// 预算金额单位变化处理
+const handleBudgetUnitChange = (unit: 'yuan' | 'thousand' | 'wan') => {
+ if (!dataForm.budget) {
+ budgetInputValue.value = null;
+ return;
+ }
+
+ // 根据新单位转换显示值
+ switch (unit) {
+ case 'yuan':
+ budgetInputValue.value = dataForm.budget;
+ break;
+ case 'thousand':
+ budgetInputValue.value = dataForm.budget / 1000;
+ break;
+ case 'wan':
+ budgetInputValue.value = dataForm.budget / 10000;
+ break;
+ }
+};
+
+// 获取预算金额输入框的 placeholder
+const getBudgetPlaceholder = (): string => {
+ switch (budgetUnit.value) {
+ case 'yuan':
+ return '请输入金额';
+ case 'thousand':
+ return '请输入金额(千元)';
+ case 'wan':
+ return '请输入金额(万元)';
+ default:
+ return '请输入金额';
+ }
+};
+
+// 监听 dataForm.budget 变化,同步到 budgetInputValue
+watch(() => dataForm.budget, (newVal) => {
+ if (newVal === null || newVal === undefined) {
+ budgetInputValue.value = null;
+ return;
+ }
+
+ // 根据当前单位转换显示值
+ switch (budgetUnit.value) {
+ case 'yuan':
+ budgetInputValue.value = newVal;
+ break;
+ case 'thousand':
+ budgetInputValue.value = newVal / 1000;
+ break;
+ case 'wan':
+ budgetInputValue.value = newVal / 10000;
+ break;
+ }
+}, { immediate: true });
+
// 监听 categoryTreeData 变化,设置回显路径
watch(() => categoryTreeData.value, () => {
if (dataForm.categoryCode) {
@@ -1665,6 +1930,11 @@ onMounted(async () => {
dataForm.isSpecial = '0';
}
+ // 初始化预算金额显示值
+ if (dataForm.budget) {
+ budgetInputValue.value = dataForm.budget;
+ }
+
// 如果有 categoryCode,设置回显路径
if (dataForm.categoryCode) {
setCategoryCodePath();
@@ -1678,6 +1948,9 @@ onMounted(async () => {
.mb20 {
margin-bottom: 20px;
}
+.mb16 {
+ margin-bottom: 16px;
+}
.mb10 {
margin-bottom: 10px;
}
@@ -1690,18 +1963,94 @@ onMounted(async () => {
color: var(--el-text-color-primary);
padding-bottom: 10px;
border-bottom: 1px solid var(--el-border-color-light);
- margin-bottom: 20px;
+ margin-bottom: 16px;
}
+
+/* 紧凑表单样式 */
+.compact-form {
+ :deep(.el-form-item) {
+ margin-bottom: 16px;
+ }
+
+ :deep(.el-form-item__label) {
+ padding-right: 12px;
+ font-size: 14px;
+ }
+
+ :deep(.el-input__inner),
+ :deep(.el-textarea__inner) {
+ font-size: 14px;
+ }
+}
+
.template-note {
margin-top: 5px;
color: var(--el-text-color-secondary);
+ font-size: 12px;
}
+
+/* 预算金额输入组样式 - 并排显示,无缝连接 */
+.budget-input-group {
+ display: flex;
+ align-items: stretch;
+ width: 100%;
+ transition: all 0.2s ease;
+
+ .budget-input {
+ flex: 1;
+
+ :deep(.el-input__wrapper) {
+ border-radius: 4px 0 0 4px;
+ border-right: none;
+ transition: all 0.2s ease;
+ }
+
+ :deep(.el-input__wrapper:hover) {
+ z-index: 1;
+ border-color: var(--el-color-primary);
+ box-shadow: 0 0 0 1px var(--el-color-primary-light-7);
+ }
+
+ :deep(.el-input__wrapper.is-focus) {
+ z-index: 1;
+ border-color: var(--el-color-primary);
+ box-shadow: 0 0 0 1px var(--el-color-primary-light-7);
+ }
+ }
+
+ .budget-unit-select {
+ width: 100px;
+ flex-shrink: 0;
+ cursor: pointer;
+
+ :deep(.el-input__wrapper) {
+ border-radius: 0 4px 4px 0;
+ border-left: 1px solid var(--el-border-color);
+ margin-left: -1px;
+ transition: all 0.2s ease;
+ }
+
+ :deep(.el-input__wrapper:hover) {
+ z-index: 1;
+ border-color: var(--el-color-primary);
+ box-shadow: 0 0 0 1px var(--el-color-primary-light-7);
+ }
+
+ :deep(.el-input__wrapper.is-focus) {
+ z-index: 1;
+ border-color: var(--el-color-primary);
+ box-shadow: 0 0 0 1px var(--el-color-primary-light-7);
+ }
+ }
+}
+
.form-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
- padding-top: 20px;
+ padding-top: 16px;
border-top: 1px solid var(--el-border-color-light);
+ margin-top: 16px;
}
diff --git a/src/views/finance/purchasingrequisition/form.vue b/src/views/finance/purchasingrequisition/form.vue
index c9937a6..c1fa2b6 100644
--- a/src/views/finance/purchasingrequisition/form.vue
+++ b/src/views/finance/purchasingrequisition/form.vue
@@ -2,12 +2,17 @@
-
-
-
+ draggable
+ :class="{ 'view-dialog': isView }">
+
+
+
@@ -16,41 +21,49 @@
ref="formRef"
:model="dataForm"
:rules="dataRules"
- label-width="140px">
+ :label-width="isView ? '120px' : '140px'"
+ :class="{ 'view-form': isView }">
-
-
-
+
+
+
+
+ {{ dataForm.projectName || '-' }}
+
+ clearable />
-
+
+
+ {{ dataForm.applyDate || '-' }}
+
-
-
-
+
+
+ {{ getDictLabel(fundSourceList, dataForm.fundSource) || '-' }}
+
-
+
+
+
+
+ {{ dataForm.budget ? Number(dataForm.budget).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) : '-' }}
+
-
-
-
+
+
+ 是
+ 否
+ -
+
-
+
+
+ {{ getDictLabel(isSpecialList, dataForm.isSpecial) || '-' }}
+
-
-
+
+
+
+ {{ getCategoryDisplayText() }}
+
@@ -126,27 +153,34 @@
-
+
-
部门自行采购
-
+ 部门自行采购
+
+
+ {{ dataForm.projectContent || '-' }}
+
+ clearable />
-
+
+
+ {{ getDictLabel(purchaseTypeDeptList, dataForm.purchaseType) || '-' }}
+
-
- 下载商务洽谈表模版
-
+ :class="isView ? 'mb16' : 'mb20'">
+
+
+ 下载商务洽谈表模版
+
+
-
- 下载市场采购纪要模版
-
+ :class="isView ? 'mb16' : 'mb20'">
+
+
+ 下载市场采购纪要模版
+
+
+ :class="isView ? 'mb16' : 'mb20'">
-
+
请上传zip格式的压缩包
-
-
+
+
+ {{ dataForm.entrustCenterType === 'service_online' ? '服务类网上商城' : dataForm.entrustCenterType === 'other' ? '其他方式' : '-' }}
+
+
服务类网上商城
其他方式
@@ -281,16 +320,17 @@
v-if="dataForm.entrustCenterType === 'other' && isGoodsCategory"
label="采购需求填报模板"
prop="purchaseRequirementTemplate"
- class="mb20">
-
- 下载《表1:采购需求填报模板》模版
-
+ :class="isView ? 'mb16' : 'mb20'">
+
+
+ 下载《表1:采购需求填报模板》模版
+
+
-
-
+
+
+ {{ dataForm.hasRecommendedSupplier === 'yes' ? '有' : dataForm.hasRecommendedSupplier === 'no' ? '无' : '-' }}
+
+
有
无
@@ -311,29 +354,34 @@
v-if="dataForm.hasRecommendedSupplier === 'yes'"
label="推荐供应商"
prop="recommendedSuppliers"
- class="mb20">
-
-
- 请输入三家供应商名称,用逗号分隔
-
+ :class="isView ? 'mb16' : 'mb20'">
+
+ {{ dataForm.recommendedSuppliers || '-' }}
+
+
+
+
+ 请输入三家供应商名称,用逗号分隔
+
+
-
- 下载《服务商城项目需求模板(邀请比选)》模版
-
+ :class="isView ? 'mb16' : 'mb20'">
+
+
+ 下载《服务商城项目需求模板(邀请比选)》模版
+
+
-
- 下载《服务商城项目需求模板(公开比选)》模版
-
+ :class="isView ? 'mb16' : 'mb20'">
+
+
+ 下载《服务商城项目需求模板(公开比选)》模版
+
+
-
学校统一采购
-
-
-
-
-
-
-
-
-
-
+
学校统一采购
+
+
+
+
+ {{ getDictLabel(purchaseModeSchoolList, dataForm.purchaseMode) || '-' }}
+
+
+
+
+
+
+
+
+
+ {{ getDictLabel(purchaseTypeUnionList, dataForm.purchaseTypeUnion) || '-' }}
+
+
+
+
+
+
+
-
- 下载《项目可行性论证报告模板.doc》
-
+ :class="isView ? 'mb16' : 'mb20'">
+
+
+ 下载《项目可行性论证报告模板.doc》
+
+
-
+
-
- 下载《单一来源论专家证附件.docx》
-
+ :class="isView ? 'mb16' : 'mb20'">
+
+
+ 下载《单一来源论专家证附件.docx》
+
+
-
+
-
- 下载《进口产品申请及专家论证意见表.doc》
-
+ :class="isView ? 'mb16' : 'mb20'">
+
+
+ 下载《进口产品申请及专家论证意见表.doc》
+
+
-
+
-
-
- 下载《采购需求填报模板》模版
-
+
+
+
+ 下载《采购需求填报模板》模版
+
+
-
- 下载《服务商城项目需求模板(公开比选)》模版
-
+ :class="isView ? 'mb16' : 'mb20'">
+
+
+ 下载《服务商城项目需求模板(公开比选)》模版
+
+
+ :class="isView ? 'mb16' : 'mb20'">
-
+
@@ -638,6 +704,19 @@ const dataForm = reactive({
governmentPurchaseIntent: '',
servicePublicSelectSchool: '',
});
+// 定义 props
+const props = defineProps<{
+ dictData?: {
+ fundSourceList?: any[];
+ isCentralizedList?: any[];
+ isSpecialList?: any[];
+ purchaseTypeDeptList?: any[];
+ purchaseModeSchoolList?: any[];
+ purchaseTypeUnionList?: any[];
+ categoryTreeData?: any[];
+ };
+}>();
+
const categoryTreeData = ref([]);
const categoryCodePath = ref([]); // 级联选择器的路径数组
const selectedCategory = ref(null);
@@ -669,6 +748,77 @@ const stepTwoTitle = computed(() => {
return isDeptPurchase.value ? '部门自行采购' : '学校统一采购';
});
+// 获取字典项的label
+const getDictLabel = (list: any[], value: string | number | null | undefined): string => {
+ if (!value || !list || list.length === 0) return '';
+ const item = list.find(item => item.value === value || item.id === value);
+ return item ? (item.label || item.dictLabel || item.name || '') : '';
+};
+
+// 获取品目编码的显示文本(用于查看模式)
+const getCategoryDisplayText = (): string => {
+ if (!dataForm.categoryCode) return '-';
+
+ // 如果 categoryCodePath 已经有值,根据路径查找名称
+ if (categoryCodePath.value && categoryCodePath.value.length > 0) {
+ const names: string[] = [];
+ let currentData = categoryTreeData.value;
+
+ for (const code of categoryCodePath.value) {
+ const found = findCategoryByCode(currentData, code);
+ if (found) {
+ names.push(found.name || found.label || code);
+ currentData = found.children || [];
+ } else {
+ names.push(code);
+ }
+ }
+
+ return names.length > 0 ? names.join(' / ') : dataForm.categoryCode;
+ }
+
+ // 如果 categoryCodePath 没有值,尝试查找路径
+ if (categoryTreeData.value && categoryTreeData.value.length > 0) {
+ const path = findCategoryPath(categoryTreeData.value, dataForm.categoryCode);
+ if (path && path.length > 0) {
+ // 根据路径查找名称
+ const names: string[] = [];
+ let currentData = categoryTreeData.value;
+
+ for (const code of path) {
+ const found = findCategoryByCode(currentData, code);
+ if (found) {
+ names.push(found.name || found.label || code);
+ currentData = found.children || [];
+ } else {
+ names.push(code);
+ }
+ }
+
+ return names.length > 0 ? names.join(' / ') : dataForm.categoryCode;
+ }
+ }
+
+ return dataForm.categoryCode || '-';
+};
+
+// 根据 code 查找分类项
+const findCategoryByCode = (data: any[], code: string): any => {
+ if (!data || !Array.isArray(data) || !code) {
+ return null;
+ }
+ for (const item of data) {
+ if (item && item.code === code) {
+ return item;
+ }
+ if (item && item.children && Array.isArray(item.children) && item.children.length > 0) {
+ const found = findCategoryByCode(item.children, code);
+ if (found) return found;
+ }
+ }
+ return null;
+};
+
// 根据 code 查找完整路径(用于回显)
const findCategoryPath = (data: any[], targetCode: string, path: string[] = []): string[] | null => {
for (const item of data) {
@@ -929,15 +1079,27 @@ const openDialog = async (type: 'add' | 'edit' | 'view', rowData?: any) => {
servicePublicSelectSchool: '',
});
- await Promise.all([
- getCategoryTreeData(),
- getFundSourceDict(),
- getIsCentralizedDict(),
- getIsSpecialDict(),
- getPurchaseTypeDeptDict(),
- getPurchaseModeSchoolDict(),
- getPurchaseTypeUnionDict(),
- ]);
+ // 如果通过 props 传递了字典数据,直接使用;否则调用接口获取
+ if (props.dictData) {
+ fundSourceList.value = props.dictData.fundSourceList || [];
+ isCentralizedList.value = props.dictData.isCentralizedList || [];
+ isSpecialList.value = props.dictData.isSpecialList || [];
+ purchaseTypeDeptList.value = props.dictData.purchaseTypeDeptList || [];
+ purchaseModeSchoolList.value = props.dictData.purchaseModeSchoolList || [];
+ purchaseTypeUnionList.value = props.dictData.purchaseTypeUnionList || [];
+ categoryTreeData.value = props.dictData.categoryTreeData || [];
+ } else {
+ // 如果没有传递字典数据,则调用接口获取(兼容性处理)
+ await Promise.all([
+ getCategoryTreeData(),
+ getFundSourceDict(),
+ getIsCentralizedDict(),
+ getIsSpecialDict(),
+ getPurchaseTypeDeptDict(),
+ getPurchaseModeSchoolDict(),
+ getPurchaseTypeUnionDict(),
+ ]);
+ }
nextTick(() => {
formRef.value?.resetFields();
@@ -946,11 +1108,16 @@ const openDialog = async (type: 'add' | 'edit' | 'view', rowData?: any) => {
getObj(rowData.id)
.then((res) => {
Object.assign(dataForm, res.data || {});
- // 设置品目编码回显路径
+ // 设置品目编码回显路径 - 使用 nextTick 确保 categoryTreeData 已经设置
if (dataForm.categoryCode) {
- setCategoryCodePath();
+ nextTick(() => {
+ setCategoryCodePath();
+ });
}
- if (type === 'edit' || type === 'view') {
+ if (type === 'edit') {
+ currentStep.value = 1;
+ } else if (type === 'view') {
+ // 查看模式下直接显示所有内容,不需要步骤
currentStep.value = 1;
}
})
@@ -1252,11 +1419,20 @@ const handleTempStore = async () => {
};
// 监听 categoryTreeData 变化,设置回显路径
-watch(() => categoryTreeData.value, () => {
- if (dataForm.categoryCode) {
+watch(() => categoryTreeData.value, (newVal) => {
+ if (newVal && newVal.length > 0 && dataForm.categoryCode) {
setCategoryCodePath();
}
-}, { deep: true });
+}, { deep: true, immediate: false });
+
+// 监听 dataForm.categoryCode 变化,设置回显路径(用于查看模式)
+watch(() => dataForm.categoryCode, (newVal) => {
+ if (newVal && categoryTreeData.value && categoryTreeData.value.length > 0) {
+ nextTick(() => {
+ setCategoryCodePath();
+ });
+ }
+});
// 暴露变量
defineExpose({
@@ -1268,6 +1444,9 @@ defineExpose({
.mb20 {
margin-bottom: 20px;
}
+.mb16 {
+ margin-bottom: 16px;
+}
.mb10 {
margin-bottom: 10px;
}
@@ -1285,4 +1464,57 @@ defineExpose({
margin-top: 5px;
color: var(--el-text-color-secondary);
}
+
+/* 查看模式优化样式 */
+:deep(.view-dialog) {
+ .el-dialog__body {
+ max-height: calc(100vh - 150px);
+ overflow-y: auto;
+ overflow-x: hidden;
+ padding: 20px;
+ }
+}
+
+.view-content {
+ max-height: calc(100vh - 200px);
+ overflow-y: auto;
+ overflow-x: hidden;
+}
+
+.view-form {
+ /* 查看模式下使用更紧凑的布局 */
+}
+
+.view-form :deep(.el-form-item) {
+ margin-bottom: 16px;
+}
+
+.view-form :deep(.el-form-item__label) {
+ font-weight: 500;
+ color: #606266;
+}
+
+/* 查看模式文本样式 */
+.view-text {
+ color: #303133;
+ font-size: 14px;
+ line-height: 1.5;
+ word-break: break-word;
+}
+
+.view-textarea {
+ white-space: pre-wrap;
+ min-height: 40px;
+ padding: 8px 0;
+}
+
+/* 查看模式下两列布局优化 */
+.view-form :deep(.el-row) {
+ margin-bottom: 0;
+}
+
+.view-form :deep(.el-col) {
+ margin-bottom: 16px;
+}
+
diff --git a/src/views/finance/purchasingrequisition/index.vue b/src/views/finance/purchasingrequisition/index.vue
index cb35355..d67529e 100644
--- a/src/views/finance/purchasingrequisition/index.vue
+++ b/src/views/finance/purchasingrequisition/index.vue
@@ -102,16 +102,10 @@
-
+
- 编号
-
-
-
-
-
- 采购编号
+ 申请单编号
@@ -120,27 +114,57 @@
采购项目名称
-
+
+
+
+ 填报日期
+
+
+
+
+
+ 需求部门
+
+
+
项目类别
- 货物
- 工程
- 服务
- -
+
+ 货物
+ 工程
+ 服务
+
+ {{ scope.row.categoryName }}
+
+
+ {{ scope.row.categoryCode }}
+
+
-
+
- 预算金额
+ 项目预算
{{ scope.row.budget ? Number(scope.row.budget).toLocaleString() : '-' }}
+
+
+
+ 是否特殊
+
+
+ 是
+ 否
+ -
+
+
@@ -152,10 +176,10 @@
-
-
+
- 状态
+ 审核状态
撤回
@@ -167,13 +191,7 @@
-
-
-
-
- 创建时间
-
-
-
+
@@ -217,7 +235,8 @@
+ />
-
+
-
+