user-modal.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. <template>
  2. <hc-dialog v-model="isShow" ui="hc-report-tasks-user-modal" widths="960px" title="选择任务人" @close="modalClose">
  3. <div class="cards-wrapper">
  4. <div class="card-div-2 h-full w-235px">
  5. <hc-card scrollbar title="角色类型" :loading="signUserLoading">
  6. <template v-for="item in signUserList" :key="item.roleId">
  7. <div class="hc-tasks-user-role-item" :class="{ cur: roleItem.roleId === item.roleId }" @click="roleItemClick(item)">
  8. {{ item.roleName }}
  9. </div>
  10. </template>
  11. </hc-card>
  12. </div>
  13. <div v-if="roleItem.roleId" class="card-div-3 h-full w-265px">
  14. <hc-card v-if="positionList.length > 0" scrollbar>
  15. <template #header>
  16. <hc-search-input v-model="positionKey" placeholder="岗位搜索" icon="" @search="positionSearch" />
  17. </template>
  18. <template v-for="item in positionList" :key="item.roleId">
  19. <div class="hc-tasks-user-role-item" :class="{ cur: positionItem.roleId === item.roleId }" @click="positionItemClick(item)">
  20. <i class="i-ph-user-list-light mr-5px" />
  21. <span>{{ item.roleName }}</span>
  22. </div>
  23. </template>
  24. </hc-card>
  25. <div v-else class="card-empty-no">
  26. <hc-empty :src="HcLoadSvg" title="请先选择角色" />
  27. </div>
  28. </div>
  29. <div class="card-div-4 h-full w-235px">
  30. <hc-card v-if="signPfxFileList.length > 0" :loading="signPfxFileListLoading">
  31. <template #header>
  32. <hc-search-input v-model="signUserKey" placeholder="人员搜索" icon="" @search="signUserSearch" />
  33. </template>
  34. <div class="hc-tasks-user-sign-pfx-box">
  35. <el-scrollbar ref="scrollRef">
  36. <template v-for="item in signPfxFileList" :key="item.name">
  37. <div v-if="item.data.length > 0" :id="`hc-sign-pfx-file-item-${item.name}`" class="hc-sign-pfx-file-item">
  38. <div class="hc-tasks-user-letter">{{ item.name }}</div>
  39. <template v-for="(items, index) in item.data" :key="index">
  40. <div class="hc-tasks-user-item" @click="signUserItemClick(items)">
  41. <i class="i-iconoir-user mr-5px" />
  42. <span>{{ items.certificateUserName }}</span>
  43. </div>
  44. </template>
  45. </div>
  46. </template>
  47. </el-scrollbar>
  48. </div>
  49. <div class="hc-tasks-user-letter-index">
  50. <div v-for="item in alphabet" :key="item" class="item" @click="alphabetClick(item)">{{ item }}</div>
  51. </div>
  52. </hc-card>
  53. <div v-else class="card-empty-no">
  54. <hc-empty :src="HcLoadSvg" title="请先选择岗位" />
  55. </div>
  56. </div>
  57. <div class="card-div-5 h-full w-205px">
  58. <hc-card v-if="userData.length > 0" scrollbar>
  59. <template #header>
  60. <span>已选择{{ userData.length || 0 }}人</span>
  61. </template>
  62. <template #extra>
  63. <el-button type="warning" size="small" @click="fixedUserSortClick">调整排序</el-button>
  64. </template>
  65. <div class="hc-tasks-user-cur-box" :class="`type-${fixedFlowLinkTypeVal}`">
  66. <template v-for="(item, index) in userData" :key="index">
  67. <div class="hc-tasks-user-item">
  68. <el-tag closable @close="fixedItemUserListDel(index)">
  69. <i class="i-ri-user-3-fill mr-5px" />
  70. <span>{{ item.userName }}</span>
  71. </el-tag>
  72. </div>
  73. </template>
  74. </div>
  75. </hc-card>
  76. <div v-else class="card-empty-no">
  77. <hc-empty :src="HcLoadSvg" title="请先选择人员" />
  78. </div>
  79. </div>
  80. </div>
  81. <template #footer>
  82. <el-button @click="modalClose">取消</el-button>
  83. <el-button type="primary" :loading="confirmLoading" @click="confirmClick">确定</el-button>
  84. </template>
  85. </hc-dialog>
  86. <!-- 任务人排序 -->
  87. <HcSortModal v-model="isUserSort" :data="userSortData" @finish="userSortFinish" />
  88. </template>
  89. <script setup>
  90. import { nextTick, ref, watch } from 'vue'
  91. import { pinyin } from 'pinyin-pro'
  92. import { deepClone, getArrValue, getObjValue, isNullES } from 'js-fast-way'
  93. import HcLoadSvg from '~src/assets/view/load.svg'
  94. import HcSortModal from './sort-modal.vue'
  95. import mainApi from '~api/tasks/flow'
  96. const props = defineProps({
  97. data: {
  98. type: Array,
  99. default: () => ([]),
  100. },
  101. datas: {
  102. type: Object,
  103. default: () => ({}),
  104. },
  105. fixedFlowLinkTypeVal: {
  106. type: [String, Number],
  107. default: '', //流程审批1,平行审批2
  108. },
  109. })
  110. const emit = defineEmits(['finish', 'close'])
  111. const fixedFlowLinkTypeVal = ref(props.fixedFlowLinkTypeVal)
  112. //双向绑定
  113. const isShow = defineModel('modelValue', {
  114. default: false,
  115. })
  116. //监听参数
  117. const dataInfo = ref(props.datas)
  118. watch(() => props.datas, (data) => {
  119. dataInfo.value = getObjValue(data)
  120. }, { deep: true, immediate: true })
  121. watch(() => props.fixedFlowLinkTypeVal, (typeVal) => {
  122. fixedFlowLinkTypeVal.value = typeVal
  123. }, { deep: true, immediate: true })
  124. //监听数据
  125. const userData = ref([])
  126. watch(() => props.data, (data) => {
  127. const res = getArrValue(data)
  128. userData.value = deepClone(res)
  129. }, { deep: true, immediate: true })
  130. watch(isShow, (val) => {
  131. if (val) setInitData()
  132. })
  133. //初始化
  134. const setInitData = async () => {
  135. await nextTick()
  136. await getAllRoleList()
  137. await getsignPfxFileList()
  138. console.log(userData.value)
  139. }
  140. const signPfxFileListLoading = ref(false)
  141. const getsignPfxFileList = async () => {
  142. signPfxFileListLoading.value = true
  143. const { contractId } = getObjValue(dataInfo.value)
  144. const { data } = await mainApi.findAllUserAndRoleList({ contractId, roleId:roleItem.value.roleId})
  145. let arr = getObjValue(data)
  146. setSignPfxUser(arr['userList'])
  147. signPfxFileListLoading.value = false
  148. }
  149. //角色列表
  150. const signUserLoading = ref(false)
  151. const signUserList = ref([])
  152. const getAllRoleList = async () => {
  153. signUserLoading.value = true
  154. const { contractId } = getObjValue(dataInfo.value)
  155. const { data } = await mainApi.queryAllRoleList({ contractId })
  156. signUserList.value = getArrValue(data)
  157. signUserLoading.value = false
  158. }
  159. //角色被点击
  160. const roleItem = ref({})
  161. const roleItemClick = async(item) => {
  162. //roleItem.value = item
  163. //const arr = getArrValue(item.childRoleList)
  164. // positionList.value = deepClone(arr)
  165. if (roleItem.value.roleId === item.roleId) {
  166. // 如果点击的是已选中的角色,则清空选择
  167. roleItem.value = {}
  168. positionList.value = []
  169. positionItem.value = {}
  170. roleItem.value = {}
  171. await getsignPfxFileList()
  172. return
  173. }
  174. roleItem.value = item
  175. const arr = getArrValue(item.childRoleList)
  176. positionList.value = deepClone(arr)
  177. setSignPfxUser(item.signPfxFileList)
  178. }
  179. //岗位搜索
  180. const positionKey = ref('')
  181. const positionList = ref([])
  182. const positionSearch = () => {
  183. const key = positionKey.value
  184. const list = getArrValue(roleItem.value?.childRoleList)
  185. const arr = deepClone(list)
  186. if (isNullES(key)) {
  187. positionList.value = arr
  188. return
  189. }
  190. positionList.value = arr.filter(({ roleName }) => roleName.toLowerCase().includes(key.toLowerCase()))
  191. }
  192. //岗位被点击
  193. const positionItem = ref({})
  194. const positionItemClick = async(item) => {
  195. // positionItem.value = item
  196. // setSignPfxUser(item.signPfxFileList)
  197. if (positionItem.value.roleId === item.roleId) {
  198. // 如果点击的是已选中的岗位,则清空选择并显示当前角色下所有岗位的所有人员
  199. positionItem.value = {}
  200. // 调用API获取当前角色下所有人员
  201. await getsignPfxFileList()
  202. return
  203. }
  204. positionItem.value = item
  205. setSignPfxUser(item.signPfxFileList)
  206. }
  207. //设置任务人数据
  208. const alphabet = Array.from({ length: 26 }, (_, i) => String.fromCharCode(65 + i))
  209. const setSignPfxUser = (data) => {
  210. const list = deepClone(data)
  211. const arr = getArrValue(list)
  212. arr.forEach(item => {
  213. item.letter = getFirstLetter(item.certificateUserName)
  214. })
  215. signPfxFileData.value = deepClone(arr)
  216. signPfxFileList.value = alphabet.map(letter => ({
  217. name: letter,
  218. data: arr.filter(item => item.letter === letter),
  219. }))
  220. }
  221. //中文转姓氏拼音
  222. const getFirstLetter = (name) => {
  223. return pinyin(name.charAt(0), { pattern: 'first', toneType: 'none', surname: 'head' }).charAt(0).toUpperCase()
  224. }
  225. //搜索任务人
  226. const signPfxFileData = ref([])
  227. const signUserKey = ref('')
  228. const signPfxFileList = ref([])
  229. const signUserSearch = () => {
  230. const key = signUserKey.value
  231. const arr = deepClone(signPfxFileData.value)
  232. if (isNullES(key)) {
  233. setSignPfxUser(arr)
  234. return
  235. }
  236. // 判断是否为全英文
  237. const isAllEnglish = /^[A-Za-z]+$/.test(key)
  238. const letterName = getFirstLetter(key)
  239. //搜索筛选
  240. const filteredData = arr.filter(({ certificateUserName, letter }) => {
  241. if (isAllEnglish) {
  242. // 如果是英文,判断首字母是否一致
  243. return letter.toLowerCase().includes(letterName.toLowerCase())
  244. } else {
  245. // 如果是中文或其他字符,进行精准搜索
  246. return certificateUserName.toLowerCase().includes(key.toLowerCase())
  247. }
  248. })
  249. signPfxFileList.value = alphabet.map(letter => ({
  250. name: letter,
  251. data: filteredData.filter(item => item.letter === letter),
  252. }))
  253. }
  254. //滚动到相关位置
  255. const scrollRef = ref(null)
  256. const alphabetClick = (key) => {
  257. const ids = `hc-sign-pfx-file-item-${key}`
  258. const dom = document.getElementById(ids)
  259. if (isNullES(dom)) return
  260. scrollRef.value?.setScrollTop(dom.offsetTop - 20)
  261. }
  262. //任务人被点击
  263. const signUserItemClick = ({ certificateUserId, certificateUserName }) => {
  264. userData.value.push({ userId: certificateUserId, userName: certificateUserName })
  265. }
  266. //删除选择的任务人
  267. const fixedItemUserListDel = (index) => {
  268. userData.value.splice(index, 1)
  269. }
  270. //任务人排序
  271. const isUserSort = ref(false)
  272. const userSortData = ref([])
  273. const fixedUserSortClick = () => {
  274. const arr = deepClone(userData.value)
  275. if (arr.length <= 0) {
  276. window.$message.warning('请先添加任务人')
  277. return
  278. }
  279. userSortData.value = arr
  280. isUserSort.value = true
  281. }
  282. //任务人排序完成
  283. const userSortFinish = (data) => {
  284. userData.value = getArrValue(data)
  285. }
  286. //确定选择
  287. const confirmLoading = ref(false)
  288. const confirmClick = async () => {
  289. // const list = deepClone(userData.value)
  290. const list = deepClone(userData.value).filter((user, index, self) =>
  291. index === self.findIndex((u) => u.userId === user.userId),
  292. )
  293. if (list.length <= 0) {
  294. window.$message.warning('请先添加任务人')
  295. return
  296. }
  297. emit('finish', list)
  298. modalClose()
  299. }
  300. //关闭窗口
  301. const modalClose = () => {
  302. isShow.value = false
  303. emit('close')
  304. }
  305. </script>
  306. <style scoped>
  307. .cards-wrapper {
  308. display: flex;
  309. gap: 12px;
  310. width: 100%;
  311. height: 100%;
  312. }
  313. .card-div-2,
  314. .card-div-3,
  315. .card-div-4,
  316. .card-div-5 {
  317. height: 100%;
  318. }
  319. .card-div-2 {
  320. width: 235px;
  321. flex-shrink: 0;
  322. }
  323. .card-div-3 {
  324. width: 255px;
  325. flex-shrink: 0;
  326. }
  327. .card-div-4,
  328. .card-div-5 {
  329. flex: 1;
  330. min-width: 205px;
  331. }
  332. </style>