問題匯總
- 問題匯總
- 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>
外嵌報表-多張報表通用代碼
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

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
、
點擊查看代碼
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
引入水印方法
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
修改接口請求頭數據類型
重點這句: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
浙公網安備 33010602011771號