优化 CK 管理和商品缓存

This commit is contained in:
yml2213
2026-07-06 21:33:52 +08:00
parent 6e20ce425b
commit ce6fdcc371
3 changed files with 584 additions and 135 deletions
+506 -96
View File
@@ -6,6 +6,7 @@ import {
Form,
Input,
InputNumber,
Modal,
Select,
Space,
Statistic,
@@ -21,11 +22,15 @@ import {
ClipboardList,
ClipboardPaste,
ListChecks,
Pencil,
Play,
Plus,
RefreshCw,
Save,
ShoppingCart,
Square,
Settings,
Trash2,
} from 'lucide-react'
import {
DefaultConfig,
@@ -42,6 +47,8 @@ import {
import { ClipboardGetText, EventsOn } from '../wailsjs/runtime/runtime'
const storageKey = 'huya-go.gui.config.v2'
const cookieStorageKey = 'huya-go.gui.cookies.v1'
const prizeCacheStorageKey = 'huya-go.gui.prizes.v1'
type UiConfig = {
transport: string
@@ -76,6 +83,15 @@ type AccountPreview = {
error?: string
}
type CookieEntry = {
id: string
text: string
uid: number
valid: boolean
error?: string
updatedAt: number
}
type LogEntry = {
time: string
level: string
@@ -95,6 +111,12 @@ type PrizeInfo = {
img?: string
}
type PrizeCache = {
prizes: PrizeInfo[]
updatedAt: number
sid?: number
}
type ScoreResult = {
uid: number
score: number
@@ -227,6 +249,55 @@ function readSavedConfig(): Partial<SavedConfig> {
}
}
function readSavedCookies(): CookieEntry[] {
try {
const items = JSON.parse(localStorage.getItem(cookieStorageKey) || '[]') as Partial<CookieEntry>[]
return items
.map((item, index) => ({
id: item.id || `ck-${Date.now()}-${index}`,
text: String(item.text || '').trim(),
uid: Number(item.uid || 0),
valid: Boolean(item.valid),
error: item.error,
updatedAt: Number(item.updatedAt || Date.now()),
}))
.filter((item) => item.text)
} catch {
return []
}
}
function writeSavedCookies(items: CookieEntry[]) {
localStorage.setItem(cookieStorageKey, JSON.stringify(items))
}
function readSavedPrizeCache(): PrizeCache {
try {
const cache = JSON.parse(localStorage.getItem(prizeCacheStorageKey) || '{}') as Partial<PrizeCache>
const prizes = Array.isArray(cache.prizes)
? cache.prizes.filter((item) => Number(item?.id || 0) > 0 && String(item?.name || '').trim())
: []
return {
prizes: prizes as PrizeInfo[],
updatedAt: Number(cache.updatedAt || 0),
sid: Number(cache.sid || 0) || undefined,
}
} catch {
return { prizes: [], updatedAt: 0 }
}
}
function writeSavedPrizeCache(prizes: PrizeInfo[], sid: number) {
localStorage.setItem(
prizeCacheStorageKey,
JSON.stringify({
prizes,
sid,
updatedAt: Date.now(),
}),
)
}
function normalizeCookieText(text: string): string {
const normalized = text
.replace(/\r\n?/g, '\n')
@@ -244,9 +315,20 @@ function normalizeCookieText(text: string): string {
.join('\n')
}
function cookieTextFromEntries(items: CookieEntry[]): string {
return items
.map((item) => item.text.trim())
.filter(Boolean)
.join('\n')
}
function newCookieID(): string {
return `ck-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
}
function pickConfig(values: Partial<UiConfig>): UiConfig {
return {
transport: values.transport || 'http',
transport: values.transport || 'wss',
wssHost: values.wssHost || '',
sid: Number(values.sid || 0),
pid: Number(values.pid || 0),
@@ -309,9 +391,10 @@ function normalizeMonitorResult(row: MonitorResult): MonitorResult {
}
export default function Dashboard() {
const { message } = AntApp.useApp()
const { message, modal } = AntApp.useApp()
const [form] = Form.useForm<UiConfig>()
const previewedCookieText = useRef('')
const watchedTransport = Form.useWatch('transport', form)
const watchedPID = Form.useWatch('pid', form)
const watchedName = Form.useWatch('name', form)
const watchedCookieText = Form.useWatch('cookieText', form)
@@ -327,19 +410,75 @@ export default function Dashboard() {
const [results, setResults] = useState<MonitorResult[]>([])
const [logs, setLogs] = useState<LogEntry[]>([])
const [now, setNow] = useState(new Date())
const [settingsOpen, setSettingsOpen] = useState(false)
const [cookieManagerOpen, setCookieManagerOpen] = useState(false)
const [cookieEditOpen, setCookieEditOpen] = useState(false)
const [editingCookie, setEditingCookie] = useState<CookieEntry | null>(null)
const [cookieDraft, setCookieDraft] = useState('')
const [cookieEntries, setCookieEntries] = useState<CookieEntry[]>([])
const [prizeCacheUpdatedAt, setPrizeCacheUpdatedAt] = useState(0)
const applyCookieEntries = async (entries: CookieEntry[], persist = true, refreshLatest = false) => {
const normalizedEntries = entries
.map((entry) => ({
...entry,
text: normalizeCookieText(entry.text).split('\n')[0] || '',
}))
.filter((entry) => entry.text)
const cookieText = cookieTextFromEntries(normalizedEntries)
previewedCookieText.current = cookieText
form.setFieldsValue({ cookieText })
setSelectedPrizeKeys([])
if (!cookieText) {
setCookieEntries([])
setAccounts([])
setSelectedAccountLines([])
if (persist) writeSavedCookies([])
return []
}
const previews = (await PreviewAccounts(cookieText)) as AccountPreview[]
const enriched = normalizedEntries.map((entry, index) => {
const preview = previews.find((item) => item.line === index + 1)
return {
...entry,
uid: preview?.uid || 0,
valid: Boolean(preview?.valid),
error: preview?.error,
}
})
setCookieEntries(enriched)
setAccounts(previews || [])
setSelectedAccountLines((previews || []).filter((item) => item.valid).map((item) => item.line))
if (persist) writeSavedCookies(enriched)
if (refreshLatest && enriched.some((item) => item.valid)) {
window.setTimeout(() => queryPrizes(true), 0)
}
return enriched
}
useEffect(() => {
let mounted = true
Promise.all([DefaultConfig(), GetLogs(), IsRunning()])
.then(([defaults, logItems, isRunning]) => {
if (!mounted) return
const savedCookies = readSavedCookies()
const savedPrizeCache = readSavedPrizeCache()
form.setFieldsValue({
...(defaults as UiConfig),
...readSavedConfig(),
cookieText: '',
cookieText: cookieTextFromEntries(savedCookies),
})
setCookieEntries(savedCookies)
setPrizes(savedPrizeCache.prizes)
setPrizeCacheUpdatedAt(savedPrizeCache.updatedAt)
setLogs((logItems || []) as LogEntry[])
setRunning(Boolean(isRunning))
if (savedCookies.length > 0) {
applyCookieEntries(savedCookies, true, true).catch((err: unknown) => message.error(String(err)))
}
})
.catch((err: unknown) => message.error(String(err)))
@@ -371,11 +510,14 @@ export default function Dashboard() {
if ((watchedCookieText || '') === previewedCookieText.current) return
setAccounts([])
setSelectedAccountLines([])
setPrizes([])
setSelectedPrizeKeys([])
}, [watchedCookieText])
const validAccounts = useMemo(() => accounts.filter((item) => item.valid), [accounts])
const importedCookieCount = useMemo(
() => normalizeCookieText(watchedCookieText || '').split('\n').filter(Boolean).length,
[watchedCookieText],
)
const selectedAccountCount = selectedAccountLines.length
const selectedPrizeID =
selectedPrizeKeys.length === 1 ? Number(selectedPrizeKeys[0]) || undefined : undefined
@@ -425,15 +567,6 @@ export default function Dashboard() {
)
}, [prizes, productSearch])
const prizeOptions = useMemo(
() =>
prizes.map((item) => ({
value: item.id,
label: `${item.name} · ${item.id} · ${priceOf(item)}积分`,
})),
[prizes],
)
const config = () => ({
...pickConfig(form.getFieldsValue(true)),
selectedAccountLines: selectedAccountLines.map(Number).filter((line) => line > 0),
@@ -456,12 +589,43 @@ export default function Dashboard() {
}
}
const previewAccounts = async (cookieText?: string) => {
const text = cookieText ?? form.getFieldValue('cookieText') ?? ''
previewedCookieText.current = text
const items = (await PreviewAccounts(text)) as AccountPreview[]
setAccounts(items || [])
setSelectedAccountLines((items || []).filter((item) => item.valid).map((item) => item.line))
const upsertCookieLines = async (lines: string[]) => {
const normalizedText = normalizeCookieText(lines.join('\n'))
const nextLines = normalizedText.split('\n').filter(Boolean)
if (nextLines.length === 0) return { added: 0, updated: 0 }
const previews = (await PreviewAccounts(nextLines.join('\n'))) as AccountPreview[]
const nextEntries = [...cookieEntries]
let added = 0
let updated = 0
nextLines.forEach((text, index) => {
const preview = previews.find((item) => item.line === index + 1)
const uid = preview?.uid || 0
const valid = Boolean(preview?.valid)
const existingIndex =
valid && uid > 0
? nextEntries.findIndex((item) => item.valid && item.uid === uid)
: nextEntries.findIndex((item) => item.text === text)
const entry: CookieEntry = {
id: existingIndex >= 0 ? nextEntries[existingIndex].id : newCookieID(),
text,
uid,
valid,
error: preview?.error,
updatedAt: Date.now(),
}
if (existingIndex >= 0) {
nextEntries[existingIndex] = entry
updated += 1
} else {
nextEntries.push(entry)
added += 1
}
})
await applyCookieEntries(nextEntries, true, true)
return { added, updated }
}
const importCookiesFromClipboard = async () => {
@@ -473,9 +637,8 @@ export default function Dashboard() {
message.warning('剪贴板没有可导入的 CK')
return
}
form.setFieldsValue({ cookieText })
await previewAccounts(cookieText)
message.success(`已从剪贴板导入 ${cookieText.split('\n').length} 条 CK`)
const result = await upsertCookieLines(cookieText.split('\n'))
message.success(`已导入 ${result.added} 条,更新 ${result.updated} 条 CK`)
} catch (err: unknown) {
message.error(`剪贴板导入失败: ${String(err)}`)
} finally {
@@ -483,20 +646,120 @@ export default function Dashboard() {
}
}
const queryPrizes = () => {
if (!String(form.getFieldValue('cookieText') || '').trim()) {
message.warning('Cookie 不能为空')
const openNewCookie = () => {
setEditingCookie(null)
setCookieDraft('')
setCookieEditOpen(true)
}
const openEditCookie = (entry: CookieEntry) => {
setEditingCookie(entry)
setCookieDraft(entry.text)
setCookieEditOpen(true)
}
const saveCookieDraft = async () => {
const normalizedText = normalizeCookieText(cookieDraft)
if (!normalizedText) {
message.warning('CK 不能为空')
return
}
runAction('list', () => ListPrizes(config()), (data) => {
const result = data as { prizes?: PrizeInfo[] }
const nextPrizes = result?.prizes || []
setPrizes(nextPrizes)
setSelectedPrizeKeys((prev) =>
prev.filter((key) => nextPrizes.some((item) => item.id === Number(key))),
)
if (nextPrizes.length === 0) {
message.warning('当前 CK 未返回可选商品')
setLoading('cookie-save')
try {
if (editingCookie) {
const text = normalizedText.split('\n')[0]
const nextEntries = cookieEntries.map((entry) =>
entry.id === editingCookie.id ? { ...entry, text, updatedAt: Date.now() } : entry,
)
await applyCookieEntries(nextEntries, true, true)
message.success('CK 已更新')
} else {
const result = await upsertCookieLines(normalizedText.split('\n'))
message.success(`已新增 ${result.added} 条,更新 ${result.updated} 条 CK`)
}
setCookieEditOpen(false)
setCookieDraft('')
setEditingCookie(null)
} catch (err: unknown) {
message.error(String(err))
} finally {
setLoading('')
}
}
const deleteCookie = async (entry: CookieEntry) => {
await applyCookieEntries(cookieEntries.filter((item) => item.id !== entry.id))
message.success('CK 已删除')
}
const clearCookies = () => {
modal.confirm({
title: '清空 CK',
content: '确认删除所有已保存 CK?',
okText: '清空',
cancelText: '取消',
okButtonProps: { danger: true },
onOk: async () => {
await applyCookieEntries([])
message.success('CK 已清空')
},
})
}
const reloadSavedCookies = async () => {
const savedCookies = readSavedCookies()
await applyCookieEntries(savedCookies, true, true)
message.success('已重新载入本地 CK')
}
function queryPrizes(quiet = false) {
if (!String(form.getFieldValue('cookieText') || '').trim()) {
if (!quiet) message.warning('Cookie 不能为空')
return
}
setLoading('list')
ListPrizes(config())
.then((data) => {
const result = data as { prizes?: PrizeInfo[] }
const nextPrizes = result?.prizes || []
if (nextPrizes.length === 0) {
if (!quiet) message.warning('当前 CK 未返回可选商品')
return
}
const updatedAt = Date.now()
setPrizes(nextPrizes)
setPrizeCacheUpdatedAt(updatedAt)
writeSavedPrizeCache(nextPrizes, Number(form.getFieldValue('sid') || 0))
setSelectedPrizeKeys((prev) =>
prev.filter((key) => nextPrizes.some((item) => item.id === Number(key))),
)
if (!quiet) message.success('商品已刷新')
})
.catch((err: unknown) => {
if (!quiet) message.error(String(err))
})
.finally(() => {
setLoading('')
})
}
const productListUpdatedAt = prizeCacheUpdatedAt
? new Date(prizeCacheUpdatedAt).toLocaleString('zh-CN', { hour12: false })
: ''
const clearPrizeCache = () => {
modal.confirm({
title: '清空商品缓存',
content: '确认清空本地缓存的商品列表?',
okText: '清空',
cancelText: '取消',
okButtonProps: { danger: true },
onOk: () => {
localStorage.removeItem(prizeCacheStorageKey)
setPrizes([])
setSelectedPrizeKeys([])
setPrizeCacheUpdatedAt(0)
message.success('商品缓存已清空')
}
})
}
@@ -546,8 +809,6 @@ export default function Dashboard() {
})
}
const choosePrize = (pid?: number) => applyPrizeSelection(pid ? [pid] : [])
const startMonitor = (scheduled: boolean) => {
const values = config()
if (!scheduled) values.startAt = ''
@@ -621,6 +882,51 @@ export default function Dashboard() {
[currentPrize?.name, latestResultByUID, scoreMap, watchedName],
)
const cookieColumns: ColumnsType<CookieEntry> = useMemo(
() => [
{
title: '账号',
width: 150,
render: (_, row) => (
<div>
<strong>{row.uid || '-'}</strong>
<div className="muted">{row.valid ? '有效 CK' : row.error || '待校验'}</div>
</div>
),
},
{
title: '状态',
width: 86,
render: (_, row) => (row.valid ? <Tag color="green">有效</Tag> : <Tag color="error">无效</Tag>),
},
{
title: 'CK',
ellipsis: true,
render: (_, row) => <code className="ck-text-preview">{row.text}</code>,
},
{
title: '更新时间',
width: 170,
render: (_, row) => new Date(row.updatedAt).toLocaleString('zh-CN', { hour12: false }),
},
{
title: '操作',
width: 150,
render: (_, row) => (
<Space size={6}>
<Button size="small" icon={<Pencil size={14} />} onClick={() => openEditCookie(row)}>
编辑
</Button>
<Button size="small" danger icon={<Trash2 size={14} />} onClick={() => deleteCookie(row)}>
删除
</Button>
</Space>
),
},
],
[cookieEntries],
)
const prizeColumns: ColumnsType<PrizeInfo> = useMemo(
() => [
{ title: 'num', dataIndex: 'id', width: 92 },
@@ -759,7 +1065,8 @@ export default function Dashboard() {
block
icon={<RefreshCw size={16} />}
loading={loading === 'list'}
onClick={queryPrizes}
disabled={validAccounts.length === 0}
onClick={() => queryPrizes()}
>
用 CK 刷新商品
</Button>
@@ -775,15 +1082,15 @@ export default function Dashboard() {
<CurrentProduct prize={currentPrize} />
<div className="inventory-line">
<span>实时库存 {filteredPrizes.length} 个</span>
<span>{now.toLocaleString('zh-CN', { hour12: false })}</span>
<span>商品缓存 {filteredPrizes.length} 个</span>
<span>{productListUpdatedAt ? `更新 ${productListUpdatedAt}` : '未缓存'}</span>
</div>
<div className="product-list">
{filteredPrizes.length === 0 ? (
<div className="product-empty">
<strong>未加载 CK 商品</strong>
<span>列表为空</span>
<strong>暂无商品缓存</strong>
<span>导入有效 CK 后刷新最新商品</span>
</div>
) : (
filteredPrizes.map((item) => (
@@ -817,19 +1124,14 @@ export default function Dashboard() {
<header className="topbar">
<div className="tab-strip">
<Button type="primary">抢购控制台</Button>
<Button>CK 管理</Button>
<Button onClick={() => setCookieManagerOpen(true)}>CK 管理</Button>
<Button>等待操作</Button>
</div>
<Space size={16}>
<Form.Item name="transport" noStyle>
<Select
className="transport-select"
options={[
{ value: 'http', label: 'HTTP' },
{ value: 'wss', label: 'WSS' },
]}
/>
</Form.Item>
<Space size={12}>
<Tag>{String(watchedTransport || 'wss').toUpperCase()}</Tag>
<Button icon={<Settings size={16} />} onClick={() => setSettingsOpen(true)}>
设置
</Button>
<Tag color={running ? 'processing' : 'default'}>{running ? '运行中' : '空闲'}</Tag>
</Space>
</header>
@@ -967,19 +1269,142 @@ export default function Dashboard() {
</div>
</section>
<section className="bottom-config">
<div>
<Typography.Text strong>目标商品</Typography.Text>
</main>
</div>
<Form.Item name="name" hidden>
<Input />
</Form.Item>
<Form.Item name="targetText" hidden>
<Input />
</Form.Item>
<Form.Item name="cookieText" hidden>
<Input />
</Form.Item>
<Modal
title="CK 管理"
open={cookieManagerOpen}
width={980}
onCancel={() => setCookieManagerOpen(false)}
footer={null}
>
<div className="ck-manager">
<div className="ck-manager-tools">
<Space wrap>
<Button type="primary" icon={<Plus size={16} />} onClick={openNewCookie}>
新增 CK
</Button>
<Button
icon={<ClipboardPaste size={16} />}
loading={loading === 'clipboard'}
onClick={importCookiesFromClipboard}
>
剪贴板导入
</Button>
<Button icon={<RefreshCw size={16} />} onClick={reloadSavedCookies}>
重新载入
</Button>
</Space>
<Space wrap>
<Tag>{cookieEntries.length} 条 CK</Tag>
<Tag color="green">{validAccounts.length} 个有效</Tag>
<Button
danger
icon={<Trash2 size={16} />}
disabled={cookieEntries.length === 0}
onClick={clearCookies}
>
清空
</Button>
</Space>
</div>
<Table
size="small"
rowKey={(row) => row.id}
columns={cookieColumns}
dataSource={cookieEntries}
pagination={{ pageSize: 8, showSizeChanger: false }}
scroll={{ x: 900, y: 430 }}
/>
</div>
</Modal>
<Modal
title={editingCookie ? '编辑 CK' : '新增 CK'}
open={cookieEditOpen}
okText="保存"
cancelText="取消"
confirmLoading={loading === 'cookie-save'}
onOk={saveCookieDraft}
onCancel={() => {
setCookieEditOpen(false)
setCookieDraft('')
setEditingCookie(null)
}}
>
<Input.TextArea
rows={editingCookie ? 5 : 8}
spellCheck={false}
value={cookieDraft}
onChange={(event) => setCookieDraft(event.target.value)}
/>
</Modal>
<Modal
title="设置"
open={settingsOpen}
width={760}
onCancel={() => setSettingsOpen(false)}
footer={[
<Button key="close" onClick={() => setSettingsOpen(false)}>
关闭
</Button>,
<Button
key="save"
type="primary"
icon={<Save size={16} />}
onClick={() => {
saveSettings()
setSettingsOpen(false)
}}
>
保存
</Button>,
]}
>
<div className="settings-modal">
<div className="settings-grid">
<Form.Item name="transport" label="传输">
<Select
allowClear
showSearch
className="target-select"
optionFilterProp="label"
value={selectedPrizeID}
options={prizeOptions}
onChange={choosePrize}
options={[
{ value: 'wss', label: 'WSS' },
{ value: 'http', label: 'HTTP' },
]}
/>
</Form.Item>
<Form.Item name="wssHost" label="WSS Host">
<Input allowClear />
</Form.Item>
<div className="settings-summary settings-wide">
<Typography.Text type="secondary">CK 来源</Typography.Text>
<strong>
{importedCookieCount > 0
? `已导入 ${validAccounts.length}/${importedCookieCount} 个有效账号`
: '未导入 CK'}
</strong>
</div>
<div className="settings-summary settings-wide">
<Typography.Text type="secondary">商品缓存</Typography.Text>
<Space size={8}>
<strong>
{prizes.length > 0
? `${prizes.length} 个 · ${productListUpdatedAt || '未知时间'}`
: '未缓存'}
</strong>
<Button size="small" disabled={prizes.length === 0} onClick={clearPrizeCache}>
清空
</Button>
</Space>
</div>
</div>
<div className="settings-param-grid">
<Form.Item name="sid" label="SID">
<InputNumber min={1} />
</Form.Item>
@@ -998,44 +1423,29 @@ export default function Dashboard() {
<Form.Item name="leadMs" label="提前">
<InputNumber min={0} max={10000} addonAfter="ms" />
</Form.Item>
</section>
<section className="advanced-config">
<Form.Item name="name" label="名称">
<Input allowClear />
</div>
<div className="settings-switch-grid">
<Form.Item name="prewarm" label="预热" valuePropName="checked">
<Switch />
</Form.Item>
<Form.Item name="targetText" label="多目标">
<Input.TextArea rows={2} spellCheck={false} />
<Form.Item name="directExchange" label="直兑" valuePropName="checked">
<Switch />
</Form.Item>
<Form.Item name="wssHost" label="WSS Host">
<Input allowClear />
<Form.Item name="sharePrizeList" label="共享列表" valuePropName="checked">
<Switch />
</Form.Item>
<Form.Item name="cookieText" label="Cookie">
<Input.TextArea rows={3} spellCheck={false} className="cookie-box" />
<Form.Item name="autoSelect" label="自动选品" valuePropName="checked">
<Switch />
</Form.Item>
<div className="switch-grid">
<Form.Item name="prewarm" label="预热" valuePropName="checked">
<Switch />
</Form.Item>
<Form.Item name="directExchange" label="直兑" valuePropName="checked">
<Switch />
</Form.Item>
<Form.Item name="sharePrizeList" label="共享列表" valuePropName="checked">
<Switch />
</Form.Item>
<Form.Item name="autoSelect" label="自动选品" valuePropName="checked">
<Switch />
</Form.Item>
<Form.Item name="stopAccountAfterSuccess" label="账号成功停" valuePropName="checked">
<Switch />
</Form.Item>
<Form.Item name="refreshAfterSuccess" label="成功刷新" valuePropName="checked">
<Switch />
</Form.Item>
</div>
</section>
</main>
</div>
<Form.Item name="stopAccountAfterSuccess" label="账号成功停" valuePropName="checked">
<Switch />
</Form.Item>
<Form.Item name="refreshAfterSuccess" label="成功刷新" valuePropName="checked">
<Switch />
</Form.Item>
</div>
</div>
</Modal>
</Form>
)
}
+77 -38
View File
@@ -165,7 +165,7 @@ body {
display: grid;
min-width: 0;
min-height: 0;
grid-template-rows: 64px 48px 72px minmax(0, 1fr) auto auto;
grid-template-rows: 64px 48px 72px minmax(0, 1fr);
background: #f7fafc;
}
@@ -184,10 +184,6 @@ body {
gap: 12px;
}
.transport-select {
width: 96px;
}
.statusbar {
display: flex;
align-items: center;
@@ -329,9 +325,7 @@ body {
padding: 8px 2px;
}
.stats-pane .ant-statistic,
.bottom-config,
.advanced-config {
.stats-pane .ant-statistic {
border: 1px solid #e6edf3;
border-radius: 8px;
background: #fff;
@@ -341,43 +335,87 @@ body {
padding: 14px;
}
.bottom-config {
.settings-modal {
display: grid;
grid-template-columns: minmax(280px, 1fr) repeat(6, max-content);
gap: 10px;
align-items: end;
margin: 0 20px 10px;
padding: 10px 12px;
gap: 16px;
padding-top: 4px;
}
.bottom-config .ant-form-item,
.advanced-config .ant-form-item {
.ck-manager {
display: grid;
gap: 12px;
}
.ck-manager-tools {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 12px;
}
.ck-text-preview {
display: block;
overflow: hidden;
color: #344054;
text-overflow: ellipsis;
white-space: nowrap;
}
.settings-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}
.settings-wide {
grid-column: 1 / -1;
}
.settings-summary {
display: flex;
align-items: center;
justify-content: space-between;
min-height: 38px;
border: 1px solid #e6edf3;
border-radius: 6px;
padding: 8px 10px;
}
.settings-grid .ant-form-item,
.settings-param-grid .ant-form-item,
.settings-switch-grid .ant-form-item {
margin-bottom: 0;
}
.target-select {
width: 100%;
margin-top: 6px;
}
.advanced-config {
display: grid;
grid-template-columns: 180px minmax(220px, 0.8fr) minmax(220px, 1fr) minmax(320px, 1.2fr);
gap: 10px;
align-items: end;
margin: 0 20px 14px;
padding: 10px 12px;
}
.switch-grid {
.settings-param-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 8px;
gap: 10px;
}
.cookie-box textarea,
.cookie-box {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
.settings-param-grid .ant-input-number {
width: 100%;
}
.settings-switch-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 10px;
}
.settings-switch-grid .ant-form-item {
display: flex;
align-items: center;
justify-content: space-between;
min-height: 38px;
border: 1px solid #e6edf3;
border-radius: 6px;
padding: 8px 10px;
}
.settings-switch-grid .ant-form-item-row {
width: 100%;
}
.ant-table-wrapper {
@@ -400,9 +438,10 @@ body {
grid-template-columns: 1fr;
}
.bottom-config,
.advanced-config {
grid-template-columns: repeat(2, minmax(0, 1fr));
.settings-grid,
.settings-param-grid,
.settings-switch-grid {
grid-template-columns: 1fr;
}
}
+1 -1
View File
@@ -125,7 +125,7 @@ func (a *App) Shutdown(ctx context.Context) {
func (a *App) DefaultConfig() Config {
return Config{
Transport: "http",
Transport: "wss",
WSSHost: huya.DefaultWSSHost,
SID: 2203,
Loops: 20,