mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-27 06:41:12 +00:00
feat: 添加演示模块并优化日期选择器组件
refactor: 重构文件导入组件为通用组件 fix: 修复导出文件名统一问题 docs: 更新README添加二次开发教程 style: 统一系统管理页面的日期选择器实现 chore: 更新数据库迁移脚本和初始化数据 perf: 优化前端页面日期范围选择交互 test: 添加演示模块相关测试文件 build: 更新依赖项配置 ci: 调整CI/CD脚本配置
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
<!-- 日期选择器 -->
|
||||
<template>
|
||||
<div class="custom-date-picker">
|
||||
<el-date-picker
|
||||
:model-value="modelValue"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:shortcuts="shortcuts"
|
||||
@update:model-value="(val) => emit('update:model-value', val)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, defineEmits } from 'vue'
|
||||
|
||||
// 定义组件属性
|
||||
defineProps({
|
||||
modelValue: {
|
||||
type: Array as unknown as () => [Date, Date] | [],
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
// 定义事件
|
||||
const emit = defineEmits(['update:model-value'])
|
||||
|
||||
// 快捷选项配置
|
||||
const shortcuts = [
|
||||
{
|
||||
text: '近一天',
|
||||
value: () => {
|
||||
const end = new Date()
|
||||
const start = new Date()
|
||||
return [start, end]
|
||||
}
|
||||
},
|
||||
{
|
||||
text: '近三天',
|
||||
value: () => {
|
||||
const end = new Date()
|
||||
const start = new Date()
|
||||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 3)
|
||||
return [start, end]
|
||||
}
|
||||
},
|
||||
{
|
||||
text: '近一周',
|
||||
value: () => {
|
||||
const end = new Date()
|
||||
const start = new Date()
|
||||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7)
|
||||
return [start, end]
|
||||
}
|
||||
},
|
||||
{
|
||||
text: '近一个月',
|
||||
value: () => {
|
||||
const end = new Date()
|
||||
const start = new Date()
|
||||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
|
||||
return [start, end]
|
||||
}
|
||||
},
|
||||
{
|
||||
text: '近三个月',
|
||||
value: () => {
|
||||
const end = new Date()
|
||||
const start = new Date()
|
||||
start.setTime(start.getTime() - 3600 * 1000 * 24 * 90)
|
||||
return [start, end]
|
||||
}
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.custom-date-picker {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,250 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 导入弹窗 -->
|
||||
<el-dialog v-model="importModalVisible" :align-center="true" :title="props.title" :width="props.width" @close="handleClose">
|
||||
<!-- 滚动 -->
|
||||
<el-scrollbar :max-height="props.maxHeight">
|
||||
<!-- 表单 -->
|
||||
<el-form ref="importFormRef" style="padding-right: var(--el-dialog-padding-primary)" :model="importFormData" :rules="importFormRules">
|
||||
<el-form-item label="文件名" prop="files">
|
||||
<el-upload ref="uploadRef" v-model:file-list="importFormData.files" class="w-full" :accept="props.accept" :drag="true" :limit="props.limit" :auto-upload="false" :on-exceed="handleFileExceed">
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">
|
||||
{{ props.dropText || '将文件拖到此处,或' }}
|
||||
<em>{{ props.browseText || '点击上传' }}</em>
|
||||
</div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip flex flex-wrap gap-2">
|
||||
<el-text v-if="props.note" type="warning" class="mx-1">{{ props.note }}</el-text>
|
||||
<el-text v-if="props.fileTypeWarning" type="danger" class="mx-1">{{ props.fileTypeWarning }}</el-text>
|
||||
<el-link v-if="props.showTemplateDownload" class="mx-1" type="primary" icon="download" underline="never" @click="handleDownloadTemplate">
|
||||
{{ props.templateDownloadText || '下载模板' }}
|
||||
</el-link>
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-scrollbar>
|
||||
<template #footer>
|
||||
<div style="padding-right: var(--el-dialog-padding-primary)">
|
||||
<el-button @click="handleClose">{{ props.cancelButtonText || '取 消' }}</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:disabled="importFormData.files.length === 0 || loading"
|
||||
@click="handleUpload"
|
||||
>
|
||||
<el-icon v-if="loading"><Loading /></el-icon>
|
||||
{{ props.confirmButtonText || '确 定' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ElMessage, type UploadUserFile } from "element-plus";
|
||||
import { ref, reactive, defineProps, defineEmits, defineModel } from "vue";
|
||||
|
||||
// 定义props
|
||||
const props = defineProps({
|
||||
/**
|
||||
* 弹窗标题
|
||||
*/
|
||||
title: {
|
||||
type: String,
|
||||
default: "导入数据"
|
||||
},
|
||||
|
||||
/**
|
||||
* 弹窗宽度
|
||||
*/
|
||||
width: {
|
||||
type: String,
|
||||
default: "600px"
|
||||
},
|
||||
|
||||
/**
|
||||
* 最大高度
|
||||
*/
|
||||
maxHeight: {
|
||||
type: String,
|
||||
default: "60vh"
|
||||
},
|
||||
|
||||
/**
|
||||
* 接受的文件类型
|
||||
*/
|
||||
accept: {
|
||||
type: String,
|
||||
default: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
|
||||
},
|
||||
|
||||
/**
|
||||
* 文件数量限制
|
||||
*/
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
|
||||
/**
|
||||
* 是否显示下载模板按钮
|
||||
*/
|
||||
showTemplateDownload: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
|
||||
/**
|
||||
* 拖放提示文本
|
||||
*/
|
||||
dropText: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
|
||||
/**
|
||||
* 浏览按钮文本
|
||||
*/
|
||||
browseText: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
|
||||
/**
|
||||
* 模板下载按钮文本
|
||||
*/
|
||||
templateDownloadText: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
|
||||
/**
|
||||
* 取消按钮文本
|
||||
*/
|
||||
cancelButtonText: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
|
||||
/**
|
||||
* 确认按钮文本
|
||||
*/
|
||||
confirmButtonText: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
|
||||
/**
|
||||
* 注意事项文本
|
||||
*/
|
||||
note: {
|
||||
type: String,
|
||||
default: "注意事项:"
|
||||
},
|
||||
|
||||
/**
|
||||
* 文件类型警告文本
|
||||
*/
|
||||
fileTypeWarning: {
|
||||
type: String,
|
||||
default: "格式为*.xlsx / *.xls,文件不超过 5MB"
|
||||
},
|
||||
|
||||
/**
|
||||
* 上传文件的参数名
|
||||
*/
|
||||
uploadFileName: {
|
||||
type: String,
|
||||
default: "file"
|
||||
},
|
||||
|
||||
/**
|
||||
* 上传请求的额外参数
|
||||
*/
|
||||
uploadData: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
});
|
||||
|
||||
// 定义模型值(控制弹窗显示/隐藏)
|
||||
const importModalVisible = defineModel("modelValue", {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
default: false
|
||||
});
|
||||
|
||||
// 定义事件
|
||||
const emit = defineEmits(["import-success", "import-fail", "close", "download-template", "upload"]);
|
||||
|
||||
// 引用
|
||||
const importFormRef = ref(null);
|
||||
const uploadRef = ref(null);
|
||||
const loading = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const importFormData = reactive<{
|
||||
files: UploadUserFile[];
|
||||
}>({
|
||||
files: []
|
||||
});
|
||||
|
||||
// 表单规则
|
||||
const importFormRules = {
|
||||
files: [{ required: true, message: "文件不能为空", trigger: "blur" }]
|
||||
};
|
||||
|
||||
// 文件超出个数限制
|
||||
const handleFileExceed = () => {
|
||||
ElMessage.warning(`只能上传${props.limit}个文件`);
|
||||
};
|
||||
|
||||
// 下载导入模板 - 由父组件实现具体逻辑
|
||||
const handleDownloadTemplate = () => {
|
||||
emit("download-template");
|
||||
};
|
||||
|
||||
// 上传文件 - 由父组件实现具体逻辑
|
||||
const handleUpload = async () => {
|
||||
if (!importFormData.files.length) {
|
||||
ElMessage.warning("请选择文件");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
const file = importFormData.files[0].raw as File;
|
||||
const formData = new FormData();
|
||||
formData.append(props.uploadFileName, file);
|
||||
|
||||
// 添加额外参数
|
||||
Object.keys(props.uploadData).forEach(key => {
|
||||
formData.append(key, props.uploadData[key]);
|
||||
});
|
||||
|
||||
// 触发上传事件,由父组件处理具体上传逻辑
|
||||
emit("upload", formData, file);
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
ElMessage.error("上传失败:" + error);
|
||||
emit("import-fail", error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 关闭弹窗
|
||||
const handleClose = () => {
|
||||
importFormData.files.length = 0;
|
||||
importModalVisible.value = false;
|
||||
emit("close");
|
||||
};
|
||||
|
||||
// 提供给父组件的方法
|
||||
defineExpose({
|
||||
handleClose
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user