封装页面切换hooks
本 hooks 主要是将需要重复书写上一页下一页的切换函数进行封装
// composables/usePagination.ts
export function usePagination(fetchCallback: () => void) {
const pageNum = ref(1)
const pageSize = ref(20)
/**
* pageSize 改变触发
*/
const handleSizeChange = (size: number) => {
pageSize.value = size
fetchCallback()
}
/**
* 页码改变触发
*/
const handleCurrentChange = (page: number) => {
pageNum.value = page
fetchCallback()
}
return {
pageNum,
pageSize,
handleSizeChange,
handleCurrentChange,
}
}
使用
// 分页 hooks 封装 传入请求数据函数
const { pageNum, pageSize, handleCurrentChange, handleSizeChange } = usePagination(getListData)
封装全局 loading
本 hooks 是封装全局的 loading 感觉 element-plus 进行封装
// composables/useGlobalLoading.ts
import { ElLoading } from 'element-plus'
import type { LoadingOptions } from 'element-plus'
/**
* 立即使用全局 loading,并返回控制对象
*/
export const useGlobalLoading = (text: string = '加载中...', options: Partial<LoadingOptions> = {}) => {
const loadingInstance = ElLoading.service({
lock: true,
text,
background: 'rgba(255, 255, 255, 0.6)',
...options,
})
return {
/** 手动关闭 */
close: () => loadingInstance.close(),
}
}
/**
* 自动 loading 包裹器:执行传入异步函数,并自动显示/关闭 loading
*/
export async function withGlobalLoading<T>(fn: () => Promise<T>, text = '加载中...') {
const loading = useGlobalLoading(text)
try {
const res = await fn()
return res
} finally {
loading.close()
}
}
这里有 两种方式进行使用 一种是 使用的 普通方式 第二种是Promise
时间格式化
本 hooks 是结合 dayjs 将时间进行格式化 含时区矫正
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc'
import timezone from 'dayjs/plugin/timezone'
// 安装插件(只需一次)
dayjs.extend(utc)
dayjs.extend(timezone)
/**
* 转换时间格式并将时区改成 东八区
* @returns
*/
export const useFormatTime = () => {
const formatTime = (val: string, format = 'YYYY-MM-DD HH:mm:ss') => {
if (!val) return '--'
return dayjs.utc(val).tz('Asia/Shanghai').format(format)
}
return {
formatTime,
}
}
文件上传封装
组件封装 结合 element-plus 进行二次封装
<!-- AvatarUpload.vue -->
<template>
<el-upload
class="avatar-uploader"
:action="uploadUrl"
:headers="{ Authorization: `Bearer ${token}` }"
:show-file-list="false"
:on-success="onSuccess"
:before-upload="onBeforeUpload">
<el-image v-if="modelValue" :src="modelValue" class="w-[88px] h-[88px] shadow-md rounded-sm" />
<iconify-icon v-else icon-name="ep:plus" class="size-4 text-[#8c939d] text-center" />
</el-upload>
</template>
<script setup lang="ts">
import store from '@/utils/store'
import { ElMessage, type UploadProps } from 'element-plus'
defineProps<{ modelValue: string }>()
const emit = defineEmits(['update:modelValue'])
// 获取 token
const token = computed(() => {
return store.get('login_token').token
})
// 请求地址
const uploadUrl = 'http://127.0.0.1:8900/api/upload-image' // 可传入 props
// 上传成功后的 回调
const onSuccess: UploadProps['onSuccess'] = (response, uploadFile) => {
if (response.type === 'success') {
ElMessage.success('图片上传成功')
emit('update:modelValue', response?.data[0].url)
}
}
// 上传前的回调
const onBeforeUpload: UploadProps['beforeUpload'] = (rawFile) => {
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']
if (!allowedTypes.includes(rawFile.type)) {
ElMessage.error('头像必须是 jpg/png/gif/webp 格式的图片!')
return false
} else if (rawFile.size / 1024 / 1024 > 2) {
ElMessage.error('最大 2MB 图片')
return false
}
return true
}
</script>
<style>
.avatar-uploader .el-upload {
width: 88px;
height: 88px;
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}
.avatar-uploader .el-upload:hover {
border-color: var(--el-color-primary);
}
</style>
接口规范 RESTful 风格
操作 | 方法 | 地址示例 | 参数方式 |
---|---|---|---|
查询列表 | GET | /user/list | query params |
新增用户 | POST | /user/add | body |
编辑用户 | PUT | /user/:uid | body + params |
删除用户 | DELETE | /user/:uid | params |
mongodb 建模关系
结构 | 表示的关系类型 | 示例说明 |
---|---|---|
toc: Object | 一对一(One-to-One) | 单个 toc 对象,结构平铺 |
toc: [Object] | ✅ 一对多(One-to-Many) | 多个 toc 项构成一个数组 |
toc: [ObjectId] | 一对多,引用关系 | 每项是外键引用,通常关联其他表 |