Files
cms-SiteForge/integrations/art-design-pro/src/views/siteforge/ThemeCenter.vue
T
浅念 3c57c167f2 chore: import SiteForge Factory V5 source (baseline)
导入 SiteForge Factory V5 基线源码:
- FastAPI + SQLite + Docker Compose(Web + Worker)架构
- 四套商业主题 × 14 页面模板(1.1.0,含历史版本快照)
- 四种建站模式骨架 + 迁移审计 + 发布门禁
- 52 项 pytest 测试
- 补全计划主文档 docs/superpowers/plans/

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-22 10:42:19 +08:00

35 lines
4.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="page-content">
<el-row justify="space-between" align="middle" style="margin-bottom:22px">
<div><h1>网站主题中心</h1><p>版本固定、主题安装与内容重新装配</p></div>
<el-upload :auto-upload="false" :limit="1" :show-file-list="false" accept=".zip" :on-change="install"><el-button type="primary">安装主题 ZIP</el-button></el-upload>
</el-row>
<el-card shadow="never" style="margin-bottom:20px"><el-form inline><el-form-item label="当前网站"><el-select v-model="siteId" filterable style="width:310px" @change="loadPinned"><el-option v-for="s in sites" :key="s.id" :label="s.name" :value="s.id"/></el-select></el-form-item></el-form></el-card>
<el-row :gutter="18"><el-col v-for="theme in themes" :key="theme.id||theme.theme_id" :xs="24" :sm="12" :lg="8" style="margin-bottom:20px"><el-card shadow="never"><div class="theme-cover" :class="theme.id||theme.theme_id"><span>WEBSITE<br>DESIGN<br>SYSTEM</span></div><h3>{{theme.name}}</h3><p class="secondary">{{theme.description}}</p><el-space><el-tag>v{{theme.version}}</el-tag><el-tag v-if="pinned.theme===(theme.id||theme.theme_id)" type="success">当前使用</el-tag></el-space><div class="buttons"><el-button type="primary" @click="apply(theme)">应用版本</el-button><el-button @click="versions(theme)">版本列表</el-button></div></el-card></el-col></el-row>
<el-card shadow="never"><h3>主题自动装配</h3><p class="secondary">自动为旧内容应用当前主题预设,保留历史 URL、SEO、正文内容。</p><el-button @click="assemble('missing')">仅补齐缺失页面</el-button><el-button type="warning" plain @click="assemble('overwrite')">覆盖全部页面布局</el-button></el-card>
</div>
</template>
<script setup lang="ts">
import {ref,onMounted} from 'vue'
import {ElMessage,ElMessageBox} from 'element-plus'
import type {UploadFile} from 'element-plus'
interface Site{id:string;name:string}
interface Theme{id?:string;theme_id?:string;name:string;description:string;version:string}
const sites=ref<Site[]>([]),themes=ref<Theme[]>([]),siteId=ref(''),pinned=ref({theme:'',version:''}),csrf=ref('')
async function request(url:string,method='GET',payload?:unknown,isFile=false){
const headers=new Headers();if(method!=='GET')headers.set('X-SiteForge-CSRF',csrf.value);if(payload&&!isFile)headers.set('Content-Type','application/json')
const r=await fetch(url,{method,headers,credentials:'same-origin',body:payload?(isFile?payload as FormData:JSON.stringify(payload)):undefined})
const v=await r.json();if(!r.ok)throw new Error(v.detail||'请求失败');return v
}
async function load(){try{sites.value=await request('/api/sites');themes.value=await request('/api/factory/themes');if(!siteId.value)siteId.value=sites.value[0]?.id||'';await loadPinned()}catch(e:any){ElMessage.error(e.message)}}
async function loadPinned(){if(siteId.value)pinned.value=await request(`/api/factory/sites/${siteId.value}/theme`)}
async function apply(theme:Theme){if(!siteId.value)return;try{const id=theme.id||theme.theme_id;await request(`/api/factory/sites/${siteId.value}/theme`,'PUT',{theme:id,version:theme.version});ElMessage.success('已绑定主题版本');await loadPinned()}catch(e:any){ElMessage.error(e.message)}}
async function versions(theme:Theme){try{let list=await request(`/api/factory/themes/${theme.id||theme.theme_id}/versions`);await ElMessageBox.alert(list.map((x:any)=>`${x.version} (${x.installed_at||'内置'})`).join('\n'),'主题版本',{confirmButtonText:'关闭'})}catch(e:any){ElMessage.error(e.message)}}
async function install(file:UploadFile){try{const form=new FormData();if(!file.raw)throw Error('未选择 ZIP');form.append('file',file.raw);let r=await request('/api/factory/themes/install','POST',form,true);ElMessage.success(`已安装 ${r.name} v${r.version}`);await load()}catch(e:any){ElMessage.error(e.message)}}
async function assemble(mode:string){if(!siteId.value)return;try{if(mode==='overwrite')await ElMessageBox.confirm('将覆盖所有已有页面蓝图,确定继续?','风险确认');let r=await request(`/api/sites/${siteId.value}/apply-theme`,'POST',{mode});ElMessage.success(`已装配 ${r.updated} 个页面`)}catch(e:any){if(e!=='cancel')ElMessage.error(e.message)}}
onMounted(async()=>{try{csrf.value=(await request('/api/auth/me')).csrf;await load()}catch(e:any){ElMessage.error(e.message)}})
</script>
<style scoped>
h1{margin:0 0 6px;font-size:26px}p.secondary,.page-content>p{font-size:13px;color:var(--el-text-color-secondary);line-height:1.7}.theme-cover{height:174px;border-radius:7px;background:#e6eafa;padding:25px;margin-bottom:16px;color:#3c4591}.theme-cover span{font-size:25px;line-height:1;letter-spacing:-1px;font-weight:900}.theme-cover.automotive{background:#172940;color:#e4c69a}.theme-cover.industrial{background:#e3edf6;color:#1c6292}.theme-cover.minimal{background:#eeeae3;color:#3b3b36}.buttons{margin-top:20px}
</style>