feat: 前台动态路由与积木页面、网页路径/发布/模式、PAGE_BUILDER 文档

Made-with: Cursor
This commit is contained in:
whm
2026-03-19 16:20:48 +08:00
parent b17e99eb93
commit ea163dbf8e
11 changed files with 732 additions and 29 deletions

View File

@@ -17,9 +17,24 @@
<el-table-column label="ID" width="240">
<template #default="{ row }">{{ row.id }}</template>
</el-table-column>
<el-table-column prop="slug" label="Slug" width="120" />
<el-table-column prop="title" label="标题" width="160" />
<el-table-column prop="type" label="类型" width="100">
<el-table-column prop="slug" label="Slug" width="100" />
<el-table-column label="前台路径" min-width="120" show-overflow-tooltip>
<template #default="{ row }">{{ row.route_path || '/' + (row.slug || '') }}</template>
</el-table-column>
<el-table-column prop="title" label="标题" width="140" />
<el-table-column label="模式" width="90">
<template #default="{ row }">
<el-tag v-if="row.content_mode === 'builder'" type="warning" size="small">积木</el-tag>
<el-tag v-else type="info" size="small">HTML</el-tag>
</template>
</el-table-column>
<el-table-column label="发布" width="70">
<template #default="{ row }">
<el-tag v-if="row.published === false" type="danger" size="small"></el-tag>
<el-tag v-else type="success" size="small"></el-tag>
</template>
</el-table-column>
<el-table-column prop="type" label="类型" width="90">
<template #default="{ row }">
<el-tag v-if="row.type === 'homepage'" type="success" size="small">首页</el-tag>
<el-tag v-else size="small">页面</el-tag>
@@ -35,10 +50,13 @@
</el-table>
</el-card>
<el-dialog v-model="dialogVisible" :title="editId ? '编辑网页' : '新增网页'" width="560px" @close="resetForm">
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px">
<el-dialog v-model="dialogVisible" :title="editId ? '编辑网页' : '新增网页'" width="720px" @close="resetForm">
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<el-form-item label="Slug" prop="slug">
<el-input v-model="form.slug" placeholder="如 about、index" :disabled="!!editId" />
<el-input v-model="form.slug" placeholder="如 about、indexindex 为首页数据,一般不单独走路由)" :disabled="!!editId" />
</el-form-item>
<el-form-item label="前台路径">
<el-input v-model="form.route_path" placeholder="留空则自动为 /{slug},可填如 /download 或 /about/us" />
</el-form-item>
<el-form-item label="标题" prop="title">
<el-input v-model="form.title" placeholder="页面标题" />
@@ -49,8 +67,18 @@
<el-option label="首页" value="homepage" />
</el-select>
</el-form-item>
<el-form-item label="内容模式">
<el-select v-model="form.content_mode" placeholder="模式" style="width: 200px">
<el-option label="HTML 富文本" value="html" />
<el-option label="积木组装JSON" value="builder" />
</el-select>
<el-button type="primary" link style="margin-left: 12px" @click="insertBuilderTemplate">插入积木模板</el-button>
</el-form-item>
<el-form-item label="发布到前台">
<el-switch v-model="form.published" />
</el-form-item>
<el-form-item label="内容" prop="content">
<el-input v-model="form.content" type="textarea" :rows="8" placeholder="HTML 或 JSON" />
<el-input v-model="form.content" type="textarea" :rows="12" placeholder="HTML 模式直接写 HTML积木模式为 JSON见项目 docs/PAGE_BUILDER.md" />
</el-form-item>
</el-form>
<template #footer>
@@ -107,7 +135,80 @@ const dialogVisible = ref(false)
const editId = ref('')
const submitting = ref(false)
const formRef = ref(null)
const form = reactive({ site_id: '', slug: '', title: '', type: 'page', content: '' })
const builderTemplate = () =>
JSON.stringify(
{
version: 1,
blocks: [
{
id: 'h1',
type: 'heading',
props: { text: '页面标题', level: 2 },
animation: { enter: 'fadeIn', delay_ms: 0, duration_ms: 600 }
},
{
id: 't1',
type: 'text',
props: { text: '在此编辑说明文字,可在后台修改 JSON 调整模块与动画。' },
animation: { enter: 'slideUp', delay_ms: 100, duration_ms: 500 }
},
{
id: 'links',
type: 'link_list',
props: {
items: [
{ label: '回首页', url: '/' },
{ label: '示例外链', url: '#', target: '_blank' }
]
}
},
{
id: 'btn',
type: 'button',
props: { text: '主要按钮', url: '#', variant: 'primary' }
},
{ id: 'sp', type: 'spacer', props: { height: 24 } },
{
id: 'sec',
type: 'section',
props: { padding: '24px 0', maxWidth: '720px' },
children: [
{
id: 'sub',
type: 'text',
props: { html: '<p>区块内可嵌套子模块(<strong>section → children</strong>)。</p>' }
}
]
}
]
},
null,
2
)
const form = reactive({
site_id: '',
slug: '',
title: '',
type: 'page',
content: '',
content_mode: 'html',
route_path: '',
published: true
})
function insertBuilderTemplate() {
form.content_mode = 'builder'
if (!form.content?.trim()) {
form.content = builderTemplate()
} else {
ElMessageBox.confirm('将用模板覆盖当前内容?', '提示', { type: 'warning' })
.then(() => {
form.content = builderTemplate()
})
.catch(() => {})
}
}
const rules = {
slug: [{ required: true, message: '请输入 slug', trigger: 'blur' }],
title: [{ required: true, message: '请输入标题', trigger: 'blur' }]
@@ -120,6 +221,9 @@ const openDialog = (row) => {
form.title = row ? row.title : ''
form.type = row ? row.type || 'page' : 'page'
form.content = row ? row.content || '' : ''
form.content_mode = row?.content_mode || 'html'
form.route_path = row?.route_path || ''
form.published = row?.published !== false
dialogVisible.value = true
}
@@ -128,6 +232,9 @@ const resetForm = () => {
form.title = ''
form.type = 'page'
form.content = ''
form.content_mode = 'html'
form.route_path = ''
form.published = true
editId.value = ''
}
@@ -135,11 +242,20 @@ const submitForm = async () => {
await formRef.value?.validate()
submitting.value = true
try {
const payload = {
slug: form.slug,
title: form.title,
type: form.type,
content: form.content,
content_mode: form.content_mode,
route_path: form.route_path || undefined,
published: form.published
}
if (editId.value) {
await updatePage(editId.value, { slug: form.slug, title: form.title, type: form.type, content: form.content })
await updatePage(editId.value, payload)
ElMessage.success('更新成功')
} else {
await createPage({ ...form, site_id: siteId.value })
await createPage({ ...payload, site_id: siteId.value })
ElMessage.success('创建成功')
}
dialogVisible.value = false