Compare commits
4 Commits
40f361cf91
...
7f0bfd67fa
| Author | SHA1 | Date | |
|---|---|---|---|
| 7f0bfd67fa | |||
| 501f314aff | |||
| 6fb3bc0198 | |||
| e4359e8093 |
@@ -46,8 +46,8 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from "vue"
|
||||
import { useMessage } from "naive-ui"
|
||||
import { html, css, js } from "../store/editors"
|
||||
import { Submission } from "../api"
|
||||
import { html, css, js } from "../../store/editors"
|
||||
import { Submission } from "../../api"
|
||||
|
||||
const props = defineProps<{ taskId: number }>()
|
||||
const message = useMessage()
|
||||
@@ -56,7 +56,7 @@ import {
|
||||
streaming,
|
||||
streamingContent,
|
||||
sendPrompt,
|
||||
} from "../store/prompt"
|
||||
} from "../../store/prompt"
|
||||
|
||||
const input = ref("")
|
||||
const messagesRef = ref<HTMLElement>()
|
||||
@@ -83,9 +83,9 @@ import * as babelParser from "prettier/parser-babel"
|
||||
import * as estreeParser from "prettier/plugins/estree"
|
||||
import Editor from "./Editor.vue"
|
||||
import Toolbar from "./Toolbar.vue"
|
||||
import { html, css, js, tab, size, reset } from "../store/editors"
|
||||
import { taskId } from "../store/task"
|
||||
import { Submission } from "../api"
|
||||
import { html, css, js, tab, size, reset } from "../../store/editors"
|
||||
import { taskId } from "../../store/task"
|
||||
import { Submission } from "../../api"
|
||||
import { NCode, useDialog, useMessage } from "naive-ui"
|
||||
import { h, ref } from "vue"
|
||||
|
||||
@@ -36,8 +36,8 @@
|
||||
import { watchDebounced } from "@vueuse/core"
|
||||
import { computed, onMounted, useTemplateRef } from "vue"
|
||||
import { useRouter } from "vue-router"
|
||||
import { Submission } from "../api"
|
||||
import { submission } from "../store/submission"
|
||||
import { Submission } from "../../api"
|
||||
import { submission } from "../../store/submission"
|
||||
import { useMessage } from "naive-ui"
|
||||
import copy from "copy-text-to-clipboard"
|
||||
|
||||
@@ -29,15 +29,15 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, h } from "vue"
|
||||
import { Icon } from "@iconify/vue"
|
||||
import { authed, roleNormal, roleSuper, user } from "../store/user"
|
||||
import { loginModal } from "../store/modal"
|
||||
import { show, panelSize } from "../store/panel"
|
||||
import { step } from "../store/tutorial"
|
||||
import { taskId } from "../store/task"
|
||||
import { Account } from "../api"
|
||||
import { Role } from "../utils/type"
|
||||
import { router } from "../router"
|
||||
import { ADMIN_URL } from "../utils/const"
|
||||
import { authed, roleNormal, roleSuper, user } from "../../store/user"
|
||||
import { loginModal } from "../../store/modal"
|
||||
import { show, panelSize } from "../../store/panel"
|
||||
import { step } from "../../store/tutorial"
|
||||
import { taskId } from "../../store/task"
|
||||
import { Account } from "../../api"
|
||||
import { Role } from "../../utils/type"
|
||||
import { router } from "../../router"
|
||||
import { ADMIN_URL } from "../../utils/const"
|
||||
|
||||
const props = defineProps<{
|
||||
submitLoading: boolean
|
||||
@@ -28,10 +28,10 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from "vue"
|
||||
import { useRouter } from "vue-router"
|
||||
import { Challenge } from "../api"
|
||||
import { taskTab } from "../store/task"
|
||||
import { TASK_TYPE } from "../utils/const"
|
||||
import type { ChallengeSlim } from "../utils/type"
|
||||
import { Challenge } from "../../api"
|
||||
import { taskTab } from "../../store/task"
|
||||
import { TASK_TYPE } from "../../utils/const"
|
||||
import type { ChallengeSlim } from "../../utils/type"
|
||||
|
||||
const router = useRouter()
|
||||
const challenges = ref<ChallengeSlim[]>([])
|
||||
@@ -49,37 +49,39 @@
|
||||
</n-button>
|
||||
</n-flex>
|
||||
</n-flex>
|
||||
<Tutorial v-if="taskTab === TASK_TYPE.Tutorial" />
|
||||
<Challenge v-else />
|
||||
<TutorialContent v-if="taskTab === TASK_TYPE.Tutorial" />
|
||||
<ChallengeList v-else />
|
||||
</div>
|
||||
<TaskStatsModal v-model:show="statsModal" :task-id="taskId" />
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { Icon } from "@iconify/vue"
|
||||
import { computed, ref } from "vue"
|
||||
import { step, tutorialIds, prev, next, prevDisabled, nextDisabled } from "../store/tutorial"
|
||||
import { authed, roleSuper } from "../store/user"
|
||||
import { taskTab, taskId, challengeDisplay } from "../store/task"
|
||||
import { computed, ref, watch } from "vue"
|
||||
import { step, tutorialIds, prev, next, prevDisabled, nextDisabled } from "../../store/tutorial"
|
||||
import { authed, roleSuper } from "../../store/user"
|
||||
import { taskTab, taskId, challengeDisplay } from "../../store/task"
|
||||
import { useRoute, useRouter } from "vue-router"
|
||||
import { TASK_TYPE } from "../utils/const"
|
||||
import Challenge from "./Challenge.vue"
|
||||
import Tutorial from "./Tutorial.vue"
|
||||
import { TASK_TYPE } from "../../utils/const"
|
||||
import ChallengeList from "./ChallengeList.vue"
|
||||
import TutorialContent from "./TutorialContent.vue"
|
||||
import TaskStatsModal from "./TaskStatsModal.vue"
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const statsModal = ref(false)
|
||||
|
||||
// 路由同步:在 setup 阶段立即执行,不等 onMounted
|
||||
const routeName = route.name as string
|
||||
if (routeName.startsWith("home-tutorial")) {
|
||||
taskTab.value = TASK_TYPE.Tutorial
|
||||
if (route.params.display) step.value = Number(route.params.display)
|
||||
} else if (routeName.startsWith("home-challenge")) {
|
||||
taskTab.value = TASK_TYPE.Challenge
|
||||
if (route.params.display)
|
||||
challengeDisplay.value = Number(route.params.display)
|
||||
// 路由同步:初始执行 + watch 响应 SPA 内部导航
|
||||
function syncRoute(routeName: string) {
|
||||
if (routeName.startsWith("home-tutorial")) {
|
||||
taskTab.value = TASK_TYPE.Tutorial
|
||||
if (route.params.display) step.value = Number(route.params.display)
|
||||
} else if (routeName.startsWith("home-challenge")) {
|
||||
taskTab.value = TASK_TYPE.Challenge
|
||||
if (route.params.display) challengeDisplay.value = Number(route.params.display)
|
||||
}
|
||||
}
|
||||
syncRoute(route.name as string)
|
||||
watch(() => route.name as string, syncRoute)
|
||||
|
||||
defineEmits(["hide"])
|
||||
|
||||
@@ -105,7 +107,6 @@ function changeTab(v: TASK_TYPE) {
|
||||
: { name: "home-tutorial-list" },
|
||||
)
|
||||
} else if (v === TASK_TYPE.Challenge) {
|
||||
challengeDisplay.value = 0
|
||||
router.push({ name: "home-challenge-list" })
|
||||
}
|
||||
}
|
||||
@@ -392,8 +392,8 @@
|
||||
import { ref, computed, watch } from "vue"
|
||||
import { Icon } from "@iconify/vue"
|
||||
import { useRouter } from "vue-router"
|
||||
import { Submission } from "../api"
|
||||
import type { TaskStatsOut } from "../utils/type"
|
||||
import { Submission } from "../../api"
|
||||
import type { TaskStatsOut } from "../../utils/type"
|
||||
|
||||
const props = defineProps<{ taskId: number; show: boolean }>()
|
||||
const emit = defineEmits<{ (e: "update:show", v: boolean): void }>()
|
||||
@@ -5,10 +5,10 @@
|
||||
import { onMounted, ref, useTemplateRef, watch } from "vue"
|
||||
import { marked } from "marked"
|
||||
import copyFn from "copy-text-to-clipboard"
|
||||
import { css, html, js, tab } from "../store/editors"
|
||||
import { Tutorial } from "../api"
|
||||
import { step, tutorialIds } from "../store/tutorial"
|
||||
import { taskId } from "../store/task"
|
||||
import { css, html, js, tab } from "../../store/editors"
|
||||
import { Tutorial } from "../../api"
|
||||
import { step, tutorialIds, loadTutorials } from "../../store/tutorial"
|
||||
import { taskId } from "../../store/task"
|
||||
import { useRouter } from "vue-router"
|
||||
|
||||
marked.use({
|
||||
@@ -36,17 +36,6 @@ const router = useRouter()
|
||||
const content = ref("")
|
||||
const $content = useTemplateRef<any>("$content")
|
||||
|
||||
async function prepare() {
|
||||
tutorialIds.value = await Tutorial.listDisplay()
|
||||
if (!tutorialIds.value.length) {
|
||||
content.value = "暂无教程"
|
||||
return
|
||||
}
|
||||
if (!tutorialIds.value.includes(step.value)) {
|
||||
step.value = tutorialIds.value[0] as number
|
||||
}
|
||||
}
|
||||
|
||||
async function render() {
|
||||
const data = await Tutorial.get(step.value)
|
||||
taskId.value = data.task_ptr
|
||||
@@ -85,7 +74,11 @@ function setupCodeActions() {
|
||||
}
|
||||
|
||||
async function init() {
|
||||
await prepare()
|
||||
await loadTutorials()
|
||||
if (!tutorialIds.value.length) {
|
||||
content.value = "暂无教程"
|
||||
return
|
||||
}
|
||||
render()
|
||||
}
|
||||
|
||||
@@ -76,13 +76,14 @@ import { useRoute, useRouter } from "vue-router"
|
||||
import { useMessage } from "naive-ui"
|
||||
import { Icon } from "@iconify/vue"
|
||||
import { marked } from "marked"
|
||||
import PromptPanel from "../components/PromptPanel.vue"
|
||||
import ExternalAIPanel from "../components/ExternalAIPanel.vue"
|
||||
import Preview from "../components/Preview.vue"
|
||||
import TaskStatsModal from "../components/TaskStatsModal.vue"
|
||||
import PromptPanel from "../components/ai/PromptPanel.vue"
|
||||
import ExternalAIPanel from "../components/ai/ExternalAIPanel.vue"
|
||||
import Preview from "../components/editor/Preview.vue"
|
||||
import TaskStatsModal from "../components/task/TaskStatsModal.vue"
|
||||
import { Challenge, Submission } from "../api"
|
||||
import { html, css, js } from "../store/editors"
|
||||
import { taskId } from "../store/task"
|
||||
import { taskId, taskTab, challengeDisplay } from "../store/task"
|
||||
import { TASK_TYPE } from "../utils/const"
|
||||
import { authed, roleAdmin, roleSuper } from "../store/user"
|
||||
import {
|
||||
connectPrompt,
|
||||
@@ -106,6 +107,8 @@ watch(streaming, (val) => {
|
||||
|
||||
async function loadChallenge() {
|
||||
const display = Number(route.params.display)
|
||||
taskTab.value = TASK_TYPE.Challenge
|
||||
challengeDisplay.value = display
|
||||
const data = await Challenge.get(display)
|
||||
taskId.value = data.task_ptr
|
||||
challengeContent.value = await marked.parse(data.content, { async: true })
|
||||
@@ -12,7 +12,7 @@
|
||||
style="height: 100%; padding-right: 10px; overflow: hidden"
|
||||
>
|
||||
<n-flex justify="space-between" style="flex-shrink: 0">
|
||||
<n-button secondary @click="() => goHome($router, taskTab, step)">
|
||||
<n-button secondary @click="() => goHome($router, taskTab, taskTab === TASK_TYPE.Challenge ? challengeDisplay : step)">
|
||||
首页
|
||||
</n-button>
|
||||
<n-flex align="center">
|
||||
@@ -112,7 +112,7 @@ import { TASK_TYPE } from "../utils/const"
|
||||
import { watchDebounced } from "@vueuse/core"
|
||||
import { useRouter, useRoute } from "vue-router"
|
||||
|
||||
import Preview from "../components/Preview.vue"
|
||||
import Preview from "../components/editor/Preview.vue"
|
||||
import TaskTitle from "../components/submissions/TaskTitle.vue"
|
||||
import CodeModal from "../components/submissions/CodeModal.vue"
|
||||
import ChainModal from "../components/submissions/ChainModal.vue"
|
||||
@@ -120,7 +120,7 @@ import FlagCell from "../components/submissions/FlagCell.vue"
|
||||
import ExpandedSubTable from "../components/submissions/ExpandedSubTable.vue"
|
||||
|
||||
import { submission } from "../store/submission"
|
||||
import { taskTab } from "../store/task"
|
||||
import { taskTab, challengeDisplay } from "../store/task"
|
||||
import { step } from "../store/tutorial"
|
||||
import { html as eHtml, css as eCss, js as eJs } from "../store/editors"
|
||||
import { roleAdmin, roleSuper, user } from "../store/user"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
max="900px"
|
||||
>
|
||||
<template #1>
|
||||
<Task @hide="hide" />
|
||||
<TaskPanel @hide="hide" />
|
||||
</template>
|
||||
<template #2>
|
||||
<n-split direction="vertical" min="200px">
|
||||
@@ -22,9 +22,9 @@
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useMagicKeys, whenever } from "@vueuse/core"
|
||||
import Editors from "../components/Editors.vue"
|
||||
import Preview from "../components/Preview.vue"
|
||||
import Task from "../components/Task.vue"
|
||||
import Editors from "../components/editor/Editors.vue"
|
||||
import Preview from "../components/editor/Preview.vue"
|
||||
import TaskPanel from "../components/task/TaskPanel.vue"
|
||||
import { show, panelSize } from "../store/panel"
|
||||
import { html, css, js } from "../store/editors"
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { createWebHistory, createRouter } from "vue-router"
|
||||
import { loginModal } from "./store/modal"
|
||||
|
||||
import Home from "./pages/Home.vue"
|
||||
import Workspace from "./pages/Workspace.vue"
|
||||
import { STORAGE_KEY } from "./utils/const"
|
||||
|
||||
const routes = [
|
||||
{ path: "/", name: "home", component: Home },
|
||||
{ path: "/tutorial", name: "home-tutorial-list", component: Home },
|
||||
{ path: "/tutorial/:display", name: "home-tutorial", component: Home },
|
||||
{ path: "/challenge", name: "home-challenge-list", component: Home },
|
||||
{ path: "/", name: "home", component: Workspace },
|
||||
{ path: "/tutorial", name: "home-tutorial-list", component: Workspace },
|
||||
{ path: "/tutorial/:display", name: "home-tutorial", component: Workspace },
|
||||
{ path: "/challenge", name: "home-challenge-list", component: Workspace },
|
||||
{
|
||||
path: "/challenge/:display",
|
||||
name: "home-challenge",
|
||||
component: () => import("./pages/ChallengeHome.vue"),
|
||||
component: () => import("./pages/ChallengeDetail.vue"),
|
||||
},
|
||||
{
|
||||
path: "/submissions/:page",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ref } from "vue"
|
||||
import { useStorage } from "@vueuse/core"
|
||||
import { TASK_TYPE } from "../utils/const"
|
||||
|
||||
const currentTask = window.location.pathname.startsWith("/challenge")
|
||||
@@ -7,4 +8,4 @@ const currentTask = window.location.pathname.startsWith("/challenge")
|
||||
|
||||
export const taskTab = ref(currentTask)
|
||||
export const taskId = ref(0)
|
||||
export const challengeDisplay = ref(0)
|
||||
export const challengeDisplay = useStorage("challenge-display", 0)
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import { useStorage } from "@vueuse/core"
|
||||
import { ref } from "vue"
|
||||
import { Tutorial } from "../api"
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search)
|
||||
const currentStep = urlParams.get("step") ?? "1"
|
||||
|
||||
export const step = ref(Number(currentStep))
|
||||
export const step = useStorage("tutorial-step", 1)
|
||||
export const tutorialIds = ref<number[]>([])
|
||||
|
||||
export async function loadTutorials(): Promise<void> {
|
||||
tutorialIds.value = await Tutorial.listDisplay()
|
||||
if (tutorialIds.value.length && !tutorialIds.value.includes(step.value)) {
|
||||
step.value = tutorialIds.value[0] as number
|
||||
}
|
||||
}
|
||||
|
||||
export function prevDisabled(): boolean {
|
||||
const i = tutorialIds.value.indexOf(step.value)
|
||||
return i <= 0
|
||||
|
||||
@@ -10,7 +10,8 @@ export function goHome(router: any, type: TASK_TYPE, display: number) {
|
||||
if (type === TASK_TYPE.Tutorial) {
|
||||
router.push({ name: "home-tutorial", params: { display } })
|
||||
} else if (type === TASK_TYPE.Challenge) {
|
||||
router.push({ name: "home-challenge", params: { display } })
|
||||
if (display) router.push({ name: "home-challenge", params: { display } })
|
||||
else router.push({ name: "home-challenge-list" })
|
||||
} else {
|
||||
router.push({ name: "home" })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user