fix(workbench): replay profile radar animation
This commit is contained in:
@@ -6,7 +6,7 @@
|
|||||||
width="min(1040px, calc(100vw - 48px))"
|
width="min(1040px, calc(100vw - 48px))"
|
||||||
:show-close="false"
|
:show-close="false"
|
||||||
:lock-scroll="true"
|
:lock-scroll="true"
|
||||||
:destroy-on-close="false"
|
destroy-on-close
|
||||||
class="expense-profile-dialog"
|
class="expense-profile-dialog"
|
||||||
modal-class="expense-profile-dialog-overlay"
|
modal-class="expense-profile-dialog-overlay"
|
||||||
body-class="expense-profile-dialog-body"
|
body-class="expense-profile-dialog-body"
|
||||||
|
|||||||
@@ -3,12 +3,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, nextTick, onBeforeUnmount, onMounted, shallowRef, watch } from 'vue'
|
import { computed, shallowRef } from 'vue'
|
||||||
import { RadarChart as EChartsRadarChart } from 'echarts/charts'
|
import { RadarChart as EChartsRadarChart } from 'echarts/charts'
|
||||||
import { RadarComponent, TooltipComponent } from 'echarts/components'
|
import { RadarComponent, TooltipComponent } from 'echarts/components'
|
||||||
import { init, use } from 'echarts/core'
|
import { use } from 'echarts/core'
|
||||||
import { CanvasRenderer } from 'echarts/renderers'
|
import { CanvasRenderer } from 'echarts/renderers'
|
||||||
|
|
||||||
|
import { useEcharts } from '../../composables/useEcharts.js'
|
||||||
import { useThemeColors } from '../../composables/useThemeColors.js'
|
import { useThemeColors } from '../../composables/useThemeColors.js'
|
||||||
|
|
||||||
use([RadarComponent, EChartsRadarChart, TooltipComponent, CanvasRenderer])
|
use([RadarComponent, EChartsRadarChart, TooltipComponent, CanvasRenderer])
|
||||||
@@ -32,8 +33,6 @@ const props = defineProps({
|
|||||||
|
|
||||||
const themeColors = useThemeColors()
|
const themeColors = useThemeColors()
|
||||||
const chartElement = shallowRef(null)
|
const chartElement = shallowRef(null)
|
||||||
let chartInstance = null
|
|
||||||
let resizeObserver = null
|
|
||||||
|
|
||||||
const normalizedItems = computed(() =>
|
const normalizedItems = computed(() =>
|
||||||
props.items.map((item, index) => ({
|
props.items.map((item, index) => ({
|
||||||
@@ -62,8 +61,11 @@ const chartOptions = computed(() => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
animationDuration: 760,
|
animation: true,
|
||||||
|
animationDuration: 980,
|
||||||
|
animationDurationUpdate: 760,
|
||||||
animationEasing: 'cubicOut',
|
animationEasing: 'cubicOut',
|
||||||
|
animationEasingUpdate: 'cubicOut',
|
||||||
color: [primary],
|
color: [primary],
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'item',
|
trigger: 'item',
|
||||||
@@ -123,6 +125,11 @@ const chartOptions = computed(() => {
|
|||||||
{
|
{
|
||||||
name: props.label,
|
name: props.label,
|
||||||
type: 'radar',
|
type: 'radar',
|
||||||
|
animation: true,
|
||||||
|
animationDuration: 980,
|
||||||
|
animationDurationUpdate: 760,
|
||||||
|
animationEasing: 'cubicOut',
|
||||||
|
animationEasingUpdate: 'cubicOut',
|
||||||
symbol: 'circle',
|
symbol: 'circle',
|
||||||
symbolSize: 7,
|
symbolSize: 7,
|
||||||
data: [
|
data: [
|
||||||
@@ -169,56 +176,7 @@ const chartOptions = computed(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
useEcharts(chartElement, chartOptions)
|
||||||
renderChart()
|
|
||||||
bindResize()
|
|
||||||
})
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
unbindResize()
|
|
||||||
if (chartInstance) {
|
|
||||||
chartInstance.dispose()
|
|
||||||
chartInstance = null
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(chartOptions, () => {
|
|
||||||
nextTick(renderChart)
|
|
||||||
}, { deep: true })
|
|
||||||
|
|
||||||
function renderChart() {
|
|
||||||
if (!chartElement.value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!chartInstance) {
|
|
||||||
chartInstance = init(chartElement.value, null, { renderer: 'canvas' })
|
|
||||||
}
|
|
||||||
chartInstance.setOption(chartOptions.value, true)
|
|
||||||
chartInstance.resize()
|
|
||||||
}
|
|
||||||
|
|
||||||
function bindResize() {
|
|
||||||
if (!chartElement.value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (typeof ResizeObserver !== 'undefined') {
|
|
||||||
resizeObserver = new ResizeObserver(() => {
|
|
||||||
chartInstance?.resize()
|
|
||||||
})
|
|
||||||
resizeObserver.observe(chartElement.value)
|
|
||||||
}
|
|
||||||
window.addEventListener('resize', handleResize)
|
|
||||||
}
|
|
||||||
|
|
||||||
function unbindResize() {
|
|
||||||
resizeObserver?.disconnect()
|
|
||||||
resizeObserver = null
|
|
||||||
window.removeEventListener('resize', handleResize)
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleResize() {
|
|
||||||
chartInstance?.resize()
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatTooltip() {
|
function formatTooltip() {
|
||||||
const rows = normalizedItems.value.map((item) => (
|
const rows = normalizedItems.value.map((item) => (
|
||||||
|
|||||||
33
web/tests/expense-profile-detail-modal.test.mjs
Normal file
33
web/tests/expense-profile-detail-modal.test.mjs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import { readFileSync } from 'node:fs'
|
||||||
|
import test from 'node:test'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
|
||||||
|
const modal = readFileSync(
|
||||||
|
fileURLToPath(new URL('../src/components/business/ExpenseProfileDetailModal.vue', import.meta.url)),
|
||||||
|
'utf8'
|
||||||
|
)
|
||||||
|
|
||||||
|
const radarChart = readFileSync(
|
||||||
|
fileURLToPath(new URL('../src/components/charts/RadarChart.vue', import.meta.url)),
|
||||||
|
'utf8'
|
||||||
|
)
|
||||||
|
|
||||||
|
test('expense profile modal remounts the behavior radar when opened', () => {
|
||||||
|
assert.match(modal, /destroy-on-close/)
|
||||||
|
assert.match(modal, /<RadarChart/)
|
||||||
|
assert.match(modal, /:key="radarRenderKey"/)
|
||||||
|
assert.match(modal, /const radarRenderKey = ref\(0\)/)
|
||||||
|
assert.match(modal, /watch\([\s\S]*\(\) => props\.visible[\s\S]*radarRenderKey\.value \+= 1/)
|
||||||
|
assert.match(modal, /scheduleRadarFrame/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('radar chart uses the shared echarts lifecycle and enables entrance animation', () => {
|
||||||
|
assert.match(radarChart, /import \{ useEcharts \} from '\.\.\/\.\.\/composables\/useEcharts\.js'/)
|
||||||
|
assert.match(radarChart, /useEcharts\(chartElement, chartOptions\)/)
|
||||||
|
assert.match(radarChart, /animation: true/)
|
||||||
|
assert.match(radarChart, /animationDuration: 980/)
|
||||||
|
assert.match(radarChart, /animationDurationUpdate: 760/)
|
||||||
|
assert.match(radarChart, /animationEasing: 'cubicOut'/)
|
||||||
|
assert.doesNotMatch(radarChart, /import \{[^}]*init[^}]*\} from 'echarts\/core'/)
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user