一、联调交互类
1、出现一个数据在当前页面进行了修改,另外一个页面的同一数据并未同步更改
当前的数据经过调用接口修改更新以后,if(code == 200) 将当前数据存入store.dispatch, 然后另一个地方获取该数据,直接获取存入的数据,这样就可以同步显示了
let XXX=computed(() => { return store.getters.***} //直接XXX.数据拿到就是同步的
2、给菜单项加权限,不显示管理员独有的菜单项
利用filter来传,但是不能直接在菜单的数组使用,这样会出现管理员打开页面没有该菜单项,需要二次刷新页面才出现,后也尝试在onMounted里写,结果仍需要二次刷新才出现
最后的解决方法1:
//template里传MenuList let managerMenu= computed(() => { return store.getters.managerMenu; }); //过滤掉管理员的菜单项,if里的条件看接口怎么定义、怎么存数据的 const menu = [{……}]; const menuList = computed(() => { if(managerMenu.value.length == 0) { return menu.fliter((item) => { return item.path != "XXX"; }); } else return menu });
方法2:
//menu里不放管理员的菜单项 const menu = [{……}]; const menuList = computed(() => { if(managerMenu.value?.length > 0) { return [...menu, ...[{menuName: XXX, menPath:XXX,……}] ]; } else return menu });
3、路由跳转到某个页面,并显示某个标签
// 1、先设置对应的路由进行跳转
router.push("/" + item?.menuPath);
// 2、跳转点击的菜单项XXX
if(item.menuName == XXX) {
store.dispatch("app/menu", "***");
}
//push是跳转当前页面,resolve+window.open则是跳转新页面
//push支持query和params,resolve只支持query,若没用query传参,也可以用localStorage
const routePath = router.resolve({path:XXX, query:XXX,});
window.open(routePath.href, "_blank");
localStorage.setItem('a', 1);
4、为简化代码,调多个接口可用三目运算符写入一个函数
const xxParams = ref({……})
const getList = async () => {
const { code, data} = await API.XXX[xxId ? "methodAPI1" : "methodAPI2"](xxParams.value);
if(code == 200) {……}
5、使用ElementUI的无线滚动加载 v-infinite-scroll,出现一直可以滚动的情况
默认情况下,infinite-scroll-disabled是false
方法1:可以在v-infinite-scroll="loadMore"的loadMore方法中限制达到列表长度就return;
方法2:
// 标签里写infinite-scroll-disabled="disabled" <p v-if="loading">加载中...</p> <p v-if="noMore">没有更多了</p> const loading= ref(false) const loadMore = () => {loading.value = true; ……} const noMore= computed(() => return list.length >=20) //eg:表长度限制 const disabled = computed(() => return loading || noMore
6、使用ElementPlus组件的tab标签用法
以标签tab为一级栏目切换,获取一级tab下的二级表利用一级tab的id来找,v-for需要指定key来进行排序,label显示当前一级的标题,name标识tab-pane,点击切换tab传入当前的一级item来获取二级表
<el-tabs v-model="firstTabId"> <el-tab-pane v-for="(item, index) in firstList" :key="item?.id" :label="item?.name" :name="item?.id" @click="handleTabChange(item)" > </el-tabs> //先调用接口,获取firstList,再进行以下: const handleTabChange = (item) => { firstTabId.value = item.id; currentfirstTab.valuue = item; getSecondListData(item.id); //调用获取二级表的接口,传入一级id }
7、编辑当前表格行时,弹出抽屉修改表单数据,此时,修改表单数据与表格里的行数据变成了同步修改,点击关闭抽屉取消修改,但行数据还是发生了变化
该问题主要是因为数据是浅拷贝的,所以在表单里修改了,表格中已经自动更新了,所以需要在显示抽屉表单时,传入表格行数据进行深拷贝传入,这样就不会出现该问题
import _ from "loadsh"; //以下的param是表格传入的行数据,判断id主要是确认是否存在当前行数据,是否开启的抽屉编辑 const showDrawer = (param) => { if (param?.id) { formData.value = _.cloneDeep(param); …… } }
8、对当前列表进行增改后台,在抽屉中怎么刷新父组件的列表
每次增删改查都需要刷新当前列表,删查在父组件直接进行,查的方法getTableList中获取当前列表接口,删的时候调用接口后在if(code==200)里最后再调用getTableList
而增改则需要弹出抽屉进行数据修改,修改后调用查的方法进行刷新列表,这种情况就需要在父组件定义时,传入 @refreshTable="getTableList"暴露给子组件,子组件通过defineEmits拿到
const emit = defineEmits(["refreshTable"]);
并在增改方法调用接口成功以后,最后emit该方法进行刷新列表:
emit("refreshTable", currentParentId.value); //传入当前列表所在的上一层级id
9、列表行拖拽排序-- 通过拖拽行拖拽图标进行重排序,序号、排序号均重排
利用Sortable方法,在onEnd调用排序方法,排序方法中先将当前表格数据备份,再用备份数据进行重排序,把原数据置空,再在nextTick中将备份重排序后的数据交给原数据,最后遍历表格数据数组,将sort利用index+1重排,最后调用接口刷新表格。
// HTML: <el-table-column prop="sortNo" label=" "> <img class="handle" :src="drag-icon"/> </el-table-column> // CSS: .handle { cursor: move; display: flex; vertical-align: middle; margin: auto; } .sortable-format { opacity: 0.5; pointer-events: none; } // JS: //导入sortablejs---没下载依赖包,需进行下载npm i sortablejs @types/sortablejs import Sortable from "sortablejs/Sortable.js"; //可能会与个人按照依赖包有关系 const sortTable= () => { const el = document.querySelector(".el-table__body-wrapper tbody"); Sortable.create(el, { animation: 150, ghostClass: 'sortable-format', //拖拽样式 handle: ".handle" //指定拖拽手柄的类名,如果需点击某个图标拖拽[需要把图标的class写在这里] // 结束拖动事件 onEnd: ({ newIndex, oldIndex }) => { dragSort(newIndex, oldIndex); }, }) } const dragSort = (newIndex, oldIndex) => { // 使用arr复制一份当前表格数组数据 const arr = tableData.value; const currentRow = arr.splice(oldIndex, 1)[0]; arr.splice(newIndex, 0, currentRow ); // 将原数据数组置空 tableData.value= []; nextTick(async () => { // 将拖拽排序后的数据赋值给原数据数组 tableData.value= arr; // 提交后后台数据进行排序--遍历数组拿到index更新sort重排序 let params = []; tableData.value.forEach((item, index) => { params.push({ id: item.id, sort: index + 1, }); }); // 调用后台接口传参刷新列表 const { code} = api.dragRowSort(params); if (code == 200) { getTableData(); //调用查询列表方法,刷新当前列表 } }); }
10、使用ELementPlus组件的upload上传文件方法及图片回显
el-upload的使用:
需要传入请求头获取token,如果采用单独调用接口,则可以使用http-request,直接传入后端接口则用action
action放图片上传调用的后台方法,headers请求头携带token,file-list存放成功上传图片列表,用于修改功能时页面已有图片显示,before-upload图片上传前进行格式校验判断,accept指定文件类型,limit限制文件数目以上传单张图片为例:
<el-upload :headers="getHerders()" action accept="image/jpeg,image/png" :file-list="imgFileList" :limit="1" :before-upload="handleImgBeforeUpload" :http-request="uploadImg" > <img :src="uploadImgUrl" /> </el-upload> // 上传登录背景图 const uploadImg= async ({ file }) => { let formData = new FormData(); formData.append("file", file); //调用接口传入当前图片 try { const { code, data } = await api.getImg(formData, { headers: {"Content-Type": "multipart/form-data", }, }); if(code == 200) { //获取当前上传的图片的路径 currentFile.value.filePath = data; // lookImg是查看图片的接口方法,用于回显图片 uploadImgUrl.value = lookImg(data); } } catch (error) { return Promise.reject(error); } }
11、 组件间通信方式
(1)ref + emit:
打开弹窗/抽屉之类的在父组件调用方法,弹窗/抽屉组件的展示方法要暴露给父组件,父组件在调用子组件时,通过ref获取:
<!-- 子组件 --> <Dialog v-model="dialogVisible" /> //script const dialogVisible= ref(false); const showDialog = () => { //打开弹窗 dialogVisible.value = true; }; defineExpose({ showDialog , }); <!-- 父组件 --> <CustomTable @addCustom="addCustom"/> <CustomDialog ref="dialogRef" @refreshList="refresh" /> //script const dialogRef = ref(null); const addCustom = () => { // 新增弹窗 dialogRef.value.showDialog(); };
其他子组件(要调用子组件的兄弟组件,emit获取):
<!-- 兄弟组件 --> <el-button @click="addProcess()"> + 新增流程 </el-button> // 打开新增弹窗和展示详情抽屉 const emits = defineEmits(["addCustom"]); const addProcess= () => { // 新增弹窗 emits("addCustom"); }
(2)v-model+props:
子组件需要使用父组件获取的数据,父组件调用子组件通过v-model传值
<!-- 父组件 --> <CustomTable :customList="customList" /> // 调接口获取数据 const customList = ref([]); const getAllList = async () => { // …… customList.value = data; } <!-- 子组件 --> <el-option v-for="item in customList" //…… ></el-option> const props = defineProps({ customList: { type: Array, default: () => [], }, });
(3)动态组件传值
<component :is="checkedMenu?.componentName" :currentId="userInfo?.id" ></component> // 组件中写: const props = defineProps(["currentId"]); // props.currentId调用
(4)路由传参
点击菜单项跳转路由,需要传递当前菜单项id(当前组件id传到另外一个组件),通过路由传参:(query路径地址会显示参数)
const enterMenu = (menuItem) => { router.push({ path: "/***", query: { fartherId: menuItem?.id, }, }); };
另一个组件调用:route?.query?.fartherId
12、el-popover 按钮的拖拽,拖拽时pop弹窗消失,停止是出现,鼠标移出消失
实现效果:el-popover移入显示弹窗,鼠标抓取可以拖拽,拖拽移动时弹窗不显示,拖拽完后弹窗显示,鼠标移出弹窗不显示,变成正常的hover。
对el-popover的菜单拖拽,利用mousedown--mousemove--mouseup来写,需要写在reference的插槽内,该插槽对应卡片弹窗的按钮。
<template #reference> <div ref="dragDiv" @mousedown="startDrag" @mouseup="endDrag"></div> </template>
实现:需要记录拖拽的初始xy,拖拽状态,针对鼠标按下、移动、抬起后方法各自定义,设置弹窗的hover状态变化(正常hover弹出,点击拖拽时隐藏,移动松开鼠标后显示,移出鼠标隐藏变成正常hover)
// 拖拽--菜单按钮标识 const dragDiv = ref(null); // 初始化偏移值和拖拽状态 let offsetX, offsetY; let dragging = false; // 是否隐藏弹框标志 const isVisiblePopover = ref(null) // 鼠标按下开始拖拽 const startDrag = (event) => { isVisiblePopover.value = false; dragging = true; // 计算初始偏移值 offsetX = event.clientX - dragDiv.value.getBoundingClientRect().left; offsetY = event.clientY - dragDiv.value.getBoundingClientRect().top; // 监听移动和松开事件 document.addEventListener("mousemove", moveDrag); }; // 鼠标移动拖拽 const moveDrag = (event) => { if(!dragging) return; // 计算新的位置 const x = event.clientX - offsetX; const y = event.clientY - offsetY; // 检查新位置是否在视口内 const viewWidth = window.innerWidth; const viewHeight = window.innerHeight; if (x >= 0 && x + dragDiv.value.clientWidth <= viewWidth) { dragDiv.value.style.left = `${x}px`; } if (y >= 0 && y + dragDiv.value.clientHeight <= viewHeight) { dragDiv.value.style.top = `${y}px`; } document.ondragstart = function (event) { event.preventDefault(); }; document.ondragend = function (event) { event.preventDefault(); }; }; // 鼠标松开结束拖拽--先true是为了松开鼠标立即显示,设置定时器又恢复原来的hover const endDrag = () => { dragging = false; isVisiblePopover.value = true; setTimeout(() => { isVisiblePopover.value = null; }, 5); };
13、同时展开/折叠不同菜单项的el-collapse-transition折叠面板
可以给每个选项加一个flag,每次点击详情展开时判断当前flag是否存在,不存在则添加,存在则判断true或false取反
// 打开关闭折叠面板 const openItem = (item) => { // 动态添加事件的折叠展开flag if (!item?.flag) item.flag = false; if (item?.flag == false) { item.flag = true; } else item.flag = false; };
14、 富文本内容显示、默认样式及图片点击放大
内容显示使用v-html
<div class="rich-text-content" v-html="item?.content" @click="showImg($event, item)"> </div> <el-image-viewer v-if="item?.imgShow" :url-list="item?.pictureList" @close="item.imgShow = false" />
富文本框添加的表格无文本框,图片默认移入是箭头,可添加:
:deep(img) { cursor: pointer; } :deep(table) { border: 1px solid #ccc; border-collapse: collapse; //不设置单元格和边框线分离 align-content: center; th { border-right: 1px solid #ccc; } td { border: 1px solid #ccc; } }
图片点击放大预览,使用方法showImg,分为两种情况,单图和多图可预览切换。
(1)单图:直接抓取富文本中的img标签,拿到src:
const showImg = (e, item) => { if (e?.target?.tagName == "IMG") { picView = e?.target?.src; item.imgShow = true; } };
(2)多图:调接口时,获取每个item的图片列表:
if (code == 200) { data.value?.map((dataItem) => { dataItem.pictureList = []; let imgList= dataItem?.content?.split("img"); imgList?.map((item) => { if (item?.indexOf("src") != -1) { dataItem?.pictureList?.push( item.split('" alt')[0].replace(/ src=/g, "").replace('"', "") ); } }); }); }
再在点击预览遍历到当前图片路径,并把前后图片数组拼接:
const showImg = (e, item) => { if (e?.target?.tagName == "IMG") { let tempBefore = []; let tempAfter = []; if (item?.pictureList?.length) { for (let i = 0; i < item?.pictureList?.length; i++) { if (item?.pictureList?.[i] == e?.target?.src) { tempBefore = item?.pictureList?.slice(0, i); tempAfter = item?.pictureList?.slice(i, item?.pictureList?.length); break; } } } item.pictureList = [...tempBefore]; item.imgShow = true; } };
15、动态路由跳转菜单项,接口获取可选菜单再匹配
使用动态路由,但菜单项需获取当前可选菜单:需要先await获取菜单列表,再用获取的菜单标识去匹配全部菜单项,拿到可选菜单显示,用checkMenu唯一标识
<div v-for="(item, key) in currentDepartMenu" :key="key" @click="handleClickMenu(menus?.[item?.menuId])" >{{ item?.moduleName }}</div> <component :is="checkedMenu?.componentName"></component> // 全部菜单 export const menus = { [menuId]: { moduleName: "***", componentName: shallowRef(组件名), }, …… }; // 点击菜单跳转 const handleClickMenu = (item) => { checkMenu.value = item; }; // 获取当前菜单 const getCurrentMenu = async (id) => { const { code, data } = await api.getCurrentMenu(id); if (code == 200) { currentMenu.value = data; checkMenu.value = menus?.[data?.[0]?.menuId]; } }; onMounted(async () => { await getCurrentMenu(); // 需先获取菜单列表,否则影响预览 if (route.query?.menuId) { checkMenu.value = menus?.[route.query?.menuId]; } });
16、如果在详情页里有多个tab,tab想要互相跳转可用provide/inject
//在点击切换tabs的详情页定义provide:
import { ref, provide } from "vue";
provide("checkedMenu", (activeTab) => {
handleClickMenu(activeTab);
}
//在需要点击切换至其他同级tab的页面inject:
import { ref, inject } from "vue";
const checkedMenu = inject("checkedMenu");
//---在点击方法里调用:
checkedMenu("要跳转的tab标识,可以是name,都可以看自己写");
二、CSS样式类
1、兄弟div盒子实现并排
1、设置其父容器盒子的样式为 display: flex; (常用)
2、两个盒子都用float:left,再用定位调整。(脱离文档流)
2、多个兄弟盒子设置样式简单方式
如果使用v-for遍历,可以利用index和个数n,用三目运算符来写样式:
:style="marginRight: (index+1) % n == 0 ? '0' : '5px' "
3、用el-input写登录输入框出现下边框消失
方法1:
先看样式里面有没有设置相关样式,比如我遇到的问题就是设置了.el-input__wrapper的padding,这个时候按照浏览器的像素,eg:Edge1440宽来调整padding就可以调好。
方法2:
.el-input__wrapper { @media screen and (max-width: 1440px) { padding:***; } }
4、el-popover卡片存在默认样式
<el-popover
placement="bottom"
trigger="hover":width="250"
popper-class="xxx-popper"//给该类设置以下样式(需在非scoped设置)
>.xxx-popper { border: none !important; padding: 0 !important; }
5、 el-popover卡片如果设置了圆角,内部头div不设置圆角,又会变成直角覆盖
border-radius: 8px !important; //.xxx-popper
border-radius: 8px 8px 0px 0px; //.xxx-popper-header
6、常见居中的样式设置
(1)父div里的子div(子div居中显示):设置父div即可
.father { height: 60px; display: flex; justify-content: center; align-content: center; }
(2)div或span中的文字居中:
line-height: 30px; //设盒子高度则垂直居中 text-align: center;
(3)如果兄弟标签宽度各不相同,但需设置div的文本居中(此时填充padding失效,水平是默认的文字宽度):可以将div文本再放入内部span标签内设置
div { line-height: 30px; //div设置行高垂直居中 span { padding: 0 16px; //内部span设置左右padding或者margin水平居中 } }
(4)grid内容居中
display: grid; place-items: center;
7、多个div的flex布局常用
display: flex; //可让内部子元素div横向排列
flex-wrap: wrap; //可以换行,属性值wrap-reverse是行内有序,行逆序
align-items: flex-end; //若只有一行可这样设置置于容器底部(需确认高度设置)
8、 v-for遍历的多个div菜单项,单个菜单项的hover样式设置
鼠标移入效果,可以通过mouseenter和mouseleave获取当前菜单项的index来作为条件,获取以后通过判断index,拿到当前菜单项,设置样式
:class="showIndex == index ? 'hoverMenuItem': ''"
9、一些ElementPlus的底层样式(都可以F12仔细看源码找到)
el-input 输入框的焦点::deep(.el-input__wrapper.is-focus)
el-timeline 时间线:
<template #dot></template> //可设置时间线上图标
:deep(.el-timeline-item__tail) // 修改时间线的竖线样式
:deep(.el-timeline-item__dot) // 修改时间线节点icon的位置
:deep(.el-timeline-item__content)// 修改时间线item间间距
:deep(.el-timeline-item__timestamp.is-bottom) // 修改时间线item间边距
10、 盒子内的文本 超长数字/单词超出区域,多个空格合并成1个,换行符未生效,首行缩进
word-wrap: break-word; //单词截断
white-space: pre-wrap; //空格不合并,换行符正常换行
text-indent: 2em; //首行缩进