新增用户管理功能
- 新增用户列表页面,支持用户查询和筛选 - 完善侧边栏菜单导航功能 - 添加用户管理相关API接口 - 更新权限系统支持用户管理路由 - 新增用户管理国际化文案
This commit is contained in:
parent
2b4b9a2e9c
commit
aa512e6154
@ -49,6 +49,30 @@
|
|||||||
"errors": {
|
"errors": {
|
||||||
"label_error": "错误页",
|
"label_error": "错误页",
|
||||||
"text_back_to_home": "返回首页"
|
"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": {
|
"common": {
|
||||||
|
|||||||
@ -39,4 +39,10 @@ export default {
|
|||||||
deleteDept: (params = {}) => request.delete('/dept/delete', { params }),
|
deleteDept: (params = {}) => request.delete('/dept/delete', { params }),
|
||||||
// auditlog
|
// auditlog
|
||||||
getAuditLogList: (params = {}) => request.get('/auditlog/list', { params }),
|
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 }),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -95,9 +95,11 @@ function getIcon(meta) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleMenuSelect(key, item) {
|
function handleMenuSelect(key, item) {
|
||||||
|
console.log("🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥 ~ handleMenuSelect ~ key, item:", key, item);
|
||||||
if (isExternal(item.path)) {
|
if (isExternal(item.path)) {
|
||||||
window.open(item.path)
|
window.open(item.path)
|
||||||
} else {
|
} else {
|
||||||
|
console.log("🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥🔥 ~ handleMenuSelect ~ item.path === curRoute.path:", item.path === curRoute.path);
|
||||||
if (item.path === curRoute.path) {
|
if (item.path === curRoute.path) {
|
||||||
appStore.reloadPage()
|
appStore.reloadPage()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -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 = []) {
|
function buildRoutes(routes = []) {
|
||||||
return routes.map((e) => {
|
return routes.map((e) => {
|
||||||
|
const routeName = getRouteName(e)
|
||||||
const route = {
|
const route = {
|
||||||
name: e.name,
|
name: routeName,
|
||||||
path: e.path,
|
path: e.path,
|
||||||
component: shallowRef(Layout),
|
component: shallowRef(Layout),
|
||||||
isHidden: e.is_hidden,
|
isHidden: e.is_hidden,
|
||||||
@ -26,7 +33,7 @@ function buildRoutes(routes = []) {
|
|||||||
if (e.children && e.children.length > 0) {
|
if (e.children && e.children.length > 0) {
|
||||||
// 有子菜单
|
// 有子菜单
|
||||||
route.children = e.children.map((e_child) => ({
|
route.children = e.children.map((e_child) => ({
|
||||||
name: e_child.name,
|
name: getRouteName(e_child),
|
||||||
path: e_child.path,
|
path: e_child.path,
|
||||||
component: vueModules[`/src/views${e_child.component}/index.vue`],
|
component: vueModules[`/src/views${e_child.component}/index.vue`],
|
||||||
isHidden: e_child.is_hidden,
|
isHidden: e_child.is_hidden,
|
||||||
@ -40,7 +47,7 @@ function buildRoutes(routes = []) {
|
|||||||
} else {
|
} else {
|
||||||
// 没有子菜单,创建一个默认的子路由
|
// 没有子菜单,创建一个默认的子路由
|
||||||
route.children.push({
|
route.children.push({
|
||||||
name: `${e.name}Default`,
|
name: `${routeName}Default`,
|
||||||
path: '',
|
path: '',
|
||||||
component: vueModules[`/src/views${e.component}/index.vue`],
|
component: vueModules[`/src/views${e.component}/index.vue`],
|
||||||
isHidden: true,
|
isHidden: true,
|
||||||
@ -49,6 +56,7 @@ function buildRoutes(routes = []) {
|
|||||||
icon: e.icon,
|
icon: e.icon,
|
||||||
order: e.order,
|
order: e.order,
|
||||||
keepAlive: e.keepalive,
|
keepAlive: e.keepalive,
|
||||||
|
activeMenu: routeName,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
274
web/src/views/user-management/user-list/index.vue
Normal file
274
web/src/views/user-management/user-list/index.vue
Normal 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>
|
||||||
Loading…
x
Reference in New Issue
Block a user