feat(ECR-010): Ops-E 系统运营;修复登出解绑;P2 Complete
ci / h5 (push) Canceled after 0s
ci / api (push) Canceled after 0s
ci / ess-docs (push) Canceled after 0s

落地管理员 RBAC/封禁/推送任务 stub,logout 解绑 device 并统一各页 ensureAccount,同时收口 P2 生日生成与状态文档。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jackyu66git
2026-08-11 18:54:59 +08:00
co-authored by Cursor
parent 0ee4d4f5ae
commit 5ceb3ce749
85 changed files with 2098 additions and 103 deletions
+7 -1
View File
@@ -1,7 +1,8 @@
import { onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { useRoute, useRouter } from 'vue-router'
import type { AskMessage, AskQuota, Profile } from '@yuxingu/types'
import { api } from '../api/client'
import { ensureAccount } from '../lib/authSession'
export const askScenes = [
{ key: 'self', label: '我想更了解自己的性格与互动风格', prompt: '我想更了解自己的性格与互动风格。' },
@@ -25,6 +26,7 @@ export const askAdvisors = [
export function useAskPage() {
const router = useRouter()
const route = useRoute()
const rail = ref<'ai' | 'advisor'>('ai')
const bootLoading = ref(true)
@@ -43,6 +45,10 @@ export function useAskPage() {
async function boot() {
bootLoading.value = true
bootError.value = ''
if (!(await ensureAccount(router, route.fullPath))) {
bootLoading.value = false
return
}
try {
const [list, q] = await Promise.all([api.listProfiles(), api.getAskQuota()])
profiles.value = list.items || []
@@ -1,8 +1,10 @@
import { computed, onMounted, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import type { GrowthReport } from '@yuxingu/types'
import { validateBirth } from '@yuxingu/utils'
import { api } from '../api/client'
import { AnalyticsEvent, track } from '../lib/analytics'
import { ensureAccount } from '../lib/authSession'
export const promptRows = [
{ icon: '🌊', label: '最近情绪有点乱,帮我理一理', scene: '情绪整理' },
@@ -12,6 +14,8 @@ export const promptRows = [
]
export function useImageCardPage() {
const router = useRouter()
const route = useRoute()
const scenes = ref<{ key: string; label: string }[]>([])
const scene = ref('情绪整理')
const quota = ref<{ remaining: number; daily_free: number; unlimited: boolean } | null>(null)
@@ -70,6 +74,7 @@ export function useImageCardPage() {
}
async function boot() {
if (!(await ensureAccount(router, route.fullPath))) return
try {
const [sc] = await Promise.all([api.listImageCardScenes(), refreshQuota()])
scenes.value = sc.items || []
@@ -82,6 +87,7 @@ export function useImageCardPage() {
}
async function draw() {
if (!(await ensureAccount(router, route.fullPath))) return
error.value = ''
const y = Number(year.value)
const m = Number(month.value)
@@ -70,7 +70,7 @@ export function useLifeRhythmPage() {
try {
const birth = `${y}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`
const profile = await api.createProfile({ relation: 'self', birth_date: birth, display_name: '我' })
report.value = await api.getLatestReport(profile.id, 'rhythm')
report.value = await api.createRhythm(profile.id)
tab.value = 'overview'
track(AnalyticsEvent.PortraitCompleted, { source: 'rhythm' })
} catch (e) {
@@ -46,7 +46,7 @@ export function usePortraitPage() {
try {
const birth = `${y}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`
const profile = await api.createProfile({ relation: 'self', birth_date: birth, display_name: '我' })
report.value = await api.getLatestReport(profile.id, 'portrait')
report.value = await api.createPortrait(profile.id)
track(AnalyticsEvent.PortraitCompleted, { source: 'form' })
} catch (e) {
error.value = e instanceof Error ? e.message : '加载失败'
@@ -1,7 +1,9 @@
import { computed, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import type { GrowthReport, Profile } from '@yuxingu/types'
import { api } from '../api/client'
import { AnalyticsEvent, track } from '../lib/analytics'
import { ensureAccount } from '../lib/authSession'
import type { RelationSharePayload } from '../lib/shareLink'
export const focusTabs = [
@@ -24,6 +26,8 @@ export const resultTabs = [
]
export function useRelationPage() {
const router = useRouter()
const route = useRoute()
const otherName = ref('TA')
const relationType = ref('partner')
const oy = ref('1992')
@@ -97,6 +101,7 @@ export function useRelationPage() {
}
async function run() {
if (!(await ensureAccount(router, route.fullPath))) return
loading.value = true
error.value = ''
needSelf.value = false
@@ -1,7 +1,8 @@
import { computed, onMounted, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import { useRoute, useRouter } from 'vue-router'
import type { GrowthReport } from '@yuxingu/types'
import { api } from '../api/client'
import { ensureAccount } from '../lib/authSession'
import type { WheelAspect, WheelPlanet } from '../components/NatalWheel.vue'
import { AnalyticsEvent, track } from '../lib/analytics'
import {
@@ -33,6 +34,7 @@ export { reportStarPanels, reportSynTabs, reportFortuneKeys }
export function useReportPage() {
const route = useRoute()
const router = useRouter()
const loading = ref(true)
const error = ref('')
const report = ref<GrowthReport | null>(null)
@@ -91,6 +93,10 @@ export function useReportPage() {
loading.value = true
error.value = ''
report.value = null
if (!(await ensureAccount(router, route.fullPath))) {
loading.value = false
return
}
try {
const id = String(route.params.id || '')
if (!id) throw new Error('缺少报告 id')
+7 -1
View File
@@ -1,11 +1,13 @@
import { computed, onMounted, reactive, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import { useRoute, useRouter } from 'vue-router'
import { api } from '../api/client'
import { ensureAccount } from '../lib/authSession'
import { clearScaleDraft, loadScaleDraft, saveScaleDraft } from '../lib/scaleDraft'
import type { PortraitSharePayload } from '../lib/shareLink'
export function useScalePage() {
const route = useRoute()
const router = useRouter()
const slug = String(route.params.slug || '')
const title = ref('')
const description = ref('')
@@ -100,6 +102,10 @@ export function useScalePage() {
loadingScale.value = true
loadError.value = ''
needProfile.value = false
if (!(await ensureAccount(router, route.fullPath))) {
loadingScale.value = false
return
}
try {
const d = await api.getScale(slug)
title.value = d.title
@@ -118,7 +118,7 @@ export function useStarProfilePage() {
birth_time: bt,
birth_place: birthPlace.value || undefined,
})
report.value = await api.getLatestReport(profile.id, 'star')
report.value = await api.createStar(profile.id)
view.value = 'overview'
track(AnalyticsEvent.PortraitCompleted, { source: 'star' })
} catch (e) {
@@ -18,6 +18,7 @@ import {
sectionsFromDetail,
type SynastryMainTab,
} from '../lib/synastryChart'
import { ensureAccount } from '../lib/authSession'
import { createSynastryPageActions } from './synastryPageActions'
export type MainTab = SynastryMainTab
@@ -135,6 +136,7 @@ export function useSynastryPage() {
})
onMounted(async () => {
if (!(await ensureAccount(router, route.fullPath))) return
await loadProfiles()
if (route.query.invite === '1') {
inviteHighlight.value = true