feat(settings,questions,school,textbooks): add brand config, question components, school dialogs, textbooks hooks

settings:

- Add actions-brand, brand-config, data-access-brand for brand management

- Add admin-file-upload-card, admin-notification-config-card, admin-school-info-card, admin-security-policy-card

- Add ai-provider-delete-dialog, ai-provider-selector, brand-config-card

- Add security-recent-logins-section, security-two-factor-section

- Add config/profile-overview-config, data-access-profile-overview, lib/system-settings-utils

questions:

- Add batch-operations, import-export-buttons, knowledge-point-selector, options-editor

- Add question-bank-results-client, question-cascade-filter, question-content-renderer, utils

school:

- Add grade-delete-dialog, grade-form-dialog, grade-list-toolbar, grade-overview-cards

- Add use-grade-data hook

textbooks:

- Add textbook-form-fields component

- Add use-kp-create, use-kp-delete, use-kp-update hooks
This commit is contained in:
SpecialX
2026-07-03 10:26:00 +08:00
parent 138b6f1b00
commit f3c223d914
77 changed files with 5397 additions and 2864 deletions

View File

@@ -53,12 +53,6 @@ export interface TextbookReaderProps {
* 必传,否则知识点面板将始终为空。
*/
textbookId: string
/**
* 是否可编辑。已废弃——改由内部 usePermission() 自动判断。
* 保留 prop 仅为向后兼容,传入值会被忽略。
* @deprecated 改用权限系统自动判断
*/
canEdit?: boolean
/**
* 题目创建器渲染函数P0-1 解耦)。
* 由页面层注入 questions 模块的 CreateQuestionDialog 实现。
@@ -176,8 +170,11 @@ export function TextbookReader({
const onCreateKnowledgePoint = async (formData: FormData) => {
setIsCreating(true)
await handleCreateKnowledgePoint(formData)
setIsCreating(false)
try {
await handleCreateKnowledgePoint(formData)
} finally {
setIsCreating(false)
}
}
const handleSaveContent = async () => {
@@ -224,19 +221,13 @@ export function TextbookReader({
return highlightKnowledgePoints(effectiveContent, currentChapterKPs)
}, [effectiveContent, currentChapterKPs])
// P2 状态驱动:仅保留 scrollIntoView滚动无法声明式实现
// 视觉高亮已由 TextbookContentPanel 中 isHighlighted 状态驱动,无需命令式 classList 操作
useEffect(() => {
if (!highlightedKpId) return
const el = document.querySelector(`[data-kp-id="${highlightedKpId}"]`)
if (!el) return
el.scrollIntoView({ behavior: "smooth", block: "center" })
el.classList.add("ring-2", "ring-primary", "ring-offset-2")
const timer = setTimeout(() => {
el.classList.remove("ring-2", "ring-primary", "ring-offset-2")
}, 2000)
return () => {
clearTimeout(timer)
}
}, [highlightedKpId])
// P2-4 侧边栏内容(章节/知识点/图谱 Tabs桌面端内联、移动端抽屉复用同一份
@@ -422,24 +413,29 @@ export function TextbookReader({
retryLabel={t("error.retry")}
>
<TextbookContentPanel
selected={selected}
isEditing={isEditing}
editContent={editContent}
setEditContent={setEditContent}
chapter={{ selected, processedContent }}
editing={{
isEditing,
editContent,
setEditContent,
isSaving,
startEditing,
cancelEditing: () => setIsEditing(false),
saveContent: handleSaveContent,
}}
selection={{
contentRef,
onPointerDown: handleContentPointerDown,
onContextMenuChange: handleContextMenuChange,
selectedText,
setCreateDialogOpen,
}}
highlight={{
highlightedKpId,
onHighlight: setHighlightedKpId,
onSwitchToKnowledgeTab: () => setActiveTab("knowledge"),
}}
canEdit={canEdit}
highlightedKpId={highlightedKpId}
onHighlight={setHighlightedKpId}
onSwitchToKnowledgeTab={() => setActiveTab("knowledge")}
contentRef={contentRef}
onPointerDown={handleContentPointerDown}
onContextMenuChange={handleContextMenuChange}
selectedText={selectedText}
setCreateDialogOpen={setCreateDialogOpen}
startEditing={startEditing}
cancelEditing={() => setIsEditing(false)}
saveContent={handleSaveContent}
isSaving={isSaving}
processedContent={processedContent}
/>
</TextbookSectionErrorBoundary>
</div>