设置中心与 Mock 数据功能
- 顶栏:移除 tauri-native 传输徽章;⋯ 更多菜单重做(设置/Mock/环境/主题/文档/官网/重置) - 设置弹窗重写:左侧导航(外观/网络/Mock 数据/数据管理/关于)+ 分组卡片 + 开关控件 - Mock 引擎(packages/core/src/mock.ts):Mock.js 常用语法子集 + 智能字段匹配 - 61 条默认规则(通配符/精确/正则 × string/number/integer/boolean) - 发送链路接入:Query/JSON Body/urlencoded/form-data/GraphQL Variables - Body 工具栏:智能填充(生成可编辑 @表达式模板)+ Mock 预览 - 设置页规则表 CRUD + 表达式速查表即时预览 - 文档新增「Mock 数据」页(/docs/mock),Manifest 顺序调整 - 新增 16 个 Mock 单测(总 37 前端用例全绿)
This commit is contained in:
1 parent
6307a017a3
commit
9a019408ab
18 files changed
+1684
-104
No files matched your search
@@ -1,10 +1,9 @@
|
||||
/**
|
||||
* 顶栏:logo / 新建 / 导入导出 / 环境 / 主题 / 设置。
|
||||
* 顶栏:logo / 新建 / 导入导出 / 环境 / 主题 / 文档 / 设置 / 更多。
|
||||
*/
|
||||
import { detectTransport } from "@opsxapi/transport";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useWorkspace } from "../workspaceStore";
|
||||
import { ContextMenu, cx, type MenuItem } from "./common";
|
||||
import { ContextMenu, type MenuItem } from "./common";
|
||||
import { EnvBadge } from "./EnvBadge";
|
||||
import { Icon, type IconName } from "./icons";
|
||||
import { Logo } from "./Logo";
|
||||
@@ -31,7 +30,7 @@ export function TopBar() {
|
||||
|
||||
const [newMenu, setNewMenu] = useState<{ x: number; y: number } | null>(null);
|
||||
const [exportMenu, setExportMenu] = useState<{ x: number; y: number } | null>(null);
|
||||
const transportKind = useMemo(() => detectTransport({ ginBaseUrl: "" }).kind, []);
|
||||
const [moreMenu, setMoreMenu] = useState<{ x: number; y: number } | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
document.title = "OpsxAPI · 接口工作台";
|
||||
@@ -90,6 +89,63 @@ export function TopBar() {
|
||||
},
|
||||
];
|
||||
|
||||
const moreItems: MenuItem[] = [
|
||||
{
|
||||
label: "设置…",
|
||||
icon: <Icon name="settings" size={14} />,
|
||||
onClick: () => setDialog({ type: "settings" }),
|
||||
},
|
||||
{
|
||||
label: "Mock 数据…",
|
||||
icon: <Icon name="flask-conical" size={14} />,
|
||||
onClick: () => setDialog({ type: "settings", tab: "mock" }),
|
||||
},
|
||||
{
|
||||
label: "环境管理…",
|
||||
icon: <Icon name="arrow-left-right" size={14} />,
|
||||
onClick: () => setDialog({ type: "environments" }),
|
||||
},
|
||||
{
|
||||
label: settings.theme === "dark" ? "切换到浅色主题" : "切换到深色主题",
|
||||
icon: <Icon name={settings.theme === "dark" ? "sun" : "moon"} size={14} />,
|
||||
onClick: () => setTheme(settings.theme === "dark" ? "light" : "dark"),
|
||||
},
|
||||
{ type: "sep" as const },
|
||||
{
|
||||
label: (
|
||||
<span className="flex items-center gap-2">
|
||||
用户文档
|
||||
<Icon name="external-link" size={11} className="ops-faint" />
|
||||
</span>
|
||||
),
|
||||
icon: <Icon name="book-open" size={14} />,
|
||||
onClick: () => window.open("/docs", "_blank", "noopener,noreferrer"),
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<span className="flex items-center gap-2">
|
||||
官网与价格
|
||||
<Icon name="external-link" size={11} className="ops-faint" />
|
||||
</span>
|
||||
),
|
||||
icon: <Icon name="globe" size={14} />,
|
||||
onClick: () => window.open("/home", "_blank", "noopener,noreferrer"),
|
||||
},
|
||||
{ type: "sep" as const },
|
||||
{
|
||||
label: "重置工作区…",
|
||||
icon: <Icon name="trash" size={14} />,
|
||||
danger: true,
|
||||
onClick: () =>
|
||||
setDialog({
|
||||
type: "confirm",
|
||||
title: "重置工作区",
|
||||
message: "将恢复到初始示例数据,当前集合 / 环境 / 历史会被覆盖(建议先在「导出」中生成全量备份)。确定继续吗?",
|
||||
action: () => useWorkspace.getState().resetWorkspace(),
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<header
|
||||
className="ops-panel ops-divider flex h-12 shrink-0 items-center justify-between gap-3 px-4"
|
||||
@@ -179,14 +235,29 @@ export function TopBar() {
|
||||
>
|
||||
<Icon name="book-open" size={14} /> 文档
|
||||
</a>
|
||||
<button className="ops-btn ops-btn-ghost" onClick={() => setDialog({ type: "settings" })} title="设置">
|
||||
<button
|
||||
className="ops-btn ops-btn-ghost"
|
||||
onClick={() => setDialog({ type: "settings" })}
|
||||
title="设置"
|
||||
>
|
||||
<Icon name="settings" size={15} />
|
||||
</button>
|
||||
<button
|
||||
className="ops-btn ops-btn-ghost"
|
||||
title="更多"
|
||||
onClick={(e) => {
|
||||
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
||||
setMoreMenu({ x: rect.right, y: rect.bottom + 4 });
|
||||
}}
|
||||
>
|
||||
<Icon name="ellipsis" size={16} />
|
||||
</button>
|
||||
<span className={cx("ops-chip")} style={{ padding: "1px 8px", fontSize: 10 }} title="请求通道">
|
||||
{transportKind}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<ContextMenu
|
||||
menu={moreMenu ? { x: moreMenu.x, y: moreMenu.y, items: moreItems } : null}
|
||||
onClose={() => setMoreMenu(null)}
|
||||
/>
|
||||
<ContextMenu
|
||||
menu={newMenu ? { x: newMenu.x, y: newMenu.y, items: newItems } : null}
|
||||
onClose={() => setNewMenu(null)}
|
||||
|
||||
Reference in new issue
Block a user