优化和平小店商品列表界面
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useProducts } from '@/composables/useProducts'
|
||||
import { useActivity } from '@/composables/useActivity'
|
||||
import ProductSearch from '@/components/product/ProductSearch.vue'
|
||||
@@ -11,6 +12,7 @@ defineProps<{
|
||||
|
||||
const { loadProducts, productInfo, filteredProducts, loading } = useProducts()
|
||||
const { activities, currentActivity, switching, switchActivity } = useActivity()
|
||||
const isXiaodian = computed(() => currentActivity.value?.id === 'xiaodian')
|
||||
|
||||
const onSwitchActivity = async (e: Event) => {
|
||||
const id = (e.target as HTMLSelectElement).value
|
||||
@@ -69,9 +71,9 @@ const onSwitchActivity = async (e: Event) => {
|
||||
|
||||
<div class="product-meta" :class="{ good: productInfo && !productInfo.stale, warn: productInfo?.stale }">
|
||||
<template v-if="productInfo?.fetchedAt">
|
||||
实时库存 {{ filteredProducts.length }} 个,刷新于 {{ productInfo.fetchedAt }}
|
||||
{{ isXiaodian ? '商品' : '实时库存' }} {{ filteredProducts.length }} 个,刷新于 {{ productInfo.fetchedAt }}
|
||||
</template>
|
||||
<template v-else>库存未刷新</template>
|
||||
<template v-else>{{ isXiaodian ? '商品未刷新' : '库存未刷新' }}</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -194,6 +196,9 @@ const onSwitchActivity = async (e: Event) => {
|
||||
font-size: 11px;
|
||||
color: var(--muted);
|
||||
padding: 0 2px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.product-meta.good {
|
||||
|
||||
@@ -5,7 +5,13 @@ import { useActivity } from '@/composables/useActivity'
|
||||
|
||||
const { selectedProductId, selectedProductName, selectedProduct } = useProducts()
|
||||
const { currentActivity } = useActivity()
|
||||
const priceLabel = computed(() => currentActivity.value?.id === 'xiaodian' ? '点券' : '积分')
|
||||
const isXiaodian = computed(() => currentActivity.value?.id === 'xiaodian')
|
||||
const priceLabel = computed(() => isXiaodian.value ? '点券' : '积分')
|
||||
const stockText = computed(() => {
|
||||
if (!selectedProduct.value) return ''
|
||||
if (isXiaodian.value && selectedProduct.value.count < 0) return '库存未公开'
|
||||
return `库存 ${selectedProduct.value.count}`
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -15,8 +21,8 @@ const priceLabel = computed(() => currentActivity.value?.id === 'xiaodian' ? '
|
||||
<div class="selected-product-meta">
|
||||
<template v-if="selectedProduct">
|
||||
<span class="chip chip-score">{{ selectedProduct.score }} {{ priceLabel }}</span>
|
||||
<span class="chip chip-stock" :class="{ low: selectedProduct.count < 500 }">
|
||||
库存 {{ selectedProduct.count }}
|
||||
<span class="chip chip-stock" :class="{ low: selectedProduct.count >= 0 && selectedProduct.count < 500 }">
|
||||
{{ stockText }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-else-if="selectedProductId">
|
||||
@@ -56,7 +62,11 @@ const priceLabel = computed(() => currentActivity.value?.id === 'xiaodian' ? '
|
||||
color: var(--text-strong);
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
word-break: break-all;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.selected-product-meta {
|
||||
|
||||
@@ -6,15 +6,18 @@ import { useActivity } from '@/composables/useActivity'
|
||||
|
||||
const { filteredProducts, selectedProductId, selectProduct, searchQuery } = useProducts()
|
||||
const { currentActivity } = useActivity()
|
||||
const priceLabel = computed(() => currentActivity.value?.id === 'xiaodian' ? '点券' : '积分')
|
||||
const isXiaodian = computed(() => currentActivity.value?.id === 'xiaodian')
|
||||
const priceLabel = computed(() => isXiaodian.value ? '点券' : '积分')
|
||||
const stockText = (count: number) => isXiaodian.value && count < 0 ? '库存未公开' : `库存 ${count}`
|
||||
|
||||
const parentRef = ref<HTMLElement | null>(null)
|
||||
const itemSize = 72
|
||||
|
||||
const virtualizer = useVirtualizer(
|
||||
computed(() => ({
|
||||
count: filteredProducts.value.length,
|
||||
getScrollElement: () => parentRef.value,
|
||||
estimateSize: () => 58,
|
||||
estimateSize: () => itemSize,
|
||||
overscan: 5
|
||||
}))
|
||||
)
|
||||
@@ -52,19 +55,31 @@ const handleSelect = (commodityId: string, commodityName: string) => {
|
||||
}"
|
||||
@click="handleSelect(filteredProducts[item.index].commodityId, filteredProducts[item.index].commodityName)"
|
||||
>
|
||||
<img
|
||||
:src="filteredProducts[item.index].pic"
|
||||
@error="($event.target as HTMLImageElement).style.display = 'none'"
|
||||
>
|
||||
<div class="thumb">
|
||||
<span class="thumb-fallback" aria-hidden="true">礼</span>
|
||||
<img
|
||||
:src="filteredProducts[item.index].pic"
|
||||
loading="lazy"
|
||||
alt=""
|
||||
@error="($event.target as HTMLImageElement).style.display = 'none'"
|
||||
>
|
||||
</div>
|
||||
<div class="pinfo">
|
||||
<div class="pname">{{ filteredProducts[item.index].commodityName }}</div>
|
||||
<div
|
||||
class="pname"
|
||||
:title="filteredProducts[item.index].commodityName"
|
||||
>{{ filteredProducts[item.index].commodityName }}</div>
|
||||
<div class="pmeta">
|
||||
<span class="chip chip-score">{{ filteredProducts[item.index].score }} {{ priceLabel }}</span>
|
||||
<span
|
||||
class="chip chip-score"
|
||||
:title="`${filteredProducts[item.index].score} ${priceLabel}`"
|
||||
>{{ filteredProducts[item.index].score }} {{ priceLabel }}</span>
|
||||
<span
|
||||
class="chip chip-stock"
|
||||
:class="{ low: filteredProducts[item.index].count < 500 }"
|
||||
:class="{ low: filteredProducts[item.index].count >= 0 && filteredProducts[item.index].count < 500 }"
|
||||
:title="stockText(filteredProducts[item.index].count)"
|
||||
>
|
||||
库存 {{ filteredProducts[item.index].count }}
|
||||
{{ stockText(filteredProducts[item.index].count) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -77,7 +92,7 @@ const handleSelect = (commodityId: string, commodityName: string) => {
|
||||
.product-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 6px 8px 12px;
|
||||
padding: 8px 8px 14px;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
@@ -87,14 +102,16 @@ const handleSelect = (commodityId: string, commodityName: string) => {
|
||||
|
||||
.prod-item {
|
||||
display: grid;
|
||||
grid-template-columns: 40px minmax(0, 1fr);
|
||||
grid-template-columns: 42px minmax(0, 1fr);
|
||||
gap: 10px;
|
||||
padding: 8px 10px;
|
||||
margin: 2px 0;
|
||||
height: 72px;
|
||||
padding: 9px 10px;
|
||||
margin: 3px 0;
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
border: 1px solid transparent;
|
||||
transition: .12s;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.prod-item:hover {
|
||||
@@ -106,31 +123,57 @@ const handleSelect = (commodityId: string, commodityName: string) => {
|
||||
border-color: var(--accent-border);
|
||||
}
|
||||
|
||||
.prod-item img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
.thumb {
|
||||
position: relative;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
align-self: center;
|
||||
overflow: hidden;
|
||||
border-radius: var(--radius-sm);
|
||||
object-fit: cover;
|
||||
background: var(--field);
|
||||
border: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.thumb-fallback {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: var(--muted-2);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.thumb img {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: var(--radius-sm);
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.pinfo {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
gap: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pname {
|
||||
font-size: 12.5px;
|
||||
font-weight: 500;
|
||||
color: var(--text-strong);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 1.3;
|
||||
line-height: 1.35;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.pmeta {
|
||||
@@ -138,6 +181,22 @@ const handleSelect = (commodityId: string, commodityName: string) => {
|
||||
color: var(--muted);
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
min-width: 0;
|
||||
flex-wrap: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pmeta .chip {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.pmeta .chip-score {
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
.pmeta .chip-stock {
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useProducts } from '@/composables/useProducts'
|
||||
import { useActivity } from '@/composables/useActivity'
|
||||
|
||||
const { searchQuery, setSearch, clearSearch } = useProducts()
|
||||
const { currentActivity } = useActivity()
|
||||
const inputValue = ref(searchQuery.value)
|
||||
const searchPlaceholder = computed(() => currentActivity.value?.id === 'xiaodian'
|
||||
? '搜索商品名 / ID / 点券 / 库存'
|
||||
: '搜索商品名 / ID / 积分 / 库存')
|
||||
|
||||
// 防抖处理
|
||||
let debounceTimer: number | null = null
|
||||
@@ -28,7 +33,7 @@ const handleClear = () => {
|
||||
<input
|
||||
v-model="inputValue"
|
||||
type="search"
|
||||
placeholder="搜索商品名 / ID / 积分 / 库存"
|
||||
:placeholder="searchPlaceholder"
|
||||
autocomplete="off"
|
||||
>
|
||||
<button
|
||||
|
||||
@@ -148,6 +148,15 @@ func TestProductsAutomaticallyResolvesXiaodianRoleAndPaginates(t *testing.T) {
|
||||
t.Errorf("_act_id = %q", got)
|
||||
}
|
||||
_, _ = w.Write([]byte(`var info = {"gameOpenId":"openid","roleId":1192083858,"type":"qq","area":1};`))
|
||||
case "/role/":
|
||||
switch r.URL.Query().Get("_jsvar") {
|
||||
case "banlanceInfo":
|
||||
_, _ = w.Write([]byte(`var banlanceInfo={"balance":8800};`))
|
||||
case "jbInfos":
|
||||
_, _ = w.Write([]byte(`var jbInfos={"jb2":"36"};`))
|
||||
default:
|
||||
http.Error(w, "unknown asset query", http.StatusBadRequest)
|
||||
}
|
||||
case "/goods":
|
||||
offset := r.URL.Query().Get("page_begin")
|
||||
goodsOffsets = append(goodsOffsets, offset)
|
||||
@@ -204,6 +213,13 @@ func TestProductsAutomaticallyResolvesXiaodianRoleAndPaginates(t *testing.T) {
|
||||
if got := strings.Join(goodsOffsets, ","); got != strings.Join([]string{"0", strconv.Itoa(2), strconv.Itoa(4)}, ",") {
|
||||
t.Fatalf("分页偏移 = %s", got)
|
||||
}
|
||||
assets, err := cli.XiaodianAssets("acf_uid=uid; acf_stk=stk; acf_ltkid=ltkid")
|
||||
if err != nil {
|
||||
t.Fatalf("XiaodianAssets() error = %v", err)
|
||||
}
|
||||
if assets.Coupons != 8800 || assets.Fragments != 36 {
|
||||
t.Fatalf("资产 = %#v", assets)
|
||||
}
|
||||
}
|
||||
|
||||
func testCTNClient(t *testing.T, ctnURL string) *DouyuClient {
|
||||
|
||||
Reference in New Issue
Block a user