index.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. <template>
  2. <div class="checker-select-container" :class="{ 'opacity-50': disabled }">
  3. <div class="checker-list" v-if="!disabled && hasData">
  4. <template v-if="processedDeptData.length">
  5. <div v-for="dept in processedDeptData" :key="dept.dept?.id || dept.deptGroupId" class="dept-section">
  6. <div v-for="team in dept.teamList" :key="team.id || team.deptGroupId" class="group-section">
  7. <div class="group-content">
  8. <template v-for="subTeam in team.memberList" :key="subTeam.id || subTeam.deptGroupId">
  9. <div class="group-members">
  10. <!-- 单选模式下隐藏小组全选复选框 -->
  11. <div class="label" v-if="props.multiple">
  12. <el-checkbox
  13. v-model="subTeam.checked"
  14. @change="(val: boolean) => handleSubTeamCheckChange(val, subTeam)"
  15. :disabled="disabled"
  16. >
  17. {{ subTeam.name }}
  18. </el-checkbox>
  19. </div>
  20. <div class="label" v-else>
  21. <span>{{ subTeam.name }}</span>
  22. </div>
  23. <div class="members-list">
  24. <!-- 多选模式 -->
  25. <el-checkbox-group
  26. v-if="props.multiple"
  27. v-model="selectedCheckerIds"
  28. @change="handleMemberChange"
  29. :disabled="disabled"
  30. >
  31. <el-checkbox
  32. v-for="member in subTeam.memberList"
  33. :key="member.memberId"
  34. :value="subTeam.id + ':' + member.memberId"
  35. >
  36. <span v-if="member.memberId === member.leaderId" class="leader-tag">组</span>
  37. {{ member.member?.nickname }}
  38. </el-checkbox>
  39. </el-checkbox-group>
  40. <!-- 单选模式:使用复选框但实现单选逻辑 -->
  41. <div v-else class="single-select-mode">
  42. <el-checkbox
  43. v-for="member in subTeam.memberList"
  44. :key="member.memberId"
  45. :model-value="isCheckerSelected(subTeam.id, member.memberId)"
  46. @change="(val: boolean) => handleSingleSelectChange(val, subTeam.id, member.memberId)"
  47. :disabled="disabled"
  48. >
  49. <span v-if="member.memberId === member.leaderId" class="leader-tag">组</span>
  50. {{ member.member?.nickname }}
  51. </el-checkbox>
  52. </div>
  53. </div>
  54. </div>
  55. </template>
  56. </div>
  57. </div>
  58. </div>
  59. </template>
  60. <el-empty v-else description="暂无检验员数据" />
  61. </div>
  62. <div v-if="disabled || !hasData" class="text-gray-400 py-4 text-center">
  63. {{ emptyText || '无需安排或无待检设备' }}
  64. </div>
  65. </div>
  66. </template>
  67. <script setup lang="ts">
  68. import { ref, watch, computed } from 'vue'
  69. import type { ProcessedDeptData } from '@/views/pressure2/equipboilerscheduling/components/inspector'
  70. import { DeptGroupTeamApi } from '@/api/pressure2/deptGroupTeam'
  71. import { processInspectorGroups } from '@/views/pressure2/equipboilerscheduling/components/inspector'
  72. export interface CheckerItem {
  73. groupTeamId: string
  74. memberId: string
  75. leaderId: string
  76. member: any
  77. isLeader: boolean
  78. }
  79. const props = defineProps({
  80. // 部门ID
  81. deptId: {
  82. type: String,
  83. required: true
  84. },
  85. // 是否禁用
  86. disabled: {
  87. type: Boolean,
  88. default: false
  89. },
  90. // 是否有数据(控制显示)
  91. hasData: {
  92. type: Boolean,
  93. default: true
  94. },
  95. // 空状态文本
  96. emptyText: {
  97. type: String,
  98. default: ''
  99. },
  100. // 已选中的检验员列表(用于初始化)
  101. modelValue: {
  102. type: Array as PropType<CheckerItem[]>,
  103. default: () => []
  104. },
  105. // 是否多选,默认为true
  106. multiple: {
  107. type: Boolean,
  108. default: true
  109. }
  110. })
  111. const emit = defineEmits(['update:modelValue', 'change'])
  112. // 处理后的部门数据
  113. const processedDeptData = ref<ProcessedDeptData[]>([])
  114. // 选中的检验员对象列表
  115. const selectedCheckers = ref<CheckerItem[]>([...props.modelValue])
  116. // 选中的检验员ID列表(用于checkbox-group绑定)
  117. const selectedCheckerIds = ref<string[]>([])
  118. /** 获取检验员列表 */
  119. const getCheckerList = async (deptId?: string) => {
  120. try {
  121. const targetDeptId = deptId || props.deptId
  122. const originalData = await DeptGroupTeamApi.getDeptGroupTeamMembers({
  123. deptIds: targetDeptId
  124. })
  125. processedDeptData.value = processInspectorGroups(originalData)
  126. // 更新选中状态
  127. updateAllSubTeamCheckStatus()
  128. } catch (error) {
  129. console.error('获取检验员列表失败:', error)
  130. }
  131. }
  132. /** 更新所有小组的选中状态 */
  133. const updateAllSubTeamCheckStatus = () => {
  134. processedDeptData.value.forEach(dept => {
  135. dept.teamList.forEach(team => {
  136. team.memberList.forEach(subTeam => {
  137. updateSubTeamCheckStatus(subTeam)
  138. })
  139. })
  140. })
  141. }
  142. /** 更新单个小组的选中状态 */
  143. const updateSubTeamCheckStatus = (subTeam: any) => {
  144. const availableMembers = subTeam.memberList
  145. const allSubTeamMembers = availableMembers.map(m => m.id + ':' + m.memberId)
  146. if (allSubTeamMembers.length === 0) {
  147. subTeam.checked = false
  148. return
  149. }
  150. const selectedSubTeamMembers = selectedCheckers.value.filter(c =>
  151. allSubTeamMembers.includes(c.groupTeamId + ':' + c.memberId)
  152. )
  153. subTeam.checked = selectedSubTeamMembers.length === allSubTeamMembers.length
  154. }
  155. /** 处理小组全选 */
  156. const handleSubTeamCheckChange = (val: boolean, subTeam: any) => {
  157. const availableMembers = subTeam.memberList
  158. const memberIds = availableMembers.map(member => ({
  159. groupTeamId: subTeam.id,
  160. memberId: member.memberId,
  161. leaderId: subTeam.leaderId,
  162. member: member.member,
  163. isLeader: member.memberId === subTeam.leaderId
  164. }))
  165. if (val) {
  166. // 选中:添加所有可用成员
  167. const existingIds = new Set(selectedCheckers.value.map(c => c.groupTeamId + ':' + c.memberId))
  168. memberIds.forEach(member => {
  169. if (!existingIds.has(member.groupTeamId + ':' + member.memberId)) {
  170. selectedCheckers.value.push(member)
  171. }
  172. })
  173. } else {
  174. // 取消选中:移除当前小组的所有成员
  175. const memberIdsSet = new Set(memberIds.map(m => m.groupTeamId + ':' + m.memberId))
  176. selectedCheckers.value = selectedCheckers.value.filter(c =>
  177. !memberIdsSet.has(c.groupTeamId + ':' + c.memberId)
  178. )
  179. }
  180. // 更新小组选中状态
  181. updateSubTeamCheckStatus(subTeam)
  182. // 触发更新
  183. emitUpdate()
  184. }
  185. /** 判断检验员是否被选中 */
  186. const isCheckerSelected = (subTeamId: string, memberId: string): boolean => {
  187. return selectedCheckers.value.some(c =>
  188. c.groupTeamId === subTeamId && c.memberId.toString() === memberId.toString()
  189. )
  190. }
  191. /** 处理单选模式下的选择变化 */
  192. const handleSingleSelectChange = (val: boolean, subTeamId: string, memberId: string) => {
  193. if (!val) {
  194. // 取消选中:清空所有选中项
  195. selectedCheckers.value = []
  196. } else {
  197. // 选中:先清空其他选项,只保留当前选中的
  198. const allMembers = getAllMembersFromData()
  199. const selectedMember = allMembers.find(m =>
  200. m.groupTeamId === subTeamId && m.memberId.toString() === memberId.toString()
  201. )
  202. if (selectedMember) {
  203. selectedCheckers.value = [selectedMember]
  204. }
  205. }
  206. // 更新所有小组的选中状态
  207. updateAllSubTeamCheckStatus()
  208. // 触发更新
  209. emitUpdate()
  210. }
  211. /** 处理成员选择变化 */
  212. const handleMemberChange = (values: string[] | string) => {
  213. const allMembers = getAllMembersFromData()
  214. // 单选模式下,values 是单个字符串;多选模式下是数组
  215. const valueArray = Array.isArray(values) ? values : [values]
  216. selectedCheckers.value = valueArray.map(id => {
  217. const [groupTeamId, memberId] = id.split(':')
  218. return allMembers.find(m => m.groupTeamId === groupTeamId && m.memberId.toString() === memberId)
  219. }).filter(Boolean)
  220. // 更新所有小组的选中状态
  221. updateAllSubTeamCheckStatus()
  222. // 触发更新
  223. emitUpdate()
  224. }
  225. /** 从处理后的数据中获取所有成员 */
  226. const getAllMembersFromData = () => {
  227. const members: CheckerItem[] = []
  228. processedDeptData.value.forEach(dept => {
  229. dept.teamList.forEach(team => {
  230. team.memberList.forEach(subTeam => {
  231. subTeam.memberList.forEach(member => {
  232. members.push({
  233. groupTeamId: subTeam.id,
  234. memberId: member.memberId,
  235. leaderId: subTeam.leaderId,
  236. member: member.member,
  237. isLeader: member.memberId === subTeam.leaderId
  238. })
  239. })
  240. })
  241. })
  242. })
  243. return members
  244. }
  245. /** 触发更新事件 */
  246. const emitUpdate = () => {
  247. emit('update:modelValue', selectedCheckers.value)
  248. emit('change', selectedCheckers.value)
  249. }
  250. // 监听检验员对象变化,同步更新ID列表
  251. watch(() => selectedCheckers.value, (newVal) => {
  252. selectedCheckerIds.value = newVal.map(c => c.groupTeamId + ':' + c.memberId)
  253. }, { deep: true })
  254. // 监听外部传入的modelValue变化
  255. watch(() => props.modelValue, (newVal) => {
  256. selectedCheckers.value = [...newVal]
  257. }, { deep: true })
  258. // 监听多选模式变化,如果是单选且有多个选中项,只保留第一个
  259. watch(() => props.multiple, (isMultiple) => {
  260. if (!isMultiple && selectedCheckers.value.length > 1) {
  261. selectedCheckers.value = [selectedCheckers.value[0]]
  262. emitUpdate()
  263. }
  264. })
  265. // 暴露方法供父组件调用
  266. defineExpose({
  267. getCheckerList,
  268. processedDeptData,
  269. selectedCheckers,
  270. getAllMembersFromData
  271. })
  272. </script>
  273. <style lang="scss" scoped>
  274. .checker-select-container {
  275. width: 100%;
  276. .checker-list {
  277. border: 1px solid #EBEEF5;
  278. border-radius: 4px;
  279. .dept-section {
  280. .group-section {
  281. margin-bottom: 0;
  282. border-bottom: 1px solid #EBEEF5;
  283. &:last-child {
  284. border-bottom: none;
  285. }
  286. .group-content {
  287. padding: 6px 0 0 16px;
  288. .group-members {
  289. display: flex;
  290. margin-bottom: 6px;
  291. align-items: flex-start;
  292. &:last-child {
  293. margin-bottom: 0;
  294. }
  295. .label {
  296. flex: 0 0 100px;
  297. font-weight: bold;
  298. :deep(.el-checkbox) {
  299. margin-right: 0;
  300. display: inline-flex;
  301. align-items: center;
  302. font-weight: bold;
  303. .el-checkbox__label {
  304. padding-left: 6px;
  305. font-weight: bold !important;
  306. white-space: nowrap;
  307. overflow: hidden;
  308. text-overflow: ellipsis;
  309. }
  310. }
  311. }
  312. .members-list {
  313. flex: 1;
  314. display: flex;
  315. flex-wrap: wrap;
  316. gap: 0 12px;
  317. // 单选模式下的样式
  318. &.single-select-mode {
  319. display: flex;
  320. flex-wrap: wrap;
  321. gap: 0 12px;
  322. }
  323. :deep(.el-checkbox) {
  324. margin-right: 0;
  325. min-width: 90px;
  326. margin-bottom: 8px;
  327. display: inline-flex;
  328. align-items: center;
  329. .el-checkbox__label {
  330. padding-left: 6px;
  331. display: inline-block;
  332. width: 80px;
  333. text-align: left;
  334. }
  335. }
  336. }
  337. }
  338. }
  339. }
  340. }
  341. }
  342. }
  343. .leader-tag {
  344. display: inline-block;
  345. width: 14px;
  346. height: 14px;
  347. line-height: 12px;
  348. text-align: center;
  349. border: 1px solid #4475d6;
  350. font-size: 10px;
  351. margin-right: 4px;
  352. color: #4475d6;
  353. }
  354. </style>