vite2.0+vue3+ts前端最新熱門技術項目搭建


閑來無事,趁假期來加強一波技能學習!不廢話直接上流程上圖;

超級簡潔和極致的本地開發體驗!!!

 來個項目簡介吧;

<!--
 * @Description: Vue 3 + Typescript + Vite2.0 +vant3 +  vue-i18n@next國際化 搭建移動端項目簡介 * @Version: 2.0
 * @Autor: lhl * @Date: 2021-04-03 23:18:54
 * @LastEditors: lhl * @LastEditTime: 2021-04-11 22:39:02
--> # Vue 3 + Typescript + Vite #### 項目初始化 yarn or cnpm or npm 安裝 【本項目為了速度一律 cnpm】 cnpm init @vitejs/app or yarn create @vitejs/app 或者快捷命令 cnpm init @vitejs/app my-vue-app --template vue-ts Node.js: - 版本最好大於 12.0.0 yarn > npm > cnpm: - 包管理工具
 ### 安裝依賴 cnpm i ### 安裝路由 cnpm i vue-router@4 -S 【--save】 ### 安裝 vuex cnpm i vuex@next -S 【--save】 ### 安裝國際化 cnpm i vue-i18n@next -S cnpm i js-cookie -S cnpm i @types/js-cookie -D
console.log(import.meta.env.VITE_APP_BASE_URL, 'VITE_APP_BASE_URL') console.log(process.env.NODE_ENV) ### 啟動項目 cnpm run dev ### 代碼規范 vscode 需要安裝的相關插件 Eslint Prettier Prettier Eslint 三個即可 cnpm i eslint -D 根目錄下創建 .eslintrc.js 文件 eslint 官方配置文檔:https://eslint.org/docs/user-guide/configuring/configuration-files#using-configuration-files
 node 環境的類型檢查 cnpm i @types/node -D
 cnpm i prettier -D 根目錄下創建 .prettierrc.js 文件 prettier 官方配置文檔:https://prettier.io/docs/en/
 安裝相關依賴 cnpm i @typescript-eslint/eslint-plugin -D 
cnpm i @typescript-eslint/parser -D 
cnpm i eslint-config-prettier -D cnpm i eslint-plugin-prettier -D cnpm i eslint-plugin-vue -D cnpm i eslint-define-config -D  ### git 代碼提交 cnpm i husky lint-staged -D 【git 代碼提交規范】 package.json 文件中配置下 cnpm i eslint-plugin-vue@latest @typescript-eslint/eslint-plugin@latest @typescript-eslint/parser@latest babel-eslint -D cnpm i eslint-config-standard eslint-friendly-formatter eslint-plugin-import eslint-plugin-standard eslint-plugin-promise -D ### 移動端適配問題 先說特別的 iPhonex 的適配 iOS11 新增特性,Webkit 的一個 CSS 函數,用於設定安全區域與邊界的距離,有四個預定義的變量: safe-area-inset-left:安全區域距離左邊邊界距離 safe-area-inset-right:安全區域距離右邊邊界距離 safe-area-inset-top:安全區域距離頂部邊界距離 safe-area-inset-bottom:安全區域距離底部邊界距離只有設置了 viewport-fit=cover,才能使用 constant 函數 <meta name=“viewport” content=“width=device-width, viewport-fit=cover”> body { padding-bottom: constant(safe-area-inset-bottom); } fixed 元素的適配 { padding-bottom: constant(safe-area-inset-bottom); } 或者直接設置 body{ padding: env(safe-area-inset-left,20px) env(safe-area-inset-right,20px) env(safe-area-inset-top,20px) env(safe-area-inset-bottom,20px) } 再或者媒體查詢 /_兼容 iphoneX_/ /_判斷是否是 iphoneX,使用@media 媒體查詢尺寸_/ @media only screen and (device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) { body { top: constant(safe-area-inset-top); bottom: constant(safe-area-inset-bottom); left: constant(safe-area-inset-left); right: constant(safe-area-inset-right); } } ios11 webview 狀態欄問題 【設置了固定定位頁面滾動過程中兩邊留白】如果是純色背景的話,可以通過給 body 設置 background 來實現填充兩邊的留白 scss 函數 $designWidth: 750; @function px2vm($size){ @return #{100\*$size / $designWidth}vw } 調用 px2vm(50) ### vm vw 適配方案 cnpm i postcss-px-to-viewport -D ### 第三方 UI 庫 vant cnpm i vant@next -S 官方文檔:https://vant-contrib.gitee.io/vant/v3/#/zh-CN 

"browserslist": [ "defaults", // 默認 "last 2 versions", 
  "last 2 versions",  // 兼容主流瀏覽器的最近兩個版本 "> 1%", 
  "> 1%", // 兼容主流瀏覽器的最近兩個版本 "> 1%", 
  "iOS 7",  // 使用的瀏覽器需要在市場上的份額大於 1 "iOS 7",
  "last 3 iOS versions" // 兼容 ios 的最新 3 個版本 
] 組件樣式按需加載配置 cnpm i vite-plugin-style-import -D import styleImport from 'vite-plugin-style-import' css:{ preprocessorOptions:{ less:{ modifyVars:{}, javascriptEnabled: true } } }, plugins:[ styleImport({ libs:[ { libraryName: 'ant-design-vue', esModule: true, resolveStyle: name => `ant-design-vue/es/${name}/style/index` } ] }) ] ### 自動添加 css 前綴插件 cnpm i autoprefixer -D ### SASS 預處理器 cnpm i node-sass sass-loader sass -D ### 生產環境生成 .gz 文件 cnpm i vite-plugin-compression -D 參考文檔:https://github.com/anncwb/vite-plugin-compression
 ### package.json 文件配置打包命令 環境變量 VITE_ 開頭 "build:dev": "vue-tsc --noEmit && vite build --mode development", "build:test": "vue-tsc --noEmit && vite build --mode test", "build:prod": "vue-tsc --noEmit && vite build --mode production" ### PWA 配置 cnpm i vite-plugin-pwa -D import { VitePWA } from 'vite-plugin-pwa' 參考文檔:https://github.com/antfu/vite-plugin-pwa
 plugins:[ VitePWA({ manifest: {}, workbox: { skipWaiting: true, clientsClaim: true } }) ]

再來個vite.config.ts常用配置

/* * @Description: vite.config.ts vite2.0配置 * @Version: 2.0 * @Autor: lhl * @Date: 2021-04-03 23:18:54 * @LastEditors: lhl * @LastEditTime: 2021-04-11 20:43:27 */ import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import viteCompression from 'vite-plugin-compression' import path from 'path' const resolve = (dir: string) => path.join(__dirname, dir) // https://vitejs.dev/config/
export default defineConfig({ plugins: [ vue(), // 生產環境生成 .gz 文件
 viteCompression({ verbose: true, disable: false, threshold: 10240, algorithm: 'gzip', ext: '.gz' }) ], base: './', // 打包路徑
 resolve: { // 設置別名
 alias: { '@': resolve('src'), // 解決vue-i18n警告You are running the esm-bundler build of vue-i18n. It is recommended to configure your bundler to explicitly replace feature flag globals with boolean literals to get proper tree-shaking in the final bundle.
      'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js' } }, server: { host: '0.0.0.0', https: false, port: 4000, // 啟動端口
    open: true, // proxy: {
    // // 選項寫法
    // '/api': 'http://xxxx'// 代理網址
    // },
    cors: true }, build: { // 生產環境移除 console
 terserOptions: { compress: { drop_console: true, drop_debugger: true } } } })

接着main.ts

/* * @Description: 入口文件 * @Version: 2.0 * @Autor: lhl * @Date: 2021-04-03 23:18:54 * @LastEditors: lhl * @LastEditTime: 2021-04-11 22:41:30 */ import { createApp } from 'vue' import App from './App.vue' import router from './router' import store from './store' import i18n from './locales/index' import Vant from 'vant' import 'vant/lib/index.css' // 全局引入樣式 // vite版本不需要配置組件的按需加載,因為Vant 3.0 內部所有模塊都是基於 ESM 編寫的,天然具備按需引入的能力
 console.log(import.meta.env.VITE_APP_BASE_URL, 'VITE_APP_BASE_URL') console.log(process.env.NODE_ENV) createApp(App).use(i18n).use(Vant).use(router).use(store).mount('#app')

接着是國際化的配置

/* * @Description: vscode自帶注釋 * @Version: 2.0 * @Autor: lhl * @Date: 2021-04-11 11:29:47 * @LastEditors: lhl * @LastEditTime: 2021-04-11 12:11:08 */ import { createI18n } from 'vue-i18n' import { getLanguage } from '../utils/cookies'

// Vant built-in lang
import { Locale } from 'vant' import enUS from 'vant/es/locale/lang/en-US' import zhCN from 'vant/es/locale/lang/zh-CN' import zhTW from 'vant/es/locale/lang/zh-TW' import jaJP from 'vant/es/locale/lang/ja-JP'

// User defined lang
import enUsLocale from './en_US/index' import zhCnLocale from './zh_CN/index' import zhTwLocale from './zh_TW/index' import jaJpLocale from './ja_JP/index' const messages: any = { 'zh-CN': { ...zhCN, ...zhCnLocale }, 'zh-TW': { ...zhTW, ...zhTwLocale }, 'en-US': { ...enUS, ...enUsLocale }, 'ja-JP': { ...jaJP, ...jaJpLocale } } export const getLocale = () => { const cookieLanguage = getLanguage() if (cookieLanguage) { document.documentElement.lang = cookieLanguage return cookieLanguage } const language = navigator.language.toLowerCase() const locales = Object.keys(messages) for (const locale of locales) { if (language.indexOf(locale) > -1) { document.documentElement.lang = locale return locale } } // Default language is english
  return 'en-US' } const CURRENT_LANG = getLocale() // first entry
Locale.use(CURRENT_LANG, messages[CURRENT_LANG]) const i18n = createI18n({ locale: CURRENT_LANG, messages }) export default i18n
/* * @Description: vscode自帶注釋 * @Version: 2.0 * @Autor: lhl * @Date: 2021-04-11 11:40:24 * @LastEditors: lhl * @LastEditTime: 2021-04-11 11:43:09 */ import Cookies from 'js-cookie'

// App
const languageKey = 'language' export const getLanguage = () => Cookies.get(languageKey) export const setLanguage = (language: string) => Cookies.set(languageKey, language)

新建四個ts文件存放四種語言;這里只放一種作為演示

/* * @Description: 中文 * @Version: 2.0 * @Autor: lhl * @Date: 2021-04-11 11:52:33 * @LastEditors: lhl * @LastEditTime: 2021-04-11 14:31:08 */ const zh_CN = { appHeader: { title: 'Vue App', selectLanguage: '語言選擇' }, goBack: { text: '返回' }, tabBarItem: { home: '首頁', product: '產品頁', vue3Study: 'vue3Study', myCenter: '個人中心' }, langSelect: { pickerTitle: '當前語言' } } export default zh_CN

興趣來了還封裝了一個語言選擇組件vant3 popup+piker組件合成

<!-- * @Description: vscode自帶注釋 * @Version: 2.0 * @Autor: lhl * @Date: 2021-04-10 21:56:40 * @LastEditors: lhl * @LastEditTime: 2021-04-11 14:08:24 -->
<template>
  <van-popup v-model:show="showPicker" v-bind="popupConfig">
    <van-picker show-toolbar swipe-duration="300" :title="$t('langSelect.pickerTitle')" :columns="langs" :default-index="defaultIndex" @confirm="onConfirm" @cancel="onClose"
    />
  </van-popup>
</template>

<script> import { defineComponent, toRefs, reactive, onMounted, getCurrentInstance, computed } from 'vue' import { useStore } from 'vuex' import { Locale } from 'vant' import { setLanguage } from '@/utils/cookies' export default defineComponent({ name: 'LangSelect', props: { popupConfig: { type: Object, default: () => ({ overlay: true, position: 'bottom', duration: 0.3, closeOnPopstate: true, transitionAppear: true, safeAreaInsetBottom: true }) } }, setup() { const state = reactive({ langs: [ { text: '中文(簡體)', value: 'zh-CN' }, { text: '中文(繁體)', value: 'zh-TW' }, { text: 'English', value: 'en-US' }, { text: '日本語', value: 'ja-JP' } ] }) const store = useStore() const { proxy } = getCurrentInstance() console.log(store, 'store', getCurrentInstance(), 'getCurrentInstance') const computedData = { showPicker: computed(() => store.getters.langPicker), defaultIndex: computed( () => state.langs.findIndex((item) => item.value === proxy.$i18n.locale) || 0 ) } const methods = { onConfirm({ value }) { // Vant basic
 Locale.use(value, proxy.$i18n.messages[value]) // Business component
 proxy.$i18n.locale = value // Cookie
 setLanguage(value) store.dispatch('changeShowPicker', false) }, onClose() { store.dispatch('changeShowPicker', false) } } return { ...toRefs(state), ...methods, ...computedData } } }) </script>

<style lang="scss" scoped></style>

最后給個文件目錄和效果圖吧

 

 

 再底部tabbar組件

<!-- * @Descripttion: 底部tabbar * @version: * @Author: lhl * @Date: 2021-04-06 16:29:07 * @LastEditors: lhl * @LastEditTime: 2021-04-14 10:47:34 -->
<template>
  <div>
    <van-tabbar v-model="active">
      <template v-for="(item, index) in tabbars" :key="index">
        <van-tabbar-item :icon="item.icon" :to="item.path">{{ $t(item.title) }}</van-tabbar-item>
      </template>
    </van-tabbar>
  </div>
</template>

<script lang="ts">
  // 注明v-for中的國際化需要自動注意 直接數組上 t(''message.menuItme.home') 不會更新
 import { defineComponent, onMounted, toRefs, reactive, ref, watch } from 'vue' import { useI18n } from 'vue-i18n' import { Toast } from 'vant' import { useRoute } from 'vue-router' export default defineComponent({ name: 'Tabbar', setup() { const { t } = useI18n() const active = ref(0) const state = reactive({ // active: 0,
        //函數接收一個普通對象,返回一個響應式的數據對象
 tabbars: [ { path: '/home', title: 'message.menuItme.home', icon: 'home-o' }, { path: '/product', title: 'message.menuItme.product', icon: 'coupon-o' }, { path: '/vue3Grammer', title: 'message.menuItme.study', icon: 'hot-o' }, { path: '/my', title: 'message.menuItme.my', icon: 'friends-o' } ] }) const route = useRoute() const methods = { initActive() { state.tabbars.map((item, index) => { if (item.path === route.path) { active.value = index } }) } } // watch的使用
 watch( () => route.path, (value) => { console.log('value改變', value) if (value) { let vIndex = state.tabbars.findIndex((item) => { return item.path == value }) console.log(vIndex, 'vIndex') if (vIndex > -1) { active.value = vIndex // Toast.success(t('message.menuItme.study'))
 } } } ) onMounted(() => { methods.initActive() }) return { active, ...methods, ...toRefs(state) } } }) </script>

<style lang="less"></style>

 再貼一個vuex配置

/*
 * @Descripttion: vuex配置入口
 */
import { createStore } from 'vuex'
const store = createStore({
  state: {
    langPicker: false,
    loading: false
  },
  mutations: {
    // 語言選擇框
    handleShowPicker(state) {
      state.langPicker = !state.langPicker
    },
    // 顯示loading
    showLoading(state) {
      state.loading = true
    },
    // 隱藏loading
    hideLoading(state) {
      state.loading = false
    }
  },
  getters: {
    langPicker: (state) => state.langPicker
  },
  actions: {
    changeShowPicker(context, value) {
      context.commit('handleShowPicker', value)
    }
  },
  modules: {}
})

export default store

再貼一個路由配置

/* * @Descripttion: 路由配置文件 參考文檔:https://next.router.vuejs.org/zh/introduction.html */ import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router' const routes: Array<RouteRecordRaw> = [ { path: '/', redirect: '/home', name: '/', component: () => import('@/components/layout/index.vue'), children: [ { path: '/home', // name: 'home', meta: { title: '首頁', i18Title: 'message.menuItme.home' }, component: () => import('@/pages/home/index.vue') }, { path: '/carDetail', name: 'carDetail', meta: { title: '購物車詳情', i18Title: 'message.menuItme.carDetail' }, component: () => import('@/pages/home/carDetail.vue') }, { path: '/product', name: 'product', meta: { title: '產品列表', i18Title: 'message.menuItme.product' }, component: () => import('@/pages/product/index.vue') }, { path: '/vue3Grammer', name: 'vue3Grammer', meta: { title: 'vue3語法', i18Title: 'message.menuItme.study' }, component: () => import('@/pages/vue3Grammer/index.vue') }, { path: '/my', name: 'my', meta: { title: '個人中心', i18Title: 'message.menuItme.my' }, component: () => import('@/pages/my/index.vue') } ] }, { path: '/404', name: '404', component: () => import('@/pages/notFound/index.vue') }, { path: '/:pathMatch(.*)', // 和以前配置有所不一樣 or /:catchAll(.*) redirect: '/404' } ] const router = createRouter({ history: createWebHashHistory(), routes }) console.log(router, 'router') // 路由前置鈎子 router.beforeEach((to, from, next) => { const title = to.meta && (to.meta.title as string) if (title) { document.title = title } next() }) router.afterEach((to, from) => { console.log(to, 'to', from, 'from') }) // 路由配置上定義 路由獨享的守衛 // beforeEnter: (to, from) => { // // reject the navigation // return false // }, // 導航守衛 // onBeforeRouteLeave, onBeforeRouteUpdate export default router

再貼一個vue3基礎用法

<!-- * @Descripttion: vue3語法 --tsx寫法(就是react 的jsx風格) ts寫法 .vue寫法 options API Composition API寫法都可以 -->
<template>
  <div class="vue3-grammer">
    <div class="num-box">{{ num }}----{{ newNum }}----{{ refData }}</div>
    <div class="change-num-btn">
      <van-button size="large" type="primary" @click="handleChange">改變數據</van-button>
    </div>
    <div class="from-box">
      <van-form @submit="onSubmit">
        <van-field v-model="username" name="username" label="用戶名" placeholder="用戶名" :rules="[{ required: true, message: '請填寫用戶名' }]" />
        <van-field v-model="password" type="password" name="password" label="密碼" placeholder="密碼" :rules="[{ required: true, message: '請填寫密碼' }]" />
        <child @reciveChildData="reciveFromChildData" />
        <div style="margin: 16px">
          <van-button round block type="primary" native-type="submit"> 提交表單 </van-button>
        </div>
      </van-form>
    </div>
    <!-- vant 組件使用 -->
    <van-button size="large" type="success" @click="goNextPage">路由跳轉</van-button>
  </div>
</template>

<script lang="ts">
  // defineComponent最重要的是:在TypeScript下,給予了組件 正確的參數類型推斷
 import { defineComponent, toRefs, reactive, getCurrentInstance, watch, computed, ref } from 'vue'
  // beforeCreate created -->用 setup 代替
 import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue' import { useRouter, useRoute } from 'vue-router' import child from './child.vue' export default defineComponent({ name: 'vue3Grammer', components: { child }, setup(props, context) { console.log(props, 'props', context, 'context') //這里的ctx 類似於vue2的this
      // 會報錯 且開發環境可以生產環境會報錯
      // const { ctx: any } = getCurrentInstance()
      // console.log(ctx, 'ctx')
      // 獲取組件實例 用於高階用法或庫的開發
      // internalInstance.appContext.config.globalProperties // 訪問 globalProperties
 const internalInstance = getCurrentInstance() console.log(internalInstance, 'internalInstance') // 訪問 globalProperties
 const refData = ref(12) //ref包裹 變為響應式對象
      // 個人覺着還是這樣寫舒服一點 類似於vue2中的data
 const state = reactive({ //函數接收一個普通對象,返回一個響應式的數據對象
 num: 0, username: '', password: '' }) //計算屬性 個人喜歡寫在對象中 因為看得更清晰一下 防止計算屬性方法等混在一起不好區分
 const computedData = { // 計算屬性寫法 別忘記引入 computed
 newNum: computed(() => state.num * 2) } const router = useRouter() console.log(router, 'this.$router') const route = useRoute() console.log(route, 'this.$route', route.meta) const methods = { // 改變數據
 handleChange: () => { state.num++
          // ref包裹的數據 必須用.value獲取
 refData.value++ }, // 提交表單
 onSubmit: (values: object) => { console.log('submit', values) }, // 跳轉
 goNextPage() { //路由跳轉
 router.push({ name: '/' }) }, // 父組件接收子組件的值
 reciveFromChildData(val: any) { console.log(val, 'val-來自子組件的值') state.username = val.username state.password = val.password } //網絡請求
        // main.js傳入的封裝axios
        // async getUser() {
        // try {
        // let { data } = await userApi()
        // console.log(data)
        // } catch (error) {
        // console.log(error)
        // }
        // }
 } onBeforeMount(() => { console.log('生命周期---等同於vue2的 beforeMount') }) onMounted(() => { // methods.getUser()
 console.log('生命周期---等同於vue2的 mounted') }) onBeforeUpdate(() => { console.log('生命周期---等同於vue2的 beforeUpdate') }) onUpdated(() => { console.log('生命周期---等同於vue2的 updated') }) onBeforeUnmount(() => { console.log('生命周期---等同於vue2的 beforeUnmount') }) onUnmounted(() => { console.log('生命周期---等同於vue2的 unmounted ') }) // watch的使用
 watch( () => state.num, (value) => { console.log('num改變', value) } ) return { ...toRefs(state), // 將響應式的對象變為普通對象 使用時不需要state.num 直接num即可使用
 ...methods, // 解構賦值
 ...computedData, refData } } }) </script>

<style lang="less" scoped> .num-box { background: #000; color: #fff; font-size: 16px; text-align: center; padding: 10px 0;
  } .change-num-btn { margin: 8px 0;
  }
</style>子組件
<!-- * @Descripttion: 子組件---vant3表單常用組件組合 --> <template> <div class="child"> <van-divider :style="{ color: '#1989fa', borderColor: '#1989fa', padding: '0 16px' }"> 子組件表單 </van-divider> <!-- 開關 --> <van-field name="switch" label="開關"> <template #input> <van-switch v-model="checked" size="20" /> </template> </van-field> <!-- 復選框組 --> <van-field name="checkboxGroup" label="學習框架"> <template #input> <van-checkbox-group v-model="groupChecked" direction="horizontal"> <van-checkbox name="1" shape="square">react</van-checkbox> <van-checkbox name="2" shape="square">vue</van-checkbox> </van-checkbox-group> </template> </van-field> <!-- 單選框 --> <van-field name="radio" label="多端"> <template #input> <van-radio-group v-model="checkedradio1" direction="horizontal"> <van-radio name="1">taro</van-radio> <van-radio name="2">uni-app</van-radio> </van-radio-group> </template> </van-field> <van-field name="radio" label="跨平台"> <template #input> <van-radio-group v-model="checkedradio2" direction="horizontal"> <van-radio name="3">Flutter</van-radio> <van-radio name="4">React native</van-radio> </van-radio-group> </template> </van-field> <!-- 步進器 --> <van-field name="stepper" label="步進器"> <template #input> <van-stepper v-model="count" /> </template> </van-field> <!-- 評分 --> <van-field name="rate" label="評分"> <template #input> <van-rate v-model="rateVal" /> </template> </van-field> <!-- 滑塊 --> <van-field name="slider" label="滑塊"> <template #input> <van-slider v-model="sliderVal" /> </template> </van-field> <!-- 文件上傳 --> <van-field name="uploader" label="文件上傳"> <template #input> <van-uploader v-model="uploadVal" /> </template> </van-field> <!-- Picker 組件 --> <van-field v-model="pickerVal" readonly clickable name="picker" label="選擇器" placeholder="點擊選擇城市" @click="showPicker = true" /> <van-popup v-model:show="showPicker" position="bottom"> <van-picker :columns="columns" @confirm="onConfirm" @cancel="showPicker = false" /> </van-popup> <!-- DatetimePicker 組件 --> <van-field v-model="datetimePickerVal" readonly clickable name="datetimePicker" label="時間選擇" placeholder="點擊選擇時間" @click="showDatetimePicker = true" /> <van-popup v-model:show="showDatetimePicker" position="bottom"> <van-datetime-picker type="time" @confirm="onDatetimePickerConfirm" @cancel="showDatetimePicker = false" /> </van-popup> <!-- 省市區選擇器 Area 組件 --> <van-field v-model="areaVal" readonly clickable name="area" label="地區選擇" placeholder="點擊選擇省市區" @click="showArea = true" /> <van-popup v-model:show="showArea" position="bottom"> <van-area :area-list="areaList" @confirm="onAreaConfirm" @cancel="showArea = false" /> </van-popup> <!-- 日歷 Calendar 組件 --> <van-field v-model="calendarVal" readonly clickable name="calendar" label="日歷" placeholder="點擊選擇日期" @click="showCalendar = true" /> <van-calendar v-model:show="showCalendar" @confirm="onCalendarConfirm" /> </div> </template> <script lang="ts"> //用到地區組件需要加載數據或者接口數據去拿 // yarn add @vant/area-data cnpm i @vant/area-data -S  import { defineComponent, reactive, toRefs } from 'vue' import { areaList } from '@vant/area-data' export default defineComponent({ name: 'child', setup(props, context) { console.log(props, context) const state = reactive({ username: '111', password: '222', checked: false, groupChecked: [], checkedradio1: '1', checkedradio2: '3', count: 1, // 最小就是1了  rateVal: 1, sliderVal: 10, uploadVal: [], pickerVal: '', showPicker: false, columns: ['北京', '上海', '廣州', '深圳'], datetimePickerVal: '', showDatetimePicker: false, areaVal: '', showArea: false, areaList, // 數據格式見 Area 組件文檔  calendarVal: '', showCalendar: false }) // 自定義事件  context.emit('reciveChildData', state) const methods = { onConfirm: (value: any) => { console.log(value, 'Picker組件') state.pickerVal = value state.showPicker = false }, onDatetimePickerConfirm: (value: any) => { console.log(value, '時間') state.datetimePickerVal = value state.showDatetimePicker = false }, onAreaConfirm: (value: any) => { console.log(value, '地區') state.showArea = false state.areaVal = value .filter((item: any) => !!item) .map((item: any) => item.name) .join('/') }, onCalendarConfirm: (date: any) => { console.log(date, '日期') state.calendarVal = `${date.getMonth() + 1}/${date.getDate()}`  state.showCalendar = false } } return { ...toRefs(state), ...methods } } }) </script> <style lang="less" scoped></style>

再貼一個axios封裝

/* * @Descripttion: api統一管理入口 */ import * as testApi from './testApi' export default { testApi }

 

/* * @Descripttion: 單獨api單獨配置 RESTful 風格 api * RESTful解釋 參考文檔:http://www.ruanyifeng.com/blog/2011/09/restful.html */ import request from './request'

/** * 封裝get請求 * @param {string} url 請求連接 * @param {Object} params 請求參數 * @param {Object} header 請求需要設置的header頭 */ export const getTest = (params: object, header: {}) => { return request({ url: `/posts`,
    method: 'get', params: params, headers: header }) } /** * 封裝post請求 * @param {string} url 請求連接 * @param {Object} data 請求參數 * @param {Object} header 請求的header頭 */ export const postTest = (data: object, header: {}) => { return request({ url: `/posts`,
    method: 'post', data: data, headers: header }) } /** * 封裝put請求 * @param {string} url 請求連接 * @param {Object} data 請求參數 * @param {Object} header 請求設置的header頭 */ export const putTest = (data: object, header: {}) => { return request({ url: `/posts/1`, method: 'put', data: data, headers: header }) } /** * 封裝delete請求 * 如果服務端將參數作為java對象來封裝接受 (data入參) * 如果服務端將參數作為url參數來接受,則請求的url為:www.xxx/url?a=1&b=2形式 (params入參) * @param {string} url 請求連接 * @param {Object} params 請求參數 * @param {Object} header 請求設置的header頭 */ export const deleteTest = (params: object, header: {}) => { return request({ url: `/posts/1`, method: 'delete', data: params, headers: header }) }
/* * @Descripttion: aioxs二次封裝 */ import axios, { AxiosRequestConfig, AxiosResponse, Method } from 'axios' import store from '../store' import { Toast } from 'vant'

// 定義接口
interface PendingType { url?: string method?: Method params: any data: any cancel: Function } // 取消重復請求
 const repeatRequstList: Array<PendingType> = [] // 使用 cancel token 取消請求
const CancelToken = axios.CancelToken console.log(import.meta.env.VITE_APP_BASE_URL, 'axios') const instance = axios.create({ // baseURL: <string>import.meta.env.VITE_APP_BASE_URL, // 給類型 不然報錯
  baseURL: import.meta.env.VITE_APP_BASE_URL as string, // 斷言 不然報錯
  // 指定請求超時的毫秒數(0 表示無超時時間)
  timeout: 10000, // 表示跨域請求時是否需要使用憑證
  withCredentials: true }) // let loadingInstance: ElLoadingComponent

// 移除重復請求
const removePending = (config: AxiosRequestConfig) => { for (const key in repeatRequstList) { console.log(key, 'key') const item: number = +key const list: PendingType = repeatRequstList[key] // 當前請求在數組中存在時執行函數體
    if (list.url === config.url && list.method === config.method && JSON.stringify(list.params) === JSON.stringify(config.params) && JSON.stringify(list.data) === JSON.stringify(config.data)) { // 執行取消操作
      list.cancel('操作太頻繁,請稍后再試') // 從數組中移除記錄
      repeatRequstList.splice(item, 1) } } } // 添加請求攔截器
instance.interceptors.request.use( (config) => { store.commit('showLoading') removePending(config) config.cancelToken = new CancelToken((c) => { repeatRequstList.push({ url: config.url, method: config.method, params: config.params, data: config.data, cancel: c }) }) return config }, (error: any) => { store.commit('hideLoading') return Promise.reject(error) } ) // 添加響應攔截器
instance.interceptors.response.use( (response: AxiosResponse) => { store.commit('hideLoading') removePending(response.config) return response.data }, (error: any) => { store.commit('hideLoading') const { response } = error // 根據返回的 http 狀態碼做不同的處理 ?.語法需要最新的谷歌瀏覽器才支持目前
    // switch (response?.status) {
    // case 401:
    // // token失效
    // break
    // case 403:
    // // 沒有權限
    // break
    // case 404:
    // // 請求的資源不存在
    // break
    // case 500:
    // // 服務端錯誤
    // Toast('提示內容')
    // break
    // default:
    // break
    // }

    // 超時重新請求
    const config = error.config // 全局的請求次數,請求的間隙
    const [RETRY_COUNT, RETRY_DELAY] = [3, 1000] if (config && RETRY_COUNT) { // 設置用於跟蹤重試計數的變量
      config.__retryCount = config.__retryCount || 0
      // 檢查是否已經把重試的總數用完
      if (config.__retryCount >= RETRY_COUNT) { return Promise.reject(response || { message: error.message }) } // 增加重試計數
      config.__retryCount++

      // 創造新的Promise來處理指數后退
      // 在typescript中定義promise返回類型 首先要在tsconfig.json中配置ES2015.promise的lib 不然ts無法支持promise
      const backClose = new Promise((resolve) => { setTimeout(() => { // 寫上 null or undefined 不然ts下面會報錯
          resolve(null) }, RETRY_DELAY || 1) }) // instance重試請求的Promise
      return backClose.then(() => { return instance(config) }) } // eslint-disable-next-line
    return Promise.reject(response || { message: error.message }) } ) export default instance
/* * @Descripttion: * @version: * @Author: lhl * @Date: 2021-04-02 15:03:55 * @LastEditors: lhl * @LastEditTime: 2021-04-14 18:13:04 */ import { createApp } from 'vue' import App from './App.vue' import router from './router' import store from './store' import Vant from 'vant' import Vconsole from 'vconsole' import 'vant/lib/index.css' import i18n from './language/index' import API from './api/index' import 'normalize.css/normalize.css' import './assets/style/index.less' console.log(import.meta.env.VITE_APP_BASE_URL, '環境變量') const isProd = process.env.NODE_ENV === 'production'
if (!isProd) { new Vconsole() } const app = createApp(App) app.config.globalProperties.API = API console.log(app.config.globalProperties.API, 'app.config.globalProperties.API') app.use(i18n).use(router).use(store).use(Vant).mount('#app')

再貼個vscode的個人配置

{
    "workbench.colorTheme": "One Dark Pro",
    "fileheader.customMade": {
        "Description": "vscode自帶注釋",
        "Version": "2.0",
        "Autor": "lhl",
        "Date": "Do not edit",
        "LastEditors": "lhl",
        "LastEditTime": "Do not edit"
      },
      "fileheader.cursorMode": {
        "description":"",
        "param": "",
        "return": "",
        "author":"lhl"
      },
      "[javascript]": {
        "editor.defaultFormatter": "HookyQR.beautify"
      },
      // "[jsonc]": {
      //   "editor.defaultFormatter": "HookyQR.beautify"
      // },
      "javascript.updateImportsOnFileMove.enabled": "always",
      // "emmet.excludeLanguages": [
      //   "markdown"
      // ],
      "emmet.includeLanguages": {
        "javascript": "javascriptreact"
      },
      "emmet.triggerExpansionOnTab": true,
      "eslint.codeAction.showDocumentation": {
        "enable": true
      },

      //===========================================
      //============= Editor ======================
      //===========================================
      "explorer.openEditors.visible": 0,
      "editor.tabSize": 2,
      "editor.renderControlCharacters": true,
      "editor.minimap.renderCharacters": false,
      "editor.minimap.maxColumn": 300,
      "editor.minimap.showSlider": "always",
      "editor.cursorBlinking": "phase",
      "editor.cursorSmoothCaretAnimation": true,
      "editor.detectIndentation": false,
      "editor.defaultFormatter": "esbenp.prettier-vscode",
      "diffEditor.ignoreTrimWhitespace": false,
      "javascript.format.insertSpaceBeforeFunctionParenthesis": true,
      "editor.suggestSelection": "first",
      "editor.trimAutoWhitespace": true,
      "editor.quickSuggestions": {
        "other": true,
        "comments": true,
        "strings": true
      },
      //===========================================
      //============= Other =======================
      //===========================================
      "breadcrumbs.enabled": true,
      "open-in-browser.default": "chrome",
      //===========================================
      //============= emmet =======================
      //===========================================
      "emmet.showAbbreviationSuggestions": true,
      "emmet.showExpandedAbbreviation": "always",
      "emmet.syntaxProfiles": {
        "vue-html": "html",
        "vue": "html",
        "xml": {
          "attr_quotes": "single"
        }
      },
      //===========================================
      //============= files =======================
      //===========================================
      "files.trimTrailingWhitespace": true,
      "files.insertFinalNewline": true,
      "files.trimFinalNewlines": true,
      "files.eol": "\n",
      "search.exclude": {
        "**/node_modules": true,
        "**/*.log": true,
        "**/*.log*": true,
        "**/bower_components": true,
        "**/dist": true,
        "**/elehukouben": true,
        "**/.git": true,
        "**/.gitignore": true,
        "**/.svn": true,
        "**/.DS_Store": true,
        "**/.idea": true,
        "**/.vscode": false,
        "**/yarn.lock": true,
        "**/tmp": true,
        "out": true,
        "dist": true,
        "node_modules": true,
        "CHANGELOG.md": true,
        "examples": true,
        "res": true,
        "screenshots": true
      },
      "files.exclude": {
        "**/bower_components": true,
        "**/.idea": true,
        "**/tmp": true,
        "**/.git": true,
        "**/.svn": true,
        "**/.hg": true,
        "**/CVS": true,
        "**/.DS_Store": true
      },
      "files.watcherExclude": {
        "**/.git/objects/**": true,
        "**/.git/subtree-cache/**": true,
        "**/.vscode/**": true,
        "**/node_modules/**": true,
        "**/tmp/**": true,
        "**/bower_components/**": true,
        "**/dist/**": true,
        "**/yarn.lock": true
      },
      // ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
      // ===========================================
      // ================ Eslint ===================
      // ===========================================
      "eslint.alwaysShowStatus": true,
      "eslint.options": {
        "plugins": ["html", "vue", "javascript", "jsx", "typescript"],
        "extensions": [".js", ".jsx", ".ts", ".tsx", ".vue"]
      },
      "eslint.validate": [
        "javascript",
        "typescript",
        "reacttypescript",
        "reactjavascript",
        "html",
        "vue"
      ],
      // ===========================================
      // ================ Vetur ====================
      // ===========================================
      "vetur.experimental.templateInterpolationService": true,
      "vetur.format.options.tabSize": 2,
      "vetur.format.defaultFormatter.html": "js-beautify-html",
      "vetur.format.defaultFormatter.scss": "prettier",
      "vetur.format.defaultFormatter.css": "prettier",
      "vetur.format.defaultFormatter.ts": "prettier-tslint",
      "vetur.format.defaultFormatter.js": "prettier",
      "vetur.languageFeatures.codeActions": false,
      "vetur.format.defaultFormatterOptions": {
        "js-beautify-html": {
          "wrap_attributes": "force-expand-multiline"
        },
        "prettier": {
          "eslintIntegration": true,
          "arrowParens": "always",
          "semi": false,
          "singleQuote": true
        }
      },
      "terminal.integrated.rendererType": "dom",
      "telemetry.enableCrashReporter": false,
      "telemetry.enableTelemetry": false,
      "workbench.settings.enableNaturalLanguageSearch": false,
      "path-intellisense.mappings": {
        "/@/": "${workspaceRoot}/src"
      },
      "prettier.requireConfig": true,
      "typescript.updateImportsOnFileMove.enabled": "always",
      "workbench.sideBar.location": "left",
      "[javascriptreact]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
      },
      "[typescript]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
      },
      "[typescriptreact]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
      },
      "[html]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
      },
      "[css]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
      },
      "[less]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
      },
      "[scss]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
      },
      // "[markdown]": {
      //   "editor.defaultFormatter": "esbenp.prettier-vscode"
      // },
      "editor.codeActionsOnSave": {
        "source.fixAll.eslint": true
      },
      "[vue]": {
        "editor.codeActionsOnSave": {
          "source.fixAll.eslint": false
        }
      },
      "terminal.integrated.automationShell.windows": "F:\\Git\\bin\\bash.exe",
      "terminal.integrated.shell.windows": "F:\\Git\\bin\\bash.exe",
      "editor.formatOnSave": true,
      "editor.formatOnPaste": true,
      "editor.formatOnType": true,
      "files.autoSave": "afterDelay",
  }

 好嘞嘗鮮到此結束~明天繼續搬磚ing!!!!內容為自己總結原創,未經同意,請勿隨意轉載~謝謝合作~~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM