前端 vue项目构建流程技术指导
为保证前端人员构建vue工程项目的流程化、规范化、统一化,并有效提升前端人员从零搭建项目的能力,特建立《前端vue项目构建流程技术指导》文档,前端人员不借助骨架项目构建项目的时候,应按照该指导说明文档进行项目构建,从而保证构建项目的一致性、全面性和规范性。
该指导文档包括统一开发工具(HBuilder X)、统一代码检测工具、统一格式化配置、统一 node 版本、统一 sass 版本、统一通用型依赖安装(ui组件库、vuex、vueRouter、axios等),统一目录结构、统一 http 请求封装。
该文档还包括从创建项目、依赖安装、相关配置、目录结构等详细的构建流程,以及一些公共性问题的处理方案。
PDF文档
目录
一、前端环境搭建
1.1 nodejs 安装
1.下载nodejs 安装程序
下载地址:CNPM Binaries Mirror (npmmirror.com). 18.1/)
根据系统下载对应的安装包 ,node版本,请统一选择v14.16.1
安装时建议修改安装目录,建议放到非C盘目录下。
安装完成后启动命令行工具,输入node -v
npm -v
查看安装版本,出现提示版本信息即为安装成功。
2.环境变量配置
这里的环境变量配置主要的是npm安装的全局模块所在的路径,以及缓存cache的路径,如果不配置在执行类似:`npm install 模块名 [-g]`的安装语句时,会将安装的模块安装到【C:\Users\用户名\AppData\Roaming\npm】路径中,占用C盘空间。
本文将nodejs
安装在D:\soft\nodejs
目录下,以下操作可以根据实际安装目录情况进行对应的调整。
- 在安装目录下,如
D:\soft\nodejs
新建两个文件夹node_global
(全局包存放目录) 和node_cache
(缓存目录); - 打开命令行工具,执行以下两句操作:
npm config set prefix "D:\soft\nodejs\node_global"``npm config set cache "D:\soft\nodejs\node_cache"
; - 配置环境变量:
- 打开系统属性-高级-环境变量,在系统变量中新建 变量名:
NODE_PATH
,变量值:D:\soft\nodejs\node_global\node_modules
(见图2); - 编辑用户变量的
path
,将默认的C
盘下APPData/Roaming\npm
修改为D:\soft\nodejs\node_global
(见图3); - 保存即可。
1.2 安装 yarn
因为 yarn 具有优势,开发中推荐使用 yarn 来替代 npm 进行操作。安装 yarn 可以下载安装包进行安装,也可以使用 npm 安装:
npm install yarn -g
1.2.1 yarn的常用命令
# 1.安装包,类似于npm install
yarn
# 2.安装某个包,类似于npm install vue --save
yarn add vue
# 3.卸载某个包,类似于npm uninstall vue --save
yarn remove vue
# 4.安装到开发依赖,类似于npm install vue --save-dev
yarn add vue --dev
# 5.更新包
yarn upgrade #更新包到基于规范范围的最新版本
yarn upgrade --latest # 忽略版本规则,升级到最新版本,并且更新 package.json
1.2.2镜像源管理器
yrm是yarn源的管理器,用于快速切换镜像源
# 安装yrm
yarn add -g yrm
# 查看当前可用源
yrm ls
# 切换源
yrm use taobao
# 查看yarn当前镜像源
yarn config get registry
# 添加源
yarn add npmhzwq http://192.168.14.25:8081/repository/npm-all/
# 删除源
yrm del npmhzwq
# 测试源响应时间
yrm test
想了解cnpm以及npm的源管理器nrm可以查看8.2关于cnpm及nrm介绍.
二、项目创建
2.1 安装 Vue CLI
执行下列命令安装Vue CLI
yarn global add @vue/cli@5.0.3 # yarn方式
npm install -g @vue/cli@5.0.3 # npm方式
安装之后,你就可以在命令行中访问 vue
命令。你可以通过简单运行 vue
,看看是否展示出了一份所有可用命令的帮助信息,来验证它是否安装成功。
你还可以用这个命令来检查其版本是否正确:
vue -V
如需升级项目中的 Vue CLI 相关模块(以 @vue/cli-plugin-
或 vue-cli-plugin-
开头),请在项目目录下运行 vue upgrade
:
用法: upgrade [options] [plugin-name]
(试用)升级 Vue CLI 服务及插件
选项:
-t, --to <version> 升级 <plugin-name> 到指定的版本
-f, --from <version> 跳过本地版本检测,默认插件是从此处指定的版本升级上来
-r, --registry <url> 使用指定的 registry 地址安装依赖
--all 升级所有的插件
--next 检查插件新版本时,包括 alpha/beta/rc 版本在内
-h, --help 输出帮助内容
2.2 创建项目
在要创建项目的路径下使用命令行创建项目
vue create hello-world
会被提示选取一个preset。这里选择基本的Babel + ESLint 设置的preset。
使用键盘上下方向键定位到 Default([Vue 2] babel, eslint),然后回车
如图,项目创建完成
执行
cd hello-world
进入新建项目,
运行项目:
yarn serve #yarn方式
npm run serve #npm方式
在浏览器中访问 http://localhost:8080
或http://192.168.23.123:8080
至此,基础的基于 @vue/cli 的项目创建完成。
项目中 package.json
文件内容:
{
"name": "hello-world",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"core-js": "^3.8.3",
"vue": "^2.6.14"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3",
"vue-template-compiler": "^2.6.14"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "@babel/eslint-parser"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
三、依赖安装
3.1 安装sass
sass-loader
:
yarn add sass -D
yarn add sass-loader -D
若安装依赖报错,请参考8.4章节.
3.2 安装vuex
:
yarn add vuex@3.6.2
对于大型应用,我们推荐 Vuex 相关代码按业务模块进行划分管理。
1.在src目录下创建如下目录结构:
└── store
├── index.js # 我们组装模块并导出 store 的地方
└── modules
├── menu.js # 模块示例:菜单模块
└── user.js # 模块示例:用户模块
2.src\index.js 书写demo:
import Vue from 'vue';
import Vuex from 'vuex';
//导入user模块
import user from './modules/user'
import menu from './modules/menu'
Vue.use(Vuex)
//创建vuex实列
export default new Vuex.Store({
//模块化创建
moudles:{
user,
menu
}
})
- user.js 模块书写demo
//导入axios
import axios from 'axios'
export default {
//开启命名空间一定要写这个
namespaced:true,
//state数据状态定义
state(){
profile:{
user:'小明', //用户名
token:null //用户的token
}
},
//vuex方法
// mutations 定义方法
mutations:{
//定义一个修改用户的方法函数
setUser(state,profile){
state.profile = profile //profile 是外界传的参数
}
},
//请求数据的地方 如果要请求一个数据可以这样来写这个是补充的
// actions 定义方法
actions:{
//调用axios的请求函数
async getUserInfo(context){
const{ data:data} = await axios.get('请求的路径地址',{请求的参数})
console.log(data) //服务器返回的数据
context.commit('setUser', data)
}
}
}
在src\main.js
中引入 store\index.js
以使用 vuex:
import Vue from 'vue'
import App from './App.vue'
// vuex
import store from './store'
Vue.config.productionTip = false
new Vue({
store, // 使用store
render: h => h(App),
}).$mount('#app')
配置路径别名,方便组件引入
在vue.config.js 中配置路径别名:
const { defineConfig } = require('@vue/cli-service')
const path = require('path')
const resolve = (dir) => path.join(__dirname, dir)
module.exports = defineConfig({
transpileDependencies: true,
chainWebpack: config => {
// 配置别名
config.resolve.alias
.set('@', resolve('src'))
.set('@views', resolve('src/views'))
}
})
!! 修改 vue.config.js 后 需要
Ctrl + C
停止项目,然后yarn serve
运行项目才能看到修改后的效果。!!
3.3 安装vue-router
yarn add vue-router@3.5.3
在 src 目录下创建 router 文件夹,
在 router 文件夹中新建 index.js
,routes.js
。
src\router\index.js:
import Vue from 'vue';
import Router from 'vue-router';
import routes from './routes';
Vue.use(Router);
const router = new Router({
mode: 'history',
routes
});
export default router;
src\router\routes.js:
const DashBoard = () => import('@views/dash-board.vue');
const DemoPage = () => import('@views/demo-page.vue');
const routes = [
{
path: '/dashboard',
name: 'DashBoard',
component: DashBoard
},
{
path: '/demo',
name: 'DemoPage',
component: DemoPage
}
];
export default routes;
注意:routes 的 name 名称要使用大驼峰命名,并与组件定义的 name 名称保持一致。
对应的在 src 目录中创建 views 文件夹,
在 src\views 文件夹中创建 dash-board.vue
demo-page.vue
。
注意:文件名采用小写中划线拼接,vue文件中必须定义 name 属性,并且和 routes 的 name 保持一致。
在 main.js 中引入 router.js:
import Vue from 'vue'
import App from './App.vue'
// vuex
import store from './store'
// vue-router
import router from './router'
Vue.config.productionTip = false
new Vue({
store, // 使用store
router, // 使用vue-router
render: h => h(App),
}).$mount('#app')
在地址栏输入 http://localhost:8080/dashboard
发现页面并没有跳转。
在 App.vue 中添加 <router-view></router-view>
:
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png" />
<!-- <HelloWorld msg="Welcome to Your Vue.js App" /> -->
<ul>
<li>
<router-link to="/">首页</router-link>
</li>
<li>
<router-link to="/dashboard">DashBoard</router-link>
</li>
<li>
<router-link to="/demo">DemoPage</router-link>
</li>
</ul>
<router-view></router-view>
</div>
</template>
<script>
// import HelloWorld from "./components/HelloWorld.vue";
export default {
name: "App",
components: {
// HelloWorld,
},
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
再次访问 http://localhost:8080/dashboard
:
访问 `http://localhost:8080/demo`:
3.4 安装element-ui
:
yarn add element-ui
3.4.1 全量引入element-ui
在 main.js 中全局引入 element-ui:
import Vue from 'vue'
import App from './App.vue'
// 全局引入 element-ui
import ElementUI from 'element-ui'
// 引入 element-ui 样式
import 'element-ui/lib/theme-chalk/index.css'
// vuex
import store from './store'
// vue-router
import router from './router'
// 配置默认尺寸 small z-index层级 3000
Vue.use(ElementUI, {
size: 'small',
zIndex: 3000
})
Vue.config.productionTip = false
new Vue({
store, // 使用store
router, // 使用vue-router
render: h => h(App),
}).$mount('#app')
使用 elment-ui 中的 el-link
替换 router-link
:
<template>
<div id="app">
...
<ul>
<li>
<!-- <router-link to="/">首页</router-link> -->
<el-link href="/" type="primary">首页</el-link>
</li>
<li>
<!-- <router-link to="/dashboard">DashBoard</router-link> -->
<el-link href="/dashboard" type="success">DashBoard</el-link>
</li>
<li>
<!-- <router-link to="/demo">DemoPage</router-link> -->
<el-link href="/demo" type="info">DemoPage</el-link>
</li>
</ul>
...
</div>
</template>
验证效果:
3.4.2 按需引入 element-ui: [推荐]
借助 babel-plugin-component,我们可以只引入需要的组件,以达到减小项目体积的目的。
1.首先,安装 babel-plugin-component:
yarn add babel-plugin-component @babel/preset-env -D
2.然后修改 babel.config.js
:
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset',
['@babel/preset-env', { modules: false }]
],
plugins: [
[
'component',
{
'libraryName': 'element-ui',
'styleLibraryName': 'theme-chalk'
}
],
"@babel/plugin-transform-runtime"
]
}
3.创建 src/utils/element.js
文件,下面列举了所有的组件,在项目中只选择需要用到的组件即可:
import Vue from 'vue'
import {
Pagination,
Dialog,
Divider,
Autocomplete,
Dropdown,
DropdownMenu,
DropdownItem,
Menu,
Submenu,
MenuItem,
MenuItemGroup,
Input,
InputNumber,
Radio,
RadioGroup,
RadioButton,
Checkbox,
CheckboxButton,
CheckboxGroup,
Switch,
Select,
Option,
OptionGroup,
Button,
ButtonGroup,
Table,
TableColumn,
DatePicker,
TimeSelect,
TimePicker,
Popover,
Tooltip,
Breadcrumb,
BreadcrumbItem,
Form,
FormItem,
Tabs,
TabPane,
Tag,
Tree,
Timeline,
TimelineItem,
Link,
Alert,
Slider,
Icon,
Row,
Col,
Upload,
Progress,
Badge,
Card,
Rate,
Scrollbar,
Steps,
Step,
Carousel,
CarouselItem,
Collapse,
CollapseItem,
Cascader,
ColorPicker,
Transfer,
Container,
Header,
Aside,
Main,
Footer,
Loading,
MessageBox,
Message,
Notification
} from 'element-ui'
Vue.use(Pagination)
Vue.use(Dialog)
Vue.use(Divider)
Vue.use(Autocomplete)
Vue.use(Dropdown)
Vue.use(DropdownMenu)
Vue.use(DropdownItem)
Vue.use(Menu)
Vue.use(Submenu)
Vue.use(MenuItem)
Vue.use(MenuItemGroup)
Vue.use(Input)
Vue.use(InputNumber)
Vue.use(Radio)
Vue.use(RadioGroup)
Vue.use(RadioButton)
Vue.use(Checkbox)
Vue.use(CheckboxButton)
Vue.use(CheckboxGroup)
Vue.use(Switch)
Vue.use(Select)
Vue.use(Option)
Vue.use(OptionGroup)
Vue.use(Button)
Vue.use(ButtonGroup)
Vue.use(Table)
Vue.use(TableColumn)
Vue.use(DatePicker)
Vue.use(TimeSelect)
Vue.use(TimePicker)
Vue.use(Popover)
Vue.use(Tooltip)
Vue.use(Breadcrumb)
Vue.use(BreadcrumbItem)
Vue.use(Form)
Vue.use(FormItem)
Vue.use(Tabs)
Vue.use(TabPane)
Vue.use(Tag)
Vue.use(Tree)
Vue.use(Timeline)
Vue.use(TimelineItem)
Vue.use(Link)
Vue.use(Alert)
Vue.use(Slider)
Vue.use(Icon)
Vue.use(Row)
Vue.use(Col)
Vue.use(Upload)
Vue.use(Progress)
Vue.use(Badge)
Vue.use(Card)
Vue.use(Rate)
Vue.use(Scrollbar)
Vue.use(Steps)
Vue.use(Step)
Vue.use(Carousel)
Vue.use(CarouselItem)
Vue.use(Collapse)
Vue.use(CollapseItem)
Vue.use(Cascader)
Vue.use(ColorPicker)
Vue.use(Transfer)
Vue.use(Container)
Vue.use(Header)
Vue.use(Aside)
Vue.use(Main)
Vue.use(Footer)
Vue.use(Loading.directive)
Vue.prototype.$loading = Loading.service
Vue.prototype.$msgbox = MessageBox
Vue.prototype.$alert = MessageBox.alert
Vue.prototype.$confirm = MessageBox.confirm
Vue.prototype.$prompt = MessageBox.prompt
Vue.prototype.$notify = Notification
Vue.prototype.$message = Message
Vue.prototype.$ELEMENT = {
size: 'small',
zIndex: 3000
};
// element组件库的Dialog对话框默认可以通过点击 modal 关闭 Dialog,即点击空白处弹框可关闭。
Dialog.props.closeOnClickModal.default = false;
在 main.js
中修改全量引入为按需引入:
import Vue from 'vue'
import App from './App.vue'
// vuex
import store from './store'
// vue-router
import router from './router'
// 全局引入 element-ui
// import ElementUI from 'element-ui'
// 引入 element-ui 样式
// import 'element-ui/lib/theme-chalk/index.css'
// 配置默认尺寸 small z-index层级 3000 [全量引入配置]
/*
Vue.use(ElementUI, {
size: 'small',
zIndex: 3000
})
*/
// 按需引入 element 组件 [先]
import '@/utils/element.js'
// 引入 自定义 element-ui 主题样式 [后]
import '../themes/index.css'
// 引入全局公用样式
import '@/common/style/index.scss'
Vue.config.productionTip = false
new Vue({
store, // 使用store
router, // 使用vue-router
render: hyperscript => hyperscript(App)
}).$mount('#app')
3.5 安装axios
:
yarn add axios
1.封装axios:在 src 文件夹下创建 utils 文件夹,并在 utils 文件夹中新建 request.js
文件,写入如下内容:
src/utils/request.js
import router from '@/router'
import axios from 'axios'
import { Message } from 'element-ui'
// 创建 axios 实例
const service = axios.create({
timeout: 30000,
headers: {
'Content-Type': 'application/json;charset=utf-8'
}
})
// 拦截 request 请求
service.interceptors.request.use(function (config) {
// 统一添加配置信息
// 防止IE浏览器缓存
config.headers.common['Cache-Control'] = 'no-cache'
// 获取 token,若不存在,则为空
const token = localStorage.getItem('token')
if (token) {
// 如果存在 token,则每个请求 header 都加上 token
config.headers.authorization = token
}
return config
}, function (error) {
return Promise.reject(error)
})
// 拦截 response, 统一处理错误
service.interceptors.response.use(function (response) {
if (response.data && response.status === 200) {
return response
}
return Promise.reject(new Error(response))
}, function (error) {
// axios 获得的响应 status code 超出了 2xx 的范围时,统一处理接口错误
if (!window.navigator.onLine) {
// 断网处理
router.push('/error')
} else {
if (error && error.response) {
const duration = 3000,
errorStatusHash = {
401: '权限不足',
404: '请求路径不存在'
// 500: '服务器异常'
}
if (error.response.status in errorStatusHash) {
Message({
message: errorStatusHash[error.response.status],
type: 'error',
duration
})
if (error.response.status === 500) {
const errMsgHash = {
S00001: '接口超时',
S00002: '接口解析错误',
S00003: '接口返回数据异常',
S00004: '代码报错',
S00005: '数据库报错'
}
console.log(errMsgHash[error.response.data.code] || '服务器异常')
// Message({
// message: '服务器异常',
// type: 'error',
// duration: 3 * 1000
// })
}
} else {
Message({
message: '操作失败',
type: 'error',
duration: 3 * 1000
})
}
}
}
return Promise.reject(error)
})
// 实例变量支持 all
service.all = axios.all
export default service
3.使用request发起api请求,并统一管理:在 src 目录下创建 api 文件夹,在api文件夹中新建 test-api.js
文件,写入如下内容:
import request from '@utils/request'
// get 请求
export const testApi = (params) => {
return request({
url: '/api/v1/images/search',
method: 'get',
params // params: params 简写
})
}
// post 请求
export const testApiPost = (params) => {
return request({
url: '/api/v1/images/search',
method: 'get',
data: params
})
}
4.测试:在 src\views\demo-page.vue 中测试 封装的axiso:
<template>
<div class="demo-page">
<h4>Demo page.</h4>
<img class="random-img" :src="randomImg" alt="" />
</div>
</template>
<script>
import { testApi } from "@api/test-api";
export default {
name: 'DemoPage',
data() {
return {
randomImg: "",
};
},
mounted() {
this.initData();
},
methods: {
initData() {
const params = {greeting: "Hi!"}
testApi(params).then((res) => {
console.log("res: ", res);
if (res.status === 200) {
if (res.data && res.data.length) {
this.randomImg = res.data[0].url;
}
}
})
.catch((err) => {
console.log("err: ", err);
});
},
},
};
</script>
<style lang="scss" scoped>
.demo-page {
text-align: center;
.random-img {
max-width: 50vw;
}
}
</style>
在 vue.config.js 中添加别名配置:
// webpack 配置
chainWebpack: config => {
config.resolve.alias
.set('@', resolve('src'))
.set('@views', resolve('src/views'))
.set('@utils', resolve('src/utils'))
.set('@api', resolve('src/api'))
}
访问http://localhost:8080/demo
:
至此
项目目录结构如下图:
!!( 图片中DashBoard.vue 、DemoPage.vue 应为 dash-board.vue 、demo-page.vue )!!
package.json
中的依赖如下:
"dependencies": {
"axios": "^0.26.1",
"core-js": "^3.8.3",
"element-ui": "^2.15.6",
"vue": "^2.6.14",
"vue-router": "3.5.3",
"vuex": "3.6.2"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3",
"sass": "^1.49.9",
"sass-loader": "10.2.1",
"vue-template-compiler": "^2.6.14"
},
四、定义全局公共样式
为了便于公共样式的统一管理,我们需要定义全局公共样式。
1.引入base.css文件
在main.js文件引入base.css文件
@charset "utf-8";
/*主要用于样式重置base.css*/
/*移动端默认样式*/
/*清除掉按下时会有一个灰色阴影*/
a,input,button{
-webkit-tap-highlight-color: rgba(0,0,0,0);
}
/*清除掉ios自带圆角*/
input,button{
-webkit-appearance: none;/*消除输入框核按钮的默认外观*/
border-radius: 0;
}
/* 禁用iPhone中Safari的字号自动调整 */
html {
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
/* 解决IOS默认滑动很卡的情况 */
-webkit-overflow-scrolling : touch;
overflow-scrolling:touch;
}
/* 禁止缩放表单 */
input[type="submit"], input[type="reset"], input[type="button"], input {
resize: none;
border: none;
}
/* 取消链接高亮 */
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 {
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
/* 设置HTML5元素为块 */
article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section {
display: block;
}
/* 图片自适应 */
img {
width: 100%;
height: auto;
width: auto\9; /* ie8 */
display: block;
-ms-interpolation-mode: bicubic;/*为了照顾ie图片缩放失真*/
}
/* 初始化 */
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 {
margin: 0;
padding: 0;
}
body{
/*禁止选中文字*/
-webkit-user-select: none;
/* iPhone 和 Android 的浏览器纵向 (Portrate mode) 和橫向 (Landscape mode) 模式皆有自动调整字体大小的功能。控制它的就是 CSS 中的 -webkit-text-size-adjust。关闭字体大小自动调整功能*/
-webkit-text-size-adjust: 100%;
}
/*字体设置*/
body *{
/*每台设备里的默认字体是不一样的(移动端设备里大多数没有宋体和微软雅黑字体)*/
font-family: helvetica;
}
em, i {font-style: normal;}
ul,li{list-style-type: none;}
h1,h2,h3,h4,h5,h6,strong,b,i,em{font-weight: normal; font-style: normal;}
a {text-decoration: none;color: #969696;font-family: 'Microsoft YaHei', Tahoma, Arial, sans-serif;}
a:hover {text-decoration: none;}
ul, ol {list-style: none;}
h1, h2, h3, h4, h5, h6 {font-size: 100%;font-family: 'Microsoft YaHei';}
img {border: none;}
input{font-family: 'Microsoft YaHei';}
/* 移动端点击a链接出现蓝色背景问题解决 */
a:link,a:active,a:visited,a:hover {
background: none;
-webkit-tap-highlight-color: rgba(0,0,0,0);
-webkit-tap-highlight-color: transparent;
}
.clearfix:after {
content: "";
display: block;
visibility: hidden;
height: 0;
clear: both;
}
.clearfix {zoom: 1;}
2.定义scss变量文件var.scss
在src/common/style/
目录中,新建var.scss文件,并定义如下内容(相关变量的值可以参考项目的ui效果图主题色信息修改):
//定义单位-uniapp项目需要使用 rpx;@return $px * 1rpx;
@function px($px) {
@return $px * 1px;
}
// 颜色
$color-primary: #2ffec4;
$color-success: #09ad9c;
$color-warning: #f8af39;
$color-danger: #ff3333;
$color-white: white;
$color-black: black;
$colors: (
primary: $color-primary,
success: $color-success,
warning: $color-warning,
danger: $color-danger,
white: $color-white,
black: $color-black,
);
//字体大小
$font-mini: px(12);
$font-small: px(16);
$font-base: px(14);
$font-large: px(18);
$fonts: (
mini: $font-mini,
small: $font-small,
base: $font-base,
large: $font-large,
);
//border
$border-width: px(1);
$border-color: #e8e8e8;
// border-radius
$radius-base: px(4);
$radius-circle: 50%;
$radius: (
base: $radius-base,
circle: $radius-circle,
);
//定位
$positions: (
absolute: absolute,
relative: relative,
fixed: fixed,
);
//文字对齐
$textAligns: (
left: left,
center: center,
right: right,
);
//浮动
$floats: (
left: left,
right: right,
);
//间距
$size-zero: 0;
$size-mini: px(10);
$size-small: px(20);
$size-base: px(24);
$size-large: px(28);
$sizes: (
zero: $size-zero,
mini: $size-mini,
small: $size-small,
base: $size-base,
large: $size-large,
);
3.定义重复性强的样式代码块文件mixins.scss
//字体
@mixin font-family(
$value: (
'Microsoft YaHei',
Tahoma,
Arial,
sans-serif,
)
) {
font-family: $value;
}
// 文字溢出隐藏显示...
@mixin text-overflow($line: 1) {
@if $line==1 {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
} @else {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: $line;
/*! autoprefixer: off */
-moz-box-orient: vertical;
-webkit-box-orient: vertical;
}
}
//border
@mixin border($color: $border-color, $width: $border-width) {
border: $width solid $color;
}
@mixin border-dir($i, $width: $border-width, $color: $border-color) {
border-#{$i}: $width solid $color;
}
// border-radius
@mixin radius($radius: $radius-base, $overflow: hidden) {
border-radius: $radius;
overflow: $overflow;
}
//间距
@mixin padding-col($n: $size-base, $sizeKey: none) {
@if $sizeKey == 'zero' {
padding-top: $n !important;
padding-bottom: $n !important;
} @else {
padding-top: $n;
padding-bottom: $n;
}
}
@mixin padding-row($n: $size-base, $sizeKey: none) {
@if $sizeKey == 'zero' {
padding-left: $n !important;
padding-right: $n !important;
} @else {
padding-left: $n;
padding-right: $n;
}
}
@mixin margin-col($n: $size-base, $sizeKey: none) {
@if $sizeKey == 'zero' {
margin-top: $n !important;
margin-bottom: $n !important;
} @else {
margin-top: $n;
margin-bottom: $n;
}
}
@mixin margin-row($n: $size-base, $sizeKey: none) {
@if $sizeKey == 'zero' {
margin-left: $n !important;
margin-right: $n !important;
} @else {
margin-left: $n;
margin-right: $n;
}
}
4.新建公共样式文件global.scss文件
在 src/common/style/
目录,新建global.scss文件,并定义全局公共样式。代码如下:
@import './var.scss';
@import './mixins.scss';
.font-family {
@include font-family;
}
// 颜色
@each $colorKey, $color in $colors {
//字体颜色 .text-#{'' + $colorKey}用于解决警告,添加‘’强制转换为字符串
.text-#{'' + $colorKey} {
color: $color;
}
//背景颜色 .bg-#{'' + $colorKey}用于解决警告,添加‘’强制转换为字符串
.bg-#{'' + $colorKey} {
background-color: $color;
}
}
//字体大小
@each $fontKey, $font in $fonts {
.font-#{$fontKey} {
font-size: $font;
}
}
// 文字溢出隐藏显示...
//1-4行
@for $i from 1 through 4 {
@if $i == 1 {
.line-ellipsis {
@include text-overflow($i);
}
} @else {
.line-limited#{$i} {
@include text-overflow($i);
}
}
}
//border
//颜色边框,.border-#{''+$borderKey} 用于解决警告,添加''强制转换为字符串
@each $borderKey, $border in $colors {
.border-#{'' + $borderKey} {
@include border($border);
}
}
//边框粗细
@for $i from 1 through 5 {
@if $i == 1 {
.border {
@include border;
}
} @else {
.border#{$i} {
@include border($border-color, px($i));
}
}
}
//边框方向
@each $i in left, right, top, bottom {
.border-#{$i} {
@include border-dir($i);
}
}
// border-radius
@each $radiusKey, $radiu in $radius {
.border-radius-#{$radiusKey} {
@include radius($radiu);
}
}
// 定位
@each $positionKey, $position in $positions {
.position-#{$positionKey} {
position: $position;
}
}
// 文字对齐
@each $textAlignKey, $textAlign in $textAligns {
.text-#{$textAlignKey} {
text-align: $textAlign;
}
}
//浮动
@each $floatKey, $float in $floats {
.float-#{$floatKey} {
float: $float;
}
}
//清除浮动
.clearfix {
zoom: 1;
&:after {
content: '';
display: block;
visibility: hidden;
height: 0;
clear: both;
}
}
//间距
@each $sizeKey, $size in $sizes {
.p-#{$sizeKey} {
@include padding-col($size, $sizeKey);
@include padding-row($size, $sizeKey);
}
.p-row-#{$sizeKey} {
@include padding-row($size, $sizeKey);
}
.p-col-#{$sizeKey} {
@include padding-col($size, $sizeKey);
}
.pl-#{$sizeKey} {
@if $sizeKey == 'zero' {
padding-left: $size !important;
} @else {
padding-left: $size;
}
}
.pr-#{$sizeKey} {
@if $sizeKey == 'zero' {
padding-right: $size !important;
} @else {
padding-right: $size;
}
}
.pt-#{$sizeKey} {
@if $sizeKey == 'zero' {
padding-top: $size !important;
} @else {
padding-top: $size;
}
}
.pb-#{$sizeKey} {
@if $sizeKey == 'zero' {
padding-bottom: $size !important;
} @else {
padding-bottom: $size;
}
}
.m-#{$sizeKey} {
@include margin-col($size, $sizeKey);
@include margin-row($size, $sizeKey);
}
.m-row-#{$sizeKey} {
@include margin-row($size, $sizeKey);
}
.m-col-#{$sizeKey} {
@include margin-col($size, $sizeKey);
}
.ml-#{$sizeKey} {
@if $sizeKey == 'zero' {
margin-left: $size !important;
} @else {
margin-left: $size;
}
}
.mr-#{$sizeKey} {
@if $sizeKey == 'zero' {
margin-right: $size !important;
} @else {
margin-right: $size;
}
}
.mt-#{$sizeKey} {
@if $sizeKey == 'zero' {
margin-top: $size !important;
} @else {
margin-top: $size;
}
}
.mb-#{$sizeKey} {
@if $sizeKey == 'zero' {
margin-bottom: $size !important;
} @else {
margin-bottom: $size;
}
}
}
//flex
.flex-row {
display: flex;
}
.flex-row-vcenter {
display: flex;
align-items: center;
} /* 垂直居中对齐 */
.flex-row-rcenter {
display: flex;
justify-content: center;
} /* 水平居中对齐 */
.flex-row-c {
display: flex;
align-items: center;
justify-content: center;
} /* 水平垂直居中对齐 */
.flex-row-bw {
display: flex;
justify-content: space-between;
}
.flex-row-ad {
display: flex;
justify-content: space-around;
}
.flex-row-end {
display: flex;
justify-content: flex-end;
}
.flex-wrap {
flex-wrap: wrap;
} /* 换行,默认nowrap(不换行)*/
.flex-col {
display: flex;
flex-direction: column;
}
.flex-col-vcenter {
display: flex;
flex-direction: column;
justify-content: center;
} /* 纵向布局垂直居中 */
.flex-col-rcenter {
display: flex;
flex-direction: column;
align-items: center;
} /* 纵向布局水平居中对齐 */
.flex-col-c {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.flex-col-bw {
display: flex;
flex-direction: column;
justify-content: space-between;
}
.flex1-row {
flex: 1;
width: 1px;
}
.flex1-col {
flex: 1;
height: 1px;
}
.w100 {
width: 100%;
}
.h100 {
height: 100%;
}
5.在 main.js
中引入公共样式文件base.css和global.scss:
// ...
// 引入 自定义 element-ui 主题样式
import '../themes/index.css'
//引入全局样式base.css
import '@/assets/style/base.scss';//全局样式
// 引入全局样式global.scss
import '@/common/style/global.scss'
// ...
五、项目配置
5.1 eslint 配置
ESLint最初是由Nicholas C. Zakas 于2013年6月创建的开源项目。它的目标是提供一个插件化的javascript代码检测工具。
安装依赖:
yarn add eslint-plugin-prettier -D
移除 package.json 中 eslintConfig 配置项,在项目根目录 新建 .eslintrc.js
文件(用来配置eslint 检测规则):
// .eslintrc.js
module.exports = {
root: true, // 标识根目录
env: {
node: true,
browser: true
},
ignorePatterns: ['node_modules/'], // 需要忽略的特定文件和目录
extends: [
/**
* @vue/cli-plugin-eslint 提供了三种模式的规则:
* 必要(默认)规则:plugin:vue/essential
* 强烈推荐规则: plugin:vue/strongly-recommended
* 推荐规则: plugin:vue/recommended
* 三者并不是独立的,而是包含关系:推荐规则 》强烈推荐规则 》必要(默认)规则
* 此外,我们也可以在 rules 对这些规则做自己的设定。
* 更多设置规则:https://eslint.vuejs.org/rules/
*/
// 'plugin:vue/recommended', // 推荐规则
'plugin:vue/strongly-recommended' // 强烈推荐规则
// 'plugin:vue/essential', // 必要(默认)规则
],
plugins: ['prettier'],
parserOptions: {
parser: '@babel/eslint-parser'
},
rules: {
/**
* 0 = off, 1 = warn, 2 = error
*/
'vue/html-self-closing': 0, // 关闭强制自闭合标签检测
'vue/max-attributes-per-line': ['error', {
'singleline': {
'max': 6 // 单行最多6个属性
},
'multiline': {
'max': 1 // 超过6个属性,多行显示,每行1个
}
}],
'vue/singleline-html-element-content-newline': 0, // 关闭强制在单行元素的内容之前和之后使用换行符
'no-tabs': ['error'], // 禁止使用tab缩进
'indent': [2, 2], // 缩进风格,两个空格
'semi': [0], // 关闭语句强制分号结尾
'no-alert': 1, // 禁止使用 alert confirm prompt
'no-dupe-keys': 'error', // 在创建对象字面量时不允许key重复 {a: 1, a: 1}
'no-eval': 'error', // 禁止使用 eval
'no-eq-null': 'error', // 禁止对 null 使用 == 或 != 运算符
'no-func-assign': 'error', // 禁止重复的函数声明
'no-implicit-coercion': 'warn', // 禁止隐式转换
'no-mixed-spaces-and-tabs': [2, false], // 禁止混用 tab 和空格
'no-multiple-empty-lines': [1, { max: 1 }], // 空行最多不能超过1行
'no-redeclare': 2, // 禁止重复声明变量
'no-spaced-func': 2, // 函数调用时 函数名与()之间不能有空格
'no-trailing-spaces': 1, // 禁止行末空格
'no-undef': 'warn', // 不能有未定义的变量
'no-use-before-define': 2, // 未定义前不能使用 ?禁止声明提前
'no-var': 2, // 禁止 var 声明变量,使用 let 和 const
'comma-style': [2, 'last'], // 逗号风格,换行时在行首还是行尾
'consistent-return': 0, // return 后面是否允许省略
'consistent-this': [2, 'me'], // this 别名
'default-case': 2, // switch 语句最后必须有 default
'dot-location': 0, // 对象访问符的位置,换行时在行首还是行尾
'id-length': [2, { min: 3 }], // 变量名长度
'init-declarations': 0, // 声明时必须赋值
'key-spacing': [2, { beforeColon: false, afterColon: true }], // 对象字面量中冒号的前后空格
'lines-around-comment': 0, // 行前/行后注释
'max-depth': [1, 4], // 嵌套块深度
'space-before-function-paren': [1, 'always'], // 函数定义时括号前面要不要有空格
'space-unary-ops': [0, { words: true, nonwords: false }], // 一元运算符的前/后要不要加空格
'use-isnan': 2, // 禁止比较时使用NaN,只能用 isNaN()
'vars-on-top': 2, // var必须放在作用域顶部
'vue/require-v-for-key': 'warn', // vue v-for 必须绑定 key
'no-await-in-loop': 'error', // 禁止在循环中使用 await
'no-console': 'off', // 关闭禁止使用console
'no-template-curly-in-string': 'error', // 禁用类似ES6模板字符串的字面量字符串定义
'block-scoped-var': 'error', // 变量在定义块的外部使用时,规则会报错
'complexity': [
'error', { max: 10 }
],
'one-var': ['error', 'consecutive'], // 连续的变量声明只使用一个let或const关键字
'quote-props': [ // 对象字面值属性名称可以用两种方式定义:使用文字或使用字符串
'error',
'consistent', // 强制执行一致的引用风格需要引用对象字面值属性名称
{
keywords: true, // 关键字作为属性名称时必须加引号
numbers: true // 数字作为属性名称时必须加引号
}],
'quotes': ['error', 'single'], // 字符串字面量强制使用单引号
'no-new': 'off', // 关闭禁止使用new构造方法而不赋值
'curly': ['error', 'all'], // 必须使用 if(){} 中的{}
'eqeqeq': ['error', 'smart'], // 必须使用全等
'no-empty-function': 'warn', // 禁止出现空函数
'no-implied-eval': 'error', // 禁止使用 eval()
'no-lone-blocks': 'error', // 禁用不必要的嵌套块
'no-loop-func': 'error', //
'no-multi-spaces': 'error', // 不能用多余的空格
'no-multi-str': 'error', // 字符串不能用\换行
'no-plusplus': 'off', // 关闭禁止 ++ --
'no-return-assign': 'error', // return 语句中不能有赋值表达式
'no-self-compare': 'error', // 不能比较自身
'no-throw-literal': 'error', // 禁止抛出字面量错误 throw "error"
'no-unmodified-loop-condition': 'error', // 循环中的变量经常在循环中修改。如果不是,那可能是一个错误。
'no-useless-concat': 'error', // 没有必要将两个字符串连接在一起 var foo = "a" + "b";
'no-delete-var': 'error', // 不能对var声明的变量使用delete操作符
'comma-dangle': 'error', // 对象字面量项尾必须有逗号
'comma-spacing': 'error', // 逗号前后的空格
'computed-property-spacing': 'error', // 不允许计算属性括号内的空格
'implicit-arrow-linebreak': 'error', // 在箭头函数体之前不允许换行
'keyword-spacing': 'error', // 关键字前后至少有一个空格
'semi-spacing': 'error', // 分号前后禁用空格
'semi-style': 'error' // 强制分号位于语句的末尾
}
}
了解更多规则配置请移步:
👉 https://eslint.vuejs.org/rules/
👉 List of available rules - ESLint中文文档 (bootcss.com)
! 本文档中 eslint 检测规则配置只做说明举例,如果与公司骨架项目冲突的话,以骨架项目中为规范。 !
此时的 package.json :
{
"name": "hello-world",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"axios": "^0.26.1",
"core-js": "^3.8.3",
"element-ui": "^2.15.6",
"vue": "^2.6.14",
"vue-router": "3.5.3",
"vuex": "3.6.2"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@babel/preset-env": "^7.16.11",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"babel-plugin-component": "^1.1.1",
"eslint": "^7.32.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.0.3",
"sass": "^1.49.9",
"sass-loader": "10.2.1",
"vue-template-compiler": "^2.6.14"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
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 文件保存,实时校验、自动修复错误功能说明
- 使用此功能,必须安装
eslint-js
和eslint-vue
插件。(菜单【工具】【插件安装】) vue-cli
项目,需要安装eslint库,并配置eslint规则.- 若满足上述条件,当编写完代码,保存时,若代码中存在错误,自动修复;
- 使用此功能,必须安装
- 插件设置
2.6.11版本,支持自定义配置:
保存时自动修复
和启用实时校验
;见下图
特别说明: 实时校验功能,默认未开启,需要手动开启此功能
工具 👉设置👉插件配置 勾选 eslint-js 下的“启用实时校验” 和 eslint-vue 下的 “启用实时校验”
编码过程中 HBuider X 会自动按照 eslint 配置规则修复不规范的编码。
在项目根目录新建 .prettierrc.js
文件:
module.exports = {
printWidth: 120, //限制每行字符个数
tabWidth: 4, //指定每个缩进级别的空格数
useTabs: false, //使用制表符而不是空格缩进
semi: false, //在语句末尾打印分号
singleQuote: true, //使用单引号而不是双引号
trailingComma: 'es5', // 在对象或数组最后一个元素后面是否加逗号(在ES5中加尾逗号)
bracketSpacing: true, //在对象文字中的括号之间打印空格
arrowParens: 'always', //始终给箭头函数的参数加括号
htmlWhitespaceSensitivity: 'css', //指定HTML文件的全局空格敏感度
endOfLine: 'auto' //检测换行符类型,如果出现大量换行符报错,可以修改为auto不检测
}
HBuilder X 安装 prettier 插件
下载 prettier 插件 prettier插件下载地址
修改 HBuilder X 配置:
HBuilder X 编辑器格式化时, 使用的缩进方式,是读取的菜单【工具 —> 设置】中的配置。配置方式:点击“工具-插件安装-已安装的插件-prettier-配置”
vue 文件的开头在 HBuilder X 中有一个红色波浪线。解决方法:.eslintrc.js
添加 requireConfigFile.false 的配置:
module.exports = {
/// ...
parserOptions: {
parser: '@babel/eslint-parser',
requireConfigFile: false // 解决 HBuilder X 中 vue 文件开头显示红色波浪线
},
/// ...
}
5.2 element-ui 主题配置
Element 默认提供一套主题,CSS 命名采用 BEM 的风格,方便使用者覆盖样式。
5.2.1 方式一、自定义element-variables.scss文件方式修改主题【推荐】
1.Element 的 theme-chalk 使用 SCSS 编写,可以直接在项目中改变 Element 在项目common
目录创建 element-variables.scss
,写入以下内容:
/* 改变主题色变量 */
$--color-primary: teal;
/* 改变 icon 字体路径变量,必需 */
$--font-path: '~element-ui/lib/theme-chalk/fonts';
@import "~element-ui/packages/theme-chalk/src/index";
2.入口文件中引入:
import Vue from 'vue'
import Element from 'element-ui'
import './element-variables.scss'
Vue.use(Element)
需要注意的是,覆盖字体路径变量是必需的,将其赋值为 Element 中 icon 图标所在的相对路径即可。
5.2.2 方式二、深层次的主题定制
1.安装主题生成工具
yarn add element-themex -D
2.安装白垩主题
yarn add element-theme-chalk -D
3.初始化变量文件
主题生成工具安装成功后,通过 node_modules/.bin/et
访问到命令。执行 -i
初始化变量文件。默认输出到 element-variables.scss
./node_modules/.bin/et -i
当前目录会有一个 element-variables.scss
文件。内部包含了主题所用到的所有变量,它们使用 SCSS 的格式定义。大致结构如下:
$--color-primary: #409EFF !default;
$--color-primary-light-1: mix($--color-white, $--color-primary, 10%) !default; /* 53a8ff */
$--color-primary-light-2: mix($--color-white, $--color-primary, 20%) !default; /* 66b1ff */
$--color-primary-light-3: mix($--color-white, $--color-primary, 30%) !default; /* 79bbff */
$--color-primary-light-4: mix($--color-white, $--color-primary, 40%) !default; /* 8cc5ff */
$--color-primary-light-5: mix($--color-white, $--color-primary, 50%) !default; /* a0cfff */
$--color-primary-light-6: mix($--color-white, $--color-primary, 60%) !default; /* b3d8ff */
$--color-primary-light-7: mix($--color-white, $--color-primary, 70%) !default; /* c6e2ff */
$--color-primary-light-8: mix($--color-white, $--color-primary, 80%) !default; /* d9ecff */
$--color-primary-light-9: mix($--color-white, $--color-primary, 90%) !default; /* ecf5ff */
$--color-success: #67c23a !default;
$--color-warning: #e6a23c !default;
$--color-danger: #f56c6c !default;
$--color-info: #909399 !default;
...
直接编辑 element-variables.scss
文件,例如修改主题色为红色。
$--color-primary: red;
4.编译主题
在 package.json 中 scripts 下添加编译主题指令:
"build:theme": "node_modules/.bin/et -o ./themes",
此时 package.json :
{
"name": "hello-world",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"build:theme": "node_modules/.bin/et -o ./themes",
"lint": "vue-cli-service lint"
},
"dependencies": {
"axios": "^0.26.1",
"core-js": "^3.8.3",
"element-ui": "^2.15.6",
"vue": "^2.6.14",
"vue-router": "3.5.3",
"vuex": "3.6.2"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@babel/preset-env": "^7.16.11",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"babel-plugin-component": "^1.1.1",
"element-theme-chalk": "^2.15.6",
"element-themex": "^1.0.3",
"eslint": "^7.32.0",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.0.3",
"sass": "^1.49.9",
"sass-loader": "10.2.1",
"vue-template-compiler": "^2.6.14"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
执行 yarn build:theme
会在根目录生成 themes 文件夹
5.引入自定义主题
修改main.js 中默认主题引入为
// 引入 element-ui 样式
// import 'element-ui/lib/theme-chalk/index.css'
// 引入 自定义 element-ui 主题样式
import '../themes/index.css'
运行 yarn serve
查看效果。
六、目录结构说明
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.安装依赖:
yarn add echarts@4.9.0
2.在main.js
中全局引入 echarts:
/// ...
// 引入 echarts
import echarts from 'echarts'
Vue.prototype.$echarts = echarts
/// ...
3.在components
中创建echarts/echart-temp.vue
文件,作为 echarts 公用组件:
<template>
<div class="chart-container" ref="container">
<div class="chart-body" ref="chartDiv" :style="style"></div>
</div>
</template>
<script>
// 引入防抖函数
import { debounce } from '@/utils'
export default {
name: 'EchartTemp',
props: {
options: {
type: Object,
default () {
return null
}
},
styles: {
type: Object,
default () {
return {}
}
}
},
data () {
return {
style: {},
chartObj: null,
chartData: {},
chartDom: null,
// eslint-disable-next-line vue/no-reserved-keys
__resizeHanlder: null
}
},
watch: {
styles (val, oldVal) {
this.style = val
},
options () {
this.drawChart()
}
},
mounted () {
// alert('mounted!');
// console.log(11111,this.options)
this.chartDom = this.$refs.chartDiv
this.chartObj = this.$echarts.init(this.chartDom)
this.chartObj.on('click', (params) => {
this.itemClick(params)
})
this.chartObj.on('legendselectchanged', (params) => {
console.log('this.chartObj: ', this.chartObj)
this.legendClick(this.chartObj, params)
})
this.drawChart()
this.__resizeHanlder = debounce(this.refreshChart)
// 添加尺寸改变事件
window.addEventListener('resize', this.__resizeHanlder)
// this.$bus.$on('toggleSideMenu', this.__resizeHanlder)
},
activated () {
// alert('activated!');
// 添加尺寸改变事件
// window.addEventListener('resize', this.__resizeHanlder);
// this.$bus.$on('toggleSideMenu', this.__resizeHanlder);
this.resizeChart()
},
computed: {},
methods: {
// 刷新图表
refresh () {
this.drawChart()
},
clearChart () {
// this.chartDom.innerHTML = ''
this.chartObj.clear()
},
resizeChart () {
let totleHeight = this.$refs['container'].clientHeight
// this.styles = { height: totleHeight + 'px' };
this.style = { height: totleHeight + 'px' }
this.refreshChart()
},
refreshChart () {
// console.log("this.chartObj",this.chartObj.resize);
this.chartObj.resize()
},
drawChart () {
this.clearChart()
if (this.options && Object.keys(this.options).length > 0) {
this.chartObj.setOption(this.options)
}
},
setOption (option) {
this.chartObj.setOption(option)
this.drawChart()
},
itemClick (params) {
this.$emit('itemClick', params)
},
legendClick (chartObj, params) {
this.$emit('legendClick', chartObj, params)
},
setProps (props) {
if (Object.keys(props).length > 0) {
for (let key in props) {
this[key] = props[key]
}
this.$nextTick(() => {
this.resizeChart()
this.drawChart()
})
}
}
},
beforeDestroy () {
// alert('beforeDestroy');
window.removeEventListener('resize', this.refreshChart)
// this.$bus.$off('toggleSideMenu', this.__resizeHanlder)
},
components: {}
}
</script>
<style lang="scss" scoped>
.chart-container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
.chart-body {
flex: 1;
}
}
</style>
src/utils/index.js
:
// 防抖函数
export function debounce (func, delay) {
// 维护一个 timer
let timer = null;
return function () {
clearTimeout(timer);
// eslint-disable-next-line consistent-this
const context = this,
args = arguments;
timer = setTimeout(function () {
func.apply(context, args);
}, delay);
};
}
组件使用:
<template>
<div class="echart-demo">
<echart-temp :options="options"></echart-temp>
</div>
</template>
<script>
import EchartTemp from '@/components/echarts/echart-temp.vue'
export default {
name: 'EchartDemo',
components: {
EchartTemp
},
data () {
return {
options: {}
}
},
mounted () {
this.initData()
},
methods: {
initData () {
let xAxisData = [],
data1 = [],
data2 = []
// eslint-disable-next-line id-length
for (let i = 0; i < 100; i++) {
xAxisData.push('类目' + i)
data1.push((Math.sin(i / 5) * (i / 5 - 10) + i / 6) * 5)
data2.push((Math.cos(i / 5) * (i / 5 - 10) + i / 6) * 5)
}
this.drawChart(xAxisData, data1, data2)
},
drawChart (xAxisData, data1, data2) {
this.options = {
title: {
text: '柱状图动画延迟'
},
legend: {
data: ['bar', 'bar2']
},
toolbox: {
// y: 'bottom',
feature: {
magicType: {
type: ['stack', 'tiled']
},
dataView: {},
saveAsImage: {
pixelRatio: 2
}
}
},
tooltip: {},
xAxis: {
data: xAxisData,
splitLine: {
show: false
}
},
yAxis: {},
series: [
{
name: 'bar',
type: 'bar',
data: data1,
animationDelay: function (idx) {
return idx * 10
}
},
{
name: 'bar2',
type: 'bar',
data: data2,
animationDelay: function (idx) {
return idx * 10 + 100
}
}
],
animationEasing: 'elasticOut',
animationDelayUpdate: function (idx) {
return idx * 5
}
}
}
}
}
</script>
<style lang="scss" scoped>
.echart-demo {
width: 800px;
height: 600px;
border: 1px solid darkblue;
}
</style>
效果:
八、常见问题
8.1 vue-router 不同的历史模式
在创建路由器实例时,history
配置允许我们在不同的历史模式中进行选择。
- Hash 模式#
hash 模式是用 createWebHashHistory()
创建的:
import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({
history: createWebHashHistory(),
routes: [
//...
],
})
它在内部传递的实际 URL 之前使用了一个哈希字符(#
)。由于这部分 URL 从未被发送到服务器,所以它不需要在服务器层面上进行任何特殊处理。不过,它在 SEO 中确实有不好的影响。如果你担心这个问题,可以使用 HTML5 模式。
- HTML5 模式#
用 createWebHistory()
创建 HTML5 模式,推荐使用这个模式:
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
//...
],
})
当使用这种历史模式时,URL 会看起来很 “正常”,例如 https://example.com/user/id
。漂亮!
不过,问题来了。由于我们的应用是一个单页的客户端应用,如果没有适当的服务器配置,用户在浏览器中直接访问 https://example.com/user/id
,就会得到一个 404 错误。这就丑了。
不用担心:要解决这个问题,你需要做的就是在你的服务器上添加一个简单的回退路由。如果 URL 不匹配任何静态资源,它应提供与你的应用程序中的 index.html
相同的页面。漂亮依旧!
8.2 cnpm与nrm
使用 npm安装包时是从国网服务器下载,受网络影响大,会遇到时间长,甚至安装失败的问题。此时可以选择使用国内镜像源进行安装,例如国内的淘宝镜像等来代替npm或者yarn的官方服务器。
常用的是使用官方推荐的 cnpm 命令行工具代替默认的 npm
,实际使用的是淘宝源:
npm install -g cnpm --registry=https://registry.npm.taobao.org
注意:有时使用 cnpm 安装的路径可能回存在问题,在使用react-native开发应用时会出现问题。此时可以使用nrm切换淘宝源。
8.2.1nrm
nrm是一个npm源管理器,可以快速的切换npm源,使用nrm有两种方式
- npm安装nrm
npm install nrm -g
- 使用 nrm
所以使用nrm切换镜像源可以是:
# 查看当前可用的源
nrm ls
# 切换源
nrm use taobao
# 添加源
nrm add '镜像名称' '镜像地址'
# 删除源
nrm del '镜像名称'
8.3 node 版本管理工具 nvm
nvm是一个node的版本管理工具,可以简单操作node版本的切换、安装、查看等等
常用命令:
# 列出本地已安装的node版本
nvm ls
# 安装node命令
nvm install '版本号'
# 卸载node命令
nvm uninstall '版本号'
# 切换 node 版本号
nvm use '版本号'
# 查看当前版本
nvm current
8.4 yarn 环境安装依赖报错解决方法(info fsevents@1.2.7)
yarn config set ignore-engines true
8.5 解决自定义element-ui
主题,刷新页面偶尔出现的字体图标乱码
- vue.config.js 增加
sass
自定义配置
'use strict'
module.exports = {
css: {
loaderOptions: {
sass: {
sassOptions: {
// 生效代码:compressed/nested/expanded/compact
outputStyle: 'expanded'
}
}
}
}
}
- 升级
sass
到1.54.x
版本。