[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> ``` 目前結果 ![image](https://hackmd.io/_uploads/rJ8IKAOree.png)