roamfuding-xcx/page_fenbao/gonglue/gongluexq.vue
2026-03-16 10:00:58 +08:00

605 lines
22 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.

<template>
<view class="page">
<u-navbar title="攻略详情" :border-bottom="false" :background="bgc" back-icon-color="#262B37" title-color='#262B37'
title-size='36' height='40' id="navbar" :custom-back="btnfh">
</u-navbar>
<view class="" v-if="dongtaiflag == false">
<!-- 顶部大图 -->
<view class="hero">
<swiper
class="hero-swiper"
:indicator-dots="heroImages.length > 1"
:autoplay="heroImages.length > 1"
:interval="3000"
:duration="500"
indicator-color="rgba(255,255,255,0.5)"
indicator-active-color="#fff"
@tap="previewHeroImage"
>
<swiper-item v-for="(img, imgIndex) in heroImages" :key="imgIndex">
<image class="hero-img" :src="img" mode="aspectFill"></image>
</swiper-item>
</swiper>
<image class="sc" @click="btnscqx" v-if="globj.isCollected == true" src="https://api.ccttiot.com/smartmeter/img/static/uREVO9VLwercFEWbnurd" mode=""></image>
<image class="sc" @click="btnscqd" v-else src="https://api.ccttiot.com/smartmeter/img/static/ukfmNwl4GOF4v090KRqy" mode=""></image>
</view>
<!-- 基本信息 -->
<view class="base-info">
<view class="title-row">
<view class="left">
<view class="name" style="font-size: 36rpx;">{{globj.name}}</view>
<!-- 标签 -->
<view class="tags-container" v-if="globj.tags && globj.tags.length > 0">
<view class="tag-item" v-for="(tag, index) in globj.tags" :key="index">{{tag}}</view>
</view>
</view>
</view>
</view>
<!-- 小节:核心景点/相关景区 -->
<view class="section-block" style="padding-bottom: 0;" v-if="getDisplaySpots() && getDisplaySpots().length > 0">
<view class="section-hd"><text class="dot"></text><text class="txt">{{ globj.isMultiArea ? '相关景区' : '核心景点' }}</text></view>
<view class="thumbs">
<view class="thumb-item" v-for="(item,index) in getDisplaySpots()" :key="index" @click="goToScenicSpot(item.id)">
<image :src="getFirstPhoto(item.picture)" mode="aspectFill" class="thumb-img" style="width: 204rpx; height: 204rpx;"></image>
<view class="thumb-name">{{item.name}}</view>
</view>
</view>
</view>
<!-- 小节:行程概览 -->
<view class="section-block">
<view class="section-hd"><text class="dot"></text><text class="txt">行程概览</text></view>
<view class="facts list">
<view class="fact"> <image src="https://api.ccttiot.com/smartmeter/img/static/unOeWhbxfVsgh2jXJ0si" mode=""></image> <text class="f-t">行程天数:{{globj.days}}天</text></view>
<view class="fact" v-if="getDisplaySpots().length > 0"> <image src="https://api.ccttiot.com/smartmeter/img/static/uEz1yLuz7ID5iHVqGnfe" style="width: 40rpx;" mode=""></image>
<text class="f-t" v-if="getDisplaySpots().length > 0">游玩景点:{{getDisplaySpots().length}}个 </text>
</view>
</view>
</view>
<!-- 行程推荐(黑灰系时间轴) -->
<view class="section-block">
<view class="timeline4">
<view class="row" v-for="(day, dIndex) in itinerary" :key="dIndex">
<view class="left">
<view class="n">{{ (dIndex+1).toString().padStart(2,'0') }}</view>
<view class="d">Day</view>
<view class="vl"></view>
</view>
<view class="right">
<view class="poi-card4" v-for="(poi, pIndex) in day.items" :key="pIndex">
<view class="poi-hd">
<text class="sq"></text>
<text
class="t"
:class="{ 'link-text': poi.bstId && (poi.bstType == 2 || poi.bstType == 3) }"
@click="handlePoiClick(poi)"
>{{ poi.title }}</text>
</view>
<view class="poi-desc">
<u-parse :html="poi.desc"></u-parse>
</view>
<view class="grid-3">
<image v-for="(img, iidx) in poi.photos" :key="iidx" :src="img" mode="aspectFill" class="gimg" style="width: 200rpx; height: 200rpx;" @click="previewImage(img, poi.photos)" />
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<image v-else style="width: 100%;height: 100vh;position: fixed;top: 160rpx;left: 0;z-index: 999;" src="https://api.ccttiot.com/mgV9maxWGSQb6aa145cf863980513eecdf75080f75a0.png" mode=""></image>
</view>
</template>
<script>
export default {
data() {
return {
bgc: {
backgroundColor: "#fff",
},
itinerary: [
{
items: []
},{
items: []
},
],
id:'',
globj:{},
strategyDetails:[],
// 按天数分组的数据对象
groupedStrategyDetails: {},
dongtaiflag:false,
fanhui:''
}
},
onLoad(option) {
console.log(option,'555');
this.id = option.id
this.getxq()
if(option.fanhui){
this.fanhui = option.fanhui
}
console.log(this.fanhui,'111');
},
// 分享到好友(会话)
onShareAppMessage: function() {
// 先打印调试,看参数拼接结果
const sharePath = '/page_fenbao/gonglue/gongluexq?id=' + this.id + '&fanhui=1';
console.log('分享路径:', sharePath); // 重点看是否有 &fanhui=1
return {
title: '玩转福鼎',
path: sharePath
}
},
// 分享到朋友圈
onShareTimeline: function() {
return {
title: '玩转福鼎',
query: '',
path: '/page_fenbao/gonglue/gongluexq?id=' + this.id + '&fanhui=1'
}
},
computed: {
// 处理顶部轮播图图片数组
heroImages() {
if (!this.globj.photo) {
return []
}
// 如果是字符串,按逗号分割;如果是数组,直接使用
if (typeof this.globj.photo === 'string') {
const images = this.globj.photo.split(',').map(url => url.trim()).filter(url => url)
return images.length > 0 ? images : []
}
return Array.isArray(this.globj.photo) ? this.globj.photo : []
}
},
methods: {
btnfh(){
console.log(this.fanhui,'000');
if(this.fanhui == 1){
uni.switchTab({
url:'/pages/index/index'
})
}else{
uni.navigateBack()
}
},
// 根据isMultiArea获取要显示的景点数据
getDisplaySpots() {
if (this.globj.isMultiArea === true) {
// isMultiArea为true时取areas
return this.globj.areas || []
} else {
// isMultiArea为false时取coreSpots
return this.globj.coreSpots || []
}
},
// 预览顶部轮播图
previewHeroImage() {
if (this.heroImages.length === 0) return
uni.previewImage({
current: this.heroImages[0], // 当前显示第一张
urls: this.heroImages, // 所有图片列表
success: function (res) {
console.log('预览图片成功')
},
fail: function (err) {
console.log('预览图片失败', err)
}
})
},
// 获取第一个图片
getFirstPhoto(photo) {
if (!photo) return ''
// 如果是数组,取第一个元素
if (Array.isArray(photo)) {
return photo[0] || ''
}
// 如果是字符串,可能是逗号分隔的多个图片,取第一个
if (typeof photo === 'string') {
const parts = photo.split(',').filter(Boolean)
return parts[0] || ''
}
return ''
},
// 点击进行收藏
btnscqd(){
let data = {
bstType:1,
bstId:this.id
}
this.$u.post(`/app/favorite/add`,data).then(res =>{
if(res.code == 200){
uni.showToast({
title: '收藏成功',
icon: 'success',
duration:3000
})
this.globj.isCollected = true
}else{
uni.showToast({
title: res.msg,
icon: 'none',
duration:3000
})
}
})
},
// 点击进行取消收藏
btnscqx(){
this.$u.delete(`/app/favorite/cancel/${this.id}?bstType=1`).then(res =>{
if(res.code == 200){
uni.showToast({
title: '取消收藏成功',
icon: 'success',
duration:3000
})
this.globj.isCollected = false
}else{
uni.showToast({
title: res.msg,
icon: 'none',
duration:3000
})
}
})
},
// 点击更多介绍进行跳转
btnjieshao(){
uni.navigateTo({
url:'/page_fenbao/remenxq?id=' + this.globj.areaId
})
},
// 点击拨打电话
btntel(){
uni.makePhoneCall({
phoneNumber: this.globj.areaPhone,
success: function(res) {
console.log('拨打电话成功', res)
},
fail: function(err) {
console.error('拨打电话失败', err)
}
})
},
// 点击进行导航
btndh(){
// 检查坐标数据是否存在
if (!this.globj.areaLat || !this.globj.areaLon) {
uni.showToast({
title: '坐标数据不完整',
icon: 'none',
duration:3000
})
return
}
// 先申请位置权限
uni.getSetting({
success: (res) => {
if (res.authSetting['scope.userLocation'] === false) {
// 用户拒绝了位置权限,引导用户开启
uni.showModal({
title: '位置权限',
content: '需要获取您的位置信息才能进行导航,请在设置中开启位置权限',
confirmText: '去设置',
success: (modalRes) => {
if (modalRes.confirm) {
uni.openSetting()
}
}
})
return
}
// 权限正常,打开地图
this.openMap()
}
})
},
// 打开地图
openMap() {
uni.openLocation({
latitude: parseFloat(this.globj.areaLat), // 确保是数字类型
longitude: parseFloat(this.globj.areaLon), // 确保是数字类型
name: this.globj.areaName || '目的地', // 地点名称
success: function(res) {
console.log('打开地图成功', res);
},
fail: function(err) {
console.error('打开地图失败', err);
uni.showToast({
title: '打开地图失败: ' + (err.errMsg || '未知错误'),
icon: 'none',
duration: 3000
})
}
})
},
// 请求攻略详情
getxq(){
this.$u.get(`/app/strategy/detail/${this.id}?assembleDetail=true`).then((res) => {
if(res.code == 200){
this.globj = res.data
this.dongtaiflag = false
// 按dayNumber分组strategyDetails
this.groupedStrategyDetails = this.groupStrategyDetailsByDay(res.data.strategyDetails)
// 更新itinerary数据使用分组后的数据
this.updateItinerary(this.groupedStrategyDetails)
}else if(res.msg == '攻略不存在'){
this.dongtaiflag = true
}
})
},
// 按天数分组策略详情数据
groupStrategyDetailsByDay(strategyDetails) {
const grouped = {}
strategyDetails.forEach(item => {
const dayNumber = item.dayNumber
if (!grouped[dayNumber]) {
grouped[dayNumber] = []
}
grouped[dayNumber].push(item)
})
// 按天数排序
const sortedGrouped = {}
Object.keys(grouped).sort((a, b) => parseInt(a) - parseInt(b)).forEach(dayNumber => {
sortedGrouped[dayNumber] = grouped[dayNumber]
})
return sortedGrouped
},
// 获取指定天数的数据
getDayData(dayNumber) {
return this.groupedStrategyDetails[dayNumber] || []
},
// 获取所有天数
getAllDays() {
return Object.keys(this.groupedStrategyDetails).map(day => parseInt(day)).sort((a, b) => a - b)
},
// 更新itinerary数据
updateItinerary(groupedDetails) {
this.itinerary = Object.keys(groupedDetails).map(dayNumber => {
return {
dayNumber: parseInt(dayNumber),
items: groupedDetails[dayNumber].map(item => ({
title: item.name || '景点',
desc: item.description || '暂无描述',
photos: item.photo ? item.photo.split(',').map(url => url.trim()) : [],
bstId: item.bstId, // 保留 bstId 用于跳转
bstType: item.bstType // 保留 bstType 用于判断类型
}))
}
}).sort((a, b) => a.dayNumber - b.dayNumber)
},
// 预览图片
previewImage(current, urls) {
uni.previewImage({
current: current, // 当前显示图片的http链接
urls: urls, // 需要预览的图片http链接列表
success: function (res) {
console.log('预览图片成功')
},
fail: function (err) {
console.log('预览图片失败', err)
}
})
},
// 跳转到景点详情页
goToScenicSpot(id) {
if (this.globj.isMultiArea == true) {
uni.navigateTo({
url: '/page_fenbao/remenxq?id=' + id
})
}else{
uni.navigateTo({
url: '/page_fenbao/gonglue/jingdianxq?id=' + id
})
}
},
// 处理景点/酒店点击跳转
handlePoiClick(poi) {
// 只有 bstType 为 2景点或 3酒店且有 bstId 时才跳转
if (!poi.bstId) {
return
}
if (poi.bstType == 2) {
// 跳转到景点详情
uni.navigateTo({
url: '/page_fenbao/gonglue/jingdianxq?id=' + poi.bstId
})
} else if (poi.bstType == 3) {
// 跳转到酒店详情
uni.navigateTo({
url: '/page_fenbao/jiudian/jiudianxq?id=' + poi.bstId
})
}
},
}
}
</script>
<style lang="scss">
page { background: #fff; }
.page { background: #fff; min-height: 100vh; }
/* 顶部大图 */
.hero {
width: 100%;
height: 420rpx;
overflow: hidden;
position: relative;
.hero-swiper {
width: 100%;
height: 100%;
}
.hero-img {
width: 100%;
height: 100%;
}
.sc {
width: 60rpx;
height: 60rpx;
position: absolute;
top: 30rpx;
right: 30rpx;
z-index: 10;
}
}
/* 基本信息 */
.base-info {
padding: 30rpx;
padding-bottom: 0;
.title-row {
display: flex; align-items: center; justify-content: space-between;
.left {
.name { font-size: 52rpx; font-weight: 700; color: #262B37; }
.meta { margin-top: 8rpx; font-size: 28rpx; color: #3D3D3D; }
}
.actions { display: flex; gap: 30rpx;align-items: flex-end; }
.act { padding: 8rpx 16rpx; border: 1rpx solid #e6e6e6; border-radius: 28rpx; font-size: 28rpx; color: #333; }
}
.tags {
margin-top: 20rpx; display: flex; gap: 16rpx; flex-wrap: wrap;
.tag { padding: 8rpx 18rpx; background: #f6f7f9; color: #555; font-size: 26rpx; border-radius: 28rpx; }
}
.tags-container {
margin-top: 20rpx;
display: flex;
gap: 16rpx;
flex-wrap: wrap;
}
.tag-item {
padding: 8rpx 20rpx;
background: #e8f5e9;
color: #10b981;
font-size: 26rpx;
border-radius: 20rpx;
border: 1rpx solid #a5d6a7;
display: inline-flex;
align-items: center;
justify-content: center;
}
.gallery { margin-top: 20rpx; white-space: nowrap; height: 140rpx;
.g-img { width: 200rpx; height: 140rpx; border-radius: 12rpx; margin-right: 16rpx; }
}
}
/* 区块标题 */
.section { padding: 10rpx 30rpx 40rpx; }
.sec-title { font-size: 32rpx; font-weight: 700; color: #262B37; padding: 20rpx 0; }
/* 简介 */
.intro .intro-text { font-size: 26rpx; color: #555; line-height: 1.8; background: #f8f9fb; padding: 28rpx; border-radius: 16rpx; }
/* 精选景点 */
.picks .picks-scroll { white-space: nowrap; }
.pick-item { display: inline-flex; flex-direction: column; margin-right: 20rpx; }
.pick-img { border-radius: 12rpx; }
.pick-name { margin-top: 10rpx; font-size: 26rpx; color: #666; text-align: center; }
/* 行程概览 */
.overview .overview-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16rpx; }
.ov-item { background: #f6f7f9; border-radius: 12rpx; padding: 18rpx; }
.ov-k { font-size: 26rpx; color: #7a7f8a; }
.ov-v { margin-top: 6rpx; font-size: 28rpx; color: #262B37; font-weight: 600; }
/* Day tabs */
.day-tabs { display: flex; gap: 20rpx; padding-top: 0; }
.day-tabs .tab { padding: 10rpx 26rpx; border-radius: 999rpx; background: #f2fbf6; color: #20b26b; font-size: 28rpx; }
.day-tabs .tab.active { background: #20b26b; color: #fff; }
/* 时间轴 */
.timeline { position: relative; }
.day-block { position: relative; padding-left: 90rpx; }
.day-block:before { content: ''; position: absolute; left: 40rpx; top: 0; bottom: 0; width: 2rpx; background: #e0f3e9; }
.day-head { display: flex; align-items: center; margin: 10rpx 0 20rpx; }
.day-num { width: 64rpx; height: 64rpx; border-radius: 8rpx; background: #20b26b; color: #fff; display: flex; flex-direction: column; align-items: center; justify-content: center; position: absolute; left: 8rpx; box-shadow: 0 4rpx 12rpx rgba(32,178,107,0.3); }
.day-num .big { font-size: 26rpx; line-height: 26rpx; }
.day-num .small { font-size: 18rpx; opacity: 0.9; }
.day-text { margin-left: 10rpx; }
.d-title { font-size: 30rpx; font-weight: 600; color: #262B37; }
.d-sub { font-size: 28rpx; color: #7a7f8a; margin-top: 6rpx; }
.day-list { display: flex; flex-direction: column; gap: 26rpx; margin-bottom: 36rpx; }
.poi { display: flex; gap: 16rpx; position: relative; }
.poi-icon { position: absolute; left: 18rpx; top: 18rpx; font-size: 26rpx; color: #20b26b; }
.poi-card { flex: 1; background: #fff; border-radius: 16rpx; padding: 20rpx; box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.06); }
.poi-title { font-size: 28rpx; font-weight: 600; color: #262B37; }
.poi-desc { margin-top: 8rpx; font-size: 28rpx; color: #666; line-height: 1.6; }
.poi-photos { margin-top: 12rpx; display: grid; grid-template-columns: repeat(3, 1fr); gap: 12rpx; }
.pimg { width: 100%; height: 160rpx; border-radius: 10rpx; }
/* 信息卡 */
.info-card { padding: 18rpx 26rpx; background: #fff; border-radius: 16rpx; margin: -40rpx 20rpx 6rpx; box-shadow: 0 6rpx 18rpx rgba(0,0,0,0.06); border: 2rpx solid #eef0f3; }
.info-card.float { position: relative; z-index: 2; }
.title-wrap { display: flex; justify-content: space-between; align-items: center; }
.title-left .cn { font-size: 32rpx; font-weight: 800; color: #1f2a37; }
.title-left .en { margin-top: 6rpx; font-size: 20rpx; color: #9aa4b2; letter-spacing: 2rpx; }
.title-left .sub { margin-top: 8rpx; font-size: 26rpx; color: #8a949e; }
.title-right { display: flex; gap: 12rpx; }
.ic { width: 48rpx; height: 48rpx; border-radius: 10rpx; background: #f3f4f6; border: 2rpx solid #eef0f3; display: flex; align-items: center; justify-content: center; color: #374151; }
/* 小节标题 */
.section-block { padding: 10rpx 20rpx 20rpx; }
.section-hd { display: flex; align-items: center; gap: 12rpx; margin: 18rpx 2rpx; }
.section-hd .dot { width: 8rpx; height: 28rpx; background: #111827; border-radius: 4rpx; }
.section-hd .txt { font-size: 36rpx; font-weight: 800; color: #111827; }
.section-hd .more { margin-left: auto; font-size: 26rpx; color: #3D3D3D; }
.para { font-size: 28rpx; color: #4b5563; line-height: 1.8; }
/* 精选景点 */
.thumbs { display: flex; gap: 16rpx; overflow: scroll;padding-bottom: 20rpx;}
.thumb-item { width: 210rpx; cursor: pointer; }
.thumb-img { border-radius: 12rpx; border: 2rpx solid #eef0f3; }
.thumb-name { margin-top: 8rpx; text-align: center; color: #6b7280; font-size: 26rpx; }
/* 行程概览 */
.kv-row { display: flex; align-items: center; background: #f1f6ff; border-radius: 12rpx; padding: 16rpx; margin-bottom: 10rpx; border: 2rpx solid #cfe0ff; }
.kv { flex: 1; display: flex; justify-content: space-between; align-items: baseline; }
.k { font-size: 26rpx; color: #6f7c96; }
.v { font-size: 26rpx; color: #24324a; font-weight: 700; }
.divider { width: 1rpx; height: 28rpx; background: #ccd9f5; margin: 0 16rpx; }
.facts { margin-top: 10rpx; display: grid; grid-template-columns: 1fr; gap: 10rpx; }
.fact { display: flex; align-items: center; gap: 10rpx; color: #405072; font-size: 28rpx; }
.fact image{ width: 28rpx; height: 28rpx; }
.f-ic { color: #3a6fd9; }
/* Day tabs 下划线风格 */
.day-tabs.underline { display: flex; gap: 40rpx; padding: 4rpx 26rpx 8rpx; }
.day-tabs.underline .tab { font-size: 26rpx; color: #6f7c96; padding-bottom: 10rpx; }
.day-tabs.underline .tab.active { color: #24324a; font-weight: 700; border-bottom: 4rpx solid #3a6fd9; }
/* 新版时间轴蓝色 */
.timeline3 { padding-top: 8rpx; }
.timeline3 .row { display: flex; }
.timeline3 .left { width: 120rpx; align-items: center; display: flex; flex-direction: column; }
.timeline3 .n { font-size: 40rpx; font-weight: 800; color: #3a6fd9; }
.timeline3 .d { font-size: 20rpx; color: #3a6fd9; margin-top: -6rpx; }
.timeline3 .vl { width: 2rpx; flex: 1; background: #d7e6ff; margin-top: 12rpx; }
.timeline3 .right { flex: 1; padding-bottom: 26rpx; }
.rt-title { font-size: 28rpx; font-weight: 800; color: #24324a; }
.rt-sub { font-size: 28rpx; color: #6f7c96; margin-top: 6rpx; }
.poi-card3 { margin-top: 18rpx; background: #fff; border-radius: 12rpx; padding: 18rpx; box-shadow: 0 4rpx 14rpx rgba(0,0,0,0.06); border: 2rpx solid #e6efff; }
.poi-hd { display: flex; align-items: center; gap: 10rpx; }
.poi-hd .sq { width: 16rpx; height: 16rpx; background: #3a6fd9; border-radius: 4rpx; }
.poi-hd .t { font-size: 32rpx; font-weight: 700; color: #24324a; }
.poi-desc { margin-top: 6rpx; font-size: 28rpx; color: #56617a; line-height: 1.8; }
.grid-3 { margin-top: 12rpx; display: grid; grid-template-columns: repeat(3, 1fr); gap: 10rpx; }
.gimg { border-radius: 10rpx; border: 2rpx solid #e6efff; }
/* 行程概览四行列表 */
.list { display: flex; flex-direction: column; gap: 14rpx; }
.fact { display: flex; align-items: center; gap: 12rpx; font-size: 28rpx; color: #4b5563; }
.f-ic { color: #10b981; }
.f-btn { margin-left: auto; font-size: 26rpx; color: #10b981; }
.spot-link { color: #3a6fd9; cursor: pointer; }
/* Day tabs 绿色下划线 */
.day-tabs.underline.g { display: flex; gap: 40rpx; padding: 6rpx 20rpx 8rpx; }
.day-tabs.underline.g .tab { font-size: 26rpx; color: #6b7280; padding-bottom: 10rpx; }
.day-tabs.underline.g .tab.active { color: #111827; font-weight: 700; border-bottom: 4rpx solid #10b981; }
/* 时间轴黑灰 */
.timeline4 { padding-top: 6rpx; }
.timeline4 .row { display: flex; }
.timeline4 .left { width: 66rpx; align-items: center; display: flex; flex-direction: column; }
.timeline4 .n { font-size: 40rpx; font-weight: 800; color: #111827; }
.timeline4 .d { font-size: 20rpx; color: #6b7280; margin-top: -6rpx; }
.timeline4 .vl { width: 2rpx; flex: 1; background: #e5e7eb; margin-top: 12rpx; }
.timeline4 .right { flex: 1; padding-bottom: 26rpx; padding-left: 6rpx; }
.rt-title { font-size: 28rpx; font-weight: 800; color: #111827; }
.rt-sub { font-size: 28rpx; color: #6b7280; margin-top: 6rpx; }
.poi-card4 { margin-top: 18rpx; background: #fff; border-radius: 12rpx; padding: 18rpx; }
.poi-hd { display: flex; align-items: center; gap: 10rpx; }
.poi-hd .sq { width: 16rpx; height: 16rpx; background: #10b981; border-radius: 4rpx; }
.poi-hd .t { font-size: 32rpx; font-weight: 700; color: #111827; }
.poi-hd .t.link-text { color: #10b981; text-decoration: underline; cursor: pointer; }
.poi-desc { margin-top: 6rpx; font-size: 28rpx; color: #4b5563; line-height: 1.8; }
.grid-3 { margin-top: 12rpx; display: grid; grid-template-columns: repeat(3, 1fr); gap: 10rpx; }
.gimg { border-radius: 10rpx; border: 2rpx solid #f3f4f6; }
</style>