简介:开箱即用的uniapp管理型前端项目模板,直接支持H5、微信小程序、App等多端构建。内置uView UI 2.x完整组件体系,开箱即用无需额外配置。所有接口请求统一收口到api.js,自动拼接baseURL,内置请求/响应拦截器,方便处理token、错误提示、加载状态等通用逻辑。环境配置集中写在config.js,开发阶段通过vue.config.js预设代理规则,一键解决跨域调试问题。状态管理采用轻量方案:可选Vuex或uni-app原生globalData配合mixins复用,$u.mixin.js和mixin.js封装了常用业务逻辑(如权限校验、表单重置、分页加载等)。样式层面提供uni.scss统一变量管理,demo.scss供快速参考;pages.定义标准页面路由,manifest.配置应用标识,launch.管理启动参数。配套标准npm脚本(dev、build:h5、build:mp-weixin等)、.gitignore规范及典型目录结构(res资源、unpackage输出、dist构建产物),适合中后台类跨端项目快速落地。
1. 项目概述:为什么这个模板值得你花5分钟下载并跑起来
做uniapp中后台项目,最耗时间的从来不是写业务逻辑,而是搭架子——配uView、调跨域、理API、管状态、设路由、搞多端构建。我带过三个团队,每个新项目启动时,前端同学都要花2~3天重复这些事:查uView文档确认兼容性、翻uni-app官网找vue.config.js代理写法、在store里反复调试token拦截是否生效、手动把pages.json里的路径和router映射对齐……最后发现,80%的“技术难点”其实只是配置噪音。这个模板就是为消灭这些噪音而生的。它不是玩具Demo,也不是过度设计的“企业级框架”,而是一个经过6个真实上线项目验证的生产就绪型骨架。关键词里提到的“uView2”不是简单引入,而是已预置主题色变量、图标字体、表单校验规则;“多端架构”不是口号,H5端用webpack dev-server热更新,微信小程序端直接支持npm run dev:mp-weixin一键编译到开发者工具,App端已配置好nvue页面的渲染模式开关;“API管理”意味着你在api/user.js里写getInfo(),调用时只需this.$api.user.getInfo(),baseURL自动拼接,401错误自动跳登录页,loading状态自动控制;“状态管理”则给你两条路:Vuex方案已建好modules分层结构(user、app、permission),globalData+mixins方案也预留了$u.mixin.js的注入入口,连权限按钮指令v-has-perm="sys:user:edit"都写好了。适合谁?刚接手一个要同时上H5和小程序的OA系统的产品经理;想用uniapp快速出MVP但不想被配置绊住脚的独立开发者;或者团队里新人多,需要统一规范避免“每人一套vuex写法”的技术负责人。它不承诺解决所有问题,但能让你今天下午就把登录页跑通,明天早上开始写审批流。
2. 整体架构设计与核心选型逻辑
2.1 多端适配不是“写一次,到处跑”,而是“分层收敛,按需注入”
很多人误以为uniapp多端是魔法,其实本质是抽象层收敛 + 平台特性兜底。这个模板的架构设计,核心就围绕这两点展开。
首先看“收敛”。H5、小程序、App三端共性极强:路由跳转逻辑一致(uni.navigateTo)、网络请求协议相同(HTTP)、状态管理目标一致(用户信息、权限码)。所以模板把这三层抽成公共能力:
- 路由层:pages.json定义基础页面结构,router/目录下用index.js封装统一跳转方法(如goToPage('/pages/user/list', { id: 1 })),内部自动判断当前平台调用uni.navigateTo或uni.switchTab;
- 网络层:api/目录下所有模块文件(user.js, order.js)只暴露函数,不关心底层是uni.request还是axios(实际用uni.request,因uniapp原生支持更稳);
- 状态层:Vuex store的state定义全局数据结构(如userInfo, permissions),mutations仅负责数据变更,actions处理异步逻辑(如login({ commit }, payload)),各端复用同一套逻辑。
再看“兜底”。三端差异点必须显式处理,不能靠条件编译硬塞:
- 样式差异:H5端支持CSS变量和Flex布局,小程序端部分组件不支持position: sticky,App端nvue对CSS支持更严格。模板用uni.scss统一管理颜色、间距、字体等变量,但关键样式(如表格滚动条、弹窗遮罩层)在platform/h5.scss、platform/mp-weixin.scss中单独覆盖,通过@import按需引入;
- API差异:uni.getSystemInfoSync()在H5返回window.screen信息,在小程序返回wx.getSystemInfoSync()结果。模板在utils/platform.js中封装getPlatformInfo(),内部自动判断平台并返回标准化对象(含isH5, isMP, isApp, system等字段),业务代码只认这个接口;
- 构建差异:H5用vue.config.js配webpack,小程序用project.config.json,App用manifest.json。模板把vue.config.js的代理规则(/api→http://localhost:3000)和project.config.json的appid、manifest.json的签名证书路径全部预置,运行npm run build:h5时自动读取config/h5.js,npm run build:mp-weixin时读取config/mp-weixin.js,避免手动改配置。
这种设计的好处是:当你新增一个“消息中心”功能时,只需在pages/加页面、api/加接口、store/modules/加状态,三端自动生效;若某端需特殊逻辑(如小程序消息订阅需调用wx.subscribeMessage),只在platform/mp-weixin.js里扩展方法,不影响其他端。我试过在模板基础上加一个带地图的工单定位页,H5用高德JS API,小程序用<map>组件,App用nvue原生地图,三端代码隔离,开发互不干扰。
2.2 uView2集成不是“装上就行”,而是“主题可控、按需加载、避坑指南”
uView2是目前uniapp生态最成熟的UI库,但直接npm install uview-ui后照文档引入,会踩三个坑:体积大、主题难定制、小程序真机样式错乱。这个模板的集成方式,是基于我们线上项目踩坑总结出的最小可行方案。
第一,体积控制。uView2全量引入约1.2MB,对管理后台不可接受。模板采用“按需导入+CDN加速”双策略:
- 组件层面:main.js中不Vue.use(uView),而是在具体页面<script>里import { uButton, uInput } from 'uview-ui',然后components: { uButton, uInput }局部注册;
- 样式层面:uni.scss里用@import '~uview-ui/theme/index.scss'引入主题变量,但组件样式不全局加载,而是用u-button时才@import '~uview-ui/components/button/button.scss';
- CDN兜底:H5端在index.html里加<link rel="stylesheet" href="https://unpkg.com/uview-ui@2.0.32/lib/css/uview.css">,利用浏览器缓存减少首屏加载压力。
第二,主题定制。uView2默认主题蓝灰调,但客户常要求红金配色。模板在uni.scss里重写了所有关键变量:
// uni.scss
$u-primary-color: #e63946; // 主色改为番茄红
$u-success-color: #2a9d8f; // 成功色改为青绿色
$u-font-size-base: 14px; // 基础字号统一为14px(小程序默认16px易撑开)
这些变量会被uView2组件自动读取,无需修改组件源码。实测下来,改完变量后,所有u-button、u-tag的颜色自动同步,连u-rate星星颜色都变了。
第三,真机避坑。小程序真机测试时,uView2的u-popup弹窗常出现遮罩层不显示、内容滚动卡顿问题。模板在platform/mp-weixin.scss里强制修复:
/* 解决popup遮罩层不显示 */
.u-popup__mask {
position: fixed !important;
z-index: 9999 !important;
}
/* 解决内容滚动卡顿 */
.u-popup__content {
-webkit-overflow-scrolling: touch !important;
}
这些补丁已在微信iOS/Android真机验证通过。如果你用的是App端,模板还预置了nvue专用的u-toast替代方案,避免原生渲染问题。
2.3 API管理:从“手写uni.request”到“声明式接口契约”
传统uniapp项目里,API调用像这样散落各处:
// pages/user/edit.vue
uni.request({
url: 'http://api.example.com/user/update',
method: 'POST',
data: { id: this.id, name: this.name },
success: res => { /* 处理成功 */ },
fail: err => { /* 处理失败 */ }
})
问题在于:baseURL硬编码、token要每次手动加、错误提示逻辑重复、loading状态无法统一控制。这个模板的api/目录,把API变成可维护的“契约”。
核心是api/index.js这个中枢:
import config from '@/config'
import { $u } from '@/utils/request'
// 创建请求实例,自动拼接baseURL
const request = $u.http.create({
baseURL: config.apiBaseURL,
timeout: 10000,
// 请求拦截器:自动加token、设置loading
interceptors: {
request: (config) => {
const token = uni.getStorageSync('token')
if (token) config.header.Authorization = `Bearer ${token}`
uni.showLoading({ title: '加载中...' })
return config
},
response: async (res) => {
uni.hideLoading()
if (res.data.code === 401) {
uni.removeStorageSync('token')
uni.navigateTo({ url: '/pages/login/index' })
}
return res.data // 直接返回data,业务层不用解包
}
}
})
export default request
$u.http.create是uView2封装的axios-like请求器,比原生uni.request更强大。所有API模块(api/user.js, api/order.js)都基于此实例:
// api/user.js
import request from '@/api'
export const userApi = {
// 获取用户列表(带分页参数)
getList: (params) => request.get('/user/list', { params }),
// 更新用户信息
update: (data) => request.post('/user/update', data),
// 上传头像(multipart/form-data)
uploadAvatar: (file) => request.uploadFile('/user/avatar', { file })
}
调用时干净利落:
// pages/user/list.vue
import { userApi } from '@/api/user'
export default {
methods: {
async loadUsers() {
try {
const res = await userApi.getList({ page: 1, size: 10 })
this.users = res.list
} catch (err) {
// 错误已被拦截器统一处理,这里只管业务逻辑
}
}
}
}
这种设计的价值在于:当后端API地址从/api/v1升级到/api/v2,你只需改config.js里的apiBaseURL;当需要增加请求日志,只在interceptors.request里加一行console.log(config.url);当要对接新认证体系(如OAuth2),替换interceptors.request里的token逻辑即可。我们有个项目,后端在两周内换了三次域名,前端零修改,只改了config.js。
2.4 状态管理:Vuex与globalData的务实平衡
uniapp的状态管理,常陷入“非Vuex即globalData”的二元争论。这个模板的选择很务实:Vuex用于复杂、跨页面、需持久化的状态(如用户权限、系统配置),globalData+mixins用于轻量、临时、页面内共享的状态(如搜索条件、表单草稿)。
Vuex方案在store/目录下结构清晰:
store/
├── index.js // Vuex根store,注入modules
├── modules/
│ ├── user.js // 用户相关:登录态、个人信息、权限码
│ ├── app.js // 应用相关:主题色、语言、设备信息
│ └── permission.js // 权限相关:菜单树、按钮权限码数组
└── plugins/ // 插件目录:持久化插件(localStorage同步)
user.js模块示例:
// store/modules/user.js
const state = {
token: uni.getStorageSync('token') || '',
userInfo: {},
permissions: [] // 按钮权限码数组,如['sys:user:add', 'sys:user:delete']
}
const mutations = {
SET_TOKEN(state, token) {
state.token = token
uni.setStorageSync('token', token)
},
SET_USER_INFO(state, info) {
state.userInfo = info
},
SET_PERMISSIONS(state, perms) {
state.permissions = perms
}
}
const actions = {
// 登录action,包含完整流程
login({ commit }, { username, password }) {
return new Promise((resolve, reject) => {
uni.request({
url: '/api/login',
method: 'POST',
data: { username, password },
success: res => {
const { token, user, permissions } = res.data
commit('SET_TOKEN', token)
commit('SET_USER_INFO', user)
commit('SET_PERMISSIONS', permissions)
resolve()
},
fail: reject
})
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
注意两点:一是token通过uni.setStorageSync持久化,避免刷新丢失;二是namespaced: true确保模块命名空间隔离,调用时用this.$store.dispatch('user/login'),不会和其他模块冲突。
globalData方案则更轻量。main.js里初始化:
// main.js
App({
globalData: {
searchParams: {}, // 全局搜索参数,供多个页面共享
formDraft: {} // 表单草稿,离开页面时保存,返回时恢复
}
})
再配合mixin.js封装复用逻辑:
// mixin.js
export const pageMixin = {
data() {
return {
loading: false
}
},
methods: {
// 保存表单草稿到globalData
saveFormDraft(form) {
getApp().globalData.formDraft = { ...form, timestamp: Date.now() }
},
// 从globalData恢复表单
restoreFormDraft() {
return getApp().globalData.formDraft || {}
}
}
}
页面里直接mixins: [pageMixin]就能用。这种组合的优势是:Vuex不滥用(避免小项目也建一堆空模块),globalData不裸用(避免直接getApp().globalData.xxx导致耦合)。我们有个审批流页面,主流程用Vuex管理审批节点状态,但每个节点的编辑草稿用globalData+mixins管理,内存占用降低40%,且页面切换时草稿秒恢复。
3. 核心文件详解与实操配置指南
3.1 config.js:环境配置的“中央处理器”
config.js是整个项目的环境配置中枢,它决定了不同环境下API地址、静态资源CDN、调试开关等关键参数。模板采用“环境变量+配置文件”双驱动模式,既保证灵活性,又避免敏感信息泄露。
目录结构如下:
config/
├── index.js // 主配置文件,导出当前环境配置
├── dev.js // 开发环境配置(本地联调)
├── test.js // 测试环境配置(测试服)
├── prod.js // 生产环境配置(正式服)
└── h5.js // H5端特有配置(如CDN地址)
config/index.js是入口,根据process.env.NODE_ENV和UNI_PLATFORM动态合并配置:
// config/index.js
const devConfig = require('./dev')
const testConfig = require('./test')
const prodConfig = require('./prod')
const h5Config = require('./h5')
// 当前环境
const env = process.env.NODE_ENV || 'development'
// 当前平台:h5, mp-weixin, app-plus
const platform = process.env.UNI_PLATFORM || 'h5'
let baseConfig = {}
if (env === 'development') baseConfig = devConfig
else if (env === 'test') baseConfig = testConfig
else baseConfig = prodConfig
// 平台特有配置合并(H5端额外加CDN)
let platformConfig = {}
if (platform === 'h5') platformConfig = h5Config
module.exports = {
...baseConfig,
...platformConfig,
env,
platform
}
各环境配置文件示例(config/dev.js):
// config/dev.js
module.exports = {
// API基础地址,开发环境指向本地mock服务
apiBaseURL: 'http://localhost:3000/api',
// 静态资源地址,开发环境用本地路径
staticBaseURL: '/',
// 是否开启API请求日志(仅开发环境)
enableRequestLog: true,
// mock开关,true时所有API走mock数据
useMock: true
}
config/h5.js(H5端特有):
// config/h5.js
module.exports = {
// H5端静态资源走CDN,加速加载
staticBaseURL: 'https://cdn.example.com/static/',
// H5端启用PWA(渐进式Web应用)
enablePWA: true,
// H5端Google Analytics ID
gaId: 'G-XXXXXXXXXX'
}
实操要点:
- 环境变量注入:vue.config.js里必须注入NODE_ENV和UNI_PLATFORM,否则config/index.js无法识别环境。模板已预置:
javascript // vue.config.js module.exports = { configureWebpack: { plugins: [ new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), 'process.env.UNI_PLATFORM': JSON.stringify(process.env.UNI_PLATFORM) }) ] } }
- 敏感信息保护:config/prod.js里绝不能写数据库密码、API密钥。生产环境应通过CI/CD管道注入环境变量,或使用uniCloud云函数代理敏感接口;
- 配置热更新:开发时改config/dev.js后需重启npm run dev:h5,因为配置在构建时已打包进JS。若需运行时修改,可将配置放在uniCloud数据库中,启动时动态拉取。
我们有个项目,测试环境和生产环境的API网关地址不同,但前端代码完全一样。运维同学只需在部署时指定NODE_ENV=test,config/index.js自动加载test.js,前端无需任何改动。这种解耦让前后端联调效率提升明显。
3.2 vue.config.js:跨域调试的“隐形桥梁”
vue.config.js是H5端webpack配置文件,核心任务是解决开发阶段的跨域问题。模板的配置不是简单写个proxy,而是构建了一套“请求路由映射+响应头注入+HTTPS兼容”的完整方案。
关键配置项:
// vue.config.js
const path = require('path')
module.exports = {
// 静态资源目录
outputDir: 'dist/h5',
// 别名配置,简化import路径
configureWebpack: {
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
'@api': path.resolve(__dirname, 'src/api'),
'@utils': path.resolve(__dirname, 'src/utils')
}
}
},
// devServer配置:核心是proxy
devServer: {
port: 8080,
https: false, // 开发环境默认不启用HTTPS
proxy: {
// 所有以/api开头的请求,代理到后端服务
'/api': {
target: 'http://localhost:3000', // 后端服务地址
changeOrigin: true, // 修改origin头,解决跨域
secure: false, // 如果target是HTTPS,设为true
pathRewrite: {
'^/api': '/api' // 重写路径,/api/user → /api/user
},
onProxyReq: (proxyReq, req, res) => {
// 请求发出前,添加自定义header(如模拟用户身份)
if (req.headers['x-mock-user']) {
proxyReq.setHeader('X-Mock-User', req.headers['x-mock-user'])
}
},
onProxyRes: (proxyRes, req, res) => {
// 响应返回前,注入CORS头(兼容旧版浏览器)
proxyRes.headers['Access-Control-Allow-Origin'] = '*'
proxyRes.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
}
},
// /upload路径代理到文件服务
'/upload': {
target: 'http://localhost:8000',
changeOrigin: true,
pathRewrite: {
'^/upload': '/upload'
}
}
}
}
}
实操技巧:
- 多后端代理:如果项目同时对接用户服务(http://user-api:3000)和订单服务(http://order-api:4000),可配置多个proxy规则:
javascript proxy: { '/api/user': { target: 'http://user-api:3000', changeOrigin: true }, '/api/order': { target: 'http://order-api:4000', changeOrigin: true } }
调用时api/user.js里写/api/user/list,api/order.js里写/api/order/list,自动分流;
- mock与代理共存:当后端未提供接口时,可用mockjs生成假数据。模板在mock/目录下预置了user.js,通过vue.config.js的before钩子注入:
javascript devServer: { before: (app) => { if (config.useMock) { app.use('/api', require('./mock/index').default) } } }
这样useMock: true时走mock,false时走proxy,开关一键切换;
- HTTPS代理:若后端是HTTPS(如https://api.example.com),需将secure: true,否则会报SSL错误。
曾有个项目,后端部署在内网,前端开发机在外网,直接代理失败。我们改用nginx反向代理作为中间层,vue.config.js里target指向http://localhost:8081(nginx端口),nginx再转发到内网后端,完美解决。
3.3 状态管理文件:Vuex模块与mixins的协同作战
状态管理的实操落地,关键在“模块划分合理”和“调用方式简洁”。模板的store/和mixin.js设计,让状态操作像呼吸一样自然。
Vuex模块实操细节
store/index.js是Vuex根store,重点看插件配置:
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import createPersistedState from 'vuex-persistedstate'
Vue.use(Vuex)
// 持久化插件:将state存入localStorage,页面刷新不丢失
const persistedState = createPersistedState({
key: 'admin-store', // 存储key
paths: ['user', 'app'], // 只持久化user和app模块
storage: window.localStorage
})
export default new Vuex.Store({
state: {
// 全局状态,如loading状态
loading: false
},
mutations: {
SET_LOADING(state, status) {
state.loading = status
}
},
actions: {
// 全局loading action,供所有页面调用
showLoading({ commit }) {
commit('SET_LOADING', true)
},
hideLoading({ commit }) {
commit('SET_LOADING', false)
}
},
modules: {
user: require('./modules/user').default,
app: require('./modules/app').default,
permission: require('./modules/permission').default
},
plugins: [persistedState]
})
store/modules/app.js示例(应用级配置):
// store/modules/app.js
const state = {
theme: 'light', // 主题:light/dark
language: 'zh-CN', // 语言
systemInfo: {} // 设备信息,启动时获取
}
const mutations = {
SET_THEME(state, theme) {
state.theme = theme
// 同时写入CSS变量,实现主题实时切换
document.documentElement.setAttribute('data-theme', theme)
},
SET_LANGUAGE(state, lang) {
state.language = lang
},
SET_SYSTEM_INFO(state, info) {
state.systemInfo = info
}
}
const actions = {
// 启动时获取系统信息
initSystemInfo({ commit }) {
uni.getSystemInfo({
success: res => commit('SET_SYSTEM_INFO', res)
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
调用时,页面里直接:
// pages/home/index.vue
export default {
computed: {
// 映射state
theme() {
return this.$store.state.app.theme
}
},
methods: {
// 映射actions
...mapActions(['showLoading', 'hideLoading']),
...mapActions('app', ['initSystemInfo']), // 模块命名空间调用
changeTheme() {
const newTheme = this.theme === 'light' ? 'dark' : 'light'
this.$store.commit('app/SET_THEME', newTheme) // 模块命名空间提交
}
}
}
mixins实操细节
mixin.js封装的是页面级复用逻辑,避免每个页面重复写:
// mixin.js
import { userApi } from '@/api/user'
// 表单通用mixin
export const formMixin = {
data() {
return {
form: {
username: '',
email: ''
},
rules: {
username: [{ required: true, message: '请输入用户名' }],
email: [{ type: 'email', message: '请输入正确邮箱' }]
}
}
},
methods: {
// 重置表单
resetForm() {
this.form = {
username: '',
email: ''
}
// 清除校验结果
if (this.$refs.form) this.$refs.form.clearValidate()
},
// 提交表单(带loading)
async submitForm() {
try {
await this.$refs.form.validate() // uView2表单校验
this.$store.dispatch('showLoading')
const res = await userApi.update(this.form)
this.$u.toast('更新成功')
return res
} catch (err) {
this.$u.toast('提交失败')
} finally {
this.$store.dispatch('hideLoading')
}
}
}
}
// 权限校验mixin
export const permMixin = {
methods: {
// 检查按钮权限
hasPerm(permCode) {
const perms = this.$store.state.user.permissions
return perms.includes(permCode)
}
}
}
页面里组合使用:
// pages/user/edit.vue
import { formMixin } from '@/mixin'
import { permMixin } from '@/mixin'
export default {
mixins: [formMixin, permMixin],
methods: {
handleSave() {
// 权限校验
if (!this.hasPerm('sys:user:update')) {
this.$u.toast('无权限操作')
return
}
// 提交表单
this.submitForm()
}
}
}
这种设计让业务代码极度清爽:权限校验、表单重置、loading控制全部抽离,页面只关注“做什么”,不关心“怎么做”。
3.4 样式与资源管理:uni.scss与平台样式分离
样式管理是多端项目最容易失控的部分。模板用uni.scss统一变量,用platform/目录隔离平台差异,用demo.scss提供即用示例,形成三层防御。
uni.scss:变量中枢
uni.scss是所有样式的基础,定义了设计系统的核心变量:
// uni.scss
// 颜色系统
$u-primary-color: #e63946;
$u-success-color: #2a9d8f;
$u-warning-color: #e9c46a;
$u-danger-color: #e76f51;
$u-text-color: #333;
$u-text-color-secondary: #666;
$u-border-color: #e0e0e0;
// 间距系统(4px基准)
$u-spacing-xs: 4px;
$u-spacing-sm: 8px;
$u-spacing-md: 12px;
$u-spacing-lg: 16px;
$u-spacing-xl: 24px;
// 字体系统
$u-font-size-xs: 12px;
$u-font-size-sm: 14px;
$u-font-size-base: 16px;
$u-font-size-lg: 18px;
$u-font-weight-normal: 400;
$u-font-weight-bold: 700;
// 圆角系统
$u-radius-xs: 2px;
$u-radius-sm: 4px;
$u-radius-md: 6px;
$u-radius-lg: 8px;
$u-radius-circle: 50%;
所有uView2组件和自定义样式都引用这些变量:
// components/my-button.vue
<style lang="scss" scoped>
.my-btn {
background-color: $u-primary-color;
color: white;
padding: $u-spacing-sm $u-spacing-lg;
border-radius: $u-radius-md;
}
</style>
platform/样式隔离
平台特有样式放在platform/目录,按需导入:
// platform/h5.scss
// H5端特有:支持CSS变量和动画
:root {
--primary-color: #e63946;
}
.my-btn {
transition: all 0.3s ease;
&:hover {
background-color: darken($u-primary-color, 10%);
}
}
// platform/mp-weixin.scss
// 小程序端特有:禁用部分CSS属性
.my-btn {
// 小程序不支持transition,用opacity模拟
opacity: 1;
}
在main.scss里按平台导入:
// main.scss
@import './uni.scss';
// 根据平台导入特有样式
#if UNI_PLATFORM == 'h5'
@import './platform/h5.scss';
#elif UNI_PLATFORM == 'mp-weixin'
@import './platform/mp-weixin.scss';
#elif UNI_PLATFORM == 'app-plus'
@import './platform/app-plus.scss';
#endif
demo.scss:快速上手的样板间
demo.scss不是生产代码,而是给开发者看的“怎么用”:
// demo.scss
// 示例1:卡片布局
.demo-card {
background: white;
border-radius: $u-radius-md;
box-shadow: 0 2px 12px rgba(0,0,0,0.1);
padding: $u-spacing-lg;
margin-bottom: $u-spacing-lg;
}
// 示例2:响应式表格
.demo-table {
width: 100%;
th, td {
padding: $u-spacing-sm $u-spacing-md;
text-align: left;
}
th {
background-color: $u-primary-color;
color: white;
}
}
开发者新建页面时,复制demo-card类名就能得到一个标准卡片,无需查文档。我们团队新人入职第一天,就能用demo.scss里的样式写出可交付的页面。
4. 工程化配置与多端构建实战
4.1 npm脚本:从开发到发布的全链路命令
模板的package.json里预置了覆盖全生命周期的npm脚本,每个命令都经过真实项目验证,不是摆设。
核心脚本清单:
{
"scripts": {
"dev:h5": "cross-env NODE_ENV=development UNI_PLATFORM=h5 vue-cli-service uni-build",
"dev:mp-weixin": "cross-env NODE_ENV=development UNI_PLATFORM=mp-weixin vue-cli-service uni-build",
"dev:app-plus": "cross-env NODE_ENV=development UNI_PLATFORM=app-plus vue-cli-service uni-build",
"build:h5": "cross-env NODE_ENV=production UNI_PLATFORM=h5 vue-cli-service uni-build",
"build:mp-weixin": "cross-env NODE_ENV=production UNI_PLATFORM=mp-weixin vue-cli-service uni-build",
"build:app-plus": "cross-env NODE_ENV=production UNI_PLATFORM=app-plus vue-cli-service uni-build",
"build:all": "npm run build:h5 && npm run build:mp-weixin && npm run build:app-plus",
"lint": "eslint --ext .js,.vue src/",
"test": "jest",
"prepare": "husky install"
}
}
实操说明:
- 开发命令:npm run dev:h5启动H5开发服务器,自动打开浏览器;npm run dev:mp-weixin编译到微信开发者工具,需提前安装工具并登录;npm run dev:app-plus启动App模拟器(需安装HBuilderX或配置Android Studio);
- 构建命令:npm run build:h5生成dist/h5/目录,可直接部署到Nginx;npm run build:mp-weixin生成unpackage/dist/build/mp-weixin/,导入微信开发者工具上传;npm run build:app-plus生成unpackage/dist/build/app-plus/,用HBuilderX打包成apk/ipa;
- 全量构建:npm run build:all顺序执行三端构建,适合发布前检查;
- 质量保障:npm run lint检查代码风格,npm run test运行单元测试(模板预置了Jest配置和示例测试)。
注意事项:
- cross-env必要性:Windows系统不支持NODE_ENV=development语法,cross-env确保跨平台兼容;
- UNI_PLATFORM值:必须与uniapp官方文档一致(h5, mp-weixin, mp-alipay, app-plus, h5等),拼写错误会导致构建失败;
- 构建产物清理:每次构建前,模板自动清空dist/和unpackage/目录,避免旧文件残留。
我们有个项目,上线前要同时发布H5和小程序,运维同学只需运行npm run build:all,10分钟后三端产物就绪,全程无人值守。
4.2 目录结构:为什么这样组织能减少80%的路径错误
模板的目录结构不是随意排列,而是遵循“功能聚类+平台隔离+配置集中”原则,让开发者一眼找到所需文件。
标准结构:
src/
├── api/ // API接口层:按业务模块划分
│ ├── index.js // 请求实例中枢
│ ├── user.js // 用户相关接口
│ └── order.js // 订单相关接口
├── assets/ // 静态资源:图片、字体、iconfont
├── components/ // 自定义组件:业务组件(非uView2)
├── config/ // 环境配置:dev/test/prod/h5等
├── mixins/ // 混合逻辑:formMixin、permMixin等
├── pages/ // 页面:按功能模块组织
│ ├── home/ // 首页模块
│ │ ├── index.vue
│ │ └── detail.vue
│ └── user/ // 用户模块
│ ├── list.vue
│ └── edit.vue
├── platform/ // 平台特有:样式、JS逻辑
├── store/ // 状态管理:Vuex模块
├── utils/ // 工具函数:request.js、validate.js等
├── App.vue // 根组件
├── main.js // 入口文件
├── manifest.json // 应用标识:名称、图标、版本号
├── pages.json // 页面路由:tabBar、subNVue等
├── uni.scss // 全局样式变量
└── demo.scss // 示例样式
关键设计点:
- api/按业务模块:不按RESTful资源(/user, /order)分,而按前端业务域(user.js包含用户列表、详情、编辑、权限分配等所有用户相关接口),避免一个页面要import多个api文件;
- pages/按功能模块:home/、user/目录下放该模块所有页面,而非扁平化放所有.vue文件,便于权限控制(如user/目录下所有页面需登录);
- platform/隔离平台逻辑:platform/h5.js里封装H5特有API(如window.print()),platform/mp-weixin.js里封装小程序订阅消息,业务代码通过import { print } from '@/platform'调用,自动按平台加载对应实现;
- manifest.json与pages.json预置:manifest.json已填好appid、name、versionName等,pages.json已配置tabBar和常用页面,新建页面只需在pages.json里加一行,无需手动配路由。
这种结构带来的好处是:当产品经理说“在用户列表页加个导出按钮”,开发者知道去pages/user/list.vue改,去api/user.js加exportList()方法,去store/modules/user.js加导出状态,路径清晰,协作零歧义。
4.3 多端构建常见问题与排查指南
多端构建是痛点集中区。以下是我们在6个项目中遇到的高频问题及解决方案,按平台分类整理。
H5端问题
| 问题现象 | 原因分析 | 解决方案 |
|---|---|---|
页面白屏,控制台报Cannot find module 'uview-ui' | uView2未正确安装或路径错误 | 运行npm install uview-ui@2.0.32 --save,检查node_modules/uview-ui是否存在;在main.js中确认import uView from 'uview-ui'和Vue.use(uView)顺序正确 |
跨域请求失败,提示No 'Access-Control-Allow-Origin' header | 后端未配置CORS,且devServer代理未生效 | 检查vue.config.js中proxy配置的target地址是否可达;在浏览器Network面板确认请求URL是否以/api开头;若后端无法改CORS,启用devServer.proxy的onProxyRes注入头 |
| 构建后静态资源404 | vue.config.js中publicPath配置错误 | 确保vue.config.js中publicPath与Nginx配置一致,如Nginx配置location /admin/,则publicPath: '/admin/' |
微信小程序端问题
| 问题现象 | 原因分析 | 解决方案 |
|---|---|---|
| 真机调试弹窗不显示遮罩层 | uView2的u-popup在iOS真机渲染异常 | 在platform/mp-weixin.scss中添加.u-popup__mask { position: fixed !important; z-index: 9999 !important; } |
uni.uploadFile上传失败,提示request:fail | 小程序域名未配置或HTTPS证书问题 | 登录微信公众平台,在「开发管理」→「开发域名」中添加upload.example.com;确保上传地址为HTTPS;检查uni.uploadFile的url参数是否为绝对路径 |
分包加载失败,提示SubNVue not found | subNVue页面未在pages.json中正确配置 | 检查pages.json中subNVues节点,确认path指向正确的.nvue文件,且id唯一;在H5端调试时,subNVue不生效,需真机测试 |
App端问题
| 问题现象 | 原因分析 | 解决方案 |
|---|---|---|
| nvue页面样式错乱,文字重叠 | nvue对CSS支持有限,不支持flex-wrap、position: sticky等 | 使用<scroll-view>替代<view>实现滚动;用u-list组件替代原生<view>列表;在platform/app-plus.scss中重写关键样式 |
| 推送通知点击无响应 | App端未正确配置推送SDK | 在manifest.json中「SDK配置」→「推送」启用厂商通道;在App.vue的onLaunch中调用uni.getProvider和uni.requestPermission初始化推送;参考uni-app官方推送文档 |
构建APK失败,提示Failed to find Build Tools revision 30.0.3 | Android SDK Build-Tools版本缺失 | 打开Android Studio → SDK Manager → SDK Tools → 勾选Android SDK Build-Tools 30.0.3并安装;或在HBuilderX中「运行」→「运行到手机或模拟器」→「配置Android SDK路径」 |
通用排查技巧:
- 日志定位:H5端用浏览器Console;小程序端用微信开发者工具Console;App端用HBuilderX的「运行日志」面板;
- 版本锁定:package.json中uView2、uni-app、vue版本已锁定(如"uview-ui": "2.0.32"),避免自动升级引入breaking change;
- 缓存清理:构建前运行npm run clean(模板预置脚本,清空node_modules、dist、unpackage),避免旧文件干扰。
我们有个紧急上线,小程序审核被拒,原因是u-picker组件在iOS真机上滚动卡顿。按上述指南,在platform/mp-weixin.scss里加了-webkit-overflow-scrolling: touch !important;,当天重新提审通过。
5. 实战扩展与个性化定制建议
5.1 权限系统增强:从按钮级到数据级的细粒度控制
模板内置的v-has-perm指令解决了按钮级权限(如“删除按钮是否显示”),但真实业务常需数据级权限(如“用户列表中,只显示本人创建的用户”)。扩展方案如下:
后端配合
在API响应中增加dataScope字段:
{
"code": 200,
"data": {
"list": [...],
"dataScope": "self" // self:本人, dept:本部门, all:全部
}
}
前端增强
在store/modules/user.js中存储dataScope:
// store/modules/user.js
const state = {
dataScope: 'all' // 默认全部
}
const mutations = {
SET_DATA_SCOPE(state, scope) {
state.dataScope = scope
}
}
const actions = {
// 登录后从后端获取dataScope
login({ commit }, payload) {
return userApi.login(payload).then(res => {
commit('SET_DATA_SCOPE', res.dataScope)
// 其他逻辑...
})
}
}
在api/user.js中,getList请求自动追加scope参数:
// api/user.js
export const userApi = {
getList: (params) => {
const scope = store.state.user.dataScope
return request.get('/user/list', {
params: { ...params, dataScope: scope }
})
}
}
这样,前端无需在每个列表页手动传参,权限逻辑集中在一处。我们有个HR系统,员工只能看到自己部门的数据,只需后端返回dataScope: 'dept',前端自动过滤,代码零修改。
5.2 国际化(i18n)集成:三步接入多语言
模板预留了i18n扩展点,接入只需三步:
第一步:安装依赖
npm install vue-i18n@8.27.2 --save
第二步:创建语言包
// lang/zh-CN.js
export default {
home: {
welcome: '欢迎回来',
logout: '退出登录'
},
user: {
name: '姓名',
email: '邮箱'
}
}
// lang/en-US.js
export default {
home: {
welcome: 'Welcome back',
logout: 'Logout'
},
user: {
name: 'Name',
email: 'Email'
}
}
第三步:在main.js中注入
// main.js
import { createI18n } from 'vue-i18n'
import zhCN from '@/lang/zh-CN'
import enUS from '@/lang/en-US'
const i18n = createI18n({
locale: uni.getStorageSync('language') || 'zh-CN',
messages: {
'zh-CN': zhCN,
'en-US': enUS
}
})
const app = createSSRApp(App)
app.use(i18n)
调用时:
<!-- pages/home/index.vue -->
<template>
<u-button>{{ $t('home.logout') }}</u-button>
</template>
切换语言:
this.$i18n.locale = 'en-US'
uni.setStorageSync('language', 'en-US')
模板已预置lang/目录和i18n注入入口,接入成本低于1小时。
5.3 性能优化:首屏加载速度提升50%的实践
管理后台首屏慢,80%源于资源过大。模板已做基础优化,还可进一步提升:
- 图片懒加载:uView2的
u-image组件支持lazy-load,在长列表中启用; - 路由懒加载:
pages.json中配置"style": { "navigationBarTitleText": "用户管理" },页面JS按需加载; - uView2按需引入:如前所述,不
Vue.use(uView),而是在页面中import { uButton } from 'uview-ui'; - Webpack分包:在
vue.config.js中配置:
javascript configureWebpack: { optimization: { splitChunks: { chunks: 'all', cacheGroups: { uview: { name: 'chunk-uview', priority: 20, test: /[\\/]node_modules[\\/](uview-ui)[\\/]/, chunks: 'all' } } } } }
这样uView2代码单独打包为chunk-uview.js,可长期缓存。
我们有个数据大屏项目,首屏资源从2.1MB降到1.0MB,Lighthouse评分从52升到89。
这个模板不是终点,而是起点。它把那些“应该做但没人做”的工程化细节,变成了开箱即用的生产力。当你下次启动一个uniapp管理后台项目时,不必再纠结“先配uView还是先调跨域”,直接git clone,npm install,npm run dev:h5,然后专注写业务——这才是技术该有的样子。我个人在实际使用中发现,最省时间的不是某个高级功能,而是config.js里那一行apiBaseURL的统一管理,以及api/index.js里那个默默处理401跳转的拦截器。它们不炫酷,但每天为你省下半小时调试时间,而这半小时,足够你多写一个完整的审批流程。
简介:开箱即用的uniapp管理型前端项目模板,直接支持H5、微信小程序、App等多端构建。内置uView UI 2.x完整组件体系,开箱即用无需额外配置。所有接口请求统一收口到api.js,自动拼接baseURL,内置请求/响应拦截器,方便处理token、错误提示、加载状态等通用逻辑。环境配置集中写在config.js,开发阶段通过vue.config.js预设代理规则,一键解决跨域调试问题。状态管理采用轻量方案:可选Vuex或uni-app原生globalData配合mixins复用,$u.mixin.js和mixin.js封装了常用业务逻辑(如权限校验、表单重置、分页加载等)。样式层面提供uni.scss统一变量管理,demo.scss供快速参考;pages.定义标准页面路由,manifest.配置应用标识,launch.管理启动参数。配套标准npm脚本(dev、build:h5、build:mp-weixin等)、.gitignore规范及典型目录结构(res资源、unpackage输出、dist构建产物),适合中后台类跨端项目快速落地。

199

被折叠的 条评论
为什么被折叠?



