汇总了下日常工作中前端的小tip,增加复制粘贴的效率。 ——来自低级前端粘贴工程师
- 1、三方组件库
- 2、Promise.all
- 3、扁平数据结构转树形结构
- 4、查找树节点
- 5、a-form-model跳转到校验失败的位置
- 6、表单校验->正则校验
- 7、节流、防抖(lodash库)
- 8、全屏展示某个DOM
- 9、滚动条样式
- 10、dayjs()
- 11、文字超出省略
- 12、文件上传相关
- 13、数组去重
- 14、导出表格选中数据(前端导出,不涉及后端文件流)
- 15、*嵌套表格-数据导出(方案待确认)
- 16、a-table
- 17、ES11 空值合并运算符(Nullish coalescing Operator)
- 18、汉字排序
- 19、数组分组
- 20、生成主键
- 21、a-select
- 22、sortablejs 拖动排序
- 23、base64转文件流 并下载
- 24、vxe
- 25、解决a-toolTip a-popover等浮窗组件上下浮动的问题
- 26、$ c o n f i r m 、 confirm、 confirm、message提示换行
- 27、Echarts图表转Base64
- 28、计算字符串的宽度(css:width)
- 29、获取递归组件、嵌套组件的DOM --> vue-ref
- 30、antd 日期选择器-开始日期< 结束日期
- 31、图片点击放大功能
- 32、Vue动态组件 component :is的使用
- 33、iframe实现缓存
- 34、vue-quill-editor富文本编辑器
- 35、ant-design-vue modal可拖动
- 36、组件库monorepo架构
- 37、数字输入框
- 38、a-tree右键菜单-屏蔽默认事件
- 998、$env:NODE_OPTIONS="--max-old-space-size=514096"
- 999、vscode eslint setting.json && .eslintrc.js 配置(备份)
持续更新ing,话不多说先上梦想~
1、三方组件库
- 大屏组件库-DataV
- JYeontu组件库 (好玩的东西比较多)
- Quasar
2、Promise.all
使用场景:多个异步操作全部执行完后,再继续执行
<a-button @click="getUserInfo">测试执行顺序</a-button>
getAAA() {
return new Promise((resolve) => {
api.getAAA()
.then(res => {
console.log('111');
this.nameList = res
resolve(true)
})
})
},
getBBB() {
return new Promise((resolve) => {
api.getBBB()
.then(res => {
console.log('222');
this.sexList = res
resolve(true)
})
})
},
getUserInfo() {
Promise.all[this.getAAA(), this.getBBB()]
.then(res => {
console.log('333');
})
}
打印顺序:111 222 333或者222 111 333
3、扁平数据结构转树形结构
flap2tree(arr, key = 'id', parentKey = 'pid') {
const result:any = [];
const mapObj = {};
const copyArr:any = [];
arr.forEach((item) => {
const newItem = { ...item };
copyArr.push(newItem);
mapObj[item[key]] = newItem;
});
copyArr.forEach((item) => {
const pid = item[parentKey];
let parent:any = null;
if (pid) {
parent = mapObj[pid];
if (parent) {
if (!parent.children) {
parent.children = [];
}
parent.children.push(item);
}
}
if (!parent) {
result.push(item);
}
mapObj[item[key]] = item;
});
return result;
}
4、查找树节点
4.1树-根据id查找该节点的父节点
输入一个pid,查找id=pid的父节点
treeSelect(selectedKeys: any, info: any) {
const nodeData = info.node.dataRef;
const getParentData= (arr: any, pid: any) => {
let obj: any;
const find = (arr: any, pid: any) => {
for (let i = 0; i < arr.length; i++) {
if (obj) break;
if (arr[i].id === pid) {
obj = arr[i];
break;
} else if (arr[i].children && arr[i].children.length > 0) {
find(arr[i].children, pid);
}
}
};
find(arr, pid);
return obj;
};
const parentData: any = getParentData(this.treeData, nodeData.pid);
console.log(companyData)
}
4.2树-根据id查找节点的所有父元素
输入一个id,查找该id的所有上级节点
// allMenuTreeData 树数据
// menuId 需要查找的节点
getFamilyData(allMenuTreeData, menuId) {
// 返回数据集合
const targetArr: any = [];
// 声明递归函数
const forFn = function(arr, id) {
// 遍历树
for (let i = 0; i < arr.length; i++) {
const item = arr[i];
if (item.menuId.toString() === id.toString()) {
// 查找到指定节点加入集合
targetArr.push(item);
// 查找其父节点
forFn(allMenuTreeData, item.parentId);
// 不必向下遍历,跳出循环
break;
} else {
if (item.children) {
// 向下查找到id
forFn(item.children, id);
}
}
}
};
// 调用函数
forFn(allMenuTreeData, menuId);
// 返回结果
return targetArr;
}
5、a-form-model跳转到校验失败的位置
<a-form-model
ref="gbFormRef"
:label-col="{span: 5}"
:wrapper-col="{span: 18}"
:model="gbForm"
:rules="gbFormRules">
<a-form-model-item>
<a-button
type="primary"
:loading="dialogLoading"
@click="submit()">
保存
</a-button>
</a-form-model-item>
</a-form-model>
submit() {
(this.$refs.gbFormRef as any).validate((valid: any) => {
if (valid) {
// 校验成功
} else {
setTimeout(() => {
const isError:any = document.getElementsByClassName('has-error'); // antd为has-error
isError[0].querySelector('input').focus();
}, 1);
return false;
}
});
}
6、表单校验->正则校验
formRules = {
contactNo: [
{ required: true, message: '请输入联系人电话', trigger: 'blur' },
{ pattern: /^(0|86|17951)?(13[0-9]|15[012356789]|17[678]|18[0-9]|16[0-9]|14[57])[0-9]{8}$/, message: '电话格式不正确', trigger: 'blur' }
],
telephone: [
{ required: false, message: '请输入联系方式', trigger: 'change' },
{
validator: this.telephoneValidate,
message: '请输入正确的手机号码',
trigger: 'change'
}
]
};
telephoneValidate = (rule, value, cb) => {
if (!value) {
return cb();
}
// 验证手机号的正则表达式
const regMobile = /^(0|86|17951)?(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$/;
if (regMobile.test(value)) {
return cb();
}
cb(new Error('请输入合法的手机号'));
}
7、节流、防抖(lodash库)
例如设置的时间为2s
节流(throttle):连续点击时,在点击时的2秒内只执行第一次,2s后 再次执行
防抖(debounce):连续点击时第一次执行,执行后出现2s的保护时间,保护时间内,再次点击时,重置2s的保护时间
<a-button @click="testClick('第一个参数','第二个参数')">
点击
</a-button>
import _ from 'lodash';
/*
例如设置的时间为2s
节流(throttle):连续点击时,在点击时的2秒内只执行第一次,2s后 再次执行
防抖(debounce):连续点击时第一次执行,执行后出现2s的保护时间,保护时间内,再次点击时,重置2s的保护时间
leading // 第一次生效
trailing // 最后一次生效
可自由设置首尾生效效果
*/
// 防抖
testClick = _.debounce(this.bbb, 1000, {
leading: false,
trailing: true,
});
// 节流同理
testClick = _.throttle(this.bbb, 1000, {
leading: false,
trailing: true,
});
bbb(param1: any, param2: any) {
console.log(param1, param2);
}
js写法
import { debounce } from 'lodash-es';
checkPurposeDebounce: debounce(function () {
this.changeSystemSet('CheckPurpose')
}, 1000, {
leading: false,
trailing: true
}),
8、全屏展示某个DOM
fullscreen:false
screenShow() {
const element = this.$refs.dashboardRef; // 选中DOM
if (this.fullscreen) {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.webkitCancelFullScreen) {
document.webkitCancelFullScreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
}
} else {
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.webkitRequestFullScreen) {
element.webkitRequestFullScreen();
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.msRequestFullscreen) {
// IE11
element.msRequestFullscreen();
}
}
this.fullscreen = !this.fullscreen;
}
9、滚动条样式
<style>
*::-webkit-scrollbar {
width: 6px;
height: 14px;
}
/*滚动条的滑轨背景颜色,可以用display:none让其不显示,也可以添加背景图片,颜色改变显示效果。*/
*::-webkit-scrollbar-track {
display: none;
background-color: #f5f5f5;
-webkit-box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.1);
border-radius: 5px;
}
*::-webkit-scrollbar-thumb {
/* 滚动条 */
background-color: rgba(0, 0, 0, 0.2) !important;
border-radius: 5px;
}
*::-webkit-scrollbar-button {
/* 上下翻页的按钮 */
background-color: #eee;
display: none;
}
*::-webkit-scrollbar-corner {
background-color: black;
}
</style>
10、dayjs()
dayjs().subtract(3, 'week').format('YYYY-MM-DD') //取前三个周的日期
dayjs().add(3, 'week').format('YYYY-MM-DD') //取三周以后的日期
11、文字超出省略
.displayClass {
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
12、文件上传相关
12.1 上传文件,并转为base64(a-upload)
适用于任何类型的文件,这里用图片为例
效果图
上传后
<a-form-model-item label="资质证书">
<a-upload
:show-upload-list="false"
list-type="picture-card"
:before-upload="beforeUpload"
:custom-request="selfUpload"
>
<img
v-if="hosForm.qualificationImage"
style="max-width: 300px"
:src="hosForm.qualificationImage"
/>
<template v-if="!hosForm.qualificationImage">
<a-button type="primary">
上传
</a-button>
<span style="color:#7b7b7b">
*要求文件格式为照片,且文件大小小于
<span style="color:red">3M</span>
!
</span>
</template>
<a-button
v-if="hosForm.qualificationImage"
type="primary"
style="margin-top:6px"
@click="deleteImg"
>
删除
</a-button>
</a-upload>
</a-form-model-item>
hosForm: any = {
qualificationImage: ''
};
// 限制文件类型与文件大小
beforeUpload(file: any) {
const isJPG = file.type === 'image/jpeg' || file.type === 'image/jpg' || file.type === 'image/png' || file.type === 'image/bmp';
if (!isJPG) {
this.$message.error('请上传图片文件');
}
console.log(file.size);
const size = file.size / 1024 / 1024;
const isLt2M = size < 4;
if (!isLt2M) {
this.$message.error(`文件大小应小于3M,当前图片大小为${size.toFixed(2)}M!`);
}
return isJPG && isLt2M;
}
// 图片转base64
selfUpload(e: any) {
const { action, file, onSuccess, onError, onProgress } = e;
const base64 = new Promise((resolve) => {
const fileReader = new FileReader();
fileReader.readAsDataURL(file);
fileReader.onload = () => {
resolve(fileReader.result);
this.hosForm.qualificationImage = fileReader.result;
};
});
}
deleteImg(e: any) {
this.hosForm.qualificationImage = '';
e.stopPropagation();
}
/* 调整备注文字与按钮样式 */
>>> .ant-upload.ant-upload-select-picture-card > .ant-upload {
display: grid !important;
}
12.2 自定义文件上传(a-upload)
在项目中使用<a-upload>自身的上传功能时,会存在没有鉴权信息(请求不携带token)的问题,因此使用组件的customRequest 属性来解决。
customRequest:通过覆盖默认的上传行为,实现自定义自己的上传。
参考文献:
https://blog.csdn.net/qy8189/article/details/127547540
点此跳转
<a-upload
name="file"
accept=".xls,.xlsx"
:custom-request="uploadExecl"
>
<a-button
:loading="uploadLoading"
type="primary"
icon="upload"
>
<span v-if="uploadLoading">
导入中...
</span>
<span v-else>
导入名单
</span>
</a-button>
</a-upload>
uploadLoading: boolean = false
uploadExecl(info: any) {
// 使用提交form表单形式,渲染入参
const param: any = new FormData();
param.append('file', info.file);
param.append('orgId', '123456789');
this.uploadLoading = true;
uploadApi.uploadFile(param)
.then(res => {
this.$message.success('导入成功!')
this.uploadLoading= false;
})
.catch(err => {
this.uploadLoading= false;
this.$message.error(err);
});
}
12.3 文件流下载
exportByFileStream() {
exportApi.downExcel()
.then((res: any) => {
const filename = `人员导入模板.xls`;
const blob = new Blob([res], { type: 'application/octet-stream;' });
if ('download' in document.createElement('a')) {
const elink = document.createElement('a');
elink.download = filename;
elink.style.display = 'none';
elink.href = URL.createObjectURL(blob);
document.body.appendChild(elink);
elink.click();
URL.revokeObjectURL(elink.href);
document.body.removeChild(elink);
} else {
(navigator as any).msSaveBlob(blob, filename);
}
})
.catch(err => {
this.$message.error(`下载失败:${err}`);
});
}
13、数组去重
不解释连招
// 数组去重
for (var i = 0; i < arr.length; i++) {
for (var j = i + 1; j < arr.length; j++) {
if (arr[i].name === arr[j].name) {
// 第一个name等于第二个name,splice方法删除第二个
arr.splice(j, 1);
j--;
}
}
}
14、导出表格选中数据(前端导出,不涉及后端文件流)
14.1 第一种(导出常规表格,无样式,无合并功能)(xlsx、file-saver)
exportTable() {
if (this.selectedRowKeys.length === 0) {
this.$message.warn('请选择需要导出的数据!');
return;
}
const excelTitle = '预约列表';
const tHeader: any = [];
const filterVal: any = []; // 字段名称
this.columns.forEach(item => {
if (item.title !== '套餐项目') {
tHeader.push(item.title);
filterVal.push(item.dataIndex);
}
});
// selectedRows:选中的表格数据
const data = this.selectedRows.map(v => filterVal.map(j => v[j]));
import('@/utils/Export2Excel').then(excel => {
excel.export_json_to_excel({
header: tHeader, // 表头 可以指定表头,如['id','name']
data, // 表头所对应的数据 如['李四','张三']
filename: excelTitle // 文件标题
});
});
}
下载依赖xlsx、file-saver
npm i xlsx --save
npm i file-saver --save
新建文件 utils/Export2Excel
/* eslint-disable */
import { saveAs } from 'file-saver';
import XLSX from 'xlsx';
function generateArray(table) {
var out = [];
var rows = table.querySelectorAll('tr');
var ranges = [];
for (var R = 0; R < rows.length; ++R) {
var outRow = [];
var row = rows[R];
var columns = row.querySelectorAll('td');
for (var C = 0; C < columns.length; ++C) {
var cell = columns[C];
var colspan = cell.getAttribute('colspan');
var rowspan = cell.getAttribute('rowspan');
var cellValue = cell.innerText;
if (cellValue !== '' && cellValue == +cellValue) cellValue = +cellValue;
//Skip ranges
ranges.forEach(function(range) {
if (R >= range.s.r && R <= range.e.r && outRow.length >= range.s.c && outRow.length <= range.e.c) {
for (var i = 0; i <= range.e.c - range.s.c; ++i) outRow.push(null);
}
});
//Handle Row Span
if (rowspan || colspan) {
rowspan = rowspan || 1;
colspan = colspan || 1;
ranges.push({
s: {
r: R,
c: outRow.length
},
e: {
r: R + rowspan - 1,
c: outRow.length + colspan - 1
}
});
}
//Handle Value
outRow.push(cellValue !== '' ? cellValue : null);
//Handle Colspan
if (colspan) for (var k = 0; k < colspan - 1; ++k) outRow.push(null);
}
out.push(outRow);
}
return [out, ranges];
}
function datenum(v, date1904) {
if (date1904) v += 1462;
var epoch = Date.parse(v);
return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000);
}
function sheet_from_array_of_arrays(data, opts) {
var ws = {};
var range = {
s: {
c: 10000000,
r: 10000000
},
e: {
c: 0,
r: 0
}
};
for (var R = 0; R != data.length; ++R) {
for (var C = 0; C != data[R].length; ++C) {
if (range.s.r > R) range.s.r = R;
if (range.s.c > C) range.s.c = C;
if (range.e.r < R) range.e.r = R;
if (range.e.c < C) range.e.c = C;
var cell = {
v: data[R][C]
};
if (cell.v == null) continue;
var cell_ref = XLSX.utils.encode_cell({
c: C,
r: R
});
if (typeof cell.v === 'number') cell.t = 'n';
else if (typeof cell.v === 'boolean') cell.t = 'b';
else if (cell.v instanceof Date) {
cell.t = 'n';
cell.z = XLSX.SSF._table[14];
cell.v = datenum(cell.v);
} else cell.t = 's';
ws[cell_ref] = cell;
}
}
if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range);
return ws;
}
function Workbook() {
if (!(this instanceof Workbook)) return new Workbook();
this.SheetNames = [];
this.Sheets = {};
}
function s2ab(s) {
var buf = new ArrayBuffer(s.length);
var view = new Uint8Array(buf);
for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xff;
return buf;
}
export function export_table_to_excel(id) {
var theTable = document.getElementById(id);
var oo = generateArray(theTable);
var ranges = oo[1];
/* original data */
var data = oo[0];
var ws_name = 'SheetJS';
var wb = new Workbook(),
ws = sheet_from_array_of_arrays(data);
/* add ranges to worksheet */
// ws['!cols'] = ['apple', 'banan'];
ws['!merges'] = ranges;
/* add worksheet to workbook */
wb.SheetNames.push(ws_name);
wb.Sheets[ws_name] = ws;
var wbout = XLSX.write(wb, {
bookType: 'xlsx',
bookSST: false,
type: 'binary'
});
saveAs(
new Blob([s2ab(wbout)], {
type: 'application/octet-stream'
}),
'test.xlsx'
);
}
export function export_json_to_excel({ multiHeader = [], header, data, filename, merges = [], autoWidth = true, bookType = 'xlsx' } = {}) {
/* original data */
filename = filename || 'excel-list';
data = [...data];
data.unshift(header);
for (let i = multiHeader.length - 1; i > -1; i--) {
data.unshift(multiHeader[i]);
}
var ws_name = 'SheetJS';
var wb = new Workbook(),
ws = sheet_from_array_of_arrays(data);
if (merges.length > 0) {
if (!ws['!merges']) ws['!merges'] = [];
merges.forEach(item => {
ws['!merges'].push(XLSX.utils.decode_range(item));
});
}
if (autoWidth) {
/*设置worksheet每列的最大宽度*/
const colWidth = data.map(row =>
row.map(val => {
/*先判断是否为null/undefined*/
if (val == null) {
return {
wch: 10
};
} else if (val.toString().charCodeAt(0) > 255) {
/*再判断是否为中文*/
return {
wch: val.toString().length * 2
};
} else {
return {
wch: val.toString().length
};
}
})
);
/*以第一行为初始值*/
let result = colWidth[0];
for (let i = 1; i < colWidth.length; i++) {
for (let j = 0; j < colWidth[i].length; j++) {
if (result[j]['wch'] < colWidth[i][j]['wch']) {
result[j]['wch'] = colWidth[i][j]['wch'];
}
}
}
ws['!cols'] = result;
}
/* add worksheet to workbook */
wb.SheetNames.push(ws_name);
wb.Sheets[ws_name] = ws;
var wbout = XLSX.write(wb, {
bookType: bookType,
bookSST: false,
type: 'binary'
});
saveAs(
new Blob([s2ab(wbout)], {
type: 'application/octet-stream'
}),
`${filename}.${bookType}`
);
}
14.2 第二种(适用场景同第一种)(xlsx)
相比第一种更j简洁,代码量更少
npm install xlsx --save
import XLSX from 'xlsx';
exportExcel() {
if (!this.selectedRowsList || this.selectedRowsList.length === 0) {
this.$message.info('请选择导出人员!');
return;
}
const columns = this.column.filter((item: { exportFlag: any; }) => item.exportFlag).map((item: { title: any; }) => item.title)
const dataIndex = this.column.filter((item: { exportFlag: any; }) => item.exportFlag).map((item: { dataIndex: any; }) => item.dataIndex)
const data = this.selectedRowsList.map((v: { [x: string]: any; }) => dataIndex.map((j:any) => v[j]))
const param = [columns, ...data]
const excelName = '人员信息' + '.xlsx';
const ws = XLSX.utils.aoa_to_sheet(param);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, '人员信息');
XLSX.writeFile(wb, excelName);
}
14.3 第三种(可以设置导出样式——>单元格格式、表格样式、行列宽高、增加表格标题、表头)(lay-excel)
以下两种方法,均使用 LAY_EXCEL插件,使用场景以及区别如下
3.1:针对普通简单的表格,且存在标题、副标题(原理:合并单元格属性,可根据表格样式固定写死)
3.2:针对存在合并单元格的表格(原理:动态渲染合并单元格所需的坐标数组)
npm install lay-excel --save //安装依赖
14.3.1 增加表头、表格标题(Excel单元格格式设置:numFmt: ‘@’)
注意:导出的数据中,表头的顺序必须与表头对应的字段的顺序保持一致!
如:tableHeader的顺序为 【姓名 性别 年龄 身份证号 出生日期】
则tableDatar的顺序为 【name sex age idcardno birthDy】
表格效果
页面样式
import LAY_EXCEL from 'lay-excel';
exportData() {
const result = []; // 导出的数据
this.tableData.forEach((item: any, index: any) => {
const { companyName, serialNo, patName, sexName, patAge, hazardAgeYear, hazardFactorName, postStatusName, yszybName } = item;
const obj = {
orderNo: index + 1,
companyName,
serialNo,
patName,
sexName,
patAge,
hazardAgeYear,
hazardFactorName,
postStatusName
};
result.push(obj); // 设置数据的顺序(此顺序为columns对应的顺序)
});
const tableHeader = {}; // 表头
const tableTitle = {}; // 标题
const memoTitle = {}; // 小标题
for (let i = 0; i < this.columns.length; i++) {
// 设置列名
this.$set(tableHeader, this.columns[i].dataIndex, this.columns[i].title);
// 设置小标题
this.$set(memoTitle, this.columns[i].dataIndex, i <= 4 ? '填报单位(盖章):山东省济南市龙奥大厦' : '填报日期: 2023-08-14');
// 设置标题
this.$set(tableTitle, this.columns[i].dataIndex, '_______年度职业健康检查发现疑似职业病人员名单');
}
result.unshift(tableHeader);
result.unshift(memoTitle);
result.unshift(tableTitle);
// 设置表格样式
/*
setExportCellStyle(param1,param2,param3)
param1:要导出的数据
param2:设置样式生效范围(Excel的xy坐标,如A1到E10,则设置为 'A1:E10')
param3:样式回调函数
*/
LAY_EXCEL.setExportCellStyle(result, `A1:K${this.tableData.length + 4}`, {
s: {
numFmt: '@',// 单元格格式 设置为 文本(这里在14.3.3详细说)
fill: {
bgColor: {
rgb: '217346'
},
fgColor: {
rgb: 'FFFFFF'
}
},
// font: {
// color: {
// rgb: 'e74032'
// },
// sz: '28',
// bold: false
// },
alignment: {
// 文本对齐方式
horizontal: 'center',
vertical: 'center'
},
border: {
// 边框颜色以及线条样式
top: { style: 'thin', color: { rgb: 'd4d4d4' } },
bottom: { style: 'thin', color: { rgb: 'd4d4d4' } },
left: { style: 'thin', color: { rgb: 'd4d4d4' } },
right: { style: 'thin', color: { rgb: 'd4d4d4' } }
}
}
});
// 行列合并设置(同是Excel的xy坐标)
var mergeConf = LAY_EXCEL.makeMergeConfig([
// 根据表格样式,写出合并坐标
['A1', 'K1'],
['A2', 'E2'],
['F2', 'K2']
]);
// 设置行的宽度(从开头到结束)
var colConf = LAY_EXCEL.makeColConfig({ A: 50, B: 300, C: 150, D: 80, E: 50, F: 50, G: 60, H: 300, I: 100, J: 100, K: 100 });
// 设置列的高度
var rowConf = LAY_EXCEL.makeRowConfig({ 0: 50, 1: 30 }); // 第0行和第一行
LAY_EXCEL.exportExcel(
{
第一页: result
},
'职业病人员名单.xlsx',
'xlsx',
{
extend: {
// extend 中可以指定某个 sheet 的属性,如果不指定 sheet 则所有 sheet 套用同一套属性
第一页: {
// 以下配置仅 sheet1 有效
'!merges': mergeConf,
'!cols': colConf,
'!rows': rowConf
}
}
}
);
}
14.3.2 合并单元格表格的导出
页面样式
页面样式
//与3.1相同,重点是mergeConf()
exportData() {
const result = []; // 导出的数据
this.tableData.forEach((item: any, index: any) => {
const { seq, statisticsMonth, personNum, price, totalPrice } = item;
result.push({ seq, statisticsMonth, personNum, price, totalPrice }); // 设置数据的顺序(此顺序为columns对应的顺序)
});
const tableHeader = {}; // 表头
const tableTitle = {}; // 标题
for (let i = 0; i < this.columns.length; i++) {
// 设置列名
this.$set(tableHeader, this.columns[i].dataIndex, this.columns[i].title);
// 设置标题
this.$set(tableTitle, this.columns[i].dataIndex, '职业病体检汇总');
}
result.unshift(tableHeader);
result.unshift(tableTitle);
// 设置表格样式
/*
setExportCellStyle(param1,param2,param3)
param1:要导出的数据
param2:设置样式生效范围(Excel的xy坐标,如A1到E10,则设置为 'A1:E10')
param3:样式回调函数
*/
LAY_EXCEL.setExportCellStyle(result, `A1:K${this.tableData.length + 2}`, {
s: {
fill: {
bgColor: {
rgb: '217346'
},
fgColor: {
rgb: 'FFFFFF'
}
},
// font: {
// color: {
// rgb: 'e74032'
// },
// sz: '28',
// bold: false
// },
alignment: {
// 文本对齐方式
horizontal: 'center',
vertical: 'center'
},
border: {
// 边框颜色以及线条样式
top: { style: 'thin', color: { rgb: 'd4d4d4' } },
bottom: { style: 'thin', color: { rgb: 'd4d4d4' } },
left: { style: 'thin', color: { rgb: 'd4d4d4' } },
right: { style: 'thin', color: { rgb: 'd4d4d4' } }
}
}
});
// 行列合并设置(同是Excel的xy坐标)
var mergeConf = LAY_EXCEL.makeMergeConfig([
['A1', 'E1'],
...this.mergeConf() // 合并列
]);
// 设置行的宽度(从开头到结束)
var colConf = LAY_EXCEL.makeColConfig({ A: 50, B: 150, C: 80, D: 80, E: 80 });
// 设置列的高度
var rowConf = LAY_EXCEL.makeRowConfig({ 0: 50, 1: 30 }); // 第0行和第一行
LAY_EXCEL.exportExcel(
{
第一页: result
},
'职业病体检汇总.xlsx',
'xlsx',
{
extend: {
// extend 中可以指定某个 sheet 的属性,如果不指定 sheet 则所有 sheet 套用同一套属性
第一页: {
// 以下配置仅 sheet1 有效
'!merges': mergeConf,
'!cols': colConf,
'!rows': rowConf
}
}
}
);
}
mergeConf() {
/*
动态渲染出Excel合并单元格所需的坐标数组
mergeArr数据格式为:[['A1', 'E1'],['A1', 'E1'],['A1', 'E1'],['A1', 'E1']]
*/
const mergeArr: any = [];
const mergeFiledList: any = this.tableData.map((itm: any) => {
return {
seq: itm.seq,
statisticsMonth: itm.statisticsMonth
};
});
// 数组去重
for (var i = 0; i < mergeFiledList.length; i++) {
for (var j = i + 1; j < mergeFiledList.length; j++) {
if (mergeFiledList[i].seq === mergeFiledList[j].seq && mergeFiledList[i].statisticsMonth === mergeFiledList[j].statisticsMonth) {
mergeFiledList.splice(j, 1);
j--;
}
}
}
mergeFiledList.forEach((item: any) => {
// 找出字段出现次数 -->渲染结束坐标
const getAppearNum = (fild: any, fildData: any) => {
const fildArr = this.tableData.map((item) => item[fild]);
return fildArr.filter((item) => item === fildData).length;
};
// 以下方法是拆分写的,可以做合并
/* ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ */
// 渲染A列的配置(序号 seq)
// +3是因为 标题+1行,表头+1行,索引从0开始再+1
const startA = this.tableData.findIndex((itm) => itm.seq === item.seq) + 3; // +3根据场景 视情况而定
const seqAppearNum = getAppearNum('seq', item.seq);
const endA = `A${startA + seqAppearNum - 1}`; // 终点坐标=起点坐标+合并列的行数-1
// push前进行去重
if (!mergeArr.some((itm: any) => itm[0] === 'A' + startA && itm[1] === endA)) {
mergeArr.push(['A' + startA, endA]);
}
// 同理
// 渲染B列的配置(月份:statisticsMonth)
const startB = this.tableData.findIndex((itm) => itm.statisticsMonth === item.statisticsMonth) + 3;
const dateAppearNum = getAppearNum('statisticsMonth', item.statisticsMonth);
const endB = `B${startB + dateAppearNum - 1}`;
if (!mergeArr.some((itm: any) => itm[0] === 'B' + startB && itm[1] === endB)) {
mergeArr.push(['B' + startB, endB]);
}
/* ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ */
// 简化写法
/* ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ */
const renderMergeArr = (axisFiled:any, excelColumn:any) => {
const start = this.tableData.findIndex((itm) => itm[axisFiled] === item[axisFiled]) + 3;
const appearNum = getAppearNum(axisFiled, item[axisFiled]);
const end = excelColumn + `${start + appearNum - 1}`;
if (!mergeArr.some((itm: any) => itm[0] === excelColumn + start && itm[1] === end)) {
mergeArr.push([excelColumn + start, end]);
}
}
renderMergeArr('seq', 'A')
renderMergeArr('statisticsMonth', 'B')
/* ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ */
});
return mergeArr;
}
14.3.3 导出表格(设置单元格格式)
背景:有时导出的数据存在长数字类型(Long),如雪花算法生成的ID,此时导出的表格中的long类型的数据会存在精度丢失的问题(如下图所示),此时需要将单元格格式设置为文本型
解决方案:目前使用lay-excel,当然也有别的方案哈,此处demo以lay-excel导出表格数据为例
// 表格列
getExportColumn() {
return [
{ title: '诊断id(为空即新增)', dataIndex: 'dictDiagnosisId' },
{ title: '诊断名称', dataIndex: 'diagnosisName' },
{ title: '排序', dataIndex: 'seq' },
{ title: '第一关键字', dataIndex: 'firstKeyword' },
{ title: '第二关键字', dataIndex: 'secondKeyword' },
{ title: '第三关键字', dataIndex: 'thirdKeyword' },
{ title: '第一过滤关键字', dataIndex: 'filterFirstKeyword' },
{ title: '第二过滤关键字', dataIndex: 'filterSecondKeyword' },
{ title: '第三过滤关键字', dataIndex: 'filterThirdKeyword' },
{ title: '性别 未知, 男, 女', dataIndex: 'sexName' },
{ title: '诊断描述', dataIndex: 'diagnosisDes' },
{ title: '是否常见病 0不是1是', dataIndex: 'comAilmFlag' },
{ title: '是否既往病 0不是1是', dataIndex: 'pastIllnessFlag' },
{ title: '是否家族病 0不是1是', dataIndex: 'familialDiseaseFlag' },
{ title: '病因', dataIndex: 'pathogeny' },
{ title: '并发症', dataIndex: 'complication' },
{ title: '复查指南', dataIndex: 'reciewGuide' },
{ title: '常用模板 0不是1是', dataIndex: 'alwaysFlag' },
{ title: '随访标记 0无关1应该随访', dataIndex: 'sfFlag' },
{ title: '诊断等级(数字1-5)', dataIndex: 'diagnosisRank' },
]
}
async exportTable() {
const allColumns:any = this.getExportColumn();
// 需要导出的列的字段
const exportColumns = allColumns.map((it:any) => it.dataIndex)
// 处理导出数据:根据exportColumns把tableData过滤出需要导出的字段(删除tableData中不需要导出的数据)
const data = LAY_EXCEL.filterExportData(this.tableData, exportColumns);
console.log(data);// 参考图1
const tableHeader: any = {};
allColumns.forEach((item:any) => {
tableHeader[item.dataIndex] = item.title;
});
console.log(tableHeader, '---tableHeader');// 参考图2
data.unshift(tableHeader) // 导出数据中 添加表头
this.exportAction(data, '诊断建议导出')
}
exportAction(result:any, fileName:any) {
// 设置单元格样式生效范围,例如生效范围为A1到Z9的单元格 :A1:Z9
// 我这里设置的动态的,根据你需求来定
const range = fileName.includes('模板') ? 'A1:U999' : `A1:U${result.length + 10}`
LAY_EXCEL.setExportCellStyle(result, range, {
s: {
// numFmt是重点!!设置单元格的格式!!!
// ( 找的源码,日了公狗的 别问我咋找到的,就这个吊玩意坑了我一下午,@代表文本类型,可解决长数字精度丢失的问题,其他格式类型 放下面了)
numFmt: '@',
fill: {
bgColor: {
rgb: '217346',
},
fgColor: {
rgb: 'FFFFFF',
},
},
// font: {
// color: {
// rgb: 'e74032'
// },
// sz: '28',
// bold: false
// },
alignment: {
// 文本对齐方式
horizontal: 'center',
vertical: 'center',
},
border: {
// 边框颜色以及线条样式
top: { style: 'thin', color: { rgb: 'd4d4d4' } },
bottom: { style: 'thin', color: { rgb: 'd4d4d4' } },
left: { style: 'thin', color: { rgb: 'd4d4d4' } },
right: { style: 'thin', color: { rgb: 'd4d4d4' } },
},
},
});
// 设置列宽(根据自己需求来设置,我设置的150)
const columnType = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'G', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U'];
const columnConfig: any = {};
for (const k in columnType) {
columnConfig[k] = 150;
}
// 设置行的宽度(从开头到结束)
// 设置列的高度
LAY_EXCEL.exportExcel(
{
第一页: result,
},
`${fileName}.xlsx`,
'xlsx',
{
extend: {
// extend 中可以指定某个 sheet 的属性,如果不指定 sheet 则所有 sheet 套用同一套属性
第一页: {
// 以下配置仅 sheet1 有效
'!cols': LAY_EXCEL.makeColConfig(columnConfig),
'!rows': LAY_EXCEL.makeRowConfig({ 0: 30 }),// 行高
},
},
}
);
}
图1
图2
格式map如下
var table_fmt = {
0: 'General',
1: '0',
2: '0.00',
3: '#,##0',
4: '#,##0.00',
9: '0%',
10: '0.00%',
11: '0.00E+00',
12: '# ?/?',
13: '# ??/??',
14: 'm/d/yy',
15: 'd-mmm-yy',
16: 'd-mmm',
17: 'mmm-yy',
18: 'h:mm AM/PM',
19: 'h:mm:ss AM/PM',
20: 'h:mm',
21: 'h:mm:ss',
22: 'm/d/yy h:mm',
37: '#,##0 ;(#,##0)',
38: '#,##0 ;[Red](#,##0)',
39: '#,##0.00;(#,##0.00)',
40: '#,##0.00;[Red](#,##0.00)',
45: 'mm:ss',
46: '[h]:mm:ss',
47: 'mmss.0',
48: '##0.0E+0',
49: '@', // 文本
56: '"上午/下午 "hh"時"mm"分"ss"秒 "'
}
15、*嵌套表格-数据导出(方案待确认)
原型以及数据结构
![在这里插入图片描述](https://img-blog.csdnimg.cn/43ad2e3eceee4300a3b35ed642ece404.jpeg
exportData() {
if (this.selectedRowKeys.length === 0) return this.$message.warn('请勾选需要导出的数据!')
const selectedRowsList = this.tableData.filter(item => this.selectedRowKeys.findIndex((key: any) => key === item.ohOrderCheckId) > -1)
const columns = this.columns.map(item => item.title)
const parentDataIndex = this.columns.map((item: { dataIndex: any; }) => item.dataIndex)
const data:any = []
selectedRowsList.forEach(item => {
data.push(parentDataIndex.map((j:any) => item[j]))
if (item.children) {
const childDataIndex = this.childColumns.map((itm: any) => itm.dataIndex)
const cHeader = this.childColumns.map(itm => itm.title)
cHeader.unshift('')
data.push(cHeader)
item.children.forEach((cItem:any) => {
const cData = childDataIndex.map((j:any) => cItem[j])
cData.unshift('')
data.push(cData)
})
}
})
const param = [columns, ...data] //param数据结构如下图
console.log(param);
const excelName = '人员信息' + '.xlsx';
const ws = XLSX.utils.aoa_to_sheet(param);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, '人员信息');
XLSX.writeFile(wb, excelName);
}
构造出来的param
16、a-table
16.1 a-table表格列渲染
columns = [
{
title: '登记状态',
dataIndex: 'regFlag',
customCell: (row:any) => {
return {
style: {
color: row.reviewStatus === 3 ? 'red' : ''
}
};
},
customRender: val => {
// customRender接收三个参数(val,row,index)
return val === 1 ? '已登记' : '未登记';
}
}
];
16.2 a-table列合并
// 定义一个全局变量,存储重复的值,支持多列
temp:any ={}
columns = [
{
title: '性别',
dataIndex: 'sexName',
customRender: (value, row, index) => {
const obj = {
children: value,
attrs: {}
};
obj.attrs.rowSpan = this.mergeCells(row.sexName, this.tableData, 'sexName');
// 第一个参数:列对应的值
// 第二个参数:表格数据tableData
// 第三个参数:列名
return obj;
}
}
];
mergeCells(text, array, columns) {
// text:列对应的值 array:表格数据tableData columns:列名
let i = 0;
if (text !== this.temp[columns]) {
this.temp[columns] = text;
array.forEach(item => {
if (item[columns] === this.temp[columns]) {
i += 1;
}
});
}
return i;
}
16.3 a-table 行选中-行样式
<a-table
:row-class-name="row => (row.clicked ? 'selectedRowClass' : '')"
/>
.selectedRowClass {
background: #3b9dff5e;
}
16.4a-table鼠标覆盖时 行变色(:hover)
.ant-table-tbody {
> tr:hover:not(.ant-table-expanded-row) > td,
.ant-table-row-hover,
.ant-table-row-hover > td {
background: #3b7cff !important;
}
}
16.5 a-table 缩小表格选择框间距
.ant-table colgroup > col.ant-table-selection-col {
width: 45px;
}
17、ES11 空值合并运算符(Nullish coalescing Operator)
17.1 空值合并操作符(??
)
空值合并操作符(??)
是一个逻辑操作符,当左边的操作数为 null 或 undefined 的时候,返回其右侧操作符,否则返回左侧操作符。
undefined ?? 'foo' // 'foo'
null ?? 'foo' // 'foo'
'foo' ?? 'bar' // 'foo'
17.2 逻辑或操作符(||
)
逻辑或操作符(||)
,会在左侧操作数为假值时返回右侧操作数,也就是说如果使用 || 来为某些变量设置默认值,可能会出现意料之外的情况。比如 0、‘’、NaN、false:
0 || 1 // 1
0 ?? 1 // 0
'' || 'bar' // 'bar'
'' ?? 'bar' // ''
NaN || 1 // 1
NaN ?? 1 // NaN
false || 'bar' // 'bar'
false ?? 'bar' // false
18、汉字排序
const newData = resData.sort((item1, item2) => {
return item1.userName.localeCompare(item2.userName, 'zh');
})
19、数组分组
renderPayUserData() {
const sortedArr = this.groupBy(this.selectedRows, item => {
return [item.groupId]; // 按照groupId进行分组
});
}
groupBy(array, f) {
const groups = {};
array.forEach(item => {
const group = JSON.stringify(f(item));
groups[group] = groups[group] || [];
groups[group].push(item);
});
return Object.keys(groups).map(group => {
return groups[group];
});
}
20、生成主键
getUUid() {
let guid = '';
for (let i = 1; i <= 32; i++) {
var n = Math.floor(Math.random() * 16.0).toString(16);
guid += n;
if (i === 8 || i === 12 || i === 16 || i === 20) {
guid += '-';
}
}
return guid;
}
21、a-select
21.1 a-select下拉框-模糊搜索(在现有的数据里,根据关键词过滤)
使用场景:下拉框数据量大时,可只获取50条,通过输入关键词来调用接口来选择
filterOption(input, option) {
return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0;
}
21.2 a-select下拉框-可输入可选择
<a-select
v-model="payForm.patName"
:show-search="true"
:not-found-content="null"
:filter-option="true"
@search="handleSearch"
@blur="handleBlur"
@change="handleChange"
>
<a-select-option
v-for="item of patNameList"
:key="item.patId"
:value="item.patName"
>
{{ item.patName }}
</a-select-option>
</a-select>
handleBlur(name) {
this.payForm.patName = name;
}
handleSearch(name) {
this.handleChange(name)
}
handleChange(value) {
this.healCertForm.healthOrgName = (value != null && value !== '') ? value : '' // 这里是重点
}
21.3 a-select下拉框-接口检索(输入关键词调用接口检索)
在这里插入代码片
<a-select
v-model="personListForm.industrialClassification"
show-search
:filter-option="false"
:not-found-content="workTypeLoading ? undefined : null"
@search="wokrTypeSelectSearch"
>
<a-spin
v-if="workTypeLoading"
slot="notFoundContent"
size="small" />
<a-select-option
v-for="d in industrialClassificationList"
:key="d.industryTypeId"
:value="d.industryTypeId">
{{ d.industryTypeName }}
</a-select-option>
</a-select>
wokrTypeSelectSearch(industryTypeName: any) {
this.workTypeLoading = true
basicMaintenance.dictIndustryType({
industryTypeName,
pageNum: 1,
pageSize: 100
})
.then((result:any) => {
this.industrialClassificationList = result.list
this.workTypeLoading = false
})
.catch(err => {
this.workTypeLoading = false
this.$message.error(`行业查询失败:${err}`)
})
}
22、sortablejs 拖动排序
文档地址 http://www.sortablejs.com/options.html
import Sortable from 'sortablejs'
mounted() {
this.initSortable()
}
initSortable() {
this.$nextTick(() => {
const el = document.querySelectorAll('.ant-table-tbody')[2]
// 创建拖拽对象
this.sortable = Sortable.create(el, {
sort: !this.tableCompDisabledFlag, // 是否可进行拖拽排序
animation: 150,// ms, number 单位:ms,定义排序动画的时间
// 拖拽完成,移除拖拽之前的位置上的元素,在拖拽之后的位置上添加拖拽元素
onEnd: ({ newIndex, oldIndex }) => {
// 注意:这里要直接操作原数据,不能深拷贝,否则存在缓存问题
// 业务代码自行忽略
const val = this.itemAdviceTableData[oldIndex]
this.itemAdviceTableData.splice(oldIndex, 1)
this.itemAdviceTableData.splice(newIndex, 0, val)
}
})
})
}
23、base64转文件流 并下载
export function base64toFile(dataurl, filename = 'file') {
// base64转文件流 并下载
const arr = dataurl.split(',')
const mime = arr[0].match(/:(.*?);/)[1]
const suffix = mime.split('/')[1]
const bstr = atob(arr[1])
let n = bstr.length
const u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
const file = new File([u8arr], `${filename}.${suffix}`, {
type: mime
})
// 下载文件流 ↓↓↓
const content = file;
const blob = new Blob([content], { type: 'application/octet-stream' });
if ('download' in document.createElement('a')) {
const elink = document.createElement('a');
elink.download = `${filename}.${suffix}`; // 文件名
elink.style.display = 'none';
elink.href = URL.createObjectURL(blob);
document.body.appendChild(elink);
elink.click();
URL.revokeObjectURL(elink.href);
document.body.removeChild(elink);
}
}
24、vxe
此处只用了model、table的部分功能,详情见vxe-table官方文档
24.1 vxe-modal 弹窗
解决a-moadal不可拖动、不可伸缩的问题
官方文档https://vxetable.cn/v3/#/table/module/modal
点此跳转
<vxe-modal
id="myModal6"
:value="true"
size="small"
:show-zoom="true"
resize
show-footer
fullscreen
esc-closable
width="70vw"
height="60vh"
remember
transfer
draggable
@hide="closeDialog"
@close="closeDialog"
>
<template #footer>
<a-button type="primary" @click="closeDialog">
关闭
</a-button>
</template>
<template #title>
总检诊台
</template>
<template #default>
aaaaaaaa
</template>
</vxe-modal>
// @hide的作用是使用Esc方式退出时,会造成打不开的问题,所以需要对value绑定值进行重置
closeDialog() {
this.$emit('closeDetailDialog');
}
24.2 vxe-table表格
使用场景:解决数据量大,表格卡顿的问题
<vxe-table
:column-config="{resizable: true}"
ref="vxeTableRef"
class="vxeTableClass"
:data="peopleTableData"
:loading="peopleTableLoading"
:max-height="cardHeight - 190"
show-overflow
:row-class-name="rowClassName"
:row-config="{isHover: true}"
:checkbox-config="{trigger: 'row', highlight: false, range: false}"
@checkbox-all="selectAllEvent"
@checkbox-change="selectChangeEvent"
@cell-dblclick="dblClick"
>
<template #empty>
<a-empty />
</template>
<vxe-table-column field="department" title="部门" width="110">
<template #default="{row}">
<a-tooltip>
<template slot="title">
{{ row.department }}
</template>
<span class="displayClass">
{{ row.department }}
</span>
</a-tooltip>
</template>
</vxe-table-column>
</vxe-table>
// API含义
@cell-dblclick="dblClick" //双击的回调
:column-config="{resizable: true}" // 列拖动
:max-height="cardHeight - 190" // show-overflow 两个组合使用,固定高度
:row-class-name="rowClassName" // 设置行的css样式,通过class名来进行修改 -> <style></style>
:row-config="{isHover: true}" // 鼠标覆盖行变色
:checkbox-config="{trigger: 'row', highlight: false, range: false}" // 点击行选中,选中时高亮显示,按住ctrl可以拖动选中
@checkbox-all="selectAllEvent" // 全选按钮回调
@checkbox-change="selectChangeEvent" // 选择框 点击回调
24.3 vxe-table合并列
<vxe-table
:span-method="mergeRowMethod"
>
mergeRowMethod({ row, _rowIndex, column, visibleData }) {
const fields = ['patName']; // 需要合并的列的字段
const cellValue = row[column.property];
if (cellValue && fields.includes(column.property)) {
const prevRow = visibleData[_rowIndex - 1];
let nextRow = visibleData[_rowIndex + 1];
if (prevRow && prevRow[column.property] === cellValue) {
return { rowspan: 0, colspan: 0 };
} else {
let countRowspan = 1;
while (nextRow && nextRow[column.property] === cellValue) {
nextRow = visibleData[++countRowspan + _rowIndex];
}
if (countRowspan > 1) {
return { rowspan: countRowspan, colspan: 1 };
}
}
}
}
25、解决a-toolTip a-popover等浮窗组件上下浮动的问题
transition-name=""
26、$ c o n f i r m 、 confirm、 confirm、message提示换行
26.1 ElementUi $message提示换行
const arr = ['测试一', '测试二', '测试三'];
(this as any).$message({
dangerouslyUseHTMLString: true,
message: arr.join(' <br/> '),
type: 'info'
});
26.2 Antd $ m e s s a g e 、 message、 message、confirm提示换行
const arr = ['测试一', '测试二', '测试三'];
that.$message.open({
type: 'error',
content: h => {
return h(
'div',
{
domProps: {
innerHTML: arr.join(' <br/> ')
}
},
[]
);
}
});
const arr = ['测试一', '测试二', '测试三'];
this.$confirm({
title: '是否继续操作?',
content: h => {
return h(
'div',
{
domProps: {
innerHTML: arr.join(' <br/> ')
}
},
[]
);
},
onOk() {},
onCancel() {}
});
27、Echarts图表转Base64
const echartBase64 = (document as any).getElementById('echartId').getElementsByTagName('canvas')[0].toDataURL()
28、计算字符串的宽度(css:width)
使用场景:计算输入框中字符串的width,如果>=输入框的width,则显示tooltip全部文字
<a-tooltip>
<span v-if="computeShowTip(inputContent)" slot="title">
{{ inputContent }}
</span>
<input v-model="item4.inputContent" />
</a-tooltip>
computeShowTip(inputContent){
const dom = document.createElement('span');
dom.style.display = 'inline-block';
dom.textContent = inputContent;
document.body.appendChild(dom);
const width = dom.clientWidth;
document.body.removeChild(dom);
return width >= 90 // 假设输入框的width:90px
}
29、获取递归组件、嵌套组件的DOM --> vue-ref
使用场景:1、使用递归组件时,只能获取到第一次渲染的ref,后续递归出的组件或取不到,此时使用vue-ref插件。2、获取父子组件、兄弟组件的ref
// npm安装
npm install vue-ref --save
// main.js中
import ref from "vue-ref"
Vue.use(ref, { name: "ant-ref" }) // name是自定义api名称
父组件
provide() {
return {
setChildrenRef: (name, ref) => {
this.$set(this.domMap,name,ref)
},
getChildrenRef: name => {
return this[name];
}
}
},
data() {
return {
domMap: {}
}
}
子组件
<a-input v-model="input" v-ant-ref="dom => setChildrenRef('inpitItemRef', dom )" />
inject: ['setChildrenRef'], // 用到父组件的哪个方法就引用哪个
这样加载页面时,子组件中的v-ant-ref指令会调用父组件的方法,把子组件中的dom存储到父组件的domMap中.
vue-ref使用案例:点击链接跳转
30、antd 日期选择器-开始日期< 结束日期
<a-date-picker
v-model="searchForm.startRegTime"
:disabled-date="regDisabledStartDate"
/>
<a-date-picker
v-model="searchForm.endRegTime"
:disabled-date="regDisabledEndDate"
/>
regDisabledStartDate(startValue: any) {
const endValue = this.searchForm.endRegTime;
if (!startValue || !endValue) {
return false;
}
const data = new Date(endValue).getTime();
return data < startValue.valueOf();
}
regDisabledEndDate(endValue: any) {
const startValue = this.searchForm.startRegTime;
if (!startValue) {
return false;
}
const data = new Date(startValue).getTime();
return data >= endValue.valueOf();
}
31、图片点击放大功能
效果图
点击前
点击图片后
1、安装依赖
npm install v-viewer --save
2、配置main.js
import Viewer from 'v-viewer'
import 'viewerjs/dist/viewer.css'
Vue.use(Viewer)
Viewer.setDefaults({
Options: { 'inline': true, 'button': true, 'navbar': true, 'title': false, 'toolbar': true, 'tooltip': true, 'movable': true, 'zoomable': true, 'rotatable': true, 'scalable': true, 'transition': true, 'fullscreen': true, 'keyboard': true, 'url': 'data-source' }
})
3、应用
<a-tab-pane
key="2"
tab="预览"
force-render
>
<a-icon type="exclamation-circle" />
图片缩略图,点击图像查看大图
<viewer
:images="remakeFileList"
style="display: flex;"
>
<!-- remakeFileList必须是数组!! -->
<a-row>
<a-col
v-for="src in remakeFileList"
:key="src.filePath"
:span="8"
>
<img
:src="src.filePath"
:title="'【'+src.orderName+'】'+src.fileName"
style="height: 100%;width: 100%;;cursor: pointer;"
/>
</a-col>
</a-row>
</viewer>
</a-tab-pane>
32、Vue动态组件 component :is的使用
33、iframe实现缓存
33.1、 keep-alive缓存不了iframe界面原因
(1)vue中的keep-alive
【1】原理:Vue 的缓存机制并不是直接存储 DOM 结构,而是将 DOM 节点抽象成了一个个 VNode节点。因此,Vue 的 keep-alive 缓存也是基于 VNode节点 而不是直接存储 DOM 节点。在需要渲染的时候从Vnode渲染到真实DOM上。
【2】参数:Keep-alive 组件提供了 include 和 exclude 两个属性,允许组件有条件的进行缓存。
include: 字符串或正则表达式。只有匹配的组件会被缓存。
exclude: 字符串或正则表达式。任何匹配的组件都不会被缓存。
【3】Keep-alive 组件提供了两个生命钩子函数,分别是 activated 和 deactivated 。
activated :当页面存在缓存的时候执行该函数。
deactivated :在页面结束时触发该方法,可清除掉滚动方法等缓存。
(2)iframe中keep-alive机制失效原因:iframe页里的内容并不属于节点的信息,所以使用keep-alive依然会重新渲染iframe内的内容。而且iframe每一次渲染就相当于打开一个新的网页窗口,即使把节点保存下来,在渲染时iframe页还是刷新的。
33.2、 解决思路
判断当前页面是普通页面还是iframe页面,让普通页面走route-view,用v-if控制显隐,让iframe走动态组件(component :is),组件指向iframe文件,用v-show控制显隐,通过v-show不销毁组件的方式,来实现缓存iframe的效果
33.3、核心代码
layout文件
<a-layout-content>
<!-- 非iframe页面 -->
<keep-alive :include="cacheViewString">
<router-view v-if="!isIframe" :key="getRoute" style="height:100%;" />
</keep-alive>
<!-- iframe页面 -->
<div v-show="isIframe">
<component
v-for="item in iframeList"
:key="item"
is="iframeComponentName"
v-show="isIframe && $route.path.indexOf(item) > -1"
/>
</div>
</a-layout-content>
// 指向iframe文件
import iframeComponentName from '@/views/oh/OhSystemIframe.vue';
@Component({
components: {
iframeComponentName
}
})
export default class Layout extends Vue {
iframeList:any = []; // iframe菜单集合
@Watch('$route')
onRouteChange(route:any) {
// 根据路由判断当前页面是否是iframe --> 判断规则视情况而定
this.renderIframeList(route.path)
}
renderIframeList(path:any) {
if (!path.includes('oh_iframe')) return
const notExit = this.iframeList.findIndex((item:any) => item === path) === -1
if (notExit) {
this.iframeList.push(path)
}
}
get isIframe() {
// iframe标识:当前页面是否是iframe
/**
注意:判断规则视情况而定
**/
return this.$route.path.includes('oh_iframe');
}
}
OhSystemIframe文件
<template>
<div class="peis-body__main">
<div class="peis-body">
<iframe
id="ohIframe"
:src="iframeSrc"
frameborder="0"
></iframe>
</div>
</div>
</template>
以下ts代码为拼接iframeSrc的过程,可忽略,但有一个知识点 给iframe传参:postSession()
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
@Component({
name: 'OhSystemIframe',
components: {},
})
export default class OhSystemIframe extends Vue {
iframeSrc: any = '';
mounted() {
this.renderIframeSrc();
}
renderIframeSrc() {
/* 拼接iframe地址 */
const sysFlag = '/oh/#'; // 职业病系统标识
const query = '?systemFrom=peis'; // 增加体检系统挂载标识
let targetUrl;
// 本地环境
if (window.location.href.includes('localhost')) {
const ohHost = 'http://localhost:9528/#'; // 本地职业病项目地址 -->确保本地已启动职业病的前端项目
targetUrl = ohHost + window.location.href.split('/#/oh_iframe')[1];
} else {
// 测试环境
targetUrl = window.location.href.split('/peis/#/oh_iframe/')[0] + sysFlag + this.$route.path.split('/oh_iframe')[1];
}
this.iframeSrc = targetUrl + query;
// this.postSession(); // 给职业病iframe传参 暂时用不到
}
postSession() {
// 传参方式
const data = {
selectedSystem: { deptCode: '72', deptId: '91', deptName: '健康管理中心', orgId: '10033', sysytemId: '100000', systemName: '职业病', url: 'oh/', userSysId: '5391553094598134274', userSysName: '职业病体检主任' },
};
this.$nextTick(() => {
const iframe: any = document.getElementById('ohIframe');
iframe.onload = () => {
iframe.contentWindow.postMessage(JSON.stringify(data), '*');
};
});
}
// 接收参数方式
// window.addEventListener('message', (data: any) => {
/* 挂载系统时 接收传入的参数 */
// data
// });
}
</script>
34、vue-quill-editor富文本编辑器
35、ant-design-vue modal可拖动
新建文件modalDrag.js注册拖动指令v-drag-modal
import Vue from 'vue';
/* 调用示例
<a-modal v-drag-modal :visible="previewVisible" :footer="null" @cancel="handleCancel">
<img alt="example" style="width: 100%" :src="previewImage" />
</a-modal> */
// v-drag-modal: 弹窗拖拽
Vue.directive('drag-modal', (el, binding, vnode, oldVnode) => {
Vue.nextTick(() => {
const { visible, destroyOnClose } = vnode.componentInstance;
if (!visible) return;
const isThemeModal = el.classList.contains('grid-theme');
const dialogHeaderEl = isThemeModal ? el.querySelector('.ant-tabs-bar') : el.querySelector('.ant-modal-header');
const dragDom = isThemeModal ? el.querySelector('.ant-modal') : el.querySelector('.ant-modal');
// dialogHeaderEl.style.cursor = 'move';
dialogHeaderEl.style.cssText += ';cursor:move;';
// dragDom.style.cssText += ';top:0px;'
// 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
const sty = (function() {
if (window.document.currentStyle) {
return (dom, attr) => dom.currentStyle[attr];
} else {
return (dom, attr) => getComputedStyle(dom, false)[attr];
}
})();
dialogHeaderEl.onmousedown = e => {
// 禁止选中文字,防止拖拽时弹框粘鼠标
document.onselectstart = function() {
return false;
};
// 鼠标按下,计算当前元素距离可视区的距离
const disX = e.clientX - dialogHeaderEl.offsetLeft;
const disY = e.clientY - dialogHeaderEl.offsetTop;
const screenWidth = document.body.clientWidth; // body当前宽度
const screenHeight = document.documentElement.clientHeight; // 可见区域高度(应为body高度,可某些环境下无法获取)
const dragDomWidth = dragDom.offsetWidth; // 对话框宽度
const dragDomheight = dragDom.offsetHeight; // 对话框高度
const minDragDomLeft = dragDom.offsetLeft;
const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth - (isThemeModal ? 10 : 0);
const minDragDomTop = dragDom.offsetTop;
const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomheight - (isThemeModal ? 10 : 0);
// 获取到的值带px 正则匹配替换
let styL = sty(dragDom, 'left');
let styT = sty(dragDom, 'top');
// 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
if (styL.includes('%')) {
// eslint-disable-next-line no-useless-escape
styL = +document.body.clientWidth * (+styL.replace(/\%/g, '') / 100);
// eslint-disable-next-line no-useless-escape
styT = +document.body.clientHeight * (+styT.replace(/\%/g, '') / 100);
} else {
styL = +styL.replace(/\px/g, '');
styT = +styT.replace(/\px/g, '');
}
// dialogHeaderEl 此处直接使用el 防止鼠标移动太快脱离范围,导致无法拖动
el.onmousemove = function(e) {
// 通过事件委托,计算移动的距离
let left = e.clientX - disX;
let top = e.clientY - disY;
// 边界处理
if (-left > minDragDomLeft) {
left = -minDragDomLeft;
} else if (left > maxDragDomLeft) {
left = maxDragDomLeft;
}
if (-top > minDragDomTop) {
top = -minDragDomTop;
} else if (top > maxDragDomTop) {
top = maxDragDomTop;
}
// 移动当前元素
dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;`;
};
el.onmouseup = function(e) {
el.onmousemove = null;
dialogHeaderEl.onmouseup = null;
};
document.onmouseup = function(e) {
el.onmousemove = null;
dialogHeaderEl.onmouseup = null;
// 在抬起鼠标之后,取消禁用选择文字
document.onselectstart = function() {
return true;
};
};
};
});
});
在main.js中注册全局
import '@/utils/modalDrag.js
<a-modal
v-drag-modal
:get-container="() => $refs.aff"
:title="contrastDetailFlag?'对照详情':'项目对照'"
:visible="true"
append-to-body
:width="contrastDetailFlag?'55vw !important':'80vw !important'"
@cancel="closeDialog"
>
</a-modal>
效果图
36、组件库monorepo架构
monorepo架构中各模块独立方便管理,以一下测试项目为例,packages中的base存放组件,example 用于测试。此测试项目有三个组件。
1、目录结构
.
├── CHANGELOG.md 更新记录
├── README.md 项目说明文档
├── package.json 项目配置文档
├── packages 多模块配置目录
│ ├── base 模块-base
│ │ ├── dist base的打包产出
│ │ ├── package.json base的项目配置
│ │ ├── rollup.config.js base的打包配置项
│ │ └── src base的源码
│ └── example 配套的测试模块
│ ├── README.md
│ ├── babel.config.js
│ ├── jsconfig.json
│ ├── package.json
│ ├── public
│ ├── src
│ └── yarn.lock
├── pnpm-lock.yaml 项目的依赖管理文件
└── pnpm-workspace.yaml monorepo的配置文件
1、pnpm install 安装依赖
2 、执行pnpm dev 与 pnpm serve
3、项目预览
语法以及架构自己研究,这套架构方便组件维护,能解决一个组件一套项目不方便维护的问题,组件引用也方便:
import { ModuleA } from 'monorepo-test';
重点来了 研究~自己研究去
37、数字输入框
1.隐藏输入框尾部的箭头
<style scoped>
>>> input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none !important;
}
</style>
2.只能输入正数负数以及一个小数点
<!-- 无法输入汉字、字母、符号等非数字 -->
<a-input
v-model="aaa"
type="number"
onmousewheel="return false"
onkeypress="return(/[\d\.]/.test(String.fromCharCode(event.keyCode)))"
/>
3.只能输入大于等于0的正整数
<a-input
type="number"
style="width: 63px;"
v-model="reRegCheckTime"
:min="0"
oninput="value=value.replace('.', '',).replace('-', '',)"
/>
4.只能输入大于0的正整数
<a-input
type="number"
style="width: 63px;"
v-model="reRegCheckTime"
:min="0"
oninput="value=value.replace('.', '',).replace('-', '',).replace(/^0|[^0-9]/g,'')"
/>
38、a-tree右键菜单-屏蔽默认事件
<a-tree id="peis_org_group_tree_datas">
<!-- 右键菜单 -->
<template #title="{ key: treeKey, text, ...node }">
<a-dropdown
v-if="node.dataRef.attributes ==='group'"
:trigger="['contextmenu']"
>
<span>{{ text }}</span>
<template #overlay>
<a-menu @click="hideTreeNode(node.dataRef)">
<a-menu-item key="1"><a-icon type="eye-invisible" />隐藏分组</a-menu-item>
</a-menu>
</template>
</a-dropdown>
<span v-else>
{{ text }}
</span>
</template>
</a-tree>
mounted() {
const handlerDom = document.getElementById('peis_org_group_tree_datas');
handlerDom.oncontextmenu = function (e) {
e.stopPropagation()
}
},
998、$env:NODE_OPTIONS=“–max-old-space-size=514096”
999、vscode eslint setting.json && .eslintrc.js 配置(备份)
vscode配置
安装插件
Prettier - Code formatter
ESLint
ESLint Chinese Rules
Monokai Dimmed+Vibrant
eslint
{
// vscode默认启用了根据文件类型自动设置tabsize的选项
"editor.detectIndentation": false,
// html格式化-对属性进行换行。
"html.format.wrapAttributes": "force-expand-multiline",
// 重新设定tabsize
"editor.tabSize": 2,
// #让prettier使用eslint的代码格式进行校验
// #去掉代码结尾的分号
"prettier.semi": false,
"prettier.trailingComma": "none",
// #使用带引号替代双引号
"prettier.singleQuote": true,
// #让函数(名)和后面的括号之间加个空格
"javascript.format.insertSpaceBeforeFunctionParenthesis": false,
"vetur.format.defaultFormatterOptions": {
"wrap_attributes": "force-aligned",
"prettier": {
"semi": false, // 格式化时不加分号
"singleQuote": true, // 格式化时使用单引号
"trailingComma": "none", // 格式化时末尾不添加逗号
},
"vetur.format.defaultFormatter.html": "prettier", //格式化.vue中html
"js-beautify-html": {
// "wrap_attributes": "aligned-multiple"
"wrap_attributes": "force-expand-multiline" //属性强制换行对齐
// "wrap_attributes": "force-aligned" //属性强制折行对齐
}
},
"editor.suggestSelection": "first",
"editor.codeActionsOnSave": {
"source.fixAll.prettier": "explicit"
},
"editor.fontLigatures": false,
/* 自定义主题颜色 */
"editor.tokenColorCustomizations": {
"[Monokai Dimmed+Vibrant]": {
"keywords": "#ff00ff", //关键字
"types": "#42b983", //类型定义
"numbers": "#00e1ff", //数字
"strings": "#FFFF00", // 字符串的颜色,
// "variables": "#9814ef", //变量
// "comments": "#beb9b9", //注释
// "functions": "#f10070f5", //函数
"textMateRules": [
{
"scope": "support.type.property-name.json", //JSON -> key
"settings": {
"foreground": "#00ff40",
// "fontStyle": "italic bold "
}
},
{
"scope": "string.quoted.double.json", //JSON->Value
"settings": {
"foreground": "#ff00f2",
// "fontStyle": "italic bold"
}
},
{
"scope": "meta.object.member.js", //声明的变量
"settings": {
"foreground": "#ffffff",
// "fontStyle": "italic bold "
}
},
{
"scope": "meta.block.js",
"settings": {
"foreground": "#00fff2", // 引用的变量名
// "fontStyle": "italic bold "
}
},
{
"scope": "storage.type.js",
"settings": {
// "foreground": "#2e09ff",
"fontStyle": " bold "
}
},
{
"scope": "keyword.control", //if ,else, try 等控制符
"settings": {
"foreground": "#ff00dd",
"fontStyle": "italic bold "
}
},
{
"scope": "entity.name.tag", // html标签
"settings": {
// "foreground": "#ff00f2",
"fontStyle": "bold italic"
}
},
{
"scope": "entity.other.attribute-name", // html标签的属性
"settings": {
// "foreground": "#0066ff",
"fontStyle": "bold italic"
}
},
{
"scope": "variable.other.object",
"settings": {
"foreground": "#0099ff",
"fontStyle": ""
}
},
{
"scope": "keyword.operator", // 运算符
"settings": {
"foreground": "#ff0000",
// "fontStyle": "bold",
}
}
]
}
},
"[vue]": {
"editor.defaultFormatter": "octref.vetur"
},
"[jsonc]": {
"editor.defaultFormatter": "vscode.json-language-features"
},
"[html]": {
"editor.defaultFormatter": "vscode.html-language-features"
},
"[less]": {
"editor.defaultFormatter": "vscode.css-language-features"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
// "editor.smoothScrolling": true, //使编辑器滚动变平
// 自定义vscode面板颜色
"workbench.colorCustomizations": {
// "tab.activeBackground": "#0f0f0f", // 活动选项卡的背景色
// "activityBar.background": "#0f0f0f", //活动栏背景色
// "sideBar.background": "#0f0f0f", //侧边栏背景色
// "activityBar.foreground": "#23f8c8", //活动栏前景色(例如用于图标)
// "editor.background": "#0f0f0f", //编辑器背景颜色
// "editor.foreground":"#ff0000", //编辑器默认前景色
// "editor.findMatchBackground":"#f83123", //当前搜索匹配项的颜色
// "editor.findMatchHighlightBackground":"#43ac93", //其他搜索匹配项的颜色
// "editor.lineHighlightBackground":"#ff0000", //光标所在行高亮文本的背景颜色
"editor.selectionBackground": "#888888", //编辑器所选内容的颜色
// "editor.selectionHighlightBackground":"#ff0000", //与所选内容具有相同内容的区域颜色
// "editor.rangeHighlightBackground":"#ff0000", //突出显示范围的背景颜色,例如 "Quick Open" 和“查找”功能
// "editorBracketMatch.background":"#edfc6a98", //匹配括号的背景色
"editorCursor.foreground": "#00ff15", //编辑器光标颜色
// "editorGutter.background":"#ff0000", //编辑器导航线的背景色,导航线包括边缘符号和行号
// "editorLineNumber.foreground":"#ff0000", //编辑器行号颜色
// "sideBar.foreground":"#ff0000", //侧边栏前景色
// "sideBarSectionHeader.background":"#ff0000", //侧边栏节标题的背景颜色
// "statusBar.background":"#ff0000", //标准状态栏背景色
// "statusBar.noFolderBackground":"#ff0000", //没有打开文件夹时状态栏的背景色
// "statusBar.debuggingBackground":"#ff0000", //调试程序时状态栏的背景色
// "tab.activeForeground":"#ff0000", //活动组中活动选项卡的前景色
// "tab.inactiveBackground":"#ff0000", //非活动选项卡的背景色
// "tab.inactiveForeground":"#ff0000" // 活动组中非活动选项卡的前景色
},
"security.workspace.trust.untrustedFiles": "open",
"git.autofetch": true,
"explorer.confirmDelete": false,
"[css]": {
"editor.defaultFormatter": "vscode.css-language-features"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"terminal.integrated.profiles.windows": {
"PowerShell": {
"source": "PowerShell",
"icon": "terminal-powershell"
},
"Command Prompt": {
"path": [
"${env:windir}\\Sysnative\\cmd.exe",
"${env:windir}\\System32\\cmd.exe"
],
"args": [],
"icon": "terminal-cmd"
},
"Git Bash": {
"source": "Git Bash"
},
"Windows PowerShell": {
"path": "C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"
}
},
"eslint.validate": [
"vue",
"javascript",
"javascriptvue",
],
// "eslint.autoFixOnSave": true,
"terminal.integrated.defaultProfile.windows": "PowerShell",
"terminal.integrated.fontFamily": "MesloLGM Nerd Font",
"javascript.updateImportsOnFileMove.enabled": "always",
"git.confirmSync": false,
"eslint.format.enable": true,
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
"gitlens.graph.minimap.additionalTypes": [
"stashes",
"remoteBranches",
"tags",
"localBranches"
],
"workbench.editorAssociations": {
"*.dat": "default"
},
"editor.fontSize": 15.5,
"sonarlint.rules": {
"typescript:S3776": {
"level": "off"
}
},
"auto-close-tag.disableOnLanguage": [],
"[dotenv]": {
"editor.defaultFormatter": "foxundermoon.shell-format"
},
"gitlens.ai.experimental.provider": "anthropic",
"gitlens.ai.experimental.openai.model": "gpt-4-turbo-preview",
"gitlens.ai.experimental.anthropic.model": "claude-3-opus-20240229",
"workbench.iconTheme": "material-icon-theme",
"editor.minimap.showSlider": "always",
"editor.minimap.size": "fill",
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"vetur.completion.scaffoldSnippetSources": {
"workspace": "💼",
"user": "🗒️",
"vetur": "✌"
},
"vetur.format.defaultFormatter.js": "vscode-typescript",
"vetur.format.defaultFormatter.html": "js-beautify-html",
"settingsSync.ignoredExtensions": [
"shalldie.background"
],
"background.enabled": false,
"background.useFront": false,
"editor.guides.bracketPairs": true,
"powermode.presets": "exploding-rift",
"powermode.explosions.backgroundMode": "image",
"powermode.combo.threshold": 100,
"tabnine.experimentalAutoImports": true,
"workbench.colorTheme": "Monokai Dimmed+Vibrant",
"git.openRepositoryInParentFolders": "always",
"editor.gotoLocation.multipleDefinitions": "goto",
"diffEditor.ignoreTrimWhitespace": false
}
eslintrc
module.exports = {
root: true,
env: {
browser: true,
node: true,
es6: true
},
extends: ['eslint:recommended', 'plugin:vue/recommended', '@vue/standard', '@vue/typescript'],
rules: {
'camelcase': 'off',
'no-console': process.env.NODE_ENV === 'production' ? 'off' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'off' : 'off',
'no-prototype-builtins': 'off',
'no-unused-vars': 'off',
semi: 'off',
'space-before-function-paren': [2, 'never'],
// 'vue/html-closing-bracket-newline': 'off',
'vue/html-self-closing': 'off',
'vue/no-v-html': 'off',
'vue/array-bracket-spacing': 'error',
'vue/arrow-spacing': 'error',
'vue/block-spacing': 'error',
'vue/brace-style': 'error',
'vue/camelcase': 'error',
'vue/comma-dangle': 'error',
'vue/component-name-in-template-casing': 'error',
'vue/eqeqeq': 'error',
'vue/key-spacing': 'error',
'vue/match-component-file-name': 'error',
'vue/object-curly-spacing': 'error',
"vue/max-attributes-per-line": ["error", {
"singleline": 2,
"multiline": {
"max": 1,
"allowFirstLine": false
}
}],
"vue/html-indent": ["error", 2, {
"attribute": 1,
"baseIndent": 1,
"closeBracket": 0,
"alignAttributesVertically": true,
"ignores": []
}],
'vue/no-parsing-error': [2, { 'x-invalid-end-tag': false }]
},
globals: {
_: true,
dayjs: true,
$: true,
echarts: true,
Echarts: true
},
parserOptions: {
parser: '@typescript-eslint/parser'
},
overrides: [
{
files: ['**/__tests__/*.{j,t}s?(x)', '**/tests/unit/**/*.spec.{j,t}s?(x)'],
env: {
jest: true
}
}
],
ignorePatterns: ['src/assets/font/iconfont.js']
};