2026-04-03 00:39:15 +08:00
|
|
|
|
<script setup lang="ts">
|
2026-05-31 19:28:35 +08:00
|
|
|
|
import { DownloadOutlined, ReloadOutlined, SearchOutlined } from '@ant-design/icons-vue'
|
|
|
|
|
|
import type {
|
|
|
|
|
|
DesktopAccountInfo,
|
|
|
|
|
|
DesktopClientDownloadableRelease,
|
|
|
|
|
|
MediaPlatform,
|
|
|
|
|
|
} from '@geo/shared-types'
|
2026-05-01 20:39:09 +08:00
|
|
|
|
import { useQuery } from '@tanstack/vue-query'
|
2026-05-31 19:28:35 +08:00
|
|
|
|
import { message } from 'ant-design-vue'
|
2026-05-01 20:39:09 +08:00
|
|
|
|
import { computed, ref } from 'vue'
|
|
|
|
|
|
import { useI18n } from 'vue-i18n'
|
|
|
|
|
|
|
2026-05-31 19:28:35 +08:00
|
|
|
|
import { desktopClientApi, mediaApi, resolveApiURL, tenantAccountsApi } from '@/lib/api'
|
2026-05-01 20:39:09 +08:00
|
|
|
|
import { resolveAccountCheckedAt, resolveAccountHealth } from '@/lib/desktop-account-runtime'
|
2026-05-31 19:28:35 +08:00
|
|
|
|
import { formatBytes, formatDateTime } from '@/lib/display'
|
2026-05-01 20:39:09 +08:00
|
|
|
|
import {
|
|
|
|
|
|
getPublishPlatformMeta,
|
|
|
|
|
|
isMediaPublishAccount,
|
|
|
|
|
|
normalizePublishPlatformId,
|
|
|
|
|
|
} from '@/lib/publish-platforms'
|
|
|
|
|
|
|
|
|
|
|
|
type AccountHealthFilter = 'all' | 'live' | 'captcha' | 'risk' | 'expired'
|
|
|
|
|
|
type PublishState = 'immediate' | 'queued' | 'unavailable'
|
2026-04-20 09:52:48 +08:00
|
|
|
|
|
|
|
|
|
|
interface MediaAccountCard {
|
2026-05-01 20:39:09 +08:00
|
|
|
|
id: string
|
|
|
|
|
|
platform: string
|
|
|
|
|
|
platformLabel: string
|
|
|
|
|
|
platformShortName: string
|
|
|
|
|
|
platformAccent: string
|
|
|
|
|
|
platformLogoUrl: string | null
|
|
|
|
|
|
displayName: string
|
|
|
|
|
|
platformUid: string
|
|
|
|
|
|
avatarUrl: string | null
|
|
|
|
|
|
health: DesktopAccountInfo['health']
|
|
|
|
|
|
verifiedAt: string | null
|
|
|
|
|
|
clientId: string | null
|
|
|
|
|
|
clientOnline: boolean | null
|
|
|
|
|
|
clientDeviceName: string | null
|
|
|
|
|
|
clientLastSeenAt: string | null
|
|
|
|
|
|
publishState: PublishState
|
|
|
|
|
|
publishHint: string
|
|
|
|
|
|
deleteRequestedAt: string | null
|
2026-04-20 09:52:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const platformLogoCatalog: Record<string, string> = {
|
2026-05-01 20:39:09 +08:00
|
|
|
|
toutiaohao: '/logos/logo_toutiao.png',
|
|
|
|
|
|
baijiahao: '/logos/logo_baijiahao.png',
|
|
|
|
|
|
sohuhao: '/logos/logo_souhu.png',
|
|
|
|
|
|
qiehao: '/logos/logo_qiehao.png',
|
|
|
|
|
|
zhihu: '/logos/logo_zhihu.png',
|
|
|
|
|
|
wangyihao: '/logos/logo_wangyihao.png',
|
|
|
|
|
|
jianshu: '/logos/logo_jianshu.svg',
|
|
|
|
|
|
bilibili: '/logos/logo_bilibili.png',
|
|
|
|
|
|
juejin: '/logos/logo_juejin.png',
|
|
|
|
|
|
smzdm: '/logos/logo_smzdm.svg',
|
|
|
|
|
|
weixin_gzh: '/logos/logo_weixin_gzh.svg',
|
|
|
|
|
|
zol: '/logos/logo_zol.png',
|
|
|
|
|
|
dongchedi: '/logos/logo_dongchedi.svg',
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const { t } = useI18n()
|
|
|
|
|
|
|
|
|
|
|
|
const searchQuery = ref('')
|
|
|
|
|
|
const selectedHealth = ref<AccountHealthFilter>('all')
|
2026-05-31 19:28:35 +08:00
|
|
|
|
const downloadingReleaseKey = ref<string | null>(null)
|
2026-04-03 00:39:15 +08:00
|
|
|
|
|
|
|
|
|
|
const platformsQuery = useQuery({
|
2026-05-01 20:39:09 +08:00
|
|
|
|
queryKey: ['media', 'platforms', 'media-view'],
|
2026-04-03 00:39:15 +08:00
|
|
|
|
queryFn: () => mediaApi.platforms(),
|
2026-05-01 20:39:09 +08:00
|
|
|
|
})
|
2026-04-03 00:39:15 +08:00
|
|
|
|
|
|
|
|
|
|
const accountsQuery = useQuery({
|
2026-05-01 20:39:09 +08:00
|
|
|
|
queryKey: ['tenant', 'desktop-accounts', 'media-view'],
|
2026-04-20 09:52:48 +08:00
|
|
|
|
queryFn: () => tenantAccountsApi.list(),
|
2026-05-13 19:36:24 +08:00
|
|
|
|
staleTime: 0,
|
|
|
|
|
|
refetchInterval: 5_000,
|
|
|
|
|
|
refetchOnWindowFocus: true,
|
2026-05-01 20:39:09 +08:00
|
|
|
|
})
|
2026-04-03 00:39:15 +08:00
|
|
|
|
|
2026-05-31 19:28:35 +08:00
|
|
|
|
const downloadableReleasesQuery = useQuery({
|
|
|
|
|
|
queryKey: ['tenant', 'desktop-client', 'downloadable-releases', 'stable'],
|
|
|
|
|
|
queryFn: async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
return await desktopClientApi.downloadableReleases('stable')
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return desktopClientApi.publicFallbackDownloadableReleases()
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
staleTime: 60_000,
|
|
|
|
|
|
refetchOnWindowFocus: true,
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-05-01 20:39:09 +08:00
|
|
|
|
const loading = computed(() => platformsQuery.isPending.value || accountsQuery.isPending.value)
|
2026-05-31 19:28:35 +08:00
|
|
|
|
const refreshing = computed(
|
|
|
|
|
|
() =>
|
|
|
|
|
|
platformsQuery.isFetching.value ||
|
|
|
|
|
|
accountsQuery.isFetching.value ||
|
|
|
|
|
|
downloadableReleasesQuery.isFetching.value,
|
|
|
|
|
|
)
|
2026-04-03 00:39:15 +08:00
|
|
|
|
|
2026-04-20 09:52:48 +08:00
|
|
|
|
const platformMap = computed(() => {
|
|
|
|
|
|
return new Map(
|
|
|
|
|
|
(platformsQuery.data.value ?? []).map((platform: MediaPlatform) => [
|
|
|
|
|
|
normalizePublishPlatformId(platform.platform_id),
|
|
|
|
|
|
platform,
|
|
|
|
|
|
]),
|
2026-05-01 20:39:09 +08:00
|
|
|
|
)
|
|
|
|
|
|
})
|
2026-04-03 00:39:15 +08:00
|
|
|
|
|
2026-04-20 09:52:48 +08:00
|
|
|
|
const mediaAccounts = computed<MediaAccountCard[]>(() => {
|
|
|
|
|
|
return [...(accountsQuery.data.value ?? [])]
|
2026-05-01 16:01:40 +08:00
|
|
|
|
.filter(isMediaPublishAccount)
|
2026-04-20 09:52:48 +08:00
|
|
|
|
.map((account) => {
|
2026-05-01 20:39:09 +08:00
|
|
|
|
const platformId = normalizePublishPlatformId(account.platform)
|
|
|
|
|
|
const platform = platformMap.value.get(platformId)
|
|
|
|
|
|
const fallback = getPublishPlatformMeta(platformId)
|
|
|
|
|
|
const publishState = resolvePublishState(account)
|
|
|
|
|
|
const health = resolveAccountHealth(account)
|
2026-04-20 09:52:48 +08:00
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
id: account.id,
|
|
|
|
|
|
platform: platformId,
|
|
|
|
|
|
platformLabel: platform?.name || fallback.name,
|
|
|
|
|
|
platformShortName: platform?.short_name || fallback.shortName,
|
|
|
|
|
|
platformAccent: platform?.accent_color || fallback.accent,
|
|
|
|
|
|
platformLogoUrl: resolvePlatformLogoUrl(platformId, platform?.logo_url),
|
|
|
|
|
|
displayName: account.display_name,
|
|
|
|
|
|
platformUid: normalizePlatformUid(account.platform_uid),
|
|
|
|
|
|
avatarUrl: resolveApiURL(account.avatar_url),
|
2026-04-27 21:34:05 +08:00
|
|
|
|
health,
|
|
|
|
|
|
verifiedAt: resolveAccountCheckedAt(account),
|
2026-04-20 09:52:48 +08:00
|
|
|
|
clientId: account.client_id,
|
|
|
|
|
|
clientOnline: account.client_online,
|
|
|
|
|
|
clientDeviceName: account.client_device_name,
|
|
|
|
|
|
clientLastSeenAt: account.client_last_seen_at,
|
|
|
|
|
|
publishState,
|
|
|
|
|
|
publishHint: resolvePublishHint(account, publishState),
|
|
|
|
|
|
deleteRequestedAt: account.delete_requested_at,
|
2026-05-01 20:39:09 +08:00
|
|
|
|
}
|
2026-04-20 09:52:48 +08:00
|
|
|
|
})
|
|
|
|
|
|
.sort((left, right) => {
|
2026-05-01 20:39:09 +08:00
|
|
|
|
const publishGap = publishRank(left.publishState) - publishRank(right.publishState)
|
2026-04-20 09:52:48 +08:00
|
|
|
|
if (publishGap !== 0) {
|
2026-05-01 20:39:09 +08:00
|
|
|
|
return publishGap
|
2026-04-20 09:52:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-01 20:39:09 +08:00
|
|
|
|
const leftAt = Date.parse(left.verifiedAt ?? '') || 0
|
|
|
|
|
|
const rightAt = Date.parse(right.verifiedAt ?? '') || 0
|
|
|
|
|
|
return rightAt - leftAt
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
2026-04-03 00:39:15 +08:00
|
|
|
|
|
2026-04-20 09:52:48 +08:00
|
|
|
|
const overview = computed(() => ({
|
|
|
|
|
|
total: mediaAccounts.value.length,
|
2026-05-01 20:39:09 +08:00
|
|
|
|
live: mediaAccounts.value.filter((item) => item.health === 'live').length,
|
|
|
|
|
|
immediate: mediaAccounts.value.filter((item) => item.publishState === 'immediate').length,
|
|
|
|
|
|
queued: mediaAccounts.value.filter((item) => item.publishState === 'queued').length,
|
|
|
|
|
|
attention: mediaAccounts.value.filter((item) => item.publishState === 'unavailable').length,
|
|
|
|
|
|
}))
|
2026-04-03 00:39:15 +08:00
|
|
|
|
|
2026-04-20 09:52:48 +08:00
|
|
|
|
const filteredAccounts = computed(() => {
|
2026-05-01 20:39:09 +08:00
|
|
|
|
const keyword = searchQuery.value.trim().toLowerCase()
|
2026-04-20 09:52:48 +08:00
|
|
|
|
|
|
|
|
|
|
return mediaAccounts.value.filter((account) => {
|
2026-05-01 20:39:09 +08:00
|
|
|
|
if (selectedHealth.value !== 'all' && account.health !== selectedHealth.value) {
|
|
|
|
|
|
return false
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
2026-04-20 09:52:48 +08:00
|
|
|
|
|
|
|
|
|
|
if (!keyword) {
|
2026-05-01 20:39:09 +08:00
|
|
|
|
return true
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
2026-04-20 09:52:48 +08:00
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
|
account.displayName,
|
|
|
|
|
|
account.platformLabel,
|
|
|
|
|
|
account.platformUid,
|
2026-05-01 20:39:09 +08:00
|
|
|
|
account.clientDeviceName ?? '',
|
|
|
|
|
|
]
|
|
|
|
|
|
.join(' ')
|
|
|
|
|
|
.toLowerCase()
|
|
|
|
|
|
.includes(keyword)
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
2026-04-03 00:39:15 +08:00
|
|
|
|
|
2026-05-31 19:28:35 +08:00
|
|
|
|
const downloadableReleases = computed(() => downloadableReleasesQuery.data.value?.items ?? [])
|
|
|
|
|
|
const downloadableReleasesError = computed(() => {
|
|
|
|
|
|
const error = downloadableReleasesQuery.error.value
|
|
|
|
|
|
if (!error) {
|
|
|
|
|
|
return ''
|
|
|
|
|
|
}
|
|
|
|
|
|
if (error instanceof Error) {
|
|
|
|
|
|
return error.message
|
|
|
|
|
|
}
|
|
|
|
|
|
return String(error)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-04-20 09:52:48 +08:00
|
|
|
|
function resolvePublishState(account: DesktopAccountInfo): PublishState {
|
2026-05-01 20:39:09 +08:00
|
|
|
|
if (resolveAccountHealth(account) !== 'live') {
|
|
|
|
|
|
return 'unavailable'
|
2026-04-20 09:52:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
if (!account.client_id) {
|
2026-05-01 20:39:09 +08:00
|
|
|
|
return 'unavailable'
|
2026-04-20 09:52:48 +08:00
|
|
|
|
}
|
2026-05-01 20:39:09 +08:00
|
|
|
|
return account.client_online === true ? 'immediate' : 'queued'
|
2026-04-20 09:52:48 +08:00
|
|
|
|
}
|
2026-04-03 00:39:15 +08:00
|
|
|
|
|
2026-04-20 09:52:48 +08:00
|
|
|
|
function resolvePublishHint(account: DesktopAccountInfo, publishState: PublishState): string {
|
2026-05-01 20:39:09 +08:00
|
|
|
|
if (publishState === 'immediate') {
|
|
|
|
|
|
return ''
|
2026-04-25 18:09:12 +08:00
|
|
|
|
}
|
2026-05-01 20:39:09 +08:00
|
|
|
|
if (publishState === 'queued') {
|
|
|
|
|
|
return '客户端当前离线,任务会先进入发布队列,等桌面端上线后自动继续。'
|
2026-04-25 18:09:12 +08:00
|
|
|
|
}
|
2026-05-01 20:39:09 +08:00
|
|
|
|
if (resolveAccountHealth(account) !== 'live') {
|
|
|
|
|
|
return '授权状态异常,先在桌面端重新校验后再发布。'
|
2026-04-20 09:52:48 +08:00
|
|
|
|
}
|
2026-05-01 20:39:09 +08:00
|
|
|
|
return '还没有绑定桌面客户端,当前无法加入发布队列。'
|
2026-04-20 09:52:48 +08:00
|
|
|
|
}
|
2026-04-03 00:39:15 +08:00
|
|
|
|
|
2026-04-20 09:52:48 +08:00
|
|
|
|
function normalizePlatformUid(value?: string | null): string {
|
2026-05-01 20:39:09 +08:00
|
|
|
|
let normalized = String(value ?? '').trim()
|
|
|
|
|
|
while (normalized.toLowerCase().startsWith('platform:')) {
|
|
|
|
|
|
normalized = normalized.slice('platform:'.length).trim()
|
2026-04-20 09:52:48 +08:00
|
|
|
|
}
|
2026-05-01 20:39:09 +08:00
|
|
|
|
return normalized || '--'
|
2026-04-20 09:52:48 +08:00
|
|
|
|
}
|
2026-04-03 00:39:15 +08:00
|
|
|
|
|
2026-04-20 09:52:48 +08:00
|
|
|
|
function resolvePlatformLogoUrl(platformId: string, remoteLogoUrl?: string | null): string | null {
|
2026-05-01 20:39:09 +08:00
|
|
|
|
const localLogoUrl = platformLogoCatalog[platformId]
|
2026-04-20 09:52:48 +08:00
|
|
|
|
if (localLogoUrl) {
|
2026-05-01 20:39:09 +08:00
|
|
|
|
return localLogoUrl
|
2026-04-20 09:52:48 +08:00
|
|
|
|
}
|
2026-04-03 00:39:15 +08:00
|
|
|
|
|
2026-05-01 20:39:09 +08:00
|
|
|
|
const resolvedRemoteLogoUrl = resolveApiURL(remoteLogoUrl)
|
|
|
|
|
|
return resolvedRemoteLogoUrl || null
|
2026-04-20 09:52:48 +08:00
|
|
|
|
}
|
2026-04-03 00:39:15 +08:00
|
|
|
|
|
2026-04-20 09:52:48 +08:00
|
|
|
|
function accountInitial(account: MediaAccountCard): string {
|
2026-05-01 20:39:09 +08:00
|
|
|
|
const name = account.displayName.trim()
|
|
|
|
|
|
return name ? name.slice(0, 1).toUpperCase() : account.platformShortName
|
2026-04-20 09:52:48 +08:00
|
|
|
|
}
|
2026-04-03 00:39:15 +08:00
|
|
|
|
|
2026-04-20 09:52:48 +08:00
|
|
|
|
function publishRank(state: PublishState): number {
|
|
|
|
|
|
switch (state) {
|
2026-05-01 20:39:09 +08:00
|
|
|
|
case 'immediate':
|
|
|
|
|
|
return 0
|
|
|
|
|
|
case 'queued':
|
|
|
|
|
|
return 1
|
2026-04-20 09:52:48 +08:00
|
|
|
|
default:
|
2026-05-01 20:39:09 +08:00
|
|
|
|
return 2
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
2026-04-20 09:52:48 +08:00
|
|
|
|
}
|
2026-04-03 00:39:15 +08:00
|
|
|
|
|
2026-05-01 20:39:09 +08:00
|
|
|
|
function healthLabel(health: DesktopAccountInfo['health']): string {
|
2026-04-20 09:52:48 +08:00
|
|
|
|
switch (health) {
|
2026-05-01 20:39:09 +08:00
|
|
|
|
case 'live':
|
|
|
|
|
|
return '授权正常'
|
|
|
|
|
|
case 'captcha':
|
|
|
|
|
|
return '待处理'
|
|
|
|
|
|
case 'risk':
|
|
|
|
|
|
return '风险观察'
|
2026-04-20 09:52:48 +08:00
|
|
|
|
default:
|
2026-05-01 20:39:09 +08:00
|
|
|
|
return '授权异常'
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
2026-04-20 09:52:48 +08:00
|
|
|
|
}
|
2026-04-03 00:39:15 +08:00
|
|
|
|
|
2026-05-01 20:39:09 +08:00
|
|
|
|
function healthColor(health: DesktopAccountInfo['health']): string {
|
2026-04-20 09:52:48 +08:00
|
|
|
|
switch (health) {
|
2026-05-01 20:39:09 +08:00
|
|
|
|
case 'live':
|
|
|
|
|
|
return 'green'
|
|
|
|
|
|
case 'captcha':
|
|
|
|
|
|
return 'orange'
|
|
|
|
|
|
case 'risk':
|
|
|
|
|
|
return 'gold'
|
2026-04-20 09:52:48 +08:00
|
|
|
|
default:
|
2026-05-01 20:39:09 +08:00
|
|
|
|
return 'red'
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
2026-04-20 09:52:48 +08:00
|
|
|
|
}
|
2026-04-03 00:39:15 +08:00
|
|
|
|
|
2026-04-20 09:52:48 +08:00
|
|
|
|
function clientStatusLabel(account: MediaAccountCard): string {
|
|
|
|
|
|
if (!account.clientId) {
|
2026-05-01 20:39:09 +08:00
|
|
|
|
return '未绑定'
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
2026-05-01 20:39:09 +08:00
|
|
|
|
return account.clientOnline ? '在线' : '离线'
|
2026-04-20 09:52:48 +08:00
|
|
|
|
}
|
2026-04-03 00:39:15 +08:00
|
|
|
|
|
2026-04-20 09:52:48 +08:00
|
|
|
|
function clientStatusColor(account: MediaAccountCard): string {
|
|
|
|
|
|
if (!account.clientId) {
|
2026-05-01 20:39:09 +08:00
|
|
|
|
return 'default'
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
2026-05-01 20:39:09 +08:00
|
|
|
|
return account.clientOnline ? 'green' : 'gold'
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-20 09:52:48 +08:00
|
|
|
|
function clientStatusText(account: MediaAccountCard): string {
|
|
|
|
|
|
if (!account.clientId) {
|
2026-05-01 20:39:09 +08:00
|
|
|
|
return '未绑定桌面客户端'
|
2026-04-20 09:52:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-01 20:39:09 +08:00
|
|
|
|
const device = account.clientDeviceName?.trim()
|
|
|
|
|
|
return device ? `${clientStatusLabel(account)} · ${device}` : clientStatusLabel(account)
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-20 09:52:48 +08:00
|
|
|
|
function publishStateLabel(state: PublishState): string {
|
|
|
|
|
|
switch (state) {
|
2026-05-01 20:39:09 +08:00
|
|
|
|
case 'immediate':
|
|
|
|
|
|
return '可立即发布'
|
|
|
|
|
|
case 'queued':
|
|
|
|
|
|
return '离线排队'
|
2026-04-20 09:52:48 +08:00
|
|
|
|
default:
|
2026-05-01 20:39:09 +08:00
|
|
|
|
return '不可发布'
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-20 09:52:48 +08:00
|
|
|
|
function publishStateColor(state: PublishState): string {
|
|
|
|
|
|
switch (state) {
|
2026-05-01 20:39:09 +08:00
|
|
|
|
case 'immediate':
|
|
|
|
|
|
return 'green'
|
|
|
|
|
|
case 'queued':
|
|
|
|
|
|
return 'gold'
|
2026-04-20 09:52:48 +08:00
|
|
|
|
default:
|
2026-05-01 20:39:09 +08:00
|
|
|
|
return 'red'
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-20 09:52:48 +08:00
|
|
|
|
async function refreshAll(): Promise<void> {
|
2026-05-31 19:28:35 +08:00
|
|
|
|
await Promise.all([
|
|
|
|
|
|
platformsQuery.refetch(),
|
|
|
|
|
|
accountsQuery.refetch(),
|
|
|
|
|
|
downloadableReleasesQuery.refetch(),
|
|
|
|
|
|
])
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function downloadRelease(release: DesktopClientDownloadableRelease): Promise<void> {
|
|
|
|
|
|
const key = releaseDownloadKey(release)
|
|
|
|
|
|
if (downloadingReleaseKey.value) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
downloadingReleaseKey.value = key
|
|
|
|
|
|
const downloadWindow = window.open('about:blank', '_blank')
|
|
|
|
|
|
if (downloadWindow) {
|
|
|
|
|
|
downloadWindow.opener = null
|
|
|
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
|
|
|
const result = await desktopClientApi.publicDownloadURL({
|
|
|
|
|
|
platform: release.platform,
|
|
|
|
|
|
arch: release.arch,
|
|
|
|
|
|
channel: release.channel,
|
|
|
|
|
|
})
|
|
|
|
|
|
const downloadURL = String(result.download_url ?? '').trim()
|
|
|
|
|
|
if (!downloadURL) {
|
|
|
|
|
|
message.warning('下载地址为空')
|
|
|
|
|
|
downloadWindow?.close()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if (downloadWindow) {
|
|
|
|
|
|
downloadWindow.location.href = downloadURL
|
|
|
|
|
|
} else {
|
|
|
|
|
|
window.open(downloadURL, '_blank', 'noopener,noreferrer')
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
downloadWindow?.close()
|
|
|
|
|
|
const detail =
|
|
|
|
|
|
typeof error === 'object' && error !== null && 'detail' in error ? error.detail : null
|
|
|
|
|
|
const reason =
|
|
|
|
|
|
typeof detail === 'string' && detail.trim()
|
|
|
|
|
|
? detail
|
|
|
|
|
|
: error instanceof Error
|
|
|
|
|
|
? error.message
|
|
|
|
|
|
: '请稍后重试'
|
|
|
|
|
|
message.error(`获取下载地址失败:${reason}`)
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
if (downloadingReleaseKey.value === key) {
|
|
|
|
|
|
downloadingReleaseKey.value = null
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function releaseDownloadKey(release: DesktopClientDownloadableRelease): string {
|
|
|
|
|
|
return `${release.platform}:${release.arch}:${release.channel}`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function releaseTargetLabel(release: DesktopClientDownloadableRelease): string {
|
|
|
|
|
|
return `${releasePlatformLabel(release.platform)} ${releaseArchLabel(release.arch)}`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function releasePlatformLabel(platform: string): string {
|
|
|
|
|
|
switch (platform) {
|
|
|
|
|
|
case 'darwin':
|
|
|
|
|
|
return 'macOS'
|
|
|
|
|
|
case 'win32':
|
|
|
|
|
|
return 'Windows'
|
|
|
|
|
|
case 'linux':
|
|
|
|
|
|
return 'Linux'
|
|
|
|
|
|
default:
|
|
|
|
|
|
return platform
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function releasePlatformMark(platform: string): string {
|
|
|
|
|
|
switch (platform) {
|
|
|
|
|
|
case 'darwin':
|
|
|
|
|
|
return 'MAC'
|
|
|
|
|
|
case 'win32':
|
|
|
|
|
|
return 'WIN'
|
|
|
|
|
|
case 'linux':
|
|
|
|
|
|
return 'LIN'
|
|
|
|
|
|
default:
|
|
|
|
|
|
return platform.slice(0, 3).toUpperCase()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function releasePlatformClass(platform: string): string {
|
|
|
|
|
|
return ['darwin', 'win32', 'linux'].includes(platform) ? platform : 'default'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function releaseArchLabel(arch: string): string {
|
|
|
|
|
|
switch (arch) {
|
|
|
|
|
|
case 'arm64':
|
|
|
|
|
|
return 'ARM64'
|
|
|
|
|
|
case 'x64':
|
|
|
|
|
|
return 'x64'
|
|
|
|
|
|
case 'ia32':
|
|
|
|
|
|
return 'ia32'
|
|
|
|
|
|
case 'universal':
|
|
|
|
|
|
return 'Universal'
|
|
|
|
|
|
default:
|
|
|
|
|
|
return arch
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function releaseDate(release: DesktopClientDownloadableRelease): string {
|
|
|
|
|
|
return formatDateTime(release.published_at ?? release.updated_at)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function releaseSize(value?: number | null): string {
|
|
|
|
|
|
return typeof value === 'number' && Number.isFinite(value) ? formatBytes(value) : '--'
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<div class="media-view">
|
2026-05-31 19:28:35 +08:00
|
|
|
|
<!-- Premium Dark Banner Section -->
|
2026-04-03 00:39:15 +08:00
|
|
|
|
<section class="media-view__top-card">
|
2026-05-31 19:28:35 +08:00
|
|
|
|
<div class="banner-gradient-orbs">
|
|
|
|
|
|
<div class="orb orb-1"></div>
|
|
|
|
|
|
<div class="orb orb-2"></div>
|
|
|
|
|
|
</div>
|
2026-04-03 00:39:15 +08:00
|
|
|
|
<div class="media-view__header">
|
|
|
|
|
|
<div class="media-view__header-title">
|
2026-05-31 19:28:35 +08:00
|
|
|
|
<h2>
|
|
|
|
|
|
{{ t('route.media.title') }}
|
|
|
|
|
|
</h2>
|
2026-05-01 20:39:09 +08:00
|
|
|
|
<p>
|
2026-05-31 19:28:35 +08:00
|
|
|
|
展示客户端里当前已绑定的媒体账号,集成发布队列状态,提供多渠道账号集中式生命周期管理。
|
2026-05-01 20:39:09 +08:00
|
|
|
|
</p>
|
2026-04-03 00:39:15 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="media-view__header-actions">
|
2026-05-31 19:28:35 +08:00
|
|
|
|
<a-button class="btn-refresh" :loading="refreshing" @click="refreshAll">
|
2026-04-03 00:39:15 +08:00
|
|
|
|
<template #icon><ReloadOutlined /></template>
|
2026-04-20 09:52:48 +08:00
|
|
|
|
刷新状态
|
2026-04-03 00:39:15 +08:00
|
|
|
|
</a-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-04-20 09:52:48 +08:00
|
|
|
|
|
|
|
|
|
|
<div class="overview-strip border-t">
|
|
|
|
|
|
<div class="overview-chip">
|
2026-05-31 19:28:35 +08:00
|
|
|
|
<div class="chip-icon-box total-bg">
|
|
|
|
|
|
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
|
|
|
|
|
|
<path d="M12 2A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2zm1 14h-2v-6h2v6zm0-8h-2V6h2v2z"/>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="chip-copy">
|
|
|
|
|
|
<span>账号总数</span>
|
|
|
|
|
|
<strong>{{ overview.total }}</strong>
|
|
|
|
|
|
</div>
|
2026-04-20 09:52:48 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="overview-chip">
|
2026-05-31 19:28:35 +08:00
|
|
|
|
<div class="chip-icon-box live-bg">
|
|
|
|
|
|
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
|
|
|
|
|
|
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="chip-copy">
|
|
|
|
|
|
<span>可立即发布</span>
|
|
|
|
|
|
<strong>{{ overview.immediate }}</strong>
|
|
|
|
|
|
</div>
|
2026-04-20 09:52:48 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="overview-chip">
|
2026-05-31 19:28:35 +08:00
|
|
|
|
<div class="chip-icon-box queued-bg">
|
|
|
|
|
|
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
|
|
|
|
|
|
<path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67z"/>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="chip-copy">
|
|
|
|
|
|
<span>离线排队</span>
|
|
|
|
|
|
<strong>{{ overview.queued }}</strong>
|
|
|
|
|
|
</div>
|
2026-04-20 09:52:48 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="overview-chip">
|
2026-05-31 19:28:35 +08:00
|
|
|
|
<div class="chip-icon-box attention-bg">
|
|
|
|
|
|
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
|
|
|
|
|
|
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="chip-copy">
|
|
|
|
|
|
<span>需处理</span>
|
|
|
|
|
|
<strong>{{ overview.attention }}</strong>
|
|
|
|
|
|
</div>
|
2026-04-20 09:52:48 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-04-03 00:39:15 +08:00
|
|
|
|
</section>
|
|
|
|
|
|
|
2026-05-31 19:28:35 +08:00
|
|
|
|
<!-- Client Download Section (App Store Style) -->
|
|
|
|
|
|
<section class="panel client-download-panel">
|
|
|
|
|
|
<div class="client-download-head">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<div class="title-with-badge">
|
|
|
|
|
|
<h3 class="panel-title">客户端下载</h3>
|
|
|
|
|
|
<span class="badge-pulsing-stable">Stable</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p class="panel-desc">高并发发布与任务执行节点,版本根据后台发布配置自动更新与推送。</p>
|
|
|
|
|
|
</div>
|
2026-04-03 00:39:15 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-05-31 19:28:35 +08:00
|
|
|
|
<a-spin :spinning="downloadableReleasesQuery.isPending.value">
|
|
|
|
|
|
<a-alert
|
|
|
|
|
|
v-if="downloadableReleasesError"
|
|
|
|
|
|
type="warning"
|
|
|
|
|
|
show-icon
|
|
|
|
|
|
class="premium-alert"
|
|
|
|
|
|
:message="`客户端列表读取失败:${downloadableReleasesError}`"
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<a-empty
|
|
|
|
|
|
v-else-if="!downloadableReleases.length"
|
|
|
|
|
|
class="premium-empty"
|
|
|
|
|
|
description="当前暂无可下载客户端,请等待运营后台发布。"
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<div v-else class="client-download-grid">
|
|
|
|
|
|
<article
|
|
|
|
|
|
v-for="release in downloadableReleases"
|
|
|
|
|
|
:key="release.id"
|
|
|
|
|
|
class="client-download-card"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="client-download-card__body">
|
|
|
|
|
|
<div class="client-download-card__logo" :class="`logo-${release.platform}`">
|
|
|
|
|
|
<template v-if="release.platform === 'darwin'">
|
|
|
|
|
|
<svg viewBox="0 0 24 24" class="os-svg-icon">
|
|
|
|
|
|
<path fill="currentColor" d="M18.71,19.5 C17.88,20.74 17,21.95 15.66,21.97 C14.32,22 13.89,21.18 12.37,21.18 C10.84,21.18 10.37,21.95 9.1,22 C7.79,22.05 6.8,20.68 5.96,19.48 C4.25,17 2.94,12.45 4.7,9.39 C5.57,7.87 7.13,6.91 8.82,6.88 C10.1,6.86 11.32,7.75 12.11,7.75 C12.89,7.75 14.37,6.68 15.92,6.84 C16.57,6.87 18.39,7.1 19.56,8.82 C19.47,8.88 17.39,10.1 17.41,12.63 C17.44,15.65 20.06,16.66 20.1,16.67 C20.08,16.74 19.67,18.11 18.71,19.5 M15.97,4.17 C16.63,3.37 17.07,2.28 16.95,1 C16,1.04 14.9,1.6 14.24,2.38 C13.68,3.04 13.19,4.14 13.34,5.39 C14.39,5.47 15.4,4.88 15.97,4.17 Z" />
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<template v-else-if="release.platform === 'win32'">
|
|
|
|
|
|
<svg viewBox="0 0 24 24" class="os-svg-icon">
|
|
|
|
|
|
<path fill="currentColor" d="M0 3.449L9.75 2.1v9.45H0V3.449zM0 12.45h9.75v9.45L0 20.551V12.45zm11.25-10.549L24 0v11.55H11.25V1.901zM11.25 12.45H24v11.55l-12.75-1.901V12.45z"/>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<template v-else>
|
|
|
|
|
|
<svg viewBox="0 0 24 24" class="os-svg-icon" stroke="currentColor" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
|
|
|
<polyline points="4 17 10 11 4 5"></polyline>
|
|
|
|
|
|
<line x1="12" y1="19" x2="20" y2="19"></line>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="client-download-card__content">
|
|
|
|
|
|
<h4>{{ releaseTargetLabel(release) }}</h4>
|
|
|
|
|
|
<div class="meta-inline">
|
|
|
|
|
|
<span class="version-tag">v{{ release.version }}</span>
|
|
|
|
|
|
<span class="dot-separator">•</span>
|
|
|
|
|
|
<span>{{ releaseSize(release.file_size_bytes) }}</span>
|
|
|
|
|
|
<span class="dot-separator">•</span>
|
|
|
|
|
|
<span>{{ releaseDate(release) }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="client-download-card__file">
|
|
|
|
|
|
<span class="file-icon">
|
|
|
|
|
|
<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
|
|
|
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
|
|
|
|
|
|
<polyline points="14 2 14 8 20 8"></polyline>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span class="file-name" :title="release.file_name || '安装包'">
|
|
|
|
|
|
{{ release.file_name || '安装包' }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="client-download-card__actions">
|
|
|
|
|
|
<a-button
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
class="btn-download"
|
|
|
|
|
|
:loading="downloadingReleaseKey === releaseDownloadKey(release)"
|
|
|
|
|
|
:disabled="Boolean(downloadingReleaseKey)"
|
|
|
|
|
|
@click="downloadRelease(release)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<template #icon><DownloadOutlined /></template>
|
|
|
|
|
|
获取客户端
|
|
|
|
|
|
</a-button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</article>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</a-spin>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Bound accounts grid section -->
|
|
|
|
|
|
<section class="panel media-list-panel">
|
|
|
|
|
|
<div class="media-list-header-row">
|
|
|
|
|
|
<div class="media-list-title-box">
|
|
|
|
|
|
<h3 class="panel-title">已绑定媒体账号</h3>
|
|
|
|
|
|
<p class="panel-desc">管理各媒体账号的授权效期、排队就绪状态和客户端物理节点映射。</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="media-list-toolbar">
|
|
|
|
|
|
<a-input
|
|
|
|
|
|
v-model:value="searchQuery"
|
|
|
|
|
|
placeholder="搜索账号昵称、UID、发布渠道..."
|
|
|
|
|
|
class="media-search"
|
|
|
|
|
|
allow-clear
|
|
|
|
|
|
>
|
|
|
|
|
|
<template #prefix>
|
|
|
|
|
|
<SearchOutlined style="color: #a0aec0" />
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</a-input>
|
|
|
|
|
|
|
|
|
|
|
|
<a-select v-model:value="selectedHealth" class="media-select" style="width: 150px">
|
|
|
|
|
|
<a-select-option value="all">全部状态</a-select-option>
|
|
|
|
|
|
<a-select-option value="live">授权正常</a-select-option>
|
|
|
|
|
|
<a-select-option value="captcha">待处理</a-select-option>
|
|
|
|
|
|
<a-select-option value="risk">风险观察</a-select-option>
|
|
|
|
|
|
<a-select-option value="expired">授权异常</a-select-option>
|
|
|
|
|
|
</a-select>
|
|
|
|
|
|
</div>
|
2026-04-03 00:39:15 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-04-20 09:52:48 +08:00
|
|
|
|
<a-spin :spinning="loading">
|
|
|
|
|
|
<a-empty
|
|
|
|
|
|
v-if="!filteredAccounts.length"
|
2026-05-31 19:28:35 +08:00
|
|
|
|
class="premium-empty"
|
|
|
|
|
|
description="没有符合过滤条件的媒体账号,请确保桌面端客户端在线并已绑定账号。"
|
2026-04-20 09:52:48 +08:00
|
|
|
|
/>
|
2026-04-03 00:39:15 +08:00
|
|
|
|
|
2026-04-20 09:52:48 +08:00
|
|
|
|
<section v-else class="media-grid">
|
2026-05-01 20:39:09 +08:00
|
|
|
|
<article v-for="account in filteredAccounts" :key="account.id" class="media-card">
|
2026-05-31 19:28:35 +08:00
|
|
|
|
<!-- Top brand line accent using accent color -->
|
|
|
|
|
|
<div class="media-card__accent-bar" :style="{ background: account.platformAccent }"></div>
|
|
|
|
|
|
|
2026-04-20 09:52:48 +08:00
|
|
|
|
<div class="media-card__head">
|
|
|
|
|
|
<div class="media-card__identity">
|
2026-05-31 19:28:35 +08:00
|
|
|
|
<span class="media-card__avatar" :style="{ border: `2px solid ${account.platformAccent}` }">
|
2026-05-01 20:39:09 +08:00
|
|
|
|
<img
|
|
|
|
|
|
v-if="account.avatarUrl"
|
|
|
|
|
|
:src="account.avatarUrl"
|
|
|
|
|
|
:alt="account.displayName"
|
|
|
|
|
|
referrerpolicy="no-referrer"
|
|
|
|
|
|
/>
|
2026-05-31 19:28:35 +08:00
|
|
|
|
<span v-else class="avatar-initial" :style="{ background: account.platformAccent }">
|
|
|
|
|
|
{{ accountInitial(account) }}
|
|
|
|
|
|
</span>
|
2026-04-20 09:52:48 +08:00
|
|
|
|
</span>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="media-card__identity-copy">
|
2026-05-31 19:28:35 +08:00
|
|
|
|
<h3 :title="account.displayName">
|
2026-05-13 19:36:24 +08:00
|
|
|
|
{{ account.displayName }}
|
2026-05-31 19:28:35 +08:00
|
|
|
|
<span class="platform-logo-container">
|
|
|
|
|
|
<img
|
|
|
|
|
|
v-if="account.platformLogoUrl"
|
|
|
|
|
|
:src="account.platformLogoUrl"
|
|
|
|
|
|
:alt="account.platformLabel"
|
|
|
|
|
|
class="media-card__inline-platform-icon"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<span
|
|
|
|
|
|
v-else
|
|
|
|
|
|
class="media-card__inline-platform-text"
|
|
|
|
|
|
:style="{ color: account.platformAccent, background: `${account.platformAccent}15` }"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ account.platformShortName }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</span>
|
2026-05-13 19:36:24 +08:00
|
|
|
|
</h3>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
<span class="media-card__platform-name">{{ account.platformLabel }}</span>
|
2026-05-31 19:28:35 +08:00
|
|
|
|
<span class="media-card__uid-divider">•</span>
|
|
|
|
|
|
<span class="media-card__uid" :title="account.platformUid">ID: {{ account.platformUid }}</span>
|
2026-05-13 19:36:24 +08:00
|
|
|
|
</p>
|
2026-04-20 09:52:48 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-04-03 00:39:15 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-05-13 19:36:24 +08:00
|
|
|
|
<div class="media-card__tags">
|
2026-05-31 19:28:35 +08:00
|
|
|
|
<span class="badge-status" :class="`badge-status--${healthColor(account.health)}`">
|
|
|
|
|
|
<span class="indicator-dot"></span>
|
2026-05-13 19:36:24 +08:00
|
|
|
|
{{ healthLabel(account.health) }}
|
2026-05-31 19:28:35 +08:00
|
|
|
|
</span>
|
|
|
|
|
|
|
|
|
|
|
|
<span class="badge-status" :class="`badge-status--${publishStateColor(account.publishState)}`">
|
|
|
|
|
|
<span class="indicator-dot"></span>
|
2026-05-13 19:36:24 +08:00
|
|
|
|
{{ publishStateLabel(account.publishState) }}
|
2026-05-31 19:28:35 +08:00
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Client mapping connection card -->
|
|
|
|
|
|
<div class="media-card__client-info">
|
|
|
|
|
|
<div class="client-info-inner">
|
|
|
|
|
|
<span class="client-label">运行节点</span>
|
|
|
|
|
|
<a-tooltip :title="clientStatusText(account)" placement="top">
|
|
|
|
|
|
<span class="client-value" :class="`client-value--${clientStatusColor(account)}`">
|
|
|
|
|
|
{{ clientStatusText(account) }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</a-tooltip>
|
|
|
|
|
|
</div>
|
2026-05-13 19:36:24 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="media-card__meta-box">
|
|
|
|
|
|
<div class="meta-box-item">
|
2026-05-31 19:28:35 +08:00
|
|
|
|
<span class="meta-label">安全校验</span>
|
|
|
|
|
|
<span class="meta-value">
|
|
|
|
|
|
{{ account.verifiedAt ? formatDateTime(account.verifiedAt) : '--' }}
|
|
|
|
|
|
</span>
|
2026-04-20 09:52:48 +08:00
|
|
|
|
</div>
|
2026-05-13 19:36:24 +08:00
|
|
|
|
<div class="meta-box-item">
|
2026-05-31 19:28:35 +08:00
|
|
|
|
<span class="meta-label">节点心跳</span>
|
|
|
|
|
|
<span class="meta-value">
|
|
|
|
|
|
{{ account.clientLastSeenAt ? formatDateTime(account.clientLastSeenAt) : '--' }}
|
|
|
|
|
|
</span>
|
2026-04-20 09:52:48 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-04-03 00:39:15 +08:00
|
|
|
|
|
2026-05-31 19:28:35 +08:00
|
|
|
|
<!-- Foot note panel -->
|
2026-04-25 17:39:02 +08:00
|
|
|
|
<div v-if="account.deleteRequestedAt || account.publishHint" class="media-card__footer">
|
2026-05-31 19:28:35 +08:00
|
|
|
|
<div v-if="account.deleteRequestedAt" class="footer-alert footer-alert--warning">
|
|
|
|
|
|
<svg viewBox="0 0 24 24" width="12" height="12" fill="currentColor">
|
|
|
|
|
|
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
<span>已申请解绑账号</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-else-if="account.publishHint" class="footer-alert">
|
|
|
|
|
|
<svg viewBox="0 0 24 24" width="12" height="12" fill="currentColor">
|
|
|
|
|
|
<path d="M11 17h2v-6h-2v6zm1-15C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zM11 9h2V7h-2v2z"/>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
<span :title="account.publishHint">{{ account.publishHint }}</span>
|
|
|
|
|
|
</div>
|
2026-04-20 09:52:48 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</article>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
</a-spin>
|
2026-04-03 00:39:15 +08:00
|
|
|
|
</section>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
2026-05-31 19:28:35 +08:00
|
|
|
|
/* Page Layout */
|
2026-04-03 00:39:15 +08:00
|
|
|
|
.media-view {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
2026-05-31 19:28:35 +08:00
|
|
|
|
gap: 24px;
|
|
|
|
|
|
background-color: #f8fafc;
|
|
|
|
|
|
min-height: 100%;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-31 19:28:35 +08:00
|
|
|
|
/* Premium Dark Banner Section */
|
2026-04-03 00:39:15 +08:00
|
|
|
|
.media-view__top-card {
|
2026-05-31 19:28:35 +08:00
|
|
|
|
position: relative;
|
|
|
|
|
|
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
|
|
|
|
|
|
border: 1px solid #e2e8f0;
|
|
|
|
|
|
border-radius: 16px;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
overflow: hidden;
|
2026-05-31 19:28:35 +08:00
|
|
|
|
padding: 0;
|
|
|
|
|
|
box-shadow: 0 10px 30px rgba(148, 163, 184, 0.06);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Fuzzy Floating Gradient Orbs */
|
|
|
|
|
|
.banner-gradient-orbs {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
|
z-index: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.orb {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
filter: blur(80px);
|
|
|
|
|
|
opacity: 0.6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.orb-1 {
|
|
|
|
|
|
top: -40px;
|
|
|
|
|
|
right: 15%;
|
|
|
|
|
|
width: 250px;
|
|
|
|
|
|
height: 250px;
|
|
|
|
|
|
background: radial-gradient(circle, #e0e7ff 0%, transparent 80%);
|
|
|
|
|
|
animation: float-slow-1 12s infinite alternate ease-in-out;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.orb-2 {
|
|
|
|
|
|
bottom: -60px;
|
|
|
|
|
|
left: 10%;
|
|
|
|
|
|
width: 300px;
|
|
|
|
|
|
height: 300px;
|
|
|
|
|
|
background: radial-gradient(circle, #f5f3ff 0%, transparent 80%);
|
|
|
|
|
|
animation: float-slow-2 15s infinite alternate ease-in-out;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@keyframes float-slow-1 {
|
|
|
|
|
|
0% { transform: translate(0, 0) scale(1); }
|
|
|
|
|
|
100% { transform: translate(40px, 20px) scale(1.1); }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@keyframes float-slow-2 {
|
|
|
|
|
|
0% { transform: translate(0, 0) scale(1.1); }
|
|
|
|
|
|
100% { transform: translate(-30px, -20px) scale(0.9); }
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.media-view__header {
|
2026-05-31 19:28:35 +08:00
|
|
|
|
position: relative;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
2026-05-31 19:28:35 +08:00
|
|
|
|
align-items: center;
|
|
|
|
|
|
padding: 32px;
|
|
|
|
|
|
z-index: 2;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.media-view__header-title {
|
|
|
|
|
|
max-width: 650px;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.media-view__header-title h2 {
|
|
|
|
|
|
margin: 0;
|
2026-05-31 19:28:35 +08:00
|
|
|
|
font-size: 24px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
color: #0f172a;
|
|
|
|
|
|
line-height: 1.3;
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 10px;
|
|
|
|
|
|
letter-spacing: -0.02em;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-31 19:28:35 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-04-03 00:39:15 +08:00
|
|
|
|
.media-view__header-title p {
|
2026-05-31 19:28:35 +08:00
|
|
|
|
margin: 8px 0 0;
|
|
|
|
|
|
font-size: 13.5px;
|
|
|
|
|
|
color: #475569;
|
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
|
font-weight: 400;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.media-view__header-actions {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-31 19:28:35 +08:00
|
|
|
|
/* Premium Refresh Button Override */
|
|
|
|
|
|
:deep(.btn-refresh.ant-btn) {
|
|
|
|
|
|
background: #ffffff;
|
|
|
|
|
|
border: 1px solid #e2e8f0;
|
|
|
|
|
|
color: #0f172a;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
height: 40px;
|
|
|
|
|
|
padding: 0 18px;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
|
|
|
box-shadow: 0 2px 4px rgba(148, 163, 184, 0.05);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.btn-refresh.ant-btn:hover) {
|
|
|
|
|
|
background: #f8fafc;
|
|
|
|
|
|
border-color: #cbd5e1;
|
|
|
|
|
|
color: #4f46e5;
|
|
|
|
|
|
transform: translateY(-1px);
|
|
|
|
|
|
box-shadow: 0 4px 12px rgba(148, 163, 184, 0.08);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Metrics strip styling */
|
2026-04-20 09:52:48 +08:00
|
|
|
|
.overview-strip {
|
2026-05-31 19:28:35 +08:00
|
|
|
|
position: relative;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
display: grid;
|
2026-05-31 19:28:35 +08:00
|
|
|
|
grid-template-columns: repeat(4, 1fr);
|
|
|
|
|
|
background: rgba(255, 255, 255, 0.4);
|
|
|
|
|
|
backdrop-filter: blur(12px);
|
|
|
|
|
|
border-top: 1px solid #e2e8f0;
|
|
|
|
|
|
z-index: 2;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-20 09:52:48 +08:00
|
|
|
|
.overview-chip {
|
2026-05-31 19:28:35 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
padding: 24px 32px;
|
|
|
|
|
|
border-right: 1px solid #e2e8f0;
|
|
|
|
|
|
transition: all 0.25s ease;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-20 09:52:48 +08:00
|
|
|
|
.overview-chip:last-child {
|
|
|
|
|
|
border-right: none;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-31 19:28:35 +08:00
|
|
|
|
.overview-chip:hover {
|
|
|
|
|
|
background: rgba(255, 255, 255, 0.7);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.chip-icon-box {
|
|
|
|
|
|
width: 42px;
|
|
|
|
|
|
height: 42px;
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Metric colored boxes styling */
|
|
|
|
|
|
.total-bg {
|
|
|
|
|
|
background: #e0e7ff;
|
|
|
|
|
|
color: #4f46e5;
|
|
|
|
|
|
border: 1px solid #c7d2fe;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.live-bg {
|
|
|
|
|
|
background: #d1fae5;
|
|
|
|
|
|
color: #059669;
|
|
|
|
|
|
border: 1px solid #a7f3d0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.queued-bg {
|
|
|
|
|
|
background: #fef3c7;
|
|
|
|
|
|
color: #d97706;
|
|
|
|
|
|
border: 1px solid #fde68a;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.attention-bg {
|
|
|
|
|
|
background: #ffe4e6;
|
|
|
|
|
|
color: #e11d48;
|
|
|
|
|
|
border: 1px solid #fecdd3;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.chip-copy {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.chip-copy span {
|
|
|
|
|
|
color: #64748b;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
font-size: 12px;
|
2026-05-31 19:28:35 +08:00
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
letter-spacing: 0.02em;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-31 19:28:35 +08:00
|
|
|
|
.chip-copy strong {
|
|
|
|
|
|
color: #0f172a;
|
|
|
|
|
|
font-size: 26px;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
font-weight: 700;
|
2026-05-31 19:28:35 +08:00
|
|
|
|
margin-top: 4px;
|
|
|
|
|
|
line-height: 1;
|
|
|
|
|
|
font-family: 'Inter', system-ui, sans-serif;
|
|
|
|
|
|
letter-spacing: -0.02em;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-31 19:28:35 +08:00
|
|
|
|
/* Panel Layout Styling */
|
2026-04-20 09:52:48 +08:00
|
|
|
|
.panel {
|
2026-05-31 19:28:35 +08:00
|
|
|
|
background: #ffffff;
|
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
|
padding: 32px;
|
|
|
|
|
|
border: 1px solid #e2e8f0;
|
|
|
|
|
|
box-shadow: 0 4px 20px rgba(148, 163, 184, 0.05);
|
|
|
|
|
|
transition: box-shadow 0.3s ease, border-color 0.3s ease;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-31 19:28:35 +08:00
|
|
|
|
.panel:hover {
|
|
|
|
|
|
box-shadow: 0 10px 30px rgba(148, 163, 184, 0.08);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.panel-title {
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
color: #0f172a;
|
|
|
|
|
|
letter-spacing: -0.01em;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.panel-desc {
|
|
|
|
|
|
margin: 6px 0 0;
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
font-size: 13.5px;
|
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Client download header and grid */
|
|
|
|
|
|
.client-download-panel {
|
2026-04-20 09:52:48 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
2026-05-31 19:28:35 +08:00
|
|
|
|
gap: 24px;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-31 19:28:35 +08:00
|
|
|
|
.client-download-head {
|
2026-04-03 00:39:15 +08:00
|
|
|
|
display: flex;
|
2026-05-31 19:28:35 +08:00
|
|
|
|
align-items: center;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
justify-content: space-between;
|
2026-05-31 19:28:35 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.title-with-badge {
|
|
|
|
|
|
display: flex;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
align-items: center;
|
2026-05-31 19:28:35 +08:00
|
|
|
|
gap: 12px;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-31 19:28:35 +08:00
|
|
|
|
.badge-pulsing-stable {
|
|
|
|
|
|
background: rgba(99, 102, 241, 0.1);
|
|
|
|
|
|
color: #4f46e5;
|
|
|
|
|
|
border: 1px solid rgba(99, 102, 241, 0.2);
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
padding: 3px 10px;
|
|
|
|
|
|
border-radius: 99px;
|
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
|
letter-spacing: 0.05em;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.badge-pulsing-stable::after {
|
|
|
|
|
|
content: '';
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
left: -100%;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
background: linear-gradient(90deg, transparent, rgba(99, 102, 241, 0.2), transparent);
|
|
|
|
|
|
animation: shine-sweep 3s infinite linear;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@keyframes shine-sweep {
|
|
|
|
|
|
0% { left: -100%; }
|
|
|
|
|
|
100% { left: 100%; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.client-download-grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
|
|
|
|
gap: 18px;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-31 19:28:35 +08:00
|
|
|
|
/* Client download cards */
|
|
|
|
|
|
.client-download-card {
|
2026-04-20 09:52:48 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
2026-05-31 19:28:35 +08:00
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
min-height: 200px;
|
|
|
|
|
|
padding: 24px;
|
|
|
|
|
|
border: 1px solid #e2e8f0;
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
|
|
|
|
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
|
|
|
position: relative;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-31 19:28:35 +08:00
|
|
|
|
.client-download-card:hover {
|
|
|
|
|
|
transform: translateY(-4px);
|
|
|
|
|
|
border-color: #cbd5e1;
|
|
|
|
|
|
box-shadow: 0 12px 24px rgba(148, 163, 184, 0.12);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.client-download-card__body {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 16px;
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Elegant custom OS SVGs logos wrapper */
|
|
|
|
|
|
.client-download-card__logo {
|
|
|
|
|
|
width: 48px;
|
|
|
|
|
|
height: 48px;
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.logo-darwin {
|
|
|
|
|
|
background: #0f172a;
|
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
|
box-shadow: 0 4px 12px rgba(15, 23, 42, 0.15);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.logo-win32 {
|
|
|
|
|
|
background: #0078d4;
|
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
|
box-shadow: 0 4px 12px rgba(0, 120, 212, 0.2);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.logo-default, .logo-linux {
|
|
|
|
|
|
background: #1e293b;
|
|
|
|
|
|
color: #38bdf8;
|
|
|
|
|
|
box-shadow: 0 4px 12px rgba(30, 41, 59, 0.2);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.os-svg-icon {
|
|
|
|
|
|
width: 22px;
|
|
|
|
|
|
height: 22px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.client-download-card__content {
|
|
|
|
|
|
min-width: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.client-download-card__content h4 {
|
2026-04-20 09:52:48 +08:00
|
|
|
|
margin: 0;
|
|
|
|
|
|
font-size: 16px;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
font-weight: 700;
|
2026-05-31 19:28:35 +08:00
|
|
|
|
color: #0f172a;
|
|
|
|
|
|
line-height: 1.3;
|
2026-04-20 09:52:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-31 19:28:35 +08:00
|
|
|
|
.meta-inline {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
margin-top: 6px;
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.version-tag {
|
|
|
|
|
|
background: #f1f5f9;
|
|
|
|
|
|
color: #475569;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
padding: 1px 6px;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.dot-separator {
|
|
|
|
|
|
color: #cbd5e1;
|
|
|
|
|
|
font-size: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Code-looking filename strip */
|
|
|
|
|
|
.client-download-card__file {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
background: #f1f5f9;
|
|
|
|
|
|
border: 1px solid #e2e8f0;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
padding: 10px 12px;
|
|
|
|
|
|
margin-top: 18px;
|
|
|
|
|
|
margin-bottom: 18px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.file-icon {
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.file-name {
|
|
|
|
|
|
color: #334155;
|
|
|
|
|
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
|
|
|
|
font-size: 11.5px;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.client-download-card__actions {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Download Button styling */
|
|
|
|
|
|
:deep(.btn-download.ant-btn-primary) {
|
|
|
|
|
|
background: #4f46e5;
|
|
|
|
|
|
border-color: #4f46e5;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
height: 38px;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
font-weight: 600;
|
2026-04-20 09:52:48 +08:00
|
|
|
|
font-size: 13px;
|
2026-05-31 19:28:35 +08:00
|
|
|
|
box-shadow: 0 4px 12px rgba(79, 70, 229, 0.2);
|
|
|
|
|
|
transition: all 0.25s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.btn-download.ant-btn-primary:hover:not(:disabled)) {
|
|
|
|
|
|
background: #4338ca;
|
|
|
|
|
|
border-color: #4338ca;
|
|
|
|
|
|
box-shadow: 0 6px 18px rgba(79, 70, 229, 0.3);
|
|
|
|
|
|
transform: translateY(-1px);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.btn-download.ant-btn-primary:disabled) {
|
|
|
|
|
|
background: #cbd5e1;
|
|
|
|
|
|
border-color: #cbd5e1;
|
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
|
box-shadow: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Spinners, alerts, empty state styling */
|
|
|
|
|
|
.premium-alert {
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
border: 1px solid #fecaca;
|
|
|
|
|
|
background: #fef2f2;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.premium-empty {
|
|
|
|
|
|
padding: 40px 0;
|
|
|
|
|
|
border: 1px dashed #e2e8f0;
|
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Media list panel & search toolbar */
|
|
|
|
|
|
.media-list-panel {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 24px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.media-list-header-row {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
|
gap: 24px;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.media-list-title-box {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
min-width: 250px;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-31 19:28:35 +08:00
|
|
|
|
.media-list-toolbar {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Styled Inputs & Selects */
|
|
|
|
|
|
:deep(.media-search.ant-input-affix-wrapper) {
|
|
|
|
|
|
width: 320px;
|
|
|
|
|
|
height: 40px;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
border-color: #cbd5e1;
|
|
|
|
|
|
background-color: #ffffff;
|
|
|
|
|
|
padding-left: 12px;
|
|
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.media-search.ant-input-affix-wrapper:hover) {
|
|
|
|
|
|
border-color: #94a3b8;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.media-search.ant-input-affix-wrapper-focused) {
|
|
|
|
|
|
border-color: #4f46e5;
|
|
|
|
|
|
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.15);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.media-select.ant-select .ant-select-selector) {
|
|
|
|
|
|
height: 40px !important;
|
|
|
|
|
|
border-radius: 8px !important;
|
|
|
|
|
|
border-color: #cbd5e1 !important;
|
|
|
|
|
|
padding: 4px 12px !important;
|
|
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.media-select.ant-select:hover .ant-select-selector) {
|
|
|
|
|
|
border-color: #94a3b8 !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.media-select.ant-select-focused .ant-select-selector) {
|
|
|
|
|
|
border-color: #4f46e5 !important;
|
|
|
|
|
|
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.15) !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Media Card Grid */
|
2026-04-03 00:39:15 +08:00
|
|
|
|
.media-grid {
|
|
|
|
|
|
display: grid;
|
2026-05-31 19:28:35 +08:00
|
|
|
|
grid-template-columns: repeat(auto-fill, minmax(295px, 1fr));
|
|
|
|
|
|
gap: 20px;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-31 19:28:35 +08:00
|
|
|
|
/* Premium Media Card */
|
2026-04-03 00:39:15 +08:00
|
|
|
|
.media-card {
|
2026-05-31 19:28:35 +08:00
|
|
|
|
position: relative;
|
2026-05-13 19:36:24 +08:00
|
|
|
|
background: #ffffff;
|
2026-05-31 19:28:35 +08:00
|
|
|
|
border: 1px solid #e2e8f0;
|
|
|
|
|
|
border-radius: 14px;
|
|
|
|
|
|
padding: 24px;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
2026-05-13 19:36:24 +08:00
|
|
|
|
gap: 16px;
|
2026-05-31 19:28:35 +08:00
|
|
|
|
transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
box-shadow: 0 4px 12px rgba(148, 163, 184, 0.03);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.media-card__accent-bar {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
height: 4px;
|
|
|
|
|
|
opacity: 0.85;
|
2026-05-13 19:36:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.media-card:hover {
|
2026-05-31 19:28:35 +08:00
|
|
|
|
transform: translateY(-5px);
|
|
|
|
|
|
border-color: #cbd5e1;
|
|
|
|
|
|
box-shadow: 0 16px 36px rgba(148, 163, 184, 0.12);
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.media-card__head {
|
|
|
|
|
|
display: flex;
|
2026-05-13 19:36:24 +08:00
|
|
|
|
align-items: center;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.media-card__identity {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
2026-05-13 19:36:24 +08:00
|
|
|
|
gap: 14px;
|
2026-04-20 09:52:48 +08:00
|
|
|
|
min-width: 0;
|
2026-05-31 19:28:35 +08:00
|
|
|
|
width: 100%;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-31 19:28:35 +08:00
|
|
|
|
/* Circular Avatar glow container */
|
2026-04-20 09:52:48 +08:00
|
|
|
|
.media-card__avatar {
|
2026-05-13 19:36:24 +08:00
|
|
|
|
width: 48px;
|
|
|
|
|
|
height: 48px;
|
|
|
|
|
|
border-radius: 50%;
|
2026-05-31 19:28:35 +08:00
|
|
|
|
display: flex;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
2026-04-20 09:52:48 +08:00
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
flex-shrink: 0;
|
2026-05-31 19:28:35 +08:00
|
|
|
|
background: #f1f5f9;
|
|
|
|
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
|
|
|
|
|
transition: transform 0.3s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.media-card:hover .media-card__avatar {
|
|
|
|
|
|
transform: scale(1.05);
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-20 09:52:48 +08:00
|
|
|
|
.media-card__avatar img {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
object-fit: cover;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-31 19:28:35 +08:00
|
|
|
|
.avatar-initial {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-20 09:52:48 +08:00
|
|
|
|
.media-card__identity-copy {
|
|
|
|
|
|
min-width: 0;
|
2026-05-31 19:28:35 +08:00
|
|
|
|
flex: 1;
|
2026-04-20 09:52:48 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.media-card__identity-copy h3 {
|
2026-04-03 00:39:15 +08:00
|
|
|
|
margin: 0;
|
2026-05-31 19:28:35 +08:00
|
|
|
|
color: #0f172a;
|
|
|
|
|
|
font-size: 15.5px;
|
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
|
line-height: 1.3;
|
2026-05-13 19:36:24 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-31 19:28:35 +08:00
|
|
|
|
.platform-logo-container {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-13 19:36:24 +08:00
|
|
|
|
.media-card__inline-platform-icon {
|
|
|
|
|
|
width: 16px;
|
|
|
|
|
|
height: 16px;
|
|
|
|
|
|
object-fit: contain;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.media-card__inline-platform-text {
|
2026-05-31 19:28:35 +08:00
|
|
|
|
font-size: 10px;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
font-weight: 700;
|
2026-05-31 19:28:35 +08:00
|
|
|
|
padding: 1px 5px;
|
2026-05-13 19:36:24 +08:00
|
|
|
|
border-radius: 4px;
|
2026-05-31 19:28:35 +08:00
|
|
|
|
letter-spacing: 0.02em;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-20 09:52:48 +08:00
|
|
|
|
.media-card__identity-copy p {
|
2026-05-31 19:28:35 +08:00
|
|
|
|
margin: 5px 0 0;
|
2026-05-13 19:36:24 +08:00
|
|
|
|
color: #64748b;
|
2026-05-31 19:28:35 +08:00
|
|
|
|
font-size: 12.5px;
|
2026-05-13 19:36:24 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
2026-05-31 19:28:35 +08:00
|
|
|
|
gap: 6px;
|
|
|
|
|
|
min-width: 0;
|
2026-05-13 19:36:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.media-card__platform-name {
|
2026-05-31 19:28:35 +08:00
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #475569;
|
2026-05-13 19:36:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.media-card__uid-divider {
|
|
|
|
|
|
color: #cbd5e1;
|
2026-05-31 19:28:35 +08:00
|
|
|
|
font-weight: bold;
|
2026-05-13 19:36:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.media-card__uid {
|
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
2026-05-31 19:28:35 +08:00
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
flex: 1;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-31 19:28:35 +08:00
|
|
|
|
/* Polished Pill Badges */
|
2026-05-13 19:36:24 +08:00
|
|
|
|
.media-card__tags {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-wrap: wrap;
|
|
|
|
|
|
gap: 8px;
|
2026-05-31 19:28:35 +08:00
|
|
|
|
margin-top: 4px;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
2026-04-20 09:52:48 +08:00
|
|
|
|
|
2026-05-31 19:28:35 +08:00
|
|
|
|
.badge-status {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
font-size: 11.5px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
padding: 3px 10px;
|
|
|
|
|
|
border-radius: 99px;
|
|
|
|
|
|
line-height: 1.4;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-31 19:28:35 +08:00
|
|
|
|
.indicator-dot {
|
|
|
|
|
|
width: 6px;
|
|
|
|
|
|
height: 6px;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
display: inline-block;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Colorful status mapping */
|
|
|
|
|
|
.badge-status--green {
|
|
|
|
|
|
background: rgba(16, 185, 129, 0.08);
|
|
|
|
|
|
color: #059669;
|
|
|
|
|
|
border: 1px solid rgba(16, 185, 129, 0.15);
|
|
|
|
|
|
}
|
|
|
|
|
|
.badge-status--green .indicator-dot {
|
|
|
|
|
|
background-color: #10b981;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.badge-status--orange {
|
|
|
|
|
|
background: rgba(245, 158, 11, 0.08);
|
|
|
|
|
|
color: #d97706;
|
|
|
|
|
|
border: 1px solid rgba(245, 158, 11, 0.15);
|
|
|
|
|
|
}
|
|
|
|
|
|
.badge-status--orange .indicator-dot {
|
|
|
|
|
|
background-color: #f59e0b;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.badge-status--gold {
|
|
|
|
|
|
background: rgba(217, 119, 6, 0.08);
|
|
|
|
|
|
color: #b45309;
|
|
|
|
|
|
border: 1px solid rgba(217, 119, 6, 0.15);
|
|
|
|
|
|
}
|
|
|
|
|
|
.badge-status--gold .indicator-dot {
|
|
|
|
|
|
background-color: #d97706;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.badge-status--red {
|
|
|
|
|
|
background: rgba(244, 63, 94, 0.08);
|
|
|
|
|
|
color: #e11d48;
|
|
|
|
|
|
border: 1px solid rgba(244, 63, 94, 0.15);
|
|
|
|
|
|
}
|
|
|
|
|
|
.badge-status--red .indicator-dot {
|
|
|
|
|
|
background-color: #f43f5e;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.badge-status--default {
|
|
|
|
|
|
background: rgba(100, 116, 139, 0.08);
|
|
|
|
|
|
color: #475569;
|
|
|
|
|
|
border: 1px solid rgba(100, 116, 139, 0.15);
|
|
|
|
|
|
}
|
|
|
|
|
|
.badge-status--default .indicator-dot {
|
|
|
|
|
|
background-color: #64748b;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Client runtime block */
|
|
|
|
|
|
.media-card__client-info {
|
2026-05-13 19:36:24 +08:00
|
|
|
|
background: #f8fafc;
|
2026-05-31 19:28:35 +08:00
|
|
|
|
border: 1px solid #f1f5f9;
|
2026-05-13 19:36:24 +08:00
|
|
|
|
border-radius: 8px;
|
2026-05-31 19:28:35 +08:00
|
|
|
|
padding: 10px 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.client-info-inner {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.client-label {
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.client-value {
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
max-width: 160px;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.client-value--green {
|
|
|
|
|
|
color: #059669;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.client-value--gold {
|
|
|
|
|
|
color: #d97706;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.client-value--default {
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Metadata box */
|
|
|
|
|
|
.media-card__meta-box {
|
2026-04-20 09:52:48 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
2026-05-31 19:28:35 +08:00
|
|
|
|
gap: 6px;
|
|
|
|
|
|
border-top: 1px solid #f1f5f9;
|
|
|
|
|
|
padding-top: 12px;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-13 19:36:24 +08:00
|
|
|
|
.meta-box-item {
|
2026-04-20 09:52:48 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
2026-05-31 19:28:35 +08:00
|
|
|
|
font-size: 11.5px;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-20 09:52:48 +08:00
|
|
|
|
.meta-label {
|
2026-05-31 19:28:35 +08:00
|
|
|
|
color: #94a3b8;
|
|
|
|
|
|
font-weight: 500;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-20 09:52:48 +08:00
|
|
|
|
.meta-value {
|
2026-05-31 19:28:35 +08:00
|
|
|
|
color: #475569;
|
|
|
|
|
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
2026-04-20 09:52:48 +08:00
|
|
|
|
font-weight: 500;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-31 19:28:35 +08:00
|
|
|
|
/* Card footer details hints */
|
|
|
|
|
|
.media-card__footer {
|
|
|
|
|
|
margin-top: auto;
|
|
|
|
|
|
border-top: 1px dashed #f1f5f9;
|
|
|
|
|
|
padding-top: 12px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.footer-alert {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 6px;
|
|
|
|
|
|
font-size: 11px;
|
|
|
|
|
|
color: #64748b;
|
|
|
|
|
|
background: #f8fafc;
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
padding: 6px 10px;
|
|
|
|
|
|
line-height: 1.4;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.footer-alert svg {
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
color: #94a3b8;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.footer-alert span {
|
2026-04-20 12:00:10 +08:00
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-31 19:28:35 +08:00
|
|
|
|
.footer-alert--warning {
|
|
|
|
|
|
background: #fffbeb;
|
|
|
|
|
|
color: #b45309;
|
|
|
|
|
|
border: 1px solid #fef3c7;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-31 19:28:35 +08:00
|
|
|
|
.footer-alert--warning svg {
|
|
|
|
|
|
color: #d97706;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-31 19:28:35 +08:00
|
|
|
|
/* Responsiveness adjustments */
|
|
|
|
|
|
@media (max-width: 1024px) {
|
2026-04-20 09:52:48 +08:00
|
|
|
|
.overview-strip {
|
2026-05-31 19:28:35 +08:00
|
|
|
|
grid-template-columns: repeat(2, 1fr);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.overview-chip {
|
|
|
|
|
|
border-bottom: 1px solid #e2e8f0;
|
2026-04-20 09:52:48 +08:00
|
|
|
|
}
|
2026-04-03 00:39:15 +08:00
|
|
|
|
|
2026-05-31 19:28:35 +08:00
|
|
|
|
.overview-chip:nth-child(even) {
|
2026-04-20 09:52:48 +08:00
|
|
|
|
border-right: none;
|
|
|
|
|
|
}
|
2026-04-03 00:39:15 +08:00
|
|
|
|
|
2026-05-31 19:28:35 +08:00
|
|
|
|
.overview-chip:nth-child(n+3) {
|
|
|
|
|
|
border-bottom: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
|
|
|
.media-view__header {
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: stretch;
|
|
|
|
|
|
gap: 20px;
|
|
|
|
|
|
padding: 24px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.media-view__header-actions {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.media-view__header-actions :deep(.ant-btn) {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.overview-chip {
|
|
|
|
|
|
padding: 16px 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.panel {
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.media-list-header-row {
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: stretch;
|
|
|
|
|
|
gap: 16px;
|
2026-04-20 09:52:48 +08:00
|
|
|
|
}
|
2026-04-03 00:39:15 +08:00
|
|
|
|
|
2026-04-20 09:52:48 +08:00
|
|
|
|
.media-list-toolbar {
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: stretch;
|
2026-05-31 19:28:35 +08:00
|
|
|
|
width: 100%;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-31 19:28:35 +08:00
|
|
|
|
:deep(.media-search.ant-input-affix-wrapper) {
|
2026-04-20 09:52:48 +08:00
|
|
|
|
width: 100%;
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
2026-05-31 19:28:35 +08:00
|
|
|
|
|
|
|
|
|
|
:deep(.media-select.ant-select) {
|
|
|
|
|
|
width: 100% !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@media (max-width: 480px) {
|
|
|
|
|
|
.overview-strip {
|
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.overview-chip {
|
|
|
|
|
|
border-right: none;
|
|
|
|
|
|
border-bottom: 1px solid #e2e8f0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.overview-chip:last-child {
|
|
|
|
|
|
border-bottom: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.client-download-grid, .media-grid {
|
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
|
}
|
2026-04-03 00:39:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
</style>
|