├── static ├── .gitkeep └── css │ └── reset.css ├── .eslintignore ├── src ├── store │ ├── getters.js │ ├── actions.js │ ├── mutation-types.js │ ├── index.js │ └── modules │ │ ├── user.js │ │ ├── msite.js │ │ └── shop.js ├── common │ ├── imgs │ │ └── loading.gif │ └── stylus │ │ └── mixins.styl ├── pages │ ├── Order │ │ ├── images │ │ │ └── person.png │ │ └── Order.vue │ ├── test │ │ ├── B2.vue │ │ ├── A.vue │ │ ├── B1.vue │ │ └── B.vue │ ├── NotFound │ │ └── NotFound.vue │ ├── Search │ │ └── Search.vue │ ├── Shop │ │ ├── Shop.vue │ │ ├── ShopInfo │ │ │ └── ShopInfo.vue │ │ ├── ShopGoods │ │ │ └── ShopGoods.vue │ │ └── ShopRatings │ │ │ └── ShopRatings.vue │ ├── MSite │ │ ├── images │ │ │ └── msite_back.svg │ │ └── MSite.vue │ ├── Profile │ │ └── Profile.vue │ └── Login │ │ ├── images │ │ └── captcha.svg │ │ └── Login.vue ├── components │ ├── Star │ │ ├── images │ │ │ ├── star24_half@2x.png │ │ │ ├── star24_half@3x.png │ │ │ ├── star24_off@2x.png │ │ │ ├── star24_off@3x.png │ │ │ ├── star24_on@2x.png │ │ │ ├── star24_on@3x.png │ │ │ ├── star36_half@2x.png │ │ │ ├── star36_half@3x.png │ │ │ ├── star36_off@2x.png │ │ │ ├── star36_off@3x.png │ │ │ ├── star36_on@2x.png │ │ │ ├── star36_on@3x.png │ │ │ ├── star48_half@2x.png │ │ │ ├── star48_half@3x.png │ │ │ ├── star48_off@2x.png │ │ │ ├── star48_off@3x.png │ │ │ ├── star48_on@2x.png │ │ │ └── star48_on@3x.png │ │ └── Star.vue │ ├── Split │ │ └── Split.vue │ ├── ShopList │ │ ├── images │ │ │ └── shop_back.svg │ │ └── ShopList.vue │ ├── NavHeader │ │ └── NavHeader.vue │ ├── CartControl │ │ └── CartControl.vue │ ├── FooterGuide │ │ └── FooterGuide.vue │ ├── RatingsFilter │ │ └── RatingsFilter.vue │ ├── Food │ │ └── Food.vue │ ├── ShopCart │ │ └── ShopCart.vue │ └── ShopHeader │ │ └── ShopHeader.vue ├── filters │ └── index.js ├── mock │ └── mockServer.js ├── App.vue ├── router │ ├── index.js │ └── routes.js ├── main.js └── api │ ├── ajax.js │ └── index.js ├── config ├── prod.env.js ├── dev.env.js └── index.js ├── .editorconfig ├── .gitignore ├── .postcssrc.js ├── .babelrc ├── .eslintrc.js ├── index.html ├── 导航卫士.md ├── package.json ├── README.md └── API文档.md /static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /config/ 3 | /dist/ 4 | /*.js 5 | *.vue 6 | *.js -------------------------------------------------------------------------------- /src/store/getters.js: -------------------------------------------------------------------------------- 1 | /* 2 | 包含n个getter计算属性方法的对象 3 | */ 4 | export default { 5 | 6 | } -------------------------------------------------------------------------------- /src/store/actions.js: -------------------------------------------------------------------------------- 1 | /* 2 | 包含n个用于间接更新状态数据的方法的对象 3 | */ 4 | 5 | export default { 6 | 7 | } -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /src/common/imgs/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxfjd3g/180810_gshop-client/HEAD/src/common/imgs/loading.gif -------------------------------------------------------------------------------- /src/pages/Order/images/person.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxfjd3g/180810_gshop-client/HEAD/src/pages/Order/images/person.png -------------------------------------------------------------------------------- /src/components/Star/images/star24_half@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxfjd3g/180810_gshop-client/HEAD/src/components/Star/images/star24_half@2x.png -------------------------------------------------------------------------------- /src/components/Star/images/star24_half@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxfjd3g/180810_gshop-client/HEAD/src/components/Star/images/star24_half@3x.png -------------------------------------------------------------------------------- /src/components/Star/images/star24_off@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxfjd3g/180810_gshop-client/HEAD/src/components/Star/images/star24_off@2x.png -------------------------------------------------------------------------------- /src/components/Star/images/star24_off@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxfjd3g/180810_gshop-client/HEAD/src/components/Star/images/star24_off@3x.png -------------------------------------------------------------------------------- /src/components/Star/images/star24_on@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxfjd3g/180810_gshop-client/HEAD/src/components/Star/images/star24_on@2x.png -------------------------------------------------------------------------------- /src/components/Star/images/star24_on@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxfjd3g/180810_gshop-client/HEAD/src/components/Star/images/star24_on@3x.png -------------------------------------------------------------------------------- /src/components/Star/images/star36_half@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxfjd3g/180810_gshop-client/HEAD/src/components/Star/images/star36_half@2x.png -------------------------------------------------------------------------------- /src/components/Star/images/star36_half@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxfjd3g/180810_gshop-client/HEAD/src/components/Star/images/star36_half@3x.png -------------------------------------------------------------------------------- /src/components/Star/images/star36_off@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxfjd3g/180810_gshop-client/HEAD/src/components/Star/images/star36_off@2x.png -------------------------------------------------------------------------------- /src/components/Star/images/star36_off@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxfjd3g/180810_gshop-client/HEAD/src/components/Star/images/star36_off@3x.png -------------------------------------------------------------------------------- /src/components/Star/images/star36_on@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxfjd3g/180810_gshop-client/HEAD/src/components/Star/images/star36_on@2x.png -------------------------------------------------------------------------------- /src/components/Star/images/star36_on@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxfjd3g/180810_gshop-client/HEAD/src/components/Star/images/star36_on@3x.png -------------------------------------------------------------------------------- /src/components/Star/images/star48_half@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxfjd3g/180810_gshop-client/HEAD/src/components/Star/images/star48_half@2x.png -------------------------------------------------------------------------------- /src/components/Star/images/star48_half@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxfjd3g/180810_gshop-client/HEAD/src/components/Star/images/star48_half@3x.png -------------------------------------------------------------------------------- /src/components/Star/images/star48_off@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxfjd3g/180810_gshop-client/HEAD/src/components/Star/images/star48_off@2x.png -------------------------------------------------------------------------------- /src/components/Star/images/star48_off@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxfjd3g/180810_gshop-client/HEAD/src/components/Star/images/star48_off@3x.png -------------------------------------------------------------------------------- /src/components/Star/images/star48_on@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxfjd3g/180810_gshop-client/HEAD/src/components/Star/images/star48_on@2x.png -------------------------------------------------------------------------------- /src/components/Star/images/star48_on@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zxfjd3g/180810_gshop-client/HEAD/src/components/Star/images/star48_on@3x.png -------------------------------------------------------------------------------- /config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"' 7 | }) 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Editor directories and files 9 | .idea 10 | .vscode 11 | *.suo 12 | *.ntvs* 13 | *.njsproj 14 | *.sln 15 | -------------------------------------------------------------------------------- /src/filters/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | 用来自定义过滤器的模块 3 | */ 4 | import Vue from 'vue' 5 | import moment from 'moment' 6 | 7 | // 日期格式化 8 | Vue.filter('date-format', (value, format) => { 9 | return moment(value).format(format || 'YYYY-MM-DD HH:mm:ss') 10 | }) 11 | -------------------------------------------------------------------------------- /src/pages/test/B2.vue: -------------------------------------------------------------------------------- 1 | 6 | 13 | -------------------------------------------------------------------------------- /src/pages/test/A.vue: -------------------------------------------------------------------------------- 1 | 6 | 13 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | "postcss-import": {}, 6 | "postcss-url": {}, 7 | // to edit target browsers: use "browserslist" field in package.json 8 | "autoprefixer": {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/pages/NotFound/NotFound.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | 14 | 17 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": ["transform-vue-jsx","transform-runtime",["component", [ 12 | { 13 | "libraryName": "mint-ui", 14 | "style": true 15 | } 16 | ]]] 17 | } 18 | 19 | -------------------------------------------------------------------------------- /src/mock/mockServer.js: -------------------------------------------------------------------------------- 1 | /* 2 | 使用mockjs提供mock数据接口 3 | */ 4 | import Mock from 'mockjs' 5 | import data from './data.json' // 已自动解析js对应的类型: 对象 6 | 7 | // goods的接口 8 | Mock.mock('/goods', {code: 0, data: data.goods}) 9 | // ratings的接口 10 | Mock.mock('/ratings', {code: 0, data: data.ratings}) 11 | // info的接口 12 | Mock.mock('/info', {code: 0, data: data.info}) 13 | 14 | console.log('mockServer....') 15 | 16 | // export 不需要向外暴露任何东西 -------------------------------------------------------------------------------- /src/components/Split/Split.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 19 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 24 | 25 | 28 | -------------------------------------------------------------------------------- /src/store/mutation-types.js: -------------------------------------------------------------------------------- 1 | /* 2 | 包含n个mutation函数常量名称的模块 3 | 有几个type就需要定义几个mutation函数 4 | */ 5 | export const RECEIVE_ADDRESS = 'receive_address' // 接收地址信息 6 | export const RECEIVE_CATEGORYS = 'receive_categorys' // 接收分类数组 7 | export const RECEIVE_SHOPS = 'receive_shops' // 接收商家数组 8 | export const RECEIVE_USER = 'receive_user' // 接收用户信息 9 | export const RESET_USER = 'reset_user' // 重置用户信息 10 | export const RECEIVE_GOODS = 'receive_goods' // 接收商品数组 11 | export const RECEIVE_RATINGS = 'receive_ratings' // 接收商家评价数组 12 | export const RECEIVE_INFO = 'receive_info' // 接收商家信息 13 | export const ADD_FOOD_COUNT = 'add_food_count' // 增加food的数量 14 | export const REDUCE_FOOD_COUNT = 'reduce_food_count' // 减少food的数量 -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | 路由器对象模块 3 | */ 4 | import Vue from 'vue' 5 | import VueRouter from 'vue-router' 6 | 7 | import routes from './routes' 8 | 9 | Vue.use(VueRouter) 10 | 11 | const router = new VueRouter({ 12 | mode: 'history', // 去除路由路径中的# 13 | routes 14 | }) 15 | 16 | // 所有需要检查是否登陆的path的数组 17 | const paths = ['/a', '/b'] 18 | 19 | // 添加全局前置守卫 20 | router.beforeEach((to, from, next) => { 21 | console.log('beforeEach', to, from) 22 | // 得到请求的路由路径 23 | const path = to.path 24 | // 如果在需要检查的paths中 25 | if(paths.indexOf(path)>=0) { 26 | // 判断是否已经登陆 27 | const userId = Vue.store.state.user.user._id 28 | // 如果已登陆, 放行 29 | if(userId) { 30 | next() 31 | } else {// 如果没有, 直接跳转到登陆 32 | next('/login') 33 | } 34 | } else { // 如果不在, 直接放行 35 | // 放行 36 | next() 37 | } 38 | 39 | }) 40 | 41 | 42 | export default router -------------------------------------------------------------------------------- /src/pages/test/B1.vue: -------------------------------------------------------------------------------- 1 | 6 | 38 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parserOptions: { 6 | parser: 'babel-eslint' 7 | }, 8 | env: { 9 | browser: true, 10 | }, 11 | extends: [ 12 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention 13 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. 14 | 'plugin:vue/essential', 15 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md 16 | 'standard' 17 | ], 18 | // required to lint *.vue files 19 | plugins: [ 20 | 'vue' 21 | ], 22 | // add your custom rules here 23 | rules: { 24 | // allow async-await 25 | 'generator-star-spacing': 'off', 26 | // allow debugger during development 27 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | vuex最核心的管理对象模块 3 | */ 4 | import Vue from 'vue' 5 | import Vuex from 'vuex' 6 | 7 | import actions from './actions' 8 | import getters from './getters' 9 | import msite from './modules/msite' 10 | import user from './modules/user' 11 | import shop from './modules/shop' 12 | 13 | Vue.use(Vuex) 14 | 15 | export default new Vuex.Store({ // options 16 | modules: { 17 | msite, 18 | user, 19 | shop 20 | }, 21 | // state, 22 | actions, 23 | getters, 24 | }) 25 | 26 | /* 27 | 28 | store.state = { 29 | msite: options.modules.msite.state, 30 | user: options.modules.user.state, 31 | } 32 | */ 33 | 34 | /* 35 | vuex管理的state的结构: 36 | { 37 | msite: { 38 | latitude: 40.10038, // 纬度 39 | longitude: 116.36867, // 经度 40 | address: {}, // 地址信息对象 41 | categorys: [], // 食品分类数组 42 | shops: [], // 商家数组 43 | }, 44 | user: { 45 | user: {}, // 登陆的用户信息 46 | } 47 | } 48 | */ -------------------------------------------------------------------------------- /src/pages/Order/Order.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /src/components/ShopList/images/shop_back.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 17 | 18 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import {Button} from 'mint-ui' 3 | import VueLazyload from 'vue-lazyload' 4 | 5 | import App from './App.vue' 6 | import router from './router' 7 | import store from './store' 8 | 9 | import NavHeader from './components/NavHeader/NavHeader.vue' 10 | import Star from './components/Star/Star.vue' 11 | import CartControl from './components/CartControl/CartControl.vue' 12 | import Split from './components/Split/Split.vue' 13 | 14 | import loading from './common/imgs/loading.gif' 15 | import './mock/mockServer' 16 | import './filters' 17 | 18 | Vue.use(VueLazyload, { 19 | loading 20 | }) // 内部定义了一个全局指令: lazy 21 | 22 | 23 | // 注册全局组件 24 | Vue.component('NavHeader', NavHeader) 25 | Vue.component('Star', Star) 26 | Vue.component('CartControl', CartControl) 27 | Vue.component('Split', Split) 28 | Vue.component(Button.name, Button) 29 | 30 | // 将store保存到Vue函数对象上 31 | Vue.store = store 32 | 33 | /* eslint-disable no-new */ 34 | new Vue({ 35 | el: '#app', 36 | render: h => h(App), 37 | router, 38 | store 39 | }) 40 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | gshop 8 | 9 | 10 | 11 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/store/modules/user.js: -------------------------------------------------------------------------------- 1 | /* 2 | 管理用户相关状态数据 3 | */ 4 | import {reqLogout, reqUserInfo} from '../../api' 5 | import { 6 | RECEIVE_USER, 7 | RESET_USER 8 | } from '../mutation-types' 9 | 10 | const state = { 11 | user: {}, // 登陆的用户信息 12 | } 13 | const mutations = { 14 | [RECEIVE_USER](state, {user}) { 15 | state.user = user 16 | }, 17 | [RESET_USER](state) { 18 | state.user = {} 19 | }, 20 | } 21 | const actions = { 22 | // 同步保存user信息 23 | saveUser({commit}, user) { 24 | commit(RECEIVE_USER, {user}) 25 | }, 26 | 27 | // 异步获取当前用户信息 28 | async getUser({commit}) { 29 | const result = await reqUserInfo() 30 | if (result.code === 0) { 31 | const user = result.data 32 | // 提交mutation 33 | commit(RECEIVE_USER, {user}) 34 | } 35 | }, 36 | 37 | // 异步退出登陆 38 | async logout({commit}) { 39 | const result = await reqLogout() 40 | if (result.code === 0) { 41 | commit(RESET_USER) 42 | } 43 | }, 44 | } 45 | const getters = {} 46 | 47 | export default { 48 | state, 49 | mutations, 50 | actions, 51 | getters 52 | } 53 | -------------------------------------------------------------------------------- /src/api/ajax.js: -------------------------------------------------------------------------------- 1 | /* 2 | 能发送ajax请求的函数模块 3 | 包装axios 4 | 函数的返回值是promise对象 5 | axios.get()/post()返回的就是promise对象 6 | */ 7 | import axios from 'axios' 8 | 9 | export default function ajax(url, data={}, method='GET') { 10 | 11 | return new Promise(function (resolve, reject) { 12 | let promise 13 | // 执行异步ajax请求 14 | if(method==='GET') { 15 | promise = axios.get(url, {params: data}) // params配置指定的是query参数 16 | } else { 17 | promise = axios.post(url, data) 18 | } 19 | promise.then(response => { 20 | // 如果成功了, 调用resolve(response.data) 21 | resolve(response.data) 22 | }).catch(error => { // 对所有ajax请求出错做统一处理, 外层就不用再处理错误了 23 | // 如果失败了, 提示请求后台出错 24 | alert('请求错误: '+error.message) 25 | }) 26 | }) 27 | } 28 | 29 | /* 30 | const promise = ajax('/xxx') 31 | promise.then(response => { 32 | const result = response.data 33 | 34 | }).catch() 35 | 36 | 37 | 38 | */ 39 | /*async function getAddress() { 40 | /!* 41 | const response = await ajax('/address') 42 | const result = response.data 43 | *!/ 44 | const result = await ajax('/address') 45 | if(result.code===0) { 46 | 47 | } else { 48 | 49 | } 50 | }*/ 51 | -------------------------------------------------------------------------------- /src/common/stylus/mixins.styl: -------------------------------------------------------------------------------- 1 | $green = #02a774; 2 | $yellow = #F5A100; 3 | $bc = #e4e4e4; 4 | 5 | // 一像素下边框 6 | bottom-border-1px($color) 7 | position relative 8 | border none 9 | &:after 10 | content '' 11 | position absolute 12 | left 0 13 | bottom 0 14 | width 100% 15 | height 1px 16 | background-color $color 17 | transform scaleY(0.5) 18 | 19 | // 一像素上边框 20 | top-border-1px($color) 21 | position relative 22 | &::before 23 | content '' 24 | position absolute 25 | z-index 200 26 | left 0 27 | top 0 28 | width 100% 29 | height 1px 30 | background-color $color 31 | 32 | //根据像素比缩放1px像素边框 33 | @media only screen and (-webkit-device-pixel-ratio: 2 ) 34 | .border-1px 35 | &::before 36 | transform scaleY(.5) 37 | 38 | @media only screen and (-webkit-device-pixel-ratio: 3 ) 39 | .border-1px 40 | &::before 41 | transform scaleY(.333333) 42 | 43 | //根据像素比来使用 2x图 3x图 44 | bg-image($url) 45 | background-image: url($url + "@2x.png") 46 | @media (-webkit-min-device-pixel-ratio: 3),(min-device-pixel-ratio: 3) 47 | background-image: url($url + "@3x.png") 48 | 49 | //清除浮动 50 | clearFix() 51 | *zoom 1 52 | &::after 53 | content '' 54 | display block 55 | clear both -------------------------------------------------------------------------------- /src/pages/Search/Search.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | 17 | -------------------------------------------------------------------------------- /src/pages/test/B.vue: -------------------------------------------------------------------------------- 1 | 21 | 28 | -------------------------------------------------------------------------------- /src/components/NavHeader/NavHeader.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 19 | 20 | 61 | -------------------------------------------------------------------------------- /src/api/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | 包含n个接口请求函数的模块 3 | 每个函数返回promise 4 | */ 5 | import ajax from './ajax' 6 | 7 | // const BASE = 'http://localhost:5000' 8 | const BASE = '/api' // 开发环境下需要使用代理帮我们转发请求 9 | // const BASE = '' // 生产环境打包 10 | 11 | 12 | // [1、根据经纬度获取位置详情](#1根据经纬度获取位置详情)
13 | /*export function reqAddress(longitude, latitude) { 14 | return ajax(`/position/${latitude},${longitude}`) 15 | }*/ 16 | export const reqAddress = (longitude, latitude) => ajax(BASE + `/position/${latitude},${longitude}`) 17 | 18 | // [2、获取食品分类列表](#2获取食品分类列表)
19 | export const reqCategorys = () => ajax(BASE + '/index_category') 20 | 21 | // [3、根据经纬度获取商铺列表](#3根据经纬度获取商铺列表)
22 | export const reqShops = (longitude, latitude) => ajax(BASE + '/shops', {longitude, latitude}) 23 | 24 | // [6、用户名密码登陆](#6用户名密码登陆)
25 | export const reqPwdLogin = ({name, pwd, captcha}) => ajax(BASE + '/login_pwd', {name, pwd, captcha}, 'POST') 26 | 27 | // [7、发送短信验证码](#7发送短信验证码)
28 | export const reqSendCode = (phone) => ajax(BASE + '/sendcode', {phone}) 29 | 30 | // [8、手机号验证码登陆](#8手机号验证码登陆)
31 | export const reqSmsLogin = (phone, code) => ajax(BASE + '/login_sms', {phone, code}, 'POST') 32 | 33 | // [9、根据会话获取用户信息](#9根据会话获取用户信息)
34 | export const reqUserInfo = () => ajax(BASE + '/userinfo') 35 | 36 | // [10、用户登出](#10用户登出)
37 | export const reqLogout = () => ajax(BASE + '/logout') 38 | 39 | export const reqGoods = () => ajax('/goods') 40 | export const reqRatings = () => ajax('/ratings') 41 | export const reqInfo = () => ajax('/info') -------------------------------------------------------------------------------- /src/pages/Shop/Shop.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 37 | 38 | 66 | -------------------------------------------------------------------------------- /src/components/CartControl/CartControl.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 25 | 26 | 64 | 65 | -------------------------------------------------------------------------------- /src/store/modules/msite.js: -------------------------------------------------------------------------------- 1 | /* 2 | 管理msite相关状态数据 3 | */ 4 | import {reqCategorys, reqAddress, reqShops} from '../../api' 5 | import { 6 | RECEIVE_ADDRESS, 7 | RECEIVE_CATEGORYS, 8 | RECEIVE_SHOPS, 9 | } from '../mutation-types' 10 | 11 | const state = { 12 | latitude: 40.10038, // 纬度 13 | longitude: 116.36867, // 经度 14 | address: {}, // 地址信息对象 15 | categorys: [], // 食品分类数组 16 | shops: [], // 商家数组 17 | } 18 | const mutations = { 19 | [RECEIVE_ADDRESS](state, {address}) { 20 | state.address = address 21 | }, 22 | [RECEIVE_CATEGORYS](state, {categorys}) { 23 | state.categorys = categorys 24 | }, 25 | [RECEIVE_SHOPS](state, {shops}) { 26 | state.shops = shops 27 | }, 28 | } 29 | const actions = { 30 | // 异步获取地址信息 31 | async getAddress({commit, state}) { 32 | // 1. 发异步ajax请求 33 | const {longitude, latitude} = state 34 | const result = await reqAddress(longitude, latitude) 35 | // 2. 有了结果后, 提交mutation 36 | if (result.code === 0) { 37 | const address = result.data 38 | commit(RECEIVE_ADDRESS, {address}) 39 | } 40 | }, 41 | 42 | // 异步获食品分类列表 43 | async getCategorys({commit}, callback) { 44 | // 1. 发异步ajax请求 45 | const result = await reqCategorys() 46 | // 2. 有了结果后, 提交mutation 47 | if (result.code === 0) { 48 | const categorys = result.data 49 | commit(RECEIVE_CATEGORYS, {categorys}) 50 | // 在提交mutation更新状态之后调用callback 51 | typeof callback === 'function' && callback() 52 | } 53 | }, 54 | 55 | // 异步获取商家列表 56 | async getShops({commit, state}) { 57 | // 1. 发异步ajax请求 58 | const {longitude, latitude} = state 59 | const result = await reqShops(longitude, latitude) 60 | // 2. 有了结果后, 提交mutation 61 | if (result.code === 0) { 62 | const shops = result.data 63 | commit(RECEIVE_SHOPS, {shops}) 64 | } 65 | }, 66 | } 67 | const getters = {} 68 | 69 | export default { 70 | state, 71 | mutations, 72 | actions, 73 | getters 74 | } 75 | -------------------------------------------------------------------------------- /src/pages/MSite/images/msite_back.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 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 | 51 | 52 | -------------------------------------------------------------------------------- /src/components/FooterGuide/FooterGuide.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 40 | 41 | -------------------------------------------------------------------------------- /导航卫士.md: -------------------------------------------------------------------------------- 1 | # 1. 导航守卫是什么? 2 | 1). 导航守卫是vue-router提供的下面2个方面的功能 3 | a. 监视路由跳转 4 | b. 控制路由跳转 5 | 2). 应用 6 | a. 在跳转到界面前, 进行用户权限检查限制(如是否已登陆) 7 | b. 在界面离开前, 做收尾工作 8 | 9 | # 2. 导航守卫分类 10 | 1). 全局守卫: 针对任意路由跳转 11 | a. 全局前置守卫 12 | b. 全局后置守卫 13 | 2). 组件守卫: 只针对当前组件的路由跳转 14 | a. 进入 15 | b. 更新 16 | c. 离开 17 | 18 | # 3. 相关API 19 | 1). 全局前置守卫: 在准备跳转到某个路由组件之前 (在开发中用的比较多) 20 | router.beforeEach((to, from, next) => {// before enter each route component 21 | 22 | }) 23 | 说明: 24 | ①. to: 目标route 25 | ②. from: 起始route 26 | ③. next: 函数 27 | next(): 执行下一个守卫回调, 如果没有跳转到目标路由 28 | next(false)/不执行: 跳转流程在当前处中断, 不会跳转到目标路由组件 29 | next(path): 跳转到指定的另一个路由 30 | 31 | 2). 全局后置守卫: 在跳转到某个路由组件之后 32 | router.afterEach((to, from) => { 33 | 34 | }) 35 | 3). 组件守卫 36 | // 在当前组件对象被创建前调用, 不能直接访问this(不是组件对象) 37 | // 但可以通过next(component => {}), 在回调函数中访问组件对象 38 | beforeRouteEnter (to, from, next) { 39 | next(component => {}) 40 | }, 41 | // 当前组件对象将要更新前调用, 可以访问this 42 | beforeRouteUpdate (to, from, next) { 43 | 44 | }, 45 | // 在当前组件离开前调用, 可以访问this 46 | beforeRouteLeave (to, from, next) { 47 | next() 48 | } 49 | 50 | # 4. 导航解析流程 51 | 导航被触发。 52 | 在失活的组件里调用组件离开守卫: beforeRouteLeave() 53 | 调用全局的前置守卫: beforeEach() 54 | 在被激活的组件里调用组件进入守卫: beforeRouteEnter(), 函数内部可能会执行next(comp => {}) 55 | 导航被确认。 56 | 创建组件对象 57 | 调用全局的后置钩子: afterEach() 58 | 调用组件中通过next(comp => {})指定的回调函数, 并将创建好的组件对象传入 59 | 60 | # 5. 应用(在vue项目中测试使用) 61 | 1). 针对部分/某个界面, 检查用户是否登陆 62 | a. 必须登陆, 如果没有跳转到登陆界面 63 | b. 不能登陆, 如果已经登陆, 跳转到特定界面 64 | 2). 在路由界面离开前, 做一些收尾工作(如清除定时器) 65 | 3). 常用的卫士回调函数: 66 | a. 全局前置卫士: beforeEach() 67 | b. 组件进入卫士: beforeRouteEnter() // 不能直接使用this 68 | c. 组件离开卫士: beforeRouteLeave() 69 | 70 | 71 | 需求: 72 | 1. 进入a/b必须登陆, 如果没有登陆自动跳转到登陆 73 | 2. 进入登陆界面时, 如果已经登陆了自动跳转到个人中心 -------------------------------------------------------------------------------- /static/css/reset.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/) 3 | * http://cssreset.com 4 | */ 5 | html, body, div, span, applet, object, iframe, 6 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 7 | a, abbr, acronym, address, big, cite, code, 8 | del, dfn, em, img, ins, kbd, q, s, samp, 9 | small, strike, strong, sub, sup, tt, var, 10 | b, u, i, center, 11 | dl, dt, dd, ol, ul, li, 12 | fieldset, form, label, legend, 13 | table, caption, tbody, tfoot, thead, tr, th, td, 14 | article, aside, canvas, details, embed, 15 | figure, figcaption, footer, header, 16 | menu, nav, output, ruby, section, summary, 17 | time, mark, audio, video, input { 18 | margin: 0; 19 | padding: 0; 20 | border: 0; 21 | font-size: 100%; 22 | font-weight: normal; 23 | vertical-align: baseline; 24 | } 25 | 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, menu, nav, section { 29 | display: block; 30 | } 31 | 32 | body { 33 | line-height: 1; 34 | } 35 | 36 | blockquote, q { 37 | quotes: none; 38 | } 39 | 40 | blockquote:before, blockquote:after, 41 | q:before, q:after { 42 | content: none; 43 | } 44 | 45 | table { 46 | border-collapse: collapse; 47 | border-spacing: 0; 48 | } 49 | 50 | /* custom */ 51 | a { 52 | color: #7e8c8d; 53 | text-decoration: none; 54 | -webkit-backface-visibility: hidden; 55 | } 56 | 57 | li { 58 | list-style: none; 59 | } 60 | 61 | ::-webkit-scrollbar { 62 | width: 5px; 63 | height: 5px; 64 | } 65 | 66 | ::-webkit-scrollbar-track-piece { 67 | background-color: rgba(0, 0, 0, 0.2); 68 | -webkit-border-radius: 6px; 69 | } 70 | 71 | ::-webkit-scrollbar-thumb:vertical { 72 | height: 5px; 73 | background-color: rgba(125, 125, 125, 0.7); 74 | -webkit-border-radius: 6px; 75 | } 76 | 77 | ::-webkit-scrollbar-thumb:horizontal { 78 | width: 5px; 79 | background-color: rgba(125, 125, 125, 0.7); 80 | -webkit-border-radius: 6px; 81 | } 82 | 83 | html, body { 84 | width: 100%; 85 | height: 100%; 86 | } 87 | 88 | body { 89 | -webkit-text-size-adjust: none; 90 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 91 | } 92 | 93 | /*显示省略号*/ 94 | .ellipsis{ 95 | overflow: hidden; 96 | text-overflow: ellipsis; 97 | white-space: nowrap; 98 | } -------------------------------------------------------------------------------- /src/components/Star/Star.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 40 | 41 | 93 | -------------------------------------------------------------------------------- /src/components/RatingsFilter/RatingsFilter.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 47 | 48 | 91 | -------------------------------------------------------------------------------- /src/router/routes.js: -------------------------------------------------------------------------------- 1 | /* 2 | 包含应用中所有路由配置的模块 3 | */ 4 | // import MSite from '../pages/MSite/MSite.vue' 5 | // import Search from '../pages/Search/Search.vue' 6 | // import Order from '../pages/Order/Order.vue' 7 | // import Profile from '../pages/Profile/Profile.vue' 8 | 9 | // 1. 通过import()加载的模块会被单独打包(单独的js: code split) 10 | // 定义的路由component是一个函数, 函数在第一次被请求时才会执行 11 | const MSite = () => import('../pages/MSite/MSite.vue') 12 | const Search = () => import('../pages/Search/Search.vue') 13 | const Order = () => import('../pages/Order/Order.vue') 14 | const Profile = () => import('../pages/Profile/Profile.vue') 15 | 16 | import Login from '../pages/Login/Login.vue' 17 | import Shop from '../pages/Shop/Shop.vue' 18 | import ShopGoods from '../pages/Shop/ShopGoods/ShopGoods.vue' 19 | import ShopRatings from '../pages/Shop/ShopRatings/ShopRatings.vue' 20 | import ShopInfo from '../pages/Shop/ShopInfo/ShopInfo.vue' 21 | import NotFound from '../pages/NotFound/NotFound.vue' 22 | 23 | import A from '../pages/test/A.vue' 24 | import B from '../pages/test/B.vue' 25 | import B1 from '../pages/test/B1.vue' 26 | import B2 from '../pages/test/B2.vue' 27 | 28 | export default [ 29 | { 30 | path: '/msite', 31 | component: MSite, 32 | meta: { 33 | showFooter: true 34 | } 35 | }, 36 | { 37 | path: '/search', 38 | component: Search, 39 | meta: { 40 | showFooter: true 41 | } 42 | }, 43 | { 44 | path: '/order', 45 | component: Order, 46 | meta: { 47 | showFooter: true 48 | } 49 | }, 50 | { 51 | path: '/profile', 52 | component: Profile, 53 | meta: { 54 | showFooter: true 55 | } 56 | }, 57 | { 58 | path: '/login', 59 | component: Login 60 | }, 61 | { 62 | path: '/shop', 63 | component: Shop, 64 | children: [ 65 | { 66 | path: '/shop/goods', 67 | component: ShopGoods 68 | }, 69 | { 70 | path: '/shop/ratings', 71 | component: ShopRatings 72 | }, 73 | { 74 | path: '/shop/info', 75 | component: ShopInfo 76 | }, 77 | 78 | { 79 | path: '', 80 | redirect: '/shop/goods' 81 | } 82 | ] 83 | }, 84 | 85 | { 86 | path: '/', 87 | redirect: '/msite' 88 | }, 89 | { 90 | path: '/a', 91 | component: A 92 | }, 93 | { 94 | path: '/b', 95 | component: B, 96 | children: [ 97 | { 98 | path: '/b/b1', 99 | component: B1 100 | }, 101 | { 102 | path: '/b/b2', 103 | component: B2 104 | }, 105 | ] 106 | }, 107 | 108 | { // 配置404组件, 放在最后 109 | path: '/*', 110 | component: NotFound 111 | }, 112 | ] -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gshop-client", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "zxfjd3g <258147149@qq.com>", 6 | "private": true, 7 | "scripts": { 8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js --host 192.168.16.38", 9 | "start": "npm run dev", 10 | "lint": "eslint --ext .js,.vue src", 11 | "build": "node build/build.js" 12 | }, 13 | "dependencies": { 14 | "axios": "^0.18.0", 15 | "better-scroll": "^1.13.2", 16 | "mint-ui": "^2.2.13", 17 | "mockjs": "^1.0.1-beta3", 18 | "moment": "^2.23.0", 19 | "swiper": "^4.4.6", 20 | "vue": "^2.5.2", 21 | "vue-router": "^3.0.2", 22 | "vuex": "^3.0.1", 23 | "vue-lazyload": "1.2.6" 24 | }, 25 | "devDependencies": { 26 | "autoprefixer": "^7.1.2", 27 | "babel-core": "^6.22.1", 28 | "babel-eslint": "^8.2.1", 29 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 30 | "babel-loader": "^7.1.1", 31 | "babel-plugin-component": "^1.1.1", 32 | "babel-plugin-syntax-jsx": "^6.18.0", 33 | "babel-plugin-transform-runtime": "^6.22.0", 34 | "babel-plugin-transform-vue-jsx": "^3.5.0", 35 | "babel-preset-env": "^1.3.2", 36 | "babel-preset-stage-2": "^6.22.0", 37 | "chalk": "^2.0.1", 38 | "copy-webpack-plugin": "^4.0.1", 39 | "css-loader": "^0.28.0", 40 | "eslint": "^4.15.0", 41 | "eslint-config-standard": "^10.2.1", 42 | "eslint-friendly-formatter": "^3.0.0", 43 | "eslint-loader": "^1.7.1", 44 | "eslint-plugin-import": "^2.7.0", 45 | "eslint-plugin-node": "^5.2.0", 46 | "eslint-plugin-promise": "^3.4.0", 47 | "eslint-plugin-standard": "^3.0.1", 48 | "eslint-plugin-vue": "^4.0.0", 49 | "extract-text-webpack-plugin": "^3.0.0", 50 | "file-loader": "^1.1.4", 51 | "friendly-errors-webpack-plugin": "^1.6.1", 52 | "html-webpack-plugin": "^2.30.1", 53 | "node-notifier": "^5.1.2", 54 | "optimize-css-assets-webpack-plugin": "^3.2.0", 55 | "ora": "^1.2.0", 56 | "portfinder": "^1.0.13", 57 | "postcss-import": "^11.0.0", 58 | "postcss-loader": "^2.0.8", 59 | "postcss-url": "^7.2.1", 60 | "rimraf": "^2.6.0", 61 | "semver": "^5.3.0", 62 | "shelljs": "^0.7.6", 63 | "stylus": "^0.54.5", 64 | "stylus-loader": "^3.0.2", 65 | "uglifyjs-webpack-plugin": "^1.1.1", 66 | "url-loader": "^0.5.8", 67 | "vue-loader": "^13.3.0", 68 | "vue-style-loader": "^3.0.1", 69 | "vue-template-compiler": "^2.5.2", 70 | "webpack": "^3.6.0", 71 | "webpack-bundle-analyzer": "^2.9.0", 72 | "webpack-dev-server": "^2.9.1", 73 | "webpack-merge": "^4.1.0" 74 | }, 75 | "engines": { 76 | "node": ">= 6.0.0", 77 | "npm": ">= 3.0.0" 78 | }, 79 | "browserslist": [ 80 | "> 1%", 81 | "last 2 versions", 82 | "not ie <= 8" 83 | ] 84 | } 85 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.3.1 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | 7 | module.exports = { 8 | dev: { 9 | 10 | // Paths 11 | assetsSubDirectory: 'static', 12 | assetsPublicPath: '/', 13 | proxyTable: { 14 | '/api': { // 匹配所有以 '/api'开头的请求路径 15 | target: 'http://localhost:5000', // 代理目标的基础路径 16 | changeOrigin: true, // 支持跨域 17 | pathRewrite: {// 重写路径: 去掉路径中开头的'/api' 18 | '^/api': '' 19 | } 20 | }, 21 | '/baidu': { // 匹配所有以 '/api'开头的请求路径 22 | target: 'http://www.baidu.com', // 代理目标的基础路径 23 | changeOrigin: true, // 支持跨域 24 | pathRewrite: {// 重写路径: 去掉路径中开头的'/api' 25 | '^/baidu': '' 26 | } 27 | } 28 | }, 29 | 30 | // Various Dev Server settings 31 | host: 'localhost', // can be overwritten by process.env.HOST 32 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 33 | autoOpenBrowser: true, 34 | errorOverlay: true, 35 | notifyOnErrors: true, 36 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 37 | 38 | // Use Eslint Loader? 39 | // If true, your code will be linted during bundling and 40 | // linting errors and warnings will be shown in the console. 41 | useEslint: true, 42 | // If true, eslint errors and warnings will also be shown in the error overlay 43 | // in the browser. 44 | showEslintErrorsInOverlay: false, 45 | 46 | /** 47 | * Source Maps 48 | */ 49 | 50 | // https://webpack.js.org/configuration/devtool/#development 51 | devtool: 'cheap-module-eval-source-map', 52 | 53 | // If you have problems debugging vue-files in devtools, 54 | // set this to false - it *may* help 55 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 56 | cacheBusting: true, 57 | 58 | cssSourceMap: true 59 | }, 60 | 61 | build: { 62 | // Template for index.html 63 | index: path.resolve(__dirname, '../dist/index.html'), 64 | 65 | // Paths 66 | assetsRoot: path.resolve(__dirname, '../dist'), 67 | assetsSubDirectory: 'static', 68 | assetsPublicPath: '/', 69 | 70 | /** 71 | * Source Maps 72 | */ 73 | 74 | productionSourceMap: true, 75 | // https://webpack.js.org/configuration/devtool/#production 76 | devtool: '#source-map', 77 | 78 | // Gzip off by default as many popular static hosts such as 79 | // Surge or Netlify already gzip all static assets for you. 80 | // Before setting to `true`, make sure to: 81 | // npm install --save-dev compression-webpack-plugin 82 | productionGzip: false, 83 | productionGzipExtensions: ['js', 'css'], 84 | 85 | // Run the build command with an extra argument to 86 | // View the bundle analyzer report after build finishes: 87 | // `npm run build --report` 88 | // Set to `true` or `false` to always turn it on or off 89 | bundleAnalyzerReport: process.env.npm_config_report 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/store/modules/shop.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import {reqGoods, reqRatings, reqInfo} from '../../api' 3 | import { 4 | RECEIVE_GOODS, 5 | RECEIVE_RATINGS, 6 | RECEIVE_INFO, 7 | ADD_FOOD_COUNT, 8 | REDUCE_FOOD_COUNT 9 | } from '../mutation-types' 10 | 11 | const state = { 12 | goods: [], // 商品列表 13 | ratings: [], // 商家评价列表 14 | info: {}, // 商家信息 15 | cartFoods: [], // 购物车中food数组 16 | } 17 | const mutations = { 18 | [RECEIVE_INFO](state, {info}) { 19 | state.info = info 20 | }, 21 | 22 | [RECEIVE_RATINGS](state, {ratings}) { 23 | state.ratings = ratings 24 | }, 25 | 26 | [RECEIVE_GOODS](state, {goods}) { 27 | state.goods = goods 28 | }, 29 | 30 | [ADD_FOOD_COUNT] (state, {food}) { 31 | if(!food.count) { 32 | // 给food添加一个新的属性, 内部不会进行数据劫持, 没有数据绑定 33 | // food.count = 1 34 | // 向响应式对象中添加一个属性,并确保这个新属性同样是响应式的 35 | Vue.set(food, 'count', 1) 36 | // 将food添加到购物车中 37 | state.cartFoods.push(food) 38 | 39 | } else { 40 | // 给food已有的属性值增加1 41 | food.count++ 42 | } 43 | 44 | }, 45 | 46 | [REDUCE_FOOD_COUNT] (state, {food}) { 47 | if(food.count>0) { 48 | food.count-- 49 | 50 | if(food.count===0) { 51 | // 将food从购物车中删除 52 | state.cartFoods.splice(state.cartFoods.indexOf(food), 1) 53 | } 54 | } 55 | }, 56 | 57 | 58 | } 59 | const actions = { 60 | async getGoods({commit}, cb) { 61 | const result = await reqGoods() 62 | if (result.code === 0) { 63 | const goods = result.data 64 | commit(RECEIVE_GOODS, {goods}) 65 | typeof cb==='function' && cb() 66 | } 67 | }, 68 | 69 | async getRatings({commit}, cb) { 70 | const result = await reqRatings() 71 | if (result.code === 0) { 72 | const ratings = result.data 73 | commit(RECEIVE_RATINGS, {ratings}) 74 | typeof cb==='function' && cb() 75 | } 76 | }, 77 | 78 | async getInfo({commit}) { 79 | const result = await reqInfo() 80 | if (result.code === 0) { 81 | const info = result.data 82 | commit(RECEIVE_INFO, {info}) 83 | } 84 | }, 85 | 86 | updateFoodCount ({commit}, {food, isAdd}) { 87 | if(isAdd) { 88 | commit(ADD_FOOD_COUNT, {food}) 89 | } else { 90 | commit(REDUCE_FOOD_COUNT, {food}) 91 | } 92 | } 93 | } 94 | const getters = { 95 | /*cartFoods (state) { 96 | const foods = [] 97 | state.goods.forEach(good => { 98 | good.foods.forEach(food => { 99 | if(food.count>0) { 100 | foods.push(food) 101 | } 102 | }) 103 | }) 104 | return foods 105 | }*/ 106 | 107 | totalCount (state) { 108 | return state.cartFoods.reduce((pre, food) => pre + food.count, 0) 109 | }, 110 | 111 | totalPrice (state) { 112 | return state.cartFoods.reduce((pre, food) => pre + food.count*food.price, 0) 113 | }, 114 | 115 | totalRatingCount (state) { 116 | return state.ratings.length 117 | }, 118 | 119 | positiveRatingCount (state) { 120 | return state.ratings.reduce((pre, rating) => pre + (rating.rateType===0 ? 1 : 0), 0) 121 | }, 122 | 123 | negativeRatingCount (state, getters) { 124 | return getters.totalRatingCount - getters.positiveRatingCount 125 | } 126 | } 127 | 128 | export default { 129 | state, 130 | mutations, 131 | actions, 132 | getters 133 | } -------------------------------------------------------------------------------- /src/components/Food/Food.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 32 | 51 | 52 | 167 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # day01 2 | ## 1. 项目开发准备 3 | 项目描述 4 | 技术选型 5 | API接口 6 | 7 | ## 2. 开启项目开发 8 | 使用脚手架创建项目 9 | 安装所有依赖/指定依赖 10 | 开发环境运行 11 | 生产环境打包与发布 12 | 13 | ## 3. 搭建项目整体界面结构 14 | stylus的理解和使用 15 | 结构化, 变量, 函数/minxin(混合) 16 | vue-router的理解和使用 17 | $router: 路由器对象, 包含一些操作路由的功能函数, 来实现编程式导航(跳转路由) 18 | $route: 当前路由对象, 一些当前路由信息数据的容器, path/meta/query/params 19 | 项目路由拆分 20 | 确定路由组件显示的区域 21 | 确定路由是几级路由 22 | 底部导航组件: FooterGuide 23 | 导航路由组件: MSite/Search/Order/Profile 24 | 25 | # day02 26 | ## 1. 抽取组件 27 | 头部组件: NavHeader, 通过slot来实现组件通信标签结构 28 | 商家列表组件: ShopList 29 | 30 | ## 2. 登陆路由组件 31 | 静态组件 32 | FooterGuide的显示/隐藏: 通过路由的meta 33 | 34 | ## 3. 后台应用 35 | 运行后台项目(启动mongodb服务), 36 | 使用postman测试后台接口, 如果不一致, 修改接口文档 37 | 38 | ## 4. 异步显示数据 39 | 封装ajax: 40 | promise+axios封装ajax请求的函数 41 | 封装每个接口对应的请求函数(能根据接口定义ajax请求函数) 42 | 解决ajax的跨越域问题: 配置代理, 对代理的理解 43 | vuex编码 44 | 创建所有相关的模块: store/index|state|mutations|actions|getters|mutation-types 45 | 设计state: 从后台获取的数据 46 | 实现actions: 47 | 定义异步action: async/await 48 | 流程: 发ajax获取数据, commit给mutation 49 | 实现mutations: 给状态赋值 50 | 实现index: 创建store对象 51 | main.js: 配置store 52 | 组件异步显示数据 53 | 在mounted()通过$store.dispatch('actionName')来异步获取后台数据到state中 54 | mapState(['xxx'])读取state中数据到组件中 55 | 在模板中显示xxx的数据 56 | 57 | ## 5. Star组件 58 | 创建组件, 设计组件的props 59 | 使用组件标签, 并传入相应的标签属性 60 | 完成组件编码: 使用计算属性 61 | 62 | # day03 63 | 64 | ## 1. 异步显示分类轮播 65 | 通过vuex获取categorys数组(发请求, 读取) 66 | 对数据进行整合一计算(维变为特定的二维数组) 67 | 使用Swiper显示轮播, 如何在界面更新之后创建Swiper对象? 68 | 1). 使用watch+$nextTick( () =>{界面更新之后立即执行}) 69 | 2). 使用回调+$nextTick() 70 | 使用svg图片实现loading的效果 71 | 72 | ## 2. 登陆的前台效果 73 | 1). 切换登陆方式: loginWay 74 | 2). 手机号验证: right_phone + isRightPhone计算属性 75 | 3). 倒计时效果: computeTime + setInterval() 76 | 4). 密码显示/隐藏的切换: isShowPwd + transition 77 | 78 | # day04 79 | ## 1. 登陆的前后台交互效果 80 | 1). 一次性图形验证码: 81 | 通过请求后台获取验证码图片显示 82 | 点击回调中更新img的src, 并携带时间戳参数, 更新验证码 83 | 2). 发送短信验证码 84 | 使用第三方短信平台接口 85 | 请求发送验证码短信 86 | 使用mint-ui实现对不同结果的不同提示效果 87 | 3). 短信登陆/注册 88 | 4). 密码登陆/注册 89 | 5). 前台表单验证 90 | 表单前台验证, 如果不通过, 提示 91 | 发送ajax请求, 得到返回的结果 92 | 根据结果的标识(code)来判断登陆请求是否成功 93 | 1: 不成功, 显示提示 94 | 0. 成功, 保存用户信息, 返回到个人中心 95 | 6). 自动登陆 96 | session与cookie的理解 97 | 后台将userid保存到session中 98 | App初始化过程中发请求获取user信息, 并保存到state 99 | 7). 退出登陆 100 | 请求退出登陆的接口, 重置state中的user 101 | 102 | ## 2. 搭建商家整体界面 103 | 1). 拆分界面路由: 嵌套路由 104 | 2). 路由的定义/配置|使用 105 | 106 | # day04 107 | ## 1. vuex的多模块编码 108 | 1). 设计多个模块 109 | msite 110 | user 111 | shop 112 | 2). 将state拆分到对应的模块中, 确定整个state的结构 113 | 3). 将mutation和action拆分到对应的模块中 114 | 4). 在组件中通过mapState读取特定模块的状态数据 115 | ...mapState({user: state => state.user.user}) 116 | 117 | ## 2. 模拟(mock)数据/接口 118 | 1). 前后台分离的理解 119 | 区别前后台分离的应用和前后台不分离的应用 120 | 区别一般的HTTP请求和ajax请求 121 | 2). json数据设计的理解 122 | json数据的类型: 对象/数组/字符串/数值/布尔值 123 | json数据的组成: 结构(名称和类型)和值 124 | 3). mockjs的理解和使用 125 | 用来提供mock数据接口的库 126 | 生成随机数据, 拦截ajax请求 127 | 128 | ## 3. ShopHeader组件 129 | 1). 异步显示数据效果的编码流程 130 | ajax 131 | ajax请求函数 132 | 接口请求函数 133 | vuex 134 | modules/shop.js 135 | 组件 136 | dispatch(): 异步获取后台数据到vuex的state 137 | mapState(): 从vuex的state中读取对应的数据 138 | 模板中显示 139 | 2). 初始显示异常 140 | 情况: Cannot read property 'xxx' of undefined" 141 | 原因: 初始值是空对象, 内部没有数据, 而模块中直接显示3层表达式 142 | 解决: 使用v-if指令 143 | 3). vue transition动画 144 | 145 | xxx-enter-active / xxx-leave-active 146 | transition 147 | xxx-enter / xxx-leave-to 148 | 隐藏时的样式 149 | 150 | ## 4. ShopGoods组件 151 | 1). 动态展现列表数据 152 | 2). 基本滑动: 153 | 使用better-scroll 154 | 创建BScroll对象的时机 155 | watch + $nextTick() 156 | 自定义callback + $nextTick 157 | better-scroll禁用了原生的dom事件, 使用的是自定义事件 158 | 159 | ## 5. CartControl组件 160 | 1). 问题: 更新状态数据, 对应的界面不变化 161 | 原因: 给一个已有绑定的对象直接添加一个新的属性, 这个属性没有数据绑定 162 | 解决: 163 | Vue.set(obj, 'xxx', value)才有数据绑定 164 | this.$set(obj, 'xxx', value)才有数据绑定 165 | 2). vue transition 166 | 167 | # day05 168 | 169 | ## 1. ShopGoods组件滑动功能 170 | 1). 滑动右侧列表, 左侧会同步更新当前分类 171 | 1). 设计一个计算属性: currentIndex代表当前分类的下标 172 | 2). 相关数据 173 | 滚动的y坐标: scrollY---> 给右侧列表绑定一个滚动的监听 174 | 右侧分类
  • 的top数组: tops-->列表第一次显示之后统计 175 | 3). 计算的逻辑 176 | scrollY>=top && scrollY 2 |
    3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 33 | 34 |
    35 |
    36 | 37 | 附近商家 38 |
    39 | 40 |
    41 |
    42 | 43 | 44 | 145 | 146 | -------------------------------------------------------------------------------- /src/components/ShopList/ShopList.vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | 73 | 74 | 180 | -------------------------------------------------------------------------------- /src/pages/Shop/ShopInfo/ShopInfo.vue: -------------------------------------------------------------------------------- 1 | 68 | 69 | 70 | 129 | 130 | 240 | -------------------------------------------------------------------------------- /src/pages/Shop/ShopGoods/ShopGoods.vue: -------------------------------------------------------------------------------- 1 | 53 | 54 | 176 | 177 | 276 | 277 | -------------------------------------------------------------------------------- /src/components/ShopCart/ShopCart.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | 126 | 127 | 283 | -------------------------------------------------------------------------------- /src/pages/Shop/ShopRatings/ShopRatings.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 134 | 135 | 270 | -------------------------------------------------------------------------------- /src/pages/Profile/Profile.vue: -------------------------------------------------------------------------------- 1 | 109 | 110 | 139 | 140 | -------------------------------------------------------------------------------- /src/pages/Login/images/captcha.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 10 | 12 | -------------------------------------------------------------------------------- /src/pages/Login/Login.vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | 171 | 172 | 311 | -------------------------------------------------------------------------------- /src/components/ShopHeader/ShopHeader.vue: -------------------------------------------------------------------------------- 1 | 110 | 111 | 112 | 131 | 132 | 507 | -------------------------------------------------------------------------------- /API文档.md: -------------------------------------------------------------------------------- 1 | # 接口文档 2 | 3 | ## 目录: 4 | [1、根据经纬度获取位置详情](#1根据经纬度获取位置详情)
    5 | [2、获取食品分类列表](#2获取食品分类列表)
    6 | [3、根据经纬度获取商铺列表](#3根据经纬度获取商铺列表)
    7 | [4、根据经纬度和关键字搜索商铺列表](#4根据经纬度和关键字搜索商铺列表)
    8 | [5、获取一次性验证码](#5获取一次性验证码)
    9 | [6、用户名密码登陆](#6用户名密码登陆)
    10 | [7、发送短信验证码](#7发送短信验证码)
    11 | [8、手机号验证码登陆](#8手机号验证码登陆)
    12 | [9、根据会话获取用户信息](#9根据会话获取用户信息)
    13 | [10、用户登出](#10用户登出)
    14 | 15 | ## 1、根据经纬度获取位置详情 16 | 17 | ### 请求URL: 18 | http://localhost:5000/position/:geohash 19 | 20 | ### 示例: 21 | [http://localhost:5000/position/40.10038,116.36867](http://localhost:5000/position/40.10038,116.36867) 22 | 23 | ### 请求方式: 24 | GET 25 | 26 | ### 参数类型:param 27 | 28 | |参数 |是否必选 |类型 |说明 29 | |geohash    |Y       |string   |经纬度 30 | 31 | ### 返回示例: 32 | 33 | { 34 | "code": 0, 35 | "data": { 36 | "address": "北京市昌平区337省道", 37 | "city": "北京市", 38 | "geohash": "40.10038,116.36867", 39 | "latitude": "40.10038", 40 | "longitude": "116.36867", 41 | "name": "昌平区北七家宏福科技园(337省道北)" 42 | } 43 | } 44 | 45 | ## 2、获取食品分类列表 46 | 47 | ### 请求URL: 48 | http://localhost:5000/index_category 49 | 50 | ### 请求方式: 51 | GET 52 | 53 | ### 参数类型: 54 | 无 55 | 56 | ### 返回示例: 57 | { 58 | "code": 0, 59 | data: [ 60 | { 61 | id: 1, 62 | is_in_serving: true, 63 | description: "0元早餐0起送,每天都有新花样。", 64 | title: "预订早餐", 65 | link: "", 66 | image_url: "/d/49/7757ff22e8ab28e7dfa5f7e2c2692jpeg.jpeg", 67 | icon_url: "", 68 | title_color: "", 69 | __v: 0 70 | }, 71 | { 72 | id: 65, 73 | is_in_serving: true, 74 | description: "", 75 | title: "土豪推荐", 76 | image_url: "/d/49/7757ff22e8ab28e7dfa5f7e2c2692jpeg.jpeg", 77 | link: "", 78 | icon_url: "", 79 | title_color: "", 80 | __v: 0 81 | }, 82 | ... 共n条数据 83 | ] 84 | } 85 | 86 | 87 | ## 3、根据经纬度获取商铺列表 88 | 89 | ### 请求URL: 90 | http://localhost:5000/shops 91 | 92 | ### 示例: 93 | [http://localhost:5000/shops?latitude=40.10038&longitude=116.36867](http://localhost:5000/shops?latitude=40.10038&longitude=116.36867) 94 | 95 | ### 请求方式: 96 | GET 97 | 98 | ### 参数类型:query 99 | |参数 |是否必选 |类型 |说明| 100 | |latitude     |Y       |string  |纬度| 101 | |longitude     |Y       |string  |经度| 102 | 103 | ### 返回示例: 104 | { 105 | "code": 0, 106 | data: [ 107 | { 108 | name: "肯德基", 109 | address: "上海市宝山区淞宝路155弄18号星月国际商务广场1层", 110 | id: 1, 111 | latitude: 31.38098, 112 | longitude: 121.50146, 113 | location: [ 114 | 121.50146, 115 | 31.38098 116 | ], 117 | phone: "1232313124324", 118 | category: "快餐便当/简餐", 119 | supports: [ 120 | { 121 | description: "已加入“外卖保”计划,食品安全有保障", 122 | icon_color: "999999", 123 | icon_name: "保", 124 | id: 7, 125 | name: "外卖保", 126 | _id: "591bec73c2bbc84a6328a1e5" 127 | } 128 | ], 129 | status: 0, 130 | recent_order_num: 615, 131 | rating_count: 389, 132 | rating: 1.6, 133 | promotion_info: "他依然有人有人有人有人有人", 134 | piecewise_agent_fee: { 135 | tips: "配送费约¥5" 136 | }, 137 | opening_hours: [ 138 | "8:30/20:30" 139 | ], 140 | license: { 141 | catering_service_license_image: "", 142 | business_license_image: "" 143 | }, 144 | is_new: true, 145 | is_premium: true, 146 | image_path: "/img/shop/15c1513a00615.jpg", 147 | identification: { 148 | registered_number: "", 149 | registered_address: "", 150 | operation_period: "", 151 | licenses_scope: "", 152 | licenses_number: "", 153 | licenses_date: "", 154 | legal_person: "", 155 | identificate_date: null, 156 | identificate_agency: "", 157 | company_name: "" 158 | }, 159 | float_minimum_order_amount: 20, 160 | float_delivery_fee: 5, 161 | distance: "19.5公里", 162 | order_lead_time: "40分钟", 163 | description: "好吃的", 164 | delivery_mode: { 165 | color: "57A9FF", 166 | id: 1, 167 | is_solid: true, 168 | text: "蜂鸟专送" 169 | }, 170 | activities: [ 171 | { 172 | icon_name: "减", 173 | name: "满减优惠", 174 | description: "满30减5,满60减8", 175 | icon_color: "f07373", 176 | id: 1, 177 | _id: "591bec73c2bbc84a6328a1e7" 178 | } 179 | ], 180 | } 181 | ] 182 | } 183 | 184 | 185 | ## 4、根据经纬度和关键字搜索商铺列表 186 | 187 | ### 请求URL: 188 | http://localhost:5000/search_shops 189 | 例子: http://localhost:5000/search_shops?keyword=test&geohash=40.10038,116.36867 190 | 191 | ### 请求方式: 192 | GET 193 | 194 | ### 参数类型:query 195 | |参数 |是否必选 |类型 |说明| 196 | |geohash     |Y       |string   |经纬度 197 | |keyword     |Y       |string  |关键字 198 | 199 | ### 返回示例: 200 | { 201 | "code": 0, 202 | "data": [ 203 | { 204 | "name": "test_shop", 205 | "address": "广东省广州市海珠区马涌直街20号", 206 | "id": 1196, 207 | "latitude": 23.09499, 208 | "longitude": 113.26166, 209 | "location": [ 210 | 113.26166, 211 | 23.09499 212 | ], 213 | "phone": "18320326523", 214 | "category": "鲜花蛋糕/面包", 215 | "supports": [ 216 | { 217 | "description": "准时必达,超时秒赔", 218 | "icon_color": "57A9FF", 219 | "icon_name": "准", 220 | "id": 9, 221 | "name": "准时达", 222 | "_id": "5ad00b4febf543051ea2e5f6" 223 | }, 224 | { 225 | "description": "该商家支持开发票,请在下单时填写好发票抬头", 226 | "icon_color": "999999", 227 | "icon_name": "票", 228 | "id": 4, 229 | "name": "开发票", 230 | "_id": "5ad00b4febf543051ea2e5f5" 231 | } 232 | ], 233 | "status": 1, 234 | "recent_order_num": 444, 235 | "rating_count": 246, 236 | "rating": 4, 237 | "promotion_info": "便靓正", 238 | "piecewise_agent_fee": { 239 | "tips": "配送费约¥5" 240 | }, 241 | "opening_hours": [ 242 | "09:00/21:30" 243 | ], 244 | "license": { 245 | "catering_service_license_image": "162bcabb96f9264.jpg", 246 | "business_license_image": "162bcabb9869263.jpg" 247 | }, 248 | "is_new": true, 249 | "is_premium": true, 250 | "image_path": "162bcab6f889262.jpg", 251 | "identification": { 252 | "registered_number": "", 253 | "registered_address": "", 254 | "operation_period": "", 255 | "licenses_scope": "", 256 | "licenses_number": "", 257 | "licenses_date": "", 258 | "legal_person": "", 259 | "identificate_date": null, 260 | "identificate_agency": "", 261 | "company_name": "" 262 | }, 263 | "float_minimum_order_amount": 20, 264 | "float_delivery_fee": 5, 265 | "distance": "2124.6公里", 266 | "order_lead_time": "31小时27分钟", 267 | "description": "普通商店", 268 | "delivery_mode": { 269 | "color": "57A9FF", 270 | "id": 1, 271 | "is_solid": true, 272 | "text": "蜂鸟专送" 273 | }, 274 | "activities": [ 275 | { 276 | "icon_name": "减", 277 | "name": "满减优惠", 278 | "description": "参加活动满减优惠", 279 | "icon_color": "f07373", 280 | "id": 1, 281 | "_id": "5ad00b4febf543051ea2e5f7" 282 | } 283 | ], 284 | "__v": 0 285 | }, 286 | { 287 | "name": "test", 288 | "address": "浙江省杭州市余杭区高教路阿里巴巴西溪园区2号楼", 289 | "id": 1271, 290 | "latitude": 30.27817, 291 | "longitude": 120.022003, 292 | "location": [ 293 | 120.022003, 294 | 30.27817 295 | ], 296 | "phone": "111", 297 | "category": "快餐便当/简餐", 298 | "supports": [ 299 | { 300 | "description": "已加入“外卖保”计划,食品安全有保障", 301 | "icon_color": "999999", 302 | "icon_name": "保", 303 | "id": 7, 304 | "name": "外卖保", 305 | "_id": "5ad7101aebf543051ea30192" 306 | }, 307 | { 308 | "description": "准时必达,超时秒赔", 309 | "icon_color": "57A9FF", 310 | "icon_name": "准", 311 | "id": 9, 312 | "name": "准时达", 313 | "_id": "5ad7101aebf543051ea30191" 314 | }, 315 | { 316 | "description": "该商家支持开发票,请在下单时填写好发票抬头", 317 | "icon_color": "999999", 318 | "icon_name": "票", 319 | "id": 4, 320 | "name": "开发票", 321 | "_id": "5ad7101aebf543051ea30190" 322 | } 323 | ], 324 | "status": 1, 325 | "recent_order_num": 820, 326 | "rating_count": 305, 327 | "rating": 4.2, 328 | "promotion_info": "111", 329 | "piecewise_agent_fee": { 330 | "tips": "配送费约¥5" 331 | }, 332 | "opening_hours": [ 333 | "05:30/05:45" 334 | ], 335 | "license": { 336 | "catering_service_license_image": "162d816cf909817.png", 337 | "business_license_image": "162d816c82e9816.png" 338 | }, 339 | "is_new": true, 340 | "is_premium": true, 341 | "image_path": "162d81696a79815.png", 342 | "identification": { 343 | "registered_number": "", 344 | "registered_address": "", 345 | "operation_period": "", 346 | "licenses_scope": "", 347 | "licenses_number": "", 348 | "licenses_date": "", 349 | "legal_person": "", 350 | "identificate_date": null, 351 | "identificate_agency": "", 352 | "company_name": "" 353 | }, 354 | "float_minimum_order_amount": 20, 355 | "float_delivery_fee": 5, 356 | "distance": "1265.1公里", 357 | "order_lead_time": "18小时13分钟", 358 | "description": "111", 359 | "delivery_mode": { 360 | "color": "57A9FF", 361 | "id": 1, 362 | "is_solid": true, 363 | "text": "蜂鸟专送" 364 | }, 365 | "activities": [], 366 | "__v": 0 367 | } 368 | ] 369 | } 370 | 371 | ## 5、获取一次性验证码 372 | 373 | ### 请求URL: 374 | http://localhost:5000/captcha 375 | 376 | ### 请求方式: 377 | GET 378 | 379 | ### 返回示例: 380 | 381 | 382 | 384 | 385 | 387 | 389 | 391 | 392 | 393 | 394 | ## 6、用户名密码登陆 395 | 396 | ### 请求URL: 397 | http://localhost:5000/login_pwd 398 | 399 | ### 请求方式: 400 | POST 401 | 402 | ### 参数类型: 请求体 403 | 404 | |参数 |是否必选 |类型 |说明 405 | |name     |Y       |string   |用户名 406 | |pwd     |Y      |string   |密码 407 | |captcha    |Y      |string   |图片验证码 408 | 409 | 410 | ### 返回示例: 411 | * 登陆成功 412 | { 413 | "code": 0, 414 | "data": { 415 | "_id": "5a9cd9c6ad5b2d34d42b385d", 416 | "name": "aaa" 417 | } 418 | } 419 | * 登陆失败 420 | { 421 | "code": 1, 422 | "msg": "用户名或密码不正确!" 423 | } 424 | 425 | 426 | ## 7、发送短信验证码 427 | 428 | ### 请求URL: 429 | http://localhost:5000/sendcode 430 | 431 | ### 示例: 432 | [http://localhost:5000/sendcode?phone=13716962779](http://localhost:5000/sendcode?phone=13716962779) 433 | 434 | ### 请求方式: 435 | GET 436 | 437 | ### 参数类型: query 438 | 439 | |参数 |是否必选 |类型 |说明 440 | |phone     |Y       |string   |手机号 441 | 442 | ### 返回示例: 443 | 成功 444 | { 445 | "code": 0, 446 | } 447 | 失败 448 | { 449 | "code": 1, 450 | msg: '短信验证码发送失败' 451 | } 452 | 453 | 454 | ## 8、手机号验证码登陆 455 | 456 | ### 请求URL: 457 | http://localhost:5000/login_sms 458 | 459 | ### 请求方式: 460 | POST 461 | 462 | ### 参数类型: 请求体 463 | 464 | |参数 |是否必选 |类型 |说明 465 | |phone     |Y       |string   |手机号 466 | |code     |Y      |string   |验证码 467 | 468 | ### 返回示例: 469 | * 登陆成功 470 | { 471 | "code": 0, 472 | "data": { 473 | "_id": "5a9cd9c6ad5b2d34d42b385d", 474 | "phone": "13716962779" 475 | } 476 | } 477 | * 登陆失败 478 | { 479 | "code": 1, 480 | "msg": "手机号或验证码不正确" 481 | } 482 | 483 | ### 9、根据会话获取用户信息 484 | 485 | #### 请求URL: 486 | http://localhost:5000/userinfo 487 | 488 | #### 请求方式: 489 | GET 490 | 491 | #### 返回示例: 492 | * 获取成功 493 | { 494 | "code": 0, 495 | "data": { 496 | "_id": "5a9cd9c6ad5b2d34d42b385d", 497 | "phone": "13716962779" 498 | } 499 | } 500 | * 获取失败 501 | { 502 | "code": 1, 503 | "msg": "请先登陆" 504 | } 505 | 506 | 507 | ### 10、用户登出 508 | 509 | #### 请求URL: 510 | http://localhost:5000/logout 511 | 512 | #### 请求方式: 513 | GET 514 | 515 | #### 返回示例: 516 | {code: 0} --------------------------------------------------------------------------------