├── .editorconfig
├── .env.development
├── .env.production
├── .gitignore
├── .prettierrc
├── README.md
├── babel.config.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
├── favicon.ico
└── index.html
├── src
├── App.vue
├── api
│ └── home.js
├── assets
│ ├── images
│ │ └── qrcode_for_gh_5ddc08b3a52c_344.jpg
│ ├── logo.png
│ └── scss
│ │ ├── index.scss
│ │ ├── mixin.scss
│ │ ├── reset.scss
│ │ └── variables.scss
├── components
│ ├── HelloWorld.vue
│ └── TabBar
│ │ └── TabBar.vue
├── config
│ ├── env.development.js
│ ├── env.production.js
│ └── index.js
├── layout
│ └── index.vue
├── main.js
├── plugins
│ └── vant.js
├── router
│ └── index.js
├── store
│ └── index.js
├── util
│ ├── index.js
│ └── request.js
└── views
│ ├── About.vue
│ ├── Detail.vue
│ └── Home.vue
└── vue.config.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{js,jsx,ts,tsx,vue}]
2 | indent_style = space
3 | indent_size = 2
4 | trim_trailing_whitespace = true
5 | insert_final_newline = true
6 |
--------------------------------------------------------------------------------
/.env.development:
--------------------------------------------------------------------------------
1 | NODE_ENV='development'
2 | # must start with VUE_APP_
3 | VUE_APP_ENV = 'development'
4 |
--------------------------------------------------------------------------------
/.env.production:
--------------------------------------------------------------------------------
1 | NODE_ENV='production'
2 | # must start with VUE_APP_
3 | VUE_APP_ENV = 'production'
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
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 | pnpm-debug.log*
15 |
16 | # Editor directories and files
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw?
24 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
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 | }
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue3.0-h5-template
2 | - 基于vue3.0全家桶 + vant3.0 +scss + rem适配方案 + axios封装,构建H5模板脚手架
3 |
4 | ### 版本要求
5 | - `vue cli`版本需要node 8.9 或者更高的版本。或者你可以使用`nvm`来切换node版本
6 | - 本案例中,node版本的是12.15.0
7 |
8 | ### 启动项目
9 | ```
10 | git clone https://github.com/MrZHLF/vue3.0-vant-h5.git
11 |
12 | cd vue3.0-vant-h5
13 |
14 | npm install 或者使用 cnpm 切换到淘宝镜像
15 |
16 | cnpm run serve
17 | ```
18 |
19 | ### 简介
20 | - 关注公众号: 回复 “加群”,即可加入前端交流群 或者搜索 ``` 攻程狮小周 ```
21 | - 项目博客地址 ```https://blog.csdn.net/Govern66/article/details/110178890```
22 |
23 |
24 |
25 |
26 | ### 目录
27 |
28 | ```
29 | |———— public index.html文件
30 | |———— src
31 | | |———— api #api接口请求
32 | | |———— assets #资源目录
33 | | |———— components #公共组件封装
34 | | |———— config #环境变量配置
35 | | |———— layout #主目录
36 | | |———— router #路由
37 | | |———— store #状态管理
38 | | |———— util #工具类
39 | | |———— views #组件以及页面文件目录
40 | | |———— App.vue #项目入口文件
41 | | |———— main.js #项目核心文件
42 | |———— .env.development #本地环境配置
43 | |———— .env.production.js #正式环境配置
44 | |———— postcss.config #PostCSS 配置
45 | |———— babel.config.js #babel常用配置
46 | |———— vue.config.js #vue常用配置项
47 | |———— package.json #项目配置文件
48 | |———— README.md #项目的说明文档,markdown 格式
49 | ```
50 | ### ✅ 路由 vue-router
51 | vue3.0和vue2.0路配置大差不差,但是在页面获取name,path,params,query需要单独处理一下
52 |
53 | ```javascript
54 | import { createRouter, createWebHistory } from 'vue-router'
55 | const routes = [
56 | {
57 | path: '/',
58 | name: 'Home',
59 | component: () => import ('./../layout/index.vue'),
60 | redirect: '/home',
61 | meta: {
62 | title: '首页',
63 | keepAlive:false
64 | },
65 | children: [
66 | {
67 | path: '/home',
68 | name: 'Home',
69 | component: () => import('@/views/Home.vue')
70 | },
71 | {
72 | path: '/about',
73 | name: 'About',
74 | component: () => import('@/views/About.vue')
75 | },
76 | {
77 | path: '/detail/:id',
78 | name: 'Detail',
79 | component: () => import('@/views/Detail.vue')
80 | }
81 | ]
82 | }
83 | ]
84 |
85 | const router = createRouter({
86 | history: createWebHistory(process.env.BASE_URL),
87 | routes
88 | })
89 |
90 | export default router
91 |
92 | ```
93 | #### 页面中获取params参数
94 | - useRoute 获取当前的路由信息,包含了当前 URL 解析得到的信息。包含当前的路径,参数,query对象等。
95 | - useRoute 是全局路由的实例,是router构造方法的实例。
96 | ``` javascript
97 | import { ref } from 'vue'
98 | import { useRoute,useRoute } from 'vue-router'
99 | export default {
100 | setup () {
101 | const route = useRoute()
102 | let currentId = ref(0)
103 | currentId = route.params.id //路由跳转id
104 | return {
105 | currentId
106 | }
107 | }
108 | }
109 | ```
110 |
111 | ### ✅ vuex状态管理
112 | - vuex用法差不大,只是使用的时候,需要引入一下
113 |
114 | `main.js` 引入
115 |
116 | ```javascript
117 | import { createApp } from 'vue'
118 | import App from './App.vue'
119 | import store from './store'
120 | createApp(App).use(store).mount('#app')
121 |
122 | ```
123 |
124 | store文件
125 | ``` javascript
126 | import { createStore } from 'vuex'
127 |
128 | export default createStore({
129 | state: {
130 | userNmae: "vue3.0开发H5模板"
131 | },
132 | mutations: {
133 | getUserNmae(state,data) {
134 | state.userNmae = data
135 | }
136 | },
137 | actions: {
138 | },
139 | modules: {
140 | }
141 | })
142 |
143 | ```
144 |
145 | 使用
146 | ```html
147 |
148 |
149 | vuex按钮
150 |
151 |
152 |
172 |
173 | ```
174 |
175 | ### ✅ 配置多环境变量
176 |
177 | `package.json` 里的 `scripts` 配置 `serve` `build`,通过 `--mode xxx` 来执行不同环境
178 |
179 | - 通过 `npm run serve` 启动本地 , 执行 `development`
180 | - 通过 `npm run build` 打包正式 , 执行 `production`
181 |
182 | ```javascript
183 | "scripts": {
184 | "serve": "vue-cli-service serve --open",
185 | "build": "vue-cli-service build",
186 | }
187 | ```
188 |
189 | ##### 配置介绍
190 |
191 | 以 `VUE_APP_` 开头的变量,在代码中可以通过 `process.env.VUE_APP_` 访问。
192 | 比如,`VUE_APP_ENV = 'development'` 通过`process.env.VUE_APP_ENV` 访问。
193 | 除了 `VUE_APP_*` 变量之外,在你的应用代码中始终可用的还有两个特殊的变量`NODE_ENV` 和`BASE_URL`
194 |
195 | 在项目根目录中新建`.env.*`
196 |
197 | - .env.development 本地开发环境配置
198 |
199 | ```bash
200 | NODE_ENV='development'
201 | # must start with VUE_APP_
202 | VUE_APP_ENV = 'development'
203 |
204 | ```
205 |
206 | - .env.production 正式环境配置
207 |
208 | ```bash
209 | NODE_ENV='production'
210 | # must start with VUE_APP_
211 | VUE_APP_ENV = 'production'
212 | ```
213 |
214 | 这里我们并没有定义很多变量,只定义了基础的 VUE_APP_ENV `development``production`
215 | 变量我们统一在 `src/config/env.*.js` 里进行管理。
216 |
217 | 这里有个问题,既然这里有了根据不同环境设置变量的文件,为什么还要去 config 下新建三个对应的文件呢?
218 | **修改起来方便,不需要重启项目,符合开发习惯。**
219 |
220 | config/index.js
221 |
222 | ```javascript
223 | // 根据环境引入不同配置 process.env.NODE_ENV
224 | const config = require('./env.' + process.env.VUE_APP_ENV)
225 | module.exports = config
226 | ```
227 |
228 | 配置对应环境的变量,拿本地环境文件 `env.development.js` 举例,用户可以根据需求修改
229 |
230 | ```javascript
231 | // 本地环境配置
232 | module.exports = {
233 | title: 'vue3.0-h5',
234 | baseApi: 'https://test.xxx.com/api', // 本地api请求地址
235 | }
236 | ```
237 |
238 | 根据环境不同,变量就会不同了
239 |
240 | ```javascript
241 | // 根据环境不同引入不同baseApi地址
242 | import { baseApi } from '@/config'
243 | console.log(baseApi)
244 | ```
245 |
246 | ### ✅ rem 适配方案
247 |
248 | 不用担心,项目已经配置好了 `rem` 适配, 下面仅做介绍:
249 |
250 | Vant 中的样式默认使用`px`作为单位,如果需要使用`rem`单位,推荐使用以下两个工具:
251 |
252 | - [postcss-pxtorem](https://github.com/cuth/postcss-pxtorem) 是一款 `postcss` 插件,用于将单位转化为 `rem`
253 | - [lib-flexible](https://github.com/amfe/lib-flexible) 用于设置 `rem` 基准值
254 |
255 | ##### PostCSS 配置
256 |
257 | 下面提供了一份基本的 `postcss` 配置,可以在此配置的基础上根据项目需求进行修改
258 |
259 | ```javascript
260 | // https://github.com/michael-ciniawsky/postcss-load-config
261 | module.exports = {
262 | plugins: {
263 | autoprefixer: {
264 | overrideBrowserslist: ['Android 4.1', 'iOS 7.1', 'Chrome > 31', 'ff > 31', 'ie >= 8']
265 | },
266 | 'postcss-pxtorem': {
267 | rootValue: 37.5,
268 | propList:['*'],
269 | selectorBlackList :['html'],
270 | minPixelValue :1.5,
271 | mediaQuery:false,
272 | exclude:'common',
273 | }
274 | }
275 | }
276 | ```
277 |
278 | ### ✅ VantUI 组件按需加载
279 | - babel-plugin-import 是一款 babel 插件,它会在编译过程中将 import 的写法自动转换为按需引入的方式。
280 |
281 | #### 安装插件
282 |
283 | ```bash
284 | npm i babel-plugin-import -D
285 | ```
286 |
287 | 在`babel.config.js` 设置
288 |
289 | ```javascript
290 | // 对于使用 babel7 的用户,可以在 babel.config.js 中配置
291 | const plugins = [
292 | [
293 | 'import',
294 | {
295 | libraryName: 'vant',
296 | libraryDirectory: 'es',
297 | style: true
298 | },
299 | 'vant'
300 | ]
301 | ]
302 | module.exports = {
303 | presets: [['@vue/cli-plugin-babel/preset']],
304 | plugins
305 | }
306 | ```
307 |
308 | #### 使用组件
309 |
310 | 用哪个引入哪个,无需在页面里重复引用
311 |
312 | ```javascript
313 | // 按需全局引入 vant组件
314 | import { createApp } from 'vue';
315 | import { Button } from 'vant';
316 |
317 | const app = createApp();
318 | app.use(Button);
319 | ```
320 |
321 | ### ✅ Sass 全局样式
322 | - 创建vue项目的时候,选择SCSS
323 | ```html
324 |
327 |
328 |
331 | ```
332 | #### scss资源文件目录
333 | ```bash
334 | ├── assets
335 | │ ├── scss
336 | │ │ ├── index.scss # 全局通用样式
337 | │ │ ├── mixin.scss # 全局mixin
338 | │ │ └── variables.scss # 全局变量
339 | | | └── reset.scss # 全局重置样式
340 | ```
341 |
342 |
343 | ### ✅ Axios 封装及接口管理
344 |
345 | `utils/request.js` 封装 axios ,开发者需要根据后台接口做修改。
346 |
347 | - `service.interceptors.request.use` 里可以设置请求头,比如设置 `token`
348 | - `config.hideloading` 是在 api 文件夹下的接口参数里设置,下文会讲
349 |
350 | ```javascript
351 | import axios from 'axios'
352 | import { baseApi } from './../config'
353 |
354 |
355 |
356 | const service = axios.create({
357 | baseURL: baseApi,
358 | withCredentials: false,
359 | timeout: 5000
360 | })
361 |
362 | // request拦截器 request interceptor
363 | service.interceptors.request.use(config => {
364 | return config
365 | },error => {
366 | console.log(error);
367 | return Promise.reject(error)
368 | })
369 |
370 |
371 | // respone拦截器
372 | service.interceptors.response.use(response => {
373 | const res = response.data
374 | if(res.status && res.status !== 200) {
375 | return Promise.reject(res)
376 | } else {
377 | return Promise.resolve(res)
378 | }
379 | },error =>{
380 | return Promise.reject(error)
381 | })
382 | export default service
383 | ```
384 |
385 | #### 接口管理
386 | 在`src/api` 文件夹下统一管理接口
387 | - 你可以建立多个模块对接接口, 比如 `home.js` 里是首页的接口这里讲解 `getUser.js`
388 | ```javascript
389 | import request from './../util/request.js'
390 |
391 | export function getUser() {
392 | return request({
393 | url: '/users',
394 | method: 'get'
395 | })
396 | }
397 | ```
398 |
399 | #### 如何调用
400 |
401 | ```javascript
402 | // 请求接口
403 | import { getUser } from '@/api/home'
404 |
405 | const params = { user: 'sunnie' }
406 | getUser().then((res) => {
407 |
408 | }).catch((error)=>{})
409 | ```
410 |
411 | ### ✅ Webpack 4 vue.config.js 基础配置
412 |
413 | 如果你的 `Vue Router` 模式是 hash
414 |
415 | ```javascript
416 | publicPath: './',
417 | ```
418 |
419 | ```javascript
420 | module.exports = {
421 | publicPath: './', // 署应用包时的基本 URL。 vue-router hash 模式使用
422 | // publicPath: '/app/', // 署应用包时的基本 URL。 vue-router history模式使用
423 | outputDir: 'dist', // 生产环境构建文件的目录
424 | assetsDir: 'static', // outputDir的静态资源(js、css、img、fonts)目录
425 | lintOnSave: !IS_PROD,
426 | productionSourceMap: false, // 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。
427 | devServer: {
428 | port: 9527, // 端口号
429 | open: false, // 启动后打开浏览器
430 | overlay: {
431 | // 当出现编译器错误或警告时,在浏览器中显示全屏覆盖层
432 | warnings: false,
433 | errors: true
434 | }
435 | // ...
436 | }
437 | }
438 | ```
439 |
440 | ### ✅ 配置 alias 别名
441 |
442 | ```javascript
443 | const path = require('path')
444 | const resolve = dir => path.join(__dirname, dir)
445 |
446 | module.exports = {
447 | chainWebpack:(config)=>{
448 | config.resolve.alias
449 | .set('@', resolve('src'))
450 | .set('assets', resolve('src/assets'))
451 | .set('api', resolve('src/api'))
452 | .set('views', resolve('src/views'))
453 | .set('components', resolve('src/components'))
454 | }
455 | }
456 | ```
457 |
458 | ### ✅ 配置 proxy 跨域
459 |
460 | 如果你的项目需要跨域设置,你需要打来 `vue.config.js` `proxy` 注释 并且配置相应参数
461 |
462 | **!!!注意:你还需要将 `src/config/env.development.js` 里的 `baseApi` 设置成 '/'**
463 |
464 | ```javascript
465 | module.exports = {
466 | devServer: {
467 | // ....
468 | proxy: {
469 | //配置跨域
470 | '/api': {
471 | target: 'https://test.xxx.com', // 接口的域名
472 | // ws: true, // 是否启用websockets
473 | changOrigin: true, // 开启代理,在本地创建一个虚拟服务端
474 | pathRewrite: {
475 | '^/api': '/'
476 | }
477 | }
478 | }
479 | }
480 | }
481 | ```
482 |
483 | ### ✅ 去掉 console.log
484 | ```javascript
485 | // 获取 VUE_APP_ENV 非 NODE_ENV,测试环境依然 console
486 | const IS_PROD = ['production', 'prod'].includes(process.env.VUE_APP_ENV)
487 | const plugins = [
488 | [
489 | 'import',
490 | {
491 | libraryName: 'vant',
492 | libraryDirectory: 'es',
493 | style: true
494 | },
495 | 'vant'
496 | ]
497 | ]
498 | // 去除 console.log
499 | if (IS_PROD) {
500 | plugins.push('transform-remove-console')
501 | }
502 |
503 | module.exports = {
504 | presets: [['@vue/cli-plugin-babel/preset', { useBuiltIns: 'entry' }]],
505 | plugins
506 | }
507 | ```
508 |
509 |
510 | 如果感觉可以,帮我点一下星星
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
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']],
21 | plugins
22 | }
23 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue3.0-h5-template",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint"
9 | },
10 | "dependencies": {
11 | "axios": "^0.21.0",
12 | "core-js": "^3.6.5",
13 | "vant": "^3.0.0-beta.10",
14 | "vue": "^3.0.0",
15 | "vue-router": "^4.0.0-0",
16 | "vuex": "^4.0.0-0"
17 | },
18 | "devDependencies": {
19 | "@vue/cli-plugin-babel": "~4.5.0",
20 | "@vue/cli-plugin-eslint": "~4.5.0",
21 | "@vue/cli-plugin-router": "~4.5.0",
22 | "@vue/cli-plugin-vuex": "~4.5.0",
23 | "@vue/cli-service": "~4.5.0",
24 | "@vue/compiler-sfc": "^3.0.0",
25 | "@vue/eslint-config-prettier": "^6.0.0",
26 | "@vue/eslint-config-standard": "^5.1.2",
27 | "babel-eslint": "^10.1.0",
28 | "babel-plugin-import": "^1.13.3",
29 | "babel-plugin-transform-remove-console": "^6.9.4",
30 | "eslint": "^6.7.2",
31 | "eslint-plugin-import": "^2.20.2",
32 | "eslint-plugin-node": "^11.1.0",
33 | "eslint-plugin-prettier": "^3.3.0",
34 | "eslint-plugin-promise": "^4.2.1",
35 | "eslint-plugin-standard": "^4.0.0",
36 | "eslint-plugin-vue": "^7.0.0-0",
37 | "lib-flexible": "^0.3.2",
38 | "node-sass": "^4.12.0",
39 | "postcss-pxtorem": "^5.1.1",
40 | "prettier": "^2.2.1",
41 | "sass-loader": "^8.0.2"
42 | },
43 | "eslintConfig": {
44 | "root": true,
45 | "env": {
46 | "node": true
47 | },
48 | "extends": [
49 | "plugin:vue/vue3-essential",
50 | "@vue/standard"
51 | ],
52 | "parserOptions": {
53 | "parser": "babel-eslint"
54 | },
55 | "rules": {}
56 | },
57 | "browserslist": [
58 | "> 1%",
59 | "last 2 versions",
60 | "not dead"
61 | ]
62 | }
63 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
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 :['html'],
11 | minPixelValue :1.5,
12 | mediaQuery:false,
13 | exclude:'common',
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MrZHLF/vue3.0-vant-h5/de56092233d9b74e30ecbed61433580918f51191/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 |
--------------------------------------------------------------------------------
/src/api/home.js:
--------------------------------------------------------------------------------
1 | import request from './../util/request.js'
2 |
3 | export function getUser() {
4 | return request({
5 | url: '/users',
6 | method: 'get'
7 | })
8 | }
--------------------------------------------------------------------------------
/src/assets/images/qrcode_for_gh_5ddc08b3a52c_344.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MrZHLF/vue3.0-vant-h5/de56092233d9b74e30ecbed61433580918f51191/src/assets/images/qrcode_for_gh_5ddc08b3a52c_344.jpg
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MrZHLF/vue3.0-vant-h5/de56092233d9b74e30ecbed61433580918f51191/src/assets/logo.png
--------------------------------------------------------------------------------
/src/assets/scss/index.scss:
--------------------------------------------------------------------------------
1 | @import './reset.scss';
2 | @import './mixin.scss';
3 | @import './variables.scss';
4 | html {
5 | @include root-font-size();
6 | }
7 |
--------------------------------------------------------------------------------
/src/assets/scss/mixin.scss:
--------------------------------------------------------------------------------
1 | // 清除浮动
2 | @mixin clearfix {
3 | &:after {
4 | content: "";
5 | display: table;
6 | clear: both;
7 | }
8 | }
9 |
10 | // 多行隐藏
11 | @mixin textoverflow($clamp:1) {
12 | display: block;
13 | overflow: hidden;
14 | text-overflow: ellipsis;
15 | -o-text-overflow: ellipsis;
16 | display: -webkit-box;
17 | -webkit-line-clamp: $clamp;
18 | /*! autoprefixer: ignore next */
19 | -webkit-box-orient: vertical;
20 | }
21 |
22 | //flex box
23 | @mixin flexbox($jc:space-between, $ai:center, $fd:row, $fw:nowrap) {
24 | display: flex;
25 | display: -webkit-flex;
26 | flex: 1;
27 | justify-content: $jc;
28 | -webkit-justify-content: $jc;
29 | align-items: $ai;
30 | -webkit-align-items: $ai;
31 | flex-direction: $fd;
32 | -webkit-flex-direction: $fd;
33 | flex-wrap: $fw;
34 | -webkit-flex-wrap: $fw;
35 | }
36 |
37 |
38 | /* 移动端页面设计稿宽度 */
39 | $design-width: 750;
40 | /* 移动端页面设计稿dpr基准值 */
41 | $design-dpr: 2;
42 | /* 将移动端页面分为10块 */
43 | $blocks: 10;
44 | /* 缩放所支持的设备最小宽度 */
45 | $min-device-width: 320px;
46 | /* 缩放所支持的设备最大宽度 */
47 | $max-device-width: 750px;
48 |
49 | /*
50 | rem与px对应关系,1rem代表html font-size值(为一块的宽度),$rem即为$px对应占多少块
51 |
52 | $px $rem
53 | ------------- === ------------
54 | $design-width $blocks
55 | */
56 |
57 | /* html根元素的font-size定义,简单地将页面分为$blocks块,方便计算 */
58 | @mixin root-font-size() {
59 | font-size: 100vw / $blocks;
60 |
61 | body {
62 | @include container-min-width();
63 | }
64 |
65 | /* 最小宽度定义 */
66 | @media screen and (max-width: $min-device-width) {
67 | font-size: $min-device-width / $blocks;
68 | }
69 |
70 | /* 最大宽度定义 */
71 | &[data-content-max] {
72 | body[data-content-max] {
73 | @include container-max-width();
74 | }
75 |
76 | @media screen and (min-width: $max-device-width) {
77 | font-size: $max-device-width / $blocks;
78 | }
79 | }
80 | }
81 |
82 | /* 单位px转化为rem */
83 | @function px2rem($px) {
84 | @return #{$px / $design-width * $blocks}rem;
85 | }
86 |
87 | /* 单位rem转化为px,可用于根据rem单位快速计算原px */
88 | @function rem2px($rem) {
89 | @return #{$rem / $blocks * $design-width}px;
90 | }
91 |
92 | /**
93 | * 实现固定宽高比
94 | * @param {string} $position: relative 定位方式
95 | * @param {string} $width: 100% 容器宽度
96 | * @param {string} $sub: null 容器的目标子元素
97 | * @param {number} $aspectX: 1 容器宽
98 | * @param {number} $aspectY: 1 容器高
99 | */
100 | @mixin aspect-ratio($position: relative,
101 | $width: 100%,
102 | $sub: null,
103 | $aspectX: 1,
104 | $aspectY: 1) {
105 | overflow: hidden;
106 | position: $position;
107 | padding-top: percentage($aspectY / $aspectX);
108 | width: $width;
109 | height: 0;
110 |
111 | @if $sub==null {
112 | $sub: "*";
113 | }
114 |
115 | &>#{$sub} {
116 | position: absolute;
117 | left: 0;
118 | top: 0;
119 | width: 100%;
120 | height: 100%;
121 | }
122 | }
123 |
124 | /* 设置容器拉伸的最小宽度 */
125 | @mixin container-min-width() {
126 | margin-right: auto;
127 | margin-left: auto;
128 | min-width: $min-device-width;
129 | }
130 |
131 | /* 设置容器拉伸的最大宽度 */
132 | @mixin container-max-width() {
133 | margin-right: auto;
134 | margin-left: auto;
135 | max-width: $max-device-width;
136 | }
137 |
138 | /* 设置字体大小,不使用rem单位, 根据dpr值分段调整 */
139 | @mixin font-size($fontSize) {
140 | font-size: $fontSize / $design-dpr;
141 |
142 | [data-dpr="2"] & {
143 | font-size: $fontSize / $design-dpr * 2;
144 | }
145 |
146 | [data-dpr="3"] & {
147 | font-size: $fontSize / $design-dpr * 3;
148 | }
149 | }
150 |
151 | /* 清除浮动 */
152 | @mixin clearfix {
153 | *zoom: 1;
154 |
155 | &:after {
156 | content: "";
157 | display: block;
158 | height: 0;
159 | visibility: hidden;
160 | clear: both;
161 | }
162 | }
163 |
164 | /*
165 | *$line:超出显示几行省略号
166 | *$substract:预留区域百分比
167 | */
168 | @mixin text-overflow($line:1, $substract:0) {
169 | overflow: hidden;
170 |
171 | @if $line==1 {
172 | white-space: nowrap;
173 | text-overflow: ellipsis;
174 | width: 100% -$substract;
175 | }
176 |
177 | @else {
178 | display: -webkit-box;
179 | -webkit-line-clamp: $line;
180 | -webkit-box-orient: vertical;
181 | }
182 | }
183 |
184 |
185 |
186 |
187 |
188 | @mixin WH($Width:100%, $Height:100%) {
189 | width: $Width;
190 | height: $Height;
191 | }
--------------------------------------------------------------------------------
/src/assets/scss/reset.scss:
--------------------------------------------------------------------------------
1 | /* http://meyerweb.com/eric/tools/css/reset/
2 | v4.0 | 20180602
3 | License: none (public domain)
4 | */
5 |
6 | html, body, div, span, applet, object, iframe,
7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
8 | a, abbr, acronym, address, big, cite, code,
9 | del, dfn, em, img, ins, kbd, q, s, samp,
10 | small, strike, strong, sub, sup, tt, var,
11 | b, u, i, center,
12 | dl, dt, dd, menu, ol, ul, li,
13 | fieldset, form, label, legend,
14 | table, caption, tbody, tfoot, thead, tr, th, td,
15 | article, aside, canvas, details, embed,
16 | figure, figcaption, footer, header, hgroup,
17 | main, menu, nav, output, ruby, section, summary,
18 | time, mark, audio, video {
19 | margin: 0;
20 | padding: 0;
21 | border: 0;
22 | font-size: 100%;
23 | font: inherit;
24 | vertical-align: baseline;
25 | }
26 | /* HTML5 display-role reset for older browsers */
27 | article, aside, details, figcaption, figure,
28 | footer, header, hgroup, main, menu, nav, section {
29 | display: block;
30 | }
31 | /* HTML5 hidden-attribute fix for newer browsers */
32 | *[hidden] {
33 | display: none;
34 | }
35 | body {
36 | line-height: 1;
37 | }
38 | menu, ol, ul {
39 | list-style: none;
40 | }
41 | blockquote, q {
42 | quotes: none;
43 | }
44 | blockquote:before, blockquote:after,
45 | q:before, q:after {
46 | content: '';
47 | content: none;
48 | }
49 | table {
50 | border-collapse: collapse;
51 | border-spacing: 0;
52 | }
53 | img{
54 | border: none;
55 | }
56 |
57 | /* 去除iPhone中默认的input样式 */
58 | input[type="submit"],input[type="reset"],input[type="button"],input:focus,button:focus,select:focus,textarea:focus{ outline: none;}
59 | input{-webkit-appearance:none; resize: none; border-radius: 0;}
60 |
61 |
--------------------------------------------------------------------------------
/src/assets/scss/variables.scss:
--------------------------------------------------------------------------------
1 | $background-color: #eeeeee;
2 | $title: 18px;
--------------------------------------------------------------------------------
/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 |
16 |
Essential Links
17 |
24 |
Ecosystem
25 |
32 |
33 |
34 |
35 |
43 |
44 |
45 |
61 |
--------------------------------------------------------------------------------
/src/components/TabBar/TabBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{item.title}}
5 |
6 |
7 |
8 |
9 |
34 |
35 |
--------------------------------------------------------------------------------
/src/config/env.development.js:
--------------------------------------------------------------------------------
1 | // 本地环境配置
2 | module.exports = {
3 | title: '本地环境',
4 | baseApi: 'http://jsonplaceholder.typicode.com',
5 | }
--------------------------------------------------------------------------------
/src/config/env.production.js:
--------------------------------------------------------------------------------
1 | // 正式环境配置
2 | module.exports = {
3 | title: '正式环境',
4 | baseApi: 'https://test.xxx.com/api', //线上环境接口地址
5 | }
--------------------------------------------------------------------------------
/src/config/index.js:
--------------------------------------------------------------------------------
1 | const config = require('./env.' + process.env.VUE_APP_ENV)
2 |
3 | module.exports = config
--------------------------------------------------------------------------------
/src/layout/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
52 |
53 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import App from './App.vue'
3 | import router from './router'
4 | import store from './store'
5 |
6 | // 移动端适配
7 | import 'lib-flexible/flexible.js';
8 |
9 | // 引入全局样式
10 | import '@/assets/scss/index.scss';
11 | // 全局引入按需引入UI库 vant
12 | import { vantPlugins } from './plugins/vant.js'
13 |
14 | createApp(App).use(store).use(router).use(vantPlugins).mount('#app')
--------------------------------------------------------------------------------
/src/plugins/vant.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author 小周
3 | * @description 按需引入Vant
4 | */
5 | import { Button, Tabbar, TabbarItem } from 'vant'
6 | const pluginsVant = [
7 | Button,
8 | Tabbar,
9 | TabbarItem
10 | ]
11 | export const vantPlugins = {
12 | install: function(vm) {
13 | pluginsVant.forEach(item => {
14 | vm.component(item.name, item);
15 | });
16 | }
17 | };
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import { createRouter, createWebHistory } from 'vue-router'
2 | const routes = [
3 | {
4 | path: '/',
5 | name: 'Home',
6 | component: () => import ('./../layout/index.vue'),
7 | redirect: '/home',
8 | meta: {
9 | title: '首页',
10 | keepAlive:false
11 | },
12 | children: [
13 | {
14 | path: '/home',
15 | name: 'Home',
16 | component: () => import('@/views/Home.vue')
17 | },
18 | {
19 | path: '/about',
20 | name: 'About',
21 | component: () => import('@/views/About.vue')
22 | },
23 | {
24 | path: '/detail/:id',
25 | name: 'Detail',
26 | component: () => import('@/views/Detail.vue')
27 | }
28 | ]
29 | }
30 | ]
31 |
32 | const router = createRouter({
33 | history: createWebHistory(process.env.BASE_URL),
34 | routes
35 | })
36 |
37 | export default router
38 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import { createStore } from 'vuex'
2 |
3 | export default createStore({
4 | state: {
5 | userNmae: "vue3.0开发H5模板"
6 | },
7 | mutations: {
8 | getUserNmae(state,data) {
9 | state.userNmae = data
10 | }
11 | },
12 | actions: {
13 | },
14 | modules: {
15 | }
16 | })
17 |
--------------------------------------------------------------------------------
/src/util/index.js:
--------------------------------------------------------------------------------
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 | }
111 |
--------------------------------------------------------------------------------
/src/util/request.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author 小周
3 | * @description 接口封装
4 | */
5 | import axios from 'axios'
6 | import { baseApi } from './../config'
7 |
8 | const service = axios.create({
9 | baseURL: baseApi,
10 | withCredentials: false,
11 | timeout: 5000
12 | })
13 |
14 | // request拦截器 request interceptor
15 | service.interceptors.request.use(config => {
16 | return config
17 | },error => {
18 | console.log(error);
19 | return Promise.reject(error)
20 | })
21 |
22 |
23 | // respone拦截器
24 | service.interceptors.response.use(response => {
25 | const res = response.data
26 | if(res.status && res.status !== 200) {
27 | return Promise.reject(res)
28 | } else {
29 | return Promise.resolve(res)
30 | }
31 | },error =>{
32 | return Promise.reject(error)
33 | })
34 | export default service
--------------------------------------------------------------------------------
/src/views/About.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
21 |
22 |
--------------------------------------------------------------------------------
/src/views/Detail.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | 详情id为:{{currentId}}
4 |
5 |
6 |
7 |
22 |
23 |
--------------------------------------------------------------------------------
/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
vuex按钮
4 |
{{name}}
5 |
{{ msg }}
6 |
7 | -
8 | {{item.name}}
9 |
10 |
11 |
12 |
13 |
14 |
40 |
41 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const resolve = dir => path.join(__dirname, dir)
3 | module.exports = {
4 | publicPath: './', // 署应用包时的基本 URL。 vue-router hash 模式使用
5 | outputDir: 'dist', // 生产环境构建文件的目录
6 | assetsDir: 'static', // outputDir的静态资源(js、css、img、fonts)目录
7 | lintOnSave: false, //eslint 检测
8 | productionSourceMap: false, // 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。
9 | devServer: {
10 | port: 9527, // 端口
11 | open: false, // 启动后打开浏览器
12 | overlay: {
13 | // 当出现编译器错误或警告时,在浏览器中显示全屏覆盖层
14 | warnings: false,
15 | errors: true
16 | }
17 | // proxy: {
18 | // //配置跨域
19 | // '/api': {
20 | // target: "https://test.xxx.com",
21 | // // ws:true,
22 | // changOrigin:true,
23 | // pathRewrite:{
24 | // '^/api':'/'
25 | // }
26 | // }
27 | // }
28 | },
29 | chainWebpack:(config)=>{
30 | config.resolve.alias
31 | .set('@', resolve('src'))
32 | .set('assets', resolve('src/assets'))
33 | .set('api', resolve('src/api'))
34 | .set('views', resolve('src/views'))
35 | .set('components', resolve('src/components'))
36 | }
37 | }
--------------------------------------------------------------------------------