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

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

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