iT邦幫忙

2025 iThome 鐵人賽

DAY 19
0
Vue.js

遊戲活動關卡查詢網站系列 第 19

遊戲活動關卡查詢網站Day19-我的最愛2(Vue:v-show)

  • 分享至 

  • xImage
  •  

目標
我的最愛功能:
讓使用者可以透過選單的方式,切換到「我的最愛」列表
並且我們將Supabase的資料過濾LocalStorage的資料
顯示在上面;

這個組件還會再分一篇講
這篇主要講的是存取與過濾資料
內容大多數之前的鐵人文章都有講過
請輕鬆閱讀即可。

步驟
1.

先看當初設計的線稿,F組件(我的最愛)是設計在底下
在選單中按下我的最愛時,會出現F組件
另外隱藏其他不必要的組件
所以在E組件的script 會先設計以下架構

<script setup>
/* global defineEmits */
import { ref } from "vue";
const emit = defineEmits(["response"]);

//..中間略
function displayEvent(name){

	const displayState = ({
		A_Display:true, //敵人資訊
		B_Display:true,//活動資訊
		C_Display:true,//副本資訊
		D_Display:false,//條件查詢
		E_Display:true,//選單
		F_Display:false//我的最愛
	})
	
	if(name == "活動一覽") { 
		displayState.B_Display = true; 
		displayState.C_Display = true; 
		displayState.D_Display = false; 
		displayState.F_Display = false; 
	}
	if(name == "我的最愛") { 
		displayState.B_Display = true; 
		displayState.F_Display = true; 
		displayState.C_Display = false; 
		displayState.D_Display = false; 
	}
	if(name == "條件查詢") { 
		displayState.D_Display = true; 
		displayState.B_Display = false; 
		displayState.C_Display = false; 
		displayState.F_Display = false; 
	}
	emit("response", displayState);
}
</script>

然後在template補上觸發事件
在分別點擊活動一覽、我的最愛、條件查詢時
會分別將特定屬性做切換
而這個東西 就靠之前說的emit傳遞給父組件

<div class="d-grid gap-2">
		<button type="button" class="btn btn-primary" v-for="item in menuList" :key="item.id"
		@click="displayEvent(item.name)">
			{{item.name}}
		</button>
	</div>

要在父組件(APP.vue)這邊接收E組件傳過來的變化前
首先要先在父組件的script做一個初始設定

<script setup>
//..以上略

const displayState = ref({
  A_Display:true,
  B_Display:true,
  C_Display:true,
  D_Display:false,
  E_Display:true,
  F_Display:false
});
</script>

然後在template上做判斷

<div class="row" >
	<div class = "col-6 " style="height:550px;">
		<A-enemy :msg="C_Msg" v-show="displayState.A_Display" />
	</div>
	<div class = "col-5 ">
			<div class="row" v-show="displayState.B_Display">
				<div class = "col-12 border" style="height:250px;overflow:auto;" >
					<B-action @response="(msg) => B_Msg = msg" />
				</div>
			</div>
			<div class="row" v-show="displayState.C_Display">
				<div class = "col-12 border" style="height:300px;overflow:auto;">
					<C-event :msg="B_Msg"  @response="(msg) => C_Msg = msg" />
				</div>
			</div>
			<div class="row" v-show="displayState.D_Display">
				<div class = "col-12 border" style="height:550px">
						<D-skillSearch />
				</div>
			</div>
			<div class="row" v-show="displayState.F_Display">
				<div class = "col-12 border" style="height:300px">
						<F-favoriteList />
				</div>
			</div>
	</div>
	<div class="col-1 border" v-show="displayState.E_Display" >
		<E-menu @response="(msg) => displayState = msg"/>
	</div>
</div>

與之前有差異的其實是在紅框這邊
v-show 加入判斷
當E組件(選單)點擊時,得以判斷哪些組件要顯示或隱藏

由於localStorage已經有了「我的最愛」資料
現在F組件還缺少從Supabase拿過來的資料
因為取資料這件事情已經在B組件做過了
所以這次一樣透過emit和props實現
在B組件中,會像下圖紅框這邊多傳一個emit給父組件
在一開始取Supabase的資料時 也同時傳一份過去

<script setup>
//...以上略
onMounted(async () => {
  const { data, error } = await supabase
    .from("action_event")
	.select("*")

  if (error) {
    console.error(error);
  } else {
    result.value = data;
	emit("responseBF", result.value);
  }
});
</script>

接下來在F組件的script為以下程式結構
裡面很多東西都是抄C組件裡的

/* global defineProps */
import { ref,onMounted,computed,watch } from "vue";

const images = require.context('@/assets/Event', false, /\.png$/)
const favoriteList = ref([]);
const props = defineProps({
  msg: Object
})

//若LocalStorage無資料 就新建
onMounted(() => {
  
  if (localStorage.getItem("favoriteList") == null) {
	console.log("建立最愛列表");
    localStorage.setItem("favoriteList", [])
  }
  else{
	favoriteList.value = JSON.parse(localStorage.getItem("favoriteList"))
  }
})

const favoriteFilterList = computed(() => {
	if (!Array.isArray(props.msg) || props.msg.length === 0) {
		return [];
	}
	

	const result = Object.values(
	props.msg.filter(x => favoriteList.value.some( y => x.actionNo + x.eventNo + x.stagePrimaryNo + x.stageSecNo + x.stageOrder == y.rrn))
		.reduce((acc, cur) => {
		const key = cur.eventNo;
		if (!acc[key]) {
			acc[key] = {
			eventNo: cur.eventNo,
			eventName: cur.eventName, 
			eventInfo: []
			};
		}
		
		const info = cur.stagePrimaryNo + cur.stageSecNo;
		if (!acc[key].eventInfo.includes(info)) {
		acc[key].eventInfo.push(info);
		}
		return acc;
	}, {})
	);
	
	return result;
})
function getImg(eventNo) {
  return images(`./E${eventNo}.png`)
}

</script>

template也幾乎照搬再做微調

<template>
我的最愛{{favoriteFilterList}}
<div class="row mb-5"  v-for="item in eventList" :key="item.eventNo">
		<div class="col-9">
			<img :src="getImg(item.eventNo)" style="width:100%;">
		</div>
		<div class="col-3">
			<div class="card" >
				<div class="card-header">
					{{item.eventName}}
				</div>
				<ul class="list-group list-group-flush">
					<li class="list-group-item" v-for="child in item.eventInfo" :key="child"
					:data-eventNo="item.eventNo" :data-stagePrimaryNo="child.substring(0,2)" 
					:data-stageSecNo="child.substring(2,4)" @click="selectEvent"
					>
					{{ String(parseInt(child.substring(0,2))).padStart(2, ' ')}}
						-
					{{ String(parseInt(child.substring(2,4))).padStart(2, ' ')}}
					</li>
				</ul>
			</div>
		</div>
	</div>
	
	
</template>

我們先來看一下線稿時做的規劃

再來回到畫面
當加入我的最愛後,F組件這邊能看到結果
而這篇我們做的就是將B組件傳過來的資料過濾
顯示在F組件 如下圖

但是,此時就會發生一個很嚴重的問題
當A組件加入到我的最愛後
A組件存LocalStorage,F組件並沒有同步到

只有在重新整理或透過手動操作,F組件才會重新抓localStorage
我們預期的結果應該是
A組件存LocalStorage,F組件也會同步到
但是實際發生的卻是下圖的情況
A組件存、讀取的localStorage實例對象 和F組件不是同一個
我們將在下一篇處理這件事,將F組件的篇章作個了結

備註
1.
v-showv-if 是有差異的
在官方文件中有提到

當 v-if 元素被觸發,元素及其包含的指令/組件都被銷毀和重構。

也就是說 如果這邊改用v-if後
因為A組件載入時,會從Supabase拿資料
此時,我們切換選單把B組件隱藏然後又顯示
B組件會再次載入,所以會再次從Supabase拿資料
由於從Supabase拿資料的動作我們是在onMounted做的
所以可以做一個簡單的驗證

把父組件載入B組件的 v-show 換成 v-if
接著我們在B組件的onMounted加入這段
在每次切換選單關掉B組件又打開,便會重新載入

關於最後一個段落的localStorage資料同步問題
也許你可能有疑問,類似下面這樣寫
將localStorage的資料加進實例 是不是就可以了呢?

const storage = ref(JSON.parse(localStorage.getItem("favoriteList")));
watch(storage, (newVal) => { alert("有變更") },{ deep: true });

因為Vue只會監聽響應式物件(如ref)
但localStorage是瀏覽器API,不是Vue的響應式物件
Vue不會去看localStorage的變化
假設接著手動更改 storage.value = []
這種操作是沒有用的


上一篇
遊戲活動關卡查詢網站Day18-我的最愛1(LocalStorage)
下一篇
遊戲活動關卡查詢網站Day20-我的最愛3(Vue:Module)
系列文
遊戲活動關卡查詢網站23
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言