提交新的移动端框架

This commit is contained in:
zlwu committed 2025-08-07 13:19:12 +08:00
1 parent 134f711349
commit 9bda3717fe
75 files changed
+5283 -9011

No files matched your search

+72
View File
@@ -0,0 +1,72 @@
// 移动端配置管理 - 统一管理密钥和请求配置
export const ENCRYPT_CONFIG = {
// RSA公钥 - 与后端保持一致
RSA_PUBLIC_KEY:
'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==',
// RSA私钥 - 与web端配置文档保持一致
RSA_PRIVATE_KEY:
'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE=',
// 客户端ID
CLIENT_ID: 'e5cd7e4891bf95d1d19206ce24a7b32e',
// 租户ID
TENANT_ID: '000000',
// 加密请求头
ENCRYPT_HEADER: 'encrypt-key',
// 是否启用加密
ENCRYPT_ENABLED: true,
};
// API请求配置
export const REQUEST_CONFIG = {
// 基础URL配置
baseURL: process.env.NODE_ENV === 'development' ? '/api' : 'https://your-api-domain.com',
// 请求超时时间
timeout: 300000, // 5分钟,适合大数据量导出
// 客户端ID
clientId: 'e5cd7e4891bf95d1d19206ce24a7b32e',
// 默认referer
referer: 'http://localhost/login?redirect=/index',
// 是否启用加密
encryptEnabled: true,
// 防重复提交间隔时间(ms)
repeatSubmitInterval: 500,
};
/**
* 更新加密配置
* @param {Object} config 新的配置对象
*/
export const updateEncryptConfig = (config) => {
Object.assign(ENCRYPT_CONFIG, config);
console.log('🔧 加密配置已更新:', ENCRYPT_CONFIG);
};
/**
* 更新请求配置
* @param {Object} config 新的配置对象
*/
export const updateRequestConfig = (config) => {
Object.assign(REQUEST_CONFIG, config);
console.log('🔧 请求配置已更新:', REQUEST_CONFIG);
};
/**
* 获取当前配置摘要
* @returns {Object} 配置摘要
*/
export const getConfigSummary = () => {
return {
hasPublicKey: !!ENCRYPT_CONFIG.RSA_PUBLIC_KEY,
hasPrivateKey: !!ENCRYPT_CONFIG.RSA_PRIVATE_KEY,
publicKeyLength: ENCRYPT_CONFIG.RSA_PUBLIC_KEY ? ENCRYPT_CONFIG.RSA_PUBLIC_KEY.length : 0,
privateKeyLength: ENCRYPT_CONFIG.RSA_PRIVATE_KEY
? ENCRYPT_CONFIG.RSA_PRIVATE_KEY.length
: 0,
clientId: ENCRYPT_CONFIG.CLIENT_ID,
tenantId: ENCRYPT_CONFIG.TENANT_ID,
encryptHeader: ENCRYPT_CONFIG.ENCRYPT_HEADER,
encryptEnabled: ENCRYPT_CONFIG.ENCRYPT_ENABLED,
baseURL: REQUEST_CONFIG.baseURL,
timeout: REQUEST_CONFIG.timeout,
};
};
+265
View File
@@ -0,0 +1,265 @@
// 移动端加密解密工具 - 与mobile工程保持一致
import CryptoJS from 'crypto-js';
import JSEncrypt from 'jsencrypt';
import { ENCRYPT_CONFIG, updateEncryptConfig } from './config.js';
/**
* 随机生成32位的字符串
* @returns {string}
*/
const generateRandomString = () => {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let result = '';
const charactersLength = characters.length;
for (let i = 0; i < 32; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
};
/**
* 随机生成AES密钥
* @returns {CryptoJS.lib.WordArray}
*/
export const generateAesKey = () => {
return CryptoJS.enc.Utf8.parse(generateRandomString());
};
/**
* 加密base64
* @param {CryptoJS.lib.WordArray} str
* @returns {string}
*/
export const encryptBase64 = (str) => {
return CryptoJS.enc.Base64.stringify(str);
};
/**
* 解密base64
* @param {string} str
* @returns {CryptoJS.lib.WordArray}
*/
export const decryptBase64 = (str) => {
return CryptoJS.enc.Base64.parse(str);
};
/**
* 使用密钥对数据进行AES加密
* @param {string} message 待加密的数据
* @param {CryptoJS.lib.WordArray} aesKey AES密钥
* @returns {string} 加密后的数据
*/
export const encryptWithAes = (message, aesKey) => {
const encrypted = CryptoJS.AES.encrypt(message, aesKey, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
});
return encrypted.toString();
};
/**
* 使用密钥对数据进行AES解密
* @param {string} message 待解密的数据
* @param {CryptoJS.lib.WordArray} aesKey AES密钥
* @returns {string} 解密后的数据
*/
export const decryptWithAes = (message, aesKey) => {
const decrypted = CryptoJS.AES.decrypt(message, aesKey, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
});
return decrypted.toString(CryptoJS.enc.Utf8);
};
/**
* RSA加密
* @param {string} txt 待加密的文本
* @returns {string|false} 加密后的数据
*/
export const encrypt = (txt) => {
try {
console.log('🔐 开始RSA加密,输入文本长度:', txt ? txt.length : 'null');
console.log('🔐 使用的RSA公钥:', ENCRYPT_CONFIG.RSA_PUBLIC_KEY.substring(0, 20) + '...');
const encryptor = new JSEncrypt();
// 构造完整的PEM格式RSA公钥
const publicKey = `-----BEGIN PUBLIC KEY-----
${ENCRYPT_CONFIG.RSA_PUBLIC_KEY}
-----END PUBLIC KEY-----`;
console.log('🔐 构造的完整公钥:', publicKey.substring(0, 50) + '...');
encryptor.setPublicKey(publicKey);
console.log('🔐 公钥设置完成');
const result = encryptor.encrypt(txt);
if (result) {
console.log('✅ RSA加密成功,结果长度:', result.length);
console.log('✅ 加密结果前50位:', result.substring(0, 50) + '...');
} else {
console.error('❌ RSA加密返回空结果');
}
return result;
} catch (error) {
console.error('❌ RSA加密失败:', error);
console.error('❌ 错误堆栈:', error.stack);
return false;
}
};
/**
* RSA解密
* @param {string} txt 待解密的文本
* @returns {string|false} 解密后的数据
*/
export const decrypt = (txt) => {
try {
console.log('🔓 开始RSA解密,输入文本长度:', txt ? txt.length : 'null');
console.log('🔓 使用的RSA私钥:', ENCRYPT_CONFIG.RSA_PRIVATE_KEY.substring(0, 20) + '...');
if (!ENCRYPT_CONFIG.RSA_PRIVATE_KEY) {
console.warn('❌ RSA私钥未配置,无法解密');
return false;
}
const encryptor = new JSEncrypt();
// 构造完整的PEM格式RSA私钥
const privateKey = `-----BEGIN PRIVATE KEY-----
${ENCRYPT_CONFIG.RSA_PRIVATE_KEY}
-----END PRIVATE KEY-----`;
console.log('🔓 构造的完整私钥:', privateKey.substring(0, 50) + '...');
encryptor.setPrivateKey(privateKey);
console.log('🔓 私钥设置完成');
const result = encryptor.decrypt(txt);
if (result) {
console.log('✅ RSA解密成功,结果长度:', result.length);
console.log('✅ 解密结果前50位:', result.substring(0, 50) + '...');
} else {
console.error('❌ RSA解密返回空结果');
}
return result;
} catch (error) {
console.error('❌ RSA解密失败:', error);
console.error('❌ 错误堆栈:', error.stack);
return false;
}
};
/**
* 生成随机字符串AES密钥(字符串格式)
* @param {number} length 密钥长度,默认32位
* @returns {string} AES密钥字符串
*/
export const generateRandomAESKey = (length = 32) => {
return generateRandomString();
};
/**
* 使用字符串密钥进行AES加密(兼容原有函数)
* @param {string} data 待加密的数据
* @param {string} key AES密钥字符串
* @returns {string} 加密后的数据
*/
export const encryptWithAES = (data, key) => {
const keyUtf8 = CryptoJS.enc.Utf8.parse(key);
const encrypted = CryptoJS.AES.encrypt(data, keyUtf8, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
});
return encrypted.toString();
};
/**
* 使用字符串密钥进行AES解密(兼容原有函数)
* @param {string} data 待解密的数据
* @param {string} key AES密钥字符串
* @returns {string} 解密后的数据
*/
export const decryptWithAES = (data, key) => {
const keyUtf8 = CryptoJS.enc.Utf8.parse(key);
const decrypted = CryptoJS.AES.decrypt(data, keyUtf8, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
});
return decrypted.toString(CryptoJS.enc.Utf8);
};
/**
* 验证RSA密钥对配置
* @returns {boolean} 验证结果
*/
export const validateKeyPair = () => {
try {
console.log('🔍 开始验证RSA密钥对配置...');
// 检查公钥和私钥是否存在
if (!ENCRYPT_CONFIG.RSA_PUBLIC_KEY) {
console.error('❌ RSA公钥未配置');
return false;
}
if (!ENCRYPT_CONFIG.RSA_PRIVATE_KEY) {
console.error('❌ RSA私钥未配置');
return false;
}
// 测试加密解密功能
const testMessage = 'RSA密钥对验证测试消息';
console.log('🧪 测试消息:', testMessage);
// 使用公钥加密
const encrypted = encrypt(testMessage);
if (!encrypted) {
console.error('❌ RSA加密测试失败');
return false;
}
console.log('✅ RSA加密测试成功');
// 使用私钥解密
const decrypted = decrypt(encrypted);
if (!decrypted || decrypted !== testMessage) {
console.error('❌ RSA解密测试失败');
console.error('原始消息:', testMessage);
console.error('解密结果:', decrypted);
return false;
}
console.log('✅ RSA解密测试成功');
console.log('🎉 RSA密钥对配置验证通过!');
return true;
} catch (error) {
console.error('❌ RSA密钥对验证过程中出现错误:', error);
return false;
}
};
/**
* 获取当前加密配置摘要
* @returns {Object} 配置摘要
*/
export const getConfigSummary = () => {
return {
hasPublicKey: !!ENCRYPT_CONFIG.RSA_PUBLIC_KEY,
hasPrivateKey: !!ENCRYPT_CONFIG.RSA_PRIVATE_KEY,
publicKeyLength: ENCRYPT_CONFIG.RSA_PUBLIC_KEY ? ENCRYPT_CONFIG.RSA_PUBLIC_KEY.length : 0,
privateKeyLength: ENCRYPT_CONFIG.RSA_PRIVATE_KEY
? ENCRYPT_CONFIG.RSA_PRIVATE_KEY.length
: 0,
clientId: ENCRYPT_CONFIG.CLIENT_ID,
tenantId: ENCRYPT_CONFIG.TENANT_ID,
encryptHeader: ENCRYPT_CONFIG.ENCRYPT_HEADER,
encryptEnabled: ENCRYPT_CONFIG.ENCRYPT_ENABLED,
};
};
// 导出配置更新方法
export { updateEncryptConfig };
+394
View File
@@ -0,0 +1,394 @@
// 移动端统一请求工具 - 与mobile工程保持一致
import {
generateAesKey,
encryptBase64,
decryptBase64,
encryptWithAes,
decryptWithAes,
encrypt,
decrypt,
} from './crypto.js';
import { ENCRYPT_CONFIG, REQUEST_CONFIG } from './config.js';
import { sessionCache, userStorage } from './storage.js';
/**
* 获取token
*/
function getToken() {
try {
const token = uni.getStorageSync('access_token');
console.log(
'🔍 获取Token:',
token ? `Bearer ${token.substring(0, 20)}...` : '❌ Token为空',
);
return token ? `Bearer ${token}` : '';
} catch (e) {
console.error('❌ 获取Token失败:', e);
return '';
}
}
/**
* 处理401未授权响应
*/
function handleUnauthorized(message = '登录已失效,请重新登录') {
console.log('🔒 处理401未授权响应:', message);
// 清除登录信息
try {
uni.removeStorageSync('access_token');
uni.removeStorageSync('userInfo');
uni.removeStorageSync('isLoggedIn');
console.log('📤 已清除本地登录信息');
} catch (e) {
console.error('❌ 清除登录信息失败:', e);
}
// 显示提示信息
uni.showToast({
title: message,
icon: 'none',
duration: 2000,
});
// 延迟跳转,确保Toast显示
setTimeout(() => {
// 获取当前页面路径
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
const currentPath = '/' + currentPage.route;
// 如果当前不是登录页,则跳转到登录页
if (currentPath !== '/pages/login/index') {
console.log('🔄 从', currentPath, '跳转到登录页');
uni.reLaunch({
url: '/pages/login/index',
fail: (error) => {
console.error('❌ 跳转登录页失败:', error);
// 如果reLaunch失败,尝试其他方式
uni.redirectTo({
url: '/pages/login/index',
});
},
});
}
}, 500);
}
/**
* 统一请求方法
*/
export const request = (options) => {
return new Promise((resolve, reject) => {
// 获取token
const token = getToken();
// 处理请求配置
const config = {
url: REQUEST_CONFIG.baseURL + options.url,
method: options.method || 'GET',
data: options.data || {},
timeout: options.timeout || REQUEST_CONFIG.timeout,
header: {
'Content-Type': 'application/json',
authorization: token,
clientid: options.clientId || REQUEST_CONFIG.clientId,
// 注意:在H5环境中,浏览器会自动设置referer,手动设置会报错
// referer: options.referer || REQUEST_CONFIG.referer,
...options.header,
},
};
// 检查特殊header配置
const isToken = options.header?.isToken === false;
const isRepeatSubmit = options.header?.repeatSubmit === false;
const isEncrypt = options.header?.isEncrypt === 'true';
// 如果不需要token,移除authorization
if (isToken) {
delete config.header.authorization;
}
// 防重复提交检查
if (!isRepeatSubmit && (config.method === 'POST' || config.method === 'PUT')) {
const requestObj = {
url: config.url,
data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
time: new Date().getTime(),
};
const sessionObj = sessionCache.get('sessionObj');
if (sessionObj) {
const s_url = sessionObj.url;
const s_data = sessionObj.data;
const s_time = sessionObj.time;
const interval = REQUEST_CONFIG.repeatSubmitInterval;
if (
s_data === requestObj.data &&
requestObj.time - s_time < interval &&
s_url === requestObj.url
) {
const message = '数据正在处理,请勿重复提交';
console.warn(`[${s_url}]: ` + message);
uni.showToast({
title: message,
icon: 'none',
});
return reject(new Error(message));
}
}
sessionCache.set('sessionObj', requestObj);
}
// 加密处理
if (
REQUEST_CONFIG.encryptEnabled &&
isEncrypt &&
(config.method === 'POST' || config.method === 'PUT')
) {
try {
console.log('🔐 开始请求加密处理...');
// 生成AES密钥
const aesKey = generateAesKey();
// 使用RSA加密AES密钥
const encryptedKey = encrypt(encryptBase64(aesKey));
if (!encryptedKey) {
throw new Error('RSA加密AES密钥失败');
}
// 设置加密头
config.header[ENCRYPT_CONFIG.ENCRYPT_HEADER] = encryptedKey;
// 加密请求数据
const dataToEncrypt =
typeof config.data === 'object' ? JSON.stringify(config.data) : config.data;
config.data = encryptWithAes(dataToEncrypt, aesKey);
console.log('✅ 请求数据加密完成');
} catch (error) {
console.error('❌ 请求加密失败:', error);
uni.showToast({
title: '请求加密失败',
icon: 'none',
});
return reject(error);
}
}
// FormData处理
if (config.data instanceof globalThis.FormData) {
delete config.header['Content-Type'];
}
console.log('📤 发送请求:', {
url: config.url,
method: config.method,
hasToken: !!token,
isEncrypt: isEncrypt,
});
// 发送请求
uni.request({
...config,
success: (res) => {
console.log('📥 收到响应:', {
statusCode: res.statusCode,
url: config.url,
hasEncryptHeader: !!res.header[ENCRYPT_CONFIG.ENCRYPT_HEADER],
});
// 解密处理
if (REQUEST_CONFIG.encryptEnabled && res.header[ENCRYPT_CONFIG.ENCRYPT_HEADER]) {
try {
console.log('🔓 开始响应解密处理...');
// 获取加密的AES密钥
const keyStr = res.header[ENCRYPT_CONFIG.ENCRYPT_HEADER];
// RSA解密获取AES密钥
const base64Str = decrypt(keyStr);
if (!base64Str) {
throw new Error('RSA解密AES密钥失败');
}
// Base64解码获取AES密钥
const aesKey = decryptBase64(base64Str);
// AES解密响应数据
const decryptedData = decryptWithAes(res.data, aesKey);
// 解析JSON
res.data = JSON.parse(decryptedData);
console.log('✅ 响应数据解密完成');
} catch (error) {
console.error('❌ 响应解密失败:', error);
uni.showToast({
title: '响应解密失败',
icon: 'none',
});
return reject(error);
}
}
// 处理响应
if (res.statusCode === 200) {
// 检查业务状态码
if (res.data && res.data.code !== undefined) {
if (res.data.code === 200) {
resolve(res.data);
} else if (res.data.code === 401) {
// 401未授权,跳转到登录页
console.log('🔒 Token失效,自动跳转到登录页');
handleUnauthorized(res.data.msg || '登录已失效,请重新登录');
reject(res.data);
} else {
// 其他业务错误
const errorMsg = res.data.msg || '请求失败';
if (res.data.code !== 404) {
// 404错误静默处理
uni.showToast({
title: errorMsg,
icon: 'none',
});
}
reject(res.data);
}
} else {
resolve(res.data);
}
} else if (res.statusCode === 401) {
// HTTP 401未授权
console.log('🔒 HTTP 401未授权,自动跳转到登录页');
handleUnauthorized('登录已失效,请重新登录');
reject(res);
} else if (res.statusCode === 404) {
// 404错误静默处理
console.log('404错误被静默处理,不显示用户提示');
reject(res);
} else {
// 其他HTTP状态码错误
const errorMsg = `请求失败: ${res.statusCode}`;
uni.showToast({
title: errorMsg,
icon: 'none',
});
reject(res);
}
},
fail: (error) => {
console.error('❌ 网络请求失败:', error);
let message = '网络请求失败';
if (error.errMsg) {
if (error.errMsg.includes('timeout')) {
message = '请求超时,请检查网络连接';
} else if (error.errMsg.includes('fail')) {
message = '网络连接失败,请检查网络';
}
}
uni.showToast({
title: message,
icon: 'none',
});
reject(error);
},
});
});
};
// API方法封装
export const api = {
// 用户登录 - 使用统一的request方法和加密拦截器
login: (data) => {
console.log('🔐 开始登录流程...');
return request({
url: '/auth/login',
method: 'POST',
data: {
username: data.username,
password: data.password,
tenantId: data.tenantId || '000000',
clientId: data.clientId || REQUEST_CONFIG.clientId,
grantType: data.grantType || 'password',
},
header: {
isToken: false, // 登录接口不需要token
isEncrypt: 'true', // 启用加密
},
});
},
// 获取用户信息
getUserInfo: () => {
const token = getToken();
console.log(
'🎫 获取用户信息,当前token:',
token ? token.substring(0, 20) + '...' : '无token',
);
return request({
url: '/system/user/getInfo',
method: 'GET',
});
},
// 获取租户列表
getTenantList: (isToken = false) =>
request({
url: '/auth/tenant/list',
method: 'GET',
header: {
isToken: isToken,
},
}),
// 注销登录
logout: () =>
request({
url: '/auth/logout',
method: 'POST',
}),
};
// 通用下载方法
export const download = (url, params, fileName) => {
// 显示加载提示
uni.showLoading({
title: '正在下载数据,请稍候',
mask: true,
});
return request({
url: url,
method: 'POST',
data: params,
header: {
'Content-Type': 'application/x-www-form-urlencoded',
},
timeout: 1200000, // 20分钟超时
responseType: 'arraybuffer',
})
.then((resp) => {
// 处理下载响应
// 注意:uni-app中文件下载需要特殊处理
console.log('文件下载完成:', fileName);
uni.hideLoading();
// 这里需要根据平台进行不同的文件保存处理
// 具体实现可能需要平台特定的代码
})
.catch((error) => {
console.error('下载失败:', error);
uni.hideLoading();
uni.showToast({
title: '下载文件出现错误,请联系管理员!',
icon: 'none',
});
});
};
+267
View File
@@ -0,0 +1,267 @@
// 用户存储管理工具 - 与mobile工程保持一致
// 会话缓存工具
export const sessionCache = {
set: (key, value) => {
try {
uni.setStorageSync(`session_${key}`, JSON.stringify(value));
} catch (e) {
console.error('设置会话缓存失败:', e);
}
},
get: (key) => {
try {
const data = uni.getStorageSync(`session_${key}`);
return data ? JSON.parse(data) : null;
} catch (e) {
console.error('获取会话缓存失败:', e);
return null;
}
},
remove: (key) => {
try {
uni.removeStorageSync(`session_${key}`);
} catch (e) {
console.error('删除会话缓存失败:', e);
}
},
};
// 用户信息缓存工具
export const userStorage = {
// 保存登录信息
saveLoginInfo: (loginResult, userInfo) => {
try {
// 保存token
uni.setStorageSync('access_token', loginResult.access_token);
// 保存用户信息(增加登录时间)
if (userInfo) {
const enhancedUserInfo = {
...userInfo,
loginTime: new Date().toISOString(),
tokenExpireTime: loginResult.expires_in
? new Date(Date.now() + loginResult.expires_in * 1000).toISOString()
: null,
};
uni.setStorageSync('userInfo', JSON.stringify(enhancedUserInfo));
}
// 保存登录状态
uni.setStorageSync('isLoggedIn', true);
// 保存其他token信息
if (loginResult.refresh_token) {
uni.setStorageSync('refresh_token', loginResult.refresh_token);
}
console.log('用户登录信息已保存', {
hasToken: !!loginResult.access_token,
hasUserInfo: !!userInfo,
loginTime: new Date().toLocaleString(),
});
} catch (e) {
console.error('保存登录信息失败:', e);
}
},
// 获取用户信息
getUserInfo: () => {
try {
const userInfo = uni.getStorageSync('userInfo');
return userInfo ? JSON.parse(userInfo) : null;
} catch (e) {
console.error('获取用户信息失败:', e);
return null;
}
},
// 获取token
getToken: () => {
try {
return uni.getStorageSync('access_token') || '';
} catch (e) {
return '';
}
},
// 检查登录状态
isLoggedIn: () => {
try {
const isLoggedIn = uni.getStorageSync('isLoggedIn');
const token = uni.getStorageSync('access_token');
return !!(isLoggedIn && token);
} catch (e) {
return false;
}
},
// 清除登录信息
clearLoginInfo: () => {
try {
uni.removeStorageSync('access_token');
uni.removeStorageSync('userInfo');
uni.removeStorageSync('isLoggedIn');
console.log('用户登录信息已清除');
} catch (e) {
console.error('清除登录信息失败:', e);
}
},
// 清除所有用户相关数据(包括refresh_token等)
clearAll: () => {
try {
// 清除所有登录相关的存储
uni.removeStorageSync('access_token');
uni.removeStorageSync('userInfo');
uni.removeStorageSync('isLoggedIn');
uni.removeStorageSync('refresh_token');
// 清除可能存在的其他用户相关数据
uni.removeStorageSync('userPermissions');
uni.removeStorageSync('userRoles');
uni.removeStorageSync('tenantInfo');
console.log('所有用户相关数据已清除');
} catch (e) {
console.error('清除所有用户数据失败:', e);
}
},
};
// 数据缓存工具
export const storage = {
// 设置缓存
set: (key, value, expire) => {
try {
const data = {
value,
timestamp: Date.now(),
expire: expire ? Date.now() + expire * 1000 : null,
};
uni.setStorageSync(key, JSON.stringify(data));
} catch (e) {
console.error('缓存设置失败:', e);
}
},
// 获取缓存
get: (key) => {
try {
const data = uni.getStorageSync(key);
if (!data) return null;
const parsed = JSON.parse(data);
// 检查是否过期
if (parsed.expire && Date.now() > parsed.expire) {
uni.removeStorageSync(key);
return null;
}
return parsed.value;
} catch (e) {
console.error('缓存获取失败:', e);
return null;
}
},
// 删除缓存
remove: (key) => {
try {
uni.removeStorageSync(key);
} catch (e) {
console.error('缓存删除失败:', e);
}
},
// 清空缓存
clear: () => {
try {
uni.clearStorageSync();
} catch (e) {
console.error('缓存清空失败:', e);
}
},
};
// Token测试工具
export const tokenTest = {
// 测试Token存储
testTokenStorage: () => {
console.log('🧪 开始Token存储测试...');
try {
// 1. 清除现有Token
uni.removeStorageSync('access_token');
console.log('🗑️ 已清除现有Token');
// 2. 测试存储Token
const testToken =
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOiJzeXNfdXNlcjoxIn0.test';
uni.setStorageSync('access_token', testToken);
console.log('💾 已存储测试Token');
// 3. 测试获取Token
const retrievedToken = uni.getStorageSync('access_token');
console.log(
'📖 获取到的Token:',
retrievedToken ? retrievedToken.substring(0, 30) + '...' : '❌ 为空',
);
// 4. 验证Token是否一致
if (retrievedToken === testToken) {
console.log('✅ Token存储和获取测试通过');
return true;
} else {
console.log('❌ Token存储和获取测试失败');
return false;
}
} catch (error) {
console.error('❌ Token测试失败:', error);
return false;
}
},
// 获取当前存储的Token信息
getCurrentTokenInfo: () => {
try {
const token = uni.getStorageSync('access_token');
const userInfo = uni.getStorageSync('userInfo');
const isLoggedIn = uni.getStorageSync('isLoggedIn');
console.log('📊 当前存储状态:');
console.log(' 🎫 Token:', token ? `存在 (${token.length}字符)` : '❌ 不存在');
console.log(' 👤 用户信息:', userInfo ? '存在' : '❌ 不存在');
console.log(' 🔐 登录状态:', isLoggedIn ? '已登录' : '❌ 未登录');
return {
hasToken: !!token,
tokenLength: token ? token.length : 0,
hasUserInfo: !!userInfo,
isLoggedIn: !!isLoggedIn,
};
} catch (error) {
console.error('❌ 获取Token信息失败:', error);
return null;
}
},
// 清除所有登录信息
clearAllLoginInfo: () => {
try {
uni.removeStorageSync('access_token');
uni.removeStorageSync('userInfo');
uni.removeStorageSync('isLoggedIn');
uni.removeStorageSync('refresh_token');
console.log('🗑️ 已清除所有登录信息');
return true;
} catch (error) {
console.error('❌ 清除登录信息失败:', error);
return false;
}
},
};
+181
View File
@@ -0,0 +1,181 @@
import { request } from './request.js';
// 工作流API配置
export const workflowApi = {
// 获取流程实例分页列表(我发起的)
pageByCurrent: (params) =>
request({
url: '/workflow/instance/pageByCurrent',
method: 'GET',
data: params,
}),
// 删除流程实例
deleteByInstanceIds: (instanceIds) =>
request({
url: '/workflow/instance/deleteByInstanceIds',
method: 'DELETE',
data: instanceIds,
}),
// 撤销流程申请
cancelProcessApply: (data) =>
request({
url: '/workflow/instance/cancelProcessApply',
method: 'POST',
data,
}),
// 获取流程分类树
categoryTree: () =>
request({
url: '/workflow/category/categoryTree',
method: 'GET',
}),
// 获取我的待办任务
todoTasks: (params) =>
request({
url: '/workflow/task/todoTasks',
method: 'GET',
data: params,
}),
// 获取我的已办任务
finishedTasks: (params) =>
request({
url: '/workflow/task/finishedTasks',
method: 'GET',
data: params,
}),
// 获取我的抄送任务
copyTasks: (params) =>
request({
url: '/workflow/task/copyTasks',
method: 'GET',
data: params,
}),
// 获取全部待办任务(对应web端allTaskWaiting.vue)
pageByAllTaskWait: (params) =>
request({
url: '/workflow/task/pageByAllTaskWait',
method: 'GET',
data: params,
}),
// 获取全部已办任务(对应web端allTaskWaiting.vue)
pageByAllTaskFinish: (params) =>
request({
url: '/workflow/task/pageByAllTaskFinish',
method: 'GET',
data: params,
}),
// 获取我的已办任务(对应web端taskFinish.vue)
pageByTaskFinish: (params) =>
request({
url: '/workflow/task/pageByTaskFinish',
method: 'GET',
data: params,
}),
// 获取我的抄送任务(对应web端taskCopyList.vue)
pageByTaskCopy: (params) =>
request({
url: '/workflow/task/pageByTaskCopy',
method: 'GET',
data: params,
}),
// 修改办理人
updateAssignee: (taskIds, assigneeId) =>
request({
url: '/workflow/task/updateAssignee',
method: 'POST',
data: { taskIds, assigneeId },
}),
// 启动流程实例
startProcessInstance: (data) =>
request({
url: '/workflow/instance/start',
method: 'POST',
data,
}),
// 办理任务
complete: (data) =>
request({
url: '/workflow/task/complete',
method: 'POST',
data,
}),
// 获取流程历史
processHistory: (instanceId) =>
request({
url: `/workflow/instance/history/${instanceId}`,
method: 'GET',
}),
// 获取流程实例详情(根据业务ID)
processDetail: (businessId) =>
request({
url: `/workflow/instance/getInfo/${businessId}`,
method: 'GET',
}),
};
// 工作流状态映射
export const workflowStatus = {
draft: { label: '草稿', color: '#909399', icon: '📝' },
waiting: { label: '待审批', color: '#e6a23c', icon: '⏰' },
complete: { label: '已完成', color: '#67c23a', icon: '✅' },
cancel: { label: '已取消', color: '#f56c6c', icon: '❌' },
back: { label: '已退回', color: '#409eff', icon: '↩️' },
suspend: { label: '挂起', color: '#909399', icon: '⏸️' },
terminate: { label: '终止', color: '#f56c6c', icon: '🛑' },
};
// 工作流分类图标映射
export const categoryIcons = {
leave: '📅',
repair: '🔧',
purchase: '🛒',
reimbursement: '💰',
approval: '📋',
default: '📄',
};
// 获取状态样式
export const getStatusStyle = (status) => workflowStatus[status] || workflowStatus.draft;
// 获取分类图标
export const getCategoryIcon = (category) => categoryIcons[category] || categoryIcons.default;
// 工作流路由跳转方法
export const routerJump = (routerJumpVo) => {
const { businessId, taskId, type, formCustom, formPath } = routerJumpVo;
const params = { businessId, taskId, type, formCustom, formPath };
const queryString = Object.keys(params)
.filter((key) => params[key] !== undefined && params[key] !== null)
.map((key) => `${key}=${encodeURIComponent(params[key])}`)
.join('&');
uni.navigateTo({
url: `/pages/workflow/process?${queryString}`,
fail: () => {
uni.showToast({
title: '页面跳转失败',
icon: 'error',
});
},
});
};
// 格式化时间显示
export const formatTime = (timeStr) => {
if (!timeStr) return '';
const time = new Date(timeStr);
const now = new Date();
const diff = now.getTime() - time.getTime();
if (diff < 60 * 1000) {
return '刚刚';
}
if (diff < 60 * 60 * 1000) {
return `${Math.floor(diff / (60 * 1000))}分钟前`;
}
if (diff < 24 * 60 * 60 * 1000) {
return `${Math.floor(diff / (60 * 60 * 1000))}小时前`;
}
if (diff < 7 * 24 * 60 * 60 * 1000) {
return `${Math.floor(diff / (24 * 60 * 60 * 1000))}天前`;
}
return time.toLocaleDateString();
};