UniApp 移动端架构
技术栈
| 技术 | 用途 |
|---|---|
| Vue 3 Composition API | UI 框架(<script setup>) |
| Pinia + persistedstate | 状态管理 + 持久化 |
| vue-i18n | 国际化(zh-CN / zh-TW / en) |
| UnoCSS | 原子化 CSS(rem-to-rpx 转换) |
| SCSS | 主题变量系统 |
支持平台
| 平台 | 说明 |
|---|---|
| H5 | 浏览器端 |
| 微信小程序 | WeChat Mini Program |
| iOS | App(uni-app 引擎) |
| Android | App(uni-app 引擎) |
| 鸿蒙 | HarmonyOS |
项目结构
uniapp-zq/
├── api/ # API 接口
│ ├── auth.js # 认证 API
│ ├── dept.js # 部门
│ ├── file.js # 文件
│ ├── user.js # 用户
│ ├── workflow.js # 工作流
│ ├── form.js # 表单
│ └── ...
├── components/
│ ├── zq-icon/ # SVG 内联图标(Lucide 风格)
│ ├── zq-navbar/ # 自定义导航栏
│ ├── zq-tabbar/ # 自定义 TabBar
│ ├── zq-ui/ # UI 组件库(32 个组件)
│ ├── form-render/ # 表单渲染器
│ ├── signature-pad/ # 手写签名
│ └── wf-icon/ # 工作流图标
├── composables/
│ ├── useTheme.js # 主题 Composable
│ ├── useFileUrl.js # 文件 URL 管理
│ ├── useImageCache.js # 图片缓存
│ └── useFormulaCalculation.js # 公式计算
├── i18n/ # 国际化
│ ├── index.js
│ ├── zh-CN.js
│ ├── zh-TW.js
│ └── en.js
├── pages/ # 页面
│ ├── login/ # 登录
│ ├── index/ # 首页
│ ├── workflow/ # 工作流
│ ├── mine/ # 我的
│ └── demo/ # 组件演示
├── stores/ # Pinia 状态管理
├── styles/
│ ├── theme.scss # CSS 变量默认值
│ ├── theme-colors.js # 主题色配置
│ └── common.scss # 公共样式
├── utils/
│ ├── request.js # HTTP 请求封装
│ ├── route-guard.js # 路由守卫
│ └── ...
├── pages.json # 页面配置
├── manifest.json # 应用配置
├── main.js # 入口文件
└── App.vue # 根组件入口文件 (main.js)
javascript
import { createSSRApp } from 'vue'
import * as Pinia from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import i18n from './i18n'
import { setupRouteGuard } from './utils/route-guard'
export function createApp() {
const app = createSSRApp(App)
const pinia = Pinia.createPinia()
pinia.use(piniaPluginPersistedstate)
app.use(pinia)
app.use(i18n)
setupRouteGuard()
return { app, Pinia }
}页面配置 (pages.json)
easycom 自动注册
json
{
"easycom": {
"autoscan": true,
"custom": {
"^zq-(icon|navbar|tabbar)$": "@/components/zq-$1/zq-$1.vue",
"^zq-(.*)": "@/components/zq-ui/zq-$1/zq-$1.vue"
}
}
}所有 zq- 前缀组件无需手动导入,直接在模板中使用。
页面定义
json
{
"pages": [
{ "path": "pages/login/index", "style": { "navigationBarTitleText": "" } },
{ "path": "pages/index/index", "style": { "navigationStyle": "custom" } },
{ "path": "pages/workflow/index", "style": { "navigationStyle": "custom" } },
{ "path": "pages/mine/index", "style": { "navigationStyle": "custom" } }
],
"tabBar": {
"custom": true,
"list": [
{ "pagePath": "pages/index/index" },
{ "pagePath": "pages/workflow/index" },
{ "pagePath": "pages/mine/index" }
]
}
}自定义导航栏
使用 navigationStyle: "custom" 配合 zq-navbar 组件实现自定义导航栏。
主题系统
设计原则
- 所有颜色通过 CSS 变量控制,禁止硬编码颜色值
- 支持 light / dark 两种主题
- 主题色配置集中在
styles/theme-colors.js
theme-colors.js
定义 light 和 dark 两套颜色:
javascript
export const lightColors = {
'--color-primary': '#409eff',
'--color-success': '#67c23a',
'--color-warning': '#e6a23c',
'--color-error': '#f56c6c',
'--color-bg-page': '#f5f7fa',
'--color-bg-card': '#ffffff',
'--color-text-primary': '#303133',
'--color-text-secondary': '#909399',
'--color-border': '#dcdfe6',
// ...
}
export const darkColors = {
'--color-primary': '#409eff',
'--color-bg-page': '#1a1a2e',
'--color-bg-card': '#16213e',
'--color-text-primary': '#e4e7ed',
// ...
}useTheme Composable
在页面根元素设置 CSS 变量:
javascript
import { useTheme } from '../../composables/useTheme'
const { themeVars } = useTheme()
// 模板中使用
// <view class="page" :style="themeVars">使用示例
scss
.page {
background-color: var(--color-bg-page);
}
.card {
background-color: var(--color-bg-card);
border: 1rpx solid var(--color-border);
}
.title {
color: var(--color-text-primary);
}国际化
配置
支持三种语言,默认跟随系统:
| 语言 | 文件 | 说明 |
|---|---|---|
zh-CN | i18n/zh-CN.js | 简体中文 |
zh-TW | i18n/zh-TW.js | 繁体中文 |
en | i18n/en.js | 英文 |
使用方式
vue
<script setup>
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
</script>
<template>
<view>{{ t('common.save') }}</view>
</template>路由守卫
utils/route-guard.js 实现登录检查:
javascript
const WHITE_LIST = [
'/pages/login/index',
// 不需要登录的页面
]
// 拦截 navigateTo / redirectTo / switchTab
// 检查 token,无 token 且不在白名单则跳转登录页后端对接
请求配置
javascript
// H5 开发环境:使用 Vite 代理
const BASE_URL = '/basic-api'
// 其他平台:完整 URL
const BASE_URL = 'http://192.168.1.100:8000'登录认证
javascript
POST /api/core/login
{
"username": "admin",
"password": "xxx"
}
// 响应
{
"accessToken": "eyJ...",
"refreshToken": "eyJ...",
"tokenType": "bearer",
"expireTime": 86400
}Token 存储在 Pinia(持久化到 Storage),请求拦截器自动添加 Authorization: Bearer {token}。
页面模板
vue
<template>
<view class="page" :style="themeVars">
<zq-navbar :title="t('page.title')" show-back />
<view class="page-content">
<!-- 页面内容 -->
</view>
<!-- TabBar 页面 -->
<zq-tabbar />
</view>
</template>
<script setup>
import { useI18n } from 'vue-i18n'
import { useTheme } from '../../composables/useTheme'
const { t } = useI18n()
const { themeVars } = useTheme()
</script>
<style lang="scss" scoped>
.page {
min-height: 100vh;
background-color: var(--color-bg-page);
}
.page-content {
padding: 24rpx;
}
</style>生命周期
必须使用 UniApp 提供的生命周期,禁止使用 Vue 原生生命周期:
| UniApp | Vue 原生 | 说明 |
|---|---|---|
onLoad | 页面加载 | |
onShow | 页面显示 | |
onHide | - | 页面隐藏 |
onUnload | 页面卸载 | |
onPullDownRefresh | - | 下拉刷新 |
onReachBottom | - | 触底加载 |
尺寸单位
使用 rpx(750rpx = 屏幕宽度),确保多端适配:
scss
.card {
width: 690rpx; // 750 - 30*2 边距
padding: 24rpx;
border-radius: 16rpx;
margin: 0 30rpx 24rpx;
}注意事项
- 禁止使用
document、window等浏览器 API,使用uni.xxx替代 - 禁止使用 emoji,使用
zq-icon组件替代 - 样式避免复杂 CSS 选择器嵌套(小程序兼容性)
- 微信小程序中嵌套 flex + scroll-view 高度不可靠,使用明确像素高度