封装统一的url构建工具
This commit is contained in:
parent
03243d62e4
commit
34c6de002c
479
ARCHITECTURE_ANALYSIS.md
Normal file
479
ARCHITECTURE_ANALYSIS.md
Normal file
|
|
@ -0,0 +1,479 @@
|
|||
# OfficeSystem 项目架构与缺陷分析报告
|
||||
|
||||
## 📋 项目概述
|
||||
|
||||
**项目名称**: OfficeSystem(办公管理系统)
|
||||
**技术栈**: uni-app + Vue 3 + Pinia + uv-ui
|
||||
**项目类型**: 跨平台移动应用(支持H5、小程序、App)
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ 项目架构分析
|
||||
|
||||
### 1. 整体架构层次
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ 页面层 (Pages) │
|
||||
│ - 任务管理、客户管理、项目管理等 │
|
||||
└─────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────┐
|
||||
│ 组件层 (Components) │
|
||||
│ - 业务组件、UI组件 │
|
||||
└─────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────┐
|
||||
│ 状态管理层 (Store/Pinia) │
|
||||
│ - user, task, customer, dict │
|
||||
└─────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────┐
|
||||
│ API层 (api/) │
|
||||
│ - 统一接口封装 │
|
||||
└─────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────┐
|
||||
│ 工具层 (utils/) │
|
||||
│ - 请求封装、工具函数、指令 │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2. 目录结构分析
|
||||
|
||||
#### ✅ 优点
|
||||
- **模块化清晰**: 按功能模块划分(task、customer、project等)
|
||||
- **职责分离**: API、Store、组件分离明确
|
||||
- **工具函数集中**: utils目录统一管理工具函数
|
||||
|
||||
#### ⚠️ 问题
|
||||
- **缺少类型定义**: 无TypeScript或JSDoc类型定义
|
||||
- **测试缺失**: 无单元测试、集成测试
|
||||
- **文档不足**: 缺少API文档、组件文档
|
||||
|
||||
---
|
||||
|
||||
## 🔍 详细架构分析
|
||||
|
||||
### 1. API层 (`api/`)
|
||||
|
||||
#### 架构设计
|
||||
- ✅ 按业务模块划分(user、task、customer等)
|
||||
- ✅ 统一使用 `uni.$uv.http` 封装
|
||||
- ✅ 支持自定义配置(auth、toast、catch)
|
||||
|
||||
#### 存在的问题
|
||||
|
||||
**🔴 严重问题**
|
||||
|
||||
1. **硬编码的baseURL**
|
||||
```javascript
|
||||
// utils/request/index.js:13-14
|
||||
config.baseURL = 'http://192.168.1.2:4001'; // 开发环境
|
||||
config.baseURL = 'https://pm.ccttiot.com/prod-api'; // 生产环境
|
||||
```
|
||||
- ❌ 环境配置未分离,需要手动注释切换
|
||||
- ❌ 应该使用环境变量或配置文件
|
||||
|
||||
2. **URL构建方式不统一**
|
||||
```javascript
|
||||
// api/customer.js - 手动拼接URL
|
||||
const queryString = queryParams.join('&');
|
||||
const url = `bst/customer/list${queryString ? `?${queryString}` : ''}`;
|
||||
|
||||
// api/task.js - 也是手动拼接
|
||||
queryParams.push(`pageNum=${params.pageNum}`);
|
||||
```
|
||||
- ❌ 应该使用URLSearchParams或统一工具函数
|
||||
- ❌ 容易出错,维护困难
|
||||
|
||||
3. **错误处理不一致**
|
||||
```javascript
|
||||
// 有些API使用 custom.catch: true
|
||||
// 有些使用默认的Promise pending
|
||||
// 导致错误处理方式不统一
|
||||
```
|
||||
|
||||
**🟡 中等问题**
|
||||
|
||||
4. **调试代码未清理**
|
||||
```javascript
|
||||
// api/customer.js:25,55
|
||||
console.log('api params:', params);
|
||||
console.log('最终请求URL:', url);
|
||||
```
|
||||
|
||||
5. **缺少请求重试机制**
|
||||
- 网络错误时无自动重试
|
||||
- 无请求去重机制
|
||||
|
||||
### 2. 请求拦截器 (`utils/request/index.js`)
|
||||
|
||||
#### 优点
|
||||
- ✅ 统一的token管理
|
||||
- ✅ 网络状态检测
|
||||
- ✅ 401/403统一处理
|
||||
|
||||
#### 存在的问题
|
||||
|
||||
**🔴 严重问题**
|
||||
|
||||
1. **Promise pending导致内存泄漏风险**
|
||||
```javascript
|
||||
// 第112行
|
||||
return new Promise(() => { }) // 永远pending的Promise
|
||||
```
|
||||
- ❌ 可能导致内存泄漏
|
||||
- ❌ 无法正确追踪错误
|
||||
|
||||
2. **重复的登录跳转逻辑**
|
||||
```javascript
|
||||
// 响应拦截和错误拦截都有401处理
|
||||
// 代码重复,维护困难
|
||||
```
|
||||
|
||||
3. **网络检测逻辑重复**
|
||||
- 请求拦截和错误拦截都检测网络
|
||||
- 可以提取为公共函数
|
||||
|
||||
**🟡 中等问题**
|
||||
|
||||
4. **错误提示不够友好**
|
||||
```javascript
|
||||
uni.$uv.toast('请求失败') // 过于简单
|
||||
```
|
||||
|
||||
5. **缺少请求超时处理**
|
||||
- 无超时配置
|
||||
- 无超时重试
|
||||
|
||||
### 3. 状态管理 (`store/`)
|
||||
|
||||
#### 优点
|
||||
- ✅ 使用Pinia,符合Vue 3最佳实践
|
||||
- ✅ 模块化设计清晰
|
||||
- ✅ 支持持久化(localStorage)
|
||||
|
||||
#### 存在的问题
|
||||
|
||||
**🟡 中等问题**
|
||||
|
||||
1. **Store职责不清晰**
|
||||
```javascript
|
||||
// store/task.js - 只存储taskDetailData
|
||||
// 但很多页面直接调用API,不使用Store
|
||||
```
|
||||
|
||||
2. **缺少数据缓存策略**
|
||||
- 无缓存过期机制
|
||||
- 无数据同步策略
|
||||
|
||||
3. **权限数据格式不统一**
|
||||
```javascript
|
||||
// 需要处理数组、对象、字符串多种格式
|
||||
// 应该在API层统一转换
|
||||
```
|
||||
|
||||
### 4. 组件层 (`components/`)
|
||||
|
||||
#### 优点
|
||||
- ✅ 按业务模块组织
|
||||
- ✅ 组件职责相对清晰
|
||||
|
||||
#### 存在的问题
|
||||
|
||||
**🟡 中等问题**
|
||||
|
||||
1. **组件复用性不足**
|
||||
- 很多业务逻辑写在页面中
|
||||
- 缺少通用业务组件
|
||||
|
||||
2. **样式管理混乱**
|
||||
- 部分使用scoped,部分未使用
|
||||
- 缺少统一的样式变量
|
||||
|
||||
### 5. 工具层 (`utils/`)
|
||||
|
||||
#### 优点
|
||||
- ✅ 权限工具函数已提取(`utils/permission.js`)
|
||||
- ✅ 请求封装统一
|
||||
|
||||
#### 存在的问题
|
||||
|
||||
**🟡 中等问题**
|
||||
|
||||
1. **工具函数分散**
|
||||
- `textSolve/` 目录下只有少量文件
|
||||
- 可以考虑合并到主utils目录
|
||||
|
||||
2. **缺少常用工具函数**
|
||||
- 无日期格式化工具(多处重复实现)
|
||||
- 无数据验证工具
|
||||
- 无防抖节流工具
|
||||
|
||||
### 6. 指令系统 (`directives/`)
|
||||
|
||||
#### 优点
|
||||
- ✅ `v-permission` 指令设计良好
|
||||
- ✅ 支持Vue 2和Vue 3
|
||||
- ✅ 文档完善
|
||||
|
||||
#### 存在的问题
|
||||
|
||||
**🟢 轻微问题**
|
||||
|
||||
1. **Pinia实例获取逻辑复杂**
|
||||
```javascript
|
||||
// 尝试多个路径获取Pinia实例
|
||||
// 可以简化
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐛 代码质量问题
|
||||
|
||||
### 1. 调试代码未清理
|
||||
|
||||
**位置**: 多处
|
||||
```javascript
|
||||
// api/customer.js
|
||||
console.log('api params:', params);
|
||||
console.log('最终请求URL:', url);
|
||||
|
||||
// pages/task/detail/index.vue
|
||||
console.log('任务详情数据:', res);
|
||||
|
||||
// composables/usePagination.js
|
||||
console.log(`获取数据成功: 第${queryParams.value.pageNum}页`);
|
||||
```
|
||||
|
||||
**影响**:
|
||||
- 生产环境性能影响
|
||||
- 可能泄露敏感信息
|
||||
|
||||
**建议**:
|
||||
- 使用环境变量控制日志输出
|
||||
- 或使用日志工具(如winston)
|
||||
|
||||
### 2. 硬编码值
|
||||
|
||||
**位置**:
|
||||
- `App.vue:15` - 测试token硬编码
|
||||
- `utils/request/index.js:13-14` - baseURL硬编码
|
||||
|
||||
**建议**:
|
||||
- 使用环境变量或配置文件
|
||||
|
||||
### 3. 错误处理不完善
|
||||
|
||||
**问题**:
|
||||
- 很多地方只有 `console.error`,无用户提示
|
||||
- 错误信息不够详细
|
||||
- 缺少错误上报机制
|
||||
|
||||
### 4. 代码重复
|
||||
|
||||
**示例**:
|
||||
- 日期格式化函数在多处重复实现
|
||||
- 权限判断逻辑在多个页面重复
|
||||
- 网络检测逻辑重复
|
||||
|
||||
---
|
||||
|
||||
## 🔒 安全性问题
|
||||
|
||||
### 1. Token存储
|
||||
- ✅ 使用uni.getStorageSync存储(相对安全)
|
||||
- ⚠️ 但无token加密
|
||||
- ⚠️ 无token刷新机制
|
||||
|
||||
### 2. 敏感信息
|
||||
- ❌ `App.vue` 中硬编码测试token
|
||||
- ❌ 生产环境可能泄露
|
||||
|
||||
### 3. 权限校验
|
||||
- ✅ 前端有权限指令
|
||||
- ⚠️ 但后端校验是最终保障(前端校验可被绕过)
|
||||
|
||||
---
|
||||
|
||||
## 📊 性能问题
|
||||
|
||||
### 1. 图片处理
|
||||
- ⚠️ 无图片懒加载
|
||||
- ⚠️ 无图片压缩
|
||||
- ⚠️ 无CDN配置
|
||||
|
||||
### 2. 数据加载
|
||||
- ⚠️ 部分页面无加载状态
|
||||
- ⚠️ 无数据预加载
|
||||
- ⚠️ 无虚拟列表(长列表性能问题)
|
||||
|
||||
### 3. 打包优化
|
||||
- ⚠️ 无代码分割
|
||||
- ⚠️ 无Tree Shaking配置检查
|
||||
|
||||
---
|
||||
|
||||
## 🧪 测试与质量保证
|
||||
|
||||
### ❌ 严重缺失
|
||||
|
||||
1. **无单元测试**
|
||||
- 无测试文件(.test.js, .spec.js)
|
||||
- 无测试框架配置
|
||||
|
||||
2. **无集成测试**
|
||||
- 无E2E测试
|
||||
- 无API测试
|
||||
|
||||
3. **无代码检查工具**
|
||||
- 无ESLint配置
|
||||
- 无Prettier配置
|
||||
- 无Git hooks
|
||||
|
||||
4. **无CI/CD**
|
||||
- 无自动化构建
|
||||
- 无自动化部署
|
||||
|
||||
---
|
||||
|
||||
## 📝 文档问题
|
||||
|
||||
### ❌ 缺失的文档
|
||||
|
||||
1. **API文档**
|
||||
- 无接口文档
|
||||
- 无参数说明
|
||||
|
||||
2. **组件文档**
|
||||
- 无组件使用说明
|
||||
- 无Props文档
|
||||
|
||||
3. **开发文档**
|
||||
- 无开发规范
|
||||
- 无部署文档
|
||||
- 无环境配置说明
|
||||
|
||||
---
|
||||
|
||||
## 🎯 改进建议
|
||||
|
||||
### 高优先级(立即修复)
|
||||
|
||||
1. **环境配置分离**
|
||||
```javascript
|
||||
// config/env.js
|
||||
export const config = {
|
||||
dev: { baseURL: 'http://192.168.1.2:4001' },
|
||||
prod: { baseURL: 'https://pm.ccttiot.com/prod-api' }
|
||||
}
|
||||
```
|
||||
|
||||
2. **修复Promise pending问题**
|
||||
```javascript
|
||||
// 应该返回rejected Promise或统一错误格式
|
||||
return Promise.reject(new Error(data.message || '请求失败'))
|
||||
```
|
||||
|
||||
3. **清理调试代码**
|
||||
- 移除所有console.log
|
||||
- 移除硬编码的测试token
|
||||
|
||||
4. **统一URL构建**
|
||||
```javascript
|
||||
// utils/request/buildUrl.js
|
||||
export function buildUrl(path, params) {
|
||||
const url = new URL(path, baseURL)
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
if (value != null) url.searchParams.append(key, value)
|
||||
})
|
||||
return url.toString()
|
||||
}
|
||||
```
|
||||
|
||||
### 中优先级(近期改进)
|
||||
|
||||
1. **添加错误处理工具**
|
||||
```javascript
|
||||
// utils/errorHandler.js
|
||||
export function handleError(error, showToast = true) {
|
||||
// 统一错误处理逻辑
|
||||
}
|
||||
```
|
||||
|
||||
2. **添加常用工具函数库**
|
||||
- 日期格式化
|
||||
- 数据验证
|
||||
- 防抖节流
|
||||
|
||||
3. **完善Store设计**
|
||||
- 统一数据缓存策略
|
||||
- 添加数据同步机制
|
||||
|
||||
4. **添加代码检查工具**
|
||||
- ESLint配置
|
||||
- Prettier配置
|
||||
- Git hooks
|
||||
|
||||
### 低优先级(长期优化)
|
||||
|
||||
1. **引入TypeScript**
|
||||
- 类型安全
|
||||
- 更好的IDE支持
|
||||
|
||||
2. **添加单元测试**
|
||||
- Jest配置
|
||||
- 核心功能测试
|
||||
|
||||
3. **性能优化**
|
||||
- 图片懒加载
|
||||
- 代码分割
|
||||
- 虚拟列表
|
||||
|
||||
4. **完善文档**
|
||||
- API文档
|
||||
- 组件文档
|
||||
- 开发规范
|
||||
|
||||
---
|
||||
|
||||
## 📈 架构评分
|
||||
|
||||
| 维度 | 评分 | 说明 |
|
||||
|------|------|------|
|
||||
| **代码组织** | ⭐⭐⭐⭐ | 模块化清晰,但缺少类型定义 |
|
||||
| **错误处理** | ⭐⭐ | 有基础处理,但不完善 |
|
||||
| **安全性** | ⭐⭐⭐ | 基础安全措施,但有硬编码问题 |
|
||||
| **性能** | ⭐⭐⭐ | 基础优化,但缺少高级优化 |
|
||||
| **可维护性** | ⭐⭐⭐ | 结构清晰,但代码重复较多 |
|
||||
| **可测试性** | ⭐ | 无测试,难以保证质量 |
|
||||
| **文档** | ⭐⭐ | 缺少完整文档 |
|
||||
|
||||
**总体评分**: ⭐⭐⭐ (3/5)
|
||||
|
||||
---
|
||||
|
||||
## 🎓 总结
|
||||
|
||||
### 优点
|
||||
1. ✅ 项目结构清晰,模块化设计良好
|
||||
2. ✅ 使用现代技术栈(Vue 3 + Pinia)
|
||||
3. ✅ 权限系统设计合理
|
||||
4. ✅ 请求拦截器功能完善
|
||||
|
||||
### 主要问题
|
||||
1. ❌ 环境配置硬编码
|
||||
2. ❌ 缺少测试和质量保证
|
||||
3. ❌ 代码重复和调试代码未清理
|
||||
4. ❌ 错误处理不完善
|
||||
5. ❌ 文档缺失
|
||||
|
||||
### 建议
|
||||
优先解决高优先级问题(环境配置、Promise pending、调试代码),然后逐步完善测试、文档和性能优化。建议引入代码检查工具和CI/CD流程,提升代码质量和开发效率。
|
||||
|
||||
---
|
||||
|
||||
**报告生成时间**: 2024年
|
||||
**分析工具**: 代码审查 + 架构分析
|
||||
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
/**
|
||||
* 通用 API(工具类接口)
|
||||
*/
|
||||
import { buildUrl } from '@/utils/url'
|
||||
|
||||
/**
|
||||
* 获取七牛云上传token
|
||||
|
|
@ -44,19 +45,8 @@ export const getDictDataList = (params = {}) => {
|
|||
// 合并参数
|
||||
const requestParams = { ...defaultParams, ...params };
|
||||
|
||||
// 构建查询参数数组(兼容uniapp)
|
||||
const queryParams = [];
|
||||
|
||||
Object.entries(requestParams).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
queryParams.push(`${key}=${encodeURIComponent(value.toString())}`);
|
||||
}
|
||||
});
|
||||
|
||||
const queryString = queryParams.join('&');
|
||||
const url = `/system/dict/data/list${queryString ? `?${queryString}` : ''}`;
|
||||
|
||||
console.log('请求URL:', url); // 调试用
|
||||
// 使用统一URL构建工具
|
||||
const url = buildUrl('/system/dict/data/list', requestParams);
|
||||
|
||||
return uni.$uv.http.get(url, {
|
||||
custom: {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
* 客户相关 API
|
||||
*/
|
||||
|
||||
import { buildUrl, mergeParams } from '@/utils/url'
|
||||
|
||||
/**
|
||||
* 获取客户列表
|
||||
* @param {Object} params 请求参数(可选)
|
||||
|
|
@ -20,12 +22,7 @@
|
|||
* @param {string[]} params.saleList 销售列表
|
||||
* @returns {Promise} 返回客户列表 { total: number, rows: array }
|
||||
*/
|
||||
// 最终推荐版本(添加默认排序参数)
|
||||
export const getCustomerList = (params = {}) => {
|
||||
console.log('api params:', params);
|
||||
|
||||
const queryParams = [];
|
||||
|
||||
// 添加默认排序参数
|
||||
const defaultParams = {
|
||||
orderByColumn: 'nextFollowTime',
|
||||
|
|
@ -33,26 +30,10 @@ export const getCustomerList = (params = {}) => {
|
|||
};
|
||||
|
||||
// 合并参数:默认参数 + 用户传入参数(用户参数优先)
|
||||
const finalParams = { ...defaultParams, ...params };
|
||||
const finalParams = mergeParams(defaultParams, params);
|
||||
|
||||
Object.entries(finalParams).forEach(([key, value]) => {
|
||||
if (value == null) return; // 过滤 null 和 undefined
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach(item => {
|
||||
if (item != null) {
|
||||
queryParams.push(`${encodeURIComponent(key)}=${encodeURIComponent(item.toString())}`);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
queryParams.push(`${encodeURIComponent(key)}=${encodeURIComponent(value.toString())}`);
|
||||
}
|
||||
});
|
||||
|
||||
const queryString = queryParams.join('&');
|
||||
const url = `bst/customer/list${queryString ? `?${queryString}` : ''}`;
|
||||
|
||||
console.log('最终请求URL:', url);
|
||||
// 使用统一URL构建工具
|
||||
const url = buildUrl('bst/customer/list', finalParams);
|
||||
|
||||
return uni.$uv.http.get(url, { custom: { auth: true } });
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
/**
|
||||
* 仪表板相关 API
|
||||
*/
|
||||
import { buildUrl } from '@/utils/url'
|
||||
|
||||
/**
|
||||
* 获取仪表板简要信息
|
||||
|
|
@ -10,20 +11,17 @@
|
|||
* @returns {Promise} 返回仪表板简要信息
|
||||
*/
|
||||
export const getDashboardBrief = ({ joinUserId, keys }) => {
|
||||
// 构建查询参数字符串
|
||||
let params=[]
|
||||
const params = {};
|
||||
if (joinUserId) {
|
||||
params = [`joinUserId=${joinUserId}`];
|
||||
params.joinUserId = joinUserId;
|
||||
}
|
||||
|
||||
if (keys && Array.isArray(keys)) {
|
||||
keys.forEach((key) => {
|
||||
params.push(`keys=${encodeURIComponent(key)}`);
|
||||
});
|
||||
params.keys = keys;
|
||||
}
|
||||
const queryString = params.join('&');
|
||||
|
||||
return uni.$uv.http.get(`dashboard/brief?${queryString}`, {
|
||||
const url = buildUrl('dashboard/brief', params);
|
||||
|
||||
return uni.$uv.http.get(url, {
|
||||
custom: {
|
||||
auth: true // 启用 token 认证
|
||||
}
|
||||
|
|
@ -37,12 +35,11 @@ export const getDashboardBrief = ({ joinUserId, keys }) => {
|
|||
export const getCustomerStatistics = (params = {}) => {
|
||||
// 期望传入为数组: [start, end] 或 [day, day]
|
||||
const dateRange = Array.isArray(params) ? params : [];
|
||||
const query =
|
||||
dateRange.length > 0
|
||||
? `?${dateRange.map(d => `queryDateRange=${encodeURIComponent(d)}`).join('&')}`
|
||||
: '';
|
||||
const queryParams = dateRange.length > 0 ? { queryDateRange: dateRange } : {};
|
||||
|
||||
const url = buildUrl('/dashboard/customer/statistics', queryParams);
|
||||
|
||||
return uni.$uv.http.get(`/dashboard/customer/statistics${query}`, {
|
||||
return uni.$uv.http.get(url, {
|
||||
custom: {
|
||||
auth: true // 启用 token 认证
|
||||
}
|
||||
|
|
@ -63,36 +60,9 @@ export const getCustomerStatistics = (params = {}) => {
|
|||
* @returns {Promise} 返回公告列表
|
||||
*/
|
||||
export const getNoticeList = (params = {}) => {
|
||||
const queryParams = [];
|
||||
const url = buildUrl('bst/notice/list', params);
|
||||
|
||||
if (params.pageNum !== undefined) {
|
||||
queryParams.push(`pageNum=${params.pageNum}`);
|
||||
}
|
||||
if (params.pageSize !== undefined) {
|
||||
queryParams.push(`pageSize=${params.pageSize}`);
|
||||
}
|
||||
if (params.title) {
|
||||
queryParams.push(`title=${encodeURIComponent(params.title)}`);
|
||||
}
|
||||
if (params.userName) {
|
||||
queryParams.push(`userName=${encodeURIComponent(params.userName)}`);
|
||||
}
|
||||
if (params.level) {
|
||||
queryParams.push(`level=${encodeURIComponent(params.level)}`);
|
||||
}
|
||||
if (params.top !== undefined && params.top !== null && params.top !== '') {
|
||||
queryParams.push(`top=${params.top}`);
|
||||
}
|
||||
if (params.orderByColumn) {
|
||||
queryParams.push(`orderByColumn=${encodeURIComponent(params.orderByColumn)}`);
|
||||
}
|
||||
if (params.isAsc) {
|
||||
queryParams.push(`isAsc=${encodeURIComponent(params.isAsc)}`);
|
||||
}
|
||||
|
||||
const queryString = queryParams.length > 0 ? `?${queryParams.join('&')}` : '';
|
||||
|
||||
return uni.$uv.http.get(`bst/notice/list${queryString}`, {
|
||||
return uni.$uv.http.get(url, {
|
||||
custom: {
|
||||
auth: true // 启用 token 认证
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
/**
|
||||
* 项目相关 API
|
||||
*/
|
||||
import { buildUrl } from '@/utils/url'
|
||||
|
||||
/**
|
||||
* 获取项目列表
|
||||
|
|
@ -16,76 +17,31 @@
|
|||
* @returns {Promise} 返回项目列表
|
||||
*/
|
||||
export const getProjectList = (params = {}) => {
|
||||
const queryParams = [];
|
||||
// 处理特殊字段映射
|
||||
const mappedParams = { ...params };
|
||||
|
||||
// 分页参数
|
||||
if (params.pageNum !== undefined) {
|
||||
queryParams.push(`pageNum=${params.pageNum}`);
|
||||
}
|
||||
if (params.pageSize !== undefined) {
|
||||
queryParams.push(`pageSize=${params.pageSize}`);
|
||||
// 将overdue映射为devOverdue
|
||||
if (mappedParams.overdue !== undefined) {
|
||||
mappedParams.devOverdue = mappedParams.overdue;
|
||||
delete mappedParams.overdue;
|
||||
}
|
||||
|
||||
// 排序参数
|
||||
if (params.orderByColumn !== undefined && params.orderByColumn !== '') {
|
||||
queryParams.push(`orderByColumn=${encodeURIComponent(params.orderByColumn)}`);
|
||||
}
|
||||
if (params.isAsc !== undefined && params.isAsc !== '') {
|
||||
queryParams.push(`isAsc=${encodeURIComponent(params.isAsc)}`);
|
||||
// 将projectName映射为name
|
||||
if (mappedParams.projectName !== undefined) {
|
||||
mappedParams.name = mappedParams.projectName;
|
||||
delete mappedParams.projectName;
|
||||
}
|
||||
|
||||
// 状态列表
|
||||
if (params.statusList !== undefined && Array.isArray(params.statusList) && params.statusList.length > 0) {
|
||||
params.statusList.forEach((status, index) => {
|
||||
queryParams.push(`statusList=${status}`);
|
||||
});
|
||||
}
|
||||
|
||||
// 是否曾经逾期
|
||||
if (params.overdue !== undefined && params.overdue !== '') {
|
||||
queryParams.push(`devOverdue=${params.overdue}`);
|
||||
}
|
||||
|
||||
// 创建人ID
|
||||
if (params.createId !== undefined && params.createId !== '') {
|
||||
queryParams.push(`createId=${params.createId}`);
|
||||
// 将projectId映射为no(用于搜索)
|
||||
if (mappedParams.projectId !== undefined && !mappedParams.no) {
|
||||
mappedParams.no = mappedParams.projectId;
|
||||
// 注意:这里不删除projectId,因为可能还有其他用途
|
||||
}
|
||||
|
||||
// 负责人ID
|
||||
if (params.ownerId !== undefined && params.ownerId !== '') {
|
||||
queryParams.push(`ownerId=${params.ownerId}`);
|
||||
}
|
||||
// 使用统一URL构建工具,keys数组使用bracket格式
|
||||
const url = buildUrl('bst/project/list', mappedParams);
|
||||
|
||||
// 搜索关键词
|
||||
if (params.keys !== undefined && Array.isArray(params.keys) && params.keys.length > 0) {
|
||||
params.keys.forEach((key, index) => {
|
||||
queryParams.push(`keys[${index}]=${encodeURIComponent(key)}`);
|
||||
});
|
||||
}
|
||||
|
||||
// 项目名称搜索
|
||||
if (params.projectName !== undefined && params.projectName !== '') {
|
||||
queryParams.push(`name=${encodeURIComponent(params.projectName)}`);
|
||||
}
|
||||
|
||||
// 项目编号搜索
|
||||
if (params.projectId !== undefined && params.projectId !== '') {
|
||||
queryParams.push(`no=${encodeURIComponent(params.projectId)}`);
|
||||
}
|
||||
|
||||
// 客户名称搜索
|
||||
if (params.customerName !== undefined && params.customerName !== '') {
|
||||
queryParams.push(`customerName=${encodeURIComponent(params.customerName)}`);
|
||||
}
|
||||
|
||||
// 成员ID搜索
|
||||
if (params.joinUserId !== undefined && params.joinUserId !== '') {
|
||||
queryParams.push(`joinUserId=${params.joinUserId}`);
|
||||
}
|
||||
|
||||
const queryString = queryParams.length > 0 ? `?${queryParams.join('&')}` : '';
|
||||
|
||||
return uni.$uv.http.get(`bst/project/list${queryString}`, {
|
||||
return uni.$uv.http.get(url, {
|
||||
custom: {
|
||||
auth: true // 启用 token 认证
|
||||
}
|
||||
|
|
|
|||
84
api/task.js
84
api/task.js
|
|
@ -1,6 +1,7 @@
|
|||
/**
|
||||
* 任务相关 API
|
||||
*/
|
||||
import { buildUrl } from '@/utils/url'
|
||||
|
||||
/**
|
||||
* 获取任务列表
|
||||
|
|
@ -23,87 +24,10 @@
|
|||
* @returns {Promise} 返回任务列表
|
||||
*/
|
||||
export const getTaskList = (params = {}) => {
|
||||
const queryParams = [];
|
||||
// 使用统一URL构建工具
|
||||
const url = buildUrl('bst/task/list', params);
|
||||
|
||||
// 分页参数
|
||||
if (params.pageNum !== undefined) {
|
||||
queryParams.push(`pageNum=${params.pageNum}`);
|
||||
}
|
||||
if (params.pageSize !== undefined) {
|
||||
queryParams.push(`pageSize=${params.pageSize}`);
|
||||
}
|
||||
|
||||
// 排序参数
|
||||
if (params.orderByColumn !== undefined && params.orderByColumn !== '') {
|
||||
queryParams.push(`orderByColumn=${encodeURIComponent(params.orderByColumn)}`);
|
||||
}
|
||||
if (params.isAsc !== undefined && params.isAsc !== '') {
|
||||
queryParams.push(`isAsc=${encodeURIComponent(params.isAsc)}`);
|
||||
}
|
||||
|
||||
// 项目ID
|
||||
if (params.projectId !== undefined && params.projectId !== '') {
|
||||
queryParams.push(`projectId=${params.projectId}`);
|
||||
}
|
||||
|
||||
// 任务类型
|
||||
if (params.type !== undefined && params.type !== '') {
|
||||
queryParams.push(`type=${params.type}`);
|
||||
}
|
||||
|
||||
// 优先级
|
||||
if (params.level !== undefined && params.level !== '') {
|
||||
queryParams.push(`level=${params.level}`);
|
||||
}
|
||||
|
||||
// 创建人ID
|
||||
if (params.createId !== undefined && params.createId !== '') {
|
||||
queryParams.push(`createId=${params.createId}`);
|
||||
}
|
||||
|
||||
// 负责人ID
|
||||
if (params.ownerId !== undefined && params.ownerId !== '') {
|
||||
queryParams.push(`ownerId=${params.ownerId}`);
|
||||
}
|
||||
|
||||
// 状态列表
|
||||
if (params.statusList !== undefined && Array.isArray(params.statusList) && params.statusList.length > 0) {
|
||||
// 将数组转换为重复参数格式,例如 [1,2] => "statusList=1&statusList=2"
|
||||
params.statusList.forEach(status => {
|
||||
queryParams.push(`statusList=${status}`);
|
||||
});
|
||||
}
|
||||
|
||||
// 任务状态(单个)
|
||||
if (params.status !== undefined && params.status !== '') {
|
||||
queryParams.push(`status=${params.status}`);
|
||||
}
|
||||
|
||||
// 是否逾期
|
||||
if (params.overdue !== undefined) {
|
||||
queryParams.push(`overdue=${params.overdue}`);
|
||||
}
|
||||
|
||||
// 过期时间范围
|
||||
if (params.expireTimeStart !== undefined && params.expireTimeStart !== null && params.expireTimeStart !== '') {
|
||||
queryParams.push(`expireTimeStart=${encodeURIComponent(params.expireTimeStart)}`);
|
||||
}
|
||||
if (params.expireTimeEnd !== undefined && params.expireTimeEnd !== null && params.expireTimeEnd !== '') {
|
||||
queryParams.push(`expireTimeEnd=${encodeURIComponent(params.expireTimeEnd)}`);
|
||||
}
|
||||
|
||||
// 通过日期范围
|
||||
if (params.passDateRange !== undefined && Array.isArray(params.passDateRange) && params.passDateRange.length > 0) {
|
||||
params.passDateRange.forEach((date, index) => {
|
||||
if (date) {
|
||||
queryParams.push(`passDateRange=${encodeURIComponent(date)}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const queryString = queryParams.length > 0 ? `?${queryParams.join('&')}` : '';
|
||||
|
||||
return uni.$uv.http.get(`bst/task/list${queryString}`, {
|
||||
return uni.$uv.http.get(url, {
|
||||
custom: {
|
||||
auth: true // 启用 token 认证
|
||||
}
|
||||
|
|
|
|||
19
api/user.js
19
api/user.js
|
|
@ -1,6 +1,7 @@
|
|||
/**
|
||||
* 用户相关 API
|
||||
*/
|
||||
import { buildUrl, mergeParams } from '@/utils/url'
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
|
|
@ -82,19 +83,19 @@ export const getUserListAll = () => {
|
|||
* @returns {Promise<Object>} 用户列表
|
||||
*/
|
||||
export const getUserList = (params = {}) => {
|
||||
// 合并参数并过滤空值
|
||||
const requestParams = Object.entries({
|
||||
// 设置默认参数
|
||||
const defaultParams = {
|
||||
pageNum: 1,
|
||||
pageSize: 100,
|
||||
status: 0,
|
||||
delFlag: 0,
|
||||
...params
|
||||
})
|
||||
.filter(([_, value]) => value !== undefined && value !== null && value !== '')
|
||||
.map(([key, value]) => `${key}=${encodeURIComponent(value.toString())}`)
|
||||
.join('&');
|
||||
delFlag: 0
|
||||
};
|
||||
|
||||
const url = `/system/user/list${requestParams ? `?${requestParams}` : ''}`;
|
||||
// 合并参数
|
||||
const requestParams = mergeParams(defaultParams, params);
|
||||
|
||||
// 使用统一URL构建工具
|
||||
const url = buildUrl('/system/user/list', requestParams);
|
||||
|
||||
return uni.$uv.http.get(url, {
|
||||
custom: { auth: true }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
/**
|
||||
* 将参数对象中的数组转换为查询字符串
|
||||
* @deprecated 请使用 @/utils/url 中的 buildUrl 函数
|
||||
* @param {Object} params - 参数对象
|
||||
* @returns {Object} { params: 处理后的参数对象, queryString: 查询字符串 }
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import {convertArrayParamsToQuery} from "@/api/utils";
|
||||
import { buildUrl, mergeParams } from '@/utils/url'
|
||||
|
||||
// 审批相关 API
|
||||
|
||||
/**
|
||||
|
|
@ -7,23 +8,22 @@ import {convertArrayParamsToQuery} from "@/api/utils";
|
|||
* @returns {Promise} 返回审核列表
|
||||
*/
|
||||
export const getVerifyList = (params = {}) => {
|
||||
// 使用通用函数处理数组参数
|
||||
const { params: processedParams, queryString } = convertArrayParamsToQuery(params);
|
||||
// 设置默认参数
|
||||
const defaultParams = {
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
orderByColumn: 'createTime',
|
||||
isAsc: 'descending',
|
||||
bstType: 'UPDATE_TASK'
|
||||
};
|
||||
|
||||
// 构建请求 URL(去掉查询字符串开头的 &)
|
||||
const url = queryString
|
||||
? `/bst/verify/list?${queryString}`
|
||||
: '/bst/verify/list';
|
||||
// 合并参数
|
||||
const requestParams = mergeParams(defaultParams, params);
|
||||
|
||||
// 使用统一URL构建工具
|
||||
const url = buildUrl('/bst/verify/list', requestParams);
|
||||
|
||||
return uni.$uv.http.get(url, {
|
||||
params: {
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
orderByColumn: 'createTime',
|
||||
isAsc: 'descending',
|
||||
bstType: 'UPDATE_TASK',
|
||||
...processedParams
|
||||
},
|
||||
custom: {
|
||||
auth: true
|
||||
}
|
||||
|
|
|
|||
165
utils/url.js
Normal file
165
utils/url.js
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
/**
|
||||
* URL构建工具
|
||||
* 统一处理查询参数的构建,支持数组、对象、特殊字符编码等
|
||||
*/
|
||||
|
||||
/**
|
||||
* 构建带查询参数的URL
|
||||
* @param {string} basePath - 基础路径(如 'bst/task/list')
|
||||
* @param {Object} params - 查询参数对象
|
||||
* @param {Object} options - 配置选项
|
||||
* @param {boolean} options.encodeValues - 是否对值进行URL编码(默认true)
|
||||
* @param {boolean} options.skipEmpty - 是否跳过空值(默认true)
|
||||
* @param {string} options.arrayFormat - 数组格式:'repeat'(重复参数)或 'bracket'(带索引),默认'repeat'
|
||||
* @returns {string} 完整的URL字符串
|
||||
*
|
||||
* @example
|
||||
* // 基础用法
|
||||
* buildUrl('bst/task/list', { pageNum: 1, pageSize: 10 })
|
||||
* // => 'bst/task/list?pageNum=1&pageSize=10'
|
||||
*
|
||||
* @example
|
||||
* // 数组参数(重复格式)
|
||||
* buildUrl('bst/task/list', { statusList: [1, 2, 3] })
|
||||
* // => 'bst/task/list?statusList=1&statusList=2&statusList=3'
|
||||
*
|
||||
* @example
|
||||
* // 数组参数(带索引格式)
|
||||
* buildUrl('bst/project/list', { keys: ['key1', 'key2'] }, { arrayFormat: 'bracket' })
|
||||
* // => 'bst/project/list?keys[0]=key1&keys[1]=key2'
|
||||
*
|
||||
* @example
|
||||
* // 包含特殊字符
|
||||
* buildUrl('bst/task/list', { orderByColumn: 'create time', isAsc: 'ascending' })
|
||||
* // => 'bst/task/list?orderByColumn=create%20time&isAsc=ascending'
|
||||
*/
|
||||
export function buildUrl(basePath, params = {}, options = {}) {
|
||||
const {
|
||||
encodeValues = true,
|
||||
skipEmpty = true,
|
||||
arrayFormat = 'repeat' // 'repeat' | 'bracket'
|
||||
} = options
|
||||
|
||||
// 如果没有参数,直接返回基础路径
|
||||
if (!params || Object.keys(params).length === 0) {
|
||||
return basePath
|
||||
}
|
||||
|
||||
const queryParams = []
|
||||
|
||||
/**
|
||||
* 编码值
|
||||
* @param {any} value - 要编码的值
|
||||
* @returns {string} 编码后的字符串
|
||||
*/
|
||||
const encodeValue = (value) => {
|
||||
if (encodeValues) {
|
||||
return encodeURIComponent(String(value))
|
||||
}
|
||||
return String(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加查询参数
|
||||
* @param {string} key - 参数名
|
||||
* @param {any} value - 参数值
|
||||
*/
|
||||
const addParam = (key, value) => {
|
||||
// 跳过空值
|
||||
if (skipEmpty && (value === null || value === undefined || value === '')) {
|
||||
return
|
||||
}
|
||||
|
||||
const encodedKey = encodeURIComponent(key)
|
||||
|
||||
// 处理数组
|
||||
if (Array.isArray(value)) {
|
||||
if (value.length === 0) return
|
||||
|
||||
value.forEach((item, index) => {
|
||||
// 跳过数组中的空值
|
||||
if (skipEmpty && (item === null || item === undefined || item === '')) {
|
||||
return
|
||||
}
|
||||
|
||||
if (arrayFormat === 'bracket') {
|
||||
// 带索引格式:keys[0]=value1
|
||||
queryParams.push(`${encodedKey}[${index}]=${encodeValue(item)}`)
|
||||
} else {
|
||||
// 重复格式:key=value1&key=value2
|
||||
queryParams.push(`${encodedKey}=${encodeValue(item)}`)
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 处理对象(转换为JSON字符串)
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
queryParams.push(`${encodedKey}=${encodeValue(JSON.stringify(value))}`)
|
||||
return
|
||||
}
|
||||
|
||||
// 处理布尔值
|
||||
if (typeof value === 'boolean') {
|
||||
queryParams.push(`${encodedKey}=${value}`)
|
||||
return
|
||||
}
|
||||
|
||||
// 处理普通值
|
||||
queryParams.push(`${encodedKey}=${encodeValue(value)}`)
|
||||
}
|
||||
|
||||
// 遍历所有参数
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
addParam(key, value)
|
||||
})
|
||||
|
||||
// 构建查询字符串
|
||||
const queryString = queryParams.length > 0 ? `?${queryParams.join('&')}` : ''
|
||||
|
||||
return `${basePath}${queryString}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并默认参数和用户参数
|
||||
* @param {Object} defaultParams - 默认参数
|
||||
* @param {Object} userParams - 用户传入的参数
|
||||
* @returns {Object} 合并后的参数对象(用户参数优先)
|
||||
*
|
||||
* @example
|
||||
* mergeParams({ pageNum: 1, pageSize: 10 }, { pageNum: 2 })
|
||||
* // => { pageNum: 2, pageSize: 10 }
|
||||
*/
|
||||
export function mergeParams(defaultParams = {}, userParams = {}) {
|
||||
return { ...defaultParams, ...userParams }
|
||||
}
|
||||
|
||||
/**
|
||||
* 从URL中提取查询参数
|
||||
* @param {string} url - 完整URL或查询字符串
|
||||
* @returns {Object} 参数对象
|
||||
*
|
||||
* @example
|
||||
* parseUrl('bst/task/list?pageNum=1&pageSize=10')
|
||||
* // => { pageNum: '1', pageSize: '10' }
|
||||
*/
|
||||
export function parseUrl(url) {
|
||||
const params = {}
|
||||
|
||||
try {
|
||||
const queryString = url.includes('?') ? url.split('?')[1] : url
|
||||
if (!queryString) return params
|
||||
|
||||
queryString.split('&').forEach(param => {
|
||||
const [key, value] = param.split('=')
|
||||
if (key) {
|
||||
params[decodeURIComponent(key)] = value ? decodeURIComponent(value) : ''
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('解析URL参数失败:', error)
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user