Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 69 additions & 1 deletion src/entries/ItemList.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,75 @@
import "virtual:uno.css";
import { createApp } from "vue";

import { TORAPPU_ENDPOINT } from "../utils/consts";
import { getImagePath } from "../utils/utils";
import ItemList from "../widgets/ItemList/index.vue";

import type { ItemData } from "../widgets/ItemList/types";

function readItemsFromDOM(): ItemData[] {
const elements = document.querySelectorAll<HTMLDivElement>("#cargo-data>div");
const items: ItemData[] = [];

for (const el of elements) {
const name = el.dataset.name ?? "";
const itemId = el.dataset.itemId ?? "";
if (!itemId) {
console.warn("[ItemList] itemId为空,跳过:", name);
continue;
}

const obtainMethodEl = el.querySelector<HTMLDivElement>(".obtain-method");
const descriptionEl = el.querySelector<HTMLDivElement>(".description");
const purposeEl = el.querySelector<HTMLDivElement>(".purpose");

const filename = el.dataset.filename ?? "";
const iconId = el.dataset.iconId ?? "";
const imgSrc = (() => {
if (filename === "") {
return iconId === ""
? getImagePath("无图片占位符.png")
: `${TORAPPU_ENDPOINT}/assets/item_icon/${iconId}.png`;
} else {
return filename;
}
})();

items.push({
name,
description: descriptionEl?.textContent ?? "",
descriptionHtml: descriptionEl?.innerHTML ?? "",
usage: purposeEl?.textContent ?? "",
usageHtml: purposeEl?.innerHTML ?? "",
obtainApproach: (obtainMethodEl?.textContent ?? "")
.split(/[,、,]/)
.map((s) => s.trim())
.filter(Boolean),
rarity: Number(el.dataset.rarity ?? "0"),
category1: el.dataset.category1 ?? "",
category2: el.dataset.category2 ?? "",
category3: el.dataset.category3 ?? "",
categories: [
el.dataset.category1,
el.dataset.category2,
el.dataset.category3,
].filter(Boolean) as string[],
itemId,
sortId: Number(el.dataset.sortId ?? "0"),
iconId,
filename,
darkBackground: el.dataset.darkBackground === "1",
imgSrc,
});
}

items.sort((a, b) => a.sortId - b.sortId);
return items;
}

const ele = document.querySelector("#root");
if (ele) createApp(ItemList, {}).mount(ele);
if (ele) {
const items = readItemsFromDOM();
console.log(items);
createApp(ItemList, { items }).mount(ele);
}
20 changes: 20 additions & 0 deletions src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,26 @@ export function getImagePath(filename: string) {
return `${MEDIA_ENDPOINT}/${md5.slice(0, 1)}/${md5.slice(0, 2)}/${filename}`;
}

export async function getImagePathWithRedirect(filename: string) {
const resp = await fetch(
`/api.php?${new URLSearchParams({
action: "query",
titles: `File:${filename}`,
redirects: "1",
format: "json",
})}`,
);
const data = await resp.json();

if (data.query?.redirects) {
filename = (data.query.redirects[0].to || filename)
.replaceAll(" ", "_")
.replace("文件:", "");
}

return getImagePath(filename);
}

export const professionMap = {
PIONEER: "先锋",
WARRIOR: "近卫",
Expand Down
40 changes: 16 additions & 24 deletions src/widgets/ItemList/ItemCard.vue
Original file line number Diff line number Diff line change
@@ -1,26 +1,11 @@
<script setup lang="ts">
import { computed } from "vue";

import { NTooltip } from "naive-ui";

import { TORAPPU_ENDPOINT } from "@/utils/consts";
import { getImagePath } from "@/utils/utils";

import type { ItemData } from "./types";

const props = defineProps<{
defineProps<{
item: ItemData;
}>();

const imgSrc = computed(() => {
if (props.item.filename === "无") {
return getImagePath("无图片占位符.png");
}
if (props.item.filename) {
return getImagePath(props.item.filename);
}
return `${TORAPPU_ENDPOINT}/assets/item_icon/${props.item.iconId}.png`;
});
</script>

<template>
Expand All @@ -34,11 +19,11 @@ const imgSrc = computed(() => {
}"
>
<img
:src="imgSrc"
:src="item.imgSrc"
:alt="item.name"
loading="lazy"
class="block"
style="width: 80px; height: 80px"
style="width: 80px; height: 80px; object-fit: contain"
/>
</div>
<div
Expand All @@ -50,12 +35,16 @@ const imgSrc = computed(() => {
</template>
<div class="max-w-80 text-sm">
<div class="mb-1 font-bold">{{ item.name }}</div>
<div v-if="item.usage" class="mb-1 text-gray-300">
{{ item.usage }}
</div>
<div v-if="item.description" class="text-gray-400">
{{ item.description }}
</div>
<div
v-if="item.usage"
class="mb-1 text-gray-300"
v-html="item.usageHtml"
></div>
<div
v-if="item.description"
class="text-gray-400 italic"
v-html="item.descriptionHtml"
></div>
</div>
</NTooltip>
</template>
Expand All @@ -68,4 +57,7 @@ const imgSrc = computed(() => {
transform: scale(1.1);
z-index: 1;
}
:deep(a) {
color: orange !important;
}
</style>
23 changes: 12 additions & 11 deletions src/widgets/ItemList/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,9 @@ export const defaultFilterConfig: FilterConfig = {
"材料",
"消耗道具",
"作战记录",
"建材",
"建材原材料",
"精英化芯片",
"技巧概要",
"芯片",
"双芯片",
"芯片组",
"基建材料",
"理智药剂",
"食物",
"物资补给",
Expand All @@ -29,15 +26,11 @@ export const defaultFilterConfig: FilterConfig = {
"私人信件",
"文件夹",
"活动道具",
"其它道具组合",
"道具组合",
"干员赠礼",
"纪念物",
"可露希尔票券",
"其他道具",
"其他干员道具",
"养成材料组合",
"家具收藏包",
"形艺特辑组件包",
"其他",
],
},
obtainApproach: {
Expand Down Expand Up @@ -88,6 +81,14 @@ export const rarityColorMap: Record<number, string> = {
5: "#e06c00",
};

//only needed aliases
export const categoryAliases: Record<string, string[]> = {
精英化芯片: ["芯片", "芯片组", "双芯片"],
基建材料: ["建材", "建材原材料"],
道具组合: ["养成材料组合", "家具收藏包", "其它道具组合", "形艺特辑组件包"],
其他: ["其他道具", "其他干员道具"],
};

export const obtainApproachAliases: Record<string, string[]> = {
采购中心: ["采购中心"],
任务奖励: ["任务奖励", "日常任务", "周常任务"],
Expand Down
78 changes: 46 additions & 32 deletions src/widgets/ItemList/index.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed, onMounted, ref } from "vue";
import { computed, ref, watch } from "vue";

import {
NButton,
Expand All @@ -17,18 +17,20 @@ import { isMobileSkin } from "@/utils/utils";
import FilterGroup from "./FilterGroup.vue";
import ItemCard from "./ItemCard.vue";
import {
categoryAliases,
defaultFilterConfig,
obtainApproachAliases,
rarityLabelMap,
sortOptions,
} from "./consts";
import { fetchAllItems } from "./itemData";

import type { ItemData } from "./types";

const itemData = ref<ItemData[]>([]);
const props = defineProps<{
items: ItemData[];
}>();

const keyword = ref("");
const isLoading = ref(true);
const i18nConfig = getNaiveUILocale();
const isMobile = isMobileSkin();
const { theme, toggleDark } = useTheme();
Expand All @@ -54,7 +56,7 @@ const filteredItemData = computed(() => {
const { states, sortOrder } = filterConfig.value;
const searchWord = keyword.value.toLowerCase();

let result = itemData.value.filter((item) => {
let result = props.items.filter((item) => {
if (
states.rarity.length > 0 &&
!states.rarity.some((r) => rarityLabelMap[r] === item.rarity)
Expand All @@ -63,7 +65,13 @@ const filteredItemData = computed(() => {
}

if (states.category.length > 0) {
const hasMatch = states.category.some((c) => item.categories.includes(c));
const hasMatch = states.category.some((category) => {
const aliases = categoryAliases[category];
if (aliases) {
return aliases.some((alias) => item.categories.includes(alias));
}
return states.category.some((c) => item.categories.includes(c));
});
if (!hasMatch) return false;
}

Expand Down Expand Up @@ -125,10 +133,17 @@ const paginatedItemData = computed(() =>
),
);

onMounted(async () => {
itemData.value = await fetchAllItems();
isLoading.value = false;
});
watch(
() => [
filterConfig.value.states,
filterConfig.value.sortOrder,
keyword.value,
],
() => {
pagination.value.page = 1;
},
{ deep: true },
);
</script>

<template>
Expand All @@ -154,6 +169,10 @@ onMounted(async () => {
"
/>
<div class="my-2 flex items-center gap-2">
<div class="w-5em">
<span class="mdi mdi-package-variant-closed"></span>
{{ filteredItemData.length }}
</div>
<NInput
v-model:value="keyword"
type="text"
Expand All @@ -172,29 +191,24 @@ onMounted(async () => {
</NButton>
</div>
</div>
<div v-if="isLoading" class="py-8 text-center text-gray-400">
加载中...
</div>
<template v-else>
<div class="flex flex-wrap gap-1">
<ItemCard
v-for="item in paginatedItemData"
:key="item.name"
:item="item"
/>
</div>
<NPagination
class="my-2 justify-center"
:item-count="filteredItemData.length"
:page="pagination.page"
:page-size="pagination.pageSize"
:page-sizes="pagination.pageSizes"
:page-slot="pagination.pageSlot"
:show-size-picker="pagination.showSizePicker"
@update:page="pagination.onChange"
@update:page-size="pagination.onUpdatePageSize"
<div class="flex flex-wrap gap-1">
<ItemCard
v-for="item in paginatedItemData"
:key="item.name"
:item="item"
/>
</template>
</div>
<NPagination
class="my-2 justify-center"
:item-count="filteredItemData.length"
:page="pagination.page"
:page-size="pagination.pageSize"
:page-sizes="pagination.pageSizes"
:page-slot="pagination.pageSlot"
:show-size-picker="pagination.showSizePicker"
@update:page="pagination.onChange"
@update:page-size="pagination.onUpdatePageSize"
/>
</NLayout>
</NConfigProvider>
</template>
Expand Down
Loading
Loading