843 lines
24 KiB
Vue
843 lines
24 KiB
Vue
<template>
|
||
<ArtTableFullScreen>
|
||
<div class="account-list-page" id="table-full-screen">
|
||
<ElCard shadow="never">
|
||
<div class="detail-header">
|
||
<ElButton @click="handleBack">
|
||
<template #icon
|
||
><ElIcon><ArrowLeft /></ElIcon
|
||
></template>
|
||
返回
|
||
</ElButton>
|
||
<h2 class="detail-title">{{ pageTitle }}</h2>
|
||
</div>
|
||
|
||
<!-- 搜索栏 -->
|
||
<ArtSearchBar
|
||
v-model:filter="searchForm"
|
||
:items="searchFormItems"
|
||
:show-expand="false"
|
||
@reset="handleReset"
|
||
@search="handleSearch"
|
||
></ArtSearchBar>
|
||
|
||
<!-- 操作按钮 -->
|
||
<div style="margin: 10px 0">
|
||
<ElButton
|
||
v-if="hasAuth(isShopType ? 'shop_account:add' : 'enterprise_account:add')"
|
||
@click="showAddAccountDialog"
|
||
>
|
||
{{ isShopType ? '新增店铺账号' : '新增企业账号' }}
|
||
</ElButton>
|
||
</div>
|
||
|
||
<!-- 表格 -->
|
||
<ArtTable
|
||
ref="tableRef"
|
||
row-key="ID"
|
||
:loading="loading"
|
||
:data="accountList"
|
||
:currentPage="pagination.page"
|
||
:pageSize="pagination.pageSize"
|
||
:total="pagination.total"
|
||
:marginTop="10"
|
||
v
|
||
height="60vh"
|
||
@size-change="handleSizeChange"
|
||
@current-change="handleCurrentChange"
|
||
>
|
||
<template #default>
|
||
<ElTableColumn
|
||
v-for="col in columns"
|
||
:key="col.prop || (col as any).type"
|
||
v-bind="col"
|
||
/>
|
||
</template>
|
||
</ArtTable>
|
||
|
||
<!-- 新增账号对话框 -->
|
||
<ElDialog
|
||
v-model="dialogVisible"
|
||
:title="isShopType ? '新增店铺账号' : '新增企业账号'"
|
||
width="500px"
|
||
>
|
||
<ElForm ref="formRef" :model="accountForm" :rules="accountRules" label-width="100px">
|
||
<ElFormItem label="用户名" prop="username">
|
||
<ElInput v-model="accountForm.username" placeholder="请输入用户名" />
|
||
</ElFormItem>
|
||
<ElFormItem label="密码" prop="password">
|
||
<ElInput
|
||
v-model="accountForm.password"
|
||
type="password"
|
||
placeholder="请输入密码"
|
||
show-password
|
||
/>
|
||
</ElFormItem>
|
||
<ElFormItem label="手机号" prop="phone">
|
||
<ElInput v-model="accountForm.phone" placeholder="请输入手机号" maxlength="11" />
|
||
</ElFormItem>
|
||
<ElFormItem label="账号类型" prop="user_type">
|
||
<ElInput :value="isShopType ? '代理账号' : '企业账号'" disabled />
|
||
</ElFormItem>
|
||
</ElForm>
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<ElButton @click="dialogVisible = false">取消</ElButton>
|
||
<ElButton type="primary" @click="handleSubmit" :loading="submitLoading">
|
||
提交
|
||
</ElButton>
|
||
</div>
|
||
</template>
|
||
</ElDialog>
|
||
|
||
<!-- 分配角色对话框 -->
|
||
<ElDialog v-model="roleDialogVisible" width="900px">
|
||
<template #header>
|
||
<div class="dialog-header">
|
||
<span class="dialog-title">{{
|
||
isShopType ? '分配店铺账号角色' : '分配企业客户角色'
|
||
}}</span>
|
||
<div class="account-info">
|
||
<span class="account-name">{{ currentAccountName }}</span>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
<div class="role-transfer-container">
|
||
<!-- 左侧:可分配角色列表 -->
|
||
<div class="transfer-panel">
|
||
<div class="panel-header">
|
||
<span class="panel-title">可分配角色</span>
|
||
<ElInput
|
||
v-model="leftRoleFilter"
|
||
placeholder="搜索角色"
|
||
clearable
|
||
size="small"
|
||
style="width: 180px"
|
||
/>
|
||
</div>
|
||
<div class="panel-body">
|
||
<ElCheckboxGroup v-model="rolesToAdd" class="role-list">
|
||
<div v-for="role in filteredAvailableRoles" :key="role.ID" class="role-item">
|
||
<ElCheckbox :label="role.ID" :disabled="selectedRoles.includes(role.ID)">
|
||
<span class="role-info">
|
||
<span>{{ role.role_name }}</span>
|
||
<ElTag :type="role.role_type === 1 ? 'primary' : 'success'" size="small">
|
||
{{ role.role_type === 1 ? '平台角色' : '客户角色' }}
|
||
</ElTag>
|
||
</span>
|
||
</ElCheckbox>
|
||
</div>
|
||
</ElCheckboxGroup>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 中间:操作按钮 -->
|
||
<div class="transfer-buttons">
|
||
<ElButton
|
||
type="primary"
|
||
:icon="'ArrowRight'"
|
||
@click="addRoles"
|
||
:disabled="rolesToAdd.length === 0"
|
||
>
|
||
添加
|
||
</ElButton>
|
||
</div>
|
||
|
||
<!-- 右侧:已分配角色列表 -->
|
||
<div class="transfer-panel">
|
||
<div class="panel-header">
|
||
<span class="panel-title">已分配角色</span>
|
||
<ElInput
|
||
v-model="rightRoleFilter"
|
||
placeholder="搜索角色"
|
||
clearable
|
||
size="small"
|
||
style="width: 180px"
|
||
/>
|
||
</div>
|
||
<div class="panel-body">
|
||
<div class="role-list">
|
||
<div
|
||
v-for="role in filteredAssignedRoles"
|
||
:key="role.ID"
|
||
class="role-item assigned-role-item"
|
||
>
|
||
<span class="role-info">
|
||
<span>{{ role.role_name }}</span>
|
||
<ElTag :type="role.role_type === 1 ? 'primary' : 'success'" size="small">
|
||
{{ role.role_type === 1 ? '平台角色' : '客户角色' }}
|
||
</ElTag>
|
||
</span>
|
||
<ElButton type="danger" size="small" link @click="removeSingleRole(role.ID)">
|
||
移除
|
||
</ElButton>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<ElButton @click="roleDialogVisible = false">关闭</ElButton>
|
||
</div>
|
||
</template>
|
||
</ElDialog>
|
||
</ElCard>
|
||
</div>
|
||
</ArtTableFullScreen>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { h, computed, ref, reactive, onMounted, nextTick } from 'vue'
|
||
import { useRoute, useRouter } from 'vue-router'
|
||
import {
|
||
ElMessage,
|
||
ElTag,
|
||
ElIcon,
|
||
ElButton,
|
||
ElCard,
|
||
ElDialog,
|
||
ElForm,
|
||
ElFormItem,
|
||
ElInput,
|
||
ElCheckbox,
|
||
ElCheckboxGroup
|
||
} from 'element-plus'
|
||
import { ArrowLeft } from '@element-plus/icons-vue'
|
||
import type { FormInstance, FormRules } from 'element-plus'
|
||
import { AccountService, RoleService } from '@/api/modules'
|
||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||
import type { SearchFormItem } from '@/types'
|
||
import type { PlatformAccount, PlatformRole } from '@/types/api'
|
||
import { formatDateTime } from '@/utils/business/format'
|
||
import { useAuth } from '@/composables/useAuth'
|
||
|
||
defineOptions({ name: 'CommonAccountList' })
|
||
|
||
const route = useRoute()
|
||
const router = useRouter()
|
||
const { hasAuth } = useAuth()
|
||
|
||
// 判断页面类型:通过 query 参数判断
|
||
const isShopType = computed(() => route.query.type === 'shop')
|
||
|
||
// 页面标题
|
||
const pageTitle = computed(() => (isShopType.value ? '店铺账号列表' : '企业客户账号列表'))
|
||
|
||
// 过滤参数键名
|
||
const filterParamKey = computed(() => (isShopType.value ? 'shop_id' : 'enterprise_id'))
|
||
|
||
// 用户类型映射
|
||
const getUserTypeName = (type: number): string => {
|
||
const typeMap: Record<number, string> = {
|
||
1: '超级管理员',
|
||
2: '平台用户',
|
||
3: '代理账号',
|
||
4: '企业账号'
|
||
}
|
||
return typeMap[type] || '未知'
|
||
}
|
||
|
||
// 状态名称映射
|
||
const getStatusName = (status: number): string => {
|
||
return status === 1 ? '启用' : '禁用'
|
||
}
|
||
|
||
const loading = ref(false)
|
||
const tableRef = ref()
|
||
const accountList = ref<PlatformAccount[]>([])
|
||
|
||
// 分配角色相关变量
|
||
const roleDialogVisible = ref(false)
|
||
const currentAccountId = ref<number>(0)
|
||
const currentAccountName = ref<string>('')
|
||
const currentAccountType = ref<number>(0)
|
||
const selectedRoles = ref<number[]>([])
|
||
const allRoles = ref<PlatformRole[]>([])
|
||
const rolesToAdd = ref<number[]>([])
|
||
const leftRoleFilter = ref('')
|
||
const rightRoleFilter = ref('')
|
||
|
||
// 搜索表单初始值
|
||
const initialSearchState = {
|
||
username: '',
|
||
phone: '',
|
||
user_type: undefined as number | undefined,
|
||
status: undefined as number | undefined
|
||
}
|
||
|
||
// 搜索表单
|
||
const searchForm = reactive({ ...initialSearchState })
|
||
|
||
// 分页
|
||
const pagination = reactive({
|
||
page: 1,
|
||
pageSize: 20,
|
||
total: 0
|
||
})
|
||
|
||
// 搜索表单配置
|
||
const searchFormItems: SearchFormItem[] = [
|
||
{
|
||
label: '用户名',
|
||
prop: 'username',
|
||
type: 'input',
|
||
config: {
|
||
clearable: true,
|
||
placeholder: '请输入用户名'
|
||
}
|
||
},
|
||
{
|
||
label: '手机号',
|
||
prop: 'phone',
|
||
type: 'input',
|
||
config: {
|
||
clearable: true,
|
||
placeholder: '请输入手机号'
|
||
}
|
||
},
|
||
{
|
||
label: '状态',
|
||
prop: 'status',
|
||
type: 'select',
|
||
config: {
|
||
clearable: true,
|
||
placeholder: '全部'
|
||
},
|
||
options: () => [
|
||
{ label: '启用', value: 1 },
|
||
{ label: '禁用', value: 0 }
|
||
]
|
||
}
|
||
]
|
||
|
||
// 获取用户类型标签类型
|
||
const getUserTypeTag = (type: number) => {
|
||
return type === 3 ? 'success' : 'primary'
|
||
}
|
||
|
||
// 获取状态标签类型
|
||
const getStatusTag = (status: number) => {
|
||
return status === 1 ? 'success' : 'info'
|
||
}
|
||
|
||
// 列配置
|
||
const columns = computed(() => {
|
||
const baseColumns = [
|
||
{
|
||
prop: 'username',
|
||
label: '用户名'
|
||
},
|
||
{
|
||
prop: 'phone',
|
||
label: '手机号'
|
||
},
|
||
{
|
||
prop: 'user_type',
|
||
label: '用户类型',
|
||
formatter: (row: PlatformAccount) => {
|
||
return h(ElTag, { type: getUserTypeTag(row.user_type) }, () =>
|
||
getUserTypeName(row.user_type)
|
||
)
|
||
}
|
||
}
|
||
]
|
||
|
||
// 根据页面类型添加不同的列
|
||
if (isShopType.value) {
|
||
// 店铺类型:显示店铺名称
|
||
baseColumns.push({
|
||
prop: 'shop_name',
|
||
label: '店铺名称',
|
||
formatter: (row: PlatformAccount) => (row as any).shop_name || '-'
|
||
})
|
||
} else {
|
||
// 企业类型:显示企业名称
|
||
baseColumns.push({
|
||
prop: 'enterprise_name',
|
||
label: '企业名称',
|
||
formatter: (row: PlatformAccount) => (row as any).enterprise_name || '-'
|
||
})
|
||
}
|
||
|
||
// 添加状态和创建时间
|
||
baseColumns.push(
|
||
{
|
||
prop: 'status',
|
||
label: '状态',
|
||
formatter: (row: PlatformAccount) => {
|
||
return h(ElTag, { type: getStatusTag(row.status) }, () => getStatusName(row.status))
|
||
}
|
||
},
|
||
{
|
||
prop: 'created_at',
|
||
label: '创建时间',
|
||
formatter: (row: PlatformAccount) => formatDateTime((row as any).created_at)
|
||
},
|
||
{
|
||
prop: 'operation',
|
||
label: '操作',
|
||
width: 160,
|
||
fixed: 'right',
|
||
formatter: (row: PlatformAccount) => {
|
||
// 根据类型检查权限
|
||
const permission = isShopType.value
|
||
? 'shop_account:patch_role'
|
||
: 'customer_accounts:patch_role'
|
||
|
||
if (!hasAuth(permission)) {
|
||
return h('span', '-')
|
||
}
|
||
|
||
return h(ArtButtonTable, {
|
||
text: isShopType.value ? '分配店铺账号角色' : '分配企业客户角色',
|
||
onClick: () => showRoleDialog(row)
|
||
})
|
||
}
|
||
}
|
||
)
|
||
|
||
return baseColumns
|
||
})
|
||
|
||
// 获取账号列表
|
||
const getTableData = async () => {
|
||
loading.value = true
|
||
try {
|
||
const entityId = Number(route.params.id)
|
||
const params: any = {
|
||
page: pagination.page,
|
||
pageSize: pagination.pageSize,
|
||
username: searchForm.username || undefined,
|
||
phone: searchForm.phone || undefined,
|
||
user_type: isShopType.value ? 3 : 4, // 根据页面类型自动设置: 3:代理账号, 4:企业账号
|
||
status: searchForm.status,
|
||
[filterParamKey.value]: entityId // 动态设置 shop_id 或 enterprise_id
|
||
}
|
||
|
||
// 清理空值
|
||
Object.keys(params).forEach((key) => {
|
||
if (params[key] === '' || params[key] === undefined) {
|
||
delete params[key]
|
||
}
|
||
})
|
||
|
||
const res = await AccountService.getAccounts(params)
|
||
if (res.code === 0) {
|
||
accountList.value = res.data.items || []
|
||
pagination.total = res.data.total || 0
|
||
}
|
||
} catch (error) {
|
||
console.error(error)
|
||
console.log('获取账号列表失败')
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
// 重置搜索
|
||
const handleReset = () => {
|
||
Object.assign(searchForm, { ...initialSearchState })
|
||
pagination.page = 1
|
||
getTableData()
|
||
}
|
||
|
||
// 搜索
|
||
const handleSearch = () => {
|
||
pagination.page = 1
|
||
getTableData()
|
||
}
|
||
|
||
// 处理表格分页变化
|
||
const handleSizeChange = (newPageSize: number) => {
|
||
pagination.pageSize = newPageSize
|
||
getTableData()
|
||
}
|
||
|
||
const handleCurrentChange = (newCurrentPage: number) => {
|
||
pagination.page = newCurrentPage
|
||
getTableData()
|
||
}
|
||
|
||
// 返回上一页
|
||
const handleBack = () => {
|
||
router.back()
|
||
}
|
||
|
||
// 对话框相关
|
||
const dialogVisible = ref(false)
|
||
const submitLoading = ref(false)
|
||
const formRef = ref<FormInstance>()
|
||
|
||
// 账号表单数据
|
||
const accountForm = reactive({
|
||
username: '',
|
||
password: '',
|
||
phone: ''
|
||
})
|
||
|
||
// 表单验证规则
|
||
const accountRules = reactive<FormRules>({
|
||
username: [
|
||
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
||
{ min: 2, max: 50, message: '长度在 2 到 50 个字符', trigger: 'blur' },
|
||
{
|
||
pattern: /^[a-zA-Z0-9_-]+$/,
|
||
message: '用户名只能包含字母、数字、下划线和横线',
|
||
trigger: 'blur'
|
||
}
|
||
],
|
||
password: [
|
||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||
{ min: 6, max: 20, message: '密码长度为 6-20 个字符', trigger: 'blur' }
|
||
],
|
||
phone: [
|
||
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
||
{ len: 11, message: '手机号必须为 11 位', trigger: 'blur' },
|
||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号格式', trigger: 'blur' }
|
||
]
|
||
})
|
||
|
||
// 显示新增账号对话框
|
||
const showAddAccountDialog = () => {
|
||
// 重置表单
|
||
accountForm.username = ''
|
||
accountForm.password = ''
|
||
accountForm.phone = ''
|
||
|
||
// 重置表单验证状态
|
||
nextTick(() => {
|
||
formRef.value?.clearValidate()
|
||
})
|
||
|
||
dialogVisible.value = true
|
||
}
|
||
|
||
// 提交表单
|
||
const handleSubmit = async () => {
|
||
if (!formRef.value) return
|
||
|
||
await formRef.value.validate(async (valid) => {
|
||
if (valid) {
|
||
submitLoading.value = true
|
||
try {
|
||
const entityId = Number(route.params.id)
|
||
const data: any = {
|
||
username: accountForm.username,
|
||
password: accountForm.password,
|
||
phone: accountForm.phone,
|
||
user_type: isShopType.value ? 3 : 4 // 3:代理账号, 4:企业账号
|
||
}
|
||
|
||
// 根据类型添加关联ID
|
||
if (isShopType.value) {
|
||
data.shop_id = entityId
|
||
} else {
|
||
data.enterprise_id = entityId
|
||
}
|
||
|
||
await AccountService.createAccount(data)
|
||
ElMessage.success('添加账号成功')
|
||
dialogVisible.value = false
|
||
|
||
// 刷新列表
|
||
getTableData()
|
||
} catch (error) {
|
||
console.error(error)
|
||
console.log('添加账号失败')
|
||
} finally {
|
||
submitLoading.value = false
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
// 加载所有角色列表
|
||
const loadAllRoles = async () => {
|
||
try {
|
||
const res = await RoleService.getRoles({ page: 1, pageSize: 100 })
|
||
if (res.code === 0) {
|
||
allRoles.value = res.data.items || []
|
||
}
|
||
} catch (error) {
|
||
console.error('获取角色列表失败:', error)
|
||
}
|
||
}
|
||
|
||
// 计算属性:过滤后的可分配角色(根据账号类型过滤)
|
||
const filteredAvailableRoles = computed(() => {
|
||
let roles = allRoles.value
|
||
|
||
// 根据账号类型过滤角色
|
||
if (currentAccountType.value === 3) {
|
||
// 代理账号:只显示客户角色
|
||
roles = roles.filter((role) => role.role_type === 2)
|
||
} else if (currentAccountType.value === 2) {
|
||
// 平台用户:只显示平台角色
|
||
roles = roles.filter((role) => role.role_type === 1)
|
||
} else if (currentAccountType.value === 4) {
|
||
// 企业账号:只显示客户角色
|
||
roles = roles.filter((role) => role.role_type === 2)
|
||
}
|
||
|
||
// 根据搜索关键词过滤
|
||
if (!leftRoleFilter.value) return roles
|
||
const keyword = leftRoleFilter.value.toLowerCase()
|
||
return roles.filter((role) => role.role_name.toLowerCase().includes(keyword))
|
||
})
|
||
|
||
// 计算属性:过滤后的已分配角色
|
||
const filteredAssignedRoles = computed(() => {
|
||
const assignedRolesList = allRoles.value.filter((role) => selectedRoles.value.includes(role.ID))
|
||
if (!rightRoleFilter.value) return assignedRolesList
|
||
const keyword = rightRoleFilter.value.toLowerCase()
|
||
return assignedRolesList.filter((role) => role.role_name.toLowerCase().includes(keyword))
|
||
})
|
||
|
||
// 显示分配角色对话框
|
||
const showRoleDialog = async (row: PlatformAccount) => {
|
||
currentAccountId.value = (row as any).id
|
||
currentAccountName.value = row.username
|
||
currentAccountType.value = row.user_type
|
||
selectedRoles.value = []
|
||
rolesToAdd.value = []
|
||
leftRoleFilter.value = ''
|
||
rightRoleFilter.value = ''
|
||
|
||
try {
|
||
// 每次打开对话框时重新加载最新的角色列表
|
||
await loadAllRoles()
|
||
|
||
// 先加载当前账号的角色,再打开对话框
|
||
const res = await AccountService.getAccountRoles((row as any).id)
|
||
if (res.code === 0) {
|
||
// 提取角色ID数组
|
||
const roles = res.data || []
|
||
// 兼容 ID 和 id 两种字段名
|
||
selectedRoles.value = roles.map((role: any) => role.ID || role.id)
|
||
// 数据加载完成后再打开对话框
|
||
roleDialogVisible.value = true
|
||
}
|
||
} catch (error) {
|
||
console.error('获取账号角色失败:', error)
|
||
}
|
||
}
|
||
|
||
// 批量添加角色
|
||
const addRoles = async () => {
|
||
if (rolesToAdd.value.length === 0) return
|
||
|
||
try {
|
||
let newRoles: number[]
|
||
|
||
// 代理账号只能分配一个角色,会覆盖之前的角色
|
||
if (currentAccountType.value === 3) {
|
||
if (rolesToAdd.value.length > 1) {
|
||
ElMessage.warning('代理账号只能分配一个角色')
|
||
return
|
||
}
|
||
// 只保留新选择的一个角色
|
||
newRoles = rolesToAdd.value
|
||
} else {
|
||
// 其他账号类型可以分配多个角色
|
||
newRoles = [...new Set([...selectedRoles.value, ...rolesToAdd.value])]
|
||
}
|
||
|
||
await AccountService.assignRolesToAccount(currentAccountId.value, newRoles)
|
||
|
||
selectedRoles.value = newRoles
|
||
rolesToAdd.value = []
|
||
|
||
ElMessage.success('角色分配成功')
|
||
// 刷新列表以更新角色显示
|
||
await getTableData()
|
||
} catch (error) {
|
||
console.error('分配角色失败:', error)
|
||
}
|
||
}
|
||
|
||
// 移除单个角色
|
||
const removeSingleRole = async (roleId: number) => {
|
||
try {
|
||
// 从已分配列表中移除该角色
|
||
const newRoles = selectedRoles.value.filter((id) => id !== roleId)
|
||
await AccountService.assignRolesToAccount(currentAccountId.value, newRoles)
|
||
|
||
selectedRoles.value = newRoles
|
||
|
||
ElMessage.success('角色移除成功')
|
||
// 刷新列表以更新角色显示
|
||
await getTableData()
|
||
} catch (error) {
|
||
console.error('移除角色失败:', error)
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
getTableData()
|
||
loadAllRoles()
|
||
})
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.account-list-page {
|
||
.detail-header {
|
||
display: flex;
|
||
gap: 16px;
|
||
align-items: center;
|
||
padding-bottom: 16px;
|
||
|
||
.detail-title {
|
||
margin: 0;
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
}
|
||
}
|
||
}
|
||
|
||
.dialog-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
width: 100%;
|
||
|
||
.dialog-title {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.account-info {
|
||
display: flex;
|
||
gap: 8px;
|
||
align-items: center;
|
||
|
||
.account-name {
|
||
font-size: 14px;
|
||
color: var(--el-text-color-regular);
|
||
}
|
||
}
|
||
}
|
||
|
||
.role-transfer-container {
|
||
display: flex;
|
||
gap: 20px;
|
||
align-items: stretch;
|
||
justify-content: space-between;
|
||
min-height: 500px;
|
||
padding: 20px 0;
|
||
|
||
.transfer-panel {
|
||
display: flex;
|
||
flex: 1;
|
||
flex-direction: column;
|
||
max-width: 380px;
|
||
overflow: hidden;
|
||
border: 1px solid var(--el-border-color);
|
||
border-radius: 4px;
|
||
|
||
.panel-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 12px 16px;
|
||
background: var(--el-fill-color-light);
|
||
border-bottom: 1px solid var(--el-border-color);
|
||
|
||
.panel-title {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: var(--el-text-color-primary);
|
||
}
|
||
}
|
||
|
||
.panel-body {
|
||
flex: 1;
|
||
padding: 12px;
|
||
overflow-y: auto;
|
||
|
||
.role-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
|
||
.role-item {
|
||
padding: 10px 12px;
|
||
border: 1px solid var(--el-border-color-lighter);
|
||
border-radius: 4px;
|
||
transition: all 0.2s;
|
||
|
||
&:hover {
|
||
background: var(--el-fill-color-light);
|
||
border-color: var(--el-border-color);
|
||
}
|
||
|
||
:deep(.el-checkbox) {
|
||
width: 100%;
|
||
|
||
.el-checkbox__label {
|
||
display: flex;
|
||
width: 100%;
|
||
|
||
.role-info {
|
||
display: flex;
|
||
flex: 1;
|
||
gap: 8px;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
width: 100%;
|
||
|
||
> span:first-child {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.assigned-role-item {
|
||
display: flex;
|
||
gap: 12px;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
|
||
.role-info {
|
||
display: flex;
|
||
flex: 1;
|
||
gap: 8px;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
|
||
> span:first-child {
|
||
flex: 1;
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
}
|
||
|
||
.el-button {
|
||
flex-shrink: 0;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.transfer-buttons {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 0 10px;
|
||
|
||
.el-button {
|
||
width: 100px;
|
||
}
|
||
}
|
||
}
|
||
</style>
|