remote pro-components

This commit is contained in:
cnwhy
2024-05-28 19:09:29 +08:00
parent 4d331e5619
commit f387276a0d
22 changed files with 2369 additions and 7383 deletions
+3
View File
@@ -22,3 +22,6 @@ dist-ssr
*.njsproj
*.sln
*.sw?
package-lock.json
public/monaco-editor
+1844 -7140
View File
File diff suppressed because it is too large Load Diff
+9 -4
View File
@@ -8,15 +8,19 @@
"build": "tsc && vite build",
"analyzer": "tsc && vite build --mode=analyzer",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
"preview": "vite preview",
"init:monaco": "cpx ./node_modules/monaco-editor/min/** ./public/monaco-editor/min",
"postinstall": "npm run init:monaco",
"predev": "npm run init:monaco",
"prebuild": "npm run init:monaco",
"preanalyzer": "npm run init:monaco"
},
"dependencies": {
"@ant-design/icons": "^5.2.6",
"@ant-design/pro-components": "^2.6.35",
"antd": "^5.11.0",
"axios": "^1.6.0",
"classnames": "^2.5.1",
"events": "^3.3.0",
"i": "^0.3.7",
"idb": "^7.1.1",
"idb-open-plus": "^1.0.0",
"jsonc": "^2.0.0",
@@ -38,11 +42,12 @@
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"@vitejs/plugin-react": "^4.0.3",
"cpx2": "^7.0.1",
"eslint": "^8.45.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"typescript": "^5.0.2",
"vite": "^4.4.5",
"vite-bundle-analyzer": "^0.4.0"
"vite-bundle-analyzer": "^0.9.2"
}
}
+34
View File
@@ -1,3 +1,12 @@
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
align-items: stretch;
}
#root {
width: 100%;
}
@@ -13,3 +22,28 @@
cursor: pointer;
}
.antd-cord-boxShadow {
box-shadow: 0 1px 2px -2px #00000029,0 3px 6px #0000001f,0 5px 12px 4px #00000017;
border-color: transparent;
}
.home-form {
width: 380px;
max-width: 100%;
box-sizing: border-box;
padding: 0 15px;
margin: 0 auto;
}
.home-form h1 {
text-align: center;
font-size: 30px;
margin: .5em 0 0 0;
}
.home-form h2 {
text-align: center;
font-size: 16px;
opacity: 0.8;
padding: 0 0 1em 0;
margin: 0;
font-weight: normal;
}
+7 -4
View File
@@ -1,4 +1,4 @@
import { useState, useEffect, useRef, useMemo, useCallback } from "react";
import React, { useState, useEffect, useRef, useMemo } from "react";
import { ConfigProvider, message, theme } from "antd";
import {
init,
@@ -13,10 +13,11 @@ import "./App.css";
import { configEvent } from "./uitls/events";
import zhCN from "antd/locale/zh_CN";
import Manage from "./Pages/Manage";
import { ServerComm } from "./api/local";
import Ctx from "./uitls/ctx";
const Manage = React.lazy(() => import("./Pages/Manage"));
function App() {
const info = useInfo();
// const [gostConfig, setGostConfig] = useState<any>(null);
@@ -44,7 +45,7 @@ function App() {
]);
useServerConfig.set(l1 as any);
useLocalConfig.set(l2);
return [l1, l2];
return [l1, l2];
} finally {
setServerLoading(false);
setLocalLoading(false);
@@ -123,7 +124,9 @@ function App() {
theme={{ algorithm: isDark ? theme.darkAlgorithm : undefined }}
locale={zhCN}
>
{info ? <Manage /> : <Home />}
<React.Suspense fallback="loading...">
{info ? <Manage /> : <Home />}
</React.Suspense>
</ConfigProvider>
</Ctx.Provider>
);
+54 -145
View File
@@ -1,132 +1,25 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import React, { useCallback, useEffect, useState } from "react";
import {
BetaSchemaForm,
ProForm,
LoginForm,
ProFormText,
ProFormCheckbox,
} from "@ant-design/pro-components";
import type {
ProFormColumnsType,
ProFormLayoutType,
} from "@ant-design/pro-components";
import {
DeleteOutlined,
GlobalOutlined,
LockOutlined,
UserOutlined,
} from "@ant-design/icons";
import { Checkbox, Col, Flex, Form, Row, Space } from "antd";
import {
GostApiConfig,
getLocalServers,
login,
deleteLocal,
} from "../uitls/server";
type DataItem = { name: string; state: string };
const columns: ProFormColumnsType<DataItem>[] = [
{
title: "gost API 地址",
dataIndex: "addr",
valueType: "text",
},
{
title: "usrname",
dataIndex: "addr",
valueType: "text",
},
{
title: "password",
dataIndex: "addr",
valueType: "password",
},
];
const LocalServers = () => {
const [list, setList] = useState<GostApiConfig[]>();
// const [local, setLocal] = useState<Record<string, GostApiConfig>>();
const updateList = useCallback(async () => {
return getLocalServers()
.then((local) => {
return local
.sort((a, b) => {
const t1 = a.time || 0;
const t2 = b.time || 0;
return t2 - t1;
});
})
.then((list) => setList(list));
}, []);
useEffect(() => {
updateList();
// getList().then(list=>setList(list))
}, []);
return (
<>
{list && list?.length > 0 ? (
<Space direction="vertical" style={{ display: "flex" }}>
<div></div>
<Row gutter={10}>
{list.map((item) => {
return (
<Col
key={item.addr}
span={12}
title={item.addr}
style={{
overflow: "hidden",
textOverflow: "ellipsis",
}}
>
<Flex gap={5} style={{ overflow: "hidden" }}>
<a
style={{
overflow: "hidden",
textOverflow: "ellipsis",
flex: "auto",
}}
href={`?use=${item.addr}`}
>
{item.addr}
</a>
<DeleteOutlined
style={{ color: "red" }}
onClick={async () => {
await deleteLocal(item.addr);
updateList();
}}
/>
</Flex>
</Col>
);
})}
</Row>
</Space>
) : null}
</>
);
};
import React from "react";
import { GlobalOutlined, LockOutlined, UserOutlined } from "@ant-design/icons";
import { Button, Checkbox, Form, Input } from "antd";
import { login } from "../uitls/server";
import LocalServers from "../components/LocalServers";
const Home: React.FC = () => {
return (
<LoginForm
containerStyle={{ boxSizing: "border-box" }}
title="GOST API Manage"
subTitle="首先连接API服务"
<Form
className="home-form"
size="large"
layout="horizontal"
submitter={{
searchConfig: { submitText: "连接" },
initialValues={{
baseURL: "http://",
save: true,
}}
onFinish={(value) => {
let addr: string = value.baseURL;
if (!/^(https?:)?\/\//.test(addr)) {
addr = `${location.protocol}//` + addr;
}else if(/^\/\//.test(addr)){
} else if (/^\/\//.test(addr)) {
addr = `${location.protocol}` + addr;
}
return login(
@@ -140,40 +33,56 @@ const Home: React.FC = () => {
value.save
);
}}
actions={<LocalServers></LocalServers>}
>
<ProFormText
<h1>GOST API Manage</h1>
<h2>API服务</h2>
<Form.Item
name="baseURL"
fieldProps={{
size: "large",
prefix: <GlobalOutlined className={"prefixIcon"} />,
}}
placeholder={"API baseURL"}
rules={[
{
required: true,
message: "请输入API地址",
},
{
validator(rule, value, callback) {
if (value === "http://") {
callback("请输入API地址");
}
callback();
},
},
// {
// type:'url'
// }
]}
/>
<ProFormText
name="username"
fieldProps={{
size: "large",
prefix: <UserOutlined className={"prefixIcon"} />,
}}
placeholder={"username"}
/>
<ProFormText.Password
name="password"
fieldProps={{
size: "large",
prefix: <LockOutlined className={"prefixIcon"} />,
}}
placeholder={"password"}
/>
<ProFormCheckbox labelAlign="right" label="保存到本地" name="save" />
</LoginForm>
>
<Input
placeholder="API baseURL"
prefix={<GlobalOutlined className={"prefixIcon"} />}
></Input>
</Form.Item>
<Form.Item name="username">
<Input
placeholder="username"
prefix={<UserOutlined className={"prefixIcon"} />}
></Input>
</Form.Item>
<Form.Item name="password">
<Input.Password
placeholder="password"
prefix={<LockOutlined className={"prefixIcon"} />}
></Input.Password>
</Form.Item>
<Form.Item name="save" valuePropName="checked">
<Checkbox></Checkbox>
</Form.Item>
<Form.Item noStyle style={{ marginBottom: "1em" }}>
<Button block type="primary" htmlType="submit">
</Button>
</Form.Item>
<LocalServers></LocalServers>
</Form>
);
};
+19 -17
View File
@@ -1,11 +1,4 @@
import {
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
Button,
Col,
@@ -31,10 +24,9 @@ import { download, jsonFormat } from "../uitls";
import { MonacoEditor } from "../uitls/userMonacoWorker";
import Ctx from "../uitls/ctx";
import * as API from "../api";
import ListCard from "../components/ListCard";
import ListCard, { ProCard } from "../components/ListCard";
import ChainCard from "../components/ListCard/Chains";
import ServiceCard from "../components/ListCard/Services";
import { ProCard } from "@ant-design/pro-components";
import HopsCard from "../components/ListCard/Hops";
import { fixOldCacheConfig } from "../api/local";
import { configEvent } from "../uitls/events";
@@ -167,9 +159,13 @@ const Manage = () => {
// style={{ padding: "0 15px" }}
>
<Col color="">
<Button type="link" icon={<ReloadOutlined />} onClick={async () => {
useServerConfig.set((await API.getConfig()) as any)
}}>
<Button
type="link"
icon={<ReloadOutlined />}
onClick={async () => {
useServerConfig.set((await API.getConfig()) as any);
}}
>
</Button>
</Col>
@@ -229,7 +225,7 @@ const Manage = () => {
<Form.Item name="autoSave" label="自动保存" valuePropName="checked">
<Switch />
</Form.Item>
<Form.Item name="saveFormat" label="格式">
<Form.Item name="saveFormat" label="格式" initialValue={'json'}>
<Radio.Group optionType="button" buttonStyle="solid">
<Radio value="json">json</Radio>
<Radio value="yaml">yaml</Radio>
@@ -271,17 +267,23 @@ const Manage = () => {
<Col {...colSpan}>
<ListCard module="resolver" />
</Col>
<Col {...colSpan}>
<ListCard module="sd" />
</Col>
<Col {...colSpan}>
<ListCard module="observer" />
</Col>
<Col span={24}>
<ProCard boxShadow title="限速限流">
<Row gutter={[16, 16]}>
<Col {...colSpan1}>
<ListCard module="limiter" bordered />
<ListCard module="limiter" bordered boxShadow={false} />
</Col>
<Col {...colSpan1}>
<ListCard module="rlimiter" bordered />
<ListCard module="rlimiter" bordered boxShadow={false} />
</Col>
<Col {...colSpan1}>
<ListCard module="climiter" bordered />
<ListCard module="climiter" bordered boxShadow={false} />
</Col>
</Row>
</ProCard>
+4
View File
@@ -15,7 +15,9 @@ export const apis = {
hops: "/config/hops",
hosts: "/config/hosts",
ingresses: "/config/ingresses",
observers: "/config/observers",
resolvers: "/config/resolvers",
sds: "/config/sds",
services: "/config/services",
};
@@ -42,6 +44,8 @@ export const hosts = getRESTfulApi<Gost.HostsConfig>(apis["hosts"]);
export const ingresses = getRESTfulApi<Gost.IngressConfig>(apis["ingresses"]);
export const resolvers = getRESTfulApi<Gost.ResolverConfig>(apis["resolvers"]);
export const services = getRESTfulApi<Gost.ServiceConfig>(apis["services"]);
export const sds = getRESTfulApi<Gost.ServiceConfig>(apis["sds"]);
export const observers = getRESTfulApi<Gost.ServiceConfig>(apis["observers"]);
// 获取当前config
export const getConfig = (format?: Format) => require.get(apis.config);
+12
View File
@@ -95,6 +95,18 @@ const modules: Module[] = [
// localApi: (LocalApi as any)['services'],
// localApi: (LocalApi as any)['services'],
},
{
name: "sd",
title: "服务发现(SD)",
subTitle: "服务发现",
...getAttr("sds"),
},
{
name: "observer",
title: "观测器(Observer)",
subTitle: "观测器",
...getAttr("observers"),
},
];
export const getModule = (name: string) =>
+12 -19
View File
@@ -1,18 +1,14 @@
import React, { useRef } from "react";
import ReactDOM from "react-dom/client";
import {
ModalForm,
ProFormInstance,
ModalFormProps,
} from "@ant-design/pro-components";
import { Button, Dropdown, Form, Space } from "antd";
import { Button, Dropdown, Form, FormInstance, Space } from "antd";
import { DownOutlined } from "@ant-design/icons";
import { jsonFormat, jsonStringFormat, jsonParse } from "../../uitls";
import { jsonFormat, jsonStringFormat, jsonParse, jsonEdit } from "../../uitls";
import * as monaco from "monaco-editor";
import { MonacoEditor, getModel, model, modelUri } from "../../uitls/userMonacoWorker";
import { Template } from "../../templates";
import { render } from "react-dom";
import { useServerConfig } from "../../uitls/server";
// import { render } from "react-dom";
// import { useServerConfig } from "../../uitls/server";
import ModalForm, { ModalFormProps } from "../ModalForm";
const template2menu: any = (template: Template) => {
const { children, ...other } = template;
@@ -52,17 +48,14 @@ type JsonFromProps = ModalFormProps & {
const JsonForm: React.FC<JsonFromProps> = (props) => {
const { templates, ...other } = props;
const formRef = useRef<ProFormInstance>();
const formRef = useRef<FormInstance<any>>();
const templateHandle = (json: string) => {
let value;
let value = json;
if (props.initialValues?.value) {
const _json = jsonParse(json);
const _old = jsonParse(props.initialValues.value);
_json.name = _old.name ? _old.name : _json.name;
value = jsonFormat(_json);
} else {
value = jsonStringFormat(json);
value = _old.name ? jsonEdit(json, [{path:'name', value: _old.name}]) : json;
}
value = jsonStringFormat(value);
formRef.current?.setFieldValue("value", value);
formRef.current?.validateFields();
};
@@ -236,8 +229,8 @@ export const showJsonForm = (props: JsonFromProps) => {
});
};
export const showUpModelForm = (props: any) => {
const { name, root, model } = props;
};
// export const showUpModelForm = (props: any) => {
// const { name, root, model } = props;
// };
export default JsonForm;
-29
View File
@@ -1,29 +0,0 @@
import {
BetaSchemaForm,
type ProFormColumnsType,
} from "@ant-design/pro-components";
import { ServiceConfig } from "../../api/types";
const columns: ProFormColumnsType<ServiceConfig>[] = [
{
dataIndex:'name',
title: "服务名",
valueType: 'text',
},
{
dataIndex:'addr',
title: "地址端口",
valueType: 'text',
},
{
dataIndex:'addr',
title: "地址端口",
valueType: 'text',
}
];
const ServiceForm: React.FC<any> = (props) => {
return <BetaSchemaForm<ServiceConfig> columns={columns}></BetaSchemaForm>;
};
export default ServiceForm;
+19 -6
View File
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useState, useMemo, useContext } from "react";
import { getRESTfulApi } from "../../api";
import { ProCard } from "@ant-design/pro-components";
import { Card, CardProps } from "antd";
import PublicList, { PublicListProps } from "../List/Public";
import AddButton from "../Forms/AddButton";
import { GostCommit } from "../../api/local";
@@ -10,6 +10,7 @@ import { Button, Input, Modal, Space, notification } from "antd";
import { getModule } from "../../api/modules";
import { configEvent } from "../../uitls/events";
import { CloseOutlined } from "@ant-design/icons";
import classnames from "classnames";
export type ListCardProps = {
module?: string;
@@ -26,6 +27,14 @@ export type ListCardProps = {
filter?: PublicListProps["filter"];
};
export const ProCard = (props: CardProps & { boxShadow?: boolean }) => {
const { boxShadow, className, ...other } = props;
const _className = classnames(className, {
"antd-cord-boxShadow": boxShadow,
});
return <Card className={_className} {...other}></Card>;
};
const ListCard: React.FC<ListCardProps> = (props) => {
const {
title,
@@ -42,7 +51,7 @@ const ListCard: React.FC<ListCardProps> = (props) => {
} = useMemo(() => {
return { ...getModule(props.module || "")!, ...props };
}, [props]);
const [keyword, setKeyword] = useState('');
const [keyword, setKeyword] = useState("");
const _prop = {
title: subTitle || "",
keyword,
@@ -149,10 +158,14 @@ const ListCard: React.FC<ListCardProps> = (props) => {
title={title}
extra={
<Space>
<Input.Search allowClear onChange={(e)=>{
const value = e.target.value;
setKeyword(value);
}} size="small"></Input.Search>
<Input.Search
allowClear
onChange={(e) => {
const value = e.target.value;
setKeyword(value);
}}
size="small"
></Input.Search>
{/* <Button icon={""} size="small">全屏编辑</Button> */}
<AddButton {..._prop} />
</Space>
+71
View File
@@ -0,0 +1,71 @@
import React, { useCallback, useEffect, useState } from "react";
import { Col, Flex, Row, Space } from "antd";
import { DeleteOutlined } from "@ant-design/icons";
import { GostApiConfig, getLocalServers, deleteLocal } from "../uitls/server";
const LocalServers = () => {
const [list, setList] = useState<GostApiConfig[]>();
// const [local, setLocal] = useState<Record<string, GostApiConfig>>();
const updateList = useCallback(async () => {
return getLocalServers()
.then((local) => {
return local.sort((a, b) => {
const t1 = a.time || 0;
const t2 = b.time || 0;
return t2 - t1;
});
})
.then((list) => setList(list));
}, []);
useEffect(() => {
updateList();
// getList().then(list=>setList(list))
}, []);
return (
<>
{list && list?.length > 0 ? (
<Space direction="vertical" style={{ display: "flex" }}>
<div></div>
<Row gutter={10}>
{list.map((item) => {
return (
<Col
key={item.addr}
span={12}
title={item.addr}
style={{
overflow: "hidden",
textOverflow: "ellipsis",
}}
>
<Flex gap={5} style={{ overflow: "hidden" }}>
<a
style={{
overflow: "hidden",
textOverflow: "ellipsis",
flex: "auto",
}}
href={`?use=${item.addr}`}
>
{item.addr}
</a>
<DeleteOutlined
style={{ color: "red" }}
onClick={async () => {
await deleteLocal(item.addr);
updateList();
}}
/>
</Flex>
</Col>
);
})}
</Row>
</Space>
) : null}
</>
);
};
export default LocalServers;
+127
View File
@@ -0,0 +1,127 @@
import React, { useCallback, useEffect, useMemo } from "react";
import { Form, Modal } from "antd";
import type { FormInstance, FormProps, ModalProps } from "antd";
import { useBindValue } from "../uitls/use";
export type ModalFormProps<T = Record<string, any>> = Omit<
FormProps<T>,
"onFinish" | "title"
> & {
/**
* 接收任意值,返回 真值 会关掉这个抽屉
*
* @name 表单结束后调用
*
* @example 结束后关闭抽屉
* onFinish: async ()=> {await save(); return true}
*
* @example 结束后不关闭抽屉
* onFinish: async ()=> {await save(); return false}
*/
onFinish?: (formData: T) => Promise<any>;
// /** @name 提交数据时,禁用取消按钮的超时时间(毫秒)。 */
// submitTimeout?: number;
/** @name 用于触发抽屉打开的 dom */
trigger?: JSX.Element;
/** @name 受控的打开关闭 */
open?: ModalProps["open"];
/** @name 打开关闭的事件 */
onOpenChange?: (visible: boolean) => void;
/**
* 不支持 'visible',请使用全局的 visible
*
* @name 弹框的属性
*/
modalProps?: Omit<ModalProps, "visible">;
/** @name 弹框的标题 */
title?: ModalProps["title"];
/** @name 弹框的宽度 */
width?: ModalProps["width"];
formRef?: any;
children?: React.ReactNode;
};
const ModalForm: React.FC<ModalFormProps> = (props) => {
const {
onFinish: propOnFinish,
trigger,
open: propOpen,
onOpenChange: propOnOpenChange,
modalProps,
title,
width,
formRef,
children,
...other
} = props;
const [open, setOpen] = useBindValue(propOpen, false);
const [form] = Form.useForm();
const onCancel = useCallback(
() => {
setOpen(false);
propOnOpenChange?.(false);
modalProps?.onCancel?.({} as any)
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[propOnOpenChange]
);
const afterClose = useCallback(() => {
form.resetFields();
modalProps?.afterClose?.()
},[form, modalProps])
const onFinish = useMemo(() => {
return async function (value: any) {
const v = await propOnFinish?.apply(null, [value]);
if (v === true) {
onCancel();
}
};
}, [propOnFinish, onCancel]); // eslint-disable-line react-hooks/exhaustive-deps
const _modalProps: ModalProps = useMemo(() => {
return {
...modalProps,
onCancel,
onOk: () => {
form.submit();
},
afterClose,
open,
title,
width,
};
}, [afterClose, form, modalProps, onCancel, open, title, width]);
useEffect(() => {
if (!formRef) return;
const type = typeof formRef;
switch (type) {
case "function":
formRef(form);
break;
case "object":
formRef.current = form;
break;
default:
break;
}
}, [form, formRef]);
return (
<>
<Modal {..._modalProps}>
<Form form={form} onFinish={onFinish} {...other}>
{children}
</Form>
</Modal>
{trigger &&
React.cloneElement(trigger, {
onClick: () => {
setOpen(true);
},
})}
</>
);
};
export default ModalForm;
+4 -9
View File
@@ -1,10 +1,5 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
ReactDOM.createRoot(document.getElementById('root')!).render(
<App />
,
)
ReactDOM.createRoot(document.getElementById("root")!).render(<App />);
+2 -2
View File
@@ -12,7 +12,7 @@ const defaultTemplates = `{
// "resolver": "resolver-0",
// "hosts": "hosts-0",
"handler": {
"type": "http",
"type": "auto",
// "auth": {
// "username": "gost",
// "password": "gost"
@@ -32,7 +32,7 @@ const defaultTemplates = `{
// "password": "pass"
// },
// "auther": "auther-0",
"chain": "chain-0",
// "chain": "chain-0",
// "tls": {
// "certFile": "cert.pem",
// "keyFile": "key.pem",
+4
View File
@@ -12,6 +12,8 @@ import climiters from "./climiters";
import limiters from "./limiters";
import rlimiters from "./rlimiters";
import resolvers from "./resolvers";
import sds from "./sds";
import observers from "./observers";
import { Template } from "./type";
export type { Template };
@@ -33,6 +35,8 @@ export default {
ingresses: ingresses,
resolvers: resolvers,
routers: routers,
sds: sds,
observers: observers,
services: services,
} as Record<string, Template[]>;
+24
View File
@@ -0,0 +1,24 @@
import { getByName } from "./default";
import { getOtherAll } from "./otherOrigin";
import { Template } from "./type";
const def = getByName("observers");
export default [
def
? def
: {
label: "插件",
json: `
// https://gost.run/concepts/observer/
{
"name": "observer-0",
"plugin": {
"type": "grpc",
// "type": "http",
"addr": "127.0.0.1:8000",
"token": "gost",
// "tls": {}
}
}`,
},
] as Template[];
+23
View File
@@ -0,0 +1,23 @@
import { getByName } from "./default";
import { getOtherAll } from "./otherOrigin";
import { Template } from "./type";
const def = getByName("sds");
export default [
def
? def
: {
label: "HTTP插件",
json: `
// https://gost.run/concepts/sd/
{
"name": "sd-0",
"plugin": {
"type": "http",
"addr": "http://127.0.0.1:8000/sd",
// "token": "gost",
// "tls": {}
}
}`,
},
] as Template[];
+26 -3
View File
@@ -1,13 +1,26 @@
import { format, parse, applyEdits } from "jsonc-parser";
import {
format,
parse,
applyEdits,
ModificationOptions,
JSONPath,
modify,
EditResult,
} from "jsonc-parser";
const localCacheKeys = ['_id_', '_key_', '_type_'];
const localCacheKeys = ["_id_", "_key_", "_type_"];
type modify = {
path: JSONPath | string;
value: any;
options?: ModificationOptions;
};
export const jsonFormat = (json: any) => {
return JSON.stringify(json, null, 4);
};
export const jsonFormatValue = (json: any) => {
const newJson = {...json};
const newJson = { ...json };
for (const key of localCacheKeys) {
delete newJson[key];
}
@@ -31,6 +44,16 @@ export const jsonParse = (str: string) => {
return obj;
};
export const jsonEdit = (str: string, edits: modify[]) => {
const results:EditResult = [];
const run = modify.bind(null,str);
edits.forEach(({path,value,options},index)=>{
path = Array.isArray(path) ? path : path.split('.');
results.push(...run(path,value,options || {}))
})
return applyEdits(str, results);
};
export const download = (content: BlobPart, filename: string) => {
const href = URL.createObjectURL(new Blob([content]));
const link = document.createElement("a");
+66
View File
@@ -0,0 +1,66 @@
import React, { useState, useMemo, useRef } from 'react';
/**
* 一般用于input组件, 入参 value(受控) , defaultValue(不受控) 与组件内部 使用 value 的协调
* @param value (受控)
* @param defaultValue (不受控)
*
* 一般还需要结合 props.onChange 方法, 同步 value(受控)
* const [v,setV] = useBindValue<string>(props.value, props.defaultValue)
* const asyncValue = (value:string)=>{
* setV(value);
* props.onChange(value)
* }
*/
function useBindValue<T>(
value: T | undefined,
defaultValue: T | undefined
): [T | undefined, React.Dispatch<React.SetStateAction<T>>];
/**
*
* @param value (受控)
* @param defaultValue (不受控)
* @param defaultV 默认值
*/
function useBindValue<T>(
value: T | undefined,
defaultValue: T | undefined,
defaultV: T
): [T, React.Dispatch<React.SetStateAction<T>>];
/**
*
* @param value (受控)
* @param defaultValue (不受控)
* @param defaultV 默认值
* @returns 反回入理过的 value 与 setValue
*/
function useBindValue<T>(
value: T | undefined,
defaultValue: T | undefined,
defaultV?: T
): [T | undefined, React.Dispatch<React.SetStateAction<T>>] {
const inputValue = value !== undefined;
const inputDefaultValue = defaultValue !== undefined;
const [_value, _setValue] = useState<T | undefined>(inputValue ? value : inputDefaultValue ? defaultValue : defaultV);
const that = useRef({
value: _value,
setValue: (s: T | ((v: T) => T)) => {
let nextValue: T;
if (typeof s === 'function') {
nextValue = (s as (v: T) => T)(that.current.value as T);
} else {
nextValue = s;
}
that.current.value = nextValue;
_setValue(nextValue);
},
});
const status = useMemo<T | undefined>(() => {
const inputValue = value !== undefined;
const status = inputValue ? value : _value;
that.current.value = status;
return status;
}, [value, _value]);
return [status, that.current.setValue];
}
export { useBindValue };
+5 -5
View File
@@ -6,7 +6,9 @@ import { analyzer } from "vite-bundle-analyzer";
export default defineConfig(({ command, mode }) => {
const config: UserConfig = {
base: "./",
plugins: [react()],
plugins: [
react()
],
};
if (mode === "development") {
@@ -21,8 +23,6 @@ export default defineConfig(({ command, mode }) => {
manualChunks: {
monaco: ["monaco-editor"],
antd: ["antd"],
// 将组件库的代码打包
"ant-design": ["@ant-design/icons", "@ant-design/pro-components"],
},
},
},
@@ -30,8 +30,8 @@ export default defineConfig(({ command, mode }) => {
}
if (mode === "analyzer") {
config.plugins.unshift(analyzer() as any);
config.build.sourcemap="hidden";
config.plugins.push(analyzer());
config.build.sourcemap = "hidden";
}
return config;