refactor: AppStack推广到全部页面根(方向/间距由页面传入)
This commit is contained in:
parent
bf5e826bbe
commit
ee2991b4b7
@ -7,6 +7,7 @@ import { useProfileStore } from '../../stores/profile'
|
||||
import { formatDateShort, formatMinutes, greetingByHour } from '../../utils/format'
|
||||
import AppCard from '../../components/base/AppCard.vue'
|
||||
import AppLoading from '../../components/feedback/AppLoading.vue'
|
||||
import AppStack from '../../components/base/AppStack.vue'
|
||||
import AppError from '../../components/feedback/AppError.vue'
|
||||
import AppEmpty from '../../components/feedback/AppEmpty.vue'
|
||||
import AppBadge from '../../components/base/AppBadge.vue'
|
||||
@ -73,7 +74,7 @@ const mockDelta = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="dashboard">
|
||||
<AppStack class="dashboard">
|
||||
<!-- 加载 / 错误 / 全局空态 -->
|
||||
<AppLoading v-if="overview.loading.value" fullscreen />
|
||||
<AppError
|
||||
@ -233,15 +234,10 @@ const mockDelta = computed(() => {
|
||||
<WeakPointsCard :weak-points="weakItems" compact />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AppStack>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.dashboard {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-5);
|
||||
}
|
||||
.num {
|
||||
font-family: var(--font-num);
|
||||
font-weight: 700;
|
||||
|
||||
@ -9,6 +9,7 @@ import AppButton from '../../components/base/AppButton.vue'
|
||||
import AppIcon from '../../components/base/AppIcon.vue'
|
||||
import AppPageHeader from '../../components/base/AppPageHeader.vue'
|
||||
import AppLoading from '../../components/feedback/AppLoading.vue'
|
||||
import AppStack from '../../components/base/AppStack.vue'
|
||||
import AppError from '../../components/feedback/AppError.vue'
|
||||
|
||||
const route = useRoute()
|
||||
@ -129,7 +130,7 @@ watch(
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mock-form">
|
||||
<AppStack class="mock-form">
|
||||
<AppPageHeader :title="title" subtitle="仅支持行测五个模块分数,总分自动汇总" sticky-mobile>
|
||||
<AppButton variant="ghost" @click="back">
|
||||
<AppIcon name="chevron-left" :size="16" />返回模考分析
|
||||
@ -211,15 +212,10 @@ watch(
|
||||
</AppButton>
|
||||
</AppCard>
|
||||
</template>
|
||||
</div>
|
||||
</AppStack>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.mock-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-5);
|
||||
}
|
||||
.mock-form__card {
|
||||
max-width: 640px;
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import { useRequest } from '../../composables/useRequest'
|
||||
import { renderMarkdown } from '../../utils/markdown'
|
||||
import { formatDateShort } from '../../utils/format'
|
||||
import AppLoading from '../../components/feedback/AppLoading.vue'
|
||||
import AppStack from '../../components/base/AppStack.vue'
|
||||
import AppError from '../../components/feedback/AppError.vue'
|
||||
import AppEmpty from '../../components/feedback/AppEmpty.vue'
|
||||
import AppPageHeader from '../../components/base/AppPageHeader.vue'
|
||||
@ -19,7 +20,7 @@ const html = computed(() => renderMarkdown(detail.data.value?.content ?? ''))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="news-detail">
|
||||
<AppStack class="news-detail" gap="0">
|
||||
<AppLoading v-if="detail.loading.value" fullscreen />
|
||||
<AppError
|
||||
v-else-if="detail.error.value"
|
||||
@ -49,14 +50,10 @@ const html = computed(() => renderMarkdown(detail.data.value?.content ?? ''))
|
||||
</template>
|
||||
|
||||
<AppEmpty v-else icon="news" title="该要闻不存在" description="可能已被删除或链接有误。" />
|
||||
</div>
|
||||
</AppStack>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.news-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.news-detail__head {
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import { formatDateShort, formatMinutes, weekdayName } from '../../utils/format'
|
||||
import AppCard from '../../components/base/AppCard.vue'
|
||||
import AppPageHeader from '../../components/base/AppPageHeader.vue'
|
||||
import AppLoading from '../../components/feedback/AppLoading.vue'
|
||||
import AppStack from '../../components/base/AppStack.vue'
|
||||
import AppError from '../../components/feedback/AppError.vue'
|
||||
import AppEmpty from '../../components/feedback/AppEmpty.vue'
|
||||
|
||||
@ -31,7 +32,7 @@ const weakTone = ['danger', 'warning', 'primary']
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="review">
|
||||
<AppStack class="review">
|
||||
<AppLoading v-if="review.loading.value" fullscreen />
|
||||
<AppError
|
||||
v-else-if="review.error.value"
|
||||
@ -107,15 +108,10 @@ const weakTone = ['danger', 'warning', 'primary']
|
||||
</ul>
|
||||
</AppCard>
|
||||
</template>
|
||||
</div>
|
||||
</AppStack>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.review {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-5);
|
||||
}
|
||||
.review__title {
|
||||
margin: 0;
|
||||
font-size: var(--fs-h1);
|
||||
|
||||
@ -12,6 +12,7 @@ import AppIcon from '../../components/base/AppIcon.vue'
|
||||
import AppModal from '../../components/base/AppModal.vue'
|
||||
import AppPageHeader from '../../components/base/AppPageHeader.vue'
|
||||
import AppLoading from '../../components/feedback/AppLoading.vue'
|
||||
import AppStack from '../../components/base/AppStack.vue'
|
||||
import AppError from '../../components/feedback/AppError.vue'
|
||||
import AppEmpty from '../../components/feedback/AppEmpty.vue'
|
||||
|
||||
@ -134,7 +135,7 @@ const profileDays = computed(() => (examProfile.data.value ? daysSince(examProfi
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="plan">
|
||||
<AppStack class="plan">
|
||||
<AppLoading v-if="today.loading.value" fullscreen />
|
||||
<AppError
|
||||
v-else-if="today.error.value"
|
||||
@ -359,15 +360,10 @@ const profileDays = computed(() => (examProfile.data.value ? daysSince(examProfi
|
||||
</AppButton>
|
||||
</template>
|
||||
</AppModal>
|
||||
</div>
|
||||
</AppStack>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.plan {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-5);
|
||||
}
|
||||
/* 指标卡 */
|
||||
.plan-metrics {
|
||||
display: grid;
|
||||
|
||||
@ -9,6 +9,7 @@ import AppIcon from '../../components/base/AppIcon.vue'
|
||||
import AppBadge from '../../components/base/AppBadge.vue'
|
||||
import AppPageHeader from '../../components/base/AppPageHeader.vue'
|
||||
import AppLoading from '../../components/feedback/AppLoading.vue'
|
||||
import AppStack from '../../components/base/AppStack.vue'
|
||||
import AppError from '../../components/feedback/AppError.vue'
|
||||
|
||||
const route = useRoute()
|
||||
@ -63,7 +64,7 @@ const mistakeText = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="ai-explain">
|
||||
<AppStack class="ai-explain">
|
||||
<AppLoading v-if="loading" fullscreen text="正在生成讲解…" />
|
||||
<AppError v-else-if="error" fullscreen title="讲解加载失败" :message="error" retry @retry="load" />
|
||||
|
||||
@ -119,15 +120,10 @@ const mistakeText = computed(() => {
|
||||
<AppIcon name="chevron-left" :size="16" />返回
|
||||
</AppButton>
|
||||
</template>
|
||||
</div>
|
||||
</AppStack>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.ai-explain {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-5);
|
||||
}
|
||||
.ai-explain__summary {
|
||||
margin: 0;
|
||||
font-size: var(--fs-body);
|
||||
|
||||
@ -8,6 +8,7 @@ import AppButton from '../../components/base/AppButton.vue'
|
||||
import AppBadge from '../../components/base/AppBadge.vue'
|
||||
import AppIcon from '../../components/base/AppIcon.vue'
|
||||
import AppLoading from '../../components/feedback/AppLoading.vue'
|
||||
import AppStack from '../../components/base/AppStack.vue'
|
||||
import AppError from '../../components/feedback/AppError.vue'
|
||||
import { formatDuration } from '../../utils/format'
|
||||
|
||||
@ -155,7 +156,7 @@ const finishAccuracy = computed(() => finishResult.value?.accuracy ?? 0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="answer">
|
||||
<AppStack class="answer">
|
||||
<!-- ============ 整卷结果页 ============ -->
|
||||
<div v-if="finished && finishResult" class="result">
|
||||
<header class="result__head">
|
||||
@ -316,15 +317,10 @@ const finishAccuracy = computed(() => finishResult.value?.accuracy ?? 0)
|
||||
</AppButton>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</AppStack>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.answer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-5);
|
||||
}
|
||||
|
||||
/* 顶部栏 */
|
||||
.answer__top {
|
||||
|
||||
@ -8,7 +8,9 @@ import AppPageHeader from '../../components/base/AppPageHeader.vue'
|
||||
import AppCard from '../../components/base/AppCard.vue'
|
||||
import AppButton from '../../components/base/AppButton.vue'
|
||||
import AppBadge from '../../components/base/AppBadge.vue'
|
||||
import AppIcon from '../../components/base/AppIcon.vue'
|
||||
import AppLoading from '../../components/feedback/AppLoading.vue'
|
||||
import AppStack from '../../components/base/AppStack.vue'
|
||||
import AppError from '../../components/feedback/AppError.vue'
|
||||
import AppEmpty from '../../components/feedback/AppEmpty.vue'
|
||||
import { useToast } from '../../composables/useToast'
|
||||
@ -74,7 +76,7 @@ function back() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="custom">
|
||||
<AppStack class="custom">
|
||||
<AppPageHeader title="自定义组卷" subtitle="从题库中勾选题目,组成一套专属练习">
|
||||
<AppButton variant="ghost" icon="chevron-left" @click="back">返回</AppButton>
|
||||
</AppPageHeader>
|
||||
@ -123,15 +125,10 @@ function back() {
|
||||
</AppButton>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</AppStack>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.custom {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-5);
|
||||
}
|
||||
.custom__toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
import AppCard from '../../components/base/AppCard.vue'
|
||||
import AppBadge from '../../components/base/AppBadge.vue'
|
||||
import AppIcon from '../../components/base/AppIcon.vue'
|
||||
import AppStack from '../../components/base/AppStack.vue'
|
||||
|
||||
const QUESTION_TYPES = [
|
||||
{ name: '归纳概括', desc: '概括给定资料的主要内容或问题' },
|
||||
@ -13,7 +14,7 @@ const QUESTION_TYPES = [
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="essay">
|
||||
<AppStack class="essay">
|
||||
<!-- 功能说明 -->
|
||||
<AppCard title="申论功能说明" icon="book-open">
|
||||
<p class="essay__intro">
|
||||
@ -53,15 +54,10 @@ const QUESTION_TYPES = [
|
||||
<p>当前为占位页面,不保存任何写作内容。建议先完成行测「刷题 → 错题复习 → 模考分析」闭环。</p>
|
||||
</div>
|
||||
</AppCard>
|
||||
</div>
|
||||
</AppStack>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.essay {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-5);
|
||||
}
|
||||
.essay__intro {
|
||||
margin: 0 0 var(--space-4);
|
||||
font-size: var(--fs-body);
|
||||
|
||||
@ -12,6 +12,7 @@ import AppButton from '../../components/base/AppButton.vue'
|
||||
import AppBadge from '../../components/base/AppBadge.vue'
|
||||
import AppIcon from '../../components/base/AppIcon.vue'
|
||||
import AppLoading from '../../components/feedback/AppLoading.vue'
|
||||
import AppStack from '../../components/base/AppStack.vue'
|
||||
import AppError from '../../components/feedback/AppError.vue'
|
||||
import AppEmpty from '../../components/feedback/AppEmpty.vue'
|
||||
import EssayView from './EssayView.vue'
|
||||
@ -160,7 +161,7 @@ const subtitle = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="practice">
|
||||
<AppStack class="practice">
|
||||
<AppPageHeader title="刷题中心" :subtitle="subtitle" :stack-mobile="true">
|
||||
<template v-if="tab === 'practice'">
|
||||
<AppButton variant="secondary" icon="plus" @click="goCustom">自定义组卷</AppButton>
|
||||
@ -393,15 +394,10 @@ const subtitle = computed(() => {
|
||||
<template v-else>
|
||||
<EssayView />
|
||||
</template>
|
||||
</div>
|
||||
</AppStack>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.practice {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-5);
|
||||
}
|
||||
|
||||
/* 子 tab */
|
||||
.practice-tabs {
|
||||
|
||||
@ -12,6 +12,7 @@ import AppButton from '../../components/base/AppButton.vue'
|
||||
import AppIcon from '../../components/base/AppIcon.vue'
|
||||
import AppPageHeader from '../../components/base/AppPageHeader.vue'
|
||||
import AppLoading from '../../components/feedback/AppLoading.vue'
|
||||
import AppStack from '../../components/base/AppStack.vue'
|
||||
import AppError from '../../components/feedback/AppError.vue'
|
||||
|
||||
const { isMobile } = useResponsive()
|
||||
@ -73,7 +74,7 @@ function editProfile() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="profile">
|
||||
<AppStack class="profile">
|
||||
<AppLoading v-if="overview.loading.value" fullscreen />
|
||||
<AppError
|
||||
v-else-if="overview.error.value"
|
||||
@ -243,15 +244,10 @@ function editProfile() {
|
||||
<RouterLink class="tip-banner__action" to="/plan/review">去加强</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AppStack>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.profile {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-5);
|
||||
}
|
||||
|
||||
/* 头像卡 */
|
||||
.me-card {
|
||||
|
||||
@ -13,6 +13,7 @@ import AppModal from '../../components/base/AppModal.vue'
|
||||
import AppPageHeader from '../../components/base/AppPageHeader.vue'
|
||||
import AppBadge from '../../components/base/AppBadge.vue'
|
||||
import AppLoading from '../../components/feedback/AppLoading.vue'
|
||||
import AppStack from '../../components/base/AppStack.vue'
|
||||
import AppError from '../../components/feedback/AppError.vue'
|
||||
import AppEmpty from '../../components/feedback/AppEmpty.vue'
|
||||
|
||||
@ -323,7 +324,7 @@ function markAnswer(key: OptionKey) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="questions">
|
||||
<AppStack class="questions" gap="space-4">
|
||||
<!-- ======================= 手动录入模式 ======================= -->
|
||||
<template v-if="mode === 'entry'">
|
||||
<AppPageHeader title="题库管理" subtitle="手动录入题目 · 支持逐题与批量 JSON 导入" sticky-mobile>
|
||||
@ -627,15 +628,10 @@ function markAnswer(key: OptionKey) {
|
||||
</AppButton>
|
||||
</template>
|
||||
</AppModal>
|
||||
</div>
|
||||
</AppStack>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.questions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
/* ---------- 录入模式 ---------- */
|
||||
.entry {
|
||||
|
||||
@ -11,6 +11,7 @@ import AppBadge from '../../components/base/AppBadge.vue'
|
||||
import AppIcon from '../../components/base/AppIcon.vue'
|
||||
import AppPageHeader from '../../components/base/AppPageHeader.vue'
|
||||
import AppLoading from '../../components/feedback/AppLoading.vue'
|
||||
import AppStack from '../../components/base/AppStack.vue'
|
||||
import AppError from '../../components/feedback/AppError.vue'
|
||||
|
||||
const { isMobile } = useResponsive()
|
||||
@ -83,7 +84,7 @@ async function save() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="settings">
|
||||
<AppStack class="settings">
|
||||
<AppLoading v-if="settings.loading.value" fullscreen />
|
||||
<AppError
|
||||
v-else-if="settings.error.value"
|
||||
@ -199,15 +200,10 @@ async function save() {
|
||||
</div>
|
||||
</AppCard>
|
||||
</template>
|
||||
</div>
|
||||
</AppStack>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.settings {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-5);
|
||||
}
|
||||
|
||||
.setting-row {
|
||||
display: flex;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user