refactor: 重组 components/ 和 pages/ 文件结构,按职责分层
components/ 拆为三层:
- providers/ — 状态管理(App/Theme/SettingsProvider)
- shell/ — 应用外壳(Layout/NavBar/PageDispatcher)
- ui/ — 通用 UI 原子(FloatingPanel/LegalTextModal)
pages/ 重命名 + 重组:
- headers/ → panels/ NavBar 触发的面板页
- login/ → auth/ 认证模块(含注册/忘记密码)
- settings/blocks/ → sections/
- home/components/ → panels/ features/ controls/ hooks/
按职责分:panels(布局壳) + features(功能面板) + controls(表单控件) + hooks
其他清理:
- 删除空占位 Test3.tsx
- HomeContext 从 HomeContent 拆出独立文件
- uploadValidation 内联到 ImageInput
- 修复过时注释(useUI → controls)
This commit is contained in:
194
src/pages/settings/sections/StoragePathSetting.tsx
Normal file
194
src/pages/settings/sections/StoragePathSetting.tsx
Normal file
@@ -0,0 +1,194 @@
|
||||
// ============================================================
|
||||
// StoragePathSetting — 存储路径设置区块
|
||||
//
|
||||
// 个人版:仅"大模型产物存储仓库路径"
|
||||
// 企业版:额外"团队创作存储仓库路径"
|
||||
// ============================================================
|
||||
|
||||
import { useState, useCallback } from 'react';
|
||||
import { Card, Button, Input, Typography, Space, App } from 'antd';
|
||||
import { FolderOpenOutlined, DeleteOutlined, EditOutlined } from '@ant-design/icons';
|
||||
import type { Edition } from '@shared/types';
|
||||
import { useSettings } from '@/components/providers/SettingsProvider';
|
||||
import { hasIpc, safeIpcInvoke } from '@/utils/ipc';
|
||||
import { BIDIRECTIONAL } from '@shared/constants/ipc-channels';
|
||||
|
||||
const { Text, Paragraph } = Typography;
|
||||
|
||||
interface StoragePathSettingProps {
|
||||
edition: Edition;
|
||||
}
|
||||
|
||||
/** 单行路径选择器 */
|
||||
function PathRow({
|
||||
label,
|
||||
path,
|
||||
onChange,
|
||||
onClear,
|
||||
}: {
|
||||
label: string;
|
||||
path: string;
|
||||
onChange: (path: string) => void;
|
||||
onClear: () => void;
|
||||
}) {
|
||||
const [editing, setEditing] = useState(false);
|
||||
const [inputValue, setInputValue] = useState(path);
|
||||
const { message } = App.useApp();
|
||||
|
||||
const handleSelectFolder = useCallback(async () => {
|
||||
if (!hasIpc()) {
|
||||
// 非 Electron 环境 → 切换为手动输入模式
|
||||
setInputValue(path);
|
||||
setEditing(true);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = (await safeIpcInvoke(BIDIRECTIONAL.FILE_DIALOG, {
|
||||
title: label,
|
||||
defaultPath: path || undefined,
|
||||
properties: ['openDirectory'],
|
||||
})) as { canceled: boolean; filePaths: string[] } | undefined;
|
||||
|
||||
if (!result || result.canceled || result.filePaths.length === 0) return;
|
||||
|
||||
const selectedPath = result.filePaths[0];
|
||||
onChange(selectedPath);
|
||||
message.success('路径已更新');
|
||||
} catch {
|
||||
message.error('选择文件夹失败');
|
||||
}
|
||||
}, [label, path, onChange, message]);
|
||||
|
||||
const handleSaveInput = useCallback(() => {
|
||||
onChange(inputValue.trim());
|
||||
setEditing(false);
|
||||
if (inputValue.trim()) {
|
||||
message.success('路径已更新');
|
||||
}
|
||||
}, [inputValue, onChange, message]);
|
||||
|
||||
const handleCancelEdit = useCallback(() => {
|
||||
setInputValue(path);
|
||||
setEditing(false);
|
||||
}, [path]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Text type="secondary" className="text-xs">
|
||||
{label}
|
||||
</Text>
|
||||
|
||||
{editing ? (
|
||||
<Space.Compact className="w-full">
|
||||
<Input
|
||||
size="small"
|
||||
value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
onPressEnter={handleSaveInput}
|
||||
placeholder="请输入或粘贴路径"
|
||||
className="flex-1"
|
||||
/>
|
||||
<Button size="small" type="primary" onClick={handleSaveInput}>
|
||||
确定
|
||||
</Button>
|
||||
<Button size="small" onClick={handleCancelEdit}>
|
||||
取消
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
) : path ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<Paragraph
|
||||
className="mb-0! flex-1 min-w-0"
|
||||
style={{ maxWidth: 240 }}
|
||||
ellipsis={{ rows: 1, tooltip: path }}
|
||||
>
|
||||
<Text className="text-xs" style={{ fontFamily: 'monospace' }}>
|
||||
{path}
|
||||
</Text>
|
||||
</Paragraph>
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => {
|
||||
setInputValue(path);
|
||||
setEditing(true);
|
||||
}}
|
||||
title="手动编辑"
|
||||
/>
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={onClear}
|
||||
title="清除路径"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
<Text type="secondary" className="text-xs italic flex-1">
|
||||
未设置
|
||||
</Text>
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => {
|
||||
setInputValue('');
|
||||
setEditing(true);
|
||||
}}
|
||||
title="手动输入"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!editing && (
|
||||
<Button
|
||||
size="small"
|
||||
icon={<FolderOpenOutlined />}
|
||||
onClick={handleSelectFolder}
|
||||
className="self-start mt-1"
|
||||
>
|
||||
{hasIpc() ? '选择文件夹' : '手动输入路径'}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function StoragePathSetting({ edition }: StoragePathSettingProps) {
|
||||
const {
|
||||
outputPath,
|
||||
teamRepoPath,
|
||||
setOutputPath,
|
||||
setTeamRepoPath,
|
||||
clearOutputPath,
|
||||
clearTeamRepoPath,
|
||||
} = useSettings();
|
||||
|
||||
const isEnterprise = edition === 'enterprise';
|
||||
|
||||
return (
|
||||
<Card size="small" title="存储路径设置" className="rounded-md!">
|
||||
<div className="flex flex-col gap-4">
|
||||
<PathRow
|
||||
label="大模型产物存储仓库路径"
|
||||
path={outputPath}
|
||||
onChange={setOutputPath}
|
||||
onClear={clearOutputPath}
|
||||
/>
|
||||
|
||||
{isEnterprise && (
|
||||
<PathRow
|
||||
label="团队创作存储仓库路径"
|
||||
path={teamRepoPath}
|
||||
onChange={setTeamRepoPath}
|
||||
onClear={clearTeamRepoPath}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user