├── .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 | 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 | 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 | 34 | 35 | 43 | 44 | 45 | 61 | -------------------------------------------------------------------------------- /src/components/TabBar/TabBar.vue: -------------------------------------------------------------------------------- 1 | 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 | 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 | 11 | 12 | 21 | 22 | -------------------------------------------------------------------------------- /src/views/Detail.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 22 | 23 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 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 | } --------------------------------------------------------------------------------