├── src
├── layouts
│ ├── ProductLayout.less
│ ├── BaseLayout.less
│ ├── ProductLayout.js
│ ├── MainLayout.js
│ └── BaseLayout.js
├── Untitled.gif
├── assets
│ ├── p1.jpg
│ ├── p2.jpg
│ ├── p3.png
│ ├── p4.jpg
│ ├── p5.jpg
│ ├── ad1.jpg
│ ├── car1.png
│ ├── car2.png
│ ├── me1.png
│ ├── me2.png
│ ├── pd1.jpg
│ ├── pd2.jpg
│ ├── pd3.jpg
│ ├── pd4.jpg
│ ├── sc1.png
│ ├── sc2.png
│ ├── sc3.png
│ ├── sc4.png
│ ├── banner1.gif
│ ├── banner2.gif
│ ├── banner3.gif
│ ├── close.png
│ ├── footer.png
│ ├── home1.png
│ ├── home2.png
│ ├── sort1.png
│ ├── sort2.png
│ ├── koubei@2x.png
│ ├── shopcar1.png
│ ├── shopcar2.png
│ ├── koubei_sel@2x.png
│ ├── search_icon_2.png
│ ├── list.svg
│ ├── home.svg
│ ├── scar.svg
│ └── logo.svg
├── index.css
├── components
│ ├── TabBar
│ │ ├── index.less
│ │ └── index.js
│ ├── ImageAd
│ │ ├── index.less
│ │ └── index.js
│ ├── ShortCut
│ │ ├── index.less
│ │ └── index.js
│ ├── Footer
│ │ ├── index.less
│ │ └── index.js
│ ├── Popup
│ │ ├── index.less
│ │ └── index.js
│ ├── SearchProduct
│ │ ├── index.js
│ │ └── index.less
│ ├── ProductDetail
│ │ ├── index.less
│ │ └── index.js
│ ├── Stepper
│ │ ├── index.less
│ │ └── index.js
│ ├── ProductList
│ │ ├── index.js
│ │ └── index.less
│ └── HomeShopCar
│ │ ├── index.less
│ │ └── index.js
├── saga
│ ├── globalSaga.js
│ ├── productSaga.js
│ ├── bannerSaga.js
│ ├── homeShopcarSaga.js
│ ├── shortcutSaga.js
│ ├── productDetailSaga.js
│ └── index.js
├── routes
│ ├── classify.js
│ ├── me.js
│ ├── shopcar.js
│ ├── homeShopcar.js
│ ├── productList.js
│ ├── shortcut.js
│ ├── productdetail.js
│ ├── home.js
│ └── banner.js
├── utils
│ ├── authority.js
│ ├── Authorized.js
│ ├── utils.less
│ ├── request.js
│ └── utils.js
├── redux
│ ├── bannerRedux.js
│ ├── productsRedux.js
│ ├── shortCutRedux.js
│ ├── homeShopcarRedux.js
│ ├── productDetailRedux.js
│ ├── globalRedux.js
│ ├── index.js
│ └── login.js
├── index.js
├── store
│ └── index.js
├── common
│ ├── router.js
│ └── menu.js
├── services
│ └── api.js
├── logo.svg
├── mock
│ └── data.js
└── registerServiceWorker.js
├── screenshot.gif
├── public
├── favicon.ico
├── manifest.json
└── index.html
├── .gitignore
├── package.json
├── LICENSE
├── README.md
└── config-overrides.js
/src/layouts/ProductLayout.less:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/layouts/BaseLayout.less:
--------------------------------------------------------------------------------
1 | .container{
2 |
3 | width: 100%;
4 |
5 | }
--------------------------------------------------------------------------------
/screenshot.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/screenshot.gif
--------------------------------------------------------------------------------
/src/Untitled.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/src/Untitled.gif
--------------------------------------------------------------------------------
/src/assets/p1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/src/assets/p1.jpg
--------------------------------------------------------------------------------
/src/assets/p2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/src/assets/p2.jpg
--------------------------------------------------------------------------------
/src/assets/p3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/src/assets/p3.png
--------------------------------------------------------------------------------
/src/assets/p4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/src/assets/p4.jpg
--------------------------------------------------------------------------------
/src/assets/p5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/src/assets/p5.jpg
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/assets/ad1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/src/assets/ad1.jpg
--------------------------------------------------------------------------------
/src/assets/car1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/src/assets/car1.png
--------------------------------------------------------------------------------
/src/assets/car2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/src/assets/car2.png
--------------------------------------------------------------------------------
/src/assets/me1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/src/assets/me1.png
--------------------------------------------------------------------------------
/src/assets/me2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/src/assets/me2.png
--------------------------------------------------------------------------------
/src/assets/pd1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/src/assets/pd1.jpg
--------------------------------------------------------------------------------
/src/assets/pd2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/src/assets/pd2.jpg
--------------------------------------------------------------------------------
/src/assets/pd3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/src/assets/pd3.jpg
--------------------------------------------------------------------------------
/src/assets/pd4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/src/assets/pd4.jpg
--------------------------------------------------------------------------------
/src/assets/sc1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/src/assets/sc1.png
--------------------------------------------------------------------------------
/src/assets/sc2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/src/assets/sc2.png
--------------------------------------------------------------------------------
/src/assets/sc3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/src/assets/sc3.png
--------------------------------------------------------------------------------
/src/assets/sc4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/src/assets/sc4.png
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | .overflow_hidden{
5 |
6 | overflow: hidden !important;
7 |
8 | }
--------------------------------------------------------------------------------
/src/assets/banner1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/src/assets/banner1.gif
--------------------------------------------------------------------------------
/src/assets/banner2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/src/assets/banner2.gif
--------------------------------------------------------------------------------
/src/assets/banner3.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/src/assets/banner3.gif
--------------------------------------------------------------------------------
/src/assets/close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/src/assets/close.png
--------------------------------------------------------------------------------
/src/assets/footer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/src/assets/footer.png
--------------------------------------------------------------------------------
/src/assets/home1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/src/assets/home1.png
--------------------------------------------------------------------------------
/src/assets/home2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/src/assets/home2.png
--------------------------------------------------------------------------------
/src/assets/sort1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/src/assets/sort1.png
--------------------------------------------------------------------------------
/src/assets/sort2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/src/assets/sort2.png
--------------------------------------------------------------------------------
/src/assets/koubei@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/src/assets/koubei@2x.png
--------------------------------------------------------------------------------
/src/assets/shopcar1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/src/assets/shopcar1.png
--------------------------------------------------------------------------------
/src/assets/shopcar2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/src/assets/shopcar2.png
--------------------------------------------------------------------------------
/src/assets/koubei_sel@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/src/assets/koubei_sel@2x.png
--------------------------------------------------------------------------------
/src/assets/search_icon_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pythonsir/wx-mobile-app/HEAD/src/assets/search_icon_2.png
--------------------------------------------------------------------------------
/src/components/TabBar/index.less:
--------------------------------------------------------------------------------
1 | .container{
2 |
3 | position: fixed;
4 | left: 0px;
5 | bottom: 0px;
6 | width: 100%;
7 | background-color: #fff;
8 | box-sizing: border-box;
9 |
10 |
11 | }
12 |
13 |
--------------------------------------------------------------------------------
/src/components/ImageAd/index.less:
--------------------------------------------------------------------------------
1 | .image_ad_container{
2 |
3 | a{
4 | display: block;
5 | width: 100%;
6 | img{
7 | height: 104px;
8 | width: inherit;
9 | }
10 | }
11 |
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/src/saga/globalSaga.js:
--------------------------------------------------------------------------------
1 | import { put,takeLatest,call,select } from 'redux-saga/effects'
2 |
3 |
4 |
5 |
6 | function* getSubTabPosition(){
7 |
8 |
9 |
10 |
11 | }
12 |
13 |
14 |
15 |
16 | function* globalSaga() {
17 |
18 |
19 |
20 | }
21 |
22 | export default globalSaga;
--------------------------------------------------------------------------------
/src/routes/classify.js:
--------------------------------------------------------------------------------
1 | import React,{PureComponent} from 'react'
2 |
3 |
4 | class Classify extends PureComponent{
5 |
6 | render(){
7 | return (
8 |
9 | 商品分类
10 |
11 | )
12 | }
13 |
14 | }
15 |
16 | export default Classify;
--------------------------------------------------------------------------------
/src/routes/me.js:
--------------------------------------------------------------------------------
1 | import React ,{PureComponent} from 'react'
2 |
3 | class Me extends PureComponent{
4 |
5 |
6 | render(){
7 |
8 | return(
9 |
10 |
11 | me
12 |
13 | )
14 |
15 | }
16 |
17 |
18 | }
19 |
20 | export default Me;
--------------------------------------------------------------------------------
/src/utils/authority.js:
--------------------------------------------------------------------------------
1 | // use localStorage to store the authority info, which might be sent from server in actual project.
2 | export function getAuthority() {
3 | return localStorage.getItem('antd-pro-authority');
4 | }
5 |
6 | export function setAuthority(authority) {
7 | return localStorage.setItem('antd-pro-authority', authority);
8 | }
9 |
--------------------------------------------------------------------------------
/src/layouts/ProductLayout.js:
--------------------------------------------------------------------------------
1 | import React,{PureComponent} from 'react'
2 | import ProductDetail from '../routes/productdetail'
3 |
4 | export default class ProductLayout extends PureComponent{
5 |
6 |
7 | render(){
8 |
9 | return (
10 |
11 |
12 |
13 |
14 | )
15 |
16 |
17 | }
18 |
19 |
20 |
21 | }
--------------------------------------------------------------------------------
/src/routes/shopcar.js:
--------------------------------------------------------------------------------
1 | import React,{PureComponent} from 'react'
2 |
3 | class ShopCar extends PureComponent{
4 |
5 | render(){
6 |
7 | return(
8 |
9 |
10 | shopcar
11 |
12 |
13 |
14 | )
15 |
16 |
17 |
18 |
19 | }
20 |
21 |
22 | }
23 |
24 | export default ShopCar;
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/src/utils/Authorized.js:
--------------------------------------------------------------------------------
1 | import RenderAuthorized from 'ant-design-pro/lib/Authorized';
2 | import { getAuthority } from './authority';
3 |
4 | let Authorized = RenderAuthorized(getAuthority()); // eslint-disable-line
5 |
6 | // Reload the rights component
7 | const reloadAuthorized = () => {
8 | Authorized = RenderAuthorized(getAuthority());
9 | };
10 |
11 | export { reloadAuthorized };
12 | export default Authorized;
13 |
--------------------------------------------------------------------------------
/src/redux/bannerRedux.js:
--------------------------------------------------------------------------------
1 | const initstate = {
2 |
3 | data:[]
4 |
5 | }
6 |
7 | function bannerRedux(state=initstate,{type,payload}){
8 |
9 | switch(type){
10 |
11 | case 'getBannerListRedux':
12 |
13 | return {
14 | ...state,
15 | data:payload
16 | }
17 |
18 | default:
19 | return state;
20 | }
21 |
22 |
23 | }
24 |
25 | export default bannerRedux;
--------------------------------------------------------------------------------
/src/redux/productsRedux.js:
--------------------------------------------------------------------------------
1 | const initstate = {
2 |
3 | data:[]
4 |
5 | }
6 |
7 | function productsRedux(state=initstate,{type,payload}){
8 |
9 | switch(type){
10 |
11 | case 'getProduct_1ReduxList':
12 |
13 | return {
14 | ...state,
15 | data:payload
16 | }
17 |
18 | default:
19 | return state;
20 | }
21 |
22 |
23 | }
24 |
25 | export default productsRedux;
--------------------------------------------------------------------------------
/src/redux/shortCutRedux.js:
--------------------------------------------------------------------------------
1 | const initstate = {
2 |
3 | data:[]
4 |
5 | }
6 |
7 | function shortCutRedux(state=initstate,{type,payload}){
8 |
9 | switch(type){
10 |
11 | case 'getShortcutReduxList':
12 |
13 | return {
14 | ...state,
15 | data:payload
16 | }
17 |
18 | default:
19 | return state;
20 | }
21 |
22 |
23 | }
24 |
25 | export default shortCutRedux;
--------------------------------------------------------------------------------
/src/redux/homeShopcarRedux.js:
--------------------------------------------------------------------------------
1 | const initstate = {
2 |
3 | data:{},
4 |
5 | }
6 |
7 | function homeShopcarRedux(state=initstate,{type,payload}){
8 |
9 | switch(type){
10 |
11 | case 'addHomeShopCar':
12 |
13 | return {
14 | ...state,
15 | data:payload,
16 | }
17 |
18 | default:
19 | return state;
20 | }
21 |
22 |
23 | }
24 |
25 | export default homeShopcarRedux;
--------------------------------------------------------------------------------
/src/redux/productDetailRedux.js:
--------------------------------------------------------------------------------
1 | const initstate = {
2 |
3 | data:{}
4 |
5 | }
6 |
7 | function productDetailRedux(state=initstate,{type,payload}){
8 |
9 | switch(type){
10 |
11 | case 'pd/prodcutDetailByid':
12 |
13 | return {
14 | ...state,
15 | data:payload,
16 | }
17 |
18 | default:
19 | return state;
20 | }
21 |
22 |
23 | }
24 |
25 | export default productDetailRedux;
--------------------------------------------------------------------------------
/src/saga/productSaga.js:
--------------------------------------------------------------------------------
1 | import { put,takeLatest,call,select } from 'redux-saga/effects'
2 | import {getproduct_1} from '../services/api'
3 |
4 |
5 |
6 | function* productList_1(){
7 |
8 | const res = yield call(getproduct_1)
9 |
10 | yield put({
11 | type:'getProduct_1ReduxList',
12 | payload:res.data,
13 | })
14 |
15 |
16 | }
17 |
18 |
19 | function* productSaga(){
20 |
21 | yield takeLatest('getproduct_1',productList_1)
22 |
23 | }
24 |
25 | export default productSaga;
--------------------------------------------------------------------------------
/src/saga/bannerSaga.js:
--------------------------------------------------------------------------------
1 | import { put,takeLatest,call,select } from 'redux-saga/effects'
2 | import {getBanners} from '../services/api'
3 |
4 | function* getBannerList(){
5 |
6 | const res = yield call(getBanners);
7 |
8 | yield put({
9 | type:'getBannerListRedux',
10 | payload:res.data,
11 | }
12 | )
13 |
14 |
15 |
16 | }
17 |
18 |
19 |
20 | function* bannerSaga(){
21 |
22 | yield takeLatest('getBannerListSaga',getBannerList)
23 |
24 | }
25 |
26 | export default bannerSaga;
--------------------------------------------------------------------------------
/src/saga/homeShopcarSaga.js:
--------------------------------------------------------------------------------
1 | import { put,takeLatest,call,select } from 'redux-saga/effects'
2 | import {getHShopCar} from '../services/api'
3 |
4 |
5 |
6 | function* getHomeShopcarSaga(){
7 |
8 | const res = yield call(getHShopCar)
9 |
10 | yield put({
11 | type:'addHomeShopCar',
12 | payload:res.data,
13 | })
14 |
15 |
16 | }
17 |
18 |
19 | function* homeShopcarSaga(){
20 |
21 | yield takeLatest('getHomeShopcarSaga',getHomeShopcarSaga)
22 |
23 | }
24 |
25 | export default homeShopcarSaga;
--------------------------------------------------------------------------------
/src/saga/shortcutSaga.js:
--------------------------------------------------------------------------------
1 | import { put,takeLatest,call,select } from 'redux-saga/effects'
2 | import {getShortcutList} from '../services/api'
3 |
4 |
5 | function* getShortcutSagaList(){
6 |
7 | const res = yield call(getShortcutList)
8 |
9 | yield put({
10 | type:'getShortcutReduxList',
11 | payload:res.data,
12 | })
13 |
14 |
15 | }
16 |
17 |
18 | function* shortcutSaga(){
19 |
20 | yield takeLatest('getShortcutSagaList',getShortcutSagaList)
21 |
22 | }
23 |
24 | export default shortcutSaga;
--------------------------------------------------------------------------------
/src/components/ShortCut/index.less:
--------------------------------------------------------------------------------
1 | .containter{
2 | display: flex;
3 | flex-direction: row;
4 | justify-content: flex-start;
5 | flex-wrap: wrap;
6 | width: inherit;
7 | overflow: hidden;
8 |
9 | div{
10 |
11 | width: 25%;
12 |
13 | a{
14 | display: flex;
15 | flex-direction: column;
16 | align-items: center;
17 |
18 | img{
19 |
20 | width: 100%;
21 |
22 | }
23 |
24 | }
25 | }
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/src/redux/globalRedux.js:
--------------------------------------------------------------------------------
1 | const initstate = {
2 |
3 | homePopup:true,
4 | }
5 |
6 | function globalRedux(state=initstate,{type,payload}){
7 |
8 | switch(type){
9 |
10 | case 'openPopup':
11 |
12 | return {
13 | ...state,
14 | homePopup:false,
15 | }
16 | case 'closePopup':
17 |
18 | return {
19 | ...state,
20 | homePopup:true,
21 | }
22 |
23 | default:
24 | return state;
25 | }
26 |
27 |
28 | }
29 |
30 | export default globalRedux;
--------------------------------------------------------------------------------
/src/saga/productDetailSaga.js:
--------------------------------------------------------------------------------
1 | import { put,takeLatest,call,select } from 'redux-saga/effects'
2 | import {getProductdetailById} from '../services/api'
3 |
4 |
5 |
6 | function* getProductdetailSaga({payload}){
7 |
8 | const res = yield call(getProductdetailById,payload);
9 |
10 |
11 | yield put({
12 | type:'pd/prodcutDetailByid',
13 | payload:res,
14 | }
15 | )
16 |
17 |
18 |
19 | }
20 |
21 |
22 |
23 | function* productDetailSaga(){
24 |
25 | yield takeLatest('getProductdetailSaga',getProductdetailSaga)
26 |
27 | }
28 |
29 | export default productDetailSaga;
--------------------------------------------------------------------------------
/src/components/Footer/index.less:
--------------------------------------------------------------------------------
1 | .footer_container{
2 |
3 | color: #666;
4 |
5 | .content{
6 | width: 200px;
7 | margin: auto;
8 | display: flex;
9 | flex-direction: row;
10 | align-items: center;
11 | a{
12 | display: flex;
13 | flex-direction: row;
14 | justify-content: center;
15 | align-items: center;
16 | color: #666;
17 | font-size: 14px;
18 | img{
19 | margin-right: 5px;
20 | }
21 | margin-right: 5px;
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/src/routes/homeShopcar.js:
--------------------------------------------------------------------------------
1 | import React,{PureComponent} from 'react'
2 | import {connect} from 'react-redux'
3 | import {default as HomeShopcarComponent} from '../components/HomeShopCar'
4 |
5 |
6 | class HomeShopcar extends PureComponent{
7 |
8 | render(){
9 |
10 | const { ajaxdata } = this.props;
11 |
12 | return (
13 |
14 | )
15 | }
16 |
17 | }
18 |
19 | export default connect(({ homeShopcarRedux })=>{
20 |
21 | return {
22 | ajaxdata : homeShopcarRedux.data
23 | }
24 |
25 | })(HomeShopcar)
--------------------------------------------------------------------------------
/src/components/Footer/index.js:
--------------------------------------------------------------------------------
1 | import React,{PureComponent} from 'react'
2 | import Styles from './index.less'
3 | import footer from '../../assets/footer.png'
4 |
5 | /**
6 | * 页尾组件
7 | */
8 | export default class Footer extends PureComponent{
9 |
10 | render(){
11 | return (
12 |
13 |
14 |
码农之家 提供技术支持
15 |
16 |
17 | )
18 | }
19 |
20 |
21 |
22 | }
--------------------------------------------------------------------------------
/src/saga/index.js:
--------------------------------------------------------------------------------
1 |
2 | import { put,takeEvery,fork,all } from 'redux-saga/effects'
3 | import globalSaga from './globalSaga'
4 | import bannerSaga from './bannerSaga'
5 | import shortcutSaga from './shortcutSaga'
6 | import productSaga from './productSaga'
7 | import homeShopcarSaga from './homeShopcarSaga'
8 | import productDetailSaga from './productDetailSaga'
9 |
10 | // 所有saga的入口配置文件
11 | const config = [
12 |
13 | fork(globalSaga),
14 | fork(bannerSaga),
15 | fork(shortcutSaga),
16 | fork(productSaga),
17 | fork(homeShopcarSaga),
18 | fork(productDetailSaga),
19 |
20 | ]
21 |
22 |
23 | export default function* rootSaga(){
24 |
25 | yield all(config)
26 |
27 | };
--------------------------------------------------------------------------------
/src/components/ImageAd/index.js:
--------------------------------------------------------------------------------
1 | import React,{PureComponent} from 'react'
2 | import {Link} from 'react-router-dom'
3 | import Styles from './index.less'
4 | import classNames from 'classnames'
5 |
6 | class ImageAd extends PureComponent{
7 |
8 |
9 | render(){
10 |
11 | const { imgsrc,imgurl } = this.props;
12 |
13 | return (
14 | // 图片广告
15 |
16 |
17 |
18 |

19 |
20 |
21 |
22 | )
23 |
24 | }
25 |
26 |
27 | }
28 |
29 | export default ImageAd;
--------------------------------------------------------------------------------
/src/routes/productList.js:
--------------------------------------------------------------------------------
1 | import React ,{PureComponent} from 'react'
2 |
3 | import {default as Products} from '../components/ProductList'
4 |
5 | import {connect} from 'react-redux'
6 |
7 |
8 | class ProductList extends PureComponent{
9 |
10 | componentDidMount(){
11 |
12 | this.props.dispatch({
13 | type:'getproduct_1'
14 | })
15 |
16 |
17 | }
18 |
19 |
20 | render(){
21 |
22 |
23 | return (
24 |
25 |
26 | )
27 |
28 | }
29 |
30 |
31 | }
32 |
33 | export default connect(({productsRedux})=>{
34 |
35 | return {
36 | data : productsRedux.data,
37 | }
38 |
39 | })(ProductList);
--------------------------------------------------------------------------------
/src/redux/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { combineReducers } from 'redux';
3 | import {
4 | routerReducer,
5 | } from 'react-router-redux'
6 | import bannerRedux from './bannerRedux'
7 | import shortCutRedux from './shortCutRedux'
8 | import productsRedux from './productsRedux'
9 | import globalRedux from './globalRedux'
10 | import homeShopcarRedux from './homeShopcarRedux'
11 | import productDetailRedux from './productDetailRedux'
12 |
13 |
14 | const config = {
15 | routerReducer,
16 | bannerRedux,
17 | shortCutRedux,
18 | productsRedux,
19 | globalRedux,
20 | homeShopcarRedux,
21 | productDetailRedux,
22 | }
23 |
24 |
25 |
26 |
27 |
28 | export default combineReducers(config);
29 |
30 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import 'babel-polyfill'
2 | import React from 'react'
3 | import { render } from 'react-dom'
4 | import { connect, Provider } from 'react-redux'
5 | import store ,{history} from './store'
6 | import { ConnectedRouter } from 'react-router-redux'
7 | import { Route, Switch,Redirect } from 'react-router-dom'
8 |
9 | import BaseLayout from './layouts/BaseLayout'
10 |
11 | import 'weui'
12 | import 'react-weui/build/packages/react-weui.css';
13 |
14 | import './index.css';
15 |
16 |
17 | render(
18 |
19 |
20 |
21 |
22 |
23 |
24 | ,
25 | document.getElementById('root')
26 | )
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import createSagaMiddleware from 'redux-saga'
2 | import createHistory from 'history/createBrowserHistory'
3 | import { createStore, applyMiddleware, compose } from 'redux'
4 | import { routerMiddleware } from 'react-router-redux'
5 | import reducer from '../redux/index'
6 | import rootSaga from '../saga/index'
7 |
8 | const history = createHistory()
9 |
10 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
11 |
12 | const sagaMiddleware = createSagaMiddleware()
13 |
14 | const middlewares = [ routerMiddleware(history),sagaMiddleware];
15 |
16 | const store = createStore(
17 | reducer,
18 | composeEnhancers(applyMiddleware(...middlewares))
19 | )
20 |
21 | sagaMiddleware.run(rootSaga)
22 |
23 | export {history}
24 |
25 | export default store;
--------------------------------------------------------------------------------
/src/routes/shortcut.js:
--------------------------------------------------------------------------------
1 | import React, {PureComponent} from 'react'
2 | import {connect} from 'react-redux'
3 |
4 | import { default as ShortCutComponent} from '../components/ShortCut'
5 |
6 | /**
7 | * @author pythonsir| baidu211@vip.qq.com | 微信: cxyzxh1388
8 | *
9 | * 首页中部快捷按钮组件
10 | */
11 | class ShortCut extends PureComponent{
12 |
13 |
14 |
15 |
16 | componentDidMount(){
17 |
18 | this.props.dispatch({
19 | type: 'getShortcutSagaList',
20 | });
21 |
22 | }
23 |
24 |
25 |
26 | render(){
27 |
28 | return (
29 |
30 | )
31 |
32 | }
33 |
34 |
35 | }
36 |
37 | export default connect(
38 | ({ shortCutRedux,})=>{
39 |
40 | return {
41 | data : shortCutRedux.data,
42 | }
43 |
44 | },
45 | )(ShortCut);
--------------------------------------------------------------------------------
/src/common/router.js:
--------------------------------------------------------------------------------
1 | import Home from '../routes/home'
2 | import Classify from '../routes/classify'
3 | import Me from '../routes/me'
4 | import ShopCar from '../routes/shopcar'
5 | import BasicLayout from '../layouts/BaseLayout'
6 |
7 |
8 | export const getRouterData = () => {
9 |
10 | const routerConfig = [
11 |
12 |
13 | {
14 | path:'/home',
15 | component:Home,
16 | },
17 | {
18 | path:'/home/classify',
19 | component:Classify,
20 | },
21 | {
22 | path:'/home/shopcar',
23 | component:ShopCar,
24 | },
25 | {
26 | path:'/home/me',
27 | component:Me,
28 | }
29 |
30 |
31 | ]
32 |
33 | return routerConfig;
34 |
35 | }
--------------------------------------------------------------------------------
/src/components/ShortCut/index.js:
--------------------------------------------------------------------------------
1 | import React,{PureComponent} from 'react'
2 |
3 | import {Link} from 'react-router-dom'
4 |
5 | import Styles from './index.less'
6 |
7 | class ShortCut extends PureComponent {
8 |
9 |
10 | render(){
11 |
12 | const {data} = this.props;
13 |
14 | return (
15 |
16 |
17 |
18 | {
19 |
20 |
21 | data.map((item,index)=>(
22 |
23 |
24 |
25 |

26 |
27 |
28 |
29 |
30 | ))
31 |
32 |
33 | }
34 |
35 |
36 |
37 |
38 |
39 | )
40 |
41 |
42 |
43 |
44 | }
45 |
46 |
47 |
48 | }
49 |
50 | export default ShortCut;
--------------------------------------------------------------------------------
/src/components/Popup/index.less:
--------------------------------------------------------------------------------
1 | .wx_modal{
2 | position: fixed;
3 | width: 100%;
4 | height: 100%;
5 | top: 0;
6 | left: 0;
7 | background-color: rgba(0, 0, 0, 0.7);
8 | z-index: 2008;
9 | }
10 |
11 | .wx_popup{
12 |
13 | position: fixed;
14 | background-color: #fff;
15 | top: 50%;
16 | left: 50%;
17 | -webkit-transform: translate3d(-50%, -50%, 0);
18 | -moz-transform: translate3d(-50%, -50%, 0);
19 | transform: translate3d(-50%, -50%, 0);
20 | -webkit-transition: .2s ease-out;
21 | -moz-transition: .2s ease-out;
22 | transition: .2s ease-out;
23 |
24 | }
25 | .wx_content_bottom{
26 |
27 | width: 100%;
28 | top: auto;
29 | bottom: 0;
30 | right: auto;
31 | left: 50%;
32 | -webkit-transform: translate3d(-50%, 0, 0);
33 | -moz-transform: translate3d(-50%, 0, 0);
34 | transform: translate3d(-50%, 0, 0);
35 | z-index: 2009;
36 |
37 | }
--------------------------------------------------------------------------------
/src/components/SearchProduct/index.js:
--------------------------------------------------------------------------------
1 | import React,{PureComponent} from 'react'
2 | import Styles from './index.less'
3 | import buttonimg from '../../assets/search_icon_2.png'
4 |
5 | class SearchProduct extends PureComponent{
6 |
7 |
8 |
9 |
10 |
11 | render(){
12 |
13 | const {text} = this.props;
14 |
15 | const bg ={
16 |
17 | backgroundImage: `url(${buttonimg})`,
18 |
19 |
20 | }
21 |
22 | return (
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | )
32 |
33 | }
34 | }
35 |
36 | export default SearchProduct;
--------------------------------------------------------------------------------
/src/components/Popup/index.js:
--------------------------------------------------------------------------------
1 | import React, {PureComponent} from 'react'
2 | import classNames from 'classnames'
3 | import Styles from './index.less'
4 | import HomeShopcar from '../../routes/homeShopcar'
5 |
6 |
7 | /**
8 | * 弹出层
9 | */
10 | class Popup extends PureComponent{
11 |
12 |
13 |
14 | render(){
15 |
16 |
17 | const {flag} = this.props;
18 |
19 | return(
20 |
21 |
22 |
23 |
24 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | )
40 |
41 |
42 | }
43 |
44 | }
45 |
46 | export default Popup;
--------------------------------------------------------------------------------
/src/layouts/MainLayout.js:
--------------------------------------------------------------------------------
1 | import React,{PureComponent} from 'react'
2 |
3 | import {Switch,Route,Redirect} from 'react-router-dom'
4 |
5 |
6 | import {getRouterData} from '../common/router'
7 |
8 | import WxTabBar from '../components/TabBar'
9 |
10 | export default class MainLayout extends PureComponent{
11 |
12 |
13 |
14 | render(){
15 | return(
16 |
17 |
18 |
19 |
20 | {
21 | getRouterData().map((item,index)=>(
22 |
23 |
24 |
25 | ))
26 | }
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | )
38 | }
39 |
40 |
41 | }
--------------------------------------------------------------------------------
/src/redux/login.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | import { setAuthority } from '../utils/authority';
3 |
4 | const initstate={
5 |
6 | status:undefined,
7 | submitting:false,
8 |
9 | }
10 |
11 | function commonlogin(state,type,payload){
12 |
13 | setAuthority(payload.currentAuthority);
14 |
15 | return {
16 | ...state,
17 | status:payload.status,
18 | type:payload.type,
19 | submitting:payload.status == 'error'?false:true,
20 | }
21 |
22 | }
23 |
24 |
25 | function login(state = initstate, {type,payload}) {
26 |
27 | switch (type) {
28 | case 'commonlogin':
29 | return commonlogin(state,type,payload)
30 | case 'tologin':
31 | return {
32 | ...state,
33 | status:payload.status,
34 | type:payload.type,
35 | submitting:false,
36 |
37 | }
38 | case 'mobilelogin':
39 |
40 | return state
41 | case 'loading':
42 | return {
43 | ...initstate,
44 | submitting:true
45 | }
46 | default:
47 | return state
48 | }
49 | }
50 |
51 | export default login;
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wx-mobile-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "amfe-flexible": "^2.2.1",
7 | "antd-mobile": "^2.1.8",
8 | "axios": "^0.18.0",
9 | "babel-polyfill": "^6.26.0",
10 | "classnames": "^2.2.5",
11 | "history": "^4.7.2",
12 | "mockjs": "^1.0.1-beta3",
13 | "react": "^16.2.0",
14 | "react-dom": "^16.2.0",
15 | "react-redux": "^5.0.7",
16 | "react-router-dom": "^4.2.2",
17 | "react-router-redux": "^5.0.0-alpha.9",
18 | "react-scripts": "1.1.1",
19 | "react-weui": "^1.1.3",
20 | "redux": "^3.7.2",
21 | "redux-saga": "^0.16.0",
22 | "weui": "1.1.1"
23 | },
24 | "scripts": {
25 | "start": "react-app-rewired start",
26 | "build": "react-app-rewired build",
27 | "test": "react-app-rewired test --env=jsdom"
28 | },
29 | "devDependencies": {
30 | "babel-plugin-import": "^1.6.7",
31 | "babel-plugin-transform-decorators-legacy": "^1.3.4",
32 | "less": "^2.7.3",
33 | "less-loader": "^4.0.5",
34 | "react-app-rewired": "^1.5.0"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/assets/list.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/ProductDetail/index.less:
--------------------------------------------------------------------------------
1 | .banner{
2 |
3 | }
4 | .pro_header{
5 |
6 | display: block;
7 | height: auto;
8 | padding: 10px 0 0;
9 | -webkit-tap-highlight-color: transparent;
10 | border-top: 1px solid #f2f2f2;
11 | background-color: #fff;
12 | margin-bottom: 10px;
13 | border-bottom-width: 0;
14 |
15 | .title{
16 | font-weight: normal;
17 | margin: 0;
18 | padding: 0 10px;
19 | font-size: 16px;
20 | line-height: 20px;
21 | text-align: left;
22 | overflow: hidden;
23 | text-overflow: ellipsis;
24 | display: -webkit-box;
25 | /* autoprefixer: off*/
26 | -webkit-line-clamp: 2;
27 | -webkit-box-orient: vertical;
28 | /* autoprefixer: on*/
29 |
30 | }
31 |
32 | }
33 | .pro_price{
34 |
35 | padding: 10px 10px 2px;
36 | text-align: left;
37 | }
38 | .pro_curr_price{
39 | color: #f44!important;
40 | span{
41 | font-size: 16px;
42 | }
43 | .price{
44 | display: inline-block;
45 | vertical-align: middle;
46 | font-size: 18px;
47 | }
48 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT LICENSE
2 |
3 | Copyright (c) 2016-present Alipay.com, https://www.manzj.net/
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/src/utils/utils.less:
--------------------------------------------------------------------------------
1 | .textOverflow() {
2 | overflow: hidden;
3 | text-overflow: ellipsis;
4 | word-break: break-all;
5 | white-space: nowrap;
6 | }
7 |
8 | .textOverflowMulti(@line: 3, @bg: #fff) {
9 | overflow: hidden;
10 | position: relative;
11 | line-height: 1.5em;
12 | max-height: @line * 1.5em;
13 | text-align: justify;
14 | margin-right: -1em;
15 | padding-right: 1em;
16 | &:before {
17 | background: @bg;
18 | content: '...';
19 | padding: 0 1px;
20 | position: absolute;
21 | right: 14px;
22 | bottom: 0;
23 | }
24 | &:after {
25 | background: white;
26 | content: '';
27 | margin-top: 0.2em;
28 | position: absolute;
29 | right: 14px;
30 | width: 1em;
31 | height: 1em;
32 | }
33 | }
34 |
35 | // mixins for clearfix
36 | // ------------------------
37 | .clearfix() {
38 | zoom: 1;
39 | &:before,
40 | &:after {
41 | content: " ";
42 | display: table;
43 | }
44 | &:after {
45 | clear: both;
46 | visibility: hidden;
47 | font-size: 0;
48 | height: 0;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/routes/productdetail.js:
--------------------------------------------------------------------------------
1 | import React,{PureComponent} from 'react'
2 | import {connect} from 'react-redux'
3 | import {history} from '../store'
4 | import {withRouter} from 'react-router-dom'
5 | import ProductDetailCom from '../components/ProductDetail'
6 |
7 |
8 |
9 | @connect(({productDetailRedux})=>(
10 | productDetailRedux
11 | ))
12 | class ProductDetail extends PureComponent{
13 |
14 |
15 | componentDidMount(){
16 |
17 | const {location} = this.props;
18 |
19 | if( undefined == location.search || "" == location.search){
20 |
21 | history.push("/home")
22 |
23 | }else{
24 |
25 | this.props.dispatch({
26 | type:'getProductdetailSaga',
27 | payload:{"id":this.props.location.search}
28 | })
29 |
30 | }
31 |
32 | console.log(this.props.location.search)
33 |
34 |
35 |
36 |
37 | }
38 |
39 | render(){
40 |
41 | return (
42 |
43 |
44 | )
45 |
46 |
47 | }
48 |
49 |
50 | }
51 |
52 | export default withRouter(ProductDetail)
53 |
--------------------------------------------------------------------------------
/src/layouts/BaseLayout.js:
--------------------------------------------------------------------------------
1 | import React ,{PureComponent} from 'react'
2 |
3 | import {Switch,Route,Redirect} from 'react-router-dom'
4 |
5 | import MainLayout from './MainLayout'
6 |
7 | import Styles from './BaseLayout.less'
8 |
9 | import ProductLayout from './ProductLayout'
10 |
11 |
12 |
13 |
14 |
15 |
16 | class BaseLayout extends PureComponent{
17 |
18 |
19 | constructor(props) {
20 | super(props);
21 | this.state = {
22 | selectedTab: 'blueTab',
23 | hidden: false,
24 | fullScreen: true,
25 | };
26 | }
27 |
28 |
29 |
30 | render(){
31 |
32 |
33 | const { match } = this.props;
34 |
35 |
36 | return (
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | )
51 |
52 |
53 |
54 | }
55 |
56 |
57 |
58 |
59 | }
60 |
61 | export default BaseLayout;
--------------------------------------------------------------------------------
/src/assets/home.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/common/menu.js:
--------------------------------------------------------------------------------
1 |
2 | import home1 from '../assets/home1.png'
3 | import home2 from '../assets/home2.png'
4 |
5 | import me1 from '../assets/me1.png'
6 | import me2 from '../assets/me2.png'
7 |
8 | import shopcar1 from '../assets/shopcar1.png'
9 | import shopcar2 from '../assets/shopcar2.png'
10 |
11 | import sort1 from '../assets/sort1.png'
12 | import sort2 from '../assets/sort2.png'
13 |
14 | /**
15 | * tabbar菜单
16 | */
17 |
18 | const menuData = [
19 |
20 | {
21 | name:'主页',
22 | key:'home',
23 | path:'/home',
24 | icon: home1,
25 | selectedIcon:home2,
26 | unselectedTintColor:"#949494",
27 | tintColor:"#33A3F4",
28 | },
29 | {
30 | name:'分类',
31 | key:'classify',
32 | path:'/home/classify',
33 | icon: sort1,
34 | selectedIcon:sort2,
35 | unselectedTintColor:"#949494",
36 | tintColor:"#33A3F4",
37 | },
38 | {
39 | name:'购物车',
40 | key:'shopcar',
41 | path:'/home/shopcar',
42 | icon: shopcar1,
43 | selectedIcon:shopcar2,
44 | unselectedTintColor:"#949494",
45 | tintColor:"#33A3F4",
46 | },
47 | {
48 | name:'我的',
49 | key:'me',
50 | path:'/home/me',
51 | icon: me1,
52 | selectedIcon:me2,
53 | unselectedTintColor:"#949494",
54 | tintColor:"#33A3F4",
55 | },
56 |
57 |
58 |
59 | ]
60 |
61 | export {
62 | menuData
63 | }
--------------------------------------------------------------------------------
/src/routes/home.js:
--------------------------------------------------------------------------------
1 | import React ,{PureComponent} from 'react'
2 |
3 | import classNames from 'classnames'
4 |
5 | import {connect} from 'react-redux'
6 |
7 | import Banner from '../routes/banner'
8 |
9 | import ShortCut from '../routes/shortcut'
10 |
11 | import SearchProduct from '../components/SearchProduct'
12 |
13 | import ImageAd from '../components/ImageAd'
14 |
15 | import Popup from '../components/Popup'
16 |
17 | import ad1 from '../assets/ad1.jpg'
18 |
19 | import {menuData} from '../common/menu'
20 |
21 | import ProductList from '../routes/productList'
22 | import { width } from 'window-size';
23 | import { inherits } from 'util';
24 |
25 | import Styles from '../index.css'
26 |
27 | import Footer from '../components/Footer'
28 |
29 |
30 | class Home extends PureComponent{
31 |
32 |
33 |
34 | render(){
35 |
36 | const {homePopup,dispatch} = this.props;
37 |
38 | return (
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | )
51 |
52 |
53 | }
54 |
55 |
56 | }
57 |
58 | export default connect(({globalRedux})=>{
59 | return {
60 | homePopup:globalRedux.homePopup,
61 | }
62 | })(Home)
63 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # wx-mobile-app
3 |
4 | []()
5 | []()
6 |
7 | 使用 [Create-React-App](https://github.com/facebook/create-react-app) 脚手架,集合了react-redux、react-router-redux、redux-saga、axios 等构建的开源微商城
8 |
9 |
10 | ## 效果gif
11 |
12 | 
13 |
14 | ## 项目结构
15 |
16 | ```
17 | - build 打包后的项目目录
18 | - public
19 | - favicon.ico
20 | - index.html 主页面
21 | - mainifest.json
22 | - src 源码文件夹
23 | - assets 资源文件夹
24 | - common
25 | - menu.js 底部Tabbar配置
26 | - router.js Tabbar切换对应的组件
27 | - components 组件文件夹
28 | ...
29 | - layouts 布局文件夹
30 | - BaseLayout.js 后台布局
31 | - BaseLayout.less
32 | - mock 模拟数据文件夹
33 | ......
34 | - redux
35 | - index.js 所有同步redux的入口
36 | ......
37 | - routes 容器组件文件夹
38 | ......
39 | - saga
40 | - index.js 异步redux的入口
41 | ......
42 | - services
43 | - api.js 请求入口
44 | - store
45 | - index.js
46 | - untils 辅助类
47 | .....
48 | index.js
49 | index.css
50 | - config-overrides.js webpack配置文件
51 | ......
52 | ```
53 |
54 |
55 | ## 安装
56 | 下载完成后,在项目根目录使用 `yarn install` , 推荐使用 `yarn` 安装。
57 | ## 启动
58 | 在根目录运行如下命令:
59 | `yarn start`
60 |
61 |
62 |
--------------------------------------------------------------------------------
/src/services/api.js:
--------------------------------------------------------------------------------
1 |
2 | import axios from 'axios'
3 | require('../mock/data')
4 |
5 |
6 | export async function getBanners(){
7 |
8 |
9 | return axios.get('/api/banner');
10 |
11 | }
12 |
13 | export async function getShortcutList(){
14 |
15 |
16 | return axios.get('/api/shortcut')
17 |
18 | }
19 |
20 | export async function getproduct_1(){
21 |
22 | return axios.get('/api/products_1')
23 |
24 |
25 | }
26 |
27 | export async function getHShopCar(){
28 | return axios.get('/api/getshopcar')
29 | }
30 |
31 |
32 | export async function getProductdetailById(param){
33 |
34 |
35 | return axios.get('/api/productdetail/').then(function(response){
36 |
37 |
38 | return response.data;
39 |
40 | })
41 |
42 | }
43 |
44 | export async function login(param) {
45 |
46 | const {username,password,type} = param;
47 |
48 |
49 | return axios.get('/api/login').then(function(response){
50 |
51 |
52 | if(username === 'admin' && password === '888888'){
53 |
54 | return {
55 | status:'ok',
56 | type:type,
57 | currentAuthority:'admin'
58 | };
59 |
60 | }else if(password === '123456' && username === 'user'){
61 |
62 | return {
63 | status: 'ok',
64 | type,
65 | currentAuthority: 'user'
66 | }
67 |
68 | }else{
69 |
70 | return {
71 | status:'error',
72 | type:type,
73 | }
74 |
75 | }
76 |
77 | })
78 | }
79 |
80 |
--------------------------------------------------------------------------------
/src/components/Stepper/index.less:
--------------------------------------------------------------------------------
1 | .wx_stepper{
2 |
3 | top: 7px;
4 | left: 4px;
5 | float: right;
6 | font-size: 0;
7 | }
8 | .wx_stepper__stepper{
9 |
10 | width: 40px;
11 | height: 30px;
12 | box-sizing: border-box;
13 | background-color: #fff;
14 | border: 1px solid #ccc;
15 | position: relative;
16 | padding: 5px;
17 | vertical-align: middle;
18 |
19 | }
20 |
21 |
22 |
23 | .wx_stepper__minus{
24 |
25 | border-radius: 2px 0 0 2px;
26 |
27 | }
28 | .wx_stepper__input{
29 |
30 | width: 33px;
31 | height: 26px;
32 | padding: 1px;
33 | border: 1px solid #ccc;
34 | border-width: 1px 0;
35 | border-radius: 0;
36 | -webkit-box-sizing: content-box;
37 | -moz-box-sizing: content-box;
38 | box-sizing: content-box;
39 | color: #666;
40 | font-size: 14px;
41 | vertical-align: middle;
42 | text-align: center;
43 | -webkit-appearance: none;
44 |
45 |
46 | }
47 | .wx_stepper__stepper::after,.wx_stepper__stepper::before{
48 |
49 | content: '';
50 | position: absolute;
51 | margin: auto;
52 | top: 0;
53 | left: 0;
54 | right: 0;
55 | bottom: 0;
56 | background-color: #6c6c6c;
57 | width: 9px;
58 | height: 1px;
59 | }
60 |
61 |
62 | .wx_stepper__stepper_1::before {
63 | width: 9px;
64 | height: 1px;
65 | }
66 | .wx_stepper__stepper_2::after{
67 |
68 | width: 1px;
69 | height: 9px;
70 | }
71 | .wx_stepper__stepper__minus_disabled{
72 |
73 | background-color: #f8f8f8;
74 | border-color: #e8e8e8 #ccc #e8e8e8 #e8e8e8;
75 | }
76 |
77 | .wx_stepper__plus{
78 |
79 | border-radius: 0 2px 2px 0;
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | 微信商城
11 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/components/SearchProduct/index.less:
--------------------------------------------------------------------------------
1 |
2 |
3 | .custom_search{
4 |
5 | background-color: #f8f8f8;
6 | padding: 10px;
7 |
8 | .form{
9 |
10 | margin: 0;
11 | position: relative;
12 | background: #fff;
13 | border-radius: 4px;
14 | border: 0 none;
15 | overflow: hidden;
16 |
17 | .custom_search_input{
18 |
19 | margin: 0;
20 | padding: 8px 8px 8px 32px;
21 | -webkit-appearance: none;
22 | -moz-appearance: none;
23 | line-height: 20px;
24 | height: 35px;
25 | width: 100%;
26 | -webkit-box-sizing: border-box;
27 | -moz-box-sizing: border-box;
28 | box-sizing: border-box;
29 | border: 1px solid #e5e5e5;
30 | border-radius: 4px;
31 | outline: none;
32 | font-size: 14px;
33 | -webkit-box-shadow: 0 0 0 0;
34 | box-shadow: 0 0 0 0;
35 |
36 | }
37 | .custom_search_button{
38 |
39 | position: absolute;
40 | top: 1px;
41 | left: 1px;
42 | border: 0px none;
43 | border-radius: 4px;
44 | height: 33px;
45 | line-height: 35px;
46 | width: 22px;
47 | padding: 0;
48 | color: #fff;
49 | font-size: 14px;
50 | text-indent: -9999px;
51 | background-size: 14px 14px;
52 | border-radius: 5px 0 0 5px;
53 | background-color: #fff;
54 | background-position: right center;
55 | background-repeat: no-repeat;
56 |
57 | }
58 |
59 | }
60 |
61 |
62 | }
--------------------------------------------------------------------------------
/src/assets/scar.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/Stepper/index.js:
--------------------------------------------------------------------------------
1 | import React , {PureComponent} from 'react'
2 | import Styles from './index.less'
3 | import classNames from 'classnames'
4 |
5 |
6 | /**
7 | * 步进器
8 | */
9 | class Stepper extends PureComponent{
10 |
11 | state={
12 | currValue:1,
13 | }
14 |
15 | subtract=()=>{
16 |
17 | if(this.state.currValue != 1){
18 |
19 | this.props.onnums(this.state.currValue -1)
20 |
21 | this.setState({
22 | currValue:this.state.currValue - 1,
23 | })
24 |
25 | }
26 |
27 |
28 | }
29 |
30 |
31 |
32 | addNums=()=>{
33 |
34 | this.props.onnums(this.state.currValue +1)
35 |
36 | this.setState({
37 | currValue:this.state.currValue +1,
38 | })
39 |
40 |
41 | }
42 |
43 |
44 | render(){
45 |
46 |
47 | return (
48 |
49 |
50 |
54 |
55 |
56 |
57 | )
58 | }
59 |
60 | }
61 |
62 | export default Stepper;
--------------------------------------------------------------------------------
/src/routes/banner.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import { Carousel } from 'antd-mobile';
3 | import { connect } from 'react-redux'
4 | import { Swiper, Flex, FlexItem } from 'react-weui'
5 |
6 |
7 | /**
8 | * banner
9 | */
10 | class Banner extends PureComponent {
11 |
12 |
13 |
14 | state = {
15 | data:[1,2,3,5,6]
16 | }
17 |
18 |
19 | componentDidMount() {
20 | // simulate img loading
21 |
22 | this.props.dispatch({
23 | type: 'getBannerListSaga',
24 | });
25 |
26 |
27 |
28 | }
29 |
30 | componentWillReceiveProps(nextprops){
31 |
32 | this.setState({
33 | data:nextprops.data,
34 | })
35 |
36 | }
37 |
38 |
39 | render() {
40 |
41 | console.log(this.state.data);
42 |
43 | return (
44 |
45 |
50 | {this.state.data.map(val => (
51 |
56 |
{
61 | window.dispatchEvent(new Event('resize'));
62 | }}
63 | />
64 |
65 | ))}
66 |
67 |
68 |
69 |
70 | )
71 | }
72 |
73 |
74 |
75 | }
76 |
77 | export default connect(({ bannerRedux }) => {
78 |
79 |
80 | return bannerRedux
81 |
82 |
83 |
84 | })(Banner);
--------------------------------------------------------------------------------
/src/components/ProductDetail/index.js:
--------------------------------------------------------------------------------
1 | import React,{PureComponent} from 'react'
2 | import { Carousel } from 'antd-mobile'
3 | import Styles from './index.less'
4 |
5 |
6 | /**
7 | * 商品详情
8 | */
9 | export default class ProductDetailCom extends PureComponent{
10 |
11 | state={
12 | imgurls:[1,2,3,4,5,6,7,9,10]
13 | }
14 |
15 |
16 | componentWillReceiveProps(nextProps){
17 |
18 |
19 | this.setState({
20 | imgurls:nextProps.data.banner
21 | })
22 |
23 | }
24 |
25 | render(){
26 |
27 | const {data} = this.props;
28 |
29 | console.log(data)
30 |
31 | return (
32 |
33 |
34 |
35 |
41 |
42 | {
43 | this.state.imgurls.map((item,index)=>(
44 |
45 |
{
48 | window.dispatchEvent(new Event('resize'));
49 | }}
50 | />
51 | ))
52 |
53 |
54 | }
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | {data.title}
65 |
66 |
67 |
68 |
69 |
70 | ¥
71 | {data.price}
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | )
81 | }
82 |
83 | }
--------------------------------------------------------------------------------
/src/components/TabBar/index.js:
--------------------------------------------------------------------------------
1 | import React , { PureComponent } from 'react'
2 |
3 | import classNames from 'classnames'
4 |
5 | import {Link} from 'react-router-dom'
6 |
7 | import {menuData} from '../../common/menu'
8 |
9 | import 'antd-mobile/lib/tab-bar/style/index.css';
10 | import 'antd-mobile/lib/badge/style/index.css'
11 |
12 | import Styles from './index.less'
13 |
14 | import home1 from '../../assets/home1.png'
15 |
16 |
17 |
18 | class WxTabBar extends PureComponent{
19 |
20 |
21 | state={
22 |
23 | selectedTab:'home'
24 |
25 | }
26 |
27 | changeTab =(tab)=>{
28 |
29 | this.setState({
30 | selectedTab:tab,
31 | })
32 |
33 |
34 | }
35 |
36 |
37 | render(){
38 |
39 |
40 | return (
41 |
42 |
43 |
46 |
47 |
48 |
49 |
50 | {
51 |
52 | menuData.map(item =>(
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | {
65 |
66 | this.state.selectedTab == item.key?
67 |
68 |
69 | :
70 |
71 |
72 | }
73 |
74 |
75 |
76 | {/* 1 */}
77 |
78 |
79 |
80 |
81 |
82 |
83 |
{item.name}
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | ))
92 |
93 | }
94 |
95 |
96 |
97 |
98 | )
99 |
100 | }
101 |
102 |
103 |
104 | }
105 |
106 | export default WxTabBar;
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/utils/request.js:
--------------------------------------------------------------------------------
1 | import { notification } from 'antd';
2 | import axios from 'axios'
3 | import store from '../store'
4 | import {push} from 'react-router-redux'
5 |
6 |
7 | const codeMessage = {
8 | 200: '服务器成功返回请求的数据',
9 | 201: '新建或修改数据成功。',
10 | 202: '一个请求已经进入后台排队(异步任务)',
11 | 204: '删除数据成功。',
12 | 400: '发出的请求有错误,服务器没有进行新建或修改数据,的操作。',
13 | 401: '用户没有权限(令牌、用户名、密码错误)。',
14 | 403: '用户得到授权,但是访问是被禁止的。',
15 | 404: '发出的请求针对的是不存在的记录,服务器没有进行操作',
16 | 406: '请求的格式不可得。',
17 | 410: '请求的资源被永久删除,且不会再得到的。',
18 | 422: '当创建一个对象时,发生一个验证错误。',
19 | 500: '服务器发生错误,请检查服务器',
20 | 502: '网关错误',
21 | 503: '服务不可用,服务器暂时过载或维护',
22 | 504: '网关超时',
23 | };
24 |
25 | function checkStatus(response) {
26 | if (response.status >= 200 && response.status < 300) {
27 | return response;
28 | }
29 | const errortext = codeMessage[response.status] || response.statusText;
30 | notification.error({
31 | message: `请求错误 ${response.status}: ${response.url}`,
32 | description: errortext,
33 | });
34 | const error = new Error(errortext);
35 | error.name = response.status;
36 | error.response = response;
37 | throw error;
38 | }
39 |
40 |
41 |
42 | /**
43 | * Requests a URL, returning a promise.
44 | *
45 | * @param {string} url The URL we want to request
46 | * @param {object} [options] The options we want to pass to "fetch"
47 | * @return {object} An object containing either "data" or "err"
48 | */
49 | export default function request(url, options) {
50 | const defaultOptions = {
51 | credentials: 'include',
52 | };
53 | const newOptions = { ...defaultOptions, ...options };
54 | if (newOptions.method === 'POST' || newOptions.method === 'PUT') {
55 | newOptions.headers = {
56 | Accept: 'application/json',
57 | 'Content-Type': 'application/json; charset=utf-8',
58 | ...newOptions.headers,
59 | };
60 | newOptions.body = JSON.stringify(newOptions.body);
61 | }
62 |
63 | return axios(url, newOptions)
64 | .then(checkStatus)
65 | .then((response) => {
66 | if (newOptions.method === 'DELETE' || response.status === 204) {
67 | return response.text();
68 | }
69 | return response.json();
70 | })
71 | .catch((e) => {
72 | const { dispatch } = store;
73 | const status = e.name;
74 | if (status === 401) {
75 | dispatch({
76 | type: 'login/logout',
77 | });
78 | return;
79 | }
80 | if (status === 403) {
81 | dispatch(push('/exception/403'));
82 | return;
83 | }
84 | if (status <= 504 && status >= 500) {
85 | dispatch(push('/exception/500'));
86 | return;
87 | }
88 | if (status >= 404 && status < 422) {
89 | dispatch(push('/exception/404'));
90 | }
91 | });
92 | }
--------------------------------------------------------------------------------
/src/components/ProductList/index.js:
--------------------------------------------------------------------------------
1 | import React,{ PureComponent } from 'react'
2 |
3 | import {history} from '../../store'
4 |
5 | import {Link} from 'react-router-dom'
6 |
7 | import Styles from './index.less'
8 |
9 | import Popup from '../../components/Popup'
10 |
11 | import scar from '../../assets/scar.svg'
12 |
13 | class ProductList extends PureComponent{
14 |
15 |
16 |
17 | state={
18 | hidden:false,
19 | }
20 |
21 |
22 | clickShopCar = (index,event)=>{
23 |
24 |
25 | event.stopPropagation();
26 |
27 | this.props.dispatch({
28 | type:'openPopup'
29 | })
30 |
31 | this.props.dispatch({
32 | type:'getHomeShopcarSaga'
33 | })
34 |
35 | var _body = document.getElementsByTagName("body")
36 |
37 | _body[0].style.overflow="hidden";
38 |
39 |
40 | }
41 |
42 | // 跳转商品详情
43 | gotoProductDetail = (id)=>{
44 |
45 | history.push({
46 | pathname:"/product",
47 | search:"?id="+id,
48 | })
49 |
50 |
51 |
52 |
53 |
54 | }
55 |
56 |
57 |
58 | render(){
59 |
60 | const {data} = this.props;
61 |
62 |
63 | return (
64 |
65 | // 商品列表
66 |
67 |
68 |
69 |
70 | {
71 | data.map((item)=>(
72 |
73 | -
74 |
75 |
76 |
77 |

78 |
79 |
82 |
83 |
84 |
¥ {item.price}
85 |
86 |
87 |

88 |
89 |
90 |
91 |
92 |
93 |
94 | ))
95 |
96 |
97 | }
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 | )
106 |
107 |
108 | }
109 |
110 |
111 |
112 |
113 | }
114 |
115 | export default ProductList;
--------------------------------------------------------------------------------
/src/mock/data.js:
--------------------------------------------------------------------------------
1 | import Mock from 'mockjs'
2 | import banner1 from '../assets/banner1.gif'
3 | import banner2 from '../assets/banner2.gif'
4 | import banner3 from '../assets/banner3.gif'
5 |
6 | import sc1 from '../assets/sc1.png'
7 | import sc2 from '../assets/sc2.png'
8 | import sc3 from '../assets/sc3.png'
9 | import sc4 from '../assets/sc4.png'
10 |
11 |
12 | import p1 from '../assets/p1.jpg'
13 | import p2 from '../assets/p2.jpg'
14 | import p3 from '../assets/p3.png'
15 | import p4 from '../assets/p4.jpg'
16 | import p5 from '../assets/p5.jpg'
17 |
18 | import pd1 from '../assets/pd1.jpg'
19 | import pd2 from '../assets/pd2.jpg'
20 | import pd3 from '../assets/pd3.jpg'
21 | import pd4 from '../assets/pd4.jpg'
22 |
23 |
24 | const Random = Mock.Random
25 |
26 |
27 | const banner= Mock.mock('/api/banner',[
28 |
29 | banner1,
30 | banner2,
31 | banner3,
32 |
33 | ])
34 |
35 | const shortcut = Mock.mock('/api/shortcut',[
36 |
37 | {
38 | url:"",
39 | img:sc1,
40 | },
41 | {
42 | url:"",
43 | img:sc2,
44 | },
45 | {
46 | url:"",
47 | img:sc3,
48 | },
49 | {
50 | url:"",
51 | img:sc4,
52 | },
53 |
54 |
55 | ])
56 |
57 | const products_1 = Mock.mock('/api/products_1',[
58 |
59 | {
60 | "id|1000-2000":1,
61 | url:'',
62 | imgsrc:p1,
63 | title:'【买2份减10元】海南贵妃芒果 香甜多汁 皮薄核小 原产地芒果新鲜采摘 中大果净重5斤装京东包邮',
64 | "price|1-100.1-2":1,
65 | },
66 | {
67 | "id|1000-2000":1,
68 | url:'',
69 | imgsrc:p2,
70 | title:'熊猫精选 黑芝麻蜜丸 手工现做 柴火慢蒸 九蒸九晒黑芝麻蜜丸12丸/罐/108克 买三罐送一罐 四罐1疗程',
71 | "price|1-100.1-2":1,
72 | },
73 | {
74 | "id|1000-2000":1,
75 | url:'',
76 | imgsrc:p3,
77 | title:'熊猫精选 陕北米脂小米 农家月子米 养胃养颜 现磨现发 5斤装包邮',
78 | "price|1-100.1-2":1,
79 | },
80 | {
81 | "id|1000-2000":1,
82 | url:'',
83 | imgsrc:p4,
84 | title:'熊猫有礼 阿九提冰川牧场纯牛奶 原生牧场 军工品质 滴滴鲜醇(200ml*16袋)',
85 | "price|1-100.1-2":1,
86 | },
87 | {
88 | "id|1000-2000":1,
89 | url:'',
90 | imgsrc:p5,
91 | title:'熊猫有礼 燕麦代餐酸奶 醇香口感,奇妙风味,能量酸奶(180g*12杯)',
92 | "price|1-100.1-2":1,
93 | },
94 | {
95 | "id|1000-2000":1,
96 | url:'',
97 | imgsrc:p4,
98 | title:'熊猫有礼 阿九提冰川牧场纯牛奶 原生牧场 军工品质 滴滴鲜醇(200ml*16袋)',
99 | "price|1-100.1-2":1,
100 | },
101 |
102 |
103 | ])
104 |
105 |
106 | const addShopcar = Mock.mock('/api/getshopcar',{
107 |
108 | "id|1000-2000":1,
109 | "title":'熊猫有礼 健康无添加 小孩可以放心吃的猪肉丝 香酥猪肉松&黄金猪肉丝',
110 | "priceRange":"20.00-30.00",
111 | "imgsrc":p1,
112 | "data|3":[
113 | {
114 | "id|3000-4000":1,
115 | "sepc|1":[
116 | "黄金猪肉丝98g+香酥猪肉松108g",
117 | "黄金猪肉丝98g",
118 | "香酥猪肉松108g"
119 | ],
120 | "price|20-30.2":1,
121 | "count|1000-2000":1,
122 | }
123 |
124 | ],
125 |
126 |
127 |
128 |
129 | })
130 |
131 | const productdetail = Mock.mock('/api/productdetail/',{
132 |
133 | "id":10004,
134 | "banner":[pd1,pd2,pd3,pd4],
135 | "title":"熊猫精选 黑芝麻蜜丸 手工现做 柴火慢蒸 九蒸九晒黑芝麻蜜丸12丸/罐/108克 买三罐送一罐 四罐1疗程",
136 | "price":"49.90",
137 | "carriage":{
138 | "carriage_id":1,
139 | "carriage_text":'免运费',
140 | "carriage_price":'0.00'
141 | },
142 | });
143 |
144 | export {
145 | banner,
146 | shortcut,
147 | products_1,
148 | addShopcar,
149 | productdetail,
150 | }
--------------------------------------------------------------------------------
/src/components/ProductList/index.less:
--------------------------------------------------------------------------------
1 |
2 | .list_warper{
3 |
4 | overflow: hidden;
5 | padding: 0 7px;
6 |
7 | ul{
8 | list-style: none;
9 | padding-left: 0px;
10 | margin-top: 0px;
11 | display: flex;
12 | flex-direction: row;
13 | justify-content: flex-start;
14 | flex-wrap: wrap;
15 | li{
16 | width: 50%;
17 | .pwarper{
18 | position: relative;
19 | display: block;
20 | margin: 5px 3px;
21 | background-color: #fff;
22 | color: #333;
23 | box-sizing: border-box;
24 |
25 | .price_warper{
26 | display: flex;
27 | flex-direction: row;
28 | justify-content: flex-start;
29 | align-items: center;
30 | padding-bottom: 10px;
31 | p{
32 | flex: 1;
33 | margin: 0px;
34 | font-size: 16px;
35 | color: #f44;
36 | font-weight: 400;
37 | font-stretch: 100%;
38 | padding-left: 10px;
39 | }
40 |
41 | .shop_cart{
42 |
43 | flex: 1;
44 | display: flex;
45 | flex-direction: row;
46 | justify-content: flex-end;
47 | padding-right: 10px;
48 | .cap_goods_list__buy_btn{
49 |
50 | width: 24px;
51 | height: 24px;
52 | color: #f44;
53 | background-color: #fff;
54 | -webkit-box-sizing: border-box;
55 | -moz-box-sizing: border-box;
56 | box-sizing: border-box;
57 | text-align: center;
58 | font-size: 12px;
59 | cursor: pointer;
60 | border: 1px solid #f44;
61 | border-radius: 50%;
62 | display: flex;
63 | flex-direction: column;
64 | justify-content: center;
65 | align-items: center;
66 |
67 | .scimg{
68 |
69 | width: 78%;
70 |
71 | }
72 | }
73 |
74 | }
75 |
76 | }
77 | .title_warper{
78 |
79 | position: relative;
80 | overflow: hidden;
81 | padding: 10px;
82 | cursor: pointer;
83 | width: inherit;
84 | p{
85 | height: 36px;
86 | overflow: hidden;
87 | text-overflow: ellipsis;
88 | display: -webkit-box;
89 | font-size: 14px;
90 | line-height: 18px;
91 | text-align: left;
92 | font-weight:none;
93 | margin: 0px;
94 | -webkit-line-clamp: 2;
95 | /* autoprefixer: off*/
96 | -webkit-box-orient: vertical;
97 | /* autoprefixer: on*/
98 | }
99 |
100 | }
101 | }
102 |
103 | img{
104 | width: 100%;
105 | height: auto;
106 | }
107 |
108 | }
109 |
110 | }
111 |
112 |
113 |
114 | }
--------------------------------------------------------------------------------
/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register() {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (isLocalhost) {
36 | // This is running on localhost. Lets check if a service worker still exists or not.
37 | checkValidServiceWorker(swUrl);
38 |
39 | // Add some additional logging to localhost, pointing developers to the
40 | // service worker/PWA documentation.
41 | navigator.serviceWorker.ready.then(() => {
42 | console.log(
43 | 'This web app is being served cache-first by a service ' +
44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ'
45 | );
46 | });
47 | } else {
48 | // Is not local host. Just register service worker
49 | registerValidSW(swUrl);
50 | }
51 | });
52 | }
53 | }
54 |
55 | function registerValidSW(swUrl) {
56 | navigator.serviceWorker
57 | .register(swUrl)
58 | .then(registration => {
59 | registration.onupdatefound = () => {
60 | const installingWorker = registration.installing;
61 | installingWorker.onstatechange = () => {
62 | if (installingWorker.state === 'installed') {
63 | if (navigator.serviceWorker.controller) {
64 | // At this point, the old content will have been purged and
65 | // the fresh content will have been added to the cache.
66 | // It's the perfect time to display a "New content is
67 | // available; please refresh." message in your web app.
68 | console.log('New content is available; please refresh.');
69 | } else {
70 | // At this point, everything has been precached.
71 | // It's the perfect time to display a
72 | // "Content is cached for offline use." message.
73 | console.log('Content is cached for offline use.');
74 | }
75 | }
76 | };
77 | };
78 | })
79 | .catch(error => {
80 | console.error('Error during service worker registration:', error);
81 | });
82 | }
83 |
84 | function checkValidServiceWorker(swUrl) {
85 | // Check if the service worker can be found. If it can't reload the page.
86 | fetch(swUrl)
87 | .then(response => {
88 | // Ensure service worker exists, and that we really are getting a JS file.
89 | if (
90 | response.status === 404 ||
91 | response.headers.get('content-type').indexOf('javascript') === -1
92 | ) {
93 | // No service worker found. Probably a different app. Reload the page.
94 | navigator.serviceWorker.ready.then(registration => {
95 | registration.unregister().then(() => {
96 | window.location.reload();
97 | });
98 | });
99 | } else {
100 | // Service worker found. Proceed as normal.
101 | registerValidSW(swUrl);
102 | }
103 | })
104 | .catch(() => {
105 | console.log(
106 | 'No internet connection found. App is running in offline mode.'
107 | );
108 | });
109 | }
110 |
111 | export function unregister() {
112 | if ('serviceWorker' in navigator) {
113 | navigator.serviceWorker.ready.then(registration => {
114 | registration.unregister();
115 | });
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/components/HomeShopCar/index.less:
--------------------------------------------------------------------------------
1 | .hsc_container{
2 |
3 | background: #f8f8f8;
4 | }
5 | .hac_layout{
6 |
7 | background: #fff;
8 | }
9 | .hac_header{
10 |
11 | margin-left: 15px;
12 | position: relative;
13 | }
14 | .hairline::after{
15 | content: '';
16 | position: absolute;
17 | top: 0;
18 | left: 0;
19 | width: 200%;
20 | height: 200%;
21 | -webkit-transform: scale(0.5);
22 | -moz-transform: scale(0.5);
23 | -ms-transform: scale(0.5);
24 | transform: scale(0.5);
25 | -webkit-transform-origin: 0 0;
26 | -moz-transform-origin: 0 0;
27 | -ms-transform-origin: 0 0;
28 | transform-origin: 0 0;
29 | pointer-events: none;
30 | -webkit-box-sizing: border-box;
31 | -moz-box-sizing: border-box;
32 | box-sizing: border-box;
33 | border: 0 solid #e5e5e5;
34 | border-bottom-width: 1px;
35 | }
36 |
37 | .hac_header_img_warper{
38 |
39 | position: relative;
40 | float: left;
41 | margin-top: -10px;
42 | width: 80px;
43 | height: 80px;
44 | background: #f8f8f8;
45 | border-radius: 2px;
46 |
47 | .hac_header_img{
48 |
49 | position: absolute;
50 | margin: auto;
51 | top: 0;
52 | left: 0;
53 | right: 0;
54 | bottom: 0;
55 | max-width: 100%;
56 | max-height: 100%;
57 | }
58 | }
59 | .hac_header_info{
60 |
61 | padding: 10px 60px 10px 10px;
62 | min-height: 82px;
63 | overflow: hidden;
64 | box-sizing: border-box;
65 |
66 | }
67 |
68 |
69 | .hac_header_info_title{
70 |
71 | font-size: 12px;
72 | overflow: hidden;
73 | white-space: nowrap;
74 | text-overflow: ellipsis;
75 | color: #333;
76 | }
77 | .hac_header_info_price{
78 |
79 | color: #f44;
80 | margin-top: 10px;
81 | font-size: 16px;
82 | vertical-align: middle;
83 |
84 | .price_symbol{
85 |
86 | color: #f44;
87 | font-size: 14px;
88 | vertical-align: middle;
89 | }
90 | .price_number{
91 |
92 |
93 | }
94 | }
95 | .close_icon{
96 |
97 | position: absolute;
98 | top: 0;
99 | right: 0;
100 | width: 44px;
101 | height: 44px;
102 | background-size: 22px 22px;
103 | background-repeat: no-repeat;
104 | background-position: 7px 10px;
105 | cursor: pointer;
106 |
107 | }
108 |
109 | .hac_body{
110 |
111 | max-height: 350px;
112 | overflow-y: scroll;
113 | -webkit-overflow-scrolling: touch;
114 |
115 | .group_container{
116 |
117 | margin-left: 15px;
118 | padding: 12px 0 2px;
119 | position: relative;
120 |
121 | .row_group{
122 |
123 | margin: 0 15px 10px 0;
124 | }
125 | .row_group:last-child {
126 | margin-bottom: 0;
127 | }
128 | .row_group_item{
129 |
130 | .row_group_item_title{
131 | font-size: 14px;
132 | padding-bottom: 10px;
133 | }
134 |
135 | .row_group_item_gg{
136 |
137 | .row_group_item_gg_i{
138 | display: inline-block;
139 | padding: 5px 9px;
140 | margin: 0 10px 10px 0;
141 | height: 28px;
142 | min-width: 52px;
143 | line-height: 16px;
144 | font-size: 12px;
145 | color: #333;
146 | text-align: center;
147 | border: 1px solid #999;
148 | border-radius: 3px;
149 | box-sizing: border-box;
150 | }
151 | .active{
152 | background-color: #f44;
153 | border-color: #f44;
154 | color: #fff;
155 | }
156 | }
157 |
158 | }
159 |
160 |
161 | }
162 |
163 | .stepper_stock{
164 |
165 | padding: 12px 0;
166 | margin-left: 15px;
167 |
168 | .stepper_container{
169 | height: 30px;
170 | margin-right: 20px;
171 | }
172 | .stock_count{
173 | display: inline-block;
174 | margin-right: 10px;
175 | color: #999;
176 | font-size: 12px;
177 | }
178 | .stepper_title{
179 |
180 | float: left;
181 | line-height: 30px;
182 | font-size: 14px;
183 |
184 | }
185 |
186 | }
187 |
188 | .hairline_top_bottom{
189 |
190 | position: relative;
191 | border-width: 1px 0;
192 |
193 | }
194 | }
195 | .wx_actions{
196 |
197 | display: flex;
198 | margin-top: 10px;
199 | }
200 | .van_button{
201 | width: 100%;
202 | height: 50px;
203 | line-height: 50px;
204 | border: 0;
205 | border-radius: 0;
206 | font-size: 16px;
207 | color: #fff;
208 | background-color: #f85;
209 | }
210 | .van_button_default{
211 | background: #f85;
212 | color: #fff;
213 | }
214 | .van_button_primary{
215 | background: #f44;
216 | color: #fff;
217 |
218 | }
--------------------------------------------------------------------------------
/src/utils/utils.js:
--------------------------------------------------------------------------------
1 | import moment from 'moment';
2 |
3 | export function fixedZero(val) {
4 | return val * 1 < 10 ? `0${val}` : val;
5 | }
6 |
7 | export function getTimeDistance(type) {
8 | const now = new Date();
9 | const oneDay = 1000 * 60 * 60 * 24;
10 |
11 | if (type === 'today') {
12 | now.setHours(0);
13 | now.setMinutes(0);
14 | now.setSeconds(0);
15 | return [moment(now), moment(now.getTime() + (oneDay - 1000))];
16 | }
17 |
18 | if (type === 'week') {
19 | let day = now.getDay();
20 | now.setHours(0);
21 | now.setMinutes(0);
22 | now.setSeconds(0);
23 |
24 | if (day === 0) {
25 | day = 6;
26 | } else {
27 | day -= 1;
28 | }
29 |
30 | const beginTime = now.getTime() - (day * oneDay);
31 |
32 | return [moment(beginTime), moment(beginTime + ((7 * oneDay) - 1000))];
33 | }
34 |
35 | if (type === 'month') {
36 | const year = now.getFullYear();
37 | const month = now.getMonth();
38 | const nextDate = moment(now).add(1, 'months');
39 | const nextYear = nextDate.year();
40 | const nextMonth = nextDate.month();
41 |
42 | return [moment(`${year}-${fixedZero(month + 1)}-01 00:00:00`), moment(moment(`${nextYear}-${fixedZero(nextMonth + 1)}-01 00:00:00`).valueOf() - 1000)];
43 | }
44 |
45 | if (type === 'year') {
46 | const year = now.getFullYear();
47 |
48 | return [moment(`${year}-01-01 00:00:00`), moment(`${year}-12-31 23:59:59`)];
49 | }
50 | }
51 |
52 | export function getPlainNode(nodeList, parentPath = '') {
53 | const arr = [];
54 | nodeList.forEach((node) => {
55 | const item = node;
56 | item.path = `${parentPath}/${item.path || ''}`.replace(/\/+/g, '/');
57 | item.exact = true;
58 | if (item.children && !item.component) {
59 | arr.push(...getPlainNode(item.children, item.path));
60 | } else {
61 | if (item.children && item.component) {
62 | item.exact = false;
63 | }
64 | arr.push(item);
65 | }
66 | });
67 | return arr;
68 | }
69 |
70 | export function digitUppercase(n) {
71 | const fraction = ['角', '分'];
72 | const digit = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'];
73 | const unit = [
74 | ['元', '万', '亿'],
75 | ['', '拾', '佰', '仟'],
76 | ];
77 | let num = Math.abs(n);
78 | let s = '';
79 | fraction.forEach((item, index) => {
80 | s += (digit[Math.floor(num * 10 * (10 ** index)) % 10] + item).replace(/零./, '');
81 | });
82 | s = s || '整';
83 | num = Math.floor(num);
84 | for (let i = 0; i < unit[0].length && num > 0; i += 1) {
85 | let p = '';
86 | for (let j = 0; j < unit[1].length && num > 0; j += 1) {
87 | p = digit[num % 10] + unit[1][j] + p;
88 | num = Math.floor(num / 10);
89 | }
90 | s = p.replace(/(零.)*零$/, '').replace(/^$/, '零') + unit[0][i] + s;
91 | }
92 |
93 | return s.replace(/(零.)*零元/, '元').replace(/(零.)+/g, '零').replace(/^整$/, '零元整');
94 | }
95 |
96 |
97 | function getRelation(str1, str2) {
98 | if (str1 === str2) {
99 | console.warn('Two path are equal!'); // eslint-disable-line
100 | }
101 | const arr1 = str1.split('/');
102 | const arr2 = str2.split('/');
103 | if (arr2.every((item, index) => item === arr1[index])) {
104 | return 1;
105 | } else if (arr1.every((item, index) => item === arr2[index])) {
106 | return 2;
107 | }
108 | return 3;
109 | }
110 |
111 | function getRenderArr(routes) {
112 | let renderArr = [];
113 | renderArr.push(routes[0]);
114 | for (let i = 1; i < routes.length; i += 1) {
115 | let isAdd = false;
116 | // 是否包含
117 | isAdd = renderArr.every(item => getRelation(item, routes[i]) === 3);
118 | // 去重
119 | renderArr = renderArr.filter(item => getRelation(item, routes[i]) !== 1);
120 | if (isAdd) {
121 | renderArr.push(routes[i]);
122 | }
123 | }
124 | return renderArr;
125 | }
126 |
127 | /**
128 | * Get router routing configuration
129 | * { path:{name,...param}}=>Array<{name,path ...param}>
130 | * @param {string} path
131 | * @param {routerData} routerData
132 | */
133 | export function getRoutes(path, routerData) {
134 | let routes = Object.keys(routerData).filter(routePath =>
135 | routePath.indexOf(path) === 0 && routePath !== path);
136 | // Replace path to '' eg. path='user' /user/name => name
137 | routes = routes.map(item => item.replace(path, ''));
138 | // Get the route to be rendered to remove the deep rendering
139 | const renderArr = getRenderArr(routes);
140 | // Conversion and stitching parameters
141 | const renderRoutes = renderArr.map((item) => {
142 | const exact = !routes.some(route => route !== item && getRelation(route, item) === 1);
143 | return {
144 | ...routerData[`${path}${item}`],
145 | key: `${path}${item}`,
146 | path: `${path}${item}`,
147 | exact,
148 | };
149 | });
150 | return renderRoutes;
151 | }
152 |
153 |
154 | /* eslint no-useless-escape:0 */
155 | const reg = /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)$/g;
156 |
157 | export function isUrl(path) {
158 | return reg.test(path);
159 | }
160 |
--------------------------------------------------------------------------------
/config-overrides.js:
--------------------------------------------------------------------------------
1 | const { injectBabelPlugin, getLoader } = require('react-app-rewired');
2 |
3 | const fileLoaderMatcher = function (rule) {
4 | return rule.loader && rule.loader.indexOf(`file-loader`) != -1;
5 | }
6 |
7 | module.exports = {
8 |
9 | webpack: function override(config, env) {
10 |
11 | config = injectBabelPlugin('transform-decorators-legacy',config)
12 |
13 | // babel-plugin-import
14 | config = injectBabelPlugin(['import',
15 | {
16 | "libraryName": "antd-mobile",
17 | "libraryDirectory": "lib", // default: lib
18 | "style": true
19 | },
20 | ], config);
21 |
22 | // customize theme
23 | config.module.rules[1].oneOf.unshift(
24 | {
25 | test: /\.less$/,
26 | use: [
27 | require.resolve('style-loader'),
28 | require.resolve('css-loader'),
29 | {
30 | loader: require.resolve('postcss-loader'),
31 | options: {
32 | // Necessary for external CSS imports to work
33 | // https://github.com/facebookincubator/create-react-app/issues/2677
34 | ident: 'postcss',
35 | plugins: () => [
36 | require('postcss-flexbugs-fixes'),
37 | autoprefixer({
38 | browsers: [
39 | '>1%',
40 | 'last 4 versions',
41 | 'Firefox ESR',
42 | 'not ie < 9', // React doesn't support IE8 anyway
43 | ],
44 | flexbox: 'no-2009',
45 | }),
46 | ],
47 | },
48 | },
49 | {
50 | loader: require.resolve('less-loader'),
51 | options: {
52 | // theme vars, also can use theme.js instead of this.
53 | modifyVars: { "@brand-primary": "#1DA57A" },
54 | },
55 | },
56 | ]
57 | }
58 | );
59 |
60 | config.module.rules[1].oneOf.unshift(
61 | {
62 | test: /\.less$/,
63 | exclude: /node_modules|antd-mobile|antd/,
64 | use: [
65 | require.resolve('style-loader'),
66 | {
67 | loader: require.resolve('css-loader'),
68 | options: {
69 | modules: true,
70 | importLoaders: 1,
71 | localIdentName: '[local]___[hash:base64:5]'
72 | },
73 | },
74 | {
75 | loader: require.resolve('postcss-loader'),
76 | options: {
77 | // Necessary for external CSS imports to work
78 | // https://github.com/facebookincubator/create-react-app/issues/2677
79 | ident: 'postcss',
80 | plugins: () => [
81 | require('postcss-flexbugs-fixes'),
82 | autoprefixer({
83 | browsers: [
84 | '>1%',
85 | 'last 4 versions',
86 | 'Firefox ESR',
87 | 'not ie < 9', // React doesn't support IE8 anyway
88 | ],
89 | flexbox: 'no-2009',
90 | }),
91 | ],
92 | },
93 | },
94 | {
95 | loader: require.resolve('less-loader'),
96 | options: {
97 | // theme vars, also can use theme.js instead of this.
98 | modifyVars: { "@brand-primary": "#1DA57A" },
99 | },
100 | },
101 | ]
102 | }
103 | );
104 |
105 | // css-modules
106 | config.module.rules[1].oneOf.unshift(
107 | {
108 | test: /\.css$/,
109 | exclude: /node_modules|antd-mobile|antd\.css/,
110 | use: [
111 | require.resolve('style-loader'),
112 | {
113 | loader: require.resolve('css-loader'),
114 | options: {
115 | modules: true,
116 | importLoaders: 1,
117 | localIdentName: '[local]___[hash:base64:5]'
118 | },
119 | },
120 | {
121 | loader: require.resolve('postcss-loader'),
122 | options: {
123 | // Necessary for external CSS imports to work
124 | // https://github.com/facebookincubator/create-react-app/issues/2677
125 | ident: 'postcss',
126 | plugins: () => [
127 | require('postcss-flexbugs-fixes'),
128 | autoprefixer({
129 | browsers: [
130 | '>1%',
131 | 'last 4 versions',
132 | 'Firefox ESR',
133 | 'not ie < 9', // React doesn't support IE8 anyway
134 | ],
135 | flexbox: 'no-2009',
136 | }),
137 | ],
138 | },
139 | },
140 | ]
141 | }
142 | );
143 |
144 |
145 |
146 | // file-loader exclude
147 | let l = getLoader(config.module.rules, fileLoaderMatcher);
148 | l.exclude.push(/\.less$/);
149 |
150 | return config;
151 | },
152 | devServer: function(configFunction){
153 |
154 | return function(proxy, allowedHost) {
155 |
156 | const config = configFunction(proxy, allowedHost);
157 |
158 | // 启用局域网通过ip访问
159 | config.disableHostCheck = true;
160 |
161 |
162 |
163 |
164 | return config;
165 | }
166 |
167 |
168 | },
169 |
170 |
171 | }
172 |
173 |
--------------------------------------------------------------------------------
/src/components/HomeShopCar/index.js:
--------------------------------------------------------------------------------
1 | import React,{PureComponent} from 'react'
2 | import Styles from './index.less'
3 | import close from '../../assets/close.png'
4 | import classNames from 'classnames'
5 | import Stepper from '../Stepper'
6 |
7 | /**
8 | * 首页添加购物车
9 | */
10 | class HomeShopCar extends PureComponent{
11 |
12 |
13 | state={
14 |
15 | submitFlag:false,
16 | price:undefined,
17 | currgg:undefined,
18 | val:1,
19 |
20 | }
21 |
22 | onChange = (val) => {
23 | // console.log(val);
24 | this.setState({ val:val });
25 | }
26 |
27 |
28 | closeMe=()=>{
29 |
30 | this.props.dispatch({
31 | type:'closePopup'
32 | })
33 |
34 | this.setState({
35 | ...this.state,
36 | price:undefined,
37 | currgg:undefined,
38 | })
39 |
40 | var _body = document.getElementsByTagName("body")
41 |
42 | _body[0].style.overflow="";
43 |
44 |
45 |
46 |
47 | }
48 |
49 |
50 | setPrice=(item)=>{
51 |
52 | this.setState({
53 | ...this.state,
54 | submitFlag:true,
55 | price:item.price,
56 | currgg:item.id,
57 | })
58 |
59 | }
60 |
61 | // 获取商品的数量
62 | getNums =(val)=>{
63 | console.log(val)
64 | }
65 |
66 | renderGg=(data)=>{
67 |
68 | if(data != undefined){
69 |
70 | return data.map(item=>(
71 |
72 |
73 | {item.sepc}
74 |
75 | ))
76 |
77 | }
78 |
79 |
80 | }
81 |
82 | render(){
83 |
84 | const {data} = this.props;
85 |
86 | const gg = this.renderGg(data.data)
87 |
88 |
89 | return (
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |

99 |
100 |
101 |
102 |
103 |
104 |
105 | {data.title}
106 |
107 |
108 |
109 |
110 |
111 | ¥
112 |
113 |
114 | {this.state.price == undefined? data.priceRange:this.state.price}
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 | 净含量:
132 |
133 |
134 |
135 | {gg}
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
购物数量:
149 |
150 |
151 |
152 |
153 |
剩余2017件
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 | )
169 |
170 | }
171 |
172 | }
173 |
174 | export default HomeShopCar;
--------------------------------------------------------------------------------