1、显示富文本的内容,自动换行样式,文本超出
<div class="introduction" v-html="introduce"></div>
.introduction {
font-size: 16px;
font-weight: 400;
color: #666666;
letter-spacing: 1px;
margin-top: 10px;
word-wrap:break-word; // 主要就是这两行
word-break:normal; // 主要就是这两行
}
2、打开新的页面窗口
toComplete() {
let routeData = this.$router.resolve({
path: '/questionnaire',
})
window.open(routeData.href, '_blank')
}
3、关闭新开窗口,回到老的页面并刷新老页面
if (window.close) {
// For IE and Firefox
window.opener.location.reload() // 刷新老页面
window.close() // 关闭新窗口
} else {
// For Chrome and Safari
window.open('', '_self', '')
window.opener.location.reload()
window.close()
}
4、倒计时
data() {
countdown: 3 // 倒计时多少s
}
startCountdown() { // 倒计时方法
let timer = setInterval(() => {
if (this.countdown > 0) {
this.countdown -= 1
} else {
clearInterval(timer)
}
}, 1000)
}
5、背景透明度
.one {
color: #e88f21;
background-color: rgba(232, 143, 33, 0.2); // .2就是透明度
}
6、数据更新dom没有更新
this.$forceUpdate();
7、获取前几天或者后几天的日期
getDate() {
// 创建一个空数组来存储日期
let dates = [];
// 获取当前日期
let now = new Date();
// 循环获取最近 7 天的日期
for (let i = 0; i < 7; i++) {
// 获取当前日期的时间戳
let timestamp = now.getTime();
// 计算 i 天前的时间戳
let dayTimestamp = 24 * 60 * 60 * 1000; // 一天的毫秒数
let iDayAgoTimestamp = timestamp + i * dayTimestamp; // 前七天还是后七天,在这里控制加还是减
// 转换为日期对象
let date = new Date(iDayAgoTimestamp);
// 格式化日期为 "yyyy-MM-dd" 的字符串并存入数组
let year = date.getFullYear();
let month = ("0" + (date.getMonth() + 1)).slice(-2);
let day = ("0" + date.getDate()).slice(-2);
dates.push(year + "-" + month + "-" + day);
}
this.dateAfter.forEach((item, index) => {
item.date = dates[index]
})
}
8、获取指定天数的日期
function getTheSpecifiedDate(date, theOtherDay) {
let myDate = new Date(date); //获取今天日期
myDate.setDate(myDate.getDate() - theOtherDay); //获取指定前几天的日期
const Y = myDate.getFullYear()
const M = myDate.getMonth() + 1 < 10 ? '0' + (myDate.getMonth() + 1) : myDate.getMonth() + 1
const D = myDate.getDate()
let dateGet = `${Y}-${M}-${D}`
return dateGet
}
9、根据身份证计算年龄
function analyzeIDCard(IDCard) {
let age = 0,
yearBirth, monthBirth, dayBirth;
//获取用户身份证号码
let userCard = IDCard;
//如果身份证号码为undefind则返回空
if (!userCard) {
return age;
}
let reg = /(^\d{15}$)|(^\d{17}([0-9]|X)$)/; //验证身份证号码的正则
if (reg.test(userCard)) {
if (userCard.length == 15) {
let org_birthday = userCard.substring(6, 12);
//获取出生年月日
yearBirth = "19" + org_birthday.substring(0, 2);
monthBirth = org_birthday.substring(2, 4);
dayBirth = org_birthday.substring(4, 6);
} else if (userCard.length == 18) {
//获取出生年月日
yearBirth = userCard.substring(6, 10);
monthBirth = userCard.substring(10, 12);
dayBirth = userCard.substring(12, 14);
}
//获取当前年月日并计算年龄
let myDate = new Date();
let monthNow = myDate.getMonth() + 1;
let dayNow = myDate.getDate();
let age = myDate.getFullYear() - yearBirth;
if (monthNow < monthBirth || (monthNow == monthBirth && dayNow < dayBirth)) {
age--;
}
//返回年龄
return age;
} else {
return ''
}
}
10、根据身份证获取性别
getGenderByIdNumber(idNumber) {
if (idNumber) {
let genderCode; // 性别代码
if (idNumber.length == 18) { // 二代身份证号码长度为18位(第17位为性别代码)
genderCode = idNumber.charAt(16);
} else if (idNumber.length == 15) { // 一代身份证号码长度为15位(第15位为性别代码)
genderCode = idNumber.charAt(14);
}
if (genderCode && !isNaN(genderCode)) {
// 两代身份证号码的性别代码都为男奇女偶
if (parseInt(genderCode) % 2 == 0) {
return '女';
}
return '男';
}
}
},
11、图片链接放到浏览器可以打开,但是在img标签里面却无法打开,原因图片的链接是第三方地址,所以在有些浏览器可以会不兼容,导致不显示图片,解决方案加上//images.weserv.nl/?url=
<img class="image-show" src="//images.weserv.nl/?url=https://lmg.jj20.com/up/allimg/tp01/1ZZQ20QJS6-0-lp.jpg" alt="">
12、后端返回文件流,前端处理下载excel
export function getExcel(data) { // 接口
return request({
url: '/Test/downloadOrder',
method: 'POST',
data,
responseType: 'blob', // 接受类型
})
}
// 下载请求
getExcel(state.formList).then((res) => {
const blob = new Blob([res], { type: 'application/vnd.ms-excel' }) // 构造一个blob对象来处理数据,并设置文件类型
if (window.navigator.msSaveOrOpenBlob) {
//兼容IE10
navigator.msSaveBlob(blob, '订单列表')
} else {
const href = URL.createObjectURL(blob) //创建新的URL表示指定的blob对象
const a = document.createElement('a') //创建a标签
a.style.display = 'none'
a.href = href // 指定下载链接
a.download = '订单列表.xlsx' //指定下载文件名
a.click() //触发下载
URL.revokeObjectURL(a.href) //释放URL对象
state.excelLoading = false
} // 这里也可以不创建a链接,直接window.open(href)也能下载
})
13、input输入框无法输入的时候
// 第一种方案
@input="changeValue"
changeValue() {
this.$forceUpdate()
}
// 第二种,可能是在table中,你需要将数据开始就遍历加入字段
this.tableData = res.data.list.map((item) => {
item.enterpriseName = item.name
item.enterpriseId = item.id
item.isSelect = false
item.enrollNumber = ''
return item
})
14、循环生成el-form,校验必填项
<div v-for="(item, index) in resultArr" :key="index">
<el-form
:ref="
(el) => {
if (el) formRef[index] = el
}
"
class="title-form"
:inline="false"
label-position="left"
label-width="80px"
:model="item"
:rules="rules"
>
<el-form-item class="result-title" :label="'结果' + (index + 1)">
<el-button type="primary" @click="delResult(index)">
删 除
</el-button>
</el-form-item>
<el-form-item label="分数范围" prop="minFraction">
<div class="score">
<el-input
v-model="item.minFraction"
clearable
placeholder=""
style="width: 150px"
/>
<div style="margin: 0 20px">至</div>
<el-input
v-model="item.maxFraction"
clearable
placeholder=""
style="width: 150px"
/>
</div>
</el-form-item>
<el-form-item label="测评结果" prop="details">
<vab-quill
v-model="item.details"
:min-height="400"
:options="configOptions_2"
/>
</el-form-item>
</el-form>
</div>
校验必填项
let isPass = true
state.resultArr.forEach((item, index) => {
state.formRef[index].validate(async (valid) => {
if (!valid) {
isPass = false
console.log(123, isPass)
return
}
})
if (!isPass) throw new Error('退出forEach循环!')
})
if (isPass) emit('resultNext', state.resultArr)
vue2中动态添加
// :ref="'parameters' + index" 主要是这个
<div class="ground-user" v-for="(item, index) in parameters" :key="index">
<el-form class="group-leader" :model="item" :rules="registerRules" label-position="top" :ref="'parameters' + index">
<el-form-item label="成员姓名:" label-width="80px" prop="name">
<el-input v-model="item.name" placeholder="成员姓名" clearable></el-input>
</el-form-item>
<i class="el-icon-delete" style="padding-top: 20px;cursor: pointer;padding-left: 20px" v-if="parameters.length > 1" @click="delParameters(index)"></i>
</div>
// 校验
this.parameters.forEach((item, index) => {
this.$refs['parameters' + index][0].validate((valid) => {
if (!valid) {
showStatus = true
} else {
return false;
}
})
})
15、两数组根据对象id去重
export function repeatArr(arr, arr1) {
let arrs=[...arr,...arr1]
//根据id去重
let map=new Map()
for(let item of arrs){
if(!map.has(item.id)){
map.set(item.id,item)
}
}
let newArr = [...map.values()]; //把map中所有的值取出来放进数组
return newArr
}
16、element ui中遇到时间、级联选择等等,数组有数据,但是视图不更新问题,应该使用push,而不是直接赋值
getBeforeTwo(time) {
this.signInTime = []
let currentTime = new Date(time)
let twoHoursAgo = currentTime.setHours(currentTime.getHours() - 2)
this.signInTime.push(getNowDate(twoHoursAgo))
this.signInTime.push(this.activityTime[1])
console.log(this.signInTime)
return this.$forceUpdate()
}
17、限制选择时间到分钟
// vue2
<el-date-picker
style="width: 100%"
v-model="activityTime"
type="datetimerange"
format="yyyy-MM-dd HH:mm"
value-format="yyyy-MM-dd HH:mm"
placeholder="选择日期"
:picker-options="option"
:disabled="disableStatus"
@change="activityTimeChange">
</el-date-picker>
// vue3
<el-date-picker
v-model="activityTime"
:disabled="disableStatus"
:disabled-date="disabledDate"
end-placeholder="结束时间"
format="YYYY-MM-DD HH:mm"
range-separator="到"
start-placeholder="开始时间"
style="width: 100%"
type="datetimerange"
value-format="YYYY-MM-DD HH:mm"
@change="activityTimeChange"
/>
18、英文不允许整个单词换行
word-wrap: break-word;
word-break: break-all;
19、web直传oss
<template>
<div style="background: #fff">
<el-upload
class="avatar-uploader"
action="https://xxxx.oss-cn-shanghai.aliyuncs.com"
:data="dataObj"
:show-file-list="false"
:headers="hearder"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload">
<img v-if="imageUrl" :src="imageUrl" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</div>
</template>
<script>
import { getToken } from '@/api/ossImage'
export default {
name: 'IntelligentAuditingIndex',
data() {
return {
imageUrl: '',
dataObj: { // 后端返回的
policy: '',
signature: '',
ossaccessKeyId: '',
dir: '',
host: '',
callback: '',
key: '',
accessid: '',
},
hearder: {},
action: '',
};
},
created() {
this.getObject()
},
mounted() {},
methods: {
getObject() {
getToken().then(res => { // 后端接口返回的
this.dataObj.policy = res.policy
this.dataObj.signature = res.signature
this.dataObj.ossaccessKeyId = res.accessid
this.dataObj.accessid = res.accessid
this.dataObj.dir = res.dir
this.dataObj.host = res.host
this.dataObj.callback = res.callback
})
this.hearder.Authorization = "Bearer " + window.localStorage.getItem('token')
},
handleAvatarSuccess(res, file) {
this.imageUrl = URL.createObjectURL(file.raw);
},
beforeAvatarUpload(file) {
this.dataObj.key = this.getUUID() + file.name
console.log(this.dataObj.host + '/' + this.dataObj.key) // 拼接之后就是图片地址
const isJPG = file.type === 'image/jpeg';
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isJPG) {
this.$message.error('上传头像图片只能是 JPG 格式!');
}
if (!isLt2M) {
this.$message.error('上传头像图片大小不能超过 2MB!');
}
return isJPG && isLt2M;
},
getUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
return (c === 'x' ? (Math.random() * 16 | 0) : ('r&0x3' | '0x8')).toString(16)
})
}
},
};
</script>
<style lang="scss" scoped>
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.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;
}
</style>
20、限制选择时间不能小于当前时间
<el-date-picker
style="width: 100%"
v-model="activityTime"
type="datetimerange"
format="yyyy-MM-dd HH:mm"
value-format="yyyy-MM-dd HH:mm"
placeholder="选择日期"
:picker-options="option"
:disabled="disableStatus"
@change="activityTimeChange">
</el-date-picker>
// 如果选择的时间小于当前时间,则把当前时间赋值给选择的时间
activityTimeChange(e) {
if(!e) {
this.submitData.activityStartTime = ''
this.submitData.activityEndTime = ''
return
}
let startAt = new Date(e[0]) * 1000 /1000;
if(startAt < Date.now()) {
this.activityTime[0] = getNowDate()
}
let endAt = new Date(e[1]) * 1000 /1000;
if(endAt < Date.now()) {
this.activityTime[1] = getNowDate()
}
this.submitData.activityStartTime = this.activityTime[0]
this.submitData.activityEndTime = this.activityTime[1]
this.getBeforeTwo(this.activityTime[0])
}
// 格式化日对象
export function getNowDate(time = null) {
var date = time ? new Date(time) : new Date()
var sign2 = ":";
var year = date.getFullYear() // 年
var month = date.getMonth() + 1; // 月
var day = date.getDate(); // 日
var hour = date.getHours(); // 时
var minutes = date.getMinutes(); // 分
var seconds = date.getSeconds() //秒
var weekArr = ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期天'];
var week = weekArr[date.getDay()];
// 给一位数的数据前面加 “0”
if (month >= 1 && month <= 9) {
month = "0" + month;
}
if (day >= 0 && day <= 9) {
day = "0" + day;
}
if (hour >= 0 && hour <= 9) {
hour = "0" + hour;
}
if (minutes >= 0 && minutes <= 9) {
minutes = "0" + minutes;
}
if (seconds >= 0 && seconds <= 9) {
seconds = "0" + seconds;
}
return year + "-" + month + "-" + day + " " + hour + sign2 + minutes;
}
22、vue路由配置提示超出循环,进入死循环的时候,因为只要内遇到next()的时候就会一直执行,只有遇到了next()才会停止
// 处理路由方法
import router from '@/router'
import store from '@/store'
// 全局前置守卫
let hasGetInfo = false
router.beforeEach(async (to, from, next) => {
// 设置页面标题
let title = (to.meta.title ? to.meta.title : "管理平台")
document.title = title
const token = window.localStorage.getItem('token')
if(token) {
if(to.path != '/compulsoryCertification' && window.localStorage.getItem('whetherAttestation') == 10){ // 这一行至关重要
next({ path: '/compulsoryCertification' })
}
if(to.path == '/login'){
next({ path: from.path ? from.path : '/' })
}
if(!hasGetInfo) {
try{
await store.dispatch("getMenu")
}catch(err){
console.log('获取菜单列表失败');
}
try{
await store.dispatch("getInfo")
}catch(err){
console.log('获取用户信息');
}
try{
await store.dispatch("getProblem")
}catch(err){
console.log('获取问卷失败');
}
hasGetInfo = true
}
next()
}else {
if(to.path != '/login' && to.path != '/notice') next({ path: "/login" })
next()
}
})
// 全局后置守卫
router.afterEach((to, from) => {
})
23、虚假进度条
<el-progress class="progress-file" v-if="flagProgress" :percentage="uploadPercent"></el-progress>
flagProgress: false, // 是否显示进度条
uploadPercent: 0, // 当前进度
countdown: 15, // 倒计时多少s
timer: null, // 定时器
handleProgress() {
this.timer = setInterval(() => {
let number = Number((Math.random() * 10).toFixed(0))
console.log(number)
if (this.countdown > 0) {
this.countdown -= 1
this.uploadPercent += number
} else {
clearInterval(this.timer)
}
}, 500)
}
24、在线预览文档
<iframe :src="attachmentSrc" frameborder="0" width="100%" height="1200"></iframe>
data(){
return {
imgUrl: '',
imgType: '.jpg,.JPEG,.PNG,.GIF,.BMP,.TIFF,.WEBP,.HEIF',
attachmentSrc: 'https://view.officeapps.live.com/op/view.aspx?src=', // 主要是这句,调用了微软的在线预览文档接口
}
},
25、oss直传
<el-upload
class="avatar-logo-uploader"
:action="uploadImageUrl" // oss上传地址,oss域名
:data="dataObj" // 上传的参数
:headers="hearder" // 上传的头部数据,token之类的
:show-file-list="false"
:on-success="handleLogoSuccess"
:before-upload="beforeUpload"
accept=".jpg,.JPEG,.PNG,.GIF,.BMP,.TIFF,.WEBP,.HEIF">
<img v-if="enterpriseInfo.enterpriseLog" :src="enterpriseInfo.enterpriseLog" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
this.uploadImageUrl = process.env.VUE_APP_OSS // 我将上传地址放env了
this.hearder = hearder // 头部参数
// 方法
handleLogoSuccess(response){
this.enterpriseInfo.enterpriseLog = this.dataObj.host + '/' + this.dataObj.key
},
async beforeUpload(file) {
this.dataObj.key = this.dataObj.key + file.name
const isJPG = file.type === 'image/jpg' || file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/gif' || file.type === 'image/bmp' || file.type === 'image/tiff' || file.type === 'image/webp' || file.type === 'image/heif'
const isLt2M = file.size / 1024 / 1024 < 50
if (!isJPG) {
this.$message.error('仅支持.JPG、.JPEG、.PNG、.GIF、.BMP、.TIFF、.WEBP、.HEIF格式!')
}
if (!isLt2M) {
this.$message.error('图片大小不能超过 50MB!')
}
return isJPG && isLt2M;
},
封装的js
import { getToken } from '@/api/ossImage'
let dataObj = {
policy: '',
signature: '',
ossaccessKeyId: '',
dir: '',
host: '',
callback: '',
key: '',
accessid: '',
}
export const hearder = {
Authorization: "Bearer " + window.localStorage.getItem('token')
}
export const getOss = async () => {
await getToken().then(res => { // 后端接口返回的,需要传回给后端的
dataObj.policy = res.policy
dataObj.signature = res.signature
dataObj.ossaccessKeyId = res.accessid
dataObj.accessid = res.accessid
dataObj.dir = res.dir
dataObj.key = res.dir + getUUID()
dataObj.host = res.host
dataObj.callback = res.callback
})
return dataObj
}
export const getUUID = () => { // 生成的路径,防止重复
let str = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
return (c === 'x' ? (Math.random() * 16 | 0) : ('r&0x3' | '0x8')).toString(16)
})
let result = '/' + str + '/' + getNowDate() + '/'
return result
}
export function getNowDate() {
var date = new Date()
var sign2 = ":";
var year = date.getFullYear() // 年
var month = date.getMonth() + 1; // 月
var day = date.getDate(); // 日
var hour = date.getHours(); // 时
var minutes = date.getMinutes(); // 分
var seconds = date.getSeconds() //秒
var weekArr = ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期天'];
var week = weekArr[date.getDay()];
// 给一位数的数据前面加 “0”
if (month >= 1 && month <= 9) {
month = "0" + month;
}
if (day >= 0 && day <= 9) {
day = "0" + day;
}
if (hour >= 0 && hour <= 9) {
hour = "0" + hour;
}
if (minutes >= 0 && minutes <= 9) {
minutes = "0" + minutes;
}
if (seconds >= 0 && seconds <= 9) {
seconds = "0" + seconds;
}
return year + "-" + month + "-" + day + "-" + hour + sign2 + minutes + sign2 + seconds;
}
26、菜单
<template>
<div class="f-menu" :style="{width: $store.state.operationMenu}">
<el-menu background-color="#FFFFFF" text-color="#5E5E5E" :default-active="$route.path" unique-opened
:collapse="isMenuCollapse" @select="handleSelect" :collapse-transition="false">
<div v-for="(item,index) in menus" :key="index">
<el-submenu v-if="item.children && item.children.length > 0" :index="item.path">
<template slot="title">
<i :class="item.meta.icon"></i>
<span slot="title">{{item.name}}</span>
</template>
<div v-for="(item2,index2) in item.children" :key="index2">
<el-submenu v-if="item2.children && item2.children.length > 0" :index="item2.path">
<template slot="title">
<i :class="item2.meta.icon"></i>
<span slot="title">{{item2.name}}</span>
</template>
<el-menu-item v-for="(item3,index3) in item2.children" :key="index3" :index="item3.path" :disabled="!item3.enable">
<i :class="item3.meta.icon"></i>
<span slot="title">{{item3.name}}</span>
</el-menu-item>
</el-submenu>
<el-menu-item v-else :index="item2.path" :disabled="!item2.enable">
<i :class="item2.meta.icon"></i>
<span slot="title">{{item2.name}}</span>
</el-menu-item>
</div>
</el-submenu>
<el-menu-item v-else :index="item.path" :disabled="!item.enable">
<i :class="item.meta.icon"></i>
<span slot="title">{{item.name}}</span>
</el-menu-item>
</div>
</el-menu>
</div>
</template>
<script>
import {
mapState
} from 'vuex'
export default {
name: 'FMenu',
data() {
return {};
},
mounted() {},
created() {},
computed: {
...mapState(["userinfo","getMenuTotal", "menus", "isMenuCollapse"])
},
methods: {
async handleSelect(e) {
if(e != '/index' && e != '/enterpriseCertification' && e != '/enterpriseEdit' && this.userinfo.whetherAttestation != 20){
this.$router.push({ path: '/enterpriseCertification'})
this.$message({ type: 'warning', message: '请先进行企业认证!'})
this.$forceUpdate()
return
}
this.$router.push(e)
}
},
};
</script>
<style lang="scss" scoped>
.f-menu {
background: #FFFFFF;
position: fixed;
left: 0;
bottom: 0;
top: 60px;
overflow-y: auto;
overflow-x: hidden;
width: 200px;
border-right: 1px solid #d7d4d4;
::v-deep .el-menu-item {
height: 56px;
}
::v-deep .is-active {
background-color: #E0E8FF !important;
color: #000000;
}
::v-deep .is-active::after {
content: '';
position: absolute;
width: 8px;
height: 56px;
right: 0;
bottom: 0;
background: #0880FF;
}
::v-deep .is-opened::after {
display: none;
}
::v-deep .el-menu {
border: none;
}
.el-menu-vertical-demo:not(.el-menu--collapse) {
width: 200px;
min-height: 400px;
}
/*隐藏文字*/
::v-deep .el-menu--collapse .el-submenu__title span {
display: none;
}
// /*隐藏 > */
::v-deep .el-menu--collapse .el-submenu__title .el-submenu__icon-arrow {
display: none;
}
}
</style>
27、element时间选择器禁止隐藏年
<el-date-picker
popper-class="RedefineScope-node-repeat" // 主要是这行,进行设置class
v-model="formSeach.month"
type="month"
placeholder="请选择月"
format="MM"
value-format="MM"
@change="clearMonth">
</el-date-picker>
<style lang="scss"> // 然后是这行,不能再当前页面更改,要不然一直不生效,这个问题找的我头都快炸了,一定要去掉scoped
.RedefineScope-node-repeat .el-date-picker__header--bordered {
display: none;
}
</style>
28、取消接口
import request from '@/utils/request'
import axios from 'axios' // 关键代码
const CancelToken = axios.CancelToken // 关键代码
export const getFileZip = (params, that) => request({
url: '/api/ReviewSubmission/Pack',
method: 'GET',
params,
// 关键代码 cancelToken
cancelToken: new CancelToken(function executor(c) {
that.cancel = c
})
})
// 使用
getFileZip(params, this).then((res) => {})
this.cancel('请求已取消') // 在哪里取消就在哪里执行这行代码
29、默认展开指定的菜单
default-openeds:当前打开的 sub-menu 的 index 的数组
<el-menu ref="elMenuRef" background-color="#FFFFFF" text-color="#5E5E5E" :default-active="$route.path" :unique-opened="false"
:collapse="isMenuCollapse" @select="handleSelect" :collapse-transition="false" :default-openeds="menuListPurview" @close="menuClose">
<div v-for="(item,index) in menus" :key="index">
<el-submenu v-if="item.children && item.children.length > 0" :index="item.path" :class="item.path == '/declaration' || '/promote' || '/exponent' ? 'hide-right' : ''">
<template slot="title">
<i :class="item.meta.icon"></i>
<span slot="title">{{item.name}}</span>
</template>
<div v-for="(item2,index2) in item.children" :key="index2">
<el-submenu v-if="item2.children && item2.children.length > 0" :index="item2.path">
<template slot="title">
<i :class="item2.meta.icon"></i>
<span slot="title">{{item2.name}}</span>
</template>
<el-menu-item v-for="(item3,index3) in item2.children" :key="index3" :index="item3.path" :disabled="!item3.enable">
<i :class="item3.meta.icon"></i>
<span slot="title">{{item3.name}}</span>
</el-menu-item>
</el-submenu>
<el-menu-item v-else :index="item2.path" :disabled="!item2.enable">
<i :class="item2.meta.icon"></i>
<span slot="title">{{item2.name}}</span>
</el-menu-item>
</div>
</el-submenu>
<el-menu-item v-else :index="item.path" :disabled="!item.enable">
<i :class="item.meta.icon"></i>
<span slot="title">{{item.name}}</span>
</el-menu-item>
</div>
</el-menu>
// 默认展开的菜单
menuListPurview: ['/declaration', '/promote', '/exponent']
// 不允许收起这三个菜单
menuClose(e) {
if(e == '/declaration' || e == '/promote' || e == '/exponent') {
this.$refs.elMenuRef.open(e)
}
}
30、el-collapse 箭头改成文字加箭头
::v-deep .el-icon-arrow-right:before{
content: "展开 \e6df";
}
::v-deep .el-icon-arrow-right.is-active:before{
content: "收起 \e6e1";
}
::v-deep .el-icon-arrow-right.is-active{
transform: rotate(0deg);
}
::v-deep .el-collapse-item__arrow{
min-width: 55px;
}
31、校验两次密码是否一致
registerRules: {
confirmPassword: [
{ required: true, message: '确认密码不能为空', trigger: 'blur' },
{ min: 10, max: 20, message: '确认密码应为10-20位数', trigger: 'blur' },
{ pattern: /^(?=.*\d)(?=.*[A-Za-z])[A-Za-z\d$@$!%*#?&]{10,}$/, message: '最小10位,至少包含一个字母和一个数字'},
{ validator: validateCheckpwd, trigger: "change" } // 主要就是这个validateCheckpwd
],
},
data() {
// 检查两次密码是否一样
let validateCheckpwd = (rule, value, callback) => {
if (value === this.registerForm.password) {
callback();
} else {
callback(new Error("密码不一致, 请重新输入确认密码"));
}
}
}
32、vue滑动指定位置
对应的标签要加上 ref="description"
window.scrollTo({
top: this.$refs[value].getBoundingClientRect().top + window.scrollY - 70,
behavior: 'smooth' // 平滑滚动
})
33、微信分享H5
先引入微信js-sdk
npm install weixin-js-sdk
封装的分享
import wx from 'weixin-js-sdk' // 使用js-sdk
import {
getWechatConfig
} from "@/api/auth" //为你提供timestamp、nonceStr、signature的后端接口
/**
* 获取微信配置
* @param {*} tag 调用页面的this
* @param {*} share_title 分享标题
* @param {*} share_desc 分享描述
* @param {*} share_link 分享链接
* @param {*} share_cover 分享封面(配图)
* @returns
*/
export const wechatConfig = (share_title, share_desc, share_link, share_cover) => {
console.log(share_title, share_desc, share_link, share_cover)
// 记录进入app时的url
if (typeof window.entryUrl === 'undefined' || window.entryUrl === '') {
window.entryUrl = encodeURIComponent(location.href.split('#')[0])
}
// 进行签名的时候 Android 不用使用之前的链接, ios 需要
let signLink = /(Android)/i.test(navigator.userAgent) ? encodeURIComponent(location.href.split('#')[0]) : window.entryUrl;
let url_share = window.location.href.split('#')[0]
// var wx_host = encodeURIComponent(window.location.href.split('#')[0]) //后端获取签名,需要前端传url,url要求看注解
const cover = share_cover || 'https://hbimg.huaban.com/a2a9a71b293f6664b342e0cefc6e1fccd5f921f83cfa5-RoYLU8_fw658/format/webp'; //不重要的默认图片地址
return new Promise((resolve, reject) => {
getWechatConfig({ url: signLink }).then((res) => {
if (res.code == 200) {
const config = {
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: res.data.app, // 必填,公众号的唯一标识
timestamp: res.data.timestamp, // 必填,生成签名的时间戳
nonceStr: res.data.nonceStr, // 必填,生成签名的随机串
signature: res.data.signature, // 必填,签名
jsApiList: ["updateAppMessageShareData", "updateTimelineShareData"], // 必填,需要使用的JS接口列表,注意查看官方文档,部分js接口即将废弃,我这里用的是新的
// openTagList: ["wx-open-launch-weapp"], // 可选,需要使用的开放标签列表(当前标签用于跳转微信小程序)
};
wx.config(config) //通过config接口注入权限验证配置
wx.ready(function () { //通过ready接口处理成功验证
//分享给朋友
wx.updateAppMessageShareData({
title: share_title, // 分享标题
desc: share_desc, // 分享描述
link: `${url_share}#/${share_link}`, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: cover, // 分享后显示的封面图
success: function () {}, // 设置成功回调
});
//分享到朋友圈
wx.updateTimelineShareData({
title: share_title, // 分享标题
link: `${url_share}#/${share_link}`, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
imgUrl: cover, // 分享图标
success: function () {
// 用户点击了分享后执行的回调函数
}
})
return resolve(true)
});
wx.error(function (res) {
// config信息验证失败会执行error函数,如签名过期导致验证失败,具体错误信息可以打开config的debug模式查看,也可以在返回的res参数中查看,对于SPA可以在这里更新签名。
alert("config信息验证失败")
alert(res)
return reject(false)
});
}
});
})
}
34、vue项目导入高德地图选择地址
<template>
<div class="app-container">
<!--搜索组件-->
<div>
<el-select
v-model="keywords"
filterable
remote
placeholder="请输入关键词"
:remote-method="remoteMethod"
:loading="loading"
:clearable="true"
size="mini"
@change="currentSelect"
style="width: 500px"
>
<el-option
v-for="item in options"
:key="item.id"
:label="item.name"
:value="item"
class="one-text"
>
<span style="float: left">{{ item.name }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{
item.district
}}</span>
</el-option>
</el-select>
</div>
<!--地图组件-->
<div id="guide-map" style="height: 500px;"></div>
</div>
</template>
<script>
import AMapLoader from "@amap/amap-jsapi-loader";
// 设置安全密钥
window._AMapSecurityConfig = {
securityJsCode: '', // 从高德获取
}
export default {
name: 'MapAddress',
props: {
deployedAddress: {
type: String,
default: '123'
},
deployedAreaLatitude: {
type: Number,
default: 39.90923
},
deployedAreaLongitude: {
type: Number,
default: 116.397428
},
},
watch: {
deployedAddress(val) {
this.keywords = val
setTimeout(() => {
//设置marker
this.searchMarker = new AMap.Marker({
map: this.map,
position: [this.deployedAreaLongitude, this.deployedAreaLatitude],
})
//定位
this.map.setCenter([
this.deployedAreaLongitude,
this.deployedAreaLatitude,
])
}, 500)
}
},
mounted() {
this.initMap();
},
data(){
return {
//地图实例
map: null,
//路径坐标点集合
coordinateList: [],
//起点坐标
startCoordinate: {},
//终点坐标
endCoordinate: {},
//起点坐标描述
startCoordinateDescription: '经度:请选择起点' + ', 纬度:请选择起点' ,
//终点坐标描述
endCoordinateDescription: '经度:请选择终点' + ', 纬度:请选择终点',
//选择起点
isStart: true,
//起点Marker
startMarker: null,
//终点Marker
endMarker: null,
//搜索点Marker
searchMarker: null,
// 搜索提示
AutoComplete: null,
// 搜索关键字
keywords: "",
// 搜索节流阀
loading: false,
// 搜索提示信息
options: [],
form: {},
}
},
methods: {
//初始化地图
initMap() {
AMapLoader.reset()
AMapLoader.load({
key: '', // 从高德获取
version: '2.0', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
plugins: ['AMap.AutoComplete', 'AMap.PlaceSearch', 'AMap.Marker', 'AMap.Geocoder', 'AMap.Geolocation'], // 需要使用的的插件列表
AMapUI: {
version: '1.1',
plugins: []
}
}).then((AMap)=>{
// 初始化地图
this.map = new AMap.Map('guide-map',{
viewMode : "2D", // 是否为3D地图模式
zoom : 14, // 初始化地图级别
center : [121.415883, 31.193473], //中心点坐标
resizeEnable: true,
willreadoften: true
});
//鼠标点击事件
this.map.on('click', this.clickMapHandler)
// 搜索提示插件
this.AutoComplete = new AMap.AutoComplete({ city: "全国" });
// 地址逆向解析插件
this.geoCoder = new AMap.Geocoder({
city: '010', //城市设为北京,默认:“全国”
radius: 1000, //范围,默认:500
})
// 正向地理编码
this.geocoder = new AMap.Geocoder({
city: this.keywords,
})
}).catch(e => {
console.log(e);
});
},
// 逆解析地址
toGetAddress() {
let lnglat = [this.form.lng, this.form.lat]
this.geoCoder.getAddress(lnglat, (status, result) => {
if (status === 'complete' && result.regeocode) {
this.keywords = result.regeocode.formattedAddress
this.form.address = result.regeocode.formattedAddress
this.$emit('markerData', this.form)
}
})
},
// 点击地图事件
clickMapHandler(e){
//选择起点
if (this.startMarker) {
// this.map.remove(this.startMarker) 不知道为什么无效,先留着
this.map.clearMap()
}
//标点
this.startMarker = new AMap.Marker({
map: this.map,
position: [e.lnglat.lng, e.lnglat.lat], // 经纬度对象,也可以是经纬度构成的一维数组[116.39, 39.9]
})
this.form.lng = e.lnglat.lng
this.form.lat = e.lnglat.lat
this.toGetAddress()
// 将创建的点标记添加到已有的地图实例
this.map.add(this.startMarker)
},
// 搜索地址
remoteMethod(query) {
if (query !== "") {
this.loading = true;
setTimeout(() => {
this.loading = false;
this.AutoComplete.search(query, (status, result) => {
this.options = result.tips;
});
}, 200);
} else {
this.options = [];
}
},
// 选中提示
currentSelect(val) {
// 清空时不执行后面代码
if (!val) {
return ;
}
// 自动适应显示想显示的范围区域
this.map.setFitView();
//清除marker
if (this.searchMarker){
this.map.remove(this.searchMarker)
}
//设置marker
this.searchMarker = new AMap.Marker({
map: this.map,
position: [val.location.lng, val.location.lat],
});
this.keywords = val.name
//定位
this.map.setCenter([val.location.lng, val.location.lat])
this.$emit('selectData', val)
}
}
}
</script>
35、css3 calc自适应用法
margin-top: calc(100vw * 50 / 1920);
36、数据大屏自适应
<template>
<div class="data-dashboard">
<div ref="viewRef" class="vive-box">
<div class="aaa"></div>
</div>
</div>
</template>
<script>
import {
defineComponent,
reactive,
toRefs,
onMounted,
onBeforeUnmount,
} from 'vue'
// import { ElMessage } from 'element-plus'
export default defineComponent({
name: 'DataDashboard',
setup() {
const state = reactive({
viewRefVal: null,
viewRef: null,
})
// 屏幕自适应处理
const handleResize = () => {
state.viewRefVal = state.viewRef
if (state.viewRefVal) {
state.viewRefVal.style.transform = `scale(${getScale()}) translate(-50%, -50%)`
state.viewRefVal.style.width = `1920px`
state.viewRefVal.style.height = `1080px` // 1080px
}
}
// 根据浏览器大小推断缩放比例
const getScale = (width = 1920, height = 1080) => {
let ww = window.innerWidth / width
let wh = window.innerHeight / height
return ww < wh ? ww : wh
}
onMounted(() => {
window.addEventListener('resize', handleResize)
handleResize()
})
onBeforeUnmount(() => {
window.removeEventListener('resize', handleResize)
})
return {
...toRefs(state),
}
},
})
</script>
<style lang="scss" scoped>
:deep() {
}
.data-dashboard {
width: calc(100vw);
height: calc(100vh);
overflow: auto;
background: radial-gradient(0% 99% at 50% 39%, #020b1c 0%, #000000 100%);
.vive-box {
position: fixed;
top: 50%;
left: 50%;
z-index: 999;
display: flex;
flex-direction: column;
overflow: hidden;
transition: all 0.3s;
transform-origin: left top;
/*!* 设置一个最小的宽度 *!*/
min-width: 1800px;
/* 设置一个最大的宽度 */
max-width: 3800px;
margin: 0 auto;
// border: 1px solid red;
background: radial-gradient(0% 99% at 50% 39%, #020b1c 0%, #000000 100%);
.aaa {
width: 100%;
height: 100%;
background-color: #fff;
}
}
}
</style>
37、修改el-table背景色,border颜色
<template>
<div class="tree-container">
<el-table
border
:data="data"
:header-cell-style="{
background: '#040819 !important',
}"
height="180"
style="width: 100%"
>
<el-table-column label="序列" prop="id" width="50" />
<el-table-column label="日期" prop="date" width="100" />
<el-table-column label="姓名" prop="name" width="100" />
<el-table-column label="内容" prop="content" />
</el-table>
</div>
</template>
<script>
import { defineComponent, reactive, toRefs } from 'vue'
export default defineComponent({
name: 'TableData',
setup() {
const state = reactive({
data: [
{
id: '001',
date: '2023-05-01',
name: '张三',
content: '购买了全面体检套餐',
},
{
id: '002',
date: '2023-05-02',
name: '李四',
content: '预约了内科挂号',
},
],
})
return {
...toRefs(state),
}
},
})
</script>
<style lang="scss" scoped>
:deep() {
.el-table {
background: #040819 !important;
border: 1px solid #235a75;
--el-table-row-hover-bg-color: rgba(35, 90, 117, 0.3);
.has-gutter {
.cell {
color: #56b5fe;
}
}
}
.el-table--border,
.el-table--group {
border: 1px solid #235a75;
}
.el-table--border .el-table__cell {
border: 1px solid #235a75;
}
.el-table td.el-table__cell,
.el-table th.el-table__cell.is-leaf {
border-bottom: 1px solid #235a75;
}
.el-table tr {
background-color: #040819;
}
.el-table--border::after,
.el-table--group::after,
.el-table::before {
background-color: #235a75;
}
}
</style>
38、自定义步骤条
<template>
<view class="steps-container">
<view
v-for="(step, index) in steps"
:key="index"
:class="{'step-item': true,'line-color': index < currentStep}"
>
<view
class="step-circle"
:class="{
'completed': index < currentStep,
'current': index === currentStep,
'pending': index > currentStep
}"
>
<span v-if="index < currentStep">✔</span>
<span class="mark" v-else>{{ index + 1 }}</span>
</view>
<view :class="{'step-label': true,'current-label': index <= currentStep}">{{ step }}</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
currentStep: 2, // 当前步骤,从0开始,2表示当前是第三步
steps: [
"提交申请",
"平台审核",
"平台收货",
"退款",
"完成"
]
}
},
onLoad() {},
onPullDownRefresh() {},
}
</script>
<style lang="scss" scoped>
.steps-container {
display: flex;
align-items: center;
justify-content: space-between;
}
.step-item {
display: flex;
flex-direction: column;
align-items: center;
position: relative;
flex: 1;
.step-circle {
width: 40rpx;
height: 40rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 14rpx;
color: white;
margin-bottom: 8rpx;
z-index: 1;
box-sizing: border-box;
.mark {
font-size: 20rpx;
}
&.completed {
background-color: #4078e0;
span {
font-size: 16rpx;
}
}
&.current {
background-color: #eff3fa;
color: #4078e0;
border: 2rpx solid #4078e0;
}
&.pending {
background-color: #b7cffc;
}
}
.step-label {
text-align: center;
color: #b7cffc;
font-size: 20rpx;
}
.current-label {
color: #4078e0;
}
&::after {
content: '';
position: absolute;
top: 20rpx;
left: 85rpx;
width: calc(100% - 33rpx);
height: 4rpx;
background-color: #b7cffc;
z-index: 0;
}
&:last-child::after {
display: none;
}
}
/* 已完成步骤的连接线颜色 */
.line-color::after {
background-color: #4078e0;
}
</style>