吐司问卷(React低代码):问卷编辑器 I

基于React的低代码平台开发实践 随着低代码平台的流行,基于React的低代码开发方案正受到越来越多开发者的关注。React作为一种流行的前端框架,结合低代码平台的易用性和灵活性,可以加速应用程序的开发过程,降低开发成本。本文将介绍如何利用基于React的低代码平台快速构建一个简单的待办事项应用。 阅读详情

问卷编辑器

Date: February 20, 2025 4:17 PM (GMT+8)


目标

  • 完成问卷编辑器的设计和开发
  • 完成复杂系统的 UI 组件拆分
  • 完成复杂系统的数据结构设计



内容

  • 需求分析
  • 技术方案设计
  • 开发

注意事项:

  • 需求指导设计,设计指导开发。前两步很重要
  • 页面复杂的话,可以设计边开发



画布 UI 分析

画布UI组成:

image.png




画布基础构建

画布Y轴滚动

要点:

  • flex 布局
  • 居中对齐
  • 画布 Y 向滚动

效果:

image.png

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/useLoadQuestionData'

const Edit: FC = () => {
  // const { id = '' } = useParams()
  // const { loading, data } = useLoadQuestionData()

  return (
    <div className={styles.container}>
      <div style={{ backgroundColor: '#fff', height: '40px' }}>Header</div>
      <div className={styles['content-wrapper']}>
        <div className={styles.content}>
          <div className={styles.left}>Left</div>
          <div className={styles.main}>
            <div className={styles['canvas-wrapper']}>
              <div style={{ height: '900px' }}>画布滚动测试</div>
            </div>
          </div>
          <div 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

目标:

image.png

要点:

  • 组件默认参数设置{ ...QuestionInputDefaultProps, ...props }

Component.ts

import React, { FC } from 'react'
import { Typography, Input } from 'antd'
import { QuestionTitleProps, QuestionInputDefaultProps } from './interface'

const { Paragraph } = Typography
const QuestionTitle: FC<QuestionTitleProps> = (props: QuestionTitleProps) => {
  const { title = '', placeholder = '' } = {
    ...QuestionInputDefaultProps,
    ...props,
  }
  return (
    <div>
      <Paragraph strong>{title}</Paragraph>
      <div>
        <Input placeholder={placeholder}></Input>
      </div>
    </div>
  )
}
export default QuestionTitle

interface.ts

export type QuestionTitleProps = {
  title?: string
  placeholder?: string
}

export const QuestionInputDefaultProps: QuestionTitleProps = {
  title: '输入框标题',
  placeholder: '请输入内容',
}


开发组件 Input

目标:

image.png

要点:

  • Input标题样式函数设计 genFontSize

Component.ts

import React, { FC } from 'react'
import { Typography } from 'antd'
import { QuestionTitleProps, defaultQuestionTitleProps } from './interface'

const { Title } = Typography
const QuestionTitle: FC<QuestionTitleProps> = (props: QuestionTitleProps) => {
  const {
    text = '',
    level = 1,
    isCenter = false,
  } = { ...defaultQuestionTitleProps, ...props }
  const genFontSize = (level: number) => {
    if (level === 1) return '24px'
    if (level === 2) return '20px'
    if (level === 3) return '16px'
    if (level === 4) return '14px'
    if (level === 5) return '12px'
    return '24px'
  }
  return (
    <Title
      level={level}
      style={{
        textAlign: isCenter ? 'center' : 'left',
        marginBottom: '0px',
        fontSize: genFontSize(level),
      }}
    >
      {text}
    </Title>
  )
}

export default QuestionTitle

interface.ts

export type QuestionTitleProps = {
  text?: string
  level?: 1 | 2 | 3 | 4 | 5
  isCenter?: boolean
}

export const defaultQuestionTitleProps: QuestionTitleProps = {
  text: '一行标题',
  level: 1,
  isCenter: false,
}

画布集成组件测试

目标:

image.png

要点:

  • 画布元素禁止点击,样式设计 .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/Component'

const EditCanvas: FC = () => {
  return (
    <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

EidtCanvas.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 中,便于组件间共享使用。

image.png

组件数据结构设计

服务端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/componentReducer'

function 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) return
    const { title = '', componentList } = data
    if (!componentList || componentList.length === 0) return
    const 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.ts

componentReducer/index.ts

import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { ComponentPropsType } from '../../component/QuestionComponents'

export type ComponentInfoType = {
  fe_id: string
  type: string
  title: string
  props: ComponentPropsType
}

export type ComponentsStateType = {
  componentList: Array<ComponentInfoType>
}

const INIT_STATE: ComponentsStateType = {
  componentList: [],
  // 其他拓展
}

export const componentsSlice = createSlice({
  name: 'component',
  initialState: INIT_STATE,
  reducers: {
    resetComponentList: (
      state: ComponentsStateType,
      action: PayloadAction<ComponentsStateType>
    ) => {
      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 './componentReducer'

export type StateType = {
  user: UserStateType
  components: 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.ts

QuestionComponents/index.ts

import QuestionInputConf, { QuestionInputPropsType } from './QuestionInput'
import QuestionTitleConf, { QuestionTitlePropsType } from './QuestionTitle'

// 各个组件的 prop type
export type ComponentPropsType = QuestionInputPropsType & QuestionTitlePropsType

// 组件的配置
export type ComponentConfType = {
  title: string
  type: string
  Component: React.FC<ComponentPropsType>
  defaultProps: ComponentPropsType
}

// 全部组件配置的列表
const componentConfList: ComponentConfType[] = [
  QuestionInputConf,
  QuestionTitleConf,
]

export function getComponentConfByType(type: string) {
  return componentConfList.find(c => c.type === type)
}

画布动态显示组件列表

效果:

image.png

要点:

  • 根据组件类型动态渲染指定组件
    • 返回组件函数实现: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/componentReducer'

type PropsType = {
  loading: boolean
}

function getComponent(componentInfo: ComponentInfoType) {
  const { type, props } = componentInfo
  const componentConf = getComponentConfByType(type)
  if (!componentConf) {
    return null
  }
  const { Component } = componentConf
  return <Component {...props} />
}

const EditCanvas: FC<PropsType> = ({ 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 } = c
        return (
          <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


点击画布选中组件

效果:

2025-02-26 12.17.58.gif

要点:

  • 选中画布中组件显示
    • 点击画布组件选中,点击空白不选中。clearSelectedId() 和 handleClick() 实现
      • 冒泡机制实现
    • 组件 selectedId 与 Redux 绑定
      • ComponentsStateType 设定 selectedId
      • useLoadQuestionData 设定进入画布时默认选中组件
  • 处理 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/componentReducer'

type PropsType = {
  loading: boolean
}

function getComponent(componentInfo: ComponentInfoType) {
  const { type, props } = componentInfo
  const componentConf = getComponentConfByType(type)
  if (!componentConf) {
    return null
  }
  const { Component } = componentConf
  return <Component {...props} />
}

const EditCanvas: FC<PropsType> = ({ 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 name
        const wrapperDefaultClassName = styles['component-wrapper']
        const selectedClassName = styles.selected
        const wrapperClassName = classNames({
          [wrapperDefaultClassName]: true,
          [selectedClassName]: fe_id === selectedId,
        })

        return (
          <div
            key={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/useLoadQuestionData'

const Edit: FC = () => {
  const { loading } = useLoadQuestionData()
  const dispatch = useDispatch()
  function clearSelectedId() {
    dispatch(changeSelectedId(''))
  }
  return (
    <div className={styles.container}>
      <div style={{ backgroundColor: '#fff', height: '40px' }}>Header</div>
      <div className={styles['content-wrapper']}>
        <div className={styles.content}>
          <div className={styles.left}>Left</div>
          <div className={styles.main} onClick={clearSelectedId}>
            <div className={styles['canvas-wrapper']}>
              <div style={{ height: '900px' }}>
                <EditCanvas loading={loading} />
              </div>
            </div>
          </div>
          <div 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/QuestionComponents'

export type ComponentInfoType = {
  fe_id: string
  type: string
  title: string
  props: ComponentPropsType
}

// ComponentsStateType 设定 selectedId
export type ComponentsStateType = {
  selectedId: string
  componentList: Array<ComponentInfoType>
}

const INIT_STATE: ComponentsStateType = {
  selectedId: '',
  componentList: [],
  // 其他拓展
}

export const componentsSlice = createSlice({
  name: 'component',
  initialState: INIT_STATE,
  reducers: {
    resetComponentList: (
      state: ComponentsStateType,
      action: PayloadAction<ComponentsStateType>
    ) => {
      return action.payload
    },
    changeSelectedId: (
      draft: ComponentsStateType,
      action: PayloadAction<string>
    ) => {
      draft.selectedId = action.payload || ''
    },
  },
})

export const { resetComponentList, changeSelectedId } = componentsSlice.actions
export default componentsSlice.reducer

useLoadQuestionData.ts

useEffect(() => {
  if (!data) return
  const { componentList } = data
  // 获取默认的 id
  let selectedId = ''
  if (componentList.length > 0) {
    const { fe_id } = componentList[0]
    selectedId = fe_id
  }

  if (!componentList || componentList.length === 0) return
  const action = resetComponentList({ componentList, selectedId })
  dispatch(action)
}, [data])

fix:immer draft 为空

**问题:**draft打印出来为null,请问是什么原因?

// ... 其他代码保持不变 ...

export const componentsSlice = createSlice({
  name: 'component',
  initialState: INIT_STATE,
  reducers: {
    // 错误写法:多层嵌套 produce
    changeSelectedId: produce( // 需移除外层 produce
      (draft: ComponentsStateType, action: PayloadAction<string>) => {
        console.log('payload', action.payload)
        console.log(draft) // 此时 draft 为 null
        draft.selectedId = action.payload || ''
      }
    ),
  },
})

原因:

  1. Redux Toolkit 已内置 Immer 集成,无需额外使用 produce 包装
  2. 双重 Immer 包装会导致状态代理失效,此时 draft 参数无法正确接收 Redux 状态树
  3. 直接通过 state 参数操作即可实现安全的不可变更新

方案:

export const componentsSlice = createSlice({
  name: 'component',
  initialState: INIT_STATE,
  reducers: {
    // 正确写法:直接使用 Immer(Redux Toolkit 已内置)
    changeSelectedId: (state: ComponentsStateType, action: PayloadAction<string>) => {
      console.log('payload', action.payload)
      state.selectedId = action.payload || ''
    },
    // ...其他 reducer 保持不变...
  },
})

注意点:

  • RTK 从 1.0 版本开始内置 Immer。
  • Immer 默认用于 createSlice 和 createReducer,允许开发者以“可变”的方式更新状态。
  • 如果需要,可以通过配置禁用 Immer,但绝大多数情况下,默认启用 Immer 是推荐的做法。



组件库面板

组件分组显示

需求:

2025-02-26 14.47.53.gif

要点:

  • 组件库配置
  • 组件库组件列表渲染显示
  • 组件库组件点击框图、鼠标样式设计

思路:

先对组件库进行配置,然后在页面中对应位置进行渲染组件即可。

QuestionComponents/index.ts

import QuestionInputConf, { QuestionInputPropsType } from './QuestionInput'
import QuestionTitleConf, { QuestionTitlePropsType } from './QuestionTitle'

export type ComponentPropsType = QuestionInputPropsType & QuestionTitlePropsType

export type ComponentConfType = {
  title: string
  type: string
  Component: React.FC<ComponentPropsType>
  defaultProps: 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.scss'

const { Title } = Typography

function genComponent(c: ComponentConfType) {
  const { Component } = c
  return (
    <div className={styles.wrapper}>
      <div className={styles.component}>
        <Component />
      </div>
    </div>
  )
}

const Lib: FC = () => {
  return (
    <>
      {componentConfGroup.map((item, index) => {
        const { groupId, groupName } = item
        return (
          <div key={groupId}>
            <Title
              level={3}
              style={{ fontSize: '16px', marginTop: index > 0 ? '20px' : '0' }}
            >
              {groupName}
            </Title>
            <div>{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



组件库添加到画布

需求:

2025-02-26 15.52.48.gif

要点:

  • 组件与画布交互逻辑
    • 组件默认插入画布末尾
    • 画布中组件选定后,组件插入会在其之后
  • 交互逻辑数据实现
    • 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/toolkit'

const { Title } = Typography

const Lib: FC = () => {
  const dispatch = useDispatch()
  function genComponent(c: ComponentConfType) {
    const { type, Component } = c

    function handleClick(c: ComponentConfType) {
      const { title, type, defaultProps } = c

      dispatch(
        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 } = item
        return (
          <div key={groupId}>
            <Title
              level={3}
              style={{ fontSize: '16px', marginTop: index > 0 ? '20px' : '0' }}
            >
              {groupName}
            </Title>
            <div>{item.components.map(c => genComponent(c))}</div>
          </div>
        )
      })}
    </>
  )
}
export default Lib

componentReducer/index.ts

import { createSlice, PayloadAction } from '@reduxjs/toolkit'
import { ComponentPropsType } from '../../component/QuestionComponents'

export type ComponentInfoType = {
  fe_id: string
  type: string
  title: string
  props: ComponentPropsType
}

export type ComponentsStateType = {
  selectedId: string
  componentList: Array<ComponentInfoType>
}

const INIT_STATE: ComponentsStateType = {
  selectedId: '',
  componentList: [],
  // 其他拓展
}

export const componentsSlice = createSlice({
  name: 'component',
  initialState: INIT_STATE,
  reducers: {
	  ......
    addComponent: (
      draft: ComponentsStateType,
      action: PayloadAction<ComponentInfoType>
    ) => {
      const newCompontent = action.payload
      const { selectedId, componentList } = draft
      const 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: string
  title: string
  isPublished: boolean
  isStar: boolean
  answerCount: number
  createdAt: string
}



组件属性面板

点击组件显示属性

需求:

2025-02-26 20.33.46.gif

要点:

  • 构建属性面板
  • 构造组件属性模块 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} /> 传参后的组件即可。

image.png


构建属性面板

RightPanel.tsx

import React, { FC } from 'react'
import { Tabs } from 'antd'
import { FileTextOutlined, SettingOutlined } from '@ant-design/icons'
import ComponentProp from './componentProp'

const RightPanel: FC = () => {
  const tabsItems = [
    {
      key: 'prop',
      label: (
        <span>
          <FileTextOutlined />
          属性
        </span>
      ),
      children: <ComponentProp />,
    },
    {
      key: 'setting',
      label: (
        <span>
          <SettingOutlined />
          页面设置
        </span>
      ),
      children: <div>页面设置</div>,
    },
  ]
  return <Tabs defaultActiveKey="prop" items={tabsItems} />
}

export default RightPanel


构造组件属性模块 PropComponet ,用于配制组件属性

question/Edit/ComponentProp.tsx

import React, { FC } from 'react'
import useGetComponentInfo from '../../../hooks/useGetComponentInfo'
import { getComponentConfByType } from '../../../component/QuestionComponents'

const NoProp = () => {
  return <div style={{ textAlign: 'center' }}>请先选择组件</div>
}

const ComponentProp: FC = () => {
  const { selectedComponent } = useGetComponentInfo()
  if (!selectedComponent) return <NoProp />
  const { type, props } = selectedComponent
  const componentConf = getComponentConfByType(type)
  if (!componentConf) return <NoProp />
  const { PropComponent } = componentConf

  return <PropComponent {...props} />
}
export default ComponentProp

QuestionComponents/index.ts 新增 PropComponent

import QuestionInputConf, { QuestionInputPropsType } from './QuestionInput'
import QuestionTitleConf, { QuestionTitlePropsType } from './QuestionTitle'
import { FC } from 'react'

export type ComponentPropsType = QuestionInputPropsType & QuestionTitlePropsType

// 组件的配置: 新增 PropComponent
export type ComponentConfType = {
  title: string
  type: string
  Component: FC<ComponentPropsType>
  PropComponent: FC<ComponentPropsType> // Here
  defaultProps: 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 './interface'

const PropComponent: FC<QuestionInputPropsType> = (
  props: QuestionInputPropsType
) => {
  const { title, placeholder } = props
  const [form] = Form.useForm()
  useEffect(() => {
    form.setFieldsValue({ title, placeholder })
  }, [title, placeholder])
  return (
    <Form layout="vertical" initialValues={{ title, placeholder }} form={form}>
      <Form.Item
        label="标题"
        name="title"
        rules={[{ required: true, message: '请输入标题' }]}
      >
        <Input />
      </Form.Item>
      <Form.Item label="Placeholder" name="placeholder">
        <Input />
      </Form.Item>
    </Form>
  )
}

export default PropComponent

QuestionTitle/PropComponent.tsx

import React, { FC } from 'react'
import { useEffect } from 'react'
import { Form, Input, Select, Checkbox } from 'antd'
import { QuestionTitlePropsType } from './interface'

const PropComponent: FC<QuestionTitlePropsType> = (
  props: QuestionTitlePropsType
) => {
  const { text, level, isCenter } = props
  const [form] = Form.useForm()
  useEffect(() => {
    form.setFieldsValue({ text, level, isCenter })
  }, [text, level, isCenter])
  return (
    <Form
      layout="vertical"
      initialValues={{ text, level, isCenter }}
      form={form}
    >
      <Form.Item
        label="标题内容"
        name="text"
        rules={[{ required: true, message: '请输入标题内容' }]}
      >
        <Input />
      </Form.Item>
      <Form.Item label="标题级别" name="level">
        <Select
          options={[
            { value: 1, label: '一级标题' },
            { value: 2, label: '二级标题' },
            { value: 3, label: '三级标题' },
          ]}
        />
      </Form.Item>
      <Form.Item name="isCenter" valuePropName="checked">
        <Checkbox>居中显示</Checkbox>
      </Form.Item>
    </Form>
  )
}

export default PropComponent



组件属性数据同步画布

需求:

2025-02-26 22.08.03.gif

要点:

  • componentProp 统一更新组件数据,更新方式传递给 PropComponent
  • Redux 设计 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/componentReducer'

const NoProp = () => {
  return <div style={{ textAlign: 'center' }}>请先选择组件</div>
}

const ComponentProp: FC = () => {
  const dispatch = useDispatch()
  const { selectedComponent } = useGetComponentInfo()
  if (!selectedComponent) return <NoProp />
  const { type, props } = selectedComponent
  const componentConf = getComponentConfByType(type)
  if (!componentConf) return <NoProp />
  const { PropComponent } = componentConf
  // 组件参数更新:传递组件参数到 Redux 进行更新
  function changeProps(newProps: ComponentPropsType) {
    if (!selectedComponent) return
    dispatch(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.payload
      const 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

react 复杂低代码项目 - 问卷星 2.设置淘宝镜像源: npm config set registry https://registry.npm.taobao.org。一个git hook工具,在git commit之前执行自定义的命令,如执行代码风格的检查,避免提交非规范代码。执行npm run format, 这个时候发现文件里面的单引号,都变成了双引号。组件化:拆分页面结构,通过组件拼装页面,复用组件。只关注数据的修改,不用再操作dom,增加开发效率。创建.prettierrc.js文件,制定规范。2.配置eslint。 阅读详情

相关推荐

(源码)基于 React 框架的低代码生成器.zip

# 基于 React 框架的低代码生成器 ## 项目简介 本项目是基于 React 框架的低代码生成器,名为 lowcodemonorepo。它提供了一套强大的可视化工具,开发者可通过简单配置与拖拽操作,快速构建功能丰富的应用程序界面。项目包含编辑器、插件系统、骨架工作台等核心功能模块。 ## 项目的主要特性和功能 1. 模块化设计清晰的项目结构支持模块化开发,便于扩展与维护。 2. 可视化编辑器提供直观易用的可视化编辑体验,支持拖拽式操作。 3. 插件系统支持自定义插件,可扩展功能。 4. 丰富的内置资源包含多种内置组件和设置器,满足常见需求。 5. 预览功能具备画布面板和模拟器,方便预览与调试。 ## 安装使用步骤 ### 前提条件 确保已安装以下环境 Node.js(版本 16.16.0) pnpm(版本 7.x) React(版本 17.0.2) React DOM(版本 17.0.2) ### 安装依赖

基于React的开源低代码框架ToolJet

ToolJet 是用于构建基于 React 的 Web 应用程序和内部工具的开源低代码框架 🚀。

wbsu2004的博客 1万+

基于STM32F103与AD8232的便携式心电监测系统设计与实现

本文详细介绍基于STM32F103微控制器与AD8232心电传感器模块的便携式心电监测系统设计与实现。系统通过ADC采集心电信号,结合软件滤波算法处理基线漂移和工频干扰,并利用OLED屏幕实时显示波形,具有低成本、高实用性的特点,适用于健康监测与嵌入式学习。

m5n6o7的博客 737

react 低代码图编辑探索

基于react的低代码可拖拽实现,低代码JsonSchema渲染实现,图可编辑实现

weixin_43783814的博客 1512

Xilinx Vitis 2020.1头文件路径配置避坑指南:解决#include找不到文件的常见问题

本文针对Xilinx Vitis 2020.1开发环境中常见的#include头文件找不到问题,提供了系统的避坑指南。文章深入解析了Vitis的头文件搜索机制,对比了通过工程属性GUI、修改编译命令及使用环境变量三种配置方法的优劣与隐藏陷阱,并给出了高级场景的排查流程与工程结构最佳实践,帮助开发者高效解决路径配置难题。

weixin_28831399的博客 570

Ubuntu离线情况下安装gcc编译器

Ubuntu离线情况下安装gcc编译器1.系统版本2.安装说明3.异常处理 1.系统版本 (1)Ubuntu系统版本:20.04.1LTS; (2)gcc编译器版本:gcc-9.3.0(下载链接为https://download.csdn.net/download/qq_18350351/13762214); 当前可找到的gcc最新版本为gcc-10,但是笔者在安装gcc-10版本时发现一些依赖项无法安装,因此选取了gcc-9版本。如果是在联网情况下安装,直接使用apt相关的安装指令即可。 2.安装说明 安

GreenHand的博客 3933

react实现问卷调查

react的index中的ReactDOM.render文件 import React from 'react'; import ReactDOM from 'react-dom'; import Check from './check.jsx'; // import Browser from './browser.jsx'; ReactDOM.render( <Check/>, document.getElementById('root') ); 基本样式文件 imp

qq_44170622的博客 1682

【RT-DETR有效改进】利用MobileNetV3替换Backbone(轻量化网络结构,提点)

本文给大家带来的改进机制是,其主要改进思想集中在结合硬件感知的网络架构搜索(NAS)和NetAdapt算法,以优化移动设备CPU上的性能。它采用了新颖的架构设计,包括反转残差结构和线性瓶颈层,以及新的高效分割解码器Lite Reduced Atrous Spatial Pyramid Pooling(LR-ASPP),以提升在移动分类、检测和分割任务上的表现。实验表明,MobileNets在资源和准确性的权衡方面表现出色,并在多种应用(如对象检测、细粒度分类、面部属性识别和大规模地理定位)中展现了其有效性。

Snu77的博客 2748

React 低代码项目:组件设计

React 低代码项目:组件设计

CaptainDrake的博客 1719

8、 使用TPM密钥:从理论到实践的全面指南

本文全面介绍了如何使用TPM(可信平台模块)进行密钥管理,涵盖密钥的创建、存储、加载、迁移以及特殊密钥类型的使用。通过详细的操作步骤和代码示例,帮助读者掌握TPM密钥管理技术,确保系统的安全性和可靠性。

weixin_36212459的博客 242

A2A 协议详解:Agent Card 的设计与实现

Agent Card 是 Agent2Agent (A2A) 协议中实现智能体发现与互操作性的核心组件。本文将深入剖析 Agent Card 的设计理念、关键字段及其在智能体生态系统中的作用。我们将通过详细的 JSON Schema 解析和 Python SDK 示例,帮助您理解如何精确地定义和发布智能体能力,为构建可发现、可协作的 AI 智能体奠定基础。当您使用a2a.Agent类初始化一个智能体时,SDK 会根据您提供的参数(nameversion等)自动构建内部的 Agent Card 表示。

加入“Super Entity”,与全能开发团队共探AI智能体与数字人项目,开启前沿技术之旅。 1476

React低代码开发——React Dnd

react dnd 低代码开发

weixin_43506403的博客 1683

利用XGBoost实现短期电力负荷预测

本数据集为某地区2009年-2015年的用电负荷,时间片为每分钟这里我们定义了XGB模型作为实例,来进行模型训练,之后我们会对其使用网格搜索找到最优的参数。

优快云 精品推荐 4495

吐司问卷(React低代码):问卷编辑器 II

工具栏设计、组件库设计

CaptainDrake的博客 1335

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

React 极简低代码实现

这篇文章讲解如何使用 React 开发一个简易的低代码生成器,实现拖拽生成页面的功能。作者使用 Next.js 搭建项目,并详细介绍了项目需求、组件设计以及核心功能的实现方式。文章中重点阐述了如何利用 react-dnd 和 react-moveable 这两个库来实现拖拽和元素移动,并分享了开发过程中遇到的难点和解决方案。最后,作者公开了项目的 GitHub 源码地址,方便读者学习和参考。

weixin_48191864的博客 1777

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

基于React低代码平台开发:构建高效、灵活的应用新范式

本书由三部分组成,第 一部分介绍基础理论知识,第二部分介绍如何构建低代码系统,先进行需求分析,涉及列表页、详情页和表单页的布局需求和逻辑需求,可视化编辑器、应用管理、组件市场和用户管理的需求等内容,然后介绍设计思路,包含架构设计、Schema 设计、组件库市场和 MongoDB Document 设计等,z后介绍详细的编码过程,包括可视化编辑器的实现、渲染 SDK 的实现、版本控制,以及如何将其集成到其他系统。秦小倩(网名何遇)

程序边界 7320

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

如何开发一个低代码编辑器(react)

结尾有源码。

weixin_45821809的博客 470

基于React的低代码开发:探索应用构建的新模式

基于React的低代码开发:探索应用构建的新模式

“路漫漫其修远兮,吾将上下求索。” 2888

react 低代码平台方案汇总

虽然不是专门针对React,但AppSmith是一个强大的通用低代码平台,支持React组件的集成。: 虽然它本质上是一个UI组件库,但React Bootstrap提供了大量现成的UI元素和布局工具,可以看作是构建低代码应用的基础。结合React和其他库,可以快速搭建和分享React应用原型,有一定的低代码特性。虽然其核心不公开基于哪种前端技术,但它生成的应用在前端层面大量使用React进行渲染,适合金融机构和大型企业的定制化需求。它底层也是基于React,且支持React组件的导入和自定义。

qq_35374791的博客 5776

基于 React + Ant Design实现一个可拖拽布局的低代码编辑器渲染引擎(支持碰撞检测)

本文基于 React + Ant Design 打造一个类似 Retool 的组件拖拽布局渲染引擎,支持组件面板、画布编辑区、右侧配置区,并实现了 碰撞检测、虚线拖影、组件属性配置等交互细节。

qq_43771593的博客 1659

用 Vue/React 搭建简易低代码编辑器:组件拖拽原理

用 Vue/React 搭建简易低代码编辑器:组件拖拽原理

fruge365的博客 6452

从零开始实现一个简单的低代码编辑器

来自 「赵越」 同学的内部分享,文章示例代码:「https://github.com/shadowings-zy/mini-editor」,欢迎 Star。目录:编辑器功能拆分编辑器数据格式的定义项目代码结构重点逻辑的实现(画布渲染、属性联动、拖拽组件)与后台交互还可以优化的点「Oh, 对了,文末有福利!!!」一、写在前面低代码编辑器作为一种能够极大地提升开发效率的PaaS软件,近些年来一直收到各...

frontend_frank的博客 938

低代码引擎实战 - 从零封装低代码组件

一、 Container 构造页面时需要给其他组件一个容器来包裹,先用 vant 的 Card 组件来封装我们的容器组件 Container。 src/components 目录下新建 Container 文件夹,再创建 Container.tsx 和 index.tsx 文件 Container.tsx import * as React from 'react'; import {createElement} from 'react'; import {Card} from 'react-vant';

开源世界 1705

react-问卷星项目(2)

一个git hook 工具,即在git commit之前执行自定义的命令,将规范流程化,如执行代码风格的检查,避免提交非规范的代码,在github搜索即可。这两条是接着执行的,表示创建husky,在文档说明里也有,我按照视频里的指令执行会显示初始化失败,这两条我自己用的没问题。

BHDDGT的博客 1780
上一篇: React低代码项目:Redux 状态管理
下一篇: 吐司问卷(React低代码):问卷编辑器 II
CaptainDrake
博客等级 码龄6年 5363粉丝 187原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值