From cb87a844b382a5f400cb09f617006742b393e735 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sun, 4 Oct 2026 17:01:34 +0800 Subject: [PATCH] =?UTF-8?q?refactor(ui):=20=E8=B4=A6=E5=8F=B7=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E6=8C=89=E3=80=8C=E5=8F=AA=E5=81=9A=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E3=80=8D=E9=87=8D=E5=81=9A=20=E2=80=94=E2=80=94=20=E7=A0=8D?= =?UTF-8?q?=E6=8E=89=E5=87=AD=E6=8D=AE/=E6=A0=87=E7=AD=BE/=E6=97=B6?= =?UTF-8?q?=E9=97=B4=E5=88=97=E4=B8=8E=E6=89=B9=E9=87=8F=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 承接上一个提交。按确认的结果,四类老壳子残留一并移除: ## ① 「显示凭据」开关 + 密码 / 邮箱 / 邮箱密码 三列 这是老「卖号」视图的残留。移掉后列表不再有任何凭据字段, `include_sensitive` 参数不再从列表请求里传(后端能力保留,需要时仍可显式请求)。 ⚠️ 副作用:**界面上暂时没有地方能看到账号密码了**。当前重登全自动, 不需要人工读凭据;但如果你偶尔要抄出来核对,告诉我,我在**详情页**加一个 带权限校验 + 写审计的「查看凭据」,而不是把三列塞回列表。 ## ② 「标签」列 标签仍可**筛选**(顶部下拉),但不再占一列。设置标签的入口统一到**详情页** (S6 已在状态卡里做了就地编辑),列表里那个内联编辑框随之删除 —— 所以 `handleSetTag` / `tagColorMap` / `TAG_COLORS` 一并清掉。 ## ③ 「导入时间」列 去掉(详情页仍有完整的导入时间与状态变更时间)。 ## ④ 「批量打标签」「批量删除」按钮 随之移除**整套勾选机制**:`rowSelection`、`selectedRowKeys`、 `selectedAllMatching`、「已选择 N 个 / 选择当前筛选下全部」提示条、 批量设置标签弹窗、以及两个 handler。**单条删除保留**(操作列,受 `account:delete` 管)。 批量接口在后端保留不动(S4 已给它们接了逐账号事件,别浪费)。 ## 结果 - 列表列:**账号 / 状态 / 绑定 / 积分·鱼翅 / 分配给 / 操作** - 工具栏:筛选(标签 / CK / 绑定 / 搜索)+ 批量导入 - 文件 601 → 478 行;`Alert` / `TagOutlined` / `Eye*` / `App` / `modal` 等 随之失效的导入与变量全部清掉(靠 `tsc --noUnusedLocals` 逐个抓出来) ## 验证 - `TZ=Asia/Shanghai`:500 passed / 6 skipped;ruff 全绿 - 前端 build 通过、`npm run lint` **0 errors** - 逐个删除后都用 `tsc` 反查死代码,没有留下未使用变量 --- web/frontend/src/pages/AccountsPage.tsx | 251 +----------------------- 1 file changed, 5 insertions(+), 246 deletions(-) diff --git a/web/frontend/src/pages/AccountsPage.tsx b/web/frontend/src/pages/AccountsPage.tsx index 2842ac6..9241bb9 100644 --- a/web/frontend/src/pages/AccountsPage.tsx +++ b/web/frontend/src/pages/AccountsPage.tsx @@ -1,12 +1,12 @@ -import { useEffect, useState, useMemo, useCallback } from 'react'; +import { useEffect, useState, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import { - Alert, Table, Button, Modal, Input, Select, Popconfirm, Typography, Tag, Space, - Row, Col, Card, Statistic, App, Tooltip, + Table, Button, Modal, Input, Select, Popconfirm, Typography, Tag, Space, + Row, Col, Card, Statistic, Tooltip, } from 'antd'; import { message } from '../utils/antdMessage'; import type { TableProps } from 'antd'; -import { ImportOutlined, DeleteOutlined, TagOutlined, FilterOutlined, EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons'; +import { ImportOutlined, DeleteOutlined, FilterOutlined } from '@ant-design/icons'; import { accountApi, userApi, type AccountItem, type BasicSummary, type UserInfo } from '../api/modules'; import { usePermissions } from '../hooks/usePermissions'; import { formatTime } from '../utils/time'; @@ -15,9 +15,6 @@ import { getErrorMessage } from '../utils/error'; const { TextArea } = Input; const { Text } = Typography; -const TAG_COLORS = [ - 'blue', 'green', 'cyan', 'geekblue', 'purple', 'orange', 'magenta', 'volcano', -]; /** * 生命周期状态 → 展示色。 @@ -73,8 +70,6 @@ function BindChip({ scope, status }: { scope: string; status?: string }) { export default function AccountsPage() { const navigate = useNavigate(); - // App.useApp() 的 modal 实例继承主题,静态 Modal.confirm 在暗色模式下是白底 - const { modal } = App.useApp(); const [accounts, setAccounts] = useState([]); const [users, setUsers] = useState([]); const [tags, setTags] = useState([]); @@ -88,11 +83,6 @@ export default function AccountsPage() { const [ckFilter, setCkFilter] = useState(''); const [bindFilter, setBindFilter] = useState(''); const [searchText, setSearchText] = useState(''); - const [selectedRowKeys, setSelectedRowKeys] = useState([]); - const [selectedAllMatching, setSelectedAllMatching] = useState(false); - const [batchTagInput, setBatchTagInput] = useState(''); - const [batchTagVisible, setBatchTagVisible] = useState(false); - const [sensitiveVisible, setSensitiveVisible] = useState(false); const [total, setTotal] = useState(0); const [summary, setSummary] = useState({ total: 0, assigned_count: 0, unassigned_count: 0, tag_count: 0 }); const [pageSize, setPageSize] = useState(() => { @@ -102,12 +92,9 @@ export default function AccountsPage() { const [currentPage, setCurrentPage] = useState(1); const { can } = usePermissions(); - const canViewFull = can('account:view_full'); const canImport = can('account:import'); - const canTag = can('account:tag') || canImport; const canAssign = can('account:assign'); const canDelete = can('account:delete'); - const canSelectRows = canTag || canDelete || canAssign; const loadAccounts = useCallback(async () => { setLoading(true); @@ -122,7 +109,6 @@ export default function AccountsPage() { page: currentPage, page_size: pageSize, search: searchText.trim() || undefined, - include_sensitive: canViewFull && sensitiveVisible, }); setAccounts(data.items); setTotal(data.total); @@ -131,7 +117,7 @@ export default function AccountsPage() { } finally { setLoading(false); } - }, [canViewFull, currentPage, pageSize, searchText, sensitiveVisible, tagFilter, statusFilter, ckFilter, bindFilter]); + }, [currentPage, pageSize, searchText, tagFilter, statusFilter, ckFilter, bindFilter]); const loadSummary = useCallback(async () => { try { @@ -170,31 +156,6 @@ export default function AccountsPage() { loadSummary(); }, [canAssign, loadUsers, loadTags, loadSummary]); - const tagColorMap = useMemo(() => { - const map: Record = {}; - tags.forEach((t, i) => { - map[t] = TAG_COLORS[i % TAG_COLORS.length]; - }); - return map; - }, [tags]); - - const selectedCount = selectedAllMatching ? total : selectedRowKeys.length; - const selectionPayload = useMemo(() => ({ - account_ids: selectedAllMatching ? [] : selectedRowKeys.map((key) => Number(key)), - all_matching: selectedAllMatching, - search: searchText.trim(), - tag: tagFilter, - }), [searchText, selectedAllMatching, selectedRowKeys, tagFilter]); - - const clearSelection = () => { - setSelectedRowKeys([]); - setSelectedAllMatching(false); - }; - - useEffect(() => { - clearSelection(); - }, [searchText, tagFilter]); - const handleImport = async () => { if (!importText.trim()) { message.warning('请输入账号数据'); @@ -227,42 +188,10 @@ export default function AccountsPage() { } }; - const handleSetTag = async (accountId: number, tag: string) => { - try { - await accountApi.setTag(accountId, tag); - message.success('标签已更新'); - loadAccounts(); - loadTags(); - } catch (e: unknown) { - message.error(getErrorMessage(e)); - } - }; - - const handleBatchTag = async () => { - if (selectedCount === 0) { - message.warning('请先选择账号'); - return; - } - try { - const result = await accountApi.batchTagSelection({ ...selectionPayload, tag_value: batchTagInput }); - message.success(result.message); - setBatchTagVisible(false); - setBatchTagInput(''); - clearSelection(); - loadAccounts(); - loadTags(); - loadSummary(); - } catch (e: unknown) { - message.error(getErrorMessage(e)); - } - }; - const handleDelete = async (id: number) => { try { await accountApi.delete(id); message.success('已删除'); - setSelectedRowKeys((prev) => prev.filter((k) => k !== id)); - setSelectedAllMatching(false); loadAccounts(); loadTags(); loadSummary(); @@ -271,44 +200,6 @@ export default function AccountsPage() { } }; - const handleBatchDelete = async () => { - if (selectedCount === 0) { - message.warning('请先选择账号'); - return; - } - try { - const result = await accountApi.batchDeleteSelection(selectionPayload); - message.success(result.message); - clearSelection(); - loadAccounts(); - loadTags(); - loadSummary(); - } catch (e: unknown) { - message.error(getErrorMessage(e)); - } - }; - - const handleSensitiveVisibility = () => { - if (sensitiveVisible) { - // 立即清除已加载的敏感内容,再由 effect 重新获取脱敏列表。 - setAccounts((previous) => previous.map((account) => ({ - ...account, - password: undefined, - email: undefined, - email_password: undefined, - }))); - setSensitiveVisible(false); - return; - } - modal.confirm({ - title: '显示账号凭据?', - content: '密码、邮箱和邮箱密码将临时显示在当前页面。关闭或刷新页面后会恢复脱敏。', - okText: '显示', - cancelText: '取消', - onOk: () => setSensitiveVisible(true), - }); - }; - const columns: TableProps['columns'] = [ { // ID 不再单独占一列(客服不看),降到用户名下面做小字 —— 报障时还能念出来。 @@ -395,53 +286,8 @@ export default function AccountsPage() { ), }, - { - title: '标签', - dataIndex: 'tag', - width: 170, - render: (tag: string, record: AccountItem) => { - if (canTag) { - return ( - setBatchTagInput(vals.length > 0 ? vals[vals.length - 1] : '')} - options={tags.map((t) => ({ value: t, label: t }))} - /> - ); }