├── .gitignore ├── hybrid └── html │ ├── README.md │ ├── js │ ├── signclient.js │ └── h5uploader.js │ └── index.html ├── static └── logo.png ├── main.js ├── App.vue ├── .hbuilderx └── launch.json ├── pages ├── index │ ├── index.vue │ ├── fileUpload.vue │ ├── h5Upload.vue │ └── androidUpload.vue └── yzcloud │ └── yz-preview.vue ├── config └── index.js ├── pages.json ├── uni.scss ├── README.md └── manifest.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .project 3 | unpackage/ 4 | .vscode/ 5 | .idea 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /hybrid/html/README.md: -------------------------------------------------------------------------------- 1 | ## web-view加载本地html 2 | > 参考https://uniapp.dcloud.io/component/web-view -------------------------------------------------------------------------------- /static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavieAll/uniapp-file-upload/HEAD/static/logo.png -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App' 3 | 4 | Vue.config.productionTip = false 5 | 6 | App.mpType = 'app' 7 | 8 | const app = new Vue({ 9 | ...App 10 | }) 11 | app.$mount() 12 | -------------------------------------------------------------------------------- /App.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 18 | -------------------------------------------------------------------------------- /.hbuilderx/launch.json: -------------------------------------------------------------------------------- 1 | { // launch.json 配置了启动调试时相关设置,configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/ 2 | // launchtype项可配置值为local或remote, local代表前端连本地云函数,remote代表前端连云端云函数 3 | "version": "0.0", 4 | "configurations": [{ 5 | "type": "uniCloud", 6 | "default": { 7 | "launchtype": "remote" 8 | } 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /pages/index/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 24 | -------------------------------------------------------------------------------- /pages/yzcloud/yz-preview.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 26 | -------------------------------------------------------------------------------- /pages/index/fileUpload.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 31 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | // 永中云服务-云预览 2 | // 文档管理接口url 3 | const yzPreviewDmc = 'http://dmc.yozocloud.cn' 4 | const yzPreviewEic = 'http://eic.yozocloud.cn' 5 | const yzPreviewAPPID = 'appId' 6 | const yzPreviewAPPKEY = 'appKey' 7 | /** 8 | * api前缀 9 | */ 10 | const apiYzPreviewDmc = '/apiYzPreviewDmc' 11 | const apiYzPreviewEic = '/apiYzPreviewEic' 12 | /** 13 | * 针对不同平台的baseUrl 14 | */ 15 | const getYzPreviewDmc = () => { 16 | // #ifdef H5 17 | return apiYzPreviewDmc 18 | // #endif 19 | // #ifndef H5 20 | return yzPreviewDmc 21 | // #endif 22 | } 23 | const getYzPreviewEic = () => { 24 | // #ifdef H5 25 | return apiYzPreviewEic 26 | // #endif 27 | // #ifndef H5 28 | return yzPreviewEic 29 | // #endif 30 | } 31 | export default { 32 | /** 33 | * 针对不同平台的baseUrl 34 | */ 35 | yzPreviewDmcUrl: getYzPreviewDmc(), 36 | yzPreviewEic, 37 | yzPreviewEicUrl: getYzPreviewEic(), 38 | yzPreviewAPPID, 39 | yzPreviewAPPKEY 40 | } 41 | -------------------------------------------------------------------------------- /pages.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages 3 | { 4 | "path": "pages/index/index", 5 | "style": { 6 | "navigationBarTitleText": "上传目录文件解决方案" 7 | } 8 | }, 9 | { 10 | "path": "pages/index/androidUpload", 11 | "style": { 12 | "navigationBarTitleText": "Android上传目录文件" 13 | } 14 | }, 15 | { 16 | "path": "pages/index/h5Upload", 17 | "style": { 18 | "navigationBarTitleText": "H5上传目录文件(不分系统)" 19 | } 20 | }, 21 | { 22 | "path": "pages/index/fileUpload", 23 | "style": { 24 | "navigationBarTitleText": "H5文件上传" 25 | } 26 | }, 27 | { 28 | "path": "pages/yzcloud/yz-preview", 29 | "style": { 30 | "navigationBarTitleText": "永中云预览" 31 | } 32 | } 33 | ], 34 | "globalStyle": { 35 | "navigationBarTextStyle": "black", 36 | "navigationBarTitleText": "uni-app", 37 | "navigationBarBackgroundColor": "#F8F8F8", 38 | "backgroundColor": "#F8F8F8" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /hybrid/html/js/signclient.js: -------------------------------------------------------------------------------- 1 | function generateSign(secret, params) { 2 | var fullParamStr = uniqSortParams(params); 3 | return hmacSHA256(fullParamStr, secret); 4 | } 5 | 6 | function uniqSortParams(params) { 7 | delete params.sign; 8 | 9 | var var5 = []; 10 | var var6 = 0; 11 | for (var key in params) { 12 | var5[var6] = key; 13 | var6++; 14 | } 15 | var5.sort(function (a, b) { 16 | return a.localeCompare(b, 'zh-CN'); 17 | }); 18 | 19 | var result = ""; 20 | for (var var7 = 0; var7 < var5.length; var7++) { 21 | var key = var5[var7] 22 | var var8 = params[key]; 23 | var8.sort(function (a, b) { 24 | return a.localeCompare(b, 'zh-CN'); 25 | }); 26 | 27 | if (var8 != null && var8.length > 0) { 28 | for (var var9 = 0; var9 < var8.length; var9++) { 29 | result += key + "=" + var8[var9]; 30 | } 31 | } else { 32 | result += key + "="; 33 | } 34 | } 35 | return result; 36 | } 37 | 38 | function hmacSHA256(data, key) { 39 | data != null ? data : ""; 40 | var var2 = CryptoJS.HmacSHA256(data, key); 41 | var var3 = var2.toString(CryptoJS.enc.Hex); 42 | return var3.toUpperCase(); 43 | } 44 | -------------------------------------------------------------------------------- /pages/index/h5Upload.vue: -------------------------------------------------------------------------------- 1 | 25 | 49 | 50 | 63 | -------------------------------------------------------------------------------- /uni.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * 这里是uni-app内置的常用样式变量 3 | * 4 | * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量 5 | * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App 6 | * 7 | */ 8 | 9 | /** 10 | * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能 11 | * 12 | * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件 13 | */ 14 | 15 | /* 颜色变量 */ 16 | 17 | /* 行为相关颜色 */ 18 | $uni-color-primary: #007aff; 19 | $uni-color-success: #4cd964; 20 | $uni-color-warning: #f0ad4e; 21 | $uni-color-error: #dd524d; 22 | 23 | /* 文字基本颜色 */ 24 | $uni-text-color:#333;//基本色 25 | $uni-text-color-inverse:#fff;//反色 26 | $uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息 27 | $uni-text-color-placeholder: #808080; 28 | $uni-text-color-disable:#c0c0c0; 29 | 30 | /* 背景颜色 */ 31 | $uni-bg-color:#ffffff; 32 | $uni-bg-color-grey:#f8f8f8; 33 | $uni-bg-color-hover:#f1f1f1;//点击状态颜色 34 | $uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色 35 | 36 | /* 边框颜色 */ 37 | $uni-border-color:#c8c7cc; 38 | 39 | /* 尺寸变量 */ 40 | 41 | /* 文字尺寸 */ 42 | $uni-font-size-sm:24upx; 43 | $uni-font-size-base:28upx; 44 | $uni-font-size-lg:32upx; 45 | 46 | /* 图片尺寸 */ 47 | $uni-img-size-sm:40upx; 48 | $uni-img-size-base:52upx; 49 | $uni-img-size-lg:80upx; 50 | 51 | /* Border Radius */ 52 | $uni-border-radius-sm: 4upx; 53 | $uni-border-radius-base: 6upx; 54 | $uni-border-radius-lg: 12upx; 55 | $uni-border-radius-circle: 50%; 56 | 57 | /* 水平间距 */ 58 | $uni-spacing-row-sm: 10px; 59 | $uni-spacing-row-base: 20upx; 60 | $uni-spacing-row-lg: 30upx; 61 | 62 | /* 垂直间距 */ 63 | $uni-spacing-col-sm: 8upx; 64 | $uni-spacing-col-base: 16upx; 65 | $uni-spacing-col-lg: 24upx; 66 | 67 | /* 透明度 */ 68 | $uni-opacity-disabled: 0.3; // 组件禁用态的透明度 69 | 70 | /* 文章场景相关 */ 71 | $uni-color-title: #2C405A; // 文章标题颜色 72 | $uni-font-size-title:40upx; 73 | $uni-color-subtitle: #555555; // 二级标题颜色 74 | $uni-font-size-subtitle:36upx; 75 | $uni-color-paragraph: #3F536E; // 文章段落颜色 76 | $uni-font-size-paragraph:30upx; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # uniapp-file-upload 2 | uni-app系统目录文件上传(非只图片和视频)解决方案 3 | 4 | ## 永中云服务 5 | 6 | 1.0.1新版本已经发布~ 7 | 8 | 新版本基于永中云服务-云预览,完善并实现了跨平台H5文件(非只图片和视频)上传,[永中开发者平台](https://open.yozocloud.cn/)。 9 | 10 | 参考技术文档[《跨平台文档在线预览解决方案(五)-水印、防复制、在线编辑等》](https://juejin.cn/post/6938247508325842958) 11 | 12 | ## 背景 13 | 公司领导提出这样的产品需求:需要上传目录文件,不只是图片和视频,而且同时要支持Android和IOS两大移动端。另外公司App的架构采用的是uni-app。 14 | 15 | ## 思考 16 | * 第一个想到的方案就是,看uni-app框架能否支持。答案可想而知,uni-app组件本身没有提供文件上传组件,不支持`````` 17 | * uni-app App端内置HTML5+引擎,提供plus接口,对于Android系统可以直接调用Android系统函数,打开系统目录。而对于IOS而言,没有找到使用方法 18 | * 既然内置HTML5+引擎,能否直接嵌入H5页面呢?当然可以。于是采用H5方式实现 19 | 20 | ## H5页面文件上传 21 | 嵌入H5页面,需要采用web-view标签,如下: 22 | 23 | 24 | 注意: 25 | * h5页面必须在项目目录:/hybrid/html/下面,因为这样uni-app才不会进行编译 26 | * @message事件是h5页面向应用发送数据的回调 27 | 28 | ## web-view传递数据问题 29 | ### 1、@message 30 | 第一种解决方法:通过@message传递数据,在h5页面中,上传完文件后,获取上传后的文件信息,直接postMessage后,web-view页面会接收 31 | ```js 32 | uni.postMessage({ 33 | data: { 34 | action: data 35 | } 36 | }); 37 | ``` 38 | #### 问题 39 | 当运行代码的时候,并没有执行@message回调,需要点击h5页面返回的时候,才会调用回调函数。于是在执行完postMessage后,调用如下函数返回上一级页面 40 | ```js 41 | uni.navigateBack({ 42 | delta: 1 43 | }); 44 | ``` 45 | 46 | 注意: 47 | * 在h5页面中调用uni-app接口时,需要添加uni SDK 48 | 49 | 50 | * 如果要让web-view的上一级页面,即表单页面接收数据,解决方法是:放到store中,表单页面从store中获取 51 | 52 | ### 2、页面跳转url传递数据 53 | 第二种解决方法:通过页面跳转url传递数据。在h5页面上传完文件后,调用页面跳转函数,将文件数据放到url参数中,如下: 54 | ```js 55 | uni.redirectTo({ 56 | url: './h5Upload?fileData=' + data, 57 | }) 58 | ``` 59 | 60 | ## Demo 61 | github:https://github.com/silianpan/uniapp-file-upload 62 | 63 | * 两种方案 64 | 65 | 66 | 67 | * 表单页 68 | 69 | 70 | 71 | * 选择系统目录文件 72 | 73 | 74 | 75 | * 页面跳转url传递数据 76 | 77 | 78 | 79 | ## 附:Android选择系统目录 80 | 81 | 82 | 转载请注明:[溜爸 » uni-app系统目录文件上传(非只图片和视频)解决方案](http://silianpan.cn/index.php/2019/09/22/uniapp_file_upload/) 83 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "uniapp-file-upload", 3 | "appid" : "", 4 | "description" : "", 5 | "versionName" : "1.0.1", 6 | "versionCode" : 101, 7 | "transformPx" : false, 8 | /* 5+App特有相关 */ 9 | "app-plus" : { 10 | "usingComponents" : true, 11 | "splashscreen" : { 12 | "alwaysShowBeforeRender" : true, 13 | "waiting" : true, 14 | "autoclose" : true, 15 | "delay" : 0 16 | }, 17 | /* 模块配置 */ 18 | "modules" : {}, 19 | /* 应用发布信息 */ 20 | "distribute" : { 21 | /* android打包配置 */ 22 | "android" : { 23 | "permissions" : [ 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 | /* ios打包配置 */ 49 | "ios" : {}, 50 | /* SDK配置 */ 51 | "sdkConfigs" : {} 52 | } 53 | }, 54 | /* 快应用特有相关 */ 55 | "quickapp" : {}, 56 | /* 小程序特有相关 */ 57 | "mp-weixin" : { 58 | "appid" : "", 59 | "setting" : { 60 | "urlCheck" : false 61 | }, 62 | "usingComponents" : true 63 | }, 64 | "mp-alipay" : { 65 | "usingComponents" : true 66 | }, 67 | "mp-baidu" : { 68 | "usingComponents" : true 69 | }, 70 | "mp-toutiao" : { 71 | "usingComponents" : true 72 | }, 73 | "h5" : { 74 | "devServer" : { 75 | "port" : 9091, 76 | "disableHostCheck" : true, 77 | "proxy" : { 78 | "/apiYzPreviewDmc" : { 79 | "target" : "http://dmc.yozocloud.cn", 80 | "changeOrigin" : true, 81 | "secure" : false, 82 | "pathRewrite" : { 83 | "^/apiYzPreviewDmc" : "" 84 | } 85 | }, 86 | "/apiYzPreviewEic" : { 87 | "target" : "http://eic.yozocloud.cn", 88 | "changeOrigin" : true, 89 | "secure" : false, 90 | "pathRewrite" : { 91 | "^/apiYzPreviewEic" : "" 92 | } 93 | } 94 | } 95 | }, 96 | "title" : "uniapp-file-upload", 97 | "router" : { 98 | "mode" : "hash", 99 | "base" : "/" 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /hybrid/html/js/h5uploader.js: -------------------------------------------------------------------------------- 1 | /**! 2 | * h5uploader.js 3 | * This is a simple file upload plugin depends on HTML5. 4 | * You can use it to mordern browser. 5 | * 6 | * version: 1.0 7 | * Copyright 2015, Ziv | http://imziv.com 8 | * Released under the MIT License 9 | * https://github.com/wewoor/h5uploader 10 | **/ 11 | 12 | (function(window) { 13 | 14 | window.H5Uploader = (function() { 15 | 16 | function createXhr() { 17 | if (typeof XMLHttpRequest != "undefined") { 18 | return new XMLHttpRequest(); 19 | } else if (typeof ActiveXObject != "undefined") { 20 | if (typeof arguments.callee.activeXString != "string") { 21 | var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0", "MSXML2.XMLHttp"]; 22 | for (var i = 0, len = versions.length; i < len; i++) { 23 | try { 24 | new ActiveXObject(versions[i]); 25 | arguments.callee.activeXString = versions[i]; 26 | break; 27 | } catch (e) { 28 | throw new Error('Create XHR ActiveXObject error.' + e); 29 | } 30 | } 31 | } 32 | return new ActiveXObject(arguments.callee.activeXString); 33 | } else throw new Error('No XHR object avaliable.' + e); 34 | } 35 | 36 | return { 37 | upload: function(literals) { 38 | if (Object.prototype.toString.call(literals) !== '[object Array]') { 39 | this.handUpload(literals); 40 | } else { 41 | for (var i = 0; i < literals.length; i++) { 42 | this.handUpload(literals[i]); 43 | } 44 | } 45 | }, 46 | 47 | handUpload: function(literals) { 48 | 49 | if (literals.action === undefined) { 50 | throw new Error('The upload action address option is undefined.'); 51 | } 52 | 53 | var xhr = createXhr(); 54 | xhr.open("POST", literals.action, true); 55 | // xhr.setRequestHeader("Content-Type", "multipart/form-data"); 56 | // xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); 57 | xhr.onreadystatechange = function() { 58 | if (xhr.readyState == 4) { 59 | var body = xhr.responseText; 60 | if (xhr.status >= 200 && xhr.status < 300 || 61 | xhr.status == 304) { 62 | if (literals.success) { 63 | literals.success(body); 64 | } 65 | } else { 66 | if (literals.fail) { 67 | literals.fail(body); 68 | } 69 | } 70 | } 71 | }; 72 | 73 | if (!literals.id) { 74 | throw new Error('The upload id option is undefined.'); 75 | } 76 | // var myForm = document.getElementById(literals.formId); 77 | var data = new FormData(); 78 | var file = document.getElementById(literals.id); 79 | if (!file) { 80 | throw new Error('The upload file element is undefined::id:' + literals.id); 81 | } 82 | var name = file.getAttribute('name'); 83 | if (!name) { 84 | throw new Error('The upload file input name is undefined.'); 85 | } 86 | 87 | if (literals.size) { // Check file Size 88 | var evt = this.checkSize(file.files, literals.size.max); 89 | if (evt) { 90 | if (literals.size.valide) literals.size.valide(evt); 91 | throw new Error('The upload file size exceed max value.'); 92 | } 93 | } 94 | 95 | if (literals.type) { // Check file type 96 | var evt1 = this.checkType(file.files, literals.type.name); 97 | if (evt1) { 98 | if (literals.type.valide) literals.type.valide(evt1); 99 | throw new Error('The upload file type is error.'); 100 | } 101 | } 102 | 103 | if (literals.progress) { // Progress 104 | literals.progress(); 105 | } 106 | 107 | for (var i = 0; i < file.files.length; i++) { 108 | data.append(name, file.files[i]); 109 | } 110 | 111 | try { 112 | xhr.send(data); 113 | } catch (e) { 114 | throw new Error(e); 115 | } 116 | }, 117 | 118 | // Validate file size 119 | checkSize: function(file, size) { 120 | for (var i = 0; i < file.length; i++) { 121 | if (file[i].size > size * 1024) { // bytes 122 | return file[i]; 123 | } 124 | } 125 | }, 126 | 127 | // Validate file type 128 | checkType: function(file, type) { 129 | for (var i = 0; i < file.length; i++) { 130 | var arr = file[i].name.split("."); 131 | if (type.indexOf(arr[arr.length - 1]) === -1) { 132 | return file[i]; 133 | } 134 | } 135 | } 136 | }; 137 | })(); 138 | 139 | })(window); 140 | -------------------------------------------------------------------------------- /hybrid/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | H5文档上传 7 | 8 | 9 | 10 |

H5文档上传

11 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 23 | 24 | 25 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /pages/index/androidUpload.vue: -------------------------------------------------------------------------------- 1 | 2 | 13 | 206 | 207 | 212 | --------------------------------------------------------------------------------