新增: 兑换复验功能

- 新增 exchangeList 接口查询斗鱼真实兑换记录
- 前端添加「兑换复验」标签页
- 点击「🔍 复验」按钮可查询所有账号的兑换记录
- 复验结果与软件内部状态对比,用于验证抢购结果
- 移除 frontend/dist 的 git 跟踪
This commit is contained in:
yml2213
2026-06-26 11:10:33 +08:00
parent b8ddc63ad3
commit 7cb62c0078
7 changed files with 301 additions and 4 deletions
+2 -1
View File
@@ -5,7 +5,7 @@ logs/
success_images/
build/
config.json
frontend/dist
frontend/node_modules
# Wails 自动生成的前端绑定
@@ -23,3 +23,4 @@ ck 对比.md
*.exe
*.app
.DS_Store
frontend/dist/
+30
View File
@@ -210,6 +210,36 @@ func (a *App) QueryScore(accountName string) map[string]any {
return map[string]any{"score": score}
}
// VerifyExchange 查询兑换记录,用于复验抢购结果。
func (a *App) VerifyExchange(accountName string) map[string]any {
if accountName == "" {
return map[string]any{"success": false, "error": "缺少账号名"}
}
cookie, ok := a.scheduler.AccountCookieByName(accountName)
if !ok || cookie == "" {
return map[string]any{"success": false, "error": fmt.Sprintf("账号不存在或 Cookie 为空: %s", accountName)}
}
records, err := a.client.ExchangeList(cookie)
if err != nil {
return map[string]any{"success": false, "error": "查询失败: " + err.Error()}
}
// 过滤当前活动的记录
var matched []client.ExchangeRecord
for _, r := range records {
if r.CommodityID != "" {
matched = append(matched, r)
}
}
return map[string]any{
"success": true,
"records": matched,
"count": len(matched),
}
}
// GetVersion 返回应用版本号。
func (a *App) GetVersion() string {
return constants.Version
+187 -3
View File
@@ -1,12 +1,15 @@
<script setup lang="ts">
import { ref, computed, watch, nextTick } from 'vue'
import { useLogs } from '@/composables/useLogs'
import { useAccounts } from '@/composables/useAccounts'
import { useAccounts, useVerifyExchange } from '@/composables/useAccounts'
import { useToast } from '@/composables/useToast'
const { logs, results, clearLogs } = useLogs()
const { accounts, selectedIndices, statusMap } = useAccounts()
const { verifyResults, verifying, verifyAll, clearResults } = useVerifyExchange()
const { showToast } = useToast()
const activeTab = ref<'log' | 'result' | 'stats'>('log')
const activeTab = ref<'log' | 'result' | 'stats' | 'verify'>('log')
const logBodyRef = ref<HTMLElement | null>(null)
// 自动滚动到底部
@@ -46,6 +49,31 @@ const stats = computed(() => {
// 结果列表
const resultEntries = computed(() => Object.entries(results.value))
// 复验结果列表
const verifyEntries = computed(() => {
return Object.entries(verifyResults.value).map(([name, result]) => ({
name,
...result
}))
})
// 复验统计
const verifyStats = computed(() => {
const entries = verifyEntries.value
const verified = entries.filter(e => e.success && e.count > 0).length
const noRecord = entries.filter(e => e.success && e.count === 0).length
const failed = entries.filter(e => !e.success).length
return { verified, noRecord, failed, total: entries.length }
})
// 执行复验
const handleVerify = async () => {
showToast('复验开始', '正在查询斗鱼兑换记录...', 'info')
await verifyAll()
activeTab.value = 'verify'
showToast('复验完成', `已查询 ${verifyStats.value.total} 个账号`, 'success')
}
</script>
<template>
@@ -73,8 +101,24 @@ const resultEntries = computed(() => Object.entries(results.value))
>
统计
</button>
<button
class="log-tab"
:class="{ active: activeTab === 'verify' }"
@click="activeTab = 'verify'"
>
兑换复验
</button>
</div>
<div class="log-actions">
<button
class="btn btn-compact btn-verify"
:disabled="verifying"
@click="handleVerify"
>
{{ verifying ? '查询中...' : '🔍 复验' }}
</button>
<button class="btn btn-compact" @click="clearLogs">清空</button>
</div>
<button class="btn btn-compact" @click="clearLogs">清空</button>
</div>
<!-- 实时日志 -->
@@ -129,6 +173,47 @@ const resultEntries = computed(() => Object.entries(results.value))
<div class="log-result log-i">待处理账号: {{ stats.pending }}</div>
<div class="log-result">已选账号: {{ stats.selected }} / 总账号: {{ stats.total }}</div>
</div>
<!-- 兑换复验 -->
<div
v-show="activeTab === 'verify'"
class="log-panel"
>
<div v-if="verifyEntries.length === 0" class="result-empty">
点击「🔍 复验」按钮查询斗鱼真实兑换记录
</div>
<template v-else>
<div class="verify-summary">
<span class="log-s">✅ 已兑换: {{ verifyStats.verified }}</span>
<span class="log-w">⚠️ 无记录: {{ verifyStats.noRecord }}</span>
<span class="log-e">❌ 查询失败: {{ verifyStats.failed }}</span>
</div>
<div class="verify-list">
<div
v-for="entry in verifyEntries"
:key="entry.name"
class="verify-item"
:class="{
'verify-success': entry.success && entry.count > 0,
'verify-empty': entry.success && entry.count === 0,
'verify-error': !entry.success
}"
>
<div class="verify-account">{{ entry.name }}</div>
<template v-if="entry.success">
<div v-if="entry.count > 0" class="verify-records">
<div v-for="(record, idx) in entry.records" :key="idx" class="verify-record">
<span class="record-name">{{ record.commodityName }}</span>
<span class="record-role">{{ record.showName }}</span>
</div>
</div>
<div v-else class="verify-no-record">暂无兑换记录</div>
</template>
<div v-else class="verify-error-msg">{{ entry.error }}</div>
</div>
</div>
</template>
</div>
</div>
</template>
@@ -249,6 +334,105 @@ const resultEntries = computed(() => Object.entries(results.value))
height: 100%;
}
/* 复验相关样式 */
.log-actions {
display: flex;
gap: 6px;
align-items: center;
}
.btn-verify {
background: var(--accent-soft);
color: var(--accent);
border-color: var(--accent-border);
}
.btn-verify:hover {
background: var(--accent);
color: #fff;
}
.btn-verify:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.verify-summary {
display: flex;
gap: 16px;
padding: 8px 12px;
margin-bottom: 12px;
background: var(--surface);
border-radius: var(--radius-sm);
border: 1px solid var(--line);
font-weight: 500;
}
.verify-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.verify-item {
padding: 10px 12px;
background: var(--surface);
border-radius: var(--radius-sm);
border: 1px solid var(--line);
}
.verify-success {
border-color: var(--success);
background: var(--success-soft);
}
.verify-empty {
border-color: var(--warning);
background: var(--warning-soft);
}
.verify-error {
border-color: var(--danger);
background: var(--danger-soft);
}
.verify-account {
font-weight: 600;
color: var(--text-strong);
margin-bottom: 6px;
}
.verify-records {
display: flex;
flex-direction: column;
gap: 4px;
}
.verify-record {
display: flex;
gap: 12px;
font-size: 12px;
}
.record-name {
color: var(--text);
font-weight: 500;
}
.record-role {
color: var(--muted);
}
.verify-no-record {
color: var(--muted);
font-size: 12px;
}
.verify-error-msg {
color: var(--danger);
font-size: 12px;
}
@media (max-width: 1180px) {
.log-wrap {
height: 360px;
+47
View File
@@ -153,3 +153,50 @@ export function useAccounts() {
buildAccountProducts
}
}
export function useVerifyExchange() {
const { accounts } = useAccounts()
const verifyResults = ref<Record<string, any>>({})
const verifying = ref(false)
const verifyAccount = async (accountName: string) => {
try {
const result = await App.VerifyExchange(accountName)
verifyResults.value[accountName] = result
return result
} catch (e: any) {
verifyResults.value[accountName] = { success: false, error: e.message }
return { success: false, error: e.message }
}
}
const verifyAll = async () => {
verifying.value = true
const results: Record<string, any> = {}
for (const account of accounts.value) {
try {
const result = await App.VerifyExchange(account.name)
results[account.name] = result
} catch (e: any) {
results[account.name] = { success: false, error: e.message }
}
}
verifyResults.value = results
verifying.value = false
return results
}
const clearResults = () => {
verifyResults.value = {}
}
return {
verifyResults,
verifying,
verifyAccount,
verifyAll,
clearResults
}
}
+1
View File
@@ -25,6 +25,7 @@ interface Window {
GetVersion: () => Promise<string>
GetSettings: () => Promise<any>
SaveSettings: (cfg: any) => Promise<any>
VerifyExchange: (accountName: string) => Promise<any>
}
}
}
+33
View File
@@ -406,6 +406,39 @@ func (c *DouyuClient) RoleName(cookie string) (string, error) {
return result.Data.GameRole.RoleName, nil
}
// ExchangeRecord 兑换记录。
type ExchangeRecord struct {
CommodityID string `json:"commodityId"`
CommodityName string `json:"commodityName"`
NickName string `json:"nickName"`
ShowName string `json:"showName"`
ExchangeNum int `json:"exchangeNum"`
Time string `json:"time"`
Score string `json:"score"`
}
// ExchangeListResponse 兑换列表响应。
type ExchangeListResponse struct {
Error int `json:"error"`
Msg string `json:"msg"`
Data struct {
List []ExchangeRecord `json:"list"`
Count int `json:"count"`
} `json:"data"`
}
// ExchangeList 查询兑换记录列表。
func (c *DouyuClient) ExchangeList(cookie string) ([]ExchangeRecord, error) {
var result ExchangeListResponse
if err := c.getJSON(constants.ExchangeListURL, cookie, "兑换记录", &result); err != nil {
return nil, err
}
if result.Error != 0 {
return nil, fmt.Errorf("兑换记录查询失败: %s", result.Msg)
}
return result.Data.List, nil
}
func (c *DouyuClient) getJSON(url, cookie, label string, out any) error {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
+1
View File
@@ -18,6 +18,7 @@ const (
BatchLimitURL = BaseURL + "/wgapi/ordnc/activity/peace/batchExchangeLimit"
PreExchangeURL = BaseURL + "/wgapi/ordnc/activity/peace/preExchange"
ExchangeURL = BaseURL + "/wgapi/ordnc/activity/peace/exchange"
ExchangeListURL = BaseURL + "/wgapi/ordnc/activity/peace/exchangeList?manualID=" + ManualID + "&limit=50&offset=0"
BindInfoURL = BaseURL + "/japi/carnivalApi/v2/tencent/bindInfo?actAlias=" + ActAlias
ScoreURL = BaseURL + "/japi/oms/web/peace/credit/balance"
UserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36"