mirror of
https://github.com/BTMuli/TeyvatGuide.git
synced 2025-12-06 08:32:51 +08:00
🎈 perf(eslint): 第二次 eslint
剩下的全给过了,明天起来跑 devtool 改 bug Signed-off-by: BTMuli <BT-Muli@outlook.com> (cherry picked from commit 59baf08cf897d31cabce4741910ea83c1a3a52d9)
This commit is contained in:
@@ -29,9 +29,6 @@ rules:
|
||||
no-case-declarations: off # 禁止在 case 子句中使用词法声明
|
||||
no-useless-constructor: off # 禁用不必要的构造函数
|
||||
# rules of standard
|
||||
strictNullChecks: # 严格空值检查
|
||||
- warn
|
||||
- true
|
||||
array-callback-return:
|
||||
- error
|
||||
- allowImplicit: true
|
||||
@@ -65,9 +62,7 @@ rules:
|
||||
"@typescript-eslint/comma-dangle":
|
||||
- warn
|
||||
- always-multiline
|
||||
"@typescript-eslint/prefer-unllish-coalescing": # 优先使用 ?? 而不是 || 进行空值判断
|
||||
- warn
|
||||
- ignoreConditionalTests: true
|
||||
"@typescript-eslint/prefer-nullish-coalescing": off # on 会出现一些问题
|
||||
"@typescript-eslint/space-before-function-paren":
|
||||
- warn
|
||||
- always # 函数名和括号之间必须有空格
|
||||
|
||||
124
src/App.vue
124
src/App.vue
@@ -1,27 +1,27 @@
|
||||
<template>
|
||||
<div v-if="isMain">
|
||||
<v-layout>
|
||||
<!-- 侧边栏菜单 -->
|
||||
<t-sidebar />
|
||||
<!-- 主体内容 -->
|
||||
<v-main class="app-main">
|
||||
<v-container fluid>
|
||||
<router-view />
|
||||
</v-container>
|
||||
</v-main>
|
||||
</v-layout>
|
||||
</div>
|
||||
<div v-else>
|
||||
<v-layout>
|
||||
<!-- 主体内容 -->
|
||||
<v-main class="app-main">
|
||||
<v-container fluid>
|
||||
<router-view />
|
||||
</v-container>
|
||||
</v-main>
|
||||
</v-layout>
|
||||
</div>
|
||||
<t-back-top />
|
||||
<div v-if="isMain">
|
||||
<v-layout>
|
||||
<!-- 侧边栏菜单 -->
|
||||
<TSidebar />
|
||||
<!-- 主体内容 -->
|
||||
<v-main class="app-main">
|
||||
<v-container fluid>
|
||||
<router-view />
|
||||
</v-container>
|
||||
</v-main>
|
||||
</v-layout>
|
||||
</div>
|
||||
<div v-else>
|
||||
<v-layout>
|
||||
<!-- 主体内容 -->
|
||||
<v-main class="app-main">
|
||||
<v-container fluid>
|
||||
<router-view />
|
||||
</v-container>
|
||||
</v-main>
|
||||
</v-layout>
|
||||
</div>
|
||||
<TBackTop />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@@ -42,53 +42,53 @@ const appStore = useAppStore();
|
||||
const isMain = ref(true as boolean);
|
||||
|
||||
onMounted(async () => {
|
||||
// 获取当前窗口
|
||||
const win = await window.getCurrent();
|
||||
isMain.value = win.label === "tauri-genshin";
|
||||
if (isMain.value) {
|
||||
const title = "Tauri.Genshin v" + (await app.getVersion()) + " Alpha";
|
||||
await win.setTitle(title);
|
||||
await checkLoad();
|
||||
}
|
||||
// 获取当前窗口
|
||||
const win = await window.getCurrent();
|
||||
isMain.value = win.label === "tauri-genshin";
|
||||
if (isMain.value) {
|
||||
const title = "Tauri.Genshin v" + (await app.getVersion()) + " Alpha";
|
||||
await win.setTitle(title);
|
||||
await checkLoad();
|
||||
}
|
||||
});
|
||||
|
||||
async function checkLoad() {
|
||||
await appStore.check();
|
||||
if (appStore.loading) {
|
||||
console.info("数据已加载!");
|
||||
return;
|
||||
}
|
||||
DeleteTGData();
|
||||
await createDataDir();
|
||||
await writeData();
|
||||
await writeIndex();
|
||||
appStore.loading = true;
|
||||
console.info("数据加载完成!");
|
||||
async function checkLoad () {
|
||||
await appStore.check();
|
||||
if (appStore.loading) {
|
||||
console.info("数据已加载!");
|
||||
return;
|
||||
}
|
||||
DeleteTGData();
|
||||
await createDataDir();
|
||||
await writeData();
|
||||
await writeIndex();
|
||||
appStore.loading = true;
|
||||
console.info("数据加载完成!");
|
||||
}
|
||||
// 创建数据文件夹
|
||||
async function createDataDir() {
|
||||
console.info("开始创建数据文件夹...");
|
||||
await fs.createDir("appData", { dir: fs.BaseDirectory.AppLocalData, recursive: true });
|
||||
await fs.createDir("userData", { dir: fs.BaseDirectory.AppLocalData, recursive: true });
|
||||
await fs.createDir("tempData", { dir: fs.BaseDirectory.AppLocalData, recursive: true });
|
||||
console.info("数据文件夹创建完成!");
|
||||
async function createDataDir () {
|
||||
console.info("开始创建数据文件夹...");
|
||||
await fs.createDir("appData", { dir: fs.BaseDirectory.AppLocalData, recursive: true });
|
||||
await fs.createDir("userData", { dir: fs.BaseDirectory.AppLocalData, recursive: true });
|
||||
await fs.createDir("tempData", { dir: fs.BaseDirectory.AppLocalData, recursive: true });
|
||||
console.info("数据文件夹创建完成!");
|
||||
}
|
||||
// 将数据写入文件夹
|
||||
async function writeData() {
|
||||
console.info("开始写入数据...");
|
||||
TGAppDataList.map(async item => {
|
||||
await fs.writeFile(`${appStore.dataPath.app}\\${item.name}`, JSON.stringify(item.data));
|
||||
});
|
||||
console.info("数据写入完成!");
|
||||
async function writeData () {
|
||||
console.info("开始写入数据...");
|
||||
TGAppDataList.map(async (item) => {
|
||||
await fs.writeFile(`${appStore.dataPath.app}\\${item.name}`, JSON.stringify(item.data));
|
||||
});
|
||||
console.info("数据写入完成!");
|
||||
}
|
||||
// 写入 IndexedDB
|
||||
async function writeIndex() {
|
||||
console.info("开始写入 IndexedDB...");
|
||||
await InitTGData();
|
||||
TGGetDataList.map(async item => {
|
||||
await WriteTGData(item.name, item.data);
|
||||
});
|
||||
console.info("IndexedDB 写入完成!");
|
||||
async function writeIndex () {
|
||||
console.info("开始写入 IndexedDB...");
|
||||
await InitTGData();
|
||||
TGGetDataList.map(async (item) => {
|
||||
await WriteTGData(item.name, item.data);
|
||||
});
|
||||
console.info("IndexedDB 写入完成!");
|
||||
}
|
||||
</script>
|
||||
<style lang="css">
|
||||
|
||||
@@ -1,50 +1,50 @@
|
||||
<template>
|
||||
<transition name="fade">
|
||||
<div class="back-top" v-show="canTop" @click="handleScrollTop">
|
||||
<img src="../assets/icons/arrow-top.svg" alt="back-icon" />
|
||||
</div>
|
||||
</transition>
|
||||
<transition name="fade">
|
||||
<div v-show="canTop" class="back-top" @click="handleScrollTop">
|
||||
<img src="../assets/icons/arrow-top.svg" alt="back-icon">
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { ref, onMounted } from "vue";
|
||||
|
||||
const scrollTop = ref(0); //滚动条距离顶部的距离
|
||||
const canTop = ref(false); //默认不显示
|
||||
const scrollTop = ref(0); // 滚动条距离顶部的距离
|
||||
const canTop = ref(false); // 默认不显示
|
||||
|
||||
// 监听滚动事件
|
||||
function handleScroll() {
|
||||
scrollTop.value = document.documentElement.scrollTop || document.body.scrollTop;
|
||||
// 超过500px显示回到顶部按钮
|
||||
canTop.value = scrollTop.value > 500;
|
||||
// 没超过500,但是到底部了,也显示回到顶部按钮
|
||||
if (!canTop.value) {
|
||||
const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
|
||||
const clientHeight = document.documentElement.clientHeight || document.body.clientHeight;
|
||||
canTop.value = scrollHeight - clientHeight - scrollTop.value <= 0;
|
||||
}
|
||||
function handleScroll () {
|
||||
scrollTop.value = document.documentElement.scrollTop || document.body.scrollTop;
|
||||
// 超过500px显示回到顶部按钮
|
||||
canTop.value = scrollTop.value > 500;
|
||||
// 没超过500,但是到底部了,也显示回到顶部按钮
|
||||
if (!canTop.value) {
|
||||
const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
|
||||
const clientHeight = document.documentElement.clientHeight || document.body.clientHeight;
|
||||
canTop.value = scrollHeight - clientHeight - scrollTop.value <= 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 点击回到顶部
|
||||
function handleScrollTop() {
|
||||
let timer = 0;
|
||||
cancelAnimationFrame(timer);
|
||||
timer = requestAnimationFrame(function fn() {
|
||||
if (scrollTop.value > 0) {
|
||||
scrollTop.value -= 50;
|
||||
document.body.scrollTop = document.documentElement.scrollTop = scrollTop.value;
|
||||
timer = requestAnimationFrame(fn);
|
||||
} else {
|
||||
cancelAnimationFrame(timer);
|
||||
canTop.value = false;
|
||||
}
|
||||
});
|
||||
function handleScrollTop () {
|
||||
let timer = 0;
|
||||
cancelAnimationFrame(timer);
|
||||
timer = requestAnimationFrame(function fn () {
|
||||
if (scrollTop.value > 0) {
|
||||
scrollTop.value -= 50;
|
||||
document.body.scrollTop = document.documentElement.scrollTop = scrollTop.value;
|
||||
timer = requestAnimationFrame(fn);
|
||||
} else {
|
||||
cancelAnimationFrame(timer);
|
||||
canTop.value = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 监听滚动事件
|
||||
// @ts-ignore
|
||||
onMounted(() => {
|
||||
window.addEventListener("scroll", handleScroll);
|
||||
window.addEventListener("scroll", handleScroll);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,52 +1,42 @@
|
||||
<template>
|
||||
<v-list class="calendar-card">
|
||||
<v-list-item>
|
||||
<v-list-item-title
|
||||
style="color: #fec90b; margin-left: 10px; margin-bottom: 10px; font-family: Genshin, serif"
|
||||
>
|
||||
<v-icon color="#EBD49E">mdi-calendar-clock</v-icon> 今日素材
|
||||
<span style="color: #faf7e8">{{ new Date().toLocaleDateString() }}</span>
|
||||
<v-btn
|
||||
v-for="text of btnText"
|
||||
@click="getShowCards(text.week)"
|
||||
class="calendar-btn"
|
||||
:style="{
|
||||
border: text.week === weekNow ? '2px solid #fec90b' : '0',
|
||||
background: text.week === btnNow ? '#fec90b' : '#4A5366',
|
||||
}"
|
||||
>
|
||||
{{ text.text }}
|
||||
</v-btn>
|
||||
</v-list-item-title>
|
||||
<div v-if="!loading" class="calendar-grid">
|
||||
<v-card title="天赋培养" class="calendar-single">
|
||||
<v-card-text class="calendar-icons">
|
||||
<v-img
|
||||
v-for="item in showCharacters"
|
||||
:src="item.cover"
|
||||
class="calendar-icon"
|
||||
@click="showContent(item)"
|
||||
/>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
<v-card title="武器突破" class="calendar-single">
|
||||
<v-card-text class="calendar-icons">
|
||||
<v-img
|
||||
v-for="item in showWeapons"
|
||||
:src="item.cover"
|
||||
class="calendar-icon"
|
||||
@click="showContent(item)"
|
||||
/>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<v-list class="calendar-card">
|
||||
<v-list-item>
|
||||
<v-list-item-title style="color: #fec90b; margin-left: 10px; margin-bottom: 10px; font-family: Genshin, serif">
|
||||
<v-icon color="#EBD49E">
|
||||
mdi-calendar-clock
|
||||
</v-icon> 今日素材
|
||||
<span style="color: #faf7e8">{{ new Date().toLocaleDateString() }}</span>
|
||||
<v-btn
|
||||
v-for="text of btnText"
|
||||
:key="text.week"
|
||||
class="calendar-btn"
|
||||
:style="{
|
||||
border: text.week === weekNow ? '2px solid #fec90b' : '0',
|
||||
background: text.week === btnNow ? '#fec90b' : '#4A5366',
|
||||
}"
|
||||
@click="getShowCards(text.week)"
|
||||
>
|
||||
{{ text.text }}
|
||||
</v-btn>
|
||||
</v-list-item-title>
|
||||
<div v-if="!loading" class="calendar-grid">
|
||||
<v-card title="天赋培养" class="calendar-single">
|
||||
<v-card-text class="calendar-icons">
|
||||
<v-img v-for="item in showCharacters" :key="item.id" :src="item.cover" class="calendar-icon" @click="showContent(item)" />
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
<v-card title="武器突破" class="calendar-single">
|
||||
<v-card-text class="calendar-icons">
|
||||
<v-img v-for="item in showWeapons" :key="item.id" :src="item.cover" class="calendar-icon" @click="showContent(item)" />
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
import { ref, onMounted } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
// plugins
|
||||
import MysOper from "../plugins/Mys";
|
||||
// interface
|
||||
@@ -63,77 +53,76 @@ const weekNow = ref(new Date().getDay());
|
||||
const btnNow = ref(0 as number);
|
||||
|
||||
const btnText = [
|
||||
{
|
||||
week: 0,
|
||||
text: "周日",
|
||||
},
|
||||
{
|
||||
week: 1,
|
||||
text: "周一",
|
||||
},
|
||||
{
|
||||
week: 2,
|
||||
text: "周二",
|
||||
},
|
||||
{
|
||||
week: 3,
|
||||
text: "周三",
|
||||
},
|
||||
{
|
||||
week: 4,
|
||||
text: "周四",
|
||||
},
|
||||
{
|
||||
week: 5,
|
||||
text: "周五",
|
||||
},
|
||||
{
|
||||
week: 6,
|
||||
text: "周六",
|
||||
},
|
||||
{
|
||||
week: 0,
|
||||
text: "周日",
|
||||
},
|
||||
{
|
||||
week: 1,
|
||||
text: "周一",
|
||||
},
|
||||
{
|
||||
week: 2,
|
||||
text: "周二",
|
||||
},
|
||||
{
|
||||
week: 3,
|
||||
text: "周三",
|
||||
},
|
||||
{
|
||||
week: 4,
|
||||
text: "周四",
|
||||
},
|
||||
{
|
||||
week: 5,
|
||||
text: "周五",
|
||||
},
|
||||
{
|
||||
week: 6,
|
||||
text: "周六",
|
||||
},
|
||||
];
|
||||
|
||||
// show data
|
||||
const showCharacters = ref([] as CalendarCard[]);
|
||||
const showWeapons = ref([] as CalendarCard[]);
|
||||
const router = useRouter();
|
||||
|
||||
// expose
|
||||
defineExpose({
|
||||
name: "素材日历",
|
||||
loading,
|
||||
name: "素材日历",
|
||||
loading,
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
const calendarData = await MysOper.Calendar.get();
|
||||
if (!calendarData) {
|
||||
await console.error("获取材料日历失败");
|
||||
return;
|
||||
}
|
||||
const calendarCards = MysOper.Calendar.card(calendarData);
|
||||
const week = new Date().getDay();
|
||||
btnNow.value = week;
|
||||
characterCards.value = calendarCards.filter(card => card.type === 2);
|
||||
weaponCards.value = calendarCards.filter(card => card.type === 1);
|
||||
getShowCards(week);
|
||||
loading.value = false;
|
||||
const calendarData = await MysOper.Calendar.get();
|
||||
if (!calendarData) {
|
||||
await console.error("获取材料日历失败");
|
||||
return;
|
||||
}
|
||||
const calendarCards = MysOper.Calendar.card(calendarData);
|
||||
const week = new Date().getDay();
|
||||
btnNow.value = week;
|
||||
characterCards.value = calendarCards.filter((card) => card.type === 2);
|
||||
weaponCards.value = calendarCards.filter((card) => card.type === 1);
|
||||
getShowCards(week);
|
||||
loading.value = false;
|
||||
});
|
||||
|
||||
// 根据星期几获取显示内容
|
||||
function getShowCards(choice: number) {
|
||||
btnNow.value = choice;
|
||||
const week = choice === 0 ? 7 : choice;
|
||||
showCharacters.value = characterCards.value
|
||||
.filter(card => card.drop_day.includes(week.toString()))
|
||||
.sort((a, b) => a.sort_day[week] - b.sort_day[week]);
|
||||
showWeapons.value = weaponCards.value
|
||||
.filter(card => card.drop_day.includes(week.toString()))
|
||||
.sort((a, b) => a.sort_day[week] - b.sort_day[week]);
|
||||
function getShowCards (choice: number) {
|
||||
btnNow.value = choice;
|
||||
const week = choice === 0 ? 7 : choice;
|
||||
showCharacters.value = characterCards.value
|
||||
.filter((card) => card.drop_day.includes(week.toString()))
|
||||
.sort((a, b) => a.sort_day[week] - b.sort_day[week]);
|
||||
showWeapons.value = weaponCards.value
|
||||
.filter((card) => card.drop_day.includes(week.toString()))
|
||||
.sort((a, b) => a.sort_day[week] - b.sort_day[week]);
|
||||
}
|
||||
|
||||
function showContent(item: CalendarCard) {
|
||||
// todo:二级跳转,目前先直接跳到角色详情页
|
||||
window.open(OBC_CONTENT_API.replace("{content_id}", item.url));
|
||||
function showContent (item: CalendarCard) {
|
||||
// todo:二级跳转,目前先直接跳到角色详情页
|
||||
window.open(OBC_CONTENT_API.replace("{content_id}", item.url));
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
<template>
|
||||
<v-overlay v-model="visible">
|
||||
<div class="confirm-div">
|
||||
<div class="confirm-box">
|
||||
<div class="confirm-title">
|
||||
{{ title }}
|
||||
</div>
|
||||
<div class="confirm-btn-box">
|
||||
<button class="confirm-btn" @click="onCancel">
|
||||
<img class="btn-icon" src="../assets/icons/circle-cancel.svg" alt="cancel" />
|
||||
<span class="btn-text">
|
||||
{{ cancel }}
|
||||
</span>
|
||||
</button>
|
||||
<button class="confirm-btn" @click="onConfirm">
|
||||
<img class="btn-icon" src="../assets/icons/circle-check.svg" alt="confirm" />
|
||||
<span class="btn-text">
|
||||
{{ confirm }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</v-overlay>
|
||||
<v-overlay v-model="visible">
|
||||
<div class="confirm-div">
|
||||
<div class="confirm-box">
|
||||
<div class="confirm-title">
|
||||
{{ title }}
|
||||
</div>
|
||||
<div class="confirm-btn-box">
|
||||
<button class="confirm-btn" @click="onCancel">
|
||||
<img class="btn-icon" src="../assets/icons/circle-cancel.svg" alt="cancel">
|
||||
<span class="btn-text">
|
||||
{{ cancel }}
|
||||
</span>
|
||||
</button>
|
||||
<button class="confirm-btn" @click="onConfirm">
|
||||
<img class="btn-icon" src="../assets/icons/circle-check.svg" alt="confirm">
|
||||
<span class="btn-text">
|
||||
{{ confirm }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</v-overlay>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@@ -29,40 +29,40 @@
|
||||
import { computed } from "vue";
|
||||
|
||||
interface TConfirmProps {
|
||||
title: string;
|
||||
cancel?: string;
|
||||
confirm?: string;
|
||||
/** 此值为 true 时显示对话框 */
|
||||
modelValue: boolean;
|
||||
title: string;
|
||||
cancel?: string;
|
||||
confirm?: string;
|
||||
/** 此值为 true 时显示对话框 */
|
||||
modelValue: boolean;
|
||||
}
|
||||
|
||||
interface TConfirmEmits {
|
||||
(e: "update:show", v: boolean): void;
|
||||
(e: "update:modelValue", v: boolean): void;
|
||||
(e: "confirm"): void;
|
||||
(e: "cancel"): void;
|
||||
(e: "update:show", v: boolean): void;
|
||||
(e: "update:modelValue", v: boolean): void;
|
||||
(e: "confirm"): void;
|
||||
(e: "cancel"): void;
|
||||
}
|
||||
|
||||
const emits = defineEmits<TConfirmEmits>();
|
||||
const props = withDefaults(defineProps<TConfirmProps>(), {
|
||||
title: "确认",
|
||||
cancel: "取消",
|
||||
confirm: "确定",
|
||||
title: "确认",
|
||||
cancel: "取消",
|
||||
confirm: "确定",
|
||||
});
|
||||
|
||||
const visible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: v => emits("update:modelValue", v),
|
||||
get: () => props.modelValue,
|
||||
set: (v) => emits("update:modelValue", v),
|
||||
});
|
||||
|
||||
const onCancel = () => {
|
||||
visible.value = false;
|
||||
emits("cancel");
|
||||
visible.value = false;
|
||||
emits("cancel");
|
||||
};
|
||||
|
||||
const onConfirm = () => {
|
||||
visible.value = false;
|
||||
emits("confirm");
|
||||
visible.value = false;
|
||||
emits("confirm");
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -73,7 +73,7 @@ const onConfirm = () => {
|
||||
height: 20vh;
|
||||
top: 40vh;
|
||||
left: 30vw;
|
||||
background: #ffffff;
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
|
||||
@@ -1,35 +1,43 @@
|
||||
<template>
|
||||
<div class="loading-div">
|
||||
<div class="loading-content">
|
||||
<div class="loading-title">
|
||||
{{ title }}
|
||||
<v-progress-circular indeterminate color="#f4d8a8" v-show="!empty" />
|
||||
</div>
|
||||
<div class="loading-subtitle" v-show="subtitle">{{ subtitle }}</div>
|
||||
<div class="loading-img" v-if="!empty">
|
||||
<img src="/source/UI/loading.webp" alt="loading" />
|
||||
</div>
|
||||
<div class="loading-img" v-else>
|
||||
<img src="/source/UI/empty.webp" alt="empty" />
|
||||
</div>
|
||||
<div class="loading-text" v-show="content">{{ content }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="loading-div">
|
||||
<div class="loading-content">
|
||||
<div class="loading-title">
|
||||
{{ title }}
|
||||
<v-progress-circular v-show="!empty" indeterminate color="#f4d8a8" />
|
||||
</div>
|
||||
<div v-if="subtitle !== ''" class="loading-subtitle">
|
||||
{{ subtitle }}
|
||||
</div>
|
||||
<div v-if="!empty" class="loading-img">
|
||||
<img src="/source/UI/loading.webp" alt="loading">
|
||||
</div>
|
||||
<div v-else class="loading-img">
|
||||
<img src="/source/UI/empty.webp" alt="empty">
|
||||
</div>
|
||||
<div v-if="content !== ''" class="loading-text">
|
||||
{{ content }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
interface LoadingProps {
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
content?: string;
|
||||
empty?: boolean;
|
||||
position?: string;
|
||||
}
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
content?: string;
|
||||
empty?: boolean;
|
||||
position?: string;
|
||||
}>(),
|
||||
{
|
||||
title: "加载中",
|
||||
empty: false,
|
||||
position: "absolute",
|
||||
}
|
||||
defineProps<LoadingProps>(),
|
||||
{
|
||||
title: "加载中",
|
||||
subtitle: "",
|
||||
content: "",
|
||||
empty: false,
|
||||
position: "absolute",
|
||||
},
|
||||
);
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
@@ -40,7 +48,7 @@ withDefaults(
|
||||
left: 25%;
|
||||
width: 50%;
|
||||
height: 50%;
|
||||
background: rgba(57, 59, 64, 0.5);
|
||||
background: rgb(57 59 64 / 50%);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
@@ -1,56 +1,45 @@
|
||||
<template>
|
||||
<v-list class="pool-card">
|
||||
<v-list-item>
|
||||
<v-list-item-title style="color: #fec90b; margin-left: 10px; font-family: Genshin, serif">
|
||||
<img src="../assets/icons/icon-wish.svg" alt="wish" class="pool-wish-icon" />
|
||||
限时祈愿</v-list-item-title
|
||||
>
|
||||
<div v-if="!loading" class="pool-grid">
|
||||
<v-card
|
||||
v-for="pool in poolCards"
|
||||
style="background: #faf7e8; color: #546d8b; border-radius: 10px"
|
||||
>
|
||||
<v-list style="background: #faf7e8; color: #546d8b">
|
||||
<v-list-item :title="pool.title" :subtitle="pool.subtitle">
|
||||
<template v-slot:prepend>
|
||||
<v-img
|
||||
:src="pool.voice.icon"
|
||||
style="transform: translate(0, -10px); width: 60px; height: 60px"
|
||||
/>
|
||||
</template>
|
||||
<template v-slot:append>
|
||||
<audio :src="pool.voice.url" controls />
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<div class="pool-cover" @click="toPost(pool)">
|
||||
<img :src="pool.cover" alt="cover" />
|
||||
</div>
|
||||
<div class="pool-character">
|
||||
<div v-for="character in pool.characters" @click="toOuter(character.url, pool.title)">
|
||||
<img :src="character.icon" class="pool-icon" alt="character" />
|
||||
</div>
|
||||
<div class="pool-clock">
|
||||
<v-progress-circular
|
||||
:model-value="poolTimePass[pool.post_id]"
|
||||
size="100"
|
||||
width="10"
|
||||
color="#90caf9"
|
||||
>
|
||||
{{ poolTimeGet[pool.post_id] }}
|
||||
</v-progress-circular>
|
||||
</div>
|
||||
</div>
|
||||
<v-card-text>
|
||||
<span style="width: 60%">
|
||||
<v-icon>mdi-calendar-clock</v-icon>
|
||||
{{ pool.time.start }}~{{ pool.time.end }}
|
||||
</span>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<v-list class="pool-card">
|
||||
<v-list-item>
|
||||
<v-list-item-title style="color: #fec90b; margin-left: 10px; font-family: Genshin, serif">
|
||||
<img src="../assets/icons/icon-wish.svg" alt="wish" class="pool-wish-icon">
|
||||
限时祈愿
|
||||
</v-list-item-title>
|
||||
<div v-if="!loading" class="pool-grid">
|
||||
<v-card v-for="pool in poolCards" :key="pool.post_id" style="background: #faf7e8; color: #546d8b; border-radius: 10px">
|
||||
<v-list style="background: #faf7e8; color: #546d8b">
|
||||
<v-list-item :title="pool.title" :subtitle="pool.subtitle">
|
||||
<template #prepend>
|
||||
<v-img :src="pool.voice.icon" style="transform: translate(0, -10px); width: 60px; height: 60px" />
|
||||
</template>
|
||||
<template #append>
|
||||
<audio :src="pool.voice.url" controls />
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<div class="pool-cover" @click="toPost(pool)">
|
||||
<img :src="pool.cover" alt="cover">
|
||||
</div>
|
||||
<div class="pool-character">
|
||||
<div v-for="character in pool.characters" :key="character.url" @click="toOuter(character.url, pool.title)">
|
||||
<img :src="character.icon" class="pool-icon" alt="character">
|
||||
</div>
|
||||
<div class="pool-clock">
|
||||
<v-progress-circular :model-value="poolTimePass[pool.post_id]" size="100" width="10" color="#90caf9">
|
||||
{{ poolTimeGet[pool.post_id] }}
|
||||
</v-progress-circular>
|
||||
</div>
|
||||
</div>
|
||||
<v-card-text>
|
||||
<span style="width: 60%">
|
||||
<v-icon>mdi-calendar-clock</v-icon>
|
||||
{{ pool.time.start }}~{{ pool.time.end }}
|
||||
</span>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
@@ -82,78 +71,87 @@ const poolTimePass = ref({} as Map<number>);
|
||||
|
||||
// expose
|
||||
defineExpose({
|
||||
name: "限时祈愿",
|
||||
loading,
|
||||
name: "限时祈愿",
|
||||
loading,
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
const gachaData = await MysOper.Gacha.get();
|
||||
if (!gachaData) {
|
||||
await console.error("获取限时祈愿数据失败");
|
||||
return;
|
||||
}
|
||||
if (!checkCover(gachaData)) {
|
||||
poolCards.value = await MysOper.Gacha.card(gachaData);
|
||||
let coverData: Map<string> = {};
|
||||
poolCards.value.map(pool => {
|
||||
coverData[pool.post_id] = pool.cover;
|
||||
});
|
||||
homeStore.poolCover = coverData;
|
||||
} else {
|
||||
poolCards.value = await MysOper.Gacha.card(gachaData, homeStore.poolCover);
|
||||
}
|
||||
poolCards.value.map(pool => {
|
||||
poolTimeGet.value[pool.post_id] = getLastPoolTime(pool.time.end_stamp - Date.now());
|
||||
poolTimePass.value[pool.post_id] = pool.time.end_stamp - Date.now();
|
||||
});
|
||||
await setInterval(() => {
|
||||
poolCards.value.map(pool => {
|
||||
poolTimeGet.value[pool.post_id] = getLastPoolTime(pool.time.end_stamp - Date.now());
|
||||
poolTimePass.value[pool.post_id] =
|
||||
const gachaData = await MysOper.Gacha.get();
|
||||
if (!gachaData) {
|
||||
await console.error("获取限时祈愿数据失败");
|
||||
return;
|
||||
}
|
||||
if (!checkCover(gachaData)) {
|
||||
poolCards.value = await MysOper.Gacha.card(gachaData);
|
||||
const coverData: Map<string> = {};
|
||||
poolCards.value.map((pool) => {
|
||||
coverData[pool.post_id] = pool.cover;
|
||||
return pool;
|
||||
});
|
||||
homeStore.poolCover = coverData;
|
||||
} else {
|
||||
poolCards.value = await MysOper.Gacha.card(gachaData, homeStore.poolCover);
|
||||
}
|
||||
poolCards.value.map((pool) => {
|
||||
poolTimeGet.value[pool.post_id] = getLastPoolTime(pool.time.end_stamp - Date.now());
|
||||
poolTimePass.value[pool.post_id] = pool.time.end_stamp - Date.now();
|
||||
return pool;
|
||||
});
|
||||
await setInterval(() => {
|
||||
poolCards.value.map((pool) => {
|
||||
poolTimeGet.value[pool.post_id] = getLastPoolTime(pool.time.end_stamp - Date.now());
|
||||
poolTimePass.value[pool.post_id] =
|
||||
((pool.time.end_stamp - Date.now()) / (pool.time.end_stamp - pool.time.start_stamp)) * 100;
|
||||
});
|
||||
}, 1000);
|
||||
loading.value = false;
|
||||
return pool;
|
||||
});
|
||||
}, 1000);
|
||||
loading.value = false;
|
||||
});
|
||||
|
||||
// 检测是否有新的限时祈愿
|
||||
function checkCover(data: GachaData[]) {
|
||||
// 如果没有缓存
|
||||
if (!homeStore.poolCover || Object.keys(homeStore.poolCover).length === 0) {
|
||||
return false;
|
||||
}
|
||||
// 获取缓存
|
||||
const cover = homeStore.poolCover;
|
||||
return data.every(item => {
|
||||
const post_id = item.activity_url.split("/").pop();
|
||||
if (!post_id || isNaN(Number(post_id))) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
cover[Number(post_id)] !== undefined && cover[Number(post_id)] !== "/source/UI/empty.webp"
|
||||
);
|
||||
});
|
||||
function checkCover (data: GachaData[]) {
|
||||
// 如果没有缓存
|
||||
if (!homeStore.poolCover || Object.keys(homeStore.poolCover).length === 0) {
|
||||
return false;
|
||||
}
|
||||
// 获取缓存
|
||||
const cover = homeStore.poolCover satisfies Map<string>;
|
||||
if (cover === undefined || cover === null) {
|
||||
return false;
|
||||
}
|
||||
return data.every((item) => {
|
||||
const post_id = item.activity_url.split("/").pop();
|
||||
if (!post_id || isNaN(Number(post_id))) {
|
||||
return false;
|
||||
}
|
||||
if (!Object.keys(cover).includes(post_id)) {
|
||||
return false;
|
||||
} else {
|
||||
const coverUrl = Object.keys(cover).find((key) => key === post_id);
|
||||
return coverUrl !== "/source/UI/empty.webp";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function toOuter(url: string, title: string) {
|
||||
createTGWindow(url, "祈愿", title, 1200, 800, true);
|
||||
function toOuter (url: string, title: string) {
|
||||
createTGWindow(url, "祈愿", title, 1200, 800, true);
|
||||
}
|
||||
|
||||
function getLastPoolTime(time: number) {
|
||||
const hour = Math.floor(time / 1000 / 60 / 60);
|
||||
const minute = Math.floor((time / 1000 / 60 / 60 - hour) * 60);
|
||||
const second = Math.floor(((time / 1000 / 60 / 60 - hour) * 60 - minute) * 60);
|
||||
return `${hour}:${minute.toFixed(0).padStart(2, "0")}:${second.toFixed(0).padStart(2, "0")}`;
|
||||
function getLastPoolTime (time: number) {
|
||||
const hour = Math.floor(time / 1000 / 60 / 60);
|
||||
const minute = Math.floor((time / 1000 / 60 / 60 - hour) * 60);
|
||||
const second = Math.floor(((time / 1000 / 60 / 60 - hour) * 60 - minute) * 60);
|
||||
return `${hour}:${minute.toFixed(0).padStart(2, "0")}:${second.toFixed(0).padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
function toPost(pool: GachaCard) {
|
||||
const path = router.resolve({
|
||||
name: "帖子详情",
|
||||
params: {
|
||||
post_id: pool.post_id.toString(),
|
||||
},
|
||||
}).href;
|
||||
createTGWindow(path, "限时祈愿", pool.title, 960, 720, false);
|
||||
function toPost (pool: GachaCard) {
|
||||
const path = router.resolve({
|
||||
name: "帖子详情",
|
||||
params: {
|
||||
post_id: pool.post_id.toString(),
|
||||
},
|
||||
}).href;
|
||||
createTGWindow(path, "限时祈愿", pool.title, 960, 720, false);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -165,7 +163,7 @@ function toPost(pool: GachaCard) {
|
||||
}
|
||||
|
||||
.pool-card {
|
||||
font-family: "Genshin", serif;
|
||||
font-family: Genshin, serif;
|
||||
width: 100%;
|
||||
background: #546d8b;
|
||||
border-radius: 10px;
|
||||
|
||||
@@ -1,52 +1,47 @@
|
||||
<template>
|
||||
<v-list class="position-card">
|
||||
<v-list-item>
|
||||
<v-list-item-title style="color: #fec90b; margin-left: 10px; font-family: Genshin, serif">
|
||||
<img src="../assets/icons/board.svg" alt="act" class="position-act-icon" />
|
||||
近期活动
|
||||
</v-list-item-title>
|
||||
<div v-if="!loading" class="position-grid">
|
||||
<v-card
|
||||
v-for="card in positionCards"
|
||||
style="background: #faf7e8; color: #546d8b; border-radius: 10px"
|
||||
>
|
||||
<v-list style="background: #faf7e8; color: #546d8b">
|
||||
<v-list-item :title="card.title" :subtitle="card.abstract">
|
||||
<template v-slot:prepend>
|
||||
<v-avatar rounded="0" @click="toPost(card)" style="cursor: pointer">
|
||||
<v-img :src="card.icon" style="border-radius: 10px" />
|
||||
</v-avatar>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<v-divider class="border-opacity-75"></v-divider>
|
||||
<v-card-text>
|
||||
<span style="width: 60%">
|
||||
<v-icon>mdi-calendar-clock</v-icon>
|
||||
{{ card.time.start }}~{{ card.time.end }}
|
||||
</span>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<span style="width: 80%; margin-left: 10px">
|
||||
<v-icon>mdi-clock-outline</v-icon>
|
||||
剩余时间:
|
||||
<span style="color: #90caf9" v-if="positionTimeGet[card.post_id] !== '已结束'">{{
|
||||
positionTimeGet[card.post_id]
|
||||
}}</span>
|
||||
<span style="color: #ff6d6d" v-if="positionTimeGet[card.post_id] === '已结束'"
|
||||
>已结束</span
|
||||
>
|
||||
</span>
|
||||
<v-btn @click="toPost(card)" class="card-btn">
|
||||
<template v-slot:prepend>
|
||||
<img src="../assets/icons/circle-check.svg" alt="check" />查看
|
||||
</template>
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</div>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<v-list class="position-card">
|
||||
<v-list-item>
|
||||
<v-list-item-title style="color: #fec90b; margin-left: 10px; font-family: Genshin, serif">
|
||||
<img src="../assets/icons/board.svg" alt="act" class="position-act-icon">
|
||||
近期活动
|
||||
</v-list-item-title>
|
||||
<div v-if="!loading" class="position-grid">
|
||||
<v-card v-for="card in positionCards" :key="card.post_id" style="background: #faf7e8; color: #546d8b; border-radius: 10px">
|
||||
<v-list style="background: #faf7e8; color: #546d8b">
|
||||
<v-list-item :title="card.title" :subtitle="card.abstract">
|
||||
<template #prepend>
|
||||
<v-avatar rounded="0" style="cursor: pointer" @click="toPost(card)">
|
||||
<v-img :src="card.icon" style="border-radius: 10px" />
|
||||
</v-avatar>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<v-divider class="border-opacity-75" />
|
||||
<v-card-text>
|
||||
<span style="width: 60%">
|
||||
<v-icon>mdi-calendar-clock</v-icon>
|
||||
{{ card.time.start }}~{{ card.time.end }}
|
||||
</span>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<span style="width: 80%; margin-left: 10px">
|
||||
<v-icon>mdi-clock-outline</v-icon>
|
||||
剩余时间:
|
||||
<span v-if="positionTimeGet[card.post_id] !== '已结束'" style="color: #90caf9">{{
|
||||
positionTimeGet[card.post_id]
|
||||
}}</span>
|
||||
<span v-if="positionTimeGet[card.post_id] === '已结束'" style="color: #ff6d6d">已结束</span>
|
||||
</span>
|
||||
<v-btn class="card-btn" @click="toPost(card)">
|
||||
<template #prepend>
|
||||
<img src="../assets/icons/circle-check.svg" alt="check">查看
|
||||
</template>
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</div>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
@@ -71,55 +66,55 @@ const router = useRouter();
|
||||
|
||||
// expose
|
||||
defineExpose({
|
||||
name: "近期活动",
|
||||
loading,
|
||||
name: "近期活动",
|
||||
loading,
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
const positionData = await MysOper.Position.get();
|
||||
if (!positionData) {
|
||||
console.error("获取近期活动失败");
|
||||
return;
|
||||
}
|
||||
positionCards.value = MysOper.Position.card(positionData);
|
||||
positionCards.value.forEach(card => {
|
||||
positionTimeGet.value[card.post_id] = getLastPositionTime(card.time.end_stamp - Date.now());
|
||||
positionTimeEnd.value[card.post_id] = card.time.end_stamp;
|
||||
});
|
||||
await setInterval(() => {
|
||||
positionCards.value.forEach(card => {
|
||||
const time = card.time.end_stamp - Date.now();
|
||||
if (time <= 0) {
|
||||
positionTimeGet.value[card.post_id] = "已结束";
|
||||
return;
|
||||
}
|
||||
positionTimeGet.value[card.post_id] = getLastPositionTime(time);
|
||||
});
|
||||
}, 1000);
|
||||
loading.value = false;
|
||||
const positionData = await MysOper.Position.get();
|
||||
if (!positionData) {
|
||||
console.error("获取近期活动失败");
|
||||
return;
|
||||
}
|
||||
positionCards.value = MysOper.Position.card(positionData);
|
||||
positionCards.value.forEach((card) => {
|
||||
positionTimeGet.value[card.post_id] = getLastPositionTime(card.time.end_stamp - Date.now());
|
||||
positionTimeEnd.value[card.post_id] = card.time.end_stamp;
|
||||
});
|
||||
await setInterval(() => {
|
||||
positionCards.value.forEach((card) => {
|
||||
const time = card.time.end_stamp - Date.now();
|
||||
if (time <= 0) {
|
||||
positionTimeGet.value[card.post_id] = "已结束";
|
||||
return;
|
||||
}
|
||||
positionTimeGet.value[card.post_id] = getLastPositionTime(time);
|
||||
});
|
||||
}, 1000);
|
||||
loading.value = false;
|
||||
});
|
||||
|
||||
function getLastPositionTime(time: number) {
|
||||
const day = Math.floor(time / (24 * 3600 * 1000));
|
||||
const hour = Math.floor((time % (24 * 3600 * 1000)) / (3600 * 1000));
|
||||
const minute = Math.floor((time % (3600 * 1000)) / (60 * 1000));
|
||||
const second = Math.floor((time % (60 * 1000)) / 1000);
|
||||
return `${day}天 ${hour.toFixed(0).padStart(2, "0")}:${minute
|
||||
.toFixed(0)
|
||||
.padStart(2, "0")}:${second.toFixed(0).padStart(2, "0")}`;
|
||||
function getLastPositionTime (time: number) {
|
||||
const day = Math.floor(time / (24 * 3600 * 1000));
|
||||
const hour = Math.floor((time % (24 * 3600 * 1000)) / (3600 * 1000));
|
||||
const minute = Math.floor((time % (3600 * 1000)) / (60 * 1000));
|
||||
const second = Math.floor((time % (60 * 1000)) / 1000);
|
||||
return `${day}天 ${hour.toFixed(0).padStart(2, "0")}:${minute.toFixed(0).padStart(2, "0")}:${second
|
||||
.toFixed(0)
|
||||
.padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
async function toPost(card: PositionCard) {
|
||||
const post_id = card.post_id;
|
||||
// 获取路由路径
|
||||
const path = router.resolve({
|
||||
name: "帖子详情",
|
||||
params: {
|
||||
post_id: post_id,
|
||||
},
|
||||
}).href;
|
||||
// 打开新窗口
|
||||
createTGWindow(path, "近期活动", card.title, 960, 720, false);
|
||||
async function toPost (card: PositionCard) {
|
||||
const post_id = card.post_id;
|
||||
// 获取路由路径
|
||||
const path = router.resolve({
|
||||
name: "帖子详情",
|
||||
params: {
|
||||
post_id,
|
||||
},
|
||||
}).href;
|
||||
// 打开新窗口
|
||||
createTGWindow(path, "近期活动", card.title, 960, 720, false);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -132,7 +127,7 @@ async function toPost(card: PositionCard) {
|
||||
|
||||
.position-card {
|
||||
margin-top: 10px;
|
||||
font-family: "Genshin", serif;
|
||||
font-family: Genshin, serif;
|
||||
background: #546d8b;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
@@ -1,104 +1,110 @@
|
||||
<template>
|
||||
<v-navigation-drawer permanent :rail="rail" style="background: #485466; color: #faf7e8">
|
||||
<v-list class="sideList" density="compact" v-model:opened="open" nav>
|
||||
<!-- 负责收缩侧边栏 -->
|
||||
<v-list-item @click="collapse">
|
||||
<template v-slot:prepend v-if="rail">
|
||||
<v-list-item-action>
|
||||
<v-icon color="rgb(205, 182, 145)">mdi-chevron-right</v-icon>
|
||||
</v-list-item-action>
|
||||
</template>
|
||||
<template v-slot:append v-else>
|
||||
<v-list-item-action>
|
||||
<v-icon color="rgb(205, 182, 145)">mdi-chevron-left</v-icon>
|
||||
</v-list-item-action>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<!-- 菜单项 -->
|
||||
<v-list-item value="home" title="首页" link href="/">
|
||||
<template v-slot:prepend>
|
||||
<img src="/source/UI/paimon.webp" alt="homeIcon" class="sideIcon" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item title="公告" value="announcements" link href="/announcements">
|
||||
<template v-slot:prepend>
|
||||
<img src="../assets/icons/board.svg" alt="annoIcon" class="sideIcon" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-divider></v-divider>
|
||||
<v-list-group value="mihoyo" fluid>
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-list-item title="米游社" v-bind="props">
|
||||
<template v-slot:prepend>
|
||||
<img src="/platforms/mhy/mys.webp" alt="mihoyo" class="sideIcon" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
</template>
|
||||
<v-list-item title="原神" value="mhy-ys" link href="/news/2">
|
||||
<template v-slot:prepend>
|
||||
<img src="/platforms/mhy/ys.webp" alt="ys" class="sideIcon" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item title="崩坏3" value="mhy-bh3" link href="/news/1">
|
||||
<template v-slot:prepend>
|
||||
<img src="/platforms/mhy/bh3.webp" alt="bh3" class="sideIcon" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item title="崩坏2" value="mhy-bh2" link href="/news/3">
|
||||
<template v-slot:prepend>
|
||||
<img src="/platforms/mhy/bh2.webp" alt="bh2" class="sideIcon" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item title="未定事件簿" value="mhy-wd" link href="/news/4">
|
||||
<template v-slot:prepend>
|
||||
<img src="/platforms/mhy/wd.webp" alt="wd" class="sideIcon" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item title="星穹铁道" value="mhy-sr" link href="/news/6">
|
||||
<template v-slot:prepend>
|
||||
<img src="/platforms/mhy/sr.webp" alt="sr" class="sideIcon" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item title="绝区零" value="mhy-zzz" link href="/news/8">
|
||||
<template v-slot:prepend>
|
||||
<img src="/platforms/mhy/zzz.webp" alt="zzz" class="sideIcon" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item title="大别野" value="mhy-dby" link href="/news/5">
|
||||
<template v-slot:prepend>
|
||||
<img src="/platforms/mhy/dby.webp" alt="dby" class="sideIcon" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list-group>
|
||||
<v-divider></v-divider>
|
||||
<v-list-item title="成就" value="achievements" link href="/achievements">
|
||||
<template v-slot:prepend>
|
||||
<img src="../assets/icons/achievements.svg" alt="achievementsIcon" class="sideIcon" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-divider></v-divider>
|
||||
<v-list-group value="database" fluid>
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-list-item title="数据库" v-bind="props">
|
||||
<template v-slot:prepend>
|
||||
<v-icon color="rgb(205, 182, 145)">mdi-database</v-icon>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</template>
|
||||
<v-list-item title="GCG" value="db-GCG" link href="/GCG">
|
||||
<template v-slot:prepend>
|
||||
<img src="../assets/icons/GCG.svg" alt="gcgIcon" class="sideIcon" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list-group>
|
||||
<v-divider></v-divider>
|
||||
<v-list-item title="设置" value="config" link href="/config">
|
||||
<template v-slot:prepend>
|
||||
<img src="../assets/icons/setting.svg" alt="setting" class="sideIcon" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-navigation-drawer>
|
||||
<v-navigation-drawer permanent :rail="rail" style="background: #485466; color: #faf7e8">
|
||||
<v-list v-model:opened="open" class="side-list" density="compact" nav>
|
||||
<!-- 负责收缩侧边栏 -->
|
||||
<v-list-item @click="collapse">
|
||||
<template v-if="rail" #prepend>
|
||||
<v-list-item-action>
|
||||
<v-icon color="rgb(205, 182, 145)">
|
||||
mdi-chevron-right
|
||||
</v-icon>
|
||||
</v-list-item-action>
|
||||
</template>
|
||||
<template v-else #append>
|
||||
<v-list-item-action>
|
||||
<v-icon color="rgb(205, 182, 145)">
|
||||
mdi-chevron-left
|
||||
</v-icon>
|
||||
</v-list-item-action>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<!-- 菜单项 -->
|
||||
<v-list-item value="home" title="首页" link href="/">
|
||||
<template #prepend>
|
||||
<img src="/source/UI/paimon.webp" alt="homeIcon" class="side-icon">
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item title="公告" value="announcements" link href="/announcements">
|
||||
<template #prepend>
|
||||
<img src="../assets/icons/board.svg" alt="annoIcon" class="side-icon">
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-divider />
|
||||
<v-list-group value="mihoyo" fluid>
|
||||
<template #activator="{ props }">
|
||||
<v-list-item title="米游社" v-bind="props">
|
||||
<template #prepend>
|
||||
<img src="/platforms/mhy/mys.webp" alt="mihoyo" class="side-icon">
|
||||
</template>
|
||||
</v-list-item>
|
||||
</template>
|
||||
<v-list-item title="原神" value="mhy-ys" link href="/news/2">
|
||||
<template #prepend>
|
||||
<img src="/platforms/mhy/ys.webp" alt="ys" class="side-icon">
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item title="崩坏3" value="mhy-bh3" link href="/news/1">
|
||||
<template #prepend>
|
||||
<img src="/platforms/mhy/bh3.webp" alt="bh3" class="side-icon">
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item title="崩坏2" value="mhy-bh2" link href="/news/3">
|
||||
<template #prepend>
|
||||
<img src="/platforms/mhy/bh2.webp" alt="bh2" class="side-icon">
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item title="未定事件簿" value="mhy-wd" link href="/news/4">
|
||||
<template #prepend>
|
||||
<img src="/platforms/mhy/wd.webp" alt="wd" class="side-icon">
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item title="星穹铁道" value="mhy-sr" link href="/news/6">
|
||||
<template #prepend>
|
||||
<img src="/platforms/mhy/sr.webp" alt="sr" class="side-icon">
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item title="绝区零" value="mhy-zzz" link href="/news/8">
|
||||
<template #prepend>
|
||||
<img src="/platforms/mhy/zzz.webp" alt="zzz" class="side-icon">
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item title="大别野" value="mhy-dby" link href="/news/5">
|
||||
<template #prepend>
|
||||
<img src="/platforms/mhy/dby.webp" alt="dby" class="side-icon">
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list-group>
|
||||
<v-divider />
|
||||
<v-list-item title="成就" value="achievements" link href="/achievements">
|
||||
<template #prepend>
|
||||
<img src="../assets/icons/achievements.svg" alt="achievementsIcon" class="side-icon">
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-divider />
|
||||
<v-list-group value="database" fluid>
|
||||
<template #activator="{ props }">
|
||||
<v-list-item title="数据库" v-bind="props">
|
||||
<template #prepend>
|
||||
<v-icon color="rgb(205, 182, 145)">
|
||||
mdi-database
|
||||
</v-icon>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</template>
|
||||
<v-list-item title="GCG" value="db-GCG" link href="/GCG">
|
||||
<template #prepend>
|
||||
<img src="../assets/icons/GCG.svg" alt="gcgIcon" class="side-icon">
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list-group>
|
||||
<v-divider />
|
||||
<v-list-item title="设置" value="config" link href="/config">
|
||||
<template #prepend>
|
||||
<img src="../assets/icons/setting.svg" alt="setting" class="side-icon">
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-navigation-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@@ -111,27 +117,27 @@ const appStore = useAppStore();
|
||||
|
||||
const rail = ref(appStore.sidebar.collapse);
|
||||
const open = computed({
|
||||
get() {
|
||||
return appStore.getSubmenu();
|
||||
},
|
||||
set(value: string[]) {
|
||||
appStore.sidebar.submenu.mihoyo = value.includes("mihoyo");
|
||||
appStore.sidebar.submenu.database = value.includes("database");
|
||||
},
|
||||
get () {
|
||||
return appStore.getSubmenu();
|
||||
},
|
||||
set (value: string[]) {
|
||||
appStore.sidebar.submenu.mihoyo = value.includes("mihoyo");
|
||||
appStore.sidebar.submenu.database = value.includes("database");
|
||||
},
|
||||
});
|
||||
|
||||
function collapse() {
|
||||
rail.value = !rail.value;
|
||||
appStore.sidebar.collapse = rail.value;
|
||||
function collapse () {
|
||||
rail.value = !rail.value;
|
||||
appStore.sidebar.collapse = rail.value;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.sideList {
|
||||
font-family: "Genshin-Light", serif;
|
||||
.side-list {
|
||||
font-family: Genshin-Light, serif;
|
||||
}
|
||||
|
||||
.sideIcon {
|
||||
.side-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 32px;
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
/**
|
||||
* @description 定义一个 Map<T> 接口
|
||||
* @since Alpha v0.1.2
|
||||
* @description 该接口的方法实现在 TGMap<T> 中
|
||||
* @see TGMap
|
||||
* @interface Map
|
||||
* @template T
|
||||
* @returns {Map<T>}
|
||||
|
||||
@@ -1,100 +1,102 @@
|
||||
<template>
|
||||
<!-- 顶部操作栏 -->
|
||||
<v-app-bar style="background: rgba(0, 0, 0, 0.5); color: #f4d8a8; font-family: Genshin, serif">
|
||||
<template v-slot:prepend>
|
||||
<span style="font-size: 30px">{{ title }}</span>
|
||||
</template>
|
||||
<v-spacer></v-spacer>
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
append-icon="mdi-magnify"
|
||||
label="搜索"
|
||||
hide-details
|
||||
@click:append="searchCard"
|
||||
@keyup.enter="searchCard"
|
||||
/>
|
||||
<template v-slot:append>
|
||||
<v-btn @click="importJson" prepend-icon="mdi-import" class="ms-2 top-btn">导入</v-btn>
|
||||
<v-btn @click="exportJson" prepend-icon="mdi-export" class="ms-2 top-btn"> 导出 </v-btn>
|
||||
</template>
|
||||
</v-app-bar>
|
||||
<div v-show="loading">
|
||||
<t-loading :title="loadingTitle" />
|
||||
</div>
|
||||
<div v-show="!loading" class="wrap">
|
||||
<!-- 左侧菜单 -->
|
||||
<div class="left-wrap">
|
||||
<v-list class="card-left" v-for="(series, index) in seriesList" @click="selectSeries(index)">
|
||||
<div class="version-icon-series">v{{ series.version }}</div>
|
||||
<v-list-item>
|
||||
<template v-slot:prepend>
|
||||
<v-img width="40px" style="margin-right: 10px" :src="series.icon" />
|
||||
</template>
|
||||
<v-list-item-title>
|
||||
{{ series.name }}
|
||||
</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
{{ series.completed_count }} / {{ series.total_count }}
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</div>
|
||||
<!-- 右侧内容-->
|
||||
<div class="right-wrap">
|
||||
<v-list
|
||||
v-show="selectedIndex !== -1 && selectedSeries !== 0 && selectedSeries !== 17"
|
||||
@click="openImg()"
|
||||
:style="{
|
||||
backgroundImage: 'url(' + getCardInfo.bg || null + ')',
|
||||
backgroundPosition: 'right',
|
||||
backgroundSize: 'auto 100%',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
margin: '10px',
|
||||
borderRadius: '10px 50px 50px 10px',
|
||||
color: '#485466',
|
||||
fontFamily: 'Genshin,serif',
|
||||
cursor: 'pointer',
|
||||
}"
|
||||
>
|
||||
<v-list-item :title="getCardInfo.name" :subtitle="getCardInfo.description">
|
||||
<template v-slot:prepend>
|
||||
<v-img width="80px" style="margin-right: 10px" :src="getCardInfo.icon" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<v-list class="card-right" v-for="achievement in selectedAchievement" :key="achievement.id">
|
||||
<v-list-item>
|
||||
<template v-slot:prepend>
|
||||
<v-icon :color="achievement.completed ? '#fec90b' : '#485466'">
|
||||
<!-- todo 图标替换 -->
|
||||
{{ achievement.completed ? "mdi-check-circle" : "mdi-circle" }}
|
||||
</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>
|
||||
{{ achievement.name }}
|
||||
{{ achievement.progress !== 0 ? "| " + achievement.progress : null }}
|
||||
<span class="version-icon-single">v{{ achievement.version }}</span>
|
||||
</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ achievement.description }}</v-list-item-subtitle>
|
||||
<template v-slot:append>
|
||||
<span v-show="achievement.completed" class="right-time">{{
|
||||
achievement.completed_time
|
||||
}}</span>
|
||||
<v-card class="reward-card" @click="showMaterial('/source/material/原石.webp')">
|
||||
<v-img src="/source/material/原石.webp" sizes="32" />
|
||||
<div class="reward-num">
|
||||
<span>{{ achievement.reward }}</span>
|
||||
</div>
|
||||
</v-card>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</div>
|
||||
<!-- 弹窗提示 -->
|
||||
<v-snackbar v-model="snackbar" timeout="1500" color="#F5810A" top>
|
||||
{{ snackbarText }}
|
||||
</v-snackbar>
|
||||
</div>
|
||||
<!-- 顶部操作栏 -->
|
||||
<v-app-bar style="background: rgb(0 0 0 / 50%); color: #f4d8a8; font-family: Genshin, serif">
|
||||
<template #prepend>
|
||||
<span style="font-size: 30px">{{ title }}</span>
|
||||
</template>
|
||||
<v-spacer />
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
append-icon="mdi-magnify"
|
||||
label="搜索"
|
||||
hide-details
|
||||
@click:append="searchCard"
|
||||
@keyup.enter="searchCard"
|
||||
/>
|
||||
<template #append>
|
||||
<v-btn prepend-icon="mdi-import" class="ms-2 top-btn" @click="importJson">
|
||||
导入
|
||||
</v-btn>
|
||||
<v-btn prepend-icon="mdi-export" class="ms-2 top-btn" @click="exportJson">
|
||||
导出
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-app-bar>
|
||||
<div v-show="loading">
|
||||
<TLoading :title="loadingTitle" />
|
||||
</div>
|
||||
<div v-show="!loading" class="wrap">
|
||||
<!-- 左侧菜单 -->
|
||||
<div class="left-wrap">
|
||||
<v-list v-for="(series, index) in seriesList" :key="series.id" class="card-left" @click="selectSeries(index)">
|
||||
<div class="version-icon-series">
|
||||
v{{ series.version }}
|
||||
</div>
|
||||
<v-list-item>
|
||||
<template #prepend>
|
||||
<v-img width="40px" style="margin-right: 10px" :src="series.icon" />
|
||||
</template>
|
||||
<v-list-item-title>
|
||||
{{ series.name }}
|
||||
</v-list-item-title>
|
||||
<v-list-item-subtitle> {{ series.completed_count }} / {{ series.total_count }} </v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</div>
|
||||
<!-- 右侧内容-->
|
||||
<div class="right-wrap">
|
||||
<v-list
|
||||
v-show="selectedIndex !== -1 && selectedSeries !== 0 && selectedSeries !== 17"
|
||||
:style="{
|
||||
backgroundImage: 'url(' + getCardInfo.bg || null + ')',
|
||||
backgroundPosition: 'right',
|
||||
backgroundSize: 'auto 100%',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
margin: '10px',
|
||||
borderRadius: '10px 50px 50px 10px',
|
||||
color: '#485466',
|
||||
fontFamily: 'Genshin,serif',
|
||||
cursor: 'pointer',
|
||||
}"
|
||||
@click="openImg()"
|
||||
>
|
||||
<v-list-item :title="getCardInfo.name" :subtitle="getCardInfo.description">
|
||||
<template #prepend>
|
||||
<v-img width="80px" style="margin-right: 10px" :src="getCardInfo.icon" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<v-list v-for="achievement in selectedAchievement" :key="achievement.id" class="card-right">
|
||||
<v-list-item>
|
||||
<template #prepend>
|
||||
<v-icon :color="achievement.completed ? '#fec90b' : '#485466'">
|
||||
<!-- todo 图标替换 -->
|
||||
{{ achievement.completed ? "mdi-check-circle" : "mdi-circle" }}
|
||||
</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>
|
||||
{{ achievement.name }}
|
||||
{{ achievement.progress !== 0 ? "| " + achievement.progress : null }}
|
||||
<span class="version-icon-single">v{{ achievement.version }}</span>
|
||||
</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ achievement.description }}</v-list-item-subtitle>
|
||||
<template #append>
|
||||
<span v-show="achievement.completed" class="right-time">{{ achievement.completed_time }}</span>
|
||||
<v-card class="reward-card" @click="showMaterial('/source/material/原石.webp')">
|
||||
<v-img src="/source/material/原石.webp" sizes="32" />
|
||||
<div class="reward-num">
|
||||
<span>{{ achievement.reward }}</span>
|
||||
</div>
|
||||
</v-card>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</div>
|
||||
<!-- 弹窗提示 -->
|
||||
<v-snackbar v-model="snackbar" timeout="1500" color="#F5810A" top>
|
||||
{{ snackbarText }}
|
||||
</v-snackbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@@ -106,22 +108,14 @@ import { dialog, fs } from "@tauri-apps/api";
|
||||
// Store
|
||||
import useAchievementsStore from "../store/modules/achievements";
|
||||
// Interface
|
||||
import { Achievements, UIAF_Info, UIAF_Achievement } from "../plugins/UIAF/interface/UIAF";
|
||||
import {
|
||||
Achievement as TGAchievement,
|
||||
AchievementSeries as TGSeries,
|
||||
} from "../interface/Achievements";
|
||||
import { Achievements, UiafHeader, UiafAchievement } from "../plugins/UIAF/interface/UIAF";
|
||||
import { Achievement as TGAchievement, AchievementSeries as TGSeries } from "../interface/Achievements";
|
||||
import { NameCard } from "../interface/NameCard";
|
||||
// Plugins
|
||||
import UIAF_Oper from "../plugins/UIAF";
|
||||
import UiafOper from "../plugins/UIAF";
|
||||
// Utils
|
||||
import { createTGWindow } from "../utils/TGWindow";
|
||||
import {
|
||||
ReadAllTGData,
|
||||
ReadTGDataByIndex,
|
||||
ReadTGDataByKey,
|
||||
UpdateTGDataByKey,
|
||||
} from "../utils/TGIndex";
|
||||
import { ReadAllTGData, ReadTGDataByIndex, ReadTGDataByKey, UpdateTGDataByKey } from "../utils/TGIndex";
|
||||
|
||||
// Store
|
||||
const achievementsStore = useAchievementsStore();
|
||||
@@ -146,241 +140,238 @@ const snackbar = ref(false as boolean);
|
||||
const snackbarText = ref("" as string);
|
||||
|
||||
onMounted(async () => {
|
||||
await loadData();
|
||||
await loadData();
|
||||
});
|
||||
|
||||
// 加载数据,数据源:合并后的本地数据
|
||||
async function loadData() {
|
||||
loadingTitle.value = "正在获取成就系列数据";
|
||||
const seriesDB: TGSeries[] = await ReadAllTGData("AchievementSeries");
|
||||
loadingTitle.value = "正在获取成就系列名片数据";
|
||||
CardsInfo.value = await ReadTGDataByIndex("NameCard", "type", 1);
|
||||
loadingTitle.value = "对成就系列数据进行排序";
|
||||
seriesList.value = seriesDB.sort((a, b) => a.order - b.order);
|
||||
loadingTitle.value = "正在获取成就数据";
|
||||
const getAchievements = await ReadAllTGData("Achievements");
|
||||
loadingTitle.value = "正在对成就数据进行排序";
|
||||
getAchievements.sort((a, b) => {
|
||||
if (a.completed === b.completed) {
|
||||
return a.id - b.id;
|
||||
} else {
|
||||
return a.completed ? 1 : -1;
|
||||
}
|
||||
});
|
||||
loadingTitle.value = "正在渲染成就数据";
|
||||
selectedAchievement.value = getAchievements;
|
||||
title.value = achievementsStore.title;
|
||||
loading.value = false;
|
||||
async function loadData () {
|
||||
loadingTitle.value = "正在获取成就系列数据";
|
||||
const seriesDB: TGSeries[] = await ReadAllTGData("AchievementSeries");
|
||||
loadingTitle.value = "正在获取成就系列名片数据";
|
||||
CardsInfo.value = await ReadTGDataByIndex("NameCard", "type", 1);
|
||||
loadingTitle.value = "对成就系列数据进行排序";
|
||||
seriesList.value = seriesDB.sort((a, b) => a.order - b.order);
|
||||
loadingTitle.value = "正在获取成就数据";
|
||||
const getAchievements = await ReadAllTGData("Achievements");
|
||||
loadingTitle.value = "正在对成就数据进行排序";
|
||||
getAchievements.sort((a, b) => {
|
||||
if (a.completed === b.completed) {
|
||||
return a.id - b.id;
|
||||
} else {
|
||||
return a.completed ? 1 : -1;
|
||||
}
|
||||
});
|
||||
loadingTitle.value = "正在渲染成就数据";
|
||||
selectedAchievement.value = getAchievements;
|
||||
title.value = achievementsStore.title;
|
||||
loading.value = false;
|
||||
}
|
||||
// 渲染选中的成就系列
|
||||
async function selectSeries(index: number) {
|
||||
// 如果选中的是已经选中的系列,则不进行操作
|
||||
if (selectedIndex.value === index) {
|
||||
snackbarText.value = "已经选中该系列";
|
||||
snackbar.value = true;
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
loadingTitle.value = "正在获取对应的成就数据";
|
||||
const getAchievements = await ReadTGDataByIndex(
|
||||
"Achievements",
|
||||
"series",
|
||||
seriesList.value[index].id
|
||||
);
|
||||
selectedIndex.value = index;
|
||||
selectedSeries.value = seriesList.value[index].id;
|
||||
loadingTitle.value = "正在查找对应的成就名片";
|
||||
let getCard: NameCard;
|
||||
if (selectedSeries.value !== 0 && selectedSeries.value !== 17) {
|
||||
getCard = CardsInfo.value.find(card => card.name === seriesList.value[index].card)!;
|
||||
} else {
|
||||
getCard = {} as NameCard;
|
||||
}
|
||||
loadingTitle.value = "正在对成就数据进行排序";
|
||||
getAchievements.sort((a, b) => {
|
||||
if (a.completed === b.completed) {
|
||||
return a.id - b.id;
|
||||
} else {
|
||||
return a.completed ? 1 : -1;
|
||||
}
|
||||
});
|
||||
loadingTitle.value = "正在渲染成就数据";
|
||||
selectedAchievement.value = getAchievements;
|
||||
getCardInfo.value = getCard;
|
||||
loading.value = false;
|
||||
async function selectSeries (index: number) {
|
||||
// 如果选中的是已经选中的系列,则不进行操作
|
||||
if (selectedIndex.value === index) {
|
||||
snackbarText.value = "已经选中该系列";
|
||||
snackbar.value = true;
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
loadingTitle.value = "正在获取对应的成就数据";
|
||||
const getAchievements = await ReadTGDataByIndex("Achievements", "series", seriesList.value[index].id);
|
||||
selectedIndex.value = index;
|
||||
selectedSeries.value = seriesList.value[index].id;
|
||||
loadingTitle.value = "正在查找对应的成就名片";
|
||||
let getCard: NameCard;
|
||||
if (selectedSeries.value !== 0 && selectedSeries.value !== 17) {
|
||||
getCard = CardsInfo.value.find((card) => card.name === seriesList.value[index].card)!;
|
||||
} else {
|
||||
getCard = {} as NameCard;
|
||||
}
|
||||
loadingTitle.value = "正在对成就数据进行排序";
|
||||
getAchievements.sort((a, b) => {
|
||||
if (a.completed === b.completed) {
|
||||
return a.id - b.id;
|
||||
} else {
|
||||
return a.completed ? 1 : -1;
|
||||
}
|
||||
});
|
||||
loadingTitle.value = "正在渲染成就数据";
|
||||
selectedAchievement.value = getAchievements;
|
||||
getCardInfo.value = getCard;
|
||||
loading.value = false;
|
||||
}
|
||||
// 打开图片
|
||||
function openImg() {
|
||||
createTGWindow(getCardInfo.value.profile, "nameCard", getCardInfo.value.name, 840, 400, false);
|
||||
function openImg () {
|
||||
createTGWindow(getCardInfo.value.profile, "nameCard", getCardInfo.value.name, 840, 400, false);
|
||||
}
|
||||
function showMaterial(path: string) {
|
||||
createTGWindow(path, "material", "原石", 256, 256, false);
|
||||
function showMaterial (path: string) {
|
||||
createTGWindow(path, "material", "原石", 256, 256, false);
|
||||
}
|
||||
async function searchCard() {
|
||||
if (search.value === "") {
|
||||
snackbarText.value = "请输入搜索内容";
|
||||
snackbar.value = true;
|
||||
return;
|
||||
}
|
||||
loadingTitle.value = "正在搜索";
|
||||
loading.value = true;
|
||||
const res: TGAchievement[] = [];
|
||||
const allAchievements = await ReadAllTGData("Achievements");
|
||||
allAchievements.map(achievement => {
|
||||
if (achievement.name.includes(search.value) || achievement.description.includes(search.value)) {
|
||||
res.push(achievement);
|
||||
}
|
||||
});
|
||||
selectedIndex.value = -1;
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
}, 500);
|
||||
if (res.length === 0) {
|
||||
snackbarText.value = "没有找到对应的成就";
|
||||
snackbar.value = true;
|
||||
selectedAchievement.value = allAchievements;
|
||||
} else {
|
||||
res.sort((a, b) => {
|
||||
if (a.completed === b.completed) {
|
||||
return a.id - b.id;
|
||||
} else {
|
||||
return a.completed ? 1 : -1;
|
||||
}
|
||||
});
|
||||
selectedAchievement.value = res;
|
||||
}
|
||||
async function searchCard () {
|
||||
if (search.value === "") {
|
||||
snackbarText.value = "请输入搜索内容";
|
||||
snackbar.value = true;
|
||||
return;
|
||||
}
|
||||
loadingTitle.value = "正在搜索";
|
||||
loading.value = true;
|
||||
const res: TGAchievement[] = [];
|
||||
const allAchievements = await ReadAllTGData("Achievements");
|
||||
allAchievements.map((achievement) => {
|
||||
if (achievement.name.includes(search.value) || achievement.description.includes(search.value)) {
|
||||
return res.push(achievement);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
selectedIndex.value = -1;
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
}, 500);
|
||||
if (res.length === 0) {
|
||||
snackbarText.value = "没有找到对应的成就";
|
||||
snackbar.value = true;
|
||||
selectedAchievement.value = allAchievements;
|
||||
} else {
|
||||
res.sort((a, b) => {
|
||||
if (a.completed === b.completed) {
|
||||
return a.id - b.id;
|
||||
} else {
|
||||
return a.completed ? 1 : -1;
|
||||
}
|
||||
});
|
||||
selectedAchievement.value = res;
|
||||
}
|
||||
}
|
||||
// 导入 UIAF 数据,进行数据合并、刷新
|
||||
async function importJson() {
|
||||
const selectedFile = await dialog.open({
|
||||
multiple: false,
|
||||
filters: [
|
||||
{
|
||||
name: "JSON",
|
||||
extensions: ["json"],
|
||||
},
|
||||
],
|
||||
});
|
||||
if (selectedFile && (await UIAF_Oper.checkUIAFData(<string>selectedFile))) {
|
||||
const remoteRaw: string | false = await UIAF_Oper.readUIAFData(<string>selectedFile);
|
||||
if (remoteRaw === false) {
|
||||
snackbarText.value = "读取 UIAF 数据失败,请检查文件是否符合规范";
|
||||
snackbar.value = true;
|
||||
return;
|
||||
}
|
||||
loadingTitle.value = "正在解析数据";
|
||||
loading.value = true;
|
||||
let remoteData: Achievements = JSON.parse(remoteRaw);
|
||||
loadingTitle.value = "正在合并成就数据";
|
||||
await Promise.allSettled(
|
||||
remoteData.list.map(async data => {
|
||||
const id = data.id;
|
||||
let localData: TGAchievement = (await ReadTGDataByKey("Achievements", [id]))[0];
|
||||
// 获取 timeStamp 2023-03-15 00:00:00
|
||||
const localTime = localData.completed_time;
|
||||
// 如果本地数据不存在,或者本地数据的 timeStamp 小于远程数据的 timeStamp,更新数据
|
||||
if (data.timestamp !== 0) {
|
||||
const fin_time = new Date(data.timestamp * 1000).toLocaleString("zh", {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
});
|
||||
if (fin_time !== localTime || localData.progress !== data.current) {
|
||||
localData.completed_time = fin_time;
|
||||
localData.progress = data.current;
|
||||
localData.completed = true;
|
||||
// 更新数据
|
||||
await UpdateTGDataByKey("Achievements", localData);
|
||||
}
|
||||
} else {
|
||||
if (localData.progress !== data.current) {
|
||||
localData.completed_time = "";
|
||||
localData.progress = data.current;
|
||||
localData.completed = false;
|
||||
// 更新数据
|
||||
await UpdateTGDataByKey("Achievements", localData);
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
loadingTitle.value = "正在更新成就系列数据";
|
||||
let seriesDB = await ReadAllTGData("AchievementSeries");
|
||||
await Promise.allSettled(
|
||||
seriesDB.map(async data => {
|
||||
const seriesId = data.id;
|
||||
const achievementsDB = await ReadTGDataByIndex("Achievements", "series", seriesId);
|
||||
data.completed_count = achievementsDB.filter(data => {
|
||||
return data.completed === true;
|
||||
}).length;
|
||||
await UpdateTGDataByKey("AchievementSeries", data);
|
||||
})
|
||||
);
|
||||
loadingTitle.value = "正在刷新数据";
|
||||
seriesDB = await ReadAllTGData("AchievementSeries");
|
||||
const fin_achievements = seriesDB.reduce((a, b) => {
|
||||
return a + b.completed_count;
|
||||
}, 0);
|
||||
const total_achievements = seriesDB.reduce((a, b) => {
|
||||
return a + b.total_count;
|
||||
}, 0);
|
||||
achievementsStore.flushData(total_achievements, fin_achievements);
|
||||
// 刷新数据
|
||||
await loadData();
|
||||
}
|
||||
async function importJson () {
|
||||
const selectedFile = await dialog.open({
|
||||
multiple: false,
|
||||
filters: [
|
||||
{
|
||||
name: "JSON",
|
||||
extensions: ["json"],
|
||||
},
|
||||
],
|
||||
});
|
||||
if (selectedFile && (await UiafOper.checkUiafData(<string>selectedFile))) {
|
||||
const remoteRaw: string | false = await UiafOper.readUiafData(<string>selectedFile);
|
||||
if (remoteRaw === false) {
|
||||
snackbarText.value = "读取 UIAF 数据失败,请检查文件是否符合规范";
|
||||
snackbar.value = true;
|
||||
return;
|
||||
}
|
||||
loadingTitle.value = "正在解析数据";
|
||||
loading.value = true;
|
||||
const remoteData: Achievements = JSON.parse(remoteRaw);
|
||||
loadingTitle.value = "正在合并成就数据";
|
||||
await Promise.allSettled(
|
||||
remoteData.list.map(async (data) => {
|
||||
const id = data.id;
|
||||
const localData: TGAchievement = (await ReadTGDataByKey("Achievements", [id]))[0];
|
||||
// 获取 timeStamp 2023-03-15 00:00:00
|
||||
const localTime = localData.completed_time;
|
||||
// 如果本地数据不存在,或者本地数据的 timeStamp 小于远程数据的 timeStamp,更新数据
|
||||
if (data.timestamp !== 0) {
|
||||
const fin_time = new Date(data.timestamp * 1000).toLocaleString("zh", {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
});
|
||||
if (fin_time !== localTime || localData.progress !== data.current) {
|
||||
localData.completed_time = fin_time;
|
||||
localData.progress = data.current;
|
||||
localData.completed = true;
|
||||
// 更新数据
|
||||
await UpdateTGDataByKey("Achievements", localData);
|
||||
}
|
||||
} else {
|
||||
if (localData.progress !== data.current) {
|
||||
localData.completed_time = "";
|
||||
localData.progress = data.current;
|
||||
localData.completed = false;
|
||||
// 更新数据
|
||||
await UpdateTGDataByKey("Achievements", localData);
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
loadingTitle.value = "正在更新成就系列数据";
|
||||
let seriesDB = await ReadAllTGData("AchievementSeries");
|
||||
await Promise.allSettled(
|
||||
seriesDB.map(async (data) => {
|
||||
const seriesId = data.id;
|
||||
const achievementsDB = await ReadTGDataByIndex("Achievements", "series", seriesId);
|
||||
data.completed_count = achievementsDB.filter((data) => {
|
||||
return data.completed === true;
|
||||
}).length;
|
||||
await UpdateTGDataByKey("AchievementSeries", data);
|
||||
}),
|
||||
);
|
||||
loadingTitle.value = "正在刷新数据";
|
||||
seriesDB = await ReadAllTGData("AchievementSeries");
|
||||
const fin_achievements = seriesDB.reduce((a, b) => {
|
||||
return a + b.completed_count;
|
||||
}, 0);
|
||||
const total_achievements = seriesDB.reduce((a, b) => {
|
||||
return a + b.total_count;
|
||||
}, 0);
|
||||
achievementsStore.flushData(total_achievements, fin_achievements);
|
||||
// 刷新数据
|
||||
await loadData();
|
||||
}
|
||||
}
|
||||
// 导出
|
||||
async function exportJson() {
|
||||
// 判断是否有数据
|
||||
if (achievementsStore.fin_achievements === 0) {
|
||||
snackbarText.value = "没有可导出的数据";
|
||||
snackbar.value = true;
|
||||
return;
|
||||
}
|
||||
// 获取本地数据
|
||||
const achievements = (await ReadAllTGData("Achievements")).filter(data => {
|
||||
return data.progress !== 0 || data.completed === true;
|
||||
});
|
||||
let UIAF_DATA = {
|
||||
info: {} as UIAF_Info,
|
||||
list: [] as UIAF_Achievement[],
|
||||
};
|
||||
// 转换数据
|
||||
UIAF_DATA.list = achievements.map(data => {
|
||||
let status;
|
||||
// 计算点数但是没有完成
|
||||
if (data.progress !== 0 && data.completed === false) {
|
||||
status = 1;
|
||||
// 已完成且未计算点数
|
||||
} else if (data.progress === 0 && data.completed === true) {
|
||||
status = 2;
|
||||
// 已完成且已计算点数
|
||||
} else if (data.progress !== 0 && data.completed === true) {
|
||||
status = 3;
|
||||
} else {
|
||||
status = 0;
|
||||
}
|
||||
return {
|
||||
id: data.id,
|
||||
timestamp: data.completed ? Math.round(new Date(data.completed_time).getTime() / 1000) : 0,
|
||||
current: data.progress,
|
||||
status: status,
|
||||
};
|
||||
});
|
||||
UIAF_DATA.info = await UIAF_Oper.getUIAFInfo();
|
||||
const is_save = await dialog.save({
|
||||
filters: [
|
||||
{
|
||||
name: "achievements",
|
||||
extensions: ["json"],
|
||||
},
|
||||
],
|
||||
});
|
||||
if (is_save) {
|
||||
await fs.writeTextFile(is_save, JSON.stringify(UIAF_DATA));
|
||||
}
|
||||
async function exportJson () {
|
||||
// 判断是否有数据
|
||||
if (achievementsStore.fin_achievements === 0) {
|
||||
snackbarText.value = "没有可导出的数据";
|
||||
snackbar.value = true;
|
||||
return;
|
||||
}
|
||||
// 获取本地数据
|
||||
const achievements = (await ReadAllTGData("Achievements")).filter((data) => {
|
||||
return data.progress !== 0 || data.completed === true;
|
||||
});
|
||||
const UIAF_DATA = {
|
||||
info: {} as UiafHeader,
|
||||
list: [] as UiafAchievement[],
|
||||
};
|
||||
// 转换数据
|
||||
UIAF_DATA.list = achievements.map((data) => {
|
||||
let status;
|
||||
// 计算点数但是没有完成
|
||||
if (data.progress !== 0 && data.completed === false) {
|
||||
status = 1;
|
||||
// 已完成且未计算点数
|
||||
} else if (data.progress === 0 && data.completed === true) {
|
||||
status = 2;
|
||||
// 已完成且已计算点数
|
||||
} else if (data.progress !== 0 && data.completed === true) {
|
||||
status = 3;
|
||||
} else {
|
||||
status = 0;
|
||||
}
|
||||
return {
|
||||
id: data.id,
|
||||
timestamp: data.completed ? Math.round(new Date(data.completed_time).getTime() / 1000) : 0,
|
||||
current: data.progress,
|
||||
status,
|
||||
};
|
||||
});
|
||||
UIAF_DATA.info = await UiafOper.getUiafInfo();
|
||||
const is_save = await dialog.save({
|
||||
filters: [
|
||||
{
|
||||
name: "achievements",
|
||||
extensions: ["json"],
|
||||
},
|
||||
],
|
||||
});
|
||||
if (is_save) {
|
||||
await fs.writeTextFile(is_save, JSON.stringify(UIAF_DATA));
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -397,8 +388,9 @@ async function exportJson() {
|
||||
flex-direction: row;
|
||||
overflow: auto;
|
||||
max-height: 90vh;
|
||||
font-family: Genshin-Light, "serif";
|
||||
font-family: Genshin-Light, serif;
|
||||
}
|
||||
|
||||
/* 左侧系列 */
|
||||
.left-wrap {
|
||||
float: left;
|
||||
@@ -406,6 +398,7 @@ async function exportJson() {
|
||||
max-height: calc(100vh - 100px);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* 右侧成就 */
|
||||
.right-wrap {
|
||||
float: right;
|
||||
@@ -413,6 +406,7 @@ async function exportJson() {
|
||||
max-height: calc(100vh - 100px);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* 版本信息 */
|
||||
.version-icon-series {
|
||||
font-family: Genshin, serif;
|
||||
@@ -422,9 +416,9 @@ async function exportJson() {
|
||||
text-align: center;
|
||||
width: 80px;
|
||||
background: #546d8b;
|
||||
border-radius: 10px 0 0 0;
|
||||
border-top: #ffffff 2px solid;
|
||||
border-left: #ffffff 2px solid;
|
||||
border-radius: 10px 0 0;
|
||||
border-top: #fff 2px solid;
|
||||
border-left: #fff 2px solid;
|
||||
color: #fec90b;
|
||||
font-size: 10px;
|
||||
}
|
||||
@@ -475,7 +469,7 @@ async function exportJson() {
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
background: rgb(0 0 0 / 50%);
|
||||
color: #faf7e8;
|
||||
display: flex;
|
||||
font-size: 8px;
|
||||
|
||||
@@ -1,86 +1,90 @@
|
||||
<template>
|
||||
<div v-if="loading">
|
||||
<t-loading :title="loadingTitle" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<v-tabs v-model="tab" align-tabs="start" class="global-font mb-2">
|
||||
<v-tab value="activity" title="活动公告" />
|
||||
<v-tab value="game" title="游戏公告" />
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn class="switch-btn" @click="switchNews">
|
||||
<template v-slot:prepend>
|
||||
<v-icon>mdi-bullhorn</v-icon>
|
||||
</template>
|
||||
切换米游社咨讯
|
||||
</v-btn>
|
||||
</v-tabs>
|
||||
<v-window v-model="tab">
|
||||
<v-window-item value="activity">
|
||||
<div class="anno-grid">
|
||||
<v-card class="anno-card" v-for="item in annoCards.activity" width="340">
|
||||
<div class="anno-cover" @click="toPost(item)">
|
||||
<img :src="item.banner" alt="cover" />
|
||||
</div>
|
||||
<v-card-title>
|
||||
{{ item.title }}
|
||||
</v-card-title>
|
||||
<v-card-subtitle>{{ item.subtitle }}</v-card-subtitle>
|
||||
<v-card-actions>
|
||||
<v-btn @click="toPost(item)" class="anno-btn">
|
||||
<template v-slot:prepend>
|
||||
<img :src="item.tag_icon || '../assets/icons/arrow-right.svg'" alt="right" />
|
||||
</template>
|
||||
查看
|
||||
</v-btn>
|
||||
<v-card-subtitle v-show="!appStore.devMode">
|
||||
<v-icon>mdi-calendar</v-icon>
|
||||
{{ item.start_time.split(" ")[0] }} -
|
||||
{{ item.end_time.split(" ")[0] }}</v-card-subtitle
|
||||
>
|
||||
<v-card-subtitle v-show="appStore.devMode">id: {{ item.id }}</v-card-subtitle>
|
||||
<v-btn v-show="appStore.devMode" class="card-dev-btn" @click="toJson(item)">
|
||||
<template v-slot:prepend>
|
||||
<img src="../assets/icons/arrow-right.svg" alt="right" />
|
||||
</template>
|
||||
查看数据
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</div>
|
||||
</v-window-item>
|
||||
<v-window-item value="game">
|
||||
<div class="anno-grid">
|
||||
<v-card class="anno-card" v-for="item in annoCards.game" width="340">
|
||||
<div class="anno-cover" @click="toPost(item)">
|
||||
<img :src="item.banner" alt="cover" />
|
||||
</div>
|
||||
<v-card-title>{{ item.title }}</v-card-title>
|
||||
<v-card-subtitle>{{ item.subtitle }}</v-card-subtitle>
|
||||
<v-card-actions>
|
||||
<v-btn @click="toPost(item)" class="anno-btn">
|
||||
<template v-slot:prepend>
|
||||
<img :src="item.tag_icon || '../assets/icons/arrow-right.svg'" alt="right" />
|
||||
</template>
|
||||
查看
|
||||
</v-btn>
|
||||
<v-card-subtitle v-show="!appStore.devMode">
|
||||
<v-icon>mdi-calendar</v-icon>
|
||||
{{ item.start_time.split(" ")[0] }} -
|
||||
{{ item.end_time.split(" ")[0] }}</v-card-subtitle
|
||||
>
|
||||
<v-card-subtitle v-show="appStore.devMode">id: {{ item.id }}</v-card-subtitle>
|
||||
<v-btn v-show="appStore.devMode" class="card-dev-btn" @click="toJson(item)">
|
||||
<template v-slot:prepend>
|
||||
<img src="../assets/icons/arrow-right.svg" alt="right" />
|
||||
</template>
|
||||
查看数据
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</div>
|
||||
</v-window-item>
|
||||
</v-window>
|
||||
</div>
|
||||
<div v-if="loading">
|
||||
<TLoading :title="loadingTitle" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<v-tabs v-model="tab" align-tabs="start" class="global-font mb-2">
|
||||
<v-tab value="activity" title="活动公告" />
|
||||
<v-tab value="game" title="游戏公告" />
|
||||
<v-spacer />
|
||||
<v-btn class="switch-btn" @click="switchNews">
|
||||
<template #prepend>
|
||||
<v-icon>mdi-bullhorn</v-icon>
|
||||
</template>
|
||||
切换米游社咨讯
|
||||
</v-btn>
|
||||
</v-tabs>
|
||||
<v-window v-model="tab">
|
||||
<v-window-item value="activity">
|
||||
<div class="anno-grid">
|
||||
<v-card v-for="item in annoCards.activity" :key="item.id" class="anno-card" width="340">
|
||||
<div class="anno-cover" @click="toPost(item)">
|
||||
<img :src="item.banner" alt="cover">
|
||||
</div>
|
||||
<v-card-title>
|
||||
{{ item.title }}
|
||||
</v-card-title>
|
||||
<v-card-subtitle>{{ item.subtitle }}</v-card-subtitle>
|
||||
<v-card-actions>
|
||||
<v-btn class="anno-btn" @click="toPost(item)">
|
||||
<template #prepend>
|
||||
<img :src="item.tag_icon || '../assets/icons/arrow-right.svg'" alt="right">
|
||||
</template>
|
||||
查看
|
||||
</v-btn>
|
||||
<v-card-subtitle v-show="!appStore.devMode">
|
||||
<v-icon>mdi-calendar</v-icon>
|
||||
{{ item.start_time.split(" ")[0] }} -
|
||||
{{ item.end_time.split(" ")[0] }}
|
||||
</v-card-subtitle>
|
||||
<v-card-subtitle v-show="appStore.devMode">
|
||||
id: {{ item.id }}
|
||||
</v-card-subtitle>
|
||||
<v-btn v-show="appStore.devMode" class="card-dev-btn" @click="toJson(item)">
|
||||
<template #prepend>
|
||||
<img src="../assets/icons/arrow-right.svg" alt="right">
|
||||
</template>
|
||||
查看数据
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</div>
|
||||
</v-window-item>
|
||||
<v-window-item value="game">
|
||||
<div class="anno-grid">
|
||||
<v-card v-for="item in annoCards.game" :key="item.id" class="anno-card" width="340">
|
||||
<div class="anno-cover" @click="toPost(item)">
|
||||
<img :src="item.banner" alt="cover">
|
||||
</div>
|
||||
<v-card-title>{{ item.title }}</v-card-title>
|
||||
<v-card-subtitle>{{ item.subtitle }}</v-card-subtitle>
|
||||
<v-card-actions>
|
||||
<v-btn class="anno-btn" @click="toPost(item)">
|
||||
<template #prepend>
|
||||
<img :src="item.tag_icon || '../assets/icons/arrow-right.svg'" alt="right">
|
||||
</template>
|
||||
查看
|
||||
</v-btn>
|
||||
<v-card-subtitle v-show="!appStore.devMode">
|
||||
<v-icon>mdi-calendar</v-icon>
|
||||
{{ item.start_time.split(" ")[0] }} -
|
||||
{{ item.end_time.split(" ")[0] }}
|
||||
</v-card-subtitle>
|
||||
<v-card-subtitle v-show="appStore.devMode">
|
||||
id: {{ item.id }}
|
||||
</v-card-subtitle>
|
||||
<v-btn v-show="appStore.devMode" class="card-dev-btn" @click="toJson(item)">
|
||||
<template #prepend>
|
||||
<img src="../assets/icons/arrow-right.svg" alt="right">
|
||||
</template>
|
||||
查看数据
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</div>
|
||||
</v-window-item>
|
||||
</v-window>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@@ -108,48 +112,48 @@ const router = useRouter();
|
||||
// 数据
|
||||
const tab = ref("");
|
||||
const annoCards = ref({
|
||||
activity: [] as AnnoListCard[],
|
||||
game: [] as AnnoListCard[],
|
||||
activity: [] as AnnoListCard[],
|
||||
game: [] as AnnoListCard[],
|
||||
});
|
||||
const annoData = ref({} as AnnoListData);
|
||||
|
||||
onMounted(async () => {
|
||||
loadingTitle.value = "正在获取公告数据";
|
||||
annoData.value = await GenshinOper.Announcement.getList();
|
||||
loadingTitle.value = "正在转换公告数据";
|
||||
const listCards = GenshinOper.Announcement.card(annoData.value);
|
||||
const activityCard = listCards.filter(item => item.type_label === "活动公告");
|
||||
const newsCard = listCards.filter(item => item.type_label === "游戏公告");
|
||||
annoCards.value = {
|
||||
activity: activityCard,
|
||||
game: newsCard,
|
||||
};
|
||||
tab.value = "activity";
|
||||
loading.value = false;
|
||||
loadingTitle.value = "正在获取公告数据";
|
||||
annoData.value = await GenshinOper.Announcement.getList();
|
||||
loadingTitle.value = "正在转换公告数据";
|
||||
const listCards = GenshinOper.Announcement.card(annoData.value);
|
||||
const activityCard = listCards.filter((item) => item.type_label === "活动公告");
|
||||
const newsCard = listCards.filter((item) => item.type_label === "游戏公告");
|
||||
annoCards.value = {
|
||||
activity: activityCard,
|
||||
game: newsCard,
|
||||
};
|
||||
tab.value = "activity";
|
||||
loading.value = false;
|
||||
});
|
||||
|
||||
function switchNews() {
|
||||
router.push("/news");
|
||||
async function switchNews () {
|
||||
await router.push("/news");
|
||||
}
|
||||
|
||||
async function toPost(item: AnnoListCard) {
|
||||
const path = router.resolve({
|
||||
name: "游戏内公告",
|
||||
params: {
|
||||
anno_id: item.id,
|
||||
},
|
||||
}).href;
|
||||
createTGWindow(path, "游戏内公告", item.title, 960, 720, false);
|
||||
async function toPost (item: AnnoListCard) {
|
||||
const path = router.resolve({
|
||||
name: "游戏内公告",
|
||||
params: {
|
||||
anno_id: item.id,
|
||||
},
|
||||
}).href;
|
||||
createTGWindow(path, "游戏内公告", item.title, 960, 720, false);
|
||||
}
|
||||
|
||||
async function toJson(item: AnnoListCard) {
|
||||
const path = router.resolve({
|
||||
name: "游戏内公告(JSON)",
|
||||
params: {
|
||||
anno_id: item.id,
|
||||
},
|
||||
}).href;
|
||||
createTGWindow(path, "游戏内公告-JSON", item.title, 960, 720, false);
|
||||
async function toJson (item: AnnoListCard) {
|
||||
const path = router.resolve({
|
||||
name: "游戏内公告(JSON)",
|
||||
params: {
|
||||
anno_id: item.id,
|
||||
},
|
||||
}).href;
|
||||
createTGWindow(path, "游戏内公告-JSON", item.title, 960, 720, false);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,122 +1,127 @@
|
||||
<template>
|
||||
<div v-if="loading">
|
||||
<t-loading />
|
||||
</div>
|
||||
<div v-else>
|
||||
<v-list class="config-list">
|
||||
<v-list-subheader inset class="config-header">应用信息</v-list-subheader>
|
||||
<v-divider inset class="border-opacity-75" />
|
||||
<v-list-item title="Tauri 版本" @click="toOuter('https://next--tauri.netlify.app/')">
|
||||
<template v-slot:prepend>
|
||||
<img class="config-icon" src="/platforms/tauri.webp" alt="Tauri" />
|
||||
</template>
|
||||
<template v-slot:append>
|
||||
<v-list-item-subtitle>{{ versionTauri }}</v-list-item-subtitle>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<template v-slot:prepend>
|
||||
<img class="config-icon" src="/icon.webp" alt="App" />
|
||||
</template>
|
||||
<v-list-item-title>
|
||||
应用版本
|
||||
<v-btn
|
||||
class="card-btn"
|
||||
size="small"
|
||||
@click="toOuter('https://github.com/BTMuli/Tauri.Genshin/releases/latest')"
|
||||
>Alpha</v-btn
|
||||
>
|
||||
</v-list-item-title>
|
||||
<template v-slot:append>
|
||||
<v-list-item-subtitle>{{ versionApp }}</v-list-item-subtitle>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item title="成就版本">
|
||||
<template v-slot:prepend>
|
||||
<img class="config-icon" src="../assets/icons/achievements.svg" alt="Achievements" />
|
||||
</template>
|
||||
<template v-slot:append>
|
||||
<v-list-item-subtitle>{{ achievementsStore.last_version }}</v-list-item-subtitle>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-subheader inset class="config-header">系统信息</v-list-subheader>
|
||||
<v-divider inset class="border-opacity-75" />
|
||||
<v-list-item title="系统平台">
|
||||
<template v-slot:prepend>
|
||||
<v-icon>mdi-desktop-classic</v-icon>
|
||||
</template>
|
||||
<template v-slot:append>
|
||||
<v-list-item-subtitle>{{ osPlatform }}</v-list-item-subtitle>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item title="系统版本">
|
||||
<template v-slot:prepend>
|
||||
<v-icon>mdi-desktop-classic</v-icon>
|
||||
</template>
|
||||
<template v-slot:append>
|
||||
<v-list-item-subtitle>{{ osVersion }}</v-list-item-subtitle>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-subheader inset class="config-header">设置</v-list-subheader>
|
||||
<v-divider inset class="border-opacity-75" />
|
||||
<v-list-item @click="openMergeData" prepend-icon="mdi-folder" title="打开用户数据目录" />
|
||||
<v-list-item @click="tryConfirm('delUser')" prepend-icon="mdi-delete" title="清除用户缓存" />
|
||||
<v-list-item @click="tryConfirm('delTemp')" prepend-icon="mdi-delete" title="清除临时数据" />
|
||||
<v-list-item @click="tryConfirm('delApp')" prepend-icon="mdi-cog" title="恢复默认设置" />
|
||||
<v-list-subheader inset class="config-header">调试</v-list-subheader>
|
||||
<v-divider inset class="border-opacity-75" />
|
||||
<v-list-item title="开发者模式" subtitle="开启后将显示调试信息">
|
||||
<template v-slot:prepend>
|
||||
<v-icon>mdi-bug</v-icon>
|
||||
</template>
|
||||
<template v-slot:append>
|
||||
<v-switch
|
||||
:label="appStore.devMode ? '开启' : '关闭'"
|
||||
inset
|
||||
v-model="appStore.devMode"
|
||||
color="#FAC51E"
|
||||
@click="submitDevMode"
|
||||
/>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<template v-slot:prepend>
|
||||
<v-icon>mdi-view-dashboard</v-icon>
|
||||
</template>
|
||||
<v-select
|
||||
v-model="showHome"
|
||||
:items="homeStore.getShowItem()"
|
||||
label="首页显示组件"
|
||||
multiple
|
||||
chips
|
||||
></v-select>
|
||||
<template v-slot:append>
|
||||
<v-btn @click="submitHome" class="card-btn">
|
||||
<template v-slot:prepend>
|
||||
<img src="../assets/icons/circle-check.svg" alt="check" />
|
||||
提交
|
||||
</template>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-subheader inset class="config-header">路径</v-list-subheader>
|
||||
<v-divider inset class="border-opacity-75" />
|
||||
<v-list-item prepend-icon="mdi-folder">
|
||||
<v-list-item-title>本地应用数据路径</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ appStore.dataPath.app }}</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
<v-list-item prepend-icon="mdi-folder">
|
||||
<v-list-item-title>本地用户数据路径</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ appStore.dataPath.user }}</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<!-- 弹窗提示条 -->
|
||||
<v-snackbar v-model="snackbar" timeout="1500" :color="snackbarColor">
|
||||
{{ snackbarText }}
|
||||
</v-snackbar>
|
||||
<!-- 确认弹窗 -->
|
||||
<t-confirm :title="confirmText" v-model="confirmShow" @confirm="doConfirm(confirmOper)" />
|
||||
</div>
|
||||
<div v-if="loading">
|
||||
<TLoading />
|
||||
</div>
|
||||
<div v-else>
|
||||
<v-list class="config-list">
|
||||
<v-list-subheader inset class="config-header">
|
||||
应用信息
|
||||
</v-list-subheader>
|
||||
<v-divider inset class="border-opacity-75" />
|
||||
<v-list-item title="Tauri 版本" @click="toOuter('https://next--tauri.netlify.app/')">
|
||||
<template #prepend>
|
||||
<img class="config-icon" src="/platforms/tauri.webp" alt="Tauri">
|
||||
</template>
|
||||
<template #append>
|
||||
<v-list-item-subtitle>{{ versionTauri }}</v-list-item-subtitle>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<template #prepend>
|
||||
<img class="config-icon" src="/icon.webp" alt="App">
|
||||
</template>
|
||||
<v-list-item-title>
|
||||
应用版本
|
||||
<v-btn
|
||||
class="card-btn"
|
||||
size="small"
|
||||
@click="toOuter('https://github.com/BTMuli/Tauri.Genshin/releases/latest')"
|
||||
>
|
||||
Alpha
|
||||
</v-btn>
|
||||
</v-list-item-title>
|
||||
<template #append>
|
||||
<v-list-item-subtitle>{{ versionApp }}</v-list-item-subtitle>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item title="成就版本">
|
||||
<template #prepend>
|
||||
<img class="config-icon" src="../assets/icons/achievements.svg" alt="Achievements">
|
||||
</template>
|
||||
<template #append>
|
||||
<v-list-item-subtitle>{{ achievementsStore.last_version }}</v-list-item-subtitle>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-subheader inset class="config-header">
|
||||
系统信息
|
||||
</v-list-subheader>
|
||||
<v-divider inset class="border-opacity-75" />
|
||||
<v-list-item title="系统平台">
|
||||
<template #prepend>
|
||||
<v-icon>mdi-desktop-classic</v-icon>
|
||||
</template>
|
||||
<template #append>
|
||||
<v-list-item-subtitle>{{ osPlatform }}</v-list-item-subtitle>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item title="系统版本">
|
||||
<template #prepend>
|
||||
<v-icon>mdi-desktop-classic</v-icon>
|
||||
</template>
|
||||
<template #append>
|
||||
<v-list-item-subtitle>{{ osVersion }}</v-list-item-subtitle>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-subheader inset class="config-header">
|
||||
设置
|
||||
</v-list-subheader>
|
||||
<v-divider inset class="border-opacity-75" />
|
||||
<v-list-item prepend-icon="mdi-folder" title="打开用户数据目录" @click="openMergeData" />
|
||||
<v-list-item prepend-icon="mdi-delete" title="清除用户缓存" @click="tryConfirm('delUser')" />
|
||||
<v-list-item prepend-icon="mdi-delete" title="清除临时数据" @click="tryConfirm('delTemp')" />
|
||||
<v-list-item prepend-icon="mdi-cog" title="恢复默认设置" @click="tryConfirm('delApp')" />
|
||||
<v-list-subheader inset class="config-header">
|
||||
调试
|
||||
</v-list-subheader>
|
||||
<v-divider inset class="border-opacity-75" />
|
||||
<v-list-item title="开发者模式" subtitle="开启后将显示调试信息">
|
||||
<template #prepend>
|
||||
<v-icon>mdi-bug</v-icon>
|
||||
</template>
|
||||
<template #append>
|
||||
<v-switch
|
||||
v-model="appStore.devMode"
|
||||
:label="appStore.devMode ? '开启' : '关闭'"
|
||||
inset
|
||||
color="#FAC51E"
|
||||
@click="submitDevMode"
|
||||
/>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<template #prepend>
|
||||
<v-icon>mdi-view-dashboard</v-icon>
|
||||
</template>
|
||||
<v-select v-model="showHome" :items="homeStore.getShowItem()" label="首页显示组件" multiple chips />
|
||||
<template #append>
|
||||
<v-btn class="card-btn" @click="submitHome">
|
||||
<template #prepend>
|
||||
<img src="../assets/icons/circle-check.svg" alt="check">
|
||||
提交
|
||||
</template>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-subheader inset class="config-header">
|
||||
路径
|
||||
</v-list-subheader>
|
||||
<v-divider inset class="border-opacity-75" />
|
||||
<v-list-item prepend-icon="mdi-folder">
|
||||
<v-list-item-title>本地应用数据路径</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ appStore.dataPath.app }}</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
<v-list-item prepend-icon="mdi-folder">
|
||||
<v-list-item-title>本地用户数据路径</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ appStore.dataPath.user }}</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<!-- 弹窗提示条 -->
|
||||
<v-snackbar v-model="snackbar" timeout="1500" :color="snackbarColor">
|
||||
{{ snackbarText }}
|
||||
</v-snackbar>
|
||||
<!-- 确认弹窗 -->
|
||||
<TConfirm v-model="confirmShow" :title="confirmText" @confirm="doConfirm(confirmOper)" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@@ -166,140 +171,138 @@ const confirmShow = ref(false as boolean);
|
||||
|
||||
// load version
|
||||
onMounted(async () => {
|
||||
versionApp.value = await app.getVersion();
|
||||
versionTauri.value = await app.getTauriVersion();
|
||||
osPlatform.value = `${await os.platform()}`;
|
||||
osVersion.value = await os.version();
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
}, 1000);
|
||||
versionApp.value = await app.getVersion();
|
||||
versionTauri.value = await app.getTauriVersion();
|
||||
osPlatform.value = `${await os.platform()}`;
|
||||
osVersion.value = await os.version();
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
// 打开外部链接
|
||||
function toOuter(url: string) {
|
||||
window.open(url);
|
||||
function toOuter (url: string) {
|
||||
window.open(url);
|
||||
}
|
||||
|
||||
// 打开用户数据目录
|
||||
async function openMergeData() {
|
||||
await dialog.open({
|
||||
defaultPath: appStore.dataPath.user,
|
||||
filters: [],
|
||||
});
|
||||
async function openMergeData () {
|
||||
await dialog.open({
|
||||
defaultPath: appStore.dataPath.user,
|
||||
filters: [],
|
||||
});
|
||||
}
|
||||
|
||||
// open confirm
|
||||
function tryConfirm(oper: string) {
|
||||
switch (oper) {
|
||||
case "delTemp":
|
||||
confirmText.value = "确认清除临时数据吗?";
|
||||
confirmOper.value = "delTemp";
|
||||
confirmShow.value = true;
|
||||
break;
|
||||
case "delUser":
|
||||
confirmText.value = "确认清除用户缓存吗?";
|
||||
confirmOper.value = "delUser";
|
||||
confirmShow.value = true;
|
||||
break;
|
||||
case "delApp":
|
||||
confirmText.value = "确认恢复默认设置吗?";
|
||||
confirmOper.value = "delApp";
|
||||
confirmShow.value = true;
|
||||
break;
|
||||
}
|
||||
function tryConfirm (oper: string) {
|
||||
switch (oper) {
|
||||
case "delTemp":
|
||||
confirmText.value = "确认清除临时数据吗?";
|
||||
confirmOper.value = "delTemp";
|
||||
confirmShow.value = true;
|
||||
break;
|
||||
case "delUser":
|
||||
confirmText.value = "确认清除用户缓存吗?";
|
||||
confirmOper.value = "delUser";
|
||||
confirmShow.value = true;
|
||||
break;
|
||||
case "delApp":
|
||||
confirmText.value = "确认恢复默认设置吗?";
|
||||
confirmOper.value = "delApp";
|
||||
confirmShow.value = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// transfer confirm oper
|
||||
function doConfirm(oper: string) {
|
||||
switch (oper) {
|
||||
case "delTemp":
|
||||
delTempData();
|
||||
break;
|
||||
case "delUser":
|
||||
delUserData();
|
||||
break;
|
||||
case "delApp":
|
||||
initAppData();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
async function doConfirm (oper: string) {
|
||||
switch (oper) {
|
||||
case "delTemp":
|
||||
await delTempData();
|
||||
break;
|
||||
case "delUser":
|
||||
await delUserData();
|
||||
break;
|
||||
case "delApp":
|
||||
await initAppData();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// confirmOper
|
||||
async function delTempData() {
|
||||
await fs.removeDir("tempData", {
|
||||
dir: fs.BaseDirectory.AppLocalData,
|
||||
recursive: true,
|
||||
});
|
||||
await fs.createDir("tempData", { dir: fs.BaseDirectory.AppLocalData });
|
||||
snackbarText.value = "临时数据已删除!";
|
||||
snackbar.value = true;
|
||||
async function delTempData () {
|
||||
await fs.removeDir("tempData", {
|
||||
dir: fs.BaseDirectory.AppLocalData,
|
||||
recursive: true,
|
||||
});
|
||||
await fs.createDir("tempData", { dir: fs.BaseDirectory.AppLocalData });
|
||||
snackbarText.value = "临时数据已删除!";
|
||||
snackbar.value = true;
|
||||
}
|
||||
|
||||
async function delUserData() {
|
||||
await fs.removeDir("userData", {
|
||||
dir: fs.BaseDirectory.AppLocalData,
|
||||
recursive: true,
|
||||
});
|
||||
await fs.removeDir("tempData", {
|
||||
dir: fs.BaseDirectory.AppLocalData,
|
||||
recursive: true,
|
||||
});
|
||||
getDataList.map(async item => {
|
||||
await WriteTGData(item.name, item.data);
|
||||
});
|
||||
snackbarText.value = "用户数据已删除!";
|
||||
snackbar.value = true;
|
||||
await achievementsStore.init();
|
||||
await fs.createDir("userData", { dir: fs.BaseDirectory.AppLocalData });
|
||||
await fs.createDir("tempData", { dir: fs.BaseDirectory.AppLocalData });
|
||||
async function delUserData () {
|
||||
await fs.removeDir("userData", {
|
||||
dir: fs.BaseDirectory.AppLocalData,
|
||||
recursive: true,
|
||||
});
|
||||
await fs.removeDir("tempData", {
|
||||
dir: fs.BaseDirectory.AppLocalData,
|
||||
recursive: true,
|
||||
});
|
||||
getDataList.map(async (item) => {
|
||||
await WriteTGData(item.name, item.data);
|
||||
});
|
||||
snackbarText.value = "用户数据已删除!";
|
||||
snackbar.value = true;
|
||||
await achievementsStore.init();
|
||||
await fs.createDir("userData", { dir: fs.BaseDirectory.AppLocalData });
|
||||
await fs.createDir("tempData", { dir: fs.BaseDirectory.AppLocalData });
|
||||
}
|
||||
|
||||
// 恢复默认配置
|
||||
async function initAppData() {
|
||||
await appStore.init();
|
||||
await homeStore.init();
|
||||
await achievementsStore.init();
|
||||
snackbarText.value = "已恢复默认配置!";
|
||||
snackbar.value = true;
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1500);
|
||||
async function initAppData () {
|
||||
await appStore.init();
|
||||
await homeStore.init();
|
||||
await achievementsStore.init();
|
||||
snackbarText.value = "已恢复默认配置!";
|
||||
snackbar.value = true;
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
// 开启 dev 模式
|
||||
async function submitDevMode() {
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
appStore.devMode
|
||||
? (snackbarText.value = "已开启 dev 模式!")
|
||||
: (snackbarText.value = "已关闭 dev 模式!");
|
||||
snackbarColor.value = "success";
|
||||
snackbar.value = true;
|
||||
async function submitDevMode () {
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
appStore.devMode ? (snackbarText.value = "已开启 dev 模式!") : (snackbarText.value = "已关闭 dev 模式!");
|
||||
snackbarColor.value = "success";
|
||||
snackbar.value = true;
|
||||
}
|
||||
|
||||
// 修改首页显示
|
||||
async function submitHome() {
|
||||
// 获取已选
|
||||
const show = showHome.value;
|
||||
if (show.length < 1) {
|
||||
snackbarText.value = "请至少选择一个!";
|
||||
snackbarColor.value = "error";
|
||||
snackbar.value = true;
|
||||
return;
|
||||
}
|
||||
// 设置
|
||||
await homeStore.setShowValue(show);
|
||||
snackbarText.value = "已修改!";
|
||||
snackbarColor.value = "success";
|
||||
snackbar.value = true;
|
||||
async function submitHome () {
|
||||
// 获取已选
|
||||
const show = showHome.value;
|
||||
if (show.length < 1) {
|
||||
snackbarText.value = "请至少选择一个!";
|
||||
snackbarColor.value = "error";
|
||||
snackbar.value = true;
|
||||
return;
|
||||
}
|
||||
// 设置
|
||||
await homeStore.setShowValue(show);
|
||||
snackbarText.value = "已修改!";
|
||||
snackbarColor.value = "success";
|
||||
snackbar.value = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.config-list {
|
||||
margin: 10px;
|
||||
font-family: "Genshin-Light", serif;
|
||||
font-family: Genshin-Light, serif;
|
||||
background: #faf7e8;
|
||||
color: #546d8b;
|
||||
border-radius: 10px;
|
||||
|
||||
@@ -1,111 +1,93 @@
|
||||
<template>
|
||||
<div v-if="loading">
|
||||
<t-loading title="正在加载卡牌列表" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<v-tabs v-model="tab" align-tabs="start" class="global-font">
|
||||
<div v-show="!doSearch">
|
||||
<v-tab value="character" title="角色牌" />
|
||||
<v-tab value="action" title="行动牌" />
|
||||
<v-tab value="monster" title="魔物牌" />
|
||||
</div>
|
||||
<v-spacer></v-spacer>
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
append-icon="mdi-magnify"
|
||||
label="搜索"
|
||||
single-line
|
||||
hide-details
|
||||
@click:append="searchCard"
|
||||
@keyup.enter="searchCard"
|
||||
></v-text-field>
|
||||
</v-tabs>
|
||||
<div v-if="!doSearch">
|
||||
<v-window v-model="tab">
|
||||
<v-window-item value="character">
|
||||
<div class="GCG-grid">
|
||||
<v-card
|
||||
v-for="item in CardsInfoC"
|
||||
:key="item.id"
|
||||
class="card-cls"
|
||||
@click="toOuter(item.name, item.id)"
|
||||
>
|
||||
<div class="GCG-border">
|
||||
<img src="/source/GCG/base/bg-normal.webp" alt="border" />
|
||||
</div>
|
||||
<div class="GCG-cover">
|
||||
<img :src="item.icon.normal" alt="cover" />
|
||||
</div>
|
||||
<div class="GCG-content">
|
||||
<span>{{ item.name }}</span>
|
||||
</div>
|
||||
</v-card>
|
||||
</div>
|
||||
</v-window-item>
|
||||
<v-window-item value="action">
|
||||
<div class="GCG-grid">
|
||||
<v-card
|
||||
v-for="item in CardsInfoA"
|
||||
:key="item.id"
|
||||
class="card-cls"
|
||||
@click="toOuter(item.name, item.id)"
|
||||
>
|
||||
<div class="GCG-border">
|
||||
<img src="/source/GCG/base/bg-normal.webp" alt="border" />
|
||||
</div>
|
||||
<div class="GCG-cover">
|
||||
<img :src="item.icon.normal" alt="cover" />
|
||||
</div>
|
||||
<div class="GCG-content">
|
||||
<span>{{ item.name }}</span>
|
||||
</div>
|
||||
</v-card>
|
||||
</div>
|
||||
</v-window-item>
|
||||
<v-window-item value="monster">
|
||||
<div class="GCG-grid">
|
||||
<v-card
|
||||
v-for="item in CardsInfoM"
|
||||
:key="item.id"
|
||||
class="card-cls"
|
||||
@click="toOuter(item.name, item.id)"
|
||||
>
|
||||
<div class="GCG-border">
|
||||
<img src="/source/GCG/base/bg-normal.webp" alt="border" />
|
||||
</div>
|
||||
<div class="GCG-cover">
|
||||
<img :src="item.icon.normal" alt="cover" />
|
||||
</div>
|
||||
<div class="GCG-content">
|
||||
<span>{{ item.name }}</span>
|
||||
</div>
|
||||
</v-card>
|
||||
</div>
|
||||
</v-window-item>
|
||||
</v-window>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="GCG-grid">
|
||||
<div
|
||||
v-for="item in CardsInfoS"
|
||||
:key="item.id"
|
||||
class="card-cls"
|
||||
@click="toOuter(item.name, item.id)"
|
||||
>
|
||||
<div class="GCG-border">
|
||||
<img src="/source/GCG/base/bg-normal.webp" alt="border" />
|
||||
</div>
|
||||
<div class="GCG-cover">
|
||||
<img :src="item.icon.normal" alt="cover" />
|
||||
</div>
|
||||
<div class="GCG-content">
|
||||
<span>{{ item.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<v-snackbar v-model="snackbar" timeout="1500" color="error"> 未找到相关卡牌 </v-snackbar>
|
||||
</div>
|
||||
<div v-if="loading">
|
||||
<TLoading title="正在加载卡牌列表" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<v-tabs v-model="tab" align-tabs="start" class="global-font">
|
||||
<div v-show="!doSearch">
|
||||
<v-tab value="character" title="角色牌" />
|
||||
<v-tab value="action" title="行动牌" />
|
||||
<v-tab value="monster" title="魔物牌" />
|
||||
</div>
|
||||
<v-spacer />
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
append-icon="mdi-magnify"
|
||||
label="搜索"
|
||||
single-line
|
||||
hide-details
|
||||
@click:append="searchCard"
|
||||
@keyup.enter="searchCard"
|
||||
/>
|
||||
</v-tabs>
|
||||
<div v-if="!doSearch">
|
||||
<v-window v-model="tab">
|
||||
<v-window-item value="character">
|
||||
<div class="cards-grid">
|
||||
<v-card v-for="item in CardsInfoC" :key="item.id" class="card-cls" @click="toOuter(item.name, item.id)">
|
||||
<div class="card-border">
|
||||
<img src="/source/GCG/base/bg-normal.webp" alt="border">
|
||||
</div>
|
||||
<div class="card-cover">
|
||||
<img :src="item.icon.normal" alt="cover">
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<span>{{ item.name }}</span>
|
||||
</div>
|
||||
</v-card>
|
||||
</div>
|
||||
</v-window-item>
|
||||
<v-window-item value="action">
|
||||
<div class="cards-grid">
|
||||
<v-card v-for="item in CardsInfoA" :key="item.id" class="card-cls" @click="toOuter(item.name, item.id)">
|
||||
<div class="card-border">
|
||||
<img src="/source/GCG/base/bg-normal.webp" alt="border">
|
||||
</div>
|
||||
<div class="card-cover">
|
||||
<img :src="item.icon.normal" alt="cover">
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<span>{{ item.name }}</span>
|
||||
</div>
|
||||
</v-card>
|
||||
</div>
|
||||
</v-window-item>
|
||||
<v-window-item value="monster">
|
||||
<div class="cards-grid">
|
||||
<v-card v-for="item in CardsInfoM" :key="item.id" class="card-cls" @click="toOuter(item.name, item.id)">
|
||||
<div class="card-border">
|
||||
<img src="/source/GCG/base/bg-normal.webp" alt="border">
|
||||
</div>
|
||||
<div class="card-cover">
|
||||
<img :src="item.icon.normal" alt="cover">
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<span>{{ item.name }}</span>
|
||||
</div>
|
||||
</v-card>
|
||||
</div>
|
||||
</v-window-item>
|
||||
</v-window>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="cards-grid">
|
||||
<div v-for="item in CardsInfoS" :key="item.id" class="card-cls" @click="toOuter(item.name, item.id)">
|
||||
<div class="card-border">
|
||||
<img src="/source/GCG/base/bg-normal.webp" alt="border">
|
||||
</div>
|
||||
<div class="card-cover">
|
||||
<img :src="item.icon.normal" alt="cover">
|
||||
</div>
|
||||
<div class="card-content">
|
||||
<span>{{ item.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<v-snackbar v-model="snackbar" timeout="1500" color="error">
|
||||
未找到相关卡牌
|
||||
</v-snackbar>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
@@ -133,38 +115,38 @@ const CardsInfoM = ref([] as MonsterCard[]);
|
||||
const CardsInfoS = ref([] as BaseCard[]);
|
||||
|
||||
onMounted(async () => {
|
||||
await loadData();
|
||||
await loadData();
|
||||
});
|
||||
|
||||
async function loadData() {
|
||||
const CardsInfo = await ReadAllTGData("GCG");
|
||||
CardsInfoC.value = CardsInfo.filter(item => item.type == "角色牌") as CharacterCard[];
|
||||
CardsInfoA.value = CardsInfo.filter(item => item.type == "行动牌") as ActionCard[];
|
||||
CardsInfoM.value = CardsInfo.filter(item => item.type == "魔物牌") as MonsterCard[];
|
||||
loading.value = false;
|
||||
async function loadData () {
|
||||
const CardsInfo = await ReadAllTGData("GCG");
|
||||
CardsInfoC.value = CardsInfo.filter((item) => item.type === "角色牌") as CharacterCard[];
|
||||
CardsInfoA.value = CardsInfo.filter((item) => item.type === "行动牌") as ActionCard[];
|
||||
CardsInfoM.value = CardsInfo.filter((item) => item.type === "魔物牌") as MonsterCard[];
|
||||
loading.value = false;
|
||||
}
|
||||
function toOuter(card_name: string, card_id: number) {
|
||||
const url = OBC_CONTENT_API.replace("{content_id}", card_id.toString());
|
||||
createTGWindow(url, "GCG", card_name, 1200, 800, true);
|
||||
function toOuter (card_name: string, card_id: number) {
|
||||
const url = OBC_CONTENT_API.replace("{content_id}", card_id.toString());
|
||||
createTGWindow(url, "GCG", card_name, 1200, 800, true);
|
||||
}
|
||||
async function searchCard() {
|
||||
loading.value = true;
|
||||
doSearch.value = true;
|
||||
const res: BaseCard[] = [];
|
||||
const allCardsInfo = await ReadAllTGData("GCG");
|
||||
allCardsInfo.map(item => (item.name.includes(search.value) ? res.push(item) : null));
|
||||
res.sort((a, b) => a.name.localeCompare(b.name));
|
||||
loading.value = false;
|
||||
if (res.length == 0) {
|
||||
snackbar.value = true;
|
||||
doSearch.value = false;
|
||||
} else {
|
||||
CardsInfoS.value = res;
|
||||
}
|
||||
async function searchCard () {
|
||||
loading.value = true;
|
||||
doSearch.value = true;
|
||||
const res: BaseCard[] = [];
|
||||
const allCardsInfo = await ReadAllTGData("GCG");
|
||||
allCardsInfo.map((item) => (item.name.includes(search.value) ? res.push(item) : null));
|
||||
res.sort((a, b) => a.name.localeCompare(b.name));
|
||||
loading.value = false;
|
||||
if (res.length === 0) {
|
||||
snackbar.value = true;
|
||||
doSearch.value = false;
|
||||
} else {
|
||||
CardsInfoS.value = res;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
.GCG-grid {
|
||||
.cards-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
||||
grid-gap: 10px;
|
||||
@@ -182,26 +164,7 @@ async function searchCard() {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.card-cls:hover .GCG-cover {
|
||||
transform: scale(1.1);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.GCG-border {
|
||||
position: absolute;
|
||||
border-radius: 10px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.GCG-border img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.GCG-cover {
|
||||
.card-cover {
|
||||
position: absolute;
|
||||
transition: all 0.3s;
|
||||
top: 0;
|
||||
@@ -211,20 +174,39 @@ async function searchCard() {
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.GCG-cover img {
|
||||
.card-cls:hover .card-cover {
|
||||
transform: scale(1.1);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.card-border {
|
||||
position: absolute;
|
||||
border-radius: 10px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-border img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.card-cover img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 10px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.GCG-content {
|
||||
.card-content {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
background: rgb(0 0 0 / 50%);
|
||||
color: white;
|
||||
display: flex;
|
||||
font-size: small;
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
<template>
|
||||
<t-loading v-if="loading" :title="loadingTitle" :subtitle="loadingSubtitle" />
|
||||
<component
|
||||
v-show="!loading"
|
||||
v-for="item in components"
|
||||
:is="item"
|
||||
:key="item"
|
||||
:ref="setItemRef"
|
||||
/>
|
||||
<TLoading v-if="loading" :title="loadingTitle" :subtitle="loadingSubtitle" />
|
||||
<component :is="item" v-for="item in components" v-show="!loading" :key="item" :ref="setItemRef" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@@ -29,44 +23,42 @@ const loadingSubtitle = ref("");
|
||||
|
||||
// data
|
||||
const components = ref([] as any[]);
|
||||
let itemRefs = ref([] as any[]);
|
||||
const itemRefs = ref([] as any[]);
|
||||
|
||||
onMounted(async () => {
|
||||
loadingTitle.value = "正在加载首页";
|
||||
const showItems = homeStore.getShowValue();
|
||||
await Promise.allSettled(
|
||||
showItems.map(item => {
|
||||
switch (item) {
|
||||
case "限时祈愿":
|
||||
return components.value.push(markRaw(TPool));
|
||||
case "近期活动":
|
||||
return components.value.push(markRaw(TPosition));
|
||||
case "素材日历":
|
||||
return components.value.push(markRaw(TCalendar));
|
||||
default:
|
||||
break;
|
||||
}
|
||||
})
|
||||
);
|
||||
setInterval(() => {
|
||||
if (!loading.value) clearInterval(this);
|
||||
const loadingMap = itemRefs.value.map(item => {
|
||||
if (item.loading) {
|
||||
return item.name;
|
||||
}
|
||||
});
|
||||
loadingSubtitle.value = "正在加载 " + loadingMap.filter(item => item)?.join("、");
|
||||
if (loadingMap.every(item => !item)) {
|
||||
loading.value = false;
|
||||
}
|
||||
}, 100);
|
||||
loadingTitle.value = "正在加载首页";
|
||||
const showItems = homeStore.getShowValue();
|
||||
await Promise.allSettled(
|
||||
showItems.map((item) => {
|
||||
switch (item) {
|
||||
case "限时祈愿":
|
||||
return components.value.push(markRaw(TPool));
|
||||
case "近期活动":
|
||||
return components.value.push(markRaw(TPosition));
|
||||
case "素材日历":
|
||||
return components.value.push(markRaw(TCalendar));
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}),
|
||||
);
|
||||
setInterval(() => {
|
||||
if (!loading.value) clearInterval(this);
|
||||
const loadingMap = itemRefs.value.map((item) => {
|
||||
return item.loading ? item.name : null;
|
||||
});
|
||||
loadingSubtitle.value = "正在加载 " + loadingMap.filter((item) => item)?.join("、");
|
||||
if (loadingMap.every((item) => !item)) {
|
||||
loading.value = false;
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
function setItemRef(item: any) {
|
||||
if (itemRefs.value.includes(item)) return;
|
||||
itemRefs.value.push(item);
|
||||
function setItemRef (item: any) {
|
||||
if (itemRefs.value.includes(item)) return;
|
||||
itemRefs.value.push(item);
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
itemRefs.value = [];
|
||||
itemRefs.value = [];
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
/**
|
||||
* @file utils TGMap
|
||||
* @description TGMap 的具体实现
|
||||
* @author BTMuli<bt-muli@outlook.com>
|
||||
* @since Alpha
|
||||
*/
|
||||
|
||||
import { Map } from "../interface/Base";
|
||||
|
||||
/**
|
||||
* @description TGMap 的具体实现
|
||||
* @since Alpha
|
||||
* @class TGMap
|
||||
* @template T
|
||||
* @see Map
|
||||
* @return TGMap
|
||||
*/
|
||||
class TGMap<T> {
|
||||
/**
|
||||
* @description TGMap 的实例化数据
|
||||
* @see Map
|
||||
* @description 使用 protected 修饰符,防止外部直接修改
|
||||
* @since Alpha
|
||||
* @return Map<T>
|
||||
*/
|
||||
protected map: Map<T>;
|
||||
|
||||
/**
|
||||
* @description TGMap 的构造函数
|
||||
* @since Alpha
|
||||
* @param {Map<T>} map - 传入的 Map<T>
|
||||
* @description 可选参数
|
||||
* @return TGMap
|
||||
*/
|
||||
constructor(map: Map<T> = {}) {
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 返回 TGMap 的实例化数据
|
||||
* @since Alpha
|
||||
* @return Map<T>
|
||||
*/
|
||||
getMap(): Map<T> {
|
||||
return this.map;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Map<T> 的 forEach 方法
|
||||
* @since Alpha
|
||||
* @param {(value: T, key: number, map: Map<T>) => void} callback - 回调函数
|
||||
* @return void
|
||||
*/
|
||||
forEach(callback: (value: T, key: number, map: Map<T>) => void): void {
|
||||
Object.keys(this.map).forEach(key => {
|
||||
callback(this.map[Number(key)], Number(key), this.map);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Map<T> 的 get 方法
|
||||
* @since Alpha
|
||||
* @param {number} key - 键
|
||||
* @return T
|
||||
*/
|
||||
get(key: number): T {
|
||||
return this.map[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Map<T> 的 set 方法
|
||||
* @since Alpha
|
||||
* @param {number} key - 键
|
||||
* @param {T} value - 值
|
||||
* @return void
|
||||
*/
|
||||
set(key: number, value: T): void {
|
||||
this.map[key] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Map<T> 的 has 方法
|
||||
* @since Alpha
|
||||
* @param {number} key - 键
|
||||
* @return boolean
|
||||
*/
|
||||
has(key: number): boolean {
|
||||
return this.map.hasOwnProperty(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Map<T> 的 sort 方法
|
||||
* @since Alpha
|
||||
* @param {(a: T, b: T) => number} callback - 回调函数
|
||||
* @return TGMap<T>
|
||||
*/
|
||||
sort(callback: (a: T, b: T) => number): TGMap<T> {
|
||||
const keys: string[] = Object.keys(this.map);
|
||||
const values: T[] = Object.values(this.map);
|
||||
const sortedValues: T[] = values.sort(callback);
|
||||
const sortedMap: Map<T> = {};
|
||||
keys.forEach((key, index) => {
|
||||
sortedMap[Number(key)] = sortedValues[index];
|
||||
});
|
||||
return new TGMap(sortedMap);
|
||||
}
|
||||
}
|
||||
|
||||
export default TGMap;
|
||||
@@ -1,13 +1,17 @@
|
||||
<template>
|
||||
<div v-if="loading">
|
||||
<t-loading :empty="loadingEmpty" :title="loadingTitle" />
|
||||
</div>
|
||||
<div v-else class="dev-json">
|
||||
<div class="anno-title">活动列表 JSON</div>
|
||||
<json-viewer :value="jsonList" copyable boxed />
|
||||
<div class="anno-title">活动内容 JSON</div>
|
||||
<json-viewer :value="jsonContent" copyable boxed />
|
||||
</div>
|
||||
<div v-if="loading">
|
||||
<TLoading :empty="loadingEmpty" :title="loadingTitle" />
|
||||
</div>
|
||||
<div v-else class="dev-json">
|
||||
<div class="anno-title">
|
||||
活动列表 JSON
|
||||
</div>
|
||||
<JsonViewer :value="jsonList" copyable boxed />
|
||||
<div class="anno-title">
|
||||
活动内容 JSON
|
||||
</div>
|
||||
<JsonViewer :value="jsonContent" copyable boxed />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
@@ -33,28 +37,25 @@ let jsonList = reactive({});
|
||||
let jsonContent = reactive({});
|
||||
|
||||
onMounted(async () => {
|
||||
await appWindow.show();
|
||||
// 检查数据
|
||||
if (!anno_id) {
|
||||
loadingEmpty.value = true;
|
||||
loadingTitle.value = "未找到数据";
|
||||
return;
|
||||
}
|
||||
// 获取数据
|
||||
loadingTitle.value = "正在获取数据...";
|
||||
const listData = await GenshinOper.Announcement.getList();
|
||||
listData.list.map((item: Announcement) => {
|
||||
return item.list.map((single: AnnoListItem) => {
|
||||
if (single.ann_id === anno_id) {
|
||||
jsonList = single;
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
jsonContent = await GenshinOper.Announcement.getContent(anno_id);
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
}, 200);
|
||||
await appWindow.show();
|
||||
// 检查数据
|
||||
if (!anno_id) {
|
||||
loadingEmpty.value = true;
|
||||
loadingTitle.value = "未找到数据";
|
||||
return;
|
||||
}
|
||||
// 获取数据
|
||||
loadingTitle.value = "正在获取数据...";
|
||||
const listData = await GenshinOper.Announcement.getList();
|
||||
listData.list.map((item: Announcement) => {
|
||||
return item.list.map((single: AnnoListItem) => {
|
||||
return single.ann_id === anno_id ? (jsonList = single) : null;
|
||||
});
|
||||
});
|
||||
jsonContent = await GenshinOper.Announcement.getContent(anno_id);
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
}, 200);
|
||||
});
|
||||
</script>
|
||||
<style lang="css" scoped>
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<template>
|
||||
<div v-if="loading" class="loading">
|
||||
<t-loading :title="loadingTitle" :empty="loadingEmpty" />
|
||||
</div>
|
||||
<div v-else class="anno-body">
|
||||
<div class="anno-title">{{ annoData.title }}</div>
|
||||
<div class="anno-subtitle">{{ annoData.subtitle }}</div>
|
||||
<img :src="annoData.banner" alt="cover" class="anno-img" />
|
||||
<div v-html="annoHtml" class="anno-content" />
|
||||
</div>
|
||||
<div v-if="loading" class="loading">
|
||||
<TLoading :title="loadingTitle" :empty="loadingEmpty" />
|
||||
</div>
|
||||
<div v-else class="anno-body">
|
||||
<div class="anno-title">
|
||||
{{ annoData.title }}
|
||||
</div>
|
||||
<div class="anno-subtitle">
|
||||
{{ annoData.subtitle }}
|
||||
</div>
|
||||
<img :src="annoData.banner" alt="cover" class="anno-img">
|
||||
<div class="anno-content" v-html="annoHtml" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
@@ -32,27 +37,27 @@ const annoData = ref({} as AnnoContentItem);
|
||||
const annoHtml = ref("");
|
||||
|
||||
onMounted(async () => {
|
||||
await appWindow.show();
|
||||
// 检查数据
|
||||
if (!anno_id) {
|
||||
loadingEmpty.value = true;
|
||||
loadingTitle.value = "未找到数据";
|
||||
return;
|
||||
}
|
||||
// 获取数据
|
||||
loadingTitle.value = "正在获取数据...";
|
||||
try {
|
||||
annoData.value = await GenshinOper.Announcement.getContent(anno_id);
|
||||
loadingTitle.value = "正在渲染数据...";
|
||||
annoHtml.value = GenshinOper.Announcement.parser(annoData.value.content);
|
||||
} catch (error) {
|
||||
loadingEmpty.value = true;
|
||||
loadingTitle.value = "公告不存在或解析失败";
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
}, 200);
|
||||
await appWindow.show();
|
||||
// 检查数据
|
||||
if (!anno_id) {
|
||||
loadingEmpty.value = true;
|
||||
loadingTitle.value = "未找到数据";
|
||||
return;
|
||||
}
|
||||
// 获取数据
|
||||
loadingTitle.value = "正在获取数据...";
|
||||
try {
|
||||
annoData.value = await GenshinOper.Announcement.getContent(anno_id);
|
||||
loadingTitle.value = "正在渲染数据...";
|
||||
annoHtml.value = GenshinOper.Announcement.parser(annoData.value.content);
|
||||
} catch (error) {
|
||||
loadingEmpty.value = true;
|
||||
loadingTitle.value = "公告不存在或解析失败";
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
}, 200);
|
||||
});
|
||||
</script>
|
||||
<style lang="css" src="../assets/css/anno-parser.css" scoped />
|
||||
|
||||
@@ -1,59 +1,69 @@
|
||||
<template>
|
||||
<div v-if="loading">
|
||||
<t-loading :empty="loadingEmpty" :title="loadingTitle" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="lottery-div">
|
||||
<div class="lottery-title">抽奖详情 {{ timeStatus }}</div>
|
||||
<v-list class="lottery-list">
|
||||
<v-list-item>
|
||||
<template v-slot:prepend>
|
||||
<v-avatar>
|
||||
<v-img :src="lotteryCard.creator.avatar_url" />
|
||||
</v-avatar>
|
||||
</template>
|
||||
{{ lotteryCard.creator.nickname }}
|
||||
<v-list-item-subtitle>{{ lotteryCard.creator.introduce }}</v-list-item-subtitle>
|
||||
<template v-slot:append>发起人</template>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-list-item-title>{{ lotteryCard.participantWay }}</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ lotteryCard.id }}</v-list-item-subtitle>
|
||||
<template v-slot:append>抽奖 ID</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<v-btn class="lottery-back" @click="backPost">返回</v-btn>
|
||||
<v-btn @click="showJson = true" class="card-dev-btn" v-show="appStore.devMode">
|
||||
<template v-slot:prepend>
|
||||
<img src="../assets/icons/arrow-right.svg" alt="right" />
|
||||
</template>
|
||||
JSON
|
||||
</v-btn>
|
||||
</div>
|
||||
<div class="dev-json" v-show="showJson">
|
||||
<json-viewer :value="jsonData" copyable boxed />
|
||||
</div>
|
||||
<div class="lottery-div">
|
||||
<div class="lottery-title">奖品详情</div>
|
||||
<div v-for="reward in lotteryCard.rewards">
|
||||
<v-list class="lottery-list">
|
||||
<v-list-item :title="reward.rewardName" :subtitle="'中奖人数' + reward.winnerNumber" />
|
||||
</v-list>
|
||||
<div class="lottery-grid">
|
||||
<v-list v-for="user in reward.users" class="lottery-sub-list">
|
||||
<v-list-item>
|
||||
<template v-slot:prepend>
|
||||
<v-avatar>
|
||||
<v-img :src="user.avatar_url" />
|
||||
</v-avatar>
|
||||
</template>
|
||||
{{ user.nickname }}
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="loading">
|
||||
<TLoading :empty="loadingEmpty" :title="loadingTitle" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="lottery-div">
|
||||
<div class="lottery-title">
|
||||
抽奖详情 {{ timeStatus }}
|
||||
</div>
|
||||
<v-list class="lottery-list">
|
||||
<v-list-item>
|
||||
<template #prepend>
|
||||
<v-avatar>
|
||||
<v-img :src="lotteryCard.creator.avatar_url" />
|
||||
</v-avatar>
|
||||
</template>
|
||||
{{ lotteryCard.creator.nickname }}
|
||||
<v-list-item-subtitle>{{ lotteryCard.creator.introduce }}</v-list-item-subtitle>
|
||||
<template #append>
|
||||
发起人
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-list-item-title>{{ lotteryCard.participantWay }}</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ lotteryCard.id }}</v-list-item-subtitle>
|
||||
<template #append>
|
||||
抽奖 ID
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<v-btn class="lottery-back" @click="backPost">
|
||||
返回
|
||||
</v-btn>
|
||||
<v-btn v-show="appStore.devMode" class="card-dev-btn" @click="showJson = true">
|
||||
<template #prepend>
|
||||
<img src="../assets/icons/arrow-right.svg" alt="right">
|
||||
</template>
|
||||
JSON
|
||||
</v-btn>
|
||||
</div>
|
||||
<div v-show="showJson" class="dev-json">
|
||||
<JsonViewer :value="jsonData" copyable boxed />
|
||||
</div>
|
||||
<div class="lottery-div">
|
||||
<div class="lottery-title">
|
||||
奖品详情
|
||||
</div>
|
||||
<div v-for="reward in lotteryCard.rewards" :key="reward.rewardName">
|
||||
<v-list class="lottery-list">
|
||||
<v-list-item :title="reward.rewardName" :subtitle="'中奖人数' + reward.winnerNumber" />
|
||||
</v-list>
|
||||
<div class="lottery-grid">
|
||||
<v-list v-for="user in reward.users" :key="user.uid" class="lottery-sub-list">
|
||||
<v-list-item>
|
||||
<template #prepend>
|
||||
<v-avatar>
|
||||
<v-img :src="user.avatar_url" />
|
||||
</v-avatar>
|
||||
</template>
|
||||
{{ user.nickname }}
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
@@ -85,50 +95,50 @@ const showJson = ref(false as boolean);
|
||||
let jsonData = reactive({} as LotteryData);
|
||||
const timeStatus = ref("未知" as string);
|
||||
|
||||
function backPost() {
|
||||
window.history.back();
|
||||
function backPost () {
|
||||
window.history.back();
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await appWindow.show();
|
||||
// 检查数据
|
||||
if (!lottery_id) {
|
||||
loadingEmpty.value = true;
|
||||
loadingTitle.value = "未找到数据";
|
||||
return;
|
||||
}
|
||||
// 获取数据
|
||||
loadingTitle.value = "正在获取数据...";
|
||||
jsonData = await MysOper.Lottery.get(lottery_id);
|
||||
if (!jsonData) {
|
||||
loadingEmpty.value = true;
|
||||
loadingTitle.value = "未找到数据";
|
||||
return;
|
||||
}
|
||||
await appWindow.setTitle("抽奖详情 " + jsonData.lottery_entity_summary);
|
||||
loadingTitle.value = "正在渲染数据...";
|
||||
lotteryCard.value = MysOper.Lottery.card.lottery(jsonData);
|
||||
if (jsonData.status === "Settled") {
|
||||
timeStatus.value = "已开奖";
|
||||
} else {
|
||||
await setInterval(() => {
|
||||
const timeNow = new Date().getTime();
|
||||
const timeDiff = Number(jsonData.draw_time) * 1000 - timeNow;
|
||||
if (timeDiff <= 0) {
|
||||
timeStatus.value = "已开奖";
|
||||
clearInterval(this);
|
||||
} else {
|
||||
const day = Math.floor(timeDiff / (24 * 3600 * 1000));
|
||||
const hour = Math.floor((timeDiff % (24 * 3600 * 1000)) / (3600 * 1000));
|
||||
const minute = Math.floor((timeDiff % (3600 * 1000)) / (60 * 1000));
|
||||
const second = Math.floor((timeDiff % (60 * 1000)) / 1000);
|
||||
timeStatus.value = `${day}天${hour}小时${minute}分${second}秒`;
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
}, 200);
|
||||
await appWindow.show();
|
||||
// 检查数据
|
||||
if (!lottery_id) {
|
||||
loadingEmpty.value = true;
|
||||
loadingTitle.value = "未找到数据";
|
||||
return;
|
||||
}
|
||||
// 获取数据
|
||||
loadingTitle.value = "正在获取数据...";
|
||||
jsonData = await MysOper.Lottery.get(lottery_id);
|
||||
if (!jsonData) {
|
||||
loadingEmpty.value = true;
|
||||
loadingTitle.value = "未找到数据";
|
||||
return;
|
||||
}
|
||||
await appWindow.setTitle("抽奖详情 " + jsonData.lottery_entity_summary);
|
||||
loadingTitle.value = "正在渲染数据...";
|
||||
lotteryCard.value = MysOper.Lottery.card.lottery(jsonData);
|
||||
if (jsonData.status === "Settled") {
|
||||
timeStatus.value = "已开奖";
|
||||
} else {
|
||||
await setInterval(() => {
|
||||
const timeNow = new Date().getTime();
|
||||
const timeDiff = Number(jsonData.draw_time) * 1000 - timeNow;
|
||||
if (timeDiff <= 0) {
|
||||
timeStatus.value = "已开奖";
|
||||
clearInterval(this);
|
||||
} else {
|
||||
const day = Math.floor(timeDiff / (24 * 3600 * 1000));
|
||||
const hour = Math.floor((timeDiff % (24 * 3600 * 1000)) / (3600 * 1000));
|
||||
const minute = Math.floor((timeDiff % (3600 * 1000)) / (60 * 1000));
|
||||
const second = Math.floor((timeDiff % (60 * 1000)) / 1000);
|
||||
timeStatus.value = `${day}天${hour}小时${minute}分${second}秒`;
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
}, 200);
|
||||
});
|
||||
</script>
|
||||
<style lang="css">
|
||||
|
||||
@@ -1,142 +1,143 @@
|
||||
<template>
|
||||
<div v-if="loading">
|
||||
<t-loading :title="loadingTitle" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<v-tabs v-model="tab" align-tabs="start" class="news-tabs">
|
||||
<v-tab value="notice" title="公告" />
|
||||
<v-tab value="activity" title="活动" />
|
||||
<v-tab value="news" title="新闻" v-if="showNews" />
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn class="switch-btn" @click="switchAnno" v-if="showSwitch">
|
||||
<template v-slot:prepend>
|
||||
<v-icon>mdi-bullhorn</v-icon>
|
||||
</template>
|
||||
切换游戏内公告
|
||||
</v-btn>
|
||||
<v-text-field
|
||||
v-show="appStore.devMode"
|
||||
v-model="search"
|
||||
append-icon="mdi-magnify"
|
||||
label="搜索"
|
||||
single-line
|
||||
hide-details
|
||||
@click:append="searchPost"
|
||||
@keyup.enter="searchPost"
|
||||
></v-text-field>
|
||||
</v-tabs>
|
||||
<v-window v-model="tab">
|
||||
<v-window-item value="notice">
|
||||
<div class="news-grid">
|
||||
<v-card v-for="item in postData.notice" class="news-card" width="340">
|
||||
<div class="news-cover" @click="toPost(item)">
|
||||
<img :src="item.cover" alt="cover" />
|
||||
</div>
|
||||
<v-card-title>{{ item.title }}</v-card-title>
|
||||
<v-card-actions>
|
||||
<v-btn @click="toPost(item)" class="card-btn">
|
||||
<template v-slot:prepend>
|
||||
<img src="../assets/icons/circle-check.svg" alt="check" />查看
|
||||
</template>
|
||||
</v-btn>
|
||||
<v-card-subtitle>id:{{ item.post_id }}</v-card-subtitle>
|
||||
<v-btn @click="toJson(item)" class="card-dev-btn" v-show="appStore.devMode">
|
||||
<template v-slot:prepend>
|
||||
<img src="../assets/icons/arrow-right.svg" alt="right" />
|
||||
</template>
|
||||
JSON
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</div>
|
||||
<div class="load-news">
|
||||
<v-btn @click="loadMore('notice')" :loading="loadingSub">
|
||||
<template v-slot:append>
|
||||
<img src="../assets/icons/arrow-left.svg" alt="right" />
|
||||
</template>
|
||||
已加载:{{ rawData.notice.last_id }},加载更多
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-window-item>
|
||||
<v-window-item value="activity">
|
||||
<div class="news-grid">
|
||||
<v-card class="news-card" v-for="item in postData.activity" width="340">
|
||||
<div class="news-cover" @click="toPost(item)">
|
||||
<img :src="item.cover" alt="cover" />
|
||||
</div>
|
||||
<v-card-title>{{ item.title }}</v-card-title>
|
||||
<v-card-subtitle>{{ item.subtitle }}</v-card-subtitle>
|
||||
<v-card-actions>
|
||||
<v-btn @click="toPost(item)" class="card-btn">
|
||||
<template v-slot:prepend>
|
||||
<img src="../assets/icons/circle-check.svg" alt="check" />查看
|
||||
</template>
|
||||
</v-btn>
|
||||
<v-card-subtitle>id:{{ item.post_id }}</v-card-subtitle>
|
||||
<div v-show="!appStore.devMode">
|
||||
<v-btn
|
||||
:style="{
|
||||
background: item.status?.colorCss,
|
||||
color: '#faf7e8 !important',
|
||||
}"
|
||||
>{{ item.status?.status }}</v-btn
|
||||
>
|
||||
</div>
|
||||
<v-btn @click="toJson(item)" class="card-dev-btn" v-show="appStore.devMode">
|
||||
<template v-slot:prepend>
|
||||
<img src="../assets/icons/arrow-right.svg" alt="right" />
|
||||
</template>
|
||||
JSON
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</div>
|
||||
<div class="load-news">
|
||||
<v-btn @click="loadMore('activity')" :loading="loadingSub">
|
||||
<template v-slot:append>
|
||||
<img src="../assets/icons/arrow-left.svg" alt="right" />
|
||||
</template>
|
||||
已加载:{{ rawData.activity.last_id }},加载更多
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-window-item>
|
||||
<v-window-item value="news" v-if="showNews">
|
||||
<div class="news-grid">
|
||||
<v-card class="news-card" v-for="item in postData.news" width="340">
|
||||
<div class="news-cover" @click="toPost(item)">
|
||||
<img :src="item.cover" alt="cover" />
|
||||
</div>
|
||||
<v-card-title>{{ item.title }}</v-card-title>
|
||||
<v-card-actions>
|
||||
<v-btn @click="toPost(item)" class="card-btn">
|
||||
<template v-slot:prepend>
|
||||
<img src="../assets/icons/circle-check.svg" alt="check" />查看
|
||||
</template>
|
||||
</v-btn>
|
||||
<v-card-subtitle>id:{{ item.post_id }}</v-card-subtitle>
|
||||
<v-btn @click="toJson(item)" class="card-dev-btn" v-show="appStore.devMode">
|
||||
<template v-slot:prepend>
|
||||
<img src="../assets/icons/arrow-right.svg" alt="right" />
|
||||
</template>
|
||||
JSON
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</div>
|
||||
<div class="load-news">
|
||||
<v-btn @click="loadMore('news')" :loading="loadingSub">
|
||||
<template v-slot:append>
|
||||
<img src="../assets/icons/arrow-left.svg" alt="right" />
|
||||
</template>
|
||||
已加载:{{ rawData.news.last_id }},加载更多
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-window-item>
|
||||
</v-window>
|
||||
<v-snackbar v-model="snackbar" timeout="1500" :color="snackbarColor">
|
||||
{{ snackbarText }}
|
||||
</v-snackbar>
|
||||
</div>
|
||||
<div v-if="loading">
|
||||
<TLoading :title="loadingTitle" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<v-tabs v-model="tab" align-tabs="start" class="news-tabs">
|
||||
<v-tab value="notice" title="公告" />
|
||||
<v-tab value="activity" title="活动" />
|
||||
<v-tab v-if="showNews" value="news" title="新闻" />
|
||||
<v-spacer />
|
||||
<v-btn v-if="showSwitch" class="switch-btn" @click="switchAnno">
|
||||
<template #prepend>
|
||||
<v-icon>mdi-bullhorn</v-icon>
|
||||
</template>
|
||||
切换游戏内公告
|
||||
</v-btn>
|
||||
<v-text-field
|
||||
v-show="appStore.devMode"
|
||||
v-model="search"
|
||||
append-icon="mdi-magnify"
|
||||
label="搜索"
|
||||
single-line
|
||||
hide-details
|
||||
@click:append="searchPost"
|
||||
@keyup.enter="searchPost"
|
||||
/>
|
||||
</v-tabs>
|
||||
<v-window v-model="tab">
|
||||
<v-window-item value="notice">
|
||||
<div class="news-grid">
|
||||
<v-card v-for="item in postData.notice" :key="item.post_id" class="news-card" width="340">
|
||||
<div class="news-cover" @click="toPost(item)">
|
||||
<img :src="item.cover" alt="cover">
|
||||
</div>
|
||||
<v-card-title>{{ item.title }}</v-card-title>
|
||||
<v-card-actions>
|
||||
<v-btn class="card-btn" @click="toPost(item)">
|
||||
<template #prepend>
|
||||
<img src="../assets/icons/circle-check.svg" alt="check">查看
|
||||
</template>
|
||||
</v-btn>
|
||||
<v-card-subtitle>id:{{ item.post_id }}</v-card-subtitle>
|
||||
<v-btn v-show="appStore.devMode" class="card-dev-btn" @click="toJson(item)">
|
||||
<template #prepend>
|
||||
<img src="../assets/icons/arrow-right.svg" alt="right">
|
||||
</template>
|
||||
JSON
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</div>
|
||||
<div class="load-news">
|
||||
<v-btn :loading="loadingSub" @click="loadMore('notice')">
|
||||
<template #append>
|
||||
<img src="../assets/icons/arrow-left.svg" alt="right">
|
||||
</template>
|
||||
已加载:{{ rawData.notice.last_id }},加载更多
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-window-item>
|
||||
<v-window-item value="activity">
|
||||
<div class="news-grid">
|
||||
<v-card v-for="item in postData.activity" :key="item.post_id" class="news-card" width="340">
|
||||
<div class="news-cover" @click="toPost(item)">
|
||||
<img :src="item.cover" alt="cover">
|
||||
</div>
|
||||
<v-card-title>{{ item.title }}</v-card-title>
|
||||
<v-card-subtitle>{{ item.subtitle }}</v-card-subtitle>
|
||||
<v-card-actions>
|
||||
<v-btn class="card-btn" @click="toPost(item)">
|
||||
<template #prepend>
|
||||
<img src="../assets/icons/circle-check.svg" alt="check">查看
|
||||
</template>
|
||||
</v-btn>
|
||||
<v-card-subtitle>id:{{ item.post_id }}</v-card-subtitle>
|
||||
<div v-show="!appStore.devMode">
|
||||
<v-btn
|
||||
:style="{
|
||||
background: item.status?.colorCss,
|
||||
color: '#faf7e8 !important',
|
||||
}"
|
||||
>
|
||||
{{ item.status?.status }}
|
||||
</v-btn>
|
||||
</div>
|
||||
<v-btn v-show="appStore.devMode" class="card-dev-btn" @click="toJson(item)">
|
||||
<template #prepend>
|
||||
<img src="../assets/icons/arrow-right.svg" alt="right">
|
||||
</template>
|
||||
JSON
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</div>
|
||||
<div class="load-news">
|
||||
<v-btn :loading="loadingSub" @click="loadMore('activity')">
|
||||
<template #append>
|
||||
<img src="../assets/icons/arrow-left.svg" alt="right">
|
||||
</template>
|
||||
已加载:{{ rawData.activity.last_id }},加载更多
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-window-item>
|
||||
<v-window-item v-if="showNews" value="news">
|
||||
<div class="news-grid">
|
||||
<v-card v-for="item in postData.news" :key="item.post_id" class="news-card" width="340">
|
||||
<div class="news-cover" @click="toPost(item)">
|
||||
<img :src="item.cover" alt="cover">
|
||||
</div>
|
||||
<v-card-title>{{ item.title }}</v-card-title>
|
||||
<v-card-actions>
|
||||
<v-btn class="card-btn" @click="toPost(item)">
|
||||
<template #prepend>
|
||||
<img src="../assets/icons/circle-check.svg" alt="check">查看
|
||||
</template>
|
||||
</v-btn>
|
||||
<v-card-subtitle>id:{{ item.post_id }}</v-card-subtitle>
|
||||
<v-btn v-show="appStore.devMode" class="card-dev-btn" @click="toJson(item)">
|
||||
<template #prepend>
|
||||
<img src="../assets/icons/arrow-right.svg" alt="right">
|
||||
</template>
|
||||
JSON
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</div>
|
||||
<div class="load-news">
|
||||
<v-btn :loading="loadingSub" @click="loadMore('news')">
|
||||
<template #append>
|
||||
<img src="../assets/icons/arrow-left.svg" alt="right">
|
||||
</template>
|
||||
已加载:{{ rawData.news.last_id }},加载更多
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-window-item>
|
||||
</v-window>
|
||||
<v-snackbar v-model="snackbar" timeout="1500" :color="snackbarColor">
|
||||
{{ snackbarText }}
|
||||
</v-snackbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@@ -177,170 +178,167 @@ const search = ref("" as string);
|
||||
// 数据
|
||||
const tab = ref("" as string);
|
||||
const postData = ref({
|
||||
notice: [] as NewsCard[],
|
||||
activity: [] as NewsCard[],
|
||||
news: [] as NewsCard[],
|
||||
notice: [] as NewsCard[],
|
||||
activity: [] as NewsCard[],
|
||||
news: [] as NewsCard[],
|
||||
});
|
||||
const rawData = ref({
|
||||
notice: {
|
||||
is_last: false,
|
||||
last_id: 0,
|
||||
},
|
||||
activity: {
|
||||
is_last: false,
|
||||
last_id: 0,
|
||||
},
|
||||
news: {
|
||||
is_last: false,
|
||||
last_id: 0,
|
||||
},
|
||||
notice: {
|
||||
is_last: false,
|
||||
last_id: 0,
|
||||
},
|
||||
activity: {
|
||||
is_last: false,
|
||||
last_id: 0,
|
||||
},
|
||||
news: {
|
||||
is_last: false,
|
||||
last_id: 0,
|
||||
},
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
loadingTitle.value = "正在获取公告数据...";
|
||||
const noticeData = await MysOper.News.get.notice(gid);
|
||||
rawData.value.notice.is_last = noticeData.is_last;
|
||||
rawData.value.notice.last_id = noticeData.list.length;
|
||||
loadingTitle.value = "正在获取活动数据...";
|
||||
const activityData = await MysOper.News.get.activity(gid);
|
||||
rawData.value.activity.is_last = activityData.is_last;
|
||||
rawData.value.activity.last_id = activityData.list.length;
|
||||
if (showNews.value) {
|
||||
loadingTitle.value = "正在获取新闻数据...";
|
||||
const newsData = await MysOper.News.get.news(gid);
|
||||
rawData.value.news.is_last = newsData.is_last;
|
||||
rawData.value.news.last_id = newsData.list.length;
|
||||
postData.value = {
|
||||
notice: MysOper.News.card.notice(noticeData),
|
||||
activity: MysOper.News.card.activity(activityData),
|
||||
news: MysOper.News.card.news(newsData),
|
||||
};
|
||||
} else {
|
||||
postData.value = {
|
||||
notice: MysOper.News.card.notice(noticeData),
|
||||
activity: MysOper.News.card.activity(activityData),
|
||||
news: [],
|
||||
};
|
||||
}
|
||||
tab.value = "notice";
|
||||
loading.value = false;
|
||||
loadingTitle.value = "正在获取公告数据...";
|
||||
const noticeData = await MysOper.News.get.notice(gid);
|
||||
rawData.value.notice.is_last = noticeData.is_last;
|
||||
rawData.value.notice.last_id = noticeData.list.length;
|
||||
loadingTitle.value = "正在获取活动数据...";
|
||||
const activityData = await MysOper.News.get.activity(gid);
|
||||
rawData.value.activity.is_last = activityData.is_last;
|
||||
rawData.value.activity.last_id = activityData.list.length;
|
||||
if (showNews.value) {
|
||||
loadingTitle.value = "正在获取新闻数据...";
|
||||
const newsData = await MysOper.News.get.news(gid);
|
||||
rawData.value.news.is_last = newsData.is_last;
|
||||
rawData.value.news.last_id = newsData.list.length;
|
||||
postData.value = {
|
||||
notice: MysOper.News.card.notice(noticeData),
|
||||
activity: MysOper.News.card.activity(activityData),
|
||||
news: MysOper.News.card.news(newsData),
|
||||
};
|
||||
} else {
|
||||
postData.value = {
|
||||
notice: MysOper.News.card.notice(noticeData),
|
||||
activity: MysOper.News.card.activity(activityData),
|
||||
news: [],
|
||||
};
|
||||
}
|
||||
tab.value = "notice";
|
||||
loading.value = false;
|
||||
});
|
||||
|
||||
function switchAnno() {
|
||||
router.push("/announcements");
|
||||
async function switchAnno () {
|
||||
await router.push("/announcements");
|
||||
}
|
||||
|
||||
// 加载更多
|
||||
async function loadMore(data: string) {
|
||||
loadingSub.value = true;
|
||||
switch (data) {
|
||||
case "notice":
|
||||
if (rawData.value.notice.is_last) {
|
||||
snackbarText.value = "已经是最后一页了";
|
||||
snackbarColor.value = "#35acce";
|
||||
snackbar.value = true;
|
||||
loadingSub.value = false;
|
||||
return;
|
||||
}
|
||||
const getNotice = await MysOper.News.get.notice(gid, 20, rawData.value.notice.last_id);
|
||||
rawData.value.notice.last_id = rawData.value.notice.last_id + getNotice.list.length;
|
||||
rawData.value.notice.is_last = getNotice.is_last;
|
||||
const noticeCard = MysOper.News.card.notice(getNotice);
|
||||
postData.value.notice = postData.value.notice.concat(noticeCard);
|
||||
loadingSub.value = false;
|
||||
break;
|
||||
case "activity":
|
||||
if (rawData.value.activity.is_last) {
|
||||
snackbarText.value = "已经是最后一页了";
|
||||
snackbarColor.value = "#35acce";
|
||||
snackbar.value = true;
|
||||
loadingSub.value = false;
|
||||
return;
|
||||
}
|
||||
const getActivity = await MysOper.News.get.activity(gid, 20, rawData.value.activity.last_id);
|
||||
rawData.value.activity.last_id = rawData.value.activity.last_id + getActivity.list.length;
|
||||
rawData.value.activity.is_last = getActivity.is_last;
|
||||
const activityCard = MysOper.News.card.activity(getActivity);
|
||||
postData.value.activity = postData.value.activity.concat(activityCard);
|
||||
loadingSub.value = false;
|
||||
break;
|
||||
case "news":
|
||||
if (rawData.value.news.is_last) {
|
||||
snackbarText.value = "已经是最后一页了";
|
||||
snackbarColor.value = "#35acce";
|
||||
snackbar.value = true;
|
||||
loadingSub.value = false;
|
||||
return;
|
||||
}
|
||||
const getNews = await MysOper.News.get.news(gid, 20, rawData.value.news.last_id);
|
||||
rawData.value.news.last_id = rawData.value.news.last_id + getNews.list.length;
|
||||
rawData.value.news.is_last = getNews.is_last;
|
||||
const newsCard = MysOper.News.card.news(getNews);
|
||||
postData.value.news = postData.value.news.concat(newsCard);
|
||||
loadingSub.value = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
async function loadMore (data: string) {
|
||||
loadingSub.value = true;
|
||||
switch (data) {
|
||||
case "notice":
|
||||
if (rawData.value.notice.is_last) {
|
||||
snackbarText.value = "已经是最后一页了";
|
||||
snackbarColor.value = "#35acce";
|
||||
snackbar.value = true;
|
||||
loadingSub.value = false;
|
||||
return;
|
||||
}
|
||||
const getNotice = await MysOper.News.get.notice(gid, 20, rawData.value.notice.last_id);
|
||||
rawData.value.notice.last_id = rawData.value.notice.last_id + getNotice.list.length;
|
||||
rawData.value.notice.is_last = getNotice.is_last;
|
||||
const noticeCard = MysOper.News.card.notice(getNotice);
|
||||
postData.value.notice = postData.value.notice.concat(noticeCard);
|
||||
loadingSub.value = false;
|
||||
break;
|
||||
case "activity":
|
||||
if (rawData.value.activity.is_last) {
|
||||
snackbarText.value = "已经是最后一页了";
|
||||
snackbarColor.value = "#35acce";
|
||||
snackbar.value = true;
|
||||
loadingSub.value = false;
|
||||
return;
|
||||
}
|
||||
const getActivity = await MysOper.News.get.activity(gid, 20, rawData.value.activity.last_id);
|
||||
rawData.value.activity.last_id = rawData.value.activity.last_id + getActivity.list.length;
|
||||
rawData.value.activity.is_last = getActivity.is_last;
|
||||
const activityCard = MysOper.News.card.activity(getActivity);
|
||||
postData.value.activity = postData.value.activity.concat(activityCard);
|
||||
loadingSub.value = false;
|
||||
break;
|
||||
case "news":
|
||||
if (rawData.value.news.is_last) {
|
||||
snackbarText.value = "已经是最后一页了";
|
||||
snackbarColor.value = "#35acce";
|
||||
snackbar.value = true;
|
||||
loadingSub.value = false;
|
||||
return;
|
||||
}
|
||||
const getNews = await MysOper.News.get.news(gid, 20, rawData.value.news.last_id);
|
||||
rawData.value.news.last_id = rawData.value.news.last_id + getNews.list.length;
|
||||
rawData.value.news.is_last = getNews.is_last;
|
||||
const newsCard = MysOper.News.card.news(getNews);
|
||||
postData.value.news = postData.value.news.concat(newsCard);
|
||||
loadingSub.value = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async function toPost(item: NewsCard | string) {
|
||||
if (typeof item === "string") {
|
||||
const path = router.resolve({
|
||||
name: "帖子详情",
|
||||
params: {
|
||||
post_id: item,
|
||||
},
|
||||
}).href;
|
||||
createTGWindow(path, "帖子-Dev", item, 960, 720, false);
|
||||
} else {
|
||||
const path = router.resolve({
|
||||
name: "帖子详情",
|
||||
params: {
|
||||
post_id: item.post_id.toString(),
|
||||
},
|
||||
}).href;
|
||||
createTGWindow(path, "帖子", item.title, 960, 720, false);
|
||||
}
|
||||
async function toPost (item: NewsCard | string) {
|
||||
if (typeof item === "string") {
|
||||
const path = router.resolve({
|
||||
name: "帖子详情",
|
||||
params: {
|
||||
post_id: item,
|
||||
},
|
||||
}).href;
|
||||
createTGWindow(path, "帖子-Dev", item, 960, 720, false);
|
||||
} else {
|
||||
const path = router.resolve({
|
||||
name: "帖子详情",
|
||||
params: {
|
||||
post_id: item.post_id.toString(),
|
||||
},
|
||||
}).href;
|
||||
createTGWindow(path, "帖子", item.title, 960, 720, false);
|
||||
}
|
||||
}
|
||||
async function toJson(item: NewsCard | string) {
|
||||
if (typeof item === "string") {
|
||||
const path = router.resolve({
|
||||
name: "帖子详情(JSON)",
|
||||
params: {
|
||||
post_id: item,
|
||||
},
|
||||
}).href;
|
||||
createTGWindow(path, "帖子-JSON-Dev", `${item}-JSON`, 960, 720, false);
|
||||
return;
|
||||
} else {
|
||||
const path = router.resolve({
|
||||
name: "帖子详情(JSON)",
|
||||
params: {
|
||||
post_id: item.post_id.toString(),
|
||||
},
|
||||
}).href;
|
||||
createTGWindow(path, "帖子-JSON", `${item.title}-JSON`, 960, 720, false);
|
||||
return;
|
||||
}
|
||||
async function toJson (item: NewsCard | string) {
|
||||
if (typeof item === "string") {
|
||||
const path = router.resolve({
|
||||
name: "帖子详情(JSON)",
|
||||
params: {
|
||||
post_id: item,
|
||||
},
|
||||
}).href;
|
||||
createTGWindow(path, "帖子-JSON-Dev", `${item}-JSON`, 960, 720, false);
|
||||
} else {
|
||||
const path = router.resolve({
|
||||
name: "帖子详情(JSON)",
|
||||
params: {
|
||||
post_id: item.post_id.toString(),
|
||||
},
|
||||
}).href;
|
||||
createTGWindow(path, "帖子-JSON", `${item.title}-JSON`, 960, 720, false);
|
||||
}
|
||||
}
|
||||
|
||||
async function searchPost() {
|
||||
if (search.value === "") {
|
||||
snackbarText.value = "请输入搜索内容";
|
||||
snackbarColor.value = "error";
|
||||
snackbar.value = true;
|
||||
return;
|
||||
}
|
||||
if (!isNaN(Number(search.value))) {
|
||||
await toPost(search.value);
|
||||
await toJson(search.value);
|
||||
} else {
|
||||
snackbarText.value = "请输入搜索内容";
|
||||
snackbarColor.value = "error";
|
||||
snackbar.value = true;
|
||||
return;
|
||||
}
|
||||
async function searchPost () {
|
||||
if (search.value === "") {
|
||||
snackbarText.value = "请输入搜索内容";
|
||||
snackbarColor.value = "error";
|
||||
snackbar.value = true;
|
||||
return;
|
||||
}
|
||||
if (!isNaN(Number(search.value))) {
|
||||
await toPost(search.value);
|
||||
await toJson(search.value);
|
||||
} else {
|
||||
snackbarText.value = "请输入搜索内容";
|
||||
snackbarColor.value = "error";
|
||||
snackbar.value = true;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -380,6 +378,7 @@ async function searchPost() {
|
||||
height: 150px;
|
||||
transition: all 0.3s linear;
|
||||
}
|
||||
|
||||
/* switch */
|
||||
.switch-btn {
|
||||
font-family: Genshin, serif;
|
||||
@@ -389,6 +388,7 @@ async function searchPost() {
|
||||
margin-top: 5px;
|
||||
color: #546d8b;
|
||||
}
|
||||
|
||||
/* load more */
|
||||
.load-news {
|
||||
font-family: Genshin, serif;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div v-if="loading">
|
||||
<t-loading :empty="loadingEmpty" :title="loadingTitle" />
|
||||
</div>
|
||||
<div v-else class="dev-json">
|
||||
<json-viewer :value="jsonData" copyable boxed />
|
||||
</div>
|
||||
<div v-if="loading">
|
||||
<TLoading :empty="loadingEmpty" :title="loadingTitle" />
|
||||
</div>
|
||||
<div v-else class="dev-json">
|
||||
<JsonViewer :value="jsonData" copyable boxed />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
@@ -27,18 +27,18 @@ const post_id = Number(useRoute().params.post_id);
|
||||
let jsonData = reactive({});
|
||||
|
||||
onMounted(async () => {
|
||||
await appWindow.show();
|
||||
// 检查数据
|
||||
if (!post_id) {
|
||||
loadingEmpty.value = true;
|
||||
loadingTitle.value = "未找到数据";
|
||||
return;
|
||||
}
|
||||
// 获取数据
|
||||
loadingTitle.value = "正在获取数据...";
|
||||
jsonData = await MysOper.Post.get(post_id);
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
}, 200);
|
||||
await appWindow.show();
|
||||
// 检查数据
|
||||
if (!post_id) {
|
||||
loadingEmpty.value = true;
|
||||
loadingTitle.value = "未找到数据";
|
||||
return;
|
||||
}
|
||||
// 获取数据
|
||||
loadingTitle.value = "正在获取数据...";
|
||||
jsonData = await MysOper.Post.get(post_id);
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
}, 200);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<template>
|
||||
<div v-if="loading">
|
||||
<t-loading :empty="loadingEmpty" :title="loadingTitle" />
|
||||
</div>
|
||||
<div v-else v-html="postHtml" class="mys-post-body" />
|
||||
<div v-if="loading">
|
||||
<TLoading :empty="loadingEmpty" :title="loadingTitle" />
|
||||
</div>
|
||||
<div v-else class="mys-post-body" v-html="postHtml" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
// vue
|
||||
@@ -24,31 +25,31 @@ const post_id = Number(useRoute().params.post_id);
|
||||
const postHtml = ref("");
|
||||
|
||||
onMounted(async () => {
|
||||
await appWindow.show();
|
||||
// 检查数据
|
||||
if (!post_id) {
|
||||
loadingEmpty.value = true;
|
||||
loadingTitle.value = "未找到数据";
|
||||
await appWindow.setTitle("未找到数据");
|
||||
return;
|
||||
}
|
||||
// 获取数据
|
||||
loadingTitle.value = "正在获取数据...";
|
||||
try {
|
||||
const postData = await MysOper.Post.get(post_id);
|
||||
loadingTitle.value = "正在渲染数据...";
|
||||
postHtml.value = MysOper.Post.parser(postData);
|
||||
await appWindow.setTitle(postData.post.subject);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
loadingEmpty.value = true;
|
||||
loadingTitle.value = "帖子不存在或解析失败";
|
||||
await appWindow.setTitle("帖子不存在或解析失败");
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
}, 200);
|
||||
await appWindow.show();
|
||||
// 检查数据
|
||||
if (!post_id) {
|
||||
loadingEmpty.value = true;
|
||||
loadingTitle.value = "未找到数据";
|
||||
await appWindow.setTitle("未找到数据");
|
||||
return;
|
||||
}
|
||||
// 获取数据
|
||||
loadingTitle.value = "正在获取数据...";
|
||||
try {
|
||||
const postData = await MysOper.Post.get(post_id);
|
||||
loadingTitle.value = "正在渲染数据...";
|
||||
postHtml.value = MysOper.Post.parser(postData);
|
||||
await appWindow.setTitle(postData.post.subject);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
loadingEmpty.value = true;
|
||||
loadingTitle.value = "帖子不存在或解析失败";
|
||||
await appWindow.setTitle("帖子不存在或解析失败");
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
}, 200);
|
||||
});
|
||||
</script>
|
||||
<style lang="css" scoped src="../assets/css/post-parser.css"></style>
|
||||
|
||||
18
src/vite-env.d.ts
vendored
18
src/vite-env.d.ts
vendored
@@ -1,12 +1,18 @@
|
||||
/**
|
||||
* @file vite-env.d.ts
|
||||
* @description vite-env.d.ts
|
||||
* @author BTMuli<bt-muli@outlook.com>
|
||||
* @since Alpha v0.1.2
|
||||
*/
|
||||
|
||||
declare module "*.vue" {
|
||||
import type { DefineComponent } from "vue";
|
||||
const component: DefineComponent<{}, {}, any>;
|
||||
export default component;
|
||||
import type { DefineComponent } from "vue";
|
||||
const component: DefineComponent<object, object, any>;
|
||||
export default component;
|
||||
}
|
||||
|
||||
declare module "vue-json-viewer" {
|
||||
import type { DefineComponent } from "vue";
|
||||
const component: DefineComponent<{}, {}, any>;
|
||||
export default component;
|
||||
import type { DefineComponent } from "vue";
|
||||
const component: DefineComponent<object, object, any>;
|
||||
export default component;
|
||||
}
|
||||
|
||||
@@ -5,36 +5,36 @@ import vuetify from "vite-plugin-vuetify";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue(), vuetify()],
|
||||
plugins: [vue(), vuetify()],
|
||||
|
||||
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
||||
// prevent vite from obscuring rust errors
|
||||
clearScreen: false,
|
||||
// tauri expects a fixed port, fail if that port is not available
|
||||
server: {
|
||||
port: 3000,
|
||||
strictPort: true,
|
||||
},
|
||||
// to make use of `TAURI_DEBUG` and other env variables
|
||||
// https://tauri.studio/v1/api/config#buildconfig.beforedevcommand
|
||||
envPrefix: ["VITE_", "TAURI_"],
|
||||
build: {
|
||||
// Tauri supports es2021
|
||||
target: process.env.TAURI_PLATFORM == "windows" ? "chrome105" : "safari13",
|
||||
// don't minify for debug builds
|
||||
minify: !process.env.TAURI_DEBUG ? "esbuild" : false,
|
||||
// produce sourcemaps for debug builds
|
||||
sourcemap: !!process.env.TAURI_DEBUG,
|
||||
// rollup options
|
||||
rollupOptions: {
|
||||
// chunking
|
||||
output: {
|
||||
manualChunks(id) {
|
||||
if (id.includes("node_modules")) {
|
||||
return id.toString().split("node_modules/")[1].split("/")[0].toString();
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
||||
// prevent vite from obscuring rust errors
|
||||
clearScreen: false,
|
||||
// tauri expects a fixed port, fail if that port is not available
|
||||
server: {
|
||||
port: 3000,
|
||||
strictPort: true,
|
||||
},
|
||||
// to make use of `TAURI_DEBUG` and other env variables
|
||||
// https://tauri.studio/v1/api/config#buildconfig.beforedevcommand
|
||||
envPrefix: ["VITE_", "TAURI_"],
|
||||
build: {
|
||||
// Tauri supports es2021
|
||||
target: process.env.TAURI_PLATFORM === "windows" ? "chrome105" : "safari13",
|
||||
// don't minify for debug builds
|
||||
minify: !process.env.TAURI_DEBUG ? "esbuild" : false,
|
||||
// produce sourcemaps for debug builds
|
||||
sourcemap: !!process.env.TAURI_DEBUG,
|
||||
// rollup options
|
||||
rollupOptions: {
|
||||
// chunking
|
||||
output: {
|
||||
manualChunks (id) {
|
||||
if (id.includes("node_modules")) {
|
||||
return id.toString().split("node_modules/")[1].split("/")[0].toString();
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user