style:修改主页样式和个人页样式
@ -2,10 +2,13 @@
|
||||
|
||||
## 一、页面结构
|
||||
|
||||
### 完整页面布局(4个分类区块纵向堆叠)
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────┐
|
||||
│ 热门推荐 │
|
||||
│ BannerCarousel │
|
||||
├──────────────────────────────────────┤
|
||||
│ 热门推荐 │
|
||||
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
|
||||
│ │ │ │ │ │ │ │ │ ← 行1 │
|
||||
│ └────┘ └────┘ └────┘ └────┘ │
|
||||
@ -13,6 +16,52 @@
|
||||
│ │ │ │ │ │ │ │ │ ← 行2 │
|
||||
│ └────┘ └────┘ └────┘ └────┘ │
|
||||
│ 🔄 刷新 查看更多 ›│
|
||||
├──────────────────────────────────────┤
|
||||
│ 热门星卡 │
|
||||
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
|
||||
│ │ │ │ │ │ │ │ │ ← 行1 │
|
||||
│ └────┘ └────┘ └────┘ └────┘ │
|
||||
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
|
||||
│ │ │ │ │ │ │ │ │ ← 行2 │
|
||||
│ └────┘ └────┘ └────┘ └────┘ │
|
||||
│ 🔄 刷新 查看更多 ›│
|
||||
├──────────────────────────────────────┤
|
||||
│ 热门吧唧 │
|
||||
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
|
||||
│ │ │ │ │ │ │ │ │ ← 行1 │
|
||||
│ └────┘ └────┘ └────┘ └────┘ │
|
||||
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
|
||||
│ │ │ │ │ │ │ │ │ ← 行2 │
|
||||
│ └────┘ └────┘ └────┘ └────┘ │
|
||||
│ 🔄 刷新 查看更多 ›│
|
||||
├──────────────────────────────────────┤
|
||||
│ 热门海报 │
|
||||
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
|
||||
│ │ │ │ │ │ │ │ │ ← 行1 │
|
||||
│ └────┘ └────┘ └────┘ └────┘ │
|
||||
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
|
||||
│ │ │ │ │ │ │ │ │ ← 行2 │
|
||||
│ └────┘ └────┘ └────┘ └────┘ │
|
||||
│ 🔄 刷新 查看更多 ›│
|
||||
├──────────────────────────────────────┤
|
||||
│ CreationGrid(原有组件,不变) │
|
||||
│ [热门作品] [最新作品] [星卡] [吧唧]... │
|
||||
└──────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 单个 HotCategoryBlock 内部结构
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────┐
|
||||
│ 热门推荐 │ ← block-title
|
||||
├──────────────────────────────────────┤
|
||||
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
|
||||
│ │ │ │ │ │ │ │ │ │
|
||||
│ └────┘ └────┘ └────┘ └────┘ │ ← block-grid (4×2)
|
||||
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
|
||||
│ │ │ │ │ │ │ │ │ │
|
||||
│ └────┘ └────┘ └────┘ └────┘ │
|
||||
│ 🔄 刷新 查看更多 ›│ ← block-actions
|
||||
└──────────────────────────────────────┘
|
||||
```
|
||||
|
||||
|
||||
@ -11,10 +11,6 @@ const app = new Vue({
|
||||
app.$mount()
|
||||
// #endif
|
||||
|
||||
// #ifdef VUE3 && H5
|
||||
import './pages/castlove/create-laser-upload.css'
|
||||
// #endif
|
||||
|
||||
// #ifdef VUE3
|
||||
import { createSSRApp } from 'vue'
|
||||
import store from './store'
|
||||
|
||||
@ -5,83 +5,85 @@
|
||||
<text class="back-icon" :style="{ color: backIconColor }">←</text>
|
||||
</view>
|
||||
|
||||
<!-- 右侧图标区域 -->
|
||||
<view class="right-icons">
|
||||
<!-- 任务图标 -->
|
||||
<view v-if="showTaskIcon" class="daily-task-group" @click="handleTaskClick">
|
||||
<!-- 1. 上层:任务图标(悬浮在上面) -->
|
||||
<view class="task-icon-box">
|
||||
<image class="task-icon-img" src="/static/icon/task.png" mode="aspectFit"></image>
|
||||
<image class="task-red-dot" src="/static/square/tishi.png" mode="aspectFit"></image>
|
||||
</view>
|
||||
|
||||
<!-- 2. 下层:文字背景块 -->
|
||||
<view class="task-text-box">
|
||||
<text class="task-text-label">每日任务</text>
|
||||
</view>
|
||||
<!-- 左侧水晶余额 -->
|
||||
<view class="crystal-balance-new" @click="handleAvatarClick">
|
||||
<!-- 1. 左侧钻石图标:层级最高,盖住右侧背景 -->
|
||||
<view class="crystal-icon-box">
|
||||
<image class="crystal-icon" src="/static/icon/crystal.png" mode="aspectFit"></image>
|
||||
</view>
|
||||
|
||||
<!-- 新手引导 -->
|
||||
<!-- <view v-if="showGuideIcon" class="daily-task-group" @click="handleGuideClick">
|
||||
1. 上层:新手引导(悬浮在上面)
|
||||
<view class="task-icon-box">
|
||||
<image class="task-icon-img" src="/static/icon/onboarding-bg.png" mode="aspectFit"></image>
|
||||
<image class="task-red-dot" src="/static/square/tishi.png" mode="aspectFit"></image>
|
||||
<!-- 2. 右侧容器:用于包裹背景和文字 -->
|
||||
<view class="crystal-info-container">
|
||||
<image class="crystal-bg-img" src="/static/square/shuijingzhanshikuang.png" mode="aspectFill">
|
||||
</image>
|
||||
|
||||
<!-- 上层:文字内容 -->
|
||||
<view class="crystal-text-layer">
|
||||
<text class="balance-number">{{ exhibitionRevenue }}</text>
|
||||
</view>
|
||||
|
||||
2. 下层:文字背景块
|
||||
<view class="task-text-box">
|
||||
<text class="task-text-label">新手引导</text>
|
||||
</view>
|
||||
</view> -->
|
||||
<!-- 收益文字 -->
|
||||
<!-- <view class="crystal-bg-layer">
|
||||
<text class="balance-income">收益 {{ hourlyEarnings }}/时</text>
|
||||
</view> -->
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 星援活动列表 -->
|
||||
<view v-if="showStarActivityIcon" class="star-activity-list">
|
||||
<view v-for="activity in starActivities" :key="activity.id" class="daily-task-group"
|
||||
@click="handleActivityClick(activity)">
|
||||
<!-- 上层:活动图标 -->
|
||||
<!-- 右侧图标区域 -->
|
||||
<view class="right-icons">
|
||||
<view class="left-items">
|
||||
<!-- 任务图标 -->
|
||||
<view v-if="showTaskIcon" class="daily-task-group" @click="handleTaskClick">
|
||||
<!-- 1. 上层:任务图标(悬浮在上面) -->
|
||||
<view class="task-icon-box">
|
||||
<image class="task-icon-img" :src="activity.icon || '/static/icon/bus-icon.png'"
|
||||
mode="aspectFit"></image>
|
||||
<image class="task-icon-img" src="/static/icon/task.png" mode="aspectFit"></image>
|
||||
<image class="task-red-dot" src="/static/square/tishi.png" mode="aspectFit"></image>
|
||||
</view>
|
||||
|
||||
<!-- 下层:文字背景块 -->
|
||||
<!-- 2. 下层:文字背景块 -->
|
||||
<view class="task-text-box">
|
||||
<text class="task-text-label">{{ activity.theme || '星援活动' }}</text>
|
||||
<text class="task-text-label">每日任务</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 新手引导 -->
|
||||
<!-- <view v-if="showGuideIcon" class="daily-task-group" @click="handleGuideClick">
|
||||
1. 上层:新手引导(悬浮在上面)
|
||||
<view class="task-icon-box">
|
||||
<image class="task-icon-img" src="/static/icon/onboarding-bg.png" mode="aspectFit"></image>
|
||||
<image class="task-red-dot" src="/static/square/tishi.png" mode="aspectFit"></image>
|
||||
</view>
|
||||
|
||||
2. 下层:文字背景块
|
||||
<view class="task-text-box">
|
||||
<text class="task-text-label">新手引导</text>
|
||||
</view>
|
||||
</view> -->
|
||||
|
||||
<!-- 星援活动列表 -->
|
||||
<view v-if="showStarActivityIcon" class="star-activity-list">
|
||||
<view v-for="activity in starActivities" :key="activity.id" class="daily-task-group"
|
||||
@click="handleActivityClick(activity)">
|
||||
<!-- 上层:活动图标 -->
|
||||
<view class="task-icon-box">
|
||||
<image class="task-icon-img" :src="activity.icon || '/static/icon/bus-icon.png'"
|
||||
mode="aspectFit"></image>
|
||||
<image class="task-red-dot" src="/static/square/tishi.png" mode="aspectFit"></image>
|
||||
</view>
|
||||
|
||||
<!-- 下层:文字背景块 -->
|
||||
<view class="task-text-box">
|
||||
<text class="task-text-label">{{
|
||||
activity.theme || "星援活动"
|
||||
}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
<!-- 顶粉水晶余额 -->
|
||||
<view class="crystal-balance-new" @click="handleAvatarClick">
|
||||
<!-- 1. 左侧钻石图标:层级最高,盖住右侧背景 -->
|
||||
<view class="crystal-icon-box">
|
||||
<image class="crystal-icon" src="/static/icon/crystal.png" mode="aspectFit"></image>
|
||||
</view>
|
||||
|
||||
<!-- 2. 右侧容器:用于包裹背景和文字 -->
|
||||
<view class="crystal-info-container">
|
||||
<image class="crystal-bg-img" src="/static/square/shuijingzhanshikuang.png" mode="aspectFill">
|
||||
</image>
|
||||
|
||||
<!-- 上层:文字内容 -->
|
||||
<view class="crystal-text-layer">
|
||||
<text class="balance-number">{{ exhibitionRevenue }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 收益文字 -->
|
||||
<view class="crystal-bg-layer">
|
||||
<text class="balance-income">收益 {{ hourlyEarnings }}/时</text>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
<!-- 用户头像 -->
|
||||
<view class="icon-item avatar-icon" @click="handleAvatarClick">
|
||||
<Avatar :key="avatarKey" :userId="userUid" :avatarUrl="userAvatarUrl" :size="96" :borderWidth="2" />
|
||||
<Avatar :key="avatarKey" :userId="userUid" :avatarUrl="userAvatarUrl" :size="68" :borderWidth="2" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@ -94,15 +96,15 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, onMounted, onUnmounted } from 'vue';
|
||||
import { onShow } from '@dcloudio/uni-app';
|
||||
import { useStore } from 'vuex';
|
||||
import Avatar from './Avatar.vue';
|
||||
import DailyTasks from '@/pages/tasks/daily-tasks.vue';
|
||||
import GuideModal from '@/pages/tasks/GuideModal.vue';
|
||||
import { useBanner } from '@/pages/square/composables/useBanner.js';
|
||||
import { reportEvent } from '@/utils/task-api.js';
|
||||
import { getEarningsSummaryApi } from '@/utils/api.js';
|
||||
import { computed, ref, onMounted, onUnmounted } from "vue";
|
||||
import { onShow } from "@dcloudio/uni-app";
|
||||
import { useStore } from "vuex";
|
||||
import Avatar from "./Avatar.vue";
|
||||
import DailyTasks from "@/pages/tasks/daily-tasks.vue";
|
||||
import GuideModal from "@/pages/tasks/GuideModal.vue";
|
||||
import { useBanner } from "@/pages/square/composables/useBanner.js";
|
||||
import { reportEvent } from "@/utils/task-api.js";
|
||||
import { getEarningsSummaryApi } from "@/utils/api.js";
|
||||
|
||||
// 获取星援活动数据(复用 square 的 useBanner)
|
||||
const { bannerActivities, loadBannerActivities } = useBanner();
|
||||
@ -111,28 +113,28 @@ const { bannerActivities, loadBannerActivities } = useBanner();
|
||||
const props = defineProps({
|
||||
showBack: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
backUrl: {
|
||||
type: String,
|
||||
default: ''
|
||||
default: "",
|
||||
},
|
||||
backIconColor: {
|
||||
type: String,
|
||||
default: '#000000'
|
||||
default: "#000000",
|
||||
},
|
||||
showTaskIcon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
default: true,
|
||||
},
|
||||
showGuideIcon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
default: true,
|
||||
},
|
||||
showStarActivityIcon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
const store = useStore();
|
||||
@ -150,24 +152,24 @@ const showGuideModal = ref(false);
|
||||
// 从本地存储读取用户信息
|
||||
const loadUserInfo = () => {
|
||||
try {
|
||||
const userStr = uni.getStorageSync('user');
|
||||
const userStr = uni.getStorageSync("user");
|
||||
if (userStr) {
|
||||
userInfo.value = JSON.parse(userStr);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('解析用户信息失败:', e);
|
||||
console.error("解析用户信息失败:", e);
|
||||
userInfo.value = null;
|
||||
}
|
||||
};
|
||||
|
||||
// 获取用户UID
|
||||
const userUid = computed(() => {
|
||||
return userInfo.value?.uid || '';
|
||||
return userInfo.value?.uid || "";
|
||||
});
|
||||
|
||||
// 获取用户头像URL
|
||||
const userAvatarUrl = computed(() => {
|
||||
return userInfo.value?.avatar_url || '';
|
||||
return userInfo.value?.avatar_url || "";
|
||||
});
|
||||
|
||||
// // 获取水晶余额
|
||||
@ -190,7 +192,7 @@ const loadEarningsSummary = async () => {
|
||||
hourlyEarnings.value = data.total_hourly_earnings || 0;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('获取收益汇总失败:', e);
|
||||
console.error("获取收益汇总失败:", e);
|
||||
}
|
||||
};
|
||||
|
||||
@ -213,7 +215,10 @@ const handleUserInfoUpdate = () => {
|
||||
// 监听余额更新事件
|
||||
const handleBalanceUpdate = (data) => {
|
||||
if (userInfo.value) {
|
||||
userInfo.value = { ...userInfo.value, crystal_balance: data.crystal_balance };
|
||||
userInfo.value = {
|
||||
...userInfo.value,
|
||||
crystal_balance: data.crystal_balance,
|
||||
};
|
||||
exhibitionRevenue.value = data.crystal_balance || 0;
|
||||
} else {
|
||||
loadUserInfo();
|
||||
@ -223,43 +228,51 @@ const handleBalanceUpdate = (data) => {
|
||||
// 检查并上报每日首次登录事件
|
||||
function checkAndReportDailyLogin() {
|
||||
// 5点之前属于昨日周期,不触发登录事件
|
||||
const now = new Date()
|
||||
const currentHour = now.getHours()
|
||||
if (currentHour < 5) return // 5点之前不触发
|
||||
const now = new Date();
|
||||
const currentHour = now.getHours();
|
||||
if (currentHour < 5) return; // 5点之前不触发
|
||||
|
||||
const today = now.toISOString().split('T')[0]
|
||||
const starId = uni.getStorageSync('star_id')
|
||||
if (!starId) return
|
||||
const today = now.toISOString().split("T")[0];
|
||||
const starId = uni.getStorageSync("star_id");
|
||||
if (!starId) return;
|
||||
|
||||
// 获取用户ID
|
||||
const userStr = uni.getStorageSync('user')
|
||||
let userId = null
|
||||
const userStr = uni.getStorageSync("user");
|
||||
let userId = null;
|
||||
if (userStr) {
|
||||
try {
|
||||
const user = JSON.parse(userStr)
|
||||
userId = user?.uid || null
|
||||
const user = JSON.parse(userStr);
|
||||
userId = user?.uid || null;
|
||||
} catch (e) { }
|
||||
}
|
||||
if (!userId) return
|
||||
if (!userId) return;
|
||||
|
||||
// 每个用户每个明星身份有独立的每日登录状态
|
||||
const dailyLoginKey = `daily_login_completed_${today}_${userId}_${starId}`
|
||||
const dailyLoginKey = `daily_login_completed_${today}_${userId}_${starId}`;
|
||||
if (!uni.getStorageSync(dailyLoginKey)) {
|
||||
reportEvent('daily_login', starId).then(() => {
|
||||
// 报告成功后标记今日已完成,并清除昨日的记录
|
||||
uni.setStorageSync(dailyLoginKey, true)
|
||||
const yesterday = new Date(now.getTime() - 86400000).toISOString().split('T')[0]
|
||||
uni.removeStorageSync(`daily_login_completed_${yesterday}_${userId}_${starId}`)
|
||||
}).catch(err => {
|
||||
console.error('上报登录事件失败:', err)
|
||||
})
|
||||
reportEvent("daily_login", starId)
|
||||
.then(() => {
|
||||
// 报告成功后标记今日已完成,并清除昨日的记录
|
||||
uni.setStorageSync(dailyLoginKey, true);
|
||||
const yesterday = new Date(now.getTime() - 86400000)
|
||||
.toISOString()
|
||||
.split("T")[0];
|
||||
uni.removeStorageSync(
|
||||
`daily_login_completed_${yesterday}_${userId}_${starId}`,
|
||||
);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error("上报登录事件失败:", err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 星援活动列表 - 从 bannerActivities 中获取非 expired 的活动
|
||||
const starActivities = computed(() => {
|
||||
// return bannerActivities.value
|
||||
return bannerActivities.value.filter(activity => activity.status !== 'expired');
|
||||
return bannerActivities.value.filter(
|
||||
(activity) => activity.status !== "expired",
|
||||
);
|
||||
});
|
||||
|
||||
// 组件挂载时加载用户信息和星援活动数据
|
||||
@ -267,9 +280,9 @@ onMounted(async () => {
|
||||
await loadUserInfo();
|
||||
await loadBannerActivities();
|
||||
// await loadEarningsSummary();
|
||||
uni.$on('avatarUpdated', handleAvatarUpdate);
|
||||
uni.$on('userInfoUpdated', handleUserInfoUpdate);
|
||||
uni.$on('balanceUpdated', handleBalanceUpdate);
|
||||
uni.$on("avatarUpdated", handleAvatarUpdate);
|
||||
uni.$on("userInfoUpdated", handleUserInfoUpdate);
|
||||
uni.$on("balanceUpdated", handleBalanceUpdate);
|
||||
|
||||
// 上报每日首次登录事件(每日5点重置后首次挂载时)
|
||||
checkAndReportDailyLogin();
|
||||
@ -282,16 +295,16 @@ onShow(() => {
|
||||
|
||||
// 组件卸载时移除事件监听
|
||||
onUnmounted(() => {
|
||||
uni.$off('avatarUpdated', handleAvatarUpdate);
|
||||
uni.$off('userInfoUpdated', handleUserInfoUpdate);
|
||||
uni.$off('balanceUpdated', handleBalanceUpdate);
|
||||
uni.$off("avatarUpdated", handleAvatarUpdate);
|
||||
uni.$off("userInfoUpdated", handleUserInfoUpdate);
|
||||
uni.$off("balanceUpdated", handleBalanceUpdate);
|
||||
});
|
||||
|
||||
// 处理返回按钮点击
|
||||
const handleBack = () => {
|
||||
if (props.backUrl) {
|
||||
uni.navigateTo({
|
||||
url: props.backUrl
|
||||
url: props.backUrl,
|
||||
});
|
||||
} else {
|
||||
// 获取页面栈
|
||||
@ -302,7 +315,7 @@ const handleBack = () => {
|
||||
} else {
|
||||
// 没有上一页,跳转到square页面
|
||||
uni.reLaunch({
|
||||
url: '/pages/square/square'
|
||||
url: "/pages/square/square",
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -316,7 +329,7 @@ const handleTaskClick = () => {
|
||||
const handleActivityClick = (activity) => {
|
||||
if (activity) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/support-activity/index?id=${activity.id}`
|
||||
url: `/pages/support-activity/index?id=${activity.id}`,
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -329,22 +342,22 @@ const handleGuideClick = () => {
|
||||
// 引导更新回调
|
||||
const handleGuideUpdated = () => {
|
||||
// 可以在这里刷新相关数据
|
||||
console.log('[Header] Guide updated');
|
||||
console.log("[Header] Guide updated");
|
||||
};
|
||||
|
||||
// 执行引导
|
||||
const handleStartGuide = (params) => {
|
||||
const key = typeof params === 'string' ? params : (params?.key || '');
|
||||
const fromStep = typeof params === 'object' ? (params.fromStep ?? 0) : 0;
|
||||
const targetPage = typeof params === 'object' ? (params.targetPage || '') : '';
|
||||
const key = typeof params === "string" ? params : params?.key || "";
|
||||
const fromStep = typeof params === "object" ? (params.fromStep ?? 0) : 0;
|
||||
const targetPage = typeof params === "object" ? params.targetPage || "" : "";
|
||||
|
||||
console.log('[Header] handleStartGuide:', { key, fromStep, targetPage });
|
||||
console.log("[Header] handleStartGuide:", { key, fromStep, targetPage });
|
||||
|
||||
showGuideListModal.value = false;
|
||||
|
||||
// 根据引导类型跳转到对应页面
|
||||
if (key === 'square_home') {
|
||||
const navigateUrl = targetPage || '/pages/square/square';
|
||||
if (key === "square_home") {
|
||||
const navigateUrl = targetPage || "/pages/square/square";
|
||||
uni.navigateTo({
|
||||
url: navigateUrl,
|
||||
success: () => {
|
||||
@ -355,7 +368,7 @@ const handleStartGuide = (params) => {
|
||||
store.dispatch("guide/resumeGuide", key);
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
},
|
||||
});
|
||||
} else {
|
||||
if (fromStep === 0) {
|
||||
@ -383,20 +396,20 @@ const handleAvatarClick = () => {
|
||||
if (pages.length > 0) {
|
||||
const currentPage = pages[pages.length - 1];
|
||||
// 检查当前页面是否是个人信息页面
|
||||
if (currentPage.route === 'pages/profile/myWorks') {
|
||||
if (currentPage.route === "pages/profile/myWorks") {
|
||||
// 已经在个人信息页面,不执行跳转
|
||||
return;
|
||||
}
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: '/pages/profile/myWorks'
|
||||
url: "/pages/profile/myWorks",
|
||||
});
|
||||
};
|
||||
|
||||
// 将方法暴露给模板
|
||||
defineExpose({
|
||||
showTaskModal,
|
||||
closeTaskModal
|
||||
closeTaskModal,
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -438,32 +451,43 @@ defineExpose({
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.left-items {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.star-activity-list {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
width: 68rpx;
|
||||
/* 控制整体宽度,根据图标大小调整 */
|
||||
height: 156rpx;
|
||||
/* 预估高度,包含图标和文字块 */
|
||||
}
|
||||
|
||||
.daily-task-group {
|
||||
position: relative;
|
||||
/* 必须是相对定位,作为子元素的定位基准 */
|
||||
width: 100rpx;
|
||||
width: 68rpx;
|
||||
/* 控制整体宽度,根据图标大小调整 */
|
||||
height: 120rpx;
|
||||
height: 156rpx;
|
||||
/* 预估高度,包含图标和文字块 */
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
|
||||
/* --- 上层图标样式 --- */
|
||||
.task-icon-box {
|
||||
position: absolute;
|
||||
top: 8rpx;
|
||||
top: 40rpx;
|
||||
/* 固定在顶部 */
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
/* 水平居中 */
|
||||
width: 88rpx;
|
||||
width: 34rpx;
|
||||
/* 图标宽度 */
|
||||
height: 88rpx;
|
||||
height: 40rpx;
|
||||
/* 图标高度 */
|
||||
z-index: 10;
|
||||
/* 确保图标在文字块上面 */
|
||||
@ -490,12 +514,13 @@ defineExpose({
|
||||
/* 关键:调整这个值,让文字块往上顶,与图标产生重叠 */
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 88rpx;
|
||||
height: 80rpx;
|
||||
background-image: url('/static/square/gerenzhongxincangpinkuang.png');
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
background-image: url("/static/square/gerenzhongxincangpinkuang.png");
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0px 4px 4px 0px #ee262654;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@ -504,11 +529,13 @@ defineExpose({
|
||||
}
|
||||
|
||||
.task-text-label {
|
||||
font-size: 16rpx;
|
||||
color: #fff;
|
||||
text-shadow: 1rpx 1rpx 2rpx rgba(0, 0, 0, 0.9);
|
||||
font-weight: 500;
|
||||
font-size: 12rpx;
|
||||
line-height: 100%;
|
||||
letter-spacing: 0%;
|
||||
color: #fff9e7;
|
||||
text-shadow: 1rpx 1rpx 2rpx rgba(0, 0, 0, 0.84);
|
||||
margin-top: 32rpx;
|
||||
|
||||
}
|
||||
|
||||
/* 水晶余额组件 */
|
||||
@ -526,13 +553,14 @@ defineExpose({
|
||||
/* --- 左侧钻石图标 --- */
|
||||
.crystal-icon-box {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 10rpx;
|
||||
left: 8rpx;
|
||||
bottom: 24rpx;
|
||||
/* 与右侧底部平齐 */
|
||||
z-index: 10;
|
||||
/* 最高层级:盖住右侧背景 */
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
width: 68rpx;
|
||||
height: 60rpx;
|
||||
transform: rotate(-15deg);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.crystal-icon {
|
||||
@ -543,12 +571,14 @@ defineExpose({
|
||||
/* --- 右侧容器 --- */
|
||||
.crystal-info-container {
|
||||
position: relative;
|
||||
height: 56rpx;
|
||||
width: 80rpx;
|
||||
border-radius: 16rpx;
|
||||
bottom: 4rpx;
|
||||
padding-right: 28rpx;
|
||||
padding-left: 56rpx;
|
||||
height: 34rpx;
|
||||
width: 120rpx;
|
||||
opacity: 1;
|
||||
border-radius: 20rpx;
|
||||
border-width: 1px;
|
||||
border: 1px solid #F3A68A40;
|
||||
box-shadow: 2px 2px 4.1px 0px #F936365C;
|
||||
|
||||
}
|
||||
|
||||
.crystal-bg-img {
|
||||
@ -567,14 +597,18 @@ defineExpose({
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 8rpx;
|
||||
border-radius: 120rpx;
|
||||
opacity: 0.75;
|
||||
background: linear-gradient(270deg, rgba(168, 17, 255, 0.83) 0%, rgba(30, 180, 217, 0.470865) 81.01%, rgba(101, 10, 153, 0) 178.07%);
|
||||
|
||||
|
||||
}
|
||||
|
||||
.balance-number {
|
||||
font-size: 24rpx;
|
||||
font-weight: bold;
|
||||
color: #FFB800;
|
||||
font-family: 'yt', sans-serif;
|
||||
color: #ffb800;
|
||||
font-family: "yt", sans-serif;
|
||||
text-shadow:
|
||||
0 0 10rpx rgba(255, 184, 0, 0.8),
|
||||
0 2rpx 4rpx rgba(0, 0, 0, 0.5);
|
||||
@ -586,14 +620,14 @@ defineExpose({
|
||||
|
||||
.balance-income {
|
||||
font-size: 18rpx;
|
||||
color: #FFFFFF;
|
||||
color: #ffffff;
|
||||
margin-bottom: 2rpx;
|
||||
margin-left: 28rpx;
|
||||
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.9);
|
||||
}
|
||||
|
||||
/* --- 下层:渐变背景 --- */
|
||||
.crystal-bg-layer {
|
||||
/* .crystal-bg-layer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@ -605,9 +639,9 @@ defineExpose({
|
||||
z-index: 1;
|
||||
|
||||
background: linear-gradient(to bottom right,
|
||||
#F0E4B1 0%,
|
||||
#F08399 50%,
|
||||
#B94E7399 100%);
|
||||
#f0e4b1 0%,
|
||||
#f08399 50%,
|
||||
#b94e7399 100%);
|
||||
|
||||
box-shadow:
|
||||
0 4rpx 12rpx rgba(255, 143, 158, 0.2),
|
||||
@ -615,15 +649,13 @@ defineExpose({
|
||||
inset 0 2rpx 4rpx rgba(255, 255, 255, 0.4),
|
||||
inset 0 -2rpx 4rpx rgba(0, 0, 0, 0.05);
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
} */
|
||||
|
||||
.icon-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
margin-left: 32rpx;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.task-icon {
|
||||
|
||||
@ -2,18 +2,22 @@
|
||||
<view class="profile-container">
|
||||
<!-- 背景图片 - 固定在容器上 -->
|
||||
<image class="background-image" src="/static/square/squearbj.png" mode="aspectFill"></image>
|
||||
<scroll-view class="profile-scroll" scroll-y :show-scrollbar="false" :enable-back-to-top="true">
|
||||
<!-- 可替换背景装饰层 -->
|
||||
<view class="replaceable-bg-layer">
|
||||
<image class="replaceable-bg-overlay-image" :src="replaceableBgOverlay" mode="aspectFill"></image>
|
||||
</view>
|
||||
<!-- Header组件 -->
|
||||
<view class="nav-back" @tap="goBack">
|
||||
<text class="nav-back-icon">←</text>
|
||||
</view>
|
||||
<view class="profile-scroll">
|
||||
<!-- 右上角模块 -->
|
||||
<view class="top-right-module">
|
||||
<text class="level-label">LV:</text>
|
||||
<text class="level-value">{{ fanLevel }}</text>
|
||||
</view>
|
||||
<!-- 上半部分:用户信息卡片 -->
|
||||
<view class="top-section">
|
||||
|
||||
<!-- 蒙层 -->
|
||||
<view class="background-overlay"></view>
|
||||
|
||||
<!-- Header组件 -->
|
||||
<view class="nav-back" @tap="goBack">
|
||||
<text class="nav-back-icon">←</text>
|
||||
</view>
|
||||
|
||||
<!-- 用户信息卡片 -->
|
||||
<view class="user-info-card">
|
||||
<!-- 上半部分:头像和用户信息左右布局 -->
|
||||
@ -43,7 +47,7 @@
|
||||
<text class="info-label">链上地址</text>
|
||||
<view class="address-row">
|
||||
<text class="info-value address-value" @longpress="copyAddress">{{ displayAddress
|
||||
}}</text>
|
||||
}}</text>
|
||||
<view class="toggle-btn" @tap="showBlockNumber = !showBlockNumber">
|
||||
<image class="toggle-icon"
|
||||
:src="showBlockNumber ? '/static/icon/show.png' : '/static/icon/hide.png'"
|
||||
@ -270,7 +274,7 @@
|
||||
<view class="logout-section">
|
||||
<button class="logout-button" @tap="handleLogout">退出登录</button>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 新手引导弹窗 -->
|
||||
<GuideModal :visible="showGuideModal" @close="showGuideModal = false" @updated="handleGuideUpdated" />
|
||||
@ -356,6 +360,15 @@ const mobile = ref('');
|
||||
const showBlockNumber = ref(false);
|
||||
const showMobile = ref(false);
|
||||
|
||||
// 可替换背景叠加层图片(类似微信更换主页背景)
|
||||
const replaceableBgOverlay = ref('/static/sucai/image-06.png');
|
||||
|
||||
|
||||
// 更新背景叠加层方法
|
||||
const updateBgOverlay = (newOverlayImage) => {
|
||||
replaceableBgOverlay.value = newOverlayImage;
|
||||
};
|
||||
|
||||
// 显示手机号(根据showMobile状态显示脱敏或未脱敏)
|
||||
const displayMobile = computed(() => {
|
||||
console.log('displayMobile computed, mobile:', mobile.value, 'showMobile:', showMobile.value);
|
||||
@ -1263,10 +1276,18 @@ onShow(() => {
|
||||
}
|
||||
|
||||
.profile-scroll {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 616rpx;
|
||||
height: 955.2rpx;
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 2;
|
||||
border-radius: 86rpx;
|
||||
background: linear-gradient(133.78deg, rgba(154, 146, 255, 0.19) 9.69%, rgba(255, 202, 229, 0.19) 43.91%, rgba(255, 250, 253, 0.1881) 76.13%, rgba(63, 63, 76, 0.19) 91.61%);
|
||||
backdrop-filter: blur(35.70000076293945px);
|
||||
box-shadow: 2px 4px 1.8px 0px #D629291F;
|
||||
|
||||
}
|
||||
|
||||
/* 隐藏滚动条 */
|
||||
@ -1276,6 +1297,35 @@ onShow(() => {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/* 右上角模块 */
|
||||
.top-right-module {
|
||||
position: fixed;
|
||||
width: 313.6rpx;
|
||||
height: 73.6rpx;
|
||||
top: -16rpx;
|
||||
right: 0;
|
||||
border-top-right-radius: 35.5rpx;
|
||||
border-top-left-radius: 7rpx;
|
||||
border-bottom-left-radius: 35.5rpx;
|
||||
border-bottom-right-radius: 6rpx;
|
||||
background: linear-gradient(135deg, #9E90FF 0%, #FFCAD3 100%);
|
||||
z-index: 4;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.level-label {
|
||||
color: #fff;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.level-value {
|
||||
color: #fff;
|
||||
font-size: 28rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 上半部分:用户信息区域 */
|
||||
.top-section {
|
||||
position: relative;
|
||||
@ -1298,12 +1348,26 @@ onShow(() => {
|
||||
/* filter: blur(20rpx); */
|
||||
}
|
||||
|
||||
.replaceable-bg-layer {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 80vh;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.replaceable-bg-overlay-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.background-overlay {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-back {
|
||||
position: relative;
|
||||
position: fixed;
|
||||
top: 96rpx;
|
||||
left: 32rpx;
|
||||
width: 80rpx;
|
||||
@ -1313,6 +1377,7 @@ onShow(() => {
|
||||
justify-content: center;
|
||||
/* background: rgba(255,255,255,0.5);
|
||||
border-radius: 50%; */
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.nav-back-icon {
|
||||
|
||||
@ -1,23 +1,88 @@
|
||||
<template>
|
||||
<view class="creation-grid">
|
||||
<view v-for="(item, index) in creationList" :key="item.id" class="creation-card" @click="handleCardClick(item)">
|
||||
<image class="creation-image" :src="item.cover_image" mode="aspectFill"></image>
|
||||
<!-- 光波动画层 - 外层 -->
|
||||
<view class="wf-like-wave wf-like-wave-outer" :class="{ 'wf-like-wave-active': likingMap[item.id] }" />
|
||||
<!-- 光波动画层 - 内层 -->
|
||||
<view class="wf-like-wave wf-like-wave-inner" :class="{ 'wf-like-wave-active': likingMap[item.id] }" />
|
||||
<view class="creation-info">
|
||||
<view class="creation-id">
|
||||
<text class="id-text">#{{ item.certificate_id }}</text>
|
||||
</view>
|
||||
<view class="creation-meta">
|
||||
<view class="creator-info">
|
||||
<image class="creator-avatar" :src="item.creator_avatar" mode="aspectFill"></image>
|
||||
<text class="creator-name">{{ item.creator_name }}</text>
|
||||
<view class="recommend-tag">
|
||||
<text class="recommend-text">猜你喜欢</text>
|
||||
</view>
|
||||
|
||||
<view class="creation-list">
|
||||
<view class="col-left">
|
||||
<view
|
||||
v-for="item in col1"
|
||||
:key="item.id"
|
||||
class="creation-card"
|
||||
@click="handleCardClick(item)"
|
||||
>
|
||||
<image
|
||||
class="creation-image"
|
||||
:src="item.cover_image"
|
||||
mode="widthFix"
|
||||
></image>
|
||||
<view class="like-badge">
|
||||
<view class="like-icon-wrapper">
|
||||
<image
|
||||
class="like-icon"
|
||||
:src="item.is_liked ? '/static/icon/heart-icon.png' : '/static/icon/heart-icon-false.png'"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
<text class="like-count">{{ formatCount(item.like_count) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="like-info">
|
||||
<image class="like-icon" src="/static/icon/heart-icon.png" mode="aspectFit"></image>
|
||||
<text class="like-count">{{ formatCount(item.like_count) }}</text>
|
||||
<view
|
||||
class="wf-like-wave wf-like-wave-outer"
|
||||
:class="{ 'wf-like-wave-active': likingMap[item.id] }"
|
||||
/>
|
||||
<view
|
||||
class="wf-like-wave wf-like-wave-inner"
|
||||
:class="{ 'wf-like-wave-active': likingMap[item.id] }"
|
||||
/>
|
||||
<view class="creation-info">
|
||||
<view class="creation-meta">
|
||||
<view class="creator-info">
|
||||
<text class="creator-name">{{ item.creator_name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="col-right">
|
||||
<view
|
||||
v-for="item in col2"
|
||||
:key="item.id"
|
||||
class="creation-card"
|
||||
@click="handleCardClick(item)"
|
||||
>
|
||||
<image
|
||||
class="creation-image"
|
||||
:src="item.cover_image"
|
||||
mode="widthFix"
|
||||
></image>
|
||||
<view class="like-badge">
|
||||
<view class="like-icon-wrapper">
|
||||
<image
|
||||
class="like-icon"
|
||||
:src="item.is_liked ? '/static/icon/heart-icon.png' : '/static/icon/heart-icon-false.png'"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
<text class="like-count">{{ formatCount(item.like_count) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="wf-like-wave wf-like-wave-outer"
|
||||
:class="{ 'wf-like-wave-active': likingMap[item.id] }"
|
||||
/>
|
||||
<view
|
||||
class="wf-like-wave wf-like-wave-inner"
|
||||
:class="{ 'wf-like-wave-active': likingMap[item.id] }"
|
||||
/>
|
||||
<view class="creation-info">
|
||||
<view class="creation-meta">
|
||||
<view class="creator-info">
|
||||
<text class="creator-name">
|
||||
{{ item.creator_name }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@ -33,154 +98,248 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, onMounted, onUnmounted } from 'vue'
|
||||
import { getInspirationFlowApi } from '@/utils/api.js'
|
||||
import { ref, watch, onMounted, onUnmounted } from "vue";
|
||||
import { getInspirationFlowApi } from "@/utils/api.js";
|
||||
|
||||
const props = defineProps({
|
||||
screenWidth: { type: Number, default: 375 },
|
||||
screenHeight: { type: Number, default: 812 },
|
||||
bannerBottom: { type: Number, default: 200 },
|
||||
useMockData: { type: Boolean, default: false },
|
||||
category: { type: String, default: '' },
|
||||
category: { type: String, default: "" },
|
||||
isActive: { type: Boolean, default: true },
|
||||
})
|
||||
});
|
||||
|
||||
const emit = defineEmits(['cardClick', 'scroll'])
|
||||
const emit = defineEmits(["cardClick", "scroll"]);
|
||||
|
||||
const creationList = ref([])
|
||||
const cursor = ref('')
|
||||
const isLoading = ref(false)
|
||||
const noMore = ref(false)
|
||||
const likingMap = ref({})
|
||||
let isComponentMounted = false
|
||||
const creationList = ref([]);
|
||||
const col1 = ref([]);
|
||||
const col2 = ref([]);
|
||||
const cursor = ref("");
|
||||
const isLoading = ref(false);
|
||||
const noMore = ref(false);
|
||||
const likingMap = ref({});
|
||||
let isComponentMounted = false;
|
||||
|
||||
const formatCount = (count) => {
|
||||
if (!count) return '0'
|
||||
if (count >= 10000) return (count / 10000).toFixed(1) + 'w'
|
||||
if (count >= 1000) return (count / 1000).toFixed(1) + 'k'
|
||||
return count.toString()
|
||||
}
|
||||
if (!count) return "0";
|
||||
if (count >= 10000) return (count / 10000).toFixed(1) + "w";
|
||||
if (count >= 1000) return (count / 1000).toFixed(1) + "k";
|
||||
return count.toString();
|
||||
};
|
||||
|
||||
const handleCardClick = (item) => {
|
||||
emit('cardClick', item)
|
||||
}
|
||||
emit("cardClick", item);
|
||||
};
|
||||
|
||||
const loadUsers = async () => {
|
||||
if (!isComponentMounted) return Promise.resolve()
|
||||
if (!isComponentMounted) return Promise.resolve();
|
||||
|
||||
cursor.value = ''
|
||||
isLoading.value = true
|
||||
noMore.value = false
|
||||
cursor.value = "";
|
||||
isLoading.value = true;
|
||||
noMore.value = false;
|
||||
|
||||
try {
|
||||
const res = await getInspirationFlowApi({ limit: 20, type: props.category, cursor: '' })
|
||||
if (!isComponentMounted) return
|
||||
const res = await getInspirationFlowApi({
|
||||
limit: 20,
|
||||
type: props.category,
|
||||
cursor: "",
|
||||
});
|
||||
if (!isComponentMounted) return;
|
||||
|
||||
if (res.code === 200 && res.data?.items && res.data.items.length > 0) {
|
||||
const items = res.data.items
|
||||
cursor.value = res.data.cursor || ''
|
||||
const items = res.data.items;
|
||||
cursor.value = res.data.cursor || "";
|
||||
|
||||
creationList.value = items.map((item) => {
|
||||
return {
|
||||
id: item.asset_id,
|
||||
certificate_id: item.asset_id,
|
||||
cover_image: item.cover_url || '',
|
||||
creator_avatar: item.owner_avatar || '',
|
||||
creator_name: item.owner_nickname || item.name || '',
|
||||
cover_image: item.cover_url || "",
|
||||
creator_avatar: item.owner_avatar || "",
|
||||
creator_name: item.owner_nickname || item.name || "",
|
||||
like_count: item.likes || item.like_count || 0,
|
||||
}
|
||||
})
|
||||
is_liked: item.is_liked || false,
|
||||
};
|
||||
});
|
||||
|
||||
// 分成两列:奇数放col1,偶数放col2
|
||||
col1.value = creationList.value.filter((_, i) => i % 2 === 0);
|
||||
col2.value = creationList.value.filter((_, i) => i % 2 === 1);
|
||||
} else {
|
||||
noMore.value = true
|
||||
noMore.value = true;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[CreationGrid] 加载数据失败', e?.message ?? e)
|
||||
console.error("[CreationGrid] 加载数据失败", e?.message ?? e);
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const loadMore = async () => {
|
||||
if (!isComponentMounted || isLoading.value || noMore.value) return
|
||||
if (!isComponentMounted || isLoading.value || noMore.value) return;
|
||||
|
||||
isLoading.value = true
|
||||
isLoading.value = true;
|
||||
try {
|
||||
const res = await getInspirationFlowApi({ limit: 20, type: props.category, cursor: cursor.value })
|
||||
if (!isComponentMounted) return
|
||||
const res = await getInspirationFlowApi({
|
||||
limit: 20,
|
||||
type: props.category,
|
||||
cursor: cursor.value,
|
||||
});
|
||||
if (!isComponentMounted) return;
|
||||
|
||||
if (res.code === 200 && res.data?.items && res.data.items.length > 0) {
|
||||
const items = res.data.items
|
||||
cursor.value = res.data.cursor || ''
|
||||
const items = res.data.items;
|
||||
cursor.value = res.data.cursor || "";
|
||||
|
||||
const newItems = items.map((item) => {
|
||||
return {
|
||||
id: item.asset_id,
|
||||
certificate_id: item.asset_id,
|
||||
cover_image: item.cover_url || '',
|
||||
creator_avatar: item.owner_avatar || '',
|
||||
creator_name: item.owner_nickname || item.name || '',
|
||||
cover_image: item.cover_url || "",
|
||||
creator_avatar: item.owner_avatar || "",
|
||||
creator_name: item.owner_nickname || item.name || "",
|
||||
like_count: item.likes || item.like_count || 0,
|
||||
}
|
||||
})
|
||||
is_liked: item.is_liked || false,
|
||||
};
|
||||
});
|
||||
|
||||
creationList.value = [...creationList.value, ...newItems]
|
||||
creationList.value = [...creationList.value, ...newItems];
|
||||
|
||||
// 重新分成两列
|
||||
col1.value = creationList.value.filter((_, i) => i % 2 === 0);
|
||||
col2.value = creationList.value.filter((_, i) => i % 2 === 1);
|
||||
} else {
|
||||
noMore.value = true
|
||||
noMore.value = true;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[CreationGrid] 加载更多失败', e?.message ?? e)
|
||||
console.error("[CreationGrid] 加载更多失败", e?.message ?? e);
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
watch(() => props.category, () => {
|
||||
loadUsers()
|
||||
})
|
||||
watch(
|
||||
() => props.category,
|
||||
() => {
|
||||
loadUsers();
|
||||
},
|
||||
);
|
||||
|
||||
watch(() => props.isActive, (active) => {
|
||||
if (active && creationList.value.length === 0) {
|
||||
loadUsers()
|
||||
}
|
||||
})
|
||||
watch(
|
||||
() => props.isActive,
|
||||
(active) => {
|
||||
if (active && creationList.value.length === 0) {
|
||||
loadUsers();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
isComponentMounted = true
|
||||
isComponentMounted = true;
|
||||
// 监听点赞事件,实时更新点赞数
|
||||
uni.$on('assetLiked', ({ asset_id, data }) => {
|
||||
uni.$on("assetLiked", ({ asset_id, data }) => {
|
||||
// 触发动画
|
||||
likingMap.value = { ...likingMap.value, [asset_id]: true }
|
||||
likingMap.value = { ...likingMap.value, [asset_id]: true };
|
||||
setTimeout(() => {
|
||||
likingMap.value = { ...likingMap.value, [asset_id]: false }
|
||||
}, 600)
|
||||
const idx = creationList.value.findIndex(c => c.id === asset_id)
|
||||
if (idx !== -1 && data && typeof data.new_like_count === 'number') {
|
||||
creationList.value[idx].like_count = data.new_like_count
|
||||
creationList.value = [...creationList.value]
|
||||
likingMap.value = { ...likingMap.value, [asset_id]: false };
|
||||
}, 600);
|
||||
const idx = creationList.value.findIndex((c) => c.id === asset_id);
|
||||
if (idx !== -1) {
|
||||
if (data && typeof data.new_like_count === "number") {
|
||||
creationList.value[idx].like_count = data.new_like_count;
|
||||
}
|
||||
if (data && typeof data.is_liked === "boolean") {
|
||||
creationList.value[idx].is_liked = data.is_liked;
|
||||
}
|
||||
creationList.value = [...creationList.value];
|
||||
}
|
||||
})
|
||||
loadUsers()
|
||||
})
|
||||
});
|
||||
loadUsers();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
isComponentMounted = false
|
||||
uni.$off('assetLiked')
|
||||
})
|
||||
isComponentMounted = false;
|
||||
uni.$off("assetLiked");
|
||||
});
|
||||
|
||||
defineExpose({ loadMore })
|
||||
defineExpose({ loadMore });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.creation-grid {
|
||||
padding: 0 24rpx;
|
||||
padding-top: 52rpx;
|
||||
padding-bottom: 88rpx;
|
||||
border-top-left-radius: 72rpx;
|
||||
border-top-right-radius: 24rpx;
|
||||
border-bottom-right-radius: 24rpx;
|
||||
border-bottom-left-radius: 24rpx;
|
||||
opacity: 0.78;
|
||||
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(148, 204, 153, 0.32) 0%,
|
||||
rgba(185, 176, 220, 0.32) 29.33%,
|
||||
rgba(207, 160, 227, 0.32) 56.25%,
|
||||
rgba(129, 227, 240, 0.32) 77.88%,
|
||||
rgba(220, 196, 155, 0.32) 100%
|
||||
);
|
||||
backdrop-filter: blur(11px);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.recommend-tag {
|
||||
position: absolute;
|
||||
top: -24rpx;
|
||||
left: 4rpx;
|
||||
width: 250rpx;
|
||||
height: 56rpx;
|
||||
border-top-left-radius: 44rpx;
|
||||
border-top-right-radius: 8rpx;
|
||||
border-bottom-right-radius: 44rpx;
|
||||
border-bottom-left-radius: 4rpx;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(96, 210, 236, 0.73) 0%,
|
||||
rgba(107, 60, 216, 0.4964) 51.44%,
|
||||
rgba(226, 137, 236, 0.73) 100%
|
||||
);
|
||||
box-shadow: 2px 2px 4px 0px #d9262640;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
padding: 24rpx;
|
||||
padding-bottom: 120rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.recommend-text {
|
||||
font-size: 24rpx;
|
||||
color: #fff;
|
||||
text-shadow: 0px 2px 8px #000000ba;
|
||||
font-weight: 600;
|
||||
line-height: 100%;
|
||||
letter-spacing: 0%;
|
||||
}
|
||||
|
||||
.creation-list {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.col-left,
|
||||
.col-right {
|
||||
width: 48%;
|
||||
}
|
||||
|
||||
.col-left {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.col-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.creation-card {
|
||||
width: 48%;
|
||||
width: 100%;
|
||||
margin-bottom: 24rpx;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-radius: 20rpx;
|
||||
@ -192,27 +351,77 @@ defineExpose({ loadMore })
|
||||
|
||||
.creation-image {
|
||||
width: 100%;
|
||||
height: 400rpx;
|
||||
display: block;
|
||||
box-shadow: 3px 3px 4.5px 2px #00000026;
|
||||
backdrop-filter: blur(0px);
|
||||
}
|
||||
|
||||
.like-badge {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 122rpx;
|
||||
height: 140rpx;
|
||||
opacity: 1;
|
||||
border-top-left-radius: 7px;
|
||||
border-bottom-right-radius: 21.5px;
|
||||
background: linear-gradient(
|
||||
177.83deg,
|
||||
rgba(83, 244, 211, 0.2) 2.52%,
|
||||
rgba(15, 9, 0, 0) 69.07%
|
||||
);
|
||||
backdrop-filter: blur(0px);
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.like-icon-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8rpx;
|
||||
}
|
||||
|
||||
.like-badge .like-icon {
|
||||
width: 38rpx;
|
||||
height: 38rpx;
|
||||
margin-right: 6rpx;
|
||||
}
|
||||
|
||||
.like-badge .like-count {
|
||||
font-size: 32rpx;
|
||||
font-weight: 400;
|
||||
line-height: 100%;
|
||||
letter-spacing: 0%;
|
||||
color: #fffabd;
|
||||
text-shadow:
|
||||
-1px 1px 4px #ce0909d6,
|
||||
0px 0px 10px #fffabd;
|
||||
}
|
||||
|
||||
/* 光波动画 */
|
||||
.wf-like-wave {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 400rpx;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.wf-like-wave-outer {
|
||||
background: radial-gradient(circle, rgba(255, 107, 107, 0.8) 0%, transparent 70%);
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
rgba(255, 107, 107, 0.8) 0%,
|
||||
transparent 70%
|
||||
);
|
||||
}
|
||||
|
||||
.wf-like-wave-inner {
|
||||
background: radial-gradient(circle, rgba(255, 184, 0, 0.6) 0%, transparent 70%);
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
rgba(255, 184, 0, 0.6) 0%,
|
||||
transparent 70%
|
||||
);
|
||||
}
|
||||
|
||||
.wf-like-wave-active {
|
||||
@ -224,6 +433,7 @@ defineExpose({ loadMore })
|
||||
opacity: 0.9;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: scale(1.5);
|
||||
@ -231,24 +441,29 @@ defineExpose({ loadMore })
|
||||
}
|
||||
|
||||
.creation-info {
|
||||
padding: 16rpx;
|
||||
}
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 148rpx;
|
||||
bottom: 0;
|
||||
border-top-left-radius: 7px;
|
||||
border-top-right-radius: 7px;
|
||||
opacity: 1;
|
||||
background: linear-gradient(
|
||||
177.83deg,
|
||||
rgba(235, 228, 219, 0) 20.38%,
|
||||
rgba(138, 135, 131, 0.228804) 45.67%,
|
||||
rgba(255, 231, 231, 0.446775) 62.04%,
|
||||
rgba(255, 255, 255, 0.710766) 83.52%,
|
||||
#ffffff 98.2%
|
||||
);
|
||||
|
||||
.creation-id {
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.id-text {
|
||||
font-size: 22rpx;
|
||||
color: #FFB800;
|
||||
font-weight: 600;
|
||||
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.3);
|
||||
backdrop-filter: blur(0px);
|
||||
}
|
||||
|
||||
.creation-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
bottom: 8rpx;
|
||||
left: 40rpx;
|
||||
}
|
||||
|
||||
.creator-info {
|
||||
@ -256,16 +471,18 @@ defineExpose({ loadMore })
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.creator-avatar {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border-radius: 50%;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.creator-name {
|
||||
font-size: 22rpx;
|
||||
color: #fff;
|
||||
font-weight: 400;
|
||||
font-size: 24rpx;
|
||||
line-height: 100%;
|
||||
letter-spacing: 0%;
|
||||
color: #554545;
|
||||
word-break: break-all;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.like-info {
|
||||
@ -273,17 +490,6 @@ defineExpose({ loadMore })
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.like-icon {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
margin-right: 4rpx;
|
||||
}
|
||||
|
||||
.like-count {
|
||||
font-size: 22rpx;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.loading-more,
|
||||
.no-more {
|
||||
width: 100%;
|
||||
@ -296,4 +502,4 @@ defineExpose({ loadMore })
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@ -56,7 +56,7 @@
|
||||
</view>
|
||||
|
||||
<!-- 区域二:分类标签区 -->
|
||||
<view id="category-section" class="category-section" :class="{ fixed: isFixed }">
|
||||
<!-- <view id="category-section" class="category-section" :class="{ fixed: isFixed }">
|
||||
<scroll-view class="category-scroll" scroll-x :show-scrollbar="false">
|
||||
<view
|
||||
v-for="(category, index) in categories"
|
||||
@ -68,7 +68,7 @@
|
||||
<text class="category-text">{{ category.label }}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view> -->
|
||||
|
||||
<!-- 热门分类区块 -->
|
||||
<view
|
||||
@ -111,14 +111,14 @@ import BannerCarousel from './components/BannerCarousel.vue'
|
||||
import HotCategoryBlock from './components/HotCategoryBlock.vue'
|
||||
import CreationGrid from './components/CreationGrid.vue'
|
||||
import { getHotInspirationFlowBatchApi } from '@/utils/api.js'
|
||||
import { clearSubStepProgress, shouldShowGuideStartModal } from '@/utils/guideConfig.js'
|
||||
// import { clearSubStepProgress, shouldShowGuideStartModal } from '@/utils/guideConfig.js'
|
||||
import { useBanner } from './composables/useBanner.js'
|
||||
import { doubleTapLike } from '@/utils/likeHelper.js'
|
||||
import { USE_MOCK_DATA } from './config/mockData.js'
|
||||
// import { USE_MOCK_DATA } from './config/mockData.js'
|
||||
|
||||
// ========== Store & User Info ==========
|
||||
const store = useStore()
|
||||
const currentUserNickname = computed(() => store.state.user?.userInfo?.nickname || '')
|
||||
// const currentUserNickname = computed(() => store.state.user?.userInfo?.nickname || '')
|
||||
const currentStarId = ref(uni.getStorageSync('star_id') || null)
|
||||
|
||||
// ========== UI State ==========
|
||||
@ -289,30 +289,30 @@ onHide(() => {
|
||||
isActive.value = false
|
||||
})
|
||||
|
||||
onLoad((options) => {
|
||||
if (options && 'guide_debug' in options) {
|
||||
const debugValue = options.guide_debug
|
||||
const isDebug = debugValue === '1' || debugValue === 'true'
|
||||
if (isDebug) {
|
||||
uni.setStorageSync('guide_debug_mode', true)
|
||||
uni.setStorageSync('is_new_user', true)
|
||||
console.log('[Guide] 调试模式已开启')
|
||||
} else {
|
||||
uni.setStorageSync('guide_debug_mode', false)
|
||||
uni.removeStorageSync('is_new_user')
|
||||
console.log('[Guide] 调试模式已关闭')
|
||||
}
|
||||
}
|
||||
// onLoad((options) => {
|
||||
// if (options && 'guide_debug' in options) {
|
||||
// const debugValue = options.guide_debug
|
||||
// const isDebug = debugValue === '1' || debugValue === 'true'
|
||||
// if (isDebug) {
|
||||
// uni.setStorageSync('guide_debug_mode', true)
|
||||
// uni.setStorageSync('is_new_user', true)
|
||||
// console.log('[Guide] 调试模式已开启')
|
||||
// } else {
|
||||
// uni.setStorageSync('guide_debug_mode', false)
|
||||
// uni.removeStorageSync('is_new_user')
|
||||
// console.log('[Guide] 调试模式已关闭')
|
||||
// }
|
||||
// }
|
||||
|
||||
if (options && options.guide_key) {
|
||||
console.log('[Guide] 收到引导跳转参数, guide_key:', options.guide_key, 'guide_step:', options.guide_step)
|
||||
store.dispatch('guide/resumeGuide', options.guide_key).then(res => {
|
||||
console.log('[Guide] resumeGuide 结果:', res)
|
||||
}).catch(err => {
|
||||
console.error('[Guide] resumeGuide 失败:', err)
|
||||
})
|
||||
}
|
||||
})
|
||||
// if (options && options.guide_key) {
|
||||
// console.log('[Guide] 收到引导跳转参数, guide_key:', options.guide_key, 'guide_step:', options.guide_step)
|
||||
// store.dispatch('guide/resumeGuide', options.guide_key).then(res => {
|
||||
// console.log('[Guide] resumeGuide 结果:', res)
|
||||
// }).catch(err => {
|
||||
// console.error('[Guide] resumeGuide 失败:', err)
|
||||
// })
|
||||
// }
|
||||
// })
|
||||
|
||||
// 监听引导打开组件事件
|
||||
uni.$on('guide:openComponent', (componentName) => {
|
||||
@ -350,7 +350,7 @@ onUnmounted(() => {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: calc(100vh - 64rpx);
|
||||
height: 100vh;
|
||||
/* margin-top: 160rpx; */
|
||||
padding: 240rpx 24rpx 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 197 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 47 KiB |
BIN
图标1 (1).png
|
Before Width: | Height: | Size: 13 KiB |