提交 e5aaebed 编写于 作者: Q qiang

Merge branch 'dev' into alpha

......@@ -25,6 +25,10 @@ const TOAST_DEPS = [
// TODO 暂不考虑 head,tabBar 的动态拆分
const DEPS = {
chooseLocation: [
['/platforms/h5/view/components/map/index.vue', 'Map'],
['/core/view/components/input/index.vue', 'Input'],
['/core/view/components/scroll-view/index.vue', 'ScrollView'],
['/platforms/h5/service/api/location/get-location.js', 'getLocation'],
['/platforms/h5/components/system-routes/choose-location/index.vue', 'ChooseLocation']
],
openLocation: [
......
......@@ -114,6 +114,11 @@ function async request () {
|[uni.previewImage](api/media/image?id=previewimage)|预览图片|
|[uni.getImageInfo](api/media/image?id=getimageinfo)|获取图片信息|
|[uni.saveImageToPhotosAlbum](api/media/image?id=saveimagetophotosalbum)|保存图片到系统相册|
##### 文件
|API|说明|
|:-|:-|
|[uni.chooseFile](api/media/file?id=chooseFile)|从本地选择文件|
##### 录音管理
|API|说明|
......
......@@ -37,6 +37,7 @@
* [地图组件控制](api/location/map.md)
* 媒体
* [图片](api/media/image.md)
* [文件](api/media/file.md)
* [录音管理](api/media/record-manager.md)
* [背景音频播放管理](api/media/background-audio-manager.md)
* [音频组件控制](api/media/audio-context.md)
......
### uni.chooseFile(OBJECT)
从本地选择文件。
**平台差异说明**
|App|H5|微信小程序|支付宝小程序|百度小程序|字节跳动小程序|QQ小程序|
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|x|√`(HBuilder X2.9.8+)`|x`(可使用wx.chooseMessageFile)`|x|x|x|x|
**OBJECT 参数说明**
|参数名|类型|默认值|必填|说明|平台差异说明|
|:-|:-|:-|:-|:-|:-|
|count|Number|100|否|最多可以选择的图片张数|见下方说明|
|type|String|'all'|否|所选的文件的类型|见下方说明|
|extension|Array<String>||否|根据文件拓展名过滤,每一项都不能是空字符串。默认不过滤。|见下方说明|
|sourceType|Array<String>|['album','camera']|否|(仅在type为`image``video`时可用)`album` 从相册选图,`camera` 使用相机,默认二者都有。如需直接开相机或直接选相册,请只使用一个选项||
|success|Function||是|成功则返回图片的本地文件路径列表 `tempFilePaths`||
|fail|Function||否|接口调用失败的回调函数||
|complete|Function||否|接口调用结束的回调函数(调用成功、失败都会执行)| |
**Tips**
- count 值在 H5 平台的表现,基于浏览器本身的规范。目前测试的结果来看,只能限制单选/多选,并不能限制数量。并且,在实际的手机浏览器很少有能够支持多选的。
- sourceType 在H5端对应`input``capture`属性,设置为`['album']`无效,依然可以使用相机。
- App端如需选择非媒体文件,可在插件市场搜索[文件选择](https://ext.dcloud.net.cn/search?q=文件选择),其中Android端可以使用Native.js,无需原生插件,而iOS端需要原生插件。
- extension暂只支持文件后缀名,例如`['.zip','.exe','.js']`,不支持`application/msword`等类似值
**注:文件的临时路径,在应用本次启动期间可以正常使用,如需持久保存,需在主动调用 [uni.saveFile](api/file/file?id=savefile),在应用下次启动时才能访问得到。**
**OBJECT.type 的合法值**
|值|说明|
|:-|:-|
|all|从所有文件选择|
|video|只能选择视频文件|
|image|只能选择图片文件|
**Tips**
- 如果type属性可extension同时存在,例如`{type:'image',extension:['.png','.jpg']}`,则会选择`image/png,image/jpg`文件
- 如果只配置extension属性,例如`{extension:['.doc','.xlsx','.docx']}`,则会选择`.doc,.xlsx,.docx`文件
- 详情见[`accept`](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Attributes/accept)属性
**success 返回参数说明**
|参数|类型|说明|
|:-|:-|:-|
|tempFilePaths|Array<String>|图片的本地文件路径列表|
|tempFiles|Array<Object>、Array<File>|图片的本地文件列表,每一项是一个 File 对象|
**File 对象结构如下**
|参数|类型|说明|
|:-|:-|:-|
|path|String|本地文件路径|
|size|Number|本地文件大小,单位:B|
|name|String|包含扩展名的文件名称,仅H5支持|
|type|String|文件类型,仅H5支持|
**示例**
```javascript
uni.chooseFile({
count: 6, //默认100
extension:['.zip','.doc'],
success: function (res) {
console.log(JSON.stringify(res.tempFilePaths));
}
});
// 选择图片文件
uni.chooseFile({
count: 10,
type: 'image',
success (res) {
// tempFilePath可以作为img标签的src属性显示图片
const tempFilePaths = res.tempFiles
}
})
```
# wx.chooseMessageFile(OBJECT)
从微信聊天会话中选择文件。
**平台差异说明**
|App|H5|微信小程序|支付宝小程序|百度小程序|字节跳动小程序|QQ小程序|
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|x|x|√`(基础库2.5.0+)`|x|x|x|x|
......@@ -9,6 +9,7 @@ App端如需要更丰富的相机拍照API(如直接调用前置摄像头)
|:-|:-|:-|:-|:-|
|count|Number|否|最多可以选择的图片张数,默认9|见下方说明|
|sizeType|Array<String>|否|original 原图,compressed 压缩图,默认二者都有|App、微信小程序、支付宝小程序、百度小程序|
|extension|Array<String>|否|根据文件拓展名过滤,每一项都不能是空字符串。默认不过滤。|H5(HBuilder X2.9.8+)|
|sourceType|Array<String>|否|album 从相册选图,camera 使用相机,默认二者都有。如需直接开相机或直接选相册,请只使用一个选项||
|success|Function|是|成功则返回图片的本地文件路径列表 tempFilePaths||
|fail|Function|否|接口调用失败的回调函数|小程序、App|
......@@ -279,16 +280,4 @@ uni.compressImage({
console.log(res.tempFilePath)
}
})
```
# wx.chooseMessageFile(OBJECT)
从微信聊天会话中选择文件。
**平台差异说明**
|App|H5|微信小程序|支付宝小程序|百度小程序|字节跳动小程序|QQ小程序|
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|x|x|√|x|x|x|x|
```
\ No newline at end of file
......@@ -12,6 +12,7 @@
|参数名|类型|必填|说明|平台差异说明|
|:-|:-|:-|:-|:-|
|sourceType|Array<String>|否|album 从相册选视频,camera 使用相机拍摄,默认为:['album', 'camera']||
|extension|Array<String>|否|根据文件拓展名过滤,每一项都不能是空字符串。默认不过滤。|H5(HBuilder X2.9.8+)|
|compressed|Boolean|否|是否压缩所选的视频源文件,默认值为 true,需要压缩。|微信小程序、百度小程序、字节跳动小程序|
|maxDuration|Number|否|拍摄视频最长拍摄时间,单位秒。最长支持 60 秒。|APP平台 1.9.7+(iOS支持,Android取决于ROM的拍照组件是否实现此功能,如果没实现此功能则忽略此属性。) 微信小程序、百度小程序|
|camera|String|否|'front'、'back',默认'back'|APP、微信小程序|
......
......@@ -17,6 +17,7 @@
|filePath|String|是|要上传文件资源的路径。||
|name|String|是|文件对应的 key , 开发者在服务器端通过这个 key 可以获取到文件二进制内容||
|header|Object|否|HTTP 请求 Header, header 中不能设置 Referer。||
|timeout|Number|否|超时时间,单位 ms|H5(HBuilderX 2.9.8+)、APP(HBuilderX 2.9.8+)|
|formData|Object|否|HTTP 请求中其他额外的 form data||
|success|Function|否|接口调用成功的回调函数||
|fail|Function|否|接口调用失败的回调函数||
......@@ -27,7 +28,7 @@
- App支持多文件上传,微信小程序只支持单文件上传,传多个文件需要反复调用本API。所以跨端的写法就是循环调用本API。
- hello uni-app中的客服反馈,支持多图上传。[uni-app插件市场](https://ext.dcloud.net.cn/)中也有多个封装的组件。
- App平台选择和上传非图像、视频文件,参考[https://ask.dcloud.net.cn/article/35547](https://ask.dcloud.net.cn/article/35547)
- 网络请求的 ``超时时间`` 可以统一在 ``manifest.json`` 中配置 [networkTimeout](/collocation/manifest?id=networktimeout)
- 网络请求的 ``超时时间`` 可以统一在 ``manifest.json`` 中配置 [networkTimeout](/collocation/manifest?id=networktimeout)
- 支付宝小程序开发工具上传文件返回的http状态码为字符串形式,支付宝小程序真机返回的状态码为数字形式
**files参数说明**
......@@ -145,10 +146,11 @@ uni.chooseImage({
**OBJECT 参数说明**
|参数名|类型|必填|说明|
|:-|:-|:-|:-|
|参数名|类型|必填|说明|平台差异说明|
|:-|:-|:-|:-|:-|
|url|String|是|下载资源的 url|
|header|Object|否|HTTP 请求 Header, header 中不能设置 Referer。|
|timeout|Number|否|超时时间,单位 ms|H5(HBuilderX 2.9.8+)、APP(HBuilderX 2.9.8+)|
|success|Function|否|下载成功后以 tempFilePath 的形式传给页面,res = {tempFilePath: '文件的临时路径'}|
|fail|Function|否|接口调用失败的回调函数|
|complete|Function|否|接口调用结束的回调函数(调用成功、失败都会执行)|
......
......@@ -11,7 +11,7 @@
|data|Object/String/ArrayBuffer|否||请求的参数|App(自定义组件编译模式)不支持ArrayBuffer类型|
|header|Object|否||设置请求的 header,header 中不能设置 Referer。|H5端会自动带上cookie不可手动覆盖|
|method|String|否|GET|有效值详见下方说明||
|timeout|Number|否|30000|超时时间,单位 ms|微信小程序(2.10.0)、支付宝小程序|
|timeout|Number|否|30000|超时时间,单位 ms|H5(HBuilderX 2.9.8+)、APP(HBuilderX 2.9.8+)、微信小程序(2.10.0)、支付宝小程序|
|dataType|String|否|json |如果设为 json,会尝试对返回的数据做一次 JSON.parse||
|responseType|String|否|text |设置响应的数据类型。合法值:text、arraybuffer|App和支付宝小程序不支持|
|sslVerify|Boolean|否|true|验证 ssl 证书|仅App安卓端支持(HBuilderX 2.3.3+)|
......
......@@ -16,7 +16,7 @@
|maxlength|Number|140|最大输入长度,设置为 -1 的时候不限制最大长度||
|cursor-spacing|Number|0|指定光标与键盘的距离,单位 px 。取 input 距离底部的距离和 cursor-spacing 指定的距离的最小值作为光标与键盘的距离|App、微信小程序、百度小程序、QQ小程序|
|focus|Boolean|false|获取焦点。|在 H5 平台能否聚焦以及软键盘是否跟随弹出,取决于当前浏览器本身的实现。|
|confirm-type|String|done|设置键盘右下角按钮的文字,仅在 type="text" 时生效。||
|confirm-type|String|done|设置键盘右下角按钮的文字,仅在 type="text" 时生效。|微信小程序基础库2.13.0、APP(HBuilder X2.9.8+)、H5(HBuilder X2.9.8+)|
|confirm-hold|Boolean|false|点击键盘右下角按钮时是否保持键盘不收起|App、微信小程序、支付宝小程序、百度小程序、QQ小程序|
|cursor|Number||指定focus时的光标位置||
|selection-start|Number|-1|光标起始位置,自动聚集时有效,需与selection-end搭配使用||
......@@ -68,6 +68,7 @@
- App平台的nvue页面,如果是uni-app编译模式,直接使用此属性设置即可生效。如果是weex编译模式,需通过weex的api设置,[weex相关文档参考](https://weex.apache.org/zh/docs/components/input.html#%E5%B1%9E%E6%80%A7)
- App平台的vue页面不支持控制键盘右下角为“发送”,涉及聊天的建议使用nvue。
- confirm-type属性仅在Chrome 77+、IOS 13.4+、Android 5-6.x WebView: Chromium 81+支持。
#### App平台iOS端软键盘上方横条去除方案
......
......@@ -17,6 +17,7 @@
|fixed|Boolean|false|如果 textarea 是在一个 position:fixed 的区域,需要显示指定属性 fixed 为 true|微信小程序、百度小程序、字节跳动小程序、QQ小程序|
|cursor-spacing|Number|0|指定光标与键盘的距离,单位 px 。取 textarea 距离底部的距离和 cursor-spacing 指定的距离的最小值作为光标与键盘的距离|App、微信小程序、百度小程序、字节跳动小程序、QQ小程序|
|cursor|Number||指定focus时的光标位置|微信小程序、App、H5、百度小程序、字节跳动小程序、QQ小程序|
|confirm-type|String|done|设置键盘右下角按钮的文字,仅在 type="text" 时生效。|微信小程序基础库2.13.0、APP(HBuilder X2.9.8+)、H5(HBuilder X2.9.8+)|
|show-confirm-bar|Boolean|true|是否显示键盘上方带有”完成“按钮那一栏|微信小程序、百度小程序、QQ小程序|
|selection-start|Number|-1|光标起始位置,自动聚焦时有效,需与selection-end搭配使用|微信小程序、App、H5、百度小程序、字节跳动小程序、QQ小程序|
|selection-end|Number|-1|光标结束位置,自动聚焦时有效,需与selection-start搭配使用|微信小程序、App、H5、百度小程序、字节跳动小程序、QQ小程序|
......@@ -28,7 +29,19 @@
|@linechange|EventHandle||输入框行数变化时调用,event.detail = {height: 0, heightRpx: 0, lineCount: 0}|字节跳动小程序不支持,nvue ios暂不支持|
|@input|EventHandle||当键盘输入时,触发 input 事件,event.detail = {value, cursor}, @input 处理函数的返回值并不会反映到 textarea 上||
|@confirm|EventHandle||点击完成时, 触发 confirm 事件,event.detail = {value: value}|微信小程序、百度小程序、QQ小程序|
|@keyboardheightchange|eventhandle||键盘高度发生变化的时候触发此事件,event.detail = {height: height, duration: duration}|微信小程序2.7.0|
|@keyboardheightchange|Eventhandle||键盘高度发生变化的时候触发此事件,event.detail = {height: height, duration: duration}|微信小程序2.7.0|
**confirm-type 有效值**
|值|说明|平台差异说明|
|:-|:-|-|
|send|右下角按钮为“发送”|微信、支付宝、百度小程序、App的nvue|
|search|右下角按钮为“搜索”||
|next|右下角按钮为“下一个”|微信、支付宝、百度小程序、App的nvue|
|go|右下角按钮为“前往”||
|done|右下角按钮为“完成”|微信、支付宝、百度小程序、App的nvue|
**示例** [查看示例](https://hellouniapp.dcloud.net.cn/pages/component/textarea/textarea)
......@@ -74,6 +87,7 @@ export default {
- 如果遇到 focus 属性设置不生效的问题参考:[组件属性设置不生效解决办法](/use?id=%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98)
- 软键盘的弹出和收起逻辑,详见[input的文档](/component/input?id=app%E5%B9%B3%E5%8F%B0ios%E7%AB%AF%E8%BD%AF%E9%94%AE%E7%9B%98%E4%B8%8A%E6%96%B9%E6%A8%AA%E6%9D%A1%E5%8E%BB%E9%99%A4%E6%96%B9%E6%A1%88)
- 如需禁止点击其他位置收起键盘的默认行为,可以监听`touch`事件并使用`prevent`修饰符(仅支持App-v3、H5,其他平台可以通过设置`focus`来使输入框重新获取焦点),例如在确认按钮上使用:```@touchend.prevent="onTap"```
- confirm-type属性仅在Chrome 77+、IOS 13.4+、Android 5-6.x WebView: Chromium 81+支持。
**富文本编辑的解决方案**
在输入框里图文混排内容,在web上该功能依赖document,而小程序和app的正常页面又没有document。
......
......@@ -49,6 +49,7 @@ const location = [
const media = [
'chooseImage',
'chooseFile',
'previewImage',
'getImageInfo',
'saveImageToPhotosAlbum',
......@@ -170,7 +171,7 @@ const ui = [
'showRightWindow',
'hideTopWindow',
'hideLeftWindow',
'hideRightWindow',
'hideRightWindow'
]
const event = [
......
const fs = require('fs')
const path = require('path')
function getTemplatePath (template) {
function getTemplatePath(template) {
if (template) {
const userTemplate = path.resolve(process.env.UNI_INPUT_DIR, template)
if (fs.existsSync(userTemplate))
......@@ -40,12 +40,16 @@ module.exports = {
vue: '@dcloudio/vue-cli-plugin-uni/packages/h5-vue'
},
copyWebpackOptions(platformOptions, vueOptions) {
return [{
const copyOptions = [{
from: require.resolve('@dcloudio/uni-h5/dist/index.css'),
to: getIndexCssPath(vueOptions.assetsDir, platformOptions.template),
transform
},
'hybrid/html'
]
global.uniModules.forEach(module => {
copyOptions.push('uni_modules/' + module + '/hybrid/html')
})
return copyOptions
}
}
}
......@@ -55,6 +55,7 @@
"title": "媒体",
"apiList": {
"uni.chooseImage": true,
"uni.chooseFile": true,
"uni.previewImage": true,
"uni.getImageInfo": true,
"uni.saveImageToPhotosAlbum": true,
......@@ -216,7 +217,7 @@
"name": "ad",
"title": "广告",
"apiList": {
"uni.createRewardedVideoAd": true,
"uni.createRewardedVideoAd": true,
"uni.createFullScreenVideoAd": true
}
}]
......@@ -1353,16 +1353,21 @@ function initRelation (detail) {
this.triggerEvent('__l', detail);
}
function selectAllComponents (mpInstance, selector, $refs) {
const components = mpInstance.selectAllComponents(selector);
components.forEach(component => {
const ref = component.dataset.ref;
$refs[ref] = component.$vm || component;
});
}
function initRefs (vm) {
const mpInstance = vm.$scope;
Object.defineProperty(vm, '$refs', {
get () {
const $refs = {};
const components = mpInstance.selectAllComponents('.vue-ref');
components.forEach(component => {
const ref = component.dataset.ref;
$refs[ref] = component.$vm || component;
});
selectAllComponents(mpInstance, '.vue-ref', $refs);
// TODO 暂不考虑 for 中的 scoped
const forComponents = mpInstance.selectAllComponents('.vue-ref-in-for');
forComponents.forEach(component => {
const ref = component.dataset.ref;
......
......@@ -33,6 +33,9 @@ module.exports = {
copyOptions.push(componentsCopyOption)
}
copyOptions.push('hybrid/html')
global.uniModules.forEach(module => {
copyOptions.push('uni_modules/' + module + '/hybrid/html')
})
if (process.env.UNI_USING_V3) { // TODO 将仅保留v3逻辑
copyOptions.push(path.resolve(__dirname, '../dist/view.css'))
copyOptions.push(path.resolve(__dirname, '../dist/view.umd.min.js'))
......
const merge = require('../lib/pages-json').default
describe('shared:merge', () => {
it('merge globalStyle', () => {
const a = {
globalStyle: {
navigationBarTitleText: 'uni-app',
'app-plus': {
bounce: 'none',
titleNView: {
background: '#ffffff',
buttons: [{
text: '分享'
}],
backButton: {
color: '#ffffff',
background: '#00FF00'
}
}
}
}
}
const b = {
globalStyle: {
navigationBarTitleText: 'hello',
navigationBarBackgroundColor: '#007AFF',
'app-plus': {
titleNView: {
background: '#000000',
buttons: [{
text: '收藏'
}],
backButton: {
background: '#00FF00'
}
}
}
}
}
const result = {
globalStyle: {
navigationBarTitleText: 'hello',
navigationBarBackgroundColor: '#007AFF',
'app-plus': {
bounce: 'none',
titleNView: {
background: '#000000',
buttons: [{
text: '收藏'
}],
backButton: {
color: '#ffffff',
background: '#00FF00'
}
}
}
}
}
expect(merge([a, b])).toEqual(result)
})
it('merge pages', () => {
const a = {
pages: [{
path: 'pages/index/index',
style: {
navigationBarTitleText: 'uni-app',
'app-plus': {
bounce: 'none',
titleNView: {
background: '#ffffff',
buttons: [{
text: '分享'
}],
backButton: {
color: '#ffffff',
background: '#00FF00'
}
}
}
}
}]
}
const b = {
pages: [{
path: 'pages/index/index',
style: {
navigationBarTitleText: 'uni-app',
'app-plus': {
titleNView: {
background: '#000000',
buttons: [{
text: '收藏'
}],
backButton: {
background: '#00FF00'
}
}
}
}
}, {
path: 'pages/login/login'
}]
}
const result = {
pages: [{
path: 'pages/index/index',
style: {
navigationBarTitleText: 'uni-app',
'app-plus': {
bounce: 'none',
titleNView: {
background: '#000000',
buttons: [{
text: '收藏'
}],
backButton: {
color: '#ffffff',
background: '#00FF00'
}
}
}
}
}, {
path: 'pages/login/login'
}]
}
expect(merge([a, b])).toEqual(result)
})
it('merge subpackages', () => {
const a = {
subPackages: [{
root: 'pages/demo',
pages: [{
path: 'index/index',
style: {
navigationBarTitleText: 'uni-app',
'app-plus': {
bounce: 'none',
titleNView: {
background: '#ffffff',
buttons: [{
text: '分享'
}],
backButton: {
color: '#ffffff',
background: '#00FF00'
}
}
}
}
}]
}]
}
const b = {
subPackages: [{
root: 'pages/demo',
pages: [{
path: 'index/index',
style: {
navigationBarTitleText: 'uni-app',
'app-plus': {
titleNView: {
background: '#000000',
buttons: [{
text: '收藏'
}],
backButton: {
background: '#00FF00'
}
}
}
}
}, {
path: 'login/login'
}]
}, {
root: 'pages/test',
pages: [{
path: 'test/test'
}]
}]
}
const result = {
subPackages: [{
root: 'pages/demo',
pages: [{
path: 'index/index',
style: {
navigationBarTitleText: 'uni-app',
'app-plus': {
bounce: 'none',
titleNView: {
background: '#000000',
buttons: [{
text: '收藏'
}],
backButton: {
color: '#ffffff',
background: '#00FF00'
}
}
}
}
}, {
path: 'login/login'
}]
}, {
root: 'pages/test',
pages: [{
path: 'test/test'
}]
}]
}
expect(merge([a, b])).toEqual(result)
})
it('merge multi', () => {
const a = {
globalStyle: {
backgroundColorTop: '#ffffff',
navigationBarTitleText: 'uni-app'
}
}
const b = {
globalStyle: {
navigationBarTitleText: 'hello1',
navigationBarBackgroundColor: '#000000',
backgroundColor: '#ffffff'
}
}
const c = {
globalStyle: {
navigationBarTitleText: 'hello2',
navigationBarBackgroundColor: '#007AFF',
backgroundTextStyle: 'light'
}
}
const result = {
globalStyle: {
backgroundColorTop: '#ffffff',
navigationBarTitleText: 'hello2',
navigationBarBackgroundColor: '#007AFF',
backgroundTextStyle: 'light',
backgroundColor: '#ffffff'
}
}
expect(merge([a, b, c])).toEqual(result)
})
})
function mergeWith (objects, customizer) {
const [first, ...rest] = objects
let ret = first
rest.forEach(a => {
ret = mergeTo(ret, a, customizer)
})
return ret
}
function mergeTo (a, b, customizer) {
const ret = {}
Object.keys(a)
.concat(Object.keys(b))
.forEach(k => {
const v = customizer(a[k], b[k], k)
ret[k] = typeof v === 'undefined' ? a[k] : v
})
return ret
}
module.exports = mergeWith
'use strict'
Object.defineProperty(exports, '__esModule', {
value: true
})
const isArray = Array.isArray
function isPlainObject (a) {
if (a === null) {
return false
}
return typeof a === 'object'
}
function mergeWith (objects, customizer) {
const [first, ...rest] = objects
let ret = first
rest.forEach(a => {
ret = mergeTo(ret, a, customizer)
})
return ret
}
function mergeTo (a, b, customizer) {
const ret = {}
Object.keys(a)
.concat(Object.keys(b))
.forEach(k => {
const v = customizer(a[k], b[k], k)
ret[k] = typeof v === 'undefined' ? a[k] : v
})
return ret
}
function mergeWithRule (a, b, k, matchField) {
if (!isArray(a)) {
return a
}
const bMatchItems = []
const ret = a.map(aItem => {
if (!matchField) {
return aItem
}
// 暂不考虑重复
const bMatchItem = b.find(bItem => aItem[matchField] === bItem[matchField])
if (bMatchItem) {
bMatchItems.push(bMatchItem)
return mergeWith([aItem, bMatchItem], createCustomizer(k))
}
return aItem
})
return ret.concat(b.filter(bItem => !bMatchItems.includes(bItem)))
}
function customizeArray (a, b, k) {
if (k === 'pages' || k === 'subPackages.pages') {
return mergeWithRule(a, b, k, 'path')
} else if (k === 'subPackages') {
return mergeWithRule(a, b, k, 'root')
}
return b
}
function customizeObject (a, b, k) {
return mergeWith([a, b], createCustomizer(k))
}
function createCustomizer (key) {
return function customizer (a, b, k) {
const newKey = key ? `${key}.${k}` : k
if (isArray(a) && isArray(b)) {
return customizeArray(a, b, newKey)
}
if (isPlainObject(a) && isPlainObject(b)) {
return customizeObject(a, b, newKey)
}
return b
}
}
function merge (pagesJsons) {
return mergeWith(pagesJsons, createCustomizer())
}
exports.default = merge
......@@ -9,10 +9,13 @@ const {
} = require('./util')
const {
getJson,
parseJson
} = require('./json')
const {
getPagesJson
} = require('./uni_modules')
let mainEntry = ''
let nvueMainEntry = ''
......@@ -33,10 +36,6 @@ function getNVueMainEntry () {
return nvueMainEntry
}
function getPagesJson () {
return processPagesJson(getJson('pages.json', true))
}
function parsePagesJson (content, loader) {
return processPagesJson(parseJson(content, true), loader)
}
......
const fs = require('fs')
const path = require('path')
const {
parseJson
} = require('./json')
const merge = require('./pages-json').default
function normalizeUniModulesPagesJson (pagesJson, pluginId) {
if (Array.isArray(pagesJson.pages)) {
pagesJson.pages.forEach(page => {
page.path = 'uni_modules/' + pluginId + '/' + page.path
})
}
if (Array.isArray(pagesJson.subPackages)) {
pagesJson.subPackages.forEach(subPackage => {
subPackage.root = 'uni_modules/' + pluginId + '/' + subPackage.root
})
}
return pagesJson
}
module.exports = {
getPagesJson (content) {
const uniModulesDir = path.resolve(process.env.UNI_INPUT_DIR, 'uni_modules')
const pluginPagesJsons = []
global.uniModules.forEach(plugin => {
const pagesJsonPath = path.resolve(uniModulesDir, plugin, 'pages.json')
if (fs.existsSync(pagesJsonPath)) {
pluginPagesJsons.push(
normalizeUniModulesPagesJson(parseJson(fs.readFileSync(pagesJsonPath).toString(), true), plugin)
)
}
})
content = content || fs.readFileSync(path.resolve(process.env.UNI_INPUT_DIR, 'pages.json'), 'utf8')
const mainPagesJson = parseJson(content, true)
if (pluginPagesJsons.length) {
const pagesJson = merge(pluginPagesJsons.concat(mainPagesJson))
if (Array.isArray(mainPagesJson.pages)) { // entry page
const entryPagePath = mainPagesJson.pages[0].path
const index = pagesJson.pages.findIndex(page => page.path === entryPagePath)
const entryPage = pagesJson.pages[index]
pagesJson.pages.splice(index, 1)
pagesJson.pages.unshift(entryPage)
}
return pagesJson
}
return mainPagesJson
}
}
......@@ -4,8 +4,8 @@ module.exports = {
cssVars: {
'--status-bar-height': '25px',
'--window-top': '0px',
'--window-bottom': '0px',
'--window-left': '0px',
'--window-bottom': '0px',
'--window-left': '0px',
'--window-right': '0px'
},
extnames: {
......@@ -17,6 +17,10 @@ module.exports = {
subPackages: true
},
copyWebpackOptions (platformOptions, vueOptions) {
return ['mycomponents']
const copyOptions = ['mycomponents']
global.uniModules.forEach(module => {
copyOptions.push('uni_modules/' + module + '/mycomponents')
})
return copyOptions
}
}
......@@ -1652,16 +1652,21 @@ function initBehavior (options) {
return Behavior(options)
}
function selectAllComponents (mpInstance, selector, $refs) {
const components = mpInstance.selectAllComponents(selector);
components.forEach(component => {
const ref = component.dataset.ref;
$refs[ref] = component.$vm || component;
});
}
function initRefs (vm) {
const mpInstance = vm.$scope;
Object.defineProperty(vm, '$refs', {
get () {
const $refs = {};
const components = mpInstance.selectAllComponents('.vue-ref');
components.forEach(component => {
const ref = component.dataset.ref;
$refs[ref] = component.$vm || component;
});
selectAllComponents(mpInstance, '.vue-ref', $refs);
// TODO 暂不考虑 for 中的 scoped
const forComponents = mpInstance.selectAllComponents('.vue-ref-in-for');
forComponents.forEach(component => {
const ref = component.dataset.ref;
......
......@@ -3,8 +3,8 @@ module.exports = {
cssVars: {
'--status-bar-height': '25px',
'--window-top': '0px',
'--window-bottom': '0px',
'--window-left': '0px',
'--window-bottom': '0px',
'--window-left': '0px',
'--window-right': '0px'
},
extnames: {
......@@ -17,6 +17,10 @@ module.exports = {
subPackages: true
},
copyWebpackOptions (platformOptions, vueOptions) {
return ['swancomponents']
const copyOptions = ['swancomponents']
global.uniModules.forEach(module => {
copyOptions.push('uni_modules/' + module + '/swancomponents')
})
return copyOptions
}
}
......@@ -1414,16 +1414,21 @@ function initRelation (detail) {
this.triggerEvent('__l', detail);
}
function selectAllComponents (mpInstance, selector, $refs) {
const components = mpInstance.selectAllComponents(selector);
components.forEach(component => {
const ref = component.dataset.ref;
$refs[ref] = component.$vm || component;
});
}
function initRefs (vm) {
const mpInstance = vm.$scope;
Object.defineProperty(vm, '$refs', {
get () {
const $refs = {};
const components = mpInstance.selectAllComponents('.vue-ref');
components.forEach(component => {
const ref = component.dataset.ref;
$refs[ref] = component.$vm || component;
});
selectAllComponents(mpInstance, '.vue-ref', $refs);
// TODO 暂不考虑 for 中的 scoped
const forComponents = mpInstance.selectAllComponents('.vue-ref-in-for');
forComponents.forEach(component => {
const ref = component.dataset.ref;
......
......@@ -3,8 +3,8 @@ module.exports = {
cssVars: {
'--status-bar-height': '25px',
'--window-top': '0px',
'--window-bottom': '0px',
'--window-left': '0px',
'--window-bottom': '0px',
'--window-left': '0px',
'--window-right': '0px'
},
extnames: {
......@@ -14,6 +14,10 @@ module.exports = {
project: 'project.ks.json'
},
copyWebpackOptions (platformOptions, vueOptions) {
return ['kscomponents']
const copyOptions = ['kscomponents']
global.uniModules.forEach(module => {
copyOptions.push('uni_modules/' + module + '/kscomponents')
})
return copyOptions
}
}
}
......@@ -1580,16 +1580,21 @@ function initRelation (detail) {
this.triggerEvent('__l', detail);
}
function selectAllComponents (mpInstance, selector, $refs) {
const components = mpInstance.selectAllComponents(selector);
components.forEach(component => {
const ref = component.dataset.ref;
$refs[ref] = component.$vm || component;
});
}
function initRefs (vm) {
const mpInstance = vm.$scope;
Object.defineProperty(vm, '$refs', {
get () {
const $refs = {};
const components = mpInstance.selectAllComponents('.vue-ref');
components.forEach(component => {
const ref = component.dataset.ref;
$refs[ref] = component.$vm || component;
});
selectAllComponents(mpInstance, '.vue-ref', $refs);
// TODO 暂不考虑 for 中的 scoped
const forComponents = mpInstance.selectAllComponents('.vue-ref-in-for');
forComponents.forEach(component => {
const ref = component.dataset.ref;
......
......@@ -8,8 +8,8 @@ module.exports = {
cssVars: {
'--status-bar-height': '25px',
'--window-top': '0px',
'--window-bottom': '0px',
'--window-left': '0px',
'--window-bottom': '0px',
'--window-left': '0px',
'--window-right': '0px'
},
extnames: {
......@@ -38,6 +38,16 @@ module.exports = {
ignore: ['**/*.vue', '**/*.css'] // v3 会自动转换生成vue,css文件,需要过滤
})
}
global.uniModules.forEach(module => {
const wxcomponentsDir = path.resolve(process.env.UNI_INPUT_DIR, 'uni_modules', module, COMPONENTS_DIR_NAME)
if (fs.existsSync(wxcomponentsDir)) {
copyOptions.push({
from: wxcomponentsDir,
to: 'uni_modules/' + module + '/' + COMPONENTS_DIR_NAME,
ignore: ['**/*.vue', '**/*.css'] // v3 会自动转换生成vue,css文件,需要过滤
})
}
})
return copyOptions
}
}
......@@ -3,8 +3,8 @@ module.exports = {
cssVars: {
'--status-bar-height': '25px',
'--window-top': '0px',
'--window-bottom': '0px',
'--window-left': '0px',
'--window-bottom': '0px',
'--window-left': '0px',
'--window-right': '0px'
},
extnames: {
......@@ -14,6 +14,10 @@ module.exports = {
project: 'project.tt.json'
},
copyWebpackOptions (platformOptions, vueOptions) {
return ['ttcomponents']
const copyOptions = ['ttcomponents']
global.uniModules.forEach(module => {
copyOptions.push('uni_modules/' + module + '/ttcomponents')
})
return copyOptions
}
}
......@@ -1444,16 +1444,28 @@ function initRelation (detail) {
this.triggerEvent('__l', detail);
}
function selectAllComponents (mpInstance, selector, $refs) {
const components = mpInstance.selectAllComponents(selector);
components.forEach(component => {
const ref = component.dataset.ref;
$refs[ref] = component.$vm || component;
{
if (component.dataset.vueGeneric === 'scoped') {
component.selectAllComponents('.scoped-ref').forEach(scopedComponent => {
selectAllComponents(scopedComponent, selector, $refs);
});
}
}
});
}
function initRefs (vm) {
const mpInstance = vm.$scope;
Object.defineProperty(vm, '$refs', {
get () {
const $refs = {};
const components = mpInstance.selectAllComponents('.vue-ref');
components.forEach(component => {
const ref = component.dataset.ref;
$refs[ref] = component.$vm || component;
});
selectAllComponents(mpInstance, '.vue-ref', $refs);
// TODO 暂不考虑 for 中的 scoped
const forComponents = mpInstance.selectAllComponents('.vue-ref-in-for');
forComponents.forEach(component => {
const ref = component.dataset.ref;
......
......@@ -29,23 +29,24 @@ module.exports = {
if (!state.componentGenerics) {
state.componentGenerics = Object.create(null)
}
state.componentGenerics[componentName] = true
const attr = props || {}
if (state.options.platform.name === 'mp-weixin') {
attr.class = 'scoped-ref'
}
// 返回多个节点,支持作用域插槽当作普通插槽使用
return [
{
type: 'slot',
attr: {
name: slotName
},
children: []
return [{
type: 'slot',
attr: {
name: slotName
},
{
type: componentName,
attr: props || {},
children: []
}
children: []
},
{
type: componentName,
attr,
children: []
}
]
},
resolveScopedSlots (slotName, {
......@@ -72,6 +73,9 @@ module.exports = {
}
state.scopedSlots[baseName]++
parentNode.attr['generic:scoped-slots-' + slotName] = componentName
if (state.options.platform.name === 'mp-weixin') {
parentNode.attr['data-vue-generic'] = 'scoped'
}
if (!parentNode.attr.generic) {
parentNode.attr.generic = {}
}
......@@ -118,7 +122,9 @@ module.exports = {
try {
// TODO 使用 getPlatformExts 在单元测试报错,改从 state.options.platform 判断
const { getPlatformExts } = require('@dcloudio/uni-cli-shared')
const {
getPlatformExts
} = require('@dcloudio/uni-cli-shared')
const styleExtname = getPlatformExts().style
const styleFile = resourcePath.replace(ownerName + extname, componentName + styleExtname)
const styleContent = generateCssCode(ownerName + styleExtname)
......@@ -134,4 +140,4 @@ module.exports = {
return ''
}
}
}
......@@ -8,8 +8,8 @@ module.exports = {
cssVars: {
'--status-bar-height': '25px',
'--window-top': '0px',
'--window-bottom': '0px',
'--window-left': '0px',
'--window-bottom': '0px',
'--window-left': '0px',
'--window-right': '0px'
},
extnames: {
......@@ -23,7 +23,7 @@ module.exports = {
darkmode: true
},
copyWebpackOptions (platformOptions, vueOptions) {
const copyOptions = [
const copyOptions = [
'theme.json',
'sitemap.json',
'ext.json',
......@@ -40,6 +40,16 @@ module.exports = {
ignore: ['**/*.vue', '**/*.css'] // v3 会自动转换生成vue,css文件,需要过滤
})
}
global.uniModules.forEach(module => {
const wxcomponentsDir = path.resolve(process.env.UNI_INPUT_DIR, 'uni_modules', module, COMPONENTS_DIR_NAME)
if (fs.existsSync(wxcomponentsDir)) {
copyOptions.push({
from: wxcomponentsDir,
to: 'uni_modules/' + module + '/' + COMPONENTS_DIR_NAME,
ignore: ['**/*.vue', '**/*.css'] // v3 会自动转换生成vue,css文件,需要过滤
})
}
})
return copyOptions
}
}
......@@ -1422,16 +1422,21 @@ function initBehavior (options) {
return Behavior(options)
}
function selectAllComponents (mpInstance, selector, $refs) {
const components = mpInstance.selectAllComponents(selector);
components.forEach(component => {
const ref = component.dataset.ref;
$refs[ref] = component.$vm || component;
});
}
function initRefs (vm) {
const mpInstance = vm.$scope;
Object.defineProperty(vm, '$refs', {
get () {
const $refs = {};
const components = mpInstance.selectAllComponents('.vue-ref');
components.forEach(component => {
const ref = component.dataset.ref;
$refs[ref] = component.$vm || component;
});
selectAllComponents(mpInstance, '.vue-ref', $refs);
// TODO 暂不考虑 for 中的 scoped
const forComponents = mpInstance.selectAllComponents('.vue-ref-in-for');
forComponents.forEach(component => {
const ref = component.dataset.ref;
......
......@@ -23,14 +23,14 @@ describe('mp:compiler-mp-weixin', () => {
it('generate scoped slot', () => {
assertCodegen(
'<foo><template slot-scope="{bar}">{{ bar.foo }}</template></foo>',
'<foo generic:scoped-slots-default="test-foo-default" vue-id="551070e6-1" bind:__l="__l" vue-slots="{{[\'default\']}}"></foo>',
'<foo generic:scoped-slots-default="test-foo-default" data-vue-generic="scoped" vue-id="551070e6-1" bind:__l="__l" vue-slots="{{[\'default\']}}"></foo>',
function (res) {
expect(res.generic[0]).toBe('test-foo-default')
}
)
assertCodegen(
'<foo><view slot-scope="{bar}">{{ bar.foo }}</view></foo>',
'<foo generic:scoped-slots-default="test-foo-default" vue-id="551070e6-1" bind:__l="__l" vue-slots="{{[\'default\']}}"></foo>',
'<foo generic:scoped-slots-default="test-foo-default" data-vue-generic="scoped" vue-id="551070e6-1" bind:__l="__l" vue-slots="{{[\'default\']}}"></foo>',
function (res) {
expect(res.generic[0]).toBe('test-foo-default')
}
......@@ -40,14 +40,14 @@ describe('mp:compiler-mp-weixin', () => {
it('generate named scoped slot', () => {
assertCodegen(
'<foo><template slot="foo" slot-scope="{bar}">{{ bar.foo }}</template></foo>',
'<foo generic:scoped-slots-foo="test-foo-foo" vue-id="551070e6-1" bind:__l="__l" vue-slots="{{[\'foo\']}}"></foo>',
'<foo generic:scoped-slots-foo="test-foo-foo" data-vue-generic="scoped" vue-id="551070e6-1" bind:__l="__l" vue-slots="{{[\'foo\']}}"></foo>',
function (res) {
expect(res.generic[0]).toBe('test-foo-foo')
}
)
assertCodegen(
'<foo><view slot="foo" slot-scope="{bar}">{{ bar.foo }}</view></foo>',
'<foo generic:scoped-slots-foo="test-foo-foo" vue-id="551070e6-1" bind:__l="__l" vue-slots="{{[\'foo\']}}"></foo>',
'<foo generic:scoped-slots-foo="test-foo-foo" data-vue-generic="scoped" vue-id="551070e6-1" bind:__l="__l" vue-slots="{{[\'foo\']}}"></foo>',
function (res) {
expect(res.generic[0]).toBe('test-foo-foo')
}
......@@ -57,14 +57,14 @@ describe('mp:compiler-mp-weixin', () => {
it('generate scoped slot with multiline v-if', () => {
assertCodegen(
'<foo><template v-if="\nshow\n" slot-scope="{bar}">{{ bar.foo }}</template></foo>',
'<foo generic:scoped-slots-default="test-foo-default" vue-id="551070e6-1" bind:__l="__l" vue-slots="{{[\'default\']}}"></foo>',
'<foo generic:scoped-slots-default="test-foo-default" data-vue-generic="scoped" vue-id="551070e6-1" bind:__l="__l" vue-slots="{{[\'default\']}}"></foo>',
function (res) {
expect(res.generic[0]).toBe('test-foo-default')
}
)
assertCodegen(
'<foo><view v-if="\nshow\n" slot="foo" slot-scope="{bar}">{{ bar.foo }}</view></foo>',
'<foo generic:scoped-slots-foo="test-foo-foo" vue-id="551070e6-1" bind:__l="__l" vue-slots="{{[\'foo\']}}"></foo>',
'<foo generic:scoped-slots-foo="test-foo-foo" data-vue-generic="scoped" vue-id="551070e6-1" bind:__l="__l" vue-slots="{{[\'foo\']}}"></foo>',
function (res) {
expect(res.generic[0]).toBe('test-foo-foo')
}
......@@ -74,21 +74,21 @@ describe('mp:compiler-mp-weixin', () => {
it('generate scoped slot', () => {
assertCodegen(
'<slot v-bind:user="user"></slot>',
'<slot></slot><scoped-slots-default user="{{user}}" bind:__l="__l"></scoped-slots-default>',
'<slot></slot><scoped-slots-default user="{{user}}" class="scoped-ref" bind:__l="__l"></scoped-slots-default>',
function (res) {
expect(res.componentGenerics['scoped-slots-default']).toBe(true)
}
)
assertCodegen( // TODO vue-id
'<span><slot v-bind:user="user">{{ user.lastName }}</slot></span>',
'<label class="_span"><block wx:if="{{$slots.default}}"><slot></slot><scoped-slots-default user="{{user}}" bind:__l="__l"></scoped-slots-default></block><block wx:else>{{user.lastName}}</block></label>',
'<label class="_span"><block wx:if="{{$slots.default}}"><slot></slot><scoped-slots-default user="{{user}}" class="scoped-ref" bind:__l="__l"></scoped-slots-default></block><block wx:else>{{user.lastName}}</block></label>',
function (res) {
expect(res.componentGenerics['scoped-slots-default']).toBe(true)
}
)
assertCodegen(
'<span><slot name="header" v-bind:user="user">{{ user.lastName }}</slot></span>',
'<label class="_span"><block wx:if="{{$slots.header}}"><slot name="header"></slot><scoped-slots-header user="{{user}}" bind:__l="__l"></scoped-slots-header></block><block wx:else>{{user.lastName}}</block></label>',
'<label class="_span"><block wx:if="{{$slots.header}}"><slot name="header"></slot><scoped-slots-header user="{{user}}" class="scoped-ref" bind:__l="__l"></scoped-slots-header></block><block wx:else>{{user.lastName}}</block></label>',
function (res) {
expect(res.componentGenerics['scoped-slots-header']).toBe(true)
}
......@@ -117,7 +117,7 @@ describe('mp:compiler-mp-weixin', () => {
<view class="red">{{label}}</view>
</slot-comp>
</view>`,
'<view><slot-comp generic:scoped-slots-test="test-slot-comp-test" vue-id="551070e6-1" bind:__l="__l" vue-slots="{{[\'test\']}}"></slot-comp><slot-comp generic:scoped-slots-test="test-slot-comp-test1" vue-id="551070e6-2" bind:__l="__l" vue-slots="{{[\'test\']}}"></slot-comp><slot-comp generic:scoped-slots-test="test-slot-comp-test2" vue-id="551070e6-3" bind:__l="__l" vue-slots="{{[\'test\']}}"></slot-comp><slot-comp generic:scoped-slots-test="test-slot-comp-test3" vue-id="551070e6-4" bind:__l="__l" vue-slots="{{[\'test\']}}"></slot-comp></view>'
'<view><slot-comp generic:scoped-slots-test="test-slot-comp-test" data-vue-generic="scoped" vue-id="551070e6-1" bind:__l="__l" vue-slots="{{[\'test\']}}"></slot-comp><slot-comp generic:scoped-slots-test="test-slot-comp-test1" data-vue-generic="scoped" vue-id="551070e6-2" bind:__l="__l" vue-slots="{{[\'test\']}}"></slot-comp><slot-comp generic:scoped-slots-test="test-slot-comp-test2" data-vue-generic="scoped" vue-id="551070e6-3" bind:__l="__l" vue-slots="{{[\'test\']}}"></slot-comp><slot-comp generic:scoped-slots-test="test-slot-comp-test3" data-vue-generic="scoped" vue-id="551070e6-4" bind:__l="__l" vue-slots="{{[\'test\']}}"></slot-comp></view>'
)
})
......
......@@ -68,7 +68,19 @@ function generateAutoComponentsCode (autoComponents, dynamic = false) {
components.push(`'${name}': require('${source}').default`)
}
})
return `var components = {${components.join(',')}}`
return `var components;
try{
components = {${components.join(',')}}
}catch(e){
if(e.message.indexOf('Cannot find module') !== -1 && e.message.indexOf('.vue') !== -1){
console.error(e.message)
console.error('1. 排查组件名称拼写是否正确')
console.error('2. 排查组件是否符合 easycom 规范,文档:https://uniapp.dcloud.net.cn/collocation/pages?id=easycom')
console.error('3. 若组件不符合 easycom 规范,需手动引入,并在 components 中注册该组件')
} else {
throw e
}
}`
}
function compileTemplate (source, options, compile) {
......
......@@ -51,8 +51,16 @@ function getAssetsCopyOptions (assetsDir) {
return copyOptions
}
function getUniModulesAssetsCopyOptions (assetsDir) {
const copyOptions = []
global.uniModules.forEach(module => {
copyOptions.push(...getAssetsCopyOptions('uni_modules/' + module + '/' + assetsDir))
})
return copyOptions
}
function getCopyWebpackPluginOptions (platformOptions, vueOptions) {
const copyOptions = getAssetsCopyOptions(assetsDir)
const copyOptions = getAssetsCopyOptions(assetsDir).concat(getUniModulesAssetsCopyOptions(assetsDir))
global.uniPlugin.copyWebpackOptions.forEach(copyWebpackOptions => {
const platformCopyOptions = copyWebpackOptions(platformOptions, vueOptions, copyOptions) || []
platformCopyOptions.forEach(copyOption => {
......
......@@ -19,6 +19,7 @@ process.env.UNI_INPUT_DIR = process.env.UNI_INPUT_DIR || path.resolve(process.cw
// 初始化全局插件对象
global.uniPlugin = require('@dcloudio/uni-cli-shared/lib/plugin').init()
const manifestJsonObj = require('@dcloudio/uni-cli-shared/lib/manifest').getManifestJson()
const platformOptions = manifestJsonObj[process.env.UNI_SUB_PLATFORM || process.env.UNI_PLATFORM] || {}
// 插件校验环境
......@@ -103,6 +104,17 @@ if (process.env.NODE_ENV === 'production') { // 发行模式,不启用 cache
delete process.env.UNI_USING_CACHE
}
global.uniModules = []
try {
global.uniModules = fs
.readdirSync(path.resolve(process.env.UNI_INPUT_DIR, 'uni_modules'))
.filter(module =>
fs.existsSync(
path.resolve(process.env.UNI_INPUT_DIR, 'uni_modules', module, 'package.json')
)
)
} catch (e) {}
const {
normalizePath,
isSupportSubPackages,
......
......@@ -166,7 +166,7 @@ module.exports = function getSplitChunks () {
if (module.resource && module.reasons) {
for (let index = 0; index < module.reasons.length; index++) {
const m = module.reasons[index]
if (m.module && m.module.resource) {
const resource = normalizePath(m.module.resource)
if (
......@@ -202,8 +202,7 @@ module.exports = function getSplitChunks () {
matchSubPackages.has(root + '/') &&
!hasMainPackage(chunks) &&
!hasMainPackageComponent(module, matchSubPackages.values().next().value)
) {
) {
if (process.env.UNI_OPT_TRACE) {
console.log(root, module.resource, chunks.map(chunk => chunk.name))
}
......
......@@ -30,6 +30,7 @@ function checkEmitFile (filePath, jsonObj, changedEmitFiles) {
}
module.exports = function (content, map) {
content = JSON.stringify(require('@dcloudio/uni-cli-shared/lib/uni_modules').getPagesJson(content))
if (this.resourceQuery) {
const params = loaderUtils.parseQuery(this.resourceQuery)
if (params) {
......@@ -40,6 +41,13 @@ module.exports = function (content, map) {
}
}
}
// add deps
global.uniModules.forEach(module => {
const uniModulePagesJsonPath = path.resolve(process.env.UNI_INPUT_DIR, 'uni_modules', module, 'pages.json')
if (fs.existsSync(uniModulePagesJsonPath)) {
this.addDependency(uniModulePagesJsonPath)
}
})
if (
process.env.UNI_USING_COMPONENTS ||
......
......@@ -154,10 +154,11 @@ const getPageComponents = function (inputDir, pagesJson) {
isTabBar,
tabBarIndex,
isQuit: isEntry || isTabBar,
windowTop,
topWindow: pageStyle.topWindow,
leftWindow: pageStyle.leftWindow,
rightWindow: pageStyle.rightWindow
windowTop,
topWindow: pageStyle.topWindow,
leftWindow: pageStyle.leftWindow,
rightWindow: pageStyle.rightWindow,
maxWidth: pageStyle.maxWidth
}
}).filter(pageComponents => !!pageComponents)
}
......@@ -214,10 +215,11 @@ const genPageRoutes = function (pageComponents) {
isEntry,
isTabBar,
windowTop,
tabBarIndex,
topWindow,
leftWindow,
rightWindow
tabBarIndex,
topWindow,
leftWindow,
rightWindow,
maxWidth
}) => {
return `
{
......@@ -228,7 +230,7 @@ component: {
'Page',
{
props: Object.assign({
${isQuit ? 'isQuit:true,\n' : ''}${isEntry ? 'isEntry:true,\n' : ''}${isTabBar ? 'isTabBar:true,\n' : ''}
${isQuit ? 'isQuit:true,\n' : ''}${isEntry ? 'isEntry:true,\n' : ''}${isTabBar ? 'isTabBar:true,\n' : ''}
${topWindow === false ? 'topWindow:false,\n' : ''}${leftWindow === false ? 'leftWindow:false,\n' : ''}${rightWindow === false ? 'rightWindow:false,\n' : ''}
${isTabBar ? ('tabBarIndex:' + tabBarIndex) : ''}
},__uniConfig.globalStyle,${JSON.stringify(props)})
......@@ -243,7 +245,7 @@ component: {
},
meta:{${isQuit ? '\nid:' + (id++) + ',' : ''}
name:'${name}',
isNVue:${isNVue},${topWindow === false ? 'topWindow:false,\n' : ''}${leftWindow === false ? 'leftWindow:false,\n' : ''}${rightWindow === false ? 'rightWindow:false,\n' : ''}
isNVue:${isNVue},maxWidth:${maxWidth || 0},${topWindow === false ? 'topWindow:false,\n' : ''}${leftWindow === false ? 'leftWindow:false,\n' : ''}${rightWindow === false ? 'rightWindow:false,\n' : ''}
pagePath:'${route}'${isQuit ? ',\nisQuit:true' : ''}${isEntry ? ',\nisEntry:true' : ''}${isTabBar ? ',\nisTabBar:true' : ''}${tabBarIndex !== -1 ? (',\ntabBarIndex:' + tabBarIndex) : ''},
windowTop:${windowTop}
}
......
......@@ -106,7 +106,8 @@ if (pixelRatio !== 1) {
args[1] *= pixelRatio
args[2] *= pixelRatio
var font = this.font
// Safari 重新设置部分属性会导致其他值恢复默认,需获取原始值
var font = this.__font__ || this.font
this.font = font.replace(
/(\d+\.?\d*)(px|em|rem|pt)/g,
function (w, m, u) {
......@@ -130,7 +131,8 @@ if (pixelRatio !== 1) {
args[1] *= pixelRatio // x
args[2] *= pixelRatio // y
var font = this.font
// Safari 重新设置部分属性会导致其他值恢复默认,需获取原始值
var font = this.__font__ || this.font
this.font = font.replace(
/(\d+\.?\d*)(px|em|rem|pt)/g,
function (w, m, u) {
......
const MEDIA_TYPE = ['all', 'image', 'video']
const SOURCE_TYPES = ['album', 'camera']
export const chooseFile = {
count: {
type: Number,
required: false,
default: 100,
validator (count, params) {
if (count <= 0) {
params.count = 100
}
}
},
sourceType: {
type: Array,
required: false,
default: SOURCE_TYPES,
validator (sourceType, params) {
sourceType = sourceType.filter(sourceType => SOURCE_TYPES.includes(sourceType))
params.sourceType = sourceType.length ? sourceType : SOURCE_TYPES
}
},
type: {
type: String,
required: false,
default: 'all',
validator (type, params) {
if (!MEDIA_TYPE.includes(type)) params.type = MEDIA_TYPE[0]
params.type = params.type === 'all' ? params.type = '*' : params.type
}
},
extension: {
type: Array,
default: ['*']
}
}
......@@ -30,5 +30,9 @@ export const chooseImage = {
sourceType = sourceType.filter(sourceType => SOURCE_TYPES.includes(sourceType))
params.sourceType = sourceType.length ? sourceType : SOURCE_TYPES
}
},
extension: {
type: Array,
default: ['*']
}
}
......@@ -17,5 +17,9 @@ export const chooseVideo = {
camera: {
type: String,
default: 'back'
},
extension: {
type: Array,
default: ['*']
}
}
......@@ -103,5 +103,8 @@ export const request = {
},
withCredentials: {
type: Boolean
},
timeout: {
type: Number
}
}
......@@ -12,10 +12,15 @@ import {
export default function createApp (vm) {
Vue.prototype.getOpenerEventChannel = function () {
if (!this.__eventChannel__) {
this.__eventChannel__ = new EventChannel()
switch (__PLATFORM__) {
case 'mp-weixin':
return this.$scope.getOpenerEventChannel()
default :
if (!this.__eventChannel__) {
this.__eventChannel__ = new EventChannel()
}
return this.__eventChannel__
}
return this.__eventChannel__
}
const callHook = Vue.prototype.__call_hook
Vue.prototype.__call_hook = function (hook, args) {
......@@ -27,4 +32,4 @@ export default function createApp (vm) {
}
App(parseApp(vm))
return vm
}
}
......@@ -212,24 +212,21 @@ export default {
data.forEach(function (color_, method_) {
c2d[_[method_]] = _[method_] === 'shadowColor' ? resolveColor(color_) : color_
})
} else {
if (method1 === 'fontSize') {
c2d.font = c2d.font.replace(/\d+\.?\d*px/, data[0] + 'px')
} else {
if (method1 === 'lineDash') {
c2d.setLineDash(data[0])
c2d.lineDashOffset = data[1] || 0
} else {
if (method1 === 'textBaseline') {
if (data[0] === 'normal') {
data[0] = 'alphabetic'
}
c2d[method1] = data[0]
} else {
c2d[method1] = data[0]
}
}
} else if (method1 === 'fontSize') {
const font = c2d.__font__ || c2d.font
c2d.__font__ = c2d.font = font.replace(/\d+\.?\d*px/, data[0] + 'px')
} else if (method1 === 'lineDash') {
c2d.setLineDash(data[0])
c2d.lineDashOffset = data[1] || 0
} else if (method1 === 'textBaseline') {
if (data[0] === 'normal') {
data[0] = 'alphabetic'
}
c2d[method1] = data[0]
} else if (method1 === 'font') {
c2d.__font__ = c2d.font = data[0]
} else {
c2d[method1] = data[0]
}
} else if (method === 'fillPath' || method === 'strokePath') {
method = method.replace(/Path/, '')
......@@ -356,8 +353,8 @@ export default {
return
}
}
// Chrome84+ 本地路径
if (src.indexOf('file://') === 0 && navigator.vendor === 'Google Inc.' && 'wakeLock' in navigator) {
// 安卓 WebView 本地路径
if (src.indexOf('file://') === 0 && navigator.vendor === 'Google Inc.') {
image.crossOrigin = 'anonymous'
}
}
......
......@@ -24,6 +24,7 @@
:type="inputType"
:maxlength="maxlength"
:step="step"
:enterkeyhint="confirmType"
class="uni-input-input"
autocomplete="off"
@focus="_onFocus"
......@@ -31,7 +32,7 @@
@input.stop="_onInput"
@compositionstart="_onComposition"
@compositionend="_onComposition"
@keyup.stop="_onKeyup"
@keyup.enter.stop="_onKeyup"
>
<!-- fix: 禁止 readonly 状态获取焦点 -->
<input
......@@ -173,11 +174,9 @@ export default {
},
methods: {
_onKeyup ($event) {
if ($event.keyCode === 13) {
this.$trigger('confirm', $event, {
value: $event.target.value
})
}
this.$trigger('confirm', $event, {
value: $event.target.value
})
},
_onInput ($event) {
if (this.composing) {
......
......@@ -26,7 +26,8 @@
</div>
</div>
<span
v-show="showValue"
v-show="showValue"
ref="uni-slider-value"
class="uni-slider-value"
>{{ sliderValue }}</span>
</div>
......@@ -150,16 +151,22 @@ export default {
})
},
methods: {
_onUserChangedValue (e) {
_onUserChangedValue (e) {
const sliderRightBox = this.$refs['uni-slider-value']
const sliderRightBoxLeft = getComputedStyle(sliderRightBox, null).marginLeft
let sliderRightBoxWidth = sliderRightBox.offsetWidth
sliderRightBoxWidth = sliderRightBoxWidth + parseInt(sliderRightBoxLeft)
const slider = this.$refs['uni-slider']
const offsetWidth = slider.offsetWidth
const offsetWidth = slider.offsetWidth - (this.showValue ? sliderRightBoxWidth : 0)
const boxLeft = slider.getBoundingClientRect().left
const value = (e.x - boxLeft) * (this.max - this.min) / offsetWidth + Number(this.min)
this.sliderValue = this._filterValue(value)
},
_filterValue (e) {
return e < this.min ? this.min : e > this.max ? this.max : Math.round((e - this.min) / this
.step) * this.step + Number(this.min)
_filterValue (e) {
const max = Number(this.max)
const min = Number(this.min)
return e < min ? min : e > max ? max : Math.round((e - min) / this
.step) * this.step + min
},
_getValueWidth () {
return 100 * (this.sliderValue - this.min) / (this.max - this.min) + '%'
......@@ -281,10 +288,11 @@ export default {
z-index: 1;
}
uni-slider .uni-slider-value {
uni-slider .uni-slider-value {
width: 3ch;
color: #888;
font-size: 14px;
margin-left: 1em;
margin-left: 1em;
}
uni-slider .uni-slider-disabled .uni-slider-track {
......
......@@ -37,6 +37,7 @@
:maxlength="maxlengthNumber"
:class="{ 'uni-textarea-textarea-fix-margin': fixMargin }"
:style="{ 'overflow-y': autoHeight ? 'hidden' : 'auto' }"
:enterkeyhint="confirmType"
class="uni-textarea-textarea"
@compositionstart="_onCompositionstart"
@compositionend="_onCompositionend"
......@@ -44,6 +45,8 @@
@focus="_onFocus"
@blur="_onBlur"
@touchstart.passive="_onTouchstart"
@keyup.enter="_onKeyUpEnter"
@keydown.enter="_onKeyDownEnter"
/>
<!-- fix: 禁止 readonly 状态获取焦点 -->
<textarea
......@@ -109,6 +112,10 @@ export default {
selectionEnd: {
type: [Number, String],
default: -1
},
confirmType: {
type: String,
default: ''
}
},
data () {
......@@ -143,6 +150,9 @@ export default {
},
valueCompute () {
return (this.composition ? this.valueComposition : this.valueSync).split('\n')
},
isDone () {
return ['done', 'go', 'next', 'search', 'send'].includes(this.confirmType)
}
},
watch: {
......@@ -210,6 +220,17 @@ export default {
})
},
methods: {
_onKeyDownEnter: function ($event) {
if (this.isDone) {
$event.preventDefault()
}
},
_onKeyUpEnter: function ($event) {
if (this.isDone) {
this._confirm($event)
this.$refs.textarea.blur()
}
},
_onFocus: function ($event) {
this.focusSync = true
this.$trigger('focus', $event, {
......
......@@ -14,7 +14,7 @@
font-weight: normal;
font-style: normal;
font-family: "unibtn";
src: url('data:application/octet-stream;base64,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') format('truetype')
src: url('data:application/octet-stream;base64,AAEAAAAKAIAAAwAgT1MvMvUTHSwAAACsAAAAYGNtYXD/1LSBAAABDAAAAVpnbHlmz06L9gAAAmgAAAQ0aGVhZA501cwAAAacAAAANmhoZWEH7wQ6AAAG1AAAACRobXR4JCoHAwAABvgAAAAkbG9jYQQeBSgAAAccAAAAFG1heHAADQBLAAAHMAAAACBuYW1l5hEPkgAAB1AAAAHacG9zdAQfBCEAAAksAAAAPAAEBAUBkAAFAAACmQLMAAAAjwKZAswAAAHrADMBCQAAAAAAAAAAAAAAAAAAAAEQAAAAAAAAAAAAAAAAAAAAAEDmAP/9A8D/wABAA8AAQAAAAAEAAAAAAAAAAAAAACAAAAAAAAMAAAADAAAAHAABAAAAAABUAAMAAQAAABwABAA4AAAACgAIAAIAAuYC5gbmUf/9//8AAOYA5gTmUP/9//8aARoAGbcAAwABAAAAAAAAAAAAAAAAAQYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAWAAeQLBAuoAFQAAASY0NzYyFwEeARUUBgcBBiInJjQ3AQFgCAgHFgcBNQQEBAT+ywcWBwgIASICxQcXBwgI/twECwUGCwT+3AgIBxcIARMAAAABAVgAeQK5AuoAFQAACQIWFAcGIicBLgE1NDY3ATYyFxYUArn+3gEiCAgIFQj+zAUDAwUBNAgVCAgCxf7t/u0IFwcICAEkBAsGBQsEASQICAcXAAACANAAaQO6Aw0AGwA0AAAlFAYjISImNRE0NjsBNSMiBhURFBYzITI2PQEjEycHFwcOAw8BMzU+ATc+AT8CBxc3JwNSBwT9wgQGBgSwwBEYGBECXxEXLmikIW53SoJjPQUBLwU2LS11QAZqbiGkAaUEBwcEAagFBjEZEf40ERkZEqUBKKwjcwEGPF9+RwgHPG4rKzIEAQF0IqwCAAACAJcAXgNzAxsALwBIAAABLgEvAi4BIyIGDwIOAQcGFh8BBwYWFx4BMzI2PwEXHgEzMjY3PgEnNDUnNz4BBTYmLwE3PgE/ARceAR8BBw4BHwEnJgYPAQNzAgoG42cDCgcGCgNk4wYKAgEDBKUlAQUFAwYEAgUDyswCBQMGCgMCAQEoowUD/foBAwSNwgUJAlZYAgkFw4wEAwEirgULBK4CFAYIAR/NBgYGBs4jAQgGBgwEn+IGDAQCAgIBbGoBAQYGAwkDAQHeoQUMsAYKBIgdAQYFsbAFBgEbiQQLBcFaAwECXAACAL8AdQNQAxEAIAA8AAABIgYdARQGIyEiJj0BNCYjIgYdAR4BMyEyNj0BNCYnLgE3AS4BIyIGBwEGFBceATMyNjcBNjIXARYyNz4BAvcKDgsH/nUICw4KCQ8BJhsBjBsnBAMDCVT+5goYDQ0YCf7mBgcDCQUECAQBGQUQBQEaBxMHBgEBsA4J4gcLCwfiCQ4OCeIbJycb4gQJAwQDNAEaCgkJCf7lBxMGBAMDAwEZBQX+5wYHBhMAAwDcAXYDMQH6AAsAFwAjAAABMjY1NCYjIgYVFBYhMjY1NCYjIgYVFBYhMjY1NCYjIgYVFBYBHhwnJxwbJycBAxwnJxwbJycBBBsnJxscJycBdicbGycnGxsnJxsbJycbGycnGxsnJxsbJwAAAQDsAJ0DFALOACUAAAE3NjQnJiIPAScmIgcGFB8BBwYUFx4BMzI2PwEXHgEzMjY3NjQnAijsCQkIGAjs6ggYCAkJ6uwJCQQKBgULBOzsBAsFBgoECQkBu+oJFwkICOvrCAgIGAjr6wgYCAQEBATr7QUEBQQIFwkAAQBdAIwD0AL4AB4AAAEWFRYHAQYHBgcGIyIvASYvAQEmJzQ3PgEXCQE2MzYDwwwBDP3/BAUCAgcGCAcEAwMD/toJAQoMHQwBDAHoCw8PAu4LDRAL/dsEAgECAQECAgMCASELDg8NCQIL/vkCCAoBAAEAAAABAADLWb2BXw889QALBAAAAAAA1ZTIqwAAAADVlMirAF0AXgPQAxsAAAAIAAIAAAAAAAAAAQAAA8D/wAAABCoAXQBGA9AAAQAAAAAAAAAAAAAAAAAAAAkEAAAABAABYAQAAVgEAADQBAAAlwQAAL8EAADcBAAA7AQqAF0AAAAAACoAVACiARQBcAGmAeICGgABAAAACQBJAAMAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAADgCuAAEAAAAAAAEADgAAAAEAAAAAAAIABwAOAAEAAAAAAAMADgAVAAEAAAAAAAQADgAjAAEAAAAAAAUACwAxAAEAAAAAAAYADgA8AAEAAAAAAAoAGgBKAAMAAQQJAAEAHABkAAMAAQQJAAIADgCAAAMAAQQJAAMAHACOAAMAAQQJAAQAHACqAAMAAQQJAAUAFgDGAAMAAQQJAAYAHADcAAMAAQQJAAoANAD4c3RyZWFtaWNvbmZvbnRSZWd1bGFyc3RyZWFtaWNvbmZvbnRzdHJlYW1pY29uZm9udFZlcnNpb24gMS4wc3RyZWFtaWNvbmZvbnRGb250IGdlbmVyYXRlZCBieSBJY29Nb29uLgBzAHQAcgBlAGEAbQBpAGMAbwBuAGYAbwBuAHQAUgBlAGcAdQBsAGEAcgBzAHQAcgBlAGEAbQBpAGMAbwBuAGYAbwBuAHQAcwB0AHIAZQBhAG0AaQBjAG8AbgBmAG8AbgB0AFYAZQByAHMAaQBvAG4AIAAxAC4AMABzAHQAcgBlAGEAbQBpAGMAbwBuAGYAbwBuAHQARgBvAG4AdAAgAGcAZQBuAGUAcgBhAHQAZQBkACAAYgB5ACAASQBjAG8ATQBvAG8AbgAuAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQAJAAABAgEDAQQBBQEGAQcBCAEJAAAAAAAAAAA=') format('truetype')
}
html,
......
......@@ -19,10 +19,11 @@ const publishStateChange = (res) => {
const createDownloadTaskById = function (downloadTaskId, {
url,
header
header,
timeout = __uniConfig.networkTimeout.downloadFile ? __uniConfig.networkTimeout.downloadFile / 1000 : 120
} = {}) {
const downloader = plus.downloader.createDownload(url, {
time: __uniConfig.networkTimeout.downloadFile ? __uniConfig.networkTimeout.downloadFile / 1000 : 120,
timeout,
filename: TEMP_PATH + '/download/',
// 需要与其它平台上的表现保持一致,不走重试的逻辑。
retry: 0,
......@@ -91,4 +92,4 @@ export function operateDownloadTask ({
export function createDownloadTask (args) {
return createDownloadTaskById(++downloadTaskId, args)
}
}
......@@ -45,7 +45,8 @@ export function createRequestTaskById (requestTaskId, {
method = 'GET',
responseType,
sslVerify = true,
firstIpv4 = false
firstIpv4 = false,
timeout = __uniConfig.networkTimeout.request
} = {}) {
const stream = requireNativePlugin('stream')
const headers = {}
......@@ -77,7 +78,6 @@ export function createRequestTaskById (requestTaskId, {
headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'
}
const timeout = __uniConfig.networkTimeout.request
if (timeout) {
abortTimeout = setTimeout(() => {
aborted = true
......
......@@ -23,10 +23,11 @@ const createUploadTaskById = function (uploadTaskId, {
name,
files,
header,
formData
formData,
timeout = __uniConfig.networkTimeout.uploadFile ? __uniConfig.networkTimeout.uploadFile / 1000 : 120
} = {}) {
const uploader = plus.uploader.createUpload(url, {
timeout: __uniConfig.networkTimeout.uploadFile ? __uniConfig.networkTimeout.uploadFile / 1000 : 120,
timeout,
// 需要与其它平台上的表现保持一致,不走重试的逻辑。
retry: 0,
retryInterval: 0
......@@ -113,4 +114,4 @@ export function operateUploadTask ({
export function createUploadTask (args) {
return createUploadTaskById(++uploadTaskId, args)
}
}
<template>
<uni-tabbar
v-if="hasTabBar"
v-show="showTabBar"
>
<div
:style="{'flex-direction':direction==='vertical'?'column':'row',backgroundColor:tabBarOptions.backgroundColor}"
class="uni-tabbar"
>
<div
v-for="(item,index) in tabBarOptions.list"
:key="item.pagePath"
class="uni-tabbar__item"
@click="_switchTab(item,index)"
>
<div class="uni-tabbar__bd">
<div
v-if="showIcon && item.iconPath"
:class="{'uni-tabbar__icon__diff':!item.text}"
class="uni-tabbar__icon"
>
<img :src="_getRealPath(selectedIndex===index?item.selectedIconPath:item.iconPath)">
<div
v-if="item.redDot"
:class="{'uni-tabbar__badge':!!item.badge}"
class="uni-tabbar__reddot"
>
{{ item.badge }}
</div>
</div>
<div
v-if="item.text"
:style="{color:selectedIndex===index?tabBarOptions.selectedColor:tabBarOptions.color,fontSize:showIcon&&item.iconPath?'10px':'14px'}"
class="uni-tabbar__label"
>
{{ item.text }}
<div
v-if="item.redDot&&(!showIcon || !item.iconPath)"
:class="{'uni-tabbar__badge':!!item.badge}"
class="uni-tabbar__reddot"
>
{{ item.badge }}
</div>
</div>
</div>
</div>
</div>
</uni-tabbar>
</template>
<script>
import getRealPath from 'uni-platform/helpers/get-real-path'
import {
tabBar
} from './observable'
export default {
name: 'CustomTabBar',
props: {
selected: {
type: Number,
default: 0
},
showIcon: {
type: Boolean,
default: true
},
direction: {
type: String,
default: 'horizontal'
}
},
data () {
return {
selectedIndex: this.selected
}
},
computed: {
tabBarOptions () {
return tabBar
},
hasTabBar () {
return tabBar.list && tabBar.list.length
},
showTabBar () {
const app = getApp()
if (app) {
return !app.$children[0].hideTabBar
}
return true
}
},
watch: {
selected (val) {
this.selectedIndex = val
},
'$route' (to, from) {
if (to.meta.isTabBar) {
const index = tabBar.list.findIndex(item => to.meta.pagePath === item.pagePath)
if (index > -1) {
this.selectedIndex = index
}
}
}
},
methods: {
_getRealPath (filePath) {
if (filePath.indexOf('/') !== 0) {
filePath = '/' + filePath
}
return getRealPath(filePath)
},
_switchTab ({
text,
pagePath
}, index) {
this.selectedIndex = index
let url = '/' + pagePath
if (url === __uniRoutes[0].alias) {
url = '/'
}
const detail = {
index,
text,
pagePath
}
this.$emit('onTabItemTap', detail)
if (this.$route.path === url) {
UniServiceJSBridge.emit('onTabItemTap', detail)
}
}
}
}
</script>
<style>
</style>
<template>
<uni-app :class="{'uni-app--showtabbar':showTabBar}">
<uni-app :class="{'uni-app--showtabbar':showTabBar,'uni-app--maxwidth':showMaxWidth}">
<layout
ref="layout"
:router-key="key"
:keep-alive-include="keepAliveInclude"
@maxWidth="onMaxWidth"
/>
<tab-bar
v-if="hasTabBar"
v-show="showTabBar"
v-bind="tabBar"
v-bind="tabBarOptions"
/>
<toast
v-if="$options.components.Toast"
......@@ -46,6 +47,10 @@ import components from './components'
import mixins from 'uni-h5-app-mixins'
import {
tabBar
} from './observable'
export default {
name: 'App',
components,
......@@ -62,16 +67,19 @@ export default {
return {
transitionName: 'fade',
hideTabBar: false,
tabBar: __uniConfig.tabBar || {},
sysComponents: this.$sysComponents
sysComponents: this.$sysComponents,
showMaxWidth: false
}
},
computed: {
key () {
return this.$route.meta.name + '-' + this.$route.params.__id__ + '-' + (__uniConfig.reLaunch || 1)
},
tabBarOptions () {
return tabBar
},
hasTabBar () {
return __uniConfig.tabBar && __uniConfig.tabBar.list && __uniConfig.tabBar.list.length
return tabBar.list && tabBar.list.length
},
showTabBar () {
return this.$route.meta.isTabBar && !this.hideTabBar
......@@ -113,6 +121,11 @@ export default {
UniServiceJSBridge.emit('onAppEnterBackground')
}
})
},
methods: {
onMaxWidth (showMaxWidth) {
this.showMaxWidth = showMaxWidth
}
}
}
</script>
......
......@@ -134,6 +134,7 @@ export default {
},
data () {
return {
marginWidth: 0,
leftWindowStyle: '',
rightWindowStyle: '',
topWindowStyle: '',
......@@ -144,7 +145,8 @@ export default {
apiShowTopWindow: false,
apiShowLeftWindow: false,
apiShowRightWindow: false,
navigationBarTitleText: ''
navigationBarTitleText: '',
maxWidthMeidaQuery: false
}
},
computed: {
......@@ -175,11 +177,14 @@ export default {
}
},
watch: {
$route () {
this.checkMaxWidth()
},
showTopWindow (newVal, val) {
if (newVal) {
this.$nextTick(this.onTopWindowInit)
} else {
updateCssVar('--window-top', '0px')
updateCssVar('--top-window-height', '0px')
}
},
showLeftWindow (newVal, val) {
......@@ -195,12 +200,16 @@ export default {
} else {
updateCssVar('--window-right', '0px')
}
},
marginWidth (newVal) {
updateCssVar('--window-margin', newVal + 'px')
}
},
beforeCreate () {
updateCssVar('--window-top', '0px')
updateCssVar('--top-window-height', '0px')
updateCssVar('--window-left', '0px')
updateCssVar('--window-right', '0px')
updateCssVar('--window-margin', '0px')
},
created () {
this.topWindow = Vue.component('VUniTopWindow')
......@@ -230,6 +239,10 @@ export default {
})
}
}
this.initMaxWidth()
},
mounted () {
this.checkMaxWidth()
},
methods: {
resetApiShowWindow () {
......@@ -251,12 +264,49 @@ export default {
if (show) {
this.$nextTick(this.onTopWindowInit)
} else {
updateCssVar('--window-top', '0px')
updateCssVar('--top-window-height', '0px')
}
}
}
}
},
initMaxWidth () {
window.addEventListener('resize', () => {
this.checkMaxWidth()
})
},
checkMaxWidth () {
const windowWidth = document.body.clientWidth
const maxWidth = parseInt(this.$route.meta.maxWidth)
let showMaxWidth = false
if (windowWidth > maxWidth) {
showMaxWidth = true
} else {
showMaxWidth = false
}
this.$emit('maxWidth', showMaxWidth)
if (!this.$containerElem) {
this.$containerElem = document.querySelector('uni-app')
}
if (!this.$containerElem) {
return
}
if (showMaxWidth && maxWidth) {
this.marginWidth = (windowWidth - maxWidth) / 2
this.$nextTick(() => {
this.onLeftWindowInit()
this.onRightWindowInit()
this.$containerElem.setAttribute('style', 'max-width:' + maxWidth + 'px;margin:0 auto;')
})
} else {
this.marginWidth = 0
this.$nextTick(() => {
this.onLeftWindowInit()
this.onRightWindowInit()
this.$containerElem.removeAttribute('style')
})
}
},
initWindowMinWidth (type) {
const name = type + 'Window'
if (this[name]) {
......@@ -296,26 +346,28 @@ export default {
windowTopHeight = this.$refs.top.$el.offsetHeight + 'px'
}
this.topWindowHeight = windowTopHeight
updateCssVar('--window-top', windowTopHeight)
updateCssVar('--top-window-height', windowTopHeight)
},
onLeftWindowInit () {
if (!(this.responsive && this.leftWindow)) {
updateCssVar('--window-left', this.marginWidth + 'px')
return
}
if (this.leftWindowStyle && this.leftWindowStyle.width) {
updateCssVar('--window-left', this.$refs.leftWindow.offsetWidth + 'px')
updateCssVar('--window-left', this.$refs.leftWindow.offsetWidth + this.marginWidth + 'px')
} else {
updateCssVar('--window-left', this.$refs.left.$el.offsetWidth + 'px')
updateCssVar('--window-left', this.$refs.left.$el.offsetWidth + this.marginWidth + 'px')
}
},
onRightWindowInit () {
if (!(this.responsive && this.rightWindow)) {
updateCssVar('--window-right', this.marginWidth + 'px')
return
}
if (this.rightWindowStyle && this.rightWindowStyle.width) {
updateCssVar('--window-right', this.$refs.rightWindow.offsetWidth + 'px')
updateCssVar('--window-right', this.$refs.rightWindow.offsetWidth + this.marginWidth + 'px')
} else {
updateCssVar('--window-right', this.$refs.right.$el.offsetWidth + 'px')
updateCssVar('--window-right', this.$refs.right.$el.offsetWidth + this.marginWidth + 'px')
}
}
}
......@@ -335,7 +387,7 @@ export default {
}
uni-top-window+uni-content {
height: calc(100vh - var(--window-top));
height: calc(100vh - var(--top-window-height));
}
uni-left-window {
......@@ -377,8 +429,8 @@ export default {
.uni-top-window {
position: fixed;
left: 0;
right: 0;
left: var(--window-margin);
right: var(--window-margin);
top: 0;
z-index: 998;
overflow: hidden;
......
import Vue from 'vue'
__uniConfig.tabBar = Vue.observable(__uniConfig.tabBar || {})
export const tabBar = __uniConfig.tabBar
......@@ -40,9 +40,9 @@
</div>
<div :style="popupStyle.triangle" />
</div>
<keypress
:disable="!visible"
@esc="_close(-1)"
<keypress
:disable="!visible"
@esc="_close(-1)"
/>
</uni-actionsheet>
</template>
......@@ -154,7 +154,7 @@ uni-actionsheet .uni-actionsheet__cell:first-child:before {
display: none;
}
@media screen and (min-width: 500px) {
@media screen and (min-width: 500px) and (min-height: 500px) {
.uni-mask.uni-actionsheet__mask {
background: none;
}
......
......@@ -6,6 +6,9 @@ export default {
}
},
computed: {
isDesktop () {
return this.popupWidth >= 500 && this.popupHeight >= 500
},
popupStyle () {
const style = {}
const contentStyle = style.content = {}
......@@ -14,7 +17,7 @@ export default {
function getNumber (value) {
return Number(value) || 0
}
if (this.popupWidth >= 500 && popover) {
if (this.isDesktop && popover) {
Object.assign(triangleStyle, {
position: 'absolute',
width: '0',
......
<template>
<uni-tabbar>
<uni-tabbar :class="['uni-tabbar-'+position]">
<div
:style="{backgroundColor:backgroundColor}"
class="uni-tabbar"
class="uni-tabbar"
>
<div
:style="{backgroundColor:borderColor}"
class="uni-tabbar-border"
class="uni-tabbar-border"
/>
<div
v-for="(item,index) in list"
......@@ -20,15 +20,13 @@
:class="{'uni-tabbar__icon__diff':!item.text}"
class="uni-tabbar__icon"
>
<img
:src="_getRealPath($route.meta.pagePath===item.pagePath?item.selectedIconPath:item.iconPath)"
>
<img :src="_getRealPath($route.meta.pagePath===item.pagePath?item.selectedIconPath:item.iconPath)">
<div
v-if="item.redDot"
:class="{'uni-tabbar__badge':!!item.badge}"
class="uni-tabbar__reddot"
>
{{ item.badge }}
>
{{ item.badge }}
</div>
</div>
<div
......@@ -41,8 +39,8 @@
v-if="item.redDot&&!item.iconPath"
:class="{'uni-tabbar__badge':!!item.badge}"
class="uni-tabbar__reddot"
>
{{ item.badge }}
>
{{ item.badge }}
</div>
</div>
</div>
......@@ -53,125 +51,130 @@
</template>
<style>
uni-tabbar {
display: block;
box-sizing: border-box;
position: fixed;
left: 0;
bottom: 0;
width: 100%;
z-index: 998;
}
uni-tabbar {
display: block;
box-sizing: border-box;
width: 100%;
z-index: 998;
}
uni-tabbar .uni-tabbar {
display: flex;
position: fixed;
left: 0;
bottom: 0;
width: 100%;
z-index: 998;
box-sizing: border-box;
padding-bottom: 0;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
uni-tabbar .uni-tabbar {
display: flex;
z-index: 998;
box-sizing: border-box;
}
uni-tabbar .uni-tabbar ~ .uni-placeholder {
width: 100%;
height: 50px;
margin-bottom: 0;
margin-bottom: constant(safe-area-inset-bottom);
margin-bottom: env(safe-area-inset-bottom);
}
uni-tabbar.uni-tabbar-top,
uni-tabbar.uni-tabbar-bottom,
uni-tabbar.uni-tabbar-top .uni-tabbar,
uni-tabbar.uni-tabbar-bottom .uni-tabbar {
position: fixed;
left: var(--window-left);
right: var(--window-right);
}
uni-tabbar .uni-tabbar * {
box-sizing: border-box;
}
uni-tabbar.uni-tabbar-bottom .uni-tabbar {
bottom: 0;
padding-bottom: 0;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
uni-tabbar .uni-tabbar__item {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
flex: 1;
font-size: 0;
text-align: center;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
uni-tabbar .uni-tabbar~.uni-placeholder {
width: 100%;
height: 50px;
margin-bottom: 0;
margin-bottom: constant(safe-area-inset-bottom);
margin-bottom: env(safe-area-inset-bottom);
}
uni-tabbar .uni-tabbar__bd {
position: relative;
height: 50px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
}
uni-tabbar .uni-tabbar * {
box-sizing: border-box;
}
uni-tabbar .uni-tabbar__icon {
position: relative;
display: inline-block;
margin-top: 5px;
width: 24px;
height: 24px;
}
uni-tabbar .uni-tabbar__item {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
flex: 1;
font-size: 0;
text-align: center;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
uni-tabbar .uni-tabbar__icon.uni-tabbar__icon__diff {
margin-top: 0px;
width: 34px;
height: 34px;
}
uni-tabbar .uni-tabbar__bd {
position: relative;
height: 50px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
}
uni-tabbar .uni-tabbar__icon img {
width: 100%;
height: 100%;
}
uni-tabbar .uni-tabbar__icon {
position: relative;
display: inline-block;
margin-top: 5px;
width: 24px;
height: 24px;
}
uni-tabbar .uni-tabbar__label {
position: relative;
text-align: center;
font-size: 10px;
line-height: 1.8;
}
uni-tabbar .uni-tabbar__icon.uni-tabbar__icon__diff {
margin-top: 0px;
width: 34px;
height: 34px;
}
uni-tabbar .uni-tabbar-border {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 1px;
transform: scaleY(0.5);
}
uni-tabbar .uni-tabbar__icon img {
width: 100%;
height: 100%;
}
uni-tabbar .uni-tabbar__reddot {
position: absolute;
top: 0;
right: 0;
width: 12px;
height: 12px;
border-radius: 50%;
background-color: #f43530;
color: #ffffff;
transform: translate(40%, -20%);
}
uni-tabbar .uni-tabbar__label {
position: relative;
text-align: center;
font-size: 10px;
line-height: 1.8;
}
uni-tabbar .uni-tabbar__badge {
width: auto;
height: 16px;
line-height: 16px;
border-radius: 16px;
min-width: 16px;
padding: 0 2px;
font-size: 12px;
text-align: center;
white-space: nowrap;
}
uni-tabbar .uni-tabbar-border {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 1px;
transform: scaleY(0.5);
}
uni-tabbar .uni-tabbar__reddot {
position: absolute;
top: 0;
right: 0;
width: 12px;
height: 12px;
border-radius: 50%;
background-color: #f43530;
color: #ffffff;
transform: translate(40%, -20%);
}
uni-tabbar .uni-tabbar__badge {
width: auto;
height: 16px;
line-height: 16px;
border-radius: 16px;
min-width: 16px;
padding: 0 2px;
font-size: 12px;
text-align: center;
white-space: nowrap;
}
</style>
<script>
import getRealPath from 'uni-platform/helpers/get-real-path'
export default {
name: 'TabBar',
props: {
......@@ -225,9 +228,6 @@ export default {
},
methods: {
_getRealPath (filePath) {
if (filePath.indexOf('/') !== 0) {
filePath = '/' + filePath
}
return getRealPath(filePath)
},
_switchTab ({
......
......@@ -4,6 +4,7 @@ import App from './app'
import Page from './page'
import AsyncError from './async-error'
import AsyncLoading from './async-loading'
import CustomTabBar from './app/customTabBar'
import SystemRouteComponents from 'uni-h5-system-routes'
......@@ -11,6 +12,7 @@ Vue.component(App.name, App)
Vue.component(Page.name, Page)
Vue.component(AsyncError.name, AsyncError)
Vue.component(AsyncLoading.name, AsyncLoading)
Vue.component(CustomTabBar.name, CustomTabBar)
Object.keys(SystemRouteComponents).forEach(name => {
const Component = SystemRouteComponents[name]
......
<template>
<uni-page :data-page="$route.meta.pagePath">
<page-head
v-if="!showTopWindow && navigationBar.type!=='none'"
v-if="navigationBar.type!=='none'"
v-bind="navigationBar"
/>
<page-refresh
......@@ -241,15 +241,6 @@ export default {
refreshOptions
}
},
computed: {
showTopWindow () {
try {
const appLayout = getApp().$children[0].$children[0]
return appLayout && appLayout.showTopWindow
} catch (e) {}
return false
}
},
created () {
const navigationBar = this.navigationBar
document.title = navigationBar.titleText
......
......@@ -118,7 +118,6 @@
position: fixed;
left: var(--window-left);
right: var(--window-right);
top: 0;
height: 44px;
height: calc(44px + constant(safe-area-inset-top));
height: calc(44px + env(safe-area-inset-top));
......
<template>
<div class="uni-system-choose-location">
<system-header
:confirm="!!data"
@back="_back"
@confirm="_choose"
<v-uni-map
v-if="latitude"
:latitude="latitude"
:longitude="longitude"
class="map"
show-location
@regionchange="_regionchange"
>
选择位置
</system-header>
<div class="map-content">
<iframe
:src="src"
allow="geolocation"
seamless
sandbox="allow-scripts allow-same-origin allow-forms"
frameborder="0"
/>
<div class="map-location" />
<div
class="map-move"
@click="_moveToLocation"
>
<i>&#xec32;</i>
</div>
</v-uni-map>
<div class="nav">
<div
class="nav-btn back"
@click="_back"
>
<i class="uni-btn-icon">&#xe650;</i>
</div>
<div
class="nav-btn confirm"
:class="{ disable: !selected }"
@click="_choose"
>
<i class="uni-btn-icon">&#xe651;</i>
</div>
</div>
<div class="menu">
<div class="search">
<v-uni-input
v-model="keyword"
class="search-input"
placeholder="搜索地点"
@focus="searching = true"
@input="_input"
/>
<div
v-if="searching"
class="search-btn"
@click="
searching = false;
keyword = '';
"
>
取消
</div>
</div>
<v-uni-scroll-view
scroll-y
class="list"
@scrolltolower="_scrolltolower"
>
<div
v-if="loading"
class="list-loading"
>
<i class="uni-loading" />
</div>
<div
v-for="(item, index) in list"
:key="index"
class="list-item"
:class="{ selected: selectedIndex === index }"
@click="
selectedIndex = index;
latitude = item.latitude;
longitude = item.longitude;
"
>
<div class="list-item-title">
{{ item.name }}
</div>
<div class="list-item-detail">
{{ item.distance ? item.distance + "米 | " : "" }}{{ item.address }}
</div>
</div>
</v-uni-scroll-view>
</div>
</div>
</template>
<script>
import SystemHeader from '../system-header'
import {
debounce
} from 'uni-shared'
import {
getJSONP
} from 'uni-platform/helpers/get-jsonp'
const key = __uniConfig.qqMapKey
export default {
name: 'SystemChooseLocation',
components: {
SystemHeader
},
data () {
const key = __uniConfig.qqMapKey
return {
src: `https://apis.map.qq.com/tools/locpicker?search=1&type=1&key=${key}&referer=uniapp`,
data: null
latitude: 0,
longitude: 0,
pageSize: 15,
pageIndex: 1,
selectedIndex: -1,
list: [],
keyword: '',
searching: false,
loading: true
}
},
mounted () {
function handler (event) {
var loc = event.data
if (loc && loc.module === 'locationPicker') {
this.data = {
name: loc.poiname,
address: loc.poiaddress,
latitude: loc.latlng.lat,
longitude: loc.latlng.lng
}
}
computed: {
selected () {
return this.list[this.selectedIndex]
}
this.__messageHandle = handler.bind(this)
window.addEventListener('message', this.__messageHandle, false)
},
created () {
this._moveToLocation()
this._search = debounce(() => {
this._reset()
if (this.keyword) {
this._getList()
}
}, 1000)
this.$watch('searching', val => {
this._reset()
if (!val) {
this._getList()
}
})
},
beforeDestroy () {
window.removeEventListener('message', this.__messageHandle, false)
},
methods: {
_choose () {
if (this.data) {
UniViewJSBridge.publishHandler('onChooseLocation', Object.assign({}, this.data))
if (this.selected) {
UniViewJSBridge.publishHandler('onChooseLocation', Object.assign({}, this.selected))
getApp().$router.back()
}
},
_back () {
UniViewJSBridge.publishHandler('onChooseLocation', null)
getApp().$router.back()
},
_moveToLocation () {
uni.getLocation({
type: 'gcj02',
success: this._move.bind(this),
fail: () => {
this._move({
latitude: 39.90960456049752,
longitude: 116.3972282409668
})
}
})
},
_regionchange ({ detail: { centerLocation } }) {
if (centerLocation) {
// TODO 图钉 icon 动画
this._move(centerLocation)
}
},
_pushData (array) {
array.forEach(item => {
this.list.push({
name: item.title,
address: item.address,
distance: item._distance,
latitude: item.location.lat,
longitude: item.location.lng
})
})
},
_getList () {
this.loading = true
const url = this.searching ? `https://apis.map.qq.com/ws/place/v1/search?output=jsonp&key=${key}&boundary=nearby(${this.latitude},${this.longitude},1000)&keyword=${this.keyword}&page_size=${this.pageSize}&page_index=${this.pageIndex}` : `https://apis.map.qq.com/ws/geocoder/v1/?output=jsonp&key=${key}&location=${this.latitude},${this.longitude}&get_poi=1&poi_options=page_size=${this.pageSize};page_index=${this.pageIndex}`
// TODO 列表加载失败提示
getJSONP(url, {
callback: 'callback'
}, (res) => {
this.loading = false
if (this.searching && 'data' in res && res.data.length) {
this._pushData(res.data)
} else if ('result' in res && res.result.pois) {
this._pushData(res.result.pois)
}
}, () => {
this.loading = false
})
},
_scrolltolower () {
if (!this.loading && this.list.length === this.pageSize * this.pageIndex) {
this.pageIndex++
this._getList()
}
},
_reset () {
this.selectedIndex = -1
this.pageIndex = 1
this.list = []
},
_move ({ latitude, longitude }) {
this.latitude = latitude
this.longitude = longitude
if (!this.searching) {
this._reset()
this._getList()
}
},
_input () {
this._search()
}
}
}
</script>
<style>
.uni-system-choose-location {
display: block;
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: #f8f8f8;
}
.map-content {
position: absolute;
left: 0;
top: 44px;
width: 100%;
bottom: 0;
overflow: hidden;
}
.map-content>iframe {
width: 100%;
height: 100%;
}
<style scoped>
@font-face {
font-weight: normal;
font-style: normal;
font-family: "unimapbtn";
src: url("data:application/octet-stream;base64,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")
format("truetype");
}
.uni-system-choose-location {
display: block;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: #f8f8f8;
}
.map {
position: absolute;
top: -40px;
left: 0;
width: 100%;
height: 380px;
}
.map-location {
position: absolute;
left: 50%;
bottom: 50%;
width: 32px;
height: 52px;
margin-left: -16px;
cursor: pointer;
background-image: url("data:image/png;base64,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");
background-size: 100%;
}
.map-move {
position: absolute;
bottom: 50px;
right: 10px;
width: 40px;
height: 40px;
box-sizing: border-box;
line-height: 40px;
background-color: white;
border-radius: 50%;
pointer-events: auto;
cursor: pointer;
box-shadow: 0px 0 5px 1px rgba(0, 0, 0, 0.3);
}
.map-move > i {
display: block;
width: 100%;
height: 100%;
font: normal normal normal 14px/1 "unimapbtn";
line-height: inherit;
text-align: center;
font-size: 24px;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
}
.nav {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 44px;
background-color: transparent;
background-image: linear-gradient(
to bottom,
rgba(0, 0, 0, 0.3),
rgba(0, 0, 0, 0)
);
}
.nav-btn {
position: absolute;
box-sizing: border-box;
top: 0;
left: 0;
width: 60px;
height: 44px;
padding: 6px;
line-height: 32px;
font-size: 26px;
color: white;
text-align: center;
cursor: pointer;
}
.nav-btn.confirm {
left: auto;
right: 0;
}
.nav-btn.disable {
opacity: 0.4;
}
.nav-btn > .uni-btn-icon {
display: block;
width: 100%;
height: 100%;
line-height: inherit;
border-radius: 2px;
}
.nav-btn.confirm > .uni-btn-icon {
background-color: #007aff;
}
.menu {
position: absolute;
top: 300px;
left: 0;
width: 100%;
bottom: 0;
background-color: white;
}
.search {
display: flex;
flex-direction: row;
height: 50px;
padding: 8px;
line-height: 34px;
box-sizing: border-box;
background-color: white;
}
.search-input {
flex: 1;
height: 100%;
border-radius: 5px;
padding: 0 5px;
background: #ebebeb;
}
.search-btn {
width: 2.8em;
color: #007aff;
font-size: 17px;
text-align: center;
}
.list {
position: absolute;
top: 50px;
left: 0;
width: 100%;
bottom: 0;
padding-bottom: 10px;
/* background-color: #f6f6f6; */
}
.list-loading {
display: flex;
height: 50px;
justify-content: center;
align-items: center;
}
.list-item {
position: relative;
padding: 10px;
padding-right: 40px;
}
.list-item.selected::before {
position: absolute;
top: 50%;
right: 10px;
width: 30px;
height: 30px;
margin-top: -15px;
text-align: center;
content: "\e651";
font: normal normal normal 14px/1 "unibtn";
font-size: 24px;
line-height: 30px;
color: #007aff;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
}
.list-item:not(:last-child)::after {
position: absolute;
content: "";
height: 1px;
left: 10px;
bottom: 0;
width: 100%;
background-color: #d3d3d3;
}
.list-item-title {
font-size: 14px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.list-item-detail {
font-size: 12px;
color: #808080;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
@media screen and (min-width: 800px) {
.map {
top: 0;
height: 100%;
}
.map-move {
bottom: 10px;
right: 320px;
}
.menu {
top: 54px;
left: auto;
right: 10px;
width: 300px;
bottom: 10px;
max-height: 600px;
box-shadow: 0px 0 20px 5px rgba(0, 0, 0, 0.3);
}
}
</style>
<template>
<div class="uni-system-open-location">
<system-header @back="_back">
位置
</system-header>
<div class="map-content">
<div
class="map-content"
:class="{ 'fix-position': isPoimarkerSrc }"
>
<iframe
ref="map"
:src="src"
allow="geolocation"
sandbox="allow-scripts allow-same-origin allow-forms allow-top-navigation allow-modals allow-popups"
frameborder="0"
@load="_load"
@load="_check"
/>
<!-- 去这里 -->
<div
......@@ -19,20 +19,21 @@
@click="_nav"
/>
</div>
<div
class="nav-btn-back"
@click="_back"
>
<i class="uni-btn-icon">&#xe601;</i>
</div>
</div>
</template>
<script>
import SystemHeader from '../system-header'
const key = __uniConfig.qqMapKey
const referer = 'uniapp'
const poimarkerSrc = 'https://apis.map.qq.com/tools/poimarker'
export default {
name: 'SystemOpenLocation',
components: {
SystemHeader
},
data () {
const {
latitude,
......@@ -48,7 +49,7 @@ export default {
name,
address,
src: latitude && longitude ? `${poimarkerSrc}?type=0&marker=coord:${latitude},${longitude};title:${name};addr:${address};&key=${key}&referer=${referer}` : '',
isPoimarkerSrc: false
isPoimarkerSrc: true
}
},
methods: {
......@@ -58,8 +59,9 @@ export default {
} else {
getApp().$router.back()
}
this._check()
},
_load () {
_check () {
if (this.$refs.map.src.indexOf(poimarkerSrc) === 0) {
this.isPoimarkerSrc = true
} else {
......@@ -68,44 +70,73 @@ export default {
},
_nav () {
var url =
`https://map.qq.com/nav/drive#routes/page?transport=2&epointy=${this.latitude}&epointx=${this.longitude}&eword=${encodeURIComponent(this.name || '目的地')}&referer=${referer}`
`https://map.qq.com/nav/drive#routes/page?transport=2&epointy=${this.latitude}&epointx=${this.longitude}&eword=${encodeURIComponent(this.name || '目的地')}&referer=${referer}`
this.$refs.map.src = url
}
}
}
</script>
<style>
.uni-system-open-location {
display: block;
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: #f8f8f8;
}
<style scoped>
.uni-system-open-location {
display: block;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: #f8f8f8;
}
.map-content {
position: absolute;
left: 0;
top: 44px;
width: 100%;
bottom: 0;
overflow: hidden;
}
.nav-btn-back {
position: absolute;
box-sizing: border-box;
top: 0;
left: 0;
width: 44px;
height: 44px;
padding: 6px;
line-height: 32px;
font-size: 26px;
color: white;
text-align: center;
cursor: pointer;
}
.map-content>iframe {
width: 100%;
height: 100%;
border: none;
}
.nav-btn-back > .uni-btn-icon {
display: block;
width: 100%;
height: 100%;
line-height: inherit;
border-radius: 50%;
background-color: rgba(0, 0, 0, 0.5);
}
.actTonav {
position: absolute;
right: 16px;
bottom: 56px;
width: 60px;
height: 60px;
border-radius: 60px;
}
.map-content {
position: absolute;
left: 0;
top: 0px;
width: 100%;
bottom: 0;
overflow: hidden;
}
.map-content.fix-position {
top: -74px;
bottom: -44px;
}
.map-content > iframe {
width: 100%;
height: 100%;
border: none;
}
.actTonav {
position: absolute;
right: 16px;
bottom: 56px;
width: 60px;
height: 60px;
border-radius: 60px;
}
</style>
<template>
<div class="system-header">
<div class="header-text">
<slot />
</div>
<div
class="header-btn header-back uni-btn-icon header-btn-icon"
@click="_back"
>
&#xe601;
</div>
<div
v-if="confirm"
class="header-btn header-confirm"
@click="_confirm"
>
<svg
class="header-btn-img"
width="200px"
height="200.00px"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M939.6960642844446 226.08613831111114c-14.635971697777777-13.725872355555557-37.719236835555556-13.070208568888889-51.445109191111115 1.6029502577777779L402.69993870222225 744.6571451733333 137.46159843555557 483.31364238222227c-14.344349013333334-14.12709944888889-37.392384-13.98030904888889-51.51948344888889 0.3640399644444444-14.12709944888889 14.30911886222222-13.945078897777778 37.392384 0.40122709333333334 51.482296319999996l291.8171704888889 287.48392106666665c0.10960327111111111 0.10960327111111111 0.2544366933333333 0.1448334222222222 0.3640399644444444 0.2544366933333333s0.1448334222222222 0.2544366933333333 0.2544366933333333 0.3640399644444444c2.293843057777778 2.1842397866666667 5.061329351111111 3.4231500799999997 7.719212373333333 4.879309937777777 1.3113264355555554 0.7652670577777777 2.43867648 1.8926159644444445 3.822419057777778 2.43867648 4.2960634311111106 1.6753664 8.846562417777779 2.548279751111111 13.361832391111111 2.548279751111111 4.769706666666666 0 9.539412195555554-0.9472864711111111 13.98030904888889-2.839903573333333 1.4933469866666664-0.6184766577777778 2.6578830222222223-1.8926159644444445 4.0416267377777775-2.6950701511111115 2.7302991644444448-1.6029502577777779 5.5702027377777785-2.9495068444444446 7.901232924444444-5.315766044444445 0.10960327111111111-0.10960327111111111 0.1448334222222222-0.2916238222222222 0.2544366933333333-0.40122709333333334 0.07241614222222222-0.10960327111111111 0.21920654222222222-0.1448334222222222 0.3268528355555555-0.2544366933333333L941.2579134577779 277.5273335466667C955.0953460622222 262.9305059555556 954.3320359822221 239.8844279466666 939.6960642844446 226.08613831111114z" />
</svg>
</div>
</div>
</template>
<script>
export default {
name: 'SystemHeader',
props: {
confirm: {
type: Boolean,
default: false
}
},
created () {
document.title = this.$slots.default[0].text
UniServiceJSBridge.emit('onNavigationBarChange', {
titleText: document.title,
textColor: '#fff',
backgroundColor: '#000'
})
},
methods: {
_back () {
this.$emit('back')
},
_confirm () {
this.$emit('confirm')
}
}
}
</script>
<style>
.system-header {
position: relative;
width: 100%;
height: 44px;
color: #fff;
background-color: black;
padding: 0 44px;
text-align: center;
line-height: 44px;
font-size: 16px;
box-sizing: border-box;
}
.system-header * {
box-sizing: border-box;
}
.header-text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.header-btn {
position: absolute;
width: 44px;
height: 44px;
top: 0;
}
.header-btn-img {
fill: #fff;
width: 50%;
height: 50%;
margin: 25%;
}
.header-back {
left: 0;
}
.header-confirm {
right: 0;
}
.header-btn-icon {
font-size: 27px;
line-height: 44px;
}
</style>
......@@ -11,8 +11,9 @@ export default function getWindowOffset () {
const bottom = parseInt((style.getPropertyValue('--window-bottom').match(/\d+/) || ['0'])[0])
const left = parseInt((style.getPropertyValue('--window-left').match(/\d+/) || ['0'])[0])
const right = parseInt((style.getPropertyValue('--window-right').match(/\d+/) || ['0'])[0])
const topWindowHeight = parseInt((style.getPropertyValue('--top-window-height').match(/\d+/) || ['0'])[0])
return {
top: top ? (top + safeAreaInsets.top) : 0,
top: (top ? (top + safeAreaInsets.top) : 0) + (topWindowHeight || 0),
bottom: bottom ? (bottom + safeAreaInsets.bottom) : 0,
left: left ? (left + safeAreaInsets.left) : 0,
right: right ? (right + safeAreaInsets.right) : 0
......
import {
getJSONP
} from '../../../helpers/get-jsonp'
/**
* wgs84坐标转Gcj02坐标
* @param {object} coords
* @param {Function} success
* @param {Function} error
*/
function wgs84ToGcj02 (coords, success, error) {
/**
* uniapp 内置key
*/
var key = __uniConfig.qqMapKey
var url =
`https://apis.map.qq.com/ws/coord/v1/translate?locations=${coords.latitude},${coords.longitude}&type=1&key=${key}&output=jsonp`
getJSONP(url, {}, (res) => {
if ('locations' in res && res.locations.length) {
success({
longitude: res.locations[0].lng,
latitude: res.locations[0].lat
})
} else {
error(res)
}
}, error)
}
/**
* 获取定位信息
* @param {*} param0
* @param {*} options
* @param {*} callbackId
*/
export function getLocation ({
......@@ -37,38 +14,62 @@ export function getLocation ({
const {
invokeCallbackHandler: invoke
} = UniServiceJSBridge
const key = __uniConfig.qqMapKey
function callback (coords) {
new Promise((resolve, reject) => {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(res => resolve(res.coords), reject, {
enableHighAccuracy: altitude,
timeout: 1000 * 100
})
} else {
reject(new Error('device nonsupport geolocation'))
}
}).catch(() => {
return new Promise((resolve, reject) => {
getJSONP(`https://apis.map.qq.com/ws/location/v1/ip?output=jsonp&key=${key}`, {
callback: 'callback'
}, (res) => {
if ('result' in res && res.result.location) {
const location = res.result.location
resolve({
latitude: location.lat,
longitude: location.lng
}, true)
} else {
reject(new Error(res.message || JSON.stringify(res)))
}
}, () => reject(new Error('network error')))
})
}).then((coords, skip) => {
if (type.toUpperCase() === 'WGS84' || skip) {
return coords
}
return new Promise((resolve, reject) => {
getJSONP(`https://apis.map.qq.com/jsapi?qt=translate&type=1&points=${coords.longitude},${coords.latitude}&key=${key}&output=jsonp&pf=jsapi&ref=jsapi`, {
callback: 'cb'
}, (res) => {
if ('detail' in res && 'points' in res.detail && res.detail.points.length) {
const location = res.detail.points[0]
resolve(Object.assign({}, coords, {
longitude: location.lng,
latitude: location.lat
}))
} else {
resolve(coords)
}
}, () => resolve(coords))
})
}).then(coords => {
invoke(callbackId, Object.assign(coords, {
errMsg: 'getLocation:ok',
verticalAccuracy: coords.altitudeAccuracy || 0,
// 无专门水平精度,使用位置精度替代
horizontalAccuracy: coords.accuracy
}))
}
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition((position) => {
var coords = position.coords
if (type === 'WGS84') {
callback(coords)
} else {
wgs84ToGcj02(coords, callback, (err) => {
invoke(callbackId, {
errMsg: 'getLocation:fail ' + JSON.stringify(err)
})
})
}
}, () => {
invoke(callbackId, {
errMsg: 'getLocation:fail'
})
}, {
enableHighAccuracy: altitude,
timeout: 1000 * 60 * 5
})
} else {
}).catch(error => {
invoke(callbackId, {
errMsg: 'getLocation:fail device nonsupport geolocation'
errMsg: 'getLocation:fail ' + error.message
})
}
}
})
}
import { fileToUrl } from 'uni-platform/helpers/file'
import _createInput from './create_input'
const {
invokeCallbackHandler: invoke
} = UniServiceJSBridge
let fileInput = null
export function chooseFile ({
// sizeType,
count,
sourceType,
type,
extension
}, callbackId) {
// TODO handle sizeType 尝试通过 canvas 压缩
if (fileInput) {
document.body.removeChild(fileInput)
fileInput = null
}
fileInput = _createInput({
count,
sourceType,
type,
extension
})
document.body.appendChild(fileInput)
fileInput.addEventListener('change', function (event) {
const tempFiles = []
const fileCount = event.target.files.length
for (let i = 0; i < fileCount; i++) {
const file = event.target.files[i]
let filePath
Object.defineProperty(file, 'path', {
get () {
filePath = filePath || fileToUrl(file)
return filePath
}
})
if (i < count) tempFiles.push(file)
}
const res = {
errMsg: 'chooseFile:ok',
get tempFilePaths () {
return tempFiles.map(({ path }) => path)
},
tempFiles: tempFiles
}
invoke(callbackId, res)
// TODO 用户取消选择时,触发 fail,目前尚未找到合适的方法。
})
fileInput.click()
}
import { fileToUrl } from 'uni-platform/helpers/file'
import { updateElementStyle } from 'uni-shared'
import _createInput from './create_input'
const {
invokeCallbackHandler: invoke
......@@ -7,34 +7,11 @@ const {
let imageInput = null
const _createInput = function (options) {
const inputEl = document.createElement('input')
inputEl.type = 'file'
updateElementStyle(inputEl, {
position: 'absolute',
visibility: 'hidden',
'z-index': -999,
width: 0,
height: 0,
top: 0,
left: 0
})
inputEl.accept = 'image/*'
if (options.count > 1) {
inputEl.multiple = 'multiple'
}
// 经过测试,仅能限制只通过相机拍摄,不能限制只允许从相册选择。
if (options.sourceType.length === 1 && options.sourceType[0] === 'camera') {
inputEl.capture = 'camera'
}
return inputEl
}
export function chooseImage ({
count,
// sizeType,
sourceType
sourceType,
extension
}, callbackId) {
// TODO handle sizeType 尝试通过 canvas 压缩
......@@ -44,8 +21,10 @@ export function chooseImage ({
}
imageInput = _createInput({
count: count,
sourceType: sourceType
count,
sourceType,
extension,
type: 'image'
})
document.body.appendChild(imageInput)
......@@ -61,7 +40,7 @@ export function chooseImage ({
return filePath
}
})
tempFiles.push(file)
if (i < count) tempFiles.push(file)
}
const res = {
errMsg: 'chooseImage:ok',
......
import { fileToUrl, revokeObjectURL } from 'uni-platform/helpers/file'
import { updateElementStyle } from 'uni-shared'
import _createInput from './create_input'
const {
invokeCallbackHandler: invoke
......@@ -7,28 +7,9 @@ const {
let videoInput = null
const _createInput = function (options) {
const inputEl = document.createElement('input')
inputEl.type = 'file'
updateElementStyle(inputEl, {
position: 'absolute',
visibility: 'hidden',
'z-index': -999,
width: 0,
height: 0,
top: 0,
left: 0
})
inputEl.accept = 'video/*'
// 经过测试,仅能限制只通过相机拍摄,不能限制只允许从相册选择。
if (options.sourceType.length === 1 && options.sourceType[0] === 'camera') {
inputEl.capture = 'camera'
}
return inputEl
}
export function chooseVideo ({
sourceType
sourceType,
extension
}, callbackId) {
if (videoInput) {
document.body.removeChild(videoInput)
......@@ -36,7 +17,9 @@ export function chooseVideo ({
}
videoInput = _createInput({
sourceType: sourceType
sourceType: sourceType,
extension,
type: 'video'
})
document.body.appendChild(videoInput)
......
import { updateElementStyle } from 'uni-shared'
export default function ({ count, sourceType, type, extension }) {
const inputEl = document.createElement('input')
inputEl.type = 'file'
updateElementStyle(inputEl, {
position: 'absolute',
visibility: 'hidden',
'z-index': -999,
width: 0,
height: 0,
top: 0,
left: 0
})
inputEl.accept = extension.map(item => {
if (type !== '*') {
// 剔除.拼接在type后
return `${type}/${item.replace('.', '')}`
} else {
// 在后缀前方加上.
return item.indexOf('.') === 0 ? item : `.${item}`
}
}).join(',')
if (count > 1) {
inputEl.multiple = 'multiple'
}
// 经过测试,仅能限制只通过相机拍摄,不能限制只允许从相册选择。
if (sourceType.length === 1 && sourceType[0] === 'camera') {
inputEl.capture = 'camera'
}
return inputEl
}
......@@ -45,9 +45,9 @@ class DownloadTask {
*/
export function downloadFile ({
url,
header
header,
timeout = (__uniConfig.networkTimeout && __uniConfig.networkTimeout.downloadFile) || 60 * 1000
}, callbackId) {
var timeout = (__uniConfig.networkTimeout && __uniConfig.networkTimeout.downloadFile) || 60 * 1000
const {
invokeCallbackHandler: invoke
} = UniServiceJSBridge
......
......@@ -51,13 +51,13 @@ export function request ({
method,
dataType,
responseType,
withCredentials
withCredentials,
timeout = (__uniConfig.networkTimeout && __uniConfig.networkTimeout.request) || 60 * 1000
}, callbackId) {
const {
invokeCallbackHandler: invoke
} = UniServiceJSBridge
var body = null
var timeout = (__uniConfig.networkTimeout && __uniConfig.networkTimeout.request) || 60 * 1000
// 根据请求类型处理数据
var contentType
for (const key in header) {
......@@ -152,4 +152,4 @@ export function request ({
xhr.withCredentials = withCredentials
xhr.send(body)
return requestTask
}
}
......@@ -56,9 +56,9 @@ export function uploadFile ({
name,
files,
header,
formData
formData,
timeout = (__uniConfig.networkTimeout && __uniConfig.networkTimeout.uploadFile) || 60 * 1000
}, callbackId) {
var timeout = (__uniConfig.networkTimeout && __uniConfig.networkTimeout.uploadFile) || 60 * 1000
const {
invokeCallbackHandler: invoke
} = UniServiceJSBridge
......
......@@ -30,7 +30,7 @@ function setTabBar (type, args = {}) {
const {
index
} = args
const tabBar = app.$children[0].tabBar
const tabBar = __uniConfig.tabBar
if (index >= __uniConfig.tabBar.list.length) {
return {
errMsg: `${type}:fail tabbar item not found`
......
import Vue from 'vue'
import {
isPlainObject,
supportsPassive
......@@ -35,9 +33,7 @@ function updateCssVar (vm) {
const windowBottom = windowBottomValue && envMethod
? `calc(${windowBottomValue}px + ${envMethod}(safe-area-inset-bottom))` : `${windowBottomValue}px`
const style = document.documentElement.style
if (!Vue.component('VUniTopWindow') || pageVm.topWindow === false) { // TODO 目前简单处理,只要包含topWindow,则不再更新--window-top
style.setProperty('--window-top', windowTop)
}
style.setProperty('--window-top', `calc(var(--top-window-height) + ${windowTop})`)
style.setProperty('--window-bottom', windowBottom)
console.debug(`${vm.$page.route}[${vm.$page.id}]:--window-top=${windowTop}`)
console.debug(`${vm.$page.route}[${vm.$page.id}]:--window-bottom=${windowBottom}`)
......
......@@ -440,7 +440,7 @@ export default {
case 'getScale':
this.mapReady(() => {
callback({
scale: Number(this.scale)
scale: this._map.getZoom()
})
})
break
......@@ -451,11 +451,12 @@ export default {
var map = this._map = new maps.Map(this.$refs.map, {
center,
zoom: Number(this.scale),
scrollwheel: false,
// scrollwheel: false,
disableDoubleClickZoom: true,
mapTypeControl: false,
zoomControl: false,
scaleControl: false,
panControl: false,
minZoom: 5,
maxZoom: 18,
draggable: true
......@@ -471,16 +472,32 @@ export default {
})
maps.event.addListener(map, 'dragstart', () => {
this.$trigger('regionchange', {}, {
type: 'begin'
type: 'begin',
causedBy: 'gesture'
})
})
function getMapInfo () {
var center = map.getCenter()
return {
scale: map.getZoom(),
centerLocation: {
latitude: center.getLat(),
longitude: center.getLng()
}
}
}
maps.event.addListener(map, 'dragend', () => {
this.$trigger('regionchange', {}, {
type: 'end'
})
this.$trigger('regionchange', {}, Object.assign({
type: 'end',
causedBy: 'drag'
}, getMapInfo()))
})
maps.event.addListener(map, 'zoom_changed', () => {
this.$emit('update:scale', map.getZoom())
this.$trigger('regionchange', {}, Object.assign({
type: 'end',
causedBy: 'scale'
}, getMapInfo()))
})
maps.event.addListener(map, 'center_changed', () => {
var latitude
......@@ -849,7 +866,7 @@ export default {
refreshLocation()
}
})
}, 1000)
}, 30000)
}
},
removeLocation () {
......@@ -904,6 +921,7 @@ export default {
return element
}
}
throw new Error('translateMarker: fail cannot find marker with id ' + id)
}
}
}
......
......@@ -7,6 +7,8 @@
<div
ref="picker"
class="uni-picker-container"
:class="`uni-${mode}-${selectorTypeComputed}`"
@wheel.prevent
@touchmove.prevent
>
<transition name="uni-fade">
......@@ -17,9 +19,10 @@
/>
</transition>
<div
v-if="!system"
:class="{ 'uni-picker-toggle': visible }"
:style="popupStyle.content"
class="uni-picker"
class="uni-picker-custom"
>
<div
class="uni-picker-header"
......@@ -57,16 +60,44 @@
</div>
</v-uni-picker-view-column>
</v-uni-picker-view>
<!-- 第二种时间单位展示方式-暂时不用这种 -->
<!-- <div v-if="units.length" class="uni-picker-units">
<div v-for="(item,index) in units" :key="index">{{item}}</div>
</div>-->
<div
ref="select"
class="uni-picker-select"
>
<div
v-for="(item, index) in rangeArray[0]"
:key="index"
class="uni-picker-item"
:class="{ selected: valueArray[0] === index }"
@click="
valueArray[0] = index;
_change();
"
>
{{ typeof item === "object" ? item[rangeKey] || "" : item }}
</div>
</div>
<div :style="popupStyle.triangle" />
</div>
</div>
<div>
<slot />
</div>
<div
v-if="system"
class="uni-picker-system"
>
<input
ref="input"
:value="valueSync"
:type="mode"
tabindex="-1"
:min="start"
:max="end"
:class="[system, popupStyle.dock]"
@change.stop="_input"
>
</div>
<keypress
:disable="!visible"
@esc="_cancel"
......@@ -92,7 +123,7 @@ function getDefaultStartValue () {
return year.toString()
case fields.MONTH:
return year + '-01'
case fields.DAY:
default:
return year + '-01-01'
}
}
......@@ -110,7 +141,7 @@ function getDefaultEndValue () {
return year.toString()
case fields.MONTH:
return year + '-12'
case fields.DAY:
default:
return year + '-12-31'
}
}
......@@ -130,6 +161,10 @@ const fields = {
MONTH: 'month',
DAY: 'day'
}
const selectorType = {
PICKER: 'picker',
SELECT: 'select'
}
export default {
name: 'Picker',
components: { keypress },
......@@ -157,15 +192,12 @@ export default {
type: String,
default: mode.SELECTOR,
validator (val) {
return Object.values(mode).indexOf(val) >= 0
return Object.values(mode).includes(val)
}
},
fields: {
type: String,
default: 'day',
validator (val) {
return Object.values(fields).indexOf(val) >= 0
}
default: ''
},
start: {
type: String,
......@@ -178,6 +210,10 @@ export default {
disabled: {
type: [Boolean, String],
default: false
},
selectorType: {
type: String,
default: ''
}
},
data () {
......@@ -211,7 +247,7 @@ export default {
return [dateArray[0]]
case fields.MONTH:
return [dateArray[0], dateArray[1]]
case fields.DAY:
default:
return [dateArray[0], dateArray[1], dateArray[2]]
}
}
......@@ -233,14 +269,31 @@ export default {
default:
return []
}
},
selectorTypeComputed () {
const type = this.selectorType
if (Object.values(selectorType).includes(type)) {
return type
}
return String(navigator.vendor).indexOf('Apple') === 0 && navigator.maxTouchPoints > 0 ? selectorType.PICKER : selectorType.SELECT
},
system () {
if (this.mode === mode.DATE && !Object.values(fields).includes(this.fields) && this.isDesktop && /win|mac/i.test(navigator.platform)) {
if (navigator.vendor === 'Google Inc.') {
return 'chrome'
} else if (/Firefox/.test(navigator.userAgent)) {
return 'firefox'
}
}
return ''
}
},
watch: {
visible (val) {
if (val) {
clearTimeout(this.__contentVisibleDelay)
this.contentVisible = val
this._select()
} else {
this.__contentVisibleDelay = setTimeout(() => {
this.contentVisible = val
......@@ -512,7 +565,15 @@ export default {
value
})
},
_cancel () {
_cancel ($event) {
if (this.system === 'firefox') {
// Firefox 在 input 同位置区域点击无法隐藏控件
const { top, left, width, height } = this.popover
const { pageX, pageY } = $event
if (pageX > left && pageX < left + width && pageY > top && pageY < top + height) {
return
}
}
this._close()
this.$trigger('cancel', {}, {})
},
......@@ -524,6 +585,17 @@ export default {
this.$el.prepend($picker)
$picker.style.display = 'none'
}, 260)
},
_select () {
if (this.mode === mode.SELECTOR && this.selectorTypeComputed === selectorType.SELECT) {
this.$refs.select.scrollTop = this.valueArray[0] * 34
}
},
_input ($event) {
this.valueSync = $event.target.value
this.$nextTick(() => {
this._change()
})
}
}
}
......@@ -531,6 +603,7 @@ export default {
<style>
uni-picker {
position: relative;
display: block;
cursor: pointer;
}
......@@ -555,11 +628,11 @@ uni-picker[disabled] {
font-size: 16px;
}
.uni-picker-container .uni-picker * {
.uni-picker-container .uni-picker-custom * {
box-sizing: border-box;
}
.uni-picker-container .uni-picker {
.uni-picker-container .uni-picker-custom {
position: fixed;
left: 0;
bottom: 0;
......@@ -572,7 +645,7 @@ uni-picker[disabled] {
transition: transform 0.3s, visibility 0.3s;
}
.uni-picker-container .uni-picker.uni-picker-toggle {
.uni-picker-container .uni-picker-custom.uni-picker-toggle {
visibility: visible;
transform: translate(0, 0);
}
......@@ -594,6 +667,7 @@ uni-picker[disabled] {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
cursor: pointer;
}
.uni-picker-container .uni-picker-header {
......@@ -642,33 +716,48 @@ uni-picker[disabled] {
color: #007aff;
}
/* .uni-picker {
position: relative;
.uni-picker-container .uni-picker-select {
display: none;
}
.uni-picker-units {
.uni-picker-system {
position: absolute;
display: flex;
display: none;
display: block;
top: 0;
left: 0;
width: 100%;
line-height: 16px;
font-size: 14px;
top: 50%;
margin-top: 22.5px;
transform: translateY(-50%);
height: 100%;
overflow: hidden;
color: #666666;
pointer-events: none;
}
.uni-picker-units > div {
flex: 1;
text-align: center;
transform: translateX(2em);
} */
@media screen and (min-width: 500px) {
.uni-picker-system > input {
position: absolute;
border: none;
height: 100%;
opacity: 0;
}
.uni-picker-system > input.firefox {
top: 0;
left: 0;
width: 100%;
}
.uni-picker-system > input.chrome {
/* 翻转且使用较大字体保证可点击日历按钮 */
top: 0;
right: 100%;
font-size: 9999px;
transform-origin: 100% 0;
transform: scaleX(-1);
}
@media screen and (min-width: 500px) and (min-height: 500px) {
.uni-mask.uni-picker-mask {
background: none;
}
.uni-picker-container .uni-picker {
.uni-picker-container .uni-picker-custom {
width: 300px;
left: 50%;
right: auto;
......@@ -689,9 +778,31 @@ uni-picker[disabled] {
overflow: hidden;
border-radius: 0 0 5px 5px;
}
.uni-picker-container .uni-picker.uni-picker-toggle {
.uni-picker-container .uni-picker-custom.uni-picker-toggle {
opacity: 1;
transform: translate(-50%, -50%);
}
.uni-selector-select .uni-picker-header,
.uni-selector-select .uni-picker-content {
display: none;
}
.uni-selector-select .uni-picker-select {
display: block;
max-height: 300px;
overflow: auto;
background-color: white;
border-radius: 5px;
padding: 6px 0;
}
.uni-selector-select .uni-picker-item {
padding: 0 10px;
color: #555555;
}
.uni-selector-select .uni-picker-item:hover {
background-color: #f6f6f6;
}
.uni-selector-select .uni-picker-item.selected {
color: #007aff;
}
}
</style>
import navigateTo from 'uni-helpers/navigate-to'
// import navigateTo from 'uni-helpers/navigate-to'
import redirectTo from '../../helpers/redirect-to'
import previewImage from '../../helpers/normalize-preview-image'
......@@ -15,7 +15,7 @@ function addSafeAreaInsets (result) {
}
export const protocols = {
redirectTo,
navigateTo,
// navigateTo, // 由于在微信开发者工具的页面参数,会显示__id__参数,因此暂时关闭mp-weixin对于navigateTo的AOP
previewImage,
getSystemInfo: {
returnValue: addSafeAreaInsets
......
......@@ -31,16 +31,28 @@ export function initRelation (detail) {
this.triggerEvent('__l', detail)
}
function selectAllComponents (mpInstance, selector, $refs) {
const components = mpInstance.selectAllComponents(selector)
components.forEach(component => {
const ref = component.dataset.ref
$refs[ref] = component.$vm || component
if (__PLATFORM__ === 'mp-weixin') {
if (component.dataset.vueGeneric === 'scoped') {
component.selectAllComponents('.scoped-ref').forEach(scopedComponent => {
selectAllComponents(scopedComponent, selector, $refs)
})
}
}
})
}
export function initRefs (vm) {
const mpInstance = vm.$scope
Object.defineProperty(vm, '$refs', {
get () {
const $refs = {}
const components = mpInstance.selectAllComponents('.vue-ref')
components.forEach(component => {
const ref = component.dataset.ref
$refs[ref] = component.$vm || component
})
selectAllComponents(mpInstance, '.vue-ref', $refs)
// TODO 暂不考虑 for 中的 scoped
const forComponents = mpInstance.selectAllComponents('.vue-ref-in-for')
forComponents.forEach(component => {
const ref = component.dataset.ref
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册