generated from wzp/v3-admin-vite
feat: adding user manager function
This commit is contained in:
parent
71384106a7
commit
0638732c73
@ -4,7 +4,7 @@ NODE_ENV = development
|
||||
# 下面是自定义的环境变量,可以修改(命名必须以 VITE_ 开头)
|
||||
|
||||
## 后端接口公共路径(如果解决跨域问题采用反向代理就只需写公共路径)
|
||||
VITE_BASE_API = '/api/v1'
|
||||
VITE_BASE_API = 'http://192.168.1.1:8001'
|
||||
|
||||
## 路由模式 hash 或 html5
|
||||
VITE_ROUTER_HISTORY = 'hash'
|
||||
|
@ -36,8 +36,10 @@
|
||||
"path-to-regexp": "^6.2.1",
|
||||
"pinia": "^2.0.33",
|
||||
"screenfull": "^6.0.2",
|
||||
"ts-md5": "^1.3.1",
|
||||
"vue": "^3.2.47",
|
||||
"vue-router": "^4.1.6",
|
||||
"vue3-slide-verify": "^1.1.4",
|
||||
"vxe-table": "^4.3.10",
|
||||
"vxe-table-plugin-element": "^3.0.6",
|
||||
"xe-utils": "^3.5.7"
|
||||
|
17
pnpm-lock.yaml
generated
17
pnpm-lock.yaml
generated
@ -37,12 +37,18 @@ dependencies:
|
||||
screenfull:
|
||||
specifier: ^6.0.2
|
||||
version: 6.0.2
|
||||
ts-md5:
|
||||
specifier: ^1.3.1
|
||||
version: 1.3.1
|
||||
vue:
|
||||
specifier: ^3.2.47
|
||||
version: 3.2.47
|
||||
vue-router:
|
||||
specifier: ^4.1.6
|
||||
version: 4.1.6(vue@3.2.47)
|
||||
vue3-slide-verify:
|
||||
specifier: ^1.1.4
|
||||
version: 1.1.4
|
||||
vxe-table:
|
||||
specifier: ^4.3.10
|
||||
version: 4.3.10(vue@3.2.47)(xe-utils@3.5.7)
|
||||
@ -4762,6 +4768,11 @@ packages:
|
||||
resolution: {integrity: sha512-/y956gpUo9ZNCb99YjxG7OaslxZWHfCHAUUfshwqOXmxUIvqLjVO581BT+gM59+QV9tFe6/CGG53tsA1Y7RSdg==}
|
||||
dev: true
|
||||
|
||||
/ts-md5@1.3.1:
|
||||
resolution: {integrity: sha512-DiwiXfwvcTeZ5wCE0z+2A9EseZsztaiZtGrtSaY5JOD7ekPnR/GoIVD5gXZAlK9Na9Kvpo9Waz5rW64WKAWApg==}
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/tslib@1.14.1:
|
||||
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
|
||||
dev: true
|
||||
@ -5151,6 +5162,12 @@ packages:
|
||||
typescript: 4.9.5
|
||||
dev: true
|
||||
|
||||
/vue3-slide-verify@1.1.4:
|
||||
resolution: {integrity: sha512-er2d9TSPsF5CcmoxBfP6eSkc4IHROwXu6Ytghkwf52apXpXy2ZtW3tOgHnmFWb3GHESKxZ9bsFXqSX+fJ6hjrg==}
|
||||
dependencies:
|
||||
vue: 3.2.47
|
||||
dev: false
|
||||
|
||||
/vue@3.2.47:
|
||||
resolution: {integrity: sha512-60188y/9Dc9WVrAZeUVSDxRQOZ+z+y5nO2ts9jWXSTkMvayiWxCWOWtBQoYjLeccfXkiiPZWAHcV+WTPhkqJHQ==}
|
||||
dependencies:
|
||||
|
@ -1,36 +0,0 @@
|
||||
/** 模拟接口响应数据 */
|
||||
const SELECT_RESPONSE_DATA = {
|
||||
code: 0,
|
||||
data: [
|
||||
{
|
||||
label: "苹果",
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
label: "香蕉",
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
label: "橘子",
|
||||
value: 3,
|
||||
disabled: true
|
||||
}
|
||||
],
|
||||
message: "获取 Select 数据成功"
|
||||
}
|
||||
|
||||
/** 模拟接口 */
|
||||
export function getSelectDataApi() {
|
||||
return new Promise<typeof SELECT_RESPONSE_DATA>((resolve, reject) => {
|
||||
// 模拟接口响应时间 2s
|
||||
setTimeout(() => {
|
||||
// 模拟接口调用成功
|
||||
if (Math.random() < 0.8) {
|
||||
resolve(SELECT_RESPONSE_DATA)
|
||||
} else {
|
||||
// 模拟接口调用出错
|
||||
reject(new Error("接口发生错误"))
|
||||
}
|
||||
}, 2000)
|
||||
})
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
/** 模拟接口响应数据 */
|
||||
const SUCCESS_RESPONSE_DATA = {
|
||||
code: 0,
|
||||
data: {},
|
||||
message: "获取成功"
|
||||
}
|
||||
|
||||
/** 模拟请求接口成功 */
|
||||
export function getSuccessApi() {
|
||||
return new Promise<typeof SUCCESS_RESPONSE_DATA>((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(SUCCESS_RESPONSE_DATA)
|
||||
}, 1000)
|
||||
})
|
||||
}
|
||||
|
||||
/** 模拟请求接口失败 */
|
||||
export function getErrorApi() {
|
||||
return new Promise((_resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
reject(new Error("发生错误"))
|
||||
}, 1000)
|
||||
})
|
||||
}
|
@ -1,5 +1,8 @@
|
||||
import { request } from "@/utils/service"
|
||||
import type * as Login from "./types/login"
|
||||
import type * as Requests from "@/api/types/requests"
|
||||
import { Md5 } from "ts-md5"
|
||||
import type * as AxiosType from "axios"
|
||||
|
||||
/** 获取登录验证码 */
|
||||
export function getLoginCodeApi() {
|
||||
@ -11,10 +14,20 @@ export function getLoginCodeApi() {
|
||||
|
||||
/** 登录并返回 Token */
|
||||
export function loginApi(data: Login.ILoginRequestData) {
|
||||
return request<Login.LoginResponseData>({
|
||||
url: "users/login",
|
||||
const md5er = new Md5()
|
||||
md5er.appendStr(data.password)
|
||||
data.password = md5er.end(false) as string
|
||||
const urlSearchParams = new URLSearchParams()
|
||||
urlSearchParams.append("username", data.username)
|
||||
urlSearchParams.append("password", data.password)
|
||||
return request<AxiosType.AxiosResponse<Requests.IResponseData<Login.ILoginResponseData>>>({
|
||||
url: "api/auth/login",
|
||||
method: "post",
|
||||
data
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded"
|
||||
},
|
||||
data: urlSearchParams,
|
||||
withCredentials: true
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1,14 +1,11 @@
|
||||
export interface ILoginRequestData {
|
||||
/** admin 或 editor */
|
||||
username: "admin" | "editor"
|
||||
/** 密码 */
|
||||
username: string
|
||||
password: string
|
||||
/** 验证码 */
|
||||
code: string
|
||||
}
|
||||
|
||||
export type LoginCodeResponseData = IApiResponseData<string>
|
||||
|
||||
export type LoginResponseData = IApiResponseData<{ token: string }>
|
||||
|
||||
export type UserInfoResponseData = IApiResponseData<{ username: string; roles: string[] }>
|
||||
export interface ILoginResponseData {
|
||||
message: string
|
||||
id: number
|
||||
role: number
|
||||
cookie: string
|
||||
}
|
||||
|
26
src/api/register/index.ts
Normal file
26
src/api/register/index.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import * as Register from "@/api/register/types/register"
|
||||
import { Md5 } from "ts-md5"
|
||||
import { request } from "@/utils/service"
|
||||
import * as AxiosType from "axios/index"
|
||||
import * as Requests from "@/api/types/requests"
|
||||
|
||||
export function registerApi(data: Register.IRegisterRequestData) {
|
||||
const md5er = new Md5()
|
||||
md5er.appendStr(data.password)
|
||||
data.password = md5er.end(false) as string
|
||||
const requestData = new URLSearchParams()
|
||||
requestData.append("username", data.username)
|
||||
requestData.append("password", data.password)
|
||||
requestData.append("relName", data.relName)
|
||||
requestData.append("email", data.email)
|
||||
requestData.append("roleId", data.roleId.toString())
|
||||
return request<AxiosType.AxiosResponse<Requests.IResponseData<Register.IRegisterResponseData>>>({
|
||||
url: "/api/auth/register",
|
||||
method: "post",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded"
|
||||
},
|
||||
data: requestData,
|
||||
withCredentials: true
|
||||
})
|
||||
}
|
7
src/api/register/types/register.ts
Normal file
7
src/api/register/types/register.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export interface IRegisterRequestData {
|
||||
username: string
|
||||
email: string
|
||||
relName: string
|
||||
password: string
|
||||
roleId: number
|
||||
}
|
11
src/api/types/requests.ts
Normal file
11
src/api/types/requests.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export interface IResponseData<T> {
|
||||
timestamp: number
|
||||
message: string
|
||||
status: number
|
||||
data: T
|
||||
}
|
||||
|
||||
export interface IPageResponseData<T> {
|
||||
total: number
|
||||
info: T[]
|
||||
}
|
51
src/api/users/index.ts
Normal file
51
src/api/users/index.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { request } from "@/utils/service"
|
||||
import * as AxiosType from "axios"
|
||||
import * as Requests from "@/api/types/requests"
|
||||
import {
|
||||
IChangeUserStatus,
|
||||
IDelUserResponseData,
|
||||
IExChangeAccountRequestsData,
|
||||
IExChangeUserResponseData,
|
||||
IGetAllUserResponseData
|
||||
} from "@/api/users/types/users"
|
||||
|
||||
export function getAllUsersApi(page: number) {
|
||||
return request<AxiosType.AxiosResponse<Requests.IResponseData<IGetAllUserResponseData>>>({
|
||||
url: "/account/getUser",
|
||||
method: "get",
|
||||
params: {
|
||||
page: page,
|
||||
num: 10
|
||||
},
|
||||
withCredentials: true
|
||||
})
|
||||
}
|
||||
export function delUserApi(userId: number) {
|
||||
return request<AxiosType.AxiosResponse<Requests.IResponseData<IDelUserResponseData>>>({
|
||||
url: "/account/delUser",
|
||||
method: "get",
|
||||
params: {
|
||||
id: userId
|
||||
},
|
||||
withCredentials: true
|
||||
})
|
||||
}
|
||||
export function exChangeUserDataApi(newUser: IExChangeAccountRequestsData) {
|
||||
return request<AxiosType.AxiosResponse<Requests.IResponseData<IExChangeUserResponseData>>>({
|
||||
url: "/account/changeAccount",
|
||||
method: "post",
|
||||
data: newUser,
|
||||
withCredentials: true
|
||||
})
|
||||
}
|
||||
export function changeUserStatusApi(userid: number, newStatus: boolean) {
|
||||
return request<AxiosType.AxiosResponse<Requests.IResponseData<IChangeUserStatus>>>({
|
||||
url: "/account/changeStatus",
|
||||
method: "get",
|
||||
params: {
|
||||
userId: userid,
|
||||
status: newStatus
|
||||
},
|
||||
withCredentials: true
|
||||
})
|
||||
}
|
15
src/api/users/types/users.ts
Normal file
15
src/api/users/types/users.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { IAccount } from "@/views/user/types"
|
||||
import { IPageResponseData } from "@/api/types/requests"
|
||||
export interface IExChangeAccountRequestsData {
|
||||
id: number
|
||||
username: string
|
||||
email: string
|
||||
relName: string
|
||||
roleId: number
|
||||
}
|
||||
|
||||
export type IDelUserResponseData = string
|
||||
export type IExChangeUserResponseData = string
|
||||
export type IChangeUserStatus = string
|
||||
|
||||
export type IGetAllUserResponseData = IPageResponseData<IAccount>
|
3
src/entities/OrderData.ts
Normal file
3
src/entities/OrderData.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export interface OrderData {
|
||||
id: number
|
||||
}
|
@ -55,16 +55,41 @@ export const constantRoutes: RouteRecordRaw[] = [
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
/**
|
||||
* 动态路由
|
||||
* 用来放置有权限 (Roles 属性) 的路由
|
||||
* 必须带有 Name 属性
|
||||
*/
|
||||
export const asyncRoutes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: "/user",
|
||||
component: Layout,
|
||||
redirect: "/user",
|
||||
children: [
|
||||
{
|
||||
path: "user",
|
||||
component: () => import("@/views/user/index.vue"),
|
||||
name: "User",
|
||||
meta: {
|
||||
title: "用户管理",
|
||||
elIcon: "User",
|
||||
roles: ["admin"]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: "/order",
|
||||
component: Layout,
|
||||
redirect: "/order/",
|
||||
redirect: "/order/manager",
|
||||
name: "Order",
|
||||
meta: {
|
||||
title: "订单",
|
||||
elIcon: "Files",
|
||||
roles: ["admin", "editor"],
|
||||
roles: ["admin"],
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
@ -74,7 +99,7 @@ export const constantRoutes: RouteRecordRaw[] = [
|
||||
name: "CreateOrder",
|
||||
meta: {
|
||||
title: "创建订单",
|
||||
roles: ["editor"],
|
||||
roles: ["admin", "editor"],
|
||||
elIcon: "FolderAdd"
|
||||
}
|
||||
},
|
||||
@ -89,15 +114,7 @@ export const constantRoutes: RouteRecordRaw[] = [
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
/**
|
||||
* 动态路由
|
||||
* 用来放置有权限 (Roles 属性) 的路由
|
||||
* 必须带有 Name 属性
|
||||
*/
|
||||
export const asyncRoutes: RouteRecordRaw[] = [
|
||||
},
|
||||
{
|
||||
path: "/permission",
|
||||
component: Layout,
|
||||
|
@ -15,7 +15,7 @@ router.beforeEach(async (to, _from, next) => {
|
||||
const userStore = useUserStoreHook()
|
||||
const permissionStore = usePermissionStoreHook()
|
||||
// 判断该用户是否登录
|
||||
if (getToken()) {
|
||||
if (getToken() && getToken() !== -1) {
|
||||
if (to.path === "/login") {
|
||||
// 如果已经登录,并准备进入 Login 页面,则重定向到主页
|
||||
next({ path: "/" })
|
||||
|
@ -5,13 +5,14 @@ import { usePermissionStore } from "./permission"
|
||||
import { useTagsViewStore } from "./tags-view"
|
||||
import { getToken, removeToken, setToken } from "@/utils/cache/cookies"
|
||||
import router, { resetRouter } from "@/router"
|
||||
import { loginApi, getUserInfoApi } from "@/api/login"
|
||||
import { loginApi } from "@/api/login"
|
||||
import { type ILoginRequestData } from "@/api/login/types/login"
|
||||
import { type RouteRecordRaw } from "vue-router"
|
||||
import asyncRouteSettings from "@/config/async-route"
|
||||
|
||||
export const useUserStore = defineStore("user", () => {
|
||||
const token = ref<string>(getToken() || "")
|
||||
/** Token即用户id */
|
||||
const token = ref<number>(getToken() || -1)
|
||||
const roles = ref<string[]>([])
|
||||
const username = ref<string>("")
|
||||
|
||||
@ -25,15 +26,14 @@ export const useUserStore = defineStore("user", () => {
|
||||
/** 登录 */
|
||||
const login = (loginData: ILoginRequestData) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
loginApi({
|
||||
username: loginData.username,
|
||||
password: loginData.password,
|
||||
code: loginData.code
|
||||
})
|
||||
loginApi(loginData)
|
||||
.then((res) => {
|
||||
setToken(res.data.token)
|
||||
token.value = res.data.token
|
||||
resolve(true)
|
||||
if (res.status == 202) {
|
||||
setToken(res.data.data.id)
|
||||
resolve(res.data.data)
|
||||
return
|
||||
}
|
||||
reject(res.data.data)
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error)
|
||||
@ -42,30 +42,31 @@ export const useUserStore = defineStore("user", () => {
|
||||
}
|
||||
/** 获取用户详情 */
|
||||
const getInfo = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
getUserInfoApi()
|
||||
.then((res) => {
|
||||
const data = res.data
|
||||
username.value = data.username
|
||||
// 验证返回的 roles 是否是一个非空数组
|
||||
if (data.roles && data.roles.length > 0) {
|
||||
roles.value = data.roles
|
||||
} else {
|
||||
// 塞入一个没有任何作用的默认角色,不然路由守卫逻辑会无限循环
|
||||
roles.value = asyncRouteSettings.defaultRoles
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
roles.value = asyncRouteSettings.defaultRoles
|
||||
roles.value = ["admin"]
|
||||
console.log("getInfo()")
|
||||
// return new Promise((resolve, reject) => {
|
||||
// getUserInfoApi()
|
||||
// .then((res) => {
|
||||
// const data = res.data
|
||||
// username.value = data.username
|
||||
// // 验证返回的 roles 是否是一个非空数组
|
||||
// if (data.roles && data.roles.length > 0) {
|
||||
// roles.value = data.roles
|
||||
// } else {
|
||||
// // 塞入一个没有任何作用的默认角色,不然路由守卫逻辑会无限循环
|
||||
// roles.value = asyncRouteSettings.defaultRoles
|
||||
// }
|
||||
// resolve(res)
|
||||
// })
|
||||
// .catch((error) => {
|
||||
// reject(error)
|
||||
// })
|
||||
// })
|
||||
}
|
||||
/** 切换角色 */
|
||||
const changeRoles = async (role: string) => {
|
||||
const newToken = "token-" + role
|
||||
token.value = newToken
|
||||
setToken(newToken)
|
||||
console.log(role)
|
||||
await getInfo()
|
||||
permissionStore.setRoutes(roles.value)
|
||||
resetRouter()
|
||||
|
11
src/utils/cache/cookies.ts
vendored
11
src/utils/cache/cookies.ts
vendored
@ -3,11 +3,14 @@
|
||||
import CacheKey from "@/constants/cacheKey"
|
||||
import Cookies from "js-cookie"
|
||||
|
||||
export const getToken = () => {
|
||||
return Cookies.get(CacheKey.TOKEN)
|
||||
export const getToken = (): number | undefined => {
|
||||
const tokenString = Cookies.get(CacheKey.TOKEN)
|
||||
if (tokenString) {
|
||||
return Number.parseInt(tokenString)
|
||||
}
|
||||
}
|
||||
export const setToken = (token: string) => {
|
||||
Cookies.set(CacheKey.TOKEN, token)
|
||||
export const setToken = (token: number) => {
|
||||
Cookies.set(CacheKey.TOKEN, token.toString())
|
||||
}
|
||||
export const removeToken = () => {
|
||||
Cookies.remove(CacheKey.TOKEN)
|
||||
|
@ -1,8 +1,8 @@
|
||||
import axios, { type AxiosInstance, type AxiosRequestConfig } from "axios"
|
||||
import { useUserStoreHook } from "@/store/modules/user"
|
||||
import { ElMessage } from "element-plus"
|
||||
import { get } from "lodash-es"
|
||||
import { getToken } from "./cache/cookies"
|
||||
import { useRouter } from "vue-router"
|
||||
import { useUserStore } from "@/store/modules/user"
|
||||
import { ElMessage } from "element-plus"
|
||||
|
||||
/** 创建请求实例 */
|
||||
function createService() {
|
||||
@ -10,77 +10,25 @@ function createService() {
|
||||
const service = axios.create()
|
||||
// 请求拦截
|
||||
service.interceptors.request.use(
|
||||
(config) => config,
|
||||
(config) => {
|
||||
return config
|
||||
},
|
||||
// 发送失败
|
||||
(error) => Promise.reject(error)
|
||||
)
|
||||
// 响应拦截(可根据具体业务作出相应的调整)
|
||||
service.interceptors.response.use(
|
||||
(response) => {
|
||||
// apiData 是 API 返回的数据
|
||||
const apiData = response.data as any
|
||||
// 这个 Code 是和后端约定的业务 Code
|
||||
const code = apiData.code
|
||||
// 如果没有 Code, 代表这不是项目后端开发的 API
|
||||
if (code === undefined) {
|
||||
ElMessage.error("非本系统的接口")
|
||||
return Promise.reject(new Error("非本系统的接口"))
|
||||
} else {
|
||||
switch (code) {
|
||||
case 0:
|
||||
// code === 0 代表没有错误
|
||||
return apiData
|
||||
default:
|
||||
// 不是正确的 Code
|
||||
ElMessage.error(apiData.message || "Error")
|
||||
return Promise.reject(new Error("Error"))
|
||||
}
|
||||
}
|
||||
return response
|
||||
},
|
||||
(error) => {
|
||||
// Status 是 HTTP 状态码
|
||||
const status = get(error, "response.status")
|
||||
switch (status) {
|
||||
case 400:
|
||||
error.message = "请求错误"
|
||||
break
|
||||
case 401:
|
||||
// Token 过期时,直接退出登录并强制刷新页面(会重定向到登录页)
|
||||
useUserStoreHook().logout()
|
||||
location.reload()
|
||||
break
|
||||
case 403:
|
||||
error.message = "拒绝访问"
|
||||
break
|
||||
case 404:
|
||||
error.message = "请求地址出错"
|
||||
break
|
||||
case 408:
|
||||
error.message = "请求超时"
|
||||
break
|
||||
case 500:
|
||||
error.message = "服务器内部错误"
|
||||
break
|
||||
case 501:
|
||||
error.message = "服务未实现"
|
||||
break
|
||||
case 502:
|
||||
error.message = "网关错误"
|
||||
break
|
||||
case 503:
|
||||
error.message = "服务不可用"
|
||||
break
|
||||
case 504:
|
||||
error.message = "网关超时"
|
||||
break
|
||||
case 505:
|
||||
error.message = "HTTP 版本不受支持"
|
||||
break
|
||||
default:
|
||||
break
|
||||
if (error.response.status === 555) {
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
userStore.logout()
|
||||
router.push("/login").then((_) => ElMessage.warning("登录过期,请重新登录!"))
|
||||
}
|
||||
ElMessage.error(error.message)
|
||||
return Promise.reject(error)
|
||||
return error
|
||||
}
|
||||
)
|
||||
return service
|
||||
@ -91,13 +39,12 @@ function createRequestFunction(service: AxiosInstance) {
|
||||
return function <T>(config: AxiosRequestConfig): Promise<T> {
|
||||
const configDefault = {
|
||||
headers: {
|
||||
// 携带 Token
|
||||
Authorization: "Bearer " + getToken(),
|
||||
"Content-Type": get(config, "headers.Content-Type", "application/json")
|
||||
},
|
||||
timeout: 5000,
|
||||
baseURL: import.meta.env.VITE_BASE_API,
|
||||
data: {}
|
||||
data: {},
|
||||
widthCredentials: true
|
||||
}
|
||||
return service(Object.assign(configDefault, config))
|
||||
}
|
||||
|
@ -2,76 +2,89 @@
|
||||
import { reactive, ref } from "vue"
|
||||
import { useRouter } from "vue-router"
|
||||
import { useUserStore } from "@/store/modules/user"
|
||||
import { User, Lock, Key, Picture, Loading } from "@element-plus/icons-vue"
|
||||
import { User, Lock } from "@element-plus/icons-vue"
|
||||
import ThemeSwitch from "@/components/ThemeSwitch/index.vue"
|
||||
import { type FormInstance, FormRules } from "element-plus"
|
||||
import { getLoginCodeApi } from "@/api/login"
|
||||
import { ElMessage, type FormInstance, FormRules } from "element-plus"
|
||||
import SlideVerify, { SlideVerifyInstance } from "vue3-slide-verify"
|
||||
import "vue3-slide-verify/dist/style.css"
|
||||
import { type ILoginRequestData } from "@/api/login/types/login"
|
||||
const isShowVerifyDialog = ref<boolean>(false)
|
||||
|
||||
const router = useRouter()
|
||||
const loginFormRef = ref<FormInstance | null>(null)
|
||||
|
||||
/** 登录按钮 Loading */
|
||||
const loading = ref(false)
|
||||
/** 验证码图片 URL */
|
||||
const codeUrl = ref("")
|
||||
/** 登录表单数据 */
|
||||
const loginForm: ILoginRequestData = reactive({
|
||||
username: "admin",
|
||||
password: "12345678",
|
||||
code: ""
|
||||
username: "",
|
||||
password: ""
|
||||
})
|
||||
/** 登录表单校验规则 */
|
||||
const loginFormRules: FormRules = {
|
||||
username: [{ required: true, message: "请输入用户名", trigger: "blur" }],
|
||||
username: [
|
||||
{ required: true, message: "请输入用户名", trigger: "blur" },
|
||||
{ min: 3, max: 10, message: "长度在 3 到 10 个字符", trigger: "blur" },
|
||||
{ pattern: "[A-Za-z0-9_-一-龥]+", message: "用户名不符合要求" }
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: "请输入密码", trigger: "blur" },
|
||||
{ min: 8, max: 16, message: "长度在 8 到 16 个字符", trigger: "blur" }
|
||||
],
|
||||
code: [{ required: true, message: "请输入验证码", trigger: "blur" }]
|
||||
]
|
||||
}
|
||||
/** 登录逻辑 */
|
||||
const handleLogin = () => {
|
||||
loginFormRef.value?.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
loading.value = true
|
||||
useUserStore()
|
||||
.login({
|
||||
username: loginForm.username,
|
||||
password: loginForm.password,
|
||||
code: loginForm.code
|
||||
})
|
||||
.then(() => {
|
||||
router.push({ path: "/" })
|
||||
})
|
||||
.catch(() => {
|
||||
createCode()
|
||||
loginForm.password = ""
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
sliderVerify.value?.refresh()
|
||||
isShowVerifyDialog.value = true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
/** 创建验证码 */
|
||||
const createCode = () => {
|
||||
// 先清空验证码的输入
|
||||
loginForm.code = ""
|
||||
// 获取验证码
|
||||
codeUrl.value = ""
|
||||
getLoginCodeApi().then((res) => {
|
||||
codeUrl.value = res.data
|
||||
})
|
||||
const sliderVerify = ref<SlideVerifyInstance>()
|
||||
const onVerifyRetry = () => {
|
||||
ElMessage.warning("检测到非人为操作的哦! try again")
|
||||
}
|
||||
|
||||
/** 初始化验证码 */
|
||||
createCode()
|
||||
const onVerifySuccess = (_: number) => {
|
||||
isShowVerifyDialog.value = false
|
||||
loading.value = true
|
||||
useUserStore()
|
||||
.login({
|
||||
username: loginForm.username,
|
||||
password: loginForm.password
|
||||
})
|
||||
.then(() => {
|
||||
router.push({ path: "/" })
|
||||
ElMessage.success("登录成功!")
|
||||
})
|
||||
.catch(() => {
|
||||
loginForm.password = ""
|
||||
ElMessage.error("登录失败,用户名或密码错误")
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const onVerifyFail = () => {
|
||||
ElMessage.error("验证失败,请重试!")
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog v-model="isShowVerifyDialog" title="滑动验证" width="350px" align-center>
|
||||
<slide-verify
|
||||
ref="sliderVerify"
|
||||
slider-text="向右滑动->"
|
||||
:accuracy="4"
|
||||
@again="onVerifyRetry"
|
||||
@success="onVerifySuccess"
|
||||
@fail="onVerifyFail"
|
||||
/>
|
||||
</el-dialog>
|
||||
<div class="login-container">
|
||||
<ThemeSwitch class="theme-switch" />
|
||||
<div class="login-card">
|
||||
@ -101,28 +114,6 @@ createCode()
|
||||
show-password
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item prop="code">
|
||||
<el-input
|
||||
v-model.trim="loginForm.code"
|
||||
placeholder="验证码"
|
||||
type="text"
|
||||
tabindex="3"
|
||||
:prefix-icon="Key"
|
||||
maxlength="7"
|
||||
size="large"
|
||||
>
|
||||
<template #append>
|
||||
<el-image :src="codeUrl" @click="createCode" draggable="false">
|
||||
<template #placeholder>
|
||||
<el-icon><Picture /></el-icon>
|
||||
</template>
|
||||
<template #error>
|
||||
<el-icon><Loading /></el-icon>
|
||||
</template>
|
||||
</el-image>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-button :loading="loading" type="primary" size="large" @click.prevent="handleLogin"> 登 录 </el-button>
|
||||
</el-form>
|
||||
</div>
|
||||
|
@ -1,5 +1,24 @@
|
||||
<template><div>OrderManger</div></template>
|
||||
<template>
|
||||
<el-table :data="tableShowData" style="width: 100%">
|
||||
<el-table-column label="Date" prop="date" />
|
||||
<el-table-column label="Name" prop="name" />
|
||||
<el-table-column align="right">
|
||||
<template #header>
|
||||
<el-input v-model="search" size="small" placeholder="Type to search" />
|
||||
</template>
|
||||
<template #default="scope">
|
||||
<el-button size="small" @click="handleEdit(scope.$index, scope.row)">Edit</el-button>
|
||||
<el-button size="small" type="danger" @click="handleDelete(scope.$index, scope.row)">Delete</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup></script>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from "vue"
|
||||
import { OrderData } from "@/entities/OrderData"
|
||||
|
||||
const tableShowData = ref<OrderData[]>([])
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
233
src/views/user/index.vue
Normal file
233
src/views/user/index.vue
Normal file
@ -0,0 +1,233 @@
|
||||
<template>
|
||||
<div class="userDiv">
|
||||
<div class="userActionDiv">
|
||||
<el-button type="warning" size="default" @click="handleAddAccountButtonClick">添加</el-button>
|
||||
</div>
|
||||
<div class="userTableDiv">
|
||||
<el-table :data="showTableData" style="width: 100%" table-layout="auto">
|
||||
<el-table-column fixed prop="id" label="ID" />
|
||||
<el-table-column prop="username" label="用户名" />
|
||||
<el-table-column prop="email" label="邮箱" />
|
||||
<el-table-column prop="relName" label="真实姓名" />
|
||||
<el-table-column label="状态">
|
||||
<template #default="scope">
|
||||
<el-switch :model-value="scope.row.status" @change="handleChangeUserStatus(scope.$index)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="roleId" label="权限等级" />
|
||||
<el-table-column fixed="right" label="操作">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="getToken() !== scope.row.id"
|
||||
link
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleDelete(scope.row)"
|
||||
>删除</el-button
|
||||
>
|
||||
<el-button link type="primary" size="small" @click="handleExchange(scope.row)">修改</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
<div class="userPaginationDiv">
|
||||
<el-pagination
|
||||
background
|
||||
layout="prev, pager, next"
|
||||
:total="totalUserCount"
|
||||
:current-page="nowPage"
|
||||
@update:current-page="handleChangePage"
|
||||
/>
|
||||
</div>
|
||||
<el-dialog v-model="addAccountDialogShow" :title="getDialogTitle()" width="30%" align-center>
|
||||
<el-form ref="addAccountFormRef" :model="addAccountData" status-icon :rules="addAccountRules" label-width="120px">
|
||||
<el-form-item label="用户名" prop="username">
|
||||
<el-input v-model="addAccountData.username" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="密码" prop="password" v-if="!modifyMode">
|
||||
<el-input v-model="addAccountData.password" show-password autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input v-model="addAccountData.email" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="真实姓名" prop="relName">
|
||||
<el-input v-model="addAccountData.relName" autocomplete="off" />
|
||||
</el-form-item>
|
||||
<el-form-item label="权限等级" prop="roleId">
|
||||
<el-input v-model.number="addAccountData.roleId" type="number" autocomplete="off" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="addAccountDialogShow = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleAddAccount">确定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from "vue"
|
||||
import { IAccount } from "@/views/user/types"
|
||||
import { ElMessage, FormInstance, FormRules } from "element-plus"
|
||||
import { IRegisterRequestData } from "@/api/register/types/register"
|
||||
import { registerApi } from "@/api/register"
|
||||
import { changeUserStatusApi, delUserApi, exChangeUserDataApi, getAllUsersApi } from "@/api/users"
|
||||
import { getToken } from "@/utils/cache/cookies"
|
||||
|
||||
const showTableData = ref<IAccount[]>([])
|
||||
const totalUserCount = ref<number>(1)
|
||||
const nowPage = ref<number>(1)
|
||||
const addAccountDialogShow = ref<boolean>(false)
|
||||
const addAccountData = ref<IRegisterRequestData>({ username: "", password: "", email: "", relName: "", roleId: 0 })
|
||||
const addAccountRules: FormRules = {
|
||||
username: [
|
||||
{ required: true, message: "请输入用户名", trigger: "blur" },
|
||||
{ min: 3, max: 10, message: "长度在 3 到 10 个字符", trigger: "blur" },
|
||||
{ pattern: "[A-Za-z0-9_-一-龥]+", message: "用户名不符合要求" }
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: "请输入密码", trigger: "blur" },
|
||||
{ min: 8, max: 16, message: "长度在 8 到 16 个字符", trigger: "blur" }
|
||||
],
|
||||
email: [
|
||||
{ required: true, message: "请输入邮箱", trigger: "blur" },
|
||||
{ pattern: "\\w[-\\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\\.)+[A-Za-z]{2,14}", message: "邮箱不符合要求" }
|
||||
],
|
||||
relName: [
|
||||
{ required: true, message: "请输入真实姓名", trigger: "blur" },
|
||||
{ min: 2, max: 6, message: "长度在 2 到 6 个字符", trigger: "blur" },
|
||||
{ pattern: "[A-Za-z0-9_-一-龥]+", message: "真实姓名不符合要求" }
|
||||
],
|
||||
roleId: [
|
||||
{ required: true, message: "请输入权限等级", trigger: "blur" },
|
||||
{ type: "number", message: "权限等级必须为数字", trigger: "blur" }
|
||||
]
|
||||
}
|
||||
const addAccountFormRef = ref<FormInstance>()
|
||||
const modifyMode = ref<IAccount>()
|
||||
const handleDelete = (account: IAccount) => {
|
||||
delUserApi(account.id)
|
||||
.then((response) => {
|
||||
ElMessage.success("删除成功,请刷新!")
|
||||
console.log(response)
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage.error("删除失败,请重试!")
|
||||
})
|
||||
}
|
||||
const handleExchange = (account: IAccount) => {
|
||||
addAccountDialogShow.value = true
|
||||
modifyMode.value = account
|
||||
addAccountData.value.password = ""
|
||||
addAccountData.value.username = account.username
|
||||
addAccountData.value.email = account.email
|
||||
addAccountData.value.relName = account.relName
|
||||
addAccountData.value.roleId = account.roleId
|
||||
}
|
||||
const handleAddAccountButtonClick = () => {
|
||||
addAccountDialogShow.value = true
|
||||
modifyMode.value = undefined
|
||||
addAccountData.value.password = ""
|
||||
addAccountData.value.username = ""
|
||||
addAccountData.value.email = ""
|
||||
addAccountData.value.relName = ""
|
||||
addAccountData.value.roleId = 0
|
||||
}
|
||||
const handleAddAccount = () => {
|
||||
if (!addAccountFormRef.value?.validate()) {
|
||||
ElMessage.error("数据存在错误!")
|
||||
return
|
||||
}
|
||||
if (modifyMode.value) {
|
||||
modifyMode.value.username = addAccountData.value.username
|
||||
modifyMode.value.email = addAccountData.value.email
|
||||
modifyMode.value.relName = addAccountData.value.relName
|
||||
modifyMode.value.roleId = addAccountData.value.roleId
|
||||
exChangeUserDataApi({
|
||||
id: modifyMode.value.id,
|
||||
username: modifyMode.value.username,
|
||||
email: modifyMode.value.email,
|
||||
relName: modifyMode.value.relName,
|
||||
roleId: modifyMode.value.roleId
|
||||
})
|
||||
.then(() => {
|
||||
ElMessage.success("修改成功")
|
||||
addAccountDialogShow.value = false
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage.error("修改失败!")
|
||||
})
|
||||
return
|
||||
}
|
||||
addAccountDialogShow.value = false
|
||||
registerApi(addAccountData.value)
|
||||
.then((_) => {
|
||||
ElMessage.success("添加成功!")
|
||||
})
|
||||
.catch((err) => {
|
||||
ElMessage.error("添加用户失败!,原因:" + err.message)
|
||||
})
|
||||
}
|
||||
const handleChangePage = () => {
|
||||
getAllUsersApi(nowPage.value)
|
||||
.then((response) => {
|
||||
showTableData.value = response.data.data.info
|
||||
totalUserCount.value = response.data.data.total
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage.error("获取用户数据失败!")
|
||||
})
|
||||
}
|
||||
const getDialogTitle = (): string => {
|
||||
if (modifyMode.value) {
|
||||
return "修改用户"
|
||||
}
|
||||
return "添加用户"
|
||||
}
|
||||
const handleChangeUserStatus = (index: number) => {
|
||||
if (getToken() !== showTableData.value[index].id) {
|
||||
const userData = showTableData.value[index]
|
||||
if (userData) {
|
||||
showTableData.value[index].status = !showTableData.value[index].status
|
||||
changeUserStatusApi(showTableData.value[index].id, showTableData.value[index].status)
|
||||
.then(() => {
|
||||
ElMessage.success("修改成功")
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
ElMessage.error("修改失败!")
|
||||
})
|
||||
}
|
||||
} else {
|
||||
ElMessage.error("用户不能修改自己的状态!")
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
handleChangePage()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.userDiv {
|
||||
width: calc(100% - 20px);
|
||||
height: calc(100% - 20px);
|
||||
margin: 10px 10px 10px 10px;
|
||||
}
|
||||
.userTableDiv {
|
||||
width: 100%;
|
||||
height: calc(90% - 20px);
|
||||
margin-top: 10px;
|
||||
}
|
||||
.userActionDiv {
|
||||
height: 5%;
|
||||
width: max-content;
|
||||
min-height: 32px;
|
||||
}
|
||||
.userPaginationDiv {
|
||||
height: 5%;
|
||||
margin-top: 10px;
|
||||
min-height: 32px;
|
||||
}
|
||||
</style>
|
9
src/views/user/types/index.ts
Normal file
9
src/views/user/types/index.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export interface IAccount {
|
||||
id: number
|
||||
username: string
|
||||
email: string
|
||||
relName: string
|
||||
password?: string
|
||||
status: boolean
|
||||
roleId: number
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user