@@ -3,6 +3,7 @@ import {embedLayoutImages} from '../../../lib/layout-images';
import { createElement } from 'react' ;
import { renderToStaticMarkup } from 'react-dom/server' ;
import layouts from '../../../content/templates/layouts.json' ;
import standardLayout from '../../../content/standard-layout.json' ;
import { LayoutCanvas } from '../../../components/LayoutCanvas' ;
import { parameters , LayoutParameterError } from '../../../lib/layout-parameters' ;
const workspaceSchema = {
@@ -14,8 +15,16 @@ const workspaceSchema={
} ;
export default async function handler ( req :NextApiRequest , res :NextApiResponse ) {
res . setHeader ( 'Cache-Control' , 'no-store' ) ;
if ( String ( req . query . agent_mode ) . toLowerCase ( ) === 'true' && [ 'GET' , 'HEAD' ] . includes ( req . method || '' ) ) { res . setHeader ( 'Content-Type' , 'text/plain; charset=utf-8' ) ; if ( req . method === 'HEAD' ) return res . status ( 200 ) . end ( ) ; return res . send ( ` # 扩展编辑参数 \ n ${ JSON . stringify ( workspaceSchema , null , 2 ) } \ n \ n# 排版参数与渲染 API \ n \ nGET /studio/api/layouts/{id} 返回模板尺寸、T/I/D 构件 ID、坐标、原始文字可辨识状态与默认参数。id 为 ref-01 .. ref-27 。 \ n页面 /studio/layouts/{id} 直接提供原生 HTML/CSS 默认排版,不加载原图。带 ?id={designId} 在服务端加载已保存页面;ID 与排版不匹配或不存在返回404。右键或编辑页面可调整元素。 \ nGET 同地址 ?format=svg 是兼容的原图图层导出,与原生页面使用的 HTML/CSS 不同。POST JSON 参数返回 SVG,不自动保存。?format=json 返回规范化参数。 \ n参数整体形状:{texts:{T01:{value:"标题",color:"#26362f",scale:1,align:"start"}},images:{I01:{color:"#788880",assetId:"已上传图片UUID",fit:"cover"}},decorations:{D01:{color:"#53645b",hidden:false}},paper:"#f4f2eb"}。全部可省略;构件 ID 必须属于当前模板。文字 value 可为空(移除),最多500字/8行;scale 0.25– 2, align start/middle/end; fit cover/contain。图片先通过 /api/upload assetOnly=true 上传。 \ n未替换构件保留原始低分辨率图层;替换文字为 SVG 文字,未辨识小字不会编造。图位使用原始形状蒙版。paper 修改原底色层,不覆盖原图已有设计色块。 \ n文字、图像、装饰分组一一对应源图,不接受任意 URL、SVG 或未知构件。最大请求64KB。 \ n保存:POST /api/studio/designs {layoutId,name,parameters},返回 design.id/url/revision。再次打开 /studio/layouts/{layoutId}?id={id}(原生页面);/studio/designs/{id} 自动跳转;PATCH /api/studio/designs/{id} {name,parameters,revision} 更新。 \ n错误400参数不合法,404模板不存在,405方法不支持。 \ n目录:/studio/api/layouts?agent_mode=True \ n ` ) }
if ( String ( req . query . agent_mode ) . toLowerCase ( ) === 'true' && [ 'GET' , 'HEAD' ] . includes ( req . method || '' ) ) { res . setHeader ( 'Content-Type' , 'text/plain; charset=utf-8' ) ; if ( req . method === 'HEAD' ) return res . status ( 200 ) . end ( ) ; return res . send ( ` # 扩展编辑参数 \ n ${ JSON . stringify ( workspaceSchema , null , 2 ) } \ n \ n# 排版参数与渲染 API \ n \ nGET /studio/api/layouts/{id} 返回模板尺寸、T/I/D 构件 ID、坐标、原始文字可辨识状态与默认参数。id 为 standard-01(标准图文)或 ref-01 .. ref-27(固定构图)。standard-01 仅返回原生模板参数,不提供兼容 SVG 。 \ n页面 /studio/layouts/{id} 直接提供原生 HTML/CSS 默认排版,不加载原图。带 ?id={designId} 在服务端加载已保存页面;ID 与排版不匹配或不存在返回404。右键或编辑页面可调整元素。 \ nGET 同地址 ?format=svg 是兼容的原图图层导出,与原生页面使用的 HTML/CSS 不同。POST JSON 参数返回 SVG,不自动保存。?format=json 返回规范化参数。 \ n参数整体形状:{texts:{T01:{value:"标题",color:"#26362f",scale:1,align:"start"}},images:{I01:{color:"#788880",assetId:"已上传图片UUID",fit:"cover"}},decorations:{D01:{color:"#53645b",hidden:false}},paper:"#f4f2eb"}。全部可省略;构件 ID 必须属于当前模板。文字 value 可为空(移除),最多500字/8行;scale 0.25– 2, align start/middle/end; fit cover/contain。图片先通过 /api/upload assetOnly=true 上传。 \ n未替换构件保留原始低分辨率图层;替换文字为 SVG 文字,未辨识小字不会编造。图位使用原始形状蒙版。paper 修改原底色层,不覆盖原图已有设计色块。 \ n文字、图像、装饰分组一一对应源图,不接受任意 URL、SVG 或未知构件。最大请求64KB。 \ n保存:POST /api/studio/designs {layoutId,name,parameters},返回 design.id/url/revision。再次打开 /studio/layouts/{layoutId}?id={id}(原生页面);/studio/designs/{id} 自动跳转;PATCH /api/studio/designs/{id} {name,parameters,revision} 更新。 \ n错误400参数不合法,404模板不存在,405方法不支持。 \ n目录:/studio/api/layouts?agent_mode=True \ n ` ) }
if ( req . query . id === 'standard-01' ) {
if ( ! [ 'GET' , 'HEAD' , 'POST' ] . includes ( req . method || '' ) ) { res . setHeader ( 'Allow' , 'GET, HEAD, POST' ) ; return res . status ( 405 ) . end ( ) }
try { const p = parameters ( standardLayout , req . method === 'POST' ? req . body : { } ) ;
if ( req . query . format !== undefined && req . query . format !== 'json' ) return res . status ( 400 ) . json ( { error : '标准排版提供原生 HTML 页面,不提供 SVG 导出;format 仅支持 json' } ) ;
if ( req . method === 'HEAD' ) return res . status ( 200 ) . end ( ) ;
return res . json ( { . . . standardLayout , schemaVersion :1 , family : 'standard' , parameters :p , workspaceSchema , url : '/studio/layouts/standard-01' , saveApi : '/api/studio/designs' } ) ;
} catch ( e ) { return res . status ( 400 ) . json ( { error :e instanceof Error ? e . message : '参数不合法' } ) }
}
if ( ! [ 'GET' , 'HEAD' , 'POST' ] . includes ( req . method || '' ) ) { res . setHeader ( 'Allow' , 'GET, HEAD, POST' ) ; return res . status ( 405 ) . end ( ) } const layout = layouts . find ( l = > l . id === req . query . id ) ; if ( ! layout ) return res . status ( 404 ) . json ( { error : '排版不存在' } ) ;
try { if ( req . query . format !== undefined && ! [ 'svg' , 'json' ] . includes ( String ( req . query . format ) ) ) throw new LayoutParameterError ( 'format 为 svg 或 json' ) ; const params = parameters ( layout , req . method === 'POST' ? req . body : { } ) ; if ( req . method === 'HEAD' ) return res . status ( 200 ) . end ( ) ; if ( req . query . format === 'json' ) return res . json ( { layoutId :layout.id , parameters :params } ) ; if ( req . method === 'POST' || req . query . format === 'svg' ) { res . setHeader ( 'Content-Type' , 'image/svg+xml; charset=utf-8' ) ; return res . send ( renderToStaticMarkup ( createElement ( LayoutCanvas , { layout , params , imageSources :await embedLayoutImages ( req , params ) } ) ) ) } return res . json ( { id :layout.id , name :layout.name , width :layout.width , height :layout.height , sourceSha256 :layout.sourceSha256 , tutorialUrl :layout.tutorialUrl , elements :layout.elements.map ( ( { id , kind , role , box , direction , text , reading } ) = > ( { id , kind , role , box , direction , text , reading } ) ) , parameters :params , workspaceSchema , url : '/studio/layouts/' + layout . id } ) } catch ( e ) { return res . status ( 400 ) . json ( { error :e instanceof Error ? e . message : '参数不合法' } ) }
try { if ( req . query . format !== undefined && ! [ 'svg' , 'json' ] . includes ( String ( req . query . format ) ) ) throw new LayoutParameterError ( 'format 为 svg 或 json' ) ; const params = parameters ( layout , req . method === 'POST' ? req . body : { } ) ; if ( req . method === 'HEAD' ) return res . status ( 200 ) . end ( ) ; if ( req . query . format === 'json' ) return res . json ( { layoutId :layout.id , parameters :params } ) ; if ( req . method === 'POST' || req . query . format === 'svg' ) { res . setHeader ( 'Content-Type' , 'image/svg+xml; charset=utf-8' ) ; return res . send ( renderToStaticMarkup ( createElement ( LayoutCanvas , { layout , params , imageSources :await embedLayoutImages ( req , params ) } ) ) ) } return res . json ( { schemaVersion :1 , family : 'fixed' , id :layout.id , name :layout.name , width :layout.width , height :layout.height , sourceSha256 :layout.sourceSha256 , tutorialUrl :layout.tutorialUrl , elements :layout.elements.map ( ( { id , kind , role , box , direction , text , reading } ) = > ( { id , kind , role , box , direction , text , reading } ) ) , parameters :params , workspaceSchema , url : '/studio/layouts/' + layout . id } ) } catch ( e ) { return res . status ( 400 ) . json ( { error :e instanceof Error ? e . message : '参数不合法' } ) }
}
export const config = { api : { bodyParser : { sizeLimit : '64kb' } } } ;