- 单元格合并
function resetSpanArr() {
let contactDot = 0;
state.spanArr = [];
state.drawerForm.socTenantDTOList.forEach((item, index) => {
//遍历tableData数据,给spanArr存入一个1,第二个item.id和前一个item.id是否相同,相同就给
//spanArr前一位加1,spanArr再存入0,因为spanArr为n的项表示n项合并,为0的项表示该项不显示,后面有spanArr打印结果
if (index === 0) {
state.spanArr.push(1);
} else {
if (item.tenantId === state.drawerForm.socTenantDTOList[index - 1].tenantId) {
state.spanArr[contactDot] += 1;
state.spanArr.push(0);
} else {
contactDot = index;
state.spanArr.push(1);
}
}
});
}
function objectSpanMethod({ row, column, rowIndex, columnIndex }) {
console.log(rowIndex, columnIndex);
if (columnIndex === 0 || columnIndex === 1) {
const _row = state.spanArr[rowIndex];
const _col = _row > 0 ? 1 : 0;
//该形式为行合并
return {
rowspan: _row,
colspan: _col,
};
}
}
- vue3封装弹框组件-函数声明方式和命令式
https://juejin.cn/post/7377578894591000586
https://juejin.cn/post/7223775785206792229?from=search-suggest
import { h, createApp, nextTick, provide, ref } from 'vue';
import MyMessageBox from './MyMessageBox.vue';
let messageBoxInstance = null;
const QWMessageBox = (options) => {
return new Promise((resolve, reject) => {
if (!messageBoxInstance) {
const div = document.createElement('div');
document.body.appendChild(div);
const visible = ref(false);
const app = createApp({
setup() {
return () =>
h(MyMessageBox, {
...options,
visible,
'onUpdate:visible': (value) => {
visible.value = value;
if (!value) {
setTimeout(() => {
app.unmount();
div.remove();
messageBoxInstance = null;
}, 300); // 等待动画完成
}
},
onConfirm: () => {
resolve();
},
onCancel: () => {
// resolve(false);
console.log('用户点击了取消');
},
});
},
});
app.mount(div);
messageBoxInstance = { app, visible };
}
nextTick(() => {
messageBoxInstance.visible.value = true;
});
});
};
export default QWMessageBox;
// import { createApp } from 'vue';
// import Dialog from './MyMessageBox1.vue';
// function openModal({ title, content, confirmBtnTxt = '确定', cancelBtnTxt = '取消' }) {
// return new Promise((resolve, reject) => {
// const app = createApp(Dialog, {
// title,
// content,
// confirmBtnTxt,
// cancelBtnTxt,
// onConfirm: () => {
// unmount();
// resolve();
// },
// onCancel: () => {
// unmount();
// // reject();
// },
// });
// // 创建一个挂载容器
// const parentNode = document.createElement('div');
// document.body.appendChild(parentNode);
// // 卸载组件
// const unmount = () => {
// app.unmount();
// document.body.removeChild(parentNode);
// };
// // 挂载组件
// app.mount(parentNode);
// });
// }
// export default openModal;
<template>
<div class="message-box-delete">
<el-dialog v-model="dialogVisible" width="450" @close="dialogVisible = false">
<template #header>
<div class="my-header">
<div class="left">
<el-icon v-if="type == 'danger'" :size="24" style="height: 100%">
<SvgIcon :iconClass="'error'" />
</el-icon>
<img v-else src="@/assets/images/command/waring.png" alt="" />
<span class="text-medium" style="font-size: 20px; font-weight: 600">删除</span>
</div>
</div>
</template>
<slot>
<div>
<span class="content text-regular"
>您确认要删除<span class="text-semibold">{{ title }}</span
>吗?</span
>
</div>
<span v-if="content && content?.length > 0" class="content text-regular">{{ content }}</span>
</slot>
<template #footer>
<el-button :disabled="isCancelDisabled" @click="onCancel">取消</el-button>
<el-button :disabled="isConfirmDisabled" type="primary" @click="onConfirm"> 确定 </el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ElButton, ElDialog, ElIcon } from 'element-plus';
import SvgIcon from '@/components/SvgIcon/index';
const emit = defineEmits(['update:visible', 'confirm', 'cancel']);
const props = defineProps({
visible: Boolean,
title: String,
type: String, // 警告(waring) 还是错误(danger)状态
isConfirmDisabled: Boolean,
isCancelDisabled: Boolean,
content: String,
});
const dialogVisible = computed({
get() {
return props.visible;
},
set(v) {
emit('update:visible', v);
},
});
const onConfirm = () => {
dialogVisible.value = false;
emit('confirm');
};
const onCancel = () => {
dialogVisible.value = false;
emit('cancel');
};
</script>
<style lang="scss">
.message-box-delete {
.my-header {
display: flex;
align-items: center;
justify-content: space-between;
.left {
display: flex;
align-items: center;
gap: 8px;
}
}
.content {
margin-bottom: 24px;
display: inline-block;
}
.el-overlay-dialog {
display: flex;
justify-content: center;
align-items: center;
}
.el-dialog {
margin-top: 0 !important;
}
.el-dialog__close {
color: #666 !important;
}
}
</style>
- 时间区间选择器,控制时间区间
<el-date-picker
v-model="state.searchForm.date"
type="datetimerange"
:default-time="defaultTimeRange"
value-format="YYYY-MM-DD HH:mm:ss"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
:disabled-date="disabledDate"
@calendar-change="calendarChange"
:clearable="true"
/>
function calendarChange(e) {
state.minDate = e[0].getTime();
}
//时间筛选范围一个月
function disabledDate(time) {
return (
time.getTime() <= moment(state.minDate).subtract(6, 'month').valueOf() ||
time.getTime() >= moment(state.minDate).add(6, 'month').valueOf()
);
}
- 居中布局
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
- 自定义指令
export default function (app) {
app.directive("special-chars", {
mounted(el, binding, vnode) {
el.addEventListener("input", function (event) {
// 获取输入的值
const value = event.target.value;
// 使用正则表达式检测反斜杠(根据需要匹配相应限制字符)
const regex =
/[`~!@#$%^&*()_\-+=<>?:"{}|,./;'\\[\]·~!@#¥%……&*()——\-+={}|《》?:“”【】、;‘’,。、]/g;
// 如果输入值包含反斜杠,则替换为空格
if (regex.test(value)) {
// 使用 replace 方法替换反斜杠为空格
const newValue = value.replace(regex, "");
// 将新值设置回输入框
event.target.value = newValue;
// 触发 @input 事件,使其更新组件中的数据
// vnode.componentInstance.$emit("input", newValue);
// 触发input事件来更新绑定的v-model值
el.dispatchEvent(new Event("input", { bubbles: true }));
}
});
},
unmounted(el) {
el.removeEventListener('input');
}
});
}
- 下载通知
// 下载视频
downLoad(item, tab) {
this.downLoadIndex++
const notify = this.$notify({
title: 'Work start download',
message: `<div id='notify-${this.downLoadIndex}'>The video is downloading, progress is 0%, please wait</div>`,
dangerouslyUseHTMLString: true,
type: 'success',
duration: 0,
})
this.downLoadFile(item, this.downLoadIndex, tab)
.then(() => {
notify.close()
this.$notify({
title: 'Work download completed',
type: 'success',
duration: 3000,
})
})
.catch(() => {
notify.close()
this.$notify({
title: 'Work download failed',
type: 'error',
duration: 3000,
})
})
},
downLoadFile(item, index, tab) {
let url = ''
if (tab == 'avatar') {
url = item.audioUrl
} else {
url = item.videoUrl
}
let name = item.workName + '_Anylang-ai'
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open('GET', url, true)
xhr.send()
xhr.responseType = 'blob'
xhr.addEventListener('progress', (ev) => {
const percent = parseInt((ev.loaded / ev.total) * 100)
const dom = document.getElementById(`notify-${index}`)
dom.innerText = `The video is downloading, progress is ${percent}%, please wait`
})
xhr.onload = (ev) => {
if (xhr.status == 200) {
const urlx = window.URL.createObjectURL(xhr.response)
const dom = document.createElement('a')
dom.href = urlx
dom.download = name
dom.click()
URL.revokeObjectURL(dom.href)
resolve()
} else if (response.status == 404) {
reject()
} else if (response.status == 500) {
reject()
}
}
})
},
- 分享一些::after和::before使用的经验
&:before画圆
<template>
<div>
<div class="title">今日新增量</div >
<div/>
<template>
<style>
title {
position: relative;
&:before {
// top: 5px;
// left: -15px;
position: absolute;
display: block;
width: 10px;
height: 10px;
border-radius: 50%;
background: #fa6861;
content: '';
}
}
<style/>
&:before竖条
<template>
<div>
<div class="title">前面加个竖条</div >
<div/>
<template>
<style>
.title {
position: relative;
&:before {
top: 25px;
left: 80px;
position: absolute;
display: block;
width: 6px;
height: 30px;
background: #4d99f9;
content: '';
}
}
<style/>
&:before插入图片
<template>
<div>
<div class="title">前面加个图片</div >
<div/>
<template>
<style>
.title {
position: relative;
&:before {
content: '';
position: absolute;
left: 75px;
top: 26px;
height: 28px;
width: 12px;
background: url('~@/assets/images/second-title.png') no-repeat;
background-size: 100% 100%;
}
}
<style/>
- 一行文字过多时,省略号隐藏显示
1.页面部分
<el-row>
<el-col :span="24">
<div class="information-title">
一行文字过多时,隐藏省略显示;一行文字过多时,隐藏省略显示;一行文字过多时,隐藏省略显示;
一行文字过多时,隐藏省略显示;一行文字过多时,隐藏省略显示;一行文字过多时,隐藏省略显示;
一行文字过多时,隐藏省略显示;一行文字过多时,隐藏省略显示;一行文字过多时,隐藏省略显示;
</div>
</el-col>
</el-row>
2.css样式部分
.information-title {
color: #19d3ea;
font-size: 18px;
width: 100%; /*一定要设置宽度,或者元素内含的百分比*/
overflow:hidden; /*溢出的部分隐藏*/
white-space: nowrap; /*文本不换行*/
text-overflow:ellipsis;/*ellipsis:文本溢出显示省略号(...);clip:不显示省略标记(...),而是简单的裁切*/
- 防止被压缩
flex-shrink: 0;
- vue3 使用类似
.sync的功能:
// 父组件
<child-modal v-model:visible="visible"></child-modal>
// 子组件
<template>
<a-modal v-model:visible="show" title="Basic Modal" @ok="handleOk">
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</a-modal>
</template>
<script setup>
import { computed, defineProps, defineEmits} from 'vue'
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
})
const $emit = defineEmits(['update:visible'])
const show = computed({
get() {
return props.visible
},
set(v) {
$emit('update:visible', v)
},
})
const handleOk = e => {
console.log(e)
show.value = false
}
</script>
- Promise
Promise有三种状态,分别是“待定”、“已完成”和“已拒绝”。当一个Promise被创建时,它处于“待定”状态。当操作成功完成时,Promise进入“已完成”状态,并返回一个包含操作结果的值。当操作失败时,Promise进入“已拒绝”状态,并返回一个包含错误信息的原因。
Promise可以链式调用,这使得它们可以更方便地组合异步操作。Promise还可以通过catch()方法来处理错误,这使得代码的错误处理更加容易。
链式调用(实现同步操作):
下面直接return值,是因为Promise会自动用Promise包裹

image.png
-
列表网格排列:
image.png
-
更新列表item数据,无法响应问题,以下两种方式都可以 https://blog.csdn.net/qq_26780317/article/details/120130388
image.png 进度条随机值
// 进度条随机值
addIntervalProgress() {
//取[10, 20]之间的随机整数
this.currentProgress = Math.floor(Math.random() * (20 - 10 + 1)) + 10;
// console.log("1111 ---- ", this.currentProgress);
let timer = setInterval(() => {
this.currentProgress += Math.floor(Math.random() * 5);
// console.log("2222 ---- ", this.currentProgress);
if (this.currentProgress > 99) {
this.currentProgress = 99;
clearInterval(timer);
}
}, 100);
},
H5 播放rtsp视频流
前段目前不支持直接播放这种格式,一般都是通过把rtsp擦、转成前段支持的格式
1、https://cloud.tencent.com/developer/article/1805057
VLC是一个不错的Chrome播放rtsp视频流方案,延迟低,性能稳定。但是从2015年以后,Chrome等浏览器取消对NPAPI支持后,VLC就不能直接使用了mac可以用node.js的多版本管理器n来升级和切换,命令如下:
1、sudo npm cache clean -f //清除node.js的cache
2、sudo npm install -g n //使用npm安装n模块
3、npm view node versions // 查看node所有版本
4、sudo n latest // 升级到最新版本
sudo n stable // 升级到稳定版本
sudo n xx.xx // 升级到具体版本号
5、node -v //查看当前安装的版本号
6、n //检查目前安装了哪些版本的node,会出现已安装的node版本,选一个就可以直接切换了
- 全局禁用空格
app.directive('space', {
// 当被绑定的元素挂载到 DOM 中时……
mounted(el) {
el.addEventListener('input', (event) => {
// 阻止空格字符的输入
if (event.target.value?.includes(' ')) {
// 移除空格,并更新输入框的值
event.target.value = event.target.value.replace(/\s+/g, '');
// 如果你需要触发 input 事件的更新(比如用于 v-model)
// 可以使用 Vue 的 nextTick 来确保 DOM 更新后再触发事件
setTimeout(() => {
const events = new Event('input', { bubbles: true });
el.dispatchEvent(events);
}, 50);
}
});
},
});

