Vue 3 常用代码片段
通用组件结构
Section titled “通用组件结构”<template> <div class="my-component"> <slot /> </div></template>
<script setup lang="ts">interface Props { title: string disabled?: boolean}
const props = withDefaults(defineProps<Props>(), { disabled: false})
const emit = defineEmits<{ (e: 'click', event: MouseEvent): void}>()</script>
<style scoped>.my-component {}</style>带加载状态的按钮
Section titled “带加载状态的按钮”<template> <button :disabled="loading || disabled" @click="handleClick" > <span v-if="loading">加载中...</span> <slot v-else /> </button></template>
<script setup lang="ts">const props = defineProps<{ loading?: boolean disabled?: boolean}>()
const emit = defineEmits<{ (e: 'click'): void}>()
const handleClick = () => { if (!props.loading && !props.disabled) { emit('click') }}</script>import { ref, reactive } from 'vue'
interface FormState { username: string email: string password: string}
interface FormErrors { username?: string email?: string password?: string}
function useForm() { const form = reactive<FormState>({ username: '', email: '', password: '' })
const errors = reactive<FormErrors>({})
const validate = () => { let valid = true
if (!form.username) { errors.username = '请输入用户名' valid = false }
if (!form.email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email)) { errors.email = '请输入正确的邮箱' valid = false }
if (!form.password || form.password.length < 6) { errors.password = '密码至少6位' valid = false }
return valid }
const reset = () => { Object.assign(form, { username: '', email: '', password: '' }) Object.keys(errors).forEach(key => delete errors[key as keyof FormErrors]) }
return { form, errors, validate, reset }}import { ref, onUnmounted } from 'vue'
function useDebounce(fn: Function, delay: number) { let timer: ReturnType<typeof setTimeout>
const debounced = (...args: any[]) => { clearTimeout(timer) timer = setTimeout(() => fn(...args), delay) }
const cancel = () => clearTimeout(timer)
onUnmounted(cancel)
return { debounced, cancel }}
function useThrottle(fn: Function, delay: number) { let lastTime = 0
const throttled = (...args: any[]) => { const now = Date.now() if (now - lastTime >= delay) { fn(...args) lastTime = now } }
return throttled}function useClipboard() { const copied = ref(false)
const copy = async (text: string) => { try { await navigator.clipboard.writeText(text) copied.value = true setTimeout(() => copied.value = false, 2000) return true } catch { return false } }
const read = async () => { try { return await navigator.clipboard.readText() } catch { return null } }
return { copied, copy, read }}Axios 实例
Section titled “Axios 实例”import axios from 'axios'import { useUserStore } from '@/stores/user'
const request = axios.create({ baseURL: import.meta.env.VITE_API_URL, timeout: 10000})
request.interceptors.request.use(config => { const userStore = useUserStore() if (userStore.token) { config.headers.Authorization = `Bearer ${userStore.token}` } return config})
request.interceptors.response.use( response => response.data, error => { if (error.response?.status === 401) { const userStore = useUserStore() userStore.logout() window.location.href = '/login' } return Promise.reject(error) })
export default request