[TOC]
# DAY20(2025/07/03)Vue+Highchart
## 進度日誌
- 更改圖表功能設計
## 筆記區📘 Vue3
### 更改圖表功能設計
改成「暫存篩選條件 → 確認送出後才刷新所有圖表」的設計
GenderAgeBar.vue
```html
<template>
<div>
<h2>性別與年齡分布</h2>
<highcharts :options="chartOptions" />
</div>
</template>
<script setup>
import { ref, onMounted, watch, inject } from 'vue'
// *** 這邊多 inject 一個 pendingFilters ***
const filters = inject('filters')
const pendingFilters = inject('pendingFilters')
const chartOptions = ref({
chart: { type: 'bar', height: 400 },
title: { text: null },
xAxis: { categories: ['<=20', '21~30', '31~40', '41~50', '51~60', '>=61', '未填寫'], title: { text: '年齡' } },
yAxis: { min: 0, title: { text: '人數', align: 'high' } },
legend: { reversed: true },
plotOptions: {
series: {
stacking: 'normal',
cursor: 'pointer',
point: {
events: {
click: function () {
// 點 bar -> 只動 pendingFilters
const age = this.category
const gender = this.series.name
// 年齡多選
const ageIdx = pendingFilters.age.indexOf(age)
if (ageIdx === -1) pendingFilters.age.push(age)
else pendingFilters.age.splice(ageIdx, 1)
// 性別多選
const genderIdx = pendingFilters.gender.indexOf(gender)
if (genderIdx === -1) pendingFilters.gender.push(gender)
else pendingFilters.gender.splice(genderIdx, 1)
}
}
}
}
},
series: []
})
let rawData = null
onMounted(async () => {
const res = await fetch('/persona_target_guid.json')
const json = await res.json()
rawData = json.guid_data.find(e => e.類型 === '性別與年齡')
updateChart()
})
function updateChart() {
if (!rawData) return
const categories = ['<=20', '21~30', '31~40', '41~50', '51~60', '>=61', '未填寫']
// 只根據 filters(正式條件)做圖表資料
const genders = filters && filters.gender.length ? filters.gender : ['男', '女', '未填寫']
const ages = filters && filters.age.length ? filters.age : categories
chartOptions.value.series = genders.map(gender => ({
name: gender,
data: ages.map(k => Number(rawData[`${gender}_${k}`] || 0))
}))
chartOptions.value.xAxis.categories = ages
}
// 監聽 filters 變化,才會觸發圖表刷新
watch(
() => [filters.gender.slice(), filters.age.slice()],
updateChart,
{ deep: true }
)
</script>
```
filters.js
```js
// src/filters.js
import { reactive } from 'vue'
export const pendingFilters = reactive({
gender: [],
age: [],
// 其他條件...
})
export const filters = reactive({
gender: [],
age: [],
// 其他條件...
})
````
App.vue
```html
<script setup>
import { provide } from 'vue'
import { filters, pendingFilters } from './filters'
import GenderAgeBar from './components/GenderAgeBar.vue'
provide('filters', filters)
provide('pendingFilters', pendingFilters)
function toggleGender(gender) {
const idx = pendingFilters.gender.indexOf(gender)
if (idx >= 0) pendingFilters.gender.splice(idx, 1)
else pendingFilters.gender.push(gender)
}
function applyFilters() {
filters.gender = pendingFilters.gender.slice()
filters.age = pendingFilters.age.slice()
}
function resetFilters() {
pendingFilters.gender = []
pendingFilters.age = []
filters.gender = []
filters.age = []
}
</script>
<template>
<div>
<button @click="toggleGender('男')">男</button>
<button @click="toggleGender('女')">女</button>
<button @click="toggleGender('未填寫')">未填寫</button>
<!-- 篩選與重置按鈕 -->
<button @click="applyFilters">篩選</button>
<button @click="resetFilters">重置</button>
<!-- 顯示暫存選項,讓用戶看見目前勾了什麼 -->
<div>
<span v-for="g in pendingFilters.gender" :key="g">{{ g }} ×</span>
<span v-for="a in pendingFilters.age" :key="a">{{ a }} ×</span>
</div>
</div>
<GenderAgeBar />
</template>
```
目前結果
