mirror of
https://github.com/chansee97/nova-admin.git
synced 2025-09-18 03:39:58 +08:00
feat: add user center
This commit is contained in:
parent
3e7fb6fa16
commit
7dd0301fb2
2
.env
2
.env
@ -23,4 +23,4 @@ VITE_COPYRIGHT_INFO = Copyright © 2024 chansee97
|
||||
VITE_AUTO_REFRESH_TOKEN = N
|
||||
|
||||
# 默认多语言 enUS | zhCN
|
||||
VITE_DEFAULT_LANG = enUS
|
||||
VITE_DEFAULT_LANG = zhCN
|
||||
|
1
.vscode/extensions.json
vendored
1
.vscode/extensions.json
vendored
@ -3,7 +3,6 @@
|
||||
"mikestead.dotenv",
|
||||
"usernamehw.errorlens",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"eamodio.gitlens",
|
||||
"mhutchie.git-graph",
|
||||
"donjayamanne.githistory",
|
||||
"lokalise.i18n-ally",
|
||||
|
@ -27,5 +27,5 @@ export function fetchRoleList() {
|
||||
*/
|
||||
export function fetchDictList(code?: string) {
|
||||
const params = { code }
|
||||
return request.Get<Api.Response<Entity.Dict[]>>('/dict/list', { params })
|
||||
return request.Get<Api.Response<Entity.DictType[]>>('/dict/list', { params })
|
||||
}
|
||||
|
@ -1,8 +1,4 @@
|
||||
export * from './login'
|
||||
export * from './demo'
|
||||
export * from './test'
|
||||
export * from './system/user'
|
||||
export * from './system/menu'
|
||||
export * from './system/role'
|
||||
export * from './system/dict'
|
||||
export * from './system/dept'
|
||||
export * from './system'
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { request } from '../../utils/alova'
|
||||
|
||||
export type SearchQuery = Partial<Pick<Entity.Dept, 'deptName' | 'status'>>
|
||||
export type DeptSearchQuery = Partial<Pick<Entity.Dept, 'deptName' | 'status'>>
|
||||
|
||||
/**
|
||||
* 创建部门
|
||||
@ -14,7 +14,7 @@ export function createDept(data: Partial<Entity.Dept>) {
|
||||
* 分页查询部门
|
||||
* GET /dept
|
||||
*/
|
||||
export function getDeptList(params?: SearchQuery) {
|
||||
export function getDeptList(params?: DeptSearchQuery) {
|
||||
return request.Get<Api.Response<Entity.Dept[]>>('/dept', { params })
|
||||
}
|
||||
|
||||
|
5
src/api/system/index.ts
Normal file
5
src/api/system/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export * from './user'
|
||||
export * from './menu'
|
||||
export * from './role'
|
||||
export * from './dict'
|
||||
export * from './dept'
|
@ -1,6 +1,6 @@
|
||||
import { request } from '../../utils/alova'
|
||||
|
||||
export type SearchQuery = Partial<Pick<Entity.Menu, 'title' | 'status'>>
|
||||
export type MenuSearchQuery = Partial<Pick<Entity.Menu, 'title' | 'status'>>
|
||||
/**
|
||||
* 创建菜单
|
||||
* POST /menu
|
||||
@ -13,7 +13,7 @@ export function createMenu(data: Partial<Entity.Menu>) {
|
||||
* 分页查询菜单
|
||||
* GET /menu
|
||||
*/
|
||||
export function getMenuList(params?: SearchQuery) {
|
||||
export function getMenuList(params?: MenuSearchQuery) {
|
||||
return request.Get<Api.Response<Entity.Menu[]>>('/menu', { params })
|
||||
}
|
||||
|
||||
|
@ -1,36 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
count?: number
|
||||
}
|
||||
const {
|
||||
count = 0,
|
||||
} = defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
change: [page: number, pageSize: number] // 具名元组语法
|
||||
}>()
|
||||
|
||||
const page = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const displayOrder: Array<'pages' | 'size-picker' | 'quick-jumper'> = ['size-picker', 'pages']
|
||||
|
||||
function changePage() {
|
||||
emit('change', page.value, pageSize.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-pagination
|
||||
v-if="count > 0"
|
||||
v-model:page="page"
|
||||
v-model:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 30, 50]"
|
||||
:item-count="count"
|
||||
:display-order="displayOrder"
|
||||
show-size-picker
|
||||
@update-page="changePage"
|
||||
@update-page-size="changePage"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
72
src/hooks/useDict.ts
Normal file
72
src/hooks/useDict.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import { useDictStore } from '@/store/dict'
|
||||
|
||||
/**
|
||||
* 字典数据转换工具类型
|
||||
*/
|
||||
export interface DictUtils {
|
||||
/** 原始字典数据 */
|
||||
data: Entity.DictData[]
|
||||
/** 枚举映射 { value: label } */
|
||||
enum: Record<string, string>
|
||||
/** 值映射 { value: dictData } */
|
||||
valueMap: Record<string, Omit<Entity.DictData, 'value'>>
|
||||
/** 标签映射 { label: dictData } */
|
||||
labelMap: Record<string, Omit<Entity.DictData, 'name'>>
|
||||
/** 选项数组 [{ label, value }] */
|
||||
options: Array<{ label: string, value: string }>
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用字典数据的 Hook
|
||||
* @param dictType 字典类型
|
||||
* @returns 字典工具对象和加载状态
|
||||
*/
|
||||
export function useDict(dictType: string) {
|
||||
const dictStore = useDictStore()
|
||||
|
||||
const dictData = ref<Entity.DictData[]>([])
|
||||
|
||||
// 获取字典数据
|
||||
async function fetchDict() {
|
||||
try {
|
||||
dictData.value = await dictStore.getDict(dictType)
|
||||
}
|
||||
catch (err) {
|
||||
console.error(`获取字典数据失败 [${dictType}]:`, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 立即获取数据
|
||||
fetchDict()
|
||||
|
||||
const enumMap = computed(() => {
|
||||
return Object.fromEntries(
|
||||
dictData.value.map(({ value, name }) => [value, name]),
|
||||
)
|
||||
})
|
||||
const valueMap = computed(() => {
|
||||
return Object.fromEntries(
|
||||
dictData.value.map(({ value, ...data }) => [value, data]),
|
||||
)
|
||||
})
|
||||
const labelMap = computed(() => {
|
||||
return Object.fromEntries(
|
||||
dictData.value.map(({ name, ...data }) => [name, data]),
|
||||
)
|
||||
})
|
||||
|
||||
const options = computed(() => {
|
||||
return dictData.value.map(({ name, value }) => ({
|
||||
label: name,
|
||||
value,
|
||||
}))
|
||||
})
|
||||
|
||||
return {
|
||||
rawData: dictData,
|
||||
enumMap,
|
||||
valueMap,
|
||||
labelMap,
|
||||
options,
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
import type { NDataTable } from 'naive-ui'
|
||||
import { useDraggable } from 'vue-draggable-plus'
|
||||
|
||||
export function useTableDrag<T = unknown>(params: {
|
||||
tableRef: Ref<InstanceType<typeof NDataTable> | undefined>
|
||||
data: Ref<T[]>
|
||||
onRowDrag: (rows: T[]) => void
|
||||
}) {
|
||||
const tableEl = computed(() => params.tableRef?.value?.$el as HTMLElement)
|
||||
const tableBodyRef = ref<HTMLElement | undefined>(undefined)
|
||||
|
||||
const { start } = useDraggable(tableBodyRef, params.data, {
|
||||
immediate: false,
|
||||
animation: 150,
|
||||
handle: '.drag-handle',
|
||||
onEnd: (event) => {
|
||||
const { oldIndex, newIndex } = event
|
||||
const start = Math.min(oldIndex!, newIndex!)
|
||||
const end = Math.max(oldIndex!, newIndex!) - start + 1
|
||||
const changedRows = [...params.data.value].splice(start, end)
|
||||
params.onRowDrag(unref([...changedRows]))
|
||||
},
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
while (!tableBodyRef.value) {
|
||||
tableBodyRef.value = tableEl.value?.querySelector('tbody') || undefined
|
||||
await new Promise(resolve => setTimeout(resolve, 100))
|
||||
}
|
||||
})
|
||||
|
||||
watchOnce(() => tableBodyRef.value, (el) => {
|
||||
el && start()
|
||||
})
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import type { RouteLocationNormalized } from 'vue-router'
|
||||
import { useAppStore, useTabStore } from '@/store'
|
||||
import { useTabScroll } from '@/hooks/useTabScroll'
|
||||
import { useTabScroll } from '@/layouts/hooks/useTabScroll'
|
||||
import { useDraggable } from 'vue-draggable-plus'
|
||||
import IconClose from '~icons/icon-park-outline/close'
|
||||
import IconDelete from '~icons/icon-park-outline/delete-four'
|
||||
|
@ -1,5 +1,6 @@
|
||||
import type { NScrollbar } from 'naive-ui'
|
||||
import { ref, type Ref, watchEffect } from 'vue'
|
||||
import { ref, watchEffect } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
import { throttle } from 'radash'
|
||||
|
||||
export function useTabScroll(currentTabPath: Ref<string>) {
|
@ -77,16 +77,6 @@ export const staticRoutes: AppRoute.RowRoute[] = [
|
||||
id: 3,
|
||||
parentId: null,
|
||||
},
|
||||
{
|
||||
name: 'commonList',
|
||||
path: '/list/common-list',
|
||||
title: '常用列表',
|
||||
requiresAuth: true,
|
||||
icon: 'icon-park-outline:list-view',
|
||||
component: '/demo/list/common-list/index.vue',
|
||||
id: 301,
|
||||
parentId: 3,
|
||||
},
|
||||
{
|
||||
name: 'cardList',
|
||||
path: '/list/card-list',
|
||||
|
@ -46,7 +46,6 @@ export const useAuthStore = defineStore('auth-store', {
|
||||
clearAuthStorage() {
|
||||
local.remove('accessToken')
|
||||
local.remove('refreshToken')
|
||||
local.remove('userInfo')
|
||||
},
|
||||
|
||||
/* 用户登录 */
|
||||
@ -62,10 +61,15 @@ export const useAuthStore = defineStore('auth-store', {
|
||||
const { data } = await fetchLogin(loginData)
|
||||
|
||||
// 处理登录信息
|
||||
|
||||
await this.handleLoginInfo(data)
|
||||
},
|
||||
|
||||
// 更新用户信息
|
||||
await this.updataUserInfo()
|
||||
},
|
||||
async updataUserInfo() {
|
||||
const { data } = await fetchUserInfo()
|
||||
this.userInfo = data
|
||||
},
|
||||
/* 处理登录返回的数据 */
|
||||
async handleLoginInfo(data: any) {
|
||||
// 将token保存下来
|
||||
@ -74,12 +78,9 @@ export const useAuthStore = defineStore('auth-store', {
|
||||
local.set('refreshToken', data.refreshToken)
|
||||
}
|
||||
|
||||
const res = await fetchUserInfo()
|
||||
this.userInfo = res.data
|
||||
|
||||
// 添加路由和菜单
|
||||
const routeStore = useRouteStore()
|
||||
await routeStore.initAuthRoute()
|
||||
const { initAuthRoute } = useRouteStore()
|
||||
await initAuthRoute()
|
||||
|
||||
// 进行重定向跳转
|
||||
const route = unref(router.currentRoute)
|
||||
|
@ -1,53 +1,70 @@
|
||||
import { fetchDictList } from '@/api'
|
||||
import { session } from '@/utils'
|
||||
import { getDictDataByType } from '@/api'
|
||||
|
||||
// 字典缓存项接口
|
||||
interface DictCacheItem {
|
||||
data: Entity.DictData[]
|
||||
timestamp: number
|
||||
expireTime: number
|
||||
}
|
||||
|
||||
// 默认缓存时间:60分钟
|
||||
const DICT_CACHE_TIME = 60 * 60 * 1000
|
||||
|
||||
export const useDictStore = defineStore('dict-store', {
|
||||
state: () => {
|
||||
return {
|
||||
dictMap: {} as DictMap,
|
||||
isInitDict: false,
|
||||
dictMap: {} as Record<string, DictCacheItem>,
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
async dict(code: string) {
|
||||
// 调用前初始化
|
||||
if (!this.dictMap) {
|
||||
this.initDict()
|
||||
|
||||
async getDict(code: string): Promise<Entity.DictData[]> {
|
||||
const cachedItem = this.dictMap[code]
|
||||
const now = Date.now()
|
||||
|
||||
// 如果缓存存在且未过期,直接返回缓存数据
|
||||
if (cachedItem && now < cachedItem.expireTime) {
|
||||
return cachedItem.data
|
||||
}
|
||||
|
||||
const targetDict = await this.getDict(code)
|
||||
|
||||
return {
|
||||
data: () => targetDict,
|
||||
enum: () => Object.fromEntries(targetDict.map(({ value, label }) => [value, label])),
|
||||
valueMap: () => Object.fromEntries(targetDict.map(({ value, ...data }) => [value, data])),
|
||||
labelMap: () => Object.fromEntries(targetDict.map(({ label, ...data }) => [label, data])),
|
||||
}
|
||||
},
|
||||
async getDict(code: string) {
|
||||
const isExist = Reflect.has(this.dictMap, code)
|
||||
|
||||
if (isExist) {
|
||||
return this.dictMap[code]
|
||||
}
|
||||
else {
|
||||
// 如果缓存不存在或已过期,尝试重新获取数据
|
||||
try {
|
||||
return await this.getDictByNet(code)
|
||||
}
|
||||
catch (error) {
|
||||
// 如果有旧的缓存数据,返回旧数据
|
||||
if (cachedItem) {
|
||||
return cachedItem.data
|
||||
}
|
||||
|
||||
// 如果没有缓存数据,抛出错误
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
async getDictByNet(code: string) {
|
||||
const { data } = await fetchDictList(code)
|
||||
Reflect.set(this.dictMap, code, data)
|
||||
// 同步至session
|
||||
session.set('dict', this.dictMap)
|
||||
async getDictByNet(type: string): Promise<Entity.DictData[]> {
|
||||
const { data } = await getDictDataByType(type)
|
||||
const now = Date.now()
|
||||
|
||||
// 创建缓存项
|
||||
const cacheItem: DictCacheItem = {
|
||||
data,
|
||||
timestamp: now,
|
||||
expireTime: now + DICT_CACHE_TIME,
|
||||
}
|
||||
|
||||
this.dictMap[type] = cacheItem
|
||||
return data
|
||||
},
|
||||
initDict() {
|
||||
const dict = session.get('dict')
|
||||
if (dict) {
|
||||
Object.assign(this.dictMap, dict)
|
||||
}
|
||||
this.isInitDict = true
|
||||
|
||||
// 清理字典缓存
|
||||
cleanDict() {
|
||||
this.dictMap = {}
|
||||
},
|
||||
|
||||
// 移除指定字典缓存
|
||||
removeDict(dictType: string) {
|
||||
delete this.dictMap[dictType]
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
|
2
src/typings/entities/user.d.ts
vendored
2
src/typings/entities/user.d.ts
vendored
@ -31,5 +31,7 @@ namespace Entity {
|
||||
remark?: string
|
||||
/** 用户角色类型 */
|
||||
roles: Entity.Role[]
|
||||
/** 所属部门 */
|
||||
dept: Entity.Dept
|
||||
}
|
||||
}
|
||||
|
132
src/views/build-in/user-center/components/PersonalInfo.vue
Normal file
132
src/views/build-in/user-center/components/PersonalInfo.vue
Normal file
@ -0,0 +1,132 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, watch } from 'vue'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import { useAuthStore } from '@/store'
|
||||
import { updateUser } from '@/api/system/user'
|
||||
|
||||
const message = useMessage()
|
||||
const authStore = useAuthStore()
|
||||
const { userInfo } = authStore
|
||||
|
||||
// 加载状态
|
||||
const loading = ref(false)
|
||||
|
||||
// 表单数据
|
||||
const formData = reactive({
|
||||
nickName: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
gender: 'unknown' as 'male' | 'female',
|
||||
})
|
||||
|
||||
// 初始化表单数据
|
||||
function initFormData() {
|
||||
if (userInfo) {
|
||||
formData.nickName = userInfo.nickName || ''
|
||||
formData.email = userInfo.email || ''
|
||||
formData.phone = userInfo.phone || ''
|
||||
formData.gender = userInfo.gender
|
||||
}
|
||||
}
|
||||
|
||||
// 监听用户信息变化,自动更新表单
|
||||
watch(() => userInfo, initFormData, { immediate: true })
|
||||
|
||||
// 更新个人信息
|
||||
async function updateUserInfo() {
|
||||
if (!userInfo?.id) {
|
||||
message.error('用户信息不完整')
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
await updateUser(userInfo.id, formData)
|
||||
|
||||
// 刷新个人信息
|
||||
await authStore.updataUserInfo()
|
||||
message.success('个人信息更新成功')
|
||||
}
|
||||
catch (error) {
|
||||
console.error('更新用户信息失败:', error)
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-form
|
||||
:model="formData"
|
||||
label-placement="left"
|
||||
label-width="80px"
|
||||
class="max-w-400px"
|
||||
>
|
||||
<n-grid :cols="1">
|
||||
<n-grid-item>
|
||||
<n-form-item label="用户名">
|
||||
{{ userInfo.username }}
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<n-form-item label="昵称">
|
||||
<n-input v-model:value="formData.nickName" placeholder="请输入昵称" />
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<n-form-item label="性别">
|
||||
<n-select
|
||||
v-model:value="formData.gender"
|
||||
:options="[
|
||||
{ label: '男', value: 'male' },
|
||||
{ label: '女', value: 'female' },
|
||||
]"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<n-form-item label="邮箱">
|
||||
<n-input v-model:value="formData.email" placeholder="请输入邮箱" />
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<n-form-item label="手机号">
|
||||
<n-input v-model:value="formData.phone" placeholder="请输入手机号" />
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<n-form-item label="所属部门">
|
||||
{{ userInfo?.dept?.deptName || '-' }}
|
||||
</n-form-item>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
|
||||
<n-form-item label="拥有角色">
|
||||
<n-space>
|
||||
<n-tag
|
||||
v-for="(role, index) in userInfo?.roles"
|
||||
:key="index"
|
||||
type="primary"
|
||||
size="medium"
|
||||
>
|
||||
{{ role?.roleName || role?.name || `角色${index + 1}` }}
|
||||
</n-tag>
|
||||
<n-tag v-if="!userInfo?.roles || userInfo?.roles.length === 0" type="default" size="medium">
|
||||
暂无角色
|
||||
</n-tag>
|
||||
</n-space>
|
||||
</n-form-item>
|
||||
|
||||
<!-- 更新按钮 -->
|
||||
<n-form-item label=" ">
|
||||
<n-button
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
@click="updateUserInfo"
|
||||
>
|
||||
更新个人信息
|
||||
</n-button>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</template>
|
@ -1,15 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useAuthStore } from '@/store'
|
||||
import PersonalInfo from './components/PersonalInfo.vue'
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const { userInfo } = authStore
|
||||
const { userInfo } = storeToRefs(authStore)
|
||||
|
||||
// 性别显示映射
|
||||
const genderMap = {
|
||||
male: '男',
|
||||
female: '女',
|
||||
unknown: '未知',
|
||||
}
|
||||
// 当前选中的标签页
|
||||
const activeTab = ref('profile')
|
||||
|
||||
// 获取问候语
|
||||
function getGreeting() {
|
||||
@ -28,62 +26,91 @@ function getGreeting() {
|
||||
return '晚上好'
|
||||
return '夜深了'
|
||||
}
|
||||
|
||||
// 组件挂载时更新用户信息
|
||||
onMounted(async () => {
|
||||
await authStore.updataUserInfo()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-space vertical>
|
||||
<!-- 基本信息卡片 -->
|
||||
<n-card :title="`${getGreeting()},${userInfo?.nickName || userInfo?.username || '用户'},欢迎使用个人中心`">
|
||||
<n-flex gap="16px" align="center">
|
||||
<!-- 欢迎标题 -->
|
||||
<n-card>
|
||||
<n-flex align="center" justify="space-between">
|
||||
<div>
|
||||
<n-h2>
|
||||
{{ getGreeting() }},{{ userInfo?.nickName || userInfo.username }}
|
||||
</n-h2>
|
||||
<n-p class="text-sm opacity-90 m-0">
|
||||
欢迎使用个人中心
|
||||
</n-p>
|
||||
</div>
|
||||
<n-avatar
|
||||
round
|
||||
:size="128"
|
||||
:size="64"
|
||||
:src="userInfo?.avatar || `https://api.dicebear.com/9.x/adventurer-neutral/svg?seed=${userInfo!.username}`"
|
||||
class="m-x-6"
|
||||
/>
|
||||
|
||||
<n-descriptions
|
||||
label-placement="left"
|
||||
class="flex-1"
|
||||
:column="2"
|
||||
>
|
||||
<n-descriptions-item label="用户名">
|
||||
{{ userInfo?.username || '-' }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="昵称">
|
||||
{{ userInfo?.nickName || '-' }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="性别">
|
||||
<n-tag size="small">
|
||||
{{ genderMap[userInfo?.gender || 'unknown'] }}
|
||||
</n-tag>
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="邮箱">
|
||||
{{ userInfo?.email || '-' }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="手机号">
|
||||
{{ userInfo?.phone || '-' }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="部门ID">
|
||||
{{ userInfo?.deptId || '-' }}
|
||||
</n-descriptions-item>
|
||||
<n-descriptions-item label="拥有角色" :span="2">
|
||||
<n-space>
|
||||
<n-tag
|
||||
v-for="(role, index) in userInfo?.roles"
|
||||
:key="index"
|
||||
type="primary"
|
||||
size="small"
|
||||
>
|
||||
{{ role?.roleName || role?.name || `角色${index + 1}` }}
|
||||
</n-tag>
|
||||
<n-tag v-if="!userInfo?.roles || userInfo?.roles.length === 0" type="default" size="small">
|
||||
暂无角色
|
||||
</n-tag>
|
||||
</n-space>
|
||||
</n-descriptions-item>
|
||||
</n-descriptions>
|
||||
</n-flex>
|
||||
</n-card>
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<n-card>
|
||||
<n-tabs
|
||||
v-model:value="activeTab"
|
||||
type="line"
|
||||
placement="left"
|
||||
tab-style="min-height: 30px;"
|
||||
pane-style="padding-left: 1rem;"
|
||||
>
|
||||
<n-tab-pane name="profile">
|
||||
<template #tab>
|
||||
<icon-park-outline-user class="mr-2" />
|
||||
个人信息
|
||||
</template>
|
||||
<n-h5>
|
||||
个人信息
|
||||
</n-h5>
|
||||
<n-divider />
|
||||
<PersonalInfo />
|
||||
</n-tab-pane>
|
||||
|
||||
<n-tab-pane name="security">
|
||||
<template #tab>
|
||||
<icon-park-outline-lock class="mr-2" />
|
||||
安全设置
|
||||
</template>
|
||||
<n-h5>
|
||||
安全设置
|
||||
</n-h5>
|
||||
<n-divider />
|
||||
<n-empty description="功能开发中..." />
|
||||
</n-tab-pane>
|
||||
|
||||
<n-tab-pane name="notification">
|
||||
<template #tab>
|
||||
<icon-park-outline-remind class="mr-2" />
|
||||
通知设置
|
||||
</template>
|
||||
<n-h5>
|
||||
通知设置
|
||||
</n-h5>
|
||||
<n-divider />
|
||||
<n-empty description="功能开发中..." />
|
||||
</n-tab-pane>
|
||||
|
||||
<n-tab-pane name="preference">
|
||||
<template #tab>
|
||||
<icon-park-outline-setting-one class="mr-2" />
|
||||
偏好设置
|
||||
</template>
|
||||
<n-h5>
|
||||
偏好设置
|
||||
</n-h5>
|
||||
<n-divider />
|
||||
<n-empty description="功能开发中..." />
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</n-card>
|
||||
</n-space>
|
||||
</template>
|
||||
|
@ -1,117 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
visible: boolean
|
||||
type?: ModalType
|
||||
modalData?: any
|
||||
}
|
||||
const {
|
||||
visible,
|
||||
type = 'add',
|
||||
modalData = null,
|
||||
} = defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
const defaultFormModal: Entity.User = {
|
||||
userName: '',
|
||||
gender: 0,
|
||||
email: '',
|
||||
role: [],
|
||||
}
|
||||
const formModel = ref({ ...defaultFormModal })
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:visible', visible: boolean): void
|
||||
}
|
||||
|
||||
const modalVisible = computed({
|
||||
get() {
|
||||
return visible
|
||||
},
|
||||
set(visible) {
|
||||
closeModal(visible)
|
||||
},
|
||||
})
|
||||
function closeModal(visible = false) {
|
||||
emit('update:visible', visible)
|
||||
}
|
||||
type ModalType = 'add' | 'edit'
|
||||
const title = computed(() => {
|
||||
const titles: Record<ModalType, string> = {
|
||||
add: '添加用户',
|
||||
edit: '编辑用户',
|
||||
}
|
||||
return titles[type]
|
||||
})
|
||||
|
||||
function UpdateFormModelByModalType() {
|
||||
const handlers = {
|
||||
add: () => {
|
||||
formModel.value = { ...defaultFormModal }
|
||||
},
|
||||
edit: () => {
|
||||
if (modalData)
|
||||
formModel.value = { ...modalData }
|
||||
},
|
||||
}
|
||||
handlers[type]()
|
||||
}
|
||||
watch(
|
||||
() => visible,
|
||||
(newValue) => {
|
||||
if (newValue)
|
||||
UpdateFormModelByModalType()
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-modal
|
||||
v-model:show="modalVisible"
|
||||
:mask-closable="false"
|
||||
preset="card"
|
||||
:title="title"
|
||||
class="w-700px"
|
||||
:segmented="{
|
||||
content: true,
|
||||
action: true,
|
||||
}"
|
||||
>
|
||||
<n-form label-placement="left" :model="formModel" label-align="left" :label-width="80">
|
||||
<n-grid :cols="24" :x-gap="18">
|
||||
<n-form-item-grid-item :span="12" label="用户名" path="name">
|
||||
<n-input v-model:value="formModel.userName" />
|
||||
</n-form-item-grid-item>
|
||||
<n-form-item-grid-item :span="12" label="年龄" path="age">
|
||||
<n-input-number v-model:value="formModel.gender" />
|
||||
</n-form-item-grid-item>
|
||||
<n-form-item-grid-item :span="12" label="性别" path="gender">
|
||||
<n-radio-group v-model:value="formModel.gender">
|
||||
<n-space>
|
||||
<n-radio :value="1">
|
||||
男
|
||||
</n-radio>
|
||||
<n-radio :value="0">
|
||||
女
|
||||
</n-radio>
|
||||
</n-space>
|
||||
</n-radio-group>
|
||||
</n-form-item-grid-item>
|
||||
<n-form-item-grid-item :span="12" label="邮箱" path="email">
|
||||
<n-input v-model:value="formModel.email" />
|
||||
</n-form-item-grid-item>
|
||||
</n-grid>
|
||||
</n-form>
|
||||
<template #action>
|
||||
<n-space justify="center">
|
||||
<n-button @click="closeModal()">
|
||||
取消
|
||||
</n-button>
|
||||
<n-button type="primary">
|
||||
提交
|
||||
</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
@ -1,209 +0,0 @@
|
||||
<script setup lang="tsx">
|
||||
import type { DataTableColumns, FormInst } from 'naive-ui'
|
||||
import { Gender } from '@/constants'
|
||||
import { useBoolean } from '@/hooks'
|
||||
import { fetchUserPage } from '@/api'
|
||||
import { NButton, NPopconfirm, NSpace, NSwitch, NTag } from 'naive-ui'
|
||||
import TableModal from './components/TableModal.vue'
|
||||
|
||||
const { bool: loading, setTrue: startLoading, setFalse: endLoading } = useBoolean(false)
|
||||
const { bool: visible, setTrue: openModal } = useBoolean(false)
|
||||
|
||||
const initialModel = {
|
||||
condition_1: '',
|
||||
condition_2: '',
|
||||
condition_3: '',
|
||||
condition_4: '',
|
||||
}
|
||||
const model = ref({ ...initialModel })
|
||||
|
||||
const formRef = ref<FormInst | null>()
|
||||
function sendMail(id?: number) {
|
||||
window.$message.success(`删除用户id:${id}`)
|
||||
}
|
||||
const columns: DataTableColumns<Entity.User> = [
|
||||
{
|
||||
title: '姓名',
|
||||
align: 'center',
|
||||
key: 'userName',
|
||||
},
|
||||
{
|
||||
title: '年龄',
|
||||
align: 'center',
|
||||
key: 'age',
|
||||
},
|
||||
{
|
||||
title: '性别',
|
||||
align: 'center',
|
||||
key: 'gender',
|
||||
render: (row) => {
|
||||
const tagType = {
|
||||
0: 'primary',
|
||||
1: 'success',
|
||||
} as const
|
||||
if (row.gender) {
|
||||
return (
|
||||
<NTag type={tagType[row.gender]}>
|
||||
{Gender[row.gender]}
|
||||
</NTag>
|
||||
)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '邮箱',
|
||||
align: 'center',
|
||||
key: 'email',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
align: 'center',
|
||||
key: 'status',
|
||||
render: (row) => {
|
||||
return (
|
||||
<NSwitch
|
||||
value={row.status}
|
||||
checked-value={1}
|
||||
unchecked-value={0}
|
||||
onUpdateValue={(value: 0 | 1) =>
|
||||
handleUpdateDisabled(value, row.id!)}
|
||||
>
|
||||
{{ checked: () => '启用', unchecked: () => '禁用' }}
|
||||
</NSwitch>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
align: 'center',
|
||||
key: 'actions',
|
||||
render: (row) => {
|
||||
return (
|
||||
<NSpace justify="center">
|
||||
<NButton
|
||||
size="small"
|
||||
onClick={() => handleEditTable(row)}
|
||||
>
|
||||
编辑
|
||||
</NButton>
|
||||
<NPopconfirm onPositiveClick={() => sendMail(row.id)}>
|
||||
{{
|
||||
default: () => '确认删除',
|
||||
trigger: () => <NButton size="small">删除</NButton>,
|
||||
}}
|
||||
</NPopconfirm>
|
||||
</NSpace>
|
||||
)
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const listData = ref<Entity.User[]>([])
|
||||
function handleUpdateDisabled(value: 0 | 1, id: number) {
|
||||
const index = listData.value.findIndex(item => item.id === id)
|
||||
if (index > -1)
|
||||
listData.value[index].status = value
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getUserList()
|
||||
})
|
||||
async function getUserList() {
|
||||
startLoading()
|
||||
await fetchUserPage().then((res: any) => {
|
||||
listData.value = res.data.list
|
||||
endLoading()
|
||||
})
|
||||
}
|
||||
function changePage(page: number, size: number) {
|
||||
window.$message.success(`分页器:${page},${size}`)
|
||||
}
|
||||
function handleResetSearch() {
|
||||
model.value = { ...initialModel }
|
||||
}
|
||||
|
||||
type ModalType = 'add' | 'edit'
|
||||
const modalType = ref<ModalType>('add')
|
||||
function setModalType(type: ModalType) {
|
||||
modalType.value = type
|
||||
}
|
||||
|
||||
const editData = ref<Entity.User | null>(null)
|
||||
function setEditData(data: Entity.User | null) {
|
||||
editData.value = data
|
||||
}
|
||||
|
||||
function handleEditTable(row: Entity.User) {
|
||||
setEditData(row)
|
||||
setModalType('edit')
|
||||
openModal()
|
||||
}
|
||||
function handleAddTable() {
|
||||
openModal()
|
||||
setModalType('add')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NSpace vertical size="large">
|
||||
<n-card>
|
||||
<n-form ref="formRef" :model="model" label-placement="left" inline :show-feedback="false">
|
||||
<n-flex>
|
||||
<n-form-item label="姓名" path="condition_1">
|
||||
<n-input v-model:value="model.condition_1" placeholder="请输入" />
|
||||
</n-form-item>
|
||||
<n-form-item label="年龄" path="condition_2">
|
||||
<n-input v-model:value="model.condition_2" placeholder="请输入" />
|
||||
</n-form-item>
|
||||
<n-form-item label="性别" path="condition_3">
|
||||
<n-input v-model:value="model.condition_3" placeholder="请输入" />
|
||||
</n-form-item>
|
||||
<n-form-item label="地址" path="condition_4">
|
||||
<n-input v-model:value="model.condition_4" placeholder="请输入" />
|
||||
</n-form-item>
|
||||
<n-flex class="ml-auto">
|
||||
<NButton type="primary" @click="getUserList">
|
||||
<template #icon>
|
||||
<icon-park-outline-search />
|
||||
</template>
|
||||
搜索
|
||||
</NButton>
|
||||
<NButton strong secondary @click="handleResetSearch">
|
||||
<template #icon>
|
||||
<icon-park-outline-redo />
|
||||
</template>
|
||||
重置
|
||||
</NButton>
|
||||
</n-flex>
|
||||
</n-flex>
|
||||
</n-form>
|
||||
</n-card>
|
||||
<n-card>
|
||||
<NSpace vertical size="large">
|
||||
<div class="flex gap-4">
|
||||
<NButton type="primary" @click="handleAddTable">
|
||||
<template #icon>
|
||||
<icon-park-outline-add-one />
|
||||
</template>
|
||||
新增
|
||||
</NButton>
|
||||
<NButton strong secondary>
|
||||
<template #icon>
|
||||
<icon-park-outline-afferent />
|
||||
</template>
|
||||
批量导入
|
||||
</NButton>
|
||||
<NButton strong secondary class="ml-a">
|
||||
<template #icon>
|
||||
<icon-park-outline-download />
|
||||
</template>
|
||||
下载
|
||||
</NButton>
|
||||
</div>
|
||||
<n-data-table :columns="columns" :data="listData" :loading="loading" />
|
||||
<Pagination :count="100" @change="changePage" />
|
||||
<TableModal v-model:visible="visible" :type="modalType" :modal-data="editData" />
|
||||
</NSpace>
|
||||
</n-card>
|
||||
</NSpace>
|
||||
</template>
|
@ -1,170 +1,103 @@
|
||||
<script setup lang="tsx">
|
||||
import type { DataTableColumns, FormInst, NDataTable } from 'naive-ui'
|
||||
import { Gender } from '@/constants'
|
||||
import { useBoolean } from '@/hooks'
|
||||
import { useTableDrag } from '@/hooks/useTableDrag'
|
||||
import { fetchUserPage } from '@/api'
|
||||
import { NButton, NPopconfirm, NSpace, NSwitch, NTag } from 'naive-ui'
|
||||
import type { ProDataTableColumns, ProDataTableDragSortEnd } from 'pro-naive-ui'
|
||||
import { AimOutlined } from '@vicons/antd'
|
||||
import { NButton, NIcon } from 'naive-ui'
|
||||
import { move } from 'pro-naive-ui'
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const { bool: loading, setTrue: startLoading, setFalse: endLoading } = useBoolean(false)
|
||||
|
||||
const initialModel = {
|
||||
condition_1: '',
|
||||
condition_2: '',
|
||||
condition_3: '',
|
||||
condition_4: '',
|
||||
interface Song {
|
||||
no: number
|
||||
title: string
|
||||
length: string
|
||||
}
|
||||
const model = ref({ ...initialModel })
|
||||
|
||||
const formRef = ref<FormInst | null>()
|
||||
function sendMail(id?: number) {
|
||||
window.$message.success(`删除用户id:${id}`)
|
||||
const dragHandle = ref(true)
|
||||
const loading = ref(false)
|
||||
|
||||
// 初始设置为空数组
|
||||
const data = ref<Song[]>([])
|
||||
|
||||
// 模拟的歌曲数据
|
||||
const songData: Song[] = [
|
||||
{ no: 3, title: 'Wonderwall', length: '4:18' },
|
||||
{ no: 4, title: 'Don\'t Look Back in Anger', length: '4:48' },
|
||||
{ no: 12, title: 'Champagne Supernova', length: '7:27' },
|
||||
{ no: 33, title: 'Wonderwall', length: '4:18' },
|
||||
{ no: 44, title: 'Don\'t Look Back in Anger', length: '4:48' },
|
||||
{ no: 122, title: 'Champagne Supernova', length: '7:27' },
|
||||
]
|
||||
|
||||
// 模拟异步请求获取数据
|
||||
function fetchData() {
|
||||
loading.value = true
|
||||
return new Promise<Song[]>((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(songData)
|
||||
loading.value = false
|
||||
}, 1500) // 1.5秒后加载完成
|
||||
})
|
||||
}
|
||||
const columns: DataTableColumns<Entity.User> = [
|
||||
|
||||
onMounted(async () => {
|
||||
data.value = await fetchData()
|
||||
})
|
||||
|
||||
const columns: ProDataTableColumns<Song> = [
|
||||
{
|
||||
title: '姓名',
|
||||
align: 'center',
|
||||
key: 'userName',
|
||||
path: 'dragSort',
|
||||
},
|
||||
{
|
||||
title: '年龄',
|
||||
align: 'center',
|
||||
key: 'age',
|
||||
title: '自定义排序名称',
|
||||
path: 'dragSort',
|
||||
width: 160,
|
||||
},
|
||||
{
|
||||
title: '性别',
|
||||
align: 'center',
|
||||
key: 'gender',
|
||||
render: (row) => {
|
||||
const tagType = {
|
||||
0: 'primary',
|
||||
1: 'success',
|
||||
} as const
|
||||
if (row.gender) {
|
||||
return (
|
||||
<NTag type={tagType[row.gender]}>
|
||||
{Gender[row.gender]}
|
||||
</NTag>
|
||||
)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '邮箱',
|
||||
align: 'center',
|
||||
key: 'email',
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
align: 'center',
|
||||
key: 'status',
|
||||
render: (row) => {
|
||||
title: '自定义渲染拖拽手柄',
|
||||
path: 'dragSort',
|
||||
width: 160,
|
||||
render() {
|
||||
return (
|
||||
<NSwitch
|
||||
value={row.status}
|
||||
checked-value={1}
|
||||
unchecked-value={0}
|
||||
onUpdateValue={(value: 0 | 1) =>
|
||||
handleUpdateDisabled(value, row.id!)}
|
||||
>
|
||||
{{ checked: () => '启用', unchecked: () => '禁用' }}
|
||||
</NSwitch>
|
||||
<NButton text={true} class="cursor-grab align-middle">
|
||||
<NIcon size={16}>
|
||||
<AimOutlined />
|
||||
</NIcon>
|
||||
</NButton>
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
align: 'center',
|
||||
key: 'actions',
|
||||
render: (row) => {
|
||||
return (
|
||||
<NSpace justify="center">
|
||||
<NPopconfirm onPositiveClick={() => sendMail(row.id)}>
|
||||
{{
|
||||
default: () => '确认删除',
|
||||
trigger: () => <NButton size="small">删除</NButton>,
|
||||
}}
|
||||
</NPopconfirm>
|
||||
</NSpace>
|
||||
)
|
||||
},
|
||||
title: '不依赖手柄',
|
||||
tooltip: '设置为 true 后再来拖拽',
|
||||
path: 'length',
|
||||
},
|
||||
]
|
||||
|
||||
const listData = ref<Entity.User[]>([])
|
||||
function handleUpdateDisabled(value: 0 | 1, id: number) {
|
||||
const index = listData.value.findIndex(item => item.id === id)
|
||||
if (index > -1)
|
||||
listData.value[index].status = value
|
||||
}
|
||||
|
||||
const tableRef = ref<InstanceType<typeof NDataTable>>()
|
||||
useTableDrag({
|
||||
tableRef,
|
||||
data: listData,
|
||||
onRowDrag(data) {
|
||||
const target = data[data.length - 1]
|
||||
window.$message.success(`拖拽数据 id: ${target.id} name: ${target.userName}`)
|
||||
},
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
getUserList()
|
||||
})
|
||||
async function getUserList() {
|
||||
startLoading()
|
||||
await fetchUserPage().then((res: any) => {
|
||||
listData.value = res.data.list
|
||||
endLoading()
|
||||
})
|
||||
}
|
||||
function changePage(page: number, size: number) {
|
||||
window.$message.success(`分页器:${page},${size}`)
|
||||
}
|
||||
function handleResetSearch() {
|
||||
model.value = { ...initialModel }
|
||||
/**
|
||||
* 你需要在这里同步数据源
|
||||
*/
|
||||
function onDragSortEnd(event: Parameters<ProDataTableDragSortEnd>['0']) {
|
||||
const { newIndex, oldIndex } = event
|
||||
move(data.value, oldIndex, newIndex)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NSpace vertical size="large">
|
||||
<n-card>
|
||||
<n-form ref="formRef" :model="model" label-placement="left" inline :show-feedback="false">
|
||||
<n-flex>
|
||||
<n-form-item label="姓名" path="condition_1">
|
||||
<n-input v-model:value="model.condition_1" placeholder="请输入" />
|
||||
</n-form-item>
|
||||
<n-form-item label="年龄" path="condition_2">
|
||||
<n-input v-model:value="model.condition_2" placeholder="请输入" />
|
||||
</n-form-item>
|
||||
<n-form-item label="性别" path="condition_3">
|
||||
<n-input v-model:value="model.condition_3" placeholder="请输入" />
|
||||
</n-form-item>
|
||||
<n-form-item label="地址" path="condition_4">
|
||||
<n-input v-model:value="model.condition_4" placeholder="请输入" />
|
||||
</n-form-item>
|
||||
<n-flex class="ml-auto">
|
||||
<NButton type="primary" @click="getUserList">
|
||||
<template #icon>
|
||||
<icon-park-outline-search />
|
||||
</template>
|
||||
搜索
|
||||
</NButton>
|
||||
<NButton strong secondary @click="handleResetSearch">
|
||||
<template #icon>
|
||||
<icon-park-outline-redo />
|
||||
</template>
|
||||
重置
|
||||
</NButton>
|
||||
</n-flex>
|
||||
</n-flex>
|
||||
</n-form>
|
||||
</n-card>
|
||||
<n-card>
|
||||
<NSpace vertical size="large">
|
||||
<n-data-table ref="tableRef" row-class-name="drag-handle" :columns="columns" :data="listData" :loading="loading" />
|
||||
<Pagination :count="100" @change="changePage" />
|
||||
</NSpace>
|
||||
</n-card>
|
||||
</NSpace>
|
||||
<pro-data-table
|
||||
:data="data"
|
||||
:loading
|
||||
:columns="columns"
|
||||
:drag-sort-options="{
|
||||
columnPath: 'dragSort',
|
||||
// 为 false 则不限制为手柄拖拽
|
||||
handle: dragHandle ? undefined : false,
|
||||
onEnd: onDragSortEnd,
|
||||
}"
|
||||
row-key="no"
|
||||
>
|
||||
<template #extra>
|
||||
<div>
|
||||
依赖手柄拖拽: <n-switch v-model:value="dragHandle" />
|
||||
</div>
|
||||
</template>
|
||||
</pro-data-table>
|
||||
</template>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { useBoolean } from '@/hooks'
|
||||
import { deleteDept, getDeptList } from '@/api'
|
||||
import type { SearchQuery } from '@/api'
|
||||
import type { MenuSearchQuery } from '@/api'
|
||||
import { createDeptColumns, deptSearchColumns } from './columns'
|
||||
import DeptModal from './components/DeptModal.vue'
|
||||
import arrayToTree from 'array-to-tree'
|
||||
@ -12,7 +12,7 @@ const { bool: loading, setTrue: startLoading, setFalse: endLoading } = useBoolea
|
||||
const deptModalRef = ref<InstanceType<typeof DeptModal>>()
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = createProSearchForm<SearchQuery>({
|
||||
const searchForm = createProSearchForm<MenuSearchQuery>({
|
||||
initialValues: {},
|
||||
onSubmit: getAllDepts,
|
||||
onReset: getAllDepts,
|
||||
@ -40,7 +40,7 @@ const deptColumns = createDeptColumns({
|
||||
const tableData = ref<Entity.Dept[]>([])
|
||||
|
||||
// 获取所有部门数据并构建树形结构
|
||||
async function getAllDepts(params?: SearchQuery) {
|
||||
async function getAllDepts(params?: MenuSearchQuery) {
|
||||
startLoading()
|
||||
try {
|
||||
const { data } = await getDeptList(params)
|
||||
|
@ -3,7 +3,7 @@ import { NButton, NPopconfirm, NSpace, NTag } from 'naive-ui'
|
||||
import { createIcon } from '@/utils'
|
||||
import { renderProCopyableText } from 'pro-naive-ui'
|
||||
import type { ProSearchFormColumns } from 'pro-naive-ui'
|
||||
import type { SearchQuery } from '@/api'
|
||||
import type { MenuSearchQuery } from '@/api'
|
||||
|
||||
// 菜单管理columns配置函数
|
||||
interface MenuColumnActions {
|
||||
@ -12,7 +12,7 @@ interface MenuColumnActions {
|
||||
onAdd: (row: Entity.Menu) => void
|
||||
}
|
||||
|
||||
export const searchColumns: ProSearchFormColumns<SearchQuery> = [
|
||||
export const searchColumns: ProSearchFormColumns<MenuSearchQuery> = [
|
||||
{
|
||||
title: '菜单名称',
|
||||
path: 'title',
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { useBoolean } from '@/hooks'
|
||||
import { deleteMenu, getMenuList } from '@/api'
|
||||
import type { SearchQuery } from '@/api'
|
||||
import type { MenuSearchQuery } from '@/api'
|
||||
import { createMenuColumns, searchColumns } from './columns'
|
||||
import MenuModal from './components/MenuModal.vue'
|
||||
import arrayToTree from 'array-to-tree'
|
||||
@ -12,7 +12,7 @@ const { bool: loading, setTrue: startLoading, setFalse: endLoading } = useBoolea
|
||||
const menuModalRef = ref()
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = createProSearchForm<SearchQuery>({
|
||||
const searchForm = createProSearchForm<MenuSearchQuery>({
|
||||
initialValues: {},
|
||||
onSubmit: getAllRoutes,
|
||||
onReset: getAllRoutes,
|
||||
@ -37,7 +37,7 @@ async function deleteData(id: number) {
|
||||
}
|
||||
|
||||
const tableData = ref<Entity.Menu[]>([])
|
||||
async function getAllRoutes(params?: SearchQuery) {
|
||||
async function getAllRoutes(params?: MenuSearchQuery) {
|
||||
startLoading()
|
||||
try {
|
||||
const { data } = await getMenuList(params)
|
||||
|
@ -6,12 +6,17 @@
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"baseUrl": ".",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"moduleResolution": "bundler",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
},
|
||||
"resolveJsonModule": true,
|
||||
"types": ["node", "vite/client", "naive-ui/volar", "unplugin-icons/types/vue"],
|
||||
"types": [
|
||||
"node",
|
||||
"vite/client",
|
||||
"naive-ui/volar",
|
||||
"unplugin-icons/types/vue"
|
||||
],
|
||||
"allowJs": true,
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
@ -21,5 +26,5 @@
|
||||
"isolatedModules": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"exclude": ["node_modules", "dist", "eslint.config.js"]
|
||||
"exclude": ["node_modules", "eslint.config.js"]
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user