实现虎牙刷新商品列表

This commit is contained in:
yml2213
2026-07-04 22:21:35 +08:00
parent e855d7e449
commit fa0d49ff8c
6 changed files with 432 additions and 18 deletions
+214
View File
@@ -104,6 +104,191 @@ class GetUserScoreResp(TafStruct):
} }
class GetActPrizeListReq(TafStruct):
"""webActUI.getActPrizeList 请求。"""
def __init__(self):
self.userId = ActivityUserId()
self.sid: int = 0
def write_to(self, os: TafOutputStream):
os.write_struct(0, self.userId)
os.write_int32(1, self.sid)
def read_from(self, ins: TafInputStream):
self.userId = ins.read_struct(0, ActivityUserId) or self.userId
self.sid = ins.read_int32(1, default=self.sid)
class ActPrizeItem(TafStruct):
"""活动商品项,字段位置来自 getActPrizeList 实测响应。"""
def __init__(self):
self.prizeId: int = 0
self.prizeType: int = 0
self.name: str = ""
self.score: int = 0
self.icon: str = ""
self.stock: int = 0
self.remainText: str = ""
self.price: int = 0
self.categoryId: str = ""
self.totalRemain: int = 0
self.rawOrder: int = 0
self.startTime: int = 0
self.endTime: int = 0
def read_from(self, ins: TafInputStream):
self.prizeId = ins.read_int64(0, default=self.prizeId)
self.prizeType = ins.read_int32(1, default=self.prizeType)
self.name = ins.read_string(2, default=self.name)
self.score = ins.read_int64(3, default=self.score)
ins.read_int32(4, default=0)
self.icon = ins.read_string(5, default=self.icon)
self.stock = ins.read_int64(10, default=self.stock)
self.remainText = ins.read_string(12, default=self.remainText)
self.price = ins.read_int64(13, default=self.price)
self.categoryId = ins.read_string(14, default=self.categoryId)
self.totalRemain = ins.read_int64(17, default=self.totalRemain)
self.rawOrder = ins.read_int64(22, default=self.rawOrder)
self.startTime = ins.read_int64(25, default=self.startTime)
self.endTime = ins.read_int64(26, default=self.endTime)
def write_to(self, os: TafOutputStream):
os.write_int64(0, self.prizeId)
os.write_int32(1, self.prizeType)
os.write_string(2, self.name)
os.write_int64(3, self.score)
os.write_string(5, self.icon)
os.write_int64(10, self.stock)
os.write_string(12, self.remainText)
os.write_int64(13, self.price)
os.write_string(14, self.categoryId)
os.write_int64(17, self.totalRemain)
os.write_int64(22, self.rawOrder)
os.write_int64(25, self.startTime)
os.write_int64(26, self.endTime)
@staticmethod
def read_list_item(ins: TafInputStream, _tag: int):
item = ActPrizeItem()
item.read_from(ins)
while True:
_end_tag, dtype = ins.read_head()
if dtype == TafType.STRUCT_END:
break
ins.skip_field(dtype)
return item
@staticmethod
def _remain_percent_text(value: str) -> str:
text = str(value or "").strip()
if not text or text.endswith("%"):
return text
try:
float(text)
except ValueError:
return text
return f"{text}%"
def to_dict(self) -> dict:
return {
"product_id": str(self.prizeId),
"name": self.name,
"price": self.price,
"remain_text": self._remain_percent_text(self.remainText),
"remain_percent": self.remainText,
"stock": self.stock,
"score": self.score,
"icon": self.icon,
"prize_type": self.prizeType,
"category_id": self.categoryId,
"total_remain": self.totalRemain,
"raw_order": self.rawOrder,
"start_time": self.startTime,
"end_time": self.endTime,
}
class ActPrizeTag(TafStruct):
"""活动商品分类。"""
def __init__(self):
self.tagId: int = 0
self.name: str = ""
self.sort: int = 0
def read_from(self, ins: TafInputStream):
self.tagId = ins.read_int64(0, default=self.tagId)
self.name = ins.read_string(1, default=self.name)
self.sort = ins.read_int32(2, default=self.sort)
def write_to(self, os: TafOutputStream):
os.write_int64(0, self.tagId)
os.write_string(1, self.name)
os.write_int32(2, self.sort)
@staticmethod
def read_list_item(ins: TafInputStream, _tag: int):
item = ActPrizeTag()
item.read_from(ins)
while True:
_end_tag, dtype = ins.read_head()
if dtype == TafType.STRUCT_END:
break
ins.skip_field(dtype)
return item
def to_dict(self) -> dict:
return {
"category_id": str(self.tagId),
"category_name": self.name,
"sort": self.sort,
}
class GetActPrizeListResp(TafStruct):
"""webActUI.getActPrizeList 响应。"""
def __init__(self):
self.status: int = 0
self.msg: str = ""
self.prizes: list[ActPrizeItem] = []
self.tags: list[ActPrizeTag] = []
def read_from(self, ins: TafInputStream):
self.status = ins.read_int32(0, default=self.status)
self.msg = ins.read_string(1, default=self.msg)
self.prizes = ins.read_list(2, item_reader=ActPrizeItem.read_list_item)
self.tags = ins.read_list(4, item_reader=ActPrizeTag.read_list_item)
def write_to(self, os: TafOutputStream):
os.write_int32(0, self.status)
os.write_string(1, self.msg)
os.write_list(2, self.prizes)
os.write_list(4, self.tags)
def to_dict(self) -> dict:
tag_info = {
str(item.tagId): {"name": item.name, "sort": index, "raw_sort": item.sort}
for index, item in enumerate(self.tags)
}
goods = []
for item in self.prizes:
data = item.to_dict()
category = tag_info.get(data["category_id"], {})
data["category_name"] = category.get("name", "")
data["category_sort"] = category.get("sort", 0)
goods.append(data)
return {
"status": self.status,
"msg": self.msg,
"goods_count": len(self.prizes),
"categories": [item.to_dict() for item in self.tags],
"goods": goods,
}
class GameAccount(TafStruct): class GameAccount(TafStruct):
"""游戏账号信息。""" """游戏账号信息。"""
@@ -532,6 +717,35 @@ def _self_check():
assert parsed.status == 200 assert parsed.status == 200
assert parsed.available_score == 180 assert parsed.available_score == 180
prize_rsp = GetActPrizeListResp()
prize = ActPrizeItem()
prize.prizeId = 12885
prize.name = "赤蝎幽灵大礼包"
prize.price = 3600
prize.remainText = "39"
prize.categoryId = "85"
tag = ActPrizeTag()
tag.tagId = 85
tag.name = "限量返场"
tag.sort = 2
prize_rsp.status = 200
prize_rsp.msg = "请求成功"
prize_rsp.prizes = [prize]
prize_rsp.tags = [tag]
os = TafOutputStream()
os.write_struct(0, prize_rsp)
ins = TafInputStream(os.get_bytes())
_, dtype = ins.read_head()
assert dtype == TafType.STRUCT_BEGIN
parsed_prize = GetActPrizeListResp()
parsed_prize.read_from(ins)
assert parsed_prize.status == 200
assert parsed_prize.prizes[0].name == "赤蝎幽灵大礼包"
assert parsed_prize.prizes[0].price == 3600
parsed_prize_data = parsed_prize.to_dict()
assert parsed_prize_data["goods"][0]["category_name"] == "限量返场"
assert parsed_prize_data["goods"][0]["remain_text"] == "39%"
link_rsp = GetLiveLinkParamResp() link_rsp = GetLiveLinkParamResp()
link_rsp.status = 200 link_rsp.status = 200
link_rsp.livelinkParam = { link_rsp.livelinkParam = {
+16
View File
@@ -270,6 +270,22 @@ class HuyaHttpClient:
timeout=timeout, timeout=timeout,
) )
def get_act_prize_list(self, uid: int, cookie: str, sid: int, timeout: float = 15.0):
"""查询活动可兑换商品列表。"""
from .activity_structs import GetActPrizeListReq, GetActPrizeListResp
req = GetActPrizeListReq()
req.userId = self._build_activity_user(uid, cookie)
req.sid = sid
return self.call_rpc(
"webActUI",
"getActPrizeList",
req,
GetActPrizeListResp,
uid=uid,
cookie=cookie,
timeout=timeout,
)
def check_user_bind_game_account( def check_user_bind_game_account(
self, self,
uid: int, uid: int,
+1 -1
View File
@@ -196,7 +196,7 @@ def list_goods(
current: User = Depends(require_permission("huya:task")), current: User = Depends(require_permission("huya:task")),
): ):
"""查看已缓存的虎牙商品快照。""" """查看已缓存的虎牙商品快照。"""
rows = db.query(HuyaGoodsSnapshot).order_by(HuyaGoodsSnapshot.updated_at.desc()).all() rows = db.query(HuyaGoodsSnapshot).order_by(HuyaGoodsSnapshot.id.asc()).all()
return rows return rows
+77 -2
View File
@@ -11,7 +11,7 @@ from sqlalchemy.orm import Session, joinedload
from core.huya import HuyaHttpClient from core.huya import HuyaHttpClient
from ..database import SessionLocal from ..database import SessionLocal
from ..models import HuyaAccount, HuyaTask from ..models import HuyaAccount, HuyaGoodsSnapshot, HuyaTask
from .huya_service import HUYA_CONFIG_FIELDS, cookie_value, ensure_huya_config, huya_config_value from .huya_service import HUYA_CONFIG_FIELDS, cookie_value, ensure_huya_config, huya_config_value
@@ -197,6 +197,73 @@ class HuyaBatchRunner:
account.updated_at = datetime.now(timezone.utc) account.updated_at = datetime.now(timezone.utc)
self._mark_task(worker_db, task, "success", f"积分: {points}", result) self._mark_task(worker_db, task, "success", f"积分: {points}", result)
def _execute_refresh_goods(
self,
worker_db: Session,
task: HuyaTask,
account: HuyaAccount,
account_info: dict,
config_info: dict,
):
sid = str(self.payload.get("sid") or config_info.get("sid") or "").strip()
if not sid:
self._mark_task(worker_db, task, "failed", "请先配置虎牙活动 SID")
return
sid_int = self._to_int(sid)
if not sid_int:
self._mark_task(worker_db, task, "failed", f"虎牙活动 SID 无效: {sid}")
return
uid = self._resolve_uid(account_info)
if not uid:
self._mark_task(worker_db, task, "failed", "无法从账号或 Cookie 解析 yyuid")
return
cookie = account_info.get("cookie") or ""
if not cookie:
self._mark_task(worker_db, task, "failed", "账号 Cookie 为空")
return
client = HuyaHttpClient(logger=lambda msg: self._push_log("info", f"[{uid}] {msg}"))
response = client.get_act_prize_list(uid=uid, cookie=cookie, sid=sid_int)
if response is None:
self._mark_task(worker_db, task, "error", "虎牙商品列表接口无响应")
return
result = response.to_dict()
result["sid"] = sid_int
if response.status != 200:
self._mark_task(
worker_db,
task,
"failed",
response.msg or f"虎牙商品列表刷新失败: {response.status}",
result,
)
return
goods = [
item for item in result.get("goods", [])
if item.get("product_id") and item.get("name")
]
now = datetime.now(timezone.utc)
worker_db.query(HuyaGoodsSnapshot).delete(synchronize_session=False)
for item in goods:
worker_db.add(HuyaGoodsSnapshot(
product_id=item["product_id"],
name=item["name"],
price=item["price"],
remain_text=item["remain_text"],
raw=item,
updated_at=now,
))
account.status = "goods_refreshed"
account.updated_at = now
message = f"已刷新商品 {len(goods)}"
self._mark_task(worker_db, task, "success", message, {**result, "goods": goods})
def _execute_get_bind_qr( def _execute_get_bind_qr(
self, self,
worker_db: Session, worker_db: Session,
@@ -556,7 +623,13 @@ class HuyaBatchRunner:
name = self._account_name(account_info) name = self._account_name(account_info)
self._push_log("info", f"[{current}/{total}] 开始虎牙任务: {name}") self._push_log("info", f"[{current}/{total}] 开始虎牙任务: {name}")
if self.task_type not in {"query_points", "get_bind_qr", "confirm_bind", "query_game_name"}: if self.task_type not in {
"query_points",
"get_bind_qr",
"confirm_bind",
"query_game_name",
"refresh_goods",
}:
self._mark_task(worker_db, task, "failed", "该虎牙任务执行器暂未实现") self._mark_task(worker_db, task, "failed", "该虎牙任务执行器暂未实现")
self._push_log("warning", f"[{current}] {name} 暂未实现: {self.task_type}") self._push_log("warning", f"[{current}] {name} 暂未实现: {self.task_type}")
return return
@@ -564,6 +637,8 @@ class HuyaBatchRunner:
try: try:
if self.task_type == "query_points": if self.task_type == "query_points":
self._execute_query_points(worker_db, task, account, account_info, config_info) self._execute_query_points(worker_db, task, account, account_info, config_info)
elif self.task_type == "refresh_goods":
self._execute_refresh_goods(worker_db, task, account, account_info, config_info)
elif self.task_type == "get_bind_qr": elif self.task_type == "get_bind_qr":
self._execute_get_bind_qr(worker_db, task, account, account_info, config_info) self._execute_get_bind_qr(worker_db, task, account, account_info, config_info)
elif self.task_type == "confirm_bind": elif self.task_type == "confirm_bind":
+3
View File
@@ -172,6 +172,9 @@ def create_planned_tasks(
batch_id = uuid.uuid4().hex[:12] batch_id = uuid.uuid4().hex[:12]
payload = payload or {} payload = payload or {}
accounts = db.query(HuyaAccount).filter(HuyaAccount.id.in_(account_ids)).all() accounts = db.query(HuyaAccount).filter(HuyaAccount.id.in_(account_ids)).all()
if task_type == "refresh_goods" and accounts:
# 商品列表是全局快照,使用一个可用 CK 刷新即可。
accounts = accounts[:1]
for account in accounts: for account in accounts:
db.add(HuyaTask( db.add(HuyaTask(
batch_id=batch_id, batch_id=batch_id,
+121 -15
View File
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { import {
Button, Card, Col, Form, Input, InputNumber, message, Modal, Row, Select, Space, Table, Tag, Tooltip, Typography, theme, Button, Card, Col, Form, Input, InputNumber, message, Modal, Row, Select, Space, Table, Tabs, Tag, Tooltip, Typography, theme,
} from 'antd'; } from 'antd';
import type { TableProps } from 'antd'; import type { TableProps } from 'antd';
import { import {
@@ -87,6 +87,34 @@ function resultProfileNick(result: Record<string, unknown> | null | undefined):
return typeof nick === 'string' ? nick : ''; return typeof nick === 'string' ? nick : '';
} }
function goodsRawString(item: HuyaGoodsItem, key: string): string {
const value = item.raw?.[key];
if (typeof value === 'string') return value;
if (typeof value === 'number') return String(value);
return '';
}
function goodsRawNumber(item: HuyaGoodsItem, key: string): number {
const value = item.raw?.[key];
if (typeof value === 'number') return value;
if (typeof value === 'string' && value.trim()) return Number(value) || 0;
return 0;
}
function goodsCategoryKey(item: HuyaGoodsItem): string {
return goodsRawString(item, 'category_id') || goodsRawString(item, 'category_name') || 'uncategorized';
}
function goodsCategoryLabel(item: HuyaGoodsItem): string {
return goodsRawString(item, 'category_name') || goodsRawString(item, 'category_id') || '未分类';
}
function formatRemainText(value: string): string {
const text = String(value || '').trim();
if (!text || text.endsWith('%')) return text;
return /^-?\d+(\.\d+)?$/.test(text) ? `${text}%` : text;
}
function hasMiniQrcode(task: HuyaTaskItem): boolean { function hasMiniQrcode(task: HuyaTaskItem): boolean {
return task.task_type === 'get_bind_qr' && Boolean(resultText(task.result, 'mini_qrcode_image')); return task.task_type === 'get_bind_qr' && Boolean(resultText(task.result, 'mini_qrcode_image'));
} }
@@ -101,6 +129,7 @@ export default function HuyaTasksPage() {
const [selectedIds, setSelectedIds] = useState<number[]>([]); const [selectedIds, setSelectedIds] = useState<number[]>([]);
const [selectedTaskType, setSelectedTaskType] = useState('query_points'); const [selectedTaskType, setSelectedTaskType] = useState('query_points');
const [selectedGoodsId, setSelectedGoodsId] = useState<string>(''); const [selectedGoodsId, setSelectedGoodsId] = useState<string>('');
const [selectedGoodsCategory, setSelectedGoodsCategory] = useState('');
const [rechargeCount, setRechargeCount] = useState(1); const [rechargeCount, setRechargeCount] = useState(1);
const [concurrency, setConcurrency] = useState(() => { const [concurrency, setConcurrency] = useState(() => {
const v = localStorage.getItem('huya_task_concurrency'); const v = localStorage.getItem('huya_task_concurrency');
@@ -200,16 +229,58 @@ export default function HuyaTasksPage() {
return accounts.map((account) => ({ value: account.id, label: accountLabel(account) })); return accounts.map((account) => ({ value: account.id, label: accountLabel(account) }));
}, [accounts]); }, [accounts]);
const sortedGoods = useMemo(() => {
return [...goods].sort((a, b) => (
goodsRawNumber(a, 'category_sort') - goodsRawNumber(b, 'category_sort')
|| goodsRawNumber(a, 'raw_order') - goodsRawNumber(b, 'raw_order')
|| a.id - b.id
));
}, [goods]);
const goodsOptions = useMemo(() => { const goodsOptions = useMemo(() => {
return goods.map((item) => ({ return sortedGoods.map((item) => ({
value: item.product_id, value: item.product_id,
label: `${item.name || item.product_id}${item.price ? ` / ${item.price}积分` : ''}`, label: `${item.name || item.product_id}${item.price ? ` / ${item.price}积分` : ''}`,
})); }));
}, [goods]); }, [sortedGoods]);
const selectedGoods = useMemo(() => { const selectedGoods = useMemo(() => {
return goods.find((item) => item.product_id === selectedGoodsId) || null; return sortedGoods.find((item) => item.product_id === selectedGoodsId) || null;
}, [goods, selectedGoodsId]); }, [sortedGoods, selectedGoodsId]);
const goodsCategories = useMemo(() => {
const map = new Map<string, { key: string; label: string; sort: number; count: number }>();
sortedGoods.forEach((item) => {
const key = goodsCategoryKey(item);
const current = map.get(key);
if (current) {
current.count += 1;
return;
}
map.set(key, {
key,
label: goodsCategoryLabel(item),
sort: goodsRawNumber(item, 'category_sort'),
count: 1,
});
});
return Array.from(map.values()).sort((a, b) => a.sort - b.sort || a.label.localeCompare(b.label));
}, [sortedGoods]);
useEffect(() => {
if (goodsCategories.length === 0) {
if (selectedGoodsCategory) setSelectedGoodsCategory('');
return;
}
if (!goodsCategories.some((item) => item.key === selectedGoodsCategory)) {
setSelectedGoodsCategory(goodsCategories[0].key);
}
}, [goodsCategories, selectedGoodsCategory]);
const filteredGoods = useMemo(() => {
if (!selectedGoodsCategory) return sortedGoods;
return sortedGoods.filter((item) => goodsCategoryKey(item) === selectedGoodsCategory);
}, [sortedGoods, selectedGoodsCategory]);
const createPayload = (taskType: string) => { const createPayload = (taskType: string) => {
if (taskType !== 'recharge_points') return {}; if (taskType !== 'recharge_points') return {};
@@ -238,13 +309,22 @@ export default function HuyaTasksPage() {
concurrency, concurrency,
payload: createPayload(taskType), payload: createPayload(taskType),
}); });
const finishTask = () => {
setBatchId(null);
setStarting(false);
if (taskType === 'refresh_goods') {
void loadAll();
} else {
void loadTasks();
}
};
setBatchId(result.batch_id); setBatchId(result.batch_id);
message.success(`已创建 ${taskTypes[taskType] || taskType},共 ${result.count} 个账号`); message.success(`已创建 ${taskTypes[taskType] || taskType},共 ${result.count} 个账号`);
await loadTasks(); await loadTasks();
connectLogs(`/api/huya/ws/${result.batch_id}`, { connectLogs(`/api/huya/ws/${result.batch_id}`, {
onClose: () => { setBatchId(null); setStarting(false); loadTasks(); }, onClose: finishTask,
onResult: () => { setBatchId(null); setStarting(false); loadTasks(); }, onResult: finishTask,
onError: () => { setBatchId(null); setStarting(false); loadTasks(); }, onError: finishTask,
}); });
} catch (e: unknown) { } catch (e: unknown) {
message.error(getErrorMessage(e)); message.error(getErrorMessage(e));
@@ -315,6 +395,11 @@ export default function HuyaTasksPage() {
if (value?.is_bound === false) return <Tag></Tag>; if (value?.is_bound === false) return <Tag></Tag>;
return <Text type="secondary">-</Text>; return <Text type="secondary">-</Text>;
} }
if (record.task_type === 'refresh_goods') {
const goodsCount = value?.goods_count;
if (typeof goodsCount === 'number') return <Tag color="green"> {goodsCount} </Tag>;
return <Text type="secondary">-</Text>;
}
const availableScore = value?.available_score; const availableScore = value?.available_score;
if (typeof availableScore === 'number') { if (typeof availableScore === 'number') {
@@ -365,6 +450,14 @@ export default function HuyaTasksPage() {
const goodsColumns: TableProps<HuyaGoodsItem>['columns'] = [ const goodsColumns: TableProps<HuyaGoodsItem>['columns'] = [
{ title: '商品ID', dataIndex: 'product_id', width: 120, ellipsis: true }, { title: '商品ID', dataIndex: 'product_id', width: 120, ellipsis: true },
{ title: '名称', dataIndex: 'name', ellipsis: true }, { title: '名称', dataIndex: 'name', ellipsis: true },
{
title: '分类',
width: 110,
render: (_: unknown, record) => {
const label = goodsCategoryLabel(record);
return label ? <Tag>{label}</Tag> : <Text type="secondary">-</Text>;
},
},
{ {
title: '价格', title: '价格',
dataIndex: 'price', dataIndex: 'price',
@@ -376,7 +469,7 @@ export default function HuyaTasksPage() {
title: '库存', title: '库存',
dataIndex: 'remain_text', dataIndex: 'remain_text',
width: 100, width: 100,
render: (value: string) => value || <Text type="secondary">-</Text>, render: (value: string) => formatRemainText(value) || <Text type="secondary">-</Text>,
}, },
{ {
title: '更新时间', title: '更新时间',
@@ -398,7 +491,7 @@ export default function HuyaTasksPage() {
</Space> </Space>
</div> </div>
<div style={{ flex: 1, minHeight: 0, overflow: 'auto', paddingRight: 2 }}> <div style={{ flex: 1, minHeight: 0, overflowY: 'auto', overflowX: 'hidden', paddingRight: 2 }}>
<Row gutter={12}> <Row gutter={12}>
<Col xs={24} xl={10}> <Col xs={24} xl={10}>
<Card <Card
@@ -468,14 +561,27 @@ export default function HuyaTasksPage() {
style={{ width: 120 }} style={{ width: 120 }}
/> />
</Space> </Space>
{goodsCategories.length > 0 && (
<Tabs
size="small"
activeKey={selectedGoodsCategory}
onChange={setSelectedGoodsCategory}
items={
goodsCategories.map((item) => ({
key: item.key,
label: `${item.label} (${item.count})`,
}))
}
/>
)}
<Table <Table
columns={goodsColumns} columns={goodsColumns}
dataSource={goods} dataSource={filteredGoods}
rowKey="id" rowKey="id"
size="small" size="small"
pagination={false} pagination={false}
scroll={{ x: 620, y: 220 }} scroll={{ x: 760, y: 220 }}
locale={{ emptyText: '暂无商品快照,后续接入刷新商品列表后写入' }} locale={{ emptyText: '暂无商品快照,请先刷新商品列表' }}
/> />
</Card> </Card>
</Col> </Col>
@@ -534,7 +640,7 @@ export default function HuyaTasksPage() {
</div> </div>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}> <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
{QUICK_ACTIONS.map((item) => ( {QUICK_ACTIONS.map((item) => (
<Tooltip key={item.key} title={item.key === 'refresh_goods' ? '当前阶段创建计划任务,真实拉取逻辑后续接入' : undefined}> <Tooltip key={item.key} title={item.key === 'refresh_goods' ? '使用一个选中的 CK 刷新当前 SID 商品快照' : undefined}>
<Button <Button
icon={item.icon} icon={item.icon}
size="small" size="small"
@@ -547,7 +653,7 @@ export default function HuyaTasksPage() {
))} ))}
</div> </div>
<Text type="secondary" style={{ fontSize: 12 }}> <Text type="secondary" style={{ fontSize: 12 }}>
</Text> </Text>
</div> </div>
</Card> </Card>