Commit a9770b3a by CreateSun

待见前端框架

parent 8160e604
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/.idea
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# 待见Web端
## 项目简介
"待见",是太原理工大学-云顶书院旗下的新型社交电商平台项目,目前项目正处于孵化初期。平台精选原生、优质、特色的农畜产品供您选择,并且您可以通过庄园、手记等站内阅读、社交渠道,学习、了解他人的饮食攻略和生活指南。为了响应国家精准扶贫方针,项目依托“第一书记”政策,利用互联网电商平台,形成农户—消费者点对点直销渠道,降低中间成本,使农户获得更高的收益,消费者得到更实惠的价格,改善中国农产品销售链现状。同时,项目组也会孵化出一批优秀的大学生创新、创业人才,为山西互联网行业的发展积蓄力量。
## 项目发展历程
+ 2019年5月,待见项目组成立,新成员进入学习阶段。
+ 2019年7月,待见Web端正式启动,我们陆续复习React基础知识、学习父子组件传值以及常用第三方库React-Navigation、状态管理器Redux等。
+ 2019年8月3日,项目正式进入开发阶段。
+ 2019年9月3日,第一次master提交,待见Web端体验版发布。
+ 2019年2月,待见Web端正式进入优化重构阶段。
## 项目技术框架
+ 开发框架:React
+ 第三方UI库:Ant Design
+ 集成开发环境:WebStorm
+ 包管理工具:npm + yarn
+ 页面数据存储:LocalStorage + SessionStorage
+ 路由管理:react-router + react-router-dom
+ css预处理语言:less
+ 全局状态管理工具:redux + mobx
+ 富文本编辑器:wangeditor
+ 数据传输框架:axios
+ 编程语言:ES5 + ES6 + ES7
/**
* @author xue chen
* @since 2019/8/3
*/
const {override, fixBabelImports, addLessLoader, addDecoratorsLegacy} = require('customize-cra');
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: true
}),
addLessLoader({
javascriptEnabled: true,
modifyVars: {
'@primary-color': '#7EC18D',
'@success-color': '#7EC18D',
}
}),
addDecoratorsLegacy()
);
\ No newline at end of file
{
"name": "daijian-web-user",
"version": "0.1.0",
"private": true,
"dependencies": {
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-scripts": "3.0.1"
},
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@babel/plugin-proposal-decorators": "^7.4.4",
"antd": "^3.20.7",
"axios": "^0.19.0",
"babel-plugin-import": "^1.12.0",
"css-animation": "1.5.0",
"customize-cra": "^0.4.1",
"immutable": "^4.0.0-rc.12",
"less": "^3.9.0",
"less-loader": "^5.0.0",
"mobx": "^5.13.0",
"mobx-react": "^6.1.3",
"moment": "^2.24.0",
"react-app-rewired": "^2.1.3",
"react-redux": "^7.1.0",
"react-router": "^5.0.1",
"react-router-dom": "^5.0.1",
"redux": "^4.0.4",
"redux-thunk": "^2.3.0",
"wangeditor": "^3.1.1"
},
"homepage": "."
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>待见</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
/**
* @author xue chen
* @since 2019/8/3
*/
import React from 'react';
import AppRouter from "./router";
import {Provider} from 'mobx-react';
import {cartStore} from "./store";
import { LocaleProvider } from 'antd';
import zh_CN from 'antd/lib/locale-provider/zh_CN';
import moment from 'moment';
import 'moment/locale/zh-cn';
moment.locale('zh-cn');
class App extends React.Component {
render() {
return (
<Provider cartStore = {cartStore} >
<LocaleProvider locale={zh_CN}>
<AppRouter />
</LocaleProvider>
</Provider>
)
}
}
export default App;
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="13" viewBox="0 0 14 13">
<path id="路径_5347" data-name="路径 5347" d="M74.311,103.58a.9.9,0,1,0,.875.9.888.888,0,0,0-.875-.9Zm3.5,0a.9.9,0,1,0,.875.9.888.888,0,0,0-.875-.9Zm3.5,0a.9.9,0,1,0,.875.9.888.888,0,0,0-.875-.9Zm1.75-4.5h-10.5a1.776,1.776,0,0,0-1.75,1.8v6.754a1.893,1.893,0,0,0,1.79,1.912h2.667c.465.491,2.389,2.467,2.389,2.467a.214.214,0,0,0,.309,0s1.409-1.585,2.326-2.467h2.729a1.893,1.893,0,0,0,1.79-1.912v-6.754A1.776,1.776,0,0,0,83.061,99.077Zm.884,8.558a1.025,1.025,0,0,1-.916,1.021H80.341a1,1,0,0,0-.618.332l-1.887,1.942-1.887-1.942a1.276,1.276,0,0,0-.667-.332H72.593a1.025,1.025,0,0,1-.916-1.021v-6.763a.889.889,0,0,1,.876-.9H83.068a.889.889,0,0,1,.876.9Zm-.009,0" transform="translate(-70.811 -99.077)" fill="#999"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 13 13">
<path id="路径_5348" data-name="路径 5348" d="M23.556.861a.823.823,0,0,1,.269.284c.243.4.562,1.422-.117,3.851l-.269,1.024h4.733a26.244,26.244,0,0,1-1.259,5.452,3.351,3.351,0,0,1-.327.715H21.391a1.09,1.09,0,0,1-.487-.154V5.59c.781-.65,2.6-2.389,2.66-4.6V.853M23.556,0c-.965,0-.764.756-.839.886,0,2.437-2.66,4.322-2.66,4.322v6.874c0,.674.982.918,1.334.918h5.371c.5,0,.915-1.284.915-1.284A25.228,25.228,0,0,0,29,6.029a.826.826,0,0,0-.839-.812H24.538C25.923.252,23.556,0,23.556,0ZM18.294,6.021v6.167H17.262l-.42-6.167h1.427m.294-.812H16.448a.444.444,0,0,0-.314.121.416.416,0,0,0-.131.3l.445,6.931a.424.424,0,0,0,.13.313A.453.453,0,0,0,16.9,13h1.855c.386,0,.386-.292.386-.292V5.736a.5.5,0,0,0-.155-.378.538.538,0,0,0-.391-.15Z" transform="translate(-16.003)" fill="#999"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="13" viewBox="0 0 20 13">
<g id="组_4762" data-name="组 4762" transform="translate(-106.72 -264.747)">
<path id="路径_5345" data-name="路径 5345" d="M381.124,384.869a3.535,3.535,0,1,1,3.311-3.528A3.427,3.427,0,0,1,381.124,384.869Zm0-5.934a2.411,2.411,0,1,0,2.258,2.406A2.339,2.339,0,0,0,381.124,378.935Z" transform="translate(-264.404 -110.094)" fill="#999"/>
<path id="路径_5346" data-name="路径 5346" d="M116.72,277.747a10.65,10.65,0,0,1-7.817-3.124,10.1,10.1,0,0,1-2.148-3.169.624.624,0,0,1,0-.415,11.155,11.155,0,0,1,17.781-3.169,10.1,10.1,0,0,1,2.148,3.169.624.624,0,0,1,0,.415A10.615,10.615,0,0,1,116.72,277.747Zm-8.9-6.5a9.42,9.42,0,0,0,8.9,5.373,9.53,9.53,0,0,0,8.9-5.378,9.427,9.427,0,0,0-8.9-5.373A9.539,9.539,0,0,0,107.824,271.247Z" fill="#999"/>
</g>
</svg>
/**
* Wrapper, Container
*
* @author xue chen
* @since 2019/8/4
*/
import React from "react";
function Wrapper({style, className, children}) {
return (
<div
style={{
backgroundColor: '#F8F8FA',
...style
}}
className={className}
>
{children}
</div>
)
}
function Container({style, className, children}) {
return (
<div
style={{
width: '1200px',
margin: '0 auto',
...style,
}}
className={className}
>
{children}
</div>
)
}
export {Wrapper, Container}
\ No newline at end of file
/**
* Row, Column
*
* @author xue chen
* @since 2019/8/4
*/
import React from "react";
function Row({flex, className, style, align = 'center', justify = 'center', children, onMouseLeave, onMouseEnter, onClick}) {
return (
<div
style={{
display: 'flex',
flex,
flexDirection: 'row',
alignItems: align,
justifyContent: justify,
...style
}}
className = {className}
onMouseEnter = {onMouseEnter}
onMouseLeave = {onMouseLeave}
onClick={onClick}
>
{children}
</div>
)
}
function Column({flex, className, style, align = 'center', justify = 'center', children, onMouseLeave, onMouseEnter, onClick}) {
return (
<div
style={{
display: 'flex',
flex,
flexDirection: 'Column',
alignItems: align,
justifyContent: justify,
...style
}}
className = {className}
onMouseEnter = {onMouseEnter}
onMouseLeave = {onMouseLeave}
onClick={onClick}
>
{children}
</div>
)
}
export {Row, Column}
\ No newline at end of file
/**
* @author xue chen
* @since 2019/8/5
*/
import React from "react";
function FlexGrow({grow = 1}) {
return <div style = {{flexGrow: grow}}/>
}
export default FlexGrow;
\ No newline at end of file
import React from "react";
import {FlexGrow, Row} from "../index";
import './style.less';
import {post} from "../../util/requests";
import {api} from "../../config/url";
import {message} from "antd";
import {withRouter} from "react-router-dom";
@withRouter
class GoodsItem extends React.PureComponent {
constructor(props) {
super(props);
this.addCart = this.addCart.bind(this);
}
/**
* 加入购物车
* @param goodsId
*/
addCart(goodsId) {
post(api.cart.add, {
goodsId,
num: 1
})
.then(res => {
if (res.code === 200) {
message.success('加入购物车')
}
}
)
}
render() {
const {data} = this.props;
const {specification, shopName, name, avatarUrl, unitPrice, monthlySales} = data;
return (
// todo hashRouter 的原因 所以 跳转链接需要一个 #
<div
className="good"
>
<a
href={`/#/goodsDetail/${data.goodsId}`}
target={'_blank'}
>
<img className="picture" src={avatarUrl} alt=""/>
<p className="name" style={{color: '#333'}}>{name}</p>
<Row className="num" justify={'start'}>
<p>{specification}/份</p>
<p style={{marginLeft: '22px'}}>销量: {monthlySales}</p>
<FlexGrow/>
<p style={{
width: '90px',
textAlign: 'right',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}}>{shopName}</p>
</Row>
</a>
<Row className="price" justify={'start'}>
<a
href={`/goodsDetail/${data.goodsId}`}
target={'_blank'}
>
<Row>
<p className="aftPrice">{unitPrice}</p>
{
this.props.data.originPrice !== undefined &&
<p className="prePrice">{this.props.data.originPrice}</p>
}
</Row>
</a>
<FlexGrow/>
<img onClick={(event) => {
this.addCart(data.goodsId);
event.stopPropagation();
}}
src={require('../../pages/platform/HomePage/asset/icon/add.svg')} alt=""
/>
</Row>
</div>
)
}
}
export default GoodsItem;
/**
* @author xue chen
* @since 2019/8/13
*/
import React from "react";
import comment from '../assets/comment.svg';
import up from '../assets/up.svg';
import view from '../assets/view.svg';
import './style.less'
import {Column, Row} from "./Flex";
import {Link} from "react-router-dom";
export const NoteItem = (props) => {
const {
data,
toUserDetail,
toNoteDetail,
userHeadIcon
} = props;
return (
<div className="item">
<Link to={`/manor/notesDetail/${props.data.notesId}`} target={'_blank'}>
<div style={{minHeight: "136px",backgroundColor: "#7EC18D", borderRadius: "10px"}}>
<img src={data.coverImageUrl} className="noteImg" onClick={toNoteDetail} alt={'封面图片'}/>
</div>
</Link>
<Column
style = {
{
height: "128px",
paddingLeft: "10px",
paddingBottom: "4px"
}
}
align = "flex-start"
justify = "space-around"
>
<p className="noteTitle" onClick={toNoteDetail}>{data.title}</p>
<Row className={"icon"} justify={"flex-start"}>
<img style={{margin: 0}} src={view} alt=""/>
<p>{data.viewNum}</p>
<img src={comment} alt=""/>
<p>{data.commentNum}</p>
<img src={up} alt=""/>
<p>{data.likeNum}</p>
</Row>
<Link to={`/manor/detail/${data.userId}`} target={"_blank"}>
<Row className={"user"} onClick={toUserDetail}>
<img src={data.manorAvatarUrl} alt=""/>
<p>{data.manorName}</p>
</Row>
</Link>
</Column>
</div>
)
};
/**
* @author han wen yao
* @since 2019/8/30
*/
import React from 'react'
import {Redirect,withRouter} from "react-router-dom";
import {connect} from "react-redux";
const mapState = (state) =>(
{
token:state.login.token
}
);
@withRouter
@connect(mapState)
class RedirectLogin extends React.Component{
render() {
const {token,children} = this.props;
return (
<>
{
token ?
<Redirect
to={{pathname:"/"}}
/>
:
<>
{children}
</>
}
</>
);
}
}
export default RedirectLogin;
\ No newline at end of file
/**
* @author han wen yao
* @since 2019/8/24
*/
import React from "react";
import {connect} from "react-redux";
import {Redirect, withRouter} from "react-router";
const mapState = (state) => (
{
token:state.login.token
}
);
@withRouter
@connect(mapState)
class RedirectPage extends React.Component{
render(){
const {token, children} = this.props;
return(
<>
{
token
?
<>
{children}
</>
:
<Redirect
to={{pathname:"/auth/login"}}
/>
}
</>
)
}
}
export default RedirectPage;
\ No newline at end of file
/**
* @author han wen yao
* @since 2019/9/4
*/
import React from "react";
import {Row, Column, FlexGrow} from "../index";
import headIcon from '../../components/common/Header/asset/icon/headIcon.svg'
import './style.less';
class ShopItem extends React.PureComponent {
render() {
const {data} = this.props;
return (
<Column className="shop">
<Row className="farmer" justify={'start'}>
<a
href={`/#/shop/${data.shopId}`}
target={'_blank'}
>
<img className='shop_avatar' src={data.avatarUrl || headIcon} alt="商户头像"/>
</a>
<a
href={`/#/shop/${data.shopId}`}
target={'_blank'}
>
<Column align='start' style={{marginLeft: '14px'}}>
<p style={{fontSize: '16px', marginBottom: '12px', color: '#000', width: '150px'}}
className="omission">{data.name}</p>
<p style={{fontSize: '12px', color: '#666666'}}>信誉值 {data.credit}</p>
</Column>
</a>
<FlexGrow/>
<a
href={`/#/shop/${data.shopId}`}
target={'_blank'}
>
<Row>
<p style={{fontSize: '14px', color: '#666666', marginRight: '6px'}}>店铺主页</p>
<img src={require('../../pages/platform/HomePage/asset/icon/right.svg')} alt=""/>
</Row>
</a>
</Row>
<Row className="pic" justify={'start'}>
{
data.goodsBriefDTOList.map((item, index) =>
<a
href={`/#/goodsDetail/${item.goodsId}`}
target={'_blank'}
>
<img className='shop_goods' src={item.avatarUrl} alt=""/>
</a>
)
}
</Row>
</Column>
)
}
}
export default ShopItem;
/**
* @author: Cui yu
* @Since: 2019/8/6
**/
import store from "../../store";
import {Redirect} from "react-router";
import React from "react";
export default function checkIdentity() {
const { token } = store.getState().login;
return !!token ? null : <Redirect to={{pathname:"/"}}/>
}
.good{
margin-right: 24px;
margin-top: 30px;
width: 282px;
height: 385px;
cursor: pointer;
.picture {
background-color: #ffffff;
width: 282px;
height: 282px;
border-radius: 15px
}
.name {
padding: 16px 0 16px 0;
width: 282px;
font-size: 16px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis
}
.num {
font-size: 12px;
color: #666666;
}
.price {
padding-top: 24px;
.aftPrice {
color: #FF6C00;
font-size: 20px;
}
.prePrice {
width: 50px;
margin-left: 18px;
color: #CCCCCC;
font-size: 14px;
text-decoration: line-through
}
}
}
.shop {
width: 588px;
height: 298px;
background-color: #ffffff;
margin-top: 40px;
border-radius: 15px;
.farmer {
width: 540px;
padding: 30px 24px 0 24px;
}
.pic {
padding: 30px 24px 30px 24px;
}
}
.shop_avatar {
width: 40px;
height: 40px;
border-radius: 20px;
}
.shop_goods {
width: 168px;
height: 168px;
border-radius: 10px;
}
.goodsContainer {
flex-wrap: wrap;
}
.omission {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis
}
//NotesItem styles
.item {
margin-top: 20px;
margin-right: 24px;
height: 264px;
width: 282px;
border-radius: 10px;
background-color: #ffffff;
.noteImg {
width: 282px;
height: 136px;
cursor: pointer;
border-radius: 10px;
}
.noteTitle {
cursor: pointer;
padding-top: 10px;
color: #222222;
font-size: 14px;
max-width: 141px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.icon {
width: 66px;
img {
margin-left: 10px;
}
p {
color: #999999;
font-size: 13px;
padding-left: 8px;
}
}
.user {
cursor: pointer;
img {
height: 24px;
width: 24px;
border-radius: 12px;
}
p {
transition: 0.5s all;
padding-left: 12px;
color: #333333;
font-size: 14px;
}
}
.user:hover>p {
color: #7EC18d;
}
}
.item:nth-child(4n) {
margin-right: 0;
}
/**
* @author xue chen
* @since 2019/8/3
*/
import {Row, Column} from './common/Flex';
import {Wrapper, Container} from "./common/Container";
import FlexGrow from "./common/FlexGrow";
import checkLogin from "./common/checkLogin";
import {PrimaryModal, SingleModal, DefaultModal} from "./modal/Modal";
export {
Row, Column,
Wrapper, Container,
FlexGrow,
checkLogin,
PrimaryModal, SingleModal, DefaultModal,
}
\ No newline at end of file
/**
* @author Cui yu
* @date 2019/8/19
* @Description: 封装好的 modal 组件
*
* 使用方法: 总共两种弹框 一种有按钮(DefaultModal) 一种没有按钮 (PrimaryModal) 按钮的显示与关闭受到父组件的 visible 的控制
*
* 第一种的内容包含在 children 中,弹框的关闭效果必须包含在按钮或者children内容的回调函数中
*
* 更多自定义样式 例如 title 、 footer 等,可以引入css文件修改 对应的 className: ant-modal-header | content | footer
*
* 点击 loading 、10s自动跳转 的例子放在 example.js 中
*/
import {Modal, Button} from 'antd';
import React, {Component} from "react";
export class PrimaryModal extends Component{
render() {
const {
style,
handleClose,
title = "",
children, // modal中 body 中的内容
visible, //是否显示 modal
close = false, // 是否显示右上角关闭按钮
} = this.props;
return (
<div>
<Modal
{...style}
visible={visible}
title={title}
onOk={handleClose}
onCancel={handleClose}
closable={close}
footer={null}
>
{children}
</Modal>
</div>
);
}
}
export class SingleModal extends Component{
render() {
const {
style,
okLoading = false, //按钮是否显示 loading 图标 ,loading的出现以及关闭,需在按钮的回调中设置
visible, //是否显示 modal
children, // modal中body 中的内容
title = "标题",
okText = "确定", // 确认按钮
okType = "primary",
close = false,
ok, // 点击确认后的回调函数(必须在回调中将visible关闭)
} = this.props;
return (
<div>
<Modal
{...style}
visible={visible}
title={title}
closable={close}
footer={[
<Button key="submit" type={okType} loading={okLoading} onClick={ok}>
{okText}
</Button>
]}
>
{children}
</Modal>
</div>
);
}
}
export class DefaultModal extends Component {
render() {
const {
style, // 额外属性
cancelLoading = false,//按钮是否显示 loading 图标 ,loading的出现以及关闭,需在按钮的回调中设置
okLoading = false,
cancelText = "取消", //取消按钮
cancelType = "default", //按钮的样式 ["default", "primary", "ghost", "dashed", "danger", "link"]
visible, //是否显示 modal
children, // modal中 body 中的内容
title = "标题",
okText = "确定", // 确认按钮
cancel, //点击确认后的回调函数
okType = "primary",
close = false,
ok, // 点击确认后的回调函数(必须在回调中将visible关闭)
} = this.props;
return (
<div>
<Modal
{...style}
visible={visible}
title={title}
closable={close}
bodyStyle={{textAlign: 'center'}}
footer={[
<Button key="back" type={cancelType} loading={cancelLoading} onClick={cancel}>
{cancelText}
</Button>,
<Button key="submit" type={okType} loading={okLoading} onClick={ok}>
{okText}
</Button>,
]}
>
{children}
</Modal>
</div>
);
}
}
import React, {Component} from 'react';
import {DefaultModal,PrimaryModal} from "./Modal";
import {Link, withRouter} from "react-router-dom";
import './modal.css'
import {Button} from "antd";
class Example extends Component {
constructor(props) {
super(props);
this.state = {
isVisible: false, //默认弹框状态
isVisible1: false, // 无内容弹框状态
loading: false, //是否出现loading
second: 10 //10s后进行下一操作
}
}
handleOk = () => { //点击确定按钮后 出现1s的loading效果
this.setState({loading: true},
() => (
setTimeout(() => {
this.setState({loading: false, visible: false});
}, 1000)
));
};
countDown = () => { // 10S 后自动跳转
let ti = this.state.second; // 获得需要计数的秒数
console.log(this.state.isVisible1);
this.setState({isVisible1: true});
setInterval(() => {
this.setState({second: this.state.second - 1}); // 刷新传入给modal的秒数
}, 1000);
setTimeout(() => {
this.setState(() => ({
isVisible1: false
}), () => (
this.props.history.push('#') // 计时器: 读秒完成之后跳转至指定页面
))
}, ti * 1000);
};
componentWillUnmount() {
clearInterval();
clearTimeout(); //用于在组件卸载之后 关闭计时器
}
handleCancel = () => {
this.setState({isVisible: false}); // 关闭弹框
};
render() {
return (
<div>
{/*// 默认样式(loading效果)*/}
<DefaultModal
visible={this.state.visible}
cancel={this.handleCancel}
ok={this.handleOk}
close={true}
okLoading={this.state.loading}
title={'确认删除'}
>
<div>
确定删除浏览记录吗?
</div>
</DefaultModal>
{/*// 自动跳转的按钮*/}
<PrimaryModal
visible={this.state.isVisible1}
cancel={this.handleCancel}
ok={this.handleOk}
okLoading={this.state.loading}
title={''}
>
<div style={{
display: 'flex',
alignItems: 'center',
flexDirection: 'column',
justifyContent: 'space-around',
height: '200px'
}}>
<p> 点击支付 {this.state.second}s后自动跳转</p>
<p onClick={() => this.setState({isVisible1: !this.state.isVisible1})}><Link
to={'./scroll'}>跳转</Link></p>
</div>
</PrimaryModal>
<Button onClick={() => this.setState({visible: !this.state.visible})}>loading</Button>
<Button onClick={this.countDown}>自动跳转</Button>
</div>
);
}
}
export default withRouter(Example);
\ No newline at end of file
.ant-modal-header {
border: none;
}
.ant-modal-footer {
display: flex;
justify-content: space-around;
/*text-align: center;*/
border: none;
}
/**
* baseUrl
* urlApi
*
* @author xue chen
* @since 2019/7/30
*/
const baseUrl = '';
export default baseUrl;
const api = {
// 认证接口 auth 归并至 user
auth: {
login: {
// 手机号 + 密码登录
password: '/daijian-user/login/password',
// 手机号 + 验证码登录i
verifyCode: '/daijian-user/login/verify-code'
},
// 获取手机号
mobile: '/daijian-user/mobile',
// 刷新token
refreshToken: '/daijian-user/refreshToken',
// 注册
register: '/daijian-user/register',
update: {
// 重置手机号
mobile: '/daijian-user/update/mobile',
// 重置密码
password: '/daijian-user/update/password'
}
},
};
export {api};
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import {Provider} from 'react-redux';
import store from "./store";
import './reset.less';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.getElementById('root'));
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
html {
height: 100%;
}
body {
line-height: 1;
background-color: #F8F8FA;
height: 100%;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
\ No newline at end of file
/**
* @author han wen yao
* @since 2019/8/17
*/
import {observable, computed, action} from "mobx";
import {get} from "../util/requests";
import {api} from "../config/url";
export default class CartStore {
@observable isAllSelected = false;
@observable allgoods = [];
@observable shopList = [];
@action getCartData() {
get(api.cart.cart)
.then(res => {
if(res.code === 200) {
res.data.map(item => {
item.shopisSelect = false;
item.cartGoodsVOList.map(item1=>(
item1.goodsisSelect = false));
return item
});
this.shopList = res.data
}
})
}
//判断是否全选
@computed get AllIsSelect() {
const allSelect = this.shopList.every(
(item) => item.shopisSelect === true
);
return allSelect;
}
//选中商品总价格
@computed get allFee() {
let totalFee = 0;
this.shopList.map(item => (
item.cartGoodsVOList.filter(item => item.goodsisSelect === true).map(item => (
totalFee += item.unitPrice * item.goodsNum
))
));
return totalFee;
}
//已选商品的个数
@computed get allSelectNum() {
let totalSelectNum = 0;
this.shopList.map(item => (
item.cartGoodsVOList.filter(item => item.goodsisSelect === true).map(item => (
totalSelectNum += item.goodsNum
))
));
return totalSelectNum;
}
//已选商品总总数
@computed get allSelectKindNum(){
let totalSelectNum = 0;
this.shopList.map(item => (
item.cartGoodsVOList.filter(item => item.goodsisSelect === true).map(() =>(
totalSelectNum += 1
))
));
return totalSelectNum;
}
//商品的总个数
@computed get allNum() {
let totalNum = 0;
this.shopList.map(item => (
item.cartGoodsVOList.map(item => (
totalNum += item.goodsNum
))
));
return totalNum
}
//商品的总种数
@computed get allKindNum() {
let totalNum = 0;
this.shopList.map(item => (
item.cartGoodsVOList.map(() => (
totalNum += 1
))
));
return totalNum
}
}
/**
* @author xue chen
* @since 2019/8/4
*/
import {createStore, applyMiddleware, compose} from "redux";
import reducer from "./reducer";
import thunk from "redux-thunk";
import CartStore from "./CartStore";
//浏览器中可以查看
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducer, composeEnhancers(
//thunk可以在dispatch中添加一异步函数
applyMiddleware(thunk)
));
export default store;
const cartStore = new CartStore();
export {
cartStore
}
\ No newline at end of file
/**
* @author xue chen
* @since 2019/8/4
*/
import {combineReducers} from "redux";
import {
reducer as loginReducer
} from '../pages/auth/LoginPage/store';
import {
reducer as homeReducer
} from '../pages/platform/HomePage/store';
import {
reducer as goodDetailReducer
} from '../pages/GoodsDetailPage/store';
import {
reducer as searchInputValue
} from '../pages/SearchResultPage/store';
const reducer = combineReducers({
home: homeReducer,
login: loginReducer,
goodDetail: goodDetailReducer,
searchInputValue: searchInputValue,
});
export default reducer;
\ No newline at end of file
class DateUtil{
/**
* @param dateString
* @return Date
*/
static parserDateString(dateString){
if(dateString){
let regEx = new RegExp("\\-","gi");
let validDateStr=dateString.replace(regEx,"/");
let milliseconds=Date.parse(validDateStr);
return new Date(milliseconds);
}
}
// timestamp时间戳 formater时间格式
static formatDate(timestamp, formater) {
let date = new Date();
date.setTime(parseInt(timestamp));
formater = (formater != null)? formater : 'yyyy-MM-dd hh:mm';
Date.prototype.Format = function (fmt) {
let o = {
"M+": this.getMonth() + 1, //月
"d+": this.getDate(), //日
"h+": this.getHours(), //小时
"m+": this.getMinutes(), //分
"s+": this.getSeconds(), //秒
"q+": Math.floor((this.getMonth() + 3) / 3), //季度
"S": this.getMilliseconds() //毫秒
};
if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
for (var k in o) {
if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ?
(o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
}
return fmt;
};
return date.Format(formater);
}
}
export default DateUtil;
/**
* requests
*
* get, post, del, put, uploadFile
*
* @author xue chen
* @since 2019/7/30
*/
import baseUrl from "../config/url";
import storage from "./storage";
import Axios from "axios";
import {STORAGE_KEY_ACCESS_TOKEN} from "../variable/storageKey";
import {message} from "antd";
// 公共请求
const requests = (Url, options) => {
// 默认头信息
const defaultHeaders = {
"Content-type": "application/json",
"Accept": "application/json"
};
let token = storage.get(STORAGE_KEY_ACCESS_TOKEN);
// let token = "token";
if(token) {
defaultHeaders.Authorization = "Bearer " +token;
}
const {
method = "GET",
headers = defaultHeaders,
data = {},
params = {}
} = options;
// request.data
let body = JSON.stringify(data);
// url
let url = `${baseUrl}${Url}`;
// Promise
return new Promise((resolve, reject) => {
// 增加对于全局消息提示的显示配置
message.config({
duration: 1.5, // 显示时间1.5s
maxCount: 2, // 一次性显示最大条数2条
});
Axios({
method,
url,
data: body,
headers,
params
})
.then(response => {
if(response.data.code === 200){
resolve(response.data)
}else if(response.data.code === 500 || response.data.code === 404) {
message.warning(response.data.message||'网络连接失败');
resolve(response.data)
}else if(response.data.code === 400){
message.warning(response.data.message||'网络连接失败');
resolve(response.data)
}else if(response.data.code===401){
message.warning(response.data.message||'请先登录');
resolve(response.data)
}
})
.catch(err => {
message.error("请检测网络连接");
reject(err);
})
})
};
// get
const get = (Url, params) => {
return requests(Url, {
method: "GET",
params
})
};
// post
const post = (Url, data) => {
return requests(Url, {
method: "POST",
data
})
};
// delete
const del = (Url, data) => {
return requests(Url, {
method: 'DELETE',
data
})
};
// put
const put = (Url, data) => {
return requests(Url, {
method: 'PUT',
data
})
};
const uploadFile = (Url, data, params) => {
let url = `${baseUrl}${Url}`;
return new Promise((resolve, reject) => {
Axios({
method: 'POST',
url,
data: data,
params,
headers: {
"Content-Type": "multipart/form-data",
//todo 修改token
// "Authorization": "token "
"Authorization": "Bearer " + storage.get(STORAGE_KEY_ACCESS_TOKEN)
},
})
.then(response => {
if(response.data.code === 200){
resolve(response.data)
}else if(response.data.code === 500 || response.data.code === 404) {
message.warning(response.data.message);
resolve(response.data)
}else if(response.data.code === 400){
message.warning(response.data.message);
resolve(response.data)
}
})
.catch(err => {
message.error("请检查网络连接");
reject(err);
})
})
};
export {get, post, del, put, uploadFile}
/**
* storage
*
* storage.set(value, key)
* storage.session.set(value, key)
*
* @author xue chen
* @since 2019/7/30
*/
// 移动端无痕模式下localStorage使用
(function () {
const KEY = '_localStorage',
VALUE = 'test';
// 检测是否正常
try {
localStorage.setItem(KEY, VALUE);
} catch (e) {
let noop = function () {};
localStorage._proto_ = {
setItem: noop,
getItem: noop,
removeItem: noop,
clear: noop
}
}
// 删除测试数据
if(localStorage.getItem(KEY) === VALUE) {
localStorage.removeItem(KEY);
}
})();
let storage = {
storage: window.localStorage,
session: {
storage: window.sessionStorage
}
};
const storageApi = {
set(key, value) {
if(!value) return;
this.storage.setItem(key, JSON.stringify(value))
},
get(key) {
if(!key) return;
const result = this.storage.getItem(key);
return JSON.parse(result);
},
remove(key) {
if(!key) return;
this.storage.removeItem(key);
},
clear() {
this.storage.clear()
},
};
Object.assign(storage, storageApi);
Object.assign(storage.session, storageApi);
export default storage;
/**
* @author: Cui yu
* @Since: 2019/8/6
*/
/**
* 匹配手机号4-7位
*/
export const MOBILE_4_TO_7_REGEX = new RegExp('(?<=[\\d]{3})\\d(?=[\\d]{4})');
/**
* 合法大陆手机号
*/
export const MOBILE_REGEX = new RegExp("^1[34578]\\d{9}$");
/**
* 合法密码(6-20位字母数字组合)
*/
export const PASSWORD_REGEX = new RegExp("(?=.*[0-9])(?=.*[a-zA-Z]).{6,20}");
/**
* 昵称(2-10位中文或3-10中英文数字混合,且不能为纯数字)
*/
export const NICK_NAME_REGEX = new RegExp("(?![0-9]+$)[\\u4e00-\\u9fa5_a-zA-Z0-9_]{3,10}|^[\\u4e00-\\u9fa5]{2,10}$");
/**
* storageKey
*
* @author xue chen
* @since 2019/7/30
*/
export const STORAGE_KEY_ACCESS_TOKEN = 'ACCESS_TOKEN'; // token
export const STORAGE_KEY_USER_INFO = 'USER_INFO'; // PerDocPage
export const STORAGE_KEY_EXPIRE_TIME = 'EXPIRE_TIME'; // expireTime
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment