• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
丶菜鸟也会飞
博客园    首页    新随笔    联系   管理    订阅  订阅
vue项目开发问题汇总

问题汇总

目录
  • 问题汇总
    • 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样式不生效问题
    • 修改接口请求头数据类型
    • 数字输入框为0时获取焦点清空,若值为空,默认为0,全局自定义指令
    • handsontable当renderAllRows: false,时可以提升页面加载时间,但是保存样式会丢失 只能拿到当前窗口的样式,解决办法如下:
    • 表格的超出tooltip样式优化修改
    • vex-table 实现虚拟滚动,解决大量数据卡顿问题
    • 拖拽改变div高度
    • 文件上传限制文件类型
    • treeselect树组件弹框被遮盖

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>

外嵌报表-多张报表通用代码

image-20230626143643618

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

image-20230704111914553

image-20230704112003438

完整代码:

<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-20230704141308889

![image-20230704141352162](C:\Users\yanni\AppData\Roaming\Typora\typora-user-images\image-20230704141352162.pngimage-20230704141705966

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

image-20230704141907302、

点击查看代码
import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

/* Layout */
import Layout from '@/layout'
import { resolve } from 'core-js/fn/promise'

/**
 * Note: 路由配置项
 *
 * hidden: true                   // 当设置 true 的时候该路由不会再侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1
 * alwaysShow: true               // 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面
 *                                // 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面
 *                                // 若你想不管路由下面的 children 声明的个数都显示你的根路由
 *                                // 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由
 * redirect: noRedirect           // 当设置 noRedirect 的时候该路由在面包屑导航中不可被点击
 * name:'router-name'             // 设定路由的名字,一定要填写不然使用<keep-alive>时会出现各种问题
 * meta : {
    noCache: true                // 如果设置为true,则不会被 <keep-alive> 缓存(默认 false)
    title: 'title'               // 设置该路由在侧边栏和面包屑中展示的名字
    icon: 'svg-name'             // 设置该路由的图标,对应路径src/assets/icons/svg
    breadcrumb: false            // 如果设置为false,则不会在breadcrumb面包屑中显示
  }
 */
const isDev = process.env.NODE_ENV === 'development'
const loginFile = isDev ? "login" : "error/404"
const loginRoute = {
  path:'/login',
  component: (resolve) => require([`@/views/${loginFile}`],resolve),
  hidden: true
}

// 公共路由
export const constantRoutes = [
  {
    path: '/redirect',
    component: Layout,
    hidden: true,
    children: [
      {
        path: '/redirect/:path(.*)',
        name: 'redirect',
        component: (resolve) => require(['@/views/redirect'], resolve)
      }
    ]
  },
  loginRoute,
  {
    path: '/sso',
    component: (resolve) => require(['@/views/sso.vue'], resolve),
    hidden: true
  },
  // {
  //   path: '/login',
  //   component: (resolve) => require(['@/views/login'], resolve),
  //   hidden: true
  // },
  {
    path: '/404',
    component: (resolve) => require(['@/views/error/404'], resolve),
    hidden: true
  },
  {
    path: '/401',
    component: (resolve) => require(['@/views/error/401'], resolve),
    hidden: true
  },
  {
    path: '/app/*',
    name: 'appLayout',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: Layout
  },
  {
    path: '',
    component: Layout,
    redirect: 'index',
    // redirect: 'indexRisk',
    // selected: false,
    children: [
      {
        path: 'index',
        component: (resolve) => require(['@/views/index'], resolve),
        name: '首页',
        meta: { title: '首页', icon: 'dashboard', noCache: true, affix: true }
      },
      {
        path: 'shenpi',
        component: (resolve) => require(['@/views/shenpi'], resolve),
        name: '审批流程',
        meta: { title: '审批流程', icon: 'dashboard', noCache: false, affix: false }
      },
      {
        path: '/indexRisk',
        component: (resolve) => require(['@/views/admin/homePages/indexRisk.vue'], resolve),
        name: '风险首页',
        meta: { title: '风险首页', icon: 'dashboard', noCache: false, affix: false }
      },
      {
        path: '/mbmbankchartanalysis',
        component: (resolve) => require(['@/views/admin/homePages/mbmpreanalysis/mbmbankchartanalysis.vue'], resolve),
        name: '预算首页',
        meta: { title: '预算首页', icon: 'dashboard', noCache: false, affix: false }
      },
      {
        path: '/performanceAssessment',
        component: (resolve) => require(['@/views/admin/homePages/performanceAssessment/performanceAssessment.vue'], resolve),
        name: '行领导首页',
        meta: { title: '行领导首页', icon: 'dashboard', noCache: false, affix: false }
      },
      {
        path: '/perAccountManagerReport',
        component: (resolve) => require(['@/views/admin/homePages/perAccountManagerReport/perAccountManagerReport.vue'], resolve),
        name: '客户经理首页',
        meta: { title: '客户经理首页', icon: 'dashboard', noCache: false, affix: false }
      },
      {
        path: '/perTOPTen',
        component: (resolve) => require(['@/views/admin/homePages/perBranchLineStatement/perBranchLineStatement.vue'], resolve),
        name: '支行长首页',
        meta: { title: '支行长首页', icon: 'dashboard', noCache: false, affix: false }
      },
    ]
  },
  {
    path: '/user',
    component: Layout,
    hidden: true,
    redirect: 'noredirect',
    children: [
      {
        path: 'profile',
        component: (resolve) => require(['@/views/system/user/profile/index'], resolve),
        name: 'Profile',
        meta: { title: '个人中心', icon: 'user' }
      }
    ]
  },
  {
    path: '/system',
    component: Layout,
    hidden: true,
    redirect: 'noredirect',
    children: [
      {
        path: 'noticeuser',
        component: (resolve) => require(['@/views/system/noticeuser/index'], resolve),
        name: 'noticeuser',
        meta: { title: '通知公告查看', icon: '' }
      }
    ]
  },
  {
    path: '/quicklogin/:auth',
    component: (resolve) => require(['@/views/quicklogin'], resolve),
    hidden: true,
  }
]


export default new Router({
  mode: 'history', // 去掉url中的#
  scrollBehavior: () => ({ y: 0 }),
  routes: constantRoutes
})

网页添加水印

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

引入水印方法

image-20230704144951081image-20230704145020341

image-20230704144951081image-20230704145020341

image-20230704145118282
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

重点代码:

  1. :append-to-body="false"
  2. target:document.querySelector("#dialogId .el-dialog .el-dialog__body"),
  3. 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,加上身份验证

image

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

修改接口请求头数据类型

重点这句:headers: { "Content-Type": 'application/x-www-form-urlencoded' },
GetTableData(queryData, pageSize, page) { const data = {...queryData, field: 'id,initCode,initName,defaultVal,refValue,initDesc,tenancyId', page: page, rows: pageSize } return request({ url: '/gientech/mas/massysinit/datagrid', // headers: { "Content-Type": 'application/x-www-form-urlencoded' }, method: 'post', data: qs.stringify(data) }) },

数字输入框为0时获取焦点清空,若值为空,默认为0,全局自定义指令

点击查看代码
//在main.js中写
// 数字输入框非控制按钮初次获取焦点清空默认值
Vue.directive("inputDefault",{
  inserted: function(el,binding,vnode){
  //注意这个根据当前元素层级 可能需要修改el.children[0].children[0]
    let defaultVal = el.children[0].children[0].value
    el.children[0].children[0].addEventListener("focus",()=>{
      if(el.children[0].children[0].value == defaultVal){
        el.children[0].children[0].value = ""
      }
    })
    el.children[0].children[0].addEventListener("blur",()=>{
      if(el.children[0].children[0].value == ""){
        el.children[0].children[0].value = defaultVal
      }
    })
  }
})
//html中添加指令v-inputDefault
<el-input-number v-model="form.year1"  :precision="6"  v-inputDefault :controls="false"></el-input-number>

handsontable当renderAllRows: false,时可以提升页面加载时间,但是保存样式会丢失 只能拿到当前窗口的样式,解决办法如下:

`
/**
* 将二维数组样式数据转换为扁平化的对象数组格式
* @param {Array[]} stylesArray 二维数组格式的样式数据
* @returns {Object[]} 扁平化的样式对象数组
*/
flattenStyles(stylesArray) {
const result = [];

  // 遍历每一行
  stylesArray.forEach((rowStyles, rowIndex) => {
    // 遍历每一列的样式
    rowStyles.forEach((cellStyle, colIndex) => {
      if (cellStyle) {
        if (cellStyle.className) {
          result.push({
            row: rowIndex,
            col: colIndex,
            className: cellStyle.className || "",
            // style: cellStyle.style || {}
          });
        }
      }
    });
  });

  return result;
},
getAllStyles() {
  // 获取所有单元格元数据
  const allMeta = this.hot.getCellsMeta();

  // 转换为按行/列组织的样式数据
  const styles = [];
  allMeta.forEach((meta) => {
    if (!styles[meta.row]) {
      styles[meta.row] = [];
    }
    styles[meta.row][meta.col] = {
      className: meta.className,
      renderer: meta.renderer,
      style: {
        background: meta.background,
        color: meta.color,
        // 其他样式属性
      },
    };
  });

  return styles;
},
let Allstyle = this.getAllStyles();
  let allClassMates = this.flattenStyles(Allstyle);
//allClassMates [{row:5,col:4,className:'CCCC'}]

`

表格的超出tooltip样式优化修改

`
//这里注意得再全局样式修改才起作用

/* 在全局样式文件中添加 /
.el-tooltip__popper {
max-width: 300px !important; /
设置最大宽度 /
word-break: break-word !important; /
允许单词换行 /
line-height: 1.5 !important; /
行高 /
padding: 8px 12px !important; /
内边距 */
}
`

vex-table 实现虚拟滚动,解决大量数据卡顿问题

点击查看代码
<vxe-table
          ref="xTable"
          auto-resize
          :border="true"
          show-overflow
          keep-source
          :edit-rules="validRules"
          :scroll-y="{ enabled: true, gt: 100 }"
          :edit-config="editConfig"
          :checkbox-config="{ checkField: 'checked' }"
          :filter-config="{ filterMethod: filterMethodFn, showIcon: false }"
          @edit-disabled="editDisabledEvent"
          @checkbox-all="selectAllEvent"
          @checkbox-change="selectChangeEvent"
        >
          <vxe-table-column type="checkbox" width="80" />
          <vxe-table-column type="seq" width="80" title="序号" />
          <vxe-table-column
            field="code"
            :title="codeColumnName"
            :edit-render="{}"
            :formatter="nameFormatter"
            :filters="[{ data: '' }]"
          >
            <template #edit="scope">
              <el-input
                v-model="scope.row.code"
                :controls="false"
                :type="nameInputType"
                :maxlength="nameInputMaxlength"
                @change="tableDataChange(scope)"
              />
            </template>
            <template #default="scope">
              {{ nameFormatter({ cellValue: scope.row.code }) }}
             
            </template>
          </vxe-table-column>
          <vxe-table-column
            v-if="form.name != '巴塞尔风险权重'"
            field="name"
            title="名称"
            :edit-render="{
              name: 'ElInput',
              events: { change: tableDataChange },
            }"
            :filters="[{ data: '' }]"
          >
            <template #default="scope">
              {{ scope.row.name }}
            </template>
          </vxe-table-column>

        
          <vxe-table-column
            v-if="enabledEdit"
            title="操作"
            align="center"
            width="200"
          >
            <template #default="{ row, $rowIndex, data }">
              <gt-button
                :disabled="0 === $rowIndex || queryParams.name.length > 0"
                type="text"
                @click="moveUp(row, $rowIndex)"
              >
                上移
              </gt-button>
              <gt-button
                :disabled="
                  data.length - 1 === $rowIndex || queryParams.name.length > 0
                "
                type="text"
                @click="moveDown(row, $rowIndex)"
              >
                下移
              </gt-button>
              <gt-button type="text" @click="handleDel(row)"> 删除 </gt-button>
            </template>
          </vxe-table-column>
        </vxe-table>
		<gt-button
          v-if="action === 'view'"
          type="edit"
          @click="handleEditForm('edit')"
        >
          编辑
        </gt-button>
		handleEditForm(type) {
      this.action = "edit";
      this.editEnabled = true;
    },
data(){
return {
      loading: false,
      id: "",
      action: "",
      form: {},
      editConfig: {
        trigger: "click",
        mode: "row",
        showIcon: false,
        showStatus: true,
        // enabled: false, // 初始禁用
        activeMethod: () => this.editEnabled,
      },
      editEnabled: false,}
}

拖拽改变div高度

点击查看代码

<template>
  <div class="resizable-container">
    <div 
      class="content-box"
      :style="{ height: currentHeight + 'px', overflowY: 'auto' }"
    >
      <!-- 你的内容区域 -->
      <slot></slot>
    </div>
    <div 
      class="drag-handle"
      @mousedown="startDrag"
    ></div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      currentHeight: 300, // 默认高度
      isDragging: false,
      startY: 0,
      startHeight: 0
    }
  },
  methods: {
    startDrag(e) {
      this.isDragging = true
      this.startY = e.clientY
      this.startHeight = this.currentHeight
      document.addEventListener('mousemove', this.handleDrag)
      document.addEventListener('mouseup', this.stopDrag)
      e.preventDefault()
    },
    handleDrag(e) {
      if (!this.isDragging) return
      const deltaY = e.clientY - this.startY
      this.currentHeight = Math.max(100, this.startHeight + deltaY) // 最小高度100px
    },
    stopDrag() {
      this.isDragging = false
      document.removeEventListener('mousemove', this.handleDrag)
      document.removeEventListener('mouseup', this.stopDrag)
    }
  }
}
</script>

<style scoped>
.resizable-container {
  position: relative;
}
.content-box {
  border: 1px solid #ddd;
  resize: none;
}
.drag-handle {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 6px;
  background-color: #f0f0f0;
  cursor: ns-resize;
  z-index: 10;
}
.drag-handle:hover {
  background-color: #ccc;
}
</style>

文件上传限制文件类型

点击查看代码
 <el-upload class="upload-license-info" :disabled="updateFlag" action="#" :limit="10" :show-file-list="false" :file-list="fileList" :on-exceed="handleExceed" multiple :auto-upload="false" :on-change="handleFileChange"
              :before-upload="checkFileDuplicate">
              <!-- :http-request="fileUpload" -->
              <el-button slot="trigger" type="primary" style="position: absolute; left: 0; top: 0">文件上传</el-button>
              <!-- <span>{{ form.evidenceName }}</span> -->
            </el-upload>
handleFileChange(file, fileList) {
      clearTimeout(this.debounceTimer);
      // 检查文件类型
///主要是这部分代码----------s----------
      const whiteList = [
        ".ofd", ".pdf", ".doc", ".docx", ".xls", ".xlsx",
        ".rar", ".zip", ".jpg", ".jpeg", ".png", ".tif", ".tiff"
      ];

      const fileExtension = file.name.substring(file.name.lastIndexOf(".")).toLowerCase();

      if (!whiteList.includes(fileExtension)) {
        this.$message.error(
          "上传文件类型只能是ofd、pdf、doc、docx、xls、xlsx、rar、zip、jpg、jpeg、png、tif、tiff格式"
        );
        // 从文件列表中移除不符合的文件
        this.fileList = this.fileList.filter(item => item.uid !== file.uid);
        return false;
      }
///主要是这部分代码----------e----------
      this.debounceTimer = setTimeout(() => {
        let diffArr = [];
        if (this.fileList.length > 0) {
          const newFiles = fileList.slice(this.fileList.length);
          const sameArr = newFiles
            .filter((item1) =>
              this.fileList.some((item2) => item2.name === item1.name)
            )
            .map((item) => item.name)
            .join();
          diffArr = newFiles.filter(
            (item1) => !this.fileList.some((item2) => item2.name === item1.name)
          );
          if (sameArr !== "") {
            this.$message.error(`文件 "${sameArr}" 已存在,请勿重复上传`);
          }
        } else {
          diffArr = [...fileList];
        }
        // 过滤掉重复文件(保持UI显示)
        this.fileList = diffArr.reduce((acc, current) => {
          const isDuplicate = acc.some((item) => item.name === current.name);
          if (!isDuplicate) {
            acc.push(current);
          }
          return acc;
        }, this.fileList);
      }, 300);
    },

treeselect树组件弹框被遮盖

设置:append-to-body=“true”就可以解决,
官网:https://www.javasoho.com/vuetreeselect/index_cn.html#events

posted on 2023-07-06 14:55  丶菜鸟也要飞  阅读(84)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3