(持续更新)
点击某个盒子之外的部分触发函数
假设此盒子的class为:item-operation
hideOperation(event) {
// 隐藏加减号图标
var op = document.getElementsByClassName('item-operation');
if (op) {
for (let i = 0; i < op.length; i++) {
if (!op[i].contains(event.target)) {
// 代码
this.operationValue = false;
}
}
}
}
element表格记住展开行状态
<el-table
:data="channelList"
:row-key="getRowKeys"
:expand-row-keys="expands"
@expand-change="expandChange"
>
getRowKeys(row) { // data
return row.id;
},
expands: []
watch: {
// 获取展开行状态
channelList: function() {
if (JSON.parse(sessionStorage.getItem('expands')) && JSON.parse(sessionStorage.getItem('expands')).length) {
JSON.parse(sessionStorage.getItem('expands')).map(item => {
this.expands.push(item);
});
this.expands = this.unique(this.expands);
} else {
this.expands = [];
}
}
},
expandChange(row, expandedRows) { // methods
this.expands = [];
if (expandedRows.length > 0) {
expandedRows.map((item, index) => {
this.expands.push(item.id);
});
sessionStorage.setItem('expands', JSON.stringify(this.expands));
} else {
sessionStorage.setItem('expands', JSON.stringify([]));
}
},
// 数组去重
unique(arr) {
// 遍历arr,把元素分别放入tmp数组(不存在才放)
let tmp = [];
for (let i in arr) {
//该元素在tmp内部不存在才允许追加
if (tmp.indexOf(arr[i]) === -1) {
tmp.push(arr[i]);
}
}
return tmp;
},
js实现下载图片
function downloadPic() {
let image = new Image();
image.crossOrigin = "anonymous";
image.src = "https://profile.csdnimg.cn/2/4/1/1_x1151605848";
let fileName = image.src.split(/(\\|\/)/g).pop();
image.onload = function () {
let canvas = document.createElement('canvas');
canvas.width = this.naturalWidth;
canvas.height = this.naturalHeight;
canvas.getContext('2d').drawImage(this, 0, 0);
let blob;
// get Data URI
if (image.src.indexOf(".jpg") > -1) {
blob = canvas.toDataURL("image/jpeg");
} else if (image.src.indexOf(".png") > -1) {
blob = canvas.toDataURL("image/png");
} else if (image.src.indexOf(".gif") > -1) {
blob = canvas.toDataURL("image/gif");
} else {
blob = canvas.toDataURL("image/png");
}
const a = document.createElement('a'); // 生成一个a元素
a.href = blob; // 将生成的URL设置为a.href属性
a.download = fileName; // 设置图片名称
a.click();
}
}
js下载视频(也可下载图片音频等)
// 转换视频为blob格式
getBlob(url, cb) {
const xhr = new XMLHttpRequest();
xhr.open('get', url, true);
xhr.responseType = 'blob'; // ""|"text"-字符串 "blob"-Blob对象 "arraybuffer"-ArrayBuffer对象
xhr.onload = function() {
cb(xhr.response);
};
xhr.send();
}
// 下载
handleDownload(row) {
console.log(row);
if (row.download_url) {
const fileName = row.download_url.split(/(\\|\/)/g).pop();
this.getBlob(row.download_url, function(res) {
const src = URL.createObjectURL(res);
const a = document.createElement('a');
a.href = src;
a.download = fileName;
console.log(a);
a.click();
});
} else {
this.$message({
showClose: true,
message: '下载地址为空!',
type: 'error'
});
}
}
深度遍历数组嵌套,并进行相应的操作
let arr= [
{
name: '层1',
children: [
{
name: '层2',
children: [
{
name: '层3',
children: [
{
name: '层4-1',
children: [
{
name: '层5',
children: [
{
name: '层6-1',
children: []
},
{
name: '层6-2',
children: []
},
{
name: '层6-3',
children: []
},
{
name: '层6-4',
children: []
}
]
}
]
},
{
name: '层4-2',
children: []
}
]
}
]
}
]
}
];
// 有一个缺点是数组只有一层的时候,无法进行遍历操作
// 调用示例:this.handlePointName(arr, '层5', 'add')
// 则可对name为‘层5’的数据进行添加子层的操作
// this.pointIndex与i的作用是防止操作同名节点
// 例如第五层有name为‘哈哈哈’的数据,第六层也有name为‘哈哈哈’的数据
// 则可以防止同时操作这两个数据,而this.pointIndex的值可另由深度遍历数组得到,此处略
handlePointName(data, name, handle) {
// 定义i用于区分同名节点
let i = 0;
data.forEach(item => {
const map = data => {
data.children &&
data.children.forEach((child, index) => {
++i;
if (child.name === name && this.pointIndex - 1 === i) {
switch (handle) {
case 'add':
child.children.push({
name: this.pointTemp,
children: []
});
break;
case 'edit':
child.name = this.pointTemp;
break;
case 'delete':
data.children.splice(index, 1);
break;
}
}
map(child);
});
};
map(item);
});
}
深克隆
/**
* 克隆对象或数组
* @param {Object, String} source type('arr'/'obj')
* @returns {Object}
*/
deepCopy(source, type){
if(!source || typeof source !== 'object'){
if(type === 'arr') {
return []
} else if(type === 'obj') {
return {}
}
}
let target = source.constructor === Array ? [] : {};
for(let keys in source){
if(source.hasOwnProperty(keys)){
if(source[keys] && typeof source[keys] === 'object'){
target[keys] = source[keys].constructor === Array ? [] : {};
target[keys] = deepCopy(source[keys]);
}else{
target[keys] = source[keys];
}
}
}
return target;
}
深度遍历并比较两个对象,返回值不相等的属性
/**
* 比较两个对象,返回值不同的属性
* @param {Object, Object} newObj oldObj
* @returns {Object}
*/
function deepCompare(newObj, oldObj) {
if (!newObj || typeof newObj !== 'object') {
throw new Error('error');
}
if (!oldObj || typeof oldObj !== 'object') {
throw new Error('error');
}
const targetObj = newObj.constructor === Array ? [] : {};
for (const keys in newObj) {
if (newObj.hasOwnProperty(keys)) {
if (newObj[keys] && typeof newObj[keys] === 'object') {
targetObj[keys] = newObj[keys].constructor === Array ? [] : {};
targetObj[keys] = deepCompare(newObj[keys], oldObj[keys]);
} else {
if (newObj[keys] !== oldObj[keys]) {
// console.log(keys);
targetObj[keys] = newObj[keys];
}
}
}
}
for (const key in targetObj) {
// 删除空对象和空数组
if (Array.prototype.isPrototypeOf(targetObj[key]) && targetObj[key].length === 0 ||
Object.prototype.isPrototypeOf(targetObj[key]) && Object.keys(targetObj[key]).length === 0) {
delete targetObj[key]
}
}
return targetObj
}
数组转对象
let obj = {};
// 纯数组形式
let arr = ['a', 'b', 'c']
for (const key in arr) {
obj[key] = arr[key];
}
// obj = { 0:'a', 1: 'b', 2: 'c' }
// 键值对形式
let arr = [
{ key: 'a', value: 'apple' },
{ key: 'b', value: 'ball' }
]
for (const keys in arr) {
obj[arr[keys].key] = arr[keys].value;
}
// obj = { a: 'apple', b: 'ball' }
vue+element设置开始日期和结束日期的相互限制
// 开始日期的限制
startDatePicker() {
const thz = this;
return {
disabledDate(time) {
if (thz.applyForm.end_date) {
return (
time.getTime() < Date.now() - 3600 * 1000 * 24 ||
time.getTime() > thz.applyForm.end_date
);
} else {
return time.getTime() < Date.now() - 3600 * 1000 * 24;
}
},
shortcuts: [
{
text: '今天',
onClick(picker) {
picker.$emit('pick', new Date());
}
},
{
text: '明天',
onClick(picker) {
const date = new Date();
date.setTime(date.getTime() + 3600 * 1000 * 24);
picker.$emit('pick', date);
}
},
{
text: '一周后',
onClick(picker) {
const date = new Date();
date.setTime(date.getTime() + 3600 * 1000 * 24 * 7);
picker.$emit('pick', date);
}
}
]
};
},
// 结束日期的限制
endDatePicker() {
const thz = this;
return {
disabledDate(time) {
return (
time.getTime() < thz.applyForm.start_date ||
time.getTime() < Date.now() - 3600 * 1000 * 24
);
},
shortcuts: [
{
text: '今天',
onClick(picker) {
picker.$emit('pick', new Date());
}
},
{
text: '明天',
onClick(picker) {
const date = new Date();
date.setTime(date.getTime() + 3600 * 1000 * 24);
picker.$emit('pick', date);
}
},
{
text: '一周后',
onClick(picker) {
const date = new Date();
date.setTime(date.getTime() + 3600 * 1000 * 24 * 7);
picker.$emit('pick', date);
}
}
]
};
},
vue+element表单验证-部分验证以及条件验证
为解决表单中,按条件显示的某一项为必填项,这样不管是用v-if还是v-show都会在验证的时候出问题
此为后续补充:更简洁的做法
// 直接将验证条件写到标签里面,这样不需要像下面一样做各种复杂的处理,可以直接if(valid)验证
<el-form-item v-if="dataForm.type!=='once'" label="结束日期" prop="end_date" :rules="[{require: true, trigger: 'blur', message: '结束日期不能为空'}]">
<el-date-picker v-model="dataForm.end_date" :picker-options="pickerOptions2" type="date" placeholder="结束日期" />
</el-form-item>
第一种写法
// 当频率为每周时,全部验证;为每天时排除周选项,再验证;为单次时,排除结束日期和周选项再验证
if (this.dataForm.type === 'week') {
this.$refs.dataForm.validate(valid => {
if (valid) {
this.$emit('submitPlan');
}
});
} else {
const arr = [];
if (this.dataForm.type === 'day') {
// 提取验证规则中的名称,组装为数组,并排除星期选择
for (const item in this.dataFormRules) {
if (item !== 'week') {
arr.push(item);
}
}
} else if (this.dataForm.type === 'once') {
// 提取验证规则中的名称,组装为数组,并排除结束日期,和星期选择
for (const item in this.dataFormRules) {
if (item !== 'end_date') {
if (item !== 'week') {
arr.push(item);
}
}
}
}
const validArr = [];
let flag = true;
this.$refs.dataForm.validateField(arr, valid => {
validArr.push(valid);
});
// 当验证数组中有不为空的项时,flag置为false
validArr.forEach((item, index) => {
if (item !== '') {
flag = false;
}
});
if (flag) {
this.$emit('submitPlan');
}
}
第二种写法
// 验证表单-是否为空
checkValidateField() {
const arr = [];
// 提取验证规则中的名称,组装为数组,并排除结束日期,分段间隔,周选项三项
for (const item in this.applyRules) {
if (item !== 'end_date') {
if (item !== 'record_mode') {
if (item !== 'task_mode') {
arr.push(item);
}
}
}
}
return new Promise((resolve, reject) => {
let flag = true;
const validArr = [];
this.$refs.applyForm.validateField(arr, valid => {
validArr.push(valid);
});
// 当验证数组中有不为空的项时
validArr.forEach((item, index) => {
if (item !== '') {
flag = false;
}
});
resolve(flag);
});
},
// 验证表单的部分项-周选项
checkValidateWeekly() {
return new Promise((resolve, reject) => {
let flag = '';
this.$refs.applyForm.validateField('task_mode.day', valid => {
if (!valid) {
flag = true;
} else {
flag = false;
}
resolve(flag);
});
});
},
// 验证表单的部分项-分段间隔
checkValidateInterval() {
return new Promise((resolve, reject) => {
let flag = '';
this.$refs.applyForm.validateField('record_mode.interval', valid => {
if (!valid) {
flag = true;
} else {
flag = false;
}
resolve(flag);
});
});
},
// 验证表单的部分项-结束日期
checkValidateEnddate() {
return new Promise((resolve, reject) => {
let flag = '';
this.$refs.applyForm.validateField('end_date', valid => {
if (!valid) {
flag = true;
} else {
flag = false;
}
resolve(flag);
});
});
},
// 组装验证表单
async checkValidate() {
let flag = '';
flag = await this.checkValidateField();
if (!flag) {
return false;
}
if (this.applyForm.task_mode.type !== 'single') {
flag = await this.checkValidateEnddate();
if (!flag) {
return false;
}
if (this.applyForm.task_mode.type === 'weekly') {
flag = await this.checkValidateWeekly();
if (!flag) {
return false;
}
}
}
if (this.applyForm.record_mode.type === 'segment_edit') {
flag = await this.checkValidateInterval();
if (!flag) {
return false;
}
}
return new Promise((resolve, reject) => {
resolve(flag);
});
},
// 在父组件调用时
const flag = await this.$refs.applyForm.checkValidate();
console.log(flag);
if (!flag) {
return;
}
vue+element实现select懒加载以及可搜索
// 首先第一个坑,如果不加载全部数据,已选值就只能以ID的值出现
// 第二个坑是,element的搜索是在已有数据中筛选,所以必须要改写搜索函数,从后端搜索
// 第一步,写一个vue指令:selectLoadMore.js 这部分代码copy自网上其他博主
export default {
install(Vue) {
Vue.directive('el-select-loadmore', {
bind: function (el, binding) {
// 获取element-ui定义好的scroll盒子
const SELECTWRAP_DOM = el.querySelector('.el-select-dropdown .el-select-dropdown__wrap');
SELECTWRAP_DOM.addEventListener('scroll', () => {
/**
* scrollHeight 获取元素内容高度(只读)
* scrollTop 获取或者设置元素的偏移值,常用于, 计算滚动条的位置, 当一个元素的容器没有产生垂直方向的滚动条, 那它的scrollTop的值默认为0.
* clientHeight 读取元素的可见高度(只读)
* 如果元素滚动到底, 下面等式返回true, 没有则返回false:
* SELECTWRAP_DOM.scrollHeight - SELECTWRAP_DOM.scrollTop === SELECTWRAP_DOM.clientHeight;
*/
const condition = SELECTWRAP_DOM.scrollHeight - SELECTWRAP_DOM.scrollTop <= SELECTWRAP_DOM.clientHeight;
if (condition) {
binding.value();
}
})
}
})
}
}
// 注册为全局指令
import selectLoadmore from '@/global/directives/selectLoadmore'
Vue.use(selectLoadmore)
// 第二步在代码中使用(节选自自己的项目)
<el-select
v-model="dataForm.userGroupIds"
multiple
filterable
:placeholder="$t('systomsetting.userApp.usergroupPlaceholder')"
style="width: 60%;"
v-el-select-loadmore="loadmoreUsergroup"
:filter-method="filterUsergroups"
>
<el-option
v-for="item in userGroupList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
// 初始化角色列表的请求参数
this.roleQuery = {
page: 1,
limit: 10,
name: '',
total: 0
};
// 角色列表,这里需要处理加载更多,搜索,正常加载三种情况时的数据,所以需要传入type区分
async getRoleList(type) {
this.roleQuery.page = type === 'filter' ? 1 : this.roleQuery.page;
const obj = {
name: this.roleQuery.name,
currentPage: this.roleQuery.page,
pageSize: this.roleQuery.limit
};
const res = await this.$store.dispatch(
'Apps/SystemSetting/role/list',
obj
);
if (res.ret.code === 0) {
this.roleQuery.total = res.data.count;
if (type === 'loadMore') {
this.roleList.push(...res.data.list);
} else {
this.roleList = res.data.list;
}
}
this.roleListTmp = deepCopy(this.roleList); // 这一句代码用于下文-穿梭框实现懒加载和搜索
// 编辑赋值时,数据中不包含已选值的情况,这里需要将已选值push到roleList中,然后去重
if (
this.dataFormTitle === this.$i18n.t('systomsetting.userApp.editTitle')
) {
this.roleList = uniqueArrObj(
this.roleList,
this.dataForm.roleInfo,
'id'
);
}
},
// deepCopy为深拷贝函数,因为js的对象和数组赋值方式是引用赋值,
// 所以很多时候需要使用深拷贝来赋值,拷贝一个副本
export function deepCopy(source) {
if (!source || typeof source !== 'object') {
throw new Error('error');
}
var targetObj = source.constructor === Array ? [] : {};
for (var keys in source) {
if (source.hasOwnProperty(keys)) {
if (source[keys] && typeof source[keys] === 'object') {
targetObj[keys] = source[keys].constructor === Array ? [] : {};
targetObj[keys] = deepCopy(source[keys]);
} else {
targetObj[keys] = source[keys];
}
}
}
return targetObj;
}
// 数组对象去重,copy自网友
/**
* 数组对象去重
* @param arr1
* @param arr2
* @param prop 对象唯一属性
* @returns {Array}
*/
export function uniqueArrObj(arr1, arr2, prop) {
var arr = arr1.concat(arr2);
var lastArr = [];
var obj = {};
arr.forEach(item => {
if(!obj[item[prop]]) {
obj[item[prop]] = item;
}
})
for (var key in obj) {
lastArr.push(obj[key]);
}
return lastArr;
}
// 用户组选择框懒加载
loadmoreUsergroup() {
if (this.usergroupQuery.total <= this.userGroupList.length) {
return;
}
this.usergroupQuery.page++;
this.getUserGropList('loadMore');
},
// 用户组选择框重写搜索方法
filterUsergroups(val) {
this.usergroupQuery.name = val;
this.getUserGropList('filter');
},
vue+element实现穿梭框transfer懒加载以及可搜索
// 第一个坑,和上文一样,处理已选值的问题
// 第二个坑,也和上文一样,重写搜索方法的问题,但此处不同于select,是个巨坑,没法直接重写搜索方法,看一下element的transfer组件就知道了,机制貌似是监听输入框的值,然后遍历绑定的data数据,最后需要返回true
// 首先写一个vue指令
export default {
install(Vue) {
Vue.directive('el-transfer-loadmore', {
bind: function (el, binding) {
const SELECTWRAP_DOM = el.querySelector('.el-transfer-panel__body .el-transfer-panel__list');
SELECTWRAP_DOM.addEventListener('scroll', () => {
const condition = SELECTWRAP_DOM.scrollHeight - SELECTWRAP_DOM.scrollTop <= SELECTWRAP_DOM.clientHeight;
if (condition) {
binding.value();
}
})
}
})
}
}
// 在项目中使用
<el-transfer
v-model="dataForm.role"
filterable
:titles="[$t('systomsetting.usergroupApp.roleList'), $t('systomsetting.usergroupApp.alreadyExisting')]"
:props="{
key: 'id',
label: 'name'
}"
:filter-placeholder="$t('systomsetting.keyPlaceholder')"
:data="roleList"
@change="roleChange"
v-el-transfer-loadmore="loadmoreRole"
:filter-method="filterRoles"
/>
// 初始化角色列表的请求参数
this.roleQuery = {
page: 1,
limit: 10,
name: '',
total: 0
};
// getRoleList见上文, roleListTmp的赋值见上文,
// 角色穿梭框懒加载
loadmoreRole() {
if (this.roleQuery.total <= this.roleList.length) {
return;
}
this.roleQuery.page++;
this.getRoleList('loadMore');
},
// 角色穿梭框重写搜索方法,获取到input标签,然后监听值的变化
filterRoles(query, item) {
let transfer = $(
'.el-transfer-panel__body .el-transfer-panel__filter .el-input__inner'
);
// 拿到输入框的值,并监听
if (transfer && transfer.length) {
this.transferLeftInput = transfer[0].value;
}
return item.name.indexOf(query) > -1; // 这一句是穿梭框原本的过滤方法,所以我们所做的只是在这之前拿到input标签并监听其值,然后去后端请求数据
},
watch: {
// 监听穿梭框左侧输入框的值
transferLeftInput(val) {
this.roleQuery.name = val;
this.getRoleList('filter');
},
// 此处就是巨坑了:其一当搜索不到数据时,再去修改搜索条件,穿梭框的过滤函数filterRoles并不会触发,
// 以至于监听不到transferLeftInput值的改变,就无法请求roleList数据;
// 其二当搜索出的数据为已选数据,处于右侧穿梭框中,此时相当于绑定的data无数据,
// 也不会触发过滤函数,所以需要手动请求一次。而判断在什么时候去请求,
// 就是下面代码中的这两个条件,roleListTmp为上文中roleList在push入已选值之前的副本
// 总结一下就是,当左侧穿梭框无数据或者全部数据为已选中而处于右侧时,
// 左侧的所绑定的data为空,过滤函数filterRoles是无法触发的,
// 因为filterRoles是一个循环函数,data所绑定的数据为空时,filterRoles也就不会循环了
// 此做法暂时可行,如发现有bug,会及时更新
roleListTmp(val) {
if (!val.length || val.length <= this.dataForm.roleInfo.length) {
this.roleQuery.name = '';
this.getRoleList('filter');
}
}
},
获取html内鼠标的位置坐标
getMousePos(event) {
const e = event || window.event;
const scrollX =
document.documentElement.scrollLeft || document.body.scrollLeft;
const scrollY =
document.documentElement.scrollTop || document.body.scrollTop;
const x = e.pageX || e.touches[0].pageX || e.clientX + scrollX; // e.touches[0].pageX为移动端坐标
const y = e.pageY || e.touches[0].pageY || e.clientY + scrollY;
return { x: x, y: y };
}
监听组合键Ctrl+Z
listenKeyCode() {
let code1 = 0;
let code2 = 0;
document.onkeydown = e => {
if (e.keyCode === 17) {
code1 = 1;
}
if (e.keyCode === 90) {
code2 = 1;
}
if (code1 === 1 && code2 === 1) {
this.handleback();
}
};
document.onkeyup = e => {
if (e.keyCode === 17) {
code1 = 0;
}
if (e.keyCode === 90) {
code2 = 0;
}
};
},
vue+element tree树形组件的数据分页请求及操作
以一个文件夹管理为例,先看效果
// html
<el-tree
ref="folderTree"
node-key="id"
:key="treeKey"
:data="dataList"
:props="defaultProps"
:show-checkbox="type === 'check'"
check-strictly
@node-click="handleNodeClick"
@node-expand="handleNodeClick"
@check-change="handleCheckChange"
>
<span
class="folderTree-node"
slot-scope="{ node, data }"
@contextmenu.prevent.stop="handleContext($event, data.id)"
>
<span class="folderTree-label">{{ node.label }}</span>
</span>
</el-tree>
// data
data() {
return {
dataList: [],
// 分页参数
listQuery: {
limit: 10,
offset: 1,
},
total: 0,
listQueryChild: {
limit: 10,
offset: 1,
},
defaultProps: {
children: 'children',
label: 'name',
},
treeKey: 0,
};
},
created() {
this.getList();
},
/
/ 请求数据
async getList() {
let obj = {
params: {
parent_id: 0,
limit: this.listQuery.limit,
offset: this.listQuery.offset,
},
};
let res = await this.axio.post(`directory/ws/list`, obj);
if (res.data.ret.code === 0) {
if (this.total === 0) {
this.dataList = res.data.directory_list.rows;
} else {
this.dataList = [...this.dataList, ...res.data.directory_list.rows];
}
this.total = res.data.directory_list.count;
res.data.directory_list.rows.forEach((item) => {
// 添加子节点请求条件
item.total = 0;
item.limit = this.listQueryChild.limit;
item.offset = this.listQueryChild.offset;
this.getNodeFile(item);
});
// 默认高亮
this.$nextTick(() => {
this.$refs.folderTree.setCurrentKey(this.folderId);
if (this.type === 'check') {
this.$refs.folderTree.setCheckedKeys([this.dataList[0].id]);
}
});
// 更新tree组件
this.treeKey++;
// 当数据很多时,递归请求
if (this.total > this.dataList.length) {
this.listQuery.offset++;
this.getList();
} else {
this.total = 0;
this.listQuery.offset = 1;
}
}
},
// 请求子文件夹
async getNodeFile(row) {
let obj = {
params: {
parent_id: Number(row.id),
limit: row.limit,
offset: row.offset,
},
};
let res = await this.axio.post(`directory/ws/list`, obj);
if (res.data.ret.code === 0) {
if (res.data.directory_list.rows.length > 0) {
if (row.total === 0) {
this.$set(row, 'children', deepCopy(res.data.directory_list.rows)); // deepCopy是一个深度克隆函数,用于防止数据污染
} else {
this.$set(row, 'children', [
...row.children,
...deepCopy(res.data.directory_list.rows),
]);
}
}
row.total = res.data.directory_list.count;
// 当数据很多时,递归请求
if (row.children && row.total > row.children.length) {
row.offset++;
this.getNodeFile(row);
} else {
row.total = 0;
row.offset = 1;
}
}
},
// 点击节点
handleNodeClick(row) {
if (row.children) {
row.children.forEach((item) => {
// 添加请求条件
item.total = 0;
item.limit = this.listQueryChild.limit;
item.offset = this.listQueryChild.offset;
this.getNodeFile(item);
});
}
},
在使用或显示一张图片之前,判断图片是否能正常显示
/**
* 判断图片是否能正常显示
* @param {String} url
* @returns {Boolean}
*/
function imageIsExist(url) {
return new Promise(resolve => {
let img = new Image();
img.src = url;
img.onload = function() {
resolve(true);
img = null;
};
img.onerror = function() {
resolve(false);
img = null;
};
});
}