`Y^
zd;ict$iFIjW0x9iCeY)*IXHS0A7jOxHa&QnwhVg?TP6Ti{!!w|9oJ3#h@b8U;f*cqRz(f57#Ywf9hOTld)Qv$E(nFPty6--qUY5FU^lU{;D
zJ!~V&lgGZ%JY$gIPpVR9AVBgBM}u%QwLscr;F}-_1oH41Eg&Z{{kkL+VL)9`DC68)
zfXog4e#uB;Q~@`HYDED+R)nQf0Q8}B46sb&Uxe!cau`OG3Yc1VMj!?BH}b+uwi5E<
zI^gIBA7BC;bw!H|FoXKa0Rot?%We$2PL~X@Vj#k62#7K)3=NpTaNNFsg)$5X%oAWw
z|8IdoVRk%yWVAp$VPPuNaG)LRxBT``lPS0bCj;zKb6BHhH~be}J1>*u{y|GVfjp31
z{QmuW;NR2J^Iqn{Tq+FYf^v~BLPE}4RrgI4$ti)*jh!eGkE#+&|!cdOz&h*=J_=nLQuQocTrTYOB#wu~7j4Xf@PT^e=Mje*#8+
zaq3SwQd}g6yS|z-_|nh53;>h0hKiD*k0tRV#qg9QT|{?dFZQvs#@Ts%jHX`&Q>U17
zro?>WXrrJm6hUOn7}q%DeNDQV^o$hU{f?RSJ=AlyesSb2^g1L#SyY^i5!|m*;L8H)
zS6I`alp#6eS#Nbf9u19FC~529+CVoS&?k@htO&J!wzoLX1a6YtH$Xr{#gE;$DMOM&
zb8aR8*0fXULsG?_)W@9~52oF5V+pFBd-o}O|2`6b;~
z=R^}eJzIhl^ir<5%3P-js_`x&+xJBW0#^%gM`1drMws1H%jXHs*G`61oi=m_vLeZ?
zMv+@=o)UWhbaxNLHhkBf=8BvS*szo=O-hJHMs7gCAM?&1UG(QiX|CQS;4S1;Ji-aR_2^d-vi|+(bErJ)GR5hGv3cOQOmE8-<%2`mNR9ZjK1fz
zpic67iq4WD1uC1So?A<%lH7SSXcl#yjM-+9Mn1-IW!_
zJE_)8Epo(Kt{#>%#}5Tvm4U*~_8bmk}-z
zY~FG^TzQMss3%Z&k3jT7uTX%ALK8VbW=<
zeDfOo*`jr@>2_C@h#|bmHfE91wzGp}GwFjx(|yag^*!Qhfo?}Pq;FcG@>J=n;O?yT
zyK7J>L3On))XdK37mgh2`~Zn&Z7h_>b7o0%z-kKVB5A%J>4>k6lZ|097P;<(qRvXA
z&4T?;kXDqY-%AG@O)K$2-f0NEVU50_VA+@x>-S8zPc<__tc%i#`yG5G3P&}IcYkHZ
z;n}|F)Hu}W^S5ljihx5rzH_!km_Ke8pR@4mFkl^kz$=bQjN`LY9HaNtjvKuecXr6N
z#Pzm@!a}YA7_*d-3tua|c2>)SOJGtXEOpE^Q@D>M3K~+oB`!7>{WE`&7_rMtSN7Gv
z$sy}rNoAj&U8>`CRc64wL*zW25iG7x;qw1FgTm;DlfWLbE)L
zs3D5@)5rN#4hr;&Ix^glrN>ORtuKCeXKoa;Q!AxfH
zV(|rOkZQdAux#ztm0cfv5Z{G=_|vlqR>cNIXLU`?u6vv)86^8z=cg<=B4e7=FEEEz<^5l4+Ao!AjDzgxzQUu5@v;+Gm|;J(3hv!
z!>pPV`-|E;kw;v_=1h+$*yQ@P*xACT2F32J%jC_`^&T&gA{rQMVXFc8x0*XTDGUM>
z^R9gUclTdzGYIG!3XrBM3E}ew45QL-muS7p4&aq(^!AT3jJnSt`1ZF`PwAd6&4+9T
zZ1TFo6*@NxUz+NY%ecDR97nh54562+W8_uuRfE>XOqwjc#W>QV@@6B_-m_oOQDn?)
zfj>8UyL&B!6fmf9`#<(!ps8l=kNRpzUcPzf8}?LJjT^#dNfIwG=>B`qK3o;tR(>Yj
zl5<g}T-4Dql7|lAH_Bi6pT=s}>RaS|)&McKioAu3BvB`Sr}4i}fkkG<3)d!$!ZhmqLWez$__
zeGUH&;BcRnL^+ghvUksGEBvX)@?`%O(|%KyX8R-=fI99zyMvvA!Kf*;z5`-&f^wu@sS>9~x}=a%`>vjSPs*ezOVgR&xSw
z^?P?cj>@mLbq;LpM}yyt3qLzB`8YGiI=LnF-CJTipCr(K`JHf;uJrx2yN
z!iOX(OLrcd$Mo*POU?q`sF8y(ossv`)`GB+q2J^9bM>uIiEqvfr7EKi6hRAd>)2B+
zD)8rx?e2q31*A-o;D#qxdBVT%ux~j_BN~@IbzR7VRx`LpxD2@}J%iWTTUO^9?-)1H
z*xGOZi=uc$szSy+RNVUyk*btUv7ud}t)Vy#b}a7ICd+p+%;KmIHj}L4#R$^gyK{|V
zM#d&1H0aSqOt&|V++Hx(q*VDwVLyeQoT$&Cg9&vV%wL|fNso695@msux_EkFr=1a&
zoV349Jw@9Qd#L+;ZCWK){5S5TPsQd&{0UH`jZ32UvhyK{S2iCZy%`twn&b&)DxFQa
z0;+5p{w)uSAFC**=3dOXBBLDM_B_|7cif6afTfgQnvx>yxLw71bKdkSwl+NM64yZF
zhuHyz8zwDD_d-M1b8?gsCness=(q6nkXM3?utHVVjdcRgT@aOkI3VnIf8|R*NUtrv
zmm01V(zh}|FY4_114%+L$Rl+EXYW5ugFNL!0LPH*H@|lnf02Pd(n?yJ+q{p{_y}}S
zzZMqiE4IbF9#_Hw^5my_zn2)m5!s9k6cxGD#az@^#iO5nVX$=vr0f9-d<`Gg1^q;n
z73VU3YJMxkG`CnwnG0LI
zyWM@{Ysl;MC>(`Nqzm?Uw+x5QA9RRzDZ9iYw@{9rdS@*$&hJ{pv7TJ
z+7RpC6kCsKWc2y{1muq&Zyj5=OqARZVBkA3?ltMuy&G!%3wXnWJie~O3VweGqYIK9
z`6MJezr|#}jLKyr34@6Yq2L}Qh`KN+IlY70FSUdAs+ZJEfjdKwKUgR~I}o+Jd|R_|
zjYLnT`Qc24PMOSf^CUGdQ*MAdX|c(#m_)b8@b$*F7yP2}6GpYPEHJ27iIY~2raBU>
zXT;R-D!4$Z{S(y@#@Ext15PM+@#BMgWgY60WMl1JRkV3Tr*TPME;!WRt*!x%m->
zgGPG~$U<669$XW;*Mb~cwAynP~bevn0ak6|qCY_N$vGncP*F4NJJn)M!8z
z8$lSTER8yTNEXmr_*jjP5>m}4I5E9)ow0oE$@jC1J4SAHnG=_fktZxx$){i9I7ky>^a?e;
z<=X7eQ5I-`4!q~kBMrKSH%nKtsnRLn++=s&LFFW~yhGNS>sn=idO!UaowCn+hXpHh
zxG;-j;?_|GwGWP*U>?2*X5o|$4$WyRqObBKtC;NkWvL3+`Q43!XWXOvA+tqiJC31x
zkxraCO>Y!nDv64s_>1_z04Dt
z2Za!T`!oiTO{5cwCI$|hMf(m1-Mu1-txAQf
zR4%NjiWMvL4?zW@G}yc?Zm6NF-!gpcSV-Ggc^OUh_>rFcd6Z4k)LqcJ`aVYz(sO6+
z#gFo}fp<_Y-U5pi%VKq(u_!*CF+pglUU1hw0OK9`A$ZND9P(yTz$sX6e7dFI(s8Na
zIK5s{t&hiemkRhGG_Njg#wtu@@yAL(#gt^E2bMM~l|g`52#(72W0P6+I>~nYy~vFC
z4RuId6XKH){$1q}IS8pNy4;Kkp9NbSY0Piqqw+6S@P
zD{>(FK~>}y@xKzLP1dgCI}E?%o>%9Wso*ki-1VZfUU5UI+KSlJL)>EMqxZz62
z9ud%5$(&m=pR+9K)gn8PIKZDR5ng0&XCyIC2Fll5hJgJy?Y7$F&&LI=!$?7gh%(7J
zuwsH&Khg=C*Ep%FpEvZJy1BDu=@n&Cmg@~UIUv_{3ToBA
zNMa-`vgcqP8;`J-?Uq+^>EM?S65Y1k88M!VXpRy84ZpsL{+oB%Kz-NT}6~+#j^w
z-n7?E(=br>hneo*%wYIroHA0uu3pE`H~W!l8~OA4
z@#_N6jZRj*mLptBBmDabb@e`f{y&>h
z{*X-rrlkTo_3DyMEMCe%u~}`qu_{gtVjLh4J=LVv|5m6GYMu7p@N+hS8p}m5c0(tZ
zo^Y4K*pz66M_LiJV(wF>Pnprc4qO8TTO9YbAcpMPwp(*kK6RPFF#(a#)9Xpq`91F7*yGmTL~VbJ_796U!!w`H@9s({ILMrPXDXcLTBN=U
zZ$bl8M($&_w->lc>@c&6<$~tuGylIZrvC|Los(c|Pp&oEv%J3mZ$RULw#pY}>#+X=
DSYLF1
literal 0
HcmV?d00001
diff --git a/uni_modules/uni-file-picker/changelog.md b/uni_modules/uni-file-picker/changelog.md
new file mode 100644
index 0000000..5320eb2
--- /dev/null
+++ b/uni_modules/uni-file-picker/changelog.md
@@ -0,0 +1,91 @@
+## 1.1.3(2025-12-03)
+- 修复: 腾讯云目录错误导致的上传错误问题
+## 1.1.2(2025-09-17)
+- 修复 设置readonly属性后内容插槽失效的问题。
+## 1.1.1(2025-09-03)
+- 修复 动态dir目录,不生效的问题
+## 1.1.0(2025-09-02)
+- 新增 dir 属性,可以选择上传目录
+## 1.0.13(2025-08-18)
+- 修复 删除文件后,返回信息不包含file对象的问题
+## 1.0.12(2025-04-14)
+- 修复 支付宝小程序 上传样式问题
+## 1.0.10(2024-07-09)
+- 优化 vue3兼容性
+## 1.0.9(2024-07-09)
+- 修复 value 属性不兼容vue3的bug
+## 1.0.8(2024-03-20)
+- 补充 删除文件时返回文件下标
+## 1.0.7(2024-02-21)
+- 新增 微信小程序选择视频时改用chooseMedia,并返回视频缩略图
+## 1.0.6(2024-01-06)
+- 新增 微信小程序不再调用chooseImage,而是调用chooseMedia
+## 1.0.5(2024-01-03)
+- 新增 上传文件至云存储携带本地文件名称
+## 1.0.4(2023-03-29)
+- 修复 手动上传删除一个文件后不能再上传的bug
+## 1.0.3(2022-12-19)
+- 新增 sourceType 属性, 可以自定义图片和视频选择的来源
+## 1.0.2(2022-07-04)
+- 修复 在uni-forms下样式不生效的bug
+## 1.0.1(2021-11-23)
+- 修复 参数为对象的情况下,url在某些情况显示错误的bug
+## 1.0.0(2021-11-19)
+- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-file-picker](https://uniapp.dcloud.io/component/uniui/uni-file-picker)
+## 0.2.16(2021-11-08)
+- 修复 传入空对象 ,显示错误的Bug
+## 0.2.15(2021-08-30)
+- 修复 return-type="object" 时且存在v-model时,无法删除文件的Bug
+## 0.2.14(2021-08-23)
+- 新增 参数中返回 fileID 字段
+## 0.2.13(2021-08-23)
+- 修复 腾讯云传入fileID 不能回显的bug
+- 修复 选择图片后,不能放大的问题
+## 0.2.12(2021-08-17)
+- 修复 由于 0.2.11 版本引起的不能回显图片的Bug
+## 0.2.11(2021-08-16)
+- 新增 clearFiles(index) 方法,可以手动删除指定文件
+- 修复 v-model 值设为 null 报错的Bug
+## 0.2.10(2021-08-13)
+- 修复 return-type="object" 时,无法删除文件的Bug
+## 0.2.9(2021-08-03)
+- 修复 auto-upload 属性失效的Bug
+## 0.2.8(2021-07-31)
+- 修复 fileExtname属性不指定值报错的Bug
+## 0.2.7(2021-07-31)
+- 修复 在某种场景下图片不回显的Bug
+## 0.2.6(2021-07-30)
+- 修复 return-type为object下,返回值不正确的Bug
+## 0.2.5(2021-07-30)
+- 修复(重要) H5 平台下如果和uni-forms组件一同使用导致页面卡死的问题
+## 0.2.3(2021-07-28)
+- 优化 调整示例代码
+## 0.2.2(2021-07-27)
+- 修复 vue3 下赋值错误的Bug
+- 优化 h5平台下上传文件导致页面卡死的问题
+## 0.2.0(2021-07-13)
+- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
+## 0.1.1(2021-07-02)
+- 修复 sourceType 缺少默认值导致 ios 无法选择文件
+## 0.1.0(2021-06-30)
+- 优化 解耦与uniCloud的强绑定关系 ,如不绑定服务空间,默认autoUpload为false且不可更改
+## 0.0.11(2021-06-30)
+- 修复 由 0.0.10 版本引发的 returnType 属性失效的问题
+## 0.0.10(2021-06-29)
+- 优化 文件上传后进度条消失时机
+## 0.0.9(2021-06-29)
+- 修复 在uni-forms 中,删除文件 ,获取的值不对的Bug
+## 0.0.8(2021-06-15)
+- 修复 删除文件时无法触发 v-model 的Bug
+## 0.0.7(2021-05-12)
+- 新增 组件示例地址
+## 0.0.6(2021-04-09)
+- 修复 选择的文件非 file-extname 字段指定的扩展名报错的Bug
+## 0.0.5(2021-04-09)
+- 优化 更新组件示例
+## 0.0.4(2021-04-09)
+- 优化 file-extname 字段支持字符串写法,多个扩展名需要用逗号分隔
+## 0.0.3(2021-02-05)
+- 调整为uni_modules目录规范
+- 修复 微信小程序不指定 fileExtname 属性选择失败的Bug
diff --git a/uni_modules/uni-file-picker/components/uni-file-picker/choose-and-upload-file.js b/uni_modules/uni-file-picker/components/uni-file-picker/choose-and-upload-file.js
new file mode 100644
index 0000000..9c6bcdf
--- /dev/null
+++ b/uni_modules/uni-file-picker/components/uni-file-picker/choose-and-upload-file.js
@@ -0,0 +1,287 @@
+'use strict';
+
+const ERR_MSG_OK = 'chooseAndUploadFile:ok';
+const ERR_MSG_FAIL = 'chooseAndUploadFile:fail';
+
+function chooseImage(opts) {
+ const {
+ count,
+ sizeType = ['original', 'compressed'],
+ sourceType,
+ extension
+ } = opts
+ return new Promise((resolve, reject) => {
+ // 微信由于旧接口不再维护,针对微信小程序平台改用chooseMedia接口
+ // #ifdef MP-WEIXIN
+ uni.chooseMedia({
+ count,
+ sizeType,
+ sourceType,
+ mediaType: ['image'],
+ extension,
+ success(res) {
+ res.tempFiles.forEach(item => {
+ item.path = item.tempFilePath;
+ })
+ resolve(normalizeChooseAndUploadFileRes(res, 'image'));
+ },
+ fail(res) {
+ reject({
+ errMsg: res.errMsg.replace('chooseImage:fail', ERR_MSG_FAIL),
+ });
+ },
+ })
+ // #endif
+ // #ifndef MP-WEIXIN
+ uni.chooseImage({
+ count,
+ sizeType,
+ sourceType,
+ extension,
+ success(res) {
+ resolve(normalizeChooseAndUploadFileRes(res, 'image'));
+ },
+ fail(res) {
+ reject({
+ errMsg: res.errMsg.replace('chooseImage:fail', ERR_MSG_FAIL),
+ });
+ },
+ });
+ // #endif
+
+ });
+}
+
+function chooseVideo(opts) {
+ const {
+ count,
+ camera,
+ compressed,
+ maxDuration,
+ sourceType,
+ extension
+ } = opts;
+ return new Promise((resolve, reject) => {
+ // 微信由于旧接口不再维护,针对微信小程序平台改用chooseMedia接口
+ // #ifdef MP-WEIXIN
+ uni.chooseMedia({
+ count,
+ compressed,
+ maxDuration,
+ sourceType,
+ extension,
+ mediaType: ['video'],
+ success(res) {
+ const {
+ tempFiles,
+ } = res;
+ resolve(normalizeChooseAndUploadFileRes({
+ errMsg: 'chooseVideo:ok',
+ tempFiles: tempFiles.map(item => {
+ return {
+ name: item.name || '',
+ path: item.tempFilePath,
+ thumbTempFilePath: item.thumbTempFilePath,
+ size:item.size,
+ type: (res.tempFile && res.tempFile.type) || '',
+ width:item.width,
+ height:item.height,
+ duration:item.duration,
+ fileType: 'video',
+ cloudPath: '',
+ }
+ }),
+ }, 'video'));
+ },
+ fail(res) {
+ reject({
+ errMsg: res.errMsg.replace('chooseVideo:fail', ERR_MSG_FAIL),
+ });
+ },
+ })
+ // #endif
+ // #ifndef MP-WEIXIN
+ uni.chooseVideo({
+ camera,
+ compressed,
+ maxDuration,
+ sourceType,
+ extension,
+ success(res) {
+ const {
+ tempFilePath,
+ duration,
+ size,
+ height,
+ width
+ } = res;
+ resolve(normalizeChooseAndUploadFileRes({
+ errMsg: 'chooseVideo:ok',
+ tempFilePaths: [tempFilePath],
+ tempFiles: [{
+ name: (res.tempFile && res.tempFile.name) || '',
+ path: tempFilePath,
+ size,
+ type: (res.tempFile && res.tempFile.type) || '',
+ width,
+ height,
+ duration,
+ fileType: 'video',
+ cloudPath: '',
+ }, ],
+ }, 'video'));
+ },
+ fail(res) {
+ reject({
+ errMsg: res.errMsg.replace('chooseVideo:fail', ERR_MSG_FAIL),
+ });
+ },
+ });
+ // #endif
+ });
+}
+
+function chooseAll(opts) {
+ const {
+ count,
+ extension
+ } = opts;
+ return new Promise((resolve, reject) => {
+ let chooseFile = uni.chooseFile;
+ if (typeof wx !== 'undefined' &&
+ typeof wx.chooseMessageFile === 'function') {
+ chooseFile = wx.chooseMessageFile;
+ }
+ if (typeof chooseFile !== 'function') {
+ return reject({
+ errMsg: ERR_MSG_FAIL + ' 请指定 type 类型,该平台仅支持选择 image 或 video。',
+ });
+ }
+ chooseFile({
+ type: 'all',
+ count,
+ extension,
+ success(res) {
+ resolve(normalizeChooseAndUploadFileRes(res));
+ },
+ fail(res) {
+ reject({
+ errMsg: res.errMsg.replace('chooseFile:fail', ERR_MSG_FAIL),
+ });
+ },
+ });
+ });
+}
+
+function normalizeChooseAndUploadFileRes(res, fileType) {
+ res.tempFiles.forEach((item, index) => {
+ if (!item.name) {
+ item.name = item.path.substring(item.path.lastIndexOf('/') + 1);
+ }
+ if (fileType) {
+ item.fileType = fileType;
+ }
+ item.cloudPath =
+ Date.now() + '_' + index + item.name.substring(item.name.lastIndexOf('.'));
+ });
+ if (!res.tempFilePaths) {
+ res.tempFilePaths = res.tempFiles.map((file) => file.path);
+ }
+ return res;
+}
+
+function uploadCloudFiles(files, max = 5, onUploadProgress) {
+ files = JSON.parse(JSON.stringify(files))
+ const len = files.length
+ let count = 0
+ let self = this
+ return new Promise(resolve => {
+ while (count < max) {
+ next()
+ }
+
+ function next() {
+ let cur = count++
+ if (cur >= len) {
+ !files.find(item => !item.url && !item.errMsg) && resolve(files)
+ return
+ }
+ const fileItem = files[cur]
+ const index = self.files.findIndex(v => v.uuid === fileItem.uuid)
+ fileItem.url = ''
+ delete fileItem.errMsg
+
+ uniCloud
+ .uploadFile({
+ filePath: fileItem.path,
+ cloudPath: fileItem.cloudPath,
+ fileType: fileItem.fileType,
+ onUploadProgress: res => {
+ res.index = index
+ onUploadProgress && onUploadProgress(res)
+ }
+ })
+ .then(res => {
+ fileItem.url = res.fileID
+ fileItem.index = index
+ if (cur < len) {
+ next()
+ }
+ })
+ .catch(res => {
+ fileItem.errMsg = res.errMsg || res.message
+ fileItem.index = index
+ if (cur < len) {
+ next()
+ }
+ })
+ }
+ })
+}
+
+
+
+
+
+function uploadFiles(choosePromise, {
+ onChooseFile,
+ onUploadProgress
+}) {
+ return choosePromise
+ .then((res) => {
+ if (onChooseFile) {
+ const customChooseRes = onChooseFile(res);
+ if (typeof customChooseRes !== 'undefined') {
+ return Promise.resolve(customChooseRes).then((chooseRes) => typeof chooseRes === 'undefined' ?
+ res : chooseRes);
+ }
+ }
+ return res;
+ })
+ .then((res) => {
+ if (res === false) {
+ return {
+ errMsg: ERR_MSG_OK,
+ tempFilePaths: [],
+ tempFiles: [],
+ };
+ }
+ return res
+ })
+}
+
+function chooseAndUploadFile(opts = {
+ type: 'all'
+}) {
+ if (opts.type === 'image') {
+ return uploadFiles(chooseImage(opts), opts);
+ } else if (opts.type === 'video') {
+ return uploadFiles(chooseVideo(opts), opts);
+ }
+ return uploadFiles(chooseAll(opts), opts);
+}
+
+export {
+ chooseAndUploadFile,
+ uploadCloudFiles
+};
diff --git a/uni_modules/uni-file-picker/components/uni-file-picker/uni-file-picker.vue b/uni_modules/uni-file-picker/components/uni-file-picker/uni-file-picker.vue
new file mode 100644
index 0000000..cf67ac7
--- /dev/null
+++ b/uni_modules/uni-file-picker/components/uni-file-picker/uni-file-picker.vue
@@ -0,0 +1,680 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/uni_modules/uni-file-picker/components/uni-file-picker/upload-file.vue b/uni_modules/uni-file-picker/components/uni-file-picker/upload-file.vue
new file mode 100644
index 0000000..0d26379
--- /dev/null
+++ b/uni_modules/uni-file-picker/components/uni-file-picker/upload-file.vue
@@ -0,0 +1,323 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{item.name}}
+
+
+
+
+
+
+
+
+
+ 点击重试
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uni_modules/uni-file-picker/components/uni-file-picker/upload-image.vue b/uni_modules/uni-file-picker/components/uni-file-picker/upload-image.vue
new file mode 100644
index 0000000..0ce5eb7
--- /dev/null
+++ b/uni_modules/uni-file-picker/components/uni-file-picker/upload-image.vue
@@ -0,0 +1,285 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 点击重试
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/uni_modules/uni-file-picker/components/uni-file-picker/utils.js b/uni_modules/uni-file-picker/components/uni-file-picker/utils.js
new file mode 100644
index 0000000..1bc9259
--- /dev/null
+++ b/uni_modules/uni-file-picker/components/uni-file-picker/utils.js
@@ -0,0 +1,110 @@
+/**
+ * 获取文件名和后缀
+ * @param {String} name
+ */
+export const get_file_ext = (name) => {
+ const last_len = name.lastIndexOf('.')
+ const len = name.length
+ return {
+ name: name.substring(0, last_len),
+ ext: name.substring(last_len + 1, len)
+ }
+}
+
+/**
+ * 获取扩展名
+ * @param {Array} fileExtname
+ */
+export const get_extname = (fileExtname) => {
+ if (!Array.isArray(fileExtname)) {
+ let extname = fileExtname.replace(/(\[|\])/g, '')
+ return extname.split(',')
+ } else {
+ return fileExtname
+ }
+ return []
+}
+
+/**
+ * 获取文件和检测是否可选
+ */
+export const get_files_and_is_max = (res, _extname) => {
+ let filePaths = []
+ let files = []
+ if(!_extname || _extname.length === 0){
+ return {
+ filePaths,
+ files
+ }
+ }
+ res.tempFiles.forEach(v => {
+ let fileFullName = get_file_ext(v.name)
+ const extname = fileFullName.ext.toLowerCase()
+ if (_extname.indexOf(extname) !== -1) {
+ files.push(v)
+ filePaths.push(v.path)
+ }
+ })
+ if (files.length !== res.tempFiles.length) {
+ uni.showToast({
+ title: `当前选择了${res.tempFiles.length}个文件 ,${res.tempFiles.length - files.length} 个文件格式不正确`,
+ icon: 'none',
+ duration: 5000
+ })
+ }
+
+ return {
+ filePaths,
+ files
+ }
+}
+
+
+/**
+ * 获取图片信息
+ * @param {Object} filepath
+ */
+export const get_file_info = (filepath) => {
+ return new Promise((resolve, reject) => {
+ uni.getImageInfo({
+ src: filepath,
+ success(res) {
+ resolve(res)
+ },
+ fail(err) {
+ reject(err)
+ }
+ })
+ })
+}
+/**
+ * 获取封装数据
+ */
+export const get_file_data = async (files, type = 'image') => {
+ // 最终需要上传数据库的数据
+ let fileFullName = get_file_ext(files.name)
+ const extname = fileFullName.ext.toLowerCase()
+ let filedata = {
+ name: files.name,
+ uuid: files.uuid,
+ extname: extname || '',
+ cloudPath: files.cloudPath,
+ fileType: files.fileType,
+ thumbTempFilePath: files.thumbTempFilePath,
+ url: files.path || files.path,
+ size: files.size, //单位是字节
+ image: {},
+ path: files.path,
+ video: {}
+ }
+ if (type === 'image') {
+ const imageinfo = await get_file_info(files.path)
+ delete filedata.video
+ filedata.image.width = imageinfo.width
+ filedata.image.height = imageinfo.height
+ filedata.image.location = imageinfo.path
+ } else {
+ delete filedata.image
+ }
+ return filedata
+}
diff --git a/uni_modules/uni-file-picker/package.json b/uni_modules/uni-file-picker/package.json
new file mode 100644
index 0000000..b13e315
--- /dev/null
+++ b/uni_modules/uni-file-picker/package.json
@@ -0,0 +1,105 @@
+{
+ "id": "uni-file-picker",
+ "displayName": "uni-file-picker 文件选择上传",
+ "version": "1.1.3",
+ "description": "文件选择上传组件,可以选择图片、视频等任意文件并上传到当前绑定的服务空间",
+ "keywords": [
+ "uni-ui",
+ "uniui",
+ "图片上传",
+ "文件上传"
+],
+ "repository": "https://github.com/dcloudio/uni-ui",
+ "engines": {
+ "HBuilderX": "",
+ "uni-app": "^4.33",
+ "uni-app-x": ""
+ },
+ "directories": {
+ "example": "../../temps/example_temps"
+ },
+ "dcloudext": {
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "无",
+ "permissions": "无"
+ },
+ "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
+ "type": "component-vue",
+ "darkmode": "x",
+ "i18n": "x",
+ "widescreen": "x"
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uni-scss"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "√",
+ "aliyun": "√",
+ "alipay": "√"
+ },
+ "client": {
+ "uni-app": {
+ "vue": {
+ "vue2": "√",
+ "vue3": "√"
+ },
+ "web": {
+ "safari": "√",
+ "chrome": "√"
+ },
+ "app": {
+ "vue": "√",
+ "nvue": "-",
+ "android": "√",
+ "ios": "√",
+ "harmony": "√"
+ },
+ "mp": {
+ "weixin": "√",
+ "alipay": "√",
+ "toutiao": "√",
+ "baidu": "√",
+ "kuaishou": "√",
+ "jd": "-",
+ "harmony": "-",
+ "qq": "√",
+ "lark": "-"
+ },
+ "quickapp": {
+ "huawei": "√",
+ "union": "√"
+ }
+ },
+ "uni-app-x": {
+ "web": {
+ "safari": "-",
+ "chrome": "-"
+ },
+ "app": {
+ "android": "-",
+ "ios": "-",
+ "harmony": "-"
+ },
+ "mp": {
+ "weixin": "-"
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/uni-file-picker/readme.md b/uni_modules/uni-file-picker/readme.md
new file mode 100644
index 0000000..7393e6c
--- /dev/null
+++ b/uni_modules/uni-file-picker/readme.md
@@ -0,0 +1,10 @@
+
+## FilePicker 文件选择上传
+
+> **组件名:uni-file-picker**
+> 代码块: `uFilePicker`
+
+
+文件选择上传组件,可以选择图片、视频等任意文件并上传到当前绑定的服务空间
+
+### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-file-picker)
\ No newline at end of file
diff --git a/utils/request.js b/utils/request.js
index dc54999..3a0ff57 100644
--- a/utils/request.js
+++ b/utils/request.js
@@ -1,6 +1,6 @@
import { getToken, removeToken } from './storage'
-const BASE_URL = 'http://c36bd4b4.natappfree.cc'
+const BASE_URL = import.meta.env.VITE_SYSTEM_URL
/**
* 网络请求封装
@@ -14,7 +14,7 @@ const request = options => {
method: 'GET',
data: {},
header: {
- 'deviceId': uni.getDeviceInfo().deviceId,
+ deviceId: uni.getDeviceInfo().deviceId,
'Content-Type': 'application/json' // 默认请求内容类型
}
}
diff --git a/utils/uploadFile.js b/utils/uploadFile.js
new file mode 100644
index 0000000..61e69a3
--- /dev/null
+++ b/utils/uploadFile.js
@@ -0,0 +1,134 @@
+import { getToken } from './storage'
+
+/**
+ * 二次封装的uni.uploadFile方法 (Promise化)
+ * @param {string} filePath - 要上传的文件路径
+ * @param {Object} [config] - 可选配置项
+ * @param {string} [config.url] - 上传接口地址,若不传则使用默认或全局配置
+ * @param {string} [config.name='file'] - 文件对应的 key
+ * @param {Object} [config.formData={}] - 额外的 form data
+ * @param {Object} [config.header] - 请求头,可覆盖默认头
+ * @param {number} [config.timeout] - 超时时间(ms)
+ * @returns {Promise} 返回一个Promise对象,成功时解析上传响应数据
+ */
+export const uploadSingleFile = (filePath, config = {}) => {
+ // 默认配置,可从全局导入或在此定义
+ const DEFAULT_CONFIG = {
+ baseURL: import.meta.env.VITE_SYSTEM_URL,
+ // timeout: 30000,
+ name: 'file',
+ header: {
+ deviceId: uni.getDeviceInfo().deviceId,
+ Authorization: `Bearer ${getToken()}`
+ }
+ }
+
+ // 合并配置
+ const mergedConfig = {
+ ...DEFAULT_CONFIG,
+ ...config,
+ header: {
+ ...DEFAULT_CONFIG.header
+ }
+ }
+
+ const { baseURL, ...uploadOptions } = mergedConfig
+ const finalUrl = baseURL
+ ? `${baseURL}${uploadOptions.url || ''}`
+ : uploadOptions.url
+
+ if (!finalUrl) {
+ return Promise.reject(new Error('上传接口地址不能为空'))
+ }
+
+ return new Promise((resolve, reject) => {
+ uni.showLoading({
+ title: '上传中...',
+ mask: true // 防止触摸穿透
+ })
+ const uploadTask = uni.uploadFile({
+ url: finalUrl,
+ filePath: filePath,
+ name: uploadOptions.name,
+ formData: uploadOptions.formData,
+ header: uploadOptions.header,
+ timeout: uploadOptions.timeout,
+ success: res => {
+ uni.hideLoading()
+ if (res.statusCode === 200) {
+ try {
+ // 尝试解析返回数据,假设是
+ const data =
+ typeof res.data === 'string'
+ ? JSON.parse(res.data)
+ : res.data
+ // 验证失效去登录页
+ if (data.code === 401) {
+ uni.removeStorage({
+ key: 'token'
+ })
+ uni.reLaunch({
+ url: '/pages/login/login'
+ })
+ return
+ }
+ if (data.code === 200) {
+ console.log('上传成功:', data)
+ resolve(data.url) // 根据后端实际结构,可能需进一步处理,如 data.data.url
+ return
+ }
+
+ uni.showToast({
+ title: data.msg || '请求失败',
+ icon: 'none'
+ })
+ } catch (e) {
+ // 解析失败可能返回的是字符串,直接返回
+ resolve(res.data)
+ }
+ return
+ }
+
+ reject(new Error(`上传失败,状态码: ${res.statusCode}`))
+ },
+ fail: err => {
+ uni.hideLoading()
+ reject(err)
+ }
+ })
+
+ // 可选:监听上传进度事件[6](@ref)
+ uploadTask.onProgressUpdate(res => {
+ console.log('上传进度:', res.progress)
+ console.log('已上传:', res.totalBytesSent, '字节')
+ console.log('总大小:', res.totalBytesExpectedToSend, '字节')
+ if (res.progress !== 100) {
+ uni.showLoading({
+ title: `업로드 진행: ${res.progress}`,
+ mask: true // 防止触摸穿透
+ })
+ }
+ // 可通过自定义事件或回调函数将进度传递出去
+ if (typeof config.onProgress === 'function') {
+ config.onProgress(res)
+ }
+ })
+ })
+}
+
+/**
+ * 多文件上传
+ * @param {Array} filePaths - 文件路径数组
+ * @param {Object} config - 同uploadSingleFile的config
+ * @returns {Promise} 返回一个Promise,解析为所有上传结果数组
+ */
+export const uploadMultipleFiles = (filePaths, config = {}) => {
+ // 使用Promise.all同时发起所有上传请求[3,4](@ref)
+ const uploadPromises = filePaths.map(
+ filePath =>
+ uploadSingleFile(filePath, config)
+ .then(res => ({ status: 'fulfilled', value: res })) // 成功,记录结果
+ .catch(err => ({ status: 'rejected', reason: err })) // 失败,记录原因
+ )
+ return Promise.all(uploadPromises)
+}