| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579 |
- <!-- 商品列表页面 -->
- <template>
- <view class="list-page">
- <view class="list-head" :style="{ paddingTop: statusBarH + 'px' }">
- <view class="head-bar">
- <view class="head-left" @tap="goBack">
- <text v-if="pagesNum > 1" class="wlIcon wlIcon-fanhui1 back-icon"></text>
- <text v-else class="wlIcon wlIcon-zhuye home-icon"></text>
- </view>
- <view class="head-search" @tap="goSearch">
- <text class="wlIcon wlIcon-sousuo1 search-icon"></text>
- <view class="search-tag" v-if="category">
- <text>{{ $t('mall.category.label') }}:{{ category }}</text>
- </view>
- <view class="search-tag" v-else-if="params.search">
- <text>{{ params.search }}</text>
- </view>
- <text v-else class="search-placeholder">{{ $t('mall.searchPlaceholder') }}</text>
- </view>
- <view class="head-right" @tap="toggleStyle">
- <text v-if="listStyle === 'col-2-20'" class="wlIcon wlIcon-liebiao2 style-icon"></text>
- <text v-else class="wlIcon wlIcon-liebiao style-icon"></text>
- </view>
- </view>
- </view>
- <view class="list-head-placeholder" :style="{ height: (statusBarH + 44) + 'px' }"></view>
- <view class="sort-bar">
- <view class="sort-item" :class="{ active: filterIndex === 0 }" @tap="tabClick(0)"><text>{{ $t('mall.sort.comprehensive') }}</text></view>
- <view class="sort-item" :class="{ active: filterIndex === 1 }" @tap="tabClick(1)"><text>{{ $t('mall.sort.sales') }}</text></view>
- <view class="sort-item" :class="{ active: filterIndex === 2 }" @tap="tabClick(2)"><text>{{ $t('mall.sort.new') }}</text></view>
- <view class="sort-item" :class="{ active: filterIndex === 3 }" @tap="tabClick(3)">
- <text>{{ $t('mall.sort.price') }}</text>
- <view class="price-arrow">
- <text class="arrow-up" :class="{ active: priceOrder === 1 && filterIndex === 3 }">▲</text>
- <text class="arrow-down" :class="{ active: priceOrder === 2 && filterIndex === 3 }">▼</text>
- </view>
- </view>
- <view class="sort-item" @tap="showDrawer"><text>{{ $t('mall.sort.filter') }}</text><text class="wlIcon wlIcon-shaixuan filter-icon"></text>
- </view>
- </view>
- <view class="list-body" v-if="goodsData.length > 0">
- <mall-product :dataList="goodsData" :dataStyle="listStyle" />
- </view>
- <view class="list-empty" v-if="goodsData.length === 0 && !loading">
- <text>{{ $t('product.noResults') }}</text>
- </view>
- <view class="list-like" v-if="goodsData.length === 0 && likeData.length > 0">
- <view class="like-divider">
- <view class="divider-line"></view>
- <text class="divider-text">{{ $t('product.guessYouLike') }}</text>
- <view class="divider-line"></view>
- </view>
- <mall-product :dataList="likeData" dataStyle="col-2-20" />
- </view>
- <view class="load-more" v-if="goodsData.length > 0">
- <text v-if="loading">{{ $t('common.loading') }}</text>
- <text v-else-if="params.page >= lastPage">{{ $t('common.noMore') }}</text>
- </view>
- <!-- 筛选抽屉 -->
- <view class="drawer-mask" v-if="showRight" @tap="closeDrawer"></view>
- <view class="drawer" :class="{ show: showRight }">
- <scroll-view scroll-y class="drawer-scroll" :style="{ height: drawerHeight + 'px', paddingTop: statusBarH + 'px' }">
- <view class="drawer-section" v-if="drawerData.brand.length > 0">
- <view class="drawer-title">{{ $t('product.brand') }}</view>
- <view class="drawer-tags">
- <text v-for="(bd, index) in drawerData.brand" :key="bd.id" :class="{ active: bd.choice }"
- @tap="onDrawerBrand(index)">{{ bd.name }}</text>
- </view>
- </view>
- <view class="drawer-section">
- <view class="drawer-title">{{ $t('product.priceRange') }}</view>
- <view class="drawer-price">
- <input type="number" :placeholder="$t('product.minPrice')" v-model="drawerData.price.low" />
- <text>—</text>
- <input type="number" :placeholder="$t('product.maxPrice')" v-model="drawerData.price.high" />
- </view>
- </view>
- <view class="drawer-section" v-for="(att, index) in drawerData.attribute" :key="att.name">
- <view class="drawer-title" @tap="toggleAttribute(index)">
- <text>{{ att.name }}</text>
- <text>{{ att.fold ? '▲' : '▼' }}</text>
- </view>
- <view class="drawer-tags" v-if="att.fold">
- <text v-for="(val, key) in att.value" :key="val.name" :class="{ active: val.choice }"
- @tap="onDrawerAttribute(index, key)">{{ val.name }}</text>
- </view>
- </view>
- </scroll-view>
- <view class="drawer-foot">
- <view class="drawer-btn reset" @tap="onDrawerReset">{{ $t('common.reset') }}</view>
- <view class="drawer-btn confirm" @tap="onDrawerTo">{{ $t('common.confirm') }}</view>
- </view>
- </view>
- </view>
- </template>
- <script>
- import { pullMallProductPage, pullMallFilterDrawer } from '@/features/mall/mall-gateway'
- export default {
- data() {
- return {
- statusBarH: 0,
- pagesNum: 0,
- filterIndex: 0,
- priceOrder: 0,
- listStyle: 'col-2-20',
- goodsData: [],
- likeData: [],
- loading: false,
- category: '',
- lastPage: 0,
- showRight: false,
- drawerLoaded: false,
- drawerHeight: 500,
- params: {
- search: '',
- sort: 'weigh',
- order: 'asc',
- page: 1,
- filter: {},
- op: {}
- },
- drawerData: {
- price: { low: '', high: '' },
- brand: [],
- attribute: []
- },
- drawerParams: {}
- }
- },
- onLoad(option) {
- this.statusBarH = uni.getSystemInfoSync().statusBarHeight || 0
- this.drawerHeight = uni.getSystemInfoSync().windowHeight - 100
- this.pagesNum = getCurrentPages().length
- if (option.search) {
- this.params.search = option.search
- this.drawerParams.search = option.search
- } else if (option.data) {
- try {
- const data = JSON.parse(decodeURIComponent(option.data))
- this.drawerParams.category_id = data.category_id
- this.category = data.category_name
- this.params.filter.category_id = data.category_id
- this.params.op.category_id = 'in'
- } catch (e) {
- console.error('解析分类数据失败:', e)
- }
- }
- this.$nextTick(() => {
- this.loadData()
- })
- },
- onReachBottom() {
- if (this.params.page >= this.lastPage) return
- this.params.page++
- this.loading = true
- this.loadData(true)
- },
- methods: {
- async loadData(append = false) {
- this.loading = true
- try {
- const res = await pullMallProductPage({
- type: 'goods',
- page: this.params.page,
- search: this.params.search,
- sort: this.params.sort,
- order: this.params.order,
- filter: this.params.filter,
- op: this.params.op
- })
- this.goodsData = append ? this.goodsData.concat(res.rows) : res.rows
- this.lastPage = res.lastPage
- if (this.goodsData.length === 0) {
- this.loadLikeData()
- }
- } catch (e) {
- console.error('加载商品失败:', e)
- } finally {
- this.loading = false
- }
- },
- async loadLikeData() {
- try {
- const res = await pullMallProductPage({ type: 'goods', page: 1 })
- this.likeData = res.rows
- } catch (e) {
- console.error('加载推荐失败:', e)
- }
- },
- async loadDrawer() {
- try {
- this.drawerData = await pullMallFilterDrawer({
- type: 'goods',
- search: this.drawerParams.search,
- category_id: this.drawerParams.category_id
- })
- } catch (e) {
- console.error('加载筛选条件失败:', e)
- }
- },
- tabClick(index) {
- if (this.filterIndex === index && index !== 3) return
- this.filterIndex = index
- if (index === 3) {
- this.priceOrder = this.priceOrder === 1 ? 2 : 1
- } else {
- this.priceOrder = 0
- }
- if (index === 0) { this.params.sort = 'weigh'; this.params.order = 'desc' }
- if (index === 1) { this.params.sort = 'sales'; this.params.order = 'desc' }
- if (index === 2) { this.params.sort = 'createtime'; this.params.order = 'desc' }
- if (index === 3 && this.priceOrder === 1) { this.params.sort = 'price'; this.params.order = 'desc' }
- if (index === 3 && this.priceOrder === 2) { this.params.sort = 'price'; this.params.order = 'asc' }
- this.params.page = 1
- this.loadData()
- uni.pageScrollTo({ duration: 300, scrollTop: 0 })
- },
- showDrawer() {
- this.showRight = true
- if (!this.drawerLoaded) {
- this.loadDrawer()
- this.drawerLoaded = true
- }
- },
- closeDrawer() {
- this.showRight = false
- },
- onDrawerBrand(index) {
- this.drawerData.brand.forEach((item, i) => {
- if (i !== index) item.choice = false
- })
- this.drawerData.brand[index].choice = !this.drawerData.brand[index].choice
- },
- onDrawerAttribute(attrIdx, valIdx) {
- this.drawerData.attribute[attrIdx].value.forEach((item, i) => {
- if (i !== valIdx) item.choice = false
- })
- this.drawerData.attribute[attrIdx].value[valIdx].choice = !this.drawerData.attribute[attrIdx].value[valIdx].choice
- },
- toggleAttribute(index) {
- this.drawerData.attribute[index].fold = !this.drawerData.attribute[index].fold
- },
- onDrawerTo() {
- if (this.drawerData.price.low !== '' && this.drawerData.price.high !== '') {
- this.params.filter.price = this.drawerData.price.low + ',' + this.drawerData.price.high
- this.params.op.price = 'BETWEEN'
- } else {
- delete this.params.filter.price
- delete this.params.op.price
- }
- var brandData = ''
- this.drawerData.brand.forEach(item => {
- if (item.choice) brandData = item.id
- })
- if (brandData) {
- this.params.filter.brand_id = brandData
- this.params.op.brand_id = '='
- } else {
- delete this.params.filter.brand_id
- delete this.params.op.brand_id
- }
- var attributeData = []
- this.drawerData.attribute.forEach(item => {
- item.value.forEach(vo => {
- if (vo.choice) attributeData.push('%' + vo.name + '%')
- })
- })
- if (attributeData.length > 0) {
- this.params.filter.category_attribute = attributeData.join(',')
- this.params.op.category_attribute = 'LIKE'
- } else {
- delete this.params.filter.category_attribute
- delete this.params.op.category_attribute
- }
- this.goodsData = []
- this.params.page = 1
- this.loadData()
- this.closeDrawer()
- },
- onDrawerReset() {
- this.loadDrawer()
- },
- goSearch() {
- uni.navigateTo({ url: '/pages/page/search?type=goods' })
- },
- toggleStyle() {
- this.listStyle = this.listStyle === 'col-2-20' ? 'col-1-20' : 'col-2-20'
- },
- goBack() {
- if (this.pagesNum > 1) {
- uni.navigateBack({ delta: 1 })
- } else {
- uni.switchTab({ url: '/pages/index/index' })
- }
- }
- }
- }
- </script>
- <style scoped>
- .list-page {
- min-height: 100vh;
- background: #f5f5f5;
- }
- .list-head {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- background: #fff;
- z-index: 999;
- }
- .head-bar {
- display: flex;
- align-items: center;
- height: 44px;
- padding: 0 20rpx;
- }
- .head-left {
- width: 60rpx;
- }
- .back-icon {
- font-size: 40rpx;
- color: #333;
- }
- .home-icon {
- font-size: 40rpx;
- color: #333;
- }
- .head-search {
- flex: 1;
- display: flex;
- align-items: center;
- height: 64rpx;
- background: #fff;
- border: 2rpx solid #fe6600;
- border-radius: 32rpx;
- padding: 0 20rpx;
- }
- .search-icon {
- font-size: 32rpx;
- margin-right: 10rpx;
- color: #999;
- }
- .search-tag {
- font-size: 24rpx;
- color: #333;
- }
- .search-placeholder {
- font-size: 26rpx;
- color: #999;
- }
- .head-right {
- width: 60rpx;
- text-align: center;
- }
- .style-icon {
- font-size: 40rpx;
- color: #333;
- }
- .list-head-placeholder {
- width: 100%;
- }
- .sort-bar {
- display: flex;
- align-items: center;
- height: 80rpx;
- background: #fff;
- border-bottom: 1rpx solid #f0f0f0;
- }
- .sort-item {
- flex: 1;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 26rpx;
- color: #666;
- }
- .sort-item.active {
- color: #e54d42;
- font-weight: bold;
- }
- .price-arrow {
- display: flex;
- flex-direction: column;
- margin-left: 4rpx;
- }
- .arrow-up,
- .arrow-down {
- font-size: 16rpx;
- line-height: 16rpx;
- color: #ccc;
- }
- .arrow-up.active,
- .arrow-down.active {
- color: #e54d42;
- }
- .filter-icon {
- font-size: 28rpx;
- margin-left: 4rpx;
- color: #999;
- }
- .list-body {
- padding: 16rpx;
- }
- .list-empty {
- text-align: center;
- padding: 100rpx 0;
- color: #999;
- font-size: 28rpx;
- }
- .list-like {
- padding: 0 16rpx;
- }
- .like-divider {
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 30rpx 0 10rpx;
- }
- .divider-line {
- width: 60rpx;
- height: 2rpx;
- background: #ddd;
- }
- .divider-text {
- margin: 0 24rpx;
- font-size: 26rpx;
- color: #999;
- }
- .load-more {
- text-align: center;
- padding: 20rpx 0;
- font-size: 24rpx;
- color: #999;
- }
- .drawer-mask {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: rgba(0, 0, 0, 0.5);
- z-index: 1000;
- }
- .drawer {
- position: fixed;
- top: 0;
- right: 0;
- bottom: 0;
- width: 600rpx;
- background: #fff;
- z-index: 1001;
- transform: translateX(100%);
- transition: transform 0.3s;
- }
- .drawer.show {
- transform: translateX(0);
- }
- .drawer-scroll {
- padding: 20rpx 30rpx;
- }
- .drawer-section {
- padding: 20rpx 0;
- border-bottom: 1rpx solid #f0f0f0;
- }
- .drawer-title {
- display: flex;
- justify-content: space-between;
- font-size: 28rpx;
- font-weight: bold;
- color: #333;
- margin-bottom: 16rpx;
- }
- .drawer-tags {
- display: flex;
- flex-wrap: wrap;
- gap: 12rpx;
- }
- .drawer-tags text {
- padding: 8rpx 20rpx;
- background: #f5f5f5;
- border-radius: 8rpx;
- font-size: 24rpx;
- color: #666;
- }
- .drawer-tags text.active {
- background: #fff2f0;
- color: #e54d42;
- border: 1rpx solid #e54d42;
- }
- .drawer-price {
- display: flex;
- align-items: center;
- gap: 16rpx;
- }
- .drawer-price input {
- flex: 1;
- height: 64rpx;
- border: 1rpx solid #ddd;
- border-radius: 8rpx;
- padding: 0 16rpx;
- font-size: 24rpx;
- }
- .drawer-price text {
- color: #999;
- }
- .drawer-foot {
- display: flex;
- padding: 20rpx 30rpx;
- padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
- border-top: 1rpx solid #f0f0f0;
- }
- .drawer-btn {
- flex: 1;
- text-align: center;
- height: 80rpx;
- line-height: 80rpx;
- font-size: 28rpx;
- font-weight: bold;
- border-radius: 40rpx;
- }
- .drawer-btn.reset {
- background: linear-gradient(to right, #f0c819, #f37b1d);
- color: #fff;
- margin-right: 16rpx;
- }
- .drawer-btn.confirm {
- background: linear-gradient(to right, #f37b1d, #e54d42);
- color: #fff;
- }
- </style>
|