React前后端分离实战租房项目一
好客租房-20天
ES6新特性以及ReactJS入门
day01-ES6新特性以及ReactJS入门&
(02.ES6新特性之了解ES6以及其发展历史&)
02.ES6新特性之了解ES6以及其发展历史&
let声明变量
(04.ES6新特性之字符串扩展&)
04.ES6新特性之字符串扩展&
判断字符串是否包含
解构表达式
参数默认值
箭头函数
对象属性简写
函数参数解构
map和reduce
三点扩展运算符
Promise
set和Map集合
class类语法
generator函数
for ...of循环
修饰器注解,
Babel
umi部署安装
模块化
(18.ReactJS入门之前端开发的演变&)
18.ReactJS入门之前端开发的演变&
(19.ReactJS入门之ReactJS简介&)
19.ReactJS入门之ReactJS简介&
(20.ReactJS入门之环境搭建以及编写HelloWorld程序&)
20.ReactJS入门之环境搭建以及编写HelloWorld程序&
创建项目工程
JSX语法
组件拆分
组件参数传递props.children
state组件状态
三点扩展运算符
组件生命周期函数
day02-Ant Design以及Ant Design Pro入门
(02.ReactJS入门之Model分层的概念&)
02.ReactJS入门之Model分层的概念&
(03.ReactJS入门之dva的使用&)
03.ReactJS入门之dva的使用&
DVA框架
ListData.js
import request from '../util/request';
export default {
namespace: 'list',
state: {
data: [],
maxNum: 1
},
reducers : { // 定义的一些函数
addNewData : function (state, result) { // state:指的是更新之前的状态数据, result: 请求到的数据
if(result.data){ //如果state中存在data数据,直接返回,在做初始化的操作
return result.data;
}
let maxNum = state.maxNum + 1;
let newArr = [...state.data, maxNum];
return {
data : newArr,
maxNum : maxNum
}
//通过return 返回更新后的数据
}
},
effects: { //新增effects配置,用于异步加载数据
*initData(params, sagaEffects) { //定义异步方法
const {call, put} = sagaEffects; //获取到call、put方法
const url = "/ds/list"; // 定义请求的url
let data = yield call(request, url); //执行请求
yield put({ // 调用reducers中的方法
type : "addNewData", //指定方法名
data : data //传递ajax回来的数据
});
}
}
}
List.js
import React from 'react';
import { connect } from 'dva';
const namespace = "list";
// 说明:第一个回调函数,作用:将page层和model层进行链接,返回modle中的数据
// 并且,将返回的数据,绑定到this.props
// 接收第二个函数,这个函数的作用:将定义的函数绑定到this.props中,调用model层中定义的函数
@connect((state) => {
return {
dataList : state[namespace].data,
maxNum : state[namespace].maxNum
}
}, (dispatch) => { // dispatch的作用:可以调用model层定义的函数
return { // 将返回的函数,绑定到this.props中
add : function () {
dispatch({ //通过dispatch调用modle中定义的函数,通过type属性,指定函数命名,格式:namespace/函数名
type : namespace + "/addNewData"
});
},
init : () => {
dispatch({ //通过dispatch调用modle中定义的函数,通过type属性,指定函数命名,格式:namespace/函数名
type : namespace + "/initData"
});
}
}
})
class List extends React.Component{
componentDidMount(){
//初始化的操作
this.props.init();
}
render(){
return (
{
this.props.dataList.map((value,index)=>{
return - {value}
})
}
);
}
}
export default List;
(04.ReactJS入门之dva的使用(实现点击事件)&)
04.ReactJS入门之dva的使用(实现点击事件)&
(05.ReactJS入门之Model中请求数据&)
05.ReactJS入门之Model中请求数据&
request.js
// import fetch from 'dva/fetch';
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
}
const error = new Error(response.statusText);
error.response = response;
throw error;
}
/**
* Requests a URL, returning a promise.
*
* @param {string} url The URL we want to request
* @param {object} [options] The options we want to pass to "fetch"
* @return {object} An object containing either "data" or "err"
*/
export default async function request(url, options) {
const response = await fetch(url, options);
checkStatus(response);
return await response.json();
}
(06.ReactJS入门之mock数据&)
06.ReactJS入门之mock数据&
MockListData.js mock数据
export default {
'get /ds/list': function (req, res) { //模拟请求返回数据
res.json({
data: [1, 2, 3, 4, 5],
maxNum: 5
});
},
'get /ds/user/list': function (req, res) {
res.json([{
key: '1',
name: '张三1',
age: 32,
address: '上海市',
tags: ['程序员', '帅气'],
}, {
key: '2',
name: '李四2',
age: 42,
address: '北京市',
tags: ['屌丝'],
}, {
key: '3',
name: '王五3',
age: 32,
address: '杭州市',
tags: ['高富帅', '富二代'],
}]);
}
}
(07.Ant Design入门之介绍&)
07.Ant Design入门之介绍&
(08.Ant Design入门之开始使用&)
08.Ant Design入门之开始使用&
MyTabs.js tab组件
import React from 'react';
import { Tabs } from 'antd'; // 第一步,导入需要使用的组件
const TabPane = Tabs.TabPane;
function callback(key) {
console.log(key);
}
class MyTabs extends React.Component{
render(){
return (
hello antd wo de 第一个 tabs
Content of Tab Pane 2
Content of Tab Pane 3
)
}
}
export default MyTabs;
(09.Ant Design入门之布局&)
09.Ant Design入门之布局&
布局layout
src\layouts\index.js
导航栏 菜单链接
import React from 'react';
import { Layout, Menu, Icon } from 'antd';
import Link from 'umi/link';
const { Header, Footer, Sider, Content } = Layout;
const SubMenu = Menu.SubMenu;
class BasicLayout extends React.Component{
constructor(props){
super(props);
this.state = {
collapsed: false,
}
}
render(){
return (
Header
{this.props.children}
)
}
}
export default BasicLayout;
umi路由配置
props.children
(10.Ant Design入门之美化布局和引入导航条&)
10.Ant Design入门之美化布局和引入导航条&
(11.Ant Design入门之导航菜单添加链接&)
11.Ant Design入门之导航菜单添加链接&
src\pages\user\UserList.js
import React from 'react';
import { connect } from 'dva';
import {Table, Divider, Tag, Pagination } from 'antd';
const {Column} = Table;
const namespace = 'userList';
@connect((state)=>{
return {
data : state[namespace].list
}
}, (dispatch) => {
return {
initData : () => {
dispatch({
type: namespace + "/initData"
});
}
}
})
class UserList extends React.Component {
componentDidMount(){
this.props.initData();
}
render() {
return (
);
}
}
export default UserList;
src\pages\user\UserAdd.js
import React from 'react'
class UserAdd extends React.Component{
render(){
return (
新增用户
);
}
}
export default UserAdd;
(12.Ant Design入门之表格的基本使用&)
12.Ant Design入门之表格的基本使用&
(13.Ant Design入门之表格的数据分离&)
13.Ant Design入门之表格的数据分离&
day03-项目介绍以及开发后台系统
(07.后台系统开发之form表单组件以及表单提交的讲解&)
07.后台系统开发之form表单组件以及表单提交的讲解&
form方法
表单校验
AddResource.js getFieldDecorator双向数据绑定,提交表单,class内部this,提交表单多选项,input组件设置
import React, { PureComponent } from 'react';
import { connect } from 'dva';
import { formatMessage, FormattedMessage } from 'umi/locale';
import {
Form,
Input,
DatePicker,
Select,
Button,
Card,
InputNumber,
Radio,
Icon,
Tooltip,
Checkbox,
AutoComplete
} from 'antd';
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
import PicturesWall from '../Utils/PicturesWall';
const FormItem = Form.Item;
const { Option } = Select;
const { RangePicker } = DatePicker;
const { TextArea } = Input;
const Search = Input.Search;
const InputGroup = Input.Group;
const CheckboxGroup = Checkbox.Group;
const estateMap = new Map([
['中远两湾城','1001|上海市,上海市,普陀区,远景路97弄'],
['上海康城','1002|上海市,上海市,闵行区,莘松路958弄'],
['保利西子湾','1003|上海市,上海市,松江区,广富林路1188弄'],
['万科城市花园','1004|上海市,上海市,闵行区,七莘路3333弄2区-15区'],
['上海阳城','1005|上海市,上海市,闵行区,罗锦路888弄']
]);
@connect(({ loading }) => ({
submitting: loading.effects['form/submitRegularForm'],
}))
@Form.create()
class AddResource extends PureComponent {
handleSubmit = e => {
const { dispatch, form } = this.props;
e.preventDefault();
console.log(this.state.fileList);
form.validateFieldsAndScroll((err, values) => {
if (!err) {
if(values.facilities){
values.facilities = values.facilities.join(",");
}
if(values.floor_1 && values.floor_2){
values.floor = values.floor_1 + "/" + values.floor_2;
}
values.houseType = values.houseType_1 + "室" + values.houseType_2 + "厅"
+ values.houseType_3 + "卫" + values.houseType_4 + "厨"
+ values.houseType_2 + "阳台";
delete values.floor_1;
delete values.floor_2;
delete values.houseType_1;
delete values.houseType_2;
delete values.houseType_3;
delete values.houseType_4;
delete values.houseType_5;
dispatch({
type: 'form/submitRegularForm',
payload: values,
});
}
});
};
handleSearch = (value)=>{
let arr = new Array();
if(value.length > 0 ){
estateMap.forEach((v, k) => {
if(k.startsWith(value)){
arr.push(k);
}
});
}
this.setState({
estateDataSource: arr
});
} ;
handleFileList = (obj)=>{
console.log(obj, "图片列表");
}
constructor(props){
super(props);
this.state = {
estateDataSource : [],
estateAddress : '',
estateId : ''
}
}
render() {
const { submitting } = this.props;
const {
form: { getFieldDecorator, getFieldValue },
} = this.props;
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 7 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 12 },
md: { span: 10 },
},
};
const submitFormLayout = {
wrapperCol: {
xs: { span: 24, offset: 0 },
sm: { span: 10, offset: 7 },
},
};
return (
);
}
}
export default AddResource;
(08.后台系统开发之新增房源的自动完成功能的讲解&)
08.后台系统开发之新增房源的自动完成功能的讲解&
AutoComplete级联自动完成表单内容填写
day04-服务的具体实现以及MybatisPlus的入门
(10.新增房源服务实现之搭建工程&)
10.新增房源服务实现之搭建工程&
(11.新增房源服务实现之AutoGenerator使用以及创建pojo对象&)
11.新增房源服务实现之AutoGenerator使用以及创建pojo对象&
BasePojo.java lombok @Data
package cn.itcast.haoke.dubbo.server.pojo;
import lombok.Data;
import java.util.Date;
@Data
public abstract class BasePojo implements java.io.Serializable {
private Date created;
private Date updated;
}
HouseResources.java Accessors链式实体编程
package cn.itcast.haoke.dubbo.server.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
*
* 房源表
*
*
* @author itcast
*/
@Data
@Accessors(chain = true)
@TableName("tb_house_resources")
public class HouseResources extends BasePojo {
private static final long serialVersionUID = 779152022777511825L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 房源标题
*/
private String title;
/**
* 楼盘id
*/
private Long estateId;
/**
* 楼号(栋)
*/
private String buildingNum;
/**
* 单元号
*/
private String buildingUnit;
/**
* 门牌号
*/
private String buildingFloorNum;
/**
* 租金
*/
private Integer rent;
/**
* 租赁方式,1-整租,2-合租
*/
private Integer rentMethod;
/**
* 支付方式,1-付一押一,2-付三押一,3-付六押一,4-年付押一,5-其它
*/
private Integer paymentMethod;
/**
* 户型,如:2室1厅1卫
*/
private String houseType;
/**
* 建筑面积
*/
private String coveredArea;
/**
* 使用面积
*/
private String useArea;
/**
* 楼层,如:8/26
*/
private String floor;
/**
* 朝向:东、南、西、北
*/
private String orientation;
/**
* 装修,1-精装,2-简装,3-毛坯
*/
private Integer decoration;
/**
* 配套设施, 如:1,2,3
*/
private String facilities;
/**
* 图片,最多5张
*/
private String pic;
/**
* 描述
*/
private String houseDesc;
/**
* 联系人
*/
private String contact;
/**
* 手机号
*/
private String mobile;
/**
* 看房时间,1-上午,2-中午,3-下午,4-晚上,5-全天
*/
private Integer time;
/**
* 物业费
*/
private String propertyCost;
}
ApiHouseResourcesService.java
package cn.itcast.haoke.dubbo.server.api;
import cn.itcast.haoke.dubbo.server.pojo.HouseResources;
public interface ApiHouseResourcesService {
/**
* 新增房源
*
* @param houseResources
*
* @return -1:输入的参数不符合要求,0:数据插入数据库失败,1:成功
*/
int saveHouseResources(HouseResources houseResources);
}
(12.新增房源服务实现之新增房源服务的具体实现&)
12.新增房源服务实现之新增房源服务的具体实现&
(13.新增房源服务实现之新增房源RESTful接口的开发&)
13.新增房源服务实现之新增房源RESTful接口的开发
HouseResourcesService.java
package cn.itcast.haoke.dubbo.api.service;
import cn.itcast.haoke.dubbo.server.api.ApiHouseResourcesService;
import cn.itcast.haoke.dubbo.server.pojo.HouseResources;
import com.alibaba.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Service;
@Service
public class HouseResourcesService {
@Reference(version = "1.0.0")
private ApiHouseResourcesService apiHouseResourcesService;
public boolean save(HouseResources houseResources) {
int result =
this.apiHouseResourcesService.saveHouseResources(houseResources);
return result == 1;
}
}
HouseResourcesController.java ResponseEntity @ResponseBody
package cn.itcast.haoke.dubbo.api.controller;
import cn.itcast.haoke.dubbo.api.service.HouseResourcesService;
import cn.itcast.haoke.dubbo.server.pojo.HouseResources;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping("house/resources")
public class HouseResourcesController {
@Autowired
private HouseResourcesService houseResourcesService;
/**
* 新增房源
*
* @param houseResources json数据
* @return
*/
@PostMapping
@ResponseBody
public ResponseEntity save(@RequestBody HouseResources houseResources) {
try {
boolean bool = this.houseResourcesService.save(houseResources);
if (bool) {
return ResponseEntity.status(HttpStatus.CREATED).build();
}
} catch (Exception e) {
e.printStackTrace();
}
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
/**
* test
*
* @return
*/
@GetMapping
@ResponseBody
public ResponseEntity get() {
return ResponseEntity.ok("ok");
}
}
\models\form.js
import { routerRedux } from 'dva/router';
import { message } from 'antd';
import { addHouseResource } from '@/services/haoke';
export default {
namespace: 'house',
state: {
},
effects: {
*submitHouseForm({ payload }, { call }) {
yield call(addHouseResource, payload);
message.success('提交成功');
}
},
reducers: {
saveStepFormData(state, { payload }) {
return {
...state
};
},
},
};
haoke.js
import request from '@/utils/request';
export async function addHouseResource(params) {
return request('/haoke/house/resources', {
method: 'POST',
body: params
});
}
request.js request封装状态码
import fetch from 'dva/fetch';
import { notification } from 'antd';
import router from 'umi/router';
import hash from 'hash.js';
import { isAntdPro } from './utils';
const codeMessage = {
200: '服务器成功返回请求的数据。',
201: '新建或修改数据成功。',
202: '一个请求已经进入后台排队(异步任务)。',
204: '删除数据成功。',
400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
401: '用户没有权限(令牌、用户名、密码错误)。',
403: '用户得到授权,但是访问是被禁止的。',
404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
406: '请求的格式不可得。',
410: '请求的资源被永久删除,且不会再得到的。',
422: '当创建一个对象时,发生一个验证错误。',
500: '服务器发生错误,请检查服务器。',
502: '网关错误。',
503: '服务不可用,服务器暂时过载或维护。',
504: '网关超时。',
};
const checkStatus = response => {
if (response.status >= 200 && response.status < 300) {
return response;
}
const errortext = codeMessage[response.status] || response.statusText;
notification.error({
message: `请求错误 ${response.status}: ${response.url}`,
description: errortext,
});
const error = new Error(errortext);
error.name = response.status;
error.response = response;
throw error;
};
const cachedSave = (response, hashcode) => {
/**
* Clone a response data and store it in sessionStorage
* Does not support data other than json, Cache only json
*/
const contentType = response.headers.get('Content-Type');
if (contentType && contentType.match(/application\/json/i)) {
// All data is saved as text
response
.clone()
.text()
.then(content => {
sessionStorage.setItem(hashcode, content);
sessionStorage.setItem(`${hashcode}:timestamp`, Date.now());
});
}
return response;
};
/**
* Requests a URL, returning a promise.
*
* @param {string} url The URL we want to request
* @param {object} [option] The options we want to pass to "fetch"
* @return {object} An object containing either "data" or "err"
*/
export default function request(
url,
option,
) {
const options = {
expirys: isAntdPro(),
...option,
};
/**
* Produce fingerprints based on url and parameters
* Maybe url has the same parameters
*/
const fingerprint = url + (options.body ? JSON.stringify(options.body) : '');
const hashcode = hash
.sha256()
.update(fingerprint)
.digest('hex');
const defaultOptions = {
credentials: 'include',
};
const newOptions = { ...defaultOptions, ...options };
if (
newOptions.method === 'POST' ||
newOptions.method === 'PUT' ||
newOptions.method === 'DELETE'
) {
if (!(newOptions.body instanceof FormData)) {
newOptions.headers = {
Accept: 'application/json',
'Content-Type': 'application/json; charset=utf-8',
...newOptions.headers,
};
newOptions.body = JSON.stringify(newOptions.body);
} else {
// newOptions.body is FormData
newOptions.headers = {
Accept: 'application/json',
...newOptions.headers,
};
}
}
const expirys = options.expirys && 60;
// options.expirys !== false, return the cache,
if (options.expirys !== false) {
const cached = sessionStorage.getItem(hashcode);
const whenCached = sessionStorage.getItem(`${hashcode}:timestamp`);
if (cached !== null && whenCached !== null) {
const age = (Date.now() - whenCached) / 1000;
if (age < expirys) {
const response = new Response(new Blob([cached]));
return response.json();
}
sessionStorage.removeItem(hashcode);
sessionStorage.removeItem(`${hashcode}:timestamp`);
}
}
return fetch(url, newOptions)
.then(checkStatus)
.then(response => cachedSave(response, hashcode))
.then(response => {
// DELETE and 204 do not return data by default
// using .json will report an error.
if (newOptions.method === 'DELETE' || response.status === 204) {
return response.text();
}
return response.json();
})
.catch(e => {
const status = e.name;
if (status === 401) {
// @HACK
/* eslint-disable no-underscore-dangle */
window.g_app._store.dispatch({
type: 'login/logout',
});
return;
}
// environment should not be used
if (status === 403) {
router.push('/exception/403');
return;
}
if (status <= 504 && status >= 500) {
router.push('/exception/500');
return;
}
if (status >= 404 && status < 422) {
router.push('/exception/404');
}
});
}
AddResource.js
import React, { PureComponent } from 'react';
import { connect } from 'dva';
import { formatMessage, FormattedMessage } from 'umi/locale';
import {
Form,
Input,
DatePicker,
Select,
Button,
Card,
InputNumber,
Radio,
Icon,
Tooltip,
Checkbox,
AutoComplete
} from 'antd';
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
import PicturesWall from '../Utils/PicturesWall';
const FormItem = Form.Item;
const { Option } = Select;
const { RangePicker } = DatePicker;
const { TextArea } = Input;
const Search = Input.Search;
const InputGroup = Input.Group;
const CheckboxGroup = Checkbox.Group;
const estateMap = new Map([
['中远两湾城','1001|上海市,上海市,普陀区,远景路97弄'],
['上海康城','1002|上海市,上海市,闵行区,莘松路958弄'],
['保利西子湾','1003|上海市,上海市,松江区,广富林路1188弄'],
['万科城市花园','1004|上海市,上海市,闵行区,七莘路3333弄2区-15区'],
['上海阳城','1005|上海市,上海市,闵行区,罗锦路888弄']
]);
@connect(({ loading }) => ({
submitting: loading.effects['form/submitRegularForm'],
}))
@Form.create()
class AddResource extends PureComponent {
handleSubmit = e => {
const { dispatch, form } = this.props;
e.preventDefault();
console.log(this.state.fileList);
form.validateFieldsAndScroll((err, values) => {
if (!err) {
if(values.facilities){
values.facilities = values.facilities.join(",");
}
if(values.floor_1 && values.floor_2){
values.floor = values.floor_1 + "/" + values.floor_2;
}
values.houseType = values.houseType_1 + "室" + values.houseType_2 + "厅"
+ values.houseType_3 + "卫" + values.houseType_4 + "厨"
+ values.houseType_2 + "阳台";
delete values.floor_1;
delete values.floor_2;
delete values.houseType_1;
delete values.houseType_2;
delete values.houseType_3;
delete values.houseType_4;
delete values.houseType_5;
// 楼盘id
values.estateId = this.state.estateId;
dispatch({
type: 'house/submitHouseForm',
payload: values,
});
}
});
};
handleSearch = (value)=>{
let arr = new Array();
if(value.length > 0 ){
estateMap.forEach((v, k) => {
if(k.startsWith(value)){
arr.push(k);
}
});
}
this.setState({
estateDataSource: arr
});
} ;
handleFileList = (obj)=>{
console.log(obj, "图片列表");
}
constructor(props){
super(props);
this.state = {
estateDataSource : [],
estateAddress : '',
estateId : ''
}
}
render() {
const { submitting } = this.props;
const {
form: { getFieldDecorator, getFieldValue },
} = this.props;
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 7 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 12 },
md: { span: 10 },
},
};
const submitFormLayout = {
wrapperCol: {
xs: { span: 24, offset: 0 },
sm: { span: 10, offset: 7 },
},
};
return (
);
}
}
export default AddResource;
config.js 跨域代理设置
// https://umijs.org/config/
import os from 'os';
import pageRoutes from './router.config';
import webpackPlugin from './plugin.config';
import defaultSettings from '../src/defaultSettings';
export default {
// add for transfer to umi
plugins: [
[
'umi-plugin-react',
{
antd: true,
dva: {
hmr: true,
},
targets: {
ie: 11,
},
locale: {
enable: true, // default false
default: 'zh-CN', // default zh-CN
baseNavigator: true, // default true, when it is true, will use `navigator.language` overwrite default
},
dynamicImport: {
loadingComponent: './components/PageLoading/index',
},
...(!process.env.TEST && os.platform() === 'darwin'
? {
dll: {
include: ['dva', 'dva/router', 'dva/saga', 'dva/fetch'],
exclude: ['@babel/runtime'],
},
hardSource: true,
}
: {}),
},
],
[
'umi-plugin-ga',
{
code: 'UA-72788897-6',
judge: () => process.env.APP_TYPE === 'site',
},
],
],
targets: {
ie: 11,
},
define: {
APP_TYPE: process.env.APP_TYPE || '',
},
// 路由配置
routes: pageRoutes,
// Theme for antd
// https://ant.design/docs/react/customize-theme-cn
theme: {
'primary-color': defaultSettings.primaryColor,
},
externals: {
'@antv/data-set': 'DataSet',
},
proxy: {
'/haoke/': {
target: 'http://127.0.0.1:18080',
changeOrigin: true,
pathRewrite: { '^/haoke/': '' }
}
},
ignoreMomentLocale: true,
lessLoaderOptions: {
javascriptEnabled: true,
},
disableRedirectHoist: true,
cssLoaderOptions: {
modules: true,
getLocalIdent: (context, localIdentName, localName) => {
if (
context.resourcePath.includes('node_modules') ||
context.resourcePath.includes('ant.design.pro.less') ||
context.resourcePath.includes('global.less')
) {
return localName;
}
const match = context.resourcePath.match(/src(.*)/);
if (match && match[1]) {
const antdProPath = match[1].replace('.less', '');
const arr = antdProPath
.split('/')
.map(a => a.replace(/([A-Z])/g, '-$1'))
.map(a => a.toLowerCase());
return `antd-pro${arr.join('-')}-${localName}`.replace(/--/g, '-');
}
return localName;
},
},
manifest: {
name: 'ant-design-pro',
background_color: '#FFF',
description: 'An out-of-box UI solution for enterprise applications as a React boilerplate.',
display: 'standalone',
start_url: '/index.html',
icons: [
{
src: '/favicon.png',
sizes: '48x48',
type: 'image/png',
},
],
},
chainWebpack: webpackPlugin,
cssnano: {
mergeRules: false,
},
};
(14.前后端整合开发之新增房源&)
14.前后端整合开发之新增房源&