diff --git a/App.vue b/App.vue
index 9e4ff62..533365c 100644
--- a/App.vue
+++ b/App.vue
@@ -47,6 +47,31 @@
diff --git a/components/yxg/yxg-page.vue b/components/yxg/yxg-page.vue
new file mode 100644
index 0000000..05456ab
--- /dev/null
+++ b/components/yxg/yxg-page.vue
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/yxg/yxg-tool-icon.vue b/components/yxg/yxg-tool-icon.vue
new file mode 100644
index 0000000..9944b81
--- /dev/null
+++ b/components/yxg/yxg-tool-icon.vue
@@ -0,0 +1,70 @@
+
+
+ {{ mark }}
+
+
+
+
+
+
diff --git a/manifest.json b/manifest.json
index dda9a5a..2441a37 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,6 +1,6 @@
{
"name" : "yuxingu-miniprogram",
- "appid" : "__UNI__E47533F",
+ "appid" : "__UNI__29F79D0",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",
diff --git a/pages.json b/pages.json
index 0f19f30..78c27dd 100644
--- a/pages.json
+++ b/pages.json
@@ -265,14 +265,28 @@
]
},
{
- "root": "pages/webview-page",
+ "root": "pages/yxg",
"pages": [
- {
- "path": "webview-page",
- "style": {
- "navigationBarTitleText": ""
- }
- }
+ { "path": "explore", "style": { "navigationBarTitleText": "探索", "navigationBarBackgroundColor": "#ffd1c7", "backgroundColor": "#ffd1c7" } },
+ { "path": "explore-bank", "style": { "navigationBarTitleText": "题库", "navigationBarBackgroundColor": "#ffd1c7", "backgroundColor": "#ffd1c7" } },
+ { "path": "explore-category", "style": { "navigationBarTitleText": "探索", "navigationBarBackgroundColor": "#ffd1c7", "backgroundColor": "#ffd1c7" } },
+ { "path": "growth-plan", "style": { "navigationBarTitleText": "成长计划", "navigationBarBackgroundColor": "#ffd1c7", "backgroundColor": "#ffd1c7" } },
+ { "path": "ask", "style": { "navigationBarTitleText": "AI问答", "navigationBarBackgroundColor": "#ffd1c7", "backgroundColor": "#ffd1c7" } },
+ { "path": "companion", "style": { "navigationBarTitleText": "节气陪伴", "navigationBarBackgroundColor": "#ffd1c7", "backgroundColor": "#ffd1c7" } },
+ { "path": "mine", "style": { "navigationBarTitleText": "我的魔方", "navigationBarBackgroundColor": "#ffd1c7", "backgroundColor": "#ffd1c7" } },
+ { "path": "profile", "style": { "navigationBarTitleText": "个人档案", "navigationBarBackgroundColor": "#ffd1c7", "backgroundColor": "#ffd1c7" } },
+ { "path": "portrait", "style": { "navigationBarTitleText": "愈心解码", "navigationBarBackgroundColor": "#ffd1c7", "backgroundColor": "#ffd1c7" } },
+ { "path": "star", "style": { "navigationBarTitleText": "星座", "navigationBarBackgroundColor": "#ffd1c7", "backgroundColor": "#ffd1c7" } },
+ { "path": "synastry", "style": { "navigationBarTitleText": "合盘", "navigationBarBackgroundColor": "#ffd1c7", "backgroundColor": "#ffd1c7" } },
+ { "path": "synastry-invite", "style": { "navigationBarTitleText": "合盘邀请", "navigationBarBackgroundColor": "#ffd1c7", "backgroundColor": "#ffd1c7" } },
+ { "path": "rhythm", "style": { "navigationBarTitleText": "身心节律", "navigationBarBackgroundColor": "#ffd1c7", "backgroundColor": "#ffd1c7" } },
+ { "path": "cards", "style": { "navigationBarTitleText": "意象卡片", "navigationBarBackgroundColor": "#ffd1c7", "backgroundColor": "#ffd1c7" } },
+ { "path": "relation", "style": { "navigationBarTitleText": "人格匹配", "navigationBarBackgroundColor": "#ffd1c7", "backgroundColor": "#ffd1c7" } },
+ { "path": "membership", "style": { "navigationBarTitleText": "成长会员", "navigationBarBackgroundColor": "#ffd1c7", "backgroundColor": "#ffd1c7" } },
+ { "path": "scale", "style": { "navigationBarTitleText": "测评", "navigationBarBackgroundColor": "#ffd1c7", "backgroundColor": "#ffd1c7" } },
+ { "path": "share", "style": { "navigationBarTitleText": "分享", "navigationBarBackgroundColor": "#ffd1c7", "backgroundColor": "#ffd1c7" } },
+ { "path": "reports", "style": { "navigationBarTitleText": "成长报告", "navigationBarBackgroundColor": "#ffd1c7", "backgroundColor": "#ffd1c7" } },
+ { "path": "report", "style": { "navigationBarTitleText": "报告详情", "navigationBarBackgroundColor": "#ffd1c7", "backgroundColor": "#ffd1c7" } }
]
}
],
diff --git a/pages/index/index.vue b/pages/index/index.vue
index 89eb8ff..dd7653c 100644
--- a/pages/index/index.vue
+++ b/pages/index/index.vue
@@ -495,7 +495,6 @@
-
@@ -511,8 +510,6 @@
const consultList = ref([])
const newHealingList = ref([])
const newActivityList = ref([])
- const showWebView = ref(false)
- const externalUrl = ref()
const dotsStyles = ref({
backgroundColor: 'rgba(96, 98, 113, 1)',
border: '1px rgba(96, 98, 113, 1) solid',
@@ -596,16 +593,6 @@
})
}
- const toTuTest=()=>{
-
- const externalUrl = 'https://cps.jsbr.org.cn/login.html';
- uni.navigateTo({
- url: `/pages/webview-page/webview-page?url=${encodeURIComponent(externalUrl)}`
- });
-
-
- }
-
const toHealingDetail=(item)=>{
dohttp.navigateTo("/pages/healing-detail/healing-detail?id=" + item.id)
}
diff --git a/pages/login-pop/login-pop.vue b/pages/login-pop/login-pop.vue
index 23aa354..fc3a67d 100644
--- a/pages/login-pop/login-pop.vue
+++ b/pages/login-pop/login-pop.vue
@@ -154,25 +154,41 @@
}
}
+ const afterLogin = () => {
+ if (type.value == 1) {
+ uni.reLaunch({
+ url: "/pages/index/index"
+ })
+ } else if (type.value == 3) {
+ uni.reLaunch({
+ url: path.value
+ })
+ } else {
+ uni.navigateBack({
+ delta: 1
+ })
+ }
+ }
const toLogin = (code, encryptedData, iv) => {
- const params = {
- grantType: "mini_app",
- account: code,
- }
-
-
- let others = {
- encryptedData: encryptedData,
- iv: iv,
- code: code
- }
- params.others = JSON.stringify(others)
- dohttp.post(urls.loginUrl, params).then((result) => {
- userInfo.value.token = result.accessToken
- uni.setStorageSync('token', result.accessToken)
+ dohttp.post(urls.loginUrl, { code, encryptedData, iv }).then((result) => {
+ const token = result.token || result.accessToken
+ userInfo.value.token = token
+ uni.setStorageSync('token', token)
+ uni.removeStorageSync('yxg_token')
+ if (result.user) {
+ uni.setStorageSync('userinfo', {
+ id: result.user.id,
+ avatarUrl: result.user.avatar_url,
+ nickName: result.user.nickname
+ })
+ }
loginPop.value.close()
-
getUserInfo()
+ }).catch((e) => {
+ uni.showToast({
+ title: (e && e.errMsg ? String(e.errMsg).replace('【request】', '') : '登录失败'),
+ icon: 'none'
+ })
})
}
const getUserInfo = () => {
@@ -182,23 +198,11 @@
avatarUrl: result.avatarUrl,
nickName: result.nickName
}
-
uni.setStorageSync('userinfo', userinfo)
- if (type.value == 1) {
- uni.reLaunch({
- url: "/pages/index/index"
- })
- } else if (type.value == 3) {
- uni.reLaunch({
- url: path.value
- })
- } else {
- uni.navigateBack({
- delta: 1
- })
- }
+ afterLogin()
+ }).catch(() => {
+ afterLogin()
})
-
}
diff --git a/pages/webview-page/webview-page.vue b/pages/webview-page/webview-page.vue
deleted file mode 100644
index 879bea1..0000000
--- a/pages/webview-page/webview-page.vue
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
diff --git a/pages/yxg-magic/index.vue b/pages/yxg-magic/index.vue
index eefd2d9..0f5fac1 100644
--- a/pages/yxg-magic/index.vue
+++ b/pages/yxg-magic/index.vue
@@ -1,140 +1,416 @@
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+ {{ selfLabel }}
+
+
+ +
+ 添加
+
+
+ 我的 ›
+
+
+
+
+
+
+ {{ profileLabel }}
+ ▾
+
+ {{ dayLabel }}
+ 更多 ›
+
+
+ 正在根据生日与此刻节律整理…
+
+
+
+
+ 穿衣指数
+
+ {{ tips.clothingIndex }}
+
+ {{ tips.clothing }}
+
+
+ 颜色搭配
+
+
+
+ {{ c.name }}
+
+
+ {{ tips.colorNote }}
+
+
+
+ 养生推荐
+ {{ tips.wellness }}
+
+ 完善生日档案后,会按你的生日与此刻节律个性化
+
+
+
+
+
+
+
+ {{ t.label }}
+ {{ t.badge }}
+
+
+
+
+
+ 探索广场
+ 测评 · 工具 · 自我理解
+ 热
+
+
+ 成长会员
+ 深度报告与全年陪伴
+ 新
+
+
+ 部分内容由 AI 生成,仅供参考
+
+
+
+ 今日推荐
+ 更多 ›
+
+
+
+ {{ f.tag }}
+
+
+
+
+ {{ f.title }}
+ {{ f.meta }}
+
+ {{ f.stat }}
+ →
+
+
+
+
-
diff --git a/pages/yxg/ask.vue b/pages/yxg/ask.vue
new file mode 100644
index 0000000..a3fe0d2
--- /dev/null
+++ b/pages/yxg/ask.vue
@@ -0,0 +1,155 @@
+
+
+ 加载中…
+ {{ bootError }}
+
+
+ 今日剩余 {{ quota?.left ?? quota?.remaining ?? '—' }} 次
+ 加购 ›
+
+
+
+ {{ p.display_name || (p.relation === 'self' ? '我' : 'TA') }}
+
+
+
+
+ {{ s.label }}
+
+
+
+
+ {{ m.content }}
+
+
+ {{ sendError }}
+
+
+ 发送
+
+
+
+
+
+
+
+
+
diff --git a/pages/yxg/cards.vue b/pages/yxg/cards.vue
new file mode 100644
index 0000000..c97124c
--- /dev/null
+++ b/pages/yxg/cards.vue
@@ -0,0 +1,104 @@
+
+
+ 加载中…
+ {{ error }}
+
+ 今日剩余 {{ quota?.left ?? quota?.remaining ?? '—' }} 次
+
+
+ {{ s.title || s.name || s.label }}
+
+
+
+ {{ drawing ? '抽取中…' : '抽取一张卡片' }}
+
+
+
+ {{ drawn.title || drawn.name || '意象卡片' }}
+ {{ drawn.meaning || drawn.interpretation || drawn.text || '' }}
+
+
+
+
+
+
+
+
+
diff --git a/pages/yxg/companion.vue b/pages/yxg/companion.vue
new file mode 100644
index 0000000..e7027f1
--- /dev/null
+++ b/pages/yxg/companion.vue
@@ -0,0 +1,102 @@
+
+
+ 加载中…
+ {{ error }}
+
+ {{ today.name || today.solar_term || '今日节气' }}
+ {{ today.date || today.as_of || '' }}
+ {{ today.guidance || today.copy || today.description || '此刻宜慢一点,给身心留一点空间。' }}
+
+
+ 今日心情
+
+
+ {{ n }}
+
+
+
+ {{ saving ? '保存中…' : '记录心情' }}
+ 已记下今天的心情
+
+
+
+
+
+
+
+
diff --git a/pages/yxg/explore-bank.vue b/pages/yxg/explore-bank.vue
new file mode 100644
index 0000000..f27033a
--- /dev/null
+++ b/pages/yxg/explore-bank.vue
@@ -0,0 +1,85 @@
+
+
+
+
+
+ {{ cat?.title || '题库分类' }}
+ {{ cat.description }}
+
+
+ 加载中…
+ {{ error }} · 重试
+
+
+
+
+ {{ it.title }}
+ {{ it.description }} · {{ it.question_count }} 题
+
+ ›
+
+
+ 题库内容仅供自我探索参考,不构成心理或医学诊断。
+
+
+
+
+
+
+
diff --git a/pages/yxg/explore-category.vue b/pages/yxg/explore-category.vue
new file mode 100644
index 0000000..1325640
--- /dev/null
+++ b/pages/yxg/explore-category.vue
@@ -0,0 +1,77 @@
+
+
+
+
+
+ {{ cat?.title || '分类' }}
+ {{ cat.description }}
+
+
+ 加载中…
+ {{ error }}
+
+
+
+
+ {{ it.title }}
+ {{ it.description }}
+
+ ›
+
+
+
+
+
+
+
+
+
diff --git a/pages/yxg/explore.vue b/pages/yxg/explore.vue
new file mode 100644
index 0000000..b8e3244
--- /dev/null
+++ b/pages/yxg/explore.vue
@@ -0,0 +1,120 @@
+
+
+
+
+
+ 探索
+ 题库测评 · 工具 · 自我理解
+
+
+
+
+ 题库精选
+ 加载中…
+ {{ bankError }} · 重试
+
+
+
+ {{ t.title }}
+
+
+
+
+
+ 题库分类
+
+
+
+
+ {{ c.title }}
+ {{ c.description }} · {{ c.count }} 套
+
+ ›
+
+
+
+
+
+ 其它工具
+
+
+
+ {{ t.label }}
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/yxg/growth-plan.vue b/pages/yxg/growth-plan.vue
new file mode 100644
index 0000000..a02f917
--- /dev/null
+++ b/pages/yxg/growth-plan.vue
@@ -0,0 +1,110 @@
+
+
+
+ 新计划标题
+
+ 焦点(可选)
+
+ 创建计划
+ {{ err }}
+
+ 加载中…
+
+ {{ p.title }}
+ {{ p.focus }}
+
+ {{ checking === p.id ? '记录中…' : '今日打卡' }}
+
+
+ {{ c.day }}{{ c.note ? ' · ' + c.note : '' }}
+
+
+ 还没有计划,先创建一个小目标吧。
+
+
+
+
+
+
+
diff --git a/pages/yxg/membership.vue b/pages/yxg/membership.vue
new file mode 100644
index 0000000..9403f78
--- /dev/null
+++ b/pages/yxg/membership.vue
@@ -0,0 +1,106 @@
+
+
+ 加载中…
+ {{ error }} · 重试
+
+
+ 会员有效 · {{ planLabel }}
+ 到期:{{ formatDate(me.expires_at) }}
+ 问答额度剩余:{{ me.ask_quota_left ?? 0 }}
+
+
+ 开通后可查看画像与关系理解的完整分析,并获得更多 AI 问答次数。
+
+ 成长会员
+
+ {{ p.tag }}
+ {{ p.label }}
+ {{ p.price }}
+ {{ p.desc }}
+
+ 当前为模拟支付,便于本地验收。
+
+
+
+
+
+
+
+
diff --git a/pages/yxg/mine.vue b/pages/yxg/mine.vue
new file mode 100644
index 0000000..7b7f94b
--- /dev/null
+++ b/pages/yxg/mine.vue
@@ -0,0 +1,160 @@
+
+
+
+
+
+
+ {{ accountLabel }}
+ {{ loggedIn ? `${profileCount} 份档案` : '登录后同步你的探索' }}
+ 退出登录
+ 登录
+
+
+ 加载中…
+ {{ error }} · 点此重试
+
+
+ 成长会员 · {{ planLabel }}
+
+
+ 档案与内容
+
+
+
+ {{ it.label }}
+ ›
+
+
+
+
+ 成长与会员
+
+
+
+ {{ it.label }}
+ ›
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/yxg/portrait.vue b/pages/yxg/portrait.vue
new file mode 100644
index 0000000..013d136
--- /dev/null
+++ b/pages/yxg/portrait.vue
@@ -0,0 +1,128 @@
+
+
+ 生成中…
+ {{ error }}
+
+ 一个生日,读懂性格与身心节奏
+ 填写公历生日,生成你的愈心解码。
+
+
+
+
+
+ {{ formError }}
+ 开始解码
+
+
+ {{ headline }}
+ {{ oneLiner }}
+
+ {{ k }}
+
+ {{ bodyText }}
+
+ 重新生成
+ 完整报告
+
+
+
+
+
+
+
+
+
diff --git a/pages/yxg/profile.vue b/pages/yxg/profile.vue
new file mode 100644
index 0000000..7346c4e
--- /dev/null
+++ b/pages/yxg/profile.vue
@@ -0,0 +1,238 @@
+
+
+ 加载中…
+ {{ error }} · 重试
+
+
+ 自己
+
+ {{ selfProfile.display_name || '我' }}
+ 生日 {{ selfProfile.birth_date || '未填写' }}
+ {{ selfProfile.birth_place || '出生地未填' }}
+ 编辑
+
+
+ 还没有自己的档案,先补一个生日吧
+
+
+
+
+
+ 重要的人
+ {{ showAdd ? '取消' : '+ 添加' }}
+
+
+
+
+
+
+
+
+
+ {{ addError }}
+ {{ adding ? '保存中…' : '保存' }}
+
+
+ {{ p.display_name || 'TA' }}
+ {{ p.birth_date }} · {{ p.birth_place || '出生地未填' }}
+
+ 编辑
+ 删除
+
+
+
+
+
+ 编辑档案
+
+
+
+
+
+
+
+ {{ formError }}
+
+ {{ saving ? '保存中…' : '保存' }}
+ 取消
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/yxg/relation.vue b/pages/yxg/relation.vue
new file mode 100644
index 0000000..cb456e2
--- /dev/null
+++ b/pages/yxg/relation.vue
@@ -0,0 +1,119 @@
+
+
+
+ 看见彼此的相处模式
+ 填写 TA 的称呼与生日,对照性格与默契。
+
+
+
+ {{ o.label }}
+
+
+
+
+
+
+
+ {{ error }}
+ {{ loading ? '分析中…' : '开始匹配' }}
+
+
+ {{ fitLabel || '相处参考' }}
+ {{ diff }}
+ 我:{{ meStyle }}
+ TA:{{ otherStyle }}
+ 默契 {{ harmony }}
+
+ 再测一次
+ 完整报告
+
+
+
+
+
+
+
+
+
diff --git a/pages/yxg/report.vue b/pages/yxg/report.vue
new file mode 100644
index 0000000..f587eee
--- /dev/null
+++ b/pages/yxg/report.vue
@@ -0,0 +1,83 @@
+
+
+ 加载中…
+ {{ error }}
+
+
+ {{ headlineOf(report) }}
+ {{ formatDate(report.created_at) }}
+ {{ oneLiner }}
+
+ {{ k }}
+
+ {{ bodyText }}
+
+ 分享
+
+
+
+
+
+
+
+
diff --git a/pages/yxg/reports.vue b/pages/yxg/reports.vue
new file mode 100644
index 0000000..a93b1db
--- /dev/null
+++ b/pages/yxg/reports.vue
@@ -0,0 +1,105 @@
+
+
+
+
+ {{ f.label }}
+
+
+
+ 成长会员
+ 解锁全部深度报告与节气陪伴 ›
+
+ 加载中…
+ {{ error }} · 重试
+
+ 还没有{{ filter === 'all' ? '' : '该类' }}成长报告
+ 去探索
+
+
+
+
+
+ {{ typeLabel(r.type) }}
+ {{ headlineOf(r) }}
+ {{ formatDate(r.created_at) }}
+
+ ›
+
+
+
+
+
+
+
+
+
diff --git a/pages/yxg/rhythm.vue b/pages/yxg/rhythm.vue
new file mode 100644
index 0000000..b39f3a4
--- /dev/null
+++ b/pages/yxg/rhythm.vue
@@ -0,0 +1,104 @@
+
+
+ 加载中…
+ {{ error }}
+
+ 看见身体与情绪的节奏
+ 用生日生成今日身心节律建议。
+
+
+
+
+
+ 查看节律
+
+
+ {{ headline }}
+ {{ oneLiner }}
+ {{ bodyText }}
+
+ 重新生成
+ 完整报告
+
+
+
+
+
+
+
+
+
diff --git a/pages/yxg/scale.vue b/pages/yxg/scale.vue
new file mode 100644
index 0000000..86b66fb
--- /dev/null
+++ b/pages/yxg/scale.vue
@@ -0,0 +1,207 @@
+
+
+ 加载中…
+ {{ loadError }}
+
+
+ {{ title }}
+ {{ description }}
+ 约 {{ estMinutes }} 分钟 · {{ questions.length }} 题
+ 该测评需开通成长会员
+ 去开通
+ {{ draftRestored ? '继续作答' : '开始测评' }}
+
+
+
+ {{ currentIdx + 1 }} / {{ questions.length }}
+
+ {{ currentQuestion.body.prompt }}
+
+ {{ opt.text }}
+
+
+ 上一题
+
+ {{ submitting ? '提交中…' : isLast ? '提交' : '下一题' }}
+
+
+ {{ submitError }}
+
+
+
+ {{ resultLabel }}
+ {{ shareLine || resultRaw?.summary || '' }}
+ {{ resultRaw.overview }}
+
+ 再测一次
+ 我的报告
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/yxg/share.vue b/pages/yxg/share.vue
new file mode 100644
index 0000000..f574bf6
--- /dev/null
+++ b/pages/yxg/share.vue
@@ -0,0 +1,77 @@
+
+
+
+ 分享内容无效
+ 可以从首页重新开始。
+ 回首页
+
+
+
+ {{ title }}
+ {{ line }}
+ 转发给朋友
+ {{ ctaText }}
+ 本内容为自我探索与生活方式参考,不构成医疗建议,亦非占卜预测。
+
+
+
+
+
+
+
+
diff --git a/pages/yxg/star.vue b/pages/yxg/star.vue
new file mode 100644
index 0000000..4534285
--- /dev/null
+++ b/pages/yxg/star.vue
@@ -0,0 +1,104 @@
+
+
+ 加载中…
+ {{ error }}
+
+ 本命盘 · 相位 · 日周月运势
+ 用生日生成星座排盘与性格解读。
+
+
+
+
+
+ 查看星盘
+
+
+ {{ headline }}
+ {{ oneLiner }}
+ {{ bodyText }}
+
+ 重新生成
+ 完整报告
+
+
+
+
+
+
+
+
+
diff --git a/pages/yxg/synastry-invite.vue b/pages/yxg/synastry-invite.vue
new file mode 100644
index 0000000..39bd38a
--- /dev/null
+++ b/pages/yxg/synastry-invite.vue
@@ -0,0 +1,110 @@
+
+
+ 加载邀请…
+
+
+ {{ meta.host_name || '好友' }} 邀请你合盘
+ 填写生日,一起看见彼此的星盘互动与相处参考
+
+ 该邀请已使用,可直接去合盘页再测。
+ 回合盘页
+
+
+
+
+
+
+
+
+ {{ error }}
+
+ {{ submitting ? '生成中…' : '接受并合盘' }}
+
+
+
+
+
+ {{ error || '邀请无效或已过期' }}
+ 去合盘页
+
+
+
+
+
+
+
+
diff --git a/pages/yxg/synastry.vue b/pages/yxg/synastry.vue
new file mode 100644
index 0000000..2cf715b
--- /dev/null
+++ b/pages/yxg/synastry.vue
@@ -0,0 +1,141 @@
+
+
+
+ 恋爱 / 友情 / 婚姻指数
+ 选择档案中的两个人,或填写 TA 的生日生成合盘。
+
+ 我 / A:{{ labels[idxA] || '请选择' }}
+
+
+ TA / B:{{ labels[idxB] || '请选择' }}
+
+ 没有 TA 的档案?填写生日快速添加
+
+
+
+
+
+
+ {{ error }}
+ {{ loading ? '生成中…' : '开始合盘' }}
+ {{ inviting ? '生成邀请…' : '生成邀请链接' }}
+
+
+ {{ headline }}
+ {{ oneLiner }}
+
+ 再测一次
+ 完整报告
+
+
+
+
+
+
+
+
+
diff --git a/util/apiUrl.js b/util/apiUrl.js
index 52cf71e..f9230f7 100644
--- a/util/apiUrl.js
+++ b/util/apiUrl.js
@@ -1,52 +1,48 @@
-//统一设置API接口地址
+// 统一打 Go /api/v1(ECR-050 咨询域已原生,不再反代 Java)
let urls = {
- // 七牛相关API
+ upload: `/api/v1/upload`,
+ newPageUrl: `/api/v1/psychic/news/page`,
+ newDetailUrl: `/api/v1/psychic/news/get`,
+ loginUrl: `/api/v1/auth/wechat`,
+ getBanner: `/api/v1/psychic/banner/all`,
+ psychicSave: `/api/v1/psychic/save`,
+ getPsychicListTest: `/api/v1/psychic/study/list`,
+ getSingleDetail: `/api/v1/psychic/study/getSingleDetail`,
+ userChoiceSave: `/api/v1/psychic/user-choice/save`,
+ getResult: `/api/v1/psychic/user-choice/getResult`,
+ myTest: `/api/v1/psychic/user-choice/my-test`,
+ businessScopeList: `/api/v1/psychic/doctor-info/business-scope-list`,
+ getDoctorInfo: `/api/v1/psychic/doctor-info/get`,
+ doctorInfopage: `/api/v1/psychic/doctor-info/page`,
+ focus: `/api/v1/psychic/doctor-info/focus`,
+ cancelFocus: `/api/v1/psychic/doctor-info/cancel-focus`,
+ appointTotalList: `/api/v1/psychic/appointment/remain-list`,
+ dateDetailList: `/api/v1/psychic/appointment/date-detail-list`,
+ getShowInfo: `/api/v1/psychic/doctor-info/get-show-info`,
+ createOrder: `/api/v1/psychic/pay/createOrder`,
+ refund: `/api/v1/psychic/pay/refund`,
- upload: `/admin-api/infra/file/upload`,
- newPageUrl: `/app-api/psychic/news/page`,
- newDetailUrl: `/app-api/psychic/news/get`,
- loginUrl: `/app-api/oauth/app/login`,
- getBanner: `/app-api/psychic/banner/all`,
- psychicSave: `/app-api/psychic/save`,
- getPsychicListTest: `/app-api/psychic/study/list`,
- getSingleDetail: `/app-api/psychic/study/getSingleDetail`,
- userChoiceSave: `/app-api/psychic/user-choice/save`,
- getResult: `/app-api/psychic/user-choice/getResult`,
- myTest: `/app-api/psychic/user-choice/my-test`,
- businessScopeList: `/app-api/psychic/doctor-info/business-scope-list`,
- getDoctorInfo: `/app-api/psychic/doctor-info/get`,
- doctorInfopage: `/app-api/psychic/doctor-info/page`,
- focus: `/app-api/psychic/doctor-info/focus`,
- cancelFocus: `/app-api/psychic/doctor-info/cancel-focus`,
- appointTotalList: `/app-api/psychic/appointment/remain-list`,
- dateDetailList: `/app-api/psychic/appointment/date-detail-list`,
- getShowInfo: `/app-api/psychic/doctor-info/get-show-info`,
- createOrder: `/app-api/psychic/pay/createOrder`,
- refund: `/app-api/psychic/pay/refund`,
+ getSelfInfo: `/api/v1/psychic/platform-user/getSelfInfo`,
+ updateUserInfo: `/api/v1/psychic/platform-user/update`,
- getSelfInfo: `/app-api/psychic/platform-user/getSelfInfo`,
- updateUserInfo: `/app-api/psychic/platform-user/update`,
-
- notReadNum: `/app-api/psychic/platform-user/not-read-num`,
- focusToRead: `/app-api/psychic/platform-user/focus-to-read`,
- orderToRead: `/app-api/psychic/platform-user/order-to-read`,
- userFeedback: `/app-api/psychic/platform-user/user-feedback`,
- feedbackFlag: `/app-api/psychic/platform-user/feedback-flag`,
+ notReadNum: `/api/v1/psychic/platform-user/not-read-num`,
+ focusToRead: `/api/v1/psychic/platform-user/focus-to-read`,
+ orderToRead: `/api/v1/psychic/platform-user/order-to-read`,
+ userFeedback: `/api/v1/psychic/platform-user/user-feedback`,
+ feedbackFlag: `/api/v1/psychic/platform-user/feedback-flag`,
- focusAll: `/app-api/psychic/platform-user/focus-all`,
- getByCode: `/app-api/psychic/procotol/getByCode`,
+ focusAll: `/api/v1/psychic/platform-user/focus-all`,
+ getByCode: `/api/v1/psychic/procotol/getByCode`,
+ createZxOrder: `/api/v1/psychic/order/create`,
- // 订单
- createZxOrder: `/app-api/psychic/order/create`,
-
- orderList: `/app-api/psychic/order/order-list`,
- orderDetail: `/app-api/psychic/order/order-detail`,
- getPayParam: `/app-api/psychic/order/get-pay-param`,
- cancelOrder: `/app-api/psychic/order/cancel`,
- deleteOrder: `/app-api/psychic/order/delete-order`,
+ orderList: `/api/v1/psychic/order/order-list`,
+ orderDetail: `/api/v1/psychic/order/order-detail`,
+ getPayParam: `/api/v1/psychic/order/get-pay-param`,
+ cancelOrder: `/api/v1/psychic/order/cancel`,
+ deleteOrder: `/api/v1/psychic/order/delete-order`,
}
export {
urls
-}
\ No newline at end of file
+}
diff --git a/util/requestConfig.js b/util/requestConfig.js
index f9ff268..2f1a998 100644
--- a/util/requestConfig.js
+++ b/util/requestConfig.js
@@ -2,13 +2,10 @@ import {
sysConsts
} from '../common/sysConsts.js';
import request from "./request";
-// 全局配置的请求域名
-// const baseUrl = 'http://192.168.100.19:8922'; //线上正式环境888
-// const baseUrl = 'https://api-qa.scyuelai.com/'; //qa测试环境
-// const baseUrl = 'https://api-zsh-dev.scyuelai.com/' //dev开发环境
-// const baseUrl = 'https://local.scyuelai.com/'; //本地测试环境
- const baseUrl = "https://miniapp.yuxingu.com.cn" //https://miniapp.yuxingu.com.cn
-// const baseUrl = "http://8.137.99.227:8922"
+import { YXG_API_BASE } from './yxgConfig.js'
+
+const DEVICE_KEY = 'yxg_device_key'
+const baseUrl = YXG_API_BASE
//可以new多个request来支持多个域名请求
@@ -69,11 +66,15 @@ $http.requestStart = function(options) {
}
}
//请求前加入token
- let myToken = uni.getStorageSync('token')
+ let myToken = uni.getStorageSync('token') || uni.getStorageSync('yxg_token')
if (myToken) {
- myToken = myToken.replace(/\"/g, "");
+ myToken = String(myToken).replace(/\"/g, "");
options.header['Authorization'] = 'Bearer ' + myToken;
}
+ const device = uni.getStorageSync(DEVICE_KEY)
+ if (device) {
+ options.header['X-Device-Key'] = device
+ }
return options; // return false 表示请求拦截,不会继续请求
}
@@ -97,19 +98,27 @@ $http.dataFactory = async function(res) {
// data: res.data,
// method: res.method,
// });
- if (res.response.statusCode && res.response.statusCode == 200) {
- let httpData = res.response.data;
-
- if (typeof(httpData) == "string") {
- httpData = JSON.parse(httpData);
+ const respHeader = res.response.header || res.response.headers || {}
+ const deviceOut = respHeader['X-Device-Key'] || respHeader['x-device-key']
+ if (deviceOut) {
+ uni.setStorageSync(DEVICE_KEY, deviceOut)
+ }
+ let httpData = res.response.data
+ if (typeof(httpData) == "string") {
+ try {
+ httpData = JSON.parse(httpData)
+ } catch (e) {
+ httpData = null
}
+ }
+ const errText = (httpData && (httpData.info || httpData.msg || httpData.message)) || ''
+ if (res.response.statusCode && res.response.statusCode == 200) {
/*********以下只是模板(及共参考),需要开发者根据各自的接口返回类型修改*********/
- //判断数据是否请求成功
- if (httpData.success || httpData.code == 0) {
+ if (httpData && (httpData.success || httpData.code == 0)) {
// 返回正确的结果(then接受数据)
return Promise.resolve(httpData.data);
- } else if (httpData.code == "401" || httpData.code == "1001" || httpData.code == 1100) {
+ } else if (httpData.code == "401" || httpData.code == "1001" || httpData.code == 1100 || httpData.code == 40100 || httpData.code == 40112) {
// let content = '此时此刻需要您登录喔~';
// if (!uni.getStorageSync('loginPageAlive')) {
// await gotoLogin().then(()=>{
@@ -121,13 +130,13 @@ $http.dataFactory = async function(res) {
// 返回错误的结果(catch接受数据)
return Promise.reject({
statusCode: 0,
- errMsg: "【request】" + (httpData.info || httpData.msg)
+ errMsg: "【request】" + errText
});
} else { //其他错误提示
if (res.isPrompt) {
setTimeout(() => {
uni.showToast({
- title: httpData.info || httpData.msg,
+ title: errText || '请求失败',
icon: "none",
duration: 3000
});
@@ -136,16 +145,25 @@ $http.dataFactory = async function(res) {
// 返回错误的结果(catch接受数据)
return Promise.reject({
statusCode: 0,
- errMsg: "【request】" + (httpData.info || httpData.msg)
+ errMsg: "【request】" + errText
});
}
} else if (res.response.statusCode && res.response.statusCode == 401) {
gotoLogin(res)
} else {
- // 返回错误的结果(catch接受数据)
+ const text = errText || ("HTTP " + res.response.statusCode)
+ if (res.isPrompt) {
+ setTimeout(() => {
+ uni.showToast({
+ title: text,
+ icon: "none",
+ duration: 3000
+ });
+ }, 100)
+ }
return Promise.reject({
- statusCode: res.response.statusCode,
- errMsg: "【request】数据工厂验证不通过"
+ statusCode: 0,
+ errMsg: "【request】" + text
});
}
};
@@ -167,38 +185,17 @@ $http.requestError = function(e) {
//token过期,退出登录
function gotoLogin(res) {
uni.removeStorageSync("token")
+ uni.removeStorageSync("yxg_token")
uni.removeStorageSync("userinfo")
- console.log('res', res)
- // uni.navigateTo({
- // "url": "/pages/login-pop/login-pop?type=1",
- // "animationType": "pop-in"
- // })
- // return new Promise((resolve, rejict)=>{
-
-
-
-
- // // uni.switchTab({
- // // "url":"/pages/login-pop/login-pop",
- // // success:(res)=>{
- // // // uni.showToast({
- // // // title: "登录已失效,请重新登录",
- // // // icon: "none"
- // // // });
- // // uni.showModal({
- // // title: '提示',
- // // content: '登录已失效,是否重新登录',
- // // success: function (res) {
- // // if (res.confirm) {
- // // uni.navigateTo({
- // // "url":"/pageLogin/user-detail/user-detail"
- // // })
- // // }
- // // }
- // // });
- // // }
- // // })
- // resolve()
- // })
+ const pages = typeof getCurrentPages === 'function' ? getCurrentPages() : []
+ const cur = pages.length ? pages[pages.length - 1] : null
+ const route = (cur && (cur.route || cur.__route__)) || ''
+ if (String(route).indexOf('login-pop') !== -1) {
+ return
+ }
+ uni.navigateTo({
+ url: "/pages/login-pop/login-pop?type=2",
+ animationType: "pop-in"
+ })
}
export default $http;
\ No newline at end of file
diff --git a/util/yxgApi.js b/util/yxgApi.js
new file mode 100644
index 0000000..34e6001
--- /dev/null
+++ b/util/yxgApi.js
@@ -0,0 +1,163 @@
+import { YXG_API_BASE, YXG_ASSET_BASE } from './yxgConfig.js'
+import $http from './requestConfig.js'
+
+const TOKEN_KEY = 'token'
+const LEGACY_TOKEN_KEY = 'yxg_token'
+const DEVICE_KEY = 'yxg_device_key'
+
+export function getYxgToken() {
+ return uni.getStorageSync(TOKEN_KEY) || uni.getStorageSync(LEGACY_TOKEN_KEY) || ''
+}
+
+export function setYxgToken(token) {
+ if (token) {
+ uni.setStorageSync(TOKEN_KEY, token)
+ uni.removeStorageSync(LEGACY_TOKEN_KEY)
+ } else {
+ uni.removeStorageSync(TOKEN_KEY)
+ uni.removeStorageSync(LEGACY_TOKEN_KEY)
+ uni.removeStorageSync('userinfo')
+ }
+}
+
+export function getDeviceKey() {
+ return uni.getStorageSync(DEVICE_KEY) || ''
+}
+
+export function setDeviceKey(key) {
+ if (key) uni.setStorageSync(DEVICE_KEY, key)
+}
+
+export function assetURL(path) {
+ const p = String(path || '').trim()
+ if (!p) return ''
+ if (/^https?:\/\//i.test(p)) return p
+ return YXG_ASSET_BASE + (p.startsWith('/') ? p : `/${p}`)
+}
+
+function joinURL(base, path) {
+ if (/^https?:\/\//i.test(path)) return path
+ return `${base.replace(/\/$/, '')}${path.startsWith('/') ? path : `/${path}`}`
+}
+
+function request(method, path, body, extra = {}) {
+ return $http.request({
+ method,
+ url: path,
+ data: body === undefined ? {} : body,
+ isPrompt: extra.isPrompt ?? false,
+ load: extra.load ?? false,
+ timeout: extra.timeout,
+ }).catch((e) => {
+ const raw = e instanceof Error ? e.message : (e && e.errMsg) || '请求失败'
+ throw new Error(String(raw).replace(/^【request】/, ''))
+ })
+}
+
+export const yxgApi = {
+ authRegister: (body) => request('POST', '/api/v1/auth/register', body),
+ authLogin: (body) => request('POST', '/api/v1/auth/login', body),
+ authLogout: () => request('POST', '/api/v1/auth/logout', {}),
+ authMe: () => request('GET', '/api/v1/auth/me'),
+ authUpdateMe: (body) => request('PATCH', '/api/v1/auth/me', body),
+ authUploadAvatar(filePath) {
+ return new Promise((resolve, reject) => {
+ const header = {}
+ const token = getYxgToken()
+ const device = getDeviceKey()
+ if (token) header.Authorization = `Bearer ${token}`
+ if (device) header['X-Device-Key'] = device
+ uni.uploadFile({
+ url: joinURL(YXG_API_BASE, '/api/v1/auth/me/avatar'),
+ filePath,
+ name: 'file',
+ header,
+ success(res) {
+ let data = res.data
+ if (typeof data === 'string') {
+ try {
+ data = JSON.parse(data)
+ } catch {
+ reject(new Error('上传失败'))
+ return
+ }
+ }
+ if (!data || data.code !== 0) {
+ reject(new Error(data?.message || '上传失败'))
+ return
+ }
+ resolve(data.data)
+ },
+ fail(err) {
+ reject(new Error(err.errMsg || '上传失败'))
+ },
+ })
+ })
+ },
+ listProfiles: () => request('GET', '/api/v1/profiles'),
+ createProfile: (body) => request('POST', '/api/v1/profiles', body),
+ updateProfile: (id, body) => request('PATCH', `/api/v1/profiles/${id}`, body),
+ deleteProfile: (id) => request('DELETE', `/api/v1/profiles/${id}`),
+ createPortrait: (profile_id) =>
+ request('POST', '/api/v1/reports/portrait', { profile_id }),
+ createStar: (profile_id) => request('POST', '/api/v1/reports/star', { profile_id }),
+ createSynastry: (profile_id_a, profile_id_b, as_of) =>
+ request('POST', '/api/v1/reports/synastry', {
+ profile_id_a,
+ profile_id_b,
+ ...(as_of ? { as_of } : {}),
+ }),
+ getLatestReport: (profile_id, type, peer_profile_id) => {
+ let q = `profile_id=${encodeURIComponent(profile_id)}&type=${encodeURIComponent(type)}`
+ if (peer_profile_id) q += `&peer_profile_id=${encodeURIComponent(peer_profile_id)}`
+ return request('GET', `/api/v1/reports/latest?${q}`)
+ },
+ listSynastryNearby: (lat, lng, radius_km = 50) =>
+ request('GET', `/api/v1/synastry/nearby?lat=${lat}&lng=${lng}&radius_km=${radius_km}`),
+ createSynastryInvite: (profile_id) =>
+ request('POST', '/api/v1/synastry/invites', { profile_id }),
+ getSynastryInvite: (token) => request('GET', `/api/v1/synastry/invites/${token}`),
+ acceptSynastryInvite: (token, body) =>
+ request('POST', `/api/v1/synastry/invites/${token}/accept`, body),
+ createRhythm: (profile_id) => request('POST', '/api/v1/reports/rhythm', { profile_id }),
+ listImageCardScenes: () => request('GET', '/api/v1/image-cards/scenes'),
+ getImageCardQuota: () => request('GET', '/api/v1/image-cards/quota'),
+ drawImageCard: (body) => request('POST', '/api/v1/image-cards/draw', body),
+ getSolarTermsToday: () => request('GET', '/api/v1/solar-terms/today'),
+ saveMood: (body) => request('POST', '/api/v1/moods', body),
+ getMoodToday: () => request('GET', '/api/v1/moods/today'),
+ getMoodsRecent: () => request('GET', '/api/v1/moods/recent'),
+ getExploreCatalog: () => request('GET', '/api/v1/explore/catalog'),
+ getExploreCategory: (key) => request('GET', `/api/v1/explore/catalog/${key}`),
+ listGrowthPlans: () => request('GET', '/api/v1/growth/plans'),
+ createGrowthPlan: (body) => request('POST', '/api/v1/growth/plans', body),
+ createGrowthCheckin: (planId, body) =>
+ request('POST', `/api/v1/growth/plans/${planId}/checkin`, body || {}),
+ listGrowthCheckins: (planId) => request('GET', `/api/v1/growth/plans/${planId}/checkins`),
+ listReports: () => request('GET', '/api/v1/reports'),
+ getReport: (id) => request('GET', `/api/v1/reports/${id}`),
+ getMembership: () => request('GET', '/api/v1/membership/me'),
+ createOrder: (body) => request('POST', '/api/v1/orders', body),
+ payMock: (orderId) => request('POST', `/api/v1/orders/${orderId}/pay-mock`, {}),
+ createRelationInsight: (profile_a_id, profile_b_id) =>
+ request('POST', '/api/v1/relation/insight', { profile_a_id, profile_b_id }),
+ listScales: () => request('GET', '/api/v1/scales'),
+ getScaleBankCatalog: () => request('GET', '/api/v1/scale-bank/catalog'),
+ getScaleBankCategory: (key) => request('GET', `/api/v1/scale-bank/categories/${key}`),
+ getScale: (slug) => request('GET', `/api/v1/scales/${slug}`),
+ getScaleResult: (slug) => request('GET', `/api/v1/scales/${slug}/result`),
+ submitScale: (slug, profile_id, answers) =>
+ request('POST', `/api/v1/scales/${slug}/result`, { profile_id, answers }),
+ getAskQuota: () => request('GET', '/api/v1/ask/quota'),
+ createAskThread: (body) => request('POST', '/api/v1/ask/threads', body),
+ clearAskThread: (threadId) => request('DELETE', `/api/v1/ask/threads/${threadId}`),
+ listAskMessages: (threadId) => request('GET', `/api/v1/ask/threads/${threadId}/messages`),
+ sendAskMessage: (threadId, content) =>
+ request('POST', `/api/v1/ask/threads/${threadId}/messages`, { content }, { timeout: 60000 }),
+ getHomeTools: () => request('GET', '/api/v1/home/tools'),
+ getHomeBanners: (placement = 'home') =>
+ request('GET', `/api/v1/home/banners?placement=${encodeURIComponent(placement)}`),
+ getHomeFeedSlots: (placement = 'home') =>
+ request('GET', `/api/v1/home/feed-slots?placement=${encodeURIComponent(placement)}`),
+ getHomeDailyTips: () => request('GET', '/api/v1/home/daily-tips'),
+}
diff --git a/util/yxgAuth.js b/util/yxgAuth.js
new file mode 100644
index 0000000..2826995
--- /dev/null
+++ b/util/yxgAuth.js
@@ -0,0 +1,126 @@
+import { yxgApi, getYxgToken, setYxgToken } from './yxgApi.js'
+
+let cachedNick = ''
+
+export function setAccountNickname(v) {
+ cachedNick = String(v || '').trim()
+}
+
+export function selfDisplayName(fallback = '我') {
+ return cachedNick || fallback
+}
+
+export async function loadAccountNickname(force = false) {
+ if (cachedNick && !force) return cachedNick
+ try {
+ const me = await yxgApi.authMe()
+ cachedNick = (me.nickname || '').trim()
+ return cachedNick
+ } catch {
+ return cachedNick
+ }
+}
+
+export function openWechatLogin() {
+ const pages = typeof getCurrentPages === 'function' ? getCurrentPages() : []
+ const cur = pages.length ? pages[pages.length - 1] : null
+ const route = (cur && (cur.route || cur.__route__)) || ''
+ if (String(route).indexOf('login-pop') !== -1) return
+ uni.navigateTo({ url: '/pages/login-pop/login-pop?type=2' })
+}
+
+export async function ensureAccount(redirect) {
+ if (!getYxgToken()) {
+ openWechatLogin()
+ return null
+ }
+ try {
+ return await yxgApi.authMe()
+ } catch {
+ setYxgToken('')
+ openWechatLogin()
+ return null
+ }
+}
+
+export async function findSelfProfile() {
+ const { items } = await yxgApi.listProfiles()
+ return (items || []).find((p) => p.relation === 'self') || null
+}
+
+export async function ensureSelfProfile(input) {
+ await loadAccountNickname()
+ const nick = selfDisplayName('我')
+ const nameIn = (input.display_name || '').trim()
+ const preferred = !nameIn || nameIn === '我' ? nick : nameIn
+ const existing = await findSelfProfile()
+ if (existing) {
+ const keep = existing.display_name?.trim()
+ const display = keep && keep !== '我' ? keep : preferred
+ return yxgApi.updateProfile(existing.id, {
+ display_name: display,
+ birth_date: input.birth_date,
+ birth_time: input.birth_time,
+ birth_place: input.birth_place,
+ })
+ }
+ try {
+ return await yxgApi.createProfile({
+ relation: 'self',
+ birth_date: input.birth_date,
+ display_name: preferred,
+ birth_time: input.birth_time,
+ birth_place: input.birth_place,
+ })
+ } catch (e) {
+ const msg = e instanceof Error ? e.message : ''
+ if (!/already exists|已有|409|self profile/.test(msg)) throw e
+ const again = await findSelfProfile()
+ if (!again) throw e
+ const keep = again.display_name?.trim()
+ return yxgApi.updateProfile(again.id, {
+ display_name: keep && keep !== '我' ? keep : preferred,
+ birth_date: input.birth_date,
+ birth_time: input.birth_time,
+ birth_place: input.birth_place,
+ })
+ }
+}
+
+export async function ensureOtherProfile(input) {
+ const name = (input.display_name || 'TA').trim() || 'TA'
+ const birth = String(input.birth_date || '').slice(0, 10)
+ const { items } = await yxgApi.listProfiles()
+ const hit = (items || []).find(
+ (p) =>
+ p.relation === 'other' &&
+ String(p.birth_date).slice(0, 10) === birth &&
+ (p.display_name || 'TA') === name,
+ )
+ if (hit) return hit
+ return yxgApi.createProfile({
+ relation: 'other',
+ birth_date: birth,
+ display_name: name,
+ relation_type: input.relation_type,
+ birth_place: input.birth_place,
+ })
+}
+
+export function pickableArchiveList(items) {
+ const list = items || []
+ const self = list.find((p) => p.relation === 'self')
+ const others = list.filter((p) => p.relation === 'other')
+ return self ? [self, ...others] : others
+}
+
+export async function loadSelfLatest(type) {
+ const profile = await findSelfProfile()
+ if (!profile) return { profile: null, report: null }
+ try {
+ const report = await yxgApi.getLatestReport(profile.id, type)
+ return { profile, report }
+ } catch {
+ return { profile, report: null }
+ }
+}
diff --git a/util/yxgCatalog.js b/util/yxgCatalog.js
new file mode 100644
index 0000000..8c0431b
--- /dev/null
+++ b/util/yxgCatalog.js
@@ -0,0 +1,139 @@
+export const ICON_NAMES = [
+ 'mbti', 'star', 'portrait', 'rhythm', 'synastry', 'astro',
+ 'companion', 'ask', 'cards', 'reports', 'growth', 'relation',
+ 'nine', 'eq', 'stress', 'sleep',
+]
+
+export function resolveIcon(raw, fallback = 'mbti') {
+ return ICON_NAMES.includes(raw) ? raw : fallback
+}
+
+export function toolIconFor(pathOrKey) {
+ const s = String(pathOrKey || '').toLowerCase()
+ if (s.includes('mbti') || s.includes('人格类型') || s.includes('tests')) return 'mbti'
+ if (s.includes('synastry') || s.includes('合盘')) return 'synastry'
+ if (s.includes('portrait') || s.includes('decode') || s.includes('解码')) return 'portrait'
+ if (s.includes('star') || s.includes('星座') || s.includes('星盘')) return 'star'
+ if (s.includes('rhythm') || s.includes('节律')) return 'rhythm'
+ if (s.includes('companion') || s.includes('节气') || s.includes('mood')) return 'companion'
+ if (s.includes('ask') || s.includes('问答')) return 'ask'
+ if (s.includes('card') || s.includes('意象')) return 'cards'
+ if (s.includes('report') || s.includes('报告')) return 'reports'
+ if (s.includes('growth') || s.includes('计划') || s.includes('会员') || s.includes('member')) return 'growth'
+ if (s.includes('relation') || s.includes('匹配') || s.includes('love')) return 'relation'
+ if (s.includes('astro') || s.includes('星象')) return 'astro'
+ if (s.includes('explore') || s.includes('探索') || s.includes('分类')) return 'mbti'
+ return 'portrait'
+}
+
+export function typeLabel(type) {
+ const map = {
+ portrait: '愈心解码',
+ star: '星座排盘',
+ rhythm: '身心节律',
+ synastry: '合盘',
+ relation: '人格匹配',
+ scale: '测评',
+ }
+ return map[type] || '成长报告'
+}
+
+export function formatDate(iso) {
+ if (!iso) return ''
+ const d = new Date(iso)
+ if (Number.isNaN(d.getTime())) return String(iso).slice(0, 10)
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
+}
+
+export function headlineOf(report) {
+ const s = report?.summary || {}
+ return s.headline || s.one_liner || s.label || s.title || typeLabel(report?.type)
+}
+
+export const homeGridRow1 = [
+ { to: '/scales/mbti-lite', icon: 'mbti', label: 'MBTI测试' },
+ { to: '/star', icon: 'star', label: '星座' },
+ { to: '/portrait', icon: 'portrait', label: '愈心解码', badge: '热', badgeTone: 'hot' },
+ { to: '/rhythm', icon: 'rhythm', label: '身心节律' },
+ { to: '/synastry', icon: 'synastry', label: '合盘', badge: '新', badgeTone: 'new' },
+ { to: '/star', icon: 'astro', label: '星象性格' },
+]
+
+export const homeGridRow2 = [
+ { to: '/companion', icon: 'companion', label: '节气陪伴' },
+ { to: '/ask', icon: 'ask', label: 'AI问答' },
+ { to: '/cards', icon: 'cards', label: '意象卡片' },
+ { to: '/reports', icon: 'reports', label: '成长报告', badge: '新', badgeTone: 'new' },
+ { to: '/growth-plan', icon: 'growth', label: '成长计划' },
+ { to: '/relation', icon: 'relation', label: '人格匹配' },
+]
+
+export const homeFeeds = [
+ { to: '/portrait', icon: 'portrait', title: '愈心解码', meta: '一个生日,读懂性格与身心节奏', stat: '核心入口', tone: 'fc-e', tag: '热' },
+ { to: '/ask', icon: 'ask', title: 'AI 成长助手', meta: '结合档案聊聊卡住的事', stat: '随时可问', tone: 'fc-a', tag: 'AI' },
+ { to: '/star', icon: 'star', title: '星座排盘', meta: '本命盘 · 相位 · 日周月运势', stat: '本周热门', tone: 'fc-b', tag: '新' },
+ { to: '/synastry', icon: 'synastry', title: '合盘', meta: '恋爱 / 友情 / 婚姻指数', stat: '了解彼此', tone: 'fc-c' },
+ { to: '/scales/mbti-lite', icon: 'mbti', title: 'MBTI测试', meta: '四维偏好探索,看见自己的能量与决策节奏', stat: '热门测评', tone: 'fc-a' },
+ { to: '/membership', icon: 'growth', title: '成长会员', meta: '深度报告与全年节气陪伴', stat: '解锁更多', tone: 'fc-e' },
+]
+
+export const mineGroupArchive = [
+ { to: '/profile', label: '个人档案', icon: 'portrait' },
+ { to: '/reports', label: '我的成长报告', icon: 'reports' },
+ { to: '/portrait', label: '愈心解码', icon: 'portrait' },
+ { to: '/star', label: '星象性格', icon: 'star' },
+ { to: '/rhythm', label: '身心节律', icon: 'rhythm' },
+ { to: '/cards', label: '意象卡片', icon: 'cards' },
+]
+
+export const mineGroupGrowth = [
+ { to: '/relation', label: '人格匹配', icon: 'relation' },
+ { to: '/ask', label: 'AI 成长助手', icon: 'ask' },
+ { to: '/explore', label: '探索测试', icon: 'mbti' },
+ { to: '/growth-plan', label: '成长计划', icon: 'growth' },
+ { to: '/membership', label: '成长会员', icon: 'growth' },
+]
+
+const FEED_TONES = ['fc-e', 'fc-a', 'fc-b', 'fc-c', 'fc-a', 'fc-e']
+
+export function mapHomeBannersToFeeds(items) {
+ const sorted = [...(items || [])].sort((a, b) => (a.sort_order ?? 0) - (b.sort_order ?? 0))
+ const out = []
+ for (let i = 0; i < sorted.length; i++) {
+ const it = sorted[i]
+ const to = String(it.link_path || '').trim()
+ if (!to.startsWith('/') || to.includes('://')) continue
+ out.push({
+ to,
+ icon: 'portrait',
+ title: it.title,
+ meta: '运营推荐',
+ stat: '推荐',
+ tone: FEED_TONES[i % FEED_TONES.length],
+ tag: '荐',
+ })
+ }
+ return out
+}
+
+export function mapHomeTools(items) {
+ const toTool = (it) => {
+ if (!ICON_NAMES.includes(it.icon)) return null
+ const t = { to: it.path, icon: it.icon, label: it.label }
+ if (it.badge) t.badge = it.badge
+ if (it.badge_tone === 'hot' || it.badge_tone === 'new') t.badgeTone = it.badge_tone
+ return t
+ }
+ const sorted = [...(items || [])].sort(
+ (a, b) => a.row_index - b.row_index || a.sort_order - b.sort_order,
+ )
+ const row1 = []
+ const row2 = []
+ for (const it of sorted) {
+ const t = toTool(it)
+ if (!t) continue
+ if (it.row_index === 1) row1.push(t)
+ else if (it.row_index === 2) row2.push(t)
+ }
+ return { row1, row2 }
+}
diff --git a/util/yxgConfig.js b/util/yxgConfig.js
index 47631ed..e1b46ca 100644
--- a/util/yxgConfig.js
+++ b/util/yxgConfig.js
@@ -1,16 +1,11 @@
-/** 愈心魔方 H5(digital-psychology user-h5) */
+/** 愈心魔方 + 咨询统一后台(Go)。本地请指向 Go API(:8080),不要指向 Vite。 */
import { LOCAL_H5_HOST, USE_LOCAL_H5 } from './yxgConfig.local.js'
-const PROD_BASE = 'https://h5.yuxingu.com.cn/psy/'
-const PROD_VERSION = '202608283'
-/** 愈心魔方顶栏 logo(小程序包内 static,不请求外网) */
-export const YXG_MP_LOGO_URL = '/static/n-main/yxg-brand-logo.png'
-
+const PROD_API = 'https://h5.yuxingu.com.cn/psy'
const isDev = import.meta.env.DEV
-const useLocalH5 = isDev && USE_LOCAL_H5
+const useLocal = isDev && USE_LOCAL_H5
-export const YXG_H5_VERSION = useLocalH5 ? 'local' : PROD_VERSION
-export const YXG_H5_BASE = useLocalH5 ? `http://${LOCAL_H5_HOST}/psy/` : PROD_BASE
-/** H5 内嵌场景远程 logo(历史 cover 方案遗留,小程序顶栏用 YXG_MP_LOGO_URL) */
-export const YXG_H5_LOGO_URL = 'https://miniapp.yuxingu.com.cn/yxg-mp/logo.png'
-export const YXG_H5_HOME_URL = `${YXG_H5_BASE}?mp=1&v=${YXG_H5_VERSION}`
+export const YXG_API_BASE = useLocal ? `http://${LOCAL_H5_HOST}` : PROD_API
+export const YXG_ASSET_BASE = useLocal ? `http://${LOCAL_H5_HOST}/psy` : PROD_API
+export const YXG_MP_LOGO_URL = '/static/n-main/yxg-brand-logo.png'
+export const YXG_DEFAULT_AVATAR = '/static/n-main/yxg-brand-logo.png'
diff --git a/util/yxgConfig.local.js b/util/yxgConfig.local.js
index 229e2f0..054c47f 100644
--- a/util/yxgConfig.local.js
+++ b/util/yxgConfig.local.js
@@ -1,7 +1,7 @@
/**
- * 本地小程序联调 H5
- * USE_LOCAL_H5=false → 开发编译也访问远程 https://h5.yuxingu.com.cn/psy/(默认)
- * USE_LOCAL_H5=true → 访问本机 Vite,LOCAL_H5_HOST 与 npm run dev:mp 打印一致
+ * 本地小程序联调统一 Go API
+ * USE_LOCAL_H5=false → https://h5.yuxingu.com.cn/psy(现网还没有 /api/v1/psychic,会 404)
+ * USE_LOCAL_H5=true → 本机 Go,LOCAL_H5_HOST 写成 host:8080(不要写 Vite :5173)
*/
export const USE_LOCAL_H5 = false
-export const LOCAL_H5_HOST = '192.168.100.16:5173'
+export const LOCAL_H5_HOST = '127.0.0.1:8080'
diff --git a/util/yxgNav.js b/util/yxgNav.js
new file mode 100644
index 0000000..7c1dfc7
--- /dev/null
+++ b/util/yxgNav.js
@@ -0,0 +1,75 @@
+const HOME = '/pages/yxg-magic/index'
+
+const MAP = [
+ [/^\/explore\/bank\/([^/?#]+)/, (_, c) => `/pages/yxg/explore-bank?category=${encodeURIComponent(c)}`],
+ [/^\/explore\/([^/?#]+)/, (_, c) => `/pages/yxg/explore-category?category=${encodeURIComponent(c)}`],
+ [/^\/explore\/?$/, '/pages/yxg/explore'],
+ [/^\/growth-plan\/?$/, '/pages/yxg/growth-plan'],
+ [/^\/ask\/?$/, '/pages/yxg/ask'],
+ [/^\/companion\/?$/, '/pages/yxg/companion'],
+ [/^\/mine\/?$/, '/pages/yxg/mine'],
+ [/^\/login\/?$/, '/pages/login-pop/login-pop?type=2'],
+ [/^\/profile\/?$/, '/pages/yxg/profile'],
+ [/^\/portrait\/?$/, '/pages/yxg/portrait'],
+ [/^\/star\/?$/, '/pages/yxg/star'],
+ [/^\/synastry\/invite\/([^/?#]+)/, (_, t) => `/pages/yxg/synastry-invite?token=${encodeURIComponent(t)}`],
+ [/^\/synastry\/?$/, '/pages/yxg/synastry'],
+ [/^\/rhythm\/?$/, '/pages/yxg/rhythm'],
+ [/^\/cards\/?$/, '/pages/yxg/cards'],
+ [/^\/relation\/?$/, '/pages/yxg/relation'],
+ [/^\/membership\/?$/, '/pages/yxg/membership'],
+ [/^\/scales\/([^/?#]+)/, (_, s) => `/pages/yxg/scale?slug=${encodeURIComponent(s)}`],
+ [/^\/share\/?$/, '/pages/yxg/share'],
+ [/^\/reports\/([^/?#]+)/, (_, id) => `/pages/yxg/report?id=${encodeURIComponent(id)}`],
+ [/^\/reports\/?$/, '/pages/yxg/reports'],
+ [/^\/decode\/?$/, '/pages/yxg/portrait'],
+ [/^\/?$/, HOME],
+]
+
+function withQuery(url, query) {
+ if (!query) return url
+ const pairs = Object.keys(query)
+ .filter((k) => query[k] !== undefined && query[k] !== null && query[k] !== '')
+ .map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(query[k])}`)
+ if (!pairs.length) return url
+ return url + (url.includes('?') ? '&' : '?') + pairs.join('&')
+}
+
+export function h5ToMp(path, query) {
+ const raw = String(path || '/')
+ const pathname = raw.split('?')[0]
+ for (const [re, dest] of MAP) {
+ const m = pathname.match(re)
+ if (!m) continue
+ const url = typeof dest === 'function' ? dest(...m) : dest
+ return withQuery(url, query)
+ }
+ return HOME
+}
+
+export function yxgGo(path, query) {
+ const url = h5ToMp(path, query)
+ if (url === HOME || url.startsWith(`${HOME}?`)) {
+ uni.switchTab({ url: HOME })
+ return
+ }
+ uni.navigateTo({ url })
+}
+
+export function yxgReplace(path, query) {
+ const url = h5ToMp(path, query)
+ if (url === HOME || url.startsWith(`${HOME}?`)) {
+ uni.switchTab({ url: HOME })
+ return
+ }
+ uni.redirectTo({ url })
+}
+
+export function yxgBack() {
+ const pages = getCurrentPages()
+ if (pages.length > 1) {
+ uni.navigateBack()
+ return
+ }
+ uni.switchTab({ url: HOME })
+}
diff --git a/util/yxgTips.js b/util/yxgTips.js
new file mode 100644
index 0000000..c7367cd
--- /dev/null
+++ b/util/yxgTips.js
@@ -0,0 +1,78 @@
+export function localDayKey(d = new Date()) {
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
+}
+
+export function localShichenIndex(d = new Date()) {
+ return ((d.getHours() + 1) % 24) >> 1
+}
+
+function nextShichenEnd(d = new Date()) {
+ const shi = localShichenIndex(d)
+ const startHour = (shi * 2 + 23) % 24
+ const start = new Date(d)
+ start.setHours(startHour, 0, 0, 0)
+ if (start.getTime() > d.getTime()) start.setDate(start.getDate() - 1)
+ return new Date(start.getTime() + 2 * 3600 * 1000)
+}
+
+export function localFallbackTips() {
+ const n = new Date()
+ const day = Math.floor(Date.UTC(n.getFullYear(), n.getMonth(), n.getDate()) / 86400000)
+ const shi = localShichenIndex(n)
+ const seed = day * 13 + shi * 19
+ const palettes = [
+ [{ name: '米白', hex: '#F5F0E8' }, { name: '雾霾蓝', hex: '#A8C4D8' }],
+ [{ name: '燕麦色', hex: '#E8DCC8' }, { name: '浅杏', hex: '#F0C9A8' }],
+ [{ name: '浅灰', hex: '#D8D6D4' }, { name: '雾粉', hex: '#E8B8C4' }],
+ [{ name: '天青', hex: '#9BB8D4' }, { name: '陶土', hex: '#C4A484' }],
+ ]
+ const clothing = [
+ '轻薄透气更舒服,外套可备一件薄衫应付温差。',
+ '今日宜层搭:薄内搭+透气外衫,方便随时增减。',
+ '选柔软面料贴身,活动一整天也不易紧绷。',
+ '宽松剪裁更自在,适合慢节奏出门与散步。',
+ ]
+ const notes = ['清爽干净,不抢戏。', '今日偏清透,层次更轻。', '温柔耐看,适合日常。', '柔和提气色,不显沉。']
+ const well = [
+ '午后泡一杯温茶,给身心一点缓冲。',
+ '今日做三次深呼吸,拉长呼气更易放松。',
+ '今晚早点放下屏幕,让眼睛歇一会儿。',
+ '走路时把肩膀放松,呼吸会顺很多。',
+ ]
+ const i = seed % palettes.length
+ return {
+ clothingIndex: 55 + (seed % 41),
+ clothing: clothing[i],
+ colorNote: notes[i],
+ palette: palettes[i],
+ wellness: well[i],
+ source: 'fallback',
+ needBirth: true,
+ asOf: localDayKey(n),
+ shichen: shi,
+ validUntil: nextShichenEnd(n).toISOString(),
+ }
+}
+
+export function tipsFromApi(t) {
+ return {
+ clothingIndex: t.clothing_index,
+ clothing: t.clothing,
+ colorNote: t.color_note,
+ palette: (t.palette || []).map((p) => ({ name: p.name, hex: p.hex })),
+ wellness: t.wellness,
+ needBirth: !!t.need_birth,
+ guaName: t.gua_name,
+ source: t.source,
+ asOf: t.as_of || localDayKey(),
+ shichen: t.shichen,
+ shichenName: t.shichen_name,
+ validUntil: t.valid_until,
+ }
+}
+
+export function ringOffset(index) {
+ const c = 2 * Math.PI * 30
+ const pct = Math.min(100, Math.max(0, Number(index) || 0)) / 100
+ return c * (1 - pct)
+}