scoped
只作用于本页面, 甚至不作用于页面引入的组件, 所以想让页面既不影响其他页面, 又能改变组件的样式, 可以使用 >>>
深度作用选择器;
iView 框架里的组件的样式无法在 scoped
里改, 但是用这个就可以, 当然, 自己写的其他组件也一样;
注意, 预处理器要用可以用 /deep/
代替 >>>
使用 $nextTick()
方法,这个方法好像是让其内部的函数在 DOM 更新后再调用(具体没查);
有时想刷新一个页面, 常常用这个, 比如先把动态组件置空,然后在此方法中再把此组件还原回去;
使用 v-if
刷新组件和页面也是一样;
computed
中使用 ajax
请求数据watch
要监控对象属性的变化,需要使用 deep: true
, 详见 官方教程
watch
监控对象时, val
和 oldVal
一模一样,官网上关于这点的解释好像是在 迁移
那一部分;
变通方法: 可以直接 watch 对象的某个属性
watch
第一次绑定时是不会执行的, 加上这个 immediate: true
就可以了. 用法: 比如一个 Modal
使用了 v-if
绑定了父组件某个属性, 那么 ``immediate可以让
Modal一被创建就能
watch` 到外部传递给它的相关属性.
标签包裹组件后,只有第一次加载组件才会触发 mounted
生命周期,后面再切换就是 activated
和 deactivated
vue2父子组件间相互通信
Vue 组件通信之 Bus
Vue使用EventBus传递数据的坑
使用 eventBus
机制传递数据时,要注意监听事件的时机,不然有可能出现----跳转前的页面发送事件带参数过去时,对方还没来得及绑定监听事件,
举例: 我在跳转后的页面的 mounted
里加上了发送 "已 mounted完成"
的 eventBus
事件, 当跳转前的页面接收到此事件才会传参数过去,因为两个页面被
标签包裹,所以也不用担心跳转后发不了事件
// 这个我没弄成功,但是思路应该是这样,自己去官网搜 $options
Object.assign(this, this.$options.data.bind(this)())
场景: 做任务计划模块,计划有提醒模块,到时间后提醒
实现:
// 这里是发送请求完成的回调, 参数是外面定义的
if (response.data.code === '1') { // 创建任务成功
if (this.taskObj.remindTimeValue === -1) { // 不提醒
this.$store.commit('setTaskChangeObj', { // 设置 taskChangeObj (Vuex)
id: JSON.parse(response.data.data).id,
type: 'noNotice'
});
} else { // 需要提醒
let time = this.taskObj.taskEndTime.getTime() - this.taskObj.remindTimeValue * 60000 - Date.now(); // 计算任务截止时间 - 任务提前多久提醒(准时, 提前多少分钟, 提前多少天) - 当前时间
if (time > 0) { // 如果计算得到的值大于 0
this.$store.commit('setTaskChangeObj', {
id: JSON.parse(response.data.data).id,
type: type === '新建' ? 'newTask' : type === '编辑' ? 'editTask' : '',
taskName: this.taskObj.taskName,
taskEndTime: this.taskObj.taskEndTime,
taskDetails: this.taskObj.taskDetails,
time: time
}); // 存入相应的 taskChangeObj
} else if (time === 0) { // 如果刚好需要提醒
this.$store.dispatch('taskNotice', {
tip: this.taskObj.taskName,
fromNick: this.taskEndTime.format('yyyy-MM-dd hh:mm'), // 这里的 format 是重写了 Date.prototype 上的方法
text: this.taskObj.taskDetails
}); // 那么直接调用通知方法(这里是之前其他同事用插件做的, 我直接照着用的)
}
}
// 其他代码
} else {
this.$Message.error(`${type}任务失败!`);
}
// 除此之外, 还有"标记任务为已完成/未完成"可以改变任务状态
// 1. 定义, 用 Map 格式存储任务提醒, 格式为 任务id(字符串): 相应的 setTimeout 返回的 id
taskNoticeTimeoutMap: new Map(),
// 2. 定义, 进入最外层页面就要请求任务详情
getTaskNotice() {
util.ajax({
url: '请求URL',
method: 'post',
data: {
// 参数
}
}).then(response => {
if (response.data.code === '1') { // 请求成功
if (Array.isArray(response.data.data)) { // 如果返回数组
response.data.data.map(item => { // 遍历数组
if (item.remindTimeLatest < Date.now()) { // 如果提醒时间已过
// 相关处理
} else if (item.remindTimeLatest > Date.now()) { // 如果提醒时间还没到
let timeout = setTimeout(() => { // 设置 setTimeout
this.taskNoticeTimeoutMap.delete(item.id + ''); // 到时间后从 map 中去掉此任务
this.$store.dispatch('taskNotice', { // 触发提醒
tip: item.taskName,
fromNick: new Date(item.taskEndTime).format('yyyy-MM-dd hh:mm:ss '),
text: item.taskDetails
});
}, item.remindTimeLatest - Date.now()); // 时间设置为相差时间
this.taskNoticeTimeoutMap.set(item.id + '', timeout); // 加入此任务到 map
} else { // 时间刚好到了, 那就直接提醒
this.$store.dispatch('taskNotice', {
tip: item.taskName,
fromNick: new Date(item.taskEndTime).format('yyyy-MM-dd hh:mm:ss '),
text: item.taskDetails
});
}
});
}
} else {
this.$Message.error(response.data.data || '任务提醒功能故障');
}
});
},
// 这个是调用上面的方法的, 此方法的作用主要是为了保证过 0 点了重新请求提醒列表
// 因为任务创建时可以设置重复(最低以天为单位), 然后请求是按天算的, 后天计算得到当天有哪些任务需要提醒, 传递给前端
updateTaskNotice() {
this.getTaskNotice(); // 先调用上面的方法请求和加入相关任务到提醒map
let tomorrowTimeStamp = new Date(new Date(Date.now() + 24 * 60 * 60 * 1000).toLocaleDateString()).getTime(); // 存入明天时间戳
if (Date.now() < tomorrowTimeStamp) { // 如果当前时间戳小于明天时间戳
setTimeout(() => { // 设置 setTimeout
setInterval(() => { // 每天 0 点
for (let i of this.taskNoticeTimeoutMap) { // 遍历 map clearTimeout
this.taskNoticeTimeoutMap.delete(i[0])
}
this.getTaskNotice(); // 重新获取任务提醒
}, 24 * 60 * 60 * 1000)
}, tomorrowTimeStamp - Date.now());
} else { // 否则从现在开始就执行上面的相关操作
setTimeout(() => {
setInterval(() => {
for (let i of this.taskNoticeTimeoutMap) {
this.taskNoticeTimeoutMap.delete(i[0])
}
this.getTaskNotice();
}, 24 * 60 * 60 * 1000)
}, 24 * 60 * 60 * 1000);
}
},
// 3. computed 里取到 Vuex 的 taskChangeObj
// 4. 然后在 watch 中监控 taskChangeObj
taskChangeObj(obj) {
if (obj) {
switch (obj.type) { // 根据不同的任务类型处理相关的提醒
case 'newTask': // 新建
case 'editTask': // 编辑
case 'sign': // 标为未完成
if (obj.id) {
// 如果当前 map 中已存在此任务, 那么 clearTimeout
if (this.taskNoticeTimeoutMap.has(obj.id + '')) {
clearTimeout(this.taskNoticeTimeoutMap.get(obj.id + ''));
}
// setTimeout 到时间提醒 + 从 map 删除
let timeout = setTimeout(() => {
this.taskNoticeTimeoutMap.delete(obj.id + '');
this.$store.dispatch('taskNotice', {
tip: obj.taskName,
fromNick: obj.taskEndTime.format('yyyy-MM-dd hh:mm:ss '),
text: obj.taskDetails
});
}, obj.time);
// 加入 clearTimeout 到 map
this.taskNoticeTimeoutMap.set(obj.id + '', timeout);
}
break;
case 'noNotice': // 不提醒
case 'unSign': // 标记为已完成
case 'delete': // 删除任务
// 清除相应的 setTimeout 和从 map 中删除
if (obj.id && this.taskNoticeTimeoutMap.has(obj.id + '')) {
clearTimeout(this.taskNoticeTimeoutMap.get(obj.id + ''));
this.taskNoticeTimeoutMap.delete(obj.id + '');
}
break;
}
}
this.$store.commit('setTaskChangeObj', null);
}
// 想在 js 中使用 Vuex, 发现前辈在项目里是这么写的
// 之前 Vuex 定义时, 存放在 store/index.js 里, 生成了 Vuex 实例, 然后 export 了
// 只要直接引用这个变量就行了
const store = new Vuex.Store(Obj);
export default store;
/* 实际使用 */
import store from '../store'; // 引入实例
store.state.app.firstRedirect // 就这么用, 相当于把 this.$store 替换成 store
store.commit('changeFirstRedirect');
// 这个是根据上面来的, 大体一致
new VueRouter(RouterConfig); // router/index.js 中生成了实例并 export 了
/* 实际使用 */
import {router} from '../router/index'; //引入
router.push({
name: 'login'
}); // 使用
这个可以用 router.currentRoute 来获取当前路由信息对象
自己在 Vue.prototype 上定义一个方法(别用箭头函数), 在 Vue 文件中调用, 这时根据"一般情况下, 谁调用此函数, 函数里的 this 就指向谁"规则, this 和 Vue 文件里的 this 等价…
首先我们都知道 axios 中有拦截器, 在请求回来发现 token 过期时, 我们需要在 success拦截器函数中, 中断后续的请求处理逻辑, 并跳转到登录页重新登录, 并显示错误提示.
假定后台返回的数据格式如下:
{
code: '0'/'1'/'NEED_REDIRECT'/..., // 0 错误, 1 正常, NEED_REDIRECT 代表 token过期, 需要跳转到登录页重新登录
data: obj
}
可能有多个请求受到 token 过期的影响, 为了防止错误提示弹出多次, 在 Vuex 定义一个变量, 比如firstRedirect
;
它初始时为 true
, 当发现后台返回 code: 'NEED_REDIRECT'
时, 如果当前它为true
,
说明这是第一个被拦截下来的函数, 这时, 我们把它赋值为 false
;
这样, 设置一个 if (store.state.app.firstRedirect)
就能拦截下后面的请求(具体在 Vuex 里怎么存看自己的代码结构), 避免弹出多次错误提示.
跳转到登录页面后, 在 router.push
的回调函数中, 再把 firstRedirect
赋值回 true
但是实际发现, 本来会弹出多次的错误提示, 现在变成只弹两次, 而不是只弹一次, 猜想可能是这样:
前面几个都被拦截下来了, 但是到了某次请求返回结果时, 已经跳到了登录页面, firstRedirect
已经变回 true
了, 于是通过了 if
语句, 但是因为已经在登录页了, router.push
实际上没有跳转, 也就没有触发它的回调函数, firstRedirect
从此变为 false
, 还会影响到后续的拦截
所以, 需要加上判断——当前页面是否在登录页.
最后是发现过期后如何中断请求, 直接看下面的代码吧:
最终拦截器代码如下:
// 这里是在 js 文件中里引用 Vuex 和 Message(iView 组件) , 用法可见本页相关知识点
// 定义拦截器函数
let success_interceptor_func = response => {
if (response.data.code === 'NEED_REDIRECT') { // 如果 token 过期
if (store.state.app.firstRedirect && router.currentRoute.name !== 'login') { // 如果是第一次拦截到过期请求, 且当前页面不是登录页
store.commit('changeFirstRedirect'); // Vuex 修改 firstRedirect 的值
Message.error(lang[Vue.config.lang].tokenExpired); // 弹出错误提示
Cookies.remove('token'); // 清除过期 token , 避免跳回登录页时被判断成"已有token, 不能再回登录页重复登录" (这里 Cookies 是引用的包)
router.push({
name: 'login' // 跳转到登录页
}, () => {
if (!store.state.app.firstRedirect) {
store.commit('changeFirstRedirect'); // 改回 true
}
});
}
throw new axios.Cancel('Token expired'); // 抛出错误, 中断请求
}
return response; // 正常返回 response
};
iView 框架的 DatePicker
组件直接使用 v-model
有问题(国际时间和本地时间的问题),可以使用 value
和 @on-change
,手动赋值,这样显示就完全正常;
Datepicker
组件,可以使用 @on-open-change
,在其中设置好唯一标志本次打开的 Datepicker
的属性,然后再在 @on-change
中处理value
并不会让 Datepicker
组件的显示也跟着改iView 中有些组件可以绑定的一些事件,其本身不需要传入参数,只需要在定义事件方法时写入形参就能取到,但如果在传入时附加参数,就取不到默认参数了
此时可以使用 @on-change="setOption($event, 其他参数)"
, 此时 $event
就是默认参数
某些函数默认带有两个参数, 此时用 $event
只能取到第一个, 搜索后发现可以通过 arguments
代替 $event
就 Poptip
组件而言(其他的没观察), 它的 transfer
属性, 是把组件的气泡放到全局 body
中.
这种情况下, 在本页面的样式中操作无用, 只有在全局中调整才可以, 但是要使用 popper-class
绑定类名到 Poptip
组件才行
但是也要注意, 在这种情况下, 触发气泡的元素还在组件中, 使用深度选择器在 scoped
中选择 ivu-poptip-rel
即可
场景: iView , 三个 Select
组件,第一个的 @on-change
会动态改变第二个的 option
数组,以及第三个的类型( input
或 DatePicker
), 它的使用场景是填写筛选条件, 比如第一项选 最近更新时间,那第二项就会是 ['早于', '晚于', '时间段']
, 第三项变为 Datepicker
, 第一项选 客户星级,那第二项就是 ['大于', '小于', '等于']
, 第三项变为 InputNumber
问题: 改变首项筛选条件时, change
前后若根据首项获取的二项的 option
数组前后 length
相等(如,都有三个选项), 那么选择二项时,虽然实际上可以正确筛选,但文字总是显示成 change 前的选项.
解决:直接用 ref
取数据,当 Select
组件的 data
上的 selectedSingle
不等于 model
时, @on-change
的方法直接把 model
赋给前者
需要注意: v-for
中写的 ref
取到的值是个数组,具体到我当时的实例中,是个只有一项的数组,用 [0]
取组件数据
场景: 做邮件系统,选择收件人,需要既可以显示候选账号进行下拉选择,又可以直接手动输入账号,但是 iView 本身没有符合需求的组件
思路:
div
,在其中加入 Tag
系列,后面追加一个 input
, 再用隐藏的 span
实现 input
的动态变长(在这过程中我还找到了 contentEditable
这个可以让 div
可编辑的属性),这一步我是直接用的 filterable
+ multiple
的 Select
组件生成的 HTML
代码input
前再加一个 div
, 再把 input
长度限制到只能显示一个光标, 动态填入输入内容到前面的 div
,这样就不用变长,也不用担心溢出和让 input
换行filterable
+ multiple
的 Select
组件已经可以了,稍作调整就行.实现:
filterable
+ multiple
的 Select
组件生成的代码结构, mounted
时,为输入框绑定 blur
事件,根据 e.target.value
判断输入值是否已在下拉列表(v-for
Option
数组),是否已被选中,如果没有,则 push
进去Select
的 @on-change
中,询盘判断当前数组各项是否正则校验邮箱(推荐 Regulex ----正则可视化工具+邮箱正则表达式)通过,如果通过,则通过 DOM
绑定对应位置的 .ivu-tag-text
字体标黑(通过的情况也要显式操作,不然也可能变红),不通过标红场景: 使用 Cascader
级联组件,要求动态加入一级目录,点击一级目录后的请求二级目录还是用组件自带的搜索+动态加载功能
实现:
首先想的是直接请求一级目录动态添加,但是组件本身的机制是,使用筛选后得到的值点击后就直接被认定为完成选择,跟我想的点击动态生成的一级目录就会请求二级目录不同
于是我使用 slot ,在其中定义一个 Input 组件,这样看上去和原来一样,也能运行,但是会报错
在 Input 组件上绑定 @input.native ,
Cascader 组件上 @on-change 做回填到 Input 用,
@on-visible-change , 本意是想要让它在关闭 panel 时检测是否已经有选中的选项,有的话就回填
避免用户已选择选项后又输入字符,没有匹配到就关闭了 cascader ,这时没有触发 cascader 的 on-change 函数,所以没有回填,这时其实 cascader 是有数据的,但是 input 里却仍然显示之前没匹配到时输入的字符
结果发现 on-visible-change 好像只检测到了 visible 为 true 的情况,
控制台报错,因为组件源码里定义的 slot 默认内容中的 input 被 slot 中的 input 组件替换了,后面用到默认 input 时就会报错,不知道是不是因为这个原因才没检测到 visible 为 false 的情况,报错的时机和 panel 关闭时间相合
不得已只能在展开时清空 cascader 的 v-model 了,毕竟展开就说明是要搜索,这也说的过去
update: true,
excludePage: ''
updateCurrent() {
this.excludePage = this.$route.name;
this.update = false;
this.$nextTick(() => {
this.update = true;
this.excludePage = '';
});
}
多选:
使用 Cascader 的 自定义显示 功能, 用带 multiple 的 Select 替换掉组件自身的文本框
修改 Select 相关样式
.select {
/* 让 select 框内的文字能正常换行 */
white-space: normal;
/* 深度作用选择器, 让 scoped 的样式能影响到子组件 */
/* 让已选项 tag 的高度自适应 */
/deep/ .ivu-tag.ivu-tag-checked {
height: auto;
}
/* 隐藏掉 Select 的下拉框 */
/deep/ .ivu-select-dropdown {
display: none;
}
}
为 Cascader 组件绑定 on-change 事件
当事件传入的已选项数据不为空时, 取出已选项数据, 格式化后填入 Select 的 option 列表和 v-model 绑定的数组
为 Cascader 组件绑定 on-visible-change 事件
每次 visible-change 时, 都通过给 Cascader 设置的 ref 来清空 Cascader 的已选项
/* 通过查看 iview 源码可知点击清空图标时组件执行的操作, 从而得到下面的代码 */
this.$refs.cas.currentValue = this.$refs.cas.selected = this.$refs.cas.tmpSelected = [];
直接 Ctrl + B (webstorm 下)跳转到组件的定义处, 就能发现源码中也是 export 组件在引用使用的, 于是照着来:
// 以 this.$Message 为例
import Message from 'iview/src/components/message'
Message.error('出错信息');