|
|
@@ -0,0 +1,325 @@
|
|
|
+<template>
|
|
|
+ <div>
|
|
|
+ <div class="mb-4">
|
|
|
+ <el-input
|
|
|
+ v-model.trim="searchKeyword"
|
|
|
+ placeholder="请输入检验员姓名"
|
|
|
+ clearable
|
|
|
+ class="!w-240px"
|
|
|
+ @keyup.enter="handleSearch"
|
|
|
+ >
|
|
|
+ <template #prefix>
|
|
|
+ <Icon icon="ep:search" />
|
|
|
+ </template>
|
|
|
+ </el-input>
|
|
|
+
|
|
|
+ <DeptSelect
|
|
|
+ v-model="selectedDept"
|
|
|
+ placeholder="请选择部门"
|
|
|
+ clearable
|
|
|
+ :multiple="true"
|
|
|
+ class="!w-240px ml-4"
|
|
|
+ @change="handleDeptChange"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <!-- 部门-分组-小组 -->
|
|
|
+ <div class="checker-list">
|
|
|
+ <template v-if="processedDeptData.length">
|
|
|
+
|
|
|
+ <div v-for="dept in processedDeptData" :key="dept.dept.id" class="dept-section">
|
|
|
+ <div v-for="team in dept.teamList" :key="team.id || team.deptGroupId" class="group-section">
|
|
|
+ <div class="group-title">
|
|
|
+ {{ dept.dept.name }} - {{ team.deptGroupName }}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="group-content">
|
|
|
+ <template v-for="subTeam in team.memberList" :key="subTeam.id || subTeam.deptGroupId">
|
|
|
+ <div class="group-members">
|
|
|
+ <div class="label">
|
|
|
+ <el-checkbox
|
|
|
+ v-model="subTeam.checked"
|
|
|
+ @change="(val: boolean) => handleSubTeamCheckChange(val, team, subTeam)"
|
|
|
+ :disabled="!max ? false : max === 1"
|
|
|
+ >
|
|
|
+ {{ subTeam.name }}
|
|
|
+ </el-checkbox>
|
|
|
+ </div>
|
|
|
+ <div class="members-list">
|
|
|
+ <el-checkbox-group
|
|
|
+ v-model="selectedMembers"
|
|
|
+ @change="(val: string[]) => handleMemberChange(val, team, subTeam)"
|
|
|
+ >
|
|
|
+ <el-checkbox
|
|
|
+ v-for="member in subTeam.memberList"
|
|
|
+ :key="member.memberId"
|
|
|
+ :value="member.id + ':' + member.memberId"
|
|
|
+ :disabled="!max ? false : (max === 1 && selectedMembers?.length === max && selectedMembers[0] !== (member.id + ':' + member.memberId))"
|
|
|
+ >
|
|
|
+ <span v-if="member.memberId === subTeam.leaderId" class="leader-tag">组长</span>
|
|
|
+ {{ member.member?.nickname }}
|
|
|
+ </el-checkbox>
|
|
|
+ </el-checkbox-group>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <template v-else>
|
|
|
+ <el-empty description="未找到符合条件的数据" />
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </div>
|
|
|
+
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import DeptSelect from './DeptSelect.vue'
|
|
|
+import { DeptGroupTeamApi } from '@/api/pressure2/deptGroupTeam'
|
|
|
+import { useUserStore, useUserStoreWithOut } from '@/store/modules/user'
|
|
|
+import { processInspectorGroups } from '@/views/pressure2/pipechecker/components/inspector'
|
|
|
+import type { DeptData, ProcessedDeptData, ProcessedTeam, ProcessedTeamMember } from '@/views/pressure2/pipechecker/components/inspector';
|
|
|
+const props = defineProps<{
|
|
|
+ modelValue: string[],
|
|
|
+ deptId?: string,
|
|
|
+ max: number
|
|
|
+}>()
|
|
|
+const emit = defineEmits(['update:modelValue', 'change'])
|
|
|
+const searchKeyword = ref('')
|
|
|
+const userStore = useUserStore()
|
|
|
+const {getUser} = useUserStoreWithOut()
|
|
|
+// const selectedDept = ref<string[]>([userStore.user.?])
|
|
|
+const selectedDept = ref<any[]>(props.deptId ? [props.deptId] : [getUser.deptId])
|
|
|
+const processedDeptData = ref<ProcessedDeptData[]>([])
|
|
|
+const selectedMembers = ref<string[]>(props.modelValue || [])
|
|
|
+const processedDeptDataCopy = ref<ProcessedDeptData[]>([])
|
|
|
+// 处理搜索
|
|
|
+const handleSearch = () => {
|
|
|
+ if (!searchKeyword.value) {
|
|
|
+ // 如果搜索关键字为空,恢复原始数据
|
|
|
+ // if (selectedDept.value.length > 0) {
|
|
|
+ // getMemberList(selectedDept.value)
|
|
|
+ // }
|
|
|
+ getMemberList(selectedDept.value)
|
|
|
+ // return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建过滤后的数据副本
|
|
|
+ const filteredData = JSON.parse(JSON.stringify(processedDeptDataCopy.value))
|
|
|
+
|
|
|
+ filteredData.forEach(dept => {
|
|
|
+ dept.teamList.forEach(team => {
|
|
|
+ team.memberList.forEach(subTeam => {
|
|
|
+ // 过滤成员列表
|
|
|
+ subTeam.memberList = subTeam.memberList.filter(member =>
|
|
|
+ member.member?.nickname?.includes(searchKeyword.value)
|
|
|
+ )
|
|
|
+
|
|
|
+ // 更新小组选中状态
|
|
|
+ updateSubTeamCheckStatus(subTeam)
|
|
|
+ })
|
|
|
+
|
|
|
+ // 过滤空的小组
|
|
|
+ team.memberList = team.memberList.filter(subTeam => subTeam.memberList.length > 0)
|
|
|
+ })
|
|
|
+
|
|
|
+ // 过滤空的团队
|
|
|
+ dept.teamList = dept.teamList.filter(team => team.memberList.length > 0)
|
|
|
+ })
|
|
|
+
|
|
|
+ // 更新显示的数据
|
|
|
+ processedDeptData.value = filteredData.filter(dept => dept.teamList.length > 0)
|
|
|
+
|
|
|
+ // 触发change事件
|
|
|
+ handleChange(selectedMembers.value)
|
|
|
+}
|
|
|
+
|
|
|
+// 获取部门的分组-小组成员列表
|
|
|
+const getMemberList = async (deptIds: string[] | null) => {
|
|
|
+ const originalData: DeptData[] = await DeptGroupTeamApi.getDeptGroupTeamMembers({
|
|
|
+ deptIds: !deptIds?.length ? '' : deptIds?.join(',')
|
|
|
+ })
|
|
|
+
|
|
|
+
|
|
|
+ // 前端处理部门-分组-小组
|
|
|
+ processedDeptData.value = processInspectorGroups(originalData)
|
|
|
+ processedDeptDataCopy.value = processInspectorGroups(originalData);
|
|
|
+
|
|
|
+ processedDeptData.value.forEach(dept => {
|
|
|
+ dept.teamList.forEach(team => {
|
|
|
+ team.memberList.forEach(subTeam => {
|
|
|
+ updateSubTeamCheckStatus(subTeam)
|
|
|
+ })
|
|
|
+ })
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 处理部门变化
|
|
|
+const handleDeptChange = (deptIds: string[]) => {
|
|
|
+ selectedDept.value = deptIds // 直接使用选中的数组
|
|
|
+ getMemberList(deptIds)
|
|
|
+}
|
|
|
+
|
|
|
+// 处理小组全选
|
|
|
+const handleSubTeamCheckChange = (val: boolean, team: ProcessedTeam, subTeam: ProcessedTeamMember) => {
|
|
|
+ // 获取当前小组所有可用成员的ID
|
|
|
+ const availableMembers = subTeam.memberList
|
|
|
+ const memberIds = availableMembers.map(member => member.id + ':' + member.memberId)
|
|
|
+
|
|
|
+ if (val) {
|
|
|
+ // 选中:添加所有可用成员
|
|
|
+ selectedMembers.value = [...new Set([...selectedMembers.value, ...memberIds])]
|
|
|
+ } else {
|
|
|
+ // 取消选中:移除当前小组的所有成员
|
|
|
+ selectedMembers.value = selectedMembers.value.filter(id => !memberIds.includes(id))
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新小组选中状态
|
|
|
+ updateSubTeamCheckStatus(subTeam)
|
|
|
+
|
|
|
+ handleChange(selectedMembers.value)
|
|
|
+}
|
|
|
+
|
|
|
+// 处理成员选择变化
|
|
|
+const handleMemberChange = (val: string[], team: ProcessedTeam, subTeam: ProcessedTeamMember) => {
|
|
|
+ // 更新小组选中状态
|
|
|
+ updateSubTeamCheckStatus(subTeam)
|
|
|
+ handleChange(selectedMembers.value)
|
|
|
+}
|
|
|
+
|
|
|
+// 更新小组的选中状态
|
|
|
+const updateSubTeamCheckStatus = (subTeam: ProcessedTeamMember) => {
|
|
|
+ // 获取当前小组所有可用成员
|
|
|
+ const availableMembers = subTeam.memberList
|
|
|
+ const allSubTeamMembers = availableMembers.map(m => m.id + ':' + m.memberId)
|
|
|
+
|
|
|
+ if (allSubTeamMembers.length === 0) {
|
|
|
+ subTeam.checked = false
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算当前小组选中的成员
|
|
|
+ const selectedSubTeamMembers = selectedMembers.value.filter(id => allSubTeamMembers.includes(id))
|
|
|
+
|
|
|
+ // 更新小组的选中状态
|
|
|
+ subTeam.checked = selectedSubTeamMembers.length === allSubTeamMembers.length
|
|
|
+}
|
|
|
+
|
|
|
+const handleChange = (value: string[]) => {
|
|
|
+ emit('update:modelValue', value)
|
|
|
+ // 获取选中成员的完整信息
|
|
|
+ const selectedCheckerInfo = processedDeptData.value.reduce((acc: any[], dept) => {
|
|
|
+ dept.teamList.forEach(team => {
|
|
|
+ team.memberList.forEach(subTeam => {
|
|
|
+ subTeam.memberList.forEach(member => {
|
|
|
+ if (value.includes(member.id + ':' + member.memberId)) {
|
|
|
+ acc.push({
|
|
|
+ memberId: member.memberId,
|
|
|
+ leaderId: subTeam.leaderId,
|
|
|
+ member: member.member,
|
|
|
+ isLeader: member.memberId === subTeam.leaderId,
|
|
|
+ groupTeamId: subTeam.id
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ })
|
|
|
+ })
|
|
|
+ return acc
|
|
|
+ }, [])
|
|
|
+ emit('change', selectedCheckerInfo)
|
|
|
+}
|
|
|
+
|
|
|
+// 监听 modelValue 变化
|
|
|
+watch(() => props.modelValue, (newVal) => {
|
|
|
+ selectedMembers.value = newVal || []
|
|
|
+ // 更新所有小组的选中状态
|
|
|
+ processedDeptData.value.forEach(dept => {
|
|
|
+ dept.teamList.forEach(team => {
|
|
|
+ team.memberList.forEach(subTeam => {
|
|
|
+ updateSubTeamCheckStatus(subTeam)
|
|
|
+ })
|
|
|
+ })
|
|
|
+ })
|
|
|
+}, { immediate: true })
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ if (selectedDept.value.length > 0) {
|
|
|
+ getMemberList(selectedDept.value)
|
|
|
+ }
|
|
|
+
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.checker-list {
|
|
|
+ max-height: 500px;
|
|
|
+ overflow-y: auto;
|
|
|
+
|
|
|
+ .leader-tag {
|
|
|
+ display: inline-block;
|
|
|
+ // width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ line-height: 14px;
|
|
|
+ text-align: center;
|
|
|
+ border: 1px solid #4475d6;
|
|
|
+ font-size: 12px;
|
|
|
+ // margin-left: 4px;
|
|
|
+ color: #4475d6;
|
|
|
+ }
|
|
|
+
|
|
|
+ .dept-section {
|
|
|
+ .group-section {
|
|
|
+ margin-bottom: 16px;
|
|
|
+ border: 1px solid #EBEEF5;
|
|
|
+ border-radius: 4px;
|
|
|
+
|
|
|
+ .group-title {
|
|
|
+ padding: 8px 16px;
|
|
|
+ background-color: #F5F7FA;
|
|
|
+ border-bottom: 1px solid #EBEEF5;
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+
|
|
|
+ .group-content {
|
|
|
+ padding: 16px;
|
|
|
+
|
|
|
+ .group-members {
|
|
|
+ display: flex;
|
|
|
+ margin-bottom: 12px;
|
|
|
+
|
|
|
+ &:last-child {
|
|
|
+ margin-bottom: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .label {
|
|
|
+ flex: 0 0 80px;
|
|
|
+ padding-top: 1px;
|
|
|
+
|
|
|
+ :deep(.el-checkbox) {
|
|
|
+ margin-right: 0;
|
|
|
+ .el-checkbox__label {
|
|
|
+ padding-left: 6px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .members-list {
|
|
|
+ padding-left: 16px;
|
|
|
+ flex: 1;
|
|
|
+
|
|
|
+ :deep(.el-checkbox) {
|
|
|
+ margin-right: 16px;
|
|
|
+
|
|
|
+ &:last-child {
|
|
|
+ margin-right: 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|