mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-06 18:36:55 +08:00
调整用户页样式, 添加过渡动画
This commit is contained in:
@@ -50,12 +50,9 @@ const layout = computed(() => {
|
|||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if (isDarkMode.value) {
|
if (isDarkMode.value) {
|
||||||
document.documentElement.classList.add('dark');
|
document.documentElement.classList.add('dark');
|
||||||
console.log('Added dark class to HTML'); // For debugging
|
|
||||||
} else {
|
} else {
|
||||||
document.documentElement.classList.remove('dark');
|
document.documentElement.classList.remove('dark');
|
||||||
console.log('Removed dark class from HTML'); // For debugging
|
|
||||||
}
|
}
|
||||||
// If you dynamically apply Naive UI theme to body or provider, do it here too
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const themeOverrides = {
|
const themeOverrides = {
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
<!-- eslint-disable vue/component-name-in-template-casing -->
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { NavigateToNewTab, isDarkMode } from '@/Utils'
|
import { NavigateToNewTab, isDarkMode } from '@/Utils';
|
||||||
import { useAccount } from '@/api/account'
|
import { useAccount } from '@/api/account';
|
||||||
import { FunctionTypes, ThemeType, UserInfo } from '@/api/api-models'
|
import { FunctionTypes, ThemeType, UserInfo } from '@/api/api-models';
|
||||||
import { useUser } from '@/api/user'
|
import { useUser } from '@/api/user';
|
||||||
import RegisterAndLogin from '@/components/RegisterAndLogin.vue'
|
import RegisterAndLogin from '@/components/RegisterAndLogin.vue';
|
||||||
import { AVATAR_URL, FETCH_API } from '@/data/constants'
|
import { FETCH_API } from '@/data/constants'; // 移除了未使用的 AVATAR_URL
|
||||||
import { useAuthStore } from '@/store/useAuthStore'
|
import { useAuthStore } from '@/store/useAuthStore';
|
||||||
import {
|
import {
|
||||||
BookCoins20Filled,
|
BookCoins20Filled,
|
||||||
CalendarClock24Filled,
|
CalendarClock24Filled,
|
||||||
Person48Filled,
|
Person48Filled,
|
||||||
VideoAdd20Filled,
|
VideoAdd20Filled,
|
||||||
WindowWrench20Filled,
|
WindowWrench20Filled,
|
||||||
} from '@vicons/fluent'
|
} from '@vicons/fluent';
|
||||||
import { Chatbox, Home, Moon, MusicalNote, Sunny } from '@vicons/ionicons5'
|
import { Chatbox, Home, Moon, MusicalNote, Sunny } from '@vicons/ionicons5';
|
||||||
import { useElementSize, useStorage } from '@vueuse/core'
|
import { useElementSize, useStorage } from '@vueuse/core';
|
||||||
import {
|
import {
|
||||||
MenuOption,
|
MenuOption,
|
||||||
NAvatar,
|
NAvatar,
|
||||||
NBackTop,
|
NBackTop,
|
||||||
NButton,
|
NButton,
|
||||||
|
NDivider,
|
||||||
NEllipsis,
|
NEllipsis,
|
||||||
NIcon,
|
NIcon,
|
||||||
NLayout,
|
NLayout,
|
||||||
@@ -36,195 +36,237 @@ import {
|
|||||||
NSwitch,
|
NSwitch,
|
||||||
NText,
|
NText,
|
||||||
useMessage,
|
useMessage,
|
||||||
} from 'naive-ui'
|
// NSpin 已默认导入,如果单独使用需确保导入
|
||||||
import { computed, h, onMounted, ref } from 'vue'
|
} from 'naive-ui';
|
||||||
import { RouterLink, useRoute } from 'vue-router'
|
import { computed, h, onMounted, ref, watch, defineAsyncComponent } from 'vue'; // 引入 watch
|
||||||
|
import { RouterLink, useRoute, useRouter } from 'vue-router'; // 引入 useRouter
|
||||||
|
|
||||||
const route = useRoute()
|
// --- 响应式状态和常量 ---
|
||||||
const id = computed(() => {
|
const route = useRoute();
|
||||||
return route.params.id
|
const router = useRouter(); // 获取 router 实例
|
||||||
})
|
const message = useMessage();
|
||||||
const themeType = useStorage('Settings.Theme', ThemeType.Auto)
|
const accountInfo = useAccount(); // 获取当前登录账户信息
|
||||||
|
const useAuth = useAuthStore(); // 获取认证状态 Store
|
||||||
|
|
||||||
const userInfo = ref<UserInfo>()
|
// 路由参数
|
||||||
const biliUserInfo = ref()
|
const id = computed(() => route.params.id);
|
||||||
const accountInfo = useAccount()
|
|
||||||
const useAuth = useAuthStore()
|
|
||||||
const message = useMessage()
|
|
||||||
|
|
||||||
const notfount = ref(false)
|
// 主题设置
|
||||||
|
const themeType = useStorage('Settings.Theme', ThemeType.Auto);
|
||||||
|
|
||||||
const registerAndLoginModalVisiable = ref(false)
|
// 用户和页面状态
|
||||||
const sider = ref()
|
const userInfo = ref<UserInfo | null>(null); // 用户信息,初始化为 null
|
||||||
const { width } = useElementSize(sider)
|
const biliUserInfo = ref<any>(null); // B站用户信息
|
||||||
const windowWidth = window.innerWidth
|
const isLoading = ref(true); // 是否正在加载数据
|
||||||
|
const notFound = ref(false); // 是否未找到用户
|
||||||
|
|
||||||
|
// UI 控制状态
|
||||||
|
const registerAndLoginModalVisiable = ref(false); // 注册/登录弹窗可见性
|
||||||
|
const sider = ref(); // 侧边栏 DOM 引用
|
||||||
|
const { width: siderWidth } = useElementSize(sider); // 侧边栏宽度
|
||||||
|
const windowWidth = window.innerWidth; // 窗口宽度,用于响应式显示
|
||||||
|
|
||||||
|
// 侧边栏菜单项
|
||||||
|
const menuOptions = ref<MenuOption[]>([]); // 初始化为空数组
|
||||||
|
|
||||||
|
// --- 方法 ---
|
||||||
|
|
||||||
|
/** 渲染图标的辅助函数 */
|
||||||
function renderIcon(icon: unknown) {
|
function renderIcon(icon: unknown) {
|
||||||
return () => h(NIcon, null, { default: () => h(icon as any) })
|
return () => h(NIcon, null, { default: () => h(icon as any) });
|
||||||
}
|
|
||||||
const menuOptions = ref<MenuOption[]>()
|
|
||||||
async function RequestBiliUserData() {
|
|
||||||
await fetch(FETCH_API + `https://workers.vrp.moe/api/bilibili/user-info/${userInfo.value?.biliId}`).then(
|
|
||||||
async (respone) => {
|
|
||||||
const data = await respone.json()
|
|
||||||
if (data.code == 0) {
|
|
||||||
biliUserInfo.value = data.card
|
|
||||||
} else {
|
|
||||||
throw new Error('Bili User API Error: ' + data.message)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
function gotoAuthPage() {
|
|
||||||
if (!accountInfo.value?.biliUserAuthInfo) {
|
|
||||||
message.error('你尚未进行 Bilibili 认证, 请前往面板进行认证和绑定')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
/*useAuthStore()
|
|
||||||
.setCurrentAuth(accountInfo.value?.biliUserAuthInfo.token)
|
|
||||||
.then(() => {
|
|
||||||
NavigateToNewTab('/bili-user')
|
|
||||||
})*/
|
|
||||||
NavigateToNewTab('/bili-user')
|
|
||||||
}
|
|
||||||
onMounted(async () => {
|
|
||||||
userInfo.value = await useUser(id.value?.toString())
|
|
||||||
if (!userInfo.value) {
|
|
||||||
notfount.value = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 根据 userInfo 更新侧边栏菜单 */
|
||||||
|
function updateMenuOptions() {
|
||||||
|
// 如果没有用户信息,清空菜单
|
||||||
|
if (!userInfo.value) {
|
||||||
|
menuOptions.value = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 基于 userInfo.extra.enableFunctions 构建菜单项
|
||||||
menuOptions.value = [
|
menuOptions.value = [
|
||||||
{
|
{
|
||||||
label: () =>
|
label: () => h(RouterLink, { to: { name: 'user-index' } }, { default: () => '主页' }),
|
||||||
h(
|
key: 'user-index', icon: renderIcon(Home),
|
||||||
RouterLink,
|
// 主页通常都显示
|
||||||
{
|
show: true
|
||||||
to: {
|
|
||||||
name: 'user-index',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ default: () => '主页' },
|
|
||||||
),
|
|
||||||
key: 'user-index',
|
|
||||||
icon: renderIcon(Home),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: () =>
|
label: () => h(RouterLink, { to: { name: 'user-songList' } }, { default: () => '歌单' }),
|
||||||
h(
|
key: 'user-songList', icon: renderIcon(MusicalNote),
|
||||||
RouterLink,
|
// 根据用户配置判断是否显示
|
||||||
{
|
show: userInfo.value?.extra?.enableFunctions.includes(FunctionTypes.SongList)
|
||||||
to: {
|
|
||||||
name: 'user-songList',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ default: () => '歌单' },
|
|
||||||
),
|
|
||||||
show: (userInfo.value?.extra?.enableFunctions.indexOf(FunctionTypes.SongList) ?? -1) > -1,
|
|
||||||
key: 'user-songList',
|
|
||||||
icon: renderIcon(MusicalNote),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: () =>
|
label: () => h(RouterLink, { to: { name: 'user-schedule' } }, { default: () => '日程' }),
|
||||||
h(
|
key: 'user-schedule', icon: renderIcon(CalendarClock24Filled),
|
||||||
RouterLink,
|
show: userInfo.value?.extra?.enableFunctions.includes(FunctionTypes.Schedule)
|
||||||
{
|
|
||||||
to: {
|
|
||||||
name: 'user-schedule',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ default: () => '日程' },
|
|
||||||
),
|
|
||||||
show: (userInfo.value?.extra?.enableFunctions.indexOf(FunctionTypes.Schedule) ?? -1) > -1,
|
|
||||||
key: 'user-schedule',
|
|
||||||
icon: renderIcon(CalendarClock24Filled),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: () =>
|
label: () => h(RouterLink, { to: { name: 'user-questionBox' } }, { default: () => '棉花糖 (提问箱)' }),
|
||||||
h(
|
key: 'user-questionBox', icon: renderIcon(Chatbox),
|
||||||
RouterLink,
|
show: userInfo.value?.extra?.enableFunctions.includes(FunctionTypes.QuestionBox)
|
||||||
{
|
|
||||||
to: {
|
|
||||||
name: 'user-questionBox',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ default: () => '棉花糖 (提问箱' },
|
|
||||||
),
|
|
||||||
show: (userInfo.value?.extra?.enableFunctions.indexOf(FunctionTypes.QuestionBox) ?? -1) > -1,
|
|
||||||
key: 'user-questionBox',
|
|
||||||
icon: renderIcon(Chatbox),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: () =>
|
label: () => h(RouterLink, { to: { name: 'user-video-collect' } }, { default: () => '视频征集' }),
|
||||||
h(
|
key: 'user-video-collect', icon: renderIcon(VideoAdd20Filled),
|
||||||
RouterLink,
|
show: userInfo.value?.extra?.enableFunctions.includes(FunctionTypes.VideoCollect)
|
||||||
{
|
|
||||||
to: {
|
|
||||||
name: 'user-video-collect',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ default: () => '视频征集' },
|
|
||||||
),
|
|
||||||
show: (userInfo.value?.extra?.enableFunctions.indexOf(FunctionTypes.VideoCollect) ?? -1) > -1,
|
|
||||||
key: 'user-video-collect',
|
|
||||||
icon: renderIcon(VideoAdd20Filled),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: () =>
|
label: () => h(RouterLink, { to: { name: 'user-goods' } }, { default: () => '积分' }),
|
||||||
h(
|
key: 'user-goods', icon: renderIcon(BookCoins20Filled),
|
||||||
RouterLink,
|
show: userInfo.value?.extra?.enableFunctions.includes(FunctionTypes.Point)
|
||||||
{
|
|
||||||
to: {
|
|
||||||
name: 'user-goods',
|
|
||||||
},
|
},
|
||||||
|
].filter(option => option.show !== false) as MenuOption[]; // 过滤掉 show 为 false 的菜单项
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** 获取 Bilibili 用户信息 */
|
||||||
|
async function RequestBiliUserData() {
|
||||||
|
// 确保 userInfo 和 biliId 存在
|
||||||
|
if (!userInfo.value?.biliId) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(FETCH_API + `https://workers.vrp.moe/api/bilibili/user-info/${userInfo.value.biliId}`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
if (data.code === 0) {
|
||||||
|
biliUserInfo.value = data.card; // 存储获取到的 B 站信息
|
||||||
|
} else {
|
||||||
|
console.error('Bili User API Error:', data.message);
|
||||||
|
// message.warning('获取B站信息失败: ' + data.message) // 可选: 轻微提示用户
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch Bili user data:', error);
|
||||||
|
// message.error('获取B站信息时网络错误') // 可选: 提示用户网络问题
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取 Vtsuru 用户信息和相关数据 */
|
||||||
|
async function fetchUserData(userId: string | string[] | undefined) {
|
||||||
|
// 验证 userId 的有效性
|
||||||
|
if (!userId || Array.isArray(userId)) {
|
||||||
|
notFound.value = true; // 标记为未找到
|
||||||
|
isLoading.value = false; // 加载结束
|
||||||
|
userInfo.value = null; // 清空用户信息
|
||||||
|
menuOptions.value = []; // 清空菜单
|
||||||
|
console.error("无效的用户 ID:", userId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置状态,准备加载新数据
|
||||||
|
isLoading.value = true;
|
||||||
|
notFound.value = false;
|
||||||
|
userInfo.value = null;
|
||||||
|
menuOptions.value = [];
|
||||||
|
biliUserInfo.value = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 调用 API 获取用户信息
|
||||||
|
const fetchedUserInfo = await useUser(userId as string); // 强制转换为 string
|
||||||
|
|
||||||
|
if (!fetchedUserInfo) {
|
||||||
|
// 如果 API 返回 null 或 undefined,则视为未找到
|
||||||
|
notFound.value = true;
|
||||||
|
userInfo.value = null;
|
||||||
|
} else {
|
||||||
|
// 成功获取用户信息
|
||||||
|
userInfo.value = fetchedUserInfo;
|
||||||
|
// 基于新的用户信息更新菜单
|
||||||
|
updateMenuOptions();
|
||||||
|
// 异步获取 B 站信息(不阻塞主流程)
|
||||||
|
await RequestBiliUserData();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("获取用户信息时出错:", error);
|
||||||
|
message.error("加载用户信息时发生错误");
|
||||||
|
notFound.value = true; // 标记为未找到状态
|
||||||
|
userInfo.value = null;
|
||||||
|
} finally {
|
||||||
|
// 无论成功或失败,加载状态都结束
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 跳转到 Bilibili 认证用户中心 */
|
||||||
|
function gotoAuthPage() {
|
||||||
|
if (!accountInfo.value?.biliUserAuthInfo) {
|
||||||
|
message.error('你尚未进行 Bilibili 认证, 请前往面板进行认证和绑定');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
NavigateToNewTab('/bili-user'); // 在新标签页打开
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Watcher ---
|
||||||
|
|
||||||
|
// 监听路由参数 id 的变化
|
||||||
|
watch(
|
||||||
|
() => route.params.id,
|
||||||
|
(newId, oldId) => {
|
||||||
|
// 只有当 newId 有效且与 oldId 不同时才重新加载数据
|
||||||
|
if (newId && newId !== oldId) {
|
||||||
|
fetchUserData(newId);
|
||||||
|
} else if (!newId) {
|
||||||
|
// 如果 id 从路由中移除,处理相应的状态
|
||||||
|
notFound.value = true;
|
||||||
|
isLoading.value = false;
|
||||||
|
userInfo.value = null;
|
||||||
|
menuOptions.value = [];
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{ default: () => '积分' },
|
{ immediate: true } // 关键: 组件挂载时立即执行一次 watcher,触发初始数据加载
|
||||||
),
|
);
|
||||||
show: (userInfo.value?.extra?.enableFunctions.indexOf(FunctionTypes.Point) ?? -1) > -1,
|
|
||||||
key: 'user-goods',
|
// --- 组件模板 ---
|
||||||
icon: renderIcon(BookCoins20Filled),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
await RequestBiliUserData()
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<!-- 情况 1: 加载完毕,但 URL 中没有提供用户 ID -->
|
||||||
<NLayoutContent
|
<NLayoutContent
|
||||||
v-if="!id"
|
v-if="!id && !isLoading"
|
||||||
style="height: 100vh"
|
class="center-container"
|
||||||
>
|
>
|
||||||
<NResult
|
<NResult
|
||||||
status="error"
|
status="error"
|
||||||
title="输入的uId无效"
|
title="未提供用户ID"
|
||||||
description="再检查检查"
|
description="请检查访问的URL地址"
|
||||||
/>
|
/>
|
||||||
</NLayoutContent>
|
</NLayoutContent>
|
||||||
|
|
||||||
|
<!-- 情况 2: 加载完毕,但未找到指定 ID 的用户 -->
|
||||||
<NLayoutContent
|
<NLayoutContent
|
||||||
v-else-if="notfount"
|
v-else-if="notFound && !isLoading"
|
||||||
style="height: 100vh"
|
class="center-container"
|
||||||
>
|
>
|
||||||
<NResult
|
<NResult
|
||||||
status="error"
|
status="error"
|
||||||
title="未找到指定 uId 的用户"
|
title="用户不存在"
|
||||||
description="或者是没有进行认证"
|
description="无法找到指定ID的用户,或者该用户未完成认证"
|
||||||
/>
|
/>
|
||||||
</NLayoutContent>
|
</NLayoutContent>
|
||||||
|
|
||||||
|
<!-- 情况 3: 存在 ID 且 (正在加载 或 加载成功且找到用户) -->
|
||||||
<NLayout
|
<NLayout
|
||||||
v-else
|
v-else
|
||||||
style="height: 100vh"
|
style="height: 100vh"
|
||||||
>
|
>
|
||||||
<NLayoutHeader style="height: 50px; padding: 5px 15px 5px 15px">
|
<!-- 顶部导航栏 -->
|
||||||
|
<NLayoutHeader class="layout-header">
|
||||||
<NPageHeader
|
<NPageHeader
|
||||||
:subtitle="($route.meta.title as string) ?? ''"
|
:subtitle="isLoading ? '加载中...' : ($route.meta.title as string) ?? ''"
|
||||||
style="margin-top: 6px"
|
style="width: 100%"
|
||||||
>
|
>
|
||||||
|
<!-- 右侧额外操作区域 -->
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<NSpace align="center">
|
<NSpace align="center">
|
||||||
|
<!-- 主题切换开关 -->
|
||||||
<NSwitch
|
<NSwitch
|
||||||
:default-value="!isDarkMode"
|
:value="themeType === ThemeType.Light"
|
||||||
@update:value="
|
:disabled="isLoading"
|
||||||
(value: string & number & boolean) => (themeType = value ? ThemeType.Light : ThemeType.Dark)
|
title="切换亮/暗色主题"
|
||||||
"
|
@update:value="(value) => (themeType = value ? ThemeType.Light : ThemeType.Dark)"
|
||||||
>
|
>
|
||||||
<template #checked>
|
<template #checked>
|
||||||
<NIcon :component="Sunny" />
|
<NIcon :component="Sunny" />
|
||||||
@@ -233,11 +275,12 @@ onMounted(async () => {
|
|||||||
<NIcon :component="Moon" />
|
<NIcon :component="Moon" />
|
||||||
</template>
|
</template>
|
||||||
</NSwitch>
|
</NSwitch>
|
||||||
<template v-if="accountInfo.id">
|
<!-- 已登录用户操作 -->
|
||||||
|
<template v-if="accountInfo?.id">
|
||||||
<NSpace>
|
<NSpace>
|
||||||
|
<!-- B站认证中心按钮 (如果已认证) -->
|
||||||
<NButton
|
<NButton
|
||||||
v-if="useAuth.isAuthed || accountInfo.biliUserAuthInfo"
|
v-if="useAuth.isAuthed || accountInfo.biliUserAuthInfo"
|
||||||
style="right: 0px; position: relative"
|
|
||||||
type="primary"
|
type="primary"
|
||||||
tag="a"
|
tag="a"
|
||||||
href="/bili-user"
|
href="/bili-user"
|
||||||
@@ -250,8 +293,8 @@ onMounted(async () => {
|
|||||||
</template>
|
</template>
|
||||||
<span v-if="windowWidth >= 768"> 认证用户中心 </span>
|
<span v-if="windowWidth >= 768"> 认证用户中心 </span>
|
||||||
</NButton>
|
</NButton>
|
||||||
|
<!-- 主播后台按钮 -->
|
||||||
<NButton
|
<NButton
|
||||||
style="right: 0px; position: relative"
|
|
||||||
type="primary"
|
type="primary"
|
||||||
size="small"
|
size="small"
|
||||||
@click="$router.push({ name: 'manage-index' })"
|
@click="$router.push({ name: 'manage-index' })"
|
||||||
@@ -263,9 +306,9 @@ onMounted(async () => {
|
|||||||
</NButton>
|
</NButton>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</template>
|
</template>
|
||||||
|
<!-- 未登录用户操作 -->
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<NButton
|
<NButton
|
||||||
style="right: 0px; position: relative"
|
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="registerAndLoginModalVisiable = true"
|
@click="registerAndLoginModalVisiable = true"
|
||||||
>
|
>
|
||||||
@@ -274,37 +317,37 @@ onMounted(async () => {
|
|||||||
</template>
|
</template>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</template>
|
</template>
|
||||||
|
<!-- 页面标题 (网站 Logo) -->
|
||||||
<template #title>
|
<template #title>
|
||||||
<NButton
|
<span>
|
||||||
text
|
|
||||||
tag="a"
|
|
||||||
@click="$router.push({ name: 'index' })"
|
|
||||||
>
|
|
||||||
<NText
|
<NText
|
||||||
strong
|
strong
|
||||||
style="font-size: 1.5rem; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1)"
|
class="site-title"
|
||||||
>
|
>
|
||||||
VTSURU
|
VTSURU
|
||||||
</NText>
|
</NText>
|
||||||
</NButton>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</NPageHeader>
|
</NPageHeader>
|
||||||
</NLayoutHeader>
|
</NLayoutHeader>
|
||||||
|
|
||||||
|
<!-- 主体布局 (包含侧边栏和内容区) -->
|
||||||
<NLayout
|
<NLayout
|
||||||
has-sider
|
has-sider
|
||||||
style="height: calc(100vh - --vtsuru-header-height)"
|
class="main-layout-body"
|
||||||
>
|
>
|
||||||
|
<!-- 左侧边栏 -->
|
||||||
<NLayoutSider
|
<NLayoutSider
|
||||||
ref="sider"
|
ref="sider"
|
||||||
show-trigger
|
show-trigger
|
||||||
default-collapsed
|
|
||||||
collapse-mode="width"
|
collapse-mode="width"
|
||||||
:collapsed-width="64"
|
:collapsed-width="64"
|
||||||
:width="180"
|
:width="180"
|
||||||
:native-scrollbar="false"
|
:native-scrollbar="false"
|
||||||
style="height: calc(100vh - --vtsuru-header-height)"
|
:default-collapsed="windowWidth < 768"
|
||||||
|
style="height: 100%"
|
||||||
>
|
>
|
||||||
<Transition>
|
<!-- 用户头像和昵称 (加载完成后显示) -->
|
||||||
<div
|
<div
|
||||||
v-if="userInfo?.streamerInfo"
|
v-if="userInfo?.streamerInfo"
|
||||||
style="margin-top: 8px"
|
style="margin-top: 8px"
|
||||||
@@ -315,16 +358,16 @@ onMounted(async () => {
|
|||||||
align="center"
|
align="center"
|
||||||
>
|
>
|
||||||
<NAvatar
|
<NAvatar
|
||||||
|
class="sider-avatar"
|
||||||
:src="userInfo.streamerInfo.faceUrl"
|
:src="userInfo.streamerInfo.faceUrl"
|
||||||
:img-props="{ referrerpolicy: 'no-referrer' }"
|
:img-props="{ referrerpolicy: 'no-referrer' }"
|
||||||
round
|
round
|
||||||
bordered
|
bordered
|
||||||
:style="{
|
title="前往用户B站主页"
|
||||||
boxShadow: isDarkMode ? 'rgb(195 192 192 / 35%) 0px 0px 8px' : '0 2px 3px rgba(0, 0, 0, 0.1)',
|
@click="NavigateToNewTab(`https://space.bilibili.com/${userInfo.biliId}`)"
|
||||||
}"
|
|
||||||
/>
|
/>
|
||||||
<NEllipsis
|
<NEllipsis
|
||||||
v-if="width > 100"
|
v-if="siderWidth > 100"
|
||||||
style="max-width: 100%"
|
style="max-width: 100%"
|
||||||
>
|
>
|
||||||
<NText strong>
|
<NText strong>
|
||||||
@@ -333,87 +376,266 @@ onMounted(async () => {
|
|||||||
</NEllipsis>
|
</NEllipsis>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
<!-- 侧边栏加载状态 -->
|
||||||
|
<div
|
||||||
|
v-else-if="isLoading"
|
||||||
|
class="sider-loading"
|
||||||
|
>
|
||||||
|
<NSpin size="small" />
|
||||||
|
</div>
|
||||||
|
<NDivider style="margin: 0; margin-top: 5px;" />
|
||||||
|
<!-- 导航菜单 -->
|
||||||
<NMenu
|
<NMenu
|
||||||
:default-value="$route.name?.toString()"
|
:value="route.name?.toString()"
|
||||||
:collapsed-width="64"
|
:collapsed-width="64"
|
||||||
:collapsed-icon-size="22"
|
:collapsed-icon-size="22"
|
||||||
:options="menuOptions"
|
:options="menuOptions"
|
||||||
|
:disabled="isLoading"
|
||||||
|
class="sider-menu"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- 侧边栏底部链接 -->
|
||||||
|
<div class="sider-footer">
|
||||||
|
<!-- 仅在侧边栏展开时显示 -->
|
||||||
<NSpace
|
<NSpace
|
||||||
v-if="width > 150"
|
v-if="siderWidth > 150"
|
||||||
justify="center"
|
justify="center"
|
||||||
align="center"
|
align="center"
|
||||||
vertical
|
vertical
|
||||||
|
size="small"
|
||||||
|
style="width: 100%;"
|
||||||
>
|
>
|
||||||
<NText depth="3">
|
<NText
|
||||||
有更多功能建议请
|
depth="3"
|
||||||
<NButton
|
class="footer-text"
|
||||||
|
>
|
||||||
|
有有更多功能建议请 <NButton
|
||||||
text
|
text
|
||||||
type="info"
|
type="info"
|
||||||
tag="a"
|
tag="a"
|
||||||
href="/feedback"
|
href="/feedback"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
size="tiny"
|
||||||
>
|
>
|
||||||
反馈
|
反馈
|
||||||
</NButton>
|
</NButton>
|
||||||
</NText>
|
</NText>
|
||||||
<NText depth="3">
|
<NDivider style="margin: 0; width: 100%" />
|
||||||
|
<NText
|
||||||
|
depth="3"
|
||||||
|
class="footer-text"
|
||||||
|
>
|
||||||
<NButton
|
<NButton
|
||||||
text
|
text
|
||||||
type="info"
|
type="info"
|
||||||
tag="a"
|
tag="a"
|
||||||
href="/about"
|
href="/about"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
size="tiny"
|
||||||
>
|
>
|
||||||
关于本站
|
关于本站
|
||||||
</NButton>
|
</NButton>
|
||||||
</NText>
|
</NText>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
|
</div>
|
||||||
</NLayoutSider>
|
</NLayoutSider>
|
||||||
<NLayout style="height: 100%">
|
|
||||||
|
<!-- 右侧内容区域布局容器 -->
|
||||||
|
<NLayout class="content-layout-container">
|
||||||
|
<!-- 全局加载动画 (覆盖内容区) -->
|
||||||
<div
|
<div
|
||||||
|
v-if="isLoading"
|
||||||
|
class="loading-container"
|
||||||
|
>
|
||||||
|
<NSpin size="large" />
|
||||||
|
</div>
|
||||||
|
<!-- 实际内容区域 (加载完成且找到用户时显示) -->
|
||||||
|
<div
|
||||||
|
v-else-if="userInfo && !notFound"
|
||||||
class="viewer-page-content"
|
class="viewer-page-content"
|
||||||
:style="`box-shadow:${isDarkMode ? 'rgb(28 28 28 / 9%) 5px 5px 6px inset, rgba(139, 139, 139, 0.09) -5px -5px 6px inset' : 'inset 5px 5px 6px #8b8b8b17, inset -5px -5px 6px #8b8b8b17;'}`"
|
:style="`box-shadow:${isDarkMode ? 'rgb(28 28 28 / 9%) 5px 5px 6px inset, rgba(139, 139, 139, 0.09) -5px -5px 6px inset' : 'inset 5px 5px 6px #8b8b8b17, inset -5px -5px 6px #8b8b8b17;'}`"
|
||||||
>
|
>
|
||||||
<RouterView
|
<!-- 路由视图和动画 -->
|
||||||
v-if="userInfo"
|
<RouterView v-slot="{ Component }">
|
||||||
v-slot="{ Component }"
|
<Transition
|
||||||
|
name="fade-slide"
|
||||||
|
mode="out-in"
|
||||||
|
:appear="true"
|
||||||
>
|
>
|
||||||
<KeepAlive>
|
<KeepAlive>
|
||||||
<component
|
<component
|
||||||
:is="Component"
|
:is="Component"
|
||||||
|
:key="route.fullPath"
|
||||||
:bili-info="biliUserInfo"
|
:bili-info="biliUserInfo"
|
||||||
:user-info="userInfo"
|
:user-info="userInfo"
|
||||||
/>
|
/>
|
||||||
</KeepAlive>
|
</KeepAlive>
|
||||||
|
</Transition>
|
||||||
</RouterView>
|
</RouterView>
|
||||||
<template v-else>
|
<NBackTop
|
||||||
<NSpin show />
|
:right="40"
|
||||||
</template>
|
:bottom="40"
|
||||||
<NBackTop />
|
:listen-to="'.viewer-page-content'"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 如果 !isLoading && notFound, 会显示顶部的 NResult,这里不需要 else -->
|
||||||
</NLayout>
|
</NLayout>
|
||||||
</NLayout>
|
</NLayout>
|
||||||
</NLayout>
|
</NLayout>
|
||||||
|
|
||||||
|
<!-- 注册/登录弹窗 -->
|
||||||
<NModal
|
<NModal
|
||||||
v-model:show="registerAndLoginModalVisiable"
|
v-model:show="registerAndLoginModalVisiable"
|
||||||
|
preset="card"
|
||||||
style="width: 500px; max-width: 90vw"
|
style="width: 500px; max-width: 90vw"
|
||||||
|
title="注册 / 登录"
|
||||||
|
:auto-focus="false"
|
||||||
|
:mask-closable="false"
|
||||||
>
|
>
|
||||||
<div>
|
<!-- 异步加载注册登录组件,优化初始加载性能 -->
|
||||||
<RegisterAndLogin />
|
<RegisterAndLogin @close="registerAndLoginModalVisiable = false" />
|
||||||
</div>
|
|
||||||
</NModal>
|
</NModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
|
// --- CSS 变量定义 ---
|
||||||
|
:root {
|
||||||
|
--vtsuru-header-height: 50px; // 顶部导航栏高度
|
||||||
|
--vtsuru-content-padding: 20px; // 内容区域内边距
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 布局样式 ---
|
||||||
|
.center-container {
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-header {
|
||||||
|
height: var(--vtsuru-header-height);
|
||||||
|
padding: 0 15px; // 左右内边距
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid var(--n-border-color); // 底部边框
|
||||||
|
flex-shrink: 0; // 防止头部被压缩
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-layout-body {
|
||||||
|
height: calc(100vh - var(--vtsuru-header-height)); // 填充剩余高度
|
||||||
|
}
|
||||||
|
|
||||||
|
.sider-avatar {
|
||||||
|
box-shadow: var(--n-avatar-box-shadow, 0 2px 3px rgba(0, 0, 0, 0.1)); // 使用 Naive UI 变量或默认值
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.2s ease; // 添加悬浮效果
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sider-username {
|
||||||
|
max-width: 90%;
|
||||||
|
margin: 8px auto 0;
|
||||||
|
font-size: 14px; // 调整字体大小
|
||||||
|
}
|
||||||
|
|
||||||
|
.sider-loading {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center; // 垂直居中
|
||||||
|
padding: 30px 0; // 增加上下间距
|
||||||
|
height: 98px; // 大致等于头像+昵称的高度,防止跳动
|
||||||
|
}
|
||||||
|
|
||||||
|
.sider-menu {
|
||||||
|
margin-top: 10px;
|
||||||
|
width: 100%; // 确保菜单宽度正确
|
||||||
|
}
|
||||||
|
|
||||||
|
.sider-footer {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 20px;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0 5px; // 左右留白,防止文字贴边
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-text {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 内容区域样式 ---
|
||||||
|
.content-layout-container {
|
||||||
|
height: 100%;
|
||||||
|
min-height: 100%; // 保证最小高度,防止塌陷
|
||||||
|
overflow: hidden; // 关键: 隐藏此容器自身的滚动条,剪切内部溢出内容
|
||||||
|
position: relative; // 关键: 作为内部绝对定位元素(过渡中的组件)的定位基准
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-container {
|
||||||
|
// ... (保持不变) ...
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--n-body-color);
|
||||||
|
position: absolute; // 相对于 content-layout-container 定位
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
.viewer-page-content {
|
.viewer-page-content {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: 18px;
|
min-height: 100%; // 同样保证最小高度
|
||||||
|
border-radius: 8px;
|
||||||
padding: var(--vtsuru-content-padding);
|
padding: var(--vtsuru-content-padding);
|
||||||
height: calc(100vh - var(--vtsuru-header-height));
|
|
||||||
margin-right: 10px;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
overflow-y: auto;
|
overflow-y: auto; // 允许内容 Y 轴滚动
|
||||||
|
overflow-x: hidden; // 禁止内容 X 轴滚动 (可选,但通常推荐)
|
||||||
|
position: relative; // 为内部非绝对定位的内容提供上下文,例如 NBackTop
|
||||||
|
background-color: var(--n-card-color);
|
||||||
|
box-shadow: var(--content-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 路由过渡动画 ---
|
||||||
|
.fade-slide-enter-active,
|
||||||
|
.fade-slide-leave-active {
|
||||||
|
transition: opacity 0.25s ease, transform 0.25s ease;
|
||||||
|
// 关键: 相对于 content-layout-container 定位
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%; // 让过渡元素也撑满容器高度
|
||||||
|
// 关键: 保持内边距和盒模型一致
|
||||||
|
padding: var(--vtsuru-content-padding);
|
||||||
|
box-sizing: border-box;
|
||||||
|
// 关键: 背景色防止透视
|
||||||
|
background-color: var(--n-card-color); // 使用内容区的背景色
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-slide-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(15px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-slide-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-15px);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 返回顶部按钮 ---
|
||||||
|
.n-back-top {
|
||||||
|
z-index: 10; // 确保在最上层
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -30,7 +30,6 @@ import { computed, onMounted, onUnmounted, ref } from 'vue'
|
|||||||
import VueTurnstile from 'vue-turnstile'
|
import VueTurnstile from 'vue-turnstile'
|
||||||
|
|
||||||
const { biliInfo, userInfo } = defineProps<{
|
const { biliInfo, userInfo } = defineProps<{
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
biliInfo: any | undefined
|
biliInfo: any | undefined
|
||||||
userInfo: UserInfo | undefined
|
userInfo: UserInfo | undefined
|
||||||
}>()
|
}>()
|
||||||
@@ -172,40 +171,50 @@ onUnmounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div style="max-width: 700px; margin: 0 auto" title="提问">
|
<div
|
||||||
|
style="max-width: 700px; margin: 0 auto"
|
||||||
|
title="提问"
|
||||||
|
>
|
||||||
<NCard embedded>
|
<NCard embedded>
|
||||||
<NSpace vertical>
|
<NSpace vertical>
|
||||||
<NCard v-if="tags.length > 0" title="投稿话题 (可选)" size="small">
|
<NCard
|
||||||
|
v-if="tags.length > 0"
|
||||||
|
title="投稿话题 (可选)"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
<NSpace>
|
<NSpace>
|
||||||
<NTag
|
<NTag
|
||||||
v-for="tag in tags"
|
v-for="tag in tags"
|
||||||
:key="tag"
|
:key="tag"
|
||||||
@click="onSelectTag(tag)"
|
|
||||||
style="cursor: pointer"
|
style="cursor: pointer"
|
||||||
:bordered="false"
|
:bordered="false"
|
||||||
:type="selectedTag == tag ? 'primary' : 'default'"
|
:type="selectedTag == tag ? 'primary' : 'default'"
|
||||||
|
@click="onSelectTag(tag)"
|
||||||
>
|
>
|
||||||
{{ tag }}
|
{{ tag }}
|
||||||
</NTag>
|
</NTag>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</NCard>
|
</NCard>
|
||||||
<NSpace align="center" justify="center">
|
<NSpace
|
||||||
|
align="center"
|
||||||
|
justify="center"
|
||||||
|
>
|
||||||
<NInput
|
<NInput
|
||||||
|
v-model:value="questionMessage"
|
||||||
:disabled="isSelf"
|
:disabled="isSelf"
|
||||||
show-count
|
show-count
|
||||||
maxlength="5000"
|
maxlength="5000"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
:count-graphemes="countGraphemes"
|
:count-graphemes="countGraphemes"
|
||||||
v-model:value="questionMessage"
|
|
||||||
style="width: 300px"
|
style="width: 300px"
|
||||||
/>
|
/>
|
||||||
<NUpload
|
<NUpload
|
||||||
|
v-model:file-list="fileList"
|
||||||
:max="1"
|
:max="1"
|
||||||
accept=".png,.jpg,.jpeg,.gif,.svg,.webp,.ico"
|
accept=".png,.jpg,.jpeg,.gif,.svg,.webp,.ico"
|
||||||
list-type="image-card"
|
list-type="image-card"
|
||||||
:disabled="!accountInfo.id || isSelf"
|
:disabled="!accountInfo.id || isSelf"
|
||||||
:default-upload="false"
|
:default-upload="false"
|
||||||
v-model:file-list="fileList"
|
|
||||||
@update:file-list="OnFileListChange"
|
@update:file-list="OnFileListChange"
|
||||||
>
|
>
|
||||||
+ 上传图片
|
+ 上传图片
|
||||||
@@ -213,14 +222,31 @@ onUnmounted(() => {
|
|||||||
</NSpace>
|
</NSpace>
|
||||||
<NDivider style="margin: 10px 0 10px 0" />
|
<NDivider style="margin: 10px 0 10px 0" />
|
||||||
<NSpace align="center">
|
<NSpace align="center">
|
||||||
<NAlert v-if="!accountInfo.id && !isSelf" type="warning"> 只有注册用户才能够上传图片 </NAlert>
|
<NAlert
|
||||||
|
v-if="!accountInfo.id && !isSelf"
|
||||||
|
type="warning"
|
||||||
|
>
|
||||||
|
只有注册用户才能够上传图片
|
||||||
|
</NAlert>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<NSpace v-if="accountInfo.id" vertical>
|
<NSpace
|
||||||
<NCheckbox :disabled="isSelf" v-model:checked="isAnonymous" label="匿名提问" />
|
v-if="accountInfo.id"
|
||||||
|
vertical
|
||||||
|
>
|
||||||
|
<NCheckbox
|
||||||
|
v-model:checked="isAnonymous"
|
||||||
|
:disabled="isSelf"
|
||||||
|
label="匿名提问"
|
||||||
|
/>
|
||||||
<NDivider style="margin: 10px 0 10px 0" />
|
<NDivider style="margin: 10px 0 10px 0" />
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<NSpace justify="center">
|
<NSpace justify="center">
|
||||||
<NButton :disabled="isSelf" type="primary" :loading="isSending || !token" @click="SendQuestion">
|
<NButton
|
||||||
|
:disabled="isSelf"
|
||||||
|
type="primary"
|
||||||
|
:loading="isSending || !token"
|
||||||
|
@click="SendQuestion"
|
||||||
|
>
|
||||||
发送
|
发送
|
||||||
</NButton>
|
</NButton>
|
||||||
<NButton
|
<NButton
|
||||||
@@ -233,24 +259,46 @@ onUnmounted(() => {
|
|||||||
</NSpace>
|
</NSpace>
|
||||||
<VueTurnstile
|
<VueTurnstile
|
||||||
ref="turnstile"
|
ref="turnstile"
|
||||||
:site-key="TURNSTILE_KEY"
|
|
||||||
v-model="token"
|
v-model="token"
|
||||||
|
:site-key="TURNSTILE_KEY"
|
||||||
theme="auto"
|
theme="auto"
|
||||||
style="text-align: center"
|
style="text-align: center"
|
||||||
/>
|
/>
|
||||||
<NAlert v-if="isSelf" type="warning"> 不能给自己提问 </NAlert>
|
<NAlert
|
||||||
|
v-if="isSelf"
|
||||||
|
type="warning"
|
||||||
|
>
|
||||||
|
不能给自己提问
|
||||||
|
</NAlert>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
</NCard>
|
</NCard>
|
||||||
<NDivider> 公开回复 </NDivider>
|
<NDivider> 公开回复 </NDivider>
|
||||||
<NList v-if="publicQuestions.length > 0">
|
<NList v-if="publicQuestions.length > 0">
|
||||||
<NListItem v-for="item in publicQuestions" :key="item.id">
|
<NListItem
|
||||||
<NCard :embedded="!item.isReaded" hoverable size="small">
|
v-for="item in publicQuestions"
|
||||||
|
:key="item.id"
|
||||||
|
>
|
||||||
|
<NCard
|
||||||
|
:embedded="!item.isReaded"
|
||||||
|
hoverable
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<NSpace :size="0" align="center">
|
<NSpace
|
||||||
<NText depth="3" style="font-size: small">
|
:size="0"
|
||||||
|
align="center"
|
||||||
|
>
|
||||||
|
<NText
|
||||||
|
depth="3"
|
||||||
|
style="font-size: small"
|
||||||
|
>
|
||||||
<NTooltip>
|
<NTooltip>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<NTime :time="item.sendAt" :to="Date.now()" type="relative" />
|
<NTime
|
||||||
|
:time="item.sendAt"
|
||||||
|
:to="Date.now()"
|
||||||
|
type="relative"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
<NTime />
|
<NTime />
|
||||||
</NTooltip>
|
</NTooltip>
|
||||||
@@ -259,12 +307,29 @@ onUnmounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
<NCard style="text-align: center">
|
<NCard style="text-align: center">
|
||||||
{{ item.question.message }}
|
{{ item.question.message }}
|
||||||
<br />
|
<br>
|
||||||
<NImage v-if="item.question.image" :src="item.question.image" height="100" lazy />
|
<NImage
|
||||||
|
v-if="item.question.image"
|
||||||
|
:src="item.question.image"
|
||||||
|
height="100"
|
||||||
|
lazy
|
||||||
|
/>
|
||||||
</NCard>
|
</NCard>
|
||||||
<template v-if="item.answer" #footer>
|
<template
|
||||||
<NSpace align="center" :size="6" :wrap="false">
|
v-if="item.answer"
|
||||||
<NAvatar :src="AVATAR_URL + userInfo?.biliId + '?size=64'" circle :size="45" :img-props="{ referrerpolicy: 'no-referrer' }" />
|
#footer
|
||||||
|
>
|
||||||
|
<NSpace
|
||||||
|
align="center"
|
||||||
|
:size="6"
|
||||||
|
:wrap="false"
|
||||||
|
>
|
||||||
|
<NAvatar
|
||||||
|
:src="AVATAR_URL + userInfo?.biliId + '?size=64'"
|
||||||
|
circle
|
||||||
|
:size="45"
|
||||||
|
:img-props="{ referrerpolicy: 'no-referrer' }"
|
||||||
|
/>
|
||||||
<NDivider vertical />
|
<NDivider vertical />
|
||||||
<NText style="font-size: 16px">
|
<NText style="font-size: 16px">
|
||||||
{{ item.answer?.message }}
|
{{ item.answer?.message }}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<div>
|
||||||
<NSpin
|
<NSpin
|
||||||
v-if="isLoading"
|
v-if="isLoading"
|
||||||
show
|
show
|
||||||
@@ -37,6 +38,7 @@
|
|||||||
:config="selectedTemplateConfig"
|
:config="selectedTemplateConfig"
|
||||||
/>
|
/>
|
||||||
</NModal>
|
</NModal>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
@@ -135,7 +137,8 @@ import { computed, onMounted, ref, watch } from 'vue';
|
|||||||
await DownloadConfig(selectedTemplate.value!.settingName, props.userInfo?.id)
|
await DownloadConfig(selectedTemplate.value!.settingName, props.userInfo?.id)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.msg) {
|
if (data.msg) {
|
||||||
message.error('加载失败: ' + data.msg);
|
//message.error('加载失败: ' + data.msg);
|
||||||
|
console.log('当前模板没有配置, 使用默认配置');
|
||||||
} else {
|
} else {
|
||||||
currentConfig.value = data.data;
|
currentConfig.value = data.data;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<div>
|
||||||
<component
|
<component
|
||||||
:is="componentType"
|
:is="componentType"
|
||||||
:user-info="userInfo"
|
:user-info="userInfo"
|
||||||
:bili-info="biliInfo"
|
:bili-info="biliInfo"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ async function get() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<div>
|
||||||
<NSpin :show="isLoading">
|
<NSpin :show="isLoading">
|
||||||
<NFlex justify="center">
|
<NFlex justify="center">
|
||||||
<NEmpty
|
<NEmpty
|
||||||
@@ -60,4 +61,5 @@ async function get() {
|
|||||||
</NList>
|
</NList>
|
||||||
</NFlex>
|
</NFlex>
|
||||||
</NSpin>
|
</NSpin>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -472,10 +472,10 @@ export const Config = defineTemplateConfig([
|
|||||||
<!-- Social Links (Visible on Hover) -->
|
<!-- Social Links (Visible on Hover) -->
|
||||||
<div class="social-links">
|
<div class="social-links">
|
||||||
<p class="social-links-title">
|
<p class="social-links-title">
|
||||||
关于
|
关于我
|
||||||
</p>
|
</p>
|
||||||
<p class="social-links-subtitle">
|
<p class="social-links-subtitle">
|
||||||
{{ props.config?.longDescription }}
|
{{ props.config?.longDescription ?? '暂时没有填写介绍' }}
|
||||||
</p>
|
</p>
|
||||||
<div class="social-icons-bar">
|
<div class="social-icons-bar">
|
||||||
<!-- Add actual icons here -->
|
<!-- Add actual icons here -->
|
||||||
@@ -918,7 +918,7 @@ html.dark .filter-input::placeholder {
|
|||||||
top: 0; left: 0; right: 0; bottom: 0;
|
top: 0; left: 0; right: 0; bottom: 0;
|
||||||
backdrop-filter: blur(5px);
|
backdrop-filter: blur(5px);
|
||||||
-webkit-backdrop-filter: blur(5px);
|
-webkit-backdrop-filter: blur(5px);
|
||||||
background-color: rgba(0, 0, 0, 0.1); /* Optional overlay */
|
background-color: rgba(80, 80, 80, 0.1); /* Optional overlay */
|
||||||
border-radius: inherit; /* Inherit rounding */
|
border-radius: inherit; /* Inherit rounding */
|
||||||
z-index: 1; /* Below content */
|
z-index: 1; /* Below content */
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|||||||
Reference in New Issue
Block a user