目录
一、接口封装(完整分层实现)
1. 核心请求层封装 (src/api/core/http.js)
2. 业务模块API (src/api/modules/user.api.js)
二、全局字典管理(完整实现)
1. Vuex Store 封装 (src/store/modules/dict.js)
2. 字典混入方法 (src/mixins/dict.js)
3. 在组件中使用
三、通用表格组件(完整实现)
1. 组件实现 (src/components/CommonTable/index.vue)
2. 渲染函数组件 (src/components/CommonTable/RenderCell.vue)
3. 使用示例
四、关键设计说明
五、扩展建议
src/api/core/http.js
)import axios from 'axios'
import { Message } from 'element-ui' // 以ElementUI为例
class HttpCore {
constructor(config) {
// 创建axios实例
this.instance = axios.create({
baseURL: config.baseURL,
timeout: config.timeout || 15000,
headers: config.headers || {}
})
// 安装拦截器
this.setupInterceptors()
}
setupInterceptors() {
// 请求拦截器
this.instance.interceptors.request.use(config => {
// 自动携带token
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
}, error => {
return Promise.reject(error)
})
// 响应拦截器
this.instance.interceptors.response.use(
response => {
// 处理标准响应结构
if (response.data.code === 200) {
return response.data.data
}
// 处理业务错误
Message.error(response.data.message || '请求失败')
return Promise.reject(response.data)
},
error => {
// 处理HTTP错误
const status = error.response?.status
let message = '请求异常'
switch (status) {
case 401:
message = '登录已过期,请重新登录'
localStorage.removeItem('token')
router.push('/login')
break
case 403:
message = '没有操作权限'
break
case 500:
message = '服务器内部错误'
break
}
Message.error(message)
return Promise.reject(error)
}
)
}
// 封装请求方法
get(url, params, config = {}) {
return this.instance.get(url, { params, ...config })
}
post(url, data, config = {}) {
return this.instance.post(url, data, config)
}
put(url, data, config = {}) {
return this.instance.put(url, data, config)
}
delete(url, config = {}) {
return this.instance.delete(url, config)
}
}
// 创建全局实例
const http = new HttpCore({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 20000
})
export default http
src/api/modules/user.api.js
)import http from '../core/http'
export default {
/**
* 获取用户列表
* @param {Object} params - 查询参数
* @returns {Promise}
*/
getUserList(params = { page: 1, size: 10 }) {
return http.get('/user/list', params)
},
/**
* 创建用户
* @param {Object} userData - 用户数据
* @returns {Promise}
*/
createUser(userData) {
return http.post('/user/create', userData)
},
/**
* 更新用户状态
* @param {Number} userId - 用户ID
* @param {Number} status - 新状态
* @returns {Promise}
*/
updateUserStatus(userId, status) {
return http.put(`/user/${userId}/status`, { status })
}
}
3. 统一出口文件 (src/api/index.js)
import userApi from './modules/user.api'
import productApi from './modules/product.api'
export default {
user: userApi,
product: productApi
}
src/store/modules/dict.js
)const state = {
dictCache: {} // 字典缓存 { dictKey: [] }
}
const mutations = {
SET_DICT_DATA: (state, { key, data }) => {
state.dictCache[key] = data
}
}
const actions = {
// 加载字典数据
async loadDict({ commit }, dictKeys) {
const unloadedKeys = dictKeys.filter(key => !state.dictCache[key])
if (unloadedKeys.length === 0) return
try {
// 假设后端接口为 POST /api/dict/list 接收keys数组
const res = await http.post('/dict/list', { keys: unloadedKeys })
Object.entries(res.data).forEach(([key, items]) => {
commit('SET_DICT_DATA', {
key,
data: items.map(item => ({
value: item.dictValue,
label: item.dictLabel,
color: item.color // 扩展字段示例
}))
})
})
} catch (err) {
console.error('字典加载失败:', err)
}
}
}
const getters = {
getDict: state => key => state.dictCache[key] || []
}
export default {
namespaced: true,
state,
mutations,
actions,
getters
}
src/mixins/dict.js
)export default {
methods: {
/**
* 初始化字典(自动处理加载和监听)
* @param {Array|String} dictKeys - 字典键
*/
initDict(dictKeys) {
const keys = Array.isArray(dictKeys) ? dictKeys : [dictKeys]
// 触发字典加载
this.$store.dispatch('dict/loadDict', keys)
// 返回计算属性
return keys.reduce((computedDict, key) => {
computedDict[key] = () => this.$store.getters['dict/getDict'](key)
return computedDict
}, {})
},
/**
* 字典格式化显示
* @param {*} value - 原始值
* @param {String} dictKey - 字典键
* @returns {String} 格式化后的显示值
*/
dictFormat(value, dictKey) {
const dict = this.$store.getters['dict/getDict'](dictKey)
const item = dict.find(d => d.value === value)
return item ? item.label : '--'
}
}
}
{{ dictFormat(row.status, 'user_status') }}
src/components/CommonTable/index.vue
)
{{ dictFormat(row[col.prop], col.dict) }}
{{ col.formatter(row[col.prop], row) }}
{{ row[col.prop] }}
src/components/CommonTable/RenderCell.vue
)
编辑
删除
接口层设计亮点
错误统一处理:拦截器中统一处理401等常见状态码
业务代码隔离:每个业务模块独立文件,避免耦合
自动重试机制:可扩展添加axios-retry插件实现
字典系统优势
按需加载:只在首次使用时请求字典数据
自动更新:当字典数据变化时,所有使用位置自动更新
样式扩展:支持通过字典返回颜色等样式信息
表格组件特性
配置驱动:通过columns配置实现不同展示需求
渲染扩展:支持JSX/render函数实现复杂交互
分页集成:内置标准化分页逻辑
排序支持:自动处理排序参数传递
性能优化措施
防抖加载:可在dataLoader中添加防抖逻辑
缓存策略:接口层可添加LRU缓存机制
虚拟滚动:对大数据量表格可集成虚拟滚动
增加表格工具栏
实现批量操作
// 在CommonTable中添加
props: {
selection: { type: Boolean, default: false }
},
methods: {
handleSelectionChange(selection) {
this.$emit('selection-change', selection)
}
}
添加导出功能
// 在CommonTable中添加
methods: {
exportData() {
const headers = this.processedColumns
.filter(col => !col.hidden)
.map(col => ({
label: col.label,
prop: col.prop
}))
exportExcel({
headers,
data: this.tableData,
filename: '导出数据'
})
}
}
这套封装方案经过多个中后台项目验证,可满足以下需求场景:
快速搭建CRUD界面
统一处理权限控制
实现复杂表格交互
保持多模块样式统一
快速响应业务需求变化
码字不易,各位大佬点点赞呗