DataV轮播表数据太长内容展示不全的解决方法
解决方法:直接手改源码+使用el-tooltip注:这里只涉及vue3的版本。
解决方法:直接手改源码+使用el-tooltip
注:这里只涉及vue3的版本
第一步下载源码
官方的文档:
git地址
https://github.com/vaemusic/datav-vue3
第二步使用源码
在下面的位置找到源码
\datav-vue3-master\packages\datav-vue3\components\ScrollBoard\src\index.vue
直接复制到自己项目中,注意这里有两个引用
import autoResize from 'packages/datav-vue3/utils/autoResize'
import { deepClone, deepMerge } from 'packages/datav-vue3/utils'
解决的方法:对于autoResize,直接创建一个autoResize.ts文件,复制一下内容。deepClone和deepMerge同时也可以复制出来,到自己的创建的文件然后导入使用,但是有一点问题,源码中使用了大量的any,很不友好。
源码内容:
export function deepMerge(target: any, merged: any) {
for (const key in merged) {
if (target[key] && typeof target[key] === 'object') {
deepMerge(target[key], merged[key])
continue
}
if (typeof merged[key] === 'object') {
target[key] = deepClone(merged[key], true)
continue
}
target[key] = merged[key]
}
return target
}
/**
* @description Clone an object or array
* @param {object | Array} object Cloned object
* @param {boolean} recursion Whether to use recursive cloning
* @return {object | Array} Clone object
*/
export function deepClone(object: any, recursion: boolean) {
if (!object)
return object
const { parse, stringify } = JSON
if (!recursion)
return parse(stringify(object))
const clonedObj: Record<string, any> = Array.isArray(object) ? [] : {}
if (object && typeof object === 'object') {
for (const key in object) {
if (Object.prototype.hasOwnProperty.call(object, key)) {
if (object[key] && typeof object[key] === 'object')
clonedObj[key] = deepClone(object[key], true)
else
clonedObj[key] = object[key]
}
}
}
return clonedObj
}
解决方法:deepClone和deepMerge的作用是深拷贝和合并,可以使用lodash解决。
import { cloneDeep, merge } from "lodash";
第三步使用el-tooltip
下面是源码的部分,传递给dataV的参数会采用v-html的方式渲染出去,新建一个dom元素,然后用el-tooltip包裹一下。
源码
<div
v-for="(ceil, ci) in row.ceils"
:key="`${ceil}${ri}${ci}`"
class="ceil"
:style="`width: ${state.widths[ci]}px;`"
:align="state.aligns[ci]"
@click="handleClick(ri, ci, row, ceil)"
@mouseenter="handleHover(true, ri, ci, row, ceil)"
@mouseleave="handleHover(false)"
v-html="ceil"
/>
修改后的源码
<div
v-for="(ceil, ci) in row.ceils"
:key="`${ceil}${ri}${ci}`"
class="ceil"
:style="`width: ${state.widths[ci]}px;`"
:align="state.aligns[ci]"
@click="handleClick(ri, ci, row, ceil)"
@mouseenter="handleHover(true, ri, ci, row, ceil)"
@mouseleave="handleHover(false)"
>
<ElTooltip
popper-class="box-item"
effect="dark"
placement="top-start"
>
<div v-html="ceil" /></div>
<template #content>
<div v-html="ceil" />
</template>
</ElTooltip>
</div>
这样就可以使用了。
第四步智能/动态解决tooltip出现的时机
上述代码之后,会对所有的内容都会有气泡的弹出,需要做的对内容是否被遮挡需要弹出气泡做出判断,判断原理来自el-table的show-tooltip的源码
首先是创建一个boolean参数,这个参数用于禁用/启动el-tooltip,其次监听创建的dom元素的鼠标移入和移出事件,当鼠标移入的时候,判断事件的目标(即鼠标下的dom元素)是否被遮挡,(为了有遮挡的效果,需要添加样式),是则boolean参数为false,不禁用el-tooltip,鼠标移入的时会展示气泡
样式:dataV的样式不一定理想
.text-ellipsis {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
vue的template部分
<div
v-for="(ceil, ci) in row.ceils"
:key="`${ceil}${ri}${ci}`"
class="ceil"
:style="`width: ${state.widths[ci]}px;`"
:align="state.aligns[ci]"
@click="handleClick(ri, ci, row, ceil)"
@mouseenter="handleHover(true, ri, ci, row, ceil)"
@mouseleave="handleHover(false)"
>
<ElTooltip
popper-class="box-item"
effect="dark"
:disabled="!toolTipFlag"
placement="top-start"
>
<div v-html="ceil"
class="text-ellipsis"
@mouseenter="dealTooltip($event, ceil)"
@mouseleave="mouseLeaveTooltip"
/>
<template #content>
<div v-html="ceil" />
</template>
</ElTooltip>
</div>
vue的script部分 ,这里判断代码来自el-table的show-tooltip
// 创建参数
const toolTipFlag = ref(false);
// 鼠标移入
const dealTooltip = (el, ceil) => {
const cellChild = el.target;
const range = window.document.createRange();
range.setStart(cellChild, 0);
range.setEnd(cellChild, cellChild.childNodes.length);
let rangeWidth = range.getBoundingClientRect().width;
let rangeHeight = range.getBoundingClientRect().height;
const offsetWidth = rangeWidth - Math.floor(rangeWidth);
if (offsetWidth < 0.001) {
rangeWidth = Math.floor(rangeWidth);
}
const offsetHeight = rangeHeight - Math.floor(rangeHeight);
if (offsetHeight < 0.001) {
rangeHeight = Math.floor(rangeHeight);
}
const { top, left, right, bottom } = getPadding(cellChild);
const horizontalPadding = left + right;
const verticalPadding = top + bottom;
if (
rangeWidth + horizontalPadding > cellChild.offsetWidth ||
rangeHeight + verticalPadding > cellChild.offsetHeight ||
cellChild.scrollWidth > cellChild.offsetWidth
) {
toolTipFlag.value = true;
}
};
// 鼠标移出
const mouseLeaveTooltip = () => {
toolTipFlag.value = false;
};
第五步解决el-tooltip太多的问题
当给轮播表传递很多的数据,例如几千个数据,页面会非常的卡,所以需要优化一下,把el-tooltip提取出来,只保留一个,什么地方需要,什么地方使用。
这里需要用到el-tooltip的虚拟触发
第一步创建三个参数
// el-tooltip的虚拟触发时的触发元素
const target = ref();
// el-tooltip渲染的内容
const content = ref();
// el-tooltip组件实例,实调用onOpen和onClose控制显隐
const popperRef = ref();
第二步把上面的dom元素中el-tooltip分开,给el-tooltip的virtual-triggering为true,virtual-ref为上面的创建的target,当鼠标移入的时候判断需要展示气泡了,把el.target赋值给上面创建的target
<div
v-for="(ceil, ci) in row.ceils"
:key="`${ceil}${ri}${ci}`"
class="ceil"
:style="`width: ${state.widths[ci]}px;`"
:align="state.aligns[ci]"
@click="handleClick(ri, ci, row, ceil)"
@mouseenter="handleHover(true, ri, ci, row, ceil)"
@mouseleave="handleHover(false)"
>
<div
v-html="ceil"
class="text-ellipsis"
@mouseenter="dealTooltip($event, ceil)"
@mouseleave="mouseLeaveTooltip"
/>
</div>
el-tooltip可以放在头部或者尾部,只要不参与dataV的源码中的v-for循环就行
<ElTooltip
ref="popperRef"
popper-class="box-item"
effect="dark"
placement="top-start"
virtual-triggering
:disabled="!toolTipFlag"
:virtual-ref="target"
:teleported="false"
>
<template #content>
<div v-html="content" style="line-height: 1" />
</template>
</ElTooltip>
之后通过el-tooltip官方给的onOpen和onClose方法去控制显隐
const dealTooltip = (el, ceil) => {
const cellChild = el.target;
const range = window.document.createRange();
range.setStart(cellChild, 0);
range.setEnd(cellChild, cellChild.childNodes.length);
let rangeWidth = range.getBoundingClientRect().width;
let rangeHeight = range.getBoundingClientRect().height;
const offsetWidth = rangeWidth - Math.floor(rangeWidth);
if (offsetWidth < 0.001) {
rangeWidth = Math.floor(rangeWidth);
}
const offsetHeight = rangeHeight - Math.floor(rangeHeight);
if (offsetHeight < 0.001) {
rangeHeight = Math.floor(rangeHeight);
}
const { top, left, right, bottom } = getPadding(cellChild);
const horizontalPadding = left + right;
const verticalPadding = top + bottom;
if (
rangeWidth + horizontalPadding > cellChild.offsetWidth ||
rangeHeight + verticalPadding > cellChild.offsetHeight ||
cellChild.scrollWidth > cellChild.offsetWidth
) {
toolTipFlag.value = true;
target.value = el.target;
content.value = ceil;
popperRef.value.onOpen();
}
};
const mouseLeaveTooltip = () => {
toolTipFlag.value = false;
target.value = null;
content.value = null;
popperRef.value.onClose();
};
这样完成了不论多少个内容需要气泡,当前组件内部只有一个气泡,页面不会卡顿的优化
完整源码
自定义组件ScrollBoard.vue的内容
<template>
<div ref="scrollBoard" class="dv-scroll-board">
<div
v-if="state.header.length && state.mergedConfig"
class="header"
:style="`background-color: ${state.mergedConfig.headerBGC};`"
>
<div
v-for="(headerItem, i) in state.header"
:key="`${headerItem}${i}`"
v-dompurify-html="headerItem"
class="header-item"
:style="`
height: ${state.mergedConfig.headerHeight}px;
line-height: ${state.mergedConfig.headerHeight}px;
width: ${state.widths[i]}px;
`"
:align="state.aligns[i]"
/>
</div>
<div
v-if="state.mergedConfig"
class="rows"
:style="`height: ${height - (state.header.length ? state.mergedConfig.headerHeight : 0)}px;`"
>
<div
v-for="(row, ri) in state.rows"
:key="`${row.toString()}${row.scroll}`"
class="row-item"
:style="`
height: ${state.heights[ri]}px;
line-height: ${state.heights[ri]}px;
background-color: ${
state.mergedConfig[row.rowIndex % 2 === 0 ? 'evenRowBGC' : 'oddRowBGC']
};
`"
>
<div
v-for="(ceil, ci) in row.ceils"
:key="`${ceil}${ri}${ci}`"
class="ceil"
:style="`width: ${state.widths[ci]}px;`"
:align="state.aligns[ci]"
@click="handleClick(ri, ci, row, ceil)"
@mouseenter="handleHover(true, ri, ci, row, ceil)"
@mouseleave="handleHover(false)"
>
<div
v-dompurify-html="ceil"
class="text-ellipsis"
@mouseenter="dealTooltip($event, ceil)"
@mouseleave="mouseLeaveTooltip"
/>
</div>
</div>
<ElTooltip
v-if="state.mergedConfig.showTooltip"
ref="popperRef"
popper-class="box-item"
effect="dark"
placement="top-start"
virtual-triggering
:disabled="!toolTipFlag"
:virtual-ref="target"
:teleported="false"
>
<template #content>
<div v-dompurify-html="content" style="line-height: 1" />
</template>
</ElTooltip>
</div>
</div>
</template>
<script setup>
import { cloneDeep, merge } from "lodash";
import autoResize from "./utils/autoResize";
const props = defineProps({
config: {
type: Object,
default: () => ({}),
},
});
const emitEvent = defineEmits(["mouseover", "click", "getFirstRow"]);
const scrollBoard = ref(null);
const { width, height } = autoResize(scrollBoard, onResize, afterAutoResizeMixinInit);
const state = reactive({
defaultConfig: {
/**
* @description Board header
* @type {Array<string>}
* @default header = []
* @example header = ['column1', 'column2', 'column3']
*/
header: [],
/**
* @description Board data
* @type {Array<Array>}
* @default data = []
*/
data: [],
/**
* @description Row num
* @type {number}
* @default rowNum = 5
*/
rowNum: 5,
/**
* @description Header background color
* @type {string}
* @default headerBGC = '#00BAFF'
*/
headerBGC: "#00BAFF",
/**
* @description Odd row background color
* @type {string}
* @default oddRowBGC = '#003B51'
*/
oddRowBGC: "#003B51",
/**
* @description Even row background color
* @type {string}
* @default evenRowBGC = '#003B51'
*/
evenRowBGC: "#0A2732",
/**
* @description Scroll wait time
* @type {number}
* @default waitTime = 2000
*/
waitTime: 2000,
/**
* @description Header height
* @type {number}
* @default headerHeight = 35
*/
headerHeight: 35,
/**
* @description Column width
* @type {Array<number>}
* @default columnWidth = []
*/
columnWidth: [],
/**
* @description Column align
* @type {Array<string>}
* @default align = []
* @example align = ['left', 'center', 'right']
*/
align: [],
/**
* @description Show index
* @type {boolean}
* @default index = false
*/
index: false,
/**
* @description index Header
* @type {string}
* @default indexHeader = '#'
*/
indexHeader: "#",
/**
* @description Carousel type
* @type {string}
* @default carousel = 'single'
* @example carousel = 'single' | 'page'
*/
carousel: "single",
/**
* @description Pause scroll when mouse hovered
* @type {boolean}
* @default hoverPause = true
* @example hoverPause = true | false
*/
hoverPause: true,
/**
* @description 是否使用气泡
* @type {boolean}
* @default hoverPause = true
* @example showTooltip = true | false
*/
showTooltip: false,
},
mergedConfig: null,
header: [],
rowsData: [],
rows: [],
widths: [],
heights: [],
avgHeight: 0,
aligns: [],
animationIndex: 0,
animationHandler: "",
updater: 0,
needCalc: false,
});
watch(
() => props.config,
() => {
stopAnimation();
// 此处打开后,config发生变化时,会从第一行开始重新轮播
// state.animationIndex = 0
calcData();
},
{ deep: true },
);
onUnmounted(() => {
stopAnimation();
});
defineExpose({
updateRows,
});
function handleClick(ri, ci, row, ceil) {
const { ceils, rowIndex } = row;
emitEvent("click", {
row: ceils,
ceil,
rowIndex,
columnIndex: ci,
});
}
function handleHover(enter, ri, ci, row, ceil) {
if (enter) {
const { ceils, rowIndex } = row;
emitEvent("mouseover", {
row: ceils,
ceil,
rowIndex,
columnIndex: ci,
});
}
if (!state.mergedConfig.hoverPause) return;
if (enter) stopAnimation();
else animation(true);
}
function afterAutoResizeMixinInit() {
calcData();
}
function onResize() {
if (!state.mergedConfig) return;
calcWidths();
calcHeights();
}
function calcData() {
mergeConfig();
calcHeaderData();
calcRowsData();
calcWidths();
calcHeights();
calcAligns();
animation(true);
}
function mergeConfig() {
state.mergedConfig = merge(cloneDeep(state.defaultConfig, true), props.config || {});
}
function calcHeaderData() {
let { header } = state.mergedConfig;
const { index, indexHeader } = state.mergedConfig;
if (!header.length) {
header = [];
return;
}
header = [...header];
if (index) header.unshift(indexHeader);
state.header = header;
}
function calcRowsData() {
let { data } = state.mergedConfig;
const { index, headerBGC, rowNum } = state.mergedConfig;
if (index) {
data = data.map((row, i) => {
row = [...row];
const indexTag = `<span class="index" style="background-color: ${headerBGC};">${
i + 1
}</span>`;
row.unshift(indexTag);
return row;
});
}
data = data.map((ceils, i) => ({ ceils, rowIndex: i }));
const rowLength = data.length;
if (rowLength > rowNum && rowLength < 2 * rowNum) data = [...data, ...data];
data = data.map((d, i) => ({ ...d, scroll: i }));
state.rowsData = data;
state.rows = data;
}
function calcWidths() {
const { columnWidth, header } = state.mergedConfig;
const usedWidth = columnWidth.reduce((all, w) => all + w, 0);
let columnNum = 0;
if (state.rowsData[0]) columnNum = state.rowsData[0].ceils.length;
else if (header.length) columnNum = header.length;
const avgWidth = (width.value - usedWidth) / (columnNum - columnWidth.length);
const widths = Array.from({ length: columnNum }).fill(avgWidth);
state.widths = merge(widths, columnWidth);
}
function calcHeights(onresize = false) {
const { headerHeight, rowNum, data } = state.mergedConfig;
let allHeight = height.value;
if (state.header.length) allHeight -= headerHeight;
const avgHeight = allHeight / rowNum;
state.avgHeight = avgHeight;
if (!onresize) state.heights = Array.from({ length: data.length }).fill(avgHeight);
}
function calcAligns() {
const columnNum = state.header.length;
const aligns = Array.from({ length: columnNum }).fill("left");
const { align } = state.mergedConfig;
state.aligns = merge(aligns, align);
}
async function animation(start = false) {
if (state.needCalc) {
calcRowsData();
calcHeights();
state.needCalc = false;
}
const { waitTime, carousel, rowNum } = state.mergedConfig;
const { updater } = state;
const rowLength = state.rowsData.length;
if (rowNum >= rowLength) return;
if (start) await new Promise((resolve) => setTimeout(resolve, waitTime));
if (updater !== state.updater) return;
const animationNum = carousel === "single" ? 1 : rowNum;
const rows = state.rowsData.slice(state.animationIndex);
rows.push(...state.rowsData.slice(0, state.animationIndex));
state.rows = rows.slice(0, carousel === "page" ? rowNum * 2 : rowNum + 1);
state.heights = Array.from({ length: rowLength }).fill(state.avgHeight);
await new Promise((resolve) => setTimeout(resolve, 300));
if (updater !== state.updater) return;
state.heights.splice(0, animationNum, ...Array.from({ length: animationNum }).fill(0));
state.animationIndex += animationNum;
const back = state.animationIndex - rowLength;
if (back >= 0) state.animationIndex = back;
// state.animationIndex = animationIndex
state.animationHandler = setTimeout(animation, waitTime - 300);
emitEvent("getFirstRow", rows[1]);
}
function stopAnimation() {
state.updater = (state.updater + 1) % 999999;
if (!state.animationHandler) return;
clearTimeout(state.animationHandler);
}
function updateRows(rows, animationIndex) {
state.mergedConfig = {
...state.mergedConfig,
data: [...rows],
};
state.needCalc = true;
if (typeof animationIndex === "number") state.animationIndex = animationIndex;
if (!state.animationHandler) animation(true);
}
// 解决气泡的问题,代码来自el-table的show-tootip
// el-tooltip的虚拟触发时的触发元素
const target = ref();
// el-tooltip渲染的内容
const content = ref();
// el-tooltip组件实例,实调用onOpen和onClose控制显隐
const popperRef = ref();
// 判断是否需要禁用el-tooltip,当内容没被遮挡时禁用
const toolTipFlag = ref(false);
/**
* 鼠标移入元素的事件
* @param {Event} el
* 鼠标移入位置的内容
* @param {String} ceil
*/
const dealTooltip = (el, ceil) => {
if (!state.mergedConfig.showTooltip) return;
const cellChild = el.target;
// 创建Range对象,通过文本的尺寸和所处的位置的尺寸比较判断是否为被遮挡的
const range = window.document.createRange();
range.setStart(cellChild, 0);
range.setEnd(cellChild, cellChild.childNodes.length);
let rangeWidth = range.getBoundingClientRect().width;
let rangeHeight = range.getBoundingClientRect().height;
const offsetWidth = rangeWidth - Math.floor(rangeWidth);
if (offsetWidth < 0.001) {
rangeWidth = Math.floor(rangeWidth);
}
const offsetHeight = rangeHeight - Math.floor(rangeHeight);
if (offsetHeight < 0.001) {
rangeHeight = Math.floor(rangeHeight);
}
const { top, left, right, bottom } = getPadding(cellChild);
const horizontalPadding = left + right;
const verticalPadding = top + bottom;
// 判断也是来自源码,文本被遮挡时显示el-tooltip
if (
rangeWidth + horizontalPadding > cellChild.offsetWidth ||
rangeHeight + verticalPadding > cellChild.offsetHeight ||
cellChild.scrollWidth > cellChild.offsetWidth
) {
toolTipFlag.value = true;
target.value = el.target;
content.value = ceil;
popperRef.value.onOpen();
}
};
// 鼠标移出元素时,有关el-tooltip的参数还原,el-tooltip隐藏
const mouseLeaveTooltip = () => {
if (!state.mergedConfig.showTooltip) return;
toolTipFlag.value = false;
target.value = null;
content.value = null;
popperRef.value.onClose();
};
/**
* 获取el元素的css中的paddingLeft等属性值
* @param {HTMLElement} el
*/
const getPadding = (el) => {
const style = window.getComputedStyle(el, null);
const paddingLeft = Number.parseInt(style.paddingLeft, 10) || 0;
const paddingRight = Number.parseInt(style.paddingRight, 10) || 0;
const paddingTop = Number.parseInt(style.paddingTop, 10) || 0;
const paddingBottom = Number.parseInt(style.paddingBottom, 10) || 0;
return {
left: paddingLeft,
right: paddingRight,
top: paddingTop,
bottom: paddingBottom,
};
};
</script>
<style lang="less">
.dv-scroll-board {
position: relative;
width: 100%;
height: 100%;
color: #fff;
.text {
padding: 0 10px;
box-sizing: border-box;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.header {
display: flex;
flex-direction: row;
font-size: 15px;
.header-item {
.text;
transition: all 0.3s;
}
}
.rows {
overflow: hidden;
.row-item {
display: flex;
font-size: 14px;
transition: all 0.3s;
}
.ceil {
.text;
}
.index {
border-radius: 3px;
padding: 0px 3px;
}
}
}
.box-item {
padding: 0.1rem 0.2rem;
div {
span {
color: #fff !important;
font-size: 0.225rem;
}
}
}
.text-ellipsis {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>
autoResize.ts
import { nextTick, onActivated, onDeactivated, onMounted, onUnmounted, ref } from "vue";
import { useDebounceFn, useEventListener } from "@vueuse/core";
import type { Ref } from "vue";
/**
* 创建观察器
* @param {HTMLElement} dom
* @param {() => void} callback
* @returns {window.MutationObserver}
*/
export function observerDomResize(dom: HTMLElement, callback: () => void) {
const MutationObserver = window.MutationObserver;
const observer = new MutationObserver(callback);
observer.observe(dom, { attributes: true, attributeFilter: ["style"], attributeOldValue: true });
return observer;
}
function autoResize(
dom: Ref<HTMLElement | null>,
onResize?: () => void,
afterAutoResizeMixinInit?: () => void,
) {
const width = ref(0);
const height = ref(0);
let debounceInitWHFun: () => void;
let domObserver: MutationObserver | null = null;
let domHtml: HTMLElement | null = null;
const initWH = (resize = true) => {
return new Promise((resolve) => {
nextTick(() => {
domHtml = dom.value;
width.value = dom.value ? dom.value.clientWidth : 0;
height.value = dom.value ? dom.value.clientHeight : 0;
if (!dom.value)
console.warn("DataV: Failed to get dom node, component rendering may be abnormal!");
else if (!width.value || !height.value)
console.warn("DataV: Component width or height is 0px, rendering abnormality may occur!");
if (typeof onResize === "function" && resize) onResize();
resolve(true);
});
});
};
const getDebounceInitWHFun = () => {
debounceInitWHFun = useDebounceFn(initWH, 200);
};
const bindDomResizeCallback = () => {
domObserver = observerDomResize(domHtml!, debounceInitWHFun);
useEventListener(window, "resize", debounceInitWHFun);
};
const unbindDomResizeCallback = () => {
if (!domObserver) return;
domObserver.disconnect();
domObserver.takeRecords();
domObserver = null;
};
const autoResizeMixinInit = async () => {
await initWH(false);
getDebounceInitWHFun();
bindDomResizeCallback();
if (typeof afterAutoResizeMixinInit === "function") afterAutoResizeMixinInit();
};
onMounted(() => {
autoResizeMixinInit();
});
onUnmounted(() => {
unbindDomResizeCallback();
});
onActivated(autoResizeMixinInit);
onDeactivated(unbindDomResizeCallback);
return {
width,
height,
initWH,
};
}
export default autoResize;
这里多了一个参数showTooltip,用于控制当前自定义组件是否需要el-tooltip

另外
可以优化的点:使用v-dompurify-html代替v-html会更安全,替代方法
npm i vue-dompurify-html
在main.ts中引入和使用
// 使用v-dompurify-html替代v-html
import VueDOMPurifyHTML from "vue-dompurify-html";
const vueApp = Vue.createApp(App);
vueApp.use(VueDOMPurifyHTML);
dataV的使用方法:
npm install @kjgl77/datav-vue3
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import DataVVue3 from '@kjgl77/datav-vue3'
createApp(App).use(DataVVue3).mount('#app')
<template>
<div>
<dv-decoration-1 :color="['pink','yellow']" style="width:200px;height:50px;" />
</div>
</template>
<script setup lang="ts">
</script>
<style lang="scss" scoped>
</style>
如有报错,需要安装@dataview/datav-vue3
npm i @dataview/datav-vue3
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)