Files
td_official/src/views/design/Professional/indexEdit.vue
2025-08-27 19:50:22 +08:00

655 lines
20 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="p-4 bg-gray-50">
<div class="max-w-4xl mx-auto">
<!-- 顶部按钮区域 -->
<el-card class="mb-4 rounded-lg shadow-sm bg-white border border-gray-100 transition-all hover:shadow-md">
<approvalButton
@submitForm="submitForm"
@approvalVerifyOpen="approvalVerifyOpen"
@handleApprovalRecord="handleApprovalRecord"
:buttonLoading="buttonLoading"
:id="form.id"
:status="form.status"
:pageType="routeParams.type"
/>
</el-card>
<!-- 表单区域 -->
<el-card class="rounded-lg shadow-sm bg-white border border-gray-100 transition-all hover:shadow-md overflow-hidden">
<div class="p-4 bg-gradient-to-r from-blue-50 to-indigo-50 border-b border-gray-100">
<h3 class="text-lg font-semibold text-gray-800">专业互提资料</h3>
</div>
<div class="p-6">
<div class="appWidth mx-auto bg-white rounded-xl shadow-sm overflow-hidden transition-all duration-300 hover:shadow-md">
<!-- 主表单基础信息校验 -->
<el-form :disabled="disableAll" ref="mainFormRef" :model="form" :rules="mainRules" label-width="120px" class="p-6">
<!-- 基本信息区域 -->
<div class="bg-blue-50 p-4 rounded-lg mb-6">
<h3 class="text-lg font-semibold text-blue-700 mb-4">基本信息</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<el-form-item label="提资人" class="mb-4">
<el-input placeholder="请输入提资人" disabled v-model="userInfo.nickName" autocomplete="off" />
</el-form-item>
<el-form-item label="专业" prop="user_major" class="mb-4">
<el-select v-model="form.user_major" placeholder="请选择专业" class="transition-all duration-300 border-gray-300">
<el-option v-for="item in des_user_major" :key="item.userMajor" :label="item.userMajorName" :value="item.userMajor" />
</el-select>
</el-form-item>
<el-form-item label="电话" prop="phone" class="mb-4">
<el-input placeholder="请输入电话" v-model="form.phone" autocomplete="off" />
</el-form-item>
<el-form-item label="邮箱" prop="email" class="mb-4">
<el-input placeholder="请输入邮箱" v-model="form.email" autocomplete="off" />
</el-form-item>
</div>
</div>
<!-- 资料文件区域单独表单校验 -->
<div class="mb-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-blue-700">资料文件清单</h3>
<el-button type="primary" size="small" @click="addDocumentItem" icon="Plus"> 添加资料 </el-button>
</div>
<el-form :disabled="disableAll" ref="documentsFormRef" :model="form" class="space-y-4" label-width="120px">
<div
v-for="(item, index) in form.documents"
:key="item.id"
class="bg-gray-50 p-4 rounded-lg transition-all duration-200 hover:shadow-sm"
>
<div class="flex justify-between items-start mb-2">
<span class="text-sm font-medium text-gray-600">资料 {{ index + 1 }}</span>
<el-button
type="text"
size="small"
text-color="#ff4d4f"
@click="removeDocumentItem(index)"
icon="el-icon-delete"
v-if="form.documents.length > 1"
>
删除
</el-button>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- 资料名称补充验证规则 -->
<el-form-item
label="资料名称"
:prop="`documents.${index}.volumeCatalogId`"
:rules="[
{ required: true, message: '请选择资料名称', trigger: 'change' },
{ required: true, message: '请选择资料名称', trigger: 'blur' }
]"
class="mb-4"
>
<el-select
id="projectSelect"
v-model="item.volumeCatalogId"
placeholder="请选择资料"
clearable
filterable
@change="handleSelect($event, item)"
style="width: 100%"
>
<el-option
v-for="project in volumeCatalogList"
:key="project.design"
:label="project.documentName"
:value="project.design"
/>
</el-select>
</el-form-item>
<el-form-item label="备注" :prop="`documents.${index}.remark`" class="mb-4">
<el-input placeholder="请输入备注" v-model="item.remark" autocomplete="off" />
</el-form-item>
</div>
</div>
</el-form>
</div>
</el-form>
</div>
</div>
</el-card>
<!-- 提交验证组件 -->
<submitVerify ref="submitVerifyRef" :task-variables="taskVariables" @submit-callback="submitCallback" />
<!-- 审批记录组件 -->
<approvalRecord ref="approvalRecordRef"></approvalRecord>
<!-- 流程选择对话框 -->
<el-dialog
draggable
v-model="dialogVisible.visible"
:title="dialogVisible.title"
:before-close="handleClose"
width="500"
class="rounded-lg shadow-lg"
>
<div class="p-4">
<p class="text-gray-600 mb-4">请选择要启动的流程</p>
<el-select v-model="flowCode" placeholder="请选择流程" style="width: 100%">
<el-option v-for="item in flowCodeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
<template #footer>
<div class="dialog-footer p-4 border-t border-gray-100 flex justify-end space-x-3">
<el-button @click="handleClose" class="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 transition-colors"
>取消</el-button
>
<el-button type="primary" @click="submitFlow()" class="px-4 py-2 bg-primary text-white rounded-md hover:bg-primary/90 transition-colors"
>确认</el-button
>
</div>
</template>
</el-dialog>
</div>
</div>
</template>
<script setup name="Leave" lang="ts">
import { LeaveForm } from '@/api/workflow/leave/types';
import { startWorkFlow } from '@/api/workflow/task';
import SubmitVerify from '@/components/Process/submitVerify.vue';
import ApprovalRecord from '@/components/Process/approvalRecord.vue';
import ApprovalButton from '@/components/Process/approvalButton.vue';
import { StartProcessBo } from '@/api/workflow/workflowCommon/types';
import { ComponentInternalInstance, nextTick, ref, reactive, computed, toRefs, onMounted } from 'vue';
import { useUserStoreHook } from '@/store/modules/user';
import { systemUserList } from '@/api/design/appointment';
import { extractBatch, extractDetail, extractUserMajor } from '@/api/design/Professional';
import { listVolumeCatalog } from '@/api/design/volumeCatalog';
import { catalogList } from '@/api/design/designChange';
import { getUser } from '@/api/system/user';
import { ElFormInstance, ElMessage } from 'element-plus';
// 类型定义
interface DialogOption {
visible: boolean;
title: string;
}
interface DocumentItem {
id: number;
catalogueName: string;
remark: string;
volumeCatalogId: string;
num?: number;
}
interface FormData {
projectId: string | undefined;
userId: string;
user_major: string;
phone: string;
email: string;
id: string;
status: string;
documents: DocumentItem[];
}
// 获取组件实例
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
// 用户状态管理
const userStore = useUserStoreHook();
const currentProject = computed(() => userStore.selectedProject);
const userId = computed(() => userStore.userId);
// 基础数据
const userInfo = ref({
nickName: '',
email: '',
phonenumber: '',
userId: ''
});
// const { des_user_major } = toRefs<any>(proxy?.useDict('des_user_major'));
const des_user_major = ref([]);
const buttonLoading = ref(false);
const loading = ref(true);
const disableAll = ref(false);
const volumeCatalogList = ref([]);
const volumeMap = new Map();
const routeParams = ref<Record<string, any>>({});
// 流程相关
const flowCodeOptions = [
{
value: currentProject.value?.id + '_extract',
label: '互提资料清单'
}
];
const flowCode = ref<string>('');
const status = ref<string>('');
const dialogVisible = reactive<DialogOption>({
visible: false,
title: '流程定义'
});
// 组件引用
const submitVerifyRef = ref<InstanceType<typeof SubmitVerify>>();
const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>();
const mainFormRef = ref<ElFormInstance>(); // 主表单引用
const documentsFormRef = ref<ElFormInstance>(); // 资料列表表单引用
// 表单数据
const form = reactive<FormData>({
projectId: currentProject.value?.id,
userId: '',
user_major: '',
phone: '',
email: '',
id: '',
status: '',
documents: [
{
id: Date.now(),
catalogueName: '',
remark: '',
volumeCatalogId: ''
}
]
});
// 主表单验证规则
const mainRules = reactive({
userId: [{ required: true, message: '请输入收资人', trigger: 'blur' }],
user_major: [{ required: true, message: '请选择专业', trigger: 'change' }],
phone: [
{ required: true, message: '请输入电话', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
],
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
]
});
// 用户列表相关
const userList = ref([]);
const userMap = new Map();
// 流程提交数据
const submitFormData = ref<StartProcessBo>({
businessId: '',
flowCode: '',
variables: {}
});
const taskVariables = ref<Record<string, any>>({});
/** 表单重置 */
const reset = () => {
mainFormRef.value?.resetFields();
documentsFormRef.value?.resetFields();
// 重置资料列表,保留一个空项
form.documents = [
{
id: Date.now(),
catalogueName: '',
remark: '',
volumeCatalogId: ''
}
];
};
/** 添加资料项 */
const addDocumentItem = () => {
form.documents.push({
id: Date.now(),
catalogueName: '',
remark: '',
volumeCatalogId: ''
});
};
/** 删除资料项 */
const removeDocumentItem = (index: number) => {
form.documents.splice(index, 1);
};
/** 关闭流程选择对话框 */
const handleClose = () => {
dialogVisible.visible = false;
flowCode.value = '';
buttonLoading.value = false;
};
/** 提交表单(主逻辑) */
const submitForm = (status1: string) => {
status.value = status1;
buttonLoading.value = true;
// 1. 校验主表单(基础信息)
mainFormRef.value?.validate(async (mainValid) => {
if (!mainValid) {
proxy?.$modal.msgError('请完善基础必填信息');
buttonLoading.value = false;
return;
}
// 2. 校验资料列表表单(资料名称)
documentsFormRef.value?.validate(async (docsValid) => {
if (!docsValid) {
proxy?.$modal.msgError('请完善所有资料的“资料名称”');
buttonLoading.value = false;
return;
}
// 3. 表单校验通过,处理资料序号
form.documents.map((item, i) => {
item.num = i + 1;
});
// 4. 提交数据到后端
const body = {
desExtractBo: {
projectId: currentProject.value?.id,
userId: form.userId,
userMajor: form.user_major,
id: form.id,
phone: form.phone,
email: form.email,
userName: userMap.get(form.userId)
},
catalogueList: form.documents
};
try {
const res = await extractBatch(body);
if (res.code === 200) {
form.id = res.data;
await submit(status.value, form); // 执行暂存/提交流程
} else {
ElMessage.error(res.msg);
}
} catch (err) {
ElMessage.error('提交失败,请重试');
} finally {
buttonLoading.value = false;
}
});
});
};
/** 提交流程选择 */
const submitFlow = async () => {
await handleStartWorkFlow(form);
dialogVisible.visible = false;
};
/** 启动工作流 */
const handleStartWorkFlow = async (data: LeaveForm) => {
try {
submitFormData.value.flowCode = flowCode.value;
submitFormData.value.businessId = data.id;
// 流程变量配置
taskVariables.value = {
userList: ['1', '3', '4']
};
submitFormData.value.variables = taskVariables.value;
const resp = await startWorkFlow(submitFormData.value);
if (submitVerifyRef.value) {
submitVerifyRef.value.openDialog(resp.data.taskId);
}
} catch (err) {
ElMessage.error('启动流程失败');
} finally {
buttonLoading.value = false;
}
};
/** 打开审批记录 */
const handleApprovalRecord = () => {
approvalRecordRef.value?.init(form.id);
};
/** 提交回调:关闭页面返回上一级 */
const submitCallback = async () => {
await proxy.$tab.closePage(proxy.$route);
proxy.$router.go(-1);
};
/** 打开审批验证弹窗 */
const approvalVerifyOpen = async () => {
submitVerifyRef.value?.openDialog(routeParams.value.taskId);
};
/** 暂存/提交分支逻辑 */
const submit = async (status: string, data: FormData) => {
if (status === 'draft') {
proxy?.$modal.msgSuccess('暂存成功');
await proxy.$tab.closePage(proxy.$route);
proxy.$router.go(-1);
} else {
// 新增/草稿状态下,默认选择第一个流程并弹窗
if ((data.status === 'draft' && !flowCode.value) || routeParams.value.type === 'add') {
flowCode.value = flowCodeOptions[0].value;
dialogVisible.visible = true;
return;
}
// 已有流程时默认填充占位值
if (!flowCode.value) {
flowCode.value = 'xx';
}
await handleStartWorkFlow(data);
}
};
/** 获取部门用户列表 */
const getDeptAllUser = async (deptId: any) => {
try {
const res = await systemUserList({ deptId });
userList.value = res.rows;
userList.value.forEach((user) => {
userMap.set(user.userId, user.nickName);
});
} catch (err) {
ElMessage.error('获取用户列表失败');
}
};
// 获取专业
const getMajor = async () => {
let res = await extractUserMajor({ userId: userId.value, projectId: currentProject.value?.id });
if (res.code == 200) {
des_user_major.value = res.data;
if (res.data.length > 0) {
form.user_major = res.data[0].userMajor;
}
}
};
/** 回显表单数据(编辑/查看/审批场景) */
const byProjectIdAll = async () => {
loading.value = true;
try {
const res = await extractDetail(routeParams.value.id);
if (res.code === 200 && res.data) {
const data = res.data;
// 回显基础信息
form.userId = data.userId || '';
form.user_major = data.userMajor || '';
form.phone = data.phone || '';
form.email = data.email || '';
form.id = data.id || '';
form.status = data.status || '';
// 非草稿状态禁用表单
if (data.status !== 'draft') {
disableAll.value = true;
}
// 回显资料列表
if (data.catalogueList && data.catalogueList.length > 0) {
form.documents = data.catalogueList.map((item: any, index: number) => ({
id: item.id || Date.now() + index,
catalogueName: item.catalogueName || '',
remark: item.remark || '',
volumeCatalogId: item.volumeCatalogId || ''
}));
} else {
form.documents = [
{
id: Date.now(),
catalogueName: '',
remark: '',
volumeCatalogId: ''
}
];
}
}
} catch (err) {
ElMessage.error('获取表单数据失败');
} finally {
loading.value = false;
buttonLoading.value = false;
}
};
/** 获取当前用户详情 */
const getUserDetail = async () => {
try {
const res = await getUser(userId.value);
userInfo.value = res.data.user;
form.userId = userInfo.value.userId;
form.phone = userInfo.value.phonenumber;
form.email = userInfo.value.email;
} catch (err) {
ElMessage.error('获取用户信息失败');
}
};
/** 获取卷册目录列表 */
const getList = async () => {
try {
const res = await catalogList(currentProject.value?.id);
volumeCatalogList.value = res.data;
volumeCatalogList.value.forEach((e) => {
volumeMap.set(e.design, e);
});
} catch (err) {
ElMessage.error('获取卷册目录失败');
}
};
/** 选择资料名称后回显目录名称 */
const handleSelect = (val: string, item: DocumentItem) => {
item.catalogueName = volumeMap.get(val)?.documentName || '';
};
/** 页面挂载初始化 */
onMounted(() => {
nextTick(async () => {
routeParams.value = proxy.$route.query;
reset();
loading.value = false;
// 初始化基础数据
await getList();
await getUserDetail();
// 编辑/查看/审批场景:加载已有数据
const { type } = routeParams.value;
await getMajor();
if (type === 'update' || type === 'view' || type === 'approval') {
await getDeptAllUser(userStore.deptId);
await byProjectIdAll();
} else {
await getDeptAllUser(userStore.deptId);
}
});
});
</script>
<style scoped lang="scss">
/* 全局变量 */
:root {
--primary: #409eff;
--primary-light: #66b1ff;
--primary-dark: #3a8ee6;
--success: #67c23a;
--warning: #e6a23c;
--danger: #f56c6c;
--info: #909399;
}
/* 表单样式优化 */
.el-form-item {
.el-form-item__label {
color: #606266;
font-weight: 500;
}
.el-input__inner,
.el-select .el-input__inner {
border-radius: 4px;
transition:
border-color 0.2s,
box-shadow 0.2s;
&:focus {
border-color: var(--primary-light);
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.1);
}
}
.el-textarea__inner {
border-radius: 4px;
transition:
border-color 0.2s,
box-shadow 0.2s;
&:focus {
border-color: var(--primary-light);
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.1);
}
}
}
/* 按钮样式优化 */
.el-button {
border-radius: 4px;
transition: all 0.2s;
&.is-primary {
background-color: var(--primary);
border-color: var(--primary);
&:hover {
background-color: var(--primary-light);
border-color: var(--primary-light);
}
&:active {
background-color: var(--primary-dark);
border-color: var(--primary-dark);
}
}
&.is-text {
color: var(--primary);
&:hover {
color: var(--primary-light);
background-color: rgba(64, 158, 255, 0.05);
}
}
}
/* 卡片样式优化 */
.el-card {
transition: all 0.3s ease;
}
/* 对话框样式优化 */
.el-dialog {
.el-dialog__header {
background-color: #f5f7fa;
border-bottom: 1px solid #ebeef5;
padding: 15px 20px;
}
.el-dialog__title {
font-size: 16px;
font-weight: 600;
color: #303133;
}
.el-dialog__footer {
padding: 15px 20px;
border-top: 1px solid #ebeef5;
}
}
</style>