diff --git a/.env b/.env new file mode 100644 index 0000000..f77b830 --- /dev/null +++ b/.env @@ -0,0 +1,5 @@ +//统一配置 +VITE_APP_PORT = 9998 +VITE_APP_TITLE = '数通智联化工云平台' +VITE_APP_SYSTEM_KEY = 'hengxingaokeApp1' +VITE_APP_CSS_URL = '/public/' diff --git a/package-lock.json b/package-lock.json index 9296df7..bb02960 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,9 +20,11 @@ "@zxing/library": "^0.21.3", "appformlowcode": "file:", "axios": "^1.7.7", + "crypto-js": "^4.2.0", "dayjs": "^1.11.13", "element-plus": "^2.8.6", "font-awesome": "^4.7.0", + "gm-crypto": "^0.1.12", "html5-qrcode": "^2.3.8", "js-beautify": "^1.15.1", "nprogress": "^0.2.0", @@ -48,6 +50,7 @@ "weixin-js-sdk": "^1.6.5" }, "devDependencies": { + "@types/crypto-js": "^4.2.2", "@types/js-beautify": "^1.14.3", "@types/node": "^22.7.8", "@types/nprogress": "^0.2.3", @@ -1586,6 +1589,13 @@ "node": ">=10.13.0" } }, + "node_modules/@types/crypto-js": { + "version": "4.2.2", + "resolved": "https://registry.npmmirror.com/@types/crypto-js/-/crypto-js-4.2.2.tgz", + "integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -2509,8 +2519,7 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "optional": true + ] }, "node_modules/big.js": { "version": "5.2.2", @@ -2629,7 +2638,6 @@ "url": "https://feross.org/support" } ], - "optional": true, "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -3075,6 +3083,12 @@ "node": ">= 8" } }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, "node_modules/css-select": { "version": "4.3.0", "resolved": "https://registry.npmmirror.com/css-select/-/css-select-4.3.0.tgz", @@ -4455,6 +4469,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gm-crypto": { + "version": "0.1.12", + "resolved": "https://registry.npmmirror.com/gm-crypto/-/gm-crypto-0.1.12.tgz", + "integrity": "sha512-ercd9ionBqxR+/FCXICr0eo+jzC8BvSK0j9L7/eB0uwbyjgeMPTdBNrcQTqIuRXOtOAKSGsTNvtLYFnIxNEoFg==", + "license": "MIT", + "dependencies": { + "buffer": "^5.7.0", + "jsbn": "^1.1.0", + "to-arraybuffer": "^1.0.1" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", @@ -4857,8 +4882,7 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "optional": true + ] }, "node_modules/ignore": { "version": "5.3.2", @@ -5374,6 +5398,12 @@ "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-9.0.1.tgz", "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==" }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "license": "MIT" + }, "node_modules/jsesc": { "version": "3.0.2", "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.0.2.tgz", @@ -8983,6 +9013,12 @@ "resolved": "https://registry.npmmirror.com/tinymce/-/tinymce-7.7.1.tgz", "integrity": "sha512-rMetqSgZtYbj4YPOX+gYgmlhy/sIjVlI/qlrSOul/Mpn9e0aIIG/fR0qvQSVYvxFv6OzRTge++NQyTbzLJK1NA==" }, + "node_modules/to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA==", + "license": "MIT" + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz", diff --git a/package.json b/package.json index 032b162..a51409a 100644 --- a/package.json +++ b/package.json @@ -53,9 +53,11 @@ "@zxing/library": "^0.21.3", "appformlowcode": "file:", "axios": "^1.7.7", + "crypto-js": "^4.2.0", "dayjs": "^1.11.13", "element-plus": "^2.8.6", "font-awesome": "^4.7.0", + "gm-crypto": "^0.1.12", "html5-qrcode": "^2.3.8", "js-beautify": "^1.15.1", "nprogress": "^0.2.0", @@ -81,6 +83,7 @@ "weixin-js-sdk": "^1.6.5" }, "devDependencies": { + "@types/crypto-js": "^4.2.2", "@types/js-beautify": "^1.14.3", "@types/node": "^22.7.8", "@types/nprogress": "^0.2.3", diff --git a/src/utils/axios/index.ts b/src/utils/axios/index.ts index f6c37fa..df63fa0 100644 --- a/src/utils/axios/index.ts +++ b/src/utils/axios/index.ts @@ -1,5 +1,7 @@ import axios, { InternalAxiosRequestConfig, AxiosResponse } from "axios"; import { userStror } from "@/utils/pinia/stores/modules/userOrders"; +import { sm4DecryptMethod, sm4EncryptMethod } from "../encryptionAndDecryption/sm4Utils"; +import { generateRandomString } from "../encryptionAndDecryption/randNumber"; // 创建 axios 实例 @@ -24,6 +26,22 @@ service.interceptors.request.use((config: InternalAxiosRequestConfig) => { if (userPinia.userToken) { config.headers["user-token"] = userPinia.userToken; } + + console.error('<---------------请求拦截---------->') + console.error('请求拦截----config------>', config.url) + console.error('请求拦截----data------>', config.data) + console.error('<---------------请求拦截---------->') + // if (config.headers['content-type'] === 'application/json') { + let { data, headers } = config + //获取16位随机数 + let randomString = generateRandomString(16) + config.headers['Auth-key'] = randomString + if (data) { + // 加密请求数据 + config.data = { + data: sm4EncryptMethod(JSON.stringify(data), randomString) + } + } return config; }, (error: any) => { // 对请求错误做些什么 @@ -35,6 +53,16 @@ service.interceptors.request.use((config: InternalAxiosRequestConfig) => { // 添加响应拦截器 service.interceptors.response.use((response: AxiosResponse) => { // 2xx 范围内的状态码都会触发该函数。 + let { data, headers } = response + let authKey = headers['auth-key'] + // console.log('行营结果----authKey------>', authKey) + // 解密响应数据 + if (authKey) { + let jsonData = sm4DecryptMethod(data.data, authKey) + response.data.data = JSON.parse(jsonData) + } + console.error('行营结果----解密结构------>', response.config.url) + console.error('行营结果----解密结构------>', response.data) // 对响应数据做点什么 const { code, msg } = response.data; //如果是以下状态直接放行 diff --git a/src/utils/axiosRequest/request.ts b/src/utils/axiosRequest/request.ts new file mode 100644 index 0000000..8933393 --- /dev/null +++ b/src/utils/axiosRequest/request.ts @@ -0,0 +1,102 @@ +/** +@ 作者: 秦东 +@ 时间: 2025-12-09 10:57:37 +@ 功能: 封装axios请求 +*/ +import axios, { AxiosError, type InternalAxiosRequestConfig } from 'axios' +import { ElMessage, ElMessageBox } from 'element-plus' +import { generateRandomString } from '@/utils/encryptionAndDecryption/randNumber' +import { sm4DecryptMethod, sm4EncryptMethod } from '@/utils/encryptionAndDecryption/sm4Utils' +import { useUserStore } from '@/stores/user' +/** +@ 作者: 秦东 +@ 时间: 2025-12-09 10:58:34 +@ 功能: 创建axios实例 +*/ +const service = axios.create({ + baseURL: import.meta.env.VITE_APP_BASE_API, + timeout: 5000 +}) + +/** +@ 作者: 秦东 +@ 时间: 2025-12-09 11:00:05 +@ 功能: 请求拦截 +*/ +service.interceptors.request.use( + (config: InternalAxiosRequestConfig) => { + const userStore = useUserStore() + // if (config.headers['content-type'] === 'application/json') { + let { data, headers } = config + console.log('请求拦截----data---1--->', data) + if (userStore.authToken) { + config.headers['Auth-token'] = userStore.authToken + } + //获取16位随机数 + let randomString = generateRandomString(16) + config.headers['Auth-key'] = randomString + if (data) { + // 加密请求数据 + config.data = { + encryptedFile: sm4EncryptMethod(JSON.stringify(data), randomString) + } + } + console.log('请求拦截---------->', randomString) + console.log('请求拦截----headers------>', headers) + console.log('请求拦截----data------>', config.data) + console.log('请求拦截----config------>', config) + // } + + // console.log('请求拦截----content-type------>', config.headers['Content-Type']) + // console.log('请求拦截----config------>', config.headers) + return config + }, + (error: AxiosError) => { + return Promise.reject(error) + } +) +/** +@ 作者: 秦东 +@ 时间: 2025-12-09 21:00:20 +@ 备注: 响应拦截 +*/ +service.interceptors.response.use( + response => { + console.log('行营结果---------->', response) + let { data, headers } = response + // console.log('行营结果----data------>', data.data) + let authKey = headers['auth-key'] + console.log('行营结果----authKey------>', authKey) + // 解密响应数据 + if (authKey) { + let jsonData = sm4DecryptMethod(data.data, authKey) + response.data.data = JSON.parse(jsonData) + } + console.log('行营结果----解密结构------>', headers['auth-key'], response) + return response.data + }, + (error: AxiosError) => { + if (error.response) { + const status = error.response.status + switch (status) { + case 401: + // 处理401错误,例如跳转到登录页 + break + case 403: + // 处理403错误,例如提示无权限 + break + case 404: + // 处理404错误,例如提示资源不存在 + break + case 500: + // 处理500错误,例如提示服务器错误 + break + default: + // 处理其他错误 + break + } + } + return Promise.reject(error) + } +) +export default service diff --git a/src/utils/encryptionAndDecryption/randNumber.ts b/src/utils/encryptionAndDecryption/randNumber.ts new file mode 100644 index 0000000..d1c74a4 --- /dev/null +++ b/src/utils/encryptionAndDecryption/randNumber.ts @@ -0,0 +1,63 @@ +/** +@ 作者: 秦东 +@ 时间: 2025-12-07 11:07:32 +@ 备注: 创建任意位数的随机字符串 +*/ +const generateRandomString = (length: number = 16): string => { + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' + const array = new Uint8Array(length) + crypto.getRandomValues(array) + return Array.from(array, byte => characters[byte % characters.length]).join('') + // let randomString = ''; + // for (let i = 0; i < length; i++) { + // const randomIndex = Math.floor(Math.random() * characters.length); + // randomString += characters.charAt(randomIndex); + // } + // return randomString; +} +/** +@ 作者: 秦东 +@ 时间: 2025-12-08 16:15:12 +@ 功能: 获取UUID +*/ +const uuid = () => { + const s: string[] = [] + const hexDigits = '123456789abcdef' + for (let i = 0; i < 36; i++) { + s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1) + } + s[14] = '4' // bits 12-15 of the time_hi_and_version field to 0010 + s[19] = hexDigits.substr((parseInt(s[19]!, 16) & 0x3) | 0x8, 1) // bits 6-7 of the clock_seq_hi_and_reserved to 01 + s[8] = s[13] = s[18] = s[23] = '-' + const uuid = s.join('') + return uuid +} +/** +@ 作者: 秦东 +@ 时间: 2025-12-08 16:15:48 +@ 功能: 获取简单随机数 +*/ +const randomString = (length: number) => { + var str = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' + var result = '' + for (var i = length; i > 0; --i) { + result += str[Math.floor(Math.random() * str.length)] + } + return result +} +/** +@ 作者: 秦东 +@ 时间: 2025-12-08 16:16:12 +@ 功能: 中间画线驼峰 +*/ +const toHump = (str: string) => { + if (!str) return str + return str + .replace(/\-(\w)/g, function (all: string, letter: string) { + return letter.toUpperCase() + }) + .replace(/(\s|^)[a-z]/g, function (char: string) { + return char.toUpperCase() + }) +} +export { generateRandomString, uuid, randomString, toHump } diff --git a/src/utils/encryptionAndDecryption/shiliu.ts b/src/utils/encryptionAndDecryption/shiliu.ts new file mode 100644 index 0000000..811ac2c --- /dev/null +++ b/src/utils/encryptionAndDecryption/shiliu.ts @@ -0,0 +1,7 @@ +import * as CryptoJS from 'crypto-js' + +const strToHexSub = (str: string): string => { + return CryptoJS.enc.Utf8.parse(str).toString(CryptoJS.enc.Hex) +} + +export { strToHexSub } diff --git a/src/utils/encryptionAndDecryption/sm4Utils.ts b/src/utils/encryptionAndDecryption/sm4Utils.ts new file mode 100644 index 0000000..4a764e6 --- /dev/null +++ b/src/utils/encryptionAndDecryption/sm4Utils.ts @@ -0,0 +1,74 @@ +import { SM4 as sm4 } from 'gm-crypto' +import * as CryptoJS from 'crypto-js' +/** +@ 作者: 秦东 +@ 时间: 2025-12-07 10:26:51 +@ 备注: 将字符串转换成16进制 +*/ +const strToHexMethod = (str: string): string => { + return CryptoJS.enc.Utf8.parse(str).toString(CryptoJS.enc.Hex) +} +/** +@ 作者: 秦东 +@ 时间: 2025-12-07 10:29:48 +@ 备注: 判断是不是12位16进制参数 +*/ +const isValidHex32 = (str: string): boolean => { + return /^[0-9a-fA-F]{32}$/.test(str) +} +/** +@ 作者: 秦东 +@ 时间: 2025-12-07 10:28:33 +@ 备注: 初始化SM4密钥参数 +*/ +const appSystemKey = strToHexMethod(import.meta.env.VITE_APP_SYSTEM_KEY) +const sm4TokenKey = strToHexMethod(import.meta.env.VITE_APP_SM4_APP_KEY) + +/** +@ 作者: 秦东 +@ 时间: 2025-12-07 10:30:40 +@ 备注: SM4加密方法 +*/ +const sm4EncryptMethod = (data: string, customKey: string): string => { + let ivSetup = sm4TokenKey + if (customKey) { + ivSetup = strToHexMethod(customKey) + if (!isValidHex32(ivSetup)) { + ivSetup = sm4TokenKey + } + } + return sm4.encrypt(data, appSystemKey, { + iv: ivSetup, + mode: sm4.constants.CBC, + inputEncoding: 'utf8', + outputEncoding: 'hex', + padding: 1 + } as never) +} +/** +@ 作者: 秦东 +@ 时间: 2025-12-07 10:31:22 +@ 备注: SM4解密方法 +*/ +const sm4DecryptMethod = (data: string, customKey: string): string => { + let ivSetup = sm4TokenKey + if (customKey) { + ivSetup = strToHexMethod(customKey) + if (!isValidHex32(ivSetup)) { + ivSetup = sm4TokenKey + } + } + // console.log('SM4解密方法----解密结构---data--->', data) + console.log('SM4解密方法----解密结构----customKey-->', customKey) + console.log('SM4解密方法----解密结构--ivSetup---->', ivSetup) + console.log('SM4解密方法----解密结构---sm4TokenKey--->', sm4TokenKey) + console.log('SM4解密方法----解密结构---appSystemKey--->', appSystemKey) + return sm4.decrypt(data, appSystemKey, { + iv: ivSetup, + mode: sm4.constants.CBC, + inputEncoding: 'hex', + outputEncoding: 'utf8', + padding: 1 + } as never) +} +export { sm4EncryptMethod, sm4DecryptMethod } diff --git a/src/utils/encryptionAndDecryption/tongyong.ts b/src/utils/encryptionAndDecryption/tongyong.ts new file mode 100644 index 0000000..184ed87 --- /dev/null +++ b/src/utils/encryptionAndDecryption/tongyong.ts @@ -0,0 +1,347 @@ +/** + * 数字 + */ +const REG_NUMBER: string = '.*\\d+.*' +/** + * 小写字母 + */ +const REG_UPPERCASE: string = '.*[A-Z]+.*' +/** + * 大写字母 + */ +const REG_LOWERCASE: string = '.*[a-z]+.*' +/** + * 特殊符号(~!@#$%^&*()_+|<>,.?/:;'[]{}\) + */ +const REG_SYMBOL: string = '.*[~!@#$%^&*()_+|<>,.?/:;\'\\[\\]{}]+".*' +/** + * 键盘字符表(小写) + * 非shift键盘字符表 + */ +const CHAR_TABLE1: string[][] = [ + ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\0'], + ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\\'], + ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', "'", '\0', '\0'], + ['z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', '\0', '\0', '\0'] +] +/** + * shift键盘的字符表 + */ +const CHAR_TABLE2: string[][] = [ + ['!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '\0'], + ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '{', '}', '|'], + ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ':', '"', '\0', '\0'], + ['z', 'x', 'c', 'v', 'b', 'n', 'm', '<', '>', '?', '\0', '\0', '\0'] +] + +/** + * 校验密码是否符合条件 + * @param password 密码 + * @param username 用户名 + */ +export const checkPasswordRule = (password: string, username: string) => { + if (password === '' || password.length < 8 || password.length > 32) { + // console.log("长度小于8,或大于32"); + return '密码长度应大于8小于32' + } + if (password.indexOf(username) !== -1) { + // console.log("包含用户名"); + return '请勿包含用户名' + } + if (isContinuousChar(password)) { + // console.log("包含3个及以上相同或字典连续字符"); + return '请勿包含3个及以上相同或连续的字符' + } + if (isKeyBoardContinuousChar(password)) { + // console.log("包含3个及以上键盘连续字符"); + return '请勿包含3个及以上键盘连续字符' + } + let i: number = 0 + if (password.match(REG_NUMBER)) i++ + if (password.match(REG_LOWERCASE)) i++ + if (password.match(REG_UPPERCASE)) i++ + if (password.match(REG_SYMBOL)) i++ + if (i < 2) { + // console.log(("数字、小写字母、大写字母、特殊字符,至少包含两种")); + return '数字、小写字母、大写字母、特殊字符,至少包含两种' + } + // console.log(i); + return '校验通过' +} + +/** + * 是否包含3个及以上相同或字典连续字符 + */ +const isContinuousChar = (password: string) => { + const chars: string[] = password.split('') + + // 遍历所有可能的三个连续字符组合 + for (let i = 0; i < chars.length - 2; i++) { + // 添加类型断言,确保字符不是undefined + const char1 = chars[i] as string + const char2 = chars[i + 1] as string + const char3 = chars[i + 2] as string + + // 获取字符的ASCII码 + const n1 = char1.charCodeAt(0) + const n2 = char2.charCodeAt(0) + const n3 = char3.charCodeAt(0) + + // 判断重复字符 + if (n1 === n2 && n1 === n3) { + return true + } + + // 判断连续字符: 正序(如abc)和倒序(如cba) + if ((n1 + 1 === n2 && n2 + 1 === n3) || (n1 - 1 === n2 && n2 - 1 === n3)) { + return true + } + } + + return false +} +/** + * 是否包含3个及以上键盘连续字符 + * @param password 待匹配的字符串 + */ +const isKeyBoardContinuousChar = (password: string) => { + if (password === '') { + return false + } + //考虑大小写,都转换成小写字母 + const lpStrChars: string[] = password.toLowerCase().split('') + // 获取字符串长度 + const nStrLen: number = lpStrChars.length + // 定义位置数组:row - 行,col - column 列,初始化为-1 + const pRowCharPos: number[] = new Array(nStrLen).fill(-1) + const pColCharPos: number[] = new Array(nStrLen).fill(-1) + + for (let i = 0; i < nStrLen; i++) { + // 添加类型断言,确保字符不是undefined + const chLower: string = lpStrChars[i] as string + + // 检索在表1中的位置,构建位置数组 + for (let nRowTable1Idx = 0; nRowTable1Idx < 4; nRowTable1Idx++) { + for (let nColTable1Idx = 0; nColTable1Idx < 13; nColTable1Idx++) { + // 获取当前字符,跳过\0字符,使用可选链和空值合并确保类型安全 + const tableChar = CHAR_TABLE1[nRowTable1Idx]?.[nColTable1Idx] ?? '' + if (tableChar && tableChar !== '\0' && chLower === tableChar) { + pRowCharPos[i] = nRowTable1Idx + pColCharPos[i] = nColTable1Idx + break + } + } + } + + // 在表1中没找到,到表二中去找,找到则continue + // 添加非空检查 + const currentColPos = pColCharPos[i] + if (currentColPos !== undefined && currentColPos >= 0) { + continue + } + + // 检索在表2中的位置,构建位置数组 + for (let nRowTable2Idx = 0; nRowTable2Idx < 4; nRowTable2Idx++) { + for (let nColTable2Idx = 0; nColTable2Idx < 13; nColTable2Idx++) { + // 获取当前字符,跳过\0字符,使用可选链和空值合并确保类型安全 + const tableChar = CHAR_TABLE2[nRowTable2Idx]?.[nColTable2Idx] ?? '' + if (tableChar && tableChar !== '\0' && chLower === tableChar) { + pRowCharPos[i] = nRowTable2Idx + pColCharPos[i] = nColTable2Idx + break + } + } + } + } + + // 匹配坐标连线 + for (let j = 1; j <= nStrLen - 2; j++) { + // 获取当前三个字符的位置,添加类型断言 + const row1 = pRowCharPos[j - 1] as number + const row2 = pRowCharPos[j] as number + const row3 = pRowCharPos[j + 1] as number + const col1 = pColCharPos[j - 1] as number + const col2 = pColCharPos[j] as number + const col3 = pColCharPos[j + 1] as number + + //同一行 + if (row1 === row2 && row2 === row3) { + // 键盘行正向连续(asd)或者键盘行反向连续(dsa) + if ( + (col1 + 1 === col2 && col2 + 1 === col3) || + (col3 + 1 === col2 && col2 + 1 === col1) + ) { + return true + } + } + + //同一列 + if (col1 === col2 && col2 === col3) { + //键盘列连续(qaz)或者键盘列反向连续(zaq) + if ( + (row1 + 1 === row2 && row2 + 1 === row3) || + (row1 - 1 === row2 && row2 - 1 === row3) + ) { + return true + } + } + } + + return false +} + +/** + * 密码强度校验 + */ +/** + * 长度 + * @param str + */ +const length = (str: string) => { + if (str.length < 5) { + return 5 + } else if (str.length < 8) { + return 10 + } else { + return 25 + } +} +/** + * 字母 + * @param str + */ +const letters = (str: string) => { + let count1 = 0, + count2 = 0 + for (let i = 0; i < str.length; i++) { + if (str.charAt(i) >= 'a' && str.charAt(i) <= 'z') { + count1++ + } + if (str.charAt(i) >= 'A' && str.charAt(i) <= 'Z') { + count2++ + } + } + if (count1 === 0 && count2 === 0) { + return 0 + } + if (count1 !== 0 && count2 !== 0) { + return 20 + } + return 10 +} + +/** + * 数字 + * @param str + */ +const numbers = (str: string) => { + let count = 0 + for (let i = 0; i < str.length; i++) { + if (str.charAt(i) >= '0' && str.charAt(i) <= '9') { + count++ + } + } + if (count === 0) { + return 0 + } + if (count === 1) { + return 10 + } + return 20 +} +/** + * 符号 + * @param str + */ +const symbols = (str: string) => { + let count = 0 + for (let i = 0; i < str.length; i++) { + if ( + (str.charCodeAt(i) >= 0x21 && str.charCodeAt(i) <= 0x2f) || + (str.charCodeAt(i) >= 0x3a && str.charCodeAt(i) <= 0x40) || + (str.charCodeAt(i) >= 0x5b && str.charCodeAt(i) <= 0x60) || + (str.charCodeAt(i) >= 0x7b && str.charCodeAt(i) <= 0x7e) + ) { + count++ + } + } + if (count === 0) { + return 0 + } + if (count === 1) { + return 10 + } + return 25 +} +/** + * 得分机制 + * @param str + */ +const rewards = (str: string) => { + let letter = letters(str) //字母 + let number = numbers(str) //数字 + let symbol = symbols(str) //符号 + if (letter > 0 && number > 0 && symbol === 0) { + //字母和数字 + return 2 + } + if (letter === 10 && number > 0 && symbol > 0) { + //字母、数字和符号 + return 3 + } + if (letter === 20 && number > 0 && symbol > 0) { + //大小写字母、数字和符号 + return 5 + } + return 0 +} +/** + * 最终评分 + * @param str + */ +export const level = (str: string) => { + let lengths = length(str) //长度 + let letter = letters(str) //字母 + let number = numbers(str) //数字 + let symbol = symbols(str) //符号 + let reward = rewards(str) //奖励 + let sum = lengths + letter + number + symbol + reward + console.log(sum) + if (sum >= 80) { + return '非常强' //非常安全 + } else if (sum >= 60) { + return '强' //非常强 + } else if (sum >= 40) { + return '一般' //一般 + } else if (sum >= 25) { + return '弱' //弱 + } else { + return '非常弱' //非常弱 + } +} + +export const checkPwdRule = (password: string) => { + if (password === '' || password.length < 8 || password.length > 32) { + // console.log("长度小于8,或大于32"); + return '密码长度应大于8小于32' + } + if (isContinuousChar(password)) { + // console.log("包含3个及以上相同或字典连续字符"); + return '请勿包含3个及以上相同或连续的字符' + } + if (isKeyBoardContinuousChar(password)) { + // console.log("包含3个及以上键盘连续字符"); + return '请勿包含3个及以上键盘连续字符' + } + let i: number = 0 + if (password.match(REG_NUMBER)) i++ + if (password.match(REG_LOWERCASE)) i++ + if (password.match(REG_UPPERCASE)) i++ + if (password.match(REG_SYMBOL)) i++ + if (i < 2) { + // console.log(("数字、小写字母、大写字母、特殊字符,至少包含两种")); + return '数字、小写字母、大写字母、特殊字符,至少包含两种' + } + // console.log(i); + return '校验通过' +} diff --git a/src/utils/loadSvgFile.ts b/src/utils/loadSvgFile.ts new file mode 100644 index 0000000..83f4e46 --- /dev/null +++ b/src/utils/loadSvgFile.ts @@ -0,0 +1,14 @@ +/** +@ 作者: 秦东 +@ 时间: 2026-01-09 15:57:48 +@ 功能: 加载svg文件 +*/ +export const loadSvgFiles = (): string[] => { + const icons = import.meta.glob('../assets/icons/*.svg') + const svgIcons: string[] = [] + for (const icon in icons) { + const iconName = icon.split('assets/icons/')[1].split('.svg')[0] + svgIcons.push(iconName) + } + return svgIcons +} diff --git a/src/utils/validate.ts b/src/utils/validate.ts new file mode 100644 index 0000000..9277d7e --- /dev/null +++ b/src/utils/validate.ts @@ -0,0 +1,32 @@ +/** +@ 作者: 秦东 +@ 时间: 2025-12-29 16:00:55 +@ 功能: 路径匹配器 + * @param {string} pattern + * @param {string} path + * @returns {Boolean} +*/ +export const isPathMatch = (pattern: string, path: string): boolean => { + const regexPattern = pattern + .replace(/\//g, '\\/') + .replace(/\*\*/g, '.*') + .replace(/\*/g, '[^\\/]*') + const regex = new RegExp(`^${regexPattern}$`) + return regex.test(path) +} +/** +@ 作者: 秦东 +@ 时间: 2025-12-29 16:02:21 +@ 功能: 判断字符串是否为空 +*/ +export const isEmptyString = (str: string): boolean => { + return str.trim().length === 0 +} +/** +@ 作者: 秦东 +@ 时间: 2025-12-29 16:03:14 +@ 功能: 判断url是否是http或https +*/ +export const isHttpOrHttpsUrl = (url: string): boolean => { + return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1 +}