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