Merge branch 'dev-v2' into pr@dev-v2@refactor_workbranc

This commit is contained in:
王嘉豪 2024-03-29 16:59:31 +08:00 committed by GitHub
commit 81df4a1073
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 565 additions and 72 deletions

View File

@ -109,6 +109,7 @@ public class MapManage {
@CacheEvict(cacheNames = WORLD_MAP_CACHE, key = "'world_map'") @CacheEvict(cacheNames = WORLD_MAP_CACHE, key = "'world_map'")
@Transactional @Transactional
public void saveMapGeo(GeometryNodeCreator request, MultipartFile file) { public void saveMapGeo(GeometryNodeCreator request, MultipartFile file) {
validateCode(request.getCode());
if (ObjectUtils.isEmpty(file) || file.isEmpty()) { if (ObjectUtils.isEmpty(file) || file.isEmpty()) {
DEException.throwException("geometry file is require"); DEException.throwException("geometry file is require");
} }
@ -154,6 +155,7 @@ public class MapManage {
@CacheEvict(cacheNames = WORLD_MAP_CACHE, key = "'world_map'") @CacheEvict(cacheNames = WORLD_MAP_CACHE, key = "'world_map'")
@Transactional @Transactional
public void deleteGeo(String code) { public void deleteGeo(String code) {
validateCode(code);
if (!StringUtils.startsWith(code, GEO_PREFIX)) { if (!StringUtils.startsWith(code, GEO_PREFIX)) {
DEException.throwException("内置Geometry禁止删除"); DEException.throwException("内置Geometry禁止删除");
} }
@ -209,5 +211,20 @@ public class MapManage {
return code.substring(0, 3); return code.substring(0, 3);
} }
public void validateCode(String code) {
if (StringUtils.isBlank(code)) DEException.throwException("区域编码不能为空");
String busiGeoCode = getBusiGeoCode(code);
if (!isNumeric(busiGeoCode)) {
DEException.throwException("有效区域编码只能是数字");
}
}
public boolean isNumeric(String str) {
for (int i = str.length(); --i >= 0; ) {
int chr = str.charAt(i);
if (chr < 48 || chr > 57)
return false;
}
return true;
}
} }

View File

@ -24,7 +24,7 @@
"axios": "^1.3.3", "axios": "^1.3.3",
"crypto-js": "^4.1.1", "crypto-js": "^4.1.1",
"dayjs": "^1.11.9", "dayjs": "^1.11.9",
"element-plus-secondary": "^0.5.3", "element-plus-secondary": "^0.5.4",
"element-resize-detector": "^1.2.4", "element-resize-detector": "^1.2.4",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"html-to-image": "^1.11.11", "html-to-image": "^1.11.11",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -92,7 +92,9 @@ const navigate = computed(() => appearanceStore.getNavigate)
border-color: #1f232926 !important; border-color: #1f232926 !important;
} }
.system, .system {
color: #000 !important;
}
.de-logo { .de-logo {
color: #3371ff !important; color: #3371ff !important;
} }

View File

@ -642,6 +642,7 @@ export default {
table_header_font_color: '表头字体', table_header_font_color: '表头字体',
table_item_font_color: '表格字体', table_item_font_color: '表格字体',
table_show_index: '显示序号', table_show_index: '显示序号',
table_header_sort: '开启表头排序',
stripe: '斑马纹', stripe: '斑马纹',
start_angle: '起始角度', start_angle: '起始角度',
end_angle: '结束角度', end_angle: '结束角度',

View File

@ -221,6 +221,10 @@ declare interface ChartTableHeaderAttr {
* 序号表头名称 * 序号表头名称
*/ */
indexLabel: string indexLabel: string
/**
* 表头排序开关
*/
tableHeaderSort: boolean
} }
/** /**
* 单元格属性 * 单元格属性

View File

@ -3,6 +3,7 @@ import '../../assets/font/index.css'
import '@/style/index.less' import '@/style/index.less'
import '@/plugins/svg-icon' import '@/plugins/svg-icon'
import 'normalize.css/normalize.css' import 'normalize.css/normalize.css'
import '@antv/s2/dist/style.min.css'
import App from './App.vue' import App from './App.vue'
import { setupI18n } from '@/plugins/vue-i18n' import { setupI18n } from '@/plugins/vue-i18n'
import { setupStore } from '@/store' import { setupStore } from '@/store'

View File

@ -1,6 +1,7 @@
import '@/style/index.less' import '@/style/index.less'
import '@/plugins/svg-icon' import '@/plugins/svg-icon'
import 'normalize.css/normalize.css' import 'normalize.css/normalize.css'
import '@antv/s2/dist/style.min.css'
import { setupI18n } from '@/plugins/vue-i18n' import { setupI18n } from '@/plugins/vue-i18n'
import { setupStore } from '@/store' import { setupStore } from '@/store'
import { setupElementPlus } from '@/plugins/element-plus' import { setupElementPlus } from '@/plugins/element-plus'

View File

@ -3,6 +3,7 @@ import '../../assets/font/index.css'
import '@/style/index.less' import '@/style/index.less'
import '@/plugins/svg-icon' import '@/plugins/svg-icon'
import 'normalize.css/normalize.css' import 'normalize.css/normalize.css'
import '@antv/s2/dist/style.min.css'
import App from './App.vue' import App from './App.vue'
import { setupI18n } from '@/plugins/vue-i18n' import { setupI18n } from '@/plugins/vue-i18n'
import { setupStore } from '@/store' import { setupStore } from '@/store'

View File

@ -58,6 +58,7 @@ import '../../assets/font/index.css'
import '@/style/index.less' import '@/style/index.less'
import '@/plugins/svg-icon' import '@/plugins/svg-icon'
import 'normalize.css/normalize.css' import 'normalize.css/normalize.css'
import '@antv/s2/dist/style.min.css'
import AppElement from './App.vue' import AppElement from './App.vue'
import { setupI18n } from '@/plugins/vue-i18n' import { setupI18n } from '@/plugins/vue-i18n'
import { setupStore } from '@/store' import { setupStore } from '@/store'

View File

@ -236,6 +236,21 @@ onMounted(() => {
@blur="changeTableHeader('indexLabel')" @blur="changeTableHeader('indexLabel')"
/> />
</el-form-item> </el-form-item>
<el-form-item
:label="t('chart.table_show_index')"
class="form-item"
:class="'form-item-' + themes"
v-if="showProperty('tableHeaderSort')"
>
<el-checkbox
size="small"
:effect="themes"
v-model="state.tableHeaderForm.tableHeaderSort"
@change="changeTableHeader('tableHeaderSort')"
>
{{ t('chart.table_header_sort') }}
</el-checkbox>
</el-form-item>
</el-form> </el-form>
</template> </template>

View File

@ -329,7 +329,8 @@ export const DEFAULT_TABLE_HEADER: ChartTableHeaderAttr = {
tableHeaderBgColor: '#6D9A49', tableHeaderBgColor: '#6D9A49',
tableHeaderFontColor: '#000000', tableHeaderFontColor: '#000000',
tableTitleFontSize: 12, tableTitleFontSize: 12,
tableTitleHeight: 36 tableTitleHeight: 36,
tableHeaderSort: false
} }
export const DEFAULT_TABLE_CELL: ChartTableCellAttr = { export const DEFAULT_TABLE_CELL: ChartTableCellAttr = {
tableFontColor: '#000000', tableFontColor: '#000000',

View File

@ -127,37 +127,6 @@ export class Waterfall extends G2PlotChartView<WaterfallOptions, G2Waterfall> {
fill: setGradientColor(hexColorToRGBA(totalColorRgba, alpha), gradient, 270) fill: setGradientColor(hexColorToRGBA(totalColorRgba, alpha), gradient, 270)
} }
}, },
legend: {
items: [
{
name: '增加',
value: '',
marker: {
style: {
fill: setGradientColor(hexColorToRGBA(risingColorRgba, alpha), gradient, 270)
}
}
},
{
name: '减少',
value: '',
marker: {
style: {
fill: setGradientColor(hexColorToRGBA(fallingColorRgba, alpha), gradient, 270)
}
}
},
{
name: '合计',
value: '',
marker: {
style: {
fill: setGradientColor(hexColorToRGBA(totalColorRgba, alpha), gradient, 270)
}
}
}
]
},
risingFill: setGradientColor(hexColorToRGBA(risingColorRgba, alpha), gradient, 270), risingFill: setGradientColor(hexColorToRGBA(risingColorRgba, alpha), gradient, 270),
fallingFill: setGradientColor(hexColorToRGBA(fallingColorRgba, alpha), gradient, 270) fallingFill: setGradientColor(hexColorToRGBA(fallingColorRgba, alpha), gradient, 270)
} }
@ -256,9 +225,55 @@ export class Waterfall extends G2PlotChartView<WaterfallOptions, G2Waterfall> {
} }
} }
protected configLegend(chart: Chart, options: WaterfallOptions): WaterfallOptions {
const tmp = super.configLegend(chart, options)
if (!tmp.legend) {
return tmp
}
const customAttr = parseJson(chart.customAttr)
const { colors, gradient, alpha } = customAttr.basicStyle
const [risingColorRgba, fallingColorRgba, totalColorRgba] = colors
return {
...tmp,
legend: {
...tmp.legend,
items: [
{
name: '增加',
value: '',
marker: {
style: {
fill: setGradientColor(hexColorToRGBA(risingColorRgba, alpha), gradient, 270)
}
}
},
{
name: '减少',
value: '',
marker: {
style: {
fill: setGradientColor(hexColorToRGBA(fallingColorRgba, alpha), gradient, 270)
}
}
},
{
name: '合计',
value: '',
marker: {
style: {
fill: setGradientColor(hexColorToRGBA(totalColorRgba, alpha), gradient, 270)
}
}
}
]
}
}
}
protected setupOptions(chart: Chart, options: WaterfallOptions): WaterfallOptions { protected setupOptions(chart: Chart, options: WaterfallOptions): WaterfallOptions {
return flow( return flow(
this.configTheme, this.configTheme,
this.configLegend,
this.configBasicStyle, this.configBasicStyle,
this.configLabel, this.configLabel,
this.configTooltip, this.configTooltip,

View File

@ -91,9 +91,6 @@ export class BubbleMap extends L7PlotChartView<ChoroplethOptions, Choropleth> {
active: { stroke: 'green', lineWidth: 1 } active: { stroke: 'green', lineWidth: 1 }
}, },
tooltip: {}, tooltip: {},
zoom: {
position: 'bottomright'
},
legend: false, legend: false,
// 禁用线上地图数据 // 禁用线上地图数据
customFetchGeoData: () => null customFetchGeoData: () => null
@ -101,6 +98,7 @@ export class BubbleMap extends L7PlotChartView<ChoroplethOptions, Choropleth> {
options = this.setupOptions(chart, options, drawOption, geoJson) options = this.setupOptions(chart, options, drawOption, geoJson)
const view = new Choropleth(container, options) const view = new Choropleth(container, options)
const dotLayer = this.getDotLayer(chart, geoJson, drawOption) const dotLayer = this.getDotLayer(chart, geoJson, drawOption)
this.configZoomButton(view)
view.once('loaded', () => { view.once('loaded', () => {
view.addLayer(dotLayer) view.addLayer(dotLayer)
view.on('fillAreaLayer:click', (ev: MapMouseEvent) => { view.on('fillAreaLayer:click', (ev: MapMouseEvent) => {

View File

@ -90,9 +90,6 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
active: { stroke: 'green', lineWidth: 1 } active: { stroke: 'green', lineWidth: 1 }
}, },
tooltip: {}, tooltip: {},
zoom: {
position: 'bottomright'
},
legend: { legend: {
position: 'bottomleft' position: 'bottomleft'
}, },
@ -101,6 +98,7 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
} }
options = this.setupOptions(chart, options, drawOption, geoJson) options = this.setupOptions(chart, options, drawOption, geoJson)
const view = new Choropleth(container, options) const view = new Choropleth(container, options)
this.configZoomButton(view)
view.once('loaded', () => { view.once('loaded', () => {
view.on('fillAreaLayer:click', (ev: MapMouseEvent) => { view.on('fillAreaLayer:click', (ev: MapMouseEvent) => {
const data = ev.feature.properties const data = ev.feature.properties

View File

@ -16,6 +16,10 @@ export class TableInfo extends S2ChartView<TableSheet> {
properties = TABLE_EDITOR_PROPERTY properties = TABLE_EDITOR_PROPERTY
propertyInner = { propertyInner = {
...TABLE_EDITOR_PROPERTY_INNER, ...TABLE_EDITOR_PROPERTY_INNER,
'table-header-selector': [
...TABLE_EDITOR_PROPERTY_INNER['table-header-selector'],
'tableHeaderSort'
],
'basic-style-selector': [ 'basic-style-selector': [
'tableColumnMode', 'tableColumnMode',
'tableBorderColor', 'tableBorderColor',
@ -101,7 +105,10 @@ export class TableInfo extends S2ChartView<TableSheet> {
height: containerDom.offsetHeight, height: containerDom.offsetHeight,
showSeriesNumber: customAttr.tableHeader.showIndex, showSeriesNumber: customAttr.tableHeader.showIndex,
style: this.configStyle(chart), style: this.configStyle(chart),
conditions: this.configConditions(chart) conditions: this.configConditions(chart),
tooltip: {
getContainer: () => containerDom
}
} }
// 开启序号之后第一列就是序号列修改 label 即可 // 开启序号之后第一列就是序号列修改 label 即可
if (s2Options.showSeriesNumber) { if (s2Options.showSeriesNumber) {
@ -128,6 +135,8 @@ export class TableInfo extends S2ChartView<TableSheet> {
} }
// tooltip // tooltip
this.configTooltip(s2Options) this.configTooltip(s2Options)
// header interaction
this.configHeaderInteraction(chart, s2Options)
// 开始渲染 // 开始渲染
const newChart = new TableSheet(containerDom, s2DataConfig, s2Options) const newChart = new TableSheet(containerDom, s2DataConfig, s2Options)

View File

@ -13,7 +13,13 @@ const { t } = useI18n()
*/ */
export class TableNormal extends S2ChartView<TableSheet> { export class TableNormal extends S2ChartView<TableSheet> {
properties = TABLE_EDITOR_PROPERTY properties = TABLE_EDITOR_PROPERTY
propertyInner = TABLE_EDITOR_PROPERTY_INNER propertyInner = {
...TABLE_EDITOR_PROPERTY_INNER,
'table-header-selector': [
...TABLE_EDITOR_PROPERTY_INNER['table-header-selector'],
'tableHeaderSort'
]
}
axis: AxisType[] = ['xAxis', 'yAxis', 'drill', 'filter'] axis: AxisType[] = ['xAxis', 'yAxis', 'drill', 'filter']
axisConfig: AxisConfig = { axisConfig: AxisConfig = {
xAxis: { xAxis: {
@ -102,7 +108,10 @@ export class TableNormal extends S2ChartView<TableSheet> {
height: containerDom.offsetHeight, height: containerDom.offsetHeight,
showSeriesNumber: customAttr.tableHeader.showIndex, showSeriesNumber: customAttr.tableHeader.showIndex,
style: this.configStyle(chart), style: this.configStyle(chart),
conditions: this.configConditions(chart) conditions: this.configConditions(chart),
tooltip: {
getContainer: () => containerDom
}
} }
// 开启序号之后第一列就是序号列修改 label 即可 // 开启序号之后第一列就是序号列修改 label 即可
if (s2Options.showSeriesNumber) { if (s2Options.showSeriesNumber) {
@ -122,6 +131,8 @@ export class TableNormal extends S2ChartView<TableSheet> {
} }
// tooltip // tooltip
this.configTooltip(s2Options) this.configTooltip(s2Options)
// header interaction
this.configHeaderInteraction(chart, s2Options)
// 开始渲染 // 开始渲染
const newChart = new TableSheet(containerDom, s2DataConfig, s2Options) const newChart = new TableSheet(containerDom, s2DataConfig, s2Options)

View File

@ -83,7 +83,7 @@ export class TablePivot extends S2ChartView<PivotSheet> {
columns.push(ele.dataeaseName) columns.push(ele.dataeaseName)
meta.push({ meta.push({
field: ele.dataeaseName, field: ele.dataeaseName,
name: ele.name, name: ele.chartShowName ?? ele.name,
formatter: value => { formatter: value => {
if (!f) { if (!f) {
return value return value

View File

@ -21,6 +21,11 @@ import {
LIST_CLASS LIST_CLASS
} from '@antv/l7plot-component/dist/esm/legend/category/constants' } from '@antv/l7plot-component/dist/esm/legend/category/constants'
import substitute from '@antv/util/esm/substitute' import substitute from '@antv/util/esm/substitute'
import { Plot as L7Plot } from '@antv/l7plot/dist/esm/core/plot'
import type { PlotOptions } from '@antv/l7plot/dist/esm/types'
import { Zoom } from '@antv/l7'
import { createL7Icon } from '@antv/l7-component/es/utils/icon'
import { DOM } from '@antv/l7-utils'
export function getPadding(chart: Chart): number[] { export function getPadding(chart: Chart): number[] {
if (chart.drill) { if (chart.drill) {
@ -814,3 +819,47 @@ export function configL7Legend(): LegendOptions {
} }
} }
} }
class CustomZoom extends Zoom {
resetButtonGroup(container) {
DOM.clearChildren(container)
this['zoomInButton'] = this['createButton'](
this.controlOption.zoomInText,
this.controlOption.zoomInTitle,
'l7-button-control',
container,
this.zoomIn
)
const resetBtnIconText = createL7Icon('l7-icon-round')
this['createButton'](resetBtnIconText, 'Reset', 'l7-button-control', container, () => {
this.mapsService.setZoomAndCenter(
this.controlOption['initZoom'],
this.controlOption['center']
)
})
if (this.controlOption.showZoom) {
this['zoomNumDiv'] = this['createButton'](
'0',
'',
'l7-button-control l7-control-zoom__number',
container
)
}
this['zoomOutButton'] = this['createButton'](
this.controlOption.zoomOutText,
this.controlOption.zoomOutTitle,
'l7-button-control',
container,
this.zoomOut
)
this['updateDisabled']()
}
}
export function configL7Zoom(plot: L7Plot<PlotOptions>) {
plot.once('loaded', () => {
const zoomOptions = {
initZoom: plot.scene.getZoom(),
center: plot.scene.getCenter()
} as any
plot.scene.addControl(new CustomZoom(zoomOptions))
})
}

View File

@ -632,24 +632,60 @@ class SortTooltip extends BaseTooltip {
left: `${this.position?.x}px`, left: `${this.position?.x}px`,
top: `${this.position?.y}px`, top: `${this.position?.y}px`,
pointerEvents: enterable ? 'all' : 'none', pointerEvents: enterable ? 'all' : 'none',
zIndex: 9999 zIndex: 9999,
position: 'absolute'
}, },
visible: true visible: true
}) })
} }
} }
export function configTooltip(option: S2Options) { const SORT_DEFAULT =
'<svg t="1711681787276" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4355" width="200" height="200"><path d="M922.345786 372.183628l-39.393195 38.687114L676.138314 211.079416l0 683.909301-54.713113 0L621.425202 129.010259l53.320393 0L922.345786 372.183628zM349.254406 894.989741 101.654214 651.815349l39.393195-38.687114 206.814276 199.792349L347.861686 129.010259l54.713113 0 0 765.978459L349.254406 894.988718z" fill="{fill}" p-id="4356"></path></svg>'
const SORT_UP =
'<svg t="1711682928245" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11756" width="200" height="200"><path d="M960 704L512 256 64 704z" fill="{fill}" p-id="11757"></path></svg>'
const SORT_DOWN =
'<svg t="1711681879346" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4655" width="200" height="200"><path d="M64 320l448 448 448-448z" fill="{fill}" p-id="4656"></path></svg>'
function svg2Base64(svg) {
return `data:image/svg+xml;charset=utf-8;base64,${btoa(svg)}`
}
export function configHeaderInteraction(chart: Chart, option: S2Options) {
const { tableHeaderFontColor, tableHeaderSort } = parseJson(chart.customAttr).tableHeader
if (!tableHeaderSort) {
return
}
const iconColor = tableHeaderFontColor ?? '#666'
const sortDefault = svg2Base64(SORT_DEFAULT.replace('{fill}', iconColor))
const sortUp = svg2Base64(SORT_UP.replace('{fill}', iconColor))
const sortDown = svg2Base64(SORT_DOWN.replace('{fill}', iconColor))
// 防止缓存
const randomSuffix = Math.random()
const sortIconMap = { const sortIconMap = {
asc: 'SortUp', asc: `customSortUp${randomSuffix}`,
desc: 'SortDown' desc: `customSortDown${randomSuffix}`
}
option.tooltip = {
...option.tooltip,
renderTooltip: sheet => new SortTooltip(sheet)
} }
option.customSVGIcons = [
{
name: `customSortDefault${randomSuffix}`,
svg: sortDefault
},
{
name: `customSortUp${randomSuffix}`,
svg: sortUp
},
{
name: `customSortDown${randomSuffix}`,
svg: sortDown
}
]
option.headerActionIcons = [ option.headerActionIcons = [
{ {
iconNames: ['GroupAsc', 'SortUp', 'SortDown'], iconNames: [
`customSortDefault${randomSuffix}`,
`customSortUp${randomSuffix}`,
`customSortDown${randomSuffix}`
],
belongsCell: 'colCell', belongsCell: 'colCell',
displayCondition: (meta, iconName) => { displayCondition: (meta, iconName) => {
if (meta.field === SERIES_NUMBER_FIELD) { if (meta.field === SERIES_NUMBER_FIELD) {
@ -660,7 +696,7 @@ export function configTooltip(option: S2Options) {
if (sortType) { if (sortType) {
return iconName === sortIconMap[sortType] return iconName === sortIconMap[sortType]
} }
return iconName === 'GroupAsc' return iconName === `customSortDefault${randomSuffix}`
}, },
onClick: props => { onClick: props => {
const { meta, event } = props const { meta, event } = props
@ -677,6 +713,16 @@ export function configTooltip(option: S2Options) {
] ]
} }
export function configTooltip(option: S2Options) {
option.tooltip = {
...option.tooltip,
adjustPosition: ({ event }) => {
return getTooltipPosition(event)
},
renderTooltip: sheet => new SortTooltip(sheet)
}
}
export function copyContent(s2Instance, event, fieldMeta) { export function copyContent(s2Instance, event, fieldMeta) {
event.preventDefault() event.preventDefault()
const cell = s2Instance.getCell(event.target) const cell = s2Instance.getCell(event.target)
@ -706,3 +752,30 @@ export function copyContent(s2Instance, event, fieldMeta) {
copyString(content, true) copyString(content, true)
} }
} }
function getTooltipPosition(event) {
const s2Instance = event.s2Instance
const { x, y } = event
const result = { x: x + 15, y: y + 10 }
if (!s2Instance) {
return result
}
const { height, width } = s2Instance.getCanvasElement().getBoundingClientRect()
const { offsetHeight, offsetWidth } = s2Instance.tooltip.getContainer()
if (offsetWidth > width) {
result.x = 0
}
if (offsetHeight > height) {
result.y = 0
}
if (!(result.x || result.y)) {
return result
}
if (result.x && result.x + offsetWidth > width) {
result.x -= result.x + offsetWidth - width
}
if (result.y && result.y + offsetHeight > height) {
result.y -= offsetHeight + 15
}
return result
}

View File

@ -6,7 +6,8 @@ import {
configL7Label, configL7Label,
configL7Legend, configL7Legend,
configL7Style, configL7Style,
configL7Tooltip configL7Tooltip,
configL7Zoom
} from '@/views/chart/components/js/panel/common/common_antv' } from '@/views/chart/components/js/panel/common/common_antv'
import { import {
AntVAbstractChartView, AntVAbstractChartView,
@ -73,6 +74,10 @@ export abstract class L7PlotChartView<
options.source.data = data options.source.data = data
return options return options
} }
protected configZoomButton(plot: P) {
configL7Zoom(plot)
}
protected constructor(name: string, defaultData?: any[]) { protected constructor(name: string, defaultData?: any[]) {
super(ChartLibraryType.L7_PLOT, name) super(ChartLibraryType.L7_PLOT, name)
this.defaultData = defaultData this.defaultData = defaultData

View File

@ -5,6 +5,7 @@ import {
} from '@/views/chart/components/js/panel/types' } from '@/views/chart/components/js/panel/types'
import { S2Theme, SpreadSheet, Style, S2Options } from '@antv/s2' import { S2Theme, SpreadSheet, Style, S2Options } from '@antv/s2'
import { import {
configHeaderInteraction,
configTooltip, configTooltip,
getConditions, getConditions,
getCustomTheme, getCustomTheme,
@ -44,6 +45,10 @@ export abstract class S2ChartView<P extends SpreadSheet> extends AntVAbstractCha
configTooltip(option) configTooltip(option)
} }
protected configHeaderInteraction(chart: Chart, option: S2Options) {
configHeaderInteraction(chart, option)
}
protected configConditions(chart: Chart) { protected configConditions(chart: Chart) {
return getConditions(chart) return getConditions(chart)
} }

View File

@ -404,7 +404,7 @@ const autoHeightStyle = computed(() => {
@trackClick="trackClick" @trackClick="trackClick"
/> />
<div v-if="!isError" class="canvas-content"> <div v-if="!isError" class="canvas-content">
<div style="height: 100%" :id="containerId"></div> <div style="position: relative; height: 100%" :id="containerId"></div>
</div> </div>
<el-row :style="autoHeightStyle" v-if="showPage && !isError"> <el-row :style="autoHeightStyle" v-if="showPage && !isError">
<div :style="autoStyle" class="table-page-info"> <div :style="autoStyle" class="table-page-info">

View File

@ -208,8 +208,14 @@ const save = () => {
<span class="open-mobile">开启移动端</span> <span class="open-mobile">开启移动端</span>
<el-switch size="small" v-model="dvInfo.mobileLayout" /> <el-switch size="small" v-model="dvInfo.mobileLayout" />
<span class="open-mobile-line"></span> <span class="open-mobile-line"></span>
<el-tooltip effect="dark" content="切换至PC端布局" placement="bottom"> <el-tooltip
<el-icon @click="handleBack"> :show-arrow="false"
:offset="9"
effect="dark"
content="切换至PC端布局"
placement="bottom"
>
<el-icon @click="handleBack" class="switch-pc">
<Icon name="icon_pc_outlined" /> <Icon name="icon_pc_outlined" />
</el-icon> </el-icon>
</el-tooltip> </el-tooltip>
@ -296,7 +302,26 @@ const save = () => {
.mobile-save { .mobile-save {
display: flex; display: flex;
align-items: center; align-items: center;
.switch-pc {
&::after {
content: '';
border-radius: 4px;
display: none;
position: absolute;
width: calc(100% + 10px);
height: calc(100% + 10px);
top: -5px;
left: -5px;
}
&:hover {
&::after {
display: block;
background: rgba(255, 255, 255, 0.1);
}
}
position: relative;
}
.open-mobile-line { .open-mobile-line {
background: #ffffff4d; background: #ffffff4d;
width: 1px; width: 1px;
@ -349,7 +374,7 @@ const save = () => {
.mobile-header { .mobile-header {
margin-top: 20px; margin-top: 20px;
height: 44px; height: 43px;
display: flex; display: flex;
img { img {
height: 100%; height: 100%;

View File

@ -81,6 +81,13 @@ const dataClick = val => {
directId.value.push(val.id) directId.value.push(val.id)
} }
const handleDir = index => {
if (index === directId.value.length - 1) return
directId.value = directId.value.slice(0, index + 1)
directName.value = directName.value.slice(0, index + 1)
activeDirectName.value = directName.value[directName.value.length - 1]
}
const getTree = async () => { const getTree = async () => {
const request = { busiFlag: 'dashboard' } as BusiTreeRequest const request = { busiFlag: 'dashboard' } as BusiTreeRequest
await interactiveStore.setInteractive(request) await interactiveStore.setInteractive(request)
@ -127,8 +134,10 @@ onMounted(() => {
<Icon name="icon_right_outlined"></Icon> <Icon name="icon_right_outlined"></Icon>
</el-icon> </el-icon>
</div> </div>
<div v-for="(ele, index) in [...directName]" :key="ele"> <div v-for="(ele, index) in [...directName]" @click="handleDir(index)" :key="ele">
<span class="label">{{ ele }}</span> <span class="label ellipsis" :class="index !== directName.length - 1 && 'primary-name'">{{
ele
}}</span>
<el-icon v-if="index !== directName.length - 1"> <el-icon v-if="index !== directName.length - 1">
<Icon name="icon_right_outlined"></Icon> <Icon name="icon_right_outlined"></Icon>
</el-icon> </el-icon>
@ -168,16 +177,20 @@ onMounted(() => {
padding: 12px 16px; padding: 12px 16px;
color: #646a73; color: #646a73;
display: flex; display: flex;
width: 100%;
overflow-x: auto;
align-items: center; align-items: center;
& > div { & > div {
display: flex; display: flex;
align-items: center; align-items: center;
white-space: nowrap;
} }
.label { .label {
font-size: 14px; font-size: 14px;
font-weight: 400; font-weight: 400;
line-height: 20px; line-height: 20px;
max-width: 250px;
} }
.ed-icon { .ed-icon {

View File

@ -48,11 +48,47 @@ const findName = () => {
} }
} }
} }
let directIdCopy = []
let directNameCopy = []
const dfsOrgTree = (arr, depth) => {
arr.forEach(item => {
const { name, id } = item
if (depth <= directIdCopy.length) {
if (depth < directIdCopy.length) {
directIdCopy = directIdCopy.slice(0, depth)
directNameCopy = directNameCopy.slice(0, depth)
}
directIdCopy.splice(directIdCopy.length - 1, 1, id)
directNameCopy.splice(directNameCopy.length - 1, 1, name)
} else {
directIdCopy.push(id)
directNameCopy.push(name)
}
let nextDepth = depth + 1
if (id === userStore.getOid) {
directName.value = [...directNameCopy]
directId.value = [...directIdCopy]
nextDepth = 999
}
if (item?.children?.length && nextDepth !== 999) {
dfsOrgTree(item?.children, nextDepth)
}
})
}
onMounted(() => { onMounted(() => {
mountedOrg().then(res => { mountedOrg().then(res => {
orgOption = res.data as OrgTreeNode[] orgOption = res.data as OrgTreeNode[]
tableData.value = res.data as OrgTreeNode[] tableData.value = res.data as OrgTreeNode[]
findName() findName()
dfsOrgTree(orgOption, 1)
directName.value.pop()
directId.value.pop()
activeDirectName.value = directName.value[directName.value.length - 1]
}) })
}) })
@ -84,6 +120,13 @@ const orgCellClick = (type, val) => {
} }
} }
const handleDir = index => {
if (index === directId.value.length - 1) return
directId.value = directId.value.slice(0, index + 1)
directName.value = directName.value.slice(0, index + 1)
activeDirectName.value = directName.value[directName.value.length - 1]
}
const tableData = ref([]) const tableData = ref([])
const directName = ref([]) const directName = ref([])
const directId = ref([]) const directId = ref([])
@ -134,8 +177,13 @@ const activeTableData = computed(() => {
@click-left="onClickLeft" @click-left="onClickLeft"
/> />
<div class="grey"> <div class="grey">
<div class="flex-align-center" v-for="(ele, index) in directName" :key="ele"> <div
<span :class="ele !== activeDirectName && 'active'">{{ ele }}</span> @click="handleDir(index)"
class="flex-align-center"
v-for="(ele, index) in directName"
:key="ele"
>
<span class="ellipsis" :class="ele !== activeDirectName && 'active'">{{ ele }}</span>
<el-icon v-if="directName.length > 1 && index !== directName.length - 1"> <el-icon v-if="directName.length > 1 && index !== directName.length - 1">
<Icon name="icon_right_outlined"></Icon> <Icon name="icon_right_outlined"></Icon>
</el-icon> </el-icon>
@ -202,6 +250,14 @@ const activeTableData = computed(() => {
display: flex; display: flex;
align-items: center; align-items: center;
& > div {
white-space: nowrap;
}
.ellipsis {
max-width: 250px;
}
.active { .active {
color: var(--ed-color-primary); color: var(--ed-color-primary);
} }

View File

@ -230,8 +230,8 @@ watch(
align-items: center; align-items: center;
} }
.main-color { .main-color {
font-size: 21.33px; font-size: 18px;
padding: 5.33px; padding: 3px;
margin-right: 12px; margin-right: 12px;
border-radius: 4px; border-radius: 4px;
color: #fff; color: #fff;

View File

@ -1,6 +1,6 @@
<script lang="tsx" setup> <script lang="tsx" setup>
import { useI18n } from '@/hooks/web/useI18n' import { useI18n } from '@/hooks/web/useI18n'
import { ref, reactive, shallowRef, computed, watch, onBeforeMount, nextTick } from 'vue' import { ref, reactive, shallowRef, computed, watch, onBeforeMount, nextTick, unref } from 'vue'
import ArrowSide from '@/views/common/DeResourceArrow.vue' import ArrowSide from '@/views/common/DeResourceArrow.vue'
import { import {
ElIcon, ElIcon,
@ -25,6 +25,7 @@ import { save } from '@/api/visualization/dataVisualization'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
import { fieldType } from '@/utils/attr' import { fieldType } from '@/utils/attr'
import { useAppStoreWithOut } from '@/store/modules/app' import { useAppStoreWithOut } from '@/store/modules/app'
import treeSort from '@/utils/treeSortUtils'
import { import {
DEFAULT_CANVAS_STYLE_DATA_LIGHT, DEFAULT_CANVAS_STYLE_DATA_LIGHT,
@ -59,7 +60,8 @@ const router = useRouter()
const route = useRoute() const route = useRoute()
const { t } = useI18n() const { t } = useI18n()
const state = reactive({ const state = reactive({
datasetTree: [] as BusiTreeNode[] datasetTree: [] as BusiTreeNode[],
curSortType: 'time_desc'
}) })
const resourceGroupOpt = ref() const resourceGroupOpt = ref()
@ -79,6 +81,13 @@ const resourceOptFinish = param => {
} }
} }
let originResourceTree = []
const sortTypeChange = sortType => {
state.datasetTree = treeSort(originResourceTree, sortType)
state.curSortType = sortType
}
const resourceCreate = (pid, name) => { const resourceCreate = (pid, name) => {
// //
const newResourceId = guid() const newResourceId = guid()
@ -201,9 +210,11 @@ const getData = () => {
if (nodeData.length && nodeData[0]['id'] === '0' && nodeData[0]['name'] === 'root') { if (nodeData.length && nodeData[0]['id'] === '0' && nodeData[0]['name'] === 'root') {
rootManage.value = nodeData[0]['weight'] >= 7 rootManage.value = nodeData[0]['weight'] >= 7
state.datasetTree = nodeData[0]['children'] || [] state.datasetTree = nodeData[0]['children'] || []
originResourceTree = cloneDeep(unref(state.datasetTree))
return return
} }
state.datasetTree = nodeData state.datasetTree = nodeData
originResourceTree = cloneDeep(unref(state.datasetTree))
}) })
.finally(() => { .finally(() => {
dtLoading.value = false dtLoading.value = false
@ -439,6 +450,27 @@ const defaultTab = [
name: 'structPreview' name: 'structPreview'
} }
] ]
const sortList = [
{
name: '按创建时间升序',
value: 'time_asc'
},
{
name: '按创建时间降序',
value: 'time_desc',
divided: true
},
{
name: '按照名称升序',
value: 'name_asc'
},
{
name: '按照名称降序',
value: 'name_desc'
}
]
const tablePanes = ref([]) const tablePanes = ref([])
const tablePaneList = computed(() => { const tablePaneList = computed(() => {
return nodeInfo.weight >= 7 ? [...defaultTab, ...tablePanes.value] : [...defaultTab] return nodeInfo.weight >= 7 ? [...defaultTab, ...tablePanes.value] : [...defaultTab]
@ -545,6 +577,34 @@ const getMenuList = (val: boolean) => {
</el-icon> </el-icon>
</template> </template>
</el-input> </el-input>
<el-dropdown @command="sortTypeChange" trigger="click">
<el-icon class="insert-filter filter-icon-span">
<Icon
v-show="state.curSortType.includes('asc')"
name="dv-sort-asc"
class="opt-icon"
></Icon>
<Icon
v-show="state.curSortType.includes('desc')"
name="dv-sort-desc"
class="opt-icon"
></Icon>
</el-icon>
<template #dropdown>
<el-dropdown-menu style="width: 246px">
<template :key="ele.value" v-for="ele in sortList">
<el-dropdown-item
class="ed-select-dropdown__item"
:class="ele.value === state.curSortType && 'selected'"
:command="ele.value"
>
{{ ele.name }}
</el-dropdown-item>
<li v-if="ele.divided" class="ed-dropdown-menu__item--divided"></li>
</template>
</el-dropdown-menu>
</template>
</el-dropdown>
</div> </div>
<el-scrollbar class="custom-tree"> <el-scrollbar class="custom-tree">
@ -727,6 +787,43 @@ const getMenuList = (val: boolean) => {
<style lang="less" scoped> <style lang="less" scoped>
@import '@/style/mixin.less'; @import '@/style/mixin.less';
.insert-filter {
display: inline-block;
font-weight: 400 !important;
font-family: '阿里巴巴普惠体 3.0 55 Regular L3';
line-height: 1;
white-space: nowrap;
cursor: pointer;
color: var(--TextPrimary, #1f2329);
-webkit-appearance: none;
text-align: center;
box-sizing: border-box;
outline: 0;
margin: 0;
transition: 0.1s;
border-radius: 3px;
&:active {
color: #000;
border-color: #3a8ee6;
background-color: red;
outline: 0;
}
&:hover {
background-color: rgba(31, 35, 41, 0.1);
color: #3a8ee6;
}
}
.filter-icon-span {
border: 1px solid #dcdfe6;
width: 32px;
height: 32px;
border-radius: 4px;
padding: 7px;
margin-left: 8px;
}
.dataset-manage { .dataset-manage {
display: flex; display: flex;
width: 100%; width: 100%;
@ -786,6 +883,7 @@ const getMenuList = (val: boolean) => {
.search-bar { .search-bar {
padding-bottom: 10px; padding-bottom: 10px;
width: calc(100% - 40px);
} }
} }
} }

View File

@ -1,5 +1,5 @@
<script lang="tsx" setup> <script lang="tsx" setup>
import { computed, reactive, ref, shallowRef, nextTick, watch, onMounted } from 'vue' import { computed, unref, reactive, ref, shallowRef, nextTick, watch, onMounted } from 'vue'
import { dsTypes } from '@/views/visualized/data/datasource/form/option' import { dsTypes } from '@/views/visualized/data/datasource/form/option'
import type { TabPaneName, ElMessageBoxOptions } from 'element-plus-secondary' import type { TabPaneName, ElMessageBoxOptions } from 'element-plus-secondary'
import { ElIcon, ElMessageBox, ElMessage, ElScrollbar, ElAside } from 'element-plus-secondary' import { ElIcon, ElMessageBox, ElMessage, ElScrollbar, ElAside } from 'element-plus-secondary'
@ -41,6 +41,7 @@ import type { BusiTreeNode, BusiTreeRequest } from '@/models/tree/TreeNode'
import { useMoveLine } from '@/hooks/web/useMoveLine' import { useMoveLine } from '@/hooks/web/useMoveLine'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
import { interactiveStoreWithOut } from '@/store/modules/interactive' import { interactiveStoreWithOut } from '@/store/modules/interactive'
import treeSort from '@/utils/treeSortUtils'
const interactiveStore = interactiveStoreWithOut() const interactiveStore = interactiveStoreWithOut()
interface Field { interface Field {
fieldShortName: string fieldShortName: string
@ -61,6 +62,7 @@ const state = reactive({
pageSize: 10, pageSize: 10,
total: 0 total: 0
}, },
curSortType: 'time_desc',
filterTable: [] filterTable: []
}) })
@ -157,6 +159,12 @@ const selectDataset = row => {
}) })
} }
let originResourceTree = []
const sortTypeChange = sortType => {
state.datasourceTree = treeSort(originResourceTree, sortType)
state.curSortType = sortType
}
const handleSizeChange = pageSize => { const handleSizeChange = pageSize => {
state.paginationConfig.currentPage = 1 state.paginationConfig.currentPage = 1
state.paginationConfig.pageSize = pageSize state.paginationConfig.pageSize = pageSize
@ -382,8 +390,10 @@ const listDs = () => {
if (nodeData.length && nodeData[0]['id'] === '0' && nodeData[0]['name'] === 'root') { if (nodeData.length && nodeData[0]['id'] === '0' && nodeData[0]['name'] === 'root') {
rootManage.value = nodeData[0]['weight'] >= 7 rootManage.value = nodeData[0]['weight'] >= 7
state.datasourceTree = nodeData[0]['children'] || [] state.datasourceTree = nodeData[0]['children'] || []
originResourceTree = cloneDeep(unref(state.datasourceTree))
return return
} }
originResourceTree = cloneDeep(unref(state.datasourceTree))
state.datasourceTree = nodeData state.datasourceTree = nodeData
}) })
.finally(() => { .finally(() => {
@ -414,7 +424,25 @@ const dfsDatasourceTree = (ds, id) => {
listDs() listDs()
const creatDsFolder = ref() const creatDsFolder = ref()
const sortList = [
{
name: '按创建时间升序',
value: 'time_asc'
},
{
name: '按创建时间降序',
value: 'time_desc',
divided: true
},
{
name: '按照名称升序',
value: 'name_asc'
},
{
name: '按照名称降序',
value: 'name_desc'
}
]
const tableData = shallowRef([]) const tableData = shallowRef([])
const tabData = shallowRef([]) const tabData = shallowRef([])
const handleNodeClick = data => { const handleNodeClick = data => {
@ -752,6 +780,34 @@ const getMenuList = (val: boolean) => {
</el-icon> </el-icon>
</template> </template>
</el-input> </el-input>
<el-dropdown @command="sortTypeChange" trigger="click">
<el-icon class="insert-filter filter-icon-span">
<Icon
v-show="state.curSortType.includes('asc')"
name="dv-sort-asc"
class="opt-icon"
></Icon>
<Icon
v-show="state.curSortType.includes('desc')"
name="dv-sort-desc"
class="opt-icon"
></Icon>
</el-icon>
<template #dropdown>
<el-dropdown-menu style="width: 246px">
<template :key="ele.value" v-for="ele in sortList">
<el-dropdown-item
class="ed-select-dropdown__item"
:class="ele.value === state.curSortType && 'selected'"
:command="ele.value"
>
{{ ele.name }}
</el-dropdown-item>
<li v-if="ele.divided" class="ed-dropdown-menu__item--divided"></li>
</template>
</el-dropdown-menu>
</template>
</el-dropdown>
</div> </div>
<el-scrollbar @scroll="handleScroll" ref="scrollbarRef" class="custom-tree"> <el-scrollbar @scroll="handleScroll" ref="scrollbarRef" class="custom-tree">
<el-tree <el-tree
@ -1408,6 +1464,43 @@ const getMenuList = (val: boolean) => {
<style lang="less" scoped> <style lang="less" scoped>
@import '@/style/mixin.less'; @import '@/style/mixin.less';
.insert-filter {
display: inline-block;
font-weight: 400 !important;
font-family: '阿里巴巴普惠体 3.0 55 Regular L3';
line-height: 1;
white-space: nowrap;
cursor: pointer;
color: var(--TextPrimary, #1f2329);
-webkit-appearance: none;
text-align: center;
box-sizing: border-box;
outline: 0;
margin: 0;
transition: 0.1s;
border-radius: 3px;
&:active {
color: #000;
border-color: #3a8ee6;
background-color: red;
outline: 0;
}
&:hover {
background-color: rgba(31, 35, 41, 0.1);
color: #3a8ee6;
}
}
.filter-icon-span {
border: 1px solid #dcdfe6;
width: 32px;
height: 32px;
border-radius: 4px;
padding: 7px;
margin-left: 8px;
}
.datasource-manage { .datasource-manage {
display: flex; display: flex;
width: 100%; width: 100%;
@ -1467,6 +1560,7 @@ const getMenuList = (val: boolean) => {
.search-bar { .search-bar {
padding-bottom: 10px; padding-bottom: 10px;
width: calc(100% - 40px);
} }
} }
} }

View File

@ -359,7 +359,7 @@ const getEmptyDesc = (): string => {
> >
<el-icon <el-icon
class="hover-icon hover-icon-in-table" class="hover-icon hover-icon-in-table"
@click="executeCancelStore(scope.row)" @click.stop="executeCancelStore(scope.row)"
> >
<Icon name="icon_cancel_store"></Icon> <Icon name="icon_cancel_store"></Icon>
</el-icon> </el-icon>
@ -370,7 +370,7 @@ const getEmptyDesc = (): string => {
<el-tooltip effect="dark" content="打开数据集" placement="top"> <el-tooltip effect="dark" content="打开数据集" placement="top">
<el-icon <el-icon
class="hover-icon hover-icon-in-table" class="hover-icon hover-icon-in-table"
@click=" @click.stop="
openDataset(activeName === 'recent' ? scope.row.id : scope.row.resourceId) openDataset(activeName === 'recent' ? scope.row.id : scope.row.resourceId)
" "
> >

@ -1 +1 @@
Subproject commit 137b8218ee27ecdd58656076423ae1bde1596007 Subproject commit 299cd6287249a793abd799f39c40b45d01eb2336