This commit is contained in:
吴红兵
2026-03-08 23:23:02 +08:00
parent 42ab802371
commit 4b33dc9aab
2 changed files with 231 additions and 2 deletions

View File

@@ -252,6 +252,19 @@ export function saveRepresentor(id: number, representorTeacherNo?: string, repre
}); });
} }
/**
* 随机抽取部门参与人
* @param applyId 采购申请ID
* @param memberTeacherNos 参与随机抽取的人员工号列表(逗号分隔)
*/
export function randomSelectRepresentor(applyId: string, memberTeacherNos: string) {
return request({
url: '/purchase/purchasingapply/randomSelectRepresentor',
method: 'post',
data: { applyId, memberTeacherNos },
});
}
/** /**
* 文件归档按文件类型打包下载该申请单下所有附件的下载地址GET 请求,浏览器直接下载 zip * 文件归档按文件类型打包下载该申请单下所有附件的下载地址GET 请求,浏览器直接下载 zip
* @param purchaseId 采购申请ID * @param purchaseId 采购申请ID

View File

@@ -208,13 +208,109 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
</el-card> </el-card>
<!-- 部门参与人选择部门负责人审核时显示 -->
<el-card v-if="showRepresentorSection" shadow="never" class="representor-card">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon><User /></el-icon>
部门参与人
</span>
<el-tag v-if="applyData.representorTeacherNo" type="success">已设置</el-tag>
<el-tag v-else type="warning">待设置</el-tag>
</div>
</template>
<el-form label-width="120px">
<el-form-item label="选择方式">
<el-radio-group v-model="representorSelectMode" :disabled="isViewMode">
<el-radio label="designate">指定一人</el-radio>
<el-radio label="random">随机抽取</el-radio>
</el-radio-group>
</el-form-item>
<!-- 指定一人 -->
<el-form-item v-if="representorSelectMode === 'designate'" label="选择参与人">
<el-select
v-model="selectedRepresentor"
placeholder="请选择参与人"
filterable
:disabled="isViewMode"
style="width: 300px"
>
<el-option
v-for="member in deptMembers"
:key="member.teacherNo"
:label="`${member.realName} (${member.teacherNo})`"
:value="member.teacherNo"
/>
</el-select>
</el-form-item>
<!-- 随机抽取 -->
<el-form-item v-if="representorSelectMode === 'random'" label="选择候选人">
<el-select
v-model="randomCandidates"
placeholder="请选择参与随机抽取的人员(可多选)"
multiple
filterable
:disabled="isViewMode"
style="width: 400px"
>
<el-option
v-for="member in deptMembers"
:key="member.teacherNo"
:label="`${member.realName} (${member.teacherNo})`"
:value="member.teacherNo"
/>
</el-select>
<el-button
type="primary"
:loading="randomSelectLoading"
:disabled="randomCandidates.length < 2 || isViewMode"
style="margin-left: 12px"
@click="handleRandomSelect"
>
随机抽取
</el-button>
</el-form-item>
<!-- 已选中的参与人 -->
<el-form-item v-if="currentRepresentor" label="已选中参与人">
<el-tag type="success" size="large">
{{ currentRepresentor.realName }} ({{ currentRepresentor.teacherNo }})
</el-tag>
</el-form-item>
<!-- 参与人身份 -->
<el-form-item label="参与人身份" required>
<el-radio-group v-model="representorType" :disabled="isViewMode">
<el-radio label="purchase_rep">采购代表</el-radio>
<el-radio label="judge">评委</el-radio>
</el-radio-group>
</el-form-item>
<!-- 保存按钮 -->
<el-form-item>
<el-button
type="primary"
:loading="saveRepresentorLoading"
:disabled="!canSaveRepresentor || isViewMode"
@click="handleSaveRepresentor"
>
保存参与人信息
</el-button>
</el-form-item>
</el-form>
</el-card>
</div> </div>
</template> </template>
<script setup lang="ts" name="BidFileAudit"> <script setup lang="ts" name="BidFileAudit">
import { ref, reactive, computed, onMounted, watch } from 'vue'; import { ref, reactive, computed, onMounted, watch } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { Document, FolderOpened, Upload, Link, Guide } from '@element-plus/icons-vue'; import { Document, FolderOpened, Upload, Link, Guide, User } from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import { Session } from '/@/utils/storage'; import { Session } from '/@/utils/storage';
import { useUserInfo } from '/@/stores/userInfo'; import { useUserInfo } from '/@/stores/userInfo';
@@ -232,6 +328,7 @@ import {
} from '/@/api/purchase/bidfile'; } from '/@/api/purchase/bidfile';
import { getRequirementFiles } from '/@/api/purchase/purchasingfiles'; import { getRequirementFiles } from '/@/api/purchase/purchasingfiles';
import { currElTabIsSave } from '/@/api/order/order-key-vue'; import { currElTabIsSave } from '/@/api/order/order-key-vue';
import { getDeptMembers, saveRepresentor, randomSelectRepresentor } from '/@/api/purchase/purchasingrequisition';
// ==================== Props & Emits ==================== // ==================== Props & Emits ====================
@@ -252,7 +349,8 @@ const currentUserRoleCodes = computed(() => userStore.userInfos.roleCodes || [])
const ROLE_LABEL_MAP: Record<string, string> = { const ROLE_LABEL_MAP: Record<string, string> = {
PURCHASE_AGENT: '招标代理', PURCHASE_AGENT: '招标代理',
PURCHASE_ASSET: '资产管理处', PURCHASE_ASSET: '资产管理处',
PURCHASE_DEPT_APPLY: '部门负责人', PURCHASE_DEPT_APPLY: '部门经办人',
PURCHASE_DEPT_AUDIT: '部门负责人',
PURCHASE_FILE_AUDIT: '内审部门', PURCHASE_FILE_AUDIT: '内审部门',
PURCHASE_CENTER: '采购中心', PURCHASE_CENTER: '采购中心',
}; };
@@ -295,6 +393,7 @@ const isViewMode = computed(() => {
const isAgent = computed(() => currentUserRoleCodes.value.includes('PURCHASE_AGENT')); const isAgent = computed(() => currentUserRoleCodes.value.includes('PURCHASE_AGENT'));
const isAsset = computed(() => currentUserRoleCodes.value.includes('PURCHASE_ASSET')); const isAsset = computed(() => currentUserRoleCodes.value.includes('PURCHASE_ASSET'));
const isDeptApply = computed(() => currentUserRoleCodes.value.includes('PURCHASE_DEPT_APPLY')); const isDeptApply = computed(() => currentUserRoleCodes.value.includes('PURCHASE_DEPT_APPLY'));
const isDeptAudit = computed(() => currentUserRoleCodes.value.includes('PURCHASE_DEPT_AUDIT'));
const isFileAudit = computed(() => currentUserRoleCodes.value.includes('PURCHASE_FILE_AUDIT')); const isFileAudit = computed(() => currentUserRoleCodes.value.includes('PURCHASE_FILE_AUDIT'));
// 是否显示上传区域 // 是否显示上传区域
@@ -311,6 +410,12 @@ const showFlowTargetSection = computed(() => {
return isAsset.value && isFlowEmbed.value; return isAsset.value && isFlowEmbed.value;
}); });
// 是否显示部门参与人选择区域(仅部门负责人在审核时显示)
const showRepresentorSection = computed(() => {
if (isViewMode.value) return false;
return isDeptAudit.value && isFlowEmbed.value;
});
// 采购申请数据 // 采购申请数据
const applyData = ref<any>({}); const applyData = ref<any>({});
const purchaseTypeLabel = computed(() => { const purchaseTypeLabel = computed(() => {
@@ -351,6 +456,24 @@ const uploadRules = {
// 流转去向(资产管理处审核时使用) // 流转去向(资产管理处审核时使用)
const flowTarget = ref<string>(''); const flowTarget = ref<string>('');
// 部门参与人选择相关(部门负责人审核时使用)
const representorSelectMode = ref<'designate' | 'random'>('designate');
const deptMembers = ref<any[]>([]);
const deptMembersLoading = ref(false);
const selectedRepresentor = ref<string>('');
const randomCandidates = ref<string[]>([]);
const currentRepresentor = ref<any>(null);
const representorType = ref<string>('purchase_rep');
const saveRepresentorLoading = ref(false);
const randomSelectLoading = ref(false);
const canSaveRepresentor = computed(() => {
if (representorSelectMode.value === 'designate') {
return !!selectedRepresentor.value;
}
return !!currentRepresentor.value;
});
// ==================== 计算属性 ==================== // ==================== 计算属性 ====================
const BID_FILE_TYPE = '130'; const BID_FILE_TYPE = '130';
@@ -634,6 +757,93 @@ const registerFlowCallbacks = () => {
} }
}; };
// ==================== 部门参与人选择 ====================
const loadDeptMembers = async () => {
if (!showRepresentorSection.value) return;
try {
deptMembersLoading.value = true;
const res = await getDeptMembers();
if (res.code === 0 && res.data) {
deptMembers.value = res.data;
}
} catch (e: any) {
ElMessage.error(e?.msg || '加载部门人员失败');
} finally {
deptMembersLoading.value = false;
}
};
const handleRandomSelect = async () => {
if (randomCandidates.value.length < 2) {
ElMessage.warning('请至少选择2名候选人');
return;
}
try {
randomSelectLoading.value = true;
const res = await randomSelectRepresentor(
effectivePurchaseId.value,
randomCandidates.value.join(',')
);
if (res.code === 0 && res.data) {
currentRepresentor.value = res.data;
ElMessage.success(`随机抽取成功:${res.data.realName}`);
} else {
ElMessage.error(res.msg || '随机抽取失败');
}
} catch (e: any) {
ElMessage.error(e?.msg || '随机抽取失败');
} finally {
randomSelectLoading.value = false;
}
};
const handleSaveRepresentor = async () => {
if (!canSaveRepresentor.value) {
ElMessage.warning('请先选择参与人');
return;
}
if (!representorType.value) {
ElMessage.warning('请选择参与人身份');
return;
}
try {
saveRepresentorLoading.value = true;
let res: any;
if (representorSelectMode.value === 'designate') {
res = await saveRepresentor(
Number(effectivePurchaseId.value),
selectedRepresentor.value,
undefined,
representorType.value
);
} else {
res = await saveRepresentor(
Number(effectivePurchaseId.value),
currentRepresentor.value?.teacherNo,
randomCandidates.value.join(','),
representorType.value
);
}
if (res.code === 0) {
ElMessage.success('保存参与人信息成功');
await loadApplyData();
} else {
ElMessage.error(res.msg || '保存失败');
}
} catch (e: any) {
ElMessage.error(e?.msg || '保存失败');
} finally {
saveRepresentorLoading.value = false;
}
};
// ==================== 生命周期 ==================== // ==================== 生命周期 ====================
onMounted(async () => { onMounted(async () => {
@@ -642,6 +852,9 @@ onMounted(async () => {
if (isAgent.value) { if (isAgent.value) {
await loadRequirementFiles(); await loadRequirementFiles();
} }
if (showRepresentorSection.value) {
await loadDeptMembers();
}
registerFlowCallbacks(); registerFlowCallbacks();
}); });
@@ -654,6 +867,9 @@ watch(
if (isAgent.value) { if (isAgent.value) {
await loadRequirementFiles(); await loadRequirementFiles();
} }
if (showRepresentorSection.value) {
await loadDeptMembers();
}
registerFlowCallbacks(); registerFlowCallbacks();
} }
}, },