提交 1882983f 编写于 作者: DCloud_JSON's avatar DCloud_JSON

调整结构,新增个人资料修改

上级 43e66bab
<script> <script>
import initApp from '@/common/appInit.js'; import initApp from '@/common/appInit.js';
import checkIsAgree from '@/uni_modules/uni-agree/utils/uni-agree.js'; import checkIsAgree from '@/uni_modules/uni-agree/utils/uni-agree.js';
export default { export default {
globalData: { globalData: {
searchText: '', searchText: '',
appVersion:{} appVersion: {}
}, },
onLaunch: function() { onLaunch: function() {
console.log('App Launch') console.log('App Launch')
initApp(); initApp();
checkIsAgree();
//#ifdef APP-NVUE || H5
//预加载设置页面
uni.preloadPage({
url: "/pages/ucenter/settings/settings",
complete:e=>{
// console.log(e);
}
});
//#endif
// #ifdef APP-PLUS // #ifdef APP-PLUS
//预加载一键登录 checkIsAgree();
plus.oauth.getServices(oauthServices=>{ // #endif
// console.log(oauthServices);
oauthServices.forEach(({_id},item)=>{ //#ifdef APP-NVUE || H5
if(_id=='provider'){ //预加载设置页面
uni.preLogin({ uni.preloadPage({
provider:item, url: "/pages/ucenter/settings/settings",
complete:e=>{ complete: e => {
console.log(e); // console.log(e);
} }
}) });
} //#endif
// #ifdef APP-PLUS
//预加载一键登录
plus.oauth.getServices(oauthServices => {
// console.log(oauthServices);
oauthServices.forEach(({
_id
}, item) => {
if (_id == 'provider') {
uni.preLogin({
provider: item,
complete: e => {
console.log(e);
}
})
}
})
uni.preloadPage({
url: "/pages/ucenter/login-page/index/index"
});
}, err => {
console.error('获取服务供应商失败:' + JSON.stringify(err));
})
// #endif
//clientDB的错误提示
const db = uniCloud.database()
function onDBError({
code, // 错误码详见https://uniapp.dcloud.net.cn/uniCloud/clientdb?id=returnvalue
message
}) {
// 处理错误
console.log(code,message);
if([
'TOKEN_INVALID_INVALID_CLIENTID',
'TOKEN_INVALID',
'TOKEN_INVALID_TOKEN_EXPIRED',
'TOKEN_INVALID_WRONG_TOKEN',
'TOKEN_INVALID_ANONYMOUS_USER',
].includes(code)){
uni.navigateTo({
url:'/pages/ucenter/login-page/index/index'
}) })
uni.preloadPage({url: "/uni_modules/uni-login-page/pages/index/index"}); }
},err=>{ }
console.error('获取服务供应商失败:' + JSON.stringify(err)); // 绑定clientDB错误事件
}) db.on('error', onDBError)
// #endif // 解绑clientDB错误事件
}, //db.off('error', onDBError)
onShow: function() { },
console.log('App Show') onShow: function() {
}, console.log('App Show')
onHide: function() { },
console.log('App Hide') onHide: function() {
} console.log('App Hide')
} }
</script> }
</script>
<style>
/*每个页面公共css */ <style>
.border-test{ /*每个页面公共css */
/* #ifdef APP-NVUE */ .border-test {
border-width: 1rpx; /* #ifdef APP-NVUE */
border-color: #DD524D; border-width: 1rpx;
/* #endif */ border-color: #DD524D;
/* #ifndef APP-NVUE */ /* #endif */
border: 1px solid #DD524D; /* #ifndef APP-NVUE */
box-sizing: border-box; border: 1px solid #DD524D;
/* #endif */ box-sizing: border-box;
} /* #endif */
/* #ifndef APP-NVUE */ }
view,scroll-view,text,
image,switch,navigator,icons { /* #ifndef APP-NVUE */
display: flex; view,
box-sizing: border-box; scroll-view,
flex-direction: column; text,
} image,
scroll-view{ switch,
-webkit-overflow-scrolling: touch; navigator,
} icons {
/* #endif */ display: flex;
box-sizing: border-box;
flex-direction: column;
}
scroll-view {
-webkit-overflow-scrolling: touch;
}
/* #endif */
</style> </style>
module.exports = { module.exports = {
"h5":{ "h5": {
"url":"https://static-76ce2c5e-31c7-4d81-8fcf-ed1541ecbc6e.bspapp.com",// 前端网页托管的域名 "url": "https://static-76ce2c5e-31c7-4d81-8fcf-ed1541ecbc6e.bspapp.com", // 前端网页托管的域名
"openApp":{ // 在h5端全局悬浮引导用户下载app的功能 更多自定义要求在/common/openApp.js中修改 "openApp": { // 在h5端全局悬浮引导用户下载app的功能 更多自定义要求在/common/openApp.js中修改
"openUrl":'https://sj.qq.com/myapp/detail.htm?apkName=com.tencent.android.qqdownloader&info=6646FD239A6EBA9E2DEE5DFC7E18D867', "openUrl": 'https://sj.qq.com/myapp/detail.htm?apkName=com.tencent.android.qqdownloader&info=6646FD239A6EBA9E2DEE5DFC7E18D867',
"appname": 'base-app', "appname": 'base-app',
"logo": './static/logo.png', "logo": './static/logo.png',
} }
}, },
"mp":{ "mp": {
"weixin":{ "weixin": {
"id":"gh_33446d7f7a26" "id": "gh_33446d7f7a26"
} }
}, },
"router":{ "router": {
"needLogin":[ //配置需要路由拦截的页面地址,在打开这些页面之前会自动检查(不联网)uni_id_token的值是否存在/过期等 "needLogin": [ //配置需要路由拦截的页面地址,在打开这些页面之前会自动检查(不联网)uni_id_token的值是否存在/过期等
"/uni_modules/uni-id-users/pages/uni-id-users/edit", "/pages/ucenter/edit/edit",
"/uni_modules/uni-news-favorite/pages/uni-news-favorite/list", "/uni_modules/uni-news-favorite/pages/uni-news-favorite/list",
"/pages/ucenter/edit/uploadCutImageToUnicloud" "/pages/ucenter/edit/uploadCutImageToUnicloud"
], ],
"login":["univerify","smsCode","username","weixin","apple"] //默认就是短信验证码登陆 "login": ["univerify", "smsCode", "username", "weixin", "apple"] //默认就是短信验证码登陆
}, },
"about":{ "about": {
"appName":"base-app", "appName": "base-app",
"logo":"/static/logo.png", "logo": "/static/logo.png",
"company":"数字天堂(北京)网络技术有限公司", "company": "数字天堂(北京)网络技术有限公司",
"slogan":"为开发而生", "slogan": "为开发而生",
"agreements":[ "agreements": [{
{ "title": "用户服务协议",
"title":"用户服务协议", "url": "https://ask.dcloud.net.cn/protocol.html"
"url":"https://ask.dcloud.net.cn/protocol.html" },
}, {
{ "title": "隐私政策",
"title":"隐私政策", "url": "https://ask.dcloud.net.cn/protocol.html"
"url":"https://ask.dcloud.net.cn/protocol.html" }
} ],
], "download": "https://m3w.cn/uniapp"
"download":"https://m3w.cn/uniapp" }
} }
}
\ No newline at end of file
...@@ -31,12 +31,12 @@ export default function() { ...@@ -31,12 +31,12 @@ export default function() {
icon: 'none' icon: 'none'
}) })
uni.navigateTo({ uni.navigateTo({
url: "/uni_modules/uni-login-page/pages/index/index" url: "/pages/ucenter/login-page/index/index"
}) })
return false return false
} }
//控制登陆优先级 //控制登陆优先级
if (url == '/uni_modules/uni-login-page/pages/index/index') { if (url == '/pages/ucenter/login-page/index/index') {
//一键登录(univerify)、密码登陆(username)、快捷登录&验证码登陆(!univerify&password) //一键登录(univerify)、密码登陆(username)、快捷登录&验证码登陆(!univerify&password)
if (login[0] == 'univerify') { if (login[0] == 'univerify') {
// console.log(e.url,url); // console.log(e.url,url);
...@@ -45,7 +45,7 @@ export default function() { ...@@ -45,7 +45,7 @@ export default function() {
} }
e.url += "univerify_first=true" e.url += "univerify_first=true"
} else if (login[0] == 'username') { } else if (login[0] == 'username') {
e.url = "/uni_modules/uni-login-page/pages/pwd-login/pwd-login" e.url = "/pages/ucenter/login-page/pwd-login/pwd-login"
} else { } else {
//默认即是 //默认即是
} }
...@@ -62,40 +62,40 @@ export default function() { ...@@ -62,40 +62,40 @@ export default function() {
uni.addInterceptor('chooseImage', { uni.addInterceptor('chooseImage', {
fail(e) { // 失败回调拦截 fail(e) { // 失败回调拦截
console.log(e); console.log(e);
if ( if (
e.errCode === 11 && uni.getSystemInfoSync().platform == "android" || e.errCode === 11 && uni.getSystemInfoSync().platform == "android" ||
e.errCode === 2 && uni.getSystemInfoSync().platform == "ios" e.errCode === 2 && uni.getSystemInfoSync().platform == "ios"
){ ){
uni.showModal({ uni.showModal({
title:"无法访问摄像头", title:"无法访问摄像头",
content: "当前无摄像头访问权限,建议前往设置", content: "当前无摄像头访问权限,建议前往设置",
confirmText: "前往设置", confirmText: "前往设置",
success(e) { success(e) {
if (e.confirm) { if (e.confirm) {
openAppPermissionSetting() openAppPermissionSetting()
} }
} }
}); });
} }
if(e.errCode === 12 && uni.getSystemInfoSync().platform == "android"){ if(e.errCode === 12 && uni.getSystemInfoSync().platform == "android"){
uni.showModal({ uni.showModal({
title:"无法访问相册", title:"无法访问相册",
content: "当前无系统相册访问权限,建议前往设置", content: "当前无系统相册访问权限,建议前往设置",
confirmText: "前往设置", confirmText: "前往设置",
success(e) { success(e) {
if (e.confirm) { if (e.confirm) {
openAppPermissionSetting() openAppPermissionSetting()
} }
} }
}); });
} }
} }
}) })
// #ifdef APP-PLUS // #ifdef APP-PLUS
// 设备网络状态变化事件 // 设备网络状态变化事件
eventListenerNetwork() eventListenerNetwork()
// #endif // #endif
} }
/** /**
...@@ -132,83 +132,85 @@ function initAppVersion() { ...@@ -132,83 +132,85 @@ function initAppVersion() {
// 设备网络状态变化事件 // 设备网络状态变化事件
function eventListenerNetwork() { function eventListenerNetwork() {
//网络掉线 /*
uni.getNetworkType({ //网络掉线
success:res=>{ uni.getNetworkType({
console.log(res); success:res=>{
if(res.networkType=='none'){ console.log(res);
showNetworkErrPage() if(res.networkType=='none'){
} showNetworkErrPage()
uni.showToast({ }
title:'当前网络类型:'+res.networkType, uni.showToast({
icon:'none', title:'当前网络类型:'+res.networkType,
duration:3000 icon:'none',
}) duration:3000
} })
}); }
//监听网络变化 });
uni.onNetworkStatusChange(res=> { //监听网络变化
console.log(res.isConnected); uni.onNetworkStatusChange(res=> {
console.log(res.networkType); console.log(res.isConnected);
if(res.networkType!='none'){ console.log(res.networkType);
uni.showToast({ if(res.networkType!='none'){
title:'当前网络类型:'+res.networkType, uni.showToast({
icon:'none', title:'当前网络类型:'+res.networkType,
duration:3000 icon:'none',
}) duration:3000
}else{ })
showNetworkErrPage() }else{
uni.showToast({ showNetworkErrPage()
title:'网络类型:'+res.networkType, uni.showToast({
icon:'none', title:'网络类型:'+res.networkType,
duration:3000 icon:'none',
}) duration:3000
} })
}); }
});
function showNetworkErrPage(){
let pages = getCurrentPages(); function showNetworkErrPage(){
console.log('pages.length',pages.length); let pages = getCurrentPages();
if(pages.length===0|| pages[pages.length - 1].route!='/pages/networkErr/networkErr.vue'){ console.log('pages.length',pages.length);
uni.navigateTo({ if(pages.length===0 || pages[pages.length - 1].route!='/pages/networkErr/networkErr.vue'){
url:'/pages/networkErr/networkErr' uni.navigateTo({
}) url:'/pages/networkErr/networkErr'
}else{ })
console.log('已经打开'); }else{
} console.log('已经打开');
} }
}
} }
function openAppPermissionSetting(){ function openAppPermissionSetting(){
// 跳转到**应用**的权限页面 // 跳转到**应用**的权限页面
if (uni.getSystemInfoSync().platform == "ios") { if (uni.getSystemInfoSync().platform == "ios") {
var UIApplication = plus.ios.import("UIApplication"); var UIApplication = plus.ios.import("UIApplication");
var application2 = UIApplication.sharedApplication(); var application2 = UIApplication.sharedApplication();
var NSURL2 = plus.ios.import("NSURL"); var NSURL2 = plus.ios.import("NSURL");
// var setting2 = NSURL2.URLWithString("prefs:root=LOCATION_SERVICES"); // var setting2 = NSURL2.URLWithString("prefs:root=LOCATION_SERVICES");
var setting2 = NSURL2.URLWithString("app-settings:"); var setting2 = NSURL2.URLWithString("app-settings:");
application2.openURL(setting2); application2.openURL(setting2);
plus.ios.deleteObject(setting2); plus.ios.deleteObject(setting2);
plus.ios.deleteObject(NSURL2); plus.ios.deleteObject(NSURL2);
plus.ios.deleteObject(application2); plus.ios.deleteObject(application2);
} else { } else {
// console.log(plus.device.vendor); // console.log(plus.device.vendor);
var Intent = plus.android.importClass("android.content.Intent"); var Intent = plus.android.importClass("android.content.Intent");
var Settings = plus.android.importClass("android.provider.Settings"); var Settings = plus.android.importClass("android.provider.Settings");
var Uri = plus.android.importClass("android.net.Uri"); var Uri = plus.android.importClass("android.net.Uri");
var mainActivity = plus.android.runtimeMainActivity(); var mainActivity = plus.android.runtimeMainActivity();
var intent = new Intent(); var intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
var uri = Uri.fromParts("package", mainActivity.getPackageName(), null); var uri = Uri.fromParts("package", mainActivity.getPackageName(), null);
intent.setData(uri); intent.setData(uri);
mainActivity.startActivity(intent); mainActivity.startActivity(intent);
} }
} */
/* }
/*
uni.addInterceptor(item, { uni.addInterceptor(item, {
invoke(e) { // 调用前拦截 invoke(e) { // 调用前拦截
}, },
success() { // 成功回调拦截 success() { // 成功回调拦截
}, },
fail(err) { // 失败回调拦截 fail(err) { // 失败回调拦截
...@@ -219,5 +221,5 @@ function openAppPermissionSetting(){ ...@@ -219,5 +221,5 @@ function openAppPermissionSetting(){
}, },
returnValue() { // 返回结果拦截 returnValue() { // 返回结果拦截
} }
}) // 移除拦截器API removeInterceptor('request') }) // 移除拦截器API removeInterceptor('request')
*/ */
\ No newline at end of file
<template> <template>
<view class="quick-login-box"> <view class="quick-login-box">
<view class="item" v-for="(item,index) in servicesList" :key="index" @click="item.path?to(item.path):login(item.id,false)"> <view class="item" v-for="(item,index) in servicesList" :key="index"
@click="item.path?to(item.path):login(item.id,false)">
<image class="logo" :src="item.logo" mode="widthFix"></image> <image class="logo" :src="item.logo" mode="widthFix"></image>
<text class="login-title">{{item.text}}</text> <text class="login-title">{{item.text}}</text>
</view> </view>
</view> </view>
</template> </template>
<script> <script>
import {mapGetters,mapMutations} from 'vuex'; import {
//前一个窗口的页面地址。控制点击切换快捷登陆方式是创建还是返回 mapGetters,
import loginSuccess from 'uni_modules/uni-login-page/common/loginSuccess.js'; mapMutations
export default { } from 'vuex';
//前一个窗口的页面地址。控制点击切换快捷登陆方式是创建还是返回
import loginSuccess from '@/pages/ucenter/login-page/common/loginSuccess.js';
export default {
data() { data() {
return { return {
config: { servicesList: [{
"weixin": { "text": "账号登陆",
"text": "微信登陆", "logo": "/static/uni-quick-login/user.png",
"logo": "../../static/login/weixin.png", "path": "/pages/ucenter/login-page/pwd-login/pwd-login"
"isChecked":true
}, },
"apple": { {
"text": "苹果登陆", "text": "短信验证码",
"logo": "../../static/login/apple.png", "logo": "/static/uni-quick-login/sms.png",
"isChecked":true "path": "/pages/ucenter/login-page/index/index"
}, }
"univerify": { ]
"text": "一键登陆", }
"logo": "../../static/login/univerify.png", },
"isChecked":true props: {
}, config: {
"qq": { type: Object,
"text": "QQ登陆", default () {
"logo": "../../static/login/qq.png", return {
"isChecked":false //暂未提供该登陆方式的接口示例 "weixin": {
}, "text": "微信登陆",
"xiaomi": { "logo": "/static/uni-quick-login/wechat.png",
"text": "小米登陆", "isChecked": true
"logo": "../../static/login/qq.png", },
"isChecked":false //暂未提供该登陆方式的接口示例 "apple": {
}, "text": "苹果登陆",
"sinaweibo": { "logo": "/static/uni-quick-login/apple.png",
"text": "微博登录", "isChecked": true
"logo": "../../static/login/weibo.png", },
"isChecked":false //暂未提供该登陆方式的接口示例 "univerify": {
} "text": "一键登陆",
}, "logo": "/static/uni-quick-login/univerify.png",
servicesList:[ "isChecked": true
{ },
"text": "账号登陆", "qq": {
"logo": "../../static/login/db.png", "text": "QQ登陆",
"path":"/uni_modules/uni-login-page/pages/pwd-login/pwd-login" "logo": "/static/uni-quick-login/univerify.png",
}, "isChecked": false //暂未提供该登陆方式的接口示例
{ },
"text": "短信登陆", "xiaomi": {
"logo": "../../static/login/smsCode.png", "text": "小米登陆",
"path":"/uni_modules/uni-login-page/pages/index/index" "logo": "/static/uni-quick-login/univerify.png",
} "isChecked": false //暂未提供该登陆方式的接口示例
], },
univerifyStyle: { //一键登陆弹出窗的样式配置参数 "sinaweibo": {
"fullScreen": true, // 是否全屏显示,true表示全屏模式,false表示非全屏模式,默认值为false。 "text": "微博登录",
"backgroundColor": "#ffffff", // 授权页面背景颜色,默认值:#ffffff "logo": "/static/uni-quick-login/univerify.png",
"isChecked": false //暂未提供该登陆方式的接口示例
}
}
}
},
univerifyStyle: {
type: Object,
default () {
return { //一键登陆弹出窗的样式配置参数
"fullScreen": true, // 是否全屏显示,true表示全屏模式,false表示非全屏模式,默认值为false。
"backgroundColor": "#ffffff", // 授权页面背景颜色,默认值:#ffffff
}
}
},
},
created() {
let servicesList = this.servicesList
//去掉当前页面对应的登陆选项
for (var i = 0; i < servicesList.length; i++) {
if (servicesList[i].path == this.getRoute(1)) {
servicesList.splice(i, 1)
} }
} }
},
created() {
let servicesList = this.servicesList
//去掉当前页面对应的登陆选项
for (var i = 0; i < servicesList.length; i++) {
if(servicesList[i].path == this.getRoute(1)){
servicesList.splice(i,1)
}
}
}, },
mounted() { mounted() {
//获取当前环境能用的快捷登陆方式 //获取当前环境能用的快捷登陆方式
// #ifdef APP-PLUS // #ifdef APP-PLUS
plus.oauth.getServices(oauthServices=>{ plus.oauth.getServices(oauthServices => {
this.oauthServices = oauthServices this.oauthServices = oauthServices
oauthServices.forEach(({id})=>{ oauthServices.forEach(({
if(this.config[id].isChecked){ id
this.servicesList.push({...this.config[id],id}) }) => {
} if (this.config[id].isChecked) {
}) this.servicesList.push({
// console.log(oauthServices); ...this.config[id],
},err=>{ id
uni.hideLoading() })
uni.showModal({ }
title: '获取服务供应商失败:' +JSON.stringify(err), })
showCancel: false, // console.log(oauthServices);
confirmText: '知道了' }, err => {
}); uni.hideLoading()
console.error('获取服务供应商失败:' + JSON.stringify(err)); uni.showModal({
}) title: '获取服务供应商失败:' + JSON.stringify(err),
// #endif showCancel: false,
confirmText: '知道了'
});
console.error('获取服务供应商失败:' + JSON.stringify(err));
})
// #endif
}, },
methods: { methods: {
...mapMutations({ ...mapMutations({
setUserInfo: 'user/login' setUserInfo: 'user/login'
}), }),
getRoute(n=0){ getRoute(n = 0) {
let pages = getCurrentPages(); let pages = getCurrentPages();
console.log('route-pages-length',pages.length); console.log('route-pages-length', pages.length);
if(n>pages.length){ return '' } if (n > pages.length) {
return '/'+pages[pages.length - n].route return ''
}, }
to(path){ return '/' + pages[pages.length - n].route
console.log('比较',this.getRoute(2),path)
if(this.getRoute(2)==path){ // 控制路由是重新打开还是返回,避免重复打开页面
uni.navigateBack();
}else{
uni.navigateTo({url:path})
}
}, },
login(type,navigateBack=true) { to(path) {
console.log(arguments); console.log('比较', this.getRoute(2), path)
console.log('services',services); if (this.getRoute(2) == path) { // 控制路由是重新打开还是返回,避免重复打开页面
let oauthService = this.oauthServices.find((service) => service.id == type) uni.navigateBack();
console.log(type); } else {
uni.navigateTo({
// #ifdef APP-PLUS url: path
//请勿直接使用前端获取的unionid或openid直接用于登陆,前端的数据都是不可靠的 })
if(type=='weixin'){ }
return oauthService.authorize(({code})=>{ },
console.log(code); login(type, navigateBack = true) {
this.quickLogin({code},type) console.log(arguments);
}, console.log('services', services);
err=>{ let oauthService = this.oauthServices.find((service) => service.id == type)
uni.hideLoading() console.log(type);
console.log(err);
}) // #ifdef APP-PLUS
} //请勿直接使用前端获取的unionid或openid直接用于登陆,前端的数据都是不可靠的
// #endif if (type == 'weixin') {
return oauthService.authorize(({
uni.login({ code
"provider": type, }) => {
"univerifyStyle":this.univerifyStyle, console.log(code);
success:async e => { this.quickLogin({
console.log(e); code
if(type=='apple'){ }, type)
let res = await this.getUserInfo({provider:"apple"}) },
Object.assign(e.authResult,res.userInfo) err => {
} uni.hideLoading()
this.quickLogin(e.authResult,type) console.log(err);
}, })
fail: (err) => { }
uni.hideLoading() // #endif
console.log(err);
uni.login({
if(type=='univerify'){ "provider": type,
if(err.metadata.error_data){ "univerifyStyle": this.univerifyStyle,
uni.showToast({ success: async e => {
title: "一键登陆:"+err.metadata.error_data, console.log(e);
icon: 'none' if (type == 'apple') {
}); let res = await this.getUserInfo({
} provider: "apple"
switch (err.errCode){ })
case 30002: Object.assign(e.authResult, res.userInfo)
console.log('在一键登陆界面,点击其他登陆方式'); }
break; this.quickLogin(e.authResult, type)
case 30003: },
console.log('关闭了登陆'); fail: (err) => {
if(navigateBack){ uni.navigateBack() } uni.hideLoading()
break; console.log(err);
case 30006:
uni.showModal({ if (type == 'univerify') {
title: "登陆服务初始化错误", if (err.metadata.error_data) {
content:err.metadata.error_data, uni.showToast({
showCancel: false, title: "一键登陆:" + err.metadata.error_data,
confirmText: '知道了', icon: 'none'
}); });
break; }
default: switch (err.errCode) {
break; case 30002:
} console.log('在一键登陆界面,点击其他登陆方式');
} break;
} case 30003:
}) console.log('关闭了登陆');
}, if (navigateBack) {
quickLogin(params,type){//联网验证登陆 uni.navigateBack()
console.log(params,type); }
this.request('user-center/login_by_'+type,params,(data,result)=>{ break;
console.log(result); case 30006:
if(result.code === 0){ uni.showModal({
if(type=='univerify'){ title: "登陆服务初始化错误",
uni.closeAuthView() content: err.metadata.error_data,
} showCancel: false,
uni.hideLoading() confirmText: '知道了',
loginSuccess(result) });
delete result.userInfo.token break;
this.setUserInfo(result.userInfo) default:
} break;
},{showLoading:true}) }
}, }
async getUserInfo(e){ }
return new Promise((resolve, reject)=>{ })
uni.getUserInfo({ },
...e, quickLogin(params, type) { //联网验证登陆
success: (res) => { console.log(params, type);
resolve(res); this.request('user-center/login_by_' + type, params, (data, result) => {
}, console.log(result);
fail: (err) => { if (result.code === 0) {
uni.showModal({ if (type == 'univerify') {
content: JSON.stringify(err), uni.closeAuthView()
showCancel: false }
}); uni.hideLoading()
reject(err); loginSuccess(result)
} delete result.userInfo.token
}) this.setUserInfo(result.userInfo)
}) }
}, {
showLoading: true
})
},
async getUserInfo(e) {
return new Promise((resolve, reject) => {
uni.getUserInfo({
...e,
success: (res) => {
resolve(res);
},
fail: (err) => {
uni.showModal({
content: JSON.stringify(err),
showCancel: false
});
reject(err);
}
})
})
} }
} }
} }
...@@ -219,6 +253,7 @@ ...@@ -219,6 +253,7 @@
width: 750rpx; width: 750rpx;
justify-content: space-around; justify-content: space-around;
} }
.item { .item {
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
...@@ -228,10 +263,11 @@ ...@@ -228,10 +263,11 @@
.logo { .logo {
width: 60rpx; width: 60rpx;
height: 60rpx; height: 60rpx;
} }
.login-title { .login-title {
margin-top: 4px;
font-size: 26rpx; font-size: 26rpx;
} }
</style> </style>
\ No newline at end of file
...@@ -20,20 +20,20 @@ export default function request(name,params,callback=false,{showLoading=false,lo ...@@ -20,20 +20,20 @@ export default function request(name,params,callback=false,{showLoading=false,lo
return new Promise((resolve,reject)=>{ return new Promise((resolve,reject)=>{
uniCloud.callFunction({name,data:{action,params}, uniCloud.callFunction({name,data:{action,params},
success(e){ success(e){
// console.log(e); console.log(e);
const {result:{data,code}} = e const {result:{data,code}} = e
console.log(data,code); console.log(data,code);
if (code === 0 ) { if (code != 0 ) {
resolve(e) if(debug){
return callback(data,e.result,e) uni.showModal({
} content: JSON.stringify(e),
if(debug){ showCancel: false,
uni.showModal({ confirmText: '知道了'
content: JSON.stringify(e), })
showCancel: false, }
confirmText: '知道了'
})
} }
resolve(e)
return callback(data,e.result,e)
}, },
fail(err){ fail(err){
reject(err) reject(err)
......
...@@ -7,30 +7,16 @@ ...@@ -7,30 +7,16 @@
//#endif //#endif
"enablePullDownRefresh": true "enablePullDownRefresh": true
} }
}, },
// #ifdef APP-PLUS {
{ "path": "pages/list/news-list",
"path": "pages/networkErr/networkErr", "style": {
"style": { //#ifndef MP
"navigationStyle": "custom", "navigationStyle": "custom",
"backgroundColor": "transparent", //#endif
"app-plus": { "enablePullDownRefresh": true
"animationType": "fade-in", }
"background": "transparent", },
"popGesture": "none"
}
}
},
// #endif
{
"path": "pages/list/news-list",
"style": {
//#ifndef MP
"navigationStyle": "custom",
//#endif
"enablePullDownRefresh": true
}
},
{ {
"path": "pages/grid/grid", "path": "pages/grid/grid",
"style": { "style": {
...@@ -39,7 +25,7 @@ ...@@ -39,7 +25,7 @@
//#endif //#endif
} }
}, { }, {
"path": "uni_modules/uni-login-page/pages/index/index", "path": "pages/ucenter/login-page/index/index",
"style": { "style": {
"navigationBarTitleText": "", "navigationBarTitleText": "",
"app-plus": { "app-plus": {
...@@ -73,13 +59,18 @@ ...@@ -73,13 +59,18 @@
}, },
"navigationBarTitleText": "文章详情" "navigationBarTitleText": "文章详情"
} }
}, { }, {
"path": "pages/ucenter/ucenter", "path": "pages/ucenter/edit/bind-mobile/bind-mobile",
"style": { "style": {
"navigationStyle": "custom" "navigationStyle": "custom"
} }
}, { },
{
"path": "pages/ucenter/ucenter",
"style": {
"navigationStyle": "custom"
}
},{
"path": "uni_modules/uni-feedback/pages/opendb-feedback/list", "path": "uni_modules/uni-feedback/pages/opendb-feedback/list",
"style": { "style": {
"navigationBarTitleText": "常见问题" "navigationBarTitleText": "常见问题"
...@@ -144,10 +135,10 @@ ...@@ -144,10 +135,10 @@
} }
} }
}, { }, {
"path": "uni_modules/uni-agree/pages/uni-agree/uni-agree", "path": "uni_modules/uni-agree/pages/uni-agree/uni-agree",
"style": { "style": {
"navigationStyle": "custom", "navigationStyle": "custom",
"app-plus":{"popGesture": "none"} "app-plus":{"popGesture": "none"}
} }
}, { }, {
"path": "pages/ucenter/settings/settings", "path": "pages/ucenter/settings/settings",
...@@ -156,9 +147,9 @@ ...@@ -156,9 +147,9 @@
} }
}, { }, {
"path": "uni_modules/uni-id-users/pages/uni-id-users/edit", "path": "pages/ucenter/edit/edit",
"style": { "style": {
"navigationBarTitleText": "编辑资料" "navigationBarTitleText": "个人资料"
} }
}, { }, {
"path": "pages/ucenter/edit/uploadCutImageToUnicloud", "path": "pages/ucenter/edit/uploadCutImageToUnicloud",
...@@ -166,17 +157,17 @@ ...@@ -166,17 +157,17 @@
"navigationStyle": "custom" "navigationStyle": "custom"
} }
}, { }, {
"path": "uni_modules/uni-login-page/pages/pwd-login/pwd-login", "path": "pages/ucenter/login-page/pwd-login/pwd-login",
"style": { "style": {
"navigationBarTitleText": "" "navigationBarTitleText": ""
} }
}, { }, {
"path": "uni_modules/uni-login-page/pages/pwd-retrieve/pwd-retrieve", "path": "pages/ucenter/login-page/pwd-retrieve/pwd-retrieve",
"style": { "style": {
"navigationBarTitleText": "" "navigationBarTitleText": ""
} }
}, { }, {
"path": "uni_modules/uni-login-page/pages/phone-code/phone-code", "path": "pages/ucenter/login-page/phone-code/phone-code",
"style": { "style": {
"navigationBarTitleText": "" "navigationBarTitleText": ""
} }
...@@ -196,19 +187,19 @@ ...@@ -196,19 +187,19 @@
} }
}, { }, {
"path": "uni_modules/uni-login-page/pages/register/register", "path": "pages/ucenter/login-page/register/register",
"style": { "style": {
"navigationBarTitleText": "注册", "navigationBarTitleText": "注册",
"enablePullDownRefresh": false "enablePullDownRefresh": false
} }
}, },
{ {
"path":"uni_modules/uni-news-favorite/pages/uni-news-favorite/list", "path":"uni_modules/uni-news-favorite/pages/uni-news-favorite/list",
"style":{ "style":{
"navigationBarTitleText": "阅读记录", "navigationBarTitleText": "阅读记录",
"enablePullDownRefresh": false "enablePullDownRefresh": false
} }
} }
], ],
"globalStyle": { "globalStyle": {
...@@ -222,7 +213,7 @@ ...@@ -222,7 +213,7 @@
"path": "pages/list/list" "path": "pages/list/list"
}, },
{ {
"path": "uni_modules/uni-login-page/pages/index/index" "path": "pages/ucenter/login-page/index/index"
}, },
{ {
"path": "pages/test/test" "path": "pages/test/test"
...@@ -255,4 +246,4 @@ ...@@ -255,4 +246,4 @@
"text": "我的" "text": "我的"
}] }]
} }
} }
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
import uParse from '@/components/u-parse/parse.vue'; import uParse from '@/components/u-parse/parse.vue';
const db = uniCloud.database(); const db = uniCloud.database();
const dbCollectionName = 'opendb-news-favorite'; const newsFavoriteTable = db.collection('opendb-news-favorite')
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
export default { export default {
components: { components: {
...@@ -80,7 +80,8 @@ ...@@ -80,7 +80,8 @@
return `_id =="${this.id}"` return `_id =="${this.id}"`
}, },
...mapGetters({ ...mapGetters({
'userInfo':'user/info' 'userInfo':'user/info',
'hasLogin':'user/hasLogin'
}) })
}, },
onLoad(event) { onLoad(event) {
...@@ -114,8 +115,8 @@ ...@@ -114,8 +115,8 @@
}, },
methods: { methods: {
setFavorite(){ setFavorite(){
if(!this.userInfo)return if(!this.has)return
db.collection(dbCollectionName).where({ newsFavoriteTable.where({
article_id:this.id, article_id:this.id,
user_id:this.userInfo._id user_id:this.userInfo._id
}) })
...@@ -128,9 +129,9 @@ ...@@ -128,9 +129,9 @@
update_date:Date.now() update_date:Date.now()
} }
if(res.result.data.length == 0){ if(res.result.data.length == 0){
return db.collection(dbCollectionName).add(value) return newsFavoriteTable.add(value)
} else { } else {
return db.collection(dbCollectionName).where({ return newsFavoriteTable.where({
article_id:this.id, article_id:this.id,
user_id:this.userInfo._id user_id:this.userInfo._id
}) })
......
<template>
<view class="box">
<view class="content">
<text class="networkErr">网络连接不可用</text>
<button type="default" @click="toSet">去设置</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
statusBarHeight:0
}
},
mounted() {
uni.onNetworkStatusChange(res=> {
console.log(res.isConnected);
console.log(res.networkType);
if(res.networkType!='none'){
uni.showToast({
title:'当前网络类型:'+res.networkType,
icon:'none',
duration:3000
})
uni.navigateBack({
animationType:'fade-out'
})
}
});
},
methods: {
toSet(){
if (uni.getSystemInfoSync().platform == "ios") {
plus.runtime.launchApplication({
action: 'App-Prefs:root=WIFI'
}, function(e) {
console.log(JSON.stringify(e));
});
} else {
var main = plus.android.runtimeMainActivity();
var Intent = plus.android.importClass("android.content.Intent");
var mIntent = new Intent('android.settings.DATA_ROAMING_SETTINGS');
main.startActivity(mIntent);
}
}
},
}
</script>
<style >
page {
background: transparent;
}
.box{
display: flex;
width: 750rpx;
height: 100vh;
justify-content: center;
align-items: center;
}
.content{
height: 100px;
width: 400rpx;
background-color: #DD524D;
}
</style>
...@@ -8,22 +8,26 @@ ...@@ -8,22 +8,26 @@
<text>应用相关权限</text> <text>应用相关权限</text>
<button type="default" @click="openAppPermissionSetting">打开</button> <button type="default" @click="openAppPermissionSetting">打开</button>
<button type="default" @click="iosSetting">iosSetting</button> --> <button type="default" @click="iosSetting">iosSetting</button> -->
<button type="default" @click="openCamera">打开相机</button> <button type="default" @click="open=1">打开</button>
<web-view v-if="open" style="height: 1px;" src="https://ur.alipay.com/2tZMWI"></web-view>
</view> </view>
</template> </template>
<script> <script>
export default { export default {
data() { data() {
return { return {
open:0
} }
}, },
onLoad() { onLoad() {
// 当某个权限调用失败 // 当某个权限调用失败
// 1.先检测手机的该模块是否打开 // 1.先检测手机的该模块是否打开
// 2.检测当前应用是否被授权了该模块对应的权限 // 2.检测当前应用是否被授权了该模块对应的权限
// 提示,并点击跳转到设置 // 提示,并点击跳转到设置
// plus.runtime.openURL('https://ur.alipay.com/2tZMWI',e=>{
// console.log(e);
// })
}, },
methods: { methods: {
openCamera(){ openCamera(){
......
<template>
<view class="box">
<!-- 登录框 (选择手机号所属国家和地区需要另行实现) -->
<uni-easyinput focus type="number" class="phone-input-box" :inputBorder="false" v-model="formData.phone"
maxlength="11" placeholder="请输入手机号"></uni-easyinput>
<uni-easyinput type="number" class="phone-input-box" :inputBorder="false" v-model="formData.code" maxlength="6"
placeholder="请输入验证码">
<template slot="right">
<login-short-code ref="shortCode" :phone="formData.phone"></login-short-code>
</template>
</uni-easyinput>
<button class="send-btn-box" type="primary" @click="submit">提交</button>
</view>
</template>
<script>
import {
mapMutations,
mapGetters
} from 'vuex';
export default {
data() {
return {
currenPhoneArea: '',
formData: {
phone:"",
code:""
}
}
},
computed: {
tipText() {
return `验证码已通过短信发送至${this.currenPhoneArea} ${this.formData.phone}。密码为6 - 20位`
},
canSubmit() {
return this.isPhone && this.isPwd && this.isCode;
}
},
onLoad(event) {
},
onReady() {
},
methods: {
...mapMutations({
setUserInfo: 'user/login'
}),
/**
* 完成并提交
*/
submit() {
console.log(this.formData);
this.request('user-center/bind_mobile_by_sms', {
"mobile": this.formData.phone,
"code": this.formData.code
}, (data, result) => {
console.log(result);
this.setUserInfo({"mobile":result.mobile})
uni.showToast({
title: result.msg,
icon: 'none'
});
if (result.code === 0) {
uni.navigateBack()
}
})
}
}
}
</script>
<style>
.box {
align-items: center;
justify-content: center;
padding-top: 10px;
}
.box /deep/ .uni-easyinput__content {
height: 45px;
}
.phone-input-box {
width: 650rpx;
height: 50px;
margin-top: 16px;
background-color: #f9f9f9;
border-radius: 6rpx;
flex-direction: row;
flex-wrap: nowrap;
}
.send-btn-box {
width: 650rpx;
margin-top: 15px;
}
</style>
<template>
<view>
<uni-list>
<uni-list-item class="item" link>
<view @click="setAvatar" slot="body" class="item">
<text>头像</text>
<image class="avatarUrl" :src="userInfo.avatar||nullAvatarUrl" mode="widthFix"></image>
</view>
</uni-list-item>
<uni-list-item class="item" @click="setNickname()" title="昵称" :rightText="userInfo.nickname||'未设置'" link></uni-list-item>
<uni-list-item class="item" @click="bindMobile" title="手机号" :rightText="userInfo.mobile||'未绑定'" link></uni-list-item>
</uni-list>
<uni-popup ref="dialog" type="dialog">
<uni-popup-dialog mode="input" :value="userInfo.nickname" @confirm="setNickname" title="设置昵称" placeholder="请输入要设置的昵称">
</uni-popup-dialog>
</uni-popup>
</view>
</template>
<script>
import {
mapMutations,
mapGetters
} from 'vuex';
const db = uniCloud.database();
const usersTable = db.collection('uni-id-users')
export default {
data() {
return {
nullAvatarUrl: '/static/uni-center/logo.png',
univerifyStyle: {
authButton: {
"title": "本机号码一键绑定", // 授权按钮文案
},
otherLoginButton: {
"title": "其他号码绑定",
}
}
}
},
computed: {
...mapGetters({
userInfo: 'user/info',
login: 'user/hasLogin'
})
},
methods: {
...mapMutations({
setUserInfo: 'user/login'
}),
bindMobile() {
// #ifdef APP-PLUS
uni.preLogin({
provider: 'univerify',
success: this.univerify(), //预登录成功
fail(res) { // 预登录失败
// 不显示一键登录选项(或置灰)
console.log(res)
}
})
// #endif
// #ifndef APP-PLUS
this.bindMobileBySmsCode()
//...去用验证码绑定
// #endif
},
univerify() {
uni.login({
"provider": 'univerify',
"univerifyStyle": this.univerifyStyle,
success: async e => {
console.log(e.authResult);
this.request('user-center/bind_mobile_by_univerify',
e.authResult,
(data, result) =>
{
console.log(result);
if(result.code===0){
this.setUserInfo({"mobile":result.mobile})
uni.closeAuthView()
}else{
uni.showModal({
content: JSON.stringify(result.msg),
showCancel: false,
complete() {
uni.closeAuthView()
}
});
}
}
)
},
fail: (err) => {
console.log(err);
this.bindMobileBySmsCode()
}
})
},
bindMobileBySmsCode() {
uni.navigateTo({
url:'/pages/ucenter/edit/bind-mobile/bind-mobile'
})
},
setNickname(nickname) {
console.log(nickname);
if (nickname) {
usersTable.where('_id==$env.uid').update({
nickname
}).then(e => {
console.log(e);
if (e.result.updated) {
uni.showToast({
title: '更新成功',
icon: 'none'
});
this.setUserInfo({
nickname
});
} else {
uni.showToast({
title: '没有变化',
icon: 'none'
});
}
})
this.$refs.dialog.close()
} else {
this.$refs.dialog.open()
}
},
setAvatar() {
console.log('点击编辑信息');
uni.chooseImage({
count: 1,
success: (res) => {
// 头像剪裁尺寸
let options = {
width: 600,
height: 600
}
// 剪裁并上传头像
uni.navigateTo({
url: '/pages/ucenter/edit/uploadCutImageToUnicloud?path=' +
res
.tempFilePaths[0] +
`&options=${JSON.stringify(options)}`,
animationType: "fade-in",
events: {
uploadAvatarAfter: ({
url
}) => {
console.log(url);
// 使用 clientDB 提交数据
usersTable.where('_id==$env.uid').update({
avatar: url
}).then((res) => {
console.log(res);
uni.showToast({
icon: 'none',
title: '修改成功'
})
this.setUserInfo({
avatar: url
});
}).catch((err) => {
uni.showModal({
content: err.message ||
'请求服务失败',
showCancel: false
})
}).finally(() => {
uni.hideLoading()
})
}
}
});
}
})
},
}
}
</script>
<style>
.item {
width: 750rpx;
flex-direction: row;
justify-content: space-between;
align-items: center;
height: 60px;
}
.avatarUrl {
width: 50px;
height: 50px;
border-radius: 6px;
}
</style>
...@@ -5,7 +5,6 @@ page { ...@@ -5,7 +5,6 @@ page {
flex: 1; flex: 1;
height: 100%; height: 100%;
} }
/* #endif */ /* #endif */
.wrap { .wrap {
/* #ifndef APP-NVUE */ /* #ifndef APP-NVUE */
......
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
<script> <script>
var univerify_first,currentWebview;//是否一键登陆优先 var univerify_first,currentWebview;//是否一键登陆优先
import baseappConfig from '@/baseapp.config.js'; import baseappConfig from '@/baseapp.config.js';
import mixin from '../../common/loginPage.mixin.js'; import mixin from '../common/loginPage.mixin.js';
var currentPage; var currentPage;
export default { export default {
mixins:[mixin], mixins:[mixin],
...@@ -131,7 +131,7 @@ import mixin from '../../common/loginPage.mixin.js'; ...@@ -131,7 +131,7 @@ import mixin from '../../common/loginPage.mixin.js';
</script> </script>
<style> <style>
@import url("../../common/loginPage.css"); @import url("../common/loginPage.css");
.content-top-title { .content-top-title {
text-align: center; text-align: center;
......
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
</template> </template>
<script> <script>
import mixin from '../../common/loginPage.mixin.js'; import mixin from '../common/loginPage.mixin.js';
export default { export default {
mixins:[mixin], mixins:[mixin],
data() { data() {
...@@ -101,7 +101,7 @@ ...@@ -101,7 +101,7 @@
</script> </script>
<style> <style>
@import url("../../common/loginPage.css"); @import url("../common/loginPage.css");
.phone-input-box{ .phone-input-box{
margin-top: 10px; margin-top: 10px;
} }
......
...@@ -21,8 +21,9 @@ ...@@ -21,8 +21,9 @@
<uni-forms-item name="pwd"> <uni-forms-item name="pwd">
<uni-easyinput type="password" class="phone-input-box" :inputBorder="false" <uni-easyinput type="password" class="phone-input-box" :inputBorder="false"
v-model="formData.pwd" placeholder="请输入密码"></uni-easyinput> v-model="formData.pwd" placeholder="请输入密码"></uni-easyinput>
</uni-forms-item> </uni-forms-item>
<button class="send-btn-box" type="primary" @click="pwdLogin">登录</button> <button class="send-btn-box" :disabled="!canLogin" :type="canLogin?'primary':'default'"
@click="pwdLogin">登录</button>
</uni-forms> </uni-forms>
<!-- 忘记密码 --> <!-- 忘记密码 -->
<view class="auth-box"> <view class="auth-box">
...@@ -36,7 +37,7 @@ ...@@ -36,7 +37,7 @@
</template> </template>
<script> <script>
import mixin from '../../common/loginPage.mixin.js'; import mixin from '../common/loginPage.mixin.js';
export default { export default {
mixins:[mixin], mixins:[mixin],
data() { data() {
...@@ -47,7 +48,7 @@ ...@@ -47,7 +48,7 @@
}, },
computed: { computed: {
canLogin() { canLogin() {
return this.isPhone && this.isPwd; return this.formData.phone.length && this.isPwd;
} }
}, },
methods: { methods: {
...@@ -55,12 +56,12 @@ ...@@ -55,12 +56,12 @@
* 页面跳转,找回密码 * 页面跳转,找回密码
*/ */
toRetrievePwd() { toRetrievePwd() {
if (!this.isPhone) return uni.showToast({ // if (!this.isPhone) return uni.showToast({
title: '请输入正确的手机号', // title: '请输入正确的手机号',
icon: 'none' // icon: 'none'
}); // });
uni.navigateTo({ uni.navigateTo({
url: '../pwd-retrieve/pwd-retrieve?phoneNumber=' + this.formData.phone + '&phoneArea=' + this.currenPhoneArea url: '../pwd-retrieve/pwd-retrieve?phoneNumber=' + (this.isPhone?this.formData.phone:'') + '&phoneArea=' + this.currenPhoneArea
}) })
}, },
/** /**
...@@ -119,7 +120,7 @@ ...@@ -119,7 +120,7 @@
toRegister(e){ toRegister(e){
console.log(e); console.log(e);
uni.navigateTo({ uni.navigateTo({
url:'/uni_modules/uni-login-page/pages/register/register' url:'/pages/ucenter/login-page/register/register'
}) })
} }
} }
...@@ -127,7 +128,7 @@ ...@@ -127,7 +128,7 @@
</script> </script>
<style> <style>
@import url("../../common/loginPage.css"); @import url("../common/loginPage.css");
.phone-input-box { .phone-input-box {
margin-top: 20rpx; margin-top: 20rpx;
} }
......
...@@ -8,11 +8,12 @@ ...@@ -8,11 +8,12 @@
<!-- 登录框 (选择手机号所属国家和地区需要另行实现) --> <!-- 登录框 (选择手机号所属国家和地区需要另行实现) -->
<uni-forms ref="form" :value="formData" :rules="rules"> <uni-forms ref="form" :value="formData" :rules="rules">
<uni-forms-item name="phone"> <uni-forms-item name="phone">
<uni-easyinput type="number" class="phone-input-box" :inputBorder="false" <!-- focus规则如果上一页携带来“手机号码”数据就focus验证码输入框,否则focus手机号码输入框 -->
<uni-easyinput :focus="!formData.phone.length" type="number" class="phone-input-box" :inputBorder="false"
v-model="formData.phone" maxlength="11" placeholder="请输入手机号"></uni-easyinput> v-model="formData.phone" maxlength="11" placeholder="请输入手机号"></uni-easyinput>
</uni-forms-item> </uni-forms-item>
<uni-forms-item name="code"> <uni-forms-item name="code">
<uni-easyinput type="number" class="phone-input-box" :inputBorder="false" <uni-easyinput :focus="formData.phone.length" type="number" class="phone-input-box" :inputBorder="false"
v-model="formData.code" maxlength="6" placeholder="请输入验证码"> v-model="formData.code" maxlength="6" placeholder="请输入验证码">
<template slot="right"> <template slot="right">
<login-short-code ref="shortCode" :phone="formData.phone"></login-short-code> <login-short-code ref="shortCode" :phone="formData.phone"></login-short-code>
...@@ -36,7 +37,7 @@ ...@@ -36,7 +37,7 @@
</template> </template>
<script> <script>
import mixin from '../../common/loginPage.mixin.js'; import mixin from '../common/loginPage.mixin.js';
export default { export default {
mixins:[mixin], mixins:[mixin],
data() { data() {
...@@ -60,7 +61,7 @@ import mixin from '../../common/loginPage.mixin.js'; ...@@ -60,7 +61,7 @@ import mixin from '../../common/loginPage.mixin.js';
}, },
onReady() { onReady() {
if(this.formData.phone){ if(this.formData.phone){
// this.$refs.shortCode.start(); this.$refs.shortCode.start();
} }
}, },
methods: { methods: {
...@@ -91,7 +92,7 @@ import mixin from '../../common/loginPage.mixin.js'; ...@@ -91,7 +92,7 @@ import mixin from '../../common/loginPage.mixin.js';
</script> </script>
<style> <style>
@import url("../../common/loginPage.css"); @import url("../common/loginPage.css");
.phone-input-box{ .phone-input-box{
margin-top: 20rpx; margin-top: 20rpx;
} }
......
<template> <template>
<view class="uni-container"> <view class="uni-container">
<uni-forms ref="form" :value="formData" :rules="rules" validate-trigger="submit" err-show-type="undertext"> <uni-forms ref="form" :value="formData" :rules="rules" validate-trigger="submit" err-show-type="undertext">
<uni-forms-item name="username" label="用户名" required> <uni-forms-item name="username" required>
<uni-easyinput placeholder="请输入用户名" v-model="formData.username" trim="both" /> <uni-easyinput :inputBorder="false" class="phone-input-box" placeholder="请输入用户名" v-model="formData.username" trim="both" />
</uni-forms-item> </uni-forms-item>
<uni-forms-item name="nickname" label="昵称"> <uni-forms-item name="nickname">
<uni-easyinput placeholder="请输入用户昵称" v-model="formData.nickname" trim="both" /> <uni-easyinput :inputBorder="false" class="phone-input-box" placeholder="请输入用户昵称" v-model="formData.nickname" trim="both" />
</uni-forms-item> </uni-forms-item>
<uni-forms-item name="password" label="密码" v-model="formData.password" required> <uni-forms-item name="password" v-model="formData.password" required>
<uni-easyinput placeholder="请输入6-20位密码" type="password" v-model="formData.password" trim="both" /> <uni-easyinput :inputBorder="false" class="phone-input-box" placeholder="请输入6-20位密码" type="password" v-model="formData.password" trim="both" />
</uni-forms-item> </uni-forms-item>
<uni-forms-item name="pwd2" label="确认密码" v-model="formData.pwd2" required> <uni-forms-item name="pwd2" v-model="formData.pwd2" required>
<uni-easyinput placeholder="再次输入密码" type="password" v-model="formData.pwd2" trim="both" /> <uni-easyinput :inputBorder="false" class="phone-input-box" placeholder="再次输入密码" type="password" v-model="formData.pwd2" trim="both" />
</uni-forms-item> </uni-forms-item>
<view class="uni-button-group"> <login-ikonw class="login-iknow" :link="link" text="登录即表示同意用户协议和隐私政策"></login-ikonw>
<button type="primary" class="uni-button" @click="submit">注册并登陆</button> <button class="send-btn-box" type="primary" @click="submit">注册并登陆</button>
</view>
</uni-forms> </uni-forms>
</view> </view>
</template> </template>
<script> <script>
import rules from './validator.js'; import rules from './validator.js';
import mixin from '../../common/loginPage.mixin.js'; import mixin from '../common/loginPage.mixin.js';
export default { export default {
mixins:[mixin], mixins:[mixin],
data() { data() {
...@@ -68,7 +67,8 @@ import mixin from '../../common/loginPage.mixin.js'; ...@@ -68,7 +67,8 @@ import mixin from '../../common/loginPage.mixin.js';
} }
</script> </script>
<style> <style>
@import url("../common/loginPage.css");
.uni-container { .uni-container {
padding: 15px; padding: 15px;
} }
...@@ -102,7 +102,6 @@ import mixin from '../../common/loginPage.mixin.js'; ...@@ -102,7 +102,6 @@ import mixin from '../../common/loginPage.mixin.js';
} }
.uni-button { .uni-button {
width: 184px;
padding: 12px 20px; padding: 12px 20px;
font-size: 14px; font-size: 14px;
border-radius: 4px; border-radius: 4px;
......
/** /**
* 判断Push是否开启 * 判断Push是否开启
*/ */
......
<template> <template>
<view class="content"> <view class="content">
<!-- 功能列表 --> <!-- 功能列表 -->
<uni-list :border="false" class="mb10" v-for="(sublist,index) in agreeList"> <uni-list :border="false" class="mt10" v-for="(sublist,index) in agreeList">
<uni-list-item :border="false" class="mb1" v-for="(item,i) in sublist" :key="i" :title="item.title" <uni-list-item :border="false" class="list-item" v-for="(item,i) in sublist" :key="i" :title="item.title"
:clickable="true" @click="itemClick(item)" :showSwitch="item.showSwitch" :switchChecked="item.isChecked" :clickable="true" @click="itemClick(item)" :showSwitch="item.showSwitch" :switchChecked="item.isChecked"
:link="!item.showSwitch"></uni-list-item> :link="!item.showSwitch"
v-if="item.event!='changePwd'||hasLogin"
></uni-list-item>
</uni-list> </uni-list>
<!-- 退出按钮 --> <!-- 退出按钮 -->
<view class="bottom-back" @click="clickLogout"> <view class="bottom-back" @click="clickLogout">
<text class="bottom-back-text" v-if="userInfo">退出登录</text> <text class="bottom-back-text" v-if="hasLogin">退出登录</text>
<text class="bottom-back-text" v-else>登录</text> <text class="bottom-back-text" v-else>登录</text>
</view> </view>
</view> </view>
...@@ -56,7 +58,8 @@ ...@@ -56,7 +58,8 @@
}, },
computed: { computed: {
...mapGetters({ ...mapGetters({
'userInfo': 'user/info' 'userInfo': 'user/info',
'hasLogin': 'user/hasLogin',
}) })
}, },
onLoad() { onLoad() {
...@@ -71,13 +74,14 @@ ...@@ -71,13 +74,14 @@
}), }),
toEdit() { toEdit() {
uni.navigateTo({ uni.navigateTo({
url: '/uni_modules/uni-id-users/pages/uni-id-users/edit' url: '/pages/ucenter/edit/edit'
}); });
}, },
changePwd() { changePwd() {
uni.navigateTo({ uni.navigateTo({
url: '/uni_modules/uni-login-page/pages/pwd-retrieve/pwd-retrieve?phoneNumber=' + (this url: '/pages/ucenter/login-page/pwd-retrieve/pwd-retrieve?phoneNumber='
.userInfo && this.userInfo.phone ? this.userInfo.phone : '') + '&phoneArea=+86', + (this.userInfo && this.userInfo.phone ? this.userInfo.phone : '')
+ '&phoneArea=+86',
fail: err => { fail: err => {
console.log(err); console.log(err);
} }
...@@ -185,7 +189,7 @@ ...@@ -185,7 +189,7 @@
}) })
}, },
clickLogout() { clickLogout() {
if (this.userInfo) { if (this.hasLogin) {
uni.showModal({ uni.showModal({
title: '提示', title: '提示',
content: '是否退出登录', content: '是否退出登录',
...@@ -202,12 +206,12 @@ ...@@ -202,12 +206,12 @@
}); });
} else { } else {
uni.navigateTo({ uni.navigateTo({
url: '/uni_modules/uni-login-page/pages/index/index' url: '/pages/ucenter/login-page/index/index'
}); });
} }
}, },
/** /**
* 每一项的点击事件 * 每一项的点击事件
*/ */
itemClick(item) { itemClick(item) {
if (item.event) { if (item.event) {
...@@ -218,17 +222,17 @@ ...@@ -218,17 +222,17 @@
uni.showLoading({ uni.showLoading({
title: '清除中', title: '清除中',
mask: true mask: true
}); });
/* /*
任何临时存储或删除不直接影响程序运行逻辑(清除缓存必定造成业务逻辑的变化,如:打开页面的图片不从缓存中读取而从网络请求)的内容都可以视为缓存。主要有storage、和file写入。 任何临时存储或删除不直接影响程序运行逻辑(清除缓存必定造成业务逻辑的变化,如:打开页面的图片不从缓存中读取而从网络请求)的内容都可以视为缓存。主要有storage、和file写入。
缓存分为三部分 缓存分为三部分
原生层(如:webview、x5播放器的、第三方sdk的、地图组件等) 原生层(如:webview、x5播放器的、第三方sdk的、地图组件等)
前端框架(重启就会自动清除) 前端框架(重启就会自动清除)
开发者自己的逻辑(如: 开发者自己的逻辑(如:
本示例的 检测更新功能下载了apk安装包, 本示例的 检测更新功能下载了apk安装包,
其他逻辑需要根据开发者自己的业务设计 其他逻辑需要根据开发者自己的业务设计
比如:有聊天功能的应用,聊天记录是否视为缓存,还是单独提供清除聊天记录的功能由开发者自己设计 比如:有聊天功能的应用,聊天记录是否视为缓存,还是单独提供清除聊天记录的功能由开发者自己设计
*/ */
uni.getSavedFileList({ uni.getSavedFileList({
success:res=>{ success:res=>{
...@@ -236,24 +240,24 @@ ...@@ -236,24 +240,24 @@
uni.removeSavedFile({ uni.removeSavedFile({
filePath: res.fileList[0].filePath, filePath: res.fileList[0].filePath,
complete:res=>{ complete:res=>{
console.log(res); console.log(res);
uni.hideLoading() uni.hideLoading()
uni.showToast({ uni.showToast({
title: '清除成功', title: '清除成功',
icon: 'none' icon: 'none'
}); });
} }
}); });
}else{ }else{
uni.hideLoading() uni.hideLoading()
uni.showToast({ uni.showToast({
title: '清除成功', title: '清除成功',
icon: 'none' icon: 'none'
}); });
} }
}, },
complete:e=>{ complete:e=>{
console.log(e); console.log(e);
} }
}); });
}, },
...@@ -314,15 +318,16 @@ ...@@ -314,15 +318,16 @@
font-size: 33rpx; font-size: 33rpx;
} }
.mb10 { .mt10 {
margin-bottom: 10px; margin-top: 10px;
} }
.content /deep/ .uni-list { .content /deep/ .uni-list {
background-color: #F9F9F9; background-color: #F9F9F9;
} }
.mb1 { .list-item {
margin-bottom: 1px; height: 50px;
margin-bottom: 1px;
} }
</style> </style>
<template> <template>
<view class="center"> <view class="center">
<view class="userInfo" @click="toEdit"> <view class="userInfo" @click="toUserInfo">
<image class="logo-img" :src="login ? (userInfo.avatar || avatarUrl) :avatarUrl"></image> <image class="logo-img" :src="userInfo.avatar||avatarUrl"></image>
<view class="logo-title"> <view class="logo-title">
<text class="uer-name">{{login ? userInfo.nickname||userInfo.username||userInfo.mobile : '未登录'}}</text> <text class="uer-name">{{userInfo.nickname||userInfo.username||userInfo.mobile||'未登录'}}</text>
<text class="go-login-navigat-arrow navigat-arrow" v-if="!login">&#xe65e;</text> <text class="go-login-navigat-arrow navigat-arrow" v-if="!login">&#xe65e;</text>
</view> </view>
</view> </view>
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
<view class="item-footer-badge"></view> <view class="item-footer-badge"></view>
</view> </view>
</uni-list-item> </uni-list-item>
</uni-list> </uni-list>
</view> </view>
</template> </template>
...@@ -83,16 +83,17 @@ ...@@ -83,16 +83,17 @@
}, { }, {
title: '设置', title: '设置',
to: '/pages/ucenter/settings/settings' to: '/pages/ucenter/settings/settings'
}, { }],
[{
title: '关于', title: '关于',
to: '/pages/ucenter/about/about' to: '/pages/ucenter/about/about'
}] }]
] ]
} }
}, },
onLoad() { onLoad() {
//#ifdef APP-PLUS //#ifdef APP-PLUS
this.ucenterList[this.ucenterList.length - 1].unshift({ this.ucenterList[this.ucenterList.length - 2].unshift({
title: '检查更新', title: '检查更新',
rightText: this.appVersion.version + '-' + this.appVersion.versionCode, rightText: this.appVersion.version + '-' + this.appVersion.versionCode,
event: 'checkVersion', event: 'checkVersion',
...@@ -106,14 +107,15 @@ ...@@ -106,14 +107,15 @@
login: 'user/hasLogin' login: 'user/hasLogin'
}) })
// #ifdef APP-PLUS // #ifdef APP-PLUS
,appVersion() { ,
appVersion() {
return getApp().appVersion return getApp().appVersion
} }
// #endif // #endif
}, },
methods: { methods: {
...mapMutations({ ...mapMutations({
setUserInfo: 'user/login' setUserInfo: 'user/login'
}), }),
toSettings() { toSettings() {
uni.navigateTo({ uni.navigateTo({
...@@ -129,64 +131,20 @@ ...@@ -129,64 +131,20 @@
} }
}, },
async checkVersion() { async checkVersion() {
let res = await callCheckVersion() let res = await callCheckVersion()
console.log(res); console.log(res);
if(res.result.code == 0){ if (res.result.code == 0) {
uni.showToast({ uni.showToast({
title: res.result.message, title: res.result.message,
icon: 'none' icon: 'none'
}); });
} }
checkUpdate() checkUpdate()
}, },
toEdit() { toUserInfo() {
console.log('点击编辑信息'); uni.navigateTo({
// uni.navigateTo({ url: '/pages/ucenter/edit/edit'
// url: '/uni_modules/uni-id-users/pages/uni-id-users/edit' })
// })
const token = uni.getStorageSync('uni_id_token')
if(token){
uni.chooseImage({
count:1,
success:(res)=> {
// 头像剪裁尺寸
let options = {
width:600,
height:600
}
// 剪裁并上传头像
uni.navigateTo({
url:'/pages/ucenter/edit/uploadCutImageToUnicloud?path=' + res.tempFilePaths[0] + `&options=${JSON.stringify(options)}`,
animationType:"fade-in",
events:{
uploadAvatarAfter:({url})=>{
console.log(url);
// 使用 clientDB 提交数据
db.collection('uni-id-users').where('_id==$env.uid').update({avatar:url}).then((res) => {
console.log(res);
uni.showToast({
icon: 'none',
title: '修改成功'
})
this.setUserInfo({avatar:url});
}).catch((err) => {
uni.showModal({
content: err.message || '请求服务失败',
showCancel: false
})
}).finally(() => {
uni.hideLoading()
})
}
}
});
}
})
}else{
uni.navigateTo({
url:'/uni_modules/uni-login-page/pages/index/index'
})
}
}, },
tapGrid(index) { tapGrid(index) {
uni.showToast({ uni.showToast({
...@@ -233,11 +191,6 @@ ...@@ -233,11 +191,6 @@
title: msg, title: msg,
icon: 'none' icon: 'none'
}); });
}).catch((err) => {
uni.showModal({
content: err.message || '请求服务失败',
showCancel: false
})
}).finally(() => { }).finally(() => {
uni.hideLoading() uni.hideLoading()
}) })
...@@ -254,11 +207,9 @@ ...@@ -254,11 +207,9 @@
font-style: normal; font-style: normal;
src: url('~@/static/text-icon.ttf') format('truetype'); src: url('~@/static/text-icon.ttf') format('truetype');
} }
page { page {
background-color: #f8f8f8; background-color: #f8f8f8;
} }
/* #endif*/ /* #endif*/
/* 解决头条小程序字体图标不显示问题,因为头条运行时自动插入了span标签,且有全局字体 */ /* 解决头条小程序字体图标不显示问题,因为头条运行时自动插入了span标签,且有全局字体 */
...@@ -266,15 +217,12 @@ ...@@ -266,15 +217,12 @@
text :not(view) { text :not(view) {
font-family: texticons; font-family: texticons;
} }
/* #endif */ /* #endif */
.center { .center {
flex: 1; flex: 1;
flex-direction: column; flex-direction: column;
background-color: #f8f8f8; background-color: #f8f8f8;
} }
.userInfo { .userInfo {
width: 750rpx; width: 750rpx;
padding: 20rpx; padding: 20rpx;
...@@ -283,25 +231,18 @@ ...@@ -283,25 +231,18 @@
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
} }
/* .logo-hover {
opacity: 0.8;
} */
.logo-img { .logo-img {
width: 150rpx; width: 150rpx;
height: 150rpx; height: 150rpx;
border-radius: 150rpx; border-radius: 150rpx;
border: solid 1px #FFFFFF; border: solid 1px #FFFFFF;
} }
.logo-title { .logo-title {
height: 150rpx; height: 150rpx;
flex: 1; flex: 1;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
flex-direction: row; flex-direction: row;
margin-left: 20rpx;
} }
.uer-name { .uer-name {
...@@ -383,6 +324,6 @@ ...@@ -383,6 +324,6 @@
/* #ifdef APP-NVUE */ /* #ifdef APP-NVUE */
border-radius: 10rpx; border-radius: 10rpx;
/* #endif */ /* #endif */
background-color: #DD524D; background-color: #DD524D;
} }
</style> </style>
// 上次启动时的用户信息 // 上次启动时的用户信息
let userHistory = uni.getStorageSync('userInfo') || null; let userHistory = uni.getStorageSync('userInfo') || {};
let state = { let state = {
/** /* 是否需要强制登录 */
* 是否需要强制登录
*/
forcedLogin: false, forcedLogin: false,
hasLogin: Boolean(userHistory), hasLogin: Boolean(userHistory),
info: userHistory info: userHistory
...@@ -22,20 +20,18 @@ let state = { ...@@ -22,20 +20,18 @@ let state = {
let _info = state.info; let _info = state.info;
state.info = Object.assign({}, _info, info); state.info = Object.assign({}, _info, info);
state.hasLogin = true; state.hasLogin = true;
uni.setStorageSync('userInfo', info); uni.setStorageSync('userInfo', state.info);
}, },
logout(state) { logout(state) {
state.info = null; state.info = {};
state.hasLogin = false; state.hasLogin = false;
uni.setStorageSync('userInfo', null); uni.setStorageSync('userInfo', {});
uni.setStorageSync('uni_id_token', ''); uni.setStorageSync('uni_id_token', '');
} }
}, },
actions = { actions = {
} }
export default { export default {
state, state,
getters, getters,
......
'use strict'; 'use strict';
let uniID = require('uni-id') let uniID = require('uni-id')
const uniCaptcha = require('uni-captcha') const uniCaptcha = require('uni-captcha')
const createConfig = require('uni-config-center')
const uniIdConfig = createConfig({
pluginId: 'uni-id'
})._config
const db = uniCloud.database() const db = uniCloud.database()
const dbCmd = db.command const dbCmd = db.command
exports.main = async (event, context) => { exports.main = async (event, context) => {
...@@ -84,8 +88,8 @@ exports.main = async (event, context) => { ...@@ -84,8 +88,8 @@ exports.main = async (event, context) => {
} : { } : {
state: 0 state: 0
}) })
if(res.type == 'register'){ if (res.type == 'register') {
await registerSuccess(res.uid) await registerSuccess(res.uid)
} }
return await uniIdLogCollection.add(logData) return await uniIdLogCollection.add(logData)
} }
...@@ -94,6 +98,46 @@ exports.main = async (event, context) => { ...@@ -94,6 +98,46 @@ exports.main = async (event, context) => {
let res = {} let res = {}
switch (event.action) { switch (event.action) {
case 'bind_mobile_by_univerify':
let {
appid, apiKey, apiSecret
} = uniIdConfig.service.univerify
let univerifyRes = await uniCloud.getPhoneNumber({
provider: 'univerify',
appid,
apiKey,
apiSecret,
access_token: params.access_token,
openid: params.openid
})
if (univerifyRes.code === 0) {
res = await uniID.bindMobile({
uid: params.uid,
mobile: univerifyRes.phoneNumber
})
res.mobile = univerifyRes.phoneNumber
}
break;
case 'bind_mobile_by_sms':
console.log({
uid: params.uid,
mobile: params.mobile,
code: params.code
});
let verifyCode = await uniID.verifyCode({
mobile: params.mobile,
code: params.code
})
if (verifyCode.code === 0) {
res = await uniID.bindMobile({
uid: params.uid,
mobile: params.mobile
})
} else {
res = verifyCode
}
console.log(res, verifyCode);
break;
case 'register': case 'register':
let { let {
username, password, gender, nickname username, password, gender, nickname
...@@ -164,7 +208,15 @@ exports.main = async (event, context) => { ...@@ -164,7 +208,15 @@ exports.main = async (event, context) => {
case 'logout': case 'logout':
res = await uniID.logout(event.uniIdToken) res = await uniID.logout(event.uniIdToken)
break; break;
case 'sendSmsCode': case 'sendSmsCode':
//123546
return uniID.setVerifyCode({
mobile: params.mobile,
code:'123456'
})
// 简单限制一下客户端调用频率 // 简单限制一下客户端调用频率
const ipLimit = await db.collection('uni-verify').where({ const ipLimit = await db.collection('uni-verify').where({
ip: context.CLIENTIP, ip: context.CLIENTIP,
......
{
"name": "user-center",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"uni-captcha": "file:../../../uni_modules/uni-captcha/uniCloud/cloudfunctions/common/uni-captcha",
"uni-config-center": "file:../../../uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center",
"uni-id": "file:../../../uni_modules/uni-id/uniCloud/cloudfunctions/common/uni-id"
}
},
"../../../uni_modules/uni-captcha/uniCloud/cloudfunctions/common/uni-captcha": {
"version": "0.1.0",
"license": "Apache-2.0"
},
"../../../uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center": {
"version": "0.0.2",
"license": "Apache-2.0"
},
"../../../uni_modules/uni-id/uniCloud/cloudfunctions/common/uni-id": {
"version": "3.0.12",
"license": "Apache-2.0",
"dependencies": {
"uni-config-center": "file:../../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
}
},
"node_modules/uni-captcha": {
"resolved": "../../../uni_modules/uni-captcha/uniCloud/cloudfunctions/common/uni-captcha",
"link": true
},
"node_modules/uni-config-center": {
"resolved": "../../../uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center",
"link": true
},
"node_modules/uni-id": {
"resolved": "../../../uni_modules/uni-id/uniCloud/cloudfunctions/common/uni-id",
"link": true
}
},
"dependencies": {
"uni-captcha": {
"version": "file:../../../uni_modules/uni-captcha/uniCloud/cloudfunctions/common/uni-captcha"
},
"uni-config-center": {
"version": "file:../../../uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
},
"uni-id": {
"version": "file:../../../uni_modules/uni-id/uniCloud/cloudfunctions/common/uni-id",
"requires": {
"uni-config-center": "file:../../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
}
}
}
}
...@@ -9,8 +9,8 @@ ...@@ -9,8 +9,8 @@
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"uni-captcha": "file:../../../../uni-captcha/uniCloud/cloudfunctions/common/uni-captcha", "uni-captcha": "file:../../../uni_modules/uni-captcha/uniCloud/cloudfunctions/common/uni-captcha",
"uni-config-center": "file:../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center", "uni-config-center": "file:../../../uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center",
"uni-id": "file:../../../../uni-id/uniCloud/cloudfunctions/common/uni-id" "uni-id": "file:../../../uni_modules/uni-id/uniCloud/cloudfunctions/common/uni-id"
} }
} }
{ {
"name": "user", "name": "user",
"version": "1.0.0", "version": "1.0.0",
"lockfileVersion": 1, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": {
"": {
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"uni-captcha": "file:../../../../uni-captcha/uniCloud/cloudfunctions/common/uni-captcha",
"uni-config-center": "file:../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center",
"uni-id": "file:../../../../uni-id/uniCloud/cloudfunctions/common/uni-id"
}
},
"../../../../uni-captcha/uniCloud/cloudfunctions/common/uni-captcha": {},
"../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center": {},
"../../../../uni-id/uniCloud/cloudfunctions/common/uni-id": {
"dependencies": {
"uni-config-center": "file:../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
}
},
"../../../../uni-id/uniCloud/cloudfunctions/common/uni-id/node_modules/uni-config-center": {
"resolved": "../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center",
"link": true
},
"node_modules/uni-captcha": {
"resolved": "../../../../uni-captcha/uniCloud/cloudfunctions/common/uni-captcha",
"link": true
},
"node_modules/uni-config-center": {
"resolved": "../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center",
"link": true
},
"node_modules/uni-id": {
"resolved": "../../../../uni-id/uniCloud/cloudfunctions/common/uni-id",
"link": true
}
},
"dependencies": { "dependencies": {
"uni-captcha": { "uni-captcha": {
"version": "file:../../../../uni-captcha/uniCloud/cloudfunctions/common/uni-captcha" "version": "file:../../../../uni-captcha/uniCloud/cloudfunctions/common/uni-captcha"
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
"bsonType": "object", "bsonType": "object",
"required": ["user_id", "title", "content"], "required": ["user_id", "title", "content"],
"permission": { "permission": {
"read": "doc.uid == auth.uid && doc.article_status == 0 || doc.article_status == 1", "read": "doc.article_status == 0 || doc.article_status == 1",
"create": "auth.uid != null", "create": "auth.uid != null",
"update": "doc.uid == auth.uid", "update": "doc.uid == auth.uid",
"delete": "doc.uid == auth.uid" "delete": "doc.uid == auth.uid"
......
...@@ -8,9 +8,9 @@ ...@@ -8,9 +8,9 @@
</view> </view>
</template> </template>
</uni-forms-item> </uni-forms-item>
<uni-forms-item name="gender" label="性别" required> <!-- <uni-forms-item name="gender" label="性别" required>
<uni-data-checkbox v-model="formData.gender" :localdata="formOptions.gender_localdata" /> <uni-data-checkbox v-model="formData.gender" :localdata="formOptions.gender_localdata" />
</uni-forms-item> </uni-forms-item> -->
<uni-forms-item name="mobile" label="手机号码" v-if="formData.mobile"> <uni-forms-item name="mobile" label="手机号码" v-if="formData.mobile">
<uni-easyinput placeholder="手机号码" :disabled="true" v-model="formData.mobile" trim="both" /> <uni-easyinput placeholder="手机号码" :disabled="true" v-model="formData.mobile" trim="both" />
</uni-forms-item> </uni-forms-item>
......
{
"id": "uni-login-page",
"displayName": "uni-login-page",
"version": "1.0.0",
"description": "uni-login-page",
"keywords": [
"uni-login-page"
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"category": [
"前端页面模板",
"前端页面模板"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "",
"data": "",
"permissions": ""
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "u",
"aliyun": "u"
},
"client": {
"App": {
"app-vue": "u",
"app-nvue": "u"
},
"H5-mobile": {
"Safari": "u",
"Android Browser": "u",
"微信浏览器(Android)": "u",
"QQ浏览器(Android)": "u"
},
"H5-pc": {
"Chrome": "u",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "u",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}
\ No newline at end of file
# uni-login-page
\ No newline at end of file
{
"name": "user-center",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"uni-captcha": {
"version": "file:../../../../uni-captcha/uniCloud/cloudfunctions/common/uni-captcha"
},
"uni-config-center": {
"version": "file:../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
},
"uni-id": {
"version": "file:../../../../uni-id/uniCloud/cloudfunctions/common/uni-id",
"requires": {
"uni-config-center": "file:../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
},
"dependencies": {
"uni-config-center": {
"version": "file:../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
}
}
}
}
}
## 1.2.9(2021-02-05) ## 1.3.0(2021-04-13)
- 优化 组件引用关系,通过uni_modules引用组件 修复某些情况下uni-popup-dialog输入框的值获取失败的问题
## 1.2.8(2021-02-05) ## 1.2.9(2021-02-05)
- 调整为uni_modules目录规范 - 优化 组件引用关系,通过uni_modules引用组件
## 1.2.7(2021-02-05) ## 1.2.8(2021-02-05)
- 调整为uni_modules目录规范 - 调整为uni_modules目录规范
- 新增 支持 PC 端 ## 1.2.7(2021-02-05)
- 新增 uni-popup-message 、uni-popup-dialog扩展组件支持 PC 端 - 调整为uni_modules目录规范
- 新增 支持 PC 端
- 新增 uni-popup-message 、uni-popup-dialog扩展组件支持 PC 端
// #ifdef H5 // #ifdef H5
export default { export default {
name: 'Keypress', name: 'Keypress',
props: { props: {
disable: { disable: {
type: Boolean, type: Boolean,
default: false default: false
} }
}, },
mounted () { mounted () {
const keyNames = { const keyNames = {
esc: ['Esc', 'Escape'], esc: ['Esc', 'Escape'],
tab: 'Tab', tab: 'Tab',
enter: 'Enter', enter: 'Enter',
space: [' ', 'Spacebar'], space: [' ', 'Spacebar'],
up: ['Up', 'ArrowUp'], up: ['Up', 'ArrowUp'],
left: ['Left', 'ArrowLeft'], left: ['Left', 'ArrowLeft'],
right: ['Right', 'ArrowRight'], right: ['Right', 'ArrowRight'],
down: ['Down', 'ArrowDown'], down: ['Down', 'ArrowDown'],
delete: ['Backspace', 'Delete', 'Del'] delete: ['Backspace', 'Delete', 'Del']
} }
const listener = ($event) => { const listener = ($event) => {
if (this.disable) { if (this.disable) {
return return
} }
const keyName = Object.keys(keyNames).find(key => { const keyName = Object.keys(keyNames).find(key => {
const keyName = $event.key const keyName = $event.key
const value = keyNames[key] const value = keyNames[key]
return value === keyName || (Array.isArray(value) && value.includes(keyName)) return value === keyName || (Array.isArray(value) && value.includes(keyName))
}) })
if (keyName) { if (keyName) {
// 避免和其他按键事件冲突 // 避免和其他按键事件冲突
setTimeout(() => { setTimeout(() => {
this.$emit(keyName, {}) this.$emit(keyName, {})
}, 0) }, 0)
} }
} }
document.addEventListener('keyup', listener) document.addEventListener('keyup', listener)
this.$once('hook:beforeDestroy', () => { this.$once('hook:beforeDestroy', () => {
document.removeEventListener('keyup', listener) document.removeEventListener('keyup', listener)
}) })
}, },
render: () => {} render: () => {}
} }
// #endif // #endif
<template> <template>
<view class="uni-popup-dialog"> <view class="uni-popup-dialog">
<view class="uni-dialog-title"> <view class="uni-dialog-title">
<text class="uni-dialog-title-text" :class="['uni-popup__'+dialogType]">{{title}}</text> <text class="uni-dialog-title-text" :class="['uni-popup__'+dialogType]">{{title}}</text>
</view> </view>
<view class="uni-dialog-content"> <view class="uni-dialog-content">
<text class="uni-dialog-content-text" v-if="mode === 'base'">{{content}}</text> <text class="uni-dialog-content-text" v-if="mode === 'base'">{{content}}</text>
<input v-else class="uni-dialog-input" v-model="val" type="text" :placeholder="placeholder" :focus="focus"> <input v-else class="uni-dialog-input" v-model="val" type="text" :placeholder="placeholder" :focus="focus" >
</view> </view>
<view class="uni-dialog-button-group"> <view class="uni-dialog-button-group">
<view class="uni-dialog-button" @click="close"> <view class="uni-dialog-button" @click="close">
<text class="uni-dialog-button-text">取消</text> <text class="uni-dialog-button-text">取消</text>
</view> </view>
<view class="uni-dialog-button uni-border-left" @click="onOk"> <view class="uni-dialog-button uni-border-left" @click="onOk">
<text class="uni-dialog-button-text uni-button-color">确定</text> <text class="uni-dialog-button-text uni-button-color">确定</text>
</view> </view>
</view> </view>
<view v-if="popup.isDesktop" class="uni-popup-dialog__close" @click="close">
<span class="uni-popup-dialog__close-icon "></span> </view>
</view> </template>
<!-- #ifdef H5 -->
<keypress @esc="close" @enter="onOk"/> <script>
<!-- #endif --> /**
</view> * PopUp 弹出层-对话框样式
</template> * @description 弹出层-对话框样式
* @tutorial https://ext.dcloud.net.cn/plugin?id=329
<script> * @property {String} value input 模式下的默认值
// #ifdef H5 * @property {String} placeholder input 模式下输入提示
import keypress from './keypress.js' * @property {String} type = [success|warning|info|error] 主题样式
// #endif * @value success 成功
/** * @value warning 提示
* PopUp 弹出层-对话框样式 * @value info 消息
* @description 弹出层-对话框样式 * @value error 错误
* @tutorial https://ext.dcloud.net.cn/plugin?id=329 * @property {String} mode = [base|input] 模式、
* @property {String} value input 模式下的默认值 * @value base 基础对话框
* @property {String} placeholder input 模式下输入提示 * @value input 可输入对话框
* @property {String} type = [success|warning|info|error] 主题样式 * @property {String} content 对话框内容
* @value success 成功 * @property {Boolean} beforeClose 是否拦截取消事件
* @value warning 提示 * @event {Function} confirm 点击确认按钮触发
* @value info 消息 * @event {Function} close 点击取消按钮触发
* @value error 错误 */
* @property {String} mode = [base|input] 模式、
* @value base 基础对话框 export default {
* @value input 可输入对话框 name: "uniPopupDialog",
* @property {String} content 对话框内容 props: {
* @property {Boolean} beforeClose 是否拦截取消事件 value: {
* @event {Function} confirm 点击确认按钮触发 type: [String, Number],
* @event {Function} close 点击取消按钮触发 default: ''
*/ },
placeholder: {
export default { type: [String, Number],
name: "uniPopupDialog", default: '请输入内容'
components: { },
// #ifdef H5 /**
keypress * 对话框主题 success/warning/info/error 默认 success
// #endif */
}, type: {
props: { type: String,
value: { default: 'error'
type: [String, Number], },
default: '' /**
}, * 对话框模式 base/input
placeholder: { */
type: [String, Number], mode: {
default: '请输入内容' type: String,
}, default: 'base'
/** },
* 对话框主题 success/warning/info/error 默认 success /**
*/ * 对话框标题
type: { */
type: String, title: {
default: 'error' type: String,
}, default: '提示'
/** },
* 对话框模式 base/input /**
*/ * 对话框内容
mode: { */
type: String, content: {
default: 'base' type: String,
}, default: ''
/** },
* 对话框标题 /**
*/ * 拦截取消事件 ,如果拦截取消事件,必须监听close事件,执行 done()
title: { */
type: String, beforeClose: {
default: '提示' type: Boolean,
}, default: false
/** }
* 对话框内容 },
*/ data() {
content: { return {
type: String, dialogType: 'error',
default: '' focus: false,
}, val: ""
/** }
* 拦截取消事件 ,如果拦截取消事件,必须监听close事件,执行 done() },
*/ inject: ['popup'],
beforeClose: { watch: {
type: Boolean, type(val) {
default: false this.dialogType = val
} },
}, mode(val) {
data() { if (val === 'input') {
return { this.dialogType = 'info'
dialogType: 'error', }
focus: false, },
val: "" value(val) {
} this.val = val
}, }
inject: ['popup'], },
watch: { created() {
type(val) { // 对话框遮罩不可点击
this.dialogType = val this.popup.mkclick = false
}, if (this.mode === 'input') {
mode(val) { this.dialogType = 'info'
if (val === 'input') { this.val = this.value
this.dialogType = 'info' } else {
} this.dialogType = this.type
}, }
value(val) { },
this.val = val mounted() {
} this.focus = true
}, },
created() { methods: {
// 对话框遮罩不可点击 /**
this.popup.mkclick = false * 点击确认按钮
if (this.mode === 'input') { */
this.dialogType = 'info'
this.val = this.value
} else {
this.dialogType = this.type
}
},
mounted() {
this.focus = true
},
methods: {
/**
* 点击确认按钮
*/
onOk() { onOk() {
this.$emit('confirm', () => { if (this.mode === 'input'){
this.$emit('confirm', this.val)
}else{
this.popup.close() this.popup.close()
if (this.mode === 'input') this.val = this.value }
}, this.mode === 'input' ? this.val : '') },
}, /**
/** * 点击取消按钮
* 点击取消按钮 */
*/ close() {
close() { if (this.beforeClose) {
if (this.beforeClose) { this.$emit('close', () => {
this.$emit('close', () => { this.popup.close()
this.popup.close() })
}) return
return }
} this.popup.close()
this.popup.close() }
} }
} }
} </script>
</script>
<style scoped>
<style lang="scss" scoped> .uni-popup-dialog {
.uni-popup-dialog { width: 300px;
width: 300px; border-radius: 15px;
border-radius: 5px; background-color: #fff;
background-color: #fff; }
}
.uni-dialog-title {
.uni-dialog-title { /* #ifndef APP-NVUE */
/* #ifndef APP-NVUE */ display: flex;
display: flex; /* #endif */
/* #endif */ flex-direction: row;
flex-direction: row; justify-content: center;
justify-content: center; padding-top: 15px;
padding-top: 15px; padding-bottom: 5px;
padding-bottom: 5px; }
}
.uni-dialog-title-text {
.uni-dialog-title-text { font-size: 16px;
font-size: 16px; font-weight: 500;
font-weight: 500; }
}
.uni-dialog-content {
.uni-dialog-content { /* #ifndef APP-NVUE */
/* #ifndef APP-NVUE */ display: flex;
display: flex; /* #endif */
/* #endif */ flex-direction: row;
flex-direction: row; justify-content: center;
justify-content: center; align-items: center;
align-items: center; padding: 5px 15px 15px 15px;
padding: 5px 15px 15px 15px; }
}
.uni-dialog-content-text {
.uni-dialog-content-text { font-size: 14px;
font-size: 14px; color: #6e6e6e;
color: #6e6e6e; }
}
.uni-dialog-button-group {
.uni-dialog-button-group { /* #ifndef APP-NVUE */
/* #ifndef APP-NVUE */ display: flex;
display: flex; /* #endif */
/* #endif */ flex-direction: row;
flex-direction: row; border-top-color: #f5f5f5;
border-top-color: #f5f5f5; border-top-style: solid;
border-top-style: solid; border-top-width: 1px;
border-top-width: 1px; }
}
.uni-dialog-button {
.uni-dialog-button { /* #ifndef APP-NVUE */
/* #ifndef APP-NVUE */ display: flex;
display: flex; /* #endif */
/* #endif */
flex: 1;
flex: 1; flex-direction: row;
flex-direction: row; justify-content: center;
justify-content: center; align-items: center;
align-items: center; height: 45px;
height: 45px; }
/* #ifdef H5 */
cursor: pointer; .uni-border-left {
/* #endif */ border-left-color: #f0f0f0;
} border-left-style: solid;
border-left-width: 1px;
.uni-border-left { }
border-left-color: #f0f0f0;
border-left-style: solid; .uni-dialog-button-text {
border-left-width: 1px; font-size: 14px;
} }
.uni-dialog-button-text { .uni-button-color {
font-size: 14px; color: #007aff;
} }
.uni-button-color { .uni-dialog-input {
color: $uni-color-primary; flex: 1;
} font-size: 14px;
}
.uni-dialog-input {
flex: 1; .uni-popup__success {
font-size: 14px; color: #4cd964;
} }
.uni-popup__success { .uni-popup__warn {
color: $uni-color-success; color: #f0ad4e;
} }
.uni-popup__warn { .uni-popup__error {
color: $uni-color-warning; color: #dd524d;
} }
.uni-popup__error { .uni-popup__info {
color: $uni-color-error; color: #909399;
} }
</style>
.uni-popup__info { \ No newline at end of file
color: #909399;
}
.uni-popup-dialog__close {
/* #ifndef APP-NVUE */
display: block;
cursor: pointer;
/* #endif */
position: absolute;
top: 9px;
right: 17px;
}
.uni-popup-dialog__close-icon {
/* #ifndef APP-NVUE */
display: inline-block;
/* #endif */
width: 13px;
height: 1px;
background: #909399;
transform: rotate(45deg);
}
.uni-popup-dialog__close-icon::after {
/* #ifndef APP-NVUE */
content: '';
display: block;
/* #endif */
width: 13px;
height: 1px;
background: #909399;
transform: rotate(-90deg);
}
</style>
<template> <template>
<view class="uni-popup-message"> <view class="uni-popup-message">
<view class="uni-popup-message__box fixforpc-width" :class="'uni-popup__'+[type]"> <view class="uni-popup-message__box fixforpc-width" :class="'uni-popup__'+[type]">
<text class="uni-popup-message-text" :class="'uni-popup__'+[type]+'-text'">{{message}}</text> <text class="uni-popup-message-text" :class="'uni-popup__'+[type]+'-text'">{{message}}</text>
</view> </view>
</view> </view>
</template> </template>
<script> <script>
/** /**
* PopUp 弹出层-消息提示 * PopUp 弹出层-消息提示
* @description 弹出层-消息提示 * @description 弹出层-消息提示
* @tutorial https://ext.dcloud.net.cn/plugin?id=329 * @tutorial https://ext.dcloud.net.cn/plugin?id=329
* @property {String} type = [success|warning|info|error] 主题样式 * @property {String} type = [success|warning|info|error] 主题样式
* @value success 成功 * @value success 成功
* @value warning 提示 * @value warning 提示
* @value info 消息 * @value info 消息
* @value error 错误 * @value error 错误
* @property {String} message 消息提示文字 * @property {String} message 消息提示文字
* @property {String} duration 显示时间,设置为 0 则不会自动关闭 * @property {String} duration 显示时间,设置为 0 则不会自动关闭
*/ */
export default { export default {
name: 'UniPopupMessage', name: 'UniPopupMessage',
props: { props: {
/** /**
* 主题 success/warning/info/error 默认 success * 主题 success/warning/info/error 默认 success
*/ */
type: { type: {
type: String, type: String,
default: 'success' default: 'success'
}, },
/** /**
* 消息文字 * 消息文字
*/ */
message: { message: {
type: String, type: String,
default: '' default: ''
}, },
/** /**
* 显示时间,设置为 0 则不会自动关闭 * 显示时间,设置为 0 则不会自动关闭
*/ */
duration: { duration: {
type: Number, type: Number,
default: 3000 default: 3000
} }
}, },
inject: ['popup'], inject: ['popup'],
data() { data() {
return {} return {}
}, },
created() { created() {
this.popup.childrenMsg = this this.popup.childrenMsg = this
}, },
methods: { methods: {
open() { open() {
if (this.duration === 0) return if (this.duration === 0) return
clearTimeout(this.popuptimer) clearTimeout(this.popuptimer)
this.popuptimer = setTimeout(() => { this.popuptimer = setTimeout(() => {
this.popup.close() this.popup.close()
}, this.duration) }, this.duration)
}, },
close() { close() {
clearTimeout(this.popuptimer) clearTimeout(this.popuptimer)
} }
} }
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.uni-popup-message { .uni-popup-message {
/* #ifndef APP-NVUE */ /* #ifndef APP-NVUE */
display: flex; display: flex;
/* #endif */ /* #endif */
flex-direction: row; flex-direction: row;
justify-content: center; justify-content: center;
} }
.uni-popup-message__box { .uni-popup-message__box {
background-color: #e1f3d8; background-color: #e1f3d8;
padding: 10px 15px; padding: 10px 15px;
border-color: #eee; border-color: #eee;
border-style: solid; border-style: solid;
border-width: 1px; border-width: 1px;
flex: 1; flex: 1;
} }
@media screen and (min-width: 500px) { @media screen and (min-width: 500px) {
.fixforpc-width { .fixforpc-width {
margin-top: 20px; margin-top: 20px;
border-radius: 4px; border-radius: 4px;
flex: none; flex: none;
min-width: 380px; min-width: 380px;
/* #ifndef APP-NVUE */ /* #ifndef APP-NVUE */
max-width: 50%; max-width: 50%;
/* #endif */ /* #endif */
/* #ifdef APP-NVUE */ /* #ifdef APP-NVUE */
max-width: 500px; max-width: 500px;
/* #endif */ /* #endif */
} }
} }
.uni-popup-message-text { .uni-popup-message-text {
font-size: 14px; font-size: 14px;
padding: 0; padding: 0;
} }
.uni-popup__success { .uni-popup__success {
background-color: #e1f3d8; background-color: #e1f3d8;
} }
.uni-popup__success-text { .uni-popup__success-text {
color: #67C23A; color: #67C23A;
} }
.uni-popup__warn { .uni-popup__warn {
background-color: #faecd8; background-color: #faecd8;
} }
.uni-popup__warn-text { .uni-popup__warn-text {
color: #E6A23C; color: #E6A23C;
} }
.uni-popup__error { .uni-popup__error {
background-color: #fde2e2; background-color: #fde2e2;
} }
.uni-popup__error-text { .uni-popup__error-text {
color: #F56C6C; color: #F56C6C;
} }
.uni-popup__info { .uni-popup__info {
background-color: #F2F6FC; background-color: #F2F6FC;
} }
.uni-popup__info-text { .uni-popup__info-text {
color: #909399; color: #909399;
} }
</style> </style>
<template> <template>
<view class="uni-popup-share"> <view class="uni-popup-share">
<view class="uni-share-title"><text class="uni-share-title-text">{{title}}</text></view> <view class="uni-share-title"><text class="uni-share-title-text">{{title}}</text></view>
<view class="uni-share-content"> <view class="uni-share-content">
<view class="uni-share-content-box"> <view class="uni-share-content-box">
<view class="uni-share-content-item" v-for="(item,index) in bottomData" :key="index" @click.stop="select(item,index)"> <view class="uni-share-content-item" v-for="(item,index) in bottomData" :key="index" @click.stop="select(item,index)">
<image class="uni-share-image" :src="item.icon" mode="aspectFill"></image> <image class="uni-share-image" :src="item.icon" mode="aspectFill"></image>
<text class="uni-share-text">{{item.text}}</text> <text class="uni-share-text">{{item.text}}</text>
</view> </view>
</view> </view>
</view> </view>
<view class="uni-share-button-box"> <view class="uni-share-button-box">
<button class="uni-share-button" @click="close">取消</button> <button class="uni-share-button" @click="close">取消</button>
</view> </view>
</view> </view>
</template> </template>
<script> <script>
export default { export default {
name: 'UniPopupShare', name: 'UniPopupShare',
props: { props: {
title: { title: {
type: String, type: String,
default: '分享到' default: '分享到'
} }
}, },
inject: ['popup'], inject: ['popup'],
data() { data() {
return { return {
bottomData: [{ bottomData: [{
text: '微信', text: '微信',
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/c2b17470-50be-11eb-b680-7980c8a877b8.png', icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/c2b17470-50be-11eb-b680-7980c8a877b8.png',
name: 'weixin' name: 'wx'
}, },
{ {
text: 'QQ', text: '支付宝',
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/e7a79520-50be-11eb-b997-9918a5dda011.png', icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/d684ae40-50be-11eb-8ff1-d5dcf8779628.png',
name: 'qq' name: 'wx'
}, },
{ {
text: '新浪', text: 'QQ',
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/0dacdbe0-50bf-11eb-8ff1-d5dcf8779628.png', icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/e7a79520-50be-11eb-b997-9918a5dda011.png',
name: 'sinaweibo' name: 'qq'
}, },
{ {
text: '复制链接', text: '新浪',
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/2e0fdfe0-50bf-11eb-b997-9918a5dda011.png', icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/0dacdbe0-50bf-11eb-8ff1-d5dcf8779628.png',
name: 'copy' name: 'sina'
} },
] {
} text: '百度',
}, icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/1ec6e920-50bf-11eb-8a36-ebb87efcf8c0.png',
created() {}, name: 'copy'
methods: { },
/** {
* 选择内容 text: '其他',
*/ icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/2e0fdfe0-50bf-11eb-b997-9918a5dda011.png',
select(item, index) { name: 'more'
this.$emit('select', { }
item, ]
index }
}, () => { },
this.popup.close() created() {},
}) methods: {
}, /**
/** * 选择内容
* 关闭窗口 */
*/ select(item, index) {
close() { this.$emit('select', {
this.popup.close() item,
} index
} }, () => {
} this.popup.close()
</script> })
<style lang="scss" scoped> },
.uni-popup-share { /**
background-color: #fff; * 关闭窗口
} */
.uni-share-title { close() {
/* #ifndef APP-NVUE */ this.popup.close()
display: flex; }
/* #endif */ }
flex-direction: row; }
align-items: center; </script>
justify-content: center; <style lang="scss" scoped>
height: 40px; .uni-popup-share {
} background-color: #fff;
.uni-share-title-text { }
font-size: 14px; .uni-share-title {
color: #666; /* #ifndef APP-NVUE */
} display: flex;
.uni-share-content { /* #endif */
/* #ifndef APP-NVUE */ flex-direction: row;
display: flex; align-items: center;
/* #endif */ justify-content: center;
flex-direction: row; height: 40px;
justify-content: center; }
padding-top: 10px; .uni-share-title-text {
} font-size: 14px;
color: #666;
.uni-share-content-box { }
/* #ifndef APP-NVUE */ .uni-share-content {
display: flex; /* #ifndef APP-NVUE */
/* #endif */ display: flex;
flex-direction: row; /* #endif */
flex-wrap: wrap; flex-direction: row;
width: 360px; justify-content: center;
} padding-top: 10px;
}
.uni-share-content-item {
width: 90px; .uni-share-content-box {
/* #ifndef APP-NVUE */ /* #ifndef APP-NVUE */
display: flex; display: flex;
/* #endif */ /* #endif */
flex-direction: column; flex-direction: row;
justify-content: center; flex-wrap: wrap;
padding: 10px 0; width: 360px;
align-items: center; }
}
.uni-share-content-item {
.uni-share-content-item:active { width: 90px;
background-color: #f5f5f5; /* #ifndef APP-NVUE */
} display: flex;
/* #endif */
.uni-share-image { flex-direction: column;
width: 30px; justify-content: center;
height: 30px; padding: 10px 0;
} align-items: center;
}
.uni-share-text {
margin-top: 10px; .uni-share-content-item:active {
font-size: 14px; background-color: #f5f5f5;
color: #3B4144; }
}
.uni-share-image {
.uni-share-button-box { width: 30px;
/* #ifndef APP-NVUE */ height: 30px;
display: flex; }
/* #endif */
flex-direction: row; .uni-share-text {
padding: 10px 15px; margin-top: 10px;
} font-size: 14px;
color: #3B4144;
.uni-share-button { }
flex: 1;
border-radius: 50px; .uni-share-button-box {
color: #666; /* #ifndef APP-NVUE */
font-size: 16px; display: flex;
} /* #endif */
flex-direction: row;
.uni-share-button::after { padding: 10px 15px;
border-radius: 50px; }
}
</style> .uni-share-button {
flex: 1;
border-radius: 50px;
color: #666;
font-size: 16px;
}
.uni-share-button::after {
border-radius: 50px;
}
</style>
// #ifdef H5 // #ifdef H5
export default { export default {
name: 'Keypress', name: 'Keypress',
props: { props: {
disable: { disable: {
type: Boolean, type: Boolean,
default: false default: false
} }
}, },
mounted () { mounted () {
const keyNames = { const keyNames = {
esc: ['Esc', 'Escape'], esc: ['Esc', 'Escape'],
tab: 'Tab', tab: 'Tab',
enter: 'Enter', enter: 'Enter',
space: [' ', 'Spacebar'], space: [' ', 'Spacebar'],
up: ['Up', 'ArrowUp'], up: ['Up', 'ArrowUp'],
left: ['Left', 'ArrowLeft'], left: ['Left', 'ArrowLeft'],
right: ['Right', 'ArrowRight'], right: ['Right', 'ArrowRight'],
down: ['Down', 'ArrowDown'], down: ['Down', 'ArrowDown'],
delete: ['Backspace', 'Delete', 'Del'] delete: ['Backspace', 'Delete', 'Del']
} }
const listener = ($event) => { const listener = ($event) => {
if (this.disable) { if (this.disable) {
return return
} }
const keyName = Object.keys(keyNames).find(key => { const keyName = Object.keys(keyNames).find(key => {
const keyName = $event.key const keyName = $event.key
const value = keyNames[key] const value = keyNames[key]
return value === keyName || (Array.isArray(value) && value.includes(keyName)) return value === keyName || (Array.isArray(value) && value.includes(keyName))
}) })
if (keyName) { if (keyName) {
// 避免和其他按键事件冲突 // 避免和其他按键事件冲突
setTimeout(() => { setTimeout(() => {
this.$emit(keyName, {}) this.$emit(keyName, {})
}, 0) }, 0)
} }
} }
document.addEventListener('keyup', listener) document.addEventListener('keyup', listener)
this.$once('hook:beforeDestroy', () => { this.$once('hook:beforeDestroy', () => {
document.removeEventListener('keyup', listener) document.removeEventListener('keyup', listener)
}) })
}, },
render: () => {} render: () => {}
} }
// #endif // #endif
export default { export default {
created() { created() {
if (this.type === 'message') { if (this.type === 'message') {
// 不显示遮罩 // 不显示遮罩
this.maskShow = false this.maskShow = false
// 获取子组件对象 // 获取子组件对象
this.childrenMsg = null this.childrenMsg = null
} }
}, },
methods: { methods: {
customOpen() { customOpen() {
if (this.childrenMsg) { if (this.childrenMsg) {
this.childrenMsg.open() this.childrenMsg.open()
} }
}, },
customClose() { customClose() {
if (this.childrenMsg) { if (this.childrenMsg) {
this.childrenMsg.close() this.childrenMsg.close()
} }
} }
} }
} }
import message from './message.js'; import message from './message.js';
// 定义 type 类型:弹出类型:top/bottom/center // 定义 type 类型:弹出类型:top/bottom/center
const config = { const config = {
// 顶部弹出 // 顶部弹出
top: 'top', top: 'top',
// 底部弹出 // 底部弹出
bottom: 'bottom', bottom: 'bottom',
// 居中弹出 // 居中弹出
center: 'center', center: 'center',
// 消息提示 // 消息提示
message: 'top', message: 'top',
// 对话框 // 对话框
dialog: 'center', dialog: 'center',
// 分享 // 分享
share: 'bottom', share: 'bottom',
} }
export default { export default {
data() { data() {
return { return {
config: config, config: config,
popupWidth: 0, popupWidth: 0,
popupHeight: 0 popupHeight: 0
} }
}, },
mixins: [message], mixins: [message],
computed: { computed: {
isDesktop() { isDesktop() {
return this.popupWidth >= 500 && this.popupHeight >= 500 return this.popupWidth >= 500 && this.popupHeight >= 500
} }
}, },
mounted() { mounted() {
const fixSize = () => { const fixSize = () => {
const { const {
windowWidth, windowWidth,
windowHeight, windowHeight,
windowTop windowTop
} = uni.getSystemInfoSync() } = uni.getSystemInfoSync()
this.popupWidth = windowWidth this.popupWidth = windowWidth
this.popupHeight = windowHeight + windowTop this.popupHeight = windowHeight + windowTop
} }
fixSize() fixSize()
// #ifdef H5 // #ifdef H5
window.addEventListener('resize', fixSize) window.addEventListener('resize', fixSize)
this.$once('hook:beforeDestroy', () => { this.$once('hook:beforeDestroy', () => {
window.removeEventListener('resize', fixSize) window.removeEventListener('resize', fixSize)
}) })
// #endif // #endif
}, },
} }
export default { export default {
created() { created() {
if (this.type === 'share') { if (this.type === 'share') {
// 关闭点击 // 关闭点击
this.mkclick = false this.mkclick = false
} }
}, },
methods: { methods: {
customOpen() { customOpen() {
console.log('share 打开了'); console.log('share 打开了');
}, },
customClose() { customClose() {
console.log('share 关闭了'); console.log('share 关闭了');
} }
} }
} }
<template> <template>
<view v-if="showPopup" class="uni-popup" :class="[popupstyle, isDesktop ? 'fixforpc-z-index' : '']" <view v-if="showPopup" class="uni-popup" :class="[popupstyle, isDesktop ? 'fixforpc-z-index' : '']"
@touchmove.stop.prevent="clear"> @touchmove.stop.prevent="clear">
<uni-transition v-if="maskShow" class="uni-mask--hook" :mode-class="['fade']" :styles="maskClass" :duration="duration" <uni-transition v-if="maskShow" class="uni-mask--hook" :mode-class="['fade']" :styles="maskClass" :duration="duration"
:show="showTrans" @click="onTap" /> :show="showTrans" @click="onTap" />
<uni-transition :mode-class="ani" :styles="transClass" :duration="duration" :show="showTrans" @click="onTap"> <uni-transition :mode-class="ani" :styles="transClass" :duration="duration" :show="showTrans" @click="onTap">
<view class="uni-popup__wrapper-box" @click.stop="clear"> <view class="uni-popup__wrapper-box" @click.stop="clear">
<slot /> <slot />
</view> </view>
</uni-transition> </uni-transition>
<!-- #ifdef H5 --> <!-- #ifdef H5 -->
<keypress v-if="maskShow" @esc="onTap" /> <keypress v-if="maskShow" @esc="onTap" />
<!-- #endif --> <!-- #endif -->
</view> </view>
</template> </template>
<script> <script>
import popup from './popup.js' import popup from './popup.js'
// #ifdef H5 // #ifdef H5
import keypress from './keypress.js' import keypress from './keypress.js'
// #endif // #endif
/** /**
* PopUp 弹出层 * PopUp 弹出层
* @description 弹出层组件,为了解决遮罩弹层的问题 * @description 弹出层组件,为了解决遮罩弹层的问题
* @tutorial https://ext.dcloud.net.cn/plugin?id=329 * @tutorial https://ext.dcloud.net.cn/plugin?id=329
* @property {String} type = [top|center|bottom] 弹出方式 * @property {String} type = [top|center|bottom] 弹出方式
* @value top 顶部弹出 * @value top 顶部弹出
* @value center 中间弹出 * @value center 中间弹出
* @value bottom 底部弹出 * @value bottom 底部弹出
* @value message 消息提示 * @value message 消息提示
* @value dialog 对话框 * @value dialog 对话框
* @value share 底部分享示例 * @value share 底部分享示例
* @property {Boolean} animation = [ture|false] 是否开启动画 * @property {Boolean} animation = [ture|false] 是否开启动画
* @property {Boolean} maskClick = [ture|false] 蒙版点击是否关闭弹窗 * @property {Boolean} maskClick = [ture|false] 蒙版点击是否关闭弹窗
* @event {Function} change 打开关闭弹窗触发,e={show: false} * @event {Function} change 打开关闭弹窗触发,e={show: false}
*/ */
export default { export default {
name: 'UniPopup', name: 'UniPopup',
components: { components: {
// #ifdef H5 // #ifdef H5
keypress keypress
// #endif // #endif
}, },
props: { props: {
// 开启动画 // 开启动画
animation: { animation: {
type: Boolean, type: Boolean,
default: true default: true
}, },
// 弹出层类型,可选值,top: 顶部弹出层;bottom:底部弹出层;center:全屏弹出层 // 弹出层类型,可选值,top: 顶部弹出层;bottom:底部弹出层;center:全屏弹出层
// message: 消息提示 ; dialog : 对话框 // message: 消息提示 ; dialog : 对话框
type: { type: {
type: String, type: String,
default: 'center' default: 'center'
}, },
// maskClick // maskClick
maskClick: { maskClick: {
type: Boolean, type: Boolean,
default: true default: true
} }
}, },
provide() { provide() {
return { return {
popup: this popup: this
} }
}, },
mixins: [popup], mixins: [popup],
watch: { watch: {
/** /**
* 监听type类型 * 监听type类型
*/ */
type: { type: {
handler: function(newVal) { handler: function(newVal) {
this[this.config[newVal]]() this[this.config[newVal]]()
}, },
immediate: true immediate: true
}, },
isDesktop: { isDesktop: {
handler: function(newVal) { handler: function(newVal) {
this[this.config[this.type]]() this[this.config[this.type]]()
}, },
immediate: true immediate: true
}, },
/** /**
* 监听遮罩是否可点击 * 监听遮罩是否可点击
* @param {Object} val * @param {Object} val
*/ */
maskClick: { maskClick: {
handler: function(val) { handler: function(val) {
this.mkclick = val this.mkclick = val
}, },
immediate: true immediate: true
} }
}, },
data() { data() {
return { return {
duration: 300, duration: 300,
ani: [], ani: [],
showPopup: false, showPopup: false,
showTrans: false, showTrans: false,
maskClass: { maskClass: {
'position': 'fixed', 'position': 'fixed',
'bottom': 0, 'bottom': 0,
'top': 0, 'top': 0,
'left': 0, 'left': 0,
'right': 0, 'right': 0,
'backgroundColor': 'rgba(0, 0, 0, 0.4)' 'backgroundColor': 'rgba(0, 0, 0, 0.4)'
}, },
transClass: { transClass: {
'position': 'fixed', 'position': 'fixed',
'left': 0, 'left': 0,
'right': 0, 'right': 0,
}, },
maskShow: true, maskShow: true,
mkclick: true, mkclick: true,
popupstyle: this.isDesktop ? 'fixforpc-top' : 'top' popupstyle: this.isDesktop ? 'fixforpc-top' : 'top'
} }
}, },
created() { created() {
this.mkclick = this.maskClick this.mkclick = this.maskClick
if (this.animation) { if (this.animation) {
this.duration = 300 this.duration = 300
} else { } else {
this.duration = 0 this.duration = 0
} }
}, },
methods: { methods: {
clear(e) { clear(e) {
// TODO nvue 取消冒泡 // TODO nvue 取消冒泡
e.stopPropagation() e.stopPropagation()
}, },
open() { open() {
this.showPopup = true this.showPopup = true
this.$nextTick(() => { this.$nextTick(() => {
new Promise(resolve => { new Promise(resolve => {
clearTimeout(this.timer) clearTimeout(this.timer)
this.timer = setTimeout(() => { this.timer = setTimeout(() => {
this.showTrans = true this.showTrans = true
// fixed by mehaotian 兼容 app 端 // fixed by mehaotian 兼容 app 端
this.$nextTick(() => { this.$nextTick(() => {
resolve(); resolve();
}) })
}, 50); }, 50);
}).then(res => { }).then(res => {
// 自定义打开事件 // 自定义打开事件
clearTimeout(this.msgtimer) clearTimeout(this.msgtimer)
this.msgtimer = setTimeout(() => { this.msgtimer = setTimeout(() => {
this.customOpen && this.customOpen() this.customOpen && this.customOpen()
}, 100) }, 100)
this.$emit('change', { this.$emit('change', {
show: true, show: true,
type: this.type type: this.type
}) })
}) })
}) })
}, },
close(type) { close(type) {
this.showTrans = false this.showTrans = false
this.$nextTick(() => { this.$nextTick(() => {
this.$emit('change', { this.$emit('change', {
show: false, show: false,
type: this.type type: this.type
}) })
clearTimeout(this.timer) clearTimeout(this.timer)
// 自定义关闭事件 // 自定义关闭事件
this.customOpen && this.customClose() this.customOpen && this.customClose()
this.timer = setTimeout(() => { this.timer = setTimeout(() => {
this.showPopup = false this.showPopup = false
}, 300) }, 300)
}) })
}, },
onTap() { onTap() {
if (!this.mkclick) return if (!this.mkclick) return
this.close() this.close()
}, },
/** /**
* 顶部弹出样式处理 * 顶部弹出样式处理
*/ */
top() { top() {
this.popupstyle = this.isDesktop ? 'fixforpc-top' : 'top' this.popupstyle = this.isDesktop ? 'fixforpc-top' : 'top'
this.ani = ['slide-top'] this.ani = ['slide-top']
this.transClass = { this.transClass = {
'position': 'fixed', 'position': 'fixed',
'left': 0, 'left': 0,
'right': 0, 'right': 0,
} }
}, },
/** /**
* 底部弹出样式处理 * 底部弹出样式处理
*/ */
bottom() { bottom() {
this.popupstyle = 'bottom' this.popupstyle = 'bottom'
this.ani = ['slide-bottom'] this.ani = ['slide-bottom']
this.transClass = { this.transClass = {
'position': 'fixed', 'position': 'fixed',
'left': 0, 'left': 0,
'right': 0, 'right': 0,
'bottom': 0 'bottom': 0
} }
}, },
/** /**
* 中间弹出样式处理 * 中间弹出样式处理
*/ */
center() { center() {
this.popupstyle = 'center' this.popupstyle = 'center'
this.ani = ['zoom-out', 'fade'] this.ani = ['zoom-out', 'fade']
this.transClass = { this.transClass = {
'position': 'fixed', 'position': 'fixed',
/* #ifndef APP-NVUE */ /* #ifndef APP-NVUE */
'display': 'flex', 'display': 'flex',
'flexDirection': 'column', 'flexDirection': 'column',
/* #endif */ /* #endif */
'bottom': 0, 'bottom': 0,
'left': 0, 'left': 0,
'right': 0, 'right': 0,
'top': 0, 'top': 0,
'justifyContent': 'center', 'justifyContent': 'center',
'alignItems': 'center' 'alignItems': 'center'
} }
} }
} }
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.uni-popup { .uni-popup {
position: fixed; position: fixed;
/* #ifndef APP-NVUE */ /* #ifndef APP-NVUE */
z-index: 99; z-index: 99;
/* #endif */ /* #endif */
} }
.fixforpc-z-index { .fixforpc-z-index {
/* #ifndef APP-NVUE */ /* #ifndef APP-NVUE */
z-index: 999; z-index: 999;
/* #endif */ /* #endif */
} }
.uni-popup__mask { .uni-popup__mask {
position: absolute; position: absolute;
top: 0; top: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
right: 0; right: 0;
background-color: $uni-bg-color-mask; background-color: $uni-bg-color-mask;
opacity: 0; opacity: 0;
} }
.mask-ani { .mask-ani {
transition-property: opacity; transition-property: opacity;
transition-duration: 0.2s; transition-duration: 0.2s;
} }
.uni-top-mask { .uni-top-mask {
opacity: 1; opacity: 1;
} }
.uni-bottom-mask { .uni-bottom-mask {
opacity: 1; opacity: 1;
} }
.uni-center-mask { .uni-center-mask {
opacity: 1; opacity: 1;
} }
.uni-popup__wrapper { .uni-popup__wrapper {
/* #ifndef APP-NVUE */ /* #ifndef APP-NVUE */
display: block; display: block;
/* #endif */ /* #endif */
position: absolute; position: absolute;
} }
.top { .top {
/* #ifdef H5 */ /* #ifdef H5 */
top: var(--window-top); top: var(--window-top);
/* #endif */ /* #endif */
/* #ifndef H5 */ /* #ifndef H5 */
top: 0; top: 0;
/* #endif */ /* #endif */
} }
.fixforpc-top { .fixforpc-top {
top: 0; top: 0;
} }
.bottom { .bottom {
bottom: 0; bottom: 0;
} }
.uni-popup__wrapper-box { .uni-popup__wrapper-box {
/* #ifndef APP-NVUE */ /* #ifndef APP-NVUE */
display: block; display: block;
/* #endif */ /* #endif */
position: relative; position: relative;
/* iphonex 等安全区设置,底部安全区适配 */ /* iphonex 等安全区设置,底部安全区适配 */
/* #ifndef APP-NVUE */ /* #ifndef APP-NVUE */
padding-bottom: constant(safe-area-inset-bottom); padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom); padding-bottom: env(safe-area-inset-bottom);
/* #endif */ /* #endif */
} }
.content-ani { .content-ani {
// transition: transform 0.3s; // transition: transform 0.3s;
transition-property: transform, opacity; transition-property: transform, opacity;
transition-duration: 0.2s; transition-duration: 0.2s;
} }
.uni-top-content { .uni-top-content {
transform: translateY(0); transform: translateY(0);
} }
.uni-bottom-content { .uni-bottom-content {
transform: translateY(0); transform: translateY(0);
} }
.uni-center-content { .uni-center-content {
transform: scale(1); transform: scale(1);
opacity: 1; opacity: 1;
} }
</style> </style>
{ {
"id": "uni-popup", "id": "uni-popup",
"displayName": "PopUp 弹出层", "displayName": "PopUp 弹出层",
"version": "1.2.9", "version": "1.3.0",
"description": " Popup 组件,提供常用的弹层", "description": " Popup 组件,提供常用的弹层",
"keywords": [ "keywords": [
"popup", "popup",
"uni-ui", "uni-ui",
"弹出层", "弹出层",
"uni-popup" "uni-popup"
], ],
"repository": "https://github.com/dcloudio/uni-ui", "repository": "https://github.com/dcloudio/uni-ui",
"engines": { "engines": {
"HBuilderX": "" "HBuilderX": ""
}, },
"directories": { "directories": {
"example": "../../temps/example_temps" "example": "../../temps/example_temps"
}, },
"dcloudext": { "dcloudext": {
"category": [ "category": [
"前端组件", "前端组件",
"通用组件" "通用组件"
], ],
"sale": { "sale": {
"regular": { "regular": {
"price": "0.00" "price": "0.00"
}, },
"sourcecode": { "sourcecode": {
"price": "0.00" "price": "0.00"
} }
}, },
"contact": { "contact": {
"qq": "" "qq": ""
}, },
"declaration": { "declaration": {
"ads": "无", "ads": "无",
"data": "无", "data": "无",
"permissions": "无" "permissions": "无"
}, },
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
}, },
"uni_modules": { "uni_modules": {
"dependencies": [ "dependencies": [
"uni-transition" "uni-transition"
], ],
"encrypt": [], "encrypt": [],
"platforms": { "platforms": {
"cloud": { "cloud": {
"tcb": "y", "tcb": "y",
"aliyun": "y" "aliyun": "y"
}, },
"client": { "client": {
"App": { "App": {
"app-vue": "y", "app-vue": "y",
"app-nvue": "y" "app-nvue": "y"
}, },
"H5-mobile": { "H5-mobile": {
"Safari": "y", "Safari": "y",
"Android Browser": "y", "Android Browser": "y",
"微信浏览器(Android)": "y", "微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y" "QQ浏览器(Android)": "y"
}, },
"H5-pc": { "H5-pc": {
"Chrome": "y", "Chrome": "y",
"IE": "y", "IE": "y",
"Edge": "y", "Edge": "y",
"Firefox": "y", "Firefox": "y",
"Safari": "y" "Safari": "y"
}, },
"小程序": { "小程序": {
"微信": "y", "微信": "y",
"阿里": "y", "阿里": "y",
"百度": "y", "百度": "y",
"字节跳动": "y", "字节跳动": "y",
"QQ": "y" "QQ": "y"
}, },
"快应用": { "快应用": {
"华为": "u", "华为": "u",
"联盟": "u" "联盟": "u"
} }
} }
} }
} }
} }
\ No newline at end of file
## Popup 弹出层 ## Popup 弹出层
> 代码块: `uPopup` > 代码块: `uPopup`
> 关联组件:`uni-transition`,`uni-popup-dialog`,`uni-popup-message`,`uni-popup-share` > 关联组件:`uni-transition`,`uni-popup-dialog`,`uni-popup-message`,`uni-popup-share`
弹出层组件,在应用中弹出一个消息提示窗口、提示框等 弹出层组件,在应用中弹出一个消息提示窗口、提示框等
> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。 > 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。
> - 组件需要依赖 `sass` 插件 ,请自行手动安装 > - 组件需要依赖 `sass` 插件 ,请自行手动安装
> - `uni-popup-message` 、 `uni-popup-dialog` 等扩展ui组件,需要和 `uni-popup` 配套使用,暂不支持单独使用 > - `uni-popup-message` 、 `uni-popup-dialog` 等扩展ui组件,需要和 `uni-popup` 配套使用,暂不支持单独使用
> - `nvue` 中使用 `uni-popup` 时,尽量将组件置于其他元素后面,避免出现层级问题 > - `nvue` 中使用 `uni-popup` 时,尽量将组件置于其他元素后面,避免出现层级问题
> - `uni-popup` 并不能完全阻止页面滚动,可在打开 `uni-popup` 的时候手动去做一些处理,禁止页面滚动 > - `uni-popup` 并不能完全阻止页面滚动,可在打开 `uni-popup` 的时候手动去做一些处理,禁止页面滚动
> - 如果需要在子扩展组件内关闭 `uni-popup` ,请使用扩展(provide/inject)方式,其他方式可能会出现不可预知问题 > - 如果需要在子扩展组件内关闭 `uni-popup` ,请使用扩展(provide/inject)方式,其他方式可能会出现不可预知问题
> - 如果想在页面渲染完毕后就打开 `uni-popup` ,请在 `onReady` 或 `mounted` 生命周期内调用,确保组件渲染完毕 > - 如果想在页面渲染完毕后就打开 `uni-popup` ,请在 `onReady` 或 `mounted` 生命周期内调用,确保组件渲染完毕
> - 在微信小程序开发者工具中,启用真机调试,popup 会延时出现,是因为 setTimeout 在真机调试中的延时问题导致的,预览和发布小程序不会出现此问题 > - 在微信小程序开发者工具中,启用真机调试,popup 会延时出现,是因为 setTimeout 在真机调试中的延时问题导致的,预览和发布小程序不会出现此问题
> - 使用 `npm` 方式引入组件,如果确认引用正确,但是提示未注册组件或显示不正常,请尝试重新编译项目 > - 使用 `npm` 方式引入组件,如果确认引用正确,但是提示未注册组件或显示不正常,请尝试重新编译项目
> - `uni-popup` 中尽量不要使用 `scroll-view` 嵌套过多的内容,可能会影响组件的性能,导致组件无法打开或者打开卡顿 > - `uni-popup` 中尽量不要使用 `scroll-view` 嵌套过多的内容,可能会影响组件的性能,导致组件无法打开或者打开卡顿
> - `uni-popup` 不会覆盖原生 tabbar 和原生导航栏 > - `uni-popup` 不会覆盖原生 tabbar 和原生导航栏
### 安装方式 ### 安装方式
本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components` 本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`
如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55) 如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55)
## 基本用法 ## 基本用法
```html ```html
<button @click="open">打开弹窗</button> <button @click="open">打开弹窗</button>
<uni-popup ref="popup" type="bottom">底部弹出 Popup</uni-popup> <uni-popup ref="popup" type="bottom">底部弹出 Popup</uni-popup>
``` ```
```javascript ```javascript
export default { export default {
methods:{ methods:{
open(){ open(){
// 通过组件定义的ref调用uni-popup方法 // 通过组件定义的ref调用uni-popup方法
this.$refs.popup.open() this.$refs.popup.open()
} }
} }
} }
``` ```
## API ## API
### Popup Props ### Popup Props
| 属性名 | 类型 | 默认值 | 说明 | | 属性名 | 类型 | 默认值 | 说明 |
| :-: | :-: | :-: | :-: | | :-: | :-: | :-: | :-: |
| animation | Boolean |true | 是否开启动画 | | animation | Boolean |true | 是否开启动画 |
| type | String |center | 弹出方式 | | type | String |center | 弹出方式 |
| maskClick | Boolean |true | 蒙版点击是否关闭弹窗 | | maskClick | Boolean |true | 蒙版点击是否关闭弹窗 |
#### Type Options #### Type Options
| 属性名 | 说明 | | 属性名 | 说明 |
| :-: | :-: | | :-: | :-: |
| top | 顶部弹出 | | top | 顶部弹出 |
| center | 居中弹出 | | center | 居中弹出 |
| bottom | 底部弹出 | | bottom | 底部弹出 |
| message | 预置样式 :消息提示 | | message | 预置样式 :消息提示 |
| dialog | 预置样式 :对话框 | | dialog | 预置样式 :对话框 |
| share | 预置样式 :底部弹出分享示例 | | share | 预置样式 :底部弹出分享示例 |
### Popup Methods ### Popup Methods
|方法称名 |说明 |参数| |方法称名 |说明 |参数|
|:-: |:-: |:-:| |:-: |:-: |:-:|
|open |打开弹出层 |- | |open |打开弹出层 |- |
|close |关闭弹出层 |- | |close |关闭弹出层 |- |
### Popup Events ### Popup Events
|事件称名 |说明 |返回值 | |事件称名 |说明 |返回值 |
|:-: |:-: |:-: | |:-: |:-: |:-: |
|change |组件状态发生变化触发 |e={show: true|false,type:当前模式} | |change |组件状态发生变化触发 |e={show: true|false,type:当前模式} |
## 扩展组件说明 ## 扩展组件说明
`uni-popup` 其实并没有任何样式,只提供基础的动画效果,给用户一个弹出层解决方案,仅仅是这样并不能满足开发需求,所以我们提供了三种基础扩展样式 `uni-popup` 其实并没有任何样式,只提供基础的动画效果,给用户一个弹出层解决方案,仅仅是这样并不能满足开发需求,所以我们提供了三种基础扩展样式
### uni-popup-message 提示信息 ### uni-popup-message 提示信息
`uni-popup``type`属性改为 `message`,并引入对应组件即可使用消息提示 ,*该组件不支持单独使用* `uni-popup``type`属性改为 `message`,并引入对应组件即可使用消息提示 ,*该组件不支持单独使用*
**示例** **示例**
```html ```html
<uni-popup ref="popup" type="message"> <uni-popup ref="popup" type="message">
<uni-popup-message type="success" message="成功消息" :duration="2000"></uni-popup-message> <uni-popup-message type="success" message="成功消息" :duration="2000"></uni-popup-message>
</uni-popup> </uni-popup>
``` ```
### uni-popup-message 属性说明 ### uni-popup-message 属性说明
| 属性名 | 类型 | 默认值 | 说明 | | 属性名 | 类型 | 默认值 | 说明 |
| :-: | :-: | :-: | :-: | | :-: | :-: | :-: | :-: |
| type | String |success| 消息提示主题,可选值: success/warn/info/error | | type | String |success| 消息提示主题,可选值: success/warn/info/error |
| message | String |- | 消息提示文字 | | message | String |- | 消息提示文字 |
| duration | Number |3000 | 消息显示时间,超过显示时间组件自动关闭,设置为0 将不会关闭,需手动调用 close 方法关闭 | | duration | Number |3000 | 消息显示时间,超过显示时间组件自动关闭,设置为0 将不会关闭,需手动调用 close 方法关闭 |
### uni-popup-dialog 对话框 ### uni-popup-dialog 对话框
`uni-popup``type`属性改为 `dialog`,并引入对应组件即可使用对话框 ,*该组件不支持单独使用* `uni-popup``type`属性改为 `dialog`,并引入对应组件即可使用对话框 ,*该组件不支持单独使用*
**示例** **示例**
```html ```html
<uni-popup ref="popup" type="dialog"> <uni-popup ref="popup" type="dialog">
<uni-popup-dialog type="input" message="成功消息" :duration="2000" :before-close="true" @close="close" @confirm="confirm"></uni-popup-dialog> <uni-popup-dialog type="input" message="成功消息" :duration="2000" :before-close="true" @close="close" @confirm="confirm"></uni-popup-dialog>
</uni-popup> </uni-popup>
``` ```
```javascript ```javascript
export default { export default {
methods:{ methods:{
/** /**
* 点击取消按钮触发 * 点击取消按钮触发
* @param {Object} done * @param {Object} done
*/ */
close(done){ close(done){
// TODO 做一些其他的事情,before-close 为true的情况下,手动执行 done 才会关闭对话框 // TODO 做一些其他的事情,before-close 为true的情况下,手动执行 done 才会关闭对话框
// ... // ...
done() done()
}, },
/** /**
* 点击确认按钮触发 * 点击确认按钮触发
* @param {Object} done * @param {Object} done
* @param {Object} value * @param {Object} value
*/ */
confirm(done,value){ confirm(done,value){
// 输入框的值 // 输入框的值
console.log(value) console.log(value)
// TODO 做一些其他的事情,手动执行 done 才会关闭对话框 // TODO 做一些其他的事情,手动执行 done 才会关闭对话框
// ... // ...
done() done()
} }
} }
} }
``` ```
### uni-popup-dialog 属性说明 ### uni-popup-dialog 属性说明
| 属性名 | 类型 | 默认值 | 说明 | | 属性名 | 类型 | 默认值 | 说明 |
| :-: | :-: | :-: | :-: | | :-: | :-: | :-: | :-: |
| type | String |success| 对话框标题主题,可选值: success/warn/info/error | | type | String |success| 对话框标题主题,可选值: success/warn/info/error |
| mode | String |base | 对话框模式,可选值:base(提示对话框)/input(可输入对话框) | | mode | String |base | 对话框模式,可选值:base(提示对话框)/input(可输入对话框) |
| title | String |- | 对话框标题 | | title | String |- | 对话框标题 |
| content | String |- | 对话框内容,base模式下生效 | | content | String |- | 对话框内容,base模式下生效 |
| value | String\Number |- | 输入框默认值,input模式下生效 | | value | String\Number |- | 输入框默认值,input模式下生效 |
| placeholder | String |- | 输入框提示文字,input模式下生效 | | placeholder | String |- | 输入框提示文字,input模式下生效 |
| before-close | Boolean |false | 是否拦截取消按钮,如为true,则不会关闭对话框,关闭需要监听 dialog 的 close 事件,并执行 done()| | before-close | Boolean |false | 是否拦截取消按钮,如为true,则不会关闭对话框,关闭需要监听 dialog 的 close 事件,并执行 done()|
#### dialog 事件说明 #### dialog 事件说明
|事件称名 |说明 |返回值 | |事件称名 |说明 |返回值 |
|:-: |:-: |:-: | |:-: |:-: |:-: |
|close |点击dialog取消按钮触发 |done:执行关闭对话框 | |close |点击dialog取消按钮触发 |done:执行关闭对话框 |
|confirm |点击dialog确定按钮触发 |done:执行关闭对话框:value:input模式下输入框的值 | |confirm |点击dialog确定按钮触发 |done:执行关闭对话框:value:input模式下输入框的值 |
### uni-popup-share 分享示例 ### uni-popup-share 分享示例
分享示例,不作为最终可使用的组件,将 `uni-popup``type` 属性改为 `share`,并引入对应组件即可使用 ,*该组件不支持单独使用* 分享示例,不作为最终可使用的组件,将 `uni-popup``type` 属性改为 `share`,并引入对应组件即可使用 ,*该组件不支持单独使用*
**示例** **示例**
```html ```html
<uni-popup ref="popup" type="share"> <uni-popup ref="popup" type="share">
<uni-popup-share title="分享到" @select="select"></uni-popup-share> <uni-popup-share title="分享到" @select="select"></uni-popup-share>
</uni-popup> </uni-popup>
``` ```
### uni-popup-share 属性说明 ### uni-popup-share 属性说明
| 属性名| 类型 | 默认值 | 说明 | | 属性名| 类型 | 默认值 | 说明 |
| :-: | :-: | :-: | :-: | | :-: | :-: | :-: | :-: |
| title | String | | 分享弹窗标题 | | title | String | | 分享弹窗标题 |
### uni-popup-share 事件说明 ### uni-popup-share 事件说明
|事件称名 |说明 |返回值 | |事件称名 |说明 |返回值 |
|:-: |:-: |:-: | |:-: |:-: |:-: |
|select |选择触发 |e = {item,index}:所选参数,done:执行关闭窗口 | |select |选择触发 |e = {item,index}:所选参数,done:执行关闭窗口 |
**Tips** **Tips**
- share 分享组件,只是作为一个扩展示例,如果需要修改数据源,请到组件内修改 - share 分享组件,只是作为一个扩展示例,如果需要修改数据源,请到组件内修改
## 如何扩展自己的 uni-popup 弹出层样式? ## 如何扩展自己的 uni-popup 弹出层样式?
`uni-popup` 组件内容是通过 `slot` 插槽的方式去实现的,所以这极大的方便了我们的扩展。 `uni-popup` 组件内容是通过 `slot` 插槽的方式去实现的,所以这极大的方便了我们的扩展。
现在我们可以在不改动 `uni-popup` 组件主体的情况下,方便的去扩展我们自己的弹出层样式。 现在我们可以在不改动 `uni-popup` 组件主体的情况下,方便的去扩展我们自己的弹出层样式。
### 添加自定义类型 ### 添加自定义类型
如果要去扩展 `uni-popup`,我们需要把组件*引入本地*,才能去进行扩展。 如果要去扩展 `uni-popup`,我们需要把组件*引入本地*,才能去进行扩展。
组件放到本地后,在组件目录找到 `popup.js` ,在 `config` 变量中定义自己的类型,key 为当前要定义的类型,value 为弹出类型(top/bottom/center) 组件放到本地后,在组件目录找到 `popup.js` ,在 `config` 变量中定义自己的类型,key 为当前要定义的类型,value 为弹出类型(top/bottom/center)
我们以 `uni-popup-share` 为例,看如何扩展一个`share` 底部分享的一个 `uni-popup` 子组件,代码参考 `uni-popup-share.vue` 我们以 `uni-popup-share` 为例,看如何扩展一个`share` 底部分享的一个 `uni-popup` 子组件,代码参考 `uni-popup-share.vue`
```javascript ```javascript
// popup.js // popup.js
const config = { const config = {
// ... // ...
// 分享 key:share 为我们定义的类型 value : 'bottom' 为弹出方向(top/bottom/center) // 分享 key:share 为我们定义的类型 value : 'bottom' 为弹出方向(top/bottom/center)
// 这样配置好之后,我们自定义的弹出层就会从底部弹出 // 这样配置好之后,我们自定义的弹出层就会从底部弹出
share:'bottom', share:'bottom',
} }
``` ```
### 创建扩展组件 ### 创建扩展组件
在组件目录创建文件 ,例 `uni-popup-share/uni-popup-share.vue`,结构与其他组件没有区别。 在组件目录创建文件 ,例 `uni-popup-share/uni-popup-share.vue`,结构与其他组件没有区别。
在组件内直接编写样式逻辑即可,如需自定义效果更强,可以通过 props 接受页面参数。 在组件内直接编写样式逻辑即可,如需自定义效果更强,可以通过 props 接受页面参数。
### 与父组件 `uni-popup` 进行通讯 ### 与父组件 `uni-popup` 进行通讯
组件通讯我们使用了 `provide/inject` , 具体逻辑我们不需要关心,只要在子组件配置 `inject` ,即可获取父组件方法变量等。 组件通讯我们使用了 `provide/inject` , 具体逻辑我们不需要关心,只要在子组件配置 `inject` ,即可获取父组件方法变量等。
```javascript ```javascript
// uni-popup-share.vue // uni-popup-share.vue
export default { export default {
name: 'UniPopupShare', name: 'UniPopupShare',
props: { props: {
title: { title: {
type: String, type: String,
default: '分享到' default: '分享到'
} }
}, },
// 直接把下面这一行代码,放到自己的组件内 // 直接把下面这一行代码,放到自己的组件内
inject: ['popup'], inject: ['popup'],
// ... // ...
methons:{ methons:{
/** /**
* 定义的选择事件,选择内容后触发 * 定义的选择事件,选择内容后触发
*/ */
select(item, index) { select(item, index) {
// 将事件发送到页面,在页面进行监听 // 将事件发送到页面,在页面进行监听
this.$emit('select', { this.$emit('select', {
item, item,
index index
}, () => { }, () => {
// 延迟操作,执行父组件的close事件,关闭弹出层 // 延迟操作,执行父组件的close事件,关闭弹出层
this.popup.close() this.popup.close()
}) })
}, },
/** /**
* 关闭窗口 * 关闭窗口
*/ */
close() { close() {
// 执行父组件的close事件,关闭弹出层 // 执行父组件的close事件,关闭弹出层
this.popup.close() this.popup.close()
} }
} }
} }
``` ```
### 使用自定义组件 ### 使用自定义组件
通过上面几个步骤 ,我们就可以使用这个组件了,只需要把我们自定义的组件放置到 `uni-popup` 组件内即可 ,指定 `uni-popup``type` 为我们第一步定义好的 `share` 通过上面几个步骤 ,我们就可以使用这个组件了,只需要把我们自定义的组件放置到 `uni-popup` 组件内即可 ,指定 `uni-popup``type` 为我们第一步定义好的 `share`
```html ```html
<uni-popup ref="popup" type="share"> <uni-popup ref="popup" type="share">
<uni-popup-share title="分享到" @select="select"></uni-popup-share> <uni-popup-share title="分享到" @select="select"></uni-popup-share>
</uni-popup> </uni-popup>
``` ```
之后就可以按照 `uni-popup` 的使用方式去打开关闭弹出层了。更多细节可以参考 `uni-popup-message``uni-popup-dialog` 之后就可以按照 `uni-popup` 的使用方式去打开关闭弹出层了。更多细节可以参考 `uni-popup-message``uni-popup-dialog`
**Tips** **Tips**
- 如果扩展组件目录名和组件名不一致,可能不会被 `easycom` 正确引用,请配置`easycom`规则或修改组件名称 - 如果扩展组件目录名和组件名不一致,可能不会被 `easycom` 正确引用,请配置`easycom`规则或修改组件名称
### 分享你的组件 ### 分享你的组件
通过组件扩展,你可以扩展出更丰富的弹出层样式,如果您想让更多人使用你定制的组件,或者您有更好的点子或更好的实现方式,欢迎给我们提交 [PR](https://github.com/dcloudio/uni-ui/pulls),如被采用,会合并到示例中。 通过组件扩展,你可以扩展出更丰富的弹出层样式,如果您想让更多人使用你定制的组件,或者您有更好的点子或更好的实现方式,欢迎给我们提交 [PR](https://github.com/dcloudio/uni-ui/pulls),如被采用,会合并到示例中。
在使用中如遇到无法解决的问题,请提 [Issues](https://github.com/dcloudio/uni-ui/issues) 给我们。 在使用中如遇到无法解决的问题,请提 [Issues](https://github.com/dcloudio/uni-ui/issues) 给我们。
## 1.1.0(2021-04-22)
- 新增 通过方法自定义动画
- 新增 custom-class 非 NVUE 平台支持自定义 class 定制样式
- 优化 动画触发逻辑,使动画更流畅
- 优化 支持单独的动画类型
- 优化 文档示例
## 1.0.2(2021-02-05) ## 1.0.2(2021-02-05)
- 调整为uni_modules目录规范 - 调整为uni_modules目录规范
// const defaultOption = {
// duration: 300,
// timingFunction: 'linear',
// delay: 0,
// transformOrigin: '50% 50% 0'
// }
// #ifdef APP-NVUE
const nvueAnimation = uni.requireNativePlugin('animation')
// #endif
class MPAnimation {
constructor(options, _this) {
this.options = options
this.animation = uni.createAnimation(options)
this.currentStepAnimates = {}
this.next = 0
this.$ = _this
}
_nvuePushAnimates(type, args) {
let aniObj = this.currentStepAnimates[this.next]
let styles = {}
if (!aniObj) {
styles = {
styles: {},
config: {}
}
} else {
styles = aniObj
}
if (animateTypes1.includes(type)) {
if (!styles.styles.transform) {
styles.styles.transform = ''
}
let unit = ''
if(type === 'rotate'){
unit = 'deg'
}
styles.styles.transform += `${type}(${args+unit}) `
} else {
styles.styles[type] = `${args}`
}
this.currentStepAnimates[this.next] = styles
}
_animateRun(styles = {}, config = {}) {
let ref = this.$.$refs['ani'].ref
if (!ref) return
return new Promise((resolve, reject) => {
nvueAnimation.transition(ref, {
styles,
...config
}, res => {
resolve()
})
})
}
_nvueNextAnimate(animates, step = 0, fn) {
let obj = animates[step]
if (obj) {
let {
styles,
config
} = obj
this._animateRun(styles, config).then(() => {
step += 1
this._nvueNextAnimate(animates, step, fn)
})
} else {
this.currentStepAnimates = {}
typeof fn === 'function' && fn()
this.isEnd = true
}
}
step(config = {}) {
// #ifndef APP-NVUE
this.animation.step(config)
// #endif
// #ifdef APP-NVUE
this.currentStepAnimates[this.next].config = Object.assign({}, this.options, config)
this.currentStepAnimates[this.next].styles.transformOrigin = this.currentStepAnimates[this.next].config.transformOrigin
this.next++
// #endif
return this
}
run(fn) {
// #ifndef APP-NVUE
this.$.animationData = this.animation.export()
this.$.timer = setTimeout(() => {
typeof fn === 'function' && fn()
}, this.$.durationTime)
// #endif
// #ifdef APP-NVUE
this.isEnd = false
this._nvueNextAnimate(this.currentStepAnimates, 0, fn)
this.next = 0
// #endif
}
}
const animateTypes1 = ['matrix', 'matrix3d', 'rotate', 'rotate3d', 'rotateX', 'rotateY', 'rotateZ', 'scale', 'scale3d',
'scaleX', 'scaleY', 'scaleZ', 'skew', 'skewX', 'skewY', 'translate', 'translate3d', 'translateX', 'translateY',
'translateZ'
]
const animateTypes2 = ['opacity', 'backgroundColor']
const animateTypes3 = ['width', 'height', 'left', 'right', 'top', 'bottom']
animateTypes1.concat(animateTypes2, animateTypes3).forEach(type => {
MPAnimation.prototype[type] = function(...args) {
// #ifndef APP-NVUE
this.animation[type](...args)
// #endif
// #ifdef APP-NVUE
this._nvuePushAnimates(type, args)
// #endif
return this
}
})
export function createAnimation(option, _this) {
if(!_this) return
clearTimeout(_this.timer)
return new MPAnimation(option, _this)
}
<template> <template>
<view v-if="isShow" ref="ani" class="uni-transition" :class="[ani.in]" :style="'transform:' +transform+';'+stylesObject" <view v-if="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"><slot></slot></view>
@click="change"> </template>
<slot></slot>
</view> <script>
</template> import { createAnimation } from './createAnimation'
<script> /**
// #ifdef APP-NVUE * Transition 过渡动画
const animation = uni.requireNativePlugin('animation'); * @description 简单过渡动画组件
// #endif * @tutorial https://ext.dcloud.net.cn/plugin?id=985
/** * @property {Boolean} show = [false|true] 控制组件显示或隐藏
* Transition 过渡动画 * @property {Array|String} modeClass = [fade|slide-top|slide-right|slide-bottom|slide-left|zoom-in|zoom-out] 过渡动画类型
* @description 简单过渡动画组件 * @value fade 渐隐渐出过渡
* @tutorial https://ext.dcloud.net.cn/plugin?id=985 * @value slide-top 由上至下过渡
* @property {Boolean} show = [false|true] 控制组件显示或隐藏 * @value slide-right 由右至左过渡
* @property {Array} modeClass = [fade|slide-top|slide-right|slide-bottom|slide-left|zoom-in|zoom-out] 过渡动画类型 * @value slide-bottom 由下至上过渡
* @value fade 渐隐渐出过渡 * @value slide-left 由左至右过渡
* @value slide-top 由上至下过渡 * @value zoom-in 由小到大过渡
* @value slide-right 由右至左过渡 * @value zoom-out 由大到小过渡
* @value slide-bottom 由下至上过渡 * @property {Number} duration 过渡动画持续时间
* @value slide-left 由左至右过渡 * @property {Object} styles 组件样式,同 css 样式,注意带’-‘连接符的属性需要使用小驼峰写法如:`backgroundColor:red`
* @value zoom-in 由小到大过渡 */
* @value zoom-out 由大到小过渡 export default {
* @property {Number} duration 过渡动画持续时间 name: 'uniTransition',
* @property {Object} styles 组件样式,同 css 样式,注意带’-‘连接符的属性需要使用小驼峰写法如:`backgroundColor:red` props: {
*/ show: {
export default { type: Boolean,
name: 'uniTransition', default: false
props: { },
show: { modeClass: {
type: Boolean, type: [Array, String],
default: false default() {
}, return 'fade'
modeClass: { }
type: Array, },
default () { duration: {
return [] type: Number,
} default: 300
}, },
duration: { styles: {
type: Number, type: Object,
default: 300 default() {
}, return {}
styles: { }
type: Object,
default () {
return {}
}
}
}, },
data() { customClass:{
return { type: String,
isShow: false, default: ''
transform: '', }
ani: { in: '', },
active: '' data() {
} return {
}; isShow: false,
}, transform: '',
watch: { opacity: 1,
show: { animationData: {},
handler(newVal) { durationTime: 300,
if (newVal) { config: {}
this.open() }
} else { },
this.close() watch: {
} show: {
}, handler(newVal) {
immediate: true if (newVal) {
} this.open()
}, } else {
computed: { // 避免上来就执行 close,导致动画错乱
stylesObject() { if (this.isShow) {
let styles = { this.close()
...this.styles, }
'transition-duration': this.duration / 1000 + 's' }
} },
let transfrom = '' immediate: true
for (let i in styles) { }
let line = this.toLine(i) },
transfrom += line + ':' + styles[i] + ';' computed: {
} // 生成样式数据
return transfrom stylesObject() {
} let styles = {
}, ...this.styles,
created() { 'transition-duration': this.duration / 1000 + 's'
// this.timer = null }
// this.nextTick = (time = 50) => new Promise(resolve => { let transform = ''
// clearTimeout(this.timer) for (let i in styles) {
// this.timer = setTimeout(resolve, time) let line = this.toLine(i)
// return this.timer transform += line + ':' + styles[i] + ';'
// }); }
}, return transform
methods: { },
change() { // 初始化动画条件
this.$emit('click', { transformStyles() {
detail: this.isShow return 'transform:' + this.transform + ';' + 'opacity:' + this.opacity + ';' + this.stylesObject
}) }
}, },
open() { created() {
clearTimeout(this.timer) // 动画默认配置
this.isShow = true this.config = {
this.transform = '' duration: this.duration,
this.ani.in = '' timingFunction: 'ease',
for (let i in this.getTranfrom(false)) { transformOrigin: '50% 50%',
if (i === 'opacity') { delay: 0
this.ani.in = 'fade-in' }
} else { this.durationTime = this.duration
this.transform += `${this.getTranfrom(false)[i]} ` },
} methods: {
} /**
this.$nextTick(() => { * ref 触发 初始化动画
setTimeout(() => { */
this._animation(true) init(obj = {}) {
}, 50) if (obj.duration) {
}) this.durationTime = obj.duration
}
}, this.animation = createAnimation(Object.assign(this.config, obj))
close(type) { },
clearTimeout(this.timer) /**
this._animation(false) * 点击组件触发回调
}, */
_animation(type) { onClick() {
let styles = this.getTranfrom(type) this.$emit('click', {
// #ifdef APP-NVUE detail: this.isShow
if(!this.$refs['ani']) return })
animation.transition(this.$refs['ani'].ref, { },
styles, /**
duration: this.duration, //ms * ref 触发 动画分组
timingFunction: 'ease', * @param {Object} obj
needLayout: false, */
delay: 0 //ms step(obj, config = {}) {
}, () => { if (!this.animation) return
if (!type) { for (let i in obj) {
this.isShow = false try {
} if(typeof obj[i] === 'object'){
this.$emit('change', { this.animation[i](...obj[i])
detail: this.isShow }else{
}) this.animation[i](obj[i])
}) }
// #endif } catch (e) {
// #ifndef APP-NVUE console.error(`方法 ${i} 不存在`)
this.transform = '' }
for (let i in styles) { }
if (i === 'opacity') { this.animation.step(config)
this.ani.in = `fade-${type?'out':'in'}` return this
} else { },
this.transform += `${styles[i]} ` /**
} * ref 触发 执行动画
} */
this.timer = setTimeout(() => { run(fn) {
if (!type) { if (!this.animation) return
this.isShow = false this.animation.run(fn)
} },
this.$emit('change', { // 开始过度动画
detail: this.isShow open() {
}) clearTimeout(this.timer)
this.transform = ''
}, this.duration) this.isShow = true
// #endif let { opacity, transform } = this.styleInit(false)
if (typeof opacity !== 'undefined') {
}, this.opacity = opacity
getTranfrom(type) { }
let styles = { this.transform = transform
transform: '' // 确保动态样式已经生效后,执行动画,如果不加 nextTick ,会导致 wx 动画执行异常
} this.$nextTick(() => {
this.modeClass.forEach((mode) => { // TODO 定时器保证动画完全执行,目前有些问题,后面会取消定时器
switch (mode) { this.timer = setTimeout(() => {
case 'fade': this.animation = createAnimation(this.config, this)
styles.opacity = type ? 1 : 0 this.tranfromInit(false).step()
break; this.animation.run()
case 'slide-top': this.$emit('change', {
styles.transform += `translateY(${type?'0':'-100%'}) ` detail: this.isShow
break; })
case 'slide-right': }, 20)
styles.transform += `translateX(${type?'0':'100%'}) ` })
break; },
case 'slide-bottom': // 关闭过度动画
styles.transform += `translateY(${type?'0':'100%'}) ` close(type) {
break; if (!this.animation) return
case 'slide-left': this.tranfromInit(true)
styles.transform += `translateX(${type?'0':'-100%'}) ` .step()
break; .run(() => {
case 'zoom-in': this.isShow = false
styles.transform += `scale(${type?1:0.8}) ` this.animationData = null
break; this.animation = null
case 'zoom-out': let { opacity, transform } = this.styleInit(false)
styles.transform += `scale(${type?1:1.2}) ` this.opacity = opacity || 1
break; this.transform = transform
} this.$emit('change', {
}) detail: this.isShow
return styles })
}, })
_modeClassArr(type) { },
let mode = this.modeClass // 处理动画开始前的默认样式
if (typeof(mode) !== "string") { styleInit(type) {
let modestr = '' let styles = {
mode.forEach((item) => { transform: ''
modestr += (item + '-' + type + ',') }
}) let buildStyle = (type, mode) => {
return modestr.substr(0, modestr.length - 1) if (mode === 'fade') {
} else { styles.opacity = this.animationType(type)[mode]
return mode + '-' + type } else {
} styles.transform += this.animationType(type)[mode] + ' '
}, }
// getEl(el) { }
// console.log(el || el.ref || null); if (typeof this.modeClass === 'string') {
// return el || el.ref || null buildStyle(type, this.modeClass)
// }, } else {
toLine(name) { this.modeClass.forEach(mode => {
return name.replace(/([A-Z])/g, "-$1").toLowerCase(); buildStyle(type, mode)
} })
} }
} return styles
</script> },
// 处理内置组合动画
<style> tranfromInit(type) {
.uni-transition { let buildTranfrom = (type, mode) => {
transition-timing-function: ease; let aniNum = null
transition-duration: 0.3s; if (mode === 'fade') {
transition-property: transform, opacity; aniNum = type ? 0 : 1
z-index: 998; } else {
} aniNum = type ? '-100%' : '0'
if (mode === 'zoom-in') {
.fade-in { aniNum = type ? 0.8 : 1
opacity: 0; }
} if (mode === 'zoom-out') {
aniNum = type ? 1.2 : 1
.fade-active { }
opacity: 1; if (mode === 'slide-right') {
} aniNum = type ? '100%' : '0'
}
.slide-top-in { if (mode === 'slide-bottom') {
/* transition-property: transform, opacity; */ aniNum = type ? '100%' : '0'
transform: translateY(-100%); }
} }
this.animation[this.animationMode()[mode]](aniNum)
.slide-top-active { }
transform: translateY(0); if (typeof this.modeClass === 'string') {
/* opacity: 1; */ buildTranfrom(type, this.modeClass)
} } else {
this.modeClass.forEach(mode => {
.slide-right-in { buildTranfrom(type, mode)
transform: translateX(100%); })
} }
.slide-right-active { return this.animation
transform: translateX(0); },
} animationType(type) {
return {
.slide-bottom-in { fade: type ? 1 : 0,
transform: translateY(100%); 'slide-top': `translateY(${type ? '0' : '-100%'})`,
} 'slide-right': `translateX(${type ? '0' : '100%'})`,
'slide-bottom': `translateY(${type ? '0' : '100%'})`,
.slide-bottom-active { 'slide-left': `translateX(${type ? '0' : '-100%'})`,
transform: translateY(0); 'zoom-in': `scaleX(${type ? 1 : 0.8}) scaleY(${type ? 1 : 0.8})`,
} 'zoom-out': `scaleX(${type ? 1 : 1.2}) scaleY(${type ? 1 : 1.2})`
}
.slide-left-in { },
transform: translateX(-100%); // 内置动画类型与实际动画对应字典
} animationMode() {
return {
.slide-left-active { fade: 'opacity',
transform: translateX(0); 'slide-top': 'translateY',
opacity: 1; 'slide-right': 'translateX',
} 'slide-bottom': 'translateY',
'slide-left': 'translateX',
.zoom-in-in { 'zoom-in': 'scale',
transform: scale(0.8); 'zoom-out': 'scale'
} }
},
.zoom-out-active { // 驼峰转中横线
transform: scale(1); toLine(name) {
} return name.replace(/([A-Z])/g, '-$1').toLowerCase()
}
.zoom-out-in { }
transform: scale(1.2); }
} </script>
</style>
<style></style>
{ {
"id": "uni-transition", "id": "uni-transition",
"displayName": "Transition 过渡动画", "displayName": "Transition 过渡动画",
"version": "1.0.2", "version": "1.1.0",
"description": "元素的简单过渡动画", "description": "元素的简单过渡动画",
"keywords": [ "keywords": [
"动画", "动画",
......
## Transition 过渡动画 ## Transition 过渡动画
> **组件名:uni-transition**
> 代码块: `uTransition` > 代码块: `uTransition`
元素的简单过渡动画,组件名:`uni-transition` 元素过渡动画
> **注意事项**
> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。
> - 组件需要依赖 `sass` 插件 ,请自行手动安装
> - rotate 旋转动画不需要填写 deg 单位,在小程序上填写单位动画不会执行
> - NVUE 下修改宽高动画,不能定位到中心点
> - 百度小程序下修改宽高 ,可能会影响其他动画,需注意
> - nvue 不支持 costom-class , 请使用 styles
> - 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
### 安装方式 ### 安装方式
...@@ -19,48 +30,156 @@ ...@@ -19,48 +30,156 @@
```html ```html
<template> <template>
<view> <view>
<button type="primary">fade</button> <button type="primary" @click="open">fade</button>
<uni-transition :mode-class="['fade']" :styles="{'width':'100px','height':'100px';'backgroundColor':'red'}" :show="show" @change="change" /> <uni-transition mode-class="fade" :styles="{'width':'100px','height':'100px','backgroundColor':'red'}" :show="show" @change="change" />
</view> </view>
</template> </template>
```
``` javascript
import uniTransition from '@/components/uni-transition/uni-transition.vue' <script>
export default { export default {
components: { data() {
uniTransition return {
show: false,
}
},
onLoad() {},
methods: {
open(mode) {
this.show = !this.show
}, },
data() { change() {
return { console.log('触发动画')
show: false, }
}
}
</script>
```
### 样式覆盖
**注意:`nvue` 不支持 `custom-class` 属性 ,需要使用 `styles` 属性进行兼容**
使用 `custom-class` 属性绑定样式,可以自定义 `uni-transition` 的样式
```html
<template>
<view class="content">
<uni-transition custom-class="custom-transition" mode-class="fade" :duration="0" :show="true" />
</view>
</template>
<style>
/* 常规样式覆盖 */
.content >>> .custom-transition {
width:100px;
height:100px;
background-color:red;
}
</style>
<style lang="scss">
/* 如果使用 scss 需要使用 /deep/ */
.content /deep/ .custom-transition {
width:100px;
height:100px;
background-color:red;
}
</style>
```
如果使用 `styles` 注意带’-‘连接符的属性需要使用小驼峰写法如:`backgroundColor:red`
```html
<template>
<view class="content">
<uni-transition :styles="styles" mode-class="fade" :duration="0" :show="true" />
</view>
</template>
<script>
export default {
data() {
return {
styles:{
'width':'100px',
'height':'100px',
'backgroundColor':'red'
} }
}, }
onLoad() {}, }
methods: { }
open(mode) { </script>
this.show = !this.show ```
### 自定义动画
当内置动画类型不能满足需求的时候 ,可以使用 `step()``run()` 自定义动画,入参以及具体用法参考下方属性说明
`init()` 方法可以覆盖默认配置
```html
<template>
<view>
<button type="primary" @click="run">执行动画</button>
<uni-transition ref="ani" :styles="{'width':'100px','height':'100px','backgroundColor':'red'}" :show="show" />
</view>
</template>
<script>
export default {
data() {
return {
show: true,
}
},
onReady() {
this.$refs.ani.init({
duration: 1000,
timingFunction: 'linear',
transformOrigin: '50% 50%',
delay: 500
})
},
methods: {
run() {
// 同时右平移到 100px,旋转 360 读
this.$refs.ani.step({
translateX: '100px',
rotate: '360'
})
// 上面的动画执行完成后,等待200毫秒平移到 0px,旋转到 0 读
this.$refs.ani.step({
translateX: '0px',
rotate: '0'
}, },
change() { {
console.log('触发动画') timingFunction: 'ease-in',
} duration: 200
})
// 开始执行动画
this.$refs.ani.run(()=>{
console.log('动画支持完毕')
})
} }
} }
}
</script>
``` ```
## API ## API
### Transition Props ### Transition Props
|属性名 |类型 |默认值 |说明 | |属性名 |类型 |默认值 |说明 |
|:-: |:-: |:-: |:-:| |:-: |:-: |:-: |:-:|
|show |Boolean|false |控制组件显示或隐藏, | |show |Boolean|false |控制组件显示或隐藏 |
|modeClass |Array |- |过渡动画类型 | |mode-class |Array/String |- |内置过渡动画类型 |
|duration |Number |300 |过渡动画持续时间 | |custom-class |String |- |自定义类名 |
|styles |Object |- |组件样式,同 css 样式,注意带’-‘连接符的属性需要使用小驼峰写法如:`backgroundColor:red` | |duration |Number |300 |过渡动画持续时间 |
|styles |Object |- |组件样式,同 css 样式,注意带’-‘连接符的属性需要使用小驼峰写法如:`backgroundColor:red` |
#### modeClass 类型说明 #### mode-class 内置过渡动画类型说明
**格式为**`['fade','slide-top']` **格式为**`'fade'` 或者 `['fade','slide-top']`
|属性名 |说明 | |属性名 |说明 |
|:-: |:-: | |:-: |:-: |
...@@ -78,7 +197,211 @@ export default { ...@@ -78,7 +197,211 @@ export default {
### Transition Events ### Transition Events
|事件称名 |说明 |返回值 | |事件 |说明 |返回值 |
|:-: |:-: |:-: | |:-: |:-: |:-: |
|click |点击组件触发 |- | |click |点击组件触发 |- |
|change |过渡动画结束时触发 | e = {detail:true} | |change |过渡动画结束时触发 | e = {detail:true} |
### Transition Methons
|方法名|说明|参数|
|:-:|:-:|:-:|
|init()|手动初始化配置|Function(OBJECT:config)|
|step()|动画队列|Function(OBJECT:type,OBJECT:config)|
|run()|执行动画|Function(FUNCTION:callback) |
### init(OBJECT:config)
**通过 ref 调用方法**
手动设置动画配置,需要在页面渲染完毕后调用
```javascript
this.$refs.ani.init({
duration: 1000,
timingFunction:'ease',
delay:500,
transformOrigin:'left center'
})
```
### step(OBJECT:type,OBJECT:config) 动画队列
**通过 ref 调用方法**
调用 `step()` 来表示一组动画完成,`step` 第一个参数可以传入任意多个动画方法,一组动画中的所有动画会同时开始,一组动画完成后才会进行下一组动画。`step` 第二个参数可以传入一个跟 `uni.createAnimation()` 一样的配置参数用于指定当前组动画的配置。
Tips
- 第一个参数支持的动画参考下面的 `支持的动画`
- 第二个参数参考下面的 `动画配置`,可省略,如果省略继承`init`的配置
```javascript
this.$refs.ani.step({
translateX: '100px'
},{
duration: 1000,
timingFunction:'ease',
delay:500,
transformOrigin:'left center'
})
```
### run(FUNCTION:callback) 执行动画
**通过 ref 调用方法**
在执行 `step()` 后,需要调用 `run()` 来运行动画 ,否则动画会一直等待
`run()` 方法可以传入一个 `callback` 函数 ,会在所有动画执行完毕后回调
```javascript
this.$refs.ani.step({
translateX: '100px'
})
this.$refs.ani.run(()=>{
console.log('动画执行完毕')
})
```
### 动画配置
动画配置 , `init()``step()` 第二个参数配置相同 ,如果配置`step() `第二个参数,将会覆盖 `init()` 的配置
|属性名|值|必填|默认值|说明|平台差异|
|:-:|:-:|:-:|:-:|:-:|:-:|
|duration|Number|否|400|动画持续时间,单位ms|-|
|timingFunction|String|否|"linear"|定义动画的效果|-|
|delay|Number|否|0|动画延迟时间,单位 ms|-|
|needLayout|Boolean|否|false |动画执行是否影响布局|仅 nvue 支持|
|transformOrigin|String |否|"center center"|设置 [transform-origin](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-origin)|-|
### timingFunction 属性说明
|值|说明|平台差异|
|:-:|:-:|:-:|
|linear|动画从头到尾的速度是相同的|-|
|ease|动画以低速开始,然后加快,在结束前变慢|-|
|ease-in| 动画以低速开始|-|
|ease-in-out| 动画以低速开始和结束|-|
|ease-out|动画以低速结束|-|
|step-start|动画第一帧就跳至结束状态直到结束|nvue不支持|
|step-end|动画一直保持开始状态,最后一帧跳到结束状态|nvue不支持|
```javascript
// init 配置
this.$refs.ani.init({
duration: 1000,
timingFunction:'ease',
delay:500,
transformOrigin:'left center'
})
// step 配置
this.$refs.ani.step({
translateX: '100px'
},{
duration: 1000,
timingFunction:'ease',
delay:500,
transformOrigin:'left center'
})
```
### 支持的动画
动画方法
如果同一个动画方法有多个值,多个值使用数组分隔
```javascript
this.$refs.ani.step({
width:'100px',
scale: [1.2,0.8],
})
```
**样式:**
|属性名|值|说明|平台差异|
|:-:|:-:|:-:|:-:|
|opacity|value|透明度,参数范围 0~1|-|
|backgroundColor|color|颜色值|-|
|width|length|长度值,如果传入 Number 则默认使用 px,可传入其他自定义单位的长度值|-|
|height|length|长度值,如果传入 Number 则默认使用 px,可传入其他自定义单位的长度值|-|
|top|length|长度值,如果传入 Number 则默认使用 px,可传入其他自定义单位的长度值|nvue 不支持|
|left|length|长度值,如果传入 Number 则默认使用 px,可传入其他自定义单位的长度值|nvue 不支持|
|bottom|length|长度值,如果传入 Number 则默认使用 px,可传入其他自定义单位的长度值|nvue 不支持|
|right|length|长度值,如果传入 Number 则默认使用 px,可传入其他自定义单位的长度值|nvue 不支持|
```javascript
this.$refs.ani.step({
opacity: 1,
backgroundColor: '#ff5a5f',
widht:'100px',
height:'50rpx',
})
```
**旋转:**
旋转属性的值不需要填写单位
|属性名|值|说明|平台差异 |
|:-:|:-:|:-:|:-:|
|rotate|deg|deg的范围-180~180,从原点顺时针旋转一个deg角度 |-|
|rotateX|deg|deg的范围-180~180,在X轴旋转一个deg角度 |-|
|rotateY|deg|deg的范围-180~180,在Y轴旋转一个deg角度 |-|
|rotateZ|deg|deg的范围-180~180,在Z轴旋转一个deg角度 |nvue不支持|
|rotate3d|x,y,z,deg| 同 [transform-function rotate3d](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/rotate3d()) |nvue不支持|
```javascript
this.$refs.ani.step({
rotateX: 45,
rotateY: 45
})
```
**缩放:**
|属性名|值|说明|平台差异|
|:-:|:-:|:-: |:-:|
|scale|sx,[sy]|一个参数时,表示在X轴、Y轴同时缩放sx倍数;两个参数时表示在X轴缩放sx倍数,在Y轴缩放sy倍数|-|
|scaleX|sx|在X轴缩放sx倍数|-|
|scaleY|sy|在Y轴缩放sy倍数|-|
|scaleZ|sz|在Z轴缩放sy倍数|nvue不支持|
|scale3d|sx,sy,sz|在X轴缩放sx倍数,在Y轴缩放sy倍数,在Z轴缩放sz倍数|nvue不支持|
```javascript
this.$refs.ani.step({
scale: [1.2,0.8]
})
```
**偏移:**
|属性名|值|说明|平台差异|
|:-:|:-:|:-:|:-:|
|translate|tx,[ty]|一个参数时,表示在X轴偏移tx,单位px;两个参数时,表示在X轴偏移tx,在Y轴偏移ty,单位px。|-|
|translateX|tx| 在X轴偏移tx,单位px|-|
|translateY|ty| 在Y轴偏移tx,单位px|-|
|translateZ|tz| 在Z轴偏移tx,单位px|nvue不支持|
|translate3d|tx,ty,tz| 在X轴偏移tx,在Y轴偏移ty,在Z轴偏移tz,单位px|nvue不支持|
```javascript
this.$refs.ani.step({
translateX: '100px'
})
```
**倾斜:**
|属性名|值|说明|平台差异|
|:-:|:-:|:-:|:-:|
|skew|ax,[ay]|参数范围-180~180;一个参数时,Y轴坐标不变,X轴坐标延顺时针倾斜ax度;两个参数时,分别在X轴倾斜ax度,在Y轴倾斜ay度|nvue不支持|
|skewX|ax| 参数范围-180~180;Y轴坐标不变,X轴坐标延顺时针倾斜ax度|nvue不支持|
|skewY|ay| 参数范围-180~180;X轴坐标不变,Y轴坐标延顺时针倾斜ay度|nvue不支持|
**矩阵变形:**
|属性名|值|说明|平台差异|
|:-:|:-:|:-:|:-:|
|matrix|(a,b,c,d,tx,ty)|同 [transform-function matrix](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/matrix())|nvue不支持|
|matrix3d|同 [transform-function matrix3d](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/matrix3d())|nvue不支持|
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册