|
@@ -0,0 +1,705 @@
|
|
|
|
|
+<!--
|
|
|
|
|
+ 商品列表页
|
|
|
|
|
+ 从店铺首页金刚区进入;支持搜索、分页触底加载、筛选弹窗(配送占位/价格/分类/场景)
|
|
|
|
|
+ 按钮配色与店铺首页统一:#FF4D6D / linear-gradient(135deg, #FF8FA3, #FF4D6D)
|
|
|
|
|
+-->
|
|
|
|
|
+<template>
|
|
|
|
|
+ <view class="goods-list-page">
|
|
|
|
|
+ <!-- 搜索栏 -->
|
|
|
|
|
+ <view class="search-bar">
|
|
|
|
|
+ <view class="search-input-wrap">
|
|
|
|
|
+ <app-search-module
|
|
|
|
|
+ v-model="searchText"
|
|
|
|
|
+ placeholder="请输入搜索商品名称"
|
|
|
|
|
+ :height="66"
|
|
|
|
|
+ :fontSize="28"
|
|
|
|
|
+ backColor="#fff"
|
|
|
|
|
+ @input="onSearchInput"
|
|
|
|
|
+ @search="doSearch"
|
|
|
|
|
+ />
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="search-btn" @click="doSearch">搜索</view>
|
|
|
|
|
+ <view class="filter-btn" @click="openFilter">筛选</view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 商品列表 -->
|
|
|
|
|
+ <view v-if="!$util.isEmpty(list.data)" class="card-list">
|
|
|
|
|
+ <view
|
|
|
|
|
+ v-for="item in list.data"
|
|
|
|
|
+ :key="item.id"
|
|
|
|
|
+ class="goods-card"
|
|
|
|
|
+ @click="goDetail(item)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <view class="cover-wrap">
|
|
|
|
|
+ <image class="cover" :src="item.smallCover || item.cover" mode="aspectFill" />
|
|
|
|
|
+ <view v-if="item.badgeText" class="badge" :class="item.badgeClass">
|
|
|
|
|
+ {{ item.badgeText }}
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="card-body">
|
|
|
|
|
+ <text class="goods-name">{{ item.name }}</text>
|
|
|
|
|
+ <view v-if="item.flowerNum > 0" class="spec-text">{{ item.flowerNum }}支/扎</view>
|
|
|
|
|
+ <view v-if="item.useCaseNames && item.useCaseNames.length" class="tag-row">
|
|
|
|
|
+ <text
|
|
|
|
|
+ v-for="(tag, idx) in item.useCaseNames"
|
|
|
|
|
+ :key="idx"
|
|
|
|
|
+ class="tag-pill"
|
|
|
|
|
+ >{{ tag }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="bottom-row">
|
|
|
|
|
+ <text class="price">
|
|
|
|
|
+ <text class="price-symbol">¥</text>
|
|
|
|
|
+ {{ formatPrice(item.price) }}
|
|
|
|
|
+ <text class="price-suffix">起</text>
|
|
|
|
|
+ </text>
|
|
|
|
|
+ <text class="sold">已售{{ formatSold(item.totalSold) }}件</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view v-if="list.finished" class="list-end">已经到底了</view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <block v-else-if="!list.loading">
|
|
|
|
|
+ <app-wrapper-empty title="暂无商品" :is-empty="true" />
|
|
|
|
|
+ </block>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 筛选弹窗 -->
|
|
|
|
|
+ <bottom-popup :show="filterShow" bgcolor="#f5f5f5" :height="0" @close="closeFilter">
|
|
|
|
|
+ <view class="filter-panel">
|
|
|
|
|
+ <view class="filter-title">全部筛选</view>
|
|
|
|
|
+ <scroll-view scroll-y class="filter-scroll">
|
|
|
|
|
+ <!-- 配送方式(占位,不参与请求) -->
|
|
|
|
|
+ <view class="filter-section">
|
|
|
|
|
+ <text class="section-label">配送方式</text>
|
|
|
|
|
+ <view class="option-grid">
|
|
|
|
|
+ <view
|
|
|
|
|
+ v-for="opt in deliveryOptions"
|
|
|
|
|
+ :key="opt.value"
|
|
|
|
|
+ class="option-item"
|
|
|
|
|
+ :class="{ active: draft.delivery === opt.value }"
|
|
|
|
|
+ @click="toggleDelivery(opt.value)"
|
|
|
|
|
+ >{{ opt.label }}</view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 价格区间 -->
|
|
|
|
|
+ <view class="filter-section">
|
|
|
|
|
+ <text class="section-label">价格区间</text>
|
|
|
|
|
+ <view class="option-grid cols-4">
|
|
|
|
|
+ <view
|
|
|
|
|
+ v-for="opt in pricePresets"
|
|
|
|
|
+ :key="opt.key"
|
|
|
|
|
+ class="option-item"
|
|
|
|
|
+ :class="{ active: draft.pricePreset === opt.key }"
|
|
|
|
|
+ @click="selectPricePreset(opt)"
|
|
|
|
|
+ >{{ opt.label }}</view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="custom-price-row">
|
|
|
|
|
+ <input
|
|
|
|
|
+ class="price-input"
|
|
|
|
|
+ type="digit"
|
|
|
|
|
+ v-model="draft.customMin"
|
|
|
|
|
+ placeholder="自定义最低价"
|
|
|
|
|
+ @input="onCustomPriceInput"
|
|
|
|
|
+ />
|
|
|
|
|
+ <text class="price-dash">—</text>
|
|
|
|
|
+ <input
|
|
|
|
|
+ class="price-input"
|
|
|
|
|
+ type="digit"
|
|
|
|
|
+ v-model="draft.customMax"
|
|
|
|
|
+ placeholder="自定义最高价"
|
|
|
|
|
+ @input="onCustomPriceInput"
|
|
|
|
|
+ />
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 分类 -->
|
|
|
|
|
+ <view class="filter-section">
|
|
|
|
|
+ <text class="section-label">分类</text>
|
|
|
|
|
+ <view class="option-grid">
|
|
|
|
|
+ <view
|
|
|
|
|
+ v-for="cat in categoryOptions"
|
|
|
|
|
+ :key="cat.id"
|
|
|
|
|
+ class="option-item"
|
|
|
|
|
+ :class="{ active: isCategoryChecked(cat.id) }"
|
|
|
|
|
+ @click="toggleCategory(cat.id)"
|
|
|
|
|
+ >{{ cat.categoryName }}</view>
|
|
|
|
|
+ <view v-if="!categoryOptions.length" class="option-empty">暂无分类</view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 场景 -->
|
|
|
|
|
+ <view class="filter-section">
|
|
|
|
|
+ <text class="section-label">场景</text>
|
|
|
|
|
+ <view class="option-grid">
|
|
|
|
|
+ <view
|
|
|
|
|
+ v-for="uc in useCaseOptions"
|
|
|
|
|
+ :key="uc.id"
|
|
|
|
|
+ class="option-item"
|
|
|
|
|
+ :class="{ active: isUseCaseChecked(uc.id) }"
|
|
|
|
|
+ @click="toggleUseCase(uc.id)"
|
|
|
|
|
+ >{{ uc.useCaseName }}</view>
|
|
|
|
|
+ <view v-if="!useCaseOptions.length" class="option-empty">暂无场景</view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="filter-scroll-space"></view>
|
|
|
|
|
+ </scroll-view>
|
|
|
|
|
+ <view class="filter-footer">
|
|
|
|
|
+ <view class="confirm-btn" @click="confirmFilter">确认</view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </bottom-popup>
|
|
|
|
|
+ </view>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script>
|
|
|
|
|
+import AppSearchModule from '@/components/item/module/app-search'
|
|
|
|
|
+import AppWrapperEmpty from '@/components/app-wrapper-empty'
|
|
|
|
|
+import BottomPopup from '@/components/plugin/bottom-popup'
|
|
|
|
|
+import { getClass, getList, getUseCaseList } from '@/api/category'
|
|
|
|
|
+import { list } from '@/mixins'
|
|
|
|
|
+
|
|
|
|
|
+const PRICE_PRESETS = [
|
|
|
|
|
+ { key: 'lt50', label: '50元以下', min: '', max: '50' },
|
|
|
|
|
+ { key: '50_100', label: '50~100元', min: '50', max: '100' },
|
|
|
|
|
+ { key: '100_200', label: '100~200元', min: '100', max: '200' },
|
|
|
|
|
+ { key: 'gt200', label: '200元以上', min: '200', max: '' }
|
|
|
|
|
+]
|
|
|
|
|
+
|
|
|
|
|
+const DELIVERY_OPTIONS = [
|
|
|
|
|
+ { label: '跑腿配送', value: 'errand' },
|
|
|
|
|
+ { label: '到店自取', value: 'pickup' },
|
|
|
|
|
+ { label: '商家配送', value: 'merchant' },
|
|
|
|
|
+ { label: '快递', value: 'express' },
|
|
|
|
|
+ { label: '物流', value: 'logistics' }
|
|
|
|
|
+]
|
|
|
|
|
+
|
|
|
|
|
+const emptyDraft = () => ({
|
|
|
|
|
+ delivery: '',
|
|
|
|
|
+ pricePreset: '',
|
|
|
|
|
+ customMin: '',
|
|
|
|
|
+ customMax: '',
|
|
|
|
|
+ categoryIds: [],
|
|
|
|
|
+ useCaseIds: []
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+export default {
|
|
|
|
|
+ name: 'goodsList',
|
|
|
|
|
+ components: {
|
|
|
|
|
+ AppSearchModule,
|
|
|
|
|
+ AppWrapperEmpty,
|
|
|
|
|
+ BottomPopup
|
|
|
|
|
+ },
|
|
|
|
|
+ mixins: [list],
|
|
|
|
|
+ data() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ searchText: '',
|
|
|
|
|
+ searchTimer: null,
|
|
|
|
|
+ filterShow: false,
|
|
|
|
|
+ categoryOptions: [],
|
|
|
|
|
+ useCaseOptions: [],
|
|
|
|
|
+ deliveryOptions: DELIVERY_OPTIONS,
|
|
|
|
|
+ pricePresets: PRICE_PRESETS,
|
|
|
|
|
+ // 已生效的筛选条件
|
|
|
|
|
+ filters: {
|
|
|
|
|
+ categoryIds: [],
|
|
|
|
|
+ useCaseIds: [],
|
|
|
|
|
+ minPrice: '',
|
|
|
|
|
+ maxPrice: '',
|
|
|
|
|
+ delivery: ''
|
|
|
|
|
+ },
|
|
|
|
|
+ // 弹窗内草稿
|
|
|
|
|
+ draft: emptyDraft()
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ init() {
|
|
|
|
|
+ this.applyRouteFilter()
|
|
|
|
|
+ this.loadFilterOptions()
|
|
|
|
|
+ this.loadGoods()
|
|
|
|
|
+ },
|
|
|
|
|
+ /** 解析金刚区带入的 filterType / filterValue */
|
|
|
|
|
+ applyRouteFilter() {
|
|
|
|
|
+ const type = parseInt((this.option && this.option.filterType) || 0, 10)
|
|
|
|
|
+ const value = (this.option && this.option.filterValue) ? String(this.option.filterValue) : ''
|
|
|
|
|
+ const ids = value.split(',').map((s) => s.trim()).filter(Boolean)
|
|
|
|
|
+ if (type === 3 && ids.length) {
|
|
|
|
|
+ this.filters.categoryIds = ids
|
|
|
|
|
+ } else if (type === 4 && ids.length) {
|
|
|
|
|
+ this.filters.useCaseIds = ids
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ loadFilterOptions() {
|
|
|
|
|
+ getClass().then((res) => {
|
|
|
|
|
+ if (res.code == 1) {
|
|
|
|
|
+ this.categoryOptions = res.data || []
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ getUseCaseList().then((res) => {
|
|
|
|
|
+ if (res.code == 1) {
|
|
|
|
|
+ this.useCaseOptions = res.data || []
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ buildQuery() {
|
|
|
|
|
+ const params = {
|
|
|
|
|
+ page: this.list.page,
|
|
|
|
|
+ pageSize: 10,
|
|
|
|
|
+ requestType: 'mall'
|
|
|
|
|
+ }
|
|
|
|
|
+ if (this.searchText) {
|
|
|
|
|
+ params.searchText = this.searchText
|
|
|
|
|
+ }
|
|
|
|
|
+ if (this.filters.categoryIds.length) {
|
|
|
|
|
+ params.categoryIds = this.filters.categoryIds.join(',')
|
|
|
|
|
+ }
|
|
|
|
|
+ if (this.filters.useCaseIds.length) {
|
|
|
|
|
+ params.useCaseIds = this.filters.useCaseIds.join(',')
|
|
|
|
|
+ }
|
|
|
|
|
+ if (this.filters.minPrice !== '' && this.filters.minPrice != null) {
|
|
|
|
|
+ params.minPrice = this.filters.minPrice
|
|
|
|
|
+ }
|
|
|
|
|
+ if (this.filters.maxPrice !== '' && this.filters.maxPrice != null) {
|
|
|
|
|
+ params.maxPrice = this.filters.maxPrice
|
|
|
|
|
+ }
|
|
|
|
|
+ return params
|
|
|
|
|
+ },
|
|
|
|
|
+ loadGoods() {
|
|
|
|
|
+ this.list.loading = true
|
|
|
|
|
+ return getList(this.buildQuery())
|
|
|
|
|
+ .then((res) => {
|
|
|
|
|
+ this.completes(res)
|
|
|
|
|
+ })
|
|
|
|
|
+ .catch(() => {
|
|
|
|
|
+ this.list.loading = false
|
|
|
|
|
+ this.list.finished = true
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ onSearchInput() {
|
|
|
|
|
+ if (this.searchTimer) {
|
|
|
|
|
+ clearTimeout(this.searchTimer)
|
|
|
|
|
+ this.searchTimer = null
|
|
|
|
|
+ }
|
|
|
|
|
+ this.searchTimer = setTimeout(() => {
|
|
|
|
|
+ this.doSearch()
|
|
|
|
|
+ }, 600)
|
|
|
|
|
+ },
|
|
|
|
|
+ doSearch() {
|
|
|
|
|
+ if (this.searchTimer) {
|
|
|
|
|
+ clearTimeout(this.searchTimer)
|
|
|
|
|
+ this.searchTimer = null
|
|
|
|
|
+ }
|
|
|
|
|
+ this.resetList()
|
|
|
|
|
+ this.loadGoods()
|
|
|
|
|
+ },
|
|
|
|
|
+ openFilter() {
|
|
|
|
|
+ const presetKey = this.resolvePresetKey(this.filters.minPrice, this.filters.maxPrice)
|
|
|
|
|
+ this.draft = {
|
|
|
|
|
+ delivery: this.filters.delivery || '',
|
|
|
|
|
+ pricePreset: presetKey,
|
|
|
|
|
+ customMin: presetKey ? '' : (this.filters.minPrice === '' || this.filters.minPrice == null ? '' : String(this.filters.minPrice)),
|
|
|
|
|
+ customMax: presetKey ? '' : (this.filters.maxPrice === '' || this.filters.maxPrice == null ? '' : String(this.filters.maxPrice)),
|
|
|
|
|
+ categoryIds: [...this.filters.categoryIds],
|
|
|
|
|
+ useCaseIds: [...this.filters.useCaseIds]
|
|
|
|
|
+ }
|
|
|
|
|
+ this.filterShow = true
|
|
|
|
|
+ },
|
|
|
|
|
+ closeFilter() {
|
|
|
|
|
+ this.filterShow = false
|
|
|
|
|
+ },
|
|
|
|
|
+ resolvePresetKey(min, max) {
|
|
|
|
|
+ const minStr = min === '' || min == null ? '' : String(min)
|
|
|
|
|
+ const maxStr = max === '' || max == null ? '' : String(max)
|
|
|
|
|
+ const found = PRICE_PRESETS.find((p) => p.min === minStr && p.max === maxStr)
|
|
|
|
|
+ return found ? found.key : ''
|
|
|
|
|
+ },
|
|
|
|
|
+ toggleDelivery(value) {
|
|
|
|
|
+ this.draft.delivery = this.draft.delivery === value ? '' : value
|
|
|
|
|
+ },
|
|
|
|
|
+ selectPricePreset(opt) {
|
|
|
|
|
+ if (this.draft.pricePreset === opt.key) {
|
|
|
|
|
+ this.draft.pricePreset = ''
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.draft.pricePreset = opt.key
|
|
|
|
|
+ this.draft.customMin = ''
|
|
|
|
|
+ this.draft.customMax = ''
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ onCustomPriceInput() {
|
|
|
|
|
+ if (this.draft.customMin || this.draft.customMax) {
|
|
|
|
|
+ this.draft.pricePreset = ''
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ isCategoryChecked(id) {
|
|
|
|
|
+ return this.draft.categoryIds.indexOf(String(id)) > -1
|
|
|
|
|
+ },
|
|
|
|
|
+ toggleCategory(id) {
|
|
|
|
|
+ const key = String(id)
|
|
|
|
|
+ const idx = this.draft.categoryIds.indexOf(key)
|
|
|
|
|
+ if (idx > -1) {
|
|
|
|
|
+ this.draft.categoryIds.splice(idx, 1)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.draft.categoryIds.push(key)
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ isUseCaseChecked(id) {
|
|
|
|
|
+ return this.draft.useCaseIds.indexOf(String(id)) > -1
|
|
|
|
|
+ },
|
|
|
|
|
+ toggleUseCase(id) {
|
|
|
|
|
+ const key = String(id)
|
|
|
|
|
+ const idx = this.draft.useCaseIds.indexOf(key)
|
|
|
|
|
+ if (idx > -1) {
|
|
|
|
|
+ this.draft.useCaseIds.splice(idx, 1)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.draft.useCaseIds.push(key)
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ confirmFilter() {
|
|
|
|
|
+ let minPrice = ''
|
|
|
|
|
+ let maxPrice = ''
|
|
|
|
|
+ if (this.draft.customMin || this.draft.customMax) {
|
|
|
|
|
+ minPrice = this.draft.customMin
|
|
|
|
|
+ maxPrice = this.draft.customMax
|
|
|
|
|
+ } else if (this.draft.pricePreset) {
|
|
|
|
|
+ const preset = PRICE_PRESETS.find((p) => p.key === this.draft.pricePreset)
|
|
|
|
|
+ if (preset) {
|
|
|
|
|
+ minPrice = preset.min
|
|
|
|
|
+ maxPrice = preset.max
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ this.filters = {
|
|
|
|
|
+ categoryIds: [...this.draft.categoryIds],
|
|
|
|
|
+ useCaseIds: [...this.draft.useCaseIds],
|
|
|
|
|
+ minPrice,
|
|
|
|
|
+ maxPrice,
|
|
|
|
|
+ delivery: this.draft.delivery
|
|
|
|
|
+ }
|
|
|
|
|
+ this.filterShow = false
|
|
|
|
|
+ this.resetList()
|
|
|
|
|
+ this.loadGoods()
|
|
|
|
|
+ },
|
|
|
|
|
+ formatPrice(val) {
|
|
|
|
|
+ const n = parseFloat(val)
|
|
|
|
|
+ if (isNaN(n)) return '0'
|
|
|
|
|
+ return n % 1 === 0 ? String(n) : n.toFixed(2)
|
|
|
|
|
+ },
|
|
|
|
|
+ formatSold(val) {
|
|
|
|
|
+ const n = parseInt(val, 10) || 0
|
|
|
|
|
+ if (n >= 10000) return (n / 10000).toFixed(1) + '万+'
|
|
|
|
|
+ return String(n)
|
|
|
|
|
+ },
|
|
|
|
|
+ goDetail(item) {
|
|
|
|
|
+ const account = (this.option && this.option.account) || uni.getStorageSync('account') || ''
|
|
|
|
|
+ const hdId = (this.option && this.option.hdId) || uni.getStorageSync('hdId') || ''
|
|
|
|
|
+ this.pageTo({
|
|
|
|
|
+ url: `/pages/goods/detail?id=${item.id}&account=${account}&hdId=${hdId}`
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ async onPullDownRefresh() {
|
|
|
|
|
+ this.resetList()
|
|
|
|
|
+ await this.loadGoods()
|
|
|
|
|
+ uni.stopPullDownRefresh()
|
|
|
|
|
+ },
|
|
|
|
|
+ async onReachBottom() {
|
|
|
|
|
+ if (!this.list.finished) {
|
|
|
|
|
+ await this.loadGoods()
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ beforeDestroy() {
|
|
|
|
|
+ if (this.searchTimer) {
|
|
|
|
|
+ clearTimeout(this.searchTimer)
|
|
|
|
|
+ this.searchTimer = null
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style lang="scss" scoped>
|
|
|
|
|
+.goods-list-page {
|
|
|
|
|
+ min-height: 100vh;
|
|
|
|
|
+ background: #f5f5f5;
|
|
|
|
|
+ padding-bottom: 40upx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.search-bar {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ padding: 16upx 20upx;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ position: sticky;
|
|
|
|
|
+ top: 0;
|
|
|
|
|
+ z-index: 20;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.search-input-wrap {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ border: 1upx solid #e5e5e5;
|
|
|
|
|
+ border-radius: 32upx;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.search-btn,
|
|
|
|
|
+.filter-btn {
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ margin-left: 12upx;
|
|
|
|
|
+ padding: 0 28upx;
|
|
|
|
|
+ height: 64upx;
|
|
|
|
|
+ line-height: 64upx;
|
|
|
|
|
+ font-size: 26upx;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ background: linear-gradient(135deg, #FF8FA3, #FF4D6D);
|
|
|
|
|
+ border-radius: 32upx;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.card-list {
|
|
|
|
|
+ padding: 20upx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.goods-card {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border-radius: 16upx;
|
|
|
|
|
+ padding: 20upx;
|
|
|
|
|
+ margin-bottom: 20upx;
|
|
|
|
|
+ box-shadow: 0 4upx 16upx rgba(0, 0, 0, 0.04);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.cover-wrap {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ width: 200upx;
|
|
|
|
|
+ height: 200upx;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ border-radius: 12upx;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ background: #f5f5f5;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.cover {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.badge {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ left: 0;
|
|
|
|
|
+ top: 0;
|
|
|
|
|
+ padding: 4upx 12upx;
|
|
|
|
|
+ font-size: 20upx;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ border-radius: 0 0 12upx 0;
|
|
|
|
|
+ background: #FF4D6D;
|
|
|
|
|
+
|
|
|
|
|
+ &.new {
|
|
|
|
|
+ background: #09C567;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.card-body {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ margin-left: 20upx;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.goods-name {
|
|
|
|
|
+ font-size: 30upx;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+ display: -webkit-box;
|
|
|
|
|
+ -webkit-line-clamp: 2;
|
|
|
|
|
+ line-clamp: 2;
|
|
|
|
|
+ -webkit-box-orient: vertical;
|
|
|
|
|
+ line-height: 1.4;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.spec-text {
|
|
|
|
|
+ margin-top: 8upx;
|
|
|
|
|
+ font-size: 22upx;
|
|
|
|
|
+ color: #999;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.tag-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ margin-top: 12upx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.tag-pill {
|
|
|
|
|
+ margin-right: 10upx;
|
|
|
|
|
+ margin-bottom: 8upx;
|
|
|
|
|
+ padding: 4upx 14upx;
|
|
|
|
|
+ font-size: 20upx;
|
|
|
|
|
+ color: #666;
|
|
|
|
|
+ background: #f5f5f5;
|
|
|
|
|
+ border-radius: 20upx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.bottom-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: baseline;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ margin-top: auto;
|
|
|
|
|
+ padding-top: 12upx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.price {
|
|
|
|
|
+ font-size: 34upx;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: #FF4D6D;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.price-symbol {
|
|
|
|
|
+ font-size: 24upx;
|
|
|
|
|
+ margin-right: 2upx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.price-suffix {
|
|
|
|
|
+ font-size: 22upx;
|
|
|
|
|
+ font-weight: 400;
|
|
|
|
|
+ margin-left: 4upx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.sold {
|
|
|
|
|
+ font-size: 22upx;
|
|
|
|
|
+ color: #999;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.list-end {
|
|
|
|
|
+ padding: 30upx 0 20upx;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ font-size: 24upx;
|
|
|
|
|
+ color: #bbb;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 筛选弹窗 */
|
|
|
|
|
+.filter-panel {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ height: 80vh;
|
|
|
|
|
+ max-height: 1100upx;
|
|
|
|
|
+ background: #f5f5f5;
|
|
|
|
|
+ border-radius: 24upx 24upx 0 0;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.filter-title {
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ padding: 28upx 0 16upx;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ font-size: 32upx;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+ background: #f5f5f5;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.filter-scroll {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ height: 0;
|
|
|
|
|
+ padding: 0 24upx;
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.filter-section {
|
|
|
|
|
+ margin-bottom: 20upx;
|
|
|
|
|
+ padding: 24upx;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border-radius: 16upx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.section-label {
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ margin-bottom: 20upx;
|
|
|
|
|
+ font-size: 28upx;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.option-grid {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ margin: 0 -8upx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.option-item {
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+ width: calc(33.33% - 16upx);
|
|
|
|
|
+ margin: 0 8upx 16upx;
|
|
|
|
|
+ padding: 16upx 8upx;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ font-size: 24upx;
|
|
|
|
|
+ color: #333;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border: 1upx solid #e5e5e5;
|
|
|
|
|
+ border-radius: 12upx;
|
|
|
|
|
+
|
|
|
|
|
+ &.active {
|
|
|
|
|
+ color: #FF4D6D;
|
|
|
|
|
+ border-color: #FF4D6D;
|
|
|
|
|
+ background: rgba(255, 77, 109, 0.06);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.option-grid.cols-4 .option-item {
|
|
|
|
|
+ width: calc(25% - 16upx);
|
|
|
|
|
+ font-size: 22upx;
|
|
|
|
|
+ padding: 16upx 4upx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.option-empty {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ padding: 20upx 0;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ font-size: 24upx;
|
|
|
|
|
+ color: #999;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.custom-price-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ margin-top: 8upx;
|
|
|
|
|
+ padding: 0 16upx;
|
|
|
|
|
+ height: 72upx;
|
|
|
|
|
+ border: 1upx solid #e5e5e5;
|
|
|
|
|
+ border-radius: 12upx;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.price-input {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ font-size: 26upx;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.price-dash {
|
|
|
|
|
+ margin: 0 12upx;
|
|
|
|
|
+ color: #999;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.filter-scroll-space {
|
|
|
|
|
+ height: 20upx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.filter-footer {
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ padding: 20upx 30upx;
|
|
|
|
|
+ padding-bottom: calc(20upx + env(safe-area-inset-bottom));
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.confirm-btn {
|
|
|
|
|
+ height: 88upx;
|
|
|
|
|
+ line-height: 88upx;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ font-size: 32upx;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ background: linear-gradient(135deg, #FF8FA3, #FF4D6D);
|
|
|
|
|
+ border-radius: 44upx;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|