提交 61a92fbd authored 作者: hejie's avatar hejie

Merge branch 'feature-20250411' of https://t.clxkj.cn/hejie/HwManageSystem into dev-hejie

...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
"editor.tabSize": 2, "editor.tabSize": 2,
"editor.formatOnPaste": true, "editor.formatOnPaste": true,
"editor.guides.bracketPairs": "active", "editor.guides.bracketPairs": "active",
"files.autoSave": "afterDelay", "files.autoSave": "off",
"git.confirmSync": false, "git.confirmSync": false,
"workbench.startupEditor": "newUntitledFile", "workbench.startupEditor": "newUntitledFile",
"editor.suggestSelection": "first", "editor.suggestSelection": "first",
......
...@@ -70,6 +70,7 @@ ...@@ -70,6 +70,7 @@
"codemirror": "^5.65.19", "codemirror": "^5.65.19",
"codemirror-editor-vue3": "^2.8.0", "codemirror-editor-vue3": "^2.8.0",
"cropperjs": "^1.6.2", "cropperjs": "^1.6.2",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"deep-chat": "^2.1.1", "deep-chat": "^2.1.1",
"echarts": "^5.6.0", "echarts": "^5.6.0",
......
...@@ -71,6 +71,9 @@ importers: ...@@ -71,6 +71,9 @@ importers:
cropperjs: cropperjs:
specifier: ^1.6.2 specifier: ^1.6.2
version: 1.6.2 version: 1.6.2
crypto-js:
specifier: ^4.2.0
version: 4.2.0
dayjs: dayjs:
specifier: ^1.11.13 specifier: ^1.11.13
version: 1.11.13 version: 1.11.13
...@@ -2599,6 +2602,9 @@ packages: ...@@ -2599,6 +2602,9 @@ packages:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
crypto-js@4.2.0:
resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
css-declaration-sorter@7.2.0: css-declaration-sorter@7.2.0:
resolution: {integrity: sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==} resolution: {integrity: sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==}
engines: {node: ^14 || ^16 || >=18} engines: {node: ^14 || ^16 || >=18}
...@@ -8519,6 +8525,8 @@ snapshots: ...@@ -8519,6 +8525,8 @@ snapshots:
shebang-command: 2.0.0 shebang-command: 2.0.0
which: 2.0.2 which: 2.0.2
crypto-js@4.2.0: {}
css-declaration-sorter@7.2.0(postcss@8.5.3): css-declaration-sorter@7.2.0(postcss@8.5.3):
dependencies: dependencies:
postcss: 8.5.3 postcss: 8.5.3
......
...@@ -6,6 +6,11 @@ export const getLogin = (data?: object) => { ...@@ -6,6 +6,11 @@ export const getLogin = (data?: object) => {
return http.request<LoginResult>("post", "/api/auth/login", { data }); return http.request<LoginResult>("post", "/api/auth/login", { data });
}; };
/** 获取验证码 */
export const getCaptcha = (time?: string) => {
return http.request<CaptchaResult>("get", "/api/auth/captchaNumber/" + time);
};
/** 刷新`token` */ /** 刷新`token` */
export const refreshTokenApi = (data?: object) => { export const refreshTokenApi = (data?: object) => {
return http.request<RefreshTokenResult>("post", "/refresh-token", { data }); return http.request<RefreshTokenResult>("post", "/refresh-token", { data });
...@@ -224,7 +229,7 @@ export type LoginResult = { ...@@ -224,7 +229,7 @@ export type LoginResult = {
status: number; status: number;
success: boolean; success: boolean;
data: { data: {
jwt: string; token: string;
/** 头像 */ /** 头像 */
avatar: string; avatar: string;
/** 用户名 */ /** 用户名 */
...@@ -255,3 +260,9 @@ export type RefreshTokenResult = { ...@@ -255,3 +260,9 @@ export type RefreshTokenResult = {
expires: Date; expires: Date;
}; };
}; };
export type CaptchaResult = {
code: string;
success: boolean;
data: string;
};
...@@ -4,7 +4,7 @@ export type UserResult = { ...@@ -4,7 +4,7 @@ export type UserResult = {
status: number; status: number;
success: boolean; success: boolean;
data: { data: {
jwt: string; token: string;
/** 头像 */ /** 头像 */
avatar: string; avatar: string;
/** 用户名 */ /** 用户名 */
...@@ -27,6 +27,8 @@ export type UserResult = { ...@@ -27,6 +27,8 @@ export type UserResult = {
export type RefreshTokenResult = { export type RefreshTokenResult = {
success: boolean; success: boolean;
data: { data: {
/** `token` */
token?: string;
/** `token` */ /** `token` */
accessToken: string; accessToken: string;
/** 用于调用刷新`accessToken`的接口时所需的`token` */ /** 用于调用刷新`accessToken`的接口时所需的`token` */
...@@ -79,7 +81,9 @@ export const getLogin = (data?: object) => { ...@@ -79,7 +81,9 @@ export const getLogin = (data?: object) => {
/** 刷新`token` */ /** 刷新`token` */
export const refreshTokenApi = (data?: object) => { export const refreshTokenApi = (data?: object) => {
return http.request<RefreshTokenResult>("post", "/refresh-token", { data }); return http.request<RefreshTokenResult>("post", "/api/auth/refresh-token", {
data
});
}; };
/** 账户设置-个人信息 */ /** 账户设置-个人信息 */
......
import { ref, onMounted } from "vue"; import { ref, onMounted } from "vue";
import { getCaptcha } from "@/api/systems";
import { useUserStoreHook } from "@/store/modules/user";
/** /**
* 绘制图形验证码 * 绘制图形验证码
* @param width - 图形宽度 * @param width - 图形宽度
...@@ -44,42 +45,50 @@ function randomColor(min: number, max: number) { ...@@ -44,42 +45,50 @@ function randomColor(min: number, max: number) {
function draw(dom: HTMLCanvasElement, width: number, height: number) { function draw(dom: HTMLCanvasElement, width: number, height: number) {
let imgCode = ""; let imgCode = "";
let captchaCode = "";
const NUMBER_STRING = "0123456789"; const now = new Date().getTime().toString();
getCaptcha(now).then(res => {
const ctx = dom.getContext("2d"); if (res.code === "0") {
if (!ctx) return imgCode; captchaCode = res.data;
useUserStoreHook().SET_VERIFYCODE(captchaCode);
ctx.fillStyle = randomColor(180, 230); useUserStoreHook().SET_CAPTCHATIME(now);
ctx.fillRect(0, 0, width, height); const ctx = dom.getContext("2d");
for (let i = 0; i < 4; i += 1) { if (!ctx) return imgCode;
const text = NUMBER_STRING[randomNum(0, NUMBER_STRING.length)]; ctx.fillStyle = randomColor(180, 230);
imgCode += text; ctx.fillRect(0, 0, width, height);
const fontSize = randomNum(18, 41); for (let i = 0; i < captchaCode.length; i += 1) {
const deg = randomNum(-30, 30); // const text = NUMBER_STRING[randomNum(0, NUMBER_STRING.length)];
ctx.font = `${fontSize}px Simhei`; const text = captchaCode[i];
ctx.textBaseline = "top"; imgCode += text;
ctx.fillStyle = randomColor(80, 150); const fontSize = randomNum(18, 41);
ctx.save(); const deg = randomNum(-30, 30);
ctx.translate(30 * i + 15, 15); ctx.font = `${fontSize}px Simhei`;
ctx.rotate((deg * Math.PI) / 180); ctx.textBaseline = "top";
ctx.fillText(text, -15 + 5, -15); ctx.fillStyle = randomColor(80, 150);
ctx.restore(); ctx.save();
} ctx.translate(30 * i + 15, 15);
for (let i = 0; i < 5; i += 1) { ctx.rotate((deg * Math.PI) / 180);
ctx.beginPath(); ctx.fillText(text, -15 + 5, -15);
ctx.moveTo(randomNum(0, width), randomNum(0, height)); ctx.restore();
ctx.lineTo(randomNum(0, width), randomNum(0, height)); }
ctx.strokeStyle = randomColor(180, 230); for (let i = 0; i < 5; i += 1) {
ctx.closePath(); ctx.beginPath();
ctx.stroke(); ctx.moveTo(randomNum(0, width), randomNum(0, height));
} ctx.lineTo(randomNum(0, width), randomNum(0, height));
for (let i = 0; i < 41; i += 1) { ctx.strokeStyle = randomColor(180, 230);
ctx.beginPath(); ctx.closePath();
ctx.arc(randomNum(0, width), randomNum(0, height), 1, 0, 2 * Math.PI); ctx.stroke();
ctx.closePath(); }
ctx.fillStyle = randomColor(150, 200); for (let i = 0; i < 41; i += 1) {
ctx.fill(); ctx.beginPath();
} ctx.arc(randomNum(0, width), randomNum(0, height), 1, 0, 2 * Math.PI);
return imgCode; ctx.closePath();
ctx.fillStyle = randomColor(150, 200);
ctx.fill();
}
return imgCode as string;
} else {
return "";
}
});
} }
...@@ -14,7 +14,13 @@ import { ...@@ -14,7 +14,13 @@ import {
refreshTokenApi refreshTokenApi
} from "@/api/user"; } from "@/api/user";
import { useMultiTagsStoreHook } from "./multiTags"; import { useMultiTagsStoreHook } from "./multiTags";
import { type DataInfo, setToken, removeToken, userKey } from "@/utils/auth"; import {
type DataInfo,
setToken,
removeToken,
userKey,
getToken
} from "@/utils/auth";
export const useUserStore = defineStore("pure-user", { export const useUserStore = defineStore("pure-user", {
state: (): userType => ({ state: (): userType => ({
...@@ -37,8 +43,10 @@ export const useUserStore = defineStore("pure-user", { ...@@ -37,8 +43,10 @@ export const useUserStore = defineStore("pure-user", {
isRemembered: false, isRemembered: false,
// 登录页的免登录存储几天,默认7天 // 登录页的免登录存储几天,默认7天
loginDay: 7, loginDay: 7,
token: localStorage.getItem("jwt") || "", token: localStorage.getItem("token") || "",
refreshToken: localStorage.getItem("refreshToken") || "" refreshToken: localStorage.getItem("refreshToken") || "",
// 拿到验证码的时间戳
captchaTime: ""
}), }),
actions: { actions: {
/** 存储头像 */ /** 存储头像 */
...@@ -77,6 +85,10 @@ export const useUserStore = defineStore("pure-user", { ...@@ -77,6 +85,10 @@ export const useUserStore = defineStore("pure-user", {
SET_LOGINDAY(value: number) { SET_LOGINDAY(value: number) {
this.loginDay = Number(value); this.loginDay = Number(value);
}, },
/** 设置登录时验证码时间戳 */
SET_CAPTCHATIME(value: string) {
this.captchaTime = value;
},
/** 登入 */ /** 登入 */
async loginByUsername(data) { async loginByUsername(data) {
return new Promise<UserResult>((resolve, reject) => { return new Promise<UserResult>((resolve, reject) => {
...@@ -106,7 +118,12 @@ export const useUserStore = defineStore("pure-user", { ...@@ -106,7 +118,12 @@ export const useUserStore = defineStore("pure-user", {
refreshTokenApi(data) refreshTokenApi(data)
.then(data => { .then(data => {
if (data) { if (data) {
if (data.data.token) {
data.data.accessToken = data.data.token;
}
setToken(data.data); setToken(data.data);
console.log("data", data.data);
console.log(getToken());
resolve(data); resolve(data);
} }
}) })
......
...@@ -49,4 +49,5 @@ export type userType = { ...@@ -49,4 +49,5 @@ export type userType = {
loginDay?: number; loginDay?: number;
token: string; token: string;
refreshToken: string; refreshToken: string;
captchaTime: string;
}; };
...@@ -19,6 +19,8 @@ export interface DataInfo<T> { ...@@ -19,6 +19,8 @@ export interface DataInfo<T> {
roles?: Array<string>; roles?: Array<string>;
/** 当前登录用户的按钮级别权限 */ /** 当前登录用户的按钮级别权限 */
permissions?: Array<string>; permissions?: Array<string>;
/** 用于调用刷新accessToken的接口时所需的token */
token?: string;
} }
export const userKey = "user-info"; export const userKey = "user-info";
...@@ -45,11 +47,11 @@ export function getToken(): DataInfo<number> { ...@@ -45,11 +47,11 @@ export function getToken(): DataInfo<number> {
* 将`accessToken`、`expires`、`refreshToken`这三条信息放在key值为authorized-token的cookie里(过期自动销毁) * 将`accessToken`、`expires`、`refreshToken`这三条信息放在key值为authorized-token的cookie里(过期自动销毁)
* 将`avatar`、`username`、`nickname`、`roles`、`permissions`、`refreshToken`、`expires`这七条信息放在key值为`user-info`的localStorage里(利用`multipleTabsKey`当浏览器完全关闭后自动销毁) * 将`avatar`、`username`、`nickname`、`roles`、`permissions`、`refreshToken`、`expires`这七条信息放在key值为`user-info`的localStorage里(利用`multipleTabsKey`当浏览器完全关闭后自动销毁)
*/ */
export function setToken(data: DataInfo<Date>) { export function setToken(data: DataInfo<number>) {
let expires = 0; let expires = 0;
const { accessToken, refreshToken } = data; const { accessToken, refreshToken } = data;
const { isRemembered, loginDay } = useUserStoreHook(); const { isRemembered, loginDay } = useUserStoreHook();
expires = new Date(data.expires).getTime(); // 如果后端直接设置时间戳,将此处代码改为expires = data.expires,然后把上面的DataInfo<Date>改成DataInfo<number>即可 expires = data.expires; // 如果后端直接设置时间戳,将此处代码改为expires = data.expires,然后把上面的DataInfo<Date>改成DataInfo<number>即可
const cookieString = JSON.stringify({ accessToken, expires, refreshToken }); const cookieString = JSON.stringify({ accessToken, expires, refreshToken });
expires > 0 expires > 0
......
...@@ -13,6 +13,7 @@ import { stringify } from "qs"; ...@@ -13,6 +13,7 @@ import { stringify } from "qs";
import NProgress from "../progress"; import NProgress from "../progress";
import { getToken, formatToken } from "@/utils/auth"; import { getToken, formatToken } from "@/utils/auth";
import { useUserStoreHook } from "@/store/modules/user"; import { useUserStoreHook } from "@/store/modules/user";
import packageJson from "../../../package.json";
// 相关配置请参考:www.axios-js.com/zh-cn/docs/#axios-request-config-1 // 相关配置请参考:www.axios-js.com/zh-cn/docs/#axios-request-config-1
const defaultConfig: AxiosRequestConfig = { const defaultConfig: AxiosRequestConfig = {
...@@ -21,7 +22,11 @@ const defaultConfig: AxiosRequestConfig = { ...@@ -21,7 +22,11 @@ const defaultConfig: AxiosRequestConfig = {
headers: { headers: {
Accept: "application/json, text/plain, */*", Accept: "application/json, text/plain, */*",
"Content-Type": "application/json", "Content-Type": "application/json",
"X-Requested-With": "XMLHttpRequest" "X-Requested-With": "XMLHttpRequest",
System: "PureAdmin",
Version: packageJson.version,
"Request-Id": "",
Device: "PC"
}, },
// 数组格式参数序列化(https://github.com/axios/axios/issues/5142) // 数组格式参数序列化(https://github.com/axios/axios/issues/5142)
paramsSerializer: { paramsSerializer: {
...@@ -52,7 +57,6 @@ class PureHttp { ...@@ -52,7 +57,6 @@ class PureHttp {
private static retryOriginalRequest(config: PureHttpRequestConfig) { private static retryOriginalRequest(config: PureHttpRequestConfig) {
return new Promise(resolve => { return new Promise(resolve => {
PureHttp.requests.push((token: string) => { PureHttp.requests.push((token: string) => {
debugger;
config.headers["Authorization"] = formatToken(token); config.headers["Authorization"] = formatToken(token);
resolve(config); resolve(config);
}); });
...@@ -80,8 +84,6 @@ class PureHttp { ...@@ -80,8 +84,6 @@ class PureHttp {
? config ? config
: new Promise(resolve => { : new Promise(resolve => {
const data = getToken(); const data = getToken();
console.log(4444, data);
if (data) { if (data) {
const now = new Date().getTime(); const now = new Date().getTime();
const expired = parseInt(data.expires) - now <= 0; const expired = parseInt(data.expires) - now <= 0;
...@@ -106,6 +108,9 @@ class PureHttp { ...@@ -106,6 +108,9 @@ class PureHttp {
config.headers["Authorization"] = formatToken( config.headers["Authorization"] = formatToken(
data.accessToken data.accessToken
); );
config.headers["Request-Id"] = crypto
.randomUUID()
.replaceAll("-", "");
resolve(config); resolve(config);
} }
} else { } else {
......
...@@ -33,6 +33,8 @@ import LoginRegist from "./components/LoginRegist.vue"; ...@@ -33,6 +33,8 @@ import LoginRegist from "./components/LoginRegist.vue";
import LoginUpdate from "./components/LoginUpdate.vue"; import LoginUpdate from "./components/LoginUpdate.vue";
// 引入二维码登录组件 // 引入二维码登录组件
import LoginQrCode from "./components/LoginQrCode.vue"; import LoginQrCode from "./components/LoginQrCode.vue";
// 引入加密相关的第三方库
import MD5 from "crypto-js/md5";
// 引入用户状态管理 hook // 引入用户状态管理 hook
import { useUserStoreHook } from "@/store/modules/user"; import { useUserStoreHook } from "@/store/modules/user";
// 引入路由初始化和获取顶部菜单的工具函数 // 引入路由初始化和获取顶部菜单的工具函数
...@@ -68,6 +70,7 @@ import Info from "~icons/ri/information-line"; ...@@ -68,6 +70,7 @@ import Info from "~icons/ri/information-line";
import Keyhole from "~icons/ri/shield-keyhole-line"; import Keyhole from "~icons/ri/shield-keyhole-line";
// 引入 token 操作工具函数 // 引入 token 操作工具函数
import { setToken, getToken } from "@/utils/auth"; import { setToken, getToken } from "@/utils/auth";
import { any } from "vue-types";
/** /**
* 定义组件选项 * 定义组件选项
...@@ -125,6 +128,7 @@ const ruleForm = reactive({ ...@@ -125,6 +128,7 @@ const ruleForm = reactive({
const onLogin = async (formEl: FormInstance | undefined) => { const onLogin = async (formEl: FormInstance | undefined) => {
if (!formEl) return; if (!formEl) return;
// 对表单进行验证 // 对表单进行验证
console.log("ruleForm", formEl);
await formEl.validate(valid => { await formEl.validate(valid => {
if (valid) { if (valid) {
// 验证通过,设置加载状态 // 验证通过,设置加载状态
...@@ -132,17 +136,19 @@ const onLogin = async (formEl: FormInstance | undefined) => { ...@@ -132,17 +136,19 @@ const onLogin = async (formEl: FormInstance | undefined) => {
useUserStoreHook() useUserStoreHook()
.loginByUsername({ .loginByUsername({
username: ruleForm.username, username: ruleForm.username,
password: ruleForm.password password: MD5(ruleForm.password).toString(),
timer: useUserStoreHook().captchaTime,
captcha: ruleForm.verifyCode
}) })
.then(res => { .then(res => {
if (res.status === 200 || res.success) { if (res.status === 200 || res.success) {
// 登录成功,设置 token // 登录成功,设置 token
setToken({ setToken({
accessToken: res.data.jwt, accessToken: res.data.token,
refreshToken: "", refreshToken: res.data.refreshToken,
expires: new Date(Date.now() + 9999999 * 1000) expires: res.data.expires
// expires: new Date(Date.now() + 9999999 * 1000)
}); });
console.log("getToken", getToken);
// 初始化路由 // 初始化路由
return initRouter().then(() => { return initRouter().then(() => {
disabled.value = true; disabled.value = true;
...@@ -310,7 +316,7 @@ watch(loginDay, value => { ...@@ -310,7 +316,7 @@ watch(loginDay, value => {
</Motion> </Motion>
<!-- 注释掉的验证码输入项 --> <!-- 注释掉的验证码输入项 -->
<!-- <Motion :delay="200"> <Motion :delay="200">
<el-form-item prop="verifyCode"> <el-form-item prop="verifyCode">
<el-input <el-input
v-model="ruleForm.verifyCode" v-model="ruleForm.verifyCode"
...@@ -323,7 +329,7 @@ watch(loginDay, value => { ...@@ -323,7 +329,7 @@ watch(loginDay, value => {
</template> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
</Motion> --> </Motion>
<!-- 记住登录和忘记密码区域 --> <!-- 记住登录和忘记密码区域 -->
<Motion :delay="250"> <Motion :delay="250">
......
...@@ -51,6 +51,11 @@ const loginRules = reactive<FormRules>({ ...@@ -51,6 +51,11 @@ const loginRules = reactive<FormRules>({
* @param callback - 验证回调函数,用于返回验证结果 * @param callback - 验证回调函数,用于返回验证结果
*/ */
validator: (rule, value, callback) => { validator: (rule, value, callback) => {
console.log("value", value);
console.log(
"useUserStoreHook().verifyCode",
useUserStoreHook().verifyCode
);
if (value === "") { if (value === "") {
// 验证码为空时,返回错误信息 // 验证码为空时,返回错误信息
callback(new Error(transformI18n($t("login.pureVerifyCodeReg")))); callback(new Error(transformI18n($t("login.pureVerifyCodeReg"))));
......
...@@ -32,9 +32,9 @@ export default ({ mode }: ConfigEnv): UserConfigExport => { ...@@ -32,9 +32,9 @@ export default ({ mode }: ConfigEnv): UserConfigExport => {
// 旭哥地址 // 旭哥地址
// target: "http://192.168.1.180:5001", // target: "http://192.168.1.180:5001",
// 服务器地址 // 服务器地址
target: "http://192.168.1.248:5001", // target: "http://192.168.1.248:5001",
// 熊熊哥地址 // 熊熊哥地址
// target: "http://192.168.1.194:5001", target: "http://192.168.0.57:5001",
changeOrigin: true, changeOrigin: true,
rewrite: path => path.replace(/^\/api/, "") rewrite: path => path.replace(/^\/api/, "")
} }
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论