|
@@ -0,0 +1,826 @@
|
|
|
|
|
+# 登录前浏览体验 Implementation Plan
|
|
|
|
|
+
|
|
|
|
|
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
|
|
|
+
|
|
|
|
|
+**Goal:** 未登录用户打开小程序即可浏览发现页(五行沙盘+活动+商品)和商城,无需看到登录页
|
|
|
|
|
+
|
|
|
|
|
+**Architecture:** 未登录时隐藏原生 TabBar,使用自定义底部导航(发现/商城);发现页为静态 HTML 内容(零后端依赖)。登录后恢复原生 4Tab。
|
|
|
|
|
+
|
|
|
|
|
+**Tech Stack:** uni-app Vue 2 (微信小程序)
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### Task 1: 品牌更名 溪艾福→汐艾福
|
|
|
|
|
+
|
|
|
|
|
+**Files:**
|
|
|
|
|
+- Modify: `zxyj-frontend/pages/index/parent-index.vue` (4处)
|
|
|
|
|
+- Modify: `zxyj-frontend/pages/index/child-index.vue` (2处)
|
|
|
|
|
+- Modify: `zxyj-frontend/pages.json` (1处)
|
|
|
|
|
+- Modify: `zxyj-frontend/pages/profile/profile.vue` (1处)
|
|
|
|
|
+- Modify: `zxyj-frontend/utils/api.js` (1处)
|
|
|
|
|
+- Modify: `zxyj-backend/src/main/java/com/zxyj/config/DatabaseInitializer.java` (4处)
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 替换所有前端「溪艾福」→「汐艾福」**
|
|
|
|
|
+
|
|
|
|
|
+Replace in parent-index.vue:
|
|
|
|
|
+- Line 68: 五行能量沙盘(溪艾福)→ 五行能量沙盘(汐艾福)
|
|
|
|
|
+- Line 125: 会员Banner(溪福俱乐部)→ 会员Banner(汐福俱乐部)
|
|
|
|
|
+- Line 1213: 溪福俱乐部 → 汐福俱乐部
|
|
|
|
|
+- Line 1249: 五行能量沙盘(溪艾福)→ 五行能量沙盘(汐艾福)
|
|
|
|
|
+
|
|
|
|
|
+Replace in child-index.vue:
|
|
|
|
|
+- Line 54: 五行能量沙盘(溪艾福)→ 五行能量沙盘(汐艾福)
|
|
|
|
|
+- Line 881: 五行能量沙盘(溪艾福)→ 五行能量沙盘(汐艾福)
|
|
|
|
|
+
|
|
|
|
|
+Replace in pages.json:
|
|
|
|
|
+- Line 329: 溪福俱乐部 → 汐福俱乐部
|
|
|
|
|
+
|
|
|
|
|
+Replace in profile.vue:
|
|
|
|
|
+- Line 52: 溪福俱乐部 → 汐福俱乐部
|
|
|
|
|
+
|
|
|
|
|
+Replace in api.js:
|
|
|
|
|
+- Line 722: 溪福俱乐部 → 汐福俱乐部
|
|
|
|
|
+
|
|
|
|
|
+Replace in DatabaseInitializer.java (comments only):
|
|
|
|
|
+- "溪福俱乐部" → "汐福俱乐部" (4 occurrences)
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: Commit**
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+git add $(rtk list -u --modified -- zxyj-frontend/ zxyj-backend/)
|
|
|
|
|
+git commit -m "chore: 品牌名溪艾福/溪福俱乐部统一改为汐艾福/汐福俱乐部"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### Task 2: 提取五行沙盘为独立组件
|
|
|
|
|
+
|
|
|
|
|
+**Files:**
|
|
|
|
|
+- Create: `zxyj-frontend/components/wuxing-sandbox.vue`
|
|
|
|
|
+- Modify: `zxyj-frontend/pages/index/parent-index.vue`
|
|
|
|
|
+- Modify: `zxyj-frontend/pages/index/child-index.vue`
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 创建 wuxing-sandbox.vue 组件**
|
|
|
|
|
+
|
|
|
|
|
+```vue
|
|
|
|
|
+<template>
|
|
|
|
|
+ <view class="wuxing-sandbox">
|
|
|
|
|
+ <view class="sandbox-header">
|
|
|
|
|
+ <text class="sandbox-title">五行能量沙盘</text>
|
|
|
|
|
+ <text class="sandbox-badge">{{ badgeText }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="sandbox-star">
|
|
|
|
|
+ <view class="star-point star-point-top" @click="onPointClick('mind')">
|
|
|
|
|
+ <text class="point-icon">🔥</text>
|
|
|
|
|
+ <text class="point-label">心·火</text>
|
|
|
|
|
+ <text class="point-status" v-if="mode === 'preview'">点此登录</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="star-point star-point-left" @click="onPointClick('action')">
|
|
|
|
|
+ <text class="point-icon">🌿</text>
|
|
|
|
|
+ <text class="point-label">行·木</text>
|
|
|
|
|
+ <text class="point-status" v-if="mode === 'preview'">点此登录</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="star-point star-point-right" @click="onPointClick('wealth')">
|
|
|
|
|
+ <text class="point-icon">💧</text>
|
|
|
|
|
+ <text class="point-label">富·水</text>
|
|
|
|
|
+ <text class="point-status" v-if="mode === 'preview'">点此登录</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="star-point star-point-bl" @click="onPointClick('wisdom')">
|
|
|
|
|
+ <text class="point-icon">⚔️</text>
|
|
|
|
|
+ <text class="point-label">智·金</text>
|
|
|
|
|
+ <text class="point-status" v-if="mode === 'preview'">点此登录</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="star-point star-point-br" @click="onPointClick('body')">
|
|
|
|
|
+ <text class="point-icon">🌏</text>
|
|
|
|
|
+ <text class="point-label">身·土</text>
|
|
|
|
|
+ <text class="point-status" v-if="mode === 'preview'">点此登录</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="star-center">
|
|
|
|
|
+ <text class="center-text">五行平衡</text>
|
|
|
|
|
+ <text class="center-sub">共同成长</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="shenke-hint" v-if="mode === 'preview'">
|
|
|
|
|
+ <text class="hint-text">🌱 完成测评,点亮「身·土」</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="dimension-bar" v-if="mode !== 'preview'">
|
|
|
|
|
+ <view class="dim-item inactive">🌿行</view>
|
|
|
|
|
+ <text class="dim-arrow">→</text>
|
|
|
|
|
+ <view class="dim-item inactive">🔥心</view>
|
|
|
|
|
+ <text class="dim-arrow">→</text>
|
|
|
|
|
+ <view class="dim-item inactive">🌏身</view>
|
|
|
|
|
+ <text class="dim-arrow">→</text>
|
|
|
|
|
+ <view class="dim-item inactive">⚔️智</view>
|
|
|
|
|
+ <text class="dim-arrow">→</text>
|
|
|
|
|
+ <view class="dim-item inactive">💧富</view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script>
|
|
|
|
|
+export default {
|
|
|
|
|
+ props: {
|
|
|
|
|
+ mode: { type: String, default: 'preview' }, // preview | parent | child
|
|
|
|
|
+ badgeText: { type: String, default: '综合成长力 0%' }
|
|
|
|
|
+ },
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ onPointClick(domain) {
|
|
|
|
|
+ this.$emit('point-click', domain)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.wuxing-sandbox {
|
|
|
|
|
+ margin: 20rpx 30rpx;
|
|
|
|
|
+ padding: 30rpx;
|
|
|
|
|
+ background: linear-gradient(135deg, #1a1a2e, #16213e);
|
|
|
|
|
+ border-radius: 24rpx;
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+}
|
|
|
|
|
+.sandbox-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ margin-bottom: 30rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.sandbox-title {
|
|
|
|
|
+ font-size: 28rpx;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+ color: #FFD700;
|
|
|
|
|
+}
|
|
|
|
|
+.sandbox-badge {
|
|
|
|
|
+ font-size: 22rpx;
|
|
|
|
|
+ color: #aaa;
|
|
|
|
|
+ background: rgba(255,255,255,0.1);
|
|
|
|
|
+ padding: 6rpx 16rpx;
|
|
|
|
|
+ border-radius: 20rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.sandbox-star {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ height: 400rpx;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+}
|
|
|
|
|
+.star-point {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ width: 120rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.star-point-top { top: 0; left: 50%; transform: translateX(-50%); }
|
|
|
|
|
+.star-point-left { top: 120rpx; left: 20rpx; }
|
|
|
|
|
+.star-point-right { top: 120rpx; right: 20rpx; }
|
|
|
|
|
+.star-point-bl { bottom: 40rpx; left: 60rpx; }
|
|
|
|
|
+.star-point-br { bottom: 40rpx; right: 60rpx; }
|
|
|
|
|
+.point-icon { font-size: 50rpx; margin-bottom: 6rpx; }
|
|
|
|
|
+.point-label { font-size: 22rpx; color: #fff; font-weight: bold; }
|
|
|
|
|
+.point-status { font-size: 18rpx; color: #FF6B6B; margin-top: 4rpx; }
|
|
|
|
|
+.star-center {
|
|
|
|
|
+ width: 120rpx; height: 120rpx;
|
|
|
|
|
+ background: radial-gradient(circle, rgba(255,215,0,0.2), rgba(255,215,0,0.05));
|
|
|
|
|
+ border: 2rpx solid rgba(255,215,0,0.3);
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+}
|
|
|
|
|
+.center-text { font-size: 22rpx; color: #FFD700; font-weight: bold; }
|
|
|
|
|
+.center-sub { font-size: 18rpx; color: #aaa; }
|
|
|
|
|
+.shenke-hint { text-align: center; margin-top: 10rpx; }
|
|
|
|
|
+.hint-text { font-size: 22rpx; color: rgba(255,215,0,0.7); }
|
|
|
|
|
+.dimension-bar {
|
|
|
|
|
+ display: flex; align-items: center; justify-content: center;
|
|
|
|
|
+ margin-top: 20rpx; gap: 8rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.dim-item {
|
|
|
|
|
+ font-size: 22rpx; color: #FFD700;
|
|
|
|
|
+ background: rgba(255,215,0,0.1);
|
|
|
|
|
+ padding: 6rpx 14rpx; border-radius: 12rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.dim-arrow { font-size: 20rpx; color: rgba(255,215,0,0.3); }
|
|
|
|
|
+</style>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: 替换 parent-index.vue 内联沙盘为组件**
|
|
|
|
|
+
|
|
|
|
|
+Remove the inline wuxing-sandbox HTML+CSS block from parent-index.vue (the entire block from `<!-- 五行能量沙盘(汐艾福) -->` to its closing `</view>`), replace with:
|
|
|
|
|
+
|
|
|
|
|
+```vue
|
|
|
|
|
+ <wuxing-sandbox mode="parent" badgeText="综合成长力 0%" @point-click="goToDomain" />
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+Add import:
|
|
|
|
|
+```javascript
|
|
|
|
|
+import WuxingSandbox from '../../components/wuxing-sandbox.vue'
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+Add to components:
|
|
|
|
|
+```javascript
|
|
|
|
|
+components: { WuxingSandbox }
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 3: 替换 child-index.vue 内联沙盘为组件**
|
|
|
|
|
+
|
|
|
|
|
+Same pattern as Step 2 — replace inline sandbox with `<wuxing-sandbox mode="child" badgeText="综合 0%" @point-click="goToDomain" />`
|
|
|
|
|
+
|
|
|
|
|
+Add import + components registration.
|
|
|
|
|
+
|
|
|
|
|
+Remove the entire inline `<style>` block for `.wuxing-sandbox` CSS from both parent-index.vue and child-index.vue (since it now lives in the component).
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 4: Commit**
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+git add zxyj-frontend/components/wuxing-sandbox.vue zxyj-frontend/pages/index/parent-index.vue zxyj-frontend/pages/index/child-index.vue
|
|
|
|
|
+git commit -m "refactor: 提取五行沙盘为独立组件(wuxing-sandbox.vue)支持三种模式"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### Task 3: 创建自定义底部导航组件
|
|
|
|
|
+
|
|
|
|
|
+**Files:**
|
|
|
|
|
+- Create: `zxyj-frontend/components/bottom-nav.vue`
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 创建 bottom-nav.vue 组件**
|
|
|
|
|
+
|
|
|
|
|
+```vue
|
|
|
|
|
+<template>
|
|
|
|
|
+ <view class="bottom-nav">
|
|
|
|
|
+ <view
|
|
|
|
|
+ class="nav-item"
|
|
|
|
|
+ :class="{ active: current === 'discover' }"
|
|
|
|
|
+ @click="switchTab('discover')"
|
|
|
|
|
+ >
|
|
|
|
|
+ <text class="nav-icon">🔍</text>
|
|
|
|
|
+ <text class="nav-text">发现</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view
|
|
|
|
|
+ class="nav-item"
|
|
|
|
|
+ :class="{ active: current === 'shop' }"
|
|
|
|
|
+ @click="switchTab('shop')"
|
|
|
|
|
+ >
|
|
|
|
|
+ <text class="nav-icon">🛒</text>
|
|
|
|
|
+ <text class="nav-text">商城</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script>
|
|
|
|
|
+export default {
|
|
|
|
|
+ props: {
|
|
|
|
|
+ current: { type: String, default: 'discover' }
|
|
|
|
|
+ },
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ switchTab(tab) {
|
|
|
|
|
+ if (tab === this.current) return
|
|
|
|
|
+ if (tab === 'discover') {
|
|
|
|
|
+ uni.redirectTo({ url: '/pages/discover/index' })
|
|
|
|
|
+ } else if (tab === 'shop') {
|
|
|
|
|
+ uni.redirectTo({ url: '/pages/shop/index' })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.bottom-nav {
|
|
|
|
|
+ position: fixed;
|
|
|
|
|
+ bottom: 0;
|
|
|
|
|
+ left: 0;
|
|
|
|
|
+ right: 0;
|
|
|
|
|
+ height: 100rpx;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border-top: 1rpx solid #eee;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-around;
|
|
|
|
|
+ padding-bottom: env(safe-area-inset-bottom);
|
|
|
|
|
+ z-index: 100;
|
|
|
|
|
+}
|
|
|
|
|
+.nav-item {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+}
|
|
|
|
|
+.nav-item.active .nav-text { color: #FF6B6B; }
|
|
|
|
|
+.nav-icon { font-size: 40rpx; }
|
|
|
|
|
+.nav-text { font-size: 20rpx; color: #999; margin-top: 4rpx; }
|
|
|
|
|
+.nav-item.active .nav-text { font-weight: bold; }
|
|
|
|
|
+</style>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: Commit**
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+git add zxyj-frontend/components/bottom-nav.vue
|
|
|
|
|
+git commit -m "feat: 创建自定义底部导航组件(bottom-nav)用于未登录态2Tab展示"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### Task 4: 重构 App.vue 登录态 + TabBar 切换逻辑
|
|
|
|
|
+
|
|
|
|
|
+**Files:**
|
|
|
|
|
+- Modify: `zxyj-frontend/App.vue`
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 修改 App.vue onLaunch**
|
|
|
|
|
+
|
|
|
|
|
+Add login state check: if no token → hide native TabBar, redirect to discover. If has token → show native TabBar, proceed normally.
|
|
|
|
|
+
|
|
|
|
|
+```vue
|
|
|
|
|
+<script>
|
|
|
|
|
+export default {
|
|
|
|
|
+ onLaunch: function() {
|
|
|
|
|
+ console.log('App Launch')
|
|
|
|
|
+ const token = uni.getStorageSync('token')
|
|
|
|
|
+ if (token) {
|
|
|
|
|
+ this.$store.commit('setToken', token)
|
|
|
|
|
+ // 已登录:确保原生 TabBar 显示
|
|
|
|
|
+ uni.showTabBar()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 未登录:隐藏原生 TabBar,跳转到发现页
|
|
|
|
|
+ uni.hideTabBar()
|
|
|
|
|
+ // 如果当前不在发现页或商城页,则跳转
|
|
|
|
|
+ const pages = getCurrentPages()
|
|
|
|
|
+ const currentRoute = pages.length > 0 ? pages[pages.length - 1].route : ''
|
|
|
|
|
+ if (currentRoute !== 'pages/discover/index' && currentRoute !== 'pages/shop/index') {
|
|
|
|
|
+ uni.reLaunch({ url: '/pages/discover/index' })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ onShow: function() {
|
|
|
|
|
+ // 每次显示时检查登录状态,同步 TabBar
|
|
|
|
|
+ const token = uni.getStorageSync('token')
|
|
|
|
|
+ if (token) {
|
|
|
|
|
+ uni.showTabBar()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ uni.hideTabBar()
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ // ... rest of existing methods (tryAutoLogin, saveLoginInfo, request)
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: 确保 login.vue 登录成功后隐藏原生 TabBar + 跳转发现页**
|
|
|
|
|
+
|
|
|
|
|
+The existing login success handler does `uni.reLaunch({ url: '/pages/index/index' })`. This should be changed to NOT re-show the native TabBar for unauthenticated users — but since login succeeded, they ARE authenticated, so showing the TabBar is correct.
|
|
|
|
|
+
|
|
|
|
|
+Actually, the flow should be:
|
|
|
|
|
+1. User logs in successfully
|
|
|
|
|
+2. `uni.showTabBar()` → restore native TabBar
|
|
|
|
|
+3. `uni.reLaunch({ url: '/pages/index/index' })` → go to home page (which now has TabBar)
|
|
|
|
|
+
|
|
|
|
|
+This should already work with the App.vue changes. No need to modify login.vue TabBar logic — just ensure the navigateToHome function works.
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 3: Commit**
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+git add zxyj-frontend/App.vue
|
|
|
|
|
+git commit -m "feat: App.vue 添加登录态TabBar切换逻辑(未登录隐藏原生TabBar)"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### Task 5: 重构发现页(核心页面)
|
|
|
|
|
+
|
|
|
|
|
+**Files:**
|
|
|
|
|
+- Modify: `zxyj-frontend/pages/discover/index.vue`
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 完整重写 discover/index.vue**
|
|
|
|
|
+
|
|
|
|
|
+```vue
|
|
|
|
|
+<template>
|
|
|
|
|
+ <view class="discover-page">
|
|
|
|
|
+ <!-- Banner -->
|
|
|
|
|
+ <view class="banner">
|
|
|
|
|
+ <view class="banner-content">
|
|
|
|
|
+ <text class="banner-brand">🌟 汐艾福</text>
|
|
|
|
|
+ <text class="banner-title">Care Family Club</text>
|
|
|
|
|
+ <text class="banner-subtitle">以五行五维为内核的家庭主动健康管理平台</text>
|
|
|
|
|
+ <view class="banner-tags">
|
|
|
|
|
+ <text class="tag">🏆 任务积分</text>
|
|
|
|
|
+ <text class="tag">⭐ 五行沙盘</text>
|
|
|
|
|
+ <text class="tag">👨👩👧 家庭共育</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 五行能量沙盘 -->
|
|
|
|
|
+ <wuxing-sandbox mode="preview" @point-click="requireLogin" />
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 精选活动 -->
|
|
|
|
|
+ <view class="section">
|
|
|
|
|
+ <view class="section-header">
|
|
|
|
|
+ <text class="section-title">🎯 精选活动</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <scroll-view class="activity-scroll" scroll-x>
|
|
|
|
|
+ <view
|
|
|
|
|
+ class="activity-card"
|
|
|
|
|
+ v-for="(item, idx) in activities"
|
|
|
|
|
+ :key="idx"
|
|
|
|
|
+ @click="requireLogin('activity')"
|
|
|
|
|
+ >
|
|
|
|
|
+ <view class="activity-icon">{{ item.icon }}</view>
|
|
|
|
|
+ <text class="activity-name">{{ item.name }}</text>
|
|
|
|
|
+ <text class="activity-desc">{{ item.desc }}</text>
|
|
|
|
|
+ <text class="activity-price">{{ item.price }}</text>
|
|
|
|
|
+ <view class="activity-btn">查看详情 →</view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </scroll-view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 推荐商品 -->
|
|
|
|
|
+ <view class="section">
|
|
|
|
|
+ <view class="section-header">
|
|
|
|
|
+ <text class="section-title">🛍️ 推荐商品</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view
|
|
|
|
|
+ class="product-card"
|
|
|
|
|
+ v-for="(item, idx) in products"
|
|
|
|
|
+ :key="idx"
|
|
|
|
|
+ @click="requireLogin('product')"
|
|
|
|
|
+ >
|
|
|
|
|
+ <view class="product-icon">{{ item.icon }}</view>
|
|
|
|
|
+ <view class="product-info">
|
|
|
|
|
+ <text class="product-name">{{ item.name }}</text>
|
|
|
|
|
+ <view class="product-meta">
|
|
|
|
|
+ <text class="product-rating">{{ item.rating }}</text>
|
|
|
|
|
+ <text class="product-sold">{{ item.sold }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <text class="product-desc">{{ item.desc }}</text>
|
|
|
|
|
+ <text class="product-price">{{ item.price }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 登录引导 -->
|
|
|
|
|
+ <view class="login-cta" @click="requireLogin('cta')">
|
|
|
|
|
+ <text class="cta-title">🔒 登录解锁完整功能</text>
|
|
|
|
|
+ <text class="cta-sub">创建家庭 · 管理任务 · 查看五行能量沙盘</text>
|
|
|
|
|
+ <view class="cta-btn">立即登录 →</view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 自定义底部导航 -->
|
|
|
|
|
+ <bottom-nav current="discover" />
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 底部占位 -->
|
|
|
|
|
+ <view style="height: 120rpx;"></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script>
|
|
|
|
|
+import WuxingSandbox from '../../components/wuxing-sandbox.vue'
|
|
|
|
|
+import BottomNav from '../../components/bottom-nav.vue'
|
|
|
|
|
+
|
|
|
|
|
+export default {
|
|
|
|
|
+ components: { WuxingSandbox, BottomNav },
|
|
|
|
|
+ data() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ activities: [
|
|
|
|
|
+ { icon: '🧘', name: '亲子瑜伽体验课', desc: '适合5-12岁儿童与家长', price: '¥199', tag: '🔥 心·火' },
|
|
|
|
|
+ { icon: '💬', name: '情绪管理工作坊', desc: '帮助孩子识别和管理情绪', price: '免费', tag: '🔥 心·火' },
|
|
|
|
|
+ { icon: '🦠', name: '肠道菌群调养讲座', desc: '从肠道健康到大脑发育', price: '¥299', tag: '🌏 身·土' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ products: [
|
|
|
|
|
+ { icon: '🧘', name: '亲子瑜伽体验课', desc: '适合5-12岁儿童与家长共同参与的瑜伽课程', rating: '★★★★☆', sold: '已售230份', price: '¥199' },
|
|
|
|
|
+ { icon: '🎮', name: '舒尔特方格专注力训练', desc: '经典注意力训练游戏,每日5分钟', rating: '★★★★★', sold: '已售568份', price: '¥49' }
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ requireLogin(action) {
|
|
|
|
|
+ const token = uni.getStorageSync('token')
|
|
|
|
|
+ if (token) return
|
|
|
|
|
+ uni.navigateTo({ url: '/pages/login/login?redirect=' + encodeURIComponent('/pages/discover/index') })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.discover-page {
|
|
|
|
|
+ min-height: 100vh;
|
|
|
|
|
+ background: #f8f8f8;
|
|
|
|
|
+ padding-bottom: 20rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* Banner */
|
|
|
|
|
+.banner {
|
|
|
|
|
+ margin: 20rpx 30rpx;
|
|
|
|
|
+ padding: 40rpx 30rpx;
|
|
|
|
|
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
|
|
+ border-radius: 24rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.banner-content { display: flex; flex-direction: column; }
|
|
|
|
|
+.banner-brand { font-size: 36rpx; font-weight: bold; color: #fff; }
|
|
|
|
|
+.banner-title { font-size: 26rpx; color: rgba(255,255,255,0.8); margin-top: 4rpx; }
|
|
|
|
|
+.banner-subtitle { font-size: 24rpx; color: rgba(255,255,255,0.7); margin-top: 12rpx; line-height: 1.5; }
|
|
|
|
|
+.banner-tags { display: flex; gap: 12rpx; margin-top: 20rpx; flex-wrap: wrap; }
|
|
|
|
|
+.tag { background: rgba(255,255,255,0.2); padding: 6rpx 16rpx; border-radius: 20rpx; font-size: 22rpx; color: #fff; }
|
|
|
|
|
+
|
|
|
|
|
+/* Sections */
|
|
|
|
|
+.section { margin: 20rpx 30rpx; }
|
|
|
|
|
+.section-header { margin-bottom: 16rpx; }
|
|
|
|
|
+.section-title { font-size: 28rpx; font-weight: bold; color: #333; }
|
|
|
|
|
+
|
|
|
|
|
+/* Activity scroll */
|
|
|
|
|
+.activity-scroll { display: flex; flex-direction: row; white-space: nowrap; overflow-x: auto; }
|
|
|
|
|
+.activity-card {
|
|
|
|
|
+ display: inline-flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ width: 240rpx;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border-radius: 16rpx;
|
|
|
|
|
+ padding: 24rpx 20rpx;
|
|
|
|
|
+ margin-right: 20rpx;
|
|
|
|
|
+ box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
|
|
|
|
|
+}
|
|
|
|
|
+.activity-icon { font-size: 60rpx; margin-bottom: 12rpx; }
|
|
|
|
|
+.activity-name { font-size: 26rpx; font-weight: bold; color: #333; white-space: normal; }
|
|
|
|
|
+.activity-desc { font-size: 22rpx; color: #999; margin-top: 8rpx; white-space: normal; }
|
|
|
|
|
+.activity-price { font-size: 28rpx; color: #FF6B6B; font-weight: bold; margin-top: 12rpx; }
|
|
|
|
|
+.activity-btn {
|
|
|
|
|
+ margin-top: 12rpx;
|
|
|
|
|
+ padding: 8rpx 0;
|
|
|
|
|
+ background: #667eea;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ border-radius: 16rpx;
|
|
|
|
|
+ font-size: 22rpx;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* Product cards */
|
|
|
|
|
+.product-card {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border-radius: 16rpx;
|
|
|
|
|
+ padding: 24rpx;
|
|
|
|
|
+ margin-bottom: 16rpx;
|
|
|
|
|
+ box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
|
|
|
|
|
+}
|
|
|
|
|
+.product-icon { font-size: 72rpx; margin-right: 20rpx; }
|
|
|
|
|
+.product-info { flex: 1; }
|
|
|
|
|
+.product-name { font-size: 28rpx; font-weight: bold; color: #333; display: block; }
|
|
|
|
|
+.product-meta { display: flex; gap: 16rpx; margin-top: 6rpx; }
|
|
|
|
|
+.product-rating { font-size: 22rpx; color: #FFA500; }
|
|
|
|
|
+.product-sold { font-size: 22rpx; color: #999; }
|
|
|
|
|
+.product-desc { font-size: 22rpx; color: #888; margin-top: 8rpx; display: block; }
|
|
|
|
|
+.product-price { font-size: 32rpx; color: #FF6B6B; font-weight: bold; margin-top: 8rpx; display: block; }
|
|
|
|
|
+
|
|
|
|
|
+/* Login CTA */
|
|
|
|
|
+.login-cta {
|
|
|
|
|
+ margin: 20rpx 30rpx;
|
|
|
|
|
+ padding: 32rpx;
|
|
|
|
|
+ background: linear-gradient(135deg, #667eea, #764ba2);
|
|
|
|
|
+ border-radius: 20rpx;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+}
|
|
|
|
|
+.cta-title { font-size: 28rpx; font-weight: bold; color: #fff; }
|
|
|
|
|
+.cta-sub { font-size: 22rpx; color: rgba(255,255,255,0.8); margin-top: 8rpx; }
|
|
|
|
|
+.cta-btn {
|
|
|
|
|
+ margin-top: 16rpx;
|
|
|
|
|
+ padding: 12rpx 48rpx;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ color: #667eea;
|
|
|
|
|
+ border-radius: 30rpx;
|
|
|
|
|
+ font-size: 26rpx;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: Commit**
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+git add zxyj-frontend/pages/discover/index.vue
|
|
|
|
|
+git commit -m "feat: 重构发现页为预登录首页(沙盘+活动+商品+登录引导)"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### Task 6: 商城页添加未登录态预览
|
|
|
|
|
+
|
|
|
|
|
+**Files:**
|
|
|
|
|
+- Modify: `zxyj-frontend/pages/shop/index.vue`
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 重写 shop/index.vue**
|
|
|
|
|
+
|
|
|
|
|
+Replace placeholder with a preview layout showing product categories + product cards, plus bottom nav and login gate.
|
|
|
|
|
+
|
|
|
|
|
+```vue
|
|
|
|
|
+<template>
|
|
|
|
|
+ <view class="shop-page">
|
|
|
|
|
+ <!-- 分类标签 -->
|
|
|
|
|
+ <view class="category-bar">
|
|
|
|
|
+ <view class="category-item active">全部</view>
|
|
|
|
|
+ <view class="category-item">课程</view>
|
|
|
|
|
+ <view class="category-item">活动</view>
|
|
|
|
|
+ <view class="category-item">商品</view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 商品列表(静态预览) -->
|
|
|
|
|
+ <view class="product-grid">
|
|
|
|
|
+ <view class="product-card" v-for="(item, idx) in products" :key="idx" @click="requireLogin">
|
|
|
|
|
+ <view class="product-img">{{ item.icon }}</view>
|
|
|
|
|
+ <view class="product-body">
|
|
|
|
|
+ <text class="product-name">{{ item.name }}</text>
|
|
|
|
|
+ <text class="product-price">{{ item.price }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 登录引导 -->
|
|
|
|
|
+ <view class="login-hint">
|
|
|
|
|
+ <text class="hint-text">🔒 登录查看全部商品和服务</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 自定义底部导航 -->
|
|
|
|
|
+ <bottom-nav current="shop" />
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 底部占位 -->
|
|
|
|
|
+ <view style="height: 120rpx;"></view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script>
|
|
|
|
|
+import BottomNav from '../../components/bottom-nav.vue'
|
|
|
|
|
+
|
|
|
|
|
+export default {
|
|
|
|
|
+ components: { BottomNav },
|
|
|
|
|
+ data() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ products: [
|
|
|
|
|
+ { icon: '🧘', name: '亲子瑜伽体验课', price: '¥199' },
|
|
|
|
|
+ { icon: '🎮', name: '舒尔特方格专注力训练', price: '¥49' },
|
|
|
|
|
+ { icon: '📖', name: '儿童情绪管理绘本套装', price: '¥89' },
|
|
|
|
|
+ { icon: '🧩', name: '家庭专注力训练礼盒', price: '¥159' }
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ methods: {
|
|
|
|
|
+ requireLogin() {
|
|
|
|
|
+ const token = uni.getStorageSync('token')
|
|
|
|
|
+ if (token) return
|
|
|
|
|
+ uni.navigateTo({ url: '/pages/login/login?redirect=' + encodeURIComponent('/pages/shop/index') })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.shop-page {
|
|
|
|
|
+ min-height: 100vh;
|
|
|
|
|
+ background: #f8f8f8;
|
|
|
|
|
+ padding-bottom: 20rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.category-bar {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 16rpx;
|
|
|
|
|
+ padding: 20rpx 30rpx;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border-bottom: 1rpx solid #f0f0f0;
|
|
|
|
|
+}
|
|
|
|
|
+.category-item {
|
|
|
|
|
+ padding: 8rpx 24rpx;
|
|
|
|
|
+ border-radius: 20rpx;
|
|
|
|
|
+ font-size: 24rpx;
|
|
|
|
|
+ color: #666;
|
|
|
|
|
+ background: #f5f5f5;
|
|
|
|
|
+}
|
|
|
|
|
+.category-item.active {
|
|
|
|
|
+ background: #667eea;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+}
|
|
|
|
|
+.product-grid {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: 1fr 1fr;
|
|
|
|
|
+ gap: 16rpx;
|
|
|
|
|
+ padding: 20rpx 30rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.product-card {
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border-radius: 16rpx;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
|
|
|
|
|
+}
|
|
|
|
|
+.product-img {
|
|
|
|
|
+ height: 180rpx;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ font-size: 80rpx;
|
|
|
|
|
+ background: #f9f9f9;
|
|
|
|
|
+}
|
|
|
|
|
+.product-body { padding: 16rpx; }
|
|
|
|
|
+.product-name { font-size: 24rpx; font-weight: bold; color: #333; display: block; }
|
|
|
|
|
+.product-price { font-size: 28rpx; color: #FF6B6B; font-weight: bold; margin-top: 8rpx; display: block; }
|
|
|
|
|
+.login-hint {
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ padding: 24rpx;
|
|
|
|
|
+ font-size: 24rpx;
|
|
|
|
|
+ color: #999;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: Commit**
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+git add zxyj-frontend/pages/shop/index.vue
|
|
|
|
|
+git commit -m "feat: 商城页添加未登录预览态(静态商品展示+登录引导)"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### Task 7: Login 页添加 redirect 参数支持
|
|
|
|
|
+
|
|
|
|
|
+**Files:**
|
|
|
|
|
+- Modify: `zxyj-frontend/pages/login/login.vue`
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 1: 修改 login.vue onLoad 接受 redirect 参数**
|
|
|
|
|
+
|
|
|
|
|
+```vue
|
|
|
|
|
+ onLoad(options) {
|
|
|
|
|
+ // 处理邀请码
|
|
|
|
|
+ if (options.inviteCode) {
|
|
|
|
|
+ uni.setStorageSync('inviteCode', options.inviteCode)
|
|
|
|
|
+ uni.setStorageSync('inviteType', options.inviteType || 'family')
|
|
|
|
|
+ }
|
|
|
|
|
+ // 存储登录后跳转地址
|
|
|
|
|
+ if (options.redirect) {
|
|
|
|
|
+ this.redirectUrl = decodeURIComponent(options.redirect)
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+Add to data:
|
|
|
|
|
+```javascript
|
|
|
|
|
+ data() {
|
|
|
|
|
+ return {
|
|
|
|
|
+ // ... existing data
|
|
|
|
|
+ redirectUrl: ''
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 2: 修改 navigateToHome 使用 redirect 参数**
|
|
|
|
|
+
|
|
|
|
|
+Replace the existing navigateToHome method:
|
|
|
|
|
+
|
|
|
|
|
+```javascript
|
|
|
|
|
+ navigateToHome(role) {
|
|
|
|
|
+ if (this.redirectUrl) {
|
|
|
|
|
+ // 有 redirect 则跳回来源页
|
|
|
|
|
+ const redirect = this.redirectUrl
|
|
|
|
|
+ this.redirectUrl = ''
|
|
|
|
|
+ uni.reLaunch({ url: redirect })
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 默认通过 TabBar 首页跳转
|
|
|
|
|
+ uni.reLaunch({ url: '/pages/index/index' })
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+- [ ] **Step 3: Commit**
|
|
|
|
|
+
|
|
|
|
|
+```bash
|
|
|
|
|
+git add zxyj-frontend/pages/login/login.vue
|
|
|
|
|
+git commit -m "feat: 登录页支持redirect参数(登录后返回来源页)"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+### Self-Review
|
|
|
|
|
+
|
|
|
|
|
+**1. Spec coverage:**
|
|
|
|
|
+- 品牌更名: ✅ Task 1 覆盖所有 9 处前端 + 4 处后端注释
|
|
|
|
|
+- 发现页重构 (沙盘+活动+商品): ✅ Task 5
|
|
|
|
|
+- 五行沙盘交互点(五角→登录): ✅ Task 2 (emits point-click), Task 5 (requireLogin handler)
|
|
|
|
|
+- 活动区: ✅ Task 5 (3张活动卡片,点击→requireLogin)
|
|
|
|
|
+- 商品区: ✅ Task 5 (2张商品卡片,点击→requireLogin)
|
|
|
|
|
+- 商城页未登录预览: ✅ Task 6
|
|
|
|
|
+- 预登录 TabBar 仅显示发现+商城: ✅ Task 3 (bottom-nav) + Task 4 (App.vue hideTabBar)
|
|
|
|
|
+- 登录页不改造: ✅ Task 7 只加 redirect 参数,不改造 UI
|
|
|
|
|
+- 登录后恢复原生 TabBar: ✅ Task 4
|
|
|
|
|
+- 退出登录回到未登录态: ✅ implicit via App.vue onShow check
|
|
|
|
|
+- 沙盘组件复用: ✅ Task 2 (preview/parent/child 三模式)
|
|
|
|
|
+
|
|
|
|
|
+**2. Placeholder scan:** No TBD, TODO, or empty sections.
|
|
|
|
|
+
|
|
|
|
|
+**3. Type consistency:**
|
|
|
|
|
+- `@point-click` used in component emit and parent handler ✅
|
|
|
|
|
+- `requireLogin()` used consistently across discover and shop pages ✅
|
|
|
|
|
+- `redirect` parameter format consistent ✅
|