删除不需要的文件及提交移动端的代码

This commit is contained in:
zlwu committed 2025-08-06 16:01:27 +08:00
1 parent 86f65df783
commit 134f711349
751 files changed
+3888 -115501

No files matched your search

+378
View File
@@ -0,0 +1,378 @@
# 移动端工具库使用说明
本文档说明移动端 `utils` 目录下各个工具的使用方法,特别是升级后的请求工具。
## 📁 文件结构
```
mobile/src/utils/
├── request.js # 统一请求工具(已升级)
├── crypto.js # 加密解密工具(新增)
├── config.js # 配置管理工具(新增)
├── encrypt-pure.js # 登录专用加密工具(保留)
├── encrypt-*.js # 其他加密实现(保留)
└── README.md # 本文档
```
## 🚀 核心升级功能
### 1. 统一的加密解密支持
移动端现在与 Web 端保持一致,支持:
- ✅ **入参加密**:通过 `isEncrypt: 'true'` 头部控制
- ✅ **出参解密**:自动处理后端返回的加密数据
- ✅ **AES + RSA 双重加密**:与后端完全兼容
- ✅ **防重复提交**:避免用户误操作
- ✅ **错误处理优化**:404静默处理、401自动跳转
### 2. 可配置的参数管理
所有关键参数都可以通过配置文件统一管理,方便调试和部署。
## 📖 使用方法
### 基础请求
```javascript
import { request } from '@/utils/request.js';
// 普通GET请求
const response = await request({
url: '/api/user/list',
method: 'GET'
});
// 普通POST请求
const response = await request({
url: '/api/user/create',
method: 'POST',
data: {
name: '张三',
age: 25
}
});
```
### 加密请求
```javascript
import { request } from '@/utils/request.js';
// 启用加密的POST请求
const response = await request({
url: '/api/sensitive/data',
method: 'POST',
data: {
sensitiveInfo: '敏感数据'
},
header: {
isEncrypt: 'true' // 关键:启用加密
}
});
```
### 免Token请求
```javascript
import { request } from '@/utils/request.js';
// 不需要Token的请求(如获取租户列表)
const response = await request({
url: '/auth/tenant/list',
method: 'GET',
header: {
isToken: false // 关键:不携带Token
}
});
```
### 允许重复提交
```javascript
import { request } from '@/utils/request.js';
// 允许重复提交的请求
const response = await request({
url: '/api/data/sync',
method: 'POST',
data: { syncData: 'xxx' },
header: {
repeatSubmit: false // 关键:允许重复提交
}
});
```
## ⚙️ 配置管理
### 基础配置更新
```javascript
import { updateConfig } from '@/utils/config.js';
// 更新API基础URL
updateConfig({
request: {
baseURL: {
production: 'https://new-api-domain.com'
}
}
});
// 更新客户端ID
updateConfig({
app: {
clientId: 'new-client-id-here'
}
});
```
### 加密配置更新
```javascript
import { updateConfig } from '@/utils/config.js';
// 更新RSA公钥
updateConfig({
encryption: {
rsaPublicKey: 'new-rsa-public-key-here'
}
});
// 临时禁用加密(调试用)
updateConfig({
encryption: {
enabled: false
}
});
```
### 预设配置方案
```javascript
import { applyPreset } from '@/utils/config.js';
// 应用生产环境配置
applyPreset('production');
// 应用测试环境配置
applyPreset('test');
// 应用开发环境配置
applyPreset('development');
```
### 单个配置项更新
```javascript
import { setConfigItem, getConfigItem } from '@/utils/config.js';
// 设置超时时间
setConfigItem('request.timeout', 600000); // 10分钟
// 获取当前客户端ID
const clientId = getConfigItem('app.clientId');
// 检查是否启用加密
const isEncrypted = getConfigItem('encryption.enabled');
```
## 🔐 加密工具直接使用
```javascript
import {
encryptWithAES,
decryptWithAES,
generateRandomAESKey,
encrypt,
decrypt
} from '@/utils/crypto.js';
// AES加密解密
const aesKey = generateRandomAESKey();
const encryptedData = encryptWithAES('敏感数据', aesKey);
const decryptedData = decryptWithAES(encryptedData, aesKey);
// RSA加密解密
const encryptedKey = encrypt('AES密钥');
const decryptedKey = decrypt(encryptedKey);
```
## 📦 API方法封装
### 登录
```javascript
import { api } from '@/utils/request.js';
// 用户登录(自动加密)
const loginResult = await api.login({
username: 'admin',
password: 'password123',
tenantId: '000000'
});
```
### 获取用户信息
```javascript
import { api } from '@/utils/request.js';
// 获取当前用户信息
const userInfo = await api.getUserInfo();
```
### 注销登录
```javascript
import { api } from '@/utils/request.js';
// 用户注销
await api.logout();
```
## 💾 缓存工具
### 用户信息缓存
```javascript
import { userStorage } from '@/utils/request.js';
// 保存登录信息
userStorage.saveLoginInfo(loginResult, userInfo);
// 获取用户信息
const user = userStorage.getUserInfo();
// 检查登录状态
const isLoggedIn = userStorage.isLoggedIn();
// 清除登录信息
userStorage.clearLoginInfo();
```
### 通用缓存
```javascript
import { storage } from '@/utils/request.js';
// 设置缓存(30分钟过期)
storage.set('cacheKey', { data: 'value' }, 1800);
// 获取缓存
const data = storage.get('cacheKey');
// 删除缓存
storage.remove('cacheKey');
// 清空所有缓存
storage.clear();
```
## 📥 文件下载
```javascript
import { download } from '@/utils/request.js';
// 下载文件
await download('/api/export/users', {
filters: { status: 'active' }
}, 'users.xlsx');
```
## 🔧 调试和日志
### 开启详细日志
```javascript
import { updateConfig } from '@/utils/config.js';
// 开启所有调试日志
updateConfig({
debug: {
enableVerboseLog: true,
showRequestLog: true,
showResponseLog: true,
showCryptoLog: true
}
});
```
### 错误处理配置
```javascript
import { updateConfig } from '@/utils/config.js';
// 自定义错误处理
updateConfig({
error: {
silentCodes: [404, 403], // 静默处理的状态码
toastDuration: 3000, // 错误提示显示时间
loginRedirectDelay: 1000 // 跳转登录延迟
}
});
```
## 🚨 注意事项
### 1. 加密兼容性
- 移动端加密算法与 Web 端完全一致
- RSA 公钥必须与后端保持同步
- 加密只对 POST/PUT 请求生效
### 2. 配置优先级
- 运行时配置 > 环境预设 > 默认配置
- 配置更新会立即生效,无需重启应用
### 3. Token 管理
- Token 自动从本地存储获取
- 401 错误会自动清除 Token 并跳转登录
- 支持 Token 过期时间管理
### 4. 错误处理
- 404 错误默认静默处理
- 403 错误会显示提示但不跳转
- 网络错误会显示友好提示
## 📊 性能优化
### 1. 防重复提交
- 默认 500ms 间隔防重复提交
- 可通过配置调整间隔时间
- 支持单个请求禁用防重复
### 2. 请求超时
- 普通请求:5分钟超时
- 下载请求:20分钟超时
- 可通过配置调整超时时间
### 3. 缓存策略
- 会话级缓存用于防重复提交
- 持久化缓存支持过期时间
- 登录信息缓存支持时间戳
## 🔗 与 Web 端对比
| 功能 | Web 端 | 移动端 | 说明 |
|------|--------|--------|------|
| 入参加密 | ✅ | ✅ | 完全一致 |
| 出参解密 | ✅ | ✅ | 完全一致 |
| 防重复提交 | ✅ | ✅ | 完全一致 |
| 401处理 | ✅ | ✅ | 跳转方式略有不同 |
| 404静默 | ✅ | ✅ | 完全一致 |
| 下载支持 | ✅ | ✅ | 平台实现不同 |
| 配置管理 | 环境变量 | 配置文件 | 更灵活的配置方式 |
## 🎯 最佳实践
1. **生产环境**:关闭调试日志,启用加密
2. **开发环境**:开启详细日志,保持加密测试
3. **配置管理**:使用预设配置,避免硬编码
4. **错误处理**:合理设置静默错误码
5. **性能优化**:适当设置超时和缓存策略
---
**📝 更新日志**
- 2025-01-07:完成移动端请求工具升级,与 Web 端功能对齐
- 支持入参加密、出参解密、防重复提交等完整功能
- 新增配置管理系统,提升可维护性
-2
View File
@@ -373,8 +373,6 @@ class AccessibilityUtils {
uni.switchTab = (options) => {
const pageTitles = {
'/pages/index/index': '智慧校园 - 首页',
'/pages/workbench/index': '智慧校园 - 办公中心',
'/pages/profile/index': '智慧校园 - 我的',
};
const title = pageTitles[options.url];
+353
View File
@@ -0,0 +1,353 @@
// 移动端配置文件 - 集中管理所有可配置参数
// 使用方法:import { updateConfig } from '@/utils/config.js' 然后调用 updateConfig(newConfig)
// 默认配置
const DEFAULT_CONFIG = {
// =========================
// 加密相关配置
// =========================
encryption: {
// 是否启用加密
enabled: true,
// RSA公钥 - 与后端保持一致
rsaPublicKey:
'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==',
// RSA私钥 - 前端不建议存放,仅用于测试环境
rsaPrivateKey: '',
// 加密请求头名称
encryptHeader: 'encrypt-key',
// AES密钥长度
aesKeyLength: 32,
},
// =========================
// 请求相关配置
// =========================
request: {
// API基础URL
baseURL: {
development: '/api',
production: 'https://your-api-domain.com',
},
// 请求超时时间(毫秒)
timeout: 300000, // 5分钟
// 下载超时时间(毫秒)
downloadTimeout: 1200000, // 20分钟
// 防重复提交间隔时间(毫秒)
repeatSubmitInterval: 500,
// 默认请求头
defaultHeaders: {
'Content-Type': 'application/json',
},
},
// =========================
// 应用相关配置
// =========================
app: {
// 客户端ID
clientId: 'e5cd7e4891bf95d1d19206ce24a7b32e',
// 租户ID
tenantId: '000000',
// 默认referer
referer: 'http://localhost/login?redirect=/index',
// 登录页面路径
loginPath: '/pages/login/index',
},
// =========================
// 缓存相关配置
// =========================
cache: {
// 默认缓存过期时间(秒)
defaultExpire: 3600, // 1小时
// Token存储键名
tokenKey: 'access_token',
// 用户信息存储键名
userInfoKey: 'userInfo',
// 登录状态存储键名
loginStatusKey: 'isLoggedIn',
// 会话缓存前缀
sessionPrefix: 'session_',
},
// =========================
// 错误处理配置
// =========================
error: {
// 静默处理的HTTP状态码
silentCodes: [404],
// 自动跳转登录的状态码
loginRedirectCodes: [401],
// 错误提示显示时间(毫秒)
toastDuration: 2000,
// 跳转登录延迟时间(毫秒)
loginRedirectDelay: 500,
},
// =========================
// 调试相关配置
// =========================
debug: {
// 是否启用详细日志
enableVerboseLog: process.env.NODE_ENV === 'development',
// 是否显示请求日志
showRequestLog: true,
// 是否显示响应日志
showResponseLog: true,
// 是否显示加密解密日志
showCryptoLog: true,
},
};
// 当前配置 - 可被动态修改
let currentConfig = JSON.parse(JSON.stringify(DEFAULT_CONFIG));
// =========================
// 配置管理方法
// =========================
/**
* 获取完整配置
* @returns {Object} 当前配置对象
*/
export const getConfig = () => {
return currentConfig;
};
/**
* 获取特定配置项
* @param {string} path 配置路径,支持嵌套 如: 'encryption.enabled' 或 'request.timeout'
* @returns {any} 配置值
*/
export const getConfigItem = (path) => {
const keys = path.split('.');
let value = currentConfig;
for (const key of keys) {
if (value && typeof value === 'object' && key in value) {
value = value[key];
} else {
console.warn(`配置项 ${path} 不存在`);
return undefined;
}
}
return value;
};
/**
* 更新配置
* @param {Object} newConfig 新配置对象(部分更新)
* @param {boolean} deep 是否深度合并,默认true
*/
export const updateConfig = (newConfig, deep = true) => {
if (deep) {
currentConfig = deepMerge(currentConfig, newConfig);
} else {
currentConfig = { ...currentConfig, ...newConfig };
}
console.log('🔧 配置已更新:', currentConfig);
// 通知其他模块配置已更新
notifyConfigUpdate();
};
/**
* 设置特定配置项
* @param {string} path 配置路径
* @param {any} value 配置值
*/
export const setConfigItem = (path, value) => {
const keys = path.split('.');
let target = currentConfig;
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
if (!target[key] || typeof target[key] !== 'object') {
target[key] = {};
}
target = target[key];
}
const lastKey = keys[keys.length - 1];
target[lastKey] = value;
console.log(`🔧 配置项 ${path} 已更新为:`, value);
// 通知其他模块配置已更新
notifyConfigUpdate();
};
/**
* 重置配置为默认值
*/
export const resetConfig = () => {
currentConfig = JSON.parse(JSON.stringify(DEFAULT_CONFIG));
console.log('🔄 配置已重置为默认值');
// 通知其他模块配置已更新
notifyConfigUpdate();
};
/**
* 获取当前环境的API基础URL
* @returns {string} API基础URL
*/
export const getBaseURL = () => {
const env = process.env.NODE_ENV || 'development';
return currentConfig.request.baseURL[env] || currentConfig.request.baseURL.development;
};
// =========================
// 配置预设方案
// =========================
/**
* 开发环境配置
*/
export const developmentConfig = {
encryption: {
enabled: true, // 开发环境也启用加密测试
},
debug: {
enableVerboseLog: true,
showRequestLog: true,
showResponseLog: true,
showCryptoLog: true,
},
error: {
toastDuration: 3000, // 开发环境错误提示显示久一点
},
};
/**
* 生产环境配置
*/
export const productionConfig = {
encryption: {
enabled: true,
},
debug: {
enableVerboseLog: false,
showRequestLog: false,
showResponseLog: false,
showCryptoLog: false,
},
request: {
timeout: 300000, // 生产环境保持较长超时
repeatSubmitInterval: 1000, // 生产环境防重复提交间隔更长
},
};
/**
* 测试环境配置
*/
export const testConfig = {
encryption: {
enabled: true,
},
debug: {
enableVerboseLog: true,
showRequestLog: true,
showResponseLog: false,
showCryptoLog: true,
},
};
/**
* 应用预设配置
* @param {string} preset 预设名称: 'development' | 'production' | 'test'
*/
export const applyPreset = (preset) => {
const presets = {
development: developmentConfig,
production: productionConfig,
test: testConfig,
};
if (presets[preset]) {
updateConfig(presets[preset]);
console.log(`✅ 已应用 ${preset} 环境配置预设`);
} else {
console.error(`❌ 未知的配置预设: ${preset}`);
}
};
// =========================
// 工具方法
// =========================
/**
* 深度合并对象
* @param {Object} target 目标对象
* @param {Object} source 源对象
* @returns {Object} 合并后的对象
*/
const deepMerge = (target, source) => {
const result = { ...target };
for (const key in source) {
if (source.hasOwnProperty(key)) {
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
result[key] = deepMerge(result[key] || {}, source[key]);
} else {
result[key] = source[key];
}
}
}
return result;
};
/**
* 通知其他模块配置已更新
*/
const notifyConfigUpdate = () => {
// 这里可以实现事件通知机制
// 例如使用 uni.$emit('configUpdated', currentConfig)
if (typeof uni !== 'undefined' && uni.$emit) {
uni.$emit('configUpdated', currentConfig);
}
};
// =========================
// 初始化配置
// =========================
// 根据环境自动应用配置预设
const env = process.env.NODE_ENV || 'development';
if (env !== 'development') {
applyPreset(env);
}
// 导出常用配置项的快捷访问方法
export const isEncryptionEnabled = () => getConfigItem('encryption.enabled');
export const getClientId = () => getConfigItem('app.clientId');
export const getTenantId = () => getConfigItem('app.tenantId');
export const getTimeout = () => getConfigItem('request.timeout');
export const isDebugMode = () => getConfigItem('debug.enableVerboseLog');
console.log('📋 移动端配置初始化完成,当前环境:', env);
+291
View File
@@ -0,0 +1,291 @@
// 移动端加密解密工具 - 与web端保持一致
import CryptoJS from 'crypto-js';
import JSEncrypt from 'jsencrypt';
// 加密配置 - 可配置
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,
};
/**
* 随机生成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);
};
/**
* 更新加密配置
* @param {Object} config 新的配置对象
*/
export const updateEncryptConfig = (config) => {
Object.assign(ENCRYPT_CONFIG, config);
console.log('🔧 加密配置已更新:', ENCRYPT_CONFIG);
};
/**
* 验证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 { ENCRYPT_CONFIG as config };
+7 -20
View File
@@ -375,7 +375,7 @@ class GestureUtils {
class PageSwipeGesture {
constructor() {
this.currentPageIndex = 0;
this.pages = ['home', 'workbench', 'profile'];
this.pages = ['home'];
this.isAnimating = false;
}
@@ -387,29 +387,18 @@ class PageSwipeGesture {
enableDoubleTap: false,
});
gesture.on('swipeleft', () => {
this.nextPage();
});
gesture.on('swiperight', () => {
this.prevPage();
});
// 只有一个页面,禁用滑动切换
return gesture;
}
nextPage() {
if (this.isAnimating) return;
this.currentPageIndex = (this.currentPageIndex + 1) % this.pages.length;
this.switchPage(this.pages[this.currentPageIndex]);
// 只有一个页面,不执行切换
return;
}
prevPage() {
if (this.isAnimating) return;
this.currentPageIndex = (this.currentPageIndex - 1 + this.pages.length) % this.pages.length;
this.switchPage(this.pages[this.currentPageIndex]);
// 只有一个页面,不执行切换
return;
}
switchPage(pageName) {
@@ -417,12 +406,10 @@ class PageSwipeGesture {
const pageUrls = {
home: '/pages/index/index',
workbench: '/pages/workbench/index',
profile: '/pages/profile/index',
};
if (pageUrls[pageName]) {
uni.switchTab({
uni.navigateTo({
url: pageUrls[pageName],
success: () => {
setTimeout(() => {
+192
View File
@@ -0,0 +1,192 @@
/**
* iconfont 图标管理工具
* 帮助开发者快速添加和管理自定义图标
*/
/**
* SVG Path 转换为 iconfont CSS 代码
* @param {string} iconName - 图标名称 (如: star, heart)
* @param {string} svgPath - SVG path 数据
* @param {string} viewBox - SVG viewBox, 默认 "0 0 24 24"
* @returns {string} CSS 代码
*/
export function generateIconCSS(iconName, svgPath, viewBox = '0 0 24 24') {
const svg = `<svg xmlns='http://www.w3.org/2000/svg' viewBox='${viewBox}' fill='currentColor'><path d='${svgPath}'/></svg>`;
const encodedSvg = encodeURIComponent(svg).replace(/'/g, '%27').replace(/"/g, '%22');
return `/* ${iconName}图标 */
.icon-${iconName} {
background-image: url("data:image/svg+xml;charset=utf8,${encodedSvg}");
}`;
}
/**
* 批量生成图标 CSS
* @param {Array} icons - 图标数组 [{name, path, viewBox}]
* @returns {string} 完整的 CSS 代码
*/
export function generateBatchIconCSS(icons) {
return icons.map((icon) => generateIconCSS(icon.name, icon.path, icon.viewBox)).join('\n\n');
}
/**
* 从完整SVG生成CSS
* @param {string} iconName - 图标名称
* @param {string} svgContent - 完整的SVG内容
* @returns {string} CSS 代码
*/
export function generateIconCSSFromSVG(iconName, svgContent) {
const encodedSvg = encodeURIComponent(svgContent).replace(/'/g, '%27').replace(/"/g, '%22');
return `/* ${iconName}图标 */
.icon-${iconName} {
background-image: url("data:image/svg+xml;charset=utf8,${encodedSvg}");
}`;
}
/**
* 预定义的常用图标路径
* 来源:Material Design Icons
*/
export const COMMON_ICONS = {
// 基础图标
user: 'M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z',
lock: 'M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zM9 6c0-1.66 1.34-3 3-3s3 1.34 3 3v2H9V6z',
home: 'M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z',
setting:
'M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z',
// 操作图标
add: 'M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z',
close: 'M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z',
edit: 'M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z',
delete: 'M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z',
search: 'M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z',
check: 'M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z',
// 状态图标
success:
'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z',
warning: 'M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z',
info: 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z',
error: 'M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm5 13.59L15.59 17 12 13.41 8.41 17 7 15.59 10.59 12 7 8.41 8.41 7 12 10.59 15.59 7 17 8.41 13.41 12 17 15.59z',
// 文件图标
download: 'M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z',
upload: 'M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z',
// 导航图标
arrowLeft: 'M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z',
arrowRight: 'M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z',
arrowUp: 'M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z',
arrowDown: 'M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6z',
// 社交图标
star: 'M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z',
heart: 'M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z',
message:
'M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-2 12H6v-2h12v2zm0-3H6V9h12v2zm0-3H6V6h12v2z',
phone: 'M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z',
mail: 'M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z',
};
/**
* 快速生成常用图标的CSS
* @param {string[]} iconNames - 图标名称数组
* @returns {string} CSS 代码
*/
export function generateCommonIconsCSS(iconNames) {
const selectedIcons = iconNames
.map((name) => ({
name,
path: COMMON_ICONS[name],
}))
.filter((icon) => icon.path);
return generateBatchIconCSS(selectedIcons);
}
/**
* 检查图标名称是否已存在
* @param {string} iconName - 图标名称
* @param {string} cssContent - 当前CSS文件内容
* @returns {boolean} 是否已存在
*/
export function checkIconExists(iconName, cssContent) {
return cssContent.includes(`.icon-${iconName}`);
}
/**
* 验证SVG内容
* @param {string} svgContent - SVG内容
* @returns {boolean} 是否有效
*/
export function validateSVG(svgContent) {
try {
const parser = new DOMParser();
const doc = parser.parseFromString(svgContent, 'image/svg+xml');
return !doc.querySelector('parsererror');
} catch (error) {
return false;
}
}
/**
* 使用示例
*/
export const USAGE_EXAMPLES = {
// 单个图标生成
singleIcon: {
code: `import { generateIconCSS } from '@/utils/iconHelper';
const css = generateIconCSS(
'calendar',
'M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7z'
);
console.log(css);`,
output: `/* calendar图标 */
.icon-calendar {
background-image: url("data:image/svg+xml;charset=utf8,...");
}`,
},
// 批量图标生成
batchIcons: {
code: `import { generateCommonIconsCSS } from '@/utils/iconHelper';
const css = generateCommonIconsCSS(['star', 'heart', 'message']);
console.log(css);`,
output: `/* star图标 */
.icon-star { ... }
/* heart图标 */
.icon-heart { ... }
/* message图标 */
.icon-message { ... }`,
},
// 从SVG生成
fromSVG: {
code: `import { generateIconCSSFromSVG } from '@/utils/iconHelper';
const svgContent = \`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z"/>
</svg>\`;
const css = generateIconCSSFromSVG('circle', svgContent);
console.log(css);`,
},
};
// 导出所有工具函数
export default {
generateIconCSS,
generateBatchIconCSS,
generateIconCSSFromSVG,
generateCommonIconsCSS,
checkIconExists,
validateSVG,
COMMON_ICONS,
USAGE_EXAMPLES,
};
+122
View File
@@ -0,0 +1,122 @@
/**
* 移动端权限校验工具
* 参考web端的permission.ts实现
*/
import { userManager } from '@/utils/user'
/**
* 字符权限校验
* @param {Array} value 校验值
* @returns {Boolean}
*/
export const checkPermi = (value) => {
if (value && value instanceof Array && value.length > 0) {
const userInfo = userManager.getCurrentUser()
if (!userInfo || !userInfo.permissions) {
return false
}
const permissions = userInfo.permissions
const permissionDatas = value
const all_permission = '*:*:*'
const hasPermission = permissions.some((permission) => {
return all_permission === permission || permissionDatas.includes(permission)
})
if (!hasPermission) {
return false
}
return true
} else {
console.error('need roles! Like checkPermi="[\'system:user:add\',\'system:user:edit\']"')
return false
}
}
/**
* 角色权限校验
* @param {Array} value 校验值
* @returns {Boolean}
*/
export const checkRole = (value) => {
if (value && value instanceof Array && value.length > 0) {
const userInfo = userManager.getCurrentUser()
if (!userInfo || !userInfo.roles) {
return false
}
const roles = userInfo.roles
const permissionRoles = value
const super_admin = 'admin'
const hasRole = roles.some((role) => {
return super_admin === role || permissionRoles.includes(role)
})
if (!hasRole) {
return false
}
return true
} else {
console.error('need roles! Like checkRole="[\'admin\',\'editor\']"')
return false
}
}
/**
* 权限指令校验函数
* 用于在模板中进行权限判断
* @param {Array|String} permissions 权限标识数组或单个权限标识
* @returns {Boolean}
*/
export const hasPermission = (permissions) => {
if (!permissions) return true
// 如果传入的是字符串,转换为数组
const perms = Array.isArray(permissions) ? permissions : [permissions]
return checkPermi(perms)
}
/**
* Vue过滤器:权限校验
* 在模板中使用:v-if="hasPermi(['system:user:add'])"
*/
export const hasPermi = hasPermission
/**
* 用户角色校验
* @param {Array|String} roles 角色标识数组或单个角色标识
* @returns {Boolean}
*/
export const hasRole = (roles) => {
if (!roles) return true
// 如果传入的是字符串,转换为数组
const roleList = Array.isArray(roles) ? roles : [roles]
return checkRole(roleList)
}
/**
* 权限控制混入
* 在页面或组件中使用
*/
export const permissionMixin = {
methods: {
// 权限校验方法
hasPermi(permissions) {
return hasPermission(permissions)
},
// 角色校验方法
hasRole(roles) {
return hasRole(roles)
},
// 检查单个权限
checkPermission(permission) {
return hasPermission([permission])
},
},
}
+321 -98
View File
@@ -1,10 +1,66 @@
import { encryptLoginData } from './encrypt-pure.js';
import {
generateAesKey,
encryptBase64,
decryptBase64,
encryptWithAes,
decryptWithAes,
encrypt,
decrypt,
ENCRYPT_CONFIG,
updateEncryptConfig,
validateKeyPair,
getConfigSummary,
} from './crypto.js';
// API请求配置
const baseURL =
process.env.NODE_ENV === 'development'
? '/api' // 开发环境使用代理
: 'https://your-api-domain.com';
// API请求配置 - 可配置
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,
};
// 会话缓存工具
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 request = (options) => {
@@ -12,18 +68,157 @@ export const request = (options) => {
// 获取token
const token = getToken();
uni.request({
url: baseURL + options.url,
// 处理请求配置
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: 'e5cd7e4891bf95d1d19206ce24a7b32e',
referer: 'http://localhost/login?redirect=/index',
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) {
// 检查业务状态码
@@ -37,10 +232,14 @@ export const request = (options) => {
reject(res.data);
} else {
// 其他业务错误
uni.showToast({
title: res.data.msg || '请求失败',
icon: 'none',
});
const errorMsg = res.data.msg || '请求失败';
if (res.data.code !== 404) {
// 404错误静默处理
uni.showToast({
title: errorMsg,
icon: 'none',
});
}
reject(res.data);
}
} else {
@@ -51,18 +250,34 @@ export const request = (options) => {
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: `请求失败: ${res.statusCode}`,
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: '网络请求失败',
title: message,
icon: 'none',
});
reject(error);
@@ -133,87 +348,25 @@ function handleUnauthorized(message = '登录已失效,请重新登录') {
// API方法封装
export const api = {
// 用户登录
login: async (data) => {
try {
console.log('🔐 开始加密登录流程...');
// 用户登录 - 使用统一的request方法和加密拦截器
login: (data) => {
console.log('🔐 开始登录流程...');
// 加密登录数据
const encryptResult = encryptLoginData({
return request({
url: '/auth/login',
method: 'POST',
data: {
username: data.username,
password: data.password,
tenantId: data.tenantId || '000000',
clientId: data.clientId || 'e5cd7e4891bf95d1d19206ce24a7b32e',
clientId: data.clientId || REQUEST_CONFIG.clientId,
grantType: data.grantType || 'password',
});
// 如果encryptLoginData返回的是Promise,需要await
const actualEncryptResult = encryptResult.then ? await encryptResult : encryptResult;
if (!actualEncryptResult.success) {
console.error('❌ 登录数据加密失败:', actualEncryptResult.error);
throw new Error(actualEncryptResult.error);
}
console.log('✅ 登录数据加密成功,发送加密请求...');
// 使用加密数据和自定义header发送请求
return new Promise((resolve, reject) => {
uni.request({
url: baseURL + '/auth/login',
method: 'POST',
data: actualEncryptResult.encryptedData, // 发送加密字符串,不是JSON对象
header: {
...actualEncryptResult.headers,
referer: 'http://localhost/login?redirect=/index', // 添加正确格式的referer
},
success: (res) => {
console.log('🔐 加密登录响应:', res);
if (res.statusCode === 200) {
if (res.data && res.data.code !== undefined) {
if (res.data.code === 200) {
console.log('✅ 加密登录成功');
resolve(res.data);
} else {
console.log('❌ 登录失败:', res.data.msg);
uni.showToast({
title: res.data.msg || '登录失败',
icon: 'none',
});
reject(res.data);
}
} else {
resolve(res.data);
}
} else {
console.log('❌ HTTP状态错误:', res.statusCode);
uni.showToast({
title: `请求失败: ${res.statusCode}`,
icon: 'none',
});
reject(res);
}
},
fail: (error) => {
console.log('❌ 登录请求失败:', error);
uni.showToast({
title: '网络请求失败',
icon: 'none',
});
reject(error);
},
});
});
} catch (error) {
console.error('❌ 登录加密处理失败:', error);
console.error('❌ 错误详情:', error.message);
console.error('❌ 错误堆栈:', error.stack);
uni.showToast({
title: '登录处理失败: ' + error.message,
icon: 'none',
});
return Promise.reject(error);
}
},
header: {
isToken: false, // 登录接口不需要token
isEncrypt: 'true', // 启用加密
},
});
},
// 获取用户信息
@@ -227,11 +380,6 @@ export const api = {
return request({
url: '/system/user/getInfo',
method: 'GET',
header: {
authorization: token,
clientid: 'e5cd7e4891bf95d1d19206ce24a7b32e',
referer: 'http://localhost/login?redirect=/index',
},
});
},
@@ -265,7 +413,7 @@ export const userStorage = {
if (userInfo) {
const enhancedUserInfo = {
...userInfo,
loginTime: new Date().toISOString(), // 记录登录时间
loginTime: new Date().toISOString(),
tokenExpireTime: loginResult.expires_in
? new Date(Date.now() + loginResult.expires_in * 1000).toISOString()
: null,
@@ -333,6 +481,26 @@ export const userStorage = {
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);
}
},
};
// 数据缓存工具
@@ -390,3 +558,58 @@ export const storage = {
}
},
};
// 通用下载方法
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',
});
});
};
// 配置更新方法
export const updateRequestConfig = (config) => {
Object.assign(REQUEST_CONFIG, config);
console.log('🔧 请求配置已更新:', REQUEST_CONFIG);
};
// 加密配置更新方法
export const updateCryptoConfig = updateEncryptConfig;
// 密钥对验证方法
export const validateCryptoKeys = validateKeyPair;
// 获取加密配置摘要
export const getCryptoConfigSummary = getConfigSummary;
// 导出配置以便外部修改
export { REQUEST_CONFIG as config, ENCRYPT_CONFIG as cryptoConfig };
+1 -1
View File
@@ -176,7 +176,7 @@ export const permissionUtils = {
*/
checkPagePermission(pagePath) {
// 公开页面,无需权限检查
const publicPages = ['/pages/login/index', '/pages/index/index', '/pages/profile/index'];
const publicPages = ['/pages/login/index', '/pages/index/index'];
if (publicPages.includes(pagePath)) {
return true;