核对到的积分余额同步刷新主界面积分列,发布0.3.6
后端在"导入账号/手动查询/抢购后核对"时都会写入积分基线,但界面一直没有读它: 积分列的 scoreMap 只由"查询积分"按钮填充,所以自动核对完余额后主界面不动, 用户必须再点一次"查询积分"才看到新余额。 - 新增 GetScoreBaselines 绑定:一次性返回各账号最近一次已记录的积分余额 (Scheduler.ScoreBaselines → ScoreBaselineStore.Snapshot),界面直接读取, 不必再为每个账号单独发请求 - 前端 useAccounts 新增 syncScoresFromBackend:把后端基线写入 scoreMap 与 localStorage(与手动查询同一份状态);导入场景下会短暂轮询等待后端异步查询写完 - 抢购状态回到 idle 时自动同步一次(对应抢购结束后的核对结果) - 导入账号成功后按导入的账号名同步一次 - 新增断言:核对后 ScoreBaselines 返回最新余额
This commit is contained in:
@@ -14,7 +14,7 @@ import {
|
||||
|
||||
const { selectedProductId, selectedProductName, selectedProduct } = useProducts()
|
||||
const { showToast } = useToast()
|
||||
const { accounts, loadAccounts, clearSelection } = useAccounts()
|
||||
const { accounts, loadAccounts, clearSelection, syncScoresFromBackend } = useAccounts()
|
||||
|
||||
// 状态
|
||||
const loading = ref(false)
|
||||
@@ -41,6 +41,16 @@ const refreshManagedAccounts = async (resetSelection = true) => {
|
||||
}
|
||||
}
|
||||
|
||||
// parseImportedNames 从粘贴内容里取出账号名(每行 账号名----密码----Cookie)。
|
||||
const parseImportedNames = (content: string): string[] => {
|
||||
return content
|
||||
.split(/\r?\n/)
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line && !line.startsWith('#'))
|
||||
.map((line) => line.split('----')[0]?.trim() || '')
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
const importAccountText = async (content: string) => {
|
||||
accountLoading.value = true
|
||||
try {
|
||||
@@ -52,6 +62,8 @@ const importAccountText = async (content: string) => {
|
||||
accountContent.value = ''
|
||||
await refreshManagedAccounts()
|
||||
showToast('导入成功', `已添加 ${result.count} 个账号`, 'success')
|
||||
// 后端会在导入后自动记一次积分基线,这里等它写完并刷新积分列
|
||||
void syncScoresFromBackend(parseImportedNames(content))
|
||||
} catch (e: any) {
|
||||
showToast('导入失败', e?.message || '', 'error')
|
||||
} finally {
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
GetXiaodianBindQRCode,
|
||||
BindXiaodianRole,
|
||||
DeleteAccounts,
|
||||
GetScoreBaselines,
|
||||
QueryScore,
|
||||
ReloadAccounts,
|
||||
VerifyAllExchange,
|
||||
@@ -150,6 +151,32 @@ export function useAccounts() {
|
||||
}
|
||||
}
|
||||
|
||||
// syncScoresFromBackend 用后端已记录的积分余额刷新积分列。
|
||||
//
|
||||
// 后端在"导入账号""手动查询积分""抢购结束后核对"时都会写入基线,
|
||||
// 这里直接读取结果,不需要再逐账号重新请求接口。
|
||||
// expectNames 非空时会短暂轮询等待后端异步查询完成(导入刚返回时可能还没写完)。
|
||||
const syncScoresFromBackend = async (expectNames: string[] = [], attempts = 4, delayMs = 600) => {
|
||||
const tries = expectNames.length > 0 ? attempts : 1
|
||||
for (let i = 0; i < tries; i++) {
|
||||
try {
|
||||
const d = await GetScoreBaselines()
|
||||
const scores = (d?.scores || {}) as Record<string, string>
|
||||
Object.keys(scores).forEach((name) => {
|
||||
scoreMap.value[name] = String(scores[name])
|
||||
localStorage.setItem('accScore_' + name, String(scores[name]))
|
||||
})
|
||||
if (expectNames.every((name) => name in scores)) {
|
||||
return true
|
||||
}
|
||||
} catch {}
|
||||
if (i < tries - 1) {
|
||||
await new Promise((resolve) => setTimeout(resolve, delayMs))
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const queryAllScore = async () => {
|
||||
showToast('查询积分', '正在并发查询...', 'info')
|
||||
const tasks = accounts.value.map(async (a) => {
|
||||
@@ -358,6 +385,7 @@ export function useAccounts() {
|
||||
selectedIndices,
|
||||
statusMap,
|
||||
scoreMap,
|
||||
syncScoresFromBackend,
|
||||
xiaodianAssetsMap,
|
||||
xiaodianRolesMap,
|
||||
xiaodianQRCode,
|
||||
|
||||
@@ -17,7 +17,7 @@ let unsubscribeSeckillState: (() => void) | null = null
|
||||
|
||||
export function useScheduler() {
|
||||
const { showToast } = useToast()
|
||||
const { resetStatus, buildAccountProducts, markSelectedPending, markAccountNamesPending, markSelectedStatus, queryXiaodianAssets } = useAccounts()
|
||||
const { resetStatus, buildAccountProducts, markSelectedPending, markAccountNamesPending, markSelectedStatus, queryXiaodianAssets, syncScoresFromBackend } = useAccounts()
|
||||
const { selectedProductId, selectedProductName } = useProducts()
|
||||
const { currentActivity } = useActivity()
|
||||
const { addLocalLog, clearLocalResults } = useLogs()
|
||||
@@ -49,6 +49,8 @@ export function useScheduler() {
|
||||
}
|
||||
if (state === 'idle') {
|
||||
void refreshXiaodianAssetsAfterRun()
|
||||
// 抢购结束后的自动核对已经把新余额写进后端基线,这里同步到积分列。
|
||||
void syncScoresFromBackend()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,14 @@ type exchangeLookupResult struct {
|
||||
EmptyCookie bool
|
||||
}
|
||||
|
||||
// GetScoreBaselines 返回各账号最近一次已记录的积分余额。
|
||||
//
|
||||
// 抢购结束后的自动核对会把新余额写进基线,界面据此刷新积分列,
|
||||
// 不必再为每个账号单独发一次查询请求。
|
||||
func (a *App) GetScoreBaselines() types.ScoreBaselineResult {
|
||||
return types.ScoreBaselineResult{Success: true, Scores: a.scheduler.ScoreBaselines()}
|
||||
}
|
||||
|
||||
// QueryScore 查询单个账号的积分。
|
||||
func (a *App) QueryScore(accountName string) types.ScoreResult {
|
||||
if accountName == "" {
|
||||
|
||||
@@ -3,7 +3,7 @@ package constants
|
||||
import "time"
|
||||
|
||||
// Version 应用版本号,与 wails.json version 同步。
|
||||
const Version = "0.3.5"
|
||||
const Version = "0.3.6"
|
||||
|
||||
// BuildTime 构建时间,由编译时 ldflags 注入。
|
||||
var BuildTime = "unknown"
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -549,6 +550,19 @@ func (s *Scheduler) ScoreBaseline(accountName string) (int, bool, time.Time) {
|
||||
return s.scoreBaselines.Get(accountName)
|
||||
}
|
||||
|
||||
// ScoreBaselines 返回各账号最新一次已记录的积分余额(字符串形式,供界面展示)。
|
||||
func (s *Scheduler) ScoreBaselines() map[string]string {
|
||||
if s.scoreBaselines == nil {
|
||||
return map[string]string{}
|
||||
}
|
||||
snapshot := s.scoreBaselines.Snapshot()
|
||||
out := make(map[string]string, len(snapshot))
|
||||
for name, score := range snapshot {
|
||||
out[name] = strconv.Itoa(score)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// RecordScoreBaseline 记录账号的最新积分余额(落盘)。
|
||||
func (s *Scheduler) RecordScoreBaseline(accountName string, score int) {
|
||||
if s.scoreBaselines != nil {
|
||||
|
||||
@@ -1081,6 +1081,10 @@ func TestVerifyRunResultsConfirmsScoreDeduction(t *testing.T) {
|
||||
if after, ok, _ := scheduler.ScoreBaseline("a"); !ok || after != 800 {
|
||||
t.Fatalf("核对后应把 800 存回基线, got %d ok=%v", after, ok)
|
||||
}
|
||||
// 界面通过 ScoreBaselines 读取,用于刷新积分列(无需再逐账号请求接口)
|
||||
if got := scheduler.ScoreBaselines()["a"]; got != "800" {
|
||||
t.Fatalf("ScoreBaselines[a] = %q, want 800", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestVerifyRunResultsWithoutBaseline 没有基线时先记录余额,不误报"未扣分"。
|
||||
|
||||
@@ -112,6 +112,20 @@ func (s *ScoreBaselineStore) Forget(accountNames []string) {
|
||||
s.flush(snapshot)
|
||||
}
|
||||
|
||||
// Snapshot 返回全部账号的最新积分余额副本。
|
||||
func (s *ScoreBaselineStore) Snapshot() map[string]int {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
out := make(map[string]int, len(s.entries))
|
||||
for name, entry := range s.entries {
|
||||
out[name] = entry.Score
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Names 返回已记录基线的账号名(按字典序),便于测试与排查。
|
||||
func (s *ScoreBaselineStore) Names() []string {
|
||||
if s == nil {
|
||||
|
||||
@@ -63,6 +63,14 @@ type TimeResult struct {
|
||||
Source string `json:"source"`
|
||||
}
|
||||
|
||||
// ScoreBaselineResult 各账号最近一次已记录的积分余额,供界面刷新积分列。
|
||||
// 这些值来自"导入账号/手动查询积分/抢购后核对",不需要再逐账号请求接口。
|
||||
type ScoreBaselineResult struct {
|
||||
Success bool `json:"success"`
|
||||
Scores map[string]string `json:"scores"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// ScoreResult 积分查询结果。score 为字符串:成功时为数字字符串,失败时为错误描述。
|
||||
type ScoreResult struct {
|
||||
Score string `json:"score"`
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
"$schema": "https://wails.io/schemas/config.v2.json",
|
||||
"name": "douyu_go",
|
||||
"outputfilename": "douyu_go",
|
||||
"version": "0.3.5",
|
||||
"version": "0.3.6",
|
||||
"frontend:dir": "frontend",
|
||||
"frontend:install": "npm install",
|
||||
"frontend:build": "npm run build",
|
||||
@@ -15,6 +15,6 @@
|
||||
"email": "yml@example.com"
|
||||
},
|
||||
"info": {
|
||||
"productVersion": "0.3.5"
|
||||
"productVersion": "0.3.6"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user