问题汇总
- 问题汇总
- calc计算数学,操作符前后必须有空格才起作用,less得加转义~才起作用
- 浏览器记住用户名密码后,form表单用户名,密码被默认填充
- vue切换页签缓存上一次的检索内容
- 外嵌报表-多张报表通用代码
- 单点登录
- 网页添加水印
- 数组赋值 slice(0)避免修改原数组
- 全局给Element的table组件每一列添加show-overflow-tooltip属性
- sass写样式不管用时加/deep/即可
- 树形结构筛选定位
- el-dialog上添加el-loding
- 遮蔽姓名第二位显示*,正则表达式
- 遮蔽信息,只显示后4位(*1111)
- 只显示前后2个字符,
- table必输
- el-date-picker选择月份限制月份只能选到上月
- 异步接口同步加载并传参
- 解决在less中无法正确计算的问题 加~
- el-table实现跨页多选
- element按需引入message,每次刷新页面都会弹一个空的信息框,解决办法如下
- 防止模拟登录攻击,在每个接口后面添加自定义token,加上身份验证
- base64图片写法
- 随机颜色背景图头像,头像文字是2位名字
- ios a链接下载不起作用,安卓正常
- app页面在部分手机不显示页面(iphone8,iphone13)
- el-table中的el-button 的 disabled属性修改不管用
- js遮蔽客户名称第二位信息,
- 移动端适配rem postcss-pxtorem
- 修改表格行数据数据状态卡顿
- 日期插件修改时间必须用this.$set()才会起作用
- 祖先给孙传值provide,inject,要想实现响应式传值,定义成箭头函数即可
- A>B>C三个组件层层嵌套调用,A是最外面一级,C是最里面一级,A>B>C通过prop层层传值,但是当C值改变后,也要一层一层emit传给A组件,去实现数据更新。
- vue + element + Promise 实现点击保存同时校验两个或者多个form表单
- el-table动态新增行及校验规则
- el-dialog组件解决修改el-dialog__body样式不生效问题
calc计算数学,操作符前后必须有空格才起作用,less得加转义~才起作用
height: calc(100vh - 84px);
less:height: calc(~"100vh - 84px");
浏览器记住用户名密码后,form表单用户名,密码被默认填充
重点代码:$\textcolor{red}{:readonly="readonly" @focus="handleIptClick" autocomplete="off"}$
点击查看代码
<el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
<el-form-item label="用户名" prop="username">
<el-input v-model.number="ruleForm.username" :readonly="readonly" @focus="handleIptClick" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="密码" prop="pass">
<el-input type="password" v-model="ruleForm.pass" :readonly="readonly" @focus="handleIptClick" autocomplete="off"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('ruleForm')">提交</el-button>
<el-button @click="resetForm('ruleForm')">重置</el-button>
</el-form-item>
</el-form>
data(){
return{
readonly:true
}
}
handleIptClick(){
this.readonly = false;
}
vue切换页签缓存上一次的检索内容
重点: $\textcolor{red}{1.每个vue组件页面中必须要写name,name必须跟路由中的name字段保持一致才会缓存}$
$\textcolor{red}{2.
//router.js
{
isMenu: true,
name: 'sysorg',
path: '/app/mas/sysorg',
hidden: false,
component: (resolve) => require(['@/views/admin/sysorg/sysorg'], resolve),
meta: { title: '机构管理', icon: 'dict', noCache: false }
},
//AppMain.vue
<transition name="fade-transform" mode="out-in">
<keep-alive :include="cachedViews">
<router-view :key="key" />
</keep-alive>
</transition>
外嵌报表-多张报表通用代码
1.gientech-ui-ap/src/layout/components/AppMain.vue
<transition name="fade-transform" mode="out-in">
<keep-alive :include="cachedViews">
<router-view :key="key" />
</keep-alive>
</transition>
//此处添加iframe内嵌报表
<transition-group name="fade-transform" mode="out-in">
//自定义组件
<link-home
v-show= "$route.path === item.path"
ref="iframe"
v-for="(item,index) of tabReportList"
:key="item.path"
:iframeId = "'iframe'+index"
:iframeSrc='item.link'
>
</link-home>
</transition-group>
点击查看代码
<template>
<section class="app-main">
<transition name="fade-transform" mode="out-in">
<keep-alive :include="cachedViews">
<router-view :key="key" />
</keep-alive>
</transition>
//此处添加iframe内嵌报表
<transition-group name="fade-transform" mode="out-in">
//自定义组件
<link-home
v-show= "$route.path === item.path"
ref="iframe"
v-for="(item,index) of tabReportList"
:key="item.path"
:iframeId = "'iframe'+index"
:iframeSrc='item.link'
>
</link-home>
</transition-group>
<!-- 微应用Container -->
<div id="vueContainer1"></div>
<div id="vueContainer2"></div>
<div id="vueContainer3"></div>
<div id="vueContainer4"></div>
<div id="vueContainer5"></div>
<div id="vueContainer6"></div>
<div id="vueContainer7"></div>
<div id="vueContainer8"></div>
<div id="vueContainer9"></div>
<div id="vueContainer10"></div>
</section>
</template>
<script>
import {loadMicroApp} from 'qiankun';
import {MICRO_CONF} from '@/register';
//报表组件
import linkHome from './linkHome'
export default {
name: 'AppMain',
components:{linkHome},
data(){
return {
MICRO_CONF: MICRO_CONF,
current: null,
microList: new Map([]),
tabReportList:[]//报表数据
};
},
watch:{
'$route':{
handler(newVal){
this.match2loadMicroApp(newVal)
}
},
//监听报表路由
'$store.state.tagsViews.visitedViews'(newVal){
this.tabReportList = newVal.filter(item=>item.link != undefined)
}
},
computed: {
cachedViews() {
return this.$store.state.tagsView.cachedViews;
},
key() {
return this.$route.path
}
},
created() {
//match2loadMicroApp 根据路由加载
this.match2loadMicroApp(this.$route)
//initLoadMicroApp 一次性加载所有
// this.initLoadMicroApp();
},
methods:{
initLoadMicroApp(){
let vm = this;
this.MICRO_CONF.forEach(conf => {
let props = {store: vm.$store, EventBus: vm.$EventBus};
let newConf = {...conf, props};
const micro = loadMicroApp(newConf);
vm.microList.set(conf.activeRule.toString(), micro)
})
},
match2loadMicroApp(route){
const conf = this.MICRO_CONF.find(item => {
if(Array.isArray(item.activeRule)){
let rules = item.activeRule.filter(rule => {
return route.path.indexOf(rule) !== -1
})
return rules.length > 0;
}else {
return route.path.indexOf(item.activeRule) !== -1
}
})
// 应用跳转
if(conf){
// 未切换子应用
if(this.current && this.current.activeRule.toString() === conf.activeRule.toString()){
const cacheMicro = this.microList.get(conf.activeRule.toString())
// 已缓存应用
if(cacheMicro){
// cacheMicro.update({store: this.$store});
}
}else {
this.current = conf
const cacheMicro = this.microList.get(conf.activeRule.toString())
// 已缓存应用
if(cacheMicro){
// cacheMicro.update({store: this.$store});
}else {
// 未缓存应用
let props = {store: this.$store, EventBus: this.$EventBus};
let newConf = {...conf, props};
const micro = loadMicroApp(newConf);
this.microList.set(conf.activeRule.toString(), micro)
}
}
}
//判断是否是报表外链接
this.$store.state.permission.httpRouters.find(item => {
if(item.path == route.path){
const {path,name, meta, url} = item
const tags = {
fullPath:path,
path:path,
name:name,
meta:{...meta},
link:url
}
this.$store.dispatch('breadcrumb/getLevelList',this.$route)
this.$store.dispatch('tagsView/addView',tags)
}
})
},
}
}
</script>
<style lang="scss" scoped>
@import "~@/assets/styles/variables.scss";
.app-main {
/* 50= navbar 50 */
//min-height: calc(100vh - 50px);
width: 100%;
position: relative;
//overflow: hidden;
overflow-y: auto;
transition: all $sideBarTransDuration ease-in 0s;
//transition: left 0.28s
}
//#vueContainer{
// width: 100%;
// position: relative;
// //overflow: hidden;
// overflow-y: auto;
// transition: all $sideBarTransDuration ease-in 0s;
//}
.fixed-header+.app-main {
//padding-top: 50px;
margin-top: 50px;
height: calc(100vh - 50px);
}
//.fixed-header+#vueContainer {
// //padding-top: 50px;
// margin-top: 50px;
// height: calc(100vh - 50px);
//}
.hasTagsView {
.app-main {
/* 84 = navbar + tags-view = 50 + 34 */
height: calc(100vh - 84px);
}
//#vueContainer{
// height: calc(100vh - 84px);
//}
.fixed-header+.app-main {
margin-top: 84px;
}
//.fixed-header+#vueContainer {
// margin-top: 84px;
//}
}
</style>
<style lang="scss">
// fix css style bug in open el-dialog
//感觉不是bug,先注释
//.el-popup-parent--hidden {
// .fixed-header {
// padding-right: 15px;
// }
//}
</style>
2.gientech-ui-ap/src/layout/components/linkHome.vue
<template>
<div>
<iframe
:id="iframeId"
:src="iframeSrc"
name="iframe"
scrolling="auto"
frameborder='0'
class="iframe-container"
></iframe>
</div>
</template>
<script>
export default{
name:'LinkHome',
props:{
iframeSrc:{
type:String,
default:'/'
},
iframeId:{
type:String,
}
},
watch:{
}
}
</script>
<style scope>
.iframe-container{
position:relative;
width:100%;
min-height:700px;
padding-bottom:16px
}
</style>
3.gientech-ui-ap\src\store\modules\permission.js
重点代码:
httpRouters
SET_HTTPROUTERS:(state,routes) =>{
state.httpRouters = routes
},
//外链特殊处理
if(route.path.slice(0,4) === 'http'){
httpRouters.push(route)
route.url = route.path;
route.path = /app/${route.name}/index
}
click me permission.js文件
// import { constantRoutes } from '@/router'
import { getRouters } from '@/api/menu'
import Layout from '@/layout/index'
const permission = {
state: {
routes: [],
addRoutes: [],
pathTitleMap:[],
httpRouters:[],
microRoutes:{},
},
mutations: {
SET_ROUTES: (state, routes) => {
state.addRoutes = routes
// state.routes = constantRoutes.concat(routes)
state.routes = routes
},
SET_HTTPROUTERS:(state,routes) =>{
state.httpRouters = routes
},
SET_PATHTITLEMAP: (state, pathTitleMap) => {
state.pathTitleMap = pathTitleMap
},
SET_ROUTES_SELECTED: (state, routes) => {
state.routes = routes
},
CHANGE_STATE: (state, { key, value }) => {
if (!state.microRoutes.hasOwnProperty(key)) {
state.microRoutes[key] = value
}
}
},
actions: {
changeState({ commit }, data) {
commit('CHANGE_STATE', data)
},
// 生成路由
async GenerateRoutes({ commit }) {
return new Promise(resolve => {
const httpRouters = []
// 向后端请求路由数据
getRouters().then(res => {
const pathTitleMap = {};
const accessedRoutes = filterAsyncRouter(res.data, 1, pathTitleMap,httpRouters)
// accessedRoutes.unshift({
// path: '/indexRisk',
// name: '风险首页',
// fullPath: '/indexRisk',
// meta: { title: '风险首页', icon: 'dashboard', noCache: true, affix: true }
// })
accessedRoutes.unshift({
path: '/index',
name: '首页',
fullPath: '/index',
meta: { title: '首页', icon: 'dashboard', noCache: true, affix: true }
})
accessedRoutes.push({ path: '*', redirect: '/404', hidden: true })
commit('SET_ROUTES', accessedRoutes)
commit('SET_PATHTITLEMAP', pathTitleMap)
commit('SET_HTTPROUTERS', httpRouters)
resolve(accessedRoutes)
})
})
},
SetSelected({ commit, state }, { item }){
return new Promise(resolve => {
const routes = state.routes;
setSelectFalseFn(routes);
if(item){
setSelectFn(routes, null, item.path);
}
commit('SET_ROUTES_SELECTED', routes)
resolve(routes)
});
}
}
}
function setSelectFalseFn(routes){
for (let i = 0; i < routes.length; i++) {
let item = routes[i];
item.selected = false;
if(item.children && item.children.length > 0){
setSelectFalseFn(item.children);
}
}
}
function setSelectFn(routes, parent, path){
for (let i = 0; i < routes.length; i++) {
let item = routes[i];
if(item.children && item.children.length > 0){
setSelectFn(item.children, item, path);
}
if(item.path === path || item.selected){
item.selected = true;
if(parent){
parent.selected = true;
break;
}
}
}
}
// 遍历后台传来的路由字符串,转换为组件对象
function filterAsyncRouter(asyncRouterMap, level, pathTitleMap,httpRouters) {
return asyncRouterMap.filter(route => {
if (route.component) {
//外链特殊处理
if(route.path.slice(0,4) === 'http'){
httpRouters.push(route)
route.url = route.path;
route.path = `/app/${route.name}/index`
}
// Layout组件特殊处理
else if (route.component === 'Layout') {
route.component = Layout
} else {
// route.component = loadView(route.component)
}
}
route.selected = false;
route.level = level;
if(route && route.isMenu){
pathTitleMap[route.path] = route.meta.title;
}
if (route.children != null && route.children && route.children.length) {
route.children = filterAsyncRouter(route.children, level + 1, pathTitleMap,httpRouters)
}
return true
})
}
export const loadView = (view) => { // 路由懒加载
return (resolve) => require([`@/views/${view}`], resolve)
}
export default permission
// 遍历后台传来的路由字符串,转换为组件对象
function filterAsyncRouter(asyncRouterMap, level, pathTitleMap,httpRouters) {
return asyncRouterMap.filter(route => {
if (route.component) {
//外链特殊处理
if(route.path.slice(0,4) === 'http'){
httpRouters.push(route)
route.url = route.path;
route.path = `/app/${route.name}/index`
}
// Layout组件特殊处理
else if (route.component === 'Layout') {
route.component = Layout
} else {
// route.component = loadView(route.component)
}
}
route.selected = false;
route.level = level;
if(route && route.isMenu){
pathTitleMap[route.path] = route.meta.title;
}
if (route.children != null && route.children && route.children.length) {
route.children = filterAsyncRouter(route.children, level + 1, pathTitleMap,httpRouters)
}
return true
})
}
单点登录
1."O:\ownerCode\gientech-ui-ap-bj单点登录\src\layout\components\Navbar.vue"
async logout() {
this.$confirm('确定注销并退出系统吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$store.dispatch('LogOut').then(() => {
// location.href = '/index';//before
// location.href = '/login' //正常退出
this.BrowserQuit() //易点单点登录退出
})
})
},
// 单点登录退出跳转到易点登录 登录页面 new add
BrowserQuit(){
if(navigator.userAgent.indexOf('Firefox')!== -1 || navigator.userAgent.indexOf('Chorme') !== -1){
window.location.href = "http://易点办公登录地址"
window.close()
}else{
window.opener = null
window.open('','_self')
window.close()
}
}
}
2.O:\ownerCode\gientech-ui-ap-bj单点登录\src\api\login.js
// 获取易点登录 单点登录认证权限
export function linkSso(){
return request({
url:'/system/uauth/address',
method: 'get'
})
}
// 获取易点平台登录返回的code 和 state
export function loginSso(code,state){
return request({
url:'system/uauth/login',
method: 'post',
data: {code,state}
})
}
3.O:\ownerCode\gientech-ui-ap-bj单点登录\src\views\login.vue
完整代码:
<script>
import { getCodeImg } from "@/api/login";
import Cookies from "js-cookie";
import { encrypt, decrypt } from '@/utils/jsencrypt'
export default {
name: "Login",
data() {
return {
codeUrl: "",
cookiePassword: "",
loginForm: {
username: "",//"admin",
password: "",//"admin123",
rememberMe: true,
code: "",
uuid: ""
},
loginRules: {
username: [
{ required: true, trigger: "blur", message: "用户名不能为空" }
],
password: [
{ required: true, trigger: "blur", message: "密码不能为空" }
],
code: [{ required: true, trigger: "change", message: "验证码不能为空" }]
},
loading: false,
redirect: undefined
};
},
watch: {
// 监控 非易点登录,加login 访问绩效系统本身的登录页,登录进入绩效系统
// 本身就有的代码
$route: {
handler: function(route) {
this.redirect = route.query && route.query.redirect;
},
immediate: true
}
},
created() {
// this.getCode();
this.getCookie();
},
methods: {
getCode() {
getCodeImg().then(res => {
this.codeUrl = "data:image/gif;base64," + res.img;
this.loginForm.uuid = res.uuid;
});
},
getCookie() {
const username = Cookies.get("username");
const password = Cookies.get("password");
const rememberMe = Cookies.get('rememberMe')
this.loginForm = {
username: username === undefined ? this.loginForm.username : username,
password: password === undefined ? this.loginForm.password : decrypt(password),
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
};
},
handleLogin() {
this.$refs.loginForm.validate(valid => {
if (valid && !this.loading) {
this.loading = true;
if (this.loginForm.rememberMe) {
Cookies.set("username", this.loginForm.username, { expires: 30 });
Cookies.set("password", encrypt(this.loginForm.password), { expires: 30 });
Cookies.set('rememberMe', this.loginForm.rememberMe, { expires: 30 });
} else {
Cookies.remove("username");
Cookies.remove("password");
Cookies.remove('rememberMe');
}
this.$store
.dispatch("Login", this.loginForm)
.then(() => {
// sessionStorage.setItem("sso","true") //单点登录需要 登录成功后加sso缓存,方便退出登录时判断
this.$router.push({ path: this.redirect || "/" }).catch(err => err);
})
.catch(() => {
// this.getCode();
setTimeout(() => {
this.loading = false;
}, 1000)
});
}
});
}
}
};
</script>
4.O:\ownerCode\gientech-ui-ap-bj单点登录\src\views\sso.vue
<template>
<div id="sso-page"></div>
</template>
<script>
import { loginSso } from '@/api/login'
import { setToken } from '@/utils/auth'
export default {
data() {
return {
};
},
methods: {
initSso(){
const { code, state } = this.$route.query;
if(code !== "" && state !== ""){
loginSso(code,state).then((res)=>{
//sessionStorage.setItem('sso',"true") //已登录标识,单点登录时解开注释
setToken(res.data);
this.$nextTick(()=>{
this.$router.push('/index');
})
})
}else{
this.$message.error(res.msg)
}
}
},
created() {
this.initSso();
},
};
</script>
<style lang="scss" scoped>
</style>
5.O:\ownerCode\gientech-ui-ap-bj单点登录\src\permission.js
![image-20230704141352162](C:\Users\yanni\AppData\Roaming\Typora\typora-user-images\image-20230704141352162.png
import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { getToken } from '@/utils/auth'
import { linkSso } from '@/api/login'
// import Layout from '@/layout/index'
NProgress.configure({ showSpinner: false })
const whiteList = ['/login', '/auth-redirect', '/bind', '/register'] //普通登录
// const whiteList = ['/login', '/auth-redirect', '/bind', '/register', '/sso'] //单点登录sso加入白名单
function getRouters(routes, addRouters){
routes.forEach(menu => {
if(menu.isMenu){
menu.path = menu.path;
addRouters.push(menu);
}
if(menu.children && menu.children.length > 0){
getRouters(menu.children, addRouters);
}
})
}
router.beforeEach(async (to, from, next) => {
NProgress.start()
if (getToken()) { //普通登录
// if (getToken() && sessionStorage.getItem('sso') !== null) { //通过易点,单点登录
/* has token*/
if (to.path === '/login') {
next({ path: '/' })
NProgress.done()
} else {
if (store.getters.roles.length === 0) {
// 判断当前用户是否已拉取完user_info信息
store.dispatch('GetInfo').then(res => {
// 拉取user_info
const roles = res.roles
store.dispatch('GenerateRoutes', { roles }).then(accessRoutes => {
// 测试 默认静态页面
// store.dispatch('permission/generateRoutes', { roles }).then(accessRoutes => {
// 根据roles权限生成可访问的路由表
//let addRoutes = [];
// getRouters(accessRoutes, addRoutes);
// let apps = [{name: 'apps', path: '/apps', component: Layout, children: addRoutes, hidden: true}]
// router.addRoutes(apps) // 动态添加可访问路由表
// router.addRoutes(accessRoutes) // 动态添加可访问路由表
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
})
})
.catch(err => {
store.dispatch('FedLogOut').then(() => {
Message.error(err)
next({ path: '/' })
})
})
} else {
if(store.state.settings.defaultMenu === 'drawerMenu'){
store.dispatch('SetSelected', { item: to });
}
next()
// 没有动态改变权限的需求可直接next() 删除下方权限判断 ↓
// if (hasPermission(store.getters.roles, to.meta.roles)) {
// next()
// } else {
// next({ path: '/401', replace: true, query: { noGoBack: true }})
// }
// 可删 ↑
}
}
} else {
// 没有token
if (whiteList.indexOf(to.path) !== -1 || to.path.startsWith("/quicklogin/")) {
// 在免登录白名单,直接进入
next()
} else {
// sso登录
if(to.query.code && to.query.state){
next({ path: '/sso',replace: true })
}else{
// 通过易点 单点登录
linkSso().then(res=>{
if(res.code === '00000' && res.data.auth){
window.location.href = res.data.auth
}
})
}
// next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页 普通登录
NProgress.done()
}
}
})
router.afterEach(() => {
NProgress.done()
})
6.O:\ownerCode\gientech-ui-ap-bj单点登录\src\router\index.js
网页添加水印
1.O:\ownerCode\gientech-ui-ap-bj单点登录\src\utils\watermark.js
// 创建水印对象
const watermark = {}
// 自定义唯一id
const id = '1.23452384164.123412416'
const setWatermark = (str) => {
if (document.getElementById(id) !== null) {
document.body.removeChild(document.getElementById(id))
}
//创建一个画布
let can = document.createElement('canvas')
//设置画布的长宽
can.width = 430
can.height = 430
let cans = can.getContext('2d')
//旋转角度
cans.rotate(-32 * Math.PI / 180)
cans.font = '14px Vedana'
//设置填充绘画的颜色、渐变或者模式
cans.fillStyle = 'rgba(0, 0, 0, 0.3)'
//设置文本内容的当前对齐方式
cans.textAlign = 'left'
//设置在绘制文本时使用的当前文本基线
cans.textBaseline = 'Middle'
//在画布上绘制填色的文本(输出的文本,开始绘制文本的X坐标位置,开始绘制文本的Y坐标位置)
cans.fillText(str, can.width / 8, can.height / 2)
// cans.fillText(date,can.width / 4, can.height / 3)
let div = document.createElement('div')
div.id = id
div.style.pointerEvents = 'none'
div.style.top = '30px'
div.style.left = '0px'
div.style.position = 'absolute'
div.style.zIndex = '100000'
div.style.width = document.documentElement.clientWidth + 'px'
div.style.height = document.documentElement.clientHeight + 'px'
div.style.background = 'url(' + can.toDataURL('image/png') + ') left top repeat'
// document.body.appendChild(div)
div.style.opacity = '1' // 水印的透明度
// let show = document.getElementById("show") // 控制水印显示的区域,设置id = show,表示在该范围内有水印
// show.appendChild(div)
document.body.appendChild(div)
return id
}
// 该方法只允许调用一次
watermark.set = (str) => {
// let id = setWatermark(str,date) // str,date代表的是两行水印。如果需好几个的话再追加。
// setInterval(() => {
// if (document.getElementById(id) === null) {
// id = setWatermark(str,date)
// }
// }, 500);
if (document.getElementById(id) === null) {
setWatermark(str)
}
window.onresize = () => {
setWatermark(str)
};
}
// 销毁水印
watermark.remove = () => {
if (document.getElementById(id) !== null) {
document.body.removeChild(document.getElementById(id))
}
}
export default watermark
2。O:\ownerCode\gientech-ui-ap-bj单点登录\src\store\modules\user.js
引入水印方法
import { login, logout, getInfo } from '@/api/login'
import { getToken, setToken, removeToken, setTenantId, removeTenantId } from '@/utils/auth'
import { watermark } from '@/utils/watermark.js'//水印
const user = {
state: {
ip: '',
port: '',
token: getToken(),
userId: '',
name: '',
nickName: '',
depts: [],
deptIds: [],
authDeptIds: [],
avatar: '',
roles: [],
permissions: [],
systemDate: '',
avatarPrefix: '',
userInfo:[]//水印
},
mutations: {
SET_IP: (state, ip) => {
state.ip = ip
},
SET_PORT: (state, port) => {
state.port = port
},
SET_TOKEN: (state, token) => {
state.token = token
},
SET_NAME: (state, name) => {
state.name = name
},
SET_USERID: (state, userId) => {
state.userId = userId
},
SET_NICKNAME: (state, nickName) => {
state.nickName = nickName
},
SET_DEPTS: (state, depts) => {
state.depts = depts
},
SET_DEPT_IDS: (state, deptIds) => {
state.deptIds = deptIds
},
SET_AUTH_DEPT_IDS: (state, authDeptIds) => {
state.authDeptIds = authDeptIds
},
SET_AVATAR: (state, avatar) => {
state.avatar = avatar
},
SET_ROLES: (state, roles) => {
state.roles = roles
},
SET_PERMISSIONS: (state, permissions) => {
state.permissions = permissions
},
SET_SYSTEMDATE: (state, systemDate) => {
state.systemDate = systemDate
},
SET_AVATARPREFIX: (state, avatarPrefix) => {
state.avatarPrefix = avatarPrefix
},
// 水印添加
SET_USERINFO: (state, userInfo) => {
state.userInfo = userInfo
localStorage.removeItem('userInfo')
}
},
actions: {
// 登录
Login({ commit }, userInfo) {
const username = userInfo.username.trim()
const password = userInfo.password
const code = userInfo.code
const uuid = userInfo.uuid
return new Promise((resolve, reject) => {
login(username, password, code, uuid).then(res => {
setToken(res.token)
// wyn
setTenantId(res["X-TENANT-HEADER"])
commit('SET_TOKEN', res.token)
resolve()
}).catch(error => {
reject(error)
})
})
},
// 获取用户信息
GetInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo(state.token).then(res => {
const { user, systemDate, ip, port, avatarPrefix } = res;
const avatar = !user.avatar ? require('@/assets/image/profile.jpg') : avatarPrefix + user.avatar
if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组
commit('SET_ROLES', res.roles)
commit('SET_PERMISSIONS', res.permissions)
} else {
commit('SET_ROLES', ['ROLE_DEFAULT'])
}
commit('SET_IP', ip)
commit('SET_PORT', port)
commit('SET_NAME', user.userName)
commit('SET_USERID', user.userId)
commit('SET_NICKNAME', user.nickName)
commit('SET_DEPTS', [user.dept])
commit('SET_DEPT_IDS', [user.deptId + ''])
commit('SET_AUTH_DEPT_IDS', res.authDeptIds);
commit('SET_AVATAR', avatar)
commit('SET_SYSTEMDATE', systemDate)
commit('SET_AVATARPREFIX', avatarPrefix)
localStorage.setItem('userInfo', JSON.stringify(res.user))
// 水印-s
const info = res.user;
let str = `${info?.nickName}、${info?.userName}、${new Date().toLocaleDateString()}`
watermark.set(str);
// 水印-e
resolve(res)
}).catch(error => {
reject(error)
})
})
},
// 退出系统
LogOut({ commit, state }) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
watermark.remove()//退出移除水印
commit('SET_TOKEN', '')
commit('SET_ROLES', [])
commit('SET_PERMISSIONS', [])
commit('SET_USERINFO', [])//移除水印,清空用户信息,防止换个用户登录,显示上一个用户水印
removeToken()
resolve()
}).catch(error => {
reject(error)
})
})
},
// 前端 登出
FedLogOut({ commit }) {
return new Promise(resolve => {
commit('SET_TOKEN', '')
removeToken()
resolve()
})
}
}
}
export default user
数组赋值 slice(0)避免修改原数组
//第一种
let a = [2,3,4]
let b=a
b[0] = 5;
b = [5,3,4] a=[5,3,4]//直接赋值,会改变原数组的值,内存没变,只是指针变了,浅拷贝
//第二种 深拷贝
let c = [7,8,9]
let d = c.slice(0)
d[1] = 1
d = [1,8,9] c = [7,8,9] //不改变原值,深拷贝
全局给Element的table组件每一列添加show-overflow-tooltip属性
1.在main.js中添加如下代码
在main.js中修改Element UI Table的默认值,代码如下:
import Element from ‘element-ui’
//全局修改element默认配置
Element.TableColumn.props.showOverflowToolTip = {
type: Boolean,
default:true
}
Element.TableColumn.props.align.default = "center"
Vue.use(Element)
sass写样式不管用时加/deep/即可
/deep/ .el-table{
.cell{
text-align:center
}
}
树形结构筛选定位
1.html内容
<!-- 树结构检索定位 -->
<el-input v-model="selectData" @change="triggerVisible" placeholder="输入回车检索"/>
<div class="tree-box">
<el-tree
:check-strictly="!form.deptCheckStrictly"
:data="deptOptions"
:props="defaultProps"
class="tree-border"
default-expand-all
empty-text="加载中,请稍后"
node-key="id"
ref="dept"
show-checkbox
highlight-current
></el-tree>
</div>
2.js代码
点击查看代码
// 递归遍历所有树节点
getAllNodes(node=[],arr=[]){
for(let item of node){
arr.push({label:item.label,id:item.id})
let parentArr = []
if(item.children) parentArr.push(...item.children)
if(parentArr && parentArr.length) this.getAllNodes(parentArr,arr)
}
return arr
},
// 树形数据筛选定位
triggerVisible(val){
let nodeOffsetTop = 0;
this.$nextTick(()=>{
for(let i=0;i<this.getAllNodes(this.deptOptions).length;i++){
if(this.getAllNodes(this.deptOptions)[i].label.indexOf(val) !== -1){
this.$refs.dept.setCurrentKey(this.getAllNodes(this.deptOptions)[i].id)
// 真实dom渲染完再获取dom
setTimeout(()=>{
nodeOffsetTop=document.querySelector(".is-current").scrollTop
// 方法二:可以定位到div中间
if(nodeOffsetTop && nodeOffsetTop > 120){
let scrollH = nodeOffsetTop - 120/2
//注意这里的元素必须是有overflow:auto属性的才可以
document.querySelector(".tree-box").scrollTop = scrollH
}
// document.querySelector(".is-current").scrollIntoView();//方法一,类似于锚点
},1000)
return//检索结果有多个,只定位到第一个
}
}
})
},
el-dialog上添加el-loding
重点代码:
- :append-to-body="false"
- target:document.querySelector("#dialogId .el-dialog .el-dialog__body"),
- fullscreen:'false',
<el-dialog
title="提示"
:visible.sync="dialogVisible"
width="30%"
id="dialogId"
:append-to-body="false"
:before-close="handleClose">
</el-dialog>
showDialog(){
this.dialogVisible = true;
//loading
const loading = this.$loading({
lock:true,
text:loading,
target:document.querySelector("#dialogId .el-dialog .el-dialog__body"),
fullscreen:'false',
spinner:'el-icon-loading'
})
//获取接口数据,异步,
getData().then(res=>{
this.dataArr = res.data;
loading.close();
})
}
遮蔽姓名第二位显示*,正则表达式
str.replace(/^(.)(?<=.)./g,'$1*')
示例:
一样前期:“一前期”
寒霜:“寒”
忘恩帅:“王*帅”
遮蔽信息,只显示后4位(*1111)
str.replace(/^.+(.{4})$/,("*$1"))
只显示前后2个字符,
str.replace(/^(.).+(.)$/,("$1*$2"))
示例:
一样前期:“一期”
忘恩帅:“王帅”
table必输
点击查看代码
<template>
<div class="hello">
<el-form :model="popup_Win" ref="popup_Win">
<el-table
:data="popup_Win.tableData"
style="width: 100%">
<el-table-column
width="280"
prop="num"
label="计算器">
<template slot-scope="scope">
<el-form-item :prop="'tableData.' + scope.$index +
'.num'" :rules="rules.num"
style="margin-bottom: 0;"
:inline-message="true">
<el-input-number v-model="scope.row.num"
:min="0" :max="scope.row.num" label=""
οninput="value=value.replace(/[^\d]/g,'')">
</el-input-number>
</el-form-item>
</template>
</el-table-column>
<el-table-column
prop="count"
label="数量">
<template slot-scope="scope">
<el-form-item :prop="'tableData.' + scope.$index +
'.count'" :rules="rules.count"
style="margin-bottom: 0;"
:inline-message="true">
<el-input v-model="scope.row.count"></el-input>
</el-form-item>
</template>
</el-table-column>
</el-table>
</el-form>
<div class="buttonbox">
<el-button type="primary" @click="confirm()">确定</el-button>
</div>
</div>
</template>
点击查看代码
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
},
data() {
return {
popup_Win: {
tableData: [{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄',
num: 20,
count: ''
}, {
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄',
num: 0,
count: ''
}, {
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
num: 0,
count: ''
}, {
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄',
num: 0,
count: ''
}]
},
rules: { // 验证规则
num: [
{
required: true,
message: '请输入',
trigger: 'change'
}
],
count: [
{
required: true,
message: '请输入',
trigger: 'blur'
}
]
}
}
},
methods: {
// 确定按钮
confirm() {
var this_ = this;
this_.$refs['popup_Win'].validate((valid) => {
if (valid) {
alert(1)
} else {
console.log('error submit!!');
return false;
}
});
}
},
}
</script>
el-date-picker选择月份限制月份只能选到上月
点击查看代码
<el-date-picker
v-model="date"
type="monthrange"
value-format="yyyy-MM"
unlink-panels
range-separator="至"
start-placeholder="开始月份"
end-placeholder="结束月份"
:picker-options="pickerOptions">
</el-date-picker>
点击查看代码
data() {
return {
date: [],
pickerOptions: {
disabledDate(time) {
let year = new Date().getFullYear()
let month = new Date().getMonth() // 上月
let days = new Date(year, month, 0).getDate() // 上月总天数
return time.getTime() > Date.now() - 24 * 60 * 60 * 1000 * `${days}`
}
}
}
}
异步接口同步加载并传参
说明:async await 同步,按顺序执行,await 后面的都是接口
接口如下:
export function getIndex(){
const url = "dsss"
return new Promise((resolve,reject)=>{
http.get(url)
.then(res=>{
resolve(res)
})
})
}
export function getComDic(indexNo){
const url = "dsss"
const params = {indexNo}
return new Promise((resolve,reject)=>{
http.get(url,{params})
.then(res=>{
resolve(res)
})
})
}
mounted(){
this.fetchData()
},
methods:{
async fetchData(){
try{
const {data:typeData} = await getIndex()
this.tabArr = typeData
const resDic = await getComDic(typeData[0].dictid)
const infolist = await getInfo()
}
catch(e){
console.warn(e)
}
}
}
解决在less中无法正确计算的问题 加~
calc(~"100% - 100px") 有时候%也不会起作用可以用vh代替即:
calc(~"100vh - 100px")
el-table实现跨页多选
实现重点:
- el-table添加属性row-key
- el-table-column添加属性reserve-selection
<el-table
row-key="id"
@selection-change="handleChange"
>
<el-table-column type="selection" :reserve-selection="true"></el-table-column>
</el-table>
element按需引入message,每次刷新页面都会弹一个空的信息框,解决办法如下
import { Dialog,Pagination ,Message,Cascader,Footer,Button} from 'element-ui'
Vue.use(Dialog)
Vue.use(Pagination)
//注意是Message.name 关键点哦
Vue.component(Message.name, Message)
Vue.use(Cascader)
Vue.use(Footer)
Vue.use(Button)
Vue.prototype.$message = Message
防止模拟登录攻击,在每个接口后面添加自定义token,加上身份验证
1.找到request.js文件,每个人文件名可能不一样
在获取token后添加
service.interceptors.request.use(config=>{
const {url} = config;
//是否需要设置token
const isToken = (config.headers || {}).isToken === false
if(getToken() && !isToken){
//让每个请求携带自定义token,请根据实际情况自行修改
config.headers['Authorization'] = 'Bearer '+ getToken();
const tokenCode = getToken().split('').map(v=>
v.charCodeAt() -2).join('');
//charCodeAt()方法用于选取字符串中某一位置上的单个字符
const onlyKey = getIndexStr(tokenCode,[0,Math.floor(tokenCode.length/10)]);
const spaceStr = url.includes('?') ? '&' : '?';
config.url = `${url}${spaceStr}onlyKey=${onlyKey}`
}
})
js文件
/*按下标选择某字符串
getIndexStr(tokenCode,[0,Math.floor(tokenCode.length/10)])
*/
export function getIndexStr(str='',indexs=[]){
let res = '';
if(str.length < 10){
return str
}
for(let i = 0;i<str.length;i++){
indexs.forEach(n=>{
if(i%n ===0){
res = res + str[i]
}
})
}
return res;
}
base64图片写法
<img :src="`data:image/jpg;base64,${item}`"
随机颜色背景图头像,头像文字是2位名字
应用场景:前三名有奖杯图片,3名之后数据头像随机背景
html:
<div v-for="(item,index) in peolist.slice(0,3)" :key="index">
<div :style="getRandomStyle(index,item && item.username)">
<div class="name">{{item && item.nickname}}</div>
</div>
</div>
<div v-for="(item,index) in peolist.slice(3)" :key="index">
<div :style="getRandomStyle(-1,item && item.username)">
<div class="name">{{item && item.nickname}}</div>
</div>
</div>
js:
getRandomStyle(t,un){
//前三名 index-》t 0 1 2
const bgColors = ['#bdbdcd','fea52e','#ca906e','#9688f1','#fb6262']
}
const bgFroms = ['#ff7211','#2865f4','#fe7b7b','#ffc71b','#22aafa'];
const bgTos = ['rgb(252,176,123)','#63a0fc','#ff9d9e','rgb(254,230,104)','#7bccfb']
//随机index
let ri = t>-1?t:Math.floor(Math.random() *5)
//按username 如005734 最后一位 n>4 ?n-5: n(res:0-9 取 0-4下标)
const lastN = Number(un[un.length -1])
let ri = t> -1 ? t :lastN >4 ? lastN -5 : lastN;
let res = {};
res = {
'background':`linear-gradient(45deg,${bgFroms[ri]} 0%,${bgTos[ri]} 100%)`
}
return res;
ios a链接下载不起作用,安卓正常
问题描述:苹果手机点击a链接没有反应,也不跳转,安卓手机可正常点击跳转
解决办法:吧target="_blank"去掉就好了
app页面在部分手机不显示页面(iphone8,iphone13)
问题描述:vue开发的app,部署之后部分苹果手机不显示页面,vconsole按钮也打印不出来,
查出原因:是因为main.js中引入的方法中有正则表达式,部分手机不支持正则表达式,会报错:
报错信息:invalid regular expression:invalid group specifier name
解决办法:不要用正则表达式
问题追踪思路:吧main.js中的方法移除,方法引入到相关页面,打包可以显示vconsole按钮,当点击js相关页面时,发现有报错信息
el-table中的el-button 的 disabled属性修改不管用
问题描述:el-table下写了静态按钮,修改disabled属性修改不管用
解决办法:加个template就可以生效了
//错误代码
<el-table>
<el-table-column>
<el-button :disabled="disabledFlag" @click="fn1"></el-button>
</el-table-column>
</el-table>
disabledFlag:false
js:
fn1(){
this.disabledFlag = true
}
正确代码如下:
<el-table>
<el-table-column>
<template slot-scope="scope">
<el-button :disabled="disabledFlag" @click="fn1"></el-button>
</template>
</el-table-column>
</el-table>
disabledFlag:false
js:
fn1(){
this.disabledFlag = true
}
js遮蔽客户名称第二位信息,
hiddenNameInfo(data = ''){
let index = 1
if(data.length >= 2){
return data.substr(0,index) + '*' +data.substr(index+1)
}else{
return data
}
}
移动端适配rem postcss-pxtorem
1.安装:npm i postcss-pxtorem@5.1.1 --save-dev
2.新建.postcssrc.js文件
module.exports = {
"plugins":{
"postcss-pxtorem":{
"rootValue":18.5,
"propList":["*"],
},
"postcss-import":{},
"autoprefixer":{}
}
}
3.新建rem.js
//兼容处理
function setHtml(){
//获取设备宽度
var deviceWidth = document.documentElement.offsetWidth;
//给html标签设置fontSize,就是给rem赋值
document.documentElement.style.fontSize = deviceWidth/375 * 10 + 'px';
}
//窗口大小变化时执行
window.onresize = setHtml;
//页面初始话也要触发
setHtml();
4.在main.js中引入rem.js
import "@/rem.js"
修改表格行数据数据状态卡顿
问题描述:修改当前行数据 直接赋值会卡顿 row.isEdit = true
解决方案:用this.$set(row,'isEdit',true)可以立马更改数据状态
日期插件修改时间必须用this.$set()才会起作用
祖先给孙传值provide,inject,要想实现响应式传值,定义成箭头函数即可
//祖先组件
data(){
return{
msg:'这是祖先数据'
}
},
provide:function(){
return{
grandMsg:()=>this.msg //实现响应式传值
}
},
methods:{
changedMsg(){
this.msg= 'changed 祖先'//这个msg可能事接口返回,会异步返回
}
}
//孙组件
inject:['grandMsg'],
computed:{
getGrandMsg(){//这个变量就是用来获取响应式祖先数据
return this.grandMsg()
}
}
A>B>C三个组件层层嵌套调用,A是最外面一级,C是最里面一级,A>B>C通过prop层层传值,但是当C值改变后,也要一层一层emit传给A组件,去实现数据更新。
此处需要注意,子组件不能修改父组件的prop属性,所以可以定义一个变量深拷贝prop值,然后层层emit
//这里只举例2级组件,多级同理
//A组件
<div>
这是A组件
<B msgB="msg" @refrshAmsg="freshMsg"></B>
</div>
data(){
return{
msg:'666'
}
}
methods:{
freshMsg(val){
this.msg = val
}
}
//B组件
<div>
这是B组件
<C ></C>
</div>
props:{
msgB:{
type:string,
default:''
}
},
methods:{
changedmsgB(){
let str = JSON.parse(JSON.stringfy(this.msgB))
this.$emit('refrshAmsg',str)
}
}
vue + element + Promise 实现点击保存同时校验两个或者多个form表单
methods: {
// 前端发给后端的参数
postParams(){
let params = {};
// name 就是后端需要的字段
params.name = this.name;
return params; //最后把结果return出去
},
save() {
let arr = ["form", "tableform"]; //需要校验的两个表单
var resultArray = []; //用来接受返回结果的数组
var _self = this; // 保存this 防止丢失
function checkForm(formName) {
//封装验证表单的函数
var result = new Promise(function(resolve, reject) {
_self.$refs[formName].validate(valid => {
if (valid) {
resolve();
} else {
reject();
}
});
});
resultArray.push(result); //push 得到promise的结果
}
arr.forEach(item => {
//根据表单的ref校验
checkForm(item);
});
Promise.all(resultArr).then(function() {
// _self.msg 这个是自己传过来的值 用于判断是新增还是编辑 调用不同的接口
if (_self.msg == "预警方案新增") {
// _self.postParams() 是自己把前端的请求参数 封装了一个函数
capacitySave(_self.postParams()).then(res => {
if (res.data.code == 200) {
_self.$message.success("新增成功");
_self.drawer = false;
// 子传父 用来刷新列表
_self.$emit("list");
} else {
_self.$message.error("新增失败");
_self.drawer = false;
}
});
} else {
capacitySave(_self.postParams()).then(res => {
if (res.data.code == 200) {
_self.$message.success("编辑成功");
_self.drawer = false;
_self.$emit("list");
} else {
_self.$message.error("编辑失败");
_self.drawer = false;
}
});
}
});
},
}
el-table动态新增行及校验规则
<template>
<el-form ref="formName" :model="studentData">
<el-table :data="studentData.studentList" stripe>
<el-table-column label="姓名" align="left">
<template slot-scope="scope">
<el-form-item
size="small"
:prop="'studentList.' + scope.$index + '.name'"
:rules="rules.name"
>
<el-input v-model="scope.row.name" placeholder="请输入姓名" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="性别">
<template slot-scope="scope">
<el-form-item
size="small"
:prop="'studentList.' + scope.$index + '.sex'"
:rules="rules.sex"
>
<el-select v-model="scope.row.sex" placeholder="请选择性别">
<el-option label="男" value="1"></el-option>
<el-option label="女" value="0"></el-option>
</el-select>
</el-form-item>
</template>
</el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button
type="text"
id="delete-btn"
@click="deleteRow(scope.row, scope.$index)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<div style="margin: 20px; text-align: right">
<el-button @click="addRow" size="small">新增</el-button>
<el-button @click="saveData('formName')" size="small">确定</el-button>
</div>
</el-form>
</template>
<script>
export default {
data() {
return {
studentData: {
studentList: [
{
name: "王小虎",
sex: "男",
},
{
name: "Linda",
sex: "女",
},
],
},
//验证规则
rules: {
name: [
{
required: true,
message: "请输入姓名",
trigger: ["blur", "change"],
},
],
sex: [
{
required: true,
message: "请选择性别",
trigger: ["blur", "change"],
},
],
},
};
},
methods: {
// 添加一行
addRow() {
const item = {
name: "",
sex: "",
};
this.studentData.studentList.push(item);
},
// 删除一行
deleteRow(row, index) {
this.studentData.studentList.splice(index, 1);
},
saveData(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
alert("submit!");
} else {
console.log("error submit!!");
return false;
}
});
},
},
};
</script>
<style scoped>
.el-form {
width: 60%;
background: white;
border: 1px solid gainsboro;
}
/deep/ .el-input {
width: 100%;
}
</style>
需要借助el-form的校验,el-table外层嵌套一层el-form,使用el-form的校验机制
由于每行都需要校验,借助scope.$index
给表单设置rules属性传入验证规则
给表单设置model属性传入表单数据
给表单项(Form-ltem)设置prop属性,值为需要校验的字段名
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_45609659/article/details/127750902
el-dialog组件解决修改el-dialog__body样式不生效问题
<el-dialog :title="title" v-model="handleAddShow" width="600px" custom-class="vDialog">
首先要通过custom-class="vDialog"设置dialog的class属性, 在通过
.vDialog .el-dialog__body {
padding-top: 0px !important;
}
来修改样式, 如果这样写了还不生效, 找到最底层的index.html页面, 在该页面的style样式中写全局该样式
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/m0_61480542/article/details/135947402