xzc 1 неделя назад
Родитель
Сommit
a2b4439385
1 измененных файлов с 125 добавлено и 98 удалено
  1. 125 98
      yudao-ui-admin-vue3/src/views/system/role/RoleAssignUserForm.vue

+ 125 - 98
yudao-ui-admin-vue3/src/views/system/role/RoleAssignUserForm.vue

@@ -1,5 +1,5 @@
 <template>
 <template>
-  <Dialog v-model="dialogVisible" title="分配用户">
+  <Dialog v-model="dialogVisible" title="分配用户" width="780">
     <el-form ref="formRef" v-loading="formLoading" :model="formData" label-width="80px">
     <el-form ref="formRef" v-loading="formLoading" :model="formData" label-width="80px">
       <el-form-item label="角色名称">
       <el-form-item label="角色名称">
         <el-tag>{{ formData.name }}</el-tag>
         <el-tag>{{ formData.name }}</el-tag>
@@ -7,34 +7,17 @@
       <el-form-item label="角色标识">
       <el-form-item label="角色标识">
         <el-tag>{{ formData.code }}</el-tag>
         <el-tag>{{ formData.code }}</el-tag>
       </el-form-item>
       </el-form-item>
-      <el-form-item label="选择用户">
-        <el-input
-          v-model="userSearch.nickName"
-          clearable
-          placeholder="请输入用户名搜索"
-          class="mb-14px"
-          @keyup.enter="fetchUserList"
-        >
-          <template #append>
-            <el-button @click="fetchUserList">
-              <Icon icon="ep:search" />
-            </el-button>
-          </template>
-        </el-input>
-        <SmartTable
-          ref="userTableRef"
-          v-model:pageNo="userSearch.pageNo"
-          v-model:pagesize="userSearch.pageSize"
-          :total="userSearch.total"
-          :columns="userColumns"
-          :data="userTableData"
-          :buttons="[]"
-          :showSettingTools="false"
-          :showSearch="false"
-          :showRefresh="false"
-          @on-page-size-change="onPageSizeChange"
-          @on-page-no-change="onPageNoChange"
-        />
+      <el-form-item label="选择用户" class="transfer-form-item">
+        <div class="transfer-wrapper">
+          <el-transfer
+            v-model="selectedUserIdList"
+            :titles="['未选用户', '已选用户']"
+            filterable
+            filter-placeholder="搜索用户"
+            :data="userList"
+            :props="{ label: 'nickname', key: 'id' }"
+          />
+        </div>
       </el-form-item>
       </el-form-item>
     </el-form>
     </el-form>
     <template #footer>
     <template #footer>
@@ -46,7 +29,6 @@
 <script lang="ts" setup>
 <script lang="ts" setup>
 import * as RoleApi from '@/api/system/role'
 import * as RoleApi from '@/api/system/role'
 import * as UserApi from '@/api/system/user'
 import * as UserApi from '@/api/system/user'
-import SmartTable from '@/components/SmartTable/SmartTable'
 
 
 defineOptions({ name: 'SystemRoleAssignUserForm' })
 defineOptions({ name: 'SystemRoleAssignUserForm' })
 
 
@@ -61,62 +43,10 @@ const formData = reactive({
 })
 })
 const formRef = ref()
 const formRef = ref()
 
 
-// 用户搜索条件
-const userSearch = reactive({
-  nickName: '',
-  pageNo: 1,
-  pageSize: 10,
-  total: 0
-})
-
-// 用户表格
-const userTableRef = ref()
-const userTableData = ref<any[]>([])
-const userColumns = [
-  { type: 'selection', width: '50px' },
-  { label: '工号', prop: 'employeeNo' },
-  { label: '用户名', prop: 'nickname' },
-  { label: '部门', prop: 'deptName' }
-]
-
-/** 获取用户列表 */
-const fetchUserList = async () => {
-  const params: PageParam = {
-    pageNo: userSearch.pageNo,
-    pageSize: userSearch.pageSize,
-    nickName: userSearch.nickName || undefined
-  }
-  const data = await UserApi.getUserPage(params)
-  userTableData.value = data.list || []
-  userSearch.total = data.total || 0
-
-  // 预选已有该角色的用户
-  await nextTick()
-  preSelectRows()
-}
-
-/** 根据角色预选已有的用户 */
-const preSelectRows = async () => {
-  if (!formData.roleId) return
-  const existingUserIds = await RoleApi.getRoleUserIdList(formData.roleId)
-  if (!existingUserIds || existingUserIds.length === 0) return
-  const tableRef = userTableRef.value?.getTableRef?.()
-  if (!tableRef) return
-  userTableData.value.forEach((row: any) => {
-    if (existingUserIds.includes(String(row.id))) {
-      tableRef.toggleRowSelection(row, true)
-    }
-  })
-}
-
-const onPageSizeChange = (val: number) => {
-  userSearch.pageSize = val
-  fetchUserList()
-}
-const onPageNoChange = (val: number) => {
-  userSearch.pageNo = val
-  fetchUserList()
-}
+// 所有用户列表(transfer 数据源)
+const userList = ref<any[]>([])
+// 已选中的用户 ID 列表
+const selectedUserIdList = ref<string[]>([])
 
 
 /** 打开弹窗 */
 /** 打开弹窗 */
 const open = async (row: RoleApi.RoleVO) => {
 const open = async (row: RoleApi.RoleVO) => {
@@ -127,7 +57,16 @@ const open = async (row: RoleApi.RoleVO) => {
   formData.code = row.code
   formData.code = row.code
   formLoading.value = true
   formLoading.value = true
   try {
   try {
-    await fetchUserList()
+    // 并行加载所有用户和已分配用户 ID
+    const [allUsers, existingUserIds] = await Promise.all([
+      UserApi.getSimpleUserList(),
+      RoleApi.getRoleUserIdList(row.id)
+    ])
+    userList.value = allUsers.map((item) => ({
+      ...item,
+      nickname: `${item.nickname}${item.employeeNo ? `(${item.employeeNo})` : ''}`
+    }))
+    selectedUserIdList.value = existingUserIds || []
   } finally {
   } finally {
     formLoading.value = false
     formLoading.value = false
   }
   }
@@ -143,17 +82,13 @@ const submitForm = async () => {
   }
   }
   formLoading.value = true
   formLoading.value = true
   try {
   try {
-    const tableRef = userTableRef.value?.getTableRef?.()
-    // 获取所有选中的行(跨页选中)
-    const selectedRows = tableRef?.getSelectionRows?.() || []
-    const selectedUserIds: string[] = selectedRows.map((row: any) => String(row.id))
-    if (selectedUserIds.length === 0) {
+    if (selectedUserIdList.value.length === 0) {
       message.warning('请至少选择一个用户')
       message.warning('请至少选择一个用户')
       return
       return
     }
     }
     await RoleApi.assignRoleUsers({
     await RoleApi.assignRoleUsers({
       roleId: String(formData.roleId),
       roleId: String(formData.roleId),
-      userIds: selectedUserIds
+      userIds: selectedUserIdList.value
     })
     })
     message.success('分配用户成功')
     message.success('分配用户成功')
     dialogVisible.value = false
     dialogVisible.value = false
@@ -165,10 +100,102 @@ const submitForm = async () => {
 
 
 /** 重置 */
 /** 重置 */
 const resetForm = () => {
 const resetForm = () => {
-  userSearch.nickName = ''
-  userSearch.pageNo = 1
-  userSearch.pageSize = 10
-  userSearch.total = 0
-  userTableData.value = []
+  userList.value = []
+  selectedUserIdList.value = []
 }
 }
 </script>
 </script>
+
+<style lang="scss" scoped>
+.transfer-form-item {
+  margin-bottom: 0;
+}
+
+.transfer-wrapper {
+  width: 100%;
+  height: 420px;
+}
+
+:deep() {
+  .el-transfer {
+    display: flex;
+    align-items: stretch;
+    height: 100%;
+  }
+
+  .el-transfer-panel {
+    flex: 1;
+    border: 1px solid var(--el-border-color-light);
+    border-radius: 4px;
+    overflow: hidden;
+    display: flex;
+    flex-direction: column;
+  }
+
+  .el-transfer-panel__header {
+    background: var(--el-fill-color-light);
+    border-bottom: 1px solid var(--el-border-color-lighter);
+    padding: 0 12px;
+    height: 40px;
+    line-height: 40px;
+
+    .el-checkbox__label {
+      font-size: 13px;
+      font-weight: 500;
+      color: var(--el-text-color-regular);
+    }
+  }
+
+  .el-transfer-panel__body {
+    flex: 1;
+    overflow: hidden;
+
+    .el-transfer-panel__list {
+      height: 100%;
+      padding: 4px 0;
+    }
+  }
+
+  .el-transfer-panel__filter {
+    padding: 8px 12px;
+
+    .el-input__wrapper {
+      border-radius: 4px;
+    }
+  }
+
+  .el-transfer-panel__item {
+    padding: 0 12px;
+    height: 32px;
+    line-height: 32px;
+    transition: background-color 0.2s;
+
+    &:hover {
+      background-color: var(--el-fill-color-light);
+    }
+  }
+
+  .el-transfer__buttons {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    gap: 12px;
+    padding: 0 16px;
+    flex-shrink: 0;
+
+    .el-transfer__button {
+      width: 32px;
+      height: 32px;
+      border-radius: 50%;
+      padding: 0;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+
+      &:nth-child(2) {
+        margin: 0;
+      }
+    }
+  }
+}
+</style>