161 lines
6.2 KiB
Plaintext
161 lines
6.2 KiB
Plaintext
---
|
||
// 品牌筛选弹窗(仅 markup + 样式)。
|
||
// Alpine 状态(modalOpen / modalKeyword / modalLetter / modalResults / modalLoading /
|
||
// modalSelected / SPIN_SVG 等)由父级 RunwayLooks 的 showsPage() 提供,本组件渲染在
|
||
// .showsroot 的 x-data 作用域内,故可直接引用这些变量,无需 props。
|
||
---
|
||
|
||
<!-- 品牌筛选弹窗:搜索 + A-Z 跳转 + 热门推荐,按需加载,避免一次拉全量 -->
|
||
<div class="showsmodal" x-show="modalOpen" x-cloak @keydown.escape.window="closeBrandModal()">
|
||
<div class="showsmodal-mask" @click="closeBrandModal()"></div>
|
||
<div class="showsmodal-panel">
|
||
<header class="showsmodal-head">
|
||
<h3>全部品牌</h3>
|
||
<button class="showsmodal-x" type="button" @click="closeBrandModal()" aria-label="关闭">✕</button>
|
||
</header>
|
||
<div class="showsmodal-search">
|
||
<input
|
||
class="showsmodal-input"
|
||
type="text"
|
||
x-model="modalKeyword"
|
||
@input.debounce.300ms="modalSearch()"
|
||
placeholder="搜索品牌名…"
|
||
autocomplete="off"
|
||
/>
|
||
<p class="showsmodal-tip">最多展示 30 条结果,可用搜索或字母筛选缩小范围</p>
|
||
</div>
|
||
<div class="showsmodal-az">
|
||
<button class="az az-hot" type="button" :class="{ active: modalLetter === 'HOT' }" @click="modalPickLetter('HOT')">HOT</button>
|
||
<template x-for="l in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ#'.split('')" :key="l">
|
||
<button class="az" type="button" :class="{ active: modalLetter === l }" @click="modalPickLetter(l)" x-text="l"></button>
|
||
</template>
|
||
</div>
|
||
<div class="showsmodal-body">
|
||
<div class="showsmodal-hint" x-show="!modalKeyword && modalLetter === 'HOT'">热门推荐</div>
|
||
<div class="showsmodal-loading" x-show="modalLoading" x-html="SPIN_SVG"></div>
|
||
<ul class="showsmodal-list">
|
||
<template x-for="b in modalResults" :key="b.id">
|
||
<li>
|
||
<label class="showsmodal-row">
|
||
<input
|
||
type="radio"
|
||
name="modalDesigner"
|
||
:value="b.id"
|
||
x-model="modalSelected"
|
||
:class="{ 'is-checked': modalSelected === b.id }"
|
||
/>
|
||
<span class="showsmodal-name" x-text="b.display || b.name"></span>
|
||
</label>
|
||
</li>
|
||
</template>
|
||
<li class="showsmodal-empty" x-show="!modalLoading && modalResults.length === 0">未找到匹配的品牌</li>
|
||
</ul>
|
||
</div>
|
||
<footer class="showsmodal-foot">
|
||
<!-- <span class="showsmodal-counter" x-text="`已选 ${modalSelected.length} / 最多 5 个`"></span> -->
|
||
<button class="showsmodal-ok" type="button" @click="confirmBrandModal()">确定</button>
|
||
</footer>
|
||
</div>
|
||
</div>
|
||
|
||
<style>
|
||
@reference "tailwindcss";
|
||
[x-cloak] { display: none !important; }
|
||
|
||
/* ===== 品牌筛选弹窗(语言切换器风格:无 mask、顶部小箭头、干净白底列表) ===== */
|
||
.showsmodal {
|
||
@apply fixed inset-0 z-[60] flex items-start justify-center pt-[14vh] pointer-events-none;
|
||
}
|
||
.showsmodal-mask {
|
||
@apply absolute inset-0 pointer-events-auto cursor-default;
|
||
background: rgba(0, 0, 0, 0.4);
|
||
}
|
||
.showsmodal-panel {
|
||
@apply relative w-full max-w-[680px] max-h-[72vh] bg-white flex flex-col border border-black/10 pointer-events-auto;
|
||
box-shadow: 0 10px 40px -10px rgba(0, 0, 0, 0.15);
|
||
}
|
||
/* 顶部小三角箭头(语言切换器同款:白底 + 浅边框) */
|
||
.showsmodal-panel::before {
|
||
content: "";
|
||
position: absolute;
|
||
top: -5px;
|
||
left: 50%;
|
||
width: 10px;
|
||
height: 10px;
|
||
background: white;
|
||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||
border-left: 1px solid rgba(0, 0, 0, 0.1);
|
||
transform: translateX(-50%) rotate(45deg);
|
||
}
|
||
.showsmodal-head {
|
||
@apply flex items-center justify-between px-4 pt-4 pb-2 shrink-0;
|
||
}
|
||
.showsmodal-head h3 {
|
||
@apply m-0 text-[12px] tracking-[0.22em] uppercase font-bold;
|
||
}
|
||
.showsmodal-x {
|
||
@apply appearance-none bg-transparent border-0 w-6 h-6 inline-flex items-center justify-center cursor-pointer text-[14px] leading-none text-[#999] transition-colors hover:text-black;
|
||
}
|
||
.showsmodal-search {
|
||
@apply px-4 pt-1 pb-3 shrink-0;
|
||
}
|
||
.showsmodal-input {
|
||
@apply w-full border border-black/15 px-3 py-2 text-[13px] outline-none bg-[#fafafa] focus:bg-white focus:border-black;
|
||
}
|
||
.showsmodal-tip {
|
||
@apply mt-2 text-[11px] leading-snug text-black/45 font-mono;
|
||
}
|
||
.showsmodal-az {
|
||
@apply flex flex-wrap gap-1 px-4 pb-3 shrink-0;
|
||
}
|
||
.showsmodal-az .az {
|
||
@apply appearance-none bg-white border border-black/10 w-6 h-6 text-[10px] cursor-pointer leading-none font-mono transition-colors hover:bg-black/5;
|
||
}
|
||
.showsmodal-az .az.active {
|
||
@apply bg-black text-white border-black;
|
||
}
|
||
.showsmodal-az .az-hot {
|
||
@apply w-auto px-2 font-sans font-bold tracking-[0.12em] text-[9px];
|
||
}
|
||
.showsmodal-body {
|
||
@apply flex-1 min-h-0 overflow-y-auto px-2 py-1 overscroll-contain;
|
||
}
|
||
.showsmodal-hint {
|
||
@apply text-[10px] tracking-[0.22em] uppercase text-[#999] px-3 py-2;
|
||
}
|
||
.showsmodal-loading {
|
||
@apply flex justify-center py-6 text-[#bdbdbd];
|
||
}
|
||
.showsmodal-list {
|
||
@apply list-none m-0 p-0;
|
||
}
|
||
.showsmodal-row {
|
||
@apply flex items-center gap-2 w-full cursor-pointer px-3 py-2 text-[13px] transition-colors hover:bg-black/5;
|
||
}
|
||
.showsmodal-row input[type="radio"] {
|
||
@apply appearance-none w-4 h-4 border-[1.5px] border-black bg-white cursor-pointer shrink-0 rounded-full;
|
||
}
|
||
.showsmodal-row input[type="radio"].is-checked {
|
||
@apply bg-white border-black;
|
||
background-image: radial-gradient(circle, #000 36%, transparent 40%);
|
||
}
|
||
.showsmodal-name {
|
||
@apply flex-1 truncate;
|
||
}
|
||
.showsmodal-ct {
|
||
@apply text-[#999] text-[10px] ml-auto;
|
||
}
|
||
.showsmodal-empty {
|
||
@apply py-6 text-center text-[#999] text-[13px];
|
||
}
|
||
.showsmodal-foot {
|
||
@apply flex items-center justify-between px-4 py-3 border-t border-black/5 shrink-0 text-[11px] text-[#888];
|
||
}
|
||
.showsmodal-counter {
|
||
@apply tracking-[0.06em];
|
||
}
|
||
.showsmodal-ok {
|
||
@apply appearance-none bg-black text-white border border-black px-4 py-1.5 cursor-pointer text-[12px] tracking-[0.1em] transition-colors hover:bg-black/85;
|
||
}
|
||
</style>
|