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

1.新增获取邀请码接口getUserInviteCode 2.在邀请用户下载应用页面,自动设置被邀请用户的剪切板为邀请者的code(仅支持安卓端)...

1.新增获取邀请码接口getUserInviteCode 2.在邀请用户下载应用页面,自动设置被邀请用户的剪切板为邀请者的code(仅支持安卓端) 3.在注册或登陆并注册请求时自动添加剪切板中的请求参数 4.统一接口名称为驼峰法
上级 fd526ab7
...@@ -42,8 +42,10 @@ uni-starter + uniCloud admin,提供了用户端和管理端的基础模板, ...@@ -42,8 +42,10 @@ uni-starter + uniCloud admin,提供了用户端和管理端的基础模板,
### 功能模块介绍 ### 功能模块介绍
#### 1. 拦截器与路由守卫 #### 1. 拦截器与路由守卫
第三方路由拦截库,一般带有window对象等问题并不适合在uni-app中使用;另外传统路由拦截方式都是给原生方法做嵌套,首先这种写法并不优雅,另外不同项目的作者可能会不同的第三方路由库,这非常不利于生态的建设。你可能从插件市场拉下来一个项目有太多的学习成本,与你自有项目结合有大量差异需要去磨平。为此`uni-starter`基于`uni.addInterceptor`拦截器。 第三方路由拦截库,一般带有window对象等问题,并不适合在uni-app中使用;另外传统路由拦截方式都是给原生方法做嵌套,首先这种写法并不优雅,也不支持ide的代码提示。
拦截器顾名思义,是在框架方法执行的各个环节(包含:拦截前触发、成功回调拦截、失败回调拦截、完成回调拦截)插入逻辑,篡改参数或终止运行。 另外不同项目的作者可能会不同的第三方路由库,这非常不利于生态的建设。你可能从插件市场拉下来一个项目有太多的学习成本,与你自有项目结合有大量差异需要去磨平。
为此`uni-starter`基于`uni.addInterceptor`(拦截器)实现路由守卫。
拦截器顾名思义,是在框架方法执行的各个环节(包含:拦截前触发、成功回调拦截、失败回调拦截、完成回调拦截)插入逻辑,篡改数据或终止运行。
``` ```
const {"router": {needLogin,login} } = uniStarterConfig //需要登录的页面 const {"router": {needLogin,login} } = uniStarterConfig //需要登录的页面
let list = ["navigateTo", "redirectTo", "reLaunch", "switchTab"]; let list = ["navigateTo", "redirectTo", "reLaunch", "switchTab"];
...@@ -85,7 +87,6 @@ uni-starter + uniCloud admin,提供了用户端和管理端的基础模板, ...@@ -85,7 +87,6 @@ uni-starter + uniCloud admin,提供了用户端和管理端的基础模板,
``` ```
#### 2.登录模块 #### 2.登录模块
- uni-start集成的登录方式有:验证码登录(smsCode)、读取手机SIM卡一键登录(univerify)、账号密码登录(username)、微信登录(weixin)、苹果登录(apple)
- 使用方式:在 `uni-starter.config.js`->`router`->`login`下完全列举你需要的登录方式。这里支持用[条件编译](https://uniapp.dcloud.io/platform?id=%e6%9d%a1%e4%bb%b6%e7%bc%96%e8%af%91)因此你可以配置在不同平台下拥有的登录方式。 - 使用方式:在 `uni-starter.config.js`->`router`->`login`下完全列举你需要的登录方式。这里支持用[条件编译](https://uniapp.dcloud.io/platform?id=%e6%9d%a1%e4%bb%b6%e7%bc%96%e8%af%91)因此你可以配置在不同平台下拥有的登录方式。
- 优先级策略: - 优先级策略:
如果:配置内容为:["username","smsCode"],用户执行如下代码: 如果:配置内容为:["username","smsCode"],用户执行如下代码:
...@@ -94,10 +95,10 @@ uni-starter + uniCloud admin,提供了用户端和管理端的基础模板, ...@@ -94,10 +95,10 @@ uni-starter + uniCloud admin,提供了用户端和管理端的基础模板,
url: "/pages/ucenter/login-page/index/index" url: "/pages/ucenter/login-page/index/index"
}) })
``` ```
访问登录页面,但会被拦截器自动切换到“配置的第0项的登录方式对应的页面”,即账户登录方式页面,路径:`/pages/ucenter/login-page/pwd-login/pwd-login` 访问登录页面,会被拦截器自动切换到"配置的第0项(这里是`username`)的登录方式对应的页面”,即`账户登录`方式页面,路径:`/pages/ucenter/login-page/pwd-login/pwd-login`
- uni-start集成的登录方式有:验证码登录(smsCode)、读取手机SIM卡一键登录(univerify)、账号密码登录(username)、微信登录(weixin)、苹果登录(apple)
- 生效策略:未列举到的或设备环境不支持的登录方式将被隐藏。 - 生效策略:登陆方式有如上5种,你希望有几种登陆方式就在配置中列举几种。有的登陆方式可能因为设备环境问题而不被支持;比如你正确地配置了微信登陆,但是用户的手机并没有安装微信,这样微信登陆功能就无法使用,并且如果出现这种情况你的app会被iOS的App Store拒绝上架。所以在这里,我们的生效策略在检测:你是否有列举到某个配置项为前提的情况下,增加了检测当前环境是否支持,如果不支持会自动隐藏。
- 配置: - 其他配置:
+ 服务端:uni-starter服务端使用[uni-config-center](https://ext.dcloud.net.cn/plugin?id=4425)统一管理这些配置,文件路径`/uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center/uni-id/config.json`详情下文[目录结构](#id=catalogue)[uni-id配置说明](https://uniapp.dcloud.io/uniCloud/uni-id?id=configjson%e7%9a%84%e8%af%b4%e6%98%8e) + 服务端:uni-starter服务端使用[uni-config-center](https://ext.dcloud.net.cn/plugin?id=4425)统一管理这些配置,文件路径`/uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center/uni-id/config.json`详情下文[目录结构](#id=catalogue)[uni-id配置说明](https://uniapp.dcloud.io/uniCloud/uni-id?id=configjson%e7%9a%84%e8%af%b4%e6%98%8e)
+ 应用模块:`manifest.json` App模块配置 --> OAuth(登录鉴权)--> 勾选并配置你所需要的模块 + 应用模块:`manifest.json` App模块配置 --> OAuth(登录鉴权)--> 勾选并配置你所需要的模块
- 短信登陆: - 短信登陆:
...@@ -186,7 +187,7 @@ uni-starter + uniCloud admin,提供了用户端和管理端的基础模板, ...@@ -186,7 +187,7 @@ uni-starter + uniCloud admin,提供了用户端和管理端的基础模板,
2. 配置后提交云端打包后生效。理论上绝大部分和`manifest.json`生效相关的配置均需要提交云打包后生效 2. 配置后提交云端打包后生效。理论上绝大部分和`manifest.json`生效相关的配置均需要提交云打包后生效
#### 10.拦截器改造后的uniCloud #### 10.拦截器改造后的uniCloud
1. Debug,调试期间开启Debug。接口一旦file就会弹出真实错误信息。否则将弹出,系统错误请稍后再试! 1. Debug,调试期间开启Debug。接口一旦fail就会弹出真实错误信息。否则将弹出,系统错误请稍后再试!
``` ```
if(Debug){ if(Debug){
console.log(e); console.log(e);
...@@ -409,3 +410,4 @@ uni-starter ...@@ -409,3 +410,4 @@ uni-starter
### 第三方插件(感谢插件作者,排名不分前后): ### 第三方插件(感谢插件作者,排名不分前后):
1. 图片裁剪 [limeClipper](https://ext.dcloud.net.cn/plugin?id=3594) @作者: 陌上华年 1. 图片裁剪 [limeClipper](https://ext.dcloud.net.cn/plugin?id=3594) @作者: 陌上华年
2. 二维码生成 [Sansnn-uQRCode](https://ext.dcloud.net.cn/plugin?id=1287) @作者: 3snn 2. 二维码生成 [Sansnn-uQRCode](https://ext.dcloud.net.cn/plugin?id=1287) @作者: 3snn
3. clipboard.js [clipboard](https://clipboardjs.com/)
\ No newline at end of file
## 1.0.19(2021-06-17)
1.新增获取邀请码接口getUserInviteCode 2.在邀请用户下载应用页面,自动设置被邀请用户的剪切板为邀请者的code(仅支持安卓端) 3.在注册或登陆并注册请求时自动添加剪切板中的请求参数 4.统一接口名称为驼峰法
## 1.0.18(2021-06-15)
修复,APP端有安装微信客户端但未显示微信登陆快捷键的问题
## 1.0.17(2021-06-09) ## 1.0.17(2021-06-09)
修复,非APP端deviceInfo为空引起的登陆失败问题 修复,非APP端deviceInfo为空引起的登陆失败问题
## 1.0.16(2021-06-08) ## 1.0.16(2021-06-08)
......
...@@ -9,11 +9,15 @@ import interceptorChooseImage from '@/uni_modules/json-interceptor-chooseImage/j ...@@ -9,11 +9,15 @@ import interceptorChooseImage from '@/uni_modules/json-interceptor-chooseImage/j
const db = uniCloud.database() const db = uniCloud.database()
export default function() { export default function() {
// #ifndef H5
setTimeout(()=>{ setTimeout(()=>{
// #endif
// uniStarterConfig挂载到getApp(). // uniStarterConfig挂载到getApp().
const app = getApp({allowDefault: true}) const app = getApp({allowDefault: true})
app.globalData.config = uniStarterConfig; app.globalData.config = uniStarterConfig;
// #ifndef H5
},30) },30)
// #endif
// 初始化appVersion(仅app生效) // 初始化appVersion(仅app生效)
initAppVersion(); initAppVersion();
...@@ -108,6 +112,31 @@ export default function() { ...@@ -108,6 +112,31 @@ export default function() {
} }
console.log("重新登陆/注册,获取设备id",deviceInfo); console.log("重新登陆/注册,获取设备id",deviceInfo);
option.data.deviceInfo = deviceInfo option.data.deviceInfo = deviceInfo
// #ifndef H5
//注册可能不仅仅走register接口,还有登陆并注册的接口
option.data.inviteCode = await new Promise((callBack)=>{
uni.getClipboardData({
success: function (res) {
if(res.data.slice(0,18) =='uniInvitationCode:'){
let uniInvitationCode = res.data.slice(18,38)
console.log('当前用户是其他用户推荐下载的,推荐者的code是:'+uniInvitationCode);
// uni.showModal({
// content: '当前用户是其他用户推荐下载的,推荐者的code是:'+uniInvitationCode,
// showCancel: false
// });
callBack(uniInvitationCode)
//当前用户是其他用户推荐下载的。这里登记他的推荐者id 为当前用户的myInviteCode。判断如果是注册
}else{
callBack(false)
}
},
fail() {
callBack(false)
}
});
})
// #endif
} }
// #endif // #endif
console.log(JSON.stringify(option)); console.log(JSON.stringify(option));
...@@ -174,10 +203,14 @@ export default function() { ...@@ -174,10 +203,14 @@ export default function() {
console.log(e.result.code); console.log(e.result.code);
switch (e.result.code){ switch (e.result.code){
case 403: case 403:
uni.showModal({ uni.navigateTo({
content: '未登陆,跳登陆', url: "/pages/ucenter/login-page/index/index"
showCancel: false })
}); break;
case 30203:
uni.navigateTo({
url: "/pages/ucenter/login-page/index/index"
})
break; break;
case 50101: case 50101:
uni.showToast({ uni.showToast({
...@@ -234,6 +267,13 @@ export default function() { ...@@ -234,6 +267,13 @@ export default function() {
}, },
fail(err) { // 失败回调拦截 fail(err) { // 失败回调拦截
console.log(err); console.log(err);
if(Debug){
console.log(err);
uni.showModal({
content: JSON.stringify(err),
showCancel: false
});
}
}, },
}) })
}) })
......
<template>
<view v-if="pass">
<slot></slot>
</view>
</template>
<script>
export default {
name:"uni-id-show",
props: {
isLogin:{
type: Boolean,
default(){
return false
}
},
role: {
type: [Array,String],
default(){
return "ALL"
}
},
permission: {
type: [Array,String],
default(){
return "ALL"
}
}
},
data() {
return {
pass: true
}
},
created() {
this.check()
},
methods:{
check(){
let {permission,role,tokenExpired,uid} = uniCloud.getCurrentUserInfo()
console.log(permission,role,tokenExpired,uid);
let pass = true
//1.是否需要登陆
if(this.isLogin){
pass = uid != null
}
//2.角色要求
if(this.role != "ALL"){
pass = role.some(item => this.role.includes(item));
}
//3.权限要求
if(this.permission != "ALL"){
pass = permission.some(item => this.permission.includes(item));
}
this.pass = pass
}
}
}
</script>
\ No newline at end of file
...@@ -121,11 +121,11 @@ ...@@ -121,11 +121,11 @@
}) => { }) => {
if (this.config[id].isChecked && this.loginConfig.includes(id)) { if (this.config[id].isChecked && this.loginConfig.includes(id)) {
if (id == 'weixin') { if (id == 'weixin') {
if (~plus.runtime.isApplicationExist({ if (!plus.runtime.isApplicationExist({
pname: 'com.tencent.mm', pname: 'com.tencent.mm',
action: 'weixin://' action: 'weixin://'
})) { })) {
// console.log("微信应用未安装"); console.log("微信应用未安装");
return return
} }
} }
...@@ -297,12 +297,12 @@ ...@@ -297,12 +297,12 @@
console.log({ console.log({
params, params,
type type
}); });
let action = 'loginBy'+ type.trim().toLowerCase().replace(type[0], type[0].toUpperCase())
uniCloud.callFunction({ uniCloud.callFunction({
name: 'uni-id-cf', name: 'uni-id-cf',
data: { data: {
action: 'login_by_' + type, action,params
params
}, },
success: ({ success: ({
result result
...@@ -321,7 +321,7 @@ ...@@ -321,7 +321,7 @@
complete: () => { complete: () => {
uni.hideLoading() uni.hideLoading()
} }
}) })
}, },
async getUserInfo(e) { async getUserInfo(e) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
......
...@@ -32,8 +32,6 @@ ...@@ -32,8 +32,6 @@
}, },
"Share": { "Share": {
}, },
"Push": {
},
"OAuth": { "OAuth": {
}, },
"FaceID": { "FaceID": {
...@@ -70,8 +68,6 @@ ...@@ -70,8 +68,6 @@
}, },
"sdkConfigs": { "sdkConfigs": {
"oauth": { "oauth": {
"univerify": {
},
"apple": { "apple": {
}, },
"weixin": { "weixin": {
......
{ {
"id": "uni-starter", "id": "uni-starter",
"displayName": "uni-starter", "displayName": "uni-starter",
"version": "1.0.17", "version": "1.0.19",
"description": "云端一体应用快速开发模版", "description": "云端一体应用快速开发基本项目模版",
"keywords": [ "keywords": [
"uni-starter", "uni-starter",
"login", "login",
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
], ],
"repository": "https://codechina.csdn.net/dcloud/uni-starter.git", "repository": "https://codechina.csdn.net/dcloud/uni-starter.git",
"engines": { "engines": {
"HBuilderX": "^3.1.17" "HBuilderX": "^3.1.18"
}, },
"dcloudext": { "dcloudext": {
"category": [ "category": [
...@@ -77,4 +77,4 @@ ...@@ -77,4 +77,4 @@
} }
} }
} }
} }
\ No newline at end of file
{ {
"pages": [ "pages": [{
{
"path": "pages/list/list", "path": "pages/list/list",
"style": { "style": {
"navigationStyle":"custom", "navigationStyle": "custom",
"enablePullDownRefresh": true "enablePullDownRefresh": true
} }
}, },
...@@ -70,7 +69,8 @@ ...@@ -70,7 +69,8 @@
"style": { "style": {
"navigationBarTitleText": "关于" "navigationBarTitleText": "关于"
// #ifdef APP-PLUS // #ifdef APP-PLUS
,"app-plus": { ,
"app-plus": {
"titleNView": { "titleNView": {
"buttons": [{ "buttons": [{
"type": "share" "type": "share"
...@@ -136,7 +136,7 @@ ...@@ -136,7 +136,7 @@
"navigationBarTitleText": "" "navigationBarTitleText": ""
} }
},{ }, {
"path": "pages/common/webview/webview", "path": "pages/common/webview/webview",
"style": { "style": {
"navigationBarTitleText": "", "navigationBarTitleText": "",
...@@ -156,6 +156,13 @@ ...@@ -156,6 +156,13 @@
"navigationBarTitleText": "阅读记录", "navigationBarTitleText": "阅读记录",
"enablePullDownRefresh": true "enablePullDownRefresh": true
} }
}, {
"path": "pages/ucenter/invite/invite",
"style": {
"navigationStyle":"custom",
"enablePullDownRefresh": false
}
} }
], ],
"globalStyle": { "globalStyle": {
...@@ -169,9 +176,9 @@ ...@@ -169,9 +176,9 @@
"list": [{ "list": [{
"path": "pages/list/list" "path": "pages/list/list"
}, },
{ {
"path": "pages/ucenter/login-page/index/index" "path": "pages/ucenter/login-page/index/index"
},{ }, {
"path": "pages/ucenter/userinfo/userinfo" "path": "pages/ucenter/userinfo/userinfo"
}, },
{ {
...@@ -202,4 +209,4 @@ ...@@ -202,4 +209,4 @@
"text": "我的" "text": "我的"
}] }]
} }
} }
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<!-- #ifndef H5 --> <!-- #ifndef H5 -->
<statusBar></statusBar> <statusBar></statusBar>
<!-- #endif --> <!-- #endif -->
<!-- 搜索功能 --> <!-- 搜索功能 -->
<uni-search-bar @click="searchClick" class="uni-search-box" v-model="keyword" ref="searchBar" radius="100" <uni-search-bar @click="searchClick" class="uni-search-box" v-model="keyword" ref="searchBar" radius="100"
cancelButton="none" disabled /> cancelButton="none" disabled />
<unicloud-db ref='udb' v-slot:default="{data,pagination,hasMore, loading, error, options}" @error="onqueryerror" <unicloud-db ref='udb' v-slot:default="{data,pagination,hasMore, loading, error, options}" @error="onqueryerror"
...@@ -199,4 +199,4 @@ ...@@ -199,4 +199,4 @@
.f1 { .f1 {
flex: 1; flex: 1;
} }
</style> </style>
\ No newline at end of file
<template>
<view>
<web-view :src="url"></web-view>
</view>
</template>
<script>
export default {
created() {
document.getElementById("openApp").style.display = 'none'
document.getElementsByTagName("body")[0].style = ""
},
onLoad({
code
}) {
this.code = code
},
onReady() {
var IframeOnClick = {
resolution: 200,
iframes: [],
interval: null,
Iframe: function() {
this.element = arguments[0];
this.cb = arguments[1];
this.hasTracked = false;
},
track: function(element, cb) {
this.iframes.push(new this.Iframe(element, cb));
if (!this.interval) {
var _this = this;
this.interval = setInterval(function() { _this.checkClick(); }, this.resolution);
}
},
checkClick: function() {
if (document.activeElement) {
var activeElement = document.activeElement;
for (var i in this.iframes) {
if (activeElement === this.iframes[i].element) { // user is in this Iframe
if (this.iframes[i].hasTracked == false) {
this.iframes[i].cb.apply(window, []);
this.iframes[i].hasTracked = true;
}
} else {
this.iframes[i].hasTracked = false;
}
}
}
}
};
IframeOnClick.track(document.getElementsByTagName("iframe")[0], ()=>{
this.copy()
});
},
computed: {
url() {
return getApp().globalData.config.about.download
}
},
data() {
return {
code: ""
}
},
methods: {
copy() {
console.log('copy');
if(!this.code){
return false
}
uni.setClipboardData({
data: 'uniInvitationCode:' + this.code,
success: () => {
uni.showModal({
content: '成功在用户剪切板中存储,邀请人code:'+this.code,
showCancel: false
});
},
fail: () => {
uni.showModal({
content: '失败,未能。在用户剪切板中存储,邀请人code',
showCancel: false
});
}
})
uni.hideToast()
/* 以下临时解决h5端样式和键盘弹出端错误解决方案,后续会直接内置*/
document.getElementById("#clipboard").style.top = '-999px';
uni.hideKeyboard()
}
}
}
</script>
<style>
</style>
...@@ -42,17 +42,6 @@ ...@@ -42,17 +42,6 @@
}, },
methods: { methods: {
submit(){ //完成并提交 submit(){ //完成并提交
// this.-request('uni-id-cf/loginBySms',
// {
// "mobile":this.phone,
// "code":this.code
// },
// e=>{
// console.log(e);
// this.loginSuccess(e)
// },
// {showLoading:true})
uniCloud.callFunction({ uniCloud.callFunction({
name:'uni-id-cf', name:'uni-id-cf',
data:{ data:{
......
...@@ -95,44 +95,8 @@ ...@@ -95,44 +95,8 @@
} }
} }
}) })
// this.-request('uni-id-cf/login', {
// "username": this.username,
// "password": this.password,
// "captcha":this.captcha
// }, result => {
// console.log(result);
// if (result.code === 0) {
// this.loginSuccess(result)
// } else {
// if (result.needCaptcha) {
// uni.showToast({
// title: result.msg,
// icon: 'none'
// });
// this.createCaptcha()
// }else{
// uni.showModal({
// title: '错误',
// content: result.msg,
// showCancel: false,
// confirmText: '知道了'
// });
// }
// }
// })
}, },
createCaptcha(){ createCaptcha(){
// this.-request(
// 'uni-id-cf/createCaptcha', {
// scene: "login"
// },
// result => {
// if (result.code === 0) {
// this.captchaBase64 = result.captchaBase64
// }
// })
uniCloud.callFunction({ uniCloud.callFunction({
name:'uni-id-cf', name:'uni-id-cf',
data:{ data:{
......
...@@ -137,20 +137,6 @@ ...@@ -137,20 +137,6 @@
submit() { submit() {
this.$refs.form.submit() this.$refs.form.submit()
.then(res => { .then(res => {
// this.-request('uni-id-cf/resetPwdBySmsCode', {
// "mobile": this.formData.phone,
// "code": this.formData.code,
// "password": this.formData.pwd
// }, result => {
// console.log(result);
// uni.showToast({
// title: result.msg,
// icon: 'none'
// });
// if (result.code === 0) {
// uni.navigateBack()
// }
// })
uniCloud.callFunction({ uniCloud.callFunction({
name:'uni-id-cf', name:'uni-id-cf',
data:{ data:{
......
...@@ -63,12 +63,6 @@ import mixin from '../common/login-page.mixin.js'; ...@@ -63,12 +63,6 @@ import mixin from '../common/login-page.mixin.js';
}) })
}, },
submitForm(params) { submitForm(params) {
// this.-request('uni-id-cf/register',params,result=>{
// console.log(result);
// if(result.code === 0){
// this.loginSuccess(result)
// }
// })
uniCloud.callFunction({ uniCloud.callFunction({
name:'uni-id-cf', name:'uni-id-cf',
data:{ data:{
......
...@@ -14,11 +14,8 @@ ...@@ -14,11 +14,8 @@
</uni-grid> </uni-grid>
<uni-list class="center-list" v-for="(sublist , index) in ucenterList" :key="index"> <uni-list class="center-list" v-for="(sublist , index) in ucenterList" :key="index">
<uni-list-item v-for="(item,i) in sublist" :title="item.title" link :rightText="item.rightText" :key="i" <uni-list-item v-for="(item,i) in sublist" :title="item.title" link :rightText="item.rightText" :key="i"
:clickable="true" :to="item.to" :clickable="true" :to="item.to" @click="ucenterListClick(item)" :show-extra-icon="true"
@click="ucenterListClick(item)" :extraIcon="{type:item.icon,color:'#999'}">
:show-extra-icon="true"
:extraIcon="{type:item.icon,color:'#999'}"
>
<view v-if="item.showBadge" class="item-footer" slot="footer"> <view v-if="item.showBadge" class="item-footer" slot="footer">
<text class="item-footer-text">{{item.rightText}}</text> <text class="item-footer-text">{{item.rightText}}</text>
<view class="item-footer-badge"></view> <view class="item-footer-badge"></view>
...@@ -35,6 +32,7 @@ ...@@ -35,6 +32,7 @@
} from 'vuex'; } from 'vuex';
import checkUpdate from '@/uni_modules/uni-upgrade-center-app/utils/check-update'; import checkUpdate from '@/uni_modules/uni-upgrade-center-app/utils/check-update';
import callCheckVersion from '@/uni_modules/uni-upgrade-center-app/utils/call-check-version'; import callCheckVersion from '@/uni_modules/uni-upgrade-center-app/utils/call-check-version';
import uniShare from 'uni_modules/uni-share/js_sdk/uni-share.js';
const db = uniCloud.database(); const db = uniCloud.database();
const dbCollectionName = 'uni-id-scores'; const dbCollectionName = 'uni-id-scores';
...@@ -65,34 +63,41 @@ ...@@ -65,34 +63,41 @@
{ {
"title": '去评分', "title": '去评分',
"event": 'gotoMarket', "event": 'gotoMarket',
"icon":"hand-thumbsup" "icon": "hand-thumbsup"
}, },
//#endif //#endif
{ {
"title": '阅读过的文章', "title": '阅读过的文章',
"to": '/pages/ucenter/read-news-log/read-news-log', "to": '/pages/ucenter/read-news-log/read-news-log',
"icon":"flag" "icon": "flag"
}, },
{ {
"title": '我的积分', "title": '我的积分',
"to": '', "to": '',
"event": 'getScore', "event": 'getScore',
"icon":"paperplane" "icon": "paperplane"
} }
// #ifndef H5
,{
"title": '分销推荐',
"event": 'share',
"icon": "redo"
}
// #endif
], ],
[{ [{
"title": '问题与反馈', "title": '问题与反馈',
"to": '/uni_modules/uni-feedback/pages/uni-feedback/uni-feedback', "to": '/uni_modules/uni-feedback/pages/uni-feedback/uni-feedback',
"icon":"help" "icon": "help"
}, { }, {
"title": '设置', "title": '设置',
"to": '/pages/ucenter/settings/settings', "to": '/pages/ucenter/settings/settings',
"icon":"gear" "icon": "gear"
}], }],
[{ [{
"title": '关于', "title": '关于',
"to": '/pages/ucenter/about/about', "to": '/pages/ucenter/about/about',
"icon":"info" "icon": "info"
}] }]
] ]
} }
...@@ -103,7 +108,7 @@ ...@@ -103,7 +108,7 @@
title: '检查更新', title: '检查更新',
rightText: this.appVersion.version + '-' + this.appVersion.versionCode, rightText: this.appVersion.version + '-' + this.appVersion.versionCode,
event: 'checkVersion', event: 'checkVersion',
icon:'loop', icon: 'loop',
showBadge: this.appVersion.hasNew showBadge: this.appVersion.hasNew
}) })
//#endif //#endif
...@@ -206,6 +211,79 @@ ...@@ -206,6 +211,79 @@
}).finally(() => { }).finally(() => {
uni.hideLoading() uni.hideLoading()
}) })
},
async share() {
let {result} = await uniCloud.callFunction({
name: 'uni-id-cf',
data: {
action: 'getUserInviteCode'
}
})
console.log(result);
let myInviteCode = result.myInviteCode || result.userInfo.my_invite_code
console.log(myInviteCode);
let {
appName,
logo,
company,
slogan
} = this.appConfig.about
// #ifdef APP-PLUS
uniShare({
content: { //公共的分享类型(type)、链接(herf)、标题(title)、summary(描述)、imageUrl(缩略图)
type: 0,
href: this.appConfig.h5.url +
`/#/pages/ucenter/invite/invite?code=${myInviteCode}`,
title: appName,
summary: slogan,
imageUrl: logo + '?x-oss-process=image/resize,m_fill,h_100,w_100' //压缩图片解决,在ios端分享图过大导致的图片失效问题
},
menus: [{
"img": "/static/app-plus/sharemenu/wechatfriend.png",
"text": "微信好友",
"share": {
"provider": "weixin",
"scene": "WXSceneSession"
}
},
{
"img": "/static/app-plus/sharemenu/wechatmoments.png",
"text": "微信朋友圈",
"share": {
"provider": "weixin",
"scene": "WXSenceTimeline"
}
},
{
"img": "/static/app-plus/sharemenu/weibo.png",
"text": "微博",
"share": {
"provider": "sinaweibo"
}
},
{
"img": "/static/app-plus/sharemenu/qq.png",
"text": "QQ",
"share": {
"provider": "qq"
}
},
{
"img": "/static/app-plus/sharemenu/copyurl.png",
"text": "复制",
"share": "copyurl"
},
{
"img": "/static/app-plus/sharemenu/more.png",
"text": "更多",
"share": "shareSystem"
}
],
cancelText: "取消分享",
}, e => { //callback
console.log(e);
})
// #endif
} }
} }
} }
...@@ -285,17 +363,17 @@ ...@@ -285,17 +363,17 @@
/*修改边线粗细示例*/ /*修改边线粗细示例*/
/* #ifndef APP-NVUE */ /* #ifndef APP-NVUE */
.center-list /deep/ .uni-list--border:after{ .center-list /deep/ .uni-list--border:after {
-webkit-transform: scaleY(0.2); -webkit-transform: scaleY(0.2);
transform: scaleY(0.2); transform: scaleY(0.2);
margin-left: 80rpx; margin-left: 80rpx;
} }
.center-list /deep/ .uni-list--border-top, .center-list /deep/ .uni-list--border-top,
.center-list /deep/ .uni-list--border-bottom{ .center-list /deep/ .uni-list--border-bottom {
display: none; display: none;
} }
/* #endif */ /* #endif */
.item-footer { .item-footer {
flex-direction: row; flex-direction: row;
...@@ -319,4 +397,4 @@ ...@@ -319,4 +397,4 @@
/* #endif */ /* #endif */
background-color: #DD524D; background-color: #DD524D;
} }
</style> </style>
...@@ -48,20 +48,6 @@ ...@@ -48,20 +48,6 @@
*/ */
submit() { submit() {
console.log(this.formData); console.log(this.formData);
// this.-request('uni-id-cf/bind_mobile_by_sms', {
// "mobile": this.formData.phone,
// "code": this.formData.code
// }, result=> {
// console.log(result);
// this.setUserInfo({"mobile":result.mobile})
// uni.showToast({
// title: result.msg,
// icon: 'none'
// });
// if (result.code === 0) {
// uni.navigateBack()
// }
// })
uniCloud.callFunction({ uniCloud.callFunction({
name:'uni-id-cf', name:'uni-id-cf',
data:{ data:{
......
...@@ -70,25 +70,6 @@ ...@@ -70,25 +70,6 @@
"univerifyStyle": this.univerifyStyle, "univerifyStyle": this.univerifyStyle,
success: async e => { success: async e => {
console.log(e.authResult); console.log(e.authResult);
// this.-request('uni-id-cf/bind_mobile_by_univerify',
// e.authResult,
// 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()
// }
// });
// }
// }
// )
uniCloud.callFunction({ uniCloud.callFunction({
name:'uni-id-cf', name:'uni-id-cf',
data:{ data:{
...@@ -114,7 +95,7 @@ ...@@ -114,7 +95,7 @@
}, },
fail: (err) => { fail: (err) => {
console.log(err); console.log(err);
if(err.code=='30002'){ if(err.code=='30002'||err.code=='30001'){
this.bindMobileBySmsCode() this.bindMobileBySmsCode()
} }
} }
......
...@@ -42,7 +42,7 @@ module.exports = { ...@@ -42,7 +42,7 @@ module.exports = {
//公司名称 //公司名称
"company": "数字天堂(北京)网络技术有限公司", "company": "数字天堂(北京)网络技术有限公司",
//口号 //口号
"slogan": "为开发而生", "slogan": "云端一体应用快速开发模版",
//政策协议 //政策协议
"agreements": [{ "agreements": [{
"title": "用户服务协议", //协议名称 "title": "用户服务协议", //协议名称
......
...@@ -7,37 +7,45 @@ const uniIdConfig = createConfig({ ...@@ -7,37 +7,45 @@ const uniIdConfig = createConfig({
})._config })._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) => {
//UNI_WYQ:这里的uniID换成新的,保证多人访问不会冲突 //UNI_WYQ:这里的uniID换成新的,保证多人访问不会冲突
uniID = uniID.createInstance({context}) uniID = uniID.createInstance({
console.log('event : ' + JSON.stringify(event)) context
/* })
1.event为客户端 uniCloud.callFunction填写的data的值,这里介绍一下其中的属性 console.log('event : ' + JSON.stringify(event))
action:表示要执行的任务名称、比如:登陆login、退出登陆 logout等 /*
params:业务数据内容 1.event为客户端 uniCloud.callFunction填写的data的值,这里介绍一下其中的属性
uniIdToken:系统自动传递的token,数据来源客户端的 uni.getStorageSync('uni_id_token') action:表示要执行的任务名称、比如:登陆login、退出登陆 logout等
*/ params:业务数据内容
const {action,uniIdToken} = event; uniIdToken:系统自动传递的token,数据来源客户端的 uni.getStorageSync('uni_id_token')
const deviceInfo = event.deviceInfo || {}; */
let params = event.params || {}; const {
/* action,
2.在某些操作之前我们要对用户对身份进行校验(也就是要检查用户的token)再将得到的uid写入params.uid uniIdToken
校验用到的方法是uniID.checkToken 详情:https://uniapp.dcloud.io/uniCloud/uni-id?id=checktoken } = event;
const deviceInfo = event.deviceInfo || {};
讨论,我们假设一个这样的场景,代码如下。 let params = event.params || {};
如: /*
uniCloud.callFunction({ 2.在某些操作之前我们要对用户对身份进行校验(也就是要检查用户的token)再将得到的uid写入params.uid
name:"xxx", 校验用到的方法是uniID.checkToken 详情:https://uniapp.dcloud.io/uniCloud/uni-id?id=checktoken
data:{
"params":{ 讨论,我们假设一个这样的场景,代码如下。
uid:"通过某种方式获取来的别人的uid" 如:
} uniCloud.callFunction({
} name:"xxx",
}) data:{
用户就这样轻易地伪造了他人的uid传递给服务端,有一句话叫:前端从来的数据是不可信任的 "params":{
所以这里我们需要将uniID.checkToken返回的uid写入到params.uid uid:"通过某种方式获取来的别人的uid"
*/ }
let noCheckAction = ['register','checkToken','login','logout','sendSmsCode','createCaptcha','verifyCaptcha','refreshCaptcha','inviteLogin','login_by_weixin','login_by_univerify','login_by_apple','loginBySms','resetPwdBySmsCode'] }
})
用户就这样轻易地伪造了他人的uid传递给服务端,有一句话叫:前端从来的数据是不可信任的
所以这里我们需要将uniID.checkToken返回的uid写入到params.uid
*/
let noCheckAction = ['register', 'checkToken', 'login', 'logout', 'sendSmsCode', 'createCaptcha',
'verifyCaptcha', 'refreshCaptcha', 'inviteLogin', 'loginByWeixin', 'loginByUniverify',
'loginByApple', 'loginBySms', 'resetPwdBySmsCode', 'registerAdmin'
]
if (!noCheckAction.includes(action)) { if (!noCheckAction.includes(action)) {
if (!uniIdToken) { if (!uniIdToken) {
return { return {
...@@ -52,14 +60,23 @@ exports.main = async (event, context) => { ...@@ -52,14 +60,23 @@ exports.main = async (event, context) => {
params.uid = payload.uid params.uid = payload.uid
} }
//禁止前台用户传递角色
if (action.slice(0,7) == "loginBy") {
if (params.role) {
return {
code: 403,
msg: '禁止前台用户传递角色'
}
}
}
//3.注册成功后创建新用户的积分表方法 //3.注册成功后创建新用户的积分表方法
async function registerSuccess(uid) { async function registerSuccess(uid) {
//添加当前用户设备信息 //添加当前用户设备信息
await db.collection('uni-id-device').add({ await db.collection('uni-id-device').add({
...deviceInfo, ...deviceInfo,
user_id:uid user_id: uid
}) })
await db.collection('uni-id-scores').add({ await db.collection('uni-id-scores').add({
user_id: uid, user_id: uid,
score: 1, score: 1,
...@@ -67,16 +84,16 @@ exports.main = async (event, context) => { ...@@ -67,16 +84,16 @@ exports.main = async (event, context) => {
balance: 1, balance: 1,
comment: "", comment: "",
create_date: Date.now() create_date: Date.now()
}) })
} }
//4.记录成功登录的日志方法 //4.记录成功登录的日志方法
const loginLog = async (res = {}, type = 'login') => { const loginLog = async (res = {}) => {
const now = Date.now() const now = Date.now()
const uniIdLogCollection = db.collection('uni-id-log') const uniIdLogCollection = db.collection('uni-id-log')
let logData = { let logData = {
deviceId: params.deviceId || context.DEVICEID, deviceId: params.deviceId || context.DEVICEID,
ip: params.ip || context.CLIENTIP, ip: params.ip || context.CLIENTIP,
type, type: res.type,
ua: context.CLIENTUA, ua: context.CLIENTUA,
create_date: now create_date: now
}; };
...@@ -88,19 +105,21 @@ exports.main = async (event, context) => { ...@@ -88,19 +105,21 @@ 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)
}else{ } else {
if(Object.keys(deviceInfo).length){ if (Object.keys(deviceInfo).length) {
//更新当前用户设备信息 //更新当前用户设备信息
await db.collection('uni-id-device').where({user_id:res.uid}).update(deviceInfo) await db.collection('uni-id-device').where({
} user_id: res.uid
}).update(deviceInfo)
}
} }
return await uniIdLogCollection.add(logData) return await uniIdLogCollection.add(logData)
} }
let res = {} let res = {}
switch (action) { //根据action的值执行对应的操作 switch (action) { //根据action的值执行对应的操作
case 'bind_mobile_by_univerify': case 'bind_mobile_by_univerify':
let { let {
appid, apiKey, apiSecret appid, apiKey, apiSecret
...@@ -122,22 +141,20 @@ exports.main = async (event, context) => { ...@@ -122,22 +141,20 @@ exports.main = async (event, context) => {
} }
break; break;
case 'bind_mobile_by_sms': case 'bind_mobile_by_sms':
console.log({ // console.log({
uid: params.uid, // uid: params.uid,
mobile: params.mobile, // mobile: params.mobile,
code: params.code // code: params.code
}); // });
res = await uniID.bindMobile({ res = await uniID.bindMobile({
uid: params.uid, uid: params.uid,
mobile: params.mobile, mobile: params.mobile,
code: params.code code: params.code
}) })
console.log(res); // console.log(res);
break; break;
case 'register': case 'register':
var { var {username, password, nickname,inviteCode} = params
username, password, nickname
} = params
if (/^1\d{10}$/.test(username)) { if (/^1\d{10}$/.test(username)) {
return { return {
code: 401, code: 401,
...@@ -150,34 +167,30 @@ exports.main = async (event, context) => { ...@@ -150,34 +167,30 @@ exports.main = async (event, context) => {
msg: '用户名不能是邮箱' msg: '用户名不能是邮箱'
} }
} }
res = await uniID.register({ res = await uniID.register({username, password, nickname,inviteCode});
username,
password,
nickname
});
if (res.code === 0) { if (res.code === 0) {
await registerSuccess(res.uid) await registerSuccess(res.uid)
} }
break; break;
case 'login': case 'login':
//防止黑客恶意破解登录,连续登录失败一定次数后,需要用户提供验证码 //防止黑客恶意破解登录,连续登录失败一定次数后,需要用户提供验证码
const getNeedCaptcha = async () => { const getNeedCaptcha = async () => {
//当用户最近“2小时内(recordDate)”登录失败达到2次(recordSize)时。要求用户提交验证码 //当用户最近“2小时内(recordDate)”登录失败达到2次(recordSize)时。要求用户提交验证码
const now = Date.now(), const now = Date.now(),
recordDate = 120 * 60 * 1000, recordDate = 120 * 60 * 1000,
recordSize = 2; recordSize = 2;
const uniIdLogCollection = db.collection('uni-id-log') const uniIdLogCollection = db.collection('uni-id-log')
let recentRecord = await uniIdLogCollection.where({ let recentRecord = await uniIdLogCollection.where({
deviceId: params.deviceId || context.DEVICEID, deviceId: params.deviceId || context.DEVICEID,
create_date: dbCmd.gt(now - recordDate), create_date: dbCmd.gt(now - recordDate),
type: 'login' type: 'login'
}) })
.orderBy('create_date', 'desc') .orderBy('create_date', 'desc')
.limit(recordSize) .limit(recordSize)
.get(); .get();
return recentRecord.data.filter(item => item.state === 0).length === recordSize; return recentRecord.data.filter(item => item.state === 0).length === recordSize;
} }
let passed = false; let passed = false;
let needCaptcha = await getNeedCaptcha(); let needCaptcha = await getNeedCaptcha();
console.log('needCaptcha', needCaptcha); console.log('needCaptcha', needCaptcha);
...@@ -193,17 +206,22 @@ exports.main = async (event, context) => { ...@@ -193,17 +206,22 @@ exports.main = async (event, context) => {
res = await uniID.login({ res = await uniID.login({
...params, ...params,
queryField: ['username', 'email', 'mobile'] queryField: ['username', 'email', 'mobile']
}); });
if(res.code === 0){ if (res.code === 0) {
await loginLog(res); await loginLog(res);
} }
needCaptcha = await getNeedCaptcha(); needCaptcha = await getNeedCaptcha();
} }
res.needCaptcha = needCaptcha; res.needCaptcha = needCaptcha;
break; break;
case 'login_by_weixin': case 'loginByWeixin':
res = await uniID.loginByWeixin({...params}); var {
username, password, nickname
} = params
res = await uniID.loginByWeixin({
...params
});
await uniID.updateUser({ await uniID.updateUser({
uid: res.uid, uid: res.uid,
username: "微信用户" username: "微信用户"
...@@ -211,11 +229,11 @@ exports.main = async (event, context) => { ...@@ -211,11 +229,11 @@ exports.main = async (event, context) => {
res.userInfo.username = "微信用户" res.userInfo.username = "微信用户"
await loginLog(res) await loginLog(res)
break; break;
case 'login_by_univerify': case 'loginByUniverify':
res = await uniID.loginByUniverify(params) res = await uniID.loginByUniverify(params)
await loginLog(res) await loginLog(res)
break; break;
case 'login_by_apple': case 'loginByApple':
res = await uniID.loginByApple(params) res = await uniID.loginByApple(params)
await loginLog(res) await loginLog(res)
break; break;
...@@ -258,7 +276,6 @@ exports.main = async (event, context) => { ...@@ -258,7 +276,6 @@ exports.main = async (event, context) => {
type: params.type, type: params.type,
templateId templateId
}) })
await loginLog(res)
break; break;
case 'loginBySms': case 'loginBySms':
if (!params.code) { if (!params.code) {
...@@ -302,7 +319,7 @@ exports.main = async (event, context) => { ...@@ -302,7 +319,7 @@ exports.main = async (event, context) => {
} }
} }
let loginBySmsRes = await uniID.loginBySms(params) let loginBySmsRes = await uniID.loginBySms(params)
console.log(loginBySmsRes); // console.log(loginBySmsRes);
if (loginBySmsRes.code === 0) { if (loginBySmsRes.code === 0) {
res = await uniID.resetPwd({ res = await uniID.resetPwd({
password: params.password, password: params.password,
...@@ -337,6 +354,19 @@ exports.main = async (event, context) => { ...@@ -337,6 +354,19 @@ exports.main = async (event, context) => {
case 'refreshCaptcha': case 'refreshCaptcha':
res = await uniCaptcha.refresh(params) res = await uniCaptcha.refresh(params)
break; break;
case 'getUserInviteCode':
res = await uniID.getUserInfo({
uid: params.uid,
field: ['my_invite_code']
})
if (!res.userInfo.my_invite_code) {
res = await uniID.setUserInviteCode({
uid: params.uid
})
}
break;
// ----------- admin api -----------
case 'registerAdmin': case 'registerAdmin':
var { var {
username, password username, password
...@@ -352,12 +382,39 @@ exports.main = async (event, context) => { ...@@ -352,12 +382,39 @@ exports.main = async (event, context) => {
message: '超级管理员已存在,请登录...' message: '超级管理员已存在,请登录...'
} }
} }
return this.ctx.uniID.register({ return uniID.register({
username, username,
password, password,
role: ["admin"] role: ["admin"]
}) })
break; break;
case 'registerUser':
const {
userInfo
} = await uniID.getUserInfo({
uid: params.uid,
})
if (userInfo.role.indexOf('admin') === -1 && params.role.indexOf('admin') > -1) {
res = {
code: 403,
message: '非法访问, 无权限注册超级管理员',
}
} else {
res = await uniID.register({
...params
})
if (res.code === 0) {
delete res.token
delete res.tokenExpired
}
}
break;
case 'getCurrentUserInfo':
res = uniID.getUserInfo({
uid: params.uid,
...params
})
break;
default: default:
res = { res = {
code: 403, code: 403,
......
// 本文件中的json内容将在云函数【运行】时作为参数传给云函数。 // 本文件中的json内容将在云函数【运行】时作为参数传给云函数。
// 配置教程参考:https://uniapp.dcloud.net.cn/uniCloud/quickstart?id=runparam // 配置教程参考:https://uniapp.dcloud.net.cn/uniCloud/quickstart?id=runparam
{ {
"action": "login_by_weixin", "action": "getUserInviteCode",
"params": { "params": {
"code": "093tK5Ga1X1D6B0MSAHa13uRH04tK5Gs" "code": "093tK5Ga1X1D6B0MSAHa13uRH04tK5Gs"
} },
"uniInvitationCode":"CY2A8B",
"uniIdToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiI2MGMwMzMwOTI0OTU3OTAwMDFiMDAwZGIiLCJyb2xlIjpbXSwicGVybWlzc2lvbiI6W10sImNsaWVudElkIjoiMTk1Zjc3YzE4MGMyM2UzZjVhOGE4ZjM4ZTQyOTAxODYiLCJpYXQiOjE2MjMyMjgxMTYsImV4cCI6MTYyMzIzNTMxNn0.vtNSqdhCaI6fdvk5aHo5Dmdsb5MkBS8omk0b0YzAgIs"
} }
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
"tokenExpiresIn": 7200, "tokenExpiresIn": 7200,
"tokenExpiresThreshold": 600, "tokenExpiresThreshold": 600,
"passwordErrorLimit": 6, "passwordErrorLimit": 6,
"bindTokenToDevice": true, "bindTokenToDevice": false,
"passwordErrorRetryTime": 3600, "passwordErrorRetryTime": 3600,
"autoSetInviteCode": false, "autoSetInviteCode": false,
"forceInviteCode": false, "forceInviteCode": false,
......
...@@ -30,7 +30,6 @@ ...@@ -30,7 +30,6 @@
"modules" : { "modules" : {
"Fingerprint" : {}, "Fingerprint" : {},
"Share" : {}, "Share" : {},
"Push" : {},
"OAuth" : {}, "OAuth" : {},
"FaceID" : {} "FaceID" : {}
}, },
...@@ -60,7 +59,6 @@ ...@@ -60,7 +59,6 @@
"ios" : {}, "ios" : {},
"sdkConfigs" : { "sdkConfigs" : {
"oauth" : { "oauth" : {
"univerify" : {},
"apple" : {}, "apple" : {},
"weixin" : { "weixin" : {
"appid" : "wxffdd8fa6ec4ef2a0", "appid" : "wxffdd8fa6ec4ef2a0",
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
"tokenExpiresIn": 7200, "tokenExpiresIn": 7200,
"tokenExpiresThreshold": 600, "tokenExpiresThreshold": 600,
"passwordErrorLimit": 6, "passwordErrorLimit": 6,
"bindTokenToDevice": true, "bindTokenToDevice": false,
"passwordErrorRetryTime": 3600, "passwordErrorRetryTime": 3600,
"autoSetInviteCode": false, "autoSetInviteCode": false,
"forceInviteCode": false, "forceInviteCode": false,
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册