├── .gitignore
├── README.md
├── babel.config.js
├── img_preview
├── home-1.png
├── home-2.png
├── login.png
├── mall.png
└── me.png
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── img
│ ├── 01.jpg
│ ├── 02.jpg
│ ├── 03.jpg
│ ├── 04.jpg
│ └── 05.jpg
└── index.html
├── src
├── App.vue
├── assets
│ └── logo.png
├── components
│ ├── BallAnimation.vue
│ ├── GoodsList.vue
│ └── Header.vue
├── cube-ui.js
├── http-interceptor.js
├── main.js
├── router.js
├── services
│ └── create.js
├── store.js
├── theme.styl
├── utils
│ └── history.js
└── views
│ ├── Cart.vue
│ ├── Home.vue
│ └── Login.vue
└── vue.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Editor directories and files
15 | .idea
16 | .vscode
17 | *.suo
18 | *.ntvs*
19 | *.njsproj
20 | *.sln
21 | *.sw?
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## mini-vue-mall
2 |
3 | 一个基于Vue.js的小型商城项目,包含登录、商品列表、购物车页面,数据为本地mock。
4 |
5 | ### 技术栈
6 |
7 | - Vue.js
8 | - vue-router
9 | - Vuex
10 | - cube-ui
11 |
12 | ### 登录
13 |
14 | - 登录账号:dora 密码:123
15 | - 全局路由守卫
16 | - Vuex管理登录信息
17 | - 页面借助cube-ui的表单组件搭建
18 | - http拦截器,将token加入请求头中,响应拦截器处理登录失败的情况
19 |
20 | ### 首页
21 |
22 | - 轮播图
23 | - 商品列表,含过滤筛选商品功能
24 | - 商品图片可预览
25 | - 购物车数据通过Vuex实现全局共享
26 |
27 | ### 购物车动画
28 |
29 | - 通过src/services/create.js中创建组件实例的方法,动态生成购物车动画组件
30 | - 借助Vue.js transition的三个动画钩子来用js控制动画
31 | - 实现思路
32 | - GoodsLIst.vue组件点击加购按钮时,派发一个事件
33 | - 通过这个事件,可以得到点击位置的坐标,这个坐标就是小球的起始位置,购物车图标为小球的终止位置
34 | - 动画结束需要销毁组件,释放内存
35 |
36 | ### 公共
37 |
38 | #### 通用 Header 组件
39 |
40 | - 自己实现一个Vue插件`src/utils/history.js`,通过堆栈的方式维护页面跳转的历史记录,控制返回跳转
41 | - 点击返回按钮是出栈操作
42 | - 在全局路由router.js 中,实例化路由前,通过原型扩展router的back()方法
43 | - 在全局afterEach中存放历史记录
44 |
45 | #### 底部tab,监听路由变化
46 |
47 | 将tab的value和路由的路径定义成相同的,这样切换tab,拿到当前的tab的value,如`/cart`,使用`this.$router.push(value)`,就可以切换页面了
48 |
49 | #### 页面切换滑动动画
50 |
51 | 动态绑定transition的name,根据当前是正常跳转页面还是返回页面操作,决定页面是左滑还是右滑
52 |
53 | ```html
54 |
55 |
56 |
57 | ```
58 |
59 | ### 用到的cube-ui组件
60 | - cube-tabs
61 | - cube-slide
62 | - cube-drawer
63 | - cube-form
64 | - cube-image-preview
65 |
66 | ### 项目预览
67 | 
68 |
69 | 
70 |
71 | 
72 |
73 | 
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/app'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/img_preview/home-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenz-fe/mini-vue-mall/f1f751c36c81931a09dfa8895bd418e39233e665/img_preview/home-1.png
--------------------------------------------------------------------------------
/img_preview/home-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenz-fe/mini-vue-mall/f1f751c36c81931a09dfa8895bd418e39233e665/img_preview/home-2.png
--------------------------------------------------------------------------------
/img_preview/login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenz-fe/mini-vue-mall/f1f751c36c81931a09dfa8895bd418e39233e665/img_preview/login.png
--------------------------------------------------------------------------------
/img_preview/mall.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenz-fe/mini-vue-mall/f1f751c36c81931a09dfa8895bd418e39233e665/img_preview/mall.png
--------------------------------------------------------------------------------
/img_preview/me.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenz-fe/mini-vue-mall/f1f751c36c81931a09dfa8895bd418e39233e665/img_preview/me.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mini-vue-mall",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint"
9 | },
10 | "dependencies": {
11 | "axios": "^0.18.0",
12 | "core-js": "^2.6.5",
13 | "cube-ui": "~1.12.15",
14 | "vue": "^2.6.10",
15 | "vue-router": "^3.0.3",
16 | "vuex": "^3.0.1"
17 | },
18 | "devDependencies": {
19 | "@vue/cli-plugin-babel": "^3.7.0",
20 | "@vue/cli-plugin-eslint": "^3.7.0",
21 | "@vue/cli-service": "^3.7.0",
22 | "babel-eslint": "^10.0.1",
23 | "eslint": "^5.16.0",
24 | "eslint-plugin-vue": "^5.0.0",
25 | "stylus": "^0.54.5",
26 | "stylus-loader": "^3.0.2",
27 | "vue-cli-plugin-cube-ui": "^0.2.5",
28 | "vue-template-compiler": "^2.5.21"
29 | },
30 | "eslintConfig": {
31 | "root": true,
32 | "env": {
33 | "node": true
34 | },
35 | "extends": [
36 | "plugin:vue/essential",
37 | "eslint:recommended"
38 | ],
39 | "rules": {
40 | "accessor-pairs": 2,
41 | "arrow-spacing": [
42 | 2,
43 | {
44 | "before": true,
45 | "after": true
46 | }
47 | ],
48 | "block-spacing": [
49 | 2,
50 | "always"
51 | ],
52 | "brace-style": [
53 | 2,
54 | "1tbs",
55 | {
56 | "allowSingleLine": true
57 | }
58 | ],
59 | "camelcase": [
60 | 0,
61 | {
62 | "properties": "always"
63 | }
64 | ],
65 | "comma-dangle": [
66 | 2,
67 | "never"
68 | ],
69 | "comma-spacing": [
70 | 2,
71 | {
72 | "before": false,
73 | "after": true
74 | }
75 | ],
76 | "comma-style": [
77 | 2,
78 | "last"
79 | ],
80 | "constructor-super": 2,
81 | "curly": [
82 | 2,
83 | "multi-line"
84 | ],
85 | "dot-location": [
86 | 2,
87 | "property"
88 | ],
89 | "eol-last": 2,
90 | "eqeqeq": [
91 | 2,
92 | "allow-null"
93 | ],
94 | "generator-star-spacing": [
95 | 2,
96 | {
97 | "before": true,
98 | "after": true
99 | }
100 | ],
101 | "handle-callback-err": [
102 | 2,
103 | "^(err|error)$"
104 | ],
105 | "indent": [
106 | 2,
107 | 2,
108 | {
109 | "SwitchCase": 1
110 | }
111 | ],
112 | "jsx-quotes": [
113 | 2,
114 | "prefer-single"
115 | ],
116 | "key-spacing": [
117 | 2,
118 | {
119 | "beforeColon": false,
120 | "afterColon": true
121 | }
122 | ],
123 | "keyword-spacing": [
124 | 2,
125 | {
126 | "before": true,
127 | "after": true
128 | }
129 | ],
130 | "new-cap": [
131 | 2,
132 | {
133 | "newIsCap": true,
134 | "capIsNew": false
135 | }
136 | ],
137 | "new-parens": 2,
138 | "no-array-constructor": 2,
139 | "no-caller": 2,
140 | "no-console": "off",
141 | "no-class-assign": 2,
142 | "no-cond-assign": 2,
143 | "no-const-assign": 2,
144 | "no-control-regex": 2,
145 | "no-delete-var": 2,
146 | "no-dupe-args": 2,
147 | "no-dupe-class-members": 2,
148 | "no-dupe-keys": 2,
149 | "no-duplicate-case": 2,
150 | "no-empty-character-class": 2,
151 | "no-empty-pattern": 2,
152 | "no-eval": 2,
153 | "no-ex-assign": 2,
154 | "no-extend-native": 2,
155 | "no-extra-bind": 2,
156 | "no-extra-boolean-cast": 2,
157 | "no-extra-parens": [
158 | 2,
159 | "functions"
160 | ],
161 | "no-fallthrough": 2,
162 | "no-floating-decimal": 2,
163 | "no-func-assign": 2,
164 | "no-implied-eval": 2,
165 | "no-inner-declarations": [
166 | 2,
167 | "functions"
168 | ],
169 | "no-invalid-regexp": 2,
170 | "no-irregular-whitespace": 2,
171 | "no-iterator": 2,
172 | "no-label-var": 2,
173 | "no-labels": [
174 | 2,
175 | {
176 | "allowLoop": false,
177 | "allowSwitch": false
178 | }
179 | ],
180 | "no-lone-blocks": 2,
181 | "no-mixed-spaces-and-tabs": 2,
182 | "no-multi-spaces": 2,
183 | "no-multi-str": 2,
184 | "no-multiple-empty-lines": [
185 | 2,
186 | {
187 | "max": 1
188 | }
189 | ],
190 | "no-native-reassign": 2,
191 | "no-negated-in-lhs": 2,
192 | "no-new-object": 2,
193 | "no-new-require": 2,
194 | "no-new-symbol": 2,
195 | "no-new-wrappers": 2,
196 | "no-obj-calls": 2,
197 | "no-octal": 2,
198 | "no-octal-escape": 2,
199 | "no-path-concat": 2,
200 | "no-proto": 2,
201 | "no-redeclare": 2,
202 | "no-regex-spaces": 2,
203 | "no-return-assign": [
204 | 2,
205 | "except-parens"
206 | ],
207 | "no-self-assign": 2,
208 | "no-self-compare": 2,
209 | "no-sequences": 2,
210 | "no-shadow-restricted-names": 2,
211 | "no-spaced-func": 2,
212 | "no-sparse-arrays": 2,
213 | "no-this-before-super": 2,
214 | "no-throw-literal": 2,
215 | "no-trailing-spaces": 2,
216 | "no-undef": 2,
217 | "no-undef-init": 2,
218 | "no-unexpected-multiline": 2,
219 | "no-unmodified-loop-condition": 2,
220 | "no-unneeded-ternary": [
221 | 2,
222 | {
223 | "defaultAssignment": false
224 | }
225 | ],
226 | "no-unreachable": 2,
227 | "no-unsafe-finally": 2,
228 | "no-unused-vars": [
229 | 2,
230 | {
231 | "vars": "all",
232 | "args": "none"
233 | }
234 | ],
235 | "no-useless-call": 2,
236 | "no-useless-computed-key": 2,
237 | "no-useless-constructor": 2,
238 | "no-useless-escape": 0,
239 | "no-whitespace-before-property": 2,
240 | "no-with": 2,
241 | "one-var": [
242 | 2,
243 | {
244 | "initialized": "never"
245 | }
246 | ],
247 | "operator-linebreak": [
248 | 2,
249 | "after",
250 | {
251 | "overrides": {
252 | "?": "before",
253 | ":": "before"
254 | }
255 | }
256 | ],
257 | "padded-blocks": [
258 | 2,
259 | "never"
260 | ],
261 | "quotes": [
262 | 2,
263 | "single",
264 | {
265 | "avoidEscape": true,
266 | "allowTemplateLiterals": true
267 | }
268 | ],
269 | "semi": [
270 | 2,
271 | "never"
272 | ],
273 | "semi-spacing": [
274 | 2,
275 | {
276 | "before": false,
277 | "after": true
278 | }
279 | ],
280 | "space-before-blocks": [
281 | 2,
282 | "always"
283 | ],
284 | "space-before-function-paren": [
285 | 2,
286 | "never"
287 | ],
288 | "space-in-parens": [
289 | 2,
290 | "never"
291 | ],
292 | "space-infix-ops": 2,
293 | "space-unary-ops": [
294 | 2,
295 | {
296 | "words": true,
297 | "nonwords": false
298 | }
299 | ],
300 | "spaced-comment": [
301 | 2,
302 | "always",
303 | {
304 | "markers": [
305 | "global",
306 | "globals",
307 | "eslint",
308 | "eslint-disable",
309 | "*package",
310 | "!",
311 | ","
312 | ]
313 | }
314 | ],
315 | "template-curly-spacing": [
316 | 2,
317 | "never"
318 | ],
319 | "use-isnan": 2,
320 | "valid-typeof": 2,
321 | "wrap-iife": [
322 | 2,
323 | "any"
324 | ],
325 | "yield-star-spacing": [
326 | 2,
327 | "both"
328 | ],
329 | "yoda": [
330 | 2,
331 | "never"
332 | ],
333 | "prefer-const": 2,
334 | "object-curly-spacing": [
335 | 2,
336 | "always",
337 | {
338 | "objectsInObjects": false
339 | }
340 | ],
341 | "array-bracket-spacing": [
342 | 2,
343 | "never"
344 | ]
345 | },
346 | "parserOptions": {
347 | "parser": "babel-eslint"
348 | }
349 | },
350 | "postcss": {
351 | "plugins": {
352 | "autoprefixer": {}
353 | }
354 | },
355 | "browserslist": [
356 | "> 1%",
357 | "last 2 versions",
358 | "not ie <= 11",
359 | "Android >= 4.0",
360 | "iOS >= 8"
361 | ],
362 | "transformModules": {
363 | "cube-ui": {
364 | "transform": "cube-ui/src/modules/${member}",
365 | "kebabCase": true
366 | }
367 | }
368 | }
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenz-fe/mini-vue-mall/f1f751c36c81931a09dfa8895bd418e39233e665/public/favicon.ico
--------------------------------------------------------------------------------
/public/img/01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenz-fe/mini-vue-mall/f1f751c36c81931a09dfa8895bd418e39233e665/public/img/01.jpg
--------------------------------------------------------------------------------
/public/img/02.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenz-fe/mini-vue-mall/f1f751c36c81931a09dfa8895bd418e39233e665/public/img/02.jpg
--------------------------------------------------------------------------------
/public/img/03.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenz-fe/mini-vue-mall/f1f751c36c81931a09dfa8895bd418e39233e665/public/img/03.jpg
--------------------------------------------------------------------------------
/public/img/04.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenz-fe/mini-vue-mall/f1f751c36c81931a09dfa8895bd418e39233e665/public/img/04.jpg
--------------------------------------------------------------------------------
/public/img/05.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenz-fe/mini-vue-mall/f1f751c36c81931a09dfa8895bd418e39233e665/public/img/05.jpg
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | mini-vue-mall
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
12 |
18 |
22 | {{item.label}}
23 | {{cartTotal}}
27 |
28 |
29 |
30 |
31 |
32 |
33 |
81 |
82 |
144 |
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chenz-fe/mini-vue-mall/f1f751c36c81931a09dfa8895bd418e39233e665/src/assets/logo.png
--------------------------------------------------------------------------------
/src/components/BallAnimation.vue:
--------------------------------------------------------------------------------
1 |
2 |
18 |
19 |
20 |
60 |
61 |
84 |
--------------------------------------------------------------------------------
/src/components/GoodsList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
![]()
16 |
17 |
18 |
{{item.title}}
19 |
20 | {{item.count}}人购买
21 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
48 |
49 |
73 |
--------------------------------------------------------------------------------
/src/components/Header.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
27 |
28 |
42 |
--------------------------------------------------------------------------------
/src/cube-ui.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | // By default we import all the components.
4 | // Only reserve the components on demand and remove the rest.
5 | // Style is always required.
6 | import {
7 | /* eslint-disable no-unused-vars */
8 | Style,
9 | // basic
10 | Button,
11 | TabBar,
12 | Input,
13 | Form,
14 | // popup
15 | Toast,
16 | Drawer,
17 | ImagePreview,
18 | Slide
19 | } from 'cube-ui'
20 |
21 | Vue.use(Button)
22 | Vue.use(TabBar)
23 | Vue.use(Input)
24 | Vue.use(Form)
25 | Vue.use(Toast)
26 | Vue.use(Drawer)
27 | Vue.use(ImagePreview)
28 | Vue.use(Slide)
29 |
--------------------------------------------------------------------------------
/src/http-interceptor.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import store from './store'
3 | import router from './router'
4 |
5 | axios.interceptors.request.use(config => {
6 | if (store.state.token) {
7 | config.headers.token = store.state.token
8 | }
9 | return config
10 | })
11 |
12 | axios.interceptors.response.use(
13 | response => {
14 | if (response.status === 200) {
15 | const data = response.data
16 | if (data.code === -1) {
17 | clearHandler()
18 | }
19 | }
20 | return response
21 | },
22 | err => {
23 | if (err.response.status === 401) {
24 | clearHandler()
25 | }
26 | }
27 | )
28 |
29 | function clearHandler() {
30 | store.commit('setToken', '')
31 | localStorage.removeItem('token')
32 | router.push({
33 | path: '/login',
34 | query: {
35 | redirect: router.currentRoute.path
36 | }
37 | })
38 | }
39 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import './cube-ui'
3 | import App from './App.vue'
4 | import router from './router'
5 | import store from './store'
6 | import axios from 'axios'
7 | import './http-interceptor'
8 | import XHeader from './components/Header.vue'
9 |
10 | Vue.config.productionTip = false
11 |
12 | Vue.component('x-header', XHeader)
13 |
14 | Vue.prototype.$http = axios
15 |
16 | new Vue({
17 | router,
18 | store,
19 | render: h => h(App)
20 | }).$mount('#app')
21 |
--------------------------------------------------------------------------------
/src/router.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 | import Home from './views/Home.vue'
4 | import Login from './views/Login.vue'
5 | import store from './store'
6 | import History from './utils/history'
7 |
8 | Vue.use(Router)
9 | Vue.use(History)
10 |
11 | Router.prototype.goBack = function() {
12 | this.isBack = true
13 | this.back()
14 | }
15 |
16 | const router = new Router({
17 | routes: [
18 | {
19 | path: '/',
20 | name: 'home',
21 | component: Home
22 | },
23 | {
24 | path: '/Login',
25 | name: 'login',
26 | component: Login
27 | },
28 | {
29 | path: '/Cart',
30 | name: 'cart',
31 | meta: { auth: true },
32 | // route level code-splitting
33 | // this generates a separate chunk (about.[hash].js) for this route
34 | // which is lazy-loaded when the route is visited.
35 | component: () =>
36 | import(/* webpackChunkName: "about" */ './views/Cart.vue')
37 | }
38 | ]
39 | })
40 |
41 | router.beforeEach((to, from, next) => {
42 | if (to.meta.auth) {
43 | if (store.state.token) {
44 | next()
45 | } else {
46 | next({
47 | path: '/login',
48 | query: { redirect: to.path }
49 | })
50 | }
51 | } else {
52 | next()
53 | }
54 | })
55 |
56 | router.afterEach((to) => {
57 | if (router.isBack) {
58 | History.pop()
59 | router.isBack = false
60 | router.transitionName = 'route-back'
61 | } else {
62 | History.push(to.path)
63 | router.transitionName = 'route-forward'
64 | }
65 | })
66 |
67 | export default router
68 |
--------------------------------------------------------------------------------
/src/services/create.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | function create(Component, props) {
4 | const instance = new Vue({
5 | render(h) {
6 | return h(Component, { props })
7 | }
8 | }).$mount() // 执行挂载
9 | // 将真实dom节点插入文档中
10 | document.body.appendChild(instance.$el)
11 | // 获取组件实例
12 | const comp = instance.$children[0]
13 | // 销毁组件,释放内存
14 | comp.remove = () => {
15 | instance.$destroy()
16 | }
17 | return comp
18 | }
19 |
20 | export default create
21 |
--------------------------------------------------------------------------------
/src/store.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 |
4 | Vue.use(Vuex)
5 |
6 | const store = new Vuex.Store({
7 | state: {
8 | token: localStorage.getItem('token') || '',
9 | cart: JSON.parse(localStorage.getItem('cart')) || []
10 | },
11 | mutations: {
12 | setToken(state, token) {
13 | state.token = token
14 | },
15 | addCart(state, item) {
16 | const good = state.cart.find(v => v.id === item.id)
17 | if (good) {
18 | good.cartCount += 1
19 | } else {
20 | state.cart.push({
21 | ...item,
22 | cartCount: 1
23 | })
24 | }
25 | },
26 | countMinus(state, index) {
27 | const item = state.cart[index]
28 | if (item.cartCount > 1) {
29 | item.cartCount -= 1
30 | } else {
31 | state.cart.splice(index, 1)
32 | }
33 | },
34 | countAdd(state, index) {
35 | state.cart[index].cartCount += 1
36 | }
37 | },
38 | actions: {},
39 | getters: {
40 | isLogin: state => {
41 | return !!state.token
42 | },
43 | cartTotal: state => {
44 | let num = 0
45 | state.cart.forEach(v => {
46 | num += v.cartCount
47 | })
48 | return num
49 | },
50 | total: state =>
51 | state.cart.reduce((num, v) => (num += v.cartCount * v.price), 0)
52 | }
53 | })
54 |
55 | store.subscribe((mutation, state) => {
56 | switch (mutation.type) {
57 | case 'setToken':
58 | localStorage.setItem('token', JSON.stringify(state.token))
59 | break
60 | case 'addCart':
61 | localStorage.setItem('cart', JSON.stringify(state.cart))
62 | break
63 | }
64 | })
65 |
66 | export default store
67 |
--------------------------------------------------------------------------------
/src/theme.styl:
--------------------------------------------------------------------------------
1 | @require "~cube-ui/src/common/stylus/var/color.styl"
2 |
3 |
4 | // action-sheet
5 | $action-sheet-color := $color-grey
6 | $action-sheet-active-color := $color-orange
7 | $action-sheet-bgc := $color-white
8 | $action-sheet-active-bgc := $color-light-grey-opacity
9 | $action-sheet-title-color := $color-dark-grey
10 | $action-sheet-space-bgc := $color-mask-bg
11 | /// picker style
12 | $action-sheet-picker-cancel-color := $color-light-grey
13 | $action-sheet-picker-cancel-active-color := $color-light-grey-s
14 |
15 | // bubble
16 |
17 | // button
18 | $btn-color := $color-white
19 | $btn-bgc := $color-regular-blue
20 | $btn-bdc := $color-regular-blue
21 | $btn-active-bgc := $color-blue
22 | $btn-active-bdc := $color-blue
23 | $btn-disabled-color := $color-white
24 | $btn-disabled-bgc := $color-light-grey-s
25 | $btn-disabled-bdc := $color-light-grey-s
26 | /// primary
27 | $btn-primary-color := $color-white
28 | $btn-primary-bgc := $color-orange
29 | $btn-primary-bdc := $color-orange
30 | $btn-primary-active-bgc := $color-dark-orange
31 | $btn-primary-active-bdc := $color-dark-orange
32 | /// light
33 | $btn-light-color := $color-grey
34 | $btn-light-bgc := $color-light-grey-sss
35 | $btn-light-bdc := $color-light-grey-sss
36 | $btn-light-active-bgc := $color-active-grey
37 | $btn-light-active-bdc := $color-active-grey
38 | /// outline
39 | $btn-outline-color := $color-grey
40 | $btn-outline-bgc := transparent
41 | $btn-outline-bdc := $color-grey
42 | $btn-outline-active-bgc := $color-grey-opacity
43 | $btn-outline-active-bdc := $color-grey
44 | /// outline-primary
45 | $btn-outline-primary-color := $color-orange
46 | $btn-outline-primary-bgc := transparent
47 | $btn-outline-primary-bdc := $color-orange
48 | $btn-outline-primary-active-bgc := $color-orange-opacity
49 | $btn-outline-primary-active-bdc := $color-dark-orange
50 |
51 | // toolbar
52 | $toolbar-bgc := $color-light-grey-sss
53 | $toolbar-active-bgc := $color-active-grey
54 |
55 | // checkbox
56 | $checkbox-color := $color-grey
57 | $checkbox-icon-color := $color-light-grey-s
58 | /// checked
59 | $checkbox-checked-icon-color := $color-orange
60 | $checkbox-checked-icon-bgc := $color-white
61 | /// disabled
62 | $checkbox-disabled-icon-color := $color-light-grey-ss
63 | $checkbox-disabled-icon-bgc := $color-light-grey-ss
64 | // checkbox hollow
65 | $checkbox-hollow-checked-icon-color := $color-orange
66 | $checkbox-hollow-disabled-icon-color := $color-light-grey-ss
67 | // checkbox-group
68 | $checkbox-group-bgc := $color-white
69 | $checkbox-group-horizontal-bdc := $color-light-grey-s
70 |
71 | // radio
72 | $radio-group-bgc := $color-white
73 | $radio-group-horizontal-bdc := $color-light-grey-s
74 | $radio-color := $color-grey
75 | $radio-icon-color := $color-light-grey-s
76 | /// selected
77 | $radio-selected-icon-color := $color-white
78 | $radio-selected-icon-bgc := $color-orange
79 | /// disabled
80 | $radio-disabled-icon-bgc := $color-light-grey-ss
81 | // radio hollow
82 | $radio-hollow-selected-icon-color := $color-orange
83 | $radio-hollow-disabled-icon-color := $color-light-grey-ss
84 |
85 | // dialog
86 | $dialog-color := $color-grey
87 | $dialog-bgc := $color-white
88 | $dialog-icon-color := $color-regular-blue
89 | $dialog-icon-bgc := $color-background
90 | $dialog-title-color := $color-dark-grey
91 | $dialog-close-color := $color-light-grey
92 | $dialog-btn-color := $color-light-grey
93 | $dialog-btn-bgc := $color-white
94 | $dialog-btn-active-bgc := $color-light-grey-opacity
95 | $dialog-btn-highlight-color := $color-orange
96 | $dialog-btn-highlight-active-bgc := $color-light-orange-opacity
97 | $dialog-btn-disabled-color := $color-light-grey
98 | $dialog-btn-disabled-active-bgc := transparent
99 | $dialog-btns-split-color := $color-row-line
100 |
101 | // index-list
102 | $index-list-bgc := $color-white
103 | $index-list-title-color := $color-dark-grey
104 | $index-list-anchor-color := $color-light-grey
105 | $index-list-anchor-bgc := #f7f7f7
106 | $index-list-item-color := $color-dark-grey
107 | $index-list-item-active-bgc := $color-light-grey-opacity
108 | $index-list-nav-color := $color-grey
109 | $index-list-nav-active-color := $color-orange
110 |
111 | // loading
112 |
113 | // picker
114 | $picker-bgc := $color-white
115 | $picker-title-color := $color-dark-grey
116 | $picker-subtitle-color := $color-light-grey
117 | $picker-confirm-btn-color := $color-orange
118 | $picker-confirm-btn-active-color := $color-light-orange
119 | $picker-cancel-btn-color := $color-light-grey
120 | $picker-cancel-btn-active-color := $color-light-grey-s
121 | $picker-item-color := $color-dark-grey
122 |
123 | // popup
124 | $popup-mask-bgc := rgb(37, 38, 45)
125 | $popup-mask-opacity := .4
126 |
127 | //scroll
128 |
129 | // slide
130 | $slide-dot-bgc := $color-light-grey-s
131 | $slide-dot-active-bgc := $color-orange
132 |
133 | // time-picker
134 |
135 | // tip
136 | $tip-color := $color-white
137 | $tip-bgc := $color-dark-grey-opacity
138 |
139 | // toast
140 | $toast-color := $color-light-grey-s
141 | $toast-bgc := rgba(37, 38, 45, 0.9)
142 |
143 | // upload
144 | $upload-btn-color := $color-grey
145 | $upload-btn-bgc := $color-white
146 | $upload-btn-active-bgc := $color-light-grey-opacity
147 | $upload-btn-box-shadow := 0 0 6px 2px $color-grey-opacity
148 | $upload-btn-border-color := #e5e5e5
149 | $upload-file-bgc := $color-white
150 | $upload-file-remove-color := rgba(0, 0, 0, .8)
151 | $upload-file-remove-bgc := $color-white
152 | $upload-file-state-bgc := $color-mask-bg
153 | $upload-file-success-color := $color-orange
154 | $upload-file-error-color := #f43530
155 | $upload-file-status-bgc := $color-white
156 | $upload-file-progress-color := $color-white
157 |
158 | // switch
159 | $switch-on-bgc := $color-orange
160 | $switch-off-bgc := $color-white
161 | $switch-off-border-color := #e4e4e4
162 |
163 | // input
164 | $input-color := $color-grey
165 | $input-bgc := $color-white
166 | $input-border-color := $color-row-line
167 | $input-focus-border-color := $color-orange
168 | $input-placeholder-color := $color-light-grey-s
169 | $input-clear-icon-color := $color-light-grey
170 |
171 | //textarea
172 | $textarea-color := $color-grey
173 | $textarea-bgc := $color-white
174 | $textarea-border-color := $color-row-line
175 | $textarea-focus-border-color := $color-orange
176 | $textarea-outline-color := $color-orange
177 | $textarea-placeholder-color := $color-light-grey-s
178 | $textarea-indicator-color := $color-light-grey-s
179 |
180 | // validator
181 | $validator-msg-def-color := #e64340
182 |
183 | // select
184 | $select-color := $color-grey
185 | $select-bgc := $color-white
186 | $select-disabled-color := #b8b8b8
187 | $select-disabled-bgc := $color-light-grey-opacity
188 | $select-border-color := $color-light-grey-s
189 | $select-border-active-color := $color-orange
190 | $select-icon-color := $color-light-grey
191 | $select-placeholder-color := $color-light-grey-s
192 |
193 | // swipe
194 | $swipe-btn-color := $color-white
195 |
196 | // form
197 | $form-color := $color-grey
198 | $form-bgc := $color-white
199 | $form-invalid-color := #e64340
200 | $form-group-legend-color := $color-light-grey
201 | $form-group-legend-bgc := $color-background
202 | $form-label-required-color := #e64340
203 |
204 | // drawer
205 | $drawer-color := $color-dark-grey
206 | $drawer-title-bdc := $color-light-grey-ss
207 | $drawer-title-bgc := $color-white
208 | $drawer-panel-bgc := $color-white
209 | $drawer-item-active-bgc := $color-light-grey-opacity
210 |
211 | // scroll-nav
212 | $scroll-nav-bgc := $color-white
213 | $scroll-nav-color := $color-grey
214 | $scroll-nav-active-color := $color-orange
215 |
216 | // image-preview
217 | $image-preview-counter-color := $color-white
218 |
219 | // tab-bar & tab-panel
220 | $tab-color := $color-grey
221 | $tab-active-color := $color-dark-orange
222 | $tab-slider-bgc := $color-dark-orange
223 |
--------------------------------------------------------------------------------
/src/utils/history.js:
--------------------------------------------------------------------------------
1 | const History = {
2 | _history: [],
3 | install(Vue) {
4 | Object.defineProperty(Vue.prototype, '$routerHistory', {
5 | get() {
6 | return History
7 | }
8 | })
9 | },
10 | push(path) {
11 | this._history.push(path)
12 | },
13 | pop() {
14 | this._history.pop()
15 | },
16 | canBack() {
17 | return this._history.length > 1
18 | }
19 | }
20 | export default History
21 |
--------------------------------------------------------------------------------
/src/views/Cart.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 | {{item.title}}
9 |
10 |
14 |
15 | {{item.cartCount}}
16 |
17 |
21 |
22 |
23 |
24 | 总价 {{total}}
25 |
26 |
27 |
31 | 还差{{minTotal-total}}可以购买
32 |
33 |
34 | 下单
35 |
36 | (需要登录)
37 |
38 |
39 |
40 |
41 |
42 |
69 |
70 |
88 |
89 |
--------------------------------------------------------------------------------
/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
12 |
13 |
17 |
18 |
19 |
20 |
选择分类
21 |
27 |
28 |
32 |
33 |
34 |
35 |
36 |
37 |
107 |
108 |
119 |
--------------------------------------------------------------------------------
/src/views/Login.vue:
--------------------------------------------------------------------------------
1 |
2 |
18 |
19 |
20 |
112 |
113 |
122 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | css: {
3 | loaderOptions: {
4 | stylus: {
5 | 'resolve url': true,
6 | import: ['./src/theme']
7 | }
8 | }
9 | },
10 | pluginOptions: {
11 | 'cube-ui': {
12 | postCompile: true,
13 | theme: true
14 | }
15 | },
16 | configureWebpack: {
17 | devServer: {
18 | before(app) {
19 | app.get('/api/goods', function(req, res) {
20 | res.json({
21 | code: 0,
22 | slider: [
23 | {
24 | id: 21,
25 | img: '/img/01.jpg'
26 | },
27 | {
28 | id: 22,
29 | img: '/img/02.jpg'
30 | },
31 | {
32 | id: 23,
33 | img: '/img/03.jpg'
34 | },
35 | {
36 | id: 24,
37 | img: '/img/04.jpg'
38 | }
39 | ],
40 | data: {
41 | phone: [
42 | {
43 | id: 1,
44 | title: '小米手机',
45 | price: '100',
46 | img: '/img/01.jpg',
47 | count: 100
48 | },
49 | {
50 | id: 2,
51 | title: 'iphone',
52 | price: '120',
53 | img: '/img/03.jpg',
54 | count: 100
55 | },
56 | {
57 | id: 3,
58 | title: '华为',
59 | price: '80',
60 | img: '/img/02.jpg',
61 | count: 100
62 | },
63 | {
64 | id: 4,
65 | title: 'vivo',
66 | price: '110',
67 | img: '/img/04.jpg',
68 | count: 100
69 | },
70 | {
71 | id: 5,
72 | title: 'oppo',
73 | price: '200',
74 | img: '/img/02.jpg',
75 | count: 100
76 | },
77 | {
78 | id: 6,
79 | title: '360手机',
80 | price: '30',
81 | img: '/img/05.jpg',
82 | count: 100
83 | }
84 | ],
85 | equipment: [
86 | {
87 | id: 7,
88 | title: '电饭煲',
89 | price: '120',
90 | img: '/img/03.jpg',
91 | count: 101
92 | },
93 | {
94 | id: 8,
95 | title: '电冰箱',
96 | price: '80',
97 | img: '/img/02.jpg',
98 | count: 100
99 | },
100 | {
101 | id: 9,
102 | title: '电热水器',
103 | price: '110',
104 | img: '/img/01.jpg',
105 | count: 100
106 | },
107 | {
108 | id: 10,
109 | title: '电磁炉',
110 | price: '200',
111 | img: '/img/04.jpg',
112 | count: 100
113 | }
114 | ],
115 | market: [
116 | {
117 | id: 11,
118 | title: '零食',
119 | price: '80',
120 | img: '/img/02.jpg',
121 | count: 100
122 | },
123 | {
124 | id: 12,
125 | title: '水果',
126 | price: '110',
127 | img: '/img/01.jpg',
128 | count: 100
129 | },
130 | {
131 | id: 13,
132 | title: '蔬菜',
133 | price: '30',
134 | img: '/img/04.jpg',
135 | count: 100
136 | }
137 | ],
138 | book: [
139 | {
140 | id: 14,
141 | title: '文学',
142 | price: '200',
143 | img: '/img/01.jpg',
144 | count: 100
145 | },
146 | {
147 | id: 15,
148 | title: '历史',
149 | price: '120',
150 | img: '/img/03.jpg',
151 | count: 100
152 | },
153 | {
154 | id: 16,
155 | title: 'IT科技',
156 | price: '80',
157 | img: '/img/02.jpg',
158 | count: 100
159 | }
160 | ],
161 | sport: [
162 | {
163 | id: 17,
164 | title: '跑步鞋',
165 | price: '100',
166 | img: '/img/01.jpg',
167 | count: 100
168 | },
169 | {
170 | id: 18,
171 | title: '山地车',
172 | price: '120',
173 | img: '/img/03.jpg',
174 | count: 100
175 | },
176 | {
177 | id: 19,
178 | title: '篮球',
179 | price: '80',
180 | img: '/img/02.jpg',
181 | count: 100
182 | },
183 | {
184 | id: 20,
185 | title: '瑜伽垫',
186 | price: '110',
187 | img: '/img/05.jpg',
188 | count: 100
189 | }
190 | ]
191 | },
192 | keys: ['phone', 'equipment', 'market', 'book', 'sport']
193 | })
194 | })
195 |
196 | app.get('/api/login', function(req, res) {
197 | const { username, password } = req.query
198 | if (username === 'dora' && password === '123') {
199 | res.json({
200 | code: 0,
201 | token: 'xxx'
202 | })
203 | } else {
204 | res.json({
205 | code: 1,
206 | message: '用户名或密码错误'
207 | })
208 | }
209 | })
210 |
211 | app.get('/api/logout', function(req, res) {
212 | res.json({ code: -1 })
213 | })
214 | }
215 | }
216 | }
217 | }
218 |
--------------------------------------------------------------------------------