手把手教你通過vue-cli搭建手機(jī)端框架

前言:歡迎前端的小伙伴們前來圍觀丰刊、學(xué)習(xí)借鑒,如果你是后端瞒斩、測(cè)試和其他的小伙伴也沒關(guān)系破婆,如果自己也想玩一下前端,想搭建一個(gè)前端的框架胸囱,那么不妨靜下心來看看這篇文章祷舀。如果你不是從事開發(fā)工作的人員,內(nèi)容可能相對(duì)而言比較枯燥旺矾,但是如果想找錯(cuò)別字蔑鹦,也不妨進(jìn)來看看夺克。

初衷:有的前端的小伙伴要說了箕宙,vue-cli不是已經(jīng)幫我們封裝好了webpack(打包)嗎?為什么铺纽,還要進(jìn)行二次的搭建和封裝呢柬帕?我想說的是,是的這些很基礎(chǔ)的配置vue-cli都幫我們做好了,但是針對(duì)手機(jī)端樣式初始化陷寝,axios的請(qǐng)求封裝锅很,常用的工具包類封裝,vuex模塊化的處理凤跑,以及開發(fā)爆安、測(cè)試、正式環(huán)境變量的拆分配置仔引,webpack打包優(yōu)化配置扔仓,手機(jī)端響應(yīng)式的處理,手機(jī)端引入第三方UI框架vant的更好的方法等等都沒有給我們搭建咖耘,因?yàn)椴煌?xiàng)目可能有不同的方式翘簇,我這里介紹的是一種大眾的、通用的一些框架:vue-cli+vue-router+vuex+axios+vant儿倒。

目的:教你如何手動(dòng)搭建屬于自己的前端手機(jī)項(xiàng)目版保。

廢話不多說,直接上干貨夫否。

第一步: vue-cli初始化項(xiàng)目(相信很多前端小伙伴這一步操作都不難)

npm install -g @vue/cli

vue create my-project

注:這里的my-project自己可以按照自己的項(xiàng)目名稱來定義
如果你沒有安裝成功彻犁,那么需要把nodejs安裝一下。

第二步:配置全局環(huán)境變量

需要我們?cè)诟夸泟?chuàng)建四個(gè)文件:.env慷吊、.env.dev袖裕、.env.test、.env.pro
目的:我們不可能反復(fù)的去更改配置文件溉瓶,而是通過運(yùn)行不同的指令來調(diào)用同變量不同環(huán)境的值急鳄。

//.env 和 .env.dev 內(nèi)容一樣
VUE_APP_NODE_ENV="development"
VUE_APP_API="http://public-api-v1.aspirantzhang.com/"
VUE_APP_VERSION = "d-1.0"

//.env.test
VUE_APP_NODE_ENV="test"
VUE_APP_API="https://wwww.baidu.com/production"
VUE_APP_VERSION = "t-1.0"

//.env.pro
VUE_APP_NODE_ENV="production"
VUE_APP_API="https://wwww.baidu.com/production"
VUE_APP_VERSION = "p-1.0"

這四個(gè)配置文件是結(jié)合package.json來使用的,啟動(dòng)不同的命令,執(zhí)行不同變量參數(shù)

"scripts": {
    "dev": "vue-cli-service serve",
    "test": "vue-cli-service serve --mode test",
    "pro": "vue-cli-service serve --mode pro",
    "build:dev": "vue-cli-service build --mode dev",
    "build:test": "vue-cli-service build --mode test",
    "build:pro": "vue-cli-service build --mode pro",
    "lint": "vue-cli-service lint"
},

第三步:路由配置

在配置路由之前我創(chuàng)建了兩個(gè)頁面:
首頁:src/views/Home/Home.vue
列表頁:src/views/List/List.vue

1.創(chuàng)建src/router/index.js

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    redirect: {
      name: 'home'
    }
  },
  {
    path: '/home',
    name: 'home',
    meta: {
      title: '首頁',
    },
    component: () => import(/* webpackChunkName: "Home" */ '../views/Home/Home.vue') // 首頁
  },
  {
    path: '/list',
    name: 'list',
    meta: {
      title: '列表頁面',
    },
    component: () => import(/* webpackChunkName: "List" */ '../views/List/List.vue') // 列表頁面
  }
]

const router = new VueRouter({
  base: process.env.BASE_URL,
  routes
})
router.beforeEach((to, from, next) => {
  /* 路由發(fā)生變化修改頁面title */
  if (to.meta.title) {
    document.title = to.meta.title
  }
  next()
})

export default router

2.在入口文件main.js中引用router

import router from './router'
new Vue({
  router,
  store,
  render: h => h(App),
}).$mount('#app')

3.在App.vue文件中通過router-view來獲取路由指向的頁面堰酿,把頁面和路由關(guān)聯(lián)起來

<template>
  <div id="app">
   <router-view />
  </div>
</template>

<script>
export default {
  name: 'App',
  created(){
    console.log(process.env.VUE_APP_NODE_ENV, '-', process.env.VUE_APP_VERSION)
  }
}
</script>

第四步:vuex模塊處理配置

1.創(chuàng)建src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
import VuexPersistence from 'vuex-persist'
import home from './modules/home'
import list from './modules/list'

const vuexLocal = new VuexPersistence({
  storage: window.localStorage,
  modules: ["home"]
})
Vue.use(Vuex)

const store = new Vuex.Store({
  strict: process.env.NODE_ENV !== 'production',
  modules: { home, list },
  plugins: [vuexLocal.plugin]
})

export default store

2.創(chuàng)建src/store/modules/home.js

export default {
    namespaced: true,
    state: {
        list: [],
        visible: false,
        firstName: 'Sunny',
        lastName: 'Fan'
    },
    mutations: {
        MGetList(state, data){
            state.list = data
        },
        MChangeVisible(state, value){
            state.visible = value
        }
    },
    actions: {
        // 異步請(qǐng)求接口數(shù)據(jù)
        AGetList ({ commit }, params) {
            const url = '/users'
            const error = '獲取數(shù)據(jù)失敗'
            return $http.get(url, params).then(res => {
                const { data } = res
                // commit 去同步更改state里面的數(shù)據(jù)
                return commit('MGetList', data)
            }).catch(e => {
                return Promise.resolve(e && e.statusText || error)
            })
        },
    },
    getters: {
        getFullName: state => {
            return state.firstName +'----'+ state.lastName
        }
    }
}

3.創(chuàng)建src/store/modules/list.js 這個(gè)參考2即可
4.在入口文件mian.js中引入store/index.js

import Vue from 'vue'
import router from './router'
import store from './store'
import Axios from '@/utils/Axios'
import App from './App.vue'
import 'lib-flexible/flexible' // 根據(jù)窗口不同疾宏,給html設(shè)置不同的font-size值
import './utils/vant' // 引入局部ui
import './assets/css/common.less'
import Vconsole from 'vconsole'

Vue.config.productionTip = false

// 在開發(fā)環(huán)境和測(cè)試環(huán)境打開console方便在真機(jī)上查看日志、追蹤問題
const environment = process.env.VUE_APP_NODE_ENV;
if(environment==='development'||environment==='test'){
  const vConsole = new Vconsole()
  Vue.use(vConsole)
}

// vue內(nèi)部全局注入
Vue.use({
  install (vue) {
    Object.assign(vue.prototype, {
      $axios: Axios,
      $store: store
    })
  }
})

new Vue({
  router,
  store,
  render: h => h(App),
}).$mount('#app')

第五步:手機(jī)端響應(yīng)式配置触创,以及初始化樣式坎藐、vant樣式框架引入(根據(jù)不同屏幕放大縮小適配)

1.在src創(chuàng)建assets/common.less

*{
    padding: 0;
    margin: 0;
    box-sizing: border-box;
    touch-action: auto;
    -webkit-overflow-scrolling:touch;
}
html, body {
     height:100vh;
     width: 100vw;
     margin: 0; 
     padding:0;
}

并且在我們的入口文件:main.js中引入common.less文件

import './assets/css/common.less'

2.安裝適配依賴

  yarn add lib-flexible autoprefixer postcss-pxtorem babel-plugin-import

3.根據(jù)依賴進(jìn)行相關(guān)的配置
在項(xiàng)目的根目錄創(chuàng)建postcss.config.js

const autoprefixer = require('autoprefixer')
const pxtorem = require('postcss-pxtorem')

module.exports = ({ file }) => {
  let rootValue
  // vant 37.5 [link](https://github.com/youzan/vant/issues/1181)
  // if (file && file.dirname && file.dirname.indexOf('vant') > -1 && file.dirname.indexOf('swiper') > -1) {
  if (file && file.dirname && file.dirname.indexOf('vant') > -1) {
    rootValue = 37.5
  } else {
    rootValue = 75
  }
  return {
    plugins: [
      autoprefixer(),
      pxtorem({
        rootValue: rootValue,
        propList: ['*'],
        selectorBlackList: ['.swiper'], // 要忽略的選擇器并保留為px。
        minPixelValue: 0
      })
    ]
  }
}

4.根據(jù)vant的官網(wǎng)文檔哼绑,我們通過在babel.config.js文件中配置來引入vant的樣式

module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset'
  ],
  plugins: [
    ['import', {
      libraryName: 'vant',
      libraryDirectory: 'es',
      style: true
    }, 'vant']
  ]
}

5.頁面調(diào)用
<van-button type="info">按鈕</van-button>
6.頁面適配岩馍,在main.js中引入lib-flexible依賴

import 'lib-flexible/flexible' // 根據(jù)窗口不同,給html設(shè)置不同的font-size值

第六步:vant UI的引入(按需引入抖韩,降低打包體積)

//通過 npm 安裝
npm i vant -S

//通過 yarn 安裝
yarn add vant
  1. 在src創(chuàng)建utils/vant.js
import Vue from 'vue'
import {Loading, Lazyload, Toast, Dialog,} from 'vant'

// 默認(rèn)vant組件
[Loading, Lazyload, Toast, Dialog,].forEach(item => Vue.use(item))

// 先預(yù)制蛀恩,后期做統(tǒng)一調(diào)整
Object.assign(window, {
  Toast, Dialog
})

從代碼我們能看出來,每個(gè)組件都是按需引入茂浮,大大的降低了打包的體積双谆,并且把Toast和Dialog注入到了window全局變量里面壳咕,為了方便我們直接調(diào)用。
2.解決vant樣式適配問題,查看上面的postcss.config.js即可
3.在入口文件main.js 引入

import './utils/vant' // 引入局部ui

第七步:Axios的封裝(公共頭部顽馋、異常谓厘、不同請(qǐng)求方式配置處理)

1.創(chuàng)建src/utils/request.js

import axios from 'axios'

const codeMessage = {
  200: '服務(wù)器成功返回請(qǐng)求的數(shù)據(jù)。',
  201: '新建或修改數(shù)據(jù)成功寸谜。',
  202: '一個(gè)請(qǐng)求已經(jīng)進(jìn)入后臺(tái)排隊(duì)(異步任務(wù))竟稳。',
  204: '刪除數(shù)據(jù)成功。',
  400: '發(fā)出的請(qǐng)求有錯(cuò)誤熊痴,服務(wù)器沒有進(jìn)行新建或修改數(shù)據(jù)的操作住练。',
  401: '用戶沒有權(quán)限(令牌、用戶名愁拭、密碼錯(cuò)誤)讲逛。',
  403: '用戶得到授權(quán),但是訪問是被禁止的岭埠。',
  404: '發(fā)出的請(qǐng)求是不存在的盏混,服務(wù)器沒有進(jìn)行操作。',
  406: '請(qǐng)求的格式不可得惜论。',
  410: '請(qǐng)求的資源被永久刪除许赃。',
  422: '當(dāng)創(chuàng)建一個(gè)對(duì)象時(shí),發(fā)生一個(gè)驗(yàn)證錯(cuò)誤馆类。',
  500: '服務(wù)器發(fā)生錯(cuò)誤混聊,請(qǐng)檢查服務(wù)器。',
  502: '網(wǎng)關(guān)錯(cuò)誤乾巧。',
  503: '服務(wù)不可用句喜,服務(wù)器暫時(shí)過載或維護(hù)。',
  504: '網(wǎng)關(guān)超時(shí)沟于。'
}
const baseURL = process.env.VUE_APP_NODE_ENV == 'development' ? '/api' : process.env.VUE_APP_API
const instance = axios.create({
  baseURL
})
class Request {
  constructor(baseURL) {
    this.baseURL = baseURL
    this.queue = {}
    this.timeout = 5000
  }

  // 檢查返回狀態(tài)
  checkStatus (response) {
    const responseData = response.data

    // 服務(wù)器返回默認(rèn)結(jié)果
    if (response && (response.status === 200 || response.status === 304 || response.status === 400)) {
      // 后臺(tái)自定義錯(cuò)誤
      // 正常
      if (responseData.status == 0) {
        return responseData
      }
      // 登錄過期
      if (responseData.errorCode === 402 || responseData.status === 401) {
        return Promise.reject(errorText)
      }
      return Promise.reject(responseData)
    }
    // 服務(wù)器錯(cuò)誤
    const errorText = response && (codeMessage[response.status] || response.statusText)
    Promise.reject(response)
  }

  // 攔截器
  interceptors (instance, scope) {
    // 請(qǐng)求攔截
    instance.interceptors.request.use(config => {
      config.baseURL = baseURL;
      config.scope = scope
      return config
    }, error => {
      return Promise.reject(error)
    })
    // 響應(yīng)攔截
    instance.interceptors.response.use(res => {
      return res
    }, error => {
      let errorInfo = error.response
      if (!errorInfo) {
        try {
          const { request: { statusText, status }, config } = JSON.parse(JSON.stringify(error))
          errorInfo = {
            statusText,
            status,
            request: { responseURL: config.url }
          }
        } catch (e) {
          errorInfo = error
        }
      }
      return Promise.reject(errorInfo)
    })
  }

  // 失敗
  error (e) {
    return Promise.reject(e)
  }


  setRequest (method, url, data, scope, file = false) {
    this.interceptors(instance, scope)

    const options = { method, url }

    let contentType = ''
    if (file) {
      contentType = 'multipart/form-data'
    } else if (method == 'post') {
      contentType = 'application/json'
    } else {
      contentType = 'application/x-www-form-urlencoded; charset=UTF-8'
    }

    const headers = {
      'X-Requested-With': 'XMLHttpRequest',
      'Content-Type': contentType,
      // token: store.state.user.token || 1
    }

    Object.assign(options, {
      headers,
      [method == 'post' ? 'data' : 'params']: data
    })

    return instance(options).then(this.checkStatus).catch(this.error)
  }

  // post 請(qǐng)求封裝
  post (url, data, scope) {
    return this.setRequest('post', url, data, scope)
  }

  // get  請(qǐng)求封裝
  get (url, data, scope) {
    return this.setRequest('get', url, data, scope)
  }

  // post 請(qǐng)求封裝
  POST (url, data, scope) {
    return this.setRequest('post', url, data, scope).then(this.success)
  }

  // get  請(qǐng)求封裝
  GET (url, data, scope) {
    return this.setRequest('get', url, data, scope).then(this.success)
  }

  // 文件
  File (url, data, scope) {
    return this.setRequest('post', url, data, scope, true).then(this.fileSuccess)
  }

  success (da) {
    return da.data
  }

  fileSuccess (da) {
    return da
  }
}

export default Request

2.創(chuàng)建src/utils/Axios.js

import Vue from "vue";
import Request from './request'
//import config from '@/config'
const Axios = new Request()
Plugin.install=(Vue)=>{
    Vue.prototype.$http = Axios
}
Object.assign(window,{
    $http:Axios
})
Vue.use(Plugin);
export default Axios

第八步:vue.config.js配置(針對(duì)webpack進(jìn)行了封裝)

這一步我們進(jìn)行了咳胃,icon圖標(biāo)雪碧圖處理,打包文件哈希命名旷太,解決緩存問題展懈,本地接口代理處理,打包引入cdn文件供璧,路徑過長別名處理等等
1.vue.config.js

const path = require('path')
const SpritesmithPlugin = require('webpack-spritesmith')// 雪碧圖
const TerserPlugin = require('terser-webpack-plugin')
const devServer = require('./server')
const CompressionPlugin = require('compression-webpack-plugin')


const cdn = {
  // 開發(fā)環(huán)境
  dev: {
    css: [
    ],
    js: [
    ]
  },
  // 生產(chǎn)環(huán)境
  build: {
    css: [
    ],
    js: [
      'https://lib.baomitu.com/vue/2.6.11/vue.min.js',
      'https://lib.baomitu.com/vue-router/3.2.0/vue-router.min.js',
      'https://lib.baomitu.com/vuex/3.5.1/vuex.min.js',
      'https://lib.baomitu.com/axios/0.19.2/axios.min.js',
      'https://lib.baomitu.com/hls.js/0.14.3/hls.min.js'
    ]
  }
}
// 打包排除包存崖,通過cdn加載
const externals = {
  'vue': 'Vue',
  'vuex': 'Vuex',
  'axios': 'axios',
  'hls.js': 'hls.js',
  'vue-router': 'VueRouter'
}

// 雪碧圖的自定義模板
const templateFunction = function (data) {
  var shared = '.icon-sprite { display: inline-block; background-image: url(I); background-size: Dpx Hpx; }'
    .replace('I', data.sprites[0].image)
    .replace('D', data.sprites[0].total_width / 2)
    .replace('H', data.sprites[0].total_height / 2)

  var perSprite = data.sprites.map(function (sprite) {
    return '.icon-N { width: Wpx; height: Hpx; background-position: Xpx Ypx; }'
      .replace('N', sprite.name.replace(/_/g, '-'))
      .replace('W', sprite.width / 2)
      .replace('H', sprite.height / 2)
      .replace('X', sprite.offset_x / 2)
      .replace('Y', sprite.offset_y / 2)
  }).join('\n')

  return shared + '\n' + perSprite
}
const configureWebpackData = {
  resolve: {
    alias: {
      // 別名
      vue$: 'vue/dist/vue.esm.js',
      '@': resolve('src'),
      '@api': resolve('src/api'),
      '@utils': resolve('src/utils'),
      '@style': resolve('src/assets/css'),
      '@images': resolve('src/assets/images'),
      '@views': resolve('src/views')
    }
  },
  plugins: [
    new SpritesmithPlugin({
      src: {
        cwd: path.resolve(__dirname, './src/assets/icon'),
        glob: '*.png'
      },
      target: { // 輸出雪碧圖文件及樣式文件,這個(gè)是打包后睡毒,自動(dòng)生成的雪碧圖和樣式
        image: path.resolve(__dirname, './src/assets/images/sprite.png'),
        css: [
          [path.resolve(__dirname, './src/assets/css/sprite.less'), {
            // 引用自己的模板
            format: 'function_based_template'
          }]
        ]
      },
      customTemplates: { // 自定義模板入口
        function_based_template: templateFunction
      },
      apiOptions: { // 樣式文件中調(diào)用雪碧圖地址寫法
        cssImageRef: '../images/sprite.png'
      },
      spritesmithOptions: { // 讓合成的每個(gè)圖片有一定的距離
        padding: 20
      }
    })
  ]
  
}
function resolve (dir) {
  return path.join(__dirname, './', dir)
}

module.exports = {
  outputDir: "dist",
  assetsDir: 'assets',
  publicPath: './',
  pages: {
    index: {
      entry: './src/main.js',
      template: path.join(__dirname, 'public/index.html'),
      filename: 'index.html',
      cdn: process.env.VUE_APP_NODE_ENV === 'production' && cdn.build || cdn.dev,
      title: '  '
    }
  },
  lintOnSave: false, // 是否開啟編譯時(shí)是否不符合eslint提示
  devServer,
  configureWebpack: config => {
    configureWebpackData.externals = process.env.VUE_APP_NODE_ENV === 'production' && externals || {};
    if (process.env.VUE_APP_NODE_ENV === 'production' || process.env.VUE_APP_NODE_ENV === 'devproduction') {
      config.plugins.push(
        new TerserPlugin({
          terserOptions: {
            ecma: undefined,
            warnings: false,
            parse: {},
            compress: {
              drop_console: true,
              drop_debugger: false,
              pure_funcs: ['console.log'] // 移除console
            }
          }
        })
      )
    }

    if (process.env.VUE_APP_NODE_ENV === 'production') {
      configureWebpackData.plugins.push(new CompressionPlugin({
        test: /\.js$|\.html$|\.css/,
        threshold: 10240,
        deleteOriginalAssets: false
      }))
    }

    return configureWebpackData
  },
  chainWebpack: config => {
    config.output.filename('assets/js/[name].[hash].js').end()
    config.output.chunkFilename('assets/js/[name].[hash].js').end()
  },
  productionSourceMap: false,
  css: {
    // extract: true,
    sourceMap: false,
    // modules: false,
    requireModuleExtension: true,
    loaderOptions: {

    }
  }
}

2.server.js 主要配置代理相關(guān)信息

module.exports = {
  host: '0.0.0.0',
  port: 8000,
  https: false,
  hotOnly: false,
  proxy: {
    '^/api': {
      // 測(cè)試環(huán)境
      target: process.env.VUE_APP_API, 
      changeOrigin: true, // 是否跨域
      pathRewrite: {
        '^/api': '' // 需要rewrite重寫的,  // /mock
      }
    }
  }
}

第九步:常見工具類的配置(時(shí)間来惧、正則、公共方法吕嘀、數(shù)據(jù)字典)

1.創(chuàng)建src/utils/index.js

//校驗(yàn)輸入文字為純數(shù)字
export function validNumber(value) {
    const reg = /^\d+$/;
    return reg.test(value);
}

//校驗(yàn)輸入的文字 --綜合搜索
export function validText(value) {
    const reg = /^([\u4E00-\u9FA5])*$/;
    return reg.test(value);
}

//電話號(hào)碼正則函數(shù)
export function checkPhone(value) {
    const reg = /^[1][3,4,5,6,7,8,9][0-9]{9}$/;
    return reg.test(value);
}

//郵箱正則函數(shù)
export function checkEmail(value) {
    const reg = /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/;
    return reg.test(value);
}

//2-10位中英文
export function checkUserName(value) {
    const reg = /^[\u4E00-\u9FA5A-Za-z]{2,10}$/;
    return reg.test(value);
}

//去除空格
export function removeSpace(value) {
    const reg = /\s+/g;
    return value.replace(reg, "");
}

//為空或全部為空格
export function checkSpace(value) {
    const reg = /^[ ]*$/;
    return reg.test(value);
}

//判斷密碼大于6位违寞,數(shù)字、字母大小寫組合
export function checkPassWord(value) {
    let regNumber = /\d+/;
    let regString = /[a-zA-Z]+/;
    return regNumber.test(value) && regString.test(value) && value.length >= 8 && value.length <= 20;
}

//獲取周幾
export function weeks(day) {
    let myDate = day ? new Date(day) : new Date();
    let wk = myDate.getDay();
    switch (wk) {
    case 0:
        return '星期日';
    case 1:
        return '星期一';
    case 2:
        return '星期二';
    case 3:
        return '星期三';
    case 4:
        return '星期四';
    case 5:
        return '星期五';
    case 6:
        return '星期六';
    }
    return wk;
}

export function checkIdCard(value) {
    const idCardNo = value;
    if(idCardNo.length === 18) {
        const birStr = value.substr(6, 8);
        const sexFlag = idCardNo.charAt(16) - 0; //奇數(shù)男 偶數(shù)女
        const sexfromIDcard = sexFlag % 2; //1男 0女
        return {sex: sexfromIDcard===1?0:1, birStr};
    } else if(idCardNo.length === 15) {
        const birStr = '19' + value.substr(6, 6);
        const sexFlag2 = idCardNo.charAt(14) - 0; //奇數(shù)男 偶數(shù)女
        const sexfromIDcard2 = sexFlag2 % 2; //1男 0女
        return {sex: sexfromIDcard2===1?0:1, birStr};
    }
}

// 獲取當(dāng)前時(shí)間年月日時(shí)分秒
export function getNowData(type) {
    let date = new Date();

    let year = date.getFullYear();

    let month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1;
    let day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate();
    let lastDay = date.getDate() - 1 < 10 ? '0' + (date.getDate() - 1) : date.getDate() - 1;
    let hour = date.getHours();
    let minute = date.getMinutes();
    let second = date.getSeconds();
    switch (type) {
    case 1:
        return `${year}-${month}-${day}`;
    case 2:
        return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
    case 3:
        return day;
    case 4:
        return `${year}.${month}`;
    case 5:
        return `${year}-${month}-${lastDay}`;
    default:
        return `${year}-${month}-${day}`;
    }
}

//數(shù)組排序
export function compare(property) {
    return function (a, b) {
        var value1 = a[property];
        var value2 = b[property];
        return value1 - value2;
    };
}

第十:總結(jié)

自己抽了一天時(shí)間偶房,一遍搭建趁曼,一遍寫文檔,反復(fù)修改棕洋,可能里面還有很多需要完善地方挡闰,后期我會(huì)出整個(gè)的搭建的過程的視頻,幫助大家更加直觀的去理解和學(xué)習(xí)掰盘。
碼字不易摄悯,如果有幫助到自己的地方或者看后對(duì)自己學(xué)習(xí)前端知識(shí)所有提升,請(qǐng)關(guān)注一下我的公眾號(hào)愧捕,后期會(huì)有更多精品的內(nèi)容推出奢驯,寫出來和大家一起分享學(xué)習(xí)。

走過路過不要錯(cuò)過次绘,既然都看到這個(gè)地方了瘪阁,那就留下一個(gè)評(píng)論和點(diǎn)贊吧。

源碼地址:https://github.com/fx35792/vue-mobile-template
原文地址:blog.sunnyfanfan.com/articles/20…

參考文獻(xiàn):
https://cli.vuejs.org/
https://vant-contrib.gitee.io/vant/#/zh-CN/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末邮偎,一起剝皮案震驚了整個(gè)濱河市管跺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌禾进,老刑警劉巖豁跑,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異泻云,居然都是意外死亡艇拍,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門宠纯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來淑倾,“玉大人,你說我怎么就攤上這事征椒〗慷撸” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵勃救,是天一觀的道長碍讨。 經(jīng)常有香客問我,道長蒙秒,這世上最難降的妖魔是什么勃黍? 我笑而不...
    開封第一講書人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮晕讲,結(jié)果婚禮上覆获,老公的妹妹穿的比我還像新娘马澈。我一直安慰自己,他們只是感情好弄息,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開白布痊班。 她就那樣靜靜地躺著,像睡著了一般摹量。 火紅的嫁衣襯著肌膚如雪涤伐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評(píng)論 1 299
  • 那天缨称,我揣著相機(jī)與錄音凝果,去河邊找鬼。 笑死睦尽,一個(gè)胖子當(dāng)著我的面吹牛器净,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播当凡,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼掌动,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了宁玫?” 一聲冷哼從身側(cè)響起粗恢,我...
    開封第一講書人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎欧瘪,沒想到半個(gè)月后眷射,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡佛掖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年妖碉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片芥被。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡欧宜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拴魄,到底是詐尸還是另有隱情冗茸,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布匹中,位于F島的核電站夏漱,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏顶捷。R本人自食惡果不足惜挂绰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望服赎。 院中可真熱鬧葵蒂,春花似錦交播、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至荔仁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間芽死,已是汗流浹背乏梁。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留关贵,地道東北人遇骑。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像揖曾,于是被迫代替她去往敵國和親落萎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353