后台更新的内容在网站上不显示,wordpress主题结构,网站开发测量像素工具,wordpress 导出word问卷编辑器
Date: February 20, 2025 4:17 PM (GMT8) 目标
完成问卷编辑器的设计和开发完成复杂系统的 UI 组件拆分完成复杂系统的数据结构设计 内容
需求分析技术方案设计开发
注意事项#xff1a;
需求指导设计#xff0c;设计指导开发。前两步很重要页面复杂的话8) 目标
完成问卷编辑器的设计和开发完成复杂系统的 UI 组件拆分完成复杂系统的数据结构设计 内容
需求分析技术方案设计开发
注意事项
需求指导设计设计指导开发。前两步很重要页面复杂的话可以设计边开发 画布 UI 分析
画布UI组成 画布基础构建
画布Y轴滚动
要点
flex 布局居中对齐画布 Y 向滚动
效果 question/Edit/index.tsx
import React, { FC } from react
import styles from ./index.module.scss
// import { useParams } from react-router-dom
// import useLoadQuestionData from ../../../hooks/useLoadQuestionDataconst Edit: FC () {// const { id } useParams()// const { loading, data } useLoadQuestionData()return (div className{styles.container}div style{{ backgroundColor: #fff, height: 40px }}Header/divdiv className{styles[content-wrapper]}div className{styles.content}div className{styles.left}Left/divdiv className{styles.main}div className{styles[canvas-wrapper]}div style{{ height: 900px }}画布滚动测试/div/div/divdiv className{styles.right}Right/div/div/div/div)
}export default Edit
question/Edit/index.module.scss
.container {display: flex;flex-direction: column;height: 100vh;background-color: #f0f2f5;
}.content-wrapper {flex: auto;padding: 12px 0;
}.content {display: flex;margin: 0 24px;height: 100%;.left {width: 285px;background-color: #fff;padding: 0 12px;}.main {flex: 1;position: relative;overflow: hidden;.canvas-wrapper {position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%);width: 400px;height: 712px;background-color: #fff; overflow: auto;box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15);}}.right {width: 300px;background-color: #fff;padding: 0 12px;}
} 开发问卷组件Title 和 Input
开发组件 Title
目标 要点
组件默认参数设置{ ...QuestionInputDefaultProps, ...props }
Component.ts
import React, { FC } from react
import { Typography, Input } from antd
import { QuestionTitleProps, QuestionInputDefaultProps } from ./interfaceconst { Paragraph } Typography
const QuestionTitle: FCQuestionTitleProps (props: QuestionTitleProps) {const { title , placeholder } {...QuestionInputDefaultProps,...props,}return (divParagraph strong{title}/ParagraphdivInput placeholder{placeholder}/Input/div/div)
}
export default QuestionTitle
interface.ts
export type QuestionTitleProps {title?: stringplaceholder?: string
}export const QuestionInputDefaultProps: QuestionTitleProps {title: 输入框标题,placeholder: 请输入内容,
} 开发组件 Input
目标 要点
Input标题样式函数设计 genFontSize
Component.ts
import React, { FC } from react
import { Typography } from antd
import { QuestionTitleProps, defaultQuestionTitleProps } from ./interfaceconst { Title } Typography
const QuestionTitle: FCQuestionTitleProps (props: QuestionTitleProps) {const {text ,level 1,isCenter false,} { ...defaultQuestionTitleProps, ...props }const genFontSize (level: number) {if (level 1) return 24pxif (level 2) return 20pxif (level 3) return 16pxif (level 4) return 14pxif (level 5) return 12pxreturn 24px}return (Titlelevel{level}style{{textAlign: isCenter ? center : left,marginBottom: 0px,fontSize: genFontSize(level),}}{text}/Title)
}export default QuestionTitle
interface.ts
export type QuestionTitleProps {text?: stringlevel?: 1 | 2 | 3 | 4 | 5isCenter?: boolean
}export const defaultQuestionTitleProps: QuestionTitleProps {text: 一行标题,level: 1,isCenter: false,
}画布集成组件测试
目标 要点
画布元素禁止点击样式设计 .componet标题样式上方多余空距问题处理见下方
EditCanvas.tsx
import React, { FC } from react
import styles from ./EditCanvas.module.scss
import QuestionTitle from ../../../component/QuestionComponents/QuestionTitle/Component
import QuestionInput from ../../../component/QuestionComponents/QuestionInput/Componentconst EditCanvas: FC () {return (div className{styles.canvas}div className{styles[component-wrapper]}div className{styles.component}QuestionTitle //div/divdiv className{styles[component-wrapper]}div className{styles.component}QuestionInput //div/div/div)
}export default EditCanvasEidtCanvas.module.scss
.canvas {min-height: 100%;background-color: #fff;overflow: hidden;
}.component-wrapper {margin: 12px;border: 1px solid #fff;padding: 12px;border-radius: 3px;// 新增修复代码推荐方案:global(.ant-typography) {margin-block-start: 0 !important;margin-block-end: 0 !important;}:hover {border: 1px solid #d9d9d9;}
}.componet {pointer-events: none; // 禁止点击
}问卷数据获取与存储
问卷信息存储在 Redux 中的原因
组件间需要不断联动如下所示段落的选中以及修改都涉及到相同的数据的访问。因此建议把问卷信息存储在 Redux 中便于组件间共享使用。 组件数据结构设计
服务端mock数据 {url: /api/question/:id,method: get,response() {return {errno: 0,data: {id: Random.id(),title: Random.ctitle(),componentList: [{id: Random.id(),type: questionTitle, // 组件类型不能重复前后端统一好title: 这是一个文本组件,props: {text: 文本内容,level1,isCenter: false}},{id: Random.id(),type: questionInput,title: 这是一个输入框组件,props: {title: 你的名字,placeholder: 请输入内容}},{id: Random.id(),type: questionInput,title: 这是一个输入框组件,props: {title: 你的电话,placeholder: 请输入内容}}],}}}}, Ajax 加载数据
要点
Hook useLoadQuestionData 设计 问卷信息获取函数id 变化更新问卷信息数据更新后存储在 Redux 中
useLoadQuestionData.ts
import { useEffect } from react
import { useParams } from react-router-dom
import { useDispatch } from react-redux
import { getQuestionService } from ../services/question
import { useRequest } from ahooks
import { resetComponentList } from ../store/componentReducerfunction useLoadQuestionData() {const { id } useParams()const dispatch useDispatch()// 问卷信息获取函数const { data, loading, error, run } useRequest(async (id: string) {const data await getQuestionService(id)return data},{manual: true,})// 数据更新后存储在 Redux 中useEffect(() {if (!data) returnconst { title , componentList } dataif (!componentList || componentList.length 0) returnconst action resetComponentList({ componentList })dispatch(action)}, [data])// id 变化更新问卷信息useEffect(() {run(id)}, [id])return {loading,error,}
}export default useLoadQuestionData Redux 数据存储
要点
设计 componentReducer定义类型以及切片设计 index作为各个切片 类型 和 reducer 的统一收口
文件树
│ ├── store
│ │ ├── componentReducer
│ │ │ └── index.ts
│ │ ├── index.ts
│ │ └── userReducer.tscomponentReducer/index.ts
import { createSlice, PayloadAction } from reduxjs/toolkit
import { ComponentPropsType } from ../../component/QuestionComponentsexport type ComponentInfoType {fe_id: stringtype: stringtitle: stringprops: ComponentPropsType
}export type ComponentsStateType {componentList: ArrayComponentInfoType
}const INIT_STATE: ComponentsStateType {componentList: [],// 其他拓展
}export const componentsSlice createSlice({name: component,initialState: INIT_STATE,reducers: {resetComponentList: (state: ComponentsStateType,action: PayloadActionComponentsStateType) {return action.payload},},
})export const { resetComponentList } componentsSlice.actions
export default componentsSlice.reducer
index.ts
import { configureStore } from reduxjs/toolkit
import userReducer, { UserStateType } from ./userReducer
import componentReducer, { ComponentsStateType } from ./componentReducerexport type StateType {user: UserStateTypecomponents: ComponentsStateType
}export default configureStore({reducer: {user: userReducer,components: componentReducer,// 组件列表// 问卷信息},
})画布显示问卷列表
组件类型设定
要点
整合各组件 prop type整合各组件 配置列表
文件树
│ │ ├── QuestionComponents
│ │ │ ├── QuestionInput
│ │ │ │ ├── Component.tsx
│ │ │ │ ├── index.ts
│ │ │ │ └── interface.ts
│ │ │ ├── QuestionTitle
│ │ │ │ ├── Component.tsx
│ │ │ │ ├── index.ts
│ │ │ │ └── interface.ts
│ │ │ └── index.tsQuestionComponents/index.ts
import QuestionInputConf, { QuestionInputPropsType } from ./QuestionInput
import QuestionTitleConf, { QuestionTitlePropsType } from ./QuestionTitle// 各个组件的 prop type
export type ComponentPropsType QuestionInputPropsType QuestionTitlePropsType// 组件的配置
export type ComponentConfType {title: stringtype: stringComponent: React.FCComponentPropsTypedefaultProps: ComponentPropsType
}// 全部组件配置的列表
const componentConfList: ComponentConfType[] [QuestionInputConf,QuestionTitleConf,
]export function getComponentConfByType(type: string) {return componentConfList.find(c c.type type)
}画布动态显示组件列表
效果 要点
根据组件类型动态渲染指定组件 返回组件函数实现getComponent 用于根据组件类型返回指定组件
question/Edit/EditCanvas.tsx
import React, { FC } from react
import styles from ./EditCanvas.module.scss
// import QuestionTitle from ../../../component/QuestionComponents/QuestionTitle/Component
// import QuestionInput from ../../../component/QuestionComponents/QuestionInput/Component
import { Spin } from antd
import useGetComponentInfo from ../../../hooks/useGetComponentInfo
import { getComponentConfByType } from ../../../component/QuestionComponents
import { ComponentInfoType } from ../../../store/componentReducertype PropsType {loading: boolean
}function getComponent(componentInfo: ComponentInfoType) {const { type, props } componentInfoconst componentConf getComponentConfByType(type)if (!componentConf) {return null}const { Component } componentConfreturn Component {...props} /
}const EditCanvas: FCPropsType ({ loading }) {const { componentList } useGetComponentInfo()if (loading) {return (div style{{ textAlign: center, padding: 20px }}Spin //div)}return (div className{styles.canvas}{componentList.map(c {const { fe_id } creturn (div key{fe_id} className{styles[component-wrapper]}div className{styles.component}{getComponent(c)}/div/div)})}/div)// div className{styles.canvas}// div className{styles[component-wrapper]}// div className{styles.component}// QuestionTitle /// /div// /div// div className{styles[component-wrapper]}// div className{styles.component}// QuestionInput /// /div// /div// /div
}export default EditCanvas 点击画布选中组件
效果 要点
选中画布中组件显示 点击画布组件选中点击空白不选中。clearSelectedId() 和 handleClick() 实现 冒泡机制实现 组件 selectedId 与 Redux 绑定 ComponentsStateType 设定 selectedIduseLoadQuestionData 设定进入画布时默认选中组件 处理 Immer 中 draft 为空的问题
EditCanvas.tsx
import React, { FC, MouseEvent } from react
import styles from ./EditCanvas.module.scss
import { useDispatch } from react-redux
import useGetComponentInfo from ../../../hooks/useGetComponentInfo
import { getComponentConfByType } from ../../../component/QuestionComponents
import classNames from classnames
import {ComponentInfoType,changeSelectedId,
} from ../../../store/componentReducertype PropsType {loading: boolean
}function getComponent(componentInfo: ComponentInfoType) {const { type, props } componentInfoconst componentConf getComponentConfByType(type)if (!componentConf) {return null}const { Component } componentConfreturn Component {...props} /
}const EditCanvas: FCPropsType ({ loading }) {const { componentList, selectedId } useGetComponentInfo()const dispatch useDispatch()// 点击冒泡机制实现function handleClick(event: MouseEvent, id: string) {event.stopPropagation()dispatch(changeSelectedId(id))}if (loading) {return (div style{{ textAlign: center, padding: 20px }}Spin //div)}return (div className{styles.canvas}{componentList.map(c {const { fe_id } c// 拼接 class nameconst wrapperDefaultClassName styles[component-wrapper]const selectedClassName styles.selectedconst wrapperClassName classNames({[wrapperDefaultClassName]: true,[selectedClassName]: fe_id selectedId,})return (divkey{fe_id}className{wrapperClassName}onClick{e handleClick(e, fe_id || )}div className{styles.component}{getComponent(c)}/div/div)})}/div)
}export default EditCanvas
/Edit/index.tsx
import React, { FC } from react
import styles from ./index.module.scss
import EditCanvas from ./EditCanvas
import { changeSelectedId } from ../../../store/componentReducer
import { useDispatch } from react-redux
import useLoadQuestionData from ../../../hooks/useLoadQuestionDataconst Edit: FC () {const { loading } useLoadQuestionData()const dispatch useDispatch()function clearSelectedId() {dispatch(changeSelectedId())}return (div className{styles.container}div style{{ backgroundColor: #fff, height: 40px }}Header/divdiv className{styles[content-wrapper]}div className{styles.content}div className{styles.left}Left/divdiv className{styles.main} onClick{clearSelectedId}div className{styles[canvas-wrapper]}div style{{ height: 900px }}EditCanvas loading{loading} //div/div/divdiv className{styles.right}Right/div/div/div/div)
}export default Edit
store/componentReducer/index.ts
import { createSlice, PayloadAction } from reduxjs/toolkit
import { ComponentPropsType } from ../../component/QuestionComponentsexport type ComponentInfoType {fe_id: stringtype: stringtitle: stringprops: ComponentPropsType
}// ComponentsStateType 设定 selectedId
export type ComponentsStateType {selectedId: stringcomponentList: ArrayComponentInfoType
}const INIT_STATE: ComponentsStateType {selectedId: ,componentList: [],// 其他拓展
}export const componentsSlice createSlice({name: component,initialState: INIT_STATE,reducers: {resetComponentList: (state: ComponentsStateType,action: PayloadActionComponentsStateType) {return action.payload},changeSelectedId: (draft: ComponentsStateType,action: PayloadActionstring) {draft.selectedId action.payload || },},
})export const { resetComponentList, changeSelectedId } componentsSlice.actions
export default componentsSlice.reducer
useLoadQuestionData.ts
useEffect(() {if (!data) returnconst { componentList } data// 获取默认的 idlet selectedId if (componentList.length 0) {const { fe_id } componentList[0]selectedId fe_id}if (!componentList || componentList.length 0) returnconst action resetComponentList({ componentList, selectedId })dispatch(action)
}, [data])fiximmer draft 为空
**问题**draft打印出来为null请问是什么原因
// ... 其他代码保持不变 ...export const componentsSlice createSlice({name: component,initialState: INIT_STATE,reducers: {// 错误写法多层嵌套 producechangeSelectedId: produce( // 需移除外层 produce(draft: ComponentsStateType, action: PayloadActionstring) {console.log(payload, action.payload)console.log(draft) // 此时 draft 为 nulldraft.selectedId action.payload || }),},
})原因
Redux Toolkit 已内置 Immer 集成无需额外使用 produce 包装双重 Immer 包装会导致状态代理失效此时 draft 参数无法正确接收 Redux 状态树直接通过 state 参数操作即可实现安全的不可变更新
方案
export const componentsSlice createSlice({name: component,initialState: INIT_STATE,reducers: {// 正确写法直接使用 ImmerRedux Toolkit 已内置changeSelectedId: (state: ComponentsStateType, action: PayloadActionstring) {console.log(payload, action.payload)state.selectedId action.payload || },// ...其他 reducer 保持不变...},
})注意点
RTK 从 1.0 版本开始内置 Immer。Immer 默认用于 createSlice 和 createReducer允许开发者以“可变”的方式更新状态。如果需要可以通过配置禁用 Immer但绝大多数情况下默认启用 Immer 是推荐的做法。 组件库面板
组件分组显示
需求 要点
组件库配置组件库组件列表渲染显示组件库组件点击框图、鼠标样式设计
思路
先对组件库进行配置然后在页面中对应位置进行渲染组件即可。
QuestionComponents/index.ts
import QuestionInputConf, { QuestionInputPropsType } from ./QuestionInput
import QuestionTitleConf, { QuestionTitlePropsType } from ./QuestionTitleexport type ComponentPropsType QuestionInputPropsType QuestionTitlePropsTypeexport type ComponentConfType {title: stringtype: stringComponent: React.FCComponentPropsTypedefaultProps: ComponentPropsType
}const componentConfList: ComponentConfType[] [QuestionInputConf,QuestionTitleConf,
]// 组件库配置
export const componentConfGroup [{groupId: textGroup,groupName: 文本显示,components: [QuestionTitleConf],},{groupId: inputGroup,groupName: 用户输入,components: [QuestionInputConf],},
]export function getComponentConfByType(type: string) {return componentConfList.find(c c.type type)
}
ComponentLib.tsx
import React, { FC } from react
import { componentConfGroup } from ../../../component/QuestionComponents
import { Typography } from antd
import { ComponentConfType } from ../../../component/QuestionComponents
import styles from ./ComponentLib.module.scssconst { Title } Typographyfunction genComponent(c: ComponentConfType) {const { Component } creturn (div className{styles.wrapper}div className{styles.component}Component //div/div)
}const Lib: FC () {return ({componentConfGroup.map((item, index) {const { groupId, groupName } itemreturn (div key{groupId}Titlelevel{3}style{{ fontSize: 16px, marginTop: index 0 ? 20px : 0 }}{groupName}/Titlediv{item.components.map(c genComponent(c))}/div/div)})}/)
}
export default Lib
ComponentLib.scss
.wrapper {padding: 12px;margin-bottom: 12px;cursor: cursor;border: 1px solid #fff;border-radius: 3px;background-color: #fff;:hover {border-color: #d9d9d9;}
}.component {pointer-events: none; // 屏蔽鼠标
} No newline at end of file 组件库添加到画布
需求 要点
组件与画布交互逻辑 组件默认插入画布末尾画布中组件选定后组件插入会在其之后 交互逻辑数据实现 ComponentLib 组件插入画布位置逻辑实现addComponent 通过 selected 判断组件插入画布位置
ComponentLib.tsx
import React, { FC } from react
import { componentConfGroup } from ../../../component/QuestionComponents
import { Typography } from antd
import { ComponentConfType } from ../../../component/QuestionComponents
import styles from ./ComponentLib.module.scss
import { useDispatch } from react-redux
import { addComponent } from ../../../store/componentReducer
import { nanoid } from reduxjs/toolkitconst { Title } Typographyconst Lib: FC () {const dispatch useDispatch()function genComponent(c: ComponentConfType) {const { type, Component } cfunction handleClick(c: ComponentConfType) {const { title, type, defaultProps } cdispatch(addComponent({fe_id: nanoid(),type,title,props: defaultProps,}))}return (div key{type} className{styles.wrapper} onClick{() handleClick(c)}div className{styles.component}Component //div/div)}return ({componentConfGroup.map((item, index) {const { groupId, groupName } itemreturn (div key{groupId}Titlelevel{3}style{{ fontSize: 16px, marginTop: index 0 ? 20px : 0 }}{groupName}/Titlediv{item.components.map(c genComponent(c))}/div/div)})}/)
}
export default Lib
componentReducer/index.ts
import { createSlice, PayloadAction } from reduxjs/toolkit
import { ComponentPropsType } from ../../component/QuestionComponentsexport type ComponentInfoType {fe_id: stringtype: stringtitle: stringprops: ComponentPropsType
}export type ComponentsStateType {selectedId: stringcomponentList: ArrayComponentInfoType
}const INIT_STATE: ComponentsStateType {selectedId: ,componentList: [],// 其他拓展
}export const componentsSlice createSlice({name: component,initialState: INIT_STATE,reducers: {......addComponent: (draft: ComponentsStateType,action: PayloadActionComponentInfoType) {const newCompontent action.payloadconst { selectedId, componentList } draftconst index componentList.findIndex(c c.fe_id selectedId)if (index 0) {draft.componentList.push(newCompontent)} else {draft.componentList.splice(index 1, 0, newCompontent)}draft.selectedId newCompontent.fe_id},},
})export const { resetComponentList, changeSelectedId, addComponent } componentsSlice.actions
export default componentsSlice.reducer 注意fe_id 和 _id 区别
要点
_id是服务端的数据_是因为 mongodb 会为每条数据生成id这是不重复的由 _id 表示fe_id 是前端用于区分组件是否被选中的标记用于组件库与画布的交互
QuestionCart.tsx
type PropsType {_id: stringtitle: stringisPublished: booleanisStar: booleananswerCount: numbercreatedAt: string
}组件属性面板
点击组件显示属性
需求 要点
构建属性面板构造组件属性模块 PropComponet 用于配制组件属性
│ │ ├── QuestionComponents
│ │ │ ├── QuestionInput
│ │ │ │ ├── Component.tsx
│ │ │ │ ├── PropComponent.tsx
│ │ │ │ ├── index.ts
│ │ │ │ └── interface.ts
│ │ │ ├── QuestionTitle
│ │ │ │ ├── Component.tsx
│ │ │ │ ├── PropComponent.tsx
│ │ │ │ ├── index.ts
│ │ │ │ └── interface.ts
│ │ │ └──m index.ts思路
当我们点击画布中的组件后更新组件 selectedId属性面板通过 useGetComponentInfo 获取 selectedComponent 从selectedComponent中层层解构出组件参数props和参数组件 PropComponent 返回 PropComponent {...props} / 传参后的组件即可。 构建属性面板
RightPanel.tsx
import React, { FC } from react
import { Tabs } from antd
import { FileTextOutlined, SettingOutlined } from ant-design/icons
import ComponentProp from ./componentPropconst RightPanel: FC () {const tabsItems [{key: prop,label: (spanFileTextOutlined /属性/span),children: ComponentProp /,},{key: setting,label: (spanSettingOutlined /页面设置/span),children: div页面设置/div,},]return Tabs defaultActiveKeyprop items{tabsItems} /
}export default RightPanel 构造组件属性模块 PropComponet 用于配制组件属性
question/Edit/ComponentProp.tsx
import React, { FC } from react
import useGetComponentInfo from ../../../hooks/useGetComponentInfo
import { getComponentConfByType } from ../../../component/QuestionComponentsconst NoProp () {return div style{{ textAlign: center }}请先选择组件/div
}const ComponentProp: FC () {const { selectedComponent } useGetComponentInfo()if (!selectedComponent) return NoProp /const { type, props } selectedComponentconst componentConf getComponentConfByType(type)if (!componentConf) return NoProp /const { PropComponent } componentConfreturn PropComponent {...props} /
}
export default ComponentPropQuestionComponents/index.ts 新增 PropComponent
import QuestionInputConf, { QuestionInputPropsType } from ./QuestionInput
import QuestionTitleConf, { QuestionTitlePropsType } from ./QuestionTitle
import { FC } from reactexport type ComponentPropsType QuestionInputPropsType QuestionTitlePropsType// 组件的配置: 新增 PropComponent
export type ComponentConfType {title: stringtype: stringComponent: FCComponentPropsTypePropComponent: FCComponentPropsType // HeredefaultProps: ComponentPropsType
}const componentConfList: ComponentConfType[] [QuestionInputConf,QuestionTitleConf,
]export const componentConfGroup [{groupId: textGroup,groupName: 文本显示,components: [QuestionTitleConf],},{groupId: inputGroup,groupName: 用户输入,components: [QuestionInputConf],},
]export function getComponentConfByType(type: string) {return componentConfList.find(c c.type type)
}
QuestionInput/PropComponent.tsx
import React, { FC } from react
import { useEffect } from react
import { Form, Input } from antd
import { QuestionInputPropsType } from ./interfaceconst PropComponent: FCQuestionInputPropsType (props: QuestionInputPropsType
) {const { title, placeholder } propsconst [form] Form.useForm()useEffect(() {form.setFieldsValue({ title, placeholder })}, [title, placeholder])return (Form layoutvertical initialValues{{ title, placeholder }} form{form}Form.Itemlabel标题nametitlerules{[{ required: true, message: 请输入标题 }]}Input //Form.ItemForm.Item labelPlaceholder nameplaceholderInput //Form.Item/Form)
}export default PropComponentQuestionTitle/PropComponent.tsx
import React, { FC } from react
import { useEffect } from react
import { Form, Input, Select, Checkbox } from antd
import { QuestionTitlePropsType } from ./interfaceconst PropComponent: FCQuestionTitlePropsType (props: QuestionTitlePropsType
) {const { text, level, isCenter } propsconst [form] Form.useForm()useEffect(() {form.setFieldsValue({ text, level, isCenter })}, [text, level, isCenter])return (FormlayoutverticalinitialValues{{ text, level, isCenter }}form{form}Form.Itemlabel标题内容nametextrules{[{ required: true, message: 请输入标题内容 }]}Input //Form.ItemForm.Item label标题级别 namelevelSelectoptions{[{ value: 1, label: 一级标题 },{ value: 2, label: 二级标题 },{ value: 3, label: 三级标题 },]}//Form.ItemForm.Item nameisCenter valuePropNamecheckedCheckbox居中显示/Checkbox/Form.Item/Form)
}export default PropComponent 组件属性数据同步画布
需求 要点
componentProp 统一更新组件数据更新方式传递给 PropComponentRedux 设计 changeComponentProps 参数更新函数
思路
用户选择画布组件后传递 selectedId 到 Redux 中用户更新组件属性面板数值会通过 onChange 事件传递参数到 Redux采用 changeComponentProps 对画布中组件数据进行修改
Edit/componentProp.tsx
import React, { FC } from react
import useGetComponentInfo from ../../../hooks/useGetComponentInfo
import {getComponentConfByType,ComponentPropsType,
} from ../../../component/QuestionComponents
import { useDispatch } from react-redux
import { changeComponentProps } from ../../../store/componentReducerconst NoProp () {return div style{{ textAlign: center }}请先选择组件/div
}const ComponentProp: FC () {const dispatch useDispatch()const { selectedComponent } useGetComponentInfo()if (!selectedComponent) return NoProp /const { type, props } selectedComponentconst componentConf getComponentConfByType(type)if (!componentConf) return NoProp /const { PropComponent } componentConf// 组件参数更新传递组件参数到 Redux 进行更新function changeProps(newProps: ComponentPropsType) {if (!selectedComponent) returndispatch(changeComponentProps({ fe_id: selectedComponent.fe_id, newProps }))}return PropComponent {...props} onChange{changeProps} /
}
export default ComponentProp
store/componentReducer/index.tsx
import { createSlice, PayloadAction } from reduxjs/toolkit
import { ComponentPropsType } from ../../component/QuestionComponents......
export const componentsSlice createSlice({name: component,initialState: INIT_STATE,reducers: {......changeComponentProps: (draft: ComponentsStateType,action: PayloadAction{ fe_id: string; newProps: ComponentPropsType }) {const { fe_id, newProps } action.payloadconst component draft.componentList.find(c c.fe_id fe_id)if (component) {component.props {...component.props,...newProps,}}},},
})export const {resetComponentList,changeSelectedId,addComponent,changeComponentProps,
} componentsSlice.actions
export default componentsSlice.reducer