feat: refine topic insights and learner experience
This commit is contained in:
@@ -12,12 +12,12 @@ import {
|
||||
Sparkles,
|
||||
Trash2,
|
||||
} from "@lucide/vue";
|
||||
import { computed, ref } from "vue";
|
||||
import { computed, ref, watch } from "vue";
|
||||
|
||||
import type { PeriodicReport, PracticeReviewResult, ShareDraft, TeacherHelpCard, UserProfile } from "../types/api";
|
||||
import AppDialog from "./AppDialog.vue";
|
||||
|
||||
type CenterSection = "overview" | "review" | "records";
|
||||
type CenterSection = "overview" | "review" | "reports" | "records";
|
||||
type CardSection = "help" | "share";
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -44,6 +44,11 @@ const activeSection = ref<CenterSection>(props.initialSection);
|
||||
const activeCardSection = ref<CardSection>("help");
|
||||
const deletingCard = ref<{ type: CardSection; id: number } | null>(null);
|
||||
const entitlement = computed(() => props.user.entitlement);
|
||||
const canReview = computed(() => Boolean(entitlement.value?.enableGrowthProfile));
|
||||
const canReports = computed(() => Boolean(entitlement.value?.enablePeriodicReports));
|
||||
const canHelpCards = computed(() => Boolean(entitlement.value?.allowHelpCard));
|
||||
const canShareDrafts = computed(() => Boolean(entitlement.value?.allowShareDraft));
|
||||
const canCards = computed(() => canHelpCards.value || canShareDrafts.value);
|
||||
const displayName = computed(() => props.user.nickname?.trim() || props.user.name);
|
||||
const nameInitial = computed(() => displayName.value.slice(0, 1));
|
||||
const maskedPhone = computed(() => props.user.phone.replace(/^(\d{3})\d{4}(\d{4})$/, "$1****$2"));
|
||||
@@ -62,6 +67,37 @@ const capabilities = computed(() => [
|
||||
{ label: "班级分享稿", enabled: Boolean(entitlement.value?.allowShareDraft) },
|
||||
]);
|
||||
|
||||
watch(
|
||||
[() => props.initialSection, canReview, canReports, canCards],
|
||||
([initialSection]) => {
|
||||
const requested = initialSection as CenterSection;
|
||||
const available = requested === "overview"
|
||||
|| (requested === "review" && canReview.value)
|
||||
|| (requested === "reports" && canReports.value)
|
||||
|| (requested === "records" && canCards.value);
|
||||
if (!available || activeSection.value === "review" && !canReview.value
|
||||
|| activeSection.value === "reports" && !canReports.value
|
||||
|| activeSection.value === "records" && !canCards.value) {
|
||||
activeSection.value = "overview";
|
||||
} else {
|
||||
activeSection.value = requested;
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
[canHelpCards, canShareDrafts],
|
||||
() => {
|
||||
if (activeCardSection.value === "help" && !canHelpCards.value && canShareDrafts.value) {
|
||||
activeCardSection.value = "share";
|
||||
} else if (activeCardSection.value === "share" && !canShareDrafts.value && canHelpCards.value) {
|
||||
activeCardSection.value = "help";
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
function usagePercent(used: number, limit: number | null) {
|
||||
if (limit === null || limit <= 0) return 0;
|
||||
return Math.min(100, Math.max(0, Math.round((used / limit) * 100)));
|
||||
@@ -112,8 +148,9 @@ function submitDeleteCard() {
|
||||
|
||||
<nav class="center-tabs" role="tablist" aria-label="个人中心栏目">
|
||||
<button type="button" role="tab" :aria-selected="activeSection === 'overview'" @click="activeSection = 'overview'">账号权益</button>
|
||||
<button type="button" role="tab" :aria-selected="activeSection === 'review'" @click="activeSection = 'review'">实修回顾</button>
|
||||
<button type="button" role="tab" :aria-selected="activeSection === 'records'" @click="activeSection = 'records'">我的卡片</button>
|
||||
<button v-if="canReview" type="button" role="tab" :aria-selected="activeSection === 'review'" @click="activeSection = 'review'">实修回顾</button>
|
||||
<button v-if="canReports" type="button" role="tab" :aria-selected="activeSection === 'reports'" @click="activeSection = 'reports'">周期报告</button>
|
||||
<button v-if="canCards" type="button" role="tab" :aria-selected="activeSection === 'records'" @click="activeSection = 'records'">我的卡片</button>
|
||||
</nav>
|
||||
|
||||
<div v-if="activeSection === 'overview'" class="center-section">
|
||||
@@ -207,8 +244,19 @@ function submitDeleteCard() {
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section v-if="reports.length" class="record-group">
|
||||
<h3>周期实修回顾</h3>
|
||||
</div>
|
||||
|
||||
<div v-else-if="activeSection === 'reports'" class="center-section reports-section">
|
||||
<header class="card-history-head">
|
||||
<div>
|
||||
<h3>周期实修报告</h3>
|
||||
<p>系统会根据已经沉淀的主题,自动整理完整自然周和自然月的实修回顾。</p>
|
||||
</div>
|
||||
<span>{{ reports.length }} 份</span>
|
||||
</header>
|
||||
<p v-if="loading" class="center-empty">正在加载周期报告...</p>
|
||||
<p v-else-if="!entitlement?.enablePeriodicReports" class="center-empty">当前权益未包含周期报告,已有聊天和主题回顾不会受到影响。</p>
|
||||
<section v-else-if="reports.length" class="record-group report-history-list">
|
||||
<details v-for="item in reports" :key="item.id" class="report-record">
|
||||
<summary>
|
||||
<CalendarDays :size="16" aria-hidden="true" />
|
||||
@@ -219,11 +267,13 @@ function submitDeleteCard() {
|
||||
<pre>{{ item.content }}</pre>
|
||||
</details>
|
||||
</section>
|
||||
<p v-else class="center-empty">暂时还没有周期报告。完成主题沉淀后,系统会在完整自然周或自然月结束后自动生成,并展示在这里。</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="center-section records-section">
|
||||
<nav class="card-type-tabs" role="tablist" aria-label="我的卡片类型">
|
||||
<button
|
||||
v-if="canHelpCards"
|
||||
type="button"
|
||||
role="tab"
|
||||
:aria-selected="activeCardSection === 'help'"
|
||||
@@ -234,6 +284,7 @@ function submitDeleteCard() {
|
||||
<small>{{ helpCards.length }}</small>
|
||||
</button>
|
||||
<button
|
||||
v-if="canShareDrafts"
|
||||
type="button"
|
||||
role="tab"
|
||||
:aria-selected="activeCardSection === 'share'"
|
||||
@@ -364,7 +415,8 @@ function submitDeleteCard() {
|
||||
.member-copy strong { overflow: hidden; font-size: 16px; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.member-copy span { color: var(--chat-muted); font-size: 12px; }
|
||||
.member-plan { max-width: 125px; overflow: hidden; padding: 6px 9px; border-radius: 999px; background: var(--chat-brand-soft); color: #2f6d59; font-size: 11px; font-weight: 700; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.center-tabs { display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px; padding: 4px; border-radius: 13px; background: #f1f5f3; }
|
||||
.center-tabs { display: flex; gap: 4px; padding: 4px; border-radius: 13px; background: #f1f5f3; }
|
||||
.center-tabs button { flex: 1 1 0; min-width: 0; }
|
||||
.center-tabs button { min-height: 36px; padding: 0 8px; border: 0; border-radius: 10px; background: transparent; color: var(--chat-muted); font-size: 13px; font-weight: 650; white-space: nowrap; }
|
||||
.center-tabs button[aria-selected="true"] { background: #fff; color: var(--chat-brand-dark); box-shadow: 0 3px 12px rgba(27, 68, 55, 0.08); }
|
||||
.center-section { display: grid; gap: 15px; }
|
||||
@@ -408,6 +460,8 @@ function submitDeleteCard() {
|
||||
.report-record summary small { color: var(--chat-weak); font-size: 10px; }
|
||||
.report-record > time { display: block; padding: 0 12px 6px; }
|
||||
.report-record pre { max-height: 260px; overflow: auto; margin: 0; padding: 11px 12px; border-top: 1px solid var(--chat-border); background: #fbfcfc; color: var(--chat-muted); font: inherit; font-size: 12px; line-height: 1.7; white-space: pre-wrap; }
|
||||
.reports-section { align-content: start; }
|
||||
.report-history-list { gap: 8px; }
|
||||
.records-section { align-content: start; }
|
||||
.card-type-tabs { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; padding: 2px 0 4px; background: #fff; }
|
||||
.card-type-tabs button { min-width: 0; min-height: 52px; display: grid; grid-template-columns: 20px minmax(0, 1fr) auto; align-items: center; gap: 7px; padding: 0 11px; border: 1px solid var(--chat-border); border-radius: 13px; background: #fff; color: var(--chat-muted); font-size: 12px; font-weight: 700; text-align: left; }
|
||||
|
||||
Reference in New Issue
Block a user