完整的管理系统,包含账户管理、卡片管理、套餐管理、财务管理等功能模块。 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
523 lines
15 KiB
Vue
523 lines
15 KiB
Vue
<template>
|
||
<ArtTableFullScreen>
|
||
<div class="account-page" id="table-full-screen">
|
||
<!-- 搜索栏 -->
|
||
<ArtSearchBar
|
||
v-model:filter="formFilters"
|
||
:items="formItems"
|
||
:show-expand="false"
|
||
@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')">新增账号</ElButton>
|
||
</template>
|
||
</ArtTableHeader>
|
||
|
||
<!-- 表格 -->
|
||
<ArtTable
|
||
ref="tableRef"
|
||
row-key="ID"
|
||
:loading="loading"
|
||
:data="tableData"
|
||
:currentPage="pagination.currentPage"
|
||
:pageSize="pagination.pageSize"
|
||
:total="pagination.total"
|
||
:marginTop="10"
|
||
@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="500px"
|
||
>
|
||
<ElForm ref="formRef" :model="formData" :rules="rules" label-width="100px">
|
||
<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 label="账号类型" prop="user_type">
|
||
<ElSelect
|
||
v-model="formData.user_type"
|
||
placeholder="请选择账号类型"
|
||
style="width: 100%"
|
||
>
|
||
<ElOption label="超级管理员" :value="1" />
|
||
<ElOption label="平台用户" :value="2" />
|
||
<ElOption label="代理账号" :value="3" />
|
||
<ElOption label="企业账号" :value="4" />
|
||
</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" title="分配角色" width="500px">
|
||
<ElRadioGroup v-model="selectedRole" class="role-radio-group">
|
||
<div v-for="role in allRoles" :key="role.ID" class="role-radio-item">
|
||
<ElRadio :label="role.ID">
|
||
{{ role.role_name }}
|
||
<ElTag
|
||
:type="role.role_type === 1 ? 'primary' : 'success'"
|
||
size="small"
|
||
style="margin-left: 8px"
|
||
>
|
||
{{ role.role_type === 1 ? '平台角色' : '客户角色' }}
|
||
</ElTag>
|
||
</ElRadio>
|
||
</div>
|
||
</ElRadioGroup>
|
||
<template #footer>
|
||
<div class="dialog-footer">
|
||
<ElButton @click="roleDialogVisible = false">取消</ElButton>
|
||
<ElButton type="primary" @click="handleAssignRoles" :loading="roleSubmitLoading"
|
||
>提交</ElButton
|
||
>
|
||
</div>
|
||
</template>
|
||
</ElDialog>
|
||
</ElCard>
|
||
</div>
|
||
</ArtTableFullScreen>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { h } from 'vue'
|
||
import { FormInstance, ElSwitch } from 'element-plus'
|
||
import { ElMessageBox, ElMessage } from 'element-plus'
|
||
import type { FormRules } from 'element-plus'
|
||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||
import { AccountService } from '@/api/modules/account'
|
||
import { RoleService } from '@/api/modules/role'
|
||
import type { SearchFormItem } from '@/types'
|
||
import type { PlatformRole } from '@/types/api'
|
||
import { formatDateTime } from '@/utils/business/format'
|
||
import { CommonStatus, getStatusText } from '@/config/constants'
|
||
|
||
defineOptions({ name: 'Account' }) // 定义组件名称,用于 KeepAlive 缓存控制
|
||
|
||
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 selectedRole = ref<number | null>(null)
|
||
const allRoles = ref<PlatformRole[]>([])
|
||
|
||
// 定义表单搜索初始值
|
||
const initialSearchState = {
|
||
name: '',
|
||
phone: ''
|
||
}
|
||
|
||
// 响应式表单数据
|
||
const formFilters = reactive({ ...initialSearchState })
|
||
|
||
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: SearchFormItem[] = [
|
||
{
|
||
label: '账号名称',
|
||
prop: 'name',
|
||
type: 'input',
|
||
config: {
|
||
clearable: true,
|
||
placeholder: '请输入账号名称'
|
||
}
|
||
},
|
||
{
|
||
label: '手机号',
|
||
prop: 'phone',
|
||
type: 'input',
|
||
config: {
|
||
clearable: true,
|
||
placeholder: '请输入手机号'
|
||
}
|
||
}
|
||
]
|
||
|
||
// 列配置
|
||
const columnOptions = [
|
||
{ label: 'ID', prop: 'ID' },
|
||
{ label: '账号名称', prop: 'username' },
|
||
{ label: '手机号', prop: 'phone' },
|
||
{ label: '账号类型', prop: 'user_type_name' },
|
||
{ label: '状态', prop: 'status' },
|
||
{ label: '创建时间', prop: 'CreatedAt' },
|
||
{ label: '操作', prop: 'operation' }
|
||
]
|
||
|
||
// 显示对话框
|
||
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.password = ''
|
||
} else {
|
||
formData.id = ''
|
||
formData.username = ''
|
||
formData.phone = ''
|
||
formData.user_type = 2
|
||
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: 'ID',
|
||
label: 'ID',
|
||
width: 80
|
||
},
|
||
{
|
||
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: '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: number) => handleStatusChange(row, val)
|
||
})
|
||
}
|
||
},
|
||
{
|
||
prop: 'CreatedAt',
|
||
label: '创建时间',
|
||
width: 180,
|
||
formatter: (row: any) => formatDateTime(row.CreatedAt)
|
||
},
|
||
{
|
||
prop: 'operation',
|
||
label: '操作',
|
||
width: 180,
|
||
fixed: 'right',
|
||
formatter: (row: any) => {
|
||
return h('div', { style: 'display: flex; gap: 8px;' }, [
|
||
h(ArtButtonTable, {
|
||
icon: '',
|
||
onClick: () => showRoleDialog(row)
|
||
}),
|
||
h(ArtButtonTable, {
|
||
type: 'edit',
|
||
onClick: () => showDialog('edit', row)
|
||
}),
|
||
h(ArtButtonTable, {
|
||
type: 'delete',
|
||
onClick: () => deleteAccount(row)
|
||
})
|
||
])
|
||
}
|
||
}
|
||
])
|
||
|
||
// 表单实例
|
||
const formRef = ref<FormInstance>()
|
||
|
||
// 表单数据
|
||
const formData = reactive({
|
||
id: '',
|
||
username: '',
|
||
password: '',
|
||
phone: '',
|
||
user_type: 2
|
||
})
|
||
|
||
onMounted(() => {
|
||
getAccountList()
|
||
loadAllRoles()
|
||
})
|
||
|
||
// 加载所有角色列表
|
||
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 showRoleDialog = async (row: any) => {
|
||
currentAccountId.value = row.ID
|
||
selectedRole.value = null
|
||
|
||
// 先加载当前账号的角色,再打开对话框
|
||
try {
|
||
const res = await AccountService.getAccountRoles(row.ID)
|
||
if (res.code === 0) {
|
||
// 提取角色ID(只取第一个角色)
|
||
const roles = res.data || []
|
||
selectedRole.value = roles.length > 0 ? roles[0].ID : null
|
||
// 数据加载完成后再打开对话框
|
||
roleDialogVisible.value = true
|
||
}
|
||
} catch (error) {
|
||
console.error('获取账号角色失败:', error)
|
||
}
|
||
}
|
||
|
||
// 提交分配角色
|
||
const handleAssignRoles = async () => {
|
||
if (selectedRole.value === null) {
|
||
ElMessage.warning('请选择一个角色')
|
||
return
|
||
}
|
||
|
||
roleSubmitLoading.value = true
|
||
try {
|
||
// 将单个角色ID包装成数组传给后端
|
||
await AccountService.assignRolesToAccount(currentAccountId.value, [selectedRole.value])
|
||
ElMessage.success('分配角色成功')
|
||
roleDialogVisible.value = false
|
||
} catch (error) {
|
||
console.error(error)
|
||
} finally {
|
||
roleSubmitLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 获取账号列表
|
||
const getAccountList = async () => {
|
||
loading.value = true
|
||
try {
|
||
const params = {
|
||
page: pagination.currentPage,
|
||
pageSize: pagination.pageSize,
|
||
keyword: formFilters.name || formFilters.phone || undefined
|
||
}
|
||
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: 3, max: 50, message: '长度在 3 到 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' }]
|
||
})
|
||
|
||
// 提交表单
|
||
const handleSubmit = async () => {
|
||
if (!formRef.value) return
|
||
|
||
await formRef.value.validate(async (valid) => {
|
||
if (valid) {
|
||
try {
|
||
const data: any = {
|
||
username: formData.username,
|
||
phone: formData.phone,
|
||
user_type: formData.user_type
|
||
}
|
||
|
||
if (dialogType.value === 'add') {
|
||
data.password = formData.password
|
||
await AccountService.createAccount(data)
|
||
ElMessage.success('添加成功')
|
||
} else {
|
||
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.updateAccount(row.ID, { status: newStatus })
|
||
ElMessage.success('状态切换成功')
|
||
} catch (error) {
|
||
// 切换失败,恢复原状态
|
||
row.status = oldStatus
|
||
console.error(error)
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.account-page {
|
||
// 账号管理页面样式
|
||
}
|
||
|
||
.role-radio-group {
|
||
width: 100%;
|
||
}
|
||
|
||
.role-radio-item {
|
||
margin-bottom: 16px;
|
||
padding: 8px;
|
||
}
|
||
</style>
|