| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343 |
- <template>
- <u-upload
- :name="name"
- :width="width"
- :height="height"
- :accept="accept"
- :fileList="fileList"
- :multiple="multiple"
- :maxCount="maxCount"
- :disabled="disabled"
- v-if="$slots.content"
- @afterRead="afterRead"
- @delete="onDelete">
- <slot name="content" :data="fileList"></slot>
- </u-upload>
- <u-upload
- :name="name"
- :width="width"
- :height="height"
- :accept="accept"
- :fileList="fileList"
- :multiple="multiple"
- :maxCount="maxCount"
- :disabled="disabled"
- v-else
- @afterRead="afterRead"
- @delete="onDelete">
- </u-upload>
- </template>
- <script>
- // import baseUrl from '@/common/config.js';
- import configService from '@/common/service/config.service';
- export default {
- name: "",
- props: {
- api: {
- type: String,
- default: '/sys/common/upload'
- },
- name: {
- type: String,
- default: (() => {
- try {
- return uni.$u && uni.$u.props && uni.$u.props.upload && uni.$u.props.upload.name || 'file'
- } catch (e) {
- return 'file'
- }
- })()
- },
- accept: {
- type: String,
- default: (() => {
- try {
- return uni.$u && uni.$u.props && uni.$u.props.upload && uni.$u.props.upload.accept || 'image'
- } catch (e) {
- return 'image'
- }
- })()
- },
- width: {
- type: [String, Number],
- default: (() => {
- try {
- return uni.$u && uni.$u.props && uni.$u.props.upload && uni.$u.props.upload.width || 150
- } catch (e) {
- return 150
- }
- })()
- },
- height: {
- type: [String, Number],
- default: (() => {
- try {
- return uni.$u && uni.$u.props && uni.$u.props.upload && uni.$u.props.upload.height || 150
- } catch (e) {
- return 150
- }
- })()
- },
- maxCount: {
- type: Number,
- default: 5
- },
- multiple: {
- type: Boolean,
- default: true
- },
- disabled: {
- type: Boolean,
- default: false
- },
- // 初始图片列表
- initList: {
- type: Array,
- default: () => []
- }
- },
- data() {
- return {
- fileList: [],
- };
- },
- watch: {
- // 更新fileList
- initList: {
- handler(newVal) {
- if (newVal && newVal.length > 0) {
- this.fileList = newVal.map(item => ({
- ...item,
- status: 'success',
- message: ''
- }));
- } else {
- this.fileList = [];
- }
- },
- immediate: true,
- deep: true
- }
- },
- computed: {
- },
- mounted() {
-
- },
- beforeDestroy() {
- },
- methods: {
- // 上传后的处理函数
- async afterRead(event) {
- // 当设置 multiple 为 true 时, file 为数组格式,否则为对象格式
- let lists = [].concat(event.file);
- let fileListLen = this[`fileList`].length;
-
- // 遍历文件,对图片进行压缩(支持H5、App、小程序三端)
- // 支持两种判断方式:1. MIME类型以image/开头 2. 文件后缀为.jpg
- for (let i = 0; i < lists.length; i++) {
- const item = lists[i];
- const isImageType = item.type && item.type.startsWith('image');
- const isJpgFile = item.name && (item.name.toLowerCase().endsWith('.jpg') || item.name.toLowerCase().endsWith('.jpeg'));
- if (isImageType || isJpgFile) {
- // 压缩后可能是 File 对象(H5)或 URL 字符串(App/小程序)
- const compressedResult = await this.compressImage(item.url);
- // 保存原始 URL 用于预览,压缩结果用于上传
- lists[i].compressedFile = compressedResult;
- }
- }
-
- lists.map((item) => {
- this[`fileList`].push({
- ...item,
- status: "uploading",
- message: "上传中",
- localUrl: item.url, // 保存原始本地路径用于预览
- });
- });
- // 无预览时,使用toast
- if (!this.$attrs.previewImage) {
- uni.showLoading({
- title: '上传中...'
- });
- }
- for (let i = 0; i < lists.length; i++) {
- // 使用压缩后的文件或原文件上传
- const uploadTarget = lists[i].compressedFile || lists[i].url;
- const result = await this.uploadFilePromise(uploadTarget);
- let item = this[`fileList`][fileListLen];
- this[`fileList`].splice(
- fileListLen,
- 1,
- Object.assign(item, {
- status: "success",
- message: "",
- url: configService.apiUrl + '/' + result,
- localUrl: item.localUrl
- })
- );
- fileListLen++;
- // 无预览时,使用toast
- if (!this.$attrs.previewImage) {
- uni.hideLoading();
- uni.showToast({
- icon: 'none',
- title: result.message || '上传完成',
- duration: 2000
- });
- this.$emit('complete');
- }
- }
- this.$emit('fileList', this.fileList);
- },
- // 图片压缩 - 支持H5、App、小程序三端
- // 使用条件编译区分平台
- async compressImage(url) {
- // #ifdef H5
- // H5 环境直接使用 Canvas 压缩
- try {
- return await this.canvasCompress(url, 0.8);
- } catch (err) {
- console.warn('Canvas 压缩失败,使用原图片:', err);
- return url;
- }
- // #endif
-
- // #ifndef H5
- // App 和小程序使用 uni.compressImage
- return new Promise((resolve) => {
- uni.compressImage({
- src: url,
- quality: 0.8,
- success: (res) => {
- resolve(res.tempFilePath);
- },
- fail: (err) => {
- console.warn('uni.compressImage 失败,使用原图片:', err);
- resolve(url);
- }
- });
- });
- // #endif
- },
- // Canvas 图片压缩(H5 专用)
- canvasCompress(url, quality) {
- return new Promise((resolve, reject) => {
- const img = new Image();
- img.crossOrigin = 'anonymous';
- img.onload = () => {
- const canvas = document.createElement('canvas');
- const ctx = canvas.getContext('2d');
-
- // 计算压缩后的尺寸(保持宽高比)
- const maxWidth = 1200;
- const maxHeight = 1200;
- let width = img.width;
- let height = img.height;
-
- if (width > maxWidth) {
- height = (maxWidth / width) * height;
- width = maxWidth;
- }
- if (height > maxHeight) {
- width = (maxHeight / height) * width;
- height = maxHeight;
- }
-
- canvas.width = width;
- canvas.height = height;
- ctx.drawImage(img, 0, 0, width, height);
-
- // 将 Canvas 转换为 File 对象
- canvas.toBlob((blob) => {
- if (blob) {
- // 确保文件名以 .jpg 结尾
- const originalName = url.split('/').pop() || 'image';
- const fileName = originalName.endsWith('.jpg') || originalName.endsWith('.jpeg')
- ? originalName
- : originalName + '.jpg';
- const file = new File([blob], fileName, { type: 'image/jpeg' });
- resolve(file);
- } else {
- reject('Blob 创建失败');
- }
- }, 'image/jpeg', quality);
- };
- img.onerror = (err) => {
- reject('图片加载失败: ' + err);
- };
- img.src = url;
- });
- },
- uploadFilePromise(fileOrUrl) {
- return new Promise((resolve, reject) => {
- // #ifdef H5
- // H5 环境使用原生 FormData 上传
- if (fileOrUrl instanceof File) {
- const formData = new FormData();
- formData.append('file', fileOrUrl);
- formData.append('biz', 'temp');
-
- fetch( configService.apiUrl + this.api, {
- method: 'POST',
- body: formData,
- headers: {
- 'Accept': 'application/json'
- }
- }).then(response => response.json())
- .then(data => {
- if (data.success) {
- resolve(data.message);
- } else {
- reject(data.message);
- }
- })
- .catch(err => {
- reject('上传失败: ' + err);
- });
- return;
- }
- // #endif
-
- // App 和小程序使用 uni.uploadFile
- uni.uploadFile({
- url: configService.apiUrl + this.api,
- name: "file",
- formData: {
- 'biz': 'temp',
- },
- filePath: fileOrUrl,
- success: (res) => {
- let data = JSON.parse(res.data);
- if (data.success) {
- resolve(data.message);
- }
- },
- fail: (err) => {
- reject(err);
- }
- });
- });
- },
- onDelete(event) {
- uni.showModal({
- title: '提示',
- content: '确定要删除?',
- cancelText: '取消',
- confirmText: '确定',
- success: res => {
- if (res.confirm) {
- this[`fileList`].splice(event.index, 1);
- this.$emit('fileList', this.fileList);
- }
- }
- })
- },
- }
- }
- </script>
- <style lang="scss" scoped>
- </style>
|