封装统一的url构建工具

This commit is contained in:
WindowBird 2025-11-27 10:14:55 +08:00
parent 03243d62e4
commit 34c6de002c
10 changed files with 713 additions and 246 deletions

479
ARCHITECTURE_ANALYSIS.md Normal file
View 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年
**分析工具**: 代码审查 + 架构分析

View File

@ -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: {

View File

@ -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 } });
};

View File

@ -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 认证
}

View File

@ -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 认证
}

View File

@ -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 认证
}

View File

@ -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 }

View File

@ -1,5 +1,6 @@
/**
* 将参数对象中的数组转换为查询字符串
* @deprecated 请使用 @/utils/url 中的 buildUrl 函数
* @param {Object} params - 参数对象
* @returns {Object} { params: 处理后的参数对象, queryString: 查询字符串 }
*/

View File

@ -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
View 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
}