Browse Source

feat:首页一些静态

dev_des
1054425342@qq.com 3 months ago
parent
commit
1b67138962
  1. 1
      .vercel/project.json
  2. 167
      components/CustomTabBar.vue
  3. 502
      components/DynamicIsland.vue
  4. 355
      components/ProductSection.vue
  5. 352
      components/WaterfallLayout.vue
  6. 202
      components/header.vue
  7. 27
      package-lock.json
  8. 16
      pages.json
  9. 318
      pages/index/index.vue
  10. 255
      pages/index/timeShopBank.vue
  11. 3
      static/image/heart.png
  12. 8
      subPackages/timeShop/index.vue
  13. 6
      uni_modules/uni-transition/components/uni-transition/createAnimation.js
  14. 5
      uni_modules/uni-transition/components/uni-transition/uni-transition.vue

1
.vercel/project.json

@ -0,0 +1 @@
{"projectName":"trae_l237eu51"}

167
components/CustomTabBar.vue

@ -1,100 +1,111 @@
<template> <template>
<view>
<!-- 占位区域防止内容被TabBar遮挡 -->
<!-- 固定的TabBar -->
<view class="custom-tab-bar"> <view class="custom-tab-bar">
<view class="tab-item" v-for="(item,i) in tabBarList" :key="i" @click="switchTab(i)"> <view
<text :style="{ 'color': currentTab === i?item.selectColor:'#fff' }">{{ item.text }}</text> class="tab-item"
</view> v-for="(item, i) in tabBarList"
:key="i"
@click="switchTab(i)"
>
<text
:style="{ color: currentTab === i ? item.selectColor : '#fff' }"
>{{ item.text }}</text
>
</view>
</view> </view>
</view>
</template> </template>
<script> <script>
export default { export default {
props: { props: {
currentTab: { currentTab: {
type: Number, type: Number,
default: 0 default: 0,
}
},
data() {
return {
tabBarList: [
{
"pagePath": "pages/index/index",
"selectColor": "#00FF00",
"text": "首页"
},
{
"pagePath": "pages/index/readingBody",
"selectColor": "#00FF00",
"text": "阅读体"
},
{
"pagePath": "pages/index/sensoryStore",
"selectColor": "#00FF00",
"text": "有感商店"
},
{
"pagePath": "pages/index/intelligentAgent",
"selectColor": "#00FFFF",
"text": "智能体"
},
{
"pagePath": "pages/index/iSoul",
"selectColor": "#00FF00",
"text": "iSoul"
}
]
};
},
onLoad() {
this.getCurrentTab();
}, },
methods: { },
getCurrentTab() { data() {
const pages = getCurrentPages(); return {
const currentPage = pages[pages.length - 1]; tabBarList: [
const currentPath = currentPage.route; {
this.tabBarList.forEach((item, index) => { pagePath: "pages/index/index",
if (item.pagePath === currentPath) { selectColor: "#00FF00",
this.currentTab = index; text: "首页",
} },
}); {
pagePath: "pages/index/readingBody",
selectColor: "#00FF00",
text: "阅读体",
},
{
pagePath: "pages/index/timeShopBank",
selectColor: "#00FF00",
text: "时间银行",
},
{
pagePath: "pages/index/intelligentAgent",
selectColor: "#00FFFF",
text: "智能体",
}, },
switchTab(index) { {
if (this.currentTab === index) return; pagePath: "pages/index/iSoul",
uni.switchTab({ selectColor: "#00FF00",
url: '/' + this.tabBarList[index].pagePath text: "iSoul",
}); },
],
};
},
onLoad() {
this.getCurrentTab();
},
methods: {
getCurrentTab() {
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
const currentPath = currentPage.route;
this.tabBarList.forEach((item, index) => {
if (item.pagePath === currentPath) {
this.currentTab = index;
} }
} });
},
switchTab(index) {
if (this.currentTab === index) return;
uni.switchTab({
url: "/" + this.tabBarList[index].pagePath,
});
},
},
}; };
</script> </script>
<style scoped> <style scoped>
.custom-tab-bar { .custom-tab-bar {
position: fixed; position: fixed;
bottom: 0; bottom: 0;
left: 0; left: 0;
right: 0; right: 0;
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
align-items: center; align-items: center;
height: 123rpx; height: 123rpx;
z-index: 30; z-index: 30;
background: #989898; background: #989898;
} }
.tab-item { .tab-item {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
flex-shrink: 0; flex-shrink: 0;
height: 100%; height: 100%;
} }
.tab-item text { .tab-item text {
font-size: 31rpx; font-size: 31rpx;
} }
</style> </style>

502
components/DynamicIsland.vue

@ -0,0 +1,502 @@
<template>
<!-- 灵动岛占位区域 - 始终存在但控制可见性 -->
<view
class="dynamic-island-placeholder"
:class="{ visible: isScrolled }"
:style="{ height: placeholderHeight + 'rpx' }"
>
<view
class="dynamic-island"
:class="{
compact: actualCompactState,
fixed: isFixed,
}"
:style="{ top: isFixed ? fixedTopPosition + 'px' : 0 }"
@click="handleToggle"
>
<!-- 展开状态 -->
<view v-if="!actualCompactState" class="expanded-content">
<view class="top-section">
<text class="welcome-text">{{ title }}</text>
<view class="qr-code">
<view class="qr-icon">
<image style="width: 39rpx;height: 39rpx;" src="https://epic.js-dyyj.com/uploads/20250728/ce88153acc92e0e2fca7acaa4ccec5c1.png"></image>
</view>
</view>
</view>
<view class="bottom-section">
<view class="stats-section">
<view class="stat-item">
<text class="stat-number">{{ getStatNumber("权益") }}</text>
<text class="stat-label">权益</text>
</view>
<view class="stat-item">
<text class="stat-number">{{ getStatNumber("时间银行") }}</text>
<text class="stat-label">时间银行</text>
</view>
</view>
<view class="divider"></view>
<view class="action-section">
<text class="action-text">{{ actionText }}</text>
<image class="avatar" src="https://epic.js-dyyj.com/uploads/20250728/7d9ba1fe109643681396cb03f60f3218.png" mode="aspectFill"></image>
</view>
</view>
</view>
<!-- 紧凑状态 -->
<view v-else class="compact-content">
<text class="compact-name">{{ getCompactName() }}</text>
<image
class="compact-avatar"
src="https://epic.js-dyyj.com/uploads/20250728/7d9ba1fe109643681396cb03f60f3218.png"
mode="aspectFill"
></image>
</view>
</view>
</view>
</template>
<script>
export default {
name: "DynamicIsland",
props: {
isCompact: {
type: Boolean,
default: false,
},
isFixed: {
type: Boolean,
default: false,
},
title: {
type: String,
default: "正在播放音乐",
},
subtitle: {
type: String,
default: "周杰伦 - 青花瓷",
},
avatarUrl: {
type: String,
default: "https://picsum.photos/80/80",
},
actionText: {
type: String,
default: "暂停",
},
},
data() {
return {
isExpanded: false,
statusBarHeight: 0,
isScrolled: false,
scrollThreshold: 160, // 160rpx
// props
currentTitle: "Hi!杰西卡酱,欢迎回来~",
currentSubtitle: "2个权益 | 120时间银行",
currentAvatar: "https://picsum.photos/80/80",
currentAction: "激活你的Agent",
};
},
computed: {
// 使
actualCompactState() {
if (this.isScrolled) {
return !this.isExpanded;
}
return false; //
},
// top
fixedTopPosition() {
// + (40px) + (20px)
return this.statusBarHeight + 40+20 ;
},
// -
placeholderHeight() {
//
// 32rpx() + 160rpx() + 24rpx() = 216rpx
const islandHeightRpx = 160; //
const topMarginRpx = 32; //
const bottomMarginRpx = 24; //
return topMarginRpx + islandHeightRpx + bottomMarginRpx;
},
// 使props
title() {
return this.currentTitle;
},
subtitle() {
return this.currentSubtitle;
},
avatarUrl() {
return this.currentAvatar;
},
actionText() {
return this.currentAction;
},
//
isFixed() {
return this.isScrolled;
},
},
mounted() {
// uni-app
this.setStatusBarHeight();
//
this.addScrollListener();
},
beforeDestroy() {
//
this.removeScrollListener();
},
methods: {
handleToggle() {
if (this.isScrolled) {
//
this.isExpanded = !this.isExpanded;
this.$emit("toggle", this.isExpanded);
} else {
//
this.$emit("toggle");
}
},
handleAction() {
this.$emit("action");
},
//
collapseIsland() {
if (this.isScrolled && this.isExpanded) {
this.isExpanded = false;
this.$emit("toggle", this.isExpanded);
}
},
//
addScrollListener() {
//
uni.$on("pageScroll", this.handlePageScroll);
},
//
removeScrollListener() {
//
uni.$off("pageScroll", this.handlePageScroll);
},
//
handlePageScroll(e) {
const scrollTop = e.scrollTop || e;
const shouldScroll = scrollTop > this.scrollThreshold;
if (this.isScrolled !== shouldScroll) {
this.isScrolled = shouldScroll;
//
if (uni.vibrateShort) {
uni.vibrateShort();
}
}
//
if (this.isScrolled) {
this.collapseIsland();
}
},
getCompactName() {
//
return "杰西卡酱";
},
getStatNumber(type) {
//
if (this.subtitle) {
if (type === "权益") {
const match = this.subtitle.match(/(\d+)个权益/);
return match ? match[1] : "0";
} else if (type === "时间银行") {
const match = this.subtitle.match(/(\d+)时间银行/);
return match ? match[1] : "0";
}
}
return "0";
},
//
setStatusBarHeight() {
try {
const systemInfo = uni.getSystemInfoSync();
this.statusBarHeight = systemInfo.statusBarHeight || 0;
} catch (e) {
console.warn("获取系统信息失败:", e);
this.statusBarHeight = 0;
}
},
},
};
</script>
<style scoped lang="scss">
/* 灵动岛占位区域样式 - 始终存在但控制可见性 */
.dynamic-island-placeholder {
width: 100%;
background: transparent;
position: relative;
opacity: 1;
transition: opacity 0.3s ease;
}
.dynamic-island-placeholder.visible {
opacity: 1;
}
/* 当灵动岛不是固定状态时,确保它在占位符内正常显示 */
.dynamic-island-placeholder .dynamic-island:not(.fixed) {
position: relative;
z-index: 100;
}
.dynamic-island {
margin: 24rpx auto 24rpx;
z-index: 100;
background: #000000;
backdrop-filter: blur(20rpx);
border-radius: 40rpx;
transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1);
overflow: hidden;
//
width: 710rpx;
height: 220rpx;
//
&.compact {
width: 300rpx;
height: 80rpx;
border-radius: 40rpx;
.expanded-content {
opacity: 0;
visibility: hidden;
transition: opacity 0.2s ease-out, visibility 0s linear 0.2s;
}
}
&:not(.compact) {
.compact-content {
opacity: 0;
visibility: hidden;
transition: opacity 0.2s ease-out, visibility 0s linear 0.2s;
}
}
//
&.fixed {
position: fixed;
left: 50%;
transform: translateX(-50%);
z-index: 998;
margin: 0;
transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1);
animation: slideInFromTop 0.6s cubic-bezier(0.4, 0, 0.2, 1) forwards;
}
}
.expanded-content {
display: flex;
flex-direction: column;
height: 100%;
padding: 24rpx 32rpx;
opacity: 1;
visibility: visible;
transition: opacity 0.3s ease-in 0.4s, visibility 0s linear 0s;
}
.top-section {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
}
.welcome-text {
font-size: 28rpx;
color: #ffffff;
font-weight: 500;
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-weight: bold;
}
.qr-code {
width: 32rpx;
height: 32rpx;
display: flex;
align-items: center;
justify-content: center;
}
.qr-icon {
width: 24rpx;
height: 24rpx;
background: rgba(255, 255, 255, 0.8);
border-radius: 4rpx;
position: relative;
&::before {
content: "";
position: absolute;
top: 2rpx;
left: 2rpx;
right: 2rpx;
bottom: 2rpx;
background: rgba(0, 0, 0, 0.6);
border-radius: 2rpx;
}
}
.bottom-section {
display: flex;
align-items: center;
flex: 1;
}
.stats-section {
display: flex;
gap: 32rpx;
}
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
font-size: 28rpx;
font-weight: bold;
}
.stat-number {
font-size: 32rpx;
color: #ffffff;
font-weight: bold;
line-height: 1;
margin-bottom: 4rpx;
}
.stat-label {
font-size: 28rpx;
color: white;
line-height: 1;
font-weight: bold;
margin-top: 20rpx;
}
.divider {
width: 2rpx;
height: 60rpx;
background: rgba(255, 255, 255, 0.3);
margin: 0 24rpx;
}
.action-section {
display: flex;
align-items: center;
flex: 1;
justify-content: space-between;
}
.action-text {
font-size: 26rpx;
color: #ffffff;
font-weight: bold;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 200rpx;
}
.avatar {
width: 113rpx;
height: 113rpx;
border-radius: 50%;
}
//
.dynamic-island:active {
transform: scale(0.98);
&.fixed {
transform: translateX(-50%) scale(0.98);
}
}
//
@keyframes slideInFromTop {
0% {
transform: translateX(-50%) translateY(-100%);
opacity: 0;
}
100% {
transform: translateX(-50%) translateY(0);
opacity: 1;
}
}
.compact-content {
display: flex;
align-items: center;
justify-content: space-between;
height: 100%;
padding: 0 24rpx;
opacity: 1;
visibility: visible;
transition: opacity 0.3s ease-in 0.4s, visibility 0s linear 0s;
}
.compact-name {
font-size: 27rpx;
color: #ffffff;
font-weight: bold;
flex: 1;
text-align: left;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 200rpx;
}
.compact-avatar {
width: 48rpx;
height: 48rpx;
border-radius: 50%;
border: 2rpx solid rgba(255, 255, 255, 0.3);
flex-shrink: 0;
}
//
@keyframes pulse {
0% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.6;
transform: scale(1.1);
}
100% {
opacity: 1;
transform: scale(1);
}
}
//
// @media (max-width: 750rpx) {
// .dynamic-island {
// &.is-expanded {
// width: 100vw;
// max-width: 750rpx;
// }
// &.is-compact {
// width: 280rpx;
// }
// }
// }
</style>

355
components/ProductSection.vue

@ -0,0 +1,355 @@
<template>
<view class="product-section">
<!-- 商品标题区域 -->
<view class="title-section" :style="{ background: titleBgColor }">
<div style="display: flex; align-items: center">
<text class="title">{{ title }}</text>
<text class="more-btn" @click="handleMoreClick">更多</text>
</div>
</view>
<!-- 商品列表 -->
<view class="product-list">
<view
class="product-card"
v-for="(item, index) in productList"
:key="index"
@click="handleProductClick(item)"
>
<view class="card-image-container">
<image
class="card-image"
:src="showImg(item.image)"
mode="aspectFill"
></image>
<!-- 图片蒙层 -->
<view class="image-overlay"></view>
<!-- 智能体标签 -->
<view class="content-box-info" v-if="!isFeel">
<view class="ai-tag">
<view class="ai-label" :style="{ borderColor: aiTagBorderColor, color: aiTagTextColor }">智能体</view>
<text class="ai-name">{{ item.aiName }}</text>
</view>
<!-- 头像 -->
<image
class="avatar"
:src="showImg(item.avatar)"
mode="aspectFill"
></image>
</view>
</view>
<view class="card-content">
<view class="title-price-heart">
<view class="card-title">{{ item.title }}</view>
<view class="card-price">¥{{ item.price }}</view>
<image
v-if="!item.isLiked"
class="heart-icon"
src="https://epic.js-dyyj.com/uploads/20250728/2f3ae212c01fa3b67be81abc5723cf5c.png"
@click.stop="handleLikeClick(item, index)"
></image>
<image
v-else
class="heart-icon"
src="https://epic.js-dyyj.com/uploads/20250728/dd7ed269b24e84a2dd141da6ab980fd6.png"
@click.stop="handleLikeClick(item, index)"
></image>
<template v-if="isFeel">
<image
v-if="!item.isShop"
class="shop-icon"
src="https://epic.js-dyyj.com/uploads/20250728/195bfc195a54b93c13595a01a5d8bb3b.png"
@click.stop="handleLikeClick(item, index)"
></image>
<image
v-else
class="shop-icon"
src="https://epic.js-dyyj.com/uploads/20250728/77c4546ac6415f9db69bb10888d2a975.png"
@click.isShop="handleLikeClick(item, index)"
></image>
</template>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: "ProductSection",
props: {
//
title: {
type: String,
default: "商品列表",
},
//
productList: {
type: Array,
default: () => [],
},
//
moreUrl: {
type: String,
default: "",
},
//
detailUrlPrefix: {
type: String,
default: "/subPackages/techan/detail",
},
//
titleBgColor: {
type: String,
default: "#9efffa",
},
// AI
aiTagBorderColor: {
type: String,
default: "#01f1f196",
},
// AI
aiTagTextColor: {
type: String,
default: "#02fcfc",
},
isFeel:{
type: Boolean,
default: false,
}
},
methods: {
//
handleMoreClick() {
if (this.moreUrl) {
uni.navigateTo({
url: this.moreUrl,
});
}
this.$emit("more-click");
},
//
handleProductClick(item) {
if (this.detailUrlPrefix) {
uni.navigateTo({
url: `${this.detailUrlPrefix}?id=${item.id}`,
});
}
this.$emit("product-click", item);
},
//
handleLikeClick(item, index) {
//
const updatedItem = { ...item, isLiked: !item.isLiked };
//
uni.showToast({
title: updatedItem.isLiked ? "已收藏" : "取消收藏",
icon: "none",
duration: 1500,
});
//
this.$emit("like-toggle", { item: updatedItem, index });
},
//
showImg(img) {
if (!img) return;
if (img.indexOf("https://") != -1 || img.indexOf("http://") != -1) {
return img;
} else {
return this.$options._base.prototype.NEWAPIURL + img;
}
},
},
};
</script>
<style lang="scss" scoped>
.product-section {
width: 100%;
}
//
.title-section {
padding: 18rpx 24rpx;
margin: 40rpx 16rpx 0rpx;
display: inline-block;
border-radius: 14rpx 14rpx 0 0;
.title {
font-size: 36rpx;
font-weight: bold;
color: #4d4d4d;
}
.more-btn {
font-size: 18rpx;
color: #999999;
margin-left: 35rpx;
}
}
//
.product-list {
display: flex;
flex-direction: column;
gap: 24rpx;
background-color: white;
padding: 22rpx;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.08);
}
//
.product-card {
background: #ffffff;
border-radius: 24rpx;
overflow: hidden;
transition: transform 0.2s ease;
&:active {
transform: scale(0.98);
}
}
//
.card-image-container {
position: relative;
height: 200rpx;
overflow: hidden;
border-radius: 20rpx;
}
.card-image {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 20rpx;
}
//
.image-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #00000047;
pointer-events: none;
border-radius: 20rpx;
}
//
.content-box-info {
position: absolute;
top: 0rpx;
left: 0rpx;
display: flex;
align-items: center;
justify-content: space-between;
width: 666rpx;
padding: 0rpx 20rpx;
}
.ai-tag {
padding: 8rpx 0rpx;
display: flex;
align-items: center;
gap: 8rpx;
.ai-label {
border: 1rpx solid;
padding: 0rpx 15rpx;
height: 40rpx;
line-height: 38rpx;
font-weight: bold;
font-size: 16rpx;
border-radius: 4rpx;
}
.ai-name {
font-size: 27rpx;
font-weight: bold;
color: #ffffff;
margin-left: 10rpx;
}
}
//
.avatar {
width: 119rpx;
height: 119rpx;
border-radius: 50%;
}
//
.card-content {
padding: 24rpx 0;
}
.title-price-heart {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.card-title {
font-size: 27rpx;
font-weight: bold;
color: #000000;
line-height: 1.4;
flex: 1;
margin-right: 20rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.card-price {
font-size: 36rpx;
font-weight: bold;
color: #ff6b6b;
margin-right: 24rpx;
flex-shrink: 0;
min-width: 120rpx;
}
.heart-icon {
width: 35rpx;
height: 29rpx;
transition: all 0.3s ease;
flex-shrink: 0;
&.liked {
opacity: 1;
filter: hue-rotate(320deg) saturate(2);
}
&:active {
transform: scale(1.2);
}
}
.shop-icon{
width: 39rpx;
height: 36rpx;
transition: all 0.3s ease;
flex-shrink: 0;
margin-left: 10rpx;
&.liked {
opacity: 1;
filter: hue-rotate(320deg) saturate(2);
}
&:active {
transform: scale(1.2);
}
}
</style>

352
components/WaterfallLayout.vue

@ -0,0 +1,352 @@
<template>
<view class="waterfall-layout">
<!-- 瀑布流容器 -->
<view
class="waterfall-container"
:style="{ height: containerHeight + 'rpx' }"
>
<view
class="waterfall-item"
v-for="(item, index) in positionedItems"
:key="item.id"
:style="{
position: 'absolute',
left: item.left + 'rpx',
top: item.top + 'rpx',
width: item.width + 'rpx',
}"
@click="handleItemClick(item)"
>
<!-- 图片 -->
<image
v-if="item.image"
:src="item.image"
class="item-image"
mode="aspectFill"
/>
<!-- 内容区域 -->
<view class="item-content">
<text v-if="item.title" class="item-title">{{ item.title }}</text>
<text v-if="item.description" class="item-desc">{{
item.description
}}</text>
<view v-if="item.tags && item.tags.length" class="item-tags">
<text class="tag" v-for="tag in item.tags" :key="tag">{{
tag
}}</text>
</view>
<!-- 用户信息和点赞 -->
<view v-if="item.user" class="item-footer">
<view class="user-info">
<image
:src="item.user.avatar"
class="user-avatar"
mode="aspectFill"
/>
<text class="username">{{ item.user.name }}</text>
</view>
<view class="like-info">
<text class="like-icon"></text>
<text class="like-count">{{ item.likes }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: "WaterfallLayout",
props: {
//
items: {
type: Array,
default: () => [],
},
//
columnCount: {
type: Number,
default: 2,
},
// rpx
columnGap: {
type: Number,
default: 16,
},
// rpx
itemGap: {
type: Number,
default: 16,
},
},
data() {
return {
positionedItems: [],
containerHeight: 0,
columnHeights: [],
};
},
watch: {
items: {
handler() {
this.$nextTick(() => {
this.calculateLayout();
});
},
immediate: true,
deep: true,
},
},
mounted() {
this.calculateLayout();
},
methods: {
//
calculateLayout() {
if (!this.items.length) {
this.positionedItems = [];
this.containerHeight = 0;
return;
}
//
this.columnHeights = new Array(this.columnCount).fill(0);
this.positionedItems = [];
//
this.items.forEach((item, index) => {
this.calculateItemPosition(item, index);
});
//
this.containerHeight = Math.max(...this.columnHeights) + this.itemGap;
},
//
calculateItemPosition(item, index) {
//
const shortestColumnIndex = this.columnHeights.indexOf(Math.min(...this.columnHeights))
// 20rpx16rpx
const sideMargin = 20 //
const columnGap = 16 //
// 20rpx
const availableWidth = 750 - 2 * sideMargin // = 750 - 40 = 710rpx
const itemWidth = Math.floor((availableWidth - (this.columnCount - 1) * columnGap) / this.columnCount)
// - 20rpx
const left = sideMargin + shortestColumnIndex * (itemWidth + columnGap)
// topitemGapitemGap
const top = this.columnHeights[shortestColumnIndex] === 0 ? this.itemGap : this.columnHeights[shortestColumnIndex] + this.itemGap;
//
const estimatedHeight = this.estimateItemHeight(item);
//
this.positionedItems.push({
...item,
left,
top,
width: itemWidth,
});
//
this.columnHeights[shortestColumnIndex] = top + estimatedHeight;
},
//
estimateItemHeight(item) {
let height = 0;
//
if (item.image) {
height += 300; //
}
// padding
height += 32; // padding16rpx
// -
if (item.title) {
//
const titleLength = item.title.length;
const charsPerLine = 12; // 12
const titleLines = Math.min(Math.ceil(titleLength / charsPerLine), 2);
height += titleLines * 40 + 12; // 40rpx +
}
// -
if (item.description) {
const descLength = item.description.length;
const charsPerLine = 15; // 15
const descLines = Math.min(Math.ceil(descLength / charsPerLine), 2);
height += descLines * 36 + 16; // 36rpx +
}
// -
if (item.tags && item.tags.length) {
//
const tagRows = Math.ceil(item.tags.length / 3); // 3
height += tagRows * 36 + 16; // 36rpx +
}
//
if (item.user) {
height += 48 + 16; // +
}
return height;
},
//
clearItems() {
this.positionedItems = [];
this.containerHeight = 0;
this.columnHeights = new Array(this.columnCount).fill(0);
this.$emit("items-cleared");
},
//
handleItemClick(item) {
this.$emit("item-click", item);
},
//
getAllItems() {
return this.positionedItems;
},
//
removeItem(itemId) {
const index = this.positionedItems.findIndex(
(item) => item.id === itemId
);
if (index !== -1) {
this.positionedItems.splice(index, 1);
this.calculateLayout(); //
}
this.$emit("item-removed", itemId);
},
},
};
</script>
<style scoped>
.waterfall-layout {
width: 100%;
box-sizing: border-box;
}
.waterfall-container {
position: relative;
width: 100%;
padding: 0 20rpx;
box-sizing: border-box;
}
.waterfall-item {
box-sizing: border-box;
border-radius: 12rpx;
background: #fff;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
overflow: hidden;
transition: transform 0.2s ease;
}
.waterfall-item:active {
transform: scale(0.98);
}
.item-image {
width: 100%;
height: 300rpx;
object-fit: cover;
}
.item-content {
padding: 16rpx;
}
.item-title {
font-size: 28rpx;
font-weight: 600;
color: #333;
line-height: 1.3;
margin-bottom: 12rpx;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
}
.item-desc {
font-size: 24rpx;
color: #666;
line-height: 1.4;
margin-bottom: 16rpx;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
}
.item-tags {
display: flex;
flex-wrap: wrap;
gap: 8rpx;
margin-bottom: 16rpx;
}
.tag {
padding: 4rpx 12rpx;
background: #f5f5f5;
color: #666;
font-size: 20rpx;
border-radius: 12rpx;
white-space: nowrap;
}
.item-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 16rpx;
}
.user-info {
display: flex;
align-items: center;
gap: 12rpx;
}
.user-avatar {
width: 32rpx;
height: 32rpx;
border-radius: 50%;
}
.username {
font-size: 22rpx;
color: #666;
}
.like-info {
display: flex;
align-items: center;
gap: 6rpx;
}
.like-icon {
font-size: 24rpx;
color: #ff6b6b;
}
.like-count {
font-size: 22rpx;
color: #666;
}
</style>

202
components/header.vue

@ -1,56 +1,160 @@
<template> <template>
<view class="header" :style="{'height': height+'px','padding-top':statusBarHeight+'px'}"> <view>
<image src="https://static.ticket.sz-trip.com/epicSoul/readingBody/search.png" mode="heightFix" <!-- 占位区域防止内容塌陷 -->
@click="gotoPath('/subPackages/search/search?type='+type)" v-if="isSearch"></image> <view v-if="fixed" class="header-placeholder" :style="{ height: height + 'px' }"></view>
<view style="width: 50rpx;" v-else></view>
<image src="https://static.ticket.sz-trip.com/uploads/20250625/9bb05097e07570a934235983e1681a9f.png" mode="heightFix"></image> <view
<view style="width: 50rpx;"></view> class="header"
</view> :class="{ 'header-fixed': fixed }"
:style="{ height: height + 'px', 'padding-top': statusBarHeight + 'px' }"
>
<!-- 左侧地区筛选和搜索 -->
<view class="left-section" v-if="isSearch">
<view class="location-selector" @click="showLocationPicker">
<text class="location-text">{{ selectedLocation }}</text>
<image
class="dropdown-icon"
src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIiIGhlaWdodD0iOCIgdmlld0JveD0iMCAwIDEyIDgiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGQ9Ik0xIDFMNiA2TDExIDEiIHN0cm9rZT0iIzk5OTk5OSIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPC9zdmc+"
mode="heightFix"
></image>
</view>
<image
class="search-icon"
src="https://static.ticket.sz-trip.com/epicSoul/readingBody/search.png"
mode="heightFix"
@click="gotoPath('/subPackages/search/search?type=' + type)"
></image>
</view>
<view class="left-section" v-else></view>
<!-- 中间Logo -->
<image
class="logo"
src="https://static.ticket.sz-trip.com/uploads/20250625/9bb05097e07570a934235983e1681a9f.png"
mode="heightFix"
></image>
<!-- 右侧占位 -->
<view class="right-section"></view>
</view>
</view>
</template> </template>
<script> <script>
export default { export default {
props: { props: {
isSearch: { isSearch: {
type: Boolean, type: Boolean,
default: true default: true,
}, },
type: { type: {
type: String, type: String,
default: '' default: "",
} },
}, fixed: {
name:"header", type: Boolean,
data() { default: false,
return { },
// },
height: 0, name: "header",
statusBarHeight: 0, data() {
}; return {
}, //
mounted() { height: 0,
this.initRectInfo() statusBarHeight: 0,
}, //
methods:{ selectedLocation: "苏州",
initRectInfo () { };
const sysInfo = uni.getSystemInfoSync() },
this.statusBarHeight = sysInfo.statusBarHeight mounted() {
// this.initRectInfo();
this.height = sysInfo.statusBarHeight + 40 },
console.log("sysInfo" ,sysInfo) methods: {
}, initRectInfo() {
} const sysInfo = uni.getSystemInfoSync();
} this.statusBarHeight = sysInfo.statusBarHeight;
//
this.height = sysInfo.statusBarHeight + 40;
console.log("sysInfo", sysInfo);
},
showLocationPicker() {
//
uni.showActionSheet({
itemList: ["苏州", "上海", "杭州", "南京", "无锡"],
success: (res) => {
const locations = ["苏州", "上海", "杭州", "南京", "无锡"];
this.selectedLocation = locations[res.tapIndex];
//
this.$emit("locationChange", this.selectedLocation);
},
});
},
},
};
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.header{ .header {
flex-shrink: 0; flex-shrink: 0;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-around; justify-content: space-between;
image{ padding: 0 20rpx;
height: 46.16rpx; background-color: #fff;
}
} &.header-fixed {
</style> position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 999;
}
}
.header-placeholder {
width: 100%;
}
.left-section {
display: flex;
align-items: center;
flex: 1;
.location-selector {
display: flex;
align-items: center;
padding: 8rpx 16rpx;
// border: 2rpx solid #e0e0e0;
border-radius: 20rpx;
background-color: #fff;
min-width: 100rpx;
margin-right: 20rpx;
.location-text {
font-size: 30rpx;
color: #333;
margin-right: 8rpx;
}
.dropdown-icon {
width: 24rpx;
height: 16rpx;
}
}
.search-icon {
height: 36.16rpx;
}
}
.logo {
height: 36.16rpx;
position: absolute;
left: 50%;
transform: translateX(-50%);
}
.right-section {
flex: 1;
}
</style>

27
package-lock.json

@ -1,8 +1,33 @@
{ {
"name": "cgc_wechat", "name": "cgc_wechat",
"version": "1.0.0", "version": "1.0.0",
"lockfileVersion": 1, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": {
"": {
"name": "cgc_wechat",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"moment": "^2.30.1",
"ydui-district": "^1.1.0"
},
"devDependencies": {}
},
"node_modules/moment": {
"version": "2.30.1",
"resolved": "https://registry.npmmirror.com/moment/-/moment-2.30.1.tgz",
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
"engines": {
"node": "*"
}
},
"node_modules/ydui-district": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/ydui-district/-/ydui-district-1.1.0.tgz",
"integrity": "sha512-MBhvfaR5Gkn6MUmEnrH1A7IFB5igALuDgtIF+gz3dRwNwW9+KOmih7z+xZFfGluMsEbWaT7C3lWOckYsLZQnFg=="
}
},
"dependencies": { "dependencies": {
"moment": { "moment": {
"version": "2.30.1", "version": "2.30.1",

16
pages.json

@ -47,7 +47,16 @@
"navigationBarTitleText": "登录", "navigationBarTitleText": "登录",
"navigationStyle": "custom" "navigationStyle": "custom"
} }
} },
{
"path": "pages/index/timeShopBank",
"style": {
"navigationBarTitleText": "时间银行",
"navigationStyle": "custom"
}
}
], ],
"subPackages": [ "subPackages": [
{ {
@ -177,6 +186,7 @@
"navigationBarTitleText": "搜索" "navigationBarTitleText": "搜索"
} }
} }
] ]
}, },
{ {
@ -386,8 +396,8 @@
"visible": false "visible": false
}, },
{ {
"pagePath": "pages/index/sensoryStore", "pagePath": "pages/index/timeShopBank",
"text": "有感商店", "text": "时间银行",
"visible": false "visible": false
}, },
{ {

318
pages/index/index.vue

@ -1,99 +1,237 @@
<template> <template>
<view class="bg"> <view class="bg">
<headerVue :isSearch="false"></headerVue> <headerVue fixed></headerVue>
<view class="content">
<swiper class="top-banner" :indicator-dots="false" :autoplay="false" v-if="topBanner && topBanner.length > 0"> <!-- 灵动岛组件 -->
<swiper-item v-for="(item, index) in topBanner" :key="index" @click.stop="gotoUrlNew(item,index)"> <!-- 灵动岛组件 - 自包含无需传递参数 -->
<image class="top-banner" :src="showImg(item.head_img)" mode="aspectFill" lazy-load="true"></image> <DynamicIsland
</swiper-item> ref="dynamicIsland"
</swiper> @toggle="handleIslandToggle"
</view> @action="handleIslandAction"
<CustomTabBar :currentTab="0" /> />
<MusicControl />
</view> <view class="content" @click="handleContentClick">
<!-- 权益商品区域 -->
<ProductSection
title="权益商品"
:productList="productList"
moreUrl="/pages/index/sensoryStore"
detailUrlPrefix="/subPackages/techan/detail"
@more-click="goToMore"
@product-click="goToDetail"
@like-toggle="handleLikeToggle"
/>
<ProductSection
titleBgColor="#92FF8F"
aiTagTextColor="#08FB05"
aiTagBorderColor="#6EAA3D"
title="有感商品"
:productList="productListFeeling"
moreUrl="/pages/index/sensoryStore"
detailUrlPrefix="/subPackages/techan/detail"
@more-click="goToMore"
@product-click="goToDetail"
@like-toggle="handleLikeToggle"
isFeel
/>
<view class="tab-bar-placeholder"></view>
</view>
<CustomTabBar :currentTab="0" />
<MusicControl />
</view>
</template> </template>
<script> <script>
import MusicControl from '@/components/MusicControl.vue'; import MusicControl from "@/components/MusicControl.vue";
import headerVue from "@/components/header.vue" import headerVue from "@/components/header.vue";
import CustomTabBar from '@/components/CustomTabBar.vue'; import CustomTabBar from "@/components/CustomTabBar.vue";
export default { import DynamicIsland from "@/components/DynamicIsland.vue";
components: {CustomTabBar,headerVue,MusicControl}, import ProductSection from "@/components/ProductSection.vue";
data() { export default {
return { components: {
topBanner: [] CustomTabBar,
} headerVue,
}, MusicControl,
onLoad() { DynamicIsland,
ProductSection,
}, },
onReady() { computed: {
this.getList() //
}, },
onShow() { data() {
this.browse_record({type: 'page',title: '首页'}); return {
}, topBanner: [],
onReachBottom() { productList: [
{
}, id: 1,
methods: { image:
gotoUrlNew(item,index) { "https://epic.js-dyyj.com/uploads/20250728/58aed304917c9d60761f833c4f8dceb8.png",
if(index == 0) { avatar:
uni.switchTab({ "https://epic.js-dyyj.com/uploads/20250728/d27ef6e6c26877da7775664fed376c6f.png",
url:"/pages/index/readingBody" aiName: "文徵明",
}) title: "世界花园 | 研学之旅",
}else if(index == 1) { price: "588.00",
uni.switchTab({ isLiked: true,
url:"/pages/index/intelligentAgent" },
}) {
} id: 2,
}, image:
viewDetail(item) { "https://epic.js-dyyj.com/uploads/20250728/00e8704b23a0c9fd57023527146211b9.png",
if (item.url) { avatar:
uni.navigateTo({ "https://epic.js-dyyj.com/uploads/20250728/d7bf0dd2f3f272afba687b525a7c575c.png",
url:"/subPackages/webPage/webPage?url="+encodeURIComponent(item.url) aiName: "苏青壳",
}) title: "生命的扶持 | 风景之旅",
return price: "398.00",
} isLiked: false,
uni.navigateTo({ },
url:'/subPackages/letter/detail?id='+item.id ],
}) productListFeeling: [
}, {
getList() { id: 1,
// image:
this.Post({ "https://epic.js-dyyj.com/uploads/20250728/58aed304917c9d60761f833c4f8dceb8.png",
type_id: 3, avatar:
position: 17, "https://epic.js-dyyj.com/uploads/20250728/d27ef6e6c26877da7775664fed376c6f.png",
}, '/api/adv/getAdv').then(res => { aiName: "文徵明",
if(res.data) { title: "OUT OF SPACE 东方线香",
this.topBanner = res.data; price: "588.00",
} isLiked: true,
}); isShop:true
}, },
gotoVideo(item) { {
uni.navigateTo({ id: 2,
url: '/subPackages/video/video?item=' + encodeURIComponent(JSON.stringify(item)) image:
}) "https://epic.js-dyyj.com/uploads/20250728/00e8704b23a0c9fd57023527146211b9.png",
} avatar:
} "https://epic.js-dyyj.com/uploads/20250728/d7bf0dd2f3f272afba687b525a7c575c.png",
} aiName: "苏青壳",
title: "AI-Agent智能玩具",
price: "398.00",
isLiked: false,
},
],
};
},
onLoad() {},
onReady() {
this.getList();
},
onShow() {
this.browse_record({ type: "page", title: "首页" });
},
onPageScroll(e) {
//
uni.$emit("pageScroll", e.scrollTop);
},
onReachBottom() {},
methods: {
gotoUrlNew(item, index) {
if (index == 0) {
uni.switchTab({
url: "/pages/index/readingBody",
});
} else if (index == 1) {
uni.switchTab({
url: "/pages/index/intelligentAgent",
});
}
},
viewDetail(item) {
if (item.url) {
uni.navigateTo({
url:
"/subPackages/webPage/webPage?url=" + encodeURIComponent(item.url),
});
return;
}
uni.navigateTo({
url: "/subPackages/letter/detail?id=" + item.id,
});
},
getList() {
//
this.Post(
{
type_id: 3,
position: 17,
},
"/api/adv/getAdv"
).then((res) => {
if (res.data) {
this.topBanner = res.data;
}
});
},
gotoVideo(item) {
uni.navigateTo({
url:
"/subPackages/video/video?item=" +
encodeURIComponent(JSON.stringify(item)),
});
},
//
goToMore() {
uni.navigateTo({
url: "/pages/index/sensoryStore",
});
},
//
goToDetail(item) {
uni.navigateTo({
url: "/subPackages/techan/detail?id=" + item.id,
});
},
//
handleLikeToggle({ item, index }) {
this.productList[index] = item;
},
//
handleIslandToggle(isExpanded) {
console.log("灵动岛状态切换:", isExpanded ? "展开模式" : "紧凑模式");
},
//
handleIslandAction() {
uni.showToast({
title: "执行操作",
icon: "none",
});
console.log("执行操作");
},
//
handleContentClick() {
//
if (this.$refs.dynamicIsland) {
this.$refs.dynamicIsland.collapseIsland();
}
},
},
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.bg {
min-height: 100vh;
background: #f5f5f5;
display: flex;
flex-direction: column;
}
/* 页面样式 */
.content {
height: calc(100vh - 123rpx);
width: 100%;
padding: 0 20rpx;
box-sizing: border-box;
margin-top: 30rpx;
}
.bg { .tab-bar-placeholder {
min-height: 100vh; height: 143rpx;
background: #FFFFFF; width: 100%;
display: flex; }
flex-direction: column;
}
.content{
height: calc(100vh - 123rpx);
width: 100%;
}
.top-banner {
width: 100%;
height: calc(100vh - 123rpx);
}
</style> </style>

255
pages/index/timeShopBank.vue

@ -0,0 +1,255 @@
<template>
<view class="page-container">
<!-- 导航栏组件 -->
<headerVue fixed></headerVue>
<!-- 瀑布流组件 -->
<WaterfallLayout
:items="waterfallItems"
:column-count="2"
:column-gap="16"
:item-gap="16"
@item-click="handleItemClick"
style="margin-top: 20rpx"
/>
<!-- 控制按钮 -->
<!-- <view class="controls">
<button @click="addRandomItem" class="control-btn primary">
添加项目
</button>
<button @click="clearAllItems" class="control-btn danger">清空</button>
</view> -->
<!-- 底部占位区域防止内容被TabBar遮挡 -->
<view class="tab-bar-placeholder"></view>
<!-- 底部TabBar组件 -->
<CustomTabBar :currentTab="2" />
</view>
</template>
<script>
import WaterfallLayout from "@/components/WaterfallLayout.vue";
import headerVue from "@/components/header.vue";
import CustomTabBar from "@/components/CustomTabBar.vue";
export default {
name: "TimeShopBank",
components: {
WaterfallLayout,
headerVue,
CustomTabBar,
},
data() {
return {
waterfallItems: [],
autoAddEnabled: false,
// URL
images: [
"https://images.unsplash.com/photo-1501854140801-50d01698950b?auto=format&fit=crop&w=800",
"https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?auto=format&fit=crop&w=800",
"https://images.unsplash.com/photo-1441974231531-c6227db76b6e?auto=format&fit=crop&w=800",
"https://images.unsplash.com/photo-1469474968028-56623f02e42e?auto=format&fit=crop&w=800",
"https://images.unsplash.com/photo-1418065460487-3e41a6c84dc5?auto=format&fit=crop&w=800",
],
//
avatars: [
"https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?auto=format&fit=crop&w=100",
"https://images.unsplash.com/photo-1494790108755-2616b612b786?auto=format&fit=crop&w=100",
"https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&w=100",
"https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=100",
"https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?auto=format&fit=crop&w=100",
],
//
usernames: [
"杨璐摄影",
"旅行者小王",
"风景猎人",
"自然探索者",
"摄影师阿明",
],
//
shortTitles: ["城市风光", "自然美景", "湖光山色", "森林小径", "日出东方"],
mediumTitles: [
"繁华都市的夜景与灯光",
"壮丽的山脉与蓝天白云",
"宁静的湖泊与秋日树木",
"蜿蜒的小路穿过金色森林",
"清晨第一缕阳光照亮大地",
],
longTitles: [
"现代都市天际线在黄昏时分展现出迷人的轮廓,灯火辉煌的建筑倒映在水面上",
"雄伟的山脉在云雾缭绕中若隐若现,展现出大自然的壮丽与神秘",
"平静如镜的湖面倒映着五彩斑斓的秋叶,构成一幅完美的自然画卷",
"阳光透过茂密的树叶,在铺满落叶的林间小路上投下斑驳的光影",
"清晨的太阳从地平线缓缓升起,金色的光芒洒满整个大地,带来新的一天",
],
descriptions: [
"探索城市中隐藏的美丽角落,发现不为人知的风景",
"大自然的鬼斧神工创造了无数令人惊叹的景观",
"水与山的完美结合,创造出宁静致远的意境",
"漫步在森林中,感受大自然的清新与宁静",
"日出时分的美景总能带给人们希望和力量",
],
tagsList: [
["城市", "摄影", "旅行"],
["自然", "山脉", "探险"],
["湖泊", "秋天", "风景"],
["森林", "小路", "自然"],
["日出", "早晨", "美景"],
],
};
},
onLoad() {
this.initializeData();
},
//
onReachBottom() {
this.loadMoreItems();
},
methods: {
//
getRandomItem() {
const titleType =
Math.random() > 0.5
? Math.random() > 0.5
? this.longTitles
: this.mediumTitles
: this.shortTitles;
return {
id: Date.now() + Math.floor(Math.random() * 1000),
title: titleType[Math.floor(Math.random() * titleType.length)],
image: this.images[Math.floor(Math.random() * this.images.length)],
description:
this.descriptions[
Math.floor(Math.random() * this.descriptions.length)
],
tags: this.tagsList[Math.floor(Math.random() * this.tagsList.length)],
//
user: {
avatar: this.avatars[Math.floor(Math.random() * this.avatars.length)],
name: this.usernames[
Math.floor(Math.random() * this.usernames.length)
],
},
likes: Math.floor(Math.random() * 999) + 1, // 1-999
};
},
//
initializeData() {
const initialItems = [];
for (let i = 0; i < 20; i++) {
initialItems.push(this.getRandomItem());
}
this.waterfallItems = initialItems;
},
//
addRandomItem() {
const newItem = this.getRandomItem();
this.waterfallItems.push(newItem);
},
//
clearAllItems() {
uni.showModal({
title: "确认清空",
content: "确定要清空所有项目吗?",
success: (res) => {
if (res.confirm) {
this.waterfallItems = [];
}
},
});
},
//
handleItemClick(item) {
uni.showToast({
title: `点击了: ${item.title}`,
icon: "none",
duration: 2000,
});
},
//
handleItemAdded(item) {
console.log("项目已添加:", item.title);
},
//
handleAutoAddRequest() {
this.addRandomItem();
},
//
loadMoreItems() {
const newItems = [];
for (let i = 0; i < 10; i++) {
newItems.push(this.getRandomItem());
}
this.waterfallItems.push(...newItems);
uni.showToast({
title: "已加载10篇新文章",
icon: "success",
duration: 1500,
});
},
},
};
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background: #f8f8f8;
color: #333;
}
.controls {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
justify-content: center;
margin: 40rpx 0;
padding: 0 20rpx;
}
.control-btn {
padding: 24rpx 48rpx;
border-radius: 48rpx;
font-size: 28rpx;
border: none;
cursor: pointer;
transition: all 0.3s ease;
background: white;
color: #333;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
font-weight: 500;
}
.control-btn.primary {
background: #ff4757;
color: white;
}
.control-btn.danger {
background: #ff6b6b;
color: white;
}
.control-btn:active {
transform: scale(0.95);
}
.tab-bar-placeholder {
height: 120rpx;
}
/* 自定义样式已移至WaterfallLayout组件内部 */
</style>

3
static/image/heart.png

@ -0,0 +1,3 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M34.6 6.6C30.8 6.6 27.4 8.4 24 11.8C20.6 8.4 17.2 6.6 13.4 6.6C6.8 6.6 1.4 12 1.4 18.6C1.4 26.8 8.6 33.4 22.2 45.8L24 47.4L25.8 45.8C39.4 33.4 46.6 26.8 46.6 18.6C46.6 12 41.2 6.6 34.6 6.6Z" fill="#CCCCCC"/>
</svg>

After

Width:  |  Height:  |  Size: 319 B

8
subPackages/timeShop/index.vue

@ -0,0 +1,8 @@
<template>
</template>
<script>
</script>
<style>
</style>

6
uni_modules/uni-transition/components/uni-transition/createAnimation.js

@ -126,3 +126,9 @@ export function createAnimation(option, _this) {
clearTimeout(_this.timer) clearTimeout(_this.timer)
return new MPAnimation(option, _this) return new MPAnimation(option, _this)
} }
// #ifdef MP-WEIXIN
module.exports = {
createAnimation
}
// #endif

5
uni_modules/uni-transition/components/uni-transition/uni-transition.vue

@ -3,7 +3,12 @@
</template> </template>
<script> <script>
// #ifndef MP-WEIXIN
import { createAnimation } from './createAnimation' import { createAnimation } from './createAnimation'
// #endif
// #ifdef MP-WEIXIN
const createAnimation = require('./createAnimation.js').createAnimation
// #endif
/** /**
* Transition 过渡动画 * Transition 过渡动画

Loading…
Cancel
Save