当前位置: 首页 > news >正文

react+AntDesign 之 pc端项目案例

1.环境搭建以及初始化目录

CRA是一个底层基于webpack快速创建React项目的脚手架工具
在这里插入图片描述

# 使用npx创建项目
npx create-react-app react-jike# 进入到项
cd react-jike# 启动项目
npm start

2.安装SCSS

SASS 是一种预编译的 CSS,支持一些比较高级的语法,可以提高编写样式的效率,CRA接入scss非常简单只需要我们装一个sass工具

  1. 安装解析 sass 的包:npm i sass -D
  2. 创建全局样式文件:index.scss

3.安装Ant Design

npm install antd --save

4.配置基础路由Router

在这里插入图片描述

npm i react-router-dom

pages/Layout/index.js

const Layout = () => {return <div>this is layout</div>
}
export default Layout

pages/Login/index.js

const Login = () => {return <div>this is login</div>
}
export default Login

router/index.js

import { createBrowserRouter } from 'react-router-dom'import Login from '../pages/Login'
import Layout from '../pages/Layout'const router = createBrowserRouter([{path: '/',element: <Layout />,},{path: '/login',element: <Login />,},
])export default router

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'ReactDOM.createRoot(document.getElementById('root')).render(<RouterProvider router={router} />
)

5.配置别名路径@

  1. 安装 craco 工具包
  2. 增加 craco.config.js 配置文件
  3. 修改 scripts 命令
  4. 测试是否生效
npm i @craco/craco -D
const path = require('path')module.exports = {// webpack 配置webpack: {// 配置别名alias: {// 约定:使用 @ 表示 src 文件所在路径'@': path.resolve(__dirname, 'src')}}
}
"scripts": {"start": "craco start","build": "craco build","test": "craco test","eject": "react-scripts eject"
}
import { createBrowserRouter } from 'react-router-dom'import Login from '@/pages/Login'
import Layout from '@/pages/Layout'const router = createBrowserRouter([{path: '/',element: <Layout />,},{path: '/login',element: <Login />,},
])export default router

VsCode提示配置

实现步骤

  1. 在项目根目录创建 jsconfig.json 配置文件
  2. 在配置文件中添加以下配置

代码实现

{"compilerOptions": {"baseUrl": "./","paths": {"@/*": ["src/*"]}}
}

:::warning
说明:VSCode会自动读取jsconfig.json 中的配置,让vscode知道@就是src目录
:::

6.gitee管理项目

在这里插入图片描述
在这里插入图片描述

git remote add origin http
git add .
git commit -m 'init'
git push

7.登陆模块开发

**实现步骤**1. 在 `Login/index.js` 中创建登录页面基本结构2. 在 Login 目录中创建 index.scss 文件,指定组件样式3. 将 `logo.png` 和 `login.png` 拷贝到 assets 目录中

代码实现
pages/Login/index.js

import './index.scss'
import { Card, Form, Input, Button } from 'antd'
import logo from '@/assets/logo.png'const Login = () => {return (<div className="login"><Card className="login-container"><img className="login-logo" src={logo} alt="" />{/* 登录表单 */}<Form><Form.Item><Input size="large" placeholder="请输入手机号" /></Form.Item><Form.Item><Input size="large" placeholder="请输入验证码" /></Form.Item><Form.Item><Button type="primary" htmlType="submit" size="large" block>登录</Button></Form.Item></Form></Card></div>)
}export default Login

pages/Login/index.scss

.login {width: 100%;height: 100%;position: absolute;left: 0;top: 0;background: center/cover url('~@/assets/login.png');.login-logo {width: 200px;height: 60px;display: block;margin: 0 auto 20px;}.login-container {width: 440px;height: 360px;position: absolute;left: 50%;top: 50%;transform: translate(-50%, -50%);box-shadow: 0 0 50px rgb(0 0 0 / 10%);}.login-checkbox-label {color: #1890ff;}
}

表单校验实现

**实现步骤**
1. 为 Form 组件添加 `validateTrigger` 属性,指定校验触发时机的集合
2. **为 Form.Item 组件添加 name 属性**
3. 为 Form.Item 组件添加 `rules` 属性,用来添加表单校验规则对象

代码实现
page/Login/index.js

const Login = () => {return (<Form validateTrigger={['onBlur']}><Form.Itemname="mobile"rules={[{ required: true, message: '请输入手机号' },{pattern: /^1[3-9]\d{9}$/,message: '手机号码格式不对'}]}><Input size="large" placeholder="请输入手机号" /></Form.Item><Form.Itemname="code"rules={[{ required: true, message: '请输入验证码' },]}><Input size="large" placeholder="请输入验证码" maxLength={6} /></Form.Item><Form.Item><Button type="primary" htmlType="submit" size="large" block>登录</Button></Form.Item></Form>)
}

获取登录表单数据

**实现步骤**
1. 为 Form 组件添加 `onFinish` 属性,该事件会在点击登录按钮时触发
2. 创建 onFinish 函数,通过函数参数 values 拿到表单值
3. Form 组件添加 `initialValues` 属性,来初始化表单值

代码实现
pages/Login/index.js

// 点击登录按钮时触发 参数values即是表单输入数据
const onFinish = formValue => {console.log(formValue)
}<FormonFinish={ onFinish }
>...</Form>

封装request工具模块

业务背景: 前端需要和后端拉取接口数据,axios是使用最广的工具插件,针对于项目中的使用,我们需要做一些简单的封装

 **实现步骤**1. 安装 axios 到项目  2. 创建 utils/request.js 文件3. 创建 axios 实例,配置 `baseURL,请求拦截器,响应拦截器` 4. 在 utils/index.js 中,统一导出request
npm i axios
import axios from 'axios'const http = axios.create({baseURL: 'http://geek.itheima.net/v1_0',timeout: 5000
})// 添加请求拦截器
http.interceptors.request.use((config)=> {return config}, (error)=> {return Promise.reject(error)
})// 添加响应拦截器
http.interceptors.response.use((response)=> {// 2xx 范围内的状态码都会触发该函数。// 对响应数据做点什么return response.data}, (error)=> {// 超出 2xx 范围的状态码都会触发该函数。// 对响应错误做点什么return Promise.reject(error)
})export { http }
import { request } from './request'
export { request }

使用Redux管理token

npm i react-redux @reduxjs/toolkit //安装Redux相关工具包

配置Redux

import { createSlice } from '@reduxjs/toolkit'
import { http } from '@/utils'
const userStore = createSlice({name: 'user',// 数据状态initialState: {token:''},// 同步修改方法reducers: {setUserInfo (state, action) {state.userInfo = action.payload}}
})// 解构出actionCreater
const { setUserInfo } = userStore.actions// 获取reducer函数
const userReducer = userStore.reducer// 异步方法封装
const fetchLogin = (loginForm) => {return async (dispatch) => {const res = await http.post('/authorizations', loginForm)dispatch(setUserInfo(res.data.token))}
}export { fetchLogin }export default userReducer

store下index.js

import { configureStore } from '@reduxjs/toolkit'import userReducer from './modules/user'export default configureStore({reducer: {// 注册子模块user: userReducer}
})

实现登录逻辑

业务逻辑:

  1. 跳转到首页
  2. 提示用户登录成功
import { message } from 'antd'
import useStore from '@/store'
import { fetchLogin } from '@/store/modules/user'
import { useDispatch } from 'react-redux'const Login = () => {const dispatch = useDispatch()const navigate = useNavigate()const onFinish = async formValue => {await dispatch(fetchLogin(formValue))navigate('/')message.success('登录成功')}return (<div className="login"><!-- 省略... --></div>)
}export default Login

token持久化

业务背景: Token数据具有一定的时效时间,通常在几个小时,有效时间内无需重新获取,而基于Redux的存储方式又是基于内存的,刷新就会丢失,为了保持持久化,我们需要单独做处理
在这里插入图片描述

封装存取方法

// 封装存取方法const TOKENKEY = 'token_key'function setToken (token) {return localStorage.setItem(TOKENKEY, token)
}function getToken () {return localStorage.getItem(TOKENKEY)
}function clearToken () {return localStorage.removeItem(TOKENKEY)
}export {setToken,getToken,clearToken
}

实现持久化逻辑

import { createSlice } from '@reduxjs/toolkit'
import { setToken as _setToken, getToken, removeToken } from '@/utils'
import { loginAPI, getProfileAPI } from '@/apis/user'const userStore = createSlice({name: "user",// 数据状态initialState: {token: getToken() || '',userInfo: {}},// 同步修改方法reducers: {setToken (state, action) {state.token = action.payload_setToken(action.payload)},setUserInfo (state, action) {state.userInfo = action.payload},clearUserInfo (state) {state.token = ''state.userInfo = {}removeToken()}}
})// 解构出actionCreaterconst { setToken, setUserInfo, clearUserInfo } = userStore.actions// 获取reducer函数const userReducer = userStore.reducer// 登录获取token异步方法封装
const fetchLogin = (loginForm) => {return async (dispatch) => {const res = await loginAPI(loginForm)dispatch(setToken(res.data.token))}
}// 获取个人用户信息异步方法
const fetchUserInfo = () => {return async (dispatch) => {const res = await getProfileAPI()dispatch(setUserInfo(res.data))}
}export { fetchLogin, fetchUserInfo, clearUserInfo }export default userReducer

刷新浏览器,通过Redux调试工具查看token数据

请求拦截器注入token

业务背景: Token作为用户的数据标识,在接口层面起到了接口权限控制的作用,也就是说后端有很多接口都需要通过查看当前请求头信息中是否含有token数据,来决定是否正常返回数据
在这里插入图片描述

拼接方式:config.headers.Authorization = Bearer ${token}}

utils/request.js

// 添加请求拦截器
request.interceptors.request.use(config => {// if not login add tokenconst token = getToken()if (token) {config.headers.Authorization = `Bearer ${token}`}return config
})
// 添加响应拦截器
// 在响应返回到客户端之前 做拦截 重点处理返回的数据
request.interceptors.response.use((response) => {// 2xx 范围内的状态码都会触发该函数。// 对响应数据做点什么return response.data
}, (error) => {// 超出 2xx 范围的状态码都会触发该函数。// 对响应错误做点什么// 监控401 token失效console.dir(error)if (error.response.status === 401) {removeToken()router.navigate('/login')window.location.reload()}return Promise.reject(error)
})export { request }

路由鉴权实现

业务背景:封装 AuthRoute 路由鉴权高阶组件,实现未登录拦截,并跳转到登录页面
实现思路:判断本地是否有token,如果有,就返回子组件,否则就重定向到登录Login
在这里插入图片描述

**实现步骤**
1. 在 components 目录中,创建 `AuthRoute/index.jsx` 文件
2. 登录时,直接渲染相应页面组件
3. 未登录时,重定向到登录页面
4. 将需要鉴权的页面路由配置,替换为 AuthRoute 组件渲染

代码实现
components/AuthRoute/index.jsx

import { getToken } from '@/utils'
import { Navigate } from 'react-router-dom'const AuthRoute = ({ children }) => {const isToken = getToken()if (isToken) {return <>{children}</>} else {return <Navigate to="/login" replace />}
}export default AuthRoute

src/router/index.jsx

import { createBrowserRouter } from 'react-router-dom'import Login from '@/pages/Login'
import Layout from '@/pages/Layout'
import AuthRoute from '@/components/Auth'const router = createBrowserRouter([{path: '/',element: <AuthRoute><Layout /></AuthRoute>,},{path: '/login',element: <Login />,},
])export default router

9.layout模块

基本结构和样式reset

在这里插入图片描述

实现步骤

  1. 打开 antd/Layout 布局组件文档,找到示例:顶部-侧边布局-通栏
  2. 拷贝示例代码到我们的 Layout 页面中
  3. 分析并调整页面布局

代码实现
pages/Layout/index.js

import { Layout, Menu, Popconfirm } from 'antd'
import {HomeOutlined,DiffOutlined,EditOutlined,LogoutOutlined,
} from '@ant-design/icons'
import './index.scss'const { Header, Sider } = Layoutconst items = [{label: '首页',key: '1',icon: <HomeOutlined />,},{label: '文章管理',key: '2',icon: <DiffOutlined />,},{label: '创建文章',key: '3',icon: <EditOutlined />,},
]const GeekLayout = () => {return (<Layout><Header className="header"><div className="logo" /><div className="user-info"><span className="user-name">柴柴老师</span><span className="user-logout"><Popconfirm title="是否确认退出?" okText="退出" cancelText="取消"><LogoutOutlined /> 退出</Popconfirm></span></div></Header><Layout><Sider width={200} className="site-layout-background"><Menumode="inline"theme="dark"defaultSelectedKeys={['1']}items={items}style={{ height: '100%', borderRight: 0 }}></Menu></Sider><Layout className="layout-content" style={{ padding: 20 }}>内容</Layout></Layout></Layout>)
}
export default GeekLayout

pages/Layout/index.scss

.ant-layout {height: 100%;
}.header {padding: 0;
}.logo {width: 200px;height: 60px;background: url('~@/assets/logo.png') no-repeat center / 160px auto;
}.layout-content {overflow-y: auto;
}.user-info {position: absolute;right: 0;top: 0;padding-right: 20px;color: #fff;.user-name {margin-right: 20px;}.user-logout {display: inline-block;cursor: pointer;}
}
.ant-layout-header {padding: 0 !important;
}

样式reset

npm install normalize.css
html,
body {margin: 0;height: 100%;
}#root {height: 100%;
}

二级路由配置

在这里插入图片描述

使用步骤

  1. 在 pages 目录中,分别创建:Home(数据概览)/Article(内容管理)/Publish(发布文章)页面文件夹
  2. 分别在三个文件夹中创建 index.jsx 并创建基础组件后导出
  3. router/index.js 中配置嵌套子路由,在Layout中配置二级路由出口
  4. 使用 Link 修改左侧菜单内容,与子路由规则匹配实现路由切换

代码实现
pages/Home/index.js

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

pages/Article/index.js

const Article = () => {return <div>Article</div>
}
export default Article

pages/Publish/index.js

const Publish = () => {return <div>Publish</div>
}
export default Publish

router/index.js

import { createBrowserRouter } from 'react-router-dom'import Login from '@/pages/Login'
import Layout from '@/pages/Layout'
import Publish from '@/pages/Publish'
import Article from '@/pages/Article'
import Home from '@/pages/Home'
import { AuthRoute } from '@/components/Auth'const router = createBrowserRouter([{path: '/',element: (<AuthRoute><Layout /></AuthRoute>),children: [{index: true,element: <Home />,},{path: 'article',element: <Article />,},{path: 'publish',element: <Publish />,},],},{path: '/login',element: <Login />,},
])export default router

配置二级路由出口

<Layout className="layout-content" style={{ padding: 20 }}><Outlet />
</Layout>

路由菜单点击交互实现

在这里插入图片描述

点击菜单跳转路由

import { Outlet, useNavigate } from 'react-router-dom'const items = [{label: '首页',key: '/',icon: <HomeOutlined />,},{label: '文章管理',key: '/article',icon: <DiffOutlined />,},{label: '创建文章',key: '/publish',icon: <EditOutlined />,},
]const GeekLayout = () => {const navigate = useNavigate()const menuClick = (route) => {navigate(route.key)}return (<Menumode="inline"theme="dark"selectedKeys={selectedKey}items={items}style={{ height: '100%', borderRight: 0 }}onClick={menuClick}/> )
}
export default GeekLayout

菜单反向高亮

const GeekLayout = () => {// 省略部分代码const location = useLocation()const selectedKey = location.pathnamereturn (<Layout><Header className="header"><div className="logo" /><div className="user-info"><span className="user-name">{name}</span><span className="user-logout"><Popconfirm title="是否确认退出?" okText="退出" cancelText="取消"><LogoutOutlined /> 退出</Popconfirm></span></div></Header><Layout><Sider width={200} className="site-layout-background"><Menumode="inline"theme="dark"selectedKeys={selectedKey}items={items}style={{ height: '100%', borderRight: 0 }}onClick={menuClickHandler}></Menu></Sider><Layout className="layout-content" style={{ padding: 20 }}><Outlet /></Layout></Layout></Layout>)
}

展示个人信息

在这里插入图片描述

实现步骤

  1. 在Redux的store中编写获取用户信息的相关逻辑
  2. 在Layout组件中触发action的执行
  3. 在Layout组件使用使用store中的数据进行用户名的渲染

代码实现
store/userStore.js

import { createSlice } from '@reduxjs/toolkit'
import { http } from '@/utils/request'
import { getToken, setToken } from '@/utils'
const userStore = createSlice({name: 'user',// 数据initialState: {token: getToken() || '',userInfo: {}},// 同步修改方法reducers: {setUserToken (state, action) {state.token = action.payload// 存入本地setToken(state.token)},setUserInfo (state, action) {state.userInfo = action.payload}}
})// 解构出actionCreater
const { setUserToken, setUserInfo } = userStore.actions// 获取reducer函数
const userReducer = userStore.reducerconst fetchLogin = (loginForm) => {return async (dispatch) => {const res = await http.post('/authorizations', loginForm)dispatch(setUserToken(res.data.token))}
}const fetchUserInfo = () => {return async (dispatch) => {const res = await http.get('/user/profile')dispatch(setUserInfo(res.data))}
}export { fetchLogin, fetchUserInfo }export default userReducer

pages/Layout/index.js

// 省略部分代码
import { fetchUserInfo } from '@/store/modules/user'
import { useDispatch, useSelector } from 'react-redux'const GeekLayout = () => {const dispatch = useDispatch()const name = useSelector(state => state.user.userInfo.name)useEffect(() => {dispatch(fetchUserInfo())}, [dispatch])return (<Layout><Header className="header"><div className="logo" /><div className="user-info"><span className="user-name">{name}</span><span className="user-logout"><Popconfirm title="是否确认退出?" okText="退出" cancelText="取消"><LogoutOutlined /> 退出</Popconfirm></span></div></Header><Layout><Sider width={200} className="site-layout-background"><Menumode="inline"theme="dark"defaultSelectedKeys={['1']}items={items}style={{ height: '100%', borderRight: 0 }}></Menu></Sider><Layout className="layout-content" style={{ padding: 20 }}><Outlet /></Layout></Layout></Layout>)
}
export default GeekLayout

退出登录实现

实现步骤

  1. 为气泡确认框添加确认回调事件
  2. store/userStore.js 中新增退出登录的action函数,在其中删除token
  3. 在回调事件中,调用userStore中的退出action
  4. 清除用户信息,返回登录页面

代码实现
store/modules/user.js

import { createSlice } from '@reduxjs/toolkit'
import { http } from '@/utils/request'
import { clearToken, getToken, setToken } from '@/utils'
const userStore = createSlice({name: 'user',// 数据initialState: {token: getToken() || '',userInfo: {}},// 同步修改方法reducers: {setUserToken (state, action) {state.token = action.payload// 存入本地setToken(state.token)},setUserInfo (state, action) {state.userInfo = action.payload},clearUserInfo (state) {state.token = ''state.userInfo = {}clearToken()}}
})// 解构出actionCreater
const { setUserToken, setUserInfo, clearUserInfo } = userStore.actions// 获取reducer函数
const userReducer = userStore.reducerexport { fetchLogin, fetchUserInfo, clearUserInfo }export default userReducer

pages/Layout/index.js

const GeekLayout = () => {// 退出登录const loginOut = () => {dispatch(clearUserInfo())navigator('/login')}return (<Layout><Header className="header"><div className="logo" /><div className="user-info"><span className="user-name">{name}</span><span className="user-logout"><Popconfirm title="是否确认退出?" okText="退出" cancelText="取消" onConfirm={loginOut}><LogoutOutlined /> 退出</Popconfirm></span></div></Header><Layout><Sider width={200} className="site-layout-background"><Menumode="inline"theme="dark"selectedKeys={selectedKey}items={items}style={{ height: '100%', borderRight: 0 }}onClick={menuClickHandler}></Menu></Sider><Layout className="layout-content" style={{ padding: 20 }}><Outlet /></Layout></Layout></Layout>)
}

处理Token失效

业务背景:如果用户一段时间不做任何操作,到时之后应该清除所有过期用户信息跳回到登录

http.interceptors.response.use((response) => {// 2xx 范围内的状态码都会触发该函数。// 对响应数据做点什么return response.data
}, (error) => {// 超出 2xx 范围的状态码都会触发该函数。// 对响应错误做点什么console.dir(error)if (error.response.status === 401) {clearToken()router.navigate('/login')window.location.reload()}return Promise.reject(error)
})

首页Home图表展示

图表基础Demo实现

图表类业务渲染,我们可以通过下面的顺序来实现

  1. 跑通基础DEMO
  2. 按照实际业务需求进行修改

安装echarts

npm i echarts

实现基础Demo

import { useEffect, useRef } from 'react'
import * as echarts from 'echarts'const Home = () => {const chartRef = useRef(null)useEffect(() => {// 1. 生成实例const myChart = echarts.init(chartRef.current)// 2. 准备图表参数const option = {xAxis: {type: 'category',data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']},yAxis: {type: 'value'},series: [{data: [120, 200, 150, 80, 70, 110, 130],type: 'bar'}]}// 3. 渲染参数myChart.setOption(option)}, [])return (<div><div ref={chartRef} style={{ width: '400px', height: '300px' }} /></div >)
}export default Home

组件封装

在这里插入图片描述

基础抽象

import { useRef, useEffect } from 'react'
import * as echarts from 'echarts'const BarChart = () => {const chartRef = useRef(null)useEffect(() => {// 1. 生成实例const myChart = echarts.init(chartRef.current)// 2. 准备图表参数const option = {xAxis: {type: 'category',data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']},yAxis: {type: 'value'},series: [{data: [120, 200, 150, 80, 70, 110, 130],type: 'bar'}]}// 3. 渲染参数myChart.setOption(option)}, [])return <div ref={chartRef} style={{ width: '400px', height: '300px' }}></div>
}export { BarChart }

抽象可变参数

import { useRef, useEffect } from 'react'
import * as echarts from 'echarts'const BarChart = ({ xData, sData, style = { width: '400px', height: '300px' } }) => {const chartRef = useRef(null)useEffect(() => {// 1. 生成实例const myChart = echarts.init(chartRef.current)// 2. 准备图表参数const option = {xAxis: {type: 'category',data: xData},yAxis: {type: 'value'},series: [{data: sData,type: 'bar'}]}// 3. 渲染参数myChart.setOption(option)}, [sData, xData])return <div ref={chartRef} style={style}></div>
}export { BarChart }

import { BarChart } from './BarChart'const Home = () => {return (<div><BarChartxData={['Vue', 'React', 'Angular']}sData={[2000, 5000, 1000]} /><BarChartxData={['Vue', 'React', 'Angular']}sData={[200, 500, 100]}style={{ width: '500px', height: '400px' }} /></div >)
}export default Home

10.API模块封装

在这里插入图片描述

// 用户相关的所有请求
import { request } from "@/utils"
// 1. 登录请求export function loginAPI (formData) {return request({url: '/authorizations',method: 'POST',data: formData})
}// 2. 获取用户信息export function getProfileAPI () {return request({url: '/user/profile',method: 'GET'})
}

11.发布文章模块

在这里插入图片描述

实现基础文章发布

创建基础结构

import {Card,Breadcrumb,Form,Button,Radio,Input,Upload,Space,Select
} from 'antd'
import { PlusOutlined } from '@ant-design/icons'
import { Link } from 'react-router-dom'
import './index.scss'const { Option } = Selectconst Publish = () => {return (<div className="publish"><Cardtitle={<Breadcrumb items={[{ title: <Link to={'/'}>首页</Link> },{ title: '发布文章' },]}/>}><FormlabelCol={{ span: 4 }}wrapperCol={{ span: 16 }}initialValues={{ type: 1 }}><Form.Itemlabel="标题"name="title"rules={[{ required: true, message: '请输入文章标题' }]}><Input placeholder="请输入文章标题" style={{ width: 400 }} /></Form.Item><Form.Itemlabel="频道"name="channel_id"rules={[{ required: true, message: '请选择文章频道' }]}><Select placeholder="请选择文章频道" style={{ width: 400 }}><Option value={0}>推荐</Option></Select></Form.Item><Form.Itemlabel="内容"name="content"rules={[{ required: true, message: '请输入文章内容' }]}></Form.Item><Form.Item wrapperCol={{ offset: 4 }}><Space><Button size="large" type="primary" htmlType="submit">发布文章</Button></Space></Form.Item></Form></Card></div>)
}export default Publish

pages/Publish/index.scss

.publish {position: relative;
}.ant-upload-list {.ant-upload-list-picture-card-container,.ant-upload-select {width: 146px;height: 146px;}
}

准备富文本编辑器

**实现步骤**1. 安装富文本编辑器 2. 导入富文本编辑器组件以及样式文件3. 渲染富文本编辑器组件4. 调整富文本编辑器的样式

代码落地
1-安装 react-quill

npm i react-quill@2.0.0-beta.2

2-导入资源渲染组件

import ReactQuill from 'react-quill'
import 'react-quill/dist/quill.snow.css'const Publish = () => {return (// ...<FormlabelCol={{ span: 4 }}wrapperCol={{ span: 16 }}><Form.Itemlabel="内容"name="content"rules={[{ required: true, message: '请输入文章内容' }]}><ReactQuillclassName="publish-quill"theme="snow"placeholder="请输入文章内容"/></Form.Item></Form>)
}
.publish-quill {.ql-editor {min-height: 300px;}
}

频道数据获取

实现步骤

  1. 使用useState初始化数据和修改数据的方法
  2. 在useEffect中调用接口并保存数据
  3. 使用数据渲染对应模版

代码实现

import { http } from '@/utils'// 频道列表
const [channels, setChannels] = useState([])// 调用接口
useEffect(() => {async function fetchChannels() {const res = await http.get('/channels')setChannels(res.data.channels)}fetchChannels()
}, [])// 模板渲染
return (<Form.Itemlabel="频道"name="channel_id"rules={[{ required: true, message: '请选择文章频道' }]}><Select placeholder="请选择文章频道" style={{ width: 200 }}>{channels.map(item => (<Option key={item.id} value={item.id}>{item.name}</Option>))}</Select></Form.Item>
)

发布文章

// 发布文章
const onFinish = async (formValue) => {const { channel_id, content, title } = formValueconst params = {channel_id,content,title,type: 1,cover: {type: 1,images: []}}await http.post('/mp/articles?draft=false', params)message.success('发布文章成功')
}

上传封面实现

在这里插入图片描述

准备上传结构

<Form.Item label="封面"><Form.Item name="type"><Radio.Group><Radio value={1}>单图</Radio><Radio value={3}>三图</Radio><Radio value={0}>无图</Radio></Radio.Group></Form.Item><UploadlistType="picture-card"showUploadList><div style={{ marginTop: 8 }}><PlusOutlined /></div></Upload>
</Form.Item>

实现基础上传

实现步骤

  1. 为 Upload 组件添加 action 属性,配置封面图片上传接口地址
  2. 为 Upload组件添加 name属性, 接口要求的字段名
  3. 为 Upload 添加 onChange 属性,在事件中拿到当前图片数据,并存储到React状态中

代码实现

import { useState } from 'react'const Publish = () => {// 上传图片const [imageList, setImageList] = useState([])const onUploadChange = (info) => {setImageList(info.fileList)}return (<Form.Item label="封面"><Form.Item name="type"><Radio.Group><Radio value={1}>单图</Radio><Radio value={3}>三图</Radio><Radio value={0}>无图</Radio></Radio.Group></Form.Item><Uploadname="image"listType="picture-card"showUploadListaction={'http://geek.itheima.net/v1_0/upload'}onChange={onUploadChange}><div style={{ marginTop: 8 }}><PlusOutlined /></div></Upload></Form.Item>)
}

切换图片Type

**实现步骤**
1. 点击单选框时拿到当前的类型value
2. 根据value控制上传组件的显示(大于零时才显示)
const Publish = ()=>{// 控制图片Typeconst [imageType, setImageType] = useState(0)const onTypeChange = (e) => {console.log(e)setImageType(e.target.value)}return (<FormItem><Radio.Group onChange={onTypeChange}><Radio value={1}>单图</Radio><Radio value={3}>三图</Radio><Radio value={0}>无图</Radio></Radio.Group>{imageType > 0 &&<Uploadname="image"listType="picture-card"showUploadListaction={'http://geek.itheima.net/v1_0/upload'}onChange={onUploadChange}><div style={{ marginTop: 8 }}><PlusOutlined /></div></Upload>}</FormItem>)
}

控制最大上传图片数量

实现步骤

  1. 通过 maxCount 属性限制图片的上传图片数量
{imageType > 0 &&
<Uploadname="image"listType="picture-card"className="avatar-uploader"showUploadListaction={'http://geek.itheima.net/v1_0/upload'}onChange={onUploadChange}maxCount={imageType}multiple={imageType > 1}
><div style={{ marginTop: 8 }}><PlusOutlined /></div>
</Upload>}

暂存图片列表实现

业务描述
如果当前为三图模式,已经完成了上传,选择单图只显示一张,再切换到三图继续显示三张,该如何实现?

实现思路
在上传完毕之后通过ref存储所有图片,需要几张就显示几张,其实也就是把ref当仓库,用多少拿多少

实现步骤

  1. 通过useRef创建一个暂存仓库,在上传完毕图片的时候把图片列表存入
  2. 如果是单图模式,就从仓库里取第一张图,以数组的形式存入fileList
  3. 如果是三图模式,就把仓库里所有的图片,以数组的形式存入fileList

代码实现

const Publish = () => {// 上传图片const cacheImageList = useRef([])const [imageList, setImageList] = useState([])const onUploadChange = (info) => {setImageList(info.fileList)cacheImageList.current = info.fileList}// 控制图片Typeconst [imageType, setImageType] = useState(0)const onRadioChange = (e) => {const type = e.target.valuesetImageType(type)if (type === 1) {// 单图,截取第一张展示const imgList = cacheImageList.current[0] ? [cacheImageList.current[0]] : []setImageList(imgList)} else if (type === 3) {// 三图,取所有图片展示setImageList(cacheImageList.current)}}return ({imageType > 0 &&<Uploadname="image"listType="picture-card"className="avatar-uploader"showUploadListaction={'http://geek.itheima.net/v1_0/upload'}onChange={onUploadChange}maxCount={imageType}multiple={imageType > 1}fileList={imageList}><div style={{ marginTop: 8 }}><PlusOutlined /></div></Upload>}
)
}

注意:需要给Upload组件添加fileList属性,达成受控的目的

发布带封面的文章

校验图片类型和数量是否吻合

// 发布文章const onFinish = async (formValue) => {if (imageType !== imageList.length) return message.warning('图片类型和数量不一致')const { channel_id, content, title } = formValueconst params = {channel_id,content,title,type: imageType,cover: {type: imageType,images: imageList.map(item => item.response.data.url)}}await http.post('/mp/articles?draft=false', params)message.success('发布文章成功')}

12.文章列表模块

静态结构创建

在这里插入图片描述

筛选区结构搭建

  1. 如何让RangePicker日期范围选择框选择中文
  2. Select组件配合Form.Item使用时,如何配置默认选中项
    <Form initialValues={{ status: null }} >

代码实现

import { Link } from 'react-router-dom'
import { Card, Breadcrumb, Form, Button, Radio, DatePicker, Select } from 'antd'
import locale from 'antd/es/date-picker/locale/zh_CN'const { Option } = Select
const { RangePicker } = DatePickerconst Article = () => {return (<div><Cardtitle={<Breadcrumb items={[{ title: <Link to={'/'}>首页</Link> },{ title: '文章列表' },]} />}style={{ marginBottom: 20 }}><Form initialValues={{ status: '' }}><Form.Item label="状态" name="status"><Radio.Group><Radio value={''}>全部</Radio><Radio value={0}>草稿</Radio><Radio value={2}>审核通过</Radio></Radio.Group></Form.Item><Form.Item label="频道" name="channel_id"><Selectplaceholder="请选择文章频道"defaultValue="lucy"style={{ width: 120 }}><Option value="jack">Jack</Option><Option value="lucy">Lucy</Option></Select></Form.Item><Form.Item label="日期" name="date">{/* 传入locale属性 控制中文显示*/}<RangePicker locale={locale}></RangePicker></Form.Item><Form.Item><Button type="primary" htmlType="submit" style={{ marginLeft: 40 }}>筛选</Button></Form.Item></Form></Card></div>)
}export default Article

表格区域结构

代码实现

// 导入资源
import { Table, Tag, Space } from 'antd'
import { EditOutlined, DeleteOutlined } from '@ant-design/icons'
import img404 from '@/assets/error.png'const Article = () => {// 准备列数据const columns = [{title: '封面',dataIndex: 'cover',width: 120,render: cover => {return <img src={cover.images[0] || img404} width={80} height={60} alt="" />}},{title: '标题',dataIndex: 'title',width: 220},{title: '状态',dataIndex: 'status',render: data => <Tag color="green">审核通过</Tag>},{title: '发布时间',dataIndex: 'pubdate'},{title: '阅读数',dataIndex: 'read_count'},{title: '评论数',dataIndex: 'comment_count'},{title: '点赞数',dataIndex: 'like_count'},{title: '操作',render: data => {return (<Space size="middle"><Button type="primary" shape="circle" icon={<EditOutlined />} /><Buttontype="primary"dangershape="circle"icon={<DeleteOutlined />}/></Space>)}}]// 准备表格body数据const data = [{id: '8218',comment_count: 0,cover: {images: [],},like_count: 0,pubdate: '2019-03-11 09:00:00',read_count: 2,status: 2,title: 'wkwebview离线化加载h5资源解决方案'}]return (<div>{/*        */}<Card title={`根据筛选条件共查询到 count 条结果:`}><Table rowKey="id" columns={columns} dataSource={data} /></Card></div>)
}

渲染频道数据

实现步骤

  1. 使用axios获取数据
  2. 将使用频道数据列表改写下拉框组件

代码实现
pages/Article/index.js

const Article = ()=>{// 获取频道列表const [channels, setChannels] = useState([])useEffect(() => {async function fetchChannels() {const res = await http.get('/channels')setChannels(res.data.channels)}fetchChannels()}, [])// 渲染模板
return (<Form.Item label="频道" name="channel_id" ><Select placeholder="请选择频道" style={{ width: 200 }} >{channels.map(item => (<Option key={item.id} value={item.id}>{item.name}</Option>))}</Select></Form.Item>)
}

在这里插入图片描述
hooks/useChannel.js

// 封装获取频道列表的逻辑
import { useState, useEffect } from 'react'
import { getChannelAPI } from '@/apis/article'
function useChannel () {// 1. 获取频道列表所有的逻辑// 获取频道列表const [channelList, setChannelList] = useState([])useEffect(() => {// 1. 封装函数 在函数体内调用接口const getChannelList = async () => {const res = await getChannelAPI()setChannelList(res.data.channels)}// 2. 调用函数getChannelList()}, [])// 2. 把组件中要用到的数据return出去return {channelList}
}export { useChannel }

pages/Article/index.js

import { useChannel } from '@/hooks/useChannel'
const { channelList } = useChannel()
<Form.Item label="频道" name="channel_id"><Select placeholder="请选择文章频道" style={{ width: 120 }}>{channelList.map(item => <Option key={item.id} value={item.id}>{item.name}</Option>)}</Select>
</Form.Item>

渲染表格数据

实现步骤

  1. 声明列表相关数据管理
  2. 使用useState声明参数相关数据管理
  3. 调用接口获取数据
  4. 使用接口数据渲染模板

代码实现

const Article = ()=>{// 省略部分代码...// 文章列表数据管理const [article, setArticleList] = useState({list: [],count: 0})const [params, setParams] = useState({page: 1,per_page: 4,begin_pubdate: null,end_pubdate: null,status: null,channel_id: null})useEffect(() => {async function fetchArticleList () {const res = await http.get('/mp/articles', { params })const { results, total_count } = res.datasetArticleList({list: results,count: total_count})}fetchArticleList()}, [params])// 模板渲染return (<Card title={`根据筛选条件共查询到 ${article.count} 条结果:`}><TabledataSource={article.list}columns={columns}/></Card>)
}

筛选功能实现

实现步骤

  1. 为表单添加onFinish属性监听表单提交事件,获取参数
  2. 根据接口字段格式要求格式化参数格式
  3. 修改params 参数并重新使用新参数重新请求数据

代码实现

// 获取文章列表const [list, setList] = useState([])const [count, setCount] = useState(0)async function getList (reqData = {}) {const res = await getArticleListAPI(reqData)setList(res.data.results)setCount(res.data.total_count)}useEffect(() => {getList()}, [])// 筛选文章列表const onFinish = async (formValue) => {console.log(formValue)// 1. 准备参数const { channel_id, date, status } = formValueconst reqData = {status,channel_id,begin_pubdate: date[0].format('YYYY-MM-DD'),end_pubdate: date[1].format('YYYY-MM-DD'),}// 2. 使用参数获取新的列表getList(reqData)}

分页功能实现

实现步骤

  1. 为Table组件指定pagination属性来展示分页效果
  2. 在分页切换事件中获取到筛选表单中选中的数据
  3. 使用当前页数据修改params参数依赖引起接口重新调用获取最新数据

代码实现

const pageChange = (page) => {// 拿到当前页参数 修改params 引起接口更新setParams({...params,page})
}return (<Table rowKey="id" columns={columns} dataSource={article.list} pagination={{current: params.page,pageSize: params.per_page,onChange: pageChange,total: article.count}} />
)

删除功能

实现步骤

  1. 给删除文章按钮绑定点击事件
  2. 弹出确认窗口,询问用户是否确定删除文章
  3. 拿到参数调用删除接口,更新列表

代码实现

// 删除回调
const delArticle = async (data) => {await http.delete(`/mp/articles/${data.id}`)// 更新列表setParams({page: 1,per_page: 10})
}const columns = [// ...{title: '操作',render: data => {return (<Space size="middle"><Button type="primary" shape="circle" icon={<EditOutlined />} /><Popconfirmtitle="确认删除该条文章吗?"onConfirm={() => delArticle(data)}okText="确认"cancelText="取消"><Buttontype="primary"dangershape="circle"icon={<DeleteOutlined />}/></Popconfirm></Space>)}
]

编辑文章跳转

代码实现

const columns = [// ...{title: '操作',render: data => (<Space size="middle"><Buttontype="primary"shape="circle"icon={<EditOutlined />}onClick={() => navagite(`/publish?id=${data.id}`)} />/></Space>)}
]

相关文章:

react+AntDesign 之 pc端项目案例

1.环境搭建以及初始化目录 CRA是一个底层基于webpack快速创建React项目的脚手架工具 # 使用npx创建项目 npx create-react-app react-jike# 进入到项 cd react-jike# 启动项目 npm start2.安装SCSS SASS 是一种预编译的 CSS&#xff0c;支持一些比较高级的语法&#xff0c;…...

实验笔记之——基于COLMAP的Instant-NGP与3D Gaussian Splatting的对比

之前博客进行了COLMAP在服务器下的测试 实验笔记之——Linux实现COLMAP-CSDN博客文章浏览阅读794次&#xff0c;点赞24次&#xff0c;收藏6次。学习笔记之——NeRF SLAM&#xff08;基于神经辐射场的SLAM&#xff09;-CSDN博客NeRF 所做的任务是 Novel View Synthesis&#xf…...

实战环境搭建-linux下安装悟空CRM

下载地址如下: 链接:https://pan.baidu.com/s/1OI9EA8Nc8ymWlERS9i0vjg?pwd=ws5c 提取码:ws5c 上传crm的程序包,如下图: 输入 unzip 72crm-java-master.zip 进行解压 create database crm9; use crm9; source /opt/72crm-java-master/docs/crm9.sql 修改/home/wukongcr…...

Redis 7.0性能大揭秘:如何优化缓存命中率?

Redis 7.0&#xff0c;这货不仅仅是一个简单的缓存工具&#xff0c;它更是一款高性能的数据结构服务器。现在&#xff0c;大家都知道缓存命中率对性能影响特别大&#xff0c;但怎么优化它呢&#xff1f; 本文&#xff0c;已收录于&#xff0c;我的技术网站 ddkk.com&#xff0…...

【深度学习每日小知识】Data Augmentation 数据增强

数据增强是通过对原始数据进行各种转换和修改来人工生成附加数据的过程&#xff0c;旨在增加机器学习模型中训练数据的大小和多样性。这对于计算机视觉领域尤为重要&#xff0c;因为图像经常被用作输入数据。 计算机视觉中的数据增强 数据增强的主要目标是解决过拟合问题&…...

网络调试 UDP1,开发板用动态地址-入门6

https://www.bilibili.com/video/BV1zx411d7eC?p11&vd_source109fb20ee1f39e5212cd7a443a0286c5 1, 开发板连接路由器 1.1&#xff0c;烧录无OS UDP例程 1.2&#xff0c;Mini USB连接电脑 1.3&#xff0c;开发板LAN接口连接路由器 2. Ping开发板与电脑之间通信* 2.1 根据…...

【Gin实战教程】快速入门

Gin是一个轻量级的Web框架&#xff0c;使用Go语言开发。它具有高性能、易用性和灵活性的特点&#xff0c;是构建可扩展的Web应用程序的理想选择。 首先&#xff0c;Gin是一个高性能的框架。它基于Go语言的原生HTTP包进行开发&#xff0c;利用了Go语言的并发特性和协程模型&…...

WPF真入门教程26--项目案例--欧姆龙PLC通讯工具

1、案例介绍 前面已经完成了25篇的文章介绍&#xff0c;概括起来就是从0开始&#xff0c;一步步熟悉了wpf的概念&#xff0c;UI布局控件&#xff0c;资源样式文件的使用&#xff0c;MVVM模式介绍&#xff0c;命令Command等内容&#xff0c;这节来完成一个实际的项目开发&#…...

C++ OpenGL 3D Game Tutorial 2: Making OpenGL 3D Engine学习笔记

视频地址https://www.youtube.com/watch?vPH5kH8h82L8&listPLv8DnRaQOs5-MR-zbP1QUdq5FL0FWqVzg&index3 一、main类 接上一篇内容&#xff0c;main.cpp的内容增加了一些代码&#xff0c;显得严谨一些&#xff1a; #include<OGL3D/Game/OGame.h> #include<i…...

Redis小计(4)

目录 1.Set和Get操作 2.mset和mget 3.mset&#xff0c;mget&#xff0c;set后加参数的优点 4.incr,incrby&#xff0c;incrbyfloat 1.Set和Get操作 flushall&#xff1a;清除所有k-v键值对。&#xff08;删库跑路小技巧&#xff09; set k v[ex | px]&#xff1a;设置超时…...

【React】常用Hook函数的梳理和总结(第二篇)

1. 前言 本篇梳理和总结React中常用的Hook函数。 欢迎大家来到Hook的世界&#xff0c;真的贼好用~ 2. 常用Hook函数 Hook说明示例useState(initialState) 功能&#xff1a;为函数组件添加状态变量&#xff0c;输入可是基本数据类型或引用数据类型&#xff0c;也可以是不带参数…...

【JaveWeb教程】(15) SpringBootWeb之 响应 详细代码示例讲解

目录 SpringBootWeb请求响应2. 响应2.1 ResponseBody2.2 统一响应结果2.3 案例2.3.1 需求说明2.3.2 准备工作2.3.3 实现步骤2.3.4 代码实现2.3.5 测试2.3.6 问题分析 SpringBootWeb请求响应 2. 响应 前面我们学习过HTTL协议的交互方式&#xff1a;请求响应模式&#xff08;有…...

「 PyMuPDF专栏 」PyMuPDF为PDF文件添加注释

文章目录 一、PyMuPDF的安装与基本使用1. 安装PyMuPDF库的方法2. 导入PyMuPDF库二、新建PDF文档1. 创建一个空白的PDF文档三、添加注释1. 导入库并打开PDF文件2. 选择要添加注释的页面3. 创建并添加注释3.1. 文本注释3.1.1. 完整代码3.1.2. 注释效果图3.2. 高亮注释3.2.1. 完整…...

5 - 视图|存储过程

视图&#xff5c;存储过程 视图视图基本使用使用视图视图进阶 存储过程创建存储过程存储过程进阶存储过程参数循环结构 视图 视图是虚拟存在的表 表头下的数据在真表里 表头下的数据存储在创建视图时 在select命令访问的真表里 优点&#xff1a; 安全数据独立简单 用户无需关…...

系统学习Python——警告信息的控制模块warnings:警告过滤器-[基础知识]

分类目录&#xff1a;《系统学习Python》总目录 警告过滤器控制着警告是否被忽略、显示或转为错误&#xff08;触发异常&#xff09;。 从概念上讲&#xff0c;警告过滤器维护着一个经过排序的过滤器类别列表&#xff1b;任何具体的警告都会依次与列表中的每种过滤器进行匹配&…...

vue中高德地图使用

1、安装 npm i amap/amap-jsapi-loader --save2、封装地图组件 <template><div id"map" ref"mapcontainer"></div> </template><script> import AMapLoader from "amap/amap-jsapi-loader"; export default {befo…...

算法训练营Day33

#Java #贪心 开源学习资料 Feeling and experiences&#xff1a; 单调递增的数字&#xff1a;力扣题目链接 当且仅当每个相邻位数上的数字 x 和 y 满足 x < y 时&#xff0c;我们称这个整数是单调递增的。 给定一个整数 n &#xff0c;返回 小于或等于 n 的最大数字&am…...

.net6解除文件上传限制。Multipart body length limit 16384 exceeded

在C#中上传文件时如果不修改默认文件的上传大小会提示Multipart body length limit 16384 exceeded这个错误提示表明你的请求中的Multipart body长度超过了16384字节的限制。这通常意味着你正在尝试发送一个太大的请求体&#xff0c;可能是因为包含了太多数据或者太大的文件。要…...

电子电器架构网络演化 —— 车载以太网TSN

电子电器架构网络演化 —— 车载以太网TSN 我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 屏蔽力是信息过载时代一个人的特殊竞争力&#xff0c;任何消…...

智能门锁触控工作原理中应用的电容式触摸芯片

智能门锁的识别技术中&#xff0c;密码几乎成为标配功能。相比机械按键的触控方式&#xff0c;电容式触控方式可以在加上一层玻璃甚至金属一体成型之后与用户进行交互&#xff0c;由于进行了物理性隔离&#xff0c;使得外壳更具完整性&#xff0c;物理上安全性更佳。电容式触控…...

Spark 中 BroadCast 导致的内存溢出(SparkFatalException)

背景 本文基于 Spark 3.1.1 open-jdk-1.8.0.352目前在排查 Spark 任务的时候&#xff0c;遇到了一个很奇怪的问题&#xff0c;在此记录一下。 现象描述 一个 Spark Application, Driver端的内存为 5GB,一直以来都是能正常调度运行&#xff0c;突然有一天&#xff0c;报…...

深度学习经典算法详细模型图

很早绘制的一些模型图&#xff0c;当时放在CSDN的草稿里&#xff0c;今天发现了&#xff0c;把它分享出来吧&#xff0c;还能更清晰的帮助理解&#xff01; 1.AlexNet(2012) 2. VGGNet(2014) 3. SqueezeNet(2016) 4. GoogleNet(2014)...

03、Kafka ------ CMAK(Kafka 图形界面管理工具) 下载、安装、启动

目录 CMAK&#xff08;Kafka 图形界面管理工具&#xff09;下载安装启动打开 cmak 图形界面 CMAK&#xff08;Kafka 图形界面管理工具&#xff09; Kafka本身并没有提供Web管理工具&#xff0c;而是推荐使用bin目录下各种工具命令来管理Kafka&#xff0c; 这些工具命令其实用起…...

复习python从入门到实践——函数function

复习python从入门到实践——函数function 函数是特别难的&#xff0c;大家一定要好好学、好好复习、反复巩固。函数没学好&#xff0c;会为后面造成很大困扰。 教科书中函数举例会稍微有点复杂。在此章复习中&#xff0c;我将整理出容易疏漏和混淆的知识点&#xff0c;并用最简…...

【Internal Server Error】pycharm解决关闭flask端口依然占用问题

Internal Server Error The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application. 起因&#xff1a; 我们在运行flask后&#xff0c;断开服务依然保持运行&#xff0…...

torch.nn.functional.interpolate与torchvision.transforms.Resize方法对张量图像Resize应用

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、非张量数据使用torch方法resize(transforms.Resize)二、张量数据使用torch方法resize(torch.nn.functional.interpolate) 前言 要使用 PyTorch 对张量进行…...

【Spring】Spring的事务管理

前言&#xff1a; package com.aqiuo.service.impl;import com.aqiuo.dao.AccountMapper; import com.aqiuo.pojo.Account; import com.aqiuo.service.AccountService; import org.springframework.jdbc.core.JdbcTemplate;import java.sql.Connection; import java.sql.SQLEx…...

配置cendos 安装docker 配置阿里云国内加速

由于我安装的cendos是镜像版。已经被配置好了。所以只需要更新相关配置信息即可。 输入 yum update自动更新所有配置 更新完成后输入 yum list docker-ce --showduplicates | sort -r 自动查询所有可用的docker版本 输入 yum install docker-ce docker-ce-cli container…...

【深度学习:Domain Adversarial Neural Networks (DANN) 】领域对抗神经网络简介

【深度学习&#xff1a;Domain Adversarial Neural Networks】领域对抗神经网络简介 前言领域对抗神经网络DANN 模型架构DANN 训练流程DANN示例 GPT示例 前言 领域适应&#xff08;DA&#xff09;指的是当不同数据集的输入分布发生变化&#xff08;这种变化通常被称为共变量变…...

STM32 ESP8266 物联网智能温室大棚 (附源码 PCB 原理图 设计文档)

资料下载: https://download.csdn.net/download/vvoennvv/88680924 一、概述 本系统以STM32F103C8T6单片机为主控芯片&#xff0c;采用相关传感器构建系统硬件电路。其中使用DHT11温湿度传感器对温度和湿度的采集&#xff0c;MQ-7一氧化碳传感器检测CO浓度&#xff0c;GP2Y101…...

南阳最新通知今天/seo交流网

-- -- PL/SQL --> PL/SQL记录 -- PL/SQL记录有着类似于表的数据结构&#xff0c;是一个或多个字段且拥有数据类型的集合体。定义了PL/SQL记录类型之后&#xff0c;可以定义PL/SQL记录变 量。声明一个PL/SQL记录变量相当于定义了多个标量变量&#xff0c;简化了变量的声明&am…...

西柏坡旅游网站建设规划书/中文搜索引擎大全

原文地址https://segmentfault.com/a/1190000011078937一&#xff1a;keepalive简述一;高可用的解决方案 1&#xff09;vrrp协议的实现keepalive 2&#xff09;ais&#xff1a;完备HA集群 RHCS&#xff08;cman&#xff09;heartbeatcorosync 二&#xff1a;vrrp协议&#xff1…...

常见的手机网站/网络推广平台代理

数据显示&#xff0c;今年7月起&#xff0c;深圳、北京、上海三地住房租金环比均上涨&#xff0c;其中北京房租涨势最猛&#xff0c;平均租金同比上涨21.89%。 通州区、昌平区和大兴区&#xff0c;同比上涨已超过30%&#xff0c;其他区的同比涨幅基本也都超过10%。 网上不少人…...

杭州装饰网站建设方案/游戏推广员如何推广引流

模板介绍 作为Web框架&#xff0c;Django提供了模板&#xff0c;可以很便利的动态生成HTML模版系统致力于表达外观&#xff0c;而不是程序逻辑模板的设计实现了业务逻辑(view)与显示内容&#xff08;template&#xff09;的分离&#xff0c;一个视图可以使用任意一个模板&#…...

疫情最新消息今天又封了/seo网站优化方案摘要

精准客户 很多业务员整天在跑客户,跑断了腿就是没有1单签下来,明明很努力却始终没有业绩,想了很多办法,但是效果不明显啊,这是怎么回事啊? 其实很大的原因就是你的客户不够精准。 1个精准的客户带给你的价值,远远大于10个普通客户。邱邦军在做销售的时候,到了后期就…...

wordpress阿里/平台优化

今天遇到一个问题&#xff0c;想把pb转成map&#xff0c;如下所示&#xff1a;func 所以很无奈&#xff0c;就不得不选择了稍微繁琐一点的方式&#xff08;使用go的反射获得pb结构体的field&#xff0c;使用for循环将field转成map&#xff09;&#xff0c;如下代码所示&#xf…...