lanjianrong 3 rokov pred
rodič
commit
fb694767a7

+ 6 - 0
config/routes.ts

@@ -104,6 +104,12 @@
         component: './Schema/Base/index.tsx'
       },
       {
+        path: 'test',
+        name: 'test',
+        layout: false,
+        component: './Schema/Designable/index.tsx'
+      },
+      {
         path: '/work-setting/schema/detail',
         name: 'schema-detail',
         hideInMenu: true,

+ 9 - 5
package.json

@@ -53,6 +53,13 @@
     "@ant-design/pro-form": "^1.18.3",
     "@ant-design/pro-layout": "6.32.2",
     "@ant-design/pro-table": "^2.30.8",
+    "@designable/core": "^1.0.0-beta.43",
+    "@designable/formily-transformer": "^1.0.0-beta.43",
+    "@designable/react": "^1.0.0-beta.43",
+    "@designable/react-settings-form": "^1.0.0-beta.43",
+    "@formily/antd": "^2.0.8",
+    "@formily/core": "^2.0.8",
+    "@formily/react": "^2.0.8",
     "@umijs/route-utils": "^1.0.36",
     "ahooks": "^3.0.0",
     "antd": "^4.18.2",
@@ -61,7 +68,7 @@
     "form-render": "1.7.3",
     "fr-generator": "^2.7.3",
     "lodash": "^4.17.11",
-    "moment": "^2.25.3",
+    "moment": "^2.29.1",
     "omit.js": "^2.0.2",
     "rc-menu": "^9.0.13",
     "rc-util": "^5.14.0",
@@ -83,7 +90,6 @@
     "@types/react-dom": "^17.0.0",
     "@types/react-helmet": "^6.1.0",
     "@umijs/fabric": "^2.6.2",
-    "@umijs/openapi": "^1.1.14",
     "@umijs/plugin-blocks": "^2.0.5",
     "@umijs/plugin-esbuild": "^1.0.1",
     "@umijs/plugin-openapi": "^1.2.0",
@@ -93,7 +99,7 @@
     "@umijs/yorkie": "^2.0.3",
     "browserslist": "^4.18.1",
     "carlo": "^0.9.46",
-    "cross-env": "^7.0.0",
+    "cross-env": "^7.0.3",
     "cross-port-killer": "^1.1.1",
     "detect-installer": "^1.0.1",
     "enzyme": "^3.11.0",
@@ -105,11 +111,9 @@
     "gh-pages": "^3.0.0",
     "jsdom-global": "^3.0.2",
     "lint-staged": "^10.0.0",
-    "mockjs": "^1.0.1-beta3",
     "prettier": "^2.3.2",
     "puppeteer-core": "^8.0.0",
     "stylelint": "^13.0.0",
-    "swagger-ui-react": "^3.52.3",
     "typescript": "^4.2.2",
     "windicss": "^3.2.0",
     "windicss-webpack-plugin": "^1.5.5"

+ 1 - 0
src/locales/zh-CN/menu.ts

@@ -26,6 +26,7 @@ export default {
   'menu.institutions.company.detail': '单位详情',
   'menu.work-setting': '业务设置',
   'menu.work-setting.schema': '基础数据设置',
+  'menu.work-setting.test': '测试',
   'menu.work-setting.schema-detail': '编辑数据模型',
   'menu.system': '系统管理',
   'menu.system.setting': '系统设置',

+ 142 - 0
src/pages/Schema/Designable/index.tsx

@@ -0,0 +1,142 @@
+import 'antd/dist/antd.less'
+import { useMemo } from 'react'
+import {
+  Designer, // 设计器根组件,主要用于下发上下文
+  DesignerToolsWidget, // 画板工具挂件
+  ViewToolsWidget, // 视图切换工具挂件
+  Workspace, // 工作区组件,核心组件,用于管理工作区内的拖拽行为,树节点数据等等...
+  OutlineTreeWidget, // 大纲树组件,它会自动识别当前工作区,展示出工作区内树节点
+  ResourceWidget, // 拖拽源挂件
+  HistoryWidget, // 历史记录挂件
+  StudioPanel, // 主布局面板
+  CompositePanel, // 左侧组合布局面板
+  WorkspacePanel, // 工作区布局面板
+  ToolbarPanel, // 工具栏布局面板
+  ViewportPanel, // 视口布局面板
+  ViewPanel, // 视图布局面板
+  SettingsPanel, // 右侧配置表单布局面板
+  ComponentTreeWidget // 组件树渲染器
+} from '@designable/react'
+import { SettingsForm } from '@designable/react-settings-form'
+import { createDesigner, GlobalRegistry, Shortcut, KeyCode } from '@designable/core'
+import { ActionsWidget, PreviewWidget, SchemaEditorWidget, MarkupSchemaWidget } from './widgets'
+import { saveSchema } from './service'
+import {
+  Form,
+  Input,
+  Select,
+  TreeSelect,
+  Cascader,
+  Radio,
+  Checkbox,
+  Transfer,
+  DatePicker,
+  TimePicker,
+  Upload,
+  Switch
+} from 'antd'
+
+GlobalRegistry.registerDesignerLocales({
+  'zh-CN': {
+    sources: {
+      Inputs: '输入控件',
+      Layouts: '布局组件',
+      Arrays: '自增组件',
+      Displays: '展示组件'
+    }
+  }
+})
+
+const Designable = () => {
+  const engine = useMemo(
+    () =>
+      createDesigner({
+        shortcuts: [
+          new Shortcut({
+            codes: [
+              [KeyCode.Meta, KeyCode.S],
+              [KeyCode.Control, KeyCode.S]
+            ],
+            handler(ctx) {
+              saveSchema(ctx.engine)
+            }
+          })
+        ],
+        rootComponentName: 'Form'
+      }),
+    []
+  )
+  return (
+    <Designer engine={engine}>
+      <StudioPanel actions={<ActionsWidget />}>
+        <CompositePanel>
+          <CompositePanel.Item title="panels.Component" icon="Component">
+            <ResourceWidget
+              title="sources.Inputs"
+              sources={[
+                Input,
+                Select,
+                TreeSelect,
+                Cascader,
+                Transfer,
+                Checkbox,
+                Radio,
+                DatePicker,
+                TimePicker,
+                Upload,
+                Switch
+              ]}
+            />
+          </CompositePanel.Item>
+          <CompositePanel.Item title="panels.OutlinedTree" icon="Outline">
+            <OutlineTreeWidget />
+          </CompositePanel.Item>
+          <CompositePanel.Item title="panels.History" icon="History">
+            <HistoryWidget />
+          </CompositePanel.Item>
+        </CompositePanel>
+        <Workspace id="form">
+          <WorkspacePanel>
+            <ToolbarPanel>
+              <DesignerToolsWidget />
+              <ViewToolsWidget use={['DESIGNABLE', 'JSONTREE', 'MARKUP', 'PREVIEW']} />
+            </ToolbarPanel>
+            <ViewportPanel>
+              <ViewPanel type="DESIGNABLE">
+                {() => (
+                  <ComponentTreeWidget
+                    components={{
+                      Form,
+                      Input,
+                      Select,
+                      TreeSelect,
+                      Cascader,
+                      Radio,
+                      Checkbox,
+                      DatePicker,
+                      TimePicker,
+                      Upload,
+                      Switch
+                    }}
+                  />
+                )}
+              </ViewPanel>
+              <ViewPanel type="JSONTREE" scrollable={false}>
+                {(tree, onChange) => <SchemaEditorWidget tree={tree} onChange={onChange} />}
+              </ViewPanel>
+              <ViewPanel type="MARKUP" scrollable={false}>
+                {tree => <MarkupSchemaWidget tree={tree} />}
+              </ViewPanel>
+              <ViewPanel type="PREVIEW">{tree => <PreviewWidget tree={tree} />}</ViewPanel>
+            </ViewportPanel>
+          </WorkspacePanel>
+        </Workspace>
+        <SettingsPanel title="panels.PropertySettings">
+          <SettingsForm uploadAction="https://www.mocky.io/v2/5cc8019d300000980a055e76" />
+        </SettingsPanel>
+      </StudioPanel>
+    </Designer>
+  )
+}
+
+export default Designable

+ 1 - 0
src/pages/Schema/Designable/service/index.ts

@@ -0,0 +1 @@
+export * from './schema'

+ 17 - 0
src/pages/Schema/Designable/service/schema.ts

@@ -0,0 +1,17 @@
+import { Engine } from '@designable/core'
+import { transformToSchema, transformToTreeNode } from '@designable/formily-transformer'
+import { message } from 'antd'
+
+export const saveSchema = (designer: Engine) => {
+  localStorage.setItem(
+    'formily-schema',
+    JSON.stringify(transformToSchema(designer.getCurrentTree()))
+  )
+  message.success('Save Success')
+}
+
+export const loadInitialSchema = (designer: Engine) => {
+  try {
+    designer.setCurrentTree(transformToTreeNode(JSON.parse(localStorage.getItem('formily-schema'))))
+  } catch {}
+}

+ 34 - 0
src/pages/Schema/Designable/widgets/ActionsWidget.tsx

@@ -0,0 +1,34 @@
+import React, { useEffect } from 'react'
+import { Space, Button } from 'antd'
+import { useDesigner, TextWidget } from '@designable/react'
+import { GlobalRegistry } from '@designable/core'
+import { observer } from '@formily/react'
+import { loadInitialSchema, saveSchema } from '../service'
+
+export const ActionsWidget = observer(() => {
+  const designer = useDesigner()
+  useEffect(() => {
+    loadInitialSchema(designer)
+    GlobalRegistry.setDesignerLanguage('zh-cn')
+  }, [])
+
+  return (
+    <Space style={{ marginRight: 10 }}>
+      <Button
+        onClick={() => {
+          saveSchema(designer)
+        }}
+      >
+        <TextWidget>Save</TextWidget>
+      </Button>
+      <Button
+        type="primary"
+        onClick={() => {
+          saveSchema(designer)
+        }}
+      >
+        <TextWidget>Publish</TextWidget>
+      </Button>
+    </Space>
+  )
+})

+ 16 - 0
src/pages/Schema/Designable/widgets/LogoWidget.tsx

@@ -0,0 +1,16 @@
+import React from 'react'
+import { useTheme } from '@designable/react'
+
+const logo = {
+  dark: '//img.alicdn.com/imgextra/i2/O1CN01NTUDi81fHLQvZCPnc_!!6000000003981-55-tps-1141-150.svg',
+  light: '//img.alicdn.com/imgextra/i2/O1CN01Kq3OHU1fph6LGqjIz_!!6000000004056-55-tps-1141-150.svg'
+}
+
+export const LogoWidget: React.FC = () => {
+  const url = logo[useTheme()]
+  return (
+    <div style={{ display: 'flex', alignItems: 'center', fontSize: 14 }}>
+      <img src={url} style={{ margin: '12px 8px', height: 18, width: 'auto' }} />
+    </div>
+  )
+}

+ 161 - 0
src/pages/Schema/Designable/widgets/MarkupSchemaWidget.tsx

@@ -0,0 +1,161 @@
+import React from 'react'
+import type { TreeNode } from '@designable/core'
+import { MonacoInput } from '@designable/react-settings-form'
+import { isEmpty, isPlainObj } from '@formily/shared'
+
+export interface IMarkupSchemaWidgetProps {
+  tree: TreeNode
+}
+
+const transformToMarkupSchemaCode = (tree: TreeNode) => {
+  const printAttribute = (node: TreeNode) => {
+    if (!node) return ''
+    const props = { ...node.props }
+    if (node.depth !== 0) {
+      props.name = node.props.name || node.id
+    }
+    return `${Object.keys(props)
+      .map(key => {
+        if (
+          key === 'x-designable-id' ||
+          key === 'x-designable-source-name' ||
+          key === '_isJSONSchemaObject' ||
+          key === 'version' ||
+          key === 'type'
+        )
+          return ''
+        const value = props[key]
+        if (isPlainObj(value) && isEmpty(value)) return ''
+        if (typeof value === 'string') return `${key}="${value}"`
+        return `${key}={${JSON.stringify(value)}}`
+      })
+      .join(' ')}`
+  }
+  const printChildren = (node: TreeNode) => {
+    if (!node) return ''
+    return node.children
+      .map(child => {
+        // eslint-disable-next-line @typescript-eslint/no-use-before-define
+        return printNode(child)
+      })
+      .join('')
+  }
+  const printTag = (node: TreeNode) => {
+    if (node.props.type === 'string') return 'SchemaField.String'
+    if (node.props.type === 'number') return 'SchemaField.Number'
+    if (node.props.type === 'boolean') return 'SchemaField.Boolean'
+    if (node.props.type === 'date') return 'SchemaField.Date'
+    if (node.props.type === 'datetime') return 'SchemaField.DateTime'
+    if (node.props.type === 'array') return 'SchemaField.Array'
+    if (node.props.type === 'object') return 'SchemaField.Object'
+    if (node.props.type === 'void') return 'SchemaField.Void'
+    return 'SchemaField.Markup'
+  }
+  const printNode = (node: TreeNode) => {
+    if (!node) return ''
+    return `<${printTag(node)} ${printAttribute(node)} ${
+      node.children.length ? `>${printChildren(node)}</${printTag(node)}>` : '/>'
+    }`
+  }
+  const root = tree.find(child => {
+    return child.componentName === 'Form' || child.componentName === 'Root'
+  })
+  return `import React, { useMemo } from 'react'
+import { createForm } from '@formily/core'
+import { createSchemaField } from '@formily/react'
+import {
+  Form,
+  FormItem,
+  DatePicker,
+  Checkbox,
+  Cascader,
+  Editable,
+  Input,
+  NumberPicker,
+  Switch,
+  Password,
+  PreviewText,
+  Radio,
+  Reset,
+  Select,
+  Space,
+  Submit,
+  TimePicker,
+  Transfer,
+  TreeSelect,
+  Upload,
+  FormGrid,
+  FormLayout,
+  FormTab,
+  FormCollapse,
+  ArrayTable,
+  ArrayCards,
+} from '@formily/antd'
+import { Card, Slider, Rate } from 'antd'
+
+const Text: React.FC<{
+  value?: string
+  content?: string
+  mode?: 'normal' | 'h1' | 'h2' | 'h3' | 'p'
+}> = ({ value, mode, content, ...props }) => {
+  const tagName = mode === 'normal' || !mode ? 'div' : mode
+  return React.createElement(tagName, props, value || content)
+}
+
+const SchemaField = createSchemaField({
+  components: {
+    Space,
+    FormGrid,
+    FormLayout,
+    FormTab,
+    FormCollapse,
+    ArrayTable,
+    ArrayCards,
+    FormItem,
+    DatePicker,
+    Checkbox,
+    Cascader,
+    Editable,
+    Input,
+    Text,
+    NumberPicker,
+    Switch,
+    Password,
+    PreviewText,
+    Radio,
+    Reset,
+    Select,
+    Submit,
+    TimePicker,
+    Transfer,
+    TreeSelect,
+    Upload,
+    Card,
+    Slider,
+    Rate,
+  },
+})
+
+export default ()=>{
+  const form = useMemo(() => createForm(), [])
+
+  return <Form form={form} ${printAttribute(root)}>
+    <SchemaField>
+      ${printChildren(root)}
+    </SchemaField>
+  </Form>
+}
+
+`
+}
+
+export const MarkupSchemaWidget: React.FC<IMarkupSchemaWidgetProps> = props => {
+  return (
+    <MonacoInput
+      {...props}
+      options={{ readOnly: true }}
+      value={transformToMarkupSchemaCode(props.tree)}
+      language="typescript"
+    />
+  )
+}

+ 91 - 0
src/pages/Schema/Designable/widgets/PreviewWidget.tsx

@@ -0,0 +1,91 @@
+import React, { useMemo } from 'react'
+import { createForm } from '@formily/core'
+import { createSchemaField } from '@formily/react'
+import {
+  Form,
+  FormItem,
+  DatePicker,
+  Checkbox,
+  Cascader,
+  Editable,
+  Input,
+  NumberPicker,
+  Switch,
+  Password,
+  PreviewText,
+  Radio,
+  Reset,
+  Select,
+  Space,
+  Submit,
+  TimePicker,
+  Transfer,
+  TreeSelect,
+  Upload,
+  FormGrid,
+  FormLayout,
+  FormTab,
+  FormCollapse,
+  ArrayTable,
+  ArrayCards
+} from '@formily/antd'
+import { Card, Slider, Rate } from 'antd'
+import { TreeNode } from '@designable/core'
+import { transformToSchema } from '@designable/formily-transformer'
+
+const Text: React.FC<{
+  value?: string
+  content?: string
+  mode?: 'normal' | 'h1' | 'h2' | 'h3' | 'p'
+}> = ({ value, mode, content, ...props }) => {
+  const tagName = mode === 'normal' || !mode ? 'div' : mode
+  return React.createElement(tagName, props, value || content)
+}
+
+const SchemaField = createSchemaField({
+  components: {
+    Space,
+    FormGrid,
+    FormLayout,
+    FormTab,
+    FormCollapse,
+    ArrayTable,
+    ArrayCards,
+    FormItem,
+    DatePicker,
+    Checkbox,
+    Cascader,
+    Editable,
+    Input,
+    Text,
+    NumberPicker,
+    Switch,
+    Password,
+    PreviewText,
+    Radio,
+    Reset,
+    Select,
+    Submit,
+    TimePicker,
+    Transfer,
+    TreeSelect,
+    Upload,
+    Card,
+    Slider,
+    Rate
+  }
+})
+
+export interface IPreviewWidgetProps {
+  tree: TreeNode
+}
+
+export const PreviewWidget: React.FC<IPreviewWidgetProps> = props => {
+  const form = useMemo(() => createForm(), [])
+  const { form: formProps, schema } = transformToSchema(props.tree)
+  return (
+    <Form {...formProps} form={form}>
+      <SchemaField schema={schema} />
+    </Form>
+  )
+}

+ 22 - 0
src/pages/Schema/Designable/widgets/SchemaEditorWidget.tsx

@@ -0,0 +1,22 @@
+import React from 'react'
+import { transformToSchema, transformToTreeNode } from '@designable/formily-transformer'
+import type { TreeNode, ITreeNode } from '@designable/core'
+import { MonacoInput } from '@designable/react-settings-form'
+
+export interface ISchemaEditorWidgetProps {
+  tree: TreeNode
+  onChange?: (tree: ITreeNode) => void
+}
+
+export const SchemaEditorWidget: React.FC<ISchemaEditorWidgetProps> = props => {
+  return (
+    <MonacoInput
+      {...props}
+      value={JSON.stringify(transformToSchema(props.tree), null, 2)}
+      onChange={value => {
+        props.onChange?.(transformToTreeNode(JSON.parse(value)))
+      }}
+      language="json"
+    />
+  )
+}

+ 5 - 0
src/pages/Schema/Designable/widgets/index.ts

@@ -0,0 +1,5 @@
+export * from './LogoWidget'
+export * from './ActionsWidget'
+export * from './PreviewWidget'
+export * from './SchemaEditorWidget'
+export * from './MarkupSchemaWidget'