feat(web): 迁移前端源代码(任务 T4.1)

- 删除 novalon 前端 src/ 下所有文件
- 从 gym-manage 复制前端 src/ 完整目录树
- 替换 gym-manage-api → novalon-manage-api
- 替换 gym_system → manage_system
- 无 gym 残留引用
This commit is contained in:
张翔
2026-04-27 14:57:45 +08:00
parent f18e904e65
commit f0746d06db
31 changed files with 105 additions and 48 deletions
+11 -11
View File
@@ -2,14 +2,14 @@ import request from '@/utils/request'
import { UserStatus } from '@/constants/status'
export interface User {
id: number
id: string
username: string
nickname: string
email: string
phone: string
avatar: string
status: UserStatus
roles: number[]
roles: string[]
createdAt: string
updatedAt: string
}
@@ -20,7 +20,7 @@ export interface CreateUserRequest {
nickname: string
email: string
phone: string
roles?: number[]
roles?: string[]
}
export interface UpdateUserRequest {
@@ -29,7 +29,7 @@ export interface UpdateUserRequest {
phone?: string
avatar?: string
status?: UserStatus
roles?: number[]
roles?: string[]
}
export interface UserPageRequest {
@@ -60,27 +60,27 @@ export const userApi = {
getPage: (params: UserPageRequest) =>
request.get<PageResponse<User>>('/users/page', { params }),
getById: (id: number) =>
getById: (id: string) =>
request.get<User>(`/users/${id}`),
create: (data: CreateUserRequest) =>
request.post<User>('/users', data),
update: (id: number, data: UpdateUserRequest) =>
update: (id: string, data: UpdateUserRequest) =>
request.put<User>(`/users/${id}`, data),
delete: (id: number) =>
delete: (id: string) =>
request.delete<void>(`/users/${id}`),
batchDelete: (ids: number[]) =>
batchDelete: (ids: string[]) =>
request.post<void>('/users/batch-delete', { ids }),
resetPassword: (id: number) =>
resetPassword: (id: string) =>
request.post<void>(`/users/${id}/reset-password`),
updateStatus: (id: number, status: UserStatus) =>
updateStatus: (id: string, status: UserStatus) =>
request.put<void>(`/users/${id}/status`, { status }),
assignRoles: (id: number, roleIds: number[]) =>
assignRoles: (id: string, roleIds: string[]) =>
request.post<void>(`/users/${id}/roles`, { roleIds }),
}
+1 -1
View File
@@ -1,5 +1,5 @@
import { createRouter, createWebHistory } from 'vue-router'
import type { RouteRecordRaw } from 'vue-router'
import type { RouteRecordRaw, RouteLocationNormalized } from 'vue-router'
declare module 'vue-router' {
interface RouteMeta {
+7 -7
View File
@@ -2,19 +2,19 @@ import { defineStore } from 'pinia'
import request from '@/utils/request'
export interface MenuItem {
id: number
id: string
name: string
path: string
icon?: string
parentId?: number
parentId?: string
sort: number
children?: MenuItem[]
}
interface BackendMenuItem {
id: number
id: string
menuName: string
parentId: number
parentId: string
orderNum: number
menuType: string
perms?: string
@@ -24,7 +24,7 @@ interface BackendMenuItem {
}
function transformMenuData(backendMenus: BackendMenuItem[]): MenuItem[] {
const menuMap = new Map<number, MenuItem>()
const menuMap = new Map<string, MenuItem>()
const rootMenus: MenuItem[] = []
const componentToPathMap: Record<string, string> = {
@@ -48,7 +48,7 @@ function transformMenuData(backendMenus: BackendMenuItem[]): MenuItem[] {
name: menu.menuName,
path: menu.component ? (componentToPathMap[menu.component] || `/${menu.component.replace('/index', '').replace('system/', '')}`) : '',
icon: getMenuIcon(menu.menuName),
parentId: menu.parentId === 0 ? undefined : menu.parentId,
parentId: menu.parentId === '0' ? undefined : menu.parentId,
sort: menu.orderNum
}
menuMap.set(menu.id, menuItem)
@@ -56,7 +56,7 @@ function transformMenuData(backendMenus: BackendMenuItem[]): MenuItem[] {
filteredMenus.forEach(menu => {
const menuItem = menuMap.get(menu.id)!
if (menu.parentId === 0) {
if (menu.parentId === '0') {
rootMenus.push(menuItem)
} else {
const parentMenu = menuMap.get(menu.parentId)
+13
View File
@@ -0,0 +1,13 @@
import { AxiosRequestConfig, AxiosInstance } from 'axios'
declare module 'axios' {
interface AxiosInstance {
request<T = any>(config: AxiosRequestConfig): Promise<T>
get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>
delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>
head<T = any>(url: string, config?: AxiosRequestConfig): Promise<T>
post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>
put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>
patch<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T>
}
}
+30 -9
View File
@@ -21,20 +21,28 @@ const permissionMapping: PermissionMapping = {
'POST /dict': 'system:dict:add',
'PUT /dict': 'system:dict:edit',
'DELETE /dict': 'system:dict:remove',
'GET /config': 'system:config:list',
'POST /config': 'system:config:list',
'PUT /config': 'system:config:list',
'DELETE /config': 'system:config:list',
'GET /sys/config': 'system:config:list',
'POST /sys/config': 'system:config:add',
'PUT /sys/config': 'system:config:edit',
'DELETE /sys/config': 'system:config:remove',
'POST /sys/config': 'system:config:list',
'PUT /sys/config': 'system:config:list',
'DELETE /sys/config': 'system:config:list',
'GET /files': 'system:file:list',
'POST /files': 'system:file:upload',
'DELETE /files': 'system:file:delete',
}
export function checkApiPermission(method: string, url: string): boolean {
const permissionStore = usePermissionStore()
const key = `${method.toUpperCase()} ${url.split('?')[0]}`
const requiredPermission = permissionMapping[key]
let requiredPermission = permissionMapping[key]
if (!requiredPermission) {
const baseUrl = url.split('?')[0].replace(/\/\d+$/, '')
const baseKey = `${method.toUpperCase()} ${baseUrl}`
requiredPermission = permissionMapping[baseKey]
}
if (!requiredPermission) {
return true
@@ -44,11 +52,24 @@ export function checkApiPermission(method: string, url: string): boolean {
return true
}
if (Array.isArray(requiredPermission)) {
return requiredPermission.some(p => permissionStore.hasPermission(p))
const stored = localStorage.getItem('permission')
if (!stored) {
return true
}
return permissionStore.hasPermission(requiredPermission)
try {
const data = JSON.parse(stored)
const permissions = data.permissions || []
if (Array.isArray(requiredPermission)) {
return requiredPermission.some(p => permissions.includes(p))
}
return permissions.includes(requiredPermission)
} catch (error) {
console.error('解析权限数据失败:', error)
return true
}
}
export function getRequiredPermission(method: string, url: string): string | string[] | null {
+2 -2
View File
@@ -1,4 +1,4 @@
import axios, { AxiosRequestConfig } from 'axios'
import axios, { InternalAxiosRequestConfig } from 'axios'
import { generateSignatureHeaders } from './signature'
import { checkApiPermission } from './permission'
@@ -8,7 +8,7 @@ const request = axios.create({
})
request.interceptors.request.use(
(config: AxiosRequestConfig) => {
(config: InternalAxiosRequestConfig) => {
const token = localStorage.getItem('token')
if (token) {
config.headers = config.headers || {}
+2 -3
View File
@@ -16,7 +16,7 @@ export function generateSignature(
timestamp: number,
nonce: string
): string {
const stringToSign = buildStringToSign(method, path, query, '', timestamp, nonce)
const stringToSign = buildStringToSign(method, path, query, body, timestamp, nonce)
const signature = CryptoJS.HmacSHA256(stringToSign, SIGNATURE_SECRET)
const signatureBase64 = CryptoJS.enc.Base64.stringify(signature)
@@ -33,13 +33,12 @@ export function generateSignatureHeaders(
const nonce = generateNonce()
const { path, query } = parseUrl(url)
const bodyString = body ? JSON.stringify(body) : ''
const signature = generateSignature(
method.toUpperCase(),
path,
query || '',
bodyString,
'',
timestamp,
nonce
)
@@ -154,15 +154,21 @@ const handleDelete = async (row: any) => {
const handleModalOk = async () => {
try {
console.log('handleModalOk called, formState:', formState)
if (formState.id) {
await request.put(`/config/${formState.id}`, formState)
console.log('Sending PUT request to /config/' + formState.id)
const response = await request.put(`/config/${formState.id}`, formState)
console.log('PUT response:', response)
} else {
await request.post('/config', formState)
console.log('Sending POST request to /config')
const response = await request.post('/config', formState)
console.log('POST response:', response)
}
ElMessage.success('操作成功')
modalVisible.value = false
fetchData()
} catch {
} catch (error) {
console.error('handleModalOk error:', error)
ElMessage.error('操作失败')
}
}
@@ -159,7 +159,7 @@ const handleDelete = async (row: any) => {
cancelButtonText: '取消',
type: 'warning'
})
await request.delete(`/dict/${row.id}`)
await request.delete(`/dict/types/${row.id}`)
ElMessage.success('删除成功')
fetchData()
} catch (error) {
@@ -199,10 +199,10 @@ const fetchStats = async () => {
request.get('/logs/operation/count')
])
stats.userCount = userCountRes.status === 'fulfilled' ? (userCountRes.value || 0) : 0
stats.roleCount = roleCountRes.status === 'fulfilled' ? (roleCountRes.value || 0) : 0
stats.todayLogin = todayLoginRes.status === 'fulfilled' ? (todayLoginRes.value || 0) : 0
stats.operationLog = operationLogRes.status === 'fulfilled' ? (operationLogRes.value || 0) : 0
stats.userCount = userCountRes.status === 'fulfilled' ? Number(userCountRes.value || 0) : 0
stats.roleCount = roleCountRes.status === 'fulfilled' ? Number(roleCountRes.value || 0) : 0
stats.todayLogin = todayLoginRes.status === 'fulfilled' ? Number(todayLoginRes.value || 0) : 0
stats.operationLog = operationLogRes.status === 'fulfilled' ? Number(operationLogRes.value || 0) : 0
} catch (error) {
console.error('Failed to fetch stats:', error)
} finally {
@@ -79,9 +79,12 @@ interface JwtPayload {
const onFinish = async () => {
loading.value = true
try {
console.log('开始登录请求...')
const res: any = await request.post('/auth/login', formState)
console.log('登录响应:', res)
if (!res || !res.token) {
console.error('登录失败:未收到有效响应')
ElMessage.error('登录失败:未收到有效响应')
return
}
@@ -103,15 +106,19 @@ const onFinish = async () => {
console.warn('解析Token中的角色信息失败:', decodeError)
}
console.log('开始获取用户菜单...')
try {
await permissionStore.fetchUserMenus()
console.log('获取用户菜单成功')
} catch (menuError) {
console.error('获取用户菜单失败:', menuError)
}
ElMessage.success('登录成功')
console.log('准备跳转到首页...')
await router.push('/')
console.log('跳转完成')
} catch (error: any) {
console.error('登录错误:', error)
ElMessage.error(error.response?.data?.message || error.message || '登录失败')
@@ -279,10 +279,10 @@ const fetchData = async () => {
size: pagination.pageSize,
sortBy: sortInfo.sortBy,
sortOrder: sortInfo.sortOrder,
name: searchKeyword.value || undefined
roleName: searchKeyword.value || undefined
})
dataSource.value = res.content
pagination.total = res.totalElements
pagination.total = Number(res.totalElements) || 0
} catch (error) {
handleApiError(error)
} finally {
@@ -226,7 +226,7 @@ import { Search } from '@element-plus/icons-vue'
import { userApi, type User, type CreateUserRequest, type UpdateUserRequest } from '@/api/user.api'
import { roleApi, type Role } from '@/api/role.api'
import { handleApiError } from '@/utils/errorHandler'
import { UserStatus, StatusHelper } from '@/constants/status'
import { UserStatus } from '@/constants/status'
import { formatDateTime } from '@/utils/dateFormat'
const loading = ref(false)
@@ -279,9 +279,9 @@ const formRules = {
}
const roleDialogVisible = ref(false)
const selectedRoles = ref<number[]>([])
const allRoles = ref<{ key: number; label: string }[]>([])
const currentUserId = ref<number | null>(null)
const selectedRoles = ref<string[]>([])
const allRoles = ref<{ key: string; label: string }[]>([])
const currentUserId = ref<string | null>(null)
const fetchData = async () => {
loading.value = true
@@ -294,7 +294,7 @@ const fetchData = async () => {
keyword: searchKeyword.value || undefined
})
dataSource.value = res.content
pagination.total = res.totalElements
pagination.total = Number(res.totalElements) || 0
} catch (error) {
handleApiError(error)
} finally {
@@ -433,6 +433,7 @@ const handleAssignRolesOk = async () => {
roleDialogVisible.value = false
fetchData()
} catch (error) {
roleDialogVisible.value = false
handleApiError(error)
}
}
+10
View File
@@ -0,0 +1,10 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_SIGNATURE_SECRET: string
readonly VITE_API_BASE_URL?: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}