feat: 添加Mock版本保护机制 - 基础架构完成
完成内容: ✅ 第一层保护: Git分支隔离 - 创建 v1.0-mock 标签 - 创建 feature/api-integration 分支 ✅ 第二层保护: 配置开关控制 - config/env.config.js (环境配置,支持Mock/API模式切换) ✅ 第三层保护: 代码架构分离 - utils/request.js (网络请求封装,支持Blade-Auth) - utils/dataAdapter.js (核心适配器,自动选择数据源) ✅ Mock数据模块 (4个文件): - mock/index.js (统一入口) - mock/login.js (登录Mock数据) - mock/athlete.js (选手Mock数据,含场地、项目) - mock/score.js (评分Mock数据,含扣分项、详情、修改) ✅ API接口模块 (4个文件): - api/index.js (统一入口) - api/auth.js (认证API,含后端接口规范) - api/athlete.js (选手API,含SQL示例) - api/score.js (评分API,含实现逻辑说明) 特性: - 通过修改 config/env.config.js 的 dataMode 即可切换Mock/API模式 - Mock模式: 完全离线,无需后端,UI功能完整 - API模式: 调用真实后端接口(需后端实现5个专用接口) - 零UI修改: 原有页面代码完全保护,仅替换数据源 下一步: - 修改5个页面使用 dataAdapter - 测试Mock模式功能 - 后端开发5个小程序专用接口 代码统计: - 新增11个文件 - 约1000行代码 - 完整的注释和使用说明
This commit is contained in:
257
utils/dataAdapter.js
Normal file
257
utils/dataAdapter.js
Normal file
@@ -0,0 +1,257 @@
|
||||
/**
|
||||
* 数据源适配器(核心文件)
|
||||
* 根据配置动态选择 Mock数据 或 真实API数据
|
||||
*
|
||||
* 这是保护Mock版本UI的核心机制:
|
||||
* - Mock模式:使用本地Mock数据,不依赖后端,UI功能完整
|
||||
* - API模式:调用真实后端接口,获取数据库数据
|
||||
*
|
||||
* 通过修改 config/env.config.js 中的 dataMode 即可切换模式
|
||||
*/
|
||||
|
||||
import config from '@/config/env.config.js'
|
||||
|
||||
/**
|
||||
* DataAdapter 类
|
||||
* 单例模式,全局统一管理数据源
|
||||
*/
|
||||
class DataAdapter {
|
||||
constructor() {
|
||||
this.mode = config.dataMode // 'mock' 或 'api'
|
||||
this.debug = config.debug
|
||||
this.mockDelay = config.mockDelay
|
||||
|
||||
// 延迟加载,避免循环依赖
|
||||
this.mockData = null
|
||||
this.apiService = null
|
||||
|
||||
if (this.debug) {
|
||||
console.log(`[DataAdapter] 初始化完成,当前模式: ${this.mode}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 延迟加载 Mock 数据模块
|
||||
*/
|
||||
async _loadMockData() {
|
||||
if (!this.mockData) {
|
||||
const mockModule = await import('@/mock/index.js')
|
||||
this.mockData = mockModule.default
|
||||
}
|
||||
return this.mockData
|
||||
}
|
||||
|
||||
/**
|
||||
* 延迟加载 API 服务模块
|
||||
*/
|
||||
async _loadApiService() {
|
||||
if (!this.apiService) {
|
||||
const apiModule = await import('@/api/index.js')
|
||||
this.apiService = apiModule.default
|
||||
}
|
||||
return this.apiService
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一数据获取接口
|
||||
* @param {String} resource - 资源名称(如 'login', 'getMyAthletes')
|
||||
* @param {Object} params - 请求参数
|
||||
* @returns {Promise} 返回统一格式的响应
|
||||
*/
|
||||
async getData(resource, params = {}) {
|
||||
if (this.mode === 'mock') {
|
||||
return this._getMockData(resource, params)
|
||||
} else {
|
||||
return this._getApiData(resource, params)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 Mock 数据
|
||||
* @private
|
||||
*/
|
||||
async _getMockData(resource, params) {
|
||||
if (this.debug) {
|
||||
console.log(`[Mock数据] 请求: ${resource}`, params)
|
||||
}
|
||||
|
||||
try {
|
||||
// 模拟网络延迟
|
||||
if (this.mockDelay > 0) {
|
||||
await this._delay(this.mockDelay)
|
||||
}
|
||||
|
||||
// 加载Mock数据模块
|
||||
const mockData = await this._loadMockData()
|
||||
|
||||
// 检查资源是否存在
|
||||
if (!mockData[resource]) {
|
||||
throw new Error(`Mock数据中未找到资源: ${resource}`)
|
||||
}
|
||||
|
||||
// 调用Mock数据函数
|
||||
const data = mockData[resource](params)
|
||||
|
||||
if (this.debug) {
|
||||
console.log(`[Mock数据] 响应: ${resource}`, data)
|
||||
}
|
||||
|
||||
// 返回统一格式
|
||||
return {
|
||||
code: 200,
|
||||
message: '成功',
|
||||
data,
|
||||
success: true
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`[Mock数据] 错误: ${resource}`, error)
|
||||
throw {
|
||||
code: 500,
|
||||
message: error.message || 'Mock数据获取失败',
|
||||
data: null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 API 数据
|
||||
* @private
|
||||
*/
|
||||
async _getApiData(resource, params) {
|
||||
if (this.debug) {
|
||||
console.log(`[API请求] 请求: ${resource}`, params)
|
||||
}
|
||||
|
||||
try {
|
||||
// 加载API服务模块
|
||||
const apiService = await this._loadApiService()
|
||||
|
||||
// 检查接口是否存在
|
||||
if (!apiService[resource]) {
|
||||
throw new Error(`API服务中未找到接口: ${resource}`)
|
||||
}
|
||||
|
||||
// 调用API接口
|
||||
const response = await apiService[resource](params)
|
||||
|
||||
if (this.debug) {
|
||||
console.log(`[API请求] 响应: ${resource}`, response)
|
||||
}
|
||||
|
||||
// API响应已经是统一格式(由 request.js 处理)
|
||||
return response
|
||||
} catch (error) {
|
||||
console.error(`[API请求] 错误: ${resource}`, error)
|
||||
// 重新抛出,由调用方处理
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 延迟函数(模拟网络请求)
|
||||
* @private
|
||||
*/
|
||||
_delay(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换数据模式
|
||||
* @param {String} mode - 'mock' 或 'api'
|
||||
*/
|
||||
switchMode(mode) {
|
||||
if (mode === 'mock' || mode === 'api') {
|
||||
this.mode = mode
|
||||
console.log(`[DataAdapter] 数据模式已切换为: ${mode}`)
|
||||
} else {
|
||||
console.error('[DataAdapter] 无效的数据模式,只能是 "mock" 或 "api"')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前模式
|
||||
* @returns {String} 'mock' 或 'api'
|
||||
*/
|
||||
getMode() {
|
||||
return this.mode
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为Mock模式
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
isMockMode() {
|
||||
return this.mode === 'mock'
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为API模式
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
isApiMode() {
|
||||
return this.mode === 'api'
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例
|
||||
export default new DataAdapter()
|
||||
|
||||
/**
|
||||
* 使用示例:
|
||||
*
|
||||
* // 在页面中使用
|
||||
* import dataAdapter from '@/utils/dataAdapter.js'
|
||||
*
|
||||
* export default {
|
||||
* data() {
|
||||
* return {
|
||||
* players: []
|
||||
* }
|
||||
* },
|
||||
*
|
||||
* async onLoad() {
|
||||
* try {
|
||||
* // 获取数据(自动根据配置选择Mock或API)
|
||||
* const response = await dataAdapter.getData('getMyAthletes', {
|
||||
* judgeId: 123,
|
||||
* venueId: 1,
|
||||
* projectId: 5
|
||||
* })
|
||||
*
|
||||
* this.players = response.data
|
||||
* } catch (error) {
|
||||
* console.error('数据加载失败:', error.message)
|
||||
* }
|
||||
* },
|
||||
*
|
||||
* methods: {
|
||||
* // 查看当前模式
|
||||
* checkMode() {
|
||||
* console.log('当前数据模式:', dataAdapter.getMode())
|
||||
* console.log('是否Mock模式:', dataAdapter.isMockMode())
|
||||
* },
|
||||
*
|
||||
* // 动态切换模式(开发调试用)
|
||||
* toggleMode() {
|
||||
* const newMode = dataAdapter.isMockMode() ? 'api' : 'mock'
|
||||
* dataAdapter.switchMode(newMode)
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* 资源名称(resource)与Mock/API的映射关系:
|
||||
*
|
||||
* | resource | Mock函数 | API函数 | 说明 |
|
||||
* |---------------------|----------------------|---------------------|---------------|
|
||||
* | login | mockData.login | apiService.login | 登录验证 |
|
||||
* | getMyAthletes | mockData.getMyAthletes | apiService.getMyAthletes | 选手列表(评委) |
|
||||
* | getAthletesForAdmin | mockData.getAthletesForAdmin | apiService.getAthletesForAdmin | 选手列表(裁判长) |
|
||||
* | submitScore | mockData.submitScore | apiService.submitScore | 提交评分 |
|
||||
* | getScoreDetail | mockData.getScoreDetail | apiService.getScoreDetail | 评分详情 |
|
||||
* | modifyScore | mockData.modifyScore | apiService.modifyScore | 修改评分 |
|
||||
* | getDeductions | mockData.getDeductions | apiService.getDeductions | 扣分项列表 |
|
||||
* | getVenues | mockData.getVenues | apiService.getVenues | 场地列表 |
|
||||
* | getProjects | mockData.getProjects | apiService.getProjects | 项目列表 |
|
||||
*/
|
||||
245
utils/request.js
Normal file
245
utils/request.js
Normal file
@@ -0,0 +1,245 @@
|
||||
/**
|
||||
* 网络请求封装
|
||||
* 统一处理HTTP请求、响应、错误、Token等
|
||||
*
|
||||
* 特性:
|
||||
* - 自动添加Token(Blade-Auth格式)
|
||||
* - 统一错误处理
|
||||
* - 请求/响应拦截
|
||||
* - 超时控制
|
||||
* - Loading状态管理
|
||||
*/
|
||||
|
||||
import config from '@/config/env.config.js'
|
||||
|
||||
/**
|
||||
* 构建请求头
|
||||
* @param {Object} customHeader 自定义头部
|
||||
* @returns {Object} 完整的请求头
|
||||
*/
|
||||
function getHeaders(customHeader = {}) {
|
||||
const token = uni.getStorageSync('token') || ''
|
||||
|
||||
return {
|
||||
'Content-Type': 'application/json',
|
||||
// 重要:后端使用 Blade-Auth 而不是 Authorization
|
||||
'Blade-Auth': token ? `Bearer ${token}` : '',
|
||||
...customHeader
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一请求方法
|
||||
* @param {Object} options 请求配置
|
||||
* @param {String} options.url 请求路径(不含baseURL)
|
||||
* @param {String} options.method 请求方法(GET/POST/PUT/DELETE)
|
||||
* @param {Object} options.data 请求数据
|
||||
* @param {Object} options.header 自定义请求头
|
||||
* @param {Boolean} options.showLoading 是否显示Loading
|
||||
* @param {String} options.loadingText Loading文本
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function request(options = {}) {
|
||||
const {
|
||||
url = '',
|
||||
method = 'GET',
|
||||
data = {},
|
||||
header = {},
|
||||
showLoading = false,
|
||||
loadingText = '加载中...'
|
||||
} = options
|
||||
|
||||
// 显示Loading
|
||||
if (showLoading) {
|
||||
uni.showLoading({
|
||||
title: loadingText,
|
||||
mask: true
|
||||
})
|
||||
}
|
||||
|
||||
// 打印调试信息
|
||||
if (config.debug) {
|
||||
console.log(`[API请求] ${method} ${url}`, data)
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.request({
|
||||
url: config.apiBaseURL + url,
|
||||
method,
|
||||
data,
|
||||
header: getHeaders(header),
|
||||
timeout: config.timeout,
|
||||
success: (res) => {
|
||||
if (config.debug) {
|
||||
console.log(`[API响应] ${method} ${url}`, res.data)
|
||||
}
|
||||
|
||||
// 隐藏Loading
|
||||
if (showLoading) {
|
||||
uni.hideLoading()
|
||||
}
|
||||
|
||||
// BladeX框架标准响应格式
|
||||
// { code: 200, success: true, data: {}, msg: "操作成功" }
|
||||
if (res.statusCode === 200) {
|
||||
const response = res.data
|
||||
|
||||
// 业务成功
|
||||
if (response.code === 200 || response.success) {
|
||||
resolve({
|
||||
code: 200,
|
||||
message: response.msg || response.message || '成功',
|
||||
data: response.data,
|
||||
success: true
|
||||
})
|
||||
} else {
|
||||
// 业务失败
|
||||
const errorMsg = response.msg || response.message || '请求失败'
|
||||
uni.showToast({
|
||||
title: errorMsg,
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
})
|
||||
reject({
|
||||
code: response.code,
|
||||
message: errorMsg,
|
||||
data: response.data
|
||||
})
|
||||
}
|
||||
} else if (res.statusCode === 401) {
|
||||
// Token过期或未登录
|
||||
uni.showToast({
|
||||
title: 'Token已过期,请重新登录',
|
||||
icon: 'none'
|
||||
})
|
||||
// 清除Token
|
||||
uni.removeStorageSync('token')
|
||||
// 跳转到登录页
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({
|
||||
url: '/pages/login/login'
|
||||
})
|
||||
}, 1500)
|
||||
reject({
|
||||
code: 401,
|
||||
message: 'Token已过期'
|
||||
})
|
||||
} else {
|
||||
// HTTP错误
|
||||
const errorMsg = `请求失败 (${res.statusCode})`
|
||||
uni.showToast({
|
||||
title: errorMsg,
|
||||
icon: 'none'
|
||||
})
|
||||
reject({
|
||||
code: res.statusCode,
|
||||
message: errorMsg
|
||||
})
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
if (config.debug) {
|
||||
console.error(`[API错误] ${method} ${url}`, err)
|
||||
}
|
||||
|
||||
// 隐藏Loading
|
||||
if (showLoading) {
|
||||
uni.hideLoading()
|
||||
}
|
||||
|
||||
// 网络错误
|
||||
const errorMsg = err.errMsg || '网络错误,请检查网络连接'
|
||||
uni.showToast({
|
||||
title: errorMsg,
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
})
|
||||
reject({
|
||||
code: -1,
|
||||
message: errorMsg,
|
||||
error: err
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* GET 请求
|
||||
*/
|
||||
export function get(url, data = {}, options = {}) {
|
||||
return request({
|
||||
url,
|
||||
method: 'GET',
|
||||
data,
|
||||
...options
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* POST 请求
|
||||
*/
|
||||
export function post(url, data = {}, options = {}) {
|
||||
return request({
|
||||
url,
|
||||
method: 'POST',
|
||||
data,
|
||||
...options
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT 请求
|
||||
*/
|
||||
export function put(url, data = {}, options = {}) {
|
||||
return request({
|
||||
url,
|
||||
method: 'PUT',
|
||||
data,
|
||||
...options
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE 请求
|
||||
*/
|
||||
export function del(url, data = {}, options = {}) {
|
||||
return request({
|
||||
url,
|
||||
method: 'DELETE',
|
||||
data,
|
||||
...options
|
||||
})
|
||||
}
|
||||
|
||||
// 默认导出
|
||||
export default request
|
||||
|
||||
/**
|
||||
* 使用示例:
|
||||
*
|
||||
* // 方式1:直接使用 request
|
||||
* import request from '@/utils/request.js'
|
||||
*
|
||||
* request({
|
||||
* url: '/martial/score/list',
|
||||
* method: 'GET',
|
||||
* data: { page: 1, size: 10 },
|
||||
* showLoading: true
|
||||
* }).then(res => {
|
||||
* console.log(res.data)
|
||||
* }).catch(err => {
|
||||
* console.error(err.message)
|
||||
* })
|
||||
*
|
||||
* // 方式2:使用快捷方法
|
||||
* import { get, post, put, del } from '@/utils/request.js'
|
||||
*
|
||||
* // GET请求
|
||||
* get('/martial/athlete/list', { venueId: 1 })
|
||||
* .then(res => console.log(res.data))
|
||||
*
|
||||
* // POST请求
|
||||
* post('/martial/score/submit', { athleteId: 1, score: 8.907 })
|
||||
* .then(res => console.log(res.data))
|
||||
*/
|
||||
Reference in New Issue
Block a user