Compare commits

..

No commits in common. "cf3e87734770632187356ba4280b6da7224b666c" and "6cfb3b2f93b4a95924119a41fd3a5a97ecc8ba61" have entirely different histories.

17 changed files with 189 additions and 81 deletions

View File

@ -1,2 +1,2 @@
<!doctype html>
<html lang="zh-CN"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width, initial-scale=1.0"/><title>备考通</title><link rel="icon" href="data:,"/></head><body><div id="app"></div><script type="module" src="/src/main.ts"></script></body></html>
<html lang="zh-CN"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width, initial-scale=1.0"/><title>备考通</title></head><body><div id="app"></div><script type="module" src="/src/main.ts"></script></body></html>

View File

@ -3,17 +3,27 @@ import { computed } from 'vue'
const props = withDefaults(
defineProps<{
/** 单行横向 / 单列纵向;默认纵向。响应式由使用处自行决定传 row 还是 column */
/** 主轴方向;默认纵向 */
direction?: 'row' | 'column'
/**
* 子项间距token space-3或纯数字字符串'12' 12px
* 缺省纵向 space-520px横向 space-416px
* 子项间距兼容三种写法
* - token space-3 / space-4 / space-5
* - 纯数字字符串'12' 12px
* - 其它 CSS '12px 16px''var(--space-3)' 等原样透传
* 缺省纵向 space-520px横向 space-416px
*/
gap?: string
/** 交叉轴对齐 */
align?: 'start' | 'center' | 'end' | 'stretch' | 'baseline'
/** 主轴对齐 */
justify?: 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly'
/** 是否允许换行(横向时使用) */
wrap?: boolean
}>(),
{ direction: 'column', gap: '' }
{ direction: 'column', gap: '', align: 'stretch', justify: 'start', wrap: false }
)
/** token 名 / 纯数字 → 兼容的 CSS gap其它原样透传 */
const gapStyle = computed(() => {
const raw = props.gap.trim()
if (!raw) return props.direction === 'row' ? 'var(--space-4)' : 'var(--space-5)'
@ -26,7 +36,12 @@ const gapStyle = computed(() => {
<template>
<div
class="app-stack"
:class="{ 'app-stack--row': direction === 'row' }"
:class="{
'app-stack--row': direction === 'row',
'app-stack--wrap': wrap,
[`app-stack--align-${align}`]: align !== 'stretch',
[`app-stack--justify-${justify}`]: justify !== 'start'
}"
:style="{ '--stack-gap': gapStyle }"
>
<slot />
@ -34,17 +49,45 @@ const gapStyle = computed(() => {
</template>
<style scoped>
/* 默认样式纵向、stretch、start特殊布局由使用页绑 class 覆盖 */
.app-stack {
display: flex;
flex-direction: column;
/* 默认 stretch/start特殊行由使用页 class 设 --stack-align / --stack-justify 覆写 */
align-items: var(--stack-align, stretch);
justify-content: var(--stack-justify, flex-start);
align-items: stretch;
justify-content: flex-start;
gap: var(--stack-gap, var(--space-5));
min-width: 0;
}
.app-stack--row {
flex-direction: row;
}
.app-stack--wrap {
flex-wrap: wrap;
}
.app-stack--align-center {
align-items: center;
}
.app-stack--align-start {
align-items: flex-start;
}
.app-stack--align-end {
align-items: flex-end;
}
.app-stack--align-baseline {
align-items: baseline;
}
.app-stack--justify-center {
justify-content: center;
}
.app-stack--justify-end {
justify-content: flex-end;
}
.app-stack--justify-between {
justify-content: space-between;
}
.app-stack--justify-around {
justify-content: space-around;
}
.app-stack--justify-evenly {
justify-content: space-evenly;
}
</style>

View File

@ -7,7 +7,6 @@ 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'
@ -74,7 +73,7 @@ const mockDelta = computed(() => {
</script>
<template>
<AppStack class="dashboard">
<div class="dashboard">
<!-- 加载 / 错误 / 全局空态 -->
<AppLoading v-if="overview.loading.value" fullscreen />
<AppError
@ -234,10 +233,15 @@ const mockDelta = computed(() => {
<WeakPointsCard :weak-points="weakItems" compact />
</div>
</div>
</AppStack>
</div>
</template>
<style scoped>
.dashboard {
display: flex;
flex-direction: column;
gap: var(--space-5);
}
.num {
font-family: var(--font-num);
font-weight: 700;

View File

@ -9,7 +9,6 @@ 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()
@ -130,7 +129,7 @@ watch(
</script>
<template>
<AppStack class="mock-form">
<div class="mock-form">
<AppPageHeader :title="title" subtitle="仅支持行测五个模块分数,总分自动汇总" sticky-mobile>
<AppButton variant="ghost" @click="back">
<AppIcon name="chevron-left" :size="16" />返回模考分析
@ -212,10 +211,15 @@ watch(
</AppButton>
</AppCard>
</template>
</AppStack>
</div>
</template>
<style scoped>
.mock-form {
display: flex;
flex-direction: column;
gap: var(--space-5);
}
.mock-form__card {
max-width: 640px;
}

View File

@ -120,12 +120,7 @@ async function confirmRemove() {
<template v-else>
<!-- 最近成绩 / 目标分差 / 较上次变化 -->
<AppStack
class="mock-page__stats"
:class="{ 'is-col': isMobile }"
:direction="isMobile ? 'column' : 'row'"
gap="space-4"
>
<section class="mock-page__stats">
<StatCard
label="最近成绩"
:value="`${latest?.total ?? 0} 分`"
@ -148,7 +143,7 @@ async function confirmRemove() {
:tone="deltaTone"
:sub-tone="deltaTone"
/>
</AppStack>
</section>
<!-- 成绩趋势 -->
<AppCard title="行测成绩趋势" icon="chart">
@ -163,10 +158,10 @@ async function confirmRemove() {
<AppCard title="模块均分对比" icon="chart">
<ul class="mock-page__modules">
<li v-for="m in moduleAverages" :key="m.module" class="mock-page__module">
<AppStack direction="row" class="mock-page__module-head">
<div class="mock-page__module-head">
<span class="mock-page__module-name">{{ m.module }}</span>
<strong class="mock-page__module-value">{{ m.average }}<small> </small></strong>
</AppStack>
</div>
<div class="mock-page__module-track">
<span class="mock-page__module-fill" :style="{ width: `${Math.min(100, m.average)}%` }" />
</div>
@ -191,14 +186,14 @@ async function confirmRemove() {
</div>
<p v-if="r.note" class="mock-page__record-note">{{ r.note }}</p>
</div>
<AppStack direction="row" gap="space-1" class="mock-page__record-actions">
<div class="mock-page__record-actions">
<AppButton variant="ghost" size="md" @click="goEdit(r)">
<AppIcon name="pen" :size="15" />编辑
</AppButton>
<AppButton variant="ghost" size="md" class="is-danger" @click="removing = r">
<AppIcon name="close" :size="15" />删除
</AppButton>
</AppStack>
</div>
</li>
</ul>
</AppCard>
@ -211,22 +206,22 @@ async function confirmRemove() {
确定要删除{{ removing.name }}{{ formatDateShort(removing.date) }}{{ removing.total }} 删除后趋势与模块分析将同步更新且不可恢复
</p>
<template #footer>
<AppStack direction="row" gap="space-3" class="mock-page__confirm-actions">
<div class="mock-page__confirm-actions">
<AppButton variant="ghost" @click="removing = null">取消</AppButton>
<AppButton variant="danger" :loading="deleting" @click="confirmRemove">
{{ deleting ? '删除中…' : '确认删除' }}
</AppButton>
</AppStack>
</div>
</template>
</AppModal>
</AppStack>
</template>
<style scoped>
/* PC 横向等宽(移动端 is-col 时自然撑满不限制高度) */
.mock-page__stats:not(.is-col) > :deep(*) {
flex: 1 1 0;
min-width: 0;
.mock-page__stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--space-4);
}
.mock-page__sub {
margin: 0 0 var(--space-3);
@ -250,6 +245,7 @@ async function confirmRemove() {
gap: var(--space-4);
}
.mock-page__module-head {
display: flex;
align-items: baseline;
justify-content: space-between;
margin-bottom: var(--space-2);
@ -339,6 +335,8 @@ async function confirmRemove() {
color: var(--text-muted);
}
.mock-page__record-actions {
display: flex;
gap: var(--space-1);
flex: none;
}
.mock-page__record-actions .is-danger {
@ -351,10 +349,15 @@ async function confirmRemove() {
color: var(--text-primary);
}
.mock-page__confirm-actions {
display: flex;
justify-content: flex-end;
gap: var(--space-3);
}
@media (max-width: 767px) {
.mock-page__stats {
grid-template-columns: 1fr;
}
.mock-page__record {
flex-direction: column;
}

View File

@ -6,7 +6,6 @@ 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'
@ -20,7 +19,7 @@ const html = computed(() => renderMarkdown(detail.data.value?.content ?? ''))
</script>
<template>
<AppStack class="news-detail" gap="0">
<div class="news-detail">
<AppLoading v-if="detail.loading.value" fullscreen />
<AppError
v-else-if="detail.error.value"
@ -50,10 +49,14 @@ const html = computed(() => renderMarkdown(detail.data.value?.content ?? ''))
</template>
<AppEmpty v-else icon="news" title="该要闻不存在" description="可能已被删除或链接有误。" />
</AppStack>
</div>
</template>
<style scoped>
.news-detail {
display: flex;
flex-direction: column;
}
.news-detail__head {
margin-bottom: var(--space-4);
}

View File

@ -6,7 +6,6 @@ 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'
@ -32,7 +31,7 @@ const weakTone = ['danger', 'warning', 'primary']
</script>
<template>
<AppStack class="review">
<div class="review">
<AppLoading v-if="review.loading.value" fullscreen />
<AppError
v-else-if="review.error.value"
@ -108,10 +107,15 @@ const weakTone = ['danger', 'warning', 'primary']
</ul>
</AppCard>
</template>
</AppStack>
</div>
</template>
<style scoped>
.review {
display: flex;
flex-direction: column;
gap: var(--space-5);
}
.review__title {
margin: 0;
font-size: var(--fs-h1);

View File

@ -12,7 +12,6 @@ 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'
@ -135,7 +134,7 @@ const profileDays = computed(() => (examProfile.data.value ? daysSince(examProfi
</script>
<template>
<AppStack class="plan">
<div class="plan">
<AppLoading v-if="today.loading.value" fullscreen />
<AppError
v-else-if="today.error.value"
@ -360,10 +359,15 @@ const profileDays = computed(() => (examProfile.data.value ? daysSince(examProfi
</AppButton>
</template>
</AppModal>
</AppStack>
</div>
</template>
<style scoped>
.plan {
display: flex;
flex-direction: column;
gap: var(--space-5);
}
/* 指标卡 */
.plan-metrics {
display: grid;

View File

@ -9,7 +9,6 @@ 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()
@ -64,7 +63,7 @@ const mistakeText = computed(() => {
</script>
<template>
<AppStack class="ai-explain">
<div class="ai-explain">
<AppLoading v-if="loading" fullscreen text="正在生成讲解…" />
<AppError v-else-if="error" fullscreen title="讲解加载失败" :message="error" retry @retry="load" />
@ -110,20 +109,25 @@ const mistakeText = computed(() => {
<!-- 答案 -->
<AppCard title="正确答案" icon="check">
<AppStack direction="row" gap="space-3" class="ai-explain__answer">
<div class="ai-explain__answer">
<strong class="ai-explain__answer-key">{{ explain.answer }}</strong>
<span class="ai-explain__answer-label">本题答案为 {{ explain.answer }}</span>
</AppStack>
</div>
</AppCard>
<AppButton v-if="!isMobile" variant="ghost" @click="back">
<AppIcon name="chevron-left" :size="16" />返回
</AppButton>
</template>
</AppStack>
</div>
</template>
<style scoped>
.ai-explain {
display: flex;
flex-direction: column;
gap: var(--space-5);
}
.ai-explain__summary {
margin: 0;
font-size: var(--fs-body);
@ -201,7 +205,9 @@ const mistakeText = computed(() => {
background: var(--warning);
}
.ai-explain__answer {
--stack-align: center;
display: flex;
align-items: center;
gap: var(--space-3);
}
.ai-explain__answer-key {
display: grid;

View File

@ -8,7 +8,6 @@ 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'
@ -156,7 +155,7 @@ const finishAccuracy = computed(() => finishResult.value?.accuracy ?? 0)
</script>
<template>
<AppStack class="answer">
<div class="answer">
<!-- ============ 整卷结果页 ============ -->
<div v-if="finished && finishResult" class="result">
<header class="result__head">
@ -317,10 +316,15 @@ const finishAccuracy = computed(() => finishResult.value?.accuracy ?? 0)
</AppButton>
</template>
</div>
</AppStack>
</div>
</template>
<style scoped>
.answer {
display: flex;
flex-direction: column;
gap: var(--space-5);
}
/* 顶部栏 */
.answer__top {

View File

@ -8,9 +8,7 @@ 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'
@ -76,7 +74,7 @@ function back() {
</script>
<template>
<AppStack class="custom">
<div class="custom">
<AppPageHeader title="自定义组卷" subtitle="从题库中勾选题目,组成一套专属练习">
<AppButton variant="ghost" icon="chevron-left" @click="back">返回</AppButton>
</AppPageHeader>
@ -125,10 +123,15 @@ function back() {
</AppButton>
</div>
</template>
</AppStack>
</div>
</template>
<style scoped>
.custom {
display: flex;
flex-direction: column;
gap: var(--space-5);
}
.custom__toolbar {
display: flex;
align-items: center;

View File

@ -2,7 +2,6 @@
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: '概括给定资料的主要内容或问题' },
@ -14,7 +13,7 @@ const QUESTION_TYPES = [
</script>
<template>
<AppStack class="essay">
<div class="essay">
<!-- 功能说明 -->
<AppCard title="申论功能说明" icon="book-open">
<p class="essay__intro">
@ -54,10 +53,15 @@ const QUESTION_TYPES = [
<p>当前为占位页面不保存任何写作内容建议先完成行测刷题 错题复习 模考分析闭环</p>
</div>
</AppCard>
</AppStack>
</div>
</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);

View File

@ -12,7 +12,6 @@ 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'
@ -161,7 +160,7 @@ const subtitle = computed(() => {
</script>
<template>
<AppStack class="practice">
<div class="practice">
<AppPageHeader title="刷题中心" :subtitle="subtitle" :stack-mobile="true">
<template v-if="tab === 'practice'">
<AppButton variant="secondary" icon="plus" @click="goCustom">自定义组卷</AppButton>
@ -394,10 +393,15 @@ const subtitle = computed(() => {
<template v-else>
<EssayView />
</template>
</AppStack>
</div>
</template>
<style scoped>
.practice {
display: flex;
flex-direction: column;
gap: var(--space-5);
}
/* 子 tab */
.practice-tabs {

View File

@ -188,11 +188,11 @@ function goAiExplain() {
<!-- 自评结果反馈 -->
<section v-if="assessed && assessResult" class="wd__result">
<AppStack direction="row" gap="space-2" class="wd__result-banner" :class="assessResult.correct ? 'is-correct' : 'is-wrong'">
<div class="wd__result-banner" :class="assessResult.correct ? 'is-correct' : 'is-wrong'">
<AppIcon :name="assessResult.correct ? 'check' : 'close'" :size="18" />
<span>{{ assessResult.correct ? '已标记为记对' : '已标记为记错' }}</span>
</AppStack>
<AppStack direction="row" gap="space-4" class="wd__result-meta">
</div>
<div class="wd__result-meta">
<div class="wd__result-stat">
<strong>已复习 {{ assessResult.reviewCount }} </strong>
<span>下次复习</span>
@ -201,7 +201,7 @@ function goAiExplain() {
<strong>{{ assessResult.status === 'mastered' ? '已掌握' : assessResult.nextReviewAt }}</strong>
<span>{{ assessResult.status === 'mastered' ? '状态' : '下次复习' }}</span>
</div>
</AppStack>
</div>
<AppButton block size="lg" @click="backToList">返回错题本</AppButton>
</section>
@ -216,20 +216,20 @@ function goAiExplain() {
<section v-if="detail.history.length > 0" class="wd__history">
<h2 class="wd__history-title">作答历史</h2>
<div class="wd__history-list">
<AppStack v-for="(h, i) in detail.history" :key="i" direction="row" gap="space-3" class="wd__history-item">
<div v-for="(h, i) in detail.history" :key="i" class="wd__history-item">
<span class="wd__history-icon" :class="h.correct ? 'is-correct' : 'is-wrong'">
<AppIcon :name="h.correct ? 'check' : 'close'" :size="14" />
</span>
<div class="wd__history-body">
<AppStack direction="row" gap="space-2" class="wd__history-main">
<div class="wd__history-main">
<span>{{ formatDateWeekday(h.date) }}</span>
<span class="wd__history-badge" :class="h.correct ? 'is-correct' : 'is-wrong'">
{{ h.correct ? '答对' : `答错(选了 ${h.userAnswer}` }}
</span>
</AppStack>
</div>
<div v-if="h.wrongReason" class="wd__history-reason">错因{{ h.wrongReason }}</div>
</div>
</AppStack>
</div>
</div>
</section>
</template>
@ -407,7 +407,9 @@ function goAiExplain() {
padding: var(--card-padding-desktop);
}
.wd__result-banner {
--stack-align: center;
display: flex;
align-items: center;
gap: var(--space-2);
padding: var(--space-3) var(--space-4);
border-radius: var(--radius-md);
color: var(--on-accent);
@ -419,6 +421,10 @@ function goAiExplain() {
.wd__result-banner.is-wrong {
background: var(--danger);
}
.wd__result-meta {
display: flex;
gap: var(--space-4);
}
.wd__result-stat {
display: flex;
flex-direction: column;
@ -458,7 +464,9 @@ function goAiExplain() {
gap: var(--space-3);
}
.wd__history-item {
--stack-align: flex-start;
display: flex;
align-items: flex-start;
gap: var(--space-3);
}
.wd__history-icon {
display: grid;
@ -480,7 +488,9 @@ function goAiExplain() {
flex: 1;
}
.wd__history-main {
--stack-align: center;
display: flex;
align-items: center;
gap: var(--space-2);
font-size: var(--fs-subtitle);
color: var(--text-primary);
}

View File

@ -12,7 +12,6 @@ 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()
@ -74,7 +73,7 @@ function editProfile() {
</script>
<template>
<AppStack class="profile">
<div class="profile">
<AppLoading v-if="overview.loading.value" fullscreen />
<AppError
v-else-if="overview.error.value"
@ -244,10 +243,15 @@ function editProfile() {
<RouterLink class="tip-banner__action" to="/plan/review">去加强</RouterLink>
</div>
</div>
</AppStack>
</div>
</template>
<style scoped>
.profile {
display: flex;
flex-direction: column;
gap: var(--space-5);
}
/* 头像卡 */
.me-card {

View File

@ -13,7 +13,6 @@ 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'
@ -324,7 +323,7 @@ function markAnswer(key: OptionKey) {
</script>
<template>
<AppStack class="questions" gap="space-4">
<div class="questions">
<!-- ======================= 手动录入模式 ======================= -->
<template v-if="mode === 'entry'">
<AppPageHeader title="题库管理" subtitle="手动录入题目 · 支持逐题与批量 JSON 导入" sticky-mobile>
@ -628,10 +627,15 @@ function markAnswer(key: OptionKey) {
</AppButton>
</template>
</AppModal>
</AppStack>
</div>
</template>
<style scoped>
.questions {
display: flex;
flex-direction: column;
gap: var(--space-4);
}
/* ---------- 录入模式 ---------- */
.entry {

View File

@ -11,7 +11,6 @@ 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()
@ -84,7 +83,7 @@ async function save() {
</script>
<template>
<AppStack class="settings">
<div class="settings">
<AppLoading v-if="settings.loading.value" fullscreen />
<AppError
v-else-if="settings.error.value"
@ -200,10 +199,15 @@ async function save() {
</div>
</AppCard>
</template>
</AppStack>
</div>
</template>
<style scoped>
.settings {
display: flex;
flex-direction: column;
gap: var(--space-5);
}
.setting-row {
display: flex;