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

This commit is contained in:
guochunsi
2026-02-04 14:00:03 +08:00
6 changed files with 1613 additions and 763 deletions

View File

@@ -0,0 +1,10 @@
import request from '/@/utils/request';
export const exportTeacherInfoBySelf = (data?: any) => {
return request({
url: '/professional/file/exportTeacherInfoBySelf',
method: 'post',
data: data,
});
};

View File

@@ -161,6 +161,7 @@ interface FileItem {
name?: string; name?: string;
url?: string; url?: string;
uid?: number; uid?: number;
id?: string; // 文件ID
} }
interface UploadFileItem { interface UploadFileItem {
@@ -170,6 +171,7 @@ interface UploadFileItem {
fileSize: number; fileSize: number;
fileName: string; fileName: string;
fileType: string; fileType: string;
id?: string; // 文件ID
} }
const props = defineProps({ const props = defineProps({
@@ -270,14 +272,74 @@ const handleBeforeUpload = (file: File) => {
// 上传成功回调 // 上传成功回调
function handleUploadSuccess(res: any, file: any) { function handleUploadSuccess(res: any, file: any) {
if (res.code === 0) { 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({ uploadList.value.push({
name: file.name, name: file.name,
url: `${res.data?.url}&originalFileName=${file.name}`, url: fileUrl,
fileUrl: res.data?.fileName, fileUrl: res.data?.fileName,
fileSize: file.size, fileSize: file.size,
fileName: file.name, fileName: file.name,
fileType: file.raw.type, fileType: file.raw.type,
id: fileId, // 保存文件ID优先使用
}); });
console.log('保存的文件对象:', uploadList.value[uploadList.value.length - 1]);
uploadedSuccessfully(); uploadedSuccessfully();
} else { } else {
number.value--; number.value--;
@@ -298,10 +360,12 @@ const uploadedSuccessfully = () => {
} }
}; };
const handleRemove = (file: { name: string }) => { const handleRemove = (file: { name?: string }) => {
fileList.value = fileList.value.filter((f) => f.name !== file.name); if (file.name) {
emit('update:modelValue', listToString(fileList.value)); fileList.value = fileList.value.filter((f) => f.name !== file.name);
emit('change', listToString(fileList.value), fileList.value); emit('update:modelValue', listToString(fileList.value));
emit('change', listToString(fileList.value), fileList.value);
}
}; };
const handlePreview = (file: any) => { const handlePreview = (file: any) => {
@@ -319,12 +383,21 @@ const handleExceed = () => {
* @param separator 分隔符,默认为逗号。 * @param separator 分隔符,默认为逗号。
* @returns {string} 返回转换后的字符串。 * @returns {string} 返回转换后的字符串。
*/ */
/**
* 将对象数组转为字符串,以逗号分隔。
* 如果文件有id优先使用id否则使用url。
* @param list 待转换的对象数组。
* @param separator 分隔符,默认为逗号。
* @returns {string} 返回转换后的字符串ID或URL用逗号分隔
*/
const listToString = (list: FileItem[], separator = ','): string => { const listToString = (list: FileItem[], separator = ','): string => {
let strs = ''; let strs = '';
separator = separator || ','; separator = separator || ',';
for (let i in list) { for (let i in list) {
if (list[i].url) { // 优先使用id如果没有id则使用url
strs += list[i].url + separator; const value = list[i].id || list[i].url;
if (value) {
strs += value + separator;
} }
} }
return strs !== '' ? strs.substr(0, strs.length - 1) : ''; return strs !== '' ? strs.substr(0, strs.length - 1) : '';
@@ -347,7 +420,28 @@ watch(
// 然后将数组转为对象数组 // 然后将数组转为对象数组
fileList.value = list.map((item: any) => { fileList.value = list.map((item: any) => {
if (typeof item === 'string') { 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++; item.uid = item.uid || new Date().getTime() + temp++;
return item as FileItem; return item as FileItem;

File diff suppressed because it is too large Load Diff

View File

@@ -2,12 +2,17 @@
<el-dialog <el-dialog
:title="dialogTitle" :title="dialogTitle"
v-model="visible" v-model="visible"
width="1200px" :width="isView ? '1400px' : '1200px'"
:close-on-click-modal="false" :close-on-click-modal="false"
draggable> draggable
<div v-loading="loading"> :class="{ 'view-dialog': isView }">
<!-- 步骤条 --> <div v-loading="loading" :class="{ 'view-content': isView }">
<el-steps :active="currentStep" finish-status="success" style="margin-bottom: 30px;"> <!-- 步骤条查看模式下隐藏 -->
<el-steps
v-if="!isView"
:active="currentStep"
finish-status="success"
style="margin-bottom: 30px;">
<el-step title="基本信息" /> <el-step title="基本信息" />
<el-step :title="stepTwoTitle" /> <el-step :title="stepTwoTitle" />
</el-steps> </el-steps>
@@ -16,41 +21,49 @@
ref="formRef" ref="formRef"
:model="dataForm" :model="dataForm"
:rules="dataRules" :rules="dataRules"
label-width="140px"> :label-width="isView ? '120px' : '140px'"
:class="{ 'view-form': isView }">
<!-- 第一步基本信息 --> <!-- 第一步基本信息 -->
<div v-show="currentStep === 0"> <div v-show="currentStep === 0 || isView">
<el-row :gutter="24"> <el-row :gutter="isView ? 16 : 24">
<el-col :span="12" class="mb20"> <el-col :span="isView ? 8 : 12" :class="isView ? 'mb16' : 'mb20'">
<el-form-item label="采购项目名称" prop="projectName"> <el-form-item label="采购项目名称" prop="projectName">
<template v-if="isView">
<span class="view-text">{{ dataForm.projectName || '-' }}</span>
</template>
<el-input <el-input
v-else
v-model="dataForm.projectName" v-model="dataForm.projectName"
placeholder="请输入采购项目名称" placeholder="请输入采购项目名称"
clearable clearable />
:disabled="isView" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" class="mb20"> <el-col :span="isView ? 8 : 12" :class="isView ? 'mb16' : 'mb20'">
<el-form-item label="填报日期" prop="applyDate"> <el-form-item label="填报日期" prop="applyDate">
<template v-if="isView">
<span class="view-text">{{ dataForm.applyDate || '-' }}</span>
</template>
<el-date-picker <el-date-picker
v-else
v-model="dataForm.applyDate" v-model="dataForm.applyDate"
type="date" type="date"
placeholder="请选择填报日期" placeholder="请选择填报日期"
format="YYYY-MM-DD" format="YYYY-MM-DD"
value-format="YYYY-MM-DD" value-format="YYYY-MM-DD"
:disabled="isView"
style="width: 100%" /> style="width: 100%" />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> <el-col :span="isView ? 8 : 12" :class="isView ? 'mb16' : 'mb20'">
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item label="资金来源" prop="fundSource"> <el-form-item label="资金来源" prop="fundSource">
<template v-if="isView">
<span class="view-text">{{ getDictLabel(fundSourceList, dataForm.fundSource) || '-' }}</span>
</template>
<el-select <el-select
v-else
v-model="dataForm.fundSource" v-model="dataForm.fundSource"
placeholder="请选择资金来源" placeholder="请选择资金来源"
clearable clearable
:disabled="isView"
style="width: 100%"> style="width: 100%">
<el-option <el-option
v-for="item in fundSourceList" v-for="item in fundSourceList"
@@ -60,26 +73,34 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" class="mb20"> </el-row>
<el-row :gutter="isView ? 16 : 24">
<el-col :span="isView ? 8 : 12" :class="isView ? 'mb16' : 'mb20'">
<el-form-item label="预算金额(元)" prop="budget"> <el-form-item label="预算金额(元)" prop="budget">
<template v-if="isView">
<span class="view-text">{{ dataForm.budget ? Number(dataForm.budget).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) : '-' }}</span>
</template>
<el-input-number <el-input-number
v-else
v-model="dataForm.budget" v-model="dataForm.budget"
:min="0.01" :min="0.01"
:precision="2" :precision="2"
placeholder="请输入预算金额" placeholder="请输入预算金额"
:disabled="isView"
style="width: 100%" /> style="width: 100%" />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> <el-col :span="isView ? 8 : 12" :class="isView ? 'mb16' : 'mb20'">
<el-row :gutter="24">
<el-col :span="12" class="mb20">
<el-form-item label="是否集采" prop="isCentralized"> <el-form-item label="是否集采" prop="isCentralized">
<template v-if="isView">
<el-tag v-if="dataForm.isCentralized === '1'" type="success"></el-tag>
<el-tag v-else-if="dataForm.isCentralized === '0'" type="info"></el-tag>
<span v-else class="view-text">-</span>
</template>
<el-select <el-select
v-else
v-model="dataForm.isCentralized" v-model="dataForm.isCentralized"
placeholder="请选择是否集采" placeholder="请选择是否集采"
clearable clearable
:disabled="isView"
style="width: 100%"> style="width: 100%">
<el-option <el-option
v-for="item in isCentralizedList" v-for="item in isCentralizedList"
@@ -89,13 +110,16 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12" class="mb20"> <el-col :span="isView ? 8 : 12" :class="isView ? 'mb16' : 'mb20'">
<el-form-item label="是否特殊情况" prop="isSpecial"> <el-form-item label="是否特殊情况" prop="isSpecial">
<template v-if="isView">
<span class="view-text">{{ getDictLabel(isSpecialList, dataForm.isSpecial) || '-' }}</span>
</template>
<el-select <el-select
v-else
v-model="dataForm.isSpecial" v-model="dataForm.isSpecial"
placeholder="请选择是否特殊情况" placeholder="请选择是否特殊情况"
clearable clearable
:disabled="isView"
style="width: 100%"> style="width: 100%">
<el-option <el-option
v-for="item in isSpecialList" v-for="item in isSpecialList"
@@ -106,10 +130,14 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<el-row :gutter="24"> <el-row :gutter="isView ? 16 : 24">
<el-col :span="24" class="mb20"> <el-col :span="24" :class="isView ? 'mb16' : 'mb20'">
<el-form-item label="品目编码" prop="categoryCode"> <el-form-item label="品目编码" prop="categoryCode">
<template v-if="isView">
<span class="view-text">{{ getCategoryDisplayText() }}</span>
</template>
<el-cascader <el-cascader
v-else
v-model="categoryCodePath" v-model="categoryCodePath"
:options="categoryTreeData" :options="categoryTreeData"
:props="{ value: 'code', label: 'name', children: 'children', checkStrictly: true }" :props="{ value: 'code', label: 'name', children: 'children', checkStrictly: true }"
@@ -117,7 +145,6 @@
clearable clearable
filterable filterable
:show-all-levels="true" :show-all-levels="true"
:disabled="isView"
style="width: 100%" style="width: 100%"
@change="handleCategoryChange" /> @change="handleCategoryChange" />
</el-form-item> </el-form-item>
@@ -126,27 +153,34 @@
</div> </div>
<!-- 第二步采购详情 --> <!-- 第二步采购详情 -->
<div v-show="currentStep === 1"> <div v-show="currentStep === 1 || isView">
<!-- 分支一部门自行采购 --> <!-- 分支一部门自行采购 -->
<div v-if="isDeptPurchase"> <div v-if="isDeptPurchase">
<div class="step-title mb20">部门自行采购</div> <div :class="isView ? 'step-title mb16' : 'step-title mb20'">部门自行采购</div>
<el-form-item label="采购内容" prop="projectContent" class="mb20"> <el-form-item label="采购内容" prop="projectContent" :class="isView ? 'mb16' : 'mb20'">
<template v-if="isView">
<div class="view-text view-textarea">{{ dataForm.projectContent || '-' }}</div>
</template>
<el-input <el-input
v-else
v-model="dataForm.projectContent" v-model="dataForm.projectContent"
type="textarea" type="textarea"
:rows="4" :rows="4"
:maxlength="1000" :maxlength="1000"
show-word-limit show-word-limit
placeholder="请输入采购内容限制1000字" placeholder="请输入采购内容限制1000字"
clearable clearable />
:disabled="isView" />
</el-form-item> </el-form-item>
<el-form-item label="采购方式" prop="purchaseType" class="mb20"> <el-form-item label="采购方式" prop="purchaseType" :class="isView ? 'mb16' : 'mb20'">
<template v-if="isView">
<span class="view-text">{{ getDictLabel(purchaseTypeDeptList, dataForm.purchaseType) || '-' }}</span>
</template>
<el-select <el-select
v-else
v-model="dataForm.purchaseType" v-model="dataForm.purchaseType"
placeholder="请选择采购方式" placeholder="请选择采购方式"
clearable clearable
:disabled="isView || isAutoSelectPurchaseType" :disabled="isAutoSelectPurchaseType"
style="width: 100%"> style="width: 100%">
<el-option <el-option
v-for="item in purchaseTypeDeptList" v-for="item in purchaseTypeDeptList"
@@ -161,16 +195,17 @@
v-if="dataForm.purchaseType === 'business_negotiation'" v-if="dataForm.purchaseType === 'business_negotiation'"
label="商务洽谈表" label="商务洽谈表"
prop="businessNegotiationTable" prop="businessNegotiationTable"
class="mb20"> :class="isView ? 'mb16' : 'mb20'">
<el-button <template v-if="!isView">
type="primary" <el-button
link type="primary"
icon="Download" link
@click="downloadTemplate('business_negotiation')" icon="Download"
:disabled="isView" @click="downloadTemplate('business_negotiation')"
class="mb10"> class="mb10">
下载商务洽谈表模版 下载商务洽谈表模版
</el-button> </el-button>
</template>
<upload-file <upload-file
v-model="dataForm.businessNegotiationTable" v-model="dataForm.businessNegotiationTable"
:limit="5" :limit="5"
@@ -183,16 +218,17 @@
v-if="dataForm.purchaseType === 'market_purchase'" v-if="dataForm.purchaseType === 'market_purchase'"
label="市场采购纪要" label="市场采购纪要"
prop="marketPurchaseMinutes" prop="marketPurchaseMinutes"
class="mb20"> :class="isView ? 'mb16' : 'mb20'">
<el-button <template v-if="!isView">
type="primary" <el-button
link type="primary"
icon="Download" link
@click="downloadTemplate('market_purchase_minutes')" icon="Download"
:disabled="isView" @click="downloadTemplate('market_purchase_minutes')"
class="mb10"> class="mb10">
下载市场采购纪要模版 下载市场采购纪要模版
</el-button> </el-button>
</template>
<upload-file <upload-file
v-model="dataForm.marketPurchaseMinutes" v-model="dataForm.marketPurchaseMinutes"
:limit="5" :limit="5"
@@ -205,22 +241,25 @@
v-if="dataForm.purchaseType === 'online_mall'" v-if="dataForm.purchaseType === 'online_mall'"
label="网上商城采购相关材料" label="网上商城采购相关材料"
prop="onlineMallMaterials" prop="onlineMallMaterials"
class="mb20"> :class="isView ? 'mb16' : 'mb20'">
<upload-file <upload-file
v-model="dataForm.onlineMallMaterials" v-model="dataForm.onlineMallMaterials"
:limit="1" :limit="1"
accept=".zip" accept=".zip"
:disabled="isView" :disabled="isView"
upload-file-url="/purchase/purchasingfiles/upload" /> upload-file-url="/purchase/purchasingfiles/upload" />
<div class="template-note mt5"> <div v-if="!isView" class="template-note mt5">
<el-text type="info" size="small">请上传zip格式的压缩包</el-text> <el-text type="info" size="small">请上传zip格式的压缩包</el-text>
</div> </div>
</el-form-item> </el-form-item>
<!-- 委托采购中心 --> <!-- 委托采购中心 -->
<template v-if="dataForm.purchaseType === 'entrust_center'"> <template v-if="dataForm.purchaseType === 'entrust_center'">
<el-form-item label="委托采购中心方式" prop="entrustCenterType" class="mb20"> <el-form-item label="委托采购中心方式" prop="entrustCenterType" :class="isView ? 'mb16' : 'mb20'">
<el-radio-group v-model="dataForm.entrustCenterType" :disabled="isView"> <template v-if="isView">
<span class="view-text">{{ dataForm.entrustCenterType === 'service_online' ? '服务类网上商城' : dataForm.entrustCenterType === 'other' ? '其他方式' : '-' }}</span>
</template>
<el-radio-group v-else v-model="dataForm.entrustCenterType">
<el-radio label="service_online">服务类网上商城</el-radio> <el-radio label="service_online">服务类网上商城</el-radio>
<el-radio label="other">其他方式</el-radio> <el-radio label="other">其他方式</el-radio>
</el-radio-group> </el-radio-group>
@@ -281,16 +320,17 @@
v-if="dataForm.entrustCenterType === 'other' && isGoodsCategory" v-if="dataForm.entrustCenterType === 'other' && isGoodsCategory"
label="采购需求填报模板" label="采购需求填报模板"
prop="purchaseRequirementTemplate" prop="purchaseRequirementTemplate"
class="mb20"> :class="isView ? 'mb16' : 'mb20'">
<el-button <template v-if="!isView">
type="primary" <el-button
link type="primary"
icon="Download" link
@click="downloadTemplate('purchase_requirement')" icon="Download"
:disabled="isView" @click="downloadTemplate('purchase_requirement')"
class="mb10"> class="mb10">
下载表1采购需求填报模板模版 下载表1采购需求填报模板模版
</el-button> </el-button>
</template>
<upload-file <upload-file
v-model="dataForm.purchaseRequirementTemplate" v-model="dataForm.purchaseRequirementTemplate"
:limit="5" :limit="5"
@@ -301,8 +341,11 @@
<!-- 特殊规则5<=金额<40万服务类目自动使用邀请比选模版 --> <!-- 特殊规则5<=金额<40万服务类目自动使用邀请比选模版 -->
<template v-if="showAutoInviteSelect"> <template v-if="showAutoInviteSelect">
<el-form-item label="是否有推荐供应商" prop="hasRecommendedSupplier" class="mb20"> <el-form-item label="是否有推荐供应商" prop="hasRecommendedSupplier" :class="isView ? 'mb16' : 'mb20'">
<el-radio-group v-model="dataForm.hasRecommendedSupplier" :disabled="isView"> <template v-if="isView">
<span class="view-text">{{ dataForm.hasRecommendedSupplier === 'yes' ? '有' : dataForm.hasRecommendedSupplier === 'no' ? '无' : '-' }}</span>
</template>
<el-radio-group v-else v-model="dataForm.hasRecommendedSupplier">
<el-radio label="yes"></el-radio> <el-radio label="yes"></el-radio>
<el-radio label="no"></el-radio> <el-radio label="no"></el-radio>
</el-radio-group> </el-radio-group>
@@ -311,29 +354,34 @@
v-if="dataForm.hasRecommendedSupplier === 'yes'" v-if="dataForm.hasRecommendedSupplier === 'yes'"
label="推荐供应商" label="推荐供应商"
prop="recommendedSuppliers" prop="recommendedSuppliers"
class="mb20"> :class="isView ? 'mb16' : 'mb20'">
<el-input <template v-if="isView">
v-model="dataForm.recommendedSuppliers" <span class="view-text">{{ dataForm.recommendedSuppliers || '-' }}</span>
placeholder="请输入三家供应商名称,用逗号分隔" </template>
clearable <template v-else>
:disabled="isView" /> <el-input
<div class="template-note mt5"> v-model="dataForm.recommendedSuppliers"
<el-text type="info" size="small">请输入三家供应商名称用逗号分隔</el-text> placeholder="请输入三家供应商名称,用逗号分隔"
</div> clearable />
<div class="template-note mt5">
<el-text type="info" size="small">请输入三家供应商名称用逗号分隔</el-text>
</div>
</template>
</el-form-item> </el-form-item>
<el-form-item <el-form-item
label="服务商城项目需求模板(邀请比选)" label="服务商城项目需求模板(邀请比选)"
prop="serviceInviteSelect" prop="serviceInviteSelect"
class="mb20"> :class="isView ? 'mb16' : 'mb20'">
<el-button <template v-if="!isView">
type="primary" <el-button
link type="primary"
icon="Download" link
@click="downloadTemplate('invite_select')" icon="Download"
:disabled="isView" @click="downloadTemplate('invite_select')"
class="mb10"> class="mb10">
下载服务商城项目需求模板邀请比选模版 下载服务商城项目需求模板邀请比选模版
</el-button> </el-button>
</template>
<upload-file <upload-file
v-model="dataForm.serviceInviteSelect" v-model="dataForm.serviceInviteSelect"
:limit="5" :limit="5"
@@ -344,16 +392,17 @@
v-if="dataForm.hasRecommendedSupplier === 'no'" v-if="dataForm.hasRecommendedSupplier === 'no'"
label="服务商城项目需求模板(公开比选)" label="服务商城项目需求模板(公开比选)"
prop="servicePublicSelectAuto" prop="servicePublicSelectAuto"
class="mb20"> :class="isView ? 'mb16' : 'mb20'">
<el-button <template v-if="!isView">
type="primary" <el-button
link type="primary"
icon="Download" link
@click="downloadTemplate('public_select')" icon="Download"
:disabled="isView" @click="downloadTemplate('public_select')"
class="mb10"> class="mb10">
下载服务商城项目需求模板公开比选模版 下载服务商城项目需求模板公开比选模版
</el-button> </el-button>
</template>
<upload-file <upload-file
v-model="dataForm.servicePublicSelectAuto" v-model="dataForm.servicePublicSelectAuto"
:limit="5" :limit="5"
@@ -365,58 +414,71 @@
<!-- 分支二学校统一采购 --> <!-- 分支二学校统一采购 -->
<div v-else> <div v-else>
<div class="step-title mb20">学校统一采购</div> <div :class="isView ? 'step-title mb16' : 'step-title mb20'">学校统一采购</div>
<el-form-item label="采购形式" prop="purchaseMode" class="mb20"> <el-row :gutter="isView ? 16 : 24">
<el-select <el-col :span="isView ? 12 : 24" :class="isView ? 'mb16' : 'mb20'">
v-model="dataForm.purchaseMode" <el-form-item label="采购形式" prop="purchaseMode">
placeholder="请选择采购形式" <template v-if="isView">
clearable <span class="view-text">{{ getDictLabel(purchaseModeSchoolList, dataForm.purchaseMode) || '-' }}</span>
:disabled="isView" </template>
style="width: 100%"> <el-select
<el-option v-else
v-for="item in purchaseModeSchoolList" v-model="dataForm.purchaseMode"
:key="item.value" placeholder="请选择采购形式"
:label="item.label" clearable
:value="item.value" /> style="width: 100%">
</el-select> <el-option
</el-form-item> v-for="item in purchaseModeSchoolList"
<el-form-item label="采购方式" prop="purchaseTypeUnion" class="mb20"> :key="item.value"
<el-select :label="item.label"
v-model="dataForm.purchaseTypeUnion" :value="item.value" />
placeholder="请选择采购方式" </el-select>
clearable </el-form-item>
:disabled="isView" </el-col>
style="width: 100%"> <el-col :span="isView ? 12 : 24" :class="isView ? 'mb16' : 'mb20'">
<el-option <el-form-item label="采购方式" prop="purchaseTypeUnion">
v-for="item in purchaseTypeUnionList" <template v-if="isView">
:key="item.value" <span class="view-text">{{ getDictLabel(purchaseTypeUnionList, dataForm.purchaseTypeUnion) || '-' }}</span>
:label="item.label" </template>
:value="item.value" /> <el-select
</el-select> v-else
</el-form-item> v-model="dataForm.purchaseTypeUnion"
placeholder="请选择采购方式"
clearable
style="width: 100%">
<el-option
v-for="item in purchaseTypeUnionList"
:key="item.value"
:label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<!-- 金额>=30显示可行性论证报告和会议纪要 --> <!-- 金额>=30显示可行性论证报告和会议纪要 -->
<template v-if="dataForm.budget && dataForm.budget >= 300000"> <template v-if="dataForm.budget && dataForm.budget >= 300000">
<el-form-item <el-form-item
label="项目可行性论证报告" label="项目可行性论证报告"
prop="feasibilityReport" prop="feasibilityReport"
class="mb20"> :class="isView ? 'mb16' : 'mb20'">
<el-button <template v-if="!isView">
type="primary" <el-button
link type="primary"
icon="Download" link
@click="downloadTemplate('feasibility_report')" icon="Download"
:disabled="isView" @click="downloadTemplate('feasibility_report')"
class="mb10"> class="mb10">
下载项目可行性论证报告模板.doc 下载项目可行性论证报告模板.doc
</el-button> </el-button>
</template>
<upload-file <upload-file
v-model="dataForm.feasibilityReport" v-model="dataForm.feasibilityReport"
:limit="5" :limit="5"
:disabled="isView" :disabled="isView"
upload-file-url="/purchase/purchasingfiles/upload" /> upload-file-url="/purchase/purchasingfiles/upload" />
</el-form-item> </el-form-item>
<el-form-item label="会议纪要" prop="meetingMinutes" class="mb20"> <el-form-item label="会议纪要" prop="meetingMinutes" :class="isView ? 'mb16' : 'mb20'">
<upload-file <upload-file
v-model="dataForm.meetingMinutes" v-model="dataForm.meetingMinutes"
:limit="5" :limit="5"
@@ -430,23 +492,24 @@
<el-form-item <el-form-item
label="单一来源论专家证附件" label="单一来源论专家证附件"
prop="singleSourceProof" prop="singleSourceProof"
class="mb20"> :class="isView ? 'mb16' : 'mb20'">
<el-button <template v-if="!isView">
type="primary" <el-button
link type="primary"
icon="Download" link
@click="downloadTemplate('single_source')" icon="Download"
:disabled="isView" @click="downloadTemplate('single_source')"
class="mb10"> class="mb10">
下载单一来源论专家证附件.docx 下载单一来源论专家证附件.docx
</el-button> </el-button>
</template>
<upload-file <upload-file
v-model="dataForm.singleSourceProof" v-model="dataForm.singleSourceProof"
:limit="5" :limit="5"
:disabled="isView" :disabled="isView"
upload-file-url="/purchase/purchasingfiles/upload" /> upload-file-url="/purchase/purchasingfiles/upload" />
</el-form-item> </el-form-item>
<el-form-item label="会议纪要" prop="meetingMinutesSingle" class="mb20"> <el-form-item label="会议纪要" prop="meetingMinutesSingle" :class="isView ? 'mb16' : 'mb20'">
<upload-file <upload-file
v-model="dataForm.meetingMinutesSingle" v-model="dataForm.meetingMinutesSingle"
:limit="5" :limit="5"
@@ -460,23 +523,24 @@
<el-form-item <el-form-item
label="进口产品申请及专家论证意见表" label="进口产品申请及专家论证意见表"
prop="importApplication" prop="importApplication"
class="mb20"> :class="isView ? 'mb16' : 'mb20'">
<el-button <template v-if="!isView">
type="primary" <el-button
link type="primary"
icon="Download" link
@click="downloadTemplate('import_application')" icon="Download"
:disabled="isView" @click="downloadTemplate('import_application')"
class="mb10"> class="mb10">
下载进口产品申请及专家论证意见表.doc 下载进口产品申请及专家论证意见表.doc
</el-button> </el-button>
</template>
<upload-file <upload-file
v-model="dataForm.importApplication" v-model="dataForm.importApplication"
:limit="5" :limit="5"
:disabled="isView" :disabled="isView"
upload-file-url="/purchase/purchasingfiles/upload" /> upload-file-url="/purchase/purchasingfiles/upload" />
</el-form-item> </el-form-item>
<el-form-item label="会议纪要" prop="meetingMinutesImport" class="mb20"> <el-form-item label="会议纪要" prop="meetingMinutesImport" :class="isView ? 'mb16' : 'mb20'">
<upload-file <upload-file
v-model="dataForm.meetingMinutesImport" v-model="dataForm.meetingMinutesImport"
:limit="5" :limit="5"
@@ -486,16 +550,17 @@
</template> </template>
<!-- 需求文件默认 --> <!-- 需求文件默认 -->
<el-form-item label="需求文件" prop="purchaseRequirement" class="mb20"> <el-form-item label="需求文件" prop="purchaseRequirement" :class="isView ? 'mb16' : 'mb20'">
<el-button <template v-if="!isView">
type="primary" <el-button
link type="primary"
icon="Download" link
@click="downloadTemplate('purchase_requirement')" icon="Download"
:disabled="isView" @click="downloadTemplate('purchase_requirement')"
class="mb10"> class="mb10">
下载采购需求填报模板模版 下载采购需求填报模板模版
</el-button> </el-button>
</template>
<upload-file <upload-file
v-model="dataForm.purchaseRequirement" v-model="dataForm.purchaseRequirement"
:limit="5" :limit="5"
@@ -508,16 +573,17 @@
v-if="showAutoPublicSelect" v-if="showAutoPublicSelect"
label="服务商城项目需求模板(公开比选)" label="服务商城项目需求模板(公开比选)"
prop="servicePublicSelectSchool" prop="servicePublicSelectSchool"
class="mb20"> :class="isView ? 'mb16' : 'mb20'">
<el-button <template v-if="!isView">
type="primary" <el-button
link type="primary"
icon="Download" link
@click="downloadTemplate('public_select')" icon="Download"
:disabled="isView" @click="downloadTemplate('public_select')"
class="mb10"> class="mb10">
下载服务商城项目需求模板公开比选模版 下载服务商城项目需求模板公开比选模版
</el-button> </el-button>
</template>
<upload-file <upload-file
v-model="dataForm.servicePublicSelectSchool" v-model="dataForm.servicePublicSelectSchool"
:limit="5" :limit="5"
@@ -530,7 +596,7 @@
v-if="dataForm.budget && dataForm.budget >= 1000000" v-if="dataForm.budget && dataForm.budget >= 1000000"
label="政府采购意向申请表" label="政府采购意向申请表"
prop="governmentPurchaseIntent" prop="governmentPurchaseIntent"
class="mb20"> :class="isView ? 'mb16' : 'mb20'">
<upload-file <upload-file
v-model="dataForm.governmentPurchaseIntent" v-model="dataForm.governmentPurchaseIntent"
:limit="5" :limit="5"
@@ -540,11 +606,11 @@
</div> </div>
</div> </div>
<el-form-item label="备注" prop="remark" v-if="currentStep === 1"> <el-form-item label="备注" prop="remark" v-if="currentStep === 1 || isView">
<el-input <el-input
v-model="dataForm.remark" v-model="dataForm.remark"
type="textarea" type="textarea"
:rows="3" :rows="isView ? 2 : 3"
placeholder="请输入备注" placeholder="请输入备注"
clearable clearable
:disabled="isView" /> :disabled="isView" />
@@ -638,6 +704,19 @@ const dataForm = reactive({
governmentPurchaseIntent: '', governmentPurchaseIntent: '',
servicePublicSelectSchool: '', servicePublicSelectSchool: '',
}); });
// 定义 props
const props = defineProps<{
dictData?: {
fundSourceList?: any[];
isCentralizedList?: any[];
isSpecialList?: any[];
purchaseTypeDeptList?: any[];
purchaseModeSchoolList?: any[];
purchaseTypeUnionList?: any[];
categoryTreeData?: any[];
};
}>();
const categoryTreeData = ref<any[]>([]); const categoryTreeData = ref<any[]>([]);
const categoryCodePath = ref<string[]>([]); // 级联选择器的路径数组 const categoryCodePath = ref<string[]>([]); // 级联选择器的路径数组
const selectedCategory = ref<any>(null); const selectedCategory = ref<any>(null);
@@ -669,6 +748,77 @@ const stepTwoTitle = computed(() => {
return isDeptPurchase.value ? '部门自行采购' : '学校统一采购'; 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 查找完整路径(用于回显) // 根据 code 查找完整路径(用于回显)
const findCategoryPath = (data: any[], targetCode: string, path: string[] = []): string[] | null => { const findCategoryPath = (data: any[], targetCode: string, path: string[] = []): string[] | null => {
for (const item of data) { for (const item of data) {
@@ -929,15 +1079,27 @@ const openDialog = async (type: 'add' | 'edit' | 'view', rowData?: any) => {
servicePublicSelectSchool: '', servicePublicSelectSchool: '',
}); });
await Promise.all([ // 如果通过 props 传递了字典数据,直接使用;否则调用接口获取
getCategoryTreeData(), if (props.dictData) {
getFundSourceDict(), fundSourceList.value = props.dictData.fundSourceList || [];
getIsCentralizedDict(), isCentralizedList.value = props.dictData.isCentralizedList || [];
getIsSpecialDict(), isSpecialList.value = props.dictData.isSpecialList || [];
getPurchaseTypeDeptDict(), purchaseTypeDeptList.value = props.dictData.purchaseTypeDeptList || [];
getPurchaseModeSchoolDict(), purchaseModeSchoolList.value = props.dictData.purchaseModeSchoolList || [];
getPurchaseTypeUnionDict(), purchaseTypeUnionList.value = props.dictData.purchaseTypeUnionList || [];
]); categoryTreeData.value = props.dictData.categoryTreeData || [];
} else {
// 如果没有传递字典数据,则调用接口获取(兼容性处理)
await Promise.all([
getCategoryTreeData(),
getFundSourceDict(),
getIsCentralizedDict(),
getIsSpecialDict(),
getPurchaseTypeDeptDict(),
getPurchaseModeSchoolDict(),
getPurchaseTypeUnionDict(),
]);
}
nextTick(() => { nextTick(() => {
formRef.value?.resetFields(); formRef.value?.resetFields();
@@ -946,11 +1108,16 @@ const openDialog = async (type: 'add' | 'edit' | 'view', rowData?: any) => {
getObj(rowData.id) getObj(rowData.id)
.then((res) => { .then((res) => {
Object.assign(dataForm, res.data || {}); Object.assign(dataForm, res.data || {});
// 设置品目编码回显路径 // 设置品目编码回显路径 - 使用 nextTick 确保 categoryTreeData 已经设置
if (dataForm.categoryCode) { if (dataForm.categoryCode) {
setCategoryCodePath(); nextTick(() => {
setCategoryCodePath();
});
} }
if (type === 'edit' || type === 'view') { if (type === 'edit') {
currentStep.value = 1;
} else if (type === 'view') {
// 查看模式下直接显示所有内容,不需要步骤
currentStep.value = 1; currentStep.value = 1;
} }
}) })
@@ -1252,11 +1419,20 @@ const handleTempStore = async () => {
}; };
// 监听 categoryTreeData 变化,设置回显路径 // 监听 categoryTreeData 变化,设置回显路径
watch(() => categoryTreeData.value, () => { watch(() => categoryTreeData.value, (newVal) => {
if (dataForm.categoryCode) { if (newVal && newVal.length > 0 && dataForm.categoryCode) {
setCategoryCodePath(); setCategoryCodePath();
} }
}, { deep: true }); }, { deep: true, immediate: false });
// 监听 dataForm.categoryCode 变化,设置回显路径(用于查看模式)
watch(() => dataForm.categoryCode, (newVal) => {
if (newVal && categoryTreeData.value && categoryTreeData.value.length > 0) {
nextTick(() => {
setCategoryCodePath();
});
}
});
// 暴露变量 // 暴露变量
defineExpose({ defineExpose({
@@ -1268,6 +1444,9 @@ defineExpose({
.mb20 { .mb20 {
margin-bottom: 20px; margin-bottom: 20px;
} }
.mb16 {
margin-bottom: 16px;
}
.mb10 { .mb10 {
margin-bottom: 10px; margin-bottom: 10px;
} }
@@ -1285,4 +1464,57 @@ defineExpose({
margin-top: 5px; margin-top: 5px;
color: var(--el-text-color-secondary); 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;
}
</style> </style>

View File

@@ -102,16 +102,10 @@
<el-icon><List /></el-icon> <el-icon><List /></el-icon>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="code" label="编号" min-width="120" show-overflow-tooltip> <el-table-column prop="code" label="申请单编号" min-width="140" show-overflow-tooltip>
<template #header> <template #header>
<el-icon><DocumentCopy /></el-icon> <el-icon><DocumentCopy /></el-icon>
<span style="margin-left: 4px">编号</span> <span style="margin-left: 4px">申请单编号</span>
</template>
</el-table-column>
<el-table-column prop="purchaseNo" label="采购编号" min-width="120" show-overflow-tooltip>
<template #header>
<el-icon><DocumentCopy /></el-icon>
<span style="margin-left: 4px">采购编号</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="projectName" label="采购项目名称" min-width="200" show-overflow-tooltip> <el-table-column prop="projectName" label="采购项目名称" min-width="200" show-overflow-tooltip>
@@ -120,27 +114,57 @@
<span style="margin-left: 4px">采购项目名称</span> <span style="margin-left: 4px">采购项目名称</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="projectType" label="项目类别" width="100" align="center"> <el-table-column prop="applyDate" label="填报日期" width="120" align="center" show-overflow-tooltip>
<template #header>
<el-icon><Calendar /></el-icon>
<span style="margin-left: 4px">填报日期</span>
</template>
</el-table-column>
<el-table-column prop="deptName" label="需求部门" min-width="150" show-overflow-tooltip>
<template #header>
<el-icon><OfficeBuilding /></el-icon>
<span style="margin-left: 4px">需求部门</span>
</template>
</el-table-column>
<el-table-column prop="projectType" label="项目类别" min-width="200" align="left" show-overflow-tooltip>
<template #header> <template #header>
<el-icon><Collection /></el-icon> <el-icon><Collection /></el-icon>
<span style="margin-left: 4px">项目类别</span> <span style="margin-left: 4px">项目类别</span>
</template> </template>
<template #default="scope"> <template #default="scope">
<el-tag v-if="scope.row.projectType === 'A'" type="success">货物</el-tag> <div>
<el-tag v-else-if="scope.row.projectType === 'B'" type="warning">工程</el-tag> <el-tag v-if="scope.row.projectType === 'A'" type="success" style="margin-right: 8px;">货物</el-tag>
<el-tag v-else-if="scope.row.projectType === 'C'" type="info">服务</el-tag> <el-tag v-else-if="scope.row.projectType === 'B'" type="warning" style="margin-right: 8px;">工程</el-tag>
<span v-else>-</span> <el-tag v-else-if="scope.row.projectType === 'C'" type="info" style="margin-right: 8px;">服务</el-tag>
<span v-if="scope.row.categoryName" style="color: #606266; font-size: 12px;">
{{ scope.row.categoryName }}
</span>
<span v-else-if="scope.row.categoryCode" style="color: #909399; font-size: 12px;">
{{ scope.row.categoryCode }}
</span>
</div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="budget" label="预算金额(元)" width="120" align="right"> <el-table-column prop="budget" label="项目预算(元)" width="130" align="right">
<template #header> <template #header>
<el-icon><Money /></el-icon> <el-icon><Money /></el-icon>
<span style="margin-left: 4px">预算金额</span> <span style="margin-left: 4px">项目预算</span>
</template> </template>
<template #default="scope"> <template #default="scope">
{{ scope.row.budget ? Number(scope.row.budget).toLocaleString() : '-' }} {{ scope.row.budget ? Number(scope.row.budget).toLocaleString() : '-' }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="isSpecial" label="是否特殊" width="100" align="center">
<template #header>
<el-icon><Warning /></el-icon>
<span style="margin-left: 4px">是否特殊</span>
</template>
<template #default="scope">
<el-tag v-if="scope.row.isSpecial === '1' || scope.row.isSpecial === 1" type="warning"></el-tag>
<el-tag v-else-if="scope.row.isSpecial === '0' || scope.row.isSpecial === 0" type="info"></el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="isCentralized" label="是否集采" width="100" align="center"> <el-table-column prop="isCentralized" label="是否集采" width="100" align="center">
<template #header> <template #header>
<el-icon><CircleCheck /></el-icon> <el-icon><CircleCheck /></el-icon>
@@ -152,10 +176,10 @@
<span v-else>-</span> <span v-else>-</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="status" label="状态" width="100" align="center"> <el-table-column prop="status" label="审核状态" width="100" align="center">
<template #header> <template #header>
<el-icon><InfoFilled /></el-icon> <el-icon><InfoFilled /></el-icon>
<span style="margin-left: 4px">状态</span> <span style="margin-left: 4px">审核状态</span>
</template> </template>
<template #default="scope"> <template #default="scope">
<el-tag v-if="scope.row.status === '-2'" type="info">撤回</el-tag> <el-tag v-if="scope.row.status === '-2'" type="info">撤回</el-tag>
@@ -167,13 +191,7 @@
<span v-else>-</span> <span v-else>-</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="createTime" label="创建时间" width="180" show-overflow-tooltip> <el-table-column label="操作" align="center" fixed="right" width="300">
<template #header>
<el-icon><Clock /></el-icon>
<span style="margin-left: 4px">创建时间</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right" width="200">
<template #default="scope"> <template #default="scope">
<el-button <el-button
icon="View" icon="View"
@@ -204,10 +222,10 @@
<!-- 分页 --> <!-- 分页 -->
<pagination <pagination
v-show="state.total > 0" v-if="state.pagination && state.pagination.total && state.pagination.total > 0"
:total="state.total" :total="state.pagination.total"
v-model:page="state.page" :current="state.pagination.current"
v-model:limit="state.limit" :size="state.pagination.size"
@pagination="getDataList" @pagination="getDataList"
/> />
</el-card> </el-card>
@@ -217,7 +235,8 @@
<el-dialog <el-dialog
v-model="showAddIframe" v-model="showAddIframe"
title="新增采购申请" title="新增采购申请"
width="1000px" width="90%"
:style="{ maxWidth: '1600px' }"
:close-on-click-modal="false" :close-on-click-modal="false"
:close-on-press-escape="true" :close-on-press-escape="true"
destroy-on-close destroy-on-close
@@ -229,26 +248,42 @@
:src="addIframeSrc" :src="addIframeSrc"
frameborder="0" frameborder="0"
class="add-iframe" class="add-iframe"
@load="onIframeLoad" /> />
</div> </div>
</el-dialog> </el-dialog>
<!-- 编辑新增表单对话框 --> <!-- 编辑新增表单对话框 -->
<FormDialog ref="formDialogRef" @refresh="getDataList" /> <FormDialog
ref="formDialogRef"
:dict-data="dictData"
@refresh="getDataList" />
</div> </div>
</template> </template>
<script setup lang="ts" name="PurchasingRequisition"> <script setup lang="ts" name="PurchasingRequisition">
import { ref, reactive, defineAsyncComponent, onUnmounted } from 'vue' import { ref, reactive, defineAsyncComponent, onUnmounted, onMounted } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { BasicTableProps, useTable } from "/@/hooks/table"; import { BasicTableProps, useTable } from "/@/hooks/table";
import { getPage, delObj } from "/@/api/finance/purchasingrequisition"; import { getPage, delObj } from "/@/api/finance/purchasingrequisition";
import { useMessage, useMessageBox } from "/@/hooks/message"; import { useMessage, useMessageBox } from "/@/hooks/message";
import { List, Document, DocumentCopy, Search, Collection, Money, CircleCheck, InfoFilled, Clock } from '@element-plus/icons-vue' import { getDicts } from '/@/api/admin/dict';
import { getTree } from '/@/api/finance/purchasingcategory';
import { List, Document, DocumentCopy, Search, Collection, Money, CircleCheck, InfoFilled, Calendar, OfficeBuilding, Warning } from '@element-plus/icons-vue'
// 引入组件 // 引入组件
const FormDialog = defineAsyncComponent(() => import('./form.vue')); const FormDialog = defineAsyncComponent(() => import('./form.vue'));
// 字典数据和品目树数据
const dictData = ref({
fundSourceList: [] as any[],
isCentralizedList: [] as any[],
isSpecialList: [] as any[],
purchaseTypeDeptList: [] as any[],
purchaseModeSchoolList: [] as any[],
purchaseTypeUnionList: [] as any[],
categoryTreeData: [] as any[],
});
// 定义变量内容 // 定义变量内容
const router = useRouter() const router = useRouter()
const tableRef = ref() const tableRef = ref()
@@ -332,11 +367,7 @@ const handleIframeMessage = (event: MessageEvent) => {
*/ */
const handleDelete = async (row: any) => { const handleDelete = async (row: any) => {
try { try {
await useMessageBox().confirm('确定要删除该记录吗?', '提示', { await useMessageBox().confirm('确定要删除该记录吗?');
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
});
} catch { } catch {
return; return;
} }
@@ -350,6 +381,151 @@ const handleDelete = async (row: any) => {
} }
}; };
// 获取字典数据和品目树数据
const loadDictData = async () => {
try {
const [
fundSourceRes,
isCentralizedRes,
isSpecialRes,
purchaseTypeDeptRes,
purchaseModeSchoolRes,
purchaseTypeUnionRes,
categoryTreeRes
] = await Promise.all([
getDicts('PURCHASE_FUND_SOURCE'),
getDicts('PURCHASE_IS_CEN'),
getDicts('PURCHASE_IS_SPEC'),
getDicts('PURCHASE_TYPE_DEPT'),
getDicts('PURCHASE_MODE_SCHOOL'),
getDicts('PURCHASE_TYPE_UNION'),
getTree()
]);
// 处理资金来源字典
if (fundSourceRes.data && Array.isArray(fundSourceRes.data)) {
dictData.value.fundSourceList = fundSourceRes.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}));
} else {
dictData.value.fundSourceList = [
{ label: '切块经费', value: '0' },
{ label: '设备购置费', value: '1' },
{ label: '专项经费', value: '2' },
{ label: '代办费', value: '3' },
{ label: '培训经费', value: '4' },
{ label: '日常公用经费', value: '5' },
{ label: '技能大赛经费', value: '6' },
{ label: '基本建设资金', value: '7' },
{ label: '暂存款', value: '8' },
{ label: '会议费', value: '9' },
];
}
// 处理是否集采字典
if (isCentralizedRes.data && Array.isArray(isCentralizedRes.data)) {
dictData.value.isCentralizedList = isCentralizedRes.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}));
} else {
dictData.value.isCentralizedList = [
{ label: '否', value: '0' },
{ label: '政府集中采购', value: '1' },
{ label: '学校集中采购', value: '2' }
];
}
// 处理是否特殊情况字典
if (isSpecialRes.data && Array.isArray(isSpecialRes.data)) {
dictData.value.isSpecialList = isSpecialRes.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}));
} else {
dictData.value.isSpecialList = [
{ label: '否', value: '0' },
{ label: '紧急', value: '1' },
{ label: '单一', value: '2' },
{ label: '进口', value: '3' }
];
}
// 处理部门采购方式字典
if (purchaseTypeDeptRes.data && Array.isArray(purchaseTypeDeptRes.data)) {
dictData.value.purchaseTypeDeptList = purchaseTypeDeptRes.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}));
}
// 处理学校采购形式字典
if (purchaseModeSchoolRes.data && Array.isArray(purchaseModeSchoolRes.data)) {
dictData.value.purchaseModeSchoolList = purchaseModeSchoolRes.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}));
} else {
dictData.value.purchaseModeSchoolList = [
{ label: '政府采购', value: '1' },
{ label: '学校自主采购', value: '2' }
];
}
// 处理学校统一采购方式字典
if (purchaseTypeUnionRes.data && Array.isArray(purchaseTypeUnionRes.data)) {
dictData.value.purchaseTypeUnionList = purchaseTypeUnionRes.data.map((item: any) => ({
label: item.label || item.dictLabel || item.name,
value: item.value || item.dictValue || item.code
}));
}
// 处理品目树数据
if (categoryTreeRes.data && Array.isArray(categoryTreeRes.data)) {
dictData.value.categoryTreeData = categoryTreeRes.data;
} else {
dictData.value.categoryTreeData = [];
}
} catch (err) {
console.error('加载字典数据失败', err);
// 设置默认值
dictData.value.fundSourceList = [
{ label: '切块经费', value: '0' },
{ label: '设备购置费', value: '1' },
{ label: '专项经费', value: '2' },
{ label: '代办费', value: '3' },
{ label: '培训经费', value: '4' },
{ label: '日常公用经费', value: '5' },
{ label: '技能大赛经费', value: '6' },
{ label: '基本建设资金', value: '7' },
{ label: '暂存款', value: '8' },
{ label: '会议费', value: '9' },
];
dictData.value.isCentralizedList = [
{ label: '否', value: '0' },
{ label: '政府集中采购', value: '1' },
{ label: '学校集中采购', value: '2' }
];
dictData.value.isSpecialList = [
{ label: '否', value: '0' },
{ label: '紧急', value: '1' },
{ label: '单一', value: '2' },
{ label: '进口', value: '3' }
];
dictData.value.purchaseModeSchoolList = [
{ label: '政府采购', value: '1' },
{ label: '学校自主采购', value: '2' }
];
dictData.value.categoryTreeData = [];
}
};
// 页面加载时获取字典数据和品目树数据
onMounted(() => {
loadDictData();
});
// 组件卸载时清理事件监听器 // 组件卸载时清理事件监听器
onUnmounted(() => { onUnmounted(() => {
window.removeEventListener('message', handleIframeMessage) window.removeEventListener('message', handleIframeMessage)

View File

@@ -1,256 +1,245 @@
<template> <template>
<el-dialog v-model="dialogEmptyFromVisible" title="教职工信息导出" width="80%"> <el-dialog v-model="dialogEmptyFromVisible" title="教职工信息导出" width="80%">
<el-form :inline="true" label-width="100px" style="margin-bottom: 20px;"> <el-form :inline="true" label-width="100px" style="margin-bottom: 20px">
<el-form-item> <el-form-item>
<el-tag type="info">导出依据"职工信息"页面的检索条件</el-tag> <el-tag type="info">导出依据"职工信息"页面的检索条件</el-tag>
<el-tag type="warning" style="margin-left: 10px;">请在搜索后再打开导出页面</el-tag> <el-tag type="warning" style="margin-left: 10px">请在搜索后再打开导出页面</el-tag>
</el-form-item> </el-form-item>
<el-form-item label="是否退休"> <el-form-item label="是否退休">
<el-select <el-select v-model="tiedTag" filterable reserve-keyword clearable style="width: 200px">
v-model="tiedTag" <el-option v-for="item in YES_OR_NO" :key="item.value" :label="item.label" :value="item.value"> </el-option>
filterable </el-select>
reserve-keyword </el-form-item>
clearable <el-form-item>
style="width: 200px;"> <el-button type="primary" @click="exportTeacherInfo" :loading="exportLoading" :icon="Download">导出</el-button>
<el-option </el-form-item>
v-for="item in YES_OR_NO" </el-form>
:key="item.value"
:label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button
type="primary"
@click="exportTeacherInfo"
:loading="exportLoading"
:icon="Download">导出</el-button>
</el-form-item>
</el-form>
<!-- 基础信息 --> <!-- 基础信息 -->
<el-card shadow="never" style="margin-bottom: 20px;"> <el-card shadow="never" style="margin-bottom: 20px">
<template #header> <template #header>
<div style="display: flex; justify-content: space-between; align-items: center;"> <div style="display: flex; justify-content: space-between; align-items: center">
<span>基础信息</span> <span>基础信息</span>
<el-checkbox v-model="allCheckTeacherBaseInfo">全选</el-checkbox> <el-checkbox v-model="allCheckTeacherBaseInfo">全选</el-checkbox>
</div> </div>
</template> </template>
<el-checkbox-group v-model="checkedTeacherBaseInfo"> <el-checkbox-group v-model="checkedTeacherBaseInfo">
<el-checkbox v-for="(item, index) in teacherBasicCheckData" :label="item.value" :key="index" style="margin-right: 20px; margin-bottom: 10px;"> <el-checkbox v-for="(item, index) in teacherBasicCheckData" :label="item.value" :key="index" style="margin-right: 20px; margin-bottom: 10px">
{{item.label}} {{ item.label }}
</el-checkbox> </el-checkbox>
</el-checkbox-group> </el-checkbox-group>
</el-card> </el-card>
<!-- 岗位信息 --> <!-- 岗位信息 -->
<el-card shadow="never" style="margin-bottom: 20px;"> <el-card shadow="never" style="margin-bottom: 20px">
<template #header> <template #header>
<div style="display: flex; justify-content: space-between; align-items: center;"> <div style="display: flex; justify-content: space-between; align-items: center">
<span>岗位信息</span> <span>岗位信息</span>
<el-checkbox v-model="allCheckStationInfo">全选</el-checkbox> <el-checkbox v-model="allCheckStationInfo">全选</el-checkbox>
</div> </div>
</template> </template>
<el-checkbox-group v-model="checkedTeacherStationInfo"> <el-checkbox-group v-model="checkedTeacherStationInfo">
<el-checkbox v-for="(item, index) in teacherStationData" :key="index" :label="item.value" style="margin-right: 20px; margin-bottom: 10px;"> <el-checkbox v-for="(item, index) in teacherStationData" :key="index" :label="item.value" style="margin-right: 20px; margin-bottom: 10px">
{{item.label}} {{ item.label }}
</el-checkbox> </el-checkbox>
</el-checkbox-group> </el-checkbox-group>
</el-card> </el-card>
<!-- 其他 -->
<el-card shadow="never">
<template #header>
<div style="display: flex; justify-content: space-between; align-items: center;">
<span>其他</span>
<el-checkbox v-model="allCheckedother">全选</el-checkbox>
</div>
</template>
<el-checkbox-group v-model="checkedTeacherOtherInfo">
<el-checkbox v-for="(item, index) in teacherOtherData" :key="index" :label="item.value" style="margin-right: 20px; margin-bottom: 10px;">
{{item.label}}
</el-checkbox>
</el-checkbox-group>
</el-card>
</el-dialog>
<!-- 其他 -->
<el-card shadow="never">
<template #header>
<div style="display: flex; justify-content: space-between; align-items: center">
<span>其他</span>
<el-checkbox v-model="allCheckedother">全选</el-checkbox>
</div>
</template>
<el-checkbox-group v-model="checkedTeacherOtherInfo">
<el-checkbox v-for="(item, index) in teacherOtherData" :key="index" :label="item.value" style="margin-right: 20px; margin-bottom: 10px">
{{ item.label }}
</el-checkbox>
</el-checkbox-group>
</el-card>
</el-dialog>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch } from 'vue' import { ref, watch } from 'vue';
import { ElNotification } from 'element-plus' import { ElNotification } from 'element-plus';
import { Download } from '@element-plus/icons-vue' import { Download } from '@element-plus/icons-vue';
import global from '/@/components/tools/commondict.vue' import global from '/@/components/tools/commondict.vue';
import request from '/@/utils/request' import request from '/@/utils/request';
import { exportTeacherInfoBySelf } from '/@/api/professional/professionalfile';
// Props // Props
const props = defineProps<{ const props = defineProps<{
search: any search: any;
}>() }>();
// 响应式数据 // 响应式数据
const tiedTag = ref('') const tiedTag = ref('');
const YES_OR_NO = global.YES_OR_NO const YES_OR_NO = global.YES_OR_NO;
const exportLoading = ref(false) const exportLoading = ref(false);
const dialogEmptyFromVisible = ref(false) const dialogEmptyFromVisible = ref(false);
const allCheckTeacherBaseInfo = ref(false) const allCheckTeacherBaseInfo = ref(false);
const allCheckStationInfo = ref(false) const allCheckStationInfo = ref(false);
const allCheckedother = ref(false) const allCheckedother = ref(false);
const checkedTeacherBaseInfo = ref<string[]>([]) const checkedTeacherBaseInfo = ref<string[]>([]);
const checkedTeacherStationInfo = ref<string[]>([]) const checkedTeacherStationInfo = ref<string[]>([]);
const checkedTeacherOtherInfo = ref<string[]>([]) const checkedTeacherOtherInfo = ref<string[]>([]);
const teacherBasicCheckData = [ const teacherBasicCheckData = [
{label:'姓名',value:'realName_姓名'}, { label: '姓名', value: 'realName_姓名' },
{label:'工号',value:'teacherNo_工号'}, { label: '工号', value: 'teacherNo_工号' },
{label:'部门',value:'deptCode_部门'}, { label: '部门', value: 'deptCode_部门' },
{label:'性别',value:'sex_性别'}, { label: '性别', value: 'sex_性别' },
{label:'身份证',value:'idCard_身份证'}, { label: '身份证', value: 'idCard_身份证' },
{label:'出生年月',value:'birthday_出生年月'}, { label: '出生年月', value: 'birthday_出生年月' },
{label:'年龄',value:'age_年龄'}, { label: '年龄', value: 'age_年龄' },
{label:'手机',value:'telPhone_手机'}, { label: '手机', value: 'telPhone_手机' },
{label:'手机2',value:'telPhoneTwo_手机2'}, { label: '手机2', value: 'telPhoneTwo_手机2' },
{label:'宅电',value:'homePhone_宅电'}, { label: '宅电', value: 'homePhone_宅电' },
{label:'民族',value:'national_民族'}, { label: '民族', value: 'national_民族' },
{label:'籍贯',value:'nativePlace_籍贯'}, { label: '籍贯', value: 'nativePlace_籍贯' },
{label:'出生地',value:'birthPlace_出生地'}, { label: '出生地', value: 'birthPlace_出生地' },
{label:'健康状况',value:'health_健康状况'}, { label: '健康状况', value: 'health_健康状况' },
{label:'特长',value:'speciality_特长'}, { label: '特长', value: 'speciality_特长' },
{label:'银行卡号',value:'bankNo_银行卡号'}, { label: '银行卡号', value: 'bankNo_银行卡号' },
{label:'开户行',value:'bankOpen_开户行'}, { label: '开户行', value: 'bankOpen_开户行' },
{label:'家庭住址',value:'homeAddress_家庭住址'}, { label: '家庭住址', value: 'homeAddress_家庭住址' },
{label:'授课类型',value:'teacherCate_授课类型'}, { label: '授课类型', value: 'teacherCate_授课类型' },
{label:'允许进出',value:'inoutFlag_允许进出'}, { label: '允许进出', value: 'inoutFlag_允许进出' },
{label:'进出情况备注',value:'inoutRemarks_进出情况备注'}, { label: '进出情况备注', value: 'inoutRemarks_进出情况备注' },
] ];
const teacherStationData = [ const teacherStationData = [
{label:'教师类型',value:'teacherTypeName_教师类型'}, { label: '教师类型', value: 'teacherTypeName_教师类型' },
{label:'党支部',value:'branchName_党支部'}, { label: '党支部', value: 'branchName_党支部' },
{label:'用工性质',value:'employmentNatureName_用工性质'}, { label: '用工性质', value: 'employmentNatureName_用工性质' },
{label:'岗位类别',value:'stationTypeName_岗位类别'}, { label: '岗位类别', value: 'stationTypeName_岗位类别' },
{label:'岗位级别',value:'stationLevelName_岗位级别'}, { label: '岗位级别', value: 'stationLevelName_岗位级别' },
{label:'职务级别',value:'stationDutyLevelName_职务级别'}, { label: '职务级别', value: 'stationDutyLevelName_职务级别' },
{label:'任现岗位职级时间',value:'stationDate_任现岗位职级时间'}, { label: '任现岗位职级时间', value: 'stationDate_任现岗位职级时间' },
{label:'在职情况',value:'atStationName_在职情况'}, { label: '在职情况', value: 'atStationName_在职情况' },
{label:'职务',value:'dutyDesc_职务'}, { label: '职务', value: 'dutyDesc_职务' },
{label:'参加工作时间',value:'workDate_参加工作时间'}, { label: '参加工作时间', value: 'workDate_参加工作时间' },
{label:'退休时间',value:'retireDate_退休时间'}, { label: '退休时间', value: 'retireDate_退休时间' },
{label:'职位类别',value:'teacherClassify_职位类别'}, { label: '职位类别', value: 'teacherClassify_职位类别' },
{label:'任现职务时间',value:'dutyDate_任现职务时间'}, { label: '任现职务时间', value: 'dutyDate_任现职务时间' },
{label:'进编时间',value:'entryDutyDate_进编时间'}, { label: '进编时间', value: 'entryDutyDate_进编时间' },
{label:'进校时间',value:'entrySchoolDate_进校时间'}, { label: '进校时间', value: 'entrySchoolDate_进校时间' },
] ];
const teacherOtherData = [ const teacherOtherData = [
{label:'职业资格工种',value:'zyzg_职业资格工种'}, { label: '职业资格工种', value: 'zyzg_职业资格工种' },
{label:'职业资格等级',value:'zyzgLevel_职业资格等级'}, { label: '职业资格等级', value: 'zyzgLevel_职业资格等级' },
{label:'政治面貌',value:'zzmm_政治面貌'}, { label: '政治面貌', value: 'zzmm_政治面貌' },
{label:'学历',value:'xl_学历'}, { label: '学历', value: 'xl_学历' },
{label:'学位',value:'xw_学位'}, { label: '学位', value: 'xw_学位' },
{label:'职称',value:'zc_职称'}, { label: '职称', value: 'zc_职称' },
{label:'高校教师资格证',value:'highCer_高校教师资格证'}, { label: '高校教师资格证', value: 'highCer_高校教师资格证' },
{label:'中等教师资格证',value:'midCer_中等教师资格证'}, { label: '中等教师资格证', value: 'midCer_中等教师资格证' },
{label:'教师上岗证',value:'priCer_教师上岗证'}, { label: '教师上岗证', value: 'priCer_教师上岗证' },
// {label:'共同居住人',value:'livewith_共同居住人'}, // {label:'共同居住人',value:'livewith_共同居住人'},
] ];
// 导出文件函数 // 导出文件函数
const exportForModel = async (params: any, fileNameStr: string, url: string) => { const exportForModel = async (params: any, fileNameStr: string, url: string) => {
try { try {
const res = await request({ const res = await request({
method: 'post', method: 'post',
url: url, url: url,
data: params, data: params,
responseType: 'blob', responseType: 'blob',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json',
} },
}) });
const blob = new Blob([res]) const blob = new Blob([res]);
const fileName = fileNameStr const fileName = fileNameStr;
const elink = document.createElement('a') const elink = document.createElement('a');
elink.download = fileName elink.download = fileName;
elink.style.display = 'none' elink.style.display = 'none';
elink.href = URL.createObjectURL(blob) elink.href = URL.createObjectURL(blob);
document.body.appendChild(elink) document.body.appendChild(elink);
elink.click() elink.click();
URL.revokeObjectURL(elink.href) URL.revokeObjectURL(elink.href);
document.body.removeChild(elink) document.body.removeChild(elink);
} catch (err) { } catch (err) {
ElNotification.error({ ElNotification.error({
title: '错误', title: '错误',
message: '导出失败', message: '导出失败',
}) });
} }
} };
// Watch // Watch
watch(allCheckTeacherBaseInfo, (newVal) => { watch(allCheckTeacherBaseInfo, (newVal) => {
if (newVal) { if (newVal) {
checkedTeacherBaseInfo.value = teacherBasicCheckData.map(item => item.value) checkedTeacherBaseInfo.value = teacherBasicCheckData.map((item) => item.value);
} else { } else {
checkedTeacherBaseInfo.value = [] checkedTeacherBaseInfo.value = [];
} }
}) });
watch(allCheckStationInfo, (newVal) => { watch(allCheckStationInfo, (newVal) => {
if (newVal) { if (newVal) {
checkedTeacherStationInfo.value = teacherStationData.map(item => item.value) checkedTeacherStationInfo.value = teacherStationData.map((item) => item.value);
} else { } else {
checkedTeacherStationInfo.value = [] checkedTeacherStationInfo.value = [];
} }
}) });
watch(allCheckedother, (newVal) => { watch(allCheckedother, (newVal) => {
if (newVal) { if (newVal) {
checkedTeacherOtherInfo.value = teacherOtherData.map(item => item.value) checkedTeacherOtherInfo.value = teacherOtherData.map((item) => item.value);
} else { } else {
checkedTeacherOtherInfo.value = [] checkedTeacherOtherInfo.value = [];
} }
}) });
// 方法 // 方法
const init = () => { const init = () => {
tiedTag.value = props.search.tied tiedTag.value = props.search.tied;
dialogEmptyFromVisible.value = true dialogEmptyFromVisible.value = true;
} };
const exportTeacherInfo = async () => { const exportTeacherInfo = async () => {
if (checkedTeacherBaseInfo.value.length == 0 && checkedTeacherStationInfo.value.length == 0 && checkedTeacherOtherInfo.value.length == 0) { if (checkedTeacherBaseInfo.value.length == 0 && checkedTeacherStationInfo.value.length == 0 && checkedTeacherOtherInfo.value.length == 0) {
ElNotification({ ElNotification({
title: '警告', title: '警告',
message: '请选择要导出的字段', message: '请选择要导出的字段',
type: 'warning', type: 'warning',
duration: 3000 duration: 3000,
}) });
return return;
} }
exportLoading.value = true exportLoading.value = true;
const searchData = JSON.parse(JSON.stringify(props.search)) const searchData = JSON.parse(JSON.stringify(props.search));
searchData.tied = tiedTag.value searchData.tied = tiedTag.value;
const params = { const params = {
"checkedTeacherBaseInfo": checkedTeacherBaseInfo.value, checkedTeacherBaseInfo: checkedTeacherBaseInfo.value,
"checkedTeacherStationInfo": checkedTeacherStationInfo.value, checkedTeacherStationInfo: checkedTeacherStationInfo.value,
"checkedTeacherOtherInfo": checkedTeacherOtherInfo.value, checkedTeacherOtherInfo: checkedTeacherOtherInfo.value,
"teacherBaseDTO": searchData teacherBaseDTO: searchData,
} };
await exportForModel(params, "教职工信息.xls", "/professional/file/exportTeacherInfoBySelf") exportTeacherInfoBySelf(params).then((res:any)=>{
ElNotification.success({
title: '下载后台进行中',
message: '操作成功'
})
});
setTimeout(() => { setTimeout(() => {
exportLoading.value = false exportLoading.value = false;
}, 3000) }, 3000);
} };
// 暴露方法给父组件 // 暴露方法给父组件
defineExpose({ defineExpose({
init init,
}) });
</script> </script>
<style scoped> <style scoped></style>
</style>