Files
浅念 601451f22b upgrade: adopt V8.21.3 (V8.13→8.21.3: P5 SFTP/S3, P6 multilingual, editor overhaul, enterprise hardening)
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>
2026-10-23 12:31:03 +08:00

39 lines
5.9 KiB
Vue
Raw Permalink 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="migration-center">
<div class="heading"><h2>CMS 迁移与数据核对</h2><p>导入前预览,保留原 URL;复杂关系和 Elementor 布局不会被静默丢弃。</p></div>
<el-card shadow="never"><el-form label-position="top">
<el-row :gutter="20"><el-col :span="8"><el-form-item label="目标网站"><el-select v-model="siteId" style="width:100%"><el-option v-for="s in sites" :key="s.id" :label="s.name" :value="s.id" /></el-select></el-form-item></el-col>
<el-col :span="8"><el-form-item label="原站系统"><el-select v-model="platform" style="width:100%"><el-option label="WordPress REST JSON" value="wordpress-rest"/><el-option label="WooCommerce JSON" value="woocommerce"/><el-option label="Drupal JSON:API" value="drupal-jsonapi"/><el-option label="Shopify Products JSON" value="shopify"/></el-select></el-form-item></el-col>
<el-col :span="8"><el-form-item label="导入文件"><input type="file" accept="application/json,.json" @change="setFile" /></el-form-item></el-col></el-row>
<el-space><el-button type="primary" :loading="loading" @click="preview">检查字段及差异</el-button><el-button :disabled="!report || report.errors.length>0" type="success" :loading="loading" @click="commit">确认导入</el-button></el-space>
</el-form></el-card>
<el-row v-if="report" :gutter="14" style="margin-top:15px"><el-col :span="8"><el-card>原记录:{{report.input}}</el-card></el-col><el-col :span="8"><el-card>可映射:{{report.mapped}}</el-card></el-col><el-col :span="8"><el-card>需人工检查:{{report.warnings.length}}</el-card></el-col></el-row>
<el-card v-if="report" shadow="never" style="margin-top:16px"><template #header>数据预览与检查结果</template>
<el-alert v-if="report.errors.length" type="error" :title="`存在 ${report.errors.length} 个错误,修复后才能导入`" :closable="false" />
<el-table :data="report.preview" max-height="300"><el-table-column prop="title" label="标题"/><el-table-column prop="type" label="类型" width="110"/><el-table-column prop="route" label="原路径"/><el-table-column prop="seo_title" label="SEO 标题"/></el-table>
<h4>字段与组件人工复核提示</h4><el-table :data="report.warnings" max-height="260"><el-table-column prop="row" label="记录" width="90"/><el-table-column prop="issue" label="问题" /></el-table>
</el-card>
<el-card shadow="never" style="margin-top:16px"><template #header><el-space>迁移人工复核 <el-tag type="warning">{{reviews.length}} 待处理</el-tag><el-button text @click="loadReviews">刷新</el-button></el-space></template>
<el-table :data="reviews" max-height="320"><el-table-column prop="platform" label="来源" width="150"/><el-table-column prop="row_number" label="行" width="75"/><el-table-column prop="issue" label="待处理问题"/><el-table-column label="复核" width="130"><template #default="{row}"><el-button link type="primary" @click="resolve(row.id)">处理并确认</el-button></template></el-table-column></el-table>
</el-card>
</div>
</template>
<script setup lang="ts">
import {watch,ref,onMounted} from 'vue'
import {ElMessage,ElMessageBox} from 'element-plus'
import {preferredSite,rememberSite,sfApi} from './_shared'
interface Site{id:string;name:string}
interface Report{input:number;mapped:number;errors:Array<{row:number;error:string}>;warnings:Array<{row:number;issue:string}>;preview:Array<{title:string;type:string;route:string;seo_title:string}>}
const sites=ref<Site[]>([]),siteId=ref(''),platform=ref('wordpress-rest'),file=ref<File|null>(null),report=ref<Report|null>(null),loading=ref(false),csrf=ref(''),reviews=ref<Array<{id:string;issue:string;platform:string;row_number:number}>>([])
function setFile(event:Event){file.value=(event.target as HTMLInputElement).files?.[0]||null;report.value=null}
async function request(url:string,init:RequestInit={}){return sfApi<any>(url,init)}
async function submit(mode:'preview'|'import'){if(!siteId.value||!file.value)return ElMessage.warning('请选网站和 JSON 文件');const body=new FormData();body.append('file',file.value);body.append('platform',platform.value);if(mode==='import')body.append('conflict','skip');loading.value=true;try{const data=await request(`/api/migrations/sites/${siteId.value}/${mode}`,{method:'POST',headers:{'X-SiteForge-CSRF':csrf.value},body});if(mode==='preview')report.value=data as Report;else{ElMessage.success(`导入完成:${data.result.imported} 条`);report.value=null;await loadReviews()}}catch(e:any){ElMessage.error(e.message)}finally{loading.value=false}}
async function preview(){await submit('preview')}
async function commit(){if(!report.value||report.value.errors.length)return;try{await ElMessageBox.confirm('导入后会写入网站数据库。复杂字段保留人工核对标记,是否继续?','确认导入');await submit('import')}catch{}}
async function loadReviews(){if(!siteId.value)return;try{reviews.value=(await request(`/api/migrations/sites/${siteId.value}/reviews`)).items}catch(e:any){ElMessage.error(e.message)}}
async function resolve(id:string){try{const result=await ElMessageBox.prompt('请输入实际核对结果或修正措施,至少12字','迁移复核',{inputValidator:(v:string)=>v.trim().length>=12||'请说明具体处理情况'});await request(`/api/migrations/sites/${siteId.value}/reviews/${id}/resolve`,{method:'POST',headers:{'Content-Type':'application/json','X-SiteForge-CSRF':csrf.value},body:JSON.stringify({resolution:result.value})});ElMessage.success('已保存复核记录');await loadReviews()}catch(e:any){if(e!=='cancel')ElMessage.error(e.message)}}
watch(siteId,(id)=>rememberSite(id))
onMounted(async()=>{try{csrf.value=(await request('/api/auth/me')).csrf;sites.value=await request('/api/sites');siteId.value=preferredSite(sites.value);await loadReviews()}catch(e:any){ElMessage.error(e.message)}})
</script>
<style scoped>.heading{margin-bottom:20px}.heading h2{margin:0;font-size:24px}.heading p{color:var(--el-text-color-secondary)}</style>