文件导入弹框

This commit is contained in:
qiuyuan 2025-10-10 11:44:56 +08:00
parent 91837700df
commit 6537e6f919
3 changed files with 357 additions and 35 deletions

View File

@ -0,0 +1,285 @@
<template>
<!-- 绑定本地visible变量 -->
<a-modal
v-model="localVisible"
title="批量导入"
:maskClosable="false"
@cancel="handleCancel"
:footer="null"
width="500px"
>
<!-- 原有内容不变 -->
<div class="import-container">
<div class="import-section" :class="{ 'section-top': true }">
<h3 class="section-title">
<span class="required-mark">*</span>下载模板填写表格信息
</h3>
<p class="section-desc">
请按照数据模板格式准备数据模板中的表头名称不可更改表头行不能删除单次导入的数据不超过1000条
</p>
<div class="file-info">
<a-icon type="file-excel-o" class="file-icon" theme="outlined" />
<div class="file-detail">
<p class="file-format">支持格式.xlsx .xls</p>
<a-button
type="primary"
class="action-btn"
@click="handleDownloadTemplate"
>
下载模板
</a-button>
</div>
</div>
</div>
<div class="import-section upload-section" :class="{ 'section-bottom': true }">
<h3 class="section-title">
<span class="required-mark">*</span>上传填好的表格信息
</h3>
<p class="section-desc">
文件后缀名必须为xls或xlsx即Excel格式文件大小不得超过10M
</p>
<div class="file-info">
<a-icon type="file-excel-o" class="file-icon" theme="filled" />
<div class="file-detail">
<p class="file-format">支持格式.xlsx .xls</p>
<a-upload
:showUploadList="false"
:beforeUpload="beforeUpload"
:customRequest="handleFileUpload"
>
<a-button
type="primary"
class="action-btn"
>
选择表格
</a-button>
</a-upload>
<div v-if="selectedFile" class="selected-file">
<span class="file-name">{{ selectedFile.name }}</span>
<a-icon
type="close-circle"
class="remove-icon"
@click="clearSelectedFile"
/>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<a-button @click="handleCancel">取消</a-button>
<a-button
type="primary"
@click="handleConfirmUpload"
:disabled="!selectedFile"
>
上传
</a-button>
</div>
</a-modal>
</template>
<script setup>
import { ref, defineProps, defineEmits, watch } from 'vue';
import { message } from 'ant-design-vue';
// 1. propv-model
const props = defineProps({
modelValue: { // modelValuev-model
type: Boolean,
default: false
}
});
// 2.
const emit = defineEmits(['update:modelValue', 'upload', 'download']);
// 3. v-model
const localVisible = ref(props.modelValue);
const selectedFile = ref(null);
// 4. modelValue
watch(
() => props.modelValue,
(newVal) => {
localVisible.value = newVal;
}
);
// 5.
watch(
() => localVisible.value,
(newVal) => {
emit('update:modelValue', newVal);
}
);
//
const handleCancel = () => {
localVisible.value = false; //
selectedFile.value = null;
};
//
const handleDownloadTemplate = () => {
emit('download');
};
//
const beforeUpload = (file) => {
const isExcel = file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|| file.type === 'application/vnd.ms-excel';
if (!isExcel) {
message.error('请上传Excel格式的文件.xls或.xlsx');
return false;
}
const isLt10M = file.size / 1024 / 1024 < 10;
if (!isLt10M) {
message.error('文件大小不能超过10M');
return false;
}
selectedFile.value = file;
return false;
};
//
const handleFileUpload = () => {
//
};
//
const clearSelectedFile = () => {
selectedFile.value = null;
};
//
const handleConfirmUpload = () => {
if (selectedFile.value) {
emit('upload', selectedFile.value);
localVisible.value = false; //
}
};
</script>
<style scoped>
.import-container {
background-color: #ffffff;
border-radius: 4px;
overflow: hidden;
}
.import-section {
padding: 16px 16px;
background-color: #fafafa;
transition: all 0.3s ease;
}
.import-section.section-top {
/* border-bottom: 1px solid #e8e8e8; */
margin-bottom: 8px;
border-radius: 4px 4px 0 0;
}
.import-section.section-bottom {
border-radius: 0 0 4px 4px;
}
.section-title {
margin: 0 0 8px 0;
font-size: 14px;
font-weight: 500;
color: rgba(0, 0, 0, 0.85);
}
.required-mark {
color: #ff4d4f;
margin-right: 4px;
}
.section-desc {
margin: 0 0 16px 0;
font-size: 12px;
color: rgba(0, 0, 0, 0.65);
line-height: 1.5;
}
.file-info {
display: flex;
align-items: center;
padding: 12px;
/* background-color: #f5f5f5; */
border-radius: 4px;
}
.file-icon {
font-size: 48px;
color: #00b42a;
margin-right: 16px;
}
.file-detail {
flex: 1;
}
.file-format {
margin: 0 0 8px 0;
font-size: 12px;
color: rgba(0, 0, 0, 0.75);
}
.action-btn {
margin-bottom: 8px;
}
.selected-file {
display: flex;
align-items: center;
font-size: 12px;
color: rgba(0, 0, 0, 0.9);
max-width: 300px;
padding: 4px 0;
}
.file-name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-right: 8px;
background-color: #fff;
padding: 2px 6px;
border-radius: 2px;
border: 1px solid #e8e8e8;
}
.remove-icon {
color: rgba(0, 0, 0, 0.45);
cursor: pointer;
transition: color 0.3s;
}
.remove-icon:hover {
color: #ff4d4f;
}
.modal-footer {
display: flex;
justify-content: flex-end;
padding: 16px;
border-top: 1px solid #f0f0f0;
margin-top: 8px;
}
.modal-footer > button:not(:last-child) {
margin-right: 8px;
}
</style>

View File

@ -14,7 +14,7 @@ export default [
}, },
children: [ children: [
{ {
path: 'serviceSites/index.vue', path: 'serviceSites',
name: 'serviceSites', name: 'serviceSites',
component: 'serviceMenu/serviceSites/index.vue', component: 'serviceMenu/serviceSites/index.vue',
meta: { meta: {
@ -25,7 +25,7 @@ export default [
}, },
}, },
{ {
path: 'serviceOrganization/index.vue', path: 'serviceOrganization',
name: 'serviceOrganization', name: 'serviceOrganization',
component: 'serviceMenu/serviceOrganization/index.vue', component: 'serviceMenu/serviceOrganization/index.vue',
meta: { meta: {
@ -34,18 +34,7 @@ export default [
keepAlive: true, keepAlive: true,
permission: '*', permission: '*',
}, },
}, }
{
path: 'add/index.vue',
name: 'serviceOrganizationAdd',
component: 'serviceMenu/serviceOrganization/pages/index.vue',
meta: {
title: '新建',
isMenu: false,
keepAlive: true,
permission: '*',
},
},
], ],
}, },
] ]

View File

@ -10,9 +10,8 @@
</a-col> </a-col>
<a-col v-bind="colSpan"> <a-col v-bind="colSpan">
<a-form-item :label="'站点类型'" name="name"> <a-form-item :label="'站点类型'" name="type">
<a-select ref="select" v-model:value="searchFormData.name" @focus="focus" <a-select v-model:value="searchFormData.type" @change="handleChange">
@change="handleChange">
<a-select-option value="jack">已结单</a-select-option> <a-select-option value="jack">已结单</a-select-option>
<a-select-option value="lucy">已作废</a-select-option> <a-select-option value="lucy">已作废</a-select-option>
</a-select> </a-select>
@ -20,15 +19,14 @@
</a-col> </a-col>
<a-col v-bind="colSpan"> <a-col v-bind="colSpan">
<a-form-item :label="'所在区域'" name="name"> <a-form-item :label="'所在区域'" name="area">
<a-input :placeholder="'请选择区域'" v-model:value="searchFormData.name"></a-input> <a-input :placeholder="'请选择区域'" v-model:value="searchFormData.area"></a-input>
</a-form-item> </a-form-item>
</a-col> </a-col>
<a-col v-bind="colSpan"> <a-col v-bind="colSpan">
<a-form-item :label="'所在节点'" name="name"> <a-form-item :label="'所在节点'" name="node">
<a-select ref="select" v-model:value="searchFormData.name" @focus="focus" <a-select v-model:value="searchFormData.node" @change="handleChange">
@change="handleChange">
<a-select-option value="jack">已结单</a-select-option> <a-select-option value="jack">已结单</a-select-option>
<a-select-option value="lucy">已作废</a-select-option> <a-select-option value="lucy">已作废</a-select-option>
</a-select> </a-select>
@ -63,7 +61,8 @@
新建 新建
</a-button> </a-button>
<a-button v-action="'add'" type="primary" @click="$refs.editDialogRef.handleCreate()"> <!-- 修改导入按钮的点击事件 -->
<a-button v-action="'add'" type="primary" @click="showImportModal = true">
导入 导入
</a-button> </a-button>
@ -141,26 +140,38 @@
</a-card> </a-card>
<edit-dialog @ok="onOk" ref="editDialogRef" /> <edit-dialog @ok="onOk" ref="editDialogRef" />
<!-- 导入弹框组件 -->
<import-modal
v-model:visible="showImportModal"
@download="handleDownloadTemplate"
@upload="handleFileUpload"
/>
</template> </template>
<script setup> <script setup>
import { Modal, message } from 'ant-design-vue' import { Modal, message } from 'ant-design-vue'
import { ref } from 'vue' import { ref } from 'vue'
import { UnorderedListOutlined } from '@ant-design/icons-vue' import { UnorderedListOutlined, EditOutlined, PlusCircleOutlined, DeleteOutlined } from '@ant-design/icons-vue'
import apis from '@/apis' import apis from '@/apis'
import { config } from '@/config' import { config } from '@/config'
import { menuTypeEnum, statusTypeEnum } from '@/enums/system' import { menuTypeEnum, statusTypeEnum } from '@/enums/system'
import { usePagination, useForm } from '@/hooks' import { usePagination, useForm } from '@/hooks'
import { formatUtcDateTime } from '@/utils/util' import { formatUtcDateTime } from '@/utils/util'
import EditDialog from './components/EditDialog.vue' import EditDialog from './components/EditDialog.vue'
//
import ImportModal from '@/components/Import/index.vue' //
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import storage from '@/utils/storage' import storage from '@/utils/storage'
defineOptions({ defineOptions({
// eslint-disable-next-line vue/no-reserved-component-names
name: 'menu', name: 'menu',
}) })
const { t } = useI18n() // t const { t } = useI18n() // t
// /
const showImportModal = ref(false)
const columns = ref([ const columns = ref([
{ title: '工单号', dataIndex: 'name', key: 'name', fixed: true, width: 280 }, { title: '工单号', dataIndex: 'name', key: 'name', fixed: true, width: 280 },
{ title: '服务对象', dataIndex: 'code', key: 'code', width: 240 }, { title: '服务对象', dataIndex: 'code', key: 'code', width: 240 },
@ -191,7 +202,6 @@ getMenuList()
async function getMenuList() { async function getMenuList() {
try { try {
showLoading() showLoading()
// const { current } = paginationState
const platform = storage.local.getItem('platform') const platform = storage.local.getItem('platform')
const { data, success, total } = await apis.menu const { data, success, total } = await apis.menu
.getMenuList({ .getMenuList({
@ -217,7 +227,6 @@ async function getMenuList() {
* 搜索 * 搜索
*/ */
function handleSearch() { function handleSearch() {
// resetForm()
resetPagination() resetPagination()
getMenuList() getMenuList()
} }
@ -267,6 +276,53 @@ async function onOk() {
} }
/**
* 处理下载模板
*/
function handleDownloadTemplate() {
// API
apis.downloadTemplate().then(() => {
message.success('模板下载成功')
}).catch(() => {
message.error('模板下载失败')
})
}
/**
* 处理文件上传
* @param file 选中的文件
*/
async function handleFileUpload(file) {
try {
showLoading()
// FormData
const formData = new FormData()
formData.append('file', file)
// API
const { success } = await apis.uploadServiceSite(formData)
hideLoading()
if (config('http.code.success') === success) {
message.success('文件上传成功')
showImportModal.value = false
//
await getMenuList()
} else {
message.error('文件上传失败')
}
} catch (error) {
hideLoading()
message.error('文件上传失败,请重试')
}
}
//
function handleChange() {
//
}
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@ -281,12 +337,4 @@ async function onOk() {
background-color: #e8e8e8; // 线 background-color: #e8e8e8; // 线
margin-top: 8px; margin-top: 8px;
} }
// a-spacesize
// :deep(.ant-btn) {
// margin-right: 8px;
// &:last-child {
// margin-right: 0;
// }
// }
</style> </style>