fix
This commit is contained in:
@@ -5,15 +5,15 @@
|
|||||||
<n-button quaternary @click="download" :disabled="!showDL">下载</n-button>
|
<n-button quaternary @click="download" :disabled="!showDL">下载</n-button>
|
||||||
<n-button quaternary @click="open">全屏</n-button>
|
<n-button quaternary @click="open">全屏</n-button>
|
||||||
<n-button quaternary v-if="props.clearable" @click="clear">清空</n-button>
|
<n-button quaternary v-if="props.clearable" @click="clear">清空</n-button>
|
||||||
<n-button quaternary v-if="props.showCodeButton" @click="emits('showCode')">查看代码</n-button>
|
<n-button quaternary v-if="props.showCodeButton" @click="emits('showCode')">代码</n-button>
|
||||||
<n-button quaternary v-if="props.submissionId" @click="copyLink">
|
<n-button quaternary v-if="props.submissionId" @click="copyLink">
|
||||||
复制链接
|
复制链接
|
||||||
</n-button>
|
</n-button>
|
||||||
<n-flex v-if="!!submission.id">
|
<n-flex v-if="!!submission.id">
|
||||||
<n-button quaternary @click="emits('showCode')">查看代码</n-button>
|
<n-button quaternary @click="emits('showCode')">代码</n-button>
|
||||||
<n-popover v-if="submission.my_score === 0">
|
<n-popover v-if="submission.my_score === 0">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<n-button secondary type="primary">手动打分</n-button>
|
<n-button secondary type="primary">打分</n-button>
|
||||||
</template>
|
</template>
|
||||||
<n-rate :size="30" @update:value="updateScore" />
|
<n-rate :size="30" @update:value="updateScore" />
|
||||||
</n-popover>
|
</n-popover>
|
||||||
|
|||||||
@@ -11,6 +11,27 @@ import { step } from "../store/tutorial"
|
|||||||
import { taskId } from "../store/task"
|
import { taskId } from "../store/task"
|
||||||
import { useRouter } from "vue-router"
|
import { useRouter } from "vue-router"
|
||||||
|
|
||||||
|
marked.use({
|
||||||
|
renderer: {
|
||||||
|
code({ text, lang }) {
|
||||||
|
const language = lang?.toLowerCase() ?? "html"
|
||||||
|
return `<div class="codeblock-wrapper" data-lang="${language}">
|
||||||
|
<div class="codeblock-action">
|
||||||
|
<span class="lang">${language.toUpperCase()}</span>
|
||||||
|
<div class="btn-group">
|
||||||
|
<button class="action-btn" data-action="copy">复制</button>
|
||||||
|
<button class="action-btn" data-action="replace">替换</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<pre><code class="language-${language}">${text}</code></pre>
|
||||||
|
</div>`
|
||||||
|
},
|
||||||
|
link({ href, text }) {
|
||||||
|
return `<a href="${href}" target="_blank">${text}</a>`
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const tutorialIds = ref<number[]>([])
|
const tutorialIds = ref<number[]>([])
|
||||||
const content = ref("")
|
const content = ref("")
|
||||||
@@ -28,12 +49,12 @@ const nextDisabled = () => {
|
|||||||
|
|
||||||
function prev() {
|
function prev() {
|
||||||
const i = tutorialIds.value.indexOf(step.value)
|
const i = tutorialIds.value.indexOf(step.value)
|
||||||
step.value = tutorialIds.value[i - 1]
|
step.value = tutorialIds.value[i - 1] as number
|
||||||
}
|
}
|
||||||
|
|
||||||
function next() {
|
function next() {
|
||||||
const i = tutorialIds.value.indexOf(step.value)
|
const i = tutorialIds.value.indexOf(step.value)
|
||||||
step.value = tutorialIds.value[i + 1]
|
step.value = tutorialIds.value[i + 1] as number
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({ tutorialIds, prevDisabled, nextDisabled, prev, next })
|
defineExpose({ tutorialIds, prevDisabled, nextDisabled, prev, next })
|
||||||
@@ -44,91 +65,43 @@ async function prepare() {
|
|||||||
content.value = "暂无教程"
|
content.value = "暂无教程"
|
||||||
}
|
}
|
||||||
if (!tutorialIds.value.includes(step.value)) {
|
if (!tutorialIds.value.includes(step.value)) {
|
||||||
step.value = tutorialIds.value[0]
|
step.value = tutorialIds.value[0] as number
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getContent() {
|
async function render() {
|
||||||
const data = await Tutorial.get(step.value)
|
const data = await Tutorial.get(step.value)
|
||||||
taskId.value = data.task_ptr
|
taskId.value = data.task_ptr
|
||||||
const merged = `# #${data.display} ${data.title}\n${data.content}`
|
const merged = `# #${data.display} ${data.title}\n${data.content}`
|
||||||
content.value = await marked.parse(merged, { async: true })
|
content.value = await marked.parse(merged, { async: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
function addButton() {
|
function flash(btn: HTMLButtonElement, done: string, original: string) {
|
||||||
const existing = $content.value?.querySelectorAll(".codeblock-action") ?? []
|
btn.textContent = done
|
||||||
for (const el of existing) el.remove()
|
setTimeout(() => {
|
||||||
|
btn.textContent = original
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
const action = document.createElement("div")
|
function setupCodeActions() {
|
||||||
action.className = "codeblock-action"
|
$content.value?.addEventListener("click", (e: MouseEvent) => {
|
||||||
const pres = $content.value?.querySelectorAll("pre") ?? []
|
const btn = (e.target as HTMLElement).closest<HTMLButtonElement>("[data-action]")
|
||||||
for (const pre of pres) {
|
if (!btn) return
|
||||||
let timer = 0
|
const wrapper = btn.closest<HTMLElement>("[data-lang]")!
|
||||||
let copyTimer = 0
|
const lang = wrapper.dataset.lang ?? "html"
|
||||||
const actions = action.cloneNode() as HTMLDivElement
|
const code = wrapper.querySelector("code")?.textContent ?? ""
|
||||||
pre.insertBefore(actions, pre.children[0])
|
|
||||||
const $code = pre.childNodes[1] as HTMLPreElement
|
|
||||||
const match = $code.className.match(/-(.*)/)
|
|
||||||
let lang = "html"
|
|
||||||
if (match) lang = match[1].toLowerCase()
|
|
||||||
|
|
||||||
const langSpan = document.createElement("span")
|
if (btn.dataset.action === "copy") {
|
||||||
langSpan.className = "lang"
|
copyFn(code)
|
||||||
langSpan.textContent = lang.toUpperCase()
|
flash(btn, "已复制", "复制")
|
||||||
|
} else if (btn.dataset.action === "replace") {
|
||||||
const btnGroup = document.createElement("div")
|
|
||||||
btnGroup.className = "btn-group"
|
|
||||||
|
|
||||||
const copyBtn = document.createElement("button")
|
|
||||||
copyBtn.className = "action-btn"
|
|
||||||
copyBtn.textContent = "复制"
|
|
||||||
|
|
||||||
const replaceBtn = document.createElement("button")
|
|
||||||
replaceBtn.className = "action-btn"
|
|
||||||
replaceBtn.textContent = "替换"
|
|
||||||
|
|
||||||
btnGroup.appendChild(copyBtn)
|
|
||||||
btnGroup.appendChild(replaceBtn)
|
|
||||||
|
|
||||||
actions.appendChild(langSpan)
|
|
||||||
actions.appendChild(btnGroup)
|
|
||||||
|
|
||||||
copyBtn.onclick = () => {
|
|
||||||
const content = pre.children[1].textContent
|
|
||||||
copyFn(content ?? "")
|
|
||||||
copyBtn.textContent = "已复制"
|
|
||||||
clearTimeout(copyTimer)
|
|
||||||
copyTimer = setTimeout(() => {
|
|
||||||
copyBtn.textContent = "复制"
|
|
||||||
}, 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
replaceBtn.onclick = () => {
|
|
||||||
tab.value = lang
|
tab.value = lang
|
||||||
const content = pre.children[1].textContent
|
if (lang === "html") html.value = code
|
||||||
if (lang === "html") html.value = content
|
if (lang === "css") css.value = code
|
||||||
if (lang === "css") css.value = content
|
if (lang === "js") js.value = code
|
||||||
if (lang === "js") js.value = content
|
flash(btn, "已替换", "替换")
|
||||||
replaceBtn.textContent = "已替换"
|
|
||||||
clearTimeout(timer)
|
|
||||||
timer = setTimeout(() => {
|
|
||||||
replaceBtn.textContent = "替换"
|
|
||||||
}, 1000)
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
|
||||||
|
|
||||||
function modifyLink() {
|
|
||||||
const links = $content.value?.querySelectorAll("a") ?? []
|
|
||||||
for (const link of links) {
|
|
||||||
link.target = "_blank"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function render() {
|
|
||||||
await getContent()
|
|
||||||
addButton()
|
|
||||||
modifyLink()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
@@ -136,7 +109,10 @@ async function init() {
|
|||||||
render()
|
render()
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(init)
|
onMounted(() => {
|
||||||
|
setupCodeActions()
|
||||||
|
init()
|
||||||
|
})
|
||||||
watch(step, (v) => {
|
watch(step, (v) => {
|
||||||
router.push({ name: "home-tutorial", params: { display: v } })
|
router.push({ name: "home-tutorial", params: { display: v } })
|
||||||
render()
|
render()
|
||||||
@@ -160,8 +136,24 @@ watch(step, (v) => {
|
|||||||
font-family: Monaco;
|
font-family: Monaco;
|
||||||
}
|
}
|
||||||
|
|
||||||
.codeblock-action {
|
.markdown-body .codeblock-wrapper {
|
||||||
|
padding: 1rem;
|
||||||
|
background-color: #f6f8fa;
|
||||||
|
border-radius: 6px;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body .codeblock-wrapper pre {
|
||||||
|
padding: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
border-radius: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.codeblock-action {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
font-family:
|
font-family:
|
||||||
v-sans,
|
v-sans,
|
||||||
system-ui,
|
system-ui,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<n-grid class="container" x-gap="10" :cols="2">
|
<n-split class="container" direction="horizontal" :default-size="0.333" :min="0.2" :max="0.8">
|
||||||
<n-gi :span="1">
|
<template #1>
|
||||||
<n-flex vertical>
|
<n-flex vertical style="height: 100%; padding-right: 10px">
|
||||||
<n-flex justify="space-between">
|
<n-flex justify="space-between">
|
||||||
<n-button secondary @click="() => goHome($router, taskTab, step)">
|
<n-button secondary @click="() => goHome($router, taskTab, step)">
|
||||||
返回首页
|
返回首页
|
||||||
@@ -43,19 +43,21 @@
|
|||||||
:row-class-name="rowClassName"
|
:row-class-name="rowClassName"
|
||||||
></n-data-table>
|
></n-data-table>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
</n-gi>
|
</template>
|
||||||
<n-gi :span="1">
|
<template #2>
|
||||||
<Preview
|
<div style="height: 100%; padding-left: 10px">
|
||||||
v-if="submission.id"
|
<Preview
|
||||||
:html="html"
|
v-if="submission.id"
|
||||||
:css="css"
|
:html="html"
|
||||||
:js="js"
|
:css="css"
|
||||||
:submission-id="submission.id"
|
:js="js"
|
||||||
@after-score="afterScore"
|
:submission-id="submission.id"
|
||||||
@show-code="codeModal = true"
|
@after-score="afterScore"
|
||||||
/>
|
@show-code="codeModal = true"
|
||||||
</n-gi>
|
/>
|
||||||
</n-grid>
|
</div>
|
||||||
|
</template>
|
||||||
|
</n-split>
|
||||||
<n-modal preset="card" v-model:show="codeModal" style="max-width: 60%">
|
<n-modal preset="card" v-model:show="codeModal" style="max-width: 60%">
|
||||||
<template #header>
|
<template #header>
|
||||||
<n-flex align="center">
|
<n-flex align="center">
|
||||||
@@ -304,19 +306,16 @@ const columns: DataTableColumn<SubmissionOut>[] = [
|
|||||||
render: (submission) => h(TaskTitle, { submission }),
|
render: (submission) => h(TaskTitle, { submission }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "我打的分",
|
title: "得分",
|
||||||
key: "my_score",
|
|
||||||
render: (row) => {
|
|
||||||
if (row.my_score > 0) return row.my_score
|
|
||||||
else return "-"
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "平均得分",
|
|
||||||
key: "score",
|
key: "score",
|
||||||
|
width: 80,
|
||||||
render: (row) => {
|
render: (row) => {
|
||||||
if (row.score > 0) return row.score.toFixed(2)
|
const myScore = row.my_score > 0 ? String(row.my_score) : "-"
|
||||||
else return "-"
|
const avgScore = row.score > 0 ? row.score.toFixed(2) : "-"
|
||||||
|
return h("div", { style: { display: "flex", gap: "6px", alignItems: "baseline" } }, [
|
||||||
|
h("span", avgScore),
|
||||||
|
h("span", { style: { fontSize: "11px", color: "#999" } }, myScore),
|
||||||
|
])
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -420,6 +419,7 @@ onUnmounted(() => {
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
height: calc(100% - 43px);
|
height: calc(100% - 43px);
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.row-active td) {
|
:deep(.row-active td) {
|
||||||
|
|||||||
Reference in New Issue
Block a user