feat: 敏感词管理页面(脱敏词后台化,限 lqq 账号)
- 后端 /api/sensitive-words:全局共享词表,开放读、admin 或 lqq 整体替换写 - 前端 desensitize 种子化:SEED_DESENSITIZE_MAP + 运行时 replaceDesensitizeMap - 启动加载后台配置回灌脱敏映射;管理页支持增删改启停 + 一键导入种子词 - 入口置于左下角设置下拉(全量/简洁两种侧边栏均有),仅用户名 lqq 可见 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
39d7dab3e2
commit
25e4e3dbc9
18 files changed
+788
-5
No files matched your search
@@ -0,0 +1,27 @@
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { replaceDesensitizeMap } from "@/lib/desensitize";
|
||||
import { useSensitiveWords, wordsToMap } from "@/strategy-components/api/sensitive-words";
|
||||
|
||||
/**
|
||||
* 启动时把后台「敏感词管理」配置加载进运行时脱敏映射表。
|
||||
*
|
||||
* - 后台返回**非空**时:用其中「启用」的词条整体替换运行时映射
|
||||
* ({@link replaceDesensitizeMap}),后台即唯一真源,被删除的词不再生效。
|
||||
* - 后台为空(全新部署还没配置)或拉取失败:什么都不做,保持使用
|
||||
* `src/lib/desensitize.ts` 内置的种子映射。
|
||||
*
|
||||
* 渲染 `null`;挂在 `AppProviders` 里(QueryClientProvider 之内)。与「敏感词管理」
|
||||
* 页共享同一份 React Query 缓存,保存后会自动刷新并回灌运行时映射。
|
||||
*/
|
||||
export function DesensitizeWordsLoader() {
|
||||
const { data } = useSensitiveWords();
|
||||
|
||||
useEffect(() => {
|
||||
if (data && data.length > 0) {
|
||||
replaceDesensitizeMap(wordsToMap(data));
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
FolderOpen,
|
||||
ListTree,
|
||||
Settings,
|
||||
ShieldAlert,
|
||||
User,
|
||||
Wrench,
|
||||
} from "lucide-react";
|
||||
@@ -22,7 +23,12 @@ import {
|
||||
import { NotificationDialog } from "@/components/workspace/notification-dialog";
|
||||
import { SettingsDialog } from "@/components/workspace/settings";
|
||||
import { useTheme } from "@/contexts/ThemeContext";
|
||||
import { getStoredAuth } from "@/core/auth";
|
||||
import { getAccountDisplayName, getStoredAuth } from "@/core/auth";
|
||||
|
||||
/** 敏感词管理仅对用户名为 lqq 的账号可见(用户名 = 设置→个人信息页展示名)。 */
|
||||
const SENSITIVE_WORDS_USERNAME = "lqq";
|
||||
const canManageSensitiveWords = () =>
|
||||
getAccountDisplayName().toLowerCase() === SENSITIVE_WORDS_USERNAME;
|
||||
import { isWujieEnv } from "@/core/embed/wujie";
|
||||
import { useI18n } from "@/core/i18n/hooks";
|
||||
import { useUnreadCount } from "@/core/notifications";
|
||||
@@ -207,6 +213,14 @@ function SecondaryNavSidebar() {
|
||||
菜单管理
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{canManageSensitiveWords() && (
|
||||
<DropdownMenuItem
|
||||
onClick={() => navigate("/page/strategy/admin/sensitive-words")}
|
||||
>
|
||||
<ShieldAlert className="size-4" />
|
||||
敏感词管理
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
) : null}
|
||||
@@ -479,6 +493,12 @@ function LegacyAccordionSidebar({
|
||||
菜单管理
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{canManageSensitiveWords() && (
|
||||
<DropdownMenuItem onClick={() => navigate("/page/strategy/admin/sensitive-words")}>
|
||||
<ShieldAlert className="size-4" />
|
||||
敏感词管理
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
) : (
|
||||
|
||||
Reference in new issue
Block a user