- 未来到家:金刚区图标去底块并对齐首页尺寸;瀑布流默认展示全部小类商品 - 我的社区:岁银改临时事件并扩充各分类假数据;公告改资讯卡样式(阅读数+更新时间); 轮播/横幅统一 300rpx 圆角、指示点右下角;tab 去灰底改红色横杠;默认页轮播换新图 - 首页:金刚区间距加大;推荐 tab 改为附近;扫一扫按钮转红色系;清理遗留 console.log Claude-Session: https://claude.ai/code/session_01WQrg6u9Cp9sFPLF6vmbJDq
456 lines
13 KiB
Vue
456 lines
13 KiB
Vue
<!--
|
||
页面:未来到家(原「到家服务」,按设计稿重做)
|
||
说明:顶部红头(品牌 + 城市 + 关键词搜索);二级分类以「金刚区」展示;
|
||
下面商品用瀑布流(与首页好店一致:两列、图片按原始宽高比)。
|
||
大类「到家服务 / 家政服务」的 tab 忽略,金刚区改为铺开展示两大类下的全部小类
|
||
(后端 /service/category/sub-list)。瀑布流默认展示全部小类的商品(category_id=0 不过滤)。
|
||
-->
|
||
<template>
|
||
<view class="fh-page">
|
||
<!-- 顶部红头 -->
|
||
<view class="fh-hd" :style="{ paddingTop: statusBarTop + 'px' }">
|
||
<view class="fh-hd-top">
|
||
<view class="fh-back" @tap="goBack">
|
||
<u-icon name="arrow-left" color="#fff" size="36"></u-icon>
|
||
</view>
|
||
<view class="fh-brand">
|
||
<text class="fh-brand-title">未来到家</text>
|
||
<text class="fh-brand-sub">专业服务 · 品质到家</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 搜索栏 -->
|
||
<view class="fh-search">
|
||
<view class="fh-search-city">
|
||
<text class="fh-city-name">{{ cityName }}</text>
|
||
<text class="fh-city-arrow">▾</text>
|
||
</view>
|
||
<input class="fh-search-input" v-model="keyword" placeholder="空调清洗" placeholder-class="fh-search-ph"
|
||
confirm-type="search" @confirm="doSearch" />
|
||
<view class="fh-search-btn" @tap="doSearch">搜索</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="fh-body">
|
||
<!-- 金刚区:二级分类 -->
|
||
<view class="fh-grid" v-if="categoryList.length">
|
||
<view class="fh-grid-item" v-for="cat in categoryList" :key="cat.id" @tap="selectCate(cat)">
|
||
<image class="fh-grid-icon" :src="picUrl + cat.category_pic" mode="aspectFill" />
|
||
<text class="fh-grid-txt">{{ cat.category_name }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 商品瀑布流(与首页好店一致:两列,图片按原始宽高比) -->
|
||
<view class="wf">
|
||
<view class="wf-col">
|
||
<view class="wf-card" v-for="item in leftCol" :key="item.id" @tap="toDetail(item)">
|
||
<image class="wf-img" :src="item.showImg" :style="{ height: item.imgH + 'rpx' }" mode="aspectFill" />
|
||
<view class="wf-meta">
|
||
<view class="wf-name">{{ item.service_name }}</view>
|
||
<view class="wf-desc" v-if="item.intro">{{ item.intro }}</view>
|
||
<text class="wf-tag" v-if="cateMap[item.category_id]">{{ cateMap[item.category_id] }}</text>
|
||
<view class="wf-foot">
|
||
<text class="wf-price" v-if="item.price_desc">{{ item.price_desc }}</text>
|
||
<text class="wf-book">去预约</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="wf-col">
|
||
<view class="wf-card" v-for="item in rightCol" :key="item.id" @tap="toDetail(item)">
|
||
<image class="wf-img" :src="item.showImg" :style="{ height: item.imgH + 'rpx' }" mode="aspectFill" />
|
||
<view class="wf-meta">
|
||
<view class="wf-name">{{ item.service_name }}</view>
|
||
<view class="wf-desc" v-if="item.intro">{{ item.intro }}</view>
|
||
<text class="wf-tag" v-if="cateMap[item.category_id]">{{ cateMap[item.category_id] }}</text>
|
||
<view class="wf-foot">
|
||
<text class="wf-price" v-if="item.price_desc">{{ item.price_desc }}</text>
|
||
<text class="wf-book">去预约</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="wf-more" v-if="hasMore && (leftCol.length || rightCol.length)">上拉加载更多</view>
|
||
<view class="wf-more" v-else-if="leftCol.length || rightCol.length">没有更多了</view>
|
||
<view class="fh-empty" v-if="!leftCol.length && !rightCol.length && !loadingMore">暂无服务</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import { request, picUrl, NavgateTo, menuButtonInfo } from '@/utils'
|
||
import { apiArr } from '@/api/homeService'
|
||
|
||
// 瀑布单列图片宽度(rpx):与首页好店一致 (750-60)/2 = 345
|
||
const WF_COL_W = 345
|
||
// 卡片文字区估算高度,仅用于两列高度均衡
|
||
const WF_META_H = 150
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
picUrl,
|
||
statusBarTop: 0,
|
||
cityName: '定位中',
|
||
keyword: '',
|
||
categoryList: [],
|
||
cateMap: {}, // 分类 id → 名称,用作商品卡片的红色标签
|
||
currentCate: 0,
|
||
// 瀑布两列
|
||
leftCol: [],
|
||
rightCol: [],
|
||
colHeights: [0, 0],
|
||
page_num: 1,
|
||
page_size: 10,
|
||
hasMore: true,
|
||
loadingMore: false
|
||
}
|
||
},
|
||
onLoad() {
|
||
const menu = menuButtonInfo()
|
||
this.statusBarTop = menu.top || 0
|
||
const loc = uni.getStorageSync('location') || {}
|
||
this.cityName = loc.cityName || '定位中'
|
||
this.initData()
|
||
},
|
||
onPullDownRefresh() {
|
||
this.initData().finally(() => uni.stopPullDownRefresh())
|
||
},
|
||
onReachBottom() {
|
||
if (this.hasMore && !this.loadingMore) this.loadList(false)
|
||
},
|
||
methods: {
|
||
async initData() {
|
||
await this.loadCategory()
|
||
// 瀑布流默认展示全部小类的商品(category_id=0 后端不过滤分类)
|
||
this.currentCate = 0
|
||
this.keyword = ''
|
||
await this.loadList(true)
|
||
},
|
||
async loadCategory() {
|
||
try {
|
||
// 两大类(到家1+家政2)全部小类合并,供金刚区铺开展示
|
||
const res = await request(apiArr.categorySubList, 'POST', {}, {}, false)
|
||
this.categoryList = (res && res.rows) ? res.rows : []
|
||
const map = {}
|
||
this.categoryList.forEach(c => { map[c.id] = c.category_name })
|
||
this.cateMap = map
|
||
} catch (e) {
|
||
this.categoryList = []
|
||
this.cateMap = {}
|
||
}
|
||
},
|
||
selectCate(cat) {
|
||
// 金刚区图标点击 → 打开服务目录页(左侧小类导航 + 右侧分组商品),定位到该小类
|
||
NavgateTo('/packages/homeService/catalog/index?cate_id=' + cat.id, { isLogin: false })
|
||
},
|
||
doSearch() {
|
||
// 关键词搜索:跨分类
|
||
this.currentCate = 0
|
||
this.loadList(true)
|
||
},
|
||
async loadList(reset) {
|
||
if (this.loadingMore) return
|
||
this.loadingMore = true
|
||
if (reset) {
|
||
this.page_num = 1
|
||
this.hasMore = true
|
||
this.leftCol = []
|
||
this.rightCol = []
|
||
this.colHeights = [0, 0]
|
||
}
|
||
try {
|
||
const res = await request(apiArr.serviceList, 'POST', {
|
||
// service_type 传 0(不限):金刚区含两大类小类,靠 category_id 唯一定位;搜索时跨两大类
|
||
service_type: 0,
|
||
category_id: this.currentCate,
|
||
keyword: this.keyword,
|
||
page_num: this.page_num,
|
||
page_size: this.page_size
|
||
}, {}, false)
|
||
const rows = (res && res.rows) ? res.rows : []
|
||
rows.forEach((item) => {
|
||
// 封面图可能是逗号分隔多张,列表取第一张
|
||
const cover0 = item.cover_pic ? String(item.cover_pic).split(',')[0] : ''
|
||
item.showImg = cover0 ? picUrl + cover0 : ''
|
||
})
|
||
this.hasMore = rows.length === this.page_size
|
||
if (this.hasMore) this.page_num++
|
||
await this.appendMasonry(rows)
|
||
} catch (e) {
|
||
console.warn('服务列表加载失败:', e)
|
||
} finally {
|
||
this.loadingMore = false
|
||
}
|
||
},
|
||
// 逐条量图片宽高比 → 追加到较矮的一列,制造瀑布(与首页好店一致)
|
||
async appendMasonry(rows) {
|
||
for (const item of rows) {
|
||
let ratio = 0.8
|
||
if (item.showImg) {
|
||
try {
|
||
const info = await new Promise((resolve, reject) => {
|
||
uni.getImageInfo({ src: item.showImg, success: resolve, fail: reject })
|
||
})
|
||
if (info && info.width) ratio = info.height / info.width
|
||
} catch (e) { /* 用默认比例 */ }
|
||
}
|
||
item.imgH = Math.round(WF_COL_W * ratio)
|
||
const target = this.colHeights[0] <= this.colHeights[1] ? 0 : 1
|
||
if (target === 0) this.leftCol.push(item)
|
||
else this.rightCol.push(item)
|
||
this.colHeights[target] += item.imgH + WF_META_H
|
||
}
|
||
},
|
||
toDetail(item) {
|
||
NavgateTo('/packages/homeService/detail/index?id=' + item.id)
|
||
},
|
||
goBack() {
|
||
uni.navigateBack({ delta: 1, fail: () => { NavgateTo('/pages/index2/index2') } })
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
page { background: #f5f6f8; }
|
||
.fh-page { min-height: 100vh; background: #f5f6f8; }
|
||
|
||
/* ===== 顶部红头(吸顶:搜索栏不随页面滚动) ===== */
|
||
.fh-hd {
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 100;
|
||
/* 设计稿:横向红色渐变 90deg #FF1A1F → #FF634A */
|
||
background: linear-gradient(90deg, #ff1a1f 0%, #ff634a 100%);
|
||
padding-left: 28rpx;
|
||
padding-right: 28rpx;
|
||
padding-bottom: 28rpx;
|
||
}
|
||
.fh-hd-top {
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
height: 80rpx;
|
||
}
|
||
.fh-back {
|
||
position: absolute;
|
||
left: 0;
|
||
top: 0;
|
||
height: 80rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
padding-right: 16rpx;
|
||
}
|
||
.fh-brand {
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
padding-left: 56rpx;
|
||
}
|
||
.fh-brand-title {
|
||
/* 设计稿 33px ≈ 58rpx */
|
||
font-size: 58rpx;
|
||
font-weight: 700;
|
||
color: #fff;
|
||
letter-spacing: 2rpx;
|
||
}
|
||
.fh-brand-sub {
|
||
margin-top: 8rpx;
|
||
/* 设计稿 12px ≈ 21rpx */
|
||
font-size: 21rpx;
|
||
color: rgba(255, 255, 255, 0.92);
|
||
letter-spacing: 4rpx;
|
||
}
|
||
|
||
/* 搜索栏 */
|
||
.fh-search {
|
||
display: flex;
|
||
align-items: center;
|
||
height: 77rpx;
|
||
margin-top: 22rpx;
|
||
background: #fff;
|
||
border-radius: 42rpx;
|
||
padding: 0 8rpx 0 26rpx;
|
||
}
|
||
.fh-search-city {
|
||
display: flex;
|
||
align-items: center;
|
||
flex-shrink: 0;
|
||
/* 设计稿城市 16px ≈ 28rpx,#111111 */
|
||
font-size: 28rpx;
|
||
color: #111;
|
||
max-width: 180rpx;
|
||
}
|
||
.fh-city-name {
|
||
overflow: hidden;
|
||
white-space: nowrap;
|
||
text-overflow: ellipsis;
|
||
}
|
||
.fh-city-arrow {
|
||
margin-left: 6rpx;
|
||
font-size: 22rpx;
|
||
color: #666;
|
||
}
|
||
.fh-search-city::after {
|
||
content: '';
|
||
width: 2rpx;
|
||
height: 34rpx;
|
||
background: #d9d9d9;
|
||
margin: 0 20rpx;
|
||
}
|
||
.fh-search-input {
|
||
flex: 1;
|
||
/* 设计稿搜索词 15px ≈ 26rpx */
|
||
font-size: 26rpx;
|
||
color: #333;
|
||
}
|
||
.fh-search-ph { color: #a0a0a0; }
|
||
.fh-search-btn {
|
||
flex-shrink: 0;
|
||
/* 设计稿搜索按钮 64×36px ≈ 112×63rpx,#FF3929,radius 18px≈31rpx */
|
||
height: 63rpx;
|
||
line-height: 63rpx;
|
||
min-width: 112rpx;
|
||
text-align: center;
|
||
padding: 0 26rpx;
|
||
background: #ff3929;
|
||
color: #fff;
|
||
font-size: 26rpx;
|
||
font-weight: 700;
|
||
border-radius: 31rpx;
|
||
}
|
||
|
||
/* ===== 主体 ===== */
|
||
.fh-body {
|
||
position: relative;
|
||
margin-top: -12rpx;
|
||
padding: 20rpx 20rpx 40rpx;
|
||
}
|
||
|
||
/* 金刚区 */
|
||
.fh-grid {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
background: #fff;
|
||
border-radius: 20rpx;
|
||
padding: 24rpx 6rpx 4rpx;
|
||
}
|
||
.fh-grid-item {
|
||
width: 20%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
margin-bottom: 26rpx;
|
||
}
|
||
.fh-grid-icon {
|
||
/* 与首页金刚区一致(index2.css .grid-icon):84rpx、圆角 24rpx,无底块无边框 */
|
||
width: 84rpx;
|
||
height: 84rpx;
|
||
border-radius: 24rpx;
|
||
display: block;
|
||
}
|
||
.fh-grid-txt {
|
||
margin-top: 12rpx;
|
||
/* 设计稿分类文字 14px ≈ 24rpx,#111111 */
|
||
font-size: 24rpx;
|
||
color: #111;
|
||
text-align: center;
|
||
}
|
||
|
||
/* ===== 瀑布流(与首页好店一致) ===== */
|
||
.wf {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
margin-top: 20rpx;
|
||
}
|
||
.wf-col {
|
||
width: 345rpx;
|
||
}
|
||
.wf-col:first-child {
|
||
margin-right: 20rpx;
|
||
}
|
||
.wf-card {
|
||
background: #fff;
|
||
/* 设计稿卡片:1px #EDEDED 描边,radius 10px≈17rpx */
|
||
border: 1rpx solid #ededed;
|
||
border-radius: 17rpx;
|
||
overflow: hidden;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
.wf-img {
|
||
width: 100%;
|
||
display: block;
|
||
background: #eee;
|
||
}
|
||
.wf-meta {
|
||
padding: 16rpx 14rpx;
|
||
}
|
||
.wf-name {
|
||
/* 设计稿标题 15px≈26rpx,#111111 */
|
||
font-size: 26rpx;
|
||
font-weight: 700;
|
||
color: #111;
|
||
overflow: hidden;
|
||
white-space: nowrap;
|
||
text-overflow: ellipsis;
|
||
}
|
||
.wf-desc {
|
||
margin-top: 8rpx;
|
||
/* 设计稿描述 11px≈19rpx,#777777 */
|
||
font-size: 19rpx;
|
||
color: #777;
|
||
overflow: hidden;
|
||
white-space: nowrap;
|
||
text-overflow: ellipsis;
|
||
}
|
||
/* 服务标签:浅底红框红字胶囊 */
|
||
.wf-tag {
|
||
display: inline-block;
|
||
margin-top: 12rpx;
|
||
background: #fff6f4;
|
||
border: 1rpx solid #ff4a38;
|
||
border-radius: 7rpx;
|
||
color: #ff3929;
|
||
font-size: 18rpx;
|
||
line-height: 1.4;
|
||
padding: 2rpx 10rpx;
|
||
}
|
||
.wf-foot {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-top: 14rpx;
|
||
}
|
||
.wf-price {
|
||
/* 设计稿价格 16px≈28rpx,#FF3929 */
|
||
font-size: 28rpx;
|
||
font-weight: 700;
|
||
color: #ff3929;
|
||
}
|
||
.wf-book {
|
||
flex-shrink: 0;
|
||
/* 设计稿预约按钮:纯红 #FF3929,radius 15px≈26rpx */
|
||
background: #ff3929;
|
||
color: #fff;
|
||
font-size: 22rpx;
|
||
font-weight: 700;
|
||
padding: 10rpx 24rpx;
|
||
border-radius: 26rpx;
|
||
}
|
||
.wf-more {
|
||
text-align: center;
|
||
color: #999;
|
||
font-size: 24rpx;
|
||
padding: 24rpx 0 20rpx;
|
||
}
|
||
.fh-empty {
|
||
text-align: center;
|
||
color: #999;
|
||
font-size: 26rpx;
|
||
padding: 100rpx 0;
|
||
}
|
||
</style>
|