升级一下 antd 的版本。
保证 rc-field-form 是高版本
npx create-react-app my-app --template typescript
要么
yarn create react-app my-app --template typescript
官网地址
最近接触ant design,再上手的时候,在用ProTable组件代码时,导入下面代码,编译报错
import request from 'umi-request';
发现可以通过 npm add 模块名 的方式,像npm add umi-request 处理这种报错,就能正常启动了
npm i @types/react-router-dom
报错原因:
webpack5中移除了nodejs核心模块的polyfill自动引入,所以需要手动引入。
解决办法:将import qs from 'querystring‘改成import qs from 'qs'
解决方法是新增一个 axios.d.ts
文件,内容如下,就可以解决了
import * as axios from 'axios'
declare module 'axios' {
interface AxiosInstance {
(config: AxiosRequestConfig): Promise
}
}
根据http-proxy-middleware文档 在src目录创建setupProxy.js后,页面无法打开,是由于setupProxy.js低版本配置。
setupProxy.js低版本配置:
const proxy = require('http-proxy-middleware')
module.exports = function(app) {
app.use(
proxy('/api1', { //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
target: 'http://localhost:5000', //配置转发目标地址(能返回数据的服务器地址)
changeOrigin: true, //控制服务器接收到的请求头中host字段的值
pathRewrite: {'^/api1': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
}),
proxy('/api2', {
target: 'http://localhost:5001',
changeOrigin: true,
pathRewrite: {'^/api2': ''}
})
)
}
修改setupProxy.js
const { createProxyMiddleware } = require("http-proxy-middleware")
module.exports = function (app) {
app.use(
createProxyMiddleware("/api1",{
target: "http://localhost:5000", //配置转发目标地址(能返回数据的服务器地址)
changeOrigin: true, //控制服务器接收到的请求头中host字段的值
pathRewrite: { "^/api1": "" },
}),
createProxyMiddleware("/api2",{
target: "http://localhost:5001", //配置转发目标地址(能返回数据的服务器地址)
changeOrigin: true, //控制服务器接收到的请求头中host字段的值
pathRewrite: { "^/api2": "" },
})
)
}
重启项目,问题解决.
隐藏头部信息 tableAlertRender={false}
在 procomponents 中,你可以通过`ProTable` 中的`rowSelection` 属性来实现类似的操作。
首先,在`getCheckboxProps` 中设置对应行的`disabled` 属性为`true`:
const rowSelection = {
// ...其他配置
getCheckboxProps: (record) => ({
disabled: record.status === 'disabled',
}),
};
然后,在`onSelectedRowKeysChange` 的回调函数中手动处理选择的行,将被禁用的行从选择中剔除,但仍然可以获取这些行的数据:
const [selectedRowKeys, setSelectedRowKeys] = useState([]);
//...
const onSelectedRowKeysChange = (keys) => {
// 过滤掉被禁用的行
const filteredKeys = keys.filter((key) => {
const rowData = dataSource.find((item) => item.key === key);
return rowData && rowData.status !== 'disabled';
});
setSelectedRowKeys(filteredKeys);
};
//...
({
disabled: record.status === 'disabled',
}),
}}
/>
在上述代码中,我们将选择的行存储在`selectedRowKeys` 状态中,并在`onSelectedRowKeysChange` 回调函数中手动处理选择的行。在这个函数中,我们使用`filter` 方法过滤掉被禁用的行,并将剩下的行的 key 存储到`selectedRowKeys` 状态中。
这样,被禁用的行虽然不能被选择,但仍然可以获取它们的数据。
出现上面错误的原因是:ts默认初始化空数组为 never[] 类型,跟你赋值的数据类型不匹配
修改为: [] as any[],即可
背景:因为所用的项目查询的数据量比较大实现分页查询,所以当点击换页时界面会从后台获取数据刷新界面,所以跨页选择的数据会丢失。
实现思路:通过将之前的和现在的selectedRowKeys进行解构赋值,合并到一个数组里、去重、再根据下面2个api的回调函数里的参数的 selected的值 判断 选中 还是 去除勾选,来删除某个元素、还是添加。
在antd Table组件的rowSelection属性官方文档(链接),于是被我发现了两个个可以用的上的API,叫做onSelect和onSelectAll
首先我们可以看到onSelect需要传入一个函数作为回调,然后这个方法的参数里有record, selected, selectedRows这几项(nativeEvent原生事件我不关心),经过测试后确定
1、record就是当前操作(选中或取消选中)的item
2、selected是个布尔值,true代表本次是选中操作,false就是取消选中
3、selectedRows是一个数组,就是当前已选择的items(没有跨页的记录)
至于onSelectAll,是在点击全选和取消全选时触发的回调函数,截图中也可以看到,它有selected, selectedRows, changeRows这三个参数
1、selected,同上,true全选,false取消全选
2、selectedRows,也同上,当前已选择的items(没有跨页的记录)
3、changeRows,这个可就优秀了,它就是你的全选/取消全选操作引起变化的items数组,打个比方,如果一页10个,你一个都没选呢,点全选,就会新选择10个,那么changeRows就是这10条数据,如果你已经选了6条,你又点了全选,那么就相当于你又一次性选了4条,那么changeRows就是这4条
然后基于这些参数,我们来定义一个可以跨页记录的selectedRows,我取名叫selectedRowsPlus
(注:因为我的是个函数式组件,所以使用useState.如果是类式组件在state中定义selectedRowKeys,selectionRowsPlus,selectedRows即可)
const [selectedRowKeys, setSelectedRowKeys] = useState([] );
const [selectionRowsPlus, setSelectionRowsPlus] = useState([] as any []);
const [selectedRows, setSelectedRows] = useState([]as any []);
然后是Table组件的rowSelection配置,
render(){
const { productListSelectedRowKeys,productListSelectedRows } = this.state;
const rowSelections: TableRowSelection = {
selectedRowKeys,
onSelect: onSelect,
onSelectAll: onSelectAll,
onChange: onSelectChange,
preserveSelectedRowKeys: true,//当数据被删除时仍然保留选项的 key,即能实现在点击下一页上一页选中的内容也是选中的
getCheckboxProps: (record:any) => ({
disabled: record.available === 'Invalid'
})
};
return(
queryTableData(params)}
//但是默认的查询表单 rules 是不生效的。需要配置 ignoreRules,才会执行指定的必录等相关规则
form={{
ignoreRules: false,
}}
//(加载查询界面时是否需要手动触发首次请求), 配置为 true 时不可隐藏搜索表单
manualRequest={true}
search={{
labelWidth: 'auto',
}}
options={{
setting: {
listsHeight: 400,
},
}}
pagination={{
defaultPageSize: 10,
//为true时代表在界面可以自己选择一页展示几条数据
showSizeChanger: true,
onChange: (page) => console.log(page),
}}
dateFormatter="string"
/>
)
}
然后是具体的回调方法的实现
const onSelect = (record:any,selected:any,selectedRows:any,nativeEvent:any) => {
console.log("record:",record);
console.log("selected:",selected);
console.log("selectedRows:",selectedRows);
console.log("nativeEvent:",nativeEvent);
if(selected){
let productListSelectedRowsPlus = selectionRowsPlus;
productListSelectedRowsPlus.push(record);
setSelectionRowsPlus([...new Set(productListSelectedRowsPlus)]);
}
if(!selected){
let productListSelectedRowsPlus = selectionRowsPlus;
let delIndex !:number;
for(let i=0;i{
console.log("selected:",selected);
console.log("selectedRows:",selectedRows);
console.log("changeRows:",changeRows);
if(selected){
let productListSelectedRowsPlus = selectionRowsPlus;
setSelectionRowsPlus([...new Set(productListSelectedRowsPlus.concat(changeRows))]);
}
if(!selected){
let productListSelectedRowsPlus = deepCopy(selectionRowsPlus);
let delIndex = [];
for(let i=0;i{
return item != undefined;
})
setSelectionRowsPlus([...new Set(pureProductListSelectedRowsPlus)]);
}
}
上面调用的深拷贝的方法是
const deepCopy:any = (data:any) => {
if (data.constructor.name === 'Array') {
// 判断为数组类型
var arrCopy = []
for (var i = 0, len = data.length; i < len; i++) {
//遍历数组
if (data[i] instanceof Object) {
arrCopy.push(deepCopy(data[i]))
} else {
// 基本类型
arrCopy.push(data[i])
}
}
return arrCopy;
} else { // 为对象
var objCopy = {};
for (let x in data) {
if(data[x] instanceof Object){
objCopy[x] = deepCopy(data[x])
}else{ // 基本类型
objCopy[x] = data[x];
}
}
return objCopy;
}
}
这样 selectedRowsPlus属性中就是最终所有页码中最终选择的数据。
1.先定义的存储下拉框中值的属性并创建调用后端获取数据的方法
//siteList为定义的存储下拉框中值的属性
const [siteList,setsiteList]=React.useState();
//用来调用后端获取下拉框数据的方法
const getSiteList = () => {
getSites("WAL").then(res => {
if (res.code === 4) {
setsiteList(res.data)//此处已经把后台返回的数据添加到siteList属性中,在select组件中直接调用即可
}
}, err => {
console.log(err)
})
}
2.先创建一个公共的调用后端的类,我这里定义成request.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Spin } from 'antd';
import axios from 'axios'
import Constants from './constants'
import { getLocalStorage, setLocalStorage } from './storage'
let isLock = false;
let refreshSubscribers = [];
let requestCount = 0
function showLoading() {
if (requestCount === 0) {
var dom = document.createElement('div')
dom.setAttribute('id', 'loading')
document.body.appendChild(dom)
ReactDOM.render( , dom)
}
requestCount++
}
function hideLoading() {
requestCount--
if (requestCount === 0) {
document.body.removeChild(document.getElementById('loading'))
}
}
//push所有请求到数组中
function subscribeTokenRefresh(cb) {
refreshSubscribers.push(cb)
}
//刷新请求(refreshSubscribers数组中的请求得到新的token之后会自执行,用新的token去请求数据)
function onRrefreshed(token) {
refreshSubscribers.map(cb => cb(token))
}
//刷新token
function refreshToken(config, token, resolve, reject) {
axios({
method: "get",
url: `${Constants.API_ROOT_URL}/oauth/v1.0/getUserInfo?token=${token}`,
}).then(response => {
isLock = false;//释放锁
const res = response.data
// if the custom code is not 20000, it is judged as an error.
if (res.code === 401) {
window.location.href = '/config/loginexpired';
return reject(res);
} else if (res.code === 412) {
window.location.href = '/config/userdeactivated';
return reject(res);
} if (res.code !== 4) {
// message.error(`Error: ${res.msg || res.message}`)
return res;
}
setLocalStorage('user', res.data.user)
setLocalStorage('needRefreshToken', false)
config.headers['token'] = token
config.headers['user'] = res.data.user
resolve(config);
//执行数组里的函数,重新发起被挂起的请求
onRrefreshed(res.data.user)
//清空数组中保存的请求
refreshSubscribers = []
}).catch(err => {
return err;
});
}
// create an axios instance
const service = axios.create({
baseURL: Constants.API_ROOT_URL, // url = base url + request url
withCredentials: true, // send cookies when cross-domain requests
timeout: 120000 // request timeout
})
// request interceptor
service.interceptors.request.use(
config => {
if(config.method === 'get'){
config.params = {
_t: Date.now(),
...config.params,
};
}
// do something before request is sent
if (config.isLoading !== false) {
showLoading()
}
//config.headers['retailer'] = Constants.RETAILER
config.headers['Content-Type'] = config.contentType || 'application/json'
const user = getLocalStorage('user')
const token = getLocalStorage('token')
const needRefreshToken = getLocalStorage('needRefreshToken')
if ((!user && token) || needRefreshToken) {
//判断当前是否正在请求刷新token
if (!isLock) {
isLock = true;//isLock设置true,锁住防止死循环。
//使用Promise等待刷新完成返回配置信息
let refresh = new Promise((resolve, reject) => {
refreshToken(config, token, resolve, reject);
})
return refresh
} else {
//判断当前url是否是刷新token的请求地址,如果是直接下一步。
if (config.url.indexOf("/config/loginexpired") === -1 || config.url.indexOf("/config/userdeactivated") === -1) {
//把请求(token)=>{....}都push到一个数组中
let retry = new Promise((resolve, reject) => {
//(token) => {...}这个函数就是回调函数
subscribeTokenRefresh((user) => {
config.headers['token'] = token
config.headers['user'] = user
//将请求挂起
resolve(config)
})
})
return retry
} else {
config.headers['token'] = getLocalStorage('token')
config.headers['user'] = user
return config;
}
}
} else {
config.headers['token'] = token
config.headers['user'] = user
return config
}
},
error => {
// do something with request error
hideLoading()
console.log(error) // for debug
return Promise.reject(error)
}
)
// response interceptor
service.interceptors.response.use(
/**
* If you want to get http information such as headers or status
* Please return response => response
*/
/**
* Determine the request status by custom code
* Here is just an example
* You can also judge the status by HTTP Status Code
*/
response => {
hideLoading()
const res = response.data
// if the custom code is not 20000, it is judged as an error.
if (res.code === 401) {
window.location.href = '/config/loginexpired';
return Promise.reject(res);
} else if (res.code === 412) {
window.location.href = '/config/userdeactivated';
return Promise.reject(res);
} if (res.code !== 4) {
// message.error(`Error: ${res.msg || res.message}`)
return res;
} else {
return res
}
},
error => {
console.log('err' + error) // for debug
hideLoading()
// Message({
// message: error.message,
// type: 'error',
// duration: 5 * 1000
// })
return Promise.reject(error)
}
)
export default service
3.创建一个叫search.js的类,用来统一存储前端调用后台的所有方法
注意:只有下面的getSites方法为本次获取下拉框中值需要用的
import request from '../utils/request'
export function submitOrder(data){
return request({
url: `/search/v1.0/submitOrder`,
method: 'post',
data: data,
headers: {
retailer: '111'
},
contentType: 'application/json'
})
}
export function queryVipsData(data){
return request({
url: `/search/v1.0/queryVipsData`,
method: 'post',
data: data,
// headers: {
// retailer: '111'
// },
contentType: 'application/json'
})
}
export function getSites(data){
return request({
url: '/search/v1.0/getSites',
method: 'get',
headers: {
retailer: data
}
})
}
4.ProFormSelect 下拉框组件
const [selectedsiteList,setselectedsiteList]=React.useState();
type DtoType ={
key: string;
value: string;
label: string;
}
const handleChangeSite = (values:any) => {
setselectedsiteList(values);
}
handleChangeSite(value),
autoClearSearchValue:true//选中后清空搜索框
}}
placeholder="Please select the site to Resend"
rules={[
//这个代表的是必选校验和对应的提示语
{ required: true, message: 'Please select the site to Resend!', type: 'array' },
]}
/>
实现结果就像下面这种
1.用下面组件进行实现
onChange(value,selectedOptions)}
multiple //代表支持多选
placeholder="Please select"
maxTagCount="responsive"
showSearch={{ filter }}
onSearch={(value) => console.log(value)}
/>
2.下拉框中的值获取同上面第一种一样。下面重点记录下在后端怎样组装这种多级数据
这是返回的数据格式
3.后台中先创建相关对象
import java.util.ArrayList;
import java.util.List;
public class TreeNode {
private String value;
private String label;
private List children = new ArrayList();
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public List getChildren() {
return children;
}
public void setChildren(List children) {
this.children = children;
}
public TreeNode(String value, String label) {
this.value = value;
this.label = label;
}
public TreeNode(String value, String label,List children) {
this.value = value;
this.label = label;
this.children=children;
}
public TreeNode() {
}
@Override
public String toString() {
return "TreeNode{" +
"value='" + value + '\'' +
", label='" + label + '\'' +
", children=" + children +
'}';
}
}
实现类
public List getUserListByRetailer(String retailer) {
List userList = null;
List result = new ArrayList<>();
try {
//这个是查询数据的脚本,返回的结果中的site要作为一级选项,name作为二级选项
userList = userInfoMapper.getUserListByRetailer(retailer);
Map map = new HashMap<>();
for (UserInfo user : userList) {
String site = user.getSite();
TreeNode treeNode;
if (map.containsKey(site)) {
treeNode = map.get(site);
treeNode.getChildren().add(new TreeNode(user.getUserName(), user.getUserName()));
} else {
treeNode = new TreeNode();
treeNode.setLabel(user.getSite());
treeNode.setValue(user.getSite());
treeNode.setChildren(setChildrenList(user.getUserName()));
}
map.put(site, treeNode);
}
for (Map.Entry entry : map.entrySet()) {
result.add(entry.getValue());
}
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
官网链接
使用新版本的 React,要使用 CSS 模块,您无需在 Webpack 中配置任何内容或拒绝您的元素。 您需要做的就是:
Hello World
在使用protable组件中的column属性中定义一个valueType为'textarea'的文本框时,无法展示为文本域。觉得这就是个坑,在他们的github上找了好久后才找到。因为查询列表中出现一个文本域的话可能会导致样式没法展示,因此底层默认textarea就为普通的文本框。
链接
表格代码
方法代码
import classes from './Vips.module.css'
const getRowClassName = (record:any) => {
if (record.available === "Invalid") { // 根据这一行的状态,改变不同的颜色
return `${classes.warning}`; // 加 s. 是因为上面引入的问题
} else {
return '';
}
}
样式代码
在css中添加这个
.warning {
background: #df3404;
}
这是因为Java中的语言环境代码采用的是ISO 639-1标准,该标准中规定语言代码必须为小写字母,而国家/地区代码采用的是ISO 3166-1标准,该标准中规定国家/地区代码必须为大写字母。因此,当使用Java中的Locale类创建语言环境时,语言代码会被强制转换为小写字母,以符合ISO 639-1标准的要求。
在 React 中使用 `ConfigProvider` 包裹父组件后,子组件也能够继承其配置。如果需要在子组件中也实现国际化,可以按照以下步骤:
1. 在父组件中引入 `react-intl` 库的 `IntlProvider` 和 `FormattedMessage` 组件以及 `ConfigProvider` 组件。
import { ConfigProvider } from 'antd';
import { IntlProvider, FormattedMessage } from 'react-intl';
import messages from './i18n';
2. 在父组件中将 `ConfigProvider` 组件包裹在 `IntlProvider` 组件中,并且将 `messages` 对象传递给 `IntlProvider` 组件用于国际化。
function ParentComponent({ children }) {
return (
{children}
);
}
3. 在子组件中使用 `FormattedMessage` 组件,而无需在子组件中再次引入 `IntlProvider` 组件。因为子组件通过 `ConfigProvider` 继承父组件的配置,所以 `FormattedMessage` 组件同样可以从父组件继承国际化配置。
function ChildComponent() {
return (
);
}
在 `路径i18n下的messages.js 对象中添加子组件中需要翻译的文本内容。
const messages = {
'zh-CN': {
greeting: '你好,世界!'
},
'en-US': {
greeting: 'Hello World!'
}
};
如果在 React 应用中使用`window.location.href` 从子组件返回父组件后再次加载页面,可能会导致国际化配置出现问题。这是因为在从子组件返回父组件后,页面的状态可能会被重置,包括国际化配置的状态也可能被重置,从而导致国际化配置失效。
为了解决这个问题,可以考虑使用 React Router 或其他路由库来管理页面的路由,以避免直接使用`window.location.href` 的方式导航到页面。使用路由库可以将页面的状态保存在路由中,从而避免页面状态重置导致的国际化配置失效问题。
如果使用了 React Router 等路由库,可以通过以下方式在父组件中传递国际化配置给子组件:
1. 在父组件中使用`React Router` 等路由库配置页面路由。
2. 在父组件中通过路由传递国际化配置给子组件。
function ParentComponent({ location }) {
const { pathname } = location;
return (
);
}
在上面的例子中,父组件`ParentComponent` 中的`ConfigProvider` 组件根据当前路由路径判断子组件`ChildComponent` 所使用的语言环境。当路由路径为 `child` 时,使用英文环境,否则使用中文环境。这样就可以在子组件中正确地继承父组件的国际化配置了。
3.或者将界面选择的语言存储到本地
// 获取语言
export function getLanguage() {
const lang = navigator.language ; // 常规浏览器语言和IE浏览器
const localStorageLang = localStorage.getItem("lang");
const defaultLang = localStorageLang || lang;
return defaultLang;
}
// 修改html.lang显示
export function changeHtmlLang(lang) {
return document.getElementById("lang").lang = lang;
}
// 设置语言
export function setLanguage(lang) {
const defaultLang = localStorage.setItem("lang", lang);
return defaultLang;
}
调用上面getLanguage()方法进行初始化获取语言
const [currentLocale, setCurrentLocale] = useState(getLanguage());
将currentLocale这个值赋值给下面的locale属性即可
下面的json为后端返回的查询结果列表的数据
// 遍历数据,将属性为available值为Invalid的单元格的样式设置为红色
const processedData = (json:any) =>json.map((record: any) => {
const newRecord = { ...record };
if (newRecord.available === "Invalid") {
newRecord.available = Invalid;
}
return newRecord;
});
在下面调用后端接口返回数据时进行处理,添加上相关样式即可
import { ParamsType} from '@ant-design/pro-components';
const queryTableData = async (params: ParamsType) => {
const msg = await queryVipsData({
page: params.current,
pageSize: params.pageSize,
...params
});
return {
total: msg.data.total,
data: processedData(msg.data.data),
// success 请返回 true,
// 不然 table 会停止解析数据,即使有数据
success: true,
// 不传会使用 data 的长度,如果是分页一定要传
};
}
在protable组件中的调用
queryTableData(params)} />
因为后端返回的json报文中的一个列属性(columns)的值中没法返回一个render的箭头函数方法,这种方式是在前端中手动添加一个自定义的render方法,让这个列的属性值为invalid时,字体变红
const processedData=(columns:any) =>columns.map((col: any) => {
if (col.dataIndex === 'available') { // 如果是目标列
// 返回列配置对象,覆盖默认配置
return {
...col,
// 自定义渲染函数 text表示当前行的值,record表示当前行的数据
render: (text: any,record: any) => {
console.log("text***"+text)
if (record.available === 'Invalid') { // 如果值为 invalid
return {text}; // 返回一个样式为红色的 span 标签
}
return {text}; // 否则返回一个普通的 span 标签
},
};
}
return col;
})
protable中是这样调用的,在protable的组件中的columns属性中调用上面的processedData方法进行处理,当目标列为(dataIndex === 'available')时添加一个render方法,并当值为Invalid的字体颜色改成红色
queryTableData(params)}
/>
在tsx文件中让reset按钮点击时调用handleReset 方法,并且在查询的方法处添加判断,如果没有输入条件,则不会查询数据
import React ,{useState,useRef}from 'react';
import classes from './Vips.module.css'
import ProTable from '@ant-design/pro-table';
import { ParamsType } from '@ant-design/pro-components';
import {queryVipsData} from "../../api/search";
export default (props:any) => {
const {error,parseData}=props;
const [submitSuccessfully, setSubmitSuccessfully] = useState(false);
const actionRef = useRef();
const queryTableData = async (params: ParamsType) => {
const { current, pageSize, ...rest } = params; // 从 params 中取出页码、页大小和其他查询条件
if (!Object.values(rest).some(Boolean)) { // 如果没有输入条件,则不会查询数据
return { data: [], total: 0, success: true };
}
const msg = await queryVipsData({
page: params.current,
pageSize: params.pageSize,
...params
});
return {
total: msg.data.total,
data: msg.data.data,
// success 请返回 true,
// 不然 table 会停止解析数据,即使有数据
success: true,
// 不传会使用 data 的长度,如果是分页一定要传
};
}
const handleReset = () => {
const { form } = actionRef.current;
if (form) {
form.resetFields(); // 重置表单中所有字段值
actionRef.current.submit(); // 提交表单
}
};
if (error===true) {
return {parseData?.errorMessage}
}else{
return (
{(submitSuccessfully === false) &&
}
);
}
}
我在采用把@ant-design/pro-table`的改成2.x版本的
1. 首先,在你的React项目中打开终端,进入项目目录,并运行以下命令卸载当前的`@ant-design/pro-table`:
npm uninstall @ant-design/pro-table
2. 接下来,安装最新版本的2.x`@ant-design/pro-table`。使用以下命令:
npm install @ant-design/[email protected]
3. 安装成功后,回到你项目的入口文件(一般是`App.js`或`index.js`),并将`@ant-design/pro-table`引入到你的项目中。使用以下代码:
import ProTable from '@ant-design/pro-table';
4. 因为`@ant-design/pro-table`的2.x版本的API与1.x版本有所不同,你需要根据新版本的API修改相应的代码。可以参考`@ant-design/pro-table`的官方文档,了解API的使用方式。
5. 修改完成后,运行你的React项目,并确保`@ant-design/pro-table`正常工作。
注意:在升级`@ant-design/pro-table`到2.x版本之前,最好备份你的代码,以便在出现问题时恢复。
实现后的效果展示
前端代码
1.App.tsx的入口文件内容如下,是在Section组件中添加路由配置和菜单展示的
import React ,{Fragment}from 'react';
import Section from './layouts/Section/Section'
import { LOCALES } from "./components/i18n/locales";
import { useState } from 'react';
export const AppContext = React.createContext({});
export default function App(){
return(
)
}
2.Section.tsx
import React from 'react'
import LoginExpired from '../../pages/Error/LoginExpired'
import UserDeactivated from '../../pages/Error/UserDeactivated'
import AuthRoute from './AuthRoute'
import SearchPage from '../../pages/SearchPage'
import ReloadI18n from '../../pages/ReloadI18n'
import Network1 from '../../pages/Network1'
import Network2 from '../../pages/Resend/Network2'
import VipsInitPage from '../../pages/Vips/VipsInitPage'
import { Link, useHistory } from 'react-router-dom';
import {getMenus} from '../../api/search'
import { MenuDataItem ,ProLayout} from '@ant-design/pro-layout';
import { useState } from 'react';
import { Router,withRouter } from 'react-router-dom';
import { FormattedMessage } from 'react-intl'
import VipsInitPageMU from '../../pages/Vips/VipsInitPageMU'
function Section (){
const routerMap = [
{path: "/loginexpired", name: "LoginExpired", component: LoginExpired},
{path: "/userdeactivated", name: "userdeactivated", component: UserDeactivated},
{path: "/VipsInitPage", name: "VipsInitPage", component: VipsInitPage},
]
const [menuData, setMenuData] = useState ([]);
const getMenuList=()=> {
//此处是调用后端获取菜单json报文
getMenus().then(res => {
if (res.code === 4) {
setMenuData(res.data)
}
})
}
React.useEffect(getMenuList,[])
//使用了`useHistory`钩子来获取`history`对象,而不是从外部传入。当用户点击菜单项时,会先触发`onClick`事件中的路由跳转逻辑,这样做的好处是可以清除路由库的缓存,确保页面能正确跳转到目标组件界面。
const history = useHistory();
const menuDataRender = (menuList:any) => {
return menuList.map((menu:any) => {
if (menu.children && menu.children.length > 0) {
return {
...menu,
name:
,
children: menuDataRender(menu.children),
};
} else {
return {
...menu,
name: (
),
};
}
});
};
const handleClick = (path:string) => {
history.push(path);
window.location.reload();
};
const menuItemRender = (menuData:any, defaultDom:any) => {
return
handleClick(menuData.path)}
>
};
return(
/*用Switch组件包裹多个Route组件。
在Switch组件下,不管有多少个Route的路由规则匹配成功,都只会渲染第一个匹配的组件*/
menuDataRender(menuData)} menuItemRender={menuItemRender}>
);
};
export default withRouter(Section);
3.Section.tsx 中 要引入的getMenus()方法,调用后端
export function getMenus(){
return request({
url: '/menus/v1.0/getMenuList',
method: 'get'
})
}
4.Section.tsx 中的AuthRoute.jsx组件
import React, { Component } from 'react'
import { Route, Redirect ,withRouter} from 'react-router-dom'
import qs from 'qs'
import { setLocalStorage, getLocalStorage } from '../../utils/storage'
import Header from '../Header/Header'
import classes from '../../pages/Vips/Vips.module.css'
export class AuthRoute extends Component {
componentWillMount() {
this.setToken()
}
setToken = () => {
const { search } = this.props.location
const token = getLocalStorage("token");
if (search) {
const searchParams = qs.parse(search.substr(1))
if( this.isValidToken(searchParams.token) && token !== searchParams.token) {
setLocalStorage('token', searchParams.token)
setLocalStorage('needRefreshToken', true)
}
}
}
isValidToken = (token) => {
const reg = /[0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z]-[0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z]-[0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z]-[0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z]-[0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z]/
return reg.test(token)
}
onChangeLanguage = (e) => {
//setLocale(e.target.value);
localStorage.setItem("locale", e.target.value);
};
render() {
const { routerConfig, location } = this.props;
const { pathname, search } = location;
const searchParams = qs.parse(search.substr(1))
const token = searchParams.token || getLocalStorage('token')
const isLogin = this.isValidToken(token)
if (isLogin) {
return (
//Header组件可以自定义菜单项跳转界面上方的公共header区域要展示什么
{ routerConfig.map(item => (
))}
)
} else {
return
}
}
}
export default withRouter(AuthRoute);
后端代码
1.创建一个Menu对象
public class Menu {
private String name;
private String path;
private List
2.获取json菜单报文
@GetMapping("/v1.0/getMenuList")
public ResponseEntity getMenuList() {
// 菜单数据
List menuList = new ArrayList<>();
Menu menu2 = new Menu("/Vips", "menu.Administration_page", null);
Menu menu2_1 = new Menu("/VipsInitPage", "Vips_Resend", null);
menuList.add(menu2);
ApiResponseMessage result=new ApiResponseMessage(ApiResponseMessage.OK, menuList);
return new ResponseEntity<>(result, HttpStatus.OK);
}
你可以尝试使用`useMemo` 钩子来缓存并优化`parseColumn` 函数的结果。
1. 首先,在组件的开头引入`useMemo`:
import React, { useMemo } from 'react';
2. 在组件中使用`useMemo` 来缓存`parseColumn` 函数的结果:
const parsedColumns = useMemo(() => parseColumn(parseData), [parseData]);
在这里,`useMemo` 函数的第一个参数是一个回调函数,该回调函数返回`parseColumn(parseData)` 的结果。第二个参数是一个数组,包含了所有会影响`parseColumn` 结果的依赖项。只有当这些依赖项发生变化时,`parseColumn` 函数才会重新执行。
3. 将`parsedColumns` 传递给`columns` 属性:
通过使用`useMemo` 缓存`parseColumn` 函数,并且只在依赖项发生改变时重新执行,可以避免不必要的重复渲染,并解决`Too many re-renders` 错误。
注意:请确保`parseData` 是一个不可变对象,或者在依赖项数组中正确地包含`parseData` 的所有引用。否则,`useMemo` 可能无法正确地检测依赖项的变化。