js执行 (!(~+[])+{})[--[~+""][+[]]*[~+[]] + ~~!+[]]+({}+[])[[~!+[]]*~+[]]
有惊喜!!!
- 未初始化的数据,例如设置对象不存在的属性,用this.$set(object, attribute, value)方法设置,否则不是响应式数据,组件不根据数据的改变重新渲染页面;
- 如果组件绑定的数据初始化后,组件还在工作,可用v-if来销毁组件;
- 为组件设置key,key改变组件会重新创建加载;
- Dialog 对话框 使用 ueditor富文本 时,关闭时要调用 $('#ueditor_textarea_editorValue').remove() 方法清除富文本缓存,否则二次以后加载失败;
- ueditor富文本 使用:
1) 下载ueditor富文本文件夹放到/public路径下,在/public/index.html中引入
2) /src/components/editor路径下创建组件index.vue<script src='<%= BASE_URL %>neditor/neditor.config.js'></script> <script src='<%= BASE_URL %>neditor/neditor.all.js'></script> <script src='<%= BASE_URL %>neditor/neditor.service.js'></script> <script src='<%= BASE_URL %>neditor/neditor.parse.js'></script> <script src='<%= BASE_URL %>neditor/i18n/zh-cn/zh-cn.js'></script>
<template> <div> <div :id="this.id"></div> </div> </template> <script> export default { name: 'editor', props: { id: { type: String }, defaultMsg: { type: String, default: '' }, attach: { type: String, default: '' }, config: { type: Object, default: { initialFrameWidth: '100%', initialFrameHeight: '350' } } }, data() { return { ue: null } }, mounted() { this.ue = UE.getEditor(this.id, this.config) this.ue.addListener('ready', () => { this.ue.setContent(this.defaultMsg) }) this.ue.addListener('contentChange', this.contentChange); }, methods: { contentChange() { this.$emit('editorContentChange', this.ue.getContent(), this.attach); this.$emit('editorContentTxtChange', this.ue.getContentTxt(), this.attach);// 获取编辑器中的纯文本内容,没有段落格式 this.$emit('editorPlainTxtChange', this.ue.getPlainTxt(), this.attach);// 得到编辑器的纯文本内容,但会保留段落格式 }, getUeContent() { return this.ue.getContent() }, setUeContent(v) { return this.ue.setContent(v) } }, beforeDestroy() { this.ue.destroy() this.ue = null } } </script>
3) 上传文件修改neditor.service.js文件及neditor.config.js文件
4) 开启表情本地化 emotionLocalization:true,下载表情图片文件,放到 /public/neditor/dialogs/emotion/images/ 路径下
5) 使用:<editor ref="resourceContent" :id="'editor'" :default-msg="template.resourceContent" :config="editorConfig"> </editor> import editor from '@/components/editor/index.vue' export default { components: { editor }, data() { return { editorConfig: { initialFrameWidth: '100%', initialFrameHeight: 350, zIndex: 3000 }, } } }
- Dialog 对话框 可使用 Tabs 标签页 或 Steps 步骤条 来分页签,校验时可用v-if来设置显示,也可以在点击下一步时,调用验证当前页表单项方法来完成校验;
- 设置 Tree 树结构 树节点选中状态高亮色的设置,
.el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content { background-color: #a6d1ff; } /deep/ .el-tree-node > .el-tree-node__children { overflow: visible !important; }
- 缩略图,用上传照片墙方式,但是只显示一张图片
<el-form-item label="缩略图" prop="thumbnail"> <el-upload class="avatar-uploader" :class="{hide: template.thumbnailUrl}" :action="uploadUrlCms" :data="{'path': siteUploadPath, 'siteId': template.siteId}" :accept="$store.state.common.imageAccepts" :file-list="thumbnailList" list-type="picture-card" :on-success="handleAvatarSuccess" :on-error="handleAvatarError" :before-upload="beforeAvatarUpload" :on-preview="handlePictureCardPreview" :on-remove="handleAvatarRemove"> <i class="el-icon-plus avatar-uploader-icon"></i> </el-upload> <el-dialog :visible.sync="dialogVisiblePicture" :append-to-body="true"> <img width="100%" :src="dialogImageUrl" alt=""> </el-dialog> </el-form-item>
/*缩略图*/ .avatar-uploader .el-upload { border: 1px dashed #d9d9d9; border-radius: 6px; cursor: pointer; position: relative; overflow: hidden; width: 178px; height: 178px; } .avatar-uploader .el-upload:hover { border-color: #409EFF; } .avatar-uploader-icon { font-size: 28px; color: #8c939d; width: 178px; height: 178px; line-height: 178px; text-align: center; } .avatar { width: 178px; height: 178px; display: block; } .avatar-uploader .el-upload-list__item { width: 178px; height: 178px; } .avatar-uploader .el-upload-list__item .el-upload-list__item-status-label { display: none; } .hide .el-upload--picture-card { display: none; }
- element 使用竖向表格
<table class="mailTable"> <tr> <td class="column"></td> <td></td> <td class="column"></td> <td></td> </tr> </table>
.mailTable, .mailTable tr, .mailTable tr td { border:1px solid #E6EAEE; } .mailTable { font-size: 12px; color: #71787E; } .mailTable tr td { border:1px solid #E6EAEE; width: 150px; height: 35px; line-height: 35px; box-sizing: border-box; padding: 0 10px; } .mailTable tr td.column { background-color: #EFF3F6; color: #393C3E; }
- 替换element图标
<i class="not-evaluated"></i> .not-evaluated{ background: url(../img/notEvaluated.jpg) center no-repeat; // 要替换的图片 background-size: cover; } .not-evaluated:before{ content: "替"; // 占位文字,必须! font-size: 14px; line-height: 14px; visibility: hidden; // 隐藏占位文字 }
- 移动端H5页面注意
// 在<head>标签插入 <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0"> // width=device-width 应用程序的宽度和屏幕的宽度是一样的 // height=device-height 应用程序的高度和屏幕的高是一样的 // initial-scale=1.0 应用程序启动时候的缩放尺度(1.0表示不缩放) // minimum-scale=1.0 用户可以缩放到的最小尺度(1.0表示不缩放) // maximum-scale=1.0 用户可以放大到的最大尺度(1.0表示不缩放) // user-scalable=no 用户是否可以通过他的手势来缩放整个应用程序,使应用程序的尺度发生一个改变(yes/no)
- el-date-picker时间选择范围控制
<el-form-item> <el-date-picker :size="searchSize" v-model.trim="listQuery.addTime" type="date" placeholder="选择时间" value-format="yyyy-MM-dd" @change="dateChange" :picker-options="pickerOptions"> </el-date-picker> </el-form-item> pickerOptions: { // disabledDate是一个函数,参数是当前选中的日期值,这个函数需要返回一个Boolean值, disabledDate: (time) => { // 如果函数处理比较简单,可以直接在这里写逻辑方法 // return time.getTime() < Date.now() - 8.64e7 // 如果函数里处理的数据比较麻烦,也可以单独放在一个函数里,避免data数据太臃肿 return this.dealDisabledDate(time) } }, dealDisabledDate (time) { // time.getTime是把选中的时间转化成自1970年1月1日 00:00:00 UTC到当前时间的毫秒数 // Date.now()是把今天的时间转化成自1970年1月1日 00:00:00 UTC到当前时间的毫秒数,这样比较好比较 // return的值,true是不可以操作选择,false可以操作选择,比如下面这个判断就只能选择今天之后的时间 return time.getTime() > Date.now() // 这里减8.64e7的作用是,让今天的日期可以选择,如果不减的话,今天的日期就不可以选择,判断中写<= 也是没用的,一天的毫秒数就是8.64e7 // return time.getTime() <= Date.now() // return time.getTime() < Date.now() - 8.64e7 },
- el-table自定义表头/表尾及列合并/行合并
<el-table :data="dailyPriceSurveyData" v-loading.body="listLoading" border highlight-current-row :span-method="spanMethod" :summary-method="getAvgMOM" show-summary> <el-table-column label="日价格调查表" align="center"> <el-table-column :label="'农业信息网络中心&' + tableAddTime + '(单位: 元/公斤 元/升)'" :render-header="renderHeader"> <el-table-column align="center" prop="typeName" label="类别"> </el-table-column> <el-table-column align="center" prop="productName" label="名称"> </el-table-column> <el-table-column align="center" label="批发价格"> <el-table-column align="center" prop="yesterdayWholesalePrice" label="昨天" :formatter="rounding"> </el-table-column> <el-table-column align="center" prop="todayWholesalePrice" label="今天" :formatter="rounding"> </el-table-column> </el-table-column> <el-table-column align="center" label="零售价格"> <el-table-column align="center" prop="yesterdayRetailPrice" label="昨天" :formatter="rounding"> </el-table-column> <el-table-column align="center" prop="todayRetailPrice" label="今天" :formatter="rounding"> </el-table-column> </el-table-column> <el-table-column align="center" label="备注"> </el-table-column> </el-table-column> </el-table-column> </el-table> reloadList(){ this.listLoading = true; this.dailyPriceSurveyData = []; this.spanArr = []; getDailyPriceSurveyData(this.listQuery).then(response => { this.listLoading = false; this.dailyPriceSurveyData = response.data; this.initSpanArr(); }) }, initSpanArr() { // 定义一个空数组:[] // 定义一个变量:0 // 遍历数据如果有相同数据 在空数组添加一个0(相同数据的起始位加1),不相同在数据push 一个1(变量改成当前索引) let contactDot = 0; this.dailyPriceSurveyData.forEach((item, index) => { if(index === 0){ this.spanArr.push(1) }else{ if(item.typeName === this.dailyPriceSurveyData[index-1].typeName){ this.spanArr[contactDot] += 1; this.spanArr.push(0) }else{ contactDot = index this.spanArr.push(1) } } }) }, spanMethod({ row, column, rowIndex, columnIndex }) { if(columnIndex ===0){ if(this.spanArr[rowIndex]){ return { rowspan:this.spanArr[rowIndex], colspan:1 } }else{ return { rowspan: 0, colspan: 0 } } } }, // render 事件 renderHeader (h,{column}) { // h即为cerateElement的简写,具体可看vue官方文档 let header = column.label.split('&'); return h( 'p', {style: 'margin: 0'}, [ h('p', {style: 'float: left; margin: 0'}, header[0]), h('p', {style: 'float: right; margin: 0'}, header[1]) ], ); }, rounding(row, column, cellValue, index) { if (typeof cellValue == 'number') { return cellValue.toFixed(2); } else { return cellValue; } }, // 自定义表尾 getAvgMOM(param) { const { columns, data } = param; const sums = []; columns.forEach((column, index) => { if (index === 0) { sums[index] = '平均值'; return; } if(column.property.toLowerCase() == 'mom') { const values = data.map(item => Number(item[column.property])); var mom = values.filter((value,index)=>{return !isNaN(value)}); if (mom.length > 0) { sums[index] = (mom.reduce((a, b) => a + b) / mom.length).toFixed(2); } else { sums[index] = '-'; } this.avgMOM = sums[index]; } }); return sums; },
- Blob.js及Export2excel.js导出excel文件,行/列合并等
export2Excel() { if (!this.dailyPriceSurveyData || this.dailyPriceSurveyData.length == 0) { this.$message.error('无导出数据'); return; } require.ensure([], () => { const Export2excel = require('@/excel/Export2excel'); //对应表格输出的title const tHeader1 = ['日价格调查表']; const tHeader2 = ['农业信息网络中心' + this.tableAddTime + '(单位: 元/公斤 元/升)']; const tHeader3 = ['类别', '名称', '批发价格', '', '零售价格', '', '备注']; const tHeader4 = ['', '', '昨天', '今天', '昨天', '今天', '']; // 对应表格输出的数据 const filterVal = ['typeName', 'productName', 'yesterdayWholesalePrice', 'todayWholesalePrice', 'yesterdayRetailPrice', 'todayRetailPrice', 'remark']; const excelData = this.formatJson(filterVal, this.dailyPriceSurveyData); // 添加表头 excelData.unshift(tHeader4); excelData.unshift(tHeader3); excelData.unshift(tHeader2); excelData.unshift(tHeader1); //对应下载文件的名字 Export2excel.exportDailyPriceSurveyData2Excel(excelData, '农产品日价格调查表excel' + new Date().toLocaleDateString().replace(/\//g,'-')); }) }, formatJson(filterVal, jsonData) { return jsonData.map(v => filterVal.map(j => v[j])) }, // Export2excel.js /****************************************修改源码,添加方法start****************************************/ export function exportDailyPriceSurveyData2Excel(jsonData, defaultTitle) { /* original data */ var ws = sheet_from_array_of_arrays(jsonData); // 合并单元格 mergeDailyCell(ws); /* add worksheet to workbook */ writeExcel(ws, defaultTitle); } export function mergeDailyCell(ws) { // 合并单元格 - 表头 ws["!merges"] = [ { s: { // s为开始 c: 0, // col r: 0 // row }, e: { // e为结束 c: 6, r: 0 } }, { s: { c: 0, r: 1 }, e: { c: 6, r: 1 } }, { s: { c: 0, r: 2 }, e: { c: 0, r: 3 } }, { s: { c: 1, r: 2 }, e: { c: 1, r: 3 } }, { s: { c: 2, r: 2 }, e: { c: 3, r: 2 } }, { s: { c: 4, r: 2 }, e: { c: 5, r: 2 } }, { s: { c: 6, r: 2 }, e: { c: 6, r: 3 } } ] mergeDataCell(ws, 5); } /** * 合并单元格 - 数据 * @param ws excel对象 * @param rowNumber 数据取值开始行数 */ export function mergeDataCell(ws, rowStart) { let mergeObj = {}; Object.keys(ws).forEach(key => { // 第rowNumber行取数据 if (key.indexOf('A') != -1 && key.slice(1) >= rowStart) { if(mergeObj.hasOwnProperty(ws[key].v)){ mergeObj[ws[key].v] += 1; ws[key].v = ''; }else{ Vue.set(mergeObj, ws[key].v, 0); } } }) Object.keys(ws).forEach(key => { if (key.indexOf('A') != -1 && key.slice(1) >= rowStart) { if (mergeObj.hasOwnProperty(ws[key].v) && mergeObj[ws[key].v]) { let merge = { s: { c: 0, r: key.slice(1) - 1 }, e: { c: 0, r: key.slice(1) - 1 + mergeObj[ws[key].v] } } ws["!merges"].push(merge); } } }) } /** * 输出Excel * @param ws excel对象 * @param defaultTitle 输出Excel名称 */ export function writeExcel(ws, defaultTitle) { var wb = new Workbook(); var ws_name = "SheetJS"; wb.SheetNames.push(ws_name); wb.Sheets[ws_name] = ws; var wbout = XLSX.write(wb, {bookType: 'xlsx', bookSST: false, type: 'binary'}); var title = defaultTitle || '列表' saveAs(new Blob([s2ab(wbout)], {type: "application/octet-stream"}), title + ".xlsx") } export function exportTenDayPriceSurveyData2Excel(jsonData, defaultTitle) { /* original data */ var ws = sheet_from_array_of_arrays(jsonData); // 合并单元格 mergeTenDayCell(ws); /* add worksheet to workbook */ writeExcel(ws, defaultTitle); } export function mergeTenDayCell(ws) { // 合并单元格 - 表头 ws["!merges"] = [ { s: { // s为开始 c: 0, // col r: 0 // row }, e: { // e为结束 c: 11, r: 0 } }, { s: { c: 0, r: 1 }, e: { c: 11, r: 1 } }, { s: { c: 0, r: 2 }, e: { c: 0, r: 3 } }, { s: { c: 1, r: 2 }, e: { c: 1, r: 3 } }, { s: { c: 2, r: 2 }, e: { c: 6, r: 2 } }, { s: { c: 7, r: 2 }, e: { c: 11, r: 2 } } ] mergeDataCell(ws, 5); } export function exportWeeklyPriceSurveyData2Excel(jsonData, defaultTitle) { /* original data */ var ws = sheet_from_array_of_arrays(jsonData); // 合并单元格 mergeWeeklyCell(ws); /* add worksheet to workbook */ writeExcel(ws, defaultTitle); } export function mergeWeeklyCell(ws) { // 合并单元格 - 表头 ws["!merges"] = [ { s: { // s为开始 c: 0, // col r: 0 // row }, e: { // e为结束 c: 5, r: 0 } }, { s: { c: 0, r: 1 }, e: { c: 5, r: 1 } } ] mergeDataCell(ws, 4); } /****************************************修改源码,添加方法end****************************************/
- js原生打印
默认打印整个页面,需新打开一个页面写入需打印内容// 打印方法 printContent(content) { // console.log(content) // const oldHtml = document.body.innerHTML; // window.document.body.innerHTML = content; // window.print(); // window.document.body.innerHTML = oldHtml; // 重新加载页面,以刷新数据 // window.location.reload() var t = window.open("","打印"); t.document.open(); t.document.write(content); // 延迟打印,防止页面没有加载完成 setTimeout(() => { t.window.print(); t.close() }, 500); },
- element dialog弹窗高度根据内容而改变
/* 弹框既能在视窗居中,又能在内容过多时防止弹框大小超出视窗,还能把滚动限制在body内部从而使得头和尾始终可见 */ /deep/ .el-dialog{ display: flex; flex-direction: column; margin:0 !important; position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); /*height:600px;*/ max-height:calc(100% - 30px); max-width:calc(100% - 30px); } /deep/ .el-dialog .el-dialog__header { padding: 10px 20px; } /deep/ .el-dialog .el-dialog__body{ flex:1; overflow: auto; padding: 10px 20px; } /* 弹窗底部按钮居中 */ /deep/ .el-dialog .el-dialog__footer { padding: 10px 20px; text-align: center; box-sizing: border-box; }
- 使用拼音插件
// 安装 npm install --save pinyin // 使用 <script> import pinyin from 'pinyin'; </script>
使用方法查询文档
- echarts堆叠图总数计算显示
<template> <basic-container> <div class="deyatech-container pull-auto"> <div class="deyatech-header"> <el-form :inline="true" ref="searchForm"> <el-form-item> <el-date-picker :size="searchSize" v-model.trim="dateRange" type="daterange" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" value-format="yyyy-MM-dd"> </el-date-picker> </el-form-item> <el-form-item> <el-button type="primary" icon="el-icon-search" :size="searchSize" @click="searchGetStatisticsInfo">{{$t('table.search')}}</el-button> <el-button icon="el-icon-delete" :size="searchSize" @click="resetSearch">{{$t('table.clear')}}</el-button> </el-form-item> </el-form> </div> <div class="splitBar"></div> <div class="row"> <div class="filter-container"> </div> <div class="className" id="informationStatistics"></div> </div> </div> </basic-container> </template> <script> const echarts = require('echarts/lib/echarts'); require('echarts/lib/chart/bar'); require('echarts/lib/chart/pie'); require('echarts/lib/chart/line'); require('echarts/lib/component/tooltip'); require('echarts/lib/component/legend'); require('echarts/lib/component/toolbox'); require('echarts/lib/component/title'); require('echarts/lib/component/visualMap'); require('echarts/lib/component/dataZoom'); import {mapGetters} from 'vuex'; import { getStatisticsInfo } from '@/api/market/information'; export default { name: 'informationStatistics', data() { return { dateRange: undefined, listQuery: { startDate: undefined, endDate: undefined }, statisticsInfo: undefined, categoryName: [], memberCategoryName: [], seriesData: [], } }, computed: { ...mapGetters([ 'textMap', 'enums', 'closeOnClickModal', 'permission', 'titleMap', 'enums', 'searchSize', 'btnSize' ]) }, created() { this.getStatisticsInfo(); }, methods: { resetSearch() { this.listQuery.startDate = undefined; this.listQuery.endDate = undefined; }, searchGetStatisticsInfo() { if (this.dateRange) { this.listQuery.startDate = this.dateRange[0]; this.listQuery.endDate = this.dateRange[1]; } this.getStatisticsInfo(); }, getStatisticsInfo() { this.categoryName = []; this.memberCategoryName = []; this.seriesData = []; getStatisticsInfo(this.listQuery).then(response => { if (response.status === 200) { this.statisticsInfo = response.data; // 获取echarts series数据 this.getSeriesData(); for (let info of this.statisticsInfo) { // 设置堆叠条分类legend if (this.categoryName.indexOf(info.categoryName) == -1) { this.categoryName.push(info.categoryName); } // 设置X轴xAxis if (this.memberCategoryName.indexOf(info.memberCategoryName) == -1) { this.memberCategoryName.push(info.memberCategoryName); } } // 初始化echarts var e = echarts.init(document.getElementById('informationStatistics')); e.setOption({ title: { text: '市场供求信息统计' }, legend: { data: this.categoryName }, tooltip: { trigger: 'axis' }, toolbox: { show: true, feature: { mark: {show: true}, dataView: {show: true, readOnly: false}, magicType: {show: true, type: ['line', 'bar']}, restore: {show: true}, saveAsImage: {show: true} } }, // calculable: true, /* dataZoom: [ { show: true, start: 0, end: 100 }, { type: 'inside', start: 0, end: 100 }, { show: true, yAxisIndex: 0, filterMode: 'empty', width: 30, showDataShadow: false, left: '6%' } ],*/ xAxis: [ { type: 'category', axisLabel: { interval: 0, rotate: 45 }, data: this.memberCategoryName } ], yAxis: [ { type: 'value' } ], series: this.seriesData }) // 添加点击监听事件,重新计算总数 e.on("legendselectchanged", function (obj) { const option = e._model.option; const series = option.series; const b = obj.selected, d = []; for (let key in b) { if (b[key]) { for (let i = 0; i < series.length; i++) { const changeName = series[i].name; if (changeName == key) { d.push(i); } } } } var fun = function (params) { var value = 0; for (let i = 0; i < d.length; i++) { for (let j = 0; j < series.length; j++) { if (d[i] == j) { value += series[j].data[params.dataIndex] } } } return value } for (let i = 0; i < series.length; i++) { series[i].label.show = false; } for (let i = series.length - 1; i >= 0; i--) { var name = series[i].name; if (obj["selected"][name]) { series[i].label.formatter = fun series[i].label.show = true break; } } e.setOption(e.getOption); }) } else { this.$message.error('数据加载失败') } }) }, getSeriesData() { const groupArray = this.groupBy(this.statisticsInfo, function (item) { return [item.categoryName]; }); for (let categoryArray of groupArray) { const data = [] for (let categoryData of categoryArray) { data.push(categoryData.total) } this.seriesData.push({ name: categoryArray[0].categoryName, type: 'bar', stack: '总数', data: data }) } // 设置总数显示 var this_ = this; this_.seriesData[this_.seriesData.length-1].label = { show: true, position: 'top', color: 'black', formatter: function (params){ var value = 0; for (let i = 0; i < this_.seriesData.length; i++) { value += this_.seriesData[i].data[params.dataIndex] } return value; } } }, /** * 按条件分组 * 1. 函数groupBy有两个形参,一为对象数组,二为匿名函数(该函数功能:返回对象的某个指定属性的属性值并存放在数组中); * 2. groupBy函数内,先创建一个空对象; * 3. 然后forEach遍历对象数组,遍历时要执行的函数中只有一个形参o(数组中的每个元素); * 4. 由于下面函数调用是想用name来分组,因此let group = JSON.stringify( f(o) ),相当于先获取到对象数组list中的name属性对应的属性值并放入数组中:["John"],然后再将属性值转换为json字符串:'["John"]'; * 5. groups[group] = groups[group] || [],在js中对象也是关联数组,因此这里相当于做了两件事,一是把group作为groups的key,二是将对应的value初始化,第一次执行为空数组,循环执行时找到相同的name时保持不变; * 6. groups[group].push( o ),这句相当于把list中每个对象压入数组中作为value; * 7. 最后,Object.keys(groups)是取出groups对象中的所有key,然后遍历一个个key组成的新数组,返回分好了组的二维数组 */ groupBy(array, f) { const groups = {}; array.forEach(function (o) { const group = JSON.stringify(f(o)); groups[group] = groups[group] || []; groups[group].push(o); }); return Object.keys(groups).map(function (group) { return groups[group]; }); } } } </script> <style> .row { margin-right: -15px; margin-left: -15px; } .className { width: 100%; height: 540px } .splitBar { height: 60px; } </style>
- element Cascader 级联选择器 获取label
<el-form-item> <el-cascader :options="departmentCascader" @change="handleDepartmentChange" v-model="selectedOptions" class="dialog-search-item dialog-keywords" ref="mycascader" :show-all-levels="false" expand-trigger="hover" clearable change-on-select :size="searchSize" placeholder="根据市场筛选"></el-cascader> </el-form-item> handleDepartmentChange(val) { if (val.length > 0) { const cascaderObj = this.getCascaderObj(val, this.departmentCascader); this.listQuery.scName = cascaderObj.label; } else { this.listQuery.scName = undefined; } }, getCascaderObj(val, opt) { const selectList = val.map(function (value, index, array) { for (var itm of opt) { if (itm.value == value) { opt = itm.children; return itm; } } return null; }); return selectList.find((obj)=>{ return obj.value === val[val.length - 1]; }); },
- 浏览器根据地址下载文件,而不是直接打开文件,.doc/.mp4...等格式
运用插件file-saver
安装 cnpm install file-saver --save
使用var FileSaver = require('file-saver'); FileSaver.saveAs(url, filename);
- 行内块高度一致
<div class="container"> <div class="item bg1">哈哈哈</div> <div class="item bg2">方法发发士大夫</div> <div class="item bg3">fasdfffffffffffffffffffffffffffas</div> </div> .container{ width: 330px; display: flex; justify-content: space-between; align-items: stretch; } .item{ width: 50px; height: auto; word-wrap:break-word; } .bg1{ background: red; } .bg2{ background: green; } .bg3{ background: blue; }
-
moment的中文显示
我在使用moment的时候,想要让时间戳转换的格式变成XXXX年XX月XX日 XX:XX:XX 星期X 这个格式
但是这个默认转换是英文 所以我们要做的操作是引入一个中文的文档:
- vue子组件触发父组件事件,并传值
// 子组件 <template> <button @click="emitEvent">点击我</button> </template> <script> export default { data() { return { msg: "我是子组件中的数据" } }, methods: { emitEvent(){ //通过按钮的点击事件触发方法,然后用$emit触发一个my-event的自定义方法,传递this.msg数据。 this.$emit('my-event', this.msg) } } } </script>
// 父组件 <template> <div id="app"> <child-a @my-event="getMyEvent"></child-a> <!--父组件中通过监测my-event事件执行一个方法,然后取到子组件中传递过来的值--> </div> </template> <script> import ChildA from './components/child.vue' export default { components: { ChildA }, methods: { getMyEvent(msg){ console.log('接收的数据--------->'+msg) //接收的数据--------->我是子组件中的数据 } } } </script>
- 前端获取后台返回文件流,如果失败获取返回错误信息
doReportPrint() { this.recordMedia.printDate = moment().format('YYYY年MM月DD日'); this.submitLoading = true; var _this = this; doReportPrint(this.recordMedia).then(res => { var content = res; const fileReader = new FileReader(); fileReader.onload = function() { try { // this.result = 字符串类型的 response const fileResult = JSON.parse(fileReader.result); // 如果接口返回的数据,非二进制的话。可以根据response做出响应的判断。 if (fileResult.status == 500) { // ps:JSON.parse()是Javascript中一个常用的 JSON 转换方法,JSON.parse()可以把JSON规则的字符串转换为JSONObject 反之就会报错。 // 如果接口内容为二进制数据。那么JSON.parse解析失败, 抛出异常来到catch进行执行下载操作 _this.$message.error('打印报告单异常'); _this.submitLoading = false; } } catch (err) { const blob = new Blob([content], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document;charset=utf-8' }); const fileName = "现场勘验报告单" + moment(_this.recordMedia.createTime, 'YYYY-MM-DD HH:mm:ss').format('YYYYMMDD') + "-" + _this.recordMedia.centerCode + ".docx"; if ("download" in document.createElement("a")) { // 非IE下载 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); // 释放URL 对象 document.body.removeChild(elink); } else { // IE10+下载 navigator.msSaveBlob(blob, fileName); } _this.closeReportDialog(); _this.reloadList(); _this.submitLoading = false; } } fileReader.readAsText(content); }).catch(e => { this.message.error('打印报告单调用后台接口失败'); }); }
- Vue定义全局变量、全局方法,在.vue文件和.js文件里使用
在.vue后缀名的文件,和.js后缀名的文件里,使用全局变量的语法是不一样的。.vue模板文件可以直接访问挂载在Vue实例上的全局对象,而普通的.js文件访问不到。定义全局变量
在任意路径下创建一个.vue文件。我这里创建在src/config路径下,取名为Global.vue
Global.vue
<script> /** * 统一认证服务器地址 */ // const ssoServer = "https://iamtest.by.gov.cn:8083/idp"; // 测试环境 const ssoServer = "https://iam.by.gov.cn/idp"; // 生产环境 /** * 统一认证登出地址 */ // const ssoServerLogout = "https://iamtest.by.gov.cn:8083/apphub/logout"; // 测试环境 const ssoServerLogout = "https://iam.by.gov.cn/apphub/logout"; // 生产环境 export default { ssoServer, ssoServerLogout } </script>
我这里定义的是常量。变量只是把const换成let,别的都一样。
export default后面必须把所有全局变量名又写一遍,属于boilerplate代码,没办法。
main.js是Vue项目的入口文件,在main.js里面把全局变量挂载到Vue实例上。
main.js
import global_ from './config/Global' // 这个路径是相对于main.js的相对路径 Vue.prototype.GLOBAL = global_
这样挂载完了以后,我们有两个办法去访问。
通过Vue.prototype对象访问import Vue from 'vue' let a = Vue.prototype.GLOBAL.ssoServer
这种写法在.vue文件和.js文件中都可以使用。
通过this关键字访问let a = this.GLOBAL.ssoServer
这种写法只能在.vue文件中使用。.vue模板文件中,this关键字可以访问到Vue.prototype上挂载的所有对象,所以写this就相当于写Vue.prototype。
定义全局方法在任意.js文件里使用export function语法,即可让这个方法变成全局方法。
common.js
export function 个人号码加密 (号码) { let length = 号码.length if (length < 8) return 号码 let 开始加密位 = length / 2 - length / 5 let 结束加密位 = 开始加密位 + length / 3 let 加密位数 = 结束加密位 - 开始加密位 + 1 let 加密结果 = 号码.substring(0, 开始加密位) for (let i = 0; i < 加密位数; i++) { 加密结果 += '*' } 加密结果 += 号码.substring(结束加密位, length) return 加密结果 }
这样定义完了,在别的.js和.vue文件里面都可以调用到。调用语法是一样的。
import * as common from '@/utils/common' // @/utils为路径, common为js文件名 let 身份证号码 = "44132319960617502X" let 身份证号码加密 = common.个人号码加密(身份证号码)
全局方法不需要挂载在Vue实例上,直接import JS文件就可以访问。
- vue 子组件和父组件加载顺序
执行顺序如下:
父组件 created
子组件 created
子组件 mounted
父组件 mounted
如果有多个子组件:
父组件created钩子结束后,依次执行子组件的created钩子
多个子组件的created执行顺序为父组件内子组件DOM顺序
多个子组件的mounted顺序无法保证,跟子组件本身复杂程度有关
父组件一定在所有子组件结束mounted钩子之后,才会进入mounted钩子
问题解决方案:
1.用watch监视props属性,将newdata赋值进data中
2.在一个工程里,子组件总最好不要写过多逻辑和对传入数据的处理