fix
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 17s
Docker Build & Deploy / Deploy to Production (push) Successful in 8s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 1s
Docker Build & Deploy / WeChat Notification (push) Successful in 1s
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 17s
Docker Build & Deploy / Deploy to Production (push) Successful in 8s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 1s
Docker Build & Deploy / WeChat Notification (push) Successful in 1s
This commit is contained in:
@@ -166,6 +166,7 @@
|
|||||||
type="bar"
|
type="bar"
|
||||||
:data="varianceChartData"
|
:data="varianceChartData"
|
||||||
:options="varianceChartOptions"
|
:options="varianceChartOptions"
|
||||||
|
:plugins="varianceChartPlugins"
|
||||||
class="chart-body variance-chart"
|
class="chart-body variance-chart"
|
||||||
:style="{ height: calculateChartHeight(budgets) + 'px' }"
|
:style="{ height: calculateChartHeight(budgets) + 'px' }"
|
||||||
/>
|
/>
|
||||||
@@ -447,6 +448,78 @@ const calculateChartHeight = (budgets) => {
|
|||||||
return calculatedHeight
|
return calculatedHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const varianceLabelPlugin = {
|
||||||
|
id: 'variance-label-plugin',
|
||||||
|
afterDatasetsDraw: (chart) => {
|
||||||
|
const dataset = chart.data?.datasets?.[0]
|
||||||
|
const metaData = dataset?._meta
|
||||||
|
if (!dataset || !metaData) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const meta = chart.getDatasetMeta(0)
|
||||||
|
if (!meta?.data) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { ctx, chartArea } = chart
|
||||||
|
const fontFamily = '"PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif'
|
||||||
|
ctx.save()
|
||||||
|
ctx.font = `12px ${fontFamily}`
|
||||||
|
ctx.textBaseline = 'middle'
|
||||||
|
|
||||||
|
meta.data.forEach((bar, index) => {
|
||||||
|
const item = metaData[index]
|
||||||
|
if (!item || item.value === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const label = formatVarianceLabelValue(item.value)
|
||||||
|
const textWidth = ctx.measureText(label).width
|
||||||
|
const position = bar.tooltipPosition ? bar.tooltipPosition() : { x: bar.x, y: bar.y }
|
||||||
|
const offset = 8
|
||||||
|
const isPositive = item.value > 0
|
||||||
|
ctx.fillStyle = getVarianceLabelColor(item.value)
|
||||||
|
let x = position.x + (isPositive ? offset : -offset)
|
||||||
|
const y = position.y
|
||||||
|
|
||||||
|
if (chartArea) {
|
||||||
|
const rightLimit = chartArea.right - 4
|
||||||
|
const leftLimit = chartArea.left + 4
|
||||||
|
if (isPositive && x + textWidth > rightLimit) {
|
||||||
|
x = rightLimit - textWidth
|
||||||
|
}
|
||||||
|
if (!isPositive && x - textWidth < leftLimit) {
|
||||||
|
x = leftLimit + textWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.textAlign = isPositive ? 'left' : 'right'
|
||||||
|
|
||||||
|
ctx.fillText(label, x, y)
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.restore()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const varianceChartPlugins = computed(() => [varianceLabelPlugin])
|
||||||
|
|
||||||
|
const formatVarianceLabelValue = (value) => {
|
||||||
|
const absValue = Math.abs(Math.round(value || 0))
|
||||||
|
return absValue.toLocaleString(undefined, {
|
||||||
|
minimumFractionDigits: 0,
|
||||||
|
maximumFractionDigits: 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const getVarianceLabelColor = (value) => {
|
||||||
|
if (props.activeTab === BudgetCategory.Expense) {
|
||||||
|
return value > 0 ? getCssVar('--van-danger-color') : getCssVar('--van-success-color')
|
||||||
|
}
|
||||||
|
return value > 0 ? getCssVar('--van-success-color') : getCssVar('--van-danger-color')
|
||||||
|
}
|
||||||
|
|
||||||
// 偏差分析图表数据
|
// 偏差分析图表数据
|
||||||
const varianceChartData = computed(() => {
|
const varianceChartData = computed(() => {
|
||||||
if (!props.budgets || props.budgets.length === 0) {
|
if (!props.budgets || props.budgets.length === 0) {
|
||||||
@@ -469,10 +542,19 @@ const varianceChartData = computed(() => {
|
|||||||
const monthlyData = data.filter((item) => item.type === BudgetPeriodType.Month)
|
const monthlyData = data.filter((item) => item.type === BudgetPeriodType.Month)
|
||||||
const annualData = data.filter((item) => item.type === BudgetPeriodType.Year)
|
const annualData = data.filter((item) => item.type === BudgetPeriodType.Year)
|
||||||
|
|
||||||
monthlyData.sort((a, b) => Math.abs(b.value) - Math.abs(a.value))
|
const sortByLimitAndRemaining = (a, b) => {
|
||||||
annualData.sort((a, b) => Math.abs(b.value) - Math.abs(a.value))
|
if (a.limit !== b.limit) {
|
||||||
|
return a.limit - b.limit
|
||||||
|
}
|
||||||
|
const remainingA = a.limit - a.current
|
||||||
|
const remainingB = b.limit - b.current
|
||||||
|
return remainingB - remainingA
|
||||||
|
}
|
||||||
|
|
||||||
const sortedData = [...annualData, ...monthlyData]
|
monthlyData.sort(sortByLimitAndRemaining)
|
||||||
|
annualData.sort(sortByLimitAndRemaining)
|
||||||
|
|
||||||
|
const sortedData = [...monthlyData, ...annualData]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
labels: sortedData.map((item) => item.name),
|
labels: sortedData.map((item) => item.name),
|
||||||
|
|||||||
@@ -267,6 +267,6 @@ const chartOptions = computed(() => {
|
|||||||
|
|
||||||
.trend-chart {
|
.trend-chart {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 190px;
|
height: 200px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -456,7 +456,5 @@ const chartOptions = computed(() => {
|
|||||||
.trend-chart {
|
.trend-chart {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 200px;
|
height: 200px;
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user