feat(frontend): 添加部门管理和系统配置页面
- 添加 department.ts 部门管理服务 - 添加 DepartmentManagementView.vue 部门管理页面 - 添加 SystemConfigView.vue 系统配置页面 - 更新路由配置添加新页面 - 更新 App.vue 添加系统菜单入口 - 前端编译验证通过
This commit is contained in:
@@ -90,6 +90,14 @@
|
||||
>
|
||||
通知
|
||||
</RouterLink>
|
||||
<RouterLink
|
||||
v-if="auth.hasPermission('system:view')"
|
||||
to="/system"
|
||||
class="rounded-full px-3 py-1.5 text-mosquito-ink/70 transition"
|
||||
:class="{ 'bg-mosquito-accent/10 text-mosquito-ink': route.path.startsWith('/system') }"
|
||||
>
|
||||
系统
|
||||
</RouterLink>
|
||||
</nav>
|
||||
<div class="flex items-center gap-2">
|
||||
<span v-if="auth.mode === 'demo'" class="mos-pill">演示模式</span>
|
||||
|
||||
@@ -17,9 +17,11 @@ import ApprovalCenterView from '../views/ApprovalCenterView.vue'
|
||||
import UserDetailView from '../views/UserDetailView.vue'
|
||||
import PermissionsView from '../views/PermissionsView.vue'
|
||||
import RoleManagementView from '../views/RoleManagementView.vue'
|
||||
import DepartmentManagementView from '../views/DepartmentManagementView.vue'
|
||||
import SystemConfigView from '../views/SystemConfigView.vue'
|
||||
import type { AdminRole } from '../auth/roles'
|
||||
|
||||
// 路由权限配置 - 使用新的角色系统
|
||||
// 路由权限配置
|
||||
const routeRoles: Record<string, AdminRole[]> = {
|
||||
'dashboard': ['super_admin', 'system_admin', 'operation_manager', 'operation_member', 'marketing_manager', 'marketing_member', 'finance_manager', 'finance_member', 'risk_manager', 'risk_member', 'customer_service', 'auditor', 'viewer'],
|
||||
'activities': ['super_admin', 'system_admin', 'operation_manager', 'operation_member', 'marketing_manager', 'marketing_member', 'customer_service', 'auditor', 'viewer'],
|
||||
@@ -35,119 +37,33 @@ const routeRoles: Record<string, AdminRole[]> = {
|
||||
'approvals': ['super_admin', 'system_admin', 'operation_manager', 'marketing_manager', 'finance_manager', 'risk_manager'],
|
||||
'permissions': ['super_admin', 'system_admin'],
|
||||
'role-management': ['super_admin', 'system_admin'],
|
||||
'notifications': ['super_admin', 'system_admin', 'operation_manager', 'operation_member', 'marketing_manager', 'marketing_member', 'finance_manager', 'finance_member', 'risk_manager', 'risk_member', 'customer_service', 'auditor', 'viewer'],
|
||||
'system': ['super_admin', 'system_admin', 'auditor']
|
||||
'department-management': ['super_admin', 'system_admin'],
|
||||
'system-config': ['super_admin', 'system_admin', 'auditor'],
|
||||
'notifications': ['super_admin', 'system_admin', 'operation_manager', 'operation_member', 'marketing_manager', 'marketing_member', 'finance_manager', 'finance_member', 'risk_manager', 'risk_member', 'customer_service', 'auditor', 'viewer']
|
||||
}
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes: [
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
component: LoginView
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
name: 'dashboard',
|
||||
component: DashboardView,
|
||||
meta: { roles: routeRoles.dashboard }
|
||||
},
|
||||
{
|
||||
path: '/activities',
|
||||
name: 'activities',
|
||||
component: ActivityListView,
|
||||
meta: { roles: routeRoles.activities }
|
||||
},
|
||||
{
|
||||
path: '/activities/new',
|
||||
name: 'activity-create',
|
||||
component: ActivityCreateView,
|
||||
meta: { roles: routeRoles['activity-create'] }
|
||||
},
|
||||
{
|
||||
path: '/activities/:id',
|
||||
name: 'activity-detail',
|
||||
component: ActivityDetailView,
|
||||
meta: { roles: routeRoles['activity-detail'] }
|
||||
},
|
||||
{
|
||||
path: '/activities/config',
|
||||
name: 'activity-config',
|
||||
component: ActivityConfigWizardView,
|
||||
meta: { roles: routeRoles['activity-config'] }
|
||||
},
|
||||
{
|
||||
path: '/activities/:id',
|
||||
name: 'activity-detail',
|
||||
component: ActivityDetailView,
|
||||
meta: { roles: routeRoles['activity-detail'] }
|
||||
},
|
||||
{
|
||||
path: '/users',
|
||||
name: 'users',
|
||||
component: UsersView,
|
||||
meta: { roles: routeRoles.users }
|
||||
},
|
||||
{
|
||||
path: '/users/:id',
|
||||
name: 'user-detail',
|
||||
component: UserDetailView,
|
||||
meta: { roles: routeRoles['user-detail'] }
|
||||
},
|
||||
{
|
||||
path: '/users/invite',
|
||||
name: 'user-invite',
|
||||
component: InviteUserView,
|
||||
meta: { roles: routeRoles['user-invite'] }
|
||||
},
|
||||
{
|
||||
path: '/rewards',
|
||||
name: 'rewards',
|
||||
component: RewardsView,
|
||||
meta: { roles: routeRoles.rewards }
|
||||
},
|
||||
{
|
||||
path: '/risk',
|
||||
name: 'risk',
|
||||
component: RiskView,
|
||||
meta: { roles: routeRoles.risk }
|
||||
},
|
||||
{
|
||||
path: '/audit',
|
||||
name: 'audit',
|
||||
component: AuditLogView,
|
||||
meta: { roles: routeRoles.audit }
|
||||
},
|
||||
{
|
||||
path: '/approvals',
|
||||
name: 'approvals',
|
||||
component: ApprovalCenterView,
|
||||
meta: { roles: routeRoles.approvals }
|
||||
},
|
||||
{
|
||||
path: '/permissions',
|
||||
name: 'permissions',
|
||||
component: PermissionsView,
|
||||
meta: { roles: routeRoles.permissions }
|
||||
},
|
||||
{
|
||||
path: '/roles',
|
||||
name: 'role-management',
|
||||
component: RoleManagementView,
|
||||
meta: { roles: routeRoles['role-management'] }
|
||||
},
|
||||
{
|
||||
path: '/notifications',
|
||||
name: 'notifications',
|
||||
component: NotificationsView,
|
||||
meta: { roles: routeRoles.notifications }
|
||||
},
|
||||
{
|
||||
path: '/403',
|
||||
name: 'forbidden',
|
||||
component: ForbiddenView
|
||||
}
|
||||
{ path: '/login', name: 'login', component: LoginView },
|
||||
{ path: '/', name: 'dashboard', component: DashboardView, meta: { roles: routeRoles.dashboard } },
|
||||
{ path: '/activities', name: 'activities', component: ActivityListView, meta: { roles: routeRoles.activities } },
|
||||
{ path: '/activities/new', name: 'activity-create', component: ActivityCreateView, meta: { roles: routeRoles['activity-create'] } },
|
||||
{ path: '/activities/:id', name: 'activity-detail', component: ActivityDetailView, meta: { roles: routeRoles['activity-detail'] } },
|
||||
{ path: '/activities/config', name: 'activity-config', component: ActivityConfigWizardView, meta: { roles: routeRoles['activity-config'] } },
|
||||
{ path: '/users', name: 'users', component: UsersView, meta: { roles: routeRoles.users } },
|
||||
{ path: '/users/:id', name: 'user-detail', component: UserDetailView, meta: { roles: routeRoles['user-detail'] } },
|
||||
{ path: '/users/invite', name: 'user-invite', component: InviteUserView, meta: { roles: routeRoles['user-invite'] } },
|
||||
{ path: '/rewards', name: 'rewards', component: RewardsView, meta: { roles: routeRoles.rewards } },
|
||||
{ path: '/risk', name: 'risk', component: RiskView, meta: { roles: routeRoles.risk } },
|
||||
{ path: '/audit', name: 'audit', component: AuditLogView, meta: { roles: routeRoles.audit } },
|
||||
{ path: '/approvals', name: 'approvals', component: ApprovalCenterView, meta: { roles: routeRoles.approvals } },
|
||||
{ path: '/permissions', name: 'permissions', component: PermissionsView, meta: { roles: routeRoles.permissions } },
|
||||
{ path: '/roles', name: 'role-management', component: RoleManagementView, meta: { roles: routeRoles['role-management'] } },
|
||||
{ path: '/departments', name: 'department-management', component: DepartmentManagementView, meta: { roles: routeRoles['department-management'] } },
|
||||
{ path: '/system', name: 'system-config', component: SystemConfigView, meta: { roles: routeRoles['system-config'] } },
|
||||
{ path: '/notifications', name: 'notifications', component: NotificationsView, meta: { roles: routeRoles.notifications } },
|
||||
{ path: '/403', name: 'forbidden', component: ForbiddenView }
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
87
frontend/admin/src/services/department.ts
Normal file
87
frontend/admin/src/services/department.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* 部门管理服务
|
||||
*/
|
||||
|
||||
export interface Department {
|
||||
id?: number
|
||||
deptName: string
|
||||
parentId?: number
|
||||
deptCode?: string
|
||||
leaderId?: number
|
||||
sortOrder?: number
|
||||
status: number
|
||||
createdAt?: string
|
||||
}
|
||||
|
||||
export interface ApiResponse<T> {
|
||||
code: number
|
||||
data: T
|
||||
message?: string
|
||||
}
|
||||
|
||||
class DepartmentService {
|
||||
private baseUrl = '/api'
|
||||
|
||||
async getDepartments(): Promise<Department[]> {
|
||||
const response = await fetch(`${this.baseUrl}/departments`, {
|
||||
credentials: 'include'
|
||||
})
|
||||
const result = await response.json() as ApiResponse<Department[]>
|
||||
if (result.code !== 200) {
|
||||
throw new Error(result.message || '获取部门列表失败')
|
||||
}
|
||||
return result.data
|
||||
}
|
||||
|
||||
async getDepartmentById(id: number): Promise<Department | null> {
|
||||
const response = await fetch(`${this.baseUrl}/departments/${id}`, {
|
||||
credentials: 'include'
|
||||
})
|
||||
const result = await response.json() as ApiResponse<Department>
|
||||
if (result.code !== 200) {
|
||||
return null
|
||||
}
|
||||
return result.data
|
||||
}
|
||||
|
||||
async createDepartment(data: Department): Promise<number> {
|
||||
const response = await fetch(`${this.baseUrl}/departments`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
const result = await response.json() as ApiResponse<number>
|
||||
if (result.code !== 200) {
|
||||
throw new Error(result.message || '创建部门失败')
|
||||
}
|
||||
return result.data
|
||||
}
|
||||
|
||||
async updateDepartment(id: number, data: Department): Promise<void> {
|
||||
const response = await fetch(`${this.baseUrl}/departments/${id}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
const result = await response.json() as ApiResponse<void>
|
||||
if (result.code !== 200) {
|
||||
throw new Error(result.message || '更新部门失败')
|
||||
}
|
||||
}
|
||||
|
||||
async deleteDepartment(id: number): Promise<void> {
|
||||
const response = await fetch(`${this.baseUrl}/departments/${id}`, {
|
||||
method: 'DELETE',
|
||||
credentials: 'include'
|
||||
})
|
||||
const result = await response.json() as ApiResponse<void>
|
||||
if (result.code !== 200) {
|
||||
throw new Error(result.message || '删除部门失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const departmentService = new DepartmentService()
|
||||
export default departmentService
|
||||
216
frontend/admin/src/views/DepartmentManagementView.vue
Normal file
216
frontend/admin/src/views/DepartmentManagementView.vue
Normal file
@@ -0,0 +1,216 @@
|
||||
<template>
|
||||
<section class="space-y-6">
|
||||
<header class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="mos-title text-2xl font-semibold">部门管理</h1>
|
||||
<p class="mos-muted text-sm">管理系统部门组织架构。</p>
|
||||
</div>
|
||||
<button class="mos-btn mos-btn-accent" @click="openCreateDialog(0 as number)">
|
||||
新建部门
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<!-- 部门树形列表 -->
|
||||
<div class="mos-card p-5">
|
||||
<div v-if="loading" class="py-8 text-center text-mosquito-ink/60">
|
||||
加载中...
|
||||
</div>
|
||||
<div v-else-if="!treeData.length" class="py-8 text-center text-mosquito-ink/60">
|
||||
暂无部门数据
|
||||
</div>
|
||||
<div v-else class="space-y-1">
|
||||
<div
|
||||
v-for="dept in treeData"
|
||||
:key="dept.id"
|
||||
class="border border-mosquito-line rounded-lg overflow-hidden"
|
||||
>
|
||||
<div class="px-4 py-3 hover:bg-mosquito-bg/50">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-semibold">{{ dept.deptName }}</span>
|
||||
<span class="text-xs text-mosquito-ink/50">{{ dept.deptCode }}</span>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button class="mos-btn mos-btn-secondary !py-1 !px-2 !text-xs" @click="openCreateDialog(dept.id || 0)">
|
||||
添加子部门
|
||||
</button>
|
||||
<button class="mos-btn mos-btn-secondary !py-1 !px-2 !text-xs" @click="openEditDialog(dept)">
|
||||
编辑
|
||||
</button>
|
||||
<button class="mos-btn mos-btn-danger !py-1 !px-2 !text-xs" @click="handleDelete(dept)">
|
||||
删除
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 子部门 -->
|
||||
<div v-if="dept.children?.length" class="mt-2 pl-6 space-y-1 bg-mosquito-bg/30 rounded-lg p-2">
|
||||
<div
|
||||
v-for="child in dept.children"
|
||||
:key="child.id"
|
||||
class="flex items-center justify-between py-2 px-3 bg-white rounded border border-mosquito-line"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<span>{{ child.deptName }}</span>
|
||||
<span class="text-xs text-mosquito-ink/50">{{ child.deptCode }}</span>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button class="mos-btn mos-btn-secondary !py-1 !px-2 !text-xs" @click="openEditDialog(child)">编辑</button>
|
||||
<button class="mos-btn mos-btn-danger !py-1 !px-2 !text-xs" @click="handleDelete(child)">删除</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 创建/编辑对话框 -->
|
||||
<div v-if="dialogVisible" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
|
||||
<div class="mos-card w-[500px] p-6">
|
||||
<h2 class="text-lg font-semibold mb-4">{{ isEdit ? '编辑部门' : '新建部门' }}</h2>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="text-xs font-semibold text-mosquito-ink/70">部门名称</label>
|
||||
<input v-model="form.deptName" class="mos-input mt-2 w-full" placeholder="如: 运营部" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-xs font-semibold text-mosquito-ink/70">部门编码</label>
|
||||
<input v-model="form.deptCode" class="mos-input mt-2 w-full" placeholder="如: DEPT001" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-xs font-semibold text-mosquito-ink/70">排序</label>
|
||||
<input v-model.number="form.sortOrder" type="number" class="mos-input mt-2 w-full" placeholder="0" />
|
||||
</div>
|
||||
<div v-if="isEdit">
|
||||
<label class="text-xs font-semibold text-mosquito-ink/70">状态</label>
|
||||
<select v-model="form.status" class="mos-input mt-2 w-full">
|
||||
<option :value="1">启用</option>
|
||||
<option :value="0">禁用</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end gap-2 mt-6">
|
||||
<button class="mos-btn mos-btn-secondary" @click="dialogVisible = false">取消</button>
|
||||
<button class="mos-btn mos-btn-accent" @click="handleSubmit">
|
||||
{{ isEdit ? '保存' : '创建' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { type Department } from '../services/department'
|
||||
import departmentService from '../services/department'
|
||||
|
||||
interface DepartmentWithChildren extends Department {
|
||||
children?: DepartmentWithChildren[]
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
const departments = ref<Department[]>([])
|
||||
const dialogVisible = ref(false)
|
||||
const isEdit = ref(false)
|
||||
|
||||
const form = ref({
|
||||
id: 0,
|
||||
parentId: 0,
|
||||
deptName: '',
|
||||
deptCode: '',
|
||||
sortOrder: 0,
|
||||
status: 1
|
||||
})
|
||||
|
||||
// 树形结构
|
||||
const treeData = computed(() => {
|
||||
const deptMap = new Map<number, DepartmentWithChildren>()
|
||||
const roots: DepartmentWithChildren[] = []
|
||||
|
||||
departments.value.forEach(dept => {
|
||||
deptMap.set(dept.id!, { ...dept, children: [] })
|
||||
})
|
||||
|
||||
departments.value.forEach(dept => {
|
||||
const node = deptMap.get(dept.id!)!
|
||||
if (dept.parentId && dept.parentId > 0 && deptMap.has(dept.parentId)) {
|
||||
deptMap.get(dept.parentId)!.children!.push(node)
|
||||
} else {
|
||||
roots.push(node)
|
||||
}
|
||||
})
|
||||
|
||||
return roots
|
||||
})
|
||||
|
||||
const loadDepartments = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
departments.value = await departmentService.getDepartments()
|
||||
} catch (error) {
|
||||
console.error('加载部门失败:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const openCreateDialog = (parentId: number) => {
|
||||
isEdit.value = false
|
||||
form.value = {
|
||||
id: 0,
|
||||
parentId,
|
||||
deptName: '',
|
||||
deptCode: '',
|
||||
sortOrder: 0,
|
||||
status: 1
|
||||
}
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const openEditDialog = (dept: Department) => {
|
||||
isEdit.value = true
|
||||
form.value = {
|
||||
id: dept.id!,
|
||||
parentId: dept.parentId || 0,
|
||||
deptName: dept.deptName,
|
||||
deptCode: dept.deptCode || '',
|
||||
sortOrder: dept.sortOrder || 0,
|
||||
status: dept.status
|
||||
}
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
if (isEdit.value) {
|
||||
await departmentService.updateDepartment(form.value.id, form.value)
|
||||
alert('部门更新成功')
|
||||
} else {
|
||||
await departmentService.createDepartment(form.value)
|
||||
alert('部门创建成功')
|
||||
}
|
||||
dialogVisible.value = false
|
||||
await loadDepartments()
|
||||
} catch (error) {
|
||||
alert(error instanceof Error ? error.message : '操作失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = async (dept: Department) => {
|
||||
if (!confirm(`确定要删除部门 ${dept.deptName} 吗?`)) return
|
||||
try {
|
||||
if (dept.id) {
|
||||
await departmentService.deleteDepartment(dept.id)
|
||||
alert('部门删除成功')
|
||||
await loadDepartments()
|
||||
}
|
||||
} catch (error) {
|
||||
alert(error instanceof Error ? error.message : '删除失败')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadDepartments()
|
||||
})
|
||||
</script>
|
||||
197
frontend/admin/src/views/SystemConfigView.vue
Normal file
197
frontend/admin/src/views/SystemConfigView.vue
Normal file
@@ -0,0 +1,197 @@
|
||||
<template>
|
||||
<section class="space-y-6">
|
||||
<header class="space-y-2">
|
||||
<h1 class="mos-title text-2xl font-semibold">系统配置</h1>
|
||||
<p class="mos-muted text-sm">管理系统参数和缓存配置。</p>
|
||||
</header>
|
||||
|
||||
<!-- 配置tabs -->
|
||||
<div class="flex gap-2 border-b border-mosquito-line pb-2">
|
||||
<button
|
||||
v-for="tab in tabs"
|
||||
:key="tab.key"
|
||||
class="px-4 py-2 text-sm font-semibold rounded-t-lg transition"
|
||||
:class="activeTab === tab.key
|
||||
? 'bg-mosquito-accent/10 text-mosquito-brand border-b-2 border-mosquito-brand'
|
||||
: 'text-mosquito-ink/70 hover:bg-mosquito-bg'"
|
||||
@click="activeTab = tab.key"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 系统参数 -->
|
||||
<div v-if="activeTab === 'params'" class="mos-card p-5">
|
||||
<div class="space-y-4">
|
||||
<div v-for="config in systemParams" :key="config.key" class="flex items-center justify-between py-3 border-b border-mosquito-line">
|
||||
<div>
|
||||
<div class="font-semibold">{{ config.label }}</div>
|
||||
<div class="text-xs text-mosquito-ink/70">{{ config.description }}</div>
|
||||
</div>
|
||||
<div class="w-64">
|
||||
<input
|
||||
v-if="config.type === 'string'"
|
||||
v-model="config.value"
|
||||
class="mos-input w-full"
|
||||
/>
|
||||
<input
|
||||
v-else-if="config.type === 'number'"
|
||||
v-model.number="config.value"
|
||||
type="number"
|
||||
class="mos-input w-full"
|
||||
/>
|
||||
<select
|
||||
v-else-if="config.type === 'boolean'"
|
||||
v-model="config.value"
|
||||
class="mos-input w-full"
|
||||
>
|
||||
<option :value="true">启用</option>
|
||||
<option :value="false">禁用</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-6 flex justify-end">
|
||||
<button class="mos-btn mos-btn-accent" @click="saveParams">保存配置</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 缓存管理 -->
|
||||
<div v-if="activeTab === 'cache'" class="mos-card p-5">
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center justify-between py-3 border-b border-mosquito-line">
|
||||
<div>
|
||||
<div class="font-semibold">活动数据缓存</div>
|
||||
<div class="text-xs text-mosquito-ink/70">缓存活动列表和统计数据</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button class="mos-btn mos-btn-secondary" @click="clearCache('activity')">清除缓存</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between py-3 border-b border-mosquito-line">
|
||||
<div>
|
||||
<div class="font-semibold">用户数据缓存</div>
|
||||
<div class="text-xs text-mosquito-ink/70">缓存用户信息和权限数据</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button class="mos-btn mos-btn-secondary" @click="clearCache('user')">清除缓存</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between py-3 border-b border-mosquito-line">
|
||||
<div>
|
||||
<div class="font-semibold">奖励数据缓存</div>
|
||||
<div class="text-xs text-mosquito-ink/70">缓存奖励配置和发放记录</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button class="mos-btn mos-btn-secondary" @click="clearCache('reward')">清除缓存</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between py-3">
|
||||
<div>
|
||||
<div class="font-semibold">全部缓存</div>
|
||||
<div class="text-xs text-mosquito-ink/70">清除所有系统缓存</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button class="mos-btn mos-btn-danger" @click="clearCache('all')">清除全部缓存</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- API密钥 -->
|
||||
<div v-if="activeTab === 'apiKey'" class="mos-card p-5">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<div class="font-semibold">API密钥管理</div>
|
||||
<button class="mos-btn mos-btn-accent" @click="createApiKey">创建新密钥</button>
|
||||
</div>
|
||||
<table class="w-full">
|
||||
<thead>
|
||||
<tr class="text-left text-xs text-mosquito-ink/70 border-b border-mosquito-line">
|
||||
<th class="pb-2">名称</th>
|
||||
<th class="pb-2">密钥</th>
|
||||
<th class="pb-2">状态</th>
|
||||
<th class="pb-2">创建时间</th>
|
||||
<th class="pb-2">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="key in apiKeys" :key="key.id" class="border-b border-mosquito-line">
|
||||
<td class="py-3">{{ key.name }}</td>
|
||||
<td class="py-3 font-mono text-sm">{{ showKeyId === key.id ? key.key : '••••••••••••••••' }}</td>
|
||||
<td class="py-3">
|
||||
<span :class="key.status === 1 ? 'text-green-600' : 'text-red-600'">
|
||||
{{ key.status === 1 ? '启用' : '禁用' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="py-3 text-sm">{{ key.createdAt }}</td>
|
||||
<td class="py-3">
|
||||
<button class="mos-btn mos-btn-secondary !py-1 !px-2 !text-xs" @click="toggleShowKey(key.id)">
|
||||
{{ showKeyId === key.id ? '隐藏' : '显示' }}
|
||||
</button>
|
||||
<button class="mos-btn mos-btn-danger !py-1 !px-2 !text-xs ml-2" @click="deleteApiKey(key.id)">
|
||||
删除
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div v-if="!apiKeys.length" class="py-8 text-center text-mosquito-ink/60">
|
||||
暂无API密钥
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
const activeTab = ref('params')
|
||||
const showKeyId = ref<number | null>(null)
|
||||
|
||||
const tabs = [
|
||||
{ key: 'params', label: '系统参数' },
|
||||
{ key: 'cache', label: '缓存管理' },
|
||||
{ key: 'apiKey', label: 'API密钥' }
|
||||
]
|
||||
|
||||
const systemParams = ref([
|
||||
{ key: 'reward.max.points', label: '单次奖励上限', description: '单次奖励发放的最大积分值', value: 10000, type: 'number' },
|
||||
{ key: 'activity.max.count', label: '最大活动数', description: '系统允许创建的最大活动数量', value: 100, type: 'number' },
|
||||
{ key: 'risk.callback.threshold', label: '回调失败阈值', description: '触发告警的回调失败率(%)', value: 5, type: 'number' },
|
||||
{ key: 'approval.auto.timeout', label: '审批超时时间', description: '审批自动通过的超时时间(小时)', value: 24, type: 'number' },
|
||||
{ key: 'user.invite.expire', label: '邀请链接有效期', description: '邀请链接有效天数', value: 7, type: 'number' },
|
||||
{ key: 'reward.batch.size', label: '批量发放大小', description: '奖励批量发放的每批数量', value: 200, type: 'number' }
|
||||
])
|
||||
|
||||
const apiKeys = ref([
|
||||
{ id: 1, name: '生产环境密钥', key: 'mk_prod_xxxxxxxxxxxxx', status: 1, createdAt: '2026-01-15' },
|
||||
{ id: 2, name: '测试环境密钥', key: 'mk_test_xxxxxxxxxxxxx', status: 1, createdAt: '2026-02-01' }
|
||||
])
|
||||
|
||||
const saveParams = () => {
|
||||
alert('配置保存成功(演示)')
|
||||
}
|
||||
|
||||
const clearCache = (type: string) => {
|
||||
if (confirm(`确定要清除${type === 'all' ? '全部' : type}缓存吗?`)) {
|
||||
alert('缓存清除成功(演示)')
|
||||
}
|
||||
}
|
||||
|
||||
const createApiKey = () => {
|
||||
const name = prompt('请输入密钥名称:')
|
||||
if (name) {
|
||||
alert('API密钥创建成功(演示)')
|
||||
}
|
||||
}
|
||||
|
||||
const toggleShowKey = (id: number) => {
|
||||
showKeyId.value = showKeyId.value === id ? null : id
|
||||
}
|
||||
|
||||
const deleteApiKey = (id: number) => {
|
||||
if (confirm('确定要删除这个API密钥吗?')) {
|
||||
alert('API密钥删除成功(演示)')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user