前端 vue项目构建流程技术指导

  1. 为保证前端人员构建vue工程项目的流程化、规范化、统一化,并有效提升前端人员从零搭建项目的能力,特建立《前端vue项目构建流程技术指导》文档,前端人员不借助骨架项目构建项目的时候,应按照该指导说明文档进行项目构建,从而保证构建项目的一致性、全面性和规范性。
  2. 该指导文档包括统一开发工具(HBuilder X)、统一代码检测工具、统一格式化配置、统一 node 版本、统一 sass 版本、统一通用型依赖安装(ui组件库、vuexvueRouteraxios等),统一目录结构、统一 http 请求封装。
  3. 该文档还包括从创建项目、依赖安装、相关配置、目录结构等详细的构建流程,以及一些公共性问题的处理方案。

PDF文档

前端vue项目构建流程指导规范.pdf

目录

一、前端环境搭建

1.1 nodejs 安装

1.下载nodejs 安装程序

下载地址:CNPM Binaries Mirror (npmmirror.com). 18.1/)

根据系统下载对应的安装包 ,node版本,请统一选择v14.16.1

安装时建议修改安装目录,建议放到非C盘目录下。

安装完成后启动命令行工具,输入node -v npm -v查看安装版本,出现提示版本信息即为安装成功。

2.环境变量配置
  1. 这里的环境变量配置主要的是npm安装的全局模块所在的路径,以及缓存cache的路径,如果不配置在执行类似:`npm install 模块名 [-g]`的安装语句时,会将安装的模块安装到【C:\Users\用户名\AppData\Roaming\npm】路径中,占用C盘空间。

本文将nodejs安装在D:\soft\nodejs 目录下,以下操作可以根据实际安装目录情况进行对应的调整。

  1. 在安装目录下,如 D:\soft\nodejs 新建两个文件夹 node_global(全局包存放目录) 和 node_cache(缓存目录);
  2. 打开命令行工具,执行以下两句操作: npm config set prefix "D:\soft\nodejs\node_global"``npm config set cache "D:\soft\nodejs\node_cache"
  3. 配置环境变量:
  • 打开系统属性-高级-环境变量,在系统变量中新建 变量名:NODE_PATH,变量值:D:\soft\nodejs\node_global\node_modules(见图2);
  • 编辑用户变量的 path,将默认的 C 盘下 APPData/Roaming\npm 修改为 D:\soft\nodejs\node_global(见图3);
  • 保存即可。

image.png
image.png

1.2 安装 yarn

因为 yarn 具有优势,开发中推荐使用 yarn 来替代 npm 进行操作。安装 yarn 可以下载安装包进行安装,也可以使用 npm 安装:

  1. npm install yarn -g

1.2.1 yarn的常用命令
  1. # 1.安装包,类似于npm install
  2. yarn
  3. # 2.安装某个包,类似于npm install vue --save
  4. yarn add vue
  5. # 3.卸载某个包,类似于npm uninstall vue --save
  6. yarn remove vue
  7. # 4.安装到开发依赖,类似于npm install vue --save-dev
  8. yarn add vue --dev
  9. # 5.更新包
  10. yarn upgrade #更新包到基于规范范围的最新版本
  11. yarn upgrade --latest # 忽略版本规则,升级到最新版本,并且更新 package.json

1.2.2镜像源管理器

yrm是yarn源的管理器,用于快速切换镜像源

  1. # 安装yrm
  2. yarn add -g yrm
  3. # 查看当前可用源
  4. yrm ls
  5. # 切换源
  6. yrm use taobao
  7. # 查看yarn当前镜像源
  8. yarn config get registry
  9. # 添加源
  10. yarn add npmhzwq http://192.168.14.25:8081/repository/npm-all/
  11. # 删除源
  12. yrm del npmhzwq
  13. # 测试源响应时间
  14. yrm test

想了解cnpm以及npm的源管理器nrm可以查看8.2关于cnpm及nrm介绍.

二、项目创建

2.1 安装 Vue CLI

执行下列命令安装Vue CLI

  1. yarn global add @vue/cli@5.0.3 # yarn方式
  2. npm install -g @vue/cli@5.0.3 # npm方式

安装之后,你就可以在命令行中访问 vue 命令。你可以通过简单运行 vue,看看是否展示出了一份所有可用命令的帮助信息,来验证它是否安装成功。

你还可以用这个命令来检查其版本是否正确:

  1. vue -V

如需升级项目中的 Vue CLI 相关模块(以 @vue/cli-plugin-vue-cli-plugin- 开头),请在项目目录下运行 vue upgrade

  1. 用法: upgrade [options] [plugin-name]
  2. (试用)升级 Vue CLI 服务及插件
  3. 选项:
  4. -t, --to <version> 升级 <plugin-name> 到指定的版本
  5. -f, --from <version> 跳过本地版本检测,默认插件是从此处指定的版本升级上来
  6. -r, --registry <url> 使用指定的 registry 地址安装依赖
  7. --all 升级所有的插件
  8. --next 检查插件新版本时,包括 alpha/beta/rc 版本在内
  9. -h, --help 输出帮助内容

2.2 创建项目

在要创建项目的路径下使用命令行创建项目

  1. vue create hello-world

会被提示选取一个preset。这里选择基本的Babel + ESLint 设置的preset。

image.png

使用键盘上下方向键定位到 Default([Vue 2] babel, eslint),然后回车

image.png

如图,项目创建完成

执行

  1. cd hello-world

进入新建项目,

运行项目:

  1. yarn serve #yarn方式
  2. npm run serve #npm方式

image.png

在浏览器中访问 http://localhost:8080http://192.168.23.123:8080

image.png

至此,基础的基于 @vue/cli 的项目创建完成。

项目中 package.json 文件内容:

  1. {
  2. "name": "hello-world",
  3. "version": "0.1.0",
  4. "private": true,
  5. "scripts": {
  6. "serve": "vue-cli-service serve",
  7. "build": "vue-cli-service build",
  8. "lint": "vue-cli-service lint"
  9. },
  10. "dependencies": {
  11. "core-js": "^3.8.3",
  12. "vue": "^2.6.14"
  13. },
  14. "devDependencies": {
  15. "@babel/core": "^7.12.16",
  16. "@babel/eslint-parser": "^7.12.16",
  17. "@vue/cli-plugin-babel": "~5.0.0",
  18. "@vue/cli-plugin-eslint": "~5.0.0",
  19. "@vue/cli-service": "~5.0.0",
  20. "eslint": "^7.32.0",
  21. "eslint-plugin-vue": "^8.0.3",
  22. "vue-template-compiler": "^2.6.14"
  23. },
  24. "eslintConfig": {
  25. "root": true,
  26. "env": {
  27. "node": true
  28. },
  29. "extends": [
  30. "plugin:vue/essential",
  31. "eslint:recommended"
  32. ],
  33. "parserOptions": {
  34. "parser": "@babel/eslint-parser"
  35. },
  36. "rules": {}
  37. },
  38. "browserslist": [
  39. "> 1%",
  40. "last 2 versions",
  41. "not dead"
  42. ]
  43. }

三、依赖安装

3.1 安装sass sass-loader:

  1. yarn add sass -D
  2. yarn add sass-loader -D

若安装依赖报错,请参考8.4章节.

3.2 安装vuex :

  1. yarn add vuex@3.6.2

对于大型应用,我们推荐 Vuex 相关代码按业务模块进行划分管理。

1.在src目录下创建如下目录结构:

  1. └── store
  2. ├── index.js # 我们组装模块并导出 store 的地方
  3. └── modules
  4. ├── menu.js # 模块示例:菜单模块
  5. └── user.js # 模块示例:用户模块

2.src\index.js 书写demo:

  1. import Vue from 'vue';
  2. import Vuex from 'vuex';
  3. //导入user模块
  4. import user from './modules/user'
  5. import menu from './modules/menu'
  6. Vue.use(Vuex)
  7. //创建vuex实列
  8. export default new Vuex.Store({
  9. //模块化创建
  10. moudles:{
  11. user,
  12. menu
  13. }
  14. })
  1. user.js 模块书写demo
  1. //导入axios
  2. import axios from 'axios'
  3. export default {
  4. //开启命名空间一定要写这个
  5. namespaced:true
  6. //state数据状态定义
  7. state(){
  8. profile:{
  9. user:'小明', //用户名
  10. token:null //用户的token
  11. }
  12. },
  13. //vuex方法
  14. // mutations 定义方法
  15. mutations:{
  16. //定义一个修改用户的方法函数
  17. setUser(state,profile){
  18. state.profile = profile //profile 是外界传的参数
  19. }
  20. },
  21. //请求数据的地方 如果要请求一个数据可以这样来写这个是补充的
  22. // actions 定义方法
  23. actions:{
  24. //调用axios的请求函数
  25. async getUserInfo(context){
  26. const{ data:data} = await axios.get('请求的路径地址',{请求的参数})
  27. console.log(data) //服务器返回的数据
  28. context.commit('setUser', data)
  29. }
  30. }
  31. }

src\main.js 中引入 store\index.js 以使用 vuex:

  1. import Vue from 'vue'
  2. import App from './App.vue'
  3. // vuex
  4. import store from './store'
  5. Vue.config.productionTip = false
  6. new Vue({
  7. store, // 使用store
  8. render: h => h(App),
  9. }).$mount('#app')

配置路径别名,方便组件引入

在vue.config.js 中配置路径别名:

  1. const { defineConfig } = require('@vue/cli-service')
  2. const path = require('path')
  3. const resolve = (dir) => path.join(__dirname, dir)
  4. module.exports = defineConfig({
  5. transpileDependencies: true,
  6. chainWebpack: config => {
  7. // 配置别名
  8. config.resolve.alias
  9. .set('@', resolve('src'))
  10. .set('@views', resolve('src/views'))
  11. }
  12. })

!! 修改 vue.config.js 后 需要 Ctrl + C 停止项目,然后 yarn serve 运行项目才能看到修改后的效果。!!

3.3 安装vue-router

  1. yarn add vue-router@3.5.3

在 src 目录下创建 router 文件夹,

在 router 文件夹中新建 index.jsroutes.js

src\router\index.js:

  1. import Vue from 'vue';
  2. import Router from 'vue-router';
  3. import routes from './routes';
  4. Vue.use(Router);
  5. const router = new Router({
  6. mode: 'history',
  7. routes
  8. });
  9. export default router;

src\router\routes.js:

  1. const DashBoard = () => import('@views/dash-board.vue');
  2. const DemoPage = () => import('@views/demo-page.vue');
  3. const routes = [
  4. {
  5. path: '/dashboard',
  6. name: 'DashBoard',
  7. component: DashBoard
  8. },
  9. {
  10. path: '/demo',
  11. name: 'DemoPage',
  12. component: DemoPage
  13. }
  14. ];
  15. export default routes;

注意:routes 的 name 名称要使用大驼峰命名,并与组件定义的 name 名称保持一致。

对应的在 src 目录中创建 views 文件夹,

在 src\views 文件夹中创建 dash-board.vue demo-page.vue

注意:文件名采用小写中划线拼接,vue文件中必须定义 name 属性,并且和 routes 的 name 保持一致。

在 main.js 中引入 router.js:

  1. import Vue from 'vue'
  2. import App from './App.vue'
  3. // vuex
  4. import store from './store'
  5. // vue-router
  6. import router from './router'
  7. Vue.config.productionTip = false
  8. new Vue({
  9. store, // 使用store
  10. router, // 使用vue-router
  11. render: h => h(App),
  12. }).$mount('#app')

在地址栏输入 http://localhost:8080/dashboard 发现页面并没有跳转。

在 App.vue 中添加 <router-view></router-view>:

  1. <template>
  2. <div id="app">
  3. <img alt="Vue logo" src="./assets/logo.png" />
  4. <!-- <HelloWorld msg="Welcome to Your Vue.js App" /> -->
  5. <ul>
  6. <li>
  7. <router-link to="/">首页</router-link>
  8. </li>
  9. <li>
  10. <router-link to="/dashboard">DashBoard</router-link>
  11. </li>
  12. <li>
  13. <router-link to="/demo">DemoPage</router-link>
  14. </li>
  15. </ul>
  16. <router-view></router-view>
  17. </div>
  18. </template>
  19. <script>
  20. // import HelloWorld from "./components/HelloWorld.vue";
  21. export default {
  22. name: "App",
  23. components: {
  24. // HelloWorld,
  25. },
  26. };
  27. </script>
  28. <style>
  29. #app {
  30. font-family: Avenir, Helvetica, Arial, sans-serif;
  31. -webkit-font-smoothing: antialiased;
  32. -moz-osx-font-smoothing: grayscale;
  33. text-align: center;
  34. color: #2c3e50;
  35. margin-top: 60px;
  36. }
  37. </style>

再次访问 http://localhost:8080/dashboard

image.png

  1. 访问 `http://localhost:8080/demo`:

image.png

3.4 安装element-ui:

  1. yarn add element-ui

3.4.1 全量引入element-ui

在 main.js 中全局引入 element-ui:

  1. import Vue from 'vue'
  2. import App from './App.vue'
  3. // 全局引入 element-ui
  4. import ElementUI from 'element-ui'
  5. // 引入 element-ui 样式
  6. import 'element-ui/lib/theme-chalk/index.css'
  7. // vuex
  8. import store from './store'
  9. // vue-router
  10. import router from './router'
  11. // 配置默认尺寸 small z-index层级 3000
  12. Vue.use(ElementUI, {
  13. size: 'small',
  14. zIndex: 3000
  15. })
  16. Vue.config.productionTip = false
  17. new Vue({
  18. store, // 使用store
  19. router, // 使用vue-router
  20. render: h => h(App),
  21. }).$mount('#app')

使用 elment-ui 中的 el-link 替换 router-link :

  1. <template>
  2. <div id="app">
  3. ...
  4. <ul>
  5. <li>
  6. <!-- <router-link to="/">首页</router-link> -->
  7. <el-link href="/" type="primary">首页</el-link>
  8. </li>
  9. <li>
  10. <!-- <router-link to="/dashboard">DashBoard</router-link> -->
  11. <el-link href="/dashboard" type="success">DashBoard</el-link>
  12. </li>
  13. <li>
  14. <!-- <router-link to="/demo">DemoPage</router-link> -->
  15. <el-link href="/demo" type="info">DemoPage</el-link>
  16. </li>
  17. </ul>
  18. ...
  19. </div>
  20. </template>

验证效果:

image.png

3.4.2 按需引入 element-ui: [推荐]

借助 babel-plugin-component,我们可以只引入需要的组件,以达到减小项目体积的目的。

1.首先,安装 babel-plugin-component:

  1. yarn add babel-plugin-component @babel/preset-env -D

2.然后修改 babel.config.js:

  1. module.exports = {
  2. presets: [
  3. '@vue/cli-plugin-babel/preset',
  4. ['@babel/preset-env', { modules: false }]
  5. ],
  6. plugins: [
  7. [
  8. 'component',
  9. {
  10. 'libraryName': 'element-ui',
  11. 'styleLibraryName': 'theme-chalk'
  12. }
  13. ],
  14. "@babel/plugin-transform-runtime"
  15. ]
  16. }

3.创建 src/utils/element.js 文件,下面列举了所有的组件,在项目中只选择需要用到的组件即可:

  1. import Vue from 'vue'
  2. import {
  3. Pagination,
  4. Dialog,
  5. Divider,
  6. Autocomplete,
  7. Dropdown,
  8. DropdownMenu,
  9. DropdownItem,
  10. Menu,
  11. Submenu,
  12. MenuItem,
  13. MenuItemGroup,
  14. Input,
  15. InputNumber,
  16. Radio,
  17. RadioGroup,
  18. RadioButton,
  19. Checkbox,
  20. CheckboxButton,
  21. CheckboxGroup,
  22. Switch,
  23. Select,
  24. Option,
  25. OptionGroup,
  26. Button,
  27. ButtonGroup,
  28. Table,
  29. TableColumn,
  30. DatePicker,
  31. TimeSelect,
  32. TimePicker,
  33. Popover,
  34. Tooltip,
  35. Breadcrumb,
  36. BreadcrumbItem,
  37. Form,
  38. FormItem,
  39. Tabs,
  40. TabPane,
  41. Tag,
  42. Tree,
  43. Timeline,
  44. TimelineItem,
  45. Link,
  46. Alert,
  47. Slider,
  48. Icon,
  49. Row,
  50. Col,
  51. Upload,
  52. Progress,
  53. Badge,
  54. Card,
  55. Rate,
  56. Scrollbar,
  57. Steps,
  58. Step,
  59. Carousel,
  60. CarouselItem,
  61. Collapse,
  62. CollapseItem,
  63. Cascader,
  64. ColorPicker,
  65. Transfer,
  66. Container,
  67. Header,
  68. Aside,
  69. Main,
  70. Footer,
  71. Loading,
  72. MessageBox,
  73. Message,
  74. Notification
  75. } from 'element-ui'
  76. Vue.use(Pagination)
  77. Vue.use(Dialog)
  78. Vue.use(Divider)
  79. Vue.use(Autocomplete)
  80. Vue.use(Dropdown)
  81. Vue.use(DropdownMenu)
  82. Vue.use(DropdownItem)
  83. Vue.use(Menu)
  84. Vue.use(Submenu)
  85. Vue.use(MenuItem)
  86. Vue.use(MenuItemGroup)
  87. Vue.use(Input)
  88. Vue.use(InputNumber)
  89. Vue.use(Radio)
  90. Vue.use(RadioGroup)
  91. Vue.use(RadioButton)
  92. Vue.use(Checkbox)
  93. Vue.use(CheckboxButton)
  94. Vue.use(CheckboxGroup)
  95. Vue.use(Switch)
  96. Vue.use(Select)
  97. Vue.use(Option)
  98. Vue.use(OptionGroup)
  99. Vue.use(Button)
  100. Vue.use(ButtonGroup)
  101. Vue.use(Table)
  102. Vue.use(TableColumn)
  103. Vue.use(DatePicker)
  104. Vue.use(TimeSelect)
  105. Vue.use(TimePicker)
  106. Vue.use(Popover)
  107. Vue.use(Tooltip)
  108. Vue.use(Breadcrumb)
  109. Vue.use(BreadcrumbItem)
  110. Vue.use(Form)
  111. Vue.use(FormItem)
  112. Vue.use(Tabs)
  113. Vue.use(TabPane)
  114. Vue.use(Tag)
  115. Vue.use(Tree)
  116. Vue.use(Timeline)
  117. Vue.use(TimelineItem)
  118. Vue.use(Link)
  119. Vue.use(Alert)
  120. Vue.use(Slider)
  121. Vue.use(Icon)
  122. Vue.use(Row)
  123. Vue.use(Col)
  124. Vue.use(Upload)
  125. Vue.use(Progress)
  126. Vue.use(Badge)
  127. Vue.use(Card)
  128. Vue.use(Rate)
  129. Vue.use(Scrollbar)
  130. Vue.use(Steps)
  131. Vue.use(Step)
  132. Vue.use(Carousel)
  133. Vue.use(CarouselItem)
  134. Vue.use(Collapse)
  135. Vue.use(CollapseItem)
  136. Vue.use(Cascader)
  137. Vue.use(ColorPicker)
  138. Vue.use(Transfer)
  139. Vue.use(Container)
  140. Vue.use(Header)
  141. Vue.use(Aside)
  142. Vue.use(Main)
  143. Vue.use(Footer)
  144. Vue.use(Loading.directive)
  145. Vue.prototype.$loading = Loading.service
  146. Vue.prototype.$msgbox = MessageBox
  147. Vue.prototype.$alert = MessageBox.alert
  148. Vue.prototype.$confirm = MessageBox.confirm
  149. Vue.prototype.$prompt = MessageBox.prompt
  150. Vue.prototype.$notify = Notification
  151. Vue.prototype.$message = Message
  152. Vue.prototype.$ELEMENT = {
  153. size: 'small',
  154. zIndex: 3000
  155. };
  156. // element组件库的Dialog对话框默认可以通过点击 modal 关闭 Dialog,即点击空白处弹框可关闭。
  157. Dialog.props.closeOnClickModal.default = false;

main.js 中修改全量引入为按需引入:

  1. import Vue from 'vue'
  2. import App from './App.vue'
  3. // vuex
  4. import store from './store'
  5. // vue-router
  6. import router from './router'
  7. // 全局引入 element-ui
  8. // import ElementUI from 'element-ui'
  9. // 引入 element-ui 样式
  10. // import 'element-ui/lib/theme-chalk/index.css'
  11. // 配置默认尺寸 small z-index层级 3000 [全量引入配置]
  12. /*
  13. Vue.use(ElementUI, {
  14. size: 'small',
  15. zIndex: 3000
  16. })
  17. */
  18. // 按需引入 element 组件 [先]
  19. import '@/utils/element.js'
  20. // 引入 自定义 element-ui 主题样式 [后]
  21. import '../themes/index.css'
  22. // 引入全局公用样式
  23. import '@/common/style/index.scss'
  24. Vue.config.productionTip = false
  25. new Vue({
  26. store, // 使用store
  27. router, // 使用vue-router
  28. render: hyperscript => hyperscript(App)
  29. }).$mount('#app')

3.5 安装axios:

  1. yarn add axios

1.封装axios:在 src 文件夹下创建 utils 文件夹,并在 utils 文件夹中新建 request.js文件,写入如下内容:

src/utils/request.js

  1. import router from '@/router'
  2. import axios from 'axios'
  3. import { Message } from 'element-ui'
  4. // 创建 axios 实例
  5. const service = axios.create({
  6. timeout: 30000,
  7. headers: {
  8. 'Content-Type': 'application/json;charset=utf-8'
  9. }
  10. })
  11. // 拦截 request 请求
  12. service.interceptors.request.use(function (config) {
  13. // 统一添加配置信息
  14. // 防止IE浏览器缓存
  15. config.headers.common['Cache-Control'] = 'no-cache'
  16. // 获取 token,若不存在,则为空
  17. const token = localStorage.getItem('token')
  18. if (token) {
  19. // 如果存在 token,则每个请求 header 都加上 token
  20. config.headers.authorization = token
  21. }
  22. return config
  23. }, function (error) {
  24. return Promise.reject(error)
  25. })
  26. // 拦截 response, 统一处理错误
  27. service.interceptors.response.use(function (response) {
  28. if (response.data && response.status === 200) {
  29. return response
  30. }
  31. return Promise.reject(new Error(response))
  32. }, function (error) {
  33. // axios 获得的响应 status code 超出了 2xx 的范围时,统一处理接口错误
  34. if (!window.navigator.onLine) {
  35. // 断网处理
  36. router.push('/error')
  37. } else {
  38. if (error && error.response) {
  39. const duration = 3000,
  40. errorStatusHash = {
  41. 401: '权限不足',
  42. 404: '请求路径不存在'
  43. // 500: '服务器异常'
  44. }
  45. if (error.response.status in errorStatusHash) {
  46. Message({
  47. message: errorStatusHash[error.response.status],
  48. type: 'error',
  49. duration
  50. })
  51. if (error.response.status === 500) {
  52. const errMsgHash = {
  53. S00001: '接口超时',
  54. S00002: '接口解析错误',
  55. S00003: '接口返回数据异常',
  56. S00004: '代码报错',
  57. S00005: '数据库报错'
  58. }
  59. console.log(errMsgHash[error.response.data.code] || '服务器异常')
  60. // Message({
  61. // message: '服务器异常',
  62. // type: 'error',
  63. // duration: 3 * 1000
  64. // })
  65. }
  66. } else {
  67. Message({
  68. message: '操作失败',
  69. type: 'error',
  70. duration: 3 * 1000
  71. })
  72. }
  73. }
  74. }
  75. return Promise.reject(error)
  76. })
  77. // 实例变量支持 all
  78. service.all = axios.all
  79. export default service

3.使用request发起api请求,并统一管理:在 src 目录下创建 api 文件夹,在api文件夹中新建 test-api.js文件,写入如下内容:

  1. import request from '@utils/request'
  2. // get 请求
  3. export const testApi = (params) => {
  4. return request({
  5. url: '/api/v1/images/search',
  6. method: 'get',
  7. params // params: params 简写
  8. })
  9. }
  10. // post 请求
  11. export const testApiPost = (params) => {
  12. return request({
  13. url: '/api/v1/images/search',
  14. method: 'get',
  15. data: params
  16. })
  17. }

4.测试:在 src\views\demo-page.vue 中测试 封装的axiso:

  1. <template>
  2. <div class="demo-page">
  3. <h4>Demo page.</h4>
  4. <img class="random-img" :src="randomImg" alt="" />
  5. </div>
  6. </template>
  7. <script>
  8. import { testApi } from "@api/test-api";
  9. export default {
  10. name: 'DemoPage',
  11. data() {
  12. return {
  13. randomImg: "",
  14. };
  15. },
  16. mounted() {
  17. this.initData();
  18. },
  19. methods: {
  20. initData() {
  21. const params = {greeting: "Hi!"}
  22. testApi(params).then((res) => {
  23. console.log("res: ", res);
  24. if (res.status === 200) {
  25. if (res.data && res.data.length) {
  26. this.randomImg = res.data[0].url;
  27. }
  28. }
  29. })
  30. .catch((err) => {
  31. console.log("err: ", err);
  32. });
  33. },
  34. },
  35. };
  36. </script>
  37. <style lang="scss" scoped>
  38. .demo-page {
  39. text-align: center;
  40. .random-img {
  41. max-width: 50vw;
  42. }
  43. }
  44. </style>

在 vue.config.js 中添加别名配置:

  1. // webpack 配置
  2. chainWebpack: config => {
  3. config.resolve.alias
  4. .set('@', resolve('src'))
  5. .set('@views', resolve('src/views'))
  6. .set('@utils', resolve('src/utils'))
  7. .set('@api', resolve('src/api'))
  8. }

访问http://localhost:8080/demo:

image.png

至此

项目目录结构如下图:

image.png

!!( 图片中DashBoard.vue 、DemoPage.vue 应为 dash-board.vue 、demo-page.vue )!!

package.json中的依赖如下:

  1. "dependencies": {
  2. "axios": "^0.26.1",
  3. "core-js": "^3.8.3",
  4. "element-ui": "^2.15.6",
  5. "vue": "^2.6.14",
  6. "vue-router": "3.5.3",
  7. "vuex": "3.6.2"
  8. },
  9. "devDependencies": {
  10. "@babel/core": "^7.12.16",
  11. "@babel/eslint-parser": "^7.12.16",
  12. "@vue/cli-plugin-babel": "~5.0.0",
  13. "@vue/cli-plugin-eslint": "~5.0.0",
  14. "@vue/cli-service": "~5.0.0",
  15. "eslint": "^7.32.0",
  16. "eslint-plugin-vue": "^8.0.3",
  17. "sass": "^1.49.9",
  18. "sass-loader": "10.2.1",
  19. "vue-template-compiler": "^2.6.14"
  20. },

四、定义全局公共样式

为了便于公共样式的统一管理,我们需要定义全局公共样式。

1.引入base.css文件

在main.js文件引入base.css文件

  1. @charset "utf-8";
  2. /*主要用于样式重置base.css*/
  3. /*移动端默认样式*/
  4. /*清除掉按下时会有一个灰色阴影*/
  5. a,input,button{
  6. -webkit-tap-highlight-color: rgba(0,0,0,0);
  7. }
  8. /*清除掉ios自带圆角*/
  9. input,button{
  10. -webkit-appearance: none;/*消除输入框核按钮的默认外观*/
  11. border-radius: 0;
  12. }
  13. /* 禁用iPhone中Safari的字号自动调整 */
  14. html {
  15. -webkit-text-size-adjust: 100%;
  16. -ms-text-size-adjust: 100%;
  17. /* 解决IOS默认滑动很卡的情况 */
  18. -webkit-overflow-scrolling : touch;
  19. overflow-scrolling:touch;
  20. }
  21. /* 禁止缩放表单 */
  22. input[type="submit"], input[type="reset"], input[type="button"], input {
  23. resize: none;
  24. border: none;
  25. }
  26. /* 取消链接高亮 */
  27. body, div, ul, li, ol, h1, h2, h3, h4, h5, h6, input, textarea, select, p, dl, dt, dd, a, img, button, form, table, th, tr, td, tbody, article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section {
  28. -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
  29. }
  30. /* 设置HTML5元素为块 */
  31. article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section {
  32. display: block;
  33. }
  34. /* 图片自适应 */
  35. img {
  36. width: 100%;
  37. height: auto;
  38. width: auto\9; /* ie8 */
  39. display: block;
  40. -ms-interpolation-mode: bicubic;/*为了照顾ie图片缩放失真*/
  41. }
  42. /* 初始化 */
  43. body, div, ul, li, ol, h1, h2, h3, h4, h5, h6, input, textarea, select, p, dl, dt, dd, a, img, button, form, table, th, tr, td, tbody, article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section {
  44. margin: 0;
  45. padding: 0;
  46. }
  47. body{
  48. /*禁止选中文字*/
  49. -webkit-user-select: none;
  50. /* iPhone 和 Android 的浏览器纵向 (Portrate mode) 和橫向 (Landscape mode) 模式皆有自动调整字体大小的功能。控制它的就是 CSS 中的 -webkit-text-size-adjust。关闭字体大小自动调整功能*/
  51. -webkit-text-size-adjust: 100%;
  52. }
  53. /*字体设置*/
  54. body *{
  55. /*每台设备里的默认字体是不一样的(移动端设备里大多数没有宋体和微软雅黑字体)*/
  56. font-family: helvetica;
  57. }
  58. em, i {font-style: normal;}
  59. ul,li{list-style-type: none;}
  60. h1,h2,h3,h4,h5,h6,strong,b,i,em{font-weight: normal; font-style: normal;}
  61. a {text-decoration: none;color: #969696;font-family: 'Microsoft YaHei', Tahoma, Arial, sans-serif;}
  62. a:hover {text-decoration: none;}
  63. ul, ol {list-style: none;}
  64. h1, h2, h3, h4, h5, h6 {font-size: 100%;font-family: 'Microsoft YaHei';}
  65. img {border: none;}
  66. input{font-family: 'Microsoft YaHei';}
  67. /* 移动端点击a链接出现蓝色背景问题解决 */
  68. a:link,a:active,a:visited,a:hover {
  69. background: none;
  70. -webkit-tap-highlight-color: rgba(0,0,0,0);
  71. -webkit-tap-highlight-color: transparent;
  72. }
  73. .clearfix:after {
  74. content: "";
  75. display: block;
  76. visibility: hidden;
  77. height: 0;
  78. clear: both;
  79. }
  80. .clearfix {zoom: 1;}

2.定义scss变量文件var.scss

src/common/style/ 目录中,新建var.scss文件,并定义如下内容(相关变量的值可以参考项目的ui效果图主题色信息修改):

  1. //定义单位-uniapp项目需要使用 rpx;@return $px * 1rpx;
  2. @function px($px) {
  3. @return $px * 1px;
  4. }
  5. // 颜色
  6. $color-primary: #2ffec4;
  7. $color-success: #09ad9c;
  8. $color-warning: #f8af39;
  9. $color-danger: #ff3333;
  10. $color-white: white;
  11. $color-black: black;
  12. $colors: (
  13. primary: $color-primary,
  14. success: $color-success,
  15. warning: $color-warning,
  16. danger: $color-danger,
  17. white: $color-white,
  18. black: $color-black,
  19. );
  20. //字体大小
  21. $font-mini: px(12);
  22. $font-small: px(16);
  23. $font-base: px(14);
  24. $font-large: px(18);
  25. $fonts: (
  26. mini: $font-mini,
  27. small: $font-small,
  28. base: $font-base,
  29. large: $font-large,
  30. );
  31. //border
  32. $border-width: px(1);
  33. $border-color: #e8e8e8;
  34. // border-radius
  35. $radius-base: px(4);
  36. $radius-circle: 50%;
  37. $radius: (
  38. base: $radius-base,
  39. circle: $radius-circle,
  40. );
  41. //定位
  42. $positions: (
  43. absolute: absolute,
  44. relative: relative,
  45. fixed: fixed,
  46. );
  47. //文字对齐
  48. $textAligns: (
  49. left: left,
  50. center: center,
  51. right: right,
  52. );
  53. //浮动
  54. $floats: (
  55. left: left,
  56. right: right,
  57. );
  58. //间距
  59. $size-zero: 0;
  60. $size-mini: px(10);
  61. $size-small: px(20);
  62. $size-base: px(24);
  63. $size-large: px(28);
  64. $sizes: (
  65. zero: $size-zero,
  66. mini: $size-mini,
  67. small: $size-small,
  68. base: $size-base,
  69. large: $size-large,
  70. );

3.定义重复性强的样式代码块文件mixins.scss
  1. //字体
  2. @mixin font-family(
  3. $value: (
  4. 'Microsoft YaHei',
  5. Tahoma,
  6. Arial,
  7. sans-serif,
  8. )
  9. ) {
  10. font-family: $value;
  11. }
  12. // 文字溢出隐藏显示...
  13. @mixin text-overflow($line: 1) {
  14. @if $line==1 {
  15. text-overflow: ellipsis;
  16. overflow: hidden;
  17. white-space: nowrap;
  18. } @else {
  19. overflow: hidden;
  20. text-overflow: ellipsis;
  21. display: -webkit-box;
  22. -webkit-line-clamp: $line;
  23. /*! autoprefixer: off */
  24. -moz-box-orient: vertical;
  25. -webkit-box-orient: vertical;
  26. }
  27. }
  28. //border
  29. @mixin border($color: $border-color, $width: $border-width) {
  30. border: $width solid $color;
  31. }
  32. @mixin border-dir($i, $width: $border-width, $color: $border-color) {
  33. border-#{$i}: $width solid $color;
  34. }
  35. // border-radius
  36. @mixin radius($radius: $radius-base, $overflow: hidden) {
  37. border-radius: $radius;
  38. overflow: $overflow;
  39. }
  40. //间距
  41. @mixin padding-col($n: $size-base, $sizeKey: none) {
  42. @if $sizeKey == 'zero' {
  43. padding-top: $n !important;
  44. padding-bottom: $n !important;
  45. } @else {
  46. padding-top: $n;
  47. padding-bottom: $n;
  48. }
  49. }
  50. @mixin padding-row($n: $size-base, $sizeKey: none) {
  51. @if $sizeKey == 'zero' {
  52. padding-left: $n !important;
  53. padding-right: $n !important;
  54. } @else {
  55. padding-left: $n;
  56. padding-right: $n;
  57. }
  58. }
  59. @mixin margin-col($n: $size-base, $sizeKey: none) {
  60. @if $sizeKey == 'zero' {
  61. margin-top: $n !important;
  62. margin-bottom: $n !important;
  63. } @else {
  64. margin-top: $n;
  65. margin-bottom: $n;
  66. }
  67. }
  68. @mixin margin-row($n: $size-base, $sizeKey: none) {
  69. @if $sizeKey == 'zero' {
  70. margin-left: $n !important;
  71. margin-right: $n !important;
  72. } @else {
  73. margin-left: $n;
  74. margin-right: $n;
  75. }
  76. }

4.新建公共样式文件global.scss文件

src/common/style/ 目录,新建global.scss文件,并定义全局公共样式。代码如下:

  1. @import './var.scss';
  2. @import './mixins.scss';
  3. .font-family {
  4. @include font-family;
  5. }
  6. // 颜色
  7. @each $colorKey, $color in $colors {
  8. //字体颜色 .text-#{'' + $colorKey}用于解决警告,添加‘’强制转换为字符串
  9. .text-#{'' + $colorKey} {
  10. color: $color;
  11. }
  12. //背景颜色 .bg-#{'' + $colorKey}用于解决警告,添加‘’强制转换为字符串
  13. .bg-#{'' + $colorKey} {
  14. background-color: $color;
  15. }
  16. }
  17. //字体大小
  18. @each $fontKey, $font in $fonts {
  19. .font-#{$fontKey} {
  20. font-size: $font;
  21. }
  22. }
  23. // 文字溢出隐藏显示...
  24. //1-4行
  25. @for $i from 1 through 4 {
  26. @if $i == 1 {
  27. .line-ellipsis {
  28. @include text-overflow($i);
  29. }
  30. } @else {
  31. .line-limited#{$i} {
  32. @include text-overflow($i);
  33. }
  34. }
  35. }
  36. //border
  37. //颜色边框,.border-#{''+$borderKey} 用于解决警告,添加''强制转换为字符串
  38. @each $borderKey, $border in $colors {
  39. .border-#{'' + $borderKey} {
  40. @include border($border);
  41. }
  42. }
  43. //边框粗细
  44. @for $i from 1 through 5 {
  45. @if $i == 1 {
  46. .border {
  47. @include border;
  48. }
  49. } @else {
  50. .border#{$i} {
  51. @include border($border-color, px($i));
  52. }
  53. }
  54. }
  55. //边框方向
  56. @each $i in left, right, top, bottom {
  57. .border-#{$i} {
  58. @include border-dir($i);
  59. }
  60. }
  61. // border-radius
  62. @each $radiusKey, $radiu in $radius {
  63. .border-radius-#{$radiusKey} {
  64. @include radius($radiu);
  65. }
  66. }
  67. // 定位
  68. @each $positionKey, $position in $positions {
  69. .position-#{$positionKey} {
  70. position: $position;
  71. }
  72. }
  73. // 文字对齐
  74. @each $textAlignKey, $textAlign in $textAligns {
  75. .text-#{$textAlignKey} {
  76. text-align: $textAlign;
  77. }
  78. }
  79. //浮动
  80. @each $floatKey, $float in $floats {
  81. .float-#{$floatKey} {
  82. float: $float;
  83. }
  84. }
  85. //清除浮动
  86. .clearfix {
  87. zoom: 1;
  88. &:after {
  89. content: '';
  90. display: block;
  91. visibility: hidden;
  92. height: 0;
  93. clear: both;
  94. }
  95. }
  96. //间距
  97. @each $sizeKey, $size in $sizes {
  98. .p-#{$sizeKey} {
  99. @include padding-col($size, $sizeKey);
  100. @include padding-row($size, $sizeKey);
  101. }
  102. .p-row-#{$sizeKey} {
  103. @include padding-row($size, $sizeKey);
  104. }
  105. .p-col-#{$sizeKey} {
  106. @include padding-col($size, $sizeKey);
  107. }
  108. .pl-#{$sizeKey} {
  109. @if $sizeKey == 'zero' {
  110. padding-left: $size !important;
  111. } @else {
  112. padding-left: $size;
  113. }
  114. }
  115. .pr-#{$sizeKey} {
  116. @if $sizeKey == 'zero' {
  117. padding-right: $size !important;
  118. } @else {
  119. padding-right: $size;
  120. }
  121. }
  122. .pt-#{$sizeKey} {
  123. @if $sizeKey == 'zero' {
  124. padding-top: $size !important;
  125. } @else {
  126. padding-top: $size;
  127. }
  128. }
  129. .pb-#{$sizeKey} {
  130. @if $sizeKey == 'zero' {
  131. padding-bottom: $size !important;
  132. } @else {
  133. padding-bottom: $size;
  134. }
  135. }
  136. .m-#{$sizeKey} {
  137. @include margin-col($size, $sizeKey);
  138. @include margin-row($size, $sizeKey);
  139. }
  140. .m-row-#{$sizeKey} {
  141. @include margin-row($size, $sizeKey);
  142. }
  143. .m-col-#{$sizeKey} {
  144. @include margin-col($size, $sizeKey);
  145. }
  146. .ml-#{$sizeKey} {
  147. @if $sizeKey == 'zero' {
  148. margin-left: $size !important;
  149. } @else {
  150. margin-left: $size;
  151. }
  152. }
  153. .mr-#{$sizeKey} {
  154. @if $sizeKey == 'zero' {
  155. margin-right: $size !important;
  156. } @else {
  157. margin-right: $size;
  158. }
  159. }
  160. .mt-#{$sizeKey} {
  161. @if $sizeKey == 'zero' {
  162. margin-top: $size !important;
  163. } @else {
  164. margin-top: $size;
  165. }
  166. }
  167. .mb-#{$sizeKey} {
  168. @if $sizeKey == 'zero' {
  169. margin-bottom: $size !important;
  170. } @else {
  171. margin-bottom: $size;
  172. }
  173. }
  174. }
  175. //flex
  176. .flex-row {
  177. display: flex;
  178. }
  179. .flex-row-vcenter {
  180. display: flex;
  181. align-items: center;
  182. } /* 垂直居中对齐 */
  183. .flex-row-rcenter {
  184. display: flex;
  185. justify-content: center;
  186. } /* 水平居中对齐 */
  187. .flex-row-c {
  188. display: flex;
  189. align-items: center;
  190. justify-content: center;
  191. } /* 水平垂直居中对齐 */
  192. .flex-row-bw {
  193. display: flex;
  194. justify-content: space-between;
  195. }
  196. .flex-row-ad {
  197. display: flex;
  198. justify-content: space-around;
  199. }
  200. .flex-row-end {
  201. display: flex;
  202. justify-content: flex-end;
  203. }
  204. .flex-wrap {
  205. flex-wrap: wrap;
  206. } /* 换行,默认nowrap(不换行)*/
  207. .flex-col {
  208. display: flex;
  209. flex-direction: column;
  210. }
  211. .flex-col-vcenter {
  212. display: flex;
  213. flex-direction: column;
  214. justify-content: center;
  215. } /* 纵向布局垂直居中 */
  216. .flex-col-rcenter {
  217. display: flex;
  218. flex-direction: column;
  219. align-items: center;
  220. } /* 纵向布局水平居中对齐 */
  221. .flex-col-c {
  222. display: flex;
  223. flex-direction: column;
  224. align-items: center;
  225. justify-content: center;
  226. }
  227. .flex-col-bw {
  228. display: flex;
  229. flex-direction: column;
  230. justify-content: space-between;
  231. }
  232. .flex1-row {
  233. flex: 1;
  234. width: 1px;
  235. }
  236. .flex1-col {
  237. flex: 1;
  238. height: 1px;
  239. }
  240. .w100 {
  241. width: 100%;
  242. }
  243. .h100 {
  244. height: 100%;
  245. }

5.在 main.js 中引入公共样式文件base.css和global.scss:
  1. // ...
  2. // 引入 自定义 element-ui 主题样式
  3. import '../themes/index.css'
  4. //引入全局样式base.css
  5. import '@/assets/style/base.scss';//全局样式
  6. // 引入全局样式global.scss
  7. import '@/common/style/global.scss'
  8. // ...

五、项目配置

5.1 eslint 配置

ESLint最初是由Nicholas C. Zakas 于2013年6月创建的开源项目。它的目标是提供一个插件化的javascript代码检测工具。

安装依赖:

  1. yarn add eslint-plugin-prettier -D

移除 package.json 中 eslintConfig 配置项,在项目根目录 新建 .eslintrc.js 文件(用来配置eslint 检测规则):

  1. // .eslintrc.js
  2. module.exports = {
  3. root: true, // 标识根目录
  4. env: {
  5. node: true,
  6. browser: true
  7. },
  8. ignorePatterns: ['node_modules/'], // 需要忽略的特定文件和目录
  9. extends: [
  10. /**
  11. * @vue/cli-plugin-eslint 提供了三种模式的规则:
  12. * 必要(默认)规则:plugin:vue/essential
  13. * 强烈推荐规则: plugin:vue/strongly-recommended
  14. * 推荐规则: plugin:vue/recommended
  15. * 三者并不是独立的,而是包含关系:推荐规则 》强烈推荐规则 》必要(默认)规则
  16. * 此外,我们也可以在 rules 对这些规则做自己的设定。
  17. * 更多设置规则:https://eslint.vuejs.org/rules/
  18. */
  19. // 'plugin:vue/recommended', // 推荐规则
  20. 'plugin:vue/strongly-recommended' // 强烈推荐规则
  21. // 'plugin:vue/essential', // 必要(默认)规则
  22. ],
  23. plugins: ['prettier'],
  24. parserOptions: {
  25. parser: '@babel/eslint-parser'
  26. },
  27. rules: {
  28. /**
  29. * 0 = off, 1 = warn, 2 = error
  30. */
  31. 'vue/html-self-closing': 0, // 关闭强制自闭合标签检测
  32. 'vue/max-attributes-per-line': ['error', {
  33. 'singleline': {
  34. 'max': 6 // 单行最多6个属性
  35. },
  36. 'multiline': {
  37. 'max': 1 // 超过6个属性,多行显示,每行1个
  38. }
  39. }],
  40. 'vue/singleline-html-element-content-newline': 0, // 关闭强制在单行元素的内容之前和之后使用换行符
  41. 'no-tabs': ['error'], // 禁止使用tab缩进
  42. 'indent': [2, 2], // 缩进风格,两个空格
  43. 'semi': [0], // 关闭语句强制分号结尾
  44. 'no-alert': 1, // 禁止使用 alert confirm prompt
  45. 'no-dupe-keys': 'error', // 在创建对象字面量时不允许key重复 {a: 1, a: 1}
  46. 'no-eval': 'error', // 禁止使用 eval
  47. 'no-eq-null': 'error', // 禁止对 null 使用 == 或 != 运算符
  48. 'no-func-assign': 'error', // 禁止重复的函数声明
  49. 'no-implicit-coercion': 'warn', // 禁止隐式转换
  50. 'no-mixed-spaces-and-tabs': [2, false], // 禁止混用 tab 和空格
  51. 'no-multiple-empty-lines': [1, { max: 1 }], // 空行最多不能超过1行
  52. 'no-redeclare': 2, // 禁止重复声明变量
  53. 'no-spaced-func': 2, // 函数调用时 函数名与()之间不能有空格
  54. 'no-trailing-spaces': 1, // 禁止行末空格
  55. 'no-undef': 'warn', // 不能有未定义的变量
  56. 'no-use-before-define': 2, // 未定义前不能使用 ?禁止声明提前
  57. 'no-var': 2, // 禁止 var 声明变量,使用 let 和 const
  58. 'comma-style': [2, 'last'], // 逗号风格,换行时在行首还是行尾
  59. 'consistent-return': 0, // return 后面是否允许省略
  60. 'consistent-this': [2, 'me'], // this 别名
  61. 'default-case': 2, // switch 语句最后必须有 default
  62. 'dot-location': 0, // 对象访问符的位置,换行时在行首还是行尾
  63. 'id-length': [2, { min: 3 }], // 变量名长度
  64. 'init-declarations': 0, // 声明时必须赋值
  65. 'key-spacing': [2, { beforeColon: false, afterColon: true }], // 对象字面量中冒号的前后空格
  66. 'lines-around-comment': 0, // 行前/行后注释
  67. 'max-depth': [1, 4], // 嵌套块深度
  68. 'space-before-function-paren': [1, 'always'], // 函数定义时括号前面要不要有空格
  69. 'space-unary-ops': [0, { words: true, nonwords: false }], // 一元运算符的前/后要不要加空格
  70. 'use-isnan': 2, // 禁止比较时使用NaN,只能用 isNaN()
  71. 'vars-on-top': 2, // var必须放在作用域顶部
  72. 'vue/require-v-for-key': 'warn', // vue v-for 必须绑定 key
  73. 'no-await-in-loop': 'error', // 禁止在循环中使用 await
  74. 'no-console': 'off', // 关闭禁止使用console
  75. 'no-template-curly-in-string': 'error', // 禁用类似ES6模板字符串的字面量字符串定义
  76. 'block-scoped-var': 'error', // 变量在定义块的外部使用时,规则会报错
  77. 'complexity': [
  78. 'error', { max: 10 }
  79. ],
  80. 'one-var': ['error', 'consecutive'], // 连续的变量声明只使用一个let或const关键字
  81. 'quote-props': [ // 对象字面值属性名称可以用两种方式定义:使用文字或使用字符串
  82. 'error',
  83. 'consistent', // 强制执行一致的引用风格需要引用对象字面值属性名称
  84. {
  85. keywords: true, // 关键字作为属性名称时必须加引号
  86. numbers: true // 数字作为属性名称时必须加引号
  87. }],
  88. 'quotes': ['error', 'single'], // 字符串字面量强制使用单引号
  89. 'no-new': 'off', // 关闭禁止使用new构造方法而不赋值
  90. 'curly': ['error', 'all'], // 必须使用 if(){} 中的{}
  91. 'eqeqeq': ['error', 'smart'], // 必须使用全等
  92. 'no-empty-function': 'warn', // 禁止出现空函数
  93. 'no-implied-eval': 'error', // 禁止使用 eval()
  94. 'no-lone-blocks': 'error', // 禁用不必要的嵌套块
  95. 'no-loop-func': 'error', //
  96. 'no-multi-spaces': 'error', // 不能用多余的空格
  97. 'no-multi-str': 'error', // 字符串不能用\换行
  98. 'no-plusplus': 'off', // 关闭禁止 ++ --
  99. 'no-return-assign': 'error', // return 语句中不能有赋值表达式
  100. 'no-self-compare': 'error', // 不能比较自身
  101. 'no-throw-literal': 'error', // 禁止抛出字面量错误 throw "error"
  102. 'no-unmodified-loop-condition': 'error', // 循环中的变量经常在循环中修改。如果不是,那可能是一个错误。
  103. 'no-useless-concat': 'error', // 没有必要将两个字符串连接在一起 var foo = "a" + "b";
  104. 'no-delete-var': 'error', // 不能对var声明的变量使用delete操作符
  105. 'comma-dangle': 'error', // 对象字面量项尾必须有逗号
  106. 'comma-spacing': 'error', // 逗号前后的空格
  107. 'computed-property-spacing': 'error', // 不允许计算属性括号内的空格
  108. 'implicit-arrow-linebreak': 'error', // 在箭头函数体之前不允许换行
  109. 'keyword-spacing': 'error', // 关键字前后至少有一个空格
  110. 'semi-spacing': 'error', // 分号前后禁用空格
  111. 'semi-style': 'error' // 强制分号位于语句的末尾
  112. }
  113. }

了解更多规则配置请移步:

👉 https://eslint.vuejs.org/rules/

👉 List of available rules - ESLint中文文档 (bootcss.com)

! 本文档中 eslint 检测规则配置只做说明举例,如果与公司骨架项目冲突的话,以骨架项目中为规范。 !

此时的 package.json :

  1. {
  2. "name": "hello-world",
  3. "version": "0.1.0",
  4. "private": true,
  5. "scripts": {
  6. "serve": "vue-cli-service serve",
  7. "build": "vue-cli-service build",
  8. "lint": "vue-cli-service lint"
  9. },
  10. "dependencies": {
  11. "axios": "^0.26.1",
  12. "core-js": "^3.8.3",
  13. "element-ui": "^2.15.6",
  14. "vue": "^2.6.14",
  15. "vue-router": "3.5.3",
  16. "vuex": "3.6.2"
  17. },
  18. "devDependencies": {
  19. "@babel/core": "^7.12.16",
  20. "@babel/eslint-parser": "^7.12.16",
  21. "@babel/preset-env": "^7.16.11",
  22. "@vue/cli-plugin-babel": "~5.0.0",
  23. "@vue/cli-plugin-eslint": "~5.0.0",
  24. "@vue/cli-service": "~5.0.0",
  25. "babel-plugin-component": "^1.1.1",
  26. "eslint": "^7.32.0",
  27. "eslint-plugin-prettier": "^4.0.0",
  28. "eslint-plugin-vue": "^8.0.3",
  29. "sass": "^1.49.9",
  30. "sass-loader": "10.2.1",
  31. "vue-template-compiler": "^2.6.14"
  32. },
  33. "browserslist": [
  34. "> 1%",
  35. "last 2 versions",
  36. "not dead"
  37. ]
  38. }

HBuilder X 中配置 eslint

HBuilderX 2.6.8+版本起,新增eslint 实时校验、自动修复错误的功能。注意:此文不适用于2.6.8之前的版本

参考:HBuilderX 使用eslint实时校验、自动修复代码错误(适用于HBuilderX 2.6.8+) - DCloud问答

下载 eslint-js 插件 eslint-js插件下载地址

下载 eslint-vue 插件 eslint-vue插件下载地址

  • eslint 文件保存,实时校验、自动修复错误功能说明
    1. 使用此功能,必须安装eslint-jseslint-vue插件。(菜单【工具】【插件安装】)
    2. vue-cli项目,需要安装eslint库,并配置eslint规则.
    3. 若满足上述条件,当编写完代码,保存时,若代码中存在错误,自动修复;
  • 插件设置

    2.6.11版本,支持自定义配置:保存时自动修复启用实时校验;见下图


特别说明: 实时校验功能,默认未开启,需要手动开启此功能
工具 👉设置👉插件配置 勾选 eslint-js 下的“启用实时校验” 和 eslint-vue 下的 “启用实时校验”
image.png

  1. 编码过程中 HBuider X 会自动按照 eslint 配置规则修复不规范的编码。

在项目根目录新建 .prettierrc.js文件:

image.png

  1. module.exports = {
  2. printWidth: 120, //限制每行字符个数
  3. tabWidth: 4, //指定每个缩进级别的空格数
  4. useTabs: false, //使用制表符而不是空格缩进
  5. semi: false, //在语句末尾打印分号
  6. singleQuote: true, //使用单引号而不是双引号
  7. trailingComma: 'es5', // 在对象或数组最后一个元素后面是否加逗号(在ES5中加尾逗号)
  8. bracketSpacing: true, //在对象文字中的括号之间打印空格
  9. arrowParens: 'always', //始终给箭头函数的参数加括号
  10. htmlWhitespaceSensitivity: 'css', //指定HTML文件的全局空格敏感度
  11. endOfLine: 'auto' //检测换行符类型,如果出现大量换行符报错,可以修改为auto不检测
  12. }

HBuilder X 安装 prettier 插件

下载 prettier 插件 prettier插件下载地址

修改 HBuilder X 配置:

HBuilder X 编辑器格式化时, 使用的缩进方式,是读取的菜单【工具 —> 设置】中的配置。配置方式:点击“工具-插件安装-已安装的插件-prettier-配置”

image.png

vue 文件的开头在 HBuilder X 中有一个红色波浪线。解决方法:.eslintrc.js添加 requireConfigFile.false 的配置:

  1. module.exports = {
  2. /// ...
  3. parserOptions: {
  4. parser: '@babel/eslint-parser',
  5. requireConfigFile: false // 解决 HBuilder X 中 vue 文件开头显示红色波浪线
  6. },
  7. /// ...
  8. }

5.2 element-ui 主题配置

Element 默认提供一套主题,CSS 命名采用 BEM 的风格,方便使用者覆盖样式。

5.2.1 方式一、自定义element-variables.scss文件方式修改主题【推荐】

1.Element 的 theme-chalk 使用 SCSS 编写,可以直接在项目中改变 Element 在项目common目录创建 element-variables.scss,写入以下内容:
  1. /* 改变主题色变量 */
  2. $--color-primary: teal;
  3. /* 改变 icon 字体路径变量,必需 */
  4. $--font-path: '~element-ui/lib/theme-chalk/fonts';
  5. @import "~element-ui/packages/theme-chalk/src/index";

2.入口文件中引入:
  1. import Vue from 'vue'
  2. import Element from 'element-ui'
  3. import './element-variables.scss'
  4. Vue.use(Element)

需要注意的是,覆盖字体路径变量是必需的,将其赋值为 Element 中 icon 图标所在的相对路径即可。

5.2.2 方式二、深层次的主题定制

1.安装主题生成工具
  1. yarn add element-themex -D

2.安装白垩主题
  1. yarn add element-theme-chalk -D

3.初始化变量文件

主题生成工具安装成功后,通过 node_modules/.bin/et 访问到命令。执行 -i 初始化变量文件。默认输出到 element-variables.scss

  1. ./node_modules/.bin/et -i

当前目录会有一个 element-variables.scss 文件。内部包含了主题所用到的所有变量,它们使用 SCSS 的格式定义。大致结构如下:

  1. $--color-primary: #409EFF !default;
  2. $--color-primary-light-1: mix($--color-white, $--color-primary, 10%) !default; /* 53a8ff */
  3. $--color-primary-light-2: mix($--color-white, $--color-primary, 20%) !default; /* 66b1ff */
  4. $--color-primary-light-3: mix($--color-white, $--color-primary, 30%) !default; /* 79bbff */
  5. $--color-primary-light-4: mix($--color-white, $--color-primary, 40%) !default; /* 8cc5ff */
  6. $--color-primary-light-5: mix($--color-white, $--color-primary, 50%) !default; /* a0cfff */
  7. $--color-primary-light-6: mix($--color-white, $--color-primary, 60%) !default; /* b3d8ff */
  8. $--color-primary-light-7: mix($--color-white, $--color-primary, 70%) !default; /* c6e2ff */
  9. $--color-primary-light-8: mix($--color-white, $--color-primary, 80%) !default; /* d9ecff */
  10. $--color-primary-light-9: mix($--color-white, $--color-primary, 90%) !default; /* ecf5ff */
  11. $--color-success: #67c23a !default;
  12. $--color-warning: #e6a23c !default;
  13. $--color-danger: #f56c6c !default;
  14. $--color-info: #909399 !default;
  15. ...

直接编辑 element-variables.scss 文件,例如修改主题色为红色。

  1. $--color-primary: red;

4.编译主题

在 package.json 中 scripts 下添加编译主题指令:

"build:theme": "node_modules/.bin/et -o ./themes",

此时 package.json :

  1. {
  2. "name": "hello-world",
  3. "version": "0.1.0",
  4. "private": true,
  5. "scripts": {
  6. "serve": "vue-cli-service serve",
  7. "build": "vue-cli-service build",
  8. "build:theme": "node_modules/.bin/et -o ./themes",
  9. "lint": "vue-cli-service lint"
  10. },
  11. "dependencies": {
  12. "axios": "^0.26.1",
  13. "core-js": "^3.8.3",
  14. "element-ui": "^2.15.6",
  15. "vue": "^2.6.14",
  16. "vue-router": "3.5.3",
  17. "vuex": "3.6.2"
  18. },
  19. "devDependencies": {
  20. "@babel/core": "^7.12.16",
  21. "@babel/eslint-parser": "^7.12.16",
  22. "@babel/preset-env": "^7.16.11",
  23. "@vue/cli-plugin-babel": "~5.0.0",
  24. "@vue/cli-plugin-eslint": "~5.0.0",
  25. "@vue/cli-service": "~5.0.0",
  26. "babel-plugin-component": "^1.1.1",
  27. "element-theme-chalk": "^2.15.6",
  28. "element-themex": "^1.0.3",
  29. "eslint": "^7.32.0",
  30. "eslint-plugin-prettier": "^4.0.0",
  31. "eslint-plugin-vue": "^8.0.3",
  32. "sass": "^1.49.9",
  33. "sass-loader": "10.2.1",
  34. "vue-template-compiler": "^2.6.14"
  35. },
  36. "browserslist": [
  37. "> 1%",
  38. "last 2 versions",
  39. "not dead"
  40. ]
  41. }

执行 yarn build:theme 会在根目录生成 themes 文件夹

5.引入自定义主题

修改main.js 中默认主题引入为

  1. // 引入 element-ui 样式
  2. // import 'element-ui/lib/theme-chalk/index.css'
  3. // 引入 自定义 element-ui 主题样式
  4. import '../themes/index.css'

运行 yarn serve 查看效果。

image.png

image.png

六、目录结构说明

6.1 项目目录结构

├─dist 生产环境打包代码输出目录 ├─public 静态资源文件目录 │ └─index.html 接口地址目录 ├─src 源码目录 │ ├─api 接口地址目录 │ ├─assets 静态资源文件目录 │ ├─common 公共文件 │ │ └─style 公共样式相关文件 │ │ ├─base.css全局公共样式 │ │ ├─var.scss全局公共样式 │ │ └─global.scss 公共样式 scss 变量 │ │ └─mixins.scss 公共样式 scss 变量 │ │ └─element-variables.scss 重置elementui主题样式 scss 变量 │ ├─components 组件目录 │ ├─router 路由文件目录 │ │ └─index.js 路由主文件 │ ├─store vuex文件目录 │ │ ├─modules vuex 模块 │ │ └─index vuex 主文件 │ ├─utils 工具目录 │ │ ├─element.js element-ui 组件按需引入 │ │ ├─utils.js 工具方法 │ │ └─request 封装的 axios 实例方法 │ ├─views 页面目录 │ ├─App.vue 入口 vue 文件 │ ├─main.js 入口 js 文件 ├─babel.config.js babel 配置文件 ├─jsconfig.json js 编译 配置文件 ├─package.json └─vue.config.js vue/cli 配置文件

七、集成echarts

1.安装依赖:
  1. yarn add echarts@4.9.0

2.在main.js中全局引入 echarts:
  1. /// ...
  2. // 引入 echarts
  3. import echarts from 'echarts'
  4. Vue.prototype.$echarts = echarts
  5. /// ...

3.在components中创建echarts/echart-temp.vue文件,作为 echarts 公用组件:
  1. <template>
  2. <div class="chart-container" ref="container">
  3. <div class="chart-body" ref="chartDiv" :style="style"></div>
  4. </div>
  5. </template>
  6. <script>
  7. // 引入防抖函数
  8. import { debounce } from '@/utils'
  9. export default {
  10. name: 'EchartTemp',
  11. props: {
  12. options: {
  13. type: Object,
  14. default () {
  15. return null
  16. }
  17. },
  18. styles: {
  19. type: Object,
  20. default () {
  21. return {}
  22. }
  23. }
  24. },
  25. data () {
  26. return {
  27. style: {},
  28. chartObj: null,
  29. chartData: {},
  30. chartDom: null,
  31. // eslint-disable-next-line vue/no-reserved-keys
  32. __resizeHanlder: null
  33. }
  34. },
  35. watch: {
  36. styles (val, oldVal) {
  37. this.style = val
  38. },
  39. options () {
  40. this.drawChart()
  41. }
  42. },
  43. mounted () {
  44. // alert('mounted!');
  45. // console.log(11111,this.options)
  46. this.chartDom = this.$refs.chartDiv
  47. this.chartObj = this.$echarts.init(this.chartDom)
  48. this.chartObj.on('click', (params) => {
  49. this.itemClick(params)
  50. })
  51. this.chartObj.on('legendselectchanged', (params) => {
  52. console.log('this.chartObj: ', this.chartObj)
  53. this.legendClick(this.chartObj, params)
  54. })
  55. this.drawChart()
  56. this.__resizeHanlder = debounce(this.refreshChart)
  57. // 添加尺寸改变事件
  58. window.addEventListener('resize', this.__resizeHanlder)
  59. // this.$bus.$on('toggleSideMenu', this.__resizeHanlder)
  60. },
  61. activated () {
  62. // alert('activated!');
  63. // 添加尺寸改变事件
  64. // window.addEventListener('resize', this.__resizeHanlder);
  65. // this.$bus.$on('toggleSideMenu', this.__resizeHanlder);
  66. this.resizeChart()
  67. },
  68. computed: {},
  69. methods: {
  70. // 刷新图表
  71. refresh () {
  72. this.drawChart()
  73. },
  74. clearChart () {
  75. // this.chartDom.innerHTML = ''
  76. this.chartObj.clear()
  77. },
  78. resizeChart () {
  79. let totleHeight = this.$refs['container'].clientHeight
  80. // this.styles = { height: totleHeight + 'px' };
  81. this.style = { height: totleHeight + 'px' }
  82. this.refreshChart()
  83. },
  84. refreshChart () {
  85. // console.log("this.chartObj",this.chartObj.resize);
  86. this.chartObj.resize()
  87. },
  88. drawChart () {
  89. this.clearChart()
  90. if (this.options && Object.keys(this.options).length > 0) {
  91. this.chartObj.setOption(this.options)
  92. }
  93. },
  94. setOption (option) {
  95. this.chartObj.setOption(option)
  96. this.drawChart()
  97. },
  98. itemClick (params) {
  99. this.$emit('itemClick', params)
  100. },
  101. legendClick (chartObj, params) {
  102. this.$emit('legendClick', chartObj, params)
  103. },
  104. setProps (props) {
  105. if (Object.keys(props).length > 0) {
  106. for (let key in props) {
  107. this[key] = props[key]
  108. }
  109. this.$nextTick(() => {
  110. this.resizeChart()
  111. this.drawChart()
  112. })
  113. }
  114. }
  115. },
  116. beforeDestroy () {
  117. // alert('beforeDestroy');
  118. window.removeEventListener('resize', this.refreshChart)
  119. // this.$bus.$off('toggleSideMenu', this.__resizeHanlder)
  120. },
  121. components: {}
  122. }
  123. </script>
  124. <style lang="scss" scoped>
  125. .chart-container {
  126. width: 100%;
  127. height: 100%;
  128. display: flex;
  129. flex-direction: column;
  130. .chart-body {
  131. flex: 1;
  132. }
  133. }
  134. </style>

src/utils/index.js:

  1. // 防抖函数
  2. export function debounce (func, delay) {
  3. // 维护一个 timer
  4. let timer = null;
  5. return function () {
  6. clearTimeout(timer);
  7. // eslint-disable-next-line consistent-this
  8. const context = this,
  9. args = arguments;
  10. timer = setTimeout(function () {
  11. func.apply(context, args);
  12. }, delay);
  13. };
  14. }

组件使用:

  1. <template>
  2. <div class="echart-demo">
  3. <echart-temp :options="options"></echart-temp>
  4. </div>
  5. </template>
  6. <script>
  7. import EchartTemp from '@/components/echarts/echart-temp.vue'
  8. export default {
  9. name: 'EchartDemo',
  10. components: {
  11. EchartTemp
  12. },
  13. data () {
  14. return {
  15. options: {}
  16. }
  17. },
  18. mounted () {
  19. this.initData()
  20. },
  21. methods: {
  22. initData () {
  23. let xAxisData = [],
  24. data1 = [],
  25. data2 = []
  26. // eslint-disable-next-line id-length
  27. for (let i = 0; i < 100; i++) {
  28. xAxisData.push('类目' + i)
  29. data1.push((Math.sin(i / 5) * (i / 5 - 10) + i / 6) * 5)
  30. data2.push((Math.cos(i / 5) * (i / 5 - 10) + i / 6) * 5)
  31. }
  32. this.drawChart(xAxisData, data1, data2)
  33. },
  34. drawChart (xAxisData, data1, data2) {
  35. this.options = {
  36. title: {
  37. text: '柱状图动画延迟'
  38. },
  39. legend: {
  40. data: ['bar', 'bar2']
  41. },
  42. toolbox: {
  43. // y: 'bottom',
  44. feature: {
  45. magicType: {
  46. type: ['stack', 'tiled']
  47. },
  48. dataView: {},
  49. saveAsImage: {
  50. pixelRatio: 2
  51. }
  52. }
  53. },
  54. tooltip: {},
  55. xAxis: {
  56. data: xAxisData,
  57. splitLine: {
  58. show: false
  59. }
  60. },
  61. yAxis: {},
  62. series: [
  63. {
  64. name: 'bar',
  65. type: 'bar',
  66. data: data1,
  67. animationDelay: function (idx) {
  68. return idx * 10
  69. }
  70. },
  71. {
  72. name: 'bar2',
  73. type: 'bar',
  74. data: data2,
  75. animationDelay: function (idx) {
  76. return idx * 10 + 100
  77. }
  78. }
  79. ],
  80. animationEasing: 'elasticOut',
  81. animationDelayUpdate: function (idx) {
  82. return idx * 5
  83. }
  84. }
  85. }
  86. }
  87. }
  88. </script>
  89. <style lang="scss" scoped>
  90. .echart-demo {
  91. width: 800px;
  92. height: 600px;
  93. border: 1px solid darkblue;
  94. }
  95. </style>

效果:

image.png

八、常见问题

8.1 vue-router 不同的历史模式

在创建路由器实例时,history 配置允许我们在不同的历史模式中进行选择。

  • Hash 模式#

hash 模式是用 createWebHashHistory() 创建的:

  1. import { createRouter, createWebHashHistory } from 'vue-router'
  2. const router = createRouter({
  3. history: createWebHashHistory(),
  4. routes: [
  5. //...
  6. ],
  7. })

它在内部传递的实际 URL 之前使用了一个哈希字符(#)。由于这部分 URL 从未被发送到服务器,所以它不需要在服务器层面上进行任何特殊处理。不过,它在 SEO 中确实有不好的影响。如果你担心这个问题,可以使用 HTML5 模式。

  • HTML5 模式#

createWebHistory() 创建 HTML5 模式,推荐使用这个模式:

  1. import { createRouter, createWebHistory } from 'vue-router'
  2. const router = createRouter({
  3. history: createWebHistory(),
  4. routes: [
  5. //...
  6. ],
  7. })

当使用这种历史模式时,URL 会看起来很 “正常”,例如 https://example.com/user/id。漂亮!

不过,问题来了。由于我们的应用是一个单页的客户端应用,如果没有适当的服务器配置,用户在浏览器中直接访问 https://example.com/user/id,就会得到一个 404 错误。这就丑了。

不用担心:要解决这个问题,你需要做的就是在你的服务器上添加一个简单的回退路由。如果 URL 不匹配任何静态资源,它应提供与你的应用程序中的 index.html 相同的页面。漂亮依旧!

8.2 cnpm与nrm

使用 npm安装包时是从国网服务器下载,受网络影响大,会遇到时间长,甚至安装失败的问题。此时可以选择使用国内镜像源进行安装,例如国内的淘宝镜像等来代替npm或者yarn的官方服务器。

常用的是使用官方推荐的 cnpm 命令行工具代替默认的 npm,实际使用的是淘宝源:

  1. npm install -g cnpm --registry=https://registry.npm.taobao.org

注意:有时使用 cnpm 安装的路径可能回存在问题,在使用react-native开发应用时会出现问题。此时可以使用nrm切换淘宝源。

8.2.1nrm

nrm是一个npm源管理器,可以快速的切换npm源,使用nrm有两种方式

  • npm安装nrm
  1. npm install nrm -g
  • 使用 nrm

所以使用nrm切换镜像源可以是:

  1. # 查看当前可用的源
  2. nrm ls
  3. # 切换源
  4. nrm use taobao
  5. # 添加源
  6. nrm add '镜像名称' '镜像地址'
  7. # 删除源
  8. nrm del '镜像名称'

8.3 node 版本管理工具 nvm

nvm是一个node的版本管理工具,可以简单操作node版本的切换、安装、查看等等

常用命令:

  1. # 列出本地已安装的node版本
  2. nvm ls
  3. # 安装node命令
  4. nvm install '版本号'
  5. # 卸载node命令
  6. nvm uninstall '版本号'
  7. # 切换 node 版本号
  8. nvm use '版本号'
  9. # 查看当前版本
  10. nvm current

8.4 yarn 环境安装依赖报错解决方法(info fsevents@1.2.7)

  1. yarn config set ignore-engines true

8.5 解决自定义element-ui主题,刷新页面偶尔出现的字体图标乱码

  1. vue.config.js 增加 sass自定义配置
  1. 'use strict'
  2. module.exports = {
  3. css: {
  4. loaderOptions: {
  5. sass: {
  6. sassOptions: {
  7. // 生效代码:compressed/nested/expanded/compact
  8. outputStyle: 'expanded'
  9. }
  10. }
  11. }
  12. }
  13. }
  1. 升级 sass1.54.x版本。