From b3d2dada8183e67b907bdf360369ee62dd411c0f Mon Sep 17 00:00:00 2001 From: Rei Date: Mon, 19 Oct 2026 15:58:02 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=8E=E5=8F=B0=E2=80=9C=E8=AE=A4=E7=9F=A5?= =?UTF-8?q?=E8=A7=82=E5=AF=9F=E2=80=9D=E6=96=87=E7=AB=A0=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E6=94=B9=E7=89=88=EF=BC=9A=E6=90=9C=E7=B4=A2=E7=AD=9B=E9=80=89?= =?UTF-8?q?=E3=80=81=E4=B8=8A=E4=B8=8B=E7=BA=BF=E3=80=81=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E5=88=A0=E9=99=A4=EF=BC=8C=E4=BF=AE=E6=94=B9=E6=AD=A3=E6=96=87?= =?UTF-8?q?=E7=BD=91=E7=AB=99=E7=AB=8B=E5=8D=B3=E7=94=9F=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 文章列表:按标题/路径搜索,按状态和来源(内容系统/手动添加)筛选,显示发布时间、字数和推荐权重;一键上线/下线、查看前台页面、单篇和批量删除,操作结果有提示 - 编辑改为右侧抽屉,正文按段落纯文本编辑;发布时间可改;来自内容系统的文章提示再次发布会覆盖修改 - 修复:后台改正文网站不变(网站显示 body_plain,后台只改了 content_html),现在两者一起保存 - 修复:修改文章时没传的字段会被清空,现在只改提交的字段 - 删除文章时同时删除内容系统的发布记录,内容系统可以重新发布;新增批量删除接口 - 上线时没有发布时间就用当前时间;路径标识重复时给出可读提示 Co-Authored-By: Claude Opus 5.5 --- cms/admin/src/App.tsx | 21 +--- cms/admin/src/Articles.tsx | 163 ++++++++++++++++++++++++++ cms/admin/src/style.css | 33 ++++++ cms/server/src/article-admin.js | 95 +++++++++++++++ cms/server/src/index.js | 39 +++--- cms/server/test/article-admin.test.js | 97 +++++++++++++++ 6 files changed, 415 insertions(+), 33 deletions(-) create mode 100644 cms/admin/src/Articles.tsx create mode 100644 cms/server/src/article-admin.js create mode 100644 cms/server/test/article-admin.test.js diff --git a/cms/admin/src/App.tsx b/cms/admin/src/App.tsx index 0322600..8cdadf0 100644 --- a/cms/admin/src/App.tsx +++ b/cms/admin/src/App.tsx @@ -1,4 +1,5 @@ import { useState, useEffect } from 'react' +import { Articles } from './Articles' const API_BASE = '' // 同域,相对路径 const SIDEBAR = [ @@ -94,7 +95,7 @@ export default function App(){ {cur==='methods' && } {cur==='services' && } {cur==='cases' && } - {cur==='articles' && } + {cur==='articles' && } {cur==='selfcheck' && } {cur==='diag_q' && } {cur==='diag_config' && } @@ -235,24 +236,6 @@ function Cases({token}:{token:string}){ } -function Articles({token}:{token:string}){ - const [list,setList]=useState([]); const [f,setF]=useState({status:'published'}); const [editing,setEditing]=useState(false) - useEffect(()=>{api('/api/cms/articles',token).then(j=> setList(j.data||[]))},[]) - const save=()=>api(editing?'/api/cms/articles/'+f.id:'/api/cms/articles',token,{method:editing?'PUT':'POST',body:JSON.stringify(f)}).then(j=>{if(j.code===0){const saved={...f,id:editing?f.id:j.data?.id};setList(editing?list.map(x=>x.id===saved.id?saved:x):[...list,saved]);setF({status:'published'});setEditing(false);alert('保存成功')}else alert(j.msg||'保存失败')}) - return <> -

文章列表

{list.map(a=> )}
标题分类推荐权重状态操作
{a.title}{a.category}{a.recommend_weight}{a.status}
-

{editing?'编辑文章':'新增文章'}(富文本)

-
setF({...f,title:e.target.value})}/>
-
setF({...f,slug:e.target.value})}/>
setF({...f,category:e.target.value})}/>
-