本次新增由后台 virtualPayAudit 开关控制的审核模式,在该模式下: 1. 隐藏底部 Tab 栏的发布按钮 2. 隐藏空状态、分类页、个人页的发布入口 3. 隐藏帖子卡片、详情页、个人发布页的评论数及评价入口 4. 发布页在审核模式下直接拦截并跳转至首页 5. 详情页的用户评价及评价弹层在审核模式下隐藏,避免 UGC 判定
764 行
21 KiB
Vue
764 行
21 KiB
Vue
<template>
|
||
<view class="list fc-page">
|
||
<!-- 规范化顶部导航 -->
|
||
<fc-navbar :title="pageTitle || '分类信息'" theme="gradient" :show-back="true" />
|
||
|
||
<!-- 二级分类实拍图横滑筛选:全部按钮常驻左侧,细分品类横向居中滑动 -->
|
||
<view v-if="subCategories.length" class="list__subcats-bar">
|
||
<!-- 1. 全部按钮:固定在左侧 -->
|
||
<view class="list__subcats-fixed">
|
||
<view
|
||
class="list__subcat"
|
||
:class="{ 'list__subcat--active': !subCategoryId }"
|
||
@click="changeSubCategory(null)"
|
||
>
|
||
<view class="list__subcat-thumb">
|
||
<image v-if="currentParentCat" class="list__subcat-img" :src="catIcon(currentParentCat)" mode="aspectFit" />
|
||
</view>
|
||
<text class="list__subcat-name">全部</text>
|
||
</view>
|
||
<view class="list__subcats-divider"></view>
|
||
</view>
|
||
|
||
<!-- 2. 二级分类项:横向滚动,支持自动居中 -->
|
||
<scroll-view
|
||
class="list__subcats-scroll"
|
||
scroll-x
|
||
:scroll-left="subCatScrollLeft"
|
||
:scroll-with-animation="canAnimateScroll"
|
||
:show-scrollbar="false"
|
||
@scroll="onSubCatScroll"
|
||
>
|
||
<view class="list__subcats-inner">
|
||
<view
|
||
v-for="sub in subCategories"
|
||
:key="sub.id"
|
||
:id="'subcat-item-' + sub.id"
|
||
class="list__subcat"
|
||
:class="{ 'list__subcat--active': Number(subCategoryId) === Number(sub.id) }"
|
||
@click="changeSubCategory(sub)"
|
||
>
|
||
<view class="list__subcat-thumb">
|
||
<image class="list__subcat-img" :src="catIcon(sub)" mode="aspectFill" />
|
||
</view>
|
||
<text class="list__subcat-name">{{ sub.name }}</text>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
|
||
<!-- 分类横滑:仅在未指定具体分类的全量列表场景下显示 -->
|
||
<scroll-view v-else-if="!isSpecificCategory" class="list__cats" scroll-x :show-scrollbar="false">
|
||
<view class="list__cats-inner">
|
||
<view v-for="c in categories" :key="c.id" class="list__cat"
|
||
:class="{ 'list__cat--active': categoryId === c.id }" @click="changeCategory(c)">
|
||
<image class="list__cat-icon" :src="catIcon(c)" mode="aspectFit" />
|
||
<text>{{ c.name }}</text>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<!-- 多维筛选 -->
|
||
<view class="list__filters">
|
||
<view v-for="s in sorts" :key="s.value" class="list__filter"
|
||
:class="{ 'list__filter--active': sort === s.value }" @click="changeSort(s.value)">
|
||
<text>{{ s.label }}</text>
|
||
<!-- 价格排序双三角:升/降序切换指示(主流交互) -->
|
||
<view v-if="s.value === 'price'" class="list__caret">
|
||
<view class="list__caret-up" :class="{ 'is-on': sort === 'price' && priceOrder === 'asc' }"></view>
|
||
<view class="list__caret-down" :class="{ 'is-on': sort === 'price' && priceOrder === 'desc' }"></view>
|
||
</view>
|
||
</view>
|
||
<view class="list__filter-area-btn" @click="openAreaPicker">
|
||
<image class="list__filter-area-icon" :src="iconUrl('home-location')" mode="aspectFit" />
|
||
<text class="list__filter-area-txt fc-ellipsis">{{ areaDisplay }}</text>
|
||
<text class="list__filter-area-arrow">▾</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 骨架屏加载 -->
|
||
<view v-if="loading && !list.length" class="list__loading">
|
||
<view v-for="i in 3" :key="i" class="fc-skeleton list__skeleton"></view>
|
||
</view>
|
||
|
||
<fc-empty v-else-if="!list.length" text="该分类下暂无信息" :btn-text="isAudit ? '' : '发布一条'" @action="goCreate" />
|
||
|
||
<view v-else>
|
||
<fc-post-card v-for="p in list" :key="p.id" :post="p" @click="goDetail" />
|
||
<view class="list__more">
|
||
<text v-if="loading">加载中…</text>
|
||
<text v-else-if="!hasMore">— 没有更多了 —</text>
|
||
<text v-else @click="loadMore">加载更多</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="!isAudit" class="list__fab" @click="goCreate">
|
||
<text>+ 发布</text>
|
||
</view>
|
||
|
||
<!-- 城市/区域选择抽屉(省/全国 → 市 两列级联,与首页一致) -->
|
||
<fc-city-picker
|
||
:visible="showAreaPicker"
|
||
:areas="areaRows"
|
||
:province="selectedProvince"
|
||
:city="selectedCity"
|
||
@select="onAreaSelect"
|
||
@close="closeAreaPicker"
|
||
/>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import FcNavbar from '@/components/fc-navbar/fc-navbar.vue'
|
||
import FcPostCard from '@/components/fc-post-card/fc-post-card.vue'
|
||
import FcEmpty from '@/components/fc-empty/fc-empty.vue'
|
||
import FcCityPicker from '@/components/fc-city-picker/fc-city-picker.vue'
|
||
import homeApi from '@/common/api/home.js'
|
||
import { iconUrl, categoryIconUrl, catIcon, loadUiConfig, uiState } from '@/common/store/ui.js'
|
||
|
||
export default {
|
||
components: { FcNavbar, FcPostCard, FcEmpty, FcCityPicker },
|
||
data() {
|
||
return {
|
||
categories: [],
|
||
categoryId: 0,
|
||
subCategoryId: 0,
|
||
// 固定具体分类ID(由路由传入,用于区分是否是进入具体分类列表)
|
||
fixedCategoryId: 0,
|
||
pageTitle: '',
|
||
list: [],
|
||
page: 1,
|
||
size: 10,
|
||
hasMore: true,
|
||
loading: false,
|
||
// 请求序号:快速切换分类/排序/区域时丢弃过期响应
|
||
reqSeq: 0,
|
||
sort: 'new',
|
||
// 价格排序方向(asc 升序 / desc 降序),点击已选中的「价格」时切换
|
||
priceOrder: 'asc',
|
||
// 区域筛选:只到市级(全国/省/市),与首页口径一致
|
||
selectedProvince: '',
|
||
selectedCity: '',
|
||
showAreaPicker: false,
|
||
manualArea: false,
|
||
lat: null,
|
||
lng: null,
|
||
// 平铺服务区域(每条自带省/市/区县)
|
||
areaRows: [],
|
||
sorts: [
|
||
{ label: '最新', value: 'new' },
|
||
{ label: '最热', value: 'hot' },
|
||
{ label: '价格', value: 'price' },
|
||
],
|
||
// 二级分类横向滚动与居中控制
|
||
subCatScrollLeft: 0,
|
||
currentScrollLeft: 0,
|
||
canAnimateScroll: false,
|
||
}
|
||
},
|
||
computed: {
|
||
/**
|
||
* 是否处于提审防审模式(由后台 virtualPayAudit 开关统一控制)
|
||
* @returns {boolean} true=审核中(隐藏发布悬浮球与引导)
|
||
*/
|
||
isAudit() {
|
||
return uiState.virtualPayAudit !== false
|
||
},
|
||
/**
|
||
* 判断是否为具体分类展示模式
|
||
* @returns {boolean}
|
||
*/
|
||
isSpecificCategory() {
|
||
return !!this.fixedCategoryId
|
||
},
|
||
/**
|
||
* 当前大类对象
|
||
*/
|
||
currentParentCat() {
|
||
return (this.categories || []).find((c) => Number(c.id) === Number(this.categoryId)) || null
|
||
},
|
||
/**
|
||
* 当前大类下的二级分类列表
|
||
*/
|
||
subCategories() {
|
||
return (this.currentParentCat && Array.isArray(this.currentParentCat.children)) ? this.currentParentCat.children : []
|
||
},
|
||
/**
|
||
* 筛选栏区域文案(城市 > 省份 > 缺省「区域」)
|
||
*/
|
||
areaDisplay() {
|
||
if (this.selectedCity) return this.selectedCity
|
||
if (this.selectedProvince) return this.selectedProvince
|
||
return '区域'
|
||
},
|
||
},
|
||
/**
|
||
* 页面加载:读取分类入参并彻底防止 URL 编码乱码
|
||
* @param {Object} query 路由参数
|
||
*/
|
||
onLoad(query) {
|
||
this.categoryId = Number(query.categoryId || 0)
|
||
this.fixedCategoryId = Number(query.categoryId || 0)
|
||
if (query.subCategoryId) {
|
||
this.subCategoryId = Number(query.subCategoryId || 0)
|
||
}
|
||
if (query.name) {
|
||
const decoded = this.safeDecode(query.name)
|
||
this.pageTitle = decoded
|
||
uni.setNavigationBarTitle({ title: decoded })
|
||
}
|
||
loadUiConfig()
|
||
this.init()
|
||
},
|
||
onReachBottom() {
|
||
this.loadMore()
|
||
},
|
||
methods: {
|
||
iconUrl,
|
||
categoryIconUrl,
|
||
catIcon,
|
||
/**
|
||
* 彻底安全解码 URL 参数(防一层、二层及多层 URL 编码导致的 %E6%95%... 乱码)
|
||
* @param {string} str 待解码的字符串
|
||
* @returns {string} 解码后的文本
|
||
*/
|
||
safeDecode(str) {
|
||
let res = String(str || '').trim()
|
||
try {
|
||
while (/%[0-9a-fA-F]{2}/.test(res)) {
|
||
const next = decodeURIComponent(res)
|
||
if (next === res) break
|
||
res = next
|
||
}
|
||
} catch (e) {}
|
||
return res
|
||
},
|
||
/**
|
||
* 页面初始化:拉取分类与区域筛选项,并执行首次信息流拉取
|
||
*/
|
||
async init() {
|
||
try {
|
||
const cats = await homeApi.categories()
|
||
this.categories = Array.isArray(cats) ? cats : []
|
||
if (this.fixedCategoryId || this.categoryId) {
|
||
let current = this.categories.find((c) => Number(c.id) === Number(this.categoryId))
|
||
if (!current) {
|
||
for (const p of this.categories) {
|
||
const sub = (p.children || []).find((s) => Number(s.id) === Number(this.categoryId))
|
||
if (sub) {
|
||
current = p
|
||
this.categoryId = Number(p.id)
|
||
this.subCategoryId = Number(sub.id)
|
||
break
|
||
}
|
||
}
|
||
}
|
||
// 若指定了 subCategoryId 但与当前大类不匹配,反查所属父级
|
||
if (this.subCategoryId && (!current || !(current.children || []).some((s) => Number(s.id) === Number(this.subCategoryId)))) {
|
||
for (const p of this.categories) {
|
||
const sub = (p.children || []).find((s) => Number(s.id) === Number(this.subCategoryId))
|
||
if (sub) {
|
||
current = p
|
||
this.categoryId = Number(p.id)
|
||
break
|
||
}
|
||
}
|
||
}
|
||
if (current && (!this.pageTitle || /%[0-9a-fA-F]{2}/.test(this.pageTitle))) {
|
||
this.pageTitle = current.name
|
||
uni.setNavigationBarTitle({ title: current.name })
|
||
}
|
||
}
|
||
// 若存在已选中的二级分类,执行居中滚动定位
|
||
if (this.subCategoryId) {
|
||
this.scrollToActiveSubCategory(false)
|
||
}
|
||
} catch (e) {}
|
||
// 服务区域:默认开放全国省/市/区县
|
||
try {
|
||
const rows = await homeApi.areas()
|
||
this.areaRows = Array.isArray(rows) ? rows : []
|
||
} catch (e) {}
|
||
this.fetch(true)
|
||
// LBS:按定位匹配默认区域(失败/无权限静默,不阻塞列表)
|
||
this.fetchLocation()
|
||
},
|
||
/**
|
||
* 取用户坐标并匹配区域(gcj02)
|
||
*/
|
||
fetchLocation() {
|
||
if (typeof uni === 'undefined' || !uni.getLocation) return
|
||
uni.getLocation({
|
||
type: 'gcj02',
|
||
success: (res) => {
|
||
this.lat = res.latitude
|
||
this.lng = res.longitude
|
||
this.applyLocationArea().finally(() => {
|
||
this.page = 1
|
||
this.fetch(true)
|
||
})
|
||
},
|
||
fail: () => {},
|
||
})
|
||
},
|
||
/**
|
||
* 把定位坐标转换成区域筛选默认值(只匹配到市级,用户手动切换过则不覆盖)
|
||
*/
|
||
async applyLocationArea() {
|
||
if (this.manualArea || this.lat == null || this.lng == null) return
|
||
try {
|
||
const found = await homeApi.locate({ lat: this.lat, lng: this.lng })
|
||
if (found && found.areaId) {
|
||
this.selectedProvince = found.province || ''
|
||
this.selectedCity = found.city || ''
|
||
}
|
||
} catch (e) {}
|
||
},
|
||
/**
|
||
* 分页获取分类信息列表
|
||
* @param {boolean} reset 是否重置分页重新拉取第一页
|
||
*/
|
||
async fetch(reset = false) {
|
||
const seq = ++this.reqSeq
|
||
this.loading = true
|
||
try {
|
||
const res = await homeApi.feed({
|
||
page: this.page,
|
||
size: this.size,
|
||
categoryId: this.categoryId || undefined,
|
||
subCategoryId: this.subCategoryId || undefined,
|
||
// 价格排序带方向:price_asc / price_desc(后端按方向排序)
|
||
sort: this.sort === 'price' ? `price_${this.priceOrder}` : this.sort,
|
||
// 区域筛选只到市级:城市优先,其次省份
|
||
province: !this.selectedCity && this.selectedProvince ? this.selectedProvince : undefined,
|
||
city: this.selectedCity || undefined,
|
||
})
|
||
// 期间又切换了筛选条件:以最后一次请求为准
|
||
if (seq !== this.reqSeq) return
|
||
this.list = reset ? res.list : this.list.concat(res.list)
|
||
this.hasMore = res.hasMore
|
||
} catch (e) {
|
||
if (seq !== this.reqSeq) return
|
||
uni.showToast({ title: '加载失败', icon: 'none' })
|
||
} finally {
|
||
if (seq === this.reqSeq) this.loading = false
|
||
}
|
||
},
|
||
/**
|
||
* 滚动触底加载下一页数据
|
||
*/
|
||
loadMore() {
|
||
if (!this.hasMore || this.loading) return
|
||
this.page += 1
|
||
this.fetch()
|
||
},
|
||
/**
|
||
* 切换当前选中的分类(全量列表视角)
|
||
* @param {Object} c 选中的分类项
|
||
*/
|
||
changeCategory(c) {
|
||
this.categoryId = this.categoryId === c.id ? 0 : c.id
|
||
this.subCategoryId = 0
|
||
this.page = 1
|
||
this.fetch(true)
|
||
},
|
||
/**
|
||
* 监听二级分类栏横向滚动事件,保持当前滚动位移与组件状态同步
|
||
* @param {Object} e 滚动事件对象
|
||
*/
|
||
onSubCatScroll(e) {
|
||
if (e && e.detail) {
|
||
this.currentScrollLeft = e.detail.scrollLeft || 0
|
||
}
|
||
},
|
||
/**
|
||
* 滚动选中的二级分类项至视口中央(兼顾左右边界保护)
|
||
* @param {boolean} withAnimation 是否带平滑滚动动画
|
||
*/
|
||
scrollToActiveSubCategory(withAnimation = true) {
|
||
if (!this.subCategoryId) {
|
||
this.canAnimateScroll = withAnimation
|
||
this.subCatScrollLeft = 0
|
||
return
|
||
}
|
||
const executeScroll = (retryCount = 0) => {
|
||
const query = uni.createSelectorQuery().in(this)
|
||
query.select('.list__subcats-scroll').boundingClientRect()
|
||
query.select(`#subcat-item-${this.subCategoryId}`).boundingClientRect()
|
||
query.exec((res) => {
|
||
if (!res || !res[0] || !res[1]) {
|
||
if (retryCount < 5) {
|
||
setTimeout(() => executeScroll(retryCount + 1), 60)
|
||
}
|
||
return
|
||
}
|
||
const scrollRect = res[0]
|
||
const itemRect = res[1]
|
||
|
||
// 滚动容器可视宽度与左边距
|
||
const containerWidth = scrollRect.width
|
||
const containerLeft = scrollRect.left
|
||
|
||
// 选中项宽度与左边距
|
||
const itemWidth = itemRect.width
|
||
const itemLeft = itemRect.left
|
||
|
||
// 选中项相对于滚动容器可视左边界的实时距离
|
||
const relativeLeft = itemLeft - containerLeft
|
||
|
||
// 居中计算:item 中心对齐 container 可视中心
|
||
// 偏移增量 = relativeLeft + itemWidth / 2 - containerWidth / 2
|
||
// 新目标 scrollLeft = 当前已滚动位置 + 偏移增量
|
||
let targetScrollLeft = this.currentScrollLeft + relativeLeft + (itemWidth / 2) - (containerWidth / 2)
|
||
|
||
// 边界处理:左边界保护(若小于0则靠左展示)
|
||
if (targetScrollLeft < 0) {
|
||
targetScrollLeft = 0
|
||
}
|
||
|
||
this.canAnimateScroll = withAnimation
|
||
if (Math.abs(this.subCatScrollLeft - targetScrollLeft) < 1) {
|
||
this.subCatScrollLeft = targetScrollLeft + 0.1
|
||
} else {
|
||
this.subCatScrollLeft = targetScrollLeft
|
||
}
|
||
})
|
||
}
|
||
|
||
this.$nextTick(() => {
|
||
setTimeout(() => {
|
||
executeScroll()
|
||
}, 60)
|
||
})
|
||
},
|
||
/**
|
||
* 切换二级分类筛选项
|
||
* @param {Object|null} sub 二级分类
|
||
*/
|
||
changeSubCategory(sub) {
|
||
const nextId = sub ? Number(sub.id) : 0
|
||
if (this.subCategoryId === nextId) return
|
||
this.subCategoryId = nextId
|
||
this.page = 1
|
||
this.fetch(true)
|
||
this.scrollToActiveSubCategory(true)
|
||
},
|
||
/**
|
||
* 切换排序方式(价格项:首次点击升序,再次点击升降序来回切换)
|
||
* @param {string} v 排序字段(new/hot/price)
|
||
*/
|
||
changeSort(v) {
|
||
if (v === 'price') {
|
||
if (this.sort === 'price') {
|
||
this.priceOrder = this.priceOrder === 'asc' ? 'desc' : 'asc'
|
||
} else {
|
||
this.priceOrder = 'asc'
|
||
}
|
||
} else if (this.sort === v) {
|
||
return
|
||
}
|
||
this.sort = v
|
||
this.page = 1
|
||
this.fetch(true)
|
||
},
|
||
/**
|
||
* 打开城市选择抽屉
|
||
*/
|
||
openAreaPicker() {
|
||
this.showAreaPicker = true
|
||
},
|
||
/**
|
||
* 关闭城市选择抽屉
|
||
*/
|
||
closeAreaPicker() {
|
||
this.showAreaPicker = false
|
||
},
|
||
/**
|
||
* 城市选择抽屉回调(全国/全省/全市统一入口)
|
||
* @param {Object} area { province, city },空串表示全国/全省
|
||
*/
|
||
onAreaSelect(area) {
|
||
this.selectedProvince = (area && area.province) || ''
|
||
this.selectedCity = (area && area.city) || ''
|
||
this.manualArea = true
|
||
this.showAreaPicker = false
|
||
this.page = 1
|
||
this.fetch(true)
|
||
},
|
||
/**
|
||
* 跳转到信息详情页
|
||
* @param {Object} p 信息项
|
||
*/
|
||
goDetail(p) {
|
||
uni.navigateTo({ url: `/pages/detail/detail?id=${p.id}` })
|
||
},
|
||
/**
|
||
* 跳转到信息发布页(审核模式防穿透拦截,自动携带当前选中的大类与二级分类参数)
|
||
*/
|
||
goCreate() {
|
||
if (this.isAudit) return
|
||
const targetCatId = Number(this.categoryId || this.fixedCategoryId || 0)
|
||
const targetSubCatId = Number(this.subCategoryId || 0)
|
||
const params = []
|
||
if (targetCatId > 0) {
|
||
params.push(`categoryId=${targetCatId}`)
|
||
}
|
||
if (targetSubCatId > 0) {
|
||
params.push(`subCategoryId=${targetSubCatId}`)
|
||
}
|
||
const url = params.length > 0 ? `/pages/post/create?${params.join('&')}` : '/pages/post/create'
|
||
uni.navigateTo({ url })
|
||
},
|
||
},
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.list__cats {
|
||
background: #ffffff;
|
||
white-space: nowrap;
|
||
padding: 16rpx 0;
|
||
border-bottom: 2rpx solid $fc-border;
|
||
}
|
||
.list__subcats-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
background: #ffffff;
|
||
padding: 18rpx 0 16rpx;
|
||
border-bottom: 1rpx solid rgba(0, 0, 0, 0.04);
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 20;
|
||
box-sizing: border-box;
|
||
width: 100%;
|
||
}
|
||
.list__subcats-fixed {
|
||
flex-shrink: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
padding-left: 20rpx;
|
||
padding-right: 18rpx;
|
||
position: relative;
|
||
z-index: 3;
|
||
background: #ffffff;
|
||
}
|
||
.list__subcats-divider {
|
||
position: absolute;
|
||
right: 0;
|
||
top: 15%;
|
||
height: 70%;
|
||
width: 2rpx;
|
||
background: #f1f5f9;
|
||
box-shadow: 2rpx 0 6rpx rgba(0, 0, 0, 0.04);
|
||
}
|
||
.list__subcats-scroll {
|
||
flex: 1;
|
||
width: 0;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
}
|
||
.list__subcats-inner {
|
||
display: inline-flex;
|
||
padding: 0 20rpx 0 16rpx;
|
||
gap: 20rpx;
|
||
}
|
||
.list__subcat {
|
||
display: inline-flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
flex: 0 0 auto;
|
||
cursor: pointer;
|
||
&--active {
|
||
.list__subcat-thumb {
|
||
border-color: #00d26a;
|
||
background: #ecfdf5;
|
||
box-shadow: 0 4rpx 12rpx rgba(0, 210, 106, 0.22);
|
||
}
|
||
.list__subcat-name {
|
||
color: #00a854;
|
||
font-weight: 700;
|
||
}
|
||
}
|
||
}
|
||
.list__subcat-thumb {
|
||
width: 96rpx;
|
||
height: 96rpx;
|
||
border-radius: 24rpx;
|
||
background: #f8fafc;
|
||
border: 2rpx solid transparent;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 8rpx;
|
||
box-sizing: border-box;
|
||
transition: all 0.2s ease;
|
||
}
|
||
.list__subcat-img {
|
||
width: 100%;
|
||
height: 100%;
|
||
border-radius: 16rpx;
|
||
}
|
||
.list__subcat-name {
|
||
margin-top: 8rpx;
|
||
font-size: 23rpx;
|
||
color: #334155;
|
||
line-height: 1.2;
|
||
text-align: center;
|
||
max-width: 110rpx;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.list__cats-inner {
|
||
display: inline-flex;
|
||
padding: 0 16rpx;
|
||
}
|
||
.list__cat {
|
||
margin: 0 10rpx;
|
||
padding: 10rpx 24rpx;
|
||
border-radius: $fc-radius-pill;
|
||
background: $fc-bg;
|
||
font-size: $fc-font-sm;
|
||
color: $fc-text-regular;
|
||
flex: 0 0 auto;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
&--active {
|
||
background: $fc-gradient-primary;
|
||
color: $fc-primary-ink;
|
||
font-weight: 600;
|
||
box-shadow: 0 6rpx 16rpx rgba(4, 48, 30, 0.3);
|
||
}
|
||
}
|
||
.list__cat-icon {
|
||
width: 34rpx;
|
||
height: 34rpx;
|
||
margin-right: 8rpx;
|
||
}
|
||
.list {
|
||
min-height: 100vh;
|
||
padding-bottom: calc(120rpx + env(safe-area-inset-bottom));
|
||
background: #f8fafc;
|
||
}
|
||
.list__cats {
|
||
background: #ffffff;
|
||
white-space: nowrap;
|
||
padding: 16rpx 0;
|
||
border-bottom: 1rpx solid rgba(0, 0, 0, 0.05);
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 20;
|
||
}
|
||
.list__cat {
|
||
cursor: pointer;
|
||
transition: all 0.15s ease;
|
||
&:active {
|
||
transform: scale(0.95);
|
||
}
|
||
}
|
||
.list__filters {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 18rpx 28rpx;
|
||
background: #ffffff;
|
||
margin-bottom: 20rpx;
|
||
border-bottom: 1rpx solid rgba(0, 0, 0, 0.04);
|
||
}
|
||
.list__filter {
|
||
cursor: pointer;
|
||
transition: color 0.15s ease;
|
||
}
|
||
.list__filter {
|
||
margin-right: 28rpx;
|
||
font-size: $fc-font;
|
||
color: $fc-text-secondary;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
&--active {
|
||
color: $fc-primary-text;
|
||
font-weight: 700;
|
||
}
|
||
}
|
||
/* 价格排序双三角指示器(未选中灰、选中主题绿) */
|
||
.list__caret {
|
||
display: inline-flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
margin-left: 6rpx;
|
||
gap: 3rpx;
|
||
}
|
||
.list__caret-up {
|
||
width: 0;
|
||
height: 0;
|
||
border-left: 7rpx solid transparent;
|
||
border-right: 7rpx solid transparent;
|
||
border-bottom: 9rpx solid #cbd5e1;
|
||
&.is-on {
|
||
border-bottom-color: #00a854;
|
||
}
|
||
}
|
||
.list__caret-down {
|
||
width: 0;
|
||
height: 0;
|
||
border-left: 7rpx solid transparent;
|
||
border-right: 7rpx solid transparent;
|
||
border-top: 9rpx solid #cbd5e1;
|
||
&.is-on {
|
||
border-top-color: #00a854;
|
||
}
|
||
}
|
||
/* 筛选栏区域按钮(与首页定位胶囊同款:白底描边胶囊 + 主题绿字) */
|
||
.list__filter-area-btn {
|
||
margin-left: auto;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
padding: 8rpx 20rpx;
|
||
background: #ffffff;
|
||
border: 1rpx solid rgba(0, 168, 84, 0.22);
|
||
border-radius: 999rpx;
|
||
box-shadow: 0 2rpx 8rpx rgba(0, 168, 84, 0.06);
|
||
}
|
||
.list__filter-area-icon {
|
||
width: 24rpx;
|
||
height: 24rpx;
|
||
margin-right: 6rpx;
|
||
flex-shrink: 0;
|
||
}
|
||
.list__filter-area-txt {
|
||
font-size: 24rpx;
|
||
color: #00a854;
|
||
font-weight: 600;
|
||
max-width: 180rpx;
|
||
}
|
||
.list__filter-area-arrow {
|
||
font-size: 20rpx;
|
||
color: #00a854;
|
||
margin-left: 6rpx;
|
||
}
|
||
.list__loading {
|
||
padding: 24rpx;
|
||
}
|
||
.list__skeleton {
|
||
height: 220rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
.list__more {
|
||
text-align: center;
|
||
padding: 24rpx 0 40rpx;
|
||
font-size: $fc-font-sm;
|
||
color: $fc-text-placeholder;
|
||
}
|
||
.list__fab {
|
||
position: fixed;
|
||
right: 32rpx;
|
||
bottom: 60rpx;
|
||
padding: 20rpx 36rpx;
|
||
border-radius: $fc-radius-pill;
|
||
background: $fc-gradient-primary;
|
||
color: $fc-primary-ink;
|
||
font-size: $fc-font;
|
||
font-weight: 700;
|
||
box-shadow: 0 10rpx 28rpx rgba(4, 48, 30, 0.4);
|
||
}
|
||
</style>
|