feat(ECR-045): ImageCardDeck 写面闭环并 Closed
复用 admin.explore.write、POST/PUT+审计、C端 GET /cards/decks、 H5 无 active 空态/失败回退;migration 000054。无牌面内容编辑。 ExploreConfig Loop STOP,禁自动 ECR-046。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -27,6 +27,8 @@ export function useImageCardPage() {
|
||||
const paying = ref(false)
|
||||
const error = ref('')
|
||||
const bootError = ref('')
|
||||
/** Fail-open until proven empty. */
|
||||
const configAvailable = ref(true)
|
||||
const cards = ref<{ id: string; title: string; imagery: string; prompt: string; tip: string }[]>([])
|
||||
const report = ref<GrowthReport | null>(null)
|
||||
const questionInput = ref('')
|
||||
@@ -52,21 +54,38 @@ export function useImageCardPage() {
|
||||
track('cards_scene_selected', { scene: key })
|
||||
}
|
||||
|
||||
async function refreshConfigGate() {
|
||||
try {
|
||||
const res = await api.getCardDecks()
|
||||
configAvailable.value = (res.items || []).length > 0
|
||||
} catch {
|
||||
configAvailable.value = true
|
||||
}
|
||||
}
|
||||
|
||||
function onPromptClick(row: { label: string; scene: string }) {
|
||||
if (!configAvailable.value) {
|
||||
error.value = '意象牌组暂未开放'
|
||||
return
|
||||
}
|
||||
selectScene(row.scene)
|
||||
questionInput.value = row.label
|
||||
if (birthFilled()) {
|
||||
draw()
|
||||
void draw()
|
||||
} else {
|
||||
showBirth.value = true
|
||||
}
|
||||
}
|
||||
|
||||
function sendDraw() {
|
||||
if (!configAvailable.value) {
|
||||
error.value = '意象牌组暂未开放'
|
||||
return
|
||||
}
|
||||
if (questionInput.value.trim()) {
|
||||
scene.value = questionInput.value.trim()
|
||||
}
|
||||
draw()
|
||||
void draw()
|
||||
}
|
||||
|
||||
async function refreshQuota() {
|
||||
@@ -75,6 +94,8 @@ export function useImageCardPage() {
|
||||
|
||||
async function boot() {
|
||||
if (!(await ensureAccount(router, route.fullPath))) return
|
||||
await refreshConfigGate()
|
||||
if (!configAvailable.value) return
|
||||
try {
|
||||
const [sc] = await Promise.all([api.listImageCardScenes(), refreshQuota()])
|
||||
scenes.value = sc.items || []
|
||||
@@ -87,6 +108,10 @@ export function useImageCardPage() {
|
||||
}
|
||||
|
||||
async function draw() {
|
||||
if (!configAvailable.value) {
|
||||
error.value = '意象牌组暂未开放'
|
||||
return
|
||||
}
|
||||
if (!(await ensureAccount(router, route.fullPath))) return
|
||||
error.value = ''
|
||||
const y = Number(year.value)
|
||||
@@ -145,6 +170,7 @@ export function useImageCardPage() {
|
||||
|
||||
return {
|
||||
scene,
|
||||
configAvailable,
|
||||
quotaLabel,
|
||||
year,
|
||||
month,
|
||||
|
||||
@@ -11,41 +11,50 @@
|
||||
</template>
|
||||
|
||||
<div class="body" :class="{ 'has-results': cards.length }">
|
||||
<ImageCardGreeting
|
||||
:boot-error="bootError"
|
||||
:quota-label="quotaLabel"
|
||||
:scene="scene"
|
||||
/>
|
||||
<div v-if="!configAvailable && !cards.length" class="empty-gate">
|
||||
<HomeToolIcon name="cards" :size="56" />
|
||||
<p class="empty-title">意象牌组暂未开放</p>
|
||||
<p class="empty-sub">运营尚未启用意象牌组,请稍后再来</p>
|
||||
<button class="cta-off" type="button" disabled>抽取意象卡</button>
|
||||
</div>
|
||||
|
||||
<ImageCardLanding
|
||||
v-if="!cards.length"
|
||||
v-model:want-depth="wantDepth"
|
||||
v-model:show-birth="showBirth"
|
||||
v-model:year="year"
|
||||
v-model:month="month"
|
||||
v-model:day="day"
|
||||
:drawing="drawing"
|
||||
@prompt-click="onPromptClick"
|
||||
@draw="draw"
|
||||
/>
|
||||
<template v-else>
|
||||
<ImageCardGreeting
|
||||
:boot-error="bootError"
|
||||
:quota-label="quotaLabel"
|
||||
:scene="scene"
|
||||
/>
|
||||
|
||||
<ImageCardResults
|
||||
v-if="cards.length"
|
||||
:cards="cards"
|
||||
:report="report"
|
||||
:summary="summary"
|
||||
:detail="detail"
|
||||
:want-depth="wantDepth"
|
||||
:paying="paying"
|
||||
@buy-deep="buyDeep"
|
||||
/>
|
||||
<ImageCardLanding
|
||||
v-if="!cards.length"
|
||||
v-model:want-depth="wantDepth"
|
||||
v-model:show-birth="showBirth"
|
||||
v-model:year="year"
|
||||
v-model:month="month"
|
||||
v-model:day="day"
|
||||
:drawing="drawing"
|
||||
@prompt-click="onPromptClick"
|
||||
@draw="draw"
|
||||
/>
|
||||
|
||||
<ImageCardResults
|
||||
v-if="cards.length"
|
||||
:cards="cards"
|
||||
:report="report"
|
||||
:summary="summary"
|
||||
:detail="detail"
|
||||
:want-depth="wantDepth"
|
||||
:paying="paying"
|
||||
@buy-deep="buyDeep"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<p class="yxg-disc ai-note">意象卡片用于投射与自我反思,不判定好坏,也不预测未来。部分内容由 AI 生成,仅供参考。</p>
|
||||
<p v-if="error" class="yxg-err err-float">{{ error }}</p>
|
||||
</div>
|
||||
|
||||
<ImageCardInputBar
|
||||
v-if="!cards.length"
|
||||
v-if="configAvailable && !cards.length"
|
||||
v-model:question-input="questionInput"
|
||||
:drawing="drawing"
|
||||
@send="sendDraw"
|
||||
@@ -64,6 +73,7 @@ import { useImageCardPage } from '../composables/useImageCardPage'
|
||||
|
||||
const {
|
||||
scene,
|
||||
configAvailable,
|
||||
quotaLabel,
|
||||
year,
|
||||
month,
|
||||
@@ -119,4 +129,35 @@ const {
|
||||
.err-float {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.empty-gate {
|
||||
text-align: center;
|
||||
padding: 28px 16px 12px;
|
||||
}
|
||||
.empty-title {
|
||||
font-family: var(--font-display);
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
margin-top: 10px;
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
.empty-sub {
|
||||
font-size: 13px;
|
||||
color: rgba(0, 0, 0, 0.42);
|
||||
margin-top: 8px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.cta-off {
|
||||
margin-top: 16px;
|
||||
width: 100%;
|
||||
max-width: 320px;
|
||||
padding: 13px;
|
||||
border: none;
|
||||
border-radius: 22px;
|
||||
color: #fff;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
background: linear-gradient(135deg, #ff7a6e, var(--color-primary));
|
||||
opacity: 0.45;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user