This commit is contained in:
2025-06-15 19:12:00 +08:00
parent 4beab17b70
commit 4556eb71d8
4 changed files with 660 additions and 82 deletions

View File

@@ -60,6 +60,7 @@ import { taskId, taskTab } from "../store/task"
import { useRoute, useRouter } from "vue-router"
import { TASK_TYPE } from "../utils/const"
import Challenge from "./Challenge.vue"
import { NButton } from 'naive-ui'
const route = useRoute()
const router = useRouter()
@@ -124,7 +125,6 @@ async function getContent() {
content.value = await marked.parse(merged, { async: true })
}
// 用 js 来写的,可以换成 vue 的方式
function addButton() {
const action = document.createElement("div")
action.className = "codeblock-action"
@@ -138,29 +138,48 @@ function addButton() {
const match = $code.className.match(/-(.*)/)
let lang = "html"
if (match) lang = match[1].toLowerCase()
actions.innerHTML = `<span class="lang">${lang.toUpperCase()}</span><div><div class="btn copy">复制</div><div class="btn">替换</div></div>`
const $copy = actions.children[1].children[0] as HTMLDivElement
const $replace = actions.children[1].children[1] as HTMLDivElement
$copy.onclick = () => {
const langSpan = document.createElement('span')
langSpan.className = 'lang'
langSpan.textContent = lang.toUpperCase()
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 ?? "")
$copy.innerHTML = "已复制"
copyBtn.textContent = "已复制"
clearTimeout(copyTimer)
copyTimer = setTimeout(() => {
$copy.innerHTML = "复制"
copyBtn.textContent = "复制"
}, 1000)
}
$replace.onclick = () => {
replaceBtn.onclick = () => {
tab.value = lang
const content = pre.children[1].textContent
if (lang === "html") html.value = content
if (lang === "css") css.value = content
if (lang === "js") js.value = content
// 样式
$replace.innerHTML = "已替换"
replaceBtn.textContent = "已替换"
clearTimeout(timer)
timer = setTimeout(() => {
$replace.innerHTML = "替换"
replaceBtn.textContent = "替换"
}, 1000)
}
}
@@ -243,23 +262,35 @@ watch(step, (v) => {
"Apple Color Emoji",
"Segoe UI Emoji",
"Segoe UI Symbol";
display: flex;
align-items: center;
justify-content: space-between;
}
.codeblock-action .lang {
font-size: 1.2rem;
font-size: 0.9rem;
font-weight: bold;
}
.codeblock-action .btn {
position: absolute;
top: 2px;
right: 0;
padding: 1rem;
.codeblock-action .btn-group {
display: flex;
gap: 0.5rem;
}
.codeblock-action .action-btn {
height: 28px;
padding: 0 14px;
font-size: 14px;
border-radius: 3px;
border: 1px solid #d9d9d9;
background-color: #fff;
color: #333;
cursor: pointer;
border-radius: 0.4rem;
font-size: 1rem;
transition: all 0.2s ease;
}
.codeblock-action .btn.copy {
right: 60px;
.codeblock-action .action-btn:hover {
border-color: #18a058;
color: #18a058;
}
</style>

View File

@@ -38,49 +38,41 @@
</n-flex>
</n-gi>
<n-gi :span="3" class="col">
<n-form>
<n-grid x-gap="10" :cols="4">
<n-gi :span="1">
<n-form-item label="序号">
<n-input-number v-model:value="tutorial.display" />
</n-form-item>
</n-gi>
<n-gi :span="2">
<n-form-item label="标题">
<n-input v-model:value="tutorial.title" />
</n-form-item>
</n-gi>
<n-gi :span="1">
<n-form-item label="公开">
<n-switch v-model:value="tutorial.is_public" />
</n-form-item>
</n-gi>
</n-grid>
<n-form-item label="内容">
<n-input
v-model:value="tutorial.content"
type="textarea"
class="editor"
/>
</n-form-item>
<n-button block type="primary" @click="submit" :disabled="!canSubmit">
提交
</n-button>
</n-form>
</n-gi>
<n-gi :span="6" class="col">
<n-flex vertical>
<n-form inline>
<n-form-item label="序号" label-placement="left">
<n-input-number v-model:value="tutorial.display" />
</n-form-item>
<n-gi :span="3" class="markdwon-body" v-html="content"></n-gi>
<n-form-item label="标题" label-placement="left">
<n-input v-model:value="tutorial.title" />
</n-form-item>
<n-form-item label="公开" label-placement="left">
<n-switch v-model:value="tutorial.is_public" />
</n-form-item>
<n-form-item label-placement="left">
<n-button type="primary" @click="submit" :disabled="!canSubmit">
提交
</n-button>
</n-form-item>
</n-form>
<MdEditor style="height: calc(100vh - 90px)" v-model="tutorial.content" />
</n-flex>
</n-gi>
</n-grid>
</template>
<script lang="ts" setup>
import { marked } from "marked"
import { computed, onMounted, reactive, ref, watch } from "vue"
import { computed, onMounted, reactive, ref } from "vue"
import { useRoute, useRouter } from "vue-router"
import { Icon } from "@iconify/vue"
import { Tutorial } from "../api"
import type { TutorialSlim } from "../utils/type"
import { useDialog, useMessage } from "naive-ui"
import { MdEditor } from "md-editor-v3"
//@ts-ignore
import "md-editor-v3/lib/style.css"
const route = useRoute()
const router = useRouter()
@@ -148,7 +140,6 @@ async function show(display: number) {
tutorial.title = item.title
tutorial.content = item.content
tutorial.is_public = item.is_public
content.value = await marked.parse(item.content, { async: true })
}
async function togglePublic(display: number) {
@@ -160,13 +151,6 @@ async function togglePublic(display: number) {
})
}
watch(
() => tutorial.content,
async (v: string) => {
content.value = await marked.parse(v, { async: true })
},
)
onMounted(getContent)
</script>
<style scoped>
@@ -186,10 +170,4 @@ onMounted(getContent)
.editor {
height: calc(100vh - 200px);
}
.markdwon-body {
box-sizing: border-box;
overflow: auto;
height: 100vh;
}
</style>