refactor(图表): 优化图片组图表

This commit is contained in:
wangjiahao 2024-09-18 17:20:38 +08:00
parent 849f06f650
commit d570c17ef7
18 changed files with 940 additions and 160 deletions

View File

@ -1,6 +1,5 @@
package io.dataease.visualization.server;
import io.dataease.api.visualization.StaticResourceApi;
import io.dataease.api.visualization.request.StaticResourceRequest;
import io.dataease.exception.DEException;
@ -17,13 +16,9 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import javax.imageio.ImageIO;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1726302584557" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6066" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M0 0h938.666667v938.666667H0V0z m64 64v810.666667h810.666667v-810.666667h-810.666667z" fill="#2C82FC" p-id="6067"></path><path d="M170.666667 170.666667h853.333333v853.333333H170.666667z" fill="#2C82FC" p-id="6068"></path><path d="M967.082667 334.208v362.965333l-151.850667-162.986666s-17.962667-21.461333-32.64-21.461334a35.072 35.072 0 0 0-30.506667 17.706667c-4.053333 7.04-73.344 76.330667-207.872 207.872-6.016 4.309333-6.016 4.309333-12.586666 0L413.653333 641.706667s-16.768-14.293333-28.373333-14.293334c-5.930667 0-11.477333 1.621333-16.426667 4.224s-8.917333 6.528-10.496 8.192l-130.816 125.312V284.458667 227.541333h739.541334v106.666667z m-55.68 632.917333H227.541333v-55.68-68.650666l151.381334-144.896c5.077333-4.266667 8.362667-4.565333 13.482666-0.085334l116.224 95.061334c10.026667 7.082667 20.949333 15.786667 29.525334 15.786666 8.576 0 18.773333-2.944 29.781333-13.952l211.242667-211.242666c1.109333 0.128 2.218667 0.384 3.370666 0.384l1.066667-0.085334 183.424 196.906667v79.744h0.042667v106.666667h-55.68z" fill="#FFFFFF" p-id="6069"></path><path d="M758.698667 672.64a35.584 35.584 0 0 1 45.013333 4.437333c1.536 1.578667 4.352 4.736 4.352 4.736l15.104 15.061334a21.461333 21.461333 0 0 1 0 30.208 21.077333 21.077333 0 0 1-24.064 4.010666c-0.469333 0.256-21.162667-19.157333-21.162667-19.157333l-30.08 30.208-63.701333 63.701333c-0.512 0.554667-0.682667 1.322667-1.28 1.834667a21.418667 21.418667 0 0 1-30.165333 0 21.418667 21.418667 0 0 1 0-30.165333l0.341333-0.298667 105.642667-104.576z m84.949333 104.917333c-0.170667-0.042667-0.256-0.213333-0.469333-0.298666a21.418667 21.418667 0 0 1 0-30.165334 21.461333 21.461333 0 0 1 30.208 0l0.256 0.384 0.085333-0.128 9.728 9.770667a21.333333 21.333333 0 0 1-30.165333 30.165333l-9.642667-9.728zM426.666667 312.874667a85.333333 85.333333 0 1 1-0.042667 170.709333A85.333333 85.333333 0 0 1 426.666667 312.874667zM426.666667 426.666667a28.458667 28.458667 0 1 0-0.042667-56.96A28.458667 28.458667 0 0 0 426.666667 426.666667z" fill="#2C82FC" p-id="6070"></path></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1726300744320" class="icon" viewBox="0 0 1448 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4293" xmlns:xlink="http://www.w3.org/1999/xlink" width="282.8125" height="200"><path d="M1081.624727 143.803796H302.916849c-49.712078 0-89.96338 40.251302-89.96338 89.96338V892.753234c0 49.712078 40.251302 89.96338 89.96338 89.963379h778.879892c49.712078 0 89.96338-40.251302 89.96338-89.963379V233.767176c-0.172014-49.712078-40.423316-89.96338-90.135394-89.96338M302.744835 203.836721h778.879892c16.513355 0 29.930455 13.417101 29.930455 29.930455V586.568117c-48.335965-39.563245-131.934823-93.059634-232.391063-93.059634-92.543591 0-183.883084 71.729884-272.126323 141.051571-67.601545 53.15236-137.611288 108.196876-186.63531 108.196875-68.805644 0-127.806484-50.572148-147.760121-69.837729V233.767176c0.172014-16.513355 13.589115-29.930455 30.10247-29.930455m778.879892 718.846968H302.744835c-16.513355 0-29.930455-13.417101-29.930456-29.930455v-142.25567c35.950949 25.458088 87.89921 52.120275 147.760121 52.120276 69.837729 0 144.491853-58.656812 223.618344-120.92592 80.33059-63.129179 163.241391-128.322526 235.143289-128.322526 128.838569 0 230.842936 113.357299 231.875021 114.561397l0.516042-0.344028V892.753234c0 16.513355-13.417101 29.930455-30.102469 29.930455m-569.366706-359.165463c66.225433 0 120.065849-53.668402 120.065849-120.065849 0-66.225433-53.668402-120.065849-120.065849-120.065849-66.225433 0-120.065849 53.668402-120.065849 120.065849 0 66.397447 53.668402 120.065849 120.065849 120.065849M512.602049 383.591466c33.026709 0 59.688896 26.662187 59.688897 59.688897 0 33.026709-26.662187 59.688896-59.688897 59.688896-33.026709 0-59.688896-26.662187-59.688896-59.688896 0-33.026709 26.662187-59.688896 59.688896-59.688897m0 0" p-id="4294"></path><path d="M1217.687888 833.236351h-96.843944v-51.08819h96.843944c24.770032 0 45.067697-20.125651 45.067697-44.895683V134.34302c0-24.770032-20.125651-44.895683-45.067697-44.895683h-844.589282c-24.770032 0-44.895683 20.125651-44.895683 44.895683v59.688897H276.942718V134.34302c0-52.980346 43.003528-95.983874 95.983874-95.983873h844.589282c52.980346 0 95.983874 43.003528 95.983874 95.983873v602.909458c0.172014 52.980346-42.831514 95.983874-95.81186 95.983873z" p-id="4295"></path></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1726302584557" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6066" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M0 0h938.666667v938.666667H0V0z m64 64v810.666667h810.666667v-810.666667h-810.666667z" fill="#2C82FC" p-id="6067"></path><path d="M170.666667 170.666667h853.333333v853.333333H170.666667z" fill="#2C82FC" p-id="6068"></path><path d="M967.082667 334.208v362.965333l-151.850667-162.986666s-17.962667-21.461333-32.64-21.461334a35.072 35.072 0 0 0-30.506667 17.706667c-4.053333 7.04-73.344 76.330667-207.872 207.872-6.016 4.309333-6.016 4.309333-12.586666 0L413.653333 641.706667s-16.768-14.293333-28.373333-14.293334c-5.930667 0-11.477333 1.621333-16.426667 4.224s-8.917333 6.528-10.496 8.192l-130.816 125.312V284.458667 227.541333h739.541334v106.666667z m-55.68 632.917333H227.541333v-55.68-68.650666l151.381334-144.896c5.077333-4.266667 8.362667-4.565333 13.482666-0.085334l116.224 95.061334c10.026667 7.082667 20.949333 15.786667 29.525334 15.786666 8.576 0 18.773333-2.944 29.781333-13.952l211.242667-211.242666c1.109333 0.128 2.218667 0.384 3.370666 0.384l1.066667-0.085334 183.424 196.906667v79.744h0.042667v106.666667h-55.68z" fill="#FFFFFF" p-id="6069"></path><path d="M758.698667 672.64a35.584 35.584 0 0 1 45.013333 4.437333c1.536 1.578667 4.352 4.736 4.352 4.736l15.104 15.061334a21.461333 21.461333 0 0 1 0 30.208 21.077333 21.077333 0 0 1-24.064 4.010666c-0.469333 0.256-21.162667-19.157333-21.162667-19.157333l-30.08 30.208-63.701333 63.701333c-0.512 0.554667-0.682667 1.322667-1.28 1.834667a21.418667 21.418667 0 0 1-30.165333 0 21.418667 21.418667 0 0 1 0-30.165333l0.341333-0.298667 105.642667-104.576z m84.949333 104.917333c-0.170667-0.042667-0.256-0.213333-0.469333-0.298666a21.418667 21.418667 0 0 1 0-30.165334 21.461333 21.461333 0 0 1 30.208 0l0.256 0.384 0.085333-0.128 9.728 9.770667a21.333333 21.333333 0 0 1-30.165333 30.165333l-9.642667-9.728zM426.666667 312.874667a85.333333 85.333333 0 1 1-0.042667 170.709333A85.333333 85.333333 0 0 1 426.666667 312.874667zM426.666667 426.666667a28.458667 28.458667 0 1 0-0.042667-56.96A28.458667 28.458667 0 0 0 426.666667 426.666667z" fill="#2C82FC" p-id="6070"></path></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -41,6 +41,7 @@ import treemap from '@/assets/svg/treemap.svg'
import waterfall from '@/assets/svg/waterfall.svg'
import wordCloud from '@/assets/svg/word-cloud.svg'
import tHeatmap from '@/assets/svg/t-heatmap.svg'
import pictureGroup from '@/assets/svg/picture-group.svg'
const iconChartMap = {
'area-stack': areaStack,
@ -85,7 +86,8 @@ const iconChartMap = {
treemap: treemap,
waterfall: waterfall,
'word-cloud': wordCloud,
't-heatmap': tHeatmap
't-heatmap': tHeatmap,
'picture-group': pictureGroup
}
export { iconChartMap }

View File

@ -306,7 +306,7 @@ const list = [
component: 'UserView',
name: '图表',
label: '图表',
propValue: { textValue: '' },
propValue: { textValue: '', urlList: [] },
icon: 'bar',
innerType: 'bar',
editing: false,

View File

@ -1,31 +1,38 @@
<script setup lang="ts">
import CommonAttr from '@/custom-component/common/CommonAttr.vue'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { snapshotStoreWithOut } from '@/store/modules/data-visualization/snapshot'
import { storeToRefs } from 'pinia'
import { ElIcon, ElMessage } from 'element-plus-secondary'
import { ref, onMounted, onBeforeUnmount, watch, PropType, reactive, toRefs, computed } from 'vue'
import { ref, onMounted, onBeforeUnmount, watch, PropType, computed } from 'vue'
import { beforeUploadCheck, uploadFileResult } from '@/api/staticResource'
import { imgUrlTrans } from '@/utils/imgUtils'
import eventBus from '@/utils/eventBus'
import ImgViewDialog from '@/custom-component/ImgViewDialog.vue'
import DatasetSelect from '@/views/chart/components/editor/dataset-select/DatasetSelect.vue'
import Icon from '../../components/icon-custom/src/Icon.vue'
import { useI18n } from '@/hooks/web/useI18n'
import { cloneDeep } from 'lodash-es'
import FilterTree from '@/views/chart/components/editor/filter/FilterTree.vue'
import { toRefs } from 'vue'
const { t } = useI18n()
const props = defineProps({
themes: {
type: String as PropType<EditorTheme>,
default: 'dark'
},
element: {
type: Object,
default() {
return {
propValue: {
urlList: []
}
}
}
}
})
const dvMainStore = dvMainStoreWithOut()
const snapshotStore = snapshotStoreWithOut()
const { element } = toRefs(props)
const { curComponent } = storeToRefs(dvMainStore)
@ -43,14 +50,14 @@ const handlePictureCardPreview = file => {
const handleRemove = (_, fileList) => {
uploadDisabled.value = false
curComponent.value.propValue.url = null
element.value.propValue['urlList'] = []
fileList.value = []
snapshotStore.recordSnapshotCache()
}
async function upload(file) {
uploadFileResult(file.file, fileUrl => {
snapshotStore.recordSnapshotCache()
curComponent.value.propValue.url = fileUrl
element.value.propValue.urlList.push({ name: file.file.name, url: fileUrl })
})
}
@ -70,28 +77,33 @@ const reUpload = e => {
}
uploadFileResult(file, fileUrl => {
snapshotStore.recordSnapshotCache()
curComponent.value.propValue.url = fileUrl
fileList.value = [{ url: imgUrlTrans(curComponent.value.propValue.url) }]
element.value.propValue.url = fileUrl
fileList.value = [{ name: file.name, url: imgUrlTrans(element.value.propValue.url) }]
})
}
const sizeMessage = () => {
ElMessage.success('图片大小不符合')
}
const init = () => {
if (curComponent.value.propValue.url) {
fileList.value = [{ url: imgUrlTrans(curComponent.value.propValue.url) }]
} else {
fileList.value = []
const fileListInit = () => {
fileList.value = []
if (element.value.propValue.urlList && element.value.propValue.urlList.length > 0) {
element.value.propValue.urlList.forEach(urlInfo => {
fileList.value.push({ name: urlInfo.name, url: imgUrlTrans(urlInfo.url) })
})
}
}
const init = () => {
fileListInit()
}
const toolTip = computed(() => {
return props.themes === 'dark' ? 'ndark' : 'dark'
})
watch(
() => curComponent.value.propValue.url,
() => element.value.propValue.url,
() => {
init()
}
@ -107,7 +119,7 @@ onBeforeUnmount(() => {
</script>
<template>
<div class="attr-list de-collapse-style">
<el-collapse-item :effect="themes" title="图片组" name="picture">
<input
id="input"
ref="files"
@ -121,77 +133,69 @@ onBeforeUnmount(() => {
"
@change="reUpload"
/>
<CommonAttr
:themes="themes"
:element="curComponent"
:background-color-picker-width="197"
:background-border-select-width="197"
>
<el-collapse-item :effect="themes" title="图片组" name="picture">
<el-row class="img-area" :class="`img-area_${themes}`">
<el-col style="width: 130px !important">
<el-upload
:themes="themes"
action=""
accept=".jpeg,.jpg,.png,.gif,.svg"
class="avatar-uploader"
list-type="picture-card"
:class="{ disabled: uploadDisabled }"
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove"
:before-upload="beforeUploadCheck"
:http-request="upload"
:file-list="fileList"
>
<el-icon><Plus /></el-icon>
</el-upload>
<img-view-dialog v-model="dialogVisible" :image-url="dialogImageUrl"></img-view-dialog>
</el-col>
</el-row>
<el-row>
<span
style="margin-top: 2px"
v-if="!curComponent.propValue.url"
class="image-hint"
:class="`image-hint_${themes}`"
>
支持JPGPNGGIFSVG
</span>
<el-row class="img-area" :class="`img-area_${themes}`">
<el-col style="width: 130px !important">
<el-upload
:themes="themes"
limit="10"
action=""
accept=".jpeg,.jpg,.png,.gif,.svg"
class="avatar-uploader"
list-type="picture-card"
:class="{ disabled: uploadDisabled }"
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove"
:before-upload="beforeUploadCheck"
:http-request="upload"
:file-list="fileList"
>
<el-icon><Plus /></el-icon>
</el-upload>
<img-view-dialog v-model="dialogVisible" :image-url="dialogImageUrl"></img-view-dialog>
</el-col>
</el-row>
<el-row>
<span
style="margin-top: 2px"
v-if="!curComponent.propValue.url"
class="image-hint"
:class="`image-hint_${themes}`"
>
支持JPGPNGGIFSVG
</span>
<el-button
size="small"
style="margin: 8px 0 0 -4px"
v-if="curComponent.propValue.url"
text
@click="goFile"
>
重新上传
</el-button>
</el-row>
<el-row class="pic-adaptor">
<el-form-item
v-if="curComponent.style.adaptation"
class="form-item"
label="图片适应方式"
size="small"
:effect="themes"
:class="'form-item-' + themes"
>
<el-radio-group
size="small"
v-model="curComponent.style.adaptation"
@change="onStyleChange"
:effect="themes"
>
<el-radio label="adaptation" :effect="themes">适应组件</el-radio>
<el-radio label="original" :effect="themes">原始尺寸</el-radio>
<el-radio label="equiratio" :effect="themes">等比适应</el-radio>
</el-radio-group>
</el-form-item>
</el-row>
</el-collapse-item>
</CommonAttr>
</div>
<el-button
size="small"
style="margin: 8px 0 0 -4px"
v-if="curComponent.propValue.url"
text
@click="goFile"
>
重新上传
</el-button>
</el-row>
<el-row class="pic-adaptor">
<el-form-item
v-if="curComponent.style.adaptation"
class="form-item"
label="图片适应方式"
size="small"
:effect="themes"
:class="'form-item-' + themes"
>
<el-radio-group
size="small"
v-model="curComponent.style.adaptation"
@change="onStyleChange"
:effect="themes"
>
<el-radio label="adaptation" :effect="themes">适应组件</el-radio>
<el-radio label="original" :effect="themes">原始尺寸</el-radio>
<el-radio label="equiratio" :effect="themes">等比适应</el-radio>
</el-radio-group>
</el-form-item>
</el-row>
</el-collapse-item>
</template>
<style lang="less" scoped>
@ -251,8 +255,6 @@ onBeforeUnmount(() => {
}
}
.img-area {
height: 80px;
width: 80px;
margin-top: 10px;
overflow: hidden;

View File

@ -2,35 +2,44 @@
<div class="pic-main" @click="onPictureClick">
<img
draggable="false"
v-if="propValue['url']"
v-if="state.showUrl"
:style="imageAdapter"
:src="imgUrlTrans(propValue['url'])"
:src="imgUrlTrans(state.showUrl)"
/>
<div v-else class="pic-upload">
<span
><el-button @click="uploadImg" text style="color: #646a73" icon="Plus"
>请上传图片...</el-button
></span
>
</div>
</div>
</template>
<script setup lang="ts">
import { CSSProperties, computed, nextTick, toRefs } from 'vue'
import { CSSProperties, computed, nextTick, toRefs, reactive, ref, PropType } from 'vue'
import { imgUrlTrans } from '@/utils/imgUtils'
import eventBus from '@/utils/eventBus'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { getData } from '@/api/chart'
import { parseJson } from '@/views/chart/components/js/util'
import { mappingColor } from '@/views/chart/components/js/panel/common/common_table'
import { storeToRefs } from 'pinia'
const dvMainStore = dvMainStoreWithOut()
const { canvasViewInfo } = storeToRefs(dvMainStore)
const state = reactive({
emptyValue: '-',
data: null,
viewDataInfo: null,
showUrl: null,
firstRender: true,
previewFirstRender: true
})
const initReady = ref(true)
const props = defineProps({
propValue: {
type: String,
required: true,
default: ''
},
element: {
type: Object,
default() {
return {
propValue: { urlList: [] }
}
}
},
view: {
type: Object as PropType<ChartObj>,
default() {
return {
propValue: null
@ -38,8 +47,13 @@ const props = defineProps({
}
}
})
const isError = ref(false)
const errMsg = ref('')
const dataRowSelect = ref({})
const dataRowNameSelect = ref({})
const dataRowFiledName = ref([])
const { propValue, element } = toRefs(props)
const { element, view } = toRefs(props)
const imageAdapter = computed(() => {
const style = {
@ -55,21 +69,132 @@ const imageAdapter = computed(() => {
}
return style as CSSProperties
})
const onPictureClick = e => {
if (element.value.events && element.value.events.checked) {
if (element.value.events.type === 'displayChange') {
//
nextTick(() => {
dvMainStore.popAreaActiveSwitch()
})
}
}
}
const uploadImg = () => {
nextTick(() => {
eventBus.emit('uploadImg')
})
}
const initCurFields = chartDetails => {
dataRowFiledName.value = []
dataRowSelect.value = {}
dataRowNameSelect.value = {}
if (chartDetails.data && chartDetails.data.sourceFields) {
const checkAllAxisStr =
JSON.stringify(chartDetails.xAxis) +
JSON.stringify(chartDetails.xAxisExt) +
JSON.stringify(chartDetails.yAxis) +
JSON.stringify(chartDetails.yAxisExt)
chartDetails.data.sourceFields.forEach(field => {
if (checkAllAxisStr.indexOf(field.id) > -1) {
dataRowFiledName.value.push(`[${field.name}]`)
}
})
if (checkAllAxisStr.indexOf('"记录数*"') > -1) {
dataRowFiledName.value.push(`[记录数*]`)
}
const sourceFieldNameIdMap = chartDetails.data.fields.reduce((pre, next) => {
pre[next['dataeaseName']] = next['name']
return pre
}, {})
const rowData = chartDetails.data.tableRow[0]
for (const key in rowData) {
dataRowNameSelect.value[sourceFieldNameIdMap[key]] = rowData[key]
}
}
conditionAdaptor(view.value)
}
const conditionAdaptor = (chart: Chart) => {
if (!chart || !chart.senior) {
return
}
const { threshold } = parseJson(chart.senior)
if (!threshold.enable) {
return
}
const conditions = threshold.tableThreshold ?? []
if (conditions?.length > 0) {
for (let i = 0; i < conditions.length; i++) {
const field = conditions[i]
let defaultValueColor = 'none'
const checkResult = mappingColor(
dataRowNameSelect.value[field.field.name],
defaultValueColor,
field,
'url'
)
if (checkResult) {
state.showUrl = checkResult
}
}
}
}
const withInit = () => {
if (element.value.propValue['urlList'] && element.value.propValue['urlList'].length > 0) {
state.showUrl = element.value.propValue['urlList'][0].url
}
}
const calcData = (view: Chart, callback) => {
console.log('===calcData1')
isError.value = false
if (view.tableId || view['dataFrom'] === 'template') {
const v = JSON.parse(JSON.stringify(view))
getData(v)
.then(res => {
if (res.code && res.code !== 0) {
isError.value = true
errMsg.value = res.msg
} else {
state.data = res?.data
state.viewDataInfo = res
state.totalItems = res?.totalItems
const curViewInfo = canvasViewInfo.value[element.value.id]
curViewInfo['curFields'] = res.data.fields
dvMainStore.setViewDataDetails(element.value.id, res)
initReady.value = true
initCurFields(res)
}
callback?.()
nextTick(() => {
initReady.value = true
})
})
.catch(e => {
console.error(e)
nextTick(() => {
initReady.value = true
})
callback?.()
})
} else if (!view.tableId) {
initReady.value = true
withInit()
callback?.()
nextTick(() => {
initReady.value = true
})
} else {
withInit()
nextTick(() => {
initReady.value = true
})
callback?.()
}
}
//
const renderChart = viewInfo => {
//do renderView
}
defineExpose({
calcData,
renderChart
})
</script>
<style lang="less" scoped>

View File

@ -194,6 +194,10 @@ declare interface Threshold {
* 背景颜色
*/
backgroundColor: string
/**
* url
*/
url: string
}
/**

View File

@ -210,7 +210,7 @@ const clearShow = computed(
() =>
props.sourceType === 'dataset' &&
dvMainStore.curComponent &&
dvMainStore.curComponent.innerType === 'rich-text'
['rich-text', 'picture-group'].includes(dvMainStore.curComponent.innerType)
)
const handleClear = e => {

View File

@ -10,7 +10,12 @@ import TextThresholdEdit from '@/views/chart/components/editor/editor-senior/com
import { fieldType } from '@/utils/attr'
import { defaultsDeep } from 'lodash-es'
import { iconFieldMap } from '@/components/icon-group/field-list'
import PictureGroupThresholdEdit from '@/views/chart/components/editor/editor-senior/components/dialog/PictureGroupThresholdEdit.vue'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
import { storeToRefs } from 'pinia'
import { imgUrlTrans } from '@/utils/imgUtils'
const dvMainStore = dvMainStoreWithOut()
const { curComponent } = storeToRefs(dvMainStore)
const { t } = useI18n()
const props = defineProps({
@ -592,22 +597,35 @@ init()
</span>
<span v-else>&nbsp;</span>
</div>
<div
:title="t('chart.textColor')"
:style="{
backgroundColor: item.color
}"
class="color-div"
:class="{ 'color-div-dark': themes === 'dark' }"
></div>
<div
:title="t('chart.backgroundColor')"
:style="{
backgroundColor: item.backgroundColor
}"
class="color-div"
:class="{ 'color-div-dark': themes === 'dark' }"
></div>
<template v-if="chart.type === 'picture-group'">
<div title="显示图片" class="pic-group-main">
<img
draggable="false"
v-if="item.url"
class="pic-group-img"
:src="imgUrlTrans(item.url)"
/>
</div>
</template>
<template v-if="chart.type !== 'picture-group'">
<div
:title="t('chart.textColor')"
:style="{
backgroundColor: item.color
}"
class="color-div"
:class="{ 'color-div-dark': themes === 'dark' }"
></div>
<div
:title="t('chart.backgroundColor')"
:style="{
backgroundColor: item.backgroundColor
}"
class="color-div"
:class="{ 'color-div-dark': themes === 'dark' }"
></div>
</template>
</div>
</el-row>
</div>
@ -672,7 +690,15 @@ init()
class="dialog-css"
append-to-body
>
<picture-group-threshold-edit
v-if="chart.type === 'picture-group' && curComponent"
:threshold="state.thresholdForm.tableThreshold"
:chart="chart"
:element="curComponent"
@onTableThresholdChange="tableThresholdChange"
></picture-group-threshold-edit>
<table-threshold-edit
v-else
:threshold="state.thresholdForm.tableThreshold"
:chart="chart"
@onTableThresholdChange="tableThresholdChange"
@ -861,4 +887,16 @@ span {
color: #5f5f5f !important;
}
}
.pic-group-main {
margin-right: 8px;
width: 24px;
height: 24px;
border: solid 1px #e1e4e8;
border-radius: 2px;
}
.pic-group-img {
width: 100%;
height: 100%;
}
</style>

View File

@ -0,0 +1,598 @@
<script lang="tsx" setup>
import icon_info_filled from '@/assets/svg/icon_info_filled.svg'
import icon_deleteTrash_outlined from '@/assets/svg/icon_delete-trash_outlined.svg'
import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
import { PropType, reactive } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import { COLOR_PANEL } from '../../../util/chart'
import { fieldType } from '@/utils/attr'
import { iconFieldMap } from '@/components/icon-group/field-list'
const { t } = useI18n()
const props = defineProps({
chart: {
type: Object as PropType<ChartObj>,
required: true
},
element: {
type: Object,
default() {
return {
propValue: { urlList: [] }
}
}
},
threshold: {
type: Array,
required: true
}
})
const emit = defineEmits(['onTableThresholdChange'])
const thresholdCondition = {
term: 'eq',
field: '0',
value: '0',
color: '#ff0000ff',
backgroundColor: '#ffffff00',
min: '0',
max: '1'
}
const textOptions = [
{
label: '',
options: [
{
value: 'eq',
label: t('chart.filter_eq')
},
{
value: 'not_eq',
label: t('chart.filter_not_eq')
}
]
},
{
label: '',
options: [
{
value: 'like',
label: t('chart.filter_like')
},
{
value: 'not like',
label: t('chart.filter_not_like')
}
]
},
{
label: '',
options: [
{
value: 'null',
label: t('chart.filter_null')
},
{
value: 'not_null',
label: t('chart.filter_not_null')
}
]
},
{
label: '',
options: [
{
value: 'default',
label: '默认'
}
]
}
]
const dateOptions = [
{
label: '',
options: [
{
value: 'eq',
label: t('chart.filter_eq')
},
{
value: 'not_eq',
label: t('chart.filter_not_eq')
}
]
},
{
label: '',
options: [
{
value: 'lt',
label: t('chart.filter_lt')
},
{
value: 'gt',
label: t('chart.filter_gt')
}
]
},
{
label: '',
options: [
{
value: 'le',
label: t('chart.filter_le')
},
{
value: 'ge',
label: t('chart.filter_ge')
}
]
},
{
label: '',
options: [
{
value: 'default',
label: '默认'
}
]
}
]
const valueOptions = [
{
label: '',
options: [
{
value: 'eq',
label: t('chart.filter_eq')
},
{
value: 'not_eq',
label: t('chart.filter_not_eq')
}
]
},
{
label: '',
options: [
{
value: 'lt',
label: t('chart.filter_lt')
},
{
value: 'gt',
label: t('chart.filter_gt')
}
]
},
{
label: '',
options: [
{
value: 'le',
label: t('chart.filter_le')
},
{
value: 'ge',
label: t('chart.filter_ge')
}
]
},
{
label: '',
options: [
{
value: 'between',
label: t('chart.filter_between')
}
]
},
{
label: '',
options: [
{
value: 'default',
label: '默认'
}
]
}
]
const predefineColors = COLOR_PANEL
const state = reactive({
thresholdArr: [] as TableThreshold[],
fields: [],
thresholdObj: {
fieldId: '',
field: {},
conditions: []
} as TableThreshold
})
const init = () => {
state.thresholdArr = JSON.parse(JSON.stringify(props.threshold)) as TableThreshold[]
initFields()
}
const initOptions = item => {
if (item.field) {
if ([0, 5, 7].includes(item.field.deType)) {
item.options = JSON.parse(JSON.stringify(textOptions))
} else if (item.field.deType === 1) {
item.options = JSON.parse(JSON.stringify(dateOptions))
} else {
item.options = JSON.parse(JSON.stringify(valueOptions))
}
item.conditions &&
item.conditions.forEach(ele => {
ele.term = ''
})
}
}
const initFields = () => {
let fields = []
if (props.chart.type === 'table-info') {
fields = JSON.parse(JSON.stringify(props.chart.xAxis))
} else if (props.chart.type === 'table-pivot') {
const xAxis = JSON.parse(JSON.stringify(props.chart.xAxis))
const xAxisExt = JSON.parse(JSON.stringify(props.chart.xAxisExt))
const yAxis = JSON.parse(JSON.stringify(props.chart.yAxis))
fields = [...xAxis, ...xAxisExt, ...yAxis]
} else {
const xAxis = JSON.parse(JSON.stringify(props.chart.xAxis))
const yAxis = JSON.parse(JSON.stringify(props.chart.yAxis))
fields = [...xAxis, ...yAxis]
}
state.fields.splice(0, state.fields.length, ...fields)
}
const addThreshold = () => {
state.thresholdArr.push(JSON.parse(JSON.stringify(state.thresholdObj)))
changeThreshold()
}
const removeThreshold = index => {
state.thresholdArr.splice(index, 1)
changeThreshold()
}
const changeThreshold = () => {
emit('onTableThresholdChange', state.thresholdArr)
}
const addConditions = item => {
item.conditions.push(JSON.parse(JSON.stringify(thresholdCondition)))
changeThreshold()
}
const removeCondition = (item, index) => {
item.conditions.splice(index, 1)
changeThreshold()
}
const addField = item => {
// get field
if (state.fields && state.fields.length > 0) {
state.fields.forEach(ele => {
if (item.fieldId === ele.id) {
item.field = JSON.parse(JSON.stringify(ele))
initOptions(item)
}
})
}
changeThreshold()
}
init()
</script>
<template>
<el-col>
<div class="tip">
<Icon name="icon_info_filled" class="icon-style"
><icon_info_filled class="svg-icon icon-style"
/></Icon>
<span style="padding-left: 10px">{{ t('chart.table_threshold_tip') }}</span>
</div>
<div @keydown.stop @keyup.stop style="max-height: 50vh; overflow-y: auto">
<div
v-for="(fieldItem, fieldIndex) in state.thresholdArr"
:key="fieldIndex"
class="field-item"
>
<el-row style="margin-top: 6px; align-items: center; justify-content: space-between">
<el-form-item class="form-item">
<el-select v-model="fieldItem.fieldId" @change="addField(fieldItem)">
<el-option
class="series-select-option"
v-for="fieldOption in state.fields"
:key="fieldOption.id"
:label="fieldOption.name"
:value="fieldOption.id"
:disabled="chart.type === 'table-info' && fieldOption.deType === 7"
>
<el-icon style="margin-right: 8px">
<Icon
:className="`field-icon-${
fieldType[[2, 3].includes(fieldOption.deType) ? 2 : 0]
}`"
><component :is="iconFieldMap[fieldType[fieldOption.deType]]"></component
></Icon>
</el-icon>
{{ fieldOption.name }}
</el-option>
</el-select>
</el-form-item>
<el-button
class="circle-button m-icon-btn"
text
:style="{ float: 'right' }"
@click="removeThreshold(fieldIndex)"
>
<el-icon size="20px" style="color: #646a73">
<Icon name="icon_delete-trash_outlined"
><icon_deleteTrash_outlined class="svg-icon"
/></Icon>
</el-icon>
</el-button>
</el-row>
<el-row :style="{ marginTop: '16px', borderTop: '1px solid #d5d6d8' }">
<el-row
v-for="(item, index) in fieldItem.conditions"
:key="index"
class="line-item"
:gutter="10"
>
<el-col :span="4">
<el-form-item class="form-item">
<el-select v-model="item.term" @change="changeThreshold">
<el-option-group
v-for="(group, idx) in fieldItem.options"
:key="idx"
:label="group.label"
>
<el-option
v-for="opt in group.options"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-option-group>
</el-select>
</el-form-item>
</el-col>
<el-col
v-if="
!item.term.includes('null') &&
!item.term.includes('empty') &&
item.term !== 'between'
"
:span="10"
style="text-align: center"
>
<el-form-item class="form-item">
<el-input-number
v-model="item.value"
v-if="[2, 3].includes(fieldItem.field.deType)"
:placeholder="t('chart.drag_block_label_value')"
controls-position="right"
class="value-item"
clearable
@change="changeThreshold"
/>
<el-input
v-model="item.value"
v-else
:placeholder="t('chart.drag_block_label_value')"
controls-position="right"
clearable
@change="changeThreshold"
/>
</el-form-item>
</el-col>
<el-col v-if="item.term === 'between'" :span="4" style="text-align: center">
<el-form-item class="form-item">
<el-input-number
v-model="item.min"
controls-position="right"
class="between-item"
:placeholder="t('chart.axis_value_min')"
clearable
@change="changeThreshold"
/>
</el-form-item>
</el-col>
<el-col v-if="item.term === 'between'" :span="2" style="text-align: center">
<span style="margin: 0 4px">
&nbsp;&nbsp;{{ t('chart.drag_block_label_value') }}&nbsp;&nbsp;
</span>
</el-col>
<el-col v-if="item.term === 'between'" :span="4" style="text-align: center">
<el-form-item class="form-item">
<el-input-number
v-model="item.max"
controls-position="right"
class="between-item"
:placeholder="t('chart.axis_value_max')"
clearable
@change="changeThreshold"
/>
</el-form-item>
</el-col>
<div
style="display: flex; align-items: center; justify-content: center; margin-left: 8px"
>
<div class="color-title">展示图片</div>
<el-form-item class="form-item">
<el-select v-model="item.url" @change="changeThreshold">
<el-option
v-for="urlInfo in element.propValue.urlList"
:key="urlInfo.url"
:label="urlInfo.name"
:value="urlInfo.url"
/>
</el-select>
</el-form-item>
</div>
<div
style="display: flex; align-items: center; justify-content: center; margin-left: 8px"
>
<el-button
class="circle-button m-icon-btn"
text
@click="removeCondition(fieldItem, index)"
>
<el-icon size="20px" style="color: #646a73">
<Icon name="icon_delete-trash_outlined"
><icon_deleteTrash_outlined class="svg-icon"
/></Icon>
</el-icon>
</el-button>
</div>
</el-row>
</el-row>
<el-button
style="margin-top: 10px"
class="circle-button"
type="primary"
text
@click="addConditions(fieldItem)"
>
<template #icon>
<Icon name="icon_add_outlined"><icon_add_outlined class="svg-icon" /></Icon>
</template>
{{ t('chart.add_style') }}
</el-button>
</div>
</div>
<el-button
class="circle-button"
text
type="primary"
style="margin-top: 10px"
@click="addThreshold"
>
<template #icon>
<Icon name="icon_add_outlined"><icon_add_outlined class="svg-icon" /></Icon>
</template>
{{ t('chart.add_condition') }}
</el-button>
</el-col>
</template>
<style lang="less" scoped>
.field-item {
width: 100%;
border-radius: 4px;
padding: 10px 16px;
margin-top: 10px;
background: #f5f6f7;
}
.line-item {
width: 100%;
display: flex;
justify-content: left;
align-items: center;
margin-top: 16px;
}
.form-item {
height: 28px !important;
:deep(.el-form-item__label) {
font-size: 12px;
}
}
span {
font-size: 12px;
}
.value-item {
position: relative;
display: inline-block;
width: 100% !important;
}
.between-item {
position: relative;
display: inline-block;
width: 100% !important;
}
.select-item {
position: relative;
display: inline-block;
width: 100% !important;
}
.el-select-dropdown__item {
padding: 0 20px;
font-size: 12px;
}
.color-picker-style {
cursor: pointer;
z-index: 1003;
width: 28px;
height: 28px;
}
.color-picker-style :deep(.el-color-picker__trigger) {
width: 28px;
height: 28px;
}
.color-title {
color: #646a73;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 22px;
padding: 0 8px;
}
.tip {
font-size: 12px;
background: #d6e2ff;
border-radius: 4px;
padding: 10px 20px;
display: flex;
align-items: center;
}
:deep(.ed-form-item) {
margin-bottom: 0 !important;
}
.icon-style {
width: 14px;
height: 14px;
color: var(--ed-color-primary);
}
.m-icon-btn {
&:hover {
background: rgba(31, 35, 41, 0.1) !important;
}
&:focus {
background: rgba(31, 35, 41, 0.1) !important;
}
&:active {
background: rgba(31, 35, 41, 0.2) !important;
}
}
.series-select-option {
display: flex;
align-items: center;
justify-content: flex-start;
padding: 0 11px;
}
</style>

View File

@ -28,9 +28,10 @@ import FlowMapLineSelector from '@/views/chart/components/editor/editor-style/co
import FlowMapPointSelector from '@/views/chart/components/editor/editor-style/components/FlowMapPointSelector.vue'
import CommonEvent from '@/custom-component/common/CommonEvent.vue'
import CommonBorderSetting from '@/custom-component/common/CommonBorderSetting.vue'
import PictureGroupAttr from '@/custom-component/picture-group/Attr.vue'
const dvMainStore = dvMainStoreWithOut()
const { dvInfo, batchOptStatus } = storeToRefs(dvMainStore)
const { dvInfo, batchOptStatus, curComponent } = storeToRefs(dvMainStore)
const { t } = useI18n()
const state = {
@ -144,6 +145,10 @@ const eventsShow = computed(() => {
)
})
const pictureGroupShow = computed(() => {
return curComponent.value?.innerType === 'picture-group'
})
const showProperties = (property: EditorProperty) => properties.value?.includes(property)
const onMiscChange = (val, prop) => {
@ -607,6 +612,11 @@ watch(
@onChangeYAxisExtForm="onChangeYAxisExtForm"
/>
</collapse-switch-item>
<PictureGroupAttr
v-if="pictureGroupShow"
:themes="themes"
:element="curComponent"
></PictureGroupAttr>
</el-collapse>
</el-row>
</div>

View File

@ -1784,7 +1784,7 @@ const deleteChartFieldItem = id => {
<el-container direction="vertical">
<el-scrollbar class="has-footer drag_main_area attr-style theme-border-class">
<el-row
v-if="!['rich-text', 'Picture'].includes(view.type)"
v-if="!['rich-text', 'Picture', 'picture-group'].includes(view.type)"
class="drag-data padding-lr"
>
<span class="data-area-label">{{ t('chart.switch_chart') }}</span>

View File

@ -1160,6 +1160,13 @@ export const CHART_TYPE_CONFIGS = [
value: 'indicator',
title: t('chart.chart_indicator'),
icon: 'indicator'
},
{
render: 'custom',
category: 'quota',
value: 'picture-group',
title: '图片组',
icon: 'picture-group'
}
]
},
@ -1505,20 +1512,6 @@ export const CHART_TYPE_CONFIGS = [
icon: 'rich-text'
}
]
},
{
category: 'other',
title: '图片组',
display: 'hidden',
details: [
{
render: 'custom',
category: 'quota',
value: 'picture-group',
title: '图片图',
icon: 'picture'
}
]
}
]

View File

@ -610,6 +610,9 @@ export function mappingColor(value, defaultColor, field, type) {
color = t[type]
flag = true
}
} else if (t.term === 'default') {
color = t[type]
flag = true
}
if (flag) {
break
@ -648,6 +651,9 @@ export function mappingColor(value, defaultColor, field, type) {
color = t[type]
flag = true
}
} else if (t.term === 'default') {
color = t[type]
flag = true
}
if (flag) {
break
@ -688,6 +694,9 @@ export function mappingColor(value, defaultColor, field, type) {
color = t[type]
flag = true
}
} else if (t.term === 'default') {
color = t[type]
flag = true
}
if (flag) {
break

View File

@ -15,7 +15,7 @@ export enum ChartLibraryType {
ECHARTS = 'echarts',
S2 = 's2',
RICH_TEXT = 'rich-text',
PICTURE_GROUP = 'picture_group',
PICTURE_GROUP = 'picture-group',
INDICATOR = 'indicator'
}
export abstract class ChartWrapper<O> {

View File

@ -125,7 +125,7 @@ const { view, showPosition, element, active, searchCount, scale } = toRefs(props
const titleShow = computed(() => {
return (
!['rich-text', 'Picture'].includes(element.value.innerType) &&
!['rich-text', 'picture-group'].includes(element.value.innerType) &&
state.title_show &&
showPosition.value !== 'viewDialog'
)
@ -678,7 +678,7 @@ const chartAreaShow = computed(() => {
return true
}
}
if (['rich-text', 'Picture'].includes(view.value.type)) {
if (['rich-text', 'picture-group'].includes(view.value.type)) {
return true
}
if (view.value?.isPlugin) {
@ -911,6 +911,7 @@ const loadPluginCategory = data => {
ref="chartComponent"
:element="element"
:active="active"
:view="view"
:show-position="showPosition"
>
</de-picture-group>