소스 검색

feat: 事项表单组件配置

lanjianrong 3 년 전
부모
커밋
15080d47a8

+ 3 - 1
config/config.ts

@@ -22,7 +22,9 @@ export default defineConfig({
   access: {},
   dva: {},
   srcTranspiler: 'esbuild',
-  mfsu: { esbuild: true },
+  mfsu: {
+    esbuild: true
+  },
   fastRefresh: true,
   unocss: {
     watch: ['src/{pages,components}/**/*.{jsx,tsx}'] // 添加其他包含 unocss 的 classname 的文件目录

+ 4 - 4
package.json

@@ -56,9 +56,9 @@
     "rc-menu": "^9.6.3",
     "rc-tween-one": "^3.0.6",
     "rc-util": "^5.23.0",
-    "react": "18.1.0",
+    "react": "18.2.0",
     "react-copy-to-clipboard": "^5.1.0",
-    "react-dom": "18.1.0",
+    "react-dom": "18.2.0",
     "react-flow-renderer": "^9.7.4",
     "react-sortable-hoc": "^2.0.0"
   },
@@ -66,7 +66,7 @@
     "@types/lodash": "^4.14.144",
     "@types/react": "^18.0.0",
     "@types/react-dom": "^18.0.0",
-    "@unocss/cli": "^0.45.25",
+    "@unocss/cli": "^0.45.29",
     "babel-plugin-import": "^1.13.5",
     "compression-webpack-plugin": "^10.0.0",
     "cross-env": "^7.0.0",
@@ -76,7 +76,7 @@
     "prettier": "^2.6.1",
     "stylelint": "^14.9.0",
     "typescript": "^4.7.3",
-    "unocss": "^0.45.25"
+    "unocss": "^0.45.29"
   },
   "engines": {
     "node": ">=14.0.0"

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 736 - 672
pnpm-lock.yaml


+ 2 - 0
src/pages/Business/Condition/index.tsx

@@ -59,6 +59,8 @@ const ConditionSetting: React.FC<ConditionSettingProps> = ({ dispatch, condition
     }
     return schema
   }, [state.activeKey, conditionSchema])
+  console.log('currentSchema', currentSchema)
+
   const renderForm = () => {
     const normalForm = createForm({
       validateFirst: true,

+ 10 - 6
src/pages/Business/Matter/components/AddAssemblyModal.tsx

@@ -9,15 +9,15 @@ import Picture from './Picture'
 import './index.less'
 import { EmitterType } from '@/enums/emit'
 import useEventEmitter from '@/utils/emit'
+import classNames from 'classnames'
 
 type AddAssemblyProps = {
   defaultData: API.MatterTreeItem
   close: () => void
   refresh: () => void
-  refreshData: () => void
 }
 
-const AddAssemblyModal: React.FC<AddAssemblyProps> = ({ defaultData, close, refresh }) => {
+const AddAssembly: React.FC<AddAssemblyProps> = ({ defaultData, close, refresh }) => {
   const [modal, ModalDOM] = useModal()
   const event$ = useEventEmitter({ global: true })
   const [assemblyList, setAssemblyList] = useState(defaultData?.assembly || [])
@@ -61,7 +61,7 @@ const AddAssemblyModal: React.FC<AddAssemblyProps> = ({ defaultData, close, refr
       <Row gutter={16} className="p-[24px]">
         <Col span={8}>
           <Card
-            className={assemblyList.includes(Assembly.FORM) ? 'cardBox' : null}
+            className={classNames({ 'border border-hex-40a9ff': assemblyList.includes(Assembly.FORM) })}
             title={
               <span>
                 <ProfileFilled className="mr-1" style={{ fontSize: '20px', color: '#40A9FF' }} />
@@ -82,7 +82,9 @@ const AddAssemblyModal: React.FC<AddAssemblyProps> = ({ defaultData, close, refr
         </Col>
         <Col span={8}>
           <Card
-            className={assemblyList.includes(Assembly.COSTPROFILE) ? 'cardBox' : null}
+            className={classNames({
+              'border border-hex-40a9ff': assemblyList.includes(Assembly.COSTPROFILE)
+            })}
             title={
               <span>
                 <SlackSquareFilled className="mr-1" style={{ fontSize: '20px', color: '#52C41A' }} />
@@ -101,7 +103,9 @@ const AddAssemblyModal: React.FC<AddAssemblyProps> = ({ defaultData, close, refr
         </Col>
         <Col span={8}>
           <Card
-            className={assemblyList.includes(Assembly.DATAPROFILE) ? 'cardBox' : null}
+            className={classNames({
+              'border border-hex-40a9ff': assemblyList.includes(Assembly.DATAPROFILE)
+            })}
             title={
               <span>
                 <CodepenSquareFilled className="mr-1" style={{ fontSize: '20px', color: '#722ED1' }} />
@@ -132,4 +136,4 @@ const AddAssemblyModal: React.FC<AddAssemblyProps> = ({ defaultData, close, refr
   )
 }
 
-export default AddAssemblyModal
+export default AddAssembly

+ 46 - 40
src/pages/Business/Matter/components/AssemblyDetail.tsx

@@ -2,13 +2,15 @@ import ProTable from '@ant-design/pro-table'
 import React, { useEffect, useState } from 'react'
 import { Button, Tag } from 'antd'
 import { ColumnsType } from 'antd/lib/table'
-import styles from './index.less'
 import useModal from '@/components/Modal'
-import AddAssemblyModal from './AddAssemblyModal'
+import AddAssembly from './AddAssembly'
 import { queryMatterDetail } from '@/services/api/business'
 import consts from '@/utils/consts'
 import useEventEmitter from '@/utils/emit'
 import { EmitterType } from '@/enums/emit'
+import useDrawer from '@/components/Drawer'
+import FormDesignable from './FormDesignable'
+import { isString } from '@/utils/is'
 
 export enum Assembly {
   FORM = 'form', // 表单
@@ -17,30 +19,28 @@ export enum Assembly {
 }
 
 export const assemblyToMap = {
-  [Assembly.FORM]: { name: '表单', type: '通用型', title: '项目信息' },
-  [Assembly.COSTPROFILE]: { name: '送审资料', type: '唯一型', title: '送审资料' },
-  [Assembly.DATAPROFILE]: { name: '造价文件', type: '唯一型', title: '造价文件' }
+  [Assembly.FORM]: { name: '表单', type: '通用型' },
+  [Assembly.COSTPROFILE]: { name: '送审资料', type: '唯一型' },
+  [Assembly.DATAPROFILE]: { name: '造价文件', type: '唯一型' }
 }
 
 type AssemblyDetailProps = {
   record?: API.MatterTreeItem
   refresh?: () => void
 }
-interface IState {
-  matterDetail: API.MatterTreeItem
-}
+type IState = Pick<API.MatterItem, 'ID' | 'formSchema' | 'assembly'>
 
 const AssemblyDetail: React.FC<AssemblyDetailProps> = ({ refresh, assemblyID }) => {
-  const [state, setState] = useState<IState>({
-    matterDetail: {}
-  })
+  const [state, setState] = useState<IState>()
 
+  const [drawer, DrawerDom] = useDrawer()
   const [modal, ModalDOM] = useModal()
 
   const initData = async params => {
     const { data = {}, code = -1 } = await queryMatterDetail(params)
     if (code === consts.RET_CODE.SUCCESS) {
-      setState({ ...state, matterDetail: data })
+      const { ID, formSchema, assembly } = data as API.MatterItem
+      setState({ ID, formSchema, assembly })
     }
   }
 
@@ -50,10 +50,12 @@ const AssemblyDetail: React.FC<AssemblyDetailProps> = ({ refresh, assemblyID })
   // }
 
   const event$ = useEventEmitter({ global: true })
-  event$.useSubscription(EmitterType.BUSINESS_MATTER_REFRESH, ({ params }) => {
-    const { ID } = params[0]
+  event$.useSubscription(EmitterType.BUSINESS_MATTER_REFRESH, () => {
+    console.log('11')
+
+    // const { ID } = params[0]
 
-    initData({ ID })
+    // initData({ ID })
   })
 
   useEffect(() => {
@@ -68,19 +70,25 @@ const AssemblyDetail: React.FC<AssemblyDetailProps> = ({ refresh, assemblyID })
       okText: '确认',
       cancelText: '取消',
       width: 1000,
-      wrapClassName: 'modalTableBox',
-      children: (
-        <AddAssemblyModal
-          defaultData={state.matterDetail}
-          // refreshData={refreshData}
-          refresh={refresh}
-          close={() => modal.close()}
-        />
-      ),
+      bodyStyle: { padding: 'unset' },
+      children: <AddAssembly defaultData={state} refresh={refresh} close={() => modal.close()} />,
       footer: null
     })
   }
 
+  const openDesignable = (type: Assembly) => {
+    const templateSchema =
+      (state?.formSchema && isString(state.formSchema) && JSON.parse(state.formSchema)) || {}
+
+    drawer.open({
+      title: `(${assemblyToMap[type].name})组件配置`,
+      closeIcon: null,
+      bodyStyle: { padding: 'unset' },
+      destroyOnClose: true,
+      children: <FormDesignable dataID={assemblyID} templateSchema={templateSchema} />
+    })
+  }
+
   const columns: ColumnsType<API.AssemblyItem> = [
     {
       title: '组件名称',
@@ -95,43 +103,41 @@ const AssemblyDetail: React.FC<AssemblyDetailProps> = ({ refresh, assemblyID })
       renderText: (_, record) => <Tag color="blue">{assemblyToMap[record.type].type}</Tag>
     },
     {
-      title: '组件标题',
-      dataIndex: 'title',
-      onHeaderCell: () => ({ style: { textAlign: 'center' } }),
-      renderText: (_, record) => <span>{assemblyToMap[record.type].title}</span>
-    },
-    {
       title: '操作',
       dataIndex: 'operate',
       align: 'center',
-      renderText: () => (
+      renderText: (_, record) => (
         <div className="divide-x divide-bg-gray-400 flex flex-row items-center">
-          <span className="px-2 text-primary cursor-pointer hover:text-hex-967bbd">组件配置</span>
+          <span
+            className="px-2 text-primary cursor-pointer hover:text-hex-967bbd"
+            onClick={() => openDesignable(record.type)}>
+            组件配置
+          </span>
         </div>
       )
     }
   ]
 
-  // drawerTableBox
   return (
-    <div className={[styles.assemblyContent, 'mt-24px'].join(' ')}>
-      {state.matterDetail ? (
+    <div className="mt-24px">
+      {state?.assembly ? (
         <ProTable
+          cardProps={{ bodyStyle: { padding: 'unset' } }}
           search={false}
           rowKey="type"
           columns={columns}
-          dataSource={state.matterDetail.assembly?.map(item => ({ type: item }))}
-          className="absolute"
+          dataSource={state.assembly?.map(item => ({ type: item }))}
           bordered
           size="small"
           pagination={false}
           toolBarRender={false}
         />
       ) : null}
-      <Button className="buttonBox" onClick={() => openModal()}>
-        添加组件
-      </Button>
+      <div className="absolute right-4 top-4">
+        <Button onClick={() => openModal()}>添加组件</Button>
+      </div>
       {ModalDOM}
+      {DrawerDom}
     </div>
   )
 }

+ 5 - 0
src/pages/Business/Matter/components/FormDesignable/index.less

@@ -0,0 +1,5 @@
+.designableRoot {
+  :global(.dn-main-panel) {
+    position: static !important;
+  }
+}

+ 136 - 0
src/pages/Business/Matter/components/FormDesignable/index.tsx

@@ -0,0 +1,136 @@
+import type { FC } from 'react'
+import { useMemo } from 'react'
+import {
+  Designer, // 设计器根组件,主要用于下发上下文
+  Workspace, // 工作区组件,核心组件,用于管理工作区内的拖拽行为,树节点数据等等...
+  ResourceWidget, // 拖拽源挂件
+  StudioPanel, // 主布局面板
+  WorkspacePanel, // 工作区布局面板
+  ViewportPanel, // 视口布局面板
+  ViewPanel, // 视图布局面板
+  SettingsPanel, // 右侧配置表单布局面板
+  ComponentTreeWidget, // 组件树渲染器
+  CompositePanel, // 左侧组合布局面板
+  ToolbarPanel, // 工具栏布局面板
+  ViewToolsWidget // 视图切换工具挂件
+} from '@designable/react'
+import { SettingsForm } from '@designable/react-settings-form'
+import { createDesigner, GlobalRegistry, Shortcut, KeyCode } from '@designable/core'
+import {
+  Form,
+  Field,
+  FormGrid,
+  FormLayout,
+  Input,
+  Select,
+  NumberPicker,
+  Card
+} from '@/pages/Schema/Budget/components/Designable/antd'
+import { transformToSchema, transformToTreeNode } from '@designable/formily-transformer'
+import styles from './index.less'
+import { Button, message } from 'antd'
+import { updateMatterFormSchema } from '@/services/api/business'
+import consts from '@/utils/consts'
+import { PreviewWidget, SchemaEditorWidget } from '@/pages/Schema/Base/components/Designable/widgets'
+GlobalRegistry.registerDesignerLocales({
+  'zh-CN': {
+    sources: {
+      Inputs: '输入控件',
+      Layouts: '布局组件'
+    }
+  }
+})
+GlobalRegistry.setDesignerLanguage('zh-cn')
+
+type FormDesignableProps = {
+  templateSchema: Schema
+  dataID: string
+}
+const FormDesignable: FC<FormDesignableProps> = ({ templateSchema, dataID }) => {
+  const engine = useMemo(
+    () =>
+      createDesigner({
+        shortcuts: [
+          new Shortcut({
+            codes: [
+              [KeyCode.Meta, KeyCode.S],
+              [KeyCode.Control, KeyCode.S]
+            ]
+          })
+        ],
+        rootComponentName: 'Form',
+        defaultComponentTree: transformToTreeNode({
+          form: {
+            labelCol: 6,
+            wrapperCol: 12,
+            fullness: true,
+            inset: false
+          },
+          schema: templateSchema
+        })
+      }),
+    []
+  )
+
+  const handleSave = async () => {
+    const formSchema = JSON.stringify(transformToSchema(engine.getCurrentTree()).schema)
+    const { code = -1 } = await updateMatterFormSchema({ ID: dataID, formSchema })
+    if (code === consts.RET_CODE.SUCCESS) {
+      message.success('保存成功')
+    }
+  }
+  return (
+    <div className={styles.designableRoot}>
+      <div className="absolute right-24px top-16px z-10">
+        <Button onClick={handleSave} type="primary">
+          保存
+        </Button>
+      </div>
+      <Designer engine={engine} position="relative">
+        <StudioPanel>
+          <CompositePanel>
+            <CompositePanel.Item title="panels.Component" icon="Component">
+              <ResourceWidget title="sources.Layouts" sources={[FormGrid, FormLayout, Card]} />
+              <ResourceWidget title="sources.Inputs" sources={[Input, Select, NumberPicker]} />
+            </CompositePanel.Item>
+          </CompositePanel>
+
+          <Workspace id="form">
+            <WorkspacePanel>
+              <ToolbarPanel>
+                <ViewToolsWidget use={['DESIGNABLE', 'JSONTREE', 'PREVIEW']} />
+              </ToolbarPanel>
+              <ViewportPanel>
+                <ViewPanel type="DESIGNABLE">
+                  {() => (
+                    <ComponentTreeWidget
+                      components={{
+                        Form,
+                        Field,
+                        FormGrid,
+                        FormLayout,
+                        Input,
+                        Select,
+                        NumberPicker,
+                        Card
+                      }}
+                    />
+                  )}
+                </ViewPanel>
+                <ViewPanel type="JSONTREE" scrollable={false}>
+                  {(tree, onChange) => <SchemaEditorWidget tree={tree} onChange={onChange} />}
+                </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>
+    </div>
+  )
+}
+
+export default FormDesignable

+ 0 - 13
src/pages/Business/Matter/components/index.less

@@ -1,16 +1,3 @@
-.assemblyContent {
-  :global(.ant-pro-table) {
-    width: calc(100% - 48px);
-  }
-  :global(.buttonBox) {
-    position: absolute;
-    top: 17px;
-    right: 15px;
-  }
-  :global(.ant-pro-card-body) {
-    padding: 0;
-  }
-}
 .modalTableBox {
   .ant-modal-body {
     padding: 0 !important;

+ 1 - 1
src/pages/Business/Step/components/Flow/components/Drawer/Auditor/MatterPermission.tsx

@@ -34,7 +34,7 @@ const MatterPermission: React.FC<MatterPermissionProps> = ({ defaultValue, toggl
         <Card
           key={matter.matterID}
           size="small"
-          className="mb-2"
+          className="mb-2!"
           title={matterFlattenList.find(item => item.ID === matter.matterID)?.name}>
           {[MatterPerm.COST, MatterPerm.FORM, MatterPerm.PROFILE].map(item => (
             <div key={item} className="flex justify-between items-center my-1">

+ 8 - 0
src/services/api/business.ts

@@ -185,3 +185,11 @@ export async function updateConditionSchema(params: { gatherID?: string; schema:
     data: params
   })
 }
+
+/** 保存事项表单 */
+export async function updateMatterFormSchema(params: { ID: string; formSchema: Schema }) {
+  return request('/matter/save/schema', {
+    method: 'POST',
+    data: params
+  })
+}

+ 1 - 0
src/services/api/typings.d.ts

@@ -492,6 +492,7 @@ declare namespace API {
     assembly?: any[]
     id?: string
     children?: MatterTreeItem[]
+    formSchema?: unknown
   }
 
   type MatterItem = Omit<MatterTreeItem, 'children'>