Vue3+ts;枚举(enum);Partial全部可选/Pick选一部分/配置 svg 图标/unplugin-vue-components组件自动按需加载

news2024/11/24 7:29:13

项目的创建

使用 create-vue 脚手架创建项目。

1.执行创建命令

pnpm create vue
# or
npm init vue@latest
# or
yarn create vue

2.选择项目依赖内容。

✔ Project name://项目名
✔ Add TypeScript? … No / `Yes`
✔ Add JSX Support?`No` / Yes
✔ Add Vue Router for Single Page Application development? … No / `Yes`
✔ Add Pinia for state management? … No / `Yes`
✔ Add Vitest for Unit Testing?`No` / Yes
✔ Add Cypress for both Unit and End-to-End testing?`No` / Yes
✔ Add ESLint for code quality? … No / `Yes`
✔ Add Prettier for code formatting? … No / `Yes`

Scaffolding project in /Users/zhousg/Desktop/patient-h5-100...

Done. Now run:

  cd //项目名
  pnpm install
  pnpm lint
  pnpm dev

项目目录结构调整

./src
├── assets        # 静态资源,图片...
├── components    # 通用组件
├── composable    # 组合功能通用函数 - 新增的
├── icons         # svg 图标
├── router        # 路由
│   └── index.ts
├── services      # 接口服务 API - 新增的
├── stores        # 状态仓库
├── styles        # 样式 				- 新增的
│   └── main.scss
├── types         # TS 类型			- 新增的
├── utils         # 工具函数			- 新增的
├── views         # 页面				- 新增的
├── App.vue        # 根组件
└── main.ts       # 入口文件

默认生成的路由相关代码解析

import {
    createRouter,
    createWebHistory
} from 'vue-router'

// createRouter 创建路由实例
// createWebHistory() 是开启history模块
// createWebHashHistory() 是开启hash模式

// vite 的配置 import.meta.env.BASE_URL 是路由的基准地址,默认是 ’/‘
// https://vitejs.dev/guide/build.html#public-base-path
// 如果将来你部署的域名路径是:http://xxx/my-path/user
// vite.config.ts 添加配置 base: my-path,路由这就会加上 my-path 前缀了

const router = createRouter({
    history: createWebHistory(import.meta.env.BASE_URL),
    routes: []
})

export default router

用户状态仓库

  1. 准备用户信息的类型,types/user.d.ts
/* 用户信息 */
export type User = {
  /* token令牌 */
  token: string
  /* 用户ID */
  id: string
  /* 用户名称 */
  account: string
  /* 手机号 */
  mobile: string
  /* 头像 */
  avatar: string
}

2.设置和删除用户信息,stores/user.ts

import type { User } from '@/types/user'
import { defineStore } from 'pinia'
import { ref } from 'vue'

export const useUserStore = defineStore('cp-user', () => {
    // 用户信息
    const user = ref < User > ()
    // 设置用户,登录后使用
    const setUser = (u: User) => {
        user.value = u
    }
    // 清空用户,退出后使用
    const delUser = () => {
        user.value = undefined
    }
    return {
        user,
        setUser,
        delUser
    }
})

数据的持久化

使用 pinia-plugin-persistedstate 实现 Pinia 仓库状态持久化,且完成测试。

  1. 安装。
pnpm i pinia-plugin-persistedstate
# or
npm i pinia-plugin-persistedstate
# or
yarn add pinia-plugin-persistedstate

2.使用 main.ts

++ import persist from 'pinia-plugin-persistedstate'
const app = createApp(App)
// 注意使用的方式
++ app.use(createPinia().use(persist))

3.配置 stores/user.ts

import type { User } from '@/types/user'
import { defineStore } from 'pinia'
import { ref } from 'vue'

export default defineStore(
    'cp-user',
    () => {
        // 用户信息
        const user = ref<User>()
        // 设置用户,登录后使用
        const setUser = (u: User) => {
            user.value = u
        }
        // 清空用户,退出后使用
        const delUser = () => {
            user.value = undefined
        }
        return { user, setUser, delUser }
    },
++    {
++        persist: true
++    }
)

4.测试 App.vue

<script setup lang="ts">
    // #1
    import useUserStore from './stores/user'
    // #2
    const store = useUserStore()
</script>

<template>
    <!-- #3 -->
    <p>{{ store.user }}</p>
    <!-- 如果这校验失败,可以修改 .eslintrc.cjs 中的 printWidth -->
    <button @click="store.setUser({ id: '1', mobile: '1', account: '1', avatar: '1', token: '1' })">
        登录
    </button>
    <button @click="store.delUser()">退出</button>
</template>

抽取 Pinia 代码(纯代码优化)

抽取 Pinia 实例代码

stores/index

import { createPinia } from 'pinia'
import persist from 'pinia-plugin-persistedstate'

import { useUserStore } from './user'

export const useStore = () => {
  return {
    userStore: useUserStore()
  }
}
// 创建 Pinia 实例
const pinia = createPinia()
// 使用 Pinia 插件
pinia.use(persist)
// 导出 Pinia 实例,给 main 使用
export default pinia

main.ts

import App from './App.vue'
import router from './router'
import pinia from './stores'

const app = createApp(App)

app.use(pinia)
app.use(router)
app.mount('#app')

stores 统一导出

统一导出,代码简洁,入口唯一, store/index

// 写法 1
// import { useUserStore } from './user'
// export { useUserStore }

// 写法 2
// export { useUserStore } from './user'

// 写法 3
export * from './user'

App.vue

import { useUserStore } from './stores'

vant 组件库使用

[vant]https://vant-contrib.gitee.io/vant/#/zh-CN/quickstart#dao-ru-suo-you-zu-jian-bu-tui-jian。

1.安装vant

# Vue3 项目,安装最新版 Vant
npm i vant
# 通过 yarn 安装
yarn add vant
# 通过 pnpm 安装
pnpm add vant

2.引入样式,main.ts

import { createApp } from 'vue'
import App from './App.vue'
import pinia from './stores'
import router from './router'
// 样式全局使用
import 'vant/lib/index.css'
import './styles/main.scss'

const app = createApp(App)

app.use(pinia)
app.use(router)
app.mount('#app')

3.组件按需使用,App.vue

<script setup lang="ts">
    import {
        Button as VanButton
    } from 'vant'
</script>

<template>
    <van-button type="primary">按钮</van-button>
</template>

实现 Vant 组件的自动按需加载

1.安装

pnpm add unplugin-vue-components -D

2.配置,vite.config.ts

import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// #1
++ import Components from 'unplugin-vue-components/vite'
// #2
++ import { VantResolver } from 'unplugin-vue-components/resolvers'

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [
        vue(),
        // #3 自动导入的插件
++        Components({
++            // #5 默认 true,开启自动生成组件的类型定义文件,而 vant 已经自带类型了,无需生成
++            dts: false,
++            // #4 main.ts 已经引入了所有的 vant 样式,不需要自动导入样式,只需要自动导入组件即可
++            resolvers: [VantResolver({ importStyle: false })]
++        })
    ],
    resolve: {
        alias: {
            '@': fileURLToPath(new URL('./src', import.meta.url))
        }
    }
})

3.组件中直接使用即可。

移动端的适配(使用 vw 完成移动端适配)

  1. 安装(https://vant-contrib.gitee.io/vant/#/zh-CN/advanced-usage#viewport-bu-ju)
npm install postcss-px-to-viewport -D
# or
yarn add -D postcss-px-to-viewport
# or
pnpm add -D postcss-px-to-viewport

2.配置 postcss.config.js (没有就自己创建)

// eslint-disable-next-line no-undef
module.exports = {
    plugins: {
        'postcss-px-to-viewport': {
            // 以设备宽度 375 为基准计算 vw 的值
            // 假如 100px 的 div
            // 375 宽度下,最终转换出的 vw 应该是:x / 100vw = 100px / 375px,x 等于 26.66vw
            // 而转换出来的 26.66vw 自然在不同的设备宽度下所表示的 div 宽度会不一样,例如 414 设备下
            // 26.66vw / 100vw = div 宽度 / 414px
            viewportWidth: 375,
        },
    },
};

3.重启项目

在这里插入图片描述

请求实例封装

1.基本模板准备,utils/request.ts

import axios from 'axios'

const instance = axios.create({
    // TODO 1. 基础地址,超时时间
})

instance.interceptors.request.use(
    (config) => {
        // TODO 2. 携带 token
        return config
    },
    (err) => Promise.reject(err)
)

instance.interceptors.response.use(
    (res) => {
        // TODO 3. 处理业务失败
        // TODO 4. 摘取核心响应数据
        return res
    },
    (err) => {
        // TODO 5. 处理 401 错误
        return Promise.reject(err)
    }
)

export default instance

2.基本封装。

import { useUserStore } from '@/stores'
import router from '@/router'
import axios from 'axios'
import { showToast } from 'vant'

// 1. 新 axios 实例,基础配置
const baseURL = 'https://XXXX/'   //基地址
const instance = axios.create({
    baseURL,
    timeout: 10000
})

// 2. 请求拦截器,携带 token
instance.interceptors.request.use(
    (config) => {
        const store = useUserStore()
        if (store.user?.token && config.headers) {
            config.headers['Authorization'] = `Bearer ${store.user?.token}`
        }
        return config
    },
    (err) => Promise.reject(err)
)

// 3. 响应拦截器,剥离无效数据,401 拦截
instance.interceptors.response.use(
    (res) => {
        // 后台约定,响应成功,但是 code 不是 10000,是业务逻辑失败
        if (res.data?.code !== 10000) {
            showToast(res.data?.message || '网络异常')
            return Promise.reject(res.data)
        }
        // 业务逻辑成功,返回响应数据,作为 axios 成功的结果
        return res.data
    },
    (err) => {
        if (err.response.status === 401) {
            // 删除用户信息
            const store = useUserStore()
            store.delUser()
            // 跳转登录,带上接口失效所在页面的地址,登录完成后回跳使用
            router.push(`/login?returnUrl=${router.currentRoute.value.fullPath}`)
        }
        return Promise.reject(err)
    }
)

export { baseURL, instance }

请求函数封装

导出一个通用的请求工具函数,支持设置响应数据类型。(utils/request.ts接着写)

import axios, { type Method } from 'axios'
// 4. 请求工具函数
const request = (url: string, method: Method = 'get', submitData?: object) => {
    return instance.request({
        url,
        method,
        [method.toLowerCase() === 'get' ? 'params' : 'data']: submitData
    })
}

instance.request 的第二个泛型参数会被直接当做返回数据的类型。

const request = (url: string, method: Method = 'get', submitData?: object) => {
  // !第二个泛型参数会被直接当做返回数据的类型
    return instance.request<User, {
        code: string
        message: string
        data: User
    }>({
        url,
        method,
        [method.toLowerCase() === 'get' ? 'params' : 'data']: submitData
    })
}

request('/user').then((res) => {
    res.
})

User 类型肯定不能写死,继续封装 request 泛型函数。

// #1
const request = <T>(
    url: string,
    method: Method = 'get',
    submitData?: object
) => {
    // #2
    return instance.request<
        T,
        {
            code: string
            message: string
            data: T
        }
    >({
        url,
        method,
        [method.toLowerCase() === 'get' ? 'params' : 'data']: submitData
    })
}

// #3
request<User>('/user').then((res) => {
    res.data.account
})

继续抽离第二个泛型参数为一个泛型 type。

type Data<T> = {
    code: number
    message: string
    data: T
}
const request = <T>(url: string, method: Method = 'get', submitData?: object) => {
    return instance.request<T, Data<T>>({
        url,
        method,
        [method.toLowerCase() === 'get' ? 'params' : 'data']: submitData
    })
}

用户登录

  1. 准备页面结构,views/Login/index.vue
<template>
    <div class="login-page">
        登录
    </div>
</template>
  1. 配置路由规则,router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
    history: createWebHistory(import.meta.env.BASE_URL),
    routes: [
        { path: '/login', component: () => import('@/views/Login/index.vue') }
    ]
})
export default router
  1. 准备路由出口,App.vue
<template>
    <router-view></router-view>
</template>

CSS 主题定制

  1. 如何定义和使用 CSS 变量
:root {    --main: #999;  }
a { color: var(--main)}
  1. 覆盖 vant 主题色, styles/main.scss
:root {
    --cp-primary: #16c2a3;
    --cp-plain: #eaf8f6;
    --cp-orange: #fca21c;
    --cp-text1: #121826;
    --cp-text2: #3c3e42;
    --cp-text3: #6f6f6f;
    --cp-tag: #848484;
    --cp-dark: #979797;
    --cp-tip: #c3c3c5;
    --cp-disable: #d9dbde;
    --cp-line: #ededed;
    --cp-bg: #f6f7f9;
    --cp-price: #eb5757;
    // 覆盖 Vant 主体色
    // 官方文档:ConfigProvider 全局配置
    --van-primary-color: var(--cp-primary);
}
  1. 测试主题色,views/Login/index.vue
<script lang="ts" setup>
    import {  Button as VanButton  } from 'vant'
</script>
<template>
    <div class="login-page">
        <van-button type="primary">按钮</van-button>
    </div>
</template>

CpNavBar 组件封装

掌握 van-nav-bar 组件,封装自己的nav-bar 组件。

在这里插入图片描述

components/CpNavBar.vue

<script setup lang="ts">
    import {
        useRouter
    } from 'vue-router'

    const router = useRouter()
    // #1 点击左侧返回
    const onClickLeft = () => {
        // 判断历史记录中是否有回退记录
        if (history.state?.back) {
            router.back()
        } else {
            router.push('/')
        }
    }

    // #2 接收 title 和 rightText
    defineProps < {
        title ? : string
        rightText ? : string
    } > ()

    // #3 右侧的点击,让外界决定做什么事情
    const emit = defineEmits < {
        (e: 'click-right'): void
    } > ()
    const onClickRight = () => {
        emit('click-right')
    }
</script>

<template>
    <van-nav-bar left-arrow @click-left="onClickLeft" fixed :title="title" :right-text="rightText" @click-right="onClickRight"></van-nav-bar>
</template>

表单校验

在这里插入图片描述

提取表单校验规则(为了其他页面复用)

utils/rules.ts

// 表单校验
const mobileRules = [
  { required: true, message: '请输入手机号' },
  { pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确' }
]

const passwordRules = [
  { required: true, message: '请输入密码' },
  { pattern: /^\w{8,24}$/, message: '密码需8-24个字符' }
]

export { mobileRules, passwordRules }

单个表单项校验

Login/index.vue

import { mobileRules, passwordRules } from '@/utils/rules'
const mobile = ref('13212345678')
const password = ref('13212345678')
const agree = ref(false)
+    <van-field v-model="mobile" :rules="mobileRules" placeholder="请输入手机号" type="tel"></van-field>
      <van-field
        v-model="password"
+       :rules="passwordRules"
        placeholder="请输入密码"
        type="password"
      >

整体表单校验

思路: 按钮上 native-type="submit" + 表单 @submit 它会自动去校验填写了rules的字段。

  1. 设置button组件为原生 submit 类型按钮

Login/index.vue

<van-button 
  block round 
  type="primary" 
  native-type="submit"> 登 录 </van-button>
  1. 监听表单submit 事件
<van-form autocomplete="off" @submit="login">
import { mobileRules, passwordRules } from '@/utils/rules'
import { ref } from 'vue'
import { showToast } from 'vant'
const mobile = ref('13212345678')
const password = ref('13212345678')
const show = ref(false)
const agree = ref(false)

// 表单提交
const login = () => {
  if (!agree.value) return showToast('请勾选我已同意')
  // 验证完毕,进行登录
}

图标组件-配置 svg 图标

安装插件

pnpm install vite-plugin-svg-icons -D

配置插件

import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import Components from 'unplugin-vue-components/vite'
import { VantResolver } from 'unplugin-vue-components/resolvers'
// #1
++ import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
// #2
++ import path from 'path'
// https://vitejs.dev/config/
export default defineConfig({
    plugins: [
        vue(),
        Components({
            dts: false,
            resolvers: [VantResolver({ importStyle: false })]
        }),
        // #3
++        createSvgIconsPlugin({
++            iconDirs: [path.resolve(process.cwd(), 'src/icons')]
++        })
    ],
    resolve: {
        alias: {
            '@': fileURLToPath(new URL('./src', import.meta.url))
        }
    }
})

从main.ts导入

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import pinia from './stores'
// mark
++ import 'virtual:svg-icons-register'
import 'vant/lib/index.css'
import './styles/main.scss'
const app = createApp(App)
app.use(pinia)
app.use(router)
app.mount('#app')

测试使用 svg 精灵地图(页面中)

<svg aria-hidden="true">
    <!-- #icon-文件夹名称-图片名称 -->
    <use href="#icon-login-eye-off" />
</svg>

图标组件-封装 svg 组件

components/CpIcon.vue

<script setup lang="ts">
    // 提供 name 属性即可
    defineProps < {
        name: string
    } > ()
</script>

<template>
    <svg aria-hidden="true" class="cp-icon">
        <use :href="`#icon-${name}`" />
    </svg>
</template>

<style lang="scss" scoped>
    .cp-icon {
        // 和 font-size 一样大
        width: 1em;
        height: 1em;
    }
</style>

类型 types/components.d.ts。

import CpNavBar from '@/components/CpNavBar.vue'
import CpIcon from '@/components/CpIcon.vue'

declare module 'vue' {
    interface GlobalComponents {
        CpNavBar: typeof CpNavBar
        CpIcon: typeof CpIcon
    }
}

有些图标可以根据 style 中 color 的值来设置颜色,图标是否有这个功能取决于 UI 做图片时否开启。
测试使用

<cp-icon  name="login-eye-on"></cp-icon>
<cp-icon  name="login-eye-off"></cp-icon>

切换密码可见

双向绑定数据,并实现切换密码可见功能,views/Login/index.vue

在这里插入图片描述

// #1 定义表单数据
const mobile = ref('')
const password = ref('')
// #3 定义控制密码是否显示的变量
++ const show = ref(false)
<!-- #2 v-model 进行绑定 -->
<van-field v-model="mobile" placeholder="请输入手机号" type="tel"></van-field>
<!-- #4 把 show 作用于框的类型 -->
<van-field v-model="password" placeholder="请输入密码" :type="show ? 'text' : 'password'">
    <template #button>
     <!-- #5 把 show 作用于图标的类型 -->
     <!-- #6 榜单点击事件,控制 show 的切换-->
     <cp-icon @click="show = !show" :name="`login-eye-${show ? 'on' : 'off'}`"></cp-icon>
    </template>
</van-field>

手机号密码登录

定义登录的 API 函数

services/user.ts

import type { User } from '@/types/user'
import { request } from '@/utils/request'

// 密码登录
export const loginByPassword = (mobile: string, password: string) => request<User>('/login/password', 'POST', { mobile, password })

进行登录

views/Login/index.vue

import { loginByPassword } from '@/services/user'
import { useUserStore } from '@/stores'
import { useRoute, useRouter } from 'vue-router'
const store = useUserStore()
const router = useRouter()
const route = useRoute()
// 表单提交
const login = async () => {
    if (!agree.value) return Toast('请勾选我已同意')
    // 验证完毕,进行登录
    const res = await loginByPassword(mobile.value, password.value)
    store.setUser(res.data)
    // 如果有回跳地址就进行回跳,没有跳转到个人中心
    router.replace((route.query.returnUrl as string) || '/user') 
    showToast('登录成功')
}

短信登录-切换效果

在这里插入图片描述

添加短信登录登录的界面切换功能,添加 code 的校验。

views/Login/index.vue

// #1 定义一个切换的变量
const isPass = ref(true)
<div class="login-head">
    <!-- #2 控制标题 -->
    <h3>{{ isPass ? '密码登录' : '短信验证码登录' }}</h3>
    <!-- #4 给 a 链接绑定点击事件,修改 isPass -->
    <a href="javascript:;" @click="isPass = !isPass">
        <!-- #3 控制按钮内容 -->
        <span>{{ !isPass ? '密码登录' : '短信验证码登录' }}</span>
        <van-icon name="arrow"></van-icon>
    </a>
</div>

完成表单项切换

<van-field v-if="isPass" v-model="password" :rules="passwordRules" placeholder="请输入密码" :type="show ? 'text' : 'password'">
    <template #button>
     <cp-icon @click="show = !show" :name="`login-eye-${show ? 'on' : 'off'}`"></cp-icon>
    </template>
</van-field>
<van-field v-else placeholder="短信验证码">
    <template #button>
        <span class="btn-send">发送验证码</span>
    </template>
</van-field>

完成 code 校验

utils/rules.ts 补充定义校验规则

const codeRules = [
    { required: true, message: '请输入验证码' },
    { pattern: /^\d{6}$/, message: '验证码6个数字' }
]

export { mobileRules, passwordRules, codeRules }

使用规则 views/Login/index.vue

<script lang="ts" setup>
    import {  codeRules } from '@/utils/rules'
    const code = ref('')
</script>
<van-field v-else v-model="code" :rules="codeRules" placeholder="短信验证码">

短信登录-发送短信

实现点击按钮发送验证码和倒计时的功能。

  1. 可以先去定义 CodeType 的类型(types/user.d.ts)
// 短信验证码类型,登录|注册|修改手机号|忘记密码|绑定手机号
export type CodeType = 'login' | 'register' | 'changeMobile' | 'forgetPassword' | 'bindMobile'
  1. 封装 API 接口函数(services/user.ts)
import { request } from '@/utils/request'
import type { CodeType } from '@/types/user'
// 发送验证码
export const sendMobileCode = (mobile: string, type: CodeType) => request('/code', 'GET', { mobile, type })
  1. 给发送验证码按钮绑定点击事件和回调,校验是否正在倒计时(views/Login/index.vue)
<span class="btn-send" @click="send">发送验证码</span>
const time = ref(0)
const send = async () => {
    // 如果正在进行倒计时,此时 time.value 的值大于 0,不允许发送验证码
    if (time.value > 0) return
}
  1. 点击发送验证码时,单独校验手机表单项
<!-- 绑定 name 属性 -->
<van-field v-model="mobile" name="mobile" :rules="mobileRules" placeholder="请输入手机号" type="tel"></van-field>
import { type FormInstance } from 'vant'
const form = ref<FormInstance>()
const time = ref(0)
const send = async () => {
    if (time.value > 0) return
    // 验证不通过报错,阻止程序继续执行
    await form.value?.validate('mobile')
}
<!-- 把 form 变量和 van-form 的 ref 属性进行绑定 -->
<van-form autocomplete="off" @submit="login" ref="form">
  1. 发送短信验证码。
import {  sendMobileCode } from '@/services/user'
import { showToast } from 'vant'
const send = async () => {
    // ...
    await sendMobileCode(mobile.value, 'login')
    showToast('发送成功')
}

进行倒计时

      <van-field v-else v-model="code" :rules="codeRules" placeholder="短信验证码">
        <template #button>
          <span v-if="time === 0" class="btn-send" @click="send">发送验证码</span>
          <span v-else class="btn-send">{{ time }}秒后重新发送</span>
        </template>
      </van-field>
import { type FormInstance } from 'vant'  //
const form = ref<FormInstance>()   
const time = ref(0)
const send = async () => {
  // 如果正在进行倒计时,此时 time.value 的值大于 0,不允许发送验证码
  if (time.value > 0) return
  // 验证不通过报错,阻止程序继续执行
  await form.value?.validate('mobile') //调用表单实例的validate方法,验证手机号是否填写正确。
  await sendMobileCode(mobile.value, 'login')    //发送获取验证码的请求
  showToast('发送成功')
  const startTimer = () => {
    const duration: number = 10
    time.value = duration
    const timerInterval = setInterval(() => {
      time.value--
      if (time.value === 0) {
        clearInterval(timerInterval)
      }
    }, 1000)
  }
  startTimer()
}

短信登录-进行登录

1. 封装接口 API,services/user.ts

// 短信登录
export const loginByMobile = (mobile: string, code: string) => request<User>('/login', 'POST', { mobile, code })

2. 合并短信登录,views/Login/index.vue

const login = async () => {
  if (!agree.value) return showToast('请勾选我已同意')
  if (code.value !== '') {
    const res = await loginByCode(mobile.value, code.value)
    store.setUser(res.data)
  } else {
    // 验证完毕,进行登录
    const res = await loginByPassword(mobile.value, password.value)
    store.setUser(res.data)
  }
  // 如果有回跳地址就进行回跳,没有跳转到个人中心
  router.replace((route.query.returnUrl as string) || '/user')
  showToast('登录成功')
}

枚举基本语法

作用:表示一组明确可选的值,使用该类型后,约定只能使用这组常量中的其中一个,和字面量类型配合联合类型类似。

enums/index.ts

export {}
// 默认是从 0 开始自增的数值
// 也可以指定初始值,Up = 10,后面是从 10 开始自增
// 成员也可以是字符串,例如 Up = 'Up',但是后面的值都需要使用字符串
enum Direction {
  Up,
  Down,
  Left,
  Right
}

// 使用枚举类型
const changeDirection = (direction: Direction) => {
  console.log(direction)
}

// 好处:不会传错且具有语义
changeDirection(Direction.Up)

枚举使用场景

枚举可用于给一组没有语义的可选值,给它们添加类型。

比如后台给的数据:

  • 0 是男,1 是女;
  • 1 是待付款,5 是已付款,8 是已完成

通过枚举可以让成员更加语义化,提高代码可读性。

export {}
// 性别
enum GenderType {
    Boy,
    Girl
}
const showGender = (gender: GenderType) => {
    if (gender === GenderType.Boy) {
        console.log('性别:男')
    }
}
showGender(GenderType.Boy)

// 订单状态
enum OrderStatus {
    UnPay = 1,
    Payed = 5,
    Complete = 8
}
const showOrderStatus = (status: OrderStatus) => {
    if (status === OrderStatus.Complete) {
        console.log('状态:已完成')
    }
}
showOrderStatus(OrderStatus.Complete)

既然枚举类型也可以当做值使用,所以它不能够写在 d.ts 文件中。

Partial全部可选/Pick选一部分

import { ConsultType, IllnessTime, ConsultLevel } from '@/enums'

export type Consult = {
    id: string
    type: ConsultType
    illnessType: ConsultLevel
    depId: string
    illnessDesc: string
    illnessTime: IllnessTime
    consultFlag: 0 | 1
    pictures: Image[]
    patientId: string
    couponId: string
}

// 全部可选,将所有的数据类型都变成可选的(?)
++ export type PartialConsult = Partial<Consult>

// 从PartialConsult中选一部分属性,生成新类型
++  export type ConsultIllness = Pick< PartialConsult, 'illnessDesc' | 'illnessTime' >

**枚举使用场景**

枚举可用于给一组**没有语义**的可选值,给它们添加类型。

比如后台给的数据:

- 0 是男,1 是女;
- 1 是待付款,5 是已付款,8 是已完成

通过枚举可以让成员更加语义化,提高代码可读性。

```js
export {}
// 性别
enum GenderType {
    Boy,
    Girl
}
const showGender = (gender: GenderType) => {
    if (gender === GenderType.Boy) {
        console.log('性别:男')
    }
}
showGender(GenderType.Boy)

// 订单状态
enum OrderStatus {
    UnPay = 1,
    Payed = 5,
    Complete = 8
}
const showOrderStatus = (status: OrderStatus) => {
    if (status === OrderStatus.Complete) {
        console.log('状态:已完成')
    }
}
showOrderStatus(OrderStatus.Complete)

既然枚举类型也可以当做值使用,所以它不能够写在 d.ts 文件中。

Partial全部可选/Pick选一部分

import { ConsultType, IllnessTime, ConsultLevel } from '@/enums'

export type Consult = {
    id: string
    type: ConsultType
    illnessType: ConsultLevel
    depId: string
    illnessDesc: string
    illnessTime: IllnessTime
    consultFlag: 0 | 1
    pictures: Image[]
    patientId: string
    couponId: string
}

// 全部可选,将所有的数据类型都变成可选的(?)
++ export type PartialConsult = Partial<Consult>

// 从PartialConsult中选一部分属性,生成新类型
++  export type ConsultIllness = Pick< PartialConsult, 'illnessDesc' | 'illnessTime' >

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/754508.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

macOS 开发 - 纯代码生成 Window

文章目录 1、创建项目删除项目自带 window创建 BaseWindowController 继承自 NSWindowController子 WC 继承 BaseWC个人更喜欢纯代码:控制力、方便复制,不用卡卡的打开 xib 这里不使用各种项目自带的 storyboard/xib,使用纯代码创建 window(controller) 本示例代码将创建如下…

记录一下Mybatis中的if标签使用时遇到的问题

记录一下Mybatis中的if标签使用时遇到的问题 前言一、if标签遇到的问题二、if标签中""和的问题字符串等于条件的两种写法&#xff1a; 三 、总结 前言 今天在项目中进行查询时使用了if标签&#xff0c;遇到了问题&#xff1a; 开始时拉过代码来的时候是这样的 <…

Java关键字interface(接口)

文章目录 接口的理解接口的声明接口的成员说明接口的使用规则类实现接口&#xff08;implements&#xff09; 接口的多实现接口的多继承(extends)接口与实现类对象构成多态引用使用接口的静态成员使用接口的非静态方法JDK8中相关冲突问题总结小测试接口与抽象类之间的对比练习 …

不允许你不知道的 MySQL 优化实战(二)

文章目录 11、使用联合索引时&#xff0c;注意索引列的顺序&#xff0c;一般遵循最左匹配原则。12、对查询进行优化&#xff0c;应考虑在where及order by涉及的列上建立索引&#xff0c;尽量避免全表扫描。13、如果插入数据过多&#xff0c;考虑批量插入。14、在适当的时候&…

[QT编程系列-9]:C++图形用户界面编程,QT框架快速入门培训 - 3- QT窗体设计 - 自动布局

目录 3. QT窗体设计 3.7 自动布局 3.7.1 自动布局 3.7.2 在主窗口中自动布局 3.7.3 在自动布局容器中自动布局 3.7.4 在widget中自动布局 3.7.5 自动布局工件 3. QT窗体设计 3.7 自动布局 3.7.1 自动布局 在QT中&#xff0c;自动布局是一种灵活而强大的方式来管理和排…

【ArcGIS Pro二次开发】(47):要素类追加至空库(批量)

本工具主要是针对国空数据入库而做的。 如果你手头已经整理了一部分要素类数据&#xff0c;但是数据格式&#xff0c;字段值可能并没有完全按照规范设置好&#xff0c;需要将这些数据按规范批量和库&#xff0c;就可以尝试用这个工具。 准备数据&#xff1a;标准空库、你已做…

Vue源码分析拓展 - Vue 模板编译渲染函数原理分析

目录 Vue 模板编译渲染函数 编译 Vue 模板编译渲染函数原理分析.html compiletoFunctions.html compileToFunctions.js vue.2.5.1.源码学习.js 一张AI生成图~ Vue 模板编译渲染函数 new Vue():初始化 $mount:挂载 compile():编译 parse:解析 optimize&#xff1a;静态节…

身份证读卡器安卓SDK在安卓12版本targetSdkVersion=32报错解决办法

之前的东信智能的EST-100身份证读卡器安卓SDK版本V1.0.40在安卓12版本&#xff0c;targetSdkVersion32的时候会出现以下错误&#xff1a; Targeting S (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingInten…

Nginx 解决漏洞扫描 弱CORS策略

主要在nginx配置允许通过的地址 如&#xff1a; if ($http_host !~* 192.168.0.1|127.0.0.1|localhost) { # 允许的ipreturn 403 ; }add_header Access-Control-Allow-Origin $http_origin; #跨域请求

高压放大器到底有什么作用

高压放大器是一种重要的电子元器件&#xff0c;其作用是将信号放大到更高的电压水平&#xff0c;以便供给需要高电压的负载使用。高压放大器被广泛应用于通讯设备、医疗仪器、仿真模拟、气体激光、光学器件等领域。下面安泰电子将详细介绍高压放大器的作用以及其在各领域中的应…

Win11 设置FTP服务详细教程

起因&#xff1a; 因测试需要&#xff0c;本机建立FTP服务测试使用&#xff0c;此文章用于记录使用&#xff01; 操作步骤&#xff1a; 1、配置FTP功能 ①、"winR" > 在运行窗口输入"control" 回车&#xff1b; ②、打开"控制面板" > 点击…

图文讲解Redis延时双删原因及必要性

目录 一、前言 二、常见更新策略 2.1 先删缓存&#xff0c;再更新数据库 2.2 先更新数据库&#xff0c;再删除缓存 2.3 普通双删 2.4 延迟双删 三、建议 一、前言 我们在实际项目中经常会使用到Redis缓存用来缓解数据库压力&#xff0c;但是当更新数据库时&#xff0c;…

今天实习第三天,vue(cli部分)

01.创建第一个vue-cli。这里用的是node.js。早上的时候&#xff0c;就需要把node.js安装上去 02.node.js安装 第一步.去官网下载node.js https://nodejs.org/en 第二步.运行官网下载的node.js的msi文件&#xff08;记住所有的node.js文件的安装包都是msi文件的形式&#xff0…

算法笔记\python 笔记: 相似性度量

1 欧氏距离 1.1 python实现&#xff1a; from scipy.spatial import distance distance.euclidean([1,2],[2,1]) #1.4142135623730951 1.2 标准化欧氏距离 先将数据标准化 &#xff08;减去的均值两两抵消&#xff09; 2 曼哈顿距离 又称为城市街区距离 2.1 python 实现 f…

C++图形开发(16):绘制一个圆环和一根针

文章目录 绘制一个圆环和一根针1.1 绘制1.2 line()函数1.3 circle()函数1.4 setlinestyle()函数1.5 setlinecolor()函数 接下来&#xff0c;我会继续制作一些小游戏&#xff0c;但因为整个难度的上升&#xff08;毕竟我也是初学者&#xff09;&#xff0c;可能文章不会再像之前…

吐血整理,性能测试-Jmeter分布式压测实战(超细详解)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 Jmeter的集群模式…

InsCode Stable Diffusion使用教程【InsCode Stable Diffusion美图活动一期】

记录一下如何使用 InsCode Stable Diffusion 进行 AI 绘图以及使用感受。 一、背景介绍 目前市面上比较权威&#xff0c;并能用于工作中的 AI 绘画软件其实就两款。一个叫 Midjourney&#xff08;简称 MJ&#xff09;&#xff0c;另一个叫 Stable Diffusion&#xff08;简称 …

Unity游戏源码分享-Unity经营类美食小摊小游戏

Unity经营类美食小摊小游戏 挺有意思的小游戏 关卡页面 游戏主页面 有顾客上门 需要给顾客搭配他们想要的美食 会不断地有顾客过来&#xff0c;这个时候就考验手速的时候了&#xff0c;真实模拟经营 服务到位立马有钱 项目地址&#xff1a; https://download.csdn.net/downl…

VisualStudio2022将printf信息打印到控制台

点击“解决方案管理器”&#xff0c;选中项目名称&#xff0c;点击鼠标右键---->属性---->生成事件---->生成后事件&#xff0c;在命令行的右侧输入框里填写如下内容&#xff1a; editbin /SUBSYSTEM:CONSOLE $(OUTDIR)\$(ProjectName).exe接下来在编译运行时&#x…

数据库--->MySQL(2)【事务、SQL优化】

文章目录 事务什么是事务&#xff1f;隔离性中的不同隔离级别事务实现的原理隔离级别的实现原理&#xff08;MVCC&#xff09;MySQL中的锁机制 SQL优化 事务 什么是事务&#xff1f; 事务就是逻辑上的一组操作&#xff0c;在同一个事务中&#xff0c;如果有多条sql语句执行&am…