feat: 统一函数命名

This commit is contained in:
2026-08-02 12:02:17 +08:00
parent 97235386ca
commit 99d5392a68
64 changed files with 331 additions and 1459 deletions

View File

@@ -11,7 +11,7 @@
"preview": "vite preview", "preview": "vite preview",
"preinstall": "npx only-allow pnpm", "preinstall": "npx only-allow pnpm",
"format": "prettier --write src/", "format": "prettier --write src/",
"type-check": "vue-tsc --build", "type-check": "tsc --noEmit -p tsconfig.app.json",
"deploy": "./scripts/deploy.sh" "deploy": "./scripts/deploy.sh"
}, },
"dependencies": { "dependencies": {
@@ -24,7 +24,6 @@
"@radix-ui/react-label": "^2.1.11", "@radix-ui/react-label": "^2.1.11",
"@radix-ui/react-separator": "^1.1.11", "@radix-ui/react-separator": "^1.1.11",
"@radix-ui/react-slot": "^1.3.0", "@radix-ui/react-slot": "^1.3.0",
"@radix-ui/react-tooltip": "^1.2.11",
"@tailwindcss/vite": "^4.3.2", "@tailwindcss/vite": "^4.3.2",
"@tanstack/react-query": "^5.101.2", "@tanstack/react-query": "^5.101.2",
"@tanstack/react-router": "^1.170.17", "@tanstack/react-router": "^1.170.17",
@@ -64,7 +63,6 @@
"eslint": "^9.39.4", "eslint": "^9.39.4",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.6", "eslint-plugin-prettier": "^5.5.6",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.26", "eslint-plugin-react-refresh": "^0.4.26",
"globals": "^16.5.0", "globals": "^16.5.0",

1108
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,31 +0,0 @@
#!/bin/bash
set -euo pipefail
PROJECT_ROOT=$(cd -- "$(dirname "${BASH_SOURCE[0]}")/.." &> /dev/null && pwd)
REMOTE_SERVER="01"
REMOTE_DIR="/srv/docker/nginx/www/admin"
cd "${PROJECT_ROOT}"
pnpm run build
echo $PWD
tar -czf dist.tar.gz ./dist >/dev/null 2>&1
scp dist.tar.gz "${REMOTE_SERVER}:${REMOTE_DIR}"
ssh "$REMOTE_SERVER" bash -s -- "${REMOTE_DIR}" << 'EOF'
REMOTE_DIR="$1"
set -euo pipefail
cd "${REMOTE_DIR}"
rm -rf dist
tar -xzf dist.tar.gz
rm dist.tar.gz
EOF
rm -rf dist
rm -f dist.tar.gz
echo "部署完成"

View File

@@ -1,22 +1,22 @@
import { http } from '@/lib' import { http } from '@/lib'
import type { Category, CategoryForm, PageParams } from '@/schemas' import type { Category, CategoryForm, PageParams } from '@/schemas'
export const getCategories = (params: PageParams) => { export const listCategories = (params: PageParams) => {
return http.get<Category[], ApiPageResponse<Category>>('/category', { params }) return http.get<Category[], ApiPageResponse<Category>>('/categories', { params })
} }
export const createCategory = (data: CategoryForm) => { export const createCategory = (data: CategoryForm) => {
return http.post('/category', data) return http.post('/categories', data)
} }
export const updateCategory = (id: number, data: CategoryForm) => { export const updateCategory = (id: number, data: CategoryForm) => {
return http.patch(`/category/${id}`, data) return http.patch(`/categories/${id}`, data)
} }
export const deleteCategory = (id: number) => { export const deleteCategory = (id: number) => {
return http.delete(`/category/${id}`) return http.delete(`/categories/${id}`)
} }
export const listAllCategories = () => { export const listAllCategories = () => {
return http.get<Category[]>('/category/all') return http.get<Category[]>('/categories/all')
} }

View File

@@ -1,22 +1,22 @@
import { http } from '@/lib' import { http } from '@/lib'
import type { PageParams, Post, PostFormOutput } from '@/schemas' import type { PageParams, Post, PostFormOutput } from '@/schemas'
export const getPosts = (params: PageParams) => { export const listPosts = (params: PageParams) => {
return http.get<Post[], ApiPageResponse<Post>>('/post', { params }) return http.get<Post[], ApiPageResponse<Post>>('/posts', { params })
} }
export const createPost = (data: PostFormOutput) => { export const createPost = (data: PostFormOutput) => {
return http.post<{ post_id: number }>('/post', data) return http.post<{ post_id: number }>('/posts', data)
} }
export const updatePost = (id: number, data: PostFormOutput) => { export const updatePost = (id: number, data: PostFormOutput) => {
return http.patch(`/post/${id}`, data) return http.patch(`/posts/${id}`, data)
} }
export const getPost = (id: number) => { export const getPost = (id: number) => {
return http.get<Post>(`/post/${id}`) return http.get<Post>(`/posts/${id}`)
} }
export const deletePost = (id: number) => { export const deletePost = (id: number) => {
return http.delete(`/post/${id}`) return http.delete(`/posts/${id}`)
} }

View File

@@ -1,22 +1,22 @@
import { http } from '@/lib' import { http } from '@/lib'
import type { PageParams, Tag, TagForm } from '@/schemas' import type { PageParams, Tag, TagForm } from '@/schemas'
export const getTags = (params: PageParams) => { export const listTags = (params: PageParams) => {
return http.get<Tag[], ApiPageResponse<Tag>>('/tag', { params }) return http.get<Tag[], ApiPageResponse<Tag>>('/tags', { params })
} }
export const createTag = (data: TagForm) => { export const createTag = (data: TagForm) => {
return http.post('/tag', data) return http.post('/tags', data)
} }
export const updateTag = (id: number, data: TagForm) => { export const updateTag = (id: number, data: TagForm) => {
return http.patch(`/tag/${id}`, data) return http.patch(`/tags/${id}`, data)
} }
export const deleteTag = (id: number) => { export const deleteTag = (id: number) => {
return http.delete(`/tag/${id}`) return http.delete(`/tags/${id}`)
} }
export const listAllTags = () => { export const listAllTags = () => {
return http.get<Tag[]>('/tag/all') return http.get<Tag[]>('/tags/all')
} }

View File

@@ -1,30 +1,30 @@
import { http } from '@/lib' import { http } from '@/lib'
import type { PageParams, SearchSysApiParams, SysApi, SysApiForm } from '@/schemas' import type { Api, ApiForm, PageParams, SearchApiParams } from '@/schemas'
export const getSysApis = (params: PageParams & SearchSysApiParams) => { export const listApis = (params: PageParams & SearchApiParams) => {
return http.get<SysApi[], ApiPageResponse<SysApi>>('/api', { params }) return http.get<Api[], ApiPageResponse<Api>>('/apis', { params })
} }
export const getSysApiGroups = () => { export const listApiGroups = () => {
return http.get<string[]>('/api/group-names') return http.get<string[]>('/apis/groups')
} }
export const getAllSysApi = () => { export const listAllApi = () => {
return http.get<SysApi[]>('/api/all') return http.get<Api[]>('/apis/all')
} }
export const getRoleApis = (roleId: number) => { export const getRoleApis = (roleId: number) => {
return http.get<SysApi[]>(`/role/${roleId}/apis`) return http.get<Api[]>(`/roles/${roleId}/apis`)
} }
export const updateSysApi = (id: number, data: SysApiForm) => { export const updateApi = (id: number, data: ApiForm) => {
return http.patch('/api/' + id, data) return http.patch('/apis/' + id, data)
} }
export const createSysApi = (data: SysApiForm) => { export const createApi = (data: ApiForm) => {
return http.post('/api', data) return http.post('/apis', data)
} }
export const deleteSysApi = (id: number) => { export const deleteApi = (id: number) => {
return http.delete(`/api/${id}`) return http.delete(`/apis/${id}`)
} }

View File

@@ -1,14 +1,14 @@
import { http } from '@/lib' import { http } from '@/lib'
import type { PageParams, SysFile, SysFileForm } from '@/schemas' import type { File, FileForm, PageParams } from '@/schemas'
export const getSysFiles = (params: PageParams) => { export const listFiles = (params: PageParams) => {
return http.get<SysFile[], ApiPageResponse<SysFile>>('/file', { params }) return http.get<File[], ApiPageResponse<File>>('/files', { params })
} }
export const uploadFile = (file: File, onProgress?: (percent: number) => void) => { export const uploadFile = (file: globalThis.File, onProgress?: (percent: number) => void) => {
const formData = new FormData() const formData = new FormData()
formData.append('file', file) formData.append('file', file)
return http.post<SysFileForm>('/file', formData, { return http.post<FileForm>('/files', formData, {
headers: { 'Content-Type': 'multipart/form-data' }, headers: { 'Content-Type': 'multipart/form-data' },
onUploadProgress: (progressEvent) => { onUploadProgress: (progressEvent) => {
if (onProgress && progressEvent.total) { if (onProgress && progressEvent.total) {

View File

@@ -1,26 +1,26 @@
import { http } from '@/lib' import { http } from '@/lib'
import type { SysMenu, SysMenuForm } from '@/schemas' import type { Menu, MenuForm } from '@/schemas'
export const getAllSysMenus = () => { export const getAllMenus = () => {
return http.get<SysMenu[]>('/menu/all') return http.get<Menu[]>('/menus/all')
} }
export const getSysRoleMenus = (roleId: number) => { export const getRoleMenus = (roleId: number) => {
return http.get<SysMenu[]>(`/role/${roleId}/menus`) return http.get<Menu[]>(`/roles/${roleId}/menus`)
} }
export const assignSysRoleMenus = (id: number, menu_ids: number[]) => { export const assignRoleMenus = (id: number, menu_ids: number[]) => {
return http.put(`role/${id}/menus`, { menu_ids }) return http.put(`roles/${id}/menus`, { menu_ids })
} }
export const createSysMenu = (data: SysMenuForm) => { export const createMenu = (data: MenuForm) => {
return http.post('/menu', data) return http.post('/menus', data)
} }
export const updateSysMenu = (id: number, data: SysMenuForm) => { export const updateMenu = (id: number, data: MenuForm) => {
return http.patch('/menu/' + id, data) return http.patch('/menus/' + id, data)
} }
export const deleteSysMenu = (id: number) => { export const deleteMenu = (id: number) => {
return http.delete(`/menu/${id}`) return http.delete(`/menus/${id}`)
} }

View File

@@ -1,27 +1,27 @@
import { http } from '@/lib' import { http } from '@/lib'
import type { SysRole, SysRoleForm } from '@/schemas' import type { Role, RoleForm } from '@/schemas'
import { type PageParams } from '@/schemas' import { type PageParams } from '@/schemas'
export const getSysRoles = (params: PageParams) => { export const listRoles = (params: PageParams) => {
return http.get<SysRole[], ApiPageResponse<SysRole>>('/role', { params }) return http.get<Role[], ApiPageResponse<Role>>('/roles', { params })
} }
export const getAllSysRoles = () => { export const listAllRoles = () => {
return http.get<SysRole[]>('/role/all') return http.get<Role[]>('/roles/all')
} }
export const updateSysRole = (id: number, data: SysRoleForm) => { export const updateRole = (id: number, data: RoleForm) => {
return http.patch('/role/' + id, data) return http.patch('/roles/' + id, data)
} }
export const createSysRole = (data: SysRoleForm) => { export const createRole = (data: RoleForm) => {
return http.post('/role', data) return http.post('/roles', data)
} }
export const deleteSysRole = (id: number) => { export const deleteRole = (id: number) => {
return http.delete(`/role/${id}`) return http.delete(`/roles/${id}`)
} }
export const assignSysRoleApis = (id: number, api_ids: number[]) => { export const assignRoleApis = (id: number, api_ids: number[]) => {
return http.put(`role/${id}/apis`, { api_ids }) return http.put(`roles/${id}/apis`, { api_ids })
} }

View File

@@ -1,41 +1,35 @@
import { http } from '@/lib' import { http } from '@/lib'
import type { import type { Role, SearchUserParams, User, UserFormOutput, UserInfo } from '@/schemas'
SearchSysUserParams,
SysRole,
SysUser,
SysUserFormOutput,
SysUserInfo,
} from '@/schemas'
import { type PageParams } from '@/schemas' import { type PageParams } from '@/schemas'
export const getSysUsers = (params: PageParams & SearchSysUserParams) => { export const listUsers = (params: PageParams & SearchUserParams) => {
return http.get<SysUser[], ApiPageResponse<SysUser>>('/user', { params }) return http.get<User[], ApiPageResponse<User>>('/users', { params })
} }
export const getSysUserRoles = (id: number) => { export const listUserRoles = (id: number) => {
return http.get<SysRole[]>(`/user/${id}/roles`) return http.get<Role[]>(`/users/${id}/roles`)
} }
export const getSysUserInfo = () => { export const getUserInfo = () => {
return http.get<SysUserInfo>('/user/info') return http.get<UserInfo>('/users/me')
} }
export const updateSysUser = (id: number, data: SysUserFormOutput) => { export const updateUser = (id: number, data: UserFormOutput) => {
return http.patch('/user/' + id, data) return http.patch('/users/' + id, data)
} }
export const createSysUser = (data: SysUserFormOutput) => { export const createUser = (data: UserFormOutput) => {
return http.post('/user', data) return http.post('/users', data)
} }
export const deleteSysUser = (id: number) => { export const deleteUser = (id: number) => {
return http.delete(`/user/${id}`) return http.delete(`/users/${id}`)
} }
export const assignSysUserRoles = (id: number, role_ids: number[]) => { export const assignUserRoles = (id: number, role_ids: number[]) => {
return http.put(`user/${id}/roles`, { role_ids }) return http.put(`users/${id}/roles`, { role_ids })
} }
export const changeSysUserPassword = (id: number, password: string) => { export const updateUserPassword = (id: number, password: string) => {
return http.patch(`/user/${id}/password`, { password }) return http.patch(`/users/${id}/password`, { password })
} }

View File

@@ -74,7 +74,7 @@ export const WEditor = ({ defaultValues, onChange }: Props) => {
uploadImage: { uploadImage: {
async customUpload(file: File, insertFn: InsertFnType) { async customUpload(file: File, insertFn: InsertFnType) {
const { data } = await uploadFile(file) const { data } = await uploadFile(file)
insertFn(data.file_path, data.file_path, data.file_path) insertFn(data.file_url, data.file_url, data.file_url)
}, },
}, },
}, },

View File

@@ -15,7 +15,7 @@ import {
} from '@/components/ui/attachment.tsx' } from '@/components/ui/attachment.tsx'
import { Input } from '@/components/ui/input.tsx' import { Input } from '@/components/ui/input.tsx'
import { Spinner } from '@/components/ui/spinner.tsx' import { Spinner } from '@/components/ui/spinner.tsx'
import type { SysFileForm } from '@/schemas' import type { FileForm } from '@/schemas'
type State = 'idle' | 'uploading' | 'processing' | 'error' | 'done' type State = 'idle' | 'uploading' | 'processing' | 'error' | 'done'
@@ -27,8 +27,8 @@ type UploadFile = {
} }
export interface FileUploadProps { export interface FileUploadProps {
defaultFiles?: SysFileForm[] defaultFiles?: FileForm[]
onChange?: (files: SysFileForm[]) => void onChange?: (files: FileForm[]) => void
} }
export const FileUpload = ({ defaultFiles, onChange }: FileUploadProps) => { export const FileUpload = ({ defaultFiles, onChange }: FileUploadProps) => {
@@ -37,7 +37,7 @@ export const FileUpload = ({ defaultFiles, onChange }: FileUploadProps) => {
id: item.id, id: item.id,
uid: nanoid(), uid: nanoid(),
state: 'done', state: 'done',
file_name: item.file_path, file_name: item.file_url,
})), })),
) )
@@ -46,7 +46,7 @@ export const FileUpload = ({ defaultFiles, onChange }: FileUploadProps) => {
.filter((item) => item.id) .filter((item) => item.id)
.map((item) => ({ .map((item) => ({
id: item.id as number, id: item.id as number,
file_path: item.file_name, file_url: item.file_name,
})) }))
onChange?.(_files) onChange?.(_files)

View File

@@ -18,11 +18,11 @@ import {
} from '@/components/ui/sidebar' } from '@/components/ui/sidebar'
import { MenuType } from '@/enum' import { MenuType } from '@/enum'
import { generateMenus } from '@/lib' import { generateMenus } from '@/lib'
import type { SysMenuTree } from '@/schemas' import type { MenuTree } from '@/schemas'
import { siteConfig } from '@/siteConfig.ts' import { siteConfig } from '@/siteConfig.ts'
import { useStore } from '@/store' import { useStore } from '@/store'
const isMenuActive = (menu: SysMenuTree, pathname: string): boolean => { const isMenuActive = (menu: MenuTree, pathname: string): boolean => {
if (menu.path === pathname) { if (menu.path === pathname) {
return true return true
} }
@@ -30,7 +30,7 @@ const isMenuActive = (menu: SysMenuTree, pathname: string): boolean => {
return menu.children?.some((item) => isMenuActive(item, pathname)) ?? false return menu.children?.some((item) => isMenuActive(item, pathname)) ?? false
} }
const MenuTreeItem = ({ menu, depth = 0 }: { menu: SysMenuTree; depth?: number }) => { const MenuTreeItem = ({ menu, depth = 0 }: { menu: MenuTree; depth?: number }) => {
const { expandedMenus, toggleMenu } = useStore() const { expandedMenus, toggleMenu } = useStore()
const pathname = useRouterState({ select: (state) => state.location.pathname }) const pathname = useRouterState({ select: (state) => state.location.pathname })
const active = isMenuActive(menu, pathname) const active = isMenuActive(menu, pathname)

View File

@@ -1,5 +1,5 @@
import { MenuType } from '@/enum' import { MenuType } from '@/enum'
import type { SysMenu, SysMenuTree } from '@/schemas' import type { Menu, MenuTree } from '@/schemas'
// 生成树状菜单 用于菜单渲染 // 生成树状菜单 用于菜单渲染
export const generateMenus = ({ export const generateMenus = ({
@@ -8,11 +8,11 @@ export const generateMenus = ({
showHidden = false, showHidden = false,
showButton = false, showButton = false,
}: { }: {
menus: SysMenu[] menus: Menu[]
parent_id?: number | null parent_id?: number | null
showHidden?: boolean showHidden?: boolean
showButton?: boolean showButton?: boolean
}): SysMenuTree[] => { }): MenuTree[] => {
return menus return menus
.filter( .filter(
(item) => (item) =>
@@ -27,7 +27,7 @@ export const generateMenus = ({
})) }))
} }
export const generateBreadcrumbs = (list: SysMenu[], path: string) => { export const generateBreadcrumbs = (list: Menu[], path: string) => {
const breadcrumbs: string[] = [] const breadcrumbs: string[] = []
const findNodeId = list.find((item) => item.path === path) const findNodeId = list.find((item) => item.path === path)
if (!findNodeId) return breadcrumbs if (!findNodeId) return breadcrumbs
@@ -45,7 +45,7 @@ export const generateBreadcrumbs = (list: SysMenu[], path: string) => {
return breadcrumbs return breadcrumbs
} }
export const getExpandedMenus = (menus: SysMenu[]) => { export const getExpandedMenus = (menus: Menu[]) => {
const expandedMenus: number[] = [] const expandedMenus: number[] = []
const path = location.pathname const path = location.pathname
const findNodeId = menus.find((item) => item.path === path) const findNodeId = menus.find((item) => item.path === path)

View File

@@ -3,7 +3,7 @@ import { useMemo, useState } from 'react'
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { getCoreRowModel, useReactTable } from '@tanstack/react-table' import { getCoreRowModel, useReactTable } from '@tanstack/react-table'
import { getCategories } from '@/api' import { listCategories } from '@/api'
import { hasPermission } from '@/lib' import { hasPermission } from '@/lib'
import { type Category } from '@/schemas' import { type Category } from '@/schemas'
@@ -22,7 +22,7 @@ export default function CrudPage() {
const { data, isFetching, refetch } = useQuery({ const { data, isFetching, refetch } = useQuery({
queryKey: [QUERY_KEY, pagination.pageIndex, pagination.pageSize], queryKey: [QUERY_KEY, pagination.pageIndex, pagination.pageSize],
queryFn: () => queryFn: () =>
getCategories({ listCategories({
page: pagination.pageIndex + 1, page: pagination.pageIndex + 1,
page_size: pagination.pageSize, page_size: pagination.pageSize,
}), }),

View File

@@ -3,17 +3,17 @@ import { useEffect, useRef, useState } from 'react'
import { ImagePlus } from 'lucide-react' import { ImagePlus } from 'lucide-react'
import { uploadFile } from '@/api' import { uploadFile } from '@/api'
import type { SysFileForm } from '@/schemas' import type { FileForm } from '@/schemas'
interface Props { interface Props {
value: SysFileForm | undefined value: FileForm | undefined
onChange?: (file: SysFileForm) => void onChange?: (file: FileForm) => void
invalid?: boolean invalid?: boolean
} }
export const CoverUpload = ({ value, onChange, invalid }: Props) => { export const CoverUpload = ({ value, onChange, invalid }: Props) => {
const fileInputRef = useRef<HTMLInputElement>(null) const fileInputRef = useRef<HTMLInputElement>(null)
const [initialFile, setInitialFile] = useState<SysFileForm>() const [initialFile, setInitialFile] = useState<FileForm>()
useEffect(() => { useEffect(() => {
setInitialFile(value) setInitialFile(value)
@@ -54,7 +54,7 @@ export const CoverUpload = ({ value, onChange, invalid }: Props) => {
> >
{initialFile ? ( {initialFile ? (
<div className='h-full'> <div className='h-full'>
<img className='object-cover w-full h-full' src={initialFile.file_path} alt='' /> <img className='object-cover w-full h-full' src={initialFile.file_url} alt='' />
</div> </div>
) : ( ) : (
<div <div

View File

@@ -76,7 +76,7 @@ export default function PostEdit() {
form.reset({ form.reset({
...data, ...data,
cover: { id: data.cover_id, file_path: data.cover }, cover: { id: data.cover_id, file_url: data.cover },
}) })
setEditorContent(data.content) setEditorContent(data.content)

View File

@@ -3,7 +3,7 @@ import { useMemo, useState } from 'react'
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { getCoreRowModel, useReactTable } from '@tanstack/react-table' import { getCoreRowModel, useReactTable } from '@tanstack/react-table'
import { getPosts } from '@/api' import { listPosts } from '@/api'
import { hasPermission } from '@/lib' import { hasPermission } from '@/lib'
import { type Post } from '@/schemas' import { type Post } from '@/schemas'
@@ -22,7 +22,7 @@ export default function CrudPage() {
const { data, isFetching, refetch } = useQuery({ const { data, isFetching, refetch } = useQuery({
queryKey: [QUERY_KEY, pagination.pageIndex, pagination.pageSize], queryKey: [QUERY_KEY, pagination.pageIndex, pagination.pageSize],
queryFn: () => queryFn: () =>
getPosts({ listPosts({
page: pagination.pageIndex + 1, page: pagination.pageIndex + 1,
page_size: pagination.pageSize, page_size: pagination.pageSize,
}), }),

View File

@@ -3,7 +3,7 @@ import { useMemo, useState } from 'react'
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { getCoreRowModel, useReactTable } from '@tanstack/react-table' import { getCoreRowModel, useReactTable } from '@tanstack/react-table'
import { getTags } from '@/api' import { listTags } from '@/api'
import { hasPermission } from '@/lib' import { hasPermission } from '@/lib'
import { type Tag } from '@/schemas' import { type Tag } from '@/schemas'
@@ -22,7 +22,7 @@ export default function CrudPage() {
const { data, isFetching, refetch } = useQuery({ const { data, isFetching, refetch } = useQuery({
queryKey: [QUERY_KEY, pagination.pageIndex, pagination.pageSize], queryKey: [QUERY_KEY, pagination.pageIndex, pagination.pageSize],
queryFn: () => queryFn: () =>
getTags({ listTags({
page: pagination.pageIndex + 1, page: pagination.pageIndex + 1,
page_size: pagination.pageSize, page_size: pagination.pageSize,
}), }),

View File

@@ -5,7 +5,7 @@ import { Controller, useForm } from 'react-hook-form'
import { toast } from 'sonner' import { toast } from 'sonner'
import z from 'zod' import z from 'zod'
import { createSysApi, updateSysApi } from '@/api' import { createApi, updateApi } from '@/api'
import { Badge } from '@/components/ui/badge.tsx' import { Badge } from '@/components/ui/badge.tsx'
import { Button } from '@/components/ui/button.tsx' import { Button } from '@/components/ui/button.tsx'
import { import {
@@ -29,10 +29,10 @@ import {
} from '@/components/ui/select.tsx' } from '@/components/ui/select.tsx'
import { Spinner } from '@/components/ui/spinner.tsx' import { Spinner } from '@/components/ui/spinner.tsx'
import { HttpMethod, HttpMethodClass } from '@/enum' import { HttpMethod, HttpMethodClass } from '@/enum'
import { SysApiFormSchema, type SysApi, type SysApiForm } from '@/schemas' import { apiFormSchema, type Api, type ApiForm } from '@/schemas'
interface Props { interface Props {
currentRow?: SysApi | null currentRow?: Api | null
onClose: () => void onClose: () => void
onConfirm: () => void onConfirm: () => void
open: boolean open: boolean
@@ -45,8 +45,8 @@ export function ActionsDialog({ open, currentRow, onClose, onConfirm }: Props) {
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const form = useForm<z.infer<typeof SysApiFormSchema>>({ const form = useForm<z.infer<typeof apiFormSchema>>({
resolver: zodResolver(SysApiFormSchema), resolver: zodResolver(apiFormSchema),
defaultValues: currentRow defaultValues: currentRow
? { ...currentRow } ? { ...currentRow }
: { : {
@@ -58,13 +58,13 @@ export function ActionsDialog({ open, currentRow, onClose, onConfirm }: Props) {
}, },
}) })
const onSubmit = async (values: SysApiForm) => { const onSubmit = async (values: ApiForm) => {
try { try {
setLoading(true) setLoading(true)
if (values.id) { if (values.id) {
await updateSysApi(values.id, values) await updateApi(values.id, values)
} else { } else {
await createSysApi(values) await createApi(values)
} }
toast('操作成功!') toast('操作成功!')
onConfirm() onConfirm()

View File

@@ -4,14 +4,14 @@ import { Plus } from 'lucide-react'
import { Auth } from '@/components/auth' import { Auth } from '@/components/auth'
import { DataTable } from '@/components/data-table' import { DataTable } from '@/components/data-table'
import { Button } from '@/components/ui/button.tsx' import { Button } from '@/components/ui/button.tsx'
import type { SearchSysApiParams } from '@/schemas' import type { SearchApiParams } from '@/schemas'
import { useCrud } from './crud-provider.tsx' import { useCrud } from './crud-provider.tsx'
import { SearchForm } from './search-form.tsx' import { SearchForm } from './search-form.tsx'
interface Props<TData> { interface Props<TData> {
table: Table<TData> table: Table<TData>
onSearch?: (params: SearchSysApiParams) => void onSearch?: (params: SearchApiParams) => void
} }
export function ApiTable<TData>({ table, onSearch }: Props<TData>) { export function ApiTable<TData>({ table, onSearch }: Props<TData>) {

View File

@@ -3,14 +3,14 @@ import type { ColumnDef } from '@tanstack/react-table'
import { Badge } from '@/components/ui/badge' import { Badge } from '@/components/ui/badge'
import { HttpMethodClass, type HttpMethodValues } from '@/enum' import { HttpMethodClass, type HttpMethodValues } from '@/enum'
import { formatDate, hasPermission } from '@/lib' import { formatDate, hasPermission } from '@/lib'
import type { SysApi } from '@/schemas' import type { Api } from '@/schemas'
import { RowActions } from './row-actions' import { RowActions } from './row-actions'
export const createColumns = (): ColumnDef<SysApi>[] => { export const createColumns = (): ColumnDef<Api>[] => {
const showAction = hasPermission(['api:update', 'api:delete'], 'any') const showAction = hasPermission(['api:update', 'api:delete'], 'any')
const columns: ColumnDef<SysApi>[] = [ const columns: ColumnDef<Api>[] = [
{ {
accessorKey: 'name', accessorKey: 'name',
header: '接口名称', header: '接口名称',

View File

@@ -1,6 +1,6 @@
import { createContext, useContext, useState, type ReactNode } from 'react' import { createContext, useContext, useState, type ReactNode } from 'react'
import { type SysApi } from '@/schemas' import { type Api } from '@/schemas'
import type { Action } from '../constants' import type { Action } from '../constants'
@@ -11,11 +11,11 @@ type CrudContextType<T> = {
setCurrentRow: (row: T | null) => void setCurrentRow: (row: T | null) => void
} }
export const CrudContext = createContext<CrudContextType<SysApi> | null>(null) export const CrudContext = createContext<CrudContextType<Api> | null>(null)
export const CrudProvider = ({ children }: { children: ReactNode }) => { export const CrudProvider = ({ children }: { children: ReactNode }) => {
const [action, setAction] = useState<Action>(null) const [action, setAction] = useState<Action>(null)
const [currentRow, setCurrentRow] = useState<SysApi | null>(null) const [currentRow, setCurrentRow] = useState<Api | null>(null)
return ( return (
<CrudContext value={{ action, setAction, currentRow, setCurrentRow }}>{children}</CrudContext> <CrudContext value={{ action, setAction, currentRow, setCurrentRow }}>{children}</CrudContext>

View File

@@ -2,7 +2,7 @@ import { useState } from 'react'
import { toast } from 'sonner' import { toast } from 'sonner'
import { deleteSysApi } from '@/api' import { deleteApi } from '@/api'
import { import {
AlertDialog, AlertDialog,
AlertDialogCancel, AlertDialogCancel,
@@ -14,10 +14,10 @@ import {
} from '@/components/ui/alert-dialog' } from '@/components/ui/alert-dialog'
import { Button } from '@/components/ui/button.tsx' import { Button } from '@/components/ui/button.tsx'
import { Spinner } from '@/components/ui/spinner.tsx' import { Spinner } from '@/components/ui/spinner.tsx'
import { type SysApi } from '@/schemas' import { type Api } from '@/schemas'
interface Props { interface Props {
currentRow: SysApi currentRow: Api
onClose: () => void onClose: () => void
onConfirm: () => void onConfirm: () => void
open: boolean open: boolean
@@ -29,7 +29,7 @@ export function DeleteDialog({ open, onClose, currentRow, onConfirm }: Props) {
const onDelete = async () => { const onDelete = async () => {
try { try {
setLoading(true) setLoading(true)
await deleteSysApi(currentRow.id) await deleteApi(currentRow.id)
toast('操作成功!') toast('操作成功!')
onConfirm() onConfirm()
} finally { } finally {

View File

@@ -11,12 +11,12 @@ import {
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger, DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu.tsx' } from '@/components/ui/dropdown-menu.tsx'
import { type SysApi } from '@/schemas' import { type Api } from '@/schemas'
import { useCrud } from './crud-provider' import { useCrud } from './crud-provider'
type Props = { type Props = {
row: Row<SysApi> row: Row<Api>
} }
export function RowActions({ row }: Props) { export function RowActions({ row }: Props) {

View File

@@ -17,24 +17,25 @@ import {
SelectValue, SelectValue,
} from '@/components/ui/select.tsx' } from '@/components/ui/select.tsx'
import { HttpMethod, HttpMethodClass } from '@/enum' import { HttpMethod, HttpMethodClass } from '@/enum'
import { searchSysApiSchema, type SearchSysApiParams } from '@/schemas' import { searchApiSchema, type SearchApiParams } from '@/schemas'
interface SearchFormProps { interface SearchFormProps {
onSearch?: (params: SearchSysApiParams) => void onSearch?: (params: SearchApiParams) => void
} }
export function SearchForm({ onSearch }: SearchFormProps) { export function SearchForm({ onSearch }: SearchFormProps) {
const formId = useId() const formId = useId()
const form = useForm<SearchSysApiParams>({ const form = useForm<SearchApiParams>({
resolver: zodResolver(searchSysApiSchema), resolver: zodResolver(searchApiSchema),
defaultValues: { defaultValues: {
name: '',
method: undefined, method: undefined,
group_name: '', group_name: '',
}, },
}) })
const handleSubmit = (values: SearchSysApiParams) => { const handleSubmit = (values: SearchApiParams) => {
onSearch?.(values) onSearch?.(values)
} }
@@ -45,6 +46,25 @@ export function SearchForm({ onSearch }: SearchFormProps) {
return ( return (
<form id={formId} onSubmit={form.handleSubmit(handleSubmit)}> <form id={formId} onSubmit={form.handleSubmit(handleSubmit)}>
<FieldGroup className='flex-row flex-wrap'> <FieldGroup className='flex-row flex-wrap'>
<Controller
name='name'
control={form.control}
render={({ field, fieldState }) => (
<Field className='max-w-85' orientation='horizontal' data-invalid={fieldState.invalid}>
<FieldLabel htmlFor={`${formId}-${field.name}`} className='w-24'>
</FieldLabel>
<Input
{...field}
id={`${formId}-${field.name}`}
aria-invalid={fieldState.invalid}
placeholder='请输入接口名称'
autoComplete='off'
/>
{fieldState.invalid && <FieldError errors={[fieldState.error]} />}
</Field>
)}
/>
<Controller <Controller
name='group_name' name='group_name'
control={form.control} control={form.control}

View File

@@ -3,9 +3,9 @@ import { useMemo, useState } from 'react'
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { getCoreRowModel, useReactTable } from '@tanstack/react-table' import { getCoreRowModel, useReactTable } from '@tanstack/react-table'
import { getSysApis } from '@/api' import { listApis } from '@/api'
import { hasPermission } from '@/lib' import { hasPermission } from '@/lib'
import { type SearchSysApiParams, type SysApi } from '@/schemas' import { type Api, type SearchApiParams } from '@/schemas'
import { ActionDialogs } from './components/action-dialogs.tsx' import { ActionDialogs } from './components/action-dialogs.tsx'
import { ApiTable } from './components/api-table.tsx' import { ApiTable } from './components/api-table.tsx'
@@ -19,12 +19,12 @@ export default function CrudPage() {
pageSize: 10, pageSize: 10,
}) })
const [searchParams, setSearchParams] = useState<SearchSysApiParams>({}) const [searchParams, setSearchParams] = useState<SearchApiParams>({})
const { data, isFetching, refetch } = useQuery({ const { data, isFetching, refetch } = useQuery({
queryKey: [QUERY_KEY, pagination.pageIndex, pagination.pageSize, searchParams], queryKey: [QUERY_KEY, pagination.pageIndex, pagination.pageSize, searchParams],
queryFn: () => queryFn: () =>
getSysApis({ listApis({
page: pagination.pageIndex + 1, page: pagination.pageIndex + 1,
page_size: pagination.pageSize, page_size: pagination.pageSize,
...searchParams, ...searchParams,
@@ -33,11 +33,11 @@ export default function CrudPage() {
enabled: hasPermission('api:list'), enabled: hasPermission('api:list'),
}) })
const tableData = useMemo<SysApi[]>(() => data?.list ?? [], [data]) const tableData = useMemo<Api[]>(() => data?.list ?? [], [data])
const columns = useMemo(() => createColumns(), []) const columns = useMemo(() => createColumns(), [])
const table = useReactTable<SysApi>({ const table = useReactTable<Api>({
data: tableData, data: tableData,
columns, columns,
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
@@ -51,7 +51,7 @@ export default function CrudPage() {
}, },
}) })
const handleSearch = (params: SearchSysApiParams) => { const handleSearch = (params: SearchApiParams) => {
const isSameParams = JSON.stringify(params) === JSON.stringify(searchParams) const isSameParams = JSON.stringify(params) === JSON.stringify(searchParams)
setSearchParams(params) setSearchParams(params)

View File

@@ -2,16 +2,16 @@ import type { ColumnDef } from '@tanstack/react-table'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { formatDate, formatFileSize } from '@/lib' import { formatDate, formatFileSize } from '@/lib'
import type { SysFile } from '@/schemas' import type { File } from '@/schemas'
export const createColumns = (): ColumnDef<SysFile>[] => { export const createColumns = (): ColumnDef<File>[] => {
return [ return [
{ {
accessorKey: 'file_path', accessorKey: 'file_url',
header: '文件路径', header: '文件路径',
meta: { className: ' max-w-24' }, meta: { className: ' max-w-24' },
cell: ({ row }) => ( cell: ({ row }) => (
<Button variant='link' onClick={() => window.open(row.getValue('file_path'))}> <Button variant='link' onClick={() => window.open(row.getValue('file_url'))}>
</Button> </Button>
), ),

View File

@@ -1,6 +1,6 @@
import { createContext, useContext, useState, type ReactNode } from 'react' import { createContext, useContext, useState, type ReactNode } from 'react'
import { type SysRole } from '@/schemas' import { type Role } from '@/schemas'
import type { Action } from '../constants' import type { Action } from '../constants'
@@ -11,11 +11,11 @@ type CrudContextType<T> = {
setCurrentRow: (row: T | null) => void setCurrentRow: (row: T | null) => void
} }
export const CrudContext = createContext<CrudContextType<SysRole> | null>(null) export const CrudContext = createContext<CrudContextType<Role> | null>(null)
export const CrudProvider = ({ children }: { children: ReactNode }) => { export const CrudProvider = ({ children }: { children: ReactNode }) => {
const [action, setAction] = useState<Action>(null) const [action, setAction] = useState<Action>(null)
const [currentRow, setCurrentRow] = useState<SysRole | null>(null) const [currentRow, setCurrentRow] = useState<Role | null>(null)
return ( return (
<CrudContext value={{ action, setAction, currentRow, setCurrentRow }}>{children}</CrudContext> <CrudContext value={{ action, setAction, currentRow, setCurrentRow }}>{children}</CrudContext>

View File

@@ -3,9 +3,9 @@ import { useMemo, useState } from 'react'
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { getCoreRowModel, useReactTable } from '@tanstack/react-table' import { getCoreRowModel, useReactTable } from '@tanstack/react-table'
import { getSysFiles } from '@/api' import { listFiles } from '@/api'
import { hasPermission } from '@/lib' import { hasPermission } from '@/lib'
import type { SysFile } from '@/schemas' import type { File } from '@/schemas'
import { createColumns } from './components/columns.tsx' import { createColumns } from './components/columns.tsx'
import { CrudProvider } from './components/crud-provider.tsx' import { CrudProvider } from './components/crud-provider.tsx'
@@ -21,7 +21,7 @@ export default function CrudPage() {
const { data, isFetching, refetch } = useQuery({ const { data, isFetching, refetch } = useQuery({
queryKey: [QUERY_KEY, pagination.pageIndex, pagination.pageSize], queryKey: [QUERY_KEY, pagination.pageIndex, pagination.pageSize],
queryFn: () => queryFn: () =>
getSysFiles({ listFiles({
page: pagination.pageIndex + 1, page: pagination.pageIndex + 1,
page_size: pagination.pageSize, page_size: pagination.pageSize,
}), }),
@@ -29,11 +29,11 @@ export default function CrudPage() {
enabled: hasPermission('file:list'), enabled: hasPermission('file:list'),
}) })
const tableData = useMemo<SysFile[]>(() => data?.list ?? [], [data]) const tableData = useMemo<File[]>(() => data?.list ?? [], [data])
const columns = useMemo(() => createColumns(), []) const columns = useMemo(() => createColumns(), [])
const table = useReactTable<SysFile>({ const table = useReactTable<File>({
data: tableData, data: tableData,
columns, columns,
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),

View File

@@ -4,7 +4,7 @@ import { zodResolver } from '@hookform/resolvers/zod'
import { Controller, useForm } from 'react-hook-form' import { Controller, useForm } from 'react-hook-form'
import { toast } from 'sonner' import { toast } from 'sonner'
import { createSysMenu, getAllSysMenus, updateSysMenu } from '@/api' import { createMenu, getAllMenus, updateMenu } from '@/api'
import { MenuIcon } from '@/components/menu-icon' import { MenuIcon } from '@/components/menu-icon'
import { TreeSelect, type TreeSelectNode } from '@/components/tree-select' import { TreeSelect, type TreeSelectNode } from '@/components/tree-select'
import { Button } from '@/components/ui/button.tsx' import { Button } from '@/components/ui/button.tsx'
@@ -30,10 +30,10 @@ import {
import { Spinner } from '@/components/ui/spinner.tsx' import { Spinner } from '@/components/ui/spinner.tsx'
import { Switch } from '@/components/ui/switch' import { Switch } from '@/components/ui/switch'
import { MenuIconOptions, MenuType, menuTypeOptions, Status, statusOptions } from '@/enum' import { MenuIconOptions, MenuType, menuTypeOptions, Status, statusOptions } from '@/enum'
import { sysMenuFormSchema, type SysMenu, type SysMenuForm, type SysMenuTree } from '@/schemas' import { menuFormSchema, type Menu, type MenuForm, type MenuTree } from '@/schemas'
interface Props { interface Props {
currentRow?: SysMenuTree | null currentRow?: MenuTree | null
onClose: () => void onClose: () => void
onConfirm: () => void onConfirm: () => void
open: boolean open: boolean
@@ -43,7 +43,7 @@ const ROOT_PARENT_VALUE = '__no_parent__'
const NO_ICON_VALUE = '__no_icon__' const NO_ICON_VALUE = '__no_icon__'
const generateMenuOptions = ( const generateMenuOptions = (
data: SysMenu[], data: Menu[],
parent_id: number | null = null, parent_id: number | null = null,
currentId: number | null = null, currentId: number | null = null,
): TreeSelectNode[] => { ): TreeSelectNode[] => {
@@ -63,8 +63,8 @@ export function ActionsDialog({ open, currentRow, onClose, onConfirm }: Props) {
const formId = useId() const formId = useId()
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const form = useForm<SysMenuForm>({ const form = useForm<MenuForm>({
resolver: zodResolver(sysMenuFormSchema), resolver: zodResolver(menuFormSchema),
defaultValues: currentRow defaultValues: currentRow
? { ...currentRow } ? { ...currentRow }
: { : {
@@ -87,7 +87,7 @@ export function ActionsDialog({ open, currentRow, onClose, onConfirm }: Props) {
if (!open) return if (!open) return
const fetchOptions = async () => { const fetchOptions = async () => {
const { data } = await getAllSysMenus() const { data } = await getAllMenus()
const options = generateMenuOptions(data, null, currentRow?.id) const options = generateMenuOptions(data, null, currentRow?.id)
setMenuOptions([{ id: ROOT_PARENT_VALUE, label: '无父级' }, ...options]) setMenuOptions([{ id: ROOT_PARENT_VALUE, label: '无父级' }, ...options])
} }
@@ -95,13 +95,13 @@ export function ActionsDialog({ open, currentRow, onClose, onConfirm }: Props) {
fetchOptions() fetchOptions()
}, [open, currentRow?.id]) }, [open, currentRow?.id])
const onSubmit = async (values: SysMenuForm) => { const onSubmit = async (values: MenuForm) => {
try { try {
setLoading(true) setLoading(true)
if (values.id) { if (values.id) {
await updateSysMenu(values.id, values) await updateMenu(values.id, values)
} else { } else {
await createSysMenu(values) await createMenu(values)
} }
toast('操作成功!') toast('操作成功!')
onConfirm() onConfirm()

View File

@@ -4,14 +4,14 @@ import { StatusLabel } from '@/components/label'
import { MenuIcon } from '@/components/menu-icon' import { MenuIcon } from '@/components/menu-icon'
import { menuTypeDict, type MenuTypeValues, type StatusValues } from '@/enum' import { menuTypeDict, type MenuTypeValues, type StatusValues } from '@/enum'
import { formatDate, hasPermission } from '@/lib' import { formatDate, hasPermission } from '@/lib'
import type { SysMenuTree } from '@/schemas' import type { MenuTree } from '@/schemas'
import { RowActions } from './row-actions.tsx' import { RowActions } from './row-actions.tsx'
export const createColumns = (): ColumnDef<SysMenuTree>[] => { export const createColumns = (): ColumnDef<MenuTree>[] => {
const showAction = hasPermission(['menu:update', 'menu:delete'], 'any') const showAction = hasPermission(['menu:update', 'menu:delete'], 'any')
const columns: ColumnDef<SysMenuTree>[] = [ const columns: ColumnDef<MenuTree>[] = [
{ {
accessorKey: 'name', accessorKey: 'name',
header: '菜单名称', header: '菜单名称',

View File

@@ -1,6 +1,6 @@
import { createContext, useContext, useState, type ReactNode } from 'react' import { createContext, useContext, useState, type ReactNode } from 'react'
import { type SysMenuTree } from '@/schemas' import { type MenuTree } from '@/schemas'
import type { Action } from '../constants' import type { Action } from '../constants'
@@ -11,11 +11,11 @@ type CrudContextType<T> = {
setCurrentRow: (row: T | null) => void setCurrentRow: (row: T | null) => void
} }
export const CrudContext = createContext<CrudContextType<SysMenuTree> | null>(null) export const CrudContext = createContext<CrudContextType<MenuTree> | null>(null)
export const CrudProvider = ({ children }: { children: ReactNode }) => { export const CrudProvider = ({ children }: { children: ReactNode }) => {
const [action, setAction] = useState<Action>(null) const [action, setAction] = useState<Action>(null)
const [currentRow, setCurrentRow] = useState<SysMenuTree | null>(null) const [currentRow, setCurrentRow] = useState<MenuTree | null>(null)
return ( return (
<CrudContext value={{ action, setAction, currentRow, setCurrentRow }}>{children}</CrudContext> <CrudContext value={{ action, setAction, currentRow, setCurrentRow }}>{children}</CrudContext>

View File

@@ -2,7 +2,7 @@ import { useState } from 'react'
import { toast } from 'sonner' import { toast } from 'sonner'
import { deleteSysMenu } from '@/api' import { deleteMenu } from '@/api'
import { import {
AlertDialog, AlertDialog,
AlertDialogCancel, AlertDialogCancel,
@@ -14,10 +14,10 @@ import {
} from '@/components/ui/alert-dialog' } from '@/components/ui/alert-dialog'
import { Button } from '@/components/ui/button.tsx' import { Button } from '@/components/ui/button.tsx'
import { Spinner } from '@/components/ui/spinner.tsx' import { Spinner } from '@/components/ui/spinner.tsx'
import { type SysMenuTree } from '@/schemas' import { type MenuTree } from '@/schemas'
interface Props { interface Props {
currentRow: SysMenuTree currentRow: MenuTree
onClose: () => void onClose: () => void
onConfirm: () => void onConfirm: () => void
open: boolean open: boolean
@@ -29,7 +29,7 @@ export function DeleteDialog({ open, onClose, currentRow, onConfirm }: Props) {
const onDelete = async () => { const onDelete = async () => {
try { try {
setLoading(true) setLoading(true)
await deleteSysMenu(currentRow.id) await deleteMenu(currentRow.id)
toast('操作成功!') toast('操作成功!')
onConfirm() onConfirm()
} finally { } finally {

View File

@@ -11,12 +11,12 @@ import {
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger, DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu.tsx' } from '@/components/ui/dropdown-menu.tsx'
import { type SysMenuTree } from '@/schemas' import { type MenuTree } from '@/schemas'
import { useCrud } from './crud-provider' import { useCrud } from './crud-provider'
type Props = { type Props = {
row: Row<SysMenuTree> row: Row<MenuTree>
} }
export function RowActions({ row }: Props) { export function RowActions({ row }: Props) {

View File

@@ -3,9 +3,9 @@ import { useMemo } from 'react'
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { getCoreRowModel, getExpandedRowModel, useReactTable } from '@tanstack/react-table' import { getCoreRowModel, getExpandedRowModel, useReactTable } from '@tanstack/react-table'
import { getAllSysMenus } from '@/api' import { getAllMenus } from '@/api'
import { generateMenus, hasPermission } from '@/lib' import { generateMenus, hasPermission } from '@/lib'
import { type SysMenuTree } from '@/schemas' import { type MenuTree } from '@/schemas'
import { ActionDialogs } from './components/action-dialogs.tsx' import { ActionDialogs } from './components/action-dialogs.tsx'
import { createColumns } from './components/columns.tsx' import { createColumns } from './components/columns.tsx'
@@ -16,19 +16,19 @@ import { QUERY_KEY } from './constants.ts'
export default function CrudPage() { export default function CrudPage() {
const { data, isFetching, refetch } = useQuery({ const { data, isFetching, refetch } = useQuery({
queryKey: [QUERY_KEY], queryKey: [QUERY_KEY],
queryFn: () => getAllSysMenus(), queryFn: () => getAllMenus(),
placeholderData: (prev) => prev, placeholderData: (prev) => prev,
enabled: hasPermission('menu:list'), enabled: hasPermission('menu:list'),
}) })
const tableData = useMemo<SysMenuTree[]>( const tableData = useMemo<MenuTree[]>(
() => generateMenus({ menus: data?.data ?? [], showHidden: true, showButton: true }), () => generateMenus({ menus: data?.data ?? [], showHidden: true, showButton: true }),
[data], [data],
) )
const columns = useMemo(() => createColumns(), []) const columns = useMemo(() => createColumns(), [])
const table = useReactTable<SysMenuTree>({ const table = useReactTable<MenuTree>({
data: tableData, data: tableData,
columns, columns,
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),

View File

@@ -5,7 +5,7 @@ import { Controller, useForm } from 'react-hook-form'
import { toast } from 'sonner' import { toast } from 'sonner'
import z from 'zod' import z from 'zod'
import { createSysRole, updateSysRole } from '@/api' import { createRole, updateRole } from '@/api'
import { Button } from '@/components/ui/button.tsx' import { Button } from '@/components/ui/button.tsx'
import { import {
Dialog, Dialog,
@@ -28,10 +28,10 @@ import {
} from '@/components/ui/select.tsx' } from '@/components/ui/select.tsx'
import { Spinner } from '@/components/ui/spinner.tsx' import { Spinner } from '@/components/ui/spinner.tsx'
import { Status, statusOptions } from '@/enum' import { Status, statusOptions } from '@/enum'
import { sysRoleFormSchema, type SysRole, type SysRoleForm } from '@/schemas' import { roleFormSchema, type Role, type RoleForm } from '@/schemas'
interface Props { interface Props {
currentRow?: SysRole | null currentRow?: Role | null
onClose: () => void onClose: () => void
onConfirm: () => void onConfirm: () => void
open: boolean open: boolean
@@ -42,8 +42,8 @@ export function ActionsDialog({ open, currentRow, onClose, onConfirm }: Props) {
const formId = useId() const formId = useId()
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const form = useForm<z.infer<typeof sysRoleFormSchema>>({ const form = useForm<z.infer<typeof roleFormSchema>>({
resolver: zodResolver(sysRoleFormSchema), resolver: zodResolver(roleFormSchema),
defaultValues: currentRow defaultValues: currentRow
? { ...currentRow } ? { ...currentRow }
: { : {
@@ -53,13 +53,13 @@ export function ActionsDialog({ open, currentRow, onClose, onConfirm }: Props) {
}, },
}) })
const onSubmit = async (values: SysRoleForm) => { const onSubmit = async (values: RoleForm) => {
try { try {
setLoading(true) setLoading(true)
if (values.id) { if (values.id) {
await updateSysRole(values.id, values) await updateRole(values.id, values)
} else { } else {
await createSysRole(values) await createRole(values)
} }
toast('操作成功!') toast('操作成功!')
onConfirm() onConfirm()

View File

@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'
import { toast } from 'sonner' import { toast } from 'sonner'
import { assignSysRoleApis, getAllSysApi, getRoleApis, getSysApiGroups } from '@/api' import { assignRoleApis, getRoleApis, listAllApi, listApiGroups } from '@/api'
import { CheckboxTree, useCheckboxTree, type CheckboxTreeNode } from '@/components/checkbox-tree' import { CheckboxTree, useCheckboxTree, type CheckboxTreeNode } from '@/components/checkbox-tree'
import { DialogSkeleton } from '@/components/dialog-skeleton' import { DialogSkeleton } from '@/components/dialog-skeleton'
import { Badge } from '@/components/ui/badge' import { Badge } from '@/components/ui/badge'
@@ -18,10 +18,10 @@ import {
} from '@/components/ui/dialog.tsx' } from '@/components/ui/dialog.tsx'
import { Spinner } from '@/components/ui/spinner.tsx' import { Spinner } from '@/components/ui/spinner.tsx'
import { HttpMethodClass, type HttpMethodValues } from '@/enum' import { HttpMethodClass, type HttpMethodValues } from '@/enum'
import type { SysRole } from '@/schemas' import type { Role } from '@/schemas'
interface Props { interface Props {
currentRow?: SysRole | null currentRow?: Role | null
onClose: () => void onClose: () => void
onConfirm: () => void onConfirm: () => void
open: boolean open: boolean
@@ -38,8 +38,8 @@ export function AssignApisDialog({ open, onClose, onConfirm, currentRow }: Props
const fetchApis = async () => { const fetchApis = async () => {
setSkeletonLoading(true) setSkeletonLoading(true)
const { data: groups } = await getSysApiGroups() const { data: groups } = await listApiGroups()
const { data: apis } = await getAllSysApi() const { data: apis } = await listAllApi()
const { data: checkedApi } = await getRoleApis(currentRow?.id as number) const { data: checkedApi } = await getRoleApis(currentRow?.id as number)
const ids: string[] = checkedApi.map((api) => api.id.toString()) const ids: string[] = checkedApi.map((api) => api.id.toString())
@@ -80,7 +80,7 @@ export function AssignApisDialog({ open, onClose, onConfirm, currentRow }: Props
.filter((item) => !item.startsWith('group_names_')) .filter((item) => !item.startsWith('group_names_'))
.map((item) => parseInt(item)) .map((item) => parseInt(item))
await assignSysRoleApis(currentRow?.id as number, ids) await assignRoleApis(currentRow?.id as number, ids)
toast('操作成功!') toast('操作成功!')
onConfirm() onConfirm()
} finally { } finally {

View File

@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'
import { toast } from 'sonner' import { toast } from 'sonner'
import { assignSysRoleMenus, getAllSysMenus, getSysRoleMenus } from '@/api' import { assignRoleMenus, getAllMenus, getRoleMenus } from '@/api'
import { CheckboxTree, useCheckboxTree, type CheckboxTreeNode } from '@/components/checkbox-tree' import { CheckboxTree, useCheckboxTree, type CheckboxTreeNode } from '@/components/checkbox-tree'
import { DialogSkeleton } from '@/components/dialog-skeleton' import { DialogSkeleton } from '@/components/dialog-skeleton'
import { Button } from '@/components/ui/button.tsx' import { Button } from '@/components/ui/button.tsx'
@@ -16,16 +16,16 @@ import {
DialogTitle, DialogTitle,
} from '@/components/ui/dialog.tsx' } from '@/components/ui/dialog.tsx'
import { Spinner } from '@/components/ui/spinner.tsx' import { Spinner } from '@/components/ui/spinner.tsx'
import type { SysMenu, SysRole } from '@/schemas' import type { Menu, Role } from '@/schemas'
interface Props { interface Props {
currentRow?: SysRole | null currentRow?: Role | null
onClose: () => void onClose: () => void
onConfirm: () => void onConfirm: () => void
open: boolean open: boolean
} }
const buildTreeData = (data: SysMenu[], parentId: string | null = null): CheckboxTreeNode[] => { const buildTreeData = (data: Menu[], parentId: string | null = null): CheckboxTreeNode[] => {
return data return data
.filter((item) => (item.parent_id?.toString() ?? null) === parentId) .filter((item) => (item.parent_id?.toString() ?? null) === parentId)
.map((item) => { .map((item) => {
@@ -52,8 +52,8 @@ export function AssignMenusDialog({ open, onClose, onConfirm, currentRow }: Prop
const fetchMenus = async () => { const fetchMenus = async () => {
setSkeletonLoading(true) setSkeletonLoading(true)
const { data: menus } = await getAllSysMenus() const { data: menus } = await getAllMenus()
const { data: checkedMenus } = await getSysRoleMenus(currentRow?.id as number) const { data: checkedMenus } = await getRoleMenus(currentRow?.id as number)
setTreeData(buildTreeData(menus)) setTreeData(buildTreeData(menus))
setCheckedIds(checkedMenus.map((item) => item.id.toString())) setCheckedIds(checkedMenus.map((item) => item.id.toString()))
@@ -66,7 +66,7 @@ export function AssignMenusDialog({ open, onClose, onConfirm, currentRow }: Prop
const handleConfirm = async () => { const handleConfirm = async () => {
try { try {
setLoading(true) setLoading(true)
await assignSysRoleMenus( await assignRoleMenus(
currentRow?.id as number, currentRow?.id as number,
getCheckedIdsWithParents().map((item) => parseInt(item)), getCheckedIdsWithParents().map((item) => parseInt(item)),
) )

View File

@@ -3,17 +3,17 @@ import type { ColumnDef } from '@tanstack/react-table'
import { StatusLabel } from '@/components/label' import { StatusLabel } from '@/components/label'
import { type StatusValues } from '@/enum' import { type StatusValues } from '@/enum'
import { formatDate, hasPermission } from '@/lib' import { formatDate, hasPermission } from '@/lib'
import type { SysRole } from '@/schemas' import type { Role } from '@/schemas'
import { RowActions } from './row-actions' import { RowActions } from './row-actions'
export const createColumns = (): ColumnDef<SysRole>[] => { export const createColumns = (): ColumnDef<Role>[] => {
const showAction = hasPermission( const showAction = hasPermission(
['role:update', 'role:delete', 'role:assign-menus', 'role:assign-apis'], ['role:update', 'role:delete', 'role:assign-menus', 'role:assign-apis'],
'any', 'any',
) )
const columns: ColumnDef<SysRole>[] = [ const columns: ColumnDef<Role>[] = [
{ {
accessorKey: 'name', accessorKey: 'name',
header: '角色名称', header: '角色名称',

View File

@@ -1,6 +1,6 @@
import { createContext, useContext, useState, type ReactNode } from 'react' import { createContext, useContext, useState, type ReactNode } from 'react'
import { type SysRole } from '@/schemas' import { type Role } from '@/schemas'
import type { Action } from '../constants' import type { Action } from '../constants'
@@ -11,11 +11,11 @@ type CrudContextType<T> = {
setCurrentRow: (row: T | null) => void setCurrentRow: (row: T | null) => void
} }
export const CrudContext = createContext<CrudContextType<SysRole> | null>(null) export const CrudContext = createContext<CrudContextType<Role> | null>(null)
export const CrudProvider = ({ children }: { children: ReactNode }) => { export const CrudProvider = ({ children }: { children: ReactNode }) => {
const [action, setAction] = useState<Action>(null) const [action, setAction] = useState<Action>(null)
const [currentRow, setCurrentRow] = useState<SysRole | null>(null) const [currentRow, setCurrentRow] = useState<Role | null>(null)
return ( return (
<CrudContext value={{ action, setAction, currentRow, setCurrentRow }}>{children}</CrudContext> <CrudContext value={{ action, setAction, currentRow, setCurrentRow }}>{children}</CrudContext>

View File

@@ -2,7 +2,7 @@ import { useState } from 'react'
import { toast } from 'sonner' import { toast } from 'sonner'
import { deleteSysRole } from '@/api' import { deleteRole } from '@/api'
import { import {
AlertDialog, AlertDialog,
AlertDialogCancel, AlertDialogCancel,
@@ -14,10 +14,10 @@ import {
} from '@/components/ui/alert-dialog' } from '@/components/ui/alert-dialog'
import { Button } from '@/components/ui/button.tsx' import { Button } from '@/components/ui/button.tsx'
import { Spinner } from '@/components/ui/spinner.tsx' import { Spinner } from '@/components/ui/spinner.tsx'
import { type SysRole } from '@/schemas' import { type Role } from '@/schemas'
interface Props { interface Props {
currentRow: SysRole currentRow: Role
onClose: () => void onClose: () => void
onConfirm: () => void onConfirm: () => void
open: boolean open: boolean
@@ -29,7 +29,7 @@ export function DeleteDialog({ open, onClose, currentRow, onConfirm }: Props) {
const onDelete = async () => { const onDelete = async () => {
try { try {
setLoading(true) setLoading(true)
await deleteSysRole(currentRow.id) await deleteRole(currentRow.id)
toast('操作成功!') toast('操作成功!')
onConfirm() onConfirm()
} finally { } finally {

View File

@@ -11,12 +11,12 @@ import {
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger, DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu.tsx' } from '@/components/ui/dropdown-menu.tsx'
import { type SysRole } from '@/schemas' import { type Role } from '@/schemas'
import { useCrud } from './crud-provider' import { useCrud } from './crud-provider'
type Props = { type Props = {
row: Row<SysRole> row: Row<Role>
} }
export function RowActions({ row }: Props) { export function RowActions({ row }: Props) {

View File

@@ -3,9 +3,9 @@ import { useMemo, useState } from 'react'
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { getCoreRowModel, useReactTable } from '@tanstack/react-table' import { getCoreRowModel, useReactTable } from '@tanstack/react-table'
import { getSysRoles } from '@/api' import { listRoles } from '@/api'
import { hasPermission } from '@/lib' import { hasPermission } from '@/lib'
import { type SysRole } from '@/schemas' import { type Role } from '@/schemas'
import { ActionDialogs } from './components/action-dialogs.tsx' import { ActionDialogs } from './components/action-dialogs.tsx'
import { createColumns } from './components/columns.tsx' import { createColumns } from './components/columns.tsx'
@@ -22,7 +22,7 @@ export default function CrudPage() {
const { data, isFetching, refetch } = useQuery({ const { data, isFetching, refetch } = useQuery({
queryKey: [QUERY_KEY, pagination.pageIndex, pagination.pageSize], queryKey: [QUERY_KEY, pagination.pageIndex, pagination.pageSize],
queryFn: () => queryFn: () =>
getSysRoles({ listRoles({
page: pagination.pageIndex + 1, page: pagination.pageIndex + 1,
page_size: pagination.pageSize, page_size: pagination.pageSize,
}), }),
@@ -30,11 +30,11 @@ export default function CrudPage() {
enabled: hasPermission('role:list'), enabled: hasPermission('role:list'),
}) })
const tableData = useMemo<SysRole[]>(() => data?.list ?? [], [data]) const tableData = useMemo<Role[]>(() => data?.list ?? [], [data])
const columns = useMemo(() => createColumns(), []) const columns = useMemo(() => createColumns(), [])
const table = useReactTable<SysRole>({ const table = useReactTable<Role>({
data: tableData, data: tableData,
columns, columns,
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),

View File

@@ -4,7 +4,7 @@ import { zodResolver } from '@hookform/resolvers/zod'
import { Controller, useForm } from 'react-hook-form' import { Controller, useForm } from 'react-hook-form'
import { toast } from 'sonner' import { toast } from 'sonner'
import { createSysUser, updateSysUser } from '@/api' import { createUser, updateUser } from '@/api'
import { FileUpload } from '@/components/file-upload' import { FileUpload } from '@/components/file-upload'
import { Button } from '@/components/ui/button.tsx' import { Button } from '@/components/ui/button.tsx'
import { import {
@@ -28,15 +28,10 @@ import {
} from '@/components/ui/select.tsx' } from '@/components/ui/select.tsx'
import { Spinner } from '@/components/ui/spinner.tsx' import { Spinner } from '@/components/ui/spinner.tsx'
import { Status, statusOptions } from '@/enum' import { Status, statusOptions } from '@/enum'
import { import { userFormSchema, type User, type UserFormInput, type UserFormOutput } from '@/schemas'
sysUserFormSchema,
type SysUser,
type SysUserFormInput,
type SysUserFormOutput,
} from '@/schemas'
interface Props { interface Props {
currentRow?: SysUser | null currentRow?: User | null
onClose: () => void onClose: () => void
onConfirm: () => void onConfirm: () => void
open: boolean open: boolean
@@ -47,13 +42,13 @@ export function ActionsDialog({ open, currentRow, onClose, onConfirm }: Props) {
const formId = useId() const formId = useId()
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const form = useForm<SysUserFormInput, undefined, SysUserFormOutput>({ const form = useForm<UserFormInput, undefined, UserFormOutput>({
resolver: zodResolver(sysUserFormSchema), resolver: zodResolver(userFormSchema),
defaultValues: currentRow defaultValues: currentRow
? { ? {
...currentRow, ...currentRow,
avatar: currentRow.avatar_id avatar: currentRow.avatar_id
? [{ id: currentRow.avatar_id, file_path: currentRow.avatar_url }] ? [{ id: currentRow.avatar_id, file_url: currentRow.avatar_url }]
: [], : [],
} }
: { : {
@@ -66,14 +61,14 @@ export function ActionsDialog({ open, currentRow, onClose, onConfirm }: Props) {
}, },
}) })
const onSubmit = async (values: SysUserFormOutput) => { const onSubmit = async (values: UserFormOutput) => {
try { try {
setLoading(true) setLoading(true)
if (values.id) { if (values.id) {
await updateSysUser(values.id, values) await updateUser(values.id, values)
} else { } else {
await createSysUser(values) await createUser(values)
} }
toast('操作成功!') toast('操作成功!')

View File

@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'
import { toast } from 'sonner' import { toast } from 'sonner'
import { assignSysUserRoles, getAllSysRoles, getSysUserRoles } from '@/api' import { assignUserRoles, listAllRoles, listUserRoles } from '@/api'
import { DialogSkeleton } from '@/components/dialog-skeleton' import { DialogSkeleton } from '@/components/dialog-skeleton'
import { Button } from '@/components/ui/button.tsx' import { Button } from '@/components/ui/button.tsx'
import { Checkbox } from '@/components/ui/checkbox' import { Checkbox } from '@/components/ui/checkbox'
@@ -24,17 +24,17 @@ import {
FieldSet, FieldSet,
} from '@/components/ui/field.tsx' } from '@/components/ui/field.tsx'
import { Spinner } from '@/components/ui/spinner.tsx' import { Spinner } from '@/components/ui/spinner.tsx'
import type { SysRole, SysUser } from '@/schemas' import type { Role, User } from '@/schemas'
interface Props { interface Props {
currentRow?: SysUser | null currentRow?: User | null
onClose: () => void onClose: () => void
onConfirm: () => void onConfirm: () => void
open: boolean open: boolean
} }
export function AssignRolesDialog({ open, onClose, onConfirm, currentRow }: Props) { export function AssignRolesDialog({ open, onClose, onConfirm, currentRow }: Props) {
const [roles, setRoles] = useState<SysRole[]>([]) const [roles, setRoles] = useState<Role[]>([])
const [checkedIds, setCheckedIds] = useState<number[]>([]) const [checkedIds, setCheckedIds] = useState<number[]>([])
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [skeletonLoading, setSkeletonLoading] = useState(true) const [skeletonLoading, setSkeletonLoading] = useState(true)
@@ -42,7 +42,7 @@ export function AssignRolesDialog({ open, onClose, onConfirm, currentRow }: Prop
const handleConfirm = async () => { const handleConfirm = async () => {
try { try {
setLoading(true) setLoading(true)
await assignSysUserRoles(currentRow!.id, checkedIds) await assignUserRoles(currentRow!.id, checkedIds)
onConfirm() onConfirm()
toast('操作成功!') toast('操作成功!')
} finally { } finally {
@@ -58,8 +58,8 @@ export function AssignRolesDialog({ open, onClose, onConfirm, currentRow }: Prop
const fetchUserRoles = async () => { const fetchUserRoles = async () => {
setSkeletonLoading(true) setSkeletonLoading(true)
const { data } = await getAllSysRoles() const { data } = await listAllRoles()
const { data: userRoles } = await getSysUserRoles(currentRow!.id) const { data: userRoles } = await listUserRoles(currentRow!.id)
setRoles(data) setRoles(data)
setCheckedIds(userRoles.map((role) => role.id)) setCheckedIds(userRoles.map((role) => role.id))

View File

@@ -5,7 +5,7 @@ import { Controller, useForm } from 'react-hook-form'
import { toast } from 'sonner' import { toast } from 'sonner'
import { z } from 'zod' import { z } from 'zod'
import { changeSysUserPassword } from '@/api' import { updateUserPassword } from '@/api'
import { Button } from '@/components/ui/button.tsx' import { Button } from '@/components/ui/button.tsx'
import { import {
Dialog, Dialog,
@@ -19,10 +19,10 @@ import {
import { Field, FieldError, FieldGroup, FieldLabel } from '@/components/ui/field' import { Field, FieldError, FieldGroup, FieldLabel } from '@/components/ui/field'
import { Input } from '@/components/ui/input' import { Input } from '@/components/ui/input'
import { Spinner } from '@/components/ui/spinner.tsx' import { Spinner } from '@/components/ui/spinner.tsx'
import { changePasswordFormSchema, type ChangePasswordForm, type SysUser } from '@/schemas' import { changePasswordFormSchema, type ChangePasswordForm, type User } from '@/schemas'
interface Props { interface Props {
currentRow?: SysUser | null currentRow?: User | null
onClose: () => void onClose: () => void
onConfirm: () => void onConfirm: () => void
open: boolean open: boolean
@@ -42,7 +42,7 @@ export function ChangePasswordDialog({ open, currentRow, onClose, onConfirm }: P
try { try {
setLoading(true) setLoading(true)
await changeSysUserPassword(currentRow.id, values.password) await updateUserPassword(currentRow.id, values.password)
toast('操作成功!') toast('操作成功!')
onConfirm() onConfirm()
} finally { } finally {

View File

@@ -4,17 +4,17 @@ import { StatusLabel } from '@/components/label'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { type StatusValues } from '@/enum' import { type StatusValues } from '@/enum'
import { formatDate, hasPermission } from '@/lib' import { formatDate, hasPermission } from '@/lib'
import type { SysUser } from '@/schemas' import type { User } from '@/schemas'
import { RowActions } from './row-actions' import { RowActions } from './row-actions'
export const createColumns = (): ColumnDef<SysUser>[] => { export const createColumns = (): ColumnDef<User>[] => {
const showAction = hasPermission( const showAction = hasPermission(
['user:update', 'user:assign-roles', 'user:update-password', 'user:delete'], ['user:update', 'user:assign-roles', 'user:update-password', 'user:delete'],
'any', 'any',
) )
const columns: ColumnDef<SysUser>[] = [ const columns: ColumnDef<User>[] = [
{ {
accessorKey: 'avatar_url', accessorKey: 'avatar_url',
header: '用户头像', header: '用户头像',

View File

@@ -1,6 +1,6 @@
import { createContext, useContext, useState, type ReactNode } from 'react' import { createContext, useContext, useState, type ReactNode } from 'react'
import { type SysUser } from '@/schemas' import { type User } from '@/schemas'
import type { Action } from '../constants' import type { Action } from '../constants'
@@ -11,11 +11,11 @@ type CrudContextType<T> = {
setCurrentRow: (row: T | null) => void setCurrentRow: (row: T | null) => void
} }
export const CrudContext = createContext<CrudContextType<SysUser> | null>(null) export const CrudContext = createContext<CrudContextType<User> | null>(null)
export const CrudProvider = ({ children }: { children: ReactNode }) => { export const CrudProvider = ({ children }: { children: ReactNode }) => {
const [action, setAction] = useState<Action>(null) const [action, setAction] = useState<Action>(null)
const [currentRow, setCurrentRow] = useState<SysUser | null>(null) const [currentRow, setCurrentRow] = useState<User | null>(null)
return ( return (
<CrudContext value={{ action, setAction, currentRow, setCurrentRow }}>{children}</CrudContext> <CrudContext value={{ action, setAction, currentRow, setCurrentRow }}>{children}</CrudContext>

View File

@@ -2,7 +2,7 @@ import { useState } from 'react'
import { toast } from 'sonner' import { toast } from 'sonner'
import { deleteSysUser } from '@/api' import { deleteUser } from '@/api'
import { import {
AlertDialog, AlertDialog,
AlertDialogCancel, AlertDialogCancel,
@@ -14,10 +14,10 @@ import {
} from '@/components/ui/alert-dialog' } from '@/components/ui/alert-dialog'
import { Button } from '@/components/ui/button.tsx' import { Button } from '@/components/ui/button.tsx'
import { Spinner } from '@/components/ui/spinner.tsx' import { Spinner } from '@/components/ui/spinner.tsx'
import { type SysUser } from '@/schemas' import { type User } from '@/schemas'
interface Props { interface Props {
currentRow: SysUser currentRow: User
onClose: () => void onClose: () => void
onConfirm: () => void onConfirm: () => void
open: boolean open: boolean
@@ -29,7 +29,7 @@ export function DeleteDialog({ open, onClose, currentRow, onConfirm }: Props) {
const onDelete = async () => { const onDelete = async () => {
try { try {
setLoading(true) setLoading(true)
await deleteSysUser(currentRow.id) await deleteUser(currentRow.id)
toast('操作成功!') toast('操作成功!')
onConfirm() onConfirm()
} finally { } finally {

View File

@@ -11,12 +11,12 @@ import {
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger, DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu.tsx' } from '@/components/ui/dropdown-menu.tsx'
import { type SysUser } from '@/schemas' import { type User } from '@/schemas'
import { useCrud } from './crud-provider' import { useCrud } from './crud-provider'
type Props = { type Props = {
row: Row<SysUser> row: Row<User>
} }
export function RowActions({ row }: Props) { export function RowActions({ row }: Props) {

View File

@@ -7,23 +7,23 @@ import { Controller, useForm } from 'react-hook-form'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Field, FieldError, FieldGroup, FieldLabel } from '@/components/ui/field.tsx' import { Field, FieldError, FieldGroup, FieldLabel } from '@/components/ui/field.tsx'
import { Input } from '@/components/ui/input.tsx' import { Input } from '@/components/ui/input.tsx'
import { searchSysUserSchema, type SearchSysUserParams } from '@/schemas' import { searchUserSchema, type SearchUserParams } from '@/schemas'
interface SearchFormProps { interface SearchFormProps {
onSearch?: (params: SearchSysUserParams) => void onSearch?: (params: SearchUserParams) => void
} }
export function SearchForm({ onSearch }: SearchFormProps) { export function SearchForm({ onSearch }: SearchFormProps) {
const formId = useId() const formId = useId()
const form = useForm<SearchSysUserParams>({ const form = useForm<SearchUserParams>({
resolver: zodResolver(searchSysUserSchema), resolver: zodResolver(searchUserSchema),
defaultValues: { defaultValues: {
username: '', username: '',
}, },
}) })
const handleSubmit = (values: SearchSysUserParams) => { const handleSubmit = (values: SearchUserParams) => {
onSearch?.(values) onSearch?.(values)
} }

View File

@@ -4,14 +4,14 @@ import { Plus } from 'lucide-react'
import { Auth } from '@/components/auth' import { Auth } from '@/components/auth'
import { DataTable } from '@/components/data-table' import { DataTable } from '@/components/data-table'
import { Button } from '@/components/ui/button.tsx' import { Button } from '@/components/ui/button.tsx'
import type { SearchSysUserParams } from '@/schemas' import type { SearchUserParams } from '@/schemas'
import { useCrud } from './crud-provider.tsx' import { useCrud } from './crud-provider.tsx'
import { SearchForm } from './search-form.tsx' import { SearchForm } from './search-form.tsx'
interface Props<TData> { interface Props<TData> {
table: Table<TData> table: Table<TData>
onSearch?: (params: SearchSysUserParams) => void onSearch?: (params: SearchUserParams) => void
} }
export function UserTable<TData>({ table, onSearch }: Props<TData>) { export function UserTable<TData>({ table, onSearch }: Props<TData>) {

View File

@@ -3,9 +3,9 @@ import { useMemo, useState } from 'react'
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { getCoreRowModel, useReactTable } from '@tanstack/react-table' import { getCoreRowModel, useReactTable } from '@tanstack/react-table'
import { getSysUsers } from '@/api' import { listUsers } from '@/api'
import { hasPermission } from '@/lib' import { hasPermission } from '@/lib'
import { type SearchSysUserParams, type SysUser } from '@/schemas' import { type SearchUserParams, type User } from '@/schemas'
import { ActionDialogs } from './components/action-dialogs.tsx' import { ActionDialogs } from './components/action-dialogs.tsx'
import { createColumns } from './components/columns.tsx' import { createColumns } from './components/columns.tsx'
@@ -19,12 +19,12 @@ export default function CrudPage() {
pageSize: 10, pageSize: 10,
}) })
const [searchParams, setSearchParams] = useState<SearchSysUserParams>({}) const [searchParams, setSearchParams] = useState<SearchUserParams>({})
const { data, isFetching, refetch } = useQuery({ const { data, isFetching, refetch } = useQuery({
queryKey: [QUERY_KEY, pagination.pageIndex, pagination.pageSize, searchParams], queryKey: [QUERY_KEY, pagination.pageIndex, pagination.pageSize, searchParams],
queryFn: () => queryFn: () =>
getSysUsers({ listUsers({
page: pagination.pageIndex + 1, page: pagination.pageIndex + 1,
page_size: pagination.pageSize, page_size: pagination.pageSize,
...searchParams, ...searchParams,
@@ -33,11 +33,11 @@ export default function CrudPage() {
enabled: hasPermission('user:list'), enabled: hasPermission('user:list'),
}) })
const tableData = useMemo<SysUser[]>(() => data?.list ?? [], [data]) const tableData = useMemo<User[]>(() => data?.list ?? [], [data])
const columns = useMemo(() => createColumns(), []) const columns = useMemo(() => createColumns(), [])
const table = useReactTable<SysUser>({ const table = useReactTable<User>({
data: tableData, data: tableData,
columns, columns,
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
@@ -51,7 +51,7 @@ export default function CrudPage() {
}, },
}) })
const handleSearch = (params: SearchSysUserParams) => { const handleSearch = (params: SearchUserParams) => {
setSearchParams(params) setSearchParams(params)
setPagination((prev) => ({ ...prev, pageIndex: 0 })) setPagination((prev) => ({ ...prev, pageIndex: 0 }))
} }

View File

@@ -12,7 +12,7 @@ import {
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
import NotFound from '@/pages/404/index' import NotFound from '@/pages/404/index'
import { type SysMenu } from '@/schemas' import { type Menu } from '@/schemas'
import { siteConfig } from '@/siteConfig.ts' import { siteConfig } from '@/siteConfig.ts'
import { useStore } from '@/store' import { useStore } from '@/store'
@@ -65,7 +65,7 @@ const getComponent = (fileName: string) => {
return lazyRouteComponent(importer) return lazyRouteComponent(importer)
} }
export const setupRouter = (menus: SysMenu[]) => { export const setupRouter = (menus: Menu[]) => {
const rootChildren: AnyRoute[] = [] const rootChildren: AnyRoute[] = []
const layoutChildren: AnyRoute[] = [] const layoutChildren: AnyRoute[] = []

View File

@@ -1,6 +1,6 @@
import { z } from 'zod' import { z } from 'zod'
import { sysFileFormSchema } from '@/schemas' import { fileFormSchema } from '@/schemas'
export const postSchema = z.object({ export const postSchema = z.object({
id: z.number().int(), id: z.number().int(),
@@ -34,7 +34,7 @@ export const postFormSchema = z
summary: z.string(), summary: z.string(),
status: z.number().int(), status: z.number().int(),
published_at: z.string().min(1, '请选择发布日期!'), published_at: z.string().min(1, '请选择发布日期!'),
cover: sysFileFormSchema, cover: fileFormSchema,
sort: z.coerce.number<number>().int().min(0, '排序值不能小于0'), sort: z.coerce.number<number>().int().min(0, '排序值不能小于0'),
content: z.string().optional(), content: z.string().optional(),
category_id: z category_id: z

View File

@@ -2,7 +2,7 @@ import { z } from 'zod'
import { HttpMethodSchema } from '@/enum' import { HttpMethodSchema } from '@/enum'
export const SysApiSchema = z.object({ export const ApiSchema = z.object({
id: z.number().int(), id: z.number().int(),
name: z.string(), name: z.string(),
group_name: z.string(), group_name: z.string(),
@@ -11,17 +11,18 @@ export const SysApiSchema = z.object({
sort: z.number(), sort: z.number(),
}) })
export type SysApi = z.infer<typeof SysApiSchema> export type Api = z.infer<typeof ApiSchema>
// 搜索参数 // 搜索参数
export const searchSysApiSchema = z.object({ export const searchApiSchema = z.object({
name: z.string().optional(),
group_name: z.string().optional(), group_name: z.string().optional(),
method: HttpMethodSchema.optional(), method: HttpMethodSchema.optional(),
}) })
export type SearchSysApiParams = z.infer<typeof searchSysApiSchema> export type SearchApiParams = z.infer<typeof searchApiSchema>
export const SysApiFormSchema = z.object({ export const apiFormSchema = z.object({
id: z.number().optional(), id: z.number().optional(),
name: z.string().min(2, '接口名称至少2个字符').max(16, '接口名称不能超过16个字符'), name: z.string().min(2, '接口名称至少2个字符').max(16, '接口名称不能超过16个字符'),
group_name: z.string().min(2, '分组名称至少2个字符').max(16, '分组名称不能超过16个字符'), group_name: z.string().min(2, '分组名称至少2个字符').max(16, '分组名称不能超过16个字符'),
@@ -30,4 +31,4 @@ export const SysApiFormSchema = z.object({
sort: z.coerce.number<number>().int().min(0, '排序值不能小于0'), sort: z.coerce.number<number>().int().min(0, '排序值不能小于0'),
}) })
export type SysApiForm = z.infer<typeof SysApiFormSchema> export type ApiForm = z.infer<typeof apiFormSchema>

View File

@@ -1,9 +1,10 @@
import { z } from 'zod' import { z } from 'zod'
export const sysFileSchema = z.object({ export const fileSchema = z.object({
id: z.number().int(), id: z.number().int(),
file_name: z.string(), file_name: z.string(),
file_path: z.string(), file_path: z.string(),
file_url: z.string(),
original_name: z.string(), original_name: z.string(),
folder_name: z.string(), folder_name: z.string(),
mime_type: z.string(), mime_type: z.string(),
@@ -12,11 +13,11 @@ export const sysFileSchema = z.object({
updated_at: z.string(), updated_at: z.string(),
}) })
export type SysFile = z.infer<typeof sysFileSchema> export type File = z.infer<typeof fileSchema>
export const sysFileFormSchema = z.object({ export const fileFormSchema = z.object({
id: z.number().int(), id: z.number().int(),
file_path: z.string(), file_url: z.string(),
}) })
export type SysFileForm = z.infer<typeof sysFileFormSchema> export type FileForm = z.infer<typeof fileFormSchema>

View File

@@ -9,7 +9,7 @@ const MenuIconKeySchema = z.custom<MenuIconKeyValues>(
}, },
) )
export const sysMenuSchema = z.object({ export const menuSchema = z.object({
id: z.number().int(), id: z.number().int(),
name: z.string(), name: z.string(),
path: z.string(), path: z.string(),
@@ -25,13 +25,13 @@ export const sysMenuSchema = z.object({
updated_at: z.string(), updated_at: z.string(),
}) })
export type SysMenu = z.infer<typeof sysMenuSchema> export type Menu = z.infer<typeof menuSchema>
export interface SysMenuTree extends SysMenu { export interface MenuTree extends Menu {
children?: SysMenuTree[] children?: MenuTree[]
} }
export const sysMenuFormSchema = z export const menuFormSchema = z
.object({ .object({
id: z.number().optional(), id: z.number().optional(),
name: z.string().min(1, '菜单名称不能为空').max(16, '菜单名称不能超过16个字符'), name: z.string().min(1, '菜单名称不能为空').max(16, '菜单名称不能超过16个字符'),
@@ -63,4 +63,4 @@ export const sysMenuFormSchema = z
return data return data
}) })
export type SysMenuForm = z.infer<typeof sysMenuFormSchema> export type MenuForm = z.infer<typeof menuFormSchema>

View File

@@ -2,7 +2,7 @@ import { z } from 'zod'
import { StatusSchema } from '@/enum' import { StatusSchema } from '@/enum'
export const sysRoleSchema = z.object({ export const roleSchema = z.object({
id: z.number().int(), id: z.number().int(),
name: z.string(), name: z.string(),
code: z.string(), code: z.string(),
@@ -11,9 +11,9 @@ export const sysRoleSchema = z.object({
updated_at: z.string(), updated_at: z.string(),
}) })
export type SysRole = z.infer<typeof sysRoleSchema> export type Role = z.infer<typeof roleSchema>
export const sysRoleFormSchema = z export const roleFormSchema = z
.object({ .object({
id: z.number().optional(), id: z.number().optional(),
name: z.string().min(2, '角色名称至少2个字符').max(16, '角色名称不能超过16个字符'), name: z.string().min(2, '角色名称至少2个字符').max(16, '角色名称不能超过16个字符'),
@@ -33,4 +33,4 @@ export const sysRoleFormSchema = z
} }
}) })
export type SysRoleForm = z.infer<typeof sysRoleFormSchema> export type RoleForm = z.infer<typeof roleFormSchema>

View File

@@ -2,10 +2,10 @@ import { z } from 'zod'
import { StatusSchema } from '@/enum' import { StatusSchema } from '@/enum'
import { sysFileFormSchema } from './file' import { fileFormSchema } from './file'
import { sysMenuSchema } from './menu' import { menuSchema } from './menu'
export const sysUserSchema = z.object({ export const userSchema = z.object({
id: z.number().int(), id: z.number().int(),
account: z.string(), account: z.string(),
username: z.string(), username: z.string(),
@@ -16,15 +16,15 @@ export const sysUserSchema = z.object({
avatar_id: z.number().int().optional(), avatar_id: z.number().int().optional(),
}) })
export type SysUser = z.infer<typeof sysUserSchema> export type User = z.infer<typeof userSchema>
// 搜索参数 // 搜索参数
export const searchSysUserSchema = z.object({ export const searchUserSchema = z.object({
username: z.string().optional(), username: z.string().optional(),
}) })
export type SearchSysUserParams = z.infer<typeof searchSysUserSchema> export type SearchUserParams = z.infer<typeof searchUserSchema>
export const sysUserInfoSchema = sysUserSchema export const userInfoSchema = userSchema
.pick({ .pick({
id: true, id: true,
account: true, account: true,
@@ -34,11 +34,11 @@ export const sysUserInfoSchema = sysUserSchema
}) })
.extend({ .extend({
roles: z.array(z.string()), roles: z.array(z.string()),
menus: z.array(sysMenuSchema), menus: z.array(menuSchema),
permissions: z.array(z.string()), permissions: z.array(z.string()),
}) })
export type SysUserInfo = z.infer<typeof sysUserInfoSchema> export type UserInfo = z.infer<typeof userInfoSchema>
const passwordRegex = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{5,16}$/ const passwordRegex = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{5,16}$/
@@ -58,7 +58,7 @@ export const changePasswordFormSchema = z
export type ChangePasswordForm = z.infer<typeof changePasswordFormSchema> export type ChangePasswordForm = z.infer<typeof changePasswordFormSchema>
export const sysUserFormSchema = z export const userFormSchema = z
.object({ .object({
id: z.number().optional(), id: z.number().optional(),
account: z account: z
@@ -75,7 +75,7 @@ export const sysUserFormSchema = z
message: '请选择状态', message: '请选择状态',
}), }),
confirmPassword: z.string().optional(), confirmPassword: z.string().optional(),
avatar: z.array(sysFileFormSchema).optional(), avatar: z.array(fileFormSchema).optional(),
}) })
.superRefine((data, ctx) => { .superRefine((data, ctx) => {
if (!data.id) { if (!data.id) {
@@ -100,6 +100,6 @@ export const sysUserFormSchema = z
} }
}) })
export type SysUserFormInput = z.input<typeof sysUserFormSchema> export type UserFormInput = z.input<typeof userFormSchema>
export type SysUserFormOutput = z.output<typeof sysUserFormSchema> export type UserFormOutput = z.output<typeof userFormSchema>

View File

@@ -1,16 +1,16 @@
import { toast } from 'sonner' import { toast } from 'sonner'
import { create } from 'zustand' import { create } from 'zustand'
import { getSysUserInfo, logout } from '@/api' import { getUserInfo, logout } from '@/api'
import { getExpandedMenus, sleep } from '@/lib' import { getExpandedMenus, sleep } from '@/lib'
import type { SysUserInfo } from '@/schemas' import type { UserInfo } from '@/schemas'
const IS_LOGIN_KEY = 'is_login' const IS_LOGIN_KEY = 'is_login'
interface Store { interface Store {
token: string | null token: string | null
tokenExp: string | null tokenExp: string | null
userInfo: SysUserInfo | null userInfo: UserInfo | null
expandedMenus: number[] expandedMenus: number[]
isLogin: boolean isLogin: boolean
setToken: (token: string | null, tokenExp?: string | null) => void setToken: (token: string | null, tokenExp?: string | null) => void
@@ -66,7 +66,7 @@ export const useStore = create<Store>((set) => {
} }
try { try {
const [{ data }] = await Promise.all([getSysUserInfo(), sleep(500)]) const [{ data }] = await Promise.all([getUserInfo(), sleep(500)])
set({ set({
userInfo: data, userInfo: data,
expandedMenus: getExpandedMenus(data.menus), expandedMenus: getExpandedMenus(data.menus),

View File

@@ -15,7 +15,9 @@ export default defineConfig(({ mode }) => {
tailwindcss(), tailwindcss(),
tsconfigPaths(), tsconfigPaths(),
checker({ checker({
typescript: true, typescript: {
tsconfigPath: './tsconfig.app.json',
},
eslint: { eslint: {
lintCommand: 'eslint "./src/**/*.{ts,tsx}"', lintCommand: 'eslint "./src/**/*.{ts,tsx}"',
useFlatConfig: true, useFlatConfig: true,