vue-工程项目结构

基于 vue.js 的前端开发环境,用于前后端分离后的单页应用开发,可以在开发时使用 ES Next、scss 等最新语言特性。项目包含:
 
基础库: vue.js、vue-router、vuex、whatwg-fetch
编译/打包工具:webpack、babel、node-sass
单元测试工具:karma、mocha、sinon-chai
本地服务器:express
目录结构
 
├── README.md            项目介绍
├── index.html           入口页面
├── build              构建脚本目录
│  ├── build-server.js         运行本地构建服务器,可以访问构建后的页面
│  ├── build.js            生产环境构建脚本
│  ├── dev-client.js          开发服务器热重载脚本,主要用来实现开发阶段的页面自动刷新
│  ├── dev-server.js          运行本地开发服务器
│  ├── utils.js            构建相关工具方法
│  ├── webpack.base.conf.js      wabpack基础配置
│  ├── webpack.dev.conf.js       wabpack开发环境配置
│  └── webpack.prod.conf.js      wabpack生产环境配置
├── config             项目配置
│  ├── dev.env.js           开发环境变量
│  ├── index.js            项目配置文件
│  ├── prod.env.js           生产环境变量
│  └── test.env.js           测试环境变量
├── mock              mock数据目录
│  └── hello.js
├── package.json          npm包配置文件,里面定义了项目的npm脚本,依赖包等信息
├── src               源码目录 
│  ├── main.js             入口js文件
│  ├── app.vue             根组件
│  ├── components           公共组件目录
│  │  └── title.vue
│  ├── assets             资源目录,这里的资源会被wabpack构建
│  │  └── images
│  │    └── logo.png
│  ├── routes             前端路由
│  │  └── index.js
│  ├── store              应用级数据(state)
│  │  └── index.js
│  └── views              页面目录
│    ├── hello.vue
│    └── notfound.vue
├── static             纯静态资源,不会被wabpack构建。
└── test              测试文件目录(unit&e2e)
  └── unit              单元测试
    ├── index.js            入口脚本
    ├── karma.conf.js          karma配置文件
    └── specs              单测case目录
      └── Hello.spec.js
环境安装
本项目依赖 node.js, 使用前先安装 node.js 和 cnpm(显著提升依赖包的下载速度)。
 
自行下载并安装 node.js: https://nodejs.org/en/download/
 
然后安装 cnpm 命令:
 
npm install -g cnpm --registry=https://registry.npm.taobao.org
快速开始
git clone https://github.com/hanan198501/vue-spa-template.git
cd vue-spa-template
cnpm install
npm run dev
命令列表:
 
开启本地开发服务器,监控项目文件的变化,实时构建并自动刷新浏览器,浏览器访问 http://localhost:8081
npm run dev
 
使用生产环境配置构建项目,构建好的文件会输出到 "dist" 目录,
npm run build
 
运行构建服务器,可以查看构建的页面
npm run build-server
 
运行单元测试
npm run unit
 
前后端分离
项目基于 spa 方式实现前后端分离,服务器通过 nginx 区分前端页面和后端接口请求,分发到不同服务。前端物理上只有一个入口页面, 路由由前端控制(基于vue-router),根据不同的 url 加载相应数据和组件进行渲染。
 
接口 mock
前后端分离后,开发前需要和后端同学定义好接口信息(请求地址,参数,返回信息等),前端通过 mock 的方式,即可开始编码,无需等待后端接口 ready。 项目的本地开发服务器是基于 express 搭建的,通过 express 的中间件机制,我们已经在 dev-server 中添加了接口 mock 功能。 开发时,接口的 mock 数据统一放在 mock 目录下,每个文件内如下:
 
module.exports = {
 
 // 接口地址
 api: '/api/hello',
 
 // 返回数据 参考http://expressjs.com/zh-cn/4x/api.html
 response: function (req, res) {
  res.send(`
   

hello vue!

`); } } 模块化 开发时可以使用 ES2015 module 语法,构建时每个文件会编译成 amd 模块。 组件化 整个应用通过 vue 组件的方式搭建起来,通过 vue-router 控制相应组件的展现,组件树结构如下: app.vue 根组件(整个应用只有一个) ├──view1.vue 页面级组件,放在 views 目录里面,有子组件时,可以建立子目录 │ ├──component1.vue 功能组件,公用的放在 components 目录,否则放在 views 子目录 │ ├──component2.vue │ └──component3.vue ├──view2.vue │ ├──component1.vue │ └──component4.vue └──view3.vue ├──component5.vue …… 单元测试 可以为每个组件编写单元测试,放在 test/unit/specs 目录下面, 单元测试用例的目录结构建议和测试的文件保持一致(相对于src),每个测试用例文件名以 .spec.js结尾。 执行 npm run unit 时会遍历所有的 spec.js 文件,产出测试报告在 test/unit/coverage 目录。   

 在这里插入图片描述

components文件夹

用来存放Vue 组件。个人建议,把每一个组件中使用到的image图片放置到对应的组件子文件目录下,便于统一的管理


Node_modules/npm安装的该项目的依赖库


vuex/文件夹

存放的是和 Vuex store 相关的东西(state对象,actions,mutations)


router/文件夹

存放的是跟vue-router相关的路由配置项

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

/* Layout */
import Layout from '@/layout'

/**
 * Note: 路由配置项
 *
 * hidden: true                     // 当设置 true 的时候该路由不会再侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1
 * alwaysShow: true                 // 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面
 *                                  // 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面
 *                                  // 若你想不管路由下面的 children 声明的个数都显示你的根路由
 *                                  // 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由
 * redirect: noRedirect             // 当设置 noRedirect 的时候该路由在面包屑导航中不可被点击
 * name:'router-name'               // 设定路由的名字,一定要填写不然使用时会出现各种问题
 * query: '{"id": 1, "name": "ry"}' // 访问路由的默认传递参数
 * roles: ['admin', 'common']       // 访问路由的角色权限
 * permissions: ['a:a:a', 'b:b:b']  // 访问路由的菜单权限
 * meta : {
    noCache: true                   // 如果设置为true,则不会被  缓存(默认 false)
    title: 'title'                  // 设置该路由在侧边栏和面包屑中展示的名字
    icon: 'svg-name'                // 设置该路由的图标,对应路径src/assets/icons/svg
    breadcrumb: false               // 如果设置为false,则不会在breadcrumb面包屑中显示
    activeMenu: '/system/user'      // 当路由设置了该属性,则会高亮相对应的侧边栏。
  }
 */

// 公共路由
export const constantRoutes = [
  {
    path: '/redirect',
    component: Layout,
    hidden: true,
    children: [
      {
        path: '/redirect/:path(.*)',
        component: () => import('@/views/redirect')
      }
    ]
  },
  {
    path: '/login',
    component: () => import('@/views/login'),
    hidden: true
  },
  {
    path: '/register',
    component: () => import('@/views/register'),
    hidden: true
  },
  {
    path: '/404',
    component: () => import('@/views/error/404'),
    hidden: true
  },
  {
    path: '/401',
    component: () => import('@/views/error/401'),
    hidden: true
  },
  {
    path: '',
    component: Layout,
    redirect: 'index',
    children: [
      {
        path: 'index',
        component: () => import('@/views/index'),
        name: 'Index',
        meta: { title: '首页', icon: 'dashboard', affix: true }
      }
    ]
  },
  {
    path: '/user',
    component: Layout,
    hidden: true,
    redirect: 'noredirect',
    children: [
      {
        path: 'profile',
        component: () => import('@/views/system/user/profile/index'),
        name: 'Profile',
        meta: { title: '个人中心', icon: 'user' }
      }
    ]
  }
]

// 动态路由,基于用户权限动态去加载
export const dynamicRoutes = [
  {
    path: '/system/user-auth',
    component: Layout,
    hidden: true,
    permissions: ['system:user:edit'],
    children: [
      {
        path: 'role/:userId(\\d+)',
        component: () => import('@/views/system/user/authRole'),
        name: 'AuthRole',
        meta: { title: '分配角色', activeMenu: '/system/user' }
      }
    ]
  },
  {
    path: '/system/role-auth',
    component: Layout,
    hidden: true,
    permissions: ['system:role:edit'],
    children: [
      {
        path: 'user/:roleId(\\d+)',
        component: () => import('@/views/system/role/authUser'),
        name: 'AuthUser',
        meta: { title: '分配用户', activeMenu: '/system/role' }
      }
    ]
  },
  {
    path: '/system/dict-data',
    component: Layout,
    hidden: true,
    permissions: ['system:dict:list'],
    children: [
      {
        path: 'index/:dictId(\\d+)',
        component: () => import('@/views/system/dict/data'),
        name: 'Data',
        meta: { title: '字典数据', activeMenu: '/system/dict' }
      }
    ]
  },
  {
    path: '/monitor/job-log',
    component: Layout,
    hidden: true,
    permissions: ['monitor:job:list'],
    children: [
      {
        path: 'index',
        component: () => import('@/views/monitor/job/log'),
        name: 'JobLog',
        meta: { title: '调度日志', activeMenu: '/monitor/job' }
      }
    ]
  },
  {
    path: '/tool/gen-edit',
    component: Layout,
    hidden: true,
    permissions: ['tool:gen:edit'],
    children: [
      {
        path: 'index/:tableId(\\d+)',
        component: () => import('@/views/tool/gen/editTable'),
        name: 'GenEdit',
        meta: { title: '修改生成配置', activeMenu: '/tool/gen' }
      }
    ]
  }
]

// 防止连续点击多次路由报错
let routerPush = Router.prototype.push;
Router.prototype.push = function push(location) {
  return routerPush.call(this, location).catch(err => err)
}

export default new Router({
  mode: 'history', // 去掉url中的#
  scrollBehavior: () => ({ y: 0 }),
  routes: constantRoutes
})


build/文件

是 webpack 的打包编译配置文件


static/文件夹

存放一些静态的、较少变动的image或者css文件


config/文件夹

1.存放的是一些配置项,比如服务器访问的端口配置等

2.vue3的项目没有config文件夹

 vue-工程项目结构_第1张图片

 index.js

'use strict'
// Template version: 1.3.1
// see http://vuejs-templates.github.io/webpack for documentation.
// 引入 node.js 的路径模块
const path = require('path')

module.exports = {
  dev: {

    // Paths
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    // 建一个虚拟 api 服务器用来代理本机的请求,只能用于开发模式
    proxyTable: {},

    // 服务 host
    host: 'localhost',
    // 服务端口号
    port: 8080,
    // 自动打开浏览器浏览器
    autoOpenBrowser: false,
    errorOverlay: true,
    notifyOnErrors: true,
    poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-

    // Use Eslint Loader?
    // If true, your code will be linted during bundling and
    // linting errors and warnings will be shown in the console.
    useEslint: true,
    // If true, eslint errors and warnings will also be shown in the error overlay
    // in the browser.
    showEslintErrorsInOverlay: false,

    /**
     * Source Maps
     */

    // https://webpack.js.org/configuration/devtool/#development
    devtool: 'cheap-module-eval-source-map',

    // If you have problems debugging vue-files in devtools,
    // set this to false - it *may* help
    // https://vue-loader.vuejs.org/en/options.html#cachebusting
    cacheBusting: true,

    cssSourceMap: true
  },

  build: {
    // 下面是相对路径的拼接,假如当前跟目录是 config,那么下面配置的 index 属性的属性值就是 dist/index.html
    index: path.resolve(__dirname, '../dist/index.html'),

    // 定义的是静态资源的根目录 也就是 dist 目录
    assetsRoot: path.resolve(__dirname, '../dist'),
    // 定义的是静态资源根目录的子目录 static,也就是 dist 目录下面的 static
    assetsSubDirectory: 'static',
    // 定义的是静态资源的公开路径,也就是真正的引用路径
    assetsPublicPath: '/',

    /**
     * Source Maps
     */
    productionSourceMap: true,
    // https://webpack.js.org/configuration/devtool/#production
    devtool: '#source-map',

    // Gzip off by default as many popular static hosts such as
    // Surge or Netlify already gzip all static assets for you.
    // Before setting to `true`, make sure to:
    // npm install --save-dev compression-webpack-plugin
    // 是否压缩代码
    productionGzip: false,
    // 定义要压缩哪些类型的文件
    productionGzipExtensions: ['js', 'css'],

    // Run the build command with an extra argument to
    // View the bundle analyzer report after build finishes:
    // `npm run build --report`
    // Set to `true` or `false` to always turn it on or off
    // 编译完成后的报告,可以通过设置值为 true 和 false 来开启或关闭
    bundleAnalyzerReport: process.env.npm_config_report
  }
}

 dev.env.js

'use strict'
// 配置文件合并模块
const merge = require('webpack-merge')
// 导入 prod.env.js 配置文件
const prodEnv = require('./prod.env')

module.exports = merge(prodEnv, {
  // 导出一个对象,NODE_ENV 是一个环境变量,指定 development 环境
  NODE_ENV: '"development"'
})

 prod.env.js

'use strict'
module.exports = {
  // 导出一个对象,NODE_ENV 是一个环境变量,指定 production 环境
  NODE_ENV: '"production"'
}

test.env.js 

'use strict'
// 配置文件合并模块
const merge = require('webpack-merge')
// 导入 dev.env.js 配置文件
const devEnv = require('./dev.env')

module.exports = merge(devEnv, {
  // 导出一个对象,NODE_ENV 是一个环境变量,指定 testing 环境
  NODE_ENV: '"testing"'
})


dist/文件夹

一开始是不存在,在我们的项目经过 build 之后才会产出


App.vue根组件

1.所有的子组件都将在这里被引用

2.作为单页面组件,只要写一个  就好了,所有的页面都会在这儿进行切换




index.html

整个项目的入口文件,将会引用我们的根组件 App.vue


main.js

入口文件的 js 逻辑,在webpack 打包之后将被注入到 index.html 中

//导入第三方库 vue
import Vue from 'vue'
//导入第三方库 js-cookie
import Cookies from 'js-cookie'

//导入第三方库 Element 
import Element from 'element-ui'
//导入一个css
import './assets/styles/element-variables.scss'

//导入一个css
import '@/assets/styles/index.scss' // global css
//导入一个css
import '@/assets/styles/ruoyi.scss' // ruoyi css

//导入本地文件 APP.VUE
import App from './App'
//导入本地文件夹store, 默认寻找index.vue
import store from './store'

//导入本地文件夹router, 默认寻找index.vue
import router from './router'
//导入本地文件夹directive , 默认寻找index.vue
import directive from './directive' // directive

//导入本地文件夹plugins , 默认寻找index.vue
import plugins from './plugins' // plugins
//导入本地文件request,解构download 方法
import { download } from '@/utils/request'

//导入本地文件夹icons 
import './assets/icons' // icon
//导入本地文件夹permission 
import './permission' // permission control

//导入本地文件data,解构getDicts 方法
import { getDicts } from "@/api/system/dict/data";

//导入本地文件config,解构getConfigKey方法
import { getConfigKey } from "@/api/system/config";

//导入本地文件ruoyi,解构这些方法
import { parseTime, resetForm, addDateRange, selectDictLabel, selectDictLabels, handleTree } from "@/utils/ruoyi";

// 分页组件,默认寻找index.vue
import Pagination from "@/components/Pagination";
// 自定义表格工具组件,默认寻找index.vue
import RightToolbar from "@/components/RightToolbar"

// 富文本组件,默认寻找index.vue
import Editor from "@/components/Editor"
// 文件上传组件,默认寻找index.vue
import FileUpload from "@/components/FileUpload"

// 图片上传组件,默认寻找index.vue
import ImageUpload from "@/components/ImageUpload"
// 图片预览组件,默认寻找index.vue
import ImagePreview from "@/components/ImagePreview"

// 字典标签组件,默认寻找index.vue
import DictTag from '@/components/DictTag'
// 头部标签组件,默认寻找index.vue
import VueMeta from 'vue-meta'

// 字典数据组件,默认寻找index.vue
import DictData from '@/components/DictData'

// 全局方法挂载
Vue.prototype.getDicts = getDicts
Vue.prototype.getConfigKey = getConfigKey
Vue.prototype.parseTime = parseTime
Vue.prototype.resetForm = resetForm
Vue.prototype.addDateRange = addDateRange
Vue.prototype.selectDictLabel = selectDictLabel
Vue.prototype.selectDictLabels = selectDictLabels
Vue.prototype.download = download
Vue.prototype.handleTree = handleTree

// 全局组件挂载
Vue.component('DictTag', DictTag)
Vue.component('Pagination', Pagination)
Vue.component('RightToolbar', RightToolbar)
Vue.component('Editor', Editor)
Vue.component('FileUpload', FileUpload)
Vue.component('ImageUpload', ImageUpload)
Vue.component('ImagePreview', ImagePreview)


//Vue 引入插件( 要想用Vue.use()的方式,首先你得将组件转换成插件的形式才可以,需要额外在src下建立一个plugin的文件夹,将自己封装的组件引入到该文件夹下(也可以不引入,引入了比较方便些),然后再建立一个index.js的文件)

Vue.use(directive)
Vue.use(plugins)
Vue.use(VueMeta)
DictData.install()



Vue.use(Element, {
  size: Cookies.get('size') || 'medium' // set element-ui default size
})

//去除错误提示
Vue.config.productionTip = false

//挂载路由,vuex到APP上
new Vue({
  el: '#app',
  router,
  store,
  render: h => h(App)
})

vue.config.js

1.设置自动打开和端口号

2.process.env.NODE_ENV表示现在的所处的环境,开发环境和打包环境

// const path = require('path');
module.exports = {
  /** 区分打包环境与开发环境
   * process.env.NODE_ENV==='production'  (打包环境)
   * process.env.NODE_ENV==='development' (开发环境)
   * baseUrl: process.env.NODE_ENV==='production'?"https://xxx":'',
   */
  // 项目部署的基础路径
  // 我们默认假设你的应用将会部署在域名的根部,
  // 例如 https://www.my-app.com/
  // 如果你的应用部署在一个子路径下,那么你需要在这里
  // 指定子路径。比如将你的应用部署在
  // https://www.foobar.com/my-app/
  // 那么将这个值改为 '/my-app/'
 
  //baseUrl: '/',//vue-cli3.3以下版本使用
  publicPath: '/',//vue-cli3.3+新版本使用
 
  // 构建好的文件输出到哪里
  outputDir: "dist",
 
  // assetsDir: "base" //静态资源打包地址
 
  //以多页模式构建应用程序。
  pages: undefined,
 
  // 是否在保存时使用‘eslint-loader’进行检查 // 有效值: true | false | 'error'
  // 当设置为‘error’时,检查出的错误会触发编译失败
  lintOnSave: true,
 
  // 使用带有浏览器内编译器的完整构建版本,是否使用包含运行时编译器的 Vue 构建版本
  runtimeCompiler: false,
 
  // babel-loader默认会跳过`node_modules`依赖. // 通过这个选项可以显示转译一个依赖
  // 默认babel-loader忽略mode_modules,这里可增加例外的依赖包名
  transpileDependencies: [],
 
  // 是否在构建生产包时生成 sourceMap 文件,false将提高构建速度  映射文件 打包时使用
  productionSourceMap: false,
 
  // 调整内部的webpack配置.
  // see https://github.com/vuejs/vue-cli/blob/dev/docs/webpack.md
  chainWebpack: () => { },
  // chainWebpack: () => {
  //   // 删除懒加载模块的prefetch,降低带宽压力
  //   // 而且预渲染时生成的prefetch标签是modern版本的,低版本浏览器是不需要的
  //   //config.plugins.delete('prefetch');
  //   //if(process.env.NODE_ENV === 'production') {
  //   // 为生产环境修改配置...process.env.NODE_ENV !== 'development'
  //   //} else {
  //   // 为开发环境修改配置...
  //   //}
  // },
  configureWebpack: () => { },
  // configureWebpack: () => {
  // // 生产and测试环境
  // let pluginsPro = [
  //   new CompressionPlugin({ //文件开启Gzip,也可以通过服务端(如:nginx)
  //     filename: '[path].gz[query]',
  //     algorithm: 'gzip',
  //     test: new RegExp('\\.(' + ['js', 'css'].join('|') + ')$'),
  //     threshold: 8192,
  //     minRatio: 0.8,
  //   }),
  //   new BundleAnalyzerPlugin(),
  // ];
  // //开发环境
  // let pluginsDev = [
  //   new vConsolePlugin({
  //     filter: [], // 需要过滤的入口文件
  //     enable: true // 发布代码前记得改回 false
  //   }),
  // ];
  // if (process.env.NODE_ENV === 'production') { // 为生产环境修改配置...process.env.NODE_ENV !== 'development'
  //   config.plugins = [...config.plugins, ...pluginsPro];
  // } else {
  //   // 为开发环境修改配置...
  //   config.plugins = [...config.plugins, ...pluginsDev];
  // }
  // },
 
  // CSS 相关选项
  css: {
    // 将组件内部的css提取到一个单独的css文件(只用在生产环境)
    // 也可以是传递给 extract-text-webpack-plugin 的选项对象
    // 是否使用css分离插件 ExtractTextPlugin,采用独立样式文件载入,不采用