React多功能管理平台项目开发全教程

news2024/9/24 11:25:34

​🌈个人主页:前端青山
🔥系列专栏:React篇
🔖人终将被年少不可得之物困其一生

依旧青山,本期给大家带来React篇专栏内容:React-综合应用开发教程:构建多功能管理平台

目录

1.创建项目

2.改造目录结构

3.安装一些必须的模块

3.1 配置预处理器

3.1.1 配置别名@

3.2安装状态管理器

3.3 路由

3.4 数据验证

3.5数据请求

3.6ui库

3.6.1 自定义主题

3.7 其他第三方工具包

4.创建主布局文件

5.拆分主界面

6.使用rtk来管理状态

6.1 定义State和Dispatch类型

6.2 定义 Hooks 类型

6.3 应用程序中使用

6.4 整合reducer

6.5 入口文件配置状态管理器

6.6 左侧菜单栏使用状态管理器

6.7 头部组件使用状态管理器

6.8保留用户习惯-可选

6.9 永久存储的 类 localStorage 的工具 store2

7.左侧菜单栏

7.1.设计左侧菜单栏的数据

7.2.渲染左侧菜单栏

7.3 低版本处理

7.4 菜单渲染优化

8.定义路由

8.1 官方文档

8.2 创建对应的页面

8.3 定义菜单路由信息

8.4.装载路由

8.5 定义路由组件

8.6 手动测试路由

8.7 设置404页面

9 切换路由

9.1 点击切换路由

9.2 刷新保持左侧菜单状态

10 设置面包屑导航

10.1 参考文档

10.2 设置面包屑导航

11.快捷切换页

11.1 准备组件

11.2 处理数据

11.3 监听路由添加数据

11.4 点击tab页切换路由,关闭效果

12.数据请求的封装

13 构建登录页面

13.1 参考组件库组件

13.2 构造登录接口API

13.3 创建登录的页面

13.4 创建登录路由

13.4 完善登录界面

14 执行登录

14.1 构建模块 admins

14.2 装载模块

14.3 登录实现

15.前端登录验证

16 .后端token校验

17.退出登录

17.1 实现退出登录

17.2 保留退出时的页面

18.隐藏左侧菜单项

19. 管理员管理

19.1.设计接口

19.2.展示管理员列表

19.3 优化表格滚动

19.4 优化表格的分页器

19.5 添加中文包

19.6删除管理员

19.7 如何批量删除管理员数据

19.9.添加管理员

19.9.1 设置添加管理员的抽屉效果(无树形控件)

19.9.2 修改菜单数据 添加了keyid字段

19.9.3 添加管理员时选择该管理员权限

19.9.4 添加管理员

19.10管理员修改

20 系统首页数据统计

21 左侧菜单栏的权限

21.1 思路

21.2 算法过程

21.3 算法实现

21.4 生成动态的左侧菜单项

22、页面权限

23、按钮权限

24、轮播图管理

24.1 封装接口

24.2 轮播图页面渲染

23.3 添加轮播图

25.产品管理

25.1 封装接口

25.2 产品列表

25.3 筛选列表

26.数据可视化

1.echarts

2.Highcharts

3.antv - g2

27.编辑器

1.富文本编辑器

2.markDown编辑器

28.导入以及导出

1.导出

2.导入

29.地图

30.项目打包发布

1.创建项目

# 现在
npx create-react-app react-admin-app --template typescript

熟悉目录结构

- react-admin-app
    -node_modules
    -public
    -src
        App.css
        App.test.tsx App.tsx的测试文件  npm run test 查看测试结果
        App.tsx
        index.css
        index.tsx react应用程序的入口文件
        logo.svg 
        react-app-env.d.ts // 声明文件 // 指令声明对包的依赖关系
        reportWebVitals.ts // 测试性能
        seupTests.ts // 使用jest做为测试工具
    .gitignore
    package-lock.json
    package.json
    README.md
    tsconfig.json

*.d.ts 代表ts的声明文件

2.改造目录结构

src
 
   api
    components
    layout
    store
    router
    utils
    views
    App.tsx
    index.tsx
    logo.svg
    react-app-env.d.ts
    reportWebVitals.ts 
  seupTests.ts 
// src/index.tsx

import React from 'react';
import ReactDOM from 'react-dom/client';
​
import App from './App';
import reportWebVitals from './reportWebVitals';
​
const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLDivElement
);
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
​
reportWebVitals();
// src/App.tsx
import React, { FC } from 'react';
​
interface IAppProps {
}
​
const App: FC<IAppProps> = (props) => {
  return (
    <>App</>
  )
}
​
export default App

3.安装一些必须的模块

3.1 配置预处理器

两种方式:

  • 抽离配置文件配置预处理器

  • 不抽离配置文件craco进行预处理器配置

本项目推荐使用第二种方式

$ cnpm i @craco/craco @types/node -D

https://www.npmjs.com/package/@craco/craco

3.1.1 配置别名@

项目根目录创建 craco.config.js,代码如下:

// craco.config.js
const path = require('path')
module.exports = {
  webpack: {
    alias: {
      '@': path.resolve(__dirname, 'src')
    }
  }
}

为了使 TS 文件引入时的别名路径能够正常解析,需要配置 tsconifg.json,在 compilerOptions选项里添加 path 等属性。为了防止配置被覆盖,需要单独创建一个文件 tsconfig.path.json,添加以下代码

// tsconfig.path.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    },
    "types": [
      "node"
    ]
  }
}

tsconifg.json 引入配置文件:

// tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx"
  },
  "extends": "./tsconfig.path.json",
  "include": [
    "src"
  ]
}

修改 package.json 如下:

"scripts": {
  "start": "craco start",
  "build": "craco build",
  "test": "craco test"
},
$ npm run start

3.2安装状态管理器

根据项目需求 任选其一即可

$ cnpm i redux -S
$ cnpm i redux react-redux -S
$ cnpm i redux react-redux redux-thunk -S
$ cnpm i redux react-redux redux-saga -S
$ cnpm i redux react-redux redux-thunk immutable redux-immutable -S
$ cnpm i redux react-redux redux-saga immutable redux-immutable -S
$ cnpm i mobx mobx-react -S

本项目不采用之前的状态管理模式,使用 rtk 技术

cnpm i @reduxjs/toolkit redux react-redux -S

3.3 路由

2021年11月4日 发布了 react-router-dom的v6.0.0版本:Home v6.26.1 | React Router

如需使用v5版本:https://v5.reactrouter.com/web/guides/quick-start cnpm i react-router-dom@5 -S

本项目采用 V6版本

cnpm i react-router-dom -S

3.4 数据验证

思考,有没有必要安装 prop-types ?

cnpm i prop-types -S

本项目其实没有必要安装,因为所有的数据都是基于ts,而ts需要指定类型注解

3.5数据请求

cnpm i axios -S

以前版本中 cnpm i @types/axios -S

Ts 中 @types/* 为声明文件

3.6ui库

官网地址:Ant Design - 一套企业级 UI 设计语言和 React 组件库 5.2.0

国内官方镜像地址:Ant Design - 一套企业级 UI 设计语言和 React 组件库

国内gitee镜像地址:https://ant-design.gitee.io/index-cn

cnpm i antd @ant-design/icons -S

src/index.tsx

// src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';

import App from './App';
import reportWebVitals from './reportWebVitals';

import 'antd/dist/reset.css'; // antd重置样式表

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLDivElement
);
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

reportWebVitals();

测试组件库

// src/App.tsx
import React, { FC } from 'react';
import { Button } from 'antd';

interface IAppProps {
}

const App: FC<IAppProps> = (props) => {
  return (
    <>
      App
      <Button type="primary">
        Primary
      </Button>
    </>
  )
}

export default App

浏览器查看发现测试通过

3.6.1 自定义主题

404 Not Found - Ant Design

antd 内建了深色主题和紧凑主题,你可以参照 使用暗色主题和紧凑主题 进行接入。

可以定制的变量列表如下:

@primary-color: #1890ff; // 全局主色
@link-color: #1890ff; // 链接色
@success-color: #52c41a; // 成功色
@warning-color: #faad14; // 警告色
@error-color: #f5222d; // 错误色
@font-size-base: 14px; // 主字号
@heading-color: rgba(0, 0, 0, 0.85); // 标题色
@text-color: rgba(0, 0, 0, 0.65); // 主文本色
@text-color-secondary: rgba(0, 0, 0, 0.45); // 次文本色
@disabled-color: rgba(0, 0, 0, 0.25); // 失效色
@border-radius-base: 2px; // 组件/浮层圆角
@border-color-base: #d9d9d9; // 边框色
@box-shadow-base: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08),
  0 9px 28px 8px rgba(0, 0, 0, 0.05); // 浮层阴影
// src/index.tsx

import React from 'react';
import ReactDOM from 'react-dom/client';
​
import { ConfigProvider } from 'antd';
​
import App from './App';
import reportWebVitals from './reportWebVitals';
​
import 'antd/dist/reset.css'; // antd重置样式表
​
const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLDivElement
);
root.render(
  <React.StrictMode>
    <ConfigProvider
      theme = { {
        token: {
          colorPrimary: '#1890ff'
        }
      } }
    >
      <App />
    </ConfigProvider>
  </React.StrictMode>
);
​
reportWebVitals();

3.7 其他第三方工具包

Lodash 简介 | Lodash中文文档 | Lodash中文网

Lodash 工具包,项目必装,它提供了很多使用的函数

$ cnpm i lodash -S
$ cnpm i @types/lodash -D
import _ from 'lodash'
​
var users = [
  { 'user': 'barney',  'active': false },
  { 'user': 'fred',    'active': false },
  { 'user': 'pebbles', 'active': true }
];
​
console.log(_.findIndex(users, (item) => item.user === 'pebbles'))
console.log(users.findIndex((item) => item.user === 'pebbles'))

4.创建主布局文件

预览模板:开箱即用的中台前端/设计解决方案 - Ant Design Pro

src/layout/Index.tsx 作为后台管理系统的主页面布局(包含左侧的菜单栏,顶部,底部等)

https://ant-design.gitee.io/components/layout-cn/#components-layout-demo-custom-trigger

不要照着代码敲,直接复制即可,给 Layout 组件添加 id为admin-app

// src/layout/Index.tsx
import React, { useState } from 'react';
import {
  MenuFoldOutlined,
  MenuUnfoldOutlined,
  UploadOutlined,
  UserOutlined,
  VideoCameraOutlined,
} from '@ant-design/icons';
import { Layout, Menu, theme } from 'antd';

const { Header, Sider, Content } = Layout;

const App: React.FC = () => {
  const [collapsed, setCollapsed] = useState(false);
  const {
    token: { colorBgContainer },
  } = theme.useToken();

  return (
    <Layout id="components-layout-demo-custom-trigger">
      <Sider trigger={null} collapsible collapsed={collapsed}>
        <div className="logo" />
        <Menu
          theme="dark"
          mode="inline"
          defaultSelectedKeys={['1']}
          items={[
            {
              key: '1',
              icon: <UserOutlined />,
              label: 'nav 1',
            },
            {
              key: '2',
              icon: <VideoCameraOutlined />,
              label: 'nav 2',
            },
            {
              key: '3',
              icon: <UploadOutlined />,
              label: 'nav 3',
            },
          ]}
        />
      </Sider>
      <Layout className="site-layout">
        <Header style={
  { padding: 0, background: colorBgContainer }}>
          {React.createElement(collapsed ? MenuUnfoldOutlined : MenuFoldOutlined, {
            className: 'trigger',
            onClick: () => setCollapsed(!collapsed),
          })}
        </Header>
        <Content
          style={
  {
            margin: '24px 16px',
            padding: 24,
            minHeight: 280,
            background: colorBgContainer,
          }}
        >
          Content
        </Content>
      </Layout>
    </Layout>
  );
};

export default App;

主组件引入 主界面的布局文件

// src/App.tsx
import React, { FC } from 'react';

import Index from '@/layout/Index'

import './App.css'

interface IAppProps {
}

const App: FC<IAppProps> = (props) => {
  return (
    <>
      <Index />
    </>
  )
}

export default App

查看浏览器,预览运行结果

发现页面并不是全屏。审查元素设置 root以及 components-layout-demo-custom-trigger 高度为 100%

/* src/App.css */
#root, #components-layout-demo-custom-trigger { height: 100%;}

#components-layout-demo-custom-trigger .trigger {
  padding: 0 24px;
  font-size: 18px;
  line-height: 64px;
  cursor: pointer;
  transition: color 0.3s;
}

#components-layout-demo-custom-trigger .trigger:hover {
  color: #1890ff;
}

#components-layout-demo-custom-trigger .logo {
  height: 32px;
  margin: 16px;
  background: rgba(255, 255, 255, 0.3);
}

5.拆分主界面

先拆分左侧的菜单栏组件

// src/layout/components/SideBar.tsx
import React, { useState } from 'react';
import {
  UploadOutlined,
  UserOutlined,
  VideoCameraOutlined,
} from '@ant-design/icons';
import { Layout, Menu } from 'antd';

const { Sider } = Layout;

const App: React.FC = () => {
  const [collapsed] = useState(false);

  return (
    <Sider trigger={null} collapsible collapsed={collapsed}>
      <div className="logo" />
      <Menu
        theme="dark"
        mode="inline"
        defaultSelectedKeys={['1']}
        items={[
          {
            key: '1',
            icon: <UserOutlined />,
            label: 'nav 1',
          },
          {
            key: '2',
            icon: <VideoCameraOutlined />,
            label: 'nav 2',
          },
          {
            key: '3',
            icon: <UploadOutlined />,
            label: 'nav 3',
          },
        ]}
      />
    </Sider>
  );
};

export default App;
// src/layout/components/AppHeader.tsx
import React, { useState } from 'react';
import {
  MenuFoldOutlined,
  MenuUnfoldOutlined
} from '@ant-design/icons';
import { Layout, theme } from 'antd';

const { Header } = Layout;

const App: React.FC = () => {
  const [collapsed, setCollapsed] = useState(false);
  const {
    token: { colorBgContainer },
  } = theme.useToken();

  return (
    <Header style={
  { padding: 0, background: colorBgContainer }}>
      {React.createElement(collapsed ? MenuUnfoldOutlined : MenuFoldOutlined, {
        className: 'trigger',
        onClick: () => setCollapsed(!collapsed),
      })}
    </Header>
  );
};

export default App;
// src/layout/components/AppMain.tsx
import React from 'react';

import { Layout, theme } from 'antd';

const { Content } = Layout;

const App: React.FC = () => {
  const {
    token: { colorBgContainer },
  } = theme.useToken();

  return (
    <Content
      style={
  {
        margin: '24px 16px',
        padding: 24,
        minHeight: 280,
        background: colorBgContainer,
      }}
    >
      Content
    </Content>
  );
};

export default App;

整和组件资源

// src/layout/components/index.ts

export { default as SideBar } from './SideBar'
export { default as AppHeader } from './AppHeader'
export { default as AppMain } from './AppMain'
// src/layout/Index.tsx
import React from 'react';

import { Layout } from 'antd';

// import SideBar from './components/SideBar'
// import AppHeader from './components/AppHeader'
// import AppMain from './components/AppMain'
import { SideBar, AppHeader, AppMain } from './components'

const App: React.FC = () => {

  return (
    <Layout id="components-layout-demo-custom-trigger">
      <SideBar />
      <Layout className="site-layout">
        <AppHeader />
        <AppMain />
      </Layout>
    </Layout>
  );
};

export default App;

此时点击头部的控制器,发现只有头部组件的 图标在切换,但是并没有影响左侧菜单的收缩

建议使用状态管理器管理控制的这个状态

6.使用rtk来管理状态

Redux 中文官网 - JavaScript 应用的状态容器,提供可预测的状态管理。 | Redux 中文官网

参考链接:TypeScript 快速开始 | Redux 中文官网

6.1 定义State和Dispatch类型

// src/store/index.ts

import { configureStore } from '@reduxjs/toolkit'
​
const store = configureStore({
  reducer: {}
})
​
// 导出类型注解
// 从 store 本身推断出 `RootState` 和 `AppDispatch` 类型

export type RootState = ReturnType<typeof store.getState>
// 推断出类型
export type AppDispatch = typeof store.dispatch
​
export default store

构建app的模块用于管理 头部和 左侧菜单的共同的状态

6.2 定义 Hooks 类型

虽然可以将RootStateandAppDispatch类型导入到每个组件中,但最好创建useDispatchand useSelectorhooks 的类型化版本以在您的应用程序中使用

// src/store/hooks.ts

import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import type { RootState, AppDispatch } from './index'
​
// 在整个应用程序中使用,而不是简单的 `useDispatch` 和 `useSelector`
export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

6.3 应用程序中使用

创建状态管理

// src/store/modules/app.ts
import { createSlice } from '@reduxjs/toolkit'
​
interface IAppState {
  collapsed: boolean
}
​
const initialState: IAppState = {
  collapsed: false
}
​
export const appSlice = createSlice({
  name: 'app',
  initialState,
  reducers: {
    changeCollapsed (state) {
      state.collapsed = !state.collapsed
    }
  }
})
​
export const { changeCollapsed } = appSlice.actions
​
export default appSlice.reducer

6.4 整合reducer

// src/store/index.ts
import { configureStore } from '@reduxjs/toolkit'
​
import app from './modules/app'
​
const store = configureStore({
  reducer: {
    app
  }
})
​
// 导出类型注解
// 从 store 本身推断出 `RootState` 和 `AppDispatch` 类型
export type RootState = ReturnType<typeof store.getState>
// 推断出类型
export type AppDispatch = typeof store.dispatch
​
export default store

6.5 入口文件配置状态管理器

// src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
​
import { ConfigProvider } from 'antd';
import { Provider } from 'react-redux'
​
import App from './App';
import reportWebVitals from './reportWebVitals';
import store from './store'
​
import 'antd/dist/reset.css'; // antd重置样式表
​
const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLDivElement
);
root.render(
  <React.StrictMode>
    <ConfigProvider
      theme = { {
        token: {
          colorPrimary: '#1890ff'
        }
      } }
    >
      <Provider store = { store }>
        <App />
      </Provider>
    </ConfigProvider>
  </React.StrictMode>
);
​
reportWebVitals();
​

6.6 左侧菜单栏使用状态管理器

// src/layout/components/SideBar.tsx
import React from 'react';
import {
  UploadOutlined,
  UserOutlined,
  VideoCameraOutlined,
} from '@ant-design/icons';
import { Layout, Menu } from 'antd';
import { useAppSelector } from '@/store/hooks'
// import { useSelector } from 'react-redux'
// import type { RootState } from '@/store'
​
const { Sider } = Layout;
​
const App: React.FC = () => {
  
  const collapsed = useAppSelector(state => state.app.collapsed)
  // const collapsed = useSelector((state: RootState) => state.app.collapsed)
  return (
    <Sider trigger={null} collapsible collapsed={collapsed}>
      <div className="logo" />
      <Menu
        theme="dark"
        mode="inline"
        defaultSelectedKeys={['1']}
        items={[
          {
            key: '1',
            icon: <UserOutlined />,
            label: 'nav 1',
          },
          {
            key: '2',
            icon: <VideoCameraOutlined />,
            label: 'nav 2',
          },
          {
            key: '3',
            icon: <UploadOutlined />,
            label: 'nav 3',
          },
        ]}
      />
    </Sider>
  );
};
​
export default App;

6.7 头部组件使用状态管理器

// src/layout/components/AppHeader.tsx
import React from 'react';
import {
  MenuFoldOutlined,
  MenuUnfoldOutlined
} from '@ant-design/icons';
import { Layout, theme } from 'antd';
import { useAppSelector, useAppDispatch } from '@/store/hooks'
import { changeCollapsed } from '@/store/modules/app'
​
const { Header } = Layout;
​
const App: React.FC = () => {
  // const [collapsed, setCollapsed] = useState(false);
  const collapsed = useAppSelector(state => state.app.collapsed)
  const dispatch = useAppDispatch()
  const {
    token: { colorBgContainer },
  } = theme.useToken();
​
  return (
    <Header style={
  { padding: 0, background: colorBgContainer }}>
      {React.createElement(collapsed ? MenuUnfoldOutlined : MenuFoldOutlined, {
        className: 'trigger',
        // onClick: () => setCollapsed(!collapsed),
        onClick: () => dispatch(changeCollapsed())
      })}
    </Header>
  );
};
​
export default App;

6.8保留用户习惯-可选

永久存储 用户习惯

数据持久化: redux-persist

此时发现 头部的 按钮可以控制左侧菜单栏了,但是还没有满足需求

需求如下:保留用户的使用习惯

// src/store/modules/app.ts
import { createSlice } from '@reduxjs/toolkit'
​
interface IAppState {
  collapsed: boolean
}
​
const initialState: IAppState = {
  // collapsed: false
  collapsed: localStorage.getItem('collapsed') === 'true'
}
​
export const appSlice = createSlice({
  name: 'app',
  initialState,
  reducers: {
    changeCollapsed (state) {
      state.collapsed = !state.collapsed
      localStorage.setItem('collapsed', String(state.collapsed))
    }
  }
})
​
export const { changeCollapsed } = appSlice.actions
​
export default appSlice.reducer

6.9 永久存储的 类 localStorage 的工具 store2

$ cnpm i store2 -S

https://www.npmjs.com/package/store2

推荐一个好用的永久存储的 类 localStorage 的工具 store2

// src/store/modules/app.ts
import { createSlice } from '@reduxjs/toolkit'
import store2 from 'store2'
interface IAppState {
  collapsed: boolean
}
​
const initialState: IAppState = {
  // collapsed: false
  // collapsed: localStorage.getItem('collapsed') === 'true'
  collapsed: store2.get('collapsed') === 'true'
}
​
export const appSlice = createSlice({
  name: 'app',
  initialState,
  reducers: {
    changeCollapsed (state) {
      state.collapsed = !state.collapsed
      // localStorage.setItem('collapsed', String(state.collapsed))
      store2.set('collapsed', String(state.collapsed))
    }
  }
})
​
export const { changeCollapsed } = appSlice.actions
​
export default appSlice.reducer

7.左侧菜单栏

7.1.设计左侧菜单栏的数据

https://ant-design.gitee.io/components/menu-cn/#components-menu-demo-sider-current

Antd 4.20以上版本直接实现 递归

antd 4.20版本以下需要手动实现

// src/router/menus.tsx
import type { MenuProps } from 'antd';
import { HomeOutlined } from '@ant-design/icons'
​
type MenuItem = Required<MenuProps>['items'][number];
​
// 扩展固有的类型
type IMyMenuItem = MenuItem & {
  path: string; // 如果后期使用key值为跳转地址时,可以不添加 path 属性
  children?: IMyMenuItem[];
  redirect?: string // 多级菜单的默认地址
}
​
const menus: IMyMenuItem[] = [
  {
    path: '/',
    label: '系统首页',
    key: '/',
    icon: <HomeOutlined />
  },
  {
    path: '/banner',
    label: '轮播图管理',
    key: '/banner',
    redirect: '/banner/list',
    icon: <HomeOutlined />,
    children: [
      {
        path: '/banner/list',
        key: '/banner/list',
        label: '轮播图列表',
        icon: <HomeOutlined />,
      },
      {
        path: '/banner/add',
        key: '/banner/add',
        label: '添加轮播图',
        icon: <HomeOutlined />,
      }
    ]
  },
  {
    path: '/pro',
    label: '产品管理',
    key: '/pro',
    redirect: '/pro/list',
    icon: <HomeOutlined />,
    children: [
      {
        path: '/pro/list',
        key: '/pro/list',
        label: '产品列表',
        icon: <HomeOutlined />,
      },
      {
        path: '/pro/search',
        key: '/pro/search',
        label: '筛选列表',
        icon: <HomeOutlined />,
      }
    ]
  },
  {
    path: '/account',
    label: '账户管理',
    key: '/account',
    redirect: '/account/user',
    icon: <HomeOutlined />,
    children: [
      {
        path: '/account/user',
        key: '/account/user',
        label: '用户列表',
        icon: <HomeOutlined />,
      },
      {
        path: '/account/admin',
        key: '/account/admin',
        label: '管理员列表',
        icon: <HomeOutlined />,
      }
    ]
  }
]
​
export default menus

7.2.渲染左侧菜单栏

左侧菜单栏的头部设定logo以及后台管理系统名称

// src/layout/components/SideBar.tsx
import React from 'react';
​
import { Layout, Menu, Image } from 'antd';
​
import menus from '@/router/menu'
​
import { useAppSelector } from '@/store/hooks'
import logo from '@/logo.svg'
​
const { Sider } = Layout;
​
const App: React.FC = () => {
  
  const collapsed = useAppSelector(state => state.app.collapsed)
​
  return (
    <Sider trigger={null} collapsible collapsed={collapsed}>
      <div className="logo" style={ { 
        display: 'flex', 
        justifyContent: 'center', 
        alignItems: 'center',
        color: '#fff'
      }}>
        <Image src = { logo } width="28px" height="28px" preview={ false }></Image>
        { !collapsed && <div style={
  {
          height: '32px', 
          overflow: 'hidden', 
          lineHeight: '32px'
        }}>嗨购后台管理系统</div> }
      </div>
      <Menu
        theme="dark"
        mode="inline"
        defaultSelectedKeys={['1']}
        items={ menus }
      />
    </Sider>
  );
};
​
export default App;

7.3 低版本处理

以上菜单项的设置在antd 4.20.0版本以上好使,如果在4.20.0版本以下,应该使用 递归组件实现

// src/layout/components/SideBar.tsx
import React from 'react';

import { Layout, Menu, Image } from 'antd';

import menus from '@/router/menu'

import { useAppSelector } from '@/store/hooks'
import logo from '@/logo.svg'

const { Sider } = Layout;

const App: React.FC = () => {
  
  const collapsed = useAppSelector(state => state.app.collapsed)

  // 自定义左侧菜单栏 - 递归
  const renderMenus = (menus: any[]) => {
    return menus.map(item => {
      if (item.children) {
        return (
          <Menu.SubMenu title = { item.label } key = { item.key }>
            { renderMenus(item.children) }
          </Menu.SubMenu>
        )
      } else {
        return <Menu.Item key = { item.key }>{ item.label }</Menu.Item>
      }
    })
  }


  return (
    <Sider trigger={null} collapsible collapsed={collapsed}>
      <div className="logo" style={ { 
        display: 'flex', 
        justifyContent: 'center', 
        alignItems: 'center',
        color: '#fff'
      }}>
        <Image src = { logo } width="28px" height="28px" preview={ false }></Image>
        { !collapsed && <div style={
  {
          height: '32px', 
          overflow: 'hidden', 
          lineHeight: '32px'
        }}>嗨购后台管理系统</div> }
      </div>
      <Menu
        theme="dark"
        mode="inline"
        defaultSelectedKeys={['1']}
      >
        {
          renderMenus(menus)
        }
      </Menu>
      
    </Sider>
  );
};

export default App;

组件形式渲染左侧菜单目前并不推荐使用

7.4 菜单渲染优化

如果左侧菜单栏数据过于庞大,每个管理项里又有很多项,需要只展开一个菜单项

// src/layout/components/SideBar.tsx
import React, { useState } from 'react';

import { Layout, Menu, Image } from 'antd';

import type { MenuProps } from 'antd';

import menus from '@/router/menu'

import { useAppSelector } from '@/store/hooks'
import logo from '@/logo.svg'

const { Sider } = Layout;

// 获取哪些项具有二级菜单
const rootSubmenuKeys: string[] = []
menus.forEach(item => {
  if (item.children) {
    rootSubmenuKeys.push(item.key as string)
  }
})

const App: React.FC = () => {
  
  const collapsed = useAppSelector(state => state.app.collapsed)


  const [openKeys, setOpenKeys] = useState(['sub1']);

  const onOpenChange: MenuProps['onOpenChange'] = (keys) => {
    const latestOpenKey = keys.find((key) => openKeys.indexOf(key) === -1);
    if (rootSubmenuKeys.indexOf(latestOpenKey!) === -1) {
      setOpenKeys(keys);
    } else {
      setOpenKeys(latestOpenKey ? [latestOpenKey] : []);
    }
  };


  return (
    <Sider trigger={null} collapsible collapsed={collapsed}>
      <div className="logo" style={ { 
        display: 'flex', 
        justifyContent: 'center', 
        alignItems: 'center',
        color: '#fff'
      }}>
        <Image src = { logo } width="28px" height="28px" preview={ false }></Image>
        { !collapsed && <div style={
  {
          height: '32px', 
          overflow: 'hidden', 
          lineHeight: '32px'
        }}>嗨购后台管理系统</div> }
      </div>
      <Menu
        theme="dark"
        mode="inline"
        defaultSelectedKeys={['1']}
        items={ menus }
        openKeys={openKeys}
        onOpenChange={onOpenChange}
      />
      
    </Sider>
  );
};

export default App;

8.定义路由

8.1 官方文档

Home v6.26.1 | React Router

8.2 创建对应的页面

|-src
|  |- ...
|  |-views
|    |- banner
|    	|- List.tsx     #首页轮播图
|	 |  |- Add.tsx		#添加轮播图
|	 	 |- home
|    |  |- Index.tsx	#系统首页
|    |- pro
|    |  |- List.tsx 	#产品管理
|    |  |- Search.tsx 	#筛选列表
|    |- account
|    |  |- User.tsx #用户列表
|    |  |- Admin.tsx#管理员列表
// src/views/home/Index.tsx

import React, { FC } from 'react';

interface IAppProps {
}

const Com: FC<IAppProps> = (props) => {
  return (
    <div>系统首页</div>
  )
}

export default Com
// src/views/account/Admin.tsx

import React, { FC } from 'react';

interface IAppProps {
}

const Com: FC<IAppProps> = (props) => {
  return (
    <div>管理员列表</div>
  )
}

export default Com
// src/views/account/User.tsx

import React, { FC } from 'react';

interface IAppProps {
}

const Com: FC<IAppProps> = (props) => {
  return (
    <div>用户列表</div>
  )
}

export default Com
// src/views/banner/Add.tsx

import React, { FC } from 'react';

interface IAppProps {
}

const Com: FC<IAppProps> = (props) => {
  return (
    <div>添加轮播图</div>
  )
}

export default Com
// src/views/banner/List.tsx

import React, { FC } from 'react';

interface IAppProps {
}

const Com: FC<IAppProps> = (props) => {
  return (
    <div>轮播图列表</div>
  )
}

export default Com
// src/views/pro/List.tsx

import React, { FC } from 'react';

interface IAppProps {
}

const Com: FC<IAppProps> = (props) => {
  return (
    <div>产品列表</div>
  )
}

export default Com
// src/views/pro/Search.tsx

import React, { FC } from 'react';

interface IAppProps {
}

const Com: FC<IAppProps> = (props) => {
  return (
    <div>筛选列表</div>
  )
}

export defa

8.3 定义菜单路由信息

v6的路由通过 element 属性定义匹配的组件

因此menus中可以添加一个 element 属性,值就为组件的引用即可

// src/router/menus.tsx
import type { MenuProps } from 'antd';
import { HomeOutlined } from '@ant-design/icons'
import { ReactNode } from 'react';

import Home from '@/views/home/Index'

import BannerList from '@/views/banner/List'
import BannerAdd from '@/views/banner/Add'

import ProList from '@/views/pro/List'
import SearchList from '@/views/pro/Search'

import UserList from '@/views/account/User'
import AdminList from '@/views/account/Admin'

type MenuItem = Required<MenuProps>['items'][number];

// 扩展固有的类型
export type IMyMenuItem = MenuItem & {
  path: string; // 如果后期使用key值为跳转地址时,可以不添加 path 属性
  children?: IMyMenuItem[];
  redirect?: string; // 多级菜单的默认地址
  element?: ReactNode
}


const menus: IMyMenuItem[] = [
  {
    path: '/',
    label: '系统首页',
    key: '/',
    icon: <HomeOutlined />,
    element: <Home />
  },
  {
    path: '/banner',
    label: '轮播图管理',
    key: '/banner',
    redirect: '/banner/list',
    icon: <HomeOutlined />,
    children: [
      {
        path: '/banner/list',
        key: '/banner/list',
        label: '轮播图列表',
        icon: <HomeOutlined />,
        element: <BannerList />
      },
      {
        path: '/banner/add',
        key: '/banner/add',
        label: '添加轮播图',
        icon: <HomeOutlined />,
        element: <BannerAdd />
      }
    ]
  },
  {
    path: '/pro',
    label: '产品管理',
    key: '/pro',
    redirect: '/pro/list',
    icon: <HomeOutlined />,
    children: [
      {
        path: '/pro/list',
        key: '/pro/list',
        label: '产品列表',
        icon: <HomeOutlined />,
        element: <ProList />
      },
      {
        path: '/pro/search',
        key: '/pro/search',
        label: '筛选列表',
        icon: <HomeOutlined />,
        element: <SearchList />
      }
    ]
  },
  {
    path: '/account',
    label: '账户管理',
    key: '/account',
    redirect: '/account/user',
    icon: <HomeOutlined />,
    children: [
      {
        path: '/account/user',
        key: '/account/user',
        label: '用户列表',
        icon: <HomeOutlined />,
        element: <UserList />
      },
      {
        path: '/account/admin',
        key: '/account/admin',
        label: '管理员列表',
        icon: <HomeOutlined />,
        element: <AdminList />
      }
    ]
  }
]

export default menus

8.4.装载路由

在根组件添加 BrowserRouter 或者 HashRouter

// src/index.tsx

import React from 'react';
import ReactDOM from 'react-dom/client';

import { ConfigProvider } from 'antd';
import { Provider } from 'react-redux'

import { BrowserRouter } from 'react-router-dom'

import App from './App';
import reportWebVitals from './reportWebVitals';

import 'antd/dist/reset.css'; // antd重置样式表

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLDivElement
);
root.render(
  <React.StrictMode>
    <ConfigProvider
      theme = { {
        token: {
          colorPrimary: '#1890ff'
        }
      } }
    >
      <Provider store = { store }>
        <BrowserRouter>
          <App />
        </BrowserRouter>
      </Provider>
    </ConfigProvider>
  </React.StrictMode>
);

reportWebVitals();

8.5 定义路由组件

menu.tsx里已经定义好了请求的路径(其实就是数据中key属性)和路径对应组件(其实就是数据中的element属性),剩下就是定义路由组件了

组件渲染的区域 AppMain组件

// src/layout/components/AppMain.tsx
import React from 'react';

import { Layout, theme } from 'antd';
import { Routes, Route, Navigate } from 'react-router-dom';

// import BannerAdd from '@/views/banner/Add'
import { IMyMenuItem } from '@/router/menu';
import menus from '@/router/menu'

const { Content } = Layout;

const App: React.FC = () => {
  const {
    token: { colorBgContainer },
  } = theme.useToken();

  const renderRoute: any = (menus: IMyMenuItem[]) => {
    return menus.map(item => {
      if (item.children) {
        // React.Fragment 也为空标签,可以设置 key 属性
        // 实现 重定向 
        return (
          <React.Fragment key = { item.path }>
            <Route path = { item.path } element = { <Navigate to = { item.redirect! } />} />
            {
              renderRoute(item.children!)
            }
          </React.Fragment>
        )
      } else {
        return <Route key = { item.path } path = { item.path } element = { item.element } />
      }
    })
  }
  return (
    <Content
      style={
  {
        margin: '24px 16px',
        padding: 24,
        minHeight: 280,
        background: colorBgContainer,
      }}
    >
      <Routes>
        {/* <Route path="/banner" element = { <Navigate to="/banner/add" /> } /> */}
        {/* <Route path="/banner/add" element = { <BannerAdd /> } /> */}
        { renderRoute(menus) }
      </Routes>
    </Content>
  );
};

export default App;

8.6 手动测试路由

可以在地址栏输入路径,测试是否正常

http://localhost:3000/ 					#系统首页

http://localhost:3000/banner			#轮播图管理
http://localhost:3000/banner/list		#轮播图列表
http://localhost:3000/banner/add		#添加轮播图

http://localhost:3000/pro				#产品管理
http://localhost:3000/pro/search		#筛选列表
http://localhost:3000/pro/list			#产品列表

http://localhost:3000/account			#账户管理
http://localhost:3000/account/user	#用户列表
http://localhost:3000/account/admin	#管理员列表

8.7 设置404页面

// src/views/error/Page404.tsx

import React, { FC } from 'react';

interface IAppProps {
}

const Com: FC<IAppProps> = (props) => {
  return (
    <div>404</div>
  )
}

export default Com
// src/layout/components/AppMain.tsx
import React from 'react';

import { Layout, theme } from 'antd';
import { Routes, Route, Navigate } from 'react-router-dom';

// import BannerAdd from '@/views/banner/Add'

import Page404 from '@/views/error/Page404'
import { IMyMenuItem } from '@/router/menu';
import menus from '@/router/menu'

const { Content } = Layout;

const App: React.FC = () => {
  const {
    token: { colorBgContainer },
  } = theme.useToken();

  const renderRoute: any = (menus: IMyMenuItem[]) => {
    return menus.map(item => {
      if (item.children) {
        // React.Fragment 也为空标签,可以设置 key 属性
        // 实现 重定向 
        return (
          <React.Fragment key = { item.path }>
            <Route path = { item.path } element = { <Navigate to = { item.redirect! } />} />
            {
              renderRoute(item.children!)
            }
          </React.Fragment>
        )
      } else {
        return <Route key = { item.path } path = { item.path } element = { item.element } />
      }
    })
  }
  return (
    <Content
      style={
  {
        margin: '24px 16px',
        padding: 24,
        minHeight: 280,
        background: colorBgContainer,
      }}
    >
      <Routes>
        {/* <Route path="/banner" element = { <Navigate to="/banner/add" /> } /> */}
        {/* <Route path="/banner/add" element = { <BannerAdd /> } /> */}
        { renderRoute(menus) }
        <Route path="*" element = { <Page404 /> } />
      </Routes>
    </Content>
  );
};

export default App;

9 切换路由

上述项目中,切换路由都是手动输入的,实际上应该点击左侧菜单栏进行路由导航。

左侧菜单的逻辑交互,前面已经生成了(openKeys 以及 onOpenChanges 实现)

现在通过点击事件来切换导航

9.1 点击切换路由

// src/layout/components/SideBar.tsx
import React, { useState } from 'react';

import { Layout, Menu, Image } from 'antd';

import type { MenuProps } from 'antd';

import menus from '@/router/menu'

import { useAppSelector } from '@/store/hooks'
import logo from '@/logo.svg'
import { useNavigate } from 'react-router-dom';

const { Sider } = Layout;

// 获取哪些项具有二级菜单
const rootSubmenuKeys: string[] = []
menus.forEach(item => {
  if (item.children) {
    rootSubmenuKeys.push(item.key as string)
  }
})

const App: React.FC = () => {
  
  const collapsed = useAppSelector(state => state.app.collapsed)


  const [openKeys, setOpenKeys] = useState(['']);

  const onOpenChange: MenuProps['onOpenChange'] = (keys) => { 
    // console.log('keys', keys)
    const latestOpenKey = keys.find((key) => openKeys.indexOf(key) === -1);
    // console.log('latestOpenKey', latestOpenKey) // /banner /pro /account
    if (rootSubmenuKeys.indexOf(latestOpenKey!) === -1) {
      setOpenKeys(keys);
    } else {
      setOpenKeys(latestOpenKey ? [latestOpenKey] : []);
    }
  };

  const navigate = useNavigate()
  const changeUrl = ({ key }: { key: string }) => {
    console.log(key)
    navigate(key)
  }
  return (
    <Sider trigger={null} collapsible collapsed={collapsed}>
      <div className="logo" style={ { 
        display: 'flex', 
        justifyContent: 'center', 
        alignItems: 'center',
        color: '#fff'
      }}>
        <Image src = { logo } width="28px" height="28px" preview={ false }></Image>
        { !collapsed && <div style={
  {
          height: '32px', 
          overflow: 'hidden', 
          lineHeight: '32px'
        }}>嗨购后台管理系统</div> }
      </div>
      <Menu
        theme="dark"
        mode="inline"
        defaultSelectedKeys={['1']}
        items={ menus }
        openKeys={openKeys}
        onOpenChange={onOpenChange}
        onClick={changeUrl}
      />
      
    </Sider>
  );
};

export default App;

9.2 刷新保持左侧菜单状态

当页面刷新时,需要保证当前二级路由是展开的,且当前路由是被选中的状态

// src/layout/components/SideBar.tsx
import React, { useState } from 'react';

import { Layout, Menu, Image } from 'antd';

import type { MenuProps } from 'antd';

import menus from '@/router/menu'

import { useAppSelector } from '@/store/hooks'
import logo from '@/logo.svg'
import { useLocation, useNavigate } from 'react-router-dom';

const { Sider } = Layout;

// 获取哪些项具有二级菜单
const rootSubmenuKeys: string[] = []
menus.forEach(item => {
  if (item.children) {
    rootSubmenuKeys.push(item.key as string)
  }
})

const App: React.FC = () => {
  
  const collapsed = useAppSelector(state => state.app.collapsed)

  // /pro/search
  const { pathname } = useLocation() // /pro/search
  // console.log(location)
  const [selectedKeys, setSelectedKeys] = useState([ pathname ]) // ['/pro/search']
  const [openKeys, setOpenKeys] = useState(['/' + pathname.split('/')[1] ]); // ['/pro']
  const onOpenChange: MenuProps['onOpenChange'] = (keys) => { 
    // console.log('keys', keys)
    const latestOpenKey = keys.find((key) => openKeys.indexOf(key) === -1);
    // console.log('latestOpenKey', latestOpenKey) // /banner /pro /account
    if (rootSubmenuKeys.indexOf(latestOpenKey!) === -1) {
      setOpenKeys(keys);
    } else {
      setOpenKeys(latestOpenKey ? [latestOpenKey] : []);
    }
  };

  const navigate = useNavigate()
  const changeUrl = ({ key }: { key: string }) => {
    // console.log(key)
    navigate(key)
    setSelectedKeys([key]) // 点击时需要告诉程序哪一项被选中
  }
  return (
    <Sider trigger={null} collapsible collapsed={collapsed}>
      <div className="logo" style={ { 
        display: 'flex', 
        justifyContent: 'center', 
        alignItems: 'center',
        color: '#fff'
      }}>
        <Image src = { logo } width="28px" height="28px" preview={ false }></Image>
        { !collapsed && <div style={
  {
          height: '32px', 
          overflow: 'hidden', 
          lineHeight: '32px'
        }}>嗨购后台管理系统</div> }
      </div>
      <Menu
        theme="dark"
        mode="inline"
        selectedKeys={ selectedKeys }
        items={ menus }
        openKeys={openKeys}
        onOpenChange={onOpenChange}
        onClick={changeUrl}
      />
      
    </Sider>
  );
};

export default App;

10 设置面包屑导航

10.1 参考文档

通过案例项目,得知 面包屑组件应该包含在 页面的头部 https://vvbin.cn/next/#/feat/breadcrumb/flat

参照组件库的面包屑 https://ant-design.g

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

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

相关文章

第八节:Nodify 编辑器属性

引言 经过前几章的学习&#xff0c;你已经对Nodify框架有了初步的编程思路。当然只局限于这些还完全不够&#xff0c;本章节将阐述各个结构组件的一些常用属性&#xff0c;以便在日后的开发过程中更得心应手。 1、编辑器 平移 简介属性默认值平移功能 控制DisablePanningfals…

了解Swagger规范检查点

目录 检查 Api注解 第一项&#xff1a;是否包含接口的文字描述、接口的类名 检查 ApiOperation注解 第一项&#xff1a;是否包含HTTP请求方法(Get、Post等) 第二项&#xff1a;是否有对方法的简单描述 第三项&#xff1a;是否有对方法的详细描述 检查 ApiResponses注解 …

分享一个基于Python的抖音女装数据分析系统flask毕设女装数据采集系统(源码、调试、LW、开题、PPT)

&#x1f495;&#x1f495;作者&#xff1a;计算机源码社 &#x1f495;&#x1f495;个人简介&#xff1a;本人 八年开发经验&#xff0c;擅长Java、Python、PHP、.NET、Node.js、Android、微信小程序、爬虫、大数据、机器学习等&#xff0c;大家有这一块的问题可以一起交流&…

[数据集][目标检测]电力场景输电线均压环歪斜检测数据集VOC+YOLO格式303张2类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;303 标注数量(xml文件个数)&#xff1a;303 标注数量(txt文件个数)&#xff1a;303 标注类别…

Apache SeaTunnel技术架构演进及其在AI领域的应用

随着数据集成需求的增长&#xff0c;Apache SeaTunnel作为新一代的数据同步引擎&#xff0c;不仅在技术架构上不断演进&#xff0c;也在AI领域展现出其独特的应用价值。在CommunityOverCode Asia 2024大会上&#xff0c;Apache SeaTunnel PMC Chair 高俊 深入探讨SeaTunnel的技…

C++核心编程02——引用

摘录于B站黑马程序员提供的笔记。 1. 引用的基本使用 作用&#xff1a; 给变量起别名 语法&#xff1a; 数据类型 &别名 原名 实例&#xff1a; #include <iostream> using namespace std;int main() {// 引用基本语法// 数据类型 &别名 原名int a 10;in…

开放式耳机怎么戴?五大市场热卖爆款推荐!

开放式耳机的佩戴方法通常比较直观&#xff0c;但具体步骤可能因不同品牌和型号的设计而异。以下是一般的佩戴步骤&#xff1a; 1. 调整耳机&#xff1a;大多数开放式耳机都有可调节的耳挂&#xff0c;首先调整耳挂&#xff0c;确保它能够舒适地适应你的耳朵形状。 2. 定位耳…

FGF20:多些研究关注

成纤维细胞生长因子20&#xff08;FGF20&#xff09;是FGF9亚家族成员&#xff0c;作为调节中枢神经发育和功能的神经营养因子。 &#xff08;数据来源AlphaFold&#xff09; FGF20由208个氨基酸组成&#xff0c;属于分泌型胞外蛋白&#xff0c;无信号肽区段&#xff0c;功能域…

独辟蹊径:找工作时的创新思维——之找到一份工作

一、背景 在日常生活中我们会遇到一些开发者抱怨“资深开发者牢牢占据着岗位&#xff0c;让年轻开发者鲜有工作机会”。与此同时&#xff0c;也有一些开发者抱怨说&#xff1a;没有人愿意招聘上了点年纪的开发者&#xff0c;每个人都在歧视大龄开发者。还有一些人抱怨他们的技…

【网络安全】服务基础阶段——第一节:Windows系统管理基础----进制转换与IP地址

一、进制转换与IP地址 进制与计算&#xff1a; 进制转换是指将一个数字从一个数制&#xff08;基数&#xff09;转换为另一个数制的过程 二进制&#xff08;Binary&#xff09;&#xff1a;基于0和1的数制&#xff0c;例如1011&#xff08;十进制11&#xff09;。八进制&…

网站分类目录提交技巧有哪些

在提交网站到分类目录时&#xff0c;掌握一定的技巧可以显著提升通过率和效果。以下是一些关键的提交技巧&#xff1a; 选择高质量的分类目录&#xff1a; 确保选择的分类目录有足够的流量和权重&#xff0c;这样提交的内容才能得到有效的展示。 考察分类目录的正规性和可信度&…

纷享AI | AI PaaS平台,企业智能转型的加速器

随着人工智能技术的飞速发展&#xff0c;企业对于智能化转型的需求愈发迫切。那么&#xff0c;企业如何把握先机&#xff0c;在激烈的市场竞争中保持领先&#xff1f;答案可能就藏在AI PaaS平台的无限潜力中。纷享销客AI PaaS平台通过Agent Builder和Model Builder为上层的场景…

山体滑坡预警摄像机

山体滑坡是一种常见的地质灾害&#xff0c;给人们的生命和财产安全带来了巨大威胁。为了及时监测山体滑坡的情况并提前预警&#xff0c;可以使用山体滑坡预警摄像机 。山体滑坡预警摄像机是一种结合了摄像技术和智能算法的设备&#xff0c;能够实时监测山体滑坡的变化情况&…

昂科烧录器支持Melexis迈来芯的位置传感器MLX90365KDC

芯片烧录行业领导者-昂科技术近日发布最新的烧录软件更新及新增支持的芯片型号列表&#xff0c;其中Melexis迈来芯的位置传感器MLX90365KDC已经被昂科的通用烧录平台AP8000所支持。 MLX90365KDC是第II代Triaxis位置传感器IC。 这款单片器件可凭借其表面的集磁点(IMC)&#xf…

民大校园学习资料转让网站设计与实现---附源码97053

摘要 在当今数字化时代&#xff0c;学习资料转让网站作为在线学习和教育资源的重要平台&#xff0c;发挥着越来越重要的作用。为了满足用户对学习资料的需求&#xff0c;本论文旨在构建一个可靠高效的学习资料转让网站系统。 NodeJS是一个基于JavaScript的服务器端运行环境&…

【R语言实战】——多模型预测及评价

&#x1f349;CSDN小墨&晓末:https://blog.csdn.net/jd1813346972 个人介绍: 研一&#xff5c;统计学&#xff5c;干货分享          擅长Python、Matlab、R等主流编程软件          累计十余项国家级比赛奖项&#xff0c;参与研究经费10w、40w级横向 文…

langchain入门系列之六 使用langchain构建PDF解析助手

本文将介绍如何使用langchain构建一个pdf解析助手&#xff0c;在此文中你将学习到langchain如何与web应用(fastapi)相结合&#xff0c;向量持久化等知识&#xff0c;话不多说&#xff0c;现在开始。 安装环境 pip install fastapi pip install python-dotenv pip install uv…

文件和注册表关联

注册表是Windows操作系统的信息存储中心&#xff0c;存放着包括计算机硬件配置、已安装软件的设置信息、当前用户的环境设置及某些文件类型与对其进行访问和操作的应用程序之间的联系等重要信息。Windows操作系统早期版本中存放在初始化文件(.ini)中的许多信息现在都存放在注册…

【STM32单片机_(HAL库)】3-4-3【中断EXTI】【智能排队控制系统】排队系统代码框架搭建

3-4-2系统框图及硬件接线 3.软件 beep、exti、gate、LCD1602、led、tasks驱动文件添加GPIO常用函数中断配置流程main.c程序 #include "sys.h" #include "delay.h" #include "led.h" #include "tasks.h" #include "gate.h"…

联华证券-股票冲高回落的意义:上方抛压恢复

“股票冲高回落”是指股票价格在一段时期内迅速上涨至较高水平后&#xff0c;随后又下跌的现象。这种情况通常表明市场对股票的短期上涨缺乏持续的支撑。以下是冲高回落的主要意义和原因&#xff1a; 1.上方抛压较重 抛压是指大量的卖出订单&#xff0c;这些订单可能在股价达到…