update
Some checks failed
Deploy / deploy (build, debian, 22) (push) Has been cancelled
Deploy / deploy (build:staging, school, 8822) (push) Has been cancelled

This commit is contained in:
2026-04-07 19:24:09 -06:00
parent 3697078fc3
commit 58f462bb3b
11 changed files with 146 additions and 118 deletions

View File

@@ -34,11 +34,7 @@
style="width: 120px" style="width: 120px"
:disabled="streaming" :disabled="streaming"
/> />
<n-button <n-button v-if="streaming" type="error" @click="stopPrompt">
v-if="streaming"
type="error"
@click="stopPrompt"
>
停止 停止
</n-button> </n-button>
<n-button <n-button

View File

@@ -1,80 +1,80 @@
<template> <template>
<div class="editors-root"> <div class="editors-root">
<n-tabs <n-tabs
:value="tab" :value="tab"
:class="`tab-active-${tab}`" :class="`tab-active-${tab}`"
pane-class="pane" pane-class="pane"
style="height: 100%" style="height: 100%"
type="card" type="card"
@update:value="changeTab" @update:value="changeTab"
> >
<n-tab-pane name="html" tab="HTML"> <n-tab-pane name="html" tab="HTML">
<template #tab> <template #tab>
<n-flex align="center">
<Icon :width="20" icon="skill-icons:html"></Icon>
<span>HTML</span>
</n-flex>
</template>
<Editor v-model:value="html" :font-size="size" language="html" />
</n-tab-pane>
<n-tab-pane name="css" tab="CSS">
<template #tab>
<n-flex align="center">
<Icon :width="20" icon="skill-icons:css"></Icon>
<span>CSS</span>
</n-flex>
</template>
<Editor v-model:value="css" :font-size="size" language="css" />
</n-tab-pane>
<n-tab-pane name="js" tab="JS">
<template #tab>
<n-flex align="center">
<Icon :width="20" icon="skill-icons:javascript"></Icon>
<span>JS</span>
</n-flex>
</template>
<Editor v-model:value="js" :font-size="size" language="js" />
</n-tab-pane>
<n-tab-pane name="actions" tab="选项">
<template #tab>
<n-flex align="center">
<Icon :width="20" icon="skill-icons:actix-dark"></Icon>
<span>选项</span>
</n-flex>
</template>
<n-flex class="wrapper" vertical>
<n-flex align="center">
<span class="label">重置</span>
<n-button @click="reset('html')">HTML</n-button>
<n-button @click="reset('css')">CSS</n-button>
<n-button @click="reset('js')">JS</n-button>
</n-flex>
<n-flex align="center">
<span class="label">字号</span>
<n-flex align="center"> <n-flex align="center">
<span :style="{ 'font-size': size + 'px' }">{{ size }}</span> <Icon :width="20" icon="skill-icons:html"></Icon>
<n-button :disabled="size === 20" @click="changeSize(size - 2)"> <span>HTML</span>
调小 </n-flex>
</n-button> </template>
<n-button :disabled="size === 40" @click="changeSize(size + 2)"> <Editor v-model:value="html" :font-size="size" language="html" />
调大 </n-tab-pane>
</n-button> <n-tab-pane name="css" tab="CSS">
<template #tab>
<n-flex align="center">
<Icon :width="20" icon="skill-icons:css"></Icon>
<span>CSS</span>
</n-flex>
</template>
<Editor v-model:value="css" :font-size="size" language="css" />
</n-tab-pane>
<n-tab-pane name="js" tab="JS">
<template #tab>
<n-flex align="center">
<Icon :width="20" icon="skill-icons:javascript"></Icon>
<span>JS</span>
</n-flex>
</template>
<Editor v-model:value="js" :font-size="size" language="js" />
</n-tab-pane>
<n-tab-pane name="actions" tab="选项">
<template #tab>
<n-flex align="center">
<Icon :width="20" icon="skill-icons:actix-dark"></Icon>
<span>选项</span>
</n-flex>
</template>
<n-flex class="wrapper" vertical>
<n-flex align="center">
<span class="label">重置</span>
<n-button @click="reset('html')">HTML</n-button>
<n-button @click="reset('css')">CSS</n-button>
<n-button @click="reset('js')">JS</n-button>
</n-flex>
<n-flex align="center">
<span class="label">字号</span>
<n-flex align="center">
<span :style="{ 'font-size': size + 'px' }">{{ size }}</span>
<n-button :disabled="size === 20" @click="changeSize(size - 2)">
调小
</n-button>
<n-button :disabled="size === 40" @click="changeSize(size + 2)">
调大
</n-button>
</n-flex>
</n-flex>
<n-flex align="center">
<span class="label">预加载</span>
<n-tag type="success">Normalize.css</n-tag>
</n-flex> </n-flex>
</n-flex> </n-flex>
<n-flex align="center"> </n-tab-pane>
<span class="label">预加载</span> <template #suffix>
<n-tag type="success">Normalize.css</n-tag> <Toolbar
</n-flex> :submit-loading="submitLoading"
</n-flex> @format="format"
</n-tab-pane> @submit="formatAndSubmit"
<template #suffix> />
<Toolbar </template>
:submit-loading="submitLoading" </n-tabs>
@format="format"
@submit="formatAndSubmit"
/>
</template>
</n-tabs>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View File

@@ -68,14 +68,31 @@ const emits = defineEmits(["afterScore", "showCode", "clear"])
type Layout = "desktop" | "mobile" | "tablet" type Layout = "desktop" | "mobile" | "tablet"
const layouts: Layout[] = ["desktop", "mobile", "tablet"] const layouts: Layout[] = ["desktop", "mobile", "tablet"]
const layoutConfig: Record<Layout, { icon: string; label: string; width: string }> = { const layoutConfig: Record<
desktop: { icon: "material-symbols:desktop-windows-outline", label: "桌面", width: "100%" }, Layout,
mobile: { icon: "material-symbols:smartphone-outline", label: "移动端 (375px)", width: "375px" }, { icon: string; label: string; width: string }
tablet: { icon: "material-symbols:tablet-outline", label: "平板 (768px)", width: "768px" }, > = {
desktop: {
icon: "material-symbols:desktop-windows-outline",
label: "桌面",
width: "100%",
},
mobile: {
icon: "material-symbols:smartphone-outline",
label: "移动端 (375px)",
width: "375px",
},
tablet: {
icon: "material-symbols:tablet-outline",
label: "平板 (768px)",
width: "768px",
},
} }
const layoutIndex = ref(0) const layoutIndex = ref(0)
const layoutIcon = computed(() => layoutConfig[layouts[layoutIndex.value]].icon) const layoutIcon = computed(() => layoutConfig[layouts[layoutIndex.value]].icon)
const layoutLabel = computed(() => layoutConfig[layouts[layoutIndex.value]].label) const layoutLabel = computed(
() => layoutConfig[layouts[layoutIndex.value]].label,
)
const iframeWrapperStyle = computed(() => ({ const iframeWrapperStyle = computed(() => ({
maxWidth: layoutConfig[layouts[layoutIndex.value]].width, maxWidth: layoutConfig[layouts[layoutIndex.value]].width,
})) }))

View File

@@ -38,7 +38,6 @@ import {
} from "../../store/user" } from "../../store/user"
import { loginModal } from "../../store/modal" import { loginModal } from "../../store/modal"
import { show, panelSize } from "../../store/panel" import { show, panelSize } from "../../store/panel"
import { step } from "../../store/tutorial"
import { taskId } from "../../store/task" import { taskId } from "../../store/task"
import { Account } from "../../api" import { Account } from "../../api"
import { Role } from "../../utils/type" import { Role } from "../../utils/type"
@@ -103,18 +102,9 @@ function showTutorial() {
function clickMenu(name: string) { function clickMenu(name: string) {
switch (name) { switch (name) {
case "dashboard": { case "dashboard": {
if (roleSuper.value) { const route = roleAdmin.value ? "challenge-editor" : "tutorial-editor"
router.push({ router.push({ name: route, query: { display: 0 } })
name: "tutorial-editor", break
params: { display: 0 },
})
break
} else if (roleAdmin.value) {
router.push({ name: "challenge-editor", params: { display: 0 } })
break
} else {
break
}
} }
case "admin": case "admin":
window.open(ADMIN_URL) window.open(ADMIN_URL)

View File

@@ -12,13 +12,17 @@
<template #header> <template #header>
<n-flex align="center" :size="6"> <n-flex align="center" :size="6">
<span v-if="item.submitted" class="check-icon"></span> <span v-if="item.submitted" class="check-icon"></span>
<span :class="{ 'submitted-title': item.submitted }">{{ item.title }}</span> <span :class="{ 'submitted-title': item.submitted }">{{
item.title
}}</span>
</n-flex> </n-flex>
</template> </template>
<template #header-extra> <template #header-extra>
<n-flex :size="6"> <n-flex :size="6">
<n-tag type="warning" size="small">{{ item.score }} </n-tag> <n-tag type="warning" size="small">{{ item.score }} </n-tag>
<n-tag v-if="item.pass_score != null" size="small">及格 {{ item.pass_score }} </n-tag> <n-tag v-if="item.pass_score != null" size="small"
>及格 {{ item.pass_score }} </n-tag
>
</n-flex> </n-flex>
</template> </template>
</n-card> </n-card>

View File

@@ -24,7 +24,9 @@
<n-button text @click="prev()" :disabled="prevDisabled()"> <n-button text @click="prev()" :disabled="prevDisabled()">
<Icon :width="24" icon="pepicons-pencil:arrow-left"></Icon> <Icon :width="24" icon="pepicons-pencil:arrow-left"></Icon>
</n-button> </n-button>
<span v-if="progressText" class="progress-text">{{ progressText }}</span> <span v-if="progressText" class="progress-text">{{
progressText
}}</span>
<n-button text @click="next()" :disabled="nextDisabled()"> <n-button text @click="next()" :disabled="nextDisabled()">
<Icon :width="24" icon="pepicons-pencil:arrow-right"></Icon> <Icon :width="24" icon="pepicons-pencil:arrow-right"></Icon>
</n-button> </n-button>
@@ -53,7 +55,14 @@
<script lang="ts" setup> <script lang="ts" setup>
import { Icon } from "@iconify/vue" import { Icon } from "@iconify/vue"
import { computed, watch } from "vue" import { computed, watch } from "vue"
import { step, tutorialIds, prev, next, prevDisabled, nextDisabled } from "../../store/tutorial" import {
step,
tutorialIds,
prev,
next,
prevDisabled,
nextDisabled,
} from "../../store/tutorial"
import { authed, roleSuper } from "../../store/user" import { authed, roleSuper } from "../../store/user"
import { taskTab, taskId, challengeDisplay } from "../../store/task" import { taskTab, taskId, challengeDisplay } from "../../store/task"
import { useRoute, useRouter } from "vue-router" import { useRoute, useRouter } from "vue-router"
@@ -71,7 +80,8 @@ function syncRoute(routeName: string) {
if (route.params.display) step.value = Number(route.params.display) if (route.params.display) step.value = Number(route.params.display)
} else if (routeName.startsWith("home-challenge")) { } else if (routeName.startsWith("home-challenge")) {
taskTab.value = TASK_TYPE.Challenge taskTab.value = TASK_TYPE.Challenge
if (route.params.display) challengeDisplay.value = Number(route.params.display) if (route.params.display)
challengeDisplay.value = Number(route.params.display)
} }
} }
syncRoute(route.name as string) syncRoute(route.name as string)
@@ -80,8 +90,7 @@ watch(() => route.name as string, syncRoute)
defineEmits(["hide"]) defineEmits(["hide"])
const hideNav = computed( const hideNav = computed(
() => () => taskTab.value !== TASK_TYPE.Tutorial || tutorialIds.value.length <= 1,
taskTab.value !== TASK_TYPE.Tutorial || tutorialIds.value.length <= 1,
) )
const progressText = computed(() => { const progressText = computed(() => {

View File

@@ -363,7 +363,8 @@
width: '20px', width: '20px',
height: '20px', height: '20px',
borderRadius: '50%', borderRadius: '50%',
background: i < 3 ? ['#f0a020', '#888', '#a07040'][i] : '#ddd', background:
i < 3 ? ['#f0a020', '#888', '#a07040'][i] : '#ddd',
color: '#fff', color: '#fff',
fontSize: '11px', fontSize: '11px',
fontWeight: '700', fontWeight: '700',
@@ -372,7 +373,8 @@
justifyContent: 'center', justifyContent: 'center',
flexShrink: 0, flexShrink: 0,
}" }"
>{{ i + 1 }}</span> >{{ i + 1 }}</span
>
<span style="flex: 1; font-size: 13px; color: #333"> <span style="flex: 1; font-size: 13px; color: #333">
{{ displayName(item.username, item.classname) }} {{ displayName(item.username, item.classname) }}
</span> </span>

View File

@@ -9,7 +9,11 @@
</template> </template>
<template #suffix> <template #suffix>
<n-flex style="margin: 0 8px"> <n-flex style="margin: 0 8px">
<n-button v-if="roleAdmin || roleSuper" text @click="showStats = true"> <n-button
v-if="roleAdmin || roleSuper"
text
@click="showStats = true"
>
<Icon :width="16" icon="lucide:bar-chart-2" /> <Icon :width="16" icon="lucide:bar-chart-2" />
</n-button> </n-button>
<n-button <n-button

View File

@@ -1,10 +1,7 @@
<template> <template>
<n-flex class="container" :wrap="false"> <n-flex class="container" :wrap="false">
<div class="sidebar"> <div class="sidebar">
<div <div class="back-btn" @click="() => goHome($router, taskTab, step)">
class="back-btn"
@click="() => goHome($router, taskTab, step)"
>
返回 返回
</div> </div>
<n-divider style="margin: 8px 0" /> <n-divider style="margin: 8px 0" />
@@ -78,7 +75,9 @@ const menu = computed(() =>
color: #888; color: #888;
cursor: pointer; cursor: pointer;
border-radius: 6px; border-radius: 6px;
transition: background-color 0.15s, color 0.15s; transition:
background-color 0.15s,
color 0.15s;
user-select: none; user-select: none;
} }
@@ -93,7 +92,9 @@ const menu = computed(() =>
font-size: 14px; font-size: 14px;
cursor: pointer; cursor: pointer;
color: #444; color: #444;
transition: background-color 0.15s, color 0.15s; transition:
background-color 0.15s,
color 0.15s;
user-select: none; user-select: none;
} }

View File

@@ -12,7 +12,17 @@
style="height: 100%; padding-right: 10px; overflow: hidden" style="height: 100%; padding-right: 10px; overflow: hidden"
> >
<n-flex justify="space-between" style="flex-shrink: 0"> <n-flex justify="space-between" style="flex-shrink: 0">
<n-button secondary @click="() => goHome($router, taskTab, taskTab === TASK_TYPE.Challenge ? challengeDisplay : step)"> <n-button
secondary
@click="
() =>
goHome(
$router,
taskTab,
taskTab === TASK_TYPE.Challenge ? challengeDisplay : step,
)
"
>
首页 首页
</n-button> </n-button>
<n-flex align="center"> <n-flex align="center">

View File

@@ -1,10 +1,5 @@
<template> <template>
<n-split <n-split :size="panelSize" @update-size="changeSize" min="400px" max="900px">
:size="panelSize"
@update-size="changeSize"
min="400px"
max="900px"
>
<template #1> <template #1>
<TaskPanel @hide="hide" /> <TaskPanel @hide="hide" />
</template> </template>