init
This commit is contained in:
109
src/lib/constants.ts
Normal file
109
src/lib/constants.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
// 系统常量定义
|
||||
|
||||
export const USER_ROLES = {
|
||||
HEADQUARTERS: "headquarters",
|
||||
DEALER: "dealer",
|
||||
MALL: "mall",
|
||||
MERCHANT: "merchant",
|
||||
WORKER: "worker",
|
||||
} as const
|
||||
|
||||
export const EQUIPMENT_STATUS = {
|
||||
NORMAL: "normal",
|
||||
WARNING: "warning",
|
||||
EXPIRED: "expired",
|
||||
FAULT: "fault",
|
||||
} as const
|
||||
|
||||
export const WORK_ORDER_STATUS = {
|
||||
PENDING: "pending",
|
||||
ASSIGNED: "assigned",
|
||||
IN_PROGRESS: "in_progress",
|
||||
COMPLETED: "completed",
|
||||
CANCELLED: "cancelled",
|
||||
} as const
|
||||
|
||||
export const WORK_ORDER_TYPE = {
|
||||
MAINTENANCE: "maintenance",
|
||||
REPAIR: "repair",
|
||||
INSPECTION: "inspection",
|
||||
} as const
|
||||
|
||||
export const PRIORITY_LEVELS = {
|
||||
LOW: "low",
|
||||
NORMAL: "normal",
|
||||
HIGH: "high",
|
||||
URGENT: "urgent",
|
||||
} as const
|
||||
|
||||
// 权限配置
|
||||
export const ROLE_PERMISSIONS = {
|
||||
[USER_ROLES.HEADQUARTERS]: {
|
||||
canManageAllDealers: true,
|
||||
canManageAllMalls: true,
|
||||
canViewAllEquipment: true,
|
||||
canViewAllWorkOrders: true,
|
||||
canManageUsers: true,
|
||||
canViewReports: true,
|
||||
},
|
||||
[USER_ROLES.DEALER]: {
|
||||
canManageWorkers: true,
|
||||
canManageRegionEquipment: true,
|
||||
canManageWorkOrders: true,
|
||||
canManageInventory: true,
|
||||
canViewRegionReports: true,
|
||||
},
|
||||
[USER_ROLES.MALL]: {
|
||||
canManageMerchants: true,
|
||||
canViewMallEquipment: true,
|
||||
canCreateWorkOrders: true,
|
||||
},
|
||||
[USER_ROLES.MERCHANT]: {
|
||||
canViewOwnEquipment: true,
|
||||
canCreateRepairOrders: true,
|
||||
canViewOwnWorkOrders: true,
|
||||
},
|
||||
[USER_ROLES.WORKER]: {
|
||||
canViewAssignedOrders: true,
|
||||
canUpdateOrderStatus: true,
|
||||
canUploadFiles: true,
|
||||
},
|
||||
}
|
||||
|
||||
// 设备状态颜色映射
|
||||
export const EQUIPMENT_STATUS_COLORS = {
|
||||
[EQUIPMENT_STATUS.NORMAL]: "text-green-600 bg-green-50",
|
||||
[EQUIPMENT_STATUS.WARNING]: "text-yellow-600 bg-yellow-50",
|
||||
[EQUIPMENT_STATUS.EXPIRED]: "text-red-600 bg-red-50",
|
||||
[EQUIPMENT_STATUS.FAULT]: "text-red-600 bg-red-100",
|
||||
}
|
||||
|
||||
// 工单状态颜色映射
|
||||
export const WORK_ORDER_STATUS_COLORS = {
|
||||
[WORK_ORDER_STATUS.PENDING]: "text-gray-600 bg-gray-50",
|
||||
[WORK_ORDER_STATUS.ASSIGNED]: "text-blue-600 bg-blue-50",
|
||||
[WORK_ORDER_STATUS.IN_PROGRESS]: "text-yellow-600 bg-yellow-50",
|
||||
[WORK_ORDER_STATUS.COMPLETED]: "text-green-600 bg-green-50",
|
||||
[WORK_ORDER_STATUS.CANCELLED]: "text-red-600 bg-red-50",
|
||||
}
|
||||
|
||||
// 优先级颜色映射
|
||||
export const PRIORITY_COLORS = {
|
||||
[PRIORITY_LEVELS.LOW]: "text-gray-600 bg-gray-50",
|
||||
[PRIORITY_LEVELS.NORMAL]: "text-blue-600 bg-blue-50",
|
||||
[PRIORITY_LEVELS.HIGH]: "text-orange-600 bg-orange-50",
|
||||
[PRIORITY_LEVELS.URGENT]: "text-red-600 bg-red-50",
|
||||
}
|
||||
|
||||
// 系统配置
|
||||
export const SYSTEM_CONFIG = {
|
||||
// 设备到期提醒天数
|
||||
EQUIPMENT_WARNING_DAYS: [30, 7],
|
||||
// 分页默认大小
|
||||
DEFAULT_PAGE_SIZE: 20,
|
||||
// 文件上传限制
|
||||
MAX_FILE_SIZE: 10 * 1024 * 1024, // 10MB
|
||||
ALLOWED_FILE_TYPES: ["image/jpeg", "image/png", "image/gif", "video/mp4", "application/pdf"],
|
||||
// 库存预警阈值
|
||||
LOW_STOCK_THRESHOLD: 10,
|
||||
}
|
||||
78
src/lib/contexts/route-context.tsx
Normal file
78
src/lib/contexts/route-context.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
"use client"
|
||||
|
||||
import React, { createContext, useContext, useEffect, useState } from 'react'
|
||||
import { RouteItem, SidebarMenuItem } from '@/lib/types/route'
|
||||
import { getRouters } from '@/lib/services/route'
|
||||
import { transformRoutes, generateSidebarMenu } from '@/lib/utils/route'
|
||||
|
||||
interface RouteContextType {
|
||||
routes: RouteItem[]
|
||||
sidebarRoutes: SidebarMenuItem[]
|
||||
loading: boolean
|
||||
error: string | null
|
||||
refreshRoutes: () => Promise<void>
|
||||
}
|
||||
|
||||
const RouteContext = createContext<RouteContextType | undefined>(undefined)
|
||||
|
||||
export function RouteProvider({ children }: { children: React.ReactNode }) {
|
||||
const [routes, setRoutes] = useState<RouteItem[]>([])
|
||||
const [sidebarRoutes, setSidebarRoutes] = useState<SidebarMenuItem[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const fetchRoutes = async () => {
|
||||
try {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
|
||||
const response = await getRouters()
|
||||
|
||||
if (response.code === 200) {
|
||||
const transformedRoutes = transformRoutes(response.data)
|
||||
const sidebarMenu = generateSidebarMenu(transformedRoutes)
|
||||
|
||||
setRoutes(transformedRoutes)
|
||||
setSidebarRoutes(sidebarMenu)
|
||||
} else {
|
||||
throw new Error(response.msg || '获取路由失败')
|
||||
}
|
||||
} catch (err) {
|
||||
const errorMessage = err instanceof Error ? err.message : '未知错误'
|
||||
setError(errorMessage)
|
||||
console.error('Failed to fetch routes:', err)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const refreshRoutes = async () => {
|
||||
await fetchRoutes()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchRoutes()
|
||||
}, [])
|
||||
|
||||
const value: RouteContextType = {
|
||||
routes,
|
||||
sidebarRoutes,
|
||||
loading,
|
||||
error,
|
||||
refreshRoutes,
|
||||
}
|
||||
|
||||
return (
|
||||
<RouteContext.Provider value={value}>
|
||||
{children}
|
||||
</RouteContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useRoutes() {
|
||||
const context = useContext(RouteContext)
|
||||
if (context === undefined) {
|
||||
throw new Error('useRoutes must be used within a RouteProvider')
|
||||
}
|
||||
return context
|
||||
}
|
||||
137
src/lib/services/api.ts
Normal file
137
src/lib/services/api.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import { getUserToken, removeStorageItem, runOnClient } from '@/lib/utils/storage'
|
||||
|
||||
// API 客户端工具,统一处理请求和认证
|
||||
const API_BASE_URL = 'http://116.204.124.80:8080/api'
|
||||
|
||||
// 获取存储的 token
|
||||
function getToken(): string | null {
|
||||
return getUserToken()
|
||||
}
|
||||
|
||||
// 统一的 API 请求函数
|
||||
export async function apiRequest<T = any>(
|
||||
endpoint: string,
|
||||
options: RequestInit = {}
|
||||
): Promise<T> {
|
||||
// 确保只在客户端执行
|
||||
if (typeof window === 'undefined') {
|
||||
throw new Error('API calls can only be made on the client side')
|
||||
}
|
||||
|
||||
const token = getToken()
|
||||
|
||||
// 构建完整的 URL
|
||||
const url = endpoint.startsWith('http') ? endpoint : `${API_BASE_URL}${endpoint}`
|
||||
|
||||
// 默认请求头
|
||||
const defaultHeaders: HeadersInit = {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
|
||||
// 如果有 token,添加到请求头
|
||||
if (token) {
|
||||
defaultHeaders['Authorization'] = `Bearer ${token}`
|
||||
}
|
||||
|
||||
// 合并请求头
|
||||
const headers = {
|
||||
...defaultHeaders,
|
||||
...options.headers,
|
||||
}
|
||||
|
||||
// 构建请求配置
|
||||
const requestConfig = {
|
||||
...options,
|
||||
headers,
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(url, requestConfig)
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
// 检查响应状态
|
||||
if (!response.ok) {
|
||||
// 构建调试信息对象
|
||||
const debugInfo = {
|
||||
url: url,
|
||||
endpoint: endpoint,
|
||||
method: requestConfig.method || options.method || 'GET',
|
||||
headers: headers,
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
timestamp: new Date().toISOString()
|
||||
}
|
||||
|
||||
// 如果是 401 未授权,清除本地存储的用户信息
|
||||
if (response.status === 401) {
|
||||
console.error('API 401 未授权错误:', debugInfo)
|
||||
removeStorageItem('user')
|
||||
// 安全地执行客户端跳转
|
||||
runOnClient(() => {
|
||||
window.location.href = '/login'
|
||||
})
|
||||
}
|
||||
|
||||
// 对于 404 错误,提供更详细的调试信息
|
||||
if (response.status === 404) {
|
||||
console.error('API 404 错误详情:', debugInfo)
|
||||
throw new Error(data.msg || `API 端点未找到 (404): ${endpoint}. 请检查端点是否正确或服务器是否正在运行。完整URL: ${url}`)
|
||||
}
|
||||
|
||||
// 记录其他HTTP错误
|
||||
console.error('API HTTP 错误:', debugInfo)
|
||||
throw new Error(data.msg || `HTTP error! status: ${response.status}`)
|
||||
}
|
||||
|
||||
return data
|
||||
} catch (error) {
|
||||
// 如果是网络错误或其他fetch错误,也记录调试信息
|
||||
if (error instanceof TypeError && error.message.includes('fetch')) {
|
||||
console.error('API 网络错误:', {
|
||||
url: url,
|
||||
endpoint: endpoint,
|
||||
method: requestConfig.method || options.method || 'GET',
|
||||
error: error.message,
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
}
|
||||
console.error('API 请求失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// GET 请求
|
||||
export async function apiGet<T = any>(endpoint: string): Promise<T> {
|
||||
return apiRequest<T>(endpoint, { method: 'GET' })
|
||||
}
|
||||
|
||||
// POST 请求
|
||||
export async function apiPost<T = any>(
|
||||
endpoint: string,
|
||||
data?: any
|
||||
): Promise<T> {
|
||||
return apiRequest<T>(endpoint, {
|
||||
method: 'POST',
|
||||
body: data ? JSON.stringify(data) : undefined,
|
||||
})
|
||||
}
|
||||
|
||||
// PUT 请求
|
||||
export async function apiPut<T = any>(
|
||||
endpoint: string,
|
||||
data?: any
|
||||
): Promise<T> {
|
||||
return apiRequest<T>(endpoint, {
|
||||
method: 'PUT',
|
||||
body: data ? JSON.stringify(data) : undefined,
|
||||
})
|
||||
}
|
||||
|
||||
// DELETE 请求
|
||||
export async function apiDelete<T = any>(endpoint: string): Promise<T> {
|
||||
return apiRequest<T>(endpoint, { method: 'DELETE' })
|
||||
}
|
||||
|
||||
// 导出 API_BASE_URL 供其他地方使用
|
||||
export { API_BASE_URL }
|
||||
11
src/lib/services/dealer.ts
Normal file
11
src/lib/services/dealer.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { DealerResponse } from '@/lib/types/dealer';
|
||||
import { apiGet } from './api';
|
||||
|
||||
export async function getDealers(): Promise<DealerResponse> {
|
||||
try {
|
||||
return await apiGet<DealerResponse>('/back/findNextDealer');
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch dealers:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
6
src/lib/services/merchant.ts
Normal file
6
src/lib/services/merchant.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { apiGet } from './api';
|
||||
import { MerchantResponse } from '@/lib/types/merchant';
|
||||
|
||||
export const getMerchants = async (): Promise<MerchantResponse> => {
|
||||
return await apiGet('/back/findNextInfo');
|
||||
};
|
||||
11
src/lib/services/region.ts
Normal file
11
src/lib/services/region.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { ProvinceResponse } from '@/lib/types/region';
|
||||
import { apiGet } from './api';
|
||||
|
||||
export async function getProvinces(): Promise<ProvinceResponse> {
|
||||
try {
|
||||
return await apiGet<ProvinceResponse>('/back/region/provinces');
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch provinces:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
37
src/lib/services/route.ts
Normal file
37
src/lib/services/route.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { RouteResponse } from '@/lib/types/route'
|
||||
import { apiGet } from './api'
|
||||
import { getUserRoleId as getStorageRoleId } from '@/lib/utils/storage'
|
||||
|
||||
// 获取用户的 roleId
|
||||
function getUserRoleId(): string | null {
|
||||
return getStorageRoleId()
|
||||
}
|
||||
|
||||
// 获取路由配置(根据角色ID)
|
||||
export async function getRouters(): Promise<RouteResponse> {
|
||||
try {
|
||||
const roleId = getUserRoleId()
|
||||
|
||||
if (!roleId) {
|
||||
throw new Error('未找到用户角色信息,请重新登录')
|
||||
}
|
||||
|
||||
// 使用 GET 请求传递 roleId 参数
|
||||
const data = await apiGet<RouteResponse>(`/getRoutersByRoleId?roleId=${roleId}`)
|
||||
return data
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch routes:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 根据指定的 roleId 获取路由配置
|
||||
export async function getRoutersByRoleId(roleId: string): Promise<RouteResponse> {
|
||||
try {
|
||||
const data = await apiGet<RouteResponse>(`/getRoutersByRoleId?roleId=${roleId}`)
|
||||
return data
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch routes by roleId:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
11
src/lib/services/skill.ts
Normal file
11
src/lib/services/skill.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { SkillLevelResponse } from '@/lib/types/skill';
|
||||
import { apiGet } from './api';
|
||||
|
||||
export async function getSkillLevels(): Promise<SkillLevelResponse> {
|
||||
try {
|
||||
return await apiGet<SkillLevelResponse>('/back/skillLevel');
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch skill levels:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
156
src/lib/types.ts
Normal file
156
src/lib/types.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
// 系统类型定义
|
||||
|
||||
export type UserRole = "headquarters" | "dealer" | "mall" | "merchant" | "worker"
|
||||
|
||||
export type EquipmentStatus = "normal" | "warning" | "expired" | "fault"
|
||||
|
||||
export type WorkOrderStatus = "pending" | "assigned" | "in_progress" | "completed" | "cancelled"
|
||||
|
||||
export type WorkOrderType = "maintenance" | "repair" | "inspection"
|
||||
|
||||
export type Priority = "low" | "normal" | "high" | "urgent"
|
||||
|
||||
export interface User {
|
||||
id: number
|
||||
username: string
|
||||
email: string
|
||||
phone?: string
|
||||
role: UserRole
|
||||
status: "active" | "inactive"
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface Equipment {
|
||||
id: number
|
||||
qr_code: string
|
||||
model: string
|
||||
serial_number?: string
|
||||
install_date: string
|
||||
last_maintenance_date?: string
|
||||
next_maintenance_date: string
|
||||
status: EquipmentStatus
|
||||
location?: string
|
||||
merchant_id?: number
|
||||
dealer_id?: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface WorkOrder {
|
||||
id: number
|
||||
order_number: string
|
||||
type: WorkOrderType
|
||||
title: string
|
||||
description?: string
|
||||
priority: Priority
|
||||
status: WorkOrderStatus
|
||||
equipment_id?: number
|
||||
merchant_id?: number
|
||||
dealer_id?: number
|
||||
worker_id?: number
|
||||
created_by: number
|
||||
assigned_at?: string
|
||||
started_at?: string
|
||||
completed_at?: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface Dealer {
|
||||
id: number
|
||||
name: string
|
||||
contact_person?: string
|
||||
phone?: string
|
||||
address?: string
|
||||
region?: string
|
||||
user_id?: number
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export interface Mall {
|
||||
id: number
|
||||
name: string
|
||||
address?: string
|
||||
contact_person?: string
|
||||
phone?: string
|
||||
dealer_id?: number
|
||||
user_id?: number
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export interface Merchant {
|
||||
id: number
|
||||
name: string
|
||||
shop_number?: string
|
||||
contact_person?: string
|
||||
phone?: string
|
||||
mall_id?: number
|
||||
user_id?: number
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export interface Worker {
|
||||
id: number
|
||||
name: string
|
||||
phone?: string
|
||||
skills?: string[]
|
||||
region?: string
|
||||
dealer_id?: number
|
||||
user_id?: number
|
||||
status: "available" | "busy" | "offline"
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export interface InventoryItem {
|
||||
id: number
|
||||
dealer_id?: number
|
||||
item_name: string
|
||||
item_code?: string
|
||||
category?: string
|
||||
current_stock: number
|
||||
min_stock_level: number
|
||||
unit?: string
|
||||
unit_price?: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface Notification {
|
||||
id: number
|
||||
user_id: number
|
||||
title: string
|
||||
content?: string
|
||||
type: string
|
||||
is_read: boolean
|
||||
created_at: string
|
||||
}
|
||||
|
||||
// API 响应类型
|
||||
export interface ApiResponse<T> {
|
||||
success: boolean
|
||||
data?: T
|
||||
message?: string
|
||||
error?: string
|
||||
}
|
||||
|
||||
// 分页类型
|
||||
export interface PaginatedResponse<T> {
|
||||
data: T[]
|
||||
total: number
|
||||
page: number
|
||||
limit: number
|
||||
totalPages: number
|
||||
}
|
||||
|
||||
// 统计数据类型
|
||||
export interface DashboardStats {
|
||||
totalEquipment: number
|
||||
normalEquipment: number
|
||||
warningEquipment: number
|
||||
expiredEquipment: number
|
||||
faultEquipment: number
|
||||
pendingOrders: number
|
||||
completedOrders: number
|
||||
activeWorkers: number
|
||||
}
|
||||
58
src/lib/types/dealer.ts
Normal file
58
src/lib/types/dealer.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
export interface Role {
|
||||
createBy: string | null;
|
||||
createTime: string | null;
|
||||
updateBy: string | null;
|
||||
updateTime: string | null;
|
||||
remark: string | null;
|
||||
roleId: string;
|
||||
roleName: string;
|
||||
roleKey: string;
|
||||
roleSort: number;
|
||||
dataScope: string;
|
||||
menuCheckStrictly: boolean;
|
||||
deptCheckStrictly: boolean;
|
||||
status: string;
|
||||
delFlag: string | null;
|
||||
flag: boolean;
|
||||
menuIds: any;
|
||||
deptIds: any;
|
||||
permissions: any;
|
||||
parentRoleKey: string | null;
|
||||
admin: boolean;
|
||||
}
|
||||
|
||||
export interface Dealer {
|
||||
createBy: string;
|
||||
createTime: string;
|
||||
updateBy: string | null;
|
||||
updateTime: string | null;
|
||||
remark: string | null;
|
||||
userId: string;
|
||||
deptId: string | null;
|
||||
userName: string;
|
||||
nickName: string;
|
||||
email: string;
|
||||
phonenumber: string;
|
||||
sex: string;
|
||||
avatar: string;
|
||||
password: string;
|
||||
status: string;
|
||||
delFlag: string;
|
||||
loginIp: string;
|
||||
loginDate: string;
|
||||
pwdUpdateDate: string | null;
|
||||
dept: any;
|
||||
roles: Role[];
|
||||
roleIds: any;
|
||||
postIds: any;
|
||||
roleId: string | null;
|
||||
roleName: string;
|
||||
provinceCode: string;
|
||||
admin: boolean;
|
||||
}
|
||||
|
||||
export interface DealerResponse {
|
||||
msg: string;
|
||||
code: number;
|
||||
data: Dealer[];
|
||||
}
|
||||
35
src/lib/types/merchant.ts
Normal file
35
src/lib/types/merchant.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
export interface Merchant {
|
||||
createBy: string;
|
||||
createTime: string;
|
||||
updateBy: string;
|
||||
updateTime: string;
|
||||
remark: string | null;
|
||||
userId: string;
|
||||
deptId: string | null;
|
||||
userName: string;
|
||||
nickName: string;
|
||||
email: string;
|
||||
phonenumber: string;
|
||||
sex: string;
|
||||
avatar: string;
|
||||
password: string;
|
||||
status: string;
|
||||
delFlag: string;
|
||||
loginIp: string;
|
||||
loginDate: string;
|
||||
pwdUpdateDate: string | null;
|
||||
dept: any;
|
||||
roles: any[];
|
||||
roleIds: string | null;
|
||||
postIds: string | null;
|
||||
roleId: string | null;
|
||||
roleName: string | null;
|
||||
provinceCode: string;
|
||||
admin: boolean;
|
||||
}
|
||||
|
||||
export interface MerchantResponse {
|
||||
msg: string;
|
||||
code: number;
|
||||
data: Merchant[];
|
||||
}
|
||||
14
src/lib/types/region.ts
Normal file
14
src/lib/types/region.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export interface Province {
|
||||
code: string;
|
||||
name: string;
|
||||
parentCode: string | null;
|
||||
level: number;
|
||||
roleId: string | null;
|
||||
children: any;
|
||||
}
|
||||
|
||||
export interface ProvinceResponse {
|
||||
msg: string;
|
||||
code: number;
|
||||
data: Province[];
|
||||
}
|
||||
35
src/lib/types/route.ts
Normal file
35
src/lib/types/route.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
// 路由元信息接口
|
||||
export interface RouteMeta {
|
||||
title: string
|
||||
icon: string
|
||||
noCache: boolean
|
||||
link: string | null
|
||||
}
|
||||
|
||||
// 路由项接口
|
||||
export interface RouteItem {
|
||||
name: string
|
||||
path: string
|
||||
hidden: boolean
|
||||
component: string
|
||||
redirect?: string
|
||||
alwaysShow?: boolean
|
||||
meta?: RouteMeta
|
||||
children?: RouteItem[]
|
||||
}
|
||||
|
||||
// API 响应接口
|
||||
export interface RouteResponse {
|
||||
msg: string
|
||||
code: number
|
||||
data: RouteItem[]
|
||||
}
|
||||
|
||||
// 侧边栏菜单项接口
|
||||
export interface SidebarMenuItem {
|
||||
name: string
|
||||
path: string
|
||||
title: string
|
||||
icon: string
|
||||
children?: SidebarMenuItem[]
|
||||
}
|
||||
13
src/lib/types/skill.ts
Normal file
13
src/lib/types/skill.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export type SkillLevel = 'PRIMARY' | 'INTERMEDIATE' | 'ADVANCED';
|
||||
|
||||
export interface SkillLevelResponse {
|
||||
msg: string;
|
||||
code: number;
|
||||
data: SkillLevel[];
|
||||
}
|
||||
|
||||
export const SKILL_LEVEL_MAP: Record<SkillLevel, string> = {
|
||||
PRIMARY: '初级技师',
|
||||
INTERMEDIATE: '中级技师',
|
||||
ADVANCED: '高级技师'
|
||||
};
|
||||
6
src/lib/utils.ts
Normal file
6
src/lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
114
src/lib/utils/icon-map.ts
Normal file
114
src/lib/utils/icon-map.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import {
|
||||
Home,
|
||||
User,
|
||||
Users,
|
||||
Settings,
|
||||
Shield,
|
||||
Wrench,
|
||||
Package,
|
||||
Bell,
|
||||
Building2,
|
||||
Store,
|
||||
ShoppingBag,
|
||||
Archive,
|
||||
UserCheck,
|
||||
BarChart3,
|
||||
MessageSquare,
|
||||
Music,
|
||||
Monitor,
|
||||
Tool,
|
||||
FileText,
|
||||
Star,
|
||||
Tag,
|
||||
Image,
|
||||
Search,
|
||||
GraduationCap,
|
||||
Button as ButtonIcon,
|
||||
Hash,
|
||||
TreePine,
|
||||
Edit,
|
||||
MessageCircle,
|
||||
Activity,
|
||||
Server,
|
||||
Database,
|
||||
Code,
|
||||
Globe,
|
||||
Calendar,
|
||||
Folder,
|
||||
FolderOpen,
|
||||
ChevronRight,
|
||||
ChevronDown,
|
||||
} from 'lucide-react'
|
||||
|
||||
// 图标映射表
|
||||
export const iconMap: Record<string, any> = {
|
||||
// 系统图标
|
||||
'user': User,
|
||||
'users': Users,
|
||||
'peoples': Users,
|
||||
'people': Users,
|
||||
'settings': Settings,
|
||||
'system': Settings,
|
||||
'shield': Shield,
|
||||
'wrench': Wrench,
|
||||
'package': Package,
|
||||
'bell': Bell,
|
||||
'home': Home,
|
||||
|
||||
// 业务图标
|
||||
'building': Building2,
|
||||
'store': Store,
|
||||
'shopping-bag': ShoppingBag,
|
||||
'archive': Archive,
|
||||
'user-check': UserCheck,
|
||||
'bar-chart': BarChart3,
|
||||
'message': MessageSquare,
|
||||
'music': Music,
|
||||
'monitor': Monitor,
|
||||
'tool': Tool,
|
||||
'documentation': FileText,
|
||||
|
||||
// 内容管理
|
||||
'star': Star,
|
||||
'tab': Tag,
|
||||
'example': Image,
|
||||
'input': Search,
|
||||
'education': GraduationCap,
|
||||
'button': ButtonIcon,
|
||||
'enter': Bell,
|
||||
'build': Tool,
|
||||
|
||||
// 系统管理
|
||||
'tree-table': TreePine,
|
||||
'tree': TreePine,
|
||||
'post': FileText,
|
||||
'dict': FileText,
|
||||
'edit': Edit,
|
||||
'log': FileText,
|
||||
'form': FileText,
|
||||
'logininfor': Activity,
|
||||
|
||||
// 监控相关
|
||||
'online': Users,
|
||||
'job': Calendar,
|
||||
'druid': Database,
|
||||
'server': Server,
|
||||
'redis': Database,
|
||||
'redis-list': Database,
|
||||
|
||||
// 工具相关
|
||||
'code': Code,
|
||||
'swagger': Globe,
|
||||
|
||||
// 通用图标
|
||||
'#': Hash,
|
||||
'folder': Folder,
|
||||
'folder-open': FolderOpen,
|
||||
'chevron-right': ChevronRight,
|
||||
'chevron-down': ChevronDown,
|
||||
}
|
||||
|
||||
// 获取图标组件
|
||||
export function getIcon(iconName: string) {
|
||||
return iconMap[iconName] || Folder
|
||||
}
|
||||
110
src/lib/utils/route.ts
Normal file
110
src/lib/utils/route.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { RouteItem, SidebarMenuItem } from '@/lib/types/route'
|
||||
|
||||
// 转换后端路由数据为前端可用格式
|
||||
export function transformRoutes(routes: RouteItem[]): RouteItem[] {
|
||||
return routes.map(route => ({
|
||||
...route,
|
||||
children: route.children ? transformRoutes(route.children) : undefined
|
||||
}))
|
||||
}
|
||||
|
||||
// 生成侧边栏菜单数据
|
||||
export function generateSidebarMenu(routes: RouteItem[]): SidebarMenuItem[] {
|
||||
const menuItems: SidebarMenuItem[] = []
|
||||
|
||||
routes.forEach(route => {
|
||||
// 跳过隐藏的路由
|
||||
if (route.hidden) return
|
||||
|
||||
// 如果有子路由,处理子路由
|
||||
if (route.children && route.children.length > 0) {
|
||||
const visibleChildren = route.children.filter(child => !child.hidden)
|
||||
|
||||
if (visibleChildren.length > 0) {
|
||||
// 如果是单个子路由且父路由没有 alwaysShow,直接显示子路由
|
||||
if (visibleChildren.length === 1 && !route.alwaysShow) {
|
||||
const child = visibleChildren[0]
|
||||
menuItems.push({
|
||||
name: child.name,
|
||||
path: child.path.startsWith('/') ? child.path : `${route.path}/${child.path}`,
|
||||
title: child.meta?.title || child.name,
|
||||
icon: child.meta?.icon || 'folder'
|
||||
})
|
||||
} else {
|
||||
// 多个子路由或强制显示父路由
|
||||
menuItems.push({
|
||||
name: route.name,
|
||||
path: route.path,
|
||||
title: route.meta?.title || route.name,
|
||||
icon: route.meta?.icon || 'folder',
|
||||
children: generateSidebarMenu(visibleChildren)
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 没有子路由的直接添加
|
||||
menuItems.push({
|
||||
name: route.name,
|
||||
path: route.path,
|
||||
title: route.meta?.title || route.name,
|
||||
icon: route.meta?.icon || 'folder'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return menuItems
|
||||
}
|
||||
|
||||
// 根据路径查找路由信息
|
||||
export function findRouteByPath(routes: RouteItem[], path: string): RouteItem | null {
|
||||
for (const route of routes) {
|
||||
if (route.path === path) {
|
||||
return route
|
||||
}
|
||||
|
||||
if (route.children) {
|
||||
const found = findRouteByPath(route.children, path)
|
||||
if (found) return found
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
// 扁平化路由结构
|
||||
export function flattenRoutes(routes: RouteItem[]): RouteItem[] {
|
||||
const flattened: RouteItem[] = []
|
||||
|
||||
routes.forEach(route => {
|
||||
flattened.push(route)
|
||||
if (route.children) {
|
||||
flattened.push(...flattenRoutes(route.children))
|
||||
}
|
||||
})
|
||||
|
||||
return flattened
|
||||
}
|
||||
|
||||
// 获取面包屑路径
|
||||
export function getBreadcrumbs(routes: RouteItem[], currentPath: string): RouteItem[] {
|
||||
const breadcrumbs: RouteItem[] = []
|
||||
|
||||
function findPath(routes: RouteItem[], path: string, parents: RouteItem[] = []): boolean {
|
||||
for (const route of routes) {
|
||||
const currentParents = [...parents, route]
|
||||
|
||||
if (route.path === path) {
|
||||
breadcrumbs.push(...currentParents)
|
||||
return true
|
||||
}
|
||||
|
||||
if (route.children && findPath(route.children, path, currentParents)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
findPath(routes, currentPath)
|
||||
return breadcrumbs
|
||||
}
|
||||
68
src/lib/utils/storage.ts
Normal file
68
src/lib/utils/storage.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
// 安全的 localStorage 访问工具,避免 SSR 水合错误
|
||||
|
||||
// 安全地获取 localStorage 中的值
|
||||
export function getStorageItem(key: string): string | null {
|
||||
try {
|
||||
return localStorage.getItem(key)
|
||||
} catch (error) {
|
||||
// 在服务端环境下返回 null
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// 安全地设置 localStorage 中的值
|
||||
export function setStorageItem(key: string, value: string): void {
|
||||
try {
|
||||
localStorage.setItem(key, value)
|
||||
} catch (error) {
|
||||
// 在服务端环境下忽略错误
|
||||
console.warn('无法访问 localStorage:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 安全地移除 localStorage 中的值
|
||||
export function removeStorageItem(key: string): void {
|
||||
try {
|
||||
localStorage.removeItem(key)
|
||||
} catch (error) {
|
||||
// 在服务端环境下忽略错误
|
||||
console.warn('无法访问 localStorage:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
export function getUserData(): any | null {
|
||||
try {
|
||||
const user = getStorageItem('user')
|
||||
if (user) {
|
||||
return JSON.parse(user)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('解析用户数据失败:', error)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// 获取用户 token
|
||||
export function getUserToken(): string | null {
|
||||
const userData = getUserData()
|
||||
return userData?.token || null
|
||||
}
|
||||
|
||||
// 获取用户角色 ID
|
||||
export function getUserRoleId(): string | null {
|
||||
const userData = getUserData()
|
||||
return userData?.role || null
|
||||
}
|
||||
|
||||
// 检查是否在客户端环境
|
||||
export function isClient(): boolean {
|
||||
return typeof window !== 'undefined'
|
||||
}
|
||||
|
||||
// 安全地执行客户端操作
|
||||
export function runOnClient(callback: () => void): void {
|
||||
if (isClient()) {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user