mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-06 18:36:55 +08:00
feat: 更新积分历史和订单卡片组件
- 在 PointHistoryCard.vue 中优化积分来源标签显示逻辑 - 在 PointOrderCard.vue 中添加收货地址信息展示 - 修复开放平台弹幕排队无法加载的问题
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user