mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-06 18:36:55 +08:00
feat: 添加定期更新检查功能并优化相关逻辑;调整 ESLint 配置以放宽类型检查规则
This commit is contained in:
@@ -61,14 +61,31 @@ export default antfu(
|
|||||||
// TypeScript 相关规则
|
// TypeScript 相关规则
|
||||||
'ts/no-explicit-any': 'off',
|
'ts/no-explicit-any': 'off',
|
||||||
'ts/ban-ts-comment': 'off',
|
'ts/ban-ts-comment': 'off',
|
||||||
|
'ts/no-floating-promises': 'off', // 允许不 await Promise
|
||||||
|
'ts/no-misused-promises': 'off', // 允许在条件表达式中使用 Promise
|
||||||
|
'@typescript-eslint/no-floating-promises': 'off',
|
||||||
|
'@typescript-eslint/no-misused-promises': 'off',
|
||||||
|
|
||||||
// 通用规则
|
// 通用规则
|
||||||
'no-console': 'off',
|
'no-console': 'off',
|
||||||
'unused-imports/no-unused-vars': 'warn',
|
'unused-imports/no-unused-vars': 'warn',
|
||||||
|
'eqeqeq': 'off', // 允许使用 == 和 !=
|
||||||
|
'no-eq-null': 'off', // 允许使用 == null
|
||||||
|
'@typescript-eslint/strict-boolean-expressions': 'off', // 允许宽松的布尔表达式
|
||||||
|
|
||||||
// 关闭一些过于严格的规则
|
// 关闭一些过于严格的规则
|
||||||
'antfu/if-newline': 'off',
|
'antfu/if-newline': 'off',
|
||||||
'style/brace-style': ['error', '1tbs'],
|
'style/brace-style': ['error', '1tbs'],
|
||||||
|
'prefer-promise-reject-errors': 'off', // 允许 reject 任何值
|
||||||
|
'no-throw-literal': 'off', // 允许 throw 任何值
|
||||||
|
'ts/no-unsafe-assignment': 'off', // 允许不安全的赋值
|
||||||
|
'ts/no-unsafe-member-access': 'off', // 允许不安全的成员访问
|
||||||
|
'ts/no-unsafe-call': 'off', // 允许不安全的调用
|
||||||
|
'ts/switch-exhaustiveness-check': 'warn', // 允许 switch 不覆盖所有情况
|
||||||
|
'ts/restrict-template-expressions': 'off', // 允许模板字符串表达式不受限制
|
||||||
|
|
||||||
|
// JSON 相关规则
|
||||||
|
'jsonc/sort-keys': 'off', // 关闭 JSON key 排序要求
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// 集成 VueVine 配置
|
// 集成 VueVine 配置
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
import { openUrl } from '@tauri-apps/plugin-opener'
|
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||||
import { relaunch } from '@tauri-apps/plugin-process'
|
import { relaunch } from '@tauri-apps/plugin-process'
|
||||||
import { check } from '@tauri-apps/plugin-updater'
|
import { check } from '@tauri-apps/plugin-updater'
|
||||||
|
import { h } from 'vue'
|
||||||
import { isLoggedIn, useAccount } from '@/api/account'
|
import { isLoggedIn, useAccount } from '@/api/account'
|
||||||
import { CN_HOST, isDev } from '@/data/constants'
|
import { CN_HOST, isDev } from '@/data/constants'
|
||||||
import { useWebFetcher } from '@/store/useWebFetcher'
|
import { useWebFetcher } from '@/store/useWebFetcher'
|
||||||
@@ -30,6 +31,8 @@ const accountInfo = useAccount()
|
|||||||
export const clientInited = ref(false)
|
export const clientInited = ref(false)
|
||||||
let tray: TrayIcon
|
let tray: TrayIcon
|
||||||
let heartbeatTimer: number | null = null
|
let heartbeatTimer: number | null = null
|
||||||
|
let updateCheckTimer: number | null = null
|
||||||
|
let updateNotificationRef: any = null
|
||||||
|
|
||||||
async function sendHeartbeat() {
|
async function sendHeartbeat() {
|
||||||
try {
|
try {
|
||||||
@@ -58,12 +61,162 @@ export function stopHeartbeat() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function startUpdateCheck() {
|
||||||
|
// 立即检查一次更新
|
||||||
|
void checkUpdatePeriodically()
|
||||||
|
|
||||||
|
// 之后每 6 小时检查一次更新
|
||||||
|
updateCheckTimer = window.setInterval(() => {
|
||||||
|
void checkUpdatePeriodically()
|
||||||
|
}, 6 * 60 * 60 * 1000) // 6 hours
|
||||||
|
info('[更新检查] 定时器已启动,间隔 6 小时')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stopUpdateCheck() {
|
||||||
|
if (updateCheckTimer !== null) {
|
||||||
|
clearInterval(updateCheckTimer)
|
||||||
|
updateCheckTimer = null
|
||||||
|
info('[更新检查] 定时器已停止')
|
||||||
|
}
|
||||||
|
if (updateNotificationRef) {
|
||||||
|
updateNotificationRef.destroy()
|
||||||
|
updateNotificationRef = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkUpdatePeriodically() {
|
||||||
|
try {
|
||||||
|
info('[更新检查] 开始检查更新...')
|
||||||
|
const update = await check()
|
||||||
|
|
||||||
|
if (update) {
|
||||||
|
info(`[更新检查] 发现新版本: ${update.version}`)
|
||||||
|
|
||||||
|
// 发送 Windows 通知
|
||||||
|
const permissionGranted = await isPermissionGranted()
|
||||||
|
if (permissionGranted) {
|
||||||
|
sendNotification({
|
||||||
|
title: 'VTsuru.Client 更新可用',
|
||||||
|
body: `发现新版本 ${update.version},点击通知查看详情`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示不可关闭的 NaiveUI notification
|
||||||
|
if (!updateNotificationRef) {
|
||||||
|
updateNotificationRef = window.$notification.warning({
|
||||||
|
title: '发现新版本',
|
||||||
|
content: `VTsuru.Client ${update.version} 现已可用`,
|
||||||
|
meta: update.date,
|
||||||
|
action: () => {
|
||||||
|
return h('div', { style: 'display: flex; gap: 8px; margin-top: 8px;' }, [
|
||||||
|
h(
|
||||||
|
'button',
|
||||||
|
{
|
||||||
|
class: 'n-button n-button--primary-type n-button--small-type',
|
||||||
|
onClick: () => { void handleUpdateInstall(update) },
|
||||||
|
},
|
||||||
|
'立即更新'
|
||||||
|
),
|
||||||
|
h(
|
||||||
|
'button',
|
||||||
|
{
|
||||||
|
class: 'n-button n-button--default-type n-button--small-type',
|
||||||
|
onClick: () => handleUpdateDismiss(),
|
||||||
|
},
|
||||||
|
'稍后提醒'
|
||||||
|
),
|
||||||
|
])
|
||||||
|
},
|
||||||
|
closable: false,
|
||||||
|
duration: 0, // 不自动关闭
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
info('[更新检查] 当前已是最新版本')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
warn(`[更新检查] 检查更新失败: ${error}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleUpdateInstall(update: any) {
|
||||||
|
try {
|
||||||
|
// 关闭提示
|
||||||
|
if (updateNotificationRef) {
|
||||||
|
updateNotificationRef.destroy()
|
||||||
|
updateNotificationRef = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示下载进度通知
|
||||||
|
let downloaded = 0
|
||||||
|
let contentLength = 0
|
||||||
|
const progressNotification = window.$notification.info({
|
||||||
|
title: '正在下载更新',
|
||||||
|
content: '更新下载中,请稍候...',
|
||||||
|
closable: false,
|
||||||
|
duration: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
info('[更新] 开始下载并安装更新')
|
||||||
|
await update.downloadAndInstall((event: any) => {
|
||||||
|
switch (event.event) {
|
||||||
|
case 'Started':
|
||||||
|
contentLength = event.data.contentLength || 0
|
||||||
|
info(`[更新] 开始下载 ${contentLength} 字节`)
|
||||||
|
break
|
||||||
|
case 'Progress': {
|
||||||
|
downloaded += event.data.chunkLength
|
||||||
|
const progress = contentLength > 0 ? Math.round((downloaded / contentLength) * 100) : 0
|
||||||
|
progressNotification.content = `下载进度: ${progress}% (${Math.round(downloaded / 1024 / 1024)}MB / ${Math.round(contentLength / 1024 / 1024)}MB)`
|
||||||
|
info(`[更新] 已下载 ${downloaded} / ${contentLength} 字节`)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'Finished':
|
||||||
|
info('[更新] 下载完成')
|
||||||
|
progressNotification.content = '下载完成,正在安装...'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
progressNotification.destroy()
|
||||||
|
info('[更新] 更新安装完成,准备重启应用')
|
||||||
|
|
||||||
|
window.$notification.success({
|
||||||
|
title: '更新完成',
|
||||||
|
content: '应用将在 3 秒后重启',
|
||||||
|
duration: 3000,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 延迟 3 秒后重启
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 3000))
|
||||||
|
await relaunch()
|
||||||
|
} catch (error) {
|
||||||
|
warn(`[更新] 安装更新失败: ${error}`)
|
||||||
|
window.$notification.error({
|
||||||
|
title: '更新失败',
|
||||||
|
content: `更新安装失败: ${error}`,
|
||||||
|
duration: 5000,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleUpdateDismiss() {
|
||||||
|
if (updateNotificationRef) {
|
||||||
|
updateNotificationRef.destroy()
|
||||||
|
updateNotificationRef = null
|
||||||
|
}
|
||||||
|
info('[更新] 用户选择稍后更新')
|
||||||
|
}
|
||||||
|
|
||||||
export async function initAll(isOnBoot: boolean) {
|
export async function initAll(isOnBoot: boolean) {
|
||||||
const setting = useSettings()
|
const setting = useSettings()
|
||||||
if (clientInited.value) {
|
if (clientInited.value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
checkUpdate()
|
// 初始检查更新(不阻塞初始化)
|
||||||
|
if (!isDev) {
|
||||||
|
void checkUpdate()
|
||||||
|
}
|
||||||
const appWindow = getCurrentWindow()
|
const appWindow = getCurrentWindow()
|
||||||
let permissionGranted = await isPermissionGranted()
|
let permissionGranted = await isPermissionGranted()
|
||||||
|
|
||||||
@@ -86,7 +239,7 @@ export async function initAll(isOnBoot: boolean) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
initNotificationHandler()
|
initNotificationHandler()
|
||||||
const detach = await attachConsole()
|
await attachConsole()
|
||||||
const settings = useSettings()
|
const settings = useSettings()
|
||||||
const biliCookie = useBiliCookie()
|
const biliCookie = useBiliCookie()
|
||||||
await settings.init()
|
await settings.init()
|
||||||
@@ -135,13 +288,9 @@ export async function initAll(isOnBoot: boolean) {
|
|||||||
tooltip: 'VTsuru 事件收集器',
|
tooltip: 'VTsuru 事件收集器',
|
||||||
icon: iconData,
|
icon: iconData,
|
||||||
action: (event) => {
|
action: (event) => {
|
||||||
switch (event.type) {
|
if (event.type === 'DoubleClick' || event.type === 'Click') {
|
||||||
case 'DoubleClick':
|
appWindow.show()
|
||||||
appWindow.show()
|
appWindow.setFocus()
|
||||||
break
|
|
||||||
case 'Click':
|
|
||||||
appWindow.show()
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -180,6 +329,12 @@ export async function initAll(isOnBoot: boolean) {
|
|||||||
useBiliFunction().init()
|
useBiliFunction().init()
|
||||||
|
|
||||||
//startHeartbeat()
|
//startHeartbeat()
|
||||||
|
|
||||||
|
// 启动定期更新检查
|
||||||
|
if (!isDev) {
|
||||||
|
startUpdateCheck()
|
||||||
|
}
|
||||||
|
|
||||||
clientInited.value = true
|
clientInited.value = true
|
||||||
}
|
}
|
||||||
export function OnClientUnmounted() {
|
export function OnClientUnmounted() {
|
||||||
@@ -188,39 +343,14 @@ export function OnClientUnmounted() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stopHeartbeat()
|
stopHeartbeat()
|
||||||
|
stopUpdateCheck()
|
||||||
tray.close()
|
tray.close()
|
||||||
// useDanmakuWindow().closeWindow();
|
// useDanmakuWindow().closeWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkUpdate() {
|
export async function checkUpdate() {
|
||||||
const update = await check()
|
// 手动检查更新(保留用于手动触发)
|
||||||
console.log(update)
|
await checkUpdatePeriodically()
|
||||||
if (update) {
|
|
||||||
console.log(
|
|
||||||
`found update ${update.version} from ${update.date} with notes ${update.body}`,
|
|
||||||
)
|
|
||||||
let downloaded = 0
|
|
||||||
let contentLength = 0
|
|
||||||
// alternatively we could also call update.download() and update.install() separately
|
|
||||||
await update.downloadAndInstall((event) => {
|
|
||||||
switch (event.event) {
|
|
||||||
case 'Started':
|
|
||||||
contentLength = event.data.contentLength || 0
|
|
||||||
console.log(`started downloading ${event.data.contentLength} bytes`)
|
|
||||||
break
|
|
||||||
case 'Progress':
|
|
||||||
downloaded += event.data.chunkLength
|
|
||||||
console.log(`downloaded ${downloaded} from ${contentLength}`)
|
|
||||||
break
|
|
||||||
case 'Finished':
|
|
||||||
console.log('download finished')
|
|
||||||
break
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log('update installed')
|
|
||||||
await relaunch()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isInitedDanmakuClient = ref(false)
|
export const isInitedDanmakuClient = ref(false)
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export function InitVTsuru() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function InitOther() {
|
async function InitOther() {
|
||||||
if (process.env.NODE_ENV !== 'development' && !window.$route.path.startsWith('/obs')) {
|
if (import.meta.env.MODE !== 'development' && !window.$route.path.startsWith('/obs')) {
|
||||||
const mod = await import('@hyperdx/browser')
|
const mod = await import('@hyperdx/browser')
|
||||||
const HyperDX = (mod as any).default ?? mod
|
const HyperDX = (mod as any).default ?? mod
|
||||||
HyperDX.init({
|
HyperDX.init({
|
||||||
@@ -59,7 +59,7 @@ async function InitOther() {
|
|||||||
advancedNetworkCapture: true, // Capture full HTTP request/response headers and bodies (default false)
|
advancedNetworkCapture: true, // Capture full HTTP request/response headers and bodies (default false)
|
||||||
ignoreUrls: [/localhost/i],
|
ignoreUrls: [/localhost/i],
|
||||||
})
|
})
|
||||||
// 将实例挂到窗口,便于后续设置全局属性(可选)
|
// 将实例挂到窗口,便于后续设置全局属性(可选)
|
||||||
;(window as any).__HyperDX__ = HyperDX
|
;(window as any).__HyperDX__ = HyperDX
|
||||||
}
|
}
|
||||||
// 加载其他数据
|
// 加载其他数据
|
||||||
|
|||||||
@@ -18,7 +18,10 @@ void import('./data/Initializer').then(m => m.InitVTsuru())
|
|||||||
const isTauri = () => (window as any).__TAURI__ !== undefined || (window as any).__TAURI_INTERNAL__ !== undefined || '__TAURI__' in window
|
const isTauri = () => (window as any).__TAURI__ !== undefined || (window as any).__TAURI_INTERNAL__ !== undefined || '__TAURI__' in window
|
||||||
if (isTauri()) {
|
if (isTauri()) {
|
||||||
// 仅在 Tauri 环境下才动态加载相关初始化,避免把 @tauri-apps/* 打入入口
|
// 仅在 Tauri 环境下才动态加载相关初始化,避免把 @tauri-apps/* 打入入口
|
||||||
void import('./client/data/initialize').then(m => m.startHeartbeat())
|
void import('./client/data/initialize').then(m => {
|
||||||
|
m.startHeartbeat();
|
||||||
|
m.checkUpdate();
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
window.$mitt = emitter
|
window.$mitt = emitter
|
||||||
|
|||||||
@@ -410,7 +410,7 @@ onMounted(async () => {
|
|||||||
background: cardBgMedium,
|
background: cardBgMedium,
|
||||||
border: borderSystem.medium,
|
border: borderSystem.medium,
|
||||||
borderRadius: borderRadius.large,
|
borderRadius: borderRadius.large,
|
||||||
boxShadow: shadowSystem.light,
|
boxShadow: 'none',
|
||||||
cursor: item.route ? 'pointer' : 'default',
|
cursor: item.route ? 'pointer' : 'default',
|
||||||
}" hoverable class="feature-card" @click="handleFunctionClick(item)">
|
}" hoverable class="feature-card" @click="handleFunctionClick(item)">
|
||||||
<NFlex vertical>
|
<NFlex vertical>
|
||||||
@@ -643,7 +643,7 @@ onMounted(async () => {
|
|||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding-bottom: 60px;
|
padding-bottom: 60px;
|
||||||
|
|
||||||
&::before
|
&::before
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -664,7 +664,7 @@ onMounted(async () => {
|
|||||||
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
&::before
|
&::before
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -674,11 +674,11 @@ onMounted(async () => {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
|
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
|
||||||
transition: left 0.6s;
|
transition: left 0.6s;
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
transform: translateY(-4px);
|
transform: translateY(-4px);
|
||||||
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
|
||||||
|
|
||||||
&::before
|
&::before
|
||||||
left: 100%;
|
left: 100%;
|
||||||
|
|
||||||
@@ -697,14 +697,14 @@ onMounted(async () => {
|
|||||||
|
|
||||||
.feature-card
|
.feature-card
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
.entry-card
|
.entry-card
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
||||||
@@ -736,7 +736,7 @@ onMounted(async () => {
|
|||||||
:deep(.n-button)
|
:deep(.n-button)
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
@@ -751,13 +751,13 @@ onMounted(async () => {
|
|||||||
.main-container
|
.main-container
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
padding-bottom: 20px;
|
padding-bottom: 20px;
|
||||||
|
|
||||||
.section-title
|
.section-title
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
|
|
||||||
.feature-card:hover
|
.feature-card:hover
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
|
|
||||||
.entry-card:hover
|
.entry-card:hover
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
|
|
||||||
@@ -794,7 +794,7 @@ onMounted(async () => {
|
|||||||
/* 新增样式 */
|
/* 新增样式 */
|
||||||
.section-header
|
.section-header
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
.section-subtitle
|
.section-subtitle
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
|
|
||||||
@@ -840,13 +840,13 @@ onMounted(async () => {
|
|||||||
min-width: 85px;
|
min-width: 85px;
|
||||||
max-width: 95px;
|
max-width: 95px;
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
transform: translateY(-3px);
|
transform: translateY(-3px);
|
||||||
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.15);
|
||||||
border-color: rgba(255, 255, 255, 0.25);
|
border-color: rgba(255, 255, 255, 0.25);
|
||||||
background: rgba(255, 255, 255, 0.15);
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
|
||||||
.streamer-avatar-wrapper img
|
.streamer-avatar-wrapper img
|
||||||
transform: scale(1.08);
|
transform: scale(1.08);
|
||||||
|
|
||||||
@@ -855,7 +855,7 @@ onMounted(async () => {
|
|||||||
width: 50px;
|
width: 50px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
||||||
img
|
img
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -897,10 +897,10 @@ onMounted(async () => {
|
|||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: rgba(255, 255, 255, 0.5);
|
background: rgba(255, 255, 255, 0.5);
|
||||||
animation: pulse-dot 1.8s ease-in-out infinite;
|
animation: pulse-dot 1.8s ease-in-out infinite;
|
||||||
|
|
||||||
&:nth-child(2)
|
&:nth-child(2)
|
||||||
animation-delay: 0.3s;
|
animation-delay: 0.3s;
|
||||||
|
|
||||||
&:nth-child(3)
|
&:nth-child(3)
|
||||||
animation-delay: 0.6s;
|
animation-delay: 0.6s;
|
||||||
|
|
||||||
@@ -917,12 +917,12 @@ onMounted(async () => {
|
|||||||
.streamers-grid-modern
|
.streamers-grid-modern
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 0 4px;
|
padding: 0 4px;
|
||||||
|
|
||||||
.streamer-card-modern
|
.streamer-card-modern
|
||||||
min-width: 80px;
|
min-width: 80px;
|
||||||
max-width: 90px;
|
max-width: 90px;
|
||||||
padding: 8px 6px;
|
padding: 8px 6px;
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
|
|
||||||
@@ -930,16 +930,16 @@ onMounted(async () => {
|
|||||||
.streamers-grid-modern
|
.streamers-grid-modern
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
padding: 0 4px;
|
padding: 0 4px;
|
||||||
|
|
||||||
.streamer-card-modern
|
.streamer-card-modern
|
||||||
min-width: 75px;
|
min-width: 75px;
|
||||||
max-width: 85px;
|
max-width: 85px;
|
||||||
padding: 8px 6px;
|
padding: 8px 6px;
|
||||||
|
|
||||||
.streamer-avatar-wrapper
|
.streamer-avatar-wrapper
|
||||||
width: 45px;
|
width: 45px;
|
||||||
height: 45px;
|
height: 45px;
|
||||||
|
|
||||||
.streamer-name
|
.streamer-name
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
|
|
||||||
|
|||||||
@@ -516,7 +516,7 @@ onUnmounted(() => {
|
|||||||
</NSpace>
|
</NSpace>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<NDivider />
|
<NDivider />
|
||||||
|
|
||||||
<NSpin :show="loading">
|
<NSpin :show="loading">
|
||||||
<!-- 空状态 -->
|
<!-- 空状态 -->
|
||||||
<NEmpty
|
<NEmpty
|
||||||
@@ -637,7 +637,7 @@ onUnmounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</NCard>
|
</NCard>
|
||||||
</NGridItem>
|
</NGridItem>
|
||||||
|
|
||||||
<NGridItem>
|
<NGridItem>
|
||||||
<NCard
|
<NCard
|
||||||
title="近30天统计"
|
title="近30天统计"
|
||||||
@@ -734,7 +734,7 @@ onUnmounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</NCard>
|
</NCard>
|
||||||
</NGridItem>
|
</NGridItem>
|
||||||
|
|
||||||
<NGridItem>
|
<NGridItem>
|
||||||
<NCard
|
<NCard
|
||||||
title="关键指标"
|
title="关键指标"
|
||||||
@@ -809,9 +809,9 @@ onUnmounted(() => {
|
|||||||
</NGridItem>
|
</NGridItem>
|
||||||
</NGrid>
|
</NGrid>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<NDivider />
|
<NDivider />
|
||||||
|
|
||||||
<!-- 图表选择器 -->
|
<!-- 图表选择器 -->
|
||||||
<div class="chart-selector">
|
<div class="chart-selector">
|
||||||
<NTabs
|
<NTabs
|
||||||
|
|||||||
@@ -12,13 +12,20 @@
|
|||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"types": ["node", "vue-vine/macros", "jszip"],
|
"types": ["node", "vue-vine/macros", "jszip"],
|
||||||
"allowJs": false,
|
"allowJs": false,
|
||||||
"strict": true,
|
"strict": false,
|
||||||
|
"alwaysStrict": false,
|
||||||
|
"noImplicitAny": false,
|
||||||
|
"noImplicitThis": false,
|
||||||
|
"strictBindCallApply": false,
|
||||||
|
"strictFunctionTypes": false,
|
||||||
|
"strictNullChecks": false,
|
||||||
|
"strictPropertyInitialization": false,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"skipLibCheck": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"sourceMap": true,
|
"isolatedModules": true,
|
||||||
"isolatedModules": true
|
"skipLibCheck": true,
|
||||||
|
"sourceMap": true
|
||||||
},
|
},
|
||||||
"references": [{ "path": "./tsconfig.node.json" }],
|
"references": [{ "path": "./tsconfig.node.json" }],
|
||||||
"include": [
|
"include": [
|
||||||
|
|||||||
Reference in New Issue
Block a user