React前后端分离实战租房项目一

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 (
            <div>
                <ul>
                    {
                        this.props.dataList.map((value,index)=>{
                            return <li key={index}>{value}</li>
                        })
                    }
                </ul>
                <button onClick={() => {
                    this.props.add();
                }}>点我</button>
            </div>
        );
    }

}

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 (
            <Tabs defaultActiveKey="1" onChange={callback}>
                <TabPane tab="Tab 1" key="1">hello antd wo de 第一个 tabs</TabPane>
                <TabPane tab="Tab 2" key="2">Content of Tab Pane 2</TabPane>
                <TabPane tab="Tab 3" key="3">Content of Tab Pane 3</TabPane>
            </Tabs>
        )
    }

}

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 (
            <Layout>
                <Sider width={256} style={{minHeight: '100vh', color: 'white'}}>
                    <div style={{ height: '32px', background: 'rgba(255,255,255,.2)', margin: '16px'}}/>
                    <Menu
                        defaultSelectedKeys={['1']}
                        defaultOpenKeys={['sub1']}
                        mode="inline"
                        theme="dark"
                        inlineCollapsed={this.state.collapsed}
                    >
                        <SubMenu key="sub1" title={<span><Icon type="user"/><span>用户管理</span></span>}>
                            <Menu.Item key="1"><Link to="/user/UserAdd">新增用户</Link></Menu.Item>
                            <Menu.Item key="2"><Link to="/user/UserList">新增列表</Link></Menu.Item>
                        </SubMenu>
                    </Menu>
                </Sider>
                <Layout>
                    <Header style={{ background: '#fff', textAlign: 'center', padding: 0 }}>Header</Header>
                    <Content style={{ margin: '24px 16px 0' }}>
                        <div style={{ padding: 24, background: '#fff', minHeight: 360 }}>
                            {this.props.children}
                        </div>
                    </Content>
                    <Footer style={{ textAlign: 'center' }}>后台系统 ©2018 Created by 黑马程序员</Footer>
                </Layout>
            </Layout>
        )
    }

}

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 (
            <div>
                <Table dataSource={this.props.data} pagination={{position:"bottom",total:500,pageSize:10, defaultCurrent:3}}>
                    <Column
                        title="姓名"
                        dataIndex="name"
                        key="name"
                    />
                    <Column
                        title="年龄"
                        dataIndex="age"
                        key="age"
                    />
                    <Column
                        title="地址"
                        dataIndex="address"
                        key="address"
                    />
                    <Column
                        title="标签"
                        dataIndex="tags"
                        key="tags"
                        render={tags => (
                            <span>
                                {tags.map(tag => <Tag color="blue" key={tag}>{tag}</Tag>)}
                            </span>
                        )}
                    />
                    <Column
                        title="操作"
                        key="action"
                        render={(text, record) => (
                            <span>
                                <a href="javascript:;">编辑</a>
                                <Divider type="vertical"/>
                                <a href="javascript:;">删除</a>
                            </span>
                        )}
                    />
                </Table>
            </div>
        );
    }

}

export default UserList;

src\pages\user\UserAdd.js

import React from 'react'

class UserAdd extends React.Component{

    render(){
        return (
            <div>新增用户</div>
        );
    }

}

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 (
            <PageHeaderWrapper>
                <Form onSubmit={this.handleSubmit} hideRequiredMark style={{ marginTop: 8 }}>
                    <Card bordered={false} title="房源信息">
                        <FormItem {...formItemLayout} label="楼盘名称">
                            <AutoComplete
                              style={{ width: '100%' }}
                              dataSource={this.state.estateDataSource}
                              placeholder="搜索楼盘"
                              onSelect={(value, option)=>{
                                let v = estateMap.get(value);
                                this.setState({
                                  estateAddress: v.substring(v.indexOf('|')+1),
                                  estateId : v.substring(0,v.indexOf('|'))
                                });
                              }}
                              onSearch={this.handleSearch}
                              filterOption={(inputValue, option) => option.props.children.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1}
                            />
                        </FormItem>
                        <FormItem {...formItemLayout} label="楼盘地址">
                          <Input
                            prefix={<Icon type="environment" style={{ color: 'rgba(0,0,0,.25)' }} />}
                             value={this.state.estateAddress} defaultValue={this.state.estateAddress} readOnly/>
                        </FormItem>
                        <FormItem {...formItemLayout} label="楼栋">
                            <InputGroup compact>
                                {getFieldDecorator('buildingNum',{rules:[{ required: true, message:"此项为必填项" }]})(<Input style={{ width: '30%' }}  addonAfter="栋" />)}
                                {getFieldDecorator('buildingUnit',{rules:[{ required: true, message:"此项为必填项" }]})(<Input style={{ width: '30%' }}  addonAfter="单元" />)}
                                {getFieldDecorator('buildingFloorNum',{rules:[{ required: true, message:"此项为必填项" }]})(<Input style={{ width: '30%' }}  addonAfter="门牌号" />)}
                            </InputGroup>
                        </FormItem>
                        <FormItem {...formItemLayout} label="租金">
                            <InputGroup compact>
                              {getFieldDecorator('rent',{rules:[{ required: true, message:"此项为必填项" }]})(<Input style={{ width: '50%' }} addonAfter="元/月" />)}
                            </InputGroup>
                        </FormItem>
                        <FormItem {...formItemLayout} label="支付方式">
                          {getFieldDecorator('paymentMethod',{initialValue:'1',rules:[{ required: true, message:"此项为必填项" }]})
                          (
                            <Select style={{ width: '50%' }}>
                              <Option value="1">付一押一</Option>
                              <Option value="2">付三押一</Option>
                              <Option value="3">付六押一</Option>
                              <Option value="4">年付押一</Option>
                              <Option value="5">其它</Option>
                            </Select>
                          )}
                        </FormItem>
                        <FormItem {...formItemLayout} label="租赁方式">
                          {getFieldDecorator('rentMethod',{initialValue:'1',rules:[{ required: true, message:"此项为必填项" }]})
                          (
                            <Select  style={{ width: '50%' }}>
                              <Option value="1">整租</Option>
                              <Option value="2">合租</Option>
                            </Select>
                          )}
                        </FormItem>
                        <FormItem {...formItemLayout} label="户型">
                            <InputGroup compact>
                              {getFieldDecorator('houseType_1',{rules:[{ required: true, message:"此项为必填项" }]})(<Input style={{ width: '18%' }} addonAfter="室" />)}
                              {getFieldDecorator('houseType_2',{rules:[{ required: true, message:"此项为必填项" }]})(<Input style={{ width: '18%', marginLeft: '5px' }} addonAfter="厅" />)}
                              {getFieldDecorator('houseType_3',{rules:[{ required: true, message:"此项为必填项" }]})(<Input style={{ width: '18%', marginLeft: '5px' }} addonAfter="卫" />)}
                              {getFieldDecorator('houseType_4',{rules:[{ required: true, message:"此项为必填项" }]})(<Input style={{ width: '18%', marginLeft: '5px' }} addonAfter="厨" />)}
                              {getFieldDecorator('houseType_5',{rules:[{ required: true, message:"此项为必填项" }]})(<Input style={{ width: '23%', marginLeft: '5px' }} addonAfter="阳台" />)}
                            </InputGroup>

                        </FormItem>
                        <FormItem {...formItemLayout} label="建筑面积">
                            <InputGroup compact>
                              {getFieldDecorator('coveredArea',{rules:[{ required: true, message:"此项为必填项" }]})(<Input style={{ width: '40%' }} addonAfter="平米" />)}
                            </InputGroup>
                        </FormItem>
                        <FormItem {...formItemLayout} label="使用面积">
                            <InputGroup compact>
                              {getFieldDecorator('useArea',{rules:[{ required: true, message:"此项为必填项" }]})(<Input style={{ width: '40%' }} addonAfter="平米" />)}
                            </InputGroup>
                        </FormItem>
                        <FormItem {...formItemLayout} label="楼层">
                            <InputGroup compact>
                              {getFieldDecorator('floor_1',{rules:[{ required: true, message:"此项为必填项" }]})(<Input style={{ width: '30%' }} addonBefore="第" addonAfter="层" />)}
                              {getFieldDecorator('floor_2',{rules:[{ required: true, message:"此项为必填项" }]})(<Input style={{ width: '30%', marginLeft: '10px' }} addonBefore="总" addonAfter="层" />)}
                            </InputGroup>
                        </FormItem>
                        <FormItem {...formItemLayout} label="朝向">
                          {getFieldDecorator('orientation',{initialValue:'南',rules:[{ required: true, message:"此项为必填项" }]})
                          (
                            <Select style={{ width: '20%' }}>
                              <Option value="南">南</Option>
                              <Option value="北">北</Option>
                              <Option value="东">东</Option>
                              <Option value="西">西</Option>
                            </Select>
                          )}
                        </FormItem>
                        <FormItem {...formItemLayout} label="装修">
                          {getFieldDecorator('decoration',{initialValue:'1',rules:[{ required: true, message:"此项为必填项" }]})
                          (
                            <Select style={{ width: '20%' }}>
                              <Option value="1">精装</Option>
                              <Option value="2">简装</Option>
                              <Option value="3">毛坯</Option>
                            </Select>
                          )}
                        </FormItem>
                        <FormItem {...formItemLayout} label="配套设施">
                          {getFieldDecorator('facilities',{initialValue:['1','2','3'],rules:[{ required: true, message:"此项为必填项" }]})
                          (
                            <CheckboxGroup options={[
                              { label: '水', value: '1' },
                              { label: '电', value: '2' },
                              { label: '煤气/天然气', value: '3' },
                              { label: '暖气', value: '4' },
                              { label: '有线电视', value: '5' },
                              { label: '宽带', value: '6' },
                              { label: '电梯', value: '7' },
                              { label: '车位/车库', value: '8' },
                              { label: '地下室/储藏室', value: '9' }
                            ]}/>
                          )}
                        </FormItem>
                    </Card>
                    <Card bordered={false} title="图片信息">
                        <FormItem {...formItemLayout} label="房源描述">
                          {getFieldDecorator('desc')
                          (
                            <TextArea placeholder="请输入备注信息" autosize={{ minRows: 4, maxRows: 10 }} />
                          )}
                            <span>请勿填写联系方式或与房源无关信息以及图片、链接或名牌、优秀、顶级、全网首发、零距离、回报率等词汇。</span>
                        </FormItem>

                        <FormItem {...formItemLayout} label="上传室内图">
                            <PicturesWall handleFileList={this.handleFileList.bind(this)}/>
                        </FormItem>
                    </Card>
                    <Card bordered={false} title="出租信息">
                        <FormItem {...formItemLayout} label="联系人">
                            <InputGroup compact>
                              {getFieldDecorator('contact',{rules:[{ required: true, message:"此项为必填项" }]})
                              (
                                <Input placeholder="请输入" />
                              )}
                            </InputGroup>
                        </FormItem>
                        <FormItem {...formItemLayout} label="手机号">
                            <InputGroup compact>
                              {getFieldDecorator('mobile',{rules:[{ required: true,max:11, message:"此项为必填项" }]})
                              (
                                <Input placeholder="请输入" maxLength="11" />
                              )}
                            </InputGroup>
                        </FormItem>
                        <FormItem {...formItemLayout} label="看房时间">
                          {getFieldDecorator('time',{initialValue:'1',rules:[{ required: true, message:"此项为必填项" }]})
                          (
                            <Select style={{ width: '20%' }}>
                              <Option value="1">上午</Option>
                              <Option value="2">中午</Option>
                              <Option value="3">下午</Option>
                              <Option value="4">晚上</Option>
                              <Option value="5">全天</Option>
                            </Select>
                          )}

                        </FormItem>
                        <FormItem {...formItemLayout} label="物业费">
                          <InputGroup compact>
                          {getFieldDecorator('propertyCost',{rules:[{ required: true,max:11, message:"此项为必填项" }]})
                          (
                              <Input style={{ width: '30%' }} addonAfter="元/平" />
                          )}
                          </InputGroup>

                        </FormItem>

                        <FormItem {...submitFormLayout} style={{ marginTop: 32 }}>
                            <Button type="primary" htmlType="submit" loading={submitting}>
                                <FormattedMessage id="form.submit" />
                            </Button>
                            <Button style={{ marginLeft: 8 }}>
                                <FormattedMessage id="form.save" />
                            </Button>
                        </FormItem>
                    </Card>
                </Form>
            </PageHeaderWrapper >
        );
    }
}

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;

/**
 * <p>
 * 房源表
 * </p>
 *
 * @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<Void> 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<String> 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 (
            <PageHeaderWrapper>
                <Form onSubmit={this.handleSubmit} hideRequiredMark style={{ marginTop: 8 }}>
                    <Card bordered={false} title="房源信息">
                        <FormItem {...formItemLayout} label="楼盘名称">
                            <AutoComplete
                              style={{ width: '100%' }}
                              dataSource={this.state.estateDataSource}
                              placeholder="搜索楼盘"
                              onSelect={(value, option)=>{
                                let v = estateMap.get(value);
                                this.setState({
                                  estateAddress: v.substring(v.indexOf('|')+1),
                                  estateId : v.substring(0,v.indexOf('|'))
                                });
                              }}
                              onSearch={this.handleSearch}
                              filterOption={(inputValue, option) => option.props.children.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1}
                            />
                        </FormItem>
                        <FormItem {...formItemLayout} label="楼盘地址">
                          <Input
                            prefix={<Icon type="environment" style={{ color: 'rgba(0,0,0,.25)' }} />}
                             value={this.state.estateAddress} defaultValue={this.state.estateAddress} readOnly/>
                        </FormItem>
                        <FormItem {...formItemLayout} label="房源标题">
                          {getFieldDecorator('title',{rules:[{ required: true, message:"此项为必填项" }]})(<Input style={{ width: '100%' }}  />)}
                        </FormItem>
                        <FormItem {...formItemLayout} label="楼栋">
                            <InputGroup compact>
                                {getFieldDecorator('buildingNum',{rules:[{ required: true, message:"此项为必填项" }]})(<Input style={{ width: '30%' }}  addonAfter="栋" />)}
                                {getFieldDecorator('buildingUnit',{rules:[{ required: true, message:"此项为必填项" }]})(<Input style={{ width: '30%' }}  addonAfter="单元" />)}
                                {getFieldDecorator('buildingFloorNum',{rules:[{ required: true, message:"此项为必填项" }]})(<Input style={{ width: '30%' }}  addonAfter="门牌号" />)}
                            </InputGroup>
                        </FormItem>
                        <FormItem {...formItemLayout} label="租金">
                            <InputGroup compact>
                              {getFieldDecorator('rent',{rules:[{ required: true, message:"此项为必填项" }]})(<Input style={{ width: '50%' }} addonAfter="元/月" />)}
                            </InputGroup>
                        </FormItem>
                        <FormItem {...formItemLayout} label="支付方式">
                          {getFieldDecorator('paymentMethod',{initialValue:'1',rules:[{ required: true, message:"此项为必填项" }]})
                          (
                            <Select style={{ width: '50%' }}>
                              <Option value="1">付一押一</Option>
                              <Option value="2">付三押一</Option>
                              <Option value="3">付六押一</Option>
                              <Option value="4">年付押一</Option>
                              <Option value="5">其它</Option>
                            </Select>
                          )}
                        </FormItem>
                        <FormItem {...formItemLayout} label="租赁方式">
                          {getFieldDecorator('rentMethod',{initialValue:'1',rules:[{ required: true, message:"此项为必填项" }]})
                          (
                            <Select  style={{ width: '50%' }}>
                              <Option value="1">整租</Option>
                              <Option value="2">合租</Option>
                            </Select>
                          )}
                        </FormItem>
                        <FormItem {...formItemLayout} label="户型">
                            <InputGroup compact>
                              {getFieldDecorator('houseType_1',{rules:[{ required: true, message:"此项为必填项" }]})(<Input style={{ width: '18%' }} addonAfter="室" />)}
                              {getFieldDecorator('houseType_2',{rules:[{ required: true, message:"此项为必填项" }]})(<Input style={{ width: '18%', marginLeft: '5px' }} addonAfter="厅" />)}
                              {getFieldDecorator('houseType_3',{rules:[{ required: true, message:"此项为必填项" }]})(<Input style={{ width: '18%', marginLeft: '5px' }} addonAfter="卫" />)}
                              {getFieldDecorator('houseType_4',{rules:[{ required: true, message:"此项为必填项" }]})(<Input style={{ width: '18%', marginLeft: '5px' }} addonAfter="厨" />)}
                              {getFieldDecorator('houseType_5',{rules:[{ required: true, message:"此项为必填项" }]})(<Input style={{ width: '23%', marginLeft: '5px' }} addonAfter="阳台" />)}
                            </InputGroup>

                        </FormItem>
                        <FormItem {...formItemLayout} label="建筑面积">
                            <InputGroup compact>
                              {getFieldDecorator('coveredArea',{rules:[{ required: true, message:"此项为必填项" }]})(<Input style={{ width: '40%' }} addonAfter="平米" />)}
                            </InputGroup>
                        </FormItem>
                        <FormItem {...formItemLayout} label="使用面积">
                            <InputGroup compact>
                              {getFieldDecorator('useArea',{rules:[{ required: true, message:"此项为必填项" }]})(<Input style={{ width: '40%' }} addonAfter="平米" />)}
                            </InputGroup>
                        </FormItem>
                        <FormItem {...formItemLayout} label="楼层">
                            <InputGroup compact>
                              {getFieldDecorator('floor_1',{rules:[{ required: true, message:"此项为必填项" }]})(<Input style={{ width: '30%' }} addonBefore="第" addonAfter="层" />)}
                              {getFieldDecorator('floor_2',{rules:[{ required: true, message:"此项为必填项" }]})(<Input style={{ width: '30%', marginLeft: '10px' }} addonBefore="总" addonAfter="层" />)}
                            </InputGroup>
                        </FormItem>
                        <FormItem {...formItemLayout} label="朝向">
                          {getFieldDecorator('orientation',{initialValue:'南',rules:[{ required: true, message:"此项为必填项" }]})
                          (
                            <Select style={{ width: '20%' }}>
                              <Option value="南">南</Option>
                              <Option value="北">北</Option>
                              <Option value="东">东</Option>
                              <Option value="西">西</Option>
                            </Select>
                          )}
                        </FormItem>
                        <FormItem {...formItemLayout} label="装修">
                          {getFieldDecorator('decoration',{initialValue:'1',rules:[{ required: true, message:"此项为必填项" }]})
                          (
                            <Select style={{ width: '20%' }}>
                              <Option value="1">精装</Option>
                              <Option value="2">简装</Option>
                              <Option value="3">毛坯</Option>
                            </Select>
                          )}
                        </FormItem>
                        <FormItem {...formItemLayout} label="配套设施">
                          {getFieldDecorator('facilities',{initialValue:['1','2','3'],rules:[{ required: true, message:"此项为必填项" }]})
                          (
                            <CheckboxGroup options={[
                              { label: '水', value: '1' },
                              { label: '电', value: '2' },
                              { label: '煤气/天然气', value: '3' },
                              { label: '暖气', value: '4' },
                              { label: '有线电视', value: '5' },
                              { label: '宽带', value: '6' },
                              { label: '电梯', value: '7' },
                              { label: '车位/车库', value: '8' },
                              { label: '地下室/储藏室', value: '9' }
                            ]}/>
                          )}
                        </FormItem>
                    </Card>
                    <Card bordered={false} title="图片信息">
                        <FormItem {...formItemLayout} label="房源描述">
                          {getFieldDecorator('houseDesc')
                          (
                            <TextArea placeholder="请输入备注信息" autosize={{ minRows: 4, maxRows: 10 }} />
                          )}
                            <span>请勿填写联系方式或与房源无关信息以及图片、链接或名牌、优秀、顶级、全网首发、零距离、回报率等词汇。</span>
                        </FormItem>

                        <FormItem {...formItemLayout} label="上传室内图">
                            <PicturesWall handleFileList={this.handleFileList.bind(this)}/>
                        </FormItem>
                    </Card>
                    <Card bordered={false} title="出租信息">
                        <FormItem {...formItemLayout} label="联系人">
                            <InputGroup compact>
                              {getFieldDecorator('contact',{rules:[{ required: true, message:"此项为必填项" }]})
                              (
                                <Input placeholder="请输入" />
                              )}
                            </InputGroup>
                        </FormItem>
                        <FormItem {...formItemLayout} label="手机号">
                            <InputGroup compact>
                              {getFieldDecorator('mobile',{rules:[{ required: true,max:11, message:"此项为必填项" }]})
                              (
                                <Input placeholder="请输入" maxLength="11" />
                              )}
                            </InputGroup>
                        </FormItem>
                        <FormItem {...formItemLayout} label="看房时间">
                          {getFieldDecorator('time',{initialValue:'1',rules:[{ required: true, message:"此项为必填项" }]})
                          (
                            <Select style={{ width: '20%' }}>
                              <Option value="1">上午</Option>
                              <Option value="2">中午</Option>
                              <Option value="3">下午</Option>
                              <Option value="4">晚上</Option>
                              <Option value="5">全天</Option>
                            </Select>
                          )}

                        </FormItem>
                        <FormItem {...formItemLayout} label="物业费">
                          <InputGroup compact>
                          {getFieldDecorator('propertyCost',{rules:[{ required: true,max:11, message:"此项为必填项" }]})
                          (
                              <Input style={{ width: '30%' }} addonAfter="元/平" />
                          )}
                          </InputGroup>

                        </FormItem>

                        <FormItem {...submitFormLayout} style={{ marginTop: 32 }}>
                            <Button type="primary" htmlType="submit" loading={submitting}>
                                <FormattedMessage id="form.submit" />
                            </Button>
                            <Button style={{ marginLeft: 8 }}>
                                <FormattedMessage id="form.save" />
                            </Button>
                        </FormItem>
                    </Card>
                </Form>
            </PageHeaderWrapper >
        );
    }
}

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.前后端整合开发之新增房源&

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wespten

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值