mirror of
https://github.com/BTMuli/TeyvatGuide.git
synced 2026-04-22 21:59:49 +08:00
✨ 首页实时便笺组件
This commit is contained in:
BIN
public/UI/daily/coin.webp
Normal file
BIN
public/UI/daily/coin.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
BIN
public/UI/daily/domain.webp
Normal file
BIN
public/UI/daily/domain.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
BIN
public/UI/daily/mission.webp
Normal file
BIN
public/UI/daily/mission.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
BIN
public/UI/daily/resin.webp
Normal file
BIN
public/UI/daily/resin.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
BIN
public/UI/daily/task.webp
Normal file
BIN
public/UI/daily/task.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
BIN
public/UI/daily/trans.webp
Normal file
BIN
public/UI/daily/trans.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
187
src/components/pageHome/ph-comp-daily-note.vue
Normal file
187
src/components/pageHome/ph-comp-daily-note.vue
Normal file
@@ -0,0 +1,187 @@
|
||||
<!-- 首页实时便笺卡片 -->
|
||||
<template>
|
||||
<THomeCard :append="isLogin" title="实时便笺">
|
||||
<template v-if="isLogin" #title-append>
|
||||
<PhUserSwitch
|
||||
:current-uid="uid ?? ''"
|
||||
:nickname="briefInfo.nickname"
|
||||
@switch-user="handleUserSwitch"
|
||||
/>
|
||||
</template>
|
||||
<template #default>
|
||||
<div v-if="!isLogin" class="dn-not-login">请先登录</div>
|
||||
<div v-else-if="gameAccounts.length === 0" class="dn-not-login">暂无游戏账户</div>
|
||||
<div v-else-if="loading" class="dn-loading">
|
||||
<div class="loading-content">
|
||||
<v-progress-linear :model-value="loadingProgress" color="blue" height="6" rounded />
|
||||
<div class="loading-text">{{ loadingText }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="dn-container">
|
||||
<PhDailyNoteItem
|
||||
v-for="item in dailyNoteAccounts"
|
||||
:key="`${item.account.gameBiz}_${item.account.gameUid}`"
|
||||
:account="item.account"
|
||||
:data="item.data"
|
||||
@refresh="handleRefresh(item.account)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</THomeCard>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import recordReq from "@req/recordReq.js";
|
||||
import TSUserAccount from "@Sqlm/userAccount.js";
|
||||
import useAppStore from "@store/app.js";
|
||||
import useUserStore from "@store/user.js";
|
||||
import TGLogger from "@utils/TGLogger.js";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
|
||||
import THomeCard from "./ph-comp-card.vue";
|
||||
import PhDailyNoteItem from "./ph-daily-note-item.vue";
|
||||
import PhUserSwitch from "./ph-user-switch.vue";
|
||||
|
||||
type DailyNoteAccount = {
|
||||
account: TGApp.Sqlite.Account.Game;
|
||||
data?: TGApp.Game.DailyNote.DnRes;
|
||||
};
|
||||
|
||||
type TDailyNoteEmits = {
|
||||
(e: "success"): void;
|
||||
(e: "delete", gameUid: string): void;
|
||||
};
|
||||
|
||||
const emits = defineEmits<TDailyNoteEmits>();
|
||||
const { cookie, uid, briefInfo } = storeToRefs(useUserStore());
|
||||
const { isLogin } = storeToRefs(useAppStore());
|
||||
|
||||
const loading = ref<boolean>(false);
|
||||
const loadingProgress = ref<number>(0);
|
||||
const loadingText = ref<string>("");
|
||||
const gameAccounts = ref<Array<TGApp.Sqlite.Account.Game>>([]);
|
||||
const dailyNoteAccounts = ref<Array<DailyNoteAccount>>([]);
|
||||
|
||||
watch(
|
||||
() => uid.value,
|
||||
async () => await loadData(),
|
||||
);
|
||||
|
||||
onMounted(async () => await loadData());
|
||||
|
||||
async function loadData(): Promise<void> {
|
||||
if (!isLogin.value || uid.value === undefined || !cookie.value) {
|
||||
gameAccounts.value = [];
|
||||
dailyNoteAccounts.value = [];
|
||||
return;
|
||||
}
|
||||
dailyNoteAccounts.value = [];
|
||||
try {
|
||||
const accounts = await TSUserAccount.game.getAccount(uid.value);
|
||||
const genshinAccounts = accounts.filter((ac) => ac.gameBiz === "hk4e_cn");
|
||||
gameAccounts.value = genshinAccounts;
|
||||
if (genshinAccounts.length === 0) {
|
||||
await TGLogger.Warn("[Daily Note Card] No Genshin Impact accounts found");
|
||||
emits("success");
|
||||
return;
|
||||
}
|
||||
emits("success");
|
||||
loading.value = true;
|
||||
loadingProgress.value = 0;
|
||||
for (let i = 0; i < genshinAccounts.length; i++) {
|
||||
const account = genshinAccounts[i];
|
||||
loadingText.value = `正在加载 ${account.gameBiz} - ${account.regionName} - ${account.gameUid}...`;
|
||||
loadingProgress.value = (i / genshinAccounts.length) * 100;
|
||||
let data: TGApp.Game.DailyNote.DnRes | undefined;
|
||||
try {
|
||||
const dataResp = await recordReq.daily(cookie.value, account);
|
||||
console.debug("dailyResp", account, dataResp);
|
||||
if (dataResp.retcode !== 0) {
|
||||
await TGLogger.Error(
|
||||
`[Daily Note Card] Failed to get daily note for ${account.gameBiz}: ${dataResp.message}`,
|
||||
);
|
||||
} else data = dataResp.data;
|
||||
} catch (error) {
|
||||
await TGLogger.Error(
|
||||
`[Daily Note Card] Error loading data for ${account.gameBiz}: ${error}`,
|
||||
);
|
||||
}
|
||||
dailyNoteAccounts.value.push({ account, data });
|
||||
}
|
||||
} catch (error) {
|
||||
await TGLogger.Error(`[Daily Note Card] Error loading data: ${error}`);
|
||||
} finally {
|
||||
loadingProgress.value = 100;
|
||||
loadingText.value = "加载完成";
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, 200));
|
||||
loading.value = false;
|
||||
loadingProgress.value = 0;
|
||||
loadingText.value = "";
|
||||
}
|
||||
}
|
||||
|
||||
async function handleUserSwitch(newUid: string): Promise<void> {
|
||||
await TGLogger.Info(`[Daily Note Card] User switched to ${newUid}`);
|
||||
}
|
||||
|
||||
async function handleRefresh(account: TGApp.Sqlite.Account.Game): Promise<void> {
|
||||
try {
|
||||
const dataResp = await recordReq.daily(cookie.value!, account);
|
||||
if (dataResp.retcode !== 0) {
|
||||
await TGLogger.Error(`[Daily Note Card] Refresh failed: ${dataResp.message}`);
|
||||
showSnackbar.error("刷新失败");
|
||||
return;
|
||||
}
|
||||
const item = dailyNoteAccounts.value.find(
|
||||
(i) => i.account.gameUid === account.gameUid && i.account.gameBiz === account.gameBiz,
|
||||
);
|
||||
if (item) {
|
||||
item.data = dataResp.data;
|
||||
}
|
||||
showSnackbar.success("刷新成功");
|
||||
} catch (error) {
|
||||
await TGLogger.Error(`[Daily Note Card] Refresh error: ${error}`);
|
||||
showSnackbar.error("刷新失败");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.dn-not-login {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px 20px;
|
||||
color: var(--box-text-1);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.dn-loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.loading-content {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
color: var(--box-text-2);
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dn-container {
|
||||
position: relative;
|
||||
display: grid;
|
||||
padding: 8px;
|
||||
gap: 8px;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
</style>
|
||||
80
src/components/pageHome/ph-daily-note-boss.vue
Normal file
80
src/components/pageHome/ph-daily-note-boss.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<!-- 周本 BOSS 显示组件 -->
|
||||
<template>
|
||||
<div class="ph-dnb-box">
|
||||
<div class="pdb-boss-icon">
|
||||
<img alt="周本 BOSS" src="/UI/daily/domain.webp" />
|
||||
</div>
|
||||
<div class="pdb-boss-info">
|
||||
<div class="pdb-boss-title">周本 BOSS</div>
|
||||
<div class="pdb-boss-desc">剩余减半次数 {{ current }}/{{ max }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
type PhDailyNoteBossProps = {
|
||||
remainResinDiscountNum?: number;
|
||||
resinDiscountNumLimit?: number;
|
||||
};
|
||||
|
||||
const props = defineProps<PhDailyNoteBossProps>();
|
||||
|
||||
const current = computed((): number => {
|
||||
return props.remainResinDiscountNum ?? 0;
|
||||
});
|
||||
|
||||
const max = computed((): number => {
|
||||
return props.resinDiscountNumLimit ?? 0;
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.ph-dnb-box {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
padding: 6px;
|
||||
border-radius: 4px;
|
||||
background: var(--box-bg-2);
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.pdb-boss-icon {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 4px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.pdb-boss-info {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.pdb-boss-title {
|
||||
font-family: var(--font-title);
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pdb-boss-desc {
|
||||
overflow: hidden;
|
||||
color: var(--box-text-2);
|
||||
font-size: 10px;
|
||||
line-height: 1.2;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
134
src/components/pageHome/ph-daily-note-coin.vue
Normal file
134
src/components/pageHome/ph-daily-note-coin.vue
Normal file
@@ -0,0 +1,134 @@
|
||||
<!-- 洞天宝钱显示组件 -->
|
||||
<template>
|
||||
<div class="ph-dnc-box">
|
||||
<div class="pdb-coin-icon">
|
||||
<img alt="洞天宝钱" src="/UI/daily/coin.webp" />
|
||||
</div>
|
||||
<div class="pdb-coin-info">
|
||||
<div class="pdb-coin-title">
|
||||
<span>洞天宝钱</span>
|
||||
<span>{{ current }}/{{ max }}</span>
|
||||
</div>
|
||||
<div class="pdb-coin-desc">
|
||||
{{ formattedTime }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted, ref } from "vue";
|
||||
|
||||
import { stamp2LastTime } from "@utils/toolFunc.js";
|
||||
|
||||
type PhDailyNoteCoinProps = {
|
||||
currentCoin: number;
|
||||
maxCoin: number;
|
||||
recoveryTime: string;
|
||||
};
|
||||
|
||||
const props = defineProps<PhDailyNoteCoinProps>();
|
||||
|
||||
const current = ref<number>(0);
|
||||
const max = ref<number>(0);
|
||||
const remainedTime = ref<number>(0);
|
||||
const formattedTime = ref<string>("");
|
||||
|
||||
let timer: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
onMounted(() => {
|
||||
initTime();
|
||||
startTimer();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
stopTimer();
|
||||
});
|
||||
|
||||
function initTime(): void {
|
||||
current.value = props.currentCoin || 0;
|
||||
max.value = props.maxCoin || 0;
|
||||
const time = props.recoveryTime;
|
||||
remainedTime.value = typeof time === "string" ? parseInt(time) : time || 0;
|
||||
updateFormattedTime();
|
||||
}
|
||||
|
||||
function startTimer(): void {
|
||||
if (remainedTime.value <= 0) return;
|
||||
|
||||
timer = setInterval(() => {
|
||||
if (remainedTime.value > 0) {
|
||||
remainedTime.value -= 1;
|
||||
updateFormattedTime();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function stopTimer(): void {
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function updateFormattedTime(): void {
|
||||
if (remainedTime.value <= 0) {
|
||||
formattedTime.value = "已存满";
|
||||
return;
|
||||
}
|
||||
|
||||
formattedTime.value = stamp2LastTime(remainedTime.value * 1000);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.ph-dnc-box {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
padding: 6px;
|
||||
border-radius: 4px;
|
||||
background: var(--box-bg-2);
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.pdb-coin-icon {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 4px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.pdb-coin-info {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.pdb-coin-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-family: var(--font-title);
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pdb-coin-desc {
|
||||
overflow: hidden;
|
||||
color: var(--box-text-2);
|
||||
font-size: 10px;
|
||||
line-height: 1.2;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
151
src/components/pageHome/ph-daily-note-expedition.vue
Normal file
151
src/components/pageHome/ph-daily-note-expedition.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<!-- 探索派遣单项 -->
|
||||
<template>
|
||||
<div class="dni-exp-item">
|
||||
<div class="dni-exp-icon">
|
||||
<img :src="expedition.avatar_side_icon" alt="角色" />
|
||||
</div>
|
||||
<div class="dni-exp-info">
|
||||
<div v-if="isFinished" class="dni-exp-status finished">已完成</div>
|
||||
<div v-else class="dni-exp-info-content">
|
||||
<div class="dni-exp-status">派遣中</div>
|
||||
<div class="dni-exp-time">{{ formattedTime }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted, ref, watch } from "vue";
|
||||
|
||||
import { stamp2LastTime } from "@utils/toolFunc.js";
|
||||
|
||||
type PhDailyNoteExpeditionProps = {
|
||||
expedition: TGApp.Game.DailyNote.Expedition;
|
||||
};
|
||||
|
||||
const props = defineProps<PhDailyNoteExpeditionProps>();
|
||||
|
||||
const remainedTime = ref<number>(0);
|
||||
const isFinished = ref<boolean>(false);
|
||||
const formattedTime = ref<string>("");
|
||||
|
||||
let timer: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
onMounted(() => {
|
||||
initTime();
|
||||
startTimer();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
stopTimer();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.expedition.remained_time,
|
||||
() => {
|
||||
initTime();
|
||||
stopTimer();
|
||||
startTimer();
|
||||
},
|
||||
);
|
||||
|
||||
function initTime(): void {
|
||||
const time = props.expedition.remained_time;
|
||||
remainedTime.value = typeof time === "string" ? parseInt(time) : time;
|
||||
isFinished.value = props.expedition.status === "Finished" || remainedTime.value <= 0;
|
||||
updateFormattedTime();
|
||||
}
|
||||
|
||||
function startTimer(): void {
|
||||
if (isFinished.value) return;
|
||||
|
||||
timer = setInterval(() => {
|
||||
if (remainedTime.value > 0) {
|
||||
remainedTime.value -= 1;
|
||||
updateFormattedTime();
|
||||
|
||||
if (remainedTime.value <= 0) {
|
||||
isFinished.value = true;
|
||||
stopTimer();
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function stopTimer(): void {
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function updateFormattedTime(): void {
|
||||
if (remainedTime.value <= 0) {
|
||||
formattedTime.value = "0 小时 0 分钟 0 秒";
|
||||
return;
|
||||
}
|
||||
|
||||
formattedTime.value = stamp2LastTime(remainedTime.value * 1000);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.dni-exp-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
height: 32px;
|
||||
align-items: center;
|
||||
padding-right: 12px;
|
||||
border-radius: 20px;
|
||||
background: linear-gradient(to right, var(--box-bg-2), transparent 120%);
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.dni-exp-icon {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
background: var(--box-bg-4);
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.dni-exp-info {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dni-exp-info-content {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.dni-exp-status {
|
||||
overflow: hidden;
|
||||
color: var(--box-text-2);
|
||||
font-size: 10px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
&.finished {
|
||||
color: var(--tgc-od-green);
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.dni-exp-time {
|
||||
overflow: hidden;
|
||||
color: var(--box-text-1);
|
||||
font-size: 10px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
248
src/components/pageHome/ph-daily-note-item.vue
Normal file
248
src/components/pageHome/ph-daily-note-item.vue
Normal file
@@ -0,0 +1,248 @@
|
||||
<!-- 实时便笺单项 -->
|
||||
<template>
|
||||
<div class="dni-container">
|
||||
<div class="dni-header">
|
||||
<div class="dni-header-title">
|
||||
<span>{{ props.account.nickname }}</span>
|
||||
<v-icon
|
||||
:size="16"
|
||||
color="var(--tgc-od-orange)"
|
||||
icon="mdi-refresh"
|
||||
variant="elevated"
|
||||
@click="handleRefresh"
|
||||
/>
|
||||
</div>
|
||||
<div class="dni-header-append">
|
||||
<span>{{ props.account.gameUid }}</span>
|
||||
<span>{{ props.account.regionName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="props.data" class="dni-content">
|
||||
<div class="dni-grid">
|
||||
<div class="dni-row">
|
||||
<PhDailyNoteResin
|
||||
:current-resin="props.data.current_resin"
|
||||
:max-resin="props.data.max_resin"
|
||||
:recovery-time="props.data.resin_recovery_time"
|
||||
/>
|
||||
<PhDailyNoteCoin
|
||||
:current-coin="props.data.current_home_coin"
|
||||
:max-coin="props.data.max_home_coin"
|
||||
:recovery-time="props.data.home_coin_recovery_time"
|
||||
/>
|
||||
<PhDailyNoteTransformer :trans="props.data.transformer" />
|
||||
</div>
|
||||
<div class="dni-row">
|
||||
<PhDailyNoteQuest :quest="props.data.archon_quest_progress" />
|
||||
<PhDailyNoteTask :task="props.data.daily_task" />
|
||||
<PhDailyNoteBoss
|
||||
:remain-resin-discount-num="props.data.remain_resin_discount_num"
|
||||
:resin-discount-num-limit="props.data.resin_discount_num_limit"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dni-exp-header">
|
||||
<span class="dni-exp-title">探索派遣</span>
|
||||
<span class="dni-exp-count">
|
||||
{{ props.data.current_expedition_num }}/{{ props.data.max_expedition_num }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="dni-exp-grid">
|
||||
<PhDailyNoteExpedition
|
||||
v-for="expedition in props.data.expeditions"
|
||||
:key="expedition.avatar_side_icon"
|
||||
:expedition="expedition"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import PhDailyNoteExpedition from "./ph-daily-note-expedition.vue";
|
||||
import PhDailyNoteResin from "./ph-daily-note-resin.vue";
|
||||
import PhDailyNoteCoin from "./ph-daily-note-coin.vue";
|
||||
import PhDailyNoteTransformer from "./ph-daily-note-transformer.vue";
|
||||
import PhDailyNoteTask from "./ph-daily-note-task.vue";
|
||||
import PhDailyNoteQuest from "./ph-daily-note-quest.vue";
|
||||
import PhDailyNoteBoss from "./ph-daily-note-boss.vue";
|
||||
|
||||
type PhDailyNoteItemProps = {
|
||||
account: TGApp.Sqlite.Account.Game;
|
||||
data?: TGApp.Game.DailyNote.DnRes;
|
||||
};
|
||||
|
||||
type TDailyNoteItemEmits = {
|
||||
(e: "refresh"): void;
|
||||
};
|
||||
|
||||
const emits = defineEmits<TDailyNoteItemEmits>();
|
||||
const props = defineProps<PhDailyNoteItemProps>();
|
||||
|
||||
function handleRefresh(): void {
|
||||
emits("refresh");
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.dni-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
padding: 8px;
|
||||
border: 1px solid var(--common-shadow-2);
|
||||
border-radius: 4px;
|
||||
background: var(--box-bg-1);
|
||||
color: var(--box-text-1);
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.dni-header {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 6px;
|
||||
border-bottom: 1px solid var(--common-shadow-1);
|
||||
}
|
||||
|
||||
.dni-header-title {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
column-gap: 4px;
|
||||
font-family: var(--font-title);
|
||||
}
|
||||
|
||||
.dni-header-append {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.dni-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.dni-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.dni-row {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.dni-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
padding: 6px;
|
||||
border-radius: 4px;
|
||||
background: var(--box-bg-2);
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.dni-icon {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 4px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.dni-info {
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.dni-title {
|
||||
font-family: var(--font-title);
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dni-desc {
|
||||
overflow: hidden;
|
||||
color: var(--box-text-2);
|
||||
font-size: 10px;
|
||||
line-height: 1.2;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dni-value {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
background: var(--box-bg-3);
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dni-exp-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 4px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.dni-exp-title {
|
||||
color: var(--box-text-1);
|
||||
font-family: var(--font-title);
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.dni-exp-count {
|
||||
color: var(--box-text-2);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.dni-exp-grid {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
column-gap: 12px;
|
||||
}
|
||||
|
||||
@media (width <= 900px) {
|
||||
.dni-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (width <= 600px) {
|
||||
.dni-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
110
src/components/pageHome/ph-daily-note-quest.vue
Normal file
110
src/components/pageHome/ph-daily-note-quest.vue
Normal file
@@ -0,0 +1,110 @@
|
||||
<!-- 魔神任务显示组件 -->
|
||||
<template>
|
||||
<div class="ph-dnq-box">
|
||||
<div class="pdb-quest-icon">
|
||||
<img alt="魔神任务" src="/UI/daily/mission.webp" />
|
||||
</div>
|
||||
<div class="pdb-quest-info">
|
||||
<div class="pdb-quest-title">
|
||||
<span>魔神任务</span>
|
||||
<span>
|
||||
{{
|
||||
props.quest.is_finish_all_mainline && props.quest.is_finish_all_interchapter
|
||||
? "已完成"
|
||||
: `未完成:${props.quest.list.length}`
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="pdb-quest-desc">
|
||||
<span v-for="q in props.quest.list" :key="q.id">
|
||||
{{ q.chapter_num }} {{ q.chapter_title }} {{ getQuestStatus(q.status) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
type PhDailyNoteQuestProps = { quest: TGApp.Game.DailyNote.ArchonQuestProgress };
|
||||
|
||||
const props = defineProps<PhDailyNoteQuestProps>();
|
||||
|
||||
function getQuestStatus(stat: string): string {
|
||||
switch (stat) {
|
||||
case "StatusNotOpen":
|
||||
return "未开启";
|
||||
default:
|
||||
return stat;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.ph-dnq-box {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 64px;
|
||||
align-items: center;
|
||||
padding: 6px;
|
||||
border-radius: 4px;
|
||||
background: var(--box-bg-2);
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.pdb-quest-icon {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 4px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.pdb-quest-info {
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.pdb-quest-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-family: var(--font-title);
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pdb-quest-desc {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
flex-direction: column;
|
||||
color: var(--box-text-2);
|
||||
font-size: 10px;
|
||||
line-height: 1.2;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pdb-quest-value {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
background: var(--box-bg-3);
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
142
src/components/pageHome/ph-daily-note-resin.vue
Normal file
142
src/components/pageHome/ph-daily-note-resin.vue
Normal file
@@ -0,0 +1,142 @@
|
||||
<!-- 原粹树脂显示组件 -->
|
||||
<template>
|
||||
<div class="ph-dnr-box">
|
||||
<div class="pdb-resin-icon">
|
||||
<img alt="原粹树脂" src="/UI/daily/resin.webp" />
|
||||
</div>
|
||||
<div class="pdb-resin-info">
|
||||
<div class="pdb-resin-title">
|
||||
<span>原粹树脂</span>
|
||||
<span>{{ current }}/{{ max }}</span>
|
||||
</div>
|
||||
<div class="pdb-resin-desc">
|
||||
{{ formattedTime }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted, ref } from "vue";
|
||||
|
||||
import { stamp2LastTime } from "@utils/toolFunc.js";
|
||||
|
||||
type PhDailyNoteResinProps = {
|
||||
currentResin: number;
|
||||
maxResin: number;
|
||||
recoveryTime: string;
|
||||
};
|
||||
|
||||
const props = defineProps<PhDailyNoteResinProps>();
|
||||
|
||||
const current = ref<number>(0);
|
||||
const max = ref<number>(0);
|
||||
const remainedTime = ref<number>(0);
|
||||
const formattedTime = ref<string>("");
|
||||
|
||||
let timer: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
onMounted(() => {
|
||||
initTime();
|
||||
startTimer();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
stopTimer();
|
||||
});
|
||||
|
||||
function initTime(): void {
|
||||
current.value = props.currentResin || 0;
|
||||
max.value = props.maxResin || 0;
|
||||
const time = props.recoveryTime;
|
||||
remainedTime.value = typeof time === "string" ? parseInt(time) : time || 0;
|
||||
updateFormattedTime();
|
||||
}
|
||||
|
||||
function startTimer(): void {
|
||||
if (remainedTime.value <= 0) return;
|
||||
|
||||
timer = setInterval(() => {
|
||||
if (remainedTime.value > 0) {
|
||||
remainedTime.value -= 1;
|
||||
updateFormattedTime();
|
||||
// 每 9 分钟恢复 1 点树脂
|
||||
const totalSecondsPassed =
|
||||
(props.recoveryTime ? parseInt(props.recoveryTime) : 0) - remainedTime.value;
|
||||
const resinToRecover = Math.floor(totalSecondsPassed / 540); // 540 秒 = 9 分钟
|
||||
const newCurrent = Math.min(current.value + resinToRecover, max.value);
|
||||
if (newCurrent !== current.value) {
|
||||
current.value = newCurrent;
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function stopTimer(): void {
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function updateFormattedTime(): void {
|
||||
if (remainedTime.value <= 0) {
|
||||
formattedTime.value = "已恢复满";
|
||||
return;
|
||||
}
|
||||
|
||||
formattedTime.value = stamp2LastTime(remainedTime.value * 1000);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.ph-dnr-box {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
padding: 6px;
|
||||
border-radius: 4px;
|
||||
background: var(--box-bg-2);
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.pdb-resin-icon {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 4px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.pdb-resin-info {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.pdb-resin-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-family: var(--font-title);
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pdb-resin-desc {
|
||||
overflow: hidden;
|
||||
color: var(--box-text-2);
|
||||
font-size: 10px;
|
||||
line-height: 1.2;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
228
src/components/pageHome/ph-daily-note-task.vue
Normal file
228
src/components/pageHome/ph-daily-note-task.vue
Normal file
@@ -0,0 +1,228 @@
|
||||
<!-- 每日委托显示组件 -->
|
||||
<template>
|
||||
<div class="ph-dnta-box">
|
||||
<div class="pdb-task-icon">
|
||||
<img alt="每日委托" src="/UI/daily/task.webp" />
|
||||
</div>
|
||||
<div class="pdb-task-info">
|
||||
<div class="pdb-task-title-row">
|
||||
<div class="pdb-task-title">
|
||||
<span>每日委托</span>
|
||||
<span>{{ taskStatus }}</span>
|
||||
</div>
|
||||
<div v-if="task?.stored_attendance" class="pdb-task-stored" title="长效历练点">
|
||||
<v-icon color="var(--tgc-od-red)" size="12">mdi-circle</v-icon>
|
||||
<span>{{ task.stored_attendance }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pdb-task-task-content">
|
||||
<div class="pdb-task-task-row">
|
||||
<span class="pdb-task-task-label">日常</span>
|
||||
<template v-for="i in props.task?.task_rewards" :key="i">
|
||||
<v-icon
|
||||
v-if="i.status === 'TaskRewardStatusFinished'"
|
||||
color="var(--tgc-od-green)"
|
||||
size="14"
|
||||
>
|
||||
mdi-check
|
||||
</v-icon>
|
||||
<v-icon v-else color="var(--tgc-od-white)" size="14"> mdi-square-outline</v-icon>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="attendanceVisible" class="pdb-task-task-row">
|
||||
<span class="pdb-task-task-label">历练</span>
|
||||
<div class="pdb-task-attendance-list">
|
||||
<div
|
||||
v-for="reward in attendanceRewards"
|
||||
:key="reward.progress"
|
||||
class="pdb-task-attendance-item"
|
||||
>
|
||||
<v-progress-circular
|
||||
:model-value="(Math.min(reward.progress, 2000) / 2000) * 100"
|
||||
:size="14"
|
||||
:width="2"
|
||||
class="pdb-task-attendance-progress"
|
||||
color="var(--tgc-od-orange)"
|
||||
/>
|
||||
<span
|
||||
v-if="reward.status !== 'AttendanceRewardStatusUnfinished'"
|
||||
class="pdb-task-attendance-icon"
|
||||
>
|
||||
<v-icon
|
||||
v-if="reward.status === 'AttendanceRewardStatusTakenAward'"
|
||||
color="var(--tgc-od-green)"
|
||||
size="8"
|
||||
>
|
||||
mdi-check
|
||||
</v-icon>
|
||||
<v-icon v-else color="var(--tgc-od-red)" size="8"> mdi-gift </v-icon>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<span v-if="hasUnclaimedAttendance" class="pdb-task-task-warn"> 可领取 </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pdb-task-value"></div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
type PhDailyNoteTaskProps = {
|
||||
task?: TGApp.Game.DailyNote.DailyTask;
|
||||
};
|
||||
|
||||
const props = defineProps<PhDailyNoteTaskProps>();
|
||||
|
||||
const attendanceVisible = computed((): boolean => {
|
||||
return props.task?.attendance_visible ?? false;
|
||||
});
|
||||
|
||||
const attendanceRewards = computed((): Array<TGApp.Game.DailyNote.AttendanceReward> => {
|
||||
return props.task?.attendance_rewards ?? [];
|
||||
});
|
||||
|
||||
const hasUnclaimedAttendance = computed((): boolean => {
|
||||
return (
|
||||
props.task?.attendance_rewards?.some(
|
||||
(r) => r.progress >= 2000 && r.status !== "AttendanceRewardStatusTakenAward",
|
||||
) ?? false
|
||||
);
|
||||
});
|
||||
|
||||
const taskStatus = computed((): string => {
|
||||
if (!props.task) return "";
|
||||
const { finished_num, total_num } = props.task;
|
||||
if (finished_num === total_num) {
|
||||
if (props.task.is_extra_task_reward_received) return "已完成";
|
||||
return `${finished_num}/${total_num} 可领取`;
|
||||
}
|
||||
return `${finished_num}/${total_num}`;
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.ph-dnta-box {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 64px;
|
||||
align-items: center;
|
||||
padding: 6px;
|
||||
border-radius: 4px;
|
||||
background: var(--box-bg-2);
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.pdb-task-icon {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 4px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.pdb-task-info {
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.pdb-task-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.pdb-task-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
column-gap: 4px;
|
||||
font-family: var(--font-title);
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pdb-task-task-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.pdb-task-task-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.pdb-task-task-label {
|
||||
color: var(--box-text-2);
|
||||
font-size: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pdb-task-task-warn {
|
||||
color: var(--tgc-od-orange);
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pdb-task-attendance-list {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 2px;
|
||||
}
|
||||
|
||||
.pdb-task-attendance-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.pdb-task-attendance-progress {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.pdb-task-attendance-icon {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.pdb-task-attendance-progress-text {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
color: var(--box-text-1);
|
||||
font-size: 8px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pdb-task-stored {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--box-text-2);
|
||||
font-size: 10px;
|
||||
gap: 2px;
|
||||
}
|
||||
</style>
|
||||
152
src/components/pageHome/ph-daily-note-transformer.vue
Normal file
152
src/components/pageHome/ph-daily-note-transformer.vue
Normal file
@@ -0,0 +1,152 @@
|
||||
<!-- 参量质变仪显示组件 -->
|
||||
<template>
|
||||
<div class="ph-dnt-box">
|
||||
<div class="pdb-trans-icon">
|
||||
<img alt="参量质变仪" src="/UI/daily/trans.webp" />
|
||||
</div>
|
||||
<div class="pdb-trans-info">
|
||||
<div class="pdb-trans-title">参量质变仪</div>
|
||||
<div class="pdb-trans-desc">
|
||||
{{ valueText }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, onUnmounted, ref } from "vue";
|
||||
|
||||
import { stamp2LastTime } from "@utils/toolFunc.js";
|
||||
|
||||
type PhDailyNoteTransformerProps = {
|
||||
trans: TGApp.Game.DailyNote.TransformerData;
|
||||
};
|
||||
|
||||
const props = defineProps<PhDailyNoteTransformerProps>();
|
||||
|
||||
const remainedTime = ref<number>(0);
|
||||
const formattedTime = ref<string>("");
|
||||
|
||||
let timer: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
const valueText = computed((): string => {
|
||||
if (!props.trans.obtained && !props.trans.recovery_time.reached) {
|
||||
return "恢复中";
|
||||
}
|
||||
if (props.trans.obtained || props.trans.recovery_time.reached) {
|
||||
return "可使用";
|
||||
}
|
||||
return formattedTime.value;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
initTime();
|
||||
startTimer();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
stopTimer();
|
||||
});
|
||||
|
||||
function initTime(): void {
|
||||
const time = props.trans.recovery_time;
|
||||
if (time) {
|
||||
const hours = time.hour || 0;
|
||||
const minutes = time.minute || 0;
|
||||
const seconds = time.second || 0;
|
||||
remainedTime.value = hours * 3600 + minutes * 60 + seconds;
|
||||
} else {
|
||||
remainedTime.value = 0;
|
||||
}
|
||||
updateFormattedTime();
|
||||
}
|
||||
|
||||
function startTimer(): void {
|
||||
if (remainedTime.value <= 0 || props.trans.obtained || props.trans.recovery_time.reached) return;
|
||||
|
||||
timer = setInterval(() => {
|
||||
if (remainedTime.value > 0) {
|
||||
remainedTime.value -= 1;
|
||||
updateFormattedTime();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function stopTimer(): void {
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function updateFormattedTime(): void {
|
||||
if (remainedTime.value <= 0) {
|
||||
formattedTime.value = "0 小时 0 分钟 0 秒";
|
||||
return;
|
||||
}
|
||||
|
||||
formattedTime.value = stamp2LastTime(remainedTime.value * 1000);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.ph-dnt-box {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
padding: 6px;
|
||||
border-radius: 4px;
|
||||
background: var(--box-bg-2);
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.pdb-trans-icon {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 4px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.pdb-trans-info {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.pdb-trans-title {
|
||||
font-family: var(--font-title);
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pdb-trans-desc {
|
||||
overflow: hidden;
|
||||
color: var(--box-text-2);
|
||||
font-size: 10px;
|
||||
line-height: 1.2;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pdb-trans-value {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
background: var(--box-bg-3);
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
118
src/enum/dailyNote.ts
Normal file
118
src/enum/dailyNote.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* 实时便笺相关枚举
|
||||
* @since Beta v0.10.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* 探索派遣状态枚举
|
||||
* @since Beta v0.10.0
|
||||
* @see TGApp.Game.DailyNote.ExpeditionStatusEnum
|
||||
*/
|
||||
const ExpeditionStatusEnum: typeof TGApp.Game.DailyNote.ExpeditionStatus = {
|
||||
ONGOING: "Ongoing",
|
||||
FINISHED: "Finished",
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取探索派遣状态描述
|
||||
* @since Beta v0.10.0
|
||||
* @param status - 探索派遣状态
|
||||
* @returns 探索派遣状态描述
|
||||
*/
|
||||
function getExpeditionStatusDesc(status: TGApp.Game.DailyNote.ExpeditionStatusEnum): string {
|
||||
switch (status) {
|
||||
case ExpeditionStatusEnum.ONGOING:
|
||||
return "派遣中";
|
||||
case ExpeditionStatusEnum.FINISHED:
|
||||
return "已完成";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务奖励状态枚举
|
||||
* @since Beta v0.10.0
|
||||
* @see TGApp.Game.DailyNote.TaskRewardStatusEnum
|
||||
*/
|
||||
const TaskRewardStatusEnum: typeof TGApp.Game.DailyNote.TaskRewardStatus = {
|
||||
UNFINISHED: "TaskRewardStatusUnfinished",
|
||||
FINISHED: "TaskRewardStatusFinished",
|
||||
RECEIVED: "TaskRewardStatusReceived",
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取任务奖励状态描述
|
||||
* @since Beta v0.10.0
|
||||
* @param status - 任务奖励状态
|
||||
* @returns 任务奖励状态描述
|
||||
*/
|
||||
function getTaskRewardStatusDesc(status: TGApp.Game.DailyNote.TaskRewardStatusEnum): string {
|
||||
switch (status) {
|
||||
case TaskRewardStatusEnum.UNFINISHED:
|
||||
return "未完成";
|
||||
case TaskRewardStatusEnum.FINISHED:
|
||||
return "已完成";
|
||||
case TaskRewardStatusEnum.RECEIVED:
|
||||
return "已领取";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 历练点奖励状态枚举
|
||||
* @since Beta v0.10.0
|
||||
* @see TGApp.Game.DailyNote.AttendanceRewardStatusEnum
|
||||
*/
|
||||
const AttendanceRewardStatusEnum: typeof TGApp.Game.DailyNote.AttendanceRewardStatus = {
|
||||
UNFINISHED: "AttendanceRewardStatusUnfinished",
|
||||
TAKEN: "AttendanceRewardStatusTakenAward",
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取历练点奖励状态描述
|
||||
* @since Beta v0.10.0
|
||||
* @param status - 历练点奖励状态
|
||||
* @returns 历练点奖励状态描述
|
||||
*/
|
||||
function getAttendanceRewardStatusDesc(
|
||||
status: TGApp.Game.DailyNote.AttendanceRewardStatusEnum,
|
||||
): string {
|
||||
switch (status) {
|
||||
case AttendanceRewardStatusEnum.UNFINISHED:
|
||||
return "未完成";
|
||||
case AttendanceRewardStatusEnum.TAKEN:
|
||||
return "已领取";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 参量质变仪状态枚举
|
||||
* @since Beta v0.10.0
|
||||
* @see TGApp.Game.DailyNote.TransformerStatusEnum
|
||||
*/
|
||||
const TransformerStatusEnum: typeof TGApp.Game.DailyNote.TransformerStatus = {
|
||||
OBTAINED: "Obtained",
|
||||
NOT_OBTAINED: "NotObtained",
|
||||
};
|
||||
|
||||
/**
|
||||
* 实时便笺枚举
|
||||
* @since Beta v0.10.0
|
||||
*/
|
||||
const dailyNoteEnum = {
|
||||
expedition: {
|
||||
status: ExpeditionStatusEnum,
|
||||
statusDesc: getExpeditionStatusDesc,
|
||||
},
|
||||
taskReward: {
|
||||
status: TaskRewardStatusEnum,
|
||||
statusDesc: getTaskRewardStatusDesc,
|
||||
},
|
||||
attendanceReward: {
|
||||
status: AttendanceRewardStatusEnum,
|
||||
statusDesc: getAttendanceRewardStatusDesc,
|
||||
},
|
||||
transformer: {
|
||||
status: TransformerStatusEnum,
|
||||
},
|
||||
};
|
||||
|
||||
export default dailyNoteEnum;
|
||||
@@ -399,7 +399,7 @@ async function handleHutaoDownload(uids: Array<string>): Promise<void> {
|
||||
await showLoading.end();
|
||||
showSnackbar.success("成功下载,即将刷新页面");
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, 1000));
|
||||
// window.location.reload();
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
async function handleHutaoDelete(uids: Array<string>): Promise<void> {
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
density="compact"
|
||||
label="首页组件显示"
|
||||
variant="outlined"
|
||||
width="360px"
|
||||
width="440px"
|
||||
/>
|
||||
<v-btn :rounded="true" class="select-btn" variant="elevated" @click="submitHome">
|
||||
确定
|
||||
@@ -72,6 +72,7 @@ import showDialog from "@comp/func/dialog.js";
|
||||
import showLoading from "@comp/func/loading.js";
|
||||
import showSnackbar from "@comp/func/snackbar.js";
|
||||
import PhCompCalendar from "@comp/pageHome/ph-comp-calendar.vue";
|
||||
import PhCompDailyNote from "@comp/pageHome/ph-comp-daily-note.vue";
|
||||
import PhCompPool from "@comp/pageHome/ph-comp-pool.vue";
|
||||
import PhCompPosition from "@comp/pageHome/ph-comp-position.vue";
|
||||
import PhCompSign from "@comp/pageHome/ph-comp-sign.vue";
|
||||
@@ -129,9 +130,10 @@ onMounted(async () => {
|
||||
await showLoading.start("正在加载首页小部件");
|
||||
games.value = gameList.value.map((i) => ({ icon: i.app_icon, title: i.name, gid: i.id }));
|
||||
showItems.value = homeStore.getShowItems();
|
||||
showItemsAll.value = ["游戏签到", "素材日历", "限时祈愿", "近期活动"];
|
||||
showItemsAll.value = ["游戏签到", "实时便笺", "素材日历", "限时祈愿", "近期活动"];
|
||||
} else {
|
||||
showItems.value = homeStore.getShowItems().filter((i) => i !== "游戏签到");
|
||||
showItemsAll.value = ["实时便笺", "素材日历", "限时祈愿", "近期活动"];
|
||||
}
|
||||
oldItems.value = showItems.value;
|
||||
await loadComp();
|
||||
@@ -157,6 +159,13 @@ async function loadComp(): Promise<void> {
|
||||
showSnackbar.warn("未登录不可设置游戏签到组件");
|
||||
}
|
||||
break;
|
||||
case "实时便笺":
|
||||
if (isLogin.value) {
|
||||
temp.push(PhCompDailyNote);
|
||||
} else {
|
||||
showSnackbar.warn("未登录不可设置实时便笺组件");
|
||||
}
|
||||
break;
|
||||
case "限时祈愿":
|
||||
temp.push(PhCompPool);
|
||||
break;
|
||||
@@ -191,6 +200,8 @@ function getName(name: string): string | undefined {
|
||||
switch (name) {
|
||||
case "ph-comp-sign":
|
||||
return "游戏签到";
|
||||
case "ph-comp-daily-note":
|
||||
return "实时便笺";
|
||||
case "ph-comp-pool":
|
||||
return "限时祈愿";
|
||||
case "ph-comp-position":
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* 首页组件状态
|
||||
* @since Beta v0.9.0
|
||||
* @since Beta v0.10.0
|
||||
*/
|
||||
|
||||
import { defineStore } from "pinia";
|
||||
@@ -10,10 +10,11 @@ import { ref } from "vue";
|
||||
* 默认展示项
|
||||
*/
|
||||
const defaultHomeShow: Array<TGApp.Store.Home.ShowItem> = [
|
||||
{ show: false, order: 4, label: "游戏签到" },
|
||||
{ show: true, order: 1, label: "限时祈愿" },
|
||||
{ show: true, order: 2, label: "近期活动" },
|
||||
{ show: true, order: 3, label: "素材日历" },
|
||||
{ show: false, order: 4, label: "游戏签到" },
|
||||
{ show: true, order: 5, label: "实时便笺" },
|
||||
];
|
||||
|
||||
const useHomeStore = defineStore("home", () => {
|
||||
|
||||
112
src/types/Game/DailyNote.d.ts
vendored
112
src/types/Game/DailyNote.d.ts
vendored
@@ -128,13 +128,8 @@ declare namespace TGApp.Game.DailyNote {
|
||||
* @since Beta v0.10.0
|
||||
*/
|
||||
type DailyTaskReward = {
|
||||
/**
|
||||
* 状态
|
||||
* @todo 枚举
|
||||
* @example
|
||||
* - TaskRewardStatusUnfinished
|
||||
*/
|
||||
status: string;
|
||||
/** 状态 */
|
||||
status: TaskRewardStatusEnum;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -142,13 +137,8 @@ declare namespace TGApp.Game.DailyNote {
|
||||
* @since Beta v0.10.0
|
||||
*/
|
||||
type AttendanceReward = {
|
||||
/**
|
||||
* 状态
|
||||
* @todo 枚举
|
||||
* @example
|
||||
* - AttendanceRewardStatusTakenAward
|
||||
*/
|
||||
status: string;
|
||||
/** 状态 */
|
||||
status: AttendanceRewardStatusEnum;
|
||||
/**
|
||||
* 进度
|
||||
* @remarks 2000 点满
|
||||
@@ -174,5 +164,97 @@ declare namespace TGApp.Game.DailyNote {
|
||||
};
|
||||
|
||||
/** 任务 */
|
||||
type ArchonQuest = unknown;
|
||||
type ArchonQuest = {
|
||||
/**
|
||||
* 章节数
|
||||
* @example 第二章 第三幕
|
||||
*/
|
||||
chapter_num: string;
|
||||
/**
|
||||
* 章节标题
|
||||
* @example 千手百眼,天下人间
|
||||
*/
|
||||
chapter_title: string;
|
||||
/**
|
||||
* 章节类型
|
||||
* @todo 枚举
|
||||
* @example 1
|
||||
*/
|
||||
chapter_type: number;
|
||||
/** 任务ID */
|
||||
id: number;
|
||||
/**
|
||||
* 任务状态
|
||||
* @todo 枚举
|
||||
* @example
|
||||
* - StatusNotOpen
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* 探索派遣状态
|
||||
* @since Beta v0.10.0
|
||||
* @todo 待确认
|
||||
*/
|
||||
const ExpeditionStatus = <const>{
|
||||
ONGOING: "Ongoing",
|
||||
FINISHED: "Finished",
|
||||
};
|
||||
|
||||
/**
|
||||
* 探索派遣状态类型
|
||||
* @since Beta v0.10.0
|
||||
*/
|
||||
type ExpeditionStatusEnum = (typeof ExpeditionStatus)[keyof typeof ExpeditionStatus];
|
||||
|
||||
/**
|
||||
* 任务奖励状态
|
||||
* @since Beta v0.10.0
|
||||
* @todo 待确认
|
||||
*/
|
||||
const TaskRewardStatus = <const>{
|
||||
UNFINISHED: "TaskRewardStatusUnfinished",
|
||||
FINISHED: "TaskRewardStatusFinished",
|
||||
RECEIVED: "TaskRewardStatusReceived",
|
||||
};
|
||||
|
||||
/**
|
||||
* 任务奖励状态类型
|
||||
* @since Beta v0.10.0
|
||||
*/
|
||||
type TaskRewardStatusEnum = (typeof TaskRewardStatus)[keyof typeof TaskRewardStatus];
|
||||
|
||||
/**
|
||||
* 历练点奖励状态
|
||||
* @since Beta v0.10.0
|
||||
* @todo 待确认
|
||||
*/
|
||||
const AttendanceRewardStatus = <const>{
|
||||
UNFINISHED: "AttendanceRewardStatusUnfinished",
|
||||
TAKEN: "AttendanceRewardStatusTakenAward",
|
||||
};
|
||||
|
||||
/**
|
||||
* 历练点奖励状态类型
|
||||
* @since Beta v0.10.0
|
||||
*/
|
||||
type AttendanceRewardStatusEnum =
|
||||
(typeof AttendanceRewardStatus)[keyof typeof AttendanceRewardStatus];
|
||||
|
||||
/**
|
||||
* 参量质变仪状态
|
||||
* @since Beta v0.10.0
|
||||
* @todo 待确认
|
||||
*/
|
||||
const TransformerStatus = <const>{
|
||||
OBTAINED: "Obtained",
|
||||
NOT_OBTAINED: "NotObtained",
|
||||
};
|
||||
|
||||
/**
|
||||
* 参量质变仪状态类型
|
||||
* @since Beta v0.10.0
|
||||
*/
|
||||
type TransformerStatusEnum = (typeof TransformerStatus)[keyof typeof TransformerStatus];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user