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",
"preinstall": "npx only-allow pnpm",
"format": "prettier --write src/",
"type-check": "vue-tsc --build",
"type-check": "tsc --noEmit -p tsconfig.app.json",
"deploy": "./scripts/deploy.sh"
},
"dependencies": {
@@ -24,7 +24,6 @@
"@radix-ui/react-label": "^2.1.11",
"@radix-ui/react-separator": "^1.1.11",
"@radix-ui/react-slot": "^1.3.0",
"@radix-ui/react-tooltip": "^1.2.11",
"@tailwindcss/vite": "^4.3.2",
"@tanstack/react-query": "^5.101.2",
"@tanstack/react-router": "^1.170.17",
@@ -64,7 +63,6 @@
"eslint": "^9.39.4",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.6",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.26",
"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 type { Category, CategoryForm, PageParams } from '@/schemas'
export const getCategories = (params: PageParams) => {
return http.get<Category[], ApiPageResponse<Category>>('/category', { params })
export const listCategories = (params: PageParams) => {
return http.get<Category[], ApiPageResponse<Category>>('/categories', { params })
}
export const createCategory = (data: CategoryForm) => {
return http.post('/category', data)
return http.post('/categories', data)
}
export const updateCategory = (id: number, data: CategoryForm) => {
return http.patch(`/category/${id}`, data)
return http.patch(`/categories/${id}`, data)
}
export const deleteCategory = (id: number) => {
return http.delete(`/category/${id}`)
return http.delete(`/categories/${id}`)
}
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 type { PageParams, Post, PostFormOutput } from '@/schemas'
export const getPosts = (params: PageParams) => {
return http.get<Post[], ApiPageResponse<Post>>('/post', { params })
export const listPosts = (params: PageParams) => {
return http.get<Post[], ApiPageResponse<Post>>('/posts', { params })
}
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) => {
return http.patch(`/post/${id}`, data)
return http.patch(`/posts/${id}`, data)
}
export const getPost = (id: number) => {
return http.get<Post>(`/post/${id}`)
return http.get<Post>(`/posts/${id}`)
}
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 type { PageParams, Tag, TagForm } from '@/schemas'
export const getTags = (params: PageParams) => {
return http.get<Tag[], ApiPageResponse<Tag>>('/tag', { params })
export const listTags = (params: PageParams) => {
return http.get<Tag[], ApiPageResponse<Tag>>('/tags', { params })
}
export const createTag = (data: TagForm) => {
return http.post('/tag', data)
return http.post('/tags', data)
}
export const updateTag = (id: number, data: TagForm) => {
return http.patch(`/tag/${id}`, data)
return http.patch(`/tags/${id}`, data)
}
export const deleteTag = (id: number) => {
return http.delete(`/tag/${id}`)
return http.delete(`/tags/${id}`)
}
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 type { PageParams, SearchSysApiParams, SysApi, SysApiForm } from '@/schemas'
import type { Api, ApiForm, PageParams, SearchApiParams } from '@/schemas'
export const getSysApis = (params: PageParams & SearchSysApiParams) => {
return http.get<SysApi[], ApiPageResponse<SysApi>>('/api', { params })
export const listApis = (params: PageParams & SearchApiParams) => {
return http.get<Api[], ApiPageResponse<Api>>('/apis', { params })
}
export const getSysApiGroups = () => {
return http.get<string[]>('/api/group-names')
export const listApiGroups = () => {
return http.get<string[]>('/apis/groups')
}
export const getAllSysApi = () => {
return http.get<SysApi[]>('/api/all')
export const listAllApi = () => {
return http.get<Api[]>('/apis/all')
}
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) => {
return http.patch('/api/' + id, data)
export const updateApi = (id: number, data: ApiForm) => {
return http.patch('/apis/' + id, data)
}
export const createSysApi = (data: SysApiForm) => {
return http.post('/api', data)
export const createApi = (data: ApiForm) => {
return http.post('/apis', data)
}
export const deleteSysApi = (id: number) => {
return http.delete(`/api/${id}`)
export const deleteApi = (id: number) => {
return http.delete(`/apis/${id}`)
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -74,7 +74,7 @@ export const WEditor = ({ defaultValues, onChange }: Props) => {
uploadImage: {
async customUpload(file: File, insertFn: InsertFnType) {
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'
import { Input } from '@/components/ui/input.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'
@@ -27,8 +27,8 @@ type UploadFile = {
}
export interface FileUploadProps {
defaultFiles?: SysFileForm[]
onChange?: (files: SysFileForm[]) => void
defaultFiles?: FileForm[]
onChange?: (files: FileForm[]) => void
}
export const FileUpload = ({ defaultFiles, onChange }: FileUploadProps) => {
@@ -37,7 +37,7 @@ export const FileUpload = ({ defaultFiles, onChange }: FileUploadProps) => {
id: item.id,
uid: nanoid(),
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)
.map((item) => ({
id: item.id as number,
file_path: item.file_name,
file_url: item.file_name,
}))
onChange?.(_files)

View File

@@ -18,11 +18,11 @@ import {
} from '@/components/ui/sidebar'
import { MenuType } from '@/enum'
import { generateMenus } from '@/lib'
import type { SysMenuTree } from '@/schemas'
import type { MenuTree } from '@/schemas'
import { siteConfig } from '@/siteConfig.ts'
import { useStore } from '@/store'
const isMenuActive = (menu: SysMenuTree, pathname: string): boolean => {
const isMenuActive = (menu: MenuTree, pathname: string): boolean => {
if (menu.path === pathname) {
return true
}
@@ -30,7 +30,7 @@ const isMenuActive = (menu: SysMenuTree, pathname: string): boolean => {
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 pathname = useRouterState({ select: (state) => state.location.pathname })
const active = isMenuActive(menu, pathname)

View File

@@ -1,5 +1,5 @@
import { MenuType } from '@/enum'
import type { SysMenu, SysMenuTree } from '@/schemas'
import type { Menu, MenuTree } from '@/schemas'
// 生成树状菜单 用于菜单渲染
export const generateMenus = ({
@@ -8,11 +8,11 @@ export const generateMenus = ({
showHidden = false,
showButton = false,
}: {
menus: SysMenu[]
menus: Menu[]
parent_id?: number | null
showHidden?: boolean
showButton?: boolean
}): SysMenuTree[] => {
}): MenuTree[] => {
return menus
.filter(
(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 findNodeId = list.find((item) => item.path === path)
if (!findNodeId) return breadcrumbs
@@ -45,7 +45,7 @@ export const generateBreadcrumbs = (list: SysMenu[], path: string) => {
return breadcrumbs
}
export const getExpandedMenus = (menus: SysMenu[]) => {
export const getExpandedMenus = (menus: Menu[]) => {
const expandedMenus: number[] = []
const path = location.pathname
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 { getCoreRowModel, useReactTable } from '@tanstack/react-table'
import { getCategories } from '@/api'
import { listCategories } from '@/api'
import { hasPermission } from '@/lib'
import { type Category } from '@/schemas'
@@ -22,7 +22,7 @@ export default function CrudPage() {
const { data, isFetching, refetch } = useQuery({
queryKey: [QUERY_KEY, pagination.pageIndex, pagination.pageSize],
queryFn: () =>
getCategories({
listCategories({
page: pagination.pageIndex + 1,
page_size: pagination.pageSize,
}),

View File

@@ -3,17 +3,17 @@ import { useEffect, useRef, useState } from 'react'
import { ImagePlus } from 'lucide-react'
import { uploadFile } from '@/api'
import type { SysFileForm } from '@/schemas'
import type { FileForm } from '@/schemas'
interface Props {
value: SysFileForm | undefined
onChange?: (file: SysFileForm) => void
value: FileForm | undefined
onChange?: (file: FileForm) => void
invalid?: boolean
}
export const CoverUpload = ({ value, onChange, invalid }: Props) => {
const fileInputRef = useRef<HTMLInputElement>(null)
const [initialFile, setInitialFile] = useState<SysFileForm>()
const [initialFile, setInitialFile] = useState<FileForm>()
useEffect(() => {
setInitialFile(value)
@@ -54,7 +54,7 @@ export const CoverUpload = ({ value, onChange, invalid }: Props) => {
>
{initialFile ? (
<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

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,14 +4,14 @@ import { Plus } from 'lucide-react'
import { Auth } from '@/components/auth'
import { DataTable } from '@/components/data-table'
import { Button } from '@/components/ui/button.tsx'
import type { SearchSysApiParams } from '@/schemas'
import type { SearchApiParams } from '@/schemas'
import { useCrud } from './crud-provider.tsx'
import { SearchForm } from './search-form.tsx'
interface Props<TData> {
table: Table<TData>
onSearch?: (params: SearchSysApiParams) => void
onSearch?: (params: SearchApiParams) => void
}
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 { HttpMethodClass, type HttpMethodValues } from '@/enum'
import { formatDate, hasPermission } from '@/lib'
import type { SysApi } from '@/schemas'
import type { Api } from '@/schemas'
import { RowActions } from './row-actions'
export const createColumns = (): ColumnDef<SysApi>[] => {
export const createColumns = (): ColumnDef<Api>[] => {
const showAction = hasPermission(['api:update', 'api:delete'], 'any')
const columns: ColumnDef<SysApi>[] = [
const columns: ColumnDef<Api>[] = [
{
accessorKey: 'name',
header: '接口名称',

View File

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

View File

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

View File

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

View File

@@ -17,24 +17,25 @@ import {
SelectValue,
} from '@/components/ui/select.tsx'
import { HttpMethod, HttpMethodClass } from '@/enum'
import { searchSysApiSchema, type SearchSysApiParams } from '@/schemas'
import { searchApiSchema, type SearchApiParams } from '@/schemas'
interface SearchFormProps {
onSearch?: (params: SearchSysApiParams) => void
onSearch?: (params: SearchApiParams) => void
}
export function SearchForm({ onSearch }: SearchFormProps) {
const formId = useId()
const form = useForm<SearchSysApiParams>({
resolver: zodResolver(searchSysApiSchema),
const form = useForm<SearchApiParams>({
resolver: zodResolver(searchApiSchema),
defaultValues: {
name: '',
method: undefined,
group_name: '',
},
})
const handleSubmit = (values: SearchSysApiParams) => {
const handleSubmit = (values: SearchApiParams) => {
onSearch?.(values)
}
@@ -45,6 +46,25 @@ export function SearchForm({ onSearch }: SearchFormProps) {
return (
<form id={formId} onSubmit={form.handleSubmit(handleSubmit)}>
<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
name='group_name'
control={form.control}

View File

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

View File

@@ -2,16 +2,16 @@ import type { ColumnDef } from '@tanstack/react-table'
import { Button } from '@/components/ui/button'
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 [
{
accessorKey: 'file_path',
accessorKey: 'file_url',
header: '文件路径',
meta: { className: ' max-w-24' },
cell: ({ row }) => (
<Button variant='link' onClick={() => window.open(row.getValue('file_path'))}>
<Button variant='link' onClick={() => window.open(row.getValue('file_url'))}>
</Button>
),

View File

@@ -1,6 +1,6 @@
import { createContext, useContext, useState, type ReactNode } from 'react'
import { type SysRole } from '@/schemas'
import { type Role } from '@/schemas'
import type { Action } from '../constants'
@@ -11,11 +11,11 @@ type CrudContextType<T> = {
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 }) => {
const [action, setAction] = useState<Action>(null)
const [currentRow, setCurrentRow] = useState<SysRole | null>(null)
const [currentRow, setCurrentRow] = useState<Role | null>(null)
return (
<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 { getCoreRowModel, useReactTable } from '@tanstack/react-table'
import { getSysFiles } from '@/api'
import { listFiles } from '@/api'
import { hasPermission } from '@/lib'
import type { SysFile } from '@/schemas'
import type { File } from '@/schemas'
import { createColumns } from './components/columns.tsx'
import { CrudProvider } from './components/crud-provider.tsx'
@@ -21,7 +21,7 @@ export default function CrudPage() {
const { data, isFetching, refetch } = useQuery({
queryKey: [QUERY_KEY, pagination.pageIndex, pagination.pageSize],
queryFn: () =>
getSysFiles({
listFiles({
page: pagination.pageIndex + 1,
page_size: pagination.pageSize,
}),
@@ -29,11 +29,11 @@ export default function CrudPage() {
enabled: hasPermission('file:list'),
})
const tableData = useMemo<SysFile[]>(() => data?.list ?? [], [data])
const tableData = useMemo<File[]>(() => data?.list ?? [], [data])
const columns = useMemo(() => createColumns(), [])
const table = useReactTable<SysFile>({
const table = useReactTable<File>({
data: tableData,
columns,
getCoreRowModel: getCoreRowModel(),

View File

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

View File

@@ -4,14 +4,14 @@ import { StatusLabel } from '@/components/label'
import { MenuIcon } from '@/components/menu-icon'
import { menuTypeDict, type MenuTypeValues, type StatusValues } from '@/enum'
import { formatDate, hasPermission } from '@/lib'
import type { SysMenuTree } from '@/schemas'
import type { MenuTree } from '@/schemas'
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 columns: ColumnDef<SysMenuTree>[] = [
const columns: ColumnDef<MenuTree>[] = [
{
accessorKey: 'name',
header: '菜单名称',

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,12 +11,12 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu.tsx'
import { type SysUser } from '@/schemas'
import { type User } from '@/schemas'
import { useCrud } from './crud-provider'
type Props = {
row: Row<SysUser>
row: Row<User>
}
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 { Field, FieldError, FieldGroup, FieldLabel } from '@/components/ui/field.tsx'
import { Input } from '@/components/ui/input.tsx'
import { searchSysUserSchema, type SearchSysUserParams } from '@/schemas'
import { searchUserSchema, type SearchUserParams } from '@/schemas'
interface SearchFormProps {
onSearch?: (params: SearchSysUserParams) => void
onSearch?: (params: SearchUserParams) => void
}
export function SearchForm({ onSearch }: SearchFormProps) {
const formId = useId()
const form = useForm<SearchSysUserParams>({
resolver: zodResolver(searchSysUserSchema),
const form = useForm<SearchUserParams>({
resolver: zodResolver(searchUserSchema),
defaultValues: {
username: '',
},
})
const handleSubmit = (values: SearchSysUserParams) => {
const handleSubmit = (values: SearchUserParams) => {
onSearch?.(values)
}

View File

@@ -4,14 +4,14 @@ import { Plus } from 'lucide-react'
import { Auth } from '@/components/auth'
import { DataTable } from '@/components/data-table'
import { Button } from '@/components/ui/button.tsx'
import type { SearchSysUserParams } from '@/schemas'
import type { SearchUserParams } from '@/schemas'
import { useCrud } from './crud-provider.tsx'
import { SearchForm } from './search-form.tsx'
interface Props<TData> {
table: Table<TData>
onSearch?: (params: SearchSysUserParams) => void
onSearch?: (params: SearchUserParams) => void
}
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 { getCoreRowModel, useReactTable } from '@tanstack/react-table'
import { getSysUsers } from '@/api'
import { listUsers } from '@/api'
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 { createColumns } from './components/columns.tsx'
@@ -19,12 +19,12 @@ export default function CrudPage() {
pageSize: 10,
})
const [searchParams, setSearchParams] = useState<SearchSysUserParams>({})
const [searchParams, setSearchParams] = useState<SearchUserParams>({})
const { data, isFetching, refetch } = useQuery({
queryKey: [QUERY_KEY, pagination.pageIndex, pagination.pageSize, searchParams],
queryFn: () =>
getSysUsers({
listUsers({
page: pagination.pageIndex + 1,
page_size: pagination.pageSize,
...searchParams,
@@ -33,11 +33,11 @@ export default function CrudPage() {
enabled: hasPermission('user:list'),
})
const tableData = useMemo<SysUser[]>(() => data?.list ?? [], [data])
const tableData = useMemo<User[]>(() => data?.list ?? [], [data])
const columns = useMemo(() => createColumns(), [])
const table = useReactTable<SysUser>({
const table = useReactTable<User>({
data: tableData,
columns,
getCoreRowModel: getCoreRowModel(),
@@ -51,7 +51,7 @@ export default function CrudPage() {
},
})
const handleSearch = (params: SearchSysUserParams) => {
const handleSearch = (params: SearchUserParams) => {
setSearchParams(params)
setPagination((prev) => ({ ...prev, pageIndex: 0 }))
}

View File

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

View File

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

View File

@@ -2,7 +2,7 @@ import { z } from 'zod'
import { HttpMethodSchema } from '@/enum'
export const SysApiSchema = z.object({
export const ApiSchema = z.object({
id: z.number().int(),
name: z.string(),
group_name: z.string(),
@@ -11,17 +11,18 @@ export const SysApiSchema = z.object({
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(),
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(),
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'),
})
export type SysApiForm = z.infer<typeof SysApiFormSchema>
export type ApiForm = z.infer<typeof apiFormSchema>

View File

@@ -1,9 +1,10 @@
import { z } from 'zod'
export const sysFileSchema = z.object({
export const fileSchema = z.object({
id: z.number().int(),
file_name: z.string(),
file_path: z.string(),
file_url: z.string(),
original_name: z.string(),
folder_name: z.string(),
mime_type: z.string(),
@@ -12,11 +13,11 @@ export const sysFileSchema = z.object({
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(),
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(),
name: z.string(),
path: z.string(),
@@ -25,13 +25,13 @@ export const sysMenuSchema = z.object({
updated_at: z.string(),
})
export type SysMenu = z.infer<typeof sysMenuSchema>
export type Menu = z.infer<typeof menuSchema>
export interface SysMenuTree extends SysMenu {
children?: SysMenuTree[]
export interface MenuTree extends Menu {
children?: MenuTree[]
}
export const sysMenuFormSchema = z
export const menuFormSchema = z
.object({
id: z.number().optional(),
name: z.string().min(1, '菜单名称不能为空').max(16, '菜单名称不能超过16个字符'),
@@ -63,4 +63,4 @@ export const sysMenuFormSchema = z
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'
export const sysRoleSchema = z.object({
export const roleSchema = z.object({
id: z.number().int(),
name: z.string(),
code: z.string(),
@@ -11,9 +11,9 @@ export const sysRoleSchema = z.object({
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({
id: z.number().optional(),
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 { sysFileFormSchema } from './file'
import { sysMenuSchema } from './menu'
import { fileFormSchema } from './file'
import { menuSchema } from './menu'
export const sysUserSchema = z.object({
export const userSchema = z.object({
id: z.number().int(),
account: z.string(),
username: z.string(),
@@ -16,15 +16,15 @@ export const sysUserSchema = z.object({
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(),
})
export type SearchSysUserParams = z.infer<typeof searchSysUserSchema>
export type SearchUserParams = z.infer<typeof searchUserSchema>
export const sysUserInfoSchema = sysUserSchema
export const userInfoSchema = userSchema
.pick({
id: true,
account: true,
@@ -34,11 +34,11 @@ export const sysUserInfoSchema = sysUserSchema
})
.extend({
roles: z.array(z.string()),
menus: z.array(sysMenuSchema),
menus: z.array(menuSchema),
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}$/
@@ -58,7 +58,7 @@ export const changePasswordFormSchema = z
export type ChangePasswordForm = z.infer<typeof changePasswordFormSchema>
export const sysUserFormSchema = z
export const userFormSchema = z
.object({
id: z.number().optional(),
account: z
@@ -75,7 +75,7 @@ export const sysUserFormSchema = z
message: '请选择状态',
}),
confirmPassword: z.string().optional(),
avatar: z.array(sysFileFormSchema).optional(),
avatar: z.array(fileFormSchema).optional(),
})
.superRefine((data, ctx) => {
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 { create } from 'zustand'
import { getSysUserInfo, logout } from '@/api'
import { getUserInfo, logout } from '@/api'
import { getExpandedMenus, sleep } from '@/lib'
import type { SysUserInfo } from '@/schemas'
import type { UserInfo } from '@/schemas'
const IS_LOGIN_KEY = 'is_login'
interface Store {
token: string | null
tokenExp: string | null
userInfo: SysUserInfo | null
userInfo: UserInfo | null
expandedMenus: number[]
isLogin: boolean
setToken: (token: string | null, tokenExp?: string | null) => void
@@ -66,7 +66,7 @@ export const useStore = create<Store>((set) => {
}
try {
const [{ data }] = await Promise.all([getSysUserInfo(), sleep(500)])
const [{ data }] = await Promise.all([getUserInfo(), sleep(500)])
set({
userInfo: data,
expandedMenus: getExpandedMenus(data.menus),

View File

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