【vue】适合大型项目的封装(接口,全局字典,表格表头)
目录
一、接口封装(完整分层实现)
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. 使用示例
四、关键设计说明
五、扩展建议
一、接口封装(完整分层实现)
1. 核心请求层封装 (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 => {// 自动携带tokenconst 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?.statuslet message = '请求异常'switch (status) {case 401:message = '登录已过期,请重新登录'localStorage.removeItem('token')router.push('/login')breakcase 403:message = '没有操作权限'breakcase 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
2. 业务模块API (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
}
二、全局字典管理(完整实现)
1. Vuex Store 封装 (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) returntry {// 假设后端接口为 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
}
2. 字典混入方法 (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 : '--'}}
}
3. 在组件中使用
<template><div><!-- 选择器使用字典 --><el-select v-model="form.status"><el-optionv-for="item in statusDict":key="item.value":label="item.label":value="item.value"/></el-select><!-- 表格格式化显示 --><el-table :data="tableData"><el-table-column prop="status" label="状态"><template v-slot="{ row }"><span :style="{ color: getStatusColor(row.status) }">{{ dictFormat(row.status, 'user_status') }}</span></template></el-table-column></el-table></div>
</template><script>
import dictMixin from '@/mixins/dict'export default {mixins: [dictMixin],data() {return {form: { status: '' },tableData: []}},computed: {// 自动关联字典数据...this.initDict(['user_status', 'gender_type']),// 获取状态颜色getStatusColor() {return (value) => {const dict = this.$store.getters['dict/getDict']('user_status')const item = dict.find(d => d.value === value)return item?.color || '#333'}}},mounted() {this.loadData()},methods: {async loadData() {try {const res = await this.$api.user.getUserList()this.tableData = res.data} catch (err) {this.$message.error('数据加载失败')}}}
}
</script>
三、通用表格组件(完整实现)
1. 组件实现 (src/components/CommonTable/index.vue
)
<template><div class="common-table"><!-- 表格主体 --><el-table:data="tableData"v-bind="$attrs"@sort-change="handleSortChange"><!-- 循环渲染列 --><template v-for="col in processedColumns"><!-- 自定义列 --><el-table-columnv-if="col.render"v-bind="col":key="col.prop"><template v-slot="{ row, $index }"><!-- 支持渲染函数 --><render-cell:render="col.render":row="row":index="$index":dict-format="dictFormat"/></template></el-table-column><!-- 常规列 --><el-table-columnv-elsev-bind="col":key="col.prop"><template v-slot="{ row }"><!-- 字典格式化 --><template v-if="col.dict"><span :style="col.dictStyle?.(row[col.prop])">{{ dictFormat(row[col.prop], col.dict) }}</span></template><!-- 自定义格式化 --><template v-else-if="col.formatter">{{ col.formatter(row[col.prop], row) }}</template><!-- 默认内容 --><template v-else>{{ row[col.prop] }}</template></template></el-table-column></template></el-table><!-- 分页器 --><div v-if="showPagination" class="pagination-wrap"><el-pagination:current-page="currentPage":page-size="pageSize":total="total"@current-change="handlePageChange"layout="total, prev, pager, next, jumper"/></div></div>
</template><script>
import dictMixin from '@/mixins/dict'export default {name: 'CommonTable',mixins: [dictMixin],props: {columns: { // 列配置type: Array,required: true,validator: cols => cols.every(c => c.prop && c.label)},dataLoader: { // 数据加载函数type: Function,required: true},immediate: { // 是否立即加载type: Boolean,default: true},pageSize: { // 每页条数type: Number,default: 10},showPagination: { // 是否显示分页type: Boolean,default: true}},data() {return {tableData: [],currentPage: 1,total: 0,sortParams: null}},computed: {processedColumns() {return this.columns.map(col => ({width: 150,align: 'center',...col,sortable: col.sortable ? 'custom' : false}))}},watch: {immediate: {immediate: true,handler(val) {val && this.loadData()}}},methods: {// 加载表格数据async loadData() {try {const params = {page: this.currentPage,size: this.pageSize,...this.sortParams}const res = await this.dataLoader(params)this.tableData = res.data.list || []this.total = res.data.total || 0} catch (err) {console.error('表格数据加载失败:', err)this.tableData = []this.total = 0}},// 处理分页变化handlePageChange(page) {this.currentPage = pagethis.loadData()},// 处理排序变化handleSortChange({ prop, order }) {this.sortParams = {sortField: prop,sortOrder: order === 'ascending' ? 'asc' : 'desc'}this.loadData()},// 刷新表格refresh() {this.currentPage = 1this.loadData()}}
}
</script><style scoped>
.pagination-wrap {margin-top: 20px;text-align: right;
}
</style>
2. 渲染函数组件 (src/components/CommonTable/RenderCell.vue
)
<script>
export default {name: 'RenderCell',functional: true,props: {render: Function,row: Object,index: Number,dictFormat: Function},render(h, ctx) {return ctx.props.render(h, {row: ctx.props.row,index: ctx.props.index,dictFormat: ctx.props.dictFormat})}
}
</script>
3. 使用示例
<template><common-table:columns="columns":data-loader="fetchData":page-size="10"><!-- 自定义操作列 --><template #action="{ row }"><el-button @click="edit(row)">编辑</el-button><el-button type="danger" @click="delete(row)">删除</el-button></template></common-table>
</template><script>
import CommonTable from '@/components/CommonTable'export default {components: { CommonTable },data() {return {columns: [{prop: 'name',label: '姓名',width: 120},{prop: 'status',label: '状态',dict: 'user_status',dictStyle: (value) => ({color: value === 1 ? 'green' : 'red'})},{prop: 'salary',label: '薪资',formatter: value => `¥${value.toFixed(2)}`},{prop: 'action',label: '操作',render: (h, { row }) => [h('el-button', {on: { click: () => this.edit(row) }}, '编辑'),h('el-button', {style: { marginLeft: '10px' },props: { type: 'danger' },on: { click: () => this.delete(row) }}, '删除')]}]}},methods: {async fetchData(params) {return this.$api.user.getUserList(params)},edit(row) { /* 编辑逻辑 */ },delete(row) { /* 删除逻辑 */ }}
}
</script>
四、关键设计说明
-
接口层设计亮点
-
错误统一处理:拦截器中统一处理401等常见状态码
-
业务代码隔离:每个业务模块独立文件,避免耦合
-
自动重试机制:可扩展添加axios-retry插件实现
-
-
字典系统优势
-
按需加载:只在首次使用时请求字典数据
-
自动更新:当字典数据变化时,所有使用位置自动更新
-
样式扩展:支持通过字典返回颜色等样式信息
-
-
表格组件特性
-
配置驱动:通过columns配置实现不同展示需求
-
渲染扩展:支持JSX/render函数实现复杂交互
-
分页集成:内置标准化分页逻辑
-
排序支持:自动处理排序参数传递
-
-
性能优化措施
-
防抖加载:可在dataLoader中添加防抖逻辑
-
缓存策略:接口层可添加LRU缓存机制
-
虚拟滚动:对大数据量表格可集成虚拟滚动
-
五、扩展建议
-
增加表格工具栏
<template><div class="table-wrapper"><!-- 工具栏 --><div class="table-toolbar"><el-button @click="refresh">刷新</el-button><column-selector :columns="columns" v-model="visibleColumns"/></div><!-- 表格 --><common-table :columns="visibleColumns" ... /></div> </template>
-
实现批量操作
// 在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界面
统一处理权限控制
实现复杂表格交互
保持多模块样式统一
快速响应业务需求变化
码字不易,各位大佬点点赞呗