From 2105ad45fdb640940b8f253b5346abcca288a98f Mon Sep 17 00:00:00 2001
From: sakurairsora <42933202+sakurairsora@users.noreply.github.com>
Date: Sun, 2 Aug 2026 03:06:34 +0800
Subject: [PATCH 1/9] Update DashBoard.tsx
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
多选逻辑增强:
卡片视图下也增加了 Checkbox(原本只有 Radio),使得 selectedAccounts 可在两种视图下使用。
“清理全部”按钮不再依赖 isTableView,改为只要有选中账号就清理选中的,否则清理全部。
UI 细节:
表格视图的列宽、内边距微调。
账号名称输入框增加了自动聚焦和光标定位逻辑(useEffect + ref)。
样式优化(边框、背景、悬停效果)。
默认账号选择:
卡片视图下仍使用 RadioGroup + Radio,与 Checkbox 互不干扰。
合并影响:
新增的 Checkbox 和 Radio 共存在卡片视图中,功能分离明确(Checkbox 用于批量操作,Radio 用于默认账号)。
清理逻辑更统一,需确保 handleCleanDailyAll 正确遍历选中的账号。
输入框聚焦优化不会影响原有交互。
---
src/components/Account/DashBoard.tsx | 518 ++++++++++++++++-----------
1 file changed, 301 insertions(+), 217 deletions(-)
diff --git a/src/components/Account/DashBoard.tsx b/src/components/Account/DashBoard.tsx
index 8e11eaf..5d9730c 100644
--- a/src/components/Account/DashBoard.tsx
+++ b/src/components/Account/DashBoard.tsx
@@ -14,7 +14,6 @@ import {
Text,
} from '@chakra-ui/react';
import { FiActivity, FiBook, FiCheck, FiCopy, FiGrid, FiKey, FiLayers, FiList, FiStar, FiTarget, FiUpload, FiUserMinus, FiUserPlus, FiUserX, FiX } from 'react-icons/fi';
-import { Radio, RadioGroup } from '../../components/ui/radio';
import React, { ChangeEvent, useMemo, useRef } from 'react';
import { Skeleton, SkeletonText } from '../../components/ui/skeleton';
import { clearAccounts, deleteAccount, getAccountDailyResultList, getUserInfo, putUserInfo } from '@api/Account';
@@ -109,16 +108,37 @@ export function DashBoard() {
}, [freshAccountInfo.open]);
const handleDefaultAccount = (value: string) => {
+ // 后端 put_info 仅在非空时写入;清空依赖后端接受空串(见 README)
putUserInfo({ default_account: value })
.then((res) => {
- setUserInfo({ ...userInfo, default_account: value });
- toaster.create({ type: 'success', title: '设置默认账号成功', description: res });
+ setUserInfo((prev) => (prev ? { ...prev, default_account: value } : prev));
+ toaster.create({
+ type: 'success',
+ title: value ? '设置默认账号成功' : '已取消默认账号',
+ description: res,
+ });
})
.catch((err: AxiosError) => {
- toaster.create({ type: 'error', title: '设置默认账号失败', description: (err?.response?.data as string) || '网络错误' });
+ toaster.create({
+ type: 'error',
+ title: value ? '设置默认账号失败' : '取消默认账号失败',
+ description: (err?.response?.data as string) || '网络错误',
+ });
});
};
+ const singleSelected = selectedAccounts.length === 1 ? selectedAccounts[0] : '';
+ const singleIsDefault = !!singleSelected && userInfo?.default_account === singleSelected;
+
+ const handleToggleDefaultForSelected = () => {
+ if (!singleSelected) return;
+ if (singleIsDefault) {
+ handleDefaultAccount('');
+ } else {
+ handleDefaultAccount(singleSelected);
+ }
+ };
+
const handleResetPassword = () => {
NiceModal.show(resetPasswdModal, {})
.then((value) => {
@@ -158,7 +178,7 @@ export function DashBoard() {
};
const handleCleanDailyAll = () => {
- if (isTableView && selectedAccounts.length > 0) {
+ if (selectedAccounts.length > 0) {
for (const accountName of selectedAccounts) {
const fn = handle.get(accountName);
if (fn) fn(false);
@@ -323,7 +343,7 @@ export function DashBoard() {
onClick={handleCleanDailyAll}
loading={count != 0}
>
- {isTableView && selectedAccounts.length > 0 ? `清选择(${selectedAccounts.length})` : '清理全部'}
+ {selectedAccounts.length > 0 ? `清选择(${selectedAccounts.length})` : '清理全部'}
- {/* 与一览 statusMeta 一致:出现在清理按钮右边 */}
{statusMeta && (
{statusMeta.icon}
From 4156e62c58b5134cd70155d436da177ab7d06c59 Mon Sep 17 00:00:00 2001
From: sakurairsora <42933202+sakurairsora@users.noreply.github.com>
Date: Mon, 10 Aug 2026 00:27:19 +0800
Subject: [PATCH 9/9] Update DashBoard.tsx
---
src/components/Account/DashBoard.tsx | 277 ++++++++++++++++-----------
1 file changed, 160 insertions(+), 117 deletions(-)
diff --git a/src/components/Account/DashBoard.tsx b/src/components/Account/DashBoard.tsx
index 5d9730c..b4eb43f 100644
--- a/src/components/Account/DashBoard.tsx
+++ b/src/components/Account/DashBoard.tsx
@@ -38,6 +38,27 @@ import { useDisclosure } from '@chakra-ui/react';
import ConfigSyncModal from './ConfigSyncModal';
import type { Candidate, ConfigType, ConfigValue, ModuleResponse } from '@interfaces/Module';
+async function getErrorDescription(err: unknown, fallback = '网络错误'): Promise {
+ const data = (err as { response?: { data?: unknown } })?.response?.data;
+ try {
+ if (data == null) {
+ if (err instanceof Error && err.message) return err.message;
+ return fallback;
+ }
+ if (typeof Blob !== 'undefined' && data instanceof Blob) {
+ const t = await data.text();
+ return t || fallback;
+ }
+ if (typeof data === 'string') return data || fallback;
+ if (typeof data === 'object') {
+ try { return JSON.stringify(data); } catch { return fallback; }
+ }
+ return String(data);
+ } catch {
+ return fallback;
+ }
+}
+
const handle: Map void> = new Map void>();
const DISPLAY_NAME_KEY = (alias: string) => `autopcr_displayName_${alias}`;
@@ -209,41 +230,43 @@ export function DashBoard() {
};
const handleCreateAccount = () => {
- if (creatAccountSwitch.open) {
- if (!alias || alias.trim() === '') {
+ if (!creatAccountSwitch.open) {
+ creatAccountSwitch.onOpen();
+ return;
+ }
+ if (!alias || alias.trim() === '') {
+ toaster.create({
+ type: 'warning',
+ title: '需输入名字,此次未创建',
+ });
+ creatAccountSwitch.onClose();
+ setAlias('');
+ return;
+ }
+
+ postAccount(alias)
+ .then((res) => {
+ toaster.create({
+ type: 'success',
+ title: '创建账号成功',
+ description: res,
+ });
+ creatAccountSwitch.onClose();
+ setAlias('');
+ freshAccountInfo.onToggle();
+ })
+ .catch(async (err: AxiosError) => {
toaster.create({
type: 'error',
title: '创建账号失败',
- description: '账号昵称不能为空',
+ description: await getErrorDescription(err),
});
- return;
- }
-
- postAccount(alias)
- .then((res) => {
- toaster.create({
- type: 'success',
- title: '创建账号成功',
- description: res,
- });
- creatAccountSwitch.onToggle();
- setAlias('');
- freshAccountInfo.onToggle();
- })
- .catch((err: AxiosError) => {
- toaster.create({
- type: 'error',
- title: '创建账号失败',
- description: (err?.response?.data as string) || '网络错误',
- });
- });
- } else {
- creatAccountSwitch.onToggle();
- }
+ });
};
const handleAccountImport = (event: ChangeEvent) => {
const file = event.target.files?.[0];
+ event.target.value = '';
if (file) {
postAccountImport(file)
.then((res) => {
@@ -480,9 +503,7 @@ export function DashBoard() {
variant={creatAccountSwitch.open ? "solid" : "solid"}
colorPalette={creatAccountSwitch.open ? "red" : "green"}
onClick={() => {
- if(creatAccountSwitch.open && !alias) creatAccountSwitch.onToggle();
- else if (creatAccountSwitch.open && alias) handleCreateAccount();
- else creatAccountSwitch.onToggle();
+ handleCreateAccount();
}}
>
{creatAccountSwitch.open ? : }
@@ -526,7 +547,7 @@ export function DashBoard() {
-
+
0 && selectedAccounts.length < (userInfo?.accounts?.length ?? 0))
@@ -665,6 +686,7 @@ function AccountInfo({
const [displayName, setDisplayName] = useState(() => getDisplayName(alias));
const [nameDraft, setNameDraft] = useState(displayName);
const composingRef = useRef(false);
+ const nameInputRef = useRef(null);
const clean = account.daily_clean_time;
const cleanStatus = clean?.status || '未知';
@@ -740,7 +762,7 @@ function AccountInfo({
};
const handleDailyResult = () => {
- toaster.create({ type: 'info', title: `正在获取${displayName || alias}的日常结果...` });
+ toaster.create({ type: 'info', title: `正在获取${alias}的日常结果...` });
getAccountDailyResultList(alias)
.then(async (res) => {
toaster.create({ type: 'success', title: '获取日常结果成功' });
@@ -750,7 +772,7 @@ function AccountInfo({
toaster.create({
type: 'error',
title: '获取日常结果失败',
- description: (await (err?.response?.data as Blob).text()) || '网络错误',
+ description: await getErrorDescription(err),
});
});
};
@@ -759,6 +781,15 @@ function AccountInfo({
void navigate({ to: `${DashBoardRoute.to || ''}${alias}` as any });
};
+
+ useEffect(() => {
+ if (isEditingName) {
+ // autoFocus 只在挂载时生效;编辑态切换时手动 focus
+ const t = window.setTimeout(() => nameInputRef.current?.focus(), 0);
+ return () => window.clearTimeout(t);
+ }
+ }, [isEditingName]);
+
const commitDisplayName = () => {
const next = nameDraft.trim();
// 空名 / 等于真实 alias:恢复为 alias
@@ -788,10 +819,10 @@ function AccountInfo({
// 始终同一 Input:可编辑区域与名字位置重合
const nameInput = (
{
e.stopPropagation();
@@ -810,10 +841,25 @@ function AccountInfo({
onCompositionEnd={(e) => {
composingRef.current = false;
setNameDraft((e.target as HTMLInputElement).value);
+ // 组词中途点到别处:compositionend 时可能已失焦,补一次提交,避免卡在编辑态
+ const el = e.target as HTMLInputElement;
+ window.setTimeout(() => {
+ if (document.activeElement !== el) {
+ commitDisplayName();
+ }
+ }, 0);
}}
onBlur={() => {
if (!isEditingName) return;
- if (composingRef.current) return;
+ if (composingRef.current) {
+ // 组词中 blur:等 composition 结束后由上面的 timeout 处理;再兜底一次
+ window.setTimeout(() => {
+ if (!composingRef.current) {
+ commitDisplayName();
+ }
+ }, 0);
+ return;
+ }
commitDisplayName();
}}
onKeyDown={(e) => {
@@ -825,7 +871,7 @@ function AccountInfo({
}
}}
fontWeight="bold"
- maxW="8em"
+ maxW="12em"
minW="4em"
h="2em"
px={isEditingName ? 2 : 0}
@@ -1079,6 +1125,7 @@ function AccountInfo({
onClick={goDetail}
title="进入详细设置"
>
+ {/* ✅ 补回内层 Flex 容器;曾用名 minW 保底,标签不挤占省略空间 */}
)}
+ {/* 标签单独一组 flexShrink=0,避免反噬曾用名 */}
{defaultAccount === account.name && (
@@ -1144,11 +1192,12 @@ function AccountInfo({
isOpen={deleteConfirm.open}
onClose={deleteConfirm.onClose}
title="删除账号"
- body={`确定删除账号${displayName || alias}吗?`}
+ body={`确定删除账号${alias}吗?`}
onConfirm={handleDeleteAccount}
>
{' '}
+ {/* 热区略小、叉图形略大 */}
+ {/* 表格状态列:保持可进详情(仅卡片状态区做安全点击区) */}
-
-
-
+ {/* 整卡 onClick 进详情;Header/状态/按钮区内控件自行 stopPropagation */}
+
+ {/* 整 Header 随卡片进详情;仅选框/改名/删除 stopPropagation */}
+
+ e.stopPropagation()}
+ flexShrink={0}
+ display="flex"
+ alignItems="center"
+ justifyContent="center"
+ title="选择账号"
+ cursor="default"
+ >
+
+
+
+
e.stopPropagation()}
- flexShrink={0}
display="flex"
alignItems="center"
- justifyContent="center"
- pl={0.5}
- pr={1}
- py={1}
- minW="2rem"
- minH="2rem"
- title="选择账号"
+ lineHeight="1"
+ fontSize="lg"
+ flex="1 1 6em"
+ minW="4em"
+ maxW={displayName !== alias ? '52%' : '75%'}
+ overflow="hidden"
+ title=""
cursor="default"
>
-
+ {nameInput}
-
- e.stopPropagation()}
- display="flex"
- alignItems="center"
+ {displayName !== alias && (
+
- {nameInput}
-
- {displayName !== alias && (
-
- {alias}
-
+ {alias}
+
+ )}
+
+
+ {defaultAccount === account.name && (
+
+ 默认
+
+ )}
+ {account.clan_forbid && (
+
+ 禁用
+
)}
-
- {defaultAccount === account.name && (
-
- 默认
-
- )}
- {account.clan_forbid && (
-
- 禁用
-
- )}
-
- {/* 空白热区:真正吃到 native title */}
-
e.stopPropagation()}
flexShrink={0}
- pr={0.5}
title="删除账号"
cursor="default"
- display="flex"
- alignItems="center"
>
{
e.stopPropagation();
deleteConfirm.onOpen();
}}
_hover={{ bg: 'red.subtle', color: 'red.fg' }}
+ css={{
+ '& svg': {
+ width: '1.4em',
+ height: '1.4em',
+ strokeWidth: 2.5,
+ },
+ }}
>
-
+