张延森 3 jaren geleden
bovenliggende
commit
65de16facf

+ 2
- 0
.browserslistrc Bestand weergeven

@@ -0,0 +1,2 @@
1
+> 1%
2
+last 2 versions

+ 14
- 0
.editorconfig Bestand weergeven

@@ -0,0 +1,14 @@
1
+# http://editorconfig.org
2
+root = true
3
+
4
+[*]
5
+charset = utf-8
6
+indent_style = space
7
+indent_size = 2
8
+end_of_line = lf
9
+insert_final_newline = true
10
+trim_trailing_whitespace = true
11
+
12
+[*.md]
13
+insert_final_newline = false
14
+trim_trailing_whitespace = false

+ 4
- 0
.env.development Bestand weergeven

@@ -0,0 +1,4 @@
1
+NODE_ENV='development'
2
+# must start with VUE_APP_ 
3
+VUE_APP_ENV = 'development'
4
+ 

+ 4
- 0
.env.production Bestand weergeven

@@ -0,0 +1,4 @@
1
+NODE_ENV='production'
2
+# must start with VUE_APP_
3
+VUE_APP_ENV = 'production'
4
+ 

+ 4
- 0
.env.staging Bestand weergeven

@@ -0,0 +1,4 @@
1
+NODE_ENV='production'
2
+# must start with VUE_APP_
3
+VUE_APP_ENV = 'staging'
4
+ 

+ 5
- 0
.eslintignore Bestand weergeven

@@ -0,0 +1,5 @@
1
+build/*.js
2
+src/assets
3
+src/config
4
+public
5
+dist

+ 261
- 0
.eslintrc.js Bestand weergeven

@@ -0,0 +1,261 @@
1
+module.exports = {
2
+  root: true,
3
+  env: {
4
+    node: true
5
+  },
6
+  extends: ['plugin:vue/essential', 'eslint:recommended'],
7
+  parserOptions: {
8
+    parser: 'babel-eslint'
9
+  },
10
+  rules: {
11
+    'vue/max-attributes-per-line': [
12
+      2,
13
+      {
14
+        singleline: 10,
15
+        multiline: {
16
+          max: 1,
17
+          allowFirstLine: false
18
+        }
19
+      }
20
+    ],
21
+    'vue/singleline-html-element-content-newline': 'off',
22
+    'vue/multiline-html-element-content-newline': 'off',
23
+    'vue/name-property-casing': ['error', 'PascalCase'],
24
+    'vue/no-v-html': 'off',
25
+    'accessor-pairs': 2,
26
+    'arrow-spacing': [
27
+      2,
28
+      {
29
+        before: true,
30
+        after: true
31
+      }
32
+    ],
33
+    'block-spacing': [2, 'always'],
34
+    'brace-style': [
35
+      2,
36
+      '1tbs',
37
+      {
38
+        allowSingleLine: true
39
+      }
40
+    ],
41
+    camelcase: [
42
+      0,
43
+      {
44
+        properties: 'always'
45
+      }
46
+    ],
47
+    'comma-dangle': [2, 'never'],
48
+    'comma-spacing': [
49
+      2,
50
+      {
51
+        before: false,
52
+        after: true
53
+      }
54
+    ],
55
+    'comma-style': [2, 'last'],
56
+    'constructor-super': 2,
57
+    curly: [2, 'multi-line'],
58
+    'dot-location': [2, 'property'],
59
+    'eol-last': 2,
60
+    eqeqeq: ['error', 'always', { null: 'ignore' }],
61
+    'generator-star-spacing': [
62
+      2,
63
+      {
64
+        before: true,
65
+        after: true
66
+      }
67
+    ],
68
+    'handle-callback-err': [2, '^(err|error)$'],
69
+    indent: [
70
+      2,
71
+      2,
72
+      {
73
+        SwitchCase: 1
74
+      }
75
+    ],
76
+    'jsx-quotes': [2, 'prefer-single'],
77
+    'key-spacing': [
78
+      2,
79
+      {
80
+        beforeColon: false,
81
+        afterColon: true
82
+      }
83
+    ],
84
+    'keyword-spacing': [
85
+      2,
86
+      {
87
+        before: true,
88
+        after: true
89
+      }
90
+    ],
91
+    'new-cap': [
92
+      2,
93
+      {
94
+        newIsCap: true,
95
+        capIsNew: false
96
+      }
97
+    ],
98
+    'new-parens': 2,
99
+    'no-array-constructor': 2,
100
+    'no-caller': 2,
101
+    'no-console': 'off',
102
+    'no-class-assign': 2,
103
+    'no-cond-assign': 2,
104
+    'no-const-assign': 2,
105
+    'no-control-regex': 0,
106
+    'no-delete-var': 2,
107
+    'no-dupe-args': 2,
108
+    'no-dupe-class-members': 2,
109
+    'no-dupe-keys': 2,
110
+    'no-duplicate-case': 2,
111
+    'no-empty-character-class': 2,
112
+    'no-empty-pattern': 2,
113
+    'no-eval': 2,
114
+    'no-ex-assign': 2,
115
+    'no-extend-native': 2,
116
+    'no-extra-bind': 2,
117
+    'no-extra-boolean-cast': 2,
118
+    'no-extra-parens': [2, 'functions'],
119
+    'no-fallthrough': 2,
120
+    'no-floating-decimal': 2,
121
+    'no-func-assign': 2,
122
+    'no-implied-eval': 2,
123
+    'no-inner-declarations': [2, 'functions'],
124
+    'no-invalid-regexp': 2,
125
+    'no-irregular-whitespace': 2,
126
+    'no-iterator': 2,
127
+    'no-label-var': 2,
128
+    'no-labels': [
129
+      2,
130
+      {
131
+        allowLoop: false,
132
+        allowSwitch: false
133
+      }
134
+    ],
135
+    'no-lone-blocks': 2,
136
+    'no-mixed-spaces-and-tabs': 2,
137
+    'no-multi-spaces': 2,
138
+    'no-multi-str': 2,
139
+    'no-multiple-empty-lines': [
140
+      2,
141
+      {
142
+        max: 1
143
+      }
144
+    ],
145
+    'no-native-reassign': 2,
146
+    'no-negated-in-lhs': 2,
147
+    'no-new-object': 2,
148
+    'no-new-require': 2,
149
+    'no-new-symbol': 2,
150
+    'no-new-wrappers': 2,
151
+    'no-obj-calls': 2,
152
+    'no-octal': 2,
153
+    'no-octal-escape': 2,
154
+    'no-path-concat': 2,
155
+    'no-proto': 2,
156
+    'no-redeclare': 2,
157
+    'no-regex-spaces': 2,
158
+    'no-return-assign': [2, 'except-parens'],
159
+    'no-self-assign': 2,
160
+    'no-self-compare': 2,
161
+    'no-sequences': 2,
162
+    'no-shadow-restricted-names': 2,
163
+    'no-spaced-func': 2,
164
+    'no-sparse-arrays': 2,
165
+    'no-this-before-super': 2,
166
+    'no-throw-literal': 2,
167
+    'no-trailing-spaces': 2,
168
+    'no-undef': 2,
169
+    'no-undef-init': 2,
170
+    'no-unexpected-multiline': 2,
171
+    'no-unmodified-loop-condition': 2,
172
+    'no-unneeded-ternary': [
173
+      2,
174
+      {
175
+        defaultAssignment: false
176
+      }
177
+    ],
178
+    'no-unreachable': 2,
179
+    'no-unsafe-finally': 2,
180
+    'no-unused-vars': [
181
+      2,
182
+      {
183
+        vars: 'all',
184
+        args: 'none'
185
+      }
186
+    ],
187
+    'no-useless-call': 2,
188
+    'no-useless-computed-key': 2,
189
+    'no-useless-constructor': 2,
190
+    'no-useless-escape': 0,
191
+    'no-whitespace-before-property': 2,
192
+    'no-with': 2,
193
+    'one-var': [
194
+      2,
195
+      {
196
+        initialized: 'never'
197
+      }
198
+    ],
199
+    'operator-linebreak': [
200
+      2,
201
+      'after',
202
+      {
203
+        overrides: {
204
+          '?': 'before',
205
+          ':': 'before'
206
+        }
207
+      }
208
+    ],
209
+    'padded-blocks': [2, 'never'],
210
+    quotes: [
211
+      2,
212
+      'single',
213
+      {
214
+        avoidEscape: true,
215
+        allowTemplateLiterals: true
216
+      }
217
+    ],
218
+    semi: [2, 'never'],
219
+    'semi-spacing': [
220
+      2,
221
+      {
222
+        before: false,
223
+        after: true
224
+      }
225
+    ],
226
+    'space-before-blocks': [2, 'always'],
227
+    'space-before-function-paren': [2, 'never'],
228
+    'space-in-parens': [2, 'never'],
229
+    'space-infix-ops': 2,
230
+    'space-unary-ops': [
231
+      2,
232
+      {
233
+        words: true,
234
+        nonwords: false
235
+      }
236
+    ],
237
+    'spaced-comment': [
238
+      2,
239
+      'always',
240
+      {
241
+        markers: ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
242
+      }
243
+    ],
244
+    'template-curly-spacing': [2, 'never'],
245
+    'use-isnan': 2,
246
+    'valid-typeof': 2,
247
+    'wrap-iife': [2, 'any'],
248
+    'yield-star-spacing': [2, 'both'],
249
+    yoda: [2, 'never'],
250
+    'prefer-const': 2,
251
+    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
252
+    'object-curly-spacing': [
253
+      2,
254
+      'always',
255
+      {
256
+        objectsInObjects: false
257
+      }
258
+    ],
259
+    'array-bracket-spacing': [2, 'never']
260
+  }
261
+}

+ 24
- 0
.gitignore Bestand weergeven

@@ -0,0 +1,24 @@
1
+.DS_Store
2
+node_modules
3
+/dist
4
+/docs
5
+# local env files
6
+.env.local
7
+.env.*.local
8
+
9
+# Log files
10
+npm-debug.log*
11
+yarn-debug.log*
12
+yarn-error.log*
13
+
14
+# Editor directories and files
15
+.idea
16
+.vscode
17
+*.suo
18
+*.ntvs*
19
+*.njsproj
20
+*.sln
21
+*.sw?
22
+
23
+package-lock.json
24
+yarn.lock

+ 13
- 0
.postcssrc.js Bestand weergeven

@@ -0,0 +1,13 @@
1
+// https://github.com/michael-ciniawsky/postcss-load-config
2
+module.exports = {
3
+  plugins: {
4
+    autoprefixer: {
5
+      overrideBrowserslist: ['Android 4.1', 'iOS 7.1', 'Chrome > 31', 'ff > 31', 'ie >= 8']
6
+    },
7
+    'postcss-pxtorem': {
8
+      rootValue: 37.5,
9
+      propList: ['*'],
10
+      //selectorBlackList: ['van-']
11
+    }
12
+  }
13
+}

+ 24
- 0
.prettierrc Bestand weergeven

@@ -0,0 +1,24 @@
1
+{
2
+  "printWidth": 120,
3
+  "tabWidth": 2,
4
+  "singleQuote": true,
5
+  "trailingComma": "none",
6
+  "semi": false,
7
+  "wrap_line_length": 120,
8
+  "wrap_attributes": "auto",
9
+  "proseWrap": "always",
10
+  "arrowParens": "avoid",
11
+  "bracketSpacing": true,
12
+  "jsxBracketSameLine": true,
13
+  "useTabs": false,
14
+  "eslintIntegration":true,
15
+  "overrides": [
16
+    {
17
+      "files": ".prettierrc",
18
+      "options": {
19
+        "parser": "json"
20
+      }
21
+    }
22
+  ],
23
+  "endOfLine": "auto"
24
+}

+ 45
- 0
README.en.md Bestand weergeven

@@ -0,0 +1,45 @@
1
+# vue-h5-template
2
+
3
+基于vue-cli3.0+webpack 4+vant ui + sass+ rem适配方案+axios封装,构建手机端模板脚手架
4
+
5
+#### 介绍
6
+[关于项目介绍](https://segmentfault.com/a/1190000019275330)
7
+
8
+ 1. vuecli3.0      
9
+ 2. 多环境开发       
10
+ 3. axios封装         
11
+ 4. rem适配方案        
12
+ 5. 生产环境cdn优化首屏加速
13
+ 6. babel低版本浏览器兼容
14
+ 7. 环境发布脚本
15
+
16
+#### 多环境
17
+
18
+
19
+之前写过一个多环境的方案,是基于vue-cli2.0的  [vue多环境配置方案-传送门](https://segmentfault.com/a/1190000019136606)
20
+最近新的项目采用了vuecli3.0开始了一番折腾
21
+
22
+这里参考了[vue-admin-template](https://github.com/PanJiaChen/vue-admin-template)  基本思路不变
23
+在src的文件里新建config 根据不同的环境去引用不同的配置文件,不同的是在根目录下有三个设置环境变量的文件
24
+这里可以参考vue-admin-template
25
+
26
+#### rem适配方案
27
+
28
+还是那句话,用vw还是用rem,这是个问题?
29
+
30
+选用rem的原因是因为vant直接给到了这个适配方案,个人也比较喜欢这个方案
31
+
32
+[vant](https://youzan.github.io/vant/#/zh-CN/quickstart)  
33
+ 
34
+ #### 总结
35
+
36
+因为项目刚刚构建起来,后面还会持续更新,实际使用过程中一定还有很多问题,如果文章中有错误希望能够被指正,一起成长
37
+
38
+# 关于我
39
+
40
+您可以扫描添加下方的微信并备注 Soul 加交流群,给我提意见,交流学习。
41
+<p>
42
+  <img src="https://tweapp.top1buyer.com/mine.jpg" width="256" style="display:inline;">
43
+</p>
44
+ 
45
+如果对你有帮助送我一颗小星星(づ ̄3 ̄)づ╭❤~

+ 22
- 0
babel.config.js Bestand weergeven

@@ -0,0 +1,22 @@
1
+// 获取 VUE_APP_ENV 非 NODE_ENV,测试环境依然 console
2
+const IS_PROD = ['production', 'prod'].includes(process.env.VUE_APP_ENV)
3
+const plugins = [
4
+  [
5
+    'import',
6
+    {
7
+      libraryName: 'vant',
8
+      libraryDirectory: 'es',
9
+      style: true
10
+    },
11
+    'vant'
12
+  ]
13
+]
14
+// 去除 console.log
15
+if (IS_PROD) {
16
+  plugins.push('transform-remove-console')
17
+}
18
+
19
+module.exports = {
20
+  presets: [['@vue/cli-plugin-babel/preset', { useBuiltIns: 'usage', corejs: 3 }]],
21
+  plugins
22
+}

+ 41
- 0
package.json Bestand weergeven

@@ -0,0 +1,41 @@
1
+{
2
+  "name": "vue-h5-template",
3
+  "version": "2.1.0",
4
+  "description": "A vue h5 template with Vant UI",
5
+  "author": "Sunnie <sunniejs@163.com>",
6
+  "private": true,
7
+  "scripts": {
8
+    "serve": "vue-cli-service serve --open",
9
+    "build": "vue-cli-service build",
10
+    "stage": "vue-cli-service build --mode staging",
11
+    "lint": "vue-cli-service lint"
12
+  },
13
+  "dependencies": {
14
+    "animate.css": "^4.1.1",
15
+    "core-js": "^3.6.4",
16
+    "lib-flexible": "^0.3.2",
17
+    "lodash": "^4.17.15",
18
+    "regenerator-runtime": "^0.13.5",
19
+    "swiper": "6.8.4",
20
+    "vue": "^2.6.11",
21
+    "vue-router": "^3.2.0"
22
+  },
23
+  "devDependencies": {
24
+    "@vue/cli-plugin-babel": "~4.5.0",
25
+    "@vue/cli-plugin-eslint": "~4.5.0",
26
+    "@vue/cli-plugin-router": "~4.5.0",
27
+    "@vue/cli-plugin-vuex": "~4.5.0",
28
+    "@vue/cli-service": "~4.5.0",
29
+    "babel-eslint": "^10.1.0",
30
+    "babel-plugin-import": "^1.13.0",
31
+    "babel-plugin-transform-remove-console": "^6.9.4",
32
+    "eslint": "^6.7.2",
33
+    "eslint-plugin-vue": "^6.2.2",
34
+    "node-sass": "^4.14.1",
35
+    "postcss-pxtorem": "^5.1.1",
36
+    "sass-loader": "^8.0.2",
37
+    "script-ext-html-webpack-plugin": "^2.1.4",
38
+    "vue-template-compiler": "^2.6.11",
39
+    "webpack-bundle-analyzer": "^3.8.0"
40
+  }
41
+}

BIN
public/favicon.ico Bestand weergeven


+ 27
- 0
public/index.html Bestand weergeven

@@ -0,0 +1,27 @@
1
+<!DOCTYPE html>
2
+<html>
3
+  <head>
4
+    <meta charset="utf-8">
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">
7
+    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
8
+    <!-- <% for (var i in
9
+      htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.css) { %>
10
+      <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="preload" as="style" />
11
+      <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet" />
12
+	  <% } %> -->
13
+    <title><%= webpackConfig.name %></title>
14
+  </head>
15
+  <body>
16
+    <noscript>
17
+      <strong>We're sorry but <%= webpackConfig.name %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
18
+    </noscript>
19
+    <div id="app"></div>
20
+    <!-- 使用CDN加速的JS文件,配置在vue.config.js下 -->
21
+    <!-- <% for (var i in
22
+      htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.js) { %>
23
+      <script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
24
+    <% } %> -->
25
+    <!-- built files will be auto injected -->
26
+  </body>
27
+</html>

+ 11
- 0
src/App.vue Bestand weergeven

@@ -0,0 +1,11 @@
1
+<template>
2
+  <div id="app">
3
+    <router-view />
4
+  </div>
5
+</template>
6
+<script>
7
+export default {
8
+  name: 'App'
9
+}
10
+</script>
11
+<style lang="scss"></style>

+ 12
- 0
src/assets/css/index.scss Bestand weergeven

@@ -0,0 +1,12 @@
1
+@import './variables.scss';
2
+@import './mixin.scss';
3
+
4
+html,
5
+body, #app {
6
+  color: #333333;
7
+  font-family: Arial, Helvetica, 'STHeiti STXihei', 'Microsoft YaHei', Tohoma, sans-serif;
8
+  background-color: $background-color;
9
+  height: 100%;
10
+  margin: 0;
11
+  padding: 0;
12
+}

+ 36
- 0
src/assets/css/mixin.scss Bestand weergeven

@@ -0,0 +1,36 @@
1
+// mixin
2
+// 清除浮动
3
+@mixin clearfix {
4
+  &:after {
5
+    content: "";
6
+    display: table;
7
+    clear: both;
8
+  }
9
+}
10
+ 
11
+// 多行隐藏
12
+@mixin textoverflow($clamp:1) {
13
+  display: block;
14
+  overflow: hidden;
15
+  text-overflow: ellipsis;
16
+  -o-text-overflow: ellipsis;
17
+  display: -webkit-box;
18
+  -webkit-line-clamp: $clamp;
19
+  /*! autoprefixer: ignore next */
20
+  -webkit-box-orient: vertical;
21
+}
22
+
23
+//flex box
24
+@mixin flexbox($jc:space-between, $ai:center, $fd:row, $fw:nowrap) {
25
+  display: flex;
26
+  display: -webkit-flex;
27
+  flex: 1;
28
+  justify-content: $jc;
29
+  -webkit-justify-content: $jc;
30
+  align-items: $ai;
31
+  -webkit-align-items: $ai;
32
+  flex-direction: $fd;
33
+  -webkit-flex-direction: $fd;
34
+  flex-wrap: $fw;
35
+  -webkit-flex-wrap: $fw;
36
+}

+ 3
- 0
src/assets/css/variables.scss Bestand weergeven

@@ -0,0 +1,3 @@
1
+ 
2
+// variables
3
+$background-color: #f8f8f8;

BIN
src/assets/images/index.png Bestand weergeven


BIN
src/assets/images/toLeft.png Bestand weergeven


BIN
src/assets/images/按钮-1.png Bestand weergeven


BIN
src/assets/images/按钮-2.png Bestand weergeven


BIN
src/assets/logo.png Bestand weergeven


+ 38
- 0
src/components/Swiper/index.vue Bestand weergeven

@@ -0,0 +1,38 @@
1
+<template>
2
+  <div class="swiper-container">
3
+    <div class="swiper-wrapper">
4
+      <slot></slot>
5
+    </div>
6
+  </div>
7
+</template>
8
+
9
+<script>
10
+import Swiper from 'swiper'
11
+
12
+export default {
13
+  name: 'Swiper',
14
+  props: {
15
+    options: {
16
+      type: Object,
17
+      default: () => ({})
18
+    }
19
+  },
20
+  data() {
21
+    return {
22
+      swiper: undefined
23
+    }
24
+  },
25
+  mounted() {
26
+    this.$nextTick(() => {
27
+      this.swiper = new Swiper(this.$el, this.options)
28
+    })
29
+  }
30
+}
31
+</script>
32
+
33
+<style>
34
+.swiper-container {
35
+  width: 100%;
36
+  height: 100%;
37
+}
38
+</style>

+ 32
- 0
src/components/SwiperSlide/index.vue Bestand weergeven

@@ -0,0 +1,32 @@
1
+<template>
2
+  <div class="swiper-slide">
3
+    <slot></slot>
4
+  </div>
5
+</template>
6
+
7
+<script>
8
+export default {
9
+  name: 'SwiperSlide'
10
+}
11
+</script>
12
+
13
+<style>
14
+.swiper-slide {
15
+  text-align: center;
16
+  background: #fff;
17
+
18
+  /* Center slide text vertically */
19
+  display: -webkit-box;
20
+  display: -ms-flexbox;
21
+  display: -webkit-flex;
22
+  display: flex;
23
+  -webkit-box-pack: center;
24
+  -ms-flex-pack: center;
25
+  -webkit-justify-content: center;
26
+  justify-content: center;
27
+  -webkit-box-align: center;
28
+  -ms-flex-align: center;
29
+  -webkit-align-items: center;
30
+  align-items: center;
31
+}
32
+</style>

+ 9
- 0
src/config/env.development.js Bestand weergeven

@@ -0,0 +1,9 @@
1
+// 本地环境配置
2
+module.exports = {
3
+  title: 'vue-h5-template',
4
+  baseUrl: 'http://localhost:9018', // 项目地址
5
+  baseApi: 'https://test.xxx.com/api', // 本地api请求地址,注意:如果你使用了代理,请设置成'/'
6
+  APPID: 'xxx',
7
+  APPSECRET: 'xxx',
8
+  $cdn: 'https://www.sunniejs.cn/static'
9
+}

+ 9
- 0
src/config/env.production.js Bestand weergeven

@@ -0,0 +1,9 @@
1
+// 正式
2
+module.exports = {
3
+  title: 'vue-h5-template',
4
+  baseUrl: 'https://www.xxx.com/', // 正式项目地址
5
+  baseApi: 'https://www.xxx.com/api', // 正式api请求地址
6
+  APPID: 'xxx',
7
+  APPSECRET: 'xxx',
8
+  $cdn: 'https://www.sunniejs.cn/static'
9
+}

+ 8
- 0
src/config/env.staging.js Bestand weergeven

@@ -0,0 +1,8 @@
1
+module.exports = {
2
+  title: 'vue-h5-template',
3
+  baseUrl: 'https://test.xxx.com', // 测试项目地址
4
+  baseApi: 'https://test.xxx.com/api', // 测试api请求地址
5
+  APPID: 'xxx',
6
+  APPSECRET: 'xxx',
7
+  $cdn: 'https://www.sunniejs.cn/static'
8
+}

+ 4
- 0
src/config/index.js Bestand weergeven

@@ -0,0 +1,4 @@
1
+// 根据环境引入不同配置 process.env.VUE_APP_ENV
2
+const environment = process.env.VUE_APP_ENV || 'production'
3
+const config = require('./env.' + environment)
4
+module.exports = config

+ 37
- 0
src/filters/filter.js Bestand weergeven

@@ -0,0 +1,37 @@
1
+/**
2
+ *格式化时间
3
+ *yyyy-MM-dd hh:mm:ss
4
+ */
5
+export function formatDate(time, fmt) {
6
+  if (time === undefined || '') {
7
+    return
8
+  }
9
+  const date = new Date(time)
10
+  if (/(y+)/.test(fmt)) {
11
+    fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))
12
+  }
13
+  const o = {
14
+    'M+': date.getMonth() + 1,
15
+    'd+': date.getDate(),
16
+    'h+': date.getHours(),
17
+    'm+': date.getMinutes(),
18
+    's+': date.getSeconds()
19
+  }
20
+  for (const k in o) {
21
+    if (new RegExp(`(${k})`).test(fmt)) {
22
+      const str = o[k] + ''
23
+      fmt = fmt.replace(RegExp.$1, RegExp.$1.length === 1 ? str : padLeftZero(str))
24
+    }
25
+  }
26
+  return fmt
27
+}
28
+
29
+function padLeftZero(str) {
30
+  return ('00' + str).substr(str.length)
31
+}
32
+/*
33
+ * 隐藏用户手机号中间四位
34
+ */
35
+export function hidePhone(phone) {
36
+  return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
37
+}

+ 7
- 0
src/filters/index.js Bestand weergeven

@@ -0,0 +1,7 @@
1
+import Vue from 'vue'
2
+import * as filter from './filter'
3
+
4
+Object.keys(filter).forEach(k => Vue.filter(k, filter[k]))
5
+
6
+Vue.prototype.$formatDate = Vue.filter('formatDate')
7
+Vue.prototype.$hidePhone = Vue.filter('hidePhone')

+ 13
- 0
src/layouts/index.vue Bestand weergeven

@@ -0,0 +1,13 @@
1
+<template>
2
+  <router-view></router-view>
3
+</template>
4
+
5
+<script>
6
+
7
+export default {
8
+  name: 'AppLayout',
9
+  data() {
10
+    return {}
11
+  }
12
+}
13
+</script>

+ 34
- 0
src/main.js Bestand weergeven

@@ -0,0 +1,34 @@
1
+// 兼容 IE
2
+// https://github.com/zloirock/core-js/blob/master/docs/2019-03-19-core-js-3-babel-and-a-look-into-the-future.md#babelpolyfill
3
+import 'core-js/stable'
4
+import 'regenerator-runtime/runtime'
5
+
6
+import Vue from 'vue'
7
+import Swiper from '@/components/Swiper'
8
+import SwiperSlide from '@/components/SwiperSlide'
9
+import App from './App.vue'
10
+import router from './router'
11
+
12
+// 设置 js中可以访问 $cdn
13
+import { $cdn } from '@/config'
14
+Vue.prototype.$cdn = $cdn
15
+
16
+// 引入全局样式
17
+import 'animate.css'
18
+import 'swiper/swiper-bundle.css'
19
+import '@/assets/css/index.scss'
20
+// 移动端适配
21
+import 'lib-flexible/flexible.js'
22
+
23
+// filters
24
+import './filters'
25
+Vue.config.productionTip = false
26
+
27
+Vue.component('swiper', Swiper)
28
+Vue.component('swiper-slide', SwiperSlide)
29
+
30
+new Vue({
31
+  el: '#app',
32
+  router,
33
+  render: h => h(App)
34
+})

+ 30
- 0
src/router/index.js Bestand weergeven

@@ -0,0 +1,30 @@
1
+import Vue from 'vue'
2
+import Router from 'vue-router'
3
+import { constantRouterMap } from './router.config.js'
4
+
5
+// hack router push callback
6
+const originalPush = Router.prototype.push
7
+Router.prototype.push = function push(location, onResolve, onReject) {
8
+  if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject)
9
+  return originalPush.call(this, location).catch(err => err)
10
+}
11
+
12
+Vue.use(Router)
13
+
14
+const createRouter = () =>
15
+  new Router({
16
+    // mode: 'history', // 如果你是 history模式 需要配置vue.config.js publicPath
17
+    // base: process.env.BASE_URL,
18
+    scrollBehavior: () => ({ y: 0 }),
19
+    routes: constantRouterMap
20
+  })
21
+
22
+const router = createRouter()
23
+
24
+// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
25
+export function resetRouter() {
26
+  const newRouter = createRouter()
27
+  router.matcher = newRouter.matcher // reset router
28
+}
29
+
30
+export default router

+ 29
- 0
src/router/router.config.js Bestand weergeven

@@ -0,0 +1,29 @@
1
+/**
2
+ * 基础路由
3
+ * @type { *[] }
4
+ */
5
+export const constantRouterMap = [
6
+  {
7
+    path: '/',
8
+    component: () => import('@/layouts/index'),
9
+    redirect: '/index',
10
+    meta: {
11
+      title: '首页',
12
+      keepAlive: false
13
+    },
14
+    children: [
15
+      {
16
+        path: '/index',
17
+        name: 'Index',
18
+        component: () => import('@/views/index'),
19
+        meta: { title: '首页', keepAlive: false }
20
+      },
21
+      {
22
+        path: '/apartment',
23
+        name: 'Apartment',
24
+        component: () => import('@/views/apartment'),
25
+        meta: { title: '公寓', keepAlive: false }
26
+      }
27
+    ]
28
+  }
29
+]

+ 110
- 0
src/utils/index.js Bestand weergeven

@@ -0,0 +1,110 @@
1
+/**
2
+ * Created by PanJiaChen on 16/11/18.
3
+ */
4
+
5
+/**
6
+ * Parse the time to string
7
+ * @param {(Object|string|number)} time
8
+ * @param {string} cFormat
9
+ * @returns {string}
10
+ */
11
+export function parseTime(time, cFormat) {
12
+  if (arguments.length === 0) {
13
+    return null
14
+  }
15
+  const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
16
+  let date
17
+  if (typeof time === 'object') {
18
+    date = time
19
+  } else {
20
+    if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
21
+      time = parseInt(time)
22
+    }
23
+    if ((typeof time === 'number') && (time.toString().length === 10)) {
24
+      time = time * 1000
25
+    }
26
+    date = new Date(time)
27
+  }
28
+  const formatObj = {
29
+    y: date.getFullYear(),
30
+    m: date.getMonth() + 1,
31
+    d: date.getDate(),
32
+    h: date.getHours(),
33
+    i: date.getMinutes(),
34
+    s: date.getSeconds(),
35
+    a: date.getDay()
36
+  }
37
+  const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
38
+    let value = formatObj[key]
39
+    // Note: getDay() returns 0 on Sunday
40
+    if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] }
41
+    if (result.length > 0 && value < 10) {
42
+      value = '0' + value
43
+    }
44
+    return value || 0
45
+  })
46
+  return time_str
47
+}
48
+
49
+/**
50
+ * @param {number} time
51
+ * @param {string} option
52
+ * @returns {string}
53
+ */
54
+export function formatTime(time, option) {
55
+  if (('' + time).length === 10) {
56
+    time = parseInt(time) * 1000
57
+  } else {
58
+    time = +time
59
+  }
60
+  const d = new Date(time)
61
+  const now = Date.now()
62
+
63
+  const diff = (now - d) / 1000
64
+
65
+  if (diff < 30) {
66
+    return '刚刚'
67
+  } else if (diff < 3600) {
68
+    // less 1 hour
69
+    return Math.ceil(diff / 60) + '分钟前'
70
+  } else if (diff < 3600 * 24) {
71
+    return Math.ceil(diff / 3600) + '小时前'
72
+  } else if (diff < 3600 * 24 * 2) {
73
+    return '1天前'
74
+  }
75
+  if (option) {
76
+    return parseTime(time, option)
77
+  } else {
78
+    return (
79
+      d.getMonth() +
80
+      1 +
81
+      '月' +
82
+      d.getDate() +
83
+      '日' +
84
+      d.getHours() +
85
+      '时' +
86
+      d.getMinutes() +
87
+      '分'
88
+    )
89
+  }
90
+}
91
+
92
+/**
93
+ * @param {string} url
94
+ * @returns {Object}
95
+ */
96
+export function param2Obj(url) {
97
+  const search = url.split('?')[1]
98
+  if (!search) {
99
+    return {}
100
+  }
101
+  return JSON.parse(
102
+    '{"' +
103
+      decodeURIComponent(search)
104
+        .replace(/"/g, '\\"')
105
+        .replace(/&/g, '","')
106
+        .replace(/=/g, '":"')
107
+        .replace(/\+/g, ' ') +
108
+      '"}'
109
+  )
110
+}

+ 20
- 0
src/utils/validate.js Bestand weergeven

@@ -0,0 +1,20 @@
1
+/**
2
+ * Created by Sunnie on 19/06/04.
3
+ */
4
+
5
+/**
6
+ * @param {string} path
7
+ * @returns {Boolean}
8
+ */
9
+export function isExternal(path) {
10
+  return /^(https?:|mailto:|tel:)/.test(path)
11
+}
12
+
13
+/**
14
+ * @param {string} str
15
+ * @returns {Boolean}
16
+ */
17
+export function validUsername(str) {
18
+  const valid_map = ['admin', 'editor']
19
+  return valid_map.indexOf(str.trim()) >= 0
20
+}

+ 84
- 0
src/views/apartment.vue Bestand weergeven

@@ -0,0 +1,84 @@
1
+<!-- home -->
2
+<template>
3
+  <swiper>
4
+    <swiper-slide v-for="item in list" :key="item.image">
5
+      <pure-image :bg-color="item.color" :bg-image="item.image"></pure-image>
6
+    </swiper-slide>
7
+  </swiper>
8
+</template>
9
+
10
+<script>
11
+
12
+export default {
13
+  components: {
14
+    PureImage: () => import('./components/PureImage.vue')
15
+  },
16
+  data() {
17
+    return {
18
+      list: [
19
+        {
20
+          image: require('@/assets/images/apartment/1.jpg'),
21
+          color: '#f0f0f0'
22
+        },
23
+        {
24
+          image: require('@/assets/images/apartment/2-2.jpg'),
25
+          color: '#f0f0f0'
26
+        },
27
+        {
28
+          image: require('@/assets/images/apartment/3-1.jpg'),
29
+          color: '#f0f0f0'
30
+        },
31
+        {
32
+          image: require('@/assets/images/apartment/4-1.jpg'),
33
+          color: '#f0f0f0'
34
+        },
35
+        {
36
+          image: require('@/assets/images/apartment/5-1.jpg'),
37
+          color: '#f0f0f0'
38
+        },
39
+        {
40
+          image: require('@/assets/images/apartment/6-1.jpg'),
41
+          color: '#f0f0f0'
42
+        },
43
+        {
44
+          image: require('@/assets/images/apartment/7-1.jpg'),
45
+          color: '#f0f0f0'
46
+        },
47
+        {
48
+          image: require('@/assets/images/apartment/7-2.jpg'),
49
+          color: '#f0f0f0'
50
+        },
51
+        {
52
+          image: require('@/assets/images/apartment/7-3.jpg'),
53
+          color: '#f0f0f0'
54
+        },
55
+        {
56
+          image: require('@/assets/images/apartment/7-4.jpg'),
57
+          color: '#f0f0f0'
58
+        },
59
+        {
60
+          image: require('@/assets/images/apartment/7-5.jpg'),
61
+          color: '#f0f0f0'
62
+        },
63
+        {
64
+          image: require('@/assets/images/apartment/7-6.jpg'),
65
+          color: '#f0f0f0'
66
+        },
67
+        {
68
+          image: require('@/assets/images/apartment/7-7.jpg'),
69
+          color: '#f0f0f0'
70
+        },
71
+        {
72
+          image: require('@/assets/images/apartment/7-8.jpg'),
73
+          color: '#f0f0f0'
74
+        }
75
+      ]
76
+    }
77
+  },
78
+
79
+  methods: {}
80
+}
81
+</script>
82
+
83
+<style lang="scss" scoped>
84
+</style>

+ 116
- 0
src/views/components/IndexImage.vue Bestand weergeven

@@ -0,0 +1,116 @@
1
+<template>
2
+  <div class="index-image">
3
+    <img :src="image" class="bg-image" @load="loadBg" />
4
+    <div class="index-inner-swiper" :style="swiperItemStyle">
5
+      <swiper v-if="showSwiper">
6
+        <swiper-slide class="index-inner-slide">
7
+          <img :src="btn1" alt="" @click="$router.push({ name: 'Apartment' })">
8
+        </swiper-slide>
9
+        <swiper-slide class="index-inner-slide">
10
+          <img :src="btn2" alt="" @click="$router.push({ name: 'Office' })">
11
+        </swiper-slide>
12
+      </swiper>
13
+    </div>
14
+    <div class="index-pos-hander" :style="handerStyle">
15
+      <img :src="hander" alt="">
16
+    </div>
17
+  </div>
18
+</template>
19
+
20
+<script>
21
+
22
+export default {
23
+  name: 'IndexImage',
24
+  data() {
25
+    return {
26
+      image: require('@/assets/images/index.png'),
27
+      btn1: require('@/assets/images/按钮-1.png'),
28
+      btn2: require('@/assets/images/按钮-2.png'),
29
+      hander: require('@/assets/images/toLeft.png'),
30
+      showSwiper: false,
31
+      swiperItemStyle: {},
32
+      handerStyle: {}
33
+    }
34
+  },
35
+  methods: {
36
+    loadBg(e) {
37
+      const { offsetTop, width, height, naturalWidth, naturalHeight } = e.target
38
+
39
+      const widthRatio = width / naturalWidth
40
+      const heightRatio = height / naturalHeight
41
+
42
+      this.swiperItemStyle = {
43
+        width: `${636 * widthRatio}px`, // 636 是图片原始宽度
44
+        height: `${637 * heightRatio}px`, // 637 是图片原始高度
45
+        top: `${offsetTop + 1612 * heightRatio}px`,
46
+        left: `${352 * widthRatio}px`
47
+      }
48
+
49
+      this.handerStyle = {
50
+        top: `${offsetTop + 1800 * heightRatio}px`,
51
+        left: `${988 * widthRatio}px`
52
+      }
53
+
54
+      this.showSwiper = true
55
+    }
56
+  }
57
+}
58
+</script>
59
+
60
+<style lang="scss" scoped>
61
+.index-image {
62
+  position: relative;
63
+  background-color: #1d429d;
64
+  width: 100%;
65
+  height: 100%;
66
+  display: flex;
67
+  align-items: center;
68
+
69
+  .bg-image {
70
+    width: 100%;
71
+    pointer-events: none;
72
+    z-index: 100;
73
+  }
74
+
75
+  .index-inner-swiper {
76
+    position: absolute;
77
+
78
+    .index-inner-slide {
79
+      background: transparent;
80
+    }
81
+
82
+    img {
83
+      width: 100%;
84
+      height: 100%;
85
+    }
86
+  }
87
+
88
+  .index-pos-hander {
89
+    position: absolute;
90
+    z-index: 200;
91
+    width: 64px;
92
+    height: 56px;
93
+    animation: leftfade 2s infinite;
94
+
95
+    img {
96
+      width: 100%;
97
+      height: 100%;
98
+    }
99
+  }
100
+
101
+  @keyframes leftfade {
102
+    0% {
103
+      opacity: 0;
104
+      transform: translateX(0);
105
+    }
106
+    70% {
107
+      opacity: 1;
108
+      transform: translateX(-30%);
109
+    }
110
+    100% {
111
+      opacity: 0;
112
+      transform: translateX(-30%);
113
+    }
114
+  }
115
+}
116
+</style>

+ 36
- 0
src/views/components/PureImage.vue Bestand weergeven

@@ -0,0 +1,36 @@
1
+<template>
2
+  <div class="pure-image" :style="custStyle">
3
+    <p></p>
4
+  </div>
5
+</template>
6
+
7
+<script>
8
+export default {
9
+  name: 'PureImage',
10
+  props: {
11
+    bgColor: {
12
+      type: String,
13
+      default: '#f0f0f0'
14
+    },
15
+    bgImage: String
16
+  },
17
+  computed: {
18
+    custStyle() {
19
+      return {
20
+        backgroundColor: this.bgColor,
21
+        backgroundImage: `url('${this.bgImage}')`
22
+      }
23
+    }
24
+  }
25
+}
26
+</script>
27
+
28
+<style lang="scss" scoped>
29
+.pure-image {
30
+  background-position: center;
31
+  background-size: 100%;
32
+  background-repeat: no-repeat;
33
+  width: 100%;
34
+  height: 100%;
35
+}
36
+</style>

+ 11
- 0
src/views/index.vue Bestand weergeven

@@ -0,0 +1,11 @@
1
+<template>
2
+  <index-image></index-image>
3
+</template>
4
+
5
+<script>
6
+export default {
7
+  components: {
8
+    IndexImage: () => import('./components/IndexImage.vue')
9
+  }
10
+}
11
+</script>

+ 3
- 0
src/views/office.vue Bestand weergeven

@@ -0,0 +1,3 @@
1
+<template>
2
+  <div></div>
3
+</template>

+ 180
- 0
vue.config.js Bestand weergeven

@@ -0,0 +1,180 @@
1
+'use strict'
2
+const path = require('path')
3
+const defaultSettings = require('./src/config/index.js')
4
+const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
5
+
6
+const resolve = dir => path.join(__dirname, dir)
7
+// page title
8
+const name = defaultSettings.title || 'vue mobile template'
9
+// 生产环境,测试和正式
10
+const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV)
11
+// externals
12
+// const externals = {
13
+//   vue: 'Vue',
14
+//   'vue-router': 'VueRouter',
15
+//   vuex: 'Vuex',
16
+//   vant: 'vant',
17
+//   axios: 'axios'
18
+// }
19
+// CDN外链,会插入到index.html中
20
+// const cdn = {
21
+//   // 开发环境
22
+//   dev: {
23
+//     css: [],
24
+//     js: []
25
+//   },
26
+//   // 生产环境
27
+//   build: {
28
+//     css: ['https://cdn.jsdelivr.net/npm/vant@2.4.7/lib/index.css'],
29
+//     js: [
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
+//   }
37
+// }
38
+
39
+module.exports = {
40
+  publicPath: './', // 署应用包时的基本 URL。 vue-router hash 模式使用
41
+  //  publicPath: '/app/', //署应用包时的基本 URL。  vue-router history模式使用
42
+  outputDir: 'dist', //  生产环境构建文件的目录
43
+  assetsDir: 'static', //  outputDir的静态资源(js、css、img、fonts)目录
44
+  lintOnSave: !IS_PROD,
45
+  productionSourceMap: false, // 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。
46
+  devServer: {
47
+    port: 9020, // 端口
48
+    open: false, // 启动后打开浏览器
49
+    overlay: {
50
+      //  当出现编译器错误或警告时,在浏览器中显示全屏覆盖层
51
+      warnings: false,
52
+      errors: true
53
+    }
54
+    // proxy: {
55
+    //   //配置跨域
56
+    //   '/api': {
57
+    //       target: "https://test.xxx.com",
58
+    //       // ws:true,
59
+    //       changOrigin:true,
60
+    //       pathRewrite:{
61
+    //           '^/api':'/'
62
+    //       }
63
+    //   }
64
+    // }
65
+  },
66
+  css: {
67
+    extract: IS_PROD, // 是否将组件中的 CSS 提取至一个独立的 CSS 文件中 (而不是动态注入到 JavaScript 中的 inline 代码)。
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/mixin.scss";
75
+          @import "assets/css/variables.scss";
76
+          $cdn: "${defaultSettings.$cdn}";
77
+          `
78
+      }
79
+    }
80
+  },
81
+  configureWebpack: config => {
82
+    config.name = name
83
+
84
+    // 为生产环境修改配置...
85
+    // if (IS_PROD) {
86
+    //   // externals
87
+    //   config.externals = externals
88
+    // }
89
+  },
90
+
91
+  chainWebpack: config => {
92
+    config.plugins.delete('preload') // TODO: need test
93
+    config.plugins.delete('prefetch') // TODO: need test
94
+
95
+    // 别名 alias
96
+    config.resolve.alias
97
+      .set('@', resolve('src'))
98
+      .set('assets', resolve('src/assets'))
99
+      .set('api', resolve('src/api'))
100
+      .set('views', resolve('src/views'))
101
+      .set('layouts', resolve('src/layouts'))
102
+      .set('components', resolve('src/components'))
103
+
104
+    /**
105
+     * 添加CDN参数到htmlWebpackPlugin配置中
106
+     */
107
+    // config.plugin('html').tap(args => {
108
+    //   if (IS_PROD) {
109
+    //     args[0].cdn = cdn.build
110
+    //   } else {
111
+    //     args[0].cdn = cdn.dev
112
+    //   }
113
+    //   return args
114
+    //  })
115
+
116
+    /**
117
+     * 设置保留空格
118
+     */
119
+    config.module
120
+      .rule('vue')
121
+      .use('vue-loader')
122
+      .loader('vue-loader')
123
+      .tap(options => {
124
+        options.compilerOptions.preserveWhitespace = true
125
+        return options
126
+      })
127
+      .end()
128
+    /**
129
+     * 打包分析
130
+     */
131
+    if (IS_PROD) {
132
+      config.plugin('webpack-report').use(BundleAnalyzerPlugin, [
133
+        {
134
+          analyzerMode: 'static'
135
+        }
136
+      ])
137
+    }
138
+    config
139
+      // https://webpack.js.org/configuration/devtool/#development
140
+      .when(!IS_PROD, config => config.devtool('cheap-source-map'))
141
+
142
+    config.when(IS_PROD, config => {
143
+      config
144
+        .plugin('ScriptExtHtmlWebpackPlugin')
145
+        .after('html')
146
+        .use('script-ext-html-webpack-plugin', [
147
+          {
148
+            // 将 runtime 作为内联引入不单独存在
149
+            inline: /runtime\..*\.js$/
150
+          }
151
+        ])
152
+        .end()
153
+      config.optimization.splitChunks({
154
+        chunks: 'all',
155
+        cacheGroups: {
156
+          // cacheGroups 下可以可以配置多个组,每个组根据test设置条件,符合test条件的模块
157
+          commons: {
158
+            name: 'chunk-commons',
159
+            test: resolve('src/components'),
160
+            minChunks: 3, //  被至少用三次以上打包分离
161
+            priority: 5, // 优先级
162
+            reuseExistingChunk: true // 表示是否使用已有的 chunk,如果为 true 则表示如果当前的 chunk 包含的模块已经被抽取出去了,那么将不会重新生成新的。
163
+          },
164
+          node_vendors: {
165
+            name: 'chunk-libs',
166
+            chunks: 'initial', // 只打包初始时依赖的第三方
167
+            test: /[\\/]node_modules[\\/]/,
168
+            priority: 10
169
+          },
170
+          vantUI: {
171
+            name: 'chunk-vantUI', // 单独将 vantUI 拆包
172
+            priority: 20, // 数字大权重到,满足多个 cacheGroups 的条件时候分到权重高的
173
+            test: /[\\/]node_modules[\\/]_?vant(.*)/
174
+          }
175
+        }
176
+      })
177
+      config.optimization.runtimeChunk('single')
178
+    })
179
+  }
180
+}