mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-06 18:36:55 +08:00
feat: 添加更新日志功能;优化组件和状态管理;修复部分逻辑错误
This commit is contained in:
3
default.d.ts
vendored
3
default.d.ts
vendored
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
import type { LoadingBarProviderInst, MessageProviderInst, ModalProviderInst, NotificationProviderInst } from 'naive-ui'
|
import type { DialogProviderInst, LoadingBarProviderInst, MessageProviderInst, ModalProviderInst, NotificationProviderInst } from 'naive-ui'
|
||||||
import type { useRoute } from 'vue-router'
|
import type { useRoute } from 'vue-router'
|
||||||
|
|
||||||
declare module 'vue3-aplayer' {
|
declare module 'vue3-aplayer' {
|
||||||
@@ -23,5 +23,6 @@ declare global {
|
|||||||
$modal: ModalProviderInst
|
$modal: ModalProviderInst
|
||||||
$mitt: Emitter<MittType>
|
$mitt: Emitter<MittType>
|
||||||
$notification: NotificationProviderInst
|
$notification: NotificationProviderInst
|
||||||
|
$dialog: DialogProviderInst
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -992,7 +992,7 @@
|
|||||||
<NIcon :component="BarChartOutline" /> {{ webfetcher.sessionEventCount?.toLocaleString() ?? 0 }}
|
<NIcon :component="BarChartOutline" /> {{ webfetcher.sessionEventCount?.toLocaleString() ?? 0 }}
|
||||||
</NStatistic>
|
</NStatistic>
|
||||||
<NStatistic label="已发送">
|
<NStatistic label="已发送">
|
||||||
{{ ((webfetcher.bytesSentSession ?? 0) / 1024).toFixed(2) }} KB <!-- Assuming exposed -->
|
{{ ((webfetcher.bytesSentSession ?? 0) / 1024 / 1024).toFixed(2) }} Mb <!-- Assuming exposed -->
|
||||||
</NStatistic>
|
</NStatistic>
|
||||||
</NFlex>
|
</NFlex>
|
||||||
</NCard>
|
</NCard>
|
||||||
|
|||||||
@@ -10,6 +10,10 @@
|
|||||||
const webfetcher = useWebFetcher();
|
const webfetcher = useWebFetcher();
|
||||||
|
|
||||||
const coverRef = ref();
|
const coverRef = ref();
|
||||||
|
const isHover = ref(false);
|
||||||
|
const roomCover = computed(() => {
|
||||||
|
return isHover.value ? roomInfo.value?.keyframe : roomInfo.value?.user_cover;
|
||||||
|
});
|
||||||
const { width, height } = useElementSize(coverRef);
|
const { width, height } = useElementSize(coverRef);
|
||||||
|
|
||||||
function logout() {
|
function logout() {
|
||||||
@@ -90,8 +94,8 @@
|
|||||||
<template #header>
|
<template #header>
|
||||||
<NSpace align="center">
|
<NSpace align="center">
|
||||||
直播状态
|
直播状态
|
||||||
<NTag :type="!accountInfo.streamerInfo?.isStreaming ? 'error' : 'success'">
|
<NTag :type="roomInfo?.live_status === 1 ? 'success' : 'error'">
|
||||||
{{ !accountInfo.streamerInfo?.isStreaming ? '未直播' : '直播中' }}
|
{{ roomInfo?.live_status === 1 ? '直播中' : '未直播' }}
|
||||||
</NTag>
|
</NTag>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<NText
|
<NText
|
||||||
@@ -102,13 +106,13 @@
|
|||||||
</NText>
|
</NText>
|
||||||
</template>
|
</template>
|
||||||
<div
|
<div
|
||||||
v-if="roomInfo?.user_cover"
|
v-if="roomCover"
|
||||||
style="position: relative"
|
style="position: relative"
|
||||||
>
|
>
|
||||||
<div style="position: relative; width: 100%; max-width: 500px;">
|
<div style="position: relative; width: 100%; max-width: 500px;">
|
||||||
<NImage
|
<NImage
|
||||||
ref="coverRef"
|
ref="coverRef"
|
||||||
:src="roomInfo?.user_cover"
|
:src="roomCover"
|
||||||
style="width: 100%; opacity: 0.5; border-radius: 8px;"
|
style="width: 100%; opacity: 0.5; border-radius: 8px;"
|
||||||
referrerpolicy="no-referrer"
|
referrerpolicy="no-referrer"
|
||||||
:img-props="{ referrerpolicy: 'no-referrer', style: { width: '100%' } }"
|
:img-props="{ referrerpolicy: 'no-referrer', style: { width: '100%' } }"
|
||||||
@@ -118,6 +122,8 @@
|
|||||||
style="position: absolute; z-index: 1; top: 0; width: 100%; background: linear-gradient(to top, rgba(0,0,0,0.8), rgba(0,0,0,0.2), transparent)"
|
style="position: absolute; z-index: 1; top: 0; width: 100%; background: linear-gradient(to top, rgba(0,0,0,0.8), rgba(0,0,0,0.2), transparent)"
|
||||||
:style="{ height: `${height}px`, maxWidth: '500px', cursor: 'pointer' }"
|
:style="{ height: `${height}px`, maxWidth: '500px', cursor: 'pointer' }"
|
||||||
@click="openUrl(`https://live.bilibili.com/${accountInfo.biliRoomId}`)"
|
@click="openUrl(`https://live.bilibili.com/${accountInfo.biliRoomId}`)"
|
||||||
|
@mouseenter="isHover = true"
|
||||||
|
@mouseleave="isHover = false"
|
||||||
/>
|
/>
|
||||||
<NText style="position: absolute; bottom: 12px; left: 16px; z-index: 2; color: white; font-size: 18px">
|
<NText style="position: absolute; bottom: 12px; left: 16px; z-index: 2; color: white; font-size: 18px">
|
||||||
{{ roomInfo?.title }}
|
{{ roomInfo?.title }}
|
||||||
|
|||||||
@@ -117,14 +117,6 @@ import { isTauri } from '@/data/constants';
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NAlert
|
|
||||||
v-if="!isTauri"
|
|
||||||
type="error"
|
|
||||||
title="错误"
|
|
||||||
>
|
|
||||||
此应用在 Tauri 环境外运行,无法使用
|
|
||||||
</NAlert>
|
|
||||||
<template v-else>
|
|
||||||
<WindowBar />
|
<WindowBar />
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -280,7 +272,6 @@ import { isTauri } from '@/data/constants';
|
|||||||
</NLayoutContent>
|
</NLayoutContent>
|
||||||
</NLayout>
|
</NLayout>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
|
|||||||
@@ -333,6 +333,9 @@ watch(minimizeOnStart, (newValue) => {
|
|||||||
Tauri 客户端
|
Tauri 客户端
|
||||||
</NButton>
|
</NButton>
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
反馈: 🐧 873260337
|
||||||
|
</p>
|
||||||
<!-- Add more about info -->
|
<!-- Add more about info -->
|
||||||
</NCard>
|
</NCard>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { info, error } from '@tauri-apps/plugin-log';
|
|||||||
import { QueryBiliAPI } from './utils'; // 假设 Bili API 工具路径
|
import { QueryBiliAPI } from './utils'; // 假设 Bili API 工具路径
|
||||||
import { BiliRoomInfo, BiliStreamingInfo, FetcherStatisticData } from './models'; // 假设模型路径
|
import { BiliRoomInfo, BiliStreamingInfo, FetcherStatisticData } from './models'; // 假设模型路径
|
||||||
import { useTauriStore } from '../store/useTauriStore';
|
import { useTauriStore } from '../store/useTauriStore';
|
||||||
|
import { useAccount } from '@/api/account';
|
||||||
// import { useAccount } from '@/api/account'; // 如果需要账户信息
|
// import { useAccount } from '@/api/account'; // 如果需要账户信息
|
||||||
|
|
||||||
// const accountInfo = useAccount(); // 如果需要
|
// const accountInfo = useAccount(); // 如果需要
|
||||||
@@ -185,10 +186,8 @@ export async function getHistoricalStatistics(days: number = 7): Promise<Fetcher
|
|||||||
* 更新房间和直播流信息
|
* 更新房间和直播流信息
|
||||||
*/
|
*/
|
||||||
async function updateRoomAndStreamingInfo() {
|
async function updateRoomAndStreamingInfo() {
|
||||||
// 需要一个房间ID来查询,这个ID可能来自设置、登录信息或固定配置
|
const account = useAccount();
|
||||||
// const roomId = accountInfo.value?.roomid ?? settings.value.roomId; // 示例:获取房间ID
|
if (!account.value.biliRoomId) {
|
||||||
const roomId = 21484828; // !!! 示例:这里需要替换成实际获取房间ID的逻辑 !!!
|
|
||||||
if (!roomId) {
|
|
||||||
// error("无法获取房间ID以更新直播信息");
|
// error("无法获取房间ID以更新直播信息");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -196,7 +195,7 @@ async function updateRoomAndStreamingInfo() {
|
|||||||
try {
|
try {
|
||||||
// 查询房间基本信息
|
// 查询房间基本信息
|
||||||
const roomRes = await QueryBiliAPI(
|
const roomRes = await QueryBiliAPI(
|
||||||
`https://api.live.bilibili.com/room/v1/Room/get_info?room_id=${roomId}`
|
`https://api.live.bilibili.com/room/v1/Room/get_info?room_id=${account.value.biliRoomId}`
|
||||||
);
|
);
|
||||||
const json = await roomRes.json();
|
const json = await roomRes.json();
|
||||||
if (json.code === 0) {
|
if (json.code === 0) {
|
||||||
|
|||||||
@@ -4,6 +4,18 @@ import { isPermissionGranted, onAction, sendNotification } from "@tauri-apps/plu
|
|||||||
import { openUrl } from "@tauri-apps/plugin-opener";
|
import { openUrl } from "@tauri-apps/plugin-opener";
|
||||||
import { CN_HOST } from "@/data/constants";
|
import { CN_HOST } from "@/data/constants";
|
||||||
|
|
||||||
|
export function onReceivedNotification(type: string, data: any) {
|
||||||
|
switch (type) {
|
||||||
|
case 'question-box':
|
||||||
|
|
||||||
|
onReceivedQuestion(data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.warn(`Unhandled notification type: ${type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function onReceivedQuestion(question: QAInfo) {
|
export async function onReceivedQuestion(question: QAInfo) {
|
||||||
const setting = useSettings();
|
const setting = useSettings();
|
||||||
if (setting.settings.notificationSettings.enableTypes.includes("question-box")) {
|
if (setting.settings.notificationSettings.enableTypes.includes("question-box")) {
|
||||||
|
|||||||
1
src/components.d.ts
vendored
1
src/components.d.ts
vendored
@@ -57,6 +57,7 @@ declare module 'vue' {
|
|||||||
SongPlayer: typeof import('./components/SongPlayer.vue')['default']
|
SongPlayer: typeof import('./components/SongPlayer.vue')['default']
|
||||||
TempComponent: typeof import('./components/TempComponent.vue')['default']
|
TempComponent: typeof import('./components/TempComponent.vue')['default']
|
||||||
TurnstileVerify: typeof import('./components/TurnstileVerify.vue')['default']
|
TurnstileVerify: typeof import('./components/TurnstileVerify.vue')['default']
|
||||||
|
UpdateNoteContainer: typeof import('./components/UpdateNoteContainer.vue')['default']
|
||||||
UserBasicInfoCard: typeof import('./components/UserBasicInfoCard.vue')['default']
|
UserBasicInfoCard: typeof import('./components/UserBasicInfoCard.vue')['default']
|
||||||
VEditor: typeof import('./components/VEditor.vue')['default']
|
VEditor: typeof import('./components/VEditor.vue')['default']
|
||||||
VideoCollectInfoCard: typeof import('./components/VideoCollectInfoCard.vue')['default']
|
VideoCollectInfoCard: typeof import('./components/VideoCollectInfoCard.vue')['default']
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ const showContent = ref(!isViolation)
|
|||||||
<br>
|
<br>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<NText :style="{ filter: showContent ? '' : 'blur(3.7px)', cursor: showContent ? '' : 'pointer' }">
|
<NText :style="{ filter: showContent ? '' : 'blur(3.7px)', cursor: showContent ? '' : 'pointer', whiteSpace: 'pre-wrap' }">
|
||||||
<NButton
|
<NButton
|
||||||
v-if="isViolation"
|
v-if="isViolation"
|
||||||
size="small"
|
size="small"
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ onMounted(() => {
|
|||||||
window.$route = useRoute()
|
window.$route = useRoute()
|
||||||
window.$modal = useModal()
|
window.$modal = useModal()
|
||||||
window.$notification = useNotification()
|
window.$notification = useNotification()
|
||||||
|
window.$dialog = useDialog()
|
||||||
const providerStore = useLoadingBarStore()
|
const providerStore = useLoadingBarStore()
|
||||||
providerStore.setLoadingBar(window.$loadingBar)
|
providerStore.setLoadingBar(window.$loadingBar)
|
||||||
})
|
})
|
||||||
|
|||||||
93
src/components/UpdateNoteContainer.vue
Normal file
93
src/components/UpdateNoteContainer.vue
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { updateNoteItemContentType, updateNotes } from '@/data/UpdateNote';
|
||||||
|
import { NDivider, NGrid } from 'naive-ui';
|
||||||
|
import { VNode } from 'vue';
|
||||||
|
|
||||||
|
const currentVer = '1'
|
||||||
|
const savedVer = useStorage('UpdateNoteVer', 0)
|
||||||
|
function renderContent(content: updateNoteItemContentType): VNode | string | undefined {
|
||||||
|
if (Array.isArray(content)) {
|
||||||
|
return h('div', { style: { whiteSpace: 'pre-wrap' } }, content.map(item => renderContent(item)))
|
||||||
|
}
|
||||||
|
if (typeof content === 'string') {
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
if (typeof content === 'function') {
|
||||||
|
return content()
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NScrollbar
|
||||||
|
style="max-height: 80vh;"
|
||||||
|
trigger="none"
|
||||||
|
>
|
||||||
|
<NFlex vertical>
|
||||||
|
<div
|
||||||
|
v-for="item in updateNotes"
|
||||||
|
:key="item.ver"
|
||||||
|
>
|
||||||
|
<NDivider title-placement="left">
|
||||||
|
{{ item.date }}
|
||||||
|
</NDivider>
|
||||||
|
<NGrid
|
||||||
|
x-gap="10"
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
v-for="note in item.items"
|
||||||
|
:key="note.title"
|
||||||
|
>
|
||||||
|
<NGridItem span="6">
|
||||||
|
<div style="">
|
||||||
|
<NTag
|
||||||
|
v-if="note.type === 'fix'"
|
||||||
|
type="info"
|
||||||
|
round
|
||||||
|
:bordered="false"
|
||||||
|
>
|
||||||
|
错误修复
|
||||||
|
</NTag>
|
||||||
|
<NTag
|
||||||
|
v-else-if="note.type === 'new'"
|
||||||
|
type="success"
|
||||||
|
round
|
||||||
|
:bordered="false"
|
||||||
|
>
|
||||||
|
功能添加
|
||||||
|
</NTag>
|
||||||
|
<NTag
|
||||||
|
v-else-if="note.type === 'optimize'"
|
||||||
|
:color="{ textColor: '#000', color: '#f0ad4e', borderColor: '#f0ad4e' }"
|
||||||
|
round
|
||||||
|
:bordered="false"
|
||||||
|
>
|
||||||
|
功能优化
|
||||||
|
</NTag>
|
||||||
|
<NTag
|
||||||
|
v-else-if="note.type === 'other'"
|
||||||
|
type="error"
|
||||||
|
round
|
||||||
|
:bordered="false"
|
||||||
|
>
|
||||||
|
其他
|
||||||
|
</NTag>
|
||||||
|
</div>
|
||||||
|
</NGridItem>
|
||||||
|
<NGridItem span="18">
|
||||||
|
<NFlex vertical>
|
||||||
|
<template
|
||||||
|
v-for="content in note.content"
|
||||||
|
:key="content"
|
||||||
|
>
|
||||||
|
<component :is="renderContent(content)" />
|
||||||
|
</template>
|
||||||
|
</NFlex>
|
||||||
|
</NGridItem>
|
||||||
|
</template>
|
||||||
|
</NGrid>
|
||||||
|
</div>
|
||||||
|
</NFlex>
|
||||||
|
</NScrollbar>
|
||||||
|
</template>
|
||||||
155
src/data/Initializer.ts
Normal file
155
src/data/Initializer.ts
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
import { GetSelfAccount, useAccount, UpdateAccountLoop } from "@/api/account";
|
||||||
|
import { QueryGetAPI } from "@/api/query";
|
||||||
|
import { useAuthStore } from "@/store/useAuthStore";
|
||||||
|
import { useNotificationStore } from "@/store/useNotificationStore";
|
||||||
|
import { createDiscreteApi, NText, NFlex, NButton } from "naive-ui";
|
||||||
|
import { BASE_API_URL, isTauri, apiFail } from "./constants";
|
||||||
|
import { GetNotifactions } from "./notifactions";
|
||||||
|
import HyperDX from '@hyperdx/browser'
|
||||||
|
import EasySpeech from "easy-speech";
|
||||||
|
import { checkUpdateNote } from "./UpdateNote";
|
||||||
|
|
||||||
|
let currentVersion: string
|
||||||
|
let isHaveNewVersion = false
|
||||||
|
|
||||||
|
const { notification } = createDiscreteApi(['notification'])
|
||||||
|
|
||||||
|
export function InitVTsuru() {
|
||||||
|
QueryGetAPI<string>(`${BASE_API_URL}vtsuru/version`)
|
||||||
|
.then((version) => {
|
||||||
|
if (version.code == 200) {
|
||||||
|
currentVersion = version.data
|
||||||
|
const savedVersion = localStorage.getItem('Version')
|
||||||
|
localStorage.setItem('Version', currentVersion)
|
||||||
|
|
||||||
|
if (currentVersion && savedVersion && savedVersion !== currentVersion) {
|
||||||
|
setTimeout(() => {
|
||||||
|
location.reload()
|
||||||
|
}, 1000)
|
||||||
|
// alert('发现新的版本更新, 请按 Ctrl+F5 强制刷新页面')
|
||||||
|
notification.info({
|
||||||
|
title: '发现新的版本更新',
|
||||||
|
content: '将自动刷新页面',
|
||||||
|
duration: 5000,
|
||||||
|
meta: () => h(NText, { depth: 3 }, () => currentVersion),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
InitVersionCheck();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InitOther();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
apiFail.value = true
|
||||||
|
console.log('默认API调用失败, 切换至故障转移节点')
|
||||||
|
})
|
||||||
|
.finally(async () => {
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function InitOther() {
|
||||||
|
if (process.env.NODE_ENV !== 'development') {
|
||||||
|
HyperDX.init({
|
||||||
|
apiKey: '7d1eb66c-24b8-445e-a406-dc2329fa9423',
|
||||||
|
service: 'vtsuru.live',
|
||||||
|
tracePropagationTargets: [/vtsuru.suki.club/i], // Set to link traces from frontend to backend requests
|
||||||
|
consoleCapture: true, // Capture console logs (default false)
|
||||||
|
advancedNetworkCapture: true, // Capture full HTTP request/response headers and bodies (default false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 加载其他数据
|
||||||
|
InitTTS()
|
||||||
|
await GetSelfAccount()
|
||||||
|
const account = useAccount()
|
||||||
|
const useAuth = useAuthStore()
|
||||||
|
if (account.value.id) {
|
||||||
|
if (account.value.biliUserAuthInfo && !useAuth.currentToken) {
|
||||||
|
useAuth.currentToken = account.value.biliUserAuthInfo.token
|
||||||
|
}
|
||||||
|
HyperDX.setGlobalAttributes({
|
||||||
|
userId: account.value.id.toString(),
|
||||||
|
userName: account.value.name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
useAuth.getAuthInfo()
|
||||||
|
GetNotifactions()
|
||||||
|
UpdateAccountLoop()
|
||||||
|
|
||||||
|
useNotificationStore().init()
|
||||||
|
}
|
||||||
|
function InitVersionCheck() {
|
||||||
|
setInterval(() => {
|
||||||
|
if (isHaveNewVersion) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
QueryGetAPI<string>(`${BASE_API_URL}vtsuru/version`).then(
|
||||||
|
(keepCheckData) => {
|
||||||
|
if (
|
||||||
|
keepCheckData.code == 200
|
||||||
|
&& keepCheckData.data != currentVersion
|
||||||
|
) {
|
||||||
|
isHaveNewVersion = true
|
||||||
|
currentVersion = keepCheckData.data
|
||||||
|
localStorage.setItem('Version', currentVersion)
|
||||||
|
console.log(`[vtsuru] 发现新版本: ${currentVersion}`)
|
||||||
|
|
||||||
|
const url = new URL(window.location.href)
|
||||||
|
const path = url.pathname
|
||||||
|
|
||||||
|
if (!path.startsWith('/obs')) {
|
||||||
|
if (isTauri) {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const n = notification.info({
|
||||||
|
title: '发现新的版本更新',
|
||||||
|
content: '是否现在刷新?',
|
||||||
|
meta: () => h(NText, { depth: 3 }, () => currentVersion),
|
||||||
|
action: () =>
|
||||||
|
h(NFlex, null, () => [
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
text: true,
|
||||||
|
type: 'primary',
|
||||||
|
onClick: () => location.reload(),
|
||||||
|
size: 'small',
|
||||||
|
},
|
||||||
|
{ default: () => '刷新' },
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
NButton,
|
||||||
|
{
|
||||||
|
text: true,
|
||||||
|
onClick: () => n.destroy(),
|
||||||
|
size: 'small',
|
||||||
|
},
|
||||||
|
{ default: () => '稍后' },
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}, 60 * 1000)
|
||||||
|
}
|
||||||
|
function InitTTS() {
|
||||||
|
try {
|
||||||
|
const result = EasySpeech.detect()
|
||||||
|
if (result.speechSynthesis) {
|
||||||
|
EasySpeech.init({ maxTimeout: 5000, interval: 250 })
|
||||||
|
.then(() => console.log('[SpeechSynthesis] 已加载tts服务'))
|
||||||
|
.catch(e => console.error(e))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log('[SpeechSynthesis] 当前浏览器不支持tts服务')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.log('[SpeechSynthesis] 当前浏览器不支持tts服务')
|
||||||
|
}
|
||||||
|
}
|
||||||
91
src/data/UpdateNote.ts
Normal file
91
src/data/UpdateNote.ts
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import UpdateNoteContainer from "@/components/UpdateNoteContainer.vue";
|
||||||
|
import { NButton, NImage, NTag } from "naive-ui";
|
||||||
|
import { VNode } from "vue";
|
||||||
|
import { FETCH_API } from "./constants";
|
||||||
|
|
||||||
|
export const updateNotes: updateNoteType[] = [
|
||||||
|
{
|
||||||
|
ver: 2,
|
||||||
|
date: '2025.4.8',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: 'new',
|
||||||
|
title: 'EventFetcher Tauri 客户端开始测试',
|
||||||
|
content: [
|
||||||
|
['比当前所有 EventFetcher 部署方法都更要简单且支持扫码登录的客户端开始测试力, 支持Windows, Linux, MacOS (后两个没测试过'],
|
||||||
|
[
|
||||||
|
'如果对此感兴趣的话可以使用 ',
|
||||||
|
() => h(NButton, {
|
||||||
|
text: true, tag: 'a', href: FETCH_API + 'https://github.com/Megghy/vtsuru-fetcher-client/releases/download/app-v0.1.0/vtsuru-fetcher-client_0.1.0_x64-setup.exe', target: '_blank', type: 'info'
|
||||||
|
}, () => '这个链接'),
|
||||||
|
' 下载Windows客户端, 其他平台请在下面的客户端 Repo 中的 Release 下载',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'当前可能存在一些问题, 可以加入秋秋群 873260337 进行反馈, 有功能需求也可以提出'
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
'源码: ',
|
||||||
|
() => h(NButton, {
|
||||||
|
text: true, tag: 'a', href: 'https://github.com/Megghy/vtsuru-fetvher-client', target: '_blank', type: 'info'
|
||||||
|
}, () => ' 客户端 Repo '),
|
||||||
|
' | ',
|
||||||
|
() => h(NButton, {
|
||||||
|
text: true, tag: 'a', href: 'https://github.com/Megghy/vtsuru.live/tree/master/src/client', target: '_blank', type: 'info'
|
||||||
|
}, () => ' UI/逻辑 '),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
() => h(NImage, { src: 'https://pan.suki.club/d/vtsuru/imgur/01295402D7FBBF192FE5608179A4A7A6.png', width: 200 }),
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ver: 1,
|
||||||
|
date: '2025.3.18',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: '歌单',
|
||||||
|
type: 'optimize',
|
||||||
|
content: [
|
||||||
|
[
|
||||||
|
'新增一个歌单样式: 列表',
|
||||||
|
() => h(NImage, { src: 'https://pan.suki.club/d/vtsuru/imgur/QQ20250408-134631.png', width: 300, height: 200 }),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const currentUpdateNoteVer = updateNotes.sort((a, b) => b.ver - a.ver)[0].ver;
|
||||||
|
export const currentUpdateNote = updateNotes.sort((a, b) => b.ver - a.ver)[0].items;
|
||||||
|
export const savedUpdateNoteVer = useStorage('UpdateNoteVer', 0);
|
||||||
|
|
||||||
|
export function checkUpdateNote() {
|
||||||
|
if (savedUpdateNoteVer.value < currentUpdateNoteVer) {
|
||||||
|
window.$dialog.create({
|
||||||
|
title: '更新日志',
|
||||||
|
content: () => h(UpdateNoteContainer),
|
||||||
|
negativeText: '确定',
|
||||||
|
positiveText: '下次更新前不再提示',
|
||||||
|
onPositiveClick: () => {
|
||||||
|
savedUpdateNoteVer.value = currentUpdateNoteVer;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type updateType = 'fix' | 'new' | 'optimize' | 'other';
|
||||||
|
export type updateNoteType = {
|
||||||
|
ver: number;
|
||||||
|
date: string;
|
||||||
|
items: updateNoteItemType[];
|
||||||
|
};
|
||||||
|
export type updateNoteItemType = {
|
||||||
|
title?: string | (() => VNode);
|
||||||
|
type: updateType;
|
||||||
|
content: updateNoteItemContentType[];
|
||||||
|
};
|
||||||
|
export type updateNoteItemContentType = (() => VNode) | string | updateNoteItemContentType[];
|
||||||
156
src/main.ts
156
src/main.ts
@@ -1,158 +1,18 @@
|
|||||||
import { QueryGetAPI } from '@/api/query'
|
import EasySpeech from 'easy-speech';
|
||||||
import { apiFail, BASE_API_URL, isTauri } from '@/data/constants'
|
import { createPinia } from 'pinia';
|
||||||
import HyperDX from '@hyperdx/browser'
|
import { createApp } from 'vue';
|
||||||
import EasySpeech from 'easy-speech'
|
import App from './App.vue';
|
||||||
import { createDiscreteApi, NButton, NFlex, NText } from 'naive-ui'
|
import { InitVTsuru } from './data/Initializer';
|
||||||
import { createPinia } from 'pinia'
|
import emitter from './mitt';
|
||||||
import { createApp, h } from 'vue'
|
import router from './router';
|
||||||
import { GetSelfAccount, UpdateAccountLoop, useAccount } from './api/account'
|
|
||||||
import App from './App.vue'
|
|
||||||
import { GetNotifactions } from './data/notifactions'
|
|
||||||
import emitter from './mitt'
|
|
||||||
import router from './router'
|
|
||||||
import { useAuthStore } from './store/useAuthStore'
|
|
||||||
import { useNotificationStore } from './store/useNotificationStore'
|
|
||||||
|
|
||||||
const pinia = createPinia()
|
const pinia = createPinia()
|
||||||
export const getPinia = () => pinia
|
export const getPinia = () => pinia
|
||||||
|
|
||||||
QueryGetAPI<string>(`${BASE_API_URL}vtsuru/version`)
|
|
||||||
.then((version) => {
|
|
||||||
if (version.code == 200) {
|
|
||||||
currentVersion = version.data
|
|
||||||
const savedVersion = localStorage.getItem('Version')
|
|
||||||
localStorage.setItem('Version', currentVersion)
|
|
||||||
|
|
||||||
if (currentVersion && savedVersion && savedVersion !== currentVersion) {
|
|
||||||
setTimeout(() => {
|
|
||||||
location.reload()
|
|
||||||
}, 1000)
|
|
||||||
// alert('发现新的版本更新, 请按 Ctrl+F5 强制刷新页面')
|
|
||||||
notification.info({
|
|
||||||
title: '发现新的版本更新',
|
|
||||||
content: '将自动刷新页面',
|
|
||||||
duration: 5000,
|
|
||||||
meta: () => h(NText, { depth: 3 }, () => currentVersion),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setInterval(() => {
|
|
||||||
if (isHaveNewVersion) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
QueryGetAPI<string>(`${BASE_API_URL}vtsuru/version`).then(
|
|
||||||
(keepCheckData) => {
|
|
||||||
if (
|
|
||||||
keepCheckData.code == 200
|
|
||||||
&& keepCheckData.data != currentVersion
|
|
||||||
) {
|
|
||||||
isHaveNewVersion = true
|
|
||||||
currentVersion = keepCheckData.data
|
|
||||||
localStorage.setItem('Version', currentVersion)
|
|
||||||
console.log(`[vtsuru] 发现新版本: ${currentVersion}`)
|
|
||||||
|
|
||||||
const url = new URL(window.location.href)
|
|
||||||
const path = url.pathname
|
|
||||||
|
|
||||||
if (!path.startsWith('/obs')) {
|
|
||||||
if (isTauri) {
|
|
||||||
location.reload();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const n = notification.info({
|
|
||||||
title: '发现新的版本更新',
|
|
||||||
content: '是否现在刷新?',
|
|
||||||
meta: () => h(NText, { depth: 3 }, () => currentVersion),
|
|
||||||
action: () =>
|
|
||||||
h(NFlex, null, () => [
|
|
||||||
h(
|
|
||||||
NButton,
|
|
||||||
{
|
|
||||||
text: true,
|
|
||||||
type: 'primary',
|
|
||||||
onClick: () => location.reload(),
|
|
||||||
size: 'small',
|
|
||||||
},
|
|
||||||
{ default: () => '刷新' },
|
|
||||||
),
|
|
||||||
h(
|
|
||||||
NButton,
|
|
||||||
{
|
|
||||||
text: true,
|
|
||||||
onClick: () => n.destroy(),
|
|
||||||
size: 'small',
|
|
||||||
},
|
|
||||||
{ default: () => '稍后' },
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}, 60 * 1000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
apiFail.value = true
|
|
||||||
console.log('默认API调用失败, 切换至故障转移节点')
|
|
||||||
})
|
|
||||||
.finally(async () => {
|
|
||||||
if (process.env.NODE_ENV !== 'development') {
|
|
||||||
HyperDX.init({
|
|
||||||
apiKey: '7d1eb66c-24b8-445e-a406-dc2329fa9423',
|
|
||||||
service: 'vtsuru.live',
|
|
||||||
tracePropagationTargets: [/vtsuru.suki.club/i], // Set to link traces from frontend to backend requests
|
|
||||||
consoleCapture: true, // Capture console logs (default false)
|
|
||||||
advancedNetworkCapture: true, // Capture full HTTP request/response headers and bodies (default false)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// 加载其他数据
|
|
||||||
InitTTS()
|
|
||||||
await GetSelfAccount()
|
|
||||||
const account = useAccount()
|
|
||||||
const useAuth = useAuthStore()
|
|
||||||
if (account.value.id) {
|
|
||||||
if (account.value.biliUserAuthInfo && !useAuth.currentToken) {
|
|
||||||
useAuth.currentToken = account.value.biliUserAuthInfo.token
|
|
||||||
}
|
|
||||||
HyperDX.setGlobalAttributes({
|
|
||||||
userId: account.value.id.toString(),
|
|
||||||
userName: account.value.name,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
useAuth.getAuthInfo()
|
|
||||||
GetNotifactions()
|
|
||||||
UpdateAccountLoop()
|
|
||||||
})
|
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
app.use(router).use(pinia).mount('#app')
|
app.use(router).use(pinia).mount('#app')
|
||||||
|
|
||||||
let currentVersion: string
|
InitVTsuru();
|
||||||
let isHaveNewVersion = false
|
|
||||||
|
|
||||||
const { notification } = createDiscreteApi(['notification'])
|
|
||||||
|
|
||||||
useNotificationStore().init()
|
|
||||||
|
|
||||||
window.$mitt = emitter
|
window.$mitt = emitter
|
||||||
|
|
||||||
function InitTTS() {
|
|
||||||
try {
|
|
||||||
const result = EasySpeech.detect()
|
|
||||||
if (result.speechSynthesis) {
|
|
||||||
EasySpeech.init({ maxTimeout: 5000, interval: 250 })
|
|
||||||
.then(() => console.log('[SpeechSynthesis] 已加载tts服务'))
|
|
||||||
.catch(e => console.error(e))
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.log('[SpeechSynthesis] 当前浏览器不支持tts服务')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
console.log('[SpeechSynthesis] 当前浏览器不支持tts服务')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { ZstdCodec, ZstdInit } from '@oneidentity/zstd-js/wasm';
|
|||||||
|
|
||||||
import { encode } from "@msgpack/msgpack";
|
import { encode } from "@msgpack/msgpack";
|
||||||
import { getVersion } from '@tauri-apps/api/app';
|
import { getVersion } from '@tauri-apps/api/app';
|
||||||
|
import { onReceivedNotification } from '@/client/data/notification';
|
||||||
|
|
||||||
export const useWebFetcher = defineStore('WebFetcher', () => {
|
export const useWebFetcher = defineStore('WebFetcher', () => {
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@@ -275,7 +276,7 @@ export const useWebFetcher = defineStore('WebFetcher', () => {
|
|||||||
Stop(); // 服务器要求断开,调用 Stop 清理所有资源
|
Stop(); // 服务器要求断开,调用 Stop 清理所有资源
|
||||||
});
|
});
|
||||||
connection.on('Request', async (url: string, method: string, body: string, useCookie: boolean) => onRequest(url, method, body, useCookie));
|
connection.on('Request', async (url: string, method: string, body: string, useCookie: boolean) => onRequest(url, method, body, useCookie));
|
||||||
|
connection.on('Notification', (type: string, data: any) => { onReceivedNotification(type, data); });
|
||||||
// --- 尝试启动连接 ---
|
// --- 尝试启动连接 ---
|
||||||
try {
|
try {
|
||||||
await connection.start();
|
await connection.start();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { cookie, isLoadingAccount, useAccount } from '@/api/account'
|
|||||||
import { ThemeType } from '@/api/api-models'
|
import { ThemeType } from '@/api/api-models'
|
||||||
import { QueryGetAPI } from '@/api/query'
|
import { QueryGetAPI } from '@/api/query'
|
||||||
import RegisterAndLogin from '@/components/RegisterAndLogin.vue'
|
import RegisterAndLogin from '@/components/RegisterAndLogin.vue'
|
||||||
|
import { checkUpdateNote } from '@/data/UpdateNote';
|
||||||
import { ACCOUNT_API_URL } from '@/data/constants'
|
import { ACCOUNT_API_URL } from '@/data/constants'
|
||||||
import { useAuthStore } from '@/store/useAuthStore'
|
import { useAuthStore } from '@/store/useAuthStore'
|
||||||
import { useMusicRequestProvider } from '@/store/useMusicRequest'
|
import { useMusicRequestProvider } from '@/store/useMusicRequest'
|
||||||
@@ -485,6 +486,9 @@ onMounted(() => {
|
|||||||
canResendEmail.value = true
|
canResendEmail.value = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 当进入管理页时检查更新日志
|
||||||
|
|
||||||
|
checkUpdateNote();
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ function saveShareImage() {
|
|||||||
// 生成的ba64图片
|
// 生成的ba64图片
|
||||||
canvas.toBlob(
|
canvas.toBlob(
|
||||||
(data) => {
|
(data) => {
|
||||||
saveAs(data, `vtsuru-提问箱-${accountInfo.value?.name}.png`)
|
saveAs(data!, `vtsuru-提问箱-${accountInfo.value?.name}.png`)
|
||||||
},
|
},
|
||||||
'image/png',
|
'image/png',
|
||||||
1,
|
1,
|
||||||
|
|||||||
@@ -183,6 +183,7 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
.question-display-text {
|
.question-display-text {
|
||||||
min-height: 50px;
|
min-height: 50px;
|
||||||
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.question-display-image {
|
.question-display-image {
|
||||||
|
|||||||
@@ -54,6 +54,11 @@ export default defineConfig({
|
|||||||
Components({
|
Components({
|
||||||
resolvers: [NaiveUiResolver()],
|
resolvers: [NaiveUiResolver()],
|
||||||
dts: 'src/components.d.ts',
|
dts: 'src/components.d.ts',
|
||||||
|
// allow auto load markdown components under `./src/components/`
|
||||||
|
extensions: ['vue', 'md'],
|
||||||
|
|
||||||
|
// allow auto import and register components used in markdown
|
||||||
|
include: [/\.vue$/, /\.vue\?vue/, /\.md$/],
|
||||||
}),
|
}),
|
||||||
monacoEditorPlugin({ languageWorkers: ['css'] }),
|
monacoEditorPlugin({ languageWorkers: ['css'] }),
|
||||||
oxlintPlugin(),
|
oxlintPlugin(),
|
||||||
|
|||||||
Reference in New Issue
Block a user