feat(ECR-012–016): 合规、题库、时辰刷新、头像、MBTI OEJTS 与埋点
落地输入合规、探索题库、报告日/时辰刷新、账号头像、OEJTS 量表,并补齐 H5 埋点与 Admin 漏斗;同步 ESS 工件、切至自建 Git、清理 GitHub Actions。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -10,21 +10,45 @@
|
||||
|
||||
<div v-if="currentQuestion" class="q-card yxg-card">
|
||||
<p class="prompt">{{ currentQuestion.body.prompt }}</p>
|
||||
<label
|
||||
v-for="opt in currentQuestion.body.options"
|
||||
:key="opt.key"
|
||||
class="opt"
|
||||
:class="{ picked: answers[currentQuestion.id] === opt.key }"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
:name="`q-${currentQuestion.id}`"
|
||||
:value="opt.key"
|
||||
:checked="answers[currentQuestion.id] === opt.key"
|
||||
@change="onPick(opt.key)"
|
||||
/>
|
||||
<span class="opt-text">{{ opt.text }}</span>
|
||||
</label>
|
||||
|
||||
<template v-if="isLikert">
|
||||
<div class="poles">
|
||||
<span class="pole left">{{ currentQuestion.body.left }}</span>
|
||||
<span class="pole right">{{ currentQuestion.body.right }}</span>
|
||||
</div>
|
||||
<div class="likert">
|
||||
<button
|
||||
v-for="opt in currentQuestion.body.options"
|
||||
:key="opt.key"
|
||||
type="button"
|
||||
class="likert-btn"
|
||||
:class="{ picked: answers[currentQuestion.id] === opt.key }"
|
||||
:aria-label="opt.text"
|
||||
@click="onPick(opt.key)"
|
||||
>
|
||||
{{ opt.key }}
|
||||
</button>
|
||||
</div>
|
||||
<p class="likert-hint">1 更接近左侧 · 5 更接近右侧</p>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<label
|
||||
v-for="opt in currentQuestion.body.options"
|
||||
:key="opt.key"
|
||||
class="opt"
|
||||
:class="{ picked: answers[currentQuestion.id] === opt.key }"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
:name="`q-${currentQuestion.id}`"
|
||||
:value="opt.key"
|
||||
:checked="answers[currentQuestion.id] === opt.key"
|
||||
@change="onPick(opt.key)"
|
||||
/>
|
||||
<span class="opt-text">{{ opt.text }}</span>
|
||||
</label>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<p v-if="submitError" class="yxg-err">{{ submitError }}</p>
|
||||
@@ -36,12 +60,23 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
draftRestored: boolean
|
||||
currentIdx: number
|
||||
progressPct: number
|
||||
totalQuestions: number
|
||||
currentQuestion: { id: string; body: { prompt: string; options: { key: string; text: string }[] } } | null
|
||||
currentQuestion: {
|
||||
id: string
|
||||
body: {
|
||||
prompt: string
|
||||
format?: string
|
||||
left?: string
|
||||
right?: string
|
||||
options: { key: string; text: string }[]
|
||||
}
|
||||
} | null
|
||||
answers: Record<string, string>
|
||||
submitError: string
|
||||
needProfile: boolean
|
||||
@@ -51,6 +86,8 @@ const emit = defineEmits<{
|
||||
answer: [questionId: string, key: string]
|
||||
}>()
|
||||
|
||||
const isLikert = computed(() => props.currentQuestion?.body.format === 'likert5')
|
||||
|
||||
function onPick(key: string) {
|
||||
if (!props.currentQuestion) return
|
||||
emit('answer', props.currentQuestion.id, key)
|
||||
@@ -97,6 +134,49 @@ function onPick(key: string) {
|
||||
margin-bottom: 16px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.poles {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.pole {
|
||||
flex: 1;
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
color: #5a4a44;
|
||||
font-weight: 600;
|
||||
}
|
||||
.pole.right {
|
||||
text-align: right;
|
||||
}
|
||||
.likert {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
.likert-btn {
|
||||
flex: 1;
|
||||
height: 44px;
|
||||
border-radius: 12px;
|
||||
border: 1.5px solid #eee;
|
||||
background: #fdfaf8;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
cursor: pointer;
|
||||
}
|
||||
.likert-btn.picked {
|
||||
border-color: var(--color-primary);
|
||||
background: #fff5f3;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
.likert-hint {
|
||||
margin: 10px 0 0;
|
||||
font-size: 11px;
|
||||
color: var(--color-text-tertiary);
|
||||
text-align: center;
|
||||
}
|
||||
.opt {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
|
||||
@@ -4,9 +4,31 @@
|
||||
<HomeToolIcon name="mbti" :size="64" />
|
||||
<h2 class="intro-title">{{ title || '探索测试' }}</h2>
|
||||
<p v-if="description" class="intro-desc">{{ description }}</p>
|
||||
<p v-if="questionCount" class="intro-count">共 {{ questionCount }} 题 · 约 {{ estMinutes }} 分钟</p>
|
||||
<p v-if="questionCount && !showMbtiVersions" class="intro-count">
|
||||
共 {{ questionCount }} 题 · 约 {{ estMinutes }} 分钟
|
||||
</p>
|
||||
<p v-if="draftRestored" class="draft-hint">检测到上次未完成的作答,开始后将继续</p>
|
||||
<button class="yxg-btn yxg-btn-block intro-start" type="button" :disabled="!questionCount" @click="$emit('start')">
|
||||
|
||||
<template v-if="showMbtiVersions">
|
||||
<button class="yxg-btn yxg-btn-block intro-start" type="button" :disabled="!questionCount" @click="$emit('start')">
|
||||
标准版 · 32 题免费
|
||||
</button>
|
||||
<button class="yxg-btn yxg-btn-ghost yxg-btn-block intro-full" type="button" @click="$emit('start-full')">
|
||||
完整版 · 60 题(成长会员)
|
||||
</button>
|
||||
<p class="credit">题库基于开源 OEJTS · 非官方 MBTI 版权量表</p>
|
||||
</template>
|
||||
<template v-else-if="locked">
|
||||
<p class="lock-hint">完整版需开通成长会员后作答</p>
|
||||
<router-link class="yxg-btn yxg-btn-block intro-start" to="/membership">去开通会员</router-link>
|
||||
</template>
|
||||
<button
|
||||
v-else
|
||||
class="yxg-btn yxg-btn-block intro-start"
|
||||
type="button"
|
||||
:disabled="!questionCount"
|
||||
@click="$emit('start')"
|
||||
>
|
||||
开始
|
||||
</button>
|
||||
</div>
|
||||
@@ -22,9 +44,11 @@ defineProps<{
|
||||
questionCount: number
|
||||
estMinutes: number
|
||||
draftRestored: boolean
|
||||
showMbtiVersions?: boolean
|
||||
locked?: boolean
|
||||
}>()
|
||||
|
||||
defineEmits<{ start: [] }>()
|
||||
defineEmits<{ start: []; 'start-full': [] }>()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -61,7 +85,8 @@ defineEmits<{ start: [] }>()
|
||||
color: var(--color-text-tertiary);
|
||||
margin: 0 0 20px;
|
||||
}
|
||||
.draft-hint {
|
||||
.draft-hint,
|
||||
.lock-hint {
|
||||
font-size: 12px;
|
||||
color: var(--color-accent-gold);
|
||||
margin: 0 0 12px;
|
||||
@@ -73,4 +98,14 @@ defineEmits<{ start: [] }>()
|
||||
background: linear-gradient(135deg, #ff7a6e, var(--color-primary));
|
||||
box-shadow: 0 6px 18px rgba(229, 77, 66, 0.28);
|
||||
}
|
||||
.intro-full {
|
||||
margin-top: 10px;
|
||||
font-weight: 650;
|
||||
}
|
||||
.credit {
|
||||
margin: 14px 0 0;
|
||||
font-size: 11px;
|
||||
color: var(--color-text-tertiary);
|
||||
line-height: 1.4;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,23 +5,38 @@
|
||||
<div class="yxg-card">
|
||||
<p v-if="shareLine" class="share">{{ shareLine }}</p>
|
||||
<button type="button" class="yxg-btn yxg-btn-ghost" @click="$emit('share')">生成分享卡</button>
|
||||
<router-link class="yxg-link-plain link" to="/relation">用结果去做关系理解 →</router-link>
|
||||
<router-link class="yxg-link-plain link" to="/ask">去问答聊聊 →</router-link>
|
||||
<button type="button" class="yxg-btn retake" @click="$emit('retake')">重新测试</button>
|
||||
<router-link class="yxg-link-plain link" to="/relation" @click="onCtaRelation">
|
||||
用结果去做关系理解 →
|
||||
</router-link>
|
||||
<router-link class="yxg-link-plain link" to="/ask" @click="onCtaAsk">
|
||||
去问答聊聊 →
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ReportRich from '../ReportRich.vue'
|
||||
import { AnalyticsEvent, track } from '../../lib/analytics'
|
||||
|
||||
defineProps<{
|
||||
const props = defineProps<{
|
||||
slug: string
|
||||
resultLabel: string
|
||||
shareLine: string
|
||||
resultSummaryObj: Record<string, unknown>
|
||||
resultDetailObj: Record<string, unknown>
|
||||
}>()
|
||||
|
||||
defineEmits<{ share: [] }>()
|
||||
defineEmits<{ share: []; retake: [] }>()
|
||||
|
||||
function onCtaRelation() {
|
||||
track(AnalyticsEvent.ScaleCtaRelation, { slug: props.slug })
|
||||
}
|
||||
|
||||
function onCtaAsk() {
|
||||
track(AnalyticsEvent.ScaleCtaAsk, { slug: props.slug })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -37,6 +52,11 @@ defineEmits<{ share: [] }>()
|
||||
margin: 0 0 10px;
|
||||
color: var(--yxg-gold);
|
||||
}
|
||||
.retake {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.link {
|
||||
display: block;
|
||||
margin-top: 12px;
|
||||
|
||||
Reference in New Issue
Block a user