删除不需要的文件及提交移动端的代码
This commit is contained in:
1 parent
86f65df783
commit
134f711349
751 files changed
+3888
-115501
No files matched your search
@@ -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 端功能对齐
|
||||
- 支持入参加密、出参解密、防重复提交等完整功能
|
||||
- 新增配置管理系统,提升可维护性
|
||||
@@ -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];
|
||||
|
||||
@@ -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);
|
||||
@@ -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 };
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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
@@ -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 };
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user