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

569 lines
17 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-6 bg-gray-50 min-h-screen">
<div class="received mx-auto bg-white rounded-xl shadow-sm overflow-hidden transition-all duration-300 hover:shadow-md">
<!-- 表单标题区域 -->
<div class="bg-gradient-to-r from-blue-500 to-blue-600 text-white p-6">
<h2 class="text-2xl font-bold flex items-center"><i class="el-icon-user-circle mr-3"></i>收集资料清单</h2>
<p class="text-blue-100 mt-2 opacity-90">请填写相关资料信息</p>
</div>
<el-form ref="mainFormRef" :model="form" :rules="mainRules" label-width="120px" class="p-6 md:p-8">
<!-- 基本信息区域 -->
<div class="bg-blue-50 p-4 rounded-lg mb-6 md:mb-8">
<h3 class="text-lg font-semibold text-blue-700 mb-4">基本信息</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<el-form-item label="收资人" prop="userId" class="mb-0">
<el-select
:disabled="disabledAll"
v-model="form.userId"
placeholder="请选择收资人"
class="w-full transition-all duration-300 border-gray-300 focus:border-blue-400 focus:ring-1 focus:ring-blue-400"
>
<el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId" />
</el-select>
</el-form-item>
<el-form-item label="专业" prop="user_major" class="mb-0">
<el-select
:disabled="disabledAll"
v-model="form.user_major"
placeholder="请选择专业"
class="w-full transition-all duration-300 border-gray-300 focus:border-blue-400 focus:ring-1 focus:ring-blue-400"
>
<el-option v-for="item in des_user_major" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="电话" prop="phone" class="mb-0">
<el-input :disabled="disabledAll" placeholder="请输入电话" v-model="form.phone" autocomplete="off" class="w-full" />
</el-form-item>
<el-form-item label="邮箱" prop="email" class="mb-0">
<el-input :disabled="disabledAll" placeholder="请输入邮箱" v-model="form.email" autocomplete="off" class="w-full" />
</el-form-item>
</div>
</div>
<!-- 资料文件区域 -->
<div class="mb-8">
<div class="flex items-center justify-between mb-5">
<h3 class="text-lg font-semibold text-blue-700">资料文件清单</h3>
<el-button type="primary" size="small" @click="addDocumentItem" v-if="!disabledAll" icon="Plus" class="transition-all hover:bg-blue-600">
添加资料
</el-button>
</div>
<!-- 资料列表表单 -->
<el-form ref="documentsFormRef" :model="form" class="space-y-5">
<div
v-for="(item, index) in form.documents"
:key="item.id"
class="bg-gray-50 p-5 rounded-lg transition-all duration-200 hover:shadow-sm border border-gray-100"
>
<div class="flex justify-between items-center mb-4 pb-3 border-b border-gray-200">
<span class="text-sm font-medium text-gray-700">资料 {{ 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 && !disabledAll"
class="transition-all hover:text-red-600"
>
删除
</el-button>
</div>
<div class="flex flex-col md:flex-row gap-5 items-stretch">
<el-form-item
label="文件目录名称"
:prop="`documents.${index}.catalogueName`"
:rules="[{ required: true, message: '请输入文件目录名称', trigger: 'blur' }]"
class="flex-1 min-w-[280px] mb-0"
>
<el-input :disabled="disabledAll" placeholder="请输入文件目录名称" v-model="item.catalogueName" autocomplete="off" class="w-full" />
</el-form-item>
<el-form-item
label="人员"
:prop="`documents.${index}.userId`"
:rules="[{ required: true, message: '请选择人员', trigger: 'blur' }]"
class="flex-1 min-w-[220px] mb-0"
>
<el-select
:disabled="disabledAll"
v-model="item.userId"
placeholder="请选择人员"
class="w-full transition-all duration-300 border-gray-300 focus:border-blue-400 focus:ring-1 focus:ring-blue-400"
>
<el-option v-for="user in userList" :key="user.userId" :label="user.nickName" :value="user.userId" />
</el-select>
</el-form-item>
<el-form-item label="备注" :prop="`documents.${index}.remark`" class="flex-1 min-w-[220px] mb-0">
<el-input :disabled="disabledAll" placeholder="请输入备注" v-model="item.remark" autocomplete="off" class="w-full" />
</el-form-item>
</div>
</div>
</el-form>
</div>
<!-- 操作按钮区域居中+间距优化 -->
<div class="flex flex-wrap justify-center gap-4 md:gap-6 mt-10">
<el-button
type="primary"
@click="submitForm"
v-hasPermi="['design:collect:add']"
v-if="!form.id || form.status == 'draft'"
size="large"
class="px-8 transition-all hover:bg-blue-600"
>
确认提交
</el-button>
<el-button
type="primary"
@click="update"
v-hasPermi="['design:collect:query']"
v-show="form.id && form.status == 'draft'"
icon="Edit"
size="large"
class="px-8 transition-all hover:bg-blue-600"
>
审核
</el-button>
<el-button
type="primary"
@click="update"
v-hasPermi="['design:collect:query']"
v-show="form.status == 'back'"
size="large"
icon="Edit"
class="px-8 transition-all hover:bg-blue-600"
>
重新发起审核
</el-button>
<el-button
type="primary"
@click="onView"
v-hasPermi="['design:collect:query']"
v-show="form.id && form.status != 'draft'"
icon="view"
size="large"
class="px-8 transition-all hover:bg-blue-600"
>
查看流程
</el-button>
<el-button
type="success"
v-hasPermi="['design:collect:export']"
@click="onLoad"
v-show="form.id && form.status != 'draft'"
icon="Download"
size="large"
class="px-8 transition-all hover:bg-green-600"
>
导出
</el-button>
</div>
</el-form>
</div>
</div>
</template>
<script setup name="DataCollectionForm" lang="ts">
import { ref, reactive, computed, onMounted, onUnmounted, watch, getCurrentInstance } from 'vue';
import { useUserStoreHook } from '@/store/modules/user';
import { ElMessage, ElLoading, FormRules } from 'element-plus';
import { systemUserList } from '@/api/design/appointment';
import { collectBatch, byProjectId, exportWord } from '@/api/design/received';
import { getUser } from '@/api/system/user';
import type { ComponentInternalInstance, ElFormInstance } from 'element-plus';
// 全局实例与状态管理
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const userStore = useUserStoreHook();
const currentProject = computed(() => userStore.selectedProject);
const { des_user_major } = toRefs<any>(proxy?.useDict('des_user_major'));
const userId = computed(() => userStore.userId);
// 表单引用
const mainFormRef = ref<ElFormInstance>();
const documentsFormRef = ref<ElFormInstance>();
// 数据定义
const userList = ref<any[]>([]);
const userMap = new Map<string, string>(); // 存储用户ID与昵称映射
const disabledAll = ref(false); // 表单是否全部禁用
// 表单核心数据
const form = reactive({
projectId: currentProject.value?.id,
userId: '', // 收资人
user_major: '', // 专业
phone: '', // 电话
email: '', // 邮箱
id: '', // 表单ID
status: '', // 表单状态
documents: [
{
id: Date.now(),
catalogueName: '', // 文件目录名称
remark: '', // 备注
userId: '' // 负责人员
}
] as Array<{
id: number;
catalogueName: string;
remark: string;
userId: string;
num?: number; // 序号(提交时用)
userName?: string; // 人员名称(提交时用)
}>
});
// 主表单验证规则
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 addDocumentItem = () => {
form.documents.push({
id: Date.now(), // 用时间戳保证ID唯一
catalogueName: '',
remark: '',
userId: ''
});
};
/** 删除资料项 */
const removeDocumentItem = (index: number) => {
if (form.documents.length <= 1) {
ElMessage.warning('至少需要保留一条资料记录');
return;
}
form.documents.splice(index, 1);
};
/** 回显项目对应的表单数据 */
const byProjectIdAll = async () => {
try {
const res = await byProjectId(currentProject.value?.id);
// 重置表单默认值
form.documents = [
{
id: Date.now(),
catalogueName: '',
remark: '',
userId: ''
}
];
disabledAll.value = false;
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 (form.status === 'finish') {
disabledAll.value = true;
}
// 回显资料列表
if (data.catalogueList && data.catalogueList.length > 0) {
form.documents = data.catalogueList.map((item: any, index: number) => ({
id: item.id || Date.now() + index, // 确保ID唯一
catalogueName: item.catalogueName || '',
remark: item.remark || '',
userId: item.userId || ''
}));
}
}
console.log(form);
} catch (error) {
ElMessage.error('获取表单数据失败,请刷新重试');
console.error('数据回显错误:', error);
}
};
/** 提交表单 */
const submitForm = async () => {
if (!mainFormRef.value) return;
try {
// 先验证主表单
await mainFormRef.value.validate();
// 再验证资料列表表单(如果存在)
if (documentsFormRef.value) {
await documentsFormRef.value.validate();
}
// 处理提交数据(补充序号和人员名称)
const submitDocuments = form.documents.map((item, i) => ({
...item,
num: i + 1,
userName: userMap.get(item.userId) || ''
}));
const submitData = {
desCollectBo: {
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: submitDocuments
};
// 调用接口提交
const res = await collectBatch(submitData);
if (res.code === 200) {
ElMessage.success('表单提交成功');
byProjectIdAll(); // 重新拉取最新数据
} else {
ElMessage.warning(res.msg || '提交失败,请重试');
}
} catch (error) {
ElMessage.error('请完善表单必填信息后再提交');
console.error('表单验证失败:', error);
}
};
/** 重置表单 */
const resetForm = () => {
if (mainFormRef.value) {
mainFormRef.value.resetFields();
// form表单数据重置
form.userId = '';
form.user_major = '';
form.phone = '';
form.email = '';
form.id = '';
form.status = '';
}
// 重置资料列表为1条空记录
form.documents = [
{
id: Date.now(),
catalogueName: '',
remark: '',
userId: ''
}
];
};
/** 获取当前部门的所有用户 */
const getDeptAllUser = async (deptId: string | number) => {
try {
const res = await systemUserList({ deptId });
userList.value = res.rows || [];
// 构建用户ID-昵称映射
userList.value.forEach((user) => {
userMap.set(user.userId, user.nickName);
});
} catch (error) {
ElMessage.error('获取用户列表失败,请刷新重试');
console.error('用户列表获取错误:', error);
}
};
/** 跳转审核页面 */
const update = () => {
proxy?.$tab.closePage(proxy?.$route);
proxy?.$router.push({
path: `/approval/received/indexEdit`,
query: {
id: form.id,
type: 'update'
}
});
};
/** 跳转流程查看页面 */
const onView = () => {
proxy?.$tab.closePage(proxy?.$route);
proxy?.$router.push({
path: `/approval/received/indexEdit`,
query: {
id: form.id,
type: 'view'
}
});
};
/** 获取当前用户详情(回显个人信息) */
const getUserDetail = async () => {
try {
const res = await getUser(userId.value);
if (res.data?.user) {
form.userId = res.data.user.userId;
form.phone = res.data.user.phonenumber || '';
form.email = res.data.user.email || '';
}
} catch (err) {
ElMessage.error('获取个人信息失败,部分字段需手动填写');
console.error('用户详情获取错误:', err);
}
};
/** 导出文件 */
const onLoad = async () => {
if (!form.id) {
ElMessage.warning('请先保存表单再导出');
return;
}
try {
proxy?.download('design/collect/exportWord', { id: form.id }, `收资清单_${new Date().getTime()}.doc`);
} catch (error) {
ElMessage.error('导出失败,请重试');
console.error('文件导出错误:', error);
}
};
/** 页面挂载初始化 */
onMounted(() => {
// 先获取当前用户信息,再获取部门用户列表,最后回显表单数据
getUserDetail().then(() => {
getDeptAllUser(userStore.deptId).then(() => {
byProjectIdAll();
});
});
});
/** 监听项目切换,刷新数据 */
const listeningProject = watch(
() => currentProject.value?.id,
(newId, oldId) => {
if (newId !== oldId) {
resetForm();
form.projectId = newId;
getUserDetail().then(() => {
getDeptAllUser(userStore.deptId).then(() => {
byProjectIdAll();
});
});
}
}
);
/** 页面卸载清理监听 */
onUnmounted(() => {
listeningProject();
});
</script>
<style lang="scss" scoped>
// 主容器样式
.received {
width: 95%;
max-width: 1200px;
margin-top: 20px;
margin-bottom: 40px;
}
// 自定义滚动条(优化长列表体验)
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-thumb {
background-color: #e5e7eb;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background-color: #d1d5db;
}
// Element 组件样式覆盖(统一风格)
::v-deep .el-input__inner,
::v-deep .el-select__input,
::v-deep .el-select-dropdown__item {
border-radius: 6px !important;
border-color: #dcdfe6 !important;
transition: all 0.2s ease !important;
}
::v-deep .el-input__inner:focus,
::v-deep .el-select__input:focus {
border-color: #409eff !important;
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2) !important;
outline: none !important;
}
::v-deep .el-form-item {
margin-bottom: 0 !important;
}
::v-deep .el-form-item__label {
font-weight: 500 !important;
color: #606266 !important;
padding-right: 12px !important;
}
::v-deep .el-button--primary {
background-color: #409eff !important;
border-color: #409eff !important;
}
::v-deep .el-button--primary:hover {
background-color: #3390e0 !important;
border-color: #3390e0 !important;
}
::v-deep .el-button--success {
background-color: #52c41a !important;
border-color: #52c41a !important;
}
::v-deep .el-button--success:hover {
background-color: #47b811 !important;
border-color: #47b811 !important;
}
// 响应式适配(小屏幕调整)
@media (max-width: 768px) {
.p-6.md\:p-8 {
padding: 4px !important;
}
::v-deep .el-form-item__label {
width: 100px !important;
font-size: 14px !important;
}
.flex.flex-col.md\:flex-row.gap-5 {
gap: 3px !important;
}
.el-button--large {
padding: 8px 16px !important;
font-size: 14px !important;
}
.bg-blue-50.p-4 {
padding: 15px !important;
}
}
</style>