♻️ 样式调整,慢慢改吧

This commit is contained in:
BTMuli
2023-09-01 16:55:43 +08:00
parent 2c129351c1
commit 343c83b185
17 changed files with 119 additions and 86 deletions

View File

@@ -0,0 +1,222 @@
<template>
<div class="calendar-box">
<div class="calendar-title">
<div class="calendar-title-left">
<v-icon size="small"> mdi-calendar-clock </v-icon>
<span>今日素材</span>
<span>{{ dateNow }}</span>
<v-btn variant="outlined" class="calendar-title-btn" @click="share">
<template #prepend>
<v-icon> mdi-share-variant </v-icon>
</template>
<span>分享</span>
</v-btn>
</div>
<div class="calendar-title-right">
<v-btn
v-for="text of btnText"
:key="text.week"
:class="btnNow === text.week ? 'calendar-btn-selected' : 'calendar-btn'"
@click="getContents(text.week)"
>
{{ text.text }}
</v-btn>
</div>
</div>
<TSubLine>角色突破</TSubLine>
<div class="calendar-grid">
<div v-for="item in characterCards" :key="item.id" @click="selectAvatar(item)">
<TibCalendarItem
:data="<TGApp.App.Calendar.Item>item"
:model="'avatar'"
:clickable="true"
/>
</div>
</div>
<TSubLine>武器突破</TSubLine>
<div class="calendar-grid">
<div v-for="item in weaponCards" :key="item.id" @click="selectWeapon(item)">
<TibCalendarItem
:data="<TGApp.App.Calendar.Item>item"
:model="'weapon'"
:clickable="true"
/>
</div>
</div>
<ToCalendar v-model="showItem" :data-type="selectedType" :data-val="selectedItem" />
</div>
</template>
<script lang="ts" setup>
// vue
import { computed, onMounted, ref } from "vue";
import TSubLine from "../main/t-subline.vue";
import ToCalendar from "../overlay/to-calendar.vue";
import TibCalendarItem from "../itembox/tib-calendar-item.vue";
// data
import { AppCalendarData } from "../../data";
// utils
import { generateShareImg } from "../../utils/TGShare";
// loading
const loading = ref<boolean>(true);
// data
const calendarData = computed<TGApp.App.Calendar.Item[]>(() => AppCalendarData);
const weekNow = ref<number>(0);
const btnNow = ref<number>(0);
const dateNow = ref<string>("");
// calendar
const calendarNow = ref<TGApp.App.Calendar.Item[]>([]);
const characterCards = ref<TGApp.App.Calendar.Item[]>([]);
const weaponCards = ref<TGApp.App.Calendar.Item[]>([]);
// calendar item
const showItem = ref<boolean>(false);
const selectedItem = ref<TGApp.App.Calendar.Item>(<TGApp.App.Calendar.Item>{});
const selectedType = ref<"avatar" | "weapon">("avatar");
const btnText = [
{
week: 7,
text: "周日",
},
{
week: 1,
text: "周一",
},
{
week: 2,
text: "周二",
},
{
week: 3,
text: "周三",
},
{
week: 4,
text: "周四",
},
{
week: 5,
text: "周五",
},
{
week: 6,
text: "周六",
},
];
// expose
defineExpose({
name: "素材日历",
loading,
});
onMounted(() => {
const dayNow = new Date().getDay() === 0 ? 7 : new Date().getDay();
const week = <{ week: number; text: string }>btnText.find((item) => item.week === dayNow);
dateNow.value =
new Date()
.toLocaleDateString("zh-CN", {
year: "numeric",
month: "2-digit",
day: "2-digit",
})
.replace(/\//g, "-") + ` ${week.text}`;
weekNow.value = dayNow;
btnNow.value = dayNow;
calendarNow.value = getCalendar(dayNow);
characterCards.value = calendarNow.value.filter((item) => item.itemType === "character");
weaponCards.value = calendarNow.value.filter((item) => item.itemType === "weapon");
loading.value = false;
});
// 获取当前日历
function getCalendar(day: number): TGApp.App.Calendar.Item[] {
return calendarData.value.filter((item) => item.dropDays.includes(day));
}
function selectAvatar(item: TGApp.App.Calendar.Item): void {
selectedItem.value = item;
selectedType.value = "avatar";
showItem.value = true;
}
function selectWeapon(item: TGApp.App.Calendar.Item): void {
selectedItem.value = item;
selectedType.value = "weapon";
showItem.value = true;
}
function getContents(day: number): void {
btnNow.value = day;
calendarNow.value = getCalendar(day);
characterCards.value = calendarNow.value.filter((item) => item.itemType === "character");
weaponCards.value = calendarNow.value.filter((item) => item.itemType === "weapon");
}
async function share(): Promise<void> {
// todo 唤起外部 loading
const div = <HTMLElement>document.querySelector(".calendar-box");
const title = `【今日素材】${dateNow.value}-${btnNow.value}`;
await generateShareImg(title, div);
}
</script>
<style lang="css" scoped>
.calendar-box {
padding: 10px;
border: 1px solid var(--common-shadow-2);
border-radius: 5px;
background: var(--common-shadow-t-2);
}
.calendar-title {
display: flex;
align-items: center;
justify-content: start;
padding-bottom: 5px;
color: var(--common-text-title);
column-gap: 2rem;
font-family: var(--font-title);
font-size: 20px;
}
.calendar-title-left {
display: flex;
align-items: center;
justify-content: start;
column-gap: 10px;
}
.calendar-title-btn {
border-radius: 5px;
color: var(--common-text-content);
cursor: pointer;
}
.calendar-title-right {
display: flex;
align-items: center;
justify-content: start;
column-gap: 15px;
}
.calendar-btn {
border-radius: 5px;
background: var(--common-bg-1);
color: var(--common-bgt-1);
}
.calendar-btn-selected {
border-radius: 5px;
background: var(--common-btn-bg-1);
color: var(--common-btn-bgt-1);
}
.calendar-grid {
display: grid;
grid-gap: 10px;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
}
</style>

View File

@@ -0,0 +1,318 @@
<template>
<div class="pool-box">
<div class="pool-title">
<img src="../../assets/icons/icon-wish.svg" alt="wish" />
限时祈愿
</div>
<div v-if="!loading" class="pool-grid">
<div v-for="pool in poolCards" :key="pool.postId" class="pool-card">
<div class="pool-cover" @click="toPost(pool)">
<img :src="pool.cover" alt="cover" />
</div>
<div class="pool-bottom">
<div class="pool-character">
<div class="pool-icon-grid">
<div
v-for="character in pool.characters"
:key="character.url"
class="pool-icon"
@click="toOuter(character.url, pool.title)"
>
<img :src="character.icon" alt="character" />
</div>
</div>
</div>
<div class="pool-time">
<div>
<v-icon>mdi-calendar-clock</v-icon>
{{ pool.time.start }}~{{ pool.time.end }}
</div>
<v-progress-linear :model-value="poolTimePass[pool.postId]" :rounded="true">
</v-progress-linear>
<div v-if="poolTimeGet[pool.postId] === '已结束'">
{{ poolTimeGet[pool.postId] }}
</div>
<div v-else>
<span>剩余时间</span>
<span>
{{ poolTimeGet[pool.postId] }}
</span>
</div>
</div>
</div>
</div>
</div>
<v-snackbar v-model="showBar" :color="barColor" timeout="1000">
{{ barText }}
</v-snackbar>
</div>
</template>
<script lang="ts" setup>
// vue
import { ref, onMounted, onUnmounted } from "vue";
import { useRouter } from "vue-router";
// store
import { useHomeStore } from "../../store/modules/home";
// utils
import { createTGWindow } from "../../utils/TGWindow";
import { stamp2LastTime } from "../../utils/toolFunc";
// plugins
import Mys from "../../plugins/Mys";
// vue
const router = useRouter();
// store
const homeStore = useHomeStore();
// loading
const loading = ref<boolean>(true);
// snackbar
const showBar = ref<boolean>(false);
const barText = ref<string>("");
const barColor = ref<string>("error");
// data
const poolCards = ref<TGApp.Plugins.Mys.Gacha.RenderCard[]>([]);
const poolTimeGet = ref<Record<number, string>>({});
const poolTimePass = ref<Record<number, number>>({});
const timer = ref<Record<number, any>>({});
// expose
defineExpose({
name: "限时祈愿",
loading,
});
function poolLastInterval(postId: number): TGApp.Plugins.Mys.Gacha.RenderCard | undefined {
const pool = poolCards.value.find((pool) => pool.postId === postId);
if (!pool) return;
if (poolTimeGet.value[postId] === "未开始") {
const isStart = pool.time.startStamp - Date.now();
if (isStart > 0) return;
poolTimeGet.value[postId] = stamp2LastTime(pool.time.endStamp - Date.now());
poolTimePass.value[postId] = pool.time.endStamp - Date.now();
} else {
const isEnd = pool.time.endStamp - Date.now();
poolTimeGet.value[postId] = stamp2LastTime(isEnd);
poolTimePass.value[postId] =
((pool.time.endStamp - Date.now()) / (pool.time.endStamp - pool.time.startStamp)) * 100;
if (isEnd >= 0) return;
clearInterval(timer.value[postId]);
timer.value[postId] = null;
poolTimePass.value[postId] = 100;
poolTimeGet.value[postId] = "已结束";
}
return pool;
}
onMounted(async () => {
const gachaData = await Mys.Gacha.get();
if (!gachaData) {
console.error("获取限时祈愿数据失败");
return;
}
console.log("获取限时祈愿数据成功");
console.info(gachaData);
if (!checkCover(gachaData)) {
poolCards.value = await Mys.Gacha.card(gachaData);
const coverData: Record<number, string> = {};
poolCards.value.map((pool) => {
coverData[pool.postId] = pool.cover;
return pool;
});
homeStore.poolCover = coverData;
} else {
poolCards.value = await Mys.Gacha.card(gachaData, homeStore.poolCover);
}
poolCards.value.map((pool) => {
poolTimeGet.value[pool.postId] = stamp2LastTime(pool.time.endStamp - Date.now());
poolTimePass.value[pool.postId] = pool.time.endStamp - Date.now();
if (poolTimePass.value[pool.postId] <= 0) {
poolTimeGet.value[pool.postId] = "已结束";
poolTimePass.value[pool.postId] = 100;
} else if (pool.time.startStamp - Date.now() > 0) {
poolTimeGet.value[pool.postId] = "未开始";
poolTimePass.value[pool.postId] = 100;
}
timer.value[pool.postId] = setInterval(() => {
poolLastInterval(pool.postId);
}, 1000);
return pool;
});
loading.value = false;
});
// 检测新卡池
function checkCover(data: TGApp.Plugins.Mys.Gacha.Data[]): boolean {
// 如果没有缓存
if (!homeStore.poolCover || Object.keys(homeStore.poolCover).length === 0) {
return false;
}
// 获取缓存
const cover = homeStore.poolCover satisfies Record<number, string>;
if (cover === undefined) {
return false;
}
return data.every((item) => {
const postId = item.activity_url.split("/").pop();
if (!postId || isNaN(Number(postId))) {
return false;
}
if (!Object.keys(cover).includes(postId)) {
return false;
} else {
const coverUrl = Object.keys(cover).find((key) => key === postId);
return coverUrl !== "/source/UI/empty.webp";
}
});
}
async function toOuter(url: string, title: string): Promise<void> {
if (!url) {
barText.value = "链接为空!";
barColor.value = "error";
showBar.value = true;
return;
}
createTGWindow(url, "祈愿", title, 1200, 800, true, true);
}
function toPost(pool: TGApp.Plugins.Mys.Gacha.RenderCard): void {
const path = router.resolve({
name: "帖子详情",
params: {
post_id: pool.postId.toString(),
},
}).href;
createTGWindow(path, "限时祈愿", pool.title, 960, 720, false, false);
}
onUnmounted(() => {
Object.keys(timer.value).forEach((key) => {
clearInterval(timer.value[Number(key)]);
});
});
</script>
<style lang="css" scoped>
.pool-box {
display: flex;
flex-direction: column;
padding: 10px;
border: 1px solid var(--common-shadow-2);
border-radius: 5px;
gap: 10px;
}
.pool-title {
display: flex;
font-family: var(--font-title);
font-size: 20px;
}
.pool-title img {
width: 25px;
height: 25px;
border-radius: 50%;
margin-right: 10px;
background: var(--common-shadow-4);
transform: translate(0, 2px);
}
.pool-grid {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.pool-card {
position: relative;
overflow: hidden;
width: 50%;
border-radius: 5px;
box-shadow: 0 0 5px var(--common-shadow-4);
}
.pool-cover {
display: flex;
overflow: hidden;
width: 100%;
height: auto;
align-items: center;
justify-content: center;
border-radius: 5px;
}
.pool-cover img {
width: 100%;
border-radius: 5px;
transition: all 0.5s;
}
.pool-cover :hover {
cursor: pointer;
transform: scale(1.1);
transition: all 0.5s;
}
.pool-bottom {
position: absolute;
bottom: 0;
left: 0;
display: flex;
width: 100%;
align-items: center;
justify-content: space-between;
backdrop-filter: blur(20px);
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
}
.pool-character {
display: flex;
width: auto;
height: 60px;
margin: 10px;
}
.pool-icon-grid {
display: grid;
grid-column-gap: 10px;
grid-template-columns: repeat(4, 60px);
}
.pool-icon {
width: 60px;
height: 60px;
border: 1px solid var(--tgc-white-1);
border-radius: 8px;
box-shadow: 0 0 5px rgb(0 0 0/20%);
transition: all ease-in-out 0.3s;
}
.pool-icon:hover {
transform: scale(1.1);
transition: all ease-in-out 0.3s;
}
.pool-icon img {
width: 100%;
height: 100%;
border-radius: 8px;
cursor: pointer;
}
.pool-time {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
margin-right: 10px;
color: var(--tgc-text-1);
font-size: 12px;
gap: 10px;
text-align: left;
}
</style>

View File

@@ -0,0 +1,196 @@
<template>
<div class="position-box">
<div class="position-title">
<img src="../../assets/icons/board.svg" alt="act" />
<span>近期活动</span>
</div>
<div v-if="!loading" class="position-grid">
<v-card v-for="card in positionCards" :key="card.postId" class="position-card">
<v-list class="position-list">
<v-list-item :title="card.title" :subtitle="card.abstract">
<template #prepend>
<v-avatar rounded="0" @click="toPost(card)">
<v-img :src="card.icon" class="position-icon" />
</v-avatar>
</template>
<template #append>
<v-btn variant="outlined" @click="toPost(card)"> 查看 </v-btn>
</template>
</v-list-item>
</v-list>
<v-divider class="border-opacity-75" />
<v-card-text>
<div class="position-card-text">
<v-icon>mdi-calendar-clock</v-icon>
<span>{{ card.time.start }} ~ {{ card.time.end }}</span>
</div>
<div class="position-card-text">
<v-icon>mdi-clock-outline</v-icon>
<span>剩余时间</span>
<!-- 玉鈫蓝 -->
<span v-if="positionTimeGet[card.postId] !== '已结束'" style="color: #126e82">{{
positionTimeGet[card.postId]
}}</span>
<!-- 粉红 -->
<span v-if="positionTimeGet[card.postId] === '已结束'" style="color: #f2b9b2"
>已结束</span
>
</div>
</v-card-text>
</v-card>
</div>
</div>
</template>
<script lang="ts" setup>
// vue
import { ref, onMounted, onUnmounted } from "vue";
import { useRouter } from "vue-router";
// utils
import { createTGWindow } from "../../utils/TGWindow";
import { stamp2LastTime } from "../../utils/toolFunc";
// plugins
import Mys from "../../plugins/Mys";
// vue
const router = useRouter();
// loading
const loading = ref<boolean>(true);
// data
const positionCards = ref<TGApp.Plugins.Mys.Position.RenderCard[]>([]);
const positionTimeGet = ref<Record<number, string>>({}); // 剩余时间/已结束/未知
const positionTimeEnd = ref<Record<number, number>>({}); // 结束时间戳
const positionTimer = ref<Record<number, any>>({}); // 定时器
// expose
defineExpose({
name: "近期活动",
loading,
});
function positionLastInterval(postId: number): void {
const timeGet = positionTimeGet.value[postId];
if (timeGet === "未知" || timeGet === "已结束") {
clearInterval(positionTimer.value[postId]);
positionTimer.value[postId] = null;
return;
}
const timeLast = positionTimeEnd.value[postId] - Date.now();
if (timeLast <= 0) {
positionTimeGet.value[postId] = "已结束";
} else {
positionTimeGet.value[postId] = stamp2LastTime(timeLast);
}
}
onMounted(async () => {
const positionData = await Mys.Position.get();
if (!positionData) {
console.error("获取近期活动失败");
return;
}
positionCards.value = Mys.Position.card(positionData);
positionCards.value.forEach((card) => {
if (card.time.endStamp === 0) {
positionTimeGet.value[card.postId] = "未知";
} else {
positionTimeGet.value[card.postId] = stamp2LastTime(card.time.endStamp - Date.now());
}
positionTimeEnd.value[card.postId] = card.time.endStamp;
positionTimer.value[card.postId] = setInterval(() => {
positionLastInterval(card.postId);
}, 1000);
});
loading.value = false;
});
async function toPost(card: TGApp.Plugins.Mys.Position.RenderCard): Promise<void> {
// 获取路由路径
const path = router.resolve({
name: "帖子详情",
params: {
post_id: card.postId,
},
}).href;
// 打开新窗口
createTGWindow(path, "近期活动", card.title, 960, 720, false, false);
}
onUnmounted(() => {
Object.keys(positionTimer.value).forEach((key) => {
clearInterval(positionTimer.value[Number(key)]);
});
});
</script>
<style lang="css" scoped>
.position-box {
padding: 10px;
border: 1px solid var(--common-shadow-2);
border-radius: 5px;
}
.position-title {
display: flex;
align-items: center;
justify-content: start;
font-family: var(--font-title);
font-size: 20px;
}
.position-title img {
width: 20px;
height: 20px;
margin: 0 10px;
}
.position-grid {
display: grid;
margin-top: 10px;
grid-gap: 20px;
grid-template-columns: repeat(auto-fill, minmax(calc(400px + 2rem), 1fr));
}
.position-card {
border-radius: 5px;
background: var(--content-box-bg-1);
color: var(--common-bgt-1);
}
.position-list {
background: inherit;
color: inherit;
font-family: var(--font-title);
}
.position-icon {
overflow: hidden;
width: 100%;
height: 100%;
border-radius: 5px;
object-fit: contain;
}
.position-icon :deep(img) {
width: 100%;
height: 100%;
object-fit: cover;
transition: all 0.3s;
}
.position-icon :hover {
cursor: pointer;
scale: 1.5;
}
.position-card-text {
display: inline-block;
min-width: 200px;
align-items: center;
}
.position-card-text :nth-child(1) {
margin: 0 5px;
}
</style>