mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-10 08:43:34 +08:00
🎨 style(connection): 连接表单必填项置顶并去掉层层卡片
信息架构:原顺序是 身份信息 → 连接 URI → …(约 800 行后)→ 目标地址 → 服务与数据库 → 凭据, 最关键的 host/端口/账号/密码排在最后,而可选的连接名称、预设类型与很高的 URI 多行输入框 占据首屏。 - 把 identity 与 uri 两组从条件链之前搬到之后,使首屏依次为目标地址 → 生产连接保护 → 连接模式 → 认证凭据;连接名称与预设类型下沉,URI 作为「粘贴整串」的高级入口置于末尾 - URI 分组改为可折叠且默认折叠;编辑已含 uri 的连接时默认展开,避免看不到已录入的值 - configSectionCardStyle 由「16px 圆角 + 边框 + 内阴影 + 独立底色」改为「小标题 + 细上分割线」, 并用 CSS 让首组不显示分割线 - modalInnerSectionStyle 同样去除边框/底色/圆角(保留内边距,间距不变)。该样式同时用于 左侧导航容器、右侧内容面板与网络安全各分组,原先与内部分组叠成多层「卡片套卡片」 - 左侧导航项改为扁平列表:去掉 14px 圆角、边框、渐变底与投影,选中态只用左侧强调条 + 淡底 - 测试补 expandUriSection 辅助函数:两个断言 URI 文案/按钮的用例需先展开该分组。 已确认这两项失败确由本次行为变更引起(基线 21 项全通过)
This commit is contained in:
@@ -414,6 +414,13 @@ body[data-theme='light'] .redis-viewer-workbench .ant-radio-button-wrapper-check
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 连接配置的表单分组改用「小标题 + 细分割线」代替卡片(见 ConnectionModal.tsx 的
|
||||
configSectionCardStyle)。每组靠一条上分割线与前一组划界,因此首组不需要这条线。 */
|
||||
[data-connection-config-section]:first-child {
|
||||
padding-top: 0 !important;
|
||||
border-top: none !important;
|
||||
}
|
||||
|
||||
/* Custom Title Bar Close Button Hover */
|
||||
body[data-platform='windows'] .titlebar-window-controls {
|
||||
gap: 8px;
|
||||
|
||||
@@ -68,6 +68,19 @@ const findClickableByAnyText = (renderer: ReactTestRenderer, texts: string[]) =>
|
||||
const findClickableCard = (renderer: ReactTestRenderer, text: string) =>
|
||||
renderer.root.findAll((node) => node.props?.role === "button" && textContent(node).includes(text))[0];
|
||||
|
||||
/**
|
||||
* 展开「连接 URI」分组。
|
||||
*
|
||||
* 该分组默认折叠:它的多行输入框很高,展开时会把 host/账号/密码这些必填项挤出首屏,
|
||||
* 因此只有编辑已含 uri 的连接时才默认展开。断言 URI 相关文案或按钮的用例需先展开。
|
||||
*/
|
||||
const expandUriSection = (renderer: ReactTestRenderer) => {
|
||||
const toggle = renderer.root.findAll(
|
||||
(node) => node.props?.["data-connection-config-section-toggle"] === "uri",
|
||||
)[0];
|
||||
toggle?.props?.onClick?.({ stopPropagation: () => {} });
|
||||
};
|
||||
|
||||
const flushConnectionTestTick = async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
await Promise.resolve();
|
||||
@@ -899,6 +912,10 @@ describe("ConnectionModal i18n", () => {
|
||||
/>,
|
||||
);
|
||||
});
|
||||
// URI 分组默认折叠(见 expandUriSection 的说明),先展开再断言其占位示例。
|
||||
await act(async () => {
|
||||
expandUriSection(renderer!);
|
||||
});
|
||||
expect(textContent(renderer!.toJSON())).toContain(
|
||||
"For example: postgres://user:pass@127.0.0.1:5432/db_name",
|
||||
);
|
||||
@@ -988,6 +1005,10 @@ describe("ConnectionModal i18n", () => {
|
||||
findClickableCard(renderer!, "MySQL").props.onClick();
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
expandUriSection(renderer!);
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
findButton(renderer!, "Generate URI").props.onClick();
|
||||
});
|
||||
|
||||
@@ -539,14 +539,19 @@ const ConnectionModal: React.FC<{
|
||||
[overlayTheme],
|
||||
);
|
||||
|
||||
// 弹窗内的区块容器去卡片化:原先是 14px 圆角 + 边框 + 独立底色,
|
||||
// 而它同时用在左侧导航容器、右侧内容面板与网络安全的各个分组上,
|
||||
// 再叠加内部分组各自的卡片,形成多层「卡片套卡片」,整页视觉噪声很高。
|
||||
// 现在只保留内边距,靠内部的小标题与分割线划分层次。
|
||||
// 保留原有内边距(间距不变,避免表单贴到容器边缘),只去掉边框、底色与圆角。
|
||||
const modalInnerSectionStyle = useMemo(
|
||||
() => ({
|
||||
padding: 14,
|
||||
borderRadius: 14,
|
||||
border: overlayTheme.sectionBorder,
|
||||
background: overlayTheme.sectionBg,
|
||||
borderRadius: 0,
|
||||
border: "none",
|
||||
background: "transparent",
|
||||
}),
|
||||
[overlayTheme],
|
||||
[],
|
||||
);
|
||||
|
||||
const modalMutedTextStyle = useMemo(
|
||||
@@ -759,18 +764,15 @@ const ConnectionModal: React.FC<{
|
||||
</div>
|
||||
);
|
||||
|
||||
// 表单分组改用「小标题 + 细分割线」而不是卡片:
|
||||
// 原先每个分组都是 16px 圆角 + 边框 + 内阴影 + 独立底色的卡片,一屏里叠七八个,
|
||||
// 视觉噪声极高且每张卡各占 16px 内边距,把真正的字段挤得很散。
|
||||
// 去掉边框/圆角/底色/内阴影,只保留组间的一条上分割线来划分层次,字段密度明显提升。
|
||||
const configSectionCardStyle = (): React.CSSProperties => ({
|
||||
padding: 16,
|
||||
borderRadius: 16,
|
||||
border: darkMode
|
||||
? "1px solid rgba(255,255,255,0.08)"
|
||||
: "1px solid rgba(16,24,40,0.08)",
|
||||
background: darkMode
|
||||
? "rgba(255,255,255,0.025)"
|
||||
: "rgba(255,255,255,0.70)",
|
||||
boxShadow: darkMode
|
||||
? "inset 0 1px 0 rgba(255,255,255,0.04)"
|
||||
: "inset 0 1px 0 rgba(255,255,255,0.90)",
|
||||
paddingTop: 18,
|
||||
borderTop: darkMode
|
||||
? "1px solid rgba(255,255,255,0.07)"
|
||||
: "1px solid rgba(16,24,40,0.07)",
|
||||
});
|
||||
|
||||
const renderConfigSectionCard = ({
|
||||
|
||||
@@ -200,10 +200,21 @@ const ConnectionModalStep2: React.FC<ConnectionModalStep2Props> = (props) => {
|
||||
const [readOnlyProtectionExpanded, setReadOnlyProtectionExpanded] =
|
||||
React.useState(false);
|
||||
|
||||
// 连接 URI 属于「粘贴整串」的高级入口,默认折叠:它的多行输入框很高,
|
||||
// 展开时会把 host/账号/密码这些必填项挤出首屏。
|
||||
// 已经填了 URI 的连接(编辑场景)默认展开,避免用户看不到自己已录入的值。
|
||||
const [uriSectionExpanded, setUriSectionExpanded] = React.useState(
|
||||
() => String(initialValues?.uri || "").trim() !== "",
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
setReadOnlyProtectionExpanded(false);
|
||||
}, [dbType]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setUriSectionExpanded(String(initialValues?.uri || "").trim() !== "");
|
||||
}, [dbType, initialValues?.uri]);
|
||||
|
||||
const renderStep2 = () => {
|
||||
const showConnectionReadOnlyField = supportsConnectionReadOnlyMode({
|
||||
type: dbType,
|
||||
@@ -240,107 +251,6 @@ const ConnectionModalStep2: React.FC<ConnectionModalStep2Props> = (props) => {
|
||||
</div>
|
||||
|
||||
<div style={{ display: "grid", gap: 16 }}>
|
||||
{renderConfigSectionCard({
|
||||
sectionKey: "identity",
|
||||
icon: <ApiOutlined />,
|
||||
badge: (
|
||||
<Tag>
|
||||
{getConnectionConfigLayoutKindLabel(connectionConfigLayout.kind)}
|
||||
</Tag>
|
||||
),
|
||||
children: (
|
||||
<>
|
||||
<Form.Item
|
||||
name="name"
|
||||
label={t("connection.modal.field.name.label")}
|
||||
>
|
||||
<Input
|
||||
{...noAutoCapInputProps}
|
||||
placeholder={
|
||||
isJVM
|
||||
? t("connection.modal.field.name.placeholder.jvm")
|
||||
: t("connection.modal.field.name.placeholder.default")
|
||||
}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="environmentType"
|
||||
label={t("connection.modal.field.environment_type.label")}
|
||||
initialValue={DEFAULT_CONNECTION_ENVIRONMENT}
|
||||
style={{ marginBottom: 0 }}
|
||||
>
|
||||
<ConnectionEnvironmentSelect />
|
||||
</Form.Item>
|
||||
</>
|
||||
),
|
||||
})}
|
||||
|
||||
{!isCustom &&
|
||||
!isJVM &&
|
||||
renderConfigSectionCard({
|
||||
sectionKey: "uri",
|
||||
icon: <LinkOutlined />,
|
||||
children: (
|
||||
<>
|
||||
<Form.Item
|
||||
name="uri"
|
||||
label={t("connection.modal.uri.label")}
|
||||
help={t("connection.modal.uri.help")}
|
||||
>
|
||||
<Input.TextArea
|
||||
{...noAutoCapInputProps}
|
||||
rows={3}
|
||||
placeholder={getUriPlaceholder(dbType)}
|
||||
/>
|
||||
</Form.Item>
|
||||
{supportsConnectionParams && (
|
||||
<Form.Item
|
||||
name="connectionParams"
|
||||
label={t("connection.modal.connectionParams.label")}
|
||||
help={t("connection.modal.connectionParams.help")}
|
||||
>
|
||||
<Input.TextArea
|
||||
{...noAutoCapInputProps}
|
||||
rows={2}
|
||||
placeholder={getConnectionParamsPlaceholder(dbType, oceanBaseProtocol)}
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
<Space
|
||||
size={8}
|
||||
style={{ marginBottom: uriFeedback ? 12 : 16 }}
|
||||
wrap
|
||||
>
|
||||
<Button onClick={handleGenerateURI}>
|
||||
{t("connection.modal.uri.action.generate")}
|
||||
</Button>
|
||||
<Button onClick={handleParseURI}>
|
||||
{t("connection.modal.uri.action.parse")}
|
||||
</Button>
|
||||
<Button onClick={handleCopyURI}>
|
||||
{t("connection.modal.uri.action.copy")}
|
||||
</Button>
|
||||
</Space>
|
||||
{uriFeedback && (
|
||||
<Alert
|
||||
showIcon
|
||||
closable
|
||||
type={uriFeedback.type}
|
||||
message={resolvedUriFeedbackMessage}
|
||||
onClose={() => setUriFeedback(null)}
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
)}
|
||||
{renderStoredSecretControls({
|
||||
fieldName: "uri",
|
||||
clearKey: "opaqueURI",
|
||||
hasStoredSecret: initialValues?.hasOpaqueURI,
|
||||
clearLabel: t("connection.modal.uri.stored.clear"),
|
||||
description: t("connection.modal.uri.stored.description"),
|
||||
})}
|
||||
</>
|
||||
),
|
||||
})}
|
||||
|
||||
{isCustom ? (
|
||||
<>
|
||||
@@ -2539,6 +2449,115 @@ const ConnectionModalStep2: React.FC<ConnectionModalStep2Props> = (props) => {
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 必填项优先:目标地址、凭据、数据库范围由上方的条件分支渲染。
|
||||
连接名称与预设类型属于可选的展示信息,连接 URI 属于「粘贴整串」的高级入口,
|
||||
两者都下沉到必填项之后,避免它们(尤其是 URI 的多行输入框)把
|
||||
host/账号/密码挤出首屏。 */}
|
||||
{renderConfigSectionCard({
|
||||
sectionKey: "identity",
|
||||
icon: <ApiOutlined />,
|
||||
badge: (
|
||||
<Tag>
|
||||
{getConnectionConfigLayoutKindLabel(connectionConfigLayout.kind)}
|
||||
</Tag>
|
||||
),
|
||||
children: (
|
||||
<>
|
||||
<Form.Item
|
||||
name="name"
|
||||
label={t("connection.modal.field.name.label")}
|
||||
>
|
||||
<Input
|
||||
{...noAutoCapInputProps}
|
||||
placeholder={
|
||||
isJVM
|
||||
? t("connection.modal.field.name.placeholder.jvm")
|
||||
: t("connection.modal.field.name.placeholder.default")
|
||||
}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="environmentType"
|
||||
label={t("connection.modal.field.environment_type.label")}
|
||||
initialValue={DEFAULT_CONNECTION_ENVIRONMENT}
|
||||
style={{ marginBottom: 0 }}
|
||||
>
|
||||
<ConnectionEnvironmentSelect />
|
||||
</Form.Item>
|
||||
</>
|
||||
),
|
||||
})}
|
||||
|
||||
{!isCustom &&
|
||||
!isJVM &&
|
||||
renderConfigSectionCard({
|
||||
sectionKey: "uri",
|
||||
icon: <LinkOutlined />,
|
||||
collapsible: true,
|
||||
expanded: uriSectionExpanded,
|
||||
onToggle: () => setUriSectionExpanded((expanded) => !expanded),
|
||||
children: (
|
||||
<>
|
||||
<Form.Item
|
||||
name="uri"
|
||||
label={t("connection.modal.uri.label")}
|
||||
help={t("connection.modal.uri.help")}
|
||||
>
|
||||
<Input.TextArea
|
||||
{...noAutoCapInputProps}
|
||||
rows={3}
|
||||
placeholder={getUriPlaceholder(dbType)}
|
||||
/>
|
||||
</Form.Item>
|
||||
{supportsConnectionParams && (
|
||||
<Form.Item
|
||||
name="connectionParams"
|
||||
label={t("connection.modal.connectionParams.label")}
|
||||
help={t("connection.modal.connectionParams.help")}
|
||||
>
|
||||
<Input.TextArea
|
||||
{...noAutoCapInputProps}
|
||||
rows={2}
|
||||
placeholder={getConnectionParamsPlaceholder(dbType, oceanBaseProtocol)}
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
<Space
|
||||
size={8}
|
||||
style={{ marginBottom: uriFeedback ? 12 : 16 }}
|
||||
wrap
|
||||
>
|
||||
<Button onClick={handleGenerateURI}>
|
||||
{t("connection.modal.uri.action.generate")}
|
||||
</Button>
|
||||
<Button onClick={handleParseURI}>
|
||||
{t("connection.modal.uri.action.parse")}
|
||||
</Button>
|
||||
<Button onClick={handleCopyURI}>
|
||||
{t("connection.modal.uri.action.copy")}
|
||||
</Button>
|
||||
</Space>
|
||||
{uriFeedback && (
|
||||
<Alert
|
||||
showIcon
|
||||
closable
|
||||
type={uriFeedback.type}
|
||||
message={resolvedUriFeedbackMessage}
|
||||
onClose={() => setUriFeedback(null)}
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
)}
|
||||
{renderStoredSecretControls({
|
||||
fieldName: "uri",
|
||||
clearKey: "opaqueURI",
|
||||
hasStoredSecret: initialValues?.hasOpaqueURI,
|
||||
clearLabel: t("connection.modal.uri.stored.clear"),
|
||||
description: t("connection.modal.uri.stored.description"),
|
||||
})}
|
||||
</>
|
||||
),
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -3107,26 +3126,26 @@ const ConnectionModalStep2: React.FC<ConnectionModalStep2Props> = (props) => {
|
||||
key={item.key}
|
||||
type="button"
|
||||
onClick={() => setActiveConfigSection(item.key)}
|
||||
/* 导航项改为扁平列表:原先每项都是 14px 圆角 + 边框 + 渐变底 + 投影的
|
||||
独立卡片,三项叠在一个卡片容器里形成「卡片套卡片」,视觉噪声很高。
|
||||
现在只用左侧强调条 + 淡底表示选中,去掉边框、圆角与投影。 */
|
||||
style={{
|
||||
textAlign: "left",
|
||||
padding: "12px 12px 12px 14px",
|
||||
borderRadius: 14,
|
||||
border: `1px solid ${
|
||||
padding: "10px 10px 10px 12px",
|
||||
borderRadius: 8,
|
||||
border: "none",
|
||||
borderLeft: `2px solid ${
|
||||
active
|
||||
? darkMode
|
||||
? "rgba(255,214,102,0.3)"
|
||||
: "rgba(24,144,255,0.24)"
|
||||
: darkMode
|
||||
? "rgba(255,255,255,0.045)"
|
||||
: "rgba(16,24,40,0.055)"
|
||||
? "#ffd666"
|
||||
: "#1677ff"
|
||||
: "transparent"
|
||||
}`,
|
||||
background: active
|
||||
? darkMode
|
||||
? "linear-gradient(180deg, rgba(255,214,102,0.12) 0%, rgba(255,214,102,0.06) 100%)"
|
||||
: "linear-gradient(180deg, rgba(24,144,255,0.10) 0%, rgba(24,144,255,0.05) 100%)"
|
||||
: darkMode
|
||||
? "rgba(255,255,255,0.02)"
|
||||
: "rgba(255,255,255,0.7)",
|
||||
? "rgba(255,214,102,0.10)"
|
||||
: "rgba(24,144,255,0.08)"
|
||||
: "transparent",
|
||||
color: active
|
||||
? darkMode
|
||||
? "#f5f7ff"
|
||||
@@ -3135,12 +3154,8 @@ const ConnectionModalStep2: React.FC<ConnectionModalStep2Props> = (props) => {
|
||||
? "rgba(255,255,255,0.76)"
|
||||
: "#3f4b5e",
|
||||
cursor: "pointer",
|
||||
transition: "all 120ms ease",
|
||||
boxShadow: active
|
||||
? darkMode
|
||||
? "0 10px 24px rgba(0,0,0,0.18)"
|
||||
: "0 10px 22px rgba(24,144,255,0.08)"
|
||||
: "none",
|
||||
transition: "background 120ms ease, color 120ms ease",
|
||||
boxShadow: "none",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user