编辑帮助功能
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { getAdminHelpContent, previewAdminHelpContent, saveAdminHelpContent } from '../api'
|
||||
import type { HelpContent } from '../types'
|
||||
|
||||
const markdown = ref('')
|
||||
const previewHtml = ref('')
|
||||
const latest = ref<HelpContent | null>(null)
|
||||
const loading = ref(false)
|
||||
const previewing = ref(false)
|
||||
const saving = ref(false)
|
||||
const error = ref('')
|
||||
const message = ref('')
|
||||
let previewTimer: number | undefined
|
||||
|
||||
function formatTime(value: string | null): string {
|
||||
return value ? new Date(value).toLocaleString() : '默认内容'
|
||||
}
|
||||
|
||||
async function loadHelpContent() {
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
message.value = ''
|
||||
try {
|
||||
const response = await getAdminHelpContent()
|
||||
latest.value = response.item
|
||||
markdown.value = response.item.markdown
|
||||
previewHtml.value = response.item.html
|
||||
} catch (err) {
|
||||
error.value = err instanceof Error ? err.message : String(err)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function previewHelpContent() {
|
||||
previewing.value = true
|
||||
error.value = ''
|
||||
try {
|
||||
const response = await previewAdminHelpContent(markdown.value)
|
||||
previewHtml.value = response.html
|
||||
} catch (err) {
|
||||
error.value = err instanceof Error ? err.message : String(err)
|
||||
} finally {
|
||||
previewing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function schedulePreview() {
|
||||
if (previewTimer !== undefined) {
|
||||
window.clearTimeout(previewTimer)
|
||||
}
|
||||
previewTimer = window.setTimeout(() => {
|
||||
previewHelpContent()
|
||||
}, 400)
|
||||
}
|
||||
|
||||
async function saveHelpContent() {
|
||||
error.value = ''
|
||||
message.value = ''
|
||||
if (!markdown.value.trim()) {
|
||||
error.value = '帮助内容不能为空'
|
||||
return
|
||||
}
|
||||
saving.value = true
|
||||
try {
|
||||
const response = await saveAdminHelpContent(markdown.value)
|
||||
latest.value = response.item
|
||||
markdown.value = response.item.markdown
|
||||
previewHtml.value = response.item.html
|
||||
message.value = `帮助内容已保存为版本 #${response.item.id}`
|
||||
} catch (err) {
|
||||
error.value = err instanceof Error ? err.message : String(err)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadHelpContent)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="admin-dashboard">
|
||||
<div class="panel admin-status-panel">
|
||||
<div class="panel-header">
|
||||
<div>
|
||||
<p class="eyebrow">Help</p>
|
||||
<h2>帮助编辑</h2>
|
||||
</div>
|
||||
<div class="admin-actions">
|
||||
<button class="admin-button" @click="loadHelpContent" :disabled="loading || saving">{{ loading ? '加载中...' : '重新加载' }}</button>
|
||||
<button class="admin-button" @click="saveHelpContent" :disabled="loading || saving">{{ saving ? '保存中...' : '保存新版本' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p v-if="error" class="error">{{ error }}</p>
|
||||
<p v-if="message" class="success">{{ message }}</p>
|
||||
<p class="help-version muted">
|
||||
当前版本:{{ latest?.id ? `#${latest.id}` : '默认内容' }} · 创建人:{{ latest?.created_by || '-' }} · 时间:{{ formatTime(latest?.created_at ?? null) }}
|
||||
</p>
|
||||
|
||||
<div class="help-editor-grid">
|
||||
<label class="help-editor-pane">
|
||||
<span>Markdown 内容</span>
|
||||
<textarea v-model="markdown" @input="schedulePreview" placeholder="请输入帮助内容 Markdown"></textarea>
|
||||
</label>
|
||||
<div class="help-editor-pane help-preview-pane">
|
||||
<div class="help-preview-header">
|
||||
<span>预览</span>
|
||||
<button class="admin-button" @click="previewHelpContent" :disabled="previewing">{{ previewing ? '预览中...' : '刷新预览' }}</button>
|
||||
</div>
|
||||
<div class="markdown-body help-preview" v-html="previewHtml"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -1,3 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { getHelpContent } from '../api'
|
||||
|
||||
const loading = ref(false)
|
||||
const error = ref('')
|
||||
const html = ref('')
|
||||
|
||||
async function loadHelpContent() {
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
try {
|
||||
const response = await getHelpContent()
|
||||
html.value = response.item.html
|
||||
} catch (err) {
|
||||
error.value = err instanceof Error ? err.message : String(err)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadHelpContent)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="help-page">
|
||||
<div class="panel">
|
||||
@@ -9,32 +33,9 @@
|
||||
</div>
|
||||
|
||||
<div class="help-content">
|
||||
<section>
|
||||
<h3>连接地址</h3>
|
||||
<p>将 Meshtastic 设备连接到本服务提供的 MQTT broker。</p>
|
||||
<ul>
|
||||
<li>默认地址:<strong>mesh.gat-iot.com</strong></li>
|
||||
<li>默认端口:<strong>1883</strong></li>
|
||||
<li>用户名称:<strong>meshdev</strong></li>
|
||||
<li>密码:<strong>large4cats</strong></li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>频道加密要求</h3>
|
||||
<p>为了让服务能够解析 Meshtastic MQTT payload,频道需要满足以下任一条件:</p>
|
||||
<ul>
|
||||
<li>频道不加密。</li>
|
||||
<li>使用 Meshtastic 默认 PSK:<strong>AQ==</strong>。</li>
|
||||
</ul>
|
||||
<p>如果使用自定义加密密钥,数据可能会被判定为无法解密并丢弃。</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>反馈问题</h3>
|
||||
<p>如果遇到 bug,请在 GitHub <a href="https://github.com/wuwenfengmi1998/meshtastic_mqtt_server" target="_blank" rel="noopener noreferrer">提交 issue</a>,或联系邮箱 <a href="mailto:kevin@lmve.net">kevin@lmve.net</a>。</p>
|
||||
</section>
|
||||
|
||||
<p v-if="loading" class="muted">正在加载帮助内容...</p>
|
||||
<p v-else-if="error" class="error">{{ error }}</p>
|
||||
<div v-else class="markdown-body" v-html="html"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user