Merge pull request #10325 from dataease/pr@dev-v2@perf_plugin_view

Pr@dev v2@perf plugin view
This commit is contained in:
fit2cloud-chenyw 2024-06-17 17:45:56 +08:00 committed by GitHub
commit bfb8032398
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 341 additions and 22 deletions

View File

@ -2,6 +2,9 @@ import request from '@/config/axios'
export const load = (key: string) => request.get({ url: `/xpackComponent/content/${key}` })
export const loadPluginApi = (key: string) =>
request.get({ url: `/xpackComponent/contentPlugin/${key}` })
export const loadDistributed = () => request.get({ url: '/DEXPack.umd.js' })
export const xpackModelApi = () => request.get({ url: '/xpackModel' })

View File

@ -43,6 +43,7 @@ import DragInfo from '@/components/visualization/common/DragInfo.vue'
import { activeWatermark } from '@/components/watermark/watermark'
import { personInfoApi } from '@/api/user'
import ComponentHangPopver from '@/custom-component/independent-hang/ComponentHangPopver.vue'
const snapshotStore = snapshotStoreWithOut()
const dvMainStore = dvMainStoreWithOut()
const composeStore = composeStoreWithOut()
@ -1490,9 +1491,27 @@ defineExpose({
@linkageSetOpen="linkageSetOpen(item)"
>
<!--如果是图表 则动态获取预存的chart-view数据-->
<!-- <PluginComponent
v-if="item['isPlugin']"
:jsname="item['pluginFlag'] || 'L2NvbXBvbmVudC9pbmRleA=='"
class="component"
:id="'component' + item.id"
:active="item.id === curComponentId"
:dv-type="dvInfo.type"
:scale="curBaseScale"
:style="getComponentStyle(item.style)"
:prop-value="item.propValue"
:is-edit="true"
:view="canvasViewInfo[item.id]"
:element="item"
:request="item.request"
@input="handleInput"
:dv-info="dvInfo"
:canvas-active="canvasActive"
/> -->
<component
:is="findComponent(item.component)"
v-if="item.component === 'UserView'"
v-if="item.component === 'UserView' || item['isPlugin']"
class="component"
:id="'component' + item.id"
:active="item.id === curComponentId"

View File

@ -5,7 +5,8 @@ import { propTypes } from '@/utils/propTypes'
const props = defineProps({
prefix: propTypes.string.def('icon'),
name: propTypes.string,
className: propTypes.string
className: propTypes.string,
staticContent: propTypes.string
})
const symbolId = computed(() => `#${props.prefix}-${props.name}`)
const svgClass = computed(() => {
@ -17,14 +18,32 @@ const svgClass = computed(() => {
</script>
<template>
<svg :class="svgClass" aria-hidden="true">
<div
class="svg-container"
v-if="staticContent"
v-html="staticContent"
:class="svgClass"
aria-hidden="true"
></div>
<svg v-else :class="svgClass" aria-hidden="true">
<use class="svg-use" :href="symbolId" />
</svg>
</template>
<style scope>
<style lang="less" scope>
.svg-icon {
overflow: hidden;
vertical-align: -0.1em; /* 因icon大小被设置为和字体大小一致而span等标签的下边缘会和字体的基线对齐故需设置一个往下的偏移比例来纠正视觉上的未对齐效果 */
fill: currentcolor; /* 定义元素的颜色currentColor是一个变量这个变量的值就表示当前元素的color值如果当前元素未设置color值则从父元素继承 */
}
.svg-container {
width: 100%;
height: 100%;
svg {
overflow: hidden;
vertical-align: -0.1em;
fill: currentcolor;
width: 100%;
height: 100%;
}
}
</style>

View File

@ -1,3 +1,4 @@
import XpackComponent from './src/index.vue'
import PluginComponent from './src/PluginComponent.vue'
export { XpackComponent }
export { XpackComponent, PluginComponent }

View File

@ -0,0 +1,139 @@
<script lang="ts" setup>
import noLic from './nolic.vue'
import { ref, useAttrs, onMounted } from 'vue'
import { execute, randomKey, formatArray } from './convert'
import { loadPluginApi, loadDistributed, xpackModelApi } from '@/api/plugin'
import { useCache } from '@/hooks/web/useCache'
import { i18n } from '@/plugins/vue-i18n'
import * as Vue from 'vue'
import axios from 'axios'
import * as Pinia from 'pinia'
import * as vueRouter from 'vue-router'
import { useEmitt } from '@/hooks/web/useEmitt'
const { wsCache } = useCache()
const plugin = ref()
const loading = ref(false)
const attrs = useAttrs()
const showNolic = () => {
plugin.value = noLic
loading.value = false
}
const generateRamStr = (len: number) => {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
let randomStr = ''
for (var i = 0; i < len; i++) {
randomStr += chars.charAt(Math.floor(Math.random() * chars.length))
}
return randomStr
}
const importProxy = (bytesArray: any[]) => {
const promise = import(
`../../../../../../../${formatArray(bytesArray[7])}/${formatArray(bytesArray[8])}/${formatArray(
bytesArray[9]
)}/${formatArray(bytesArray[10])}/${formatArray(bytesArray[11])}.vue`
)
promise
.then((res: any) => {
plugin.value = res.default
})
.catch(e => {
console.error(e)
showNolic()
})
}
const loadComponent = () => {
loading.value = true
const byteArray = wsCache.get(`de-plugin-proxy-plugin`)
if (byteArray) {
importProxy(JSON.parse(byteArray))
loading.value = false
return
}
const key = generateRamStr(randomKey())
loadPluginApi(key)
.then(response => {
let code = response.data
const byteArray = execute(code, key)
storeCacheProxy(byteArray)
importProxy(byteArray)
})
.catch(() => {
emits('loadFail')
showNolic()
})
.finally(() => {
loading.value = false
})
}
const storeCacheProxy = byteArray => {
const result = []
byteArray.forEach(item => {
result.push([...item])
})
wsCache.set(`de-plugin-proxy-plugin`, JSON.stringify(result))
}
const pluginProxy = ref(null)
const invokeMethod = param => {
if (pluginProxy.value['invokeMethod']) {
pluginProxy.value['invokeMethod'](param)
} else {
pluginProxy.value[param.methodName](param.args)
}
}
onMounted(async () => {
const key = 'xpack-model-distributed'
let distributed = false
if (wsCache.get(key) === null) {
const res = await xpackModelApi()
wsCache.set('xpack-model-distributed', res.data)
distributed = res.data
} else {
distributed = wsCache.get(key)
}
if (distributed) {
if (window['DEXPack']) {
const xpack = await window['DEXPack'].mapping[attrs.jsname]
plugin.value = xpack.default
} else {
window['Vue'] = Vue
window['Axios'] = axios
window['Pinia'] = Pinia
window['vueRouter'] = vueRouter
window['MittAll'] = useEmitt().emitter.all
window['I18n'] = i18n
loadDistributed().then(async res => {
new Function(res.data)()
const xpack = await window['DEXPack'].mapping[attrs.jsname]
plugin.value = xpack.default
})
}
} else {
loadComponent()
}
})
const emits = defineEmits(['loadFail'])
defineExpose({
invokeMethod
})
</script>
<template>
<component
:key="attrs.jsname"
ref="pluginProxy"
:is="plugin"
v-loading="loading"
v-bind="attrs"
></component>
</template>
<style lang="less" scoped></style>

View File

@ -5,6 +5,7 @@ import { CHART_TYPE_CONFIGS } from '@/views/chart/components/editor/util/chart'
import Icon from '@/components/icon-custom/src/Icon.vue'
import { commonHandleDragEnd, commonHandleDragStart } from '@/utils/canvasUtils'
import { ElScrollbar } from 'element-plus-secondary'
import { XpackComponent } from '@/components/plugin'
const props = defineProps({
propValue: {
@ -47,8 +48,8 @@ const anchorPosition = anchor => {
scrollTo(element.offsetTop)
}
const newComponent = innerType => {
eventBus.emit('handleNew', { componentName: 'UserView', innerType: innerType })
const newComponent = (innerType, isPlugin) => {
eventBus.emit('handleNew', { componentName: 'UserView', innerType: innerType, isPlugin })
}
const handleDragStart = e => {
@ -63,6 +64,36 @@ const groupActiveChange = category => {
state.curCategory = category
anchorPosition('#' + category)
}
const loadPluginCategory = data => {
data.forEach(item => {
const { category, title, render, chartValue, chartTitle, icon } = item
const node = {
render,
category,
icon,
value: chartValue,
title: chartTitle,
isPlugin: true
}
const stack = [...state.chartGroupList]
let findParent = false
while (stack?.length) {
const parent = stack.pop()
if (parent.category === category) {
parent.details.push(node)
findParent = true
}
}
if (!findParent) {
state.chartGroupList.push({
category,
title,
display: 'show',
details: [node]
})
}
})
}
</script>
<template>
@ -97,12 +128,18 @@ const groupActiveChange = category => {
:key="chartInfo.title"
>
<div
v-on:click="newComponent(chartInfo.value)"
v-on:click="newComponent(chartInfo.value, chartInfo['isPlugin'])"
class="item-top"
draggable="true"
:data-id="'UserView&' + chartInfo.value"
>
<Icon
class-name="item-top-icon"
v-if="chartInfo['isPlugin']"
:static-content="chartInfo.icon"
/>
<Icon
v-else
class-name="item-top-icon"
:name="chartInfo.icon + (props.themes === 'dark' ? '-dark' : '')"
/>
@ -115,6 +152,10 @@ const groupActiveChange = category => {
</el-row>
</el-scrollbar>
</el-row>
<XpackComponent
jsname="L2NvbXBvbmVudC9wbHVnaW5zLWhhbmRsZXIvVmlld0NhdGVnb3J5SGFuZGxlcg=="
@load-plugin-category="loadPluginCategory"
/>
</template>
<style lang="less" scoped>

View File

@ -514,7 +514,12 @@ for (let i = 0, len = list.length; i < len; i++) {
list[i] = { ...commonAttr, ...item }
}
export function findNewComponentFromList(componentName, innerType, curOriginThemes) {
export function findNewComponentFromList(
componentName,
innerType,
curOriginThemes,
isPlugin?: boolean
) {
let newComponent
list.forEach(comp => {
if (comp.component === componentName) {
@ -534,6 +539,7 @@ export function findNewComponentFromList(componentName, innerType, curOriginThem
newComponent.name = viewConfig?.title
newComponent.label = viewConfig?.title
newComponent.render = viewConfig?.render
newComponent.isPlugin = !!isPlugin
}
return newComponent
}

View File

@ -0,0 +1,4 @@
declare interface ChartPlugin {
isPlugin: boolean
pluginResourceId?: string
}

View File

@ -56,15 +56,18 @@ declare interface Chart {
linkageActive: boolean
jumpActive: boolean
aggregate?: boolean
plugin?: CustomPlugin
}
declare type CustomAttr = DeepPartial<ChartAttr> | JSONString<DeepPartial<ChartAttr>>
declare type CustomStyle = DeepPartial<ChartStyle> | JSONString<DeepPartial<ChartStyle>>
declare type CustomSenior = DeepPartial<ChartSenior> | JSONString<DeepPartial<ChartSenior>>
declare type CustomPlugin = DeepPartial<ChartPlugin> | JSONString<DeepPartial<ChartPlugin>>
declare type ChartObj = Omit<Chart, 'customAttr' | 'customStyle' | 'senior'> & {
declare type ChartObj = Omit<Chart, 'customAttr' | 'customStyle' | 'senior' | 'plugin'> & {
customAttr: ChartAttr
customStyle: ChartStyle
senior: ChartSenior
plugin?: ChartPlugin
}
/**

View File

@ -3,7 +3,7 @@ import { cloneDeep } from 'lodash'
import { XpackComponent } from '@/components/plugin'
const modules = import.meta.glob('../views/**/*.vue')
export const Layout = () => import('@/layout/index.vue')
const pluginComponent = 'components/plugin'
const xpackComName = 'components/plugin'
// 后端控制路由生成
export const generateRoutesFn2 = (routes: AppCustomRouteRecordRaw[]): AppRouteRecordRaw[] => {
const res: AppRouteRecordRaw[] = []
@ -17,7 +17,7 @@ export const generateRoutesFn2 = (routes: AppCustomRouteRecordRaw[]): AppRouteRe
if (route.plugin) {
const jsName = route.component
route.component = pluginComponent
route.component = xpackComName
route.props = {
jsname: jsName,
inLayout: route.inLayout
@ -35,7 +35,7 @@ export const generateRoutesFn2 = (routes: AppCustomRouteRecordRaw[]): AppRouteRe
if (route.component) {
let comModule = null
if (route.component === pluginComponent) {
if (route.component === xpackComName) {
comModule = XpackComponent
} else {
comModule = modules[`../views/${route.component}/index.vue`]

View File

@ -384,7 +384,11 @@ export const dvMainStore = defineStore('dataVisualization', {
...defaultConfig,
id: component.id,
type: component.innerType,
render: component.render
render: component.render,
plugin: {
isPlugin: component.isPlugin,
pluginResourceId: component.pluginResourceId
}
} as unknown as ChartObj
// 处理配置项默认值不同图表的同一配置项默认值不同
const chartViewInstance = chartViewManager.getChartView(newView.render, newView.type)

View File

@ -44,7 +44,7 @@ export function findDragComponent(componentInfo) {
return findNewComponent(componentName, innerType)
}
export function findNewComponent(componentName, innerType) {
export function findNewComponent(componentName, innerType, isPlugin?: boolean) {
let newComponent
componentList.forEach(comp => {
if (comp.component === componentName || comp.component === innerType) {
@ -67,6 +67,7 @@ export function findNewComponent(componentName, innerType) {
newComponent.name = viewConfig?.title
newComponent.label = viewConfig?.title
newComponent.render = viewConfig?.render
newComponent.isPlugin = !!isPlugin
}
return newComponent
}

View File

@ -73,9 +73,10 @@ const editStyle = computed(() => {
//
const handleNewFromCanvasMain = newComponentInfo => {
const { componentName, innerType } = newComponentInfo
const { componentName, innerType, isPlugin } = newComponentInfo
if (componentName) {
const component = findNewComponentFromList(componentName, innerType, curOriginThemes)
const component = findNewComponentFromList(componentName, innerType, curOriginThemes, isPlugin)
component.isPlugin = !!isPlugin
syncShapeItemStyle(component, baseWidth.value, baseHeight.value)
component.id = guid()
component.y = 200

View File

@ -50,6 +50,7 @@ import chartViewManager from '@/views/chart/components/js/panel'
import DatasetSelect from '@/views/chart/components/editor/dataset-select/DatasetSelect.vue'
import { useDraggable } from '@vueuse/core'
import { set, concat, keys } from 'lodash-es'
import { PluginComponent } from '@/components/plugin'
import {
Field,
getFieldByDQ,
@ -259,6 +260,9 @@ const chartStyleShow = computed(() => {
})
const chartViewInstance = computed(() => {
if (view.value.render === 'highchart') {
return chartViewManager.getChartView('antv', view.value.type)
}
return chartViewManager.getChartView(view.value.render, view.value.type)
})
const showAxis = (axis: AxisType) => chartViewInstance.value?.axis?.includes(axis)
@ -1597,6 +1601,15 @@ const deleteChartFieldItem = id => {
/>
</div>
</div>
<plugin-component
v-else-if="view.plugin?.isPlugin"
jsname="L2NvbXBvbmVudC9lZGl0b3IvaW5kZXg="
:view="view"
:dimension="state.dimension"
:quota="state.quota"
:themes="themes"
@update-chart-data="updateChartData"
/>
<el-tabs
v-else
v-model="tabActive"

View File

@ -6,6 +6,7 @@ import { useAppStoreWithOut } from '@/store/modules/app'
import router from '@/router'
import { useEmbedded } from '@/store/modules/embedded'
import { XpackComponent } from '@/components/plugin'
import { PluginComponent } from '@/components/plugin'
import {
computed,
CSSProperties,
@ -752,8 +753,23 @@ const showActionIcons = computed(() => {
</div>
<!--这里去渲染不同图库的图表-->
<div v-if="chartAreaShow" style="flex: 1; overflow: hidden">
<plugin-component
v-if="view.plugin?.isPlugin"
jsname="L2NvbXBvbmVudC9pbmRleA=="
:scale="scale"
:dynamic-area-id="dynamicAreaId"
:view="view"
:show-position="showPosition"
:element="element"
ref="chartComponent"
@onChartClick="chartClick"
@onPointClick="onPointClick"
@onDrillFilters="onDrillFilters"
@onJumpClick="jumpClick"
@resetLoading="() => (loading = false)"
/>
<de-rich-text-view
v-if="showChartView(ChartLibraryType.RICH_TEXT)"
v-else-if="showChartView(ChartLibraryType.RICH_TEXT)"
:themes="canvasStyleData.dashboard.themeColor"
ref="chartComponent"
:element="element"

View File

@ -108,10 +108,10 @@ const contentStyle = computed(() => {
//
const handleNew = newComponentInfo => {
const { componentName, innerType } = newComponentInfo
const { componentName, innerType, isPlugin } = newComponentInfo
if (componentName) {
const { width, height, scale } = canvasStyleData.value
const component = findNewComponent(componentName, innerType)
const component = findNewComponent(componentName, innerType, isPlugin)
component.style.top = ((height - component.style.height) * scale) / 200
component.style.left = ((width - component.style.width) * scale) / 200
component.id = guid()

@ -1 +1 @@
Subproject commit 89894c78c876ed49c2d7040cec42816cd7c2c309
Subproject commit 070ee75a6da1e59e8b021462fe0604cbc04b0f64

View File

@ -1,6 +1,7 @@
package io.dataease.api.xpack.component;
import io.dataease.api.xpack.component.vo.XpackMenuVO;
import io.dataease.api.xpack.component.vo.XpackPluginsViewVO;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@ -11,6 +12,12 @@ public interface XpackComponentApi {
@GetMapping("/content/{name}")
String content(@PathVariable("name") String name);
@GetMapping("/contentPlugin/{name}")
String pluginContent(@PathVariable("name") String name);
@GetMapping("/menu")
List<XpackMenuVO> menu();
@GetMapping("/viewPlugins")
List<XpackPluginsViewVO> viewPlugins();
}

View File

@ -0,0 +1,29 @@
package io.dataease.api.xpack.component.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
@Data
public class XpackPluginsViewVO implements Serializable {
@Serial
private static final long serialVersionUID = 5059944608544058565L;
private Long id;
private String name;
private String icon;
private String category;
private String title;
private String chartValue;
private String chartTitle;
private String render;
}

View File

@ -62,7 +62,7 @@ public class WhitelistUtils {
|| StringUtils.startsWithAny(requestURI, "/static-resource/")
|| StringUtils.startsWithAny(requestURI, "/appearance/image/")
|| StringUtils.startsWithAny(requestURI, "/share/proxyInfo")
|| StringUtils.startsWithAny(requestURI, "/xpackComponent/content/")
|| StringUtils.startsWithAny(requestURI, "/xpackComponent/content")
|| StringUtils.startsWithAny(requestURI, "/geo/")
|| StringUtils.startsWithAny(requestURI, "/websocket")
|| StringUtils.startsWithAny(requestURI, "/map/")

View File

@ -2,6 +2,7 @@ package io.dataease.extensions.view.factory;
import io.dataease.extensions.view.template.PluginsChartTemplate;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ -19,4 +20,12 @@ public class PluginsChartFactory {
if (templateMap.containsKey(key)) return;
templateMap.put(key, template);
}
public static List<String> getViewConfigList() {
return templateMap.values().stream().map(PluginsChartTemplate::getConfig).toList();
}
public static List<String> getAllPlugins() {
return null;
}
}

View File

@ -10,6 +10,10 @@ import java.util.Map;
public abstract class PluginsChartTemplate {
public abstract String getConfig();
public abstract Map<String, List<ChartViewFieldDTO>> formatChartAxis(ChartViewDTO view);
/*public Map<String, List<ChartViewFieldDTO>> formatChartAxis(ChartViewDTO view) {