前言

记录平时遇到的技术问题和学习到的新知识


动态加载js

1
2
3
4
5
6
7
// 使用document.write 动态加载 script(每次引入的js文件名称不同防止浏览器读取缓存文件)
<script>
document.write("<script src='xxx.js?num=" + Date.now() + "'><\/script>")
</script>

// 注意:如果后面的闭合标签不使用\分割就会与上面的标签对应,导致后面的成了HTML文本
// 以后在<script></script>中的字符串中使用到</script>,都需要分割处理。否则还会出现此BUG!

element plus 自动按需加载与其他

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import { defineConfig } from "vite";
/**
* element-plus 自动按需引入
* 注意:vue文件中不需要引入组件或组件api既可以使用
*/
import AutoImport from "unplugin-auto-import/vite"; // 自动导入
import Components from "unplugin-vue-components/vite"; // 组件注册
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
/**
* 自动按需引入后:使用手动引入组件或者组件api方式会丢失样式 (需要手动引入样式文件)
* 使用插件解决手动引入的样式丢失问题 (即可不需要手动引入)
* 不使用插件ElMessage使用 如:
* import { ElMessage } from 'element-plus'
* import 'element-plus/es/components/message/style/css'
*/
import ElementPlus from "unplugin-element-plus/vite";
/**
* 自动引入 element plus 图标
* 注意:使用时需要添加 i-ep 前缀
* 如:<el-icon><IEpSearch /></el-icon>
* 其他如input、button添加图标要使用插槽的方式
* <el-button><template #icon><i-ep-edit /></template></el-button>
*/
import Icons from "unplugin-icons/vite";
import IconsResolver from "unplugin-icons/resolver";

export default defineConfig({
plugins: [
vue(),
AutoImport({
// 自动导入 Vue、Vue Router、pinia 相关函数,如:ref, reactive, toRef 等
imports: ["vue", "vue-router", "pinia"],
// 解决 eslint 报错
eslintrc: {
enabled: false, // Default `false`
filepath: "./.eslintrc-auto-import.json", // Default `./.eslintrc-auto-import.json`
globalsPropValue: true, // Default `true`, (true | false | 'readonly' | 'readable' | 'writable' | 'writeable')
},
// 解析器,例如element-plus 的 ElementPlusResolver
resolvers: [
// 自动导入 Element Plus 相关函数,如:ElMessage, ElMessageBox... (带样式)
ElementPlusResolver(),
// 自动导入图标组件
IconsResolver({
prefix: "Icon",
}),
],
}),
Components({
resolvers: [
// 自动注册图标组件
IconsResolver({
enabledCollections: ["ep"],
}),
// 自动导入 Element Plus 组件
ElementPlusResolver(),
],
}),
Icons({
autoInstall: true,
}),
ElementPlus(),
viteMockServe(),
],
});


// 注:element plus 官网上的
//(如果您使用 unplugin-element-plus 并且只使用组件 API,您需要手动导入样式。)
// 暂无触发该bug、使用插件后并没有样式丢失

// 国际化
APP.vue 中 使用全局组件 Config Provider

svg 使用

参考:掘金

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* 注意:直接在浏览器上访问svg,需要使用 xmlns 声明渲染规则
svg 在使用img 背景图时需要添加xmlns,直接在HTML中使用则不需要
如果 svg 没有写宽高有默认宽高,在背景图片不会出现 repeat、背景大小无效(不推荐)
<svg xmlns="http://www.w3.org/2000/svg"></svg> */

/* 项目安装 vite-plugin-svg-icons svgo插件 */
// vite.config.js
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons' // svg 雪碧图
import path from 'path' // path 模块
createSvgIconsPlugin({
// 指定需要缓存的图标文件夹
iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
// 指定symbolId格式
symbolId: 'icon-[dir]-[name]'
}),
// main.js
import 'virtual:svg-icons-register'

// components 新建 SvgIcon组件,使用自动注册组件 unplugin-vue-components/vite 即可使用
// svgo 安装后 package添加 "svgo": "svgo -f src/assets/icons"

vite 中的引入资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!-- 1. import 后再使用 -->
<img :src="myImg" alt="">
import img from 'xxx.png'

const myImg = img

<!-- 2. 使用new URL(),获取静态图片资源
在vite.config.js 中
import.meta.url -> file:///D:/code/vue3-moxie-admin/vite.config.js
new URL('./src', import.meta.url) -> 一个 URL 对象,其中的href是当前目录的src 为 file:///D:/code/vue3-moxie-admin/src
fileURLToPath(new URL('./src', import.meta.url)) -> D:\code\vue3-moxie-admin\src
-->
<!-- 获取地址函数 -->
const getImage = (name) => {
return new URL(`/src/assets/${name}`, import.meta.url).href
}
<img :src="getImage('vue.svg')" alt="" />

<!-- 3.使用import.meta.glob,配置 eager: true 为直接导入-->
const getImage = (name) => {
const modules = import.meta.glob('/src/assets/*.svg', { eager: true })
const path = `/src/assets/${name}.svg`
return modules[path]
}

<img :src="getImage(name)">

<!-- 4.使用await import -->
<img :src="imgUrl" alt="" />
let imgUrl = ref('')
const handleImgSrc = async () => {
let middle = await import('@/assets/vue.svg')
imgUrl.value = middle.default
}
handleImgSrc()

flex 中的 margin:auto

参考:css flex布局中妙用margin: auto


websocket

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 连接到WebSockets
const webSocket = new WebSocket('ws://localhost:8080');
// onopen连接成功后触发
webSocket.onopen = function(event){
console.log('成功连接到websocket服务器')
};
// onmessage收到服务器发送的信息时触发
webSocket.onmessage = function(event){
console.log(event.data)
};
// onclose与服务器连接丢失或关闭时触发
webSocket.onclose = function(event){
console.log('连接关闭')
};
// send发送信息到服务器
webSocket.send('发送消息')

pinia 的 setup 用法与补充

参考:基本知识

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 对象定义数据的补充,stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => { // 注意:必须使用箭头函数
return { count: 0 }
},
actions: { // 注意:需要写成普通函数
increment() { // 该函数也可以接收参数
this.count++ // 直接使用 this
this.$patch((state) => { // 使用 $patch
state.count++
})
// 测试在action 中 $patch 对象,$reset、$state等都可以使用(如下面的使用)
},
},
getters: { // 使用普通函数和箭头函数都可以,如果使用this则需要使用普通函数(不推荐使用this)
double: (state) => state.count * 2,
// 也可以返回一个函数并接受传递的参数
getCount: (state) => { // 返回一个函数别忘记调用
return (num) => state.count + num
},
},
})

// 使用 pinia 数据 (以下是在 setup 语法的使用,vue2选项式的调用参考 pinia 文档)
import {useCounterStore} from '@/stores/counter'
import { storeToRefs } from 'pinia'
const counter = useCounterStore()
counter.count++ // 直接修改
counter.$patch({ count: counter.count + 1 }) // 使用$patch对象
counter.$patch((state)=>{state.count += 1}) // 使用$patch函数
counter.increment() // 使用 action
console.log(counter.double) // getters 的访问使用
counter.getCount(1) // getters 函数传递参数
const { count } = storeToRefs(counter) // 使用 storeToRefs 解构数据得到 refs,避免丢失响应式,
// 也不能直接使用count.value修改,打印得到的是一个 undefined
counter.$reset() // 重置 state 数据
counter.$state = { count: 1 } // 更改整个 state,相当于Object.assign()

1
2
3
4
5
6
7
8
9
10
11
12
13
// 使用setup的方式定义 pinia
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0) // 也支持使用 reactive 定义(不推荐,不方便重新赋值)
function increment() {
count.value++
}
const doubleCount = computed(() => count.value * 2)
return { count, increment, doubleCount } // 注意:需要 return 出去
})

// 组件使用和上面的类似,组件中的修改也不需要加value,注意:使用改语法不能使用 $reset 会报错

vue 动画补充

参考:Vue中transition过渡组件全掌握

1
2
3
/* 1. v-move 属性,只在 transition-group 中生效,
通过 name 来自定义前缀,或者 move-class 自定义 */
/* 2. 不使用 CSS的 class,也可使用 js 钩子钩子函数 */

flex:auto与flex:1的区别

1
2
3
4
flex: auto 相当于 1 1 auto,不管内容多少,是根据内容的大小来分,不是均分的(如:元素里面有文字)
flex: 1 相当于 1 1 0%,一般都是平分空间,空间大小都一致。
flex: auto 的 flex-basis: auto,不覆盖元素的 width 而 flex: 1 则会覆盖
在 flex: auto 相当于有宽度,有个基准值。

表单编辑与新增共用时表单重置的问题

1
2
3
4
5
6
7
8
9
// 1. 原因:
// 表单重置的逻辑是在表单 DOM 生成时,初始绑定的 form 的值(在 DOM 生成之前就有)就是后面重置的值
// 出现这种原因是,打开编辑后,新增打开表单时的 form 的值不是该新增所要的值(编辑每次 form DOM 渲染都绑定了值,所以编辑没有问题)
// 2. 解决方案:
// 表单关闭使用表单重置:新增与编辑一样,打开之前先赋值(或者,打开编辑时,使用新增的默认数据,在 nexttick 中赋值编辑的值)
// 表单关闭使用赋值为新增一样的值 (相当于新增打开给表单赋初始值)
// 故:最佳为新增打开时赋值form,这样都不用管关闭的逻辑

// 注意:使用弹框时,关闭弹框表单的 DOM 没有消失,而是控制的样式。

el-tree 的使用问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 在使用 el-tree 做权限管理时,默认选择与选择问题
// 设置默认选中时,如果父选中了,所有的子也会选中,如果新加了一个子就会出现问题
// 1. 先设置默认,获取所选中的值与接口应该保存的值比较,再将那些不该保存的值取消
nextTick(() => {
// 设置值
refRolePermissionTree.value.setCheckedKeys(defaultCheckedKeys.value);
setTimeout(() => {
// 获取树中所有选中的值
let defaultCheckAll = refRolePermissionTree.value.getCheckedKeys()
// 通过对比树所有选中的值(defaultCheckAll)和详情返回选中的值(res.data.menuIdDtos)去取两个数组的差集
let deleteArr = defaultCheckAll.filter(item => !defaultCheckedKeys.value.some(item_1 => item_1 === item))
// 遍历去掉不需要选中的值
deleteArr.map(item => {
refRolePermissionTree.value.setChecked(item, false)
})
}, 0)
})
// 2. 设置时,先比较,不设置父的选中


// 注意获取半选:
let ids = refRolePermissionTree.value.getCheckedKeys();
ids = [...ids, ...refRolePermissionTree.value.getHalfCheckedKeys()]

按钮权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 全局指令
import { usePerms } from "@/store/perms.js";
let ownPermission = []; // 这里不能直接在Local Storage拿取,只会执行一次。
function toolPermission(el, permission) {
ownPermission = usePerms(); // 里面获取 pinia
if (permission && !ownPermission.perms.includes(permission)) {
el.parentNode && el.parentNode.removeChild(el);
}
}

const permission = {
mounted(el, binding) {
toolPermission(el, binding.value);
},
updated(el, binding) {
toolPermission(el, binding.value);
},
};

export { permission };

// 注意:pinia 在组件外面使用时,放在外面使用时会报错,
// getActivePinia was called with no active Pinia. Did you forget to install pinia
// 不能直接在函数外面获取 menus,不刷新就无法更新权限,设置权限后退出登录还需要刷新的bug

element plus 无限滚动报错

1
2
3
4
5
6
7
8
9
10
11
12
// 原因:需要 nextTick() 之后再去显示 DOM
// 解决:
<div
v-infinite-scroll="load"
infinite-scroll-immediate="false"
style="overflow: hidden" v-if="isMounted">
<p v-for="i in 8">{{ i }}</p>
</div>
const isMounted = ref(false);
onMounted(() => {
isMounted.value = true;
});

el-popover 与 el-select 使用的问题

1
2
3
4
// 在 el-popover 里面有 el-select 时,当选择完成后,会自动关闭同时 el-popover
// 解决:
// element plus 中在 el-select 添加 :teleported="false" 即可解决
// element ui 可能时使用 popper-append-to-body 在 plus 中配置无效

js 数组在批量删除的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 错误案例
// 删除数组中的 delete 为 true 的值
let arr = [
{id: 1,delete: true},{id: 2,delete: true},{id: 2,delete: true},{id: 3,delete: true}
]
arr.forEach((item, index) => {
arr.splice(index, 1) // 这样删除会出现第二次索引错误
})

// 正确案例
// 1. 倒序删除
for(let n = arr.length-1 ; n>=0 ; n--) {
var item = arr[n]
if( item.delete ) { arr.splice(n, 1) }
}

// 2. 先找到要删除的项放在一个数组里面
let arrDelete = arr.filter(item => item.delete )
arrDelete.forEach(i => {
let index = arr.findIndex((item)=> item.id == i.id )
arr.splice( index, 1 )
})

// 使用 filter
arr = arr.filter(item => !item.delete)

自定义表格部分问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 1. 表格 table-layout 的 fixed 、auto 区别 
// fixed 也就是表格每项固定,文字多了会换行,
// auto 则不会,文字在同一行,回挤压其他列的宽度

// 2. 表格列设置宽度的问题,设置无效
// 在第一行中,设置会有效,如果是其他行(不在第一行)则无效
// 当第一行有 colspan 使用时候,无法设置某个列的宽度
// 解决:使用 colgroup 标签
<table>
<colgroup>
<col width="100">
<col>
</colgroup>
</table>

// 3. 表格文字居中
// 在默认行高为 40 的情况下,其中一列换行了,设置行高 20 等会出现高度不正确
// 解决:设置 vertical-align: middle 居中即可

定位遇到的问题

1
2
3
1. 固定定位元素的父元素有 translate 属性,则会相对于该父元素
2. 在父元素使用了 overflow: hidden; 子元素定位超出也会被隐藏,
可以采用 fixed ,元素的上线位置改变移动可以使用 translate 属性

前端表格导出 pdf 记录

1
2
3
4
5
6
7
// 使用 html2canvas 、jsPDF 导出 pdf
// 参考:https://blog.csdn.net/weixin_45021027/article/details/129335654

// 相关问题:
// html2canvas 时会出现 input 文字向上偏移 textarea 不能换行
// 解决:将 html2canvas 换成 @nidi/html2canvas
// 修复版本:https://github.com/niklasvh/html2canvas/pull/2132

flex 布局,子元素 flex: 1,overflow: auto失效

1
2
3
4
5
6
7
8
9
10
// 参考:https://blog.csdn.net/jiladahe1997/article/details/107736576
// 参考:https://blog.51cto.com/u_15127637/4208524
// 参考:https://www.cnblogs.com/liangshaoming/p/16974731.html

// 要实现子元素 flex: 1,overflow: auto 效果
// 父元素都要写:overflow: auto 既可以实现

// 补充
// 父元素 flex:1,子元素的 flex: 1; overflow: hidden;white-space: nowrap;text-overflow:ellipsis 无效
// 解决:父元素设置 width: 0;

浏览器滚动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 滚动到顶部方案
// 1. 使用 scrollTo
window.scrollTo(0, 0);
// 2. 注意 window 上面没有 scrollTop 属性
document.documentElement.scrollTop = 0;
// 3. 使用 scrollBy
window.scrollBy({
top: -window.scrollY,
behavior: 'smooth'
});

// 注意
// 1.如果一个元素不能被滚动(例如,它没有溢出,或者这个元素有一个 "non-scrollable" 属性),
// scrollTop 将被设置为 0。
// 2.设置 scrollTop的值小于 0,scrollTop 被设为 0
// 3.如果设置了超出这个容器可滚动的值,scrollTop 会被设为最大值。