React全家桶+AntD 单车后台管理系统开发(完整版)
(2-1React基础介绍&)
2-1React基础介绍&
(2-2React脚手架使用&)
2-2React脚手架使用&
react脚手架,
yarn使用
(2-3 React生命周期介绍&)
React生命周期介绍&
getDefaultProps初始化Props属性,getInitialState初始化当前组件状态,componentWillMount组件初始化之前调用,render渲染Ui通过setState来render,componentDidMount组件Dom渲染完毕调用,componentWillReceiveProps父组件属性传递调用,shouldComponentUpdate调用setState执行,componentWillUpdate组件更新之前,componentDidUpdate组件更新之后,componentWillUnmount组件销毁。
forceUpdate
启动项目
constructor 初始化state,style,setState,bind(this)
import React from 'react'
import Child from './Child'
import {Button,Input} from 'antd'
import './index.less'
export default class Life extends React.Component{
// constructor(props) {
// super(props);
// this.state = {
// count:0
// };
// }
state = {
count:0
}
handleAdd=()=>{
this.setState({
count: this.state.count + 1
})
}
handleClick(){
this.setState({
count: this.state.count + 1
})
}
render(){
let style = {
padding:50
}
return
React生命周期介绍
AntD点击一下
点击一下
点击一下
{this.state.count}
}
}
.content{
padding:50px;
}
Child.js
shouldComponentUpdate
import React from 'react'
export default class Child extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentWillMount(){
console.log('will mount');
}
componentDidMount(){
console.log('did mount');
}
componentWillReceiveProps(newProps){
console.log('will props' + newProps.name)
}
shouldComponentUpdate(){
console.log('should upate')
return true;
}
componentWillUpdate(){
console.log('will upate')
}
componentDidUpdate(){
console.log('did upate')
}
render(){
return
这里是子组件,测试子组件的生命周期
{this.props.name}
}
}
页面加载
点击Button,只要setState就会调用update
(3-1基础插件安装&)
3-1基础插件安装&
暴露配置文件
不用再任何地方引入antd.css了,自动导入antd.less文件
eject,从后往前进行解析less-loader,更换主题
index.less
.content{
padding:50px;
}
Life.js className
import React from 'react'
import Child from './Child'
import {Button,Input} from 'antd'
import './index.less'
export default class Life extends React.Component{
// constructor(props) {
// super(props);
// this.state = {
// count:0
// };
// }
state = {
count:0
}
handleAdd=()=>{
this.setState({
count: this.state.count + 1
})
}
handleClick(){
this.setState({
count: this.state.count + 1
})
}
render(){
let style = {
padding:50
}
return
React生命周期介绍
AntD点击一下
点击一下
点击一下
{this.state.count}
}
}
antD的theme主题按需加载css
webpack.config.prod.js
'use strict';
const autoprefixer = require('autoprefixer');
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const ManifestPlugin = require('webpack-manifest-plugin');
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
const eslintFormatter = require('react-dev-utils/eslintFormatter');
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
const paths = require('./paths');
const getClientEnvironment = require('./env');
// Webpack uses `publicPath` to determine where the app is being served from.
// It requires a trailing slash, or the file assets will get an incorrect path.
const publicPath = paths.servedPath;
// Some apps do not use client-side routing with pushState.
// For these, "homepage" can be set to "." to enable relative asset paths.
const shouldUseRelativeAssetPaths = publicPath === './';
// Source maps are resource heavy and can cause out of memory issue for large source files.
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
// `publicUrl` is just like `publicPath`, but we will provide it to our app
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
// Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz.
const publicUrl = publicPath.slice(0, -1);
// Get environment variables to inject into our app.
const env = getClientEnvironment(publicUrl);
// Assert this just to be safe.
// Development builds of React are slow and not intended for production.
if (env.stringified['process.env'].NODE_ENV !== '"production"') {
throw new Error('Production builds must have NODE_ENV=production.');
}
// Note: defined here because it will be used more than once.
const cssFilename = 'static/css/[name].[contenthash:8].css';
const extractTextPluginOptions = shouldUseRelativeAssetPaths
? // Making sure that the publicPath goes back to to build folder.
{ publicPath: Array(cssFilename.split('/').length).join('../') }
: {};
module.exports = {
// Don't attempt to continue if there are any errors.
bail: true,
// We generate sourcemaps in production. This is slow but gives good results.
// You can exclude the *.map files from the build during deployment.
devtool: shouldUseSourceMap ? 'source-map' : false,
// In production, we only want to load the polyfills and the app code.
entry: [require.resolve('./polyfills'), paths.appIndexJs],
output: {
// The build folder.
path: paths.appBuild,
filename: 'static/js/[name].[chunkhash:8].js',
chunkFilename: 'static/js/[name].[chunkhash:8].chunk.js',
// We inferred the "public path" (such as / or /my-project) from homepage.
publicPath: publicPath,
// Point sourcemap entries to original disk location (format as URL on Windows)
devtoolModuleFilenameTemplate: info =>
path
.relative(paths.appSrc, info.absoluteResourcePath)
.replace(/\\/g, '/'),
},
resolve: {
// This allows you to set a fallback for where Webpack should look for modules.
modules: ['node_modules', paths.appNodeModules].concat(
// It is guaranteed to exist because we tweak it in `env.js`
process.env.NODE_PATH.split(path.delimiter).filter(Boolean)
),
extensions: ['.web.js', '.mjs', '.js', '.json', '.web.jsx', '.jsx'],
alias: {
// Support React Native Web
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
'react-native': 'react-native-web',
},
plugins: [
// Prevents users from importing files from outside of src/ (or node_modules/).
// This often causes confusion because we only process files within src/ with babel.
// To fix this, we prevent you from importing files out of src/ -- if you'd like to,
// please link the files into your node_modules/ and let module-resolution kick in.
// Make sure your source files are compiled, as they will not be processed in any way.
new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
],
},
module: {
strictExportPresence: true,
rules: [
// TODO: Disable require.ensure as it's not a standard language feature.
// We are waiting for https://github.com/facebookincubator/create-react-app/issues/2176.
{
oneOf: [
// "url" loader works just like "file" loader but it also embeds
// assets smaller than specified size as data URLs to avoid requests.
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
loader: require.resolve('url-loader'),
options: {
limit: 10000,
name: 'static/media/[name].[hash:8].[ext]',
},
},
// Process JS with Babel.
{
test: /\.(js|jsx|mjs)$/,
include: paths.appSrc,
loader: require.resolve('babel-loader'),
options: {
plugins:[
['import',[{
libraryName:'antd',
style:true
}]]
],
compact: true,
},
},
{
test: /\.less$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
},
},
{
loader: require.resolve('postcss-loader'),
options: {
// Necessary for external CSS imports to work
// https://github.com/facebookincubator/create-react-app/issues/2677
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
autoprefixer({
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9', // React doesn't support IE8 anyway
],
flexbox: 'no-2009',
}),
],
},
},
{
loader:require.resolve('less-loader')
}
],
},
{
test: /\.css$/,
loader: ExtractTextPlugin.extract(
Object.assign(
{
fallback: {
loader: require.resolve('style-loader'),
options: {
hmr: false,
},
},
use: [
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
minimize: true,
sourceMap: shouldUseSourceMap,
},
},
{
loader: require.resolve('postcss-loader'),
options: {
// Necessary for external CSS imports to work
// https://github.com/facebookincubator/create-react-app/issues/2677
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
autoprefixer({
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9', // React doesn't support IE8 anyway
],
flexbox: 'no-2009',
}),
],
},
},
],
},
extractTextPluginOptions
)
),
// Note: this won't work without `new ExtractTextPlugin()` in `plugins`.
},
{
loader: require.resolve('file-loader'),、
exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/],
options: {
name: 'static/media/[name].[hash:8].[ext]',
},
},
// ** STOP ** Are you adding a new loader?
// Make sure to add the new loader(s) before the "file" loader.
],
},
],
},
plugins: [
new InterpolateHtmlPlugin(env.raw),
// Generates an `index.html` file with the injected.
new HtmlWebpackPlugin({
inject: true,
template: paths.appHtml,
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
}),
new webpack.DefinePlugin(env.stringified),
// Minify the code.
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
comparisons: false,
},
mangle: {
safari10: true,
},
output: {
comments: false,
// Turned on because emoji and regex is not minified properly using default
// https://github.com/facebookincubator/create-react-app/issues/2488
ascii_only: true,
},
sourceMap: shouldUseSourceMap,
}),
// Note: this won't work without ExtractTextPlugin.extract(..) in `loaders`.
new ExtractTextPlugin({
filename: cssFilename,
}),
// Generate a manifest file which contains a mapping of all asset filenames
// to their corresponding output file so that tools can pick it up without
// having to parse `index.html`.
new ManifestPlugin({
fileName: 'asset-manifest.json',
}),
// Generate a service worker script that will precache, and keep up to date,
// the HTML & assets that are part of the Webpack build.
new SWPrecacheWebpackPlugin({
// By default, a cache-busting query parameter is appended to requests
// used to populate the caches, to ensure the responses are fresh.
// If a URL is already hashed by Webpack, then there is no concern
// about it being stale, and the cache-busting can be skipped.
dontCacheBustUrlsMatching: /\.\w{8}\./,
filename: 'service-worker.js',
logger(message) {
if (message.indexOf('Total precache size is') === 0) {
// This message occurs for every build and is a bit too noisy.
return;
}
if (message.indexOf('Skipping static resource') === 0) {
// This message obscures real errors so we ignore it.
// https://github.com/facebookincubator/create-react-app/issues/2612
return;
}
console.log(message);
},
minify: true,
// For unknown URLs, fallback to the index page
navigateFallback: publicUrl + '/index.html',
// Ignores URLs starting from /__ (useful for Firebase):
// https://github.com/facebookincubator/create-react-app/issues/2237#issuecomment-302693219
navigateFallbackWhitelist: [/^(?!\/__).*/],
// Don't precache sourcemaps (they're large) and build asset manifest:
staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
}),
// Moment.js is an extremely popular library that bundles large locale files
// by default due to how Webpack interprets its code. This is a practical
// solution that requires the user to opt into importing specific locales.
// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
// You can remove this if you don't use Moment.js:
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
],
// Some libraries import Node modules but don't use them in the browser.
// Tell Webpack to provide empty mocks for them so importing them works.
node: {
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty',
},
};
webpack.config.dev.js
'use strict';
const autoprefixer = require('autoprefixer');
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
const eslintFormatter = require('react-dev-utils/eslintFormatter');
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
const getClientEnvironment = require('./env');
const paths = require('./paths');
const env = getClientEnvironment(publicUrl);
module.exports = {
entry: [
require.resolve('./polyfills'),
],
output: {
devtoolModuleFilenameTemplate: info =>
path.resolve(info.absoluteResourcePath).replace(/\\/g, '/'),
},
resolve: {
modules: ['node_modules', paths.appNodeModules].concat(
process.env.NODE_PATH.split(path.delimiter).filter(Boolean)
),
extensions: ['.web.js', '.mjs', '.js', '.json', '.web.jsx', '.jsx'],
alias: {
// Support React Native Web
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
'react-native': 'react-native-web',
},
plugins: [
new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
],
},
module: {
strictExportPresence: true,
rules: [
{
oneOf: [
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
loader: require.resolve('url-loader'),
options: {
limit: 10000,
name: 'static/media/[name].[hash:8].[ext]',
},
},
{
test: /\.(js|jsx|mjs)$/,
include: paths.appSrc,
loader: require.resolve('babel-loader'),
options: {
plugins: [
['import', [{ libraryName: 'antd', style: true }]], // import less
],
cacheDirectory: true,
},
},
{
test: /\.less$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
},
},
{
loader: require.resolve('postcss-loader'),
options: {
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
autoprefixer({
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9', // React doesn't support IE8 anyway
],
flexbox: 'no-2009',
}),
],
},
},
{
loader:require.resolve('less-loader'),
options: {
modules: false,
modifyVars: {
"@primary-color": "#f9c700"
}
}
}
],
},
{
test: /\.css$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
},
},
{
loader: require.resolve('postcss-loader'),
options: {
// Necessary for external CSS imports to work
// https://github.com/facebookincubator/create-react-app/issues/2677
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
autoprefixer({
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9', // React doesn't support IE8 anyway
],
flexbox: 'no-2009',
}),
],
},
},
],
},
{
exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/],
loader: require.resolve('file-loader'),
options: {
name: 'static/media/[name].[hash:8].[ext]',
},
},
],
},
],
},
plugins: [
new InterpolateHtmlPlugin(env.raw),
// Generates an `index.html` file with the injected.
new HtmlWebpackPlugin({
inject: true,
template: paths.appHtml,
}),
// Add module names to factory functions so they appear in browser profiler.
new webpack.NamedModulesPlugin(),
new webpack.DefinePlugin(env.stringified),
// This is necessary to emit hot updates (currently CSS only):
new webpack.HotModuleReplacementPlugin(),
new CaseSensitivePathsPlugin(),
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
],
// Some libraries import Node modules but don't use them in the browser.
// Tell Webpack to provide empty mocks for them so importing them works.
node: {
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty',
},
performance: {
hints: false,
},
};
(页面结构开发&)
页面结构开发&
相加一定为24
默认写到文件夹一层,NPM规范会加载index.js如果没有才会寻找名字
this.props.children点击菜单加载页面内容到内容区域
admin.js
Grid栅格,this.props.children加载单页内容
import React from 'react'
import { Row,Col } from 'antd';
import Header from './components/Header'
import Footer from './components/Footer'
import { connect } from 'react-redux'
import NavLeft from './components/NavLeft'
import './style/common.less'
import Home from './pages/home'
class Admin extends React.Component{
render(){
return (
{/* */}
{this.props.children}
);
}
}
export default connect()(Admin)
style/common.js
calc计算
@import './default.less';
@import './loading.less';
ul,li{
list-style: none;
}
.clearfix{
&::after{
content:' ';
clear:both;
display: block;
visibility: hidden;
}
}
.container{
.nav-left{
background-color:#001529;
color: #ffffff;
height: calc(100vh);
}
.main{
height: calc(100vh);
background-color: @colorL;
overflow: auto;
}
.content{
position: relative;
padding: 20px;
}
}
.content-wrap{
background: #ffffff;
border: 1px solid #e8e8e8;
margin-top: -3px;
.ant-table-wrapper{
margin-left: -1px;
margin-right: -2px;
}
}
.ant-card{
button{
margin-right: 10px;
}
}
default.less
less定义变量
/** 常用色值 **/
@colorA: #f9c700;
@colorB: #ff5400;
@colorC: #333;
@colorD: #6699cc;
@colorE: #9cb3c5;
@colorF: #e0e6ec;
@colorG: #666;
@colorH: #999;
@colorI: #ccc;
@colorJ: #d7d7d7;
@colorK: #e3e3e3;
@colorL: #f1f3f5;
@colorM: #ffffff;
@colorN: #e5e5e5;
@colorO: #afafaf;
@colorP: #ff8605;
@colorQ: #f9fbfc;
@colorR: #001529;
@colorS: #002140;
@colorT: #232526;
@colorU: #bebebe;
/** 常用字体大小 **/
@fontA: 34px;
@fontB: 22px;
@fontC: 18px;
@fontD: 16px;
@fontE: 14px;
@fontF: 12px;
@fontG: 20px;
loading.less
/** load **/
.ajax-loading{
display: none;
.loading{
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
padding:0 40px;
height: 80px;
line-height: 80px;
background: rgba(0, 0, 0, 0.75);
border-radius: 6px;
text-align: center;
z-index: 9999;
font-size:@fontD;
color:#fff;
img{
width: 32px;
vertical-align: middle;
}
span{
margin-left:12px;
}
}
.overlay{
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
z-index: 9998;
background: rgb(255, 255, 255);
opacity: 0.1;
}
}
/****/
src/utils/utils.js
import React from 'react';
import { Select } from 'antd'
const Option = Select.Option;
export default {
formateDate(time){
if(!time)return '';
let date = new Date(time);
return date.getFullYear()+'-'+(date.getMonth()+1)+'-'+date.getDate()+' '+date.getHours()+':'+date.getMinutes()+':'+date.getSeconds();
},
pagination(data,callback){
return {
onChange:(current)=>{
callback(current)
},
current:data.result.page,
pageSize:data.result.page_size,
total: data.result.total_count,
showTotal:()=>{
return `共${data.result.total_count}条`
},
showQuickJumper:true
}
},
// 格式化金额,单位:分(eg:430分=4.30元)
formatFee(fee, suffix = '') {
if (!fee) {
return 0;
}
return Number(fee).toFixed(2) + suffix;
},
// 格式化公里(eg:3000 = 3公里)
formatMileage(mileage, text) {
if (!mileage) {
return 0;
}
if (mileage >= 1000) {
text = text || " km";
return Math.floor(mileage / 100) / 10 + text;
} else {
text = text || " m";
return mileage + text;
}
},
// 隐藏手机号中间4位
formatPhone(phone) {
phone += '';
return phone.replace(/(\d{3})\d*(\d{4})/g, '$1***$2')
},
// 隐藏身份证号中11位
formatIdentity(number) {
number += '';
return number.replace(/(\d{3})\d*(\d{4})/g, '$1***********$2')
},
getOptionList(data){
if(!data){
return [];
}
let options = [] //[全部];
data.map((item)=>{
options.push({item.name})
})
return options;
},
/**
* ETable 行点击通用函数
* @param {*选中行的索引} selectedRowKeys
* @param {*选中行对象} selectedItem
*/
updateSelectedItem(selectedRowKeys, selectedRows, selectedIds) {
if (selectedIds) {
this.setState({
selectedRowKeys,
selectedIds: selectedIds,
selectedItem: selectedRows
})
} else {
this.setState({
selectedRowKeys,
selectedItem: selectedRows
})
}
},
}
// 百度天气API接口 //http://api.map.baidu.com/telematics/v3/weather?location=beijing&output=json&ak=3p49MVra6urFRGOT9s8UBWr2
src/axios/index.js
跨域通过callback进行回调接收
import JsonP from 'jsonp'
import axios from 'axios'
import { Modal } from 'antd'
export default class Axios {
static jsonp(options) {
return new Promise((resolve, reject) => {
JsonP(options.url, {
param: 'callback'
}, function (err, response) {
if (response.status == 'success') {
resolve(response);
} else {
reject(response.messsage);
}
})
})
}
static ajax(options){
let loading;
if (options.data && options.data.isShowLoading !== false){
loading = document.getElementById('ajaxLoading');
loading.style.display = 'block';
}
let baseApi = 'https://www.easy-mock.com/mock/5a7278e28d0c633b9c4adbd7/api';
return new Promise((resolve,reject)=>{
axios({
url:options.url,
method:'get',
baseURL:baseApi,
timeout:5000,
params: (options.data && options.data.params) || ''
}).then((response)=>{
if (options.data && options.data.isShowLoading !== false) {
loading = document.getElementById('ajaxLoading');
loading.style.display = 'none';
}
if (response.status == '200'){
let res = response.data;
if (res.code == '0'){
resolve(res);
}else{
Modal.info({
title:"提示",
content:res.msg
})
}
}else{
reject(response.data);
}
})
});
}
}
src/index.js
components/Header/index.js
跨域调用接口,通过jsonp插件的方式调用,jsonp跨域天气,日期格式化
import React from 'react'
import { Row,Col } from "antd"
import './index.less'
import Util from '../../utils/utils'
import axios from '../../axios'
import { connect } from 'react-redux'
class Header extends React.Component{
state={}
componentWillMount(){
this.setState({
userName:'河畔一角'
})
setInterval(()=>{
let sysTime = Util.formateDate(new Date().getTime());
this.setState({
sysTime
})
},1000)
this.getWeatherAPIData();
}
getWeatherAPIData(){
let city = '北京';
axios.jsonp({
url:'http://api.map.baidu.com/telematics/v3/weather?location='+encodeURIComponent(city)+'&output=json&ak=3p49MVra6urFRGOT9s8UBWr2'
}).then((res)=>{
if(res.status == 'success'){
let data = res.results[0].weather_data[0];
this.setState({
dayPictureUrl:data.dayPictureUrl,
weather:data.weather
})
}
})
}
render(){
const { menuName, menuType } = this.props;
return (
{
menuType?
IMooc 通用管理系统
:''
}
欢迎,{this.state.userName}
退出
{
menuType?'':
{menuName || '首页'}
{this.state.sysTime}
{this.state.weather}
}
);
}
}
const mapStateToProps = state => {
return {
menuName: state.menuName
}
};
export default connect(mapStateToProps)(Header)
index.less css实现箭头图标
@import './../../style/default.less';
.header{
background-color: @colorM;
.header-top{
height: 60px;
line-height: 60px;
padding: 0 20px;
text-align: right;
.logo{
line-height: 60px;
text-align: left;
font-size: 18px;
img{
height: 40px;
vertical-align: middle;
}
span{
margin-left: 5px;
}
}
a{
margin-left: 40px;
}
}
.breadcrumb{
height: 40px;
line-height: 40px;
padding: 0 20px;
border-top: 1px solid #f9c700;
.breadcrumb-title{
text-align: center;
font-size: @fontC;
&:after{
position: absolute;
content: '';
left:73px;
top:39px;
border-top: 9px solid @colorM;
border-left: 12px solid transparent;
border-right: 12px solid transparent;
}
}
.weather{
text-align: right;
font-size: 14px;
.date{
margin-right: 10px;
vertical-align: middle;
}
.weather-img{
img{
height: 15px;
}
}
.weather-detail{
margin-left: 5px;
vertical-align: middle;
}
}
}
}
.simple-page{
.header-top{
background:#1890ff;
color:@colorM;
}
}
components/Footer/index.js
import React from 'react'
import './index.less'
export default class Footer extends React.Component {
render() {
return (
版权所有:慕课网&河畔一角(推荐使用谷歌浏览器,可以获得更佳操作页面体验) 技术支持:河畔一角
);
}
}
index.less
@import "./../../style/default.less";
.footer{
height: 100px;
padding: 40px 0;
text-align: center;
color:@colorJ;
}
components/NavLeft/index.js
通过setState和forceUpdate都可以调用render方法渲染页面,使用递归来获取子菜单
递归渲染菜单
import React from 'react'
import { Menu, Icon } from 'antd';
import { NavLink } from 'react-router-dom'
import { connect } from 'react-redux'
import { switchMenu, saveBtnList } from './../../redux/action'
import MenuConfig from './../../config/menuConfig'
import './index.less'
const SubMenu = Menu.SubMenu;
class NavLeft extends React.Component {
state = {
currentKey: ''
}
// 菜单点击
handleClick = ({ item, key }) => {
if (key == this.state.currentKey) {
return false;
}
// 事件派发,自动调用reducer,通过reducer保存到store对象中
const { dispatch } = this.props;
dispatch(switchMenu(item.props.title));
this.setState({
currentKey: key
});
// hashHistory.push(key);
};
componentWillMount(){
const menuTreeNode = this.renderMenu(MenuConfig);
this.setState({
menuTreeNode
})
}
// 菜单渲染
renderMenu =(data)=>{
return data.map((item)=>{
if(item.children){
return (
{ this.renderMenu(item.children)}
)
}
return
{item.title}
})
}
homeHandleClick = () => {
const { dispatch } = this.props;
dispatch(switchMenu('首页'));
this.setState({
currentKey: ""
});
};
render() {
return (
Imooc MS
{ this.state.menuTreeNode }
);
}
}
export default connect()(NavLeft)
index.less
.nav-left{
.logo{
line-height: 90px;
padding-left: 20px;
background-color: #002140;
img{
height: 35px;
}
h1{
color: #ffffff;
font-size: 20px;
display: inline-block;
vertical-align: middle;
margin: 0 0 0 10px;
}
}
}
src/config/menuConfig.js
静态资源,title菜单标题,key是antD加载路由的地址
const menuList = [
{
title:'首页',
key:'/home'
},
{
title:'UI',
key:'/ui',
children:[
{
title:'按钮',
key:'/ui/buttons',
},
{
title:'弹框',
key:'/ui/modals',
},
{
title:'Loading',
key:'/ui/loadings',
},
{
title:'通知提醒',
key:'/ui/notification',
},
{
title:'全局Message',
key:'/ui/messages',
},
{
title:'Tab页签',
key:'/ui/tabs',
},
{
title:'图片画廊',
key:'/ui/gallery',
},
{
title:'轮播图',
key:'/ui/carousel',
}
]
},
{
title:'表单',
key:'/form',
children:[
{
title:'登录',
key:'/form/login',
},
{
title:'注册',
key:'/form/reg',
}
]
},
{
title:'表格',
key:'/table',
children:[
{
title:'基础表格',
key:'/table/basic',
},
{
title:'高级表格',
key:'/table/high',
}
]
},
{
title:'富文本',
key:'/rich'
},
{
title:'城市管理',
key:'/city'
},
{
title:'订单管理',
key:'/order',
btnList:[
{
title:'订单详情',
key:'detail'
},
{
title:'结束订单',
key:'finish'
}
]
},
{
title:'员工管理',
key:'/user'
},
{
title:'车辆地图',
key:'/bikeMap'
},
{
title:'图标',
key:'/charts',
children:[
{
title:'柱形图',
key:'/charts/bar'
},
{
title:'饼图',
key:'/charts/pie'
},
{
title:'折线图',
key:'/charts/line'
},
]
},
{
title:'权限设置',
key:'/permission'
},
];
export default menuList;
pages/home/index.less
@import './../../style/default.less';
.home-wrap{
background-color: @colorM;
height: calc(62vh);
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
}
pages/home/index.js
import React from 'react'
import './index.less'
export default class Home extends React.Component{
render(){
return (
欢迎学习IMooc后台管理系统课程
);
}
}
Router 4.0子路由 动态路由
多路由匹配,switch
指向匹配一个
Redirect重定向
点击之后,会把Home组件的代码替换到Route标签位置,exact精准匹配只有/能匹配上
获取动态路由参数,this.props.match.param.id
url参数
/:id与,match.params.id的名称必须一致
404 4.0路由会一层一层的嵌套,访问不存在路由会加载NoMatch
通过定义路由对象,遍历对象实现路由功能
(4-2- React-Router4.0 路由Demo演示&)
4-2- React-Router4.0 路由Demo演示&
Home.js
混合组件化,跟标签必须是路由HashRouter否则页面没法加载,switch一旦匹配第一个路由不会匹配下面的
import React from 'react'
import {HashRouter , Route , Link, Switch} from 'react-router-dom'
import Main from './Main'
import About from './about'
import Topic from './topic'
export default class Home extends React.Component{
render(){
return (
-
Home
-
About
-
Topics
);
}
}
Main.js
import React from 'react'
import { Link } from 'react-router-dom'
export default class Main extends React.Component {
render() {
return (
this is main page.
);
}
}
about.js
import React from 'react'
export default class About extends React.Component {
render() {
return (
this is about page.
);
}
}
topic.js
import React from 'react'
export default class Topic extends React.Component {
render() {
return (
this is topic page.
);
}
}
(路由演示Demo2&)
路由演示Demo2&
router.js 一开始就访问主页面 Home,嵌套路由子节点标签,没有设置component组件会读取render方法
一开始加载页,路由分离,嵌套路由
import React from 'react'
import {HashRouter as Router,Route,LinK} from 'react-router-dom'
import Main from './Main'
import About from './../route1/about'
import Topic from './../route1/topic'
import Home from './Home'
export default class IRouter extends React.Component{
render(){
return (
}>
);
}
}
Home.js,会把路由匹配的组件替换到this.props.children的位置
import React from 'react'
import { Link } from 'react-router-dom'
export default class Home extends React.Component {
render() {
return (
-
Home1
-
About1
-
Topics1
{this.props.children}
);
}
}
Main.js,再次点击嵌套路由会继续加载about路由
import React from 'react'
import { Link } from 'react-router-dom'
export default class Main extends React.Component {
render() {
return (
this is main page.
嵌套路由
{this.props.children}
);
}
}
动态路由
router.js 404页面
import React from 'react'
import { HashRouter as Router, Route, Link, Switch} from 'react-router-dom'
import Main from './Main'
import Info from './info'
import About from './../route1/about'
import Topic from './../route1/topic'
import Home from './Home'
import NoMatch from './NoMatch'
export default class IRouter extends React.Component{
render(){
return (
}>
);
}
}
Main.js
import React from 'react'
import { Link } from 'react-router-dom'
export default class Main extends React.Component {
render() {
return (
this is main page.
嵌套路由1
嵌套路由2
{this.props.children}
);
}
}
NoMatch.js
import React from 'react'
export default class Home extends React.Component {
render() {
return (
404 No Pages.
);
}
}
info.js 匹配动态路由
import React from 'react'
export default class Info extends React.Component {
render() {
return (
这里是测试动态路由功能。
动态路由的值是:{this.props.match.params.value}
);
}
}
Home.js
import React from 'react'
import { Link } from 'react-router-dom'
export default class Home extends React.Component {
render() {
return (
-
Home1
-
About1
-
Topics1
-
imooc1
-
imooc2
{this.props.children}
);
}
}
src/router.js
只有retrun才能接受传递的组件进行加载
跟组件不能只加载div一个页面要加载其他组件,要包含详情页面,主页面和登录页面
平级路由,子路由render进行嵌套匹配,()=>不需要加大括号,有大括号就没有返回值
全局Router,Router 4.0子路由
\src\index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Life from './pages/demo/Life';
import Admin from './admin'
import Router from './router'
import { Provider } from 'react-redux'
import configureStore from './redux/store/configureStore';
import registerServiceWorker from './registerServiceWorker';
// Redux Store对象,管理所有的Redux状态
const store = configureStore();
ReactDOM.render(
,
document.getElementById('root')
);
// ReactDOM.render(, document.getElementById('root'));
registerServiceWorker();
\src\index.css
body {
margin: 0;
padding: 0;
font-family: sans-serif;
}
\src\router.js
import React from 'react'
import { HashRouter, Route, Switch, Redirect} from 'react-router-dom'
import App from './App'
import Login from './pages/login'
import Admin from './admin'
import Home from './pages/home';
import Buttons from './pages/ui/buttons'
import Modals from './pages/ui/modals'
import NoMatch from './pages/nomatch'
import Loadings from './pages/ui/loadings'
import Notice from './pages/ui/notice'
import Messages from './pages/ui/messages'
import Tabs from './pages/ui/tabs'
import Gallery from './pages/ui/gallery'
import Carousel from './pages/ui/carousel'
import FormLogin from './pages/form/login'
import FormRegister from './pages/form/register'
import BasicTable from './pages/table/basicTable'
import HighTable from './pages/table/highTable'
import Rich from './pages/rich'
import City from './pages/city/index'
import Order from './pages/order/index'
import Common from './common'
import OrderDetail from './pages/order/detail'
import BikeMap from './pages/map/bikeMap'
import User from './pages/user/index'
import Bar from './pages/echarts/bar/index'
import Pie from './pages/echarts/pie/index'
import Line from './pages/echarts/line/index'
import Permission from './pages/permission'
export default class ERouter extends React.Component{
render(){
return (
}
/>
{/* */}
} />
);
}
}
scr/App.js
App入口根组件,接纳任意组件
this.props.children
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
render() {
return (
{this.props.children}
);
}
}
export default App;
src/admin.js
import React from 'react'
import { Row,Col } from 'antd';
import Header from './components/Header'
import Footer from './components/Footer'
import { connect } from 'react-redux'
import NavLeft from './components/NavLeft'
import './style/common.less'
import Home from './pages/home'
class Admin extends React.Component{
render(){
return (
{/* */}
{this.props.children}
);
}
}
export default connect()(Admin)
src\components\NavLeft\index.js
NavLink点击才能跳转,key是路由跳转地址
import React from 'react'
import { Menu, Icon } from 'antd';
import { NavLink } from 'react-router-dom'
import { connect } from 'react-redux'
import { switchMenu, saveBtnList } from './../../redux/action'
import MenuConfig from './../../config/menuConfig'
import './index.less'
const SubMenu = Menu.SubMenu;
class NavLeft extends React.Component {
state = {
currentKey: ''
}
// 菜单点击
handleClick = ({ item, key }) => {
if (key == this.state.currentKey) {
return false;
}
// 事件派发,自动调用reducer,通过reducer保存到store对象中
const { dispatch } = this.props;
dispatch(switchMenu(item.props.title));
this.setState({
currentKey: key
});
// hashHistory.push(key);
};
componentWillMount(){
const menuTreeNode = this.renderMenu(MenuConfig);
this.setState({
menuTreeNode
})
}
// 菜单渲染
renderMenu =(data)=>{
return data.map((item)=>{
if(item.children){
return (
{ this.renderMenu(item.children)}
)
}
return
{item.title}
})
}
homeHandleClick = () => {
const { dispatch } = this.props;
dispatch(switchMenu('首页'));
this.setState({
currentKey: ""
});
};
render() {
return (
Imooc MS
{ this.state.menuTreeNode }
);
}
}
export default connect()(NavLeft)
pages/login/index.js
import React from 'react'
import {Form, Input, Button} from 'antd'
import axios from '../../axios/index'
import Footer from '../../components/Footer'
import Utils from '../../utils/utils'
import './index.less'
const FormItem = Form.Item;
export default class Login extends React.Component {
state = {};
componentDidMount() {//每次进入登录页清除之前的登录信息
}
loginReq = (params) => {
window.location.href = '/#/';
};
render() {
return (
React全家桶+AntD 共享经济热门项目后台管理系统
共享出行 引领城市新经济
{this.state.errorMsg}
慕课欢迎你
)
}
}
class LoginForm extends React.Component {
state = {};
loginSubmit = (e)=> {
e && e.preventDefault();
const _this = this;
this.props.form.validateFieldsAndScroll((err, values) => {
if (!err) {
var formValue = _this.props.form.getFieldsValue();
_this.props.loginSubmit({
username: formValue.username,
password: formValue.password
});
}
});
};
checkUsername = (rule, value, callback) => {
var reg = /^\w+$/;
if (!value) {
callback('请输入用户名!');
} else if (!reg.test(value)) {
callback('用户名只允许输入英文字母');
} else {
callback();
}
};
checkPassword = (rule, value, callback) => {
if (!value) {
callback('请输入密码!');
} else {
callback();
}
};
render() {
const { getFieldDecorator } = this.props.form;
return (
{getFieldDecorator('username', {
initialValue:'admin',
rules: [{validator: this.checkUsername}]
})(
)}
{getFieldDecorator('password', {
initialValue:'admin',
rules: [{validator: this.checkPassword}]
})(
this.pwd = inst } />
)}
登录
)
}
}
LoginForm = Form.create({})(LoginForm);
(AntD UI组件&)
AntD UI组件&
基础组件封装
src/pages/ui/buttons.js
card卡片,Button,icon,loading,按钮组,按钮事件,className
import React from 'react'
import {Card,Button,Radio} from 'antd'
import './ui.less'
export default class Buttons extends React.Component {
state = {
loading:true,
size:'default'
}
handleCloseLoading=()=>{
this.setState({
loading:false
});
}
handleChange = (e)=>{
this.setState({
size:e.target.value
})
}
render() {
return (
Imooc
Imooc
Imooc
Imooc
Imooc
创建
编辑
删除
搜索
下载
确定
点击加载
关闭
{marginBottom:10}}
返回
前进
小
中
大
Imooc
Imooc
Imooc
Imooc
);
}
}
ui.less
.card-wrap{
margin-bottom: 10px;
button{
margin-right: 10px;
}
}
/* modals */
/* use css to set position of modal */
.vertical-center-modal {
text-align: center;
white-space: nowrap;
}
.vertical-center-modal:before {
content: '';
display: inline-block;
height: 100%;
vertical-align: middle;
width: 0;
}
.vertical-center-modal .ant-modal {
display: inline-block;
vertical-align: middle;
top: 0;
text-align: left;
}
/* For demo */
.ant-carousel .slick-slide {
text-align: center;
height: 160px;
line-height: 160px;
background: #364d79;
overflow: hidden;
}
.ant-carousel .slick-slide h3 {
color: #fff;
}
.slider-wrap .ant-carousel .slick-slide{
height: 240px!important;
}
(5-3Model组件使用讲解&)
5-3Model组件使用讲解&
src/pages/ui/modals.js
如果传参this.handleOpen绑定事件一开始时就会自动调用,使用箭头函数()=>去return方法,只有点击才会触发,传参必须要箭头函数,不传参不需要箭头函数。
把变量当做状态属性,如果用中括号就会当做变量动态属性设置
Modal弹框组件,[]对象属性设置,自定义页脚,modal样式,信息确认框
import React from 'react'
import { Card, Button, Modal } from 'antd'
import './ui.less'
export default class Buttons extends React.Component {
state = {
showModal1: false,
showModal2: false,
showModal3: false,
showModal4: false
}
handleOpen = (type)=>{
this.setState({
[type]:true
})
}
handleConfirm = (type)=>{
Modal[type]({
title:'确认?',
content:'你确定你学会了React了吗?',
onOk(){
console.log('Ok')
},
onCancel(){
console.log('Cancel')
}
})
}
render(){
return (
this.handleOpen('showModal1')}>Open
this.handleOpen('showModal2')}>自定义页脚
this.handleOpen('showModal3')}>顶部20px弹框
this.handleOpen('showModal4')}>水平垂直居中
this.handleConfirm('confirm')}>Confirm
this.handleConfirm('info')}>Info
this.handleConfirm('success')}>Success
this.handleConfirm('warning')}>Warning
{
this.setState({
showModal1:false
})
}}
>
欢迎学习慕课新推出的React高级课程
{
this.setState({
showModal2: false
})
}}
>
欢迎学习慕课新推出的React高级课程
{top:20}}
visible={this.state.showModal3}
onCancel={() = {
this.setState({
showModal3: false
})
}}
>
欢迎学习慕课新推出的React高级课程
{
this.setState({
showModal4: false
})
}}
>
欢迎学习慕课新推出的React高级课程
);
}
}
(5-5loading组件使用&)
5-5loading组件使用&
src/pages/ui/loadings.js
大括号必须要使用驼峰的样式
spin组件,margin,遮罩层
import React from 'react'
import { Card, Button, Spin, Icon, Alert} from 'antd'
import './ui.less'
export default class Loadings extends React.Component{
render(){
const icon = {fontSize:24}}/
const iconLoading = { fontSize: 24 }} /
return (
{margin:'0 10px'}}/
{ marginLeft: 10 }} spinning={true}/
{ marginBottom: 10 }}
/
{ marginBottom: 10 }}
/
{ marginBottom: 10 }}
/
);
}
}
(5-6 Notification组件使用&)
5-6 Notification组件使用&
notice.js
Notification通知提醒框
import React from 'react'
import { Card, Button, notification } from 'antd'
import './ui.less'
export default class Buttons extends React.Component {
openNotification = (type,direction)=>{
if (direction){
notification.config({
placement: direction
})
}
notification[type]({
message:'发工资了',
description:'上个月考勤22天,迟到12天,实发工资250,请笑纳'
});
}
render(){
return (
this.openNotification('success')}>Success
this.openNotification('info')}>Info
this.openNotification('warning')}>Warning
this.openNotification('error')}>Error
this.openNotification('success','topLeft')}>Success
this.openNotification('info','topRight')}>Info
this.openNotification('warning','bottomLeft')}>Warning
this.openNotification('error','bottomRight')}>Error
);
}
}
messages.js
import React from 'react'
import { Card, Button, message } from 'antd'
import './ui.less'
export default class Buttons extends React.Component {
showMessage = (type)=>{
message[type]("恭喜你,React课程晋级成功");
}
render(){
return (
this.showMessage('success')}>Success
this.showMessage('info')}>Info
this.showMessage('warning')}>Warning
this.showMessage('error')}>Error
this.showMessage('loading')}>Loading
);
}
}
通过消息,toast
tabs.js
大括号必须是根对象span,activeKey当前打开的Tab页签,绑定箭头函数,componentWillMount,动态页签
import React from 'react'
import { Card, Button, Tabs, message, Icon } from 'antd'
import './ui.less'
const TabPane = Tabs.TabPane;
export default class Buttons extends React.Component {
newTabIndex = 0;
handleCallback = (key)=>{
message.info("Hi,您选择了页签:"+key)
}
componentWillMount(){
const panes = [
{
title:'Tab 1',
content: 'Tab 1',
key:'1'
},
{
title: 'Tab 2',
content: 'Tab 2',
key: '2'
},
{
title: 'Tab 3',
content: 'Tab 3',
key: '3'
}
]
this.setState({
activeKey: panes[0].key,
panes
})
}
onChange = (activeKey)=>{
this.setState({
activeKey
})
}
onEdit = (targetKey, action) => {
this[action](targetKey);
}
add = () => {
const panes = this.state.panes;
const activeKey = `newTab${this.newTabIndex++}`;
panes.push({ title: activeKey, content: 'New Tab Pane', key: activeKey });
this.setState({ panes, activeKey });
}
remove = (targetKey) => {
let activeKey = this.state.activeKey;
let lastIndex;
this.state.panes.forEach((pane, i) => {
if (pane.key === targetKey) {
lastIndex = i - 1;
}
});
const panes = this.state.panes.filter(pane => pane.key !== targetKey);
if (lastIndex >= 0 && activeKey === targetKey) {
activeKey = panes[lastIndex].key;
}
this.setState({ panes, activeKey });
}
render(){
return (
欢迎学习React课程
欢迎学习React课程
React是一个非常受欢迎的MV*框架
欢迎学习React课程
欢迎学习React课程
React是一个非常受欢迎的MV*框架
{
this.state.panes.map((panel)=>{
return
})
}
);
}
}
(图片画廊&)
图片画廊&
gallery.js
Row,Col,图片画廊,map遍历数组,图片Card
public路径下面的内容都是根路径通过/访问
import React from 'react'
import {Card, Row, Col, Modal} from 'antd'
export default class Gallery extends React.Component{
state={
visible:false
}
openGallery = (imgSrc)=>{
this.setState({
visible:true,
currentImg: '/gallery/'+imgSrc
})
}
render(){
const imgs = [
['1.png', '2.png', '3.png', '4.png', '5.png'],
['6.png', '7.png', '8.png', '9.png', '10.png'],
['11.png', '12.png', '13.png', '14.png', '15.png'],
['16.png', '17.png', '18.png', '19.png', '20.png'],
['21.png', '22.png', '23.png', '24.png', '25.png']
]
const imgList = imgs.map((list) => list.map((item) =>
{marginBottom:10}}
cover={
}
>
))
return (
{imgList[0]}
{imgList[1]}
{imgList[2]}
{imgList[3]}
{imgList[4]}
{
this.setState({
visible:false
})
}}
footer={null}
>
{{width:'100%'}}/}
);
}
}
轮播图Carousel
carousel.js
import React from 'react'
import {Card,Carousel} from 'antd'
import './ui.less'
export default class Carousels extends React.Component{
render(){
return (
Ant Motion Banner - React
Ant Motion Banner - Vue
Ant Motion Banner - Angular
);
}
}
(AntD Form组件&)
AntD Form组件&
pages/form/login.js
getFeildDecorator双向绑定,封装表单,内联表单,水平表单,表单校验,js数字正则,Checkbox默认选中
import React from "react";
import { Card, Form, Input, Button, message, Icon, Checkbox } from "antd";
const FormItem = Form.Item;
class FormLogin extends React.Component{
handleSubmit = ()=>{
let userInfo = this.props.form.getFieldsValue();
this.props.form.validateFields((err,values)=>{
if(!err){
message.success(`${userInfo.userName} 恭喜你,您通过本次表单组件学习,当前密码为:${userInfo.userPwd}`)
}
})
}
render(){
const { getFieldDecorator } = this.props.form;
return (
登录
{marginTop:10}}
{width:300}}
{
getFieldDecorator('userName',{
initialValue:'',
rules:[
{
required:true,
message:'用户名不能为空'
},
{
min:5,max:10,
message:'长度不在范围内'
},
{
pattern:new RegExp('^\\w+$','g'),
message:'用户名必须为字母或者数字'
}
]
})(
)
}
{
getFieldDecorator('userPwd', {
initialValue: '',
rules: []
})(
)
}
{
getFieldDecorator('remember', {
valuePropName:'checked',
initialValue: true
})(
记住密码
)
}
{float:'right'}}忘记密码
登录
);
}
}
export default Form.create()(FormLogin);
register.js
栅格布局,xs小于576像素指定占24列用户名独占一行,大于576只占用4行
switch,按钮组,select多选单选,DatePicker,TextArea,TimePicker,moment,upload,checkbox
import React from 'react'
import {Card,Form,Button,Input,Checkbox,Radio,Select,Switch,DatePicker,TimePicker,Upload,Icon,message, InputNumber} from 'antd'
import moment from 'moment';
const FormItem = Form.Item;
const RadioGroup = Radio.Group;
const Option = Select.Option;
const TextArea = Input.TextArea;
class FormRegister extends React.Component{
state={}
handleSubmit = ()=>{
let userInfo = this.props.form.getFieldsValue();
console.log(JSON.stringify(userInfo))
message.success(`${userInfo.userName} 恭喜你,您通过本次表单组件学习,当前密码为:${userInfo.userPwd}`)
}
getBase64 = (img, callback)=>{
const reader = new FileReader();
reader.addEventListener('load', () => callback(reader.result));
reader.readAsDataURL(img);
}
handleChange = (info) => {
if (info.file.status === 'uploading') {
this.setState({ loading: true });
return;
}
if (info.file.status === 'done') {
// Get this url from response in real world.
this.getBase64(info.file.originFileObj, imageUrl => this.setState({
userImg:imageUrl,
loading: false,
}));
}
}
render(){
const { getFieldDecorator } = this.props.form;
const formItemLayout = {
labelCol:{
xs:24,
sm:4
},
wrapperCol:{
xs:24,
sm:12
}
}
const offsetLayout = {
wrapperCol:{
xs:24,
sm:{
span:12,
offset:4
}
}
}
const rowObject = {
minRows: 4, maxRows: 6
}
return (
{
getFieldDecorator('userName', {
initialValue: '',
rules: [
{
required: true,
message: '用户名不能为空'
}
]
})(
)
}
{
getFieldDecorator('userPwd', {
initialValue: ''
})(
)
}
{
getFieldDecorator('sex', {
initialValue: '1'
})(
男
女
)
}
{
getFieldDecorator('age', {
initialValue: 18
})(
)
}
{
getFieldDecorator('state', {
initialValue: '2'
})(
咸鱼一条
风华浪子
北大才子一枚
百度FE
创业者
)
}
{
getFieldDecorator('interest', {
initialValue: ['2','5']
})(
游泳
打篮球
踢足球
跑步
爬山
骑行
桌球
麦霸
)
}
{
getFieldDecorator('isMarried', {
valuePropName:'checked',
initialValue: true
})(
)
}
{
getFieldDecorator('birthday',{
initialValue:moment('2018-08-08')
})(
)
}
{
getFieldDecorator('address',{
initialValue:'北京市海淀区奥林匹克公园'
})(
)
}
{
getFieldDecorator('time')(
)
}
{
getFieldDecorator('userImg')(
{this.state.userImg?
:}
)
}
{
getFieldDecorator('userImg')(
我已阅读过慕课协议
)
}
注册
);
}
}
export default Form.create()(FormRegister);
(AntD Table表格组件&)
AntD Table表格组件&
basicTable.js dataIndex是字段名与数据源一一映射。
axios获取动态数据,render格式化表单,rowSelection单选表单,onRow点击行,表格Table多选框删除数据
import React from 'react';
import { Card, Table, Modal, Button, message} from 'antd';
import axios from './../../axios/index'
import Utils from './../../utils/utils';
export default class BasicTable extends React.Component{
state={
dataSource2:[]
}
params = {
page:1
}
componentDidMount(){
const data = [
{
id:'0',
userName:'Jack',
sex:'1',
state:'1',
interest:'1',
birthday:'2000-01-01',
address:'北京市海淀区奥林匹克公园',
time:'09:00'
},
{
id: '1',
userName: 'Tom',
sex: '1',
state: '1',
interest: '1',
birthday: '2000-01-01',
address: '北京市海淀区奥林匹克公园',
time: '09:00'
},
{
id: '2',
userName: 'Lily',
sex: '1',
state: '1',
interest: '1',
birthday: '2000-01-01',
address: '北京市海淀区奥林匹克公园',
time: '09:00'
},
]
data.map((item,index)=>{
item.key = index;
})
this.setState({
dataSource: data
})
this.request();
}
// 动态获取mock数据
request = ()=>{
let _this = this;
axios.ajax({
url:'/table/list',
data:{
params:{
page:this.params.page
}
}
}).then((res)=>{
if(res.code == 0){
res.result.list.map((item, index) => {
item.key = index;
})
this.setState({
dataSource2:res.result.list,
selectedRowKeys:[],
selectedRows:null,
pagination: Utils.pagination(res,(current)=>{
_this.params.page = current;
this.request();
})
})
}
})
}
onRowClick = (record,index)=>{
let selectKey = [index];
Modal.info({
title:'信息',
content:`用户名:${record.userName},用户爱好:${record.interest}`
})
this.setState({
selectedRowKeys:selectKey,
selectedItem: record
})
}
// 多选执行删除动作
handleDelete = (()=>{
let rows = this.state.selectedRows;
let ids = [];
rows.map((item)=>{
ids.push(item.id)
})
Modal.confirm({
title:'删除提示',
content: `您确定要删除这些数据吗?${ids.join(',')}`,
onOk:()=>{
message.success('删除成功');
this.request();
}
})
})
render(){
const columns = [
{
title:'id',
key:'id',
dataIndex:'id'
},
{
title: '用户名',
key: 'userName',
dataIndex: 'userName'
},
{
title: '性别',
key: 'sex',
dataIndex: 'sex',
render(sex){
return sex ==1 ?'男':'女'
}
},
{
title: '状态',
key: 'state',
dataIndex: 'state',
render(state){
let config = {
'1':'咸鱼一条',
'2':'风华浪子',
'3':'北大才子',
'4':'百度FE',
'5':'创业者'
}
return config[state];
}
},
{
title: '爱好',
key: 'interest',
dataIndex: 'interest',
render(abc) {
let config = {
'1': '游泳',
'2': '打篮球',
'3': '踢足球',
'4': '跑步',
'5': '爬山',
'6': '骑行',
'7': '桌球',
'8': '麦霸'
}
return config[abc];
}
},
{
title: '生日',
key: 'birthday',
dataIndex: 'birthday'
},
{
title: '地址',
key: 'address',
dataIndex: 'address'
},
{
title: '早起时间',
key: 'time',
dataIndex: 'time'
}
]
const selectedRowKeys = this.state.selectedRowKeys;
const rowSelection = {
type:'radio',
selectedRowKeys
}
const rowCheckSelection = {
type: 'checkbox',
selectedRowKeys,
onChange:(selectedRowKeys,selectedRows)=>{
this.setState({
selectedRowKeys,
selectedRows
})
}
}
return (
{margin:'10px 0'}}
{ margin: '10px 0' }}
{
return {
onClick:()=>{
this.onRowClick(record,index);
}
};
}}
columns={columns}
dataSource={this.state.dataSource2}
pagination={false}
/>
{ margin: '10px 0' }}
{marginBottom:10}}
删除
{ margin: '10px 0' }}
,
数据字典转为对应文字
获取选中行数据
src/axios.js
axios工具封装
import JsonP from 'jsonp'
import axios from 'axios'
import { Modal } from 'antd'
export default class Axios {
static jsonp(options) {
return new Promise((resolve, reject) => {
JsonP(options.url, {
param: 'callback'
}, function (err, response) {
if (response.status == 'success') {
resolve(response);
} else {
reject(response.messsage);
}
})
})
}
static ajax(options){
let loading;
if (options.data && options.data.isShowLoading !== false){
loading = document.getElementById('ajaxLoading');
loading.style.display = 'block';
}
let baseApi = 'https://www.easy-mock.com/mock/5a7278e28d0c633b9c4adbd7/api';
return new Promise((resolve,reject)=>{
axios({
url:options.url,
method:'get',
baseURL:baseApi,
timeout:5000,
params: (options.data && options.data.params) || ''
}).then((response)=>{
if (options.data && options.data.isShowLoading !== false) {
loading = document.getElementById('ajaxLoading');
loading.style.display = 'none';
}
if (response.status == '200'){
let res = response.data;
if (res.code == '0'){
resolve(res);
}else{
Modal.info({
title:"提示",
content:res.msg
})
}
}else{
reject(response.data);
}
})
});
}
}
实现分页
utils.js 分页工具,callback点击下一页要获取的页面
分页pagination分页工具类
import React from 'react';
import { Select } from 'antd'
const Option = Select.Option;
export default {
formateDate(time){
if(!time)return '';
let date = new Date(time);
return date.getFullYear()+'-'+(date.getMonth()+1)+'-'+date.getDate()+' '+date.getHours()+':'+date.getMinutes()+':'+date.getSeconds();
},
pagination(data,callback){
return {
onChange:(current)=>{
callback(current)
},
current:data.result.page,
pageSize:data.result.page_size,
total: data.result.total_count,
showTotal:()=>{
return `共${data.result.total_count}条`
},
showQuickJumper:true
}
},
// 格式化金额,单位:分(eg:430分=4.30元)
formatFee(fee, suffix = '') {
if (!fee) {
return 0;
}
return Number(fee).toFixed(2) + suffix;
},
// 格式化公里(eg:3000 = 3公里)
formatMileage(mileage, text) {
if (!mileage) {
return 0;
}
if (mileage >= 1000) {
text = text || " km";
return Math.floor(mileage / 100) / 10 + text;
} else {
text = text || " m";
return mileage + text;
}
},
// 隐藏手机号中间4位
formatPhone(phone) {
phone += '';
return phone.replace(/(\d{3})\d*(\d{4})/g, '$1***$2')
},
// 隐藏身份证号中11位
formatIdentity(number) {
number += '';
return number.replace(/(\d{3})\d*(\d{4})/g, '$1***********$2')
},
getOptionList(data){
if(!data){
return [];
}
let options = [] //[全部];
data.map((item)=>{
options.push({item.name})
})
return options;
},
/**
* ETable 行点击通用函数
* @param {*选中行的索引} selectedRowKeys
* @param {*选中行对象} selectedItem
*/
updateSelectedItem(selectedRowKeys, selectedRows, selectedIds) {
if (selectedIds) {
this.setState({
selectedRowKeys,
selectedIds: selectedIds,
selectedItem: selectedRows
})
} else {
this.setState({
selectedRowKeys,
selectedItem: selectedRows
})
}
},
}
page当前第几页,pageSize一页的数据,total一共多少条数据
(高级表格&)
高级表格&
表头固定,左右侧栏目固定
setState使用场景,scroll滚动,表头固定colums,固定列fixed,sorter排序,Badge状态栏,Table操作栏
import React from 'react';
import { Card, Table, Modal, Button, message, Badge } from 'antd';
import axios from './../../axios/index'
import Utils from './../../utils/utils';
export default class BasicTable extends React.Component {
state = {
}
params = {
page:1
}
componentDidMount(){
this.request();
}
// 动态获取mock数据
request = () => {
let _this = this;
axios.ajax({
url: '/table/high/list',
data: {
params: {
page: this.params.page
}
}
}).then((res) => {
if (res.code == 0) {
res.result.list.map((item, index) => {
item.key = index;
})
this.setState({
dataSource: res.result.list
})
}
})
}
handleChange = (pagination, filters, sorter)=>{
console.log("::" + sorter)
this.setState({
sortOrder:sorter.order
})
}
// 删除操作
handleDelete = (item)=>{
let id = item.id;
Modal.confirm({
title:'确认',
content:'您确认要删除此条数据吗?',
onOk:()=>{
message.success('删除成功');
this.request();
}
})
}
render(){
const columns = [
{
title: 'id',
key: 'id',
width:80,
dataIndex: 'id'
},
{
title: '用户名',
key: 'userName',
width: 80,
dataIndex: 'userName'
},
{
title: '性别',
key: 'sex',
width: 80,
dataIndex: 'sex',
render(sex) {
return sex == 1 ? '男' : '女'
}
},
{
title: '状态',
key: 'state',
width: 80,
dataIndex: 'state',
render(state) {
let config = {
'1': '咸鱼一条',
'2': '风华浪子',
'3': '北大才子',
'4': '百度FE',
'5': '创业者'
}
return config[state];
}
},
{
title: '爱好',
key: 'interest',
width: 80,
dataIndex: 'interest',
render(abc) {
let config = {
'1': '游泳',
'2': '打篮球',
'3': '踢足球',
'4': '跑步',
'5': '爬山',
'6': '骑行',
'7': '桌球',
'8': '麦霸'
}
return config[abc];
}
},
{
title: '生日',
key: 'birthday',
width: 120,
dataIndex: 'birthday'
},
{
title: '地址',
key: 'address',
width: 120,
dataIndex: 'address'
},
{
title: '早起时间',
key: 'time',
width: 80,
dataIndex: 'time'
}
]
const columns2 = [
{
title: 'id',
key: 'id',
width: 80,
fixed:'left',
dataIndex: 'id'
},
{
title: '用户名',
key: 'userName',
width: 80,
fixed: 'left',
dataIndex: 'userName'
},
{
title: '性别',
key: 'sex',
width: 80,
dataIndex: 'sex',
render(sex) {
return sex == 1 ? '男' : '女'
}
},
{
title: '状态',
key: 'state',
width: 80,
dataIndex: 'state',
render(state) {
let config = {
'1': '咸鱼一条',
'2': '风华浪子',
'3': '北大才子',
'4': '百度FE',
'5': '创业者'
}
return config[state];
}
},
{
title: '爱好',
key: 'interest',
width: 80,
dataIndex: 'interest',
render(abc) {
let config = {
'1': '游泳',
'2': '打篮球',
'3': '踢足球',
'4': '跑步',
'5': '爬山',
'6': '骑行',
'7': '桌球',
'8': '麦霸'
}
return config[abc];
}
},
{
title: '生日',
key: 'birthday',
width: 120,
dataIndex: 'birthday'
},
{
title: '生日',
key: 'birthday',
width: 120,
dataIndex: 'birthday'
}, {
title: '生日',
key: 'birthday',
width: 120,
dataIndex: 'birthday'
}, {
title: '生日',
key: 'birthday',
width: 120,
dataIndex: 'birthday'
}, {
title: '生日',
key: 'birthday',
width: 120,
dataIndex: 'birthday'
}, {
title: '生日',
key: 'birthday',
width: 120,
dataIndex: 'birthday'
}, {
title: '生日',
key: 'birthday',
width: 120,
dataIndex: 'birthday'
}, {
title: '生日',
key: 'birthday',
width: 120,
dataIndex: 'birthday'
}, {
title: '生日',
key: 'birthday',
width: 120,
dataIndex: 'birthday'
}, {
title: '生日',
key: 'birthday',
width: 120,
dataIndex: 'birthday'
}, {
title: '生日',
key: 'birthday',
width: 120,
dataIndex: 'birthday'
}, {
title: '生日',
key: 'birthday',
width: 120,
dataIndex: 'birthday'
}, {
title: '生日',
key: 'birthday',
width: 120,
dataIndex: 'birthday'
}, {
title: '生日',
key: 'birthday',
width: 120,
dataIndex: 'birthday'
}, {
title: '生日',
key: 'birthday',
width: 120,
dataIndex: 'birthday'
}, {
title: '生日',
key: 'birthday',
width: 120,
dataIndex: 'birthday'
}, {
title: '生日',
key: 'birthday',
width: 120,
dataIndex: 'birthday'
},
{
title: '地址',
key: 'address',
width: 120,
fixed: 'right',
dataIndex: 'address'
},
{
title: '早起时间',
key: 'time',
width: 80,
fixed: 'right',
dataIndex: 'time'
}
]
const columns3 = [
{
title: 'id',
key: 'id',
dataIndex: 'id'
},
{
title: '用户名',
key: 'userName',
dataIndex: 'userName'
},
{
title: '性别',
key: 'sex',
dataIndex: 'sex',
render(sex) {
return sex == 1 ? '男' : '女'
}
},
{
title: '年龄',
key: 'age',
dataIndex: 'age',
sorter:(a,b)=>{
return a.age - b.age;
},
sortOrder:this.state.sortOrder
},
{
title: '状态',
key: 'state',
dataIndex: 'state',
render(state) {
let config = {
'1': '咸鱼一条',
'2': '风华浪子',
'3': '北大才子',
'4': '百度FE',
'5': '创业者'
}
return config[state];
}
},
{
title: '爱好',
key: 'interest',
dataIndex: 'interest',
render(abc) {
let config = {
'1': '游泳',
'2': '打篮球',
'3': '踢足球',
'4': '跑步',
'5': '爬山',
'6': '骑行',
'7': '桌球',
'8': '麦霸'
}
return config[abc];
}
},
{
title: '生日',
key: 'birthday',
dataIndex: 'birthday'
},
{
title: '地址',
key: 'address',
dataIndex: 'address'
},
{
title: '早起时间',
key: 'time',
dataIndex: 'time'
}
]
const columns4 = [
{
title: 'id',
dataIndex: 'id'
},
{
title: '用户名',
dataIndex: 'userName'
},
{
title: '性别',
dataIndex: 'sex',
render(sex) {
return sex == 1 ? '男' : '女'
}
},
{
title: '年龄',
dataIndex: 'age'
},
{
title: '状态',
dataIndex: 'state',
render(state) {
let config = {
'1': '咸鱼一条',
'2': '风华浪子',
'3': '北大才子',
'4': '百度FE',
'5': '创业者'
}
return config[state];
}
},
{
title: '爱好',
dataIndex: 'interest',
render(abc) {
let config = {
'1': ,
'2': ,
'3': ,
'4': ,
'5':
}
return config[abc];
}
},
{
title: '生日',
dataIndex: 'birthday'
},
{
title: '地址',
dataIndex: 'address'
},
{
title: '操作',
render:(text,item)=>{
return { this.handleDelete(item) }}>删除
}
}
]
return (
{y:240}}
/
{ margin: '10px 0' }}
{ x: 2650 }}
/
{ margin: '10px 0' }}
{ margin: '10px 0' }}
)
}
}