2026-10-22 16:49:25 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<section class="siteforge-editor">
|
|
|
|
|
|
<el-card shadow="never">
|
|
|
|
|
|
<div class="toolbar">
|
|
|
|
|
|
<div><h2>可视化网站编辑器</h2><p>在统一管理后台内编辑,支持多列布局、图片替换、撤销/重做、设备预览和全局设计规范。</p></div>
|
|
|
|
|
|
<div class="actions"><el-select v-model="siteId" placeholder="选择网站" filterable @change="refreshPages" style="width:210px"><el-option v-for="s in sites" :key="s.id" :label="s.name" :value="s.id" /></el-select>
|
|
|
|
|
|
<el-select v-model="entryId" placeholder="选择页面" filterable style="width:220px"><el-option v-for="p in pages" :key="p.id" :label="p.title+' · '+p.route" :value="p.id" /></el-select>
|
|
|
|
|
|
<el-button type="primary" :disabled="!entryId" @click="loadEditor">打开页面</el-button></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
<div class="editor-viewport" v-if="url"><iframe :key="url" :src="url" title="SiteForge Visual Studio" /></div>
|
|
|
|
|
|
<el-empty v-else description="选择网站和页面,打开可视化编辑器" />
|
|
|
|
|
|
</section>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
2026-10-23 12:31:03 +08:00
|
|
|
|
import {watch,ref,onMounted} from 'vue'
|
2026-10-22 16:49:25 +08:00
|
|
|
|
import {ElMessage} from 'element-plus'
|
2026-10-23 12:31:03 +08:00
|
|
|
|
import {preferredSite,rememberSite,sfApi} from './_shared'
|
2026-10-22 16:49:25 +08:00
|
|
|
|
interface Site{id:string;name:string}
|
|
|
|
|
|
interface Page{id:string;title:string;route:string}
|
|
|
|
|
|
const sites=ref<Site[]>([]),pages=ref<Page[]>([]),siteId=ref(''),entryId=ref(''),url=ref('')
|
2026-10-23 12:31:03 +08:00
|
|
|
|
async function get<T>(path:string):Promise<T>{return sfApi<T>(path)}
|
2026-10-22 16:49:25 +08:00
|
|
|
|
async function refreshPages(){url.value='';pages.value=[];entryId.value='';if(!siteId.value)return;try{pages.value=await get(`/api/sites/${siteId.value}/entries`);entryId.value=pages.value.find(p=>p.route==='/')?.id||pages.value[0]?.id||''}catch(e:any){ElMessage.error(e.message)}}
|
|
|
|
|
|
function loadEditor(){if(entryId.value)url.value=`/editor/${siteId.value}/${entryId.value}`}
|
2026-10-23 12:31:03 +08:00
|
|
|
|
watch(siteId,(id)=>rememberSite(id))
|
|
|
|
|
|
onMounted(async()=>{try{sites.value=await get('/api/sites');if(sites.value.length){siteId.value=preferredSite(sites.value);await refreshPages()}}catch(e:any){ElMessage.error(e.message)}})
|
2026-10-22 16:49:25 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.siteforge-editor{display:flex;flex-direction:column;gap:12px;height:calc(100vh - 140px);min-height:680px}
|
|
|
|
|
|
.toolbar{display:flex;justify-content:space-between;gap:14px;align-items:center}
|
|
|
|
|
|
h2{margin:0;font-size:22px}p{margin:7px 0 0;color:var(--el-text-color-secondary);font-size:13px}.actions{display:flex;gap:10px;align-items:center}
|
|
|
|
|
|
.editor-viewport{flex:1;min-height:540px;border:1px solid var(--el-border-color);border-radius:12px;overflow:hidden;background:#fff}
|
|
|
|
|
|
.editor-viewport iframe{width:100%;height:100%;border:0}
|
|
|
|
|
|
@media(max-width:1020px){.toolbar,.actions{flex-wrap:wrap}.siteforge-editor{height:calc(100vh - 80px)}}
|
|
|
|
|
|
</style>
|