vite2.0+vue3+ts前端最新热门技术项目搭建

vue

闲来无事,趁假期来加强一波技能学习!不废话直接上流程上图;

超级简洁和极致的本地开发体验!!!

 来个项目简介吧;

<!--

* @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!!!!内容为自己总结原创,未经同意,请勿随意转载~谢谢合作~~

以上是 vite2.0+vue3+ts前端最新热门技术项目搭建 的全部内容, 来源链接: utcz.com/z/377068.html

回到顶部