diff --git a/build/rollup.config.js b/build/rollup.config.js index 94e782a9fcffef928385b485c8cdd1971b224f35..f261bbc7c2560b53675382387cf8703df9cac3c4 100644 --- a/build/rollup.config.js +++ b/build/rollup.config.js @@ -41,6 +41,7 @@ module.exports = { replace({ __GLOBAL__: platform.prefix, __PLATFORM_TITLE__: platform.title, + __PLATFORM_PREFIX__: JSON.stringify(platform.prefix), __PLATFORM__: JSON.stringify(process.env.UNI_PLATFORM) }) ], diff --git a/package.json b/package.json index 0e017e0ff76bc1a3ffb359e0e795924e42f03869..9ab868b0b30c6c48338886e9570cbdb83615e79d 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,8 @@ "__PLATFORM__": true, "__VERSION__": true, "__GLOBAL__": true, - "__PLATFORM_TITLE__": true + "__PLATFORM_TITLE__": true, + "__PLATFORM_PREFIX__":true }, "rules": { "no-tabs": 0, diff --git a/packages/uni-app-plus/dist/index.js b/packages/uni-app-plus/dist/index.js index 672330eed81d0cb362a7e40063385c9b21b3616f..1fe07d7a8f173e33b479fb557695fc1c56dc4e88 100644 --- a/packages/uni-app-plus/dist/index.js +++ b/packages/uni-app-plus/dist/index.js @@ -173,7 +173,7 @@ function processArgs (methodName, fromArgs, argsOption = {}, returnValue = {}, k } else if (isPlainObject(keyOption)) { // {name:newName,value:value}可重新指定参数 key:value toArgs[keyOption.name ? keyOption.name : key] = keyOption.value; } - } else if (CALLBACKS.includes(key)) { + } else if (CALLBACKS.indexOf(key) !== -1) { toArgs[key] = processCallback(methodName, fromArgs[key], returnValue); } else { if (!keepFromArgs) { @@ -309,11 +309,43 @@ Component = function (options = {}) { return MPComponent(options) }; -const MOCKS = ['__route__', '__wxExparserNodeId__', '__wxWebviewId__', '__webviewId__']; +const mocks = ['__route__', '__wxExparserNodeId__', '__wxWebviewId__']; -function initMocks (vm) { +function triggerLink (mpInstance, vueOptions) { + mpInstance.triggerEvent('__l', mpInstance.$vm || vueOptions, { + bubbles: true, + composed: true + }); +} + +function handleLink (event) { + if (event.detail.$mp) { // vm + if (!event.detail.$parent) { + event.detail.$parent = this.$vm; + event.detail.$parent.$children.push(event.detail); + + event.detail.$root = this.$vm.$root; + } + } else { // vueOptions + if (!event.detail.parent) { + event.detail.parent = this.$vm; + } + } +} + +function initPage$1 (pageOptions) { + initComponent$1(pageOptions); +} + +function initComponent$1 (componentOptions) { + componentOptions.methods.$getAppWebview = function () { + return plus.webview.getWebviewById(`${this.__wxWebviewId__}`) + }; +} + +function initMocks (vm, mocks) { const mpInstance = vm.$mp[vm.mpType]; - MOCKS.forEach(mock => { + mocks.forEach(mock => { if (hasOwn(mpInstance, mock)) { vm[mock] = mpInstance[mock]; } @@ -347,6 +379,10 @@ function getData (vueOptions, context) { } catch (e) {} } + if (!isPlainObject(data)) { + data = {}; + } + Object.keys(methods).forEach(methodName => { if (context.__lifecycle_hooks__.indexOf(methodName) === -1 && !hasOwn(data, methodName)) { data[methodName] = methods[methodName]; @@ -366,9 +402,65 @@ function createObserver (name) { } } -function getProperties (props) { - const properties = { - vueSlots: { // 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots +function getBehaviors (vueOptions) { + const vueBehaviors = vueOptions['behaviors']; + const vueExtends = vueOptions['extends']; + const vueMixins = vueOptions['mixins']; + + let vueProps = vueOptions['props']; + + if (!vueProps) { + vueOptions['props'] = vueProps = []; + } + + const behaviors = []; + if (Array.isArray(vueBehaviors)) { + vueBehaviors.forEach(behavior => { + behaviors.push(behavior.replace('uni://', `${"wx"}://`)); + if (behavior === 'uni://form-field') { + if (Array.isArray(vueProps)) { + vueProps.push('name'); + vueProps.push('value'); + } else { + vueProps['name'] = String; + vueProps['value'] = null; + } + } + }); + } + if (isPlainObject(vueExtends) && vueExtends.props) { + behaviors.push( + Behavior({ + properties: getProperties(vueExtends.props, true) + }) + ); + } + if (Array.isArray(vueMixins)) { + vueMixins.forEach(vueMixin => { + if (isPlainObject(vueMixin) && vueMixin.props) { + behaviors.push( + Behavior({ + properties: getProperties(vueMixin.props, true) + }) + ); + } + }); + } + return behaviors +} + +function parsePropType (key, type, defaultValue, file) { + // [String]=>String + if (Array.isArray(type) && type.length === 1) { + return type[0] + } + return type +} + +function getProperties (props, isBehavior = false, file = '') { + const properties = {}; + if (!isBehavior) { + properties.vueSlots = { // 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots type: null, value: [], observer: function (newVal, oldVal) { @@ -380,8 +472,8 @@ function getProperties (props) { $slots }); } - } - }; + }; + } if (Array.isArray(props)) { // ['title'] props.forEach(key => { properties[key] = { @@ -397,14 +489,18 @@ function getProperties (props) { if (isFn(value)) { value = value(); } + + opts.type = parsePropType(key, opts.type, value, file); + properties[key] = { - type: PROP_TYPES.includes(opts.type) ? opts.type : null, + type: PROP_TYPES.indexOf(opts.type) !== -1 ? opts.type : null, value, observer: createObserver(key) }; } else { // content:String + const type = parsePropType(key, opts, null, file); properties[key] = { - type: PROP_TYPES.includes(opts) ? opts : null, + type: PROP_TYPES.indexOf(type) !== -1 ? type : null, observer: createObserver(key) }; } @@ -414,6 +510,11 @@ function getProperties (props) { } function wrapper$1 (event) { + // TODO 又得兼容 mpvue 的 mp 对象 + try { + event.mp = JSON.parse(JSON.stringify(event)); + } catch (e) {} + event.stopPropagation = noop; event.preventDefault = noop; @@ -423,9 +524,6 @@ function wrapper$1 (event) { event.detail = {}; } - // TODO 又得兼容 mpvue 的 mp 对象 - event.mp = event; - if (isPlainObject(event.detail)) { event.target = Object.assign({}, event.target, event.detail); } @@ -470,7 +568,7 @@ function getExtraValue (vm, dataPathsArray) { return context } -function processEventExtra (vm, extra) { +function processEventExtra (vm, extra, event) { const extraObj = {}; if (Array.isArray(extra) && extra.length) { @@ -490,7 +588,13 @@ function processEventExtra (vm, extra) { if (!dataPath) { // model,prop.sync extraObj['$' + index] = vm; } else { - extraObj['$' + index] = vm.__get_value(dataPath); + if (dataPath === '$event') { // $event + extraObj['$' + index] = event; + } else if (dataPath.indexOf('$event.') === 0) { // $event.target.value + extraObj['$' + index] = vm.__get_value(dataPath.replace('$event.', ''), event); + } else { + extraObj['$' + index] = vm.__get_value(dataPath); + } } } else { extraObj['$' + index] = getExtraValue(vm, dataPath); @@ -501,6 +605,15 @@ function processEventExtra (vm, extra) { return extraObj } +function getObjByArray (arr) { + const obj = {}; + for (let i = 1; i < arr.length; i++) { + const element = arr[i]; + obj[element[0]] = element[1]; + } + return obj +} + function processEventArgs (vm, event, args = [], extra = [], isCustom, methodName) { let isCustomMPEvent = false; // wxcomponent 组件,传递原始 event 对象 if (isCustom) { // 自定义事件 @@ -515,7 +628,7 @@ function processEventArgs (vm, event, args = [], extra = [], isCustom, methodNam } } - const extraObj = processEventExtra(vm, extra); + const extraObj = processEventExtra(vm, extra, event); const ret = []; args.forEach(arg => { @@ -530,7 +643,9 @@ function processEventArgs (vm, event, args = [], extra = [], isCustom, methodNam } } } else { - if (typeof arg === 'string' && hasOwn(extraObj, arg)) { + if (Array.isArray(arg) && arg[0] === 'o') { + ret.push(getObjByArray(arg)); + } else if (typeof arg === 'string' && hasOwn(extraObj, arg)) { ret.push(extraObj[arg]); } else { ret.push(arg); @@ -653,7 +768,7 @@ function createApp (vm) { { // 头条的 selectComponent 竟然是异步的 initRefs(this); } - initMocks(this); + initMocks(this, mocks); } }, created () { // 处理 injections @@ -688,38 +803,6 @@ function createApp (vm) { return vm } -function triggerLink (mpInstance, vueOptions) { - mpInstance.triggerEvent('__l', mpInstance.$vm || vueOptions, { - bubbles: true, - composed: true - }); -} - -function handleLink (event) { - if (event.detail.$mp) { // vm - if (!event.detail.$parent) { - event.detail.$parent = this.$vm; - event.detail.$parent.$children.push(event.detail); - - event.detail.$root = this.$vm.$root; - } - } else { // vueOptions - if (!event.detail.parent) { - event.detail.parent = this.$vm; - } - } -} - -function initPage$1 (pageOptions) { - initComponent$1(pageOptions); -} - -function initComponent$1 (componentOptions) { - componentOptions.methods.$getAppWebview = function () { - return plus.webview.getWebviewById(`${this.__wxWebviewId__}`) - }; -} - const hooks$1 = [ 'onShow', 'onHide', @@ -830,7 +913,9 @@ function initVm$2 (VueComponent) { function createComponent (vueOptions) { vueOptions = vueOptions.default || vueOptions; - const properties = getProperties(vueOptions.props); + const behaviors = getBehaviors(vueOptions); + + const properties = getProperties(vueOptions.props, false, vueOptions.__file); const VueComponent = Vue.extend(vueOptions); @@ -840,6 +925,7 @@ function createComponent (vueOptions) { addGlobalClass: true }, data: getData(vueOptions, Vue.prototype), + behaviors, properties, lifetimes: { attached () { diff --git a/packages/uni-app-plus/package.json b/packages/uni-app-plus/package.json index 95c0094a403287b0c1cdf7915943ba7891756b21..50ba1b2b20f0864a2d7e400d35509dbbf2ea0180 100644 --- a/packages/uni-app-plus/package.json +++ b/packages/uni-app-plus/package.json @@ -1,6 +1,6 @@ { "name": "@dcloudio/uni-app-plus", - "version": "0.0.218", + "version": "0.0.228", "description": "uni-app app-plus", "main": "dist/index.js", "scripts": { diff --git a/packages/uni-mp-baidu/dist/index.js b/packages/uni-mp-baidu/dist/index.js index b71e33d938f5a0b310829d750e0c0e62b79de28a..a981de4e9d253950ae3fec8d7ead67f7d0e16fb8 100644 --- a/packages/uni-mp-baidu/dist/index.js +++ b/packages/uni-mp-baidu/dist/index.js @@ -270,7 +270,7 @@ function processArgs (methodName, fromArgs, argsOption = {}, returnValue = {}, k } else if (isPlainObject(keyOption)) { // {name:newName,value:value}可重新指定参数 key:value toArgs[keyOption.name ? keyOption.name : key] = keyOption.value; } - } else if (CALLBACKS.includes(key)) { + } else if (CALLBACKS.indexOf(key) !== -1) { toArgs[key] = processCallback(methodName, fromArgs[key], returnValue); } else { if (!keepFromArgs) { @@ -444,11 +444,41 @@ Component = function (options = {}) { return MPComponent(options) }; -const MOCKS = ['__route__', '__wxExparserNodeId__', '__wxWebviewId__', '__webviewId__']; +const mocks = ['nodeId']; -function initMocks (vm) { +function initPage (pageOptions) { + initComponent(pageOptions); +} + +function initComponent (componentOptions) { + componentOptions.messages = { + '__l': handleLink + }; +} + +function triggerLink (mpInstance, vueOptions) { + mpInstance.dispatch('__l', mpInstance.$vm || vueOptions); +} + +function handleLink (event) { + const target = event.value; + if (target.$mp) { + if (!target.$parent) { + target.$parent = this.$vm; + target.$parent.$children.push(target); + + target.$root = this.$vm.$root; + } + } else { + if (!target.parent) { + target.parent = this.$vm; + } + } +} + +function initMocks (vm, mocks) { const mpInstance = vm.$mp[vm.mpType]; - MOCKS.forEach(mock => { + mocks.forEach(mock => { if (hasOwn(mpInstance, mock)) { vm[mock] = mpInstance[mock]; } @@ -482,6 +512,10 @@ function getData (vueOptions, context) { } catch (e) {} } + if (!isPlainObject(data)) { + data = {}; + } + Object.keys(methods).forEach(methodName => { if (context.__lifecycle_hooks__.indexOf(methodName) === -1 && !hasOwn(data, methodName)) { data[methodName] = methods[methodName]; @@ -501,9 +535,81 @@ function createObserver (name) { } } -function getProperties (props) { - const properties = { - vueSlots: { // 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots +function getBehaviors (vueOptions) { + const vueBehaviors = vueOptions['behaviors']; + const vueExtends = vueOptions['extends']; + const vueMixins = vueOptions['mixins']; + + let vueProps = vueOptions['props']; + + if (!vueProps) { + vueOptions['props'] = vueProps = []; + } + + const behaviors = []; + if (Array.isArray(vueBehaviors)) { + vueBehaviors.forEach(behavior => { + behaviors.push(behavior.replace('uni://', `${"swan"}://`)); + if (behavior === 'uni://form-field') { + if (Array.isArray(vueProps)) { + vueProps.push('name'); + vueProps.push('value'); + } else { + vueProps['name'] = String; + vueProps['value'] = null; + } + } + }); + } + if (isPlainObject(vueExtends) && vueExtends.props) { + behaviors.push( + Behavior({ + properties: getProperties(vueExtends.props, true) + }) + ); + } + if (Array.isArray(vueMixins)) { + vueMixins.forEach(vueMixin => { + if (isPlainObject(vueMixin) && vueMixin.props) { + behaviors.push( + Behavior({ + properties: getProperties(vueMixin.props, true) + }) + ); + } + }); + } + return behaviors +} + +function parsePropType (key, type, defaultValue, file) { + // [String]=>String + if (Array.isArray(type) && type.length === 1) { + return type[0] + } + { + if ( + defaultValue === false && + Array.isArray(type) && + type.length === 2 && + type.indexOf(String) !== -1 && + type.indexOf(Boolean) !== -1 + ) { // [String,Boolean]=>Boolean + if (file) { + console.warn( + `props.${key}.type should use Boolean instead of [String,Boolean] at ${file}` + ); + } + return Boolean + } + } + return type +} + +function getProperties (props, isBehavior = false, file = '') { + const properties = {}; + if (!isBehavior) { + properties.vueSlots = { // 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots type: null, value: [], observer: function (newVal, oldVal) { @@ -515,8 +621,8 @@ function getProperties (props) { $slots }); } - } - }; + }; + } if (Array.isArray(props)) { // ['title'] props.forEach(key => { properties[key] = { @@ -532,14 +638,18 @@ function getProperties (props) { if (isFn(value)) { value = value(); } + + opts.type = parsePropType(key, opts.type, value, file); + properties[key] = { - type: PROP_TYPES.includes(opts.type) ? opts.type : null, + type: PROP_TYPES.indexOf(opts.type) !== -1 ? opts.type : null, value, observer: createObserver(key) }; } else { // content:String + const type = parsePropType(key, opts, null, file); properties[key] = { - type: PROP_TYPES.includes(opts) ? opts : null, + type: PROP_TYPES.indexOf(type) !== -1 ? type : null, observer: createObserver(key) }; } @@ -549,6 +659,11 @@ function getProperties (props) { } function wrapper$1 (event) { + // TODO 又得兼容 mpvue 的 mp 对象 + try { + event.mp = JSON.parse(JSON.stringify(event)); + } catch (e) {} + event.stopPropagation = noop; event.preventDefault = noop; @@ -568,9 +683,6 @@ function wrapper$1 (event) { } } - // TODO 又得兼容 mpvue 的 mp 对象 - event.mp = event; - if (isPlainObject(event.detail)) { event.target = Object.assign({}, event.target, event.detail); } @@ -615,7 +727,7 @@ function getExtraValue (vm, dataPathsArray) { return context } -function processEventExtra (vm, extra) { +function processEventExtra (vm, extra, event) { const extraObj = {}; if (Array.isArray(extra) && extra.length) { @@ -635,7 +747,13 @@ function processEventExtra (vm, extra) { if (!dataPath) { // model,prop.sync extraObj['$' + index] = vm; } else { - extraObj['$' + index] = vm.__get_value(dataPath); + if (dataPath === '$event') { // $event + extraObj['$' + index] = event; + } else if (dataPath.indexOf('$event.') === 0) { // $event.target.value + extraObj['$' + index] = vm.__get_value(dataPath.replace('$event.', ''), event); + } else { + extraObj['$' + index] = vm.__get_value(dataPath); + } } } else { extraObj['$' + index] = getExtraValue(vm, dataPath); @@ -646,6 +764,15 @@ function processEventExtra (vm, extra) { return extraObj } +function getObjByArray (arr) { + const obj = {}; + for (let i = 1; i < arr.length; i++) { + const element = arr[i]; + obj[element[0]] = element[1]; + } + return obj +} + function processEventArgs (vm, event, args = [], extra = [], isCustom, methodName) { let isCustomMPEvent = false; // wxcomponent 组件,传递原始 event 对象 if (isCustom) { // 自定义事件 @@ -660,7 +787,7 @@ function processEventArgs (vm, event, args = [], extra = [], isCustom, methodNam } } - const extraObj = processEventExtra(vm, extra); + const extraObj = processEventExtra(vm, extra, event); const ret = []; args.forEach(arg => { @@ -675,7 +802,9 @@ function processEventArgs (vm, event, args = [], extra = [], isCustom, methodNam } } } else { - if (typeof arg === 'string' && hasOwn(extraObj, arg)) { + if (Array.isArray(arg) && arg[0] === 'o') { + ret.push(getObjByArray(arg)); + } else if (typeof arg === 'string' && hasOwn(extraObj, arg)) { ret.push(extraObj[arg]); } else { ret.push(arg); @@ -812,7 +941,7 @@ function createApp (vm) { { // 头条的 selectComponent 竟然是异步的 initRefs(this); } - initMocks(this); + initMocks(this, mocks); } }, created () { // 处理 injections @@ -847,36 +976,6 @@ function createApp (vm) { return vm } -function initPage (pageOptions) { - initComponent(pageOptions); -} - -function initComponent (componentOptions) { - componentOptions.messages = { - '__l': handleLink - }; -} - -function triggerLink (mpInstance, vueOptions) { - mpInstance.dispatch('__l', mpInstance.$vm || vueOptions); -} - -function handleLink (event) { - const target = event.value; - if (target.$mp) { - if (!target.$parent) { - target.$parent = this.$vm; - target.$parent.$children.push(target); - - target.$root = this.$vm.$root; - } - } else { - if (!target.parent) { - target.parent = this.$vm; - } - } -} - const hooks$1 = [ 'onShow', 'onHide', @@ -997,7 +1096,9 @@ function initVm$2 (VueComponent) { function createComponent (vueOptions) { vueOptions = vueOptions.default || vueOptions; - const properties = getProperties(vueOptions.props); + const behaviors = getBehaviors(vueOptions); + + const properties = getProperties(vueOptions.props, false, vueOptions.__file); const VueComponent = Vue.extend(vueOptions); @@ -1007,6 +1108,7 @@ function createComponent (vueOptions) { addGlobalClass: true }, data: getData(vueOptions, Vue.prototype), + behaviors, properties, lifetimes: { attached () { diff --git a/packages/uni-mp-baidu/package.json b/packages/uni-mp-baidu/package.json index a06a2a6e6873b935c7b2e822326dfcd9fad944fb..fd9ce10f1a3eeedf0c945321f2e1de4e7a721309 100644 --- a/packages/uni-mp-baidu/package.json +++ b/packages/uni-mp-baidu/package.json @@ -1,6 +1,6 @@ { "name": "@dcloudio/uni-mp-baidu", - "version": "0.0.813", + "version": "0.0.825", "description": "uni-app mp-baidu", "main": "dist/index.js", "scripts": { diff --git a/packages/uni-mp-toutiao/dist/index.js b/packages/uni-mp-toutiao/dist/index.js index 59006669eb61530b50918540b8bebe5937da8693..7db66e24c8825882952e10ff52ef8dfeeb27ddc1 100644 --- a/packages/uni-mp-toutiao/dist/index.js +++ b/packages/uni-mp-toutiao/dist/index.js @@ -331,7 +331,7 @@ function processArgs (methodName, fromArgs, argsOption = {}, returnValue = {}, k } else if (isPlainObject(keyOption)) { // {name:newName,value:value}可重新指定参数 key:value toArgs[keyOption.name ? keyOption.name : key] = keyOption.value; } - } else if (CALLBACKS.includes(key)) { + } else if (CALLBACKS.indexOf(key) !== -1) { toArgs[key] = processCallback(methodName, fromArgs[key], returnValue); } else { if (!keepFromArgs) { @@ -489,11 +489,80 @@ Component = function (options = {}) { return MPComponent(options) }; -const MOCKS = ['__route__', '__wxExparserNodeId__', '__wxWebviewId__', '__webviewId__']; +const instances = Object.create(null); + +const mocks = ['__route__', '__webviewId__', '__nodeid__']; + +function initPage (pageOptions) { + initComponent(pageOptions); +} + +function initComponent (componentOptions) { + if (componentOptions.properties) { // ref + componentOptions.properties.vueRef = { + type: String, + value: '' + }; + } + const oldAttached = componentOptions.lifetimes.attached; + componentOptions.lifetimes.attached = function () { + oldAttached.call(this); + // TODO 需要处理动态变化后的 refs + initRefs.call(this); + }; +} + +function initRefs () { + this.selectAllComponents('.vue-ref', (components) => { + components.forEach(component => { + const ref = component.data.vueRef; // 头条的组件 dataset 竟然是空的 + this.$vm.$refs[ref] = component.$vm || component; + }); + }); + this.selectAllComponents('.vue-ref-in-for', (forComponents) => { + forComponents.forEach(component => { + const ref = component.data.vueRef; + if (!this.$vm.$refs[ref]) { + this.$vm.$refs[ref] = []; + } + this.$vm.$refs[ref].push(component.$vm || component); + }); + }); +} + +function triggerLink (mpInstance) { + const nodeId = mpInstance.__nodeid__ + ''; + const webviewId = mpInstance.__webviewId__ + ''; + + instances[webviewId + '_' + nodeId] = mpInstance.$vm; -function initMocks (vm) { + mpInstance.triggerEvent('__l', { + nodeId, + webviewId + }, { + bubbles: true, + composed: true + }); +} +// TODO 目前有 bug,composed 不生效 +function handleLink (event) { + const nodeId = event.detail.nodeId; + const webviewId = event.detail.webviewId; + + const childVm = instances[webviewId + '_' + nodeId]; + + if (childVm) { + childVm.$parent = this.$vm; + childVm.$parent.$children.push(event.detail); + + childVm.$root = this.$vm.$root; + delete instances[webviewId + '_' + nodeId]; + } +} + +function initMocks (vm, mocks) { const mpInstance = vm.$mp[vm.mpType]; - MOCKS.forEach(mock => { + mocks.forEach(mock => { if (hasOwn(mpInstance, mock)) { vm[mock] = mpInstance[mock]; } @@ -527,6 +596,10 @@ function getData (vueOptions, context) { } catch (e) {} } + if (!isPlainObject(data)) { + data = {}; + } + Object.keys(methods).forEach(methodName => { if (context.__lifecycle_hooks__.indexOf(methodName) === -1 && !hasOwn(data, methodName)) { data[methodName] = methods[methodName]; @@ -546,9 +619,65 @@ function createObserver (name) { } } -function getProperties (props) { - const properties = { - vueSlots: { // 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots +function getBehaviors (vueOptions) { + const vueBehaviors = vueOptions['behaviors']; + const vueExtends = vueOptions['extends']; + const vueMixins = vueOptions['mixins']; + + let vueProps = vueOptions['props']; + + if (!vueProps) { + vueOptions['props'] = vueProps = []; + } + + const behaviors = []; + if (Array.isArray(vueBehaviors)) { + vueBehaviors.forEach(behavior => { + behaviors.push(behavior.replace('uni://', `${"tt"}://`)); + if (behavior === 'uni://form-field') { + if (Array.isArray(vueProps)) { + vueProps.push('name'); + vueProps.push('value'); + } else { + vueProps['name'] = String; + vueProps['value'] = null; + } + } + }); + } + if (isPlainObject(vueExtends) && vueExtends.props) { + behaviors.push( + Behavior({ + properties: getProperties(vueExtends.props, true) + }) + ); + } + if (Array.isArray(vueMixins)) { + vueMixins.forEach(vueMixin => { + if (isPlainObject(vueMixin) && vueMixin.props) { + behaviors.push( + Behavior({ + properties: getProperties(vueMixin.props, true) + }) + ); + } + }); + } + return behaviors +} + +function parsePropType (key, type, defaultValue, file) { + // [String]=>String + if (Array.isArray(type) && type.length === 1) { + return type[0] + } + return type +} + +function getProperties (props, isBehavior = false, file = '') { + const properties = {}; + if (!isBehavior) { + properties.vueSlots = { // 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots type: null, value: [], observer: function (newVal, oldVal) { @@ -560,8 +689,8 @@ function getProperties (props) { $slots }); } - } - }; + }; + } if (Array.isArray(props)) { // ['title'] props.forEach(key => { properties[key] = { @@ -577,14 +706,18 @@ function getProperties (props) { if (isFn(value)) { value = value(); } + + opts.type = parsePropType(key, opts.type, value, file); + properties[key] = { - type: PROP_TYPES.includes(opts.type) ? opts.type : null, + type: PROP_TYPES.indexOf(opts.type) !== -1 ? opts.type : null, value, observer: createObserver(key) }; } else { // content:String + const type = parsePropType(key, opts, null, file); properties[key] = { - type: PROP_TYPES.includes(opts) ? opts : null, + type: PROP_TYPES.indexOf(type) !== -1 ? type : null, observer: createObserver(key) }; } @@ -594,6 +727,11 @@ function getProperties (props) { } function wrapper$1 (event) { + // TODO 又得兼容 mpvue 的 mp 对象 + try { + event.mp = JSON.parse(JSON.stringify(event)); + } catch (e) {} + event.stopPropagation = noop; event.preventDefault = noop; @@ -603,9 +741,6 @@ function wrapper$1 (event) { event.detail = {}; } - // TODO 又得兼容 mpvue 的 mp 对象 - event.mp = event; - if (isPlainObject(event.detail)) { event.target = Object.assign({}, event.target, event.detail); } @@ -650,7 +785,7 @@ function getExtraValue (vm, dataPathsArray) { return context } -function processEventExtra (vm, extra) { +function processEventExtra (vm, extra, event) { const extraObj = {}; if (Array.isArray(extra) && extra.length) { @@ -670,7 +805,13 @@ function processEventExtra (vm, extra) { if (!dataPath) { // model,prop.sync extraObj['$' + index] = vm; } else { - extraObj['$' + index] = vm.__get_value(dataPath); + if (dataPath === '$event') { // $event + extraObj['$' + index] = event; + } else if (dataPath.indexOf('$event.') === 0) { // $event.target.value + extraObj['$' + index] = vm.__get_value(dataPath.replace('$event.', ''), event); + } else { + extraObj['$' + index] = vm.__get_value(dataPath); + } } } else { extraObj['$' + index] = getExtraValue(vm, dataPath); @@ -681,6 +822,15 @@ function processEventExtra (vm, extra) { return extraObj } +function getObjByArray (arr) { + const obj = {}; + for (let i = 1; i < arr.length; i++) { + const element = arr[i]; + obj[element[0]] = element[1]; + } + return obj +} + function processEventArgs (vm, event, args = [], extra = [], isCustom, methodName) { let isCustomMPEvent = false; // wxcomponent 组件,传递原始 event 对象 if (isCustom) { // 自定义事件 @@ -695,7 +845,7 @@ function processEventArgs (vm, event, args = [], extra = [], isCustom, methodNam } } - const extraObj = processEventExtra(vm, extra); + const extraObj = processEventExtra(vm, extra, event); const ret = []; args.forEach(arg => { @@ -710,7 +860,9 @@ function processEventArgs (vm, event, args = [], extra = [], isCustom, methodNam } } } else { - if (typeof arg === 'string' && hasOwn(extraObj, arg)) { + if (Array.isArray(arg) && arg[0] === 'o') { + ret.push(getObjByArray(arg)); + } else if (typeof arg === 'string' && hasOwn(extraObj, arg)) { ret.push(extraObj[arg]); } else { ret.push(arg); @@ -807,7 +959,7 @@ function createApp (vm) { delete this.$options.mpInstance; if (this.mpType !== 'app') { - initMocks(this); + initMocks(this, mocks); } }, created () { // 处理 injections @@ -842,75 +994,6 @@ function createApp (vm) { return vm } -const instances = Object.create(null); - -function initPage (pageOptions) { - initComponent(pageOptions); -} - -function initComponent (componentOptions) { - if (componentOptions.properties) { // ref - componentOptions.properties.vueRef = { - type: String, - value: '' - }; - } - const oldAttached = componentOptions.lifetimes.attached; - componentOptions.lifetimes.attached = function () { - oldAttached.call(this); - // TODO 需要处理动态变化后的 refs - initRefs$1.call(this); - }; -} - -function initRefs$1 () { - this.selectAllComponents('.vue-ref', (components) => { - components.forEach(component => { - const ref = component.data.vueRef; // 头条的组件 dataset 竟然是空的 - this.$vm.$refs[ref] = component.$vm || component; - }); - }); - this.selectAllComponents('.vue-ref-in-for', (forComponents) => { - forComponents.forEach(component => { - const ref = component.data.vueRef; - if (!this.$vm.$refs[ref]) { - this.$vm.$refs[ref] = []; - } - this.$vm.$refs[ref].push(component.$vm || component); - }); - }); -} - -function triggerLink (mpInstance) { - const nodeId = mpInstance.__nodeid__ + ''; - const webviewId = mpInstance.__webviewId__ + ''; - - instances[webviewId + '_' + nodeId] = mpInstance.$vm; - - mpInstance.triggerEvent('__l', { - nodeId, - webviewId - }, { - bubbles: true, - composed: true - }); -} -// TODO 目前有 bug,composed 不生效 -function handleLink (event) { - const nodeId = event.detail.nodeId; - const webviewId = event.detail.webviewId; - - const childVm = instances[webviewId + '_' + nodeId]; - - if (childVm) { - childVm.$parent = this.$vm; - childVm.$parent.$children.push(event.detail); - - childVm.$root = this.$vm.$root; - delete instances[webviewId + '_' + nodeId]; - } -} - const hooks$1 = [ 'onShow', 'onHide', @@ -1021,7 +1104,9 @@ function initVm$2 (VueComponent) { function createComponent (vueOptions) { vueOptions = vueOptions.default || vueOptions; - const properties = getProperties(vueOptions.props); + const behaviors = getBehaviors(vueOptions); + + const properties = getProperties(vueOptions.props, false, vueOptions.__file); const VueComponent = Vue.extend(vueOptions); @@ -1031,6 +1116,7 @@ function createComponent (vueOptions) { addGlobalClass: true }, data: getData(vueOptions, Vue.prototype), + behaviors, properties, lifetimes: { attached () { diff --git a/packages/uni-mp-toutiao/package.json b/packages/uni-mp-toutiao/package.json index 6f595d5667ddb606ee1ee8b7a20bbaf7d3037582..b9a406ffff794e6ea1eca24d3b0356db3323ecff 100644 --- a/packages/uni-mp-toutiao/package.json +++ b/packages/uni-mp-toutiao/package.json @@ -1,6 +1,6 @@ { "name": "@dcloudio/uni-mp-toutiao", - "version": "0.0.313", + "version": "0.0.323", "description": "uni-app mp-toutiao", "main": "dist/index.js", "scripts": { diff --git a/packages/uni-mp-weixin/dist/index.js b/packages/uni-mp-weixin/dist/index.js index 42e8692d579100c6ced1ca040b25bf871b0edae9..cc49efb8b2919fb56893e2e36cbd94650f50e398 100644 --- a/packages/uni-mp-weixin/dist/index.js +++ b/packages/uni-mp-weixin/dist/index.js @@ -173,7 +173,7 @@ function processArgs (methodName, fromArgs, argsOption = {}, returnValue = {}, k } else if (isPlainObject(keyOption)) { // {name:newName,value:value}可重新指定参数 key:value toArgs[keyOption.name ? keyOption.name : key] = keyOption.value; } - } else if (CALLBACKS.includes(key)) { + } else if (CALLBACKS.indexOf(key) !== -1) { toArgs[key] = processCallback(methodName, fromArgs[key], returnValue); } else { if (!keepFromArgs) { @@ -336,11 +336,33 @@ Component = function (options = {}) { return MPComponent(options) }; -const MOCKS = ['__route__', '__wxExparserNodeId__', '__wxWebviewId__', '__webviewId__']; +const mocks = ['__route__', '__wxExparserNodeId__', '__wxWebviewId__']; -function initMocks (vm) { +function triggerLink (mpInstance, vueOptions) { + mpInstance.triggerEvent('__l', mpInstance.$vm || vueOptions, { + bubbles: true, + composed: true + }); +} + +function handleLink (event) { + if (event.detail.$mp) { // vm + if (!event.detail.$parent) { + event.detail.$parent = this.$vm; + event.detail.$parent.$children.push(event.detail); + + event.detail.$root = this.$vm.$root; + } + } else { // vueOptions + if (!event.detail.parent) { + event.detail.parent = this.$vm; + } + } +} + +function initMocks (vm, mocks) { const mpInstance = vm.$mp[vm.mpType]; - MOCKS.forEach(mock => { + mocks.forEach(mock => { if (hasOwn(mpInstance, mock)) { vm[mock] = mpInstance[mock]; } @@ -374,6 +396,10 @@ function getData (vueOptions, context) { } catch (e) {} } + if (!isPlainObject(data)) { + data = {}; + } + Object.keys(methods).forEach(methodName => { if (context.__lifecycle_hooks__.indexOf(methodName) === -1 && !hasOwn(data, methodName)) { data[methodName] = methods[methodName]; @@ -393,9 +419,65 @@ function createObserver (name) { } } -function getProperties (props) { - const properties = { - vueSlots: { // 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots +function getBehaviors (vueOptions) { + const vueBehaviors = vueOptions['behaviors']; + const vueExtends = vueOptions['extends']; + const vueMixins = vueOptions['mixins']; + + let vueProps = vueOptions['props']; + + if (!vueProps) { + vueOptions['props'] = vueProps = []; + } + + const behaviors = []; + if (Array.isArray(vueBehaviors)) { + vueBehaviors.forEach(behavior => { + behaviors.push(behavior.replace('uni://', `${"wx"}://`)); + if (behavior === 'uni://form-field') { + if (Array.isArray(vueProps)) { + vueProps.push('name'); + vueProps.push('value'); + } else { + vueProps['name'] = String; + vueProps['value'] = null; + } + } + }); + } + if (isPlainObject(vueExtends) && vueExtends.props) { + behaviors.push( + Behavior({ + properties: getProperties(vueExtends.props, true) + }) + ); + } + if (Array.isArray(vueMixins)) { + vueMixins.forEach(vueMixin => { + if (isPlainObject(vueMixin) && vueMixin.props) { + behaviors.push( + Behavior({ + properties: getProperties(vueMixin.props, true) + }) + ); + } + }); + } + return behaviors +} + +function parsePropType (key, type, defaultValue, file) { + // [String]=>String + if (Array.isArray(type) && type.length === 1) { + return type[0] + } + return type +} + +function getProperties (props, isBehavior = false, file = '') { + const properties = {}; + if (!isBehavior) { + properties.vueSlots = { // 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots type: null, value: [], observer: function (newVal, oldVal) { @@ -407,8 +489,8 @@ function getProperties (props) { $slots }); } - } - }; + }; + } if (Array.isArray(props)) { // ['title'] props.forEach(key => { properties[key] = { @@ -424,14 +506,18 @@ function getProperties (props) { if (isFn(value)) { value = value(); } + + opts.type = parsePropType(key, opts.type, value, file); + properties[key] = { - type: PROP_TYPES.includes(opts.type) ? opts.type : null, + type: PROP_TYPES.indexOf(opts.type) !== -1 ? opts.type : null, value, observer: createObserver(key) }; } else { // content:String + const type = parsePropType(key, opts, null, file); properties[key] = { - type: PROP_TYPES.includes(opts) ? opts : null, + type: PROP_TYPES.indexOf(type) !== -1 ? type : null, observer: createObserver(key) }; } @@ -441,6 +527,11 @@ function getProperties (props) { } function wrapper$1 (event) { + // TODO 又得兼容 mpvue 的 mp 对象 + try { + event.mp = JSON.parse(JSON.stringify(event)); + } catch (e) {} + event.stopPropagation = noop; event.preventDefault = noop; @@ -450,9 +541,6 @@ function wrapper$1 (event) { event.detail = {}; } - // TODO 又得兼容 mpvue 的 mp 对象 - event.mp = event; - if (isPlainObject(event.detail)) { event.target = Object.assign({}, event.target, event.detail); } @@ -497,7 +585,7 @@ function getExtraValue (vm, dataPathsArray) { return context } -function processEventExtra (vm, extra) { +function processEventExtra (vm, extra, event) { const extraObj = {}; if (Array.isArray(extra) && extra.length) { @@ -517,7 +605,13 @@ function processEventExtra (vm, extra) { if (!dataPath) { // model,prop.sync extraObj['$' + index] = vm; } else { - extraObj['$' + index] = vm.__get_value(dataPath); + if (dataPath === '$event') { // $event + extraObj['$' + index] = event; + } else if (dataPath.indexOf('$event.') === 0) { // $event.target.value + extraObj['$' + index] = vm.__get_value(dataPath.replace('$event.', ''), event); + } else { + extraObj['$' + index] = vm.__get_value(dataPath); + } } } else { extraObj['$' + index] = getExtraValue(vm, dataPath); @@ -528,6 +622,15 @@ function processEventExtra (vm, extra) { return extraObj } +function getObjByArray (arr) { + const obj = {}; + for (let i = 1; i < arr.length; i++) { + const element = arr[i]; + obj[element[0]] = element[1]; + } + return obj +} + function processEventArgs (vm, event, args = [], extra = [], isCustom, methodName) { let isCustomMPEvent = false; // wxcomponent 组件,传递原始 event 对象 if (isCustom) { // 自定义事件 @@ -542,7 +645,7 @@ function processEventArgs (vm, event, args = [], extra = [], isCustom, methodNam } } - const extraObj = processEventExtra(vm, extra); + const extraObj = processEventExtra(vm, extra, event); const ret = []; args.forEach(arg => { @@ -557,7 +660,9 @@ function processEventArgs (vm, event, args = [], extra = [], isCustom, methodNam } } } else { - if (typeof arg === 'string' && hasOwn(extraObj, arg)) { + if (Array.isArray(arg) && arg[0] === 'o') { + ret.push(getObjByArray(arg)); + } else if (typeof arg === 'string' && hasOwn(extraObj, arg)) { ret.push(extraObj[arg]); } else { ret.push(arg); @@ -685,7 +790,7 @@ function createApp (vm) { { // 头条的 selectComponent 竟然是异步的 initRefs(this); } - initMocks(this); + initMocks(this, mocks); } }, created () { // 处理 injections @@ -720,28 +825,6 @@ function createApp (vm) { return vm } -function triggerLink (mpInstance, vueOptions) { - mpInstance.triggerEvent('__l', mpInstance.$vm || vueOptions, { - bubbles: true, - composed: true - }); -} - -function handleLink (event) { - if (event.detail.$mp) { // vm - if (!event.detail.$parent) { - event.detail.$parent = this.$vm; - event.detail.$parent.$children.push(event.detail); - - event.detail.$root = this.$vm.$root; - } - } else { // vueOptions - if (!event.detail.parent) { - event.detail.parent = this.$vm; - } - } -} - const hooks$1 = [ 'onShow', 'onHide', @@ -850,7 +933,9 @@ function initVm$2 (VueComponent) { function createComponent (vueOptions) { vueOptions = vueOptions.default || vueOptions; - const properties = getProperties(vueOptions.props); + const behaviors = getBehaviors(vueOptions); + + const properties = getProperties(vueOptions.props, false, vueOptions.__file); const VueComponent = Vue.extend(vueOptions); @@ -860,6 +945,7 @@ function createComponent (vueOptions) { addGlobalClass: true }, data: getData(vueOptions, Vue.prototype), + behaviors, properties, lifetimes: { attached () { diff --git a/packages/uni-mp-weixin/package.json b/packages/uni-mp-weixin/package.json index a96c2835da36f73ec571eff3ed4c591a92c3b4e2..4f2cdbc02063224bc661043e94556b56e3114339 100644 --- a/packages/uni-mp-weixin/package.json +++ b/packages/uni-mp-weixin/package.json @@ -1,6 +1,6 @@ { "name": "@dcloudio/uni-mp-weixin", - "version": "0.0.937", + "version": "0.0.947", "description": "uni-app mp-weixin", "main": "dist/index.js", "scripts": { diff --git a/src/core/helpers/hidpi.js b/src/core/helpers/hidpi.js new file mode 100644 index 0000000000000000000000000000000000000000..e3bb8e856570b17b411ec315a74c4f2dd8bbd464 --- /dev/null +++ b/src/core/helpers/hidpi.js @@ -0,0 +1,168 @@ +const pixelRatio = (function () { + const canvas = document.createElement('canvas') + const context = canvas.getContext('2d') + const backingStore = context.backingStorePixelRatio || + context.webkitBackingStorePixelRatio || + context.mozBackingStorePixelRatio || + context.msBackingStorePixelRatio || + context.oBackingStorePixelRatio || + context.backingStorePixelRatio || 1 + return (window.devicePixelRatio || 1) / backingStore +})() + +const forEach = function (obj, func) { + for (let key in obj) { + if (obj.hasOwnProperty(key)) { + func(obj[key], key) + } + } +} +const ratioArgs = { + 'fillRect': 'all', + 'clearRect': 'all', + 'strokeRect': 'all', + 'moveTo': 'all', + 'lineTo': 'all', + 'arc': [0, 1, 2], + 'arcTo': 'all', + 'bezierCurveTo': 'all', + 'isPointinPath': 'all', + 'isPointinStroke': 'all', + 'quadraticCurveTo': 'all', + 'rect': 'all', + 'translate': 'all', + 'createRadialGradient': 'all', + 'createLinearGradient': 'all', + 'setTransform': [4, 5] +} +if (pixelRatio !== 1) { + const proto = CanvasRenderingContext2D.prototype + + forEach(ratioArgs, function (value, key) { + proto[key] = (function (_super) { + return function () { + if (this.__ignore__) { + return _super.apply(this, arguments) + } + + let args = Array.prototype.slice.call(arguments) + + if (value === 'all') { + args = args.map(function (a) { + return a * pixelRatio + }) + } else if (Array.isArray(value)) { + for (let i = 0; i < value.length; i++) { + args[value[i]] *= pixelRatio + } + } + return _super.apply(this, args) + } + })(proto[key]) + }) + + proto.stroke = (function (_super) { + return function () { + if (this.__ignore__) { + return _super.apply(this, arguments) + } + this.lineWidth *= pixelRatio + _super.apply(this, arguments) + this.lineWidth /= pixelRatio + } + })(proto.stroke) + + proto.fillText = (function (_super) { + return function () { + if (this.__ignore__) { + return _super.apply(this, arguments) + } + const args = Array.prototype.slice.call(arguments) + + args[1] *= pixelRatio + args[2] *= pixelRatio + + this.font = this.font.replace( + /(\d+)(px|em|rem|pt)/g, + function (w, m, u) { + return (m * pixelRatio) + u + } + ) + + _super.apply(this, args) + + this.font = this.font.replace( + /(\d+)(px|em|rem|pt)/g, + function (w, m, u) { + return (m / pixelRatio) + u + } + ) + } + })(proto.fillText) + + proto.strokeText = (function (_super) { + return function () { + if (this.__ignore__) { + return _super.apply(this, arguments) + } + var args = Array.prototype.slice.call(arguments) + + args[1] *= pixelRatio // x + args[2] *= pixelRatio // y + + this.font = this.font.replace( + /(\d+)(px|em|rem|pt)/g, + function (w, m, u) { + return (m * pixelRatio) + u + } + ) + + _super.apply(this, args) + + this.font = this.font.replace( + /(\d+)(px|em|rem|pt)/g, + function (w, m, u) { + return (m / pixelRatio) + u + } + ) + } + })(proto.strokeText) + + proto.drawImageByCanvas = (function (_super) { + return function (canvas, srcx, srcy, srcw, srch, desx, desy, desw, desh, isScale) { + if (this.__ignore__) { + return _super.apply(this, arguments) + } + srcx *= pixelRatio + srcy *= pixelRatio + srcw *= pixelRatio + srch *= pixelRatio + desx *= pixelRatio + desy *= pixelRatio + desw = isScale ? desw * pixelRatio : desw + desh = isScale ? desh * pixelRatio : desh + _super.call(this, canvas, srcx, srcy, srcw, srch, desx, desy, desw, desh) + } + })(proto.drawImage) + + proto.drawImage = (function (_super) { + return function () { + if (this.__ignore__) { + return _super.apply(this, arguments) + } + this.scale(pixelRatio, pixelRatio) + _super.apply(this, arguments) + this.scale(1 / pixelRatio, 1 / pixelRatio) + } + })(proto.drawImage) +} + +export function wrapper (canvas) { + canvas.style.height = canvas.height + 'px' + canvas.style.width = canvas.width + 'px' + canvas.width *= pixelRatio + canvas.height *= pixelRatio + + console.log(canvas.width) + console.log(canvas.height) +} diff --git a/src/core/runtime/wrapper.js b/src/core/runtime/wrapper.js index 8b33a4be53d48943dac58e9257c56aa13c7e48cc..f6a3e7e214fbccadaf75e147c9c625b5c80374b5 100644 --- a/src/core/runtime/wrapper.js +++ b/src/core/runtime/wrapper.js @@ -39,7 +39,7 @@ function processArgs (methodName, fromArgs, argsOption = {}, returnValue = {}, k } else if (isPlainObject(keyOption)) { // {name:newName,value:value}可重新指定参数 key:value toArgs[keyOption.name ? keyOption.name : key] = keyOption.value } - } else if (CALLBACKS.includes(key)) { + } else if (CALLBACKS.indexOf(key) !== -1) { toArgs[key] = processCallback(methodName, fromArgs[key], returnValue) } else { if (!keepFromArgs) { diff --git a/src/core/runtime/wrapper/create-app.js b/src/core/runtime/wrapper/create-app.js index f95b39ac12337033d35ed67dc021a2090c86b919..667b70863d8ed3df6f276222390056055028e4fc 100644 --- a/src/core/runtime/wrapper/create-app.js +++ b/src/core/runtime/wrapper/create-app.js @@ -1,6 +1,10 @@ import 'uni-platform/runtime/index' import Vue from 'vue' + +import { + mocks +} from 'uni-platform/runtime/wrapper/index' import { initRefs, @@ -51,7 +55,7 @@ export function createApp (vm) { if (__PLATFORM__ !== 'mp-toutiao') { // 头条的 selectComponent 竟然是异步的 initRefs(this) } - initMocks(this) + initMocks(this, mocks) } }, created () { // 处理 injections diff --git a/src/core/runtime/wrapper/create-component.js b/src/core/runtime/wrapper/create-component.js index e7ff868e6a4b709d1fac0875733ab699f72ace07..f987c017784c4fe2ad652bb8e7c98e179246415b 100644 --- a/src/core/runtime/wrapper/create-component.js +++ b/src/core/runtime/wrapper/create-component.js @@ -9,6 +9,7 @@ import { import { getData, handleEvent, + getBehaviors, getProperties } from './util' @@ -42,7 +43,9 @@ function initVm (VueComponent) { export function createComponent (vueOptions) { vueOptions = vueOptions.default || vueOptions - const properties = getProperties(vueOptions.props) + const behaviors = getBehaviors(vueOptions) + + const properties = getProperties(vueOptions.props, false, vueOptions.__file) const VueComponent = Vue.extend(vueOptions) @@ -52,6 +55,7 @@ export function createComponent (vueOptions) { addGlobalClass: true }, data: getData(vueOptions, Vue.prototype), + behaviors, properties, lifetimes: { attached () { diff --git a/src/core/runtime/wrapper/util.js b/src/core/runtime/wrapper/util.js index 8034a59224e10326c23d2c11a0c566b908a6bc7b..e08c4cf68cf3fd3c8d40bf4e1abc000ae1ce5444 100644 --- a/src/core/runtime/wrapper/util.js +++ b/src/core/runtime/wrapper/util.js @@ -5,11 +5,9 @@ import { isPlainObject } from 'uni-shared' -const MOCKS = ['__route__', '__wxExparserNodeId__', '__wxWebviewId__', '__webviewId__'] - -export function initMocks (vm) { +export function initMocks (vm, mocks) { const mpInstance = vm.$mp[vm.mpType] - MOCKS.forEach(mock => { + mocks.forEach(mock => { if (hasOwn(mpInstance, mock)) { vm[mock] = mpInstance[mock] } @@ -43,6 +41,10 @@ export function getData (vueOptions, context) { } catch (e) {} } + if (!isPlainObject(data)) { + data = {} + } + Object.keys(methods).forEach(methodName => { if (context.__lifecycle_hooks__.indexOf(methodName) === -1 && !hasOwn(data, methodName)) { data[methodName] = methods[methodName] @@ -62,9 +64,81 @@ function createObserver (name) { } } -export function getProperties (props) { - const properties = { - vueSlots: { // 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots +export function getBehaviors (vueOptions) { + const vueBehaviors = vueOptions['behaviors'] + const vueExtends = vueOptions['extends'] + const vueMixins = vueOptions['mixins'] + + let vueProps = vueOptions['props'] + + if (!vueProps) { + vueOptions['props'] = vueProps = [] + } + + const behaviors = [] + if (Array.isArray(vueBehaviors)) { + vueBehaviors.forEach(behavior => { + behaviors.push(behavior.replace('uni://', `${__PLATFORM_PREFIX__}://`)) + if (behavior === 'uni://form-field') { + if (Array.isArray(vueProps)) { + vueProps.push('name') + vueProps.push('value') + } else { + vueProps['name'] = String + vueProps['value'] = null + } + } + }) + } + if (isPlainObject(vueExtends) && vueExtends.props) { + behaviors.push( + Behavior({ + properties: getProperties(vueExtends.props, true) + }) + ) + } + if (Array.isArray(vueMixins)) { + vueMixins.forEach(vueMixin => { + if (isPlainObject(vueMixin) && vueMixin.props) { + behaviors.push( + Behavior({ + properties: getProperties(vueMixin.props, true) + }) + ) + } + }) + } + return behaviors +} + +function parsePropType (key, type, defaultValue, file) { + // [String]=>String + if (Array.isArray(type) && type.length === 1) { + return type[0] + } + if (__PLATFORM__ === 'mp-baidu') { + if ( + defaultValue === false && + Array.isArray(type) && + type.length === 2 && + type.indexOf(String) !== -1 && + type.indexOf(Boolean) !== -1 + ) { // [String,Boolean]=>Boolean + if (file) { + console.warn( + `props.${key}.type should use Boolean instead of [String,Boolean] at ${file}` + ) + } + return Boolean + } + } + return type +} + +export function getProperties (props, isBehavior = false, file = '') { + const properties = {} + if (!isBehavior) { + properties.vueSlots = { // 小程序不能直接定义 $slots 的 props,所以通过 vueSlots 转换到 $slots type: null, value: [], observer: function (newVal, oldVal) { @@ -93,14 +167,18 @@ export function getProperties (props) { if (isFn(value)) { value = value() } + + opts.type = parsePropType(key, opts.type, value, file) + properties[key] = { - type: PROP_TYPES.includes(opts.type) ? opts.type : null, + type: PROP_TYPES.indexOf(opts.type) !== -1 ? opts.type : null, value, observer: createObserver(key) } } else { // content:String + const type = parsePropType(key, opts, null, file) properties[key] = { - type: PROP_TYPES.includes(opts) ? opts : null, + type: PROP_TYPES.indexOf(type) !== -1 ? type : null, observer: createObserver(key) } } @@ -110,6 +188,11 @@ export function getProperties (props) { } function wrapper (event) { + // TODO 又得兼容 mpvue 的 mp 对象 + try { + event.mp = JSON.parse(JSON.stringify(event)) + } catch (e) {} + event.stopPropagation = noop event.preventDefault = noop @@ -129,9 +212,6 @@ function wrapper (event) { } } - // TODO 又得兼容 mpvue 的 mp 对象 - event.mp = event - if (isPlainObject(event.detail)) { event.target = Object.assign({}, event.target, event.detail) } @@ -176,7 +256,7 @@ function getExtraValue (vm, dataPathsArray) { return context } -function processEventExtra (vm, extra) { +function processEventExtra (vm, extra, event) { const extraObj = {} if (Array.isArray(extra) && extra.length) { @@ -196,7 +276,13 @@ function processEventExtra (vm, extra) { if (!dataPath) { // model,prop.sync extraObj['$' + index] = vm } else { - extraObj['$' + index] = vm.__get_value(dataPath) + if (dataPath === '$event') { // $event + extraObj['$' + index] = event + } else if (dataPath.indexOf('$event.') === 0) { // $event.target.value + extraObj['$' + index] = vm.__get_value(dataPath.replace('$event.', ''), event) + } else { + extraObj['$' + index] = vm.__get_value(dataPath) + } } } else { extraObj['$' + index] = getExtraValue(vm, dataPath) @@ -207,6 +293,15 @@ function processEventExtra (vm, extra) { return extraObj } +function getObjByArray (arr) { + const obj = {} + for (let i = 1; i < arr.length; i++) { + const element = arr[i] + obj[element[0]] = element[1] + } + return obj +} + function processEventArgs (vm, event, args = [], extra = [], isCustom, methodName) { let isCustomMPEvent = false // wxcomponent 组件,传递原始 event 对象 if (isCustom) { // 自定义事件 @@ -221,7 +316,7 @@ function processEventArgs (vm, event, args = [], extra = [], isCustom, methodNam } } - const extraObj = processEventExtra(vm, extra) + const extraObj = processEventExtra(vm, extra, event) const ret = [] args.forEach(arg => { @@ -236,7 +331,9 @@ function processEventArgs (vm, event, args = [], extra = [], isCustom, methodNam } } } else { - if (typeof arg === 'string' && hasOwn(extraObj, arg)) { + if (Array.isArray(arg) && arg[0] === 'o') { + ret.push(getObjByArray(arg)) + } else if (typeof arg === 'string' && hasOwn(extraObj, arg)) { ret.push(extraObj[arg]) } else { ret.push(arg) diff --git a/src/core/service/api/context/canvas.js b/src/core/service/api/context/canvas.js index c3ea0826a03ffe55ef053788e6e1aeb9b8aec7df..3aeb1da19f73b0f5890dbf5e66d2fba80b03cdff 100644 --- a/src/core/service/api/context/canvas.js +++ b/src/core/service/api/context/canvas.js @@ -1,4 +1,5 @@ import createCallbacks from 'uni-helpers/callbacks' +import { wrapper } from 'uni-helpers/hidpi' const canvasEventCallbacks = createCallbacks('canvasEvent') @@ -257,6 +258,7 @@ var tempCanvas function getTempCanvas () { if (!tempCanvas) { tempCanvas = document.createElement('canvas') + wrapper(tempCanvas) } return tempCanvas } @@ -816,7 +818,7 @@ export function canvasToTempFilePath ({ pageId = app.$route.params.__id__ } else { invoke(callbackId, { - errMsg: 'canvasPutImageData:fail' + errMsg: 'canvasToTempFilePath:fail' }) return } @@ -840,20 +842,24 @@ export function canvasToTempFilePath ({ canvas.width = data.width canvas.height = data.height var c2d = canvas.getContext('2d') - c2d.putImageData(imgData, 0, 0) - var base64 = canvas.toDataURL('image/png') - var img = new Image() - img.onload = function () { - canvas.width = destWidth || imgData.width - canvas.height = destHeight || imgData.height - c2d.drawImage(img, 0, 0) - base64 = canvas.toDataURL(`image/${fileType.toLowerCase()}`, qualit) - invoke(callbackId, { - errMsg: 'canvasToTempFilePath:ok', - tempFilePath: base64 - }) - } - img.src = base64 + c2d.putImageData(imgData, 0, 0, 0, 0, destWidth || imgData.width, destHeight || imgData.height) + var base64 = canvas.toDataURL(`image/${fileType.toLowerCase()}`, qualit) + invoke(callbackId, { + errMsg: 'canvasToTempFilePath:ok', + tempFilePath: base64 + }) + // var img = new Image() + // img.onload = function () { + // canvas.width = destWidth || imgData.width + // canvas.height = destHeight || imgData.height + // c2d.drawImage(img, 0, 0) + // base64 = canvas.toDataURL(`image/${fileType.toLowerCase()}`, qualit) + // invoke(callbackId, { + // errMsg: 'canvasToTempFilePath:ok', + // tempFilePath: base64 + // }) + // } + // img.src = base64 }) operateCanvas(canvasId, pageId, 'getImageData', { x, diff --git a/src/core/service/plugins/app/router-guard.js b/src/core/service/plugins/app/router-guard.js index a0e36bee15774d7dd1e7730f6153a0f9a8d4fc41..9eb4223600dd3a7acd2c2ba541416c81bd2e9751 100644 --- a/src/core/service/plugins/app/router-guard.js +++ b/src/core/service/plugins/app/router-guard.js @@ -15,7 +15,28 @@ function removeKeepAliveInclude (componentName) { } } -function switchTab (routes) { +let positionStore = Object.create(null) + +export function getTabBarScrollPosition (id) { + return positionStore[id] +} + +function saveTabBarScrollPosition (id) { + positionStore[id] = { + x: window.pageXOffset, + y: window.pageYOffset + } +} + +function switchTab (routes, to, from) { + if ( + to && + from && + to.meta.isTabBar && + from.meta.isTabBar + ) { // tabbar 跳 tabbar + saveTabBarScrollPosition(from.params.__id__) + } // 关闭非 tabBar 页面 const pages = getCurrentPages() for (let i = pages.length - 1; i >= 0; i--) { @@ -33,11 +54,13 @@ function reLaunch (toName) { // 关闭所有页面 const pages = getCurrentPages(true) for (let i = pages.length - 1; i >= 0; i--) { - callPageHook(pages[i], 'onUnload') - // 重新reLaunch至首页可能会被keepAlive,先手动强制destroy + callPageHook(pages[i], 'onUnload') + // 重新reLaunch至首页可能会被keepAlive,先手动强制destroy pages[i].$destroy() } - this.keepAliveInclude = [] + this.keepAliveInclude = [] + // 清空 positionStore + positionStore = Object.create(null) } let currentPages = [] @@ -65,7 +88,7 @@ function beforeEach (to, from, next, routes) { removeKeepAliveInclude.call(this, fromName) if (from.meta) { if (from.meta.isQuit) { // 如果 redirectTo 的前一个页面是 quit 类型,则新打开的页面也是 quit - to.meta.isQuit = true + to.meta.isQuit = true to.meta.isEntry = !!from.meta.isEntry } if (from.meta.isTabBar) { // 如果是 tabBar,需要更新系统组件 tabBar 内的 list 数据 @@ -78,7 +101,7 @@ function beforeEach (to, from, next, routes) { break case 'switchTab': - switchTab.call(this, routes) + switchTab.call(this, routes, to, from) break case 'reLaunch': reLaunch.call(this, toName) diff --git a/src/core/service/plugins/index.js b/src/core/service/plugins/index.js index 1f2c8262ad5791c2ddd13d42bd8793a6497277f2..b961a5587fa1107a07323cb6c995bc8a3dd50e88 100644 --- a/src/core/service/plugins/index.js +++ b/src/core/service/plugins/index.js @@ -16,6 +16,10 @@ import { createPageMixin } from './page' +import { + getTabBarScrollPosition +} from './app/router-guard' + function getMinId (routes) { let minId = 0 routes.forEach(route => { @@ -62,6 +66,17 @@ export default { if (savedPosition) { return savedPosition } else { + if ( + to && + from && + to.meta.isTabBar && + from.meta.isTabBar + ) { // tabbar 跳 tabbar + const position = getTabBarScrollPosition(to.params.__id__) + if (position) { + return position + } + } return { x: 0, y: 0 @@ -101,7 +116,9 @@ export default { const appMixin = createAppMixin(routes, entryRoute) // mixin app hooks Object.keys(appMixin).forEach(hook => { - options[hook] = options[hook] ? [].concat(appMixin[hook], options[hook]) : [appMixin[hook]] + options[hook] = options[hook] ? [].concat(appMixin[hook], options[hook]) : [ + appMixin[hook] + ] }) // router @@ -117,7 +134,9 @@ export default { const pageMixin = createPageMixin() // mixin page hooks Object.keys(pageMixin).forEach(hook => { - options[hook] = options[hook] ? [].concat(pageMixin[hook], options[hook]) : [pageMixin[hook]] + options[hook] = options[hook] ? [].concat(pageMixin[hook], options[hook]) : [ + pageMixin[hook] + ] }) } else { if (this.$parent && this.$parent.__page__) { diff --git a/src/core/view/components/canvas/index.vue b/src/core/view/components/canvas/index.vue index ec7db333be67b91d02488182481e7a92e0e840eb..11abd6f33162d051788e499563de169bcf9e1c67 100644 --- a/src/core/view/components/canvas/index.vue +++ b/src/core/view/components/canvas/index.vue @@ -1,450 +1,462 @@ - - - + + + diff --git a/src/core/view/components/scroll-view/index.vue b/src/core/view/components/scroll-view/index.vue index 632321f584686a883955d9200688a7c80f4f82e5..b60017cde6d584cca96c2b41beee5c7147dc751e 100644 --- a/src/core/view/components/scroll-view/index.vue +++ b/src/core/view/components/scroll-view/index.vue @@ -166,6 +166,11 @@ export default { passive: false } : false) }, + activated () { + // 还原 scroll-view 滚动位置 + this.scrollY && (this.$refs.main.scrollTop = this.lastScrollTop) + this.scrollX && (this.$refs.main.scrollLeft = this.lastScrollLeft) + }, beforeDestroy () { this.$refs.main.removeEventListener('touchstart', this.__handleTouchStart, passiveOptions) this.$refs.main.removeEventListener('touchmove', this.__handleTouchMove, passiveOptions) diff --git a/src/core/view/plugins/behaviors/form-field.js b/src/core/view/plugins/behaviors/form-field.js new file mode 100644 index 0000000000000000000000000000000000000000..56548ab864dcddaaf2930fd2ea7a77e6ce198112 --- /dev/null +++ b/src/core/view/plugins/behaviors/form-field.js @@ -0,0 +1,103 @@ +/** + * uni://form-field + */ +import { + hasOwn +} from 'uni-shared' + +import { + emitter +} from 'uni-mixins' + +function created () { + this.$dispatch('Form', 'uni-form-group-update', { + type: 'add', + vm: this + }) +} + +function beforeDestroy () { + this.$dispatch('Form', 'uni-form-group-update', { + type: 'remove', + vm: this + }) +} + +export default { + name: 'uni://form-field', + init (options, vm) { + if ( + !vm.constructor.options.props.name || + !vm.constructor.options.props.value + ) { // 未初始化 props + if (!vm.constructor.options.props.name) { + vm.constructor.options.props.name = options.props.name = { + type: String + } + } + if (!vm.constructor.options.props.value) { + vm.constructor.options.props.value = options.props.value = { + type: null + } + } + } + + if (!options.propsData) { + options.propsData = {} + } + + const $vnode = vm.$vnode + if ($vnode && $vnode.data && $vnode.data.attrs) { + if (hasOwn($vnode.data.attrs, 'name')) { + options.propsData.name = $vnode.data.attrs.name + } + if (hasOwn($vnode.data.attrs, 'value')) { + options.propsData.value = $vnode.data.attrs.value + } + } + + if ( + !vm.constructor.options.methods || + !vm.constructor.options.methods._getFormData + ) { // 未初始化 methods + if (!vm.constructor.options.methods) { + vm.constructor.options.methods = {} + } + + if (!options.methods) { + options.methods = {} + } + + const formMethods = { + _getFormData () { + return this.name ? { + key: this.name, + value: this.value + } : {} + }, + _resetFormData () { + this.value = '' + } + } + + Object.assign(vm.constructor.options.methods, formMethods) + Object.assign(options.methods, formMethods) + + // add $dispatch + Object.assign(vm.constructor.options.methods, emitter.methods) + Object.assign(options.methods, emitter.methods) + + const createdHooks = options['created'] + vm.constructor.options['created'] = options['created'] = + createdHooks ? [].concat(created, createdHooks) : [ + created + ] + + const beforeDestroyHooks = options['beforeDestroy'] + vm.constructor.options['beforeDestroy'] = options['beforeDestroy'] = + beforeDestroyHooks ? [].concat(beforeDestroy, beforeDestroyHooks) : [ + beforeDestroy + ] + } + } +} diff --git a/src/core/view/plugins/behaviors/index.js b/src/core/view/plugins/behaviors/index.js new file mode 100644 index 0000000000000000000000000000000000000000..fa1a4265f3747b059e02cda71b161607b7886117 --- /dev/null +++ b/src/core/view/plugins/behaviors/index.js @@ -0,0 +1,12 @@ +import formField from './form-field' + +const behaviors = { + [formField.name]: formField +} + +export default function initBehaviors (options, vm) { + options.behaviors.forEach(name => { + const behavior = behaviors[name] + behavior && behavior.init(options, vm) + }) +} diff --git a/src/core/view/plugins/index.js b/src/core/view/plugins/index.js index 643d1f08d4fe8455f1dcb19fc1a8a0795ef3f0c6..f0e0dccf0c210be9d3fe109fc77a880181ecbb25 100644 --- a/src/core/view/plugins/index.js +++ b/src/core/view/plugins/index.js @@ -7,6 +7,8 @@ import { processEvent } from './events' +import initBehaviors from './behaviors' + function pageMounted () { // 通知 Service,View 层已 ready UniViewJSBridge.publishHandler('onPageReady', {}, this.$page.id) @@ -34,13 +36,18 @@ export default { } } $event = processEvent.call(this, $event.type, $event, {}, target || $event.target, $event.currentTarget) - } + } return $event } Vue.mixin({ beforeCreate () { const options = this.$options + + if (options.behaviors && options.behaviors.length) { + initBehaviors(options, this) + } + if (isPage(this)) { options.mounted = options.mounted ? [].concat(pageMounted, options.mounted) : [pageMounted] } diff --git a/src/platforms/app-plus/runtime/wrapper/index.js b/src/platforms/app-plus/runtime/wrapper/index.js index d5dceb800d3c45a9e56e321a4761d98557b19c33..2628b2b95222c1eed2e626f8a6561e5ec6d292da 100644 --- a/src/platforms/app-plus/runtime/wrapper/index.js +++ b/src/platforms/app-plus/runtime/wrapper/index.js @@ -1,4 +1,5 @@ export { + mocks, handleLink, triggerLink } diff --git a/src/platforms/h5/components/app/popup/modal.vue b/src/platforms/h5/components/app/popup/modal.vue index bbed8644ea84b86e71eb779bbe066a21a4705f1f..2404da5b9f88dd55e76fdd5716f90f3db66870b6 100644 --- a/src/platforms/h5/components/app/popup/modal.vue +++ b/src/platforms/h5/components/app/popup/modal.vue @@ -3,9 +3,7 @@ -
+