iT邦幫忙

2025 iThome 鐵人賽

DAY 20
0
Vue.js

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

遊戲活動關卡查詢網站Day20-我的最愛3(Vue:Module)

  • 分享至 

  • xImage
  •  

目標
上一篇在F組件有成功抓取到localStorage
並顯示在畫面上
這一篇我們的目標是要解決以下問題
A組件的「我的最愛」功能和F組件不同步
以及新增移除功能 讓畫面更完善

最後會做一個驗證,展示從Day18到這一篇畫面上有實作的功能
並且和我們預期的結果符合

步驟
1
為了達到A組件的「我的最愛」功能和F組件同步 這個目的
首先在src/創建一隻檔案useLocalStorageRef.js
內容如下,這是一個Module(模組)
主要是創建localStorage的ref實例 並做監聽存取的事情
我們接下來A、F組件都會需要引用它

//創建於路徑 src/useLocalStorageRef.js
import { ref, watch } from "vue";

const favoriteList = ref(JSON.parse(localStorage.getItem("favoriteList") || "[]"));

watch(favoriteList, (newVal) => {
  localStorage.setItem("favoriteList", JSON.stringify(newVal));
}, { deep: true });

export function useLocalStorageRef() {
  return favoriteList;
}

2
另外原先A、F組件的存、讀取localStorage部份
我們也需要進行改寫;
A、F組件的script 引入我們剛才寫的modoule
由於監聽的事情已經在modoule做了 這邊也移除掉
下面是分別移除和新增的程式架構

<script setup>
//A組件  移除 以下
const favoriteList = ref([]);
watch(favoriteList, (newVal) => {
	console.log("新增物件")
  localStorage.setItem("favoriteList", JSON.stringify(newVal));
},{ deep: true }); 


//A組件、F組件 新增以下
import { useLocalStorageRef } from "../useLocalStorageRef.js"
const favoriteList = useLocalStorageRef();

</script>

這樣在A組件中點選我的最愛時
F組件就會更新資料

來驗證一下 當F組件和A組件同時顯示時
點選我的最愛時 F組件也會即時更新資料

點選其他關卡(99-99)時 也即時更新資料

3
到了這一步,還會有個顯示的問題
因為當初我們在做選單規劃時 只有用E組件上面的事件去控制
如果畫面上顯示的是F組件
當按下B組件的活動圖案時,C組件不會顯示
我們要修正的是
按下B組件的活動圖案時,C組件要能顯示

4
所以我們要做的是把B組件點圖案這件事,告訴E組件
整體流程會像下面這張圖

而B組件的script需要稍微做個調整
我們多定義一筆emits
並在按下圖案時 把資訊傳給父組件

<script setup>
const emit = defineEmits(["response","responseBF","responseBE"]);
function selectAction(e) {
	var data = e.target.getAttribute("data-dataInfo");
	actionSelect.value = data;
	console.log(eventList.value);
	
	eventList.value = 
		actionSelect.value == "" 
		? result.value
		: result.value.filter(x=>x.actionNo == actionSelect.value)

	emit("response", {"actionNo":actionSelect.value,"info":eventList.value});
	emit("responseBE",{"actionNo":actionSelect.value,"name":"活動一覽"});
}
</script>

如下圖紅框標示處
要多傳actionNo作為識別,watch才會辨別是不同的資料
這樣子,點不同活動圖案時才能正常切換

4
父組件的程式架構與之前做法相同,這邊略過
接下來是E組件的script程式架構 調整如下
在watch監聽到B組件傳過來的資料變動後
會重新執行 我們之前寫的畫面切換的函式

<script setup>
/* global defineProps */
import { ref,watch } from "vue";

const props = defineProps({
  msg: Object
})
watch(  () => props.msg, async (newVal) => {
 displayEvent(props.msg.name);
})
</script>

5
接著,我們把移除功能加進來
F組件的script 程式架構調整如下
建立移除事件 並在移除前加入使用者確認
而這個事件將會依照移除項目的資訊 過濾掉favoriteList
而favoriteFilterList同時也會computed重新計算 即時變更畫面

<script setup>
//...以上略
function removeList(e){
	if(!confirm("確認移除該筆資料嗎?")){return;}
	const removeData = e.target.getAttribute("data-info");
	favoriteList.value = favoriteList.value.filter(x=> x.rrn != removeData)
}
function dataInfo(item,str){ // 第一個參數:取活動編號和副本編號  第二個參數:取副本主編號、副編號、關卡序號
  
  const result = item.actionNo + item.eventNo + str ;
  return result;
}
</script>

由於畫面應該要多呈現Stage的資訊
Template會有些微調整如下

<template>
<div v-if="showfavoriteFilterList">
	我的最愛
	<div class="row mb-5"  v-for="item in favoriteFilterList" :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 d-flex flex-row align-items-center" v-for="child in item.eventInfo" :key="child">
						<li class="list-group-item flex-grow-1 mb-0" 
						:data-info="dataInfo(item,child)"  @click="selectEvent" 
						>
						{{ String(parseInt(child.substring(0,2))).padStart(2, ' ')}}
							-
						{{ String(parseInt(child.substring(2,4))).padStart(2, ' ')}}
						<p></p>Stage
						{{ String(parseInt(child.substring(4,6))).padStart(2, ' ')}}
						</li>
						<button class="btn btn-sm btn-outline-danger ms-2" :data-info="dataInfo(item,child)"
						@click="removeList">X</button>
					</ul>
					
				</div>
			</div>
	</div>
</div>
<div v-else>尚未有資料,請新增關卡至我的最愛</div>	
</template>

我們除了樣式有做變動外,主要異動的是下圖紅框的部分
在移除項目新增一個X按鈕 並加入data-info的資訊
當按下X觸發事件後 依照資訊過濾favoriteList
讓F組件上的資料做到即時更新

6
因為C組件在按下副本時,會顯示A組件的敵人資訊
所以F組件這邊我們也做同樣的事情
F組件增加以下點擊事件 把資料透過父組件傳給A組件

<script setup>
const emit = defineEmits(["responseFA"]);

function selectEvent(e) {

	const data = e.target.getAttribute("data-info");
	emit("responseFA",data);
}
</script>

因為A組件接收資料,所以調整以下script程式架構
這邊我們會用rrn編號 去取Supabase的資料
rrn編號 總共14碼
由活動編號(4)+副本編號(4)+主編號(2)+副編號(2)+關卡序號(2)組成

<script setup>
const props = defineProps({
  msg: Object,
  msg2:String
})
watch(  () => props.msg2, async (newVal) => { //更新我的最愛的敵人資訊
if (newVal) {
		const rrn = newVal;
		const { data, error } = await supabase
		.rpc("getenemy_byfav", { p_rrn: newVal}) 

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

但那時候我們建立函式時 用的rrn編號條件是12碼
所以現在回到Supabase的SQL Editor
再建立一個函式getenemy_byfav
這邊做的就是 當F組件點擊該關卡時,這邊會顯示另外的敵人資訊

CREATE OR REPLACE FUNCTION getenemy_byfav(p_rrn text)
RETURNS TABLE(
    stageOrder text,
    actionName text,
    eventName text,
    element text,
    species text,
    hp text,
    angerCondition text,
    skillOrder bigint,
    rrn text,
    skillFlag text,
    skill1Name text,
    skill1Value text,
    skill1Unit text,
    skill1Target text,
    skill1Time text,
    skill2Name text,
    skill2Value text,
    skill2Unit text,
    skill2Target text,
    skill2Time text,
    skill3Name text,
    skill3Value text,
    skill3Unit text,
    skill3Target text,
    skill3Time text
)
AS $$
  SELECT  
    action_event."stageOrder",
    action_event."actionName",
    action_event."eventName",
    enemy."element",
    enemy."species",
    enemy."hp",
    enemy."angerCondition",
    skill."skillOrder" ,
    enemy."rrn" || enemy."posNo",
    skill."skillFlag",
    skill."skill1Name",
    skill."skill1Value",
    skill."skill1Unit",
    skill."skill1Target",
    skill."skill1Time",
    skill."skill2Name",
    skill."skill2Value",
    skill."skill2Unit",
    skill."skill2Target",
    skill."skill2Time",
    skill."skill3Name",
    skill."skill3Value",
    skill."skill3Unit",
    skill."skill3Target",
    skill."skill3Time"
  FROM action_event
  LEFT JOIN enemy  
    ON (action_event."actionNo" || action_event."eventNo" || action_event."stagePrimaryNo" 
        || action_event."stageSecNo" || action_event."stageOrder") = enemy."rrn"
  LEFT JOIN skill  
    ON (enemy."rrn" || enemy."posNo") = skill."rrn"
  WHERE (action_event."actionNo" || action_event."eventNo" 
         || action_event."stagePrimaryNo" 
         || action_event."stageSecNo" || action_event."stageOrder" ) = p_rrn;
$$ LANGUAGE sql;

驗證

在A組件中,於5-3的stage3關卡
點選我的最愛圖案

到F組件中,預期應該要顯示5-3-3的資料

按下移除時,會請使用者先確認
確認後,5-3-3的資料應該要消失

5-3-3的資料消失,符合預期結果

按下F組件上的資訊時,A組件應該要個別帶入該關卡的資訊
點擊後,只帶入5-1-2的敵人資訊
符合預期結果

備註
這篇前面部分講述的是module的使用
解決了A組件和F組件引用的問題
關鍵在於module是在第一次載入時會有快取
因此A組件和F組件import引入時 都是拿到同一份東西
也就是說同一個引用的對象

這樣也直接解決了
將localStorage的資料emit來emit去、prop來prop去的問題
對比上一篇,A組件和F組件現在引用的實例對象關係圖如下


上一篇
遊戲活動關卡查詢網站Day19-我的最愛2(Vue:v-show)
下一篇
遊戲活動關卡查詢網站Day21-條件查詢1(Vue:v-model)
系列文
遊戲活動關卡查詢網站23
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言