├── static ├── .gitkeep ├── img │ ├── bj.png │ └── userImg.png └── fruit.json ├── src ├── components │ ├── detail │ │ ├── pages │ │ │ ├── ProDetai.vue │ │ │ └── ProInfo.vue │ │ ├── a.vue │ │ └── Detail.vue │ ├── home │ │ ├── Home.vue │ │ └── pages │ │ │ ├── HomeSwipe.vue │ │ │ └── HomeContainer.vue │ ├── pay │ │ ├── PaySuccess.vue │ │ └── Pay.vue │ ├── my │ │ └── My.vue │ ├── address │ │ ├── AddressList.vue │ │ └── AddressEdit.vue │ ├── article │ │ ├── Article.vue │ │ └── ArticleDetail.vue │ ├── collect │ │ └── Collection.vue │ ├── order │ │ ├── Order.vue │ │ └── OrderDetail.vue │ ├── car │ │ └── Car.vue │ └── area │ │ └── area.js ├── assets │ └── logo.png ├── font │ ├── iconfont.eot │ ├── iconfont.ttf │ ├── iconfont.woff │ ├── iconfont.css │ ├── iconfont.svg │ └── iconfont.js ├── vuex │ ├── index.js │ ├── type.js │ ├── actions.js │ ├── state.js │ └── mutations.js ├── main.js ├── App.vue ├── common │ ├── FooterBar.vue │ └── Header.vue ├── rem │ └── rem.js ├── router │ └── index.js └── styles │ └── reset.css ├── test ├── unit │ ├── setup.js │ ├── .eslintrc │ ├── specs │ │ └── HelloWorld.spec.js │ └── jest.conf.js └── e2e │ ├── specs │ └── test.js │ ├── custom-assertions │ └── elementCount.js │ ├── nightwatch.conf.js │ └── runner.js ├── config ├── prod.env.js ├── test.env.js ├── dev.env.js └── index.js ├── .editorconfig ├── .gitignore ├── .postcssrc.js ├── index.html ├── .babelrc ├── README.md └── package.json /static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/detail/pages/ProDetai.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/detail/pages/ProInfo.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/unit/setup.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | Vue.config.productionTip = false 4 | -------------------------------------------------------------------------------- /static/img/bj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dabaoRain/vueFruitShop/HEAD/static/img/bj.png -------------------------------------------------------------------------------- /config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dabaoRain/vueFruitShop/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /src/font/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dabaoRain/vueFruitShop/HEAD/src/font/iconfont.eot -------------------------------------------------------------------------------- /src/font/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dabaoRain/vueFruitShop/HEAD/src/font/iconfont.ttf -------------------------------------------------------------------------------- /src/font/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dabaoRain/vueFruitShop/HEAD/src/font/iconfont.woff -------------------------------------------------------------------------------- /static/img/userImg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dabaoRain/vueFruitShop/HEAD/static/img/userImg.png -------------------------------------------------------------------------------- /test/unit/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | }, 5 | "globals": { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /config/test.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const devEnv = require('./dev.env') 4 | 5 | module.exports = merge(devEnv, { 6 | NODE_ENV: '"testing"' 7 | }) 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/vuex/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import state from './state' 4 | import mutations from './mutations' 5 | import actions from './actions' 6 | 7 | Vue.use(Vuex) 8 | 9 | export default new Vuex.Store({ 10 | state, 11 | mutations, 12 | actions 13 | }) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | /test/unit/coverage/ 8 | /test/e2e/reports/ 9 | selenium-debug.log 10 | 11 | # Editor directories and files 12 | .idea 13 | .vscode 14 | *.suo 15 | *.ntvs* 16 | *.njsproj 17 | *.sln 18 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | vue-fruit-shop 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /test/unit/specs/HelloWorld.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import HelloWorld from '@/components/HelloWorld' 3 | 4 | describe('HelloWorld.vue', () => { 5 | it('should render correct contents', () => { 6 | const Constructor = Vue.extend(HelloWorld) 7 | const vm = new Constructor().$mount() 8 | expect(vm.$el.querySelector('.hello h1').textContent) 9 | .toEqual('Welcome to Your Vue.js App') 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /src/components/home/Home.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 18 | 19 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue' 4 | import App from './App' 5 | import router from './router' 6 | import axios from 'axios' 7 | import store from './vuex/index' 8 | Vue.config.productionTip = false 9 | Vue.prototype.$http = axios 10 | /* eslint-disable no-new */ 11 | new Vue({ 12 | el: '#app', 13 | router, 14 | store, 15 | components: { App }, 16 | template: '' 17 | }) 18 | -------------------------------------------------------------------------------- /.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", 12 | ["import", [{ "libraryName": "vant", "style": true }]] 13 | ], 14 | "env": { 15 | "test": { 16 | "presets": ["env", "stage-2"], 17 | "plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"] 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 14 | 15 | 30 | -------------------------------------------------------------------------------- /test/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // For authoring Nightwatch tests, see 2 | // http://nightwatchjs.org/guide#usage 3 | 4 | module.exports = { 5 | 'default e2e tests': function (browser) { 6 | // automatically uses dev Server port from /config.index.js 7 | // default: http://localhost:8080 8 | // see nightwatch.conf.js 9 | const devServer = browser.globals.devServerURL 10 | 11 | browser 12 | .url(devServer) 13 | .waitForElementVisible('#app', 5000) 14 | .assert.elementPresent('.hello') 15 | .assert.containsText('h1', 'Welcome to Your Vue.js App') 16 | .assert.elementCount('img', 1) 17 | .end() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/components/detail/a.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 37 | 38 | -------------------------------------------------------------------------------- /src/vuex/type.js: -------------------------------------------------------------------------------- 1 | export const PRAISE_ARTICLE ='PRAISE_ARTICLE' //文章收藏 2 | export const SET_FRUIT ='SET_FRUIT' //水果数据 3 | export const SET_ADDRESSLIST ='SET_ADDRESSLIST' //地址列表 4 | export const ADD_CARTS = 'ADD_CARTS' //加入购物车 5 | export const GET_ORDERS='GET_ORDERS' //所有订单 6 | export const SET_ORDERS='SET_ORDERS' //本次结算订单 7 | export const SET_CURRENTORDER='SET_CURRENTORDER' //当前操作订单 8 | export const SET_INDEX='SET_INDEX' //设置当前导航索引 9 | export const SET_ADDRESSEDIT='SET_ADDRESSEDIT' //设置当前地址编辑对象 10 | export const DEFAULT_ADDRESS='DEFAULT_ADDRESS' //设置当前地址 11 | export const EMPTY_ADDRESS='EMPTY_ADDRESS' //新增时清空当前编辑地址,避免新增输入框里面有编辑地址时的内容 12 | export const GET_ARTICLE ='GET_ARTICLE' //获取全部文章 13 | export const SET_ARTICLE ='SET_ARTICLE' //文章收藏 14 | export const COLLECT_GOODS='COLLECT_GOODS' //商品收藏 -------------------------------------------------------------------------------- /test/unit/jest.conf.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | rootDir: path.resolve(__dirname, '../../'), 5 | moduleFileExtensions: [ 6 | 'js', 7 | 'json', 8 | 'vue' 9 | ], 10 | moduleNameMapper: { 11 | '^@/(.*)$': '/src/$1' 12 | }, 13 | transform: { 14 | '^.+\\.js$': '/node_modules/babel-jest', 15 | '.*\\.(vue)$': '/node_modules/vue-jest' 16 | }, 17 | testPathIgnorePatterns: [ 18 | '/test/e2e' 19 | ], 20 | snapshotSerializers: ['/node_modules/jest-serializer-vue'], 21 | setupFiles: ['/test/unit/setup'], 22 | mapCoverage: true, 23 | coverageDirectory: '/test/unit/coverage', 24 | collectCoverageFrom: [ 25 | 'src/**/*.{js,vue}', 26 | '!src/main.js', 27 | '!src/router/index.js', 28 | '!**/node_modules/**' 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /test/e2e/custom-assertions/elementCount.js: -------------------------------------------------------------------------------- 1 | // A custom Nightwatch assertion. 2 | // The assertion name is the filename. 3 | // Example usage: 4 | // 5 | // browser.assert.elementCount(selector, count) 6 | // 7 | // For more information on custom assertions see: 8 | // http://nightwatchjs.org/guide#writing-custom-assertions 9 | 10 | exports.assertion = function (selector, count) { 11 | this.message = 'Testing if element <' + selector + '> has count: ' + count 12 | this.expected = count 13 | this.pass = function (val) { 14 | return val === this.expected 15 | } 16 | this.value = function (res) { 17 | return res.value 18 | } 19 | this.command = function (cb) { 20 | var self = this 21 | return this.api.execute(function (selector) { 22 | return document.querySelectorAll(selector).length 23 | }, [selector], function (res) { 24 | cb.call(self, res) 25 | }) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/common/FooterBar.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 43 | 44 | -------------------------------------------------------------------------------- /test/e2e/nightwatch.conf.js: -------------------------------------------------------------------------------- 1 | require('babel-register') 2 | var config = require('../../config') 3 | 4 | // http://nightwatchjs.org/gettingstarted#settings-file 5 | module.exports = { 6 | src_folders: ['test/e2e/specs'], 7 | output_folder: 'test/e2e/reports', 8 | custom_assertions_path: ['test/e2e/custom-assertions'], 9 | 10 | selenium: { 11 | start_process: true, 12 | server_path: require('selenium-server').path, 13 | host: '127.0.0.1', 14 | port: 4444, 15 | cli_args: { 16 | 'webdriver.chrome.driver': require('chromedriver').path 17 | } 18 | }, 19 | 20 | test_settings: { 21 | default: { 22 | selenium_port: 4444, 23 | selenium_host: 'localhost', 24 | silent: true, 25 | globals: { 26 | devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port) 27 | } 28 | }, 29 | 30 | chrome: { 31 | desiredCapabilities: { 32 | browserName: 'chrome', 33 | javascriptEnabled: true, 34 | acceptSslCerts: true 35 | } 36 | }, 37 | 38 | firefox: { 39 | desiredCapabilities: { 40 | browserName: 'firefox', 41 | javascriptEnabled: true, 42 | acceptSslCerts: true 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/common/Header.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 23 | 24 | -------------------------------------------------------------------------------- /src/rem/rem.js: -------------------------------------------------------------------------------- 1 | (function flexible (window, document) { 2 | var docEl = document.documentElement 3 | var dpr = window.devicePixelRatio || 1 4 | 5 | // adjust body font size 6 | function setBodyFontSize () { 7 | if (document.body) { 8 | document.body.style.fontSize = (12 * dpr) + 'px' 9 | } 10 | else { 11 | document.addEventListener('DOMContentLoaded', setBodyFontSize) 12 | } 13 | } 14 | 15 | setBodyFontSize(); 16 | // set 1rem = viewWidth / 10 17 | function setRemUnit () { 18 | var rem = docEl.clientWidth / 10 19 | docEl.style.fontSize = rem + 'px' 20 | } 21 | setRemUnit() 22 | 23 | // reset rem unit on page resize 24 | window.addEventListener('resize', setRemUnit) 25 | window.addEventListener('pageshow', function (e) { 26 | if (e.persisted) { 27 | setRemUnit() 28 | } 29 | }) 30 | 31 | // detect 0.5px supports 32 | if (dpr >= 2) { 33 | var fakeBody = document.createElement('body') 34 | var testElement = document.createElement('div') 35 | testElement.style.border = '.5px solid transparent' 36 | fakeBody.appendChild(testElement) 37 | docEl.appendChild(fakeBody) 38 | if (testElement.offsetHeight === 1) { 39 | docEl.classList.add('hairlines') 40 | } 41 | docEl.removeChild(fakeBody) 42 | } 43 | }(window, document)) -------------------------------------------------------------------------------- /src/components/pay/PaySuccess.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 39 | 40 | -------------------------------------------------------------------------------- /src/vuex/actions.js: -------------------------------------------------------------------------------- 1 | const actions={ 2 | 3 | //水果数据 4 | setFruit({commit},data){ 5 | commit('SET_FRUIT',data) 6 | }, 7 | //地址列表 8 | setAddresslist({commit},data){ 9 | commit('SET_ADDRESSLIST',data) 10 | }, 11 | 12 | //添加到购物车 13 | addCar({commit},data){ 14 | commit('ADD_CARTS',data) 15 | }, 16 | //购物车结算 17 | setOrders({commit},data){ 18 | commit('SET_ORDERS',data) 19 | }, 20 | //全部订单 21 | getAllOrders({commit},data){ 22 | commit('GET_ORDERS',data) 23 | }, 24 | 25 | //当前订单 26 | setCurrentorder({commit},data){ 27 | commit('SET_CURRENTORDER',data) 28 | }, 29 | //设置当前导航索引 30 | setIndex({commit},data){ 31 | commit('SET_INDEX',data) 32 | }, 33 | 34 | //设置当前地址编辑对象 35 | setAddressedit({commit},data){ 36 | commit('SET_ADDRESSEDIT',data) 37 | }, 38 | //设置当前收货地址 39 | defaultAddress({commit},data){ 40 | commit('DEFAULT_ADDRESS',data) 41 | }, 42 | //新增时清空当前编辑地址,避免新增输入框里面有编辑地址时的内容 43 | emptyAddress({commit}){ 44 | commit('EMPTY_ADDRESS') 45 | }, 46 | //获取全部文章 47 | getArticle({commit},data){ 48 | commit('GET_ARTICLE',data) 49 | }, 50 | //文章收藏 51 | setArticle({commit},data){ 52 | commit('SET_ARTICLE',data) 53 | }, 54 | 55 | //文章点赞 56 | praiseArticle({commit},data){ 57 | commit('SET_ARTICLE',data) 58 | }, 59 | //商品收藏 60 | collectGoods({commit},data){ 61 | commit('COLLECT_GOODS',data) 62 | } 63 | } 64 | 65 | export default actions -------------------------------------------------------------------------------- /src/vuex/state.js: -------------------------------------------------------------------------------- 1 | const state={ 2 | fruitData:localStorage["fruitData"]?JSON.parse(localStorage["fruitData"]): [], //水果数据 3 | addressList:localStorage["addressList"]?JSON.parse(localStorage["addressList"]): [], //地址列表 4 | addressEdit:localStorage.getItem("addressEdit")?JSON.parse(localStorage.getItem("addressEdit")):{}, //当前编辑地址对象 5 | carts:localStorage["carts"]?JSON.parse(localStorage["carts"]): [], //购物车 6 | orders:localStorage["orders"]?JSON.parse(localStorage["orders"]): [], //本次结算订单 7 | ordersList:localStorage["ordersList"]?JSON.parse(localStorage["ordersList"]): [], //全部订单 8 | payStyles:[ 9 | { 10 | id:"1", 11 | name:"在线支付", 12 | introduce:"支持支付宝支付、微信支付、银行卡支付、财付通等" 13 | }, 14 | { 15 | id:"2", 16 | name:"蚂蚁花呗", 17 | introduce:"花呗分期是花呗联合天猫淘宝推出的,面向互联网的赊购服务,通过支付宝轻松还款,0首付" 18 | }, 19 | { 20 | id:"3", 21 | name:"货到付款", 22 | introduce:"货到再付款,支持现金交易" 23 | } 24 | ], 25 | currentOrder:localStorage.getItem("currentOrder")?JSON.parse(localStorage.getItem("currentOrder")):{}, //当前操作订单 26 | nowIndex:localStorage.getItem("nowIndex")?JSON.parse(localStorage.getItem("nowIndex")):0, 27 | articles:localStorage["articles"]?JSON.parse(localStorage["articles"]): [], //全部文章 28 | articlesCollect:localStorage["articlesCollect"]?JSON.parse(localStorage["articlesCollect"]): [], //收藏文章 29 | goodsCollect:localStorage["goodsCollect"]?JSON.parse(localStorage["goodsCollect"]): [], //收藏商品 30 | } 31 | 32 | export default state -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue2.0全家桶实现vue水果商城 2 | vue全家桶 vue+vuex+vue-router+axios+localstorage+sass 还有就是vant-ui组件库 3 | 4 | 5 | ## 开头 6 | 7 | ### 在线预览 8 | 暂无 9 | 10 | ### 初次见面 请多指教 11 | 项目源码地址:[vue-fruit-shop](https://github.com/dabaoRain/vueFruitShop),觉得还可以的话给个star 在这先谢谢了~
12 | 做前端有时间了,第一次在github发项目,有什么不足的地方,大家多多指教!所以帮忙star鼓励下!🙏 13 | 14 | ## 写在前面 15 | 16 | 本项目是参考github一个vivo商城项目,地址为https://github.com/Mynameisfwk/vivo-shop 17 | 参考该项目思路,在这里对原作者表示感谢。 18 | 参考网站:http://jspang.com/ vue官网 19 | 20 | 21 | 22 | # 技术栈 23 | > [vue-router](https://router.vuejs.org/zh-cn/) 是官方提供的路由器,使用vue.js构建单页面应用程序变得轻而易举。 24 | 25 | > [vuex](https://vuex.vuejs.org/zh-cn/) 是一个专为 vue.js 应用程序开发的状态管理模式,简单来说Vuex就是管理数据的。 26 | 27 | > [Vant UI](https://www.youzanyun.com/zanui/vant) 有赞前端团队基于有赞统一的规范实现的 Vue 组件库,提供了一整套 UI 基础组件和业务组件。 28 | 29 | > [localstorage] 本地存储对象 可以结合vuex存放用户操作数据 30 | 31 | > [axios](https://www.npmjs.com/package/axios):用来请求后端api数据 32 | 33 | > [sass](https://www.sass.hk/):css预编译---变量 css嵌套循环的使用 34 | 35 | 36 | ## 效果预览 37 | 38 | 暂无 39 | 40 | ## 开发目的 41 | 42 | 一直在学习vue,就尝试自己开发一个项目放到github上,与各位多多交流,更好的晚上自己的技术 43 | 44 | 45 | ## 实现功能 46 | 商品详情、文章详情、订单详情、订单提交、、商品/文章收藏、购物车功能(增、删、单全选)、订单管理、收货地址管理、localstorage储存等功能 47 | 48 | 49 | ## 项目运行 50 | ``` 51 | # 安装项目依赖 52 | npm install 53 | 54 | # 启动服务 访问http://localhost:8080 55 | npm run dev 56 | 57 | # 编译打包 58 | npm run build 59 | ``` 60 | 61 | ## 贵在坚持 62 | 63 | 冰冻三尺非一日之寒,与君共勉。 64 | 65 | 66 | ## 写在最后 67 | 数据都来自于水果网官网如有侵权请请联系删除这个小项目做的有点粗糙可以说是非常粗糙、各位将就看看吧、大佬轻喷、还有就是我要厚着脸皮要个star 感谢 🙏 68 | 以上readme 也是参考github上构建格式,以后会更加完善。 69 | 70 | 71 | -------------------------------------------------------------------------------- /src/components/my/My.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 37 | 38 | -------------------------------------------------------------------------------- /test/e2e/runner.js: -------------------------------------------------------------------------------- 1 | // 1. start the dev server using production config 2 | process.env.NODE_ENV = 'testing' 3 | 4 | const webpack = require('webpack') 5 | const DevServer = require('webpack-dev-server') 6 | 7 | const webpackConfig = require('../../build/webpack.prod.conf') 8 | const devConfigPromise = require('../../build/webpack.dev.conf') 9 | 10 | let server 11 | 12 | devConfigPromise.then(devConfig => { 13 | const devServerOptions = devConfig.devServer 14 | const compiler = webpack(webpackConfig) 15 | server = new DevServer(compiler, devServerOptions) 16 | const port = devServerOptions.port 17 | const host = devServerOptions.host 18 | return server.listen(port, host) 19 | }) 20 | .then(() => { 21 | // 2. run the nightwatch test suite against it 22 | // to run in additional browsers: 23 | // 1. add an entry in test/e2e/nightwatch.conf.js under "test_settings" 24 | // 2. add it to the --env flag below 25 | // or override the environment flag, for example: `npm run e2e -- --env chrome,firefox` 26 | // For more information on Nightwatch's config file, see 27 | // http://nightwatchjs.org/guide#settings-file 28 | let opts = process.argv.slice(2) 29 | if (opts.indexOf('--config') === -1) { 30 | opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js']) 31 | } 32 | if (opts.indexOf('--env') === -1) { 33 | opts = opts.concat(['--env', 'chrome']) 34 | } 35 | 36 | const spawn = require('cross-spawn') 37 | const runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' }) 38 | 39 | runner.on('exit', function (code) { 40 | server.close() 41 | process.exit(code) 42 | }) 43 | 44 | runner.on('error', function (err) { 45 | server.close() 46 | throw err 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /src/components/home/pages/HomeSwipe.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 62 | 63 | -------------------------------------------------------------------------------- /src/font/iconfont.css: -------------------------------------------------------------------------------- 1 | 2 | @font-face { 3 | font-family: 'custom-iconfont'; 4 | src: url('./iconfont.ttf') format('truetype'); 5 | } 6 | 7 | .van-icon { 8 | font-family: 'vant-icon', 'custom-iconfont' !important; 9 | } 10 | 11 | .iconfont { 12 | font-family:"iconfont" !important; 13 | font-size:16px; 14 | font-style:normal; 15 | -webkit-font-smoothing: antialiased; 16 | -moz-osx-font-smoothing: grayscale; 17 | } 18 | 19 | .icon-icon3:before { content: "\e62b"; } 20 | 21 | .icon-shoucangxing:before { content: "\e813"; } 22 | 23 | .icon-checkboxround0:before { content: "\e672"; } 24 | 25 | .icon-checkbox-marked-circle:before { content: "\e69f"; } 26 | 27 | .icon-shanchu:before { content: "\e600"; } 28 | 29 | .icon-zuojiantou:before { content: "\e641"; } 30 | 31 | .icon-chenggong:before { content: "\e625"; } 32 | 33 | 34 | .icon-gouwucheman:before { content: "\e602"; } 35 | .icon-user:before { content: "\e610"; } 36 | .icon-wenzhang:before { content: "\e624"; } 37 | .icon-shouye:before { content: "\e626"; } 38 | .icon-dianzan:before { content: "\e609"; } 39 | 40 | .van-icon-icon3:before { 41 | content: "\e62b"; 42 | } 43 | .van-icon-checkboxround0:before { 44 | content: "\e672"; 45 | } 46 | .van-icon-checkbox-marked-circle:before { 47 | content: "\e69f"; 48 | } 49 | .van-icon-shanchu:before { 50 | content: "\e600"; 51 | } 52 | .van-icon-zuojiantou:before { 53 | content: "\e641"; 54 | } 55 | .van-icon-chenggong:before { 56 | content: "\e625"; 57 | } 58 | 59 | 60 | .van-icon-gouwucheman:before { 61 | content: "\e602"; 62 | } 63 | .van-icon-user:before { 64 | content: "\e610"; 65 | } 66 | .van-icon-wenzhang:before { 67 | content: "\e624"; 68 | } 69 | .van-icon-shouye:before { 70 | content: "\e626"; 71 | } 72 | .van-icon-dianzan:before { 73 | content: "\e609"; 74 | } 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /src/components/address/AddressList.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 76 | 77 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import Home from '@/components/home/Home' 4 | import Detail from '@/components/detail/Detail' 5 | import Car from '@/components/car/Car' 6 | import Pay from '@/components/pay/Pay' 7 | import PaySuccess from '@/components/pay/PaySuccess' 8 | import Order from '@/components/order/Order' 9 | import OrderDetail from '@/components/order/OrderDetail' 10 | import My from '@/components/my/My' 11 | import Article from '@/components/article/Article' 12 | import ArticleDetail from '@/components/article/ArticleDetail' 13 | import AddressList from '@/components/address/AddressList' 14 | import AddressEdit from '@/components/address/AddressEdit' 15 | import Collection from '@/components/collect/Collection' 16 | Vue.use(Router) 17 | 18 | export default new Router({ 19 | routes: [ 20 | { 21 | path: '/', 22 | name: 'Home', 23 | component: Home 24 | }, 25 | { 26 | path: '/goodDetail', 27 | name: 'goodDetail', 28 | component: Detail 29 | },{ 30 | path: '/car', 31 | name: 'Car', 32 | component: Car 33 | }, 34 | { 35 | path: '/pay', 36 | name: 'Pay', 37 | component: Pay 38 | }, 39 | { 40 | path: '/paySuccess', 41 | name: 'PaySuccess', 42 | component: PaySuccess 43 | }, 44 | { 45 | path: '/order', 46 | name: 'Order', 47 | component: Order 48 | }, 49 | { 50 | path: '/orderDetail', 51 | name: 'OrderDetail', 52 | component: OrderDetail 53 | }, 54 | { 55 | path: '/my', 56 | name: 'My', 57 | component: My 58 | }, 59 | { 60 | path: '/article', 61 | name: 'Article', 62 | component: Article 63 | }, 64 | { 65 | path: '/articledetail', 66 | name: 'ArticleDetail', 67 | component: ArticleDetail 68 | }, 69 | { 70 | path: '/address', 71 | name: 'AddressList', 72 | component: AddressList 73 | }, 74 | { 75 | path: '/addressEdit', 76 | name: 'AddressEdit', 77 | component: AddressEdit 78 | }, 79 | { 80 | path: '/collect', 81 | name: 'Collection', 82 | component: Collection 83 | } 84 | ] 85 | }) 86 | -------------------------------------------------------------------------------- /src/styles/reset.css: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2011, KISSY UI Library v1.20 3 | MIT Licensed 4 | build time: Nov 28 12:38 5 | */ 6 | /* 7 | KISSY CSS Reset 8 | 理念:1. reset 的目的不是清除浏览器的默认样式,这仅是部分工作。清除和重置是紧密不可分的。 9 | 2. reset 的目的不是让默认样式在所有浏览器下一致,而是减少默认样式有可能带来的问题。 10 | 3. reset 期望提供一套普适通用的基础样式。但没有银弹,推荐根据具体需求,裁剪和修改后再使用。 11 | 特色:1. 适应中文;2. 基于最新主流浏览器。 12 | 维护:玉伯, 正淳 13 | */ 14 | 15 | /** 清除内外边距 **/ 16 | body, h1, h2, h3, h4, h5, h6, hr, p, blockquote, 17 | dl, dt, dd, ul, ol, li, 18 | pre, 19 | form, fieldset, legend, button, input, textarea, 20 | th, td { 21 | margin: 0; 22 | padding: 0; 23 | } { 24 | margin: 0; 25 | padding: 0; 26 | } 27 | 28 | /** 设置默认字体 **/ 29 | body, 30 | button, input, select, textarea /* for ie */ { 31 | font: 14px/1.5 arial,"Microsoft Yahei","Hiragino Sans GB",sans-serif; 32 | } 33 | h1, h2, h3, h4, h5, h6 { font-size: 100%; } 34 | address, cite, dfn, em, var { font-style: normal; } /* 将斜体扶正 */ 35 | code, kbd, pre, samp { font-family: courier new, courier, monospace; } /* 统一等宽字体 */ 36 | small { font-size: 12px; } /* 小于 12px 的中文很难阅读,让 small 正常化 */ 37 | 38 | /** 重置列表元素 **/ 39 | ul, ol { list-style: none; } 40 | 41 | /** 重置文本格式元素 **/ 42 | a { text-decoration: none; } 43 | /*a:hover { text-decoration: underline; }*/ 44 | 45 | sup { vertical-align: text-top; } /* 重置,减少对行高的影响 */ 46 | sub { vertical-align: text-bottom; } 47 | 48 | /** 重置表单元素 **/ 49 | legend { color: #000; } /* for ie6 */ 50 | fieldset, img { border: 0; } /* img 搭车:让链接里的 img 无边框 */ 51 | button, input, select, textarea { font-size: 100%; vertical-align: middle;} /* 使得表单元素在 ie 下能继承字体大小 */ 52 | /* 注:optgroup 无法扶正 */ 53 | 54 | /** 重置表格元素 **/ 55 | table { border-collapse: collapse; border-spacing: 0; } 56 | 57 | /* 重置 HTML5 元素 */ 58 | article, aside, details, figcaption, figure, footer,header, hgroup, menu, nav, section, 59 | summary, time, mark, audio, video { 60 | display: block; 61 | margin: 0; 62 | padding: 0; 63 | } 64 | mark { background: #ff0; } 65 | .clearfix:after { 66 | visibility: hidden; 67 | display: block; 68 | font-size: 0; 69 | content: " "; 70 | clear: both; 71 | height: 0; 72 | } 73 | .clearfix{*zoom:1;} 74 | .clearfix{ 75 | clear: both; 76 | } 77 | img{ vertical-align:middle;width: 100%;} 78 | -------------------------------------------------------------------------------- /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 | 15 | // Various Dev Server settings 16 | host: 'localhost', // can be overwritten by process.env.HOST 17 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 18 | autoOpenBrowser: false, 19 | errorOverlay: true, 20 | notifyOnErrors: true, 21 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 22 | 23 | 24 | /** 25 | * Source Maps 26 | */ 27 | 28 | // https://webpack.js.org/configuration/devtool/#development 29 | devtool: 'cheap-module-eval-source-map', 30 | 31 | // If you have problems debugging vue-files in devtools, 32 | // set this to false - it *may* help 33 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 34 | cacheBusting: true, 35 | 36 | cssSourceMap: true 37 | }, 38 | 39 | build: { 40 | // Template for index.html 41 | index: path.resolve(__dirname, '../dist/index.html'), 42 | 43 | // Paths 44 | assetsRoot: path.resolve(__dirname, '../dist'), 45 | assetsSubDirectory: 'static', 46 | assetsPublicPath: '/', 47 | 48 | /** 49 | * Source Maps 50 | */ 51 | 52 | productionSourceMap: true, 53 | // https://webpack.js.org/configuration/devtool/#production 54 | devtool: '#source-map', 55 | 56 | // Gzip off by default as many popular static hosts such as 57 | // Surge or Netlify already gzip all static assets for you. 58 | // Before setting to `true`, make sure to: 59 | // npm install --save-dev compression-webpack-plugin 60 | productionGzip: false, 61 | productionGzipExtensions: ['js', 'css'], 62 | 63 | // Run the build command with an extra argument to 64 | // View the bundle analyzer report after build finishes: 65 | // `npm run build --report` 66 | // Set to `true` or `false` to always turn it on or off 67 | bundleAnalyzerReport: process.env.npm_config_report 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/components/article/Article.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 66 | 67 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-fruit-shop", 3 | "version": "1.0.0", 4 | "description": "vue-fruit-shop", 5 | "author": "dabao", 6 | "private": true, 7 | "scripts": { 8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "unit": "jest --config test/unit/jest.conf.js --coverage", 11 | "e2e": "node test/e2e/runner.js", 12 | "test": "npm run unit && npm run e2e", 13 | "build": "node build/build.js" 14 | }, 15 | "dependencies": { 16 | "axios": "^0.18.0", 17 | "vant": "^1.1.10", 18 | "vue": "^2.5.2", 19 | "vue-router": "^3.0.1", 20 | "vuex": "^3.0.1" 21 | }, 22 | "devDependencies": { 23 | "autoprefixer": "^7.1.2", 24 | "babel-core": "^6.22.1", 25 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 26 | "babel-jest": "^21.0.2", 27 | "babel-loader": "^7.1.1", 28 | "babel-plugin-dynamic-import-node": "^1.2.0", 29 | "babel-plugin-import": "^1.8.0", 30 | "babel-plugin-syntax-jsx": "^6.18.0", 31 | "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0", 32 | "babel-plugin-transform-runtime": "^6.22.0", 33 | "babel-plugin-transform-vue-jsx": "^3.5.0", 34 | "babel-preset-env": "^1.3.2", 35 | "babel-preset-stage-2": "^6.22.0", 36 | "babel-register": "^6.22.0", 37 | "chalk": "^2.0.1", 38 | "chromedriver": "^2.27.2", 39 | "copy-webpack-plugin": "^4.0.1", 40 | "cross-spawn": "^5.0.1", 41 | "css-loader": "^0.28.0", 42 | "extract-text-webpack-plugin": "^3.0.0", 43 | "file-loader": "^1.1.4", 44 | "friendly-errors-webpack-plugin": "^1.6.1", 45 | "html-webpack-plugin": "^2.30.1", 46 | "jest": "^22.0.4", 47 | "jest-serializer-vue": "^0.3.0", 48 | "nightwatch": "^0.9.12", 49 | "node-notifier": "^5.1.2", 50 | "node-sass": "^4.9.2", 51 | "optimize-css-assets-webpack-plugin": "^3.2.0", 52 | "ora": "^1.2.0", 53 | "portfinder": "^1.0.13", 54 | "postcss-import": "^11.0.0", 55 | "postcss-loader": "^2.0.8", 56 | "postcss-url": "^7.2.1", 57 | "rimraf": "^2.6.0", 58 | "sass-loader": "^7.0.3", 59 | "selenium-server": "^3.0.1", 60 | "semver": "^5.3.0", 61 | "shelljs": "^0.7.6", 62 | "uglifyjs-webpack-plugin": "^1.1.1", 63 | "url-loader": "^0.5.8", 64 | "vue-jest": "^1.0.2", 65 | "vue-loader": "^13.3.0", 66 | "vue-style-loader": "^3.0.1", 67 | "vue-template-compiler": "^2.5.2", 68 | "webpack": "^3.6.0", 69 | "webpack-bundle-analyzer": "^2.9.0", 70 | "webpack-dev-server": "^2.9.1", 71 | "webpack-merge": "^4.1.0" 72 | }, 73 | "engines": { 74 | "node": ">= 6.0.0", 75 | "npm": ">= 3.0.0" 76 | }, 77 | "browserslist": [ 78 | "> 1%", 79 | "last 2 versions", 80 | "not ie <= 8" 81 | ] 82 | } 83 | -------------------------------------------------------------------------------- /src/components/address/AddressEdit.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 88 | 89 | 96 | 97 | -------------------------------------------------------------------------------- /src/components/article/ArticleDetail.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 68 | 69 | -------------------------------------------------------------------------------- /src/components/collect/Collection.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 85 | 86 | -------------------------------------------------------------------------------- /src/components/home/pages/HomeContainer.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 96 | 97 | -------------------------------------------------------------------------------- /src/components/order/Order.vue: -------------------------------------------------------------------------------- 1 | 53 | 54 | 91 | 92 | -------------------------------------------------------------------------------- /src/components/order/OrderDetail.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 74 | 75 | -------------------------------------------------------------------------------- /src/components/car/Car.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 124 | 125 | -------------------------------------------------------------------------------- /src/components/detail/Detail.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 135 | 136 | -------------------------------------------------------------------------------- /src/vuex/mutations.js: -------------------------------------------------------------------------------- 1 | import state from './state' 2 | import * as type from './type.js' 3 | import { Dialog } from 'vant'; 4 | const matutaions={ 5 | //水果数据 6 | [type.SET_FRUIT](state,data){ 7 | state.fruitData = data; 8 | localStorage.setItem("fruitData",JSON.stringify(state.fruitData)); 9 | }, 10 | //地址列表 11 | [type.SET_ADDRESSLIST](state,data){ 12 | state.addressList = data; 13 | localStorage.setItem("addressList",JSON.stringify(state.addressList)); 14 | }, 15 | //购物车 16 | [type.ADD_CARTS](state,data){ 17 | state.carts.push(data); 18 | localStorage.setItem("carts",JSON.stringify(state.carts)); 19 | }, 20 | //购物车删除 21 | shanchu:(state,index)=>{ 22 | Dialog.confirm({ 23 | title: '确认删除', 24 | message: '您确认删除嘛?' 25 | }).then(() => { 26 | state.carts.splice(index,1) 27 | localStorage.setItem("carts",JSON.stringify(state.carts)); 28 | }).catch(() => { 29 | }); 30 | }, 31 | //订单 32 | [type.SET_ORDERS](state,data){ //本次结算订单 33 | state.orders = data 34 | localStorage.setItem("orders",JSON.stringify(state.orders)); 35 | }, 36 | //获取全部订单 37 | [type.GET_ORDERS](state,data){ 38 | state.ordersList.push(data); 39 | localStorage.setItem("ordersList",JSON.stringify(state.ordersList)); 40 | }, 41 | //当前操作订单 42 | [type.SET_CURRENTORDER](state,data){ 43 | state.currentOrder = data 44 | localStorage.setItem("currentOrder",JSON.stringify(state.currentOrder)); 45 | }, 46 | //设置当前导航索引 47 | 48 | [type.SET_INDEX](state,data){ 49 | state.nowIndex = data 50 | localStorage.setItem("nowIndex",JSON.stringify(state.nowIndex)); 51 | }, 52 | 53 | //设置当前地址编辑对象 54 | [type.SET_ADDRESSEDIT](state,data){ 55 | state.addressEdit = data 56 | localStorage.setItem("addressEdit",JSON.stringify(state.addressEdit)); 57 | }, 58 | //设置当前地址 59 | [type.DEFAULT_ADDRESS](state,data){ 60 | state.defaultAddress = data 61 | //先将地址列表的默认地址都设置为false 62 | state.addressList.forEach((item,index)=>{ 63 | item.is_default = false 64 | }); 65 | var addressId = data.id; 66 | //再讲当前选中地址设置为true 67 | state.addressList.forEach((item,index)=>{ 68 | if(item.id === addressId){ 69 | item.is_default = true 70 | } 71 | }); 72 | console.log(state.addressList); 73 | localStorage.setItem("addressList",JSON.stringify(state.addressList)); 74 | }, 75 | //新增时清空当前编辑地址,避免新增输入框里面有编辑地址时的内容 76 | [type.EMPTY_ADDRESS](state){ 77 | state.addressEdit = {}; 78 | localStorage.setItem("addressEdit",JSON.stringify(state.addressEdit)); 79 | }, 80 | //获取全部文章 81 | [type.GET_ARTICLE](state,data){ 82 | state.articles = data 83 | localStorage.setItem("articles",JSON.stringify(state.articles)); 84 | }, 85 | 86 | //收藏文章 87 | [type.SET_ARTICLE](state,data){ 88 | var collectId = data.id; 89 | if(data.isCollected){ 90 | state.articles.forEach((item)=>{ 91 | if(item.id === collectId){ 92 | item.isCollected = false 93 | } 94 | }); 95 | state.articlesCollect.forEach((item,index)=>{ 96 | if(item.id === collectId){ 97 | state.articlesCollect.splice(index,1); 98 | } 99 | }) 100 | } else { 101 | state.articles.forEach((item)=>{ 102 | if(item.id === collectId){ 103 | item.isCollected = true 104 | } 105 | }); 106 | state.articlesCollect.push(data); 107 | } 108 | localStorage.setItem("articles",JSON.stringify(state.articles)); 109 | localStorage.setItem("articlesCollect",JSON.stringify(state.articlesCollect)); 110 | }, 111 | //点赞文章 112 | [type.PRAISE_ARTICLE](state,data){ 113 | }, 114 | //收藏商品 115 | [type.COLLECT_GOODS](state,data){ 116 | var collectId = data.id; 117 | if(data.isCollected){ 118 | state.fruitData.forEach((item,index)=>{ 119 | if(item.id === collectId){ 120 | item.isCollected = false 121 | } 122 | }) 123 | state.fruitData.forEach((item,index)=>{ 124 | if(item.id === collectId){ 125 | state.goodsCollect.splice(index,1); 126 | } 127 | }) 128 | }else{ 129 | state.fruitData.forEach((item,index)=>{ 130 | if(item.id === collectId){ 131 | item.isCollected = true 132 | } 133 | }); 134 | state.goodsCollect.push(data); 135 | } 136 | localStorage.setItem("goodsCollect",JSON.stringify(state.goodsCollect)); 137 | localStorage.setItem("fruitData",JSON.stringify(state.fruitData)); 138 | }, 139 | //文章删除 140 | del:(state,index)=>{ 141 | MessageBox.confirm('确定取消收藏该文章么?').then(action=>{ 142 | state.article.splice(index,1) 143 | localStorage.setItem("article",JSON.stringify(state.article)); 144 | }) 145 | }, 146 | //商品删除 147 | cancel:(state,index)=>{ 148 | MessageBox.confirm('确定取消收藏该商品么?').then(action=>{ 149 | state.collections.splice(index,1) 150 | localStorage.setItem("collections",JSON.stringify(state.collections)); 151 | }) 152 | }, 153 | laji:(state,index)=>{ 154 | MessageBox.confirm('确定删除收货地址么?').then(action=>{ 155 | state.address.splice(index,1) 156 | localStorage.setItem("address",JSON.stringify(state.address)); 157 | }) 158 | }, 159 | //订单删除 160 | delOrders:(state,index)=>{ 161 | Dialog.confirm({ 162 | title: '确认删除', 163 | message: '确定删除该订单么?' 164 | }).then(() => { 165 | state.ordersList.splice(index,1) 166 | localStorage.setItem("ordersList",JSON.stringify(state.ordersList)); 167 | }).catch(() => { 168 | }); 169 | }, 170 | 171 | 172 | //数量加 173 | add(state,index){ 174 | state.carts[index].value++ 175 | }, 176 | //数量减 177 | reduce(state,index){ 178 | state.carts[index].value==1?state.carts[index].value=1: state.carts[index].value-- 179 | }, 180 | 181 | settlement:(state,data)=>{ 182 | state.carts=[]; 183 | localStorage.setItem("carts",JSON.stringify(state.carts)); 184 | }, 185 | } 186 | 187 | export default matutaions -------------------------------------------------------------------------------- /src/components/pay/Pay.vue: -------------------------------------------------------------------------------- 1 |