SelectRole.vue 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. <template>
  2. <div class="le-select-role">
  3. <el-input :model-value="modelValue" style="display: none" />
  4. <el-tooltip v-if="!disabled" content="添加角色" placement="left">
  5. <el-button style="width: 32px" @click="selectHandler">
  6. <svg-icon style="font-size: 18px" icon-class="flow-group-add" />
  7. </el-button>
  8. </el-tooltip>
  9. <FlowNodeAvatar v-for="(item, i) of modelValue" :key="i" :name="item.name">
  10. <template #avatar>
  11. <svg-icon icon-class="flow-group" color="#fff" />
  12. </template>
  13. </FlowNodeAvatar>
  14. <el-dialog v-model="dialogVisible" class="le-dialog" title="角色选择" :width="460" destroy-on-close append-to-body>
  15. <div class="sc-user-select sc-user-select-role">
  16. <div class="sc-user-select__left">
  17. <div class="sc-user-select__select">
  18. <div v-loading="dataInfo.loading" class="sc-user-select__tree">
  19. <el-scrollbar>
  20. <el-tree
  21. ref="treeRef"
  22. class="menu"
  23. :data="dataInfo.list"
  24. :node-key="roleProps.key"
  25. :props="roleProps"
  26. show-checkbox
  27. check-strictly
  28. check-on-click-node
  29. :expand-on-click-node="false"
  30. :default-checked-keys="selectedIds"
  31. @check-change="checkChange"
  32. />
  33. </el-scrollbar>
  34. </div>
  35. </div>
  36. </div>
  37. <div class="sc-user-select__toicon">
  38. <el-icon><ArrowRight /></el-icon>
  39. </div>
  40. <div class="sc-user-select__selected">
  41. <header>已选 ({{ selected.length }})</header>
  42. <ul style="padding: 0; margin: 0">
  43. <el-scrollbar>
  44. <li v-for="(item, index) in selected" :key="item.id">
  45. <span class="name">
  46. <label>{{ item.name || '-' }}</label>
  47. </span>
  48. <span class="delete">
  49. <el-button type="danger" icon="Delete" circle size="small" @click="removeItem(index)" />
  50. </span>
  51. </li>
  52. </el-scrollbar>
  53. </ul>
  54. </div>
  55. </div>
  56. <template #footer>
  57. <el-button @click="dialogVisible = false">取消</el-button>
  58. <el-button type="primary" @click="submit">确认</el-button>
  59. </template>
  60. </el-dialog>
  61. </div>
  62. </template>
  63. <script lang="tsx" setup>
  64. import { watch, unref, ref, computed, PropType } from 'vue'
  65. import FlowNodeAvatar from '@/components/Flow/FlowNodeAvatar.vue'
  66. import role from '@/api/system/role.ts'
  67. import { ElMessage } from 'element-plus'
  68. defineOptions({ name: 'LeSelectRole' })
  69. const emit = defineEmits(['update:modelValue', 'change', 'input', 'update:visible', 'getCurRow'])
  70. type Item = { name: string; id: string; [key: string]: any }
  71. const props = defineProps({
  72. modelValue: {
  73. type: Array as PropType<Item[]>,
  74. default: () => []
  75. },
  76. min: { type: Number, default: 0 },
  77. max: { type: Number, default: 999999 },
  78. disabled: {
  79. type: Boolean,
  80. default: false
  81. },
  82. // params: {
  83. // type: Object
  84. // }
  85. })
  86. const treeRef = ref()
  87. const dialogVisible = ref(false)
  88. const dataInfo = ref({ list: [], loading: false })
  89. const roleProps = {
  90. key: 'id',
  91. label: 'name',
  92. children: 'children'
  93. }
  94. // 命中的数据
  95. const selected = ref<Item[]>([])
  96. const selectedIds = computed(() => {
  97. return selected.value.map(v => v.id)
  98. })
  99. watch(
  100. () => props.modelValue,
  101. vals => {
  102. selected.value = JSON.parse(JSON.stringify(vals || []))
  103. },
  104. {
  105. immediate: true,
  106. deep: true
  107. }
  108. )
  109. const checkChange = (data, checked) => {
  110. console.log(data, checked, 'data, checked')
  111. if (checked) {
  112. selected.value.push({
  113. id: data.id,
  114. name: data.name
  115. })
  116. } else {
  117. const idx = selected.value.findIndex(v => v.id === data.id)
  118. if (idx >= 0) {
  119. selected.value.splice(idx, 1)
  120. }
  121. }
  122. }
  123. const removeItem = (index: number) => {
  124. selected.value.splice(index, 1)
  125. treeRef.value?.setCheckedKeys(selectedIds.value)
  126. }
  127. // const { t } = useI18n()
  128. const selectHandler = (nodeKey: string, type: 1 | 3) => {
  129. console.log(nodeKey, 'nodeKey type', type)
  130. dialogVisible.value = true
  131. const _info = dataInfo.value
  132. if (!_info.loading && !_info.list.length) {
  133. queryRoleData()
  134. }
  135. }
  136. const queryRoleData = () => {
  137. dataInfo.value.loading = true
  138. role
  139. .rolePageApi({ page: 1, pageSize: 999 })
  140. .then((res: any) => {
  141. dataInfo.value = {
  142. list: res.records || [],
  143. loading: false
  144. }
  145. })
  146. .catch(() => {
  147. dataInfo.value.loading = false
  148. })
  149. }
  150. const submit = () => {
  151. // props.modelValue
  152. const _selected = selected.value
  153. if (!Array.isArray(_selected)) return ElMessage.warning('请选择数据')
  154. // 最小限制
  155. if (_selected.length < props.min) return ElMessage.warning(`选中的数据个数不能小于${props.min}条`)
  156. // 最大限制
  157. if (_selected.length > props.max) return ElMessage.warning(`选中的数据个数不能大于${props.max}条`)
  158. dialogVisible.value = false
  159. emit('update:modelValue', _selected)
  160. emit('change', _selected)
  161. console.log('我submit了...')
  162. // emit('input', _selected)
  163. }
  164. // const { prefixIcon, suffixIcon, prop, controlsPosition, t_placeholder, size, placeholder, max = 999999999999999, ...local_props } = ctx.attrs
  165. </script>
  166. <style scoped lang="scss">
  167. .le-select-role {
  168. display: flex;
  169. align-items: center;
  170. gap: 6px;
  171. flex-wrap: wrap;
  172. min-height: 32px;
  173. }
  174. .sc-user-select {
  175. display: flex;
  176. .sc-user-select__left {
  177. width: 200px;
  178. }
  179. .sc-user-select__tree {
  180. width: 200px;
  181. //height: 300px;
  182. //border: none;
  183. height: 343px;
  184. }
  185. .sc-user-select__toicon {
  186. display: flex;
  187. justify-content: center;
  188. align-items: center;
  189. margin: 0 10px;
  190. }
  191. .sc-user-select__toicon i {
  192. display: flex;
  193. justify-content: center;
  194. align-items: center;
  195. background: #ccc;
  196. width: 20px;
  197. height: 20px;
  198. text-align: center;
  199. line-height: 20px;
  200. border-radius: 50%;
  201. color: #fff;
  202. }
  203. .sc-user-select__select {
  204. display: flex;
  205. border: 1px solid var(--el-border-color-light);
  206. background: var(--el-color-white);
  207. }
  208. .sc-user-select__selected {
  209. height: 345px;
  210. width: 200px;
  211. border: 1px solid var(--el-border-color-light);
  212. background: var(--el-color-white);
  213. header {
  214. height: 43px;
  215. line-height: 43px;
  216. border-bottom: 1px solid var(--el-border-color-light);
  217. padding: 0 15px;
  218. font-size: 12px;
  219. }
  220. ul {
  221. height: 300px;
  222. overflow: auto;
  223. }
  224. li {
  225. display: flex;
  226. align-items: center;
  227. justify-content: space-between;
  228. padding: 5px 5px 5px 15px;
  229. height: 38px;
  230. .name {
  231. display: flex;
  232. align-items: center;
  233. .el-avatar {
  234. background: var(--el-color-primary);
  235. margin-right: 10px;
  236. }
  237. label {
  238. }
  239. }
  240. .delete {
  241. display: none;
  242. }
  243. &:hover {
  244. background: var(--el-color-primary-light-9);
  245. .delete {
  246. display: inline-block;
  247. }
  248. }
  249. }
  250. }
  251. }
  252. </style>