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

目标

问卷编辑器的开发

设计UI - 拆分布局

水平垂直居中

画布 y方向滚动

自定义问卷组件

后端 返回组件数据

js 复制代码
 //获取单个问卷信息

    {

        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 存储后端返回组件数据

切片

js 复制代码
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

js 复制代码
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// 存储问卷组件列表的数据

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

  
  

    // 存储问卷信息数据

  }

})

发请求时存储数据

js 复制代码
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获取数据

js 复制代码
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页面

js 复制代码
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

js 复制代码
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

页面注册点击事件

js 复制代码
 //点击选中组件

 function handleClick(id: string) {

   dispatch(changeSelctedId(id))

 }

点击后改变样式

classsNames css样式的拼接

js 复制代码
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

页面中使用

组件属性面板

组件属性的配置

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

js 复制代码
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 中增加修改属性的方法

头部编辑栏

定义组件

页面中引入

js 复制代码
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;
相关推荐
DanCheng-studio5 小时前
毕设 基于机器视觉的驾驶疲劳检测系统(源码+论文)
python·毕业设计·毕设
利刃大大16 小时前
【在线五子棋对战】二、websocket && 服务器搭建
服务器·c++·websocket·网络协议·项目
crary,记忆1 天前
微前端 - Module Federation使用完整示例
前端·react·angular
aiguangyuan1 天前
浅谈 React Hooks
react·前端开发
whatever who cares3 天前
React hook之userReducer
react.js·react
aiguangyuan3 天前
React Hooks 基础指南
react·前端开发
aiguangyuan4 天前
React 项目初始化与搭建指南
react·前端开发
aiguangyuan4 天前
React 组件异常捕获机制详解
react·前端开发
aiguangyuan4 天前
深入理解 JSX:React 的核心语法
react·前端开发
aiguangyuan5 天前
React 基础语法
react·前端开发