新增tabs标签工具
This commit is contained in:
@@ -27,7 +27,7 @@
|
|||||||
v-for="(it, idx) in item.list"
|
v-for="(it, idx) in item.list"
|
||||||
:key="idx"
|
:key="idx"
|
||||||
draggable="true"
|
draggable="true"
|
||||||
@dragstart="dragStart(it.code)"
|
@dragstart="dragStart(it.code, $event)"
|
||||||
@dragend="dragEnd()"
|
@dragend="dragEnd()"
|
||||||
>
|
>
|
||||||
<div class="tools-item">
|
<div class="tools-item">
|
||||||
@@ -526,9 +526,14 @@ export default {
|
|||||||
getPXUnderScale(px) {
|
getPXUnderScale(px) {
|
||||||
return this.bigscreenScaleInWorkbench * px;
|
return this.bigscreenScaleInWorkbench * px;
|
||||||
},
|
},
|
||||||
dragStart(widgetCode) {
|
dragStart(widgetCode, evt) {
|
||||||
this.dragWidgetCode = widgetCode;
|
this.dragWidgetCode = widgetCode;
|
||||||
this.currentWidgetTotal = this.widgets.length; // 当前操作面板上有多少各组件
|
this.currentWidgetTotal = this.widgets.length; // 当前操作面板上有多少各组件
|
||||||
|
// 通过 dataTransfer 传递组件 code,便于 Tabs 等嵌套容器在 drop 时读取(不依赖父组件链)
|
||||||
|
if (evt && evt.dataTransfer) {
|
||||||
|
evt.dataTransfer.setData("application/x-widget-code", widgetCode);
|
||||||
|
evt.dataTransfer.effectAllowed = "copy";
|
||||||
|
}
|
||||||
},
|
},
|
||||||
dragEnd() {
|
dragEnd() {
|
||||||
/**
|
/**
|
||||||
@@ -555,12 +560,48 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
dragOver(evt) {
|
dragOver(evt) {
|
||||||
|
// 鼠标在 Tabs 标签内容区内时不处理,让 tab 内容区成为 drop 目标,避免工作台抢走
|
||||||
|
if (evt.target && evt.target.closest && (evt.target.closest(".tab-content") || evt.target.closest("[data-tab-content]"))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
evt.dataTransfer.dropEffect = "copy";
|
evt.dataTransfer.dropEffect = "copy";
|
||||||
},
|
},
|
||||||
// 拖动一个组件放到工作区中去,在拖动结束时,放到工作区对应的坐标点上去
|
// 拖动一个组件放到工作区中去,在拖动结束时,放到工作区对应的坐标点上去
|
||||||
widgetOnDragged(evt) {
|
widgetOnDragged(evt) {
|
||||||
|
// 若落在 Tabs 标签内容区内,由 widgetTabs 自己处理,不在此处添加到主画布
|
||||||
|
if (evt.target && evt.target.closest && (evt.target.closest(".tab-content") || evt.target.closest("[data-tab-content]"))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 若落在 Tabs 组件的外层容器上(如 avue-draggable),委托给对应 Tabs 加入当前激活的 tab
|
||||||
|
const widgetsRef = this.$refs.widgets;
|
||||||
|
const widgetList = Array.isArray(widgetsRef) ? widgetsRef : (widgetsRef ? [widgetsRef] : []);
|
||||||
|
if (evt.target && widgetList.length) {
|
||||||
|
for (let i = 0; i < widgetList.length; i++) {
|
||||||
|
const w = widgetList[i];
|
||||||
|
if (!w || !w.$el || !w.$el.contains(evt.target)) continue;
|
||||||
|
if (this.widgets[i] && this.widgets[i].type === "widget-tabs") {
|
||||||
|
const findTabs = (comp) => {
|
||||||
|
if (!comp) return null;
|
||||||
|
if (typeof comp.addWidgetFromDrop === "function") return comp;
|
||||||
|
if (comp.$children && comp.$children.length) {
|
||||||
|
for (let j = 0; j < comp.$children.length; j++) {
|
||||||
|
const found = findTabs(comp.$children[j]);
|
||||||
|
if (found) return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
const tabsComp = findTabs(w);
|
||||||
|
if (tabsComp) {
|
||||||
|
tabsComp.addWidgetFromDrop(evt);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
let widgetType = this.dragWidgetCode;
|
let widgetType = this.dragWidgetCode;
|
||||||
|
|
||||||
// 获取结束坐标和列名
|
// 获取结束坐标和列名
|
||||||
|
|||||||
@@ -0,0 +1,221 @@
|
|||||||
|
/*
|
||||||
|
* @Descripttion: Tabs标签组件
|
||||||
|
* @version:
|
||||||
|
* @Author: Devli
|
||||||
|
* @Date: 2024-01-01 00:00:00
|
||||||
|
* @LastEditors: Devli
|
||||||
|
* @LastEditTime: 2024-01-01 00:00:00
|
||||||
|
*/
|
||||||
|
export const widgetTabs = {
|
||||||
|
code: 'widget-tabs',
|
||||||
|
type: 'form',
|
||||||
|
tabName: '表单',
|
||||||
|
label: 'Tabs标签',
|
||||||
|
icon: 'iconkuangjia',
|
||||||
|
options: {
|
||||||
|
// 配置
|
||||||
|
setup: [
|
||||||
|
{
|
||||||
|
type: 'el-input-text',
|
||||||
|
label: '图层名称',
|
||||||
|
name: 'layerName',
|
||||||
|
required: false,
|
||||||
|
placeholder: '',
|
||||||
|
value: 'Tabs标签',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'el-select',
|
||||||
|
label: '标签位置',
|
||||||
|
name: 'tabPosition',
|
||||||
|
required: false,
|
||||||
|
placeholder: '',
|
||||||
|
selectOptions: [
|
||||||
|
{ code: 'top', name: '顶部' },
|
||||||
|
{ code: 'right', name: '右侧' },
|
||||||
|
{ code: 'bottom', name: '底部' },
|
||||||
|
{ code: 'left', name: '左侧' },
|
||||||
|
],
|
||||||
|
value: 'top',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'el-select',
|
||||||
|
label: '标签类型',
|
||||||
|
name: 'type',
|
||||||
|
required: false,
|
||||||
|
placeholder: '',
|
||||||
|
selectOptions: [
|
||||||
|
{ code: '', name: '默认' },
|
||||||
|
{ code: 'border-card', name: '边框卡片' },
|
||||||
|
{ code: 'card', name: '卡片' },
|
||||||
|
],
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'el-switch',
|
||||||
|
label: '可关闭',
|
||||||
|
name: 'closable',
|
||||||
|
required: false,
|
||||||
|
placeholder: '',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'el-switch',
|
||||||
|
label: '可添加',
|
||||||
|
name: 'addable',
|
||||||
|
required: false,
|
||||||
|
placeholder: '',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'el-switch',
|
||||||
|
label: '可拉伸',
|
||||||
|
name: 'stretch',
|
||||||
|
required: false,
|
||||||
|
placeholder: '',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'vue-color',
|
||||||
|
label: '标签字体颜色',
|
||||||
|
name: 'labelColor',
|
||||||
|
required: false,
|
||||||
|
placeholder: '',
|
||||||
|
value: '#303133',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'vue-color',
|
||||||
|
label: '激活标签颜色',
|
||||||
|
name: 'activeColor',
|
||||||
|
required: false,
|
||||||
|
placeholder: '',
|
||||||
|
value: '#409EFF',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'el-input-number',
|
||||||
|
label: '字体字号',
|
||||||
|
name: 'fontSize',
|
||||||
|
required: false,
|
||||||
|
placeholder: '',
|
||||||
|
value: 14,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'el-button',
|
||||||
|
label: '标签列表',
|
||||||
|
name: 'tabsList',
|
||||||
|
required: false,
|
||||||
|
placeholder: '',
|
||||||
|
value: [
|
||||||
|
{ label: '标签一', name: 'tab1', content: '标签一的内容', children: [] },
|
||||||
|
{ label: '标签二', name: 'tab2', content: '标签二的内容', children: [] },
|
||||||
|
{ label: '标签三', name: 'tab3', content: '标签三的内容', children: [] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: '组件联动',
|
||||||
|
list: [
|
||||||
|
{
|
||||||
|
type: 'componentLinkage',
|
||||||
|
label: '',
|
||||||
|
name: 'componentLinkage',
|
||||||
|
required: false,
|
||||||
|
value: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
],
|
||||||
|
// 数据
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
type: 'el-radio-group',
|
||||||
|
label: '数据类型',
|
||||||
|
name: 'dataType',
|
||||||
|
require: false,
|
||||||
|
placeholder: '',
|
||||||
|
selectValue: true,
|
||||||
|
selectOptions: [
|
||||||
|
{
|
||||||
|
code: 'staticData',
|
||||||
|
name: '静态数据',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'dynamicData',
|
||||||
|
name: '动态数据',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
value: 'staticData',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'el-input-number',
|
||||||
|
label: '刷新时间(毫秒)',
|
||||||
|
name: 'refreshTime',
|
||||||
|
relactiveDom: 'dataType',
|
||||||
|
relactiveDomValue: 'dynamicData',
|
||||||
|
value: 30000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'el-button',
|
||||||
|
label: '静态数据',
|
||||||
|
name: 'staticData',
|
||||||
|
required: false,
|
||||||
|
placeholder: '',
|
||||||
|
relactiveDom: 'dataType',
|
||||||
|
relactiveDomValue: 'staticData',
|
||||||
|
value: [
|
||||||
|
{ label: '标签一', name: 'tab1', content: '标签一的内容', children: [] },
|
||||||
|
{ label: '标签二', name: 'tab2', content: '标签二的内容', children: [] },
|
||||||
|
{ label: '标签三', name: 'tab3', content: '标签三的内容', children: [] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'dycustComponents',
|
||||||
|
label: '',
|
||||||
|
name: 'dynamicData',
|
||||||
|
required: false,
|
||||||
|
placeholder: '',
|
||||||
|
relactiveDom: 'dataType',
|
||||||
|
relactiveDomValue: 'dynamicData',
|
||||||
|
chartType: 'widget-tabs',
|
||||||
|
dictKey: 'TABS_PROPERTIES',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
// 坐标
|
||||||
|
position: [
|
||||||
|
{
|
||||||
|
type: 'el-input-number',
|
||||||
|
label: '左边距',
|
||||||
|
name: 'left',
|
||||||
|
required: false,
|
||||||
|
placeholder: '',
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'el-input-number',
|
||||||
|
label: '上边距',
|
||||||
|
name: 'top',
|
||||||
|
required: false,
|
||||||
|
placeholder: '',
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'el-input-number',
|
||||||
|
label: '宽度',
|
||||||
|
name: 'width',
|
||||||
|
required: false,
|
||||||
|
placeholder: '该容器在1920px大屏中的宽度',
|
||||||
|
value: 400,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'el-input-number',
|
||||||
|
label: '高度',
|
||||||
|
name: 'height',
|
||||||
|
required: false,
|
||||||
|
placeholder: '该容器在1080px大屏中的高度',
|
||||||
|
value: 300,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -13,6 +13,7 @@ import {widgetHref} from "./configure/texts/widget-href"
|
|||||||
import {widgetTime} from "./configure/texts/widget-time"
|
import {widgetTime} from "./configure/texts/widget-time"
|
||||||
import {widgetImage} from "./configure/texts/widget-image"
|
import {widgetImage} from "./configure/texts/widget-image"
|
||||||
import {widgetButton} from "./configure/form/widget-button"
|
import {widgetButton} from "./configure/form/widget-button"
|
||||||
|
import {widgetTabs} from "./configure/form/widget-tabs"
|
||||||
import {widgetSliders} from "./configure/texts/widget-slider"
|
import {widgetSliders} from "./configure/texts/widget-slider"
|
||||||
import {widgetVideo} from "./configure/texts/widget-video"
|
import {widgetVideo} from "./configure/texts/widget-video"
|
||||||
import {widgetVideoMonitor} from "./configure/texts/widget-videoMonitor"
|
import {widgetVideoMonitor} from "./configure/texts/widget-videoMonitor"
|
||||||
@@ -68,6 +69,7 @@ export const widgetTool = [
|
|||||||
widgetTime,
|
widgetTime,
|
||||||
widgetImage,
|
widgetImage,
|
||||||
widgetButton,
|
widgetButton,
|
||||||
|
widgetTabs,
|
||||||
// widgetSliders,
|
// widgetSliders,
|
||||||
widgetVideo,
|
widgetVideo,
|
||||||
widgetVideoMonitor,
|
widgetVideoMonitor,
|
||||||
|
|||||||
638
src/views/bigscreenDesigner/designer/widget/form/widgetTabs.vue
Normal file
638
src/views/bigscreenDesigner/designer/widget/form/widgetTabs.vue
Normal file
@@ -0,0 +1,638 @@
|
|||||||
|
<!--
|
||||||
|
* @Author: Devli
|
||||||
|
* @Date: 2024-01-01 00:00:00
|
||||||
|
* @Last Modified by: Devli
|
||||||
|
* @Last Modified time: 2024-01-01 00:00:00
|
||||||
|
!-->
|
||||||
|
<template>
|
||||||
|
<div class="widget-tabs">
|
||||||
|
<el-tabs
|
||||||
|
:style="styleObj"
|
||||||
|
:tab-position="tabPosition"
|
||||||
|
:type="tabsType"
|
||||||
|
:closable="closable"
|
||||||
|
:addable="addable"
|
||||||
|
:stretch="stretch"
|
||||||
|
v-model="activeTab"
|
||||||
|
@tab-click="handleTabClick"
|
||||||
|
@tab-remove="handleTabRemove"
|
||||||
|
@tab-add="handleTabAdd"
|
||||||
|
>
|
||||||
|
<el-tab-pane
|
||||||
|
v-for="(tab, index) in tabsList"
|
||||||
|
:key="tab.name || index"
|
||||||
|
:label="tab.label"
|
||||||
|
:name="tab.name || `tab${index}`"
|
||||||
|
:closable="closable"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="tab-content"
|
||||||
|
data-tab-content
|
||||||
|
:style="contentStyle"
|
||||||
|
@drop="handleTabDrop($event, tab, index)"
|
||||||
|
@dragover.capture.prevent="handleTabDragOver($event)"
|
||||||
|
@dragover.prevent="handleTabDragOver($event)"
|
||||||
|
@dragenter.capture.prevent="handleTabDragEnter($event)"
|
||||||
|
@dragenter.prevent="handleTabDragEnter($event)"
|
||||||
|
>
|
||||||
|
<!-- 渲染子组件 -->
|
||||||
|
<template v-if="tab.children && tab.children.length > 0">
|
||||||
|
<component
|
||||||
|
v-for="(childWidget, childIndex) in tab.children"
|
||||||
|
:key="childWidget.value.widgetId || `child-${index}-${childIndex}`"
|
||||||
|
:is="getComponentName(childWidget.type)"
|
||||||
|
:value="childWidget.value"
|
||||||
|
:widget-index="childIndex"
|
||||||
|
:ispreview="ispreview"
|
||||||
|
:style="{
|
||||||
|
position: 'absolute',
|
||||||
|
left: (childWidget.value.position && childWidget.value.position.left) ? childWidget.value.position.left + 'px' : '0px',
|
||||||
|
top: (childWidget.value.position && childWidget.value.position.top) ? childWidget.value.position.top + 'px' : '0px',
|
||||||
|
width: (childWidget.value.position && childWidget.value.position.width) ? childWidget.value.position.width + 'px' : '100px',
|
||||||
|
height: (childWidget.value.position && childWidget.value.position.height) ? childWidget.value.position.height + 'px' : '50px',
|
||||||
|
zIndex: 10,
|
||||||
|
}"
|
||||||
|
@contextmenu.prevent.native="handleChildWidgetRightClick($event, childIndex, index)"
|
||||||
|
@mousedown.prevent.native="handleChildWidgetClick($event, childIndex, index)"
|
||||||
|
@mouseup.prevent.native="grade = false"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<div v-else class="tab-content-empty">
|
||||||
|
拖拽组件到这里
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
originWidgetLinkageLogic,
|
||||||
|
targetWidgetLinkageLogic,
|
||||||
|
} from "@/views/bigscreenDesigner/designer/linkageLogic";
|
||||||
|
import { getToolByCode } from "@/views/bigscreenDesigner/designer/tools/index";
|
||||||
|
// 与主画布 widget.vue 保持一致:注册所有可拖入的组件,否则图表、下拉等会报 Unknown custom element
|
||||||
|
import widgetHref from "../texts/widgetHref.vue";
|
||||||
|
import widgetText from "../texts/widgetText.vue";
|
||||||
|
import widgetButton from "./widgetButton.vue";
|
||||||
|
import widgetImage from "../texts/widgetImage.vue";
|
||||||
|
import widgetTable from "../texts/widgetTable.vue";
|
||||||
|
import WidgetMarquee from "../texts/widgetMarquee.vue";
|
||||||
|
import widgetTime from "../texts/widgetTime.vue";
|
||||||
|
import widgetSlider from "../texts/widgetSlider.vue";
|
||||||
|
import widgetVideo from "../texts/widgetVideo.vue";
|
||||||
|
import widgetVideoMonitor from "../texts/widgetVideoMonitor.vue";
|
||||||
|
import WidgetIframe from "../texts/widgetIframe.vue";
|
||||||
|
import widgetCalendar from "../texts/widgetCalendar.vue";
|
||||||
|
import widgetBarchart from "../bar/widgetBarchart.vue";
|
||||||
|
import widgetScatter from "../scatter/widgetScatter.vue";
|
||||||
|
import widgetGradientColorBarchart from "../bar/widgetGradientColorBarchart.vue";
|
||||||
|
import widgetLinechart from "../line/widgetLinechart.vue";
|
||||||
|
import widgetBarlinechart from "../barline/widgetBarlinechart";
|
||||||
|
import WidgetPiechart from "../pie/widgetPiechart.vue";
|
||||||
|
import WidgetFunnel from "../funnel/widgetFunnel.vue";
|
||||||
|
import WidgetGauge from "../percent/widgetGauge.vue";
|
||||||
|
import WidgetPieNightingaleRoseArea from "../pie/widgetPieNightingaleRose";
|
||||||
|
import widgetLineMap from "../map/widgetLineMap.vue";
|
||||||
|
import widgetPiePercentageChart from "../percent/widgetPiePercentageChart";
|
||||||
|
import widgetAirBubbleMap from "../map/widgetAirBubbleMap.vue";
|
||||||
|
import widgetBarStackChart from "../bar/widgetBarStackChart.vue";
|
||||||
|
import widgetLineStackChart from "../line/widgetLineStackChart.vue";
|
||||||
|
import widgetBarCompareChart from "../bar/widgetBarCompareChart.vue";
|
||||||
|
import widgetLineCompareChart from "../line/widgetLineCompareChart.vue";
|
||||||
|
import widgetDecoratePieChart from "../styleWidget/widgetDecoratePieChart.vue";
|
||||||
|
import widgetMoreBarLineChart from "../barline/widgetMoreBarLineChart";
|
||||||
|
import widgetWordCloud from "../wordcloud/widgetWordCloud.vue";
|
||||||
|
import widgetHeatmap from "../heatmap/widgetHeatmap.vue";
|
||||||
|
import widgetRadar from "../radar/widgetRadar.vue";
|
||||||
|
import widgetBarLineStackChart from "../barline/widgetBarLineStackChart";
|
||||||
|
import widgetSelect from "./widgetSelect.vue";
|
||||||
|
import widgetFormTime from "./widgetFormTime.vue";
|
||||||
|
import widgetScaleVertical from "../scale/widgetScaleVertical.vue";
|
||||||
|
import widgetScaleHorizontal from "../scale/widgetScaleHorizontal.vue";
|
||||||
|
import widgetBarDoubleYaxisChart from "../bar/widgetBarDoubleYaxisChart.vue";
|
||||||
|
import widgetBorder from "../styleWidget/widgetBorder.vue";
|
||||||
|
import widgetDecorateFlowLine from "../styleWidget/widgetDecorateFlowLine.vue";
|
||||||
|
import widgetDecoration from "../styleWidget/widgetDecoration.vue";
|
||||||
|
import widgetBarMap from "../map/widgetBarMap.vue";
|
||||||
|
import widgetChinaMap from "../map/widgetChinaMap.vue";
|
||||||
|
import widgetGlobalMap from "../map/widgetGlobalMap.vue";
|
||||||
|
import widgetBarStackMoreShowChart from "../bar/widgetBarStackMoreShowChart.vue";
|
||||||
|
import widgetBarLineSingleChart from "../barline/widgetBarLineSingleChart.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "WidgetTabs",
|
||||||
|
components: {
|
||||||
|
widgetHref,
|
||||||
|
widgetText,
|
||||||
|
widgetButton,
|
||||||
|
widgetImage,
|
||||||
|
widgetTable,
|
||||||
|
WidgetMarquee,
|
||||||
|
widgetTime,
|
||||||
|
widgetSlider,
|
||||||
|
widgetVideo,
|
||||||
|
widgetVideoMonitor,
|
||||||
|
WidgetIframe,
|
||||||
|
widgetCalendar,
|
||||||
|
widgetBarchart,
|
||||||
|
widgetGradientColorBarchart,
|
||||||
|
widgetLinechart,
|
||||||
|
widgetBarlinechart,
|
||||||
|
WidgetPiechart,
|
||||||
|
WidgetFunnel,
|
||||||
|
WidgetGauge,
|
||||||
|
WidgetPieNightingaleRoseArea,
|
||||||
|
widgetLineMap,
|
||||||
|
widgetPiePercentageChart,
|
||||||
|
widgetAirBubbleMap,
|
||||||
|
widgetBarStackChart,
|
||||||
|
widgetLineStackChart,
|
||||||
|
widgetBarCompareChart,
|
||||||
|
widgetLineCompareChart,
|
||||||
|
widgetDecoratePieChart,
|
||||||
|
widgetMoreBarLineChart,
|
||||||
|
widgetWordCloud,
|
||||||
|
widgetHeatmap,
|
||||||
|
widgetRadar,
|
||||||
|
widgetBarLineStackChart,
|
||||||
|
widgetScaleVertical,
|
||||||
|
widgetScaleHorizontal,
|
||||||
|
widgetSelect,
|
||||||
|
widgetFormTime,
|
||||||
|
widgetBarDoubleYaxisChart,
|
||||||
|
widgetBorder,
|
||||||
|
widgetDecorateFlowLine,
|
||||||
|
widgetDecoration,
|
||||||
|
widgetBarMap,
|
||||||
|
widgetChinaMap,
|
||||||
|
widgetGlobalMap,
|
||||||
|
widgetScatter,
|
||||||
|
widgetBarStackMoreShowChart,
|
||||||
|
widgetBarLineSingleChart,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
value: Object,
|
||||||
|
ispreview: Boolean,
|
||||||
|
widgetIndex: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
options: {},
|
||||||
|
optionsSetup: {},
|
||||||
|
optionsData: {},
|
||||||
|
optionsStyle: {},
|
||||||
|
activeTab: '',
|
||||||
|
dynamicTabsList: [],
|
||||||
|
flagInter: null,
|
||||||
|
grade: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
transStyle() {
|
||||||
|
return this.objToOne(this.options);
|
||||||
|
},
|
||||||
|
styleObj() {
|
||||||
|
return {
|
||||||
|
position: this.ispreview ? "relative" : "static",
|
||||||
|
width: this.optionsStyle.width + "px",
|
||||||
|
height: this.optionsStyle.height + "px",
|
||||||
|
left: this.optionsStyle.left + "px",
|
||||||
|
top: this.optionsStyle.top + "px",
|
||||||
|
display:
|
||||||
|
this.transStyle.hideLayer === undefined
|
||||||
|
? "block"
|
||||||
|
: this.transStyle.hideLayer ? "none" : "block",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
contentStyle() {
|
||||||
|
return {
|
||||||
|
color: this.transStyle.labelColor || '#303133',
|
||||||
|
'font-size': (this.transStyle.fontSize || 14) + 'px',
|
||||||
|
padding: '20px',
|
||||||
|
height: '100%',
|
||||||
|
'box-sizing': 'border-box',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
tabPosition() {
|
||||||
|
return this.transStyle.tabPosition || 'top';
|
||||||
|
},
|
||||||
|
tabsType() {
|
||||||
|
return this.transStyle.type || '';
|
||||||
|
},
|
||||||
|
closable() {
|
||||||
|
return this.transStyle.closable || false;
|
||||||
|
},
|
||||||
|
addable() {
|
||||||
|
return this.transStyle.addable || false;
|
||||||
|
},
|
||||||
|
stretch() {
|
||||||
|
return this.transStyle.stretch || false;
|
||||||
|
},
|
||||||
|
tabsList() {
|
||||||
|
if (this.optionsData && this.optionsData.dataType === 'dynamicData' && this.optionsData.dynamicData && this.dynamicTabsList.length > 0) {
|
||||||
|
// 动态数据
|
||||||
|
return this.dynamicTabsList;
|
||||||
|
} else {
|
||||||
|
// 静态数据 - 优先使用setup中的tabsList,确保children数据不丢失
|
||||||
|
const tabsList = this.optionsSetup.tabsList || this.transStyle.tabsList || [];
|
||||||
|
// 确保每个tab都有children数组
|
||||||
|
tabsList.forEach(tab => {
|
||||||
|
if (!tab.children) {
|
||||||
|
this.$set(tab, 'children', []);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return tabsList;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
allComponentLinkage() {
|
||||||
|
return this.$store.state.designer.allComponentLinkage;
|
||||||
|
},
|
||||||
|
tabBigscreen() {
|
||||||
|
// 子组件的bigscreen应该是相对于tab容器的
|
||||||
|
return {
|
||||||
|
bigscreenWidth: this.optionsStyle.width,
|
||||||
|
bigscreenHeight: this.optionsStyle.height,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value: {
|
||||||
|
handler(val) {
|
||||||
|
this.options = val;
|
||||||
|
this.optionsSetup = val.setup || {};
|
||||||
|
this.optionsData = val.data || {};
|
||||||
|
this.optionsStyle = val.position || {};
|
||||||
|
this.setActiveTab();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.options = this.value;
|
||||||
|
this.optionsSetup = this.value.setup;
|
||||||
|
this.optionsData = this.value.data;
|
||||||
|
this.optionsStyle = this.value.position;
|
||||||
|
this.setActiveTab();
|
||||||
|
targetWidgetLinkageLogic(this); // 联动-目标组件逻辑
|
||||||
|
|
||||||
|
// 设置动态数据
|
||||||
|
if (this.optionsData && this.optionsData.dataType === 'dynamicData') {
|
||||||
|
this.dynamicDataFn(this.optionsData.dynamicData, this.optionsData.refreshTime);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setActiveTab() {
|
||||||
|
const tabs = this.tabsList;
|
||||||
|
if (tabs && tabs.length > 0) {
|
||||||
|
this.activeTab = tabs[0].name || 'tab0';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleTabClick(tab) {
|
||||||
|
const currentTab = this.tabsList.find(t => (t.name || `tab${this.tabsList.indexOf(t)}`) === tab.name);
|
||||||
|
if (currentTab) {
|
||||||
|
originWidgetLinkageLogic(this, true, {
|
||||||
|
currentData: currentTab,
|
||||||
|
}); // 联动-源组件逻辑
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleTabRemove(name) {
|
||||||
|
// 处理标签页移除
|
||||||
|
console.log('移除标签页:', name);
|
||||||
|
},
|
||||||
|
handleTabAdd() {
|
||||||
|
// 处理标签页添加
|
||||||
|
console.log('添加标签页');
|
||||||
|
},
|
||||||
|
//动态数据字典解析
|
||||||
|
dynamicDataFn(val, refreshTime) {
|
||||||
|
if (!val) return;
|
||||||
|
clearInterval(this.flagInter); // 清除之前的定时器
|
||||||
|
if (this.ispreview) {
|
||||||
|
this.getEchartData(val);
|
||||||
|
this.flagInter = setInterval(() => {
|
||||||
|
this.getEchartData(val);
|
||||||
|
}, refreshTime);
|
||||||
|
} else {
|
||||||
|
this.getEchartData(val);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getEchartData(val) {
|
||||||
|
const data = this.queryEchartsData(val);
|
||||||
|
data.then((res) => {
|
||||||
|
this.renderingFn(res);
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error('获取tabs数据失败:', err);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
renderingFn(val) {
|
||||||
|
// 将动态数据转换为tabs格式
|
||||||
|
this.dynamicTabsList = Array.isArray(val) ? val.map((item, index) => ({
|
||||||
|
label: item.label || item.name || `标签${index + 1}`,
|
||||||
|
name: item.name || item.value || `tab${index}`,
|
||||||
|
content: item.content || item.label || item.name || '',
|
||||||
|
children: item.children || [],
|
||||||
|
})) : [];
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.setActiveTab();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 处理tab内容区域的拖拽
|
||||||
|
handleTabDragOver(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
evt.stopPropagation();
|
||||||
|
evt.stopImmediatePropagation();
|
||||||
|
evt.dataTransfer.dropEffect = "copy";
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
handleTabDragEnter(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
evt.stopPropagation();
|
||||||
|
evt.stopImmediatePropagation();
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
handleTabDrop(evt, tab, tabIndex) {
|
||||||
|
evt.preventDefault();
|
||||||
|
evt.stopPropagation();
|
||||||
|
evt.stopImmediatePropagation();
|
||||||
|
|
||||||
|
console.log('Tabs drop事件触发', tab, tabIndex);
|
||||||
|
|
||||||
|
// 优先从 dataTransfer 读取(index 在 dragStart 时已写入),否则再沿父组件查找
|
||||||
|
let dragWidgetCode = null;
|
||||||
|
if (evt.dataTransfer && evt.dataTransfer.types && evt.dataTransfer.types.indexOf('application/x-widget-code') !== -1) {
|
||||||
|
dragWidgetCode = evt.dataTransfer.getData('application/x-widget-code');
|
||||||
|
}
|
||||||
|
if (!dragWidgetCode) {
|
||||||
|
dragWidgetCode = this.getDragWidgetCode();
|
||||||
|
}
|
||||||
|
console.log('拖拽的组件类型:', dragWidgetCode);
|
||||||
|
|
||||||
|
if (!dragWidgetCode) {
|
||||||
|
console.warn('未获取到拖拽组件类型');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取相对于tab内容区域的坐标
|
||||||
|
const tabContentEl = evt.currentTarget;
|
||||||
|
const tabContentRect = tabContentEl.getBoundingClientRect();
|
||||||
|
const x = evt.clientX - tabContentRect.left;
|
||||||
|
const y = evt.clientY - tabContentRect.top;
|
||||||
|
|
||||||
|
// 创建新组件
|
||||||
|
const tool = getToolByCode(dragWidgetCode);
|
||||||
|
if (!tool) {
|
||||||
|
console.warn('未找到组件配置:', dragWidgetCode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const widgetJson = {
|
||||||
|
type: dragWidgetCode,
|
||||||
|
value: {
|
||||||
|
setup: {},
|
||||||
|
data: {},
|
||||||
|
position: {
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
zIndex: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: this.deepClone(tool.options),
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理默认值
|
||||||
|
const widgetJsonValue = this.getWidgetConfigValue(widgetJson);
|
||||||
|
|
||||||
|
// 设置位置(相对于tab内容区域)
|
||||||
|
widgetJsonValue.value.position.left = x - widgetJsonValue.value.position.width / 2;
|
||||||
|
widgetJsonValue.value.position.top = y - widgetJsonValue.value.position.height / 2;
|
||||||
|
|
||||||
|
// 确保位置在容器内
|
||||||
|
if (widgetJsonValue.value.position.left < 0) {
|
||||||
|
widgetJsonValue.value.position.left = 0;
|
||||||
|
}
|
||||||
|
if (widgetJsonValue.value.position.top < 0) {
|
||||||
|
widgetJsonValue.value.position.top = 0;
|
||||||
|
}
|
||||||
|
if (widgetJsonValue.value.position.left + widgetJsonValue.value.position.width > this.optionsStyle.width) {
|
||||||
|
widgetJsonValue.value.position.left = this.optionsStyle.width - widgetJsonValue.value.position.width;
|
||||||
|
}
|
||||||
|
if (widgetJsonValue.value.position.top + widgetJsonValue.value.position.height > this.optionsStyle.height) {
|
||||||
|
widgetJsonValue.value.position.top = this.optionsStyle.height - widgetJsonValue.value.position.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成唯一ID
|
||||||
|
const uuid = Number(Math.random().toString().substr(2)).toString(36);
|
||||||
|
widgetJsonValue.value.widgetId = uuid;
|
||||||
|
widgetJsonValue.value.widgetCode = dragWidgetCode;
|
||||||
|
|
||||||
|
// 获取当前的tabsList(确保使用正确的数据源)
|
||||||
|
let currentTabsList = this.tabsList;
|
||||||
|
|
||||||
|
// 找到对应的tab并添加子组件
|
||||||
|
const targetTab = currentTabsList[tabIndex];
|
||||||
|
if (!targetTab) {
|
||||||
|
console.warn('未找到目标tab');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!targetTab.children) {
|
||||||
|
this.$set(targetTab, 'children', []);
|
||||||
|
}
|
||||||
|
targetTab.children.push(this.deepClone(widgetJsonValue));
|
||||||
|
|
||||||
|
// 更新到setup中
|
||||||
|
this.optionsSetup.tabsList = currentTabsList;
|
||||||
|
|
||||||
|
// 更新value.setup
|
||||||
|
if (!this.value.setup) {
|
||||||
|
this.$set(this.value, 'setup', {});
|
||||||
|
}
|
||||||
|
this.value.setup.tabsList = currentTabsList;
|
||||||
|
|
||||||
|
// 触发更新
|
||||||
|
this.$emit('input', this.value);
|
||||||
|
|
||||||
|
console.log('子组件已添加:', targetTab.children);
|
||||||
|
|
||||||
|
this.grade = false;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 当 drop 落在 Tabs 外层容器(如 avue-draggable)时由设计器调用,将组件加入当前激活的 tab
|
||||||
|
*/
|
||||||
|
addWidgetFromDrop(evt) {
|
||||||
|
if (!evt || !evt.dataTransfer) return;
|
||||||
|
let dragWidgetCode = null;
|
||||||
|
if (evt.dataTransfer.types && evt.dataTransfer.types.indexOf("application/x-widget-code") !== -1) {
|
||||||
|
dragWidgetCode = evt.dataTransfer.getData("application/x-widget-code");
|
||||||
|
}
|
||||||
|
if (!dragWidgetCode) dragWidgetCode = this.getDragWidgetCode();
|
||||||
|
if (!dragWidgetCode) return;
|
||||||
|
|
||||||
|
const tabIndex = this.tabsList.findIndex(
|
||||||
|
(t, i) => (t.name || `tab${i}`) === this.activeTab
|
||||||
|
);
|
||||||
|
const tab = tabIndex >= 0 ? this.tabsList[tabIndex] : this.tabsList[0];
|
||||||
|
const actualIndex = tabIndex >= 0 ? tabIndex : 0;
|
||||||
|
|
||||||
|
const contentEls = this.$el.querySelectorAll("[data-tab-content]");
|
||||||
|
const contentEl = contentEls[actualIndex] || contentEls[0];
|
||||||
|
const rect = contentEl ? contentEl.getBoundingClientRect() : this.$el.getBoundingClientRect();
|
||||||
|
const x = evt.clientX - rect.left;
|
||||||
|
const y = evt.clientY - rect.top;
|
||||||
|
|
||||||
|
const tool = getToolByCode(dragWidgetCode);
|
||||||
|
if (!tool) return;
|
||||||
|
|
||||||
|
const widgetJson = {
|
||||||
|
type: dragWidgetCode,
|
||||||
|
value: {
|
||||||
|
setup: {},
|
||||||
|
data: {},
|
||||||
|
position: { width: 0, height: 0, left: 0, top: 0, zIndex: 0 },
|
||||||
|
},
|
||||||
|
options: this.deepClone(tool.options),
|
||||||
|
};
|
||||||
|
const widgetJsonValue = this.getWidgetConfigValue(widgetJson);
|
||||||
|
widgetJsonValue.value.position.left = Math.max(0, x - widgetJsonValue.value.position.width / 2);
|
||||||
|
widgetJsonValue.value.position.top = Math.max(0, y - widgetJsonValue.value.position.height / 2);
|
||||||
|
if (widgetJsonValue.value.position.left + widgetJsonValue.value.position.width > this.optionsStyle.width) {
|
||||||
|
widgetJsonValue.value.position.left = this.optionsStyle.width - widgetJsonValue.value.position.width;
|
||||||
|
}
|
||||||
|
if (widgetJsonValue.value.position.top + widgetJsonValue.value.position.height > this.optionsStyle.height) {
|
||||||
|
widgetJsonValue.value.position.top = this.optionsStyle.height - widgetJsonValue.value.position.height;
|
||||||
|
}
|
||||||
|
const uuid = Number(Math.random().toString().substr(2)).toString(36);
|
||||||
|
widgetJsonValue.value.widgetId = uuid;
|
||||||
|
widgetJsonValue.value.widgetCode = dragWidgetCode;
|
||||||
|
|
||||||
|
const currentTabsList = this.tabsList;
|
||||||
|
const targetTab = currentTabsList[actualIndex];
|
||||||
|
if (!targetTab) return;
|
||||||
|
if (!targetTab.children) this.$set(targetTab, "children", []);
|
||||||
|
targetTab.children.push(this.deepClone(widgetJsonValue));
|
||||||
|
|
||||||
|
this.optionsSetup.tabsList = currentTabsList;
|
||||||
|
if (!this.value.setup) this.$set(this.value, "setup", {});
|
||||||
|
this.value.setup.tabsList = currentTabsList;
|
||||||
|
this.$emit("input", this.value);
|
||||||
|
},
|
||||||
|
getDragWidgetCode() {
|
||||||
|
// 尝试从父组件获取dragWidgetCode
|
||||||
|
let parent = this.$parent;
|
||||||
|
while (parent) {
|
||||||
|
if (parent.dragWidgetCode !== undefined) {
|
||||||
|
return parent.dragWidgetCode;
|
||||||
|
}
|
||||||
|
parent = parent.$parent;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
getWidgetConfigValue(widgetJson) {
|
||||||
|
this.setWidgetConfigValue(
|
||||||
|
widgetJson.options.setup,
|
||||||
|
widgetJson.value.setup
|
||||||
|
);
|
||||||
|
this.setWidgetConfigValue(
|
||||||
|
widgetJson.options.position,
|
||||||
|
widgetJson.value.position
|
||||||
|
);
|
||||||
|
this.setWidgetConfigValue(widgetJson.options.data, widgetJson.value.data);
|
||||||
|
return widgetJson;
|
||||||
|
},
|
||||||
|
setWidgetConfigValue(config, configValue) {
|
||||||
|
if (config) {
|
||||||
|
config.forEach((item) => {
|
||||||
|
if (this.isObjectFn(item)) {
|
||||||
|
configValue[item.name] = item.value;
|
||||||
|
}
|
||||||
|
if (this.isArrayFn(item)) {
|
||||||
|
item.forEach((itemChild) => {
|
||||||
|
itemChild.list.forEach((ev) => {
|
||||||
|
configValue[ev.name] = ev.value;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleChildWidgetActivated(data) {
|
||||||
|
// 处理子组件激活事件
|
||||||
|
console.log('子组件激活:', data);
|
||||||
|
},
|
||||||
|
handleChildWidgetClick(event, childIndex, tabIndex) {
|
||||||
|
// 处理子组件点击
|
||||||
|
event.stopPropagation();
|
||||||
|
this.grade = true;
|
||||||
|
},
|
||||||
|
handleChildWidgetRightClick(event, childIndex, tabIndex) {
|
||||||
|
// 处理子组件右键
|
||||||
|
event.stopPropagation();
|
||||||
|
},
|
||||||
|
// 将组件类型字符串转换为组件名称
|
||||||
|
getComponentName(type) {
|
||||||
|
// 将 widget-text 转换为 widgetText
|
||||||
|
if (!type) return '';
|
||||||
|
return type.split('-').map((part, index) => {
|
||||||
|
if (index === 0) return part;
|
||||||
|
return part.charAt(0).toUpperCase() + part.slice(1);
|
||||||
|
}).join('');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.widget-tabs {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
::v-deep .el-tabs {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.el-tabs__header {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-tabs__content {
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-tab-pane {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
position: relative;
|
||||||
|
min-height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content-empty {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #999;
|
||||||
|
font-size: 14px;
|
||||||
|
border: 2px dashed #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
import widgetHref from "./texts/widgetHref.vue";
|
import widgetHref from "./texts/widgetHref.vue";
|
||||||
import widgetText from "./texts/widgetText.vue";
|
import widgetText from "./texts/widgetText.vue";
|
||||||
import widgetButton from './form/widgetButton.vue';
|
import widgetButton from './form/widgetButton.vue';
|
||||||
|
import widgetTabs from './form/widgetTabs.vue';
|
||||||
import WidgetMarquee from "./texts/widgetMarquee.vue";
|
import WidgetMarquee from "./texts/widgetMarquee.vue";
|
||||||
import widgetTime from "./texts/widgetTime.vue";
|
import widgetTime from "./texts/widgetTime.vue";
|
||||||
import widgetImage from "./texts/widgetImage.vue";
|
import widgetImage from "./texts/widgetImage.vue";
|
||||||
@@ -72,6 +73,7 @@ export default {
|
|||||||
widgetHref,
|
widgetHref,
|
||||||
widgetText,
|
widgetText,
|
||||||
widgetButton,
|
widgetButton,
|
||||||
|
widgetTabs,
|
||||||
widgetBorder,
|
widgetBorder,
|
||||||
widgetDecorateFlowLine,
|
widgetDecorateFlowLine,
|
||||||
widgetDecoration,
|
widgetDecoration,
|
||||||
|
|||||||
Reference in New Issue
Block a user