Kaynağa Gözat

feat(mobile-wx): preview uploads from local paths only

Use blob/temp previewUrl for thumbnails; keep remote url only for
business submit payloads.

Co-authored-by: Cursor <cursoragent@cursor.com>
wuzilong 2 ay önce
ebeveyn
işleme
2e0a18a6a3

+ 131 - 63
mobile-wx/pages/feedback/index.vue

@@ -1,32 +1,82 @@
 <template>
 <template>
   <view class="page">
   <view class="page">
-    <view class="card box">
+    <view class="form-section">
       <view class="sec-title">反馈类型</view>
       <view class="sec-title">反馈类型</view>
-      <view class="types">
-        <view class="type" :class="{ on: form.fbType === '投诉' }" @click="form.fbType = '投诉'">投诉</view>
-        <view class="type" :class="{ on: form.fbType === '表扬' }" @click="form.fbType = '表扬'">表扬</view>
+      <view class="field-row">
+        <view class="label"><text class="req">*</text>类型</view>
+        <view class="type-switch">
+          <view class="type" :class="{ on: form.fbType === '投诉' }" @click="form.fbType = '投诉'">投诉</view>
+          <view class="type" :class="{ on: form.fbType === '表扬' }" @click="form.fbType = '表扬'">表扬</view>
+        </view>
       </view>
       </view>
+    </view>
 
 
+    <view class="form-section">
       <view class="sec-title">被反馈人信息(选填)</view>
       <view class="sec-title">被反馈人信息(选填)</view>
-      <view class="field"><input v-model="form.targetName" placeholder="被反馈人姓名" /></view>
-      <view class="field"><input v-model="form.targetDept" placeholder="部门" /></view>
-      <view class="field"><input v-model="form.targetPost" placeholder="职务" /></view>
+      <view class="field-row">
+        <view class="label">姓名</view>
+        <input v-model="form.targetName" placeholder="请输入被反馈人姓名" />
+      </view>
+      <view class="field-row">
+        <view class="label">单位</view>
+        <input v-model="form.targetDept" placeholder="请输入所在单位/科室" />
+      </view>
+      <view class="field-row">
+        <view class="label">职务</view>
+        <input v-model="form.targetPost" placeholder="请输入职务" />
+      </view>
+    </view>
 
 
+    <view class="form-section">
       <view class="sec-title">反馈内容</view>
       <view class="sec-title">反馈内容</view>
-      <view class="field">
-        <textarea v-model="form.content" placeholder="请客观具体描述" maxlength="2000" />
+      <view class="field-row top">
+        <view class="label"><text class="req">*</text>内容</view>
+        <textarea
+          v-model="form.content"
+          placeholder="请客观、具体描述相关情况(必填)"
+          maxlength="2000"
+        />
       </view>
       </view>
-
-      <view class="sec-title">反馈材料(选填)</view>
-      <view class="uploads">
-        <image v-for="(u, i) in form.attachmentUrls" :key="i" :src="u" class="thumb" mode="aspectFill" />
-        <view class="add" @click="pickFiles">+</view>
+      <view class="upload-area">
+        <view class="upload-hint">反馈材料(选填)· 支持图片、视频、文件,可多选</view>
+        <view class="upload-list">
+          <view v-for="(item, i) in attachments" :key="i" class="upload-item">
+            <image v-if="isImage(item)" :src="item.previewUrl" mode="aspectFill" />
+            <view v-else class="file-chip">
+              <text class="file-ext">{{ extLabel(item) }}</text>
+              <text class="file-name">{{ item.fileName || '附件' }}</text>
+            </view>
+            <view class="rm" @click="removeFile(i)">×</view>
+          </view>
+          <view class="upload-add" @click="pickFiles">
+            <text class="plus">+</text>
+            <text>上传</text>
+          </view>
+        </view>
       </view>
       </view>
+    </view>
 
 
-      <view class="sec-title">联系方式(选填,可匿名)</view>
-      <view class="field"><input v-model="form.reporterName" placeholder="姓名" /></view>
-      <view class="field"><input v-model="form.reporterPhone" placeholder="手机号" /></view>
+    <view class="form-section">
+      <view class="sec-title">举报人信息(选填)</view>
+      <view class="field-row">
+        <view class="label">姓名</view>
+        <input v-model="form.reporterName" placeholder="可匿名,选填" />
+      </view>
+      <view class="field-row">
+        <view class="label">单位</view>
+        <input v-model="form.reporterUnit" placeholder="请输入单位" />
+      </view>
+      <view class="field-row">
+        <view class="label">职务</view>
+        <input v-model="form.reporterPost" placeholder="请输入职务" />
+      </view>
+      <view class="field-row">
+        <view class="label">联系方式</view>
+        <input v-model="form.reporterPhone" placeholder="手机号 / 微信等" />
+      </view>
+    </view>
 
 
+    <view class="submit-bar">
       <view class="btn-primary" :class="{ disabled: submitting }" @click="submit">提交反馈</view>
       <view class="btn-primary" :class="{ disabled: submitting }" @click="submit">提交反馈</view>
     </view>
     </view>
   </view>
   </view>
@@ -34,12 +84,18 @@
 
 
 <script>
 <script>
   import { submitFeedback } from '../../api/public.js'
   import { submitFeedback } from '../../api/public.js'
-  import { chooseAndUploadImages } from '../../utils/upload.js'
+  import {
+    chooseAndUploadFiles,
+    fileNameFromUrl,
+    isImageUrl,
+    revokePreview
+  } from '../../utils/upload.js'
 
 
   export default {
   export default {
     data() {
     data() {
       return {
       return {
         submitting: false,
         submitting: false,
+        attachments: [],
         form: {
         form: {
           fbType: '投诉',
           fbType: '投诉',
           targetName: '',
           targetName: '',
@@ -47,18 +103,40 @@
           targetPost: '',
           targetPost: '',
           content: '',
           content: '',
           reporterName: '',
           reporterName: '',
-          reporterPhone: '',
-          attachmentUrls: []
+          reporterUnit: '',
+          reporterPost: '',
+          reporterPhone: ''
         }
         }
       }
       }
     },
     },
+    beforeUnmount() {
+      ;(this.attachments || []).forEach((a) => revokePreview(a))
+    },
+    // #ifndef VUE3
+    beforeDestroy() {
+      ;(this.attachments || []).forEach((a) => revokePreview(a))
+    },
+    // #endif
     methods: {
     methods: {
+      isImage(item) {
+        return isImageUrl(item.fileName || item.previewUrl || item.url)
+      },
+      extLabel(item) {
+        const name = item.fileName || fileNameFromUrl(item.url)
+        const i = name.lastIndexOf('.')
+        return i >= 0 ? name.slice(i + 1).toUpperCase() : 'FILE'
+      },
       async pickFiles() {
       async pickFiles() {
         try {
         try {
-          const urls = await chooseAndUploadImages(6, 'feedback')
-          this.form.attachmentUrls = this.form.attachmentUrls.concat(urls)
+          const items = await chooseAndUploadFiles('feedback')
+          this.attachments = this.attachments.concat(items)
         } catch (e) {}
         } catch (e) {}
       },
       },
+      removeFile(i) {
+        const item = this.attachments[i]
+        if (item) revokePreview(item)
+        this.attachments.splice(i, 1)
+      },
       async submit() {
       async submit() {
         if (!this.form.content || !this.form.content.trim()) {
         if (!this.form.content || !this.form.content.trim()) {
           uni.showToast({ title: '请填写反馈内容', icon: 'none' })
           uni.showToast({ title: '请填写反馈内容', icon: 'none' })
@@ -67,7 +145,12 @@
         if (this.submitting) return
         if (this.submitting) return
         this.submitting = true
         this.submitting = true
         try {
         try {
-          await submitFeedback({ ...this.form })
+          await submitFeedback({
+            ...this.form,
+            attachmentUrls: this.attachments.map((a) => a.url)
+          })
+          ;(this.attachments || []).forEach((a) => revokePreview(a))
+          this.attachments = []
           uni.showToast({ title: '提交成功', icon: 'success' })
           uni.showToast({ title: '提交成功', icon: 'success' })
           setTimeout(() => {
           setTimeout(() => {
             uni.reLaunch({ url: '/pages/home/index' })
             uni.reLaunch({ url: '/pages/home/index' })
@@ -82,50 +165,35 @@
 
 
 <style scoped>
 <style scoped>
   .page {
   .page {
-    padding: 24rpx;
+    padding-bottom: calc(40rpx + env(safe-area-inset-bottom));
   }
   }
-  .box {
-    padding: 28rpx;
-  }
-  .types {
+
+  .file-chip {
+    width: 100%;
+    height: 100%;
+    padding: 12rpx;
+    box-sizing: border-box;
     display: flex;
     display: flex;
-    gap: 16rpx;
-    margin-bottom: 24rpx;
-  }
-  .type {
-    flex: 1;
-    text-align: center;
-    padding: 18rpx;
-    border: 1rpx solid var(--line);
-    border-radius: 10rpx;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    gap: 8rpx;
+    background: #f5f5f5;
   }
   }
-  .type.on {
-    background: var(--primary-soft);
-    border-color: var(--primary);
+
+  .file-ext {
+    font-size: 22rpx;
+    font-weight: 700;
     color: var(--primary);
     color: var(--primary);
-    font-weight: 600;
-  }
-  textarea {
-    min-height: 180rpx;
   }
   }
-  .uploads {
-    display: flex;
-    flex-wrap: wrap;
-    gap: 16rpx;
-    margin-bottom: 20rpx;
-  }
-  .thumb,
-  .add {
-    width: 140rpx;
-    height: 140rpx;
-    border-radius: 10rpx;
-  }
-  .add {
-    border: 1rpx dashed #ccc;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    font-size: 48rpx;
-    color: #999;
+
+  .file-name {
+    font-size: 18rpx;
+    color: #666;
+    text-align: center;
+    width: 100%;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
   }
   }
 </style>
 </style>

+ 108 - 41
mobile-wx/pages/suggest/index.vue

@@ -1,22 +1,55 @@
 <template>
 <template>
   <view class="page">
   <view class="page">
-    <view class="card box">
+    <view class="form-section">
       <view class="sec-title">建议内容</view>
       <view class="sec-title">建议内容</view>
-      <view class="field">
-        <textarea v-model="form.content" placeholder="请填写建言内容" maxlength="2000" />
+      <view class="field-row top">
+        <view class="label"><text class="req">*</text>内容</view>
+        <textarea
+          v-model="form.content"
+          placeholder="请填写您的意见或建议(必填)"
+          maxlength="2000"
+        />
       </view>
       </view>
-      <view class="sec-title">联系人信息(选填)</view>
-      <view class="field"><input v-model="form.contactName" placeholder="姓名" /></view>
-      <view class="field"><input v-model="form.contactUnit" placeholder="单位" /></view>
-      <view class="field"><input v-model="form.contactPost" placeholder="职务" /></view>
-      <view class="field"><input v-model="form.contactPhone" placeholder="联系方式" /></view>
+      <view class="upload-area">
+        <view class="upload-hint">附件(选填)· 支持图片、视频、文件,可多选</view>
+        <view class="upload-list">
+          <view v-for="(item, i) in attachments" :key="i" class="upload-item">
+            <image v-if="isImage(item)" :src="item.previewUrl" mode="aspectFill" />
+            <view v-else class="file-chip">
+              <text class="file-ext">{{ extLabel(item) }}</text>
+              <text class="file-name">{{ item.fileName || '附件' }}</text>
+            </view>
+            <view class="rm" @click="removeFile(i)">×</view>
+          </view>
+          <view class="upload-add" @click="pickFiles">
+            <text class="plus">+</text>
+            <text>上传</text>
+          </view>
+        </view>
+      </view>
+    </view>
 
 
-      <view class="sec-title">附件(选填)</view>
-      <view class="uploads">
-        <image v-for="(u, i) in form.attachmentUrls" :key="i" :src="u" class="thumb" mode="aspectFill" />
-        <view class="add" @click="pickFiles">+</view>
+    <view class="form-section">
+      <view class="sec-title">联系人信息(选填)</view>
+      <view class="field-row">
+        <view class="label">姓名</view>
+        <input v-model="form.contactName" placeholder="可匿名,选填" />
+      </view>
+      <view class="field-row">
+        <view class="label">单位</view>
+        <input v-model="form.contactUnit" placeholder="请输入单位" />
+      </view>
+      <view class="field-row">
+        <view class="label">职务</view>
+        <input v-model="form.contactPost" placeholder="请输入职务" />
       </view>
       </view>
+      <view class="field-row">
+        <view class="label">联系方式</view>
+        <input v-model="form.contactPhone" placeholder="手机号 / 微信等" />
+      </view>
+    </view>
 
 
+    <view class="submit-bar">
       <view class="btn-primary" :class="{ disabled: submitting }" @click="submit">提交建议</view>
       <view class="btn-primary" :class="{ disabled: submitting }" @click="submit">提交建议</view>
     </view>
     </view>
   </view>
   </view>
@@ -24,29 +57,55 @@
 
 
 <script>
 <script>
   import { submitSuggestion } from '../../api/public.js'
   import { submitSuggestion } from '../../api/public.js'
-  import { chooseAndUploadImages } from '../../utils/upload.js'
+  import {
+    chooseAndUploadFiles,
+    fileNameFromUrl,
+    isImageUrl,
+    revokePreview
+  } from '../../utils/upload.js'
 
 
   export default {
   export default {
     data() {
     data() {
       return {
       return {
         submitting: false,
         submitting: false,
+        attachments: [],
         form: {
         form: {
           content: '',
           content: '',
           contactName: '',
           contactName: '',
           contactUnit: '',
           contactUnit: '',
           contactPost: '',
           contactPost: '',
-          contactPhone: '',
-          attachmentUrls: []
+          contactPhone: ''
         }
         }
       }
       }
     },
     },
+    beforeUnmount() {
+      ;(this.attachments || []).forEach((a) => revokePreview(a))
+    },
+    // #ifndef VUE3
+    beforeDestroy() {
+      ;(this.attachments || []).forEach((a) => revokePreview(a))
+    },
+    // #endif
     methods: {
     methods: {
+      isImage(item) {
+        return isImageUrl(item.fileName || item.previewUrl || item.url)
+      },
+      extLabel(item) {
+        const name = item.fileName || fileNameFromUrl(item.url)
+        const i = name.lastIndexOf('.')
+        return i >= 0 ? name.slice(i + 1).toUpperCase() : 'FILE'
+      },
       async pickFiles() {
       async pickFiles() {
         try {
         try {
-          const urls = await chooseAndUploadImages(6, 'suggestion')
-          this.form.attachmentUrls = this.form.attachmentUrls.concat(urls)
+          const items = await chooseAndUploadFiles('suggestion')
+          this.attachments = this.attachments.concat(items)
         } catch (e) {}
         } catch (e) {}
       },
       },
+      removeFile(i) {
+        const item = this.attachments[i]
+        if (item) revokePreview(item)
+        this.attachments.splice(i, 1)
+      },
       async submit() {
       async submit() {
         if (!this.form.content || !this.form.content.trim()) {
         if (!this.form.content || !this.form.content.trim()) {
           uni.showToast({ title: '请填写建议内容', icon: 'none' })
           uni.showToast({ title: '请填写建议内容', icon: 'none' })
@@ -55,7 +114,12 @@
         if (this.submitting) return
         if (this.submitting) return
         this.submitting = true
         this.submitting = true
         try {
         try {
-          await submitSuggestion({ ...this.form })
+          await submitSuggestion({
+            ...this.form,
+            attachmentUrls: this.attachments.map((a) => a.url)
+          })
+          ;(this.attachments || []).forEach((a) => revokePreview(a))
+          this.attachments = []
           uni.showToast({ title: '提交成功', icon: 'success' })
           uni.showToast({ title: '提交成功', icon: 'success' })
           setTimeout(() => {
           setTimeout(() => {
             uni.reLaunch({ url: '/pages/home/index' })
             uni.reLaunch({ url: '/pages/home/index' })
@@ -70,32 +134,35 @@
 
 
 <style scoped>
 <style scoped>
   .page {
   .page {
-    padding: 24rpx;
-  }
-  .box {
-    padding: 28rpx;
-  }
-  textarea {
-    min-height: 200rpx;
-  }
-  .uploads {
-    display: flex;
-    flex-wrap: wrap;
-    gap: 16rpx;
-    margin-bottom: 20rpx;
-  }
-  .thumb,
-  .add {
-    width: 140rpx;
-    height: 140rpx;
-    border-radius: 10rpx;
+    padding-bottom: calc(40rpx + env(safe-area-inset-bottom));
   }
   }
-  .add {
-    border: 1rpx dashed #ccc;
+
+  .file-chip {
+    width: 100%;
+    height: 100%;
+    padding: 12rpx;
+    box-sizing: border-box;
     display: flex;
     display: flex;
+    flex-direction: column;
     align-items: center;
     align-items: center;
     justify-content: center;
     justify-content: center;
-    font-size: 48rpx;
-    color: #999;
+    gap: 8rpx;
+    background: #f5f5f5;
+  }
+
+  .file-ext {
+    font-size: 22rpx;
+    font-weight: 700;
+    color: var(--primary);
+  }
+
+  .file-name {
+    font-size: 18rpx;
+    color: #666;
+    text-align: center;
+    width: 100%;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
   }
   }
 </style>
 </style>

+ 221 - 17
mobile-wx/utils/upload.js

@@ -1,12 +1,62 @@
 import { BASE_URL } from '../config/api.js'
 import { BASE_URL } from '../config/api.js'
 
 
-export function uploadFile(filePath, folder = 'feedback') {
+export const ACCEPT =
+  'image/*,video/*,.pdf,.doc,.docx,.xls,.xlsx,.zip'
+
+export const ACCEPT_EXTENSIONS = [
+  '.jpg',
+  '.jpeg',
+  '.png',
+  '.gif',
+  '.webp',
+  '.bmp',
+  '.mp4',
+  '.mov',
+  '.avi',
+  '.webm',
+  '.pdf',
+  '.doc',
+  '.docx',
+  '.xls',
+  '.xlsx',
+  '.zip'
+]
+
+const IMAGE_EXTS = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp']
+
+export function isImageUrl(urlOrName = '') {
+  const name = String(urlOrName).split('?')[0].toLowerCase()
+  return IMAGE_EXTS.some((ext) => name.endsWith(ext))
+}
+
+export function fileNameFromUrl(url = '') {
+  try {
+    const path = String(url).split('?')[0]
+    const seg = path.substring(path.lastIndexOf('/') + 1)
+    return decodeURIComponent(seg || '附件')
+  } catch (e) {
+    return '附件'
+  }
+}
+
+export function revokePreview(item) {
+  const p = item && item.previewUrl
+  if (p && String(p).startsWith('blob:')) {
+    try {
+      URL.revokeObjectURL(p)
+    } catch (e) {}
+  }
+}
+
+export function uploadFile(filePath, folder = 'feedback', fileName) {
   return new Promise((resolve, reject) => {
   return new Promise((resolve, reject) => {
+    const formData = { folder }
+    if (fileName) formData.fileName = fileName
     uni.uploadFile({
     uni.uploadFile({
       url: `${BASE_URL}/public/attachment/upload`,
       url: `${BASE_URL}/public/attachment/upload`,
       filePath,
       filePath,
       name: 'file',
       name: 'file',
-      formData: { folder },
+      formData,
       success: (res) => {
       success: (res) => {
         try {
         try {
           const body = typeof res.data === 'string' ? JSON.parse(res.data) : res.data
           const body = typeof res.data === 'string' ? JSON.parse(res.data) : res.data
@@ -25,23 +75,177 @@ export function uploadFile(filePath, folder = 'feedback') {
   })
   })
 }
 }
 
 
-export function chooseAndUploadImages(count = 3, folder = 'feedback') {
+function pickFilesViaInput() {
   return new Promise((resolve, reject) => {
   return new Promise((resolve, reject) => {
-    uni.chooseImage({
-      count,
-      success: async (res) => {
-        try {
-          const urls = []
-          for (const path of res.tempFilePaths || []) {
-            const data = await uploadFile(path, folder)
-            if (data?.url) urls.push(data.url)
+    // #ifdef H5
+    const input = document.createElement('input')
+    input.type = 'file'
+    input.multiple = true
+    input.accept = ACCEPT
+    input.style.display = 'none'
+    const cleanup = () => {
+      if (input.parentNode) input.parentNode.removeChild(input)
+    }
+    input.onchange = () => {
+      const files = Array.from(input.files || [])
+      cleanup()
+      resolve(files)
+    }
+    input.oncancel = () => {
+      cleanup()
+      reject(new Error('cancel'))
+    }
+    document.body.appendChild(input)
+    input.click()
+    // #endif
+    // #ifndef H5
+    reject(new Error('unsupported'))
+    // #endif
+  })
+}
+
+function uploadBlobAsFile(file, folder) {
+  // H5: uni.uploadFile 需要本地临时路径;用 blob URL / File 路径
+  return new Promise((resolve, reject) => {
+    // #ifdef H5
+    const url = `${BASE_URL}/public/attachment/upload`
+    const fd = new FormData()
+    fd.append('file', file, file.name)
+    fd.append('folder', folder)
+    fetch(url, { method: 'POST', body: fd })
+      .then((r) => r.json())
+      .then((body) => {
+        if (body.code === 200) {
+          resolve(body.data)
+          return
+        }
+        uni.showToast({ title: body.message || '上传失败', icon: 'none' })
+        reject(body)
+      })
+      .catch(reject)
+    // #endif
+    // #ifndef H5
+    reject(new Error('unsupported'))
+    // #endif
+  })
+}
+
+/**
+ * 选择并上传多种类型附件,不限制个数。
+ * @returns {Promise<Array<{previewUrl:string,url:string,fileName?:string,size?:number}>>}
+ */
+export function chooseAndUploadFiles(folder = 'feedback') {
+  return new Promise(async (resolve, reject) => {
+    try {
+      // 优先 H5 原生多选(支持任意类型、不限个数)
+      // #ifdef H5
+      try {
+        const files = await pickFilesViaInput()
+        if (!files.length) {
+          resolve([])
+          return
+        }
+        const items = []
+        for (const file of files) {
+          const previewUrl = URL.createObjectURL(file)
+          try {
+            const data = await uploadBlobAsFile(file, folder)
+            if (data?.url) {
+              items.push({
+                previewUrl,
+                url: data.url,
+                fileName: data.fileName || file.name,
+                size: data.size
+              })
+            } else {
+              URL.revokeObjectURL(previewUrl)
+            }
+          } catch (err) {
+            URL.revokeObjectURL(previewUrl)
+            throw err
           }
           }
-          resolve(urls)
-        } catch (e) {
-          reject(e)
         }
         }
-      },
-      fail: reject
-    })
+        resolve(items)
+        return
+      } catch (e) {
+        if (e && e.message === 'cancel') {
+          resolve([])
+          return
+        }
+      }
+      // #endif
+
+      // App / 部分端:uni.chooseFile
+      if (typeof uni.chooseFile === 'function') {
+        uni.chooseFile({
+          count: 99,
+          type: 'all',
+          extension: ACCEPT_EXTENSIONS.map((e) => e.replace(/^\./, '')),
+          success: async (res) => {
+            try {
+              const items = []
+              const paths = res.tempFilePaths || []
+              const tempFiles = res.tempFiles || []
+              for (let i = 0; i < paths.length; i++) {
+                const path = paths[i]
+                const name = tempFiles[i]?.name
+                const data = await uploadFile(path, folder, name)
+                if (data?.url) {
+                  items.push({
+                    previewUrl: path,
+                    url: data.url,
+                    fileName: data.fileName || name || fileNameFromUrl(data.url),
+                    size: data.size
+                  })
+                }
+              }
+              resolve(items)
+            } catch (err) {
+              reject(err)
+            }
+          },
+          fail: (err) => {
+            if (err && (err.errMsg || '').includes('cancel')) {
+              resolve([])
+              return
+            }
+            reject(err)
+          }
+        })
+        return
+      }
+
+      // 兜底:仅图片
+      uni.chooseImage({
+        count: 9,
+        success: async (res) => {
+          try {
+            const items = []
+            for (const path of res.tempFilePaths || []) {
+              const data = await uploadFile(path, folder)
+              if (data?.url) {
+                items.push({
+                  previewUrl: path,
+                  url: data.url,
+                  fileName: data.fileName || fileNameFromUrl(data.url),
+                  size: data.size
+                })
+              }
+            }
+            resolve(items)
+          } catch (err) {
+            reject(err)
+          }
+        },
+        fail: reject
+      })
+    } catch (e) {
+      reject(e)
+    }
   })
   })
 }
 }
+
+/** @deprecated 使用 chooseAndUploadFiles */
+export function chooseAndUploadImages(count = 9, folder = 'feedback') {
+  return chooseAndUploadFiles(folder).then((items) => items.map((i) => i.url))
+}