移动端项目结构
本文档详细介绍 vue-uniapp-template 项目的目录结构和各模块职责。
整体结构
vue-uniapp-template
├── pages/ # 页面目录
│ ├── index/ # 首页
│ ├── login/ # 登录页
│ ├── user/ # 用户中心
│ └── ...
├── components/ # 组件目录
│ ├── common/ # 公共组件
│ └── business/ # 业务组件
├── static/ # 静态资源
│ ├── images/ # 图片
│ ├── icons/ # 图标
│ └── tabbar/ # 底部导航图标
├── store/ # 状态管理
│ ├── modules/ # 模块
│ │ ├── user.js # 用户模块
│ │ └── app.js # 应用模块
│ └── index.js
├── utils/ # 工具函数
│ ├── request.js # 请求封装
│ ├── auth.js # 认证工具
│ ├── validate.js # 验证工具
│ └── ...
├── api/ # 接口定义
│ ├── user.js # 用户接口
│ ├── auth.js # 认证接口
│ └── ...
├── config/ # 配置文件
│ └── config.js # 环境配置
├── uni_modules/ # uni 插件
├── App.vue # 应用配置
├── main.js # 入口文件
├── manifest.json # 应用配置
├── pages.json # 页面路由
└── uni.scss # 全局样式变量核心目录详解
1. pages 目录(页面)
pages 目录存放所有页面,每个页面是一个独立的文件夹。
pages/
├── index/ # 首页模块
│ ├── index.vue # 首页主文件
│ └── index.scss # 首页样式(可选)
├── login/ # 登录模块
│ ├── login.vue
│ └── components/ # 页面私有组件
│ └── LoginForm.vue
├── user/ # 用户中心
│ ├── user.vue
│ ├── profile.vue # 个人资料
│ └── setting.vue # 设置页
└── system/ # 系统管理
├── user/
│ ├── index.vue # 用户列表
│ └── detail.vue # 用户详情
└── role/
└── index.vue # 角色列表页面示例
vue
<template>
<view class="container">
<view class="header">
<text class="title">{{ title }}</text>
</view>
<view class="content">
<!-- 页面内容 -->
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useUserStore } from '@/store/modules/user'
const userStore = useUserStore()
const title = ref('首页')
onMounted(() => {
loadData()
})
const loadData = async () => {
// 加载数据
}
</script>
<style scoped lang="scss">
.container {
padding: 20rpx;
.header {
margin-bottom: 20rpx;
.title {
font-size: 32rpx;
font-weight: bold;
}
}
}
</style>2. components 目录(组件)
components 目录存放公共组件和业务组件。
components/
├── common/ # 公共组件
│ ├── Empty.vue # 空状态
│ ├── Loading.vue # 加载中
│ ├── NavBar.vue # 导航栏
│ └── TabBar.vue # 底部导航
└── business/ # 业务组件
├── UserCard.vue # 用户卡片
├── RoleSelect.vue # 角色选择器
└── DeptTree.vue # 部门树组件示例
vue
<!-- components/common/Empty.vue -->
<template>
<view class="empty">
<image :src="image" class="empty-image" />
<text class="empty-text">{{ text }}</text>
<button v-if="showButton" @click="handleClick">
{{ buttonText }}
</button>
</view>
</template>
<script setup>
defineProps({
image: {
type: String,
default: '/static/images/empty.png'
},
text: {
type: String,
default: '暂无数据'
},
showButton: {
type: Boolean,
default: false
},
buttonText: {
type: String,
default: '返回'
}
})
const emit = defineEmits(['click'])
const handleClick = () => {
emit('click')
}
</script>
<style scoped lang="scss">
.empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 100rpx 0;
&-image {
width: 200rpx;
height: 200rpx;
margin-bottom: 20rpx;
}
&-text {
font-size: 28rpx;
color: #999;
}
}
</style>3. store 目录(状态管理)
使用 Pinia 进行状态管理。
store/
├── modules/ # 模块
│ ├── user.js # 用户状态
│ ├── app.js # 应用状态
│ └── dict.js # 字典状态
└── index.js # Store 入口User Store 示例
javascript
// store/modules/user.js
import { defineStore } from 'pinia'
import { login, getUserInfo } from '@/api/auth'
export const useUserStore = defineStore('user', {
state: () => ({
token: uni.getStorageSync('token') || '',
userInfo: null,
permissions: []
}),
getters: {
// 是否已登录
isLogin: (state) => !!state.token,
// 用户名
username: (state) => state.userInfo?.username || '',
// 是否有权限
hasPermission: (state) => {
return (permission) => {
return state.permissions.includes(permission)
}
}
},
actions: {
// 登录
async login(loginForm) {
try {
const res = await login(loginForm)
this.token = res.data.accessToken
uni.setStorageSync('token', this.token)
return Promise.resolve(res)
} catch (error) {
return Promise.reject(error)
}
},
// 获取用户信息
async getUserInfo() {
try {
const res = await getUserInfo()
this.userInfo = res.data
this.permissions = res.data.permissions
return Promise.resolve(res)
} catch (error) {
return Promise.reject(error)
}
},
// 登出
logout() {
this.token = ''
this.userInfo = null
this.permissions = []
uni.removeStorageSync('token')
// 跳转到登录页
uni.reLaunch({
url: '/pages/login/login'
})
}
}
})4. utils 目录(工具函数)
utils/
├── request.js # 请求封装
├── auth.js # 认证工具
├── validate.js # 验证工具
├── format.js # 格式化工具
└── permission.js # 权限工具request.js - 请求封装
javascript
import config from '@/config/config'
// 获取环境配置
const env = process.env.NODE_ENV
const baseURL = config[env].baseURL
const timeout = config[env].timeout
/**
* 发起请求
*/
const request = (options) => {
return new Promise((resolve, reject) => {
// 获取 Token
const token = uni.getStorageSync('token')
uni.request({
url: baseURL + options.url,
method: options.method || 'GET',
data: options.data || {},
timeout: options.timeout || timeout,
header: {
'Content-Type': 'application/json',
'Authorization': token ? `Bearer ${token}` : ''
},
success: (res) => {
// 成功响应
if (res.statusCode === 200) {
const data = res.data
// 业务成功
if (data.code === '00000') {
resolve(data)
} else {
// 业务失败
uni.showToast({
title: data.msg || '请求失败',
icon: 'none'
})
reject(data)
}
}
// Token 过期
else if (res.statusCode === 401) {
uni.showToast({
title: '登录已过期,请重新登录',
icon: 'none'
})
// 跳转到登录页
setTimeout(() => {
uni.reLaunch({
url: '/pages/login/login'
})
}, 1500)
reject(res)
}
// 其他错误
else {
uni.showToast({
title: '请求失败',
icon: 'none'
})
reject(res)
}
},
fail: (err) => {
// 网络错误
uni.showToast({
title: '网络连接失败',
icon: 'none'
})
reject(err)
}
})
})
}
// 导出请求方法
export default request
// 快捷方法
export const get = (url, data, options = {}) => {
return request({
url,
method: 'GET',
data,
...options
})
}
export const post = (url, data, options = {}) => {
return request({
url,
method: 'POST',
data,
...options
})
}
export const put = (url, data, options = {}) => {
return request({
url,
method: 'PUT',
data,
...options
})
}
export const del = (url, data, options = {}) => {
return request({
url,
method: 'DELETE',
data,
...options
})
}5. api 目录(接口定义)
api/
├── auth.js # 认证接口
├── user.js # 用户接口
├── role.js # 角色接口
└── menu.js # 菜单接口api/user.js 示例
javascript
import request from '@/utils/request'
/**
* 获取用户分页列表
*/
export function getUserPage(params) {
return request({
url: '/api/v1/users/page',
method: 'GET',
data: params
})
}
/**
* 获取用户详情
*/
export function getUserDetail(id) {
return request({
url: `/api/v1/users/${id}/form`,
method: 'GET'
})
}
/**
* 新增用户
*/
export function addUser(data) {
return request({
url: '/api/v1/users',
method: 'POST',
data
})
}
/**
* 更新用户
*/
export function updateUser(id, data) {
return request({
url: `/api/v1/users/${id}`,
method: 'PUT',
data
})
}
/**
* 删除用户
*/
export function deleteUser(ids) {
return request({
url: `/api/v1/users/${ids}`,
method: 'DELETE'
})
}配置文件
pages.json - 页面配置
json
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页",
"enablePullDownRefresh": true
}
},
{
"path": "pages/login/login",
"style": {
"navigationBarTitleText": "登录",
"navigationStyle": "custom"
}
},
{
"path": "pages/user/user",
"style": {
"navigationBarTitleText": "我的"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "有来管理系统",
"navigationBarBackgroundColor": "#FFFFFF",
"backgroundColor": "#F8F8F8"
},
"tabBar": {
"color": "#7A7E83",
"selectedColor": "#007AFF",
"borderStyle": "black",
"backgroundColor": "#FFFFFF",
"list": [
{
"pagePath": "pages/index/index",
"iconPath": "static/tabbar/home.png",
"selectedIconPath": "static/tabbar/home-active.png",
"text": "首页"
},
{
"pagePath": "pages/user/user",
"iconPath": "static/tabbar/user.png",
"selectedIconPath": "static/tabbar/user-active.png",
"text": "我的"
}
]
},
"condition": {
"current": 0,
"list": [
{
"name": "首页",
"path": "pages/index/index",
"query": ""
}
]
}
}manifest.json - 应用配置
json
{
"name": "有来管理系统",
"appid": "__UNI__XXXXXX",
"description": "",
"versionName": "1.0.0",
"versionCode": "100",
// H5 配置
"h5": {
"title": "有来管理系统",
"template": "index.html",
"router": {
"mode": "hash",
"base": "/"
},
"optimization": {
"treeShaking": {
"enable": true
}
}
},
// 微信小程序配置
"mp-weixin": {
"appid": "",
"setting": {
"urlCheck": false,
"es6": true,
"minified": true
},
"usingComponents": true
},
// App 配置
"app-plus": {
"splashscreen": {
"alwaysShowBeforeRender": true,
"autoclose": true
},
"modules": {},
"distribute": {
"android": {
"permissions": []
},
"ios": {},
"sdkConfigs": {}
}
}
}最佳实践
1. 目录结构规范
- 页面文件放在
pages目录 - 公共组件放在
components/common - 业务组件放在
components/business - 接口定义放在
api目录 - 工具函数放在
utils目录
2. 命名规范
- 页面文件名使用小写:
index.vue、login.vue - 组件文件名使用 PascalCase:
UserCard.vue、NavBar.vue - 工具函数文件名使用小写:
request.js、auth.js
3. 代码组织
- 使用 Vue 3 Composition API
- 使用
<script setup>语法糖 - 合理拆分组件,提高复用性
- 统一状态管理,避免 prop drilling
下一步
- 🎨 学习 组件使用
- 🔐 查看 后端对接
- 📖 阅读 UniApp 官方文档
