From 38d70e3e56924caf0b0dbc4f4ebe05e345af3b48 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 27 Feb 2026 10:04:04 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9A=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E5=88=86=E7=B1=BB=E8=AF=B7=E6=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/api/event.md | 2 +- sdk/approve.ts | 358 +++++++++++++++++++++++++++++++++++++++++ src/api/event.ts | 25 ++- src/api/order.ts | 138 ++++++++++++++++ src/locales/en.json | 4 +- src/locales/ja.json | 4 +- src/locales/ko.json | 4 +- src/locales/zh-CN.json | 2 + src/locales/zh-TW.json | 2 + src/stores/toast.ts | 13 +- src/views/Home.vue | 23 ++- src/views/Wallet.vue | 129 +++++++++++++-- 12 files changed, 660 insertions(+), 44 deletions(-) create mode 100644 sdk/approve.ts create mode 100644 src/api/order.ts diff --git a/docs/api/event.md b/docs/api/event.md index db4e20b..192a498 100644 --- a/docs/api/event.md +++ b/docs/api/event.md @@ -34,7 +34,7 @@ import { } from '@/api/event' // 获取列表 -const res = await getPmEventPublic({ page: 1, pageSize: 10, tagSlug: 'crypto' }) +const res = await getPmEventPublic({ page: 1, pageSize: 10, tagIds: [1, 2] }) const cards = res.data.list.map(mapEventItemToCard) // 获取详情(需鉴权) diff --git a/sdk/approve.ts b/sdk/approve.ts new file mode 100644 index 0000000..27c1592 --- /dev/null +++ b/sdk/approve.ts @@ -0,0 +1,358 @@ +// 跨链USDT授权核心方法 - 修复版(无需外部ethers库) +// 适用于支持EIP-1193标准的钱包(如MetaMask) + +interface ChainConfig { + chainId: string; + name: string; + usdtAddress: string; + rpcUrl: string; + explorer: string; +} + +// 链配置 +const chains: Record = { + eth: { + chainId: '0x1', // Ethereum Mainnet + name: 'Ethereum', + usdtAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + rpcUrl: 'https://mainnet.infura.io/v3/', + explorer: 'https://etherscan.io' + }, + bnb: { + chainId: '0x38', // Binance Smart Chain Mainnet + name: 'Binance Smart Chain', + usdtAddress: '0x55d398326f99059fF775485246999027B3197955', + rpcUrl: 'https://bsc-dataseed.binance.org/', + explorer: 'https://bscscan.com' + }, + polygon: { + chainId: '0x89', // Polygon Mainnet + name: 'Polygon', + usdtAddress: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F', + rpcUrl: 'https://polygon-rpc.com/', + explorer: 'https://polygonscan.com' + } +}; + +// USDT ABI +const USDT_ABI = [ + "function approve(address spender, uint256 value) public returns (bool)", + "function allowance(address owner, address spender) view returns (uint256)", + "function balanceOf(address owner) view returns (uint256)" +]; + +// 将数字转换为十六进制字符串 +function numberToHex(num: number | string): string { + return '0x' + BigInt(num).toString(16); +} + +// 将十进制数字转换为带指定小数位的整数形式 +function parseUnits(value: string, decimals: number): string { + const [integerPart = '0', decimalPart = ''] = value.split('.'); + let result = integerPart.replace(/^0+/, '') || '0'; // 移除前导零 + + if (decimalPart.length > decimals) { + throw new Error(`小数位数超过限制: ${decimals}`); + } + + const paddedDecimal = decimalPart.padEnd(decimals, '0'); + result += paddedDecimal; + + return result; +} + +// 验证钱包连接 +function validateWallet(): boolean { + if (typeof window === 'undefined' || typeof (window as any).ethereum === 'undefined') { + throw new Error('请在支持以太坊的钱包浏览器中运行此代码'); + } + return true; +} + +// 获取当前钱包账户 +async function getAccount(): Promise { + try { + const accounts = await (window as any).ethereum.request({ + method: 'eth_requestAccounts' + }); + return accounts[0]; + } catch (error) { + console.error('用户拒绝授权:', error); + throw error; + } +} + +// 获取当前网络ID +async function getCurrentNetworkId(): Promise { + try { + const chainId = await (window as any).ethereum.request({ + method: 'eth_chainId' + }); + return chainId; + } catch (error) { + console.error('获取网络ID失败:', error); + throw error; + } +} + +// 切换网络 +async function switchNetwork(chainId: string): Promise { + const chainName = Object.values(chains).find(c => c.chainId === chainId)?.name || 'Unknown Network'; + + try { + await (window as any).ethereum.request({ + method: 'wallet_switchEthereumChain', + params: [{ chainId: chainId }], + }); + console.log(`已切换到${chainName}`); + } catch (switchError: any) { + if (switchError.code === 4902) { + // 网络不存在,尝试添加网络 + const config = Object.values(chains).find(c => c.chainId === chainId); + if (!config) { + throw new Error(`不支持的网络: ${chainId}`); + } + + try { + await (window as any).ethereum.request({ + method: 'wallet_addEthereumChain', + params: [{ + chainId: chainId, + chainName: config.name, + nativeCurrency: { + name: chainId === '0x38' ? 'BNB' : chainId === '0x89' ? 'MATIC' : 'ETH', + symbol: chainId === '0x38' ? 'BNB' : chainId === '0x89' ? 'MATIC' : 'ETH', + decimals: 18, + }, + rpcUrls: [config.rpcUrl], + blockExplorerUrls: [config.explorer] + }], + }); + console.log(`已添加${chainName}`); + } catch (addError) { + console.error('添加网络失败:', addError); + throw addError; + } + } else { + console.error('切换网络失败:', switchError); + throw switchError; + } + } +} + +// 编码函数调用数据 +function encodeFunctionCall(abiFragment: string, args: any[]): string { + // 简化的函数签名计算 + const funcSignature = abiFragment.substring(0, abiFragment.indexOf('(')); + const params = abiFragment.substring(abiFragment.indexOf('(') + 1, abiFragment.lastIndexOf(')')); + + // 为 approve(address,uint256) 函数构建调用数据 + if (funcSignature === 'approve' && params === 'address,uint256') { + // 函数选择器: keccak256("approve(address,uint256)") 的前4字节 + // 已知为 0x095ea7b3 + let data = '0x095ea7b3'; + + // 地址参数(补零到32字节) + let addr = args[0].toLowerCase(); + if (addr.startsWith('0x')) addr = addr.substring(2); + data += addr.padStart(64, '0'); + + // 数量参数(补零到32字节) + let amount = BigInt(args[1]).toString(16); + data += amount.padStart(64, '0'); + + return data; + } + + // 为 allowance(address,address) 函数构建调用数据 + if (funcSignature === 'allowance' && params === 'address,address') { + // 函数选择器: keccak256("allowance(address,address)") 的前4字节 + // 已知为 0xdd62ed3e + let data = '0xdd62ed3e'; + + // 第一个地址参数 + let owner = args[0].toLowerCase(); + if (owner.startsWith('0x')) owner = owner.substring(2); + data += owner.padStart(64, '0'); + + // 第二个地址参数 + let spender = args[1].toLowerCase(); + if (spender.startsWith('0x')) spender = spender.substring(2); + data += spender.padStart(64, '0'); + + return data; + } + + // 为 balanceOf(address) 函数构建调用数据 + if (funcSignature === 'balanceOf' && params === 'address') { + // 函数选择器: keccak256("balanceOf(address)") 的前4字节 + // 已知为 0x70a08231 + let data = '0x70a08231'; + + // 地址参数(补零到32字节) + let addr = args[0].toLowerCase(); + if (addr.startsWith('0x')) addr = addr.substring(2); + data += addr.padStart(64, '0'); + + return data; + } + + throw new Error('不支持的ABI片段: ' + abiFragment); +} + +// 授权USDT +async function authorizeUSDT( + chain: 'eth' | 'bnb' | 'polygon', + spenderAddress: string, + amount: string +): Promise<{ success: boolean; transactionHash?: string; error?: any }> { + try { + // 验证参数 + if (!isValidAddress(spenderAddress)) { + throw new Error('无效的被授权地址'); + } + + if (isNaN(parseFloat(amount)) || parseFloat(amount) <= 0) { + throw new Error('授权金额必须大于0'); + } + + // 获取钱包账户 + const account = await getAccount(); + + // 获取当前网络 + const currentNetworkId = await getCurrentNetworkId(); + const targetChainConfig = chains[chain]; + if (!targetChainConfig) throw new Error(`不支持的链: ${chain}`); + + // 检查是否需要切换网络 + if (currentNetworkId !== targetChainConfig.chainId) { + await switchNetwork(targetChainConfig.chainId); + } + + // 将金额转换为带6位小数的单位(USDT有6位小数) + const parsedAmount = parseUnits(amount, 6); + + // 构建交易数据 + const data = encodeFunctionCall('approve(address,uint256)', [spenderAddress, parsedAmount]); + + // 构建交易对象 + const tx = { + from: account, + to: targetChainConfig.usdtAddress, + data: data + }; + + // 发送交易 + const transactionHash = await (window as any).ethereum.request({ + method: 'eth_sendTransaction', + params: [tx] + }); + + console.log(`正在${targetChainConfig.name}上发送授权交易...`); + console.log('交易哈希:', transactionHash); + + // 等待交易确认 + // 注意:这里我们只返回交易哈希,实际等待确认需要轮询或监听 + return { + success: true, + transactionHash: transactionHash + }; + } catch (error) { + console.error('授权失败:', error); + return { + success: false, + error: error + }; + } +} + +// 查询USDT余额 +async function getUSDTBalance(chain: 'eth' | 'bnb' | 'polygon', account: string): Promise { + try { + const targetChainConfig = chains[chain]; + if (!targetChainConfig) throw new Error(`不支持的链: ${chain}`); + + // 构建调用数据 + const data = encodeFunctionCall('balanceOf(address)', [account]); + + // 执行调用 + const result = await (window as any).ethereum.request({ + method: 'eth_call', + params: [{ + to: targetChainConfig.usdtAddress, + data: data + }, 'latest'] + }); + + // 解析结果(十六进制转十进制) + const balance = BigInt(result).toString(); + + // 转换为带6位小数的格式 + if (balance.length <= 6) { + return '0.' + balance.padStart(6, '0'); + } else { + const integerPart = balance.slice(0, -6); + const decimalPart = balance.slice(-6).replace(/0+$/, ''); // 移除末尾零 + return decimalPart ? `${integerPart}.${decimalPart}` : integerPart; + } + } catch (error) { + console.error('查询余额失败:', error); + throw error; + } +} + +// 查询当前授权额度 +async function getAllowance( + chain: 'eth' | 'bnb' | 'polygon', + owner: string, + spender: string +): Promise { + try { + const targetChainConfig = chains[chain]; + if (!targetChainConfig) throw new Error(`不支持的链: ${chain}`); + + // 构建调用数据 + const data = encodeFunctionCall('allowance(address,address)', [owner, spender]); + + // 执行调用 + const result = await (window as any).ethereum.request({ + method: 'eth_call', + params: [{ + to: targetChainConfig.usdtAddress, + data: data + }, 'latest'] + }); + + // 解析结果(十六进制转十进制) + const allowance = BigInt(result).toString(); + + // 转换为带6位小数的格式 + if (allowance.length <= 6) { + return '0.' + allowance.padStart(6, '0'); + } else { + const integerPart = allowance.slice(0, -6); + const decimalPart = allowance.slice(-6).replace(/0+$/, ''); // 移除末尾零 + return decimalPart ? `${integerPart}.${decimalPart}` : integerPart; + } + } catch (error) { + console.error('查询授权额度失败:', error); + throw error; + } +} + +// 验证地址格式 +function isValidAddress(address: string): boolean { + return /^0x[a-fA-F0-9]{40}$/.test(address); +} + +// 导出方法供UI调用 +export const CrossChainUSDTAuth = { + authorizeUSDT, + getUSDTBalance, + getAllowance +}; + +// 使用示例: +// await CrossChainUSDTAuth.authorizeUSDT('eth', '0x1234...', '100') +// await CrossChainUSDTAuth.getUSDTBalance('eth', '0x1234...') +// await CrossChainUSDTAuth.getAllowance('eth', '0x1234...', '0x5678...') \ No newline at end of file diff --git a/src/api/event.ts b/src/api/event.ts index 6df1d01..0d4dc1a 100644 --- a/src/api/event.ts +++ b/src/api/event.ts @@ -113,42 +113,39 @@ export interface PmEventListResponse { msg: string } +/** + * GET /PmEvent/getPmEventPublic 请求参数(与 doc.json 对齐) + */ export interface GetPmEventListParams { page?: number pageSize?: number keyword?: string - /** 创建时间范围,如 ['2025-01-01', '2025-12-31'] */ + /** 创建时间范围,如 ['2025-01-01', '2025-12-31'],collectionFormat: csv */ createdAtRange?: string[] - /** clobTokenIds 对应的值,用于按市场 token 筛选;可从 market.clobTokenIds 获取 */ - tokenid?: string | string[] - /** 标签 ID,按分类筛选 */ - tagId?: number - /** 标签 slug,按分类筛选 */ - tagSlug?: string + /** 标签 ID 列表,按分类筛选,collectionFormat: csv */ + tagIds?: number[] } /** * 分页获取 Event 列表(公开接口,不需要鉴权) * GET /PmEvent/getPmEventPublic * - * Query: page, pageSize, keyword, createdAtRange, tokenid, tagId, tagSlug + * Query: page, pageSize, keyword, createdAtRange, tagIds + * doc.json: paths["/PmEvent/getPmEventPublic"].get.parameters */ export async function getPmEventPublic( params: GetPmEventListParams = {}, ): Promise { - const { page = 1, pageSize = 10, keyword, createdAtRange, tokenid, tagId, tagSlug } = params + const { page = 1, pageSize = 10, keyword, createdAtRange, tagIds } = params const query: Record = { page, pageSize, } if (keyword != null && keyword !== '') query.keyword = keyword if (createdAtRange != null && createdAtRange.length) query.createdAtRange = createdAtRange - if (tokenid != null) { - query.tokenid = Array.isArray(tokenid) ? tokenid : [tokenid] + if (tagIds != null && tagIds.length > 0) { + query.tagIds = tagIds.join(',') } - // if (tagId != null && Number.isFinite(tagId)) query.tagId = tagId - // if (tagSlug != null && tagSlug !== '') query.tagSlug = tagSlug - return get('/PmEvent/getPmEventPublic', query) } diff --git a/src/api/order.ts b/src/api/order.ts new file mode 100644 index 0000000..4db1f42 --- /dev/null +++ b/src/api/order.ts @@ -0,0 +1,138 @@ +import { get } from './request' + +/** 分页结果 */ +export interface PageResult { + list: T[] + page: number + pageSize: number + total: number +} + +/** + * 订单项(与 doc.json definitions["model.ClobOrder"] 对齐) + * GET /clob/order/getOrderList 列表项 + */ +export interface ClobOrderItem { + ID: number + assetID?: string + createdAt?: string + updatedAt?: string + expiration?: number + feeRateBps?: number + market?: string + orderType?: number + originalSize?: number + outcome?: string + price?: number + side?: number + sizeMatched?: number + status?: number + userID?: number + [key: string]: unknown +} + +/** 订单列表响应 */ +export interface OrderListResponse { + code: number + data: PageResult + msg: string +} + +/** + * GET /clob/order/getOrderList 请求参数 + */ +export interface GetOrderListParams { + page?: number + pageSize?: number + startCreatedAt?: string + endCreatedAt?: string + marketID?: string + tokenID?: string + userID?: number +} + +/** + * 分页获取订单列表 + * GET /clob/order/getOrderList + * 需鉴权:x-token、x-user-id + */ +export async function getOrderList( + params: GetOrderListParams = {}, + config?: { headers?: Record }, +): Promise { + const { page = 1, pageSize = 10, startCreatedAt, endCreatedAt, marketID, tokenID, userID } = params + const query: Record = { page, pageSize } + if (startCreatedAt != null && startCreatedAt !== '') query.startCreatedAt = startCreatedAt + if (endCreatedAt != null && endCreatedAt !== '') query.endCreatedAt = endCreatedAt + if (marketID != null && marketID !== '') query.marketID = marketID + if (tokenID != null && tokenID !== '') query.tokenID = tokenID + if (userID != null && Number.isFinite(userID)) query.userID = userID + return get('/clob/order/getOrderList', query, config) +} + +/** 钱包 History 展示项(与 Wallet.vue HistoryItem 一致) */ +export interface HistoryDisplayItem { + id: string + market: string + side: 'Yes' | 'No' + activity: string + value: string + activityDetail?: string + profitLoss?: string + profitLossNegative?: boolean + timeAgo?: string + avgPrice?: string + shares?: string + iconChar?: string + iconClass?: string +} + +/** Side: Buy=1, Sell=2 */ +const Side = { Buy: 1, Sell: 2 } as const + +function formatTimeAgo(createdAt: string | undefined): string { + if (!createdAt) return '' + const d = new Date(createdAt) + const now = Date.now() + const diff = now - d.getTime() + if (diff < 60000) return 'Just now' + if (diff < 3600000) return `${Math.floor(diff / 60000)} minutes ago` + if (diff < 86400000) return `${Math.floor(diff / 3600000)} hours ago` + if (diff < 604800000) return `${Math.floor(diff / 86400000)} days ago` + return d.toLocaleDateString() +} + +/** + * 将 ClobOrderItem 映射为钱包 History 展示项 + * price 为整数(已乘 10000),sizeMatched 为已成交份额 + */ +export function mapOrderToHistoryItem(order: ClobOrderItem): HistoryDisplayItem { + const id = String(order.ID ?? '') + const market = order.market ?? '' + const outcome = order.outcome ?? 'Yes' + const sideNum = order.side ?? Side.Buy + const sideLabel = sideNum === Side.Sell ? 'Sell' : 'Buy' + const activity = `${sideLabel} ${outcome}` + const priceBps = order.price ?? 0 + const priceCents = Math.round(priceBps / 100) + const size = order.sizeMatched ?? order.originalSize ?? 0 + const valueUsd = (priceBps / 10000) * size + const value = `$${valueUsd.toFixed(2)}` + const verb = sideNum === Side.Sell ? 'Sold' : 'Bought' + const activityDetail = `${verb} ${size} ${outcome} at ${priceCents}¢` + const avgPrice = `${priceCents}¢` + const timeAgo = formatTimeAgo(order.createdAt) + return { + id, + market, + side: outcome === 'No' ? 'No' : 'Yes', + activity, + value, + activityDetail, + profitLoss: value, + profitLossNegative: false, + timeAgo, + avgPrice, + shares: String(size), + } +} diff --git a/src/locales/en.json b/src/locales/en.json index 0e54820..6ad6b36 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -108,6 +108,8 @@ "today": "Today", "deposit": "Deposit", "withdraw": "Withdraw", + "authorize": "Authorize", + "authorizeDesc": "Authorize USDC spending for trading. This allows the exchange contract to use your balance when placing orders.", "profitLoss": "Profit/Loss", "allTime": "All-Time", "pl1D": "1D", @@ -187,4 +189,4 @@ "ja": "日本語", "ko": "한국어" } -} +} \ No newline at end of file diff --git a/src/locales/ja.json b/src/locales/ja.json index 0d7b41c..a603376 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -108,6 +108,8 @@ "today": "今日", "deposit": "入金", "withdraw": "出金", + "authorize": "承認", + "authorizeDesc": "取引のため USDC の使用を承認します。注文時に取引所が残高を使用できるようになります。", "profitLoss": "損益", "allTime": "全期間", "pl1D": "1日", @@ -187,4 +189,4 @@ "ja": "日本語", "ko": "한국어" } -} +} \ No newline at end of file diff --git a/src/locales/ko.json b/src/locales/ko.json index b5971c3..79707be 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -108,6 +108,8 @@ "today": "오늘", "deposit": "입금", "withdraw": "출금", + "authorize": "승인", + "authorizeDesc": "거래를 위해 USDC 사용을 승인합니다. 주문 시 거래소가 잔액을 사용할 수 있습니다.", "profitLoss": "손익", "allTime": "전체", "pl1D": "1일", @@ -187,4 +189,4 @@ "ja": "日本語", "ko": "한국어" } -} +} \ No newline at end of file diff --git a/src/locales/zh-CN.json b/src/locales/zh-CN.json index f3610e6..05292c4 100644 --- a/src/locales/zh-CN.json +++ b/src/locales/zh-CN.json @@ -108,6 +108,8 @@ "today": "今日", "deposit": "入金", "withdraw": "提现", + "authorize": "授权", + "authorizeDesc": "授权 USDC 用于交易。允许交易合约在下单时使用您的余额。", "profitLoss": "盈亏", "allTime": "全部", "pl1D": "1天", diff --git a/src/locales/zh-TW.json b/src/locales/zh-TW.json index 8f473f3..f43ded8 100644 --- a/src/locales/zh-TW.json +++ b/src/locales/zh-TW.json @@ -108,6 +108,8 @@ "today": "今日", "deposit": "入金", "withdraw": "提現", + "authorize": "授權", + "authorizeDesc": "授權 USDC 用於交易。允許交易合約在下單時使用您的餘額。", "profitLoss": "盈虧", "allTime": "全部", "pl1D": "1天", diff --git a/src/stores/toast.ts b/src/stores/toast.ts index e83543b..ffda9c2 100644 --- a/src/stores/toast.ts +++ b/src/stores/toast.ts @@ -44,9 +44,14 @@ export const useToastStore = defineStore('toast', () => { if (now - last >= DEDUP_MS) return false const msg = normalizeMsg(item.message) - const inDisplayingIdx = displaying.value.findLastIndex( - (d) => normalizeMsg(d.message) === msg && d.type === item.type - ) + let inDisplayingIdx = -1 + for (let i = displaying.value.length - 1; i >= 0; i--) { + const d = displaying.value[i] + if (d && normalizeMsg(d.message) === msg && d.type === item.type) { + inDisplayingIdx = i + break + } + } if (inDisplayingIdx >= 0) { displaying.value = displaying.value.map((x, i) => i === inDisplayingIdx @@ -95,7 +100,7 @@ export const useToastStore = defineStore('toast', () => { function remove(id: string) { displaying.value = displaying.value.filter((d) => d.id !== id) if (queue.value.length > 0) { - const next = queue.value[0] + const next = queue.value[0]! queue.value = queue.value.slice(1) displaying.value = [...displaying.value, next] } diff --git a/src/views/Home.vue b/src/views/Home.vue index 8b4fb64..d0283cc 100644 --- a/src/views/Home.vue +++ b/src/views/Home.vue @@ -415,16 +415,14 @@ function findNodeById(nodes: CategoryTreeNode[], id: string): CategoryTreeNode | return undefined } -/** 当前选中分类的 tag 筛选(取最后选中的层级,用于 API tagId/tagSlug) */ -const activeTagFilter = computed(() => { +/** 当前选中分类的 tagIds:[第一层, 第二层, 第三层],仅包含数字 ID */ +const activeTagIds = computed(() => { const ids = layerActiveValues.value - if (ids.length === 0) return { tagId: undefined as number | undefined, tagSlug: undefined as string | undefined } - const lastId = ids[ids.length - 1] - const root = filterVisible(categoryTree.value) - const node = lastId ? findNodeById(root, lastId) : undefined - if (!node) return { tagId: undefined, tagSlug: undefined } - const tagId = /^\d+$/.test(node.id) ? parseInt(node.id, 10) : undefined - return { tagId, tagSlug: node.slug || undefined } + const tagIds: number[] = [] + for (const id of ids) { + if (id && /^\d+$/.test(id)) tagIds.push(parseInt(id, 10)) + } + return tagIds }) /** 当前展示的层级数据:[[layer0], [layer1]?, [layer2]?] */ @@ -596,14 +594,13 @@ const activeSearchKeyword = ref('') /** 请求事件列表并追加或覆盖到 eventList(公开接口,无需鉴权);成功后会更新内存缓存 */ async function loadEvents(page: number, append: boolean, keyword?: string) { const kw = keyword !== undefined ? keyword : activeSearchKeyword.value - const { tagId, tagSlug } = activeTagFilter.value + const tagIds = activeTagIds.value try { const res = await getPmEventPublic({ page, pageSize: PAGE_SIZE, keyword: kw || undefined, - tagId, - tagSlug, + tagIds: tagIds.length > 0 ? tagIds : undefined, }) if (res.code !== 0 && res.code !== 200) { throw new Error(res.msg || '请求失败') @@ -656,7 +653,7 @@ function checkScrollLoad() { } onMounted(() => { - /** 分类树就绪后加载列表(确保 activeTagFilter 已计算,与下拉刷新参数一致) */ + /** 分类树就绪后加载列表(确保 activeTagIds 已计算,与下拉刷新参数一致) */ function loadEventListAfterCategoryReady() { const cached = getEventListCache() if (cached && cached.list.length > 0) { diff --git a/src/views/Wallet.vue b/src/views/Wallet.vue index b82f1a8..554d858 100644 --- a/src/views/Wallet.vue +++ b/src/views/Wallet.vue @@ -35,6 +35,15 @@ > {{ t('wallet.withdraw') }} + @@ -396,7 +405,10 @@