Browse Source

feat: 时间银行

dev_des
1054425342@qq.com 3 months ago
parent
commit
7c5e7c19df
  1. 68
      components/DynamicIsland.vue
  2. 219
      components/WaterfallLayout.vue

68
components/DynamicIsland.vue

@ -16,11 +16,14 @@
> >
<!-- 展开状态 --> <!-- 展开状态 -->
<view v-if="!actualCompactState" class="expanded-content"> <view v-if="!actualCompactState" class="expanded-content">
<template v-if="styleType!='timeShop'"> <template v-if="styleType != 'timeShop'">
<view class="top-section" > <view class="top-section">
<text class="welcome-text">{{ title }}</text> <text class="welcome-text">{{ title }}</text>
<view class="qr-code"> <view class="qr-code">
<image style="width: 39rpx;height: 39rpx;" src="https://epic.js-dyyj.com/uploads/20250728/88e0991e58e692c86c25e42537edc6ca.png"></image> <image
style="width: 39rpx; height: 39rpx"
src="https://epic.js-dyyj.com/uploads/20250728/88e0991e58e692c86c25e42537edc6ca.png"
></image>
</view> </view>
</view> </view>
<view class="bottom-section"> <view class="bottom-section">
@ -37,11 +40,15 @@
<view class="divider"></view> <view class="divider"></view>
<view class="action-section"> <view class="action-section">
<text class="action-text">{{ actionText }}</text> <text class="action-text">{{ actionText }}</text>
<image class="avatar" src="https://epic.js-dyyj.com/uploads/20250728/7d9ba1fe109643681396cb03f60f3218.png" mode="aspectFill"></image> <image
class="avatar"
src="https://epic.js-dyyj.com/uploads/20250728/7d9ba1fe109643681396cb03f60f3218.png"
mode="aspectFill"
></image>
</view> </view>
</view> </view>
</template> </template>
<template v-if="styleType=='timeShop'"> <template v-if="styleType == 'timeShop'">
<view class="bottom-section"> <view class="bottom-section">
<view class="stats-section"> <view class="stats-section">
<view class="stat-item"> <view class="stat-item">
@ -56,11 +63,25 @@
在努力一点点为更好的未来蓄力吧 在努力一点点为更好的未来蓄力吧
</view> </view>
<view class="action-text-box-msg"> <view class="action-text-box-msg">
<image class="action-text-box-img" :src="showImg('/uploads/20250728/d7ac383902515c9b507c78fdc8d29520.png')"></image> <image
今日点赞和留言<text style="font-size: 30rpx;font-weight: bold;margin: 0 10rpx;">100</text> class="action-text-box-img"
:src="
showImg(
'/uploads/20250728/d7ac383902515c9b507c78fdc8d29520.png'
)
"
></image>
今日点赞和留言<text
style="font-size: 30rpx; font-weight: bold; margin: 0 10rpx"
>100</text
>
</view> </view>
</view> </view>
<image class="avatar" src="https://epic.js-dyyj.com/uploads/20250728/7d9ba1fe109643681396cb03f60f3218.png" mode="aspectFill"></image> <image
class="avatar"
src="https://epic.js-dyyj.com/uploads/20250728/7d9ba1fe109643681396cb03f60f3218.png"
mode="aspectFill"
></image>
</view> </view>
</view> </view>
</template> </template>
@ -113,9 +134,8 @@ export default {
}, },
// //
pageId: { pageId: {
type: String, type: String,
default: 'default_page' default: "default_page",
}, },
}, },
data() { data() {
@ -143,7 +163,7 @@ export default {
// top // top
fixedTopPosition() { fixedTopPosition() {
// + (40px) + (20px) // + (40px) + (20px)
return this.statusBarHeight + 40+20 ; return this.statusBarHeight + 40 + 20;
}, },
// - // -
placeholderHeight() { placeholderHeight() {
@ -209,7 +229,7 @@ export default {
addScrollListener() { addScrollListener() {
// ID // ID
const eventName = `pageScroll_${this.pageId}`; const eventName = `pageScroll_${this.pageId}`;
console.log('DynamicIsland 添加滚动监听:', eventName); console.log("DynamicIsland 添加滚动监听:", eventName);
uni.$on(eventName, this.handlePageScroll); uni.$on(eventName, this.handlePageScroll);
}, },
// //
@ -223,17 +243,8 @@ export default {
const scrollTop = e.scrollTop || e; const scrollTop = e.scrollTop || e;
const shouldScroll = scrollTop > this.scrollThreshold; const shouldScroll = scrollTop > this.scrollThreshold;
console.log('DynamicIsland 接收到滚动事件:', {
pageId: this.pageId,
scrollTop,
shouldScroll,
currentIsScrolled: this.isScrolled
});
if (this.isScrolled !== shouldScroll) { if (this.isScrolled !== shouldScroll) {
this.isScrolled = shouldScroll; this.isScrolled = shouldScroll;
console.log('DynamicIsland 状态切换:', shouldScroll ? '紧凑模式' : '展开模式');
// //
if (uni.vibrateShort) { if (uni.vibrateShort) {
uni.vibrateShort(); uni.vibrateShort();
@ -278,7 +289,11 @@ export default {
// //
getUserInfo() { getUserInfo() {
try { try {
this.userInfo = (uni.getStorageSync('userInfo') && JSON.parse(uni.getStorageSync('userInfo'))) || this.$store.state.user.userInfo || {}; this.userInfo =
(uni.getStorageSync("userInfo") &&
JSON.parse(uni.getStorageSync("userInfo"))) ||
this.$store.state.user.userInfo ||
{};
// //
if (this.userInfo && this.userInfo.nickname) { if (this.userInfo && this.userInfo.nickname) {
this.currentTitle = `Hi!${this.userInfo.nickname},欢迎回来~`; this.currentTitle = `Hi!${this.userInfo.nickname},欢迎回来~`;
@ -395,7 +410,6 @@ export default {
.qr-code { .qr-code {
width: 32rpx; width: 32rpx;
height: 32rpx; height: 32rpx;
} }
.qr-icon { .qr-icon {
@ -563,19 +577,19 @@ export default {
// } // }
// } // }
// } // }
.action-text-box{ .action-text-box {
color: white; color: white;
.action-text-box-des{ .action-text-box-des {
font-size: 20rpx; font-size: 20rpx;
} }
.action-text-box-msg{ .action-text-box-msg {
font-size: 24rpx; font-size: 24rpx;
display: flex; display: flex;
align-items: center; align-items: center;
margin-top: 20rpx; margin-top: 20rpx;
} }
.action-text-box-img{ .action-text-box-img {
width: 57rpx; width: 57rpx;
height: 46rpx; height: 46rpx;
margin-right: 10rpx; margin-right: 10rpx;

219
components/WaterfallLayout.vue

@ -1,37 +1,56 @@
<template> <template>
<view class="waterfall-layout"> <view class="waterfall-layout">
<!-- 瀑布流容器 --> <view class="waterfall-container">
<!-- 左列 -->
<view class="column">
<view <view
class="waterfall-container" v-for="(item, index) in leftItems"
:style="{ height: containerHeight + 'rpx' }" :key="item.id || index"
class="waterfall-item"
@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>
<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 class="column">
<view <view
v-for="(item, index) in rightItems"
:key="item.id || index"
class="waterfall-item" 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)" @click="handleItemClick(item)"
> >
<!-- 图片 -->
<image <image
v-if="item.image" v-if="item.image"
:src="item.image" :src="item.image"
class="item-image" class="item-image"
mode="aspectFill" mode="aspectFill"
/> />
<!-- 内容区域 -->
<view class="item-content"> <view class="item-content">
<text v-if="item.title" class="item-title">{{ item.title }}</text> <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 v-if="item.user" class="item-footer">
<view class="user-info"> <view class="user-info">
<image <image
@ -50,6 +69,7 @@
</view> </view>
</view> </view>
</view> </view>
</view>
</template> </template>
<script> <script>
@ -61,7 +81,7 @@ export default {
type: Array, type: Array,
default: () => [], default: () => [],
}, },
// // 2
columnCount: { columnCount: {
type: Number, type: Number,
default: 2, default: 2,
@ -79,128 +99,66 @@ export default {
}, },
data() { data() {
return { return {
positionedItems: [], leftItems: [],
containerHeight: 0, rightItems: [],
columnHeights: [],
}; };
}, },
watch: { watch: {
items: { items: {
handler() { handler(newItems) {
this.$nextTick(() => { this.calculateLayout(newItems);
this.calculateLayout();
});
}, },
immediate: true, immediate: true,
deep: true, deep: true,
}, },
}, },
mounted() { mounted() {
this.calculateLayout(); this.calculateLayout(this.items);
}, },
methods: { methods: {
// // DOM
calculateLayout() { getColumnHeight(columnRef) {
if (!this.items.length) { if (!columnRef) return 0;
this.positionedItems = []; const query = uni.createSelectorQuery().in(this);
this.containerHeight = 0; return new Promise((resolve) => {
return; query.select(columnRef).boundingClientRect((data) => {
} resolve(data ? data.height : 0);
}).exec();
//
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) { calculateLayout(items) {
// if (!items || !items.length) {
const shortestColumnIndex = this.columnHeights.indexOf(Math.min(...this.columnHeights)) this.leftItems = [];
this.rightItems = [];
// 20rpx16rpx return;
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 += 476; //
} }
// padding //
height += 32; // padding16rpx this.leftItems = [];
this.rightItems = [];
// - //
if (item.title) { for (let i = 0; i < items.length; i++) {
// this.addItem(items[i]);
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) { addItem(item) {
// const descLength = item.description.length; //
// const charsPerLine = 15; // 15 if (this.leftItems.length <= this.rightItems.length) {
// const descLines = Math.min(Math.ceil(descLength / charsPerLine), 2); this.leftItems.push(item);
// height += descLines * 36 + 16; // 36rpx + } else {
// } this.rightItems.push(item);
// -
// 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() { clearItems() {
this.positionedItems = []; this.leftItems = [];
this.containerHeight = 0; this.rightItems = [];
this.columnHeights = new Array(this.columnCount).fill(0);
this.$emit("items-cleared"); this.$emit("items-cleared");
}, },
@ -211,19 +169,25 @@ export default {
// //
getAllItems() { getAllItems() {
return this.positionedItems; return [...this.leftItems, ...this.rightItems];
}, },
// //
removeItem(itemId) { removeItem(itemId) {
const index = this.positionedItems.findIndex( //
(item) => item.id === itemId let index = this.leftItems.findIndex(item => item.id === itemId);
);
if (index !== -1) { if (index !== -1) {
this.positionedItems.splice(index, 1); this.leftItems.splice(index, 1);
this.calculateLayout(); // this.$emit("item-removed", itemId);
return;
} }
//
index = this.rightItems.findIndex(item => item.id === itemId);
if (index !== -1) {
this.rightItems.splice(index, 1);
this.$emit("item-removed", itemId); this.$emit("item-removed", itemId);
}
}, },
}, },
}; };
@ -236,12 +200,19 @@ export default {
} }
.waterfall-container { .waterfall-container {
position: relative; display: flex;
width: 100%; gap: 16rpx;
padding: 0 20rpx; padding: 0 20rpx;
box-sizing: border-box; box-sizing: border-box;
} }
.column {
flex: 1;
display: flex;
flex-direction: column;
gap: 16rpx;
}
.waterfall-item { .waterfall-item {
box-sizing: border-box; box-sizing: border-box;
border-radius: 12rpx; border-radius: 12rpx;

Loading…
Cancel
Save