Initial commit: One Pipe System
完整的管理系统,包含账户管理、卡片管理、套餐管理、财务管理等功能模块。 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
522
src/views/account-management/account/index.vue
Normal file
522
src/views/account-management/account/index.vue
Normal file
@@ -0,0 +1,522 @@
|
||||
<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>
|
||||
468
src/views/account-management/agent/index.vue
Normal file
468
src/views/account-management/agent/index.vue
Normal file
@@ -0,0 +1,468 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<ElRow>
|
||||
<ElCol :xs="24" :sm="12" :lg="6">
|
||||
<ElInput v-model="searchQuery" placeholder="代理商名称/联系人" clearable></ElInput>
|
||||
</ElCol>
|
||||
<div style="width: 12px"></div>
|
||||
<ElCol :xs="24" :sm="12" :lg="6">
|
||||
<ElSelect v-model="levelFilter" placeholder="代理商等级" clearable style="width: 100%">
|
||||
<ElOption label="一级代理" value="1" />
|
||||
<ElOption label="二级代理" value="2" />
|
||||
<ElOption label="三级代理" value="3" />
|
||||
</ElSelect>
|
||||
</ElCol>
|
||||
<div style="width: 12px"></div>
|
||||
<ElCol :xs="24" :sm="12" :lg="6" class="el-col2">
|
||||
<ElButton v-ripple @click="handleSearch">搜索</ElButton>
|
||||
<ElButton v-ripple @click="showDialog('add')">新增代理商</ElButton>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
|
||||
<ArtTable :data="filteredData" index row-key="id">
|
||||
<template #default>
|
||||
<ElTableColumn label="代理商名称" prop="agentName" min-width="150" />
|
||||
<ElTableColumn label="代理商编码" prop="agentCode" />
|
||||
<ElTableColumn label="等级" prop="level">
|
||||
<template #default="scope">
|
||||
<ElTag :type="getLevelTagType(scope.row.level)">
|
||||
{{ getLevelText(scope.row.level) }}
|
||||
</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="上级代理" prop="parentName" show-overflow-tooltip />
|
||||
<ElTableColumn label="联系人" prop="contactPerson" />
|
||||
<ElTableColumn label="联系电话" prop="contactPhone" />
|
||||
<ElTableColumn label="账号数量" prop="accountCount" />
|
||||
<ElTableColumn label="网卡数量" prop="simCardCount" />
|
||||
<ElTableColumn label="佣金总额" prop="totalCommission">
|
||||
<template #default="scope"> ¥{{ scope.row.totalCommission.toFixed(2) }} </template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="状态" prop="status">
|
||||
<template #default="scope">
|
||||
<ElTag :type="scope.row.status === 'active' ? 'success' : 'info'">
|
||||
{{ scope.row.status === 'active' ? '启用' : '禁用' }}
|
||||
</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="创建时间" prop="createTime" width="180" />
|
||||
<ElTableColumn fixed="right" label="操作" width="280">
|
||||
<template #default="scope">
|
||||
<el-button link @click="viewAccountList(scope.row)">账号管理</el-button>
|
||||
<el-button link @click="viewCommission(scope.row)">佣金配置</el-button>
|
||||
<el-button link @click="showDialog('edit', scope.row)">编辑</el-button>
|
||||
<el-button link @click="handleDelete(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
</template>
|
||||
</ArtTable>
|
||||
|
||||
<!-- 新增/编辑对话框 -->
|
||||
<ElDialog
|
||||
v-model="dialogVisible"
|
||||
:title="dialogType === 'add' ? '新增代理商' : '编辑代理商'"
|
||||
width="700px"
|
||||
align-center
|
||||
>
|
||||
<ElForm ref="formRef" :model="form" :rules="rules" label-width="120px">
|
||||
<ElRow :gutter="20">
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="代理商名称" prop="agentName">
|
||||
<ElInput v-model="form.agentName" placeholder="请输入代理商名称" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="代理商编码" prop="agentCode">
|
||||
<ElInput v-model="form.agentCode" placeholder="请输入代理商编码" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
<ElRow :gutter="20">
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="代理商等级" prop="level">
|
||||
<ElSelect v-model="form.level" placeholder="请选择代理商等级" style="width: 100%">
|
||||
<ElOption label="一级代理" :value="1" />
|
||||
<ElOption label="二级代理" :value="2" />
|
||||
<ElOption label="三级代理" :value="3" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="上级代理" prop="parentId">
|
||||
<ElSelect v-model="form.parentId" placeholder="请选择上级代理" clearable style="width: 100%">
|
||||
<ElOption label="无" :value="null" />
|
||||
<ElOption
|
||||
v-for="agent in parentAgentOptions"
|
||||
:key="agent.id"
|
||||
:label="agent.agentName"
|
||||
:value="agent.id"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
<ElRow :gutter="20">
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="联系人" prop="contactPerson">
|
||||
<ElInput v-model="form.contactPerson" placeholder="请输入联系人" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="联系电话" prop="contactPhone">
|
||||
<ElInput v-model="form.contactPhone" placeholder="请输入联系电话" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
<ElRow :gutter="20">
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="邮箱" prop="email">
|
||||
<ElInput v-model="form.email" placeholder="请输入邮箱" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :span="12">
|
||||
<ElFormItem label="公司地址" prop="address">
|
||||
<ElInput v-model="form.address" placeholder="请输入公司地址" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
<ElFormItem label="备注" prop="remark">
|
||||
<ElInput v-model="form.remark" type="textarea" :rows="2" placeholder="请输入备注信息" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="状态">
|
||||
<ElSwitch v-model="form.status" active-value="active" inactive-value="inactive" />
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton @click="dialogVisible = false">取消</ElButton>
|
||||
<ElButton type="primary" @click="handleSubmit(formRef)">提交</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
|
||||
<!-- 账号管理对话框 -->
|
||||
<ElDialog v-model="accountDialogVisible" title="代理商账号管理" width="900px" align-center>
|
||||
<div style="margin-bottom: 16px">
|
||||
<ElButton type="primary" size="small" @click="addSubAccount">创建子账号</ElButton>
|
||||
</div>
|
||||
<ArtTable :data="currentAgentAccounts" index>
|
||||
<template #default>
|
||||
<ElTableColumn label="账号" prop="username" />
|
||||
<ElTableColumn label="真实姓名" prop="realName" />
|
||||
<ElTableColumn label="手机号" prop="phone" />
|
||||
<ElTableColumn label="角色" prop="role" />
|
||||
<ElTableColumn label="状态" prop="status">
|
||||
<template #default="scope">
|
||||
<ElTag :type="scope.row.status === 'active' ? 'success' : 'info'">
|
||||
{{ scope.row.status === 'active' ? '正常' : '禁用' }}
|
||||
</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="创建时间" prop="createTime" />
|
||||
<ElTableColumn fixed="right" label="操作" width="150">
|
||||
<template #default="scope">
|
||||
<el-button link>编辑</el-button>
|
||||
<el-button link>禁用</el-button>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
</template>
|
||||
</ArtTable>
|
||||
</ElDialog>
|
||||
|
||||
<!-- 佣金配置对话框 -->
|
||||
<ElDialog v-model="commissionDialogVisible" title="佣金配置" width="600px" align-center>
|
||||
<ElForm label-width="120px">
|
||||
<ElFormItem label="佣金模式">
|
||||
<ElRadioGroup v-model="commissionForm.mode">
|
||||
<ElRadio value="fixed">固定佣金</ElRadio>
|
||||
<ElRadio value="percent">比例佣金</ElRadio>
|
||||
</ElRadioGroup>
|
||||
</ElFormItem>
|
||||
<ElFormItem v-if="commissionForm.mode === 'fixed'" label="固定金额">
|
||||
<ElInputNumber v-model="commissionForm.fixedAmount" :min="0" :precision="2" />
|
||||
<span style="margin-left: 8px">元/笔</span>
|
||||
</ElFormItem>
|
||||
<ElFormItem v-if="commissionForm.mode === 'percent'" label="佣金比例">
|
||||
<ElInputNumber v-model="commissionForm.percent" :min="0" :max="100" :precision="2" />
|
||||
<span style="margin-left: 8px">%</span>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="结算周期">
|
||||
<ElSelect v-model="commissionForm.settlementCycle" style="width: 100%">
|
||||
<ElOption label="实时结算" value="realtime" />
|
||||
<ElOption label="日结" value="daily" />
|
||||
<ElOption label="周结" value="weekly" />
|
||||
<ElOption label="月结" value="monthly" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton @click="commissionDialogVisible = false">取消</ElButton>
|
||||
<ElButton type="primary" @click="saveCommissionConfig">保存</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
|
||||
defineOptions({ name: 'AgentManagement' })
|
||||
|
||||
interface Agent {
|
||||
id?: string
|
||||
agentName: string
|
||||
agentCode: string
|
||||
level: number
|
||||
parentId?: string | null
|
||||
parentName?: string
|
||||
contactPerson: string
|
||||
contactPhone: string
|
||||
email?: string
|
||||
address?: string
|
||||
remark?: string
|
||||
accountCount?: number
|
||||
simCardCount?: number
|
||||
totalCommission?: number
|
||||
status: 'active' | 'inactive'
|
||||
createTime?: string
|
||||
}
|
||||
|
||||
// Mock 数据
|
||||
const mockData = ref<Agent[]>([
|
||||
{
|
||||
id: '1',
|
||||
agentName: '华东区总代理',
|
||||
agentCode: 'AGENT_HD_001',
|
||||
level: 1,
|
||||
parentId: null,
|
||||
parentName: '无',
|
||||
contactPerson: '张三',
|
||||
contactPhone: '13800138001',
|
||||
email: 'zhangsan@example.com',
|
||||
address: '上海市浦东新区',
|
||||
accountCount: 5,
|
||||
simCardCount: 1000,
|
||||
totalCommission: 158900.50,
|
||||
status: 'active',
|
||||
createTime: '2026-01-01 10:00:00'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
agentName: '江苏省代理',
|
||||
agentCode: 'AGENT_JS_001',
|
||||
level: 2,
|
||||
parentId: '1',
|
||||
parentName: '华东区总代理',
|
||||
contactPerson: '李四',
|
||||
contactPhone: '13800138002',
|
||||
email: 'lisi@example.com',
|
||||
address: '江苏省南京市',
|
||||
accountCount: 3,
|
||||
simCardCount: 500,
|
||||
totalCommission: 78500.00,
|
||||
status: 'active',
|
||||
createTime: '2026-01-05 11:00:00'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
agentName: '南京市代理',
|
||||
agentCode: 'AGENT_NJ_001',
|
||||
level: 3,
|
||||
parentId: '2',
|
||||
parentName: '江苏省代理',
|
||||
contactPerson: '王五',
|
||||
contactPhone: '13800138003',
|
||||
email: 'wangwu@example.com',
|
||||
address: '江苏省南京市玄武区',
|
||||
accountCount: 2,
|
||||
simCardCount: 200,
|
||||
totalCommission: 32800.00,
|
||||
status: 'active',
|
||||
createTime: '2026-01-10 12:00:00'
|
||||
}
|
||||
])
|
||||
|
||||
const searchQuery = ref('')
|
||||
const levelFilter = ref('')
|
||||
const dialogVisible = ref(false)
|
||||
const accountDialogVisible = ref(false)
|
||||
const commissionDialogVisible = ref(false)
|
||||
const dialogType = ref<'add' | 'edit'>('add')
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
const form = reactive<Agent>({
|
||||
agentName: '',
|
||||
agentCode: '',
|
||||
level: 1,
|
||||
parentId: null,
|
||||
contactPerson: '',
|
||||
contactPhone: '',
|
||||
email: '',
|
||||
address: '',
|
||||
remark: '',
|
||||
status: 'active'
|
||||
})
|
||||
|
||||
const commissionForm = reactive({
|
||||
mode: 'fixed',
|
||||
fixedAmount: 10,
|
||||
percent: 5,
|
||||
settlementCycle: 'monthly'
|
||||
})
|
||||
|
||||
const currentAgentAccounts = ref([
|
||||
{
|
||||
username: 'agent001',
|
||||
realName: '张三',
|
||||
phone: '13800138001',
|
||||
role: '管理员',
|
||||
status: 'active',
|
||||
createTime: '2026-01-01 10:00:00'
|
||||
}
|
||||
])
|
||||
|
||||
const rules = reactive<FormRules>({
|
||||
agentName: [{ required: true, message: '请输入代理商名称', trigger: 'blur' }],
|
||||
agentCode: [{ required: true, message: '请输入代理商编码', trigger: 'blur' }],
|
||||
level: [{ required: true, message: '请选择代理商等级', trigger: 'change' }],
|
||||
contactPerson: [{ required: true, message: '请输入联系人', trigger: 'blur' }],
|
||||
contactPhone: [
|
||||
{ required: true, message: '请输入联系电话', trigger: 'blur' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
|
||||
]
|
||||
})
|
||||
|
||||
const parentAgentOptions = computed(() => {
|
||||
return mockData.value.filter((item) => item.level < form.level)
|
||||
})
|
||||
|
||||
const filteredData = computed(() => {
|
||||
let data = mockData.value
|
||||
if (searchQuery.value) {
|
||||
data = data.filter(
|
||||
(item) =>
|
||||
item.agentName.includes(searchQuery.value) || item.contactPerson.includes(searchQuery.value)
|
||||
)
|
||||
}
|
||||
if (levelFilter.value) {
|
||||
data = data.filter((item) => item.level === parseInt(levelFilter.value))
|
||||
}
|
||||
return data
|
||||
})
|
||||
|
||||
const getLevelText = (level: number) => {
|
||||
const levelMap: Record<number, string> = { 1: '一级代理', 2: '二级代理', 3: '三级代理' }
|
||||
return levelMap[level] || '未知'
|
||||
}
|
||||
|
||||
const getLevelTagType = (level: number) => {
|
||||
const typeMap: Record<number, string> = { 1: '', 2: 'success', 3: 'warning' }
|
||||
return typeMap[level] || 'info'
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
// 搜索逻辑已通过 computed 实现
|
||||
}
|
||||
|
||||
const showDialog = (type: 'add' | 'edit', row?: Agent) => {
|
||||
dialogType.value = type
|
||||
dialogVisible.value = true
|
||||
|
||||
if (type === 'edit' && row) {
|
||||
Object.assign(form, row)
|
||||
} else {
|
||||
resetForm()
|
||||
}
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
Object.assign(form, {
|
||||
agentName: '',
|
||||
agentCode: '',
|
||||
level: 1,
|
||||
parentId: null,
|
||||
contactPerson: '',
|
||||
contactPhone: '',
|
||||
email: '',
|
||||
address: '',
|
||||
remark: '',
|
||||
status: 'active'
|
||||
})
|
||||
}
|
||||
|
||||
const handleSubmit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
|
||||
await formEl.validate((valid) => {
|
||||
if (valid) {
|
||||
if (dialogType.value === 'add') {
|
||||
const parentAgent = mockData.value.find((item) => item.id === form.parentId)
|
||||
mockData.value.push({
|
||||
...form,
|
||||
id: Date.now().toString(),
|
||||
parentName: parentAgent ? parentAgent.agentName : '无',
|
||||
accountCount: 0,
|
||||
simCardCount: 0,
|
||||
totalCommission: 0,
|
||||
createTime: new Date().toLocaleString('zh-CN')
|
||||
})
|
||||
ElMessage.success('新增成功')
|
||||
} else {
|
||||
const index = mockData.value.findIndex((item) => item.id === form.id)
|
||||
if (index !== -1) {
|
||||
const parentAgent = mockData.value.find((item) => item.id === form.parentId)
|
||||
mockData.value[index] = {
|
||||
...form,
|
||||
parentName: parentAgent ? parentAgent.agentName : '无'
|
||||
}
|
||||
}
|
||||
ElMessage.success('修改成功')
|
||||
}
|
||||
dialogVisible.value = false
|
||||
formEl.resetFields()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleDelete = (row: Agent) => {
|
||||
ElMessageBox.confirm('确定删除该代理商吗?删除后其下属代理商和账号也将被删除。', '删除确认', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'error'
|
||||
}).then(() => {
|
||||
const index = mockData.value.findIndex((item) => item.id === row.id)
|
||||
if (index !== -1) {
|
||||
mockData.value.splice(index, 1)
|
||||
}
|
||||
ElMessage.success('删除成功')
|
||||
})
|
||||
}
|
||||
|
||||
const viewAccountList = (row: Agent) => {
|
||||
accountDialogVisible.value = true
|
||||
// 实际应用中应该根据代理商ID加载账号列表
|
||||
}
|
||||
|
||||
const viewCommission = (row: Agent) => {
|
||||
commissionDialogVisible.value = true
|
||||
// 实际应用中应该根据代理商ID加载佣金配置
|
||||
}
|
||||
|
||||
const addSubAccount = () => {
|
||||
ElMessage.info('创建子账号功能')
|
||||
}
|
||||
|
||||
const saveCommissionConfig = () => {
|
||||
ElMessage.success('佣金配置保存成功')
|
||||
commissionDialogVisible.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-content {
|
||||
// 自定义样式
|
||||
}
|
||||
</style>
|
||||
276
src/views/account-management/customer-account/index.vue
Normal file
276
src/views/account-management/customer-account/index.vue
Normal file
@@ -0,0 +1,276 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<ElRow>
|
||||
<ElCol :xs="24" :sm="12" :lg="6">
|
||||
<ElInput v-model="searchQuery" placeholder="账号/姓名/手机号" clearable></ElInput>
|
||||
</ElCol>
|
||||
<div style="width: 12px"></div>
|
||||
<ElCol :xs="24" :sm="12" :lg="6">
|
||||
<ElSelect v-model="typeFilter" placeholder="客户类型" clearable style="width: 100%">
|
||||
<ElOption label="代理商" value="agent" />
|
||||
<ElOption label="企业客户" value="enterprise" />
|
||||
</ElSelect>
|
||||
</ElCol>
|
||||
<div style="width: 12px"></div>
|
||||
<ElCol :xs="24" :sm="12" :lg="6">
|
||||
<ElSelect v-model="statusFilter" placeholder="账号状态" clearable style="width: 100%">
|
||||
<ElOption label="正常" value="active" />
|
||||
<ElOption label="禁用" value="disabled" />
|
||||
<ElOption label="已锁定" value="locked" />
|
||||
</ElSelect>
|
||||
</ElCol>
|
||||
<div style="width: 12px"></div>
|
||||
<ElCol :xs="24" :sm="12" :lg="6" class="el-col2">
|
||||
<ElButton v-ripple @click="handleSearch">搜索</ElButton>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
|
||||
<ArtTable :data="filteredData" index>
|
||||
<template #default>
|
||||
<ElTableColumn label="账号" prop="username" min-width="120" />
|
||||
<ElTableColumn label="真实姓名" prop="realName" />
|
||||
<ElTableColumn label="客户类型" prop="customerType">
|
||||
<template #default="scope">
|
||||
<ElTag :type="scope.row.customerType === 'agent' ? '' : 'success'">
|
||||
{{ scope.row.customerType === 'agent' ? '代理商' : '企业客户' }}
|
||||
</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="所属组织" prop="organizationName" show-overflow-tooltip />
|
||||
<ElTableColumn label="手机号" prop="phone" />
|
||||
<ElTableColumn label="邮箱" prop="email" show-overflow-tooltip />
|
||||
<ElTableColumn label="登录次数" prop="loginCount" />
|
||||
<ElTableColumn label="最后登录" prop="lastLoginTime" width="180" />
|
||||
<ElTableColumn label="状态" prop="status">
|
||||
<template #default="scope">
|
||||
<ElTag :type="getStatusTagType(scope.row.status)">
|
||||
{{ getStatusText(scope.row.status) }}
|
||||
</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="创建时间" prop="createTime" width="180" />
|
||||
<ElTableColumn fixed="right" label="操作" width="280">
|
||||
<template #default="scope">
|
||||
<el-button link @click="viewDetail(scope.row)">详情</el-button>
|
||||
<el-button link @click="unbindPhone(scope.row)">解绑手机</el-button>
|
||||
<el-button link @click="resetPassword(scope.row)">重置密码</el-button>
|
||||
<el-button
|
||||
link
|
||||
:type="scope.row.status === 'active' ? 'danger' : 'primary'"
|
||||
@click="toggleStatus(scope.row)"
|
||||
>
|
||||
{{ scope.row.status === 'active' ? '禁用' : '启用' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
</template>
|
||||
</ArtTable>
|
||||
|
||||
<!-- 操作记录对话框 -->
|
||||
<ElDialog v-model="detailDialogVisible" title="账号详情" width="800px" align-center>
|
||||
<ElDescriptions :column="2" border>
|
||||
<ElDescriptionsItem label="账号">{{ currentAccount?.username }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="真实姓名">{{ currentAccount?.realName }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="客户类型">{{
|
||||
currentAccount?.customerType === 'agent' ? '代理商' : '企业客户'
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="所属组织">{{
|
||||
currentAccount?.organizationName
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="手机号">{{ currentAccount?.phone }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="邮箱">{{ currentAccount?.email }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="注册时间">{{ currentAccount?.createTime }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="最后登录">{{
|
||||
currentAccount?.lastLoginTime
|
||||
}}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="登录次数">{{ currentAccount?.loginCount }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="状态">
|
||||
<ElTag :type="getStatusTagType(currentAccount?.status || 'active')">
|
||||
{{ getStatusText(currentAccount?.status || 'active') }}
|
||||
</ElTag>
|
||||
</ElDescriptionsItem>
|
||||
</ElDescriptions>
|
||||
|
||||
<ElDivider>操作记录</ElDivider>
|
||||
<ArtTable :data="operationRecords" index max-height="300">
|
||||
<template #default>
|
||||
<ElTableColumn label="操作类型" prop="operationType" />
|
||||
<ElTableColumn label="操作描述" prop="description" />
|
||||
<ElTableColumn label="操作人" prop="operator" />
|
||||
<ElTableColumn label="操作时间" prop="operateTime" width="180" />
|
||||
</template>
|
||||
</ArtTable>
|
||||
</ElDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
defineOptions({ name: 'CustomerAccount' })
|
||||
|
||||
interface CustomerAccount {
|
||||
id: string
|
||||
username: string
|
||||
realName: string
|
||||
customerType: 'agent' | 'enterprise'
|
||||
organizationName: string
|
||||
phone: string
|
||||
email: string
|
||||
loginCount: number
|
||||
lastLoginTime: string
|
||||
status: 'active' | 'disabled' | 'locked'
|
||||
createTime: string
|
||||
}
|
||||
|
||||
// Mock 数据
|
||||
const mockData = ref<CustomerAccount[]>([
|
||||
{
|
||||
id: '1',
|
||||
username: 'agent001',
|
||||
realName: '张三',
|
||||
customerType: 'agent',
|
||||
organizationName: '华东区总代理',
|
||||
phone: '13800138001',
|
||||
email: 'zhangsan@example.com',
|
||||
loginCount: 328,
|
||||
lastLoginTime: '2026-01-09 08:30:00',
|
||||
status: 'active',
|
||||
createTime: '2026-01-01 10:00:00'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
username: 'enterprise001',
|
||||
realName: '李四',
|
||||
customerType: 'enterprise',
|
||||
organizationName: '某某科技有限公司',
|
||||
phone: '13800138002',
|
||||
email: 'lisi@example.com',
|
||||
loginCount: 156,
|
||||
lastLoginTime: '2026-01-08 18:45:00',
|
||||
status: 'active',
|
||||
createTime: '2026-01-03 11:00:00'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
username: 'agent002',
|
||||
realName: '王五',
|
||||
customerType: 'agent',
|
||||
organizationName: '江苏省代理',
|
||||
phone: '13800138003',
|
||||
email: 'wangwu@example.com',
|
||||
loginCount: 89,
|
||||
lastLoginTime: '2026-01-07 14:20:00',
|
||||
status: 'disabled',
|
||||
createTime: '2026-01-05 12:00:00'
|
||||
}
|
||||
])
|
||||
|
||||
const operationRecords = ref([
|
||||
{
|
||||
operationType: '重置密码',
|
||||
description: '管理员重置登录密码',
|
||||
operator: 'admin',
|
||||
operateTime: '2026-01-08 10:00:00'
|
||||
},
|
||||
{
|
||||
operationType: '解绑手机',
|
||||
description: '解绑原手机号并重新绑定',
|
||||
operator: 'admin',
|
||||
operateTime: '2026-01-06 15:30:00'
|
||||
}
|
||||
])
|
||||
|
||||
const searchQuery = ref('')
|
||||
const typeFilter = ref('')
|
||||
const statusFilter = ref('')
|
||||
const detailDialogVisible = ref(false)
|
||||
const currentAccount = ref<CustomerAccount | null>(null)
|
||||
|
||||
const filteredData = computed(() => {
|
||||
let data = mockData.value
|
||||
if (searchQuery.value) {
|
||||
const query = searchQuery.value.toLowerCase()
|
||||
data = data.filter(
|
||||
(item) =>
|
||||
item.username.toLowerCase().includes(query) ||
|
||||
item.realName.includes(query) ||
|
||||
item.phone.includes(query)
|
||||
)
|
||||
}
|
||||
if (typeFilter.value) {
|
||||
data = data.filter((item) => item.customerType === typeFilter.value)
|
||||
}
|
||||
if (statusFilter.value) {
|
||||
data = data.filter((item) => item.status === statusFilter.value)
|
||||
}
|
||||
return data
|
||||
})
|
||||
|
||||
const getStatusText = (status: string) => {
|
||||
const statusMap: Record<string, string> = {
|
||||
active: '正常',
|
||||
disabled: '禁用',
|
||||
locked: '已锁定'
|
||||
}
|
||||
return statusMap[status] || '未知'
|
||||
}
|
||||
|
||||
const getStatusTagType = (status: string) => {
|
||||
const typeMap: Record<string, string> = {
|
||||
active: 'success',
|
||||
disabled: 'info',
|
||||
locked: 'danger'
|
||||
}
|
||||
return typeMap[status] || 'info'
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
// 搜索逻辑已通过 computed 实现
|
||||
}
|
||||
|
||||
const viewDetail = (row: CustomerAccount) => {
|
||||
currentAccount.value = row
|
||||
detailDialogVisible.value = true
|
||||
}
|
||||
|
||||
const unbindPhone = (row: CustomerAccount) => {
|
||||
ElMessageBox.confirm(`确定要解绑账号 ${row.username} 的手机号吗?`, '解绑确认', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
ElMessage.success('手机号解绑成功')
|
||||
})
|
||||
}
|
||||
|
||||
const resetPassword = (row: CustomerAccount) => {
|
||||
ElMessageBox.confirm(`确定要重置账号 ${row.username} 的密码吗?新密码将发送至其手机。`, '重置密码', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
ElMessage.success('密码重置成功,新密码已发送至手机')
|
||||
})
|
||||
}
|
||||
|
||||
const toggleStatus = (row: CustomerAccount) => {
|
||||
const action = row.status === 'active' ? '禁用' : '启用'
|
||||
ElMessageBox.confirm(`确定要${action}账号 ${row.username} 吗?`, `${action}确认`, {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
row.status = row.status === 'active' ? 'disabled' : 'active'
|
||||
ElMessage.success(`${action}成功`)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-content {
|
||||
:deep(.el-descriptions__label) {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
441
src/views/account-management/customer-commission/index.vue
Normal file
441
src/views/account-management/customer-commission/index.vue
Normal file
@@ -0,0 +1,441 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<!-- 统计卡片 -->
|
||||
<ElRow :gutter="20" style="margin-bottom: 20px">
|
||||
<ElCol :xs="24" :sm="12" :lg="6">
|
||||
<ElCard shadow="hover" class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-label">客户总数</div>
|
||||
<div class="stat-value">{{ statistics.totalCustomers }}</div>
|
||||
</div>
|
||||
<el-icon class="stat-icon" style="color: var(--el-color-primary)"><User /></el-icon>
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12" :lg="6">
|
||||
<ElCard shadow="hover" class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-label">累计佣金</div>
|
||||
<div class="stat-value" style="color: var(--el-color-success)">
|
||||
¥{{ statistics.totalCommission.toFixed(2) }}
|
||||
</div>
|
||||
</div>
|
||||
<el-icon class="stat-icon" style="color: var(--el-color-success)"><Money /></el-icon>
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12" :lg="6">
|
||||
<ElCard shadow="hover" class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-label">已提现</div>
|
||||
<div class="stat-value" style="color: var(--el-color-warning)">
|
||||
¥{{ statistics.totalWithdrawn.toFixed(2) }}
|
||||
</div>
|
||||
</div>
|
||||
<el-icon class="stat-icon" style="color: var(--el-color-warning)"><WalletFilled /></el-icon>
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12" :lg="6">
|
||||
<ElCard shadow="hover" class="stat-card">
|
||||
<div class="stat-content">
|
||||
<div class="stat-label">待提现</div>
|
||||
<div class="stat-value" style="color: var(--el-color-danger)">
|
||||
¥{{ statistics.pendingWithdrawal.toFixed(2) }}
|
||||
</div>
|
||||
</div>
|
||||
<el-icon class="stat-icon" style="color: var(--el-color-danger)"><Wallet /></el-icon>
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
|
||||
<!-- 搜索和筛选区 -->
|
||||
<ElRow :gutter="12">
|
||||
<ElCol :xs="24" :sm="12" :lg="6">
|
||||
<ElInput v-model="searchQuery" placeholder="客户名称/手机号" clearable />
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12" :lg="6">
|
||||
<ElSelect v-model="customerTypeFilter" placeholder="客户类型" clearable style="width: 100%">
|
||||
<ElOption label="全部" value="" />
|
||||
<ElOption label="代理商" value="agent" />
|
||||
<ElOption label="企业客户" value="enterprise" />
|
||||
</ElSelect>
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12" :lg="6">
|
||||
<ElSelect v-model="commissionRangeFilter" placeholder="佣金范围" clearable style="width: 100%">
|
||||
<ElOption label="全部" value="" />
|
||||
<ElOption label="0-1000元" value="0-1000" />
|
||||
<ElOption label="1000-5000元" value="1000-5000" />
|
||||
<ElOption label="5000-10000元" value="5000-10000" />
|
||||
<ElOption label="10000元以上" value="10000+" />
|
||||
</ElSelect>
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12" :lg="6" class="el-col2">
|
||||
<ElButton v-ripple @click="handleSearch">搜索</ElButton>
|
||||
<ElButton v-ripple @click="exportData">导出</ElButton>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
|
||||
<!-- 客户佣金列表 -->
|
||||
<ArtTable :data="filteredData" index style="margin-top: 20px">
|
||||
<template #default>
|
||||
<ElTableColumn label="客户名称" prop="customerName" min-width="150" show-overflow-tooltip />
|
||||
<ElTableColumn label="客户类型" prop="customerType" width="120">
|
||||
<template #default="scope">
|
||||
<ElTag :type="scope.row.customerType === 'agent' ? 'warning' : 'primary'">
|
||||
{{ scope.row.customerType === 'agent' ? '代理商' : '企业客户' }}
|
||||
</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="联系电话" prop="phone" width="130" />
|
||||
<ElTableColumn label="累计佣金" prop="totalCommission" width="130" sortable>
|
||||
<template #default="scope">
|
||||
<span style="color: var(--el-color-success); font-weight: 600">
|
||||
¥{{ scope.row.totalCommission.toFixed(2) }}
|
||||
</span>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="已提现" prop="withdrawnAmount" width="130" sortable>
|
||||
<template #default="scope">
|
||||
<span style="color: var(--el-color-warning)">
|
||||
¥{{ scope.row.withdrawnAmount.toFixed(2) }}
|
||||
</span>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="待提现" prop="pendingAmount" width="130" sortable>
|
||||
<template #default="scope">
|
||||
<span style="color: var(--el-color-danger); font-weight: 600">
|
||||
¥{{ scope.row.pendingAmount.toFixed(2) }}
|
||||
</span>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="提现次数" prop="withdrawalCount" width="100" align="center" />
|
||||
<ElTableColumn label="卡片数量" prop="cardCount" width="100" align="center">
|
||||
<template #default="scope">
|
||||
<span style="color: var(--el-color-primary)">{{ scope.row.cardCount }}</span>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="最近提现" prop="lastWithdrawalTime" width="180" />
|
||||
<ElTableColumn label="注册时间" prop="registerTime" width="180" />
|
||||
<ElTableColumn fixed="right" label="操作" width="220">
|
||||
<template #default="scope">
|
||||
<el-button link :icon="View" @click="viewCommissionDetail(scope.row)">佣金详情</el-button>
|
||||
<el-button link :icon="List" @click="viewWithdrawalHistory(scope.row)">提现记录</el-button>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
</template>
|
||||
</ArtTable>
|
||||
|
||||
<!-- 佣金详情对话框 -->
|
||||
<ElDialog v-model="commissionDialogVisible" title="佣金详情" width="900px" align-center>
|
||||
<ElDescriptions :column="2" border style="margin-bottom: 20px">
|
||||
<ElDescriptionsItem label="客户名称">{{ currentCustomer.customerName }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="客户类型">
|
||||
<ElTag :type="currentCustomer.customerType === 'agent' ? 'warning' : 'primary'">
|
||||
{{ currentCustomer.customerType === 'agent' ? '代理商' : '企业客户' }}
|
||||
</ElTag>
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="累计佣金">
|
||||
<span style="color: var(--el-color-success); font-weight: 600">
|
||||
¥{{ currentCustomer.totalCommission.toFixed(2) }}
|
||||
</span>
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="已提现">
|
||||
<span style="color: var(--el-color-warning)">
|
||||
¥{{ currentCustomer.withdrawnAmount.toFixed(2) }}
|
||||
</span>
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="待提现">
|
||||
<span style="color: var(--el-color-danger); font-weight: 600">
|
||||
¥{{ currentCustomer.pendingAmount.toFixed(2) }}
|
||||
</span>
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="提现次数">{{ currentCustomer.withdrawalCount }}</ElDescriptionsItem>
|
||||
</ElDescriptions>
|
||||
|
||||
<ElDivider content-position="left">佣金明细</ElDivider>
|
||||
<ArtTable :data="commissionDetails" index>
|
||||
<template #default>
|
||||
<ElTableColumn label="来源" prop="source" width="120" />
|
||||
<ElTableColumn label="订单号" prop="orderNo" width="180" />
|
||||
<ElTableColumn label="佣金金额" prop="amount" width="120">
|
||||
<template #default="scope">
|
||||
<span style="color: var(--el-color-success)">+¥{{ scope.row.amount.toFixed(2) }}</span>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="佣金比例" prop="rate" width="100" />
|
||||
<ElTableColumn label="订单金额" prop="orderAmount" width="120" />
|
||||
<ElTableColumn label="获得时间" prop="createTime" width="180" />
|
||||
<ElTableColumn label="状态" prop="status" width="100">
|
||||
<template #default="scope">
|
||||
<ElTag v-if="scope.row.status === 'completed'" type="success">已结算</ElTag>
|
||||
<ElTag v-else-if="scope.row.status === 'pending'" type="warning">待结算</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
</template>
|
||||
</ArtTable>
|
||||
|
||||
<template #footer>
|
||||
<ElButton @click="commissionDialogVisible = false">关闭</ElButton>
|
||||
</template>
|
||||
</ElDialog>
|
||||
|
||||
<!-- 提现记录对话框 -->
|
||||
<ElDialog v-model="withdrawalDialogVisible" title="提现记录" width="900px" align-center>
|
||||
<ArtTable :data="withdrawalHistory" index>
|
||||
<template #default>
|
||||
<ElTableColumn label="提现单号" prop="withdrawalNo" width="180" />
|
||||
<ElTableColumn label="提现金额" prop="amount" width="120">
|
||||
<template #default="scope">
|
||||
<span style="color: var(--el-color-danger); font-weight: 600">
|
||||
¥{{ scope.row.amount.toFixed(2) }}
|
||||
</span>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="手续费" prop="fee" width="100">
|
||||
<template #default="scope"> ¥{{ scope.row.fee.toFixed(2) }} </template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="实际到账" prop="actualAmount" width="120">
|
||||
<template #default="scope">
|
||||
<span style="color: var(--el-color-success)">
|
||||
¥{{ scope.row.actualAmount.toFixed(2) }}
|
||||
</span>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="提现方式" prop="method" width="100" />
|
||||
<ElTableColumn label="状态" prop="status" width="100">
|
||||
<template #default="scope">
|
||||
<ElTag v-if="scope.row.status === 'completed'" type="success">已完成</ElTag>
|
||||
<ElTag v-else-if="scope.row.status === 'processing'" type="warning">处理中</ElTag>
|
||||
<ElTag v-else-if="scope.row.status === 'pending'" type="info">待审核</ElTag>
|
||||
<ElTag v-else type="danger">已拒绝</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="申请时间" prop="applyTime" width="180" />
|
||||
<ElTableColumn label="完成时间" prop="completeTime" width="180">
|
||||
<template #default="scope">
|
||||
{{ scope.row.completeTime || '-' }}
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
</template>
|
||||
</ArtTable>
|
||||
|
||||
<template #footer>
|
||||
<ElButton @click="withdrawalDialogVisible = false">关闭</ElButton>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { View, List, User, Money, WalletFilled, Wallet } from '@element-plus/icons-vue'
|
||||
|
||||
defineOptions({ name: 'CustomerCommission' })
|
||||
|
||||
interface CustomerCommission {
|
||||
id: string
|
||||
customerName: string
|
||||
customerType: 'agent' | 'enterprise'
|
||||
phone: string
|
||||
totalCommission: number
|
||||
withdrawnAmount: number
|
||||
pendingAmount: number
|
||||
withdrawalCount: number
|
||||
cardCount: number
|
||||
lastWithdrawalTime: string
|
||||
registerTime: string
|
||||
}
|
||||
|
||||
const searchQuery = ref('')
|
||||
const customerTypeFilter = ref('')
|
||||
const commissionRangeFilter = ref('')
|
||||
const commissionDialogVisible = ref(false)
|
||||
const withdrawalDialogVisible = ref(false)
|
||||
|
||||
const statistics = reactive({
|
||||
totalCustomers: 156,
|
||||
totalCommission: 580000.00,
|
||||
totalWithdrawn: 420000.00,
|
||||
pendingWithdrawal: 160000.00
|
||||
})
|
||||
|
||||
const mockData = ref<CustomerCommission[]>([
|
||||
{
|
||||
id: '1',
|
||||
customerName: '华东区总代理',
|
||||
customerType: 'agent',
|
||||
phone: '13800138000',
|
||||
totalCommission: 85000.00,
|
||||
withdrawnAmount: 70000.00,
|
||||
pendingAmount: 15000.00,
|
||||
withdrawalCount: 12,
|
||||
cardCount: 500,
|
||||
lastWithdrawalTime: '2026-01-08 10:00:00',
|
||||
registerTime: '2025-06-01 09:00:00'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
customerName: '深圳市科技有限公司',
|
||||
customerType: 'enterprise',
|
||||
phone: '13900139000',
|
||||
totalCommission: 45000.00,
|
||||
withdrawnAmount: 30000.00,
|
||||
pendingAmount: 15000.00,
|
||||
withdrawalCount: 8,
|
||||
cardCount: 300,
|
||||
lastWithdrawalTime: '2026-01-05 14:30:00',
|
||||
registerTime: '2025-07-15 10:00:00'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
customerName: '北京智能制造',
|
||||
customerType: 'enterprise',
|
||||
phone: '13700137000',
|
||||
totalCommission: 68000.00,
|
||||
withdrawnAmount: 55000.00,
|
||||
pendingAmount: 13000.00,
|
||||
withdrawalCount: 10,
|
||||
cardCount: 450,
|
||||
lastWithdrawalTime: '2026-01-07 16:00:00',
|
||||
registerTime: '2025-08-20 11:00:00'
|
||||
}
|
||||
])
|
||||
|
||||
const currentCustomer = ref<CustomerCommission>({
|
||||
id: '',
|
||||
customerName: '',
|
||||
customerType: 'agent',
|
||||
phone: '',
|
||||
totalCommission: 0,
|
||||
withdrawnAmount: 0,
|
||||
pendingAmount: 0,
|
||||
withdrawalCount: 0,
|
||||
cardCount: 0,
|
||||
lastWithdrawalTime: '',
|
||||
registerTime: ''
|
||||
})
|
||||
|
||||
const commissionDetails = ref([
|
||||
{
|
||||
id: '1',
|
||||
source: '套餐销售',
|
||||
orderNo: 'ORD202601090001',
|
||||
amount: 150.00,
|
||||
rate: '10%',
|
||||
orderAmount: '¥1,500.00',
|
||||
createTime: '2026-01-09 09:30:00',
|
||||
status: 'completed'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
source: '卡片激活',
|
||||
orderNo: 'ORD202601080025',
|
||||
amount: 80.00,
|
||||
rate: '8%',
|
||||
orderAmount: '¥1,000.00',
|
||||
createTime: '2026-01-08 15:20:00',
|
||||
status: 'completed'
|
||||
}
|
||||
])
|
||||
|
||||
const withdrawalHistory = ref([
|
||||
{
|
||||
id: '1',
|
||||
withdrawalNo: 'WD202601080001',
|
||||
amount: 10000.00,
|
||||
fee: 20.00,
|
||||
actualAmount: 9980.00,
|
||||
method: '银行卡',
|
||||
status: 'completed',
|
||||
applyTime: '2026-01-08 10:00:00',
|
||||
completeTime: '2026-01-08 15:30:00'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
withdrawalNo: 'WD202601050002',
|
||||
amount: 5000.00,
|
||||
fee: 10.00,
|
||||
actualAmount: 4990.00,
|
||||
method: '支付宝',
|
||||
status: 'completed',
|
||||
applyTime: '2026-01-05 14:00:00',
|
||||
completeTime: '2026-01-05 18:20:00'
|
||||
}
|
||||
])
|
||||
|
||||
const filteredData = computed(() => {
|
||||
let data = mockData.value
|
||||
|
||||
if (searchQuery.value) {
|
||||
data = data.filter(
|
||||
(item) =>
|
||||
item.customerName.includes(searchQuery.value) ||
|
||||
item.phone.includes(searchQuery.value)
|
||||
)
|
||||
}
|
||||
|
||||
if (customerTypeFilter.value) {
|
||||
data = data.filter((item) => item.customerType === customerTypeFilter.value)
|
||||
}
|
||||
|
||||
if (commissionRangeFilter.value) {
|
||||
data = data.filter((item) => {
|
||||
const commission = item.totalCommission
|
||||
if (commissionRangeFilter.value === '0-1000') return commission >= 0 && commission < 1000
|
||||
if (commissionRangeFilter.value === '1000-5000') return commission >= 1000 && commission < 5000
|
||||
if (commissionRangeFilter.value === '5000-10000') return commission >= 5000 && commission < 10000
|
||||
if (commissionRangeFilter.value === '10000+') return commission >= 10000
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
return data
|
||||
})
|
||||
|
||||
const handleSearch = () => {}
|
||||
|
||||
const exportData = () => {
|
||||
ElMessage.success('数据导出中...')
|
||||
}
|
||||
|
||||
const viewCommissionDetail = (row: CustomerCommission) => {
|
||||
currentCustomer.value = { ...row }
|
||||
commissionDialogVisible.value = true
|
||||
}
|
||||
|
||||
const viewWithdrawalHistory = (row: CustomerCommission) => {
|
||||
currentCustomer.value = { ...row }
|
||||
withdrawalDialogVisible.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-content {
|
||||
.stat-card {
|
||||
:deep(.el-card__body) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.stat-content {
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-secondary);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
font-size: 40px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
240
src/views/account-management/customer-role/index.vue
Normal file
240
src/views/account-management/customer-role/index.vue
Normal file
@@ -0,0 +1,240 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<ElRow>
|
||||
<ElCol :xs="24" :sm="12" :lg="6">
|
||||
<ElInput v-model="searchQuery" placeholder="角色名称" clearable></ElInput>
|
||||
</ElCol>
|
||||
<div style="width: 12px"></div>
|
||||
<ElCol :xs="24" :sm="12" :lg="6" class="el-col2">
|
||||
<ElButton v-ripple @click="handleSearch">搜索</ElButton>
|
||||
<ElButton v-ripple @click="showDialog('add')">新增角色</ElButton>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
|
||||
<ArtTable :data="filteredData" index>
|
||||
<template #default>
|
||||
<ElTableColumn label="角色名称" prop="roleName" />
|
||||
<ElTableColumn label="角色编码" prop="roleCode" />
|
||||
<ElTableColumn label="能力边界" prop="abilities">
|
||||
<template #default="scope">
|
||||
<ElTag
|
||||
v-for="(ability, index) in scope.row.abilities"
|
||||
:key="index"
|
||||
size="small"
|
||||
style="margin-right: 4px"
|
||||
>
|
||||
{{ ability }}
|
||||
</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="描述" prop="description" show-overflow-tooltip />
|
||||
<ElTableColumn label="状态" prop="status">
|
||||
<template #default="scope">
|
||||
<ElTag :type="scope.row.status === 'active' ? 'success' : 'info'">
|
||||
{{ scope.row.status === 'active' ? '启用' : '禁用' }}
|
||||
</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="创建时间" prop="createTime" width="180" />
|
||||
<ElTableColumn fixed="right" label="操作" width="180">
|
||||
<template #default="scope">
|
||||
<el-button link @click="showDialog('edit', scope.row)">编辑</el-button>
|
||||
<el-button link @click="handleDelete(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
</template>
|
||||
</ArtTable>
|
||||
|
||||
<!-- 新增/编辑对话框 -->
|
||||
<ElDialog
|
||||
v-model="dialogVisible"
|
||||
:title="dialogType === 'add' ? '新增客户角色' : '编辑客户角色'"
|
||||
width="600px"
|
||||
align-center
|
||||
>
|
||||
<ElForm ref="formRef" :model="form" :rules="rules" label-width="120px">
|
||||
<ElFormItem label="角色名称" prop="roleName">
|
||||
<ElInput v-model="form.roleName" placeholder="请输入角色名称" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="角色编码" prop="roleCode">
|
||||
<ElInput v-model="form.roleCode" placeholder="请输入角色编码" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="能力边界" prop="abilities">
|
||||
<ElCheckboxGroup v-model="form.abilities">
|
||||
<ElCheckbox label="查看网卡">查看网卡</ElCheckbox>
|
||||
<ElCheckbox label="操作网卡">操作网卡</ElCheckbox>
|
||||
<ElCheckbox label="查看套餐">查看套餐</ElCheckbox>
|
||||
<ElCheckbox label="购买套餐">购买套餐</ElCheckbox>
|
||||
<ElCheckbox label="查看设备">查看设备</ElCheckbox>
|
||||
<ElCheckbox label="管理设备">管理设备</ElCheckbox>
|
||||
<ElCheckbox label="查看佣金">查看佣金</ElCheckbox>
|
||||
<ElCheckbox label="提现佣金">提现佣金</ElCheckbox>
|
||||
</ElCheckboxGroup>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="描述" prop="description">
|
||||
<ElInput v-model="form.description" type="textarea" :rows="3" placeholder="请输入角色描述" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="状态">
|
||||
<ElSwitch v-model="form.status" active-value="active" inactive-value="inactive" />
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton @click="dialogVisible = false">取消</ElButton>
|
||||
<ElButton type="primary" @click="handleSubmit(formRef)">提交</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
|
||||
defineOptions({ name: 'CustomerRole' })
|
||||
|
||||
interface CustomerRole {
|
||||
id?: string
|
||||
roleName: string
|
||||
roleCode: string
|
||||
abilities: string[]
|
||||
description: string
|
||||
status: 'active' | 'inactive'
|
||||
createTime?: string
|
||||
}
|
||||
|
||||
// Mock 数据
|
||||
const mockData = ref<CustomerRole[]>([
|
||||
{
|
||||
id: '1',
|
||||
roleName: '标准客户',
|
||||
roleCode: 'CUSTOMER_STANDARD',
|
||||
abilities: ['查看网卡', '查看套餐', '查看设备'],
|
||||
description: '标准客户角色,拥有基本查看权限',
|
||||
status: 'active',
|
||||
createTime: '2026-01-01 10:00:00'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
roleName: 'VIP客户',
|
||||
roleCode: 'CUSTOMER_VIP',
|
||||
abilities: ['查看网卡', '操作网卡', '查看套餐', '购买套餐', '查看设备', '管理设备'],
|
||||
description: 'VIP客户角色,拥有更多操作权限',
|
||||
status: 'active',
|
||||
createTime: '2026-01-02 11:00:00'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
roleName: '企业客户',
|
||||
roleCode: 'CUSTOMER_ENTERPRISE',
|
||||
abilities: ['查看网卡', '操作网卡', '查看套餐', '购买套餐', '查看设备', '管理设备', '查看佣金'],
|
||||
description: '企业客户角色,拥有完整业务权限',
|
||||
status: 'active',
|
||||
createTime: '2026-01-03 12:00:00'
|
||||
}
|
||||
])
|
||||
|
||||
const searchQuery = ref('')
|
||||
const dialogVisible = ref(false)
|
||||
const dialogType = ref<'add' | 'edit'>('add')
|
||||
const formRef = ref<FormInstance>()
|
||||
|
||||
const form = reactive<CustomerRole>({
|
||||
roleName: '',
|
||||
roleCode: '',
|
||||
abilities: [],
|
||||
description: '',
|
||||
status: 'active'
|
||||
})
|
||||
|
||||
const rules = reactive<FormRules>({
|
||||
roleName: [{ required: true, message: '请输入角色名称', trigger: 'blur' }],
|
||||
roleCode: [{ required: true, message: '请输入角色编码', trigger: 'blur' }],
|
||||
abilities: [{ required: true, message: '请至少选择一项能力', trigger: 'change' }]
|
||||
})
|
||||
|
||||
const filteredData = computed(() => {
|
||||
if (!searchQuery.value) return mockData.value
|
||||
return mockData.value.filter((item) =>
|
||||
item.roleName.toLowerCase().includes(searchQuery.value.toLowerCase())
|
||||
)
|
||||
})
|
||||
|
||||
const handleSearch = () => {
|
||||
// 搜索逻辑已通过 computed 实现
|
||||
}
|
||||
|
||||
const showDialog = (type: 'add' | 'edit', row?: CustomerRole) => {
|
||||
dialogType.value = type
|
||||
dialogVisible.value = true
|
||||
|
||||
if (type === 'edit' && row) {
|
||||
form.id = row.id
|
||||
form.roleName = row.roleName
|
||||
form.roleCode = row.roleCode
|
||||
form.abilities = [...row.abilities]
|
||||
form.description = row.description
|
||||
form.status = row.status
|
||||
} else {
|
||||
resetForm()
|
||||
}
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
form.id = undefined
|
||||
form.roleName = ''
|
||||
form.roleCode = ''
|
||||
form.abilities = []
|
||||
form.description = ''
|
||||
form.status = 'active'
|
||||
}
|
||||
|
||||
const handleSubmit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
|
||||
await formEl.validate((valid) => {
|
||||
if (valid) {
|
||||
if (dialogType.value === 'add') {
|
||||
mockData.value.push({
|
||||
...form,
|
||||
id: Date.now().toString(),
|
||||
createTime: new Date().toLocaleString('zh-CN')
|
||||
})
|
||||
ElMessage.success('新增成功')
|
||||
} else {
|
||||
const index = mockData.value.findIndex((item) => item.id === form.id)
|
||||
if (index !== -1) {
|
||||
mockData.value[index] = { ...form }
|
||||
}
|
||||
ElMessage.success('修改成功')
|
||||
}
|
||||
dialogVisible.value = false
|
||||
formEl.resetFields()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleDelete = (row: CustomerRole) => {
|
||||
ElMessageBox.confirm('确定删除该客户角色吗?', '删除确认', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'error'
|
||||
}).then(() => {
|
||||
const index = mockData.value.findIndex((item) => item.id === row.id)
|
||||
if (index !== -1) {
|
||||
mockData.value.splice(index, 1)
|
||||
}
|
||||
ElMessage.success('删除成功')
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-content {
|
||||
:deep(.el-checkbox) {
|
||||
margin-right: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
359
src/views/account-management/customer/index.vue
Normal file
359
src/views/account-management/customer/index.vue
Normal file
@@ -0,0 +1,359 @@
|
||||
<template>
|
||||
<ArtTableFullScreen>
|
||||
<div class="customer-management-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 type="primary" @click="handleSearch">查询</ElButton>
|
||||
<ElButton @click="exportExcel">导出excel</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>
|
||||
</ElCard>
|
||||
</div>
|
||||
</ArtTableFullScreen>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { h } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||
import { SearchChangeParams, SearchFormItem } from '@/types'
|
||||
|
||||
defineOptions({ name: 'CustomerManagement' })
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
// 定义表单搜索初始值
|
||||
const initialSearchState = {
|
||||
distributorName: '',
|
||||
distributorAccount: ''
|
||||
}
|
||||
|
||||
// 响应式表单数据
|
||||
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 mockData = [
|
||||
{
|
||||
id: 1,
|
||||
distributorName: '北京优享科技有限公司',
|
||||
distributorAccount: 'bjyx2024',
|
||||
totalCommission: 12560.8,
|
||||
instantCommission: 8320.5,
|
||||
pendingCommission: 4240.3,
|
||||
prepaidCommission: 2400.0,
|
||||
withdrawnCommission: 6800.2,
|
||||
withdrawingCommission: 1520.3,
|
||||
availableCommission: 2000.0
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
distributorName: '上海智联通信技术公司',
|
||||
distributorAccount: 'shzl2024',
|
||||
totalCommission: 18750.25,
|
||||
instantCommission: 12500.75,
|
||||
pendingCommission: 6249.5,
|
||||
prepaidCommission: 3600.0,
|
||||
withdrawnCommission: 8900.25,
|
||||
withdrawingCommission: 2100.0,
|
||||
availableCommission: 3750.0
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
distributorName: '广州物联网络有限公司',
|
||||
distributorAccount: 'gzwl2024',
|
||||
totalCommission: 9876.4,
|
||||
instantCommission: 6584.3,
|
||||
pendingCommission: 3292.1,
|
||||
prepaidCommission: 1800.0,
|
||||
withdrawnCommission: 4938.2,
|
||||
withdrawingCommission: 980.2,
|
||||
availableCommission: 1958.0
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
distributorName: '深圳云联科技股份公司',
|
||||
distributorAccount: 'szyl2024',
|
||||
totalCommission: 24500.6,
|
||||
instantCommission: 16333.73,
|
||||
pendingCommission: 8166.87,
|
||||
prepaidCommission: 4800.0,
|
||||
withdrawnCommission: 11225.3,
|
||||
withdrawingCommission: 2450.06,
|
||||
availableCommission: 4900.12
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
distributorName: '杭州通达网络服务公司',
|
||||
distributorAccount: 'hztd2024',
|
||||
totalCommission: 15200.35,
|
||||
instantCommission: 10133.57,
|
||||
pendingCommission: 5066.78,
|
||||
prepaidCommission: 2900.0,
|
||||
withdrawnCommission: 7600.18,
|
||||
withdrawingCommission: 1520.04,
|
||||
availableCommission: 3040.07
|
||||
}
|
||||
]
|
||||
|
||||
// 重置表单
|
||||
const handleReset = () => {
|
||||
Object.assign(formFilters, { ...initialSearchState })
|
||||
pagination.currentPage = 1
|
||||
getCustomerList()
|
||||
}
|
||||
|
||||
// 搜索处理
|
||||
const handleSearch = () => {
|
||||
console.log('搜索参数:', formFilters)
|
||||
pagination.currentPage = 1
|
||||
getCustomerList()
|
||||
}
|
||||
|
||||
// 表单项变更处理
|
||||
const handleFormChange = (params: SearchChangeParams): void => {
|
||||
console.log('表单项变更:', params)
|
||||
}
|
||||
|
||||
// 表单配置项
|
||||
const formItems: SearchFormItem[] = [
|
||||
{
|
||||
label: '代理商名称',
|
||||
prop: 'distributorName',
|
||||
type: 'input',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请输入代理商名称'
|
||||
},
|
||||
onChange: handleFormChange
|
||||
},
|
||||
{
|
||||
label: '代理商账号',
|
||||
prop: 'distributorAccount',
|
||||
type: 'input',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请输入代理商账号'
|
||||
},
|
||||
onChange: handleFormChange
|
||||
}
|
||||
]
|
||||
|
||||
// 列配置
|
||||
const columnOptions = [
|
||||
{ label: '勾选', type: 'selection' },
|
||||
{ label: '代理商名称', prop: 'distributorName' },
|
||||
{ label: '代理商账号', prop: 'distributorAccount' },
|
||||
{ label: '总佣金', prop: 'totalCommission' },
|
||||
{ label: '已秒返佣金', prop: 'instantCommission' },
|
||||
{ label: '未秒返佣金', prop: 'pendingCommission' },
|
||||
{ label: '预存佣金', prop: 'prepaidCommission' },
|
||||
{ label: '已提现佣金', prop: 'withdrawnCommission' },
|
||||
{ label: '提现中佣金', prop: 'withdrawingCommission' },
|
||||
{ label: '可提现佣金', prop: 'availableCommission' },
|
||||
{ label: '操作', prop: 'operation' }
|
||||
]
|
||||
|
||||
// 导出Excel
|
||||
const exportExcel = () => {
|
||||
if (selectedRows.value.length === 0) {
|
||||
ElMessage.warning('请先选择要导出的数据')
|
||||
return
|
||||
}
|
||||
ElMessage.success(`导出 ${selectedRows.value.length} 条客户记录`)
|
||||
}
|
||||
|
||||
// 查看提现详情
|
||||
const viewWithdrawDetail = (row: any) => {
|
||||
ElMessage.info(`查看 ${row.distributorName} 的提现详情`)
|
||||
}
|
||||
|
||||
// 查看佣金明细
|
||||
const viewCommissionDetail = (row: any) => {
|
||||
ElMessage.info(`查看 ${row.distributorName} 的佣金明细`)
|
||||
}
|
||||
|
||||
// 查看自动提现详情
|
||||
const viewAutoWithdrawDetail = (row: any) => {
|
||||
ElMessage.info(`查看 ${row.distributorName} 的自动提现详情`)
|
||||
}
|
||||
|
||||
// 动态列配置
|
||||
const { columnChecks, columns } = useCheckedColumns(() => [
|
||||
{ type: 'selection' },
|
||||
{
|
||||
prop: 'distributorName',
|
||||
label: '代理商名称',
|
||||
minWidth: 180
|
||||
},
|
||||
{
|
||||
prop: 'distributorAccount',
|
||||
label: '代理商账号',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
prop: 'totalCommission',
|
||||
label: '总佣金',
|
||||
width: 120,
|
||||
formatter: (row) => `¥${row.totalCommission.toFixed(2)}`
|
||||
},
|
||||
{
|
||||
prop: 'instantCommission',
|
||||
label: '已秒返佣金',
|
||||
width: 120,
|
||||
formatter: (row) => `¥${row.instantCommission.toFixed(2)}`
|
||||
},
|
||||
{
|
||||
prop: 'pendingCommission',
|
||||
label: '未秒返佣金',
|
||||
width: 120,
|
||||
formatter: (row) => `¥${row.pendingCommission.toFixed(2)}`
|
||||
},
|
||||
{
|
||||
prop: 'prepaidCommission',
|
||||
label: '预存佣金',
|
||||
width: 120,
|
||||
formatter: (row) => `¥${row.prepaidCommission.toFixed(2)}`
|
||||
},
|
||||
{
|
||||
prop: 'withdrawnCommission',
|
||||
label: '已提现佣金',
|
||||
width: 120,
|
||||
formatter: (row) => `¥${row.withdrawnCommission.toFixed(2)}`
|
||||
},
|
||||
{
|
||||
prop: 'withdrawingCommission',
|
||||
label: '提现中佣金',
|
||||
width: 120,
|
||||
formatter: (row) => `¥${row.withdrawingCommission.toFixed(2)}`
|
||||
},
|
||||
{
|
||||
prop: 'availableCommission',
|
||||
label: '可提现佣金',
|
||||
width: 120,
|
||||
formatter: (row) => `¥${row.availableCommission.toFixed(2)}`
|
||||
},
|
||||
{
|
||||
prop: 'operation',
|
||||
label: '操作',
|
||||
width: 200,
|
||||
formatter: (row: any) => {
|
||||
return h('div', { style: 'display: flex; gap: 5px; flex-wrap: wrap;' }, [
|
||||
h(ArtButtonTable, {
|
||||
type: 'view',
|
||||
text: '提现详情',
|
||||
onClick: () => viewWithdrawDetail(row)
|
||||
}),
|
||||
h(ArtButtonTable, {
|
||||
type: 'detail',
|
||||
text: '佣金明细',
|
||||
onClick: () => viewCommissionDetail(row)
|
||||
}),
|
||||
h(ArtButtonTable, {
|
||||
type: 'info',
|
||||
text: '自动提现详情',
|
||||
onClick: () => viewAutoWithdrawDetail(row)
|
||||
})
|
||||
])
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
onMounted(() => {
|
||||
getCustomerList()
|
||||
})
|
||||
|
||||
// 获取客户列表
|
||||
const getCustomerList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
// 模拟API调用
|
||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||
|
||||
const startIndex = (pagination.currentPage - 1) * pagination.pageSize
|
||||
const endIndex = startIndex + pagination.pageSize
|
||||
const paginatedData = mockData.slice(startIndex, endIndex)
|
||||
|
||||
tableData.value = paginatedData
|
||||
pagination.total = mockData.length
|
||||
loading.value = false
|
||||
} catch (error) {
|
||||
console.error('获取客户列表失败:', error)
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleRefresh = () => {
|
||||
getCustomerList()
|
||||
}
|
||||
|
||||
// 处理表格行选择变化
|
||||
const handleSelectionChange = (selection: any[]) => {
|
||||
selectedRows.value = selection
|
||||
}
|
||||
|
||||
// 处理表格分页变化
|
||||
const handleSizeChange = (newPageSize: number) => {
|
||||
pagination.pageSize = newPageSize
|
||||
getCustomerList()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (newCurrentPage: number) => {
|
||||
pagination.currentPage = newCurrentPage
|
||||
getCustomerList()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.customer-management-page {
|
||||
// 可以添加特定样式
|
||||
}
|
||||
</style>
|
||||
498
src/views/account-management/enterprise-customer/index.vue
Normal file
498
src/views/account-management/enterprise-customer/index.vue
Normal file
@@ -0,0 +1,498 @@
|
||||
<template>
|
||||
<div class="page-content">
|
||||
<!-- 搜索和操作区 -->
|
||||
<ElRow :gutter="12">
|
||||
<ElCol :xs="24" :sm="12" :lg="6">
|
||||
<ElInput v-model="searchQuery" placeholder="企业名称/联系人" clearable />
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12" :lg="6">
|
||||
<ElSelect v-model="statusFilter" placeholder="状态筛选" clearable style="width: 100%">
|
||||
<ElOption label="全部" value="" />
|
||||
<ElOption label="正常" value="active" />
|
||||
<ElOption label="禁用" value="disabled" />
|
||||
<ElOption label="待审核" value="pending" />
|
||||
</ElSelect>
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12" :lg="6">
|
||||
<ElSelect v-model="roleFilter" placeholder="角色筛选" clearable style="width: 100%">
|
||||
<ElOption label="全部" value="" />
|
||||
<ElOption label="企业管理员" value="admin" />
|
||||
<ElOption label="企业用户" value="user" />
|
||||
</ElSelect>
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12" :lg="6" class="el-col2">
|
||||
<ElButton v-ripple @click="handleSearch">搜索</ElButton>
|
||||
<ElButton v-ripple type="primary" @click="showDialog('add')">新增企业客户</ElButton>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
|
||||
<!-- 企业客户列表 -->
|
||||
<ArtTable :data="filteredData" index style="margin-top: 20px">
|
||||
<template #default>
|
||||
<ElTableColumn label="企业名称" prop="enterpriseName" min-width="180" show-overflow-tooltip />
|
||||
<ElTableColumn label="统一社会信用代码" prop="creditCode" width="180" />
|
||||
<ElTableColumn label="联系人" prop="contactPerson" width="120" />
|
||||
<ElTableColumn label="联系电话" prop="contactPhone" width="130" />
|
||||
<ElTableColumn label="所属角色" prop="roleName" width="120">
|
||||
<template #default="scope">
|
||||
<ElTag>{{ scope.row.roleName }}</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="卡片数量" prop="cardCount" width="100">
|
||||
<template #default="scope">
|
||||
<span style="color: var(--el-color-primary)">{{ scope.row.cardCount }}</span>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="设备数量" prop="deviceCount" width="100">
|
||||
<template #default="scope">
|
||||
<span style="color: var(--el-color-success)">{{ scope.row.deviceCount }}</span>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="账户余额" prop="balance" width="120">
|
||||
<template #default="scope"> ¥{{ scope.row.balance.toFixed(2) }} </template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="状态" prop="status" width="100">
|
||||
<template #default="scope">
|
||||
<ElTag v-if="scope.row.status === 'active'" type="success">正常</ElTag>
|
||||
<ElTag v-else-if="scope.row.status === 'disabled'" type="danger">禁用</ElTag>
|
||||
<ElTag v-else-if="scope.row.status === 'pending'" type="warning">待审核</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="创建时间" prop="createTime" width="180" />
|
||||
<ElTableColumn fixed="right" label="操作" width="260">
|
||||
<template #default="scope">
|
||||
<el-button link :icon="View" @click="viewDetail(scope.row)">详情</el-button>
|
||||
<el-button link @click="showDialog('edit', scope.row)">编辑</el-button>
|
||||
<el-button
|
||||
link
|
||||
:type="scope.row.status === 'active' ? 'warning' : 'success'"
|
||||
@click="toggleStatus(scope.row)"
|
||||
>
|
||||
{{ scope.row.status === 'active' ? '禁用' : '启用' }}
|
||||
</el-button>
|
||||
<el-button link type="danger" @click="handleDelete(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
</template>
|
||||
</ArtTable>
|
||||
|
||||
<!-- 新增/编辑企业客户对话框 -->
|
||||
<ElDialog
|
||||
v-model="dialogVisible"
|
||||
:title="dialogType === 'add' ? '新增企业客户' : '编辑企业客户'"
|
||||
width="700px"
|
||||
align-center
|
||||
>
|
||||
<ElForm ref="formRef" :model="form" :rules="rules" label-width="140px">
|
||||
<ElDivider content-position="left">企业信息</ElDivider>
|
||||
|
||||
<ElFormItem label="企业名称" prop="enterpriseName">
|
||||
<ElInput v-model="form.enterpriseName" placeholder="请输入企业名称" />
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="统一社会信用代码" prop="creditCode">
|
||||
<ElInput v-model="form.creditCode" placeholder="请输入统一社会信用代码" maxlength="18" />
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="企业地址" prop="address">
|
||||
<ElInput v-model="form.address" placeholder="请输入企业地址" />
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="营业执照" prop="businessLicense">
|
||||
<ElUpload
|
||||
:action="uploadUrl"
|
||||
:limit="1"
|
||||
list-type="picture-card"
|
||||
accept="image/*"
|
||||
>
|
||||
<el-icon><Plus /></el-icon>
|
||||
</ElUpload>
|
||||
<div style="color: var(--el-text-color-secondary); font-size: 12px">支持 JPG、PNG 格式</div>
|
||||
</ElFormItem>
|
||||
|
||||
<ElDivider content-position="left">联系人信息</ElDivider>
|
||||
|
||||
<ElFormItem label="联系人" prop="contactPerson">
|
||||
<ElInput v-model="form.contactPerson" placeholder="请输入联系人姓名" />
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="联系电话" prop="contactPhone">
|
||||
<ElInput v-model="form.contactPhone" placeholder="请输入联系电话" maxlength="11" />
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="联系邮箱" prop="contactEmail">
|
||||
<ElInput v-model="form.contactEmail" placeholder="请输入联系邮箱" />
|
||||
</ElFormItem>
|
||||
|
||||
<ElDivider content-position="left">账号配置</ElDivider>
|
||||
|
||||
<ElFormItem label="登录账号" prop="username">
|
||||
<ElInput v-model="form.username" placeholder="请输入登录账号" :disabled="dialogType === 'edit'" />
|
||||
<div style="color: var(--el-text-color-secondary); font-size: 12px">只能登录企业端</div>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem v-if="dialogType === 'add'" label="登录密码" prop="password">
|
||||
<ElInput v-model="form.password" type="password" placeholder="请输入登录密码" show-password />
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="分配角色" prop="roleId">
|
||||
<ElSelect v-model="form.roleId" placeholder="请选择客户角色" style="width: 100%">
|
||||
<ElOption
|
||||
v-for="role in availableRoles"
|
||||
:key="role.id"
|
||||
:label="role.roleName"
|
||||
:value="role.id"
|
||||
/>
|
||||
</ElSelect>
|
||||
<div style="color: var(--el-text-color-secondary); font-size: 12px">角色决定企业客户的能力边界</div>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="初始余额" prop="initialBalance">
|
||||
<ElInputNumber v-model="form.initialBalance" :min="0" :precision="2" style="width: 100%" />
|
||||
<span style="margin-left: 8px">元</span>
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="备注" prop="remark">
|
||||
<ElInput v-model="form.remark" type="textarea" :rows="3" placeholder="请输入备注信息" />
|
||||
</ElFormItem>
|
||||
|
||||
<ElFormItem label="状态">
|
||||
<ElSwitch v-model="form.status" active-value="active" inactive-value="disabled" />
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton @click="dialogVisible = false">取消</ElButton>
|
||||
<ElButton type="primary" @click="handleSubmit(formRef)">提交</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
|
||||
<!-- 企业详情对话框 -->
|
||||
<ElDialog v-model="detailDialogVisible" title="企业客户详情" width="800px" align-center>
|
||||
<ElDescriptions :column="2" border>
|
||||
<ElDescriptionsItem label="企业名称">{{ currentDetail.enterpriseName }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="统一社会信用代码">{{ currentDetail.creditCode }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="联系人">{{ currentDetail.contactPerson }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="联系电话">{{ currentDetail.contactPhone }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="联系邮箱">{{ currentDetail.contactEmail }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="企业地址" :span="2">{{ currentDetail.address }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="所属角色">
|
||||
<ElTag>{{ currentDetail.roleName }}</ElTag>
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="登录账号">{{ currentDetail.username }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="卡片数量">
|
||||
<span style="color: var(--el-color-primary)">{{ currentDetail.cardCount }}</span>
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="设备数量">
|
||||
<span style="color: var(--el-color-success)">{{ currentDetail.deviceCount }}</span>
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="账户余额">¥{{ currentDetail.balance.toFixed(2) }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="状态">
|
||||
<ElTag v-if="currentDetail.status === 'active'" type="success">正常</ElTag>
|
||||
<ElTag v-else-if="currentDetail.status === 'disabled'" type="danger">禁用</ElTag>
|
||||
<ElTag v-else-if="currentDetail.status === 'pending'" type="warning">待审核</ElTag>
|
||||
</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="创建时间">{{ currentDetail.createTime }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="最后登录">{{ currentDetail.lastLoginTime || '未登录' }}</ElDescriptionsItem>
|
||||
<ElDescriptionsItem label="备注" :span="2">{{ currentDetail.remark || '无' }}</ElDescriptionsItem>
|
||||
</ElDescriptions>
|
||||
|
||||
<template #footer>
|
||||
<ElButton @click="detailDialogVisible = false">关闭</ElButton>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { View, Plus } from '@element-plus/icons-vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
|
||||
defineOptions({ name: 'EnterpriseCustomer' })
|
||||
|
||||
interface EnterpriseCustomer {
|
||||
id: string
|
||||
enterpriseName: string
|
||||
creditCode: string
|
||||
address: string
|
||||
contactPerson: string
|
||||
contactPhone: string
|
||||
contactEmail: string
|
||||
username: string
|
||||
roleId: string
|
||||
roleName: string
|
||||
cardCount: number
|
||||
deviceCount: number
|
||||
balance: number
|
||||
status: 'active' | 'disabled' | 'pending'
|
||||
createTime: string
|
||||
lastLoginTime?: string
|
||||
remark?: string
|
||||
}
|
||||
|
||||
const searchQuery = ref('')
|
||||
const statusFilter = ref('')
|
||||
const roleFilter = ref('')
|
||||
const dialogVisible = ref(false)
|
||||
const detailDialogVisible = ref(false)
|
||||
const dialogType = ref<'add' | 'edit'>('add')
|
||||
const formRef = ref<FormInstance>()
|
||||
const uploadUrl = ref('/api/upload/image')
|
||||
|
||||
const form = reactive({
|
||||
enterpriseName: '',
|
||||
creditCode: '',
|
||||
address: '',
|
||||
businessLicense: '',
|
||||
contactPerson: '',
|
||||
contactPhone: '',
|
||||
contactEmail: '',
|
||||
username: '',
|
||||
password: '',
|
||||
roleId: '',
|
||||
initialBalance: 0,
|
||||
remark: '',
|
||||
status: 'active'
|
||||
})
|
||||
|
||||
const rules = reactive<FormRules>({
|
||||
enterpriseName: [{ required: true, message: '请输入企业名称', trigger: 'blur' }],
|
||||
creditCode: [
|
||||
{ required: true, message: '请输入统一社会信用代码', trigger: 'blur' },
|
||||
{ len: 18, message: '统一社会信用代码为18位', trigger: 'blur' }
|
||||
],
|
||||
contactPerson: [{ required: true, message: '请输入联系人', trigger: 'blur' }],
|
||||
contactPhone: [
|
||||
{ required: true, message: '请输入联系电话', trigger: 'blur' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
|
||||
],
|
||||
contactEmail: [
|
||||
{ required: true, message: '请输入联系邮箱', trigger: 'blur' },
|
||||
{ type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
|
||||
],
|
||||
username: [{ required: true, message: '请输入登录账号', trigger: 'blur' }],
|
||||
password: [
|
||||
{ required: true, message: '请输入登录密码', trigger: 'blur' },
|
||||
{ min: 6, max: 20, message: '密码长度为6-20位', trigger: 'blur' }
|
||||
],
|
||||
roleId: [{ required: true, message: '请选择客户角色', trigger: 'change' }]
|
||||
})
|
||||
|
||||
const availableRoles = ref([
|
||||
{ id: '1', roleName: '企业管理员' },
|
||||
{ id: '2', roleName: '企业用户' },
|
||||
{ id: '3', roleName: '企业财务' }
|
||||
])
|
||||
|
||||
const mockData = ref<EnterpriseCustomer[]>([
|
||||
{
|
||||
id: '1',
|
||||
enterpriseName: '深圳市科技有限公司',
|
||||
creditCode: '91440300MA5DXXXX01',
|
||||
address: '深圳市南山区科技园',
|
||||
contactPerson: '张经理',
|
||||
contactPhone: '13800138000',
|
||||
contactEmail: 'zhang@company.com',
|
||||
username: 'shenzhen_tech',
|
||||
roleId: '1',
|
||||
roleName: '企业管理员',
|
||||
cardCount: 500,
|
||||
deviceCount: 450,
|
||||
balance: 15000.00,
|
||||
status: 'active',
|
||||
createTime: '2026-01-01 10:00:00',
|
||||
lastLoginTime: '2026-01-09 09:30:00',
|
||||
remark: '重要客户'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
enterpriseName: '北京智能制造有限公司',
|
||||
creditCode: '91110000MA5DXXXX02',
|
||||
address: '北京市海淀区中关村',
|
||||
contactPerson: '李总监',
|
||||
contactPhone: '13900139000',
|
||||
contactEmail: 'li@manufacturing.com',
|
||||
username: 'beijing_smart',
|
||||
roleId: '1',
|
||||
roleName: '企业管理员',
|
||||
cardCount: 800,
|
||||
deviceCount: 750,
|
||||
balance: 28000.00,
|
||||
status: 'active',
|
||||
createTime: '2026-01-03 14:00:00',
|
||||
lastLoginTime: '2026-01-09 08:15:00'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
enterpriseName: '上海物联网科技公司',
|
||||
creditCode: '91310000MA5DXXXX03',
|
||||
address: '上海市浦东新区张江高科技园区',
|
||||
contactPerson: '王主管',
|
||||
contactPhone: '13700137000',
|
||||
contactEmail: 'wang@iot-shanghai.com',
|
||||
username: 'shanghai_iot',
|
||||
roleId: '2',
|
||||
roleName: '企业用户',
|
||||
cardCount: 300,
|
||||
deviceCount: 280,
|
||||
balance: 8500.00,
|
||||
status: 'pending',
|
||||
createTime: '2026-01-08 16:00:00'
|
||||
}
|
||||
])
|
||||
|
||||
const currentDetail = ref<EnterpriseCustomer>({
|
||||
id: '',
|
||||
enterpriseName: '',
|
||||
creditCode: '',
|
||||
address: '',
|
||||
contactPerson: '',
|
||||
contactPhone: '',
|
||||
contactEmail: '',
|
||||
username: '',
|
||||
roleId: '',
|
||||
roleName: '',
|
||||
cardCount: 0,
|
||||
deviceCount: 0,
|
||||
balance: 0,
|
||||
status: 'active',
|
||||
createTime: ''
|
||||
})
|
||||
|
||||
const filteredData = computed(() => {
|
||||
let data = mockData.value
|
||||
|
||||
if (searchQuery.value) {
|
||||
data = data.filter(
|
||||
(item) =>
|
||||
item.enterpriseName.includes(searchQuery.value) ||
|
||||
item.contactPerson.includes(searchQuery.value) ||
|
||||
item.contactPhone.includes(searchQuery.value)
|
||||
)
|
||||
}
|
||||
|
||||
if (statusFilter.value) {
|
||||
data = data.filter((item) => item.status === statusFilter.value)
|
||||
}
|
||||
|
||||
if (roleFilter.value) {
|
||||
data = data.filter((item) => item.roleId === roleFilter.value)
|
||||
}
|
||||
|
||||
return data
|
||||
})
|
||||
|
||||
const handleSearch = () => {}
|
||||
|
||||
const showDialog = (type: 'add' | 'edit', row?: EnterpriseCustomer) => {
|
||||
dialogType.value = type
|
||||
dialogVisible.value = true
|
||||
if (type === 'edit' && row) {
|
||||
Object.assign(form, {
|
||||
enterpriseName: row.enterpriseName,
|
||||
creditCode: row.creditCode,
|
||||
address: row.address,
|
||||
contactPerson: row.contactPerson,
|
||||
contactPhone: row.contactPhone,
|
||||
contactEmail: row.contactEmail,
|
||||
username: row.username,
|
||||
roleId: row.roleId,
|
||||
initialBalance: row.balance,
|
||||
remark: row.remark,
|
||||
status: row.status
|
||||
})
|
||||
} else {
|
||||
Object.assign(form, {
|
||||
enterpriseName: '',
|
||||
creditCode: '',
|
||||
address: '',
|
||||
businessLicense: '',
|
||||
contactPerson: '',
|
||||
contactPhone: '',
|
||||
contactEmail: '',
|
||||
username: '',
|
||||
password: '',
|
||||
roleId: '',
|
||||
initialBalance: 0,
|
||||
remark: '',
|
||||
status: 'active'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
await formEl.validate((valid) => {
|
||||
if (valid) {
|
||||
if (dialogType.value === 'add') {
|
||||
const selectedRole = availableRoles.value.find((r) => r.id === form.roleId)
|
||||
mockData.value.unshift({
|
||||
id: Date.now().toString(),
|
||||
enterpriseName: form.enterpriseName,
|
||||
creditCode: form.creditCode,
|
||||
address: form.address,
|
||||
contactPerson: form.contactPerson,
|
||||
contactPhone: form.contactPhone,
|
||||
contactEmail: form.contactEmail,
|
||||
username: form.username,
|
||||
roleId: form.roleId,
|
||||
roleName: selectedRole?.roleName || '',
|
||||
cardCount: 0,
|
||||
deviceCount: 0,
|
||||
balance: form.initialBalance,
|
||||
status: form.status as any,
|
||||
createTime: new Date().toLocaleString('zh-CN'),
|
||||
remark: form.remark
|
||||
})
|
||||
ElMessage.success('企业客户创建成功')
|
||||
} else {
|
||||
ElMessage.success('企业客户更新成功')
|
||||
}
|
||||
dialogVisible.value = false
|
||||
formEl.resetFields()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const viewDetail = (row: EnterpriseCustomer) => {
|
||||
currentDetail.value = { ...row }
|
||||
detailDialogVisible.value = true
|
||||
}
|
||||
|
||||
const toggleStatus = (row: EnterpriseCustomer) => {
|
||||
const action = row.status === 'active' ? '禁用' : '启用'
|
||||
ElMessageBox.confirm(`确定${action}该企业客户吗?`, `${action}确认`, {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
row.status = row.status === 'active' ? 'disabled' : 'active'
|
||||
ElMessage.success(`${action}成功`)
|
||||
})
|
||||
}
|
||||
|
||||
const handleDelete = (row: EnterpriseCustomer) => {
|
||||
ElMessageBox.confirm('删除后无法恢复,确定要删除该企业客户吗?', '删除确认', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'error'
|
||||
}).then(() => {
|
||||
const index = mockData.value.findIndex((item) => item.id === row.id)
|
||||
if (index !== -1) mockData.value.splice(index, 1)
|
||||
ElMessage.success('删除成功')
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-content {
|
||||
:deep(.el-upload-list--picture-card .el-upload-list__item) {
|
||||
width: 148px;
|
||||
height: 148px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
669
src/views/account-management/platform-account/index.vue
Normal file
669
src/views/account-management/platform-account/index.vue
Normal file
@@ -0,0 +1,669 @@
|
||||
<template>
|
||||
<ArtTableFullScreen>
|
||||
<div class="platform-account-page" id="table-full-screen">
|
||||
<!-- 搜索栏 -->
|
||||
<ArtSearchBar
|
||||
v-model:filter="searchForm"
|
||||
:items="searchFormItems"
|
||||
: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="120px">
|
||||
<ElFormItem label="账号名称" prop="username">
|
||||
<ElInput
|
||||
v-model="formData.username"
|
||||
placeholder="请输入账号名称"
|
||||
:disabled="dialogType === 'edit'"
|
||||
/>
|
||||
</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%"
|
||||
:disabled="dialogType === 'edit'"
|
||||
>
|
||||
<ElOption label="超级管理员" :value="1" />
|
||||
<ElOption label="平台用户" :value="2" />
|
||||
<ElOption label="代理账号" :value="3" />
|
||||
<ElOption label="企业账号" :value="4" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem v-if="formData.user_type === 3" label="关联店铺ID" prop="shop_id">
|
||||
<ElInputNumber
|
||||
v-model="formData.shop_id"
|
||||
:min="1"
|
||||
placeholder="请输入店铺ID"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem v-if="formData.user_type === 4" label="关联企业ID" prop="enterprise_id">
|
||||
<ElInputNumber
|
||||
v-model="formData.enterprise_id"
|
||||
:min="1"
|
||||
placeholder="请输入企业ID"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem v-if="dialogType === 'edit'" label="状态">
|
||||
<ElSwitch
|
||||
v-model="formData.status"
|
||||
:active-value="CommonStatus.ENABLED"
|
||||
:inactive-value="CommonStatus.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" title="分配角色" width="500px">
|
||||
<ElCheckboxGroup v-model="selectedRoles">
|
||||
<div v-for="role in allRoles" :key="role.ID" style="margin-bottom: 12px">
|
||||
<ElCheckbox :label="role.ID">{{ role.role_name }}</ElCheckbox>
|
||||
</div>
|
||||
</ElCheckboxGroup>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton @click="roleDialogVisible = false">取消</ElButton>
|
||||
<ElButton type="primary" @click="handleAssignRoles" :loading="roleSubmitLoading"
|
||||
>提交</ElButton
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
|
||||
<!-- 修改密码对话框 -->
|
||||
<ElDialog v-model="passwordDialogVisible" title="修改密码" width="400px">
|
||||
<ElForm ref="passwordFormRef" :model="passwordForm" :rules="passwordRules">
|
||||
<ElFormItem label="新密码" prop="new_password">
|
||||
<ElInput
|
||||
v-model="passwordForm.new_password"
|
||||
type="password"
|
||||
placeholder="请输入新密码"
|
||||
show-password
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton @click="passwordDialogVisible = false">取消</ElButton>
|
||||
<ElButton
|
||||
type="primary"
|
||||
@click="handleChangePassword"
|
||||
:loading="passwordSubmitLoading"
|
||||
>提交</ElButton
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</ElCard>
|
||||
</div>
|
||||
</ArtTableFullScreen>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { h } from 'vue'
|
||||
import { FormInstance, ElMessage, ElMessageBox, ElTag, ElSwitch } from 'element-plus'
|
||||
import type { FormRules } from 'element-plus'
|
||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||
import { PlatformAccountService, RoleService } from '@/api/modules'
|
||||
import type { SearchFormItem } from '@/types'
|
||||
import type { PlatformRole, PlatformAccountResponse } from '@/types/api'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import { CommonStatus, getStatusText, STATUS_SELECT_OPTIONS } from '@/config/constants'
|
||||
|
||||
defineOptions({ name: 'PlatformAccount' }) // 定义组件名称,用于 KeepAlive 缓存控制
|
||||
|
||||
const dialogType = ref('add')
|
||||
const dialogVisible = ref(false)
|
||||
const roleDialogVisible = ref(false)
|
||||
const passwordDialogVisible = ref(false)
|
||||
const loading = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
const roleSubmitLoading = ref(false)
|
||||
const passwordSubmitLoading = ref(false)
|
||||
const currentAccountId = ref<number>(0)
|
||||
const selectedRoles = ref<number[]>([])
|
||||
const allRoles = ref<PlatformRole[]>([])
|
||||
|
||||
// 定义表单搜索初始值
|
||||
const initialSearchState = {
|
||||
username: '',
|
||||
phone: '',
|
||||
status: undefined as number | undefined
|
||||
}
|
||||
|
||||
// 响应式表单数据
|
||||
const searchForm = reactive({ ...initialSearchState })
|
||||
|
||||
const pagination = reactive({
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
total: 0
|
||||
})
|
||||
|
||||
// 表格数据
|
||||
const tableData = ref<PlatformAccountResponse[]>([])
|
||||
|
||||
// 表格实例引用
|
||||
const tableRef = ref()
|
||||
|
||||
// 选中的行数据
|
||||
const selectedRows = ref<any[]>([])
|
||||
|
||||
// 重置表单
|
||||
const handleReset = () => {
|
||||
Object.assign(searchForm, { ...initialSearchState })
|
||||
pagination.currentPage = 1 // 重置到第一页
|
||||
getAccountList()
|
||||
}
|
||||
|
||||
// 搜索处理
|
||||
const handleSearch = () => {
|
||||
console.log('搜索参数:', searchForm)
|
||||
pagination.currentPage = 1 // 搜索时重置到第一页
|
||||
getAccountList()
|
||||
}
|
||||
|
||||
// 表单配置项
|
||||
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',
|
||||
options: STATUS_SELECT_OPTIONS,
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请选择状态'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
// 列配置
|
||||
const columnOptions = [
|
||||
{ label: 'ID', prop: 'ID' },
|
||||
{ label: '账号名称', prop: 'username' },
|
||||
{ label: '手机号', prop: 'phone' },
|
||||
{ label: '账号类型', prop: 'user_type' },
|
||||
{ label: '状态', prop: 'status' },
|
||||
{ label: '创建时间', prop: 'CreatedAt' },
|
||||
{ label: '操作', prop: 'operation' }
|
||||
]
|
||||
|
||||
// 显示对话框
|
||||
const showDialog = (type: string, row?: PlatformAccountResponse) => {
|
||||
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.enterprise_id = row.enterprise_id || null
|
||||
formData.shop_id = row.shop_id || null
|
||||
formData.status = row.status
|
||||
formData.password = ''
|
||||
} else {
|
||||
formData.id = 0
|
||||
formData.username = ''
|
||||
formData.phone = ''
|
||||
formData.user_type = 2
|
||||
formData.enterprise_id = null
|
||||
formData.shop_id = null
|
||||
formData.status = CommonStatus.ENABLED
|
||||
formData.password = ''
|
||||
}
|
||||
}
|
||||
|
||||
// 删除账号
|
||||
const deleteAccount = (row: PlatformAccountResponse) => {
|
||||
ElMessageBox.confirm(`确定要删除平台账号 ${row.username} 吗?`, '删除平台账号', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'error'
|
||||
})
|
||||
.then(async () => {
|
||||
try {
|
||||
await PlatformAccountService.deletePlatformAccount(row.ID)
|
||||
ElMessage.success('删除成功')
|
||||
getAccountList()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// 账号取消删除
|
||||
})
|
||||
}
|
||||
|
||||
// 显示修改密码对话框
|
||||
const showPasswordDialog = (row: PlatformAccountResponse) => {
|
||||
currentAccountId.value = row.ID
|
||||
passwordForm.new_password = ''
|
||||
passwordDialogVisible.value = true
|
||||
if (passwordFormRef.value) {
|
||||
passwordFormRef.value.resetFields()
|
||||
}
|
||||
}
|
||||
|
||||
// 动态列配置
|
||||
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: PlatformAccountResponse) => {
|
||||
const typeMap: Record<number, string> = {
|
||||
1: '超级管理员',
|
||||
2: '平台用户',
|
||||
3: '代理账号',
|
||||
4: '企业账号'
|
||||
}
|
||||
return typeMap[row.user_type] || '-'
|
||||
}
|
||||
},
|
||||
{
|
||||
prop: 'status',
|
||||
label: '状态',
|
||||
width: 100,
|
||||
formatter: (row: PlatformAccountResponse) => {
|
||||
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: PlatformAccountResponse) => formatDateTime(row.CreatedAt)
|
||||
},
|
||||
{
|
||||
prop: 'operation',
|
||||
label: '操作',
|
||||
width: 240,
|
||||
fixed: 'right',
|
||||
formatter: (row: PlatformAccountResponse) => {
|
||||
return h('div', { style: 'display: flex; gap: 8px;' }, [
|
||||
h(ArtButtonTable, {
|
||||
icon: '',
|
||||
onClick: () => showRoleDialog(row)
|
||||
}),
|
||||
h(ArtButtonTable, {
|
||||
icon: '',
|
||||
onClick: () => showPasswordDialog(row)
|
||||
}),
|
||||
h(ArtButtonTable, {
|
||||
type: 'edit',
|
||||
onClick: () => showDialog('edit', row)
|
||||
}),
|
||||
h(ArtButtonTable, {
|
||||
type: 'delete',
|
||||
onClick: () => deleteAccount(row)
|
||||
})
|
||||
])
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
// 表单实例
|
||||
const formRef = ref<FormInstance>()
|
||||
const passwordFormRef = ref<FormInstance>()
|
||||
|
||||
// 表单数据
|
||||
const formData = reactive({
|
||||
id: 0,
|
||||
username: '',
|
||||
password: '',
|
||||
phone: '',
|
||||
user_type: 2,
|
||||
enterprise_id: null as number | null,
|
||||
shop_id: null as number | null,
|
||||
status: CommonStatus.ENABLED
|
||||
})
|
||||
|
||||
// 密码表单数据
|
||||
const passwordForm = reactive({
|
||||
new_password: ''
|
||||
})
|
||||
|
||||
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: PlatformAccountResponse) => {
|
||||
currentAccountId.value = row.ID
|
||||
selectedRoles.value = []
|
||||
|
||||
// 先加载当前账号的角色,再打开对话框
|
||||
try {
|
||||
const res = await PlatformAccountService.getPlatformAccountRoles(row.ID)
|
||||
if (res.code === 0) {
|
||||
// 提取角色ID数组
|
||||
const roles = res.data || []
|
||||
selectedRoles.value = roles.map((role: any) => role.ID)
|
||||
// 数据加载完成后再打开对话框
|
||||
roleDialogVisible.value = true
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取账号角色失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 提交分配角色
|
||||
const handleAssignRoles = async () => {
|
||||
roleSubmitLoading.value = true
|
||||
try {
|
||||
await PlatformAccountService.assignRolesToPlatformAccount(currentAccountId.value, {
|
||||
role_ids: selectedRoles.value
|
||||
})
|
||||
ElMessage.success('分配角色成功')
|
||||
roleDialogVisible.value = false
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
roleSubmitLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 提交修改密码
|
||||
const handleChangePassword = async () => {
|
||||
if (!passwordFormRef.value) return
|
||||
|
||||
await passwordFormRef.value.validate(async (valid) => {
|
||||
if (valid) {
|
||||
passwordSubmitLoading.value = true
|
||||
try {
|
||||
await PlatformAccountService.changePlatformAccountPassword(currentAccountId.value, {
|
||||
new_password: passwordForm.new_password
|
||||
})
|
||||
ElMessage.success('修改密码成功')
|
||||
passwordDialogVisible.value = false
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
passwordSubmitLoading.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取账号列表
|
||||
const getAccountList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
page: pagination.currentPage,
|
||||
page_size: pagination.pageSize,
|
||||
username: searchForm.username || undefined,
|
||||
phone: searchForm.phone || undefined,
|
||||
status: searchForm.status
|
||||
}
|
||||
const res = await PlatformAccountService.getPlatformAccounts(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' }],
|
||||
shop_id: [
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
if (formData.user_type === 3 && !value) {
|
||||
callback(new Error('代理账号必须关联店铺ID'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
enterprise_id: [
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
if (formData.user_type === 4 && !value) {
|
||||
callback(new Error('企业账号必须关联企业ID'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// 密码验证规则
|
||||
const passwordRules = reactive<FormRules>({
|
||||
new_password: [
|
||||
{ required: true, message: '请输入新密码', trigger: 'blur' },
|
||||
{ min: 8, max: 32, message: '密码长度为 8-32 个字符', trigger: 'blur' }
|
||||
]
|
||||
})
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async () => {
|
||||
if (!formRef.value) return
|
||||
|
||||
await formRef.value.validate(async (valid) => {
|
||||
if (valid) {
|
||||
submitLoading.value = true
|
||||
try {
|
||||
if (dialogType.value === 'add') {
|
||||
const data: any = {
|
||||
username: formData.username,
|
||||
password: formData.password,
|
||||
phone: formData.phone,
|
||||
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 PlatformAccountService.createPlatformAccount(data)
|
||||
ElMessage.success('新增成功')
|
||||
} else {
|
||||
const data: any = {
|
||||
username: formData.username,
|
||||
phone: formData.phone,
|
||||
status: formData.status
|
||||
}
|
||||
|
||||
await PlatformAccountService.updatePlatformAccount(formData.id, data)
|
||||
ElMessage.success('更新成功')
|
||||
}
|
||||
|
||||
dialogVisible.value = false
|
||||
getAccountList()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 处理表格分页变化
|
||||
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 PlatformAccountService.updatePlatformAccount(row.ID, { status: newStatus })
|
||||
ElMessage.success('状态切换成功')
|
||||
} catch (error) {
|
||||
// 切换失败,恢复原状态
|
||||
row.status = oldStatus
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.platform-account-page {
|
||||
// 平台账号管理页面样式
|
||||
}
|
||||
</style>
|
||||
482
src/views/account-management/shop-account/index.vue
Normal file
482
src/views/account-management/shop-account/index.vue
Normal file
@@ -0,0 +1,482 @@
|
||||
<template>
|
||||
<ArtTableFullScreen>
|
||||
<div class="shop-account-page" id="table-full-screen">
|
||||
<!-- 搜索栏 -->
|
||||
<ArtSearchBar
|
||||
v-model:filter="searchForm"
|
||||
:items="searchFormItems"
|
||||
@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="120px">
|
||||
<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="店铺ID" prop="shop_id">
|
||||
<ElInputNumber v-model="formData.shop_id" :min="1" placeholder="请输入店铺ID" style="width: 100%" />
|
||||
</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="passwordDialogVisible"
|
||||
title="重置密码"
|
||||
width="400px"
|
||||
>
|
||||
<ElForm ref="passwordFormRef" :model="passwordForm" :rules="passwordRules">
|
||||
<ElFormItem label="新密码" prop="new_password">
|
||||
<ElInput v-model="passwordForm.new_password" type="password" placeholder="请输入新密码" show-password />
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<ElButton @click="passwordDialogVisible = false">取消</ElButton>
|
||||
<ElButton type="primary" @click="handleChangePassword" :loading="passwordSubmitLoading">提交</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
</ElCard>
|
||||
</div>
|
||||
</ArtTableFullScreen>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { h } from 'vue'
|
||||
import { FormInstance, ElMessage, ElMessageBox, ElSwitch } from 'element-plus'
|
||||
import type { FormRules } from 'element-plus'
|
||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||
import ArtButtonTable from '@/components/core/forms/ArtButtonTable.vue'
|
||||
import { ShopAccountService } from '@/api/modules'
|
||||
import type { SearchFormItem } from '@/types'
|
||||
import type { ShopAccountResponse } from '@/types/api'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import { CommonStatus, getStatusText, STATUS_SELECT_OPTIONS } from '@/config/constants'
|
||||
|
||||
defineOptions({ name: 'ShopAccount' }) // 定义组件名称,用于 KeepAlive 缓存控制
|
||||
|
||||
const dialogType = ref('add')
|
||||
const dialogVisible = ref(false)
|
||||
const passwordDialogVisible = ref(false)
|
||||
const loading = ref(false)
|
||||
const submitLoading = ref(false)
|
||||
const passwordSubmitLoading = ref(false)
|
||||
const currentAccountId = ref<number>(0)
|
||||
|
||||
// 定义表单搜索初始值
|
||||
const initialSearchState = {
|
||||
username: '',
|
||||
phone: '',
|
||||
shop_id: undefined as number | undefined,
|
||||
status: undefined as number | undefined
|
||||
}
|
||||
|
||||
// 响应式表单数据
|
||||
const searchForm = reactive({ ...initialSearchState })
|
||||
|
||||
const pagination = reactive({
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
total: 0
|
||||
})
|
||||
|
||||
// 表格数据
|
||||
const tableData = ref<ShopAccountResponse[]>([])
|
||||
|
||||
// 表格实例引用
|
||||
const tableRef = ref()
|
||||
|
||||
// 选中的行数据
|
||||
const selectedRows = ref<any[]>([])
|
||||
|
||||
// 重置表单
|
||||
const handleReset = () => {
|
||||
Object.assign(searchForm, { ...initialSearchState })
|
||||
pagination.currentPage = 1 // 重置到第一页
|
||||
getAccountList()
|
||||
}
|
||||
|
||||
// 搜索处理
|
||||
const handleSearch = () => {
|
||||
console.log('搜索参数:', searchForm)
|
||||
pagination.currentPage = 1 // 搜索时重置到第一页
|
||||
getAccountList()
|
||||
}
|
||||
|
||||
// 表单配置项
|
||||
const searchFormItems: SearchFormItem[] = [
|
||||
{
|
||||
label: '用户名',
|
||||
prop: 'username',
|
||||
type: 'input',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请输入用户名'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '手机号',
|
||||
prop: 'phone',
|
||||
type: 'input',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请输入手机号'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '店铺ID',
|
||||
prop: 'shop_id',
|
||||
type: 'input',
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请输入店铺ID'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
prop: 'status',
|
||||
type: 'select',
|
||||
options: STATUS_SELECT_OPTIONS,
|
||||
config: {
|
||||
clearable: true,
|
||||
placeholder: '请选择状态'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
// 列配置
|
||||
const columnOptions = [
|
||||
{ label: 'ID', prop: 'id' },
|
||||
{ label: '用户名', prop: 'username' },
|
||||
{ label: '手机号', prop: 'phone' },
|
||||
{ label: '店铺ID', prop: 'shop_id' },
|
||||
{ label: '状态', prop: 'status' },
|
||||
{ label: '创建时间', prop: 'created_at' },
|
||||
{ label: '操作', prop: 'operation' }
|
||||
]
|
||||
|
||||
// 显示对话框
|
||||
const showDialog = (type: string, row?: ShopAccountResponse) => {
|
||||
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.shop_id = row.shop_id
|
||||
formData.password = ''
|
||||
} else {
|
||||
formData.id = 0
|
||||
formData.username = ''
|
||||
formData.phone = ''
|
||||
formData.shop_id = 0
|
||||
formData.password = ''
|
||||
}
|
||||
}
|
||||
|
||||
// 显示修改密码对话框
|
||||
const showPasswordDialog = (row: ShopAccountResponse) => {
|
||||
currentAccountId.value = row.id
|
||||
passwordForm.new_password = ''
|
||||
passwordDialogVisible.value = true
|
||||
if (passwordFormRef.value) {
|
||||
passwordFormRef.value.resetFields()
|
||||
}
|
||||
}
|
||||
|
||||
// 状态切换处理
|
||||
const handleStatusChange = async (row: ShopAccountResponse, newStatus: number) => {
|
||||
const oldStatus = row.status
|
||||
// 先更新UI
|
||||
row.status = newStatus
|
||||
try {
|
||||
await ShopAccountService.updateShopAccountStatus(row.id, { status: newStatus })
|
||||
ElMessage.success('状态切换成功')
|
||||
} catch (error) {
|
||||
// 切换失败,恢复原状态
|
||||
row.status = oldStatus
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 动态列配置
|
||||
const { columnChecks, columns } = useCheckedColumns(() => [
|
||||
{
|
||||
prop: 'id',
|
||||
label: 'ID',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
prop: 'username',
|
||||
label: '用户名',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
prop: 'phone',
|
||||
label: '手机号',
|
||||
width: 130
|
||||
},
|
||||
{
|
||||
prop: 'shop_id',
|
||||
label: '店铺ID',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
prop: 'status',
|
||||
label: '状态',
|
||||
width: 100,
|
||||
formatter: (row: ShopAccountResponse) => {
|
||||
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: 'created_at',
|
||||
label: '创建时间',
|
||||
width: 180,
|
||||
formatter: (row: ShopAccountResponse) => formatDateTime(row.created_at)
|
||||
},
|
||||
{
|
||||
prop: 'operation',
|
||||
label: '操作',
|
||||
width: 120,
|
||||
fixed: 'right',
|
||||
formatter: (row: ShopAccountResponse) => {
|
||||
return h('div', { style: 'display: flex; gap: 8px;' }, [
|
||||
h(ArtButtonTable, {
|
||||
icon: '',
|
||||
onClick: () => showPasswordDialog(row)
|
||||
}),
|
||||
h(ArtButtonTable, {
|
||||
type: 'edit',
|
||||
onClick: () => showDialog('edit', row)
|
||||
})
|
||||
])
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
// 表单实例
|
||||
const formRef = ref<FormInstance>()
|
||||
const passwordFormRef = ref<FormInstance>()
|
||||
|
||||
// 表单数据
|
||||
const formData = reactive({
|
||||
id: 0,
|
||||
username: '',
|
||||
password: '',
|
||||
phone: '',
|
||||
shop_id: 0
|
||||
})
|
||||
|
||||
// 密码表单数据
|
||||
const passwordForm = reactive({
|
||||
new_password: ''
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
getAccountList()
|
||||
})
|
||||
|
||||
// 提交修改密码
|
||||
const handleChangePassword = async () => {
|
||||
if (!passwordFormRef.value) return
|
||||
|
||||
await passwordFormRef.value.validate(async (valid) => {
|
||||
if (valid) {
|
||||
passwordSubmitLoading.value = true
|
||||
try {
|
||||
await ShopAccountService.updateShopAccountPassword(currentAccountId.value, {
|
||||
new_password: passwordForm.new_password
|
||||
})
|
||||
ElMessage.success('重置密码成功')
|
||||
passwordDialogVisible.value = false
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
passwordSubmitLoading.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取账号列表
|
||||
const getAccountList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
page: pagination.currentPage,
|
||||
page_size: pagination.pageSize,
|
||||
username: searchForm.username || undefined,
|
||||
phone: searchForm.phone || undefined,
|
||||
shop_id: searchForm.shop_id,
|
||||
status: searchForm.status
|
||||
}
|
||||
const res = await ShopAccountService.getShopAccounts(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' }
|
||||
],
|
||||
shop_id: [
|
||||
{ required: true, message: '请输入店铺ID', trigger: 'blur' },
|
||||
{ type: 'number', min: 1, message: '店铺ID必须大于0', trigger: 'blur' }
|
||||
]
|
||||
})
|
||||
|
||||
// 密码验证规则
|
||||
const passwordRules = reactive<FormRules>({
|
||||
new_password: [
|
||||
{ required: true, message: '请输入新密码', trigger: 'blur' },
|
||||
{ min: 8, max: 32, message: '密码长度为 8-32 个字符', trigger: 'blur' }
|
||||
]
|
||||
})
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async () => {
|
||||
if (!formRef.value) return
|
||||
|
||||
await formRef.value.validate(async (valid) => {
|
||||
if (valid) {
|
||||
submitLoading.value = true
|
||||
try {
|
||||
if (dialogType.value === 'add') {
|
||||
const data = {
|
||||
username: formData.username,
|
||||
password: formData.password,
|
||||
phone: formData.phone,
|
||||
shop_id: formData.shop_id
|
||||
}
|
||||
|
||||
await ShopAccountService.createShopAccount(data)
|
||||
ElMessage.success('新增成功')
|
||||
} else {
|
||||
const data = {
|
||||
username: formData.username
|
||||
}
|
||||
|
||||
await ShopAccountService.updateShopAccount(formData.id, data)
|
||||
ElMessage.success('更新成功')
|
||||
}
|
||||
|
||||
dialogVisible.value = false
|
||||
getAccountList()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 处理表格分页变化
|
||||
const handleSizeChange = (newPageSize: number) => {
|
||||
pagination.pageSize = newPageSize
|
||||
getAccountList()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (newCurrentPage: number) => {
|
||||
pagination.currentPage = newCurrentPage
|
||||
getAccountList()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.shop-account-page {
|
||||
// 代理账号管理页面样式
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user