fix
This commit is contained in:
@@ -1,280 +1,236 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
title="文件归档"
|
||||
width="900px"
|
||||
destroy-on-close
|
||||
append-to-body
|
||||
>
|
||||
<template #header>
|
||||
<div class="dialog-header">
|
||||
<span class="dialog-title">
|
||||
<el-icon><FolderOpened /></el-icon>
|
||||
文件归档 - {{ purchaseNo || purchaseId }}
|
||||
</span>
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="downloading"
|
||||
@click="handleDownloadAll"
|
||||
>
|
||||
<el-icon><Download /></el-icon>
|
||||
下载全部文件
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<el-dialog v-model="visible" title="文件归档" width="900px" destroy-on-close append-to-body>
|
||||
<template #header>
|
||||
<div class="dialog-header">
|
||||
<span class="dialog-title">
|
||||
<el-icon><FolderOpened /></el-icon>
|
||||
文件归档 - {{ purchaseNo || purchaseId }}
|
||||
</span>
|
||||
<el-button type="primary" :loading="downloading" @click="handleDownloadAll">
|
||||
<el-icon><Download /></el-icon>
|
||||
下载全部文件
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="fileList"
|
||||
stripe
|
||||
border
|
||||
max-height="500px"
|
||||
class="file-table"
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="fileTitle" label="文件名称" min-width="220" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<div class="file-name">
|
||||
<el-icon class="file-icon"><Document /></el-icon>
|
||||
<span>{{ row.fileTitle }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="fileTypeDesc" label="文件类型" width="160" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag type="info">{{ row.fileTypeDesc || '未知类型' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="150" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
icon="View"
|
||||
@click="handlePreview(row)"
|
||||
>
|
||||
预览
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
icon="Download"
|
||||
@click="handleDownloadFile(row)"
|
||||
>
|
||||
下载
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-table v-loading="loading" :data="fileList" stripe border max-height="500px" class="file-table">
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="fileTitle" label="文件名称" min-width="220" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<div class="file-name">
|
||||
<el-icon class="file-icon"><Document /></el-icon>
|
||||
<span>{{ row.fileTitle }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="fileTypeDesc" label="文件类型" width="160" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag type="info">{{ row.fileTypeDesc || '未知类型' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="150" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link icon="View" @click="handlePreview(row)"> 预览 </el-button>
|
||||
<el-button type="primary" link icon="Download" @click="handleDownloadFile(row)"> 下载 </el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div v-if="!loading && fileList.length === 0" class="empty-tip">
|
||||
<el-empty description="暂无文件" />
|
||||
</div>
|
||||
<div v-if="!loading && fileList.length === 0" class="empty-tip">
|
||||
<el-empty description="暂无文件" />
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- PDF预览弹窗 -->
|
||||
<el-dialog
|
||||
v-model="previewVisible"
|
||||
title="文件预览"
|
||||
width="90%"
|
||||
top="5vh"
|
||||
destroy-on-close
|
||||
append-to-body
|
||||
class="preview-dialog"
|
||||
>
|
||||
<div class="preview-container">
|
||||
<iframe
|
||||
v-if="previewUrl"
|
||||
:src="previewUrl"
|
||||
class="preview-iframe"
|
||||
frameborder="0"
|
||||
/>
|
||||
</div>
|
||||
</el-dialog>
|
||||
<!-- PDF预览弹窗 -->
|
||||
<el-dialog v-model="previewVisible" title="文件预览" width="90%" top="5vh" destroy-on-close append-to-body class="preview-dialog">
|
||||
<div class="preview-container">
|
||||
<iframe v-if="previewUrl" :src="previewUrl" class="preview-iframe" frameborder="0" />
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onUnmounted } from 'vue'
|
||||
import { FolderOpened, Download, Document } from '@element-plus/icons-vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { listDownloadUrls, getArchiveDownloadUrl, downloadFileById, previewFileById } from '/@/api/purchase/purchasingrequisition'
|
||||
import other from '/@/utils/other'
|
||||
import { ref, computed, onUnmounted } from 'vue';
|
||||
import { FolderOpened, Download, Document } from '@element-plus/icons-vue';
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
import { listDownloadUrls, getArchiveDownloadUrl, downloadFileById, previewFileById } from '/@/api/purchase/purchasingrequisition';
|
||||
import other from '/@/utils/other';
|
||||
|
||||
interface FileItem {
|
||||
id: string
|
||||
fileTitle: string
|
||||
fileType: string
|
||||
fileTypeDesc: string
|
||||
downloadUrl: string
|
||||
id: string;
|
||||
fileTitle: string;
|
||||
fileType: string;
|
||||
fileTypeDesc: string;
|
||||
downloadUrl: string;
|
||||
}
|
||||
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const downloading = ref(false)
|
||||
const previewVisible = ref(false)
|
||||
const previewUrl = ref('')
|
||||
const previewLoading = ref(false)
|
||||
const purchaseId = ref('')
|
||||
const purchaseNo = ref('')
|
||||
const fileList = ref<FileItem[]>([])
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
const downloading = ref(false);
|
||||
const previewVisible = ref(false);
|
||||
const previewUrl = ref('');
|
||||
const previewLoading = ref(false);
|
||||
const purchaseId = ref('');
|
||||
const purchaseNo = ref('');
|
||||
const fileList = ref<FileItem[]>([]);
|
||||
|
||||
const open = async (id: string, no?: string) => {
|
||||
purchaseId.value = id || ''
|
||||
purchaseNo.value = no || ''
|
||||
visible.value = true
|
||||
await loadFileList()
|
||||
}
|
||||
purchaseId.value = id || '';
|
||||
purchaseNo.value = no || '';
|
||||
visible.value = true;
|
||||
await loadFileList();
|
||||
};
|
||||
|
||||
const loadFileList = async () => {
|
||||
if (!purchaseId.value) {
|
||||
useMessage().warning('无法获取采购申请ID')
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await listDownloadUrls(purchaseId.value)
|
||||
if (res && res.data) {
|
||||
fileList.value = res.data as FileItem[]
|
||||
} else {
|
||||
fileList.value = []
|
||||
}
|
||||
} catch (err: any) {
|
||||
useMessage().error(err?.msg || '获取文件列表失败')
|
||||
fileList.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
if (!purchaseId.value) {
|
||||
useMessage().warning('无法获取采购申请ID');
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await listDownloadUrls(purchaseId.value);
|
||||
if (res && res.data) {
|
||||
fileList.value = res.data as FileItem[];
|
||||
} else {
|
||||
fileList.value = [];
|
||||
}
|
||||
} catch (err: any) {
|
||||
useMessage().error(err?.msg || '获取文件列表失败');
|
||||
fileList.value = [];
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const isPdfFile = (fileName: string): boolean => {
|
||||
if (!fileName) return false
|
||||
const ext = fileName.toLowerCase().split('.').pop()
|
||||
return ext === 'pdf'
|
||||
}
|
||||
if (!fileName) return false;
|
||||
const ext = fileName.toLowerCase().split('.').pop();
|
||||
return ext === 'pdf';
|
||||
};
|
||||
|
||||
const handlePreview = async (row: FileItem) => {
|
||||
if (!row.id) {
|
||||
useMessage().warning('文件ID不存在')
|
||||
return
|
||||
}
|
||||
if (!row.id) {
|
||||
useMessage().warning('文件ID不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isPdfFile(row.fileTitle)) {
|
||||
useMessage().info('仅支持PDF格式文件预览,将为您下载文件')
|
||||
handleDownloadFile(row)
|
||||
return
|
||||
}
|
||||
if (!isPdfFile(row.fileTitle)) {
|
||||
useMessage().info('仅支持PDF格式文件预览,将为您下载文件');
|
||||
handleDownloadFile(row);
|
||||
return;
|
||||
}
|
||||
|
||||
previewLoading.value = true
|
||||
previewVisible.value = true
|
||||
previewUrl.value = ''
|
||||
previewLoading.value = true;
|
||||
previewVisible.value = true;
|
||||
previewUrl.value = '';
|
||||
|
||||
try {
|
||||
const res = await previewFileById(row.id)
|
||||
const blob = res as unknown as Blob
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
previewUrl.value = url
|
||||
} catch (err: any) {
|
||||
useMessage().error(err?.msg || '预览失败')
|
||||
previewVisible.value = false
|
||||
} finally {
|
||||
previewLoading.value = false
|
||||
}
|
||||
}
|
||||
try {
|
||||
const res = await previewFileById(row.id);
|
||||
const blob = res as unknown as Blob;
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
previewUrl.value = url;
|
||||
} catch (err: any) {
|
||||
useMessage().error(err?.msg || '预览失败');
|
||||
previewVisible.value = false;
|
||||
} finally {
|
||||
previewLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownloadFile = async (row: FileItem) => {
|
||||
if (!row.id) {
|
||||
useMessage().warning('文件ID不存在')
|
||||
return
|
||||
}
|
||||
try {
|
||||
const res = await downloadFileById(row.id)
|
||||
const blob = res as unknown as Blob
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = row.fileTitle || 'download'
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(url)
|
||||
} catch (err: any) {
|
||||
useMessage().error(err?.msg || '下载失败')
|
||||
}
|
||||
}
|
||||
if (!row.id) {
|
||||
useMessage().warning('文件ID不存在');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await downloadFileById(row.id);
|
||||
const blob = res as unknown as Blob;
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = row.fileTitle || 'download';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
} catch (err: any) {
|
||||
useMessage().error(err?.msg || '下载失败');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownloadAll = () => {
|
||||
if (!purchaseId.value) {
|
||||
useMessage().warning('无法获取采购申请ID')
|
||||
return
|
||||
}
|
||||
downloading.value = true
|
||||
try {
|
||||
const url = getArchiveDownloadUrl(purchaseId.value)
|
||||
const fileName = `归档_${purchaseNo.value || purchaseId.value}.zip`
|
||||
other.downBlobFile(url, {}, fileName)
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
downloading.value = false
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
if (!purchaseId.value) {
|
||||
useMessage().warning('无法获取采购申请ID');
|
||||
return;
|
||||
}
|
||||
downloading.value = true;
|
||||
try {
|
||||
const url = getArchiveDownloadUrl(purchaseId.value);
|
||||
const fileName = `归档_${purchaseNo.value || purchaseId.value}.zip`;
|
||||
other.downBlobFile(url, {}, fileName);
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
downloading.value = false;
|
||||
}, 500);
|
||||
}
|
||||
};
|
||||
|
||||
onUnmounted(() => {
|
||||
if (previewUrl.value) {
|
||||
window.URL.revokeObjectURL(previewUrl.value)
|
||||
}
|
||||
})
|
||||
if (previewUrl.value) {
|
||||
window.URL.revokeObjectURL(previewUrl.value);
|
||||
}
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
open
|
||||
})
|
||||
open,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.dialog-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dialog-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.file-table {
|
||||
width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.file-icon {
|
||||
color: #409eff;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
padding: 40px 0;
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
.preview-container {
|
||||
width: 100%;
|
||||
height: calc(90vh - 120px);
|
||||
width: 100%;
|
||||
height: calc(90vh - 120px);
|
||||
}
|
||||
|
||||
.preview-iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -9,7 +9,12 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-alert title="仅支持上传PDF格式文件,每个类型仅能上传1个文件,如需更新,请先删除原文件" type="info" :closable="false" style="margin-bottom: 16px" />
|
||||
<el-alert
|
||||
title="仅支持上传PDF格式文件,每个类型仅能上传1个文件,如需更新,请先删除原文件"
|
||||
type="info"
|
||||
:closable="false"
|
||||
style="margin-bottom: 16px"
|
||||
/>
|
||||
|
||||
<el-empty v-if="uploadedFileTypes.length === 0" description="该采购申请暂无上传材料" />
|
||||
|
||||
@@ -82,12 +87,7 @@ const fileTypeList: FileTypeItem[] = [
|
||||
|
||||
// 根据已上传文件类型过滤显示列表(排除履约验收110和采购文件130)
|
||||
const displayedFileTypes = computed(() => {
|
||||
return fileTypeList.filter(
|
||||
(item) =>
|
||||
uploadedFileTypes.value.includes(item.value) &&
|
||||
item.value !== '110' &&
|
||||
item.value !== '130'
|
||||
);
|
||||
return fileTypeList.filter((item) => uploadedFileTypes.value.includes(item.value) && item.value !== '110' && item.value !== '130');
|
||||
});
|
||||
|
||||
const open = async (id: string, no?: string) => {
|
||||
@@ -149,13 +149,13 @@ const handleSubmit = async () => {
|
||||
uploadedFileTypes.value
|
||||
.filter((ft) => ft !== '110' && ft !== '130')
|
||||
.forEach((fileType) => {
|
||||
const files = fileMap[fileType] || [];
|
||||
files.forEach((file) => {
|
||||
if (file.id) {
|
||||
allFileIds.push(file.id);
|
||||
}
|
||||
const files = fileMap[fileType] || [];
|
||||
files.forEach((file) => {
|
||||
if (file.id) {
|
||||
allFileIds.push(file.id);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if (allFileIds.length === 0) {
|
||||
useMessage().warning('请至少保留或上传一个文件');
|
||||
|
||||
@@ -1,188 +1,201 @@
|
||||
<template>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="160px" >
|
||||
<el-row :gutter="24">
|
||||
<!-- <el-col :span="12" class="mb20">-->
|
||||
<!-- <el-form-item label="验收日期" prop="acceptDate">-->
|
||||
<!-- <el-date-picker-->
|
||||
<!-- v-model="form.acceptDate"-->
|
||||
<!-- type="date"-->
|
||||
<!-- placeholder="请选择"-->
|
||||
<!-- format="YYYY-MM-DD"-->
|
||||
<!-- value-format="YYYY-MM-DD"-->
|
||||
<!-- style="width: 100%"-->
|
||||
<!-- :disabled="readonly"-->
|
||||
<!-- />-->
|
||||
<!-- </el-form-item>-->
|
||||
<!-- </el-col>-->
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="160px">
|
||||
<el-row :gutter="24">
|
||||
<!-- <el-col :span="12" class="mb20">-->
|
||||
<!-- <el-form-item label="验收日期" prop="acceptDate">-->
|
||||
<!-- <el-date-picker-->
|
||||
<!-- v-model="form.acceptDate"-->
|
||||
<!-- type="date"-->
|
||||
<!-- placeholder="请选择"-->
|
||||
<!-- format="YYYY-MM-DD"-->
|
||||
<!-- value-format="YYYY-MM-DD"-->
|
||||
<!-- style="width: 100%"-->
|
||||
<!-- :disabled="readonly"-->
|
||||
<!-- />-->
|
||||
<!-- </el-form-item>-->
|
||||
<!-- </el-col>-->
|
||||
|
||||
<!-- 上传履约验收模版 -->
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="履约验收文件" prop="templateFileIds" :required="true">
|
||||
<upload-file v-model="templateFiles" :limit="1" :file-type="['pdf']" :data="{ purchaseId: purchaseId || '', fileType: '110' }" upload-file-url="/purchase/purchasingfiles/upload" :disabled="readonly" />
|
||||
<el-link v-if="!readonly" type="primary" @click="handleDownloadTemplate" style="margin-top: 8px; display: inline-flex; align-items: center;">
|
||||
<el-icon><Download /></el-icon>
|
||||
<span style="margin-left: 4px;">下载{{ lyysTemplateLabel }}</span>
|
||||
</el-link>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- 上传履约验收模版 -->
|
||||
<el-col :span="12" class="mb20">
|
||||
<el-form-item label="履约验收文件" prop="templateFileIds" :required="true">
|
||||
<upload-file
|
||||
v-model="templateFiles"
|
||||
:limit="1"
|
||||
:file-type="['pdf']"
|
||||
:data="{ purchaseId: purchaseId || '', fileType: '110' }"
|
||||
upload-file-url="/purchase/purchasingfiles/upload"
|
||||
:disabled="readonly"
|
||||
/>
|
||||
<el-link v-if="!readonly" type="primary" @click="handleDownloadTemplate" style="margin-top: 8px; display: inline-flex; align-items: center">
|
||||
<el-icon><Download /></el-icon>
|
||||
<span style="margin-left: 4px">下载{{ lyysTemplateLabel }}</span>
|
||||
</el-link>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入" :disabled="readonly" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入" :disabled="readonly" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch, onMounted } from 'vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { Download } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import UploadFile from "/@/components/Upload/index.vue";
|
||||
import { downloadTemplate } from "/@/api/purchase/purchasingAccept";
|
||||
import { ref, reactive, computed, watch, onMounted } from 'vue';
|
||||
import type { FormInstance, FormRules } from 'element-plus';
|
||||
import { Download } from '@element-plus/icons-vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import UploadFile from '/@/components/Upload/index.vue';
|
||||
import { downloadTemplate } from '/@/api/purchase/purchasingAccept';
|
||||
|
||||
/** 项目类型 A:货物 B:工程 C:服务 */
|
||||
const LYYS_TEMPLATE_MAP: Record<string, { label: string }> = {
|
||||
A: { label: '履约验收表模板(货物)' },
|
||||
B: { label: '履约验收表模板(工程)' },
|
||||
C: { label: '履约验收表模板(服务)' },
|
||||
}
|
||||
A: { label: '履约验收表模板(货物)' },
|
||||
B: { label: '履约验收表模板(工程)' },
|
||||
C: { label: '履约验收表模板(服务)' },
|
||||
};
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: Record<string, any>
|
||||
readonly?: boolean
|
||||
purchaseId?: string
|
||||
/** 项目类型 A:货物 B:工程 C:服务,用于模版下载 */
|
||||
projectType?: string
|
||||
/** 预算金额,用于判断模板目录 */
|
||||
budget?: number
|
||||
batchNum?: number
|
||||
}>()
|
||||
modelValue: Record<string, any>;
|
||||
readonly?: boolean;
|
||||
purchaseId?: string;
|
||||
/** 项目类型 A:货物 B:工程 C:服务,用于模版下载 */
|
||||
projectType?: string;
|
||||
/** 预算金额,用于判断模板目录 */
|
||||
budget?: number;
|
||||
batchNum?: number;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
const formRef = ref<FormInstance>();
|
||||
// 文件对象数组,用于上传组件显示(包含id和fileTitle)
|
||||
const templateFiles = ref<any[]>([])
|
||||
const templateFiles = ref<any[]>([]);
|
||||
|
||||
const projectTypeKey = computed(() => (props.projectType === 'A' || props.projectType === 'B' || props.projectType === 'C' ? props.projectType : 'A'))
|
||||
const projectTypeKey = computed(() =>
|
||||
props.projectType === 'A' || props.projectType === 'B' || props.projectType === 'C' ? props.projectType : 'A'
|
||||
);
|
||||
const lyysTemplateLabel = computed(() => {
|
||||
const amountLabel = (props.budget && props.budget >= 50000) ? '(≥5万)' : '(<5万)'
|
||||
return LYYS_TEMPLATE_MAP[projectTypeKey.value]?.label + amountLabel || LYYS_TEMPLATE_MAP.A.label + amountLabel
|
||||
})
|
||||
const lyysTemplateDownloadName = computed(() => `${lyysTemplateLabel.value}.docx`)
|
||||
const amountLabel = props.budget && props.budget >= 50000 ? '(≥5万)' : '(<5万)';
|
||||
return LYYS_TEMPLATE_MAP[projectTypeKey.value]?.label + amountLabel || LYYS_TEMPLATE_MAP.A.label + amountLabel;
|
||||
});
|
||||
const lyysTemplateDownloadName = computed(() => `${lyysTemplateLabel.value}.docx`);
|
||||
|
||||
/** 下载模板 - 调用后台接口 */
|
||||
const handleDownloadTemplate = async () => {
|
||||
if (!props.purchaseId) {
|
||||
ElMessage.warning('采购ID不存在')
|
||||
return
|
||||
}
|
||||
try {
|
||||
const res = await downloadTemplate(props.purchaseId)
|
||||
const blob = res as unknown as Blob
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = lyysTemplateDownloadName.value
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(url)
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.message || '下载模板失败')
|
||||
}
|
||||
}
|
||||
if (!props.purchaseId) {
|
||||
ElMessage.warning('采购ID不存在');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await downloadTemplate(props.purchaseId);
|
||||
const blob = res as unknown as Blob;
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = lyysTemplateDownloadName.value;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e?.message || '下载模板失败');
|
||||
}
|
||||
};
|
||||
|
||||
const form = reactive({
|
||||
acceptType: '2', // 固定为上传模式
|
||||
acceptDate: '',
|
||||
templateFileIds: [] as string[],
|
||||
remark: '',
|
||||
})
|
||||
acceptType: '2', // 固定为上传模式
|
||||
acceptDate: '',
|
||||
templateFileIds: [] as string[],
|
||||
remark: '',
|
||||
});
|
||||
|
||||
// 从外部数据初始化(仅在挂载和 modelValue 引用变化时执行)
|
||||
const initData = () => {
|
||||
const val = props.modelValue
|
||||
if (!val) return
|
||||
const val = props.modelValue;
|
||||
if (!val) return;
|
||||
|
||||
form.acceptType = '2'
|
||||
form.acceptDate = val.acceptDate || ''
|
||||
form.remark = val.remark || ''
|
||||
form.acceptType = '2';
|
||||
form.acceptDate = val.acceptDate || '';
|
||||
form.remark = val.remark || '';
|
||||
|
||||
// 处理文件数据:支持 _templateFiles 数组或 templateFileIds
|
||||
if (val._templateFiles && Array.isArray(val._templateFiles)) {
|
||||
templateFiles.value = val._templateFiles.map((f: any) => ({
|
||||
id: f.id,
|
||||
fileTitle: f.fileTitle,
|
||||
name: f.fileTitle || '',
|
||||
url: '',
|
||||
}))
|
||||
form.templateFileIds = val._templateFiles.map((f: any) => f.id)
|
||||
} else if (val.templateFileIds) {
|
||||
if (typeof val.templateFileIds === 'string') {
|
||||
const ids = val.templateFileIds.split(',').filter(Boolean)
|
||||
templateFiles.value = ids.map((id: string) => ({ id: id.trim(), name: '', url: '' }))
|
||||
form.templateFileIds = ids
|
||||
} else if (Array.isArray(val.templateFileIds)) {
|
||||
templateFiles.value = val.templateFileIds.map((item: any) => {
|
||||
if (typeof item === 'string') return { id: item, name: '', url: '' }
|
||||
return { id: item.id, fileTitle: item.fileTitle, name: item.fileTitle || '', url: '' }
|
||||
})
|
||||
form.templateFileIds = val.templateFileIds.map((item: any) => typeof item === 'string' ? item : item.id).filter(Boolean)
|
||||
}
|
||||
} else {
|
||||
templateFiles.value = []
|
||||
form.templateFileIds = []
|
||||
}
|
||||
}
|
||||
// 处理文件数据:支持 _templateFiles 数组或 templateFileIds
|
||||
if (val._templateFiles && Array.isArray(val._templateFiles)) {
|
||||
templateFiles.value = val._templateFiles.map((f: any) => ({
|
||||
id: f.id,
|
||||
fileTitle: f.fileTitle,
|
||||
name: f.fileTitle || '',
|
||||
url: '',
|
||||
}));
|
||||
form.templateFileIds = val._templateFiles.map((f: any) => f.id);
|
||||
} else if (val.templateFileIds) {
|
||||
if (typeof val.templateFileIds === 'string') {
|
||||
const ids = val.templateFileIds.split(',').filter(Boolean);
|
||||
templateFiles.value = ids.map((id: string) => ({ id: id.trim(), name: '', url: '' }));
|
||||
form.templateFileIds = ids;
|
||||
} else if (Array.isArray(val.templateFileIds)) {
|
||||
templateFiles.value = val.templateFileIds.map((item: any) => {
|
||||
if (typeof item === 'string') return { id: item, name: '', url: '' };
|
||||
return { id: item.id, fileTitle: item.fileTitle, name: item.fileTitle || '', url: '' };
|
||||
});
|
||||
form.templateFileIds = val.templateFileIds.map((item: any) => (typeof item === 'string' ? item : item.id)).filter(Boolean);
|
||||
}
|
||||
} else {
|
||||
templateFiles.value = [];
|
||||
form.templateFileIds = [];
|
||||
}
|
||||
};
|
||||
|
||||
// 挂载时初始化
|
||||
onMounted(() => {
|
||||
initData()
|
||||
})
|
||||
initData();
|
||||
});
|
||||
|
||||
// 监听文件变化,更新 form.templateFileIds
|
||||
watch(templateFiles, (files) => {
|
||||
if (Array.isArray(files)) {
|
||||
form.templateFileIds = files.map((f: any) => f.id).filter(Boolean)
|
||||
} else {
|
||||
form.templateFileIds = []
|
||||
}
|
||||
}, { deep: true })
|
||||
watch(
|
||||
templateFiles,
|
||||
(files) => {
|
||||
if (Array.isArray(files)) {
|
||||
form.templateFileIds = files.map((f: any) => f.id).filter(Boolean);
|
||||
} else {
|
||||
form.templateFileIds = [];
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
const rules: FormRules = {
|
||||
templateFileIds: [
|
||||
{
|
||||
validator: (_rule: any, value: any, callback: (e?: Error) => void) => {
|
||||
if (!value || (Array.isArray(value) && value.length === 0) || (typeof value === 'string' && !value.trim())) {
|
||||
callback(new Error('请上传履约验收文件'))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
},
|
||||
trigger: 'change'
|
||||
},
|
||||
],
|
||||
}
|
||||
templateFileIds: [
|
||||
{
|
||||
validator: (_rule: any, value: any, callback: (e?: Error) => void) => {
|
||||
if (!value || (Array.isArray(value) && value.length === 0) || (typeof value === 'string' && !value.trim())) {
|
||||
callback(new Error('请上传履约验收文件'));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
},
|
||||
trigger: 'change',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const validate = () => formRef.value?.validate()
|
||||
const validate = () => formRef.value?.validate();
|
||||
|
||||
// 获取当前表单数据(供父组件调用)
|
||||
const getFormData = () => ({
|
||||
acceptType: form.acceptType,
|
||||
acceptDate: form.acceptDate,
|
||||
templateFileIds: [...form.templateFileIds],
|
||||
remark: form.remark,
|
||||
})
|
||||
acceptType: form.acceptType,
|
||||
acceptDate: form.acceptDate,
|
||||
templateFileIds: [...form.templateFileIds],
|
||||
remark: form.remark,
|
||||
});
|
||||
|
||||
defineExpose({ validate, form, getFormData, initData })
|
||||
defineExpose({ validate, form, getFormData, initData });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mb20 {
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,356 +1,351 @@
|
||||
<template>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="140px">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="8" class="mb20">
|
||||
<el-form-item label="项目名称">
|
||||
<el-input :model-value="projectName || form.projectName" readonly placeholder="-" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb20">
|
||||
<el-form-item label="需求部门">
|
||||
<el-input :model-value="deptName || form.deptName" readonly placeholder="-" disabled/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb20">
|
||||
<el-form-item label="是否签订合同" prop="hasContract">
|
||||
<el-radio-group v-model="form.hasContract">
|
||||
<el-radio label="0">否</el-radio>
|
||||
<el-radio label="1">是</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb20" v-if="form.hasContract === '1'">
|
||||
<el-form-item label="合同" prop="contractId">
|
||||
<el-select
|
||||
v-model="form.contractId"
|
||||
placeholder="请选择合同"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%"
|
||||
:loading="contractLoading"
|
||||
@visible-change="onContractSelectVisibleChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in contractOptions"
|
||||
:key="item.id"
|
||||
:label="item.contractName || item.contractNo || item.id"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb20">
|
||||
<el-form-item label="是否分期验收" prop="isInstallment">
|
||||
<el-radio-group v-model="form.isInstallment">
|
||||
<el-radio label="0">否</el-radio>
|
||||
<el-radio label="1">是</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb20" v-if="form.isInstallment === '1'">
|
||||
<el-form-item label="分期次数" prop="totalPhases">
|
||||
<el-input-number v-model="form.totalPhases" :min="1" :max="99" placeholder="请输入" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb20">
|
||||
<el-form-item label="供应商名称" prop="supplierName">
|
||||
<el-input v-model="form.supplierName" placeholder="选择合同后自动带出" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb20">
|
||||
<el-form-item label="资产管理员" prop="assetAdminId">
|
||||
<el-select
|
||||
v-model="form.assetAdminId"
|
||||
placeholder="请输入姓名或工号搜索"
|
||||
filterable
|
||||
remote
|
||||
clearable
|
||||
reserve-keyword
|
||||
:remote-method="searchAssetAdmin"
|
||||
:loading="assetAdminLoading"
|
||||
style="width: 100%"
|
||||
@change="onAssetAdminChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in assetAdminOptions"
|
||||
:key="item.teacherNo"
|
||||
:label="(item.commonDeptName ? item.commonDeptName + ' - ' : '') + (item.realName || item.name) + ' (' + item.teacherNo + ')'"
|
||||
:value="item.teacherNo"
|
||||
>
|
||||
<span>{{ item.commonDeptName ? item.commonDeptName + ' - ' : '' }}{{ item.realName || item.name }}</span>
|
||||
<span style="color: #999; font-size: 12px; margin-left: 8px;">{{ item.teacherNo }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
<div class="field-note">如入固定资产,必填</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb20">
|
||||
<el-form-item label="采购人员" prop="purchaserId">
|
||||
<el-select
|
||||
v-model="form.purchaserId"
|
||||
placeholder="请输入姓名或工号搜索"
|
||||
filterable
|
||||
remote
|
||||
clearable
|
||||
reserve-keyword
|
||||
:remote-method="searchPurchaser"
|
||||
:loading="purchaserLoading"
|
||||
style="width: 100%"
|
||||
@change="onPurchaserChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in purchaserOptions"
|
||||
:key="item.teacherNo"
|
||||
:label="(item.commonDeptName ? item.commonDeptName + ' - ' : '') + (item.realName || item.name) + ' (' + item.teacherNo + ')'"
|
||||
:value="item.teacherNo"
|
||||
>
|
||||
<span>{{ item.commonDeptName ? item.commonDeptName + ' - ' : '' }}{{ item.realName || item.name }}</span>
|
||||
<span style="color: #999; font-size: 12px; margin-left: 8px;">{{ item.teacherNo }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="140px">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="8" class="mb20">
|
||||
<el-form-item label="项目名称">
|
||||
<el-input :model-value="projectName || form.projectName" readonly placeholder="-" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb20">
|
||||
<el-form-item label="需求部门">
|
||||
<el-input :model-value="deptName || form.deptName" readonly placeholder="-" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb20">
|
||||
<el-form-item label="是否签订合同" prop="hasContract">
|
||||
<el-radio-group v-model="form.hasContract">
|
||||
<el-radio label="0">否</el-radio>
|
||||
<el-radio label="1">是</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb20" v-if="form.hasContract === '1'">
|
||||
<el-form-item label="合同" prop="contractId">
|
||||
<el-select
|
||||
v-model="form.contractId"
|
||||
placeholder="请选择合同"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 100%"
|
||||
:loading="contractLoading"
|
||||
@visible-change="onContractSelectVisibleChange"
|
||||
>
|
||||
<el-option v-for="item in contractOptions" :key="item.id" :label="item.contractName || item.contractNo || item.id" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb20">
|
||||
<el-form-item label="是否分期验收" prop="isInstallment">
|
||||
<el-radio-group v-model="form.isInstallment">
|
||||
<el-radio label="0">否</el-radio>
|
||||
<el-radio label="1">是</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb20" v-if="form.isInstallment === '1'">
|
||||
<el-form-item label="分期次数" prop="totalPhases">
|
||||
<el-input-number v-model="form.totalPhases" :min="1" :max="99" placeholder="请输入" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb20">
|
||||
<el-form-item label="供应商名称" prop="supplierName">
|
||||
<el-input v-model="form.supplierName" placeholder="选择合同后自动带出" clearable />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb20">
|
||||
<el-form-item label="资产管理员" prop="assetAdminId">
|
||||
<el-select
|
||||
v-model="form.assetAdminId"
|
||||
placeholder="请输入姓名或工号搜索"
|
||||
filterable
|
||||
remote
|
||||
clearable
|
||||
reserve-keyword
|
||||
:remote-method="searchAssetAdmin"
|
||||
:loading="assetAdminLoading"
|
||||
style="width: 100%"
|
||||
@change="onAssetAdminChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in assetAdminOptions"
|
||||
:key="item.teacherNo"
|
||||
:label="(item.commonDeptName ? item.commonDeptName + ' - ' : '') + (item.realName || item.name) + ' (' + item.teacherNo + ')'"
|
||||
:value="item.teacherNo"
|
||||
>
|
||||
<span>{{ item.commonDeptName ? item.commonDeptName + ' - ' : '' }}{{ item.realName || item.name }}</span>
|
||||
<span style="color: #999; font-size: 12px; margin-left: 8px">{{ item.teacherNo }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
<div class="field-note">如入固定资产,必填</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8" class="mb20">
|
||||
<el-form-item label="采购人员" prop="purchaserId">
|
||||
<el-select
|
||||
v-model="form.purchaserId"
|
||||
placeholder="请输入姓名或工号搜索"
|
||||
filterable
|
||||
remote
|
||||
clearable
|
||||
reserve-keyword
|
||||
:remote-method="searchPurchaser"
|
||||
:loading="purchaserLoading"
|
||||
style="width: 100%"
|
||||
@change="onPurchaserChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in purchaserOptions"
|
||||
:key="item.teacherNo"
|
||||
:label="(item.commonDeptName ? item.commonDeptName + ' - ' : '') + (item.realName || item.name) + ' (' + item.teacherNo + ')'"
|
||||
:value="item.teacherNo"
|
||||
>
|
||||
<span>{{ item.commonDeptName ? item.commonDeptName + ' - ' : '' }}{{ item.realName || item.name }}</span>
|
||||
<span style="color: #999; font-size: 12px; margin-left: 8px">{{ item.teacherNo }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="8" class="mb20" v-if="form.hasContract === '0'">
|
||||
<el-form-item label="成交金额" prop="transactionAmount" :required="form.hasContract === '0'">
|
||||
<el-input-number
|
||||
v-model="form.transactionAmount"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
placeholder="请输入成交金额"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<el-col :span="8" class="mb20" v-if="form.hasContract === '0'">
|
||||
<el-form-item label="成交金额" prop="transactionAmount" :required="form.hasContract === '0'">
|
||||
<el-input-number v-model="form.transactionAmount" :min="0" :precision="2" placeholder="请输入成交金额" style="width: 100%" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, watch, onMounted } from 'vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { getContracts, searchTeachers } from '/@/api/purchase/purchasingrequisition'
|
||||
import { ref, reactive, watch, onMounted } from 'vue';
|
||||
import type { FormInstance, FormRules } from 'element-plus';
|
||||
import { getContracts, searchTeachers } from '/@/api/purchase/purchasingrequisition';
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: Record<string, any>
|
||||
projectName?: string
|
||||
deptName?: string
|
||||
/** 采购申请ID,用于拉取合同列表 */
|
||||
purchaseId?: string | number
|
||||
/** 每次打开弹窗时变化,用于强制重置内部 form */
|
||||
resetKey?: number
|
||||
}>()
|
||||
modelValue: Record<string, any>;
|
||||
projectName?: string;
|
||||
deptName?: string;
|
||||
/** 采购申请ID,用于拉取合同列表 */
|
||||
purchaseId?: string | number;
|
||||
/** 每次打开弹窗时变化,用于强制重置内部 form */
|
||||
resetKey?: number;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
const contractOptions = ref<any[]>([])
|
||||
const contractLoading = ref(false)
|
||||
const contractLoaded = ref(false)
|
||||
const formRef = ref<FormInstance>();
|
||||
const contractOptions = ref<any[]>([]);
|
||||
const contractLoading = ref(false);
|
||||
const contractLoaded = ref(false);
|
||||
|
||||
// 采购人员相关
|
||||
const purchaserOptions = ref<any[]>([])
|
||||
const purchaserLoading = ref(false)
|
||||
const purchaserOptions = ref<any[]>([]);
|
||||
const purchaserLoading = ref(false);
|
||||
|
||||
// 资产管理员相关
|
||||
const assetAdminOptions = ref<any[]>([])
|
||||
const assetAdminLoading = ref(false)
|
||||
const assetAdminOptions = ref<any[]>([]);
|
||||
const assetAdminLoading = ref(false);
|
||||
|
||||
const form = reactive({
|
||||
hasContract: '0',
|
||||
contractId: '',
|
||||
isInstallment: '0',
|
||||
totalPhases: 1,
|
||||
projectName: '',
|
||||
deptName: '',
|
||||
supplierName: '',
|
||||
purchaserId: '',
|
||||
purchaserName: '',
|
||||
assetAdminId: '',
|
||||
assetAdminName: '',
|
||||
transactionAmount: null,
|
||||
...props.modelValue,
|
||||
})
|
||||
hasContract: '0',
|
||||
contractId: '',
|
||||
isInstallment: '0',
|
||||
totalPhases: 1,
|
||||
projectName: '',
|
||||
deptName: '',
|
||||
supplierName: '',
|
||||
purchaserId: '',
|
||||
purchaserName: '',
|
||||
assetAdminId: '',
|
||||
assetAdminName: '',
|
||||
transactionAmount: null,
|
||||
...props.modelValue,
|
||||
});
|
||||
|
||||
const syncFormFromModel = (val: Record<string, any> | undefined) => {
|
||||
Object.assign(form, val || {})
|
||||
// 加载已选人员信息用于回显
|
||||
if (form.purchaserId && form.purchaserName) {
|
||||
purchaserOptions.value = [{ teacherNo: form.purchaserId, realName: form.purchaserName, name: form.purchaserName }]
|
||||
}
|
||||
if (form.assetAdminId && form.assetAdminName) {
|
||||
assetAdminOptions.value = [{ teacherNo: form.assetAdminId, realName: form.assetAdminName, name: form.assetAdminName }]
|
||||
}
|
||||
}
|
||||
Object.assign(form, val || {});
|
||||
// 加载已选人员信息用于回显
|
||||
if (form.purchaserId && form.purchaserName) {
|
||||
purchaserOptions.value = [{ teacherNo: form.purchaserId, realName: form.purchaserName, name: form.purchaserName }];
|
||||
}
|
||||
if (form.assetAdminId && form.assetAdminName) {
|
||||
assetAdminOptions.value = [{ teacherNo: form.assetAdminId, realName: form.assetAdminName, name: form.assetAdminName }];
|
||||
}
|
||||
};
|
||||
|
||||
const loadContractOptions = async () => {
|
||||
if (contractLoaded.value || contractLoading.value) return
|
||||
if (form.hasContract !== '1') return
|
||||
contractLoading.value = true
|
||||
try {
|
||||
const res = await getContracts(props.purchaseId ? { id: props.purchaseId } : {})
|
||||
const list = res?.data
|
||||
contractOptions.value = Array.isArray(list) ? list : []
|
||||
contractLoaded.value = true
|
||||
// 回显时:列表中含当前合同,用其供应商名称填充(若尚未有值)
|
||||
if (form.contractId) {
|
||||
const c = contractOptions.value.find((it: any) => it.id === form.contractId)
|
||||
if (c?.supplierName) form.supplierName = c.supplierName
|
||||
}
|
||||
} catch (_) {
|
||||
contractOptions.value = []
|
||||
} finally {
|
||||
contractLoading.value = false
|
||||
}
|
||||
}
|
||||
if (contractLoaded.value || contractLoading.value) return;
|
||||
if (form.hasContract !== '1') return;
|
||||
contractLoading.value = true;
|
||||
try {
|
||||
const res = await getContracts(props.purchaseId ? { id: props.purchaseId } : {});
|
||||
const list = res?.data;
|
||||
contractOptions.value = Array.isArray(list) ? list : [];
|
||||
contractLoaded.value = true;
|
||||
// 回显时:列表中含当前合同,用其供应商名称填充(若尚未有值)
|
||||
if (form.contractId) {
|
||||
const c = contractOptions.value.find((it: any) => it.id === form.contractId);
|
||||
if (c?.supplierName) form.supplierName = c.supplierName;
|
||||
}
|
||||
} catch (_) {
|
||||
contractOptions.value = [];
|
||||
} finally {
|
||||
contractLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const onContractSelectVisibleChange = (visible: boolean) => {
|
||||
if (visible && form.hasContract === '1' && contractOptions.value.length === 0) {
|
||||
loadContractOptions()
|
||||
}
|
||||
}
|
||||
if (visible && form.hasContract === '1' && contractOptions.value.length === 0) {
|
||||
loadContractOptions();
|
||||
}
|
||||
};
|
||||
|
||||
const searchPurchaser = async (query: string) => {
|
||||
if (!query) {
|
||||
purchaserOptions.value = []
|
||||
return
|
||||
}
|
||||
purchaserLoading.value = true
|
||||
try {
|
||||
const res = await searchTeachers(query)
|
||||
purchaserOptions.value = res?.data || []
|
||||
} catch (_) {
|
||||
purchaserOptions.value = []
|
||||
} finally {
|
||||
purchaserLoading.value = false
|
||||
}
|
||||
}
|
||||
if (!query) {
|
||||
purchaserOptions.value = [];
|
||||
return;
|
||||
}
|
||||
purchaserLoading.value = true;
|
||||
try {
|
||||
const res = await searchTeachers(query);
|
||||
purchaserOptions.value = res?.data || [];
|
||||
} catch (_) {
|
||||
purchaserOptions.value = [];
|
||||
} finally {
|
||||
purchaserLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const searchAssetAdmin = async (query: string) => {
|
||||
if (!query) {
|
||||
assetAdminOptions.value = []
|
||||
return
|
||||
}
|
||||
assetAdminLoading.value = true
|
||||
try {
|
||||
const res = await searchTeachers(query)
|
||||
assetAdminOptions.value = res?.data || []
|
||||
} catch (_) {
|
||||
assetAdminOptions.value = []
|
||||
} finally {
|
||||
assetAdminLoading.value = false
|
||||
}
|
||||
}
|
||||
if (!query) {
|
||||
assetAdminOptions.value = [];
|
||||
return;
|
||||
}
|
||||
assetAdminLoading.value = true;
|
||||
try {
|
||||
const res = await searchTeachers(query);
|
||||
assetAdminOptions.value = res?.data || [];
|
||||
} catch (_) {
|
||||
assetAdminOptions.value = [];
|
||||
} finally {
|
||||
assetAdminLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const onPurchaserChange = (teacherNo: string) => {
|
||||
if (!teacherNo) {
|
||||
form.purchaserId = ''
|
||||
form.purchaserName = ''
|
||||
return
|
||||
}
|
||||
const selected = purchaserOptions.value.find((item: any) => item.teacherNo === teacherNo)
|
||||
if (selected) {
|
||||
form.purchaserId = selected.teacherNo
|
||||
form.purchaserName = selected.realName || selected.name
|
||||
}
|
||||
}
|
||||
if (!teacherNo) {
|
||||
form.purchaserId = '';
|
||||
form.purchaserName = '';
|
||||
return;
|
||||
}
|
||||
const selected = purchaserOptions.value.find((item: any) => item.teacherNo === teacherNo);
|
||||
if (selected) {
|
||||
form.purchaserId = selected.teacherNo;
|
||||
form.purchaserName = selected.realName || selected.name;
|
||||
}
|
||||
};
|
||||
|
||||
const onAssetAdminChange = (teacherNo: string) => {
|
||||
if (!teacherNo) {
|
||||
form.assetAdminId = ''
|
||||
form.assetAdminName = ''
|
||||
return
|
||||
}
|
||||
const selected = assetAdminOptions.value.find((item: any) => item.teacherNo === teacherNo)
|
||||
if (selected) {
|
||||
form.assetAdminId = selected.teacherNo
|
||||
form.assetAdminName = selected.realName || selected.name
|
||||
}
|
||||
}
|
||||
if (!teacherNo) {
|
||||
form.assetAdminId = '';
|
||||
form.assetAdminName = '';
|
||||
return;
|
||||
}
|
||||
const selected = assetAdminOptions.value.find((item: any) => item.teacherNo === teacherNo);
|
||||
if (selected) {
|
||||
form.assetAdminId = selected.teacherNo;
|
||||
form.assetAdminName = selected.realName || selected.name;
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
syncFormFromModel(val)
|
||||
// 回显:已有合同ID时主动加载合同列表,以便下拉显示合同名称(后端已排除"其他申请"的合同,当前申请合同会在列表中)
|
||||
if (form.hasContract === '1' && form.contractId && props.purchaseId && !contractLoaded.value && !contractLoading.value) {
|
||||
loadContractOptions()
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
)
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
syncFormFromModel(val);
|
||||
// 回显:已有合同ID时主动加载合同列表,以便下拉显示合同名称(后端已排除"其他申请"的合同,当前申请合同会在列表中)
|
||||
if (form.hasContract === '1' && form.contractId && props.purchaseId && !contractLoaded.value && !contractLoading.value) {
|
||||
loadContractOptions();
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
// resetKey 变化时强制用 modelValue 覆盖内部 form,并重置合同列表以便重新拉取
|
||||
watch(() => props.resetKey, () => {
|
||||
syncFormFromModel(props.modelValue)
|
||||
contractLoaded.value = false
|
||||
contractOptions.value = []
|
||||
})
|
||||
watch(form, () => emit('update:modelValue', { ...form }), { deep: true })
|
||||
watch(
|
||||
() => props.resetKey,
|
||||
() => {
|
||||
syncFormFromModel(props.modelValue);
|
||||
contractLoaded.value = false;
|
||||
contractOptions.value = [];
|
||||
}
|
||||
);
|
||||
watch(form, () => emit('update:modelValue', { ...form }), { deep: true });
|
||||
|
||||
watch(() => form.hasContract, (val) => {
|
||||
if (val === '1') {
|
||||
contractLoaded.value = false
|
||||
loadContractOptions()
|
||||
} else {
|
||||
contractOptions.value = []
|
||||
contractLoaded.value = false
|
||||
}
|
||||
// hasContract 变化时触发 transactionAmount 校验
|
||||
formRef.value?.validateField('transactionAmount')
|
||||
})
|
||||
watch(
|
||||
() => form.hasContract,
|
||||
(val) => {
|
||||
if (val === '1') {
|
||||
contractLoaded.value = false;
|
||||
loadContractOptions();
|
||||
} else {
|
||||
contractOptions.value = [];
|
||||
contractLoaded.value = false;
|
||||
}
|
||||
// hasContract 变化时触发 transactionAmount 校验
|
||||
formRef.value?.validateField('transactionAmount');
|
||||
}
|
||||
);
|
||||
|
||||
// 选择合同后,自动带出合同供应商名称
|
||||
watch(
|
||||
() => form.contractId,
|
||||
(val) => {
|
||||
if (!val) {
|
||||
form.supplierName = ''
|
||||
return
|
||||
}
|
||||
const c = contractOptions.value.find((it: any) => it.id === val)
|
||||
if (c && c.supplierName) {
|
||||
form.supplierName = c.supplierName
|
||||
}
|
||||
}
|
||||
)
|
||||
() => form.contractId,
|
||||
(val) => {
|
||||
if (!val) {
|
||||
form.supplierName = '';
|
||||
return;
|
||||
}
|
||||
const c = contractOptions.value.find((it: any) => it.id === val);
|
||||
if (c && c.supplierName) {
|
||||
form.supplierName = c.supplierName;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
if (form.hasContract === '1') {
|
||||
loadContractOptions()
|
||||
}
|
||||
})
|
||||
if (form.hasContract === '1') {
|
||||
loadContractOptions();
|
||||
}
|
||||
});
|
||||
|
||||
const rules: FormRules = {
|
||||
hasContract: [{ required: true, message: '请选择是否签订合同', trigger: 'change' }],
|
||||
isInstallment: [{ required: true, message: '请选择是否分期验收', trigger: 'change' }],
|
||||
totalPhases: [{ required: true, message: '请输入分期次数', trigger: 'blur' }],
|
||||
transactionAmount: [
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
// 未签订合同时,成交金额为必填
|
||||
if (form.hasContract === '0' && (value === null || value === undefined || value === '')) {
|
||||
callback(new Error('未签订合同时,成交金额为必填'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
}
|
||||
hasContract: [{ required: true, message: '请选择是否签订合同', trigger: 'change' }],
|
||||
isInstallment: [{ required: true, message: '请选择是否分期验收', trigger: 'change' }],
|
||||
totalPhases: [{ required: true, message: '请输入分期次数', trigger: 'blur' }],
|
||||
transactionAmount: [
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
// 未签订合同时,成交金额为必填
|
||||
if (form.hasContract === '0' && (value === null || value === undefined || value === '')) {
|
||||
callback(new Error('未签订合同时,成交金额为必填'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const validate = () => formRef.value?.validate()
|
||||
const validate = () => formRef.value?.validate();
|
||||
|
||||
defineExpose({ validate, form })
|
||||
defineExpose({ validate, form });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mb20 {
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.field-note {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-top: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,475 +1,456 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
title="履约验收"
|
||||
width="75%"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
class="purchasing-accept-modal"
|
||||
@close="handleClose"
|
||||
>
|
||||
<div v-loading="loading" class="modal-body" :key="String(purchaseId)">
|
||||
<div class="main-tabs">
|
||||
<div class="main-tab-nav">
|
||||
<div
|
||||
class="main-tab-item"
|
||||
:class="{ active: mainTab === 'common' }"
|
||||
@click="mainTab = 'common'"
|
||||
>
|
||||
公共信息
|
||||
</div>
|
||||
<div
|
||||
class="main-tab-item"
|
||||
:class="{ active: mainTab === 'batch' }"
|
||||
@click="mainTab = 'batch'"
|
||||
>
|
||||
{{ commonForm?.isInstallment === '0' ? '验收' : '分期验收' }}{{ commonForm?.isInstallment !== '0' && batches.length > 0 ? ` (${batches.length})` : '' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="main-tab-content">
|
||||
<div v-show="mainTab === 'common'" class="tab-content">
|
||||
<AcceptCommonForm
|
||||
:key="`${purchaseId}-${openToken}`"
|
||||
:reset-key="openToken"
|
||||
ref="commonFormRef"
|
||||
v-model="commonForm"
|
||||
:purchase-id="purchaseId"
|
||||
:project-name="applyInfo?.projectName"
|
||||
:dept-name="applyInfo?.deptName"
|
||||
/>
|
||||
</div>
|
||||
<div v-show="mainTab === 'batch'" class="tab-content">
|
||||
<div v-if="batches.length > 0">
|
||||
<div v-show="commonForm?.isInstallment !== '0'" class="batch-tabs">
|
||||
<div
|
||||
v-for="b in batches"
|
||||
:key="b.id"
|
||||
class="batch-tab-item"
|
||||
:class="{ active: String(b.batch) === activeTab, disabled: !canEditBatch(b.batch) }"
|
||||
@click="canEditBatch(b.batch) && (activeTab = String(b.batch))"
|
||||
>
|
||||
<span>第{{ b.batch }}期</span>
|
||||
<el-tag v-if="isBatchCompleted(b)" type="success" size="small">已填</el-tag>
|
||||
<el-tag v-else-if="!canEditBatch(b.batch)" type="info" size="small">需先完成上一期</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="batch-panel">
|
||||
<AcceptBatchForm
|
||||
v-for="b in batches"
|
||||
v-show="String(b.batch) === activeTab"
|
||||
:key="b.id"
|
||||
:ref="(el) => setBatchFormRef(b.batch, el)"
|
||||
:model-value="batchForms[b.batch]"
|
||||
:readonly="false"
|
||||
:purchase-id="String(purchaseId)"
|
||||
:project-type="acceptProjectType"
|
||||
:budget="applyInfo?.budget"
|
||||
:batch-num="b.batch"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="tip-box">
|
||||
<el-alert type="info" :closable="false" show-icon>
|
||||
请先在「公共信息」中填写并点击「保存公共配置」,系统将按分期次数自动生成验收批次
|
||||
</el-alert>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
title="履约验收"
|
||||
width="75%"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
class="purchasing-accept-modal"
|
||||
@close="handleClose"
|
||||
>
|
||||
<div v-loading="loading" class="modal-body" :key="String(purchaseId)">
|
||||
<div class="main-tabs">
|
||||
<div class="main-tab-nav">
|
||||
<div class="main-tab-item" :class="{ active: mainTab === 'common' }" @click="mainTab = 'common'">公共信息</div>
|
||||
<div class="main-tab-item" :class="{ active: mainTab === 'batch' }" @click="mainTab = 'batch'">
|
||||
{{ commonForm?.isInstallment === '0' ? '验收' : '分期验收'
|
||||
}}{{ commonForm?.isInstallment !== '0' && batches.length > 0 ? ` (${batches.length})` : '' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="main-tab-content">
|
||||
<div v-show="mainTab === 'common'" class="tab-content">
|
||||
<AcceptCommonForm
|
||||
:key="`${purchaseId}-${openToken}`"
|
||||
:reset-key="openToken"
|
||||
ref="commonFormRef"
|
||||
v-model="commonForm"
|
||||
:purchase-id="purchaseId"
|
||||
:project-name="applyInfo?.projectName"
|
||||
:dept-name="applyInfo?.deptName"
|
||||
/>
|
||||
</div>
|
||||
<div v-show="mainTab === 'batch'" class="tab-content">
|
||||
<div v-if="batches.length > 0">
|
||||
<div v-show="commonForm?.isInstallment !== '0'" class="batch-tabs">
|
||||
<div
|
||||
v-for="b in batches"
|
||||
:key="b.id"
|
||||
class="batch-tab-item"
|
||||
:class="{ active: String(b.batch) === activeTab, disabled: !canEditBatch(b.batch) }"
|
||||
@click="canEditBatch(b.batch) && (activeTab = String(b.batch))"
|
||||
>
|
||||
<span>第{{ b.batch }}期</span>
|
||||
<el-tag v-if="isBatchCompleted(b)" type="success" size="small">已填</el-tag>
|
||||
<el-tag v-else-if="!canEditBatch(b.batch)" type="info" size="small">需先完成上一期</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="batch-panel">
|
||||
<AcceptBatchForm
|
||||
v-for="b in batches"
|
||||
v-show="String(b.batch) === activeTab"
|
||||
:key="b.id"
|
||||
:ref="(el) => setBatchFormRef(b.batch, el)"
|
||||
:model-value="batchForms[b.batch]"
|
||||
:readonly="false"
|
||||
:purchase-id="String(purchaseId)"
|
||||
:project-type="acceptProjectType"
|
||||
:budget="applyInfo?.budget"
|
||||
:batch-num="b.batch"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="tip-box">
|
||||
<el-alert type="info" :closable="false" show-icon>
|
||||
请先在「公共信息」中填写并点击「保存公共配置」,系统将按分期次数自动生成验收批次
|
||||
</el-alert>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<span>
|
||||
<el-button @click="handleClose">关 闭</el-button>
|
||||
<el-button
|
||||
v-if="mainTab === 'common' || batches.length === 0"
|
||||
type="primary"
|
||||
@click="saveCommonConfig"
|
||||
:loading="saving"
|
||||
>
|
||||
保存公共配置
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else-if="mainTab === 'batch' && activeBatchId"
|
||||
type="primary"
|
||||
@click="saveCurrentBatch"
|
||||
:loading="saving"
|
||||
>
|
||||
保存第{{ activeTab }}期
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<template #footer>
|
||||
<span>
|
||||
<el-button @click="handleClose">关 闭</el-button>
|
||||
<el-button v-if="mainTab === 'common' || batches.length === 0" type="primary" @click="saveCommonConfig" :loading="saving">
|
||||
保存公共配置
|
||||
</el-button>
|
||||
<el-button v-else-if="mainTab === 'batch' && activeBatchId" type="primary" @click="saveCurrentBatch" :loading="saving">
|
||||
保存第{{ activeTab }}期
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, nextTick } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import {
|
||||
saveCommonConfig as apiSaveCommonConfig,
|
||||
getCommonConfigWithBatches,
|
||||
updateBatch,
|
||||
getDetail,
|
||||
} from '/@/api/purchase/purchasingAccept'
|
||||
import AcceptCommonForm from './AcceptCommonForm.vue'
|
||||
import AcceptBatchForm from './AcceptBatchForm.vue'
|
||||
import { ref, reactive, computed, nextTick } from 'vue';
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
import { saveCommonConfig as apiSaveCommonConfig, getCommonConfigWithBatches, updateBatch, getDetail } from '/@/api/purchase/purchasingAccept';
|
||||
import AcceptCommonForm from './AcceptCommonForm.vue';
|
||||
import AcceptBatchForm from './AcceptBatchForm.vue';
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
const emit = defineEmits(['refresh']);
|
||||
|
||||
const visible = ref(false)
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const purchaseId = ref<string | number>('')
|
||||
const applyInfo = ref<any>(null)
|
||||
const rowProjectType = ref<string>('A')
|
||||
const batches = ref<any[]>([])
|
||||
const mainTab = ref('common')
|
||||
const activeTab = ref('1')
|
||||
const commonFormRef = ref()
|
||||
const batchFormRefMap = ref<Record<number, any>>({})
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
const purchaseId = ref<string | number>('');
|
||||
const applyInfo = ref<any>(null);
|
||||
const rowProjectType = ref<string>('A');
|
||||
const batches = ref<any[]>([]);
|
||||
const mainTab = ref('common');
|
||||
const activeTab = ref('1');
|
||||
const commonFormRef = ref();
|
||||
const batchFormRefMap = ref<Record<number, any>>({});
|
||||
/** 使用 ref 并在每次打开时替换整个对象,确保子组件能感知引用变化并清空 */
|
||||
const commonForm = ref<Record<string, any>>({})
|
||||
const commonForm = ref<Record<string, any>>({});
|
||||
/** 每次打开自增,用于强制 AcceptCommonForm 重新挂载,确保公共信息彻底清空 */
|
||||
const openToken = ref(0)
|
||||
const batchForms = reactive<Record<number, any>>({})
|
||||
const openToken = ref(0);
|
||||
const batchForms = reactive<Record<number, any>>({});
|
||||
/** 记录哪些期已保存到服务器,用于控制”下一期可填”:只有上一期已保存才允许填下一期 */
|
||||
const batchSavedFlags = ref<Record<number, boolean>>({})
|
||||
const batchSavedFlags = ref<Record<number, boolean>>({});
|
||||
|
||||
const setBatchFormRef = (batch: number, el: any) => {
|
||||
if (el) batchFormRefMap.value[batch] = el
|
||||
}
|
||||
if (el) batchFormRefMap.value[batch] = el;
|
||||
};
|
||||
|
||||
const activeBatchId = computed(() => {
|
||||
const b = batches.value.find((x: any) => String(x.batch) === activeTab.value)
|
||||
return b?.id || ''
|
||||
})
|
||||
const b = batches.value.find((x: any) => String(x.batch) === activeTab.value);
|
||||
return b?.id || '';
|
||||
});
|
||||
|
||||
/** 项目类型 A:货物 B:工程 C:服务,用于批次表单模版下载 */
|
||||
const acceptProjectType = computed(() => applyInfo.value?.projectType || rowProjectType.value || 'A')
|
||||
const acceptProjectType = computed(() => applyInfo.value?.projectType || rowProjectType.value || 'A');
|
||||
|
||||
/** 是否允许编辑该期:第 1 期始终可编辑;第 N 期仅当第 1~N-1 期均已保存后才可编辑 */
|
||||
const canEditBatch = (batch: number) => {
|
||||
if (batch === 1) return true
|
||||
for (let i = 1; i < batch; i++) {
|
||||
if (!batchSavedFlags.value[i]) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
if (batch === 1) return true;
|
||||
for (let i = 1; i < batch; i++) {
|
||||
if (!batchSavedFlags.value[i]) return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/** 该期是否已保存(用于 tab 上显示“已填”标签) */
|
||||
const isBatchCompleted = (b: any) => {
|
||||
return !!batchSavedFlags.value[b.batch]
|
||||
}
|
||||
return !!batchSavedFlags.value[b.batch];
|
||||
};
|
||||
|
||||
const isBatchCompletedByIdx = (batch: number) => {
|
||||
return !!batchSavedFlags.value[batch]
|
||||
}
|
||||
return !!batchSavedFlags.value[batch];
|
||||
};
|
||||
|
||||
const loadData = async () => {
|
||||
if (!purchaseId.value) return
|
||||
const currentId = String(purchaseId.value)
|
||||
loading.value = true
|
||||
try {
|
||||
const configRes = await getCommonConfigWithBatches(currentId)
|
||||
// 防止快速切换:若已打开其他申请单,忽略本次结果
|
||||
if (String(purchaseId.value) !== currentId) return
|
||||
if (!purchaseId.value) return;
|
||||
const currentId = String(purchaseId.value);
|
||||
loading.value = true;
|
||||
try {
|
||||
const configRes = await getCommonConfigWithBatches(currentId);
|
||||
// 防止快速切换:若已打开其他申请单,忽略本次结果
|
||||
if (String(purchaseId.value) !== currentId) return;
|
||||
|
||||
const config = configRes?.data
|
||||
const config = configRes?.data;
|
||||
|
||||
if (config?.common) {
|
||||
applyInfo.value = config.common
|
||||
// 采购人员和资产管理员始终回填
|
||||
commonForm.value.purchaserId = config.common.purchaserId || ''
|
||||
commonForm.value.purchaserName = config.common.purchaserName || ''
|
||||
commonForm.value.assetAdminId = config.common.assetAdminId || ''
|
||||
commonForm.value.assetAdminName = config.common.assetAdminName || ''
|
||||
|
||||
// 其他字段仅当存在已保存批次时回填
|
||||
if (config?.batches?.length) {
|
||||
Object.assign(commonForm.value, {
|
||||
hasContract: config.common.hasContract || '0',
|
||||
contractId: config.common.contractId || '',
|
||||
isInstallment: config.common.isInstallment || '0',
|
||||
totalPhases: config.common.totalPhases || 1,
|
||||
supplierName: config.common.supplierName || '',
|
||||
transactionAmount: config.common.transactionAmount || null,
|
||||
})
|
||||
}
|
||||
}
|
||||
if (config?.common) {
|
||||
applyInfo.value = config.common;
|
||||
// 采购人员和资产管理员始终回填
|
||||
commonForm.value.purchaserId = config.common.purchaserId || '';
|
||||
commonForm.value.purchaserName = config.common.purchaserName || '';
|
||||
commonForm.value.assetAdminId = config.common.assetAdminId || '';
|
||||
commonForm.value.assetAdminName = config.common.assetAdminName || '';
|
||||
|
||||
if (config?.batches?.length) {
|
||||
batches.value = config.batches.sort((a: any, b: any) => (a.batch || 0) - (b.batch || 0))
|
||||
activeTab.value = String(batches.value[0]?.batch || '1')
|
||||
mainTab.value = 'batch'
|
||||
for (const b of batches.value) {
|
||||
if (!batchForms[b.batch]) batchForms[b.batch] = {}
|
||||
}
|
||||
await loadBatchDetails()
|
||||
if (String(purchaseId.value) !== currentId) return
|
||||
} else {
|
||||
batches.value = []
|
||||
}
|
||||
} catch (e: any) {
|
||||
useMessage().error(e?.msg || '加载失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
// 其他字段仅当存在已保存批次时回填
|
||||
if (config?.batches?.length) {
|
||||
Object.assign(commonForm.value, {
|
||||
hasContract: config.common.hasContract || '0',
|
||||
contractId: config.common.contractId || '',
|
||||
isInstallment: config.common.isInstallment || '0',
|
||||
totalPhases: config.common.totalPhases || 1,
|
||||
supplierName: config.common.supplierName || '',
|
||||
transactionAmount: config.common.transactionAmount || null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (config?.batches?.length) {
|
||||
batches.value = config.batches.sort((a: any, b: any) => (a.batch || 0) - (b.batch || 0));
|
||||
activeTab.value = String(batches.value[0]?.batch || '1');
|
||||
mainTab.value = 'batch';
|
||||
for (const b of batches.value) {
|
||||
if (!batchForms[b.batch]) batchForms[b.batch] = {};
|
||||
}
|
||||
await loadBatchDetails();
|
||||
if (String(purchaseId.value) !== currentId) return;
|
||||
} else {
|
||||
batches.value = [];
|
||||
}
|
||||
} catch (e: any) {
|
||||
useMessage().error(e?.msg || '加载失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const loadBatchDetails = async () => {
|
||||
for (const b of batches.value) {
|
||||
batchSavedFlags.value[b.batch] = false
|
||||
}
|
||||
for (const b of batches.value) {
|
||||
try {
|
||||
const res = await getDetail(String(purchaseId.value), b.batch)
|
||||
const d = res?.data
|
||||
if (d?.accept) {
|
||||
// 仅当该期在服务端有验收日期时才视为已保存
|
||||
const hasSaved = !!d.accept.acceptDate
|
||||
batchSavedFlags.value[b.batch] = hasSaved
|
||||
// 优先使用 templateFiles(包含id和fileTitle),否则降级使用 templateFileIds
|
||||
let fileIdsStr = ''
|
||||
if (d.accept.templateFiles && d.accept.templateFiles.length > 0) {
|
||||
// 使用 templateFiles,格式为 {id: string, fileTitle: string}[]
|
||||
fileIdsStr = d.accept.templateFiles.map((f: any) => f.id).join(',')
|
||||
} else if (d.accept.templateFileIds) {
|
||||
// 降级使用 templateFileIds
|
||||
const fileIds = d.accept.templateFileIds
|
||||
fileIdsStr = Array.isArray(fileIds) ? fileIds.join(',') : (fileIds || '')
|
||||
}
|
||||
batchForms[b.batch] = {
|
||||
acceptType: '2', // 固定为上传模式
|
||||
acceptDate: d.accept.acceptDate || '',
|
||||
remark: d.accept.remark || '',
|
||||
templateFileIds: fileIdsStr,
|
||||
// 保存文件信息用于显示
|
||||
_templateFiles: d.accept.templateFiles || [],
|
||||
}
|
||||
// 通知子组件初始化数据
|
||||
await nextTick()
|
||||
const batchFormRef = batchFormRefMap.value[b.batch]
|
||||
if (batchFormRef?.initData) {
|
||||
batchFormRef.initData()
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
for (const b of batches.value) {
|
||||
batchSavedFlags.value[b.batch] = false;
|
||||
}
|
||||
for (const b of batches.value) {
|
||||
try {
|
||||
const res = await getDetail(String(purchaseId.value), b.batch);
|
||||
const d = res?.data;
|
||||
if (d?.accept) {
|
||||
// 仅当该期在服务端有验收日期时才视为已保存
|
||||
const hasSaved = !!d.accept.acceptDate;
|
||||
batchSavedFlags.value[b.batch] = hasSaved;
|
||||
// 优先使用 templateFiles(包含id和fileTitle),否则降级使用 templateFileIds
|
||||
let fileIdsStr = '';
|
||||
if (d.accept.templateFiles && d.accept.templateFiles.length > 0) {
|
||||
// 使用 templateFiles,格式为 {id: string, fileTitle: string}[]
|
||||
fileIdsStr = d.accept.templateFiles.map((f: any) => f.id).join(',');
|
||||
} else if (d.accept.templateFileIds) {
|
||||
// 降级使用 templateFileIds
|
||||
const fileIds = d.accept.templateFileIds;
|
||||
fileIdsStr = Array.isArray(fileIds) ? fileIds.join(',') : fileIds || '';
|
||||
}
|
||||
batchForms[b.batch] = {
|
||||
acceptType: '2', // 固定为上传模式
|
||||
acceptDate: d.accept.acceptDate || '',
|
||||
remark: d.accept.remark || '',
|
||||
templateFileIds: fileIdsStr,
|
||||
// 保存文件信息用于显示
|
||||
_templateFiles: d.accept.templateFiles || [],
|
||||
};
|
||||
// 通知子组件初始化数据
|
||||
await nextTick();
|
||||
const batchFormRef = batchFormRefMap.value[b.batch];
|
||||
if (batchFormRef?.initData) {
|
||||
batchFormRef.initData();
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
};
|
||||
|
||||
const saveCommonConfig = async () => {
|
||||
const formRef = commonFormRef.value
|
||||
const valid = await formRef?.validate?.().catch(() => false)
|
||||
if (!valid) return
|
||||
// 直接从子组件 form 读取,确保拿到用户填写的最新值(避免 v-model 同步延迟)
|
||||
const form = formRef?.form || commonForm.value
|
||||
const isInstallment = form.isInstallment === '1' || form.isInstallment === 1
|
||||
if (isInstallment && (!form.totalPhases || form.totalPhases < 1)) {
|
||||
useMessage().error('请填写分期次数')
|
||||
return
|
||||
}
|
||||
saving.value = true
|
||||
try {
|
||||
await apiSaveCommonConfig({
|
||||
purchaseId: String(purchaseId.value),
|
||||
hasContract: form.hasContract ?? '0',
|
||||
contractId: form.contractId ?? '',
|
||||
isInstallment: form.isInstallment ?? '0',
|
||||
totalPhases: isInstallment ? (Number(form.totalPhases) || 1) : 1,
|
||||
supplierName: String(form.supplierName ?? ''),
|
||||
purchaserId: String(form.purchaserId ?? ''),
|
||||
purchaserName: String(form.purchaserName ?? ''),
|
||||
assetAdminId: String(form.assetAdminId ?? ''),
|
||||
assetAdminName: String(form.assetAdminName ?? ''),
|
||||
transactionAmount: form.transactionAmount ?? null,
|
||||
})
|
||||
useMessage().success('保存成功')
|
||||
await loadData()
|
||||
} catch (e: any) {
|
||||
useMessage().error(e?.msg || '保存失败')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
const formRef = commonFormRef.value;
|
||||
const valid = await formRef?.validate?.().catch(() => false);
|
||||
if (!valid) return;
|
||||
// 直接从子组件 form 读取,确保拿到用户填写的最新值(避免 v-model 同步延迟)
|
||||
const form = formRef?.form || commonForm.value;
|
||||
const isInstallment = form.isInstallment === '1' || form.isInstallment === 1;
|
||||
if (isInstallment && (!form.totalPhases || form.totalPhases < 1)) {
|
||||
useMessage().error('请填写分期次数');
|
||||
return;
|
||||
}
|
||||
saving.value = true;
|
||||
try {
|
||||
await apiSaveCommonConfig({
|
||||
purchaseId: String(purchaseId.value),
|
||||
hasContract: form.hasContract ?? '0',
|
||||
contractId: form.contractId ?? '',
|
||||
isInstallment: form.isInstallment ?? '0',
|
||||
totalPhases: isInstallment ? Number(form.totalPhases) || 1 : 1,
|
||||
supplierName: String(form.supplierName ?? ''),
|
||||
purchaserId: String(form.purchaserId ?? ''),
|
||||
purchaserName: String(form.purchaserName ?? ''),
|
||||
assetAdminId: String(form.assetAdminId ?? ''),
|
||||
assetAdminName: String(form.assetAdminName ?? ''),
|
||||
transactionAmount: form.transactionAmount ?? null,
|
||||
});
|
||||
useMessage().success('保存成功');
|
||||
await loadData();
|
||||
} catch (e: any) {
|
||||
useMessage().error(e?.msg || '保存失败');
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const saveCurrentBatch = async () => {
|
||||
const curBatch = Number(activeTab.value)
|
||||
const batchFormRef = batchFormRefMap.value[curBatch]
|
||||
const valid = await batchFormRef?.validate?.().catch(() => false)
|
||||
if (!valid) return
|
||||
const curBatch = Number(activeTab.value);
|
||||
const batchFormRef = batchFormRefMap.value[curBatch];
|
||||
const valid = await batchFormRef?.validate?.().catch(() => false);
|
||||
if (!valid) return;
|
||||
|
||||
const b = batches.value.find((x: any) => String(x.batch) === activeTab.value)
|
||||
if (!b?.id) return
|
||||
const b = batches.value.find((x: any) => String(x.batch) === activeTab.value);
|
||||
if (!b?.id) return;
|
||||
|
||||
// 从子组件获取表单数据
|
||||
const formData = batchFormRef?.getFormData?.() || batchFormRef?.form
|
||||
if (!formData) return
|
||||
// 从子组件获取表单数据
|
||||
const formData = batchFormRef?.getFormData?.() || batchFormRef?.form;
|
||||
if (!formData) return;
|
||||
|
||||
// acceptDate is now optional - removed the validation check
|
||||
// acceptDate is now optional - removed the validation check
|
||||
|
||||
// templateFileIds: 提取ID数组
|
||||
let fileIds: string[] = []
|
||||
if (formData.templateFileIds) {
|
||||
if (Array.isArray(formData.templateFileIds)) {
|
||||
fileIds = formData.templateFileIds.map((item: any) => {
|
||||
if (typeof item === 'string') return item
|
||||
if (item && item.id) return item.id
|
||||
return null
|
||||
}).filter(Boolean)
|
||||
} else if (typeof formData.templateFileIds === 'string') {
|
||||
fileIds = formData.templateFileIds.split(',').map((s: string) => s.trim()).filter(Boolean)
|
||||
}
|
||||
}
|
||||
// templateFileIds: 提取ID数组
|
||||
let fileIds: string[] = [];
|
||||
if (formData.templateFileIds) {
|
||||
if (Array.isArray(formData.templateFileIds)) {
|
||||
fileIds = formData.templateFileIds
|
||||
.map((item: any) => {
|
||||
if (typeof item === 'string') return item;
|
||||
if (item && item.id) return item.id;
|
||||
return null;
|
||||
})
|
||||
.filter(Boolean);
|
||||
} else if (typeof formData.templateFileIds === 'string') {
|
||||
fileIds = formData.templateFileIds
|
||||
.split(',')
|
||||
.map((s: string) => s.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
}
|
||||
|
||||
saving.value = true
|
||||
try {
|
||||
await updateBatch({
|
||||
id: b.id,
|
||||
purchaseId: String(purchaseId.value),
|
||||
acceptType: '2', // 固定为上传模式
|
||||
acceptDate: formData.acceptDate,
|
||||
remark: formData.remark,
|
||||
templateFileIds: fileIds,
|
||||
})
|
||||
useMessage().success('保存成功')
|
||||
batchSavedFlags.value[curBatch] = true
|
||||
await loadData()
|
||||
} catch (e: any) {
|
||||
useMessage().error(e?.msg || '保存失败')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
saving.value = true;
|
||||
try {
|
||||
await updateBatch({
|
||||
id: b.id,
|
||||
purchaseId: String(purchaseId.value),
|
||||
acceptType: '2', // 固定为上传模式
|
||||
acceptDate: formData.acceptDate,
|
||||
remark: formData.remark,
|
||||
templateFileIds: fileIds,
|
||||
});
|
||||
useMessage().success('保存成功');
|
||||
batchSavedFlags.value[curBatch] = true;
|
||||
await loadData();
|
||||
} catch (e: any) {
|
||||
useMessage().error(e?.msg || '保存失败');
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
visible.value = false
|
||||
emit('refresh')
|
||||
}
|
||||
visible.value = false;
|
||||
emit('refresh');
|
||||
};
|
||||
|
||||
const DEFAULT_COMMON_FORM = {
|
||||
hasContract: '0',
|
||||
contractId: '',
|
||||
isInstallment: '0',
|
||||
totalPhases: 1,
|
||||
supplierName: '',
|
||||
purchaserId: '',
|
||||
purchaserName: '',
|
||||
assetAdminId: '',
|
||||
assetAdminName: '',
|
||||
transactionAmount: null,
|
||||
}
|
||||
hasContract: '0',
|
||||
contractId: '',
|
||||
isInstallment: '0',
|
||||
totalPhases: 1,
|
||||
supplierName: '',
|
||||
purchaserId: '',
|
||||
purchaserName: '',
|
||||
assetAdminId: '',
|
||||
assetAdminName: '',
|
||||
transactionAmount: null,
|
||||
};
|
||||
|
||||
/** 将弹窗内所有内容恢复为初始空值(替换整个对象以确保引用变化) */
|
||||
const resetAllToDefault = () => {
|
||||
openToken.value++
|
||||
commonForm.value = { ...DEFAULT_COMMON_FORM }
|
||||
applyInfo.value = null
|
||||
mainTab.value = 'common'
|
||||
activeTab.value = '1'
|
||||
batchFormRefMap.value = {}
|
||||
batches.value = []
|
||||
Object.keys(batchForms).forEach((k) => delete batchForms[Number(k)])
|
||||
batchSavedFlags.value = {}
|
||||
}
|
||||
openToken.value++;
|
||||
commonForm.value = { ...DEFAULT_COMMON_FORM };
|
||||
applyInfo.value = null;
|
||||
mainTab.value = 'common';
|
||||
activeTab.value = '1';
|
||||
batchFormRefMap.value = {};
|
||||
batches.value = [];
|
||||
Object.keys(batchForms).forEach((k) => delete batchForms[Number(k)]);
|
||||
batchSavedFlags.value = {};
|
||||
};
|
||||
|
||||
const open = async (row: any) => {
|
||||
purchaseId.value = row?.id ?? ''
|
||||
rowProjectType.value = row?.projectType || 'A'
|
||||
purchaseId.value = row?.id ?? '';
|
||||
rowProjectType.value = row?.projectType || 'A';
|
||||
|
||||
// 1. 先将弹窗内所有内容恢复为初始空值
|
||||
resetAllToDefault()
|
||||
// 1. 先将弹窗内所有内容恢复为初始空值
|
||||
resetAllToDefault();
|
||||
|
||||
// 2. 显示弹窗并开启 loading,避免接口返回前展示旧数据
|
||||
visible.value = true
|
||||
loading.value = true
|
||||
// 2. 显示弹窗并开启 loading,避免接口返回前展示旧数据
|
||||
visible.value = true;
|
||||
loading.value = true;
|
||||
|
||||
// 3. 等待 Vue 完成渲染,确保子组件已接收并展示空值
|
||||
await nextTick()
|
||||
await nextTick()
|
||||
// 3. 等待 Vue 完成渲染,确保子组件已接收并展示空值
|
||||
await nextTick();
|
||||
await nextTick();
|
||||
|
||||
// 4. 再进行接口查询并覆盖
|
||||
await loadData()
|
||||
}
|
||||
// 4. 再进行接口查询并覆盖
|
||||
await loadData();
|
||||
};
|
||||
|
||||
defineExpose({ open })
|
||||
defineExpose({ open });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal-body {
|
||||
padding: 0;
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 0;
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.main-tab-nav {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
margin-bottom: 16px;
|
||||
border-bottom: 1px solid var(--el-border-color);
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
margin-bottom: 16px;
|
||||
border-bottom: 1px solid var(--el-border-color);
|
||||
}
|
||||
.main-tab-item {
|
||||
padding: 12px 20px;
|
||||
cursor: pointer;
|
||||
color: var(--el-text-color-regular);
|
||||
border-bottom: 2px solid transparent;
|
||||
margin-bottom: -1px;
|
||||
transition: all 0.2s;
|
||||
padding: 12px 20px;
|
||||
cursor: pointer;
|
||||
color: var(--el-text-color-regular);
|
||||
border-bottom: 2px solid transparent;
|
||||
margin-bottom: -1px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.main-tab-item:hover {
|
||||
color: var(--el-color-primary);
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
.main-tab-item.active {
|
||||
color: var(--el-color-primary);
|
||||
font-weight: 600;
|
||||
border-bottom-color: var(--el-color-primary);
|
||||
color: var(--el-color-primary);
|
||||
font-weight: 600;
|
||||
border-bottom-color: var(--el-color-primary);
|
||||
}
|
||||
.main-tab-content {
|
||||
padding-top: 4px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
.tab-content {
|
||||
min-height: 200px;
|
||||
display: block;
|
||||
min-height: 200px;
|
||||
display: block;
|
||||
}
|
||||
.tip-box {
|
||||
padding: 20px 0;
|
||||
padding: 20px 0;
|
||||
}
|
||||
.batch-tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
flex-wrap: wrap;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.batch-tab-item {
|
||||
padding: 8px 16px;
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
transition: all 0.2s;
|
||||
padding: 8px 16px;
|
||||
border: 1px solid var(--el-border-color);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.batch-tab-item:hover:not(.disabled) {
|
||||
border-color: var(--el-color-primary);
|
||||
color: var(--el-color-primary);
|
||||
border-color: var(--el-color-primary);
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
.batch-tab-item.active {
|
||||
background: var(--el-color-primary);
|
||||
border-color: var(--el-color-primary);
|
||||
color: #fff;
|
||||
background: var(--el-color-primary);
|
||||
border-color: var(--el-color-primary);
|
||||
color: #fff;
|
||||
}
|
||||
.batch-tab-item.disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
.batch-panel {
|
||||
min-height: 200px;
|
||||
min-height: 200px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 弹窗横向滚动修复,需非 scoped 以影响 el-dialog */
|
||||
.purchasing-accept-modal .el-dialog__body {
|
||||
overflow-x: hidden;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,229 +1,206 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="state.visible"
|
||||
:title="state.title"
|
||||
width="700px"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
@close="handleClose">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="state.formData"
|
||||
:rules="rules"
|
||||
label-width="140px">
|
||||
<el-form-item label="合同编号" prop="contractNo">
|
||||
<el-input
|
||||
v-model="state.formData.contractNo"
|
||||
placeholder="请输入合同编号"
|
||||
:disabled="state.operation === 'view'" />
|
||||
</el-form-item>
|
||||
<el-form-item label="合同名称" prop="contractName">
|
||||
<el-input
|
||||
v-model="state.formData.contractName"
|
||||
placeholder="请输入合同名称"
|
||||
:disabled="state.operation === 'view'" />
|
||||
</el-form-item>
|
||||
<el-form-item label="合同金额(元)" prop="money">
|
||||
<el-input-number
|
||||
v-model="state.formData.money"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
placeholder="请输入合同金额"
|
||||
style="width: 100%"
|
||||
:disabled="state.operation === 'view'" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否需要招标" prop="isBidding">
|
||||
<el-radio-group
|
||||
v-model="state.formData.isBidding"
|
||||
:disabled="state.operation === 'view'">
|
||||
<el-radio value="0">否</el-radio>
|
||||
<el-radio value="1">是</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否需要法律顾问" prop="isLegalAdviser">
|
||||
<el-radio-group
|
||||
v-model="state.formData.isLegalAdviser"
|
||||
:disabled="state.operation === 'view'">
|
||||
<el-radio value="0">否</el-radio>
|
||||
<el-radio value="1">是</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="state.formData.isLegalAdviser === '1'"
|
||||
label="法律顾问意见"
|
||||
prop="legalAdviserOpinion">
|
||||
<el-input
|
||||
v-model="state.formData.legalAdviserOpinion"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入法律顾问意见"
|
||||
:disabled="state.operation === 'view'" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否涉及多个部门" prop="isDepts">
|
||||
<el-radio-group
|
||||
v-model="state.formData.isDepts"
|
||||
:disabled="state.operation === 'view'">
|
||||
<el-radio value="0">否</el-radio>
|
||||
<el-radio value="1">是</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否全校合同" prop="isSchool">
|
||||
<el-radio-group
|
||||
v-model="state.formData.isSchool"
|
||||
:disabled="state.operation === 'view'">
|
||||
<el-radio value="0">否</el-radio>
|
||||
<el-radio value="1">是</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remarks">
|
||||
<el-input
|
||||
v-model="state.formData.remarks"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="请输入备注"
|
||||
:disabled="state.operation === 'view'" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer v-if="state.operation !== 'view'">
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit" :loading="state.loading">确定</el-button>
|
||||
</template>
|
||||
<template #footer v-else>
|
||||
<el-button @click="handleClose">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog
|
||||
v-model="state.visible"
|
||||
:title="state.title"
|
||||
width="700px"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
@close="handleClose"
|
||||
>
|
||||
<el-form ref="formRef" :model="state.formData" :rules="rules" label-width="140px">
|
||||
<el-form-item label="合同编号" prop="contractNo">
|
||||
<el-input v-model="state.formData.contractNo" placeholder="请输入合同编号" :disabled="state.operation === 'view'" />
|
||||
</el-form-item>
|
||||
<el-form-item label="合同名称" prop="contractName">
|
||||
<el-input v-model="state.formData.contractName" placeholder="请输入合同名称" :disabled="state.operation === 'view'" />
|
||||
</el-form-item>
|
||||
<el-form-item label="合同金额(元)" prop="money">
|
||||
<el-input-number
|
||||
v-model="state.formData.money"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
placeholder="请输入合同金额"
|
||||
style="width: 100%"
|
||||
:disabled="state.operation === 'view'"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否需要招标" prop="isBidding">
|
||||
<el-radio-group v-model="state.formData.isBidding" :disabled="state.operation === 'view'">
|
||||
<el-radio value="0">否</el-radio>
|
||||
<el-radio value="1">是</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否需要法律顾问" prop="isLegalAdviser">
|
||||
<el-radio-group v-model="state.formData.isLegalAdviser" :disabled="state.operation === 'view'">
|
||||
<el-radio value="0">否</el-radio>
|
||||
<el-radio value="1">是</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="state.formData.isLegalAdviser === '1'" label="法律顾问意见" prop="legalAdviserOpinion">
|
||||
<el-input
|
||||
v-model="state.formData.legalAdviserOpinion"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入法律顾问意见"
|
||||
:disabled="state.operation === 'view'"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否涉及多个部门" prop="isDepts">
|
||||
<el-radio-group v-model="state.formData.isDepts" :disabled="state.operation === 'view'">
|
||||
<el-radio value="0">否</el-radio>
|
||||
<el-radio value="1">是</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否全校合同" prop="isSchool">
|
||||
<el-radio-group v-model="state.formData.isSchool" :disabled="state.operation === 'view'">
|
||||
<el-radio value="0">否</el-radio>
|
||||
<el-radio value="1">是</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remarks">
|
||||
<el-input v-model="state.formData.remarks" type="textarea" :rows="2" placeholder="请输入备注" :disabled="state.operation === 'view'" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer v-if="state.operation !== 'view'">
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit" :loading="state.loading">确定</el-button>
|
||||
</template>
|
||||
<template #footer v-else>
|
||||
<el-button @click="handleClose">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="ContractDialog">
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { addObj, editObj } from "/@/api/purchase/purchasingcontract";
|
||||
import { useMessage } from "/@/hooks/message";
|
||||
import { ref, reactive, computed } from 'vue';
|
||||
import { addObj, editObj } from '/@/api/purchase/purchasingcontract';
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
|
||||
const emit = defineEmits(['refresh']);
|
||||
|
||||
const formRef = ref()
|
||||
const formRef = ref();
|
||||
|
||||
const state = reactive({
|
||||
visible: false,
|
||||
loading: false,
|
||||
operation: 'add' as 'add' | 'edit' | 'view',
|
||||
title: computed(() => {
|
||||
return state.operation === 'add' ? '新增合同' : state.operation === 'edit' ? '编辑合同' : '合同详情';
|
||||
}),
|
||||
formData: {
|
||||
id: '',
|
||||
contractNo: '',
|
||||
contractName: '',
|
||||
money: 0,
|
||||
purchaseId: '',
|
||||
isBidding: '0',
|
||||
isLegalAdviser: '0',
|
||||
legalAdviserOpinion: '',
|
||||
isDepts: '0',
|
||||
isSchool: '0',
|
||||
remarks: ''
|
||||
},
|
||||
purchaseNo: ''
|
||||
visible: false,
|
||||
loading: false,
|
||||
operation: 'add' as 'add' | 'edit' | 'view',
|
||||
title: computed(() => {
|
||||
return state.operation === 'add' ? '新增合同' : state.operation === 'edit' ? '编辑合同' : '合同详情';
|
||||
}),
|
||||
formData: {
|
||||
id: '',
|
||||
contractNo: '',
|
||||
contractName: '',
|
||||
money: 0,
|
||||
purchaseId: '',
|
||||
isBidding: '0',
|
||||
isLegalAdviser: '0',
|
||||
legalAdviserOpinion: '',
|
||||
isDepts: '0',
|
||||
isSchool: '0',
|
||||
remarks: '',
|
||||
},
|
||||
purchaseNo: '',
|
||||
});
|
||||
|
||||
const rules = {
|
||||
contractNo: [{ required: true, message: '请输入合同编号', trigger: 'blur' }],
|
||||
contractName: [{ required: true, message: '请输入合同名称', trigger: 'blur' }],
|
||||
money: [{ required: true, message: '请输入合同金额', trigger: 'blur' }],
|
||||
isBidding: [{ required: true, message: '请选择是否需要招标', trigger: 'change' }],
|
||||
isLegalAdviser: [{ required: true, message: '请选择是否需要法律顾问', trigger: 'change' }],
|
||||
isDepts: [{ required: true, message: '请选择是否涉及多部门', trigger: 'change' }],
|
||||
isSchool: [{ required: true, message: '请选择是否全校合同', trigger: 'change' }]
|
||||
contractNo: [{ required: true, message: '请输入合同编号', trigger: 'blur' }],
|
||||
contractName: [{ required: true, message: '请输入合同名称', trigger: 'blur' }],
|
||||
money: [{ required: true, message: '请输入合同金额', trigger: 'blur' }],
|
||||
isBidding: [{ required: true, message: '请选择是否需要招标', trigger: 'change' }],
|
||||
isLegalAdviser: [{ required: true, message: '请选择是否需要法律顾问', trigger: 'change' }],
|
||||
isDepts: [{ required: true, message: '请选择是否涉及多部门', trigger: 'change' }],
|
||||
isSchool: [{ required: true, message: '请选择是否全校合同', trigger: 'change' }],
|
||||
};
|
||||
|
||||
const openDialog = (purchaseApply: any, contract: any) => {
|
||||
state.visible = true;
|
||||
state.loading = false;
|
||||
state.purchaseNo = purchaseApply?.purchaseNo || '';
|
||||
state.formData.purchaseId = purchaseApply?.id || purchaseApply?.purchaseId || '';
|
||||
|
||||
if (contract && contract.id) {
|
||||
state.operation = 'edit';
|
||||
state.formData = {
|
||||
id: contract.id,
|
||||
contractNo: contract.contractNo || '',
|
||||
contractName: contract.contractName || '',
|
||||
money: contract.money || 0,
|
||||
purchaseId: contract.purchaseId || state.formData.purchaseId,
|
||||
isBidding: contract.isBidding || '0',
|
||||
isLegalAdviser: contract.isLegalAdviser || '0',
|
||||
legalAdviserOpinion: contract.legalAdviserOpinion || '',
|
||||
isDepts: contract.isDepts || '0',
|
||||
isSchool: contract.isSchool || '0',
|
||||
remarks: contract.remarks || ''
|
||||
};
|
||||
} else {
|
||||
state.operation = 'add';
|
||||
state.formData = {
|
||||
id: '',
|
||||
contractNo: '',
|
||||
contractName: '',
|
||||
money: 0,
|
||||
purchaseId: state.formData.purchaseId,
|
||||
isBidding: '0',
|
||||
isLegalAdviser: '0',
|
||||
legalAdviserOpinion: '',
|
||||
isDepts: '0',
|
||||
isSchool: '0',
|
||||
remarks: ''
|
||||
};
|
||||
}
|
||||
state.visible = true;
|
||||
state.loading = false;
|
||||
state.purchaseNo = purchaseApply?.purchaseNo || '';
|
||||
state.formData.purchaseId = purchaseApply?.id || purchaseApply?.purchaseId || '';
|
||||
|
||||
if (contract && contract.id) {
|
||||
state.operation = 'edit';
|
||||
state.formData = {
|
||||
id: contract.id,
|
||||
contractNo: contract.contractNo || '',
|
||||
contractName: contract.contractName || '',
|
||||
money: contract.money || 0,
|
||||
purchaseId: contract.purchaseId || state.formData.purchaseId,
|
||||
isBidding: contract.isBidding || '0',
|
||||
isLegalAdviser: contract.isLegalAdviser || '0',
|
||||
legalAdviserOpinion: contract.legalAdviserOpinion || '',
|
||||
isDepts: contract.isDepts || '0',
|
||||
isSchool: contract.isSchool || '0',
|
||||
remarks: contract.remarks || '',
|
||||
};
|
||||
} else {
|
||||
state.operation = 'add';
|
||||
state.formData = {
|
||||
id: '',
|
||||
contractNo: '',
|
||||
contractName: '',
|
||||
money: 0,
|
||||
purchaseId: state.formData.purchaseId,
|
||||
isBidding: '0',
|
||||
isLegalAdviser: '0',
|
||||
legalAdviserOpinion: '',
|
||||
isDepts: '0',
|
||||
isSchool: '0',
|
||||
remarks: '',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const valid = await formRef.value?.validate().catch(() => false);
|
||||
if (!valid) return;
|
||||
const valid = await formRef.value?.validate().catch(() => false);
|
||||
if (!valid) return;
|
||||
|
||||
try {
|
||||
state.loading = true;
|
||||
if (state.operation === 'add') {
|
||||
await addObj(state.formData);
|
||||
useMessage().success('新增成功');
|
||||
} else {
|
||||
await editObj(state.formData);
|
||||
useMessage().success('修改成功');
|
||||
}
|
||||
handleClose();
|
||||
emit('refresh');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || '操作失败');
|
||||
} finally {
|
||||
state.loading = false;
|
||||
}
|
||||
try {
|
||||
state.loading = true;
|
||||
if (state.operation === 'add') {
|
||||
await addObj(state.formData);
|
||||
useMessage().success('新增成功');
|
||||
} else {
|
||||
await editObj(state.formData);
|
||||
useMessage().success('修改成功');
|
||||
}
|
||||
handleClose();
|
||||
emit('refresh');
|
||||
} catch (err: any) {
|
||||
useMessage().error(err.msg || '操作失败');
|
||||
} finally {
|
||||
state.loading = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
state.visible = false;
|
||||
formRef.value?.resetFields();
|
||||
state.formData = {
|
||||
id: '',
|
||||
contractNo: '',
|
||||
contractName: '',
|
||||
money: 0,
|
||||
purchaseId: '',
|
||||
isBidding: '0',
|
||||
isLegalAdviser: '0',
|
||||
legalAdviserOpinion: '',
|
||||
isDepts: '0',
|
||||
isSchool: '0',
|
||||
remarks: ''
|
||||
};
|
||||
state.visible = false;
|
||||
formRef.value?.resetFields();
|
||||
state.formData = {
|
||||
id: '',
|
||||
contractNo: '',
|
||||
contractName: '',
|
||||
money: 0,
|
||||
purchaseId: '',
|
||||
isBidding: '0',
|
||||
isLegalAdviser: '0',
|
||||
legalAdviserOpinion: '',
|
||||
isDepts: '0',
|
||||
isSchool: '0',
|
||||
remarks: '',
|
||||
};
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
openDialog
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.el-dialog__body) {
|
||||
padding: 20px;
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,65 +1,69 @@
|
||||
<template>
|
||||
<el-table :data="records" stripe v-loading="loading" max-height="400">
|
||||
<el-table-column prop="operateTypeDesc" label="操作类型" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getOperateTypeStyle(scope.row.operateType)">
|
||||
{{ scope.row.operateTypeDesc }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="operateRoleDesc" label="操作角色" width="100" />
|
||||
<el-table-column prop="operateByName" label="操作人" width="100" />
|
||||
<el-table-column prop="currentVersion" label="文件版本" width="80" align="center" />
|
||||
<el-table-column prop="remark" label="批注意见" min-width="200" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ scope.row.remark || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="operateTime" label="操作时间" width="160" />
|
||||
</el-table>
|
||||
<el-table :data="records" stripe v-loading="loading" max-height="400">
|
||||
<el-table-column prop="operateTypeDesc" label="操作类型" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getOperateTypeStyle(scope.row.operateType)">
|
||||
{{ scope.row.operateTypeDesc }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="operateRoleDesc" label="操作角色" width="100" />
|
||||
<el-table-column prop="operateByName" label="操作人" width="100" />
|
||||
<el-table-column prop="currentVersion" label="文件版本" width="80" align="center" />
|
||||
<el-table-column prop="remark" label="批注意见" min-width="200" show-overflow-tooltip>
|
||||
<template #default="scope">
|
||||
{{ scope.row.remark || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="operateTime" label="操作时间" width="160" />
|
||||
</el-table>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { getAuditRecords } from '/@/api/purchase/docProcess'
|
||||
import { ref, watch } from 'vue';
|
||||
import { getAuditRecords } from '/@/api/purchase/docProcess';
|
||||
|
||||
const props = defineProps<{
|
||||
applyId: number | string
|
||||
}>()
|
||||
applyId: number | string;
|
||||
}>();
|
||||
|
||||
const records = ref<any[]>([])
|
||||
const loading = ref(false)
|
||||
const records = ref<any[]>([]);
|
||||
const loading = ref(false);
|
||||
|
||||
const loadRecords = async () => {
|
||||
if (!props.applyId) return
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getAuditRecords(props.applyId)
|
||||
records.value = res.data || []
|
||||
} catch (e) {
|
||||
records.value = []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
if (!props.applyId) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await getAuditRecords(props.applyId);
|
||||
records.value = res.data || [];
|
||||
} catch (e) {
|
||||
records.value = [];
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const refresh = () => {
|
||||
loadRecords()
|
||||
}
|
||||
loadRecords();
|
||||
};
|
||||
|
||||
const getOperateTypeStyle = (type: string) => {
|
||||
const styleMap: Record<string, string> = {
|
||||
'UPLOAD': 'primary',
|
||||
'CONFIRM': 'success',
|
||||
'RETURN': 'warning',
|
||||
'COMPLETE': 'success'
|
||||
}
|
||||
return styleMap[type] || 'info'
|
||||
}
|
||||
const styleMap: Record<string, string> = {
|
||||
UPLOAD: 'primary',
|
||||
CONFIRM: 'success',
|
||||
RETURN: 'warning',
|
||||
COMPLETE: 'success',
|
||||
};
|
||||
return styleMap[type] || 'info';
|
||||
};
|
||||
|
||||
watch(() => props.applyId, () => {
|
||||
loadRecords()
|
||||
}, { immediate: true })
|
||||
watch(
|
||||
() => props.applyId,
|
||||
() => {
|
||||
loadRecords();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
defineExpose({ refresh })
|
||||
</script>
|
||||
defineExpose({ refresh });
|
||||
</script>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,402 +1,397 @@
|
||||
<template>
|
||||
<div class="reviewer-setting">
|
||||
<el-form :model="formData" label-width="100px" v-loading="loading">
|
||||
<!-- 当前采购代表 -->
|
||||
<el-form-item label="当前代表" v-if="currentRepresentor.teacherName">
|
||||
<el-tag type="success">{{ currentRepresentor.teacherName }} ({{ currentRepresentor.teacherNo }})</el-tag>
|
||||
<el-tag v-if="currentRepresentor.representorType" type="info" style="margin-left: 8px;">{{ getRepresentorTypeLabel(currentRepresentor.representorType) }}</el-tag>
|
||||
</el-form-item>
|
||||
<div class="reviewer-setting">
|
||||
<el-form :model="formData" label-width="100px" v-loading="loading">
|
||||
<!-- 当前采购代表 -->
|
||||
<el-form-item label="当前代表" v-if="currentRepresentor.teacherName">
|
||||
<el-tag type="success">{{ currentRepresentor.teacherName }} ({{ currentRepresentor.teacherNo }})</el-tag>
|
||||
<el-tag v-if="currentRepresentor.representorType" type="info" style="margin-left: 8px">{{
|
||||
getRepresentorTypeLabel(currentRepresentor.representorType)
|
||||
}}</el-tag>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 选择方式 -->
|
||||
<el-form-item label="选择方式">
|
||||
<el-radio-group v-model="formData.selectMode" @change="handleSelectModeChange">
|
||||
<el-radio label="DESIGNATED">指定人员</el-radio>
|
||||
<el-radio label="RANDOM">随机抽取</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<!-- 选择方式 -->
|
||||
<el-form-item label="选择方式">
|
||||
<el-radio-group v-model="formData.selectMode" @change="handleSelectModeChange">
|
||||
<el-radio label="DESIGNATED">指定人员</el-radio>
|
||||
<el-radio label="RANDOM">随机抽取</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 指定人员模式 -->
|
||||
<el-form-item v-if="formData.selectMode === 'DESIGNATED'" label="选择人员" required>
|
||||
<org-selector
|
||||
v-model:orgList="selectedUserList"
|
||||
type="user"
|
||||
:multiple="false"
|
||||
@update:orgList="handleUserChange" />
|
||||
</el-form-item>
|
||||
<!-- 指定人员模式 -->
|
||||
<el-form-item v-if="formData.selectMode === 'DESIGNATED'" label="选择人员" required>
|
||||
<org-selector v-model:orgList="selectedUserList" type="user" :multiple="false" @update:orgList="handleUserChange" />
|
||||
</el-form-item>
|
||||
|
||||
<!-- 随机抽取模式 -->
|
||||
<template v-if="formData.selectMode === 'RANDOM'">
|
||||
<el-form-item label="选择候选人" required>
|
||||
<org-selector
|
||||
v-model:orgList="candidateUserList"
|
||||
type="user"
|
||||
:multiple="true"
|
||||
@update:orgList="handleCandidateChange" />
|
||||
</el-form-item>
|
||||
<!-- 随机抽取模式 -->
|
||||
<template v-if="formData.selectMode === 'RANDOM'">
|
||||
<el-form-item label="选择候选人" required>
|
||||
<org-selector v-model:orgList="candidateUserList" type="user" :multiple="true" @update:orgList="handleCandidateChange" />
|
||||
</el-form-item>
|
||||
|
||||
<!-- 候选人列表 -->
|
||||
<el-form-item label="候选人列表" v-if="candidates.length > 0">
|
||||
<el-table :data="candidates" stripe size="small" max-height="200">
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="teacherNo" label="工号" width="120" />
|
||||
<el-table-column prop="teacherName" label="姓名" />
|
||||
</el-table>
|
||||
</el-form-item>
|
||||
<!-- 候选人列表 -->
|
||||
<el-form-item label="候选人列表" v-if="candidates.length > 0">
|
||||
<el-table :data="candidates" stripe size="small" max-height="200">
|
||||
<el-table-column type="index" label="序号" width="60" align="center" />
|
||||
<el-table-column prop="teacherNo" label="工号" width="120" />
|
||||
<el-table-column prop="teacherName" label="姓名" />
|
||||
</el-table>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 随机抽取结果 -->
|
||||
<el-form-item label="抽取结果">
|
||||
<div class="random-roller">
|
||||
<span v-if="rollingName" class="rolling">{{ rollingName }}</span>
|
||||
<span v-else-if="selectedCandidate.teacherName" class="selected">
|
||||
已抽取:{{ selectedCandidate.teacherName }} ({{ selectedCandidate.teacherNo }})
|
||||
</span>
|
||||
<span v-else class="placeholder">点击下方按钮进行随机抽取</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<!-- 随机抽取结果 -->
|
||||
<el-form-item label="抽取结果">
|
||||
<div class="random-roller">
|
||||
<span v-if="rollingName" class="rolling">{{ rollingName }}</span>
|
||||
<span v-else-if="selectedCandidate.teacherName" class="selected">
|
||||
已抽取:{{ selectedCandidate.teacherName }} ({{ selectedCandidate.teacherNo }})
|
||||
</span>
|
||||
<span v-else class="placeholder">点击下方按钮进行随机抽取</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 随机抽取按钮 -->
|
||||
<el-form-item v-if="candidates.length > 1">
|
||||
<el-button type="primary" :loading="rolling" @click="handleRandomSelect">
|
||||
{{ rolling ? '抽取中...' : '随机抽取' }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<!-- 随机抽取按钮 -->
|
||||
<el-form-item v-if="candidates.length > 1">
|
||||
<el-button type="primary" :loading="rolling" @click="handleRandomSelect">
|
||||
{{ rolling ? '抽取中...' : '随机抽取' }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<!-- 人员类型选择 -->
|
||||
<el-form-item label="人员类型">
|
||||
<el-select v-model="formData.representorType" placeholder="请选择人员类型" clearable style="width: 200px;">
|
||||
<el-option
|
||||
v-for="item in representorTypeOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<!-- 人员类型选择 -->
|
||||
<el-form-item label="人员类型">
|
||||
<el-select v-model="formData.representorType" placeholder="请选择人员类型" clearable style="width: 200px">
|
||||
<el-option v-for="item in representorTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 保存按钮 -->
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="saving" :disabled="!canSave" @click="handleSave">
|
||||
保存设置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<!-- 保存按钮 -->
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="saving" :disabled="!canSave" @click="handleSave"> 保存设置 </el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="ReviewerSetting">
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { useMessage } from '/@/hooks/message'
|
||||
import { useDict } from '/@/hooks/dict'
|
||||
import { getReviewerSetting, setReviewerSetting, randomSelectReviewer } from '/@/api/purchase/docProcess'
|
||||
import orgSelector from '/@/components/OrgSelector/index.vue'
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
||||
import { useMessage } from '/@/hooks/message';
|
||||
import { useDict } from '/@/hooks/dict';
|
||||
import { getReviewerSetting, setReviewerSetting, randomSelectReviewer } from '/@/api/purchase/docProcess';
|
||||
import orgSelector from '/@/components/OrgSelector/index.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
applyId: string | number
|
||||
}>()
|
||||
applyId: string | number;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'saved'): void
|
||||
}>()
|
||||
(e: 'saved'): void;
|
||||
}>();
|
||||
|
||||
// 常量
|
||||
const SELECT_MODE = {
|
||||
DESIGNATED: 'DESIGNATED',
|
||||
RANDOM: 'RANDOM',
|
||||
} as const
|
||||
DESIGNATED: 'DESIGNATED',
|
||||
RANDOM: 'RANDOM',
|
||||
} as const;
|
||||
|
||||
// 字典数据
|
||||
const { PURCHASE_REPRESENTOR_TYPE: representorTypeDict } = useDict('PURCHASE_REPRESENTOR_TYPE')
|
||||
const { PURCHASE_REPRESENTOR_TYPE: representorTypeDict } = useDict('PURCHASE_REPRESENTOR_TYPE');
|
||||
|
||||
// 人员类型选项
|
||||
const representorTypeOptions = computed(() => {
|
||||
return (representorTypeDict.value || []).map((item: any) => ({
|
||||
value: item.value,
|
||||
label: item.label
|
||||
}))
|
||||
})
|
||||
return (representorTypeDict.value || []).map((item: any) => ({
|
||||
value: item.value,
|
||||
label: item.label,
|
||||
}));
|
||||
});
|
||||
|
||||
// 表单数据
|
||||
const formData = ref({
|
||||
selectMode: 'DESIGNATED' as 'DESIGNATED' | 'RANDOM',
|
||||
teacherNo: '',
|
||||
teacherName: '',
|
||||
candidates: [] as Array<{ teacherNo: string; teacherName: string }>,
|
||||
representorType: ''
|
||||
})
|
||||
selectMode: 'DESIGNATED' as 'DESIGNATED' | 'RANDOM',
|
||||
teacherNo: '',
|
||||
teacherName: '',
|
||||
candidates: [] as Array<{ teacherNo: string; teacherName: string }>,
|
||||
representorType: '',
|
||||
});
|
||||
|
||||
// 当前采购代表(已有设置)
|
||||
const currentRepresentor = ref({
|
||||
teacherNo: '',
|
||||
teacherName: '',
|
||||
representorType: ''
|
||||
})
|
||||
teacherNo: '',
|
||||
teacherName: '',
|
||||
representorType: '',
|
||||
});
|
||||
|
||||
// 用户选择相关
|
||||
const selectedUserList = ref<any[]>([])
|
||||
const candidateUserList = ref<any[]>([])
|
||||
const selectedUserList = ref<any[]>([]);
|
||||
const candidateUserList = ref<any[]>([]);
|
||||
|
||||
// 候选人列表
|
||||
const candidates = ref<Array<{ teacherNo: string; teacherName: string }>>([])
|
||||
const candidates = ref<Array<{ teacherNo: string; teacherName: string }>>([]);
|
||||
|
||||
// 随机抽取相关
|
||||
const rolling = ref(false)
|
||||
const rollingName = ref('')
|
||||
const selectedCandidate = ref<{ teacherNo: string; teacherName: string }>({ teacherNo: '', teacherName: '' })
|
||||
let rollInterval: ReturnType<typeof setInterval> | null = null
|
||||
const rolling = ref(false);
|
||||
const rollingName = ref('');
|
||||
const selectedCandidate = ref<{ teacherNo: string; teacherName: string }>({ teacherNo: '', teacherName: '' });
|
||||
let rollInterval: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
// 加载状态
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
|
||||
// 是否可以保存
|
||||
const canSave = computed(() => {
|
||||
if (formData.value.selectMode === SELECT_MODE.DESIGNATED) {
|
||||
return formData.value.teacherNo && formData.value.teacherName
|
||||
} else {
|
||||
return formData.value.teacherNo && formData.value.teacherName && candidates.value.length > 0
|
||||
}
|
||||
})
|
||||
if (formData.value.selectMode === SELECT_MODE.DESIGNATED) {
|
||||
return formData.value.teacherNo && formData.value.teacherName;
|
||||
} else {
|
||||
return formData.value.teacherNo && formData.value.teacherName && candidates.value.length > 0;
|
||||
}
|
||||
});
|
||||
|
||||
// 处理选择方式变化
|
||||
const handleSelectModeChange = () => {
|
||||
formData.value.teacherNo = ''
|
||||
formData.value.teacherName = ''
|
||||
formData.value.candidates = []
|
||||
selectedUserList.value = []
|
||||
candidateUserList.value = []
|
||||
candidates.value = []
|
||||
selectedCandidate.value = { teacherNo: '', teacherName: '' }
|
||||
rollingName.value = ''
|
||||
}
|
||||
formData.value.teacherNo = '';
|
||||
formData.value.teacherName = '';
|
||||
formData.value.candidates = [];
|
||||
selectedUserList.value = [];
|
||||
candidateUserList.value = [];
|
||||
candidates.value = [];
|
||||
selectedCandidate.value = { teacherNo: '', teacherName: '' };
|
||||
rollingName.value = '';
|
||||
};
|
||||
|
||||
// 获取人员类型标签
|
||||
const getRepresentorTypeLabel = (value: string): string => {
|
||||
const option = representorTypeOptions.value.find((item: any) => item.value === value)
|
||||
return option ? option.label : value
|
||||
}
|
||||
const option = representorTypeOptions.value.find((item: any) => item.value === value);
|
||||
return option ? option.label : value;
|
||||
};
|
||||
|
||||
// 处理指定人员变化
|
||||
const handleUserChange = (list: any[]) => {
|
||||
if (list && list.length > 0) {
|
||||
const user = list[0]
|
||||
formData.value.teacherNo = user.username || user.userName || ''
|
||||
formData.value.teacherName = user.name || user.realName || ''
|
||||
} else {
|
||||
formData.value.teacherNo = ''
|
||||
formData.value.teacherName = ''
|
||||
}
|
||||
}
|
||||
if (list && list.length > 0) {
|
||||
const user = list[0];
|
||||
formData.value.teacherNo = user.username || user.userName || '';
|
||||
formData.value.teacherName = user.name || user.realName || '';
|
||||
} else {
|
||||
formData.value.teacherNo = '';
|
||||
formData.value.teacherName = '';
|
||||
}
|
||||
};
|
||||
|
||||
// 处理候选人变化
|
||||
const handleCandidateChange = (list: any[]) => {
|
||||
candidates.value = (list || []).map(user => ({
|
||||
teacherNo: user.username || user.userName || '',
|
||||
teacherName: user.name || user.realName || ''
|
||||
}))
|
||||
// 重置已选结果
|
||||
if (candidates.value.length > 0) {
|
||||
selectedCandidate.value = { ...candidates.value[0] }
|
||||
formData.value.teacherNo = selectedCandidate.value.teacherNo
|
||||
formData.value.teacherName = selectedCandidate.value.teacherName
|
||||
} else {
|
||||
selectedCandidate.value = { teacherNo: '', teacherName: '' }
|
||||
formData.value.teacherNo = ''
|
||||
formData.value.teacherName = ''
|
||||
}
|
||||
}
|
||||
candidates.value = (list || []).map((user) => ({
|
||||
teacherNo: user.username || user.userName || '',
|
||||
teacherName: user.name || user.realName || '',
|
||||
}));
|
||||
// 重置已选结果
|
||||
if (candidates.value.length > 0) {
|
||||
selectedCandidate.value = { ...candidates.value[0] };
|
||||
formData.value.teacherNo = selectedCandidate.value.teacherNo;
|
||||
formData.value.teacherName = selectedCandidate.value.teacherName;
|
||||
} else {
|
||||
selectedCandidate.value = { teacherNo: '', teacherName: '' };
|
||||
formData.value.teacherNo = '';
|
||||
formData.value.teacherName = '';
|
||||
}
|
||||
};
|
||||
|
||||
// 随机抽取动画
|
||||
const startRollingAnimation = (finalCandidate: { teacherNo: string; teacherName: string }) => {
|
||||
if (candidates.value.length === 0) return
|
||||
if (candidates.value.length === 0) return;
|
||||
|
||||
if (rollInterval) {
|
||||
clearInterval(rollInterval)
|
||||
rollInterval = null
|
||||
}
|
||||
if (rollInterval) {
|
||||
clearInterval(rollInterval);
|
||||
rollInterval = null;
|
||||
}
|
||||
|
||||
rollingName.value = ''
|
||||
rolling.value = true
|
||||
rollingName.value = '';
|
||||
rolling.value = true;
|
||||
|
||||
let currentIndex = 0
|
||||
const totalDuration = 2000
|
||||
const intervalTime = 80
|
||||
let currentIndex = 0;
|
||||
const totalDuration = 2000;
|
||||
const intervalTime = 80;
|
||||
|
||||
rollInterval = setInterval(() => {
|
||||
rollingName.value = candidates.value[currentIndex].teacherName
|
||||
currentIndex = (currentIndex + 1) % candidates.value.length
|
||||
}, intervalTime)
|
||||
rollInterval = setInterval(() => {
|
||||
rollingName.value = candidates.value[currentIndex].teacherName;
|
||||
currentIndex = (currentIndex + 1) % candidates.value.length;
|
||||
}, intervalTime);
|
||||
|
||||
setTimeout(() => {
|
||||
if (rollInterval) {
|
||||
clearInterval(rollInterval)
|
||||
rollInterval = null
|
||||
}
|
||||
rolling.value = false
|
||||
rollingName.value = ''
|
||||
selectedCandidate.value = finalCandidate
|
||||
formData.value.teacherNo = finalCandidate.teacherNo
|
||||
formData.value.teacherName = finalCandidate.teacherName
|
||||
}, totalDuration)
|
||||
}
|
||||
setTimeout(() => {
|
||||
if (rollInterval) {
|
||||
clearInterval(rollInterval);
|
||||
rollInterval = null;
|
||||
}
|
||||
rolling.value = false;
|
||||
rollingName.value = '';
|
||||
selectedCandidate.value = finalCandidate;
|
||||
formData.value.teacherNo = finalCandidate.teacherNo;
|
||||
formData.value.teacherName = finalCandidate.teacherName;
|
||||
}, totalDuration);
|
||||
};
|
||||
|
||||
// 执行随机抽取
|
||||
const handleRandomSelect = async () => {
|
||||
if (candidates.value.length < 2) {
|
||||
useMessage().warning('请至少选择2位候选人')
|
||||
return
|
||||
}
|
||||
if (candidates.value.length < 2) {
|
||||
useMessage().warning('请至少选择2位候选人');
|
||||
return;
|
||||
}
|
||||
|
||||
rolling.value = true
|
||||
try {
|
||||
const res = await randomSelectReviewer({
|
||||
applyId: props.applyId,
|
||||
selectMode: SELECT_MODE.RANDOM,
|
||||
candidates: candidates.value
|
||||
})
|
||||
const result = res?.data || res
|
||||
if (result?.teacherNo) {
|
||||
startRollingAnimation({
|
||||
teacherNo: result.teacherNo,
|
||||
teacherName: result.teacherName || ''
|
||||
})
|
||||
}
|
||||
} catch (e: any) {
|
||||
rolling.value = false
|
||||
useMessage().error(e?.msg || '随机抽取失败')
|
||||
}
|
||||
}
|
||||
rolling.value = true;
|
||||
try {
|
||||
const res = await randomSelectReviewer({
|
||||
applyId: props.applyId,
|
||||
selectMode: SELECT_MODE.RANDOM,
|
||||
candidates: candidates.value,
|
||||
});
|
||||
const result = res?.data || res;
|
||||
if (result?.teacherNo) {
|
||||
startRollingAnimation({
|
||||
teacherNo: result.teacherNo,
|
||||
teacherName: result.teacherName || '',
|
||||
});
|
||||
}
|
||||
} catch (e: any) {
|
||||
rolling.value = false;
|
||||
useMessage().error(e?.msg || '随机抽取失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 保存设置
|
||||
const handleSave = async () => {
|
||||
if (!canSave.value) {
|
||||
useMessage().warning('请完善设置信息')
|
||||
return
|
||||
}
|
||||
if (!canSave.value) {
|
||||
useMessage().warning('请完善设置信息');
|
||||
return;
|
||||
}
|
||||
|
||||
saving.value = true
|
||||
try {
|
||||
const params: any = {
|
||||
applyId: props.applyId,
|
||||
selectMode: formData.value.selectMode,
|
||||
teacherNo: formData.value.teacherNo,
|
||||
teacherName: formData.value.teacherName,
|
||||
representorType: formData.value.representorType
|
||||
}
|
||||
saving.value = true;
|
||||
try {
|
||||
const params: any = {
|
||||
applyId: props.applyId,
|
||||
selectMode: formData.value.selectMode,
|
||||
teacherNo: formData.value.teacherNo,
|
||||
teacherName: formData.value.teacherName,
|
||||
representorType: formData.value.representorType,
|
||||
};
|
||||
|
||||
if (formData.value.selectMode === SELECT_MODE.RANDOM) {
|
||||
params.candidates = candidates.value
|
||||
}
|
||||
if (formData.value.selectMode === SELECT_MODE.RANDOM) {
|
||||
params.candidates = candidates.value;
|
||||
}
|
||||
|
||||
await setReviewerSetting(params)
|
||||
useMessage().success('保存成功')
|
||||
emit('saved')
|
||||
await loadData()
|
||||
} catch (e: any) {
|
||||
useMessage().error(e?.msg || '保存失败')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
await setReviewerSetting(params);
|
||||
useMessage().success('保存成功');
|
||||
emit('saved');
|
||||
await loadData();
|
||||
} catch (e: any) {
|
||||
useMessage().error(e?.msg || '保存失败');
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 加载数据
|
||||
const loadData = async () => {
|
||||
if (!props.applyId) return
|
||||
if (!props.applyId) return;
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await getReviewerSetting(props.applyId)
|
||||
const data = res?.data || res
|
||||
if (data) {
|
||||
currentRepresentor.value = {
|
||||
teacherNo: data.teacherNo || '',
|
||||
teacherName: data.teacherName || '',
|
||||
representorType: data.representorType || ''
|
||||
}
|
||||
formData.value.selectMode = data.selectMode || SELECT_MODE.DESIGNATED
|
||||
formData.value.teacherNo = data.teacherNo || ''
|
||||
formData.value.teacherName = data.teacherName || ''
|
||||
formData.value.representorType = data.representorType || ''
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await getReviewerSetting(props.applyId);
|
||||
const data = res?.data || res;
|
||||
if (data) {
|
||||
currentRepresentor.value = {
|
||||
teacherNo: data.teacherNo || '',
|
||||
teacherName: data.teacherName || '',
|
||||
representorType: data.representorType || '',
|
||||
};
|
||||
formData.value.selectMode = data.selectMode || SELECT_MODE.DESIGNATED;
|
||||
formData.value.teacherNo = data.teacherNo || '';
|
||||
formData.value.teacherName = data.teacherName || '';
|
||||
formData.value.representorType = data.representorType || '';
|
||||
|
||||
if (data.candidateList && data.candidateList.length > 0) {
|
||||
candidates.value = data.candidateList
|
||||
formData.value.candidates = data.candidateList
|
||||
// 回显候选人
|
||||
candidateUserList.value = data.candidateList.map((c: any) => ({
|
||||
username: c.teacherNo,
|
||||
userName: c.teacherNo,
|
||||
name: c.teacherName,
|
||||
realName: c.teacherName
|
||||
}))
|
||||
}
|
||||
if (data.candidateList && data.candidateList.length > 0) {
|
||||
candidates.value = data.candidateList;
|
||||
formData.value.candidates = data.candidateList;
|
||||
// 回显候选人
|
||||
candidateUserList.value = data.candidateList.map((c: any) => ({
|
||||
username: c.teacherNo,
|
||||
userName: c.teacherNo,
|
||||
name: c.teacherName,
|
||||
realName: c.teacherName,
|
||||
}));
|
||||
}
|
||||
|
||||
if (data.teacherNo && data.selectMode === SELECT_MODE.DESIGNATED) {
|
||||
selectedUserList.value = [{
|
||||
username: data.teacherNo,
|
||||
userName: data.teacherNo,
|
||||
name: data.teacherName,
|
||||
realName: data.teacherName
|
||||
}]
|
||||
}
|
||||
if (data.teacherNo && data.selectMode === SELECT_MODE.DESIGNATED) {
|
||||
selectedUserList.value = [
|
||||
{
|
||||
username: data.teacherNo,
|
||||
userName: data.teacherNo,
|
||||
name: data.teacherName,
|
||||
realName: data.teacherName,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
if (data.selectMode === SELECT_MODE.RANDOM && data.teacherNo) {
|
||||
selectedCandidate.value = {
|
||||
teacherNo: data.teacherNo,
|
||||
teacherName: data.teacherName || ''
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (_) {
|
||||
// 忽略错误
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
if (data.selectMode === SELECT_MODE.RANDOM && data.teacherNo) {
|
||||
selectedCandidate.value = {
|
||||
teacherNo: data.teacherNo,
|
||||
teacherName: data.teacherName || '',
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (_) {
|
||||
// 忽略错误
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
loadData();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (rollInterval) {
|
||||
clearInterval(rollInterval)
|
||||
rollInterval = null
|
||||
}
|
||||
})
|
||||
if (rollInterval) {
|
||||
clearInterval(rollInterval);
|
||||
rollInterval = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.reviewer-setting {
|
||||
padding: 16px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.random-roller {
|
||||
padding: 12px 16px;
|
||||
background: var(--el-fill-color-light);
|
||||
border-radius: 4px;
|
||||
min-height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
background: var(--el-fill-color-light);
|
||||
border-radius: 4px;
|
||||
min-height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.rolling {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: var(--el-color-primary);
|
||||
animation: blink 0.1s infinite;
|
||||
}
|
||||
.rolling {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: var(--el-color-primary);
|
||||
animation: blink 0.1s infinite;
|
||||
}
|
||||
|
||||
.selected {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--el-color-success);
|
||||
}
|
||||
.selected {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--el-color-success);
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
color: var(--el-text-color-placeholder);
|
||||
}
|
||||
.placeholder {
|
||||
color: var(--el-text-color-placeholder);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.7; }
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,252 +1,241 @@
|
||||
<template>
|
||||
<div class="modern-page-container">
|
||||
<div class="page-wrapper">
|
||||
<!-- 搜索表单卡片 -->
|
||||
<el-card v-show="showSearch" class="search-card" shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
<el-icon class="title-icon"><Search /></el-icon>
|
||||
筛选条件
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList" class="search-form">
|
||||
<el-form-item label="采购编号" prop="purchaseNo">
|
||||
<el-input
|
||||
v-model="state.queryForm.purchaseNo"
|
||||
placeholder="请输入采购编号"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="项目名称" prop="projectName">
|
||||
<el-input
|
||||
v-model="state.queryForm.projectName"
|
||||
placeholder="请输入项目名称"
|
||||
clearable
|
||||
style="width: 200px" />
|
||||
</el-form-item>
|
||||
<!-- 审核状态筛选 - 仅审核模式显示 -->
|
||||
<el-form-item v-if="mode === 'audit'" label="审核状态" prop="docAuditStatus">
|
||||
<el-select
|
||||
v-model="state.queryForm.docAuditStatus"
|
||||
placeholder="请选择审核状态"
|
||||
clearable
|
||||
style="width: 200px">
|
||||
<el-option label="待上传" value="PENDING_UPLOAD" />
|
||||
<el-option label="草稿" value="DRAFT" />
|
||||
<el-option label="资产管理处审核中" value="ASSET_REVIEWING" />
|
||||
<el-option label="需求部门审核中" value="DEPT_REVIEWING" />
|
||||
<el-option label="内审部门审核中" value="AUDIT_REVIEWING" />
|
||||
<el-option label="资产管理处确认中" value="ASSET_CONFIRMING" />
|
||||
<el-option label="已完成" value="COMPLETED" />
|
||||
<el-option label="已退回" value="RETURNED" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="getDataList">查询</el-button>
|
||||
<el-button icon="Refresh" @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
<div class="modern-page-container">
|
||||
<div class="page-wrapper">
|
||||
<!-- 搜索表单卡片 -->
|
||||
<el-card v-show="showSearch" class="search-card" shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
<el-icon class="title-icon"><Search /></el-icon>
|
||||
筛选条件
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-form :model="state.queryForm" ref="searchFormRef" :inline="true" @keyup.enter="getDataList" class="search-form">
|
||||
<el-form-item label="采购编号" prop="purchaseNo">
|
||||
<el-input v-model="state.queryForm.purchaseNo" placeholder="请输入采购编号" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="项目名称" prop="projectName">
|
||||
<el-input v-model="state.queryForm.projectName" placeholder="请输入项目名称" clearable style="width: 200px" />
|
||||
</el-form-item>
|
||||
<!-- 审核状态筛选 - 仅审核模式显示 -->
|
||||
<el-form-item v-if="mode === 'audit'" label="审核状态" prop="docAuditStatus">
|
||||
<el-select v-model="state.queryForm.docAuditStatus" placeholder="请选择审核状态" clearable style="width: 200px">
|
||||
<el-option label="待上传" value="PENDING_UPLOAD" />
|
||||
<el-option label="草稿" value="DRAFT" />
|
||||
<el-option label="资产管理处审核中" value="ASSET_REVIEWING" />
|
||||
<el-option label="需求部门审核中" value="DEPT_REVIEWING" />
|
||||
<el-option label="内审部门审核中" value="AUDIT_REVIEWING" />
|
||||
<el-option label="资产管理处确认中" value="ASSET_CONFIRMING" />
|
||||
<el-option label="已完成" value="COMPLETED" />
|
||||
<el-option label="已退回" value="RETURNED" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="getDataList">查询</el-button>
|
||||
<el-button icon="Refresh" @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 内容卡片 -->
|
||||
<el-card class="content-card" shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
<el-icon class="title-icon">
|
||||
<component :is="titleIcon" />
|
||||
</el-icon>
|
||||
{{ pageTitle }}
|
||||
</span>
|
||||
<div class="header-actions">
|
||||
<right-toolbar v-model:showSearch="showSearch" class="ml10" @queryTable="getDataList" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 内容卡片 -->
|
||||
<el-card class="content-card" shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
<el-icon class="title-icon">
|
||||
<component :is="titleIcon" />
|
||||
</el-icon>
|
||||
{{ pageTitle }}
|
||||
</span>
|
||||
<div class="header-actions">
|
||||
<right-toolbar v-model:showSearch="showSearch" class="ml10" @queryTable="getDataList" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 表格 -->
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
:data="state.dataList"
|
||||
v-loading="state.loading"
|
||||
stripe
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
class="modern-table">
|
||||
<el-table-column type="index" label="序号" width="70" align="center">
|
||||
<template #header>
|
||||
<el-icon><List /></el-icon>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="purchaseNo" label="采购编号" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="projectName" label="项目名称" min-width="200" show-overflow-tooltip />
|
||||
<!-- 需求部门 - 仅审核模式显示 -->
|
||||
<el-table-column v-if="mode === 'audit'" prop="deptName" label="需求部门" min-width="150" show-overflow-tooltip />
|
||||
<!-- 预算金额 - 仅审核模式显示 -->
|
||||
<el-table-column v-if="mode === 'audit'" prop="budget" label="预算金额(元)" width="120" align="right">
|
||||
<template #default="scope">
|
||||
{{ scope.row.budget ? Number(scope.row.budget).toLocaleString() : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 文件状态 -->
|
||||
<el-table-column :prop="statusProp" label="文件状态" width="140" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusType(getRowStatus(scope.row))">
|
||||
{{ getStatusLabel(getRowStatus(scope.row)) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 当前版本 - 仅审核模式显示 -->
|
||||
<el-table-column v-if="mode === 'audit'" prop="currentDocVersion" label="当前版本" width="100" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.currentDocVersion || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 操作 -->
|
||||
<el-table-column label="操作" align="center" fixed="right" width="120">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link icon="View" @click="handleProcess(scope.row)">
|
||||
{{ actionLabel }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 表格 -->
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
:data="state.dataList"
|
||||
v-loading="state.loading"
|
||||
stripe
|
||||
:cell-style="tableStyle.cellStyle"
|
||||
:header-cell-style="tableStyle.headerCellStyle"
|
||||
class="modern-table"
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="70" align="center">
|
||||
<template #header>
|
||||
<el-icon><List /></el-icon>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="purchaseNo" label="采购编号" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column prop="projectName" label="项目名称" min-width="200" show-overflow-tooltip />
|
||||
<!-- 需求部门 - 仅审核模式显示 -->
|
||||
<el-table-column v-if="mode === 'audit'" prop="deptName" label="需求部门" min-width="150" show-overflow-tooltip />
|
||||
<!-- 预算金额 - 仅审核模式显示 -->
|
||||
<el-table-column v-if="mode === 'audit'" prop="budget" label="预算金额(元)" width="120" align="right">
|
||||
<template #default="scope">
|
||||
{{ scope.row.budget ? Number(scope.row.budget).toLocaleString() : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 文件状态 -->
|
||||
<el-table-column :prop="statusProp" label="文件状态" width="140" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusType(getRowStatus(scope.row))">
|
||||
{{ getStatusLabel(getRowStatus(scope.row)) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 当前版本 - 仅审核模式显示 -->
|
||||
<el-table-column v-if="mode === 'audit'" prop="currentDocVersion" label="当前版本" width="100" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.currentDocVersion || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 操作 -->
|
||||
<el-table-column label="操作" align="center" fixed="right" width="120">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" link icon="View" @click="handleProcess(scope.row)">
|
||||
{{ actionLabel }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<pagination
|
||||
v-if="state.pagination && state.pagination.total && state.pagination.total > 0"
|
||||
:total="state.pagination.total"
|
||||
:current="state.pagination.current"
|
||||
:size="state.pagination.size"
|
||||
@sizeChange="sizeChangeHandle"
|
||||
@currentChange="currentChangeHandle"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
<!-- 分页 -->
|
||||
<pagination
|
||||
v-if="state.pagination && state.pagination.total && state.pagination.total > 0"
|
||||
:total="state.pagination.total"
|
||||
:current="state.pagination.current"
|
||||
:size="state.pagination.size"
|
||||
@sizeChange="sizeChangeHandle"
|
||||
@currentChange="currentChangeHandle"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 处理弹窗 -->
|
||||
<!-- {{mode}}-->
|
||||
<DocProcessDialog ref="docProcessDialogRef" :mode="mode" @refresh="getDataList" />
|
||||
</div>
|
||||
<!-- 处理弹窗 -->
|
||||
<!-- {{mode}}-->
|
||||
<DocProcessDialog ref="docProcessDialogRef" :mode="mode" @refresh="getDataList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="PurchasingDocProcess">
|
||||
import { ref, reactive, computed, defineAsyncComponent, onMounted } from 'vue'
|
||||
import { BasicTableProps, useTable } from "/@/hooks/table";
|
||||
import { getDocProcessList } from "/@/api/purchase/docProcess";
|
||||
import { Search, DocumentCopy, DocumentChecked, List } from '@element-plus/icons-vue'
|
||||
import { ref, reactive, computed, defineAsyncComponent, onMounted } from 'vue';
|
||||
import { BasicTableProps, useTable } from '/@/hooks/table';
|
||||
import { getDocProcessList } from '/@/api/purchase/docProcess';
|
||||
import { Search, DocumentCopy, DocumentChecked, List } from '@element-plus/icons-vue';
|
||||
import { useUserInfo } from '/@/stores/userInfo';
|
||||
|
||||
// 引入组件
|
||||
const DocProcessDialog = defineAsyncComponent(() => import('./DocProcessDialog.vue'));
|
||||
|
||||
const userInfoStore = useUserInfo()
|
||||
const docProcessDialogRef = ref()
|
||||
const searchFormRef = ref()
|
||||
const showSearch = ref(true)
|
||||
const userInfoStore = useUserInfo();
|
||||
const docProcessDialogRef = ref();
|
||||
const searchFormRef = ref();
|
||||
const showSearch = ref(true);
|
||||
|
||||
// 从用户角色自动判断模式
|
||||
const mode = computed(() => {
|
||||
const roleCodes = userInfoStore.userInfos.roleCodes || [];
|
||||
// 有 PURCHASE_AGENT 角色则显示代理模式
|
||||
if (roleCodes.includes('PURCHASE_AGENT')) {
|
||||
return 'agent';
|
||||
}
|
||||
// 其他情况显示审核模式
|
||||
return 'audit';
|
||||
})
|
||||
const roleCodes = userInfoStore.userInfos.roleCodes || [];
|
||||
// 有 PURCHASE_AGENT 角色则显示代理模式
|
||||
if (roleCodes.includes('PURCHASE_AGENT')) {
|
||||
return 'agent';
|
||||
}
|
||||
// 其他情况显示审核模式
|
||||
return 'audit';
|
||||
});
|
||||
|
||||
// 页面标题
|
||||
const pageTitle = computed(() => {
|
||||
return mode.value === 'agent' ? '招标代理工作台' : '招标文件审核'
|
||||
})
|
||||
return mode.value === 'agent' ? '招标代理工作台' : '招标文件审核';
|
||||
});
|
||||
|
||||
// 标题图标
|
||||
const titleIcon = computed(() => {
|
||||
return mode.value === 'agent' ? DocumentCopy : DocumentChecked
|
||||
})
|
||||
return mode.value === 'agent' ? DocumentCopy : DocumentChecked;
|
||||
});
|
||||
|
||||
// 操作按钮标签
|
||||
const actionLabel = computed(() => {
|
||||
return mode.value === 'agent' ? '处理' : '审核'
|
||||
})
|
||||
return mode.value === 'agent' ? '处理' : '审核';
|
||||
});
|
||||
|
||||
// 状态字段
|
||||
const statusProp = computed(() => {
|
||||
return mode.value === 'agent' ? 'status' : 'docAuditStatus'
|
||||
})
|
||||
return mode.value === 'agent' ? 'status' : 'docAuditStatus';
|
||||
});
|
||||
|
||||
// 查询表单
|
||||
const getQueryForm = () => {
|
||||
const base: any = {
|
||||
purchaseNo: '',
|
||||
projectName: '',
|
||||
}
|
||||
if (mode.value === 'audit') {
|
||||
base.docAuditStatus = ''
|
||||
}
|
||||
return base
|
||||
}
|
||||
const base: any = {
|
||||
purchaseNo: '',
|
||||
projectName: '',
|
||||
};
|
||||
if (mode.value === 'audit') {
|
||||
base.docAuditStatus = '';
|
||||
}
|
||||
return base;
|
||||
};
|
||||
|
||||
const state: BasicTableProps = reactive<BasicTableProps>({
|
||||
pageList: (params?: any) => getDocProcessList(mode.value, params),
|
||||
queryForm: getQueryForm(),
|
||||
createdIsNeed: true
|
||||
pageList: (params?: any) => getDocProcessList(mode.value, params),
|
||||
queryForm: getQueryForm(),
|
||||
createdIsNeed: true,
|
||||
});
|
||||
|
||||
const { getDataList, tableStyle, sizeChangeHandle, currentChangeHandle } = useTable(state);
|
||||
|
||||
const handleReset = () => {
|
||||
searchFormRef.value?.resetFields();
|
||||
getDataList();
|
||||
searchFormRef.value?.resetFields();
|
||||
getDataList();
|
||||
};
|
||||
|
||||
// 获取行状态(兼容两种字段名)
|
||||
const getRowStatus = (row: any) => {
|
||||
return mode.value === 'agent' ? row.status : row.docAuditStatus
|
||||
}
|
||||
return mode.value === 'agent' ? row.status : row.docAuditStatus;
|
||||
};
|
||||
|
||||
const getStatusType = (status: string) => {
|
||||
const typeMap: Record<string, string> = {
|
||||
'PENDING_UPLOAD': 'info',
|
||||
'DRAFT': 'info',
|
||||
'ASSET_REVIEWING': 'warning',
|
||||
'DEPT_REVIEWING': 'warning',
|
||||
'AUDIT_REVIEWING': 'warning',
|
||||
'ASSET_CONFIRMING': 'primary',
|
||||
'COMPLETED': 'success',
|
||||
'RETURNED': 'danger'
|
||||
};
|
||||
return typeMap[status] || 'info';
|
||||
const typeMap: Record<string, string> = {
|
||||
PENDING_UPLOAD: 'info',
|
||||
DRAFT: 'info',
|
||||
ASSET_REVIEWING: 'warning',
|
||||
DEPT_REVIEWING: 'warning',
|
||||
AUDIT_REVIEWING: 'warning',
|
||||
ASSET_CONFIRMING: 'primary',
|
||||
COMPLETED: 'success',
|
||||
RETURNED: 'danger',
|
||||
};
|
||||
return typeMap[status] || 'info';
|
||||
};
|
||||
|
||||
const getStatusLabel = (status: string) => {
|
||||
const labelMap: Record<string, string> = {
|
||||
'PENDING_UPLOAD': '待上传',
|
||||
'DRAFT': '草稿',
|
||||
'ASSET_REVIEWING': '资产管理处审核中',
|
||||
'DEPT_REVIEWING': '需求部门审核中',
|
||||
'AUDIT_REVIEWING': '内审部门审核中',
|
||||
'ASSET_CONFIRMING': '资产管理处确认中',
|
||||
'COMPLETED': '已完成',
|
||||
'RETURNED': '已退回'
|
||||
};
|
||||
return labelMap[status] || '-';
|
||||
const labelMap: Record<string, string> = {
|
||||
PENDING_UPLOAD: '待上传',
|
||||
DRAFT: '草稿',
|
||||
ASSET_REVIEWING: '资产管理处审核中',
|
||||
DEPT_REVIEWING: '需求部门审核中',
|
||||
AUDIT_REVIEWING: '内审部门审核中',
|
||||
ASSET_CONFIRMING: '资产管理处确认中',
|
||||
COMPLETED: '已完成',
|
||||
RETURNED: '已退回',
|
||||
};
|
||||
return labelMap[status] || '-';
|
||||
};
|
||||
|
||||
const handleProcess = (row: any) => {
|
||||
docProcessDialogRef.value?.open(row);
|
||||
docProcessDialogRef.value?.open(row);
|
||||
};
|
||||
|
||||
// 监听路由参数变化,重新加载数据
|
||||
onMounted(() => {
|
||||
// 重置查询表单
|
||||
state.queryForm = getQueryForm()
|
||||
// 重置查询表单
|
||||
state.queryForm = getQueryForm();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '/@/assets/styles/modern-page.scss';
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,120 +1,115 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="dialogTitle"
|
||||
width="90%"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
class="form-iframe-dialog"
|
||||
@close="handleClose"
|
||||
>
|
||||
<div class="form-iframe-content">
|
||||
<iframe
|
||||
ref="iframeRef"
|
||||
:src="iframeSrc"
|
||||
frameborder="0"
|
||||
class="form-iframe"
|
||||
/>
|
||||
</div>
|
||||
</el-dialog>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="dialogTitle"
|
||||
width="90%"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
class="form-iframe-dialog"
|
||||
@close="handleClose"
|
||||
>
|
||||
<div class="form-iframe-content">
|
||||
<iframe ref="iframeRef" :src="iframeSrc" frameborder="0" class="form-iframe" />
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="PurchasingRequisitionForm">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { ref, computed, watch } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
dictData?: Record<string, any>
|
||||
}>()
|
||||
dictData?: Record<string, any>;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'refresh'): void
|
||||
}>()
|
||||
(e: 'refresh'): void;
|
||||
}>();
|
||||
|
||||
const visible = ref(false)
|
||||
const iframeRef = ref<HTMLIFrameElement>()
|
||||
const mode = ref<'add' | 'edit' | 'view'>('add')
|
||||
const rowId = ref<string | number>('')
|
||||
const visible = ref(false);
|
||||
const iframeRef = ref<HTMLIFrameElement>();
|
||||
const mode = ref<'add' | 'edit' | 'view'>('add');
|
||||
const rowId = ref<string | number>('');
|
||||
|
||||
const dialogTitle = computed(() => {
|
||||
const titles = {
|
||||
add: '新增采购申请',
|
||||
edit: '编辑采购申请',
|
||||
view: '查看采购申请',
|
||||
}
|
||||
return titles[mode.value] || titles.add
|
||||
})
|
||||
const titles = {
|
||||
add: '新增采购申请',
|
||||
edit: '编辑采购申请',
|
||||
view: '查看采购申请',
|
||||
};
|
||||
return titles[mode.value] || titles.add;
|
||||
});
|
||||
|
||||
const iframeSrc = computed(() => {
|
||||
const baseUrl = window.location.origin + window.location.pathname
|
||||
let src = `${baseUrl}#/purchase/purchasingrequisition/add`
|
||||
if (mode.value !== 'add' && rowId.value) {
|
||||
src += `?mode=${mode.value}&id=${rowId.value}`
|
||||
}
|
||||
return src
|
||||
})
|
||||
const baseUrl = window.location.origin + window.location.pathname;
|
||||
let src = `${baseUrl}#/purchase/purchasingrequisition/add`;
|
||||
if (mode.value !== 'add' && rowId.value) {
|
||||
src += `?mode=${mode.value}&id=${rowId.value}`;
|
||||
}
|
||||
return src;
|
||||
});
|
||||
|
||||
const handleClose = () => {
|
||||
visible.value = false
|
||||
window.removeEventListener('message', handleMessage)
|
||||
}
|
||||
visible.value = false;
|
||||
window.removeEventListener('message', handleMessage);
|
||||
};
|
||||
|
||||
const handleMessage = (event: MessageEvent) => {
|
||||
if (event.data?.type === 'purchasingrequisition:submitSuccess') {
|
||||
handleClose()
|
||||
emit('refresh')
|
||||
} else if (event.data?.type === 'purchasingrequisition:close') {
|
||||
handleClose()
|
||||
}
|
||||
}
|
||||
if (event.data?.type === 'purchasingrequisition:submitSuccess') {
|
||||
handleClose();
|
||||
emit('refresh');
|
||||
} else if (event.data?.type === 'purchasingrequisition:close') {
|
||||
handleClose();
|
||||
}
|
||||
};
|
||||
|
||||
const openDialog = (openMode: 'add' | 'edit' | 'view', row?: any) => {
|
||||
mode.value = openMode
|
||||
rowId.value = row?.id ?? ''
|
||||
visible.value = true
|
||||
window.addEventListener('message', handleMessage)
|
||||
}
|
||||
mode.value = openMode;
|
||||
rowId.value = row?.id ?? '';
|
||||
visible.value = true;
|
||||
window.addEventListener('message', handleMessage);
|
||||
};
|
||||
|
||||
watch(visible, (val) => {
|
||||
if (!val) {
|
||||
window.removeEventListener('message', handleMessage)
|
||||
}
|
||||
})
|
||||
if (!val) {
|
||||
window.removeEventListener('message', handleMessage);
|
||||
}
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
openDialog,
|
||||
})
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.form-iframe-content {
|
||||
width: 100%;
|
||||
height: 70vh;
|
||||
min-height: 500px;
|
||||
max-height: calc(100vh - 200px);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 70vh;
|
||||
min-height: 500px;
|
||||
max-height: calc(100vh - 200px);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
.form-iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 500px;
|
||||
border: none;
|
||||
display: block;
|
||||
}
|
||||
.form-iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 500px;
|
||||
border: none;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.form-iframe-dialog.el-dialog {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 90vh;
|
||||
margin-top: 5vh !important;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 90vh;
|
||||
margin-top: 5vh !important;
|
||||
}
|
||||
.form-iframe-dialog .el-dialog__body {
|
||||
padding: 20px;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 20px;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,84 +1,73 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
title="实施采购"
|
||||
width="800px"
|
||||
:close-on-click-modal="false"
|
||||
destroy-on-close
|
||||
@close="handleClose"
|
||||
>
|
||||
<ImplementContent
|
||||
ref="implementContentRef"
|
||||
@close="handleContentClose"
|
||||
@saved="handleContentSaved"
|
||||
/>
|
||||
<el-dialog v-model="visible" title="实施采购" width="800px" :close-on-click-modal="false" destroy-on-close @close="handleClose">
|
||||
<ImplementContent ref="implementContentRef" @close="handleContentClose" @saved="handleContentSaved" />
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" :loading="confirming" @click="handleConfirm">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" :loading="confirming" @click="handleConfirm">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="ImplementForm">
|
||||
import { ref, nextTick } from 'vue'
|
||||
import ImplementContent from './implement.vue'
|
||||
import { ref, nextTick } from 'vue';
|
||||
import ImplementContent from './implement.vue';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'refresh'): void
|
||||
}>()
|
||||
(e: 'refresh'): void;
|
||||
}>();
|
||||
|
||||
const visible = ref(false)
|
||||
const implementContentRef = ref<InstanceType<typeof ImplementContent>>()
|
||||
const confirming = ref(false)
|
||||
const pendingRow = ref<{ id: string | number } | null>(null)
|
||||
const visible = ref(false);
|
||||
const implementContentRef = ref<InstanceType<typeof ImplementContent>>();
|
||||
const confirming = ref(false);
|
||||
const pendingRow = ref<{ id: string | number } | null>(null);
|
||||
|
||||
const handleClose = () => {
|
||||
visible.value = false
|
||||
}
|
||||
visible.value = false;
|
||||
};
|
||||
|
||||
const handleContentClose = () => {
|
||||
visible.value = false
|
||||
emit('refresh')
|
||||
}
|
||||
visible.value = false;
|
||||
emit('refresh');
|
||||
};
|
||||
|
||||
const handleContentSaved = () => {
|
||||
emit('refresh')
|
||||
}
|
||||
emit('refresh');
|
||||
};
|
||||
|
||||
const handleConfirm = async () => {
|
||||
confirming.value = true
|
||||
try {
|
||||
await implementContentRef.value?.handleConfirm()
|
||||
visible.value = false
|
||||
emit('refresh')
|
||||
} finally {
|
||||
confirming.value = false
|
||||
}
|
||||
}
|
||||
confirming.value = true;
|
||||
try {
|
||||
await implementContentRef.value?.handleConfirm();
|
||||
visible.value = false;
|
||||
emit('refresh');
|
||||
} finally {
|
||||
confirming.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const openDialog = async (row: { id: string | number }) => {
|
||||
pendingRow.value = row
|
||||
visible.value = true
|
||||
// 等待 dialog 及内部组件挂载完成后再调用 open
|
||||
await nextTick()
|
||||
if (pendingRow.value) {
|
||||
implementContentRef.value?.open(pendingRow.value)
|
||||
pendingRow.value = null
|
||||
}
|
||||
}
|
||||
pendingRow.value = row;
|
||||
visible.value = true;
|
||||
// 等待 dialog 及内部组件挂载完成后再调用 open
|
||||
await nextTick();
|
||||
if (pendingRow.value) {
|
||||
implementContentRef.value?.open(pendingRow.value);
|
||||
pendingRow.value = null;
|
||||
}
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
openDialog,
|
||||
})
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user