fix: replace 82 smart quotes with straight quotes in workspace.js
This commit is contained in:
@@ -84,7 +84,7 @@ function guide(){
|
||||
<p><strong>生成网站</strong><span>制作一份可预览、可下载的静态网站版本。</span></p>
|
||||
<p><strong>检查与审批</strong><span>核对页面、图片、网址和 SEO,并取得相应审批。</span></p>
|
||||
<p><strong>正式上线</strong><span>把批准的版本部署到正式服务器,再核对公网访问。</span></p></div></section>
|
||||
<section class="sf-journey-notice" role="note"><strong>重要说明</strong>:${journey.live?'系统发现正式发布记录,但无法仅凭该记录确认当前公网网站可访问,请人工检查实际域名。':'当前没有可确认的正式发布记录;生成 ZIP 或点击“发布设计”都不代表网站已上线。'}${journey.changedAfterBuild?' 网站内容在最近一次构建后可能有变化,建议重新构建并预览。':''}${!journey.domain?' 正式域名尚未填写,不影响预览。':''}</section>`;
|
||||
<section class="sf-journey-notice" role="note"><strong>重要说明</strong>:${journey.live?'系统发现正式发布记录,但无法仅凭该记录确认当前公网网站可访问,请人工检查实际域名。':'当前没有可确认的正式发布记录;生成 ZIP 或点击"发布设计"都不代表网站已上线。'}${journey.changedAfterBuild?' 网站内容在最近一次构建后可能有变化,建议重新构建并预览。':''}${!journey.domain?' 正式域名尚未填写,不影响预览。':''}</section>`;
|
||||
}
|
||||
// V8.28 guided delivery: one deliberate action per stage, existing secure APIs only.
|
||||
function launch(){
|
||||
@@ -124,11 +124,11 @@ function content(){if(!currentSite())return noSite();let arr=state.entries.filte
|
||||
const pageItems=arr.slice(start,start+PAGE_SIZE);
|
||||
const typeCounts={};state.entries.forEach(e=>typeCounts[e.type]=(typeCounts[e.type]||0)+1);
|
||||
const tabs=[{key:'',label:'全部',count:state.entries.length},...Object.entries(typeName).filter(([k])=>typeCounts[k]).map(([key,value])=>({key,label:value,count:typeCounts[key]}))];
|
||||
const tabHtml=`<div class=”type-tabs” role=”tablist”>${tabs.map(t=>`<button class=”type-tab ${(!state.typ&&t.key==='')||(state.typ===t.key)?'active':''}” role=”tab” data-action=”set-type-tab” data-type=”${t.key}”>${t.label}<span class=”count”>${t.count}</span></button>`).join('')}</div>`;
|
||||
const pagination=totalPages>1?`<div class=”pagination”><button class=”page-btn” data-action=”page-prev” ${state.page<=1?'disabled':''}>‹ 上一页</button><span class=”page-info”>第 <strong>${state.page}</strong> / ${totalPages} 页 · 共 ${arr.length} 条</span><button class=”page-btn primary” data-action=”page-next” ${state.page>=totalPages?'disabled':''}>下一页 ›</button></div>`:'';
|
||||
const tabHtml=`<div class="type-tabs" role="tablist">${tabs.map(t=>`<button class="type-tab ${(!state.typ&&t.key==='')||(state.typ===t.key)?'active':''}" role="tab" data-action="set-type-tab" data-type="${t.key}">${t.label}<span class="count">${t.count}</span></button>`).join('')}</div>`;
|
||||
const pagination=totalPages>1?`<div class="pagination"><button class="page-btn" data-action="page-prev" ${state.page<=1?'disabled':''}>‹ 上一页</button><span class="page-info">第 <strong>${state.page}</strong> / ${totalPages} 页 · 共 ${arr.length} 条</span><button class="page-btn primary" data-action="page-next" ${state.page>=totalPages?'disabled':''}>下一页 ›</button></div>`:'';
|
||||
return htmlHeading('内容与页面','所有网页、产品、车型和新闻都在这里维护,保存后重新构建即可更新网站。',actionButton('+ 添加内容','new-entry'))+
|
||||
`<div class=”panel”><div class=”panel-head”><div><h2>内容列表</h2><p>共 ${state.entries.length} 条内容。点击”设计页面”可进行可视化排版。</p></div></div><div style=”margin-bottom:12px”>${tabHtml}</div><div class=”toolbar”><input id=”contentSearch” aria-label=”搜索内容” placeholder=”搜索标题或页面路径…” value=”${esc(state.search)}”></div>`+
|
||||
(pageItems.length?`<div class=”table-wrap”><table class=”data-table content-table”><thead><tr><th style=”min-width:200px”>名称</th><th>类型</th><th style=”min-width:180px”>页面地址</th><th>状态</th><th style=”min-width:220px”>操作</th></tr></thead><tbody>${pageItems.map(e=>`<tr><td><strong>${esc(e.title)}</strong></td><td><span class=”type-badge ${e.type}”>${esc(typeName[e.type]||e.type)}</span></td><td><span class=”route-code”>${esc(e.route)}</span></td><td><span class=”status-dot ${e.status==='draft'?'draft':'published'}”>${e.status==='draft'?'草稿':'已发布'}</span></td><td class=”action-cell”><button class=”action-btn” data-action=”edit-entry” data-id=”${esc(e.id)}”>✎ 修改内容</button>${e.id?` <a class=”action-btn design” href=”/editor/${encodeURIComponent(state.site.id)}/${encodeURIComponent(e.id)}”>▤ 设计页面</a>`:''}</td></tr>`).join('')}</tbody></table></div>${pagination}`:emptyState('没有找到内容','可以清除筛选条件,或者添加新内容。',actionButton('+ 添加内容','new-entry')))+
|
||||
`<div class="panel"><div class="panel-head"><div><h2>内容列表</h2><p>共 ${state.entries.length} 条内容。点击"设计页面"可进行可视化排版。</p></div></div><div style="margin-bottom:12px">${tabHtml}</div><div class="toolbar"><input id="contentSearch" aria-label="搜索内容" placeholder="搜索标题或页面路径…" value="${esc(state.search)}"></div>`+
|
||||
(pageItems.length?`<div class="table-wrap"><table class="data-table content-table"><thead><tr><th style="min-width:200px">名称</th><th>类型</th><th style="min-width:180px">页面地址</th><th>状态</th><th style="min-width:220px">操作</th></tr></thead><tbody>${pageItems.map(e=>`<tr><td><strong>${esc(e.title)}</strong></td><td><span class="type-badge ${e.type}">${esc(typeName[e.type]||e.type)}</span></td><td><span class="route-code">${esc(e.route)}</span></td><td><span class="status-dot ${e.status==='draft'?'draft':'published'}">${e.status==='draft'?'草稿':'已发布'}</span></td><td class="action-cell"><button class="action-btn" data-action="edit-entry" data-id="${esc(e.id)}">✎ 修改内容</button>${e.id?` <a class="action-btn design" href="/editor/${encodeURIComponent(state.site.id)}/${encodeURIComponent(e.id)}">▤ 设计页面</a>`:''}</td></tr>`).join('')}</tbody></table></div>${pagination}`:emptyState('没有找到内容','可以清除筛选条件,或者添加新内容。',actionButton('+ 添加内容','new-entry')))+
|
||||
`</div>`;
|
||||
}
|
||||
function design(){if(!currentSite())return noSite();const s=state.site;const cards=Object.entries(themeName).map(([id,name])=>`<div class="theme-card"><div class="theme-preview ${id}">${name}</div><div class="theme-desc"><h3>${name} ${s.theme===id?'<span class="pill success">当前使用</span>':''}</h3><p>${({automotive:'适合汽车出口、车型及品牌展示',industrial:'适合制造业、产品与技术参数',corporate:'适合集团公司、服务和案例',minimal:'适合作品展示和品牌形象','trade-pro':'适合外贸 B2B 英文站点与询盘'})[id]}</p><button class="btn btn-tiny ${s.theme===id?'btn-muted':'btn-primary'}" ${s.theme===id?'disabled':''} data-action="set-theme" data-theme="${id}">${s.theme===id?'正在使用':'使用此主题'}</button><a class="btn btn-tiny btn-muted" href="/theme-gallery?theme=${encodeURIComponent(id)}" target="_blank" rel="noopener">预览页面效果</a></div></div>`).join('');
|
||||
@@ -158,7 +158,7 @@ function builds(){if(!currentSite())return noSite();const latest=latestDone();co
|
||||
<h3>最近发布任务</h3>${(state.delivery.jobs||[]).length?state.delivery.jobs.slice(0,8).map(j=>`<div class="build-row"><div class="build-meta"><strong>${j.kind==='remote_rollback'?'回滚':'部署'} · ${esc(buildStatusLabel(j.state))}</strong><p>${esc(j.id.slice(0,12))} · ${esc(j.created_at)}${j.error?' · '+esc(j.error):''}</p></div>${['queued','running','retrying'].includes(j.state)?`<button class="btn btn-tiny btn-danger" data-action="cancel-job" data-id="${esc(j.id)}">取消</button>`:''}</div>`).join(''):'<p class="muted">暂无发布任务。</p>'}
|
||||
<h3>已发布版本</h3>${(state.delivery.history||[]).length?state.delivery.history.slice(0,8).map(r=>`<div class="build-row"><div class="build-meta"><strong>${esc(r.target)} · ${esc(r.release_id)}</strong><p>构建 ${esc(r.build_id.slice(0,12))} · ${esc(r.deployed_at)}</p></div><button class="btn btn-tiny" data-action="release-rollback" data-target="${esc(r.target)}" data-id="${esc(r.release_id)}">回滚到此版本</button></div>`).join(''):'<p class="muted">当前没有远程部署记录。</p>'}</div></details>`;
|
||||
}
|
||||
function settings(){if(!currentSite())return noSite();const s=state.site;return htmlHeading('网站设置','这里只管理公司资料、域名与站点属性。网页样式请到“外观与设计”修改。')+
|
||||
function settings(){if(!currentSite())return noSite();const s=state.site;return htmlHeading('网站设置','这里只管理公司资料、域名与站点属性。网页样式请到"外观与设计"修改。')+
|
||||
`<div class="panel"><h2>基本资料</h2><p class="panel-desc">没有域名也可以制作与预览网站,正式上线前再填写即可。</p><form id="settingsForm"><div class="field-grid"><label class="full">网站名称<input name="name" required value="${esc(s.name)}"></label><label class="full">正式域名<input name="domain" placeholder="https://www.example.com" value="${esc(s.domain||'')}"></label><label class="full">企业介绍 / 网站标语<textarea name="tagline" rows="3">${esc(s.tagline||'')}</textarea></label><label class="full">Logo 图片地址<input name="logo" value="${esc(s.logo||'')}"></label><label>网站类型<select name="mode"><option value="new" ${s.mode==='new'?'selected':''}>新建网站</option><option value="migration" ${s.mode==='migration'?'selected':''}>旧站迁移</option><option value="hybrid" ${s.mode==='hybrid'?'selected':''}>混合建站</option></select></label><label>主题色<input type="color" name="accent" value="${esc(s.accent||'#245ac6')}" style="padding:4px;height:43px"></label></div><div class="form-actions"><button class="btn btn-primary" type="submit">保存网站设置</button></div></form></div>`;
|
||||
}
|
||||
function languages(){
|
||||
@@ -166,13 +166,13 @@ function languages(){
|
||||
const d=state.language||{group:null,sites:[],units:[]};const g=d.group;
|
||||
const groups=state.languageGroups||[];
|
||||
const header=htmlHeading('多语言管理','多个语言版本分别拥有独立的网站、域名和内容。系统只关联已审核的翻译,不会自动翻译企业事实。','<button class="btn" data-action="language-refresh">刷新语言状态</button>');
|
||||
if(!g)return header+`<div class="panel"><h2>第一步 · 建立语言组</h2><p class="panel-desc">例如“集团全球网站”包含中文版、英文版、法文版。创建语言组后将网站加入对应语言。</p><form id="langGroupForm"><div class="field-grid"><label>语言组名称<input name="name" required minlength="2" placeholder="HESU 国际站"></label><label>默认语言<select name="default_locale"><option value="en">English (en)</option><option value="zh-CN">简体中文 (zh-CN)</option><option value="fr-FR">Français (fr-FR)</option><option value="ar">العربية (ar)</option></select></label><label>当前网站的实际语言<input name="current_locale" value="zh-CN" required placeholder="例如 zh-CN 或 en"></label></div><div class="form-actions"><button class="btn btn-primary">创建语言组并关联当前网站</button></div></form></div>`+
|
||||
if(!g)return header+`<div class="panel"><h2>第一步 · 建立语言组</h2><p class="panel-desc">例如"集团全球网站"包含中文版、英文版、法文版。创建语言组后将网站加入对应语言。</p><form id="langGroupForm"><div class="field-grid"><label>语言组名称<input name="name" required minlength="2" placeholder="HESU 国际站"></label><label>默认语言<select name="default_locale"><option value="en">English (en)</option><option value="zh-CN">简体中文 (zh-CN)</option><option value="fr-FR">Français (fr-FR)</option><option value="ar">العربية (ar)</option></select></label><label>当前网站的实际语言<input name="current_locale" value="zh-CN" required placeholder="例如 zh-CN 或 en"></label></div><div class="form-actions"><button class="btn btn-primary">创建语言组并关联当前网站</button></div></form></div>`+
|
||||
(groups.length?`<div class="panel"><h2>加入已有语言组</h2><p class="panel-desc">仅可关联您有管理权限、且已配置独立 HTTPS 域名的网站。</p><form id="langJoinForm"><div class="field-grid"><label>语言组<select name="group_id">${groups.map(x=>`<option value="${esc(x.id)}">${esc(x.name)}</option>`).join('')}</select></label><label>当前网站的语言<input name="locale" value="zh-CN" required placeholder="zh-CN / en / fr-FR"></label></div><div class="form-actions"><button class="btn btn-primary">加入语言组</button></div></form></div>`:'')+
|
||||
`<div class="help-hint"><strong>上线前提示:</strong>网站设置中的正式域名应为 https://example.com 形式;不同语言网站不能共用一个域名。当前域名:${esc(state.site.domain||'未设置')}</div>`;
|
||||
const groupSites=d.sites||[]; const uniqueUnits=[...new Map((d.units||[]).map(u=>[u.id,u])).values()];
|
||||
const canJoin=state.sites.filter(x=>!groupSites.some(m=>m.site_id===x.id));
|
||||
return header+`<div class="panel"><div class="panel-head"><div><h2>${esc(g.name)}</h2><p>当前站语言 ${esc(g.locale)} · 默认语言 ${esc(g.default_locale)} · ${groupSites.length} 个语言站点</p></div></div><div class="table-wrap"><table class="data-table"><thead><tr><th>语言</th><th>网站</th><th>正式域名</th></tr></thead><tbody>${groupSites.map(x=>`<tr><td>${esc(x.locale)}</td><td>${esc(x.name)}</td><td>${esc(x.domain)}</td></tr>`).join('')}</tbody></table></div></div>`+
|
||||
`<div class="panel"><h2>第二步 · 添加其他语言网站</h2><form id="langJoinForm"><div class="field-grid"><input type="hidden" name="group_id" value="${esc(g.group_id)}"><label>已有网站<select name="site_id">${canJoin.map(x=>`<option value="${esc(x.id)}">${esc(x.name)}</option>`).join('')}</select></label><label>对应语言<input name="locale" required placeholder="例如 en、fr-FR"></label></div><div class="form-actions"><button class="btn btn-primary" ${!canJoin.length?'disabled':''}>加入语言站</button></div></form><p class="muted">先通过“我的网站”创建另一语言站并设置 HTTPS 域名,再加入语言组。</p></div>`+
|
||||
`<div class="panel"><h2>第二步 · 添加其他语言网站</h2><form id="langJoinForm"><div class="field-grid"><input type="hidden" name="group_id" value="${esc(g.group_id)}"><label>已有网站<select name="site_id">${canJoin.map(x=>`<option value="${esc(x.id)}">${esc(x.name)}</option>`).join('')}</select></label><label>对应语言<input name="locale" required placeholder="例如 en、fr-FR"></label></div><div class="form-actions"><button class="btn btn-primary" ${!canJoin.length?'disabled':''}>加入语言站</button></div></form><p class="muted">先通过"我的网站"创建另一语言站并设置 HTTPS 域名,再加入语言组。</p></div>`+
|
||||
`<div class="panel"><h2>第三步 · 建立翻译内容关系</h2><p class="panel-desc">相同产品、文章或页面可以使用不同 URL。用一个内容标识关联它们,只有正式发布且审核通过的内容才会生成 hreflang。</p><form id="langUnitForm"><div class="field-grid"><label class="full">翻译组标识<input name="reference_key" required placeholder="例如 product:rox-01 或 about-page"></label></div><div class="form-actions"><button class="btn btn-primary">创建内容翻译组</button></div></form></div>`+
|
||||
`<div class="panel"><h2>第四步 · 关联各语言的实际内容</h2><form id="langLinkForm"><div class="field-grid"><label>翻译内容组<select name="unit_id">${uniqueUnits.map(x=>`<option value="${esc(x.id)}">${esc(x.reference_key)}</option>`).join('')}</select></label><label>语言网站<select id="langMemberSite" name="site_id">${groupSites.map(x=>`<option value="${esc(x.site_id)}" ${x.site_id===state.site.id?'selected':''}>${esc(x.locale)} · ${esc(x.name)}</option>`).join('')}</select></label><label class="full">对应内容<select id="langMemberEntry" name="entry_id">${(state.languageEntries||[]).map(x=>`<option value="${esc(x.id)}">${esc(x.title)} (${esc(x.route)}) · ${x.status==='published'?'正式':'草稿'}</option>`).join('')}</select></label><label>审核状态<select name="state"><option value="pending">待翻译(仅允许草稿)</option><option value="approved">翻译已确认(仅允许正式内容)</option></select></label></div><div class="form-actions"><button class="btn btn-primary" ${uniqueUnits.length?'':'disabled'}>保存关联</button></div></form></div>`+
|
||||
`<div class="panel"><h2>已有语言关系</h2><p class="panel-desc">没有已审核的双向内容关系,就不会输出假语言链接。</p>${d.units.length?`<div class="table-wrap"><table class="data-table"><thead><tr><th>内容组</th><th>语言</th><th>内容 / URL</th><th>状态</th></tr></thead><tbody>${d.units.map(u=>`<tr><td>${esc(u.reference_key)}</td><td>${esc(u.locale)}</td><td>${esc(u.title)}<br><small>${esc(u.route)}</small></td><td>${u.state==='approved'?'已审核':'待翻译'}</td></tr>`).join('')}</tbody></table></div>`:'<p class="muted">还没有关联内容。</p>'}</div>`;
|
||||
@@ -283,7 +283,7 @@ async function cancelJob(jobId){if(!state.site)return;if(!confirm('确定取消
|
||||
async function buildNow(){if(!state.site)return toast('请先选择网站',true);if(state.building||activeJobs().length)return toast('已有构建在执行,请先查看任务状态');
|
||||
const id=state.site.id;state.building=true;
|
||||
try{navigate('build');state.job='正在提交构建任务…';rerender();const job=await api(`/api/sites/${encodeURIComponent(id)}/builds`,'POST',{mirror_images:false},{'Idempotency-Key':crypto.randomUUID()});
|
||||
if(state.site?.id!==id)return;state.job=`任务 ${job.id?.slice(0,12)||'已提交'} 已进入队列,可以离开页面;重新打开“构建与交付”查看结果。`;toast('已提交构建任务');await refreshBuilds();
|
||||
if(state.site?.id!==id)return;state.job=`任务 ${job.id?.slice(0,12)||'已提交'} 已进入队列,可以离开页面;重新打开"构建与交付"查看结果。`;toast('已提交构建任务');await refreshBuilds();
|
||||
}catch(e){notifyError(e)}finally{state.building=false;if(state.site?.id===id&&state.view==='build')rerender()}}
|
||||
function selectedReleaseBuild(){const id=$('#deliveryBuild')?.value;if(!id)throw Error('没有可发布的构建版本,请先完成网站构建');return id}
|
||||
function showDeliveryMessage(message){const el=$('#deliveryNotice');if(el){el.hidden=false;el.textContent=message}toast(message)}
|
||||
|
||||
Reference in New Issue
Block a user