v0.4.1:新增 CK 有效性检查并修复抢购调度
This commit is contained in:
+318
-33
@@ -29,6 +29,7 @@ import {
|
||||
Plus,
|
||||
RefreshCw,
|
||||
Save,
|
||||
ShieldCheck,
|
||||
ShoppingCart,
|
||||
Square,
|
||||
Settings,
|
||||
@@ -36,6 +37,7 @@ import {
|
||||
Zap,
|
||||
} from 'lucide-react'
|
||||
import {
|
||||
CheckCookies,
|
||||
DefaultConfig,
|
||||
Exchange,
|
||||
GetInstanceInfo,
|
||||
@@ -58,6 +60,9 @@ import { ClipboardGetText, ClipboardSetText, EventsOn } from '../wailsjs/runtime
|
||||
const configStorageBase = 'huya-go.gui.config.v2'
|
||||
const cookieStorageBase = 'huya-go.gui.cookies.v1'
|
||||
const prizeCacheStorageBase = 'huya-go.gui.prizes.v1'
|
||||
const leadDefaultMigrationBase = 'huya-go.gui.lead-default.v1'
|
||||
// 旧版本默认把提前量写成 2500ms,与现在的动态提前设计冲突;只迁移一次。
|
||||
const legacyDefaultLeadMs = 2500
|
||||
|
||||
// 界面本地存储保留的日志上限,与后端的增量拉取配合使用。
|
||||
const maxClientLogs = 1000
|
||||
@@ -123,11 +128,26 @@ type UiConfig = {
|
||||
disableAdaptiveThrottle: boolean
|
||||
}
|
||||
|
||||
type CookieCheckStatus = 'valid' | 'expired' | 'unknown' | 'invalid'
|
||||
|
||||
type CookieCheckResult = {
|
||||
line: number
|
||||
uid: number
|
||||
status: CookieCheckStatus
|
||||
message?: string
|
||||
statusCode?: number
|
||||
checkedAt: number
|
||||
}
|
||||
|
||||
type AccountPreview = {
|
||||
line: number
|
||||
uid: number
|
||||
valid: boolean
|
||||
error?: string
|
||||
// checkStatus 是服务端登录态检查结果;valid 仅表示本地能解析出 uid。
|
||||
checkStatus?: CookieCheckStatus
|
||||
checkMessage?: string
|
||||
checkedAt?: number
|
||||
}
|
||||
|
||||
type CookieEntry = {
|
||||
@@ -136,6 +156,9 @@ type CookieEntry = {
|
||||
uid: number
|
||||
valid: boolean
|
||||
error?: string
|
||||
checkStatus?: CookieCheckStatus
|
||||
checkMessage?: string
|
||||
checkedAt?: number
|
||||
updatedAt: number
|
||||
}
|
||||
|
||||
@@ -483,9 +506,45 @@ function categoryTag(category?: string): ReactNode {
|
||||
return <Tag color={item.color}>{item.label}</Tag>
|
||||
}
|
||||
|
||||
function normalizeCookieCheckStatus(value: unknown): CookieCheckStatus | undefined {
|
||||
const status = String(value || '').trim()
|
||||
if (status === 'valid' || status === 'expired' || status === 'unknown' || status === 'invalid') {
|
||||
return status
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
function isCookieCheckUnusable(status?: CookieCheckStatus): boolean {
|
||||
return status === 'expired' || status === 'unknown' || status === 'invalid'
|
||||
}
|
||||
|
||||
function cookieCheckTag(status?: CookieCheckStatus, message?: string): ReactNode {
|
||||
const config: Record<CookieCheckStatus | 'unchecked', { color: string; label: string }> = {
|
||||
valid: { color: 'success', label: '可用' },
|
||||
expired: { color: 'error', label: '失效' },
|
||||
unknown: { color: 'error', label: '失效' },
|
||||
invalid: { color: 'error', label: '无效' },
|
||||
unchecked: { color: 'default', label: '未检查' },
|
||||
}
|
||||
const item = config[status || 'unchecked']
|
||||
const tag = <Tag color={item.color}>{item.label}</Tag>
|
||||
return message ? <Tooltip title={message}>{tag}</Tooltip> : tag
|
||||
}
|
||||
|
||||
function readSavedConfig(): Partial<SavedConfig> {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem(scopedKey(configStorageBase)) || '{}') as Partial<SavedConfig>
|
||||
const configKey = scopedKey(configStorageBase)
|
||||
const saved = JSON.parse(localStorage.getItem(configKey) || '{}') as Partial<SavedConfig>
|
||||
const migrationKey = scopedKey(leadDefaultMigrationBase)
|
||||
if (!localStorage.getItem(migrationKey)) {
|
||||
if (Number(saved.leadMs || 0) === legacyDefaultLeadMs) {
|
||||
saved.leadMs = 0
|
||||
// 迁移必须立即落盘,否则下一次 readSavedConfig 又会读回旧值。
|
||||
localStorage.setItem(configKey, JSON.stringify(saved))
|
||||
}
|
||||
localStorage.setItem(migrationKey, '1')
|
||||
}
|
||||
return saved
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
@@ -505,6 +564,9 @@ function readSavedCookies(): CookieEntry[] {
|
||||
uid: Number(item.uid || 0),
|
||||
valid: Boolean(item.valid),
|
||||
error: item.error,
|
||||
checkStatus: normalizeCookieCheckStatus(item.checkStatus),
|
||||
checkMessage: String(item.checkMessage || '').trim() || undefined,
|
||||
checkedAt: Number(item.checkedAt || 0) || undefined,
|
||||
updatedAt: Number(item.updatedAt || Date.now()),
|
||||
}))
|
||||
.filter((item) => item.text)
|
||||
@@ -706,11 +768,23 @@ export default function Dashboard() {
|
||||
}
|
||||
})
|
||||
setCookieEntries(enriched)
|
||||
setAccounts(previews || [])
|
||||
setSelectedAccountLines((previews || []).filter((item) => item.valid).map((item) => item.line))
|
||||
const enrichedPreviews = (previews || []).map((preview) => {
|
||||
const entry = enriched[preview.line - 1]
|
||||
return {
|
||||
...preview,
|
||||
checkStatus: entry?.checkStatus,
|
||||
checkMessage: entry?.checkMessage,
|
||||
checkedAt: entry?.checkedAt,
|
||||
}
|
||||
})
|
||||
setAccounts(enrichedPreviews)
|
||||
const selectableLines = enrichedPreviews
|
||||
.filter((item) => item.valid && !isCookieCheckUnusable(item.checkStatus))
|
||||
.map((item) => item.line)
|
||||
setSelectedAccountLines(selectableLines)
|
||||
if (persist) writeSavedCookies(enriched)
|
||||
if (refreshLatest && enriched.some((item) => item.valid)) {
|
||||
window.setTimeout(() => queryPrizes(true), 0)
|
||||
if (refreshLatest && selectableLines.length > 0) {
|
||||
window.setTimeout(() => queryPrizes(true, selectableLines), 0)
|
||||
}
|
||||
return enriched
|
||||
}
|
||||
@@ -798,7 +872,22 @@ export default function Dashboard() {
|
||||
writeSavedConfigPatch({ startAt: String(form.getFieldValue('startAt') || '') })
|
||||
}, [configLoaded, form, watchedStartAt])
|
||||
|
||||
const validAccounts = useMemo(() => accounts.filter((item) => item.valid), [accounts])
|
||||
const validAccounts = useMemo(
|
||||
() => accounts.filter((item) => item.valid && !isCookieCheckUnusable(item.checkStatus)),
|
||||
[accounts],
|
||||
)
|
||||
const cookieCheckStats = useMemo(() => {
|
||||
const stats = { valid: 0, expired: 0, unknown: 0, invalid: 0, unchecked: 0 }
|
||||
cookieEntries.forEach((entry) => {
|
||||
if (entry.checkStatus === 'valid') stats.valid += 1
|
||||
else if (entry.checkStatus === 'expired') stats.expired += 1
|
||||
else if (entry.checkStatus === 'unknown') stats.unknown += 1
|
||||
else if (entry.checkStatus === 'invalid') stats.invalid += 1
|
||||
else stats.unchecked += 1
|
||||
})
|
||||
return stats
|
||||
}, [cookieEntries])
|
||||
const failedCookieCount = cookieCheckStats.expired + cookieCheckStats.unknown + cookieCheckStats.invalid
|
||||
const importedCookieCount = useMemo(
|
||||
() => normalizeCookieText(watchedCookieText || '').split('\n').filter(Boolean).length,
|
||||
[watchedCookieText],
|
||||
@@ -918,10 +1007,10 @@ export default function Dashboard() {
|
||||
[logs, logFilter],
|
||||
)
|
||||
|
||||
const config = () => ({
|
||||
const config = (selectedLines: Key[] = selectedAccountLines) => ({
|
||||
...pickConfig(form.getFieldsValue(true)),
|
||||
category: activeCategory,
|
||||
selectedAccountLines: selectedAccountLines.map(Number).filter((line) => line > 0),
|
||||
selectedAccountLines: selectedLines.map(Number).filter((line) => line > 0),
|
||||
})
|
||||
|
||||
const runAction = async <T,>(
|
||||
@@ -1021,7 +1110,16 @@ export default function Dashboard() {
|
||||
if (editingCookie) {
|
||||
const text = normalizedText.split('\n')[0]
|
||||
const nextEntries = cookieEntries.map((entry) =>
|
||||
entry.id === editingCookie.id ? { ...entry, text, updatedAt: Date.now() } : entry,
|
||||
entry.id === editingCookie.id
|
||||
? {
|
||||
...entry,
|
||||
text,
|
||||
checkStatus: undefined,
|
||||
checkMessage: undefined,
|
||||
checkedAt: undefined,
|
||||
updatedAt: Date.now(),
|
||||
}
|
||||
: entry,
|
||||
)
|
||||
await applyCookieEntries(nextEntries, true, true)
|
||||
message.success('CK 已更新')
|
||||
@@ -1064,13 +1162,110 @@ export default function Dashboard() {
|
||||
message.success('已重新载入本地 CK')
|
||||
}
|
||||
|
||||
function queryPrizes(quiet = false) {
|
||||
// 检查走服务端 getUserScore;失效结果连续确认 3 次,避免偶发「账号未绑定」误判。
|
||||
const checkCookies = async () => {
|
||||
if (running) {
|
||||
message.warning('抢购运行中,请先停止再检查 CK')
|
||||
return
|
||||
}
|
||||
if (cookieEntries.length === 0) {
|
||||
message.warning('没有可检查的 CK')
|
||||
return
|
||||
}
|
||||
setLoading('cookie-check')
|
||||
try {
|
||||
const snapshot = cookieEntries
|
||||
const results = (await CheckCookies(config())) as CookieCheckResult[]
|
||||
const byLine = new Map<number, CookieCheckResult>()
|
||||
const byUID = new Map<number, CookieCheckResult>()
|
||||
results.forEach((item) => {
|
||||
byLine.set(Number(item.line || 0), item)
|
||||
if (Number(item.uid || 0) > 0) byUID.set(Number(item.uid), item)
|
||||
})
|
||||
|
||||
const nextEntries = snapshot.map((entry, index) => {
|
||||
const result = byLine.get(index + 1) || (entry.uid > 0 ? byUID.get(entry.uid) : undefined)
|
||||
if (!result) return entry
|
||||
return {
|
||||
...entry,
|
||||
uid: Number(result.uid || entry.uid || 0),
|
||||
checkStatus: normalizeCookieCheckStatus(result.status),
|
||||
checkMessage: result.status === 'valid' ? '' : String(result.message || '').trim(),
|
||||
checkedAt: Number(result.checkedAt || Date.now()),
|
||||
}
|
||||
})
|
||||
|
||||
const stats = { valid: 0, expired: 0, unknown: 0, invalid: 0 }
|
||||
nextEntries.forEach((entry) => {
|
||||
if (entry.checkStatus === 'valid') stats.valid += 1
|
||||
else if (entry.checkStatus === 'expired') stats.expired += 1
|
||||
else if (entry.checkStatus === 'unknown') stats.unknown += 1
|
||||
else if (entry.checkStatus === 'invalid') stats.invalid += 1
|
||||
})
|
||||
const failed = stats.expired + stats.unknown + stats.invalid
|
||||
|
||||
setCookieEntries(nextEntries)
|
||||
writeSavedCookies(nextEntries)
|
||||
setAccounts((prev) =>
|
||||
prev.map((account) => {
|
||||
const result = byLine.get(account.line) || (account.uid > 0 ? byUID.get(account.uid) : undefined)
|
||||
if (!result) return account
|
||||
return {
|
||||
...account,
|
||||
checkStatus: normalizeCookieCheckStatus(result.status),
|
||||
checkMessage: result.status === 'valid' ? '' : String(result.message || '').trim(),
|
||||
checkedAt: Number(result.checkedAt || Date.now()),
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
const failedLines = new Set(
|
||||
nextEntries
|
||||
.map((entry, index) => (isCookieCheckUnusable(entry.checkStatus) ? index + 1 : 0))
|
||||
.filter((line) => line > 0),
|
||||
)
|
||||
setSelectedAccountLines((prev) => prev.filter((line) => !failedLines.has(Number(line))))
|
||||
|
||||
const summary = `检查完成:可用 ${stats.valid},失效 ${failed}`
|
||||
if (failed > 0) {
|
||||
message.warning(summary)
|
||||
} else {
|
||||
message.success(summary)
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
message.error(`检查 CK 失败: ${String(err)}`)
|
||||
} finally {
|
||||
setLoading('')
|
||||
}
|
||||
}
|
||||
|
||||
const clearExpiredCookies = () => {
|
||||
if (failedCookieCount === 0) return
|
||||
modal.confirm({
|
||||
title: '清理失效 CK',
|
||||
content: `确认删除 ${failedCookieCount} 条失效 CK?未检查的 CK 会保留。`,
|
||||
okText: '清理',
|
||||
cancelText: '取消',
|
||||
okButtonProps: { danger: true },
|
||||
onOk: async () => {
|
||||
const remaining = cookieEntries.filter((entry) => !isCookieCheckUnusable(entry.checkStatus))
|
||||
await applyCookieEntries(remaining, true, false)
|
||||
message.success(`已清理 ${failedCookieCount} 条失效 CK`)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function queryPrizes(quiet = false, selectedLines: Key[] = selectedAccountLines) {
|
||||
if (!String(form.getFieldValue('cookieText') || '').trim()) {
|
||||
if (!quiet) message.warning('Cookie 不能为空')
|
||||
return
|
||||
}
|
||||
if (selectedLines.length === 0) {
|
||||
if (!quiet) message.warning('请先选择账号')
|
||||
return
|
||||
}
|
||||
setLoading('list')
|
||||
ListPrizes(config())
|
||||
ListPrizes(config(selectedLines))
|
||||
.then((data) => {
|
||||
const result = data as {
|
||||
prizes?: PrizeInfo[]
|
||||
@@ -1187,6 +1382,10 @@ export default function Dashboard() {
|
||||
|
||||
const startMonitor = (scheduled: boolean) => {
|
||||
const values = config()
|
||||
if (values.selectedAccountLines.length === 0) {
|
||||
message.warning('请先选择账号')
|
||||
return undefined
|
||||
}
|
||||
const countdown = scheduled ? buildScheduleCountdown(values.startAt, values.leadMs, now) : null
|
||||
if (scheduled && !countdown) {
|
||||
message.warning('请先填写定时目标时间(HH:mm:ss)')
|
||||
@@ -1205,6 +1404,10 @@ export default function Dashboard() {
|
||||
|
||||
const directExchange = () => {
|
||||
const values = config()
|
||||
if (values.selectedAccountLines.length === 0) {
|
||||
message.warning('请先选择账号')
|
||||
return undefined
|
||||
}
|
||||
if (Number(values.pid || 0) <= 0) {
|
||||
message.warning('请先在左侧商品列表中选择要兑换的商品')
|
||||
return undefined
|
||||
@@ -1262,14 +1465,29 @@ export default function Dashboard() {
|
||||
render: (_, row) => (
|
||||
<div>
|
||||
<strong>{row.uid || `第${row.line}行`}</strong>
|
||||
<div className="muted">{row.valid ? '已加入任务' : row.error || '未选择'}</div>
|
||||
<div className="muted">
|
||||
{isCookieCheckUnusable(row.checkStatus)
|
||||
? row.checkMessage || 'CK 已失效'
|
||||
: row.valid
|
||||
? row.checkStatus === 'valid'
|
||||
? 'CK 可用'
|
||||
: '已加入任务'
|
||||
: row.error || '未选择'}
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '凭据',
|
||||
width: 90,
|
||||
render: (_, row) => (row.valid ? <Tag color="green">已加载</Tag> : <Tag>无效</Tag>),
|
||||
width: 110,
|
||||
render: (_, row) =>
|
||||
row.checkStatus ? (
|
||||
cookieCheckTag(row.checkStatus, row.checkMessage)
|
||||
) : row.valid ? (
|
||||
<Tag color="green">已加载</Tag>
|
||||
) : (
|
||||
<Tag>无效</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '商品',
|
||||
@@ -1368,18 +1586,37 @@ export default function Dashboard() {
|
||||
() => [
|
||||
{
|
||||
title: '账号',
|
||||
width: 150,
|
||||
width: 140,
|
||||
render: (_, row) => (
|
||||
<div>
|
||||
<strong>{row.uid || '-'}</strong>
|
||||
<div className="muted">{row.valid ? '有效 CK' : row.error || '待校验'}</div>
|
||||
<div className="muted">
|
||||
{isCookieCheckUnusable(row.checkStatus) ? 'CK 已失效' : row.valid ? '可解析 UID' : row.error || '待校验'}
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
width: 86,
|
||||
render: (_, row) => (row.valid ? <Tag color="green">有效</Tag> : <Tag color="error">无效</Tag>),
|
||||
title: '格式',
|
||||
width: 78,
|
||||
render: (_, row) => (row.valid ? <Tag color="green">有效</Tag> : <Tag color="default">无效</Tag>),
|
||||
},
|
||||
{
|
||||
title: 'CK 状态',
|
||||
width: 190,
|
||||
render: (_, row) => (
|
||||
<div>
|
||||
{cookieCheckTag(row.checkStatus, row.checkMessage)}
|
||||
{row.checkStatus && row.checkStatus !== 'valid' && row.checkMessage ? (
|
||||
<div className="muted ck-check-message" title={row.checkMessage}>
|
||||
{row.checkMessage}
|
||||
</div>
|
||||
) : null}
|
||||
{row.checkedAt ? (
|
||||
<div className="muted">检查于 {new Date(row.checkedAt).toLocaleString('zh-CN', { hour12: false })}</div>
|
||||
) : null}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'CK',
|
||||
@@ -1595,7 +1832,7 @@ export default function Dashboard() {
|
||||
block
|
||||
icon={<RefreshCw size={16} />}
|
||||
loading={loading === 'list'}
|
||||
disabled={validAccounts.length === 0}
|
||||
disabled={selectedAccountCount === 0}
|
||||
onClick={() => queryPrizes()}
|
||||
>
|
||||
用 CK 刷新商品
|
||||
@@ -1719,7 +1956,7 @@ export default function Dashboard() {
|
||||
type="primary"
|
||||
icon={<ShoppingCart size={16} />}
|
||||
loading={loading === 'monitor'}
|
||||
disabled={running}
|
||||
disabled={running || selectedAccountCount === 0}
|
||||
onClick={() => startMonitor(false)}
|
||||
>
|
||||
立即抢购
|
||||
@@ -1729,7 +1966,7 @@ export default function Dashboard() {
|
||||
type="primary"
|
||||
icon={<Play size={16} />}
|
||||
loading={loading === 'monitor'}
|
||||
disabled={running}
|
||||
disabled={running || selectedAccountCount === 0}
|
||||
onClick={() => startMonitor(true)}
|
||||
>
|
||||
开启定时
|
||||
@@ -1737,7 +1974,7 @@ export default function Dashboard() {
|
||||
<Button
|
||||
icon={<Zap size={16} />}
|
||||
loading={loading === 'exchange'}
|
||||
disabled={running || validAccounts.length === 0}
|
||||
disabled={running || selectedAccountCount === 0}
|
||||
onClick={directExchange}
|
||||
>
|
||||
直接兑换
|
||||
@@ -1752,22 +1989,42 @@ export default function Dashboard() {
|
||||
<Button
|
||||
icon={<RefreshCw size={16} />}
|
||||
loading={loading === 'score'}
|
||||
onClick={() =>
|
||||
runAction('score', () => QueryScores(config()), (data) =>
|
||||
disabled={selectedAccountCount === 0}
|
||||
onClick={() => {
|
||||
const values = config()
|
||||
if (values.selectedAccountLines.length === 0) {
|
||||
message.warning('请先选择账号')
|
||||
return
|
||||
}
|
||||
runAction('score', () => QueryScores(values), (data) =>
|
||||
setScores((data || []) as ScoreResult[]),
|
||||
)
|
||||
}
|
||||
}}
|
||||
>
|
||||
查询积分
|
||||
</Button>
|
||||
<Button
|
||||
icon={<ShieldCheck size={16} />}
|
||||
loading={loading === 'cookie-check'}
|
||||
disabled={running || cookieEntries.length === 0}
|
||||
onClick={checkCookies}
|
||||
>
|
||||
CK 有效性
|
||||
</Button>
|
||||
<Button
|
||||
icon={<ClipboardList size={16} />}
|
||||
loading={loading === 'records'}
|
||||
onClick={() =>
|
||||
runAction('records', () => GetRecords(config()), (data) =>
|
||||
disabled={selectedAccountCount === 0}
|
||||
onClick={() => {
|
||||
const values = config()
|
||||
if (values.selectedAccountLines.length === 0) {
|
||||
message.warning('请先选择账号')
|
||||
return
|
||||
}
|
||||
runAction('records', () => GetRecords(values), (data) =>
|
||||
setRecords((data || []) as RecordResult[]),
|
||||
)
|
||||
}
|
||||
}}
|
||||
>
|
||||
兑换记录
|
||||
</Button>
|
||||
@@ -1815,7 +2072,9 @@ export default function Dashboard() {
|
||||
rowSelection={{
|
||||
selectedRowKeys: selectedAccountLines,
|
||||
onChange: setSelectedAccountLines,
|
||||
getCheckboxProps: (row) => ({ disabled: !row.valid }),
|
||||
getCheckboxProps: (row) => ({
|
||||
disabled: !row.valid || isCookieCheckUnusable(row.checkStatus),
|
||||
}),
|
||||
}}
|
||||
columns={accountColumns}
|
||||
dataSource={accounts}
|
||||
@@ -1848,7 +2107,15 @@ export default function Dashboard() {
|
||||
<Modal
|
||||
title="CK 管理"
|
||||
open={cookieManagerOpen}
|
||||
width={980}
|
||||
width="min(1080px, calc(100vw - 48px))"
|
||||
style={{ maxWidth: 'calc(100vw - 24px)' }}
|
||||
styles={{
|
||||
body: {
|
||||
maxHeight: 'calc(100vh - 180px)',
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden',
|
||||
},
|
||||
}}
|
||||
onCancel={() => setCookieManagerOpen(false)}
|
||||
footer={null}
|
||||
>
|
||||
@@ -1868,10 +2135,28 @@ export default function Dashboard() {
|
||||
<Button icon={<RefreshCw size={16} />} onClick={reloadSavedCookies}>
|
||||
重新载入
|
||||
</Button>
|
||||
<Button
|
||||
icon={<ShieldCheck size={16} />}
|
||||
loading={loading === 'cookie-check'}
|
||||
disabled={running || cookieEntries.length === 0}
|
||||
onClick={checkCookies}
|
||||
>
|
||||
检查 CK 有效性
|
||||
</Button>
|
||||
</Space>
|
||||
<Space wrap>
|
||||
<Tag>{cookieEntries.length} 条 CK</Tag>
|
||||
<Tag color="green">{validAccounts.length} 个有效</Tag>
|
||||
<Tag color="green">{cookieCheckStats.valid} 可用</Tag>
|
||||
{failedCookieCount > 0 ? <Tag color="error">{failedCookieCount} 失效</Tag> : null}
|
||||
{cookieCheckStats.unchecked > 0 ? <Tag>{cookieCheckStats.unchecked} 未检查</Tag> : null}
|
||||
<Button
|
||||
danger
|
||||
icon={<Trash2 size={16} />}
|
||||
disabled={failedCookieCount === 0}
|
||||
onClick={clearExpiredCookies}
|
||||
>
|
||||
清理失效
|
||||
</Button>
|
||||
<Button
|
||||
danger
|
||||
icon={<Trash2 size={16} />}
|
||||
@@ -1888,7 +2173,7 @@ export default function Dashboard() {
|
||||
columns={cookieColumns}
|
||||
dataSource={cookieEntries}
|
||||
pagination={{ pageSize: 8, showSizeChanger: false }}
|
||||
scroll={{ x: 900, y: 430 }}
|
||||
scroll={{ x: 1000, y: 430 }}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
+26
-1
@@ -502,24 +502,49 @@ body {
|
||||
.ck-manager {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.ck-manager-tools {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.ck-manager-tools > .ant-space {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.ck-manager-tools > .ant-space:last-child {
|
||||
margin-left: auto;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.ck-manager .ant-table-wrapper,
|
||||
.ck-manager .ant-spin-nested-loading,
|
||||
.ck-manager .ant-spin-container {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.ck-text-preview {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
color: #344054;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ck-check-message {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.settings-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
|
||||
Reference in New Issue
Block a user