前言
本文讲述了毕业实习和正式工作1年以来,使用Vue开发项目的一些个人经历和想法,仅是个人总结,如有不合理的地方,欢迎吐槽。以下是本文的大概内容。
1.Vue项目搭建
1.1从VueCli2到VueCli3
一开始实习接触Vue的脚手架是VueCli2版本,学习的webpack配置也都Cli2的,后来公司使用的是Cli3,所以有一个学习和适应的过程。
VueCli2和VueCli3的差别大概体现在:
- 创建项目
3.0:vue create。
2.0:vue init webpack
- 启动项目
3.0启动npm run serve
2.0启动npm run dev
- 项目配置途径
2.0 config、build文件夹中进行项目的webpack、多环境和打包等配置
3.0 项目结构比2.0要简洁,缺少了build和confilg文件,可自行创建与package.json同级的 vue.config.js 文件,进行配置。
主要的常用配置整理如下:
// vue.config.js 基本配置方法
module.exports = {
// 项目部署的基础路径
// 我们默认假设你的应用将会部署在域名的根部,
// 比如 https://www.my-app.com/
// 如果你的应用时部署在一个子路径下,那么你需要在这里
// 指定子路径。比如,如果你的应用部署在
// https://www.foobar.com/my-app/
// 那么将这个值改为 `/my-app/`
// 基本路径 baseURL已经过时
publicPath: './',
// 打包项目时构建的文件目录,用法与webpack本身的output.path一致
outputDir: 'dist',
// 静态资源目录 (js, css, img, fonts)
assetsDir: 'assets',
// eslint-loader 是否在保存的时候检查,编译不规范时,设为true在命令行中警告,若设为error则不仅警告,并且编译失败
lintOnSave: true,
// 调整内部的 webpack 配置。查阅 https://github.com/vuejs/vue-docs-zh-cn/blob/master/vue-cli/webpack.md
chainWebpack: () => {},
configureWebpack: () => {},
// vue-loader 配置项 https://vue-loader.vuejs.org/en/options.html
vueLoader: {},
// 生产环境是否生成 sourceMap 文件,默认true,若不需要生产环境的sourceMap,可以设置为false,加速生产环境的构建
productionSourceMap: true,
// css相关配置
css: {
// 是否使用css分离插件 ExtractTextPlugin,采用独立样式文件载入,不采用<style>方式内联至html文件中
extract: true,
// 是否在构建样式地图,false将提高构建速度
sourceMap: false,
// css预设器配置项
loaderOptions: {},
// 启用 CSS modules for all css / pre-processor files.
// 这个选项不会影响 `*.vue` 文件
modules: false
},
// 在生产环境下为 Babel 和 TypeScript 使用 `thread-loader`
// 在多核机器下会默认开启。
parallel: require('os').cpus().length > 1,
// 是否启用dll See https://github.com/vuejs/vue-cli/blob/dev/docs/cli-service.md#dll-mode
dll: false,
// PWA 插件相关配置 see https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-pwa
pwa: {},
// webpack-dev-server 相关配置
devServer: {
open: process.platform === 'darwin',
host: '0.0.0.0',//如果是真机测试,就使用这个IP
port: 1234,
https: false,
hotOnly: false,
proxy: null, // 设置代理
// proxy: {
// '/api': {
// target: '<url>',
// ws: true,
// changOrigin: true
// }
// },
before: app => {}
},
// 第三方插件配置
pluginOptions: {
// ...
}
}
1.2 Axios二次封装
axios二次封装的目的主要是三个方面:
1.2.1 接口请求拦截处理
1.2.1.1 可配置项
在进行接口请求拦截进行配置处理的时候,针对以下参数,可以灵活配置。
参数 | 意义 | 例子 |
---|---|---|
url | 用于请求的服务器 URL | url: ‘/user’ |
method | 创建请求时使用的方法 | method: ‘get’ |
baseURL | 自动加在 url 前面,除非 url 是一个绝对 URL,通过设置一个 baseURL 便于为 axios 实例的方法传递相对 URL |
baseURL: ‘some-domain.com/api/‘ |
transformRequest | 允许在向服务器发送前,修改请求数据 // 只能用在 ‘PUT’, ‘POST’ 和 ‘PATCH’ 这几个请求方法 // 后面数组中的函数必须返回一个字符串,或 ArrayBuffer,或 Stream |
transformRequest: [function (data) { // 对 data 进行任意转换处理 |
return data; }], |
||
headers | 即将被发送的自定义请求头 | headers: {‘X-Requested-With’: ‘XMLHttpRequest’}, |
params | 即将与请求一起发送的 URL 参数 | params: { ID: 12345 }, |
paramsSerializer | 负责 params 序列化的函数(e.g. www.npmjs.com/package/qs, api.jquery.com/jquery.para…) |
paramsSerializer: function(params) { return Qs.stringify(params, {arrayFormat: ‘brackets’}) } |
data | data 是作为请求主体被发送的数据只适用于这些请求方法 ‘PUT’, ‘POST’, 和 ‘PATCH’ 在没有设置 transformRequest 时,必须是以下类型之一:– string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams – 浏览器专属:FormData, File, Blob – Node 专属: Stream |
data: { firstName: ‘Fred’ } |
timeout | 指定请求超时的毫秒数(0 表示无超时时间) | timeout: 1000 |
adapter | 允许自定义处理请求,以使测试更轻松,返回一个 promise 并应用一个有效的响应 | adapter: function (config) { /* … */ }, |
auth | 表示应该使用 HTTP 基础验证,并提供凭据,这将设置一个 Authorization 头,覆写掉现有的任意使用 headers 设置的自定义 Authorization 头 |
auth: { username: ‘janedoe’, password: ‘s00pers3cret’ }, |
responseType | 服务器响应的数据类型,可以是 ‘arraybuffer’, ‘blob’, ‘document’, ‘json’, ‘text’, ‘stream’ | responseType: ‘json’, // 默认的 |
xsrfCookieName | 用作 xsrf token 的值的cookie的名称 | xsrfCookieName: ‘XSRF-TOKEN’ |
xsrfHeaderName | 承载 xsrf token 的值的 HTTP 头的名称 | xsrfHeaderName: ‘X-XSRF-TOKEN’, // 默认的 |
onUploadProgress | 允许为上传处理进度事件 | onUploadProgress: function (progressEvent) { // 对原生进度事件的处理 }, |
onDownloadProgress | 允许为下载处理进度事件 | onDownloadProgress: function (progressEvent) { // 对原生进度事件的处理 }, |
maxContentLength | 定义允许的响应内容的最大尺寸 | maxContentLength: 2000 |
validateStatus | 定义对于给定的HTTP 响应状态码是 resolve 或 reject promise 。如果 validateStatus 返回 true (或者设置为 null 或 undefined ),promise 将被 resolve; 否则,promise 将被 rejecte |
validateStatus: function (status) { return status >= 200 && status < 300; // 默认的 }, |
maxRedirects | 定义在 node.js 中 follow 的最大重定向数目 | maxRedirects: 5, // 默认的 |
httpAgent | httpAgent 和 httpsAgent 分别在 node.js 中用于定义在执行 http 和 https 时使用的自定义代理。 |
httpAgent: new http.Agent({ keepAlive: true }), httpsAgent: new https.Agent({ keepAlive: true }), |
proxy | 定义代理服务器的主机名称和端口 | proxy: { host: ‘127.0.0.1’, port: 9000, auth: : { username: ‘mikeymike’, password: ‘rapunz3l’ } }, |
cancelToken | 指定用于取消请求的 cancel token | cancelToken: new CancelToken(function (cancel) { }) |
1.2.1.2 拦截重复请求
在网速较慢的情况下,容易出现用户多次点击而重复请求使得页面抖动的问题,用户体验不好,因此进行拦截重复请求的处理。思路是:
创建请求队列 —->
—–拦截处理——
标识即将发送的请求—->
判断即将发送的请求与队列中的请求是否相同—->
若相同则执行当前请求的取消方法,并从请求队列中删除—->
创建即将请求的取消方法,放入队列中
拦截处理
request.interceptors.request.use(
config => {
// 拦截重复请求(即当前正在进行的相同请求)
const requestData = getRequestIdentify(config, true); // 标识请求
removePending(requestData, true);// 取消重复请求
config.cancelToken = new CancelToken((c) => { // 创建当前请求的取消方法
pending[requestData] = c;
});
return config;
}, error => {
return Promise.reject(error)
})
标识请求
const getRequestIdentify = (config, isReuest = false) => {
let url = config.url;
if (isReuest) {
url = config.baseURL + config.url.substring(1, config.url.length);
}
return config.method === 'get' ? encodeURIComponent(url + JSON.stringify(config.params)) : encodeURIComponent(config.url + JSON.stringify(config.data));
};
取消重复请求
const pending = {};
const CancelToken = axios.CancelToken;
const removePending = (key, isRequest = false) => {
if (pending[key] && isRequest) {
pending[key]('取消重复请求');
}
delete pending[key];
};
1.2.2 接口响应拦截处理
接口响应的拦截主要是对接口返回的数据进行提取、封装使用,以及对请求异常进行统一配置处理。
request.interceptors.response.use(
response => {
const data = response.data || {};
return data;
},
error => {
const code = error.response.status;
if (code) {
let msg = '';
switch (code) {
case 400:
msg = '请求错误';
break;
case 401:
msg = '未授权,请登录';
break;
case 403:
msg = '拒绝访问';
break;
case 404:
msg = `请求${error.response.config.url}出现404错误`;
break;
case 408:
msg = '请求超时';
break;
case 500:
msg = '服务器内部错误';
break;
case 501:
msg = '服务未实现';
break;
case 502:
msg = '网关错误';
break;
case 503:
msg = '服务不可用';
break;
case 504:
msg = '网关超时';
break;
case 505:
msg = 'HTTP版本不受支持';
break;
}
Message.error(msg);
}
return Promise.reject(error);
}
)
1.2.3 API方法封装
单独封装接口请求方法, GET方法的参数为params,POST方法的参数为data。
// api.js
import request from '@/utils/request';
export function APIPostMethod(data) { // 自定义接口方法
return request({
url: '/url1',
method: 'post',
data
});
}
export function APIGetMethod(params) { // 自定义接口方法
return request({
url: '/url2',
method: 'get',
params
});
}
在业务中调用API方法
import { APIGetMethod, APIPostMethod } from '@/utils/request';
const params = {}
APIGetMethod(params).then(res => {
//...
//对数据处理
})
1.3跨域处理
简单来说,为了防止XSS和CSFR攻击,浏览器的同源策略限制带来了前后端分离开发时的跨域问题。即当请求与响应不在同一个协议+域名+端口下,就不会被浏览器允许。但是同源策略只是浏览器的一种策略,不是HTTP协议的一部分,因此服务端调用HTTP接口只是使用HTTP协议,而不会通过浏览器,更不会执行JS脚本,所以不会触发同源策略机制,不存在跨域问题。针对这个特点,可以从前端配置代理服务器入手。
尝试过两种解决跨域的方法:
1.Node.js中间件代理
在vue-cli中,里用node+webpack+webpack-dev-server代理接口跨域。
//vue.config.js
const config = {
// ...
devServer: {
hot: true,
open: true,
host: '127.0.0.1',
// host: '0.0.0.0',//如果是真机测试,就使用这个IP
port: 8899,
https: false,
hotOnly: false,
proxy: {
'/': {
target: 'http://xxx.xxx.xx.xx:xxxx/',
logLevel: 'debug',
ws: false,
changOrigin: true
}
}
}
}
module.exports = config;
2.Nginx反向代理
通过nginx配置一个代理服务器,域名与本地域名A一致,但端口不同,反向代理访问对方B域名下的接口。
#proxy服务器
server {
listen 8088;
server_name _;
client_max_body_size 50m;
location / {
root html/dist;
try_files $uri $uri/ /index.html;
index /index.html;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
location ^~/api {
proxy_pass http://xxx.xxx.xx.xx:xxxx/api;
proxy_redirect default;
}
}
前端在启动项目的时候,需要把项目proxy代理对应的8088端口上(感觉这种方式有点多余,但是同事用这种方式,咱不敢说,所以个人这边是用的第一种)。
Nginx的方式更适合用于线上部署解决跨域使用,开发环境下,使用vue-cli中的devserve既方便又快捷。
2.Vue技巧
2.1 懒加载
懒加载也叫延迟加载,使组件进行异步加载。目的是延迟非必要资源的加载,减少页面加载的时间,从而优化页面的性能。
2.1.1 路由懒加载
export default new Router({
routes:[
{
path: '/test',
name: 'test',
//懒加载
component: resolve => require(['../page/test.vue'], resolve),
},
]
})
在路由懒加载下,代码根据路由被拆分为不同的代码块,在切换进入相应的路由时,才对对应的代码块进行加载,加载更加高效了。
2.1.2 组件懒加载
components: {
UpdateModal: resolve => { require(['./UpdateNormalTaskModal'], resolve); }
},
在路由懒加载的前提下,进行组件懒加载的对比实验。
未使用组件懒加载:
整个页面为一个js,大小为718KB,加载耗时166ms。
在使用组件懒加载的时候:
整个页面被拆分为三个js(53.js),其中懒加载的两个组件,各自一个js(78.js、91.js),可看出来,53.js的文件大小变小,加载速度变快,将一个js拆分为多个进行并行加载,可以提高加载的效率,从而提升性能。
2.2按需引入
按需加载一般用于在使用第三方库的时候,为了避免第三方库过大,而造成的对首屏加载带来的过大的压力。
以VantUI按需加载为例
- 安装babel-plugin-import
npm i babel-plugin-import -D
- 在babel.config.js中配置plugins(插件)
module.exports = {
plugins: [
['import', {
libraryName: 'vant',
libraryDirectory: 'es',
style: true
}, 'vant']
],
presets: [
'@vue/app'
]
};
- 在要用到三方组件的vue文件中引入使用
import { Swipe, SwipeItem, ImagePreview } from 'vant';
export default {
components: {
vanSwipe: Swipe,
vanSwipeItem: SwipeItem,
}
}
2.3 JS中使用本地图片资源
在vue的v-bind语法中使用到本地资源时,路径是相对本地文件夹的相对路径,打包时无法解析。
2.3.1.静态资源读取
将图片资源放在static静态资源文件夹下,在使用src时,直接访问根目录下的资源
比如图片放在public目录下,路径直接写为'/img/case/gkjg/7.jpg'
2.3.2导入资源
在data中采用require的方式,将图片资源导入,然后使用imgUrl变量。
data(){
return {
imgUrl:require("../assets/test.png")
}
}
2.4 keep-alive
<keep-alive></keep-alive>
包含的组件会被缓存下来,不进行再次渲染DOM,从而节省性能,切换内容时会出发activated和deactivated两个生命周期钩子函数,被缓存的组件会保留当前组件的状态。
2.4.1路由页面缓存
利用router的meta字段
//...router.js
export default new Router({
routes: [
{
path: '/',
name: 'Hello',
component: Hello,
meta: {
keepAlive: false // 不需要缓存
}
},
{
path: '/page1',
name: 'Page1',
component: Page1,
meta: {
keepAlive: true // 需要被缓存
}
}
]
})
<keep-alive>
<router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"></router-view>
2.4.2组件缓存
<keep-alive include="test-keep-alive">
<!-- 将缓存name为test-keep-alive的组件 -->
<component></component>
</keep-alive>
<keep-alive include="a,b">
<!-- 将缓存name为a或者b的组件,结合动态组件使用 -->
<component :is="view"></component>
</keep-alive>
<!-- 使用正则表达式,需使用v-bind -->
<keep-alive :include="/a|b/">
<component :is="view"></component>
</keep-alive>
<keep-alive exclude="test-keep-alive">
<!-- 将不缓存name为test-keep-alive的组件 -->
<component></component>
</keep-alive>
2.4.3 结合berforeRouteEnter
结合路由beforeRouteLeave(to, from, next)
的钩子,设置to.meta.keepAlive
来指定目的页面是否进行keepAlive。
export default {
data() {
return {};
},
methods: {},
beforeRouteLeave(to, from, next) {
// 设置下一个路由的 meta
to.meta.keepAlive = true; // 当前页面跳转到下一个页面时,让目的页面缓存,不刷新
next();
}
};
恰当使用keep-alive,结合activated和deactivated两个钩子函数,将不需要更新的内容缓存下来,将需要更新的内容放在两个钩子中去处理,这样可以减少不必要的http请求和DOM重复渲染,提升了不少性能
2.5自定义v-model指令
2.5.1 寻常的v-model
- 应用场景
有的状态在子组件和父组件中都会被修改,同时,子父组件都会在这个状态变化的同时,去写一些业务逻辑,如果用props和事件监听的方式,写下来组件的代码里会很长很丑,所以我个人特别喜欢用v-model来实现子父组件的双向绑定!非常方便!
- v-model的作用
v-model常用于input这样的表单组件,用于实现表单值的双向绑定,意思就是从外界可以传入一个值给表单,当你改变表单的值时,外界的这个值同时也会被改变,因此他们的影响时双向的。
- v-model本质
v-model的本质实际上是一个语法糖,比如在input元素中
<!--v-model是语法糖-->
<Input v-model="username">
<!--默认等效于下⾯面这⾏行行-->
<Input :value="username" @input="username=$event">
2.5.2自定义v-model
既然v-model是一个语法糖,那么实际上的props
和$emit
是不能少的,但是v-model指向的propd是哪一个,对应的$emit
事件是哪一个,是需要我们进行声明的。
以一个demo为例:
// 父组件中对demo组件的调用
<demo v-model="currentData" :other="otherProps"></demo>
父组件里绑定一个值
// demo.vue
<template>
<div>
<button @click="cEvent">点击</button>
{{ current }}
</div>
</template>
<script>
export default {
props: {
current: {
type: String
},
other: {
type: String
}
},
watch:{
current(newVal,oldVal) {
...doSomething...
}
},
model: {
prop: 'current',
event: 'event'
},
methods: {
cEvent() {
this.$emit('event', '改变啦');
}
}
};
</script>
子组件中声明修改model的默认行为,指定v-model
的传入是props
中的curren
t, 当要改变current
的值时,$emit
一个event事件通知父组件修改current
的值,实现了v-model
的双向绑定。
其实上述行为等同于以下使用常规props
和 $emit
的写法
// 父组件中对demo组件的调用
<template>
<div>
<demo :current"currentData" :other="otherProps" @event="changeCurrent"></demo>
</div>
</template>
<script>
export default {
data() {
return {
currentData: '初始值'
}
},
methods: {
changeCurrent(val) {
this.changeCurrent = val
...doSomething...
}
}
};
</script>
// demo.vue
<template>
<div>
<button @click="cEvent">点击</button>
{{ current }}
</div>
</template>
<script>
export default {
props: {
current: {
type: String
},
other: {
type: String
}
},
watch:{
current(newVal,oldVal) {
...doSomething...
}
}
methods: {
cEvent() {
this.$emit('event', '改变啦');
}
}
};
</script>
2.6 动态组件+组件的自动化注册
场景:
2.6.1自动化注册
// 此段代码直接写在script标签里
var templatePage = {};
var nameList = [];
function initPage() {
/* require.context()的参数中,不能含有变量 因此不能动态导入,只能一次性导入之后,动态引用 */
const requireComponent = require.context(
// 其组件目录的相对路径
'../views/template',
// 是否查询其子目录
true,
// 匹配基础组件文件名的正则表达式
/.vue$/
);
requireComponent.keys().forEach(fileName => {
var names = fileName
.split('/')[1] + '-' + fileName
.split('/')[2].split('.')[0];
const componentConfig = requireComponent(fileName);
templatePage[names] = componentConfig.default || componentConfig;
nameList.push(names);
});
}
initPage();
components: {
...templatePage
},
2.6.2 动态组件
通过is匹配组件,因此这些组件必须要有name。
// template中
<component
:is="comp"
v-for="comp in fileList"
:id="comp"
:key="comp"
class="content__item">
</component>
computed: {
fileList() {
return nameList
.filter(v => v.split('-')[0] === this.$route.params.templateId);
}
},
2.7封装自定义指令
官方文档
按钮级粒度的权限控制,使用自定义指令v-permission来实现。
该指令通过传递进来的权限数组和当前用户角色数组过滤
如果用户拥有要求的权限,可以看到,否则删除指令挂钩的dom元素
// permission.js
export default {
// el - 挂载dom
// binding- v-per="[]" {value: []}
inserted(el,binding) {
// 获得传入的值
const {value: permissionRoles} = binding;
//...业务逻辑
// 获取用户角色
const roles = stroe.getters.roles;
// 合法性判断
if(permissionRoles && permissionRoles instanceof Array
permissionRoles.length > 0) {
const hasPermission = roles.some(role => {
return permissionRoles.includes(role)
});
if(!hasPermission) {
el.parentNode && el.parentNode.removeChild(el);
}
} else {
throw new Error('需要指定数组类型的权限');
}
}
}
//main.js
import permission from '@../permission; //注册指令 Vue.directive('permission',permission);
2.8 装饰器
Decorator 的语法还没有通过提案,所以项目中很少用
blog.csdn.net/weixin_3423…
juejin.cn/post/685651…
2.9 上传文件的进度条
利用axios的onUploadProgress和onDownloadProgress可分别获得上传和下载的进度,配置方法和配置contentType之类的config是一样的。
onUploadProgress: function (progressEvent) {
// Do whatever you want with the native progress event
}
// `onDownloadProgress` allows handling of progress events for downloads
// browser only
onDownloadProgress: function (progressEvent) {
// Do whatever you want with the native progress event
}
3.Element-UI踩坑
公司后台管理系统UI库选的是Element,期间踩过挺多坑,但是忙于业务疏于整理,记不清了,之后这一块会慢慢积累,然后记录上去。
3.1Dialog
3.1.1生命周期(Dialog作为子组件的场景下)
子父组件的在生命周期种加载渲染的顺序: 父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
因此在el-dialog在父组件created之后,紧接着也进入了created、mounted周期,无论dialog是否被打开,出现在界面里。
为了能在打开时执行获取数据的方法,绕了很多弯弯。
1.v-if控制渲染法
在父组件里,对el-dialog进行v-if控制,每次显示都会重新渲染。然后就可以在created生命周期里执行你想执行的方法。
2.watch监听法
在el-dialog组件里,监听控制显示隐藏的变量,当变量为true时,代表dialog显示,执行你执行的方法。
3.open事件回调法
别提了,之前一直没发现el-dialog有open这个回调函数! 才会有了上面两种方法,使用oepn回调,在回调中执行你的方法就完事了!
3.1.2 可拖拽
将element-ui的dialog组件修改为可以拖拽的,用户可以根据实际需求拖拽决定dialog的位置。
实际实现参考了 大佬网友 的代码,根据实际需求,取消了边界判断
具体实现如下:
- 创建directives.js文件
import Vue from 'vue';
// v-dialogDrag: 弹窗拖拽
Vue.directive('dialogDrag', {
bind(el, binding, vnode, oldVnode) {
// 获取拖拽内容头部
const dialogHeaderEl = el.querySelector('.el-dialog__header');
// 获取拖拽内容整体 这个rrc-dialog是我自己封装的组件 如果使用element的组件应写成.el-dialog
const dragDom = el.querySelector('.el-dialog');
dialogHeaderEl.style.cursor = 'move';
// 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null);
// 鼠标按下事件
dialogHeaderEl.onmousedown = (e) => {
e.stopPropagation();
// 鼠标按下,计算当前元素距离可视区的距离 (鼠标点击位置距离可视窗口的距离)
const disX = e.clientX - dialogHeaderEl.offsetLeft;
const disY = e.clientY - dialogHeaderEl.offsetTop;
// 获取到的值带px 正则匹配替换
let styL, styT;
// 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
if (sty.left.includes('%')) {
styL = +document.body.clientWidth * (+sty.left.replace(/\%/g, '') / 100);
styT = +document.body.clientHeight * (+sty.top.replace(/\%/g, '') / 100);
} else {
styL = +sty.left.replace(/\px/g, '');
styT = +sty.top.replace(/\px/g, '');
}
// 鼠标拖拽事件
document.onmousemove = function(e) {
e.stopPropagation();
// 通过事件委托,计算移动的距离 (开始拖拽至结束拖拽的距离)
const l = e.clientX - disX;
const t = e.clientY - disY;
let finallyL = l + styL;
let finallyT = t + styT;
// 边界值判定 注意clientWidth scrollWidth区别 要减去之前的top left值
// dragDom.offsetParent表示弹窗阴影部分
if (finallyL < 0) {
// finallyL = 0;
} else if (finallyL > dragDom.offsetParent.clientWidth - dragDom.clientWidth - dragDom.offsetParent.offsetLeft) {
finallyL = dragDom.offsetParent.clientWidth - dragDom.clientWidth - dragDom.offsetParent.offsetLeft;
}
if (finallyT < 0) {
// finallyT = 0;
} else if (finallyT > dragDom.offsetParent.clientHeight - dragDom.clientHeight - dragDom.offsetParent.offsetTop) {
finallyT = dragDom.offsetParent.clientHeight - dragDom.clientHeight - dragDom.offsetParent.offsetTop;
}
// 移动当前元素
dragDom.style.left = `${finallyL}px`;
dragDom.style.top = `${finallyT}px`;
// 将此时的位置传出去
// binding.value({x:e.pageX,y:e.pageY})
};
document.onmouseup = function(e) {
e.stopPropagation();
document.onmousemove = null;
document.onmouseup = null;
};
};
}
});
- 在main.js中引入
import './utils/directives';
- 在需要拖拽的dialog标签中使用v-dialogDrag指令,即可实现拖拽
<el-dialog v-dialogDrag custom-class="enter-dialog" title="""></el-dialog>
3.2表格reserve-selection跨页选择
el-table中的reserve-selection属性,可以实现分页切换后,依然保留上一页勾选的数据(进行跨页选择)
注:
1.设置row-key,这个row-key一定要是每条数据唯一的标识。
2.当涉及到表格内容的切换,不希望被保存勾选,但是没有进行重新渲染时,一定要记住清除勾选,避免发生误操作。
3.3自定义样式
3.3.1 样式穿透
在vue中使用element的组件时,通常使用在组件文件中,每个组件为了防止命名带来的样式污染,通常会使用scoped。在这样的情况下,如果要对elment的组件进行自定义,可以采用三种办法:
<style scoped>
/* 外层>>>穿透 */
.my-dialog >>> .el-dialog {
background: #ff0;
}
/* /deep/穿透 */
/deep/ .el-dialog{
background: #ff0;
}
</style>
/* 单独一个不使用scoped的style用来设置第三方样式的修改*/
/* 全局css配合外层样式限制 */
<style>
.my-dialog .el-dialog{
background: #ff0;
}
</style>
通过调试可以看出,都达到了只在组件内部修改elment组件样式的效果,不会影响到全局的element组件样式:
- 在scoped下,外层>>>穿透,在外层加上了[data-v-***]唯一标识符
- /deep/穿透,直接在el组件前加上了标识符
- 单独设置一个全局的标签,并在外层套一个样式进行局部限制的方法,没有什么特别的,但是也实现了预期的效果。
- 三个的优先级: >>>穿透 > 全局配合外层样式 > /deep/
![image.png](https://cdn.nlark.com/yuque/0/2020/png/200737/1586402040845-b2dfbbc8-9708-4c75-9f93-9f09e1544242.png#align=left&display=inline&height=319&name=image.png&originHeight=319&originWidth=383&size=43503&status=done&style=none&width=383)
注:
- /deep/是chrome自己的标准,在其他浏览器上可能会无效
- <<< 穿透在有些预处理器上无法解析
> 所以我目前用的还是第三种(单独设置style标签),虽然代码略丑,但是还是比较稳的方法
3.3.2特殊的下拉框样式
坑:
在自定义包含下拉框的组件样式时,比如下拉选择el-select,下拉框的部分是脱离组件文件的,直接与最外部的App.vue平级,这意味着,任何的外层样式都包裹不住这个下拉框。因此不存在穿透了,无论怎么穿透都是穿不出来的。
官方文档:
在propper-class下进行样式的修改,这时候只能使用不带scoped的style实现,为了各个页面的下拉框不影响,propper-class的类名一定要具有唯一标识性。
拥有propper-class属性的组件大概有:
3.4 Image
3.4.1 加载时默认的白色背景
加载时,图片会有一个默认的白色背景,加载的一瞬间很难捕捉是哪个css在作妖,所以只好去源码里看。// node_modules/element-ui/packages/image/src/main.vue
看到了是el-image__placeholder这个class在作妖。这时在自己的组件里就可以重写样式,改成自己想要的颜色啦!!
今天的文章Vue项目经验总结(持续更新中…)分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/21378.html