|
- {% extends "../layout.html" %}
-
- {% block title %}{{ columnInfo.name if columnInfo else '单页管理' }}{% endblock %}
-
- {% block content %}
- <div id="pageApp">
- <el-card shadow="never">
- <template #header>
- <div class="flex items-center justify-between">
- <div class="flex items-center gap-2">
- <span class="font-medium">{{ columnInfo.name if columnInfo else '单页管理' }}</span>
- <el-tag type="info" size="small">单页</el-tag>
- <el-tag :type="pageStatus ? 'success' : 'warning'" size="small">${ pageStatus ? '已发布' : '草稿' }</el-tag>
- <span v-if="updateInfo" class="text-gray-400 text-xs ml-2">${ updateInfo }</span>
- </div>
- <div class="flex gap-2">
- <el-button :icon="View" @click="openPreview">预览</el-button>
- <el-button type="primary" :icon="DocumentChecked" @click="savePage" :loading="saving">保存</el-button>
- </div>
- </div>
- </template>
-
- <!-- WangEditor 富文本编辑器 -->
- <div class="editor-wrap">
- <div id="toolbar-container"></div>
- <div id="editor-container"></div>
- </div>
-
- <!-- 发布状态 -->
- <div class="flex items-center justify-between mt-4 pt-4 border-t">
- <div class="flex items-center gap-4">
- <span class="text-gray-500">发布状态:</span>
- <el-switch v-model="pageStatus" :active-value="1" :inactive-value="0" active-text="已发布" inactive-text="草稿"></el-switch>
- </div>
- <div class="flex gap-2">
- <el-button :icon="View" @click="openPreview">预览</el-button>
- <el-button type="primary" :icon="DocumentChecked" @click="savePage" :loading="saving">保存</el-button>
- </div>
- </div>
- </el-card>
- </div>
-
- <style>
- .editor-wrap { border: 1px solid #e4e7ed; border-radius: 4px; overflow: hidden; }
- #toolbar-container { border-bottom: 1px solid #e4e7ed; }
- #editor-container { min-height: 500px; }
- </style>
- {% endblock %}
-
- {% block js %}
- <!-- WangEditor -->
- <link href="https://unpkg.com/@wangeditor/editor@5.1.23/dist/css/style.css" rel="stylesheet">
- <script src="https://unpkg.com/@wangeditor/editor@5.1.23/dist/index.js"></script>
-
- <script>
- const col = '{{ col }}';
- const initContent = `{{ pageData.content | safe if pageData.content else "" }}`;
- const initStatus = parseInt('{{ pageData.status if pageData.status is defined else 1 }}') || 1;
- const initUpdateTime = '{{ pageData.update_time if pageData.update_time else "" }}';
- const initUpdateBy = '{{ pageData.update_by if pageData.update_by else "" }}';
-
- const { createApp, ref, onMounted, onBeforeUnmount } = Vue;
-
- const app = createApp({
- delimiters: ['${', '}'],
- setup() {
- const saving = ref(false);
- const pageStatus = ref(initStatus);
- const updateInfo = ref('');
- let editor = null;
-
- // 格式化更新信息
- if (initUpdateTime) {
- updateInfo.value = '最后更新:' + initUpdateTime.slice(0, 16).replace('T', ' ') + (initUpdateBy ? ' · ' + initUpdateBy : '');
- }
-
- onMounted(() => {
- // 创建编辑器
- const { createEditor, createToolbar } = window.wangEditor;
-
- editor = createEditor({
- selector: '#editor-container',
- html: initContent || '<p></p>',
- config: {
- placeholder: '请输入页面内容...',
- MENU_CONF: {
- uploadImage: {
- server: '/admin/upload',
- fieldName: 'file',
- maxFileSize: 10 * 1024 * 1024,
- customInsert(res, insertFn) {
- if (res.code === 0) {
- insertFn(res.data.url, res.data.name || '', res.data.url);
- } else {
- ElementPlus.ElMessage.error(res.msg || '上传失败');
- }
- }
- }
- }
- }
- });
-
- createToolbar({
- editor,
- selector: '#toolbar-container',
- config: {}
- });
- });
-
- onBeforeUnmount(() => {
- if (editor) {
- editor.destroy();
- editor = null;
- }
- });
-
- function openPreview() {
- if (!editor) return;
- const content = editor.getHtml();
- const columnName = '{{ columnInfo.name if columnInfo else "页面预览" }}';
-
- const html = `<!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>${columnName} - 预览</title>
- <style>
- * { margin: 0; padding: 0; box-sizing: border-box; }
- body { font-family: "Source Han Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif; color: #303133; background: #fff; }
- .preview-tip { background: #ff7800; color: #fff; padding: 10px 0; text-align: center; font-size: 13px; }
- .preview-tip span { background: rgba(0,0,0,.15); padding: 3px 10px; border-radius: 3px; font-size: 12px; margin-left: 8px; }
- .preview-header { background: #fff; border-bottom: 1px solid #e4e7ed; padding: 20px 40px; }
- .preview-header h1 { font-size: 24px; color: #1A3550; font-weight: 700; }
- .preview-body { max-width: 960px; margin: 0 auto; padding: 40px 20px 60px; font-size: 15px; line-height: 2; }
- .preview-body h1 { font-size: 28px; margin: 24px 0 16px; font-weight: 700; color: #1A3550; }
- .preview-body h2 { font-size: 22px; margin: 20px 0 12px; font-weight: 600; color: #1A3550; border-left: 4px solid #ff7800; padding-left: 14px; }
- .preview-body h3 { font-size: 17px; margin: 16px 0 10px; font-weight: 600; }
- .preview-body p { margin-bottom: 14px; }
- .preview-body blockquote { border-left: 4px solid #ff7800; padding: 14px 20px; margin: 16px 0; background: #fff8f0; color: #606266; }
- .preview-body ul, .preview-body ol { padding-left: 26px; margin-bottom: 14px; }
- .preview-body li { margin-bottom: 6px; }
- .preview-body table { width: 100%; border-collapse: collapse; margin: 16px 0; }
- .preview-body td, .preview-body th { border: 1px solid #e4e7ed; padding: 10px 14px; font-size: 14px; }
- .preview-body th { background: #fafafa; font-weight: 600; }
- .preview-body img { max-width: 100%; border-radius: 6px; margin: 12px 0; }
- .preview-body a { color: #ff7800; }
- </style>
- </head>
- <body>
- <div class="preview-tip">前台预览模式 <span>内容尚未发布,仅供预览</span></div>
- <div class="preview-header"><h1>${columnName}</h1></div>
- <div class="preview-body">${content}</div>
- </body>
- </html>`;
-
- const win = window.open('', '_blank');
- win.document.write(html);
- win.document.close();
- }
-
- async function savePage() {
- if (!editor) return;
- const content = editor.getHtml();
-
- saving.value = true;
- try {
- const res = await fetch('/admin/content/page/save', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ col, content, status: pageStatus.value })
- }).then(r => r.json());
-
- if (res.code === 0) {
- ElementPlus.ElMessage.success('保存成功');
- // 更新时间显示
- const now = new Date();
- const timeStr = now.toISOString().slice(0, 16).replace('T', ' ');
- updateInfo.value = '最后更新:' + timeStr + ' · 管理员';
- } else {
- ElementPlus.ElMessage.error(res.msg || '保存失败');
- }
- } finally {
- saving.value = false;
- }
- }
-
- return { saving, pageStatus, updateInfo, openPreview, savePage, View: ElementPlusIconsVue.View, DocumentChecked: ElementPlusIconsVue.DocumentChecked };
- }
- });
-
- for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
- app.component(key, component);
- }
- app.use(ElementPlus, { locale: ElementPlusLocaleZhCn });
- app.mount('#pageApp');
- </script>
- {% endblock %}
|