Skip to content

移动端项目结构

本文档详细介绍 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.vuelogin.vue
  • 组件文件名使用 PascalCase:UserCard.vueNavBar.vue
  • 工具函数文件名使用小写:request.jsauth.js

3. 代码组织

  • 使用 Vue 3 Composition API
  • 使用 <script setup> 语法糖
  • 合理拆分组件,提高复用性
  • 统一状态管理,避免 prop drilling

下一步

基于 MIT 许可发布