diff --git a/src/client/store/autoAction/modules/followThank.ts b/src/client/store/autoAction/modules/followThank.ts index 51fcc8c..8707d1d 100644 --- a/src/client/store/autoAction/modules/followThank.ts +++ b/src/client/store/autoAction/modules/followThank.ts @@ -90,14 +90,6 @@ export function useFollowThank( } } - /** - * 处理关注事件 - 旧方式实现,用于兼容现有代码 - */ - function onFollow(event: EventModel) { - // 将在useAutoAction.ts中进行迁移,此方法保留但不实现具体逻辑 - console.log('关注事件处理已迁移到新的AutoActionItem结构'); - } - /** * 清理计时器 */ @@ -109,7 +101,6 @@ export function useFollowThank( } return { - onFollow, processFollow, clearTimer }; diff --git a/src/client/store/autoAction/modules/guardPm.ts b/src/client/store/autoAction/modules/guardPm.ts index 0c3619a..ef64499 100644 --- a/src/client/store/autoAction/modules/guardPm.ts +++ b/src/client/store/autoAction/modules/guardPm.ts @@ -155,17 +155,8 @@ export function useGuardPm( } } - /** - * 处理上舰事件 - 旧方式实现,用于兼容现有代码 - */ - function onGuard(event: EventModel) { - // 将在useAutoAction.ts中进行迁移,此方法保留但不实现具体逻辑 - console.log('舰长事件处理已迁移到新的AutoActionItem结构'); - } - return { config, - onGuard, processGuard }; } \ No newline at end of file diff --git a/src/client/store/autoAction/modules/scheduledDanmaku.ts b/src/client/store/autoAction/modules/scheduledDanmaku.ts index 06dddbe..34d5ad0 100644 --- a/src/client/store/autoAction/modules/scheduledDanmaku.ts +++ b/src/client/store/autoAction/modules/scheduledDanmaku.ts @@ -73,20 +73,6 @@ export function useScheduledDanmaku( }); } - /** - * 启动定时弹幕 (旧方式) - */ - function startScheduledDanmaku() { - console.log('定时弹幕已迁移到新的AutoActionItem结构'); - } - - /** - * 停止定时弹幕 (旧方式) - */ - function stopScheduledDanmaku() { - console.log('定时弹幕已迁移到新的AutoActionItem结构'); - } - /** * 格式化剩余时间为分:秒格式 */ @@ -111,8 +97,6 @@ export function useScheduledDanmaku( } return { - startScheduledDanmaku, - stopScheduledDanmaku, processScheduledActions, clearTimer, remainingSeconds, diff --git a/src/client/store/useAutoAction.ts b/src/client/store/useAutoAction.ts index 5b707a9..0b0c055 100644 --- a/src/client/store/useAutoAction.ts +++ b/src/client/store/useAutoAction.ts @@ -55,14 +55,12 @@ export const useAutoAction = defineStore('autoAction', () => { (roomId: number, message: string) => biliFunc.sendLiveDanmaku(roomId, message) ); - // @ts-ignore - 忽略类型错误以保持功能正常 const guardPm = useGuardPm( isLive, roomId, (userId: number, message: string) => biliFunc.sendPrivateMessage(userId, message) ); - // @ts-ignore - 忽略类型错误以保持功能正常 const followThank = useFollowThank( isLive, roomId, @@ -70,7 +68,6 @@ export const useAutoAction = defineStore('autoAction', () => { (roomId: number, message: string) => biliFunc.sendLiveDanmaku(roomId, message) ); - // @ts-ignore - 忽略类型错误以保持功能正常 const entryWelcome = useEntryWelcome( isLive, roomId, @@ -78,14 +75,12 @@ export const useAutoAction = defineStore('autoAction', () => { (roomId: number, message: string) => biliFunc.sendLiveDanmaku(roomId, message) ); - // @ts-ignore - 忽略类型错误以保持功能正常 const autoReply = useAutoReply( isLive, roomId, (roomId: number, message: string) => biliFunc.sendLiveDanmaku(roomId, message) ); - // @ts-ignore - 忽略类型错误以保持功能正常 const scheduledDanmaku = useScheduledDanmaku( isLive, roomId, @@ -168,11 +163,11 @@ export const useAutoAction = defineStore('autoAction', () => { break; case TriggerType.GUARD: - guardPm.onGuard(event); + guardPm.processGuard(event, autoActions.value, runtimeState.value); break; case TriggerType.FOLLOW: - followThank.onFollow(event); + followThank.processFollow(event, autoActions.value, runtimeState.value); break; case TriggerType.ENTER: @@ -366,7 +361,7 @@ export const useAutoAction = defineStore('autoAction', () => { if (!roomId.value) return; // 使用专用模块处理定时发送 - scheduledDanmaku.startScheduledDanmaku(); + scheduledDanmaku.processScheduledActions(autoActions.value, runtimeState.value); // 同时处理自定义的定时任务 const scheduledActions = autoActions.value.filter( @@ -382,37 +377,33 @@ export const useAutoAction = defineStore('autoAction', () => { const intervalSeconds = action.triggerConfig.intervalSeconds || 300; // 默认5分钟 const timerFunc = () => { - if (!isLive.value && action.triggerConfig.onlyDuringLive) { - // 如果设置了仅直播时发送,且当前未直播,则跳过 - return; + // 仅在检查时判断直播状态,不停止定时器 + const shouldExecute = + !action.triggerConfig.onlyDuringLive || isLive.value; + + if (shouldExecute && !(action.triggerConfig.ignoreTianXuan && isTianXuanActive.value)) { + // 创建执行上下文 + const context: ExecutionContext = { + roomId: roomId.value, + variables: { + date: { + formatted: new Date().toLocaleString(), + year: new Date().getFullYear(), + month: new Date().getMonth() + 1, + day: new Date().getDate(), + hour: new Date().getHours(), + minute: new Date().getMinutes(), + second: new Date().getSeconds(), + } + }, + timestamp: Date.now() + }; + + // 执行定时操作 + executeAction(action, context); } - if (action.triggerConfig.ignoreTianXuan && isTianXuanActive.value) { - // 如果设置了天选时刻不发送,且当前有天选,则跳过 - return; - } - - // 创建执行上下文 - const context: ExecutionContext = { - roomId: roomId.value, - variables: { - date: { - formatted: new Date().toLocaleString(), - year: new Date().getFullYear(), - month: new Date().getMonth() + 1, - day: new Date().getDate(), - hour: new Date().getHours(), - minute: new Date().getMinutes(), - second: new Date().getSeconds(), - } - }, - timestamp: Date.now() - }; - - // 执行定时操作 - executeAction(action, context); - - // 设置下一次执行 + // 无论是否执行,都设置下一次定时 runtimeState.value.scheduledTimers[action.id] = setTimeout(timerFunc, intervalSeconds * 1000); }; @@ -465,8 +456,48 @@ export const useAutoAction = defineStore('autoAction', () => { // 如果是定时操作,重新配置定时器 if (action.triggerType === TriggerType.SCHEDULED) { if (enabled) { - // 启用时重新启动定时器 - startScheduledActions(); + // 如果已有定时器,先清理 + if (runtimeState.value.scheduledTimers[id]) { + clearTimeout(runtimeState.value.scheduledTimers[id]!); + runtimeState.value.scheduledTimers[id] = null; + } + + // 启用时单独启动这个定时器 + const intervalSeconds = action.triggerConfig.intervalSeconds || 300; + + const timerFunc = () => { + // 仅在检查时判断直播状态,不停止定时器 + const shouldExecute = + !action.triggerConfig.onlyDuringLive || isLive.value; + + if (shouldExecute && !(action.triggerConfig.ignoreTianXuan && isTianXuanActive.value)) { + // 创建执行上下文 + const context: ExecutionContext = { + roomId: roomId.value, + variables: { + date: { + formatted: new Date().toLocaleString(), + year: new Date().getFullYear(), + month: new Date().getMonth() + 1, + day: new Date().getDate(), + hour: new Date().getHours(), + minute: new Date().getMinutes(), + second: new Date().getSeconds(), + } + }, + timestamp: Date.now() + }; + + // 执行定时操作 + executeAction(action, context); + } + + // 无论是否执行,都设置下一次定时 + runtimeState.value.scheduledTimers[id] = setTimeout(timerFunc, intervalSeconds * 1000); + }; + + // 启动定时器 + runtimeState.value.scheduledTimers[id] = setTimeout(timerFunc, intervalSeconds * 1000); } else if (runtimeState.value.scheduledTimers[id]) { // 禁用时清理定时器 clearTimeout(runtimeState.value.scheduledTimers[id]!); @@ -484,14 +515,14 @@ export const useAutoAction = defineStore('autoAction', () => { // 启动所有定时发送任务 startScheduledActions(); - // 监听直播状态变化,自动启停定时任务 - watch(isLive, (newIsLive) => { + // 不再根据直播状态停止定时任务,只在回调中判断 + /*watch(isLive, (newIsLive) => { if (newIsLive) { startScheduledActions(); } else { stopAllScheduledActions(); } - }); + });*/ // 安全地订阅事件 try { @@ -510,115 +541,6 @@ export const useAutoAction = defineStore('autoAction', () => { clearAllTimers(); }); } - - // 迁移旧的配置 - migrateAutoReplyConfig(); - migrateGiftThankConfig(); - } - - /** - * 迁移旧的自动回复配置到新的AutoActionItem格式 - */ - function migrateAutoReplyConfig() { - try { - // 尝试从localStorage获取旧配置 - const oldConfigStr = localStorage.getItem('autoAction.autoReplyConfig'); - if (!oldConfigStr) return; - - const oldConfig = JSON.parse(oldConfigStr); - if (!oldConfig.enabled || !oldConfig.rules || !Array.isArray(oldConfig.rules)) return; - - // 检查是否已经迁移过(防止重复迁移) - const migratedKey = 'autoAction.autoReplyMigrated'; - if (localStorage.getItem(migratedKey) === 'true') return; - - // 将旧规则转换为新的AutoActionItem - const newItems = oldConfig.rules.map((rule: any) => { - const item = createDefaultAutoAction(TriggerType.DANMAKU); - item.name = `弹幕回复: ${rule.keywords.join(',')}`; - item.enabled = oldConfig.enabled; - item.templates = rule.replies || ['感谢您的弹幕']; - item.triggerConfig = { - ...item.triggerConfig, - keywords: rule.keywords || [], - blockwords: rule.blockwords || [], - onlyDuringLive: oldConfig.onlyDuringLive, - userFilterEnabled: oldConfig.userFilterEnabled, - requireMedal: oldConfig.requireMedal, - requireCaptain: oldConfig.requireCaptain - }; - item.actionConfig = { - ...item.actionConfig, - cooldownSeconds: oldConfig.cooldownSeconds || 5 - }; - return item; - }); - - // 添加到现有的autoActions中 - autoActions.value = [...autoActions.value, ...newItems]; - - // 标记为已迁移 - localStorage.setItem(migratedKey, 'true'); - console.log(`成功迁移 ${newItems.length} 条自动回复规则`); - } catch (error) { - console.error('迁移自动回复配置失败:', error); - } - } - - /** - * 迁移旧的礼物感谢配置到新的AutoActionItem格式 - */ - function migrateGiftThankConfig() { - try { - // 尝试从localStorage获取旧配置 - const oldConfigStr = localStorage.getItem('autoAction.giftThankConfig'); - if (!oldConfigStr) return; - - const oldConfig = JSON.parse(oldConfigStr); - if (!oldConfig.enabled || !oldConfig.templates || !Array.isArray(oldConfig.templates)) return; - - // 检查是否已经迁移过(防止重复迁移) - const migratedKey = 'autoAction.giftThankMigrated'; - if (localStorage.getItem(migratedKey) === 'true') return; - - // 创建新的礼物感谢项 - const item = createDefaultAutoAction(TriggerType.GIFT); - item.name = '礼物感谢'; - item.enabled = oldConfig.enabled; - item.templates = oldConfig.templates; - - // 设置触发配置 - item.triggerConfig = { - ...item.triggerConfig, - onlyDuringLive: oldConfig.onlyDuringLive ?? true, - ignoreTianXuan: oldConfig.ignoreTianXuan ?? true, - userFilterEnabled: oldConfig.userFilterEnabled ?? false, - requireMedal: oldConfig.requireMedal ?? false, - requireCaptain: oldConfig.requireCaptain ?? false, - filterMode: oldConfig.filterModes?.useWhitelist ? 'whitelist' : - oldConfig.filterModes?.useBlacklist ? 'blacklist' : undefined, - filterGiftNames: oldConfig.filterGiftNames || [], - minValue: oldConfig.minValue || 0 - }; - - // 设置操作配置 - item.actionConfig = { - ...item.actionConfig, - delaySeconds: oldConfig.delaySeconds || 0, - cooldownSeconds: 5, - maxUsersPerMsg: oldConfig.maxUsersPerMsg || 3, - maxItemsPerUser: oldConfig.maxGiftsPerUser || 3 - }; - - // 添加到现有的autoActions中 - autoActions.value.push(item); - - // 标记为已迁移 - localStorage.setItem(migratedKey, 'true'); - console.log('成功迁移礼物感谢配置'); - } catch (error) { - console.error('迁移礼物感谢配置失败:', error); - } } // 卸载时清理 diff --git a/src/components.d.ts b/src/components.d.ts index 49c29bf..74ba544 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -18,6 +18,7 @@ declare module 'vue' { LabelItem: typeof import('./components/LabelItem.vue')['default'] LiveInfoContainer: typeof import('./components/LiveInfoContainer.vue')['default'] MonacoEditorComponent: typeof import('./components/MonacoEditorComponent.vue')['default'] + NAlert: typeof import('naive-ui')['NAlert'] NAvatar: typeof import('naive-ui')['NAvatar'] NBadge: typeof import('naive-ui')['NBadge'] NButton: typeof import('naive-ui')['NButton'] diff --git a/src/data/Initializer.ts b/src/data/Initializer.ts index 78c18f1..465ac3c 100644 --- a/src/data/Initializer.ts +++ b/src/data/Initializer.ts @@ -99,7 +99,7 @@ function InitVersionCheck() { const path = url.pathname if (!path.startsWith('/obs')) { - if (isTauri) { + if (isTauri()) { location.reload(); } else { diff --git a/src/data/UpdateNote.ts b/src/data/UpdateNote.ts index dae780f..113fdf0 100644 --- a/src/data/UpdateNote.ts +++ b/src/data/UpdateNote.ts @@ -88,6 +88,9 @@ export function checkUpdateNote() { positiveText: '下次更新前不再提示', onPositiveClick: () => { savedUpdateNoteVer.value = currentUpdateNoteVer; + }, + onClose: () => { + savedUpdateNoteVer.value = currentUpdateNoteVer; } }); } diff --git a/src/data/constants.ts b/src/data/constants.ts index 92628bc..38ef389 100644 --- a/src/data/constants.ts +++ b/src/data/constants.ts @@ -11,7 +11,7 @@ const failoverAPI = `https://failover-api.vtsuru.suki.club/`; export const isBackendUsable = ref(true); export const isDev = import.meta.env.MODE === 'development'; // @ts-ignore -export const isTauri = window.__TAURI__ !== undefined || window.__TAURI_INTERNAL__ !== undefined; +export const isTauri = () => window.__TAURI__ !== undefined || window.__TAURI_INTERNAL__ !== undefined || '__TAURI__' in window; export const AVATAR_URL = 'https://workers.vrp.moe/api/bilibili/avatar/'; export const FILE_BASE_URL = 'https://files.vtsuru.suki.club'; diff --git a/src/store/useWebFetcher.ts b/src/store/useWebFetcher.ts index 9aa6be6..ae71cff 100644 --- a/src/store/useWebFetcher.ts +++ b/src/store/useWebFetcher.ts @@ -196,7 +196,7 @@ export const useWebFetcher = defineStore('WebFetcher', () => { danmakuClientState.value = 'connected'; // 明确设置状态 danmakuServerUrl.value = client.danmakuClient!.serverUrl; // 获取服务器地址 // 启动事件发送定时器 (如果之前没有启动) - timer ??= setInterval(sendEvents, 1500); // 每 1.5 秒尝试发送一次事件 + timer ??= setInterval(sendEvents, 2000); // 每 2 秒尝试发送一次事件 return { success: true, message: '弹幕客户端已启动' }; } else { console.error(prefix.value + '弹幕客户端启动失败'); @@ -301,7 +301,7 @@ export const useWebFetcher = defineStore('WebFetcher', () => { Data: string; }; async function onRequest(url: string, method: string, body: string, useCookie: boolean) { - if (!isTauri) { + if (!isTauri()) { console.error(prefix.value + '非Tauri环境下无法处理请求: ' + url); return { Message: '非Tauri环境', @@ -386,7 +386,7 @@ export const useWebFetcher = defineStore('WebFetcher', () => { } // 批量处理事件,每次最多发送20条 - const batchSize = 20; + const batchSize = 30; const batch = events.slice(0, batchSize); try { diff --git a/src/views/AboutView.vue b/src/views/AboutView.vue index 560a81d..7ef739c 100644 --- a/src/views/AboutView.vue +++ b/src/views/AboutView.vue @@ -1,20 +1,24 @@ - + - 关于 + + 关于 + 一个兴趣使然的网站 @@ -52,23 +56,174 @@ import { NButton, NCard, NDivider, NLayoutContent, NSpace, NText, NTimeline, NTi - - MADE WITH ❤️ BY + + MADE WITH BY Megghy + + + + + + 源代码仓库 + + + + + + 技术栈 + + + + + + + + + 前端: + + + + Vue.js + + + + + TypeScript + + + + + Naive UI + + + + + + + + + 后端: + + + + C# .NET 10 + + + + + PostgreSQL + + + + + KeyDB + + + - 赞助我 + + 赞助我 + - 更新日志 + + 更新日志 + + + - + 回到控制台 + + diff --git a/src/views/ManageLayout.vue b/src/views/ManageLayout.vue index f01b28f..75e0dc4 100644 --- a/src/views/ManageLayout.vue +++ b/src/views/ManageLayout.vue @@ -21,6 +21,7 @@ import { TabletSpeaker24Filled, VehicleShip24Filled, VideoAdd20Filled, + Mail24Filled, } from '@vicons/fluent' import { AnalyticsSharp, BrowsersOutline, Chatbox, Moon, MusicalNote, Sunny, Eye } from '@vicons/ionicons5' import { useElementSize, useStorage } from '@vueuse/core' @@ -561,37 +562,90 @@ onMounted(() => { - - 请进行邮箱验证 - - - + + - 重新发送验证邮件 - - + + + + + 请验证您的邮箱 + + + 我们已向您的邮箱发送了验证链接,请查收并点击链接完成验证 + + - + + + + + + 如果长时间未收到邮件,请检查垃圾邮件文件夹,或点击下方按钮重新发送 + + + + + + + + + + 重新发送验证邮件 + + + + 后可重新发送 + + + + + + - - 登出 + + + + + + + 切换账号 - 确定登出? + 确定要登出当前账号吗? - + diff --git a/src/views/ViewerLayout.vue b/src/views/ViewerLayout.vue index 3b8d937..36c1a83 100644 --- a/src/views/ViewerLayout.vue +++ b/src/views/ViewerLayout.vue @@ -13,7 +13,7 @@ VideoAdd20Filled, WindowWrench20Filled, } from '@vicons/fluent'; - import { Chatbox, Home, Moon, MusicalNote, Sunny } from '@vicons/ionicons5'; + import { BrowsersOutline, Chatbox, Home, Moon, MusicalNote, Sunny } from '@vicons/ionicons5'; import { useElementSize, useStorage } from '@vueuse/core'; import { MenuOption, @@ -491,6 +491,36 @@ :auto-focus="false" :mask-closable="false" > + + + + 如果你不是主播且不发送棉花糖(提问)的话则不需要注册登录 + + + + + + + 前往 Bilibili 认证用户主页 + + + + + diff --git a/src/views/manage/EventView.vue b/src/views/manage/EventView.vue index d10d15c..eba99ab 100644 --- a/src/views/manage/EventView.vue +++ b/src/views/manage/EventView.vue @@ -34,26 +34,10 @@ import { NTime, NUl, useMessage, + NInfiniteScroll, } from 'naive-ui' -import { computed, ref } from 'vue' - -// 事件类型枚举 -enum EventType { - Guard, - SC, -} - -// 事件数据模型接口 -interface EventModel { - type: EventType - name: string - uid: number - msg: string - time: number - num: number - price: number - uface: string -} +import { computed, ref, watch } from 'vue' +import { EventDataTypes, EventModel } from '@/api/api-models' const accountInfo = useAccount() const message = useMessage() @@ -76,45 +60,92 @@ const rangeShortcuts = { // 响应式状态 const selectedDate = ref<[number, number]>([rangeShortcuts.本月()[0], rangeShortcuts.本月()[1]]) -const selectedType = ref(EventType.Guard) -const events = ref(await get()) -const isLoading = ref(false) +const selectedType = ref(EventDataTypes.Guard) +const events = ref([]) // 初始为空数组 +const isLoading = ref(false) // 用于初始加载 +const isLoadingMore = ref(false) // 用于无限滚动加载 const displayMode = ref<'grid' | 'column'>('grid') -const exportType = ref<'json' | 'csv'>('csv') // 移除了未实现的xml选项 +const exportType = ref<'json' | 'csv'>('csv') +const offset = ref(0) // 当前偏移量 +const limit = ref(20) // 每次加载数量 +const hasMore = ref(true) // 是否还有更多数据 -// 根据类型过滤事件 +// 根据类型过滤事件 - 这个计算属性现在可能只显示当前已加载的事件 +// 如果需要导出 *所有* 选定日期/类型的数据,导出逻辑需要调整 const selectedEvents = computed(() => { return events.value.filter((e) => e.type == selectedType.value) }) -// 获取事件数据 -async function onDateChange() { - isLoading.value = true - const data = await get() - events.value = data - isLoading.value = false -} - -// API请求获取数据 -async function get() { +// API请求获取数据 - 修改为支持分页 +async function get(currentOffset: number, currentLimit: number) { try { const data = await QueryGetAPI(EVENT_API_URL + 'get', { start: selectedDate.value[0], end: selectedDate.value[1], + offset: currentOffset, // 添加 offset 参数 + limit: currentLimit, // 添加 limit 参数 }) if (data.code == 200) { - message.success('已获取数据') - return new List(data.data).OrderByDescending((d) => d.time).ToArray() + message.success(`成功获取 ${data.data.length} 条数据`) // 调整提示 + return data.data // 直接返回数据数组 } else { message.error('获取数据失败: ' + data.message) return [] } } catch (err) { - message.error('获取数据失败') + message.error('获取数据失败: ' + (err as Error).message) // 提供更详细的错误信息 return [] } } +// 封装的数据获取函数 +async function fetchData(isInitialLoad = false) { + if (isLoading.value || isLoadingMore.value) return // 防止重复加载 + + if (isInitialLoad) { + isLoading.value = true + offset.value = 0 // 重置偏移量 + events.value = [] // 清空现有事件 + hasMore.value = true // 假设有更多数据 + } else { + isLoadingMore.value = true + } + + const currentOffset = offset.value + const fetchedData = await get(currentOffset, limit.value) + + if (fetchedData.length > 0) { + // 使用 Linqts 进行排序后追加或设置 + const sortedData = new List(fetchedData).OrderByDescending((d) => d.time).ToArray() + events.value = isInitialLoad ? sortedData : [...events.value, ...sortedData] + offset.value += fetchedData.length // 更新偏移量 + hasMore.value = fetchedData.length === limit.value // 如果返回的数量等于请求的数量,则可能还有更多 + } else { + hasMore.value = false // 没有获取到数据,说明没有更多了 + } + + if (isInitialLoad) { + isLoading.value = false + } else { + isLoadingMore.value = false + } +} + +// 日期或类型变化时重新加载 +async function onFilterChange() { + await fetchData(true) // 初始加载 +} + +// 无限滚动加载更多 +async function loadMore() { + if (!hasMore.value || isLoadingMore.value || isLoading.value) return + console.log('Loading more...') // 调试信息 + await fetchData(false) +} + +// 监视日期和类型变化 +watch([selectedDate, selectedType], onFilterChange, { immediate: true }) // 初始加载数据 + // 获取SC颜色 function GetSCColor(price: number): string { if (price === 0) return `#2a60b2` @@ -139,8 +170,11 @@ function GetGuardColor(price: number | null | undefined): string { return '' } -// 导出数据功能 +// 导出数据功能 - 注意:这现在只导出已加载的数据 function exportData() { + if(hasMore.value) { + message.warning('当前导出的是已加载的部分数据,并非所有数据。') + } let text = '' const fileName = generateExportFileName() @@ -154,7 +188,7 @@ function exportData() { selectedEvents.value.map((v) => ({ type: v.type, time: format(v.time, 'yyyy-MM-dd HH:mm:ss'), - name: v.name, + name: v.uname, uId: v.uid, num: v.num, price: v.price, @@ -202,7 +236,7 @@ function objectsToCSV(arr: any[]) { + > - - + + 舰长 - + Superchat - - 刷新 - @@ -255,18 +285,28 @@ function objectsToCSV(arr: any[]) { - 导出 + 导出{{ hasMore ? ' (已加载部分)' : ' (全部)' }} + + 当前仅显示已加载的部分数据,滚动到底部可加载更多。导出功能也仅导出已加载数据。 + - 共 {{ selectedEvents.length }} 条 + + 共加载 {{ selectedEvents.length }} 条 {{ hasMore ? '(滚动加载更多...)' : '' }} + + - - - + - - - - - {{ item.msg }} - - - {{ item.price }} - - - - {{ item.name }} - - - - - - {{ item.msg }} - - - - - + + + + {{ item.msg }} + + + {{ item.price }} + + + + + {{ item.uname }} + + + + + + + {{ item.msg }} + + + + + + + + + + 加载中... + + + + + 没有更多数据了 + + + - - + + + 用户名 UId 时间 - + 类型 价格 - + 内容 @@ -376,20 +453,24 @@ function objectsToCSV(arr: any[]) { - {{ item.name }} + {{ item.uname }} {{ item.uid }} - + - + {{ item.msg }} - + {{ item.msg }} @@ -405,6 +486,14 @@ function objectsToCSV(arr: any[]) { + + + 在选定的时间范围和类型内没有找到数据。 + @@ -474,4 +563,9 @@ function objectsToCSV(arr: any[]) { .fade-leave-to { opacity: 0; } + +/* 网格卡片样式微调 */ +.n-card { + transition: box-shadow 0.3s ease; /* 平滑阴影过渡 */ +}