├── .gitignore ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── prettier.config.js ├── public ├── index.html ├── js │ └── flexible.js └── logo.png ├── src ├── App.vue ├── assets │ ├── css │ │ ├── base.css │ │ └── normalize.min.css │ └── img │ │ ├── common │ │ ├── back.svg │ │ ├── collect.svg │ │ ├── placeholder.png │ │ └── top.png │ │ ├── detail │ │ └── detail_bottom.png │ │ ├── home │ │ └── recommend_bg.png │ │ └── profile │ │ ├── arrow_right.png │ │ ├── phone.png │ │ └── user.png ├── common │ ├── mixin.js │ └── utils.js ├── components │ ├── common │ │ ├── gridView │ │ │ └── GridView.vue │ │ ├── navbar │ │ │ └── NavBar.vue │ │ ├── scroll │ │ │ └── Scroll.vue │ │ ├── swiper │ │ │ └── MySwiper.vue │ │ ├── tabbar │ │ │ └── TabBar.vue │ │ └── toast │ │ │ ├── MyToast.vue │ │ │ └── index.js │ └── content │ │ ├── backTop │ │ └── BackTop.vue │ │ ├── goods │ │ └── GoodsList.vue │ │ └── tabControl │ │ └── TabControl.vue ├── main-dev.js ├── main-prod.js ├── network │ ├── category.js │ ├── home.js │ ├── productDetail.js │ └── request.js ├── router │ └── index.js ├── store │ ├── actions.js │ ├── getters.js │ ├── index.js │ ├── mutations.js │ └── types.js └── views │ ├── cart │ ├── Cart.vue │ └── children │ │ └── CartList.vue │ ├── category │ ├── Category.vue │ └── children │ │ ├── SlideBar.vue │ │ └── Subcategory.vue │ ├── detail │ ├── ProductDetail.vue │ └── children │ │ ├── DetailBaseInfo.vue │ │ ├── DetailBottomBar.vue │ │ ├── DetailCommentInfo.vue │ │ ├── DetailImagesInfo.vue │ │ ├── DetailParamInfo.vue │ │ ├── DetailShopInfo.vue │ │ ├── DetailSwiper.vue │ │ └── ProductDetailNavBar.vue │ ├── home │ ├── Home.vue │ └── children │ │ ├── FeatureView.vue │ │ └── RecommendView.vue │ └── profile │ ├── Profile.vue │ └── children │ ├── Login.vue │ ├── Money.vue │ └── profileList.vue └── vue.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | /dist 3 | 4 | # local env files 5 | .env.local 6 | .env.*.local 7 | 8 | # Log files 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | 13 | # Editor directories and files 14 | .idea 15 | .vscode 16 | *.suo 17 | *.ntvs* 18 | *.njsproj 19 | *.sln 20 | *.sw? 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue商城项目 2 | 3 | # 真实项目接口请联系微信coderwhy001 4 | 5 | ## 项目安装 6 | ``` 7 | npm install 8 | ``` 9 | 10 | ### 项目运行 11 | ``` 12 | npm run serve 13 | ``` 14 | 15 | ### 项目打包 16 | ``` 17 | npm run build 18 | ``` 19 | 20 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | const plugins = []; 2 | 3 | // 生产环境移除console 4 | if (process.env.NODE_ENV === "production") { 5 | plugins.push("transform-remove-console"); 6 | } 7 | 8 | module.exports = { 9 | plugins: [...plugins], 10 | presets: ["@vue/cli-plugin-babel/preset"] 11 | }; 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-shop", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build" 8 | }, 9 | "dependencies": { 10 | "axios": "^0.19.2", 11 | "babel-plugin-transform-remove-console": "^6.9.4", 12 | "better-scroll": "^1.15.2", 13 | "core-js": "^3.6.5", 14 | "cssnano": "^4.1.10", 15 | "postcss-import": "^12.0.1", 16 | "postcss-preset-env": "^6.7.0", 17 | "postcss-pxtorem": "^5.1.1", 18 | "vant": "^2.9.0", 19 | "vue": "^2.6.11", 20 | "vue-router": "^3.3.4", 21 | "vuex": "^3.5.1" 22 | }, 23 | "devDependencies": { 24 | "@vue/cli-plugin-babel": "^4.4.6", 25 | "@vue/cli-service": "^4.4.6", 26 | "vue-template-compiler": "^2.6.11" 27 | }, 28 | "browserslist": [ 29 | "last 5 versions", 30 | "ios >= 7.0", 31 | "android >= 4.0" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | "postcss-import": {}, 4 | "postcss-preset-env": {}, 5 | "postcss-pxtorem": { 6 | rootValue: 37.5, 7 | unitPrecision: 6, 8 | propList: ["*"], 9 | selectorBlackList: [".hairlines"], 10 | replace: true, 11 | mediaQuery: true, 12 | minPixelValue: 2 13 | }, 14 | cssnano: { 15 | "cssnano-preset-advanced": { 16 | zindex: false, 17 | autoprefixer: false 18 | } 19 | } 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 100, // 最大换行长度 3 | tabWidth: 2, // tab缩进大小,默认为2 4 | useTabs: false, // 使用tab缩进,默认false 5 | semi: true, // 使用分号, 默认true 6 | singleQuote: false, // 使用单引号, 默认false(在jsx中配置无效, 默认都是双引号) 7 | trailingComma: "none", // 行尾逗号,默认none,可选 8 | bracketSpacing: true, // 对象中的空格 默认true 9 | jsxBracketSameLine: false, // JSX标签闭合位置 默认false 10 | arrowParens: "avoid", // 箭头函数参数括号,默认always 可选 11 | htmlWhitespaceSensitivity: "ignore" // html空格严格程度,可选 12 | }; 13 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 小码哥商城 12 | 22 | 23 | <% if(htmlWebpackPlugin.options.isProd) { %> 24 | 25 | 26 | 27 | 28 | 29 | 30 | <% } %> 31 | 32 | 33 |
34 | 35 | 36 | -------------------------------------------------------------------------------- /public/js/flexible.js: -------------------------------------------------------------------------------- 1 | !function(e,t){function n(){t.body?t.body.style.fontSize=12*o+"px":t.addEventListener("DOMContentLoaded",n)}function d(){var e=i.clientWidth/10;i.style.fontSize=e+"px"}var i=t.documentElement,o=e.devicePixelRatio||1;if(n(),d(),e.addEventListener("resize",d),e.addEventListener("pageshow",function(e){e.persisted&&d()}),o>=2){var a=t.createElement("body"),s=t.createElement("div");s.style.border=".5px solid transparent",a.appendChild(s),i.appendChild(a),1===s.offsetHeight&&i.classList.add("hairlines"),i.removeChild(a)}}(window,document); 2 | -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaofei3062/vue-shop/e629477128d97eda11cdf693234c9551ffe8768f/public/logo.png -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 19 | -------------------------------------------------------------------------------- /src/assets/css/base.css: -------------------------------------------------------------------------------- 1 | @import "normalize.min.css"; 2 | 3 | * { 4 | box-sizing: border-box; 5 | margin: 0; 6 | padding: 0; 7 | } 8 | 9 | body, 10 | html { 11 | font-family: -apple-system, "MIUI"; 12 | width: 100%; 13 | height: 100%; 14 | color: #666666; 15 | background: #ffffff; 16 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 17 | } 18 | 19 | a { 20 | text-decoration: none; 21 | color: #666666; 22 | } 23 | 24 | .clear-fix:after { 25 | font-size: 0; 26 | display: block; 27 | clear: both; 28 | height: 0; 29 | content: ""; 30 | } 31 | 32 | .arrow-right { 33 | display: inline-block; 34 | width: 9px; 35 | height: 9px; 36 | margin-left: 0.1rem; 37 | transform: rotate(135deg); 38 | border-top: 1px solid #999999; 39 | border-left: 1px solid #999999; 40 | background-color: transparent; 41 | } 42 | 43 | .left { 44 | float: left; 45 | } 46 | 47 | .right { 48 | float: right; 49 | } 50 | 51 | .shop-container { 52 | font-size: 16px; 53 | overflow-x: hidden; 54 | width: 100%; 55 | height: 100%; 56 | } 57 | -------------------------------------------------------------------------------- /src/assets/css/normalize.min.css: -------------------------------------------------------------------------------- 1 | html { 2 | line-height: 1.15; 3 | -webkit-text-size-adjust: 100%; 4 | } 5 | 6 | body { 7 | margin: 0; 8 | } 9 | 10 | main { 11 | display: block; 12 | } 13 | 14 | h1 { 15 | font-size: 2em; 16 | margin: 0.67em 0; 17 | } 18 | 19 | hr { 20 | overflow: visible; 21 | box-sizing: content-box; 22 | height: 0; 23 | } 24 | 25 | pre { 26 | font-family: monospace, monospace; 27 | font-size: 1em; 28 | } 29 | 30 | a { 31 | background-color: transparent; 32 | } 33 | 34 | abbr[title] { 35 | text-decoration: underline; 36 | text-decoration: underline dotted; 37 | border-bottom: none; 38 | } 39 | 40 | b, 41 | strong { 42 | font-weight: bolder; 43 | } 44 | 45 | code, 46 | kbd, 47 | samp { 48 | font-family: monospace, monospace; 49 | font-size: 1em; 50 | } 51 | 52 | small { 53 | font-size: 80%; 54 | } 55 | 56 | sub, 57 | sup { 58 | font-size: 75%; 59 | line-height: 0; 60 | position: relative; 61 | vertical-align: baseline; 62 | } 63 | 64 | sub { 65 | bottom: -0.25em; 66 | } 67 | 68 | sup { 69 | top: -0.5em; 70 | } 71 | 72 | img { 73 | border-style: none; 74 | } 75 | 76 | button, 77 | input, 78 | optgroup, 79 | select, 80 | textarea { 81 | font-family: inherit; 82 | font-size: 100%; 83 | line-height: 1.15; 84 | margin: 0; 85 | } 86 | 87 | button, 88 | input { 89 | overflow: visible; 90 | } 91 | 92 | button, 93 | select { 94 | text-transform: none; 95 | } 96 | 97 | [type="button"], 98 | [type="reset"], 99 | [type="submit"], 100 | button { 101 | -webkit-appearance: button; 102 | } 103 | 104 | [type="button"]::-moz-focus-inner, 105 | [type="reset"]::-moz-focus-inner, 106 | [type="submit"]::-moz-focus-inner, 107 | button::-moz-focus-inner { 108 | padding: 0; 109 | border-style: none; 110 | } 111 | 112 | [type="button"]:-moz-focusring, 113 | [type="reset"]:-moz-focusring, 114 | [type="submit"]:-moz-focusring, 115 | button:-moz-focusring { 116 | outline: 1px dotted ButtonText; 117 | } 118 | 119 | fieldset { 120 | padding: 0.35em 0.75em 0.625em; 121 | } 122 | 123 | legend { 124 | display: table; 125 | box-sizing: border-box; 126 | max-width: 100%; 127 | padding: 0; 128 | white-space: normal; 129 | color: inherit; 130 | } 131 | 132 | progress { 133 | vertical-align: baseline; 134 | } 135 | 136 | textarea { 137 | overflow: auto; 138 | } 139 | 140 | [type="checkbox"], 141 | [type="radio"] { 142 | box-sizing: border-box; 143 | padding: 0; 144 | } 145 | 146 | [type="number"]::-webkit-inner-spin-button, 147 | [type="number"]::-webkit-outer-spin-button { 148 | height: auto; 149 | } 150 | 151 | [type="search"] { 152 | outline-offset: -2px; 153 | -webkit-appearance: textfield; 154 | } 155 | 156 | [type="search"]::-webkit-search-decoration { 157 | -webkit-appearance: none; 158 | } 159 | 160 | ::-webkit-file-upload-button { 161 | font: inherit; 162 | -webkit-appearance: button; 163 | } 164 | 165 | details { 166 | display: block; 167 | } 168 | 169 | summary { 170 | display: list-item; 171 | } 172 | 173 | template { 174 | display: none; 175 | } 176 | 177 | [hidden] { 178 | display: none; 179 | } 180 | -------------------------------------------------------------------------------- /src/assets/img/common/back.svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | 6 | 7 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/img/common/collect.svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/img/common/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaofei3062/vue-shop/e629477128d97eda11cdf693234c9551ffe8768f/src/assets/img/common/placeholder.png -------------------------------------------------------------------------------- /src/assets/img/common/top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaofei3062/vue-shop/e629477128d97eda11cdf693234c9551ffe8768f/src/assets/img/common/top.png -------------------------------------------------------------------------------- /src/assets/img/detail/detail_bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaofei3062/vue-shop/e629477128d97eda11cdf693234c9551ffe8768f/src/assets/img/detail/detail_bottom.png -------------------------------------------------------------------------------- /src/assets/img/home/recommend_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaofei3062/vue-shop/e629477128d97eda11cdf693234c9551ffe8768f/src/assets/img/home/recommend_bg.png -------------------------------------------------------------------------------- /src/assets/img/profile/arrow_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaofei3062/vue-shop/e629477128d97eda11cdf693234c9551ffe8768f/src/assets/img/profile/arrow_right.png -------------------------------------------------------------------------------- /src/assets/img/profile/phone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaofei3062/vue-shop/e629477128d97eda11cdf693234c9551ffe8768f/src/assets/img/profile/phone.png -------------------------------------------------------------------------------- /src/assets/img/profile/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaofei3062/vue-shop/e629477128d97eda11cdf693234c9551ffe8768f/src/assets/img/profile/user.png -------------------------------------------------------------------------------- /src/common/mixin.js: -------------------------------------------------------------------------------- 1 | import { debounce } from "./utils"; 2 | 3 | export const imgListenerMixin = { 4 | data() { 5 | return { 6 | // 监听图片 7 | imgListener: null 8 | }; 9 | }, 10 | mounted() { 11 | // 给防抖函数赋值一个新的函数 12 | const refresh = debounce(this.$refs.scroll.refresh, 50); 13 | 14 | // 接收发射的事件总线,并用监听图片变量保存 15 | this.imgListener = () => { 16 | refresh(); 17 | }; 18 | this.$bus.$on("imgLoad", this.imgListener); 19 | } 20 | }; 21 | 22 | export const backTopMixin = { 23 | data() { 24 | return { 25 | // 当前滚动的位置 26 | curPosition: 0 27 | }; 28 | }, 29 | methods: { 30 | // 回到顶部 31 | backTop() { 32 | // 调用子组件里面封装的scrollTo方法即可 33 | this.$refs.scroll.scrollTo(0, 0); 34 | } 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /src/common/utils.js: -------------------------------------------------------------------------------- 1 | // 防抖函数,解决重复频繁调用问题 2 | export function debounce(fn, delay) { 3 | let timer = null; 4 | return function (...args) { 5 | if (timer) { 6 | clearTimeout(timer); 7 | } 8 | timer = setTimeout(() => { 9 | fn.apply(this, args); 10 | }, delay); 11 | }; 12 | } 13 | 14 | function padLeftZero(str) { 15 | return ("00" + str).substr(str.length); 16 | } 17 | 18 | // 时间格式化 19 | export function formatDate(date, fmt) { 20 | if (/(y+)/.test(fmt)) { 21 | fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length)); 22 | } 23 | 24 | let o = { 25 | "M+": date.getMonth() + 1, 26 | "d+": date.getDate(), 27 | "h+": date.getHours(), 28 | "m+": date.getMinutes(), 29 | "s+": date.getSeconds() 30 | }; 31 | 32 | for (let k in o) { 33 | if (new RegExp(`(${k})`).test(fmt)) { 34 | let str = o[k] + ""; 35 | fmt = fmt.replace(RegExp.$1, RegExp.$1.length === 1 ? str : padLeftZero(str)); 36 | } 37 | } 38 | 39 | return fmt; 40 | } 41 | -------------------------------------------------------------------------------- /src/components/common/gridView/GridView.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 64 | 65 | 71 | -------------------------------------------------------------------------------- /src/components/common/navbar/NavBar.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 20 | 21 | 41 | -------------------------------------------------------------------------------- /src/components/common/scroll/Scroll.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 81 | -------------------------------------------------------------------------------- /src/components/common/swiper/MySwiper.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 37 | 38 | 57 | -------------------------------------------------------------------------------- /src/components/common/tabbar/TabBar.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 28 | 29 | 42 | -------------------------------------------------------------------------------- /src/components/common/toast/MyToast.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 28 | 29 | 43 | -------------------------------------------------------------------------------- /src/components/common/toast/index.js: -------------------------------------------------------------------------------- 1 | import MyToast from "./MyToast"; 2 | 3 | const myToast = {}; 4 | 5 | // 将对象安装到Vue上 6 | myToast.install = function (Vue) { 7 | // 1.创建组件构造器 8 | const toastConstructor = Vue.extend(MyToast); 9 | 10 | // 2.new的方式,根据组件构造器,可以创建出来一个组件对象 11 | const toast = new toastConstructor(); 12 | 13 | // 3.将组件对象手动挂载到某个元素上 14 | toast.$mount(document.createElement("div")); 15 | 16 | // 4.myToast.$el就已经挂载到上面创建的div了,然后将div挂载到body上即可 17 | document.body.appendChild(toast.$el); 18 | 19 | // 最后将myToast挂载到Vue的原型上 20 | Vue.prototype.$mytoast = toast; 21 | }; 22 | 23 | export default myToast; 24 | -------------------------------------------------------------------------------- /src/components/content/backTop/BackTop.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 26 | -------------------------------------------------------------------------------- /src/components/content/goods/GoodsList.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 69 | 70 | 145 | -------------------------------------------------------------------------------- /src/components/content/tabControl/TabControl.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 40 | 41 | 67 | -------------------------------------------------------------------------------- /src/main-dev.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | // 全局引入网络请求 3 | import "./network/request"; 4 | // 全局引入vant 5 | import vant from "vant"; 6 | import "vant/lib/index.css"; 7 | // 全局引入样式 8 | import "./assets/css/base.css"; 9 | import App from "./App.vue"; 10 | import router from "./router"; 11 | import store from "./store"; 12 | // 全局引入懒加载 13 | import { Lazyload } from "vant"; 14 | 15 | Vue.config.productionTip = false; 16 | 17 | Vue.use(vant); 18 | 19 | // 使用懒加载 20 | Vue.use(Lazyload, { 21 | // 未加载的占位图片 22 | loading: require("@/assets/img/common/placeholder.png") 23 | }); 24 | 25 | // 全局实例化$bus事件总线 26 | Vue.prototype.$bus = new Vue(); 27 | 28 | new Vue({ 29 | router, 30 | store, 31 | render: h => h(App) 32 | }).$mount("#app"); 33 | -------------------------------------------------------------------------------- /src/main-prod.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | // 全局引入网络请求 3 | import "./network/request"; 4 | // 全局引入vant 5 | import vant from "vant"; 6 | import "vant/lib/index.css"; 7 | // 全局引入样式 8 | import "./assets/css/base.css"; 9 | import App from "./App.vue"; 10 | import router from "./router"; 11 | import store from "./store"; 12 | // 全局引入懒加载 13 | import { Lazyload } from "vant"; 14 | 15 | Vue.config.productionTip = false; 16 | 17 | Vue.use(vant); 18 | 19 | // 使用懒加载 20 | Vue.use(Lazyload, { 21 | // 未加载的占位图片 22 | loading: require("@/assets/img/common/placeholder.png") 23 | }); 24 | 25 | // 全局实例化$bus事件总线 26 | Vue.prototype.$bus = new Vue(); 27 | 28 | new Vue({ 29 | router, 30 | store, 31 | render: h => h(App) 32 | }).$mount("#app"); 33 | -------------------------------------------------------------------------------- /src/network/category.js: -------------------------------------------------------------------------------- 1 | export function getCategory() { 2 | return axios({ 3 | url: "/category" 4 | }).catch(err => err); 5 | } 6 | 7 | export function getSubcategory(maitKey) { 8 | return axios({ 9 | url: "/subcategory", 10 | params: { 11 | maitKey 12 | } 13 | }).catch(err => err); 14 | } 15 | 16 | export function getCategoryDetail(miniWallkey, type) { 17 | return axios({ 18 | url: "/subcategory/detail", 19 | params: { 20 | miniWallkey, 21 | type 22 | } 23 | }).catch(err => err); 24 | } 25 | -------------------------------------------------------------------------------- /src/network/home.js: -------------------------------------------------------------------------------- 1 | export function getHomeMultiData() { 2 | return axios({ 3 | url: "/home/multidata" 4 | }).catch(err => err); 5 | } 6 | 7 | export function getHomeGoodsData(type, page) { 8 | return axios({ 9 | url: "/home/data", 10 | params: { type, page } 11 | }).catch(err => err); 12 | } 13 | -------------------------------------------------------------------------------- /src/network/productDetail.js: -------------------------------------------------------------------------------- 1 | export function getProductDetail(iid) { 2 | return axios({ 3 | url: "/detail", 4 | params: { iid } 5 | }).catch(err => err); 6 | } 7 | 8 | export function getRecommend() { 9 | return axios({ 10 | url: "/recommend" 11 | }).catch(err => err); 12 | } 13 | 14 | // ES6的类,详情数据 15 | export class Goods { 16 | constructor(itemInfo, columns, services) { 17 | this.title = itemInfo.title; 18 | this.desc = itemInfo.desc; 19 | this.newPrice = itemInfo.price; 20 | this.lowNowPrice = itemInfo.lowNowPrice; 21 | this.oldPrice = itemInfo.oldPrice; 22 | this.discount = itemInfo.discountDesc; 23 | this.discountBgColor = itemInfo.discountBgColor; 24 | this.columns = columns; 25 | this.services = services; 26 | this.nowPrice = itemInfo.highNowPrice; 27 | } 28 | } 29 | 30 | // 店铺数据 31 | export class Shop { 32 | constructor(shopInfo) { 33 | this.logo = shopInfo.shopLogo; 34 | this.name = shopInfo.name; 35 | this.fans = shopInfo.cFans; 36 | this.sells = shopInfo.cSells; 37 | this.score = shopInfo.score; 38 | this.goodsCount = shopInfo.cGoods; 39 | } 40 | } 41 | 42 | // 尺寸数据 43 | export class GoodsParams { 44 | constructor(info, rule) { 45 | // 注: images可能没有值(某些商品有值, 某些没有值) 46 | this.image = info.images ? info.images[0] : ""; 47 | this.infos = info.set; 48 | this.sizes = rule.tables; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/network/request.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { Toast } from "vant"; 3 | import Loading from "../store/index"; 4 | 5 | // 本接口为测试接口,真实接口请联系coderwhy001 6 | const url = "xxx"; 7 | 8 | let config = { 9 | baseURL: url, 10 | timeout: 10000 11 | }; 12 | 13 | const _axios = axios.create(config); 14 | 15 | // 请求拦截 16 | _axios.interceptors.request.use( 17 | req => { 18 | // 当getters里面的isLoading为true再显示请求加载 19 | if (Loading.getters.isLoading) { 20 | Toast.loading({ 21 | forbidClick: true, 22 | message: "加载中..." 23 | }); 24 | } 25 | return req; 26 | }, 27 | err => { 28 | return Promise.reject(err); 29 | } 30 | ); 31 | 32 | // 响应拦截 33 | _axios.interceptors.response.use( 34 | res => { 35 | Toast.clear(); 36 | return res.data; 37 | }, 38 | err => { 39 | Toast.clear(); 40 | return Promise.reject(err); 41 | } 42 | ); 43 | 44 | // 全局注册axios 45 | window.axios = _axios; 46 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import VueRouter from "vue-router"; 3 | 4 | // 重写路由push方法,阻止重复点击报错 5 | const originalPush = VueRouter.prototype.push; 6 | VueRouter.prototype.push = function push(location) { 7 | return originalPush.call(this, location).catch(err => err); 8 | }; 9 | 10 | // 重写路由replace方法,阻止重复点击报错 11 | const originalReplace = VueRouter.prototype.replace; 12 | VueRouter.prototype.replace = function replace(location) { 13 | return originalReplace.call(this, location).catch(err => err); 14 | }; 15 | 16 | Vue.use(VueRouter); 17 | 18 | // 正常路由加载,会将所有路由的js跟css合并到一个文件 19 | import Home from "../views/home/Home"; 20 | import Category from "../views/category/Category"; 21 | import Cart from "../views/cart/Cart"; 22 | import Profile from "../views/profile/Profile"; 23 | import ProductDetail from "../views/detail/ProductDetail"; 24 | 25 | // 路由配置 26 | const routes = [ 27 | { path: "/", name: "home", component: Home }, 28 | { path: "/category", name: "category", component: Category }, 29 | { path: "/cart", name: "cart", component: Cart }, 30 | { path: "/profile", name: "profile", component: Profile }, 31 | { path: "/detail/:id", name: "detail", component: ProductDetail }, 32 | { path: "*", redirect: "/" } 33 | ]; 34 | 35 | const router = new VueRouter({ 36 | mode: "history", 37 | base: process.env.NODE_ENV === "production" ? "/shop" : "/", 38 | routes 39 | }); 40 | 41 | export default router; 42 | -------------------------------------------------------------------------------- /src/store/actions.js: -------------------------------------------------------------------------------- 1 | export default { 2 | setCateGoryData({ state, commit }, data) { 3 | let list = JSON.parse(localStorage.getItem("cartList")) || []; 4 | 5 | if (list) { 6 | state.cartList = list; 7 | // find返回的是item 8 | let result = state.cartList.find(item => { 9 | return item.id === data.id; 10 | }); 11 | 12 | // 利用actions分发多个commit事件用于调试 13 | if (result) { 14 | commit("addCounter", result); 15 | } else { 16 | commit("addToCart", data); 17 | } 18 | } else { 19 | commit("addToCart", data); 20 | } 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /src/store/getters.js: -------------------------------------------------------------------------------- 1 | export default { 2 | tabBarShow: state => state.tabBarShow, 3 | cartList: state => state.cartList, 4 | // 把getters传给自己,getters默认在第二个参数 5 | cartListLength: (state, getters) => getters.cartList.length, 6 | // 映射请求加载 7 | isLoading: state => state.isLoading 8 | }; 9 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Vuex from "vuex"; 3 | import getters from "./getters"; 4 | import mutations from "./mutations"; 5 | import actions from "./actions"; 6 | 7 | Vue.use(Vuex); 8 | 9 | const state = { 10 | // 默认tabBar显示 11 | tabBarShow: true, 12 | // 购物车数据 13 | cartList: [], 14 | // 是否开启请求加载 15 | isLoading: true 16 | }; 17 | 18 | export default new Vuex.Store({ 19 | state, 20 | getters, 21 | mutations, 22 | // 异步操作或者复杂的操作放到actions 23 | actions 24 | }); 25 | -------------------------------------------------------------------------------- /src/store/mutations.js: -------------------------------------------------------------------------------- 1 | import { 2 | ADD_COUNTER, 3 | ADD_TO_CART, 4 | CLEAR_CART_LIST, 5 | SET_CART_LIST, 6 | SET_LOADING, 7 | SET_TABBAR_SHOW 8 | } from "./types"; 9 | import { Toast } from "vant"; 10 | 11 | export default { 12 | // 设置tabBar显示和隐藏 13 | [SET_TABBAR_SHOW](state, bol) { 14 | state.tabBarShow = bol; 15 | }, 16 | // 设置请求加载 17 | [SET_LOADING](state, bol) { 18 | state.isLoading = bol; 19 | }, 20 | // id相同的时候购物车叠加数量 21 | [ADD_COUNTER](state, result) { 22 | result.count++; 23 | Toast({ 24 | type: "success", 25 | message: `当前商品数量为${result.count}`, 26 | // 弹框的时候禁止点击 27 | forbidClick: true, 28 | duration: 1500 29 | }); 30 | localStorage.setItem("cartList", JSON.stringify(state.cartList)); 31 | }, 32 | // 往购物车push数据 33 | [ADD_TO_CART](state, data) { 34 | data.count = 1; 35 | data.checked = false; 36 | state.cartList.push(data); 37 | Toast({ 38 | type: "success", 39 | message: `加入购物车成功`, 40 | // 弹框的时候禁止点击 41 | forbidClick: true, 42 | duration: 1500 43 | }); 44 | localStorage.setItem("cartList", JSON.stringify(state.cartList)); 45 | }, 46 | // 清空购物车 47 | [CLEAR_CART_LIST](state) { 48 | // 判断选中哪些数据,过滤没选中的数组返回一个新数组即可 49 | let result = state.cartList.filter(item => item.checked !== true); 50 | if (result.length === 0) { 51 | localStorage.removeItem("cartList"); 52 | state.cartList = []; 53 | } else { 54 | state.cartList = result; 55 | localStorage.setItem("cartList", JSON.stringify(state.cartList)); 56 | } 57 | }, 58 | // 给购物车赋值本地存储 59 | [SET_CART_LIST](state, data) { 60 | state.cartList = data; 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /src/store/types.js: -------------------------------------------------------------------------------- 1 | export const SET_TABBAR_SHOW = "setTabBarShow"; 2 | export const ADD_COUNTER = "addCounter"; 3 | export const ADD_TO_CART = "addToCart"; 4 | export const CLEAR_CART_LIST = "clearCartList"; 5 | export const SET_CART_LIST = "setCartList"; 6 | export const SET_LOADING = "setLoading"; 7 | -------------------------------------------------------------------------------- /src/views/cart/Cart.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 32 | 33 | 40 | -------------------------------------------------------------------------------- /src/views/cart/children/CartList.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 123 | 124 | 246 | -------------------------------------------------------------------------------- /src/views/category/Category.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 85 | 86 | 102 | -------------------------------------------------------------------------------- /src/views/category/children/SlideBar.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 49 | 50 | 86 | -------------------------------------------------------------------------------- /src/views/category/children/Subcategory.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 26 | 27 | 55 | -------------------------------------------------------------------------------- /src/views/detail/ProductDetail.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 183 | 184 | 218 | -------------------------------------------------------------------------------- /src/views/detail/children/DetailBaseInfo.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 48 | 49 | 117 | -------------------------------------------------------------------------------- /src/views/detail/children/DetailBottomBar.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 62 | 63 | 84 | -------------------------------------------------------------------------------- /src/views/detail/children/DetailCommentInfo.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 48 | 49 | 120 | -------------------------------------------------------------------------------- /src/views/detail/children/DetailImagesInfo.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 54 | 55 | 110 | -------------------------------------------------------------------------------- /src/views/detail/children/DetailParamInfo.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 36 | 37 | 79 | -------------------------------------------------------------------------------- /src/views/detail/children/DetailShopInfo.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 61 | 62 | 164 | -------------------------------------------------------------------------------- /src/views/detail/children/DetailSwiper.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 38 | 39 | 62 | -------------------------------------------------------------------------------- /src/views/detail/children/ProductDetailNavBar.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 45 | 46 | 71 | -------------------------------------------------------------------------------- /src/views/home/Home.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 162 | 163 | 208 | -------------------------------------------------------------------------------- /src/views/home/children/FeatureView.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | 15 | 27 | -------------------------------------------------------------------------------- /src/views/home/children/RecommendView.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 25 | 26 | 54 | -------------------------------------------------------------------------------- /src/views/profile/Profile.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 27 | 28 | 55 | -------------------------------------------------------------------------------- /src/views/profile/children/Login.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 29 | 30 | 93 | -------------------------------------------------------------------------------- /src/views/profile/children/Money.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 33 | 34 | 73 | -------------------------------------------------------------------------------- /src/views/profile/children/profileList.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 32 | 33 | 66 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | productionSourceMap: false, 3 | publicPath: process.env.NODE_ENV === "production" ? "/shop" : "/", 4 | chainWebpack: config => { 5 | // 发布阶段打包入口 6 | config.when(process.env.NODE_ENV === "production", config => { 7 | config 8 | .entry("app") 9 | .clear() 10 | .add("./src/main-prod.js"); 11 | 12 | // 配置cdn依赖 13 | config.set("externals", { 14 | vue: "Vue", 15 | "better-scroll": "BScroll", 16 | vant: "vant" 17 | }); 18 | 19 | // 是否发布模式,是 20 | config.plugin("html").tap(args => { 21 | args[0].isProd = true; 22 | return args; 23 | }); 24 | }); 25 | // 开发阶段打包入口 26 | config.when(process.env.NODE_ENV === "development", config => { 27 | config 28 | .entry("app") 29 | .clear() 30 | .add("./src/main-dev.js"); 31 | // 是否发布模式,否 32 | config.plugin("html").tap(args => { 33 | args[0].isProd = false; 34 | return args; 35 | }); 36 | }); 37 | }, 38 | configureWebpack: { 39 | resolve: { 40 | // 配置路径别名 41 | alias: { 42 | assets: "@/assets", 43 | common: "@/common", 44 | components: "@/components", 45 | network: "@/network", 46 | views: "@/views" 47 | } 48 | } 49 | } 50 | }; 51 | --------------------------------------------------------------------------------