去掉协会 WebView 和魔方账密页,微信登录与咨询接口共用同一 token 和拦截器。 Co-authored-by: Cursor <cursoragent@cursor.com>
417 lines
13 KiB
Vue
417 lines
13 KiB
Vue
<template>
|
||
<view class="yxg-home">
|
||
<yxg-nav :logo="true" :show-back="false" />
|
||
<view class="atm a1" />
|
||
<view class="atm a2" />
|
||
<view class="atm a3" />
|
||
|
||
<view class="archive">
|
||
<view class="av-self" @click="yxgGo('/profile')">
|
||
<view class="av">
|
||
<image class="av-img" :src="avatarSrc" mode="aspectFill" />
|
||
</view>
|
||
<text class="av-name">{{ selfLabel }}</text>
|
||
</view>
|
||
<view class="av-add" @click="yxgGo('/profile', { add: '1' })">
|
||
<text class="plus-dot">+</text>
|
||
<text class="av-name">添加</text>
|
||
</view>
|
||
<view class="archive-tail" @click="yxgGo('/mine')">
|
||
<text class="chev">我的 ›</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="self-card">
|
||
<view class="self-head">
|
||
<view class="who-btn" @click="yxgGo('/profile')">
|
||
<text class="who-name">{{ profileLabel }}</text>
|
||
<text class="caret">▾</text>
|
||
</view>
|
||
<text class="who-day">{{ dayLabel }}</text>
|
||
<text class="more" @click="yxgGo('/profile')">更多 ›</text>
|
||
</view>
|
||
<view v-if="tipsLoading" class="tips-loading">
|
||
<text>正在根据生日与此刻节律整理…</text>
|
||
</view>
|
||
<view v-else class="self-body">
|
||
<view class="duo">
|
||
<view class="wear-panel">
|
||
<text class="wear-title">穿衣指数</text>
|
||
<view class="wear-ring">
|
||
<text class="ring-num">{{ tips.clothingIndex }}</text>
|
||
</view>
|
||
<text class="wear-text">{{ tips.clothing }}</text>
|
||
</view>
|
||
<view class="block color-block">
|
||
<text class="block-title">颜色搭配</text>
|
||
<view class="swatches">
|
||
<view v-for="c in tips.palette" :key="c.name" class="swatch">
|
||
<view class="chip" :style="{ background: c.hex }" />
|
||
<text class="chip-name">{{ c.name }}</text>
|
||
</view>
|
||
</view>
|
||
<text class="block-note">{{ tips.colorNote }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="block wellness-block">
|
||
<text class="block-title">养生推荐</text>
|
||
<text class="wellness">{{ tips.wellness }}</text>
|
||
</view>
|
||
<text v-if="tips.needBirth" class="birth-hint">完善生日档案后,会按你的生日与此刻节律个性化</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="sheet">
|
||
<view class="tool-grid">
|
||
<view
|
||
v-for="t in tools"
|
||
:key="t.to + t.label"
|
||
class="tool-item"
|
||
@click="yxgGo(t.to)"
|
||
>
|
||
<yxg-tool-icon :name="t.icon" :size="44" />
|
||
<text class="label">{{ t.label }}</text>
|
||
<text v-if="t.badge" class="badge" :class="'b-' + (t.badgeTone || 'hot')">{{ t.badge }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="promo-pair">
|
||
<view class="promo p-plaza" @click="yxgGo('/explore')">
|
||
<text class="p-title">探索广场</text>
|
||
<text class="p-sub">测评 · 工具 · 自我理解</text>
|
||
<text class="p-chip">热</text>
|
||
</view>
|
||
<view class="promo p-vip" @click="yxgGo('/membership')">
|
||
<text class="p-title">成长会员</text>
|
||
<text class="p-sub">深度报告与全年陪伴</text>
|
||
<text class="p-chip soft">新</text>
|
||
</view>
|
||
</view>
|
||
<text class="ai-note">部分内容由 AI 生成,仅供参考</text>
|
||
|
||
<view v-if="feedsVisible" class="feed-sec">
|
||
<view class="sec-head">
|
||
<text class="title">今日推荐</text>
|
||
<text class="more" @click="yxgGo('/explore')">更多 ›</text>
|
||
</view>
|
||
<view class="feed-grid">
|
||
<view
|
||
v-for="f in feeds"
|
||
:key="f.to + f.title"
|
||
class="feed-card"
|
||
:class="f.tone"
|
||
@click="yxgGo(f.to)"
|
||
>
|
||
<text v-if="f.tag" class="feed-tag">{{ f.tag }}</text>
|
||
<view class="feed-cover">
|
||
<yxg-tool-icon :name="f.icon" :size="40" />
|
||
</view>
|
||
<view class="feed-body">
|
||
<text class="name">{{ f.title }}</text>
|
||
<text class="meta">{{ f.meta }}</text>
|
||
<view class="stat-row">
|
||
<text class="stat">{{ f.stat }}</text>
|
||
<text class="go">→</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import YxgNav from '@/components/yxg/yxg-nav.vue'
|
||
import YxgToolIcon from '@/components/yxg/yxg-tool-icon.vue'
|
||
import { yxgApi, assetURL } from '@/util/yxgApi.js'
|
||
import { setAccountNickname } from '@/util/yxgAuth.js'
|
||
import {
|
||
homeFeeds,
|
||
homeGridRow1,
|
||
homeGridRow2,
|
||
mapHomeBannersToFeeds,
|
||
mapHomeTools,
|
||
} from '@/util/yxgCatalog.js'
|
||
import { YXG_DEFAULT_AVATAR } from '@/util/yxgConfig.js'
|
||
import { yxgGo } from '@/util/yxgNav.js'
|
||
import { localFallbackTips, tipsFromApi } from '@/util/yxgTips.js'
|
||
|
||
const profileLabel = ref('访客')
|
||
const avatarUrl = ref('')
|
||
const tips = ref(localFallbackTips())
|
||
const tipsLoading = ref(true)
|
||
const gridRow1 = ref([...homeGridRow1])
|
||
const gridRow2 = ref([...homeGridRow2])
|
||
const feeds = ref([...homeFeeds])
|
||
const feedsVisible = ref(true)
|
||
|
||
const tools = computed(() => [...gridRow1.value, ...gridRow2.value])
|
||
const selfLabel = computed(() => {
|
||
const n = (profileLabel.value || '').trim()
|
||
if (!n) return '访客'
|
||
return n.length > 4 ? `${n.slice(0, 4)}…` : n
|
||
})
|
||
const avatarSrc = computed(() => assetURL(avatarUrl.value) || YXG_DEFAULT_AVATAR)
|
||
const dayLabel = computed(() => {
|
||
if (tipsLoading.value || !tips.value.asOf) return ''
|
||
return `今日 ${tips.value.asOf.slice(5)}`
|
||
})
|
||
|
||
function loadHome() {
|
||
yxgApi
|
||
.authMe()
|
||
.then((me) => {
|
||
if (me.nickname) {
|
||
profileLabel.value = me.nickname
|
||
setAccountNickname(me.nickname)
|
||
}
|
||
avatarUrl.value = me.avatar_url || ''
|
||
})
|
||
.catch(() => {
|
||
profileLabel.value = '访客'
|
||
avatarUrl.value = ''
|
||
})
|
||
yxgApi
|
||
.getHomeDailyTips()
|
||
.then((t) => {
|
||
tips.value = tipsFromApi(t)
|
||
})
|
||
.catch(() => {
|
||
tips.value = localFallbackTips()
|
||
})
|
||
.finally(() => {
|
||
tipsLoading.value = false
|
||
})
|
||
yxgApi
|
||
.getHomeTools()
|
||
.then((res) => {
|
||
const mapped = mapHomeTools(res.items || [])
|
||
if (mapped.row1.length || mapped.row2.length) {
|
||
gridRow1.value = mapped.row1
|
||
gridRow2.value = mapped.row2
|
||
}
|
||
})
|
||
.catch(() => {})
|
||
yxgApi
|
||
.getHomeBanners('home')
|
||
.then((res) => {
|
||
const mapped = mapHomeBannersToFeeds(res.items || [])
|
||
if (mapped.length) feeds.value = mapped
|
||
})
|
||
.catch(() => {})
|
||
yxgApi
|
||
.getHomeFeedSlots('home')
|
||
.then((res) => {
|
||
feedsVisible.value = (res.items || []).length > 0
|
||
})
|
||
.catch(() => {
|
||
feedsVisible.value = true
|
||
})
|
||
}
|
||
|
||
onShow(() => {
|
||
loadHome()
|
||
})
|
||
</script>
|
||
|
||
<style>
|
||
page {
|
||
background-color: #ffd1c7;
|
||
}
|
||
</style>
|
||
|
||
<style scoped>
|
||
.yxg-home {
|
||
min-height: 100vh;
|
||
background: linear-gradient(180deg, #ffd1c7 0%, #ffe4d8 28%, #fff4ef 52%, #fff9f7 100%);
|
||
padding-bottom: 40px;
|
||
position: relative;
|
||
overflow-x: hidden;
|
||
}
|
||
.atm {
|
||
position: absolute;
|
||
pointer-events: none;
|
||
border-radius: 50%;
|
||
z-index: 0;
|
||
}
|
||
.a1 { width: 160px; height: 160px; top: 88px; right: -40px; background: rgba(255, 255, 255, 0.5); }
|
||
.a2 { width: 100px; height: 100px; top: 148px; left: -28px; background: rgba(255, 200, 180, 0.45); }
|
||
.a3 { width: 70px; height: 70px; top: 220px; right: 40px; background: rgba(255, 230, 210, 0.5); }
|
||
|
||
.archive {
|
||
margin: 12px 16px 0;
|
||
position: relative;
|
||
z-index: 2;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 14px;
|
||
padding: 10px 12px;
|
||
border-radius: 20px;
|
||
background: rgba(255, 255, 255, 0.88);
|
||
box-shadow: 0 6px 20px rgba(229, 77, 66, 0.08);
|
||
}
|
||
.av-self, .av-add {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 4px;
|
||
flex-shrink: 0;
|
||
}
|
||
.av {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 50%;
|
||
overflow: hidden;
|
||
background: linear-gradient(145deg, #ffe8e0, #ffd0c4);
|
||
border: 2px solid rgba(229, 77, 66, 0.35);
|
||
}
|
||
.av-img { width: 40px; height: 40px; }
|
||
.av-name {
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
color: #8a4a3a;
|
||
max-width: 3.2em;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.plus-dot {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 50%;
|
||
background: rgba(255, 236, 230, 0.95);
|
||
border: 1.5px dashed rgba(229, 77, 66, 0.35);
|
||
color: #e54d42;
|
||
font-size: 22px;
|
||
line-height: 38px;
|
||
text-align: center;
|
||
}
|
||
.archive-tail { margin-left: auto; padding: 8px 4px; }
|
||
.chev { font-size: 13px; color: #c4a090; }
|
||
|
||
.self-card {
|
||
margin: 12px 16px 0;
|
||
position: relative;
|
||
z-index: 2;
|
||
padding: 14px;
|
||
background: #fff;
|
||
border-radius: 20px;
|
||
box-shadow: 0 8px 28px rgba(229, 77, 66, 0.12);
|
||
}
|
||
.self-head {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 12px;
|
||
}
|
||
.who-btn { display: flex; align-items: center; gap: 4px; min-width: 0; }
|
||
.who-name { font-size: 16px; font-weight: 700; color: #333; max-width: 7em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||
.caret { font-size: 10px; color: #bbb; }
|
||
.who-day { flex: 1; text-align: center; font-size: 11px; color: #bbb; }
|
||
.more { font-size: 12px; color: #bbb; }
|
||
.tips-loading { padding: 20px 8px; text-align: center; font-size: 12px; color: #bbb; }
|
||
.self-body { display: flex; flex-direction: column; gap: 10px; }
|
||
.duo { display: flex; gap: 10px; }
|
||
.wear-panel, .block {
|
||
flex: 1;
|
||
min-width: 0;
|
||
padding: 12px 10px;
|
||
border-radius: 16px;
|
||
}
|
||
.wear-panel { background: linear-gradient(160deg, #fff8f4 0%, #ffe8e0 55%, #fff5f0 100%); display: flex; flex-direction: column; align-items: center; gap: 8px; }
|
||
.wear-title, .block-title { font-size: 12px; font-weight: 700; color: #333; align-self: flex-start; }
|
||
.wear-ring {
|
||
width: 64px; height: 64px; border-radius: 50%;
|
||
border: 6px solid rgba(229, 77, 66, 0.12);
|
||
display: flex; align-items: center; justify-content: center;
|
||
}
|
||
.ring-num { font-size: 20px; font-weight: 700; color: #e54d42; }
|
||
.wear-text, .block-note { font-size: 11px; color: #7a6f6a; line-height: 1.45; text-align: center; }
|
||
.color-block { background: linear-gradient(160deg, #f7f9fc 0%, #eef4fa 100%); }
|
||
.wellness-block { background: linear-gradient(160deg, #f5faf6 0%, #eaf5ee 100%); }
|
||
.swatches { display: flex; justify-content: center; gap: 10px; margin: 8px 0; }
|
||
.swatch { display: flex; flex-direction: column; align-items: center; gap: 5px; }
|
||
.chip { width: 36px; height: 36px; border-radius: 11px; }
|
||
.chip-name { font-size: 10px; font-weight: 600; color: #999; }
|
||
.wellness { font-size: 13px; color: #333; font-weight: 500; margin-top: 8px; display: block; }
|
||
.birth-hint { font-size: 11px; color: #bbb; text-align: center; }
|
||
|
||
.sheet {
|
||
margin-top: 14px;
|
||
background: #fff;
|
||
border-radius: 22px 22px 0 0;
|
||
padding: 8px 0 28px;
|
||
box-shadow: 0 -4px 24px rgba(180, 100, 80, 0.06);
|
||
position: relative;
|
||
z-index: 3;
|
||
}
|
||
.tool-grid {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
padding: 8px 8px 2px;
|
||
}
|
||
.tool-item {
|
||
width: 25%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 8px 0 10px;
|
||
position: relative;
|
||
}
|
||
.label { font-size: 11px; color: #555; }
|
||
.badge {
|
||
position: absolute; top: 2px; right: 10px;
|
||
font-size: 9px; color: #fff; padding: 2px 5px; border-radius: 7px;
|
||
}
|
||
.b-hot { background: linear-gradient(135deg, #ff7a6e, #e54d42); }
|
||
.b-new { background: linear-gradient(135deg, #6eb6ff, #4a90e2); }
|
||
|
||
.promo-pair { display: flex; gap: 10px; padding: 14px 16px 0; }
|
||
.promo {
|
||
flex: 1; position: relative; border-radius: 16px; padding: 16px 14px; min-height: 96px;
|
||
display: flex; flex-direction: column; justify-content: center;
|
||
}
|
||
.p-plaza { background: linear-gradient(145deg, #ffe9e2, #ffc8ba); }
|
||
.p-vip { background: linear-gradient(145deg, #fff4e0, #ffd98a); }
|
||
.p-title { font-size: 15px; font-weight: 700; color: #333; }
|
||
.p-sub { font-size: 11px; color: rgba(0,0,0,.42); margin-top: 5px; }
|
||
.p-chip {
|
||
position: absolute; top: 10px; right: 10px;
|
||
font-size: 9px; font-weight: 700; color: #fff;
|
||
background: linear-gradient(135deg, #ff7a6e, #e54d42);
|
||
padding: 2px 6px; border-radius: 6px;
|
||
}
|
||
.p-chip.soft { background: linear-gradient(135deg, #6eb6ff, #4a90e2); }
|
||
.ai-note { display: block; margin-top: 10px; font-size: 10px; color: #ccc; text-align: center; }
|
||
|
||
.feed-sec { padding: 18px 16px 0; }
|
||
.sec-head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 12px; }
|
||
.sec-head .title { font-size: 17px; font-weight: 700; color: #333; }
|
||
.feed-grid { display: flex; flex-wrap: wrap; gap: 10px; }
|
||
.feed-card {
|
||
width: calc(50% - 5px);
|
||
border-radius: 16px;
|
||
overflow: hidden;
|
||
background: #fff;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,.04);
|
||
position: relative;
|
||
}
|
||
.feed-cover { height: 86px; display: flex; align-items: center; justify-content: center; }
|
||
.fc-a .feed-cover { background: linear-gradient(155deg, #ffd8d0, #ffb4a8); }
|
||
.fc-b .feed-cover { background: linear-gradient(155deg, #e8dff8, #cbb8f0); }
|
||
.fc-c .feed-cover { background: linear-gradient(155deg, #d8eaff, #b4d2f5); }
|
||
.fc-e .feed-cover { background: linear-gradient(155deg, #ffe9cc, #f5cc8a); }
|
||
.feed-body { padding: 10px 11px 12px; display: flex; flex-direction: column; gap: 4px; }
|
||
.name { font-size: 13px; font-weight: 700; color: #333; }
|
||
.meta { font-size: 11px; color: #999; line-height: 1.4; }
|
||
.stat-row { display: flex; justify-content: space-between; margin-top: 4px; }
|
||
.stat { font-size: 10px; color: #c4c4c4; }
|
||
.go { font-size: 12px; color: #e54d42; }
|
||
.feed-tag {
|
||
position: absolute; top: 8px; left: 8px; z-index: 1;
|
||
font-size: 10px; color: #fff; background: #e54d42;
|
||
padding: 2px 7px; border-radius: 7px;
|
||
}
|
||
</style>
|