601451f22b
Overlay ~150 upstream items onto our V8.12 base, preserving trade-pro
theme, registry tests, and design docs. Re-applied our patch set
(discovery/env_for/blocks.css/type-mapping/theme_color/frontend entries,
test_theme_pro_v3 mods) — all anchors survived upstream evolution.
New platform enhancement (fixes upgrade-introduced regression):
multilingual.site_locale now honors settings_json.locale, plus
PUT /api/i18n/sites/{id}/locale — standalone English sites get
<html lang='en'> under the V8.15 locale-rewrite pipeline.
compose: adopt V8.21.3 (image 8.21.3-dev.1, /api/ready probes, MySQL/S3
env) with project name pinned to siteforge-v8-10 (data volumes).
Tests: 356 passed / 11 failed — all 11 confirmed pre-existing in
pristine V8.21.3 on this host (10 upstream Windows env + our build test
now green after locale fix). Patch registry updated in plan doc.
Co-Authored-By: Claude Code <noreply@anthropic.com>
83 lines
9.3 KiB
Vue
83 lines
9.3 KiB
Vue
<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 {preferredSite,rememberSite,sfApi} from './_shared'
|
||
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={}){return sfApi<T>(path,init)}
|
||
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>
|