文章目錄
- 1 開發兩個問卷組件
- 1.1 Title組件
- 1.2 Input組件
- 1.3 畫布靜態展示TItle和Input
- 2 Ajax獲取問卷數據,并存儲到Redux store
- 2.1 API接口
- 2.2 組件列表存儲到Redux store統一管理
- 2.3 重構useLoadQuestionData
- 3 在畫布顯示問卷列表,點擊可選中
- 3.1 Redux獲取組件列表
- 3.2 根據Redux組件列表渲染組件
- 3.3 點擊選擇組件,共享selectedId
- 關于
1 開發兩個問卷組件
1.1 Title組件
數據類型和默認值,interface.ts代碼如下:
export type QuestionTitlePropsType = {text?: string;level?: 1 | 2 | 3 | 4 | 5;isCenter?: boolean;
};export const QuestionTitleDefaultProps: QuestionTitlePropsType = {text: "一行標題",level: 1,isCenter: false,
};
組件Component.tsx代碼如下所示:
import { FC } from "react";
import { Typography } from "antd";
import { QuestionTitlePropsType, QuestionTitleDefaultProps } from "./interface";const { Title } = Typography;const QuestionTitle: FC<QuestionTitlePropsType> = (props: QuestionTitlePropsType,
) => {const {text = "",level = 1,isCenter = false,} = { ...QuestionTitleDefaultProps, ...props };const genFontSize = (level: number) => {if (level === 1) {return "24px";} else if (level === 2) {return "20px";} else if (level === 3) {return "16px";} else if (level === 4) {return "12px";} else {return "16px";}};return (<Titlelevel={level}style={{textAlign: isCenter ? "center" : "start",marginBottom: "0",fontSize: genFontSize(level),}}>{text}</Title>);
};export default QuestionTitle;
1.2 Input組件
數據類型和默認值,interface.ts代碼如下:
export type QuestionInputPropsType = {title?: string;placeholder?: string;
};export const QuestionInputDefaultProps: QuestionInputPropsType = {title: "輸入框標題",placeholder: "請輸入...",
};
組件Component.tsx代碼如下所示:
import { FC } from "react";
import { Typography, Input } from "antd";
import { QuestionInputPropsType, QuestionInputDefaultProps } from "./interface";const { Paragraph } = Typography;const QuestionInput: FC<QuestionInputPropsType> = (props: QuestionInputPropsType,
) => {const { text = "", placeholder = "" } = {...QuestionInputDefaultProps,...props,};return (<div><Paragraph strong>{text}</Paragraph><div><Input placeholder={placeholder}></Input></div></div>);
};export default QuestionInput;
1.3 畫布靜態展示TItle和Input
組件EditCanvas.tsx代碼如下:
import { FC } from "react";
import styles from "./EditCanvas.module.scss";import { Spin } from "antd";import QuestionTitle from "@/components/QuestionComponents/QuestionTitle/Component";
import QuestionInput from "@/components/QuestionComponents/QuestionInput/Component";type PropsType = {loading: boolean;
};const EditCanvas: FC<PropsType> = ({ loading }) => {if (loading) {return (<div style={{ textAlign: "center", marginTop: "24px" }}><Spin /></div>);}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;
樣式EditCanvas.module.scss代碼如下:
.canvas {min-height: 100%;background-color: #fff;overflow: hidden;
}.component-wrapper {margin: 12px;border: 1px solid #fff;padding: 12px;border-radius: 3px;&:hover {border-color: #d9d9d9;}
}.component {pointer-events: none; // 屏蔽鼠標行為,組件不讓被點擊到
}
效果如下圖所示:
2 Ajax獲取問卷數據,并存儲到Redux store
2.1 API接口
獲取問卷詳情API接口如下:
{// 獲取問卷信息url: '/api/question/:id',method: 'get',response() {return {errno: 0,data: {id: Random.id(),title: Random.ctitle(),componentList: [{fe_id: Random.id(),type: 'questionTitle',title: '標題',props: {text: '個人信息調研',level: 1,isCenter: false}},{fe_id: Random.id(),type: 'questionInput',title: '輸入框',props: {text: '你的姓名',placeholder: '請輸入姓名...',}},{fe_id: Random.id(),type: 'questionInput',title: '輸入框',props: {text: '你的電話',placeholder: '請輸入電話...',}},]},}}}
2.2 組件列表存儲到Redux store統一管理
src/store/componentsReducer/index.ts代碼如下:
import { createSlice, PayloadAction } from "@reduxjs/toolkit";import { ComponentPropsType } from "@/components/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: "components",initialState: INIT_STATE,reducers: {// 重置所有組件resetComponents(state: ComponentsStateType,action: PayloadAction<ComponentsStateType>,) {return action.payload;},},
});export const { resetComponents } = componentsSlice.actions;
export default componentsSlice.reducer;
組件屬性類型src/components/QuestionComponents/index.ts代碼如下:
import { FC } from "react";import QuestionInputConf, { QuestionInputPropsType } from "./QuestionInput";
import QuestionTitleConf, { QuestionTitlePropsType } from "./QuestionTitle";// 各個組件屬性類型
export type ComponentPropsType = QuestionInputPropsType &QuestionTitlePropsType;// 統一組件配置
export type ComponentConfType = {title: string;type: string;Component: FC<ComponentPropsType>;defaultProps: ComponentPropsType;
};// 全部組件配置列表
const componentConfList: ComponentConfType[] = [QuestionInputConf,QuestionTitleConf,
];// 根據組件類型獲取組件
export function getComponentConfByType(type: string) {return componentConfList.find((c) => c.type === type);
}
2.3 重構useLoadQuestionData
代碼如下所示:
import { useParams } from "react-router-dom";
import { useRequest } from "ahooks";
import { getQuestionApi } from "@/api/question";
import { useEffect } from "react";
import { useDispatch } from "react-redux";
import { resetComponents } from "@/store/componentsReducer";/*** 獲取帶加載狀態的問卷信息* @returns loading狀態,問卷信息*/
function useLoadQuestionData() {const { id = "" } = useParams();const dispatch = useDispatch();// ajax 加載const { data, loading, error, run } = useRequest(async (id: string) => {if (!id) {throw new Error("沒有問卷 id");}const data = await getQuestionApi(id);return data;},{manual: true,},);// 根據獲取的data,設置storeuseEffect(() => {if (!data) {return;}const { componentList = [] } = data;// componentList 存入redux storedispatch(resetComponents({ componentList }));// eslint-disable-next-line react-hooks/exhaustive-deps}, [data]);// 根據id變化,加載問卷數據useEffect(() => {run(id);// eslint-disable-next-line react-hooks/exhaustive-deps}, [id]);return { loading, error };
}export default useLoadQuestionData;
3 在畫布顯示問卷列表,點擊可選中
3.1 Redux獲取組件列表
自定義hook-useGetComponentInfo.ts 代碼如下所示:
import { StateType } from "@/store";
import { useSelector } from "react-redux";
import { ComponentsStateType } from "@/store/componentsReducer";function useGetComponentInfo() {const components = useSelector<StateType>((state) => state.components,) as ComponentsStateType;const { componentList = [] } = components;return { componentList };
}export default useGetComponentInfo;
3.2 根據Redux組件列表渲染組件
EditCanvas.tsx代碼如下所示:
import { FC } from "react";
import styles from "./EditCanvas.module.scss";import { Spin } from "antd";// import QuestionTitle from "@/components/QuestionComponents/QuestionTitle/Component";
// import QuestionInput from "@/components/QuestionComponents/QuestionInput/Component";
import useGetComponentInfo from "@/hooks/useGetComponentInfo";
import { getComponentConfByType } from "@/components/QuestionComponents";
import { ComponentInfoType } from "@/store/componentsReducer";type PropsType = {loading: boolean;
};function genComponent(componentInfo: ComponentInfoType) {const { type, props } = componentInfo;const componentConf = getComponentConfByType(type);if (componentConf == null) {return null;}const { Component } = componentConf;return <Component {...props} />;
}const EditCanvas: FC<PropsType> = ({ loading }) => {const { componentList = [] } = useGetComponentInfo();if (loading) {return (<div style={{ textAlign: "center", marginTop: "24px" }}><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}>{genComponent(c)}</div></div>);})}{/* <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;
3.3 點擊選擇組件,共享selectedId
componentsReducer/index.ts 添加selectId及修改,代碼如下:
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { produce } from "immer";
import { ComponentPropsType } from "@/components/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: "components",initialState: INIT_STATE,reducers: {// 重置所有組件resetComponents(state: ComponentsStateType,action: PayloadAction<ComponentsStateType>,) {return action.payload;},// 切換選中組件changeSelectedId: produce((draft: ComponentsStateType, action: PayloadAction<string>) => {draft.selectedId = action.payload;}),},
});export const { resetComponents, changeSelectedId } = componentsSlice.actions;
export default componentsSlice.reducer;
新增選中 css 樣式,EditCanvas.module 新增代碼如下所示:
.selected {border-color: #1890ff !important;
}
組件點擊選擇,點擊畫布空白處取消,阻止默認冒泡行為,EditCanvas.tsx代碼如下:
import { FC } from "react";
import styles from "./EditCanvas.module.scss";import { Spin } from "antd";// import QuestionTitle from "@/components/QuestionComponents/QuestionTitle/Component";
// import QuestionInput from "@/components/QuestionComponents/QuestionInput/Component";
import useGetComponentInfo from "@/hooks/useGetComponentInfo";
import { getComponentConfByType } from "@/components/QuestionComponents";
import { ComponentInfoType } from "@/store/componentsReducer";
import { useDispatch } from "react-redux";
import classNames from "classnames";
import { changeSelectedId } from "@/store/componentsReducer";type PropsType = {loading: boolean;
};function genComponent(componentInfo: ComponentInfoType) {const { type, props } = componentInfo;const componentConf = getComponentConfByType(type);if (componentConf == null) {return null;}const { Component } = componentConf;return <Component {...props} />;
}const EditCanvas: FC<PropsType> = ({ loading }) => {const { componentList = [], selectedId } = useGetComponentInfo();const dispatch = useDispatch();// 獲取當前選中的組件function handleClick(e: React.MouseEvent<HTMLDivElement>, fe_id: string) {e.stopPropagation();dispatch(changeSelectedId(fe_id));}if (loading) {return (<div style={{ textAlign: "center", marginTop: "24px" }}><Spin /></div>);}return (<div className={styles.canvas}>{componentList.map((c) => {const { fe_id } = c;// 拼接 class nameconst 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}>{genComponent(c)}</div></div>);})}{/* <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;
默認selectId為組件列表第一個,沒有不選中,useLoadQuestionData.ts代碼如下所示:
import { useParams } from "react-router-dom";
import { useRequest } from "ahooks";
import { getQuestionApi } from "@/api/question";
import { useEffect } from "react";
import { useDispatch } from "react-redux";
import { resetComponents } from "@/store/componentsReducer";/*** 獲取帶加載狀態的問卷信息* @returns loading狀態,問卷信息*/
function useLoadQuestionData() {const { id = "" } = useParams();const dispatch = useDispatch();// ajax 加載const { data, loading, error, run } = useRequest(async (id: string) => {if (!id) {throw new Error("沒有問卷 id");}const data = await getQuestionApi(id);return data;},{manual: true,},);// 根據獲取的data,設置storeuseEffect(() => {if (!data) {return;}const { componentList = [] } = data;// 獲取默認的 selectedIdlet selectedId = "";if (componentList.length > 0) {selectedId = componentList[0].fe_id;}// componentList 存入redux storedispatch(resetComponents({ componentList, selectedId }));// eslint-disable-next-line react-hooks/exhaustive-deps}, [data]);// 根據id變化,加載問卷數據useEffect(() => {run(id);// eslint-disable-next-line react-hooks/exhaustive-deps}, [id]);return { loading, error };
}export default useLoadQuestionData;
效果如下圖所示:
關于
?QQ:806797785
??倉庫地址:https://gitee.com/gaogzhen
??倉庫地址:https://github.com/gaogzhen
[1]react官網[CP/OL].
[2]Redux官網[CP/OL].