1013 lines
29 KiB
Vue
1013 lines
29 KiB
Vue
<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"
|
||
:row-class-name="getRowClassName"
|
||
@selection-change="handleSelectionChange"
|
||
@size-change="handleSizeChange"
|
||
@current-change="handleCurrentChange"
|
||
@row-contextmenu="handleRowContextMenu"
|
||
@cell-mouse-enter="handleCellMouseEnter"
|
||
@cell-mouse-leave="handleCellMouseLeave"
|
||
>
|
||
<template #default>
|
||
<ElTableColumn v-for="col in columns" :key="col.prop || col.type" v-bind="col" />
|
||
</template>
|
||
</ArtTable>
|
||
|
||
<!-- 鼠标悬浮提示 -->
|
||
<TableContextMenuHint :visible="showContextMenuHint" :position="hintPosition" />
|
||
|
||
<!-- 右键菜单 -->
|
||
<ArtMenuRight
|
||
ref="contextMenuRef"
|
||
:menu-items="contextMenuItems"
|
||
:menu-width="120"
|
||
@select="handleContextMenuSelect"
|
||
/>
|
||
|
||
<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%"
|
||
>
|
||
<ElOption label="超级管理员" :value="1" />
|
||
<ElOption label="平台用户" :value="2" />
|
||
</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="900px">
|
||
<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"
|
||
/>
|
||
</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 } from 'vue'
|
||
import { useRoute } from 'vue-router'
|
||
import { FormInstance, ElSwitch, ElCheckbox, ElCheckboxGroup, ElTag } 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 { useTableContextMenu } from '@/composables/useTableContextMenu'
|
||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||
import ArtMenuRight from '@/components/core/others/ArtMenuRight.vue'
|
||
import TableContextMenuHint from '@/components/core/others/TableContextMenuHint.vue'
|
||
import type { MenuItemType } from '@/components/core/others/ArtMenuRight.vue'
|
||
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 route = useRoute()
|
||
|
||
// 使用表格右键菜单功能
|
||
const {
|
||
showContextMenuHint,
|
||
hintPosition,
|
||
getRowClassName,
|
||
handleCellMouseEnter,
|
||
handleCellMouseLeave
|
||
} = useTableContextMenu()
|
||
|
||
const dialogType = ref('add')
|
||
const dialogVisible = ref(false)
|
||
const roleDialogVisible = ref(false)
|
||
const loading = ref(false)
|
||
const roleSubmitLoading = ref(false)
|
||
const currentAccountId = ref<number>(0)
|
||
const currentAccountName = ref<string>('')
|
||
const currentAccountType = ref<number>(0)
|
||
const contextMenuRef = ref<InstanceType<typeof ArtMenuRight>>()
|
||
const currentRow = ref<any | null>(null)
|
||
const selectedRoles = ref<number[]>([])
|
||
const allRoles = ref<PlatformRole[]>([])
|
||
const rolesToAdd = ref<number[]>([])
|
||
const leftRoleFilter = ref('')
|
||
const rightRoleFilter = ref('')
|
||
|
||
// 定义表单搜索初始值
|
||
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: '请输入店铺名称搜索'
|
||
}
|
||
},
|
||
{
|
||
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: '请输入企业名称搜索'
|
||
}
|
||
},
|
||
{
|
||
label: '状态',
|
||
prop: 'status',
|
||
type: 'select',
|
||
options: STATUS_SELECT_OPTIONS,
|
||
config: {
|
||
clearable: true,
|
||
placeholder: '请选择状态'
|
||
}
|
||
}
|
||
])
|
||
|
||
// 列配置
|
||
const columnOptions = [
|
||
{ label: '账号名称', prop: 'username' },
|
||
{ label: '手机号', prop: 'phone' },
|
||
{ label: '账号类型', prop: 'user_type' },
|
||
{ label: '店铺名称', prop: 'shop_name' },
|
||
{ label: '企业名称', prop: 'enterprise_name' },
|
||
{ label: '状态', prop: 'status' },
|
||
{ label: '创建时间', prop: 'created_at' }
|
||
]
|
||
|
||
// 显示对话框
|
||
const showDialog = (type: string, row?: any) => {
|
||
dialogVisible.value = true
|
||
dialogType.value = type
|
||
|
||
// 重置表单验证状态
|
||
if (formRef.value) {
|
||
formRef.value.resetFields()
|
||
}
|
||
|
||
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 = ''
|
||
} else {
|
||
formData.id = ''
|
||
formData.username = ''
|
||
formData.phone = ''
|
||
formData.user_type = 2
|
||
formData.shop_id = null
|
||
formData.enterprise_id = null
|
||
formData.password = ''
|
||
}
|
||
}
|
||
|
||
// 删除账号
|
||
const deleteAccount = (row: any) => {
|
||
ElMessageBox.confirm(`确定要删除账号 ${row.username} 吗?`, '删除账号', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'error'
|
||
})
|
||
.then(async () => {
|
||
try {
|
||
await AccountService.deleteAccount(row.id)
|
||
ElMessage.success('删除成功')
|
||
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 || '-'
|
||
}
|
||
},
|
||
{
|
||
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,
|
||
disabled: !hasAuth('account:modify_status'),
|
||
'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 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
|
||
})
|
||
|
||
onMounted(() => {
|
||
// 从 URL 查询参数中读取 shop_id
|
||
const shopIdParam = route.query.shop_id
|
||
if (shopIdParam) {
|
||
formFilters.shop_id = Number(shopIdParam)
|
||
}
|
||
|
||
getAccountList()
|
||
loadAllRoles()
|
||
loadShopList()
|
||
loadEnterpriseList()
|
||
})
|
||
|
||
// 加载所有角色列表
|
||
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 === 1) {
|
||
// 超级管理员:不能分配任何角色
|
||
return []
|
||
} else if (currentAccountType.value === 3) {
|
||
// 代理账号:只显示客户角色
|
||
roles = roles.filter((role) => role.role_type === 2)
|
||
} else if (currentAccountType.value === 4) {
|
||
// 企业账号:只显示客户角色
|
||
roles = roles.filter((role) => role.role_type === 2)
|
||
} else if (currentAccountType.value === 2) {
|
||
// 平台用户:只显示平台角色
|
||
roles = roles.filter((role) => role.role_type === 1)
|
||
}
|
||
|
||
// 根据搜索关键词过滤
|
||
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: any) => {
|
||
currentAccountId.value = row.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.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 {
|
||
// 所有账号只能分配一个角色
|
||
if (rolesToAdd.value.length > 1) {
|
||
ElMessage.warning('只能分配一个角色')
|
||
return
|
||
}
|
||
|
||
// 新角色会替换之前的角色
|
||
const newRoles = rolesToAdd.value
|
||
|
||
await AccountService.assignRolesToAccount(currentAccountId.value, newRoles)
|
||
|
||
selectedRoles.value = newRoles
|
||
rolesToAdd.value = []
|
||
|
||
ElMessage.success('角色分配成功')
|
||
// 刷新列表以更新角色显示
|
||
await getAccountList()
|
||
} 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 getAccountList()
|
||
} catch (error) {
|
||
console.error('移除角色失败:', error)
|
||
ElMessage.error('角色移除失败')
|
||
}
|
||
}
|
||
|
||
// 获取账号列表
|
||
const getAccountList = async () => {
|
||
loading.value = true
|
||
try {
|
||
const params = {
|
||
page: pagination.currentPage,
|
||
pageSize: 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' }
|
||
],
|
||
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
|
||
}
|
||
|
||
await AccountService.createAccount(data)
|
||
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
|
||
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)
|
||
}
|
||
|
||
// 右键菜单项配置
|
||
const contextMenuItems = computed((): MenuItemType[] => {
|
||
const items: MenuItemType[] = []
|
||
|
||
if (hasAuth('account:patch_role')) {
|
||
items.push({ key: 'assignRole', label: '分配角色' })
|
||
}
|
||
|
||
if (hasAuth('account:edit')) {
|
||
items.push({ key: 'edit', label: '编辑' })
|
||
}
|
||
|
||
if (hasAuth('account:delete')) {
|
||
items.push({ key: 'delete', label: '删除' })
|
||
}
|
||
|
||
return items
|
||
})
|
||
|
||
// 处理表格行右键菜单
|
||
const handleRowContextMenu = (row: any, column: any, event: MouseEvent) => {
|
||
event.preventDefault()
|
||
event.stopPropagation()
|
||
currentRow.value = row
|
||
contextMenuRef.value?.show(event)
|
||
}
|
||
|
||
// 处理右键菜单选择
|
||
const handleContextMenuSelect = (item: MenuItemType) => {
|
||
if (!currentRow.value) return
|
||
|
||
switch (item.key) {
|
||
case 'assignRole':
|
||
showRoleDialog(currentRow.value)
|
||
break
|
||
case 'edit':
|
||
showDialog('edit', currentRow.value)
|
||
break
|
||
case 'delete':
|
||
deleteAccount(currentRow.value)
|
||
break
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.account-page {
|
||
// 账号管理页面样式
|
||
}
|
||
|
||
:deep(.el-table__row.table-row-with-context-menu) {
|
||
cursor: pointer;
|
||
}
|
||
|
||
.dialog-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
width: 100%;
|
||
|
||
.dialog-title {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.account-info {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
|
||
.account-name {
|
||
font-size: 14px;
|
||
color: var(--el-text-color-regular);
|
||
}
|
||
}
|
||
}
|
||
|
||
.role-transfer-container {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: stretch;
|
||
gap: 20px;
|
||
padding: 20px 0;
|
||
min-height: 500px;
|
||
|
||
.transfer-panel {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
border: 1px solid var(--el-border-color);
|
||
border-radius: 4px;
|
||
overflow: hidden;
|
||
max-width: 380px;
|
||
|
||
.panel-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
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;
|
||
overflow-y: auto;
|
||
padding: 12px;
|
||
|
||
.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);
|
||
}
|
||
|
||
.role-info {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
flex: 1;
|
||
}
|
||
|
||
:deep(.el-checkbox) {
|
||
width: 100%;
|
||
|
||
.el-checkbox__label {
|
||
width: 100%;
|
||
}
|
||
}
|
||
}
|
||
|
||
.assigned-role-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
gap: 12px;
|
||
|
||
.role-info {
|
||
flex: 1;
|
||
}
|
||
|
||
.el-button {
|
||
flex-shrink: 0;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.transfer-buttons {
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 0 10px;
|
||
|
||
.el-button {
|
||
width: 100px;
|
||
}
|
||
}
|
||
}
|
||
</style>
|