chore: baseline V8.10.0-dev.1 with v8_workflows type-mapping fix and trade-pro spec

- SiteForge Enterprise V8.10.0-dev.1 upstream source (210 tests)
- Fix: v8_workflows._mapped_input now reads both 类型/分类 columns so WXR
  imports keep post/product types instead of collapsing to page
- Docs: trade-pro theme port spec (R3) and implementation plan

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-10-22 16:49:25 +08:00
commit 8cea7dee20
354 changed files with 20736 additions and 0 deletions
@@ -0,0 +1,33 @@
<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">
import {ref,onMounted} from 'vue'
import {ElMessage} from 'element-plus'
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('')
async function get<T>(path:string):Promise<T>{const r=await fetch(path,{credentials:'same-origin'});if(!r.ok)throw Error((await r.json()).detail||'请求失败');return r.json()}
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}`}
onMounted(async()=>{try{sites.value=await get('/api/sites');if(sites.value.length){siteId.value=sites.value[0].id;await refreshPages()}}catch(e:any){ElMessage.error(e.message)}})
</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>