React前端项目实战

1,531 阅读10分钟

前言

前面介绍了 React前端 项目的构建,这这里实战一下小项目,分别是 javascripttypescript 版本的各一个,就只讲解一个 typescript 的版本,JavaScript 相信很容易看明白

这里的项目实战介绍,用的是 typescript 测试案例,和 javascript的测试案例一样,javascript的仅仅比 typescript的少了类型代码

自己根据需要选择使用,为了项目的易读性,且避免一些编译器就能排除的错误,推荐使用typescript编写项目

另外,typescript 就是类型化的 javascript,很容易上手,不要担心太多

----javascript测试案例

----typescript测试案例

----react-router-dom参考文章

----flex布局参考文章

:本篇文章适会 flex布局,会React,但是没项目开发过的人入门开发项目参考用的

项目实战

先看一下效果图(以前做的一个小项目删减改编,有点丑😂)

DataImage3719065492.jpg

这篇文章中用了 React-ComponentReact-hooks两种方式创建组件,以便与自己选择,通常交互比较多,需要用到组件多个生命周期的,直接使用 React-Component 即可,其他情况两者都行,不过个人感觉 React-hooks 平时使用要简单方便一些

这里主要是项目展示,介绍比较粗糙,主要是给一个案例代码,推荐下载下来查看

首页

从上面的图可以看到,首页布局分为 顶部、左侧、右侧三部分,由于顶部比较简单,就写到首页布局中了,而左右两侧,分别写到了布局组件中,且分别以 React-componentReact-hooks的方式辨别

首页的布局如下所示

import { useEffect, useState } from 'react'; //react中自带的 hooks
import {toggleFull} from 'be-full'; //切换屏幕框架
import LeftView from './leftView';
import RightView from './rightVIew';
import { useNavigate } from 'react-router-dom'; //react-router-dom路由框架,可查看前言链接

//这个是声明的list的内部item类型,js中并不需要
export interface listType {
    id: number
    title: string
    text: string
    number: number
    unit: string
    status: number
}

function HomeView() {
    //前面两个分别是获取和设置属性的方法,可以用更新UI和更新数据
    //useState后面的是变量类型以及赋初值
    const [isLoading, setIsLoading] = useState<boolean>(true);
    const [list, setList] = useState<Array<listType>>([{
        id: 0,
        title: '斗罗大陆',
        text: '累计观看:',
        number: 70,
        unit: '亿次',
        status: 1, //0下架 1热播中 2等待上映
    }]);
    
    //这个是声明的路由,用于跳转路由
    const navigate = useNavigate()

    //这个相当于 componentDidMount,后面参数得设置空集合
    useEffect(() => {
        setTimeout(() => {
            setIsLoading(false)
        }, 1500);
    }, []) //这个集合中,如果放了某一个状态变量,当变量改变时会回调这个useEffect

    //进入详情页,通过路由,跳转到指定path
    const enterDetail = () => {
        navigate('/home/detail')
    }

    //react UI 节点布局
    return (<div>{
        isLoading ? (
            //loading gif加载中的代码,外层是flex布局,为了让gif图片窗口居中
            <div 
                style={{ 
                    position: 'absolute', 
                    width: '100vw', 
                    height: '100vh', 
                    display: `${isLoading ? 'flex' : 'none'}`, 
                    flexDirection: 'row', 
                    alignItems: 'center', 
                    justifyContent: 'center', 
                    background: '#000000'
                }}
                //加载gif图片
                <img alt='' src="../currency_loading.gif" style={{ width: '50%' }} />
            </div>
        ) : (
            <div style={{userSelect: 'none', pointerEvents: 'none'}}>
                //左侧布局组件,后面是props传参
                <LeftView list={list} />

                //右侧布局组件,其中的参数是React中的属性
                <RightView 
                    list={list} 
                    updateListCallback={(list) => {
                        setList(list)
                    }} 
                    enterDetailCallback={enterDetail}
                    />

                //顶部布局文件,也是一个绝对布局 + flex
                <div 
                    onDoubleClick={() => {
                        toggleFull() //切换全屏或者恢复,使用了 be-full框架
                    }}
                    style={{
                        width: '100%',
                        position: 'absolute',
                        backgroundImage: 'linear-gradient(to bottom, #000000DD, #000000BB)',
                        height: 60,
                        top: 0,
                        left: 0,
                        pointerEvents: 'auto',
                        display: 'flex',
                        justifyContent: 'center',
                        alignItems: 'center'
                    }}
                >
                    <div style={{color: '#FFF'}}>奇葩电影控制台</div>
                </div>
            </div>)
        }
    </div>)
}

export default HomeView

设置背景图片

底部空白,需要设置图片的话可以通过设置背景图片的方式来解决

//url 布局位置上下、左右 固定方式默认fixed 覆盖方式cover最短方向填充满  no-repeat不重复
background: `url(${process.env.PUBLIC_URL}/home_bkg.png) center center / cover no-repeat`

左侧组件Component

左侧信息栏使用的是常见的 React-component组件,拥有 React 组件的正常生命周期,如下所示

//设置接口 props传递参数的类型
interface LeftProps {
    list: Array<listType> //如果想加入回调,直接放一个Function类型的callback属性接口即可
}

//后面需要实现LeftProps接口,以便于访问,跟其他语言的很像
export default class LeftView extends React.Component<LeftProps> {
    state = {
        list: this.props.list
    }

    //组件加载完毕时
    componentDidMount() {
        console.log('组件加载完毕了,在这里可以做组件自身内容')
    }

    //组件更新时
    componentDidUpdate(prevProps: Readonly<LeftProps>, prevState: Readonly<{}>, snapshot?: any) {
        if (prevProps.list === this.props.list) return
        
        //通过setState方法,更新 state 状态机中的参数,可以触发list所在节点重新渲染
        this.setState({
            list: this.props.list
        })
    }

    //组件将要卸载时
    componentWillUnmount() {
        console.log('组件即将被释放')
    }
    
    //下面是一个常见的flex布局
    render() {
        const {list} = this.props
        return (
            <div style={{
                position: 'absolute',
                backgroundColor: "#000000BB",
                top: 60,
                left: 0,
                display: 'flex',
                flexDirection: 'column',
                height: "100%",
                minWidth: 370,
                userSelect: 'none',
                pointerEvents: 'auto' //设置点击事件如果父节点关闭子节点需要开启此属性可响应点击事件
            }}>
                <div
                    style={{
                        marginTop: 20,
                        marginLeft: 24,
                        marginRight: 30,
                        display: 'flex',
                        height: "90%",
                        overflowY: 'auto'
                    }}>
                    <div style={{
                        display: 'flex',
                        minHeight: 940,
                        alignItems: 'flex-end'
                    }}>
                        //布局列表,看了布局可能会觉得,一个集合横着罗列不就可以了么,确实可以
                        //这里只是当时方便解决右侧数字文字过大,向上偏移的问题,有些电脑没事,这里有改动
                        //也间接说明了,同样的效果,有多种布局方式,或者解决方案
                        <div style={{ display: 'flex', flexDirection: 'column' }}>
                            {
                                list && list.length > 0 &&
                                list.map((item, index) => {
                                    return (
                                        <div 
                                            key={index}
                                            style={{
                                                display: 'flex', 
                                                flexDirection: 'column', 
                                                justifyContent: 'flex-end', 
                                                marginBottom: 14, 
                                                height: 80, 
                                        }}>
                                            <div style={{ fontSize: 24, color: '#A7FAFF' }}>
                                                {item.title}</div>
                                            <div style={{ fontSize: 17, color: '#FFF',
                                                marginTop: 13 , marginBottom: 8}}>
                                                {item.text}</div>
                                        </div>
                                    )
                                })
                            }
                        </div>
                        //这里是右侧的数字和单位文字
                        <div style={{ display: 'flex', flexDirection: 'column',
                            minWidth: 180, alignItems: 'flex-end' }}>
                            {
                                list && list.length > 0 &&
                                list.map((item, index) => {
                                    return (
                                        <div 
                                            key={index}
                                            style={{ 
                                                marginLeft: 4, 
                                                display: 'flex', 
                                                alignItems: 'flex-end', 
                                                marginBottom: 13, 
                                                height: 80
                                            }}>
                                            <div style={{ color: '#50E7F9', 
                                                fontSize: 64, fontFamily: 'LcdD' }}>
                                                {item.number}</div>
                                            <div style={{ color: '#FFF', fontSize: 17, 
                                                marginLeft: 6, marginBottom: 8}}>
                                                {item.unit}</div>
                                        </div>
                                    )
                                })
                            }
                        </div>
                    </div>
                </div>
            </div>
        )
    }
}

右侧组件Hooks

右侧组件采用了 React-hooks,组件代码比较长,贴出一部分介绍,推荐下载下来代码观看

使用了 hooks 之后,会发现里面不使用 this了,相当于访问的都是自己函数内部的方法,使用很方便

//设置属性,和component一样,这里同时设置了回调
interface RightProps {
    list: Array<listType>
    updateListCallback?: (list: Array<listType>) => any
    enterDetailCallback?: Function //可以理解任意类型的函数
}

//传递属性的方式如下所示,和component不一样了
const RightView = (props: RightProps) => {
    //通过 useState来触发组件渲染
    const [list, setList] = useState<Array<listType>>(props.list)
    const [isShowSimulate, setIsShowSimulate] = useState<boolean>(true)

    //react-router-dom中跳转路由使用
    const navigate = useNavigate()

    useEffect(() => {
        console.log('组件加载完毕了,在这里可以做组件自身内容')
    }, [])

    //当 props发生改变时,更新内容
    useEffect(() => {
        //更新list
        setList(props.list)
    }, [props.list])

    //这里只介绍UI是哪里的
    return (
        //外层布局,控制整体位置
        <div
            style={{
                position: 'absolute',
                background: "#000000BB",
                top: 60,
                right: 0,
                display: 'flex',
                flexDirection: 'column',
                alignItems: 'flex-end',
                height: "100%",
                width: 360,
                userSelect: 'none',
                pointerEvents: 'auto',
            }}>
            //设置内部滚动效果
            <div
                style={{
                    height: "90%",
                    overflowY: 'auto'
                }}>
                <div style={{
                    marginRight: 24,
                    display: 'flex',
                    alignItems: 'flex-end',
                    flexDirection: 'column',
                }}>
                    //设备控制模块代码
                    <div style={{
                        display: 'flex',
                        width: 300,
                        height: 40,
                        marginTop: 20,
                        alignItems: 'center',
                        backgroundImage: 'linear-gradient(to right, rgba(28, 83, 112, 1), rgba(28, 83, 112, 0))'
                    }}>
                        <div style={{ fontSize: 24, fontWeight: 'bold', color: '#FFF', marginLeft: 16 }}>设备控制</div>
                    </div>
                    <div style={{
                        display: "flex",
                        marginTop: 20
                    }}>
                        //下架按钮
                        <div
                            onClick={() => {
                                let newList = list.map(item => {
                                    item.status = 0
                                    return item
                                })
                                setList(newList)
                                props.updateListCallback && props.updateListCallback(newList)
                            }}
                            style={{
                                cursor: "pointer",
                                width: 114,
                                height: 40,
                                marginRight: 16,
                                backgroundImage: 'linear-gradient(to bottom, #A7FAFF, #0EBBBE)',
                                display: 'flex',
                                justifyContent: 'center',
                                alignItems: 'center'
                            }}>
                            <div style={{ fontSize: 20, color: '#0B1017' }}>一键下架</div>
                        </div>
                        //上架按钮
                        <div
                            onClick={() => {
                                let newList = list.map(item => {
                                    item.status = 1
                                    return item
                                })
                                setList(newList)
                                props.updateListCallback && props.updateListCallback(newList)
                            }}
                            style={{
                                width: 114,
                                height: 40,
                                cursor: "pointer",
                                backgroundImage: 'linear-gradient(to bottom, #A7FAFF, #0EBBBE)',
                                display: 'flex',
                                justifyContent: 'center',
                                alignItems: 'center'
                            }}>
                            <div style={{ fontSize: 20, color: '#0B1017' }}>一键上架</div>
                        </div>
                    </div>
                    
                    //开关顶部说明代码
                    <div style={{
                        display: 'flex',
                        marginTop: 20,
                        marginBottom: 6
                    }}>
                        <div style={{
                            display: 'flex',
                            marginRight: 40,
                            justifyContent: 'flex-end',
                            alignItems: 'center'
                        }}>
                            <div style={{ color: '#FFF', fontSize: 20, 
                                fontWeight: 'bold' }}>机器名称</div>
                        </div>
                        <div style={{
                            display: 'flex',
                            width: 160,
                            height: 40,
                            justifyContent: 'center',
                            alignItems: 'center'
                        }}>
                            <div style={{ color: '#FFF', fontSize: 20, 
                                fontWeight: 'bold' }}>运行开关</div>
                        </div>
                    </div>
                    //开关代码,通过集合纵向展开
                    //可以发现使用 hooks 渲染不需要this.state了
                    {
                        list && list.length > 0 &&
                        list.map((item, index) => {
                            return (
                                <div
                                    key={index}
                                    style={{
                                        display: 'flex',
                                        alignItems: 'center',
                                    }}>
                                    <div style={{
                                        display: 'flex',
                                        justifyContent: 'flex-end',
                                        alignItems: 'center',
                                        height: 44,
                                        marginRight: 40,
                                    }}>
                                        {
                                            item.status > 1 &&
                                            <div style={{
                                                display: 'flex',
                                                flexDirection: 'row',
                                                marginRight: 9,
                                            }}>
                                                {
                                                    item.status > 1 &&
                                                    <img
                                                        alt=''
                                                        src={`../icon_${item.status === 0?'warning':'error'}.png`}
                                                        style={{
                                                            width: 22,
                                                            height: 22
                                                        }} />
                                                }
                                                {
                                                    item.status === 2 &&
                                                    <div
                                                        onClick={() => {
                                                            const opItem = list[index]
                                                            opItem.status = 1
                                                            let newList = list.map(item => item) //更新一次才能自动更新list
                                                            setList(newList)
                                                            props.updateListCallback && props.updateListCallback(newList)
                                                        }}
                                                        style={{
                                                            cursor: "pointer",
                                                            color: '#FF4646',
                                                            fontSize: 18,
                                                            height: 28,
                                                            marginLeft: 6,
                                                            marginBottom: -4,
                                                            borderBottom: '1px solid #FF4646'
                                                        }}></div>
                                                }
                                            </div>
                                        }
                                        <div 
                                            style={{
                                                color: item.status === 2 ? '#FF4646' : item.status === 3 ? '#FFC600' : '#FFF', 
                                                fontSize: 18,
                                                marginRight: 10 
                                        }}>{item.title}</div>
                                    </div>

                                    <div
                                        onClick={() => {
                                            const opItem = list[index]
                                            if (opItem.status === 2) return //等待上映无法操作

                                            opItem.status = opItem.status === 1 ? 0 : 1
                                            let newList = list.map(item => item) //更新一次才能自动更新list
                                            setList(newList)
                                            props.updateListCallback && props.updateListCallback(newList)
                                        }}
                                        style={{
                                            cursor: "pointer",
                                            border: '2px solid #526283',
                                            borderRadius: '4px',
                                            display: 'flex',
                                            width: 160,
                                            height: 32,
                                        }}>
                                        <div style={{
                                            display: 'flex',
                                            marginTop: -2,
                                            marginLeft: -2,
                                            border: item.status === 1 ? '2px solid #54E9FC' : '',
                                            borderRadius: '4px 0 0 4px',
                                            width: 80,
                                            height: 32,
                                            justifyContent: 'center',
                                            alignItems: 'center',
                                        }}>
                                            <div style={{color: item.status === 1 ? '#54E9FC' : '#8394B6', fontSize: 18}}>上架</div>
                                        </div>
                                        <div style={{
                                            marginLeft: -2,
                                            marginTop: -2,
                                            marginRight: -2,
                                            display: 'flex',
                                            border: item.status === 1 ? '' : '2px solid #54E9FC',
                                            borderRadius: '0 4px 4px 0',
                                            width: 80,
                                            height: 32,
                                            justifyContent: 'center',
                                            alignItems: 'center'
                                        }}>
                                            <div style={{color: item.status === 1 ? '#8394B6' : '#54E9FC', fontSize: 18}}>下架</div>
                                        </div>
                                    </div>
                                </div>
                            )
                        })
                    }

                    //测试操控菜单
                    <div style={{
                        display: 'flex',
                        width: 300,
                        height: 40,
                        marginTop: 10,
                        alignItems: 'center',
                        justifyContent: 'space-between',
                        backgroundImage: 'linear-gradient(to right, rgba(28, 83, 112, 1), rgba(28, 83, 112, 0))'
                    }}>
                        <div style={{ fontSize: 24, fontWeight: 'bold', color: '#FFF', marginLeft: 16 }}>模拟测试</div>
                        <img
                            alt=''
                            onClick={() => {
                                setIsShowSimulate(!isShowSimulate)
                            }}
                            src={`../arrow_${isShowSimulate ? 'up' : 'down'}.png`}
                            style={{
                                cursor: "pointer",
                                width: 24,
                                height: 24,
                                marginRight: 10
                            }} />
                    </div>
                    //通过isShowSimulate控制底部是否显示,为false则释放底部view
                    {
                        isShowSimulate &&
                        <div style={{
                            display: "flex",
                            flexDirection: 'column',
                            marginTop: 10
                        }}>
                            <div
                                onClick={() => {
                                    props.enterDetailCallback && props.enterDetailCallback()
                                }}
                                style={{
                                    cursor: "pointer",
                                    width: 250,
                                    height: 40,
                                    marginRight: 25,
                                    backgroundImage: 'linear-gradient(to bottom, #A7FAFF, #0EBBBE)',
                                    display: 'flex',
                                    justifyContent: 'center',
                                    alignItems: 'center'
                                }}>
                                <div style={{ fontSize: 20, color: '#0B1017' }}>进入详情页</div>
                            </div>
                            <div
                                onClick={() => {
                                    navigate('/other/mine')
                                    // navigate('other/mine') //如果不加'/'.则是默认在当前页后面拼接这个路径
                                }}
                                style={{
                                    cursor: "pointer",
                                    width: 250,
                                    height: 40,
                                    marginTop: 10,
                                    marginRight: 25,
                                    backgroundImage: 'linear-gradient(to bottom, #A7FAFF, #0EBBBE)',
                                    display: 'flex',
                                    justifyContent: 'center',
                                    alignItems: 'center'
                                }}>
                                <div style={{ fontSize: 20, color: '#0B1017' }}>跳转到个人页</div>
                            </div>
                            <div
                                onClick={() => {
                                    toggleFull()
                                }}
                                style={{
                                    cursor: "pointer",
                                    width: 250,
                                    height: 40,
                                    marginRight: 25,
                                    marginTop: 10,
                                    backgroundImage: 'linear-gradient(to bottom, #A7FAFF, #0EBBBE)',
                                    display: 'flex',
                                    justifyContent: 'center',
                                    alignItems: 'center'
                                }}>
                                <div style={{ fontSize: 20, color: '#0B1017' }}>切换全屏</div>
                            </div>
                        </div>
                    }
                </div>
            </div>
        </div>
    )
}

export default RightView

APP页面react-router-dom

一个项目的入口则是这个 App 页面,这里通过路由 Router 的方式控制整个项目的跳转以及显示

APP的代码,路由的设置如下所示

import HomeView from './home/homeView';
import DetailView from './detail/detailView';
import MineView from './other/mine';
import HomeDetailView from './home/homeDetailVIew';
//Browser与浏览器互动,在网址栏的地方显示路径,MemoryRouter不显示路径
import { BrowserRouter, MemoryRouter, Routes, Route } from "react-router-dom"

function App() {
    return (
        // <MemoryRouter>
        <BrowserRouter>
            <Routes>
                {/* 默认进入页面 */}
                <Route path="/" element={<HomeView />} /> 
                //设置父子路由节点,进入detail即 /home/detail 节点
                <Route path="/home" element={<HomeView />} >
                    <Route path="detail" element={<HomeDetailView />} />
                </Route>
                
                <Route path="/detail" element={<DetailView />} />

                <Route path="/other" >
                    <Route path="mine" element={<MineView />} />
                </Route>

                {/* 可以匹配全路径,找不到的时候就走着一个,可以用于匹配404 */}
                {/* <Route path='*' element={NotFound}></Route> */}
            </Routes>
        </BrowserRouter>
        // </MemoryRouter>
    )
}

export default App;

路由跳转

路由的跳转 navigate

mport { Link, useNavigate } from "react-router-dom"

const MineView = () => {
    const navigate = useNavigate()

    return (
        <div
            style={{
                background: "#000000BB",
                display: 'flex',
                flexDirection: 'column',
                alignItems: 'center',
                width: '100%',
                height: '100',
                userSelect: 'none',
                pointerEvents: 'auto',
            }}>
            <div style={{fontSize: 48, marginTop: 20}}>我是个人页的标题</div>

            <Link to='/home'>通过Link跳转到home</Link>

            <Link to='/home/detail'>通过Link跳转到通用detail</Link>

            <div 
                onClick={() => {
                    navigate('/home')
                    
                    //使用全名进入指定路由,路径相对于域名,推荐使用全名
                    //navigate('/home')

                    //如果不加'/'.则是默认在当前页后面拼接这个路径,即相当于子路径页面
                    // navigate('home')执行这句相当与进入 .../other/mine/home,路径就错了
                    //如果在 home 页面跳转 detail,则没问题,相当于 .../home/detail
                }}
                style={{
                    fontSize: 18, 
                    marginTop: 20
                }}
                >通过navigate,我也可以跳转到home页</div>
        </div>
    )
}

const navigate = useNavigate()

传递值与导航路由带参

方式如下所示

//只需要在后面拼接?id=1即可,key=value,多个与get请求一样,用&隔开 ?key1=value1&key2=value2
http://localhost:3000/detail?id=1

如果使用的是react

,接收路由带参路径传递过来的值
query-string框架
//获取参数
let obj = queryString.parse(window.location.search)
obj?.id  //即id

如果使用的是hooks

const params = useSearchParams()[0]
params?.id //即id

监听路由变化

我们需要引出 history 就可以监听了

//创建历史对象
let history = createBrowserHistory()

//声明 pathname 用于更新页面
const [pathname, setPathname] = useState<any>()

//初次进入更新pathname,并且监听 pathname 变化,以便于路由和页面效果相匹配
setPathname(window.location.pathname)
history.listen((res) => {
  setPathname(res.location.pathname)
})

//页面点击跳转后,在 replace 我们的历史,即可实现保存历史,点击返回箭头就可以监听到了
setPathname(item.pathname)
navigate(item.pathname)
history.replace(item.pathname) //不要用push

umi-request的使用

使用案例如下所示,很简单,不多介绍

import { extend } from "umi-request"

//一般不使用url,都是使用代理,实际部署服务时,后台或者运维会处理好
export const requestUrl = 'http://192.168.1.2/editor/api'
// export const requestUrl = '/editor/api' //测试代理需要,正式解决跨域,应用上面的,并删除setupProxy文件或者注释即可

const request = extend({
    prefix: requestUrl,
    timeout: 10000,
    requestType: 'form', //默认form形式,可以设置成 json
    credentials: 'include' //跨域时使用,否则不用写这个参数
})

/** 创建项目 POST,修改 PUT,删除 DELETE ,获取项目列表GET*/
//获取列表GET
export async function requestByProjectInfo(
    params?: API.pojectListParams, //参数类型
    options?: { [key: string]: any }
) {
    //这里是返回值类型
    return request<API.projectListResponse>('/project/list', {
        method: 'GET',
        params: params ? params : {},
        ...(options || {}),
    })
}

//创建POST
export async function requestByCreateProject(
    body?: API.createProjectParams,
    params?: {},
    options?: { [key: string]: any }
) {
    return request<API.projectResponse>('/project', {
        method: 'POST',
        params,
        data: body,
        ...(options || {}),
    })
}

//修改 PUT
export async function requestByModifyProject(
    body?: API.createProjectParams,
    params?: {},
    options?: { [key: string]: any }
) {
    return request<API.projectResponse>('/project', {
        method: 'PUT',
        params,
        data: body,
        ...(options || {}),
    })
}

//删除 DELETE
export async function requestByDeleteProject(
    body?: API.deleteProjectParams,
    params?: {},
    options?: { [key: string]: any }
) {
    return request<API.projectResponse>('/project', {
        method: 'DELETE',
        params,
        data: body,
        ...(options || {}),
    })
}

上传富文本对象时,data直接传递 FormData类型对象即可

let formdata = new FormData()
formdata.append('key', "value可以是String或者二进制Blob")
formdata.append('key2', new Blob())
request('/upload', {
    method: 'POST',
    params,
    data: formdata,
    options: {}
})

声明类型代码如下所示

declare namespace API {
  //获取项目列表参数
  type pojectListParams = {
    name?: string
  }

  //创建修改项目参数
  type createProjectParams = {
    name: string,
    description: string,
    project_type: number
  }

  //删除项目
  type deleteProjectParams = {
    object_id: number
  }

  //返回列表类型
  type projectListResponse = {
    /** 标识代码;200表示成功,非200表示出错 */
    code: number;
    /** 返回的数据 */
    data: Required<ProjectData>;
    /** 结果提示信息 */
    message: string;
  }

  //返回项目类型
  type projectResponse = {
    /** 标识代码;200表示成功,非200表示出错 */
    code: number;
    /** 返回的数据 */
    data: Required;
    /** 结果提示信息 */
    message: string;
  }

  //项目信息
  type projectData = {
    object_id: string,
    create_time: string,
    update_time: string,
    project_type: number,
    name: string,
    description: string
  }
}

禁止拖拽

有时候发现拖拽行为非常讨厌,可以直接禁止掉,某些组件不禁止则可以直接重新 ondragstart 方法

window.ondragstart = () => false

最后

可以模仿理解尝试哈,这篇文章仅仅是对懂得布局,会React,但是不会写项目的参考,快来试试吧