优化账号商品选择并发布0.2.1
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user