add commiunity
@ -1,6 +1,7 @@
|
|||||||
export const apiArr = {
|
export const apiArr = {
|
||||||
// 到家服务
|
// 到家服务
|
||||||
categoryList: "/api/v2/wechat/service/category/list", // 服务分类列表
|
categoryList: "/api/v2/wechat/service/category/list", // 服务分类列表
|
||||||
|
categorySubList: "/api/v2/wechat/service/category/sub-list", // 全部小类(到家+家政两大类合并,供金刚区)
|
||||||
serviceList: "/api/v2/wechat/service/list", // 服务商品列表
|
serviceList: "/api/v2/wechat/service/list", // 服务商品列表
|
||||||
serviceInfo: "/api/v2/wechat/service/info", // 服务详情(含SKU+师傅)
|
serviceInfo: "/api/v2/wechat/service/info", // 服务详情(含SKU+师傅)
|
||||||
orderCreate: "/api/v2/wechat/service/order/create", // 下单(定金单)
|
orderCreate: "/api/v2/wechat/service/order/create", // 下单(定金单)
|
||||||
|
|||||||
@ -47,7 +47,7 @@ export default {
|
|||||||
photoAc:
|
photoAc:
|
||||||
"https://static.hshuishang.com/property-img-file/footer_homeAc.png",
|
"https://static.hshuishang.com/property-img-file/footer_homeAc.png",
|
||||||
nav_name: "智慧首页",
|
nav_name: "智慧首页",
|
||||||
url: "/pages/index/index",
|
url: "/pages/index2/index2",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
photo:
|
photo:
|
||||||
@ -55,7 +55,7 @@ export default {
|
|||||||
photoAc:
|
photoAc:
|
||||||
"https://static.hshuishang.com/property-img-file/footer_facilityAc.png",
|
"https://static.hshuishang.com/property-img-file/footer_facilityAc.png",
|
||||||
nav_name: "智能设备",
|
nav_name: "智能设备",
|
||||||
url: "/packages/smartDevice/index/index",
|
url: "/packages/smartDevice/home/index",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
photo:
|
photo:
|
||||||
@ -70,7 +70,7 @@ export default {
|
|||||||
"https://static.hshuishang.com/property-img-file/footer_community.png",
|
"https://static.hshuishang.com/property-img-file/footer_community.png",
|
||||||
photoAc:
|
photoAc:
|
||||||
"https://static.hshuishang.com/property-img-file/footer_communityAc.png",
|
"https://static.hshuishang.com/property-img-file/footer_communityAc.png",
|
||||||
nav_name: "我的小区",
|
nav_name: "我的社区",
|
||||||
url: "/packages/community/index/index",
|
url: "/packages/community/index/index",
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
@ -200,9 +200,10 @@ export default {
|
|||||||
|
|
||||||
const ctoken = uni.getStorageSync("ctoken");
|
const ctoken = uni.getStorageSync("ctoken");
|
||||||
const authen = [
|
const authen = [
|
||||||
|
"/pages/index2/index2",
|
||||||
"/pages/index/index",
|
"/pages/index/index",
|
||||||
"/pages/user/index",
|
"/pages/user/index",
|
||||||
"/packages/smartDevice/index/index",
|
"/packages/smartDevice/home/index",
|
||||||
];
|
];
|
||||||
// 除首页及个人中心页不需要鉴定ctoken, 其他tab页均需存在登录态才可访问
|
// 除首页及个人中心页不需要鉴定ctoken, 其他tab页均需存在登录态才可访问
|
||||||
if (authen.includes(url)) {
|
if (authen.includes(url)) {
|
||||||
|
|||||||
@ -138,9 +138,9 @@ export default {
|
|||||||
icon: "none",
|
icon: "none",
|
||||||
duration: 1500,
|
duration: 1500,
|
||||||
});
|
});
|
||||||
// 提示显示后延迟跳转
|
// 提示显示后清空页面栈,直接回到个人中心(避免逐层返回多次)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
NavgateTo("/packages/community/myCommunity/index");
|
uni.reLaunch({ url: "/pages/user/index" });
|
||||||
}, 1500);
|
}, 1500);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@ -53,14 +53,21 @@ image {
|
|||||||
min-height: calc(100vh - 120rpx);
|
min-height: calc(100vh - 120rpx);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
/* 去掉渐变续色,页面纯白底 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.searchBox {
|
.searchBox {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
position: absolute;
|
/* 不要红头,保持页面白底 */
|
||||||
left: 0;
|
background: #ffffff;
|
||||||
z-index: 2;
|
}
|
||||||
|
|
||||||
|
.back-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-left: 20rpx;
|
||||||
|
padding-right: 4rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.searchBox_add {
|
.searchBox_add {
|
||||||
@ -73,7 +80,7 @@ image {
|
|||||||
width: auto;
|
width: auto;
|
||||||
height: 44rpx;
|
height: 44rpx;
|
||||||
padding: 5rpx 15rpx;
|
padding: 5rpx 15rpx;
|
||||||
background: rgba(255, 255, 255, 0.8);
|
background: #f4f5f7;
|
||||||
border-radius: 100rpx 100rpx 100rpx 100rpx;
|
border-radius: 100rpx 100rpx 100rpx 100rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -103,7 +110,11 @@ image {
|
|||||||
|
|
||||||
.swiperBox1 {
|
.swiperBox1 {
|
||||||
height: 500rpx;
|
height: 500rpx;
|
||||||
width: 750rpx;
|
width: 710rpx;
|
||||||
|
/* 与首页 banner 一致:内缩圆角卡片,两侧留白让 .container 续色透出 */
|
||||||
|
margin: 20rpx auto 0;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,8 +122,8 @@ image {
|
|||||||
height: 300rpx;
|
height: 300rpx;
|
||||||
width: 710rpx;
|
width: 710rpx;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-top: 170rpx;
|
/* 头区已改普通流,去掉原为绝对定位 searchBox 预留的 170rpx 顶部空隙 */
|
||||||
margin: 170rpx auto 0;
|
margin: 20rpx auto 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.swiperBox_no_img{
|
.swiperBox_no_img{
|
||||||
@ -123,15 +134,9 @@ image {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* B方案:去掉 banner 底部的半透明→白 遮罩(会截断红色续色、造成不连续) */
|
||||||
.swiperBox1::after {
|
.swiperBox1::after {
|
||||||
content: '';
|
content: none;
|
||||||
width: 100%;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
height: 120rpx;
|
|
||||||
/* 从上到下 透明渐变成白色 */
|
|
||||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dot {
|
.dot {
|
||||||
@ -163,14 +168,14 @@ image {
|
|||||||
.swiperBox2 {
|
.swiperBox2 {
|
||||||
height: 150rpx;
|
height: 150rpx;
|
||||||
width: 710rpx;
|
width: 710rpx;
|
||||||
margin: 0 auto;
|
/* 与金刚区互换后位于其下方,去掉原上叠 banner 的 -96rpx,改正常上间距 */
|
||||||
margin-top: -96rpx;
|
margin: 30rpx auto 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.swiperBox_no2 {
|
.swiperBox_no2 {
|
||||||
height: 150rpx;
|
height: 150rpx;
|
||||||
width: 710rpx;
|
width: 710rpx;
|
||||||
margin: 0 auto;;
|
margin: 30rpx auto 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.funcList {
|
.funcList {
|
||||||
@ -284,6 +289,55 @@ image {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 宣传栏:图左 + 右侧标题与内容预览 */
|
||||||
|
.newsItemL {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 30rpx 0;
|
||||||
|
margin: 0 20rpx;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-bottom: 1rpx solid #EBEBEB;
|
||||||
|
}
|
||||||
|
|
||||||
|
.newsItemL:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.newsItemL_pic {
|
||||||
|
width: 220rpx;
|
||||||
|
height: 160rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.newsItemL_body {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
align-self: stretch;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.newsItemL_tit {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #222222;
|
||||||
|
margin-bottom: 14rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.newsItemL_desc {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #999999;
|
||||||
|
line-height: 1.5;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.tips {
|
.tips {
|
||||||
font-size: 26rpx;
|
font-size: 26rpx;
|
||||||
color: #999999;
|
color: #999999;
|
||||||
|
|||||||
@ -8,6 +8,9 @@
|
|||||||
|
|
||||||
<view v-if="communityList.length == 0">
|
<view v-if="communityList.length == 0">
|
||||||
<view class="searchBox" :style="{ height: localHeight + 'px', paddingTop: top + 'px' }">
|
<view class="searchBox" :style="{ height: localHeight + 'px', paddingTop: top + 'px' }">
|
||||||
|
<view class="back-btn" v-if="showBack" @tap="goBack">
|
||||||
|
<u-icon name="arrow-left" color="#333" size="40"></u-icon>
|
||||||
|
</view>
|
||||||
<view class="searchBox_add">
|
<view class="searchBox_add">
|
||||||
<view class="emptyCommunity" @click="addCommunity">
|
<view class="emptyCommunity" @click="addCommunity">
|
||||||
{{ communityVal }}
|
{{ communityVal }}
|
||||||
@ -28,13 +31,7 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="swiperBox_no2">
|
<!-- 金刚区与「消费送物业费」广告互换:金刚区在上、广告在下(与已绑定状态一致) -->
|
||||||
<swiper @animationfinish="swipers" autoplay circular>
|
|
||||||
<image src="https://static.hshuishang.com/community_no_image2.png" mode="aspectFill"
|
|
||||||
class="swiperBox_no_img" @click="addCommunity" />
|
|
||||||
</swiper>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="funcList">
|
<view class="funcList">
|
||||||
<u-grid :col="5" :border="false">
|
<u-grid :col="5" :border="false">
|
||||||
<u-grid-item v-for="(item, index) in noValFunctionList" @click="addCommunity" :key="index">
|
<u-grid-item v-for="(item, index) in noValFunctionList" @click="addCommunity" :key="index">
|
||||||
@ -44,6 +41,13 @@
|
|||||||
</u-grid>
|
</u-grid>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<view class="swiperBox_no2">
|
||||||
|
<swiper @animationfinish="swipers" autoplay circular>
|
||||||
|
<image src="https://static.hshuishang.com/community_no_image2.png" mode="aspectFill"
|
||||||
|
class="swiperBox_no_img" @click="addCommunity" />
|
||||||
|
</swiper>
|
||||||
|
</view>
|
||||||
|
|
||||||
<view class="tabs" v-if="defaultCategoryList.length > 0">
|
<view class="tabs" v-if="defaultCategoryList.length > 0">
|
||||||
<view v-for="(item, index) in defaultCategoryList" :key="index"
|
<view v-for="(item, index) in defaultCategoryList" :key="index"
|
||||||
:class="['tabItem', selectedTab === index ? 'active2' : '']" @click="selectTab(index, item)">
|
:class="['tabItem', selectedTab === index ? 'active2' : '']" @click="selectTab(index, item)">
|
||||||
@ -51,21 +55,25 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 宣传栏:图片在左,标题在右,标题下方展示内容预览 -->
|
||||||
<view class="newsList" v-if="defaultCategoryList.length > 0">
|
<view class="newsList" v-if="defaultCategoryList.length > 0">
|
||||||
<view class="newsItem" v-for="item in defaultInfoList" @click="defaultDetail(item)" :key="item.id">
|
<view class="newsItemL" v-for="item in defaultInfoList" @click="defaultDetail(item)" :key="item.id">
|
||||||
<view class="newsItem_left">
|
<view class="newsItemL_pic">
|
||||||
<view class="newsItem_left_tit">{{ item.title }}</view>
|
|
||||||
<view class="newsItem_left_sub">{{ item.author }}</view>
|
|
||||||
</view>
|
|
||||||
<view class="newsItem_right">
|
|
||||||
<image :src="item.pic" mode="aspectFill" />
|
<image :src="item.pic" mode="aspectFill" />
|
||||||
</view>
|
</view>
|
||||||
|
<view class="newsItemL_body">
|
||||||
|
<view class="newsItemL_tit">{{ item.title }}</view>
|
||||||
|
<view class="newsItemL_desc">{{ contentPreview(item.id) }}</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
<view v-else>
|
<view v-else>
|
||||||
<view class="searchBox" :style="{ height: localHeight + 'px', paddingTop: top + 'px' }">
|
<view class="searchBox" :style="{ height: localHeight + 'px', paddingTop: top + 'px' }">
|
||||||
|
<view class="back-btn" v-if="showBack" @tap="goBack">
|
||||||
|
<u-icon name="arrow-left" color="#333" size="40"></u-icon>
|
||||||
|
</view>
|
||||||
<view class="searchBox_add">
|
<view class="searchBox_add">
|
||||||
<view class="emptyCommunity" @click="addCommunity">
|
<view class="emptyCommunity" @click="addCommunity">
|
||||||
{{ communityVal }}
|
{{ communityVal }}
|
||||||
@ -100,6 +108,16 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 金刚区(与「消费送物业费」广告互换:金刚区在上,广告在下) -->
|
||||||
|
<view class="funcList">
|
||||||
|
<u-grid :col="rowNum" :border="false">
|
||||||
|
<u-grid-item v-for="(item, index) in functionList" @click="jump(item.mini_program_url)" :key="index">
|
||||||
|
<image class="grid_Pic" :src="item.nav_icon" mode=""></image>
|
||||||
|
<text class="grid_Text">{{ item.nav_name }}</text>
|
||||||
|
</u-grid-item>
|
||||||
|
</u-grid>
|
||||||
|
</view>
|
||||||
|
|
||||||
<view class="swiperBox2" v-if="streamerList.length > 0">
|
<view class="swiperBox2" v-if="streamerList.length > 0">
|
||||||
<swiper @animationfinish="swipers" autoplay circular>
|
<swiper @animationfinish="swipers" autoplay circular>
|
||||||
<swiper-item v-for="(item, index) in streamerList" :key="index" @click="headerServerClick(item)">
|
<swiper-item v-for="(item, index) in streamerList" :key="index" @click="headerServerClick(item)">
|
||||||
@ -115,15 +133,6 @@
|
|||||||
</swiper>
|
</swiper>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="funcList">
|
|
||||||
<u-grid :col="rowNum" :border="false">
|
|
||||||
<u-grid-item v-for="(item, index) in functionList" @click="jump(item.mini_program_url)" :key="index">
|
|
||||||
<image class="grid_Pic" :src="item.nav_icon" mode=""></image>
|
|
||||||
<text class="grid_Text">{{ item.nav_name }}</text>
|
|
||||||
</u-grid-item>
|
|
||||||
</u-grid>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view v-for="(item, index) in tileList" :key="index" :class="['ads', index == 0 ? 'ads_first' : '']"
|
<view v-for="(item, index) in tileList" :key="index" :class="['ads', index == 0 ? 'ads_first' : '']"
|
||||||
@click="headerServerClick(item)">
|
@click="headerServerClick(item)">
|
||||||
<image :src="item.ad_picture" mode="aspectFill" />
|
<image :src="item.ad_picture" mode="aspectFill" />
|
||||||
@ -143,27 +152,28 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<!-- 已绑定:临时通知列表,图左 + 右侧标题与内容预览 -->
|
||||||
<view class="newsList" v-if="categoryList.length > 0">
|
<view class="newsList" v-if="categoryList.length > 0">
|
||||||
<view class="newsItem" v-for="item in infoList" @click="detail(item)" :key="item.id">
|
<view class="newsItemL" v-for="item in infoList" @click="detail(item)" :key="item.id">
|
||||||
<view class="newsItem_left">
|
<view class="newsItemL_pic">
|
||||||
<view class="newsItem_left_tit">{{ item.title }}</view>
|
|
||||||
<view class="newsItem_left_sub">{{ item.author }}</view>
|
|
||||||
</view>
|
|
||||||
<view class="newsItem_right">
|
|
||||||
<image :src="item.list_image" mode="aspectFill" />
|
<image :src="item.list_image" mode="aspectFill" />
|
||||||
</view>
|
</view>
|
||||||
|
<view class="newsItemL_body">
|
||||||
|
<view class="newsItemL_tit">{{ item.title }}</view>
|
||||||
|
<view class="newsItemL_desc">{{ noticePreview(item) }}</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="newsList" v-else>
|
<view class="newsList" v-else>
|
||||||
<view class="newsItem" v-for="item in defaultInfoList" @click="defaultDetail(item)" :key="item.id">
|
<view class="newsItemL" v-for="item in defaultInfoList" @click="defaultDetail(item)" :key="item.id">
|
||||||
<view class="newsItem_left">
|
<view class="newsItemL_pic">
|
||||||
<view class="newsItem_left_tit">{{ item.title }}</view>
|
|
||||||
<view class="newsItem_left_sub">{{ item.author }}</view>
|
|
||||||
</view>
|
|
||||||
<view class="newsItem_right">
|
|
||||||
<image :src="item.pic" mode="aspectFill" />
|
<image :src="item.pic" mode="aspectFill" />
|
||||||
</view>
|
</view>
|
||||||
|
<view class="newsItemL_body">
|
||||||
|
<view class="newsItemL_tit">{{ item.title }}</view>
|
||||||
|
<view class="newsItemL_desc">{{ contentPreview(item.id) }}</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@ -227,6 +237,7 @@ export default {
|
|||||||
picUrl,
|
picUrl,
|
||||||
top: "",
|
top: "",
|
||||||
localHeight: "",
|
localHeight: "",
|
||||||
|
showBack: false, // 有上级页面时(如从未来社区「我的房屋」跳入)显示返回按钮;作为底部tab进入时不显示
|
||||||
queryPage: {
|
queryPage: {
|
||||||
page_num: 1,
|
page_num: 1,
|
||||||
page_size: 10,
|
page_size: 10,
|
||||||
@ -308,6 +319,7 @@ export default {
|
|||||||
// 从JSON文件中获取默认数据
|
// 从JSON文件中获取默认数据
|
||||||
defaultCategoryList: dataJson.defaultCategoryList,
|
defaultCategoryList: dataJson.defaultCategoryList,
|
||||||
defaultInfoList: dataJson.defaultInfoList,
|
defaultInfoList: dataJson.defaultInfoList,
|
||||||
|
defaultNoticeList: dataJson.defaultNoticeList, // 含正文,用于宣传栏内容预览
|
||||||
// 用于存储原始的defaultInfoList数据
|
// 用于存储原始的defaultInfoList数据
|
||||||
originalDefaultInfoList: null
|
originalDefaultInfoList: null
|
||||||
};
|
};
|
||||||
@ -330,6 +342,7 @@ export default {
|
|||||||
this.top = meun.top;
|
this.top = meun.top;
|
||||||
// this.top = meun.height + meun.top;
|
// this.top = meun.height + meun.top;
|
||||||
this.localHeight = meun.height;
|
this.localHeight = meun.height;
|
||||||
|
this.showBack = getCurrentPages().length > 1;
|
||||||
this.defaultFunctionList = [
|
this.defaultFunctionList = [
|
||||||
{
|
{
|
||||||
create_time: "",
|
create_time: "",
|
||||||
@ -514,6 +527,9 @@ export default {
|
|||||||
addCommunity() {
|
addCommunity() {
|
||||||
NavgateTo("/packages/community/myCommunity/index");
|
NavgateTo("/packages/community/myCommunity/index");
|
||||||
},
|
},
|
||||||
|
goBack() {
|
||||||
|
uni.navigateBack({ delta: 1 });
|
||||||
|
},
|
||||||
|
|
||||||
swipers(event) {
|
swipers(event) {
|
||||||
// 获取当前轮播图索引
|
// 获取当前轮播图索引
|
||||||
@ -826,6 +842,39 @@ export default {
|
|||||||
console.log(e);
|
console.log(e);
|
||||||
NavgateTo("../defaultNotice/index?id=" + e.id);
|
NavgateTo("../defaultNotice/index?id=" + e.id);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 静态默认宣传栏内容预览:按 id 取对应正文,去掉换行/空白占位/标签,截取一段
|
||||||
|
contentPreview(id) {
|
||||||
|
const n = (this.defaultNoticeList || []).find((x) => x.id === id);
|
||||||
|
if (!n || !n.content) return "";
|
||||||
|
const text = n.content
|
||||||
|
.replace(/ /g, " ")
|
||||||
|
.replace(/<[^>]+>/g, "")
|
||||||
|
.replace(/\s+/g, " ")
|
||||||
|
.trim();
|
||||||
|
return text.length > 50 ? text.slice(0, 50) + "…" : text;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 已绑定:临时通知列表项内容预览(接口数据),字段兜底 + 去标签截取
|
||||||
|
noticePreview(item) {
|
||||||
|
if (!item) return "";
|
||||||
|
const raw =
|
||||||
|
item.content ||
|
||||||
|
item.summary ||
|
||||||
|
item.describe ||
|
||||||
|
item.intro ||
|
||||||
|
item.sub_title ||
|
||||||
|
"";
|
||||||
|
if (raw) {
|
||||||
|
const text = String(raw)
|
||||||
|
.replace(/ /g, " ")
|
||||||
|
.replace(/<[^>]+>/g, "")
|
||||||
|
.replace(/\s+/g, " ")
|
||||||
|
.trim();
|
||||||
|
if (text) return text.length > 50 ? text.slice(0, 50) + "…" : text;
|
||||||
|
}
|
||||||
|
return item.author || "";
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
144
packages/futureCommunity/activityDetail/index.vue
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
<!-- 未来社区 - 活动详情 -->
|
||||||
|
<template>
|
||||||
|
<view class="ad-page">
|
||||||
|
<view class="fc-nav" :style="{ paddingTop: statusBarTop + 'px' }">
|
||||||
|
<view class="fc-nav-bar">
|
||||||
|
<view class="fc-back" @tap="goBack"><u-icon name="arrow-left" color="#1a1a1a" size="38"></u-icon></view>
|
||||||
|
<text class="fc-nav-title">活动详情</text>
|
||||||
|
<view class="ad-star" @tap="onFollow" v-if="act"><u-icon :name="followed ? 'star-fill' : 'star'" :color="followed ? '#ff9a12' : '#9098a0'" size="40"></u-icon></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="ad-body" v-if="act">
|
||||||
|
<!-- 封面 -->
|
||||||
|
<view class="ad-banner">
|
||||||
|
<image v-if="act.image" :src="act.image" class="ad-banner-img" mode="aspectFill" />
|
||||||
|
<view v-else class="ad-banner-img" :style="{ background: sceneBg(act.scene) }"></view>
|
||||||
|
<text class="ad-badge" :style="statusStyle(act.status)">{{ statusLabel(act.status) }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 摘要 -->
|
||||||
|
<view class="ad-summary">
|
||||||
|
<text class="ad-title">{{ act.title }}</text>
|
||||||
|
<view class="ad-sum-row">
|
||||||
|
<view class="ad-dist"><u-icon name="map" color="#9098a0" size="24"></u-icon><text>{{ act.distance || '社区内' }}</text></view>
|
||||||
|
<view class="ad-call" v-if="act.phone" @tap="onCall"><u-icon name="phone" color="#e9272d" size="24"></u-icon><text>联系电话</text></view>
|
||||||
|
</view>
|
||||||
|
<text class="ad-addr">{{ act.address || act.place }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 活动说明 -->
|
||||||
|
<view class="ad-card">
|
||||||
|
<text class="ad-card-tt">活动说明</text>
|
||||||
|
<view class="ad-info-row"><u-icon name="account" color="#9098a0" size="26"></u-icon><text class="ad-lb">报名人数</text><text class="ad-vl">{{ act.enrolled || act.quota }}</text></view>
|
||||||
|
<view class="ad-info-row"><u-icon name="clock" color="#9098a0" size="26"></u-icon><text class="ad-lb">报名时间</text><text class="ad-vl">{{ act.signupRange || act.deadline }}</text></view>
|
||||||
|
<view class="ad-info-row"><u-icon name="calendar" color="#9098a0" size="26"></u-icon><text class="ad-lb">活动时间</text><text class="ad-vl">{{ act.time }}</text></view>
|
||||||
|
<view class="ad-info-row"><u-icon name="integral" color="#9098a0" size="26"></u-icon><text class="ad-lb">报名费用</text><text class="ad-vl">{{ act.fee || '免费' }}</text></view>
|
||||||
|
<view class="ad-info-row"><u-icon name="server-man" color="#9098a0" size="26"></u-icon><text class="ad-lb">组织方</text><text class="ad-vl">{{ act.organizer || '九九社区' }}</text></view>
|
||||||
|
<view class="ad-info-row"><u-icon name="phone" color="#9098a0" size="26"></u-icon><text class="ad-lb">联系电话</text><text class="ad-vl">{{ act.phone || '暂无' }}</text></view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 活动介绍 -->
|
||||||
|
<view class="ad-card">
|
||||||
|
<text class="ad-card-tt">活动介绍</text>
|
||||||
|
<rich-text class="ad-content" :nodes="act.content"></rich-text>
|
||||||
|
<view class="ad-points" v-if="act.points && act.points.length">
|
||||||
|
<view class="ad-point" v-for="(p, i) in act.points" :key="i"><text class="ad-dot">•</text><text class="ad-pt">{{ p }}</text></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 参与须知 -->
|
||||||
|
<view class="ad-card">
|
||||||
|
<text class="ad-card-tt">参与须知</text>
|
||||||
|
<view class="ad-point" v-for="(p, i) in ACTIVITY_REGISTER_NOTICE" :key="i"><text class="ad-dot">•</text><text class="ad-pt">{{ p }}</text></view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="fc-spacer"></view>
|
||||||
|
</view>
|
||||||
|
<view v-else class="fc-empty">活动不存在</view>
|
||||||
|
|
||||||
|
<view class="ad-foot" v-if="act">
|
||||||
|
<view class="ad-foot-fee"><text class="ad-foot-lb">报名费用</text><text class="ad-foot-vl">{{ act.fee || '免费' }}</text></view>
|
||||||
|
<text class="ad-join" :class="{ done: joined, ended: act.status === 'ended' }" @tap="onJoin">{{ joinLabel }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { findActivity, ACTIVITY_STATUS_META, ACTIVITY_SCENE_BG, ACTIVITY_REGISTER_NOTICE, isActivityJoined, toggleActivityJoin, isActivityFollowed, toggleActivityFollow } from '../data.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() { return { statusBarTop: 20, act: null, joined: false, followed: false, ACTIVITY_REGISTER_NOTICE } },
|
||||||
|
onLoad(options) {
|
||||||
|
const sys = uni.getSystemInfoSync()
|
||||||
|
this.statusBarTop = sys.statusBarHeight || 20
|
||||||
|
this.act = findActivity(options && options.id) || null
|
||||||
|
if (this.act) { this.joined = isActivityJoined(this.act.id); this.followed = isActivityFollowed(this.act.id) }
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
joinLabel() {
|
||||||
|
if (!this.act) return ''
|
||||||
|
if (this.act.status === 'ended') return '活动已结束'
|
||||||
|
return this.joined ? '已报名 · 点击取消' : '立即报名'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
statusLabel(s) { return (ACTIVITY_STATUS_META[s] || {}).label || '' },
|
||||||
|
statusStyle(s) { const m = ACTIVITY_STATUS_META[s] || {}; return { color: m.color, background: m.bg } },
|
||||||
|
sceneBg(s) { return ACTIVITY_SCENE_BG[s] || ACTIVITY_SCENE_BG.handicraft },
|
||||||
|
goBack() { uni.navigateBack({ delta: 1 }) },
|
||||||
|
onCall() { uni.showToast({ title: '联系电话:' + this.act.phone, icon: 'none' }) },
|
||||||
|
onFollow() { this.followed = toggleActivityFollow(this.act.id); uni.showToast({ title: this.followed ? '已关注活动' : '已取消关注', icon: 'none' }) },
|
||||||
|
onJoin() {
|
||||||
|
if (this.act.status === 'ended') { uni.showToast({ title: '活动已结束', icon: 'none' }); return }
|
||||||
|
this.joined = toggleActivityJoin(this.act.id)
|
||||||
|
uni.showToast({ title: this.joined ? '报名成功' : '已取消报名', icon: 'none' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
page { background: #f5f6f7; }
|
||||||
|
.ad-page { min-height: 100vh; background: #f5f6f7; padding-bottom: 140rpx; }
|
||||||
|
.fc-nav { position: sticky; top: 0; z-index: 100; background: #fff; padding: 0 24rpx; }
|
||||||
|
.fc-nav-bar { position: relative; display: flex; align-items: center; justify-content: center; height: 88rpx; }
|
||||||
|
.fc-back { position: absolute; left: 0; top: 0; height: 88rpx; display: flex; align-items: center; }
|
||||||
|
.fc-nav-title { font-size: 34rpx; font-weight: 700; color: #1a1a1a; }
|
||||||
|
.ad-star { position: absolute; right: 0; top: 0; height: 88rpx; display: flex; align-items: center; }
|
||||||
|
|
||||||
|
.ad-body { padding: 20rpx; }
|
||||||
|
.ad-banner { position: relative; border-radius: 20rpx; overflow: hidden; }
|
||||||
|
.ad-banner-img { width: 100%; height: 360rpx; display: block; }
|
||||||
|
.ad-badge { position: absolute; left: 20rpx; top: 20rpx; font-size: 22rpx; padding: 4rpx 16rpx; border-radius: 6rpx; }
|
||||||
|
|
||||||
|
.ad-summary { background: #fff; border-radius: 20rpx; padding: 26rpx; margin-top: 16rpx; }
|
||||||
|
.ad-title { font-size: 38rpx; font-weight: 800; color: #1a1a1a; }
|
||||||
|
.ad-sum-row { display: flex; align-items: center; justify-content: space-between; margin-top: 16rpx; }
|
||||||
|
.ad-dist { display: flex; align-items: center; font-size: 24rpx; color: #6b7078; }
|
||||||
|
.ad-dist text { margin-left: 8rpx; }
|
||||||
|
.ad-call { display: flex; align-items: center; font-size: 24rpx; color: #e9272d; }
|
||||||
|
.ad-call text { margin-left: 6rpx; }
|
||||||
|
.ad-addr { display: block; margin-top: 14rpx; font-size: 24rpx; color: #9098a0; }
|
||||||
|
|
||||||
|
.ad-card { background: #fff; border-radius: 20rpx; padding: 26rpx; margin-top: 16rpx; }
|
||||||
|
.ad-card-tt { display: block; font-size: 30rpx; font-weight: 700; color: #1a1a1a; margin-bottom: 18rpx; }
|
||||||
|
.ad-info-row { display: flex; align-items: center; padding: 14rpx 0; }
|
||||||
|
.ad-lb { width: 130rpx; margin-left: 14rpx; font-size: 26rpx; color: #9098a0; }
|
||||||
|
.ad-vl { flex: 1; font-size: 26rpx; color: #333; }
|
||||||
|
.ad-content { display: block; font-size: 28rpx; color: #333; line-height: 1.8; }
|
||||||
|
.ad-points { margin-top: 12rpx; }
|
||||||
|
.ad-point { display: flex; margin-top: 12rpx; }
|
||||||
|
.ad-dot { color: #e9272d; margin-right: 12rpx; font-size: 28rpx; line-height: 1.6; }
|
||||||
|
.ad-pt { flex: 1; font-size: 26rpx; color: #555; line-height: 1.6; }
|
||||||
|
|
||||||
|
.ad-foot { position: fixed; left: 0; right: 0; bottom: 0; background: #fff; display: flex; align-items: center; padding: 14rpx 24rpx calc(14rpx + env(safe-area-inset-bottom)); box-shadow: 0 -4rpx 20rpx rgba(0,0,0,0.04); }
|
||||||
|
.ad-foot-fee { flex: 1; display: flex; flex-direction: column; }
|
||||||
|
.ad-foot-lb { font-size: 20rpx; color: #9098a0; }
|
||||||
|
.ad-foot-vl { font-size: 34rpx; color: #e9272d; font-weight: 700; }
|
||||||
|
.ad-join { flex-shrink: 0; background: linear-gradient(90deg, #ff5b5b 0%, #ef111b 100%); color: #fff; font-size: 30rpx; font-weight: 700; padding: 20rpx 70rpx; border-radius: 44rpx; }
|
||||||
|
.ad-join.done { background: #fff0ee; color: #ef111b; }
|
||||||
|
.ad-join.ended { background: #eef0f3; color: #9aa0a8; }
|
||||||
|
.fc-empty { text-align: center; color: #9aa0a8; font-size: 26rpx; padding: 120rpx 0; }
|
||||||
|
.fc-spacer { height: 20rpx; }
|
||||||
|
</style>
|
||||||
99
packages/futureCommunity/activityList/index.vue
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
<!-- 未来社区 - 社区活动列表 -->
|
||||||
|
<template>
|
||||||
|
<view class="al-page">
|
||||||
|
<view class="fc-nav" :style="{ paddingTop: statusBarTop + 'px' }">
|
||||||
|
<view class="fc-nav-bar">
|
||||||
|
<view class="fc-back" @tap="goBack"><u-icon name="arrow-left" color="#1a1a1a" size="38"></u-icon></view>
|
||||||
|
<text class="fc-nav-title">社区活动</text>
|
||||||
|
</view>
|
||||||
|
<view class="al-search">
|
||||||
|
<u-icon name="search" color="#9098a0" size="28"></u-icon>
|
||||||
|
<input class="al-search-input" v-model="keyword" placeholder="搜索活动名称、地点" placeholder-class="al-ph" />
|
||||||
|
</view>
|
||||||
|
<scroll-view class="al-tabs" scroll-x>
|
||||||
|
<text class="al-tab" :class="{ on: activeTab === t.key }" v-for="t in ACTIVITY_TABS" :key="t.key" @tap="activeTab = t.key">{{ t.label }}</text>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="al-body">
|
||||||
|
<view class="al-card" v-for="a in filtered" :key="a.id" @tap="goDetail(a)">
|
||||||
|
<view class="al-cover">
|
||||||
|
<image v-if="a.image" :src="a.image" class="al-img" mode="aspectFill" />
|
||||||
|
<view v-else class="al-img" :style="{ background: sceneBg(a.scene) }"></view>
|
||||||
|
<text class="al-badge" :style="statusStyle(a.status)">{{ statusLabel(a.status) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="al-info">
|
||||||
|
<text class="al-title">{{ a.title }}</text>
|
||||||
|
<view class="al-row"><u-icon name="map" color="#9098a0" size="22"></u-icon><text>{{ a.place }}</text></view>
|
||||||
|
<view class="al-row"><u-icon name="clock" color="#9098a0" size="22"></u-icon><text>{{ a.time }}</text></view>
|
||||||
|
<view class="al-row"><u-icon name="account" color="#9098a0" size="22"></u-icon><text>{{ a.enrolled || a.quota }}</text></view>
|
||||||
|
<view class="al-foot">
|
||||||
|
<view class="al-row"><u-icon name="map" color="#c2c7ce" size="20"></u-icon><text class="al-dist">{{ a.distance || '社区内' }}</text></view>
|
||||||
|
<text class="al-fee">{{ a.fee || '免费' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="fc-empty" v-if="!filtered.length">暂无相关活动</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { NavgateTo } from '@/utils'
|
||||||
|
import { activities, ACTIVITY_TABS, ACTIVITY_STATUS_META, ACTIVITY_SCENE_BG } from '../data.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() { return { statusBarTop: 20, ACTIVITY_TABS, activeTab: 'all', keyword: '', activities } },
|
||||||
|
onLoad(options) {
|
||||||
|
const sys = uni.getSystemInfoSync()
|
||||||
|
this.statusBarTop = sys.statusBarHeight || 20
|
||||||
|
if (options && options.tab && ACTIVITY_TABS.some(t => t.key === options.tab)) this.activeTab = options.tab
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
filtered() {
|
||||||
|
const kw = this.keyword.trim()
|
||||||
|
return this.activities.filter(a => {
|
||||||
|
const okTab = this.activeTab === 'all' || a.status === this.activeTab
|
||||||
|
const okKw = !kw || (a.title + a.subtitle + a.place).indexOf(kw) >= 0
|
||||||
|
return okTab && okKw
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
statusLabel(s) { return (ACTIVITY_STATUS_META[s] || {}).label || '' },
|
||||||
|
statusStyle(s) { const m = ACTIVITY_STATUS_META[s] || {}; return { color: m.color, background: m.bg } },
|
||||||
|
sceneBg(s) { return ACTIVITY_SCENE_BG[s] || ACTIVITY_SCENE_BG.handicraft },
|
||||||
|
goBack() { uni.navigateBack({ delta: 1 }) },
|
||||||
|
goDetail(a) { NavgateTo('/packages/futureCommunity/activityDetail/index?id=' + a.id, { isLogin: false }) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
page { background: #f5f6f7; }
|
||||||
|
.al-page { min-height: 100vh; background: #f5f6f7; }
|
||||||
|
.fc-nav { position: sticky; top: 0; z-index: 100; background: #fff; padding: 0 24rpx 12rpx; }
|
||||||
|
.fc-nav-bar { position: relative; display: flex; align-items: center; justify-content: center; height: 88rpx; }
|
||||||
|
.fc-back { position: absolute; left: 0; top: 0; height: 88rpx; display: flex; align-items: center; }
|
||||||
|
.fc-nav-title { font-size: 34rpx; font-weight: 700; color: #1a1a1a; }
|
||||||
|
.al-search { display: flex; align-items: center; height: 68rpx; background: #f4f5f7; border-radius: 34rpx; padding: 0 24rpx; }
|
||||||
|
.al-search-input { flex: 1; margin-left: 12rpx; font-size: 26rpx; color: #333; }
|
||||||
|
.al-ph { color: #9098a0; }
|
||||||
|
.al-tabs { white-space: nowrap; padding: 14rpx 0 4rpx; }
|
||||||
|
.al-tab { display: inline-block; font-size: 28rpx; color: #6b7078; padding: 8rpx 6rpx; margin-right: 40rpx; }
|
||||||
|
.al-tab.on { color: #e9272d; font-weight: 700; }
|
||||||
|
|
||||||
|
.al-body { padding: 20rpx; }
|
||||||
|
.al-card { background: #fff; border-radius: 22rpx; overflow: hidden; margin-bottom: 20rpx; }
|
||||||
|
.al-cover { position: relative; width: 100%; height: 300rpx; }
|
||||||
|
.al-img { width: 100%; height: 300rpx; display: block; }
|
||||||
|
.al-badge { position: absolute; left: 16rpx; top: 16rpx; font-size: 20rpx; padding: 4rpx 14rpx; border-radius: 6rpx; }
|
||||||
|
.al-info { padding: 22rpx 24rpx; display: flex; flex-direction: column; }
|
||||||
|
.al-title { font-size: 32rpx; font-weight: 700; color: #1a1a1a; }
|
||||||
|
.al-row { display: flex; align-items: center; margin-top: 12rpx; font-size: 24rpx; color: #6b7078; }
|
||||||
|
.al-row text { margin-left: 8rpx; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
||||||
|
.al-foot { display: flex; align-items: center; justify-content: space-between; margin-top: 16rpx; }
|
||||||
|
.al-dist { color: #9098a0; }
|
||||||
|
.al-fee { font-size: 28rpx; color: #e9272d; font-weight: 700; }
|
||||||
|
.fc-empty { text-align: center; color: #9aa0a8; font-size: 26rpx; padding: 120rpx 0; }
|
||||||
|
</style>
|
||||||
66
packages/futureCommunity/conveniencePhone/index.vue
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<!-- 未来社区 - 便民电话 -->
|
||||||
|
<template>
|
||||||
|
<view class="cp-page">
|
||||||
|
<view class="fc-nav" :style="{ paddingTop: statusBarTop + 'px' }">
|
||||||
|
<view class="fc-nav-bar">
|
||||||
|
<view class="fc-back" @tap="goBack"><u-icon name="arrow-left" color="#1a1a1a" size="38"></u-icon></view>
|
||||||
|
<text class="fc-nav-title">便民电话</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="cp-body">
|
||||||
|
<view class="cp-tip"><u-icon name="info-circle" color="#f53d42" size="28"></u-icon><text>{{ phoneTip }}</text></view>
|
||||||
|
|
||||||
|
<view class="cp-card" v-for="item in phoneList" :key="item.id">
|
||||||
|
<view class="cp-icon" :style="{ background: item.iconBg }"><u-icon :name="item.icon" color="#fff" size="44"></u-icon></view>
|
||||||
|
<view class="cp-main">
|
||||||
|
<text class="cp-name">{{ item.name }}</text>
|
||||||
|
<text class="cp-phone">{{ item.phone }}</text>
|
||||||
|
<text class="cp-time">{{ item.time }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="cp-dial" @tap="onDial(item)"><u-icon name="phone" color="#f53d42" size="40"></u-icon></view>
|
||||||
|
</view>
|
||||||
|
<view class="fc-spacer"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { phoneTip, phoneList } from '../data.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() { return { statusBarTop: 20, phoneTip, phoneList } },
|
||||||
|
onLoad() {
|
||||||
|
const sys = uni.getSystemInfoSync()
|
||||||
|
this.statusBarTop = sys.statusBarHeight || 20
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
goBack() { uni.navigateBack({ delta: 1 }) },
|
||||||
|
onDial(item) {
|
||||||
|
const tel = item.phone.replace(/\s/g, '')
|
||||||
|
uni.makePhoneCall({ phoneNumber: tel, fail: () => uni.showToast({ title: '拨打 ' + item.name + ':' + item.phone, icon: 'none' }) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
page { background: #f5f6f7; }
|
||||||
|
.cp-page { min-height: 100vh; background: #f5f6f7; }
|
||||||
|
.fc-nav { position: sticky; top: 0; z-index: 100; background: #fff; padding: 0 24rpx 16rpx; }
|
||||||
|
.fc-nav-bar { position: relative; display: flex; align-items: center; justify-content: center; height: 88rpx; }
|
||||||
|
.fc-back { position: absolute; left: 0; top: 0; height: 88rpx; display: flex; align-items: center; }
|
||||||
|
.fc-nav-title { font-size: 34rpx; font-weight: 700; color: #1a1a1a; }
|
||||||
|
|
||||||
|
.cp-body { padding: 20rpx; }
|
||||||
|
.cp-tip { display: flex; align-items: center; background: #fff2ee; border-radius: 25rpx; padding: 22rpx 28rpx; }
|
||||||
|
.cp-tip text { margin-left: 12rpx; font-size: 24rpx; color: #676c74; }
|
||||||
|
.cp-card { display: flex; align-items: center; background: #fff; border-radius: 20rpx; border: 1rpx solid #eeeef0; box-shadow: 0 5rpx 16rpx rgba(0,0,0,0.055); padding: 28rpx; margin-top: 20rpx; }
|
||||||
|
.cp-icon { flex-shrink: 0; width: 96rpx; height: 96rpx; border-radius: 22rpx; display: flex; align-items: center; justify-content: center; margin-right: 20rpx; }
|
||||||
|
.cp-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
|
||||||
|
.cp-name { font-size: 30rpx; font-weight: 700; color: #111; }
|
||||||
|
.cp-phone { font-size: 26rpx; color: #666b72; margin-top: 8rpx; }
|
||||||
|
.cp-time { font-size: 22rpx; color: #9098a0; margin-top: 8rpx; }
|
||||||
|
.cp-dial { flex-shrink: 0; width: 84rpx; height: 84rpx; border-radius: 999rpx; background: #fff0f0; display: flex; align-items: center; justify-content: center; }
|
||||||
|
.fc-spacer { height: 40rpx; }
|
||||||
|
</style>
|
||||||
194
packages/futureCommunity/data.js
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
/**
|
||||||
|
* 未来社区(九九社区)静态内容数据 —— 1:1 还原设计稿 mock
|
||||||
|
* 来源:liuyuanxitong-yidong/src/config/community-data.js、community-content-data.js、convenience-phone-data.js
|
||||||
|
* 全部为固定数据;关注/报名等可变状态用本地存储维护。
|
||||||
|
* 图标名为 uview u-icon 名称(已由 Vant 名映射而来)。
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** 顶部定位 */
|
||||||
|
export const locationInfo = { community: '上河坊·九九社区' }
|
||||||
|
export const searchPlaceholder = '搜索公告、活动、便民信息'
|
||||||
|
|
||||||
|
/** 5 个快捷入口(彩色渐变圆形图标) */
|
||||||
|
export const shortcuts = [
|
||||||
|
{ key: 'notice', label: '社区公告', icon: 'volume', bg: 'linear-gradient(135deg, #FF9A12 0%, #FF7900 100%)' },
|
||||||
|
{ key: 'activity', label: '社区活动', icon: 'calendar', bg: 'linear-gradient(135deg, #FF4752 0%, #EF2231 100%)' },
|
||||||
|
{ key: 'phone', label: '便民电话', icon: 'phone', bg: 'linear-gradient(135deg, #21C86A 0%, #08A84E 100%)' },
|
||||||
|
{ key: 'house', label: '我的房屋', icon: 'home', bg: 'linear-gradient(135deg, #3C82FF 0%, #2164F0 100%)' },
|
||||||
|
{ key: 'goodshop', label: '周边好店', icon: 'shopping-cart', bg: 'linear-gradient(135deg, #B04AFF 0%, #913EE7 100%)' }
|
||||||
|
]
|
||||||
|
|
||||||
|
/** 顶部轮播(用渐变+文案,不依赖外链图片) */
|
||||||
|
export const heroSlides = [
|
||||||
|
{ key: 'community-life', title: '九九社区美好生活', subtitle: '邻里相伴,生活更暖心', bg: 'linear-gradient(120deg, #ff4752 0%, #ff8a3d 100%)' },
|
||||||
|
{ key: 'community-activity', title: '邻里活动精彩不断', subtitle: '一起参与,共享社区好时光', bg: 'linear-gradient(120deg, #ff6a3d 0%, #ffb14a 100%)' },
|
||||||
|
{ key: 'community-service', title: '便民服务就在身边', subtitle: '公告、报修、生活服务一站直达', bg: 'linear-gradient(120deg, #2164f0 0%, #4aa8ff 100%)' }
|
||||||
|
]
|
||||||
|
|
||||||
|
/** 公告分类标签样式 */
|
||||||
|
export const NOTICE_TAG_META = {
|
||||||
|
announcement: { label: '公告', color: '#ffffff', bg: '#df3c3f' },
|
||||||
|
urgent: { label: '紧急', color: '#ffffff', bg: '#e9272d' },
|
||||||
|
notice: { label: '通知', color: '#e46d25', bg: '#ffe3cb' },
|
||||||
|
service: { label: '服务', color: '#1f8a4c', bg: '#dcf5e5' },
|
||||||
|
activity: { label: '活动', color: '#2164f0', bg: '#e2ecff' }
|
||||||
|
}
|
||||||
|
export const NOTICE_TABS = [
|
||||||
|
{ key: 'all', label: '全部' },
|
||||||
|
{ key: 'urgent', label: '紧急' },
|
||||||
|
{ key: 'notice', label: '通知' },
|
||||||
|
{ key: 'service', label: '服务' }
|
||||||
|
]
|
||||||
|
|
||||||
|
/** 活动状态样式 */
|
||||||
|
export const ACTIVITY_STATUS_META = {
|
||||||
|
upcoming: { label: '即将开始', color: '#e46d25', bg: '#ffe9d6' },
|
||||||
|
ongoing: { label: '进行中', color: '#ffffff', bg: '#e9272d' },
|
||||||
|
ended: { label: '已结束', color: '#9aa0a8', bg: '#eef0f3' }
|
||||||
|
}
|
||||||
|
export const ACTIVITY_TABS = [
|
||||||
|
{ key: 'all', label: '全部' },
|
||||||
|
{ key: 'upcoming', label: '即将开始' },
|
||||||
|
{ key: 'ongoing', label: '进行中' },
|
||||||
|
{ key: 'ended', label: '已结束' }
|
||||||
|
]
|
||||||
|
export const ACTIVITY_REGISTER_NOTICE = [
|
||||||
|
'请准时到场,活动开始后 15 分钟未到视为放弃名额',
|
||||||
|
'如需取消报名,请提前 1 天在「邻里关注」中操作',
|
||||||
|
'活动现场请听从工作人员安排,注意自身与财物安全',
|
||||||
|
'亲子类活动需家长全程陪同,确保儿童安全'
|
||||||
|
]
|
||||||
|
/** 活动场景 → 兜底渐变(无封面图时用) */
|
||||||
|
export const ACTIVITY_SCENE_BG = {
|
||||||
|
handicraft: 'linear-gradient(135deg, #ffd9c0 0%, #ffb088 100%)',
|
||||||
|
market: 'linear-gradient(135deg, #ffe6a3 0%, #ffc24d 100%)',
|
||||||
|
garden: 'linear-gradient(135deg, #c8efb0 0%, #7cc36a 100%)',
|
||||||
|
fitness: 'linear-gradient(135deg, #bfe3ff 0%, #6db4f0 100%)',
|
||||||
|
festival: 'linear-gradient(135deg, #ffc1bb 0%, #f37c6e 100%)'
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 社区公告 */
|
||||||
|
export const notices = [
|
||||||
|
{
|
||||||
|
id: 'n1', icon: 'volume', tag: 'announcement', title: '关于五一劳动节期间服务安排的通知',
|
||||||
|
summary: '五一期间物业服务中心值班安排及服务时间调整…', date: '2025-04-28', source: '物业服务中心',
|
||||||
|
readCount: 1286, pinned: true,
|
||||||
|
content: '<p>尊敬的业主:</p><p>五一劳动节期间,物业服务中心将安排工作人员轮值。前台服务时间调整为每日 9:00-17:00,24 小时报修及应急电话保持畅通。</p><p>如需办理装修登记、门禁卡等业务,建议提前预约。祝各位业主节日愉快。</p>'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'n2', icon: 'bell', tag: 'notice', title: '电梯例行维护保养通知',
|
||||||
|
summary: '本周将对小区部分楼栋电梯进行例行维护保养…', date: '2025-04-27', source: '物业服务中心',
|
||||||
|
readCount: 942, pinned: false,
|
||||||
|
content: '<p>尊敬的业主:</p><p>本周将对小区部分楼栋电梯进行例行维护保养。维护期间电梯将分时段暂停运行,请根据现场提示错峰出行。</p><p>给您带来的不便敬请谅解,感谢您的理解与配合。</p>'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'n3', icon: 'account', tag: 'notice', title: '物业服务中心工作时间调整',
|
||||||
|
summary: '自5月1日起,物业服务中心工作时间调整如下…', date: '2025-04-25', source: '社区服务站',
|
||||||
|
readCount: 658, pinned: false,
|
||||||
|
content: '<p>自 5 月 1 日起,物业服务中心工作时间调整为周一至周日 8:30-18:00,午间照常提供咨询服务。</p><p>非工作时间如遇紧急情况,请拨打物业 24 小时值班电话。</p>'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'n4', icon: 'account', tag: 'activity', title: '邻里议事会:小区充电桩加装意见征集',
|
||||||
|
summary: '诚邀业主代表参与议事会,就充电桩选址与方案建言。', date: '2026-06-12', source: '社区居委会',
|
||||||
|
readCount: 473, pinned: false,
|
||||||
|
content: '<p>为缓解小区新能源车充电难题,社区拟在地面停车区加装公共充电桩。现面向全体业主征集选址与方案意见,欢迎参加本月邻里议事会,共同决定社区事务。</p>'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'n5', icon: 'gift', tag: 'notice', title: '端午粽香·社区敬老慰问活动回顾',
|
||||||
|
summary: '社区联合志愿者为高龄独居老人送上粽子与节日问候。', date: '2026-06-09', source: '社区志愿队',
|
||||||
|
readCount: 805, pinned: false,
|
||||||
|
content: '<p>端午节前夕,社区志愿者走访慰问了 30 余位高龄独居老人,送上亲手包的粽子与节日祝福。邻里互助、守望相助,是九九社区一直倡导的温暖底色。</p>'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
/** 社区活动(image 为本地封面,空串则用 sceneBg 渐变兜底) */
|
||||||
|
export const activities = [
|
||||||
|
{
|
||||||
|
id: 'a6', scene: 'festival', status: 'ended', title: '端午竹编小龙舟手工活动', subtitle: '粽香艾草间,一起把龙舟装进端午记忆',
|
||||||
|
image: 'https://static.hshuishang.com/static/act-dragonboat.jpg', time: '2026-06-18 14:30 至 16:00',
|
||||||
|
place: '五常街道海汇社区党群服务中心', address: '五常街道海汇社区党群服务中心一楼活动室', distance: '4.08km',
|
||||||
|
quota: '限 25 人', enrolled: '25/25人', signupRange: '2026-06-16 10:00 至 2026-06-18 14:00', deadline: '2026-06-18 14:00',
|
||||||
|
fee: '免费', phone: '0571-8888 0618', organizer: '海汇社区联合九九社区',
|
||||||
|
points: ['认识端午节、龙舟与竹编手作的传统文化', '在老师指导下完成竹编龙舟骨架与端午挂饰', '活动材料由社区统一提供,成品可带回家留念'],
|
||||||
|
content: '<p>五月五,过端午。粽子香、艾草芳,龙舟划进手掌上。</p><p>本次活动将传统端午文化与亲子手作结合,老师会从竹编基础、龙舟结构和端午纹样讲起,带领居民一步步完成属于自己的竹编小龙舟。</p><p>活动现场提供竹篾、彩绳、粽子挂饰等材料,完成作品后还可参与社区端午作品展示与合影。</p>'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'a1', scene: 'handicraft', status: 'upcoming', title: '亲子手工课堂', subtitle: '一起动手,创意无限',
|
||||||
|
image: 'https://static.hshuishang.com/static/act-handicraft.jpg', time: '2025-05-10 10:00',
|
||||||
|
place: '橘园社区活动室', address: '橘园社区党群服务中心二楼手工教室', distance: '320m',
|
||||||
|
quota: '限 20 人', enrolled: '12/20人', signupRange: '2025-05-01 09:00 至 2025-05-09 18:00', deadline: '2025-05-09 18:00',
|
||||||
|
fee: '免费', phone: '021-5666 0510', organizer: '橘园社区',
|
||||||
|
points: ['亲子协作完成手工制作,增进亲子感情', '培养孩子动手能力与创造力,激发想象力', '作品展示与分享,收获成就感与快乐'],
|
||||||
|
content: '<p>本周末邀请专业老师带领小朋友们体验黏土手工,材料由社区免费提供。完成的作品可带回家,也可参与社区作品展。</p><p>适合 3-10 岁儿童及家长共同参与,名额有限,报满即止。</p>'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'a2', scene: 'market', status: 'upcoming', title: '邻里集市 · 乐享生活', subtitle: '好物共享,邻里更亲',
|
||||||
|
image: 'https://static.hshuishang.com/static/act-market.jpg', time: '2025-05-17 09:00',
|
||||||
|
place: '小区中心广场', address: '上河坊小区中心广场东侧', distance: '560m',
|
||||||
|
quota: '限 50 人', enrolled: '31/50人', signupRange: '2025-05-08 09:00 至 2025-05-16 20:00', deadline: '2025-05-16 20:00',
|
||||||
|
fee: '免费', phone: '021-5666 0517', organizer: '九九社区邻里中心',
|
||||||
|
points: ['闲置好物以物换物或低价转让', '现场分区:儿童玩具 / 二手书 / 生活用品', '报名后可领取专属摊位号'],
|
||||||
|
content: '<p>把家里的闲置好物带到市集,与邻居以物换物或低价转让。现场设有儿童玩具区、二手书区、生活用品区,欢迎全家一起来逛。</p><p>报名后可领取专属摊位号。</p>'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'a3', scene: 'garden', status: 'ongoing', title: '社区花园认养计划', subtitle: '一平米花园,邻里共养',
|
||||||
|
image: '', time: '2026-06-01 至 2026-08-31', place: '小区中央花园', address: '', distance: '',
|
||||||
|
quota: '剩余 8 个地块', enrolled: '16/24人', signupRange: '', deadline: '认养至 2026-08-31',
|
||||||
|
fee: '免费', phone: '', organizer: '',
|
||||||
|
points: ['认养小块菜地,自由种植花草蔬果', '社区统一提供工具与水源', '共建绿意社区,结识园艺邻居'],
|
||||||
|
content: '<p>社区中央花园开放小块菜地认养,认养后可自行种植花草蔬果,社区提供工具与水源。让我们一起把社区装扮得更绿意盎然。</p>'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'a4', scene: 'fitness', status: 'ongoing', title: '清晨广场健身操', subtitle: '动起来,遇见更好的邻居',
|
||||||
|
image: '', time: '每日 07:00 · 长期', place: '中心广场', address: '', distance: '',
|
||||||
|
quota: '无需报名亦可参加', enrolled: '不限人数', signupRange: '', deadline: '长期开放',
|
||||||
|
fee: '免费', phone: '', organizer: '',
|
||||||
|
points: ['专业志愿者带操,节奏舒缓适合各年龄', '报名后加入晨练群,接收每日提醒', '坚持打卡有机会获得社区健康好礼'],
|
||||||
|
content: '<p>社区健身志愿者每天清晨带操,舒缓的有氧节奏适合各年龄段邻居。报名后可加入晨练群,接收每日提醒。</p>'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'a5', scene: 'festival', status: 'ended', title: '端午包粽子邻里大赛', subtitle: '巧手包粽,温情端午',
|
||||||
|
image: '', time: '2026-06-08 周日 10:00', place: '社区活动中心', address: '', distance: '',
|
||||||
|
quota: '活动已结束', enrolled: '40/40人', signupRange: '', deadline: '报名已截止',
|
||||||
|
fee: '免费', phone: '', organizer: '',
|
||||||
|
points: ['邻里比拼包粽手艺,现场欢声笑语', '本次共 40 组家庭参与', '感谢参与,下次活动再见'],
|
||||||
|
content: '<p>端午佳节,邻里齐聚一堂比拼包粽手艺,现场欢声笑语不断。本次活动共有 40 组家庭参与,感谢大家的热情参与,我们下次活动再见。</p>'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
/** 便民电话 */
|
||||||
|
export const phoneTip = '社区常用便民电话,点击即可一键拨打'
|
||||||
|
export const phoneList = [
|
||||||
|
{ id: 'P01', name: '物业服务中心', phone: '021-8266 1234', time: '服务时间 08:00-20:00', icon: 'server-man', iconBg: '#f53d42' },
|
||||||
|
{ id: 'P02', name: '社区卫生服务站', phone: '021-8266 5678', time: '服务时间 08:30-17:30', icon: 'plus', iconBg: '#24bd6c' },
|
||||||
|
{ id: 'P03', name: '社区警务室', phone: '021-8266 0110', time: '24 小时值班', icon: 'account', iconBg: '#397eef' },
|
||||||
|
{ id: 'P04', name: '水电报修热线', phone: '021-8266 9000', time: '服务时间 07:00-22:00', icon: 'home', iconBg: '#ff8a24' },
|
||||||
|
{ id: 'P05', name: '社区便民服务台', phone: '021-8266 2233', time: '服务时间 09:00-18:00', icon: 'server-man', iconBg: '#ff4164' }
|
||||||
|
]
|
||||||
|
|
||||||
|
// ===== 查找辅助 =====
|
||||||
|
export function findNotice(id) { return notices.find(n => n.id === id) }
|
||||||
|
export function findActivity(id) { return activities.find(a => a.id === id) }
|
||||||
|
|
||||||
|
// ===== 关注 / 报名状态(本地存储,初始种子与设计稿一致)=====
|
||||||
|
const K_FN = 'fc_followed_notices'
|
||||||
|
const K_JA = 'fc_joined_acts'
|
||||||
|
const K_FA = 'fc_followed_acts'
|
||||||
|
function getArr(key, seed) {
|
||||||
|
const v = uni.getStorageSync(key)
|
||||||
|
if (v === '' || v === undefined || v === null) { uni.setStorageSync(key, seed); return seed.slice() }
|
||||||
|
return Array.isArray(v) ? v : seed.slice()
|
||||||
|
}
|
||||||
|
function toggle(key, seed, id) {
|
||||||
|
const arr = getArr(key, seed)
|
||||||
|
const i = arr.indexOf(id)
|
||||||
|
if (i >= 0) arr.splice(i, 1); else arr.push(id)
|
||||||
|
uni.setStorageSync(key, arr)
|
||||||
|
return i < 0
|
||||||
|
}
|
||||||
|
export function isNoticeFollowed(id) { return getArr(K_FN, ['n1']).includes(id) }
|
||||||
|
export function toggleNoticeFollow(id) { return toggle(K_FN, ['n1'], id) }
|
||||||
|
export function isActivityJoined(id) { return getArr(K_JA, ['a3', 'a6']).includes(id) }
|
||||||
|
export function toggleActivityJoin(id) { return toggle(K_JA, ['a3', 'a6'], id) }
|
||||||
|
export function isActivityFollowed(id) { return getArr(K_FA, ['a1']).includes(id) }
|
||||||
|
export function toggleActivityFollow(id) { return toggle(K_FA, ['a1'], id) }
|
||||||
218
packages/futureCommunity/index/index.vue
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
<!--
|
||||||
|
页面:未来社区(九九社区)主页 —— 还原设计稿 liuyuanxitong-yidong/pages/community/community.vue
|
||||||
|
内容为静态 mock(见 ../data.js)。挂到首页金刚区「未来社区」跳转目标。
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<view class="fc-page">
|
||||||
|
<!-- 头部 -->
|
||||||
|
<view class="fc-hd" :style="{ paddingTop: statusBarTop + 'px' }">
|
||||||
|
<view class="fc-hd-bar">
|
||||||
|
<view class="fc-back" @tap="goBack"><u-icon name="arrow-left" color="#1a1a1a" size="38"></u-icon></view>
|
||||||
|
<text class="fc-hd-title">九九社区</text>
|
||||||
|
</view>
|
||||||
|
<view class="fc-loc" @tap="goSelect">
|
||||||
|
<u-icon name="map" color="#e9272d" size="26"></u-icon>
|
||||||
|
<text class="fc-loc-txt">{{ locationInfo.community }}</text>
|
||||||
|
<u-icon name="arrow-down" color="#9098a0" size="20"></u-icon>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="fc-body">
|
||||||
|
<!-- 轮播 -->
|
||||||
|
<swiper class="fc-hero" :autoplay="true" :interval="4000" :duration="500" circular indicator-dots
|
||||||
|
indicator-active-color="#fff" indicator-color="rgba(255,255,255,0.5)">
|
||||||
|
<swiper-item v-for="s in heroSlides" :key="s.key">
|
||||||
|
<view class="fc-hero-slide" :style="{ background: s.bg }">
|
||||||
|
<view class="fc-hero-mask"></view>
|
||||||
|
<view class="fc-hero-copy">
|
||||||
|
<text class="fc-hero-title">{{ s.title }}</text>
|
||||||
|
<text class="fc-hero-sub">{{ s.subtitle }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</swiper-item>
|
||||||
|
</swiper>
|
||||||
|
|
||||||
|
<!-- 快捷入口 -->
|
||||||
|
<view class="fc-shortcut">
|
||||||
|
<view class="fc-sc-item" v-for="s in shortcuts" :key="s.key" @tap="onShortcut(s)">
|
||||||
|
<view class="fc-sc-icon" :style="{ background: s.bg }">
|
||||||
|
<u-icon :name="s.icon" color="#fff" size="40"></u-icon>
|
||||||
|
</view>
|
||||||
|
<text class="fc-sc-label">{{ s.label }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 社区公告 -->
|
||||||
|
<view class="fc-sec-head">
|
||||||
|
<view class="fc-sec-left"><text class="fc-sec-bar"></text><text class="fc-sec-title">社区公告</text></view>
|
||||||
|
<text class="fc-sec-more" @tap="goNoticeList">查看更多 ›</text>
|
||||||
|
</view>
|
||||||
|
<view class="fc-notice-panel">
|
||||||
|
<view class="fc-notice" v-for="n in topNotices" :key="n.id" @tap="goNoticeDetail(n)">
|
||||||
|
<view class="fc-notice-icon"><u-icon :name="n.icon" color="#e43f43" size="34"></u-icon></view>
|
||||||
|
<view class="fc-notice-main">
|
||||||
|
<text class="fc-notice-title">{{ n.title }}</text>
|
||||||
|
<text class="fc-notice-sum">{{ n.summary }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="fc-notice-aside">
|
||||||
|
<text class="fc-tag" :style="tagStyle(n.tag)">{{ tagLabel(n.tag) }}</text>
|
||||||
|
<text class="fc-notice-date">{{ n.date }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 社区活动 -->
|
||||||
|
<view class="fc-sec-head">
|
||||||
|
<view class="fc-sec-left"><text class="fc-sec-bar"></text><text class="fc-sec-title">社区活动</text></view>
|
||||||
|
<text class="fc-sec-more" @tap="goActivityList">查看更多 ›</text>
|
||||||
|
</view>
|
||||||
|
<view class="fc-act-list">
|
||||||
|
<view class="fc-act" v-for="a in topActivities" :key="a.id" @tap="goActivityDetail(a)">
|
||||||
|
<view class="fc-act-cover">
|
||||||
|
<image v-if="a.image" :src="a.image" class="fc-act-img" mode="aspectFill" />
|
||||||
|
<view v-else class="fc-act-img" :style="{ background: sceneBg(a.scene) }"></view>
|
||||||
|
<text class="fc-act-badge" :style="statusStyle(a.status)">{{ statusLabel(a.status) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="fc-act-info">
|
||||||
|
<text class="fc-act-title">{{ a.title }}</text>
|
||||||
|
<text class="fc-act-fee">{{ a.fee || '免费' }}</text>
|
||||||
|
<text class="fc-act-sub">{{ a.subtitle }}</text>
|
||||||
|
<view class="fc-act-row"><u-icon name="clock" color="#9098a0" size="22"></u-icon><text>{{ a.time }}</text></view>
|
||||||
|
<view class="fc-act-row"><u-icon name="map" color="#9098a0" size="22"></u-icon><text>{{ a.place }}</text></view>
|
||||||
|
<view class="fc-act-foot">
|
||||||
|
<view class="fc-act-row"><u-icon name="account" color="#9098a0" size="22"></u-icon><text>{{ a.enrolled }}</text></view>
|
||||||
|
<text class="fc-act-btn" :class="{ done: isJoined(a.id) }" @tap.stop="onJoin(a)">{{ isJoined(a.id) ? '已报名' : '立即报名' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="fc-spacer"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { NavgateTo } from '@/utils'
|
||||||
|
import {
|
||||||
|
locationInfo, shortcuts, heroSlides, notices, activities,
|
||||||
|
NOTICE_TAG_META, ACTIVITY_STATUS_META, ACTIVITY_SCENE_BG,
|
||||||
|
isActivityJoined, toggleActivityJoin
|
||||||
|
} from '../data.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
statusBarTop: 20,
|
||||||
|
locationInfo, shortcuts, heroSlides,
|
||||||
|
topNotices: notices.slice(0, 3),
|
||||||
|
topActivities: activities.filter(a => a.status !== 'ended').slice(0, 2),
|
||||||
|
joinedIds: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoad() {
|
||||||
|
const sys = uni.getSystemInfoSync()
|
||||||
|
this.statusBarTop = sys.statusBarHeight || 20
|
||||||
|
},
|
||||||
|
onShow() {
|
||||||
|
this.joinedIds = activities.filter(a => isActivityJoined(a.id)).map(a => a.id)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
tagLabel(t) { return (NOTICE_TAG_META[t] || {}).label || '' },
|
||||||
|
tagStyle(t) { const m = NOTICE_TAG_META[t] || {}; return { color: m.color, background: m.bg } },
|
||||||
|
statusLabel(s) { return (ACTIVITY_STATUS_META[s] || {}).label || '' },
|
||||||
|
statusStyle(s) { const m = ACTIVITY_STATUS_META[s] || {}; return { color: m.color, background: m.bg } },
|
||||||
|
sceneBg(s) { return ACTIVITY_SCENE_BG[s] || ACTIVITY_SCENE_BG.handicraft },
|
||||||
|
isJoined(id) { return this.joinedIds.includes(id) },
|
||||||
|
goBack() { uni.navigateBack({ delta: 1, fail: () => NavgateTo('/pages/index2/index2') }) },
|
||||||
|
goSelect() { uni.showToast({ title: '当前社区:' + this.locationInfo.community, icon: 'none' }) },
|
||||||
|
onShortcut(s) {
|
||||||
|
const map = {
|
||||||
|
notice: '/packages/futureCommunity/noticeList/index',
|
||||||
|
activity: '/packages/futureCommunity/activityList/index',
|
||||||
|
phone: '/packages/futureCommunity/conveniencePhone/index',
|
||||||
|
house: '/packages/community/index/index',
|
||||||
|
goodshop: '/packages/localLife/index/index'
|
||||||
|
}
|
||||||
|
if (map[s.key]) NavgateTo(map[s.key], { isLogin: false })
|
||||||
|
else uni.showToast({ title: s.label + '功能开发中', icon: 'none' })
|
||||||
|
},
|
||||||
|
goNoticeList() { NavgateTo('/packages/futureCommunity/noticeList/index', { isLogin: false }) },
|
||||||
|
goActivityList() { NavgateTo('/packages/futureCommunity/activityList/index', { isLogin: false }) },
|
||||||
|
goNoticeDetail(n) { NavgateTo('/packages/futureCommunity/noticeDetail/index?id=' + n.id, { isLogin: false }) },
|
||||||
|
goActivityDetail(a) { NavgateTo('/packages/futureCommunity/activityDetail/index?id=' + a.id, { isLogin: false }) },
|
||||||
|
onJoin(a) {
|
||||||
|
if (a.status === 'ended') { uni.showToast({ title: '活动已结束', icon: 'none' }); return }
|
||||||
|
const joined = toggleActivityJoin(a.id)
|
||||||
|
this.joinedIds = activities.filter(x => isActivityJoined(x.id)).map(x => x.id)
|
||||||
|
uni.showToast({ title: joined ? '报名成功' : '已取消报名', icon: 'none' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
page { background: #f5f6f7; }
|
||||||
|
.fc-page { min-height: 100vh; background: #f5f6f7; }
|
||||||
|
|
||||||
|
/* 头部 */
|
||||||
|
.fc-hd { position: sticky; top: 0; z-index: 100; background: #fff; padding: 0 24rpx 16rpx; }
|
||||||
|
.fc-hd-bar { position: relative; display: flex; align-items: center; justify-content: center; height: 80rpx; }
|
||||||
|
.fc-back { position: absolute; left: 0; top: 0; height: 80rpx; display: flex; align-items: center; }
|
||||||
|
.fc-hd-title { font-size: 34rpx; font-weight: 700; color: #1a1a1a; }
|
||||||
|
.fc-loc { display: flex; align-items: center; }
|
||||||
|
.fc-loc-txt { margin: 0 8rpx; font-size: 26rpx; color: #333; }
|
||||||
|
|
||||||
|
.fc-body { padding: 20rpx; }
|
||||||
|
|
||||||
|
/* 轮播 */
|
||||||
|
.fc-hero { width: 100%; height: 286rpx; border-radius: 24rpx; overflow: hidden; }
|
||||||
|
.fc-hero-slide { position: relative; width: 100%; height: 286rpx; }
|
||||||
|
.fc-hero-mask { position: absolute; inset: 0; background: linear-gradient(90deg, rgba(40,25,20,0.5) 0%, rgba(40,25,20,0.04) 72%); }
|
||||||
|
.fc-hero-copy { position: absolute; left: 36rpx; bottom: 46rpx; z-index: 2; display: flex; flex-direction: column; }
|
||||||
|
.fc-hero-title { font-size: 40rpx; font-weight: 800; color: #fff; }
|
||||||
|
.fc-hero-sub { margin-top: 10rpx; font-size: 24rpx; color: rgba(255,255,255,0.92); }
|
||||||
|
|
||||||
|
/* 快捷入口 */
|
||||||
|
.fc-shortcut { display: flex; justify-content: space-between; background: #fff; border-radius: 22rpx; padding: 28rpx 16rpx; margin-top: 20rpx; }
|
||||||
|
.fc-sc-item { display: flex; flex-direction: column; align-items: center; }
|
||||||
|
.fc-sc-icon { width: 92rpx; height: 92rpx; border-radius: 50%; display: flex; align-items: center; justify-content: center; }
|
||||||
|
.fc-sc-label { margin-top: 14rpx; font-size: 24rpx; color: #333; }
|
||||||
|
|
||||||
|
/* 分区标题 */
|
||||||
|
.fc-sec-head { display: flex; align-items: center; justify-content: space-between; margin: 32rpx 4rpx 18rpx; }
|
||||||
|
.fc-sec-left { display: flex; align-items: center; }
|
||||||
|
.fc-sec-bar { width: 6rpx; height: 30rpx; background: #e9272d; border-radius: 3rpx; margin-right: 14rpx; }
|
||||||
|
.fc-sec-title { font-size: 32rpx; font-weight: 700; color: #1a1a1a; }
|
||||||
|
.fc-sec-more { font-size: 24rpx; color: #9aa0a8; }
|
||||||
|
|
||||||
|
/* 公告 */
|
||||||
|
.fc-notice-panel { background: #fff; border-radius: 22rpx; padding: 6rpx 24rpx; }
|
||||||
|
.fc-notice { display: flex; align-items: center; padding: 24rpx 0; border-bottom: 1rpx solid #f2f3f5; }
|
||||||
|
.fc-notice:last-child { border-bottom: none; }
|
||||||
|
.fc-notice-icon { flex-shrink: 0; width: 64rpx; height: 64rpx; border-radius: 50%; background: #fff1f1; display: flex; align-items: center; justify-content: center; margin-right: 18rpx; }
|
||||||
|
.fc-notice-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
|
||||||
|
.fc-notice-title { font-size: 28rpx; font-weight: 600; color: #1a1a1a; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
||||||
|
.fc-notice-sum { margin-top: 8rpx; font-size: 22rpx; color: #9098a0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
||||||
|
.fc-notice-aside { flex-shrink: 0; margin-left: 16rpx; display: flex; flex-direction: column; align-items: flex-end; }
|
||||||
|
.fc-tag { font-size: 20rpx; padding: 2rpx 12rpx; border-radius: 6rpx; }
|
||||||
|
.fc-notice-date { margin-top: 10rpx; font-size: 20rpx; color: #b6bcc4; }
|
||||||
|
|
||||||
|
/* 活动 */
|
||||||
|
.fc-act-list { display: flex; flex-direction: column; }
|
||||||
|
.fc-act { display: flex; background: #fff; border-radius: 22rpx; overflow: hidden; margin-bottom: 20rpx; }
|
||||||
|
.fc-act-cover { position: relative; width: 260rpx; flex-shrink: 0; }
|
||||||
|
.fc-act-img { width: 260rpx; height: 100%; min-height: 220rpx; display: block; }
|
||||||
|
.fc-act-badge { position: absolute; left: 12rpx; top: 12rpx; font-size: 20rpx; padding: 2rpx 12rpx; border-radius: 6rpx; }
|
||||||
|
.fc-act-info { position: relative; flex: 1; min-width: 0; padding: 18rpx 20rpx; display: flex; flex-direction: column; }
|
||||||
|
.fc-act-title { font-size: 30rpx; font-weight: 700; color: #1a1a1a; max-width: 280rpx; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
||||||
|
.fc-act-fee { position: absolute; right: 20rpx; top: 18rpx; font-size: 24rpx; color: #e9272d; font-weight: 700; }
|
||||||
|
.fc-act-sub { margin-top: 8rpx; font-size: 22rpx; color: #9098a0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
||||||
|
.fc-act-row { display: flex; align-items: center; margin-top: 10rpx; font-size: 22rpx; color: #6b7078; }
|
||||||
|
.fc-act-row text { margin-left: 8rpx; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
||||||
|
.fc-act-foot { display: flex; align-items: center; justify-content: space-between; margin-top: 14rpx; }
|
||||||
|
.fc-act-btn { flex-shrink: 0; background: linear-gradient(90deg, #ff5b5b 0%, #ef111b 100%); color: #fff; font-size: 22rpx; padding: 8rpx 24rpx; border-radius: 26rpx; }
|
||||||
|
.fc-act-btn.done { background: #eef0f3; color: #9aa0a8; }
|
||||||
|
|
||||||
|
.fc-spacer { height: 40rpx; }
|
||||||
|
</style>
|
||||||
78
packages/futureCommunity/noticeDetail/index.vue
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
<!-- 未来社区 - 公告详情 -->
|
||||||
|
<template>
|
||||||
|
<view class="nd-page">
|
||||||
|
<view class="fc-nav" :style="{ paddingTop: statusBarTop + 'px' }">
|
||||||
|
<view class="fc-nav-bar">
|
||||||
|
<view class="fc-back" @tap="goBack"><u-icon name="arrow-left" color="#1a1a1a" size="38"></u-icon></view>
|
||||||
|
<text class="fc-nav-title">公告详情</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="nd-body" v-if="notice">
|
||||||
|
<view class="nd-card">
|
||||||
|
<text class="fc-tag" :style="tagStyle(notice.tag)">{{ tagLabel(notice.tag) }}</text>
|
||||||
|
<text class="nd-title">{{ notice.title }}</text>
|
||||||
|
<view class="nd-meta">
|
||||||
|
<u-icon name="clock" color="#b6bcc4" size="22"></u-icon>
|
||||||
|
<text>{{ notice.date }}</text>
|
||||||
|
<text class="nd-meta-dot">·</text>
|
||||||
|
<text>上河坊·九九社区</text>
|
||||||
|
</view>
|
||||||
|
<rich-text class="nd-content" :nodes="notice.content"></rich-text>
|
||||||
|
</view>
|
||||||
|
<view class="fc-spacer"></view>
|
||||||
|
</view>
|
||||||
|
<view v-else class="fc-empty">公告不存在</view>
|
||||||
|
|
||||||
|
<view class="nd-foot" v-if="notice">
|
||||||
|
<text class="nd-share" @tap="onShare">分享</text>
|
||||||
|
<text class="nd-follow" :class="{ on: followed }" @tap="onFollow">{{ followed ? '已关注' : '关注公告' }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { findNotice, NOTICE_TAG_META, isNoticeFollowed, toggleNoticeFollow } from '../data.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() { return { statusBarTop: 20, notice: null, followed: false } },
|
||||||
|
onLoad(options) {
|
||||||
|
const sys = uni.getSystemInfoSync()
|
||||||
|
this.statusBarTop = sys.statusBarHeight || 20
|
||||||
|
this.notice = findNotice(options && options.id) || null
|
||||||
|
if (this.notice) this.followed = isNoticeFollowed(this.notice.id)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
tagLabel(t) { return (NOTICE_TAG_META[t] || {}).label || '' },
|
||||||
|
tagStyle(t) { const m = NOTICE_TAG_META[t] || {}; return { color: m.color, background: m.bg } },
|
||||||
|
goBack() { uni.navigateBack({ delta: 1 }) },
|
||||||
|
onShare() { uni.showToast({ title: '分享功能开发中', icon: 'none' }) },
|
||||||
|
onFollow() { this.followed = toggleNoticeFollow(this.notice.id); uni.showToast({ title: this.followed ? '已关注' : '已取消关注', icon: 'none' }) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
page { background: #f5f6f7; }
|
||||||
|
.nd-page { min-height: 100vh; background: #f5f6f7; padding-bottom: 130rpx; }
|
||||||
|
.fc-nav { position: sticky; top: 0; z-index: 100; background: #fff; padding: 0 24rpx 16rpx; }
|
||||||
|
.fc-nav-bar { position: relative; display: flex; align-items: center; justify-content: center; height: 88rpx; }
|
||||||
|
.fc-back { position: absolute; left: 0; top: 0; height: 88rpx; display: flex; align-items: center; }
|
||||||
|
.fc-nav-title { font-size: 34rpx; font-weight: 700; color: #1a1a1a; }
|
||||||
|
|
||||||
|
.nd-body { padding: 20rpx; }
|
||||||
|
.nd-card { background: #fff; border-radius: 20rpx; padding: 30rpx; }
|
||||||
|
.fc-tag { display: inline-block; font-size: 22rpx; padding: 4rpx 16rpx; border-radius: 6rpx; }
|
||||||
|
.nd-title { display: block; font-size: 38rpx; font-weight: 800; color: #1a1a1a; margin-top: 18rpx; line-height: 1.4; }
|
||||||
|
.nd-meta { display: flex; align-items: center; margin-top: 18rpx; font-size: 22rpx; color: #b6bcc4; }
|
||||||
|
.nd-meta text { margin-left: 6rpx; }
|
||||||
|
.nd-meta-dot { margin: 0 10rpx; }
|
||||||
|
.nd-content { display: block; margin-top: 24rpx; font-size: 28rpx; color: #333; line-height: 1.8; }
|
||||||
|
|
||||||
|
.nd-foot { position: fixed; left: 0; right: 0; bottom: 0; background: #fff; display: flex; align-items: center; gap: 20rpx; padding: 16rpx 24rpx calc(16rpx + env(safe-area-inset-bottom)); box-shadow: 0 -4rpx 20rpx rgba(0,0,0,0.04); }
|
||||||
|
.nd-share { flex: 1; text-align: center; font-size: 30rpx; color: #333; border: 1rpx solid #e5e5e5; border-radius: 44rpx; padding: 18rpx 0; }
|
||||||
|
.nd-follow { flex: 1; text-align: center; font-size: 30rpx; color: #fff; background: linear-gradient(90deg, #ff5b5b 0%, #ef111b 100%); border-radius: 44rpx; padding: 18rpx 0; }
|
||||||
|
.nd-follow.on { background: #eef0f3; color: #9aa0a8; }
|
||||||
|
.fc-empty { text-align: center; color: #9aa0a8; font-size: 26rpx; padding: 120rpx 0; }
|
||||||
|
.fc-spacer { height: 20rpx; }
|
||||||
|
</style>
|
||||||
109
packages/futureCommunity/noticeList/index.vue
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<!-- 未来社区 - 社区公告列表 -->
|
||||||
|
<template>
|
||||||
|
<view class="nl-page">
|
||||||
|
<view class="fc-nav" :style="{ paddingTop: statusBarTop + 'px' }">
|
||||||
|
<view class="fc-nav-bar">
|
||||||
|
<view class="fc-back" @tap="goBack"><u-icon name="arrow-left" color="#1a1a1a" size="38"></u-icon></view>
|
||||||
|
<view class="fc-nav-tt"><text class="fc-nav-title">社区公告</text><text class="fc-nav-sub">上河坊·九九社区</text></view>
|
||||||
|
</view>
|
||||||
|
<scroll-view class="nl-tabs" scroll-x>
|
||||||
|
<text class="nl-tab" :class="{ on: activeTab === t.key }" v-for="t in NOTICE_TABS" :key="t.key" @tap="activeTab = t.key">{{ t.label }}</text>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="nl-body">
|
||||||
|
<!-- 置顶(全部tab) -->
|
||||||
|
<view class="nl-pinned" v-if="activeTab === 'all' && pinned" @tap="goDetail(pinned)">
|
||||||
|
<view class="nl-pin-corner">置顶</view>
|
||||||
|
<view class="nl-pin-head">
|
||||||
|
<view class="nl-icon" :style="{ background: '#fff1f1' }"><u-icon :name="pinned.icon" color="#e43f43" size="34"></u-icon></view>
|
||||||
|
<view class="nl-pin-tt">
|
||||||
|
<text class="nl-title">{{ pinned.title }}</text>
|
||||||
|
<text class="nl-meta">{{ pinned.date }} · {{ pinned.source }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<text class="nl-sum">{{ pinned.summary }}</text>
|
||||||
|
<text class="nl-read">{{ pinned.readCount }} 次阅读</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="nl-card" v-for="n in listNotices" :key="n.id" @tap="goDetail(n)">
|
||||||
|
<view class="nl-icon" :style="{ background: tagBg(n.tag, true) }"><u-icon :name="n.icon" :color="tagColor(n.tag)" size="34"></u-icon></view>
|
||||||
|
<view class="nl-main">
|
||||||
|
<view class="nl-row1">
|
||||||
|
<text class="nl-title">{{ n.title }}</text>
|
||||||
|
<text class="fc-tag" :style="tagStyle(n.tag)">{{ tagLabel(n.tag) }}</text>
|
||||||
|
</view>
|
||||||
|
<text class="nl-meta">{{ n.date }} · {{ n.source }}</text>
|
||||||
|
<text class="nl-sum">{{ n.summary }}</text>
|
||||||
|
<text class="nl-read">{{ n.readCount }} 次阅读</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="fc-empty" v-if="!listNotices.length && !(activeTab === 'all' && pinned)">暂无公告</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { NavgateTo } from '@/utils'
|
||||||
|
import { notices, NOTICE_TABS, NOTICE_TAG_META } from '../data.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return { statusBarTop: 20, NOTICE_TABS, activeTab: 'all', notices }
|
||||||
|
},
|
||||||
|
onLoad(options) {
|
||||||
|
const sys = uni.getSystemInfoSync()
|
||||||
|
this.statusBarTop = sys.statusBarHeight || 20
|
||||||
|
if (options && options.tab && NOTICE_TABS.some(t => t.key === options.tab)) this.activeTab = options.tab
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
pinned() { return this.notices.find(n => n.pinned) },
|
||||||
|
listNotices() {
|
||||||
|
if (this.activeTab === 'all') return this.notices.filter(n => !n.pinned)
|
||||||
|
return this.notices.filter(n => n.tag === this.activeTab)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
tagLabel(t) { return (NOTICE_TAG_META[t] || {}).label || '' },
|
||||||
|
tagColor(t) { return (NOTICE_TAG_META[t] || {}).color || '#666' },
|
||||||
|
tagBg(t) { return (NOTICE_TAG_META[t] || {}).bg || '#eee' },
|
||||||
|
tagStyle(t) { const m = NOTICE_TAG_META[t] || {}; return { color: m.color, background: m.bg } },
|
||||||
|
goBack() { uni.navigateBack({ delta: 1 }) },
|
||||||
|
goDetail(n) { NavgateTo('/packages/futureCommunity/noticeDetail/index?id=' + n.id, { isLogin: false }) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
page { background: #f5f6f7; }
|
||||||
|
.nl-page { min-height: 100vh; background: #f5f6f7; }
|
||||||
|
.fc-nav { position: sticky; top: 0; z-index: 100; background: #fff; padding: 0 24rpx; }
|
||||||
|
.fc-nav-bar { position: relative; display: flex; align-items: center; justify-content: center; min-height: 88rpx; }
|
||||||
|
.fc-back { position: absolute; left: 0; top: 0; height: 88rpx; display: flex; align-items: center; }
|
||||||
|
.fc-nav-tt { display: flex; flex-direction: column; align-items: center; }
|
||||||
|
.fc-nav-title { font-size: 34rpx; font-weight: 700; color: #1a1a1a; }
|
||||||
|
.fc-nav-sub { font-size: 20rpx; color: #9098a0; margin-top: 2rpx; }
|
||||||
|
.nl-tabs { white-space: nowrap; padding: 8rpx 0 14rpx; }
|
||||||
|
.nl-tab { display: inline-block; font-size: 28rpx; color: #6b7078; padding: 8rpx 6rpx; margin-right: 40rpx; }
|
||||||
|
.nl-tab.on { color: #e9272d; font-weight: 700; }
|
||||||
|
|
||||||
|
.nl-body { padding: 20rpx; }
|
||||||
|
.fc-tag { font-size: 20rpx; padding: 2rpx 12rpx; border-radius: 6rpx; flex-shrink: 0; }
|
||||||
|
.nl-icon { flex-shrink: 0; width: 64rpx; height: 64rpx; border-radius: 16rpx; display: flex; align-items: center; justify-content: center; }
|
||||||
|
.nl-title { font-size: 28rpx; font-weight: 600; color: #1a1a1a; }
|
||||||
|
.nl-meta { font-size: 20rpx; color: #b6bcc4; margin-top: 6rpx; }
|
||||||
|
.nl-sum { font-size: 24rpx; color: #6b7078; margin-top: 10rpx; }
|
||||||
|
.nl-read { font-size: 20rpx; color: #b6bcc4; margin-top: 10rpx; }
|
||||||
|
|
||||||
|
.nl-pinned { position: relative; background: #fff; border-radius: 20rpx; padding: 24rpx; margin-bottom: 20rpx; box-shadow: 0 6rpx 20rpx rgba(30,34,44,0.06); display: flex; flex-direction: column; }
|
||||||
|
.nl-pin-corner { position: absolute; right: 0; top: 0; background: #e9272d; color: #fff; font-size: 20rpx; padding: 4rpx 16rpx; border-radius: 0 20rpx 0 16rpx; }
|
||||||
|
.nl-pin-head { display: flex; align-items: center; }
|
||||||
|
.nl-pin-tt { margin-left: 18rpx; display: flex; flex-direction: column; }
|
||||||
|
|
||||||
|
.nl-card { display: flex; background: #fff; border-radius: 20rpx; padding: 24rpx; margin-bottom: 18rpx; }
|
||||||
|
.nl-main { flex: 1; min-width: 0; margin-left: 18rpx; display: flex; flex-direction: column; }
|
||||||
|
.nl-row1 { display: flex; align-items: center; justify-content: space-between; }
|
||||||
|
.nl-row1 .nl-title { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; margin-right: 12rpx; }
|
||||||
|
.fc-empty { text-align: center; color: #9aa0a8; font-size: 26rpx; padding: 100rpx 0; }
|
||||||
|
</style>
|
||||||
157
packages/homeService/catalog/index.vue
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
<!--
|
||||||
|
页面:未来到家 - 服务目录(金刚区图标点击进入)
|
||||||
|
格式:顶部「服务」标题 + 搜索栏;左侧小类竖向导航(选中红条高亮),
|
||||||
|
右侧按小类分组的服务网格(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>
|
||||||
@ -2,10 +2,10 @@
|
|||||||
<view class="hsd-page">
|
<view class="hsd-page">
|
||||||
<swiper class="hsd-swiper" autoplay circular v-if="carousel.length">
|
<swiper class="hsd-swiper" autoplay circular v-if="carousel.length">
|
||||||
<swiper-item v-for="(p, i) in carousel" :key="i">
|
<swiper-item v-for="(p, i) in carousel" :key="i">
|
||||||
<image :src="p" mode="aspectFill" class="hsd-swiper-img" />
|
<image :src="p" mode="aspectFit" class="hsd-swiper-img" />
|
||||||
</swiper-item>
|
</swiper-item>
|
||||||
</swiper>
|
</swiper>
|
||||||
<image v-else :src="picUrl + (info.cover_pic || '')" class="hsd-swiper-img" mode="aspectFill" />
|
<image v-else :src="picUrl + (info.cover_pic || '')" class="hsd-swiper-img" mode="aspectFit" />
|
||||||
|
|
||||||
<view class="hsd-main">
|
<view class="hsd-main">
|
||||||
<view class="hsd-name">{{ info.service_name }}</view>
|
<view class="hsd-name">{{ info.service_name }}</view>
|
||||||
@ -38,10 +38,10 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 详情图 -->
|
<!-- 服务流程图(可多张,按序展示) -->
|
||||||
<view class="hsd-block" v-if="detailPics.length">
|
<view class="hsd-block" v-if="flowChartList.length">
|
||||||
<view class="hsd-block-tit">服务详情</view>
|
<view class="hsd-block-tit">服务流程</view>
|
||||||
<image v-for="(p, i) in detailPics" :key="i" :src="p" mode="widthFix" class="hsd-detail-img" />
|
<image v-for="(p, i) in flowChartList" :key="i" :src="p" mode="widthFix" class="hsd-detail-img" />
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="hsd-bottom">
|
<view class="hsd-bottom">
|
||||||
@ -70,6 +70,7 @@ export default {
|
|||||||
info: {},
|
info: {},
|
||||||
carousel: [],
|
carousel: [],
|
||||||
detailPics: [],
|
detailPics: [],
|
||||||
|
flowChartList: [],
|
||||||
skuList: [],
|
skuList: [],
|
||||||
workerList: [],
|
workerList: [],
|
||||||
currentSku: null,
|
currentSku: null,
|
||||||
@ -90,8 +91,12 @@ export default {
|
|||||||
loadInfo() {
|
loadInfo() {
|
||||||
request(apiArr.serviceInfo, 'POST', { id: this.id }, {}, false).then(res => {
|
request(apiArr.serviceInfo, 'POST', { id: this.id }, {}, false).then(res => {
|
||||||
this.info = res || {}
|
this.info = res || {}
|
||||||
this.carousel = (res.carousel ? res.carousel.split(',') : []).filter(Boolean).map(p => p.startsWith('http') ? p : this.picUrl + p)
|
const toUrls = (str) => (str ? String(str).split(',') : []).filter(Boolean).map(p => p.startsWith('http') ? p : this.picUrl + p)
|
||||||
this.detailPics = (res.detail_pic ? res.detail_pic.split(',') : []).filter(Boolean).map(p => p.startsWith('http') ? p : this.picUrl + p)
|
// 轮播优先用封面图(后台维护的多图);无则兜底用历史 carousel 字段
|
||||||
|
const coverArr = toUrls(res.cover_pic)
|
||||||
|
this.carousel = coverArr.length ? coverArr : toUrls(res.carousel)
|
||||||
|
this.detailPics = toUrls(res.detail_pic)
|
||||||
|
this.flowChartList = toUrls(res.flow_chart) // 服务流程图,可多张
|
||||||
this.skuList = res.sku_list || []
|
this.skuList = res.sku_list || []
|
||||||
this.workerList = res.worker_list || []
|
this.workerList = res.worker_list || []
|
||||||
if (this.skuList.length) this.currentSku = this.skuList[0]
|
if (this.skuList.length) this.currentSku = this.skuList[0]
|
||||||
@ -128,7 +133,8 @@ export default {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.hsd-page { background: #f5f5f5; min-height: 100vh; padding-bottom: 130rpx; }
|
.hsd-page { background: #f5f5f5; min-height: 100vh; padding-bottom: 130rpx; }
|
||||||
.hsd-swiper, .hsd-swiper-img { width: 100%; height: 520rpx; }
|
/* 轮播按正方形完整显示封面(1:1),aspectFit 不裁剪;非方图留白居中 */
|
||||||
|
.hsd-swiper, .hsd-swiper-img { width: 100%; height: 750rpx; background: #f2f2f2; }
|
||||||
.hsd-main { background: #fff; padding: 24rpx; }
|
.hsd-main { background: #fff; padding: 24rpx; }
|
||||||
.hsd-name { font-size: 36rpx; font-weight: 600; color: #222; }
|
.hsd-name { font-size: 36rpx; font-weight: 600; color: #222; }
|
||||||
.hsd-pricedesc { font-size: 26rpx; color: #FF370B; margin-top: 14rpx; }
|
.hsd-pricedesc { font-size: 26rpx; color: #FF370B; margin-top: 14rpx; }
|
||||||
|
|||||||
@ -1,124 +1,461 @@
|
|||||||
|
<!--
|
||||||
|
页面:未来到家(原「到家服务」,按设计稿重做)
|
||||||
|
说明:顶部红头(品牌 + 城市 + 关键词搜索);二级分类以「金刚区」展示;
|
||||||
|
下面商品用瀑布流(与首页好店一致:两列、图片按原始宽高比)。
|
||||||
|
大类「到家服务 / 家政服务」的 tab 忽略,金刚区改为铺开展示两大类下的全部小类
|
||||||
|
(后端 /service/category/sub-list)。默认瀑布流取金刚区第一个小类的商品列表。
|
||||||
|
-->
|
||||||
<template>
|
<template>
|
||||||
<view class="hs-page">
|
<view class="fh-page">
|
||||||
<!-- 到家/家政 Tab -->
|
<!-- 顶部红头 -->
|
||||||
<view class="hs-tabs">
|
<view class="fh-hd" :style="{ paddingTop: statusBarTop + 'px' }">
|
||||||
<view class="hs-tab" :class="{ active: serviceType === 1 }" @tap="switchType(1)">到家服务</view>
|
<view class="fh-hd-top">
|
||||||
<view class="hs-tab" :class="{ active: serviceType === 2 }" @tap="switchType(2)">家政服务</view>
|
<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>
|
||||||
|
|
||||||
<view class="hs-body">
|
<view class="fh-body">
|
||||||
<!-- 左侧分类 -->
|
<!-- 金刚区:二级分类 -->
|
||||||
<scroll-view class="hs-cate" scroll-y>
|
<view class="fh-grid" v-if="categoryList.length">
|
||||||
<view class="hs-cate-item" :class="{ active: currentCate === 0 }" @tap="selectCate(0)">全部</view>
|
<view class="fh-grid-item" v-for="cat in categoryList" :key="cat.id" @tap="selectCate(cat)">
|
||||||
<view class="hs-cate-item" :class="{ active: currentCate === item.id }"
|
<image class="fh-grid-icon" :src="picUrl + cat.category_pic" mode="aspectFit" />
|
||||||
v-for="item in categoryList" :key="item.id" @tap="selectCate(item.id)">
|
<text class="fh-grid-txt" :class="{ on: currentCate === cat.id }">{{ cat.category_name }}</text>
|
||||||
{{ item.category_name }}
|
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</view>
|
||||||
|
|
||||||
<!-- 右侧服务列表 -->
|
<!-- 商品瀑布流(与首页好店一致:两列,图片按原始宽高比) -->
|
||||||
<scroll-view class="hs-list" scroll-y @scrolltolower="loadMore">
|
<view class="wf">
|
||||||
<view class="hs-card" v-for="item in serviceList" :key="item.id" @tap="toDetail(item)">
|
<view class="wf-col">
|
||||||
<image class="hs-card-pic" :src="picUrl + item.cover_pic" mode="aspectFill" />
|
<view class="wf-card" v-for="item in leftCol" :key="item.id" @tap="toDetail(item)">
|
||||||
<view class="hs-card-info">
|
<image class="wf-img" :src="item.showImg" :style="{ height: item.imgH + 'rpx' }" mode="aspectFill" />
|
||||||
<view class="hs-card-name">{{ item.service_name }}</view>
|
<view class="wf-meta">
|
||||||
<view class="hs-card-desc">{{ item.price_desc }}</view>
|
<view class="wf-name">{{ item.service_name }}</view>
|
||||||
<view class="hs-card-foot">
|
<view class="wf-desc" v-if="item.intro">{{ item.intro }}</view>
|
||||||
<text class="hs-card-tag">{{ serviceType === 1 ? '到家' : '家政' }}</text>
|
<text class="wf-tag" v-if="cateMap[item.category_id]">{{ cateMap[item.category_id] }}</text>
|
||||||
<text class="hs-card-btn">查看</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>
|
</view>
|
||||||
<view v-if="serviceList.length === 0" class="hs-empty">暂无服务</view>
|
<view class="wf-col">
|
||||||
</scroll-view>
|
<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>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { request, picUrl, NavgateTo } from '@/utils'
|
import { request, picUrl, NavgateTo, menuButtonInfo } from '@/utils'
|
||||||
import { apiArr } from '@/api/homeService'
|
import { apiArr } from '@/api/homeService'
|
||||||
|
|
||||||
|
// 瀑布单列图片宽度(rpx):与首页好店一致 (750-60)/2 = 345
|
||||||
|
const WF_COL_W = 345
|
||||||
|
// 卡片文字区估算高度,仅用于两列高度均衡
|
||||||
|
const WF_META_H = 150
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
picUrl,
|
picUrl,
|
||||||
serviceType: 1,
|
statusBarTop: 0,
|
||||||
|
cityName: '定位中',
|
||||||
|
keyword: '',
|
||||||
categoryList: [],
|
categoryList: [],
|
||||||
|
cateMap: {}, // 分类 id → 名称,用作商品卡片的红色标签
|
||||||
currentCate: 0,
|
currentCate: 0,
|
||||||
serviceList: [],
|
// 瀑布两列
|
||||||
pageNum: 1,
|
leftCol: [],
|
||||||
pageSize: 10,
|
rightCol: [],
|
||||||
hasMore: true
|
colHeights: [0, 0],
|
||||||
|
page_num: 1,
|
||||||
|
page_size: 10,
|
||||||
|
hasMore: true,
|
||||||
|
loadingMore: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLoad() {
|
onLoad() {
|
||||||
this.loadCategory()
|
const menu = menuButtonInfo()
|
||||||
this.loadList(true)
|
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: {
|
methods: {
|
||||||
switchType(t) {
|
async initData() {
|
||||||
if (this.serviceType === t) return
|
await this.loadCategory()
|
||||||
this.serviceType = t
|
// 默认取金刚区第一个分类的商品列表
|
||||||
this.currentCate = 0
|
this.currentCate = this.categoryList.length ? this.categoryList[0].id : 0
|
||||||
this.loadCategory()
|
this.keyword = ''
|
||||||
this.loadList(true)
|
await this.loadList(true)
|
||||||
},
|
},
|
||||||
selectCate(id) {
|
async loadCategory() {
|
||||||
this.currentCate = id
|
try {
|
||||||
this.loadList(true)
|
// 两大类(到家1+家政2)全部小类合并,供金刚区铺开展示
|
||||||
},
|
const res = await request(apiArr.categorySubList, 'POST', {}, {}, false)
|
||||||
loadCategory() {
|
this.categoryList = (res && res.rows) ? res.rows : []
|
||||||
request(apiArr.categoryList, 'POST', { service_type: this.serviceType }, {}, false).then(res => {
|
const map = {}
|
||||||
this.categoryList = res.rows || []
|
this.categoryList.forEach(c => { map[c.id] = c.category_name })
|
||||||
})
|
this.cateMap = map
|
||||||
},
|
} catch (e) {
|
||||||
loadList(reset) {
|
this.categoryList = []
|
||||||
if (reset) {
|
this.cateMap = {}
|
||||||
this.pageNum = 1
|
|
||||||
this.hasMore = true
|
|
||||||
this.serviceList = []
|
|
||||||
}
|
}
|
||||||
if (!this.hasMore) return
|
|
||||||
request(apiArr.serviceList, 'POST', {
|
|
||||||
service_type: this.serviceType,
|
|
||||||
category_id: this.currentCate,
|
|
||||||
page_num: this.pageNum,
|
|
||||||
page_size: this.pageSize
|
|
||||||
}, {}, false).then(res => {
|
|
||||||
const rows = res.rows || []
|
|
||||||
this.serviceList = this.serviceList.concat(rows)
|
|
||||||
this.hasMore = rows.length === this.pageSize
|
|
||||||
this.pageNum++
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
loadMore() {
|
selectCate(cat) {
|
||||||
this.loadList(false)
|
// 金刚区图标点击 → 打开服务目录页(左侧小类导航 + 右侧分组商品),定位到该小类
|
||||||
|
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) {
|
toDetail(item) {
|
||||||
NavgateTo('/packages/homeService/detail/index?id=' + item.id)
|
NavgateTo('/packages/homeService/detail/index?id=' + item.id)
|
||||||
|
},
|
||||||
|
goBack() {
|
||||||
|
uni.navigateBack({ delta: 1, fail: () => { NavgateTo('/pages/index2/index2') } })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.hs-page { display: flex; flex-direction: column; height: 100vh; background: #f5f5f5; }
|
page { background: #f5f6f8; }
|
||||||
.hs-tabs { display: flex; background: #fff; }
|
.fh-page { min-height: 100vh; background: #f5f6f8; }
|
||||||
.hs-tab { flex: 1; text-align: center; padding: 26rpx 0; font-size: 28rpx; color: #666; }
|
|
||||||
.hs-tab.active { color: #FF370B; font-weight: 600; border-bottom: 4rpx solid #FF370B; }
|
/* ===== 顶部红头(吸顶:搜索栏不随页面滚动) ===== */
|
||||||
.hs-body { flex: 1; display: flex; overflow: hidden; }
|
.fh-hd {
|
||||||
.hs-cate { width: 180rpx; background: #fafafa; height: 100%; }
|
position: sticky;
|
||||||
.hs-cate-item { padding: 28rpx 16rpx; font-size: 26rpx; color: #555; text-align: center; }
|
top: 0;
|
||||||
.hs-cate-item.active { background: #fff; color: #FF370B; font-weight: 600; }
|
z-index: 100;
|
||||||
.hs-list { flex: 1; padding: 20rpx; box-sizing: border-box; }
|
/* 设计稿:横向红色渐变 90deg #FF1A1F → #FF634A */
|
||||||
.hs-card { display: flex; background: #fff; border-radius: 12rpx; padding: 16rpx; margin-bottom: 16rpx; }
|
background: linear-gradient(90deg, #ff1a1f 0%, #ff634a 100%);
|
||||||
.hs-card-pic { width: 200rpx; height: 200rpx; border-radius: 8rpx; flex-shrink: 0; background: #f0f0f0; }
|
padding-left: 28rpx;
|
||||||
.hs-card-info { flex: 1; margin-left: 18rpx; display: flex; flex-direction: column; min-width: 0; }
|
padding-right: 28rpx;
|
||||||
.hs-card-name { font-size: 30rpx; color: #222; font-weight: 600; }
|
padding-bottom: 28rpx;
|
||||||
.hs-card-desc { font-size: 24rpx; color: #999; margin-top: 12rpx; flex: 1; }
|
}
|
||||||
.hs-card-foot { display: flex; align-items: center; justify-content: space-between; }
|
.fh-hd-top {
|
||||||
.hs-card-tag { font-size: 20rpx; color: #FF370B; border: 1rpx solid #FFD9CD; border-radius: 6rpx; padding: 2rpx 10rpx; }
|
position: relative;
|
||||||
.hs-card-btn { background: linear-gradient(91deg, #FF7658, #FF370B); color: #fff; font-size: 24rpx; padding: 8rpx 28rpx; border-radius: 30rpx; }
|
display: flex;
|
||||||
.hs-empty { text-align: center; color: #999; font-size: 26rpx; padding: 80rpx 0; }
|
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 {
|
||||||
|
/* 设计稿分类图标底 46×46px ≈ 80rpx,浅暖底 #FFF4F0 */
|
||||||
|
width: 80rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
border-radius: 18rpx;
|
||||||
|
background: #fff4f0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 8rpx;
|
||||||
|
}
|
||||||
|
.fh-grid-txt {
|
||||||
|
margin-top: 12rpx;
|
||||||
|
/* 设计稿分类文字 14px ≈ 24rpx,#111111 */
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #111;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.fh-grid-txt.on {
|
||||||
|
color: #ff3929;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 瀑布流(与首页好店一致) ===== */
|
||||||
|
.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>
|
</style>
|
||||||
|
|||||||
323
packages/smartDevice/home/index.css
Normal file
@ -0,0 +1,323 @@
|
|||||||
|
/* ============ 智能设备聚合首页 样式(还原设计稿 device 页) ============ */
|
||||||
|
page {
|
||||||
|
background: #f5f6f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.device-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #f5f6f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 内容体:去掉渐变续色;保留 12rpx 顶部间距(Hero 与页头之间的空隙,纯页面底色)。 */
|
||||||
|
.device-body {
|
||||||
|
padding-top: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 头部:居中标题 ===== */
|
||||||
|
.device-header {
|
||||||
|
padding-left: 30rpx;
|
||||||
|
padding-right: 30rpx;
|
||||||
|
/* 不要红头,保持页面底色 */
|
||||||
|
background: #f5f6f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 88rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1a1a1a;
|
||||||
|
letter-spacing: 1rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== Hero:红橙渐变底 + 叠加文案 ===== */
|
||||||
|
.device-hero {
|
||||||
|
position: relative;
|
||||||
|
/* margin-top 移到 .device-body 的 padding-top,避免 margin 塌陷露出灰底白条 */
|
||||||
|
margin: 0 30rpx;
|
||||||
|
height: 360rpx;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
/* 图片加载失败时露出的红橙渐变兜底 */
|
||||||
|
background: linear-gradient(120deg, #f4141c 0%, #ff8758 100%);
|
||||||
|
box-shadow: 0 12rpx 30rpx rgba(244, 20, 28, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 轮播底图 */
|
||||||
|
.hero-swiper {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 文字可读性遮罩:左深右透 */
|
||||||
|
.hero-mask {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 1;
|
||||||
|
background: linear-gradient(100deg, rgba(120, 20, 14, 0.7) 0%, rgba(180, 40, 24, 0.34) 52%, rgba(255, 135, 88, 0.06) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-overlay {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 40rpx 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-greeting {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-greeting-sub {
|
||||||
|
margin-top: 6rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: rgba(255, 255, 255, 0.85);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-title {
|
||||||
|
margin-top: 12rpx;
|
||||||
|
font-size: 46rpx;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #fff;
|
||||||
|
letter-spacing: -1rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-sub {
|
||||||
|
margin-top: 12rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 登录前双按钮 */
|
||||||
|
.hero-actions {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-btn {
|
||||||
|
height: 64rpx;
|
||||||
|
padding: 0 36rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-btn.primary {
|
||||||
|
background: #fff;
|
||||||
|
color: #ef111b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-btn.ghost {
|
||||||
|
margin-left: 20rpx;
|
||||||
|
border: 2rpx solid rgba(255, 255, 255, 0.9);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 登录后 4 特性标签 */
|
||||||
|
.hero-tags {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-tag {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 36rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-tag-icon {
|
||||||
|
width: 56rpx;
|
||||||
|
height: 56rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255, 255, 255, 0.22);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-tag-label {
|
||||||
|
margin-top: 10rpx;
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 设备分类卡:2 列网格 ===== */
|
||||||
|
.cat-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 24rpx 30rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-card {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 336rpx;
|
||||||
|
height: 240rpx;
|
||||||
|
margin-bottom: 18rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
background: linear-gradient(135deg, #fbf2f0 0%, #fdf8f7 100%);
|
||||||
|
box-shadow: 0 6rpx 18rpx rgba(0, 0, 0, 0.04);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 右下角产品几何块 */
|
||||||
|
.cat-pic {
|
||||||
|
position: absolute;
|
||||||
|
right: 14rpx;
|
||||||
|
bottom: 14rpx;
|
||||||
|
width: 130rpx;
|
||||||
|
height: 130rpx;
|
||||||
|
z-index: 0;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-pic-img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.illust-access { background: linear-gradient(180deg, #2c3440 0%, #4a5566 100%); }
|
||||||
|
.illust-parking { background: linear-gradient(180deg, #9ee7cf 0%, #62c99b 100%); }
|
||||||
|
.illust-guard { background: linear-gradient(180deg, #eef1f4 0%, #c9d2dc 100%); }
|
||||||
|
.illust-environment { background: linear-gradient(180deg, #2a2f3a 0%, #525a6b 100%); }
|
||||||
|
.illust-monitor { background: linear-gradient(180deg, #f0f2f5 0%, #cdd4dd 100%); }
|
||||||
|
.illust-water { background: linear-gradient(180deg, #eaf4ff 0%, #bcd8f5 100%); }
|
||||||
|
.illust-lighting { background: linear-gradient(180deg, #ffe9b0 0%, #f4c45a 100%); }
|
||||||
|
.illust-scene { background: linear-gradient(180deg, #ffe2e0 0%, #ffb9b3 100%); }
|
||||||
|
|
||||||
|
.cat-text {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-width: 220rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-title {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1a1a1a;
|
||||||
|
letter-spacing: 1rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cat-desc {
|
||||||
|
margin-top: 8rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
line-height: 1.4;
|
||||||
|
color: #9a9a9a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 底部行:锁 / 链接 */
|
||||||
|
.cat-foot {
|
||||||
|
position: absolute;
|
||||||
|
left: 24rpx;
|
||||||
|
bottom: 24rpx;
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.foot-lock {
|
||||||
|
margin-left: 6rpx;
|
||||||
|
font-size: 21rpx;
|
||||||
|
color: #b8b8b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.foot-action {
|
||||||
|
margin-left: 6rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #ef111b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 登录后底部统计条 ===== */
|
||||||
|
.stat-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 24rpx 30rpx 0;
|
||||||
|
padding: 24rpx 28rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
background: linear-gradient(135deg, #fff3f1 0%, #fff9f8 100%);
|
||||||
|
box-shadow: 0 6rpx 18rpx rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 56rpx;
|
||||||
|
height: 56rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: linear-gradient(135deg, #ff5e61 0%, #ef111b 100%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-text {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-left: 18rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-title {
|
||||||
|
font-size: 26rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1a1a1a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-sub {
|
||||||
|
margin-top: 6rpx;
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-num {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: 48rpx;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #ef111b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-unit {
|
||||||
|
margin-left: 6rpx;
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 底栏占位 */
|
||||||
|
.tabbar-spacer {
|
||||||
|
height: 160rpx;
|
||||||
|
}
|
||||||
193
packages/smartDevice/home/index.vue
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
<!--
|
||||||
|
页面:智能设备(聚合首页)
|
||||||
|
说明:按设计稿 liuyuanxitong-yidong/pages/device 整体还原到 uniapp。
|
||||||
|
顶部固定标题「智能设备」;登录前引导态(Hero 双按钮 + 8 卡锁文案),
|
||||||
|
登录后总览态(问候 + 4 特性标签 + 8 卡操作链接 + 底部统计条)。
|
||||||
|
「停车充电」卡挂到现有停车页 /packages/smartDevice/index/index,其余卡占位。
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<view class="device-page">
|
||||||
|
<!-- 头部:固定标题「智能设备」 -->
|
||||||
|
<view class="device-header" :style="{ paddingTop: statusBarHeight + 'px' }">
|
||||||
|
<view class="title-bar">
|
||||||
|
<text class="page-title">智能设备</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 内容体:承接页头渐变往下续色(渐变加在此,从页头下方 0 处起、与页头 #ff4848 无缝) -->
|
||||||
|
<view class="device-body">
|
||||||
|
<!-- Hero:图片轮播底图 + 叠加文案(登录前双按钮 / 登录后问候 + 4 特性标签) -->
|
||||||
|
<view class="device-hero">
|
||||||
|
<swiper class="hero-swiper" :autoplay="true" :interval="4000" :duration="500" circular>
|
||||||
|
<swiper-item v-for="(img, idx) in heroImages" :key="idx">
|
||||||
|
<image class="hero-img" :src="img" mode="aspectFill" />
|
||||||
|
</swiper-item>
|
||||||
|
</swiper>
|
||||||
|
<view class="hero-mask"></view>
|
||||||
|
|
||||||
|
<view class="hero-overlay">
|
||||||
|
<!-- 登录后问候 -->
|
||||||
|
<block v-if="isLogin">
|
||||||
|
<text class="hero-greeting">您好,{{ nickName || '业主' }}</text>
|
||||||
|
<text class="hero-greeting-sub">欢迎查看您所在社区的智能设备</text>
|
||||||
|
<text class="hero-title">社区智能设备总览</text>
|
||||||
|
<text class="hero-sub">一站式浏览社区设备,智慧生活触手可及</text>
|
||||||
|
<view class="hero-tags">
|
||||||
|
<view class="hero-tag" v-for="tag in heroTags" :key="tag.key">
|
||||||
|
<view class="hero-tag-icon">
|
||||||
|
<u-icon :name="tag.icon" color="#fff" size="30"></u-icon>
|
||||||
|
</view>
|
||||||
|
<text class="hero-tag-label">{{ tag.label }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</block>
|
||||||
|
|
||||||
|
<!-- 登录前引导 -->
|
||||||
|
<block v-else>
|
||||||
|
<text class="hero-title">连接社区智能设备</text>
|
||||||
|
<text class="hero-sub">门禁、停车、安防、环境,一页轻松查看</text>
|
||||||
|
<view class="hero-actions">
|
||||||
|
<view class="hero-btn primary" @click="goLogin">立即登录</view>
|
||||||
|
<view class="hero-btn ghost" @click="handlePreview">先看看</view>
|
||||||
|
</view>
|
||||||
|
</block>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 设备分类卡:2 列网格 -->
|
||||||
|
<view class="cat-grid">
|
||||||
|
<view class="cat-card" v-for="cat in deviceCategories" :key="cat.key" @click="handleCardTap(cat)">
|
||||||
|
<!-- 右下角产品图(还原设计稿,渐变块为兜底底色) -->
|
||||||
|
<view class="cat-pic" :class="'illust-' + cat.illust">
|
||||||
|
<image class="cat-pic-img" :src="cat.image" mode="aspectFill" />
|
||||||
|
</view>
|
||||||
|
<!-- 文案 -->
|
||||||
|
<view class="cat-text">
|
||||||
|
<text class="cat-title">{{ cat.title }}</text>
|
||||||
|
<text class="cat-desc">{{ cat.desc[0] }}</text>
|
||||||
|
<text class="cat-desc">{{ cat.desc[1] }}</text>
|
||||||
|
</view>
|
||||||
|
<!-- 底部:登录前锁文案 / 登录后红色链接 -->
|
||||||
|
<view class="cat-foot">
|
||||||
|
<block v-if="isLogin">
|
||||||
|
<u-icon name="play-circle" color="#ef111b" size="24"></u-icon>
|
||||||
|
<text class="foot-action">{{ cat.actionText }}</text>
|
||||||
|
</block>
|
||||||
|
<block v-else>
|
||||||
|
<u-icon name="lock" color="#b8b8b8" size="22"></u-icon>
|
||||||
|
<text class="foot-lock">{{ cat.lockText }}</text>
|
||||||
|
</block>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 登录后底部统计条 -->
|
||||||
|
<view class="stat-bar" v-if="isLogin">
|
||||||
|
<view class="stat-icon">
|
||||||
|
<u-icon name="home" color="#fff" size="30"></u-icon>
|
||||||
|
</view>
|
||||||
|
<view class="stat-text">
|
||||||
|
<text class="stat-title">社区已接入多类智能设备</text>
|
||||||
|
<text class="stat-sub">持续升级中,为您带来更智慧的社区体验</text>
|
||||||
|
</view>
|
||||||
|
<view class="stat-num">
|
||||||
|
<text class="stat-value">128+</text>
|
||||||
|
<text class="stat-unit">设备类型</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="tabbar-spacer"></view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<nav-footer :current="1" />
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { request, NavgateTo } from '../../../utils';
|
||||||
|
import { apiArr } from '../../../api/v2User';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
statusBarHeight: 20,
|
||||||
|
isLogin: false,
|
||||||
|
nickName: '',
|
||||||
|
// 顶部轮播图:还原设计稿的 4 张底图(已下载为本地图,必定可加载)
|
||||||
|
heroImages: [
|
||||||
|
'/static/smartDevice/hero1.jpg',
|
||||||
|
'/static/smartDevice/hero2.jpg',
|
||||||
|
'/static/smartDevice/hero3.jpg',
|
||||||
|
'/static/smartDevice/hero4.jpg'
|
||||||
|
],
|
||||||
|
// 登录后 4 特性标签(图标用 uview 内置图标名)
|
||||||
|
heroTags: [
|
||||||
|
{ key: 'rich', label: '设备丰富', icon: 'grid' },
|
||||||
|
{ key: 'safe', label: '安全可靠', icon: 'checkmark-circle' },
|
||||||
|
{ key: 'smart', label: '智慧便捷', icon: 'star-fill' },
|
||||||
|
{ key: 'life', label: '美好生活', icon: 'heart' }
|
||||||
|
],
|
||||||
|
// 8 个设备分类卡(还原设计稿 device-data.js)
|
||||||
|
deviceCategories: [
|
||||||
|
{ key: 'access', title: '门禁通行', desc: ['安全有序出入', '无忧生活体验'], illust: 'access', image: '/static/smartDevice/cat-access.jpg', lockText: '登录后远程开门', actionText: '设备展示' },
|
||||||
|
{ key: 'parking', title: '停车充电', desc: ['社区停车便捷', '充电设备丰富'], illust: 'parking', image: '/static/smartDevice/cat-parking.jpg', lockText: '登录后查看车位与充电桩', actionText: '进入停车' },
|
||||||
|
{ key: 'guard', title: '家庭安防', desc: ['守护家庭安全', '安心每一天'], illust: 'guard', image: '/static/smartDevice/cat-guard.jpg', lockText: '登录后可控制', actionText: '设备简介' },
|
||||||
|
{ key: 'environment', title: '智慧环境', desc: ['空气与温湿度监测', '营造舒适环境'], illust: 'environment', image: '/static/smartDevice/cat-environment.jpg', lockText: '登录后可查看', actionText: '运行概览' },
|
||||||
|
{ key: 'monitor', title: '视频监控', desc: ['高清实时监控', '守护社区安全'], illust: 'monitor', image: '/static/smartDevice/cat-monitor.jpg', lockText: '登录后查看摄像头', actionText: '仅供查看' },
|
||||||
|
{ key: 'water', title: '净水设备', desc: ['优质净水体验', '健康饮水每一天'], illust: 'water', image: '/static/smartDevice/cat-water.jpg', lockText: '登录后可查看', actionText: '设备展示' },
|
||||||
|
{ key: 'lighting', title: '智能照明', desc: ['灯光随心营造', '温馨舒适氛围'], illust: 'lighting', image: '/static/smartDevice/cat-lighting.jpg', lockText: '登录后可控制', actionText: '场景展示' },
|
||||||
|
{ key: 'scene', title: '场景联动', desc: ['多设备协同', '智慧生活体验'], illust: 'scene', image: '/static/smartDevice/cat-scene.jpg', lockText: '登录后可使用', actionText: '场景展示' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoad() {
|
||||||
|
const sys = uni.getSystemInfoSync();
|
||||||
|
this.statusBarHeight = sys.statusBarHeight || 20;
|
||||||
|
},
|
||||||
|
onShow() {
|
||||||
|
this.refreshLogin();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
refreshLogin() {
|
||||||
|
const ctoken = uni.getStorageSync('ctoken');
|
||||||
|
this.isLogin = !!ctoken;
|
||||||
|
if (this.isLogin) {
|
||||||
|
this.getUserInfo();
|
||||||
|
} else {
|
||||||
|
this.nickName = '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getUserInfo() {
|
||||||
|
try {
|
||||||
|
const res = await request(apiArr.getUserInfo, 'POST', {}, { silent: false });
|
||||||
|
this.nickName = (res && res.nick_name) ? res.nick_name : '';
|
||||||
|
} catch (e) {
|
||||||
|
this.nickName = '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
goLogin() {
|
||||||
|
uni.redirectTo({ url: '/pages/login/login' });
|
||||||
|
},
|
||||||
|
handlePreview() {
|
||||||
|
uni.showToast({ title: '浏览模式,登录后可使用完整功能', icon: 'none' });
|
||||||
|
},
|
||||||
|
// 卡片点击:停车充电 → 现有停车页;其余占位
|
||||||
|
handleCardTap(item) {
|
||||||
|
if (item.key === 'parking') {
|
||||||
|
// 现有智能设备(停车)页;未登录时 NavgateTo 自动跳登录
|
||||||
|
NavgateTo('/packages/smartDevice/index/index');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.isLogin) {
|
||||||
|
uni.showToast({ title: '登录后可查看设备详情', icon: 'none' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uni.showToast({ title: item.title + '功能开发中', icon: 'none' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@import url("./index.css");
|
||||||
|
</style>
|
||||||
57
pages.json
@ -1,5 +1,16 @@
|
|||||||
{
|
{
|
||||||
"pages": [
|
"pages": [
|
||||||
|
{
|
||||||
|
"path": "pages/index2/index2",
|
||||||
|
"style": {
|
||||||
|
"navigationStyle": "custom",
|
||||||
|
"navigationBarBackgroundColor": "#FFF",
|
||||||
|
"enablePullDownRefresh": true,
|
||||||
|
"usingComponents": {
|
||||||
|
"nav-footer": "/components/nav/nav"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "pages/index/index",
|
"path": "pages/index/index",
|
||||||
"style": {
|
"style": {
|
||||||
@ -139,7 +150,11 @@
|
|||||||
"pages": [
|
"pages": [
|
||||||
{
|
{
|
||||||
"path": "index/index",
|
"path": "index/index",
|
||||||
"style": { "navigationBarTitleText": "到家服务", "navigationBarBackgroundColor": "#fff" }
|
"style": { "navigationBarTitleText": "未来到家", "navigationStyle": "custom", "navigationBarBackgroundColor": "#ff3838", "enablePullDownRefresh": true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "catalog/index",
|
||||||
|
"style": { "navigationBarTitleText": "服务", "navigationStyle": "custom" }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "detail/index",
|
"path": "detail/index",
|
||||||
@ -1194,6 +1209,17 @@
|
|||||||
{
|
{
|
||||||
"root": "packages/smartDevice",
|
"root": "packages/smartDevice",
|
||||||
"pages": [
|
"pages": [
|
||||||
|
{
|
||||||
|
"path": "home/index",
|
||||||
|
"style": {
|
||||||
|
"navigationStyle": "custom",
|
||||||
|
"navigationBarTitleText": "智能设备",
|
||||||
|
"navigationBarBackgroundColor": "#f5f6f7",
|
||||||
|
"usingComponents": {
|
||||||
|
"nav-footer": "/components/nav/nav"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "index/index",
|
"path": "index/index",
|
||||||
"style": {
|
"style": {
|
||||||
@ -1206,6 +1232,35 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"root": "packages/futureCommunity",
|
||||||
|
"pages": [
|
||||||
|
{
|
||||||
|
"path": "index/index",
|
||||||
|
"style": { "navigationStyle": "custom", "navigationBarTitleText": "九九社区", "enablePullDownRefresh": false }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "noticeList/index",
|
||||||
|
"style": { "navigationStyle": "custom", "navigationBarTitleText": "社区公告" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "noticeDetail/index",
|
||||||
|
"style": { "navigationStyle": "custom", "navigationBarTitleText": "公告详情" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "activityList/index",
|
||||||
|
"style": { "navigationStyle": "custom", "navigationBarTitleText": "社区活动" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "activityDetail/index",
|
||||||
|
"style": { "navigationStyle": "custom", "navigationBarTitleText": "活动详情" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "conveniencePhone/index",
|
||||||
|
"style": { "navigationStyle": "custom", "navigationBarTitleText": "便民电话" }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"root": "packages/advertising",
|
"root": "packages/advertising",
|
||||||
"pages": [
|
"pages": [
|
||||||
|
|||||||
@ -102,11 +102,11 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="white_container margin_top20 padding_bottom32">
|
<view class="white_container margin_top20 padding_bottom32">
|
||||||
<view v-if="!currentCity.district" class="Tit">定位错误 未知区域</view>
|
<!--view v-if="!currentCity.district" class="Tit">定位错误 未知区域</view>
|
||||||
<view v-else class="Tit">
|
<view v-else class="Tit">
|
||||||
<view class="local">{{ currentCity.district }}</view>附近
|
<view class="local">{{ currentCity.district }}</view>附近
|
||||||
<view class="Tit_desc">一刻钟便利生活圈</view>
|
<view class="Tit_desc">一刻钟便利生活圈</view>
|
||||||
</view>
|
</view-->
|
||||||
<view class="serverList">
|
<view class="serverList">
|
||||||
<view class="serverList_left">
|
<view class="serverList_left">
|
||||||
<swiper>
|
<swiper>
|
||||||
|
|||||||
382
pages/index2/index2.css
Normal file
@ -0,0 +1,382 @@
|
|||||||
|
/* ============ 新首页 index2 样式 ============ */
|
||||||
|
page {
|
||||||
|
background: #f5f6f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home2 {
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 顶部红色头区(吸顶固定:滚动/切 tab 时搜索栏与分类条保持不变) ===== */
|
||||||
|
.hd {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
/* 不要红头,保持页面底色 */
|
||||||
|
background: #f5f6f8;
|
||||||
|
padding: 0 24rpx 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hd-loc {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 64rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hd-loc-icon {
|
||||||
|
width: 30rpx;
|
||||||
|
margin-right: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hd-loc-txt {
|
||||||
|
font-size: 30rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
max-width: 420rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hd-loc-arrow {
|
||||||
|
font-size: 24rpx;
|
||||||
|
margin-left: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 搜索框 */
|
||||||
|
.hd-search {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 72rpx;
|
||||||
|
background: #fff;
|
||||||
|
border: 2rpx solid #ef111b;
|
||||||
|
border-radius: 40rpx;
|
||||||
|
padding: 0 8rpx 0 24rpx;
|
||||||
|
margin-top: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hd-search-icon {
|
||||||
|
width: 32rpx;
|
||||||
|
height: 32rpx;
|
||||||
|
margin-right: 12rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hd-search-input {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hd-search-input::-webkit-input-placeholder {
|
||||||
|
color: #b0b3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hd-search-btn {
|
||||||
|
flex-shrink: 0;
|
||||||
|
height: 56rpx;
|
||||||
|
line-height: 56rpx;
|
||||||
|
padding: 0 28rpx;
|
||||||
|
/* 与源码搜索按钮一致:纯红 #ff2b2f */
|
||||||
|
background: #ff2b2f;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 26rpx;
|
||||||
|
border-radius: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 分类条:移到瀑布上方并吸顶(top 由内联样式给页头高度);仅瀑布在其下滚动 */
|
||||||
|
.hd-cate {
|
||||||
|
position: sticky;
|
||||||
|
z-index: 50;
|
||||||
|
white-space: nowrap;
|
||||||
|
/* 抵消 .bd 的 20rpx 左右内边距,吸顶时整宽铺满、盖住下方瀑布 */
|
||||||
|
margin: 0 -20rpx;
|
||||||
|
padding: 16rpx 20rpx;
|
||||||
|
background: #f5f6f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hd-cate-item {
|
||||||
|
display: inline-block;
|
||||||
|
color: #666;
|
||||||
|
font-size: 28rpx;
|
||||||
|
padding: 6rpx 4rpx;
|
||||||
|
margin-right: 34rpx;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hd-cate-item.on {
|
||||||
|
color: #ff4848;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hd-cate-item.on::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
bottom: -4rpx;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: 36rpx;
|
||||||
|
height: 6rpx;
|
||||||
|
border-radius: 3rpx;
|
||||||
|
background: #ff4848;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 主体 ===== */
|
||||||
|
.bd {
|
||||||
|
position: relative;
|
||||||
|
margin-top: -8rpx;
|
||||||
|
background: #f5f6f8;
|
||||||
|
border-radius: 24rpx 24rpx 0 0;
|
||||||
|
padding: 20rpx 20rpx 180rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 轮播 */
|
||||||
|
.banner {
|
||||||
|
position: relative;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner-swiper {
|
||||||
|
width: 100%;
|
||||||
|
height: 300rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner-img {
|
||||||
|
width: 100%;
|
||||||
|
height: 300rpx;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner-dots {
|
||||||
|
position: absolute;
|
||||||
|
right: 20rpx;
|
||||||
|
bottom: 16rpx;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner-dots .dot {
|
||||||
|
width: 12rpx;
|
||||||
|
height: 12rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255, 255, 255, 0.5);
|
||||||
|
margin-left: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner-dots .dot.on {
|
||||||
|
width: 24rpx;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 金刚区(自绘 flex,上下留白 = .grid 的 margin,各 24rpx,无组件内建空间) */
|
||||||
|
.grid {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
padding: 0;
|
||||||
|
margin: 24rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-icon {
|
||||||
|
width: 84rpx;
|
||||||
|
height: 84rpx;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-txt {
|
||||||
|
display: block;
|
||||||
|
margin-top: 8rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
line-height: 1;
|
||||||
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 广告栏 */
|
||||||
|
.ads {
|
||||||
|
display: flex;
|
||||||
|
/* 与金刚区间距由 .grid 的 padding-bottom 5px 控制 */
|
||||||
|
margin-top: 0;
|
||||||
|
/* 按原主页广告位尺寸放大:整体约 450rpx 高 */
|
||||||
|
height: 450rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ads-left {
|
||||||
|
width: 50%;
|
||||||
|
margin-right: 16rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ads-left image {
|
||||||
|
width: 100%;
|
||||||
|
height: 450rpx;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ads-right {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ads-right-item {
|
||||||
|
height: 217rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ads-right-item image {
|
||||||
|
width: 100%;
|
||||||
|
height: 217rpx;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 空广告位占位(招商中) */
|
||||||
|
.ad-ph {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 217rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: linear-gradient(135deg, #fff3ee, #ffe9e0);
|
||||||
|
color: #ff9a76;
|
||||||
|
font-size: 24rpx;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ads-left .ad-ph {
|
||||||
|
height: 450rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 好店标题 */
|
||||||
|
.wf-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
/* 内容已注释为空,去掉多余留白 */
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wf-title-main {
|
||||||
|
font-size: 34rpx;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wf-title-loc {
|
||||||
|
color: #ff4d4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wf-title-tag {
|
||||||
|
margin-left: 16rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #ff7a45;
|
||||||
|
background: #fff0e8;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
padding: 4rpx 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 瀑布 */
|
||||||
|
.wf {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wf-col {
|
||||||
|
width: 345rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wf-col:first-child {
|
||||||
|
margin-right: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wf-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wf-img {
|
||||||
|
width: 345rpx;
|
||||||
|
display: block;
|
||||||
|
background: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wf-meta {
|
||||||
|
padding: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wf-name {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #222;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wf-sub {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wf-stars {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wf-stars image {
|
||||||
|
width: 22rpx;
|
||||||
|
height: 22rpx;
|
||||||
|
margin-right: 2rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wf-dist {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wf-tags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-top: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wf-tag {
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #ff6a3d;
|
||||||
|
border: 1rpx solid #ffcbb3;
|
||||||
|
background: #fff5f0;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
padding: 2rpx 10rpx;
|
||||||
|
margin-right: 8rpx;
|
||||||
|
margin-bottom: 6rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wf-addr {
|
||||||
|
margin-top: 8rpx;
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #999;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wf-more {
|
||||||
|
text-align: center;
|
||||||
|
color: #999;
|
||||||
|
font-size: 24rpx;
|
||||||
|
padding: 24rpx 0 40rpx;
|
||||||
|
}
|
||||||
691
pages/index2/index2.vue
Normal file
@ -0,0 +1,691 @@
|
|||||||
|
<template>
|
||||||
|
<view class="home2" v-if="loading">
|
||||||
|
<!-- ===== 顶部红色头区(状态栏 + 定位 + 搜索 + 分类条) ===== -->
|
||||||
|
<view class="hd" :style="{ paddingTop: top + 'px' }">
|
||||||
|
<!-- 定位 -->
|
||||||
|
<view class="hd-loc" @tap="map">
|
||||||
|
<image class="hd-loc-icon" src="https://static.hshuishang.com/Index_add.png" mode="widthFix" />
|
||||||
|
<text class="hd-loc-txt">{{ currentCity.cityName || '选择城市' }}</text>
|
||||||
|
<text class="hd-loc-arrow">▾</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 搜索框(新增,本轮仅 UI + 占位,后台好店搜索接口回头接入 handleSearch) -->
|
||||||
|
<view class="hd-search">
|
||||||
|
<image class="hd-search-icon"
|
||||||
|
src="https://static.hshuishang.com/property-img-file/com_communitySearchIcon.png" mode="aspectFill" />
|
||||||
|
<input class="hd-search-input" v-model="keyword" :placeholder="searchPlaceholder"
|
||||||
|
confirm-type="search" @confirm="handleSearch" />
|
||||||
|
<view class="hd-search-btn" @tap="handleSearch">搜索</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- ===== 主体(白底圆角上浮) ===== -->
|
||||||
|
<view class="bd">
|
||||||
|
<!-- 轮播图 -->
|
||||||
|
<view class="banner" v-if="isRecommend && bannerList.length">
|
||||||
|
<swiper class="banner-swiper" @animationfinish="swipers" autoplay circular>
|
||||||
|
<swiper-item v-for="(item, index) in bannerList" :key="index" @click="handleAdClick(item)">
|
||||||
|
<image class="banner-img" :src="item.pic_src" mode="aspectFill" />
|
||||||
|
</swiper-item>
|
||||||
|
</swiper>
|
||||||
|
<view class="banner-dots">
|
||||||
|
<view :class="['dot', currentIdx == index ? 'on' : '']" v-for="(item, index) in bannerList" :key="index">
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 金刚区(自绘 flex 布局,间距完全可控,不用 u-grid 以免组件自带底部空间) -->
|
||||||
|
<view class="grid" v-if="isRecommend && tabList.length">
|
||||||
|
<view class="grid-item" :style="{ width: (100 / (rowNum || 5)) + '%' }"
|
||||||
|
v-for="(item, index) in tabList" :key="index" @tap="headerServerClick(item)">
|
||||||
|
<image class="grid-icon" :src="item.icon_src" mode="aspectFill"></image>
|
||||||
|
<text class="grid-txt">{{ item.title }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 广告栏:固定三格(左大 + 右上 + 右下),空位显示招商占位 -->
|
||||||
|
<view class="ads" v-if="isRecommend">
|
||||||
|
<view class="ads-left">
|
||||||
|
<image v-if="isRealAd(serverLeftList[0])" :src="serverLeft" mode="aspectFill"
|
||||||
|
@tap="handleAdClick(serverLeftList[0])" />
|
||||||
|
<view v-else class="ad-ph">广告招商中</view>
|
||||||
|
</view>
|
||||||
|
<view class="ads-right">
|
||||||
|
<view class="ads-right-item" v-for="(item, index) in serverRightList" :key="index">
|
||||||
|
<image v-if="isRealAd(item)" :src="item.pic_src" mode="aspectFill" @tap="handleAdClick(item)" />
|
||||||
|
<view v-else class="ad-ph">广告招商中</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 好店列表标题 -->
|
||||||
|
<view class="wf-title" v-if="isRecommend">
|
||||||
|
<!--view class="wf-title-main">
|
||||||
|
<text class="wf-title-loc" v-if="currentCity.district">{{ currentCity.district }}</text>
|
||||||
|
附近好店
|
||||||
|
</view>
|
||||||
|
<view class="wf-title-tag">一刻钟便利生活圈</view-->
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 分类条:移到瀑布上方,上滑时吸顶(top=页头高度),仅瀑布在其下滚动 -->
|
||||||
|
<scroll-view scroll-x class="hd-cate" v-if="lifeCircleList.length" :style="{ top: cateTop + 'px' }">
|
||||||
|
<view class="hd-cate-item" :class="{ on: currentTab === 'recommend' }" @tap="selectRecommend">推荐</view>
|
||||||
|
<view class="hd-cate-item" v-for="(item, index) in lifeCircleList" :key="index"
|
||||||
|
:class="{ on: currentTab === item.title }" @tap="selectCircle(item)">
|
||||||
|
{{ item.title }}
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
<!-- 好店瀑布(两列,图片按原始宽高比制造瀑布效果) -->
|
||||||
|
<view class="wf">
|
||||||
|
<view class="wf-col">
|
||||||
|
<view class="wf-card" v-for="item in leftCol" :key="item.id" @click="Info(item)">
|
||||||
|
<image class="wf-img" :src="item.showImg" :style="{ height: item.imgH + 'rpx' }" mode="aspectFill" />
|
||||||
|
<view class="wf-meta">
|
||||||
|
<view class="wf-name">{{ item.merchant_name }}</view>
|
||||||
|
<view class="wf-sub">
|
||||||
|
<view class="wf-stars">
|
||||||
|
<image v-for="i in 5" :key="i" :src="i < item.rating
|
||||||
|
? 'https://static.hshuishang.com/property-img-file/local_start1.png'
|
||||||
|
: 'https://static.hshuishang.com/property-img-file/local_start2.png'" mode="aspectFill"></image>
|
||||||
|
</view>
|
||||||
|
<text class="wf-dist">{{ item.distances }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="wf-tags">
|
||||||
|
<text class="wf-tag" v-if="item.refund_property_fee_ratio">买单返岁银</text>
|
||||||
|
<text class="wf-tag" v-if="item.refund_user_points_ratio">买单返积分</text>
|
||||||
|
</view>
|
||||||
|
<view class="wf-addr">{{ item.comAddress }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="wf-col">
|
||||||
|
<view class="wf-card" v-for="item in rightCol" :key="item.id" @click="Info(item)">
|
||||||
|
<image class="wf-img" :src="item.showImg" :style="{ height: item.imgH + 'rpx' }" mode="aspectFill" />
|
||||||
|
<view class="wf-meta">
|
||||||
|
<view class="wf-name">{{ item.merchant_name }}</view>
|
||||||
|
<view class="wf-sub">
|
||||||
|
<view class="wf-stars">
|
||||||
|
<image v-for="i in 5" :key="i" :src="i < item.rating
|
||||||
|
? 'https://static.hshuishang.com/property-img-file/local_start1.png'
|
||||||
|
: 'https://static.hshuishang.com/property-img-file/local_start2.png'" mode="aspectFill"></image>
|
||||||
|
</view>
|
||||||
|
<text class="wf-dist">{{ item.distances }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="wf-tags">
|
||||||
|
<text class="wf-tag" v-if="item.refund_property_fee_ratio">买单返岁银</text>
|
||||||
|
<text class="wf-tag" v-if="item.refund_user_points_ratio">买单返积分</text>
|
||||||
|
</view>
|
||||||
|
<view class="wf-addr">{{ item.comAddress }}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="wf-more" v-if="merchantHasMore">上拉加载更多</view>
|
||||||
|
<view class="wf-more" v-else-if="leftCol.length || rightCol.length">没有更多了</view>
|
||||||
|
|
||||||
|
<nav-footer :current="0" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {
|
||||||
|
NavgateTo,
|
||||||
|
calculateDistance,
|
||||||
|
} from '../../utils/index';
|
||||||
|
|
||||||
|
import {
|
||||||
|
util,
|
||||||
|
picUrl,
|
||||||
|
request,
|
||||||
|
menuButtonInfo
|
||||||
|
} from '../../utils/index.js';
|
||||||
|
import {
|
||||||
|
apiArr
|
||||||
|
} from '../../api/community.js';
|
||||||
|
import {
|
||||||
|
apiArr as apiArr2
|
||||||
|
} from '../../api/v2Home.js'
|
||||||
|
|
||||||
|
import nav from '../../components/nav/nav';
|
||||||
|
|
||||||
|
// 首页广告位常量(对应后台 home_banner_region.ad_position),与旧首页保持一致
|
||||||
|
const AD_POS = {
|
||||||
|
TOP_BANNER: 1, // 顶部轮播
|
||||||
|
CARD_LEFT: 2, // 广告栏左(大图)
|
||||||
|
CARD_RIGHT_TOP: 3, // 广告栏右上
|
||||||
|
CARD_RIGHT_BOTTOM: 4, // 广告栏右下
|
||||||
|
MID_LEFT: 5, // 中部广告位左
|
||||||
|
MID_CENTER_TOP: 6, // 生活圈:中部中上
|
||||||
|
MID_CENTER: 7, // 生活圈:中部中
|
||||||
|
MID_CENTER_BOTTOM: 8, // 生活圈:中部中下
|
||||||
|
MID_RIGHT_TOP: 9, // 生活圈:中部右上
|
||||||
|
MID_RIGHT_CENTER: 10, // 生活圈:中部右中
|
||||||
|
MID_RIGHT_BOTTOM: 11, // 生活圈:中部右下
|
||||||
|
POPUP: 12,
|
||||||
|
BOTTOM_RECOMMEND: 13,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 瀑布单列图片宽度(rpx):页面左右各 20rpx 内边距 + 两列间距 20rpx → (750-60)/2 = 345
|
||||||
|
const WF_COL_W = 345;
|
||||||
|
// 卡片图片下方文字区的估算高度(rpx),仅用于两列高度均衡,不影响真实布局
|
||||||
|
const WF_META_H = 150;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: { nav },
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
top: 0,
|
||||||
|
localHeight: 44,
|
||||||
|
cateTop: 0, // 分类条吸顶位置 = 页头(.hd)高度(px)
|
||||||
|
|
||||||
|
currentCity: '', // 定位信息
|
||||||
|
qqmap_key: '',
|
||||||
|
|
||||||
|
keyword: '', // 搜索关键字(本轮仅占位)
|
||||||
|
hotWord: [], // 搜索热词(取第一条做 placeholder)
|
||||||
|
|
||||||
|
currentIdx: 0,
|
||||||
|
bannerList: [], // 轮播图(位1)
|
||||||
|
tabList: [], // 金刚区
|
||||||
|
rowNum: 0,
|
||||||
|
colNum: 0,
|
||||||
|
|
||||||
|
// 广告栏(位2 / 位3 / 位4)
|
||||||
|
serverLeft: '',
|
||||||
|
serverLeftList: [],
|
||||||
|
serverRightList: [],
|
||||||
|
|
||||||
|
// 分类条:生活圈(位6-11)
|
||||||
|
lifeCircleList: [],
|
||||||
|
currentTab: 'recommend', // 当前分类条选中项:'recommend' 或某生活圈标题
|
||||||
|
|
||||||
|
// 分类(驱动好店列表)
|
||||||
|
categoryList: [],
|
||||||
|
currentCategoryId: null,
|
||||||
|
|
||||||
|
// 好店瀑布
|
||||||
|
page_num: 1,
|
||||||
|
page_size: 10,
|
||||||
|
leftCol: [],
|
||||||
|
rightCol: [],
|
||||||
|
colHeights: [0, 0],
|
||||||
|
merchantHasMore: false,
|
||||||
|
loadingMore: false,
|
||||||
|
|
||||||
|
_bannerGroups: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
searchPlaceholder() {
|
||||||
|
return this.hotWord && this.hotWord.length ? this.hotWord[0] : '搜索好店 / 商家 / 优惠'
|
||||||
|
},
|
||||||
|
// 仅「推荐」页展示轮播/金刚区/广告栏等;选中具体生活圈时只留瀑布列表
|
||||||
|
isRecommend() {
|
||||||
|
return this.currentTab === 'recommend'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
/* ---------- 顶部/通用 ---------- */
|
||||||
|
map() {
|
||||||
|
uni.navigateTo({ url: '/pages/shopcity/shopcity' });
|
||||||
|
},
|
||||||
|
|
||||||
|
// 搜索:本轮仅占位。后台好店搜索接口接入后,这里改为按 keyword 过滤 getMechantList。
|
||||||
|
handleSearch() {
|
||||||
|
const kw = (this.keyword || '').trim()
|
||||||
|
// TODO: 接后台好店关键字搜索接口(给 getMerchantList 增加 keyword 参数)
|
||||||
|
uni.showToast({
|
||||||
|
title: kw ? `搜索「${kw}」功能即将开放` : '请输入关键字',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
swipers(e) {
|
||||||
|
this.currentIdx = e.detail.current
|
||||||
|
},
|
||||||
|
|
||||||
|
NotOpen() {
|
||||||
|
uni.showModal({ title: '提示', content: '此功能暂未开通!', showCancel: false, complete: () => { } })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 统一广告点击:按后台 promotion_type 分发(与旧首页一致)
|
||||||
|
handleAdClick(item) {
|
||||||
|
if (!item) return
|
||||||
|
switch (item.promotion_type) {
|
||||||
|
case 3:
|
||||||
|
if (!item.link_url) return this.NotOpen()
|
||||||
|
if (item.link_url === '/packages/shopEnter/index/index') return this.headershopEnterClick()
|
||||||
|
return NavgateTo(item.link_url, { isLogin: false })
|
||||||
|
case 4:
|
||||||
|
if (!item.appid) return this.NotOpen()
|
||||||
|
return uni.navigateToMiniProgram({ appId: item.appid, path: item.link_url || '', envVersion: 'release' })
|
||||||
|
case 5:
|
||||||
|
return NavgateTo('/packages/advertising/index/index?id=' + item.id, { isLogin: false })
|
||||||
|
case 1:
|
||||||
|
default:
|
||||||
|
return this.NotOpen()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 金刚区点击(与旧首页 headerServerClick 一致)
|
||||||
|
headerServerClick(e) {
|
||||||
|
if (!e.link_url) return this.NotOpen()
|
||||||
|
if (e.link_url === '/packages/shopEnter/index/index') return this.headershopEnterClick()
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
uni.navigateTo({ url: '/pages/webview/webview?url=' + encodeURIComponent(e.link_url) });
|
||||||
|
// #endif
|
||||||
|
// #ifdef H5
|
||||||
|
window.open(e.link_url, '_blank');
|
||||||
|
// #endif
|
||||||
|
// #ifdef MP-WEIXIN || MP-ALIPAY || MP-BAIDU
|
||||||
|
if (e.appid) {
|
||||||
|
uni.navigateToMiniProgram({ appId: e.appid, path: e.link_url, extraData: { data1: 'test' } })
|
||||||
|
} else {
|
||||||
|
NavgateTo(e.link_url, { isLogin: false })
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
|
||||||
|
// 「推荐」对应的商品分类 id:暂取美食圈(无则取第一个生活圈,再无则第一个分类)
|
||||||
|
recommendCateId() {
|
||||||
|
const target = this.lifeCircleList.find(c => c.title === '美食圈') || this.lifeCircleList[0]
|
||||||
|
if (target) {
|
||||||
|
const cate = this.categoryList.find(c => c.cate_name === target.title)
|
||||||
|
if (cate) return cate.id
|
||||||
|
}
|
||||||
|
return this.categoryList.length ? this.categoryList[0].id : null
|
||||||
|
},
|
||||||
|
|
||||||
|
// 点击「推荐」:暂时显示美食圈内容
|
||||||
|
async selectRecommend() {
|
||||||
|
this.currentTab = 'recommend'
|
||||||
|
const id = this.recommendCateId()
|
||||||
|
if (id) await this.switchCategory(id)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 点击某生活圈:只显示该圈对应分类的好店瀑布
|
||||||
|
async selectCircle(item) {
|
||||||
|
if (item.title == '更多') {
|
||||||
|
return NavgateTo('/packages/localLife/index/index', { isLogin: false })
|
||||||
|
}
|
||||||
|
const cate = this.categoryList.find(c => c.cate_name === item.title)
|
||||||
|
if (cate && cate.id) {
|
||||||
|
this.currentTab = item.title
|
||||||
|
await this.switchCategory(cate.id)
|
||||||
|
} else {
|
||||||
|
uni.showToast({ title: '暂无「' + item.title + '」相关好店', icon: 'none' })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async headershopEnterClick() {
|
||||||
|
if (!uni.getStorageSync('userId')) {
|
||||||
|
return NavgateTo('/packages/shopEnter/choose/index', { isLogin: false })
|
||||||
|
}
|
||||||
|
const res = await request(apiArr2.statusQuery, 'POST', {}, { silent: false })
|
||||||
|
if (res.status) {
|
||||||
|
NavgateTo('/packages/shopEnter/auditStatus/index?itemObj=' + JSON.stringify(res))
|
||||||
|
} else {
|
||||||
|
NavgateTo('/packages/shopEnter/choose/index')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/* ---------- 定位 + 首屏取数 ---------- */
|
||||||
|
init() {
|
||||||
|
uni.showLoading({ title: '加载中', mask: true })
|
||||||
|
this.qqmap_key = '55NBZ-MUQYW-EAJRL-YIWPA-ZXCR6-4NBPP'
|
||||||
|
this.getUserLocation()
|
||||||
|
},
|
||||||
|
|
||||||
|
getUserLocation() {
|
||||||
|
const that = this
|
||||||
|
const { qqmap_key } = this
|
||||||
|
const userlocat = uni.getStorageSync('location')
|
||||||
|
uni.getLocation({
|
||||||
|
type: 'wgs84',
|
||||||
|
success: (res) => {
|
||||||
|
const latitude = userlocat ? userlocat.lat : res.latitude
|
||||||
|
const longitude = userlocat ? userlocat.lng : res.longitude
|
||||||
|
uni.request({
|
||||||
|
url: `https://apis.map.qq.com/ws/geocoder/v1/?location=${latitude},${longitude}&key=${qqmap_key}&get_poi=0`,
|
||||||
|
success: async (r) => {
|
||||||
|
const { data: { result: { address_component, ad_info } } } = r
|
||||||
|
const preciseLocation = {
|
||||||
|
cityName: address_component.city,
|
||||||
|
region: address_component.province + address_component.city + address_component.district,
|
||||||
|
district: address_component.district + `${address_component.street_number ? address_component.street_number : address_component.street}`,
|
||||||
|
address: address_component.city + address_component.district + address_component.street_number,
|
||||||
|
lat: latitude,
|
||||||
|
lng: longitude,
|
||||||
|
}
|
||||||
|
that.currentCity = preciseLocation
|
||||||
|
uni.setStorageSync('location', preciseLocation)
|
||||||
|
uni.setStorageSync('ad_code', ad_info.adcode)
|
||||||
|
|
||||||
|
// 一次拉取各广告位(1-11),后续各 getHomeXxx 读缓存
|
||||||
|
await this.prefetchBannerGroups()
|
||||||
|
const [bannerList, serverLeft, serverRightList, lifeCircleList, tabList] = await Promise.all([
|
||||||
|
this.getHomeBanner(),
|
||||||
|
this.getServerLeft(),
|
||||||
|
this.getServerRight(),
|
||||||
|
this.getLifeCircle(), // 生活圈 6-11 → 分类条
|
||||||
|
this.getButtonNum(), // 金刚区
|
||||||
|
])
|
||||||
|
this.bannerList = bannerList
|
||||||
|
this.serverLeft = serverLeft
|
||||||
|
this.serverRightList = serverRightList
|
||||||
|
this.lifeCircleList = lifeCircleList
|
||||||
|
this.tabList = tabList
|
||||||
|
|
||||||
|
// 先渲染首屏(轮播/金刚/广告/分类条),好店瀑布再异步加载填充,
|
||||||
|
// 避免整页等待瀑布——与原主页一致(原页 loading=true 后才单独加载好店列表)。
|
||||||
|
this.loading = true
|
||||||
|
uni.hideLoading()
|
||||||
|
this.$nextTick(() => setTimeout(() => this.measureLayout(), 60)) // 量测吸顶高度 + 对齐金刚区上下间距
|
||||||
|
this.getCateList().catch(e => console.warn('好店分类/瀑布加载失败:', e)) // 异步:拉分类 + 加载首个分类的好店瀑布
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.log('地图API解析地址失败:', err)
|
||||||
|
uni.showToast({ title: '定位失败,请检查网络', icon: 'none' })
|
||||||
|
uni.hideLoading()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.log('获取位置信息失败:', err)
|
||||||
|
if (err.errMsg && err.errMsg.indexOf('auth deny') >= 0) {
|
||||||
|
uni.showModal({
|
||||||
|
title: '提示', content: '需要您授权位置信息才能使用此功能', confirmText: '去授权',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
wx.openSetting();
|
||||||
|
// #endif
|
||||||
|
// #ifdef APP-PLUS
|
||||||
|
uni.openSetting();
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
uni.hideLoading()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
uni.showToast({ title: '定位失败,请重试', icon: 'none' })
|
||||||
|
uni.hideLoading()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/* ---------- 广告位取数(沿用旧首页逻辑) ---------- */
|
||||||
|
async prefetchBannerGroups(positions = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) {
|
||||||
|
try {
|
||||||
|
const res = await request(apiArr2.getHomeBannerMulti, 'POST', {
|
||||||
|
ad_code: Number(uni.getStorageSync('ad_code')),
|
||||||
|
ad_positions: positions,
|
||||||
|
longitude: uni.getStorageSync('location').lng,
|
||||||
|
latitude: uni.getStorageSync('location').lat,
|
||||||
|
page_size: 50
|
||||||
|
}, { silent: false })
|
||||||
|
this._bannerGroups = (res && res.groups) ? res.groups : {}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('批量拉取首页广告失败,回退逐个请求:', e)
|
||||||
|
this._bannerGroups = null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async getBannerRows(position) {
|
||||||
|
if (this._bannerGroups) {
|
||||||
|
return this._bannerGroups[String(position)] || []
|
||||||
|
}
|
||||||
|
const res = await request(apiArr2.getHomeBanner, 'POST', {
|
||||||
|
ad_code: uni.getStorageSync('ad_code'),
|
||||||
|
ad_position: position,
|
||||||
|
longitude: uni.getStorageSync('location').lng,
|
||||||
|
latitude: uni.getStorageSync('location').lat,
|
||||||
|
page_num: 1,
|
||||||
|
page_size: 50
|
||||||
|
}, { silent: false })
|
||||||
|
return (res && res.rows) ? res.rows : []
|
||||||
|
},
|
||||||
|
|
||||||
|
withPicUrl(list) {
|
||||||
|
list.forEach(item => { item.pic_src = picUrl + item.pic_src })
|
||||||
|
return list
|
||||||
|
},
|
||||||
|
|
||||||
|
filterShowList(list, type) {
|
||||||
|
if (list && list.length) return list.filter(item => item.show_status == type)
|
||||||
|
return []
|
||||||
|
},
|
||||||
|
|
||||||
|
async getBannerAt(position, { placeholder = false } = {}) {
|
||||||
|
const rows = await this.getBannerRows(position)
|
||||||
|
const [first] = this.withPicUrl(this.filterShowList(rows, 1))
|
||||||
|
if (first) return first
|
||||||
|
return placeholder ? { ad_position: position, pic_src: '' } : null
|
||||||
|
},
|
||||||
|
|
||||||
|
async getBannersAt(positions, opts) {
|
||||||
|
return Promise.all(positions.map(p => this.getBannerAt(p, opts)))
|
||||||
|
},
|
||||||
|
|
||||||
|
async getHomeBanner() {
|
||||||
|
const rows = await this.getBannerRows(AD_POS.TOP_BANNER)
|
||||||
|
return this.withPicUrl(this.filterShowList(rows, 1))
|
||||||
|
},
|
||||||
|
|
||||||
|
// 广告栏固定三格:左大(位2) + 右上(位3) + 右下(位4)。
|
||||||
|
// 某位无「已发布且在投放期」广告时用占位对象补齐,保证版式始终是设计稿的三格。
|
||||||
|
async getServerLeft() {
|
||||||
|
const item = await this.getBannerAt(AD_POS.CARD_LEFT, { placeholder: true })
|
||||||
|
this.serverLeftList = [item]
|
||||||
|
return item ? item.pic_src : ''
|
||||||
|
},
|
||||||
|
|
||||||
|
async getServerRight() {
|
||||||
|
// placeholder:true → 始终返回两格(空位为占位对象,pic_src 为空串)
|
||||||
|
return this.getBannersAt([AD_POS.CARD_RIGHT_TOP, AD_POS.CARD_RIGHT_BOTTOM], { placeholder: true })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 是否为真实可点广告(有图);占位格不可点
|
||||||
|
isRealAd(item) {
|
||||||
|
return !!(item && item.pic_src)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 生活圈:中部 6-11,取各位第一条已发布,作为顶部分类条
|
||||||
|
async getLifeCircle() {
|
||||||
|
const list = await this.getBannersAt([
|
||||||
|
AD_POS.MID_CENTER_TOP, AD_POS.MID_CENTER, AD_POS.MID_CENTER_BOTTOM,
|
||||||
|
AD_POS.MID_RIGHT_TOP, AD_POS.MID_RIGHT_CENTER, AD_POS.MID_RIGHT_BOTTOM,
|
||||||
|
])
|
||||||
|
return list.filter(Boolean).filter(item => item.title)
|
||||||
|
},
|
||||||
|
|
||||||
|
/* ---------- 金刚区 ---------- */
|
||||||
|
async getButtonNum() {
|
||||||
|
const res = await request(apiArr2.getButtonNum, 'POST', {}, { slice: false })
|
||||||
|
this.rowNum = res.nav_row_num
|
||||||
|
this.colNum = res.nav_row_total
|
||||||
|
return await this.getHomeButton()
|
||||||
|
},
|
||||||
|
|
||||||
|
async getHomeButton() {
|
||||||
|
const res = await request(apiArr2.getHomeButton, 'POST', {
|
||||||
|
ad_code: Number(uni.getStorageSync('ad_code')),
|
||||||
|
page_num: 1,
|
||||||
|
page_size: 50
|
||||||
|
}, { slice: false })
|
||||||
|
const filterRes = this.filterShowList(res?.rows, 1)
|
||||||
|
const newList = filterRes.slice(0, this.rowNum * this.colNum)
|
||||||
|
if (newList.length) {
|
||||||
|
newList.forEach(item => { item.icon_src = picUrl + item.icon_src })
|
||||||
|
return newList
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
},
|
||||||
|
|
||||||
|
/* ---------- 分类 + 好店瀑布 ---------- */
|
||||||
|
async getCateList() {
|
||||||
|
const res = await request(apiArr2.getCateList, 'POST', { isShop: 1 }, { slice: false })
|
||||||
|
if (res.rows && res.rows.length) {
|
||||||
|
this.categoryList = res.rows
|
||||||
|
// 默认选中「推荐」,内容暂取美食圈
|
||||||
|
this.currentTab = 'recommend'
|
||||||
|
this.currentCategoryId = this.recommendCateId() || res.rows[0].id
|
||||||
|
await this.getMechantList(true)
|
||||||
|
} else {
|
||||||
|
this.categoryList = []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async switchCategory(id) {
|
||||||
|
if (this.currentCategoryId === id) return
|
||||||
|
this.currentCategoryId = id
|
||||||
|
await this.getMechantList(true)
|
||||||
|
// 不再强制滚动:分类条常驻吸顶,切换时保持用户当前位置,避免突兀的自动滚动
|
||||||
|
},
|
||||||
|
|
||||||
|
// reset=true 表示重新加载(换分类/下拉刷新);否则为触底追加
|
||||||
|
async getMechantList(reset = false) {
|
||||||
|
if (this.loadingMore) return
|
||||||
|
this.loadingMore = true
|
||||||
|
if (reset) {
|
||||||
|
this.page_num = 1
|
||||||
|
this.leftCol = []
|
||||||
|
this.rightCol = []
|
||||||
|
this.colHeights = [0, 0]
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await request(apiArr2.getMerchantList, 'POST', {
|
||||||
|
merchant_cate_id: this.currentCategoryId,
|
||||||
|
page_num: this.page_num,
|
||||||
|
page_size: this.page_size,
|
||||||
|
})
|
||||||
|
const latitude = uni.getStorageSync('location').lat
|
||||||
|
const longitude = uni.getStorageSync('location').lng
|
||||||
|
const rows = (res && res.rows) ? res.rows : []
|
||||||
|
rows.forEach((item) => {
|
||||||
|
item.bigImg = item.album_images?.split(',')
|
||||||
|
item.showImg = item.album_images ? picUrl + item.album_images.split(',')[0] : ''
|
||||||
|
const distanceInKm = calculateDistance(latitude, longitude, item.latitude, item.longitude)
|
||||||
|
item.distances = distanceInKm >= 1
|
||||||
|
? `${distanceInKm.toFixed(1)}km`
|
||||||
|
: `${(distanceInKm * 1000).toFixed(1)}m`
|
||||||
|
item.comAddress = item.ad ? item.ad.ad_name.replace(/,/g, '') + item.address : item.address
|
||||||
|
})
|
||||||
|
// 就近排序
|
||||||
|
rows.sort((a, b) => {
|
||||||
|
const va = parseFloat(a.distances) * (a.distances.includes('km') ? 1000 : 1)
|
||||||
|
const vb = parseFloat(b.distances) * (b.distances.includes('km') ? 1000 : 1)
|
||||||
|
return va - vb
|
||||||
|
})
|
||||||
|
|
||||||
|
this.merchantHasMore = rows.length === this.page_size
|
||||||
|
if (this.merchantHasMore) this.page_num++
|
||||||
|
|
||||||
|
await this.appendMasonry(rows)
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('好店列表加载失败:', e)
|
||||||
|
} finally {
|
||||||
|
this.loadingMore = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 并行量图(并发比串行快很多)→ 按两列累计高度均衡分列,避免一列过长、另一列底部大片留白。
|
||||||
|
// 图片仍 mode="widthFix" 自适应显示;此处测量仅用于分列均衡。
|
||||||
|
async appendMasonry(rows) {
|
||||||
|
const ratios = await Promise.all(rows.map(item => new Promise(resolve => {
|
||||||
|
if (!item.showImg) return resolve(0.8)
|
||||||
|
uni.getImageInfo({
|
||||||
|
src: item.showImg,
|
||||||
|
success: info => resolve(info && info.width ? info.height / info.width : 0.8),
|
||||||
|
fail: () => resolve(0.8)
|
||||||
|
})
|
||||||
|
})))
|
||||||
|
rows.forEach((item, i) => {
|
||||||
|
item.imgH = Math.round(WF_COL_W * ratios[i]) // 显式高度,即时到位不回流
|
||||||
|
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
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 量测页头真实高度,供分类条吸顶定位
|
||||||
|
measureLayout() {
|
||||||
|
uni.createSelectorQuery().in(this)
|
||||||
|
.select('.hd').boundingClientRect(rect => {
|
||||||
|
if (rect && rect.height) this.cateTop = Math.round(rect.height)
|
||||||
|
}).exec()
|
||||||
|
},
|
||||||
|
|
||||||
|
Info(e) {
|
||||||
|
uni.setStorageSync('merchantInfo', e)
|
||||||
|
NavgateTo('/packages/localLife/detail/index', { isLogin: false })
|
||||||
|
},
|
||||||
|
|
||||||
|
async getSearchVal() {
|
||||||
|
try {
|
||||||
|
const res = await request(apiArr.hotWord, 'POST', {}, { slice: false })
|
||||||
|
this.hotWord = res.search_hot_word ? res.search_hot_word.split(/[,,]/) : []
|
||||||
|
} catch (e) { this.hotWord = [] }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
onLoad() {
|
||||||
|
const menu = menuButtonInfo()
|
||||||
|
this.top = menu.top
|
||||||
|
this.localHeight = menu.height
|
||||||
|
// 分类条吸顶位置 = 页头高度:状态栏 top(px) + 页头内容(定位64+搜索8+72+下内边距20=164rpx)换算 px
|
||||||
|
const sys = uni.getSystemInfoSync()
|
||||||
|
this.cateTop = Math.round(this.top + 164 * sys.windowWidth / 750)
|
||||||
|
this.init()
|
||||||
|
this.getSearchVal()
|
||||||
|
},
|
||||||
|
onReady() {
|
||||||
|
// 用真实渲染高度校正吸顶位置(估算可能有偏差)
|
||||||
|
this.$nextTick(() => {
|
||||||
|
uni.createSelectorQuery().in(this).select('.hd').boundingClientRect(rect => {
|
||||||
|
if (rect && rect.height) this.cateTop = Math.round(rect.height)
|
||||||
|
}).exec()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
onShow() {
|
||||||
|
const storageLocation = uni.getStorageSync('location')
|
||||||
|
if (storageLocation && this.loading) {
|
||||||
|
// 仅当定位城市/区域「真正变化」时才重新初始化。
|
||||||
|
// 注意:不能拿 currentCity.address 和 storageLocation.district 比(两者是不同字段,
|
||||||
|
// 几乎恒为不等),否则每次返回首页都会重载 → 回到顶部、丢失当前分类,体验很差。
|
||||||
|
const changed = !this.currentCity ||
|
||||||
|
this.currentCity.cityName !== storageLocation.cityName ||
|
||||||
|
this.currentCity.district !== storageLocation.district
|
||||||
|
if (changed) {
|
||||||
|
this.currentCity = storageLocation
|
||||||
|
this.init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onPullDownRefresh() {
|
||||||
|
this.init()
|
||||||
|
uni.stopPullDownRefresh()
|
||||||
|
},
|
||||||
|
|
||||||
|
async onReachBottom() {
|
||||||
|
if (this.merchantHasMore && !this.loadingMore) {
|
||||||
|
await this.getMechantList(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@import url("./index2.css");
|
||||||
|
</style>
|
||||||
@ -7,8 +7,13 @@ page {
|
|||||||
/* height: 578rpx; */
|
/* height: 578rpx; */
|
||||||
/* background: linear-gradient(to bottom, #FFE7E1, #FFFFFF); */
|
/* background: linear-gradient(to bottom, #FFE7E1, #FFFFFF); */
|
||||||
/* background: linear-gradient( 359deg, rgba(255,255,255,0) 0%, #FDD9CA 100%); */
|
/* background: linear-gradient( 359deg, rgba(255,255,255,0) 0%, #FDD9CA 100%); */
|
||||||
background: url('https://static.hshuishang.com/property-img-file/page_user_Group_1584.png');
|
/* 与设计稿个人中心一致:顶部柔和五彩渐变(薄荷绿/粉/淡紫低饱和叠加)向下淡出到灰底 */
|
||||||
background-size: contain;
|
background:
|
||||||
|
radial-gradient(120% 56% at 12% 0%, rgba(150, 222, 210, 0.62) 0%, rgba(150, 222, 210, 0) 52%),
|
||||||
|
radial-gradient(110% 52% at 52% 2%, rgba(255, 188, 210, 0.55) 0%, rgba(255, 188, 210, 0) 56%),
|
||||||
|
radial-gradient(120% 58% at 96% 0%, rgba(200, 176, 242, 0.6) 0%, rgba(200, 176, 242, 0) 56%),
|
||||||
|
linear-gradient(180deg, rgba(244, 244, 246, 0) 0%, #f4f4f6 540rpx),
|
||||||
|
#f4f4f6;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-attachment: fixed;
|
background-attachment: fixed;
|
||||||
}
|
}
|
||||||
@ -85,10 +90,12 @@ page {
|
|||||||
.login-btn {
|
.login-btn {
|
||||||
font-size: 36rpx;
|
font-size: 36rpx;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
|
color: #1a1a1a;
|
||||||
}
|
}
|
||||||
|
|
||||||
.avater_mobile {
|
.avater_mobile {
|
||||||
margin-top: 10rpx;
|
margin-top: 10rpx;
|
||||||
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
.center {
|
.center {
|
||||||
|
|||||||
@ -192,17 +192,17 @@ export default {
|
|||||||
],
|
],
|
||||||
visitList: [{
|
visitList: [{
|
||||||
image: "https://static.hshuishang.com/property-img-file/page_user_Group_1572.png",
|
image: "https://static.hshuishang.com/property-img-file/page_user_Group_1572.png",
|
||||||
name: "到家服务订单",
|
name: "服务订单",
|
||||||
url: "/packages/homeService/myOrders/index",
|
url: "/packages/homeService/myOrders/index",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
image: "https://static.hshuishang.com/property-img-file/page_user_Group_1573.png",
|
image: "https://static.hshuishang.com/property-img-file/page_user_Group_1573.png",
|
||||||
name: "我的月账单",
|
name: "月账单",
|
||||||
url: "/packages/homeService/myMonthlyBills/index",
|
url: "/packages/homeService/myMonthlyBills/index",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
image: "https://static.hshuishang.com/property-img-file/page_user_Group_1574.png",
|
image: "https://static.hshuishang.com/property-img-file/page_user_Group_1574.png",
|
||||||
name: "我的家政合同",
|
name: "家政合同",
|
||||||
url: "/packages/homeService/myContracts/index",
|
url: "/packages/homeService/myContracts/index",
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
BIN
static/smartDevice/cat-access.jpg
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
static/smartDevice/cat-environment.jpg
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
static/smartDevice/cat-guard.jpg
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
static/smartDevice/cat-lighting.jpg
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
static/smartDevice/cat-monitor.jpg
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
static/smartDevice/cat-parking.jpg
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
static/smartDevice/cat-scene.jpg
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
static/smartDevice/cat-water.jpg
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
static/smartDevice/hero1.jpg
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
static/smartDevice/hero2.jpg
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
static/smartDevice/hero3.jpg
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
static/smartDevice/hero4.jpg
Normal file
|
After Width: | Height: | Size: 33 KiB |