feat(数据大屏、仪表板): 新增图片组

This commit is contained in:
wangjiahao 2024-09-14 16:36:57 +08:00
parent 60cfb291f7
commit cf678ee54a
9 changed files with 507 additions and 30 deletions

View File

@ -693,6 +693,9 @@ import icon_font from '@/assets/svg/icon_font.svg'
import tHeatmap from '@/assets/svg/t-heatmap.svg'
import tHeatmapDark from '@/assets/svg/t-heatmap-dark.svg'
import tHeatmapOrigin from '@/assets/svg/t-heatmap-origin.svg'
import pictureGroupDark from '@/assets/svg/picture-group-dark.svg'
import pictureGroupOrigin from '@/assets/svg/picture-group-origin.svg'
import pictureGroup from '@/assets/svg/picture-group.svg'
const iconMap = {
'401': _401,
icon_link_calculated_outlined,
@ -1384,7 +1387,10 @@ const iconMap = {
clock,
't-heatmap': tHeatmap,
't-heatmap-dark': tHeatmapDark,
't-heatmap-origin': tHeatmapOrigin
't-heatmap-origin': tHeatmapOrigin,
'picture-group': pictureGroup,
'picture-group-dark': pictureGroupDark,
'picture-group-origin': pictureGroupOrigin
}
const props = defineProps({

View File

@ -0,0 +1,357 @@
<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 { 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'
const { t } = useI18n()
const props = defineProps({
themes: {
type: String as PropType<EditorTheme>,
default: 'dark'
}
})
const dvMainStore = dvMainStoreWithOut()
const snapshotStore = snapshotStoreWithOut()
const { curComponent } = storeToRefs(dvMainStore)
const fileList = ref([])
const dialogImageUrl = ref('')
const dialogVisible = ref(false)
const uploadDisabled = ref(false)
const files = ref(null)
const maxImageSize = 15000000
const handlePictureCardPreview = file => {
dialogImageUrl.value = file.url
dialogVisible.value = true
}
const handleRemove = (_, fileList) => {
uploadDisabled.value = false
curComponent.value.propValue.url = null
fileList.value = []
snapshotStore.recordSnapshotCache()
}
async function upload(file) {
uploadFileResult(file.file, fileUrl => {
snapshotStore.recordSnapshotCache()
curComponent.value.propValue.url = fileUrl
})
}
const onStyleChange = () => {
snapshotStore.recordSnapshotCache()
}
const goFile = () => {
files.value.click()
}
const reUpload = e => {
const file = e.target.files[0]
if (file.size > maxImageSize) {
sizeMessage()
return
}
uploadFileResult(file, fileUrl => {
snapshotStore.recordSnapshotCache()
curComponent.value.propValue.url = fileUrl
fileList.value = [{ url: imgUrlTrans(curComponent.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 toolTip = computed(() => {
return props.themes === 'dark' ? 'ndark' : 'dark'
})
watch(
() => curComponent.value.propValue.url,
() => {
init()
}
)
onMounted(() => {
init()
eventBus.on('uploadImg', goFile)
})
onBeforeUnmount(() => {
eventBus.off('uploadImg', goFile)
})
</script>
<template>
<div class="attr-list de-collapse-style">
<input
id="input"
ref="files"
type="file"
accept=".jpeg,.jpg,.png,.gif,.svg"
hidden
@click="
e => {
e.target.value = ''
}
"
@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-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>
</template>
<style lang="less" scoped>
.de-collapse-style {
:deep(.ed-collapse-item__header) {
height: 36px !important;
line-height: 36px !important;
font-size: 12px !important;
padding: 0 !important;
font-weight: 500 !important;
.ed-collapse-item__arrow {
margin: 0 6px 0 8px;
}
}
:deep(.ed-collapse-item__content) {
padding: 16px 8px 0;
}
:deep(.ed-form-item) {
display: block;
margin-bottom: 8px;
}
:deep(.ed-form-item__label) {
justify-content: flex-start;
}
}
.disabled :deep(.el-upload--picture-card) {
display: none;
}
.avatar-uploader :deep(.ed-upload) {
width: 80px;
height: 80px;
line-height: 90px;
}
.avatar-uploader :deep(.ed-upload-list li) {
width: 80px !important;
height: 80px !important;
}
:deep(.ed-upload--picture-card) {
background: #eff0f1;
border: 1px dashed #dee0e3;
border-radius: 4px;
.ed-icon {
color: #1f2329;
}
&:hover {
.ed-icon {
color: var(--ed-color-primary);
}
}
}
.img-area {
height: 80px;
width: 80px;
margin-top: 10px;
overflow: hidden;
&.img-area_dark {
:deep(.ed-upload-list__item).is-success {
border-color: #434343;
}
:deep(.ed-upload--picture-card) {
background: #373737;
border-color: #434343;
.ed-icon {
color: #ebebeb;
}
&:hover {
.ed-icon {
color: var(--ed-color-primary);
}
}
}
}
&.img-area_light {
:deep(.ed-upload-list__item).is-success {
border-color: #dee0e3;
}
}
}
.image-hint {
color: #8f959e;
size: 14px;
line-height: 22px;
font-weight: 400;
margin-top: 2px;
&.image-hint_dark {
color: #757575;
}
}
.re-update-span {
cursor: pointer;
color: var(--ed-color-primary);
size: 14px;
line-height: 22px;
font-weight: 400;
}
.pic-adaptor {
margin: 8px 0 16px 0;
:deep(.ed-form-item__content) {
margin-top: 8px !important;
}
}
.form-item-dark {
.ed-radio {
margin-right: 4px !important;
}
}
.drag-data {
padding-top: 8px;
padding-bottom: 16px;
.tree-btn {
width: 100%;
margin-top: 8px;
background: #fff;
height: 32px;
border-radius: 4px;
border: 1px solid #dcdfe6;
display: flex;
color: #cccccc;
align-items: center;
cursor: pointer;
justify-content: center;
font-size: 12px;
&.tree-btn--dark {
background: rgba(235, 235, 235, 0.05);
border-color: #5f5f5f;
}
&.active {
color: #3370ff;
border-color: #3370ff;
}
}
&.no-top-border {
border-top: none !important;
}
&.no-top-padding {
padding-top: 0 !important;
}
&:nth-child(n + 2) {
border-top: 1px solid @side-outline-border-color;
}
&:first-child {
border-top: none !important;
}
}
</style>

View File

@ -0,0 +1,90 @@
<template>
<div class="pic-main" @click="onPictureClick">
<img
draggable="false"
v-if="propValue['url']"
:style="imageAdapter"
:src="imgUrlTrans(propValue['url'])"
/>
<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 { imgUrlTrans } from '@/utils/imgUtils'
import eventBus from '@/utils/eventBus'
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
const dvMainStore = dvMainStoreWithOut()
const props = defineProps({
propValue: {
type: String,
required: true,
default: ''
},
element: {
type: Object,
default() {
return {
propValue: null
}
}
}
})
const { propValue, element } = toRefs(props)
const imageAdapter = computed(() => {
const style = {
position: 'relative',
width: '100%',
height: '100%'
}
if (element.value.style.adaptation === 'original') {
style.width = 'auto'
style.height = 'auto'
} else if (element.value.style.adaptation === 'equiratio') {
style.height = 'auto'
}
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')
})
}
</script>
<style lang="less" scoped>
.pic-main {
overflow: hidden;
width: 100%;
height: 100%;
cursor: pointer;
}
.pic-upload {
display: flex;
width: 100%;
height: 100%;
color: #5370af;
align-items: center;
justify-content: center;
}
</style>

View File

@ -35,6 +35,8 @@ import ScrollText from '@/custom-component/scroll-text/Component.vue'
import ScrollTextAttr from '@/custom-component/scroll-text/Attr.vue'
import PopArea from '@/custom-component/pop-area/Component.vue'
import PopAreaAttr from '@/custom-component/pop-area/Attr.vue'
import PictureGroup from '@/custom-component/picture-group/Component.vue'
import PictureGroupAttr from '@/custom-component/picture-group/Attr.vue'
export const componentsMap = {
VText: VText,
VQuery,
@ -72,7 +74,9 @@ export const componentsMap = {
ScrollText: ScrollText,
ScrollTextAttr: ScrollTextAttr,
PopArea: PopArea,
PopAreaAttr: PopAreaAttr
PopAreaAttr: PopAreaAttr,
PictureGroup: PictureGroup,
PictureGroupAttr: PictureGroupAttr
}
export default function findComponent(key) {

View File

@ -1508,14 +1508,14 @@ export const CHART_TYPE_CONFIGS = [
},
{
category: 'other',
title: '图片',
title: '图片',
display: 'hidden',
details: [
{
render: 'custom',
category: 'quota',
value: 'Picture',
title: '图片',
value: 'picture-group',
title: '图片',
icon: 'picture'
}
]

View File

@ -1,19 +0,0 @@
import { AbstractChartView, ChartLibraryType, ChartRenderType } from '../../types'
import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n()
/**
* 富文本图表
*/
export class PictureChartView extends AbstractChartView {
properties: EditorProperty[] = ['background-overall-component', 'border-style']
propertyInner: EditorPropertyInner = {
'background-overall-component': ['all'],
'border-style': ['all']
}
axis: AxisType[] = ['filter']
axisConfig: AxisConfig = {}
constructor() {
super(ChartRenderType.CUSTOM, ChartLibraryType.PICTURE, 'Picture')
}
}

View File

@ -0,0 +1,35 @@
import { AbstractChartView, ChartLibraryType, ChartRenderType } from '../../types'
import { useI18n } from '@/hooks/web/useI18n'
const { t } = useI18n()
/**
* 图片组图表
*/
export class PictureGroupView extends AbstractChartView {
properties: EditorProperty[] = [
'background-overall-component',
'border-style',
'threshold',
'function-cfg'
]
propertyInner: EditorPropertyInner = {
'background-overall-component': ['all'],
'border-style': ['all'],
threshold: ['tableThreshold'],
'function-cfg': ['emptyDataStrategy']
}
axis: AxisType[] = ['xAxis', 'yAxis', 'filter']
axisConfig: AxisConfig = {
xAxis: {
name: `${t('chart.dimension')}`,
type: 'd'
},
yAxis: {
name: `${t('chart.quota')}`,
type: 'q'
}
}
constructor() {
super(ChartRenderType.CUSTOM, ChartLibraryType.PICTURE_GROUP, 'picture-group')
}
}

View File

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

View File

@ -43,7 +43,7 @@ import { storeToRefs } from 'pinia'
import { checkAddHttp, setIdValueTrans } from '@/utils/canvasUtils'
import { Base64 } from 'js-base64'
import DeRichTextView from '@/custom-component/rich-text/DeRichTextView.vue'
import DePictureV2 from '@/custom-component/picture/Component.vue'
import DePictureGroup from '@/custom-component/picture-group/component.vue'
import ChartEmptyInfo from '@/views/chart/components/views/components/ChartEmptyInfo.vue'
import { snapshotStoreWithOut } from '@/store/modules/data-visualization/snapshot'
import { viewFieldTimeTrans } from '@/utils/viewUtils'
@ -897,11 +897,15 @@ const loadPluginCategory = data => {
@onJumpClick="jumpClick"
@resetLoading="() => (loading = false)"
/>
<de-picture-v2
<de-picture-group
v-else-if="showChartView(ChartLibraryType.PICTURE_GROUP)"
:themes="canvasStyleData.dashboard.themeColor"
ref="chartComponent"
:element="element"
:prop-value="element.propValue"
v-else-if="showChartView(ChartLibraryType.PICTURE)"
></de-picture-v2>
:active="active"
:show-position="showPosition"
>
</de-picture-group>
<de-rich-text-view
v-else-if="showChartView(ChartLibraryType.RICH_TEXT)"
:themes="canvasStyleData.dashboard.themeColor"