Files
one-pipe-system/src/views/account-management/account/index.vue
sexygoat 38cf7d12e9
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 4m42s
fix: 真流量虚流量权限
2026-05-14 10:52:11 +08:00

1086 lines
32 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<ArtTableFullScreen>
<div class="account-page" id="table-full-screen">
<!-- 搜索栏 -->
<ArtSearchBar
v-model:filter="formFilters"
:items="formItems"
@reset="handleReset"
@search="handleSearch"
></ArtSearchBar>
<ElCard shadow="never" class="art-table-card">
<!-- 表格头部 -->
<ArtTableHeader
:columnList="columnOptions"
v-model:columns="columnChecks"
@refresh="handleRefresh"
>
<template #left>
<ElButton @click="showDialog('add')" v-permission="'account:add'">新增账号</ElButton>
</template>
</ArtTableHeader>
<!-- 表格 -->
<ArtTable
ref="tableRef"
row-key="id"
:loading="loading"
:data="tableData"
:currentPage="pagination.currentPage"
:pageSize="pagination.pageSize"
:total="pagination.total"
:marginTop="10"
:actions="getActions"
:actionsWidth="160"
@selection-change="handleSelectionChange"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
<template #default>
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
</template>
</ArtTable>
<ElDialog
v-model="dialogVisible"
:title="dialogType === 'add' ? '添加账号' : '编辑账号'"
width="30%"
>
<ElForm ref="formRef" :model="formData" :rules="rules" label-width="80px">
<ElFormItem label="账号名称" prop="username">
<ElInput v-model="formData.username" placeholder="请输入账号名称" />
</ElFormItem>
<ElFormItem v-if="dialogType === 'add'" label="密码" prop="password">
<ElInput
v-model="formData.password"
type="password"
placeholder="请输入密码"
show-password
/>
</ElFormItem>
<ElFormItem label="手机号" prop="phone">
<ElInput v-model="formData.phone" placeholder="请输入手机号" maxlength="11" />
</ElFormItem>
<ElFormItem v-if="dialogType === 'add'" label="账号类型" prop="user_type">
<ElSelect
v-model="formData.user_type"
placeholder="请选择账号类型"
style="width: 100%"
@change="handleUserTypeChange"
>
<ElOption label="超级管理员" :value="1" />
<ElOption label="平台用户" :value="2" />
</ElSelect>
</ElFormItem>
<ElFormItem v-if="dialogType === 'add' && formData.user_type === 2" label="分配角色">
<ElSelect
v-model="formData.role_id"
placeholder="请选择角色(可选)"
style="width: 100%"
clearable
>
<ElOption
v-for="role in availableRolesForCreate"
:key="role.ID"
:label="role.role_name"
:value="role.ID"
>
<div style="display: flex; justify-content: space-between; align-items: center">
<span>{{ role.role_name }}</span>
<ElTag :type="role.role_type === 1 ? 'primary' : 'success'" size="small">
{{ role.role_type === 1 ? '平台角色' : '客户角色' }}
</ElTag>
</div>
</ElOption>
</ElSelect>
</ElFormItem>
</ElForm>
<template #footer>
<div class="dialog-footer">
<ElButton @click="dialogVisible = false">取消</ElButton>
<ElButton type="primary" @click="handleSubmit">提交</ElButton>
</div>
</template>
</ElDialog>
<!-- 分配角色对话框 -->
<ElDialog v-model="roleDialogVisible" width="55%">
<template #header>
<div class="dialog-header">
<span class="dialog-title">分配角色</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"
@input="handleRoleSearch"
@clear="handleRoleSearch('')"
/>
</div>
<div class="panel-body">
<ElEmpty v-if="filteredAvailableRoles.length === 0" description="暂无角色" />
<template v-else-if="isPlatformUser">
<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>
</template>
<template v-else>
<ElRadioGroup v-model="roleToAdd" class="role-list">
<div v-for="role in filteredAvailableRoles" :key="role.ID" class="role-item">
<ElRadio :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>
</ElRadio>
</div>
</ElRadioGroup>
</template>
</div>
</div>
<!-- 中间操作按钮 -->
<div class="transfer-buttons">
<ElButton
type="primary"
:icon="'ArrowRight'"
@click="addRoles"
:disabled="isPlatformUser ? rolesToAdd.length === 0 : !roleToAdd"
>
分配
</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, onActivated } from 'vue'
import { useRoute } from 'vue-router'
import {
FormInstance,
ElSwitch,
ElCheckbox,
ElCheckboxGroup,
ElTag,
ElEmpty,
ElRadioGroup,
ElRadio
} from 'element-plus'
import { ElMessageBox, ElMessage } from 'element-plus'
import type { FormRules } from 'element-plus'
import { useCheckedColumns } from '@/composables/useCheckedColumns'
import { useAuth } from '@/composables/useAuth'
import { AccountService } from '@/api/modules/account'
import { RoleService } from '@/api/modules/role'
import { ShopService, EnterpriseService } from '@/api/modules'
import type { SearchFormItem } from '@/types'
import type { PlatformRole } from '@/types/api'
import { formatDateTime } from '@/utils/business/format'
import { CommonStatus, getStatusText, STATUS_SELECT_OPTIONS } from '@/config/constants'
defineOptions({ name: 'Account' }) // 定义组件名称,用于 KeepAlive 缓存控制
const { hasAuth } = useAuth()
const canModifyAccountStatus = hasAuth('account:modify_status')
const route = useRoute()
const dialogType = ref('add')
const dialogVisible = ref(false)
const roleDialogVisible = ref(false)
const loading = 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 roleToAdd = ref<number | undefined>(undefined) // 单选时使用
const leftRoleFilter = ref('')
const rightRoleFilter = ref('')
// 是否为平台用户(平台用户可多选,其他单选)
const isPlatformUser = computed(() => currentAccountType.value === 2)
// 定义表单搜索初始值
const initialSearchState = {
name: '',
phone: '',
user_type: undefined as number | undefined,
shop_id: undefined as number | undefined,
enterprise_id: undefined as number | undefined,
status: undefined as number | undefined
}
// 响应式表单数据
const formFilters = reactive({ ...initialSearchState })
// 店铺和企业列表
const shopList = ref<any[]>([])
const enterpriseList = ref<any[]>([])
const pagination = reactive({
currentPage: 1,
pageSize: 20,
total: 0
})
// 表格数据
const tableData = ref<any[]>([])
// 表格实例引用
const tableRef = ref()
// 选中的行数据
const selectedRows = ref<any[]>([])
// 重置表单
const handleReset = () => {
Object.assign(formFilters, { ...initialSearchState })
pagination.currentPage = 1 // 重置到第一页
getAccountList()
}
// 搜索处理
const handleSearch = () => {
console.log('搜索参数:', formFilters)
pagination.currentPage = 1 // 搜索时重置到第一页
getAccountList()
}
// 表单配置项
const formItems = computed<SearchFormItem[]>(() => [
{
label: '账号名称',
prop: 'name',
type: 'input',
config: {
clearable: true,
placeholder: '请输入账号名称'
}
},
{
label: '手机号',
prop: 'phone',
type: 'input',
config: {
clearable: true,
placeholder: '请输入手机号'
}
},
{
label: '账号类型',
prop: 'user_type',
type: 'select',
options: [
{ label: '超级管理员', value: 1 },
{ label: '平台用户', value: 2 },
{ label: '代理账号', value: 3 },
{ label: '企业账号', value: 4 }
],
config: {
clearable: true,
placeholder: '请选择账号类型'
}
},
{
label: '关联店铺',
prop: 'shop_id',
type: 'select',
options: shopList.value.map((shop) => ({
label: shop.shop_name,
value: shop.id
})),
config: {
clearable: true,
filterable: true,
remote: true,
remoteMethod: handleShopSearch,
placeholder: '请输入店铺名称搜索',
reserveKeyword: true
}
},
{
label: '状态',
prop: 'status',
type: 'select',
options: STATUS_SELECT_OPTIONS,
config: {
clearable: true,
placeholder: '请选择状态'
}
},
{
label: '关联企业',
prop: 'enterprise_id',
type: 'select',
options: enterpriseList.value.map((enterprise) => ({
label: enterprise.enterprise_name,
value: enterprise.id
})),
config: {
clearable: true,
filterable: true,
remote: true,
remoteMethod: handleEnterpriseSearch,
placeholder: '请输入企业名称搜索',
reserveKeyword: true
}
}
])
// 列配置
const columnOptions = [
{ label: '账号名称', prop: 'username' },
{ label: '手机号', prop: 'phone' },
{ label: '账号类型', prop: 'user_type' },
{ label: '店铺名称', prop: 'shop_name' },
{ label: '企业名称', prop: 'enterprise_name' },
...(canModifyAccountStatus ? [{ label: '状态', prop: 'status' }] : []),
{ label: '创建时间', prop: 'created_at' }
]
// 显示对话框
const showDialog = async (type: string, row?: any) => {
dialogVisible.value = true
dialogType.value = type
// 重置表单验证状态
if (formRef.value) {
formRef.value.resetFields()
}
if (type === 'add') {
await loadAllRoles()
}
if (type === 'edit' && row) {
formData.id = row.id
formData.username = row.username
formData.phone = row.phone
formData.user_type = row.user_type
formData.shop_id = row.shop_id || null
formData.enterprise_id = row.enterprise_id || null
formData.password = ''
formData.role_id = undefined
} else {
formData.id = ''
formData.username = ''
formData.phone = ''
formData.user_type = 2
formData.shop_id = null
formData.enterprise_id = null
formData.password = ''
formData.role_id = undefined
}
}
// 账号类型变更处理
const handleUserTypeChange = () => {
// 清空角色选择
formData.role_id = undefined
}
// 删除账号
const deleteAccount = (row: any) => {
ElMessageBox.confirm(`确定要删除账号 ${row.username} 吗?`, '删除账号', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'error'
})
.then(async () => {
try {
await AccountService.deleteAccount(row.id)
ElMessage.success('删除成功')
await getAccountList()
} catch (error) {
console.error(error)
}
})
.catch(() => {
// 账号取消删除
})
}
// 动态列配置
const { columnChecks, columns } = useCheckedColumns(() => [
{
prop: 'username',
label: '账号名称',
minWidth: 120
},
{
prop: 'phone',
label: '手机号',
width: 130
},
{
prop: 'user_type',
label: '账号类型',
width: 120,
formatter: (row: any) => {
const typeMap: Record<number, string> = {
1: '超级管理员',
2: '平台用户',
3: '代理账号',
4: '企业账号'
}
return typeMap[row.user_type] || '-'
}
},
{
prop: 'shop_name',
label: '店铺名称',
minWidth: 150,
formatter: (row: any) => {
return row.shop_name || '-'
}
},
{
prop: 'enterprise_name',
label: '企业名称',
minWidth: 150,
formatter: (row: any) => {
return row.enterprise_name || '-'
}
},
...(canModifyAccountStatus
? [
{
prop: 'status',
label: '状态',
width: 100,
formatter: (row: any) => {
return h(ElSwitch, {
modelValue: row.status,
activeValue: CommonStatus.ENABLED,
inactiveValue: CommonStatus.DISABLED,
activeText: getStatusText(CommonStatus.ENABLED),
inactiveText: getStatusText(CommonStatus.DISABLED),
inlinePrompt: true,
'onUpdate:modelValue': (val: string | number | boolean) =>
handleStatusChange(row, val as number)
})
}
}
]
: []),
{
prop: 'created_at',
label: '创建时间',
width: 180,
formatter: (row: any) => formatDateTime(row.created_at)
}
])
// 操作列配置
const getActions = (row: any) => {
const actions: any[] = []
if (hasAuth('account:patch_role')) {
actions.push({
label: '分配角色',
handler: () => showRoleDialog(row),
type: 'primary'
})
}
if (hasAuth('account:edit')) {
actions.push({
label: '编辑',
handler: () => showDialog('edit', row),
type: 'primary'
})
}
if (hasAuth('account:delete')) {
actions.push({
label: '删除',
handler: () => deleteAccount(row),
type: 'danger'
})
}
return actions
}
// 表单实例
const formRef = ref<FormInstance>()
// 表单数据
const formData = reactive({
id: '',
username: '',
password: '',
phone: '',
user_type: 2,
shop_id: null as number | null,
enterprise_id: null as number | null,
role_id: undefined as number | undefined
})
onMounted(() => {
// 从 URL 查询参数中读取 shop_id
const shopIdParam = route.query.shop_id
if (shopIdParam) {
formFilters.shop_id = Number(shopIdParam)
}
getAccountList()
})
onActivated(() => {
getAccountList()
})
// 加载所有角色列表
const loadAllRoles = async (keyword: string = '', roleType?: number) => {
try {
const params: any = {
page: 1,
page_size: keyword ? 50 : 20, // 搜索时加载更多默认20条
role_type: roleType !== undefined ? roleType : 1 // 默认为平台角色
}
if (keyword) {
params.role_name = keyword
}
const res = await RoleService.getRoles(params)
if (res.code === 0) {
allRoles.value = res.data.items || []
}
} catch (error) {
console.error('获取角色列表失败:', error)
}
}
// 计算属性:新建账号时可选的角色
const availableRolesForCreate = computed(() => {
return allRoles.value
})
// 计算属性:过滤后的可分配角色
const filteredAvailableRoles = computed(() => {
return allRoles.value
})
// 搜索防抖定时器
let roleSearchTimer: any = null
// 远程搜索角色
const handleRoleSearch = (keyword: string) => {
// 清除之前的定时器
if (roleSearchTimer) {
clearTimeout(roleSearchTimer)
}
// 设置新的定时器500ms 后执行搜索
roleSearchTimer = setTimeout(async () => {
// 根据当前账号类型决定加载哪种角色
const roleType = currentAccountType.value === 2 ? 1 : 2 // 平台用户(2)加载平台角色(1),其他加载客户角色(2)
await loadAllRoles(keyword, roleType)
}, 500)
}
// 计算属性:过滤后的已分配角色
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: any) => {
if (row.user_type === 1) {
ElMessage.warning('超级管理员无法分配角色')
return
}
currentAccountId.value = row.id
currentAccountName.value = row.username
currentAccountType.value = row.user_type
selectedRoles.value = []
rolesToAdd.value = []
roleToAdd.value = undefined
leftRoleFilter.value = ''
rightRoleFilter.value = ''
try {
// 根据账号类型加载对应的角色列表
// 平台用户(user_type=2) -> 平台角色(role_type=1)
// 企业账号/代理账号(user_type=3,4) -> 客户角色(role_type=2)
const roleType = row.user_type === 2 ? 1 : 2
await loadAllRoles('', roleType)
// 先加载当前账号的角色,再打开对话框
const res = await AccountService.getAccountRoles(row.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 () => {
try {
let newRoles: number[]
// 平台用户可以分配多个角色,其他用户只能分配一个角色
if (isPlatformUser.value) {
if (rolesToAdd.value.length === 0) return
// 平台用户可以分配多个角色
newRoles = [...new Set([...selectedRoles.value, ...rolesToAdd.value])]
} else {
// 其他用户只能分配一个角色(单选)
if (!roleToAdd.value) return
newRoles = [...selectedRoles.value, roleToAdd.value]
}
await AccountService.assignRolesToAccount(currentAccountId.value, newRoles)
selectedRoles.value = newRoles
rolesToAdd.value = []
roleToAdd.value = undefined
ElMessage.success('角色分配成功')
// 刷新列表以更新角色显示
await getAccountList()
} catch (error) {
console.error('分配角色失败:', error)
}
}
// 移除单个角色
const removeSingleRole = async (roleId: number) => {
try {
await AccountService.removeRoleFromAccount(currentAccountId.value, roleId)
// 从本地已分配列表中移除该角色
selectedRoles.value = selectedRoles.value.filter((id) => id !== roleId)
ElMessage.success('角色移除成功')
// 刷新列表以更新角色显示
await getAccountList()
} catch (error) {
console.error('移除角色失败:', error)
}
}
// 获取账号列表
const getAccountList = async () => {
loading.value = true
try {
const params = {
page: pagination.currentPage,
page_size: pagination.pageSize,
username: formFilters.name || undefined,
phone: formFilters.phone || undefined,
user_type: formFilters.user_type,
shop_id: formFilters.shop_id,
enterprise_id: formFilters.enterprise_id,
status: formFilters.status
}
const res = await AccountService.getAccounts(params)
if (res.code === 0) {
tableData.value = res.data.items || []
pagination.total = res.data.total || 0
}
} catch (error) {
console.error('获取账号列表失败:', error)
} finally {
loading.value = false
}
}
const handleRefresh = () => {
getAccountList()
}
// 处理表格行选择变化
const handleSelectionChange = (selection: any[]) => {
selectedRows.value = selection
}
// 表单验证规则
const rules = 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: 8, max: 32, message: '密码长度为 8-32 个字符', trigger: 'blur' }
],
phone: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ len: 11, message: '手机号必须为 11 位', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号格式', trigger: 'blur' }
],
user_type: [{ required: true, message: '请选择账号类型', trigger: 'change' }],
shop_id: [
{
validator: (_rule: any, value: any, callback: any) => {
if (formData.user_type === 3 && !value) {
callback(new Error('代理账号必须关联店铺'))
} else {
callback()
}
},
trigger: 'change'
}
],
enterprise_id: [
{
validator: (_rule: any, value: any, callback: any) => {
if (formData.user_type === 4 && !value) {
callback(new Error('企业账号必须关联企业'))
} else {
callback()
}
},
trigger: 'change'
}
]
})
// 提交表单
const handleSubmit = async () => {
if (!formRef.value) return
await formRef.value.validate(async (valid) => {
if (valid) {
try {
if (dialogType.value === 'add') {
// 创建账号
const data: any = {
username: formData.username,
phone: formData.phone,
password: formData.password,
user_type: formData.user_type
}
// 根据账号类型添加相应的字段
if (formData.user_type === 3) {
data.shop_id = formData.shop_id
} else if (formData.user_type === 4) {
data.enterprise_id = formData.enterprise_id
}
const createRes = await AccountService.createAccount(data)
// 创建成功后分配角色(如果选择了)
if (createRes.code === 0) {
const accountId = createRes.data?.ID || createRes.data?.id
if (formData.role_id && accountId) {
try {
await AccountService.assignRolesToAccount(accountId, [formData.role_id])
ElMessage.success('账号创建并分配角色成功')
} catch (error) {
console.error('分配角色失败:', error)
ElMessage.warning('账号创建成功,但角色分配失败')
}
} else {
ElMessage.success('添加成功')
}
}
} else {
// 编辑账号 - 只提交username和phone
const data: any = {
username: formData.username,
phone: formData.phone
}
await AccountService.updateAccount(Number(formData.id), data)
ElMessage.success('更新成功')
}
dialogVisible.value = false
await getAccountList()
} catch (error) {
console.error(error)
}
}
})
}
// 处理表格分页变化
const handleSizeChange = (newPageSize: number) => {
pagination.pageSize = newPageSize
getAccountList()
}
const handleCurrentChange = (newCurrentPage: number) => {
pagination.currentPage = newCurrentPage
getAccountList()
}
// 状态切换
const handleStatusChange = async (row: any, newStatus: number) => {
const oldStatus = row.status
// 先更新UI
row.status = newStatus
try {
await AccountService.updateAccountStatus(row.id, newStatus as 0 | 1)
ElMessage.success('状态切换成功')
} catch (error) {
// 切换失败,恢复原状态
row.status = oldStatus
console.error(error)
}
}
// 加载店铺列表
const loadShopList = async (keyword: string = '') => {
try {
const res = await ShopService.getShops({
page: 1,
page_size: 20, // 默认加载20条
status: 1, // 只加载启用的店铺
shop_name: keyword || undefined // 根据店铺名称搜索
})
if (res.code === 0) {
shopList.value = res.data.items || []
}
} catch (error) {
console.error('获取店铺列表失败:', error)
}
}
// 店铺搜索处理
const handleShopSearch = (query: string) => {
loadShopList(query)
}
// 加载企业列表
const loadEnterpriseList = async (keyword: string = '') => {
try {
const res = await EnterpriseService.getEnterprises({
page: 1,
page_size: 20, // 默认加载20条
status: 1, // 只加载启用的企业
enterprise_name: keyword || undefined // 根据企业名称搜索
})
if (res.code === 0) {
enterpriseList.value = res.data.items || []
}
} catch (error) {
console.error('获取企业列表失败:', error)
}
}
// 企业搜索处理
const handleEnterpriseSearch = (query: string) => {
loadEnterpriseList(query)
}
// 右键菜单项配置
</script>
<style lang="scss" scoped>
.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: 440px;
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;
width: 100%;
.role-item {
display: block;
width: 100%;
padding: 10px 12px;
border: 1px solid var(--el-border-color-lighter);
border-radius: 4px;
transition: all 0.2s;
box-sizing: border-box;
&:hover {
background: var(--el-fill-color-light);
border-color: var(--el-border-color);
}
.role-info {
display: flex;
flex: 1;
justify-content: space-between;
align-items: center;
}
:deep(.el-checkbox),
:deep(.el-radio) {
display: flex;
width: 100%;
.el-checkbox__label,
.el-radio__label {
display: flex;
width: 100%;
}
}
}
.assigned-role-item {
display: flex;
gap: 12px;
align-items: center;
justify-content: space-between;
.role-info {
flex: 1;
}
.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>