2023.01.12
1、使用computed实时计算按钮的颜色
<div :class="className" @click="handleClick">去认证</div>
const className = computed(() => {
const pattern =
/^[1-9]\d{5}(18|19|20|(3\d))\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/
const rerus = codeId.value === '000000000000000000'
if ((pattern.test(codeId.value) && name.value && !twoToken.value) || rerus) {
return 'custom-btn custom-btn-active'
}
return 'custom-btn'
})
2023.02.03
1、获取当前页面的路由
console.log(router.currentRoute.value)
console.log(router.currentRoute.value.fullPath)
2、解决vite+vue3项目中不能使用require导入图片的问题
在vite+vue3项目中是没有require的,使用require会报错。
解决方法1:
const tabLists = [
{
icon: new URL("../assets/accountManage.png", import.meta.url).href,
name: "账号管理",
}
];
解决方法2:在asset 前面加上src。
<img :src="'/src/assets/img/unbind_success.png'" alt="" />
3、vue3 在关闭弹窗之后暂停video或audio的播放
video与audio的写法一致。直接套用这个代码也没问题
<template>
<el-dialog
v-model="materialVisible"
title=""
:close-on-click-modal="false"
@close="handlePause"
>
<audio
controls
ref="audio" //重要
class="audioStyle"
@play="isPlay = true"
@pause="isPlay = false"
>
<source src="../../assets/Autumn morning.mp3" type="audio/ogg" />
<source src="../../assets/Autumn morning.mp3" type="audio/mpeg" />
</audio>
</el-dialog>
</template>
<script lang="ts" setup>
const isPlay = ref(false);
const audio = ref(null) as any;
const handlePause = () => {
audio.value.pause();
};
</script>
2023.02.09
1、隐藏html默认滚动条
.wrap {
width: 100%;
overflow-y: scroll;
}
::-webkit-scrollbar {
display: none;
}
2、获取html一屏幕的窗口高度
window.innerHeight
3、当display: none; 与 display: flex; 相遇,其一作用失效。
解决办法:在子元素的外层,原来父元素的内层套一个盒子,比如div,display: none;就能作用了。
<div class="imgListFree">
<div class="imgList">
<img src="./img/imgList1.png" alt="" />
<img src="./img/imgList2.png" alt="" />
<img src="./img/imgList3.png" alt="" />
<img src="./img/imgList4.png" alt="" />
<img src="./img/imgList5.png" alt="" />
<img src="./img/imgList6.png" alt="" />
</div>
</div>
if (i === 0) {
imgListFree.style.display = "block";
} else {
imgListFree.style.display = "none";
}
.imgListFree {
display: block;
}
.imgList {
display: flex;
justify-content: space-between;
padding-top: 20px;
}
.imgList img {
background: rgba(218, 219, 224, 1);
width: 15.5%;
border-radius: 12px;
}
2023.02.11
1、原生js Ajax请求接口数据
window.onload = function () {
var xhr = new XMLHttpRequest();
//建立连接
xhr.open(
"GET",
"https://xxxx/api/article/detail",
true
);
//参数1:请求的方式 post get
//参数2:url地址
//参数3:是否异步 布尔值,默认为true,若为false,则为同步;
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
//设置请求头信息
xhr.send(null);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
console.log(data);
}
};
};
2023.02.17
1、遇到的坑:点击删除按钮,删除按钮变蓝,其他不变还是灰色
在获取到后端接口数据时,循环数组添加icon_src字段,当点击删除图标时,更换该图标的src。
![](https://i-blog.csdnimg.cn/blog_migrate/6dae2b85c186e0af550dec8ca44d768e.png)
2、遇到的坑:一个页面怎么发送两次ajax请求
![](https://i-blog.csdnimg.cn/blog_migrate/6f86607be566660e956d86f3466f237a.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/39cb4bd659a92a1290e858201a9c41ef.jpeg)
解决办法:把第二个请求放在第一个请求成功的里面(也就是回调套回调,但是真的不会地狱回调嘛?所以promise应运而生,避免层层的回调)
2023.02.20
1、遇到的坑:vue 视频播放 切换视频地址后还播放着前一个视频
问题描述:使用的是elementn plus的dialog弹窗,弹出弹窗播放视频。即使console.log出来的视频地址是对的,但是弹窗上播放的还是前一个视频。
解决办法:在dialog标签中添加 v-if 判断。
原理:v-if 与 v-show 都表示显示隐藏。但是v-if成立后会重新创建,不成立时就会消除代码块。换句话说就是成立时创建video标签,不成立则销毁video标签。而v-show 则只是控制其样式隐藏,并没有对其销毁。
2、element plus:table表格自定义列模板
vue3 + element plus的table表格。
表格自定义的东西都在父组件中书写。首先在父项定义加一个参数slot。
父组件:
<Table
v-if="totalData > 0"
:lists="lists"
:table-data="tableData"
:total="totalData"
@prev-click.stop="prevClick"
@next-click.stop="nextClick"
@current-change="currentChange"
>
<template #pay_status="{ row }">
<-- 调用slot传过来的数据row -->
<span class="status1">{{ row.pay_status }}</span>
<el-button
v-if="row.pay_status === '未支付'"
link
type="primary"
class="operation1"
size="small"
@click="handlePay(row)"
>
去支付
</el-button>
</template>
</Table>
const lists = [
{ prop: "created_at", label: "订单时间", width: "130" },
{ prop: "pay_status", label: "状态", width: "100", slot: "pay_status" },
];
自定义模板,如果有slot参数,就可以调用。如果没有,就显示默认的。
对应的项的list.slot值是啥,自定义模板的slot插槽的name就是啥。
子组件:
<el-table
:data="tableData"
empty-text="暂无数据"
style="width: 100%"
border
>
<el-table-column
v-for="(list, index) in (lists as any)"
:min-width="list.width"
:height="150"
:key="index"
:prop="list.prop"
:label="list.label"
>
<!-- 自定义模板 -->
<template v-if="list.slot" #default="{ row, $index }">
<slot :name="list.slot" :row="row" :index="$index"></slot>
</template>
<template v-else #default="{ row }">
<span>{{ row[list.prop] || "" }}</span>
</template>
</el-table-column>
</el-table>
2023.03.02
1、改变element plus table表格的表头颜色
:deep(.el-table th.el-table__cell) {
background-color: rgba(245, 245, 245, 1);
}
2023.03.08
1、element 重置form表单
1.将数据绑定在el-form表单的model上
2.给el-form-item加上prop属性。
![](https://i-blog.csdnimg.cn/blog_migrate/d8ff6bdf740f8ad9fdf590ff034a405a.png)
import type { FormInstance } from 'element-plus'
const ruleForm = ref<FormInstance>()
// 表单重置方法
const resetForm = () => {
ruleForm.value?.resetFields()
}
// 或者是另一种方法
const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.resetFields();
};
2023.03.17
1、二次封装element form表单
子组件:
<template>
<div class="form">
<el-form
ref="ruleFormRef"
:model="formData"
:label-width="props.labelWidth"
:label-position="labelPosition"
status-icon
:class="layoutClass"
>
<template v-for="item in props.formItems" :key="item.label">
<el-form-item
:label="item.label"
:prop="item.prop"
:rules="item.rules"
:class="item.class"
>
<!-- 文本input框/密码框 -->
<template
v-if="
item.type === 'text' ||
item.type === 'textarea' ||
item.type === 'password'
"
>
<el-input
:type="item.type"
:placeholder="item.placeholder"
:maxlength="item.maxLength"
show-word-limit
:rows="6"
v-model="formData[`${item.field}`]"
></el-input>
<div v-if="item.slot" class="addIcon" @click="handleAdd">
<img src="@/assets/img/icon_add2.png" alt="" />
</div>
</template>
<!-- 选择器 -->
<template v-else-if="item.type === 'select'">
<el-select
:placeholder="item.placeholder"
v-model="formData[`${item.field}`]"
>
<el-option
v-for="option in item.options"
:key="option.value"
:value="option.value"
:label="option.label"
/>
</el-select>
</template>
<!-- 级联选择器 -->
<template v-else-if="item.type === 'cascader'">
<el-cascader
v-model="formData[`${item.field}`]"
:options="item.options"
:show-all-levels="false"
:placeholder="item.placeholder"
/>
</template>
<!-- 日期选择器 -->
<template v-else-if="item.type === 'date'">
<el-date-picker
v-model="formData[`${item.field}`]"
type="date"
range-separator="至"
:placeholder="item.placeholder"
end-placeholder="End date"
/>
</template>
<!-- 日期范围选择器 -->
<template v-else-if="item.type === 'daterange'">
<el-date-picker
v-model="formData[`${item.field}`]"
type="daterange"
range-separator="至"
start-placeholder="开始时间"
:placeholder="item.placeholder"
end-placeholder="结束时间"
/>
</template>
<!-- 文件上传 -->
<template v-else-if="item.type === 'upload'">
<el-upload
ref="item.prop"
class="upload-demo"
action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15"
:auto-upload="false"
>
<div class="uploadFile">
<el-icon :size="33" color="rgba(196,196,196,1)">
<Plus />
</el-icon>
<div>上传文件</div>
</div>
</el-upload>
<div class="tipBox" v-html="item.html"></div>
</template>
<!-- 单选框 -->
<template v-else-if="item.type === 'radio'">
<el-radio-group v-model="formData[`${item.field}`]">
<el-radio
v-for="option in item.options"
:key="option.value"
:label="option.label"
/> </el-radio-group
></template>
<!-- 单个多选框 -->
<template v-else-if="item.type === 'checkbox'">
<el-checkbox
v-model="formData[`${item.field}`]"
:label="item.placeholder"
/>
<!-- <el-checkbox-group v-model="formData[`${item.field}`]">
<el-checkbox
v-for="option in item.options"
:key="option.value"
:label="option.label"
:name="item.type"
/>
</el-checkbox-group> -->
</template>
</el-form-item>
</template>
<el-form-item v-if="showSubmit">
<div class="btn">
<el-button v-show="showCancel" @click="handleCancel(ruleFormRef)"
>取消</el-button
>
<el-button type="primary" @click="onSubmit(ruleFormRef)">{{
confirmText
}}</el-button>
</div>
</el-form-item>
</el-form>
</div>
</template>
<script setup lang="ts">
import { IFormItem } from "../store/types/form";
import { ref, watch } from "vue";
import type { FormInstance } from "element-plus";
import { Plus } from "@element-plus/icons-vue";
const ruleFormRef = ref<FormInstance>();
const props = defineProps({
modelValue: {
type: Object, // 双向绑定
required: true,
},
formItems: {
type: Array as () => IFormItem[], // 组件数据
default: () => [],
},
labelWidth: {
type: String,
default: "210px",
},
labelPosition: {
type: String,
default: "left",
},
showSubmit: {
type: Boolean,
default: false,
},
layoutClass: {
type: String,
default: "formItem",
},
showCancel: {
type: Boolean,
default: false,
},
confirmText: {
type: String,
default: "确定",
},
});
const emits = defineEmits([
"update:modelValue",
"handleAdd",
"handleCancel",
"onSubmit",
]);
const handleAdd = () => {
emits("handleAdd", false);
};
const handleCancel = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.resetFields();
};
const onSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
await formEl.validate((valid, fields) => {
if (valid) {
console.log(formData.value);
emits("onSubmit", formData.value);
} else {
console.log("error submit!", fields);
}
});
};
// 获取值:{ ...props.modelValue} 进行浅拷贝,避免破坏props单向数据流。
// v-model="formData[`${item.field}`]"
const formData = ref({ ...props.modelValue });
// 进行深度监听,实现组件双向绑定
watch(
formData,
(newValue) => {
emits("update:modelValue", newValue);
},
{
deep: true,
}
);
</script>
<style scoped lang="scss">
.form {
margin-top: 20px;
.formItem {
:deep(.el-form-item) {
width: 860px;
margin-bottom: 22px;
}
:deep(.el-form-item__label) {
font-size: 16px;
}
}
.formDialog {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
:deep(.el-form-item) {
width: 400px;
margin-bottom: 22px;
}
:deep(.el-form-item__label) {
font-size: 14px;
}
.btn {
width: 200px;
:deep(.el-button) {
margin: 0;
}
}
}
:deep(.el-form-item__content) {
align-items: flex-start;
}
:deep(.el-form-item__label) {
font-weight: bold;
}
:deep(.el-input) {
height: 38px;
}
.el-select {
width: 100%;
}
:deep(.el-cascader) {
width: 100%;
}
:deep(.el-date-editor.el-input) {
width: 100%;
}
.uploadFile {
width: 120px;
height: 120px;
border-radius: 4px;
background: rgba(255, 255, 255, 1);
border: 1px solid rgba(204, 204, 204, 1);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
div {
font-size: 14px;
font-weight: 400;
color: rgba(171, 174, 181, 1);
}
}
.tipBox {
margin-left: 30px;
:deep(.redFont) {
font-size: 14px;
font-weight: 400;
color: rgba(245, 108, 108, 1);
}
:deep(div) {
font-size: 14px;
font-weight: 400;
line-height: 20px;
color: rgba(171, 174, 181, 1);
}
}
:deep(.el-radio__inner) {
width: 24px;
height: 24px;
}
:deep(.el-radio__inner::after) {
width: 8px;
height: 8px;
}
:deep(.el-radio__label) {
font-size: 16px;
font-weight: 700;
color: rgba(85, 85, 85, 1);
margin: 10px 0;
}
:deep(.el-radio) {
margin-bottom: 20px;
margin-right: 42px;
&:last-child {
margin-right: 0;
}
}
:deep(.el-checkbox) {
font-size: 14px;
font-weight: 400;
color: rgba(171, 174, 181, 1);
}
:deep(.el-checkbox__inner) {
width: 22px;
height: 22px;
}
:deep(.el-checkbox__inner::after) {
width: 6px;
height: 12px;
left: 6px;
}
.addIcon {
cursor: pointer;
position: absolute;
top: 1px;
right: -35px;
img {
width: 24px;
height: 24px;
}
}
.btn {
margin: 0 auto;
:deep(.el-button) {
width: 114px;
height: 40px;
margin: 0 15px;
}
}
}
</style>
父组件:
<template>
<div id="addBox">
<Form
v-bind="addRightOwnerConfig"
v-model="formData"
:label-width="'140px'"
:label-position="'right'"
:layout-class="'formDialog'"
:show-submit="true"
@on-submit="onSubmit"
></Form>
</div>
</template>
<script lang="ts" setup>
import Form from "./Form.vue";
import { ref } from "vue";
import { addRightOwnerConfig } from "@/store/types/addRightOwnerConfig";
// 获取数据:
const formItems = addRightOwnerConfig.formItems ?? [];
// 对数据进行初始化
const formOriginData: any = {};
for (const item of formItems) {
formOriginData[item.field] = "";
}
// 把初始化的数据进行赋值
const formData = ref(formOriginData);
const emits = defineEmits([ "confirmAdd"]);
const onSubmit = (form: any) => {
emits("confirmAdd", form);
};
</script>
配置文件:(addRightOwnerConfig.ts文件)
import { IForm } from "./form";
export const addRightOwnerConfig: IForm = {
formItems: [
{
field: "type",
type: "select",
label: "类型:",
prop: "type",
placeholder: "请选择类型",
options: [
{ label: "足球", value: "33" },
{ label: "篮球", value: "22" },
{ label: "排球", value: "11" },
],
rules: [
{
required: true,
message: "请选择类型",
},
],
},
{
field: "name",
type: "text",
label: "姓名或名称:",
prop: "name",
placeholder: "请输入姓名或名称",
rules: [
{
required: true,
message: "请输入姓名或名称",
},
],
},
{
field: "IDType",
type: "select",
label: "证件类型:",
prop: "IDType",
placeholder: "请选择证件类型",
options: [
{ label: "足球", value: "33" },
{ label: "篮球", value: "22" },
{ label: "排球", value: "11" },
],
rules: [
{
required: true,
message: "请选择证件类型",
},
],
},
{
field: "identificationNumber",
type: "text",
label: "证件号码:",
prop: "identificationNumber",
placeholder: "请输入证件号码",
rules: [
{
required: true,
message: "请输入证件号码",
},
],
},
{
field: "phone",
type: "text",
label: "电话号码:",
prop: "phone",
placeholder: "请输入电话号码",
},
],
};
表单类型:(form.d.ts文件)
// 表单中的组件类型
type IFormType =
| "text"
| "textarea"
| "password"
| "select"
| "cascader"
| "date"
| "daterange"
| "upload"
| "radio"
| "checkbox"
| "submit";
/**
* 表单所需要的数据类型
field:双向绑定关键字
type:表单中组件的类型(通过type进行匹配:比如:text是一个文字输入框,password则是密码框)
label 标签名称
prop
maxLength 输入框最大输入限制
placeholder 提醒文字
options 数据(比如select )
rules 验证规则
*/
export interface IFormItem {
field: string;
type: IFormType;
label?: string;
prop?: string;
maxLength?: number;
placeholder?: string | number;
options?: any[];
rules?: any[];
html?: any;
slot?: any;
}
// 表单的配置
export interface IForm {
formItems: IFormItem[];
labelWidth?: string;
}
2、router策略模式
要求:当刷新页面的时候,active的样式还是在原来的url上,而不是跑到url为 '/' 的tab上。
以下是原代码,又臭又长的,受不了了。
![](https://i-blog.csdnimg.cn/blog_migrate/7169b014bc73cba379fc685139017929.png)
下面是用策略模式改过的:
![](https://i-blog.csdnimg.cn/blog_migrate/204b26ad99a5a144397290eed2fd5814.png)
2023.03.28
1、js使用对象解构删除对象属性
方法:使用对象解构。
应用场景:前端的表单对象不需要全部提交至后端时。
const ruleForm = reactive({
name: "",
phone: userInfo.value.mobile,
email: "",
});
// 使用对象解构将对象中的phone属性除去
const { phone, ...newData } = ruleForm;
console.log(newData)// 新的对象
2、解决vue中v-html元素标签样式失效的问题
解决办法:使用deep scoped来实现对v-html的样式应用,并且不设置为全局
deep选择器在css中的写法为>>>
>>> p {
font-size: 18px;
}
可惜>>>在sass/less中不生效,可以使用:deep()
:deep(p){
font-size: 18px;
}
如果不生效,也可以试试 : :v-deep 或者 /deep/ 呢
::v-deep p{
font-size: 18px;
}
// 或者
/deep/ p{
font-size: 18px;
}
3、element plus:表格table+分页pagination组件
1.table组件
<el-table
:data="tableData"
empty-text="暂无数据"
style="width: 100%"
border
@row-click="rowClick"
>
<el-table-column
v-for="(list, index) in (lists as any)"
:min-width="list.width"
:height="150"
:key="index"
:prop="list.prop"
:label="list.label"
>
<!-- v-if 命名插槽 -->
<template v-if="list.slot" #default="{ row, $index }">
<slot :name="list.slot" :row="row" :index="$index"></slot>
</template>
<!-- v-else 显示默认内容 -->
<template v-else #default="{ row }">
<span>{{ row[list.prop] }}</span>
</template>
</el-table-column>
</el-table>
<div class="pagination">
<el-pagination
small
hide-on-single-page
v-model:current-page="currentPage"
v-model:page-size="pageSize"
layout="total, prev, pager, next, jumper"
:total="total"
@current-change="handleCurrentChange"
/>
</div>
2.页面中使用table组件
<Table
v-if="totalData > 0"
:lists="lists"
:table-data="tableData"
:total="totalData"
@handle-current-change="currentChange"
@row-click="rowClick"
>
<template #type="{ row }">
<span>{{ typeOwnerObj[row.type] }}</span>
</template>
</Table>
<Empty v-else :show="true"></Empty>
import Table from "../../components/Table.vue";
import Empty from "../../components/Empty.vue";
const currentChange = (val: number) => {
parameter.page = val;
store.dispatch("user/userList", parameter);
};
const rowClick = (row: any) => {
router.push({
path: "/detail",
query: {
oid: row.id,
},
});
};
2023.03.29
1、vue3下载后端返回的pdf(数据流)
请求的接口,返回的东西如下:
![](https://i-blog.csdnimg.cn/blog_migrate/47bba21f196a015759a257ceea7f4d60.png)
![](https://i-blog.csdnimg.cn/blog_migrate/f10ac7fafb6a75c9c096222e82acef2e.png)
解决代码如下:
// 证书下载API
export const downloadCert = (cid: number) => {
return request({
url: `/api/xxxxxxxxxx/download?cid=${cid}`,
responseType: "arraybuffer", //重点,否则下载的pdf会空白!!!
});
};
// 页面中调用API接口
try {
const res = await store
.dispatch("copyright/downloadCert", detail.value.id)
.then((response: any) => {
downloadCert(response); //调用downloadCert()方法
});
} catch (error: any) {
console.log(error.message);
}
// downloadCert()方法
const downloadCert = (data: any) => {
if (!data) {
return;
}
// 创建a标签,设置download属性,插入到文档中并click
let url = window.URL.createObjectURL(
new Blob([data], {
type: "application/pdf;chartset=UTF-8",
})
);
let link = document.createElement("a");
link.style.display = "none";
link.href = url;
link.setAttribute("download", "证书.pdf");
document.body.appendChild(link);
link.click();
};
2、vue3通过scrollIntoView来实现锚点定位
1.子组件
<div class="tabList">
<div
:class="tabActive === '#home' ? 'active' : ''"
@click="goAnchor('#home')"
>
首页
</div>
<div
:class="tabActive === '#introduce' ? 'active' : ''"
@click="goAnchor('#introduce')"
>
介绍
</div>
<div
:class="tabActive === '#superiority' ? 'active' : ''"
@click="goAnchor('#superiority')"
>
优势
</div>
<div
:class="tabActive === '#application' ? 'active' : ''"
@click="goAnchor('#application')"
>
应用
</div>
</div>
const tabActive = ref("#home");
const goAnchor = (selector: string) => {
tabActive.value = selector;
let anchor = document.querySelector(selector) as any;
anchor.scrollIntoView({
// 定义动画过渡效果, "auto"或 "smooth" 之一。默认为auto,"smooth"为平滑过渡。
behavior: "smooth",
// 定义垂直方向的对齐, "start", "center", "end",默认值为start(上边框与视窗顶部平齐)。
block: "start",
});
};
.tabList {
display: flex;
align-items: center;
div {
cursor: pointer;
text-align: center;
width: 100px;
font-weight: bold;
}
}
.active {
color: rgba(59, 106, 246, 1);
}
2.父组件
<div id="home">
<HeaderPage></HeaderPage>
<div id="introduce">介绍</div>
<div id="superiority">优势</div>
<div id="application">应用</div>
</div>
import HeaderPage from "@/components/HeaderPage.vue";
#home {
width: 100%;
padding-top: 110px;
}
#introduce {
width: 100%;
height: 900px;
background-color: lightcyan;
}
#superiority {
width: 100%;
height: 1500px;
background-color: lightgray;
}
#application {
width: 100%;
height: 500px;
background-color: lightcoral;
}
2023.03.31
1、vue3实现下滑页面隐藏头部导航栏,上滑显示。
父组件相关代码:
// 引入并使用子组件Header导航栏
<HeaderPage :class-name="topScroll"></HeaderPage>
// 滑动页面
const scrollTop = ref(0);
const topScroll = ref(false); //上移样式成立
onMounted(() => {
window.addEventListener("scroll", handleScroll);
});
// 监听页面滚动
const handleScroll = () => {
scrollTop.value =
window.pageYOffset ||
document.documentElement.scrollTop ||
document.body.scrollTop;
};
// 监听top值的变化
watch(scrollTop, (newValue, oldValue) => {
// 等新值大于100的时候再做变化(优化一下)
if (newValue > 100) {
if (newValue > oldValue) {
topScroll.value = true;
} else {
topScroll.value = false;
}
}
});
子组件Header:
<div id="headerPage" :class="className ? 'top' : ''">
......
</div>
const props = defineProps({
className: {
type: Boolean,
default: false,
},
});
.top {
transform: translateY(-90px);
}