【Vue3项目实战系列一】—— 初始目录结构说明,了解每一个文件的作用

😉 你好呀,我是爱编程的Sherry,很高兴在这里遇见你!我是一名拥有十多年开发经验的前端工程师。这一路走来,面对困难时也曾感到迷茫,凭借不懈的努力和坚持,重新找到了前进的方向。我的人生格言是——认准方向,坚持不懈,你终将迎来辉煌!欢迎关注我😁,我将在这里分享工作中积累的经验和心得,希望我的分享能够给你带来帮助。
👉 关于【Vue3项目实战系列】专栏,本专栏旨在帮助新人粉丝全面快速的掌握vue3开发技术,在接下来的日子里我带着大家从零开始一个vue3项目,项目包含了框架搭建,用户登录,用户权限,表格增删改查,表单验证,等一套完整的用户管理系统的实践开发,学完可以帮助前端新人至少拥有一年工作经验,本专栏完全免费,欢迎关注并订阅!🎈
🎯如果你有任何困惑或疑问欢迎评论区给我留言哦,我收到后会第一时间为你答疑解惑😉

项目初始化完成了,本篇给大家讲讲,初始目录的结构以及他们的作用。

项目目录

/vue3-js-demo
├── node_modules/                   # 第三方包存储目录,存放项目依赖的npm包
├── public/                         # 静态资源目录,任何放置在 public 文件夹的静态资源都会被简单复制,而不经过构建工具处理
│   └── favicon.ico                 # 网站图标
├── src/                            # 源代码目录
│   ├── api/                        # 接口文件
│   ├── assets/                     # 公共资源目录,放图片、样式表等资源
│   ├── components/                 # 公共组件目录,存放可复用的Vue组件
│   ├── router/                     # 路由相关模块,配置路由规则和导航逻辑
│   ├── stores/                     # 状态管理容器相关模块,使用Pinia或Vuex管理应用状态(这里假设使用的是Pinia)
│   ├── views/                      # 路由页面组件存储目录,通常存放对应不同路由的页面级组件
│   ├── App.vue                     # 根组件,最终被替换渲染到index.html页面中的#app入口节点
│   └── main.js                     # 整个项目的启动入口模块,负责创建Vue实例并挂载到DOM
├── .gitignore                      # Git 的忽略配置文件,告诉Git项目中要忽略的文件或文件夹
├── index.html                      # HTML 模板文件,作为应用的入口点
├── package.json                    # 包说明文件,记录项目中使用的第三方包依赖信息等内容
│   ├── .editorconfig               # Editorconfig 帮助开发人员定义和维护跨编辑器(或IDE)的统一代码风格
│   ├── eslint.config.js            # ESLint 的配置文件,用于定义代码检查规则
│   ├── package-lock.json           # 记录安装时的包版本号,以保证自己或其他人在 npm install 时依赖的一致性
├── README.md                       # 项目说明文档,描述项目的基本信息和使用方法
├── vite.config.js                  # Vite 配置文件,配置Vite构建工具的行为
│   └── jsconfig.json               # JavaScript 语言服务配置文件,提供智能感知、转到定义等功能支持

接下来会详细讲解,每个文件的作用与用法,并结合实际项目给出示例代码,欢迎学习交流。

public 静态资源目录

它是一个静态资源目录,任何放置在 public 文件夹的静态资源都会被简单复制,而不经过构建工具处理。比如你可以存放图片、字体、html文件、favicon.ico和其他静态资源(任何你想要直接通过URL访问而不需要经过打包过程的文件,比如视频文件、PDF文档等)
在这里插入图片描述

src 源代码目录

用来存放项目源代码,这里面的代码都会被打包。

api 接口文件

将所有接口文件统一放在这个目录下,方便管理。

import request from '@/plugins/request';

// 树形列表
export function getTreeList (params) {
    return request({
        url: '/dataVisualization/treeList',
        method: 'get',
        params
    });
}

export function deleteTable (data) {
    return request({
        url: `/dataModel/deleteModel`,
        method: 'post',
        data
    });
}

这段代码就是文件里的内容,request是一个公共的封装方法,在这里我们只需要调用它就可以了。这样我们在调用接口的时候可以很简洁。

request代码
import axios from 'axios';
import { merge } from 'lodash';
import store from '@/store';
import util from '@/libs/util';
import { tansParams, blobValidate } from '@/libs';
import { errorCode } from '@/libs/config';
import Setting from '@/setting';
import Router from '@/router';
import ViewUIPlus, { Message, Notice } from 'view-ui-plus';
import { saveAs } from 'file-saver';

// 创建一个错误
function errorCreate (msg) {
    const err = new Error(msg);
    errorLog(err);
    throw err;
}

// 记录和显示错误
function errorLog (err) {
    // 添加到日志
    store.dispatch('admin/log/push', {
        message: '数据请求异常',
        type: 'error',
        meta: {
            error: err
        }
    });
    // 打印到控制台
    if (process.env.NODE_ENV === 'development') {
        util.log.error('>>>>>> Error >>>>>>');
        console.log(err);
    }
    // 显示提示,可配置使用 View UI Plus 的 $Message 还是 $Notice 组件来显示
    if (Setting.request.errorModalType === 'Message') {
        Message.error({
            content: err.message,
            duration: Setting.request.modalDuration
        });
    } else if (Setting.request.errorModalType === 'Notice') {
        Notice.error({
            title: '提示',
            desc: err.message,
            duration: Setting.request.modalDuration
        });
    }
}

// 默认content-type
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8';

// 创建一个 axios 实例
const service = axios.create({
    baseURL: Setting.request.apiBaseURL,
    timeout: Setting.request.timeout,
    withCredentials: Setting.request.withCredentials
});

// 不需携带token的接口地址
const noTokenUrl = [ '/code/captchaInfo', '/system/tenant/list', '/code', '/code/get', '/code/check', '/auth/oauth/token', '/register' ];

// 请求拦截器
service.interceptors.request.use(
    (config) => {
        // 是否需要设置 token
        const isToken = (config.headers || {}).isToken === false;

        // 除白名单外,其余接口请求附加token
        const token = util.cookies.get('token');
        if (
            token &&
            !noTokenUrl.includes(config.url) &&
            !isToken
        ) {
            config.headers['Authorization'] = 'Bearer ' + token;
        }

        if (config.isRefreshToken) {
          const refreshToken = util.cookies.get('refresh_token');
          config.headers['Authorization'] = 'Bearer ' + refreshToken;
        }
        // get请求映射params参数
        if (config.method === 'get' && config.params) {
            let url = config.url + '?' + tansParams(config.params);
            url = url.slice(0, -1);
            config.params = {};
            config.url = url;
        }
        return merge(config, Setting.request.requestConfig(config, util));
    },
    (error) => {
        // 发送失败
        console.log(error);
        Promise.reject(error);
    }
);

// 响应拦截器
service.interceptors.response.use(
    (response) => {
        // dataAxios 是 axios 返回数据中的 data
        const dataAxios = response.data;
        // 这个状态码是和后端约定的
        const { code } = dataAxios;
        // 根据 code 进行判断
        if (code === undefined) {
            // 如果没有 code 代表这不是项目后端开发的接口
            return dataAxios;
        } else {
            // 有 code 代表这是一个后端接口 可以进行进一步的判断
            switch (code) {
                case 200:
                    store.dispatch('admin/account/refreshAccessToken')
                    return dataAxios.data;
                case 401:
                    Message.error({
                        content: '登录凭证过期',
                        duration: Setting.request.modalDuration
                    });
                    store.dispatch('admin/account/logout', {
                        confirm: false,
                        expired: true,
                        vm: {}
                    });
                    break;
                case 404:
                    Router.replace({ name: '404' });
                    break;
                // case 'xxx':
                //     // [ 示例 ] 其它和后台约定的 code
                //     errorCreate(`[ code: xxx ] ${dataAxios.msg}: ${response.config.url}`);
                //     break;
                case 500:
                    // Router.replace({ name: '500' });
                    if (dataAxios.msg === '验证码错误') {
                        Router.replace({ name: 'login' });
                        Message.error({
                            content: dataAxios.msg,
                            duration: Setting.request.modalDuration
                        });
                        break;
                    }
                    errorCreate(`${dataAxios.msg}`);
                    break;
                // 针对二维转一维的编辑接口返回的错误信息回显
                case 5001:
                    return dataAxios
                default:
                    // 不是正确的 code
                    errorCreate(`${dataAxios.msg}`);
                    break;
            }
        }
    },
    (error) => {
        if (error && error.response) {
            switch (error.response.status) {
                case 400:
                    error.message = '请求错误';
                    break;
                case 401:
                    error.message = '未授权,请登录';
                    break;
                case 403:
                    error.message = '拒绝访问';
                    break;
                case 404:
                    error.message = `请求地址出错: ${error.response.config.url}`;
                    break;
                case 408:
                    error.message = '请求超时';
                    break;
                case 500:
                    error.message = '服务器内部错误';
                    break;
                case 501:
                    error.message = '服务未实现';
                    break;
                case 502:
                    error.message = '网关错误';
                    break;
                case 503:
                    error.message = '服务不可用';
                    break;
                case 504:
                    error.message = '网关超时';
                    break;
                case 505:
                    error.message = 'HTTP版本不受支持';
                    break;
                default:
                    break;
            }
        }
        errorLog(error);
        return Promise.reject(error);
    }
);



export default service;

assets 公共资源目录

放图片、字体、样式表等资源
在这里插入图片描述

components 公共组件目录

存放可复用的Vue组件,我们一般将与业务无关的组件放到这个目录下,不与业务挂钩可以无限制复用。

router路由相关模块

路由相关模块,配置路由规则和导航逻辑。
在这里插入图片描述

index.js
import {
  createRouter,
  createWebHistory,
  createWebHashHistory,
  createMemoryHistory
} from 'vue-router'

import ViewUIPlus from 'view-ui-plus'

import util from '@/libs/util'

import Setting from '@/setting'

import store from '@/store/index'

// 路由数据
import routes from './routes'
import { frameInRoutes } from '@/router/routes'

// 导出路由 在 main.js 里使用
let router = createRouter({
  history:
    Setting.routerMode === 'history'
      ? createWebHistory(Setting.routerBase)
      : Setting.routerMode === 'hash'
      ? createWebHashHistory(Setting.routerBase)
      : createMemoryHistory(Setting.routerBase),
  routes
})

// 登录后,无需比对 access 的路由
const whiteList = [
  '/login',
  '/user/profile',
  '/register',
  '/register/result',
  '/forget'
]

/**
 * 路由拦截
 * 权限验证
 */
router.beforeEach(async (to, from, next) => {
  if (Setting.showProgressBar) ViewUIPlus.LoadingBar.start()
  // 这里依据 token 判断是否登录,可视情况修改
  const token = util.cookies.get('token')
  if (token && token !== 'undefined') {
    if (to.path === '/login') {
      next({
        path: '/'
      })
    } else if (
      !store.state.admin.user.roles ||
      store.state.admin.user.roles.length === 0
    ) {
      // 没有用户信息,则获取用户信息
      await store.dispatch('admin/account/getUserInfo')
      const newRoutes = store.state.admin.menu.routes
      newRoutes.forEach((item) => {
        router.addRoute(item)
      })
      // 最后添加404
      router.addRoute({
        path: '/:pathMatch(.*)',
        name: '404',
        meta: {
          title: '404'
        },
        component: () => import('@/pages/system/error/404')
      })
      const allRoutes = frameInRoutes.concat(newRoutes)

      store.commit('admin/page/init', allRoutes, { root: true })
      next({ ...to, replace: true })
    } else {
      next()
    }
  } else {
    const isPermission = whiteList.includes(to.path)
      if (isPermission) {
      next()
      } else {
      next({
          name: 'login',
          query: {
            redirect: to.fullPath
          }
        })
      }
  }
})

router.afterEach((to) => {
  if (Setting.showProgressBar) ViewUIPlus.LoadingBar.finish()
  // 多页控制 打开新的页面
  if (
    !('meta' in to) ||
    (to.meta && !('tabs' in to.meta)) ||
    (to.meta && to.meta.tabs)
  ) {
    store.dispatch('admin/page/open', to)
  }
  // 更改标题
  util.title({
    title: to.meta.title
  })
  // 返回页面顶端
  window.scrollTo(0, 0)
})

export default router

routes.js
import { h } from 'vue'

import account from './modules/account'
import dashboard from './modules/dashboard'
import workplace from './modules/workplace'
import datamodel from './modules/datamodel'
import datatable from './modules/datatable'
import merged from './modules/merged'
import examine from './modules/examine'
import system from './modules/system'
import BasicLayout from '@/layouts/basic-layout'

/**
 * 在主框架内显示
 */

const frameIn = [
  {
    path: '/',
    redirect: {
      name: 'dashboard'
    },
    component: BasicLayout,
    children: [
      // 刷新页面 必须保留
      {
        path: 'refresh',
        name: 'refresh',
        hidden: true,
        component: {
          beforeRouteEnter (to, from, next) {
            next((instance) => instance.$router.replace(from.fullPath))
          },
          render: () => h()
        }
      },
      // 页面重定向 必须保留
      {
        path: 'redirect/:route*',
        name: 'redirect',
        hidden: true,
        component: {
          beforeRouteEnter (to, from, next) {
            next((instance) =>
              instance.$router.replace(JSON.parse(from.params.route))
            )
          },
          render: () => h()
        }
      }
    ]
  },
  dashboard,
  workplace,
  datamodel,
  datatable,
  merged,
  examine,
  account,
  system
]

/**
 * 在主框架之外显示
 */

const frameOut = [
  // 登录
  {
    path: '/login',
    name: 'login',
    meta: {
      title: '$t:page.login.title'
    },
    component: () => import('@/pages/account/login')
  },
  {
    path: '/dashboard/create',
    name: 'dashboard-create',
    meta: {
      title: '新建仪表板',
      closable: false
    },
    component: () => import('@/pages/dashboard/components/dataease-create')
  }
  // // 注册
  // {
  //   path: '/register',
  //   name: 'register',
  //   meta: {
  //     title: '$t:page.register.title'
  //   },
  //   component: () => import('@/pages/account/register')
  // },
  // // 注册结果
  // {
  //   path: '/register/result',
  //   name: 'register-result',
  //   meta: {
  //     title: '注册结果'
  //   },
  //   component: () => import('@/pages/account/register/result')
  // },
  // // 找回密码
  // {
  //   path: '/forget',
  //   name: 'forget',
  //   meta: {
  //     title: '找回密码'
  //   },
  //   component: () => import('@/pages/account/forget')
  // }
]

/**
 * 错误页面
 */

const errorPage = [
  {
    path: '/403',
    name: '403',
    meta: {
      title: '403'
    },
    component: () => import('@/pages/system/error/403')
  },
  {
    path: '/500',
    name: '500',
    meta: {
      title: '500'
    },
    component: () => import('@/pages/system/error/500')
  }
  // {
  //   path: '/:pathMatch(.*)',
  //   name: '404',
  //   meta: {
  //     title: '404'
  //   },
  //   component: () => import('@/pages/system/error/404')
  // }
]

// 导出需要显示菜单的
export const frameInRoutes = frameIn

// 重新组织后导出
export default [...frameIn, ...frameOut, ...errorPage]

示例代码仅用来参考,根据自己需要删减。

stores 状态管理容器相关模块

主要用于集中管理和共享应用的全局状态。

App.vue

根组件,最终被替换渲染到index.html页面中的#app入口节点

main.js

整个项目的启动入口模块,负责创建Vue实例并挂载到DOM

以上的示例代码仅用来参考,具体代码应该根据项目需要进行增减。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sherry Tian

打赏1元鼓励作者

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值