@@ -0,0 +1,18 @@
< script setup lang = "ts" >
import { onMounted , ref } from 'vue'
import { deleteKnowledge , getKnowledgeHistory , getKnowledgePage , updateKnowledge , type AuditItem , type KnowledgeItem , type VerificationStatus } from '../api'
type Form = { title : string ; category : string ; content : string ; source _url : string ; collected _at : string }
const items = ref < KnowledgeItem [ ] > ( [ ] ) , selected = ref < KnowledgeItem > ( ) , historyFor = ref < KnowledgeItem > ( ) , historyItems = ref < AuditItem [ ] > ( [ ] )
const query = ref ( '' ) , category = ref ( '' ) , status = ref < VerificationStatus | ' ' > ( '' ) , page = ref ( 1 ) , total = ref ( 0 ) , loading = ref ( true ) , saving = ref ( false ) , historyLoading = ref ( false ) , error = ref ( '' ) , notice = ref ( '' )
const form = ref < Form > ( { title : '' , category : '' , content : '' , source _url : '' , collected _at : '' } )
function failure ( cause : unknown , fallback : string ) { return cause instanceof Error ? cause . message : fallback }
function validUrl ( value : string ) { if ( ! value ) return true ; try { const url = new URL ( value ) ; return url . protocol === 'http:' || url . protocol === 'https:' } catch { return false } }
async function refresh ( next = page . value ) { loading . value = true ; error . value = '' ; try { const result = await getKnowledgePage ( { q : query . value , category : category . value , verification _status : status . value || undefined , page : next } ) ; items . value = result . items ; page . value = result . page ; total . value = result . total } catch ( cause ) { error . value = failure ( cause , '无法读取知识条目' ) } finally { loading . value = false } }
function edit ( item : KnowledgeItem ) { selected . value = item ; form . value = { title : item . title , category : item . category , content : item . content , source _url : item . source _url ? ? '' , collected _at : item . collected _at ? new Date ( item . collected _at ) . toISOString ( ) . slice ( 0 , 16 ) : '' } ; error . value = '' ; notice . value = '' }
function cancel ( ) { selected . value = undefined }
async function save ( ) { if ( ! selected . value ) return ; error . value = '' ; notice . value = '' ; if ( ! form . value . title . trim ( ) || ! form . value . category . trim ( ) || ! form . value . content . trim ( ) ) { error . value = '标题、分类和正文不能为空。' ; return } if ( ! validUrl ( form . value . source _url . trim ( ) ) ) { error . value = '来源网址必须是有效的 http(s) 地址。' ; return } saving . value = true ; try { const payload = { expected _version : selected . value . version , title : form . value . title . trim ( ) , category : form . value . category . trim ( ) , content : form . value . content . trim ( ) , source _url : form . value . source _url . trim ( ) || null , ... ( form . value . collected _at ? { collected _at : new Date ( form . value . collected _at ) . toISOString ( ) } : { } ) } ; await updateKnowledge ( selected . value . id , payload ) ; notice . value = '知识条目已更新,后端已将其重新置为待审核。' ; cancel ( ) ; await refresh ( ) } catch ( cause ) { error . value = failure ( cause , '更新知识条目失败' ) } finally { saving . value = false } }
async function remove ( item : KnowledgeItem ) { if ( ! window . confirm ( ` 确定删除“ ${ item . title } ”吗?该操作为软删除,历史记录会保留。 ` ) ) return ; error . value = '' ; notice . value = '' ; try { await deleteKnowledge ( item . id , item . version ) ; notice . value = '知识条目已软删除。' ; if ( selected . value ? . id === item . id ) cancel ( ) ; await refresh ( items . value . length === 1 && page . value > 1 ? page . value - 1 : page . value ) } catch ( cause ) { error . value = failure ( cause , '删除知识条目失败' ) } }
async function showHistory ( item : KnowledgeItem ) { historyFor . value = item ; historyLoading . value = true ; error . value = '' ; try { historyItems . value = ( await getKnowledgeHistory ( item . id ) ) . items } catch ( cause ) { error . value = failure ( cause , '无法读取知识历史' ) ; historyFor . value = undefined } finally { historyLoading . value = false } }
onMounted ( ( ) => refresh ( 1 ) )
< / script >
< template > < main > < RouterLink to = "/admin" > 返回管理员工作台 < / RouterLink > < h1 > 知识管理 < / h1 > < p > 列表 、 编辑 、 删除和历史均直接调用管理员受保护接口 ; 编辑后条目会由后端重新置为待审核 。 < / p > < p v-if = "error" role="alert">{{error}}</p><p v-if="notice" role="status">{{notice}}</p><form @submit.prevent="refresh(1)"><label>关键词<input v-model="query"/></label><label>分类<input v-model="category"/></label><label>状态<select v-model="status"><option value="">全部</option><option value="pending">待审核</option><option value="verified">已核验</option><option value="rejected">已驳回</option></select></label><button>筛选</button></form><p v-if="loading">正在读取知识条目…</p><template v-else><table><thead><tr><th>标题</th><th>分类</th><th>状态</th><th>来源</th><th>操作</th></tr></thead><tbody><tr v-for="item in items" :key="item.id"><td><b>{{item.title}}</b><p>{{item.content.slice(0,120)}}</p></td><td>{{item.category}}</td><td>{{item.verification_status}}</td><td><a v-if="item.source_url" :href="item.source_url" target="_blank" rel="noreferrer">打开来源</a><span v-else>未填写</span></td><td><button @click="edit(item)">编辑</button><button @click="showHistory(item)">历史</button><button @click="remove(item)">删除</button></td></tr><tr v-if="!items.length"><td colspan="5">暂无符合条件的知识条目。</td></tr></tbody></table><div><button :disabled="page===1" @click="refresh(page-1)">上一页</button><span>第 {{page}} 页,共 {{total}} 条</span><button :disabled="page*20>=total" @click="refresh(page+1)">下一页</button></div></template><form v-if="selected" @submit.prevent="save"><h2>编辑:{{selected.title}}</h2><label>标题<input v-model.trim="form.title" required maxlength="250"/></label><label>分类<input v-model.trim="form.category" required maxlength="80"/></label><label>正文<textarea v-model.trim="form.content" required maxlength="100000" rows="6"/></label><label>来源网址<input v-model.trim="form.source_url" type="url" maxlength="1000" placeholder="https://..."/></label><label>采集时间<input v-model="form.collected_at" type="datetime-local"/></label><button type="button" :disabled="saving" @click="cancel">取消</button><button :disabled="saving">{{saving?'保存中…':'保存修改'}}</button></form><section v-if="historyFor"><h2>“{{historyFor.title}}”的历史</h2><button @click="historyFor=undefined">关闭历史</button><p v-if="historyLoading">正在读取历史…</p><table v-else><thead><tr><th>操作</th><th>时间</th><th>操作者</th><th>版本快照</th></tr></thead><tbody><tr v-for="item in historyItems" :key="item.id"><td>{{item.action}}</td><td>{{new Date(item.created_at).toLocaleString()}}</td><td>{{item.actor||'系统'}}</td><td><details><summary>查看快照</summary><pre>{{JSON.stringify(item.snapshot,null,2)}}</pre></details></td></tr><tr v-if="!historyItems.length"><td colspan="4" > 暂无历史记录 。 < / td > < / tr > < / tbody > < / table > < / section > < / main > < / template >