优化账号商品选择并发布0.2.1

This commit is contained in:
yml2213
2026-08-09 20:04:55 +08:00
parent 266f4a3da1
commit 911f967463
4 changed files with 174 additions and 43 deletions
+164 -37
View File
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import QRCode from 'qrcode'
import { useAccounts } from '@/composables/useAccounts'
import { useProducts } from '@/composables/useProducts'
@@ -34,6 +34,7 @@ const { currentActivity } = useActivity()
const { showToast } = useToast()
const isXiaodian = computed(() => currentActivity.value?.id === 'xiaodian')
const productFilterMap = ref<Record<string, string>>({})
const openProductAccount = ref<string | null>(null)
const qrImage = ref('')
const recordsModal = ref<{ accountName: string; loading: boolean; error: string; records: any[] } | null>(null)
const exchangeLookup = ref<{ productId: string; productName: string; loading: boolean; results: Record<string, any> } | null>(null)
@@ -55,6 +56,16 @@ watch(selectedProductId, () => {
exchangeLookup.value = null
})
const handleDocumentClick = (event: MouseEvent) => {
const target = event.target
if (!(target instanceof Element) || !target.closest('.account-product-picker')) {
openProductAccount.value = null
}
}
onMounted(() => document.addEventListener('click', handleDocumentClick))
onUnmounted(() => document.removeEventListener('click', handleDocumentClick))
const roleChannel = (type?: string) => type === 'wx' ? '微信' : type === 'qq' ? '手Q' : (type || '')
const handleBind = async () => {
@@ -102,12 +113,26 @@ const allSelected = computed(() =>
accounts.value.length > 0 && selectedIndices.value.size === accounts.value.length
)
const handleProductFilter = (accountName: string, event: Event) => {
productFilterMap.value[accountName] = (event.target as HTMLInputElement).value
const globalProductLabel = () => `跟随全局:${selectedProductName.value || '未选择商品'}`
const accountProductInputValue = (accountName: string) => {
return productFilterMap.value[accountName] ?? accountProductLabel(accountName)
}
const handleAccountProductChange = (accountName: string, event: Event) => {
setAccountProduct(accountName, (event.target as HTMLSelectElement).value)
const handleAccountProductInput = (accountName: string, event: Event) => {
const value = (event.target as HTMLInputElement).value
productFilterMap.value[accountName] = value
openProductAccount.value = accountName
}
const openAccountProductPicker = (accountName: string) => {
openProductAccount.value = accountName
}
const chooseAccountProduct = (accountName: string, productId: string) => {
setAccountProduct(accountName, productId)
delete productFilterMap.value[accountName]
openProductAccount.value = null
}
const productMatches = (product: Product, keyword: string) => {
@@ -125,7 +150,7 @@ const productMatches = (product: Product, keyword: string) => {
const filteredAccountProducts = (accountName: string) => {
const keyword = (productFilterMap.value[accountName] || '').trim().toLowerCase()
const selectedId = getAccountProduct(accountName)
const matched = products.value.filter(product => productMatches(product, keyword)).slice(0, 80)
const matched = products.value.filter(product => productMatches(product, keyword))
if (selectedId && !matched.some(product => product.commodityId === selectedId)) {
const selected = products.value.find(product => product.commodityId === selectedId)
if (selected) return [selected, ...matched]
@@ -133,6 +158,12 @@ const filteredAccountProducts = (accountName: string) => {
return matched
}
const accountProductLabel = (accountName: string) => {
const productId = getAccountProduct(accountName)
if (!productId) return selectedProductName.value || '未选择商品'
return products.value.find(product => product.commodityId === productId)?.commodityName || productId
}
const selectedLookupProduct = computed(() =>
products.value.find(product => product.commodityId === selectedProductId.value)
)
@@ -354,30 +385,51 @@ const closeExchangeRecords = () => {
class="account-product-picker"
>
<input
class="account-product-filter"
class="account-product-combo"
type="text"
placeholder="筛选商品"
:value="productFilterMap[account.name] || ''"
:value="accountProductInputValue(account.name)"
:placeholder="globalProductLabel()"
@click.stop
@input="handleProductFilter(account.name, $event)"
@focus="openAccountProductPicker(account.name)"
@input="handleAccountProductInput(account.name, $event)"
@keydown.esc="openProductAccount = null"
>
<select
class="account-product-select"
:value="getAccountProduct(account.name)"
@click.stop
@change="handleAccountProductChange(account.name, $event)"
<span
class="account-product-scope"
:class="{ independent: !!getAccountProduct(account.name) }"
>{{ getAccountProduct(account.name) ? '独立' : '全局' }}</span>
<div
v-if="openProductAccount === account.name"
class="account-product-menu"
role="listbox"
@mousedown.prevent
>
<option value="">
跟随全局:{{ selectedProductName || '未选择' }}
</option>
<option
<button
type="button"
class="account-product-option account-product-global"
:class="{ selected: !getAccountProduct(account.name) }"
@click="chooseAccountProduct(account.name, '')"
>
<span>{{ globalProductLabel() }}</span>
<small>恢复全局选择</small>
</button>
<button
v-for="product in filteredAccountProducts(account.name)"
:key="product.commodityId"
:value="product.commodityId"
type="button"
class="account-product-option"
:class="{ selected: getAccountProduct(account.name) === product.commodityId }"
role="option"
:aria-selected="getAccountProduct(account.name) === product.commodityId"
@click="chooseAccountProduct(account.name, product.commodityId)"
>
{{ product.commodityName }} · {{ product.score }}{{ product.payLabel || '点券' }}
</option>
</select>
<span class="account-product-option-name">{{ product.commodityName }}</span>
<small>{{ product.score }}{{ product.payLabel || '点券' }} · {{ product.stockLabel || `库存 ${product.count}` }}</small>
</button>
<div v-if="filteredAccountProducts(account.name).length === 0" class="account-product-empty">
没有匹配的商品
</div>
</div>
</div>
<span v-else-if="selectedProductName" class="prod-tag">
{{ selectedProductName }}
@@ -930,41 +982,116 @@ tbody tr:hover {
}
.account-product-picker {
width: 240px;
position: relative;
width: 260px;
max-width: 100%;
display: grid;
grid-template-columns: minmax(70px, .8fr) minmax(120px, 1.2fr);
display: flex;
align-items: center;
gap: 5px;
}
.account-product-filter,
.account-product-select {
width: 220px;
max-width: 100%;
.account-product-combo {
min-width: 0;
flex: 1 1 auto;
width: 0;
height: 30px;
padding: 0 8px;
border-radius: 6px;
border: 1px solid var(--line);
border-radius: 6px;
background: var(--surface-2);
color: var(--text);
font-size: 11.5px;
}
.account-product-filter,
.account-product-select {
width: 100%;
.account-product-scope {
flex: 0 0 auto;
padding: 3px 5px;
border: 1px solid var(--line-strong);
border-radius: 4px;
color: var(--muted);
font-size: 10px;
line-height: 1.2;
}
.account-product-filter::placeholder {
.account-product-scope.independent {
border-color: var(--accent-border);
color: var(--accent);
background: var(--accent-soft);
}
.account-product-combo::placeholder {
color: var(--muted);
}
.account-product-filter:focus,
.account-product-select:focus {
.account-product-combo:focus {
outline: none;
border-color: var(--accent);
}
.account-product-menu {
position: absolute;
top: calc(100% + 4px);
left: 0;
z-index: 8;
width: min(420px, 42vw);
max-height: 320px;
overflow-y: auto;
padding: 4px;
border: 1px solid var(--line-strong);
border-radius: 7px;
background: var(--surface);
box-shadow: 0 10px 26px rgba(15, 23, 42, .18);
}
.account-product-option {
width: 100%;
min-height: 42px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 7px 9px;
border: 0;
border-radius: 5px;
background: transparent;
color: var(--text);
text-align: left;
cursor: pointer;
}
.account-product-option:hover,
.account-product-option.selected {
background: var(--accent-soft);
}
.account-product-option-name {
min-width: 0;
overflow: hidden;
font-size: 12px;
font-weight: 500;
text-overflow: ellipsis;
white-space: nowrap;
}
.account-product-option small {
flex: 0 0 auto;
color: var(--muted);
font-size: 10px;
white-space: nowrap;
}
.account-product-global {
border-bottom: 1px solid var(--line);
color: var(--accent);
}
.account-product-empty {
padding: 16px 10px;
color: var(--muted);
font-size: 12px;
text-align: center;
}
@media (max-width: 1180px) {
.table-wrap {
max-height: 520px;
@@ -595,9 +595,13 @@ onUnmounted(() => {
.buy-title { margin-left: 8px; }
.btn-fixed { border-color: var(--warning); color: var(--warning); }
.btn-watch { border-color: var(--accent-border); color: var(--accent); }
.btn-watch.watching { background: var(--accent-soft); }
.btn-stock-watch { border-color: var(--success); color: var(--success); }
.btn-stock-watch.watching { background: var(--success-soft); }
.btn-stock-watch { border-color: var(--accent-border); color: var(--accent); }
.btn-watch.watching,
.btn-stock-watch.watching {
border-color: var(--danger);
color: var(--danger);
background: var(--danger-soft);
}
.stock-watch-target {
display: inline-block;
max-width: 180px;
+1 -1
View File
@@ -3,7 +3,7 @@ package constants
import "time"
// Version 应用版本号,与 wails.json version 同步。
const Version = "0.2.0"
const Version = "0.2.1"
// BuildTime 构建时间,由编译时 ldflags 注入。
var BuildTime = "unknown"
+2 -2
View File
@@ -2,7 +2,7 @@
"$schema": "https://wails.io/schemas/config.v2.json",
"name": "douyu_go",
"outputfilename": "douyu_go",
"version": "0.2.0",
"version": "0.2.1",
"frontend:dir": "frontend",
"frontend:install": "npm install",
"frontend:build": "npm run build",
@@ -15,6 +15,6 @@
"email": "yml@example.com"
},
"info": {
"productVersion": "0.2.0"
"productVersion": "0.2.1"
}
}