新增: 兑换复验功能
- 新增 exchangeList 接口查询斗鱼真实兑换记录
- 前端添加「兑换复验」标签页
- 点击「🔍 复验」按钮可查询所有账号的兑换记录
- 复验结果与软件内部状态对比,用于验证抢购结果
- 移除 frontend/dist 的 git 跟踪
This commit is contained in:
+2
-1
@@ -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/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
@@ -25,6 +25,7 @@ interface Window {
|
||||
GetVersion: () => Promise<string>
|
||||
GetSettings: () => Promise<any>
|
||||
SaveSettings: (cfg: any) => Promise<any>
|
||||
VerifyExchange: (accountName: string) => Promise<any>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user