Skip to content

zaxtseng/react-house-rent

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

React移动端项目-宜居租房

简介

本项目采用React脚手架create-react-app搭建,使用less预处理配合rem,使用redux保存调用相关数据. 后端mock数据采用node.js的express框架进行创建.前后端分离,跨域使用http-proxy-middleware中间件. 使用模块化的方式处理各个页面.公共组件抽离到统一目录下方便调用.

技术栈

React+React-Router(v4)+React-Redux+Less+Express+Fetch

项目启动

//安装依赖
npm install

再另开一个终端

node ./mock/index.js

回到第一个终端

npm start

环境配置

  1. less配置

通过npm install less less-loader -S安装less. npm eject加载webpack配置,打开config/webpack.config.js文件, 找到sass配置,复制一份.改为less配置.

const lessRegex = /\.less$/;
const lessModuleRegex = /\.module\.less$/;

搜索sassRegexsassModuleRegex将相应的文件配置复制一份进行修改.

{
    test: lessRegex,
    exclude: lessModuleRegex,
    use: getStyleLoaders(
    {
        importLoaders: 2,
        sourceMap: isEnvProduction && shouldUseSourceMap,
    },
    'less-loader'
    ),

    sideEffects: true,
},
{
    test: lessModuleRegex,
    use: getStyleLoaders(
    {
        importLoaders: 2,
        sourceMap: isEnvProduction && shouldUseSourceMap,
        modules: {
        getLocalIdent: getCSSModuleLocalIdent,
        },
    },
    'less-loader'
    ),
},

经验之谈

如果出现Cannot find module 'resolve'报错,说明依赖没有安装好,大部分是网络原因,运行npm install重新安装一次就好了.

  1. rem配置

在index.html中的<header>最后位置放入下列代码.

<script>
(function (doc, win) {
    var docEl = doc.documentElement,
            resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize',
            recalc = function () {
                var clientWidth = docEl.clientWidth;
                if (!clientWidth) return;
                if(clientWidth>=750){
                    docEl.style.fontSize = '100px';
                }else{
                    docEl.style.fontSize = 100 * (clientWidth / 750) + 'px';
                }
            };

    if (!doc.addEventListener) return;
    win.addEventListener(resizeEvt, recalc, false);
    doc.addEventListener('DOMContentLoaded', recalc, false);
})(document, window);
</script>
  1. 创建文件夹

src下创建以下文件夹.

  • 路由文件夹router
  • 公共组件文件夹components
  • 工具类文件夹utils
  • 页面文件夹pages
  • 静态文件夹static

pages下创建各个文件夹 Home, Shop, Life, Mine, NotFound下生成组件index.jsxstyle.less.

static文件夹下放入初始化资源 css,iconfont,images

  1. 路由支持

npm install react-router react-router-dom --save

router文件夹下创建AppRouter.jsx文件,将该文件导出挂载到app.js文件内作为组件. AppRouter文件引入{Route, HashRouter, Switch},pages下各个页面,将路径和component填入各个Route中.

  1. css文件初始化

static/common.less中将样式初始化,或者直接引入normalize.css.

  1. 底部导航

引入iconfont字体图标库.在index.js中也引入. 底部导航是公共组件,在components下创建FootNav文件夹. 引入router的NavLink,因为需要点击跳转.(注意首页的路径使用exact精确匹配) 因为每个页面都会用到底部导航,故在每个页面进行引入. 在各个底部菜单放入图标<i className="">引入各个图标.

  1. HomeHeader

创建Home/HomeHeader,按照划分分为3部分,home-header-left,right,middle. 其中home-header-middle部分包含一个search-container,之中包含一个iconfont和一个input.

  1. 轮播图

安装swiper

npm install --save react-swipeable-views

在公共组件components文件夹下创建轮播图文件夹Swiper. 引入

import SwipeableViews from 'react-swipeable-views'

将图片添加到staic/images文件夹下. 将引入写到home.jsx中,由home向swiper组件传递banner.

<Swiper banners={ [banners1, banners2, banners3] } />

Swiper中直接将得到的this.props.bannersmap方法遍历到SwipeableVIews中.

添加轮播图底部小圆点

在swiper文件夹下创建Pagination文件夹及相关文件. <Pagination /><SwipeableViews>是同级关系. 小点的数量由banners的长度决定,在<Pagination dots={banners.length} />中传递给子组件. 此时dots是数字,需要的是数组.使用new Array的方法将长度转化为数组

//转化为[1,1,1]
const dotsArr = new Array(dots).fill(1)

将数组map到<li></li>中.

将小点和轮播图绑定

<SwipeableViews>绑定事件,滑动到的当前的图的下标currentIndex,给赋值到index,传递给<Pagination />, 它得到currentIndex后拿到<li>中进行判断,

<li className={currentIndex === index ? "selected" : ""}>
  1. 搭建服务器

在项目根目录创建mock文件夹(或者server). 包含config,index,router文件和一个data文件夹. data文件夹下创建home文件夹,热销商品数据hotdata.js. 安装express,node index.js启动.

  1. 跨域问题

安装npm install http-proxy-middleware --save 创建src/setupProxy.js文件

const { createProxyMiddleware } = require("http-proxy-middleware");

module.exports = function(app) {
  app.use(createProxyMiddleware ("/api", { target: "http://localhost:5000/" }));
};
  1. Http处理网络请求

utils文件夹下创建http.js 主要由两个函数构成,getData, postData

import qs from 'querystring'

export function getDate(url){
    const result = fetch(url)
    return result
}

export ...
  1. 创建API

创建src/api/base.js和index.js,homehot.js.

//base.js
const base = {
    baseUrl: "http://localhost:5000",
    homehot1: "/api/homehot1",
    homehot2: "/api/homehot2" 
}
// 首页热门推荐
import base from "./base"
import {getData} from "../utils/http"

const homehot = {
    homehot1Data(){
        return getData(base.homehot1)
    },
    homehot2Data(){
        return getData(base.homehot2)
    }
}

export default homehot
//index.js
import homehot from './homehot'
//这里注意加括号
export default {homehot}
  1. 创建HomeHot文件夹

引入api.在componentDidMount中调用api请求数据.

componentDidMount(){
    api.homehot.homehot1Data()
        .then(res => res.json())
        .then(data =>{ console.log(data)})
}
  1. 分离HomeHot的UI

创建./HomeHot/HomeHotView. 将data传入HomeHotView,HomeHotView中map遍历数据data,放在li中. 创建style.less样式.

  1. 城市选项

创建src/pages/City.将相关引入放到router中. 给北京添加<Link>,可以点击跳转到城市页面.

  1. 城市选择页面

创建公共头部component/Header. 有一个返回箭头,一个title.其中title由使用页面传参 返回箭头绑定返回事件. window.history.back(-1) this.props.history.push("/home")

  1. 当前城市

创建./City/CurrentCity,city也是从父级传递过来.

  1. 热门城市

创建./City/HotCity. 点击传递参数时,建议使用bind传参,属于隐式传递.使用箭头函数属于显式传递. 此时事件定义时就不能用箭头函数了.

//定义事件
clickHandler(cityName){
    console.lig(cityName)
}
//...
//向事件函数传参
//bind隐式传递
<li onClick={this.clickHandler.bind(this,'北京')}>北京</li>
//箭头函数,显式传递
<li onClick={(e)=>this.clickHandler('北京',e)}>北京</li>
  1. 创建Redux

由于每个页面都需要获取城市信息,所以需要使用Redux存储.

安装redux

npm install --save redux react-redux

安装redux调试工具

npm install --save-dev redux-dectools 创建src/store/index.js,src/actions/city.js,src/actions/index.jssrc/reducer/index.js. 再创建constants文件用于定义基本字符串.

//src/constants/city.js
export const INIT_CITY = "INIT_CITY"
export const UPDATE_CITY = "UPDATE_CITY"

创建初始化城市initCity和更新城市updateCity的函数

//src/actions/city.js
import * as cityActions from "../constants/city";

export function initCity(data) {
  return {
    type: cityActions.INIT_CITY,
    data
  };
}

export function updateCity(data) {
  return {
    type: cityActions.UPDATE_CITY,
    data
  };
}

创建city的Reducer

//src/reducers/city.js
import * as cityActions from '../constants/city'

const initState = {}

export default function city(state = initState, action ){
    switch(action.type){
        case cityActions.INIT_CITY:
            return state = action.data;
        case cityActions.UPDATE_CITY:
            return state = action.data;
        default:
            return state;
    }
}   

合并Reducer

//src/reducers/index.js
import { combineReducers } from "redux"
import city from './city'
const rootReducers = combineReducers({
    city
})
export default rootReducers

将Reducer传入store中,创建好store.

//src/store/index.js
import { createStore } from 'redux'
import rootReducers from '../reducers'

export default function configureStore(){
    const store = createStore(rootReducers, 
        window.__REDUX_DEVTOOLS_EXTENSION__ ? window.__REDUX_DEVTOOLS_EXTENSION__() :undefined
        )
    return store
}
  1. 使用Redux

主入口文件index.js进行关联.

//index.js
import {Provider} from 'react-redux'
import configtureStore from './store'
const store = configtureStore();

ReactDOM.render(
  <Provider store={store}>
    <AppRouter />
  </Provider>,
  document.getElementById("root")
);

创建初始化页面src/pages/app.jsx

在index页面包裹所有的二级路由,并且路径指向首页

初始化项目需求 1.1 城市初始化

引入bindActionCreators,connect

//app.js
import { connect } from 'react-redux'
import * as cityActions from "../actions/city"
import { bindActionCreators } from "redux"

class App extends React.Component{
    //初始化城市数据
    componentDidMount(){
        this.props.cityActions.initCity({
            cityName: '北京
        })
    }
    render(){
        return(
            <div>{this.props.children}</div>
        )
    }
}
mapStateToProps = state => {
    return {
        city: state.city
    }
}
mapDispatchToProps = dispatch => {
    return {
        cityActions: bindActionCreators(cityAction, dispatch)
    }
}
export default connect(
    mapStateToProps,
    mapDispatchToProps
)(App)

1.2 在Home页面引入读取操作

import { connect } from 'react-redux'

const mapStateToProps = state => {
    return {
        city: state.city
    }
}

export default connect(
    mapStateToProps
)(Home)

1.3 在City页面也要引入

1.4 将HotCity选中的城市传递到主组件City

定义自定义事件进行传递

传递之后,引入mapDispatchToProps触发修改

改变之后,返回上一页面

1.5 传递城市信息

修改mock中参数

//mock/router.js
var url = require("url")
router.get(config.homehot1,function(req,res){
    //接收城市作为参数
    var cityName = url.parse(req.url,true).query.city
    console.log(cityName)
    res.send(homehot.hot1)
})

Home组件中得到city参数 HomeHot组件中接受到city参数 api中同样接受到参数,注意?符号.

const homehot = {
    homehot1Data(city){
        return getData(base.homehot1+"?city="+city)

city如果被选中后又刷新,需要保存之前的数据. 在City组件中使用localStorage.setItem("city",cityName) 之后再componentDidMount中请求city数据可以默认先查看本地是否有数据

const city = this.props.city || localStorage.getItem("city") ||  '北京'
  1. 搜索框

搜索框可复用,放在公共组件component中.创建SearchInput组件.绑定相应事件. 创建src/pages/Search页面.在路由中引入 搜索框组件中,绑定按下Enter开始搜索

//src/components/SearchInput
keyUpHandler = (event)=>{
    if(event.keyCode === 13){
        this.props.history.push('/search')
    }
}

...
<input onKeyUp={this.keyUpHandler}/>

由于history在组件SearchInput中并没有(只有直接被路由管理的才有),需要从父组件HomeHeader中获取,也没有,继续向上索取, 最后在Home组件中获取. 22. 搜索页面

搜索后跳转到搜索页面,创建搜索页面的顶部搜索框,依旧传入history. 搜索时所填入的数据需要带到搜索页面,用到路由传参. 在路由中

<Route path="/search/:content" component={Search}></Route>

数据在SearchInput中,将value附加到push的网址后面传递出去.

this.history.push("/search/"+this.state.value)

获取数据,

this.props.match.params.content
  1. 搜索服务器

创建mock/data/search/index.jsmock/router.js创建搜索接口

// 搜索接口
router.get("/search",function(req,res){
    // 接受参数:city,searchcontent
    var cityName = url.parse(req.url, true).query.city;
    var searchContent = url.parse(req.url, true).query.content;
    var page = url.parse(req.url, true).query.page;
    console.log("城市:"+cityName,"搜索内容:"+searchContent,"页码:"+page);
    res.send(searchData)
})
  1. 添加API

创建api/search

// 搜索接口
import base from "./base"
import {getData} from "../utils/http"

const search = {
    searchData(city,content){
        return getData(base.search+"?city="+city+"&content="+content)
    }
}
export default search

/api/index中引入再导出.

  1. Search页面

创建处理搜索结果的页面SearchList SearchList下创建SearchListView SearchListView下创建Item

//SearchList
componentDidMount(){
    //获取城市
    const city = this.props.city
    //获取搜索内容
    const content = this.props.content
    api.search.searchData(city,content)
        .then(res => res.json())
        .then(data => {
            this.setState({
                searchData: data
            })               
        })
}
    render(){
        return (
            <div>
                {
                 this.state.searchData.data ?
                 <SearchListView data={this.state.searchData.data}/> 
                 : <div>数据正在加载</div>
                }
           </div>
        )
    }

SearchListView中将得到的data使用map方法return出Item进行展示. Item将data传入组件内. 组件内将传入的data拆解,渲染.

//Item
const item = this.props.data
return (
    <div className="list-item">
        <img src={item.img} alt=''/>
        <div className="mask">
            <div className="left">
                <p>{item.title}</p>
                <p>{item.houseType}</p>
            </div>
            <div className="right">
                <div className="btn">
                    {item.rentType}
                </div>
                <p dangerouslySetInnerHTML={{__html: item.price+"/月"}}/>
            </div>
        </div>
    </div>

再次请求的刷新问题

在生命周期的componentDidUpdate中再次请求 这时组件渲染之后加载 将多次使用的api函数封装成一个函数

http(city,content){
        api.search.searchData(city,content)
        .then(res => res.json())
        .then(data => {
            this.setState({
                searchData: data
            })               
        })
     }

将上面的函数在componentDidMountcomponentDidUpdate中分别调用. 注意这里容易死循环,组件更新后data被setState重新赋值,然后更新,更新后得到新的参数,又继续更新.死循环.

解决办法: 加上判断,componentDidUpdate有两个参数,prevProps和prevState,表示上一次的属性和state. 比较本次搜索的content和上次是否相等,如果是,就return. 否则,发起网络请求.

componentDidUpdate(prevProps,prevState){
    const city = this.props.city
    const content = this.props.content
    if(prevProps.content === content){
        return
    }else{
        this.http(city, content)
    }
}
  1. 搜索详情页的瀑布流

封装上拉加载组件LoadMore 判断是否加载到LoadMore,监听滚动事件. 获取元素偏移量,创建Refs.

constructor(){
    super()
     this.load = React.crateRef()
}
componentDidMount(){
    window.onScroll = () => {
         console.log(this.load.current)
}
}

render(){
    return(
        <div ref={this.load}></div>
    )
}

滚动事件需要加上函数防抖,只要滚动就不加载触发函数,稍等100ms后加载一次.这就是函数防抖debounce.

//loadMore.js
componentDidMount(){
//事件防抖
let timer = null
const winHeight = document.documentElement.clientHeight
window.onscroll = ()=>{
    if(timer){
        clearTimeout(timer)
    }
    timer = setTimeout(()=>{
        if(this.load.current.getBoundingClientRect().top < winHeight){
            this.props.onLoadMore()
        }
    },100)
  }
}
componentWillUnmount(){
    // 取消滚动事件
    window.onscroll = null;
}

数据加载时是会更新数据,需要把新数据和旧数据拼接在一起.注意要设置初始化的state. 状态上再添加hasMore,判断是否还有数据. 将SearchData全部更改为[],获取时也就只要获取对象里的data.

在SearchList中增加生命周期componentWillUnmount,将请求取消.因为页面如果被点击而跳转到其他页面,请求是异步操作,有可能还没返回. 等回来了数据发现没有state接收请求了,会报错.需要在页面卸载时取消请求.

//searchList.js
http(city,content,page){
    api.search.searchData(city,content,page)
    .then(res => res.json())
    .then(data => {
        this.setState({
            searchData: this.state.searchData.concat(data.data),
            page: this.state.page + 1,
            hasMore: data.hasMore
        })               
    })
    }
//如果数据请求未完成就跳转到其他页面需要取消网络请求,否则报错
componentWillUnmount(){
    // 取消网络请求
    this.setState = (state,callback) => {
        return;
    }
}

增加页码,修改服务器. 注意修改api

//api/search.js
const search = {
    searchData(city,content,page){
        return getData(base.search+"?city="+city+"&content="+content+"&page="+page)
    }
}

每次做网络请求,page+1. 添加hasMore参数,判断是否还有数据.

  1. 详情页路由配置

创建pages/Details.路由中增加详情页. 在Item页面进行路由传参,item由Link包裹.<Link to={/detail/${item.id}}>

  1. 详情页服务端配置

创建mock/data/details/index.js. 在mock/router.js中增加详情接口.

//mock/router.js
//详情接口
router.get("/details", function(req,res){
  //接收商品id
  var id = url.parse(req.url, true).query.id
  console.log(id)
  res.send(detailData)
})

同时导入detailData数据.

  1. 详情页API部分
//api/details.js
import base from "./base"
import { getData } from "../utils/http"

const details = {
    detailsData(id){
        return getData(base.details+"?id="+id);
    }
}

export default details;

其他文件中增加detail的选项.

  1. detailData分离

创建page/Details/DetailsData.用来请求数据. 在detail中把id传递进去.

export default class index extends Component {
  constructor() {
    super();
    this.state = {
      datas: {}
    };
  }

  componentDidMount() {
    const id = this.props.id;
    api.detail
      .detailsData(id)
      .then(res => res.json())
      .then(data => {
        this.setState({
          datas: data
        });
      });
  }

  render() {
    return (
      <div>
        {this.state.datas.img ? (
          <DetailsDataView data={this.state.datas} />
        ) : (
          <div>数据请求中</div>
        )}
      </div>
    );
  }
}

创建page/Details/DetailsDataView.用来将请求数据展示出来. 再把请求到的datas传递给DetailsDataView. DetailsDataView需要多个公共组件,可以直接引入.

  1. Tab切换

创建公共组件Tabs. 使用React.children.map()去遍历子元素. 绑定onClick事件给点击的绑定高亮.将index赋值给currentIndex.

<div className="Tab_title_wrap">
{
    React.Children.map(this.props.children, (element,index)=>{
        return(
            <div className={this.check_title_index(index)} 
            onClick={this.tabHandler.bind(this,index)}>
                {element.props.tabname}
            </div>
        )
    })
}

使用函数赋值给className的显隐切换.

check_title_index = index=>{
    return index === this.state.currentIndex ? "Tab_title active" : "Tab_title"
}
check_item_index = index=>{
    return index === this.state.currentIndex ? "show" : "hide"
}
  1. 将信息放到Tabs内

  2. 增加评论服务端数据

创建mock/data/comment. 增加mock/router评论接口.

//评论接口
router.get("/comment", function(req,res){
  //获取商品ID
  var id = url.parse(req.url, true).query.id
  res.send(commentData)
})
  1. 增加api中comment

  2. 创建详情页DetailData的CommentView组件 将其中的Item拆分出来

  3. Star组件

创建公共组件Star. 使用数组判断星星是否高亮.

render(){
    if(star >= 5){
        star = 5
    }
    return(
        <div className="star-container">
            {[1,2,3,4,5].map((item,index)=>{
                let lightClass = star >= item ? ' light' : ''
                return(
                    <i key={index} className={'icon-star' + lightClass}></i>
                )
            })}
        </div>
    )
}
  1. 登录页面

创建pages/Login页面,储存到Redux. 引入到路由. 创建actions/userinfo.js 创建reducers/userinfo.js,将userinfo写到reducers/index.js中.

创建pages/Login/LoginView页面.展示登录界面. 绑定到Login页面,通过绑定事件loginHandler获取user

  1. 收藏功能

新增pages/Details/DetailsData/StoreBuy/StoreBuyView. 在DetailsDataView中引入. 收藏页面也增加Redux的连接.点击收藏存在redux中. 创建Redux一套流程. 注意此时actions中的收藏初始数据应为数组. state存储也是数组形式,

import * as collectActions from "../constants/collect"

const initState = [];
export default function collect(state = initState, action) {
    switch (action.type) {
        case collectActions.COLLECT:
            state.push(action.data)
            return state;
        case collectActions.UNCOLLECT:
            return state.filter((element) => {
                //filter作用把当前数组的element遍历,如果和当前所选不等,就是为true
                //就把该element添加到新数组里,否则,不添加,这样就排除了所选元素
                if (element.id !== action.data.id) {
                    return element;
                }
            })
        default:
            return state;
    }
}

判断是否收藏.

//StoreBuy
storeHandler = ()=>{
    const username = this.props.userinfo.name
    const goods_id = this.props.id
    if(username){
        //调用函数判断
        if(this.isStore()){
            //为true,说明有,取消收藏
            this.props.collectActions.cancelCollect({
                id: goods_id
            })
        }else{
            //收藏实现
            this.props.collectActions.setCollect({
                id: goods_id
            })
        }

    }else{
        //去登陆
        this.props.history.push("/login")
    }
}

//收藏判断
isStore = ()=>{
    const id = this.props.id
    const collects = this.props.collect
    //some判断集合中是否有该id,有返回true
    return collects.some((element)=>{
        return element.id === id 

    })
}
  1. 购物车功能

创建pages/ShopCart. 子组件在创建OrderViewUserViewHomeHeader中购物车图标增加Link标签跳转. 进入组件之前判断登录状态.关联redux.

//ShopCart
componentWillMount(){
const userinfo = this.props.userinfo.name
    if(userinfo){
        return
    }else{
        //重定向到登录
        this.props.history.push("/login")
    }
}
  1. 新增order的服务端数据

创建mock/data/order文件夹.

//order
module.exports = [
    {
        id: Math.random().toString().slice(2),
        title: "东城区 安外大街3号院",
        houseType: "1室1厅1卫 - 48m²",
        price: "4800",
        rentType: "整租",
        commentState: 0,
        img: "http://iwenwiki.com/api/livable/shop/z1.jpg"
    },
    {
        id: Math.random().toString().slice(2),
        title: "整租 · 义宾北区2居室-南北",
        houseType: "2室1厅1卫 - 78m²",
        price: "7200",
        rentType: "整租",
        commentState: 0,
        img: "http://iwenwiki.com/api/livable/shop/z5.jpg"
    },
    {
        id: Math.random().toString().slice(2),
        title: "整租 · 杨庄北区2居室-南北",
        houseType: "1室1厅1卫 - 48m²",
        price: "4300",
        rentType: "整租",
        commentState: 2,
        img: "http://iwenwiki.com/api/livable/shop/z6.jpg"
    }
]

mock/router.js中新增order.

// 购物车

router.get("/car",function(req,res){
  var user =  url.parse(req.url, true).query.user;
  console.log("用户:"+user);
  res.send(orderComment)
})

router.post("/ordercomment",function(req,res){
  var info = req.body.info;
  console.log("评论信息:"+info);
  res.send({
      msg:'评价成功'
  })
})
  1. 新增order的api

创建api/orderapi/ordercomment. 在base.js中新增order和ordercomment的路由. 在index.js中新增order和ordercomment的引入. 购物车评价接口需要写入数据,所以是postData.

//ordercomment.js
// 订单评价接口
import base from "./base"
import {postData} from "../utils/http"

const orderComment = {
    orderCommentData(info){
        return postData(base.ordercomment,info)
    }
}

export default orderComment
  1. 网络请求

引入api,在登录后发送网络请求

//shopcar
componentWillMount(){
    const userinfo = this.props.userinfo.name
    if(userinfo){
    //网络请求
    api.order.orderData(userinfo)
    .then(res=>res.json())
    .then(data=>{
        this.setState({
            order: data
        })       
    })

    }else{
        //重定向到登录
        this.props.history.push("/login")
    }

把数据order传给<OrderView />. 还是先判断数据是否到来

{
    this.state.order.length > 0 ?
    <OrderView data={this.state.order} />
    : <div>数据加载中</div>
}
  1. 数据接收渲染

OrderView接收上级传过来的数据,使用map渲染到页面.

render(){
    const data = this.props.order
    return(
        <div>
            {data.map(item, index)=>{
                return <Item key={index} data={item} />
            }}
        </div>
    )
} 
  1. Item渲染
render() {
    const data = this.props.data
    return (
        <div className="clear-fix order-item-container">
            <div className="order-item-img float-left">
                <img src={data.img} alt={data.title}/>
            </div>
            <div className="order-item-comment float-right">
                {
                    data.commentState === 0?
                    <button className="btn">评价</button>
                    : <button className="btn  unselected-btn">已评价</button>
                }
            </div>
            <div className="order-item-content">
                <span>商户:{data.title}</span>
                <span>类型:{data.houseType}</span>
                <span>价格:{data.price}</span>
            </div>
        </div>
    )
}
  1. 添加评价

给评价绑定事件. 评价状态commentState的值0,1,2分别代表未评价,评价中,已评价.

this.state.commentState === 0 ?
<button className="btn" onClick={this.commentHandler }>评价</button>
: this.state.commentState === 1 ?
<button className="btn">评价中</button>
: <button className="btn  unselected-btn">已评价</button>
  1. 添加评价框
{
this.state.commentState === 1 ?
<div className="comment-text-container">
<textarea style={{ width: '100%', height: '80px' }} className="comment-text" ref={ this.commentText }></textarea>
<button className="btn" onClick={this.submitCommentHandler}>提交</button>
&nbsp;
        <button className="btn unseleted-btn" onClick={this.hideComment}>取消</button>
</div>
:  ""
}
  1. 评价事件提交到后台

后台mock/router里创建评价接口.

//mock/router.js
router.post("/ordercomment",function(req,res){
  var info = req.body.info;
  console.log("评论信息:"+info);
  res.send({
      msg:'评价成功'
  })
})

mock/index.js中添加解析.

//mock/index.js
var bodyParser = require('body-parser')

app.use(bodyParser.urlencoded({
    extended: true
}))
  1. 增加相关API

创建api/ordercomment

// 订单评价接口
import base from "./base"
import {postData} from "../utils/http"

const orderComment = {
    orderCommentData(info){
        return postData(base.ordercomment,info)
    }
}

export default orderComment
  1. 创建非受控组件
this.commentText = React.createRef()

submitCommentHandler(){
    this.setState({
        commentState:2
    })
    // 发送网络请求
    api.orderComment.orderCommentData({
        info:this.commentText.current.value
    })
    .then(res => res.json())
    .then(data => {
        alert(data.msg);
    })
}

<textarea ref={this.commentText}></textarea>

页面暂时写这么多.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published