feat: 完成基础配置、API层、状态管理和路由配置
This commit is contained in:
41
src/api/appointment.ts
Normal file
41
src/api/appointment.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
import type { AppointmentInfo } from '@/types/appointment'
|
||||||
|
|
||||||
|
export const appointmentApi = {
|
||||||
|
// 创建预约
|
||||||
|
create(data: {
|
||||||
|
title: string
|
||||||
|
gameId: string
|
||||||
|
groupId: string
|
||||||
|
startTime: string
|
||||||
|
endTime: string
|
||||||
|
maxParticipants: number
|
||||||
|
}) {
|
||||||
|
return request.post<AppointmentInfo>('/appointments', data)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取我的预约列表
|
||||||
|
getMyAppointments(params?: { status?: string }) {
|
||||||
|
return request.get<AppointmentInfo[]>('/appointments/my', { params })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取预约详情
|
||||||
|
getAppointmentDetail(id: string) {
|
||||||
|
return request.get<AppointmentInfo>(`/appointments/${id}`)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 加入预约
|
||||||
|
join(id: string) {
|
||||||
|
return request.post(`/appointments/${id}/join`)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 退出预约
|
||||||
|
leave(id: string) {
|
||||||
|
return request.delete(`/appointments/${id}/leave`)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 取消预约
|
||||||
|
cancel(id: string) {
|
||||||
|
return request.delete(`/appointments/${id}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/api/auth.ts
Normal file
38
src/api/auth.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
export interface LoginParams {
|
||||||
|
account: string
|
||||||
|
password: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RegisterParams {
|
||||||
|
username: string
|
||||||
|
password: string
|
||||||
|
email?: string
|
||||||
|
phone?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LoginResponse {
|
||||||
|
user: import('@/types/user').UserInfo
|
||||||
|
accessToken: string
|
||||||
|
refreshToken: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const authApi = {
|
||||||
|
// 登录
|
||||||
|
login(params: LoginParams) {
|
||||||
|
return request.post<LoginResponse>('/auth/login', params)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 注册
|
||||||
|
register(params: RegisterParams) {
|
||||||
|
return request.post<LoginResponse>('/auth/register', params)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 刷新令牌
|
||||||
|
refreshToken(refreshToken: string) {
|
||||||
|
return request.post<{ accessToken: string; refreshToken: string }>('/auth/refresh', {
|
||||||
|
refreshToken
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/api/game.ts
Normal file
24
src/api/game.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
import type { GameInfo } from '@/types/game'
|
||||||
|
|
||||||
|
export const gameApi = {
|
||||||
|
// 获取游戏列表
|
||||||
|
getGames(params?: { page?: number; limit?: number }) {
|
||||||
|
return request.get<GameInfo[]>('/games', { params })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取热门游戏
|
||||||
|
getPopularGames(limit: number = 10) {
|
||||||
|
return request.get<GameInfo[]>('/games/popular', { params: { limit } })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取游戏详情
|
||||||
|
getGameDetail(id: string) {
|
||||||
|
return request.get<GameInfo>(`/games/${id}`)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 搜索游戏
|
||||||
|
searchGames(keyword: string) {
|
||||||
|
return request.get<GameInfo[]>('/games/search', { params: { keyword } })
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/api/group.ts
Normal file
39
src/api/group.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
import type { GroupInfo, CreateGroupParams } from '@/types/group'
|
||||||
|
|
||||||
|
export const groupApi = {
|
||||||
|
// 创建小组
|
||||||
|
create(data: CreateGroupParams) {
|
||||||
|
return request.post<GroupInfo>('/groups', data)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 加入小组
|
||||||
|
join(data: { groupId: string; nickname?: string }) {
|
||||||
|
return request.post<GroupInfo>('/groups/join', data)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取我的小组列表
|
||||||
|
getMyGroups() {
|
||||||
|
return request.get<GroupInfo[]>('/groups/my')
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取小组详情
|
||||||
|
getGroupDetail(id: string) {
|
||||||
|
return request.get<GroupInfo>(`/groups/${id}`)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 更新小组信息
|
||||||
|
updateGroup(id: string, data: Partial<GroupInfo>) {
|
||||||
|
return request.put<GroupInfo>(`/groups/${id}`, data)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 退出小组
|
||||||
|
leaveGroup(id: string) {
|
||||||
|
return request.delete(`/groups/${id}/leave`)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 解散小组
|
||||||
|
dissolveGroup(id: string) {
|
||||||
|
return request.delete(`/groups/${id}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
6
src/api/index.ts
Normal file
6
src/api/index.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
// 导出所有 API 模块
|
||||||
|
export * from './auth'
|
||||||
|
export * from './user'
|
||||||
|
export * from './group'
|
||||||
|
export * from './game'
|
||||||
|
export * from './appointment'
|
||||||
24
src/api/user.ts
Normal file
24
src/api/user.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
import type { UserInfo } from '@/types/user'
|
||||||
|
|
||||||
|
export const userApi = {
|
||||||
|
// 获取当前用户信息
|
||||||
|
getProfile() {
|
||||||
|
return request.get<UserInfo>('/users/me')
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取指定用户信息
|
||||||
|
getUserInfo(id: string) {
|
||||||
|
return request.get<UserInfo>(`/users/${id}`)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 更新用户信息
|
||||||
|
updateProfile(data: Partial<UserInfo>) {
|
||||||
|
return request.put<UserInfo>('/users/me', data)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 修改密码
|
||||||
|
changePassword(data: { oldPassword: string; newPassword: string }) {
|
||||||
|
return request.put('/users/me/password', data)
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/main.ts
15
src/main.ts
@@ -1,9 +1,24 @@
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
|
import router from './router'
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
|
||||||
|
// Element Plus
|
||||||
|
import ElementPlus from 'element-plus'
|
||||||
|
import 'element-plus/dist/index.css'
|
||||||
|
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||||
|
|
||||||
// 样式
|
// 样式
|
||||||
import './assets/styles/main.css'
|
import './assets/styles/main.css'
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
||||||
|
// 注册所有图标
|
||||||
|
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||||
|
app.component(key, component)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.use(createPinia())
|
||||||
|
app.use(router)
|
||||||
|
app.use(ElementPlus)
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|||||||
106
src/router/index.ts
Normal file
106
src/router/index.ts
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
import type { RouteRecordRaw } from 'vue-router'
|
||||||
|
|
||||||
|
const routes: RouteRecordRaw[] = [
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
name: 'Login',
|
||||||
|
component: () => import('@/views/auth/Login.vue'),
|
||||||
|
meta: { requiresAuth: false }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/register',
|
||||||
|
name: 'Register',
|
||||||
|
component: () => import('@/views/auth/Register.vue'),
|
||||||
|
meta: { requiresAuth: false }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
component: () => import('@/components/layout/MainLayout.vue'),
|
||||||
|
meta: { requiresAuth: true },
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
name: 'Home',
|
||||||
|
component: () => import('@/views/home/index.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'user/profile',
|
||||||
|
name: 'UserProfile',
|
||||||
|
component: () => import('@/views/user/Profile.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'user/settings',
|
||||||
|
name: 'UserSettings',
|
||||||
|
component: () => import('@/views/user/Settings.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'groups',
|
||||||
|
name: 'GroupList',
|
||||||
|
component: () => import('@/views/group/List.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'groups/create',
|
||||||
|
name: 'GroupCreate',
|
||||||
|
component: () => import('@/views/group/Create.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'groups/:id',
|
||||||
|
name: 'GroupDetail',
|
||||||
|
component: () => import('@/views/group/Detail.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'games',
|
||||||
|
name: 'GameList',
|
||||||
|
component: () => import('@/views/game/List.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'games/:id',
|
||||||
|
name: 'GameDetail',
|
||||||
|
component: () => import('@/views/game/Detail.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'appointments',
|
||||||
|
name: 'AppointmentList',
|
||||||
|
component: () => import('@/views/appointment/List.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'appointments/create',
|
||||||
|
name: 'AppointmentCreate',
|
||||||
|
component: () => import('@/views/appointment/Create.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'points/ranking',
|
||||||
|
name: 'PointsRanking',
|
||||||
|
component: () => import('@/views/points/Ranking.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'honors',
|
||||||
|
name: 'Honors',
|
||||||
|
component: () => import('@/views/honor/Timeline.vue')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(),
|
||||||
|
routes
|
||||||
|
})
|
||||||
|
|
||||||
|
// 路由守卫
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
const token = localStorage.getItem('access_token')
|
||||||
|
const requiresAuth = to.meta.requiresAuth !== false
|
||||||
|
|
||||||
|
if (requiresAuth && !token) {
|
||||||
|
next({
|
||||||
|
name: 'Login',
|
||||||
|
query: { redirect: to.fullPath }
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
||||||
29
src/stores/app.ts
Normal file
29
src/stores/app.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
export const useAppStore = defineStore('app', () => {
|
||||||
|
const theme = ref<'light' | 'dark'>('light')
|
||||||
|
const sidebarCollapsed = ref(false)
|
||||||
|
const loading = ref(false)
|
||||||
|
|
||||||
|
function toggleTheme() {
|
||||||
|
theme.value = theme.value === 'light' ? 'dark' : 'light'
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSidebar() {
|
||||||
|
sidebarCollapsed.value = !sidebarCollapsed.value
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLoading(value: boolean) {
|
||||||
|
loading.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
theme,
|
||||||
|
sidebarCollapsed,
|
||||||
|
loading,
|
||||||
|
toggleTheme,
|
||||||
|
toggleSidebar,
|
||||||
|
setLoading
|
||||||
|
}
|
||||||
|
})
|
||||||
77
src/stores/auth.ts
Normal file
77
src/stores/auth.ts
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { authApi } from '@/api/auth'
|
||||||
|
import { storage } from '@/utils/storage'
|
||||||
|
import type { UserInfo } from '@/types/user'
|
||||||
|
|
||||||
|
export const useAuthStore = defineStore('auth', () => {
|
||||||
|
// State
|
||||||
|
const userInfo = ref<UserInfo | null>(null)
|
||||||
|
const accessToken = ref<string>('')
|
||||||
|
const refreshToken = ref<string>('')
|
||||||
|
|
||||||
|
// Getters
|
||||||
|
const isLoggedIn = computed(() => !!accessToken.value && !!userInfo.value)
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
async function login(account: string, password: string) {
|
||||||
|
const data = await authApi.login({ account, password })
|
||||||
|
userInfo.value = data.user
|
||||||
|
accessToken.value = data.accessToken
|
||||||
|
refreshToken.value = data.refreshToken
|
||||||
|
|
||||||
|
// 保存到 localStorage
|
||||||
|
storage.setToken(data.accessToken, data.refreshToken)
|
||||||
|
storage.setUserInfo(data.user)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function register(params: {
|
||||||
|
username: string
|
||||||
|
password: string
|
||||||
|
email?: string
|
||||||
|
phone?: string
|
||||||
|
}) {
|
||||||
|
const data = await authApi.register(params)
|
||||||
|
userInfo.value = data.user
|
||||||
|
accessToken.value = data.accessToken
|
||||||
|
refreshToken.value = data.refreshToken
|
||||||
|
|
||||||
|
storage.setToken(data.accessToken, data.refreshToken)
|
||||||
|
storage.setUserInfo(data.user)
|
||||||
|
}
|
||||||
|
|
||||||
|
function logout() {
|
||||||
|
userInfo.value = null
|
||||||
|
accessToken.value = ''
|
||||||
|
refreshToken.value = ''
|
||||||
|
|
||||||
|
storage.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
function initAuth() {
|
||||||
|
const token = storage.getAccessToken()
|
||||||
|
const refresh = storage.getRefreshToken()
|
||||||
|
const user = storage.getUserInfo()
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
accessToken.value = token
|
||||||
|
}
|
||||||
|
if (refresh) {
|
||||||
|
refreshToken.value = refresh
|
||||||
|
}
|
||||||
|
if (user) {
|
||||||
|
userInfo.value = user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
userInfo,
|
||||||
|
accessToken,
|
||||||
|
refreshToken,
|
||||||
|
isLoggedIn,
|
||||||
|
login,
|
||||||
|
register,
|
||||||
|
logout,
|
||||||
|
initAuth
|
||||||
|
}
|
||||||
|
})
|
||||||
62
src/utils/format.ts
Normal file
62
src/utils/format.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
/**
|
||||||
|
* 格式化日期时间
|
||||||
|
*/
|
||||||
|
export function formatDateTime(date: string | Date): string {
|
||||||
|
const d = new Date(date)
|
||||||
|
const year = d.getFullYear()
|
||||||
|
const month = String(d.getMonth() + 1).padStart(2, '0')
|
||||||
|
const day = String(d.getDate()).padStart(2, '0')
|
||||||
|
const hours = String(d.getHours()).padStart(2, '0')
|
||||||
|
const minutes = String(d.getMinutes()).padStart(2, '0')
|
||||||
|
return `${year}-${month}-${day} ${hours}:${minutes}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化日期
|
||||||
|
*/
|
||||||
|
export function formatDate(date: string | Date): string {
|
||||||
|
const d = new Date(date)
|
||||||
|
const year = d.getFullYear()
|
||||||
|
const month = String(d.getMonth() + 1).padStart(2, '0')
|
||||||
|
const day = String(d.getDate()).padStart(2, '0')
|
||||||
|
return `${year}-${month}-${day}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化时间
|
||||||
|
*/
|
||||||
|
export function formatTime(date: string | Date): string {
|
||||||
|
const d = new Date(date)
|
||||||
|
const hours = String(d.getHours()).padStart(2, '0')
|
||||||
|
const minutes = String(d.getMinutes()).padStart(2, '0')
|
||||||
|
return `${hours}:${minutes}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化相对时间
|
||||||
|
*/
|
||||||
|
export function formatRelativeTime(date: string | Date): string {
|
||||||
|
const d = new Date(date)
|
||||||
|
const now = new Date()
|
||||||
|
const diff = now.getTime() - d.getTime()
|
||||||
|
const seconds = Math.floor(diff / 1000)
|
||||||
|
const minutes = Math.floor(seconds / 60)
|
||||||
|
const hours = Math.floor(minutes / 60)
|
||||||
|
const days = Math.floor(hours / 24)
|
||||||
|
|
||||||
|
if (days > 0) return `${days}天前`
|
||||||
|
if (hours > 0) return `${hours}小时前`
|
||||||
|
if (minutes > 0) return `${minutes}分钟前`
|
||||||
|
return '刚刚'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化文件大小
|
||||||
|
*/
|
||||||
|
export function formatFileSize(bytes: number): string {
|
||||||
|
if (bytes === 0) return '0 B'
|
||||||
|
const k = 1024
|
||||||
|
const sizes = ['B', 'KB', 'MB', 'GB']
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||||
|
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i]
|
||||||
|
}
|
||||||
54
src/utils/request.ts
Normal file
54
src/utils/request.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import axios, { AxiosInstance, AxiosError, AxiosResponse } from 'axios'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { API_BASE_URL, API_TIMEOUT } from '@/constants/config'
|
||||||
|
|
||||||
|
// 创建 axios 实例
|
||||||
|
const service: AxiosInstance = axios.create({
|
||||||
|
baseURL: API_BASE_URL,
|
||||||
|
timeout: API_TIMEOUT,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 请求拦截器
|
||||||
|
service.interceptors.request.use(
|
||||||
|
(config) => {
|
||||||
|
// 从 localStorage 获取 token
|
||||||
|
const token = localStorage.getItem('access_token')
|
||||||
|
if (token) {
|
||||||
|
config.headers.Authorization = `Bearer ${token}`
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 响应拦截器
|
||||||
|
service.interceptors.response.use(
|
||||||
|
(response: AxiosResponse) => {
|
||||||
|
const { code, message, data } = response.data
|
||||||
|
|
||||||
|
if (code === 0) {
|
||||||
|
return data
|
||||||
|
} else {
|
||||||
|
ElMessage.error(message || '请求失败')
|
||||||
|
return Promise.reject(new Error(message))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error: AxiosError) => {
|
||||||
|
if (error.response?.status === 401) {
|
||||||
|
ElMessage.error('登录已过期,请重新登录')
|
||||||
|
localStorage.removeItem('access_token')
|
||||||
|
localStorage.removeItem('refresh_token')
|
||||||
|
window.location.href = '/login'
|
||||||
|
} else {
|
||||||
|
ElMessage.error(error.message || '网络错误')
|
||||||
|
}
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export default service
|
||||||
66
src/utils/storage.ts
Normal file
66
src/utils/storage.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { STORAGE_KEYS } from '@/constants/config'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 本地存储工具
|
||||||
|
*/
|
||||||
|
export const storage = {
|
||||||
|
/**
|
||||||
|
* 设置 token
|
||||||
|
*/
|
||||||
|
setToken(accessToken: string, refreshToken: string) {
|
||||||
|
localStorage.setItem(STORAGE_KEYS.ACCESS_TOKEN, accessToken)
|
||||||
|
localStorage.setItem(STORAGE_KEYS.REFRESH_TOKEN, refreshToken)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 access token
|
||||||
|
*/
|
||||||
|
getAccessToken(): string | null {
|
||||||
|
return localStorage.getItem(STORAGE_KEYS.ACCESS_TOKEN)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 refresh token
|
||||||
|
*/
|
||||||
|
getRefreshToken(): string | null {
|
||||||
|
return localStorage.getItem(STORAGE_KEYS.REFRESH_TOKEN)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除 token
|
||||||
|
*/
|
||||||
|
clearToken() {
|
||||||
|
localStorage.removeItem(STORAGE_KEYS.ACCESS_TOKEN)
|
||||||
|
localStorage.removeItem(STORAGE_KEYS.REFRESH_TOKEN)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置用户信息
|
||||||
|
*/
|
||||||
|
setUserInfo(userInfo: any) {
|
||||||
|
localStorage.setItem(STORAGE_KEYS.USER_INFO, JSON.stringify(userInfo))
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户信息
|
||||||
|
*/
|
||||||
|
getUserInfo(): any | null {
|
||||||
|
const info = localStorage.getItem(STORAGE_KEYS.USER_INFO)
|
||||||
|
return info ? JSON.parse(info) : null
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除用户信息
|
||||||
|
*/
|
||||||
|
clearUserInfo() {
|
||||||
|
localStorage.removeItem(STORAGE_KEYS.USER_INFO)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除所有数据
|
||||||
|
*/
|
||||||
|
clear() {
|
||||||
|
this.clearToken()
|
||||||
|
this.clearUserInfo()
|
||||||
|
}
|
||||||
|
}
|
||||||
42
src/utils/validate.ts
Normal file
42
src/utils/validate.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* 验证邮箱
|
||||||
|
*/
|
||||||
|
export function validateEmail(email: string): boolean {
|
||||||
|
const reg = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||||
|
return reg.test(email)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证手机号
|
||||||
|
*/
|
||||||
|
export function validatePhone(phone: string): boolean {
|
||||||
|
const reg = /^1[3-9]\d{9}$/
|
||||||
|
return reg.test(phone)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证用户名 (3-20位字母数字下划线)
|
||||||
|
*/
|
||||||
|
export function validateUsername(username: string): boolean {
|
||||||
|
const reg = /^[a-zA-Z0-9_]{3,20}$/
|
||||||
|
return reg.test(username)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证密码 (6-20位)
|
||||||
|
*/
|
||||||
|
export function validatePassword(password: string): boolean {
|
||||||
|
return password.length >= 6 && password.length <= 20
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证URL
|
||||||
|
*/
|
||||||
|
export function validateURL(url: string): boolean {
|
||||||
|
try {
|
||||||
|
new URL(url)
|
||||||
|
return true
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user