Files
Starlight_Lancher/apps/app-frontend/src/components/ui/skin-site/SkinSiteNavCards.vue
T
AxTps 71b8d111fb
Sync source to CNB / Sync Git ref (push) Skipped
feat: 新增皮肤站维基只读浏览页
- 新增 WikiView 页面:输入标题加载页面,支持内部词条跳转与返回栈
- 新增 WikiNodeRenderer 递归渲染器:标题、段落、加粗斜体下划线删除线、高亮、行内代码、黑幕、分隔线、内外链、有序无序列表、引用、代码块、表格、附件、提示块
- 新增 WikiNotice 提示块与 WikiAttachmentNode 附件组件,图片支持点击查看大图
- 补充维基节点与附件类型定义,新增附件查询接口与附件内容 URL 拼接
- 主页导航 Wiki 卡片改为站内打开
- 中英文各补 10 个文案
2026-10-18 13:13:03 +08:00

146 lines
3.9 KiB
Vue

<script setup lang="ts">
import {
BookIcon,
BugIcon,
DownloadIcon,
MapIcon,
ShieldCheckIcon,
UserIcon,
} from '@modrinth/assets'
import { defineMessages, useVIntl } from '@modrinth/ui'
import type { Component } from 'vue'
const { formatMessage } = useVIntl()
const emit = defineEmits<{
(e: 'navigate', view: 'downloads' | 'review' | 'bugs' | 'map' | 'wiki'): void
}>()
const messages = defineMessages({
title: { id: 'app.skin-site.home.nav.title', defaultMessage: 'Skin site' },
download: { id: 'app.skin-site.home.nav.download', defaultMessage: 'Downloads' },
downloadDesc: {
id: 'app.skin-site.home.nav.download-desc',
defaultMessage: 'Browse files shared by the server.',
},
review: { id: 'app.skin-site.home.nav.review', defaultMessage: 'Player review' },
reviewDesc: {
id: 'app.skin-site.home.nav.review-desc',
defaultMessage: 'Submit and review player applications.',
},
wiki: { id: 'app.skin-site.home.nav.wiki', defaultMessage: 'Server wiki' },
wikiDesc: {
id: 'app.skin-site.home.nav.wiki-desc',
defaultMessage: 'Guides and documentation for the server.',
},
bugs: { id: 'app.skin-site.home.nav.bugs', defaultMessage: 'Bug tracker' },
bugsDesc: {
id: 'app.skin-site.home.nav.bugs-desc',
defaultMessage: 'Report issues and follow their progress.',
},
map: { id: 'app.skin-site.home.nav.map', defaultMessage: 'Server map' },
mapDesc: {
id: 'app.skin-site.home.nav.map-desc',
defaultMessage: 'Explore the live world map.',
},
profile: { id: 'app.skin-site.home.nav.profile', defaultMessage: 'Profile' },
profileDesc: {
id: 'app.skin-site.home.nav.profile-desc',
defaultMessage: 'Manage your account and textures.',
},
})
interface NavItem {
id: string
title: string
description: string
icon: Component
href: string
}
const SKIN_SITE = 'https://skin.starlight.cool'
const items: NavItem[] = [
{
id: 'download',
title: formatMessage(messages.download),
description: formatMessage(messages.downloadDesc),
icon: DownloadIcon,
href: `${SKIN_SITE}/download`,
},
{
id: 'review',
title: formatMessage(messages.review),
description: formatMessage(messages.reviewDesc),
icon: ShieldCheckIcon,
href: `${SKIN_SITE}/review`,
},
{
id: 'wiki',
title: formatMessage(messages.wiki),
description: formatMessage(messages.wikiDesc),
icon: BookIcon,
href: `${SKIN_SITE}/wiki`,
},
{
id: 'bugs',
title: formatMessage(messages.bugs),
description: formatMessage(messages.bugsDesc),
icon: BugIcon,
href: `${SKIN_SITE}/bugs`,
},
{
id: 'map',
title: formatMessage(messages.map),
description: formatMessage(messages.mapDesc),
icon: MapIcon,
href: `${SKIN_SITE}/map`,
},
{
id: 'profile',
title: formatMessage(messages.profile),
description: formatMessage(messages.profileDesc),
icon: UserIcon,
href: `${SKIN_SITE}/profile`,
},
]
function open(item: NavItem) {
if (
item.id === 'download' ||
item.id === 'review' ||
item.id === 'bugs' ||
item.id === 'map' ||
item.id === 'wiki'
) {
emit(
'navigate',
item.id === 'download'
? 'downloads'
: (item.id as 'review' | 'bugs' | 'map' | 'wiki'),
)
return
}
void import('@tauri-apps/plugin-opener').then(({ openUrl }) => openUrl(item.href))
}
</script>
<template>
<div class="flex flex-col gap-3">
<h3 class="m-0 text-base font-semibold text-contrast">{{ formatMessage(messages.title) }}</h3>
<div class="grid grid-cols-2 gap-3 sm:grid-cols-3">
<button
v-for="item in items"
:key="item.id"
type="button"
class="flex flex-col items-start gap-1 rounded-xl border border-solid border-surface-5 bg-bg-raised p-3 text-left transition-colors hover:bg-button-bg"
@click="open(item)"
>
<component :is="item.icon" class="size-5 text-brand" />
<span class="text-sm font-semibold text-contrast">{{ item.title }}</span>
<span class="text-xs text-secondary">{{ item.description }}</span>
</button>
</div>
</div>
</template>