diff --git a/docs/api/media/image.md b/docs/api/media/image.md index 55492be0ffc1a22be92f19dca9f64648d5716def..217e5965ef32c26e3d88ef740d5cedfbbfe521eb 100644 --- a/docs/api/media/image.md +++ b/docs/api/media/image.md @@ -29,7 +29,7 @@ App端如需要更丰富的相机拍照API(如直接调用前置摄像头) |参数|类型|说明| |:-|:-|:-| |tempFilePaths|Array<String>|图片的本地文件路径列表| -|tempFiles|Array<Object>|图片的本地文件列表,每一项是一个 File 对象| +|tempFiles|Array<Object>、Array<File>|图片的本地文件列表,每一项是一个 File 对象| **File 对象结构如下** @@ -38,6 +38,7 @@ App端如需要更丰富的相机拍照API(如直接调用前置摄像头) |path|String|本地文件路径| |size|Number|本地文件大小,单位:B| |name|String|包含扩展名的文件名称,仅H5支持| +|type|String|文件类型,仅H5支持| **示例** diff --git a/docs/api/media/video.md b/docs/api/media/video.md index 990c7fad980eaafc07e42a022aca8580bf6c1fb2..8b50c95f0ec6f57a3cc6c0124eda0595a512b1fb 100644 --- a/docs/api/media/video.md +++ b/docs/api/media/video.md @@ -21,14 +21,15 @@ **success 返回参数说明** -|参数|说明|平台差异说明说明| -|:-|:-|:-| -|tempFilePath|选定视频的临时文件路径|| -|duration|选定视频的时间长度,单位为 s|APP平台 2.1.0+、微信小程序| -|size|选定视频的数据量大小|APP平台 2.1.0+、微信小程序| -|height|返回选定视频的高|APP平台 2.1.0+、微信小程序| -|width|返回选定视频的宽|APP平台 2.1.0+、微信小程序| -|name|包含扩展名的文件名称|仅H5支持| +|参数|类型|说明|平台差异说明说明| +|:-|:-|:-|:-| +|tempFilePath|String|选定视频的临时文件路径|| +|tempFile|File|选定的视频文件|仅H5(2.7.0+)支持| +|duration|Number|选定视频的时间长度,单位为 s|APP 2.1.0+、H5、微信小程序| +|size|Number|选定视频的数据量大小|APP 2.1.0+、H5、微信小程序| +|height|Number|返回选定视频的高|APP 2.1.0+、H5、微信小程序| +|width|Number|返回选定视频的宽|APP 2.1.0+、H5、微信小程序| +|name|String|包含扩展名的文件名称|仅H5支持| **注意:** * 文件的临时路径,在应用本次启动期间可以正常使用,如需持久保存,需在主动调用 [uni.saveFile](api/file/file?id=savefile),在应用下次启动时才能访问得到。 diff --git a/docs/api/request/network-file.md b/docs/api/request/network-file.md index d272f87607ef7b052d6c290d3e3e028a9c83c926..aaf98cb34ac3df2268918c238cb90fabe2b9d5f9 100644 --- a/docs/api/request/network-file.md +++ b/docs/api/request/network-file.md @@ -9,8 +9,9 @@ |参数名|类型|必填|说明|平台差异说明| |:-|:-|:-|:-|:-| |url|String|是|开发者服务器 url|| -|files|Array|否|需要上传的文件列表。**使用 files 时,filePath 和 name 不生效。**|App| +|files|Array|否|需要上传的文件列表。**使用 files 时,filePath 和 name 不生效。**|App、H5( 2.7.0+)| |fileType|String|见平台差异说明|文件类型,image/video/audio|仅支付宝小程序,且必填。| +|file|File|否|要上传的文件对象。|仅H5(2.7.0+)支持| |filePath|String|是|要上传文件资源的路径。|| |name|String|是|文件对应的 key , 开发者在服务器端通过这个 key 可以获取到文件二进制内容|| |header|Object|否|HTTP 请求 Header, header 中不能设置 Referer。|| @@ -33,6 +34,7 @@ files 参数是一个 file 对象的数组,file 对象的结构如下: |参数名|类型|必填|说明| |:-|:-|:-|:-| |name|String|否|multipart 提交时,表单的项目名,默认为 file| +|file|File|否|要上传的文件对象,仅H5(2.7.0+)支持| |uri|String|是|文件的本地地址| Tip: diff --git a/docs/api/request/request.md b/docs/api/request/request.md index c5855b95c7a2c28524f0ad8b3544509cfe1409d3..c667dbac0aed78cf2f2b62273376ca6602769d74 100644 --- a/docs/api/request/request.md +++ b/docs/api/request/request.md @@ -15,6 +15,7 @@ |dataType|String|否|json |如果设为 json,会尝试对返回的数据做一次 JSON.parse|| |responseType|String|否|text |设置响应的数据类型。合法值:text、arraybuffer|App和支付宝小程序不支持| |sslVerify|Boolean|否|true|验证 ssl 证书|仅App安卓端支持(HBuilderX 2.3.3+)| +|withCredentials|Boolean|否|false|跨域请求时是否携带凭证(cookies)|仅H5支持(HBuilderX 2.7.0+)| |success|Function|否||收到开发者服务成功返回的回调函数|| |fail|Function|否||接口调用失败的回调函数|| |complete|Function|否||接口调用结束的回调函数(调用成功、失败都会执行)| | diff --git a/packages/uni-mp-toutiao/dist/index.js b/packages/uni-mp-toutiao/dist/index.js index 666b02e8ac47c896b7d440333803ac24af2a3df7..8cc806ac4f6d0669d3b65135886a91f9634960bb 100644 --- a/packages/uni-mp-toutiao/dist/index.js +++ b/packages/uni-mp-toutiao/dist/index.js @@ -231,13 +231,13 @@ const promiseInterceptor = { }; const SYNC_API_RE = - /^\$|restoreGlobal|getCurrentSubNVue|getMenuButtonBoundingClientRect|^report|interceptors|Interceptor$|getSubNVueById|requireNativePlugin|upx2px|hideKeyboard|canIUse|^create|Sync$|Manager$|base64ToArrayBuffer|arrayBufferToBase64/; + /^\$|sendNativeEvent|restoreGlobal|getCurrentSubNVue|getMenuButtonBoundingClientRect|^report|interceptors|Interceptor$|getSubNVueById|requireNativePlugin|upx2px|hideKeyboard|canIUse|^create|Sync$|Manager$|base64ToArrayBuffer|arrayBufferToBase64/; const CONTEXT_API_RE = /^create|Manager$/; const ASYNC_API = ['createBLEConnection']; -const CALLBACK_API_RE = /^on/; +const CALLBACK_API_RE = /^on|^off/; function isContextApi (name) { return CONTEXT_API_RE.test(name) @@ -1699,6 +1699,17 @@ function parsePage (vuePageOptions) { } else { this.is && console.warn(this.is + ' is not ready'); } + }; + + pageOptions.lifetimes.detached = function detached () { + this.$vm && this.$vm.$destroy(); + // 清理 + const webviewId = this.__webviewId__; + webviewId && Object.keys(instances).forEach(key => { + if (key.indexOf(webviewId + '_') === 0) { + delete instances[key]; + } + }); }; return pageOptions diff --git a/packages/vue-cli-plugin-uni/lib/format-log.js b/packages/vue-cli-plugin-uni/lib/format-log.js index f7e9dcffc6a263577d19e80dcc5f445652feb3cf..cc3c89bac6553f8eea96faba1d19064ce415e722 100644 --- a/packages/vue-cli-plugin-uni/lib/format-log.js +++ b/packages/vue-cli-plugin-uni/lib/format-log.js @@ -1,71 +1,71 @@ -function typof (v) { - var s = Object.prototype.toString.call(v) - return s.substring(8, s.length - 1) -} - -function isDebugMode () { - /* eslint-disable no-undef */ - return typeof __channelId__ === 'string' && __channelId__ -} - -export function log (type) { - for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { - args[_key - 1] = arguments[_key] - } - console[type].apply(console, args) -} - -export default function formatLog () { - for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { - args[_key] = arguments[_key] - } - var type = args.shift() - if (isDebugMode()) { - args.push(args.pop().replace('at ', 'uni-app:///')) - return console[type]['apply'](console, args) - } - - var msgs = args.map(function (v) { - var type = Object.prototype.toString.call(v).toLowerCase() - - if (type === '[object object]' || type === '[object array]') { - try { - v = '---BEGIN:JSON---' + JSON.stringify(v) + '---END:JSON---' - } catch (e) { - v = '[object object]' - } - } else { - if (v === null) { - v = '---NULL---' - } else if (v === undefined) { - v = '---UNDEFINED---' - } else { - var vType = typof(v).toUpperCase() - - if (vType === 'NUMBER' || vType === 'BOOLEAN') { - v = '---BEGIN:' + vType + '---' + v + '---END:' + vType + '---' - } else { - v = String(v) - } - } - } - - return v - }) - var msg = '' - - if (msgs.length > 1) { - var lastMsg = msgs.pop() - msg = msgs.join('---COMMA---') - - if (lastMsg.indexOf(' at ') === 0) { - msg += lastMsg - } else { - msg += '---COMMA---' + lastMsg - } - } else { - msg = msgs[0] - } - - console[type](msg) +function typof (v) { + var s = Object.prototype.toString.call(v) + return s.substring(8, s.length - 1) +} + +function isDebugMode () { + /* eslint-disable no-undef */ + return typeof __channelId__ === 'string' && __channelId__ +} + +export function log (type) { + for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + args[_key - 1] = arguments[_key] + } + console[type].apply(console, args) +} + +export default function formatLog () { + for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key] + } + var type = args.shift() + if (isDebugMode()) { + args.push(args.pop().replace('at ', 'uni-app:///')) + return console[type]['apply'](console, args) + } + + var msgs = args.map(function (v) { + var type = Object.prototype.toString.call(v).toLowerCase() + + if (type === '[object object]' || type === '[object array]') { + try { + v = '---BEGIN:JSON---' + JSON.stringify(v) + '---END:JSON---' + } catch (e) { + v = '[object object]' + } + } else { + if (v === null) { + v = '---NULL---' + } else if (v === undefined) { + v = '---UNDEFINED---' + } else { + var vType = typof(v).toUpperCase() + + if (vType === 'NUMBER' || vType === 'BOOLEAN') { + v = '---BEGIN:' + vType + '---' + v + '---END:' + vType + '---' + } else { + v = String(v) + } + } + } + + return v + }) + var msg = '' + + if (msgs.length > 1) { + var lastMsg = msgs.pop() + msg = msgs.join('---COMMA---') + + if (lastMsg.indexOf(' at ') === 0) { + msg += lastMsg + } else { + msg += '---COMMA---' + lastMsg + } + } else { + msg = msgs[0] + } + + console[type](msg) } diff --git a/packages/vue-cli-plugin-uni/lib/options.js b/packages/vue-cli-plugin-uni/lib/options.js index ca65e53249e0e73c2ff12cd3d0d2d362973cccff..05d0770ee592850939e7f7344aca538d68a23106 100644 --- a/packages/vue-cli-plugin-uni/lib/options.js +++ b/packages/vue-cli-plugin-uni/lib/options.js @@ -75,7 +75,10 @@ module.exports = function initOptions (options) { options.css.loaderOptions.sass.sassOptions = {} } // 指定 outputStyle, 否则 production 模式下会被默认成 compressed - options.css.loaderOptions.sass.sassOptions.outputStyle = 'nested' + const outputStyle = options.css.loaderOptions.sass.sassOptions.outputStyle + if (!outputStyle || outputStyle === 'compressed') { + options.css.loaderOptions.sass.sassOptions.outputStyle = 'expanded' + } if (sassLoaderVersion < 8) { options.css.loaderOptions.sass.data = sassData diff --git a/packages/webpack-uni-pages-loader/lib/platforms/app-plus/app-config-service.js b/packages/webpack-uni-pages-loader/lib/platforms/app-plus/app-config-service.js index d74092e4a1a932a7ec5abb465bc0456cfc81f019..32ff1c3e932fe5b29b77f678a944f55ec2735251 100644 --- a/packages/webpack-uni-pages-loader/lib/platforms/app-plus/app-config-service.js +++ b/packages/webpack-uni-pages-loader/lib/platforms/app-plus/app-config-service.js @@ -26,6 +26,31 @@ function parseRoutes (config) { return __uniRoutes } + +const GLOBALS = [ + 'global', + 'window', + 'document', + 'frames', + 'self', + 'location', + 'navigator', + 'localStorage', + 'history', + 'Caches', + 'screen', + 'alert', + 'confirm', + 'prompt', + 'fetch', + 'XMLHttpRequest', + 'WebSocket', + 'webkit', + 'print' +] + +const globalStatement = GLOBALS.map(g => `${g}:void 0`).join(',') + module.exports = function definePages (appJson) { const __uniRoutes = parseRoutes(appJson) @@ -42,7 +67,7 @@ var isReady=false;var onReadyCallbacks=[]; var __uniConfig = ${JSON.stringify(appJson, null)}; var __uniRoutes = ${JSON.stringify(__uniRoutes)}; __uniConfig.onReady=function(callback){if(__uniConfig.ready){callback()}else{onReadyCallbacks.push(callback)}};Object.defineProperty(__uniConfig,"ready",{get:function(){return isReady},set:function(val){isReady=val;if(!isReady){return}const callbacks=onReadyCallbacks.slice(0);onReadyCallbacks.length=0;callbacks.forEach(function(callback){callback()})}}); -service.register("uni-app-config",{create(a,b,c){if(!__uniConfig.viewport){var d=b.weex.config.env.scale,e=b.weex.config.env.deviceWidth,f=Math.ceil(e/d);Object.assign(__uniConfig,{viewport:f,defaultFontSize:Math.round(f/20)})}return{instance:{__uniConfig:__uniConfig,__uniRoutes:__uniRoutes,window:void 0,global:void 0}}}}); +service.register("uni-app-config",{create(a,b,c){if(!__uniConfig.viewport){var d=b.weex.config.env.scale,e=b.weex.config.env.deviceWidth,f=Math.ceil(e/d);Object.assign(__uniConfig,{viewport:f,defaultFontSize:Math.round(f/20)})}return{instance:{__uniConfig:__uniConfig,__uniRoutes:__uniRoutes,${globalStatement}}}}}); ` } } diff --git a/packages/webpack-uni-pages-loader/lib/platforms/h5.js b/packages/webpack-uni-pages-loader/lib/platforms/h5.js index 3c78cf388536c288afb5dca186d79d216bb62dd8..d2b606faa74972c68a2e87a92fbe2d94d89a1160 100644 --- a/packages/webpack-uni-pages-loader/lib/platforms/h5.js +++ b/packages/webpack-uni-pages-loader/lib/platforms/h5.js @@ -355,7 +355,8 @@ import Vue from 'vue' global['____${h5.appid}____'] = true; delete global['____${h5.appid}____']; global.__uniConfig = ${JSON.stringify(pagesJson)}; -global.__uniConfig.router = ${JSON.stringify(h5.router)}; +global.__uniConfig.router = ${JSON.stringify(h5.router)}; +global.__uniConfig.publicPath = ${JSON.stringify(h5.publicPath)}; global.__uniConfig['async'] = ${JSON.stringify(h5['async'])}; global.__uniConfig.debug = ${manifestJson.debug === true}; global.__uniConfig.networkTimeout = ${JSON.stringify(networkTimeoutConfig)}; diff --git a/src/core/helpers/protocol/network/request.js b/src/core/helpers/protocol/network/request.js index 76286be30762d3e2de11e8a5016a3c77e8e0c370..b119a255000f51045ddd5d85de1b024e0f570256 100644 --- a/src/core/helpers/protocol/network/request.js +++ b/src/core/helpers/protocol/network/request.js @@ -100,5 +100,8 @@ export const request = { value = (value || '').toLowerCase() params.responseType = Object.values(responseType).indexOf(value) < 0 ? responseType.TEXT : value } + }, + withCredentials: { + type: Boolean } -} +} diff --git a/src/core/helpers/protocol/network/upload-file.js b/src/core/helpers/protocol/network/upload-file.js index 47cdd37659ffd0869082c2850837aa94e9471449..bcf582eb7d3c47917f5cd9023ad0b0b10ebc2e36 100644 --- a/src/core/helpers/protocol/network/upload-file.js +++ b/src/core/helpers/protocol/network/upload-file.js @@ -9,6 +9,9 @@ export const uploadFile = { files: { type: Array }, + file: { + type: File + }, filePath: { type: String, validator (value, params) { diff --git a/src/core/service/api/network/socket.js b/src/core/service/api/network/socket.js index f9e12508220627138a7c1e1ae0638d68bfe5a620..0cc9bc2c8a4fbe56218f7ede914b94fc3de03d92 100644 --- a/src/core/service/api/network/socket.js +++ b/src/core/service/api/network/socket.js @@ -60,7 +60,7 @@ class SocketTask { success, fail, complete - }, errMsg) { + } = {}, errMsg) { var data = { errMsg } diff --git a/src/core/service/bridge/subscribe.js b/src/core/service/bridge/subscribe.js index 1f9e474f7a663cf1327b8edb05fc4a25a48fa385..c5e6dee2dd0a9d68f2381b2d1e442f1b83b0d108 100644 --- a/src/core/service/bridge/subscribe.js +++ b/src/core/service/bridge/subscribe.js @@ -9,13 +9,13 @@ export default function initSubscribe (subscribe, { getCurrentPages }) { function createPageEvent (eventType) { - return function (args, pageId) { + return function (args, pageId) { pageId = parseInt(pageId) const pages = getCurrentPages() const page = pages.find(page => page.$page.id === pageId) if (page) { callPageHook(page, eventType, args) - } else { + } else if (process.env.NODE_ENV !== 'production') { console.error(`Not Found:Page[${pageId}]`) } } @@ -48,7 +48,7 @@ export default function initSubscribe (subscribe, { } callback(res) } - } + } if (__PLATFORM__ === 'h5') { subscribe('onPageReady', createPageEvent('onReady')) @@ -59,4 +59,4 @@ export default function initSubscribe (subscribe, { subscribe('onRequestComponentInfo', onRequestComponentInfo) subscribe('onRequestComponentObserver', onRequestComponentObserver) -} +} diff --git a/src/platforms/app-plus/service/api/context/audio.js b/src/platforms/app-plus/service/api/context/audio.js index 397c258a1c23c67a4d50c94843e8fd91c6a60add..1f7ccbd3d54316708b40132688be965be40932a2 100644 --- a/src/platforms/app-plus/service/api/context/audio.js +++ b/src/platforms/app-plus/service/api/context/audio.js @@ -48,7 +48,7 @@ const initStateChage = audioId => { export function createAudioInstance () { const audioId = `${Date.now()}${Math.random()}` - const audio = audios[audioId] = plus.audio.createPlayer('') + const audio = audios[audioId] = plus.audio.createPlayer('') audio.src = '' audio.volume = 1 audio.startTime = 0 @@ -122,7 +122,7 @@ export function getAudioState ({ errMsg: 'getAudioState:ok', duration: 1e3 * (audio.getDuration() || 0), currentTime: audio.isStopped ? 0 : 1e3 * audio.getPosition(), - paused: audio.isPaused, + paused: audio.isPaused(), src, volume, startTime: 1e3 * startTime, @@ -138,7 +138,7 @@ export function operateAudio ({ const audio = audios[audioId] const operationTypes = ['play', 'pause', 'stop'] if (operationTypes.indexOf(operationType) >= 0) { - audio[operationType === operationTypes[0] && audio.isPaused ? 'resume' : operationType]() + audio[operationType === operationTypes[0] && audio.isPaused() ? 'resume' : operationType]() } else if (operationType === 'seek') { audio.seekTo(currentTime / 1e3) } diff --git a/src/platforms/app-plus/service/api/context/background-audio.js b/src/platforms/app-plus/service/api/context/background-audio.js index fc43c7e3eb956f01016f14e0689c30ef5b583c68..4e9364aceb7b11be75323e70d24722dd266361d2 100644 --- a/src/platforms/app-plus/service/api/context/background-audio.js +++ b/src/platforms/app-plus/service/api/context/background-audio.js @@ -6,9 +6,9 @@ import { publish } from '../../bridge' -let audio - -let timeUpdateTimer = null +let audio + +let timeUpdateTimer = null const TIME_UPDATE = 250 const publishBackgroundAudioStateChange = (state, res = {}) => publish('onBackgroundAudioStateChange', Object.assign({ @@ -31,15 +31,15 @@ function initMusic () { audio.addEventListener(event, () => { // 添加 isStopped 属性是为了解决 安卓设备停止播放后获取播放进度不正确的问题 if (event === 'play') { - audio.isStopped = false + audio.isStopped = false startTimeUpdateTimer() } else if (event === 'stop') { audio.isStopped = true - } - - if (event === 'pause' || event === 'ended' || event === 'stop') { - stopTimeUpdateTimer() - } + } + + if (event === 'pause' || event === 'ended' || event === 'stop') { + stopTimeUpdateTimer() + } const eventName = `onMusic${event[0].toUpperCase() + event.substr(1)}` publish(eventName, { @@ -51,13 +51,13 @@ function initMusic () { }) }) }) - audio.addEventListener('waiting', () => { + audio.addEventListener('waiting', () => { stopTimeUpdateTimer() publishBackgroundAudioStateChange('waiting', { dataUrl: audio.src }) }) - audio.addEventListener('error', err => { + audio.addEventListener('error', err => { stopTimeUpdateTimer() publish('onMusicError', { dataUrl: audio.src, @@ -71,20 +71,20 @@ function initMusic () { }) audio.addEventListener('prev', () => publish('onBackgroundAudioPrev')) audio.addEventListener('next', () => publish('onBackgroundAudioNext')) -} - -function startTimeUpdateTimer () { - stopTimeUpdateTimer() - timeUpdateTimer = setInterval(() => { - publishBackgroundAudioStateChange('timeUpdate', {}) - }, TIME_UPDATE) -} +} -function stopTimeUpdateTimer () { - if (timeUpdateTimer !== null) { - clearInterval(timeUpdateTimer) - } -} +function startTimeUpdateTimer () { + stopTimeUpdateTimer() + timeUpdateTimer = setInterval(() => { + publishBackgroundAudioStateChange('timeUpdate', {}) + }, TIME_UPDATE) +} + +function stopTimeUpdateTimer () { + if (timeUpdateTimer !== null) { + clearInterval(timeUpdateTimer) + } +} function setMusicState (args) { initMusic() @@ -113,7 +113,7 @@ export function getMusicPlayerState () { dataUrl: audio.src, duration: audio.getDuration() || 0, currentPosition: audio.getPosition(), - status: audio.isPaused ? 0 : 1, + status: audio.isPaused() ? 0 : 1, downloadPercent: Math.round(100 * audio.getBuffered() / audio.getDuration()), errMsg: `getMusicPlayerState:ok` } @@ -189,7 +189,7 @@ export function getBackgroundAudioState () { let newData = { duration: audio.getDuration() || 0, currentTime: audio.isStopped ? 0 : audio.getPosition(), - paused: audio.isPaused, + paused: audio.isPaused(), src: audio.src, buffered: audio.getBuffered(), title: audio.title, diff --git a/src/platforms/app-plus/service/framework/plugins/diff.js b/src/platforms/app-plus/service/framework/plugins/diff.js index 25cf9bde887602a43b6386fc79cc756e731877b3..c2e7494cc4e2ec5ca6ec5be3c5b5e01b666e538e 100644 --- a/src/platforms/app-plus/service/framework/plugins/diff.js +++ b/src/platforms/app-plus/service/framework/plugins/diff.js @@ -52,8 +52,7 @@ function diffElmData (newObj, oldObj) { cur = newObj[key] old = oldObj[key] if (old !== cur) { - // 全量同步 style (因为 style 可能会动态删除部分样式) - if (key === B_STYLE && isPlainObject(cur) && isPlainObject(old)) { + if (key === B_STYLE && isPlainObject(cur) && isPlainObject(old)) { // 全量同步 style (因为 style 可能会动态删除部分样式) if (Object.keys(cur).length !== Object.keys(old).length) { // 长度不等 setResult(result || (result = Object.create(null)), B_STYLE, cur) } else { @@ -64,6 +63,14 @@ function diffElmData (newObj, oldObj) { const vFor = diffArray(cur, old) vFor && setResult(result || (result = Object.create(null)), V_FOR, vFor) } else { + if (key.indexOf('change:') === 0) { // wxs change:prop + try { + // 先简单的用 stringify 判断 + if (JSON.stringify(cur) === JSON.stringify(old)) { + continue + } + } catch (e) {} + } setResult(result || (result = Object.create(null)), key, cur) } } diff --git a/src/platforms/h5/helpers/file.js b/src/platforms/h5/helpers/file.js index 3a9a82a6b39611c8b6c3072c4e6c44d6445c23da..c6337cdd552ae047d9d7037442bff986375b2985 100644 --- a/src/platforms/h5/helpers/file.js +++ b/src/platforms/h5/helpers/file.js @@ -61,3 +61,8 @@ export function fileToUrl (file) { files[url] = file return url } + +export function revokeObjectURL (url) { + (window.URL || window.webkitURL).revokeObjectURL(url) + delete files[url] +} diff --git a/src/platforms/h5/service/api/media/choose-image.js b/src/platforms/h5/service/api/media/choose-image.js index e3c09b89c46d1fb497405711406438918eaa054d..927b360ecf40034bc6d963c930874375e84e5b11 100644 --- a/src/platforms/h5/service/api/media/choose-image.js +++ b/src/platforms/h5/service/api/media/choose-image.js @@ -50,26 +50,27 @@ export function chooseImage ({ document.body.appendChild(imageInput) imageInput.addEventListener('change', function (event) { - const tempFilePaths = [] const tempFiles = [] const fileCount = event.target.files.length for (let i = 0; i < fileCount; i++) { const file = event.target.files[i] - const filePath = fileToUrl(file) - - tempFilePaths.push(filePath) - tempFiles.push({ - path: filePath, - size: file.size, - name: file.name + let filePath + Object.defineProperty(file, 'filePath', { + get () { + filePath = filePath || fileToUrl(file) + return filePath + } }) + tempFiles.push(file) } - - invoke(callbackId, { + const res = { errMsg: 'chooseImage:ok', - tempFilePaths: tempFilePaths, + get tempFilePaths () { + return tempFiles.map(({ filePath }) => filePath) + }, tempFiles: tempFiles - }) + } + invoke(callbackId, res) // TODO 用户取消选择时,触发 fail,目前尚未找到合适的方法。 }) diff --git a/src/platforms/h5/service/api/media/choose-video.js b/src/platforms/h5/service/api/media/choose-video.js index 65258be5964f2f03c0edef239b55b86a72ab8ccd..7dc0ccdaee4a90e297100bc0bb7b0db043851eb4 100644 --- a/src/platforms/h5/service/api/media/choose-video.js +++ b/src/platforms/h5/service/api/media/choose-video.js @@ -1,4 +1,4 @@ -import { fileToUrl } from 'uni-platform/helpers/file' +import { fileToUrl, revokeObjectURL } from 'uni-platform/helpers/file' import { updateElementStyle } from 'uni-shared' const { @@ -42,23 +42,30 @@ export function chooseVideo ({ videoInput.addEventListener('change', function (event) { const file = event.target.files[0] - const filePath = fileToUrl(file) - - let callbackResult = { + const callbackResult = { errMsg: 'chooseVideo:ok', - tempFilePath: filePath, + tempFile: file, size: file.size, duration: 0, width: 0, height: 0, name: file.name } + let filePath + Object.defineProperty(callbackResult, 'tempFilePath', { + get () { + filePath = filePath || fileToUrl(this.tempFile) + return filePath + } + }) const video = document.createElement('video') if (video.onloadedmetadata !== undefined) { + const filePath = fileToUrl(file) // 尝试获取视频的宽高信息 video.onloadedmetadata = function () { - invoke(callbackId, Object.assign({}, callbackResult, { + revokeObjectURL(filePath) + invoke(callbackId, Object.assign(callbackResult, { duration: video.duration || 0, width: video.videoWidth || 0, height: video.videoHeight || 0 @@ -66,11 +73,9 @@ export function chooseVideo ({ } // 部分浏览器(如微信内置浏览器)未播放无法触发loadedmetadata事件 setTimeout(() => { - invoke(callbackId, Object.assign({}, callbackResult, { - duration: 0, - width: 0, - height: 0 - })) + video.onloadedmetadata = null + revokeObjectURL(filePath) + invoke(callbackId, callbackResult) }, 300) video.src = filePath } else { diff --git a/src/platforms/h5/service/api/network/request.js b/src/platforms/h5/service/api/network/request.js index 4eee528465f7a5f4bd34ca33cb7852dbfb0441a8..f147d7e1a3672ca0684de4c19f75784e9500364b 100644 --- a/src/platforms/h5/service/api/network/request.js +++ b/src/platforms/h5/service/api/network/request.js @@ -45,7 +45,8 @@ export function request ({ header, method, dataType, - responseType + responseType, + withCredentials }, callbackId) { const { invokeCallbackHandler: invoke @@ -143,6 +144,7 @@ export function request ({ errMsg: 'request:fail' }) } + xhr.withCredentials = withCredentials xhr.send(body) return requestTask } diff --git a/src/platforms/h5/service/api/network/upload-file.js b/src/platforms/h5/service/api/network/upload-file.js index 1e9db34e52ccc5a5b91b1ed99798ca103aaad6ba..15b97051a63978bfa30419487d2414733b0874ae 100644 --- a/src/platforms/h5/service/api/network/upload-file.js +++ b/src/platforms/h5/service/api/network/upload-file.js @@ -45,8 +45,10 @@ class UploadTask { */ export function uploadFile ({ url, + file, filePath, name, + files, header, formData }, callbackId) { @@ -55,15 +57,24 @@ export function uploadFile ({ invokeCallbackHandler: invoke } = UniServiceJSBridge var uploadTask = new UploadTask(null, callbackId) - - function upload (file) { + if (!Array.isArray(files) || !files.length) { + files = [{ + name, + file, + uri: filePath + }] + } + function upload (realFiles) { var xhr = new XMLHttpRequest() var form = new FormData() var timer Object.keys(formData).forEach(key => { form.append(key, formData[key]) }) - form.append(name, file, file.name || `file-${Date.now()}`) + Object.values(files).forEach(({ name }, index) => { + const file = realFiles[index] + form.append(name || 'file', file, file.name || `file-${Date.now()}`) + }) xhr.open('POST', url) Object.keys(header).forEach(key => { xhr.setRequestHeader(key, header[key]) @@ -118,13 +129,16 @@ export function uploadFile ({ } } - urlToFile(filePath).then(upload).catch(() => { - setTimeout(() => { - invoke(callbackId, { - errMsg: 'uploadFile:fail file error' - }) - }, 0) - }) + Promise + .all(files.map(({ file, uri }) => file instanceof File ? Promise.resolve(file) : urlToFile(uri))) + .then(upload) + .catch(() => { + setTimeout(() => { + invoke(callbackId, { + errMsg: 'uploadFile:fail file error' + }) + }, 0) + }) return uploadTask } diff --git a/src/platforms/mp-toutiao/runtime/wrapper/page-parser.js b/src/platforms/mp-toutiao/runtime/wrapper/page-parser.js index 26959e8d61b6c621790e5ecba6796d4771d85418..6598851b0a2982bd538f235064b7cd82f9db1f8b 100644 --- a/src/platforms/mp-toutiao/runtime/wrapper/page-parser.js +++ b/src/platforms/mp-toutiao/runtime/wrapper/page-parser.js @@ -1,5 +1,6 @@ import { - isPage, + isPage, + instances, initRelation } from './util' @@ -21,6 +22,17 @@ export default function parsePage (vuePageOptions) { } else { this.is && console.warn(this.is + ' is not ready') } + } + + pageOptions.lifetimes.detached = function detached () { + this.$vm && this.$vm.$destroy() + // 清理 + const webviewId = this.__webviewId__ + webviewId && Object.keys(instances).forEach(key => { + if (key.indexOf(webviewId + '_') === 0) { + delete instances[key] + } + }) } return pageOptions diff --git a/src/platforms/mp-toutiao/runtime/wrapper/util.js b/src/platforms/mp-toutiao/runtime/wrapper/util.js index 87dc0989ab3764a1d0e18d5e99cd053ade2313ae..01980a0cca5b251dca285b3133c0f62b16eb6afe 100644 --- a/src/platforms/mp-toutiao/runtime/wrapper/util.js +++ b/src/platforms/mp-toutiao/runtime/wrapper/util.js @@ -51,7 +51,7 @@ export function initRefs (vm) { } } -const instances = Object.create(null) +export const instances = Object.create(null) export function initRelation ({ vuePid, @@ -101,4 +101,4 @@ export function handleLink ({ vm._isMounted = true vm.__call_hook('mounted') vm.__call_hook('onReady') -} +} diff --git a/src/platforms/quickapp/README.md b/src/platforms/quickapp/README.md index 2f098e8772f4a7db95ddd36a0b4ed58d1958d2b1..7967202300e1432b25d59b2d2ccbfb5aa65b2753 100644 --- a/src/platforms/quickapp/README.md +++ b/src/platforms/quickapp/README.md @@ -78,7 +78,8 @@ src #### 开发示例 -- button 组件 [https://github.com/dcloudio/uni-app/tree/master/src/platforms/quickapp/view/components/button](https://github.com/dcloudio/uni-app/tree/master/src/platforms/quickapp/view/components/button) +- button 组件 `src/platforms/quickapp/view/components/button` +- clipboard API `src/platforms/quickapp/service/api/device/clipboard` diff --git a/src/platforms/quickapp/service/api/device/clipboard.js b/src/platforms/quickapp/service/api/device/clipboard.js new file mode 100644 index 0000000000000000000000000000000000000000..d4c560a8404a13aa82609692660fed0be3cda649 --- /dev/null +++ b/src/platforms/quickapp/service/api/device/clipboard.js @@ -0,0 +1,33 @@ +import clipboard from '@system.clipboard' + +import { + invoke +} from '../../bridge' + +export function getClipboardData (options, callbackId) { + clipboard.get({ + success: (ret) => { + invoke(callbackId, { + data: ret.text, + errMsg: 'getClipboardData:ok' + }) + }, + fail: (data, code) => { + invoke(callbackId, { + data: code, + errMsg: 'getClipboardData:fail' + }) + } + }) +} + +export function setClipboardData ({ + data +}) { + clipboard.set({ + text: data + }) + return { + errMsg: 'setClipboardData:ok' + } +} diff --git a/src/platforms/quickapp/service/platform-api.js b/src/platforms/quickapp/service/platform-api.js index a36bfa45f872d6adfc8597c7f748f5178013e572..a0a78e4d43f3fb95b5004969df1f162ae689ffd8 100644 --- a/src/platforms/quickapp/service/platform-api.js +++ b/src/platforms/quickapp/service/platform-api.js @@ -1,3 +1,6 @@ export * from './api/route/navigate-back' export * from './api/route/navigate-to' export * from './api/route/redirect-to' + +// device +export * from './api/device/clipboard'