2026-07-08 16:17:50 +08:00

158 lines
7.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!--
页面:未来到家 - 服务目录(金刚区图标点击进入)
格式:顶部「服务」标题 + 搜索栏;左侧小类竖向导航(选中红条高亮),
右侧按小类分组的服务网格(3列:封面图 + 名称),点左侧定位到对应分组。
-->
<template>
<view class="sc-page">
<!-- 头部 -->
<view class="sc-hd" :style="{ paddingTop: statusBarTop + 'px' }">
<view class="sc-nav">
<view class="sc-back" @tap="goBack"><u-icon name="arrow-left" color="#1a1a1a" size="38"></u-icon></view>
<text class="sc-title">服务</text>
</view>
<view class="sc-search">
<text class="sc-city">{{ cityName }}</text>
<text class="sc-city-arrow">▾</text>
<view class="sc-search-line"></view>
<u-icon name="search" color="#9098a0" size="30"></u-icon>
<input class="sc-search-input" v-model="keyword" placeholder="搜索您需要的服务" placeholder-class="sc-ph" />
</view>
</view>
<!-- 主体:左侧分类导航 + 右侧分组商品 -->
<view class="sc-body">
<scroll-view class="sc-side" scroll-y>
<view class="sc-side-item" :class="{ on: activeCate === cat.id }" v-for="cat in categoryList" :key="cat.id"
@tap="selectCate(cat)">
<text>{{ cat.category_name }}</text>
</view>
</scroll-view>
<!-- 右侧:直接显示当前选中分类的商品(3列网格) -->
<scroll-view class="sc-main" scroll-y :scroll-top="mainScrollTop">
<view class="sc-sec">
<text class="sc-sec-tt">{{ activeCateName }}</text>
<view class="sc-grid">
<view class="sc-item" v-for="s in activeItems" :key="s.id" @tap="toDetail(s)">
<!-- 与详情页轮播一致:固定高度铺满格子,超出部分居中裁剪(aspectFill) -->
<image class="sc-item-img" :src="s.showImg" mode="aspectFill" />
<text class="sc-item-name">{{ s.service_name }}</text>
</view>
</view>
</view>
<view class="sc-empty" v-if="!activeItems.length">该分类暂无服务</view>
<view class="sc-pad"></view>
</scroll-view>
</view>
</view>
</template>
<script>
import { request, picUrl, NavgateTo } from '@/utils'
import { apiArr } from '@/api/homeService'
export default {
data() {
return {
statusBarTop: 20,
cityName: '定位中',
keyword: '',
categoryList: [],
activeCate: 0,
mainScrollTop: 0,
groups: [] // [{ id, name, items: [service] }]
}
},
onLoad(options) {
const sys = uni.getSystemInfoSync()
this.statusBarTop = sys.statusBarHeight || 20
const loc = uni.getStorageSync('location') || {}
this.cityName = loc.cityName || '定位中'
this.initData(options && options.cate_id ? Number(options.cate_id) : 0)
},
computed: {
activeCateName() {
const c = this.categoryList.find(x => x.id === this.activeCate)
return c ? c.category_name : ''
},
// 当前分类的商品(3列直出),支持搜索词过滤
activeItems() {
const grp = this.groups.find(g => g.id === this.activeCate)
const items = grp ? grp.items : []
const kw = this.keyword.trim()
return kw ? items.filter(s => (s.service_name || '').indexOf(kw) >= 0) : items
}
},
methods: {
async initData(cateId) {
try {
const [cateRes, listRes] = await Promise.all([
request(apiArr.categorySubList, 'POST', {}, {}, false),
// 一次拉全量服务(目录页数据量小),按分类分组
request(apiArr.serviceList, 'POST', { service_type: 0, category_id: 0, page_num: 1, page_size: 500 }, {}, false)
])
this.categoryList = (cateRes && cateRes.rows) ? cateRes.rows : []
const services = ((listRes && listRes.rows) ? listRes.rows : []).map(s => {
const cover0 = s.cover_pic ? String(s.cover_pic).split(',')[0] : ''
s.showImg = cover0 ? picUrl + cover0 : ''
return s
})
this.groups = this.categoryList.map(c => ({
id: c.id, name: c.category_name,
items: services.filter(s => s.category_id === c.id)
}))
this.activeCate = cateId || (this.categoryList[0] ? this.categoryList[0].id : 0)
} catch (e) {
console.warn('服务目录加载失败:', e)
}
},
selectCate(cat) {
this.activeCate = cat.id
// 切分类右侧回到顶部
this.mainScrollTop = this.mainScrollTop === 0 ? 0.01 : 0
},
toDetail(s) {
NavgateTo('/packages/homeService/detail/index?id=' + s.id)
},
goBack() {
uni.navigateBack({ delta: 1, fail: () => NavgateTo('/packages/homeService/index/index', { isLogin: false }) })
}
}
}
</script>
<style scoped>
page { background: #fff; }
.sc-page { height: 100vh; display: flex; flex-direction: column; background: #fff; }
/* 头部 */
.sc-hd { flex-shrink: 0; background: #fff; padding: 0 24rpx 16rpx; }
.sc-nav { position: relative; display: flex; align-items: center; justify-content: center; height: 88rpx; }
.sc-back { position: absolute; left: 0; top: 0; height: 88rpx; display: flex; align-items: center; }
.sc-title { font-size: 36rpx; font-weight: 700; color: #1a1a1a; }
.sc-search { display: flex; align-items: center; height: 76rpx; background: #f4f5f7; border-radius: 16rpx; padding: 0 24rpx; }
.sc-city { flex-shrink: 0; font-size: 28rpx; color: #111; max-width: 160rpx; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.sc-city-arrow { margin: 0 10rpx 0 6rpx; font-size: 22rpx; color: #666; }
.sc-search-line { width: 2rpx; height: 32rpx; background: #d9d9d9; margin-right: 18rpx; }
.sc-search-input { flex: 1; margin-left: 12rpx; font-size: 28rpx; color: #333; }
.sc-ph { color: #a0a0a0; }
/* 主体双栏 */
.sc-body { flex: 1; min-height: 0; display: flex; }
.sc-side { flex-shrink: 0; width: 200rpx; height: 100%; background: #f5f6f7; }
.sc-side-item { position: relative; padding: 34rpx 16rpx; font-size: 28rpx; color: #333; text-align: center; }
.sc-side-item.on { background: #fff; color: #e9272d; font-weight: 700; }
.sc-side-item.on::before { content: ''; position: absolute; left: 0; top: 50%; transform: translateY(-50%); width: 8rpx; height: 40rpx; background: #e9272d; border-radius: 0 4rpx 4rpx 0; }
.sc-main { flex: 1; min-width: 0; height: 100%; background: #fff; }
.sc-sec { padding: 24rpx 20rpx 6rpx; }
.sc-sec-tt { display: block; font-size: 32rpx; font-weight: 700; color: #1a1a1a; margin-bottom: 20rpx; }
.sc-grid { display: flex; flex-wrap: wrap; }
.sc-item { width: 33.33%; display: flex; flex-direction: column; align-items: center; margin-bottom: 28rpx; padding: 0 8rpx; box-sizing: border-box; }
.sc-item-img { width: 100%; height: 84px; border-radius: 16rpx; background: #f4f5f7; display: block; }
.sc-item-name { margin-top: 12rpx; font-size: 26rpx; color: #333; text-align: center; line-height: 1.35; }
.sc-empty { text-align: center; color: #9aa0a8; font-size: 26rpx; padding: 120rpx 0; }
.sc-pad { height: 40rpx; }
</style>