React + 项目(从基础到实战) -- 第11期

目标

问卷编辑器的开发

设计UI - 拆分布局

水平垂直居中

在这里插入图片描述

画布 y方向滚动

在这里插入图片描述

自定义问卷组件

在这里插入图片描述

后端 返回组件数据

 //获取单个问卷信息

    {

        url: '/api/question/:id',

        method: 'get',

        response: () => {

            return {

                errno: 0,

                data: {

                    id: Random.id(),

                    title: Random.ctitle(),

                    componentList:[

                        //Title

                        {

                            id:Random.id(),

                            type:'questionTitle', //组件类型不能重复,前后端统一

                            title:"标题",

                            props:{

                                text:"问卷标题",

                                level:1,

                                isCenter:false

                            }

                        },

                        //Input

                        {

                            id:Random.id(),

                            type:'questionInput',

                            title:"输入框",

                            props:{

                                title:"输入框",

                                placeholder:"请输入内容",

                            }

                        },

                          //Input2

                          {

                            id:Random.id(),

                            type:'questionInput',

                            title:"输入框2",

                            props:{

                                title:"输入框2",

                                placeholder:"请输入内容2",

                            }

                        }

  
  

                    ]

                }

            }

        }

    },

前端 redux 存储后端返回组件数据

切片

import { createSlice , PayloadAction } from "@reduxjs/toolkit";

import { ComponentPropsType } from "../../components/QuestionComponents";

  
  

//单个组件的信息

export type ComponentInfoType={

    fe_id : string,//为什么下划线

    type : string,

    title: string,

    props:ComponentPropsType

}

  
  

//redux存放组件列表

//1. 定义数据结构

export type ComponentsStateType={

    componentList:Array<ComponentInfoType>,

}

  

//2. 初始化

const INIT_STATE:ComponentsStateType={

    componentList:[],

    //其他扩展

  

}

 export const componentsSlice = createSlice({

     name:"components",

     initialState:INIT_STATE,

     reducers:{

         //重置所有组件

         //看不懂啊老铁!!!!

         resetComponentList:(state: ComponentsStateType , action: PayloadAction<ComponentsStateType>)=>{

             return action.payload

         }

     }

  

 })

  

//导出所有的actions

  

export const {resetComponentList} = componentsSlice.actions

  

export default componentsSlice.reducer

store

import { configureStore } from '@reduxjs/toolkit'

import userReducer, { UserStateType } from './userReducer'

import componentsReducer , {ComponentsStateType}from './componentsReducer'

  
  

export type StateType={

  user : UserStateType,

  components : ComponentsStateType

}

  

export default configureStore({

  reducer: {

    //分模块注册

    user: userReducer, // 存储user数据

    components : componentsReducer// 存储问卷组件列表的数据

    // 存储问卷组件列表的数据

  
  

    // 存储问卷信息数据

  }

})

发请求时存储数据

import { useEffect , useState } from "react";

  

import { useParams } from "react-router-dom";

import { useRequest } from "ahooks";

  

import {useDispatch} from 'react-redux'

import {resetComponentList} from '../store/componentsReducer'

  

//导入发起请求的函数

  

import { getQuestinService } from "../services/question";

  

function useLoadQuestionData() {

    const dispatch = useDispatch()

    const {id = ''} =useParams()

  

   const {data , loading , error , run} = useRequest(

    async (id : string) => {

        if(!id) throw new Error('不存在问卷id')

        const data = await getQuestinService(id)

        return data

    },

    {

        manual: true,

    }

   )

    //根据获取的data 设置redux store

    useEffect(() => {

        if(!data) return

        const {title ='' , componentList = []} = data

  

        //获取到的componentList 存储到 Redux store中

        dispatch(resetComponentList({

            componentList

        }))

  

    },[data])

  
  

    //问卷改变时, 重新加载问卷数据

    useEffect(() => {

        run(id)

    },[id])

  
  

    return {

        loading,

        error,

    }

  
  

}

  
  

export default useLoadQuestionData;

在这里插入图片描述

页面画布区域显示组件列表

自定义hook获取数据

import { useSelector } from "react-redux";

import { StateType } from "../store";

import { ComponentsStateType } from "../store/componentsReducer";

  
  

function useGetComponentInfo() {

    //使用useSelector获取store中的数据

    const componens= useSelector<StateType>(state => state.components) as ComponentsStateType

  

    //结构出空数组

    const {componentList = []} = componens

    return {

        componentList

    }

  

}

  

export default useGetComponentInfo;

重构canvas页面

import { FC } from 'react';

import styles from './EditCanvas.module.scss';

//静态展示



import useGetComponentInfo from '../../../hooks/useGetComponentInfo';

import { ComponentInfoType } from '../../../store/componentsReducer';

import { getComponentConfByType } from '../../../components/QuestionComponents';

type PropsType={

  loading : boolean

}

  
  

const EditCanvas: FC<PropsType> = ({loading}) => {

  const {componentList } = useGetComponentInfo();

  

  if(loading){

    return <div>loading</div>

  }

  

  //根据传入的组件 ,

  function getComponent(componetInfo : ComponentInfoType)

  {

    const {type , props} = componetInfo

    //根据组件类型找到对应的组件配置

    const componentConf= getComponentConfByType(type)

    if(!componentConf) return null

   const {Component} = componentConf

  

    return <Component {...props} />

  }

  

  return (

    <div className={styles.canvas}>

  

      {componentList.map(c => {

        const {id} = c

        return (

          <div key={id} className={styles['component-warpper']}>

          <div className={styles.component}>

            {getComponent(c)}

          </div>

        </div>

        )

      })}

      

    </div>

  );

};

  

export default EditCanvas;

点击组件选中效果

添加selectedId,点击时,修改当前选中组件id

import { createSlice , PayloadAction } from "@reduxjs/toolkit";

import { ComponentPropsType } from "../../components/QuestionComponents";

import {produce} from "immer";

  

//单个组件的信息

export type ComponentInfoType={

    // fe_id : string,//为什么是fe_id

    id: string

    type : string,

    title: string,

    props:ComponentPropsType

}

  
  

//redux存放组件列表

//1. 定义数据结构

export type ComponentsStateType={

    componentList:Array<ComponentInfoType>,

    selectedId:string

}

  

//2. 初始化

const INIT_STATE:ComponentsStateType={

    selectedId:'',

    componentList:[],

    //其他扩展

  

}

 export const componentsSlice = createSlice({

     name:"components",

     initialState:INIT_STATE,

     reducers:{

         //1. 重置所有组件

         //看不懂啊老铁!!!!

         resetComponentList:(state: ComponentsStateType , action: PayloadAction<ComponentsStateType>)=>{

             return action.payload

         },

  

         //2.修改选中的组件id

         //使用immer , 改进state不可变数据的写法

         changeSelctedId:produce((state: ComponentsStateType , action: PayloadAction<string>)=>{

             state.selectedId=action.payload

         })

  

     }

  

 })

  

//导出所有的actions

  

export const {resetComponentList} = componentsSlice.actions

  

export default componentsSlice.reducer

页面注册点击事件

 //点击选中组件

 function handleClick(id: string) {

   dispatch(changeSelctedId(id))

 }

点击后改变样式

classsNames css样式的拼接

import classNames from 'classnames'; // 这个是实现css样式的拼接


  {componentList.map(c => {

        const {id} = c

  

        //拼接classname

        const defaultClassName=styles['component-warpper']

        const selectedClassName=styles.selected

        const wrapperClassName = classNames({

          [defaultClassName]: true,

          [selectedClassName]: id === selectedId

        })

  
  

        return (

          <div onClick={()=>{handleClick(id)}} key={id} className={wrapperClassName}>

          <div className={styles.component}>

            {getComponent(c)}

          </div>

        </div>

        )

      })}

在这里插入图片描述

点击空白处,取消选中效果

注意这里阻止冒泡的操作

在这里插入图片描述

默认初始加载时选择第一个组件

在这里插入图片描述

组件库

组件分组

left 布局搭建
选取 组件库中的 tabs组件
又提取出组件 componentlib

在这里插入图片描述

显示到组件库

在这里插入图片描述

点击,添加组件到画布

画布信息需要更新
画布信息存在Redux中

处理redux

在这里插入图片描述

页面中使用

在这里插入图片描述

组件属性面板

组件属性的配置

每个组件的属性不一样,单独配置

import React, {FC, useEffect} from "react";

import { QuestionInputPropsType } from "./interface";

//引入组件库

import {Form ,Input} from "antd";

  
  

const PropComponent:FC<QuestionInputPropsType> = (props:QuestionInputPropsType) => {

    const {title , placeholder} = props;

  

    return (

        <div>

            <Form

              layout="vertical"

              initialValues={{ title ,placeholder }}

              >

                 <Form.Item label="标题" name="title" rules={[{ required: true, message: '请输入标题' }]}>

                  <Input />

                 </Form.Item>

                 <Form.Item label="Placeholder" name="placeholder">

                  <Input/>

                 </Form.Item>

  

            </Form>

        </div>

    )

}

  

export default PropComponent;

画布中点击不同的组件时,监听变化,即时显示在右侧属性栏

在这里插入图片描述

组件配置中引入属性配置

在这里插入图片描述

属性面板显示组件属性

根据selectedID面板显示组件属性

在这里插入图片描述

在这里插入图片描述

onchange改变组件属性时,同步到Redux Store

改变组件属性时,统一交给上层的componentProops管理

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在redux store 中增加修改属性的方法

在这里插入图片描述

头部编辑栏

定义组件
页面中引入

import React , {FC} from "react";

import styles from "./EditHeader.module.scss";

import {

    Button,

    Space,

    Typography,

}from 'antd';

import { LeftOutlined } from "@ant-design/icons";

import { useNavigate } from "react-router-dom";

  

const {Title} = Typography;

  

const EditHeader:FC = ()=>{

    const nav = useNavigate()

    return (

        <div className={styles['header-wrapper']}>

          <div className={styles.header}>

            <div className={styles.left}>

                <Space>

                    <Button type="link" icon={<LeftOutlined></LeftOutlined>} onClick={()=>nav(-1)}>返回</Button>

                    <Title>问卷标题</Title>

                </Space>

            </div>

            <div className={styles.main}></div>

            <div className={styles.right}>

                <Space>

                    <Button>保存</Button>

                    <Button>发布</Button>

                </Space>

            </div>

          </div>

        </div>

    )

  

}

  

export default EditHeader;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/596032.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

1W 3KVDC 隔离双输出 DC/DC 电源模块 ——TPD 系列

TPD系列提供双独立输出电压&#xff0c;并且两组电压可以不同&#xff0c;这样就节省一个电源模块&#xff0c;特别适合一块板上有多个不同电压要求的设计&#xff0c;而外形尺寸和TPA一样&#xff0c;工作温度范围广-40℃到 105℃。

【go项目01_学习记录05】

学习记录 1 依赖管理 Go Modules1.1 弃用 $GOPATH1.2 Go Modules 日常使用1.2.1 初始化生成go.mod文件1.2.2 Go Proxy代理1.2.3 go.mod文件查看1.2.4 go.sum文件查看1.2.5 indirect 含义1.2.6 go mod tidy 命令1.2.7 清空 Go Modules 缓存1.2.8 下载依赖1.2.9 所有 Go Modules …

sip转webrtc方案

技术选型 由于很多企业会议协议用的主要是webrtc&#xff0c;但是项目上很多时候的一些旧设备只支持sip协议&#xff0c;并不支持webrtc协议。所以sip和webrtc的相互转换就很有必要。 流媒体服务mediasoup本身并不支持sip协议。那么如何实现sip转webrtc呢&#xff1f; 根据调研…

攻防世界-xff-referer

题目信息 分析过程 显示ip必须为123.123.123.123&#xff0c;则进行伪造 解题过程 打开repeator 提示必须来自https://www.google.com&#xff0c;则再次构造Referer 相关知识 x-forwarded-for 和 referer的区别: x-forwarded-for 用来证明ip的像是“127.0.0.1”这种&a…

迭代器解释(C++)

一、什么是迭代器 为了提高C编程的效率&#xff0c;STL&#xff08;Standard Template Library&#xff09;中提供了许多容器&#xff0c;包括vector、list、map、set等。然而有些容器&#xff08;vector&#xff09;可以通过下标索引的方式访问容器里面的数据&#xff0c;但是…

【论文泛读】如何进行动力学重构? 神经网络自动编码器结合SINDy发现数据背后蕴含的方程

这一篇文章叫做 数据驱动的坐标发现与方程发现算法。 想回答的问题很简单&#xff0c;“如何根据数据写方程”。 想想牛顿的处境&#xff0c;如何根据各种不同物体下落的数据&#xff0c;写出万有引力的数学公式的。这篇文章就是来做这件事的。当然&#xff0c;这篇论文并没有…

流畅的python-学习笔记_对象引用、可变性、垃圾回收

变量不是盒子 即变量是引用&#xff0c;而不是实际内存&#xff0c;多个标识赋值相同变量时&#xff0c;多余标识是引用 标识、相等性、别名 比较对象的值&#xff0c;is比较对象的id。实际调用对象的__eq__方法。is速度比快&#xff0c;因为is不能重载&#xff0c;省去了寻…

TypeScript学习日志-第十九天(namespace命名空间)

namespace命名空间 一、基本用法 namespace 所有的变量以及方法必须要导出才能访问&#xff0c;如图&#xff1a; 二、 嵌套 namespace 可以进行嵌套使用&#xff0c;如图&#xff1a; 它也必须需要导出才能访问 三、合并 当我们出现两个同名的 namespace 它就会合并这两…

4+1视图,注意区分类图与对象图

注意区分类图和对象图。对象图标记的是对象名&#xff0c;命名形式 对象名:类名&#xff0c;或者:类名。这里没有出现冒号&#xff0c;表示的是类图。 对象图(object diagram)。 对象图描述一组对象及它们之间的关系。对象图描述了在类图中所建立的事物实例的静态快照。和类图一…

创造未来知识管理新篇章:Ollama与AnythingLLM联手打造个人与企业的安全知识库!

一 Ollama 1.1 简介 Ollama是一个开源的大型语言模型服务工具,它帮助用户快速在本地运行大模型。通过简单的安装指令,用户可以执行一条命令就在本地运行开源大型语言模型,如Llama 2。Ollama极大地简化了在Docker容器内部署和管理LLM的过程,使得用户能够快速地在本地运行大…

软件测试,软件评测师

如果你想考软件评测师证书&#xff0c;那这篇文章可以帮你少走很多弯路&#xff0c;估计你用别人一半的时间备考就可以通过考试&#xff0c;以下为本人亲身经验哈&#xff0c;你可以先收藏后看哦&#xff0c;提前祝你考试过过过。 如果以后想从事一份软件测试工程师的工作&…

浅析扩散模型与图像生成【应用篇】(二十一)——DALLE·2

21. Hierarchical Text-Conditional Image Generation with CLIP Latents 该文提出一种基于层级式扩散模型的由文本生成图像的方法&#xff0c;也就是大名鼎鼎的DALLE2。在DALLE2之前呢&#xff0c;OpenAI团队已经推出了DALLE和GLIDE两个文生图模型了&#xff0c;其中DALLE是基…

fabric部署调用合约示例

一 打包智能合约 ①进入fabric-samples文件夹下的chaincode/fabcar/go目录下执行 GO111MODULEon go mod vendor下载依赖&#xff08;文件夹下已经有go.mod&#xff0c;不需要使用go mod init生成该module文件&#xff09;②进入到test-network文件下使用以下命令将二进制文件…

2002-2021年各地区平均受教育年限数据(分性别)(含原始数据+计算过程+计算结果)

2002-2021年各地区平均受教育年限数据&#xff08;分性别&#xff09;&#xff08;含原始数据计算过程计算结果&#xff09; 1、时间&#xff1a;2002-2021年 2、来源&#xff1a;国家统计局、统计年鉴、各省年鉴 3、指标&#xff1a;行政区划代码、地区、年份、人均受教育年…

Footprint Analytics 与 Core Chain 达成战略合作

​ 领先的区块链数据解决方案提供商 Footprint Analytics 与比特币驱动、EVM 兼容的 Layer 1 区块链 Core Chain 宣布达成战略合作。此次合作旨在将 Footprint Analytics 的前沿数据解决方案与 Core Chain 的区块链基础设施相结合&#xff0c;共同引领区块链领域的创新发展。 …

苹果挖走大量谷歌人才,建立神秘人工智能实验室;李飞飞创业成立「空间智能」公司丨 RTE 开发者日报 Vol.197

开发者朋友们大家好&#xff1a; 这里是 「RTE 开发者日报」 &#xff0c;每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE&#xff08;Real Time Engagement&#xff09; 领域内「有话题的 新闻 」、「有态度的 观点 」、「有意思的 数据 」、「有思考的 文…

Apache.commons.lang3 的 isNumber 将会在 lang 4 的时候丢弃

在判断输入的字符串是不是一个数字的时候&#xff0c;我们通常用的最多的方法就是 &#xff1a; NumberUtils.isNumber("12"); 但是这个方法将会在 Lang 4.0 版本中被丢弃。 可以使用的替代方法为&#xff1a;isCreatable(String) 通过查看源代码&#xff0c;我们…

【数据结构】有关环形链表题目的总结

文章目录 引入 - 快慢指针思考 - 快慢指针行走步数进阶 - 寻找环形链表的头 引入 - 快慢指针 141-环形链表 - Leetcode 关于这道题&#xff0c;大家可以利用快慢指针&#xff0c;一个每次走两步&#xff0c;一个每次走一步&#xff0c;只要他们有一次相撞了就代表说这是一个链…

Leetcode编程练习

面试题-消失的数字 . - 力扣&#xff08;LeetCode&#xff09; class Solution { public:void reverse(vector<int>& nums, int start, int end) {while (start < end) {swap(nums[start], nums[end]);start 1;end - 1;}}void rotate(vector<int>& …

python爬虫(一)之 抓取极氪网站汽车文章

极氪汽车文章爬虫 闲来没事&#xff0c;将极氪网站的汽车文章吃干抹尽&#xff0c;全部抓取到本地&#xff0c;还是有点小小的难度。不能抓取太快&#xff0c;太快容易被封禁IP&#xff0c;不过就算被封了问题也不大&#xff0c;大不了重启路由器&#xff0c;然后你的IP里面又…