提交 76c9b054 编写于 作者: DCloud_JSON's avatar DCloud_JSON 提交者: study夏羽

修复已知问题

上级 5f20f8e0
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
// #ifdef APP-PLUS // #ifdef APP-PLUS
//idfa有需要的用户在应用首次启动时自己获取存储到storage中 //idfa有需要的用户在应用首次启动时自己获取存储到storage中
var idfa = ''; /*var idfa = '';
var manager = plus.ios.invoke('ASIdentifierManager', 'sharedManager'); var manager = plus.ios.invoke('ASIdentifierManager', 'sharedManager');
if(plus.ios.invoke(manager, 'isAdvertisingTrackingEnabled')){ if(plus.ios.invoke(manager, 'isAdvertisingTrackingEnabled')){
var identifier = plus.ios.invoke(manager, 'advertisingIdentifier'); var identifier = plus.ios.invoke(manager, 'advertisingIdentifier');
...@@ -39,19 +39,7 @@ ...@@ -39,19 +39,7 @@
plus.ios.deleteObject(identifier); plus.ios.deleteObject(identifier);
} }
plus.ios.deleteObject(manager); plus.ios.deleteObject(manager);
console.log('idfa = '+idfa); console.log('idfa = '+idfa);*/
//https://ask.dcloud.net.cn/article/36107
/*if(~plus.storage.getItem('idfa')){
plus.device.getInfo({//需要勾选IDFA
success:function(e){
console.log('idfa = '+JSON.stringify(e.idfa));
},
fail:function(e){
console.log('getDeviceInfo failed: '+JSON.stringify(e));
}
});
}*/
// #endif // #endif
}, },
onShow: function() { onShow: function() {
......
此差异已折叠。
## 2.0.0(2022-08-10) ## 2.0.0(2022-08-10)
应用`uni-id-pages``uniIdRouter` - 重要:应用`uni-id-pages``uniIdRouter`
## 1.2.7(2022-08-10) ## 1.2.7(2022-08-10)
- 修复微信小程序绑定手机号失败的问题 - 修复微信小程序绑定手机号失败的问题
## 1.2.6(2022-06-29) ## 1.2.6(2022-06-29)
......
...@@ -4,7 +4,7 @@ import uniStarterConfig from '@/uni-starter.config.js'; ...@@ -4,7 +4,7 @@ import uniStarterConfig from '@/uni-starter.config.js';
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';
// 实现,路由拦截。当应用无访问摄像头/相册权限,引导跳到设置界面 // 实现,路由拦截。当应用无访问摄像头/相册权限,引导跳到设置界面 https://ext.dcloud.net.cn/plugin?id=5095
import interceptorChooseImage from '@/uni_modules/json-interceptor-chooseImage/js_sdk/main.js'; import interceptorChooseImage from '@/uni_modules/json-interceptor-chooseImage/js_sdk/main.js';
interceptorChooseImage() interceptorChooseImage()
...@@ -82,7 +82,7 @@ export default async function() { ...@@ -82,7 +82,7 @@ export default async function() {
} }
}); });
}) })
console.log(7897897897899,params); // console.log(params);
} }
console.log(params); console.log(params);
}, },
...@@ -93,12 +93,17 @@ export default async function() { ...@@ -93,12 +93,17 @@ export default async function() {
}, },
fail(e){ fail(e){
console.error(e);
if (debug) { if (debug) {
uni.showModal({ uni.showModal({
content: JSON.stringify(e), content: JSON.stringify(e),
showCancel: false showCancel: false
}); });
console.error(e); }else{
uni.showToast({
title: '系统错误请稍后再试',
icon:'error'
});
} }
} }
}) })
...@@ -156,77 +161,3 @@ function initAppVersion() { ...@@ -156,77 +161,3 @@ function initAppVersion() {
// 检查更新 // 检查更新
// #endif // #endif
} }
\ No newline at end of file
async function getDeviceInfo() {
let deviceInfo = {
"uuid": '',
"vendor": '',
"push_clientid": '',
"imei": '',
"oaid": '',
"idfa": '',
"model": '',
"platform": '',
}
const {
model,
platform,
} = uni.getSystemInfoSync();
Object.assign(deviceInfo, {
model,
platform
});
// #ifdef APP-PLUS
const oaid = await new Promise((callBack, fail) => {
if (deviceInfo.platform == "android") {
plus.device.getOAID({
success: function(e) {
callBack(e.oaid)
// console.log('getOAID success: '+JSON.stringify(e));
},
fail: function(e) {
callBack()
console.log('getOAID failed: ' + JSON.stringify(e));
}
});
} else {
callBack()
}
}),
{
imei,
uuid
} = await new Promise((callBack, fail) => {
plus.device.getInfo({
success: function(e) {
callBack(e)
// console.log('getOAID success: '+JSON.stringify(e));
},
fail: function(e) {
callBack()
console.log('getOAID failed: ' + JSON.stringify(e));
}
});
}),
idfa = plus.storage.getItem('idfa') || '', //idfa有需要的用户在应用首次启动时自己获取存储到storage中
vendor = plus.device.vendor;
try {
deviceInfo.push_clientid = plus.push.getClientInfo().clientid
} catch (e) {
uni.showModal({
content: '获取推送标识失败。如果你的应用不需要推送功能,请注释掉本代码块',
showCancel: false,
confirmText: "好的"
});
console.log(e)
}
Object.assign(deviceInfo, {
imei,
uuid,
idfa,
vendor
});
// #endif
return deviceInfo
}
{ {
"id": "uni-starter", "id": "uni-starter",
"displayName": "uni-starter", "displayName": "uni-starter",
"version": "1.2.7", "version": "2.0.0",
"description": "云端一体应用快速开发基本项目模版", "description": "云端一体应用快速开发基本项目模版",
"keywords": [ "keywords": [
"login", "login",
......
...@@ -176,7 +176,26 @@ ...@@ -176,7 +176,26 @@
"navigationBarTitleText": "修改密码" "navigationBarTitleText": "修改密码"
} }
} }
], ,{
"path": "uni_modules/uni-id-pages/pages/register/register-by-email",
"style": {
"navigationBarTitleText": "邮箱验证码注册"
}
}
,{
"path": "uni_modules/uni-id-pages/pages/retrieve/retrieve-by-email",
"style": {
"navigationBarTitleText": "通过邮箱重置密码"
}
}
,{
"path": "uni_modules/uni-id-pages/pages/register/register-admin",
"style": {
"enablePullDownRefresh": false,
"navigationBarTitleText": "注册管理员账号"
}
}
],
"globalStyle": { "globalStyle": {
// #ifdef H5 // #ifdef H5
"h5": { "h5": {
......
...@@ -21,7 +21,6 @@ ...@@ -21,7 +21,6 @@
</uni-swiper-dot> </uni-swiper-dot>
</unicloud-db> </unicloud-db>
<!-- 宫格 --> <!-- 宫格 -->
<uni-section :title="$t('grid.grid')" style="margin: 0;" type="line"></uni-section> <uni-section :title="$t('grid.grid')" style="margin: 0;" type="line"></uni-section>
<view class="example-body"> <view class="example-body">
......
...@@ -41,8 +41,8 @@ export default { ...@@ -41,8 +41,8 @@ export default {
}, },
//用于打开应用市场评分界面 //用于打开应用市场评分界面
"marketId":{ "marketId":{
"ios":"id1417078253", "ios":"",
"android":"123456" "android":""
}, },
//配置多语言国际化。i18n为英文单词 internationalization的首末字符i和n,18为中间的字符数 是“国际化”的简称 //配置多语言国际化。i18n为英文单词 internationalization的首末字符i和n,18为中间的字符数 是“国际化”的简称
"i18n":{ "i18n":{
......
## 1.2.1(2022-09-05)
- 修复 当 text 超过 max-num 时,badge 的宽度计算是根据 text 的长度计算,更改为 css 计算实际展示宽度,详见:[https://ask.dcloud.net.cn/question/150473](https://ask.dcloud.net.cn/question/150473)
## 1.2.0(2021-11-19) ## 1.2.0(2021-11-19)
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) - 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-badge](https://uniapp.dcloud.io/component/uniui/uni-badge) - 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-badge](https://uniapp.dcloud.io/component/uniui/uni-badge)
......
<template> <template>
<view class="uni-badge--x"> <view class="uni-badge--x">
<slot /> <slot />
<text v-if="text" :class="classNames" :style="[badgeWidth, positionStyle, customStyle, dotStyle]" <text v-if="text" :class="classNames" :style="[positionStyle, customStyle, dotStyle]"
class="uni-badge" @click="onClick()">{{displayValue}}</text> class="uni-badge" @click="onClick()">{{displayValue}}</text>
</view> </view>
</template> </template>
...@@ -130,16 +130,13 @@ ...@@ -130,16 +130,13 @@
const match = whiteList[this.absolute] const match = whiteList[this.absolute]
return match ? match : whiteList['rightTop'] return match ? match : whiteList['rightTop']
}, },
badgeWidth() {
return {
width: `${this.width}px`
}
},
dotStyle() { dotStyle() {
if (!this.isDot) return {} if (!this.isDot) return {}
return { return {
width: '10px', width: '10px',
minWidth: '0',
height: '10px', height: '10px',
padding: '0',
borderRadius: '10px' borderRadius: '10px'
} }
}, },
...@@ -160,7 +157,7 @@ ...@@ -160,7 +157,7 @@
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" >
$uni-primary: #2979ff !default; $uni-primary: #2979ff !default;
$uni-success: #4cd964 !default; $uni-success: #4cd964 !default;
$uni-warning: #f0ad4e !default; $uni-warning: #f0ad4e !default;
...@@ -199,6 +196,8 @@ ...@@ -199,6 +196,8 @@
justify-content: center; justify-content: center;
flex-direction: row; flex-direction: row;
height: 20px; height: 20px;
min-width: 20px;
padding: 0 4px;
line-height: 18px; line-height: 18px;
color: #fff; color: #fff;
border-radius: 100px; border-radius: 100px;
...@@ -207,6 +206,7 @@ ...@@ -207,6 +206,7 @@
border: 1px solid #fff; border: 1px solid #fff;
text-align: center; text-align: center;
font-family: 'Helvetica Neue', Helvetica, sans-serif; font-family: 'Helvetica Neue', Helvetica, sans-serif;
font-feature-settings: "tnum";
font-size: $bage-size; font-size: $bage-size;
/* #ifdef H5 */ /* #ifdef H5 */
z-index: 999; z-index: 999;
......
{ {
"id": "uni-badge", "id": "uni-badge",
"displayName": "uni-badge 数字角标", "displayName": "uni-badge 数字角标",
"version": "1.2.0", "version": "1.2.1",
"description": "数字角标(徽章)组件,在元素周围展示消息提醒,一般用于列表、九宫格、按钮等地方。", "description": "数字角标(徽章)组件,在元素周围展示消息提醒,一般用于列表、九宫格、按钮等地方。",
"keywords": [ "keywords": [
"", "",
...@@ -18,11 +18,7 @@ ...@@ -18,11 +18,7 @@
"directories": { "directories": {
"example": "../../temps/example_temps" "example": "../../temps/example_temps"
}, },
"dcloudext": { "dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": { "sale": {
"regular": { "regular": {
"price": "0.00" "price": "0.00"
...@@ -39,10 +35,11 @@ ...@@ -39,10 +35,11 @@
"data": "无", "data": "无",
"permissions": "无" "permissions": "无"
}, },
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
}, },
"uni_modules": { "uni_modules": {
"dependencies": [], "dependencies": ["uni-scss"],
"encrypt": [], "encrypt": [],
"platforms": { "platforms": {
"cloud": { "cloud": {
......
## 1.0.3(2022-09-16)
- 可以使用 uni-scss 控制主题色
## 1.0.2(2022-06-30)
- 优化 在 uni-forms 中的依赖注入方式
## 1.0.1(2022-02-07) ## 1.0.1(2022-02-07)
- 修复 multiple 为 true 时,v-model 的值为 null 报错的 bug - 修复 multiple 为 true 时,v-model 的值为 null 报错的 bug
## 1.0.0(2021-11-19) ## 1.0.0(2021-11-19)
......
...@@ -155,17 +155,17 @@ ...@@ -155,17 +155,17 @@
value(newVal) { value(newVal) {
this.dataList = this.getDataList(newVal) this.dataList = this.getDataList(newVal)
// fix by mehaotian is_reset 在 uni-forms 中定义 // fix by mehaotian is_reset 在 uni-forms 中定义
if(!this.is_reset){ // if(!this.is_reset){
this.is_reset = false // this.is_reset = false
this.formItem && this.formItem.setValue(newVal) // this.formItem && this.formItem.setValue(newVal)
} // }
}, },
modelValue(newVal) { modelValue(newVal) {
this.dataList = this.getDataList(newVal); this.dataList = this.getDataList(newVal);
if(!this.is_reset){ // if(!this.is_reset){
this.is_reset = false // this.is_reset = false
this.formItem && this.formItem.setValue(newVal) // this.formItem && this.formItem.setValue(newVal)
} // }
} }
}, },
data() { data() {
...@@ -193,22 +193,22 @@ ...@@ -193,22 +193,22 @@
} }
}, },
created() { created() {
this.form = this.getForm('uniForms') // this.form = this.getForm('uniForms')
this.formItem = this.getForm('uniFormsItem') // this.formItem = this.getForm('uniFormsItem')
// this.formItem && this.formItem.setValue(this.value) // this.formItem && this.formItem.setValue(this.value)
if (this.formItem) { // if (this.formItem) {
this.isTop = 6 // this.isTop = 6
if (this.formItem.name) { // if (this.formItem.name) {
// 如果存在name添加默认值,否则formData 中不存在这个字段不校验 // // 如果存在name添加默认值,否则formData 中不存在这个字段不校验
if(!this.is_reset){ // if(!this.is_reset){
this.is_reset = false // this.is_reset = false
this.formItem.setValue(this.dataValue) // this.formItem.setValue(this.dataValue)
} // }
this.rename = this.formItem.name // this.rename = this.formItem.name
this.form.inputChildrens.push(this) // this.form.inputChildrens.push(this)
} // }
} // }
if (this.localdata && this.localdata.length !== 0) { if (this.localdata && this.localdata.length !== 0) {
this.isLocal = true this.isLocal = true
...@@ -273,7 +273,7 @@ ...@@ -273,7 +273,7 @@
} }
} }
} }
this.formItem && this.formItem.setValue(detail.value) // this.formItem && this.formItem.setValue(detail.value)
// TODO 兼容 vue2 // TODO 兼容 vue2
this.$emit('input', detail.value); this.$emit('input', detail.value);
// // TOTO 兼容 vue3 // // TOTO 兼容 vue3
...@@ -375,7 +375,7 @@ ...@@ -375,7 +375,7 @@
selectedArr.push(item[this.map.value]) selectedArr.push(item[this.map.value])
} }
}) })
return this.dataValue && this.dataValue.length > 0 ? this.dataValue : selectedArr return this.dataValue.length > 0 ? this.dataValue : selectedArr
}, },
/** /**
...@@ -384,12 +384,14 @@ ...@@ -384,12 +384,14 @@
setStyleBackgroud(item) { setStyleBackgroud(item) {
let styles = {} let styles = {}
let selectedColor = this.selectedColor?this.selectedColor:'#2979ff' let selectedColor = this.selectedColor?this.selectedColor:'#2979ff'
if (this.selectedColor) {
if (this.mode !== 'list') { if (this.mode !== 'list') {
styles['border-color'] = item.selected?selectedColor:'#DCDFE6' styles['border-color'] = item.selected?selectedColor:'#DCDFE6'
} }
if (this.mode === 'tag') { if (this.mode === 'tag') {
styles['background-color'] = item.selected? selectedColor:'#f5f5f5' styles['background-color'] = item.selected? selectedColor:'#f5f5f5'
} }
}
let classles = '' let classles = ''
for (let i in styles) { for (let i in styles) {
classles += `${i}:${styles[i]};` classles += `${i}:${styles[i]};`
...@@ -399,6 +401,7 @@ ...@@ -399,6 +401,7 @@
setStyleIcon(item) { setStyleIcon(item) {
let styles = {} let styles = {}
let classles = '' let classles = ''
if (this.selectedColor) {
let selectedColor = this.selectedColor?this.selectedColor:'#2979ff' let selectedColor = this.selectedColor?this.selectedColor:'#2979ff'
styles['background-color'] = item.selected?selectedColor:'#fff' styles['background-color'] = item.selected?selectedColor:'#fff'
styles['border-color'] = item.selected?selectedColor:'#DCDFE6' styles['border-color'] = item.selected?selectedColor:'#DCDFE6'
...@@ -407,7 +410,7 @@ ...@@ -407,7 +410,7 @@
styles['background-color'] = '#F2F6FC' styles['background-color'] = '#F2F6FC'
styles['border-color'] = item.selected?selectedColor:'#DCDFE6' styles['border-color'] = item.selected?selectedColor:'#DCDFE6'
} }
}
for (let i in styles) { for (let i in styles) {
classles += `${i}:${styles[i]};` classles += `${i}:${styles[i]};`
} }
...@@ -416,6 +419,7 @@ ...@@ -416,6 +419,7 @@
setStyleIconText(item) { setStyleIconText(item) {
let styles = {} let styles = {}
let classles = '' let classles = ''
if (this.selectedColor) {
let selectedColor = this.selectedColor?this.selectedColor:'#2979ff' let selectedColor = this.selectedColor?this.selectedColor:'#2979ff'
if (this.mode === 'tag') { if (this.mode === 'tag') {
styles.color = item.selected?(this.selectedTextColor?this.selectedTextColor:'#fff'):'#666' styles.color = item.selected?(this.selectedTextColor?this.selectedTextColor:'#fff'):'#666'
...@@ -425,7 +429,7 @@ ...@@ -425,7 +429,7 @@
if(!item.selected && item.disabled){ if(!item.selected && item.disabled){
styles.color = '#999' styles.color = '#999'
} }
}
for (let i in styles) { for (let i in styles) {
classles += `${i}:${styles[i]};` classles += `${i}:${styles[i]};`
} }
...@@ -448,7 +452,7 @@ ...@@ -448,7 +452,7 @@
</script> </script>
<style lang="scss"> <style lang="scss">
$checked-color: #2979ff; $uni-primary: #2979ff !default;
$border-color: #DCDFE6; $border-color: #DCDFE6;
$disable:0.4; $disable:0.4;
...@@ -614,8 +618,8 @@ ...@@ -614,8 +618,8 @@
// 选中 // 选中
&.is-checked { &.is-checked {
.checkbox__inner { .checkbox__inner {
border-color: $checked-color; border-color: $uni-primary;
background-color: $checked-color; background-color: $uni-primary;
.checkbox__inner-icon { .checkbox__inner-icon {
opacity: 1; opacity: 1;
...@@ -623,14 +627,14 @@ ...@@ -623,14 +627,14 @@
} }
} }
.radio__inner { .radio__inner {
border-color: $checked-color; border-color: $uni-primary;
.radio__inner-icon { .radio__inner-icon {
opacity: 1; opacity: 1;
background-color: $checked-color; background-color: $uni-primary;
} }
} }
.checklist-text { .checklist-text {
color: $checked-color; color: $uni-primary;
} }
// 选中禁用 // 选中禁用
&.is-disable { &.is-disable {
...@@ -683,10 +687,10 @@ ...@@ -683,10 +687,10 @@
} }
&.is-checked { &.is-checked {
border-color: $checked-color; border-color: $uni-primary;
.checkbox__inner { .checkbox__inner {
border-color: $checked-color; border-color: $uni-primary;
background-color: $checked-color; background-color: $uni-primary;
.checkbox__inner-icon { .checkbox__inner-icon {
opacity: 1; opacity: 1;
transform: rotate(45deg); transform: rotate(45deg);
...@@ -694,16 +698,16 @@ ...@@ -694,16 +698,16 @@
} }
.radio__inner { .radio__inner {
border-color: $checked-color; border-color: $uni-primary;
.radio__inner-icon { .radio__inner-icon {
opacity: 1; opacity: 1;
background-color: $checked-color; background-color: $uni-primary;
} }
} }
.checklist-text { .checklist-text {
color: $checked-color; color: $uni-primary;
} }
// 选中禁用 // 选中禁用
...@@ -735,8 +739,8 @@ ...@@ -735,8 +739,8 @@
} }
&.is-checked { &.is-checked {
background-color: $checked-color; background-color: $uni-primary;
border-color: $checked-color; border-color: $uni-primary;
.checklist-text { .checklist-text {
color: #fff; color: #fff;
...@@ -775,8 +779,8 @@ ...@@ -775,8 +779,8 @@
&.is-checked { &.is-checked {
.checkbox__inner { .checkbox__inner {
border-color: $checked-color; border-color: $uni-primary;
background-color: $checked-color; background-color: $uni-primary;
.checkbox__inner-icon { .checkbox__inner-icon {
opacity: 1; opacity: 1;
...@@ -789,13 +793,13 @@ ...@@ -789,13 +793,13 @@
} }
} }
.checklist-text { .checklist-text {
color: $checked-color; color: $uni-primary;
} }
.checklist-content { .checklist-content {
.checkobx__list { .checkobx__list {
opacity: 1; opacity: 1;
border-color: $checked-color; border-color: $uni-primary;
} }
} }
......
{ {
"id": "uni-data-checkbox", "id": "uni-data-checkbox",
"displayName": "uni-data-checkbox 数据选择器", "displayName": "uni-data-checkbox 数据选择器",
"version": "1.0.1", "version": "1.0.3",
"description": "通过数据驱动的单选框和复选框", "description": "通过数据驱动的单选框和复选框",
"keywords": [ "keywords": [
"uni-ui", "uni-ui",
...@@ -17,11 +17,7 @@ ...@@ -17,11 +17,7 @@
"directories": { "directories": {
"example": "../../temps/example_temps" "example": "../../temps/example_temps"
}, },
"dcloudext": { "dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": { "sale": {
"regular": { "regular": {
"price": "0.00" "price": "0.00"
...@@ -38,7 +34,8 @@ ...@@ -38,7 +34,8 @@
"data": "无", "data": "无",
"permissions": "无" "permissions": "无"
}, },
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
}, },
"uni_modules": { "uni_modules": {
"dependencies": ["uni-load-more","uni-scss"], "dependencies": ["uni-load-more","uni-scss"],
......
## 1.1.0(2022-06-30)
- 新增 在 uni-forms 1.4.0 中使用可以在 blur 时校验内容
- 新增 clear 事件,点击右侧叉号图标触发
- 新增 change 事件 ,仅在输入框失去焦点或用户按下回车时触发
- 优化 组件样式,组件获取焦点时高亮显示,图标颜色调整等
-
## 1.0.5(2022-06-07)
- 优化 clearable 显示策略
## 1.0.4(2022-06-07)
- 优化 clearable 显示策略
## 1.0.3(2022-05-20) ## 1.0.3(2022-05-20)
- 修复 关闭图标某些情况下无法取消的bug - 修复 关闭图标某些情况下无法取消的bug
## 1.0.2(2022-04-12) ## 1.0.2(2022-04-12)
......
<template> <template>
<view class="uni-easyinput" :class="{'uni-easyinput-error':msg}" :style="{color:inputBorder && msg?'#e43d33':styles.color}"> <view class="uni-easyinput" :class="{'uni-easyinput-error':msg}" :style="boxStyle">
<view class="uni-easyinput__content" :class="{'is-input-border':inputBorder ,'is-input-error-border':inputBorder && msg,'is-textarea':type==='textarea','is-disabled':disabled}" <view class="uni-easyinput__content" :class="inputContentClass" :style="inputContentStyle">
:style="{'border-color':inputBorder && msg?'#dd524d':styles.borderColor,'background-color':disabled?styles.disableColor:''}"> <uni-icons v-if="prefixIcon" class="content-clear-icon" :type="prefixIcon" color="#c0c4cc"
<uni-icons v-if="prefixIcon" class="content-clear-icon" :type="prefixIcon" color="#c0c4cc" @click="onClickIcon('prefix')"></uni-icons> @click="onClickIcon('prefix')" size="22"></uni-icons>
<textarea v-if="type === 'textarea'" class="uni-easyinput__content-textarea" :class="{'input-padding':inputBorder}" <textarea v-if="type === 'textarea'" class="uni-easyinput__content-textarea"
:name="name" :value="val" :placeholder="placeholder" :placeholderStyle="placeholderStyle" :disabled="disabled" placeholder-class="uni-easyinput__placeholder-class" :class="{'input-padding':inputBorder}" :name="name" :value="val" :placeholder="placeholder"
:maxlength="inputMaxlength" :focus="focused" :autoHeight="autoHeight" @input="onInput" @blur="onBlur" @focus="onFocus" :placeholderStyle="placeholderStyle" :disabled="disabled"
@confirm="onConfirm"></textarea> placeholder-class="uni-easyinput__placeholder-class" :maxlength="inputMaxlength" :focus="focused"
<input v-else :type="type === 'password'?'text':type" class="uni-easyinput__content-input" :style="{ :autoHeight="autoHeight" @input="onInput" @blur="_Blur" @focus="_Focus" @confirm="onConfirm"></textarea>
'padding-right':type === 'password' ||clearable || prefixIcon?'':'10px', <input v-else :type="type === 'password'?'text':type" class="uni-easyinput__content-input"
'padding-left':prefixIcon?'':'10px' :style="inputStyle" :name="name" :value="val" :password="!showPassword && type === 'password'"
}" :placeholder="placeholder" :placeholderStyle="placeholderStyle"
:name="name" :value="val" :password="!showPassword && type === 'password'" :placeholder="placeholder" placeholder-class="uni-easyinput__placeholder-class" :disabled="disabled" :maxlength="inputMaxlength"
:placeholderStyle="placeholderStyle" placeholder-class="uni-easyinput__placeholder-class" :disabled="disabled" :maxlength="inputMaxlength" :focus="focused" :confirmType="confirmType" @focus="onFocus" :focus="focused" :confirmType="confirmType" @focus="_Focus" @blur="_Blur" @input="onInput"
@blur="onBlur" @input="onInput" @confirm="onConfirm" /> @confirm="onConfirm" />
<template v-if="type === 'password' && passwordIcon" > <template v-if="type === 'password' && passwordIcon">
<uni-icons v-if="val" class="content-clear-icon" :class="{'is-textarea-icon':type==='textarea'}" :type="showPassword?'eye-slash-filled':'eye-filled'" <!-- 开启密码时显示小眼睛 -->
:size="18" color="#c0c4cc" @click="onEyes"></uni-icons> <uni-icons v-if="isVal" class="content-clear-icon" :class="{'is-textarea-icon':type==='textarea'}"
:type="showPassword?'eye-slash-filled':'eye-filled'" :size="22"
:color="focusShow?'#2979ff':'#c0c4cc'" @click="onEyes">
</uni-icons>
</template> </template>
<template v-else-if="suffixIcon"> <template v-else-if="suffixIcon">
<uni-icons v-if="suffixIcon" class="content-clear-icon" :type="suffixIcon" color="#c0c4cc" @click="onClickIcon('suffix')"></uni-icons> <uni-icons v-if="suffixIcon" class="content-clear-icon" :type="suffixIcon" color="#c0c4cc"
@click="onClickIcon('suffix')" size="22"></uni-icons>
</template> </template>
<template v-else> <template v-else>
<uni-icons class="content-clear-icon" :class="{'is-textarea-icon':type==='textarea'}" type="clear" :size="clearSize" <uni-icons v-if="clearable && isVal && !disabled && type !== 'textarea'" class="content-clear-icon"
v-if="clearable && val && !disabled" color="#c0c4cc" @click="onClear"></uni-icons> :class="{'is-textarea-icon':type==='textarea'}" type="clear" :size="clearSize"
:color="msg?'#dd524d':(focusShow?'#2979ff':'#c0c4cc')" @click="onClear"></uni-icons>
</template> </template>
<slot name="right"></slot> <slot name="right"></slot>
</view> </view>
...@@ -31,10 +36,6 @@ ...@@ -31,10 +36,6 @@
</template> </template>
<script> <script>
// import {
// debounce,
// throttle
// } from './common.js'
/** /**
* Easyinput 输入框 * Easyinput 输入框
* @description 此组件可以实现表单的输入与校验,包括 "text" 和 "textarea" 类型。 * @description 此组件可以实现表单的输入与校验,包括 "text" 和 "textarea" 类型。
...@@ -48,7 +49,7 @@ ...@@ -48,7 +49,7 @@
* @value idcard 身份证输入键盘,信、支付宝、百度、QQ小程序 * @value idcard 身份证输入键盘,信、支付宝、百度、QQ小程序
* @value digit 带小数点的数字键盘 ,App的nvue页面、微信、支付宝、百度、头条、QQ小程序支持 * @value digit 带小数点的数字键盘 ,App的nvue页面、微信、支付宝、百度、头条、QQ小程序支持
* @property {Boolean} clearable 是否显示右侧清空内容的图标控件,点击可清空输入框内容(默认true) * @property {Boolean} clearable 是否显示右侧清空内容的图标控件,点击可清空输入框内容(默认true)
* @property {Boolean} autoHeight 是否自动增高输入区域,type为textarea时有效(默认false) * @property {Boolean} autoHeight 是否自动增高输入区域,type为textarea时有效(默认true)
* @property {String } placeholder 输入框的提示文字 * @property {String } placeholder 输入框的提示文字
* @property {String } placeholderStyle placeholder的样式(内联样式,字符串),如"color: #ddd" * @property {String } placeholderStyle placeholder的样式(内联样式,字符串),如"color: #ddd"
* @property {Boolean} focus 是否自动获得焦点(默认false) * @property {Boolean} focus 是否自动获得焦点(默认false)
...@@ -76,13 +77,44 @@ ...@@ -76,13 +77,44 @@
* @event {Function} iconClick 点击图标时触发 * @event {Function} iconClick 点击图标时触发
* @example <uni-easyinput v-model="mobile"></uni-easyinput> * @example <uni-easyinput v-model="mobile"></uni-easyinput>
*/ */
function obj2strClass(obj) {
let classess = ''
for (let key in obj) {
const val = obj[key]
if (val) {
classess += `${key} `
}
}
return classess
}
function obj2strStyle(obj) {
let style = ''
for (let key in obj) {
const val = obj[key]
style += `${key}:${val};`
}
return style
}
export default { export default {
name: 'uni-easyinput', name: 'uni-easyinput',
emits:['click','iconClick','update:modelValue','input','focus','blur','confirm'], emits: ['click', 'iconClick', 'update:modelValue', 'input', 'focus', 'blur', 'confirm', 'clear', 'eyes', 'change'],
model:{ model: {
prop:'modelValue', prop: 'modelValue',
event:'update:modelValue' event: 'update:modelValue'
},
options: {
virtualHost: true
},
inject: {
form: {
from: 'uniForm',
default: null
},
formItem: {
from: 'uniFormItem',
default: null
},
}, },
props: { props: {
name: String, name: String,
...@@ -100,7 +132,10 @@ ...@@ -100,7 +132,10 @@
type: Boolean, type: Boolean,
default: false default: false
}, },
placeholder: String, placeholder: {
type: String,
default: ' '
},
placeholderStyle: String, placeholderStyle: String,
focus: { focus: {
type: Boolean, type: Boolean,
...@@ -120,7 +155,7 @@ ...@@ -120,7 +155,7 @@
}, },
clearSize: { clearSize: {
type: [Number, String], type: [Number, String],
default: 15 default: 24
}, },
inputBorder: { inputBorder: {
type: Boolean, type: Boolean,
...@@ -138,7 +173,7 @@ ...@@ -138,7 +173,7 @@
type: [Boolean, String], type: [Boolean, String],
default: true default: true
}, },
passwordIcon:{ passwordIcon: {
type: Boolean, type: Boolean,
default: true default: true
}, },
...@@ -152,79 +187,105 @@ ...@@ -152,79 +187,105 @@
} }
} }
}, },
errorMessage:{ errorMessage: {
type:[String,Boolean], type: [String, Boolean],
default:'' default: ''
} }
}, },
data() { data() {
return { return {
focused: false, focused: false,
errMsg: '',
val: '', val: '',
showMsg: '', showMsg: '',
border: false, border: false,
isFirstBorder: false, isFirstBorder: false,
showClearIcon: false, showClearIcon: false,
showPassword: false showPassword: false,
focusShow: false,
localMsg: ''
}; };
}, },
computed: { computed: {
// 输入框内是否有值
isVal() {
const val = this.val
// fixed by mehaotian 处理值为0的情况,字符串0不在处理范围
if (val || val === 0) {
return true
}
return false
},
msg() { msg() {
return this.errorMessage || this.errMsg; // console.log('computed', this.form, this.formItem);
// if (this.form) {
// return this.errorMessage || this.formItem.errMsg;
// }
// TODO 处理头条 formItem 中 errMsg 不更新的问题
return this.localMsg || this.errorMessage
}, },
// 因为uniapp的input组件的maxlength组件必须要数值,这里转为数值,用户可以传入字符串数值 // 因为uniapp的input组件的maxlength组件必须要数值,这里转为数值,用户可以传入字符串数值
inputMaxlength() { inputMaxlength() {
return Number(this.maxlength); return Number(this.maxlength);
}, },
// 处理外层样式的style
boxStyle() {
return `color:${this.inputBorder && this.msg?'#e43d33':this.styles.color};`
},
// input 内容的类和样式处理
inputContentClass() {
return obj2strClass({
'is-input-border': this.inputBorder,
'is-input-error-border': this.inputBorder && this.msg,
'is-textarea': this.type === 'textarea',
'is-disabled': this.disabled
})
},
inputContentStyle() {
const focusColor = this.focusShow ? '#2979ff' : this.styles.borderColor
const borderColor = this.inputBorder && this.msg ? '#dd524d' : focusColor
return obj2strStyle({
'border-color': borderColor || '#e5e5e5',
'background-color': this.disabled ? this.styles.disableColor : '#fff'
})
},
// input右侧样式
inputStyle() {
const paddingRight = this.type === 'password' || this.clearable || this.prefixIcon ? '' : '10px'
return obj2strStyle({
'padding-right': paddingRight,
'padding-left': this.prefixIcon ? '' : '10px'
})
}
}, },
watch: { watch: {
value(newVal) { value(newVal) {
if (this.errMsg) this.errMsg = ''
this.val = newVal this.val = newVal
// fix by mehaotian is_reset 在 uni-forms 中定义
if (this.form && this.formItem &&!this.is_reset) {
this.is_reset = false
this.formItem.setValue(newVal)
}
}, },
modelValue(newVal) { modelValue(newVal) {
if (this.errMsg) this.errMsg = ''
this.val = newVal this.val = newVal
if (this.form && this.formItem &&!this.is_reset) {
this.is_reset = false
this.formItem.setValue(newVal)
}
}, },
focus(newVal) { focus(newVal) {
this.$nextTick(() => { this.$nextTick(() => {
this.focused = this.focus this.focused = this.focus
this.focusShow = this.focus
}) })
} }
}, },
created() { created() {
if(!this.value && this.value !== 0){ this.init()
this.val = this.modelValue // TODO 处理头条vue3 computed 不监听 inject 更改的问题(formItem.errMsg)
}
if(!this.modelValue && this.modelValue !== 0){
this.val = this.value
}
this.form = this.getForm('uniForms')
this.formItem = this.getForm('uniFormsItem')
if (this.form && this.formItem) { if (this.form && this.formItem) {
if (this.formItem.name) { this.$watch('formItem.errMsg', (newVal) => {
if(!this.is_reset){ this.localMsg = newVal
this.is_reset = false })
this.formItem.setValue(this.val)
}
this.rename = this.formItem.name
this.form.inputChildrens.push(this)
}
} }
}, },
mounted() { mounted() {
this.$nextTick(() => { this.$nextTick(() => {
this.focused = this.focus this.focused = this.focus
this.focusShow = this.focus
}) })
}, },
methods: { methods: {
...@@ -232,28 +293,35 @@ ...@@ -232,28 +293,35 @@
* 初始化变量值 * 初始化变量值
*/ */
init() { init() {
if (this.value || this.value === 0) {
this.val = this.value
} else if (this.modelValue || this.modelValue === 0) {
this.val = this.modelValue
} else {
this.val = null
}
}, },
/**
* 点击图标时触发
* @param {Object} type
*/
onClickIcon(type) { onClickIcon(type) {
this.$emit('iconClick', type) this.$emit('iconClick', type)
}, },
/** /**
* 获取父元素实例 * 显示隐藏内容,密码框时生效
*/ */
getForm(name = 'uniForms') {
let parent = this.$parent;
let parentName = parent.$options.name;
while (parentName !== name) {
parent = parent.$parent;
if (!parent) return false;
parentName = parent.$options.name;
}
return parent;
},
onEyes() { onEyes() {
this.showPassword = !this.showPassword this.showPassword = !this.showPassword
this.$emit('eyes', this.showPassword)
}, },
/**
* 输入时触发
* @param {Object} event
*/
onInput(event) { onInput(event) {
let value = event.detail.value; let value = event.detail.value;
// 判断是否去除空格 // 判断是否去除空格
...@@ -270,30 +338,79 @@ ...@@ -270,30 +338,79 @@
// TODO 兼容 vue2 // TODO 兼容 vue2
this.$emit('input', value); this.$emit('input', value);
// TODO 兼容 vue3 // TODO 兼容 vue3
this.$emit('update:modelValue',value) this.$emit('update:modelValue', value)
},
/**
* 外部调用方法
* 获取焦点时触发
* @param {Object} event
*/
onFocus() {
this.$nextTick(() => {
this.focused = true
})
this.$emit('focus', null);
}, },
onFocus(event) { _Focus(event) {
this.focusShow = true
this.$emit('focus', event); this.$emit('focus', event);
}, },
onBlur(event) {
/**
* 外部调用方法
* 失去焦点时触发
* @param {Object} event
*/
onBlur() {
this.focused = false
this.$emit('focus', null);
},
_Blur(event) {
let value = event.detail.value; let value = event.detail.value;
this.focusShow = false
this.$emit('blur', event); this.$emit('blur', event);
// 根据类型返回值,在event中获取的值理论上讲都是string
this.$emit('change', this.val)
// 失去焦点时参与表单校验
if (this.form && this.formItem) {
const {
validateTrigger
} = this.form
if (validateTrigger === 'blur') {
this.formItem.onFieldChange()
}
}
}, },
/**
* 按下键盘的发送键
* @param {Object} e
*/
onConfirm(e) { onConfirm(e) {
this.$emit('confirm', e.detail.value); this.$emit('confirm', this.val);
this.$emit('change', this.val)
}, },
/**
* 清理内容
* @param {Object} event
*/
onClear(event) { onClear(event) {
this.val = ''; this.val = '';
// TODO 兼容 vue2 // TODO 兼容 vue2
this.$emit('input', ''); this.$emit('input', '');
// TODO 兼容 vue2 // TODO 兼容 vue2
// TODO 兼容 vue3 // TODO 兼容 vue3
this.$emit('update:modelValue','') this.$emit('update:modelValue', '')
}, // 点击叉号触发
fieldClick() { this.$emit('clear')
this.$emit('click');
}, },
/**
* 去除空格
*/
trimStr(str, pos = 'both') { trimStr(str, pos = 'both') {
if (pos === 'both') { if (pos === 'both') {
return str.trim(); return str.trim();
...@@ -316,9 +433,10 @@ ...@@ -316,9 +433,10 @@
}; };
</script> </script>
<style lang="scss" > <style lang="scss">
$uni-error: #e43d33; $uni-error: #e43d33;
$uni-border-1: #DCDFE6 !default; $uni-border-1: #DCDFE6 !default;
.uni-easyinput { .uni-easyinput {
/* #ifndef APP-NVUE */ /* #ifndef APP-NVUE */
width: 100%; width: 100%;
...@@ -336,10 +454,14 @@ ...@@ -336,10 +454,14 @@
width: 100%; width: 100%;
display: flex; display: flex;
box-sizing: border-box; box-sizing: border-box;
min-height: 36px; // min-height: 36px;
/* #endif */ /* #endif */
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
// 处理border动画刚开始显示黑色的问题
border-color: #fff;
transition-property: border-color;
transition-duration: 0.3s;
} }
.uni-easyinput__content-input { .uni-easyinput__content-input {
...@@ -351,12 +473,16 @@ ...@@ -351,12 +473,16 @@
flex: 1; flex: 1;
line-height: 1; line-height: 1;
font-size: 14px; font-size: 14px;
height: 35px;
// min-height: 36px;
} }
.uni-easyinput__placeholder-class { .uni-easyinput__placeholder-class {
color: #999; color: #999;
font-size: 12px; font-size: 12px;
font-weight: 200; // font-weight: 200;
} }
.is-textarea { .is-textarea {
align-items: flex-start; align-items: flex-start;
} }
...@@ -371,9 +497,10 @@ ...@@ -371,9 +497,10 @@
flex: 1; flex: 1;
line-height: 1.5; line-height: 1.5;
font-size: 14px; font-size: 14px;
padding-top: 6px; margin: 6px;
padding-bottom: 10px; margin-left: 0;
height: 80px; height: 80px;
min-height: 80px;
/* #ifndef APP-NVUE */ /* #ifndef APP-NVUE */
min-height: 80px; min-height: 80px;
width: auto; width: auto;
...@@ -403,6 +530,9 @@ ...@@ -403,6 +530,9 @@
align-items: center; align-items: center;
border: 1px solid $uni-border-1; border: 1px solid $uni-border-1;
border-radius: 4px; border-radius: 4px;
/* #ifdef MP-ALIPAY */
overflow: hidden;
/* #endif */
} }
.uni-error-message { .uni-error-message {
...@@ -423,8 +553,10 @@ ...@@ -423,8 +553,10 @@
.is-input-error-border { .is-input-error-border {
border-color: $uni-error; border-color: $uni-error;
.uni-easyinput__placeholder-class { .uni-easyinput__placeholder-class {
color: mix(#fff, $uni-error, 50%);; color: mix(#fff, $uni-error, 50%);
;
} }
} }
...@@ -450,9 +582,9 @@ ...@@ -450,9 +582,9 @@
} }
.is-disabled { .is-disabled {
border-color: red;
background-color: #F7F6F6; background-color: #F7F6F6;
color: #D5D5D5; color: #D5D5D5;
.uni-easyinput__placeholder-class { .uni-easyinput__placeholder-class {
color: #D5D5D5; color: #D5D5D5;
font-size: 12px; font-size: 12px;
......
{ {
"id": "uni-easyinput", "id": "uni-easyinput",
"displayName": "uni-easyinput 增强输入框", "displayName": "uni-easyinput 增强输入框",
"version": "1.0.3", "version": "1.1.0",
"description": "Easyinput 组件是对原生input组件的增强", "description": "Easyinput 组件是对原生input组件的增强",
"keywords": [ "keywords": [
"uni-ui", "uni-ui",
......
## 1.4.8(2022-08-23)
- 优化 根据 rules 自动添加 required 的问题
## 1.4.7(2022-08-22)
- 修复 item 未设置 require 属性,rules 设置 require 后,星号也显示的 bug,详见:[https://ask.dcloud.net.cn/question/151540](https://ask.dcloud.net.cn/question/151540)
## 1.4.6(2022-07-13)
- 修复 model 需要校验的值没有声明对应字段时,导致第一次不触发校验的bug
## 1.4.5(2022-07-05)
- 新增 更多表单示例
- 优化 子表单组件过期提示的问题
- 优化 子表单组件uni-datetime-picker、uni-data-select、uni-data-picker的显示样式
## 1.4.4(2022-07-04)
- 更新 删除组件日志
## 1.4.3(2022-07-04)
- 修复 由 1.4.0 引发的 label 插槽不生效的bug
## 1.4.2(2022-07-04)
- 修复 子组件找不到 setValue 报错的bug
## 1.4.1(2022-07-04)
- 修复 uni-data-picker 在 uni-forms-item 中报错的bug
- 修复 uni-data-picker 在 uni-forms-item 中宽度不正确的bug
## 1.4.0(2022-06-30)
- 【重要】组件逻辑重构,部分用法用旧版本不兼容,请注意兼容问题
- 【重要】组件使用 Provide/Inject 方式注入依赖,提供了自定义表单组件调用 uni-forms 校验表单的能力
- 新增 model 属性,等同于原 value/modelValue 属性,旧属性即将废弃
- 新增 validateTrigger 属性的 blur 值,仅 uni-easyinput 生效
- 新增 onFieldChange 方法,可以对子表单进行校验,可替代binddata方法
- 新增 子表单的 setRules 方法,配合自定义校验函数使用
- 新增 uni-forms-item 的 setRules 方法,配置动态表单使用可动态更新校验规则
- 优化 动态表单校验方式,废弃拼接name的方式
## 1.3.3(2022-06-22)
- 修复 表单校验顺序无序问题
## 1.3.2(2021-12-09) ## 1.3.2(2021-12-09)
- -
## 1.3.1(2021-11-19) ## 1.3.1(2021-11-19)
......
<template> <template>
<view class="uni-forms" :class="{ 'uni-forms--top': !border }"> <view class="uni-forms">
<form @submit.stop="submitForm" @reset="resetForm"> <form>
<slot></slot> <slot></slot>
</form> </form>
</view> </view>
</template> </template>
<script> <script>
import Validator from './validate.js';
import {
deepCopy,
getValue,
isRequiredField,
setDataValue,
getDataValue,
realName,
isRealName,
rawData,
isEqual
} from './utils.js'
// #ifndef VUE3 // #ifndef VUE3
// 后续会慢慢废弃这个方法
import Vue from 'vue'; import Vue from 'vue';
Vue.prototype.binddata = function(name, value, formName) { Vue.prototype.binddata = function(name, value, formName) {
if (formName) { if (formName) {
...@@ -26,18 +40,15 @@ ...@@ -26,18 +40,15 @@
} }
}; };
// #endif // #endif
import Validator from './validate.js';
/** /**
* Forms 表单 * Forms 表单
* @description 由输入框、选择器、单选框、多选框等控件组成,用以收集、校验、提交数据 * @description 由输入框、选择器、单选框、多选框等控件组成,用以收集、校验、提交数据
* @tutorial https://ext.dcloud.net.cn/plugin?id=2773 * @tutorial https://ext.dcloud.net.cn/plugin?id=2773
* @property {Object} rules 表单校验规则 * @property {Object} rules 表单校验规则
* @property {String} validateTrigger = [bind|submit] 校验触发器方式 默认 submit * @property {String} validateTrigger = [bind|submit|blur] 校验触发器方式 默认 submit
* @value bind 发生变化时触发 * @value bind 发生变化时触发
* @value submit 提交时触发 * @value submit 提交时触发
* @value blur 失去焦点时触发
* @property {String} labelPosition = [top|left] label 位置 默认 left * @property {String} labelPosition = [top|left] label 位置 默认 left
* @value top 顶部显示 label * @value top 顶部显示 label
* @value left 左侧显示 label * @value left 左侧显示 label
...@@ -51,25 +62,34 @@ ...@@ -51,25 +62,34 @@
* @value toast 错误信息toast显示 * @value toast 错误信息toast显示
* @value modal 错误信息modal显示 * @value modal 错误信息modal显示
* @event {Function} submit 提交时触发 * @event {Function} submit 提交时触发
* @event {Function} validate 校验结果发生变化触发
*/ */
export default { export default {
name: 'uniForms', name: 'uniForms',
components: {}, emits: ['validate', 'submit'],
emits:['input','reset','validate','submit'], options: {
virtualHost: true
},
props: { props: {
// 即将弃用 // 即将弃用
value: { value: {
type: Object, type: Object,
default () { default () {
return {}; return null;
} }
}, },
// 替换 value 属性 // vue3 替换 value 属性
modelValue: { modelValue: {
type: Object, type: Object,
default () { default () {
return {}; return null;
}
},
// 1.4.0 开始将不支持 v-model ,且废弃 value 和 modelValue
model: {
type: Object,
default () {
return null;
} }
}, },
// 表单校验规则 // 表单校验规则
...@@ -79,58 +99,68 @@ ...@@ -79,58 +99,68 @@
return {}; return {};
} }
}, },
// 校验触发器方式,默认 关闭 //校验错误信息提示方式 默认 undertext 取值 [undertext|toast|modal]
errShowType: {
type: String,
default: 'undertext'
},
// 校验触发器方式 默认 bind 取值 [bind|submit]
validateTrigger: { validateTrigger: {
type: String, type: String,
default: '' default: 'submit'
}, },
// label 位置,可选值 top/left // label 位置,默认 left 取值 top/left
labelPosition: { labelPosition: {
type: String, type: String,
default: 'left' default: 'left'
}, },
// label 宽度,单位 px // label 宽度
labelWidth: { labelWidth: {
type: [String, Number], type: [String, Number],
default: '' default: ''
}, },
// label 居中方式,可选值 left/center/right // label 居中方式,默认 left 取值 left/center/right
labelAlign: { labelAlign: {
type: String, type: String,
default: 'left' default: 'left'
}, },
errShowType: {
type: String,
default: 'undertext'
},
border: { border: {
type: Boolean, type: Boolean,
default: false default: false
} }
}, },
provide() {
return {
uniForm: this
}
},
data() { data() {
return { return {
formData: {} // 表单本地值的记录,不应该与传如的值进行关联
formData: {},
formRules: {}
}; };
}, },
computed: { computed: {
dataValue() { // 计算数据源变化的
if (JSON.stringify(this.modelValue) === '{}') { localData() {
return this.value const localVal = this.model || this.modelValue || this.value
} else { if (localVal) {
return this.modelValue return deepCopy(localVal)
} }
return {}
} }
}, },
watch: { watch: {
rules(newVal) { // 监听数据变化 ,暂时不使用,需要单独赋值
// 如果规则发生变化,要初始化组件 // localData: {},
this.init(newVal); // 监听规则变化
rules: {
handler: function(val, oldVal) {
this.setRules(val)
}, },
labelPosition() { deep: true,
this.childrens.forEach(vm => { immediate: true
vm.init()
})
} }
}, },
created() { created() {
...@@ -156,137 +186,132 @@ ...@@ -156,137 +186,132 @@
} }
// #endif // #endif
// 存放watch 监听数组 // 子组件实例数组
this.unwatchs = []; this.childrens = []
// 存放子组件数组 // TODO 兼容旧版 uni-data-picker ,新版本中无效,只是避免报错
this.childrens = []; this.inputChildrens = []
// 存放 easyInput 组件 this.setRules(this.rules)
this.inputChildrens = [];
// 存放 dataCheckbox 组件
this.checkboxChildrens = [];
// 存放规则
this.formRules = [];
this.init(this.rules);
}, },
// mounted() {
// this.init(this.rules)
// },
methods: { methods: {
init(formRules) {
// 判断是否有规则
if (Object.keys(formRules).length === 0) {
this.formData = this.dataValue
return
};
this.formRules = formRules;
this.validator = new Validator(formRules);
this.registerWatch();
},
// 监听 watch
registerWatch() {
// 取消监听,避免多次调用 init 重复执行 $watch
this.unwatchs.forEach(v => v());
this.childrens.forEach((v) => {
v.init()
})
// watch 每个属性 ,需要知道具体那个属性发变化
Object.keys(this.dataValue).forEach(key => {
let watch = this.$watch(
'dataValue.' + key,
value => {
if (!value) return
// 如果是对象 ,则平铺内容
if (value.toString() === '[object Object]') {
for (let i in value) {
let name = `${key}[${i}]`;
this.formData[name] = this._getValue(name, value[i]);
}
} else {
this.formData[key] = this._getValue(key, value);
}
},
{
deep: true,
immediate: true
}
);
this.unwatchs.push(watch);
});
},
/** /**
* 公开给用户使用 * 外部调用方法
* 设置验规则 * 设置规则 ,主要用于小程序自定义检验规则
* @param {Object} formRules * @param {Array} rules 规则源数据
*/ */
setRules(formRules) { setRules(rules) {
this.init(formRules); // TODO 有可能子组件合并规则的时机比这个要早,所以需要合并对象 ,而不是直接赋值,可能会被覆盖
this.formRules = Object.assign({}, this.formRules, rules)
// 初始化校验函数
this.validator = new Validator(rules);
}, },
/** /**
* 公开给用户使用 * 外部调用方法
* 设置自定义表单组件 value 值 * 设置数据,用于设置表单数据,公开给用户使用 , 不支持在动态表单中使用
* @param {String} name 字段名称 * @param {Object} key
* @param {String} value 字段值 * @param {Object} value
*/ */
setValue(name, value, callback) { setValue(key, value) {
let example = this.childrens.find(child => child.name === name); let example = this.childrens.find(child => child.name === key);
if (!example) return null; if (!example) return null;
value = this._getValue(example.name, value); this.formData[key] = getValue(key, value, (this.formRules[key] && this.formRules[key].rules) || [])
this.formData[name] = value; return example.onFieldChange(this.formData[key]);
example.val = value;
return example.triggerCheck(value, callback);
}, },
/** /**
* 表单重置 * 外部调用方法
* @param {Object} event * 手动提交校验表单
* 对整个表单进行校验的方法,参数为一个回调函数。
* @param {Array} keepitem 保留不参与校验的字段
* @param {type} callback 方法回调
*/ */
resetForm(event) { validate(keepitem, callback) {
return this.checkAll(this.formData, keepitem, callback);
},
/**
* 外部调用方法
* 部分表单校验
* @param {Array|String} props 需要校验的字段
* @param {Function} 回调函数
*/
validateField(props = [], callback) {
props = [].concat(props);
let invalidFields = {};
this.childrens.forEach(item => { this.childrens.forEach(item => {
item.errMsg = ''; const name = realName(item.name)
const inputComp = this.inputChildrens.find(child => child.rename === item.name); if (props.indexOf(name) !== -1) {
if (inputComp) { invalidFields = Object.assign({}, invalidFields, {
inputComp.errMsg = ''; [name]: this.formData[name]
// fix by mehaotian 不触发其他组件的 setValue });
inputComp.is_reset = true
inputComp.$emit('input', inputComp.multiple ? [] : '');
inputComp.$emit('update:modelValue', inputComp.multiple ? [] : '');
} }
}); });
return this.checkAll(invalidFields, [], callback);
},
/**
* 外部调用方法
* 移除表单项的校验结果。传入待移除的表单项的 prop 属性或者 prop 组成的数组,如不传则移除整个表单的校验结果
* @param {Array|String} props 需要移除校验的字段 ,不填为所有
*/
clearValidate(props = []) {
props = [].concat(props);
this.childrens.forEach(item => { this.childrens.forEach(item => {
if (item.name) { if (props.length === 0) {
this.formData[item.name] = this._getValue(item.name, ''); item.errMsg = '';
} else {
const name = realName(item.name)
if (props.indexOf(name) !== -1) {
item.errMsg = '';
}
} }
}); });
this.$emit('reset', event);
}, },
/** /**
* 触发表单校验,通过 @validate 获取 * 外部调用方法 ,即将废弃
* @param {Object} validate * 手动提交校验表单
* 对整个表单进行校验的方法,参数为一个回调函数。
* @param {Array} keepitem 保留不参与校验的字段
* @param {type} callback 方法回调
*/ */
validateCheck(validate) { submit(keepitem, callback, type) {
if (validate === null) validate = null; for (let i in this.dataValue) {
this.$emit('validate', validate); const itemData = this.childrens.find(v => v.name === i);
if (itemData) {
if (this.formData[i] === undefined) {
this.formData[i] = this._getValue(i, this.dataValue[i]);
}
}
}
if (!type) {
console.warn('submit 方法即将废弃,请使用validate方法代替!');
}
return this.checkAll(this.formData, keepitem, callback, 'submit');
}, },
/**
* 校验所有或者部分表单 // 校验所有
*/ async checkAll(invalidFields, keepitem, callback, type) {
async validateAll(invalidFields, type, keepitem, callback) { // 不存在校验规则 ,则停止校验流程
if (!this.validator) return
let childrens = [] let childrens = []
// 处理参与校验的item实例
for (let i in invalidFields) { for (let i in invalidFields) {
const item = this.childrens.find(v => v.name === i) const item = this.childrens.find(v => realName(v.name) === i)
if (item) { if (item) {
childrens.push(item) childrens.push(item)
} }
} }
// 如果validate第一个参数是funciont ,那就走回调
if (!callback && typeof keepitem === 'function') { if (!callback && typeof keepitem === 'function') {
callback = keepitem; callback = keepitem;
} }
let promise; let promise;
// 如果不存在回调,那么使用 Promise 方式返回
if (!callback && typeof callback !== 'function' && Promise) { if (!callback && typeof callback !== 'function' && Promise) {
promise = new Promise((resolve, reject) => { promise = new Promise((resolve, reject) => {
callback = function(valid, invalidFields) { callback = function(valid, invalidFields) {
...@@ -296,47 +321,39 @@ ...@@ -296,47 +321,39 @@
} }
let results = []; let results = [];
let newFormData = {}; // 避免引用错乱 ,建议拷贝对象处理
if (this.validator) { let tempFormData = JSON.parse(JSON.stringify(invalidFields))
for (let key in childrens) { // 所有子组件参与校验,使用 for 可以使用 awiat
const child = childrens[key]; for (let i in childrens) {
let name = child.isArray ? child.arrayField : child.name; const child = childrens[i]
if (child.isArray) { let name = realName(child.name);
if (child.name.indexOf('[') !== -1 && child.name.indexOf(']') !== -1) { const result = await child.onFieldChange(tempFormData[name]);
const fieldData = child.name.split('[');
const fieldName = fieldData[0];
const fieldValue = fieldData[1].replace(']', '');
if (!newFormData[fieldName]) {
newFormData[fieldName] = {};
}
newFormData[fieldName][fieldValue] = this._getValue(name, invalidFields[name]);
}
} else {
newFormData[name] = this._getValue(name, invalidFields[name]);
}
const result = await child.triggerCheck(invalidFields[name], true);
if (result) { if (result) {
results.push(result); results.push(result);
// toast ,modal 只需要执行第一次就可以
if (this.errShowType === 'toast' || this.errShowType === 'modal') break; if (this.errShowType === 'toast' || this.errShowType === 'modal') break;
} }
} }
} else {
newFormData = invalidFields
}
if (Array.isArray(results)) { if (Array.isArray(results)) {
if (results.length === 0) results = null; if (results.length === 0) results = null;
} }
if (Array.isArray(keepitem)) { if (Array.isArray(keepitem)) {
keepitem.forEach(v => { keepitem.forEach(v => {
newFormData[v] = this.dataValue[v]; let vName = realName(v);
let value = getDataValue(v, this.localData)
if (value !== undefined) {
tempFormData[vName] = value
}
}); });
} }
// TODO submit 即将废弃
if (type === 'submit') { if (type === 'submit') {
this.$emit('submit', { this.$emit('submit', {
detail: { detail: {
value: newFormData, value: tempFormData,
errors: results errors: results
} }
}); });
...@@ -344,129 +361,37 @@ ...@@ -344,129 +361,37 @@
this.$emit('validate', results); this.$emit('validate', results);
} }
callback && typeof callback === 'function' && callback(results, newFormData); // const resetFormData = rawData(tempFormData, this.localData, this.name)
let resetFormData = {}
resetFormData = rawData(tempFormData, this.name)
callback && typeof callback === 'function' && callback(results, resetFormData);
if (promise && callback) { if (promise && callback) {
return promise; return promise;
} else { } else {
return null; return null;
} }
},
submitForm() {},
/**
* 外部调用方法
* 手动提交校验表单
* 对整个表单进行校验的方法,参数为一个回调函数。
*/
submit(keepitem, callback, type) {
for (let i in this.dataValue) {
const itemData = this.childrens.find(v => v.name === i);
if (itemData) {
if (this.formData[i] === undefined) {
this.formData[i] = this._getValue(i, this.dataValue[i]);
}
}
}
if (!type) {
console.warn('submit 方法即将废弃,请使用validate方法代替!');
}
return this.validateAll(this.formData, 'submit', keepitem, callback);
},
/**
* 外部调用方法
* 校验表单
* 对整个表单进行校验的方法,参数为一个回调函数。
*/
validate(keepitem, callback) {
return this.submit(keepitem, callback, true);
}, },
/** /**
* 部分表单校验 * 返回validate事件
* @param {Object} props * @param {Object} result
* @param {Object} cb
*/ */
validateField(props, callback) { validateCheck(result) {
props = [].concat(props); this.$emit('validate', result);
let invalidFields = {};
this.childrens.forEach(item => {
if (props.indexOf(item.name) !== -1) {
invalidFields = Object.assign({}, invalidFields, {
[item.name]: this.formData[item.name]
});
}
});
return this.validateAll(invalidFields, 'submit', [], callback);
}, },
_getValue: getValue,
/** _isRequiredField: isRequiredField,
* 对整个表单进行重置,将所有字段值重置为初始值并移除校验结果 _setDataValue: setDataValue,
*/ _getDataValue: getDataValue,
resetFields() { _realName: realName,
this.resetForm(); _isRealName: isRealName,
}, _isEqual: isEqual
/**
* 移除表单项的校验结果。传入待移除的表单项的 prop 属性或者 prop 组成的数组,如不传则移除整个表单的校验结果
*/
clearValidate(props) {
props = [].concat(props);
this.childrens.forEach(item => {
const inputComp = this.inputChildrens.find(child => child.rename === item.name);
if (props.length === 0) {
item.errMsg = '';
if (inputComp) {
inputComp.errMsg = '';
}
} else {
if (props.indexOf(item.name) !== -1) {
item.errMsg = '';
if (inputComp) {
inputComp.errMsg = '';
}
}
}
});
},
/**
* 把 value 转换成指定的类型
* @param {Object} key
* @param {Object} value
*/
_getValue(key, value) {
const rules = (this.formRules[key] && this.formRules[key].rules) || [];
const isRuleNum = rules.find(val => val.format && this.type_filter(val.format));
const isRuleBool = rules.find(val => (val.format && val.format === 'boolean') || val.format === 'bool');
// 输入值为 number
if (isRuleNum) {
value = isNaN(value) ? value : value === '' || value === null ? null : Number(value);
}
// 简单判断真假值
if (isRuleBool) {
value = !value ? false : true;
}
return value;
},
/**
* 过滤数字类型
* @param {Object} format
*/
type_filter(format) {
return format === 'int' || format === 'double' || format === 'number' || format === 'timestamp';
}
} }
}; };
</script> </script>
<style lang="scss" > <style lang="scss">
.uni-forms { .uni-forms {}
// overflow: hidden;
// padding: 10px 15px;
}
.uni-forms--top {
// padding: 10px 15px;
// padding-top: 22px;
}
</style> </style>
{ {
"id": "uni-forms", "id": "uni-forms",
"displayName": "uni-forms 表单", "displayName": "uni-forms 表单",
"version": "1.3.2", "version": "1.4.8",
"description": "由输入框、选择器、单选框、多选框等控件组成,用以收集、校验、提交数据", "description": "由输入框、选择器、单选框、多选框等控件组成,用以收集、校验、提交数据",
"keywords": [ "keywords": [
"uni-ui", "uni-ui",
...@@ -17,11 +17,7 @@ ...@@ -17,11 +17,7 @@
"directories": { "directories": {
"example": "../../temps/example_temps" "example": "../../temps/example_temps"
}, },
"dcloudext": { "dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": { "sale": {
"regular": { "regular": {
"price": "0.00" "price": "0.00"
...@@ -38,7 +34,8 @@ ...@@ -38,7 +34,8 @@
"data": "无", "data": "无",
"permissions": "无" "permissions": "无"
}, },
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
}, },
"uni_modules": { "uni_modules": {
"dependencies": [ "dependencies": [
...@@ -74,7 +71,8 @@ ...@@ -74,7 +71,8 @@
"阿里": "y", "阿里": "y",
"百度": "y", "百度": "y",
"字节跳动": "y", "字节跳动": "y",
"QQ": "y" "QQ": "y",
"京东": "u"
}, },
"快应用": { "快应用": {
"华为": "u", "华为": "u",
......
## 1.0.14(2022-09-16)
- 修改 配置项`isAdmin`默认值为`false`
## 1.0.13(2022-09-16)
- 新增 管理员注册页面
- 新增 配置项`isAdmin`区分是否为管理端
- 新增 登录成功后自动跳转;跳转优先级:路由携带(`uniIdRedirectUrl`参数) > 返回上一路由 > 跳转首页
- uni-id-co 优化 注册管理员时管理员存在提示文案
## 1.0.12(2022-09-07) ## 1.0.12(2022-09-07)
- 修复 getSupportedLoginType判断是否支持微信公众号、PC网页微信扫码登录方式报错的Bug - 修复 getSupportedLoginType判断是否支持微信公众号、PC网页微信扫码登录方式报错的Bug
- 优化 适配pc端样式 - 优化 适配pc端样式
......
import config from '../config'
const uniIdCo = uniCloud.importObject("uni-id-co") const uniIdCo = uniCloud.importObject("uni-id-co")
export default { export default {
async logout() { async logout() {
...@@ -5,7 +6,7 @@ export default { ...@@ -5,7 +6,7 @@ export default {
uni.removeStorageSync('uni_id_token'); uni.removeStorageSync('uni_id_token');
uni.setStorageSync('uni_id_token_expired', 0) uni.setStorageSync('uni_id_token_expired', 0)
uni.redirectTo({ uni.redirectTo({
url: '/uni_modules/uni-id-pages/pages/login/login-withoutpwd', url: config.isAdmin ? '/uni_modules/uni-id-pages/pages/login/login-withpwd': '/uni_modules/uni-id-pages/pages/login/login-withoutpwd',
}); });
uni.$emit('uni-id-pages-logout') uni.$emit('uni-id-pages-logout')
}, },
......
...@@ -4,6 +4,7 @@ let mixin = { ...@@ -4,6 +4,7 @@ let mixin = {
data() { data() {
return { return {
config, config,
uniIdRedirectUrl: '',
isMounted: false isMounted: false
} }
}, },
...@@ -38,6 +39,10 @@ let mixin = { ...@@ -38,6 +39,10 @@ let mixin = {
}, 'weixin') }, 'weixin')
}) })
} }
if (e.uniIdRedirectUrl) {
this.uniIdRedirectUrl = decodeURIComponent(e.uniIdRedirectUrl)
}
}, },
computed: { computed: {
needAgreements() { needAgreements() {
...@@ -70,7 +75,10 @@ let mixin = { ...@@ -70,7 +75,10 @@ let mixin = {
}, },
methods: { methods: {
loginSuccess(e) { loginSuccess(e) {
loginSuccess(e) loginSuccess({
...e,
uniIdRedirectUrl: this.uniIdRedirectUrl
})
} }
} }
} }
......
import pagesJson from '@/pages.json'
export default function(e = {}) { export default function(e = {}) {
const { const {
showToast = true, toastText = '登录成功', autoBack = true showToast = true, toastText = '登录成功', autoBack = true, uniIdRedirectUrl = ''
} = e } = e
console.log({ console.log({
toastText, toastText,
...@@ -23,6 +25,11 @@ export default function(e = {}) { ...@@ -23,6 +25,11 @@ export default function(e = {}) {
} }
}) })
console.log('判断需要返回几层:',pages, delta); console.log('判断需要返回几层:',pages, delta);
if (uniIdRedirectUrl) {
return uni.reLaunch({
url: uniIdRedirectUrl
})
}
// #ifdef H5 // #ifdef H5
if(e.loginType == 'weixin'){ if(e.loginType == 'weixin'){
console.log('window.history',window.history); console.log('window.history',window.history);
...@@ -30,6 +37,13 @@ export default function(e = {}) { ...@@ -30,6 +37,13 @@ export default function(e = {}) {
} }
// #endif // #endif
if (delta) {
const page = pagesJson.pages[0]
return uni.reLaunch({
url: `/${page.path}`
})
}
uni.navigateBack({ uni.navigateBack({
delta delta
}) })
......
...@@ -46,8 +46,8 @@ ...@@ -46,8 +46,8 @@
watch: { watch: {
src:{ src:{
handler(src) { handler(src) {
console.log(src); // console.log(src);
console.log(src.substring(0, 8)); // console.log(src.substring(0, 8));
if (src&&src.substring(0, 8) == "cloud://") { if (src&&src.substring(0, 8) == "cloud://") {
uniCloud.getTempFileURL({ uniCloud.getTempFileURL({
fileList: [src] fileList: [src]
......
...@@ -5,6 +5,7 @@ export default { ...@@ -5,6 +5,7 @@ export default {
登录类型 未列举到的或运行环境不支持的,将被自动隐藏。 登录类型 未列举到的或运行环境不支持的,将被自动隐藏。
如果需要在不同平台有不同的配置,直接用条件编译即可 如果需要在不同平台有不同的配置,直接用条件编译即可
*/ */
"isAdmin": false, // 区分管理端与用户端
"loginTypes": [ "loginTypes": [
// "qq", // "qq",
// "xiaomi", // "xiaomi",
......
{ {
"id": "uni-id-pages", "id": "uni-id-pages",
"displayName": "uni-id-pages", "displayName": "uni-id-pages",
"version": "1.0.10", "version": "1.0.14",
"description": "云端一体简单、统一、可扩展的用户中心页面模版", "description": "云端一体简单、统一、可扩展的用户中心页面模版",
"keywords": [ "keywords": [
"用户管理", "用户管理",
......
...@@ -21,11 +21,12 @@ ...@@ -21,11 +21,12 @@
<button class="uni-btn" type="primary" @click="pwdLogin">登录</button> <button class="uni-btn" type="primary" @click="pwdLogin">登录</button>
<!-- 忘记密码 --> <!-- 忘记密码 -->
<view class="link-box"> <view class="link-box">
<view> <view v-if="!config.isAdmin">
<text class="forget">忘记了?</text> <text class="forget">忘记了?</text>
<text class="link" @click="toRetrievePwd">找回密码</text> <text class="link" @click="toRetrievePwd">找回密码</text>
</view> </view>
<text class="link" @click="toRegister">注册账号</text> <text class="link" @click="toRegister">{{config.isAdmin ? '注册管理员账号': '注册账号'}}</text>
<!-- <text class="link" @click="toRegister" v-if="!config.isAdmin">注册账号</text> -->
</view> </view>
<!-- 悬浮登录方式组件 --> <!-- 悬浮登录方式组件 -->
<uni-id-pages-fab-login ref="uniFabLogin"></uni-id-pages-fab-login> <uni-id-pages-fab-login ref="uniFabLogin"></uni-id-pages-fab-login>
...@@ -33,13 +34,13 @@ ...@@ -33,13 +34,13 @@
</template> </template>
<script> <script>
import mixin from '@/uni_modules/uni-id-pages/common/login-page.mixin.js'; import mixin from '@/uni_modules/uni-id-pages/common/login-page.mixin.js';
const uniIdCo = uniCloud.importObject("uni-id-co",{ const uniIdCo = uniCloud.importObject("uni-id-co",{
errorOptions:{ errorOptions:{
type:'toast' type:'toast'
} }
}) })
export default { export default {
mixins: [mixin], mixins: [mixin],
data() { data() {
return { return {
...@@ -129,36 +130,38 @@ ...@@ -129,36 +130,38 @@
}) })
}, },
/* 前往注册 */ /* 前往注册 */
toRegister(e) { toRegister() {
console.log(e);
uni.navigateTo({ uni.navigateTo({
url: '/uni_modules/uni-id-pages/pages/register/register' url: this.config.isAdmin ? '/uni_modules/uni-id-pages/pages/register/register-admin': '/uni_modules/uni-id-pages/pages/register/register',
}) fail(e) {
console.error(e);
} }
})
} }
} }
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@import "@/uni_modules/uni-id-pages/common/login-page.scss"; @import "@/uni_modules/uni-id-pages/common/login-page.scss";
@media screen and (min-width: 690px) { @media screen and (min-width: 690px) {
} }
.forget{ .forget{
font-size: 12px; font-size: 12px;
color: #8a8f8b; color: #8a8f8b;
} }
.link-box { .link-box {
/* #ifndef APP-NVUE */ /* #ifndef APP-NVUE */
display: flex; display: flex;
/* #endif */ /* #endif */
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
margin-top: 20px; margin-top: 20px;
} }
.link { .link {
font-size: 12px; font-size: 12px;
} }
</style> </style>
<!-- 创建超级管理员 -->
<template>
<view class="uni-content">
<match-media :min-width="690">
<view class="login-logo">
<image :src="logo"></image>
</view>
<!-- 顶部文字 -->
<text class="title title-box">创建超级管理员</text>
</match-media>
<uni-forms ref="form" :value="formData" :rules="rules" validate-trigger="submit" err-show-type="toast">
<uni-forms-item name="username" required>
<uni-easyinput :inputBorder="false" :focus="focusUsername" @blur="focusUsername = false"
class="input-box" placeholder="请输入用户名" v-model="formData.username" trim="both" />
</uni-forms-item>
<uni-forms-item name="nickname">
<uni-easyinput :inputBorder="false" :focus="focusNickname" @blur="focusNickname = false" class="input-box" placeholder="请输入用户昵称" v-model="formData.nickname"
trim="both" />
</uni-forms-item>
<uni-forms-item name="password" v-model="formData.password" required>
<uni-easyinput :inputBorder="false" :focus="focusPassword" @blur="focusPassword = false"
class="input-box" maxlength="20" :placeholder="'请输入' + (config.passwordStrength == 'weak'?'6':'8') + '-16位密码'" type="password"
v-model="formData.password" trim="both" />
</uni-forms-item>
<uni-forms-item name="password2" v-model="formData.password2" required>
<uni-easyinput :inputBorder="false" :focus="focusPassword2" @blur="focusPassword2 =false"
class="input-box" placeholder="再次输入密码" maxlength="20" type="password" v-model="formData.password2"
trim="both" />
</uni-forms-item>
<uni-forms-item>
<uni-captcha ref="captcha" scene="register" v-model="formData.captcha" />
</uni-forms-item>
<uni-id-pages-agreements scope="register" ref="agreements" ></uni-id-pages-agreements>
<button class="uni-btn" type="primary" @click="submit">注册</button>
<button @click="navigateBack" class="register-back">返回</button>
<match-media :min-width="690">
<view class="link-box">
<text class="link" @click="toLogin">已有账号?点此登录</text>
</view>
</match-media>
</uni-forms>
</view>
</template>
<script>
import rules from './validator.js';
import mixin from '@/uni_modules/uni-id-pages/common/login-page.mixin.js';
import config from '@/uni_modules/uni-id-pages/config.js'
const uniIdCo = uniCloud.importObject("uni-id-co", {customUI: true})
export default {
mixins: [mixin],
data() {
return {
formData: {
username: "",
nickname: "",
password: "",
password2: "",
captcha: ""
},
rules,
focusUsername:false,
focusNickname:false,
focusPassword:false,
focusPassword2:false,
logo: "/static/logo.png"
}
},
onReady() {
this.$refs.form.setRules(this.rules)
},
onShow() {
// #ifdef H5
document.onkeydown = event => {
var e = event || window.event;
if (e && e.keyCode == 13) { //回车键的键值为13
this.submit()
}
};
// #endif
},
methods: {
/**
* 触发表单提交
*/
submit() {
this.$refs.form.validate().then((res) => {
if(this.formData.captcha.length != 4){
this.$refs.captcha.focusCaptchaInput = true
return uni.showToast({
title: '请输入验证码',
icon: 'none'
});
}
if (this.needAgreements && !this.agree) {
return this.$refs.agreements.popup(()=>{
this.submitForm(res)
})
}
this.submitForm(res)
}).catch((errors) => {
let key = errors[0].key
key = key.replace(key[0], key[0].toUpperCase())
console.log(key);
this['focus'+key] = true
})
},
submitForm(params) {
uniIdCo.registerAdmin(this.formData).then(e => {
console.log(e);
uni.navigateBack()
})
.catch(e => {
console.log(e);
console.log(e.message);
//更好的体验:登录错误,直接刷新验证码
this.$refs.captcha.getImageCaptcha()
uni.showModal({
title: '提示',
content: e.errMsg || `创建失败: ${e.errCode}`,
showCancel: false
})
})
},
navigateBack() {
uni.navigateBack()
},
toLogin() {
uni.navigateTo({
url: '/uni_modules/uni-id-pages/pages/login/login-withpwd'
})
},
registerByEmail() {
uni.navigateTo({
url: '/uni_modules/uni-id-pages/pages/register/register-by-email'
})
}
}
}
</script>
<style lang="scss">
@import "@/uni_modules/uni-id-pages/common/login-page.scss";
@media screen and (max-width: 690px) {
.uni-content{
margin-top: 15px;
height: 100%;
background-color: #fff;
}
}
@media screen and (min-width: 690px) {
.uni-content{
padding: 30px 40px 60px;
}
.link-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: space-between;
margin-top: 10px;
}
.link {
font-size: 12px;
}
}
.uni-content ::v-deep .uni-forms-item__label {
position: absolute;
left: -15px;
}
button {
margin-top: 15px;
}
</style>
...@@ -9,6 +9,8 @@ ...@@ -9,6 +9,8 @@
</uni-list-item> </uni-list-item>
<uni-list-item class="item" @click="bindMobile" title="手机号" :rightText="userInfo.mobile||'未绑定'" link> <uni-list-item class="item" @click="bindMobile" title="手机号" :rightText="userInfo.mobile||'未绑定'" link>
</uni-list-item> </uni-list-item>
<uni-list-item v-if="userInfo.email" class="item" title="电子邮箱" :rightText="userInfo.email">
</uni-list-item>
<uni-list-item v-if="hasPwd" class="item" @click="changePassword" title="修改密码" link> <uni-list-item v-if="hasPwd" class="item" @click="changePassword" title="修改密码" link>
</uni-list-item> </uni-list-item>
</uni-list> </uni-list>
...@@ -87,7 +89,7 @@ ...@@ -87,7 +89,7 @@
uni.showLoading({ uni.showLoading({
mask: true mask: true
}); });
usersTable.where("'_id' == $cloudEnv_uid").field('mobile,nickname').get().then(res => { usersTable.where("'_id' == $cloudEnv_uid").field('mobile,nickname,email').get().then(res => {
console.log({res}); console.log({res});
this.userInfo = res.result.data[0] this.userInfo = res.result.data[0]
console.log('this.userInfo', this.userInfo); console.log('this.userInfo', this.userInfo);
......
...@@ -36,7 +36,9 @@ const CAPTCHA_SCENE = { ...@@ -36,7 +36,9 @@ const CAPTCHA_SCENE = {
LOGIN_BY_PWD: 'login-by-pwd', LOGIN_BY_PWD: 'login-by-pwd',
LOGIN_BY_SMS: 'login-by-sms', LOGIN_BY_SMS: 'login-by-sms',
RESET_PWD_BY_SMS: 'reset-pwd-by-sms', RESET_PWD_BY_SMS: 'reset-pwd-by-sms',
RESET_PWD_BY_EMAIL: 'reset-pwd-by-email',
SEND_SMS_CODE: 'send-sms-code', SEND_SMS_CODE: 'send-sms-code',
SEND_EMAIL_CODE: 'send-email-code',
BIND_MOBILE_BY_SMS: 'bind-mobile-by-sms' BIND_MOBILE_BY_SMS: 'bind-mobile-by-sms'
} }
...@@ -45,6 +47,7 @@ const LOG_TYPE = { ...@@ -45,6 +47,7 @@ const LOG_TYPE = {
LOGIN: 'login', LOGIN: 'login',
REGISTER: 'register', REGISTER: 'register',
RESET_PWD_BY_SMS: 'reset-pwd', RESET_PWD_BY_SMS: 'reset-pwd',
RESET_PWD_BY_EMAIL: 'reset-pwd',
BIND_MOBILE: 'bind-mobile', BIND_MOBILE: 'bind-mobile',
BIND_WEIXIN: 'bind-weixin', BIND_WEIXIN: 'bind-weixin',
BIND_QQ: 'bind-qq', BIND_QQ: 'bind-qq',
...@@ -58,6 +61,13 @@ const SMS_SCENE = { ...@@ -58,6 +61,13 @@ const SMS_SCENE = {
BIND_MOBILE_BY_SMS: 'bind-mobile-by-sms' BIND_MOBILE_BY_SMS: 'bind-mobile-by-sms'
} }
const EMAIL_SCENE = {
REGISTER: 'register',
LOGIN_BY_EMAIL: 'login-by-email',
RESET_PWD_BY_EMAIL: 'reset-pwd-by-email',
BIND_EMAIL: 'bind-email'
}
module.exports = { module.exports = {
db, db,
dbCmd, dbCmd,
...@@ -68,5 +78,6 @@ module.exports = { ...@@ -68,5 +78,6 @@ module.exports = {
USER_STATUS, USER_STATUS,
CAPTCHA_SCENE, CAPTCHA_SCENE,
LOG_TYPE, LOG_TYPE,
SMS_SCENE SMS_SCENE,
EMAIL_SCENE
} }
...@@ -15,7 +15,8 @@ const middleware = require('./middleware/index') ...@@ -15,7 +15,8 @@ const middleware = require('./middleware/index')
const { const {
registerAdmin, registerAdmin,
registerUser registerUser,
registerUserByEmail
} = require('./module/register/index') } = require('./module/register/index')
const { const {
addUser, addUser,
...@@ -45,13 +46,15 @@ const { ...@@ -45,13 +46,15 @@ const {
const { const {
updatePwd, updatePwd,
resetPwdBySms, resetPwdBySms,
resetPwdByEmail,
closeAccount, closeAccount,
getAccountInfo getAccountInfo
} = require('./module/account/index') } = require('./module/account/index')
const { const {
createCaptcha, createCaptcha,
refreshCaptcha, refreshCaptcha,
sendSmsCode sendSmsCode,
sendEmailCode
} = require('./module/verify/index') } = require('./module/verify/index')
const { const {
refreshToken, refreshToken,
...@@ -273,6 +276,17 @@ module.exports = { ...@@ -273,6 +276,17 @@ module.exports = {
* @returns * @returns
*/ */
registerUser, registerUser,
/**
* 通过邮箱+验证码注册用户
* @param {Object} params
* @param {String} params.email 邮箱
* @param {String} params.password 密码
* @param {String} params.nickname 昵称
* @param {String} params.code 邮箱验证码
* @param {String} params.inviteCode 邀请码
* @returns
*/
registerUserByEmail,
/** /**
* 用户名密码登录 * 用户名密码登录
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#login
...@@ -433,6 +447,16 @@ module.exports = { ...@@ -433,6 +447,16 @@ module.exports = {
* @returns {object} * @returns {object}
*/ */
resetPwdBySms, resetPwdBySms,
/**
* 通过邮箱验证码重置密码
* @param {object} params
* @param {string} params.email 邮箱
* @param {string} params.code 邮箱验证码
* @param {string} params.password 密码
* @param {string} params.captcha 图形验证码
* @returns {object}
*/
resetPwdByEmail,
/** /**
* 注销账户 * 注销账户
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#close-account * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#close-account
...@@ -470,6 +494,16 @@ module.exports = { ...@@ -470,6 +494,16 @@ module.exports = {
* @returns * @returns
*/ */
sendSmsCode, sendSmsCode,
/**
* 发送邮箱验证码
* @tutorial 需自行实现功能
* @param {Object} params
* @param {String} params.email 邮箱
* @param {String} params.captcha 图形验证码
* @param {String} params.scene 短信验证码使用场景
* @returns
*/
sendEmailCode,
/** /**
* 刷新token * 刷新token
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#refresh-token * @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#refresh-token
......
module.exports = { module.exports = {
updatePwd: require('./update-pwd'), updatePwd: require('./update-pwd'),
resetPwdBySms: require('./reset-pwd-by-sms'), resetPwdBySms: require('./reset-pwd-by-sms'),
resetPwdByEmail: require('./reset-pwd-by-email'),
closeAccount: require('./close-account'), closeAccount: require('./close-account'),
getAccountInfo: require('./get-account-info') getAccountInfo: require('./get-account-info')
} }
const {
ERROR
} = require('../../common/error')
const {
getNeedCaptcha,
verifyCaptcha
} = require('../../lib/utils/captcha')
const {
verifyEmailCode
} = require('../../lib/utils/verify-code')
const {
userCollection,
EMAIL_SCENE,
CAPTCHA_SCENE,
LOG_TYPE
} = require('../../common/constants')
const {
findUser
} = require('../../lib/utils/account')
const PasswordUtils = require('../../lib/utils/password')
/**
* 通过邮箱验证码重置密码
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#reset-pwd-by-email
* @param {object} params
* @param {string} params.email 邮箱
* @param {string} params.code 邮箱验证码
* @param {string} params.password 密码
* @param {string} params.captcha 图形验证码
* @returns {object}
*/
module.exports = async function (params = {}) {
const schema = {
email: 'email',
code: 'string',
password: 'password',
captcha: {
required: false,
type: 'string'
}
}
this.middleware.validate(params, schema)
const {
email,
code,
password,
captcha
} = params
const needCaptcha = await getNeedCaptcha.call(this, {
email,
type: LOG_TYPE.RESET_PWD_BY_EMAIL
})
if (needCaptcha) {
await verifyCaptcha.call(this, {
captcha,
scene: CAPTCHA_SCENE.RESET_PWD_BY_EMAIL
})
}
try {
// 验证手机号验证码,验证不通过时写入失败日志
await verifyEmailCode({
email,
code,
scene: EMAIL_SCENE.RESET_PWD_BY_EMAIL
})
} catch (error) {
await this.middleware.uniIdLog({
data: {
email
},
type: LOG_TYPE.RESET_PWD_BY_EMAIL,
success: false
})
throw error
}
// 根据手机号查找匹配的用户
const userMatched = await findUser.call(this, {
userQuery: {
email
},
authorizedApp: [this.getClientInfo().appId]
})
if (userMatched.length === 0) {
throw {
errCode: ERROR.ACCOUNT_NOT_EXISTS
}
} else if (userMatched.length > 1) {
throw {
errCode: ERROR.ACCOUNT_CONFLICT
}
}
const { _id: uid } = userMatched[0]
const {
passwordHash,
version
} = new PasswordUtils({
passwordSecret: this.config.passwordSecret
}).generatePasswordHash({
password
})
// 更新用户密码
await userCollection.doc(uid).update({
password: passwordHash,
password_secret_version: version,
valid_token_date: Date.now()
})
// 写入成功日志
await this.middleware.uniIdLog({
data: {
email
},
type: LOG_TYPE.RESET_PWD_BY_SMS
})
return {
errCode: 0
}
}
module.exports = { module.exports = {
registerUser: require('./register-user'), registerUser: require('./register-user'),
registerAdmin: require('./register-admin') registerAdmin: require('./register-admin'),
registerUserByEmail: require('./register-user-by-email')
} }
...@@ -38,7 +38,8 @@ module.exports = async function (params = {}) { ...@@ -38,7 +38,8 @@ module.exports = async function (params = {}) {
}).limit(1).get() }).limit(1).get()
if (getAdminRes.data.length > 0) { if (getAdminRes.data.length > 0) {
return { return {
errCode: ERROR.ADMIN_EXISTS errCode: ERROR.ADMIN_EXISTS,
errMsg: this.t('uni-id-admin-exists')
} }
} }
const { const {
......
const {
postRegister,
preRegisterWithPassword
} = require('../../lib/utils/register')
const {
verifyCaptcha
} = require('../../lib/utils/captcha')
const {
CAPTCHA_SCENE,
EMAIL_SCENE,
LOG_TYPE
} = require('../../common/constants')
const {
verifyEmailCode
} = require('../../lib/utils/verify-code')
/**
* 通过邮箱+验证码注册普通用户
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#register-user-by-email
* @param {Object} params
* @param {String} params.email 邮箱
* @param {String} params.password 密码
* @param {String} params.nickname 昵称
* @param {String} params.code 邮箱验证码
* @param {String} params.inviteCode 邀请码
* @returns
*/
module.exports = async function (params = {}) {
const schema = {
email: 'email',
password: 'password',
nickname: {
required: false,
type: 'nickname'
},
code: 'string',
inviteCode: {
required: false,
type: 'string'
}
}
this.middleware.validate(params, schema)
const {
email,
password,
nickname,
code,
inviteCode
} = params
try {
// 验证邮箱验证码,验证不通过时写入失败日志
await verifyEmailCode({
email,
code,
scene: EMAIL_SCENE.REGISTER
})
} catch (error) {
await this.middleware.uniIdLog({
data: {
email
},
type: LOG_TYPE.REGISTER,
success: false
})
throw error
}
const {
user,
extraData
} = await preRegisterWithPassword.call(this, {
user: {
email
},
password
})
return postRegister.call(this, {
user,
extraData: {
...extraData,
nickname,
email_confirmed: 1
},
inviteCode
})
}
const {
verifyCaptcha
} = require('../../lib/utils/captcha')
const {
EMAIL_SCENE
} = require('../../common/constants')
const {
ERROR
} = require('../../common/error')
/** /**
* 发送邮箱验证码,可用于登录、注册、绑定邮箱、修改密码等操作 * 发送邮箱验证码,可用于登录、注册、绑定邮箱、修改密码等操作
* @tutorial * @tutorial
* @param {Object} params * @param {Object} params
* @param {String} params.email 邮箱 * @param {String} params.email 邮箱
* @param {String} params.captcha 图形验证码
* @param {String} params.scene 使用场景 * @param {String} params.scene 使用场景
* @returns * @returns
*/ */
module.exports = async function (params = {}) { module.exports = async function (params = {}) {
// 此接口暂未实现,欢迎向我们提交pr const schema = {
throw new Error('api[sendEmailCode] is not yet implemented') email: 'email',
captcha: 'string',
scene: 'string'
}
this.middleware.validate(params, schema)
const {
email,
captcha,
scene
} = params
if (!(Object.values(EMAIL_SCENE).includes(scene))) {
throw {
errCode: ERROR.INVALID_PARAM
}
}
await verifyCaptcha.call(this, {
scene: 'send-email-code',
captcha
})
// -- 测试代码
require('../../lib/utils/verify-code')
.setEmailVerifyCode.call(this, {
email,
code: '123456',
expiresIn: 180,
scene
})
return {
errCode: 'uni-id-invalid-mail-template',
errMsg: `已启动测试模式,直接使用:123456作为邮箱验证码即可。\n如果是正式项目,需自行实现发送邮件的相关功能`
}
// -- 测试代码
//发送邮件--需自行实现
} }
{ {
"name": "uni-id-co", "name": "uni-id-co",
"version": "1.0.12", "version": "1.0.13",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"keywords": [], "keywords": [],
......
...@@ -77,7 +77,7 @@ ...@@ -77,7 +77,7 @@
}, },
"network_model": { "network_model": {
"bsonType": "string", "bsonType": "string",
"description": "设备网络型号wifi/3G/4G/" "description": "设备网络型号wifi\/3G\/4G\/"
}, },
"window_width": { "window_width": {
"bsonType": "string", "bsonType": "string",
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
"bsonType": "object", "bsonType": "object",
"permission": { "permission": {
"update": "doc._id == auth.uid", "update": "doc._id == auth.uid",
"read": true "read": "doc._id == auth.uid"
}, },
"properties": { "properties": {
"_id": { "_id": {
......
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
## 1.0.3(2022-09-06)
- 修复 过期时间问题,容错 AccessToken 默认 fallback 逻辑,当微信服务器没有返回过期时间时设置为2小时后过期
## 1.0.2(2022-09-02)
- 新增 依赖数据表schema opendb-open-data
## 1.0.0(2022-08-22) ## 1.0.0(2022-08-22)
- 首次发布 - 首次发布
{ {
"id": "uni-open-bridge-common", "id": "uni-open-bridge-common",
"displayName": "uni-open-bridge-common", "displayName": "uni-open-bridge-common",
"version": "1.0.0", "version": "1.0.3",
"description": "统一接管微信等三方平台认证凭据", "description": "统一接管微信等三方平台认证凭据",
"keywords": [ "keywords": [
"uni-open-bridge-common", "uni-open-bridge-common",
......
...@@ -43,7 +43,7 @@ class AccessToken extends Storage { ...@@ -43,7 +43,7 @@ class AccessToken extends Storage {
const responseData = await WeixinServer[methodName](oauthConfig) const responseData = await WeixinServer[methodName](oauthConfig)
const duration = responseData.expires_in const duration = responseData.expires_in || (60 * 60 * 2)
delete responseData.expires_in delete responseData.expires_in
return { return {
......
...@@ -61,7 +61,7 @@ class DatabaseCache { ...@@ -61,7 +61,7 @@ class DatabaseCache {
value = this._serializeValue(value) value = this._serializeValue(value)
await this.collection.doc(key).set({ await this.collection.doc(key).set({
value, value,
expired: duration && duration !== -1 ? Date.now() + duration : -1 expired: duration && duration !== -1 ? Date.now() + (duration * 1000) : -1
}) })
} }
......
// 文档教程: https://uniapp.dcloud.net.cn/uniCloud/schema
{
"bsonType": "object",
"required": ["_id", "value"],
"properties": {
"_id": {
"bsonType": "string",
"description": "key,格式:uni-id:[dcloudAppid]:[platform]:[openid]:[access-token|user-access-token|session-key|encrypt-key-version|ticket]"
},
"value": {
"bsonType": "object",
"description": "字段_id对应的值"
},
"expired": {
"bsonType": "date",
"description": "过期时间"
}
}
}
## 0.5.1(2022-07-06)
- 修复 上版带出云函数不存在的Bug
- 升级 uni-admin 大于等于 1.9.0 务必更新至此版本。uni-admin 版本小于 1.9.0 请不要更新,历史版本在 Gitee 有发行版。后续 uni-admin 会集成升级中心
## 0.5.0(2022-07-05)
- 修复 版本列表默认显示全部版本的Bug
- 升级 uni-admin 1.9.0 务必更新至此版本。uni-admin 版本小于 1.9.0 请不要更新,后续 uni-admin 会集成升级中心
## 0.4.2(2021-12-07)
- 更新 优化 list 页面显示,修复 list 页面报错
## 0.4.1(2021-12-01)
- 修复 0.4.0版本带出来,发布新版时 appid、name 不会自动填充的Bug
## 0.4.0(2021-11-26)
- 更新 升级中心移除应用管理,现在由uni-admin接管。旧版本若没有应用管理请做升级处理
## 0.3.0(2021-11-18)
- 兼容 uni-admin 新版内置 $request 函数改动
## 0.2.2(2021-09-06)
- 解决 opendb-app-list表对应的schema名称冲突的问题
## 0.2.1(2021-09-03)
- 修复 一个在添加菜单时报错,createdate不与默认值匹配 的Bug
## 0.2.0(2021-08-25)
- 兼容vue3.0
## 0.1.9(2021-08-13)
- 更新 uni-forms使用validate校验字段
- 修复 报错dirty_data、create_date在数据库中并不存在
## 0.1.8(2021-08-09)
- 修复 默认配置项配置错误
## 0.1.7(2021-08-09)
- 移除测试时配置项
## 0.1.6(2021-08-09)
- 修复 修改版本信息时,上传时间丢失问题
## 0.1.5(2021-07-21)
- 更新 :value.sync 改为 :value 和 @update:value
## 0.1.4(2021-07-13)
- 修复 uni-easyinput去除输入字符长度限制
- 更新文档 关于 uni-id缺少配置信息 错误。请查看安装指引第13条
## 0.1.3(2021-06-15)
- 修复 wgt更新某些情况下获取数据错误
## 0.1.2(2021-06-04)
- 修复 上传包时根据平台筛选文件
- 更新 文档
## 0.1.1(2021-05-18)
- 更新uni-table中uni-tr组件的selectable属性为disabled
## 0.1.0(2021-04-07)
- 更新版本对比函数 compare
## 0.0.6(2021-04-01)
- 调整db_init.json
## 0.0.5(2021-03-25)
- 调整为uni_modules目录
- 升级中心后台管理系统拆分为 Admin 后台管理 和 前台检查更新(uni-upgrade-center-app)
// 表单校验规则由 schema2code 生成,不建议直接修改校验规则,而建议通过 schema2code 生成, 详情: https://uniapp.dcloud.net.cn/uniCloud/schema
const validator = {
"appid": {
"rules": [
{
"required": true
},
{
"format": "string"
}
],
"label": "AppID"
},
"name": {
"rules": [
{
"required": true
},
{
"format": "string"
}
],
"label": "应用名称"
},
"description": {
"rules": [
{
"required": true
},
{
"format": "string"
}
],
"label": "应用描述"
}
}
const enumConverter = {}
export { validator, enumConverter }
// 表单校验规则由 schema2code 生成,不建议直接修改校验规则,而建议通过 schema2code 生成, 详情: https://uniapp.dcloud.net.cn/uniCloud/schema
const validator = {
"appid": {
"rules": [{
"required": true
},
{
"format": "string"
}
],
"label": "AppID"
},
"name": {
"rules": [{
"format": "string"
}],
"label": "应用名称"
},
"title": {
"rules": [{
"format": "string"
}],
"label": "更新标题"
},
"contents": {
"rules": [{
"required": true
},
{
"format": "string"
}
],
"label": "更新内容"
},
"platform": {
"rules": [{
"required": true
},
/* 此处不校验数据类型,因为platform在发布app端是单选,在发布wgt时可能是多选
{
"format": "array"
}, */
{
"range": [{
"value": "Android",
"text": "安卓"
},
{
"value": "iOS",
"text": "苹果"
}
]
}
],
"label": "平台"
},
"type": {
"rules": [{
"required": true
}, {
"format": "string"
},
{
"range": [{
"value": "native_app",
"text": "原生App安装包"
},
{
"value": "wgt",
"text": "wgt资源包"
}
]
}
],
"label": "安装包类型"
},
"version": {
"rules": [{
"required": true
},
{
"format": "string"
}
],
"label": "版本号"
},
"min_uni_version": {
"rules": [{
"format": "string"
}],
"label": "原生App最低版本"
},
"url": {
"rules": [{
"required": true
}, {
"format": "string"
}],
"label": "包地址"
},
"stable_publish": {
"rules": [{
"format": "bool"
}],
"label": "上线发行"
},
"create_date": {
"rules": [{
"format": "timestamp"
}],
"label": "上传时间"
},
"is_silently": {
"rules": [{
"format": "bool"
}],
"label": "静默更新",
"defaultValue": false
},
"is_mandatory": {
"rules": [{
"format": "bool"
}],
"label": "强制更新",
"defaultValue": false
}
}
const enumConverter = {
"platform_valuetotext": [{
"value": "Android",
"text": "安卓"
},
{
"value": "iOS",
"text": "苹果"
}
],
"type_valuetotext": {
"native_app": "原生App安装包",
"wgt": "wgt资源包"
}
}
export {
validator,
enumConverter
}
[{
"menu_id": "system_update",
"name": "升级中心",
"icon": "uni-icons-cloud-upload",
"url": "uni_modules/uni-upgrade-center/pages/version/list",
"sort": 1050,
"parent_id": "system_management",
"permission": [],
"enable": true
}]
{
"id": "uni-upgrade-center",
"displayName": "升级中心 uni-upgrade-center - Admin",
"version": "0.5.1",
"description": "uni升级中心 - 后台管理系统",
"keywords": [
"uniCloud",
"admin",
"update",
"升级",
"wgt"
],
"repository": "https://gitee.com/dcloud/uni-upgrade-center/tree/master/uni_modules/uni-upgrade-center",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"category": [
"uniCloud",
"Admin插件"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "插件不采集任何数据",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [
"uni-data-checkbox",
"uni-data-picker",
"uni-dateformat",
"uni-easyinput",
"uni-file-picker",
"uni-forms",
"uni-icons",
"uni-pagination",
"uni-table"
],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "y",
"联盟": "y"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}
<template>
<view style="position: relative;">
<uni-icons @mouseenter.native="mouseenter" @mouseleave.native="showStableInfo = false"
style="padding:0 10px;color: #a8a8a8;cursor: pointer;" type="info" />
<view v-if="showStableInfo" class="show-stable" :style="{top:`${top}px`,left:`${left}px`,width:`${width}px`}">
<text>{{content}}</text>
</view>
</view>
</template>
<script>
export default {
props: {
content: String,
top: {
type: [Number, String],
default: -60
},
left: {
type: [Number, String],
default: -100
},
width: {
type: [Number, String],
default: 200
}
},
data() {
return {
showStableInfo: false,
arrowStyle: {}
}
},
methods: {
mouseenter(e) {
this.showStableInfo = true
}
}
}
</script>
<style lang="scss" scoped>
$main_color: #fff;
$main_back_color: #303133;
.show-stable {
width: 200px;
position: absolute;
padding: 5px 10px;
background-color: $main_back_color;
color: $main_color;
border-radius: 4px;
border: 1px solid #e9e9eb;
z-index: 99999;
}
</style>
import {
validator,
enumConverter
} from '@/uni_modules/uni-upgrade-center/js_sdk/validator/opendb-app-versions.js';
const platform_iOS = 'iOS';
const platform_Android = 'Android';
function getValidator(fields) {
let reuslt = {}
for (let key in validator) {
if (fields.includes(key)) {
reuslt[key] = validator[key]
}
}
return reuslt
}
export const fields =
'appid,name,title,contents,platform,type,version,min_uni_version,url,stable_publish,is_silently,is_mandatory,create_date'
export default {
data() {
return {
labelWidth: '80px',
enableiOSWgt: true, // 是否开启iOS的wgt更新
silentlyContent: '静默更新:App升级时会在后台下载wgt包并自行安装。新功能在下次启动App时生效',
mandatoryContent: '强制更新:App升级弹出框不可取消',
stablePublishContent: '同时只可有一个线上发行版,线上发行不可更设为下线。\n未上线可以设为上线发行并自动替换当前线上发行版',
stablePublishContent2: '使用本包替换当前线上发行版',
uploadFileContent: '可下载安装包地址。上传文件到云存储自动填写,也可以手动填写',
minUniVersionContent: '上次使用新Api或打包新模块的App版本',
priorityContent: '检查更新时,按照优先级从大到小依次尝试跳转商店。如果都跳转失败,则会打开浏览器使用下载链接下载apk安装包',
latestStableData: [], // 库中最新已上线版
appFileList: null, // 上传包
type_valuetotext: enumConverter.type_valuetotext,
preUrl: '',
formData: {
"appid": "",
"name": "",
"title": "",
"contents": "",
"platform": [],
"type": "",
"version": "",
"min_uni_version": "",
"url": "",
"stable_publish": false,
"create_date": null
},
formOptions: {
"platform_localdata": [{
"value": "Android",
"text": "安卓"
},
{
"value": "iOS",
"text": "苹果"
}
],
"type_localdata": [{
"value": "native_app",
"text": "原生App安装包"
},
{
"value": "wgt",
"text": "App资源包"
}
]
},
rules: {
...getValidator(["appid", "contents", "platform", "type", "version", "min_uni_version", "url",
"stable_publish",
"title", "name", "is_silently", "is_mandatory"
])
}
}
},
onReady() {
this.$refs.form.setRules(this.rules)
},
computed: {
isWGT() {
return this.formData.type === 'wgt'
},
isiOS() {
return !this.isWGT ? this.formData.platform.includes(platform_iOS) : false;
},
hasPackage() {
return this.appFileList && !!Object.keys(this.appFileList).length
},
fileExtname() {
return this.isWGT ? ['wgt'] : ['apk']
},
platformLocaldata() {
return !this.isWGT ? this.formOptions.platform_localdata : this.enableiOSWgt ? this.formOptions
.platform_localdata : [this.formOptions.platform_localdata[0]]
},
uni_platform() {
return (this.isiOS ? platform_iOS : platform_Android).toLocaleLowerCase()
}
},
methods: {
packageUploadSuccess(res) {
uni.showToast({
icon: 'success',
title: '上传成功',
duration: 800
})
this.preUrl = this.formData.url
this.formData.url = res.tempFilePaths[0]
},
async packageDelete(res) {
if (!this.hasPackage) return;
let [deleteRes] = await this.$request('deleteFile', {
fileList: [res.tempFilePath]
}, {
functionName: 'upgrade-center'
})
if (deleteRes.success) {
uni.showToast({
icon: 'success',
title: '删除成功',
duration: 800
})
this.formData.url = this.preUrl
this.$refs.form.clearValidate('url')
}
},
selectFile() {
if (this.hasPackage) {
uni.showToast({
icon: 'none',
title: '只可上传一个文件,请删除已上传后重试',
duration: 1000
});
}
},
createCenterRecord(value) {
return {
...value,
uni_platform: this.uni_platform,
create_env: 'upgrade-center'
}
},
createCenterQuery({
appid
}) {
return {
appid,
create_env: 'upgrade-center'
}
},
createStatQuery({
appid,
type,
version,
uni_platform
}) {
return {
appid,
type,
version,
uni_platform: uni_platform ? uni_platform : this.uni_platform,
create_env: 'uni-stat',
stable_publish: false
}
}
}
}
// 判断arr是否为一个数组,返回一个bool值
function isArray(arr) {
return Object.prototype.toString.call(arr) === '[object Array]';
}
// 深度克隆
export function deepClone(obj) {
// 对常见的“非”值,直接返回原来值
if ([null, undefined, NaN, false].includes(obj)) return obj;
if (typeof obj !== "object" && typeof obj !== 'function') {
//原始类型直接返回
return obj;
}
var o = isArray(obj) ? [] : {};
for (let i in obj) {
if (obj.hasOwnProperty(i)) {
o[i] = typeof obj[i] === "object" ? deepClone(obj[i]) : obj[i];
}
}
return o;
}
export const appListDbName = 'opendb-app-list'
export const appVersionListDbName = 'opendb-app-versions'
// 版本列表默认显示应用Appid
export const defaultDisplayApp = ''
此差异已折叠。
此差异已折叠。
此差异已折叠。
# uni-upgrade-center - Admin
### 概述
> 统一管理App及App在`Android`、`iOS`平台上`App安装包`和`wgt资源包`的发布升级
> 本插件为升级中心Admin后台管理系统,前台检查更新函数请点击查看 [uni-upgrade-center-app](https://ext.dcloud.net.cn/plugin?id=4542)
### 基于uniCloud的App升级中心,本插件具有如下特征:
- 云端基于uniCloud云函数实现
- 数据库遵循opendb规范
- 遵循uniCloud Admin框架规范,可直接导入Admin项目中
- 支持App整包升级及wgt资源包升级
## 什么是 uniCloud
uniCloud 是 DCloud 联合阿里云、腾讯云,为开发者提供的基于 serverless 模式和 js 编程的云开发平台,更多请参考[uniCloud 文档](https://uniapp.dcloud.io/uniCloud)
## 升级中心解决了什么问题?
升级中心是一款uniCloud admin插件,负责App版本更新业务。包含后台管理界面、更新检查逻辑,App内只要调用弹出提示即可。
升级中心有以下功能点:
- 应用管理,对App的信息记录和应用版本管理
- 版本管理,可以发布新版,也可方便直观的对当前App历史版本以及线上发行版本进行查看、编辑和删除操作
- 版本发布信息管理,包括 更新标题,更新内容,版本号,静默更新,强制更新,灵活上线发行 的设置和修改
- 原生App安装包,发布Apk更新,用于App的整包更新,可设置是否强制更新
- wgt资源包,发布wgt更新,用于App的热更新,可设置是否强制更新,静默更新
- App管理列表及App版本记录列表搜索
只需导入插件,初始化数据库即可拥有上述功能。
您也可以自己修改逻辑自定义数据库字段,和随意定制 UI 样式。
## 安装指引
1. 使用`HBuilderX 3.1.0+`,因为要使用到`uni_modules`
2. 使用已有`uniCloud-admin`项目或新建项目:`打开HBuilderX` -> `文件` -> `新建` -> `项目` -> `uni-app` 选择 `uniCloud admin`模板,键入一个名字,确定
3. 鼠标右键选择`关联云服务空间``运行云服务空间初始化向导`
3. 在插件市场打开本插件页面,在右侧点击`使用 HBuilderX 导入插件`,选择 `uniCloud admin` 项目点击确定
4. 等待下载安装完毕。由于本插件依赖一些uni-ui插件,下载完成后会显示合并插件页面,自行选择即可
5. 找到`/uni_modules/uni-upgrade-center/uniCloud/cloudfunctions/upgrade-center`,右键上传部署
6. 找到`/uni_modules/uni-upgrade-center/uniCloud/database/db_init.json`,右键初始化数据库
7.`pages.json`中添加页面路径
```json
//此结构与uniCloud admin中的pages.json结构一致
{
"pages": [
// ……其他页面配置
{
"path": "uni_modules/uni-upgrade-center/pages/version/list",
"style": {
"navigationBarTitleText": "版本列表"
}
}, {
"path": "uni_modules/uni-upgrade-center/pages/version/add",
"style": {
"navigationBarTitleText": "新版发布"
}
}, {
"path": "uni_modules/uni-upgrade-center/pages/version/detail",
"style": {
"navigationBarTitleText": "版本信息查看"
}
}
]
}
```
8.`manifest.json -> 源码视图`中添加以下配置:
```js
"networkTimeout":{
"uploadFile":1200000 //ms, 如果不配置,上传大文件可能会超时
}
```
9. 运行项目到`Chrome`
10. 运行起来uniCloud admin,菜单管理模块会自动读取`/uni_modules/uni-upgrade-center/menu.json`文件中的菜单配置,生成【待添加菜单】,选中升级中心,点击`添加选中的菜单`即可
<div align="center">
<img src="https://vkceyugu.cdn.bspapp.com/VKCEYUGU-a90b5f95-90ba-4d30-a6a7-cd4d057327db/16dc338e-7d5b-4290-98a9-adb7f0c23754.png" width="800"></img>
</div>
11. 添加成功后,就可以在左侧的菜单栏中找到`升级中心`菜单
<div align="center">
<img src="https://vkceyugu.cdn.bspapp.com/VKCEYUGU-a90b5f95-90ba-4d30-a6a7-cd4d057327db/fcf04804-0c4c-4342-9a8b-dfc273dfc83c.png" width="300"></img>
</div>
12. 在进入`升级中心`之前:
1. 需要到`uni-admin``应用管理`中添加一个应用,才可以在`升级中心`中发布对应应用的版本。
2. 当你有多个应用时,可以在`/uni_modules/uni-upgrade-center/pages/utils.js`中修改`defaultDisplayApp`字段来设置默认显示应用的`appid`
3. 如果不设置或设置应用不存在则默认从数据库中查出来的第一个应用。
13. 由于插件依赖的uni-ui的一些组件,建议右键`/uni_modules/uni-upgrade-center`安装一下第三方依赖,否则可能会出现一些问题
14. 运行在`uniCloud`,由于本插件使用了`clientDB`,因此可能需要配置一下`uni-config-center插件`关于`uni-id`的配置信息。如提示`公用模块uni-id缺少配置信息`请这样做:
1. 点击[uni-config-center](https://ext.dcloud.net.cn/plugin?id=4425)导入插件
2.`/uniCloud/cloudfunctions/common/uni-config-center/`下创建`uni-id`文件夹,文件夹内创建`config.json`文件。
3. 点击[config.json默认配置](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=start)。将内容拷贝至`config.json`中。**注:一定要把注释去除!**
## 使用指南
### 升级中心
#### 应用列表
1. 点击菜单 `应用管理`,这里展示你所添加的 App,点击右上角 `新增` 可以新增一个 App
<div align="center">
<img src="https://vkceyugu.cdn.bspapp.com/VKCEYUGU-a90b5f95-90ba-4d30-a6a7-cd4d057327db/7f85aa6a-eff3-4cc6-bb32-9feaaeaf97d0.png" width="400"></img>
</div>
2. 将App的信息都填写完善后,你可以在列表的操作列进行`修改`应用信息或者`删除`该应用。
**Tips**
- 删除应用会把该应用的所有版本记录同时删除
#### 版本管理
1. 在版本管理list的右上角点击`发布新版`,可以发布`原生App安装包``wgt资源包`。在左上角点击`下拉列表`,可以切换展示应用。
<div align="center">
<img src="https://vkceyugu.cdn.bspapp.com/VKCEYUGU-a90b5f95-90ba-4d30-a6a7-cd4d057327db/442e84e7-e7f3-4d27-9c98-45568e5db835.png" width="800"></img>
</div>
- #### 发布原生App安装包
1. 在上传安装包界面填写此次发版信息
<div align="center" >
<img src="https://vkceyugu.cdn.bspapp.com/VKCEYUGU-a90b5f95-90ba-4d30-a6a7-cd4d057327db/67932ae3-1a7a-4f21-9849-ba3bcc500c36.png" width="400"></img>
</div>
2. `包地址`
- 可以选择手动上传一个文件到 `云存储`,会自动将地址填入该项
- 也可以手动填写一个地址,就可以不用再上传文件
- 如果是发布`苹果`版本,包地址则为 应用在`AppStore的链接`
3. `强制更新`
- 如果使用强制更新,App端接收到该字段后,App升级弹出框不可取消
4. `上线发行`
- 可设置当前包是否上线发行,只有已上线才会进行更新检测
- 同时只可有一个线上发行版,线上发行不可更设为下线。未上线可以设为上线发行并自动替换当前线上发行版
- 修改当前包为上线发行,自动替换当前线上发行版
- #### 发布wgt资源包
1. 大部分配置与发布 `原生App安装包` 一致
<div align="center">
<img src="https://vkceyugu.cdn.bspapp.com/VKCEYUGU-a90b5f95-90ba-4d30-a6a7-cd4d057327db/ec916cde-0d0e-4bf3-a735-643ea2a45b74.png" width="400"></img>
</div>
2. `原生App最低版本`
- 上次使用新Api或打包新模块的App版本
- 如果此次打包wgt使用了`新的api`或者打包了`新的模块`,则在发布 `wgt资源包` 的时候,将此版本更新为本次版本
- 如果已有正式版`wgt资源包`,则本次新增会自动带出
2. `静默更新`
- App升级时会在后台下载wgt包并自行安装。新功能在下次启动App时生效
- #### 发布完成页面
<div align="center">
<img src="https://vkceyugu.cdn.bspapp.com/VKCEYUGU-a90b5f95-90ba-4d30-a6a7-cd4d057327db/c5470d8c-cc37-4b41-8d56-6d50f8daac62.png" width="800"></img>
</div>
**Tips**
1. `pages/system/upgradecenter/version/add.vue`中有版本对比函数(compare)。
- 使用多段式版本格式(如:"3.0.0.0.0.1.0.1", "3.0.0.0.0.1")。如果不满足对比规则,请自行修改。
## 项目代码说明
### uniCloud 数据表
数据表基于 [openDB](https://gitee.com/dcloud/opendb/tree/master) 规范,它约定了一个标准用户表的表名和字段定义,并且基于 nosql 的特性,可以由开发者自行扩展字段。
本项目用到了 2 个表:
- opendb-app-list:app管理列表。记录应用的 appid、name、description 用于展示。[详见](https://gitee.com/dcloud/opendb/tree/master/collection/opendb-app-list)
- opendb-app-versions:应用版本管理表。记录管理应用的版本信息。[详见](https://gitee.com/dcloud/opendb/tree/master/collection/opendb-app-versions)
### 前端页面
点击`升级中心`,会进入应用管理列表,在这里你可以新增应用,或者在`应用详情`中查看、修改或删除一个已经录入的应用。
在应用管理列表中点击某个应用的`版本管理`,进入该应用的所有版本记录。列表排序为:先排序已上线版本,剩下已下线版本根据创建时间排列。
在应用版本列表中点击`详情`,即可进入该版本的信息详情中查看、修改或删除该记录。
**Tips**
- 升级中心设计之初就支持iOS的wgt更新
- iOS的wgt更新肯定是违反apple政策的,注意事项:
- 审核期间请不要弹窗升级
- 升级完后尽量不要自行重启
- 尽量使用静默更新
- 可以通过以下修改支持iOS的wgt更新:
> \uni_modules\uni-upgrade-center\pages\mixin\version_add_detail_mixin.js
>
> 将 `data` 中的 `enableiOSWgt: false` 中 改为 `enableiOSWgt: true`
**常见问题**
- 以下问题可以通过升级插件版本解决:
- createdate不与默认值匹配
- ["create_date"]在数据库中并不存在
- 提交的字段["dirty_data"]在数据库中并不存在
- 集合[opendb-app-list]对应的schema内存在错误,详细信息:opendb-app-list表对应的schema名称冲突,这是什么意思呢
- 没有/找不到 [opendb-app-list] 集合/表。**解决方案:**升级 admin 至 1.6.0+ 即可
- 测试时发布了高版本的包,测试完了发布包提示需要大于版本号 (x.x.x)。**解决方案:**直接在控制台修改数据库
\ No newline at end of file
'use strict';
exports.main = async (event, context) => {
//event为客户端上传的参数
console.log('event : ', event)
let res = {};
let params = event.data || event.params;
switch (event.action) {
case 'deleteFile':
res = await uniCloud.deleteFile({
fileList: params.fileList
})
break;
}
//返回数据给客户端
return res
};
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册