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:
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<div class="sf-art-page">
|
||||
<div class="sf-art-intro">
|
||||
<div><div class="sf-overline">SITEFORGE / WEBSITE FACTORY</div><h1>网站生产工厂</h1><p>多站点管理 · 旧站迁移 · 主题装配 · 可视化页面设计 · 静态发布</p></div>
|
||||
<el-button type="primary" size="large" @click="createVisible=true"><el-icon><Plus /></el-icon> 创建新网站</el-button>
|
||||
</div>
|
||||
<el-row :gutter="18" class="metric-line">
|
||||
<el-col :span="6"><el-card shadow="never"><div class="metric-label">网站数量</div><strong>{{ sites.length }}</strong></el-card></el-col>
|
||||
<el-col :span="6"><el-card shadow="never"><div class="metric-label">站点内容</div><strong>{{ sites.reduce((sum, s)=>sum+s.entries_count,0) }}</strong></el-card></el-col>
|
||||
<el-col :span="6"><el-card shadow="never"><div class="metric-label">构建版本</div><strong>{{ sites.reduce((sum, s)=>sum+s.build_count,0) }}</strong></el-card></el-col>
|
||||
<el-col :span="6"><el-card shadow="never"><div class="metric-label">行业主题</div><strong>{{ themes.length }}</strong></el-card></el-col>
|
||||
</el-row>
|
||||
<el-card shadow="never" class="listing-card">
|
||||
<template #header><div class="table-head"><div><h3>我的网站</h3><p>可选择站点继续编辑、批量套版或进行静态构建</p></div><el-input v-model="filter" clearable placeholder="搜索网站名称" style="width:240px" /></div></template>
|
||||
<el-table :data="visibleSites" v-loading="loading" size="large" stripe>
|
||||
<el-table-column label="网站项目" min-width="220"><template #default="{ row }"><div class="site-name"><span class="site-mark">{{ row.name[0] }}</span><div><b>{{ row.name }}</b><small>{{ row.domain || '尚未绑定域名' }}</small></div></div></template></el-table-column>
|
||||
<el-table-column label="主题" width="135"><template #default="{ row }"><el-tag effect="plain">{{ themeTitle(row.theme) }}</el-tag></template></el-table-column>
|
||||
<el-table-column prop="entries_count" label="内容" width="85" />
|
||||
<el-table-column prop="build_count" label="版本" width="85" />
|
||||
<el-table-column prop="updated_at" label="更新日期" min-width="180" />
|
||||
<el-table-column label="操作" width="350" fixed="right"><template #default="{ row }"><el-button link type="primary" @click="editSite(row)">管理站点</el-button><el-button link @click="openStudio(row)">设计首页</el-button><el-button link @click="applyPreset(row)">内容套版</el-button><el-button link type="success" @click="build(row)">构建</el-button></template></el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
<el-dialog v-model="createVisible" title="创建网站项目" width="540px" destroy-on-close>
|
||||
<el-form :model="draft" label-position="top">
|
||||
<el-form-item label="网站名称"><el-input v-model="draft.name" placeholder="例如:HESU Global Auto" /></el-form-item>
|
||||
<el-form-item label="网站地址"><el-input v-model="draft.domain" placeholder="https://example.com" /></el-form-item>
|
||||
<el-form-item label="描述"><el-input v-model="draft.tagline" placeholder="品牌服务介绍" /></el-form-item>
|
||||
<el-form-item label="行业主题"><el-select v-model="draft.theme" style="width:100%"><el-option v-for="t in themes" :key="t.id" :label="t.name" :value="t.id" /></el-select></el-form-item>
|
||||
<el-form-item label="建站模式"><el-radio-group v-model="draft.mode"><el-radio value="new">新建网站</el-radio><el-radio value="migration">旧站迁移</el-radio><el-radio value="hybrid">混合建站</el-radio></el-radio-group></el-form-item>
|
||||
</el-form>
|
||||
<template #footer><el-button @click="createVisible=false">取消</el-button><el-button type="primary" :loading="loading" @click="create">创建网站</el-button></template>
|
||||
</el-dialog>
|
||||
<el-dialog v-model="siteVisible" :title="active?.name || '网站管理'" width="680px">
|
||||
<el-tabs v-model="siteTab"><el-tab-pane label="基本信息" name="info"><p>主题:{{ themeTitle(active?.theme) }} · {{ active?.domain }}</p><p>{{ active?.tagline }}</p></el-tab-pane><el-tab-pane label="内容管理" name="content"><el-button @click="nativeConsole(active)">打开内容编辑工作台</el-button><p class="hint">支持页面、产品、文章、SEO及 WordPress/CSV/XLSX 数据导入。</p></el-tab-pane><el-tab-pane label="主题与编辑" name="design"><el-button type="primary" @click="openStudio(active)">进入 SiteForge Visual Studio</el-button></el-tab-pane><el-tab-pane label="静态发布" name="build"><el-button type="success" @click="build(active)">生成静态 HTML</el-button><p class="hint">构建后可以在 SiteForge Web 控制台预览、下载 ZIP 或发布到持久化目录。</p></el-tab-pane></el-tabs>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {computed,onMounted,ref} from 'vue'
|
||||
import {ElMessage,ElMessageBox} from 'element-plus'
|
||||
import {Plus} from '@element-plus/icons-vue'
|
||||
interface Site {id:string;name:string;domain:string;tagline:string;theme:string;entries_count:number;build_count:number;updated_at:string}
|
||||
interface Theme {id:string;name:string}
|
||||
const sites=ref<Site[]>([]),themes=ref<Theme[]>([]),filter=ref(''),loading=ref(false),createVisible=ref(false),siteVisible=ref(false)
|
||||
const active=ref<Site|null>(null),siteTab=ref('info'),csrf=ref('')
|
||||
const draft=ref({name:'',domain:'',tagline:'',theme:'corporate',mode:'new'})
|
||||
const visibleSites=computed(()=>sites.value.filter(s=>s.name.toLowerCase().includes(filter.value.trim().toLowerCase())))
|
||||
const themeTitle=(key?:string)=>themes.value.find(t=>t.id===key)?.name||key||'—'
|
||||
async function api<T>(path:string,init:RequestInit={}){
|
||||
const method=(init.method||'GET').toUpperCase(),headers=new Headers(init.headers)
|
||||
if(!['GET','HEAD'].includes(method))headers.set('X-SiteForge-CSRF',csrf.value)
|
||||
const res=await fetch(path,{...init,headers,credentials:'same-origin'})
|
||||
if(res.status===401)throw Error('请先通过 SiteForge 登录')
|
||||
const json=await res.json()
|
||||
if(!res.ok)throw Error(json.detail||'请求失败')
|
||||
return json as T
|
||||
}
|
||||
const post=(data:unknown):RequestInit=>({method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(data)})
|
||||
async function reload(){loading.value=true;try{const [list,ts]=await Promise.all([api<Site[]>('/api/sites'),api<Theme[]>('/api/themes')]);sites.value=list;themes.value=ts}catch(e:any){ElMessage.error(e.message)}finally{loading.value=false}}
|
||||
onMounted(async()=>{try{csrf.value=(await api<{csrf:string}>('/api/auth/me')).csrf;await reload()}catch(e:any){ElMessage.error(e.message)}})
|
||||
async function create(){if(!draft.value.name.trim())return ElMessage.warning('请输入网站名称');loading.value=true;try{await api('/api/sites',post(draft.value));createVisible.value=false;await reload();ElMessage.success('网站创建成功')}catch(e:any){ElMessage.error(e.message)}finally{loading.value=false}}
|
||||
function editSite(s:Site){active.value=s;siteTab.value='info';siteVisible.value=true}
|
||||
function nativeConsole(s:Site|null){if(s)window.open('/','_blank','noopener')}
|
||||
async function openStudio(s:Site|null){if(!s)return;try{const entries=await api<Array<{id:string;route:string}>>(`/api/sites/${s.id}/entries`);const home=entries.find(x=>x.route==='/')||entries[0];if(!home)return ElMessage.warning('网站暂无可编辑的页面');window.open(`/editor/${s.id}/${home.id}`,'_blank','noopener')}catch(e:any){ElMessage.error(e.message)}}
|
||||
async function applyPreset(s:Site){try{await ElMessageBox.confirm('自动给没有布局的页面装配新主题,原 URL、正文和 SEO 不变。继续吗?','批量内容套版');const r=await api<{updated:number}>(`/api/sites/${s.id}/apply-theme`,post({mode:'missing'}));ElMessage.success(`已完成 ${r.updated} 页布局装配`)}catch(e:any){if(e!=='cancel')ElMessage.error(e.message)}}
|
||||
async function build(s:Site|null){
|
||||
if(!s)return
|
||||
try{
|
||||
const task=await api<{id:string;state:string}>(`/api/sites/${s.id}/builds`,post({mirror_images:false}))
|
||||
ElMessage.info('构建任务已加入 Worker 队列,正在执行')
|
||||
for(let attempts=0;attempts<240;attempts++){
|
||||
await new Promise(resolve=>setTimeout(resolve,1500))
|
||||
const job=await api<{state:string;error:string;result_json:string}>(`/api/enterprise/sites/${s.id}/jobs/${task.id}`)
|
||||
if(job.state==='done'){
|
||||
const result=JSON.parse(job.result_json||'{}')
|
||||
ElMessage.success(`构建成功:${result.pages||0} 个静态页面`)
|
||||
if(result.version)window.open(`/preview/${s.id}/${result.version}/`,'_blank','noopener')
|
||||
await reload();return
|
||||
}
|
||||
if(['failed','cancelled'].includes(job.state))throw new Error(job.error||job.state)
|
||||
}
|
||||
ElMessage.warning('任务仍在运行,可以在构建中心查询进度')
|
||||
}catch(e:any){ElMessage.error(e.message)}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.sf-art-page{padding:4px 4px 30px}.sf-art-intro{display:flex;justify-content:space-between;align-items:center;gap:18px;margin-bottom:27px}.sf-art-intro h1{font-size:30px;color:var(--el-text-color-primary);margin:5px 0 7px}.sf-art-intro p,.table-head p{color:var(--el-text-color-secondary);font-size:13px;margin:0}.sf-overline{font-size:10px;letter-spacing:2px;font-weight:800;color:#5884d4}.metric-line{margin-bottom:22px}.metric-label{font-size:12px;color:var(--el-text-color-secondary);margin-bottom:10px}.metric-line strong{font-size:32px;line-height:1.2;display:block}.table-head{display:flex;justify-content:space-between;align-items:center}.table-head h3{margin:0 0 5px}.site-name{display:flex;align-items:center;gap:11px}.site-mark{background:#eaf1ff;color:#4b73ca;width:38px;height:38px;border-radius:10px;display:grid;place-items:center;font-size:16px;font-weight:800}.site-name b{display:block;font-size:13px}.site-name small{display:block;font-size:11px;color:#98a4b6;margin-top:3px}.hint{font-size:12px;color:#8997a8;line-height:1.8}
|
||||
</style>
|
||||
Reference in New Issue
Block a user