about
Showing
12 changed files
with
334 additions
and
257 deletions
... | @@ -2,7 +2,7 @@ | ... | @@ -2,7 +2,7 @@ |
2 | 2 | ||
3 | 基于 vue-cli3.0+webpack 4+vant ui + sass+ rem 适配方案+axios 封装,构建手机端模板脚手架 | 3 | 基于 vue-cli3.0+webpack 4+vant ui + sass+ rem 适配方案+axios 封装,构建手机端模板脚手架 |
4 | 4 | ||
5 | [关于项目介绍](https://juejin.im/post/5cfefc73f265da1bba58f9f7) | 5 | 掘金: [vue-cli4 vant rem 移动端框架方案](https://juejin.im/post/5cfefc73f265da1bba58f9f7) |
6 | 6 | ||
7 | [demo](https://solui.cn/vue-h5-template/#/)建议手机端查看 | 7 | [demo](https://solui.cn/vue-h5-template/#/)建议手机端查看 |
8 | 8 | ||
... | @@ -13,6 +13,19 @@ | ... | @@ -13,6 +13,19 @@ |
13 | 13 | ||
14 | 本示例 Node.js 12.14.1 | 14 | 本示例 Node.js 12.14.1 |
15 | 15 | ||
16 | ### 启动项目 | ||
17 | |||
18 | ```bash | ||
19 | |||
20 | git clone https://github.com/sunniejs/vue-h5-template.git | ||
21 | |||
22 | cd vue-h5-template | ||
23 | |||
24 | npm install | ||
25 | |||
26 | npm run serve | ||
27 | ``` | ||
28 | |||
16 | <span id="top">目录</span> | 29 | <span id="top">目录</span> |
17 | 30 | ||
18 | - [√ Vue-cli4](https://cli.vuejs.org/zh/guide/) | 31 | - [√ Vue-cli4](https://cli.vuejs.org/zh/guide/) |
... | @@ -30,10 +43,9 @@ | ... | @@ -30,10 +43,9 @@ |
30 | - [√ 配置 打包分析](#bundle) | 43 | - [√ 配置 打包分析](#bundle) |
31 | - [√ 配置 externals 引入 cdn 资源 ](#externals) | 44 | - [√ 配置 externals 引入 cdn 资源 ](#externals) |
32 | - [√ 去掉 console.log ](#console) | 45 | - [√ 去掉 console.log ](#console) |
33 | - [√ splitChunks ](#console) | 46 | - [√ splitChunks 单独打包第三方模块](#chunks) |
34 | - [√ 添加 IE 兼容 ](#ie) | 47 | - [√ 添加 IE 兼容 ](#ie) |
35 | 48 | ||
36 | |||
37 | * Vuex | 49 | * Vuex |
38 | * Axios 封装 | 50 | * Axios 封装 |
39 | * 生产环境 cdn 优化首屏加速 | 51 | * 生产环境 cdn 优化首屏加速 |
... | @@ -466,7 +478,7 @@ module.exports = { | ... | @@ -466,7 +478,7 @@ module.exports = { |
466 | outputDir: 'dist', // 生产环境构建文件的目录 | 478 | outputDir: 'dist', // 生产环境构建文件的目录 |
467 | assetsDir: 'static', // outputDir的静态资源(js、css、img、fonts)目录 | 479 | assetsDir: 'static', // outputDir的静态资源(js、css、img、fonts)目录 |
468 | lintOnSave: false, | 480 | lintOnSave: false, |
469 | productionSourceMap: !IS_PROD, // 生产环境的 source map | 481 | productionSourceMap: false, // 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。 |
470 | devServer: { | 482 | devServer: { |
471 | port: 9020, // 端口号 | 483 | port: 9020, // 端口号 |
472 | open: false, // 启动后打开浏览器 | 484 | open: false, // 启动后打开浏览器 |
... | @@ -524,9 +536,9 @@ export function getUserInfo(params) { | ... | @@ -524,9 +536,9 @@ export function getUserInfo(params) { |
524 | ### <span id="alias">✅ 配置 alias 别名 </span> | 536 | ### <span id="alias">✅ 配置 alias 别名 </span> |
525 | 537 | ||
526 | ```javascript | 538 | ```javascript |
527 | const path = require("path"); | 539 | const path = require('path') |
528 | const resolve = dir => path.join(__dirname, dir); | 540 | const resolve = dir => path.join(__dirname, dir) |
529 | const IS_PROD = ["production", "prod"].includes(process.env.NODE_ENV); | 541 | const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV) |
530 | 542 | ||
531 | module.exports = { | 543 | module.exports = { |
532 | chainWebpack: config => { | 544 | chainWebpack: config => { |
... | @@ -538,40 +550,52 @@ module.exports = { | ... | @@ -538,40 +550,52 @@ module.exports = { |
538 | .set('views', resolve('src/views')) | 550 | .set('views', resolve('src/views')) |
539 | .set('components', resolve('src/components')) | 551 | .set('components', resolve('src/components')) |
540 | } | 552 | } |
541 | }; | 553 | } |
542 | ``` | 554 | ``` |
555 | |||
543 | [▲ 回顶部](#top) | 556 | [▲ 回顶部](#top) |
544 | 557 | ||
545 | ### <span id="bundle">✅ 配置 打包分析 </span> | 558 | ### <span id="bundle">✅ 配置 打包分析 </span> |
546 | 559 | ||
547 | ```javascript | 560 | ```javascript |
548 | const BundleAnalyzerPlugin = require("webpack-bundle-analyzer") | 561 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin |
549 | .BundleAnalyzerPlugin; | ||
550 | 562 | ||
551 | module.exports = { | 563 | module.exports = { |
552 | chainWebpack: config => { | 564 | chainWebpack: config => { |
553 | // 打包分析 | 565 | // 打包分析 |
554 | if (IS_PROD) { | 566 | if (IS_PROD) { |
555 | config.plugin("webpack-report").use(BundleAnalyzerPlugin, [ | 567 | config.plugin('webpack-report').use(BundleAnalyzerPlugin, [ |
556 | { | 568 | { |
557 | analyzerMode: "static" | 569 | analyzerMode: 'static' |
558 | } | 570 | } |
559 | ]); | 571 | ]) |
560 | } | 572 | } |
561 | } | 573 | } |
562 | }; | 574 | } |
563 | ``` | 575 | ``` |
576 | |||
564 | ```bash | 577 | ```bash |
565 | npm run build | 578 | npm run build |
566 | ``` | 579 | ``` |
580 | |||
567 | [▲ 回顶部](#top) | 581 | [▲ 回顶部](#top) |
568 | 582 | ||
569 | ### <span id="proxy">✅ 配置 externals 引入 cdn 资源 </span> | 583 | ### <span id="proxy">✅ 配置 externals 引入 cdn 资源 </span> |
570 | 584 | ||
585 | 这个版本 CDN 不再引入,我测试了一下使用引入 CDN 和不使用,不使用会比使用时间少。网上不少文章测试 CDN 速度块,这个开发者可 | ||
586 | 以实际测试一下。 | ||
587 | |||
588 | 另外项目中使用的是公共 CDN 不稳定,域名解析也是需要时间的(如果你要使用请尽量使用同一个域名) | ||
589 | |||
590 | 因为页面每次遇到`<script>`标签都会停下来解析执行,所以应该尽可能减少`<script>`标签的数量 `HTTP`请求存在一定的开销,100K | ||
591 | 的文件比 5 个 20K 的文件下载的更快,所以较少脚本数量也是很有必要的 | ||
592 | |||
593 | 暂时还没有研究放到自己的 cdn 服务器上。 | ||
594 | |||
571 | ```javascript | 595 | ```javascript |
572 | const defaultSettings = require('./src/config/index.js') | 596 | const defaultSettings = require('./src/config/index.js') |
573 | const name = defaultSettings.title || 'vue mobile template' | 597 | const name = defaultSettings.title || 'vue mobile template' |
574 | const IS_PROD = ["production", "prod"].includes(process.env.NODE_ENV); | 598 | const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV) |
575 | 599 | ||
576 | // externals | 600 | // externals |
577 | const externals = { | 601 | const externals = { |
... | @@ -581,45 +605,57 @@ const externals = { | ... | @@ -581,45 +605,57 @@ const externals = { |
581 | vant: 'vant', | 605 | vant: 'vant', |
582 | axios: 'axios' | 606 | axios: 'axios' |
583 | } | 607 | } |
584 | // cdn | 608 | // CDN外链,会插入到index.html中 |
585 | const cdn = { | 609 | const cdn = { |
586 | css: ['https://cdn.jsdelivr.net/npm/vant@beta/lib/index.css'], | 610 | // 开发环境 |
587 | js: [ | 611 | dev: { |
588 | 'https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js', | 612 | css: [], |
589 | 'https://cdnjs.cloudflare.com/ajax/libs/vue-router/3.0.6/vue-router.min.js', | 613 | js: [] |
590 | 'https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js', | 614 | }, |
591 | 'https://cdnjs.cloudflare.com/ajax/libs/vuex/3.1.1/vuex.min.js', | 615 | // 生产环境 |
592 | 'https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/crypto-js.min.js', | 616 | build: { |
593 | 'https://cdn.jsdelivr.net/npm/vant@beta/lib/vant.min.js' | 617 | css: ['https://cdn.jsdelivr.net/npm/vant@2.4.7/lib/index.css'], |
594 | ] | 618 | js: [ |
619 | 'https://cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.min.js', | ||
620 | 'https://cdn.jsdelivr.net/npm/vue-router@3.1.5/dist/vue-router.min.js', | ||
621 | 'https://cdn.jsdelivr.net/npm/axios@0.19.2/dist/axios.min.js', | ||
622 | 'https://cdn.jsdelivr.net/npm/vuex@3.1.2/dist/vuex.min.js', | ||
623 | 'https://cdn.jsdelivr.net/npm/vant@2.4.7/lib/index.min.js' | ||
624 | ] | ||
625 | } | ||
595 | } | 626 | } |
596 | module.exports = { | 627 | module.exports = { |
597 | configureWebpack: config => { | 628 | configureWebpack: config => { |
598 | config.name = name | 629 | config.name = name |
599 | // 为生产环境修改配置... | 630 | // 为生产环境修改配置... |
600 | if (IS_PROD) { | 631 | if (IS_PROD) { |
601 | // externals | 632 | // externals |
602 | config.externals = externals | 633 | config.externals = externals |
603 | }; | 634 | } |
604 | }, | 635 | }, |
605 | chainWebpack: config => { | 636 | chainWebpack: config => { |
606 | // 添加CDN参数到htmlWebpackPlugin配置中, 详见public/index.html 修改 | 637 | /** |
638 | * 添加CDN参数到htmlWebpackPlugin配置中 | ||
639 | */ | ||
607 | config.plugin('html').tap(args => { | 640 | config.plugin('html').tap(args => { |
608 | if (IS_PROD) { | 641 | if (IS_PROD) { |
609 | // html中添加cdn | 642 | args[0].cdn = cdn.build |
610 | args[0].cdn = cdn | 643 | } else { |
611 | } | 644 | args[0].cdn = cdn.dev |
645 | } | ||
612 | return args | 646 | return args |
613 | }) | 647 | }) |
614 | } | 648 | } |
615 | }; | 649 | } |
616 | ``` | 650 | ``` |
617 | 在 public/index.html 中添加 | 651 | |
652 | 在 public/index.html 中添加 | ||
618 | 653 | ||
619 | ```javascript | 654 | ```javascript |
620 | <!-- 使用CDN的CSS文件 --> | 655 | <!-- 使用CDN的CSS文件 --> |
621 | <% for (var i in | 656 | <% for (var i in |
622 | htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.css) { %> | 657 | htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.css) { %> |
658 | <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="preload" as="style" /> | ||
623 | <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet" /> | 659 | <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet" /> |
624 | <% } %> | 660 | <% } %> |
625 | <!-- 使用CDN加速的JS文件,配置在vue.config.js下 --> | 661 | <!-- 使用CDN加速的JS文件,配置在vue.config.js下 --> |
... | @@ -633,9 +669,12 @@ module.exports = { | ... | @@ -633,9 +669,12 @@ module.exports = { |
633 | 669 | ||
634 | ### <span id="console">✅ 去掉 console.log </span> | 670 | ### <span id="console">✅ 去掉 console.log </span> |
635 | 671 | ||
672 | 保留了测试环境和本地环境的 `console.log` | ||
673 | |||
636 | ```bash | 674 | ```bash |
637 | npm i -D babel-plugin-transform-remove-console | 675 | npm i -D babel-plugin-transform-remove-console |
638 | ``` | 676 | ``` |
677 | |||
639 | 在 babel.config.js 中配置 | 678 | 在 babel.config.js 中配置 |
640 | 679 | ||
641 | ```javascript | 680 | ```javascript |
... | @@ -661,19 +700,82 @@ module.exports = { | ... | @@ -661,19 +700,82 @@ module.exports = { |
661 | presets: [['@vue/cli-plugin-babel/preset', {useBuiltIns: 'entry'}]], | 700 | presets: [['@vue/cli-plugin-babel/preset', {useBuiltIns: 'entry'}]], |
662 | plugins | 701 | plugins |
663 | } | 702 | } |
703 | ``` | ||
704 | |||
705 | [▲ 回顶部](#top) | ||
706 | |||
707 | ### <span id="chunks">✅ splitChunks 单独打包第三方模块</span> | ||
664 | 708 | ||
709 | ```javascript | ||
710 | module.exports = { | ||
711 | chainWebpack: config => { | ||
712 | config.when(IS_PROD, config => { | ||
713 | config | ||
714 | .plugin('ScriptExtHtmlWebpackPlugin') | ||
715 | .after('html') | ||
716 | .use('script-ext-html-webpack-plugin', [ | ||
717 | { | ||
718 | // 将 runtime 作为内联引入不单独存在 | ||
719 | inline: /runtime\..*\.js$/ | ||
720 | } | ||
721 | ]) | ||
722 | .end() | ||
723 | config.optimization.splitChunks({ | ||
724 | chunks: 'all', | ||
725 | cacheGroups: { | ||
726 | // cacheGroups 下可以可以配置多个组,每个组根据test设置条件,符合test条件的模块 | ||
727 | commons: { | ||
728 | name: 'chunk-commons', | ||
729 | test: resolve('src/components'), | ||
730 | minChunks: 3, // 被至少用三次以上打包分离 | ||
731 | priority: 5, // 优先级 | ||
732 | reuseExistingChunk: true // 表示是否使用已有的 chunk,如果为 true 则表示如果当前的 chunk 包含的模块已经被抽取出去了,那么将不会重新生成新的。 | ||
733 | }, | ||
734 | node_vendors: { | ||
735 | name: 'chunk-libs', | ||
736 | chunks: 'initial', // 只打包初始时依赖的第三方 | ||
737 | test: /[\\/]node_modules[\\/]/, | ||
738 | priority: 10 | ||
739 | }, | ||
740 | vantUI: { | ||
741 | name: 'chunk-vantUI', // 单独将 vantUI 拆包 | ||
742 | priority: 20, // 数字大权重到,满足多个 cacheGroups 的条件时候分到权重高的 | ||
743 | test: /[\\/]node_modules[\\/]_?vant(.*)/ | ||
744 | } | ||
745 | } | ||
746 | }) | ||
747 | config.optimization.runtimeChunk('single') | ||
748 | }) | ||
749 | } | ||
750 | } | ||
665 | ``` | 751 | ``` |
666 | 752 | ||
667 | [▲ 回顶部](#top) | 753 | [▲ 回顶部](#top) |
668 | 754 | ||
669 | ### <span id="ie">✅ 添加 IE 兼容 </span> | 755 | ### <span id="ie">✅ 添加 IE 兼容 </span> |
670 | 756 | ||
671 | [▲ 回顶部](#top) | 757 | ```bash |
758 | npm i -S @babel/polyfill | ||
759 | ``` | ||
672 | 760 | ||
673 | ### <span id="console">✅ 去掉 console.log </span> | 761 | 在 `main.js` 中添加 |
674 | [▲ 回顶部](#top) | 762 | |
763 | ```javascript | ||
764 | import '@babel/polyfill' | ||
765 | ``` | ||
766 | |||
767 | 配置 `babel.config.js` | ||
768 | |||
769 | ```javascript | ||
770 | const plugins = [] | ||
675 | 771 | ||
772 | module.exports = { | ||
773 | presets: [['@vue/cli-plugin-babel/preset', {useBuiltIns: 'entry'}]], | ||
774 | plugins | ||
775 | } | ||
776 | ``` | ||
676 | 777 | ||
778 | [▲ 回顶部](#top) | ||
677 | 779 | ||
678 | #### 总结 | 780 | #### 总结 |
679 | 781 | ... | ... |
... | @@ -5,11 +5,11 @@ | ... | @@ -5,11 +5,11 @@ |
5 | <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> | 5 | <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> |
6 | <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> | 6 | <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> |
7 | <link rel="icon" href="<%= BASE_URL %>favicon.ico"> | 7 | <link rel="icon" href="<%= BASE_URL %>favicon.ico"> |
8 | <% for (var i in | 8 | <!-- <% for (var i in |
9 | htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.css) { %> | 9 | htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.css) { %> |
10 | <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="preload" as="style" /> | 10 | <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="preload" as="style" /> |
11 | <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet" /> | 11 | <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet" /> |
12 | <% } %> | 12 | <% } %> --> |
13 | <title><%= webpackConfig.name %></title> | 13 | <title><%= webpackConfig.name %></title> |
14 | </head> | 14 | </head> |
15 | <body> | 15 | <body> |
... | @@ -18,10 +18,10 @@ | ... | @@ -18,10 +18,10 @@ |
18 | </noscript> | 18 | </noscript> |
19 | <div id="app"></div> | 19 | <div id="app"></div> |
20 | <!-- 使用CDN加速的JS文件,配置在vue.config.js下 --> | 20 | <!-- 使用CDN加速的JS文件,配置在vue.config.js下 --> |
21 | <% for (var i in | 21 | <!-- <% for (var i in |
22 | htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.js) { %> | 22 | htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.js) { %> |
23 | <script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script> | 23 | <script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script> |
24 | <% } %> | 24 | <% } %> --> |
25 | <!-- built files will be auto injected --> | 25 | <!-- built files will be auto injected --> |
26 | </body> | 26 | </body> |
27 | </html> | 27 | </html> | ... | ... |
... | @@ -4,10 +4,19 @@ | ... | @@ -4,10 +4,19 @@ |
4 | <router-view v-if="$route.meta.keepAlive"></router-view> | 4 | <router-view v-if="$route.meta.keepAlive"></router-view> |
5 | </keep-alive> | 5 | </keep-alive> |
6 | <router-view v-if="!$route.meta.keepAlive"></router-view> | 6 | <router-view v-if="!$route.meta.keepAlive"></router-view> |
7 | <!-- tabbar --> | ||
8 | <TabBar></TabBar> | ||
7 | </div> | 9 | </div> |
8 | </template> | 10 | </template> |
9 | <script> | 11 | <script> |
12 | import TabBar from '@/components/TabBar' | ||
13 | |||
10 | export default { | 14 | export default { |
11 | name: 'App' | 15 | name: 'App', |
16 | components: { | ||
17 | TabBar | ||
18 | }, | ||
12 | } | 19 | } |
13 | </script> | 20 | </script> |
21 | <style lang="scss"> | ||
22 | </style> | ||
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
1 | @import './variables.scss'; | 1 | @import './variables.scss'; |
2 | @import './mixin.scss'; | 2 | @import './mixin.scss'; |
3 | /* http://meyerweb.com/eric/tools/css/reset/ | ||
4 | v2.0 | 20110126 | ||
5 | License: none (public domain) | ||
6 | */ | ||
7 | |||
8 | html, body, div, span, applet, object, iframe, | ||
9 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, | ||
10 | a, abbr, acronym, address, big, cite, code, | ||
11 | del, dfn, em, img, ins, kbd, q, s, samp, | ||
12 | small, strike, strong, sub, sup, tt, var, | ||
13 | b, u, i, center, | ||
14 | dl, dt, dd, ol, ul, li, | ||
15 | fieldset, form, label, legend, | ||
16 | table, caption, tbody, tfoot, thead, tr, th, td, | ||
17 | article, aside, canvas, details, embed, | ||
18 | figure, figcaption, footer, header, hgroup, | ||
19 | menu, nav, output, ruby, section, summary, | ||
20 | time, mark, audio, video { | ||
21 | margin: 0; | ||
22 | padding: 0; | ||
23 | border: 0; | ||
24 | font-size: 100%; | ||
25 | font: inherit; | ||
26 | vertical-align: baseline; | ||
27 | } | ||
28 | /* HTML5 display-role reset for older browsers */ | ||
29 | article, aside, details, figcaption, figure, | ||
30 | footer, header, hgroup, menu, nav, section { | ||
31 | display: block; | ||
32 | } | ||
33 | body { | ||
34 | line-height: 1; | ||
35 | } | ||
36 | ol, ul { | ||
37 | list-style: none; | ||
38 | } | ||
39 | blockquote, q { | ||
40 | quotes: none; | ||
41 | } | ||
42 | blockquote:before, blockquote:after, | ||
43 | q:before, q:after { | ||
44 | content: ''; | ||
45 | content: none; | ||
46 | } | ||
47 | table { | ||
48 | border-collapse: collapse; | ||
49 | border-spacing: 0; | ||
50 | } | ||
51 | |||
52 | .app-container{ | 3 | .app-container{ |
53 | padding-bottom:50px | 4 | padding-bottom:50px |
54 | } | 5 | } |
... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
1 | <template> | 1 | <template> |
2 | <div> | 2 | <div> |
3 | <van-tabbar fixed v-model="active" @change="onChange"> | 3 | <van-tabbar fixed route> |
4 | <van-tabbar-item icon="home-o">首页</van-tabbar-item> | 4 | <van-tabbar-item to="/" icon="home-o"> |
5 | <van-tabbar-item icon="good-job-o">github</van-tabbar-item> | 5 | 首页 |
6 | </van-tabbar> | 6 | </van-tabbar-item> |
7 | <van-tabbar-item to="/about" icon="user-o"> | ||
8 | 关于我 | ||
9 | </van-tabbar-item> | ||
10 | </van-tabbar> | ||
11 | <!-- <van-tabbar fixed v-model="active" @change="onChange"> | ||
12 | <van-tabbar-item to="/home" icon="home-o">首页</van-tabbar-item> | ||
13 | <van-tabbar-item to="/about" icon="user-o">关于我</van-tabbar-item> | ||
14 | </van-tabbar> --> | ||
7 | </div> | 15 | </div> |
8 | </template> | 16 | </template> |
9 | 17 | ||
10 | <script> | 18 | <script> |
11 | import _ from 'lodash' | 19 | |
12 | console.log(_.join(['a', 'b'], '~')) | ||
13 | export default { | 20 | export default { |
14 | name: 'TabBar', | 21 | name: 'TabBar', |
15 | data() { | 22 | data() { |
... | @@ -18,9 +25,9 @@ export default { | ... | @@ -18,9 +25,9 @@ export default { |
18 | } | 25 | } |
19 | }, | 26 | }, |
20 | methods: { | 27 | methods: { |
21 | onChange(index) { | 28 | // onChange(index) { |
22 | if (index === 1) window.location.href = 'https://github.com/sunniejs/vue-h5-template' | 29 | // if (index === 1) window.location.href = 'https://github.com/sunniejs/vue-h5-template' |
23 | } | 30 | // } |
24 | } | 31 | } |
25 | } | 32 | } |
26 | </script> | 33 | </script> | ... | ... |
... | @@ -4,5 +4,6 @@ module.exports = { | ... | @@ -4,5 +4,6 @@ module.exports = { |
4 | baseUrl: 'http://localhost:9018', // 项目地址 | 4 | baseUrl: 'http://localhost:9018', // 项目地址 |
5 | baseApi: 'https://test.xxx.com/api', // 本地api请求地址 | 5 | baseApi: 'https://test.xxx.com/api', // 本地api请求地址 |
6 | APPID: 'xxx', | 6 | APPID: 'xxx', |
7 | APPSECRET: 'xxx' | 7 | APPSECRET: 'xxx', |
8 | $cdn:'https://imgs.solui.cn' | ||
8 | } | 9 | } | ... | ... |
... | @@ -4,5 +4,6 @@ module.exports = { | ... | @@ -4,5 +4,6 @@ module.exports = { |
4 | baseUrl: 'https://www.xxx.com/', // 正式项目地址 | 4 | baseUrl: 'https://www.xxx.com/', // 正式项目地址 |
5 | baseApi: 'https://www.xxx.com/api', // 正式api请求地址 | 5 | baseApi: 'https://www.xxx.com/api', // 正式api请求地址 |
6 | APPID: 'xxx', | 6 | APPID: 'xxx', |
7 | APPSECRET: 'xxx' | 7 | APPSECRET: 'xxx', |
8 | $cdn:'https://imgs.solui.cn' | ||
8 | } | 9 | } | ... | ... |
... | @@ -3,5 +3,6 @@ module.exports = { | ... | @@ -3,5 +3,6 @@ module.exports = { |
3 | baseUrl: 'https://test.xxx.com', // 测试项目地址 | 3 | baseUrl: 'https://test.xxx.com', // 测试项目地址 |
4 | baseApi: 'https://test.xxx.com/api', // 测试api请求地址 | 4 | baseApi: 'https://test.xxx.com/api', // 测试api请求地址 |
5 | APPID: 'xxx', | 5 | APPID: 'xxx', |
6 | APPSECRET: 'xxx' | 6 | APPSECRET: 'xxx', |
7 | $cdn:'https://imgs.solui.cn' | ||
7 | } | 8 | } | ... | ... |
... | @@ -5,7 +5,7 @@ import App from './App.vue' | ... | @@ -5,7 +5,7 @@ import App from './App.vue' |
5 | import router from './router' | 5 | import router from './router' |
6 | import store from './store' | 6 | import store from './store' |
7 | // 引入全局样式 | 7 | // 引入全局样式 |
8 | import '@/assets/css/index.scss' | 8 | // import '@/assets/css/index.scss' |
9 | 9 | ||
10 | // 全局引入按需引入UI库 vant | 10 | // 全局引入按需引入UI库 vant |
11 | import '@/plugins/vant' | 11 | import '@/plugins/vant' | ... | ... |
... | @@ -6,45 +6,31 @@ | ... | @@ -6,45 +6,31 @@ |
6 | <h2 class="demo-home__desc"> | 6 | <h2 class="demo-home__desc"> |
7 | A vue h5 template with Vant UI | 7 | A vue h5 template with Vant UI |
8 | </h2> | 8 | </h2> |
9 | <div class="list"> | ||
10 | <div class="item">项目地址: <a href="https://github.com/sunniejs">https://github.com/sunniejs</a></div> | ||
11 | <div class="item">项目作者: sunnie</div> | ||
12 | <div class="item"></div> | ||
13 | <div class="author"></div> | ||
14 | </div> | ||
9 | </div> | 15 | </div> |
10 | <van-cell icon="success" v-for="item in list" :key="item" :title="item" /> | ||
11 | <!-- tabbar --> | ||
12 | <TabBar></TabBar> | ||
13 | </div> | 16 | </div> |
14 | </template> | 17 | </template> |
15 | 18 | ||
16 | <script> | 19 | <script> |
17 | import TabBar from '@/components/TabBar' | ||
18 | // 请求接口 | 20 | // 请求接口 |
19 | import {getUserInfo} from '@/api/user.js' | 21 | import { getUserInfo } from '@/api/user.js' |
20 | 22 | ||
21 | export default { | 23 | export default { |
22 | components: { | ||
23 | TabBar | ||
24 | }, | ||
25 | |||
26 | data() { | 24 | data() { |
27 | return { | 25 | return { |
28 | list: [ | 26 | |
29 | 'Vue-cli4', | ||
30 | 'VantUI组件按需加载', | ||
31 | 'Sass', | ||
32 | 'Webpack 4', | ||
33 | 'Vue-router', | ||
34 | 'Vuex', | ||
35 | 'Axios封装', | ||
36 | 'rem适配方案', | ||
37 | '多环境配置', | ||
38 | '生产环境cdn优化首屏加速', | ||
39 | 'babel低版本浏览器兼容', | ||
40 | 'Eslint+Pettier统一开发规范' | ||
41 | ] | ||
42 | } | 27 | } |
43 | }, | 28 | }, |
44 | 29 | ||
45 | computed: {}, | 30 | computed: {}, |
46 | 31 | ||
47 | mounted() { | 32 | mounted() { |
33 | |||
48 | this.initData() | 34 | this.initData() |
49 | }, | 35 | }, |
50 | 36 | ||
... | @@ -52,10 +38,10 @@ export default { | ... | @@ -52,10 +38,10 @@ export default { |
52 | // 请求数据案例 | 38 | // 请求数据案例 |
53 | initData() { | 39 | initData() { |
54 | // 请求接口数据,仅作为展示,需要配置src->config下环境文件 | 40 | // 请求接口数据,仅作为展示,需要配置src->config下环境文件 |
55 | const params = {user: 'sunnie'} | 41 | const params = { user: 'sunnie' } |
56 | getUserInfo(params) | 42 | getUserInfo(params) |
57 | .then(() => {}) | 43 | .then(() => { }) |
58 | .catch(() => {}) | 44 | .catch(() => { }) |
59 | } | 45 | } |
60 | } | 46 | } |
61 | } | 47 | } |
... | @@ -64,6 +50,7 @@ export default { | ... | @@ -64,6 +50,7 @@ export default { |
64 | .app-container { | 50 | .app-container { |
65 | .warpper { | 51 | .warpper { |
66 | padding: 12px; | 52 | padding: 12px; |
53 | background: $background-color; | ||
67 | .demo-home__title { | 54 | .demo-home__title { |
68 | margin: 0 0 6px; | 55 | margin: 0 0 6px; |
69 | font-size: 32px; | 56 | font-size: 32px; |
... | @@ -85,6 +72,23 @@ export default { | ... | @@ -85,6 +72,23 @@ export default { |
85 | color: rgba(69, 90, 100, 0.6); | 72 | color: rgba(69, 90, 100, 0.6); |
86 | font-size: 14px; | 73 | font-size: 14px; |
87 | } | 74 | } |
75 | .list { | ||
76 | display: flex; | ||
77 | flex-direction: column; | ||
78 | color: #666; | ||
79 | font-size: 14px; | ||
80 | .item { | ||
81 | font-size: 14px; | ||
82 | line-height: 24px; | ||
83 | } | ||
84 | } | ||
85 | |||
86 | .author { | ||
87 | margin:10px auto; | ||
88 | width: 200px; | ||
89 | height: 200px; | ||
90 | background: url($cdn+'/weapp/me.png') center / contain no-repeat; | ||
91 | } | ||
88 | } | 92 | } |
89 | } | 93 | } |
90 | </style> | 94 | </style> | ... | ... |
... | @@ -8,36 +8,35 @@ | ... | @@ -8,36 +8,35 @@ |
8 | </h2> | 8 | </h2> |
9 | </div> | 9 | </div> |
10 | <van-cell icon="success" v-for="item in list" :key="item" :title="item" /> | 10 | <van-cell icon="success" v-for="item in list" :key="item" :title="item" /> |
11 | <!-- tabbar --> | ||
12 | <TabBar></TabBar> | ||
13 | </div> | 11 | </div> |
14 | </template> | 12 | </template> |
15 | 13 | ||
16 | <script> | 14 | <script> |
17 | import TabBar from '@/components/TabBar' | 15 | import TabBar from '@/components/TabBar' |
18 | // 请求接口 | 16 | // 请求接口 |
19 | import {getUserInfo} from '@/api/user.js' | 17 | import { getUserInfo } from '@/api/user.js' |
20 | 18 | ||
21 | export default { | 19 | export default { |
22 | components: { | ||
23 | TabBar | ||
24 | }, | ||
25 | 20 | ||
26 | data() { | 21 | data() { |
27 | return { | 22 | return { |
28 | list: [ | 23 | list: [ |
29 | 'Vue-cli4', | 24 | 'Vue-cli4', |
30 | 'VantUI组件按需加载', | 25 | ' 配置多环境变量', |
31 | 'Sass', | 26 | ' VantUI 组件按需加载', |
27 | ' Sass', | ||
32 | 'Webpack 4', | 28 | 'Webpack 4', |
33 | 'Vue-router', | ||
34 | 'Vuex', | 29 | 'Vuex', |
35 | 'Axios封装', | 30 | ' Axios 封装及接口管理', |
36 | 'rem适配方案', | 31 | 'Vue-router', |
37 | '多环境配置', | 32 | 'vue.config.js 基础配置', |
38 | '生产环境cdn优化首屏加速', | 33 | '配置 proxy 跨域', |
39 | 'babel低版本浏览器兼容', | 34 | '配置 alias 别名', |
40 | 'Eslint+Pettier统一开发规范' | 35 | '配置 打包分析', |
36 | '配置 externals 引入 cdn 资源', | ||
37 | '去掉 console.log', | ||
38 | 'splitChunks 单独打包第三方模块', | ||
39 | ' 添加 IE 兼容' | ||
41 | ] | 40 | ] |
42 | } | 41 | } |
43 | }, | 42 | }, |
... | @@ -52,19 +51,19 @@ export default { | ... | @@ -52,19 +51,19 @@ export default { |
52 | // 请求数据案例 | 51 | // 请求数据案例 |
53 | initData() { | 52 | initData() { |
54 | // 请求接口数据,仅作为展示,需要配置src->config下环境文件 | 53 | // 请求接口数据,仅作为展示,需要配置src->config下环境文件 |
55 | const params = {user: 'sunnie'} | 54 | const params = { user: 'sunnie' } |
56 | getUserInfo(params) | 55 | getUserInfo(params) |
57 | .then(() => {}) | 56 | .then(() => { }) |
58 | .catch(() => {}) | 57 | .catch(() => { }) |
59 | } | 58 | } |
60 | } | 59 | } |
61 | } | 60 | } |
62 | </script> | 61 | </script> |
63 | <style lang="scss" scoped> | 62 | <style lang="scss" scoped> |
64 | // @import '@/assets/css/index.scss'; | ||
65 | .app-container { | 63 | .app-container { |
66 | .warpper { | 64 | .warpper { |
67 | padding: 12px; | 65 | padding: 12px; |
66 | background: $background-color; | ||
68 | .demo-home__title { | 67 | .demo-home__title { |
69 | margin: 0 0 6px; | 68 | margin: 0 0 6px; |
70 | font-size: 32px; | 69 | font-size: 32px; | ... | ... |
... | @@ -9,44 +9,31 @@ const name = defaultSettings.title || 'vue mobile template' | ... | @@ -9,44 +9,31 @@ const name = defaultSettings.title || 'vue mobile template' |
9 | const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV) | 9 | const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV) |
10 | 10 | ||
11 | // externals | 11 | // externals |
12 | const externals = { | 12 | // const externals = { |
13 | vue: 'Vue', | 13 | // vue: 'Vue', |
14 | 'vue-router': 'VueRouter', | 14 | // 'vue-router': 'VueRouter', |
15 | vuex: 'Vuex', | 15 | // vuex: 'Vuex', |
16 | vant: 'vant', | 16 | // vant: 'vant', |
17 | axios: 'axios' | 17 | // axios: 'axios' |
18 | } | 18 | // } |
19 | // CDN外链,会插入到index.html中 | 19 | // CDN外链,会插入到index.html中 |
20 | const cdn = { | ||
21 | // 开发环境 | ||
22 | dev: { | ||
23 | css: [ | ||
24 | ], | ||
25 | js: [] | ||
26 | }, | ||
27 | // 生产环境 | ||
28 | build: { | ||
29 | css: ['https://cdn.jsdelivr.net/npm/vant@2.4.7/lib/index.css'], | ||
30 | js: [ | ||
31 | 'https://cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.min.js', | ||
32 | 'https://cdn.jsdelivr.net/npm/vue-router@3.1.5/dist/vue-router.min.js', | ||
33 | 'https://cdn.jsdelivr.net/npm/axios@0.19.2/dist/axios.min.js', | ||
34 | 'https://cdn.jsdelivr.net/npm/vuex@3.1.2/dist/vuex.min.js', | ||
35 | 'https://cdn.jsdelivr.net/npm/vant@2.4.7/lib/index.min.js' | ||
36 | ] | ||
37 | } | ||
38 | } | ||
39 | // const cdn = { | 20 | // const cdn = { |
40 | // css: ['https://cdn.jsdelivr.net/npm/vant@2.4.7/lib/index.css'], | 21 | // // 开发环境 |
41 | // js: [ | 22 | // dev: { |
42 | // 'https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js', | 23 | // css: [], |
43 | // 'https://cdnjs.cloudflare.com/ajax/libs/vue-router/3.1.5/vue-router.min.js', | 24 | // js: [] |
44 | // 'https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.2/axios.min.js', | 25 | // }, |
45 | // 'https://cdnjs.cloudflare.com/ajax/libs/vuex/3.1.2/vuex.min.js', | 26 | // // 生产环境 |
46 | // 'https://cdn.jsdelivr.net/npm/vant@2.4.7/lib/index.min.js', | 27 | // build: { |
47 | // 'https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.9-1/crypto-js.min.js', | 28 | // css: ['https://cdn.jsdelivr.net/npm/vant@2.4.7/lib/index.css'], |
48 | 29 | // js: [ | |
49 | // ] | 30 | // 'https://cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.min.js', |
31 | // 'https://cdn.jsdelivr.net/npm/vue-router@3.1.5/dist/vue-router.min.js', | ||
32 | // 'https://cdn.jsdelivr.net/npm/axios@0.19.2/dist/axios.min.js', | ||
33 | // 'https://cdn.jsdelivr.net/npm/vuex@3.1.2/dist/vuex.min.js', | ||
34 | // 'https://cdn.jsdelivr.net/npm/vant@2.4.7/lib/index.min.js' | ||
35 | // ] | ||
36 | // } | ||
50 | // } | 37 | // } |
51 | 38 | ||
52 | module.exports = { | 39 | module.exports = { |
... | @@ -55,7 +42,7 @@ module.exports = { | ... | @@ -55,7 +42,7 @@ module.exports = { |
55 | outputDir: 'dist', // 生产环境构建文件的目录 | 42 | outputDir: 'dist', // 生产环境构建文件的目录 |
56 | assetsDir: 'static', // outputDir的静态资源(js、css、img、fonts)目录 | 43 | assetsDir: 'static', // outputDir的静态资源(js、css、img、fonts)目录 |
57 | lintOnSave: false, | 44 | lintOnSave: false, |
58 | productionSourceMap: !IS_PROD, // 生产环境的 source map | 45 | productionSourceMap: false, // 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。 |
59 | devServer: { | 46 | devServer: { |
60 | port: 9020, // 端口 | 47 | port: 9020, // 端口 |
61 | open: false, // 启动后打开浏览器 | 48 | open: false, // 启动后打开浏览器 |
... | @@ -76,16 +63,27 @@ module.exports = { | ... | @@ -76,16 +63,27 @@ module.exports = { |
76 | // } | 63 | // } |
77 | // } | 64 | // } |
78 | }, | 65 | }, |
79 | 66 | css: { | |
67 | extract: IS_PROD, | ||
68 | sourceMap: false, | ||
69 | loaderOptions: { | ||
70 | scss: { | ||
71 | // 向全局sass样式传入共享的全局变量, $src可以配置图片cdn前缀 | ||
72 | // 详情: https://cli.vuejs.org/guide/css.html#passing-options-to-pre-processor-loaders | ||
73 | prependData: ` | ||
74 | @import "assets/css/index.scss"; | ||
75 | $cdn: "${defaultSettings.$cdn}"; | ||
76 | ` | ||
77 | } | ||
78 | } | ||
79 | }, | ||
80 | configureWebpack: config => { | 80 | configureWebpack: config => { |
81 | config.name = name | 81 | config.name = name |
82 | |||
82 | // 为生产环境修改配置... | 83 | // 为生产环境修改配置... |
83 | if (IS_PROD) { | 84 | // if (IS_PROD) { |
84 | // externals | 85 | // // externals |
85 | config.externals = externals | 86 | // config.externals = externals |
86 | } | ||
87 | // 为开发环境修改配置... | ||
88 | // if (process.env.NODE_ENV === 'development') { | ||
89 | // } | 87 | // } |
90 | }, | 88 | }, |
91 | 89 | ||
... | @@ -100,7 +98,34 @@ module.exports = { | ... | @@ -100,7 +98,34 @@ module.exports = { |
100 | .set('api', resolve('src/api')) | 98 | .set('api', resolve('src/api')) |
101 | .set('views', resolve('src/views')) | 99 | .set('views', resolve('src/views')) |
102 | .set('components', resolve('src/components')) | 100 | .set('components', resolve('src/components')) |
103 | // 打包分析 | 101 | |
102 | /** | ||
103 | * 添加CDN参数到htmlWebpackPlugin配置中 | ||
104 | */ | ||
105 | // config.plugin('html').tap(args => { | ||
106 | // if (IS_PROD) { | ||
107 | // args[0].cdn = cdn.build | ||
108 | // } else { | ||
109 | // args[0].cdn = cdn.dev | ||
110 | // } | ||
111 | // return args | ||
112 | // }) | ||
113 | |||
114 | /** | ||
115 | * 设置保留空格 | ||
116 | */ | ||
117 | config.module | ||
118 | .rule('vue') | ||
119 | .use('vue-loader') | ||
120 | .loader('vue-loader') | ||
121 | .tap(options => { | ||
122 | options.compilerOptions.preserveWhitespace = true | ||
123 | return options | ||
124 | }) | ||
125 | .end() | ||
126 | /** | ||
127 | * 打包分析 | ||
128 | */ | ||
104 | if (IS_PROD) { | 129 | if (IS_PROD) { |
105 | config.plugin('webpack-report').use(BundleAnalyzerPlugin, [ | 130 | config.plugin('webpack-report').use(BundleAnalyzerPlugin, [ |
106 | { | 131 | { |
... | @@ -108,69 +133,46 @@ module.exports = { | ... | @@ -108,69 +133,46 @@ module.exports = { |
108 | } | 133 | } |
109 | ]) | 134 | ]) |
110 | } | 135 | } |
111 | /** | 136 | config |
112 | * 添加CDN参数到htmlWebpackPlugin配置中 | 137 | // https://webpack.js.org/configuration/devtool/#development |
113 | */ | 138 | .when(!IS_PROD, config => config.devtool('cheap-source-map')) |
114 | config.plugin('html').tap(args => { | ||
115 | if (IS_PROD) { | ||
116 | args[0].cdn = cdn.build | ||
117 | } else { | ||
118 | args[0].cdn = cdn.dev | ||
119 | } | ||
120 | |||
121 | return args | ||
122 | }) | ||
123 | // set preserveWhitespace | ||
124 | // config.module | ||
125 | // .rule('vue') | ||
126 | // .use('vue-loader') | ||
127 | // .loader('vue-loader') | ||
128 | // .tap(options => { | ||
129 | // options.compilerOptions.preserveWhitespace = true | ||
130 | // return options | ||
131 | // }) | ||
132 | // .end() | ||
133 | 139 | ||
134 | // config | 140 | config.when(IS_PROD, config => { |
135 | // // https://webpack.js.org/configuration/devtool/#development | 141 | config |
136 | // .when(process.env.NODE_ENV === 'development', config => config.devtool('cheap-source-map')) | 142 | .plugin('ScriptExtHtmlWebpackPlugin') |
137 | 143 | .after('html') | |
138 | // config.when(IS_PROD, config => { | 144 | .use('script-ext-html-webpack-plugin', [ |
139 | // config | 145 | { |
140 | // .plugin('ScriptExtHtmlWebpackPlugin') | 146 | // 将 runtime 作为内联引入不单独存在 |
141 | // .after('html') | 147 | inline: /runtime\..*\.js$/ |
142 | // .use('script-ext-html-webpack-plugin', [ | 148 | } |
143 | // { | 149 | ]) |
144 | // // `runtime` must same as runtimeChunk name. default is `runtime` | 150 | .end() |
145 | // inline: /runtime\..*\.js$/ | 151 | config.optimization.splitChunks({ |
146 | // } | 152 | chunks: 'all', |
147 | // ]) | 153 | cacheGroups: { |
148 | // .end() | 154 | // cacheGroups 下可以可以配置多个组,每个组根据test设置条件,符合test条件的模块 |
149 | // config.optimization.splitChunks({ | 155 | commons: { |
150 | // chunks: 'all', | 156 | name: 'chunk-commons', |
151 | // cacheGroups: { | 157 | test: resolve('src/components'), |
152 | // // cacheGroups 下可以可以配置多个组,每个组根据test设置条件,符合test条件的模块 | 158 | minChunks: 3, // 被至少用三次以上打包分离 |
153 | // commons: { | 159 | priority: 5, // 优先级 |
154 | // name: 'chunk-commons', | 160 | reuseExistingChunk: true // 表示是否使用已有的 chunk,如果为 true 则表示如果当前的 chunk 包含的模块已经被抽取出去了,那么将不会重新生成新的。 |
155 | // test: resolve('src/components'), | 161 | }, |
156 | // minChunks: 3, // 被至少用三次以上打包分离 | 162 | node_vendors: { |
157 | // priority: 5, // 优先级 | 163 | name: 'chunk-libs', |
158 | // reuseExistingChunk: true // 复用其他chunk内已拥有的模块 | 164 | chunks: 'initial', // 只打包初始时依赖的第三方 |
159 | // }, | 165 | test: /[\\/]node_modules[\\/]/, |
160 | // // vantUI: { | 166 | priority: 10 |
161 | // // name: 'chunk-vantUI', // 将 vant 打包到单独文件 | 167 | }, |
162 | // // priority: 20, | 168 | vantUI: { |
163 | // // test: /[\\/]node_modules[\\/]_?vant(.*)/ // in order to adapt to cnpm | 169 | name: 'chunk-vantUI', // 单独将 vantUI 拆包 |
164 | // // }, | 170 | priority: 20, // 数字大权重到,满足多个 cacheGroups 的条件时候分到权重高的 |
165 | // libs: { | 171 | test: /[\\/]node_modules[\\/]_?vant(.*)/ |
166 | // name: 'chunk-libs', | 172 | } |
167 | // chunks: 'initial', // only package third parties that are initially dependent | 173 | } |
168 | // test: /[\\/]node_modules[\\/]/, | 174 | }) |
169 | // priority: 10 | 175 | config.optimization.runtimeChunk('single') |
170 | // } | 176 | }) |
171 | // } | ||
172 | // }) | ||
173 | // config.optimization.runtimeChunk('single') | ||
174 | // }) | ||
175 | } | 177 | } |
176 | } | 178 | } | ... | ... |
-
Please register or sign in to post a comment