feat:添加接口签名

This commit is contained in:
2026-04-08 11:26:40 +08:00
parent 8229c2cc67
commit 2d7ea7e5c8
10 changed files with 753 additions and 208 deletions

1
.gitignore vendored
View File

@@ -10,6 +10,7 @@ lerna-debug.log*
node_modules node_modules
.DS_Store .DS_Store
dist dist
xSynergy-manage
dist-ssr dist-ssr
coverage coverage
*.local *.local

View File

@@ -27,3 +27,24 @@ npm run dev
```sh ```sh
npm run build npm run build
``` ```
## 注意事项
1.在本地启动项目时要修改签名的host,在utils/tools.js
```
// 获取host从baseURL或当前域名 本地启动要求host为xsynergy.gxtech.ltd
// let host = 'xsynergy.gxtech.ltd';
let host = '';
if (config.baseURL) {
try {
const urlObj = new URL(config.baseURL);
host = urlObj.host;
} catch (e) {
host = window.location.host;
}
} else {
host = window.location.host;
}
```

BIN
dist.zip

Binary file not shown.

12
package-lock.json generated
View File

@@ -15,9 +15,11 @@
"@vueuse/core": "^9.5.0", "@vueuse/core": "^9.5.0",
"autoprefixer": "^10.4.21", "autoprefixer": "^10.4.21",
"axios": "^0.27.2", "axios": "^0.27.2",
"blakejs": "^1.2.1",
"code-inspector-plugin": "^0.20.12", "code-inspector-plugin": "^0.20.12",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"element-plus": "^2.2.27", "element-plus": "^2.2.27",
"hashids": "^1.2.2",
"js-cookie": "^3.0.1", "js-cookie": "^3.0.1",
"livekit-client": "^2.7.5", "livekit-client": "^2.7.5",
"mitt": "^3.0.0", "mitt": "^3.0.0",
@@ -2144,6 +2146,11 @@
"readable-stream": "^4.2.0" "readable-stream": "^4.2.0"
} }
}, },
"node_modules/blakejs": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz",
"integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ=="
},
"node_modules/bluebird": { "node_modules/bluebird": {
"version": "3.7.2", "version": "3.7.2",
"resolved": "https://registry.npmmirror.com/bluebird/-/bluebird-3.7.2.tgz", "resolved": "https://registry.npmmirror.com/bluebird/-/bluebird-3.7.2.tgz",
@@ -4563,6 +4570,11 @@
"minimalistic-assert": "^1.0.1" "minimalistic-assert": "^1.0.1"
} }
}, },
"node_modules/hashids": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/hashids/-/hashids-1.2.2.tgz",
"integrity": "sha512-dEHCG2LraR6PNvSGxosZHIRgxF5sNLOIBFEHbj8lfP9WWmu/PWPMzsip1drdVSOFi51N2pU7gZavrgn7sbGFuw=="
},
"node_modules/hasown": { "node_modules/hasown": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz",

View File

@@ -15,9 +15,11 @@
"@vueuse/core": "^9.5.0", "@vueuse/core": "^9.5.0",
"autoprefixer": "^10.4.21", "autoprefixer": "^10.4.21",
"axios": "^0.27.2", "axios": "^0.27.2",
"blakejs": "^1.2.1",
"code-inspector-plugin": "^0.20.12", "code-inspector-plugin": "^0.20.12",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"element-plus": "^2.2.27", "element-plus": "^2.2.27",
"hashids": "^1.2.2",
"js-cookie": "^3.0.1", "js-cookie": "^3.0.1",
"livekit-client": "^2.7.5", "livekit-client": "^2.7.5",
"mitt": "^3.0.0", "mitt": "^3.0.0",

View File

@@ -1,5 +1,4 @@
import request from '@/utils/request' import request from '@/utils/request'
// import request from '@/views/custom/Meter/public/request.js'
// 登录方法 // 登录方法
export function login(username, password) { export function login(username, password) {

View File

@@ -5,6 +5,7 @@ import {
ElMessage, ElMessage,
} from "element-plus"; } from "element-plus";
import { tansParams } from "@/utils/ruoyi"; import { tansParams } from "@/utils/ruoyi";
import { generateSign } from "@/utils/tools";
import cache from "@/plugins/cache"; import cache from "@/plugins/cache";
import { getToken, removeToken } from "@/utils/auth"; import { getToken, removeToken } from "@/utils/auth";
import router from '@/router'; import router from '@/router';
@@ -38,7 +39,7 @@ const service = axios.create({
// request拦截器 // request拦截器
service.interceptors.request.use( service.interceptors.request.use(
(config) => { async (config) => {
// 在拦截器内部安全地使用 store // 在拦截器内部安全地使用 store
let sudid = '' let sudid = ''
try { try {
@@ -66,7 +67,6 @@ service.interceptors.request.use(
} }
// get请求映射params参数 // get请求映射params参数
if (config.method === "get" && config.params) { if (config.method === "get" && config.params) {
let url = config.url + "?" + tansParams(config.params); let url = config.url + "?" + tansParams(config.params);
url = url.slice(0, -1); url = url.slice(0, -1);
config.params = {}; config.params = {};
@@ -118,6 +118,22 @@ service.interceptors.request.use(
} }
} }
} }
console.log(sudid,'sudid')
// 生成sign并添加到URL
if (sudid) {
try {
const now = Date.now();
const sign = await generateSign(config, sudid, now);
if (sign) {
// 将sign添加到URL
const separator = config.url.includes('?') ? '&' : '?';
config.url += `${separator}sign=${encodeURIComponent(sign)}`;
}
} catch (signError) {
console.error('添加sign失败:', signError);
}
}
return config; return config;
}, },
(error) => { (error) => {
@@ -125,8 +141,28 @@ service.interceptors.request.use(
} }
); );
let isShowingError = false;
const showError = (message) => {
if (isShowingError) return;
isShowingError = true;
ElMessage({
message,
type: 'error',
duration: 3000
});
setTimeout(() => {
isShowingError = false;
}, 2000);
};
service.interceptors.response.use( service.interceptors.response.use(
(response) => { (response) => {
try {
// console.log('原始响应:', response);
// 1. 检查响应是否存在 // 1. 检查响应是否存在
if (!response) { if (!response) {
ElMessage.error('无响应数据'); ElMessage.error('无响应数据');
@@ -145,6 +181,8 @@ service.interceptors.response.use(
return responseData; return responseData;
} }
// console.log('响应数据:', responseData);
// 4. 根据业务码处理不同情况 // 4. 根据业务码处理不同情况
switch (businessCode) { switch (businessCode) {
case 200: case 200:
@@ -162,35 +200,97 @@ service.interceptors.response.use(
// }); // });
// } // }
case 500: case 500:
const serverErrorMsg = responseData.meta?.message || '服务器内部错误'; const serverErrorMsg = responseData.meta?.message || responseData.meta?.msg || '服务器内部错误';
ElMessage({ message: serverErrorMsg, type: 'error' }); ElMessage({ message: serverErrorMsg, type: 'error' });
return Promise.reject({ code: 500, message: serverErrorMsg }); return Promise.reject({ code: 500, message: serverErrorMsg });
default: default:
const errorMsg = responseData.meta?.message || `业务错误 (${businessCode})`; const errorMsg = responseData.meta?.message || responseData.meta?.msg || `业务错误 (${businessCode})`;
ElNotification.error({ title: errorMsg }); ElNotification.error({ title: errorMsg });
return Promise.reject({ code: businessCode, message: errorMsg }); return Promise.reject({ code: businessCode, message: errorMsg });
} }
} catch (err) {
console.error('响应拦截器异常:', err);
showError('响应处理异常');
return Promise.reject({
code: -1,
message: '响应处理异常',
raw: err
});
}
}, },
// (error) => {
// let { message } = error;
// let code = error?.response?.status || -1;
// if (message == 'Network Error') {
// message = '后端接口连接异常';
// ElMessage({ message, type: 'error', duration: 5 * 1000 });
// } else if (message.includes('timeout')) {
// message = '系统接口请求超时';
// ElMessage({ message, type: 'error', duration: 5 * 1000 });
// } else if (message.includes('Request failed with status code')) {
// // message = '系统接口' + message.substr(message.length - 3) + '异常';
// }
// // 返回结构化错误
// return Promise.reject({
// code,
// message,
// raw: error // 保留原始 error
// });
// }
(error) => { (error) => {
let { message } = error; try {
let message = error?.message || '请求失败';
let code = error?.response?.status || -1; let code = error?.response?.status || -1;
if (message == 'Network Error') { if (message === 'Network Error') {
message = '后端接口连接异常'; message = '后端接口连接异常';
ElMessage({ message, type: 'error', duration: 5 * 1000 });
} else if (message.includes('timeout')) { } else if (message.includes('timeout')) {
message = '系统接口请求超时'; message = '系统接口请求超时';
ElMessage({ message, type: 'error', duration: 5 * 1000 });
} else if (message.includes('Request failed with status code')) {
// message = '系统接口' + message.substr(message.length - 3) + '异常';
} }
else if (code) {
const statusMap = {
400: '请求参数错误',
401: '登录已过期,请重新登录',
403: '没有权限访问',
404: '请求资源不存在',
408: '请求超时,请稍后重试',
409: '数据冲突,请刷新后重试',
422: '参数校验失败',
500: '服务器异常,请联系管理员',
501: '功能暂未实现',
502: '服务器内部错误,请联系管理员',
503: '服务繁忙,请稍后重试',
504: '服务器响应超时,请稍后重试'
};
message = message || statusMap[code];
// 401 特殊处理(不影响你原逻辑)
if (code === 401) {
return handleUnauthorized();
}
}
showError(message);
// 返回结构化错误
return Promise.reject({ return Promise.reject({
code, code,
message, message,
raw: error // 保留原始 error raw: error
}); });
} catch (err) {
console.error('错误拦截器异常:', err);
return Promise.reject({
code: -1,
message: '网络异常处理失败',
raw: err
});
}
} }
); );

View File

@@ -1,3 +1,7 @@
import Hashids from 'hashids';
import blake from 'blakejs';
import CryptoJS from 'crypto-js';
// 获取uuid // 获取uuid
export function generateUUID() { export function generateUUID() {
var d = new Date().getTime(); var d = new Date().getTime();
@@ -158,3 +162,429 @@ function isValidBox(x1, y1, x2, y2, imgWidth, imgHeight) {
x2 <= imgWidth && y2 <= imgHeight x2 <= imgWidth && y2 <= imgHeight
); );
} }
// 全局临时变量,用于存储最近一次请求的签名信息
let lastRequestKey = '';
let lastTimestamp = 0;
let lastUsedTimestamp = 0;
let collisionCount = 0;
/**
* 生成请求唯一标识key
* @param {number} timestamp - 时间戳
* @param {string} method - 请求方法
* @param {string} urlPath - API路径
* @returns {string} 唯一标识
*/
function getRequestKey(timestamp, method, urlPath) {
return `${timestamp}_${method}_${urlPath}`;
}
/**
* 获取去重后的时间戳
* 处理同一毫秒内相同接口的重复请求
* @param {number} originalTimestamp - 原始时间戳
* @param {string} method - 请求方法
* @param {string} urlPath - API路径
* @returns {number} 处理后的时间戳
*/
function getDeduplicatedTimestamp(originalTimestamp, method, urlPath) {
const currentKey = getRequestKey(originalTimestamp, method, urlPath);
// 如果当前请求与上一次请求的时间戳、方法、API路径完全相同
if (currentKey === lastRequestKey && originalTimestamp === lastTimestamp) {
// 碰撞次数增加
collisionCount++;
// 时间戳增加碰撞次数毫秒
const adjustedTimestamp = originalTimestamp + collisionCount;
console.warn(`检测到重复请求: ${method} ${urlPath} @ ${originalTimestamp}ms, 调整时间戳为: ${adjustedTimestamp}ms (碰撞次数: ${collisionCount})`);
return adjustedTimestamp;
} else {
// 不同请求,重置碰撞计数
collisionCount = 0;
// 更新最后一次请求的记录
lastRequestKey = currentKey;
lastTimestamp = originalTimestamp;
return originalTimestamp;
}
}
/**
* 生成sign签名
* @param {Object} config - axios请求配置
* @param {string} udid - 用户设备ID22位短udid
* @param {number} timestamp - 时间戳(毫秒) - 由调用方传入确保与生成salt时使用的时间一致避免因时间差导致的sign不匹配问题·
* @returns {Promise<string>} sign值
*/
export async function generateSign(config, udid, timestamp) {
try {
// 1. 准备模版字典
const reqMap = new Map();
// 获取请求方法
const method = config.method?.toUpperCase() || 'GET';
// 获取host从baseURL或当前域名 本地启动要求host为xsynergy.gxtech.ltd
let host = 'xsynergy.gxtech.ltd';
// let host = '';
// // console.log('请求配置:', config.baseURL)
// if (config.baseURL) {
// try {
// const urlObj = new URL(config.baseURL);
// // console.log('解析baseURL成功:', urlObj);
// host = urlObj.host;
// } catch (e) {
// // console.log('解析baseURL失败使用window.location.host:', window.location);
// host = window.location.host;
// }
// } else {
// host = window.location.host;
// }
// console.log('请求host:', host);
// 获取完整URL路径不含域名和查询参数
let urlPath = config.url || '';
// 移除baseURL部分
if (config.baseURL && urlPath.startsWith(config.baseURL)) {
urlPath = urlPath.substring(config.baseURL.length);
}
// 移除查询参数
const queryIndex = urlPath.indexOf('?');
if (queryIndex !== -1) {
urlPath = urlPath.substring(0, queryIndex);
}
// 获取user-agent和token
const userAgent = config.headers?.['X-User-Agent'];
// const userAgent = config.headers?.['X-User-Agent'] || navigator.userAgent;
const token = config.headers?.['Authorization'] || '';
// 填充基础模板 - agent字段包含userAgent和token
reqMap.set('host', [host]);
reqMap.set('method', [method]);
reqMap.set('agent', [userAgent, token]);
reqMap.set('uri', [urlPath]);
// 2. 处理URL查询参数不包括sign本身
let fullUrl = config.url || '';
if (config.baseURL && !fullUrl.startsWith('http')) {
fullUrl = config.baseURL + fullUrl;
}
try {
const urlObj = new URL(fullUrl, window.location.origin);
const searchParams = urlObj.searchParams;
// 遍历所有查询参数排除sign
for (const [key, value] of searchParams.entries()) {
if (key !== 'sign') {
// 处理数组参数key[]格式)
const normalizedKey = key.replace('[]', '');
if (reqMap.has(normalizedKey)) {
reqMap.get(normalizedKey).push(value);
} else {
reqMap.set(normalizedKey, [value]);
}
}
}
} catch (e) {
console.warn('解析URL参数失败:', e);
}
// 3. 处理GET请求的params参数
if (config.method?.toLowerCase() === 'get' && config.params) {
for (const [key, value] of Object.entries(config.params)) {
if (key !== 'sign') {
const strValue = String(value);
const normalizedKey = key.replace('[]', '');
if (reqMap.has(normalizedKey)) {
reqMap.get(normalizedKey).push(strValue);
} else {
reqMap.set(normalizedKey, [strValue]);
}
}
}
}
// 4. 处理请求体POST、PUT等
if (config.method?.toLowerCase() !== 'get' && config.data) {
const contentType = config.headers?.['Content-Type'] || '';
if (contentType.includes('application/json')) {
// JSON数据 - 后端用"json"作为key
const jsonStr = typeof config.data === 'object'
? JSON.stringify(config.data)
: String(config.data);
reqMap.set('json', [jsonStr]);
} else if (contentType.includes('application/x-www-form-urlencoded')) {
// form-urlencoded数据
let formData = {};
if (typeof config.data === 'object') {
formData = config.data;
} else if (typeof config.data === 'string') {
const params = new URLSearchParams(config.data);
formData = Object.fromEntries(params.entries());
}
// 添加每个表单字段到reqMap
for (const [key, value] of Object.entries(formData)) {
const strValue = String(value);
const normalizedKey = key.replace('[]', '');
if (reqMap.has(normalizedKey)) {
reqMap.get(normalizedKey).push(strValue);
} else {
reqMap.set(normalizedKey, [strValue]);
}
}
} else if (contentType.includes('multipart/form-data')) {
// multipart/form-data
if (config.data instanceof FormData) {
for (const [key, value] of config.data.entries()) {
if (!(value instanceof File || value instanceof Blob)) {
const strValue = String(value);
const normalizedKey = key.replace('[]', '');
if (reqMap.has(normalizedKey)) {
reqMap.get(normalizedKey).push(strValue);
} else {
reqMap.set(normalizedKey, [strValue]);
}
}
}
}
}
}
// 5. 生成salt - 完全匹配后端的genSalt函数
// 后端: GenTokenSalt(uid, udid + signSalt)
// signSalt固定为: publicSignSalt
const signSalt = 'publicSignSalt';
const salt = await generateSalt(udid, signSalt);
// 6. 参数排序并进行HMAC-MD5哈希
const sortedKeys = Array.from(reqMap.keys()).sort();
// console.log('排序后的参数键:', sortedKeys);
// 创建HMAC-MD5
const hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.MD5, salt);
// 按排序后的key拼接值
let contentStr = '';
for (const key of sortedKeys) {
const values = reqMap.get(key);
const joinedValue = values.join('');
contentStr += joinedValue;
hmac.update(joinedValue);
}
// 获取最终的hash字符串
const hashStr = hmac.finalize().toString(CryptoJS.enc.Hex).toUpperCase();
// 7. 分段拆散计算
const ints = new Array(6).fill(0);
// 拆封模版
const parts = [
[0, 7],
[7, 7],
[14, 4],
[18, 7],
[25, 7]
];
// 使用模版将hashStr拆为5个hex字串并转为整数再倒序排列
for (let i = 0; i < parts.length; i++) {
// console.log(`拆分hashStr - part ${i}:`, hashStr.substring(parts[i][0], parts[i][0] + parts[i][1]));
const [start, length] = parts[i];
const hexStr = hashStr.substring(start, start + length);
const intValue = parseInt(hexStr, 16);
// console.log(`转换为整数 - part ${i}:`, intValue);
ints[4 - i] = intValue;
}
// 8. 获取调用接口时间戳(毫秒)并进行去重处理
const now = timestamp || Date.now();
// 对时间戳进行去重处理(处理同一毫秒内相同接口的重复请求)
const adjustedTimestamp = getDeduplicatedTimestamp(now, method, urlPath);
const ts = adjustedTimestamp.toString();
// 将时间拆分为两部分
const t0 = parseInt(ts.substring(0, ts.length - 9), 10);
const t1 = parseInt(ts.substring(ts.length - 9), 10);
ints[2] += t0;
ints[5] = t1;
// 记录实际使用的时间戳,用于后续的重复检测
lastUsedTimestamp = adjustedTimestamp;
// console.log('时间戳:', now, '调整后:', adjustedTimestamp, '拆分:', { t0, t1 });
// 9. 使用hashids算法加密
const alphabet = "GxJRk37QAe51FCsPW92uEOyq4Bg6Sp8YzVTmnU0liwDdHXLajZrfNhobIcMvKt";
const hashids = new Hashids(salt, 0, alphabet);
// 将ints数组编码为字符串
const sign = hashids.encode(ints);
// 打印调试信息
// console.log('=== Sign生成调试信息 ===');
// console.log('udid:', udid);
// console.log('signSalt:', signSalt);
// console.log('生成的salt:', salt);
// console.log('参数映射:', Object.fromEntries(reqMap));
// console.log('排序后的keys:', sortedKeys);
// console.log('拼接字符串:', contentStr);
// console.log('HMAC-MD5哈希:', hashStr);
// console.log('分割后的ints:', ints);
// console.log('原始时间戳:', now, '调整后时间戳:', adjustedTimestamp, '拆分:', { t0, t1 });
// console.log('最终sign:', sign);
// console.log('========================');
return sign;
} catch (error) {
console.error('生成sign失败:', error);
return '';
}
}
/**
* 重置去重状态(可选,用于测试或特殊场景)
*/
export function resetSignDeduplication() {
lastRequestKey = '';
lastTimestamp = 0;
lastUsedTimestamp = 0;
collisionCount = 0;
console.log('Sign去重状态已重置');
}
/**
* 生成salt - 匹配后端的genSalt函数
* 后端逻辑: GenTokenSalt(uid, udid + signSalt)
*
* @param {string} shortUdid - 22位短udid
* @param {string} signSalt - 固定的signSalt值 'xspotter-s3'
* @returns {Promise<string>} salt值
*/
async function generateSalt(shortUdid, signSalt) {
try {
// 1. 将22位短udid解压为完整UUID
const fullUdid = await unpackUUID(shortUdid);
// 2. 解析UUID并提取每个部分的第一个字符
// UUID格式: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
const uuidParts = fullUdid.split('-');
if (uuidParts.length !== 5) {
throw new Error('Invalid UUID format');
}
// 提取每个部分的第一个字符组成hexStr
// 后端: for i, item := range strings.Split(udidStr, "-") { hexStr += string(item[i]) }
let hexStr = '';
for (let i = 0; i < uuidParts.length; i++) {
if (uuidParts[i].length > i) {
hexStr += uuidParts[i][i]; // 注意取第i个字符不是第一个
} else {
hexStr += uuidParts[i][0]; // 如果长度不够,取第一个
}
}
// 3. 将hexStr解析为整数16进制
const baseInt = parseInt(hexStr, 16);
// 4. 转换为8进制字符串
const base8Str = baseInt.toString(8);
// 5. secret = udid + signSalt
const secret = shortUdid + signSalt;
// 6. 返回 base8 + secret
const result = base8Str + secret;
return result;
} catch (error) {
console.error('生成salt失败:', error);
throw error;
}
}
/**
* 将22位短udid解压为完整UUID
* 匹配后端的unpackUUID函数
*/
async function unpackUUID(shortUdid) {
try {
// 后端逻辑: shortUdid + "==" 然后base64解码
const base64Str = shortUdid + '==';
// 将url安全的base64转换为标准base64
const standardBase64 = base64Str.replace(/-/g, '+').replace(/_/g, '/');
// base64解码
const binaryStr = atob(standardBase64);
// 转换为16进制格式
const hex = [];
for (let i = 0; i < binaryStr.length; i++) {
const hexByte = binaryStr.charCodeAt(i).toString(16).padStart(2, '0');
hex.push(hexByte);
}
// 格式化为UUID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
const hexStr = hex.join('');
const uuid = `${hexStr.substring(0, 8)}-${hexStr.substring(8, 12)}-${hexStr.substring(12, 16)}-${hexStr.substring(16, 20)}-${hexStr.substring(20, 32)}`;
return uuid;
} catch (error) {
console.error('解压UUID失败:', error);
// 如果已经是完整UUID格式直接返回
if (shortUdid.includes('-') && shortUdid.length === 36) {
return shortUdid;
}
throw error;
}
}
/**
* 将完整UUID压缩为22位短udid
* 匹配后端的packUDID函数
*/
export function packUUID(fullUuid) {
try {
// 移除UUID中的连字符
const hexStr = fullUuid.replace(/-/g, '');
// 将16进制字符串转换为字节数组
const bytes = [];
for (let i = 0; i < hexStr.length; i += 2) {
bytes.push(parseInt(hexStr.substring(i, i + 2), 16));
}
// 转换为二进制字符串
let binary = '';
for (let i = 0; i < bytes.length; i++) {
binary += String.fromCharCode(bytes[i]);
}
// base64编码并转换为url安全格式
const base64 = btoa(binary);
const base64url = base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
return base64url;
} catch (error) {
console.error('压缩UUID失败:', error);
return fullUuid;
}
}

View File

@@ -39,39 +39,20 @@
<h2 class="login-title">系统登录</h2> <h2 class="login-title">系统登录</h2>
<el-form <el-form ref="loginRef" class="login-form" :model="loginForm" :rules="loginRules">
ref="loginRef"
class="login-form"
:model="loginForm"
:rules="loginRules"
>
<el-form-item prop="username"> <el-form-item prop="username">
<el-input <el-input v-model="loginForm.username" placeholder="请输入账号" size="large" />
v-model="loginForm.username"
placeholder="请输入账号"
size="large"
/>
</el-form-item> </el-form-item>
<el-form-item prop="password"> <el-form-item prop="password">
<el-input <el-input v-model="loginForm.password" type="password" placeholder="请输入密码" size="large"
v-model="loginForm.password" @keyup.enter="handleLogin" />
type="password"
placeholder="请输入密码"
size="large"
@keyup.enter="handleLogin"
/>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button <el-button :loading="loading" class="login-button" type="primary" size="large"
:loading="loading" @click.prevent="handleLogin">
class="login-button"
type="primary"
size="large"
@click.prevent="handleLogin"
>
<span v-if="!loading"> </span> <span v-if="!loading"> </span>
<span v-else>登录中...</span> <span v-else>登录中...</span>
</el-button> </el-button>
@@ -129,6 +110,7 @@ function handleLogin() {
proxy.$refs.loginRef.validate((valid) => { proxy.$refs.loginRef.validate((valid) => {
if (valid) { if (valid) {
loading.value = true loading.value = true
meterStore.initUdid()
if (!localStorage?.getItem('UDID')) { if (!localStorage?.getItem('UDID')) {
ElMessage({ ElMessage({
message: '服务错误,请刷新页面', message: '服务错误,请刷新页面',
@@ -219,13 +201,12 @@ function requestNotificationPermission() {
onMounted(async () => { onMounted(async () => {
meterStore.initUdid() // meterStore.initUdid()
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.loginView { .loginView {
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
@@ -254,11 +235,9 @@ onMounted(async () => {
width: 600px; width: 600px;
height: 600px; height: 600px;
background: radial-gradient( background: radial-gradient(circle,
circle,
rgba(64, 158, 255, .25), rgba(64, 158, 255, .25),
transparent 70% transparent 70%);
);
filter: blur(80px); filter: blur(80px);
@@ -403,11 +382,9 @@ onMounted(async () => {
border-radius: 6px; border-radius: 6px;
background: linear-gradient( background: linear-gradient(135deg,
135deg,
#409EFF, #409EFF,
#66b1ff #66b1ff);
);
border: none; border: none;
} }
@@ -442,5 +419,4 @@ onMounted(async () => {
} }
} }
</style> </style>

View File

@@ -11,6 +11,10 @@ export default defineConfig(({ mode, command }) => {
return { return {
//生产环境使用相对路径,开发环境用 / //生产环境使用相对路径,开发环境用 /
base: command === 'build' ? './' : '/', base: command === 'build' ? './' : '/',
build: {
outDir: 'xSynergy-manage',
},
plugins: createVitePlugins(env, command === "build"), plugins: createVitePlugins(env, command === "build"),
server: { server: {
host: '0.0.0.0', // 关键配置,允许局域网访问 host: '0.0.0.0', // 关键配置,允许局域网访问