Newer
Older
KaiFengPC / src / components / FileUpload / index.vue
@鲁yixuan 鲁yixuan on 21 Aug 7 KB updata
  1. <template>
  2. <div class="upload-file">
  3. <el-upload
  4. multiple
  5. :action="uploadFileUrl"
  6. :before-upload="handleBeforeUpload"
  7. :file-list="fileList"
  8. :limit="limit"
  9. :on-error="handleUploadError"
  10. :on-exceed="handleExceed"
  11. :on-success="handleUploadSuccess"
  12. :show-file-list="false"
  13. :headers="headers"
  14. :accept="accept.join()"
  15. class="upload-file-uploader"
  16. ref="fileUpload"
  17. :disabled="props.disabled"
  18. >
  19. <!-- 上传按钮 -->
  20. <el-button type="primary">选取文件</el-button>
  21. </el-upload>
  22. <!-- 上传提示 -->
  23. <div class="el-upload__tip" v-if="showTip">
  24. 请上传
  25. <template v-if="fileSize">
  26. 大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
  27. </template>
  28. <template v-if="fileType">
  29. 格式为 <b style="color: #f56c6c">{{ fileType.join('/') }}</b>
  30. </template>
  31. 的文件
  32. </div>
  33. <!-- 文件列表 -->
  34. <transition-group class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul">
  35. <li :key="file.uid" :title="file.name" class="el-upload-list__item ele-upload-list__item-content" v-for="(file, index) in fileList">
  36. <el-link class="el-icon-document-a" :href="file.url" :underline="false" target="_blank" @click="handlePreview(file)">
  37. <span class="el-icon-document"> {{ file.name }} </span>
  38. </el-link>
  39. <div class="ele-upload-list__item-content-action" v-if="!props.disabled">
  40. <el-link :underline="false" @click="handleDelete(index)" type="danger">删除</el-link>
  41. </div>
  42. </li>
  43. </transition-group>
  44. </div>
  45. </template>
  46.  
  47. <script setup>
  48. import { getToken } from '@/utils/auth';
  49.  
  50. const props = defineProps({
  51. modelValue: [String, Object, Array],
  52. // 数量限制
  53. limit: {
  54. type: Number,
  55. default: 5,
  56. },
  57. // 大小限制(MB)
  58. fileSize: {
  59. type: Number,
  60. default: 0,
  61. },
  62. // 文件类型, 例如['png', 'jpg', 'jpeg']
  63. fileType: {
  64. type: Array,
  65. default: () => ['doc', 'xls', 'ppt', 'txt', 'pdf'],
  66. },
  67. // 是否显示提示
  68. isShowTip: {
  69. type: Boolean,
  70. default: true,
  71. },
  72. //是否返回文件名name
  73. isBackName: {
  74. type: Boolean,
  75. default: false,
  76. },
  77. disabled: {
  78. type: Boolean,
  79. default: false,
  80. },
  81. });
  82.  
  83. const { proxy } = getCurrentInstance();
  84. const emit = defineEmits();
  85. const number = ref(0);
  86. const uploadList = ref([]);
  87. const baseUrl = import.meta.env.VITE_APP_ENV == 'development' ? '/prod-api' : import.meta.env.VITE_APP_BASE_API;
  88. const uploadFileUrl = ref(baseUrl + '/system/upload'); // 上传文件服务器地址
  89. const headers = ref({ Authorization: 'Bearer ' + getToken() });
  90. const fileList = ref([]);
  91. const showTip = computed(() => {
  92. return props.isShowTip && (props.fileType || props.fileSize);
  93. });
  94.  
  95. watch(
  96. () => props.modelValue,
  97. val => {
  98. if (val) {
  99. let temp = 1;
  100. // 首先将值转为数组
  101. const list = Array.isArray(val) ? val : props.modelValue.split(',');
  102. // 然后将数组转为对象数组
  103. fileList.value = list.map(item => {
  104. if (typeof item === 'string') {
  105. item = { name: item, url: item };
  106. }
  107. item.uid = item.uid || new Date().getTime() + temp++;
  108. return item;
  109. });
  110. } else {
  111. fileList.value = [];
  112. return [];
  113. }
  114. },
  115. { deep: true, immediate: true }
  116. );
  117. const accept = computed(() => {
  118. if (props.fileType.length) {
  119. let data = [];
  120. data = props.fileType.map(val => {
  121. return `.${val}`;
  122. });
  123. return data;
  124. } else {
  125. return [];
  126. }
  127. });
  128. // 上传前校检格式和大小
  129. function handleBeforeUpload(file) {
  130. // 校检文件类型
  131. if (props.fileType.length) {
  132. const fileName = file.name.split('.');
  133. const fileExt = fileName[fileName.length - 1];
  134. const isTypeOk = props.fileType.indexOf(fileExt) >= 0;
  135. if (!isTypeOk) {
  136. proxy.$modal.msgError(`文件格式不正确, 请上传${props.fileType.join('/')}格式文件!`);
  137. return false;
  138. }
  139. }
  140. // 校检文件大小
  141. if (props.fileSize) {
  142. const isLt = file.size / 1024 / 1024 < props.fileSize;
  143. if (!isLt) {
  144. proxy.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
  145. return false;
  146. }
  147. }
  148. proxy.$modal.loading('正在上传文件,请稍候...');
  149. number.value++;
  150. return true;
  151. }
  152.  
  153. // 文件个数超出
  154. function handleExceed() {
  155. proxy.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
  156. }
  157.  
  158. // 上传失败
  159. function handleUploadError(err) {
  160. proxy.$modal.msgError('上传文件失败');
  161. }
  162.  
  163. // 上传成功回调
  164. function handleUploadSuccess(res, file) {
  165. if (res.code === 200) {
  166. uploadList.value.push({
  167. name: res.data.url,
  168. url: res.data.url,
  169. originalName: res.data.originalName,
  170. ...res.data,
  171. });
  172. uploadedSuccessfully();
  173. } else {
  174. number.value--;
  175. proxy.$modal.closeLoading();
  176. proxy.$modal.msgError(res.msg);
  177. proxy.$refs.fileUpload.handleRemove(file);
  178. uploadedSuccessfully();
  179. }
  180. }
  181.  
  182. // 预览文件
  183. function handlePreview(file) {
  184. emit('preview', file);
  185. }
  186.  
  187. // 删除文件
  188. function handleDelete(index) {
  189. fileList.value.splice(index, 1);
  190. if (props.isBackName) {
  191. emit('update:modelValue', fileList.value);
  192. } else {
  193. emit('update:modelValue', listToString(fileList.value));
  194. }
  195. // emit("update:modelValue", listToString(fileList.value));
  196. }
  197.  
  198. // 上传结束处理
  199. function uploadedSuccessfully() {
  200. if (number.value > 0 && uploadList.value.length === number.value) {
  201. fileList.value = fileList.value.filter(f => f.url !== undefined).concat(uploadList.value);
  202. console.log('fileList.value', fileList.value);
  203. uploadList.value = [];
  204. number.value = 0;
  205. if (props.isBackName) {
  206. emit('update:modelValue', fileList.value);
  207. } else {
  208. emit('update:modelValue', listToString(fileList.value));
  209. }
  210. // emit("update:modelValue", listToString(fileList.value));
  211. proxy.$modal.closeLoading();
  212. }
  213. }
  214.  
  215. // 获取文件名称
  216. function getFileName(name) {
  217. if (name.lastIndexOf('/') > -1) {
  218. return name.slice(name.lastIndexOf('/') + 1);
  219. } else {
  220. return '';
  221. }
  222. }
  223.  
  224. // 对象转成指定字符串分隔
  225. function listToString(list, separator) {
  226. let strs = '';
  227. separator = separator || ',';
  228. for (let i in list) {
  229. if (list[i].url) {
  230. strs += list[i].url + separator;
  231. }
  232. }
  233. return strs != '' ? strs.substr(0, strs.length - 1) : '';
  234. }
  235. </script>
  236.  
  237. <style scoped lang="scss">
  238. .upload-file {
  239. width: 100%;
  240. }
  241. .upload-file-uploader {
  242. margin-bottom: 5px;
  243. }
  244. .upload-file-list .el-upload-list__item {
  245. border: 1px solid #e4e7ed;
  246. line-height: 2;
  247. margin-bottom: 10px;
  248. position: relative;
  249. }
  250. .upload-file-list .ele-upload-list__item-content {
  251. display: flex;
  252. justify-content: space-between;
  253. align-items: center;
  254. color: inherit;
  255. padding: 5px 10px;
  256. width: 100%;
  257. overflow: hidden;
  258. :deep(.el-icon-document-a) {
  259. flex: 1;
  260. overflow: hidden;
  261. .el-link__inner {
  262. white-space: nowrap;
  263. text-overflow: ellipsis;
  264. overflow: hidden;
  265. display: block;
  266. }
  267. }
  268. .ele-upload-list__item-content-action {
  269. flex-shrink: 0;
  270. }
  271. }
  272. .ele-upload-list__item-content-action .el-link {
  273. margin-left: 10px;
  274. }
  275. </style>