├── .editorconfig
├── .env
├── .env.development
├── .env.production
├── .gitignore
├── LICENSE
├── README.md
├── babel.config.js
├── init.sh
├── package.json
├── public
├── favicon.ico
└── index.html
├── push.sh
├── src
├── App.vue
├── api
│ └── common.js
├── assets
│ ├── iconfont
│ │ ├── README.md
│ │ ├── iconfont.css
│ │ ├── iconfont.eot
│ │ ├── iconfont.svg
│ │ ├── iconfont.ttf
│ │ └── iconfont.woff
│ ├── img
│ │ └── common
│ │ │ ├── cross_a.png
│ │ │ ├── cross_h.png
│ │ │ ├── cross_n.png
│ │ │ └── icon_1.png
│ └── logo.png
├── common
│ ├── README.md
│ ├── request.js
│ ├── sniff.js
│ └── util.js
├── components
│ ├── CountDown.js
│ ├── CountDown.vue
│ ├── Dialog.vue
│ ├── HelloWorld.vue
│ └── toast
│ │ ├── Toast.vue
│ │ └── index.js
├── config
│ └── env.js
├── filters
│ └── index.js
├── layouts
│ └── index.vue
├── locale
│ ├── cn
│ │ ├── home.js
│ │ └── index.js
│ ├── en
│ │ ├── home.js
│ │ └── index.js
│ ├── index.js
│ └── messages.js
├── main.js
├── router
│ └── index.js
├── store
│ ├── actions.js
│ ├── home
│ │ ├── index.js
│ │ └── user.js
│ ├── index.js
│ ├── mutation-types.js
│ └── mutations.js
├── style
│ ├── _var.scss
│ ├── common.scss
│ ├── mixin.scss
│ ├── normalize.scss
│ └── reset.scss
└── views
│ ├── about.vue
│ ├── home.vue
│ └── template.vue
├── vue.config.js
└── 使用说明.md
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{js,jsx,ts,tsx,vue}]
2 | indent_style = space
3 | indent_size = 4
4 | trim_trailing_whitespace = true
5 | insert_final_newline = true
6 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | # 在所有的环境中都被载入的环境变量
2 |
3 | #接口地址
4 | #VUE_APP_API = 'http://api.zhuishushenqi.com'
--------------------------------------------------------------------------------
/.env.development:
--------------------------------------------------------------------------------
1 | # 只在开发环境存在的环境变量
2 |
3 | # 接口地址
4 | VUE_APP_API = 'http://api.zhuishushenqi.com'
--------------------------------------------------------------------------------
/.env.production:
--------------------------------------------------------------------------------
1 | # 只在生产环境存在的环境变量
2 |
3 | # 接口地址
4 | VUE_APP_API = 'http://api.zhuishushenqi.com'
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 | /devdist
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 |
15 | # Editor directories and files
16 | .idea
17 | .vscode
18 | *.suo
19 | *.ntvs*
20 | *.njsproj
21 | *.sln
22 | *.sw*
23 |
24 | yarn.lock
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 东半球少女的梦
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |

2 | vue-vuex-router
3 | 基于Vue CLI 3构建的项目初始化文件,master、mobile两个分支,pc端、mobile端开箱即用。
4 |
5 | ## 文档内容
6 |
7 | - [注意](#注意)
8 | - [安装和使用](#安装和使用)
9 | - [项目结构](#项目结构)
10 | - [推荐规范](#推荐规范)
11 | - [常见问题](#常见问题)
12 | - [更多自定义配置](#更多自定义配置)
13 | - [进阶vue3](#进阶vue3)
14 | - [License](#License)
15 |
16 | ## 注意
17 |
18 | 注意!这是一份Vue的最佳实践,包括了实践展示,但是并不包括新手入门,如果你是Vue新手,可以按如下路径学习Vue:
19 |
20 | ```
21 | 1、用Vue+Vue-Router做一个展示网站。网站按页面划分模块,每个页面按section(部分)再划分模块。培养自己的模块化思想。
22 | 2、用Vue+Vue-Router+Axios做一个带请求的网站。把请求结果放在页面上展示出来。锻炼请求接口的能力,了解前后端分离思想。
23 | 3、用Vue+Vue-Router+Axios+Vuex做一个能管理数据的网站。把请求放到Vuex中,用store管理数据。搞懂Vuex在开发中的帮助,提升项目开发能力。
24 |
25 | 做完上面三步你就已经入门Vue了,接下来就可以用这个最佳实践来构建更优雅的代码、组织更简洁的项目。
26 | ```
27 |
28 | ## 安装和使用
29 |
30 | ```
31 | 初始化项目
32 | ./init.sh
33 |
34 | 安装插件
35 | yarn install
36 |
37 | 项目启动
38 | yarn serve
39 |
40 | 项目打包
41 | yarn build
42 |
43 | 分析打包体积
44 | yarn analyz
45 |
46 | 提交代码
47 | yarn push
48 | ```
49 |
50 | ## 项目结构
51 |
52 | ```
53 | - src
54 | - api // 接口管理目录
55 | - assets // 图片资源目录
56 | - common // 公共资源目录(函数库、请求封装)
57 | - components // 组件目录
58 | - config // 配置文件目录
59 | - env.js // 环境配置
60 | - fliters // 公共过滤器
61 | - layouts // 外层布局
62 | - locale // i18n语言目录
63 | - router // 路由
64 | - store // Vuex管理目录
65 | - style // 公共样式目录
66 | - views // 页面目录
67 | ```
68 |
69 | ## 推荐规范
70 |
71 | 1、命名
72 |
73 | 组件命名:
74 |
75 | ```
76 | 文件与组件命名保持一致,使用 PascalCase,即驼峰命名法,如:CountDown.vue 为一个倒计时组件。
77 |
78 | 参考链接:
79 | https://cn.vuejs.org/v2/guide/components-registration.html#%E7%BB%84%E4%BB%B6%E5%90%8D%E5%A4%A7%E5%B0%8F%E5%86%99
80 | ```
81 |
82 | 页面命名:
83 |
84 | ```
85 | Vue中什么都是组件,包括页面,但是我们为了与UI、业务组件作出区分,凡是页面组件的命名均以"小驼峰法"命名,如:home.vue。
86 | ```
87 |
88 | 变量命名:
89 | ```
90 | 常量——全大写+下划线,如:
91 |
92 | let IMAGE_SERVER = 'http://img.alibaba.com';
93 | let MAX_LENGTH = 200;
94 |
95 | 变量——小驼峰式命名法,如:
96 | let maxCount = 10;
97 | let tableTitle = 'LoginTable';
98 |
99 | 全局变量——g+变量名,如:
100 | var gConfig = {
101 | a:1,
102 | b:2
103 | };
104 |
105 | 私有变量——下划线+变量名,如:
106 | let _name = '小明';
107 | ```
108 | 2、样式
109 |
110 | 样式统一在 style 文件夹里维护
111 |
112 | |- common.scss 里面放公共的、全局的、使用频率比较高的样式,这样可以方便组织样式。如:.hide{display: none;}
113 | |- mixin.scss 里面放全局的样式方法,已经在webpack中全局引入,使用的时候 @include ct();
114 | |- reset.scss 里面放页面样式重置的css,不需要修改
115 | |- var.scss 里面放公共变量,方便全局样式,如:颜色、字体大小等
116 |
117 | 3、vuex
118 |
119 | store 按页面或者业务划分,然后统一由 store/index.js 输出
120 |
121 | 所有的mutations方法,需要现在最外层的 mutation-type.js 说明后,然后在单个 mutations.js 中引入使用
122 |
123 | 4、请求
124 |
125 | 请求统一在 api 文件夹下管理,可以按照页面或者业务划分。所有的请求放在vuex的action中,不得放在页面内,数据统一存储在state中,取数据用getter,修改用mutations,保持数据的单向性。
126 |
127 | ```
128 | 如何Vuex的作用及处理数据的方式?
129 |
130 | 在前端可以把Vuex看做后端mvc中的m层,即数据处理层,它与控制器分开,只有它与数据库接触,控制器只是用它调用或是获取数据,并不能直接修改数据库数据,也是需要m层来修改数据库。
131 |
132 | 而在Vuex(store)中,action就负责取数据(请求接口),getter负责处理数据返回相应格式,mutation负责改变数据(即改变数据库),而state就是那个数据库。
133 |
134 | 即Vuex就是单纯做数据处理的,它应该与页面分开,保持数据的独立和单向性。
135 | ```
136 |
137 | 5、格式化方法
138 |
139 | 数据的格式化,如时间戳、隐藏手机号、格式化银行卡等操作,不建议在vue页面中用方法修改数据,建议在 fliters 中说明过滤器,然后在模版中 {{data | fliter}} 格式化数据,可以使数据更干净。
140 |
141 | 6、语言
142 |
143 | 建议一直使用i18n作为语言的处理工具,方便国际化和统一管理,它在 config/lang 中(建议按页面区分)。
144 |
145 | 它的使用方法是,在.vue文件中 {{$t('name')}}
146 |
147 | 在js中,获取当前语言的方法是,console.log(this.$i18n.locale),设置语言的方法同样 this.$18n.local = 'zh-CN'
148 |
149 | 7、vue-router
150 |
151 | 统一采用按需加载,如:const Home = () => import( /* webpackChunkName: "home" */ '../views/home.vue'),其中的 webpackChunkName 指的是打包出来的包名。
152 |
153 | 8、布局
154 |
155 | 布局在 layouts 文件夹中,然后在 App.vue 中引入,放在了路由的最外层,充当整个项目的公共布局,如:页眉、页脚等。
156 |
157 | 9、参考
158 |
159 | [凹凸实验室前端代码规范](https://guide.aotu.io/docs/)
160 | [风格指南](https://cn.vuejs.org/v2/style-guide/)
161 |
162 | ## 常见问题
163 |
164 | 1、yarn push 不能用怎么办?
165 |
166 | ```
167 | 命令行工具切到项目目录下,执行 chmod 755 push.sh
168 | ```
169 |
170 | 2、如何用手机调试,或者让别人看到?
171 |
172 | ```
173 | 项目启动的时候已经是你的本地ip了,只需要将手机和电脑连在同一个网络环境下(如:连同一个WIFI),复制地址栏的地址发送给手机即可访问。
174 |
175 | 之后,你改动任何一个地方,就能方便的在手机上看到改动了。也可以将这个地址发给同一网络环境的其他人,他们也就可以看到效果了。
176 | ```
177 |
178 | 3、我要开发移动端H5怎么做?
179 |
180 | ```
181 | 1、切换到mobile_template分支,这是为移动端开发专门准备的模版,执行命令:git checkout mobile_template。
182 | 2、查看设计稿的宽度,如750px、375px。
183 | 3、将设计稿宽度/10,然后修改 package.json 中的 postcss-px2rem 下的 remUnit
184 | 4、样式、css等就可以按照设计稿的大小用px来写了
185 |
186 | 例子:
187 | "postcss": {
188 | "plugins": {
189 | "autoprefixer": {},
190 | "postcss-px2rem": {
191 | "remUnit": 37.5 // 修改这个值为"设计稿宽度/10"
192 | }
193 | }
194 | },
195 | ```
196 |
197 | 4、我想把格式化后4个空格改成2个怎么改?
198 |
199 | ```
200 | .editorconfig 中 indent_size = 2 即可
201 | ```
202 |
203 | 5、我怎么在组件中引入图片?
204 |
205 | ```
206 | 图片目录在src/assets下,在组件中引入方式如下:
207 |
208 |
209 | Tips:建议动态引入,避免调整页面带来的路径问题
210 | ```
211 |
212 | 6、我怎么无视层级导入css,或是引入图片?
213 |
214 | ```
215 | 使用 ~
216 |
217 | @import "~@/scss/_var.css";
218 | @import "~@/assets/iconfont/iconfont.css";
219 | background: url("~@/assets/img/xxx.png");
220 |
221 | 原理:
222 | CSS loader 会把把非根路径的url解释为相对路径,加~前缀才会解释成模块路径。
223 |
224 | 参考文档:
225 | https://cli.vuejs.org/zh/guide/css.html#%E5%BC%95%E7%94%A8%E9%9D%99%E6%80%81%E8%B5%84%E6%BA%90
226 | https://cli.vuejs.org/zh/guide/html-and-static-assets.html#url-%E8%BD%AC%E6%8D%A2%E8%A7%84%E5%88%99
227 | ```
228 |
229 | 7、我想修改Toast的样式怎么办?
230 |
231 | ```
232 | src/components/toast/toast.vue
233 | 修改此文件即可,更多修改可以查找 "vue插件" 资料。
234 | ```
235 |
236 | 8、我想在本地和服务端调试怎么弄?
237 |
238 | ```
239 | 修改 vue.config.js 中的 proxy 为你的服务端地址即可,如:
240 |
241 | proxy: 'http://xxx.com'
242 | ```
243 |
244 | 9、我引入了第三方组件,如何修改样式?
245 |
246 | ```
247 | 有两个方法可以解决这个问题:
248 |
249 | 方法一:
250 | 在scoped的scss中使用 >>> 或者 /deep/ ,它们是vue提供的"深度作用选择器",你可以参考 —— https://vue-loader.vuejs.org/zh/guide/scoped-css.html#%E6%B7%B1%E5%BA%A6%E4%BD%9C%E7%94%A8%E9%80%89%E6%8B%A9%E5%99%A8
251 |
255 |
256 | 方法二:
257 | 单独写一个style,不加scoped,在这里面单独修改
258 |
261 | ```
262 |
263 | 10、UI说我移动端的border太粗了,和他UI严重不符,怎么解决?
264 |
265 | ```
266 | 移动端屏幕像素比(dpr)导致的,如果你开发移动端,我为你准备了现成的1px border,不要使用border: 1px solid #ff6632,而是用:
267 |
268 | @include border-bottom();
269 | @include border-top();
270 | @include border-left();
271 | @include border-right();
272 | @include border();
273 |
274 | 同时你也可以传入一些配置项来设置border的颜色、粗细等等,详细的请切换到 mobile_template 分支,查看 src/style/mixin.scss 里面的 border 类。
275 | ```
276 |
277 | 11、在数学运算时,出现了1.1999999999这种无限小数位,我该怎么解决啊?
278 |
279 | ```
280 | 浮点数运算失精,可以用 number-precision 这个插件,具体使用方法为:
281 |
282 | yarn add number-precision
283 |
284 | import NP from 'number-precision'
285 |
286 | NP.plus(0.1, 0.2)
287 |
288 | ......
289 |
290 | 更多使用方法参考官方文档:https://github.com/nefe/number-precision
291 | ```
292 |
293 | 12、前端好多日期展示,每次在methods里面处理好麻烦,我又不想修改原数据,有更方便的办法吗?
294 |
295 | ```
296 | 有!你完全不需要每次写方法处理,只需要在需要模版中用 "| 过滤器" 即可!
297 |
298 | 因为我们全局混入了过滤器,内置了常用的方法,因此,对待时间戳转日期、隐藏手机号中间四位等要求,我们可以这样做:
299 |
300 | 时间戳:{{1544179366 | timeFilter}} => 2018-12-07 18:42:46
301 | 手机格式化:{{15311959057 | formatPhone}} => 153****9057
302 | 银行卡格式化:{{123123123123132 | formatBank}} => 1231 2312 3123 132
303 | 千分位分隔符:{{5000039 | toThousands}} => 5,000,039
304 |
305 | 如果你需要自定义一些方法,可以在 src/filters/index.js 里自己添加,用法和示例一样。
306 |
307 | 看,就是如此方便!向一堆重复方法说Bye Bye吧!
308 |
309 | 更多使用方法参考官方文档:https://cn.vuejs.org/v2/guide/filters.html
310 | ```
311 |
312 | 13、我用你这个框架开发微信公众号,在做微信分享的时候wx.config总是不过,一直报"invalid signature",后端也找不出毛病来,你给我解决解决吧
313 |
314 | ```
315 | vue这样引入sdk
316 | yarn add weixin-js-sdk
317 | import wx from 'weixin-js-sdk'
318 |
319 | 别的先不说,我先给你解决方案:
320 | 1、vue-router用hash模式
321 | 2、wx.config里面的url要动态获取并且encode,直接把这个换上去 encodeURIComponent(location.href.split('#')[0])
322 | 3、wx.config放到页面mounted里面,生命周期放的越前越好
323 |
324 | 为什么?
325 | 首先,wx.config里面的url必须是动态获取的,不能写死,因为在分享的时候微信会给你在链接里下毒,没毒不让分享。
326 | 其次,url必须和你后台配置的js安全域名一样,本地测试不了你可以放到开发环境测试,实在不行你过来打我。
327 | 最后,微信开发者文档在这里,有不懂的加群问我,群号在最后 https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115
328 | ```
329 |
330 | 14、为什么我关了node服务,再次yarn serve的时候,端口号就变了,每关一次,端口号就会变一次,调试起来好麻烦呀,能不能让他不变?
331 |
332 | ```
333 | 如果你是mac电脑,试试用 control + c 来关闭node服务,而不要用 control + z
334 |
335 | windows电脑,我不知道
336 | ```
337 |
338 | 15、我用你这个开发微信公众号,在IOS手机上下滑动的时候,整个页面就被拖着走,这个怎么解决?
339 |
340 | ```
341 | 使用iNoBounce,使用方法:
342 | 1、在public文件夹下自己创建一份inobounce.min.js
343 | 2、然后在index.html加这段代码
344 |
345 | github地址:https://github.com/wangyupo/iNoBounce
346 | ```
347 |
348 | 16、加上iNoBounce以后IOS不能滚动什么情况?部分页面不能滚动又是什么情况?
349 |
350 | ```
351 | 所有页面都不能滚动
352 | 在最外层加这样的css:
353 | overflow-y:scroll;
354 | -webkit-overflow-scrolling:touch;
355 |
356 | 如果有单个页面不滚动,比如你在页面里面加了vant的list组件,发现页面不能上下滑动
357 | 本页面最外层的css这样写:
358 | height: 100vh;
359 | overflow-y:scroll;
360 | -webkit-overflow-scrolling:touch;
361 | ```
362 |
363 | 17、ios input获取焦点有延迟怎么解决?
364 |
365 | ```
366 | //main.js 引入
367 | import FastClick from 'fastclick';
368 | FastClick.attach(document.body);
369 | FastClick.prototype.focus = (ele) => { 'use strict'; ele.focus(); }; //修改focus()方法
370 |
371 | 参考链接:https://github.com/ftlabs/fastclick/issues/583
372 | ```
373 |
374 | 18、我公司有多个环境,我想打包出对应的代码包来,我该怎么弄?
375 |
376 | ```
377 | .env 文件里配置所有环境都能用的变量
378 | .env.development 文件里配置开发环境的变量
379 | .env.production 文件里配置生产环境的变量
380 | .env.xx 文件内变量的使用方法都是 process.env. 开头,后跟你定义的变量名称,如 request.js 中的 process.env.VUE_APP_API ,用来控制不同打包情况下的api请求地址不同。
381 |
382 | 最后需要打包 development 环境的包,就运行 yarn devbuild,打包生产环境的包,就运行 yarn build。
383 |
384 | 参考链接:
385 | https://cli.vuejs.org/zh/guide/mode-and-env.html#%E6%A8%A1%E5%BC%8F
386 | https://blog.csdn.net/qq_36407748/article/details/82050976
387 | ```
388 |
389 | ## 更多自定义配置
390 | See [Configuration Reference](https://cli.vuejs.org/config/)
391 |
392 | ## 进阶vue3
393 | vue3 的入门书,它有直观的示例、代码、说明,可以让你快速上手 vue3 的 setup 写法,迅速过渡到 组合式API 写法中去,传送门:[vue3-cookbook](https://github.com/wangyupo/vue3-cookbook)
394 |
395 | ## License
396 |
397 | [MIT © Richard McRichface.](https://github.com/wangyupo/vue-vuex-router/blob/master/LICENSE)
398 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/app'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/init.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | echo "开始初始化模版..."
4 | echo "我们需要精简一些项目无关文件,请放心使用"
5 | rm -rf 使用说明.md LICENSE README.md
6 | echo "开始将模版替换为简洁版本"
7 | cp './src/views/template.vue' './src/views/home.vue'
8 | cp './src/views/template.vue' './src/views/about.vue'
9 | echo "模版初始化完成,开始销毁本脚步和模版文件,祝开发顺利!"
10 | rm -rf './src/views/template.vue' init.sh
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue_template",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "devbuild": "vue-cli-service build --mode development",
9 | "analyz": "vue-cli-service build --report",
10 | "push": "./push.sh"
11 | },
12 | "dependencies": {
13 | "animate.css": "^3.7.0",
14 | "axios": "^0.18.0",
15 | "dayjs": "^1.7.7",
16 | "number-precision": "^1.2.1",
17 | "tween.js": "^16.6.0",
18 | "vconsole": "^3.2.0",
19 | "vue": "^2.5.17",
20 | "vue-i18n": "^8.3.2",
21 | "vue-router": "^3.0.1",
22 | "vuex": "^3.0.1"
23 | },
24 | "devDependencies": {
25 | "@vue/cli-plugin-babel": "^3.2.0",
26 | "@vue/cli-service": "^3.2.0",
27 | "node-sass": "^4.9.0",
28 | "sass-loader": "^7.0.1",
29 | "vue-template-compiler": "^2.5.17"
30 | },
31 | "postcss": {
32 | "plugins": {
33 | "autoprefixer": {}
34 | }
35 | },
36 | "browserslist": [
37 | "> 1%",
38 | "last 2 versions",
39 | "not ie <= 8"
40 | ]
41 | }
42 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangyupo/vue-vuex-router/2b13bedeadfcf53af1c1866aad06c8f3961c00c4/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | vue_template
11 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/push.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | echo "正在添加文件..."
4 | git add .
5 | echo -n "正在提交备注...,请填写备注(可空)"
6 | read remarks
7 | if [ ! -n "$remarks" ];then
8 | remarks="常规提交"
9 | fi
10 | git commit -m "$remarks"
11 | echo "正在开始提交代码..."
12 | git push
13 | echo "代码提交成功,正在关闭..."
14 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
19 |
20 |
35 |
--------------------------------------------------------------------------------
/src/api/common.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 请求统一管理
3 | */
4 | import Request from '../common/request';
5 |
6 | /* Common */
7 | export const rankGender = data => Request.get('/ranking/gender', data);
8 |
--------------------------------------------------------------------------------
/src/assets/iconfont/README.md:
--------------------------------------------------------------------------------
1 | ## 为什么以及如何使用以icon font?
2 |
3 | ### 为什么?
4 |
5 | ```
6 | iconfont在针对小型图标的处理上能帮助我们减少网络请求,而且更便于控制(如:用css的font-size、color即可控制其大小、颜色)
7 | ```
8 |
9 | ### 如何使用?
10 |
11 | ```
12 | 1、登陆iconfont —— http://www.iconfont.cn/home/index?spm=a313x.7781069.1998910419.2
13 | 2、选择自己想用的图标到购物车
14 | 3、点击购物车,将图标添加到一个项目中去
15 | 4、进入项目,选择Font class
16 | 5、点击下载至本地
17 | 6、将以下文件拷贝自己的项目目录去iconfont.eot、iconfont.svg、iconfont.ttf、iconfont.woff、iconfont.css
18 | 7、在需要使用的地方@import 'iconfont.css'
19 | 8、用以下方式即可应用图标
20 |
21 |
22 | 补充:
23 | 图标的大小和颜色可以通过css来控制,比如:color: #ff6632;font-size: 16px;
24 | ```
25 |
26 | ### svg如何转换成iconfont?
27 |
28 | ```
29 | 1、进入iconfont的上传页面 —— https://www.iconfont.cn/icons/upload?spm=a313x.7781069.1998910419.d059fa781
30 | 2、将你的svg上传上去
31 | 3、在"图标管理" - "我的图标" - "我上传的icon"中,把icon添加到购物车
32 | 4、将购物车里所有icon添加到项目中
33 | 5、找到"我的项目",点击"下载到本地"
34 | 6、svg转iconfont完成
35 | ```
36 |
--------------------------------------------------------------------------------
/src/assets/iconfont/iconfont.css:
--------------------------------------------------------------------------------
1 |
2 | @font-face {font-family: "iconfont";
3 | src: url('iconfont.eot?t=1543549454914'); /* IE9*/
4 | src: url('iconfont.eot?t=1543549454914#iefix') format('embedded-opentype'), /* IE6-IE8 */
5 | url('data:application/x-font-woff;charset=utf-8;base64,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') format('woff'),
6 | url('iconfont.ttf?t=1543549454914') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
7 | url('iconfont.svg?t=1543549454914#iconfont') format('svg'); /* iOS 4.1- */
8 | }
9 |
10 | .iconfont {
11 | font-family:"iconfont" !important;
12 | font-size:16px;
13 | font-style:normal;
14 | -webkit-font-smoothing: antialiased;
15 | -moz-osx-font-smoothing: grayscale;
16 | }
17 |
18 | .icon-person:before { content: "\e80b"; }
19 |
20 | .icon-mobile:before { content: "\e627"; }
21 |
22 | .icon-twitter:before { content: "\ecc1"; }
23 |
24 | .icon-chat:before { content: "\e610"; }
25 |
26 | .icon-email:before { content: "\e653"; }
27 |
28 | .icon-phone-circle:before { content: "\e69f"; }
29 |
30 | .icon-weibo:before { content: "\e608"; }
31 |
32 | .icon-mail:before { content: "\e70a"; }
33 |
34 | .icon-phone-:before { content: "\e700"; }
35 |
36 | .icon-wechat:before { content: "\e60e"; }
37 |
38 |
--------------------------------------------------------------------------------
/src/assets/iconfont/iconfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangyupo/vue-vuex-router/2b13bedeadfcf53af1c1866aad06c8f3961c00c4/src/assets/iconfont/iconfont.eot
--------------------------------------------------------------------------------
/src/assets/iconfont/iconfont.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
57 |
--------------------------------------------------------------------------------
/src/assets/iconfont/iconfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangyupo/vue-vuex-router/2b13bedeadfcf53af1c1866aad06c8f3961c00c4/src/assets/iconfont/iconfont.ttf
--------------------------------------------------------------------------------
/src/assets/iconfont/iconfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangyupo/vue-vuex-router/2b13bedeadfcf53af1c1866aad06c8f3961c00c4/src/assets/iconfont/iconfont.woff
--------------------------------------------------------------------------------
/src/assets/img/common/cross_a.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangyupo/vue-vuex-router/2b13bedeadfcf53af1c1866aad06c8f3961c00c4/src/assets/img/common/cross_a.png
--------------------------------------------------------------------------------
/src/assets/img/common/cross_h.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangyupo/vue-vuex-router/2b13bedeadfcf53af1c1866aad06c8f3961c00c4/src/assets/img/common/cross_h.png
--------------------------------------------------------------------------------
/src/assets/img/common/cross_n.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangyupo/vue-vuex-router/2b13bedeadfcf53af1c1866aad06c8f3961c00c4/src/assets/img/common/cross_n.png
--------------------------------------------------------------------------------
/src/assets/img/common/icon_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangyupo/vue-vuex-router/2b13bedeadfcf53af1c1866aad06c8f3961c00c4/src/assets/img/common/icon_1.png
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangyupo/vue-vuex-router/2b13bedeadfcf53af1c1866aad06c8f3961c00c4/src/assets/logo.png
--------------------------------------------------------------------------------
/src/common/README.md:
--------------------------------------------------------------------------------
1 | ### 为什么工具类叫util?
2 |
3 | ```
4 | Util是utiliy的缩写,是一个多功能、基于工具的包。
5 |
6 | java.util是包含集合框架、遗留的 collection 类、事件模型、日期和时间设施、国际化和各种实用工具类(字符串标记生成器、随机数生成器和位数组、日期Date类、堆栈Stack类、向量Vector类等)。集合类、时间处理模式、日期时间工具等各类常用工具包。
7 | ```
8 |
--------------------------------------------------------------------------------
/src/common/request.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import env from '@/config/env';
3 |
4 | const MOCKURL = ''; // mock数据地址
5 |
6 | /**
7 | * 自定义Axios实例
8 | */
9 | const AJAX = axios.create({
10 | baseURL: process.env.VUE_APP_API,
11 | timeout: 30000,
12 | withCredentials: env.credential
13 | });
14 |
15 | // 添加请求拦截器
16 | AJAX.interceptors.request.use(function (config) {
17 | // 在发送请求之前做些什么
18 | // if (process.env.NODE_ENV === 'development') {
19 | // config.url = `http://${location.host}` + config.url; // 自定义反向代理,可以在demo阶段打开看下请求效果
20 | // }
21 | return config;
22 | }, function (error) {
23 | // 对请求错误做些什么
24 | return Promise.reject(error);
25 | });
26 |
27 | // 添加响应拦截器
28 | AJAX.interceptors.response.use(function (response) {
29 | // 对响应数据做点什么
30 | return response.data;
31 | }, function (error) {
32 | // 对响应错误做点什么,比如400、401、402等等
33 | if (error && error.response) {
34 | console.log(error.response)
35 | }
36 | return Promise.reject(error);
37 | });
38 |
39 | // 定义对外Get、Post、File请求
40 | export default {
41 | get(url, param = {}, headers = {}) {
42 | return AJAX.get(url, {
43 | params: param,
44 | headers,
45 | })
46 | },
47 | post(url, param = null, headers = {}) {
48 | return AJAX.post(url, param, {
49 | headers,
50 | })
51 | },
52 | put(url, param = null, headers = {}) {
53 | return AJAX.put(url, param, {
54 | headers,
55 | })
56 | },
57 | file(url, param = null, headers = {}) {
58 | return AJAX.post(url, param, {
59 | headers: Object.assign({
60 | 'Content-Type': 'multipart/form-data'
61 | }, headers)
62 | })
63 | },
64 | delete(url, param = null, headers = {}) {
65 | return AJAX.delete(url, {
66 | param,
67 | headers: Object.assign({
68 | 'Content-Type': 'multipart/form-data'
69 | }, headers)
70 | })
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/common/sniff.js:
--------------------------------------------------------------------------------
1 | /**
2 | * sniff
3 | * 使用场景:查看运行环境,设备,系统等信息
4 | *
5 | * @description
6 | *
7 | * | 运行环境 | 是否支持 |
8 | * | :-----: | :-----: |
9 | * | touch | √ |
10 | * | wechat | √ |
11 | *
12 | */
13 | let sniff = {
14 | browsers: {},
15 | info: {}
16 | }; // 结果
17 |
18 | let ua = navigator.userAgent,
19 | platform = navigator.platform,
20 | android = ua.match(/(Android);?[\s\/]+([\d.]+)?/), // 匹配 android
21 | ipad = ua.match(/(iPad).*OS\s([\d_]+)/), // 匹配 ipad
22 | ipod = ua.match(/(iPod)(.*OS\s([\d_]+))?/), // 匹配 ipod
23 | iphone = ua.match(/(iPhone\sOS)\s([\d_]+)/), // 匹配 iphone
24 | webApp = ua.indexOf('Safari') === -1; // 匹配 桌面 webApp
25 |
26 | let browsers = {
27 | wechat: ua.match(/(MicroMessenger)\/([\d\.]+)/), // 匹配 weChat
28 | alipay: ua.match(/(AlipayClient)\/([\d\.]+)/), // 匹配 支付宝
29 | qq: ua.match(/(MQQBrowser)\/([\d\.]+)/), // 匹配 QQ 浏览器
30 | weibo: ua.match(/(weibo__)([\d\.]+)/), // 匹配 微博
31 | uc: ua.match(/(UCBrower)\/([\d\.]+)/), // 匹配 uc
32 | opera: ua.match(/(Opera)\/([\d\.]+)/) // 匹配 opera
33 | };
34 |
35 | // iphone model
36 | let model = ua.match(/(iPhone\sOS).*m\/(\d+\.\d+)/);
37 |
38 | // iphoneX model
39 | let iphoneXModel = ['10.3', '10.6'];
40 |
41 | // 系统
42 |
43 | sniff.ios = sniff.android = sniff.iphone = sniff.ipad = sniff.ipod = false;
44 |
45 | if (android) {
46 | sniff.os = 'android';
47 | sniff.osVersion = android[2];
48 | sniff.android = true;
49 | }
50 |
51 | if (ipad || iphone || ipod) {
52 | sniff.os = 'ios';
53 | sniff.ios = true;
54 | }
55 |
56 | if (iphone) {
57 | sniff.osVersion = iphone[2].replace(/_/g, '.');
58 | sniff.iphone = true;
59 | sniff.imobile = true;
60 | sniff.model = model && model[2];
61 | // iPhoneX 竖屏宽高
62 | const iphoneXVerticalWidth = 375;
63 | const iphoneXVerticalHeight = 812;
64 | const { width, height } = window.screen || {};
65 | sniff.iphoneX = iphoneXModel.indexOf(sniff.model) > -1
66 | || (width === iphoneXVerticalWidth && height === iphoneXVerticalHeight)
67 | // 考虑横屏情况
68 | || (width === iphoneXVerticalHeight && height === iphoneXVerticalWidth);
69 | }
70 |
71 | if (ipad) {
72 | sniff.osVersion = ipad[2].replace(/_/g, '.');
73 | sniff.ipad = true;
74 | }
75 |
76 | if (ipod) {
77 | sniff.osVersion = ipod[3] ? ipod[3].replace(/_/g, '.') : null;
78 | sniff.ipod = true;
79 | sniff.imobile = true;
80 | }
81 |
82 | // iOS 8+ changed UA
83 | if (sniff.ios && sniff.osVersion && ua.indexOf('Version/') >= 0) {
84 | if (sniff.osVersion.split('.')[0] === '10') {
85 | sniff.osVersion = ua.toLowerCase().split('version/')[1].split(' ')[0];
86 | }
87 | }
88 |
89 | if (sniff.osVersion) {
90 | sniff.osVersionN = parseInt(sniff.osVersion.match(/\d+\.?\d*/)[0]);
91 | }
92 |
93 | // 配置
94 |
95 | sniff.pixelRatio = window.devicePixelRatio || 1;
96 |
97 | sniff.retina = sniff.pixelRatio >= 2;
98 |
99 | // 浏览器
100 | for (let key in browsers) {
101 | if (browsers[key]) {
102 | webApp = false;
103 | sniff.browsers[key] = browsers[key][2];
104 | } else {
105 | sniff.browsers[key] = false;
106 | }
107 | }
108 |
109 | sniff.webApp = sniff.os === 'ios' && webApp;
110 |
111 | // 其他信息
112 | ua.split(' ').forEach((item) => {
113 | var kv = item.split('/');
114 | if (kv.length === 2) {
115 | sniff.info[kv[0]] = kv[1];
116 | }
117 | });
118 |
119 | // PC
120 | sniff.pc = platform.indexOf('Mac') === 0 || platform.indexOf('Win') === 0 || (platform.indexOf('linux') === 0 && !sniff.android);
121 |
122 |
123 | window.sniff = sniff;
124 |
125 | export default sniff;
126 |
--------------------------------------------------------------------------------
/src/common/util.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 存储localStorage
3 | */
4 | export const setStore = (name, content) => {
5 | if (!name) return;
6 | if (typeof content !== 'string') {
7 | content = JSON.stringify(content);
8 | }
9 | window.localStorage.setItem(name, content);
10 | };
11 |
12 | /**
13 | * 获取localStorage
14 | */
15 | export const getStore = name => {
16 | if (!name) return;
17 | return window.localStorage.getItem(name);
18 | };
19 |
20 | /**
21 | * 删除localStorage
22 | */
23 | export const removeStore = name => {
24 | if (!name) return;
25 | window.localStorage.removeItem(name);
26 | };
27 |
28 | /**
29 | * 生成随机字符串(可指定长度)
30 | * @param len
31 | * @returns {string}
32 | */
33 | export const randomString = (len) => {
34 | len = len || 8;
35 | let $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';
36 | /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/
37 | let maxPos = $chars.length;
38 | let pwd = '';
39 | for (let i = 0; i < len; i++) {
40 | pwd += $chars.charAt(Math.floor(Math.random() * maxPos));
41 | }
42 | return pwd;
43 | };
44 |
45 | /**
46 | * randomWord 产生任意长度随机字母数字组合
47 | * @param randomFlag 是否任意长度 min-任意长度最小位[固定位数] max-任意长度最大位
48 | * @param min
49 | * @param max
50 | * @returns {string}
51 | * 调用方法:
52 | * 生成 3 - 32 位随即字符串
53 | * randomWord(true,3,32); 例如:olyOXUF5oDsuMmXl3Mi48
54 | * 生成 32 位随机字符串
55 | * randomWord(false,32); 例如:fjpnWj29Bb8boiXbLeDF0nxkR4aYcLRl
56 | */
57 | export const randomWord = (randomFlag, min, max) => {
58 | let str = "",
59 | range = min,
60 | arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
61 |
62 | // 随机产生
63 | if (randomFlag) {
64 | range = Math.round(Math.random() * (max - min)) + min;
65 | }
66 | for (let i = 0; i < range; i++) {
67 | let pos = Math.round(Math.random() * (arr.length - 1));
68 | str += arr[pos];
69 | }
70 | return str;
71 | };
72 |
73 | /**
74 | * 获取url后参数
75 | */
76 | export const GetRequest = () => {
77 | let url = location.search; //获取url中"?"符后的字串
78 | let theRequest = new Object();
79 | if (url.indexOf("?") != -1) {
80 | let str = url.substr(1);
81 | let strs = str.split("&");
82 | for (let i = 0; i < strs.length; i++) {
83 | theRequest[strs[i].split("=")[0]] = (strs[i].split("=")[1]);
84 | }
85 | }
86 | return theRequest;
87 | };
88 |
89 | /**
90 | * 生成随机颜色值
91 | */
92 | export const getRandomColor = () => {
93 | const rgb = [];
94 | for (let i = 0; i < 3; ++i) {
95 | let color = Math.floor(Math.random() * 256).toString(16);
96 | color = color.length === 1 ? '0' + color : color;
97 | rgb.push(color)
98 | }
99 | return '#' + rgb.join('')
100 | };
101 |
102 | /**
103 | * 验证身份证号
104 | * @param el 号码输入input
105 | * @returns {boolean}
106 | */
107 | export const checkCardNo = (el) => {
108 | let txtval = el.value;
109 | let reg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
110 | return reg.test(txtval);
111 | };
112 |
113 | /**
114 | * 获取字符串字节长度
115 | * @param {String}
116 | * @returns {Boolean}
117 | */
118 | export const checkLength = (v) => {
119 | let realLength = 0;
120 | let len = v.length;
121 | for (let i = 0; i < len; i++) {
122 | let charCode = v.charCodeAt(i);
123 | if (charCode >= 0 && charCode <= 128) realLength += 1;
124 | else realLength += 2;
125 | }
126 | return realLength;
127 | };
128 |
129 | /**
130 | * 判断微信浏览器
131 | * @returns {Boolean}
132 | */
133 | export const isWeiXin = () => {
134 | let ua = window.navigator.userAgent.toLowerCase();
135 | if (ua.match(/MicroMessenger/i) == 'micromessenger') {
136 | return true;
137 | } else {
138 | return false;
139 | }
140 | };
141 |
142 | /**
143 | * 写cookies
144 | */
145 | export const setCookie = (name, value, time) => {
146 | let strsec = getsec(time);
147 | let exp = new Date();
148 | exp.setTime(exp.getTime() + strsec * 1);
149 | document.cookie = name + "=" + escape(value) + ";expires=" + exp.toGMTString();
150 | };
151 |
152 | /**
153 | * 读取cookies
154 | */
155 | export const getCookie = (name) => {
156 | let arr, reg = new RegExp("(^| )" + name + "=([^;]*)(;|$)");
157 | if (arr = document.cookie.match(reg)) return (arr[2]);
158 | else return null;
159 | };
160 |
161 | /**
162 | * 删除cookies
163 | */
164 | export const delCookie = (name) => {
165 | let exp = new Date();
166 | exp.setTime(exp.getTime() - 1);
167 | let cval = getCookie(name);
168 | if (cval != null) document.cookie = name + "=" + cval + ";expires=" + exp.toGMTString();
169 | };
170 |
171 | /**
172 | * 浏览器判断
173 | * 用法示例——多套页面判断是否显示移动端:
174 | * let ua = parseUA();
175 | * if (!ua.mobile) {
176 | * location.href = './pc.html';
177 | * }
178 | */
179 | export const parseUA = () => {
180 | let u = navigator.userAgent;
181 | let u2 = navigator.userAgent.toLowerCase();
182 | return { //移动终端浏览器版本信息
183 | trident: u.indexOf('Trident') > -1,
184 | //IE内核
185 | presto: u.indexOf('Presto') > -1,
186 | //opera内核
187 | webKit: u.indexOf('AppleWebKit') > -1,
188 | //苹果、谷歌内核
189 | gecko: u.indexOf('Gecko') > -1 && u.indexOf('KHTML') == -1,
190 | //火狐内核
191 | mobile: !!u.match(/AppleWebKit.*Mobile.*/),
192 | //是否为移动终端
193 | ios: !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/),
194 | //ios终端
195 | android: u.indexOf('Android') > -1 || u.indexOf('Linux') > -1,
196 | //android终端或uc浏览器
197 | iPhone: u.indexOf('iPhone') > -1,
198 | //是否为iPhone或者QQHD浏览器
199 | iPad: u.indexOf('iPad') > -1,
200 | //是否iPad
201 | webApp: u.indexOf('Safari') == -1,
202 | //是否web应该程序,没有头部与底部
203 | iosv: u.substr(u.indexOf('iPhone OS') + 9, 3),
204 | weixin: u2.match(/MicroMessenger/i) == "micromessenger",
205 | ali: u.indexOf('AliApp') > -1,
206 | };
207 | };
208 |
209 | /**
210 | * 生成UUID
211 | * @returns {string}
212 | */
213 | export const generateUUID = () => {
214 | let d = new Date().getTime();
215 | let uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
216 | let r = (d + Math.random() * 16) % 16 | 0;
217 | d = Math.floor(d / 16);
218 | return (c === 'x' ? r : (r & 0x7 | 0x8)).toString(16);
219 | });
220 | return uuid;
221 | };
222 |
223 | /**
224 | * 删除左右两端的空格
225 | * @param str
226 | * @returns {string | * | void}
227 | */
228 | function trim(str) {
229 | return str.replace(/(^\s*)|(\s*$)/g, "");
230 | }
231 |
232 | /**
233 | * 删除左边的空格
234 | * @param str
235 | * @returns {string | * | void}
236 | */
237 | function ltrim(str) {
238 | return str.replace(/(^\s*)/g, "");
239 | }
240 |
241 | /**
242 | * 删除右边的空格
243 | * @param str
244 | * @returns {string | * | void}
245 | */
246 | function rtrim(str) {
247 | return str.replace(/(\s*$)/g, "");
248 | }
249 |
250 | /**
251 | * 对象数组转二维数组
252 | * @param objArr
253 | */
254 | function obj2Arr(objArr) {
255 | objArr.length > 0 && objArr.map(item => {
256 | return Object.values(item);
257 | })
258 | }
259 |
260 | /**
261 | * 找出对象数组中某属性的最大值
262 | * @param array
263 | * @param item
264 | * @returns val
265 | */
266 | function maxItemInObjArr(array, item) {
267 | let max = Math.max.apply(Math, array.map(function (obj) {
268 | return obj[item];
269 | }));
270 | return max;
271 | }
272 |
273 | /**
274 | * 判断当前网络环境
275 | */
276 | export const isWifi = () => {
277 | try {
278 | let wifi = true;
279 | let ua = window.navigator.userAgent;
280 | let con = window.navigator.connection;
281 | // 如果是微信
282 | if (/MicroMessenger/.test(ua)) {
283 | if (ua.indexOf('WIFI') >= 0) {
284 | return true
285 | } else {
286 | wifi = false
287 | }
288 | // 如果支持navigator.connection
289 | } else if (con) {
290 | let network = con.type;
291 | if (network !== 'wifi' && network !== '2' && network !== 'unknown') {
292 | wifi = false
293 | }
294 | }
295 | return wifi
296 | } catch (e) {
297 | return false
298 | }
299 | };
300 |
301 | /**
302 | * 首字母大写
303 | * @param str
304 | * @returns {string}
305 | */
306 | export const fistLetterUpper = (str) => {
307 | return str.charAt(0).toUpperCase() + str.slice(1);
308 | };
309 |
310 | /**
311 | * 过滤非法字符串
312 | */
313 | export const illegalFilter = (str) => {
314 | let regEn = /[`~!@#$%^&*()_+<>?:"{},.\/;'[\]]/im;
315 | let regCn = /[·!#¥(——):;“”‘、,|《。》?、【】[\]]/im;
316 |
317 | if (regEn.test(str) || regCn.test(str)) return false;
318 | return true;
319 | };
320 |
--------------------------------------------------------------------------------
/src/components/CountDown.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 倒计时组件
3 | * @type {number}
4 | * Github —— https://github.com/fengyuanchen/vue-countdown#browser-support
5 | */
6 | const MILLISECONDS_SECOND = 1000;
7 | const MILLISECONDS_MINUTE = 60 * MILLISECONDS_SECOND;
8 | const MILLISECONDS_HOUR = 60 * MILLISECONDS_MINUTE;
9 | const MILLISECONDS_DAY = 24 * MILLISECONDS_HOUR;
10 | const EVENT_VISIBILITY_CHANGE = 'visibilitychange';
11 |
12 | export default {
13 | name: 'countdown',
14 |
15 | data() {
16 | return {
17 | /**
18 | * It is counting down.
19 | * @type {boolean}
20 | */
21 | counting: false,
22 |
23 | /**
24 | * The absolute end time.
25 | * @type {number}
26 | */
27 | endTime: 0,
28 |
29 | /**
30 | * The remaining milliseconds.
31 | * @type {number}
32 | */
33 | totalMilliseconds: 0,
34 | };
35 | },
36 |
37 | props: {
38 | /**
39 | * Starts the countdown automatically when initialized.
40 | */
41 | autoStart: {
42 | type: Boolean,
43 | default: true,
44 | },
45 |
46 | /**
47 | * Emits the countdown events.
48 | */
49 | emitEvents: {
50 | type: Boolean,
51 | default: true,
52 | },
53 |
54 | /**
55 | * The interval time (in milliseconds) of the countdown progress.
56 | */
57 | interval: {
58 | type: Number,
59 | default: 1000,
60 | validator: value => value >= 0,
61 | },
62 |
63 | /**
64 | * Generate the current time of a specific time zone.
65 | */
66 | now: {
67 | type: Function,
68 | default: () => Date.now(),
69 | },
70 |
71 | /**
72 | * The tag name of the component's root element.
73 | */
74 | tag: {
75 | type: String,
76 | default: 'span',
77 | },
78 |
79 | /**
80 | * The time (in milliseconds) to count down from.
81 | */
82 | time: {
83 | type: Number,
84 | default: 0,
85 | validator: value => value >= 0,
86 | },
87 |
88 | /**
89 | * Transforms the output props before render.
90 | */
91 | transform: {
92 | type: Function,
93 | default: props => props,
94 | },
95 | },
96 |
97 | computed: {
98 | /**
99 | * Remaining days.
100 | * @returns {number} The computed value.
101 | */
102 | days() {
103 | return Math.floor(this.totalMilliseconds / MILLISECONDS_DAY);
104 | },
105 |
106 | /**
107 | * Remaining hours.
108 | * @returns {number} The computed value.
109 | */
110 | hours() {
111 | return Math.floor((this.totalMilliseconds % MILLISECONDS_DAY) / MILLISECONDS_HOUR);
112 | },
113 |
114 | /**
115 | * Remaining minutes.
116 | * @returns {number} The computed value.
117 | */
118 | minutes() {
119 | return Math.floor((this.totalMilliseconds % MILLISECONDS_HOUR) / MILLISECONDS_MINUTE);
120 | },
121 |
122 | /**
123 | * Remaining seconds.
124 | * @returns {number} The computed value.
125 | */
126 | seconds() {
127 | return Math.floor((this.totalMilliseconds % MILLISECONDS_MINUTE) / MILLISECONDS_SECOND);
128 | },
129 |
130 | /**
131 | * Remaining milliseconds.
132 | * @returns {number} The computed value.
133 | */
134 | milliseconds() {
135 | return Math.floor(this.totalMilliseconds % MILLISECONDS_SECOND);
136 | },
137 |
138 | /**
139 | * Total remaining days.
140 | * @returns {number} The computed value.
141 | */
142 | totalDays() {
143 | return this.days;
144 | },
145 |
146 | /**
147 | * Total remaining hours.
148 | * @returns {number} The computed value.
149 | */
150 | totalHours() {
151 | return Math.floor(this.totalMilliseconds / MILLISECONDS_HOUR);
152 | },
153 |
154 | /**
155 | * Total remaining minutes.
156 | * @returns {number} The computed value.
157 | */
158 | totalMinutes() {
159 | return Math.floor(this.totalMilliseconds / MILLISECONDS_MINUTE);
160 | },
161 |
162 | /**
163 | * Total remaining seconds.
164 | * @returns {number} The computed value.
165 | */
166 | totalSeconds() {
167 | return Math.floor(this.totalMilliseconds / MILLISECONDS_SECOND);
168 | },
169 | },
170 |
171 | render(createElement) {
172 | return createElement(this.tag, this.$scopedSlots.default ? [
173 | this.$scopedSlots.default(this.transform({
174 | days: this.days,
175 | hours: this.hours,
176 | minutes: this.minutes,
177 | seconds: this.seconds,
178 | milliseconds: this.milliseconds,
179 | totalDays: this.totalDays,
180 | totalHours: this.totalHours,
181 | totalMinutes: this.totalMinutes,
182 | totalSeconds: this.totalSeconds,
183 | totalMilliseconds: this.totalMilliseconds,
184 | })),
185 | ] : this.$slots.default);
186 | },
187 |
188 | watch: {
189 | $props: {
190 | deep: true,
191 | immediate: true,
192 |
193 | /**
194 | * Update the countdown when props changed.
195 | */
196 | handler() {
197 | this.totalMilliseconds = this.time;
198 | this.endTime = this.now() + this.time;
199 |
200 | if (this.autoStart) {
201 | this.start();
202 | }
203 | },
204 | },
205 | },
206 |
207 | methods: {
208 | /**
209 | * Starts to countdown.
210 | * @public
211 | * @emits Countdown#start
212 | */
213 | start() {
214 | if (this.counting) {
215 | return;
216 | }
217 |
218 | this.counting = true;
219 |
220 | if (this.emitEvents) {
221 | /**
222 | * Countdown start event.
223 | * @event Countdown#start
224 | */
225 | this.$emit('start');
226 | }
227 |
228 | this.continue();
229 | },
230 |
231 | /**
232 | * Continues the countdown.
233 | * @private
234 | */
235 | continue() {
236 | if (!this.counting) {
237 | return;
238 | }
239 |
240 | const delay = Math.min(this.totalMilliseconds, this.interval);
241 |
242 | if (delay > 0) {
243 | this.timeout = setTimeout(() => {
244 | this.progress();
245 | }, delay);
246 | } else {
247 | this.end();
248 | }
249 | },
250 |
251 | /**
252 | * Pauses the countdown.
253 | * @private
254 | */
255 | pause() {
256 | clearTimeout(this.timeout);
257 | },
258 |
259 | /**
260 | * Progresses to countdown.
261 | * @private
262 | * @emits Countdown#progress
263 | */
264 | progress() {
265 | if (!this.counting) {
266 | return;
267 | }
268 |
269 | this.totalMilliseconds -= this.interval;
270 |
271 | if (this.emitEvents && this.totalMilliseconds > 0) {
272 | /**
273 | * Countdown progress event.
274 | * @event Countdown#progress
275 | */
276 | this.$emit('progress', {
277 | days: this.days,
278 | hours: this.hours,
279 | minutes: this.minutes,
280 | seconds: this.seconds,
281 | totalDays: this.totalDays,
282 | totalHours: this.totalHours,
283 | totalMinutes: this.totalMinutes,
284 | totalSeconds: this.totalSeconds,
285 | });
286 | }
287 |
288 | this.continue();
289 | },
290 |
291 | /**
292 | * Aborts the countdown.
293 | * @public
294 | * @emits Countdown#abort
295 | */
296 | abort() {
297 | if (!this.counting) {
298 | return;
299 | }
300 |
301 | this.pause();
302 | this.counting = false;
303 |
304 | if (this.emitEvents) {
305 | /**
306 | * Countdown abort event.
307 | * @event Countdown#abort
308 | */
309 | this.$emit('abort');
310 | }
311 | },
312 |
313 | /**
314 | * Ends the countdown.
315 | * @public
316 | * @emits Countdown#end
317 | */
318 | end() {
319 | if (!this.counting) {
320 | return;
321 | }
322 |
323 | this.pause();
324 | this.totalMilliseconds = 0;
325 | this.counting = false;
326 |
327 | if (this.emitEvents) {
328 | /**
329 | * Countdown end event.
330 | * @event Countdown#end
331 | */
332 | this.$emit('end');
333 | }
334 | },
335 |
336 | /**
337 | * Updates the count.
338 | * @private
339 | */
340 | update() {
341 | if (this.counting) {
342 | this.totalMilliseconds = Math.max(0, this.endTime - this.now());
343 | }
344 | },
345 |
346 | /**
347 | * visibility change event handler.
348 | * @private
349 | */
350 | handleVisibilityChange() {
351 | switch (document.visibilityState) {
352 | case 'visible':
353 | this.update();
354 | this.continue();
355 | break;
356 |
357 | case 'hidden':
358 | this.pause();
359 | break;
360 |
361 | default:
362 | }
363 | },
364 | },
365 |
366 | mounted() {
367 | document.addEventListener(EVENT_VISIBILITY_CHANGE, this.handleVisibilityChange);
368 | },
369 |
370 | beforeDestroy() {
371 | document.removeEventListener(EVENT_VISIBILITY_CHANGE, this.handleVisibilityChange);
372 | this.pause();
373 | },
374 | };
375 |
--------------------------------------------------------------------------------
/src/components/CountDown.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{tipText}}
6 | {{tipTextEnd}}
7 |
8 | {{msTime.day}}{{dayTxt}}
9 |
10 | {{msTime.hour}}{{hourTxt}}
11 | {{msTime.minutes}}{{minutesTxt}}
12 | {{msTime.seconds}}{{secondsTxt}}
13 |
14 |
{{endText}}
15 |
16 |
17 |
208 |
209 |
218 |
--------------------------------------------------------------------------------
/src/components/Dialog.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
13 |
18 |
19 |
20 |
21 |
22 |
63 |
64 |
89 |
--------------------------------------------------------------------------------
/src/components/HelloWorld.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ msg }}
4 |
5 | For a guide and recipes on how to configure / customize this project,
6 | check out the
7 | vue-cli documentation.
8 |
9 |
Installed CLI Plugins
10 |
14 |
Essential Links
15 |
22 |
Ecosystem
23 |
30 |
31 |
32 |
33 |
41 |
42 |
43 |
59 |
--------------------------------------------------------------------------------
/src/components/toast/Toast.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 成功
5 | {{message}}
6 |
7 |
8 |
9 |
10 |
21 |
22 |
51 |
--------------------------------------------------------------------------------
/src/components/toast/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Vue插件
3 | *
4 | * 原理:通过 vm.extend 构造器,将 .vue 文件做成一个"子类",实例化后,挂载在html文档上,然后添加到body即可。具体的操控即可通过'实例'对象来组成了。
5 | *
6 | * 参考链接:
7 | * https://cn.vuejs.org/v2/api/#Vue-extend
8 | * https://cn.vuejs.org/v2/api/#vm-mount
9 | */
10 |
11 | import ToastComponent from './Toast'
12 |
13 | const Toast = {};
14 |
15 | Toast.install = (Vue) => {
16 | // 第一步:使用基础 Vue 构造器,创建一个“子类”
17 | const ToastConstructor = Vue.extend(ToastComponent);
18 | // 第二步:创造一个组件实例
19 | const instance = new ToastConstructor();
20 | // 第三步:挂载实例。挂载的目标就是把模板渲染成最终的DOM。
21 | instance.$mount();
22 | // 第四步:在body添加组件
23 | document.body.appendChild(instance.$el);
24 |
25 | // 第五步:添加实例方法,以供全局调用
26 | Vue.prototype.$toast = (msg, type, duration = 2000) => {
27 | instance.message = msg;
28 | instance.type = type;
29 | instance.show = true;
30 | setTimeout(() => {
31 | instance.show = false;
32 | }, duration)
33 | }
34 | };
35 |
36 | export default Toast;
37 |
--------------------------------------------------------------------------------
/src/config/env.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 配置编译环境和线上环境之间的切换
3 | *
4 | * baseUrl: 域名地址
5 | * routerMode: 路由模式
6 | * credential: 跨域
7 | *
8 | */
9 |
10 | const dev = {
11 | routerMode: 'history', // hash
12 |
13 | /* beta */
14 | baseUrl: `${location.origin}`, // 自定义反向代理
15 | credential: true,
16 | };
17 |
18 | const prod = {
19 | ...dev,
20 | };
21 |
22 | export default process.env.NODE_ENV === 'development' ? dev : prod;
23 |
--------------------------------------------------------------------------------
/src/filters/index.js:
--------------------------------------------------------------------------------
1 | import dayjs from 'dayjs';
2 |
3 | /**
4 | * 格式化时间戳(秒|毫秒)
5 | * @param {timestamp} value
6 | */
7 | const timeFilter = (value) => {
8 | value = value.toString();
9 | if (value) {
10 | if (value.length === 13) {
11 | return dayjs(Number(value)).format("YYYY-MM-DD HH:mm:ss");
12 | }
13 | return dayjs.unix(Number(value)).format("YYYY-MM-DD HH:mm:ss");
14 | } else {
15 | return '-';
16 | }
17 | }
18 |
19 | /**
20 | * 手机号格式化
21 | * @param {String} phone
22 | */
23 | const formatPhone = (phone) => {
24 | phone = phone.toString();
25 | return phone.substr(0, 3) + '****' + phone.substr(7, 11);
26 | };
27 |
28 | /**
29 | * 4位一空格(格式化银行卡)
30 | * @param {String} val
31 | */
32 | const formatBank = (val) => {
33 | if (val) {
34 | return val.toString().replace(/\s/g, '').replace(/(.{4})/g, "$1 ");
35 | }
36 | };
37 |
38 | /**
39 | * 千分位格式化
40 | * @param {数字} val
41 | */
42 | const toThousands = (val) => {
43 | let num = (val || 0).toString(),
44 | result = '';
45 | while (num.length > 3) {
46 | result = ',' + num.slice(-3) + result;
47 | num = num.slice(0, num.length - 3);
48 | }
49 | if (num) {
50 | result = num + result;
51 | }
52 | return result;
53 | }
54 |
55 | /**
56 | * 格式化小数位
57 | * @param val 传入的值
58 | * @param pos 保留的小数位
59 | * @returns {*}
60 | */
61 | const formatFloat = (val, pos = 2) => {
62 | let f = parseFloat(val);
63 | if (isNaN(f)) {
64 | return false;
65 | }
66 | f = Math.round(val * Math.pow(10, pos)) / Math.pow(10, pos); // pow 幂
67 | let s = f.toString();
68 | let rs = s.indexOf('.');
69 | if (rs < 0) {
70 | rs = s.length;
71 | s += '.';
72 | }
73 | while (s.length <= rs + pos) {
74 | s += '0';
75 | }
76 | return s;
77 | }
78 |
79 | /**
80 | * 格式化时长
81 | * @param second
82 | * @returns {string}
83 | */
84 | const realFormatSecond = (second) => {
85 | let secondType = typeof second;
86 |
87 | if (secondType === 'number' || secondType === 'string') {
88 | second = parseInt(second);
89 |
90 | let hours = Math.floor(second / 3600);
91 | second = second - hours * 3600;
92 | let mimute = Math.floor(second / 60);
93 | second = second - mimute * 60;
94 |
95 | return hours + ':' + ('0' + mimute).slice(-2) + ':' + ('0' + second).slice(-2)
96 | } else {
97 | return '0:00:00'
98 | }
99 | };
100 |
101 | export default {
102 | timeFilter,
103 | formatPhone,
104 | formatBank,
105 | toThousands,
106 | formatFloat,
107 | realFormatSecond,
108 | }
109 |
--------------------------------------------------------------------------------
/src/layouts/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
16 |
17 |
22 |
--------------------------------------------------------------------------------
/src/locale/cn/home.js:
--------------------------------------------------------------------------------
1 | export default {
2 | name: '我的名字是 {name}',
3 | }
4 |
--------------------------------------------------------------------------------
/src/locale/cn/index.js:
--------------------------------------------------------------------------------
1 | import home from "./home";
2 |
3 | export default{
4 | home,
5 | }
6 |
--------------------------------------------------------------------------------
/src/locale/en/home.js:
--------------------------------------------------------------------------------
1 | export default {
2 | name: 'My name is {name}',
3 | }
4 |
--------------------------------------------------------------------------------
/src/locale/en/index.js:
--------------------------------------------------------------------------------
1 | import home from "./home";
2 |
3 | export default{
4 | home,
5 | }
6 |
--------------------------------------------------------------------------------
/src/locale/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import VueI18n from 'vue-i18n';
3 | import messages from './messages';
4 | import {
5 | getUrlParam,
6 | getStore
7 | } from '@/common/util';
8 |
9 | Vue.use(VueI18n);
10 | /**
11 | * 从localStorage中拿到用户的语言选择,如果没有,那默认中文。
12 | * @type {VueI18n}
13 | */
14 | const browserLang = navigator.language || navigator.browserLanguage;
15 |
16 | const i18n = new VueI18n({
17 | // locale: getStore("language") || browserLang || 'zh-CN',
18 | locale: 'zh-CN',
19 | messages
20 | });
21 |
22 | export default i18n;
23 |
--------------------------------------------------------------------------------
/src/locale/messages.js:
--------------------------------------------------------------------------------
1 | import cn from "./cn/index";
2 | import en from "./en/index";
3 |
4 | export default {
5 | 'en-US': en,
6 | 'zh-CN': cn,
7 | }
8 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 | import router from './router' // vue-router配置
4 | import store from './store' // vuex配置
5 | import i18n from './locale' // 国际化配置
6 | import filters from './filters' // vue过滤器
7 |
8 | // 自定义的toast(这里仅做示例,需自己根据需求变动)
9 | import Toast from './components/toast/index'
10 | Vue.use(Toast);
11 |
12 | Vue.config.productionTip = false
13 |
14 |
15 | // 如果是非线上环境,加载 VConsole(移动端适用)
16 | // if (process.env.NODE_ENV !== 'production') {
17 | // var VConsole = require('vconsole/dist/vconsole.min.js');
18 | // var vConsole = new VConsole();
19 | // }
20 |
21 | // 全局过滤器
22 | Object.keys(filters).forEach(filterName => {
23 | Vue.filter(filterName, filters[filterName])
24 | })
25 |
26 | /* eslint-disable no-new */
27 | new Vue({
28 | router,
29 | store,
30 | i18n,
31 | render: h => h(App)
32 | }).$mount('#app')
33 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 |
4 | // 正常加载
5 | // import Home from '../views/home'
6 | // import About from '../views/About'
7 |
8 | // 按需(懒)加载(vue实现)
9 | const Home = () => import( /* webpackChunkName: "home" */ '../views/home')
10 | const About = () => import( /* webpackChunkName: "about" */ '../views/about')
11 |
12 | // 按需(懒)加载(webpack动态导入)
13 | // require.ensure() 是 webpack 特有的,已经被 import() 取代。大家理解其作用即可,参考issues —— https://github.com/wangyupo/vue-vuex-router/issues/1
14 | // const Home = r => require.ensure([], () => r(require('../views/home')), 'home')
15 | // const About = r => require.ensure([], () => r(require('../views/About')), 'about')
16 |
17 | Vue.use(Router)
18 |
19 | let base = `${process.env.BASE_URL}` // 动态获取二级目录
20 |
21 | const router = new Router({
22 | // mode: 'history', // 路由有两种模式:history、hash,想要不带#号就选history,默认是hash模式
23 | base: base,
24 | routes: [{
25 | path: '/',
26 | name: 'home',
27 | component: Home
28 | },
29 | {
30 | path: '/about',
31 | name: 'about',
32 | component: About
33 | },
34 | {
35 | path: '*',
36 | redirect: '/'
37 | }
38 | ],
39 | scrollBehavior(to, from, savedPosition) {
40 | if (savedPosition) {
41 | return savedPosition
42 | } else {
43 | return {
44 | x: 0,
45 | y: 0
46 | }
47 | }
48 | }
49 | });
50 |
51 | router.beforeEach((to, from, next) => {
52 | // 做些什么,通常权限控制就在这里做。
53 |
54 | // 这里必须写next(),否则页面会阻止下一步操作。
55 | next();
56 | });
57 |
58 | export default router;
59 |
--------------------------------------------------------------------------------
/src/store/actions.js:
--------------------------------------------------------------------------------
1 | import * as types from '@/store/mutation-types.js';
2 | // import {userInfo} from "@/api/common";
3 |
4 | export default {
5 | changeUserInfo({commit}, info) {
6 | let userInfo = `this is ${info}`;
7 | commit(types.SET_USER_INFO, userInfo);
8 | },
9 | }
10 |
--------------------------------------------------------------------------------
/src/store/home/index.js:
--------------------------------------------------------------------------------
1 | import user from './user';
2 |
3 | export default {
4 | user,
5 | };
6 |
--------------------------------------------------------------------------------
/src/store/home/user.js:
--------------------------------------------------------------------------------
1 | import * as types from '@/store/mutation-types.js';
2 | import {rankGender} from "@/api/common";
3 |
4 | export default {
5 | namespaced: true,
6 | state: {
7 | userInfo: '123',
8 | rank: {},
9 | },
10 | getters: {
11 | getUserInfo: (state) => {
12 | const { userInfo } = state;
13 | return `${userInfo} 111`;
14 | },
15 | },
16 | actions: {
17 | changeUserInfo({commit}, info) {
18 | let userInfo = `this is ${info}`;
19 | commit(types.SET_USER_INFO, userInfo);
20 | },
21 | async getRank({commit}, opts) {
22 | try {
23 | const res = await rankGender(opts);
24 | if (res) {
25 | commit(types.SET_RANK, res);
26 | }
27 | } catch (e) {
28 | console.log(e)
29 | }
30 | },
31 | },
32 | mutations: {
33 | [types.SET_USER_INFO](state, userInfo) {
34 | state.userInfo = userInfo;
35 | },
36 | [types.SET_RANK](state, rank) {
37 | state.rank = rank;
38 | },
39 | },
40 | };
41 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Vuex from 'vuex';
3 | import mutations from './mutations'
4 | import actions from './actions'
5 |
6 | import home from './home';
7 |
8 | Vue.use(Vuex);
9 |
10 | let modules = {
11 | ...home,
12 | };
13 |
14 | export default new Vuex.Store({
15 | state: {
16 | groups: [1]
17 | },
18 | modules,
19 | actions, // 根级别的 action
20 | mutations, // 根级别的 mutations
21 | // 根级别的 getters
22 | getters: {
23 | getGroups(state) {
24 | return state.groups
25 | }
26 | }
27 | })
28 |
--------------------------------------------------------------------------------
/src/store/mutation-types.js:
--------------------------------------------------------------------------------
1 | // common type
2 | export const SET_USER_INFO = 'SET_USER_INFO';
3 | export const SET_RANK = 'SET_RANK';
4 |
--------------------------------------------------------------------------------
/src/store/mutations.js:
--------------------------------------------------------------------------------
1 | import * as types from '@/store/mutation-types.js';
2 |
3 | export default {
4 | [types.SET_USER_INFO](state, userInfo) {
5 | state.userInfo = userInfo;
6 | },
7 | }
8 |
--------------------------------------------------------------------------------
/src/style/_var.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * 全局变量
3 | * 命名规范 —— 遵守语义化命名:lg-大号、md-中号、base-基本号、sm-小号、mini-超小号
4 | */
5 |
6 | $font-weight-base: 400;
7 |
8 | $border-color-base: rgba(101, 135, 248, 0.36);
9 |
10 | $font-size-lg: 18px;
11 | $font-size-md: 16px;
12 | $font-size-base: 14px;
13 | $font-size-sm: 12px;
14 | $font-size-mini: 10px;
15 |
16 | $color-important: #222222;
17 | $color-common: #606060;
18 | $color-desc: #909090;
19 | $color-disabled: #BFBFBF;
20 | $color-error: #F04134;
21 | $color-warn: #F5A623;
22 | $color-lighting: #9013FE;
23 |
--------------------------------------------------------------------------------
/src/style/common.scss:
--------------------------------------------------------------------------------
1 | body {
2 | -webkit-font-smoothing: antialiased;
3 | }
4 |
5 | .margin {
6 | margin: 0 auto;
7 | }
8 |
9 | .left {
10 | float: left;
11 | }
12 |
13 | .right {
14 | float: right;
15 | }
16 |
17 | .hide {
18 | display: none;
19 | }
20 |
21 | .show {
22 | display: block;
23 | }
24 |
25 | .ellipsis {
26 | overflow: hidden;
27 | text-overflow: ellipsis;
28 | white-space: nowrap;
29 | }
30 |
31 | /*定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/
32 | ::-webkit-scrollbar {
33 | width: 0px;
34 | height: 0px;
35 | background-color: #F5F5F5;
36 | }
37 |
38 | /*定义滚动条轨道 内阴影+圆角*/
39 | ::-webkit-scrollbar-track {
40 | -webkit-box-shadow: inset 0 0 1px rgba(0, 0, 0, 0);
41 | border-radius: 10px;
42 | background-color: #F5F5F5;
43 | }
44 |
45 | /*定义滑块 内阴影+圆角*/
46 | ::-webkit-scrollbar-thumb {
47 | border-radius: 10px;
48 | -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .3);
49 | background-color: #555;
50 | }
51 |
--------------------------------------------------------------------------------
/src/style/mixin.scss:
--------------------------------------------------------------------------------
1 |
2 | //定位全屏
3 | @mixin allcover {
4 | position: absolute;
5 | top: 0;
6 | right: 0;
7 | }
8 |
9 | //transform上下左右居中
10 | @mixin ct {
11 | position: absolute;
12 | top: 50%;
13 | left: 50%;
14 | transform: translate(-50%, -50%);
15 | }
16 |
17 | //定位上下左右居中
18 | @mixin ctp($width, $height) {
19 | position: absolute;
20 | top: 50%;
21 | left: 50%;
22 | margin-top: -$height/2;
23 | margin-left: -$width/2;
24 | }
25 |
26 | //flex上下左右剧中
27 | @mixin fct() {
28 | display: flex;
29 | justify-content: center;
30 | align-items: center;
31 | }
32 |
33 | //定位上下居中
34 | @mixin tb {
35 | position: absolute;
36 | top: 50%;
37 | transform: translateY(-50%);
38 | }
39 |
40 | //flex上下剧中
41 | @mixin ftb {
42 | display: flex;
43 | align-items: center;
44 | }
45 |
46 | //定位左右居中
47 | @mixin lr {
48 | position: absolute;
49 | left: 50%;
50 | transform: translateX(-50%);
51 | }
52 |
53 | //flex左右剧中
54 | @mixin flr() {
55 | display: flex;
56 | justify-content: center;
57 | }
58 |
59 | //宽高
60 | @mixin wh($width, $height) {
61 | width: $width;
62 | height: $height;
63 | }
64 |
65 | //字体大小、行高、字体
66 | @mixin ft($size, $line-height) {
67 | font-size: $size;
68 | line-height: $line-height;
69 | }
70 |
71 | //字体大小,颜色
72 | @mixin sc($size, $color) {
73 | font-size: $size;
74 | color: $color;
75 | }
76 |
77 | //flex 布局和 子元素 对其方式
78 | @mixin fj($type: space-between) {
79 | display: flex;
80 | justify-content: $type;
81 | }
82 |
83 | //2倍图、3倍图,默认2倍图
84 | @mixin bg-image($url) {
85 | background-image: url("./images/"+ $url + "@2x.png");
86 |
87 | @media only screen and (-webkit-min-device-pixel-ratio: 2),
88 | only screen and (min-device-pixel-ratio: 2) {
89 | background-image: url("./images/"+ $url + "@3x.png");
90 | }
91 | }
92 |
93 | //多行超出省略号
94 | @mixin ellipsis($line: 2, $line-height: 1.2) {
95 | overflow: hidden;
96 | text-overflow: ellipsis;
97 | display: -webkit-box;
98 | -webkit-box-orient: vertical;
99 | -webkit-line-clamp: $line;
100 | line-height: $line-height;
101 | }
102 |
103 | @mixin modalbg($color-bg: rgba(0, 0, 0, 0.2)) {
104 | position: fixed;
105 | top: 0;
106 | left: 0;
107 | right: 0;
108 | bottom: 0;
109 | background-color: $color-bg;
110 | }
111 |
112 | @mixin flex($direction: column, $wrap: nowrap) {
113 | display: flex;
114 | flex-direction: $direction;
115 | flex-wrap: $wrap;
116 | }
117 |
118 | @mixin clear() {
119 | &:after {
120 | content: '.';
121 | clear: both;
122 | display: block;
123 | width: 0;
124 | height: 0;
125 | visibility: hidden;
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/style/normalize.scss:
--------------------------------------------------------------------------------
1 | /*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
2 |
3 | /* Document
4 | ========================================================================== */
5 |
6 | /**
7 | * 1. Correct the line height in all browsers.
8 | * 2. Prevent adjustments of font size after orientation changes in iOS.
9 | */
10 |
11 | html {
12 | line-height: 1.15; /* 1 */
13 | -webkit-text-size-adjust: 100%; /* 2 */
14 | }
15 |
16 | /* Sections
17 | ========================================================================== */
18 |
19 | /**
20 | * Remove the margin in all browsers.
21 | */
22 |
23 | body {
24 | margin: 0;
25 | }
26 |
27 | /**
28 | * Render the `main` element consistently in IE.
29 | */
30 |
31 | main {
32 | display: block;
33 | }
34 |
35 | /**
36 | * Correct the font size and margin on `h1` elements within `section` and
37 | * `article` contexts in Chrome, Firefox, and Safari.
38 | */
39 |
40 | h1 {
41 | font-size: 2em;
42 | margin: 0.67em 0;
43 | }
44 |
45 | /* Grouping content
46 | ========================================================================== */
47 |
48 | /**
49 | * 1. Add the correct box sizing in Firefox.
50 | * 2. Show the overflow in Edge and IE.
51 | */
52 |
53 | hr {
54 | box-sizing: content-box; /* 1 */
55 | height: 0; /* 1 */
56 | overflow: visible; /* 2 */
57 | }
58 |
59 | /**
60 | * 1. Correct the inheritance and scaling of font size in all browsers.
61 | * 2. Correct the odd `em` font sizing in all browsers.
62 | */
63 |
64 | pre {
65 | font-family: monospace, monospace; /* 1 */
66 | font-size: 1em; /* 2 */
67 | }
68 |
69 | /* Text-level semantics
70 | ========================================================================== */
71 |
72 | /**
73 | * Remove the gray background on active links in IE 10.
74 | */
75 |
76 | a {
77 | background-color: transparent;
78 | }
79 |
80 | /**
81 | * 1. Remove the bottom border in Chrome 57-
82 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
83 | */
84 |
85 | abbr[title] {
86 | border-bottom: none; /* 1 */
87 | text-decoration: underline; /* 2 */
88 | text-decoration: underline dotted; /* 2 */
89 | }
90 |
91 | /**
92 | * Add the correct font weight in Chrome, Edge, and Safari.
93 | */
94 |
95 | b,
96 | strong {
97 | font-weight: bolder;
98 | }
99 |
100 | /**
101 | * 1. Correct the inheritance and scaling of font size in all browsers.
102 | * 2. Correct the odd `em` font sizing in all browsers.
103 | */
104 |
105 | code,
106 | kbd,
107 | samp {
108 | font-family: monospace, monospace; /* 1 */
109 | font-size: 1em; /* 2 */
110 | }
111 |
112 | /**
113 | * Add the correct font size in all browsers.
114 | */
115 |
116 | small {
117 | font-size: 80%;
118 | }
119 |
120 | /**
121 | * Prevent `sub` and `sup` elements from affecting the line height in
122 | * all browsers.
123 | */
124 |
125 | sub,
126 | sup {
127 | font-size: 75%;
128 | line-height: 0;
129 | position: relative;
130 | vertical-align: baseline;
131 | }
132 |
133 | sub {
134 | bottom: -0.25em;
135 | }
136 |
137 | sup {
138 | top: -0.5em;
139 | }
140 |
141 | /* Embedded content
142 | ========================================================================== */
143 |
144 | /**
145 | * Remove the border on images inside links in IE 10.
146 | */
147 |
148 | img {
149 | border-style: none;
150 | }
151 |
152 | /* Forms
153 | ========================================================================== */
154 |
155 | /**
156 | * 1. Change the font styles in all browsers.
157 | * 2. Remove the margin in Firefox and Safari.
158 | */
159 |
160 | button,
161 | input,
162 | optgroup,
163 | select,
164 | textarea {
165 | font-family: inherit; /* 1 */
166 | font-size: 100%; /* 1 */
167 | line-height: 1.15; /* 1 */
168 | margin: 0; /* 2 */
169 | }
170 |
171 | /**
172 | * Show the overflow in IE.
173 | * 1. Show the overflow in Edge.
174 | */
175 |
176 | button,
177 | input { /* 1 */
178 | overflow: visible;
179 | }
180 |
181 | /**
182 | * Remove the inheritance of text transform in Edge, Firefox, and IE.
183 | * 1. Remove the inheritance of text transform in Firefox.
184 | */
185 |
186 | button,
187 | select { /* 1 */
188 | text-transform: none;
189 | }
190 |
191 | /**
192 | * Correct the inability to style clickable types in iOS and Safari.
193 | */
194 |
195 | button,
196 | [type="button"],
197 | [type="reset"],
198 | [type="submit"] {
199 | -webkit-appearance: button;
200 | }
201 |
202 | /**
203 | * Remove the inner border and padding in Firefox.
204 | */
205 |
206 | button::-moz-focus-inner,
207 | [type="button"]::-moz-focus-inner,
208 | [type="reset"]::-moz-focus-inner,
209 | [type="submit"]::-moz-focus-inner {
210 | border-style: none;
211 | padding: 0;
212 | }
213 |
214 | /**
215 | * Restore the focus styles unset by the previous rule.
216 | */
217 |
218 | button:-moz-focusring,
219 | [type="button"]:-moz-focusring,
220 | [type="reset"]:-moz-focusring,
221 | [type="submit"]:-moz-focusring {
222 | outline: 1px dotted ButtonText;
223 | }
224 |
225 | /**
226 | * Correct the padding in Firefox.
227 | */
228 |
229 | fieldset {
230 | padding: 0.35em 0.75em 0.625em;
231 | }
232 |
233 | /**
234 | * 1. Correct the text wrapping in Edge and IE.
235 | * 2. Correct the color inheritance from `fieldset` elements in IE.
236 | * 3. Remove the padding so developers are not caught out when they zero out
237 | * `fieldset` elements in all browsers.
238 | */
239 |
240 | legend {
241 | box-sizing: border-box; /* 1 */
242 | color: inherit; /* 2 */
243 | display: table; /* 1 */
244 | max-width: 100%; /* 1 */
245 | padding: 0; /* 3 */
246 | white-space: normal; /* 1 */
247 | }
248 |
249 | /**
250 | * Add the correct vertical alignment in Chrome, Firefox, and Opera.
251 | */
252 |
253 | progress {
254 | vertical-align: baseline;
255 | }
256 |
257 | /**
258 | * Remove the default vertical scrollbar in IE 10+.
259 | */
260 |
261 | textarea {
262 | overflow: auto;
263 | }
264 |
265 | /**
266 | * 1. Add the correct box sizing in IE 10.
267 | * 2. Remove the padding in IE 10.
268 | */
269 |
270 | [type="checkbox"],
271 | [type="radio"] {
272 | box-sizing: border-box; /* 1 */
273 | padding: 0; /* 2 */
274 | }
275 |
276 | /**
277 | * Correct the cursor style of increment and decrement buttons in Chrome.
278 | */
279 |
280 | [type="number"]::-webkit-inner-spin-button,
281 | [type="number"]::-webkit-outer-spin-button {
282 | height: auto;
283 | }
284 |
285 | /**
286 | * 1. Correct the odd appearance in Chrome and Safari.
287 | * 2. Correct the outline style in Safari.
288 | */
289 |
290 | [type="search"] {
291 | -webkit-appearance: textfield; /* 1 */
292 | outline-offset: -2px; /* 2 */
293 | }
294 |
295 | /**
296 | * Remove the inner padding in Chrome and Safari on macOS.
297 | */
298 |
299 | [type="search"]::-webkit-search-decoration {
300 | -webkit-appearance: none;
301 | }
302 |
303 | /**
304 | * 1. Correct the inability to style clickable types in iOS and Safari.
305 | * 2. Change font properties to `inherit` in Safari.
306 | */
307 |
308 | ::-webkit-file-upload-button {
309 | -webkit-appearance: button; /* 1 */
310 | font: inherit; /* 2 */
311 | }
312 |
313 | /* Interactive
314 | ========================================================================== */
315 |
316 | /*
317 | * Add the correct display in Edge, IE 10+, and Firefox.
318 | */
319 |
320 | details {
321 | display: block;
322 | }
323 |
324 | /*
325 | * Add the correct display in all browsers.
326 | */
327 |
328 | summary {
329 | display: list-item;
330 | }
331 |
332 | /* Misc
333 | ========================================================================== */
334 |
335 | /**
336 | * Add the correct display in IE 10+.
337 | */
338 |
339 | template {
340 | display: none;
341 | }
342 |
343 | /**
344 | * Add the correct display in IE 10.
345 | */
346 |
347 | [hidden] {
348 | display: none;
349 | }
350 |
--------------------------------------------------------------------------------
/src/style/reset.scss:
--------------------------------------------------------------------------------
1 | /*! minireset.css v0.0.4 | MIT License | github.com/jgthms/minireset.css */
2 | html,
3 | body,
4 | p,
5 | ol,
6 | ul,
7 | li,
8 | dl,
9 | dt,
10 | dd,
11 | blockquote,
12 | figure,
13 | fieldset,
14 | legend,
15 | textarea,
16 | pre,
17 | iframe,
18 | hr,
19 | h1,
20 | h2,
21 | h3,
22 | h4,
23 | h5,
24 | h6 {
25 | margin: 0;
26 | padding: 0;
27 | }
28 |
29 | h1,
30 | h2,
31 | h3,
32 | h4,
33 | h5,
34 | h6 {
35 | font-size: 100%;
36 | font-weight: normal;
37 | }
38 |
39 | ul {
40 | list-style: none;
41 | }
42 |
43 | button,
44 | input,
45 | select,
46 | textarea {
47 | margin: 0;
48 | }
49 |
50 | html {
51 | box-sizing: border-box;
52 | }
53 |
54 | *, *:before, *:after {
55 | box-sizing: inherit;
56 | }
57 |
58 | img,
59 | embed,
60 | iframe,
61 | object,
62 | video {
63 | height: auto;
64 | max-width: 100%;
65 | }
66 |
67 | audio {
68 | max-width: 100%;
69 | }
70 |
71 | iframe {
72 | border: 0;
73 | }
74 |
75 | table {
76 | border-collapse: collapse;
77 | border-spacing: 0;
78 | }
79 |
80 | td,
81 | th {
82 | padding: 0;
83 | text-align: left;
84 | }
85 |
--------------------------------------------------------------------------------
/src/views/about.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
1、防抖
4 |
常用在input搜索的时候,控制请求频率。实现方式就是监听键盘输入事件(@keyup),然后每次事件都绑定一个setTimeout,
5 | 延迟250ms执行,每次执行都取消(clearTimeout)上一次定时器。可以打开调试工具,Console栏里看下效果。
6 |
7 |
8 |
9 |
10 |
28 |
--------------------------------------------------------------------------------
/src/views/home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |

6 |
7 |
8 |
9 |
10 |
11 |
12 | vuex示例:
13 | {{getUserInfo}}
14 |
15 |
16 |
17 |
18 | iconfont:
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | i18n国际化:
28 | {{$t('home.name', {name: 'Jelly'})}}
29 |
30 |
31 |
32 |
33 | vue过滤器:
34 |
35 | 时间戳:{{1544179366 | timeFilter}}
36 |
37 | 手机格式化:{{15311959057 | formatPhone}}
38 |
39 | 银行卡格式化:{{123123123123132 | formatBank}}
40 |
41 | 千分位分隔符:{{5000039 | toThousands}}
42 |
43 |
44 |
45 |
58 |
59 |
60 |
61 | 反向代理:
62 | {{rank.ok?'成功':'失败'}}
63 |
64 |
65 |
66 |
67 |
68 |
补间动画:{{num | formatFloat}}
69 |
70 | 浮点数运算:
71 | 0.1+0.2={{number | formatFloat(2)}}
72 |
73 |
74 |
75 |
76 | 发送验证码:
77 |
83 |
84 |
85 |
86 |
Toast插件
87 |
88 |
89 |
92 |
93 |
94 |
95 |
210 |
211 |
229 |
--------------------------------------------------------------------------------
/src/views/template.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
14 |
15 |
19 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | // vue.config.js
2 | const path = require('path');
3 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
4 |
5 | function resolve(dir) {
6 | return path.join(__dirname, dir)
7 | }
8 |
9 | module.exports = {
10 | // 公共路径
11 | publicPath: './',
12 |
13 | // 不同的环境打不同包名
14 | outputDir: process.env.NODE_ENV === "development" ? 'devdist' : 'dist',
15 |
16 | // 使用运行时编译器的 Vue 构建版本
17 | runtimeCompiler: true,
18 |
19 | // 开启生产环境SourceMap,设为false打包时不生成.map文件
20 | productionSourceMap: false,
21 |
22 | // 关闭ESLint,如果你需要使用ESLint,把lintOnSave设为true即可
23 | lintOnSave: false,
24 |
25 | devServer: {
26 | open: false, // 是否自动打开浏览器页面
27 | host: '0.0.0.0', // 指定使用一个 host,默认是 localhost
28 | port: 8080, // 端口地址
29 | https: false, // 使用https提供服务
30 | // 这里写你调用接口的基础路径,来解决跨域,如果设置了代理,那你本地开发环境的axios的baseUrl要写为 '' ,即空字符串
31 | proxy: 'http://api.zhuishushenqi.com'
32 | },
33 |
34 | chainWebpack: (config) => {
35 | // 设置一些常用alias
36 | config.resolve.alias
37 | .set('@', resolve('src'))
38 | .set('@assets', resolve('src/assets'))
39 | .set('@components', resolve('src/components'))
40 |
41 | // 移除 prefetch 插件,减少首屏加载
42 | config.plugins.delete('prefetch')
43 | },
44 |
45 | configureWebpack: config => {
46 | // 生产环境
47 | if (process.env.NODE_ENV === 'production') {
48 | // 生产环境去除console
49 | config.optimization.minimizer[0].options.terserOptions.compress.drop_console = true
50 |
51 | // 生产环境打包分析体积
52 | return {
53 | plugins: [
54 | new BundleAnalyzerPlugin()
55 | ]
56 | }
57 | }
58 | },
59 | css: {
60 | loaderOptions: {
61 | // 全局使用的一些scss样式
62 | sass: {
63 | data: `
64 | @import "@/style/mixin.scss";
65 | @import "@/style/_var.scss";
66 | `
67 | }
68 | }
69 | },
70 | };
71 |
--------------------------------------------------------------------------------
/使用说明.md:
--------------------------------------------------------------------------------
1 | 所有内容都在`src`目录下
2 |
3 | 项目的**接口**在`api`目录下统一管理,用的时候引入该目录下对应的接口文本即可
4 |
5 | ```js
6 | import commonAPI from '@/api/common'
7 |
8 | commonAPI.xxx({data}).then(res => console.log(res))
9 | ```
10 |
11 | `assets`目录存放图片、iconfont等资源文件
12 |
13 | `common`目录存放所有公共的方法,比如我们用axios封装的request.js和工具类util.js
14 |
15 | `components`目录存放项目的组件,建议只存放无关业务的组件,或是从业务中抽离的公共组件
16 |
17 | `config`目录存放各类配置文件
18 |
19 | `filters`目录存放自定义的过滤器,过滤器已经在`main.js`中全局引入,一般情况下只需要在`filters/index.js`中新增过滤器即可
20 |
21 | `layouts`目录存放页面的最外层布局组件,是从views中单独抽离出来的,方便直接改变、管理最外层布局
22 |
23 | `locale`目录存放多语言文件,使用规则如下:
24 |
25 | > 配置对应语言的文件夹,然后在messages.js中引入并导出即可。
26 |
27 | `router`目录存放并控制项目的所有路由
28 |
29 | `store`目录是`VUEX`的存放目录,负责统一管理VUEX
30 |
31 | `style`目录存放所有的样式文件,目前`mixin.scss`和`_var.scss`是全局引入的,如果需要全局引入更多的样式文件,可以在`vue.config.js`中的`css->loaderOptions->sass`下依法添加
32 |
33 | `views`目录存放所有的页面组件,可以依据项目自己划分层级
--------------------------------------------------------------------------------