新增用户管理功能

- 新增用户列表页面,支持用户查询和筛选
- 完善侧边栏菜单导航功能
- 添加用户管理相关API接口
- 更新权限系统支持用户管理路由
- 新增用户管理国际化文案
This commit is contained in:
Wei_佳 2025-11-13 15:35:16 +08:00
parent 2b4b9a2e9c
commit aa512e6154
5 changed files with 317 additions and 3 deletions

View File

@ -49,6 +49,30 @@
"errors": {
"label_error": "错误页",
"text_back_to_home": "返回首页"
},
"user_management": {
"label_user_management": "用户管理",
"label_user_list": "用户列表",
"label_phone": "手机号",
"label_wechat": "微信号",
"label_register_time": "注册时间",
"label_remark": "备注",
"label_data_count": "创建数据数量",
"label_status": "状态",
"label_actions": "操作",
"button_detail": "详情",
"button_freeze": "冻结",
"button_unfreeze": "解冻",
"button_add_user": "新建用户",
"placeholder_phone": "请输入手机号",
"placeholder_wechat": "请输入微信号",
"placeholder_remark": "请输入备注",
"message_phone_required": "请输入手机号",
"message_phone_format_error": "请输入正确的手机号格式",
"message_freeze_success": "已冻结该用户",
"message_unfreeze_success": "已解冻该用户",
"text_status_normal": "正常",
"text_status_frozen": "冻结"
}
},
"common": {

View File

@ -39,4 +39,10 @@ export default {
deleteDept: (params = {}) => request.delete('/dept/delete', { params }),
// auditlog
getAuditLogList: (params = {}) => request.get('/auditlog/list', { params }),
// app users (客户端用户管理) - 使用现有的后端接口
getAppUserList: (params = {}) => request.get('/app-user/list', { params }),
getAppUserById: (params = {}) => request.get('/app-user/detail', { params }),
createAppUser: (data = {}) => request.post('/app-user/register', data),
updateAppUser: (data = {}) => request.post('/app-user/update', data),
deleteAppUser: (params = {}) => request.delete('/app-user/delete', { params }),
}

View File

@ -95,9 +95,11 @@ function getIcon(meta) {
}
function handleMenuSelect(key, item) {
console.log("🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥 ~ handleMenuSelect ~ key, item:", key, item);
if (isExternal(item.path)) {
window.open(item.path)
} else {
console.log("🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥 ~ handleMenuSelect ~ item.path === curRoute.path:", item.path === curRoute.path);
if (item.path === curRoute.path) {
appStore.reloadPage()
} else {

View File

@ -6,10 +6,17 @@ import api from '@/api'
// * 后端路由相关函数
// 根据后端传来数据构建出前端路由
function getRouteName(route) {
if (route?.id) return `menu-${route.id}`
if (route?.path) return route.path.replace(/\//g, '-') || route.name
return route?.name
}
function buildRoutes(routes = []) {
return routes.map((e) => {
const routeName = getRouteName(e)
const route = {
name: e.name,
name: routeName,
path: e.path,
component: shallowRef(Layout),
isHidden: e.is_hidden,
@ -26,7 +33,7 @@ function buildRoutes(routes = []) {
if (e.children && e.children.length > 0) {
// 有子菜单
route.children = e.children.map((e_child) => ({
name: e_child.name,
name: getRouteName(e_child),
path: e_child.path,
component: vueModules[`/src/views${e_child.component}/index.vue`],
isHidden: e_child.is_hidden,
@ -40,7 +47,7 @@ function buildRoutes(routes = []) {
} else {
// 没有子菜单,创建一个默认的子路由
route.children.push({
name: `${e.name}Default`,
name: `${routeName}Default`,
path: '',
component: vueModules[`/src/views${e.component}/index.vue`],
isHidden: true,
@ -49,6 +56,7 @@ function buildRoutes(routes = []) {
icon: e.icon,
order: e.order,
keepAlive: e.keepalive,
activeMenu: routeName,
},
})
}

View File

@ -0,0 +1,274 @@
<script setup>
import { h, onMounted, ref, resolveDirective, withDirectives } from 'vue'
import {
NButton,
NForm,
NFormItem,
NInput,
NSpace,
NSwitch,
NTag,
NPopconfirm,
} from 'naive-ui'
import CommonPage from '@/components/page/CommonPage.vue'
import QueryBarItem from '@/components/query-bar/QueryBarItem.vue'
import CrudModal from '@/components/table/CrudModal.vue'
import CrudTable from '@/components/table/CrudTable.vue'
import { formatDate, renderIcon } from '@/utils'
import { useCRUD } from '@/composables'
import api from '@/api'
import TheIcon from '@/components/icon/TheIcon.vue'
defineOptions({ name: '用户管理' })
const $table = ref(null)
const queryItems = ref({})
const vPermission = resolveDirective('permission')
const {
modalVisible,
modalTitle,
modalAction,
modalLoading,
handleSave,
modalForm,
modalFormRef,
handleEdit,
handleDelete,
handleAdd,
} = useCRUD({
name: '用户',
initForm: { is_active: true },
doCreate: (data) => api.createAppUser({ phone: data.phone }),
doUpdate: (data) => api.updateAppUser(data),
doDelete: (data) => api.deleteAppUser({ id: data.id }),
refresh: () => $table.value?.handleSearch(),
})
onMounted(() => {
$table.value?.handleSearch()
})
const columns = [
{
title: 'ID',
key: 'id',
width: 80,
align: 'center',
ellipsis: { tooltip: true },
},
{
title: '手机号',
key: 'phone',
width: 120,
align: 'center',
ellipsis: { tooltip: true },
},
{
title: '邮箱',
key: 'email',
width: 120,
align: 'center',
ellipsis: { tooltip: true },
render(row) {
return row.email || '-'
},
},
{
title: '注册时间',
key: 'created_at',
align: 'center',
width: 160,
ellipsis: { tooltip: true },
render(row) {
return h(
NButton,
{ size: 'small', type: 'text', ghost: true },
{
default: () => (row.created_at ? formatDate(row.created_at) : '-'),
icon: renderIcon('mdi:calendar', { size: 16 }),
}
)
},
},
{
title: '最后登录',
key: 'last_login',
align: 'center',
width: 160,
ellipsis: { tooltip: true },
render(row) {
return h(
NButton,
{ size: 'small', type: 'text', ghost: true },
{
default: () => (row.last_login ? formatDate(row.last_login) : '未登录'),
icon: renderIcon('mdi:login', { size: 16 }),
}
)
},
},
{
title: '状态',
key: 'is_active',
width: 80,
align: 'center',
render(row) {
return h(NSwitch, {
size: 'small',
rubberBand: false,
value: row.is_active,
loading: !!row.publishing,
checkedValue: true,
uncheckedValue: false,
onUpdateValue: () => handleUpdateStatus(row),
})
},
},
{
title: '操作',
key: 'actions',
width: 200,
align: 'center',
fixed: 'right',
render(row) {
return [
withDirectives(
h(
NButton,
{
size: 'small',
type: 'info',
style: 'margin-right: 8px;',
onClick: () => handleViewDetail(row),
},
{
default: () => '详情',
icon: renderIcon('material-symbols:info', { size: 16 }),
}
),
[[vPermission, 'get/api/v1/app_user/detail']]
),
withDirectives(
h(
NButton,
{
size: 'small',
type: row.is_active ? 'warning' : 'success',
style: 'margin-right: 8px;',
onClick: () => handleUpdateStatus(row),
},
{
default: () => (row.is_active ? '冻结' : '解冻'),
icon: renderIcon(row.is_active ? 'material-symbols:block' : 'material-symbols:check-circle', { size: 16 }),
}
),
[[vPermission, 'post/api/v1/app_user/update']]
),
]
},
},
]
//
async function handleUpdateStatus(row) {
if (!row.id) return
// API
$message.info('用户状态修改功能需要后端支持')
}
//
function handleViewDetail(row) {
//
$message.info('查看用户详情功能待实现')
}
const validateForm = {
phone: [
{
required: true,
message: '请输入手机号',
trigger: ['input', 'blur'],
},
{
trigger: ['blur'],
validator: (rule, value, callback) => {
const phoneRegex = /^1[3-9]\d{9}$/
if (!phoneRegex.test(value)) {
callback('请输入正确的手机号格式')
return
}
callback()
},
},
],
}
</script>
<template>
<CommonPage show-footer title="用户列表">
<template #action>
<NButton v-permission="'post/api/v1/app_user/create'" type="primary" @click="handleAdd">
<TheIcon icon="material-symbols:add" :size="18" class="mr-5" />新建用户
</NButton>
</template>
<!-- 表格 -->
<CrudTable
ref="$table"
v-model:query-items="queryItems"
:columns="columns"
:get-data="api.getAppUserList"
>
<template #queryBar>
<QueryBarItem label="手机号" :label-width="50">
<NInput
v-model:value="queryItems.phone"
clearable
type="text"
placeholder="请输入手机号"
@keypress.enter="$table?.handleSearch()"
/>
</QueryBarItem>
<QueryBarItem label="邮箱" :label-width="50">
<NInput
v-model:value="queryItems.email"
clearable
type="text"
placeholder="请输入邮箱"
@keypress.enter="$table?.handleSearch()"
/>
</QueryBarItem>
</template>
</CrudTable>
<!-- 新增/编辑 弹窗 -->
<CrudModal
v-model:visible="modalVisible"
:title="modalTitle"
:loading="modalLoading"
@save="handleSave"
>
<NForm
ref="modalFormRef"
label-placement="left"
label-align="left"
:label-width="80"
:model="modalForm"
:rules="validateForm"
>
<NFormItem label="手机号" path="phone">
<NInput v-model:value="modalForm.phone" clearable placeholder="请输入手机号" />
</NFormItem>
<NFormItem v-if="modalAction === 'add'" label="说明">
<span style="color: #999; font-size: 12px;">
注册后默认密码为手机号后6位
</span>
</NFormItem>
</NForm>
</CrudModal>
</CommonPage>
</template>