Files
td_official/src/views/design/appointment/index.vue
2025-08-13 19:11:51 +08:00

521 lines
16 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">
<div class="appWidth 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>
<el-button @click="disabledForm = false" class="px-8 py-2.5 transition-all duration-300 font-medium" v-if="disabledForm">
点击编辑
</el-button>
<!-- <span class="text-red-300">*</span> 为必填项 -->
</div>
<!-- 表单内容区域 -->
<el-form ref="leaveFormRef" :model="form" :disabled="disabledForm" :rules="rules" label-width="120px" class="p-6 space-y-6">
<!-- 设计负责人 -->
<div class="fonts">
<el-form-item label="设计负责人" prop="designLeader" class="mb-4">
<el-select
v-model="form.designLeader"
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>
</div>
<!-- 设计人员 -->
<div class="border border-gray-200 rounded-lg p-5 transition-all duration-300 hover:shadow-md bg-gray-50">
<div class="flex justify-between items-center mb-5">
<h3 class="text-lg font-semibold text-gray-700 flex items-center"><i class="el-icon-user mr-2 text-blue-500"></i>设计人员</h3>
<el-button
type="primary"
size="small"
@click="addPerson('designers')"
class="transition-all duration-300 transform hover:scale-105 bg-blue-500 hover:bg-blue-600"
>
<i class="el-icon-plus mr-1"></i>新增设计人员
</el-button>
</div>
<div v-for="(designer, index) in form.designers" :key="index" class="flex items-center mb-4 animate-fadeIn">
<el-form-item
:prop="`designers.${index}.userId`"
:rules="{ required: true, message: '请选择设计人员', trigger: 'change' }"
class="flex-1 mr-3"
>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<el-select
v-model="designer.userMajor"
placeholder="请选择专业"
class="transition-all duration-300 border-gray-300"
:rules="{ required: true, message: '请选择专业', trigger: 'change' }"
>
<el-option v-for="item in des_user_major" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-select v-model="designer.userId" placeholder="请选择设计人员" class="transition-all duration-300 border-gray-300">
<el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId" />
</el-select>
</div>
</el-form-item>
<el-button
type="danger"
size="small"
@click="removePerson('designers', index)"
class="transition-all duration-300 hover:bg-red-600"
:disabled="form.designers.length <= 1"
>
<el-icon :size="16">
<Delete />
</el-icon>
</el-button>
</div>
<div v-if="form.designers.length === 0" class="text-gray-500 text-center py-4 bg-gray-100 rounded-lg border border-dashed border-gray-200">
暂无设计人员请点击"新增设计人员"添加
</div>
</div>
<!-- 校审人员 -->
<div class="border border-gray-200 rounded-lg p-5 transition-all duration-300 hover:shadow-md bg-gray-50">
<div class="flex justify-between items-center mb-5">
<h3 class="text-lg font-semibold text-gray-700 flex items-center"><i class="el-icon-check-circle mr-2 text-green-500"></i>校审人员</h3>
<el-button
type="primary"
size="small"
@click="addPerson('reviewers')"
class="transition-all duration-300 transform hover:scale-105 bg-blue-500 hover:bg-blue-600"
>
<i class="el-icon-plus mr-1"></i>新增校审人员
</el-button>
</div>
<div v-for="(reviewer, index) in form.reviewers" :key="index" class="flex items-center mb-4 animate-fadeIn">
<el-form-item
:prop="`reviewers.${index}.userId`"
:rules="{ required: true, message: '请选择校审人员', trigger: 'change' }"
class="flex-1 mr-3"
>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<el-select
v-model="reviewer.userMajor"
placeholder="请选择专业"
class="transition-all duration-300 border-gray-300"
:rules="{ required: true, message: '请选择专业', trigger: 'change' }"
>
<el-option v-for="item in des_user_major" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-select v-model="reviewer.userId" placeholder="请选择校审人员" class="transition-all duration-300 border-gray-300">
<el-option v-for="item in userList" :key="item.userId" :label="item.nickName" :value="item.userId" />
</el-select>
</div>
</el-form-item>
<el-button
type="danger"
size="small"
@click="removePerson('reviewers', index)"
class="transition-all duration-300 hover:bg-red-600"
:disabled="form.reviewers.length <= 1"
>
<el-icon :size="16">
<Delete />
</el-icon>
</el-button>
</div>
<div v-if="form.reviewers.length === 0" class="text-gray-500 text-center py-4 bg-gray-100 rounded-lg border border-dashed border-gray-200">
暂无校审人员请点击"新增校审人员"添加
</div>
</div>
<!-- 提交按钮区域 -->
<div class="flex justify-center space-x-6 mt-8 pt-6 border-t border-gray-100">
<el-button
type="primary"
size="large"
@click="submitForm"
class="px-8 py-2.5 transition-all duration-300 transform hover:scale-105 bg-blue-500 hover:bg-blue-600 text-white font-medium"
>
<i class="el-icon-check mr-2"></i>确认提交
</el-button>
<el-button size="large" @click="resetForm" class="px-8 py-2.5 transition-all duration-300 border-gray-300 hover:bg-gray-100 font-medium">
<i class="el-icon-refresh mr-2"></i>重置
</el-button>
</div>
</el-form>
</div>
</div>
</template>
<script setup name="PersonnelForm" lang="ts">
import { ref, reactive, computed, onMounted, toRefs } from 'vue';
import { getCurrentInstance } from 'vue';
import type { ComponentInternalInstance } from 'vue';
import { useUserStoreHook } from '@/store/modules/user';
import { listUserByDeptId } from '@/api/system/user';
import { ElMessage, ElLoading } from 'element-plus';
import { Delete } from '@element-plus/icons-vue';
import { designUserAdd, designUserList, systemUserList } from '@/api/design/appointment';
import { disable } from 'ol/rotationconstraint';
// 获取当前实例
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
// 获取用户 store
const userStore = useUserStoreHook();
// 从 store 中获取当前选中的项目
const currentProject = computed(() => userStore.selectedProject);
// 专业字典数据
const { des_user_major } = toRefs<any>(proxy?.useDict('des_user_major'));
// 表单数据
const form = reactive({
projectId: currentProject.value?.id,
designLeader: null, // 设计负责人
// 设计人员列表包含用户ID和专业
designers: [] as Array<{ userId: number | null; userMajor: string | null }>,
// 校审人员列表包含用户ID和专业
reviewers: [] as Array<{ userId: number | null; userMajor: string | null }>
});
// 表单验证规则
const rules = reactive({
designLeader: [{ required: true, message: '请选择设计负责人', trigger: 'change' }]
});
// 用户列表
const userList = ref([]);
// 表单引用
const leaveFormRef = ref();
const disabledForm = ref(false); //控制提交按钮状态
/** 查询当前部门的所有用户 */
const getDeptAllUser = async (deptId: any) => {
try {
const res = await systemUserList({ deptId });
// 实际项目中使用接口返回的数据
userList.value = res.rows;
} catch (error) {
ElMessage.error('获取用户列表失败');
} finally {
}
};
/** 查询当前表单数据并回显 */
const designUser = async () => {
if (!currentProject.value?.id) return;
const loading = ElLoading.service({
lock: true,
text: '加载配置数据中...',
background: 'rgba(255, 255, 255, 0.7)'
});
try {
const res = await designUserList({ projectId: currentProject.value?.id });
if (res.code == 200 && res.rows) {
// 清空现有数据
form.designLeader = null;
form.designers = [];
form.reviewers = [];
if (res.rows.length > 0) {
disabledForm.value = true;
}
// 处理返回的数据,进行回显
res.rows.forEach((item: any) => {
if (item.userType == 1) {
item.userType = 'designLeader';
} else if (item.userType == 2) {
item.userType = 'designer';
} else if (item.userType == 3) {
item.userType = 'reviewer';
}
// 根据userType区分不同类型的人员
switch (item.userType) {
case 'designLeader':
case 1:
form.designLeader = item.userId;
break;
case 'designer':
case 2:
form.designers.push({
userId: item.userId,
userMajor: item.userMajor || null
});
break;
case 'reviewer':
case 3:
form.reviewers.push({
userId: item.userId,
userMajor: item.userMajor || null
});
break;
}
});
// 如果没有设计人员或校审人员,添加一个空项
if (form.designers.length === 0) {
form.designers.push({ userId: null, userMajor: null });
}
if (form.reviewers.length === 0) {
form.reviewers.push({ userId: null, userMajor: null });
}
} else {
// 添加默认空项
form.designers.push({ userId: null, userMajor: null });
form.reviewers.push({ userId: null, userMajor: null });
}
} catch (error) {
ElMessage.error('获取配置数据失败');
// 添加默认空项
form.designers.push({ userId: null, userMajor: null });
form.reviewers.push({ userId: null, userMajor: null });
} finally {
loading.close();
}
};
/** 添加人员 */
const addPerson = (type: 'designers' | 'reviewers') => {
form[type].push({ userId: null, userMajor: null });
// 滚动到最后一个新增的元素
setTimeout(() => {
const elements = document.querySelectorAll(`[data-v-${proxy?.$options.__scopeId}] .el-select`);
if (elements.length > 0) {
elements[elements.length - 1].scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
}, 100);
};
/** 移除人员 */
const removePerson = (type: 'designers' | 'reviewers', index: number) => {
if (form[type].length <= 1) {
ElMessage.warning('至少保留一个人员');
return;
}
form[type].splice(index, 1);
};
/** 提交表单 */
const submitForm = async () => {
if (!leaveFormRef.value) return;
try {
// 表单验证
await leaveFormRef.value.validate();
// 构建提交数据 - 所有人员信息放在一个数组中
const submitData = {
projectId: form.projectId,
personnel: [
// 设计负责人
{
userId: form.designLeader,
userType: 'designLeader', // 设计负责人类型标识
userMajor: null // 负责人不需要专业
},
// 设计人员
...form.designers.map((designer) => ({
userId: designer.userId,
userType: 'designer', // 设计人员类型标识
userMajor: designer.userMajor // 包含专业信息
})),
// 校审人员
...form.reviewers.map((reviewer) => ({
userId: reviewer.userId,
userType: 'reviewer', // 校审人员类型标识
userMajor: reviewer.userMajor // 包含专业信息
}))
]
};
// 数据处理
const arr = [];
userList.value.forEach((item) => {
submitData.personnel.forEach((item1) => {
if (item1.userId === item.userId) {
let userType = 1; // 默认为设计负责人
if (item1.userType === 'designer') {
userType = 2; // 设计人员
} else if (item1.userType === 'reviewer') {
userType = 3; // 校审人员
}
arr.push({
userName: item.nickName,
projectId: submitData.projectId,
userId: item1.userId,
userType: userType,
userMajor: item1.userMajor
});
}
});
});
// 提交到后端
const res = await designUserAdd({
list: arr,
projectId: currentProject.value?.id
});
if (res.code === 200) {
disabledForm.value = true;
ElMessage.success('提交成功');
} else {
ElMessage.error(res.msg || '提交失败');
}
} catch (error) {
ElMessage.error('请完善表单信息后再提交');
} finally {
// 关闭加载状态
ElLoading.service().close();
}
};
/** 重置表单 */
const resetForm = () => {
if (leaveFormRef.value) {
leaveFormRef.value.resetFields();
form.designers = [{ userId: null, userMajor: null }];
form.reviewers = [{ userId: null, userMajor: null }];
ElMessage.info('表单已重置');
}
};
// 页面挂载时初始化数据
onMounted(() => {
console.log(userStore.deptId);
// 先获取用户列表,再加载表单数据
getDeptAllUser(userStore.deptId).then(() => {
designUser();
});
});
</script>
<style lang="scss">
.appWidth {
width: 50vw;
max-width: 1200px;
.el-select__wrapper {
width: 16vw !important;
}
.el-button--small {
margin-bottom: 10px;
}
.fonts {
.el-form-item--default .el-form-item__label {
font-size: 18px !important;
}
}
}
// 自定义动画
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fadeIn {
animation: fadeIn 0.3s ease-out forwards;
}
// 表单样式优化
::v-deep .el-form {
--el-form-item-margin-bottom: 0;
}
::v-deep .el-form-item {
margin-bottom: 0;
&__label {
font-weight: 500;
color: #4e5969;
padding: 0 0 8px 0;
}
&__content {
padding: 0;
}
}
::v-deep .el-select {
width: 100%;
.el-input__inner {
border-radius: 6px;
transition: all 0.3s ease;
}
&:hover .el-input__inner {
border-color: #66b1ff;
}
&.el-select-focus .el-input__inner {
border-color: #409eff;
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
}
}
::v-deep .el-button {
border-radius: 6px;
padding: 8px 16px;
&--primary {
background-color: #409eff;
border-color: #409eff;
&:hover {
background-color: #66b1ff;
border-color: #66b1ff;
}
}
&--danger {
background-color: #f56c6c;
border-color: #f56c6c;
&:hover {
background-color: #f78989;
border-color: #f78989;
}
&:disabled {
background-color: #ffcccc;
border-color: #ffbbbb;
cursor: not-allowed;
}
}
}
// 响应式网格布局
.grid {
display: grid;
}
.grid-cols-1 {
grid-template-columns: repeat(1, minmax(0, 1fr));
}
.md\:grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.gap-4 {
gap: 1rem;
}
// 适配小屏幕
@media (max-width: 768px) {
.appWidth {
width: 95vw;
}
::v-deep .el-form {
padding: 4px;
}
::v-deep .el-form-item__label {
width: 100px;
}
}
</style>