创建一个react项目(router,store,axios,antd)最后有项目地址

news2024/11/16 5:50:57

第一步:使用cra脚手架 创建项目

文档地址:Create React App 中文文档

npx create-react-app '你的项目名称'

第二步:整理项目结构和删除多余代码

目标效果图:

在src目录下分别新建apis,assets,components,pages,router,store,utlis文件夹

原始项目目录结构只保留App.js,index.css,index.js这个三个文件

App.js:

index.css:

index.js:

拓展:安装scss

npm install sass -D

测试和使用:

App.js内容:

把index.css改为index.scss

index.js内容:

效果:

 第三步:安装ui组件库-Ant Design

官网链接:在 create-react-app 中使用 - Ant Design

npm install antd --save

测试使用:

src/App.js:

import { Button } from 'antd';
function App() {
  return (
    <div>
      <Button type="primary">Button</Button>
    </div>
  );
}

export default App;

效果图:

第四步:配置路由

中文文档:在开始之前 | React Router6 中文文档

第一步:安装

npm install react-router-dom

第二步:新建两个页面文件

在src/pages下新建两个文件Layout和Login

Layout/index.js:

const Layout= ()=>{
    return(
        <div>
            我是Layout
        </div>
    )
}

export default Layout

Login/index.js:

const Login= ()=>{
    return(
        <div>
            我是Login
        </div>
    )
}

export default Login

第三步:新建路由实例

 src/router下新建index.js

import Layout from "../pages/Layout";
import Login from "../pages/Login";
import { createBrowserRouter } from "react-router-dom";
// 配置路由
const router = createBrowserRouter([
    {
        path: '/',
        element: <Layout></Layout>
    },
    {
        path: '/login',
        element: <Login></Login>
    }
])

export default router

第四步:使用挂载实例

在src/index.js种导入

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.scss';
import router from './router';
import { RouterProvider } from 'react-router-dom';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <RouterProvider router={router}></RouterProvider>
  </React.StrictMode>
);

效果图:

第五步:封装request

第一步:修改src/pages/Login/index.js文件

import { Card, Button, Form, Input, message } from 'antd';
import './index.scss'
import { useDispatch } from 'react-redux';
import { fetchLogin } from '../../store/modules/user';
import { useNavigate } from 'react-router-dom';
const Login = () => {
    const navigate= useNavigate()
    // 实例的账号密码
    // 13800000002
    // 246810
    const dispatch = useDispatch()
    const onFinish =async (values) => {
        await dispatch(fetchLogin(values))
        // 跳转
        navigate('/')
        // 提示
        message.success('登录成功')
        console.log('Success:', values);
      };
      const onFinishFailed = (errorInfo) => {
        console.log('Failed:', errorInfo);
      };
    return (
        <div>
            <Card className='card'>
                <Form
                    name="basic"
                    labelCol={{
                        span: 8,
                    }}
                    wrapperCol={{
                        span: 16,
                    }}
                    style={{
                        maxWidth: 600,
                    }}
                    initialValues={{
                        remember: true,
                    }}
                    onFinish={onFinish}
                    onFinishFailed={onFinishFailed}
                    autoComplete="off"
                >
                    <Form.Item
                        label="Username"
                        name="mobile"
                        rules={[
                            {
                                required: true,
                                message: 'Please input your username!',
                            },
                        ]}
                    >
                        <Input />
                    </Form.Item>

                    <Form.Item
                        label="Password"
                        name="code"
                        rules={[
                            {
                                required: true,
                                message: 'Please input your password!',
                            },
                        ]}
                    >
                        <Input.Password />
                    </Form.Item>

                    <Form.Item
                        wrapperCol={{
                            offset: 8,
                            span: 16,
                        }}
                    >
                        <Button type="primary" htmlType="submit">
                            Submit
                        </Button>
                    </Form.Item>
                </Form>
            </Card>
        </div>
    )
}

export default Login

在src/pages/Login下添加index.scss

.card{
    display: flex;
    justify-content: center;
    position: fixed;
    top: 100px;
    left:35%;
    width: 500px;
}

第二步:封装request

中文文档起步 | Axios中文文档 | Axios中文网

安装axios

npm install axios

在src/utlis下新建request.js和index.js

request.js:

// axios封装
// 1 根域名配置
// 2 超时时间
// 3 请求拦截器
// 4 响应拦截器
import axios from "axios";
import { getToken } from "./token";
const request = axios.create({
    baseURL:'http://geek.itheima.net/v1_0',
    timeout:5000
})

// 添加请求拦截器
request.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    const token= getToken()
    if(token){
        config.headers.Authorization='Bearer '+token
    }
    return config;
  }, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  });

// 添加响应拦截器
request.interceptors.response.use(function (response) {
    // 2xx 范围内的状态码都会触发该函数。
    // 对响应数据做点什么
    return response.data;
  }, function (error) {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么
    return Promise.reject(error);
  });

export {request}

index.js:

// 统一中转工具函数导出
import { request } from "./request";
import { setToken,getToken,removeToken } from "./token";
export {
    request,
    setToken,
    getToken,
    removeToken
}

第六步:利用redux封装token

第一步安装react-redux/@reduxjs/toolkit

中文文档:入门 Redux | Redux 中文官网

npm install @reduxjs/toolkit react-redux

 在src/store新建modules/user.js和index.js

src/store/index.js:

// 组合子模块
import { configureStore } from "@reduxjs/toolkit";
import userStore from "./modules/user";
const store = configureStore({
    reducer:{
        userStore
    }
})

export default store

src/store/modules/user.js:

// 放置和用户相关的
import { createSlice } from "@reduxjs/toolkit";
import { request,setToken as _setToken,getToken } from "../../utils";
const userSlice= createSlice({
    name:'user',
    initialState:{
        // token持久化
        token:getToken()||'',
    },
    reducers:{
        setToken:(state,action)=>{
            state.token=action.payload
            // token持久化
            _setToken(action.payload)
        }
    }

})

// 解构
const {setToken} = userSlice.actions

// 获取reducer函数
const userStore= userSlice.reducer

// 封装一部方法 完成登录 获取token
const fetchLogin=(loginForm)=>{
    return async (dispatch)=>{
        // 1 发送异步请求
        const res = await request.post('/authorizations',loginForm)
        // 2 提交同步修改方法
        dispatch(setToken(res.data.token))

    }
}

export {fetchLogin,setToken}

export default userStore

最后在src/index.js添加store

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.scss';
import router from './router';
import { RouterProvider } from 'react-router-dom';
import { Provider } from 'react-redux';
import store from './store';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <Provider store={store}>
      <RouterProvider router={router}></RouterProvider>
    </Provider>
  </React.StrictMode>
);

 然后在login进行调用

封装存,取,删token的方法

在src/utlis新建token.js

const TOKENKEY='token'
const setToken =(token)=>{
    localStorage.setItem(TOKENKEY,token)
}

const getToken=()=>{
   return localStorage.getItem(TOKENKEY)
}

const removeToken=()=>{
    localStorage.removeItem(TOKENKEY)
}

export {setToken,getToken,removeToken}

在src/pages/Layout下进行测试

// 测试toekn
import { useEffect } from "react"
import { request } from "../../utils"
const Layout= ()=>{
    useEffect(()=>{
        request.get('/user/profile')
    },[])
    return(
        <div>
            我是Layout
        </div>
    )
}

export default Layout

 第七步:根据token进行路由权限判断

在src/components下新建Auth.js

// 封装高阶组件
// 有token正常访问 无token跳转login

import { getToken } from "../utils"
import { Navigate } from "react-router-dom"

const Auth = ({children}) => {
    const token = getToken()
    if(token){
        return <>{children}</>
    }else{
        return <Navigate to={'/login'} replace></Navigate>
    }
}

export default Auth

 然后在router/index.js修改

import Layout from "../pages/Layout";
import Login from "../pages/Login";
import { createBrowserRouter } from "react-router-dom";
import Auth from "../components/Auth";
import Home from "../pages/Home";
import Article from "../pages/Article";
import Publish from "../pages/Publish";
// 配置路由
const router = createBrowserRouter([
    {
        path: '/',
        element: <Auth><Layout></Layout></Auth>,
        children:[
            {
                index:true,
                element:<Home></Home>
            },
            {
                path:'article',
                element:<Article></Article>
            },            {
                path:'publish',
                element:<Publish></Publish>
            }
        ]
    },
    {
        path: '/login',
        element: <Login></Login>
    }
])

export default router

新建src/pages下的Home,Article,Publish文件

 Home.js:

const Home = ()=>{
    return(
        <div>我是Home</div>
    )
}
export default Home

下面两个文件类推!!!

 第八步:封装Laout样式组件

覆盖src/pages/Layout/index.js

import React, { useEffect, useState } from 'react';
import {
    MenuFoldOutlined,
    MenuUnfoldOutlined,
    UploadOutlined,
    UserOutlined,
    VideoCameraOutlined,
} from '@ant-design/icons';
import { Button, Layout, Menu, theme, Popconfirm } from 'antd';
import { Outlet, useNavigate } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import { clearUserInfo, fetchUserInfo } from '../../store/modules/user';
const { Header, Sider, Content } = Layout;
const App = () => {
    const navigste = useNavigate()
    const dispatch = useDispatch()
    const [collapsed, setCollapsed] = useState(false);
    const {
        token: { colorBgContainer, borderRadiusLG },
    } = theme.useToken();

    // 侧边跳转
    const MenuClick = (key) => {
        navigste(key)
    }

    useEffect(() => {
        dispatch(fetchUserInfo())
    }, [dispatch])

    const userInfo = useSelector(state => state.userStore.userInfo)

    // 退出按钮
    const confirm=()=>{
        dispatch(clearUserInfo())
        navigste('/login')
    }
    return (
        <Layout>
            <Sider trigger={null} collapsible collapsed={collapsed}>
                <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                    <img src='https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg' style={{ width: '50%', height: '50px' }} alt=''></img>
                    {!collapsed &&
                        <div style={{ color: '#fff', width: '50%' }}>哈哈哈</div>
                    }
                </div>
                <Menu
                    onClick={({ key }) => MenuClick(key)}
                    theme="dark"
                    mode="inline"
                    defaultSelectedKeys={['/']}
                    items={[
                        {
                            key: '/',
                            icon: <UserOutlined />,
                            label: '首页',
                        },
                        {
                            key: '/article',
                            icon: <VideoCameraOutlined />,
                            label: '文章',
                        },
                        {
                            key: '/publish',
                            icon: <UploadOutlined />,
                            label: '发布',
                        },
                    ]}
                />
            </Sider>
            <Layout>
                <Header
                    style={{
                        padding: '0 24px 0 0',
                        background: colorBgContainer,
                        display: 'flex',
                        justifyContent: 'space-between'
                    }}
                >
                    <Button
                        type="text"
                        icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
                        onClick={() => setCollapsed(!collapsed)}
                        style={{
                            fontSize: '16px',
                            width: 64,
                            height: 64,
                        }}
                    />
                    <div style={{ display: 'flex', alignItems: 'center', fontSize: '16px' }}>
                        <span>{userInfo.name}</span>
                        <UserOutlined style={{ marginLeft: '10px' }} />
                        <Popconfirm
                            title="退出"
                            description="确认退出吗?"
                            onConfirm={confirm}
                            okText="Yes"
                            cancelText="No"
                        >
                            <span style={{ cursor: "pointer" }}>退出</span>

                        </Popconfirm>
                    </div>
                </Header>
                <Content
                    style={{
                        margin: '24px 16px',
                        padding: 24,
                        minHeight: 280,
                        background: colorBgContainer,
                        borderRadius: borderRadiusLG,
                    }}
                >
                    {/* 二级路由出口 */}
                    <Outlet></Outlet>
                </Content>
            </Layout>
        </Layout>
    );
};
export default App;

封装退出方法

src/store/modules/user.js

// 放置和用户相关的
import { createSlice } from "@reduxjs/toolkit";
import { request,setToken as _setToken,getToken, removeToken } from "../../utils";
const userSlice= createSlice({
    name:'user',
    initialState:{
        // token持久化
        token:getToken()||'',
        userInfo:{}
    },
    reducers:{
        setToken:(state,action)=>{
            state.token=action.payload
            // token持久化
            _setToken(action.payload)
        },
        setUserInfo:(state,action)=>{
            state.userInfo=action.payload
        },
        clearUserInfo:(state)=>{
            state.token=''
            state.userInfo={}
            removeToken()
        }
    }

})

// 解构
const {setToken,setUserInfo,clearUserInfo} = userSlice.actions

// 获取reducer函数
const userStore= userSlice.reducer

// 封装异步方法 完成登录 获取token
const fetchLogin=(loginForm)=>{
    return async (dispatch)=>{
        // 1 发送异步请求
        const res = await request.post('/authorizations',loginForm)
        // 2 提交同步修改方法
        dispatch(setToken(res.data.token))

    }
}
// 获取个人信息
const fetchUserInfo=()=>{
    return async (dispatch)=>{
        // 1 发送异步请求
        const res = await request.get('/user/profile')
        // 2 提交同步修改方法
        dispatch(setUserInfo(res.data))
    }
}

export {fetchLogin,setToken,fetchUserInfo,clearUserInfo}

export default userStore

 第九步:处理token失效

src/utlis/request.js

// axios封装
// 1 根域名配置
// 2 超时时间
// 3 请求拦截器
// 4 响应拦截器
import axios from "axios";
import { getToken, removeToken } from "./token";
import router from "../router";
const request = axios.create({
    baseURL:'http://geek.itheima.net/v1_0',
    timeout:5000
})

// 添加请求拦截器
request.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    const token= getToken()
    if(token){
        config.headers.Authorization='Bearer '+token
    }
    return config;
  }, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  });

// 添加响应拦截器
request.interceptors.response.use(function (response) {
    // 2xx 范围内的状态码都会触发该函数。
    // 对响应数据做点什么
    return response.data;
  }, function (error) {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么
    // 处理token失效
    if(error.response.status===401){
        removeToken()
        router.navigate('/login')
        window.location.reload()
    }
    return Promise.reject(error);
  });

export {request}

第十步:封装Api请求

src/apis/user.js

import { request } from "../utils";

const loginApi=(data)=>{
    return request({
        url:'/authorizations',
        method:'post',
        data
    })
}

const userInfoApi=()=>{
    return request({
        url:'/user/profile',
        method:'get',
    })
}

export {loginApi,userInfoApi}

修改src/store/modules/user.js

// 放置和用户相关的
import { createSlice } from "@reduxjs/toolkit";
import {setToken as _setToken,getToken, removeToken } from "../../utils";
import { loginApi, userInfoApi } from "../../apis/user";
const userSlice= createSlice({
    name:'user',
    initialState:{
        // token持久化
        token:getToken()||'',
        userInfo:{}
    },
    reducers:{
        setToken:(state,action)=>{
            state.token=action.payload
            // token持久化
            _setToken(action.payload)
        },
        setUserInfo:(state,action)=>{
            state.userInfo=action.payload
        },
        clearUserInfo:(state)=>{
            state.token=''
            state.userInfo={}
            removeToken()
        }
    }

})

// 解构
const {setToken,setUserInfo,clearUserInfo} = userSlice.actions

// 获取reducer函数
const userStore= userSlice.reducer

// 封装异步方法 完成登录 获取token
const fetchLogin=(loginForm)=>{
    return async (dispatch)=>{
        // 1 发送异步请求
        const res = await loginApi(loginForm)
        // 2 提交同步修改方法
        dispatch(setToken(res.data.token))

    }
}
// 获取个人信息
const fetchUserInfo=()=>{
    return async (dispatch)=>{
        // 1 发送异步请求
        const res = await userInfoApi()
        // 2 提交同步修改方法
        dispatch(setUserInfo(res.data))
    }
}

export {fetchLogin,setToken,fetchUserInfo,clearUserInfo}

export default userStore

到这里恭喜你完成了一个简单版本的react后台模板

示例代码地址:react-demo-pc: 练习使用的一个react后台模板,主要熟悉联系ui组件库,redux,router,axios

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

英文文本标点恢复

文章目录 一、安装 rpunct二、使用三、下载模型时报错1、报错详情2、报错原因3、解决方案 四、程序运行时报错1、报错详情2、报错原因3、解决方案 五、修改默认缓存路径 一、安装 rpunct pip install rpunct 相关依赖包信息&#xff1a; langdetect1.0.9 pandas1.2.4 simpletr…

记录一下Hql遇到的零碎问题

建表相关 -- 地区维度表 drop table dim_province_full; create table dim_province_full( id string comment 编号, name string comment 省份名称, region_id string comment 大区id, area_code string comment 行政区位码, iso_code string comment 国际编码, iso_3166_2 s…

关于位操作符的实际应用<C语言>

前言 位操作符在C语言初学阶段相对其他操作符来说&#xff0c;是一种难度比较大的操作符&#xff0c;且运用较少的一类操作符&#xff0c;但是位操作符并不是“一无是处”&#xff0c;合理运用的位操作符&#xff0c;在某些场景下可以优化算法&#xff0c;提高代码的执行效率&a…

最新AI实景自动无人直播软件教你实现24小时不下播带货;智能化引领直播新时代

随着互联网的快速发展&#xff0c;直播行业已经成为商家品牌推广和商品销售的热门方式。而如今&#xff0c;一款令人惊叹的AI实景自动无人直播软件正在让直播变得更加智能化和便捷化&#xff0c;为商家带来全新的直播体验。 AI实景自动无人直播软件的一大优势是其智能讲解功能。…

远动通讯屏的作用

远动通讯屏的作用 远动通讯屏有时有称为调度数据网柜&#xff0c;远动通讯屏具体干啥作用&#xff1f;远动通讯屏是以计算机为基础的生产过程与调度自动化系统&#xff0c;可以对现场的运行设备进行监视和控制、以实现数据采集、设备测量、参数调节以及各类信号报警等各项功能。…

AI视频教程下载:学会用AI创作文本图片音频视频

在不断发展的科技领域&#xff0c;人工智能 (AI) 是毋庸置疑的冠军&#xff0c;它是一种不断创新的力量&#xff0c;在我们的生活中扮演着越来越重要的角色。随着 2023 年的到来&#xff0c;我们诚挚地欢迎您加入人工智能精通课程的大门。 这不仅仅是一个课程&#xff0c;它专为…

第08章 IP分类编址和无分类编址

8.1 本章目标 了解IP地址的用途和种类了解分类编址和无分类编址区别掌握IP地址、子网掩码、网关概念及使用掌握子网划分及超网划分方法掌握无分类编址的改变和使用 8.2 IP地址的用途和种类 分类编址&#xff1a;造成地址的浪费&#xff0c;以及地址不够用&#xff1b;无分类编…

泰迪科技2024中职大数据实训室方案解读

中职在大数据专业建设所遇到的困难 数据、信息安全、人工智能等新信息技术产业发展迅猛&#xff0c;人才极其匮乏&#xff0c;各个中职院校纷纷开设相应的专业方向。但是&#xff0c;绝大多数院校因为师资和积累问题&#xff0c;在专业建设规划、办学特色提炼、创新教学模…

男士内裤选什么牌子的比较好?男士内裤盘点测评

随着夏日的脚步逐渐临近&#xff0c;你是否已经开始为挑选一款舒适透气的男士内裤而犯愁&#xff1f;市场上男士内裤品牌琳琅满目&#xff0c;各种材质层出不穷&#xff0c;让你在选购时犹豫不决。别担心&#xff0c;我最近精心挑选了市面上热门的男士内裤品牌进行了一番细致测…

kubernetes中StorageClass动态存储资源

StorageClass动态存储资源&#xff1a;简称sc资源&#xff1b; 动态存储类&#xff0c;它自动创建pv&#xff1b;不再需要手动创建pv&#xff1b; 但是&#xff0c;我们的存储卷系统nfs本身不支持这个sc动态存储&#xff0c;所以&#xff0c;我们需要借助一个插件来实现nfs配合…

如何vscode中刷力扣

推荐你阅读 互联网大厂万字专题总结 Redis总结 JUC总结 操作系统总结 JVM总结 Mysql总结 微服务总结 互联网大厂常考知识点 什么是系统调用 CPU底层锁指令有哪些 AQS与ReentrantLock原理 旁路策略缓存一致性 Java通配符看这一篇就够 Java自限定泛型 技术分享 如何vscode中刷力扣…

java爬虫代理ip(java爬虫代码示例)

java爬虫代理ip 在编写java爬虫时&#xff0c;经常会遇到需要使用代理IP来访问目标网站的情况。这时候&#xff0c;我们就需要编写代码来实现代理IP的功能。接下来&#xff0c;我们将为大家介绍如何在java爬虫中使用代理IP&#xff0c;以及给出相应的代码示例。 首先&#xff…

【LLM第三篇】名词解释:RLHF——chatgpt的功臣

RLHF (Reinforcement Learning from Human Feedback) &#xff0c;直译为&#xff1a;“来自人类反馈的强化学习”。RLHF是一种结合了强化学习和人类反馈的机器学习方法&#xff0c;主要用于训练大模型以执行复杂的任务&#xff0c;尤其是当这些任务难以通过传统的奖励函数来精…

系统权限控制插件封装-实现系统权限控制插件化

背景&#xff1a;按照传统的开发方式方式&#xff0c;每次新开发一个系统&#xff0c;就需要花费大量时间精力去搭建权限控制模块&#xff0c;如果我们把权限控制这一整个模块都抽离成一个独立的权限控制插件&#xff0c;支持单命令安装&#xff0c;全面暴露参数与方法&#xf…

Linux Ubuntu(玩客云) qBittorrent docker BT下载(qbittorrent 密码错误无法登录 ip地址被禁止登录等)

提示&#xff1a; 需要提前安装Docker 根据qBittorrent官网的更新日志https://www.qbittorrent.org/news &#xff0c;4.6.1.0包含一个重大更新。可以看到自4.6.1.0开始&#xff0c;qBittorrent将弃用adminadmin默认密码&#xff0c;采用随机密码&#xff0c;将在终端控制台输出…

伦敦银软件下载完成后如何开始交易?

在伦敦金投资的整个流程中&#xff0c;进行伦敦银软件的下载可以说也是重要的一步。伦敦银软件是由交易平台提供的&#xff0c;也是交易服务的具体体现。没有平台&#xff0c;我们就不能下单&#xff0c;也不能入场。那么&#xff0c;伦敦银软件下载完成后如何开始交易呢&#…

找不到msvcr120.dll无法继续执行

windows&#xff08;新安装的系统&#xff09;安装mysql&#xff0c;报错MSVCR120.dll找不到 官方下载地址 https://www.microsoft.com/zh-CN/download/details.aspx?id40784&wd&eqid9eba4d380059694e00000004658ce260 安装上就好了

Spring AI实战之一:快速体验(OpenAI)

欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码)&#xff1a;https://github.com/zq2599/blog_demos 关于Spring AI Spring Boot、Spring Cloud、Spring Data&#xff0c;作为一名Java程序员&#xff0c;相信您对这些概览早已耳熟能详&#xff0c;或者天天在用…

1802907-91-0,甲基四嗪PEG4羧酸一种胺反应试剂

基本信息&#xff1a; 中文名称&#xff1a;甲基四嗪-四聚乙二醇-羧基&#xff0c;甲基四嗪PEG4羧酸 英文名称&#xff1a;Methyltetrazine-PEG4-acid&#xff0c;Methyltetrazine-PEG4-COOH CAS号&#xff1a;1802907-91-0 分子式&#xff1a;C20H28N4O7 分子量&#xff…

双翻斗雨量计学习

双翻斗雨量计用户手册&#xff08;脉冲型&#xff09; 本仪器由雨量计壳体、承雨口、漏斗、翻斗支撑、上漏斗雨量调节支架、上漏斗、汇集漏斗、计数翻斗雨量调节支架、计数翻斗、干簧管安装架、轴承螺钉、出水漏斗、腿部支架、干簧管、水平泡、调节支撑板、控制盒、调平装置、接…