| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- <template>
- <Dialog v-model="dialogVisible" title="分配用户" width="780">
- <el-form ref="formRef" v-loading="formLoading" :model="formData" label-width="80px">
- <el-form-item label="角色名称">
- <el-tag>{{ formData.name }}</el-tag>
- </el-form-item>
- <el-form-item label="角色标识">
- <el-tag>{{ formData.code }}</el-tag>
- </el-form-item>
- <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>
- <template #footer>
- <el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
- <el-button @click="dialogVisible = false">取 消</el-button>
- </template>
- </Dialog>
- </template>
- <script lang="ts" setup>
- import * as RoleApi from '@/api/system/role'
- import * as UserApi from '@/api/system/user'
- defineOptions({ name: 'SystemRoleAssignUserForm' })
- const message = useMessage()
- const dialogVisible = ref(false)
- const formLoading = ref(false)
- const formData = reactive({
- roleId: undefined as number | undefined,
- name: '',
- code: ''
- })
- const formRef = ref()
- // 所有用户列表(transfer 数据源)
- const userList = ref<any[]>([])
- // 已选中的用户 ID 列表
- const selectedUserIdList = ref<string[]>([])
- // 打开弹窗时的原始已选用户 ID(用于计算新增/移除)
- const originalUserIds = ref<string[]>([])
- /** 打开弹窗 */
- const open = async (row: RoleApi.RoleVO) => {
- dialogVisible.value = true
- resetForm()
- formData.roleId = row.id
- formData.name = row.name
- formData.code = row.code
- formLoading.value = true
- try {
- // 并行加载所有用户和已分配用户 ID
- const [allUsers, existingUserIds] = await Promise.all([
- UserApi.getSimpleUserList(),
- RoleApi.getRoleUserIdList(row.id)
- ])
- // 统一将 id 转为字符串,避免 el-transfer 因类型不一致(number vs string)导致无法移除
- userList.value = allUsers.map((item) => ({
- ...item,
- id: String(item.id),
- nickname: `${item.nickname}${item.employeeNo ? `(${item.employeeNo})` : ''}`
- }))
- const userIdSet = new Set(userList.value.map((u) => u.id))
- selectedUserIdList.value = (existingUserIds || []).map(String).filter((id) => userIdSet.has(id))
- // 保存原始已选用户 ID,用于提交时计算新增/移除
- originalUserIds.value = [...selectedUserIdList.value]
- } finally {
- formLoading.value = false
- }
- }
- defineExpose({ open })
- /** 提交表单 */
- const emit = defineEmits(['success'])
- const submitForm = async () => {
- if (!formData.roleId) {
- message.error('角色信息异常')
- return
- }
- formLoading.value = true
- try {
- // 计算新增用户(当前选中但原来不在)
- const currentSet = new Set(selectedUserIdList.value)
- const originalSet = new Set(originalUserIds.value)
- const addUserIds = selectedUserIdList.value.filter((id) => !originalSet.has(id))
- // 计算移除用户(原来在但当前未选中)
- const removeUserIds = originalUserIds.value.filter((id) => !currentSet.has(id))
- if (addUserIds.length === 0 && removeUserIds.length === 0) {
- message.warning('未做任何更改')
- return
- }
- await RoleApi.assignRoleUsers({
- roleId: String(formData.roleId),
- userIds: addUserIds.length > 0 ? addUserIds : undefined,
- removeUserIds: removeUserIds.length > 0 ? removeUserIds : undefined
- })
- message.success('分配用户成功')
- dialogVisible.value = false
- emit('success')
- } finally {
- formLoading.value = false
- }
- }
- /** 重置 */
- const resetForm = () => {
- userList.value = []
- selectedUserIdList.value = []
- originalUserIds.value = []
- }
- </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>
|