wujianwei
2025-12-24 b5b6177f7dcad6e4bf004720073778dd008fca32
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
<template>
  <el-dialog 
    v-model="upload.open" 
    :title="title" 
    width="500px" 
    append-to-body 
    @close="handleCancel"
  >
    <DialRightTop :optMessgDesc="optMessgDesc" />
 
    <div class="upload-container">
      <el-upload
        ref="uploadRef"
        v-model:file-list="fileList"
        drag
        :limit="1"
        accept=".xlsx, .xls"
        :headers="elHeader"
        :action="upload.url + uploadUrl"
        :disabled="upload.isUploading"
        :data="paramsData"
        :auto-upload="false"
        :on-progress="handleProgress"
        :on-error="handleFileError"
        :on-success="handleSuccess"
        :on-exceed="handleExceed"
        class="upload-wrapper"
      >
        <el-icon class="el-icon--upload"><upload-filled /></el-icon>
        <div class="el-upload__text">
          将文件拖到此处,或 <em>点击上传</em>
        </div>
        
        <template #tip>
          <div class="el-upload__tip text-center">
            <span>仅允许导入 xls、xlsx 格式文件。</span>
            <el-link 
              type="primary" 
              :underline="false" 
              class="template-link"
              @click="handleDownloadTemplate"
            >下载模板</el-link>
          </div>
        </template>
      </el-upload>
    </div>
 
    <template #footer>
      <div class="dialog-footer">
        <el-button type="primary" :loading="upload.isUploading" @click="handleSubmit">确 定</el-button>
        <el-button @click="handleCancel">取 消</el-button>
      </div>
    </template>
  </el-dialog>
</template>
 
<script setup lang="ts">
import { reactive, ref, watch, computed } from "vue";
import { getToken } from "@/utils/auth";
import useCurrentInstance from "@/utils/useCurrentInstance";
import { download } from '@/utils/request'
import { genFileId, type UploadInstance, type UploadRawFile } from 'element-plus';
import { UploadFilled } from '@element-plus/icons-vue';
 
interface Props {
  uploadUrl: string;       // 上传接口地址
  templateUrl?: string;    // 模板下载地址
  title?: string;          // 弹窗标题
  open: boolean;           // 弹窗开关
  paramsData?: object;     // 上传携带的额外参数
  optMessgDesc?: string;   // 提示文字内容
}
 
const props = withDefaults(defineProps<Props>(), {
  title: '文件导入',
  open: false,
  paramsData: () => ({}),
  optMessgDesc: '1.请下载正确的模板导入数据; 2.表头请勿改动; 3.必填项需填充; 4.字典数据需校验合法性。'
});
 
const emit = defineEmits(['submit', 'cancel']);
const { proxy } = useCurrentInstance();
const uploadRef = ref<UploadInstance>();
const fileList = ref([]);
 
// 状态管理
const upload = reactive({
  open: false,
  isUploading: false,
  url: import.meta.env.VITE_APP_BASE_API
});
 
// 请求头
const elHeader = computed(() => ({
  Authorization: "Bearer " + getToken(),
}));
 
// 监听弹窗打开状态
watch(() => props.open, (val) => {
  upload.open = val;
  if (val) {
    fileList.value = [];
    upload.isUploading = false;
  }
});
 
/** 核心方法:提交上传 */
const handleSubmit = () => {
  if (fileList.value.length === 0) {
    proxy.$modal.msgError('请选择需要上传的文件');
    return;
  }
  upload.isUploading = true;
  uploadRef.value!.submit();
};
 
/** 核心方法:下载模板 */
const handleDownloadTemplate = () => {
  if (!props.templateUrl) return;
  
  const [url, search] = props.templateUrl.split('?');
  const params: Record<string, any> = {};
  if (search) {
    search.split('&').forEach(item => {
      const [k, v] = item.split('=');
      params[k] = v;
    });
  }
  
  // 直接使用引入的方法,不再通过 proxy
  download(url, params, `template_${new Date().getTime()}.xlsx`);
};
/** 上传进度 */
const handleProgress = () => {
  upload.isUploading = true;
};
 
/** 上传结果处理(保留业务逻辑策略) */
const handleSuccess = (response: any) => {
  upload.isUploading = false;
  uploadRef.value!.clearFiles();
  
  const { code, msg, data } = response;
 
  if (code === 200) {
    proxy.$modal.msgSuccess(msg || "导入成功");
    emit('submit', response);
    handleCancel();
  } 
  else if (code === 201) {
    // 部分成功或有错误文件
    if (!data || data === 'null') {
      proxy.$alert(msg, "导入提示");
    } else {
      proxy.$confirm(msg, "导入结果", {
        confirmButtonText: '确定',
        cancelButtonText: '下载错误文件',
        type: 'warning',
        distinguishCancelAndClose: true
      }).catch((action) => {
        if (action === 'cancel') proxy.downloadFile(data);
      });
    }
    emit('submit', response); // 部分成功也触发刷新
  } 
  else {
    proxy.$alert(msg || "导入失败", "错误提示", { type: 'error' });
  }
};
 
/** 文件超出限制(自动替换) */
const handleExceed = (files: File[]) => {
  uploadRef.value!.clearFiles();
  const file = files[0] as UploadRawFile;
  file.uid = genFileId();
  uploadRef.value!.handleStart(file);
};
 
const handleCancel = () => {
  upload.open = false;
  emit('cancel');
};
/** 上传失败处理 */
const handleFileError = (err: any) => {
  upload.isUploading = false; // 恢复按钮点击状态
  uploadRef.value!.clearFiles(); // 清空失败的文件列表,方便用户重选
  
  // 解析报错信息
  try {
    const response = JSON.parse(err.message);
    proxy.$modal.msgError(`上传失败(404):接口地址 ${response.path} 不存在`);
  } catch (e) {
    proxy.$modal.msgError("上传失败,请检查网络或联系管理员");
  }
};
</script>
 
<style scoped>
.upload-container {
  padding: 20px 0;
  display: flex;
  justify-content: center;
}
 
.upload-wrapper {
  width: 100%;
}
 
.template-link {
  font-size: 12px;
  vertical-align: baseline;
  margin-left: 10px;
}
 
:deep(.el-upload-dragger) {
  padding: 40px;
}
 
/* 覆盖错误按钮样式 */
:deep(.el-button--danger) {
  background-color: #f56c6c;
  border-color: #f56c6c;
}
</style>