feat: 更新积分历史和订单卡片组件

- 在 PointHistoryCard.vue 中优化积分来源标签显示逻辑
- 在 PointOrderCard.vue 中添加收货地址信息展示
- 修复开放平台弹幕排队无法加载的问题
This commit is contained in:
2025-05-01 14:43:29 +08:00
parent e3ee1bcc26
commit ffa3b09bc4
3 changed files with 1060 additions and 1024 deletions

View File

@@ -96,7 +96,7 @@ const historyColumn: DataTableColumns<ResponsePointHisrotyModel> = [
case PointFrom.Manual: case PointFrom.Manual:
return h( return h(
NTag, NTag,
{ type: 'success', bordered: false, size: 'small' }, { type: row.point > 0 ? 'success' : 'error', bordered: false, size: 'small' },
() => '主播' + (row.point > 0 ? '赠予' : '扣除'), () => '主播' + (row.point > 0 ? '赠予' : '扣除'),
) )
case PointFrom.Use: case PointFrom.Use:
@@ -194,22 +194,40 @@ const historyColumn: DataTableColumns<ResponsePointHisrotyModel> = [
<template> <template>
<!-- 无数据时显示提示 --> <!-- 无数据时显示提示 -->
<NEmpty v-if="!histories || histories.length === 0" description="暂无积分历史记录" /> <NEmpty
v-if="!histories || histories.length === 0"
description="暂无积分历史记录"
/>
<!-- 有数据时显示表格 --> <!-- 有数据时显示表格 -->
<NDataTable v-else :columns="historyColumn" :data="histories" :pagination="{ <NDataTable
v-else
:columns="historyColumn"
:data="histories"
:pagination="{
showSizePicker: true, showSizePicker: true,
pageSizes: [10, 25, 50, 100], pageSizes: [10, 25, 50, 100],
defaultPageSize: 10, defaultPageSize: 10,
size: 'small' size: 'small'
}" /> }"
/>
<!-- 商品详情模态框 --> <!-- 商品详情模态框 -->
<NModal v-model:show="showGoodsModal" preset="card" title="礼物详情 (快照)" style="max-width: 400px; height: auto"> <NModal
v-model:show="showGoodsModal"
preset="card"
title="礼物详情 (快照)"
style="max-width: 400px; height: auto"
>
<PointGoodsItem :goods="currentGoods" /> <PointGoodsItem :goods="currentGoods" />
<template v-if="currentGoods?.content"> <template v-if="currentGoods?.content">
<NDivider>礼物内容</NDivider> <NDivider>礼物内容</NDivider>
<NInput :value="currentGoods?.content" type="textarea" readonly placeholder="无内容" /> <NInput
:value="currentGoods?.content"
type="textarea"
readonly
placeholder="无内容"
/>
</template> </template>
</NModal> </NModal>
</template> </template>

View File

@@ -721,6 +721,16 @@ onMounted(() => {
</NSpace> </NSpace>
</NCard> </NCard>
<!-- 收货地址信息 -->
<template v-if="orderDetail.type === GoodsTypes.Physical">
<NDivider>收货地址</NDivider>
<NCard
size="small"
class="address-info-card"
>
<AddressDisplay :address="orderDetail.address" />
</NCard>
</template>
<!-- 快递信息 --> <!-- 快递信息 -->
<template v-if="orderDetail.status === PointOrderStatus.Shipped && orderDetail.instanceOf === 'owner' && orderDetail.type === GoodsTypes.Physical"> <template v-if="orderDetail.status === PointOrderStatus.Shipped && orderDetail.instanceOf === 'owner' && orderDetail.type === GoodsTypes.Physical">
@@ -939,6 +949,10 @@ onMounted(() => {
box-shadow: 0 2px 8px var(--n-box-shadow-color); box-shadow: 0 2px 8px var(--n-box-shadow-color);
} }
.address-info-card {
margin-bottom: 16px;
}
@media (max-width: 768px) { @media (max-width: 768px) {
.order-detail-modal { .order-detail-modal {
max-width: 95vw; max-width: 95vw;

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { AddBiliBlackList, SaveEnableFunctions, SaveSetting, useAccount } from '@/api/account'; import { AddBiliBlackList, SaveEnableFunctions, SaveSetting, useAccount } from '@/api/account';
import { import {
DanmakuUserInfo, DanmakuUserInfo,
EventDataTypes, EventDataTypes,
EventModel, EventModel,
@@ -13,11 +13,11 @@
QueueStatus, QueueStatus,
ResponseQueueModel, ResponseQueueModel,
Setting_Queue, Setting_Queue,
} from '@/api/api-models'; } from '@/api/api-models';
import { QueryGetAPI, QueryPostAPI, QueryPostAPIWithParams } from '@/api/query'; import { QueryGetAPI, QueryPostAPI, QueryPostAPIWithParams } from '@/api/query';
import { CURRENT_HOST, QUEUE_API_URL } from '@/data/constants'; // CURRENT_HOST 用于 OBS Modal import { CURRENT_HOST, QUEUE_API_URL } from '@/data/constants'; // CURRENT_HOST 用于 OBS Modal
import { useDanmakuClient } from '@/store/useDanmakuClient'; import { useDanmakuClient } from '@/store/useDanmakuClient';
import { import {
Checkmark12Regular, Checkmark12Regular,
ClipboardTextLtr24Filled, ClipboardTextLtr24Filled,
Delete24Filled, Delete24Filled,
@@ -25,12 +25,12 @@
Info24Filled, Info24Filled,
PeopleQueue24Filled, PeopleQueue24Filled,
PresenceBlocked16Regular, PresenceBlocked16Regular,
} from '@vicons/fluent'; } from '@vicons/fluent';
import { ReloadCircleSharp } from '@vicons/ionicons5'; import { ReloadCircleSharp } from '@vicons/ionicons5';
import { useStorage } from '@vueuse/core'; import { useStorage } from '@vueuse/core';
import { isSameDay } from 'date-fns'; import { isSameDay } from 'date-fns';
import { List } from 'linqts'; import { List } from 'linqts';
import { import {
DataTableColumns, DataTableColumns,
NAlert, NAlert,
NButton, NButton,
@@ -66,14 +66,14 @@
NUl, NUl,
useMessage, useMessage,
useNotification, useNotification,
} from 'naive-ui'; } from 'naive-ui';
import { computed, h, onActivated, onDeactivated, onMounted, onUnmounted, ref, VNodeChild, CSSProperties } from 'vue'; import { computed, h, onActivated, onDeactivated, onMounted, onUnmounted, ref, VNodeChild, CSSProperties } from 'vue';
// import { useRoute } from 'vue-router' // 未使用 // import { useRoute } from 'vue-router' // 未使用
import QueueOBS from '../obs/QueueOBS.vue'; import QueueOBS from '../obs/QueueOBS.vue';
import { copyToClipboard } from '@/Utils'; import { copyToClipboard } from '@/Utils';
// 默认队列设置 // 默认队列设置
const defaultSettings = { const defaultSettings = {
keyword: '排队', keyword: '排队',
enableOnStreaming: false, enableOnStreaming: false,
queueMaxSize: 10, queueMaxSize: 10,
@@ -103,34 +103,34 @@
showPayment: true, showPayment: true,
sendGiftDirectJoin: true, sendGiftDirectJoin: true,
sendGiftIgnoreLimit: false, sendGiftIgnoreLimit: false,
} as Setting_Queue; } as Setting_Queue;
// 队列状态映射 // 队列状态映射
const STATUS_MAP = { const STATUS_MAP = {
[QueueStatus.Waiting]: '等待中', [QueueStatus.Waiting]: '等待中',
[QueueStatus.Progressing]: '处理中', [QueueStatus.Progressing]: '处理中',
[QueueStatus.Finish]: '已完成', [QueueStatus.Finish]: '已完成',
[QueueStatus.Cancel]: '已取消', [QueueStatus.Cancel]: '已取消',
}; };
// const route = useRoute() // 未使用 // const route = useRoute() // 未使用
const accountInfo = useAccount(); const accountInfo = useAccount();
const message = useMessage(); const message = useMessage();
const notice = useNotification(); const notice = useNotification();
const client = await useDanmakuClient().initOpenlive(); // 初始化弹幕客户端 const client = await useDanmakuClient().initOpenlive(); // 初始化弹幕客户端
const isWarnMessageAutoClose = useStorage('Queue.Settings.WarnMessageAutoClose', false); // 警告消息是否自动关闭 const isWarnMessageAutoClose = useStorage('Queue.Settings.WarnMessageAutoClose', false); // 警告消息是否自动关闭
const isReverse = useStorage('Queue.Settings.Reverse', false); // 本地存储的倒序设置 (未登录时使用) const isReverse = useStorage('Queue.Settings.Reverse', false); // 本地存储的倒序设置 (未登录时使用)
// const volumn = useStorage('Settings.Volumn', 0.5) // 未使用 // const volumn = useStorage('Settings.Volumn', 0.5) // 未使用
const isLoading = ref(false); // 加载状态 const isLoading = ref(false); // 加载状态
const showOBSModal = ref(false); // OBS 组件模态框显示状态 const showOBSModal = ref(false); // OBS 组件模态框显示状态
const filterName = ref(''); // 历史记录筛选用户名 const filterName = ref(''); // 历史记录筛选用户名
const filterNameContains = ref(false); // 历史记录筛选是否包含 const filterNameContains = ref(false); // 历史记录筛选是否包含
// 队列设置 (登录后使用账户设置, 否则使用默认设置) // 队列设置 (登录后使用账户设置, 否则使用默认设置)
const settings = computed({ const settings = computed({
get: () => { get: () => {
if (accountInfo.value.id) { if (accountInfo.value.id) {
return accountInfo.value.settings.queue; return accountInfo.value.settings.queue;
@@ -142,18 +142,18 @@
accountInfo.value.settings.queue = value; accountInfo.value.settings.queue = value;
} }
}, },
}); });
// Props 定义 (虽然未在逻辑中直接使用,但可能由父组件传入或用于类型检查) // Props 定义 (虽然未在逻辑中直接使用,但可能由父组件传入或用于类型检查)
const props = defineProps<{ const props = defineProps<{
roomInfo?: OpenLiveInfo; roomInfo?: OpenLiveInfo;
code?: string | undefined; code?: string | undefined;
isOpenLive?: boolean; isOpenLive?: boolean;
}>(); }>();
const localQueues = useStorage('Local.Queue', [] as ResponseQueueModel[]); // 本地存储的队列 (未登录时使用) const localQueues = useStorage('Local.Queue', [] as ResponseQueueModel[]); // 本地存储的队列 (未登录时使用)
const originQueue = ref<ResponseQueueModel[]>([]); // 从 API 获取或本地存储的原始队列数据 const originQueue = ref<ResponseQueueModel[]>([]); // 从 API 获取或本地存储的原始队列数据
const queue = computed(() => { // 当前显示的活动队列 (过滤、排序后) const queue = computed(() => { // 当前显示的活动队列 (过滤、排序后)
let list = new List(accountInfo.value ? originQueue.value : localQueues.value) let list = new List(accountInfo.value ? originQueue.value : localQueues.value)
.Where( // 按用户名筛选 .Where( // 按用户名筛选
(q) => (q) =>
@@ -195,26 +195,26 @@
// 将处理中的项置顶 // 将处理中的项置顶
list = list.OrderByDescending((q) => (q.status == QueueStatus.Progressing ? 1 : 0)); list = list.OrderByDescending((q) => (q.status == QueueStatus.Progressing ? 1 : 0));
return list.ToArray(); return list.ToArray();
}); });
const historySongs = computed(() => { // 历史队列 (已完成或取消) const historySongs = computed(() => { // 历史队列 (已完成或取消)
return (accountInfo.value ? originQueue.value : localQueues.value) return (accountInfo.value ? originQueue.value : localQueues.value)
.filter((song) => { .filter((song) => {
return song.status == QueueStatus.Finish || song.status == QueueStatus.Cancel; return song.status == QueueStatus.Finish || song.status == QueueStatus.Cancel;
}) })
.sort((a, b) => (b.finishAt ?? b.createAt) - (a.finishAt ?? a.createAt)); // 按完成/创建时间降序 .sort((a, b) => (b.finishAt ?? b.createAt) - (a.finishAt ?? a.createAt)); // 按完成/创建时间降序
}); });
const newQueueName = ref(''); // 手动添加的用户名 const newQueueName = ref(''); // 手动添加的用户名
const defaultKeyword = useStorage('Settings.Queue.DefaultKeyword', '排队'); // 本地存储的默认关键词 const defaultKeyword = useStorage('Settings.Queue.DefaultKeyword', '排队'); // 本地存储的默认关键词
const configCanEdit = computed(() => { // 配置是否可编辑 (是否已登录) const configCanEdit = computed(() => { // 配置是否可编辑 (是否已登录)
return accountInfo.value != null && accountInfo.value != undefined; return accountInfo.value != null && accountInfo.value != undefined;
}); });
const table = ref(); // NDataTable 引用 const table = ref(); // NDataTable 引用
// 获取所有队列数据 // 获取所有队列数据
async function getAll() { async function getAll() {
if (accountInfo.value.id) { if (accountInfo.value.id) {
try { try {
isLoading.value = true; isLoading.value = true;
@@ -239,14 +239,10 @@
// 未登录时返回本地数据 // 未登录时返回本地数据
return localQueues.value; return localQueues.value;
} }
} }
// 尝试添加队列 (处理弹幕、礼物事件) // 尝试添加队列 (处理弹幕、礼物事件)
async function add(danmaku: EventModel) { async function add(danmaku: EventModel) {
// 检查功能是否启用
if (!accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.Queue)) {
return;
}
// 检查消息是否符合加入条件 // 检查消息是否符合加入条件
if (!checkMessage(danmaku)) { if (!checkMessage(danmaku)) {
return; return;
@@ -259,6 +255,10 @@
} }
if (accountInfo.value.id) { // 已登录,调用 API if (accountInfo.value.id) { // 已登录,调用 API
// 检查功能是否启用
if (!accountInfo.value?.settings.enableFunctions.includes(FunctionTypes.Queue)) {
return;
}
try { try {
const data = await QueryPostAPI<ResponseQueueModel>(QUEUE_API_URL + 'try-add', danmaku); const data = await QueryPostAPI<ResponseQueueModel>(QUEUE_API_URL + 'try-add', danmaku);
if (data.code == 200) { if (data.code == 200) {
@@ -313,10 +313,10 @@
localQueues.value.unshift(songData); // 添加到本地队列开头 localQueues.value.unshift(songData); // 添加到本地队列开头
message.success(`[${danmaku.uname}] 添加至本地队列`); message.success(`[${danmaku.uname}] 添加至本地队列`);
} }
} }
// 手动添加用户至队列 // 手动添加用户至队列
async function addManual() { async function addManual() {
if (!newQueueName.value) { if (!newQueueName.value) {
message.error('请输入用户名'); message.error('请输入用户名');
return; return;
@@ -352,10 +352,10 @@
message.success(`已手动添加用户至队列: ${newQueueName.value}`); message.success(`已手动添加用户至队列: ${newQueueName.value}`);
newQueueName.value = ''; newQueueName.value = '';
} }
} }
// 更新队列状态 // 更新队列状态
async function updateStatus(queueData: ResponseQueueModel, status: QueueStatus) { async function updateStatus(queueData: ResponseQueueModel, status: QueueStatus) {
if (!configCanEdit.value) { // 未登录,直接修改本地状态 if (!configCanEdit.value) { // 未登录,直接修改本地状态
const localItem = localQueues.value.find(q => q.id === queueData.id); const localItem = localQueues.value.find(q => q.id === queueData.id);
if (localItem) { if (localItem) {
@@ -395,19 +395,19 @@
} finally { } finally {
isLoading.value = false; isLoading.value = false;
} }
} }
// 弹幕事件处理 // 弹幕事件处理
function onGetDanmaku(danmaku: EventModel) { function onGetDanmaku(danmaku: EventModel) {
add(danmaku); add(danmaku);
} }
// 礼物事件处理 // 礼物事件处理
function onGetGift(danmaku: EventModel) { function onGetGift(danmaku: EventModel) {
add(danmaku); add(danmaku);
} }
// 检查消息是否符合加入队列的条件 // 检查消息是否符合加入队列的条件
function checkMessage(eventData: EventModel): boolean { function checkMessage(eventData: EventModel): boolean {
// 未登录时,如果用户已在本地队列,则不允许重复添加 (简单检查) // 未登录时,如果用户已在本地队列,则不允许重复添加 (简单检查)
if (!configCanEdit.value && localQueues.value.some((q) => q.user?.uid == eventData.uid && q.status < QueueStatus.Finish)) { if (!configCanEdit.value && localQueues.value.some((q) => q.user?.uid == eventData.uid && q.status < QueueStatus.Finish)) {
console.log(`[OPEN-LIVE-QUEUE] 本地队列已存在用户 [${eventData.uname}],跳过`); console.log(`[OPEN-LIVE-QUEUE] 本地队列已存在用户 [${eventData.uname}],跳过`);
@@ -480,10 +480,10 @@
return false; return false;
} }
} }
} }
// 更新功能启用状态 // 更新功能启用状态
async function onUpdateFunctionEnable() { async function onUpdateFunctionEnable() {
if (accountInfo.value.id) { if (accountInfo.value.id) {
const oldValue = JSON.parse(JSON.stringify(accountInfo.value.settings.enableFunctions)); const oldValue = JSON.parse(JSON.stringify(accountInfo.value.settings.enableFunctions));
const isEnabling = !accountInfo.value.settings.enableFunctions.includes(FunctionTypes.Queue); const isEnabling = !accountInfo.value.settings.enableFunctions.includes(FunctionTypes.Queue);
@@ -522,10 +522,10 @@
console.error(`[OPEN-LIVE-QUEUE] 更新功能状态失败:`, err); console.error(`[OPEN-LIVE-QUEUE] 更新功能状态失败:`, err);
} }
} }
} }
// 更新设置 // 更新设置
async function updateSettings() { async function updateSettings() {
if (accountInfo.value.id) { if (accountInfo.value.id) {
isLoading.value = true; isLoading.value = true;
try { try {
@@ -544,10 +544,10 @@
} else { } else {
message.success('本地设置已更新 (未登录)'); // 对于未登录用户,设置是响应式的,无需显式保存 message.success('本地设置已更新 (未登录)'); // 对于未登录用户,设置是响应式的,无需显式保存
} }
} }
// 删除队列记录 // 删除队列记录
async function deleteQueue(values: ResponseQueueModel[]) { async function deleteQueue(values: ResponseQueueModel[]) {
if (!values || values.length === 0) return; if (!values || values.length === 0) return;
if (accountInfo.value.id) { // 已登录,调用 API if (accountInfo.value.id) { // 已登录,调用 API
@@ -574,10 +574,10 @@
localQueues.value = localQueues.value.filter(q => !idsToDelete.includes(q.id)); localQueues.value = localQueues.value.filter(q => !idsToDelete.includes(q.id));
message.success(`成功删除 ${values.length} 条本地记录`); message.success(`成功删除 ${values.length} 条本地记录`);
} }
} }
// 取消所有活动队列项 // 取消所有活动队列项
async function deactiveAllSongs() { async function deactiveAllSongs() {
if (accountInfo.value.id) { // 已登录,调用 API if (accountInfo.value.id) { // 已登录,调用 API
isLoading.value = true; isLoading.value = true;
try { try {
@@ -609,10 +609,10 @@
}); });
message.success('已全部取消本地活动队列'); message.success('已全部取消本地活动队列');
} }
} }
// 状态筛选选项 // 状态筛选选项
const statusFilterOptions = computed(() => { const statusFilterOptions = computed(() => {
return Object.values(QueueStatus) return Object.values(QueueStatus)
.filter((t): t is QueueStatus => typeof t === 'number') // 确保是数字枚举值 .filter((t): t is QueueStatus => typeof t === 'number') // 确保是数字枚举值
.map((t) => { .map((t) => {
@@ -621,10 +621,10 @@
value: t, value: t,
}; };
}); });
}); });
// 历史记录表格列定义 // 历史记录表格列定义
const columns = computed<DataTableColumns<ResponseQueueModel>>(() => [ const columns = computed<DataTableColumns<ResponseQueueModel>>(() => [
{ {
title: '用户名', title: '用户名',
key: 'user.name', key: 'user.name',
@@ -796,24 +796,24 @@
return h(NSpace, { justify: 'center', size: 4 }, () => buttons); // 减小间距 return h(NSpace, { justify: 'center', size: 4 }, () => buttons); // 减小间距
}, },
}, },
]); ]);
// 用于标记历史记录表格中正在操作的行,以显示 loading // 用于标记历史记录表格中正在操作的行,以显示 loading
const queueDataBeingManaged = ref<number | null>(null); const queueDataBeingManaged = ref<number | null>(null);
// 监听筛选条件变化,手动触发 NDataTable 筛选 // 监听筛选条件变化,手动触发 NDataTable 筛选
watch([filterName, filterNameContains], () => { watch([filterName, filterNameContains], () => {
if (table.value) { if (table.value) {
// 更新第一列的 filterOptionValue 来触发筛选 // 更新第一列的 filterOptionValue 来触发筛选
const cols = table.value.columns; const cols = table.value.columns;
cols[0].filterOptionValue = filterName.value + filterNameContains.value.toString(); cols[0].filterOptionValue = filterName.value + filterNameContains.value.toString();
table.value.filter(cols[0]); table.value.filter(cols[0]);
} }
}); });
// 获取舰长等级对应的颜色 // 获取舰长等级对应的颜色
function GetGuardColor(level: number | null | undefined): string { function GetGuardColor(level: number | null | undefined): string {
if (level) { if (level) {
switch (level) { switch (level) {
case 1: return 'rgb(122, 4, 35)'; // 总督 case 1: return 'rgb(122, 4, 35)'; // 总督
@@ -822,10 +822,10 @@
} }
} }
return '#999'; // 默认颜色或无舰长 return '#999'; // 默认颜色或无舰长
} }
// 定时更新活动队列信息 (增量更新) // 定时更新活动队列信息 (增量更新)
async function updateActive() { async function updateActive() {
if (!accountInfo.value.id) return; // 未登录则不执行 if (!accountInfo.value.id) return; // 未登录则不执行
try { try {
const data = await QueryGetAPI<ResponseQueueModel[]>(QUEUE_API_URL + 'get-active', { const data = await QueryGetAPI<ResponseQueueModel[]>(QUEUE_API_URL + 'get-active', {
@@ -881,10 +881,10 @@
} catch (err: any) { } catch (err: any) {
console.warn('[OPEN-LIVE-Queue] 更新活动队列失败:', err.message || err); console.warn('[OPEN-LIVE-Queue] 更新活动队列失败:', err.message || err);
} }
} }
// 拉黑用户 (仅限弹幕来源) // 拉黑用户 (仅限弹幕来源)
function blockUser(item: ResponseQueueModel) { function blockUser(item: ResponseQueueModel) {
if (item.from != QueueFrom.Danmaku && item.from != QueueFrom.Gift) { // 允许拉黑礼物用户 if (item.from != QueueFrom.Danmaku && item.from != QueueFrom.Gift) { // 允许拉黑礼物用户
message.error(`[${item.user?.name}] 不是来自弹幕或礼物的用户,无法拉黑`); message.error(`[${item.user?.name}] 不是来自弹幕或礼物的用户,无法拉黑`);
return; return;
@@ -912,14 +912,14 @@
} else { } else {
message.error(`用户 [${item.user?.name}] 没有有效的 UID无法拉黑`); message.error(`用户 [${item.user?.name}] 没有有效的 UID无法拉黑`);
} }
} }
let timer: any; // 用于更新相对时间的计时器 let timer: any; // 用于更新相对时间的计时器
let updateActiveTimer: any; // 用于轮询活动队列的计时器 let updateActiveTimer: any; // 用于轮询活动队列的计时器
const updateKey = ref(0); // 用于强制更新 NTime 组件 const updateKey = ref(0); // 用于强制更新 NTime 组件
// 初始化操作 // 初始化操作
async function init() { async function init() {
dispose(); // 先清理旧的计时器 dispose(); // 先清理旧的计时器
// 如果登录了,获取一次全量数据 // 如果登录了,获取一次全量数据
if (accountInfo.value.id) { if (accountInfo.value.id) {
@@ -932,18 +932,18 @@
updateActiveTimer = setInterval(() => { updateActiveTimer = setInterval(() => {
updateActive(); // 定期更新活动队列 updateActive(); // 定期更新活动队列
}, 5000); // 轮询间隔调整为 5 秒 }, 5000); // 轮询间隔调整为 5 秒
} }
// 清理操作 // 清理操作
function dispose() { function dispose() {
clearInterval(timer); clearInterval(timer);
clearInterval(updateActiveTimer); clearInterval(updateActiveTimer);
timer = null; timer = null;
updateActiveTimer = null; updateActiveTimer = null;
} }
// --- 生命周期钩子 --- // --- 生命周期钩子 ---
onMounted(async () => { onMounted(async () => {
// 挂载时初始化 // 挂载时初始化
if (accountInfo.value.id) { if (accountInfo.value.id) {
// 如果已登录,同步一次设置到本地状态 (虽然 computed 会处理,但显式同步更清晰) // 如果已登录,同步一次设置到本地状态 (虽然 computed 会处理,但显式同步更清晰)
@@ -953,27 +953,27 @@
client.onEvent('danmaku', onGetDanmaku); client.onEvent('danmaku', onGetDanmaku);
client.onEvent('gift', onGetGift); client.onEvent('gift', onGetGift);
await init(); // 初始化数据和定时器 await init(); // 初始化数据和定时器
}); });
onActivated(async () => { onActivated(async () => {
// 组件被 keep-alive 激活时重新初始化 // 组件被 keep-alive 激活时重新初始化
await init(); await init();
}); });
onDeactivated(() => { onDeactivated(() => {
// 组件被 keep-alive 停用时清理定时器 // 组件被 keep-alive 停用时清理定时器
dispose(); dispose();
}); });
onUnmounted(() => { onUnmounted(() => {
// 组件卸载时彻底清理 // 组件卸载时彻底清理
client.offEvent('danmaku', onGetDanmaku); client.offEvent('danmaku', onGetDanmaku);
client.offEvent('gift', onGetGift); client.offEvent('gift', onGetGift);
dispose(); dispose();
}); });
// --- 辅助函数 --- // --- 辅助函数 ---
function getIndexStyle(status: QueueStatus): CSSProperties { function getIndexStyle(status: QueueStatus): CSSProperties {
// 基础颜色定义 - 扁平化风格 // 基础颜色定义 - 扁平化风格
let backgroundColor; let backgroundColor;
@@ -1010,7 +1010,7 @@
}; };
return style; return style;
} }
</script> </script>
<template> <template>
@@ -1090,7 +1090,7 @@
<NCard style="margin-top: 10px;"> <NCard style="margin-top: 10px;">
<!-- 主内容区域 --> <!-- 主内容区域 -->
<NTabs <NTabs
v-if="!accountInfo || accountInfo.settings.enableFunctions.includes(FunctionTypes.Queue)" v-if="!accountInfo.id || accountInfo.settings.enableFunctions.includes(FunctionTypes.Queue)"
type="line" type="line"
animated animated
display-directive="show:lazy" display-directive="show:lazy"
@@ -1895,43 +1895,45 @@
</template> </template>
<style> <style>
/* 处理中状态的边框动画 */ /* 处理中状态的边框动画 */
@keyframes animated-border { @keyframes animated-border {
0% { 0% {
box-shadow: 0 0 0 0px rgba(103, 194, 58, 0.7); box-shadow: 0 0 0 0px rgba(103, 194, 58, 0.7);
} }
70% { 70% {
box-shadow: 0 0 0 5px rgba(103, 194, 58, 0); box-shadow: 0 0 0 5px rgba(103, 194, 58, 0);
} }
100% { 100% {
box-shadow: 0 0 0 0px rgba(103, 194, 58, 0); box-shadow: 0 0 0 0px rgba(103, 194, 58, 0);
} }
} }
/* 处理中状态的卡片左边框或标签动画 */ /* 处理中状态的卡片左边框或标签动画 */
.n-card[style*="border-left: 4px solid #63e2b7;"], .n-card[style*="border-left: 4px solid #63e2b7;"],
.n-tag--success[style*="animation: animated-border"] { .n-tag--success[style*="animation: animated-border"] {
animation: animated-border 1.5s infinite; animation: animated-border 1.5s infinite;
} }
/* 优化 NDataTable 内容过长时的显示 */ /* 优化 NDataTable 内容过长时的显示 */
.n-data-table-td { .n-data-table-td {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
/* 序号悬停效果 - 扁平化风格 */ /* 序号悬停效果 - 扁平化风格 */
.queue-index:hover { .queue-index:hover {
opacity: 0.85; opacity: 0.85;
} }
/* 处理中状态的序号动画 - 扁平化风格 */ /* 处理中状态的序号动画 - 扁平化风格 */
.queue-index-processing { .queue-index-processing {
position: relative; position: relative;
} }
.queue-index-processing::after { .queue-index-processing::after {
content: ''; content: '';
position: absolute; position: absolute;
top: -2px; top: -2px;
@@ -1942,20 +1944,22 @@
border: 2px solid #18a058; border: 2px solid #18a058;
opacity: 0; opacity: 0;
animation: flat-pulse 2s infinite; animation: flat-pulse 2s infinite;
} }
@keyframes flat-pulse { @keyframes flat-pulse {
0% { 0% {
transform: scale(1); transform: scale(1);
opacity: 0.7; opacity: 0.7;
} }
70% { 70% {
transform: scale(1.1); transform: scale(1.1);
opacity: 0; opacity: 0;
} }
100% { 100% {
transform: scale(1.1); transform: scale(1.1);
opacity: 0; opacity: 0;
} }
} }
</style> </style>