提交 cf250a73 编写于 作者: study夏羽's avatar study夏羽

更新依赖的插件

上级 27f2ea0a
## 3.5.1(2022-08-11)
v3.5.1
修复vue3引入报错;
修复vue导出临时文件失败;
修复在其他dom元素更新时,可能导致二维码重新渲染;
修复size重新赋值导致组件偏移、闪烁。
## 3.6.5(2022-10-14)
修复组件size值为string类型时,调用toTempFilePath导出临时文件路径失败;
优化组件加载本地图片。
{
"name": "uqrcode",
"version": "3.5.1",
"version": "3.6.0",
"description": "",
"main": "uqrcode.js",
"scripts": {
......
{
"id": "Sansnn-uQRCode",
"displayName": "uQRCode 全端二维码生成插件 支持nvue 支持nodejs服务端",
"version": "3.5.1",
"version": "3.6.5",
"description": "uQRCode是一款基于Javascript环境开发的二维码生成插件,适用所有Javascript运行环境的前端应用和Node.js。",
"keywords": [
"uQRCode",
"二维码",
"uQRCode",
"qrcode",
"qr"
],
"repository": "https://github.com/Sansnn/uQRCode",
"engines": {
},
"dcloudext": {
"dcloudext": {
"category": [
"JS SDK",
"通用 SDK"
],
"sale": {
"regular": {
"price": "0.00"
......@@ -29,8 +33,7 @@
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/uqrcodejs",
"type": "sdk-js"
"npmurl": "https://www.npmjs.com/package/uqrcodejs"
},
"uni_modules": {
"dependencies": [],
......
......@@ -6,6 +6,24 @@
欢迎加入群聊【uQRCode交流群】:[695070434](https://jq.qq.com/?_wv=1027&k=JRjzDqiw)
# 设计器
uQRCode发布了配套的可视化设计器,可根据自己喜好在设计器中设计二维码样式,一键生成配置代码复制到项目中,详情请在微信小程序搜索“柚子二维码”,或扫描下方小程序码体验。
![uQRCode设计器](https://doc.uqrcode.cn/mp_weixin_code.jpg)
## 设计器模板示例
![uQRCode设计器](https://doc.uqrcode.cn/yz_1.png)
![uQRCode设计器](https://doc.uqrcode.cn/yz_2.png)
![uQRCode设计器](https://doc.uqrcode.cn/yz_3.png)
![uQRCode设计器](https://doc.uqrcode.cn/yz_4.png)
![uQRCode设计器](https://doc.uqrcode.cn/yz_5.png)
![uQRCode设计器](https://doc.uqrcode.cn/yz_6.png)
![uQRCode设计器](https://doc.uqrcode.cn/yz_7.png)
![uQRCode设计器](https://doc.uqrcode.cn/yz_8.png)
![uQRCode设计器](https://doc.uqrcode.cn/yz_9.png)
# 快速上手
> 在`uni-app`中,我们更推荐使用组件方式来生成二维码,组件方式大大提高了页面的可读性以及避开了一些平台容易出问题的地方,当组件无法满足需求的时候,再考虑切换成原生方式。
......@@ -20,7 +38,7 @@ uni-app插件市场地址:[https://ext.dcloud.net.cn/plugin?id=1287](https://e
## 原生方式
原生方式仅需要获取`uqrcode.js`文件便可使用。
原生方式仅需要获取`uqrcode.js`文件便可使用。详细配置请移步到:文档 > [原生](https://doc.uqrcode.cn/document/native.html)
### 安装
......@@ -38,12 +56,18 @@ npm install @uqrcode/js
- 通过`import`引入。
``` javascript
import UQRCode from 'uqrcode';
// npm安装
import UQRCode from 'uqrcodejs'; // npm install uqrcodejs
// 或者
import UQRCode from '@uqrcode/js'; // npm install @uqrcode/js
```
- `Node.js`通过`require`引入。
``` javascript
const UQRCode = require('uqrcode');
// npm安装
const UQRCode = require('uqrcodejs'); // npm install uqrcodejs
// 或者
const UQRCode = require('@uqrcode/js'); // npm install @uqrcode/js
```
- 原生浏览器环境,在js脚本加载时添加到`window`
......@@ -54,8 +78,6 @@ const UQRCode = require('uqrcode');
</script>
```
> `vue3`推荐使用`npm`安装,非`npm`安装直接引入`uqrcode.js`文件如果出现报错:`SyntaxError: The requested module 'uqrcode.js' does not provide an export named 'default'`,在`uqrcode.js`文件中最后一行添加`export default uQRCode`即可。
### 简单用法
`uQRCode`基于`Canvas API`封装了一套方法,建议开发者使用`canvas`生成,一键调用,非常方便。以下是示例:
......@@ -250,7 +272,7 @@ aEle.remove(); // 下载之后把创建的元素删除
### 安装
通过uni-app插件市场地址安装:[https://ext.dcloud.net.cn/plugin?id=1287](https://ext.dcloud.net.cn/plugin?id=1287)
通过uni-app插件市场地址安装:[https://ext.dcloud.net.cn/plugin?id=1287](https://ext.dcloud.net.cn/plugin?id=1287)详细配置请移步到:文档 > [uni-app组件](https://doc.uqrcode.cn/document/uni-app.html)
### 引入
......@@ -258,15 +280,15 @@ uni-app默认为easycom模式,可直接键入`<uqrcode>`标签。
### 简单用法
安装`uqrcode`组件后,在`template`中键入`<uqrcode/>`。设置`ref`属性可使用组件内部方法,`canvas-id`属性为组件内部的canvas组件标识,`value`属性为二维码生成对应内容。
安装`uqrcode`组件后,在`template`中键入`<uqrcode/>`。设置`ref`属性可使用组件内部方法,`canvas-id`属性为组件内部的canvas组件标识,`value`属性为二维码生成对应内容`options`为配置选项,可配置二维码样式,绘制Logo等,详见:[options](https://doc.uqrcode.cn/document/uni-app.html#options)
``` html
<uqrcode ref="qrcode" canvas-id="qrcode" value="https://doc.uqrcode.cn"></uqrcode>
<uqrcode ref="uqrcode" canvas-id="qrcode" value="https://doc.uqrcode.cn" :options="{ margin: 10 }"></uqrcode>
```
### 导出临时文件路径
为了保证方法调用成功,请在 [complete](/document/uni-app.md#complete) 事件返回`success=true`后调用。
为了保证方法调用成功,请在 [complete](https://doc.uqrcode.cn/document/uni-app.html#complete) 事件返回`success=true`后调用。
```javascript
// uqrcode为组件的ref名称
......@@ -279,7 +301,7 @@ this.$refs.uqrcode.toTempFilePath({
### 保存二维码到本地相册
为了保证方法调用成功,请在 [complete](/document/uni-app.md#complete) 事件返回`success=true`后调用。
为了保证方法调用成功,请在 [complete](https://doc.uqrcode.cn/document/uni-app.html#complete) 事件返回`success=true`后调用。
```javascript
// uqrcode为组件的ref名称
......@@ -293,4 +315,4 @@ this.$refs.uqrcode.save({
});
```
## 更多配置说明请前往官方文档查看:[https://doc.uqrcode.cn](https://doc.uqrcode.cn)。
\ No newline at end of file
## 更多使用说明请前往官方文档查看:[https://doc.uqrcode.cn](https://doc.uqrcode.cn)。
\ No newline at end of file
## 0.0.3(2022-11-11)
- 修复 config 方法获取根节点为数组格式配置时错误的转化为了对象的Bug
## 0.0.2(2021-04-16)
- 修改插件package信息
## 0.0.1(2021-03-15)
......
{
"id": "uni-config-center",
"displayName": "uni-config-center",
"version": "0.0.2",
"version": "0.0.3",
"description": "uniCloud 配置中心",
"keywords": [
"配置",
......@@ -11,11 +11,7 @@
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"category": [
"uniCloud",
"云函数模板"
],
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
......@@ -32,7 +28,8 @@
"data": "无",
"permissions": "无"
},
"npmurl": ""
"npmurl": "",
"type": "unicloud-template-function"
},
"directories": {
"example": "../../../scripts/dist"
......@@ -73,6 +70,10 @@
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "u"
}
}
}
......
{
"name": "uni-config-center",
"version": "0.0.2",
"version": "0.0.3",
"description": "配置中心",
"main": "index.js",
"keywords": [],
......
## 1.1.3(2022-09-22)
- 修复,引入 uni.scss 引入默认主题色报错的问题
## 1.1.2(2022-09-22)
- 增加主题色 primaryColor 配置选项
## 1.1.1(2022-09-19)
- 修复,输入后回车,change 事件触发两次,[详情](https://ask.dcloud.net.cn/question/152149)
## 1.1.4(2022-10-27)
- 修复 props 中背景颜色无默认值的bug
## 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)
- 修复 关闭图标某些情况下无法取消的bug
- 修复 关闭图标某些情况下无法取消的 bug
## 1.0.2(2022-04-12)
- 修复 默认值不生效的bug
- 修复 默认值不生效的 bug
## 1.0.1(2022-04-02)
- 修复 value不能为0的bug
- 修复 value 不能为 0 的 bug
## 1.0.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-easyinput](https://uniapp.dcloud.io/component/uniui/uni-easyinput)
## 0.1.4(2021-08-20)
- 修复 在 uni-forms 的动态表单中默认值校验不通过的 bug
## 0.1.3(2021-08-11)
- 修复 在 uni-forms 中重置表单,错误信息无法清除的问题
## 0.1.2(2021-07-30)
- 优化 vue3下事件警告的问题
- 优化 vue3 下事件警告的问题
## 0.1.1
- 优化 errorMessage 属性支持 Boolean 类型
## 0.1.0(2021-07-13)
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
- 组件兼容 vue3,如何创建 vue3 项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 0.0.16(2021-06-29)
- 修复 confirmType 属性(仅 type="text" 生效)导致多行文本框无法换行的 bug
## 0.0.15(2021-06-21)
- 修复 passwordIcon 属性拼写错误的 bug
## 0.0.14(2021-06-18)
- 新增 passwordIcon 属性,当type=password时是否显示小眼睛图标
- 新增 passwordIcon 属性,当 type=password 时是否显示小眼睛图标
- 修复 confirmType 属性不生效的问题
## 0.0.13(2021-06-04)
- 修复 disabled 状态可清出内容的 bug
## 0.0.12(2021-05-12)
- 新增 组件示例地址
## 0.0.11(2021-05-07)
- 修复 input-border 属性不生效的问题
## 0.0.10(2021-04-30)
- 修复 ios 遮挡文字、显示一半的问题
## 0.0.9(2021-02-05)
- 调整为uni_modules目录规范
- 调整为 uni_modules 目录规范
- 优化 兼容 nvue 页面
<template>
<view class="uni-easyinput" :class="{'uni-easyinput-error':msg}" :style="boxStyle">
<view class="uni-easyinput" :class="{ 'uni-easyinput-error': msg }" :style="boxStyle">
<view class="uni-easyinput__content" :class="inputContentClass" :style="inputContentStyle">
<uni-icons v-if="prefixIcon" class="content-clear-icon" :type="prefixIcon" color="#c0c4cc"
@click="onClickIcon('prefix')" size="22"></uni-icons>
<textarea v-if="type === 'textarea'" class="uni-easyinput__content-textarea"
:class="{'input-padding':inputBorder}" :name="name" :value="val" :placeholder="placeholder"
:placeholderStyle="placeholderStyle" :disabled="disabled"
placeholder-class="uni-easyinput__placeholder-class" :maxlength="inputMaxlength" :focus="focused"
:autoHeight="autoHeight" @input="onInput" @blur="_Blur" @focus="_Focus" @confirm="onConfirm"></textarea>
<input v-else :type="type === 'password'?'text':type" class="uni-easyinput__content-input"
:style="inputStyle" :name="name" :value="val" :password="!showPassword && type === 'password'"
:placeholder="placeholder" :placeholderStyle="placeholderStyle"
placeholder-class="uni-easyinput__placeholder-class" :disabled="disabled" :maxlength="inputMaxlength"
:focus="focused" :confirmType="confirmType" @focus="_Focus" @blur="_Blur" @input="onInput"
@confirm="onConfirm" />
<uni-icons v-if="prefixIcon" class="content-clear-icon" :type="prefixIcon" color="#c0c4cc" @click="onClickIcon('prefix')" size="22"></uni-icons>
<textarea
v-if="type === 'textarea'"
class="uni-easyinput__content-textarea"
:class="{ 'input-padding': inputBorder }"
:name="name"
:value="val"
:placeholder="placeholder"
:placeholderStyle="placeholderStyle"
:disabled="disabled"
placeholder-class="uni-easyinput__placeholder-class"
:maxlength="inputMaxlength"
:focus="focused"
:autoHeight="autoHeight"
@input="onInput"
@blur="_Blur"
@focus="_Focus"
@confirm="onConfirm"
></textarea>
<input
v-else
:type="type === 'password' ? 'text' : type"
class="uni-easyinput__content-input"
:style="inputStyle"
:name="name"
:value="val"
:password="!showPassword && type === 'password'"
:placeholder="placeholder"
:placeholderStyle="placeholderStyle"
placeholder-class="uni-easyinput__placeholder-class"
:disabled="disabled"
:maxlength="inputMaxlength"
:focus="focused"
:confirmType="confirmType"
@focus="_Focus"
@blur="_Blur"
@input="onInput"
@confirm="onConfirm"
/>
<template v-if="type === 'password' && passwordIcon">
<!-- 开启密码时显示小眼睛 -->
<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 ? primaryColor :'#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 ? primaryColor : '#c0c4cc'"
@click="onEyes"
></uni-icons>
</template>
<template v-else-if="suffixIcon">
<uni-icons v-if="suffixIcon" class="content-clear-icon" :type="suffixIcon" color="#c0c4cc"
@click="onClickIcon('suffix')" size="22"></uni-icons>
<uni-icons v-if="suffixIcon" class="content-clear-icon" :type="suffixIcon" color="#c0c4cc" @click="onClickIcon('suffix')" size="22"></uni-icons>
</template>
<template v-else>
<uni-icons v-if="clearable && isVal && !disabled && type !== 'textarea'" class="content-clear-icon"
:class="{'is-textarea-icon':type==='textarea'}" type="clear" :size="clearSize"
:color="msg?'#dd524d':(focusShow? primaryColor :'#c0c4cc')" @click="onClear"></uni-icons>
<uni-icons
v-if="clearable && isVal && !disabled && type !== 'textarea'"
class="content-clear-icon"
:class="{ 'is-textarea-icon': type === 'textarea' }"
type="clear"
:size="clearSize"
:color="msg ? '#dd524d' : focusShow ? primaryColor : '#c0c4cc'"
@click="onClear"
></uni-icons>
</template>
<slot name="right"></slot>
</view>
......@@ -36,7 +72,7 @@
</template>
<script>
/**
/**
* Easyinput 输入框
* @description 此组件可以实现表单的输入与校验,包括 "text" 和 "textarea" 类型。
* @tutorial https://ext.dcloud.net.cn/plugin?id=3455
......@@ -78,26 +114,26 @@
* @event {Function} iconClick 点击图标时触发
* @example <uni-easyinput v-model="mobile"></uni-easyinput>
*/
function obj2strClass(obj) {
let classess = ''
function obj2strClass(obj) {
let classess = '';
for (let key in obj) {
const val = obj[key]
const val = obj[key];
if (val) {
classess += `${key} `
}
classess += `${key} `;
}
return classess
}
return classess;
}
function obj2strStyle(obj) {
let style = ''
function obj2strStyle(obj) {
let style = '';
for (let key in obj) {
const val = obj[key]
style += `${key}:${val};`
const val = obj[key];
style += `${key}:${val};`;
}
return style
}
export default {
return style;
}
export default {
name: 'uni-easyinput',
emits: ['click', 'iconClick', 'update:modelValue', 'input', 'focus', 'blur', 'confirm', 'clear', 'eyes', 'change'],
model: {
......@@ -115,7 +151,7 @@
formItem: {
from: 'uniFormItem',
default: null
},
}
},
props: {
name: String,
......@@ -184,12 +220,13 @@
},
styles: {
type: Object,
default () {
default() {
return {
color: '#333',
backgroundColor: '#fff',
disableColor: '#F7F6F6',
borderColor: '#e5e5e5'
}
};
}
},
errorMessage: {
......@@ -214,12 +251,12 @@
computed: {
// 输入框内是否有值
isVal() {
const val = this.val
const val = this.val;
// fixed by mehaotian 处理值为0的情况,字符串0不在处理范围
if (val || val === 0) {
return true
return true;
}
return false
return false;
},
msg() {
......@@ -228,7 +265,7 @@
// return this.errorMessage || this.formItem.errMsg;
// }
// TODO 处理头条 formItem 中 errMsg 不更新的问题
return this.localMsg || this.errorMessage
return this.localMsg || this.errorMessage;
},
// 因为uniapp的input组件的maxlength组件必须要数值,这里转为数值,用户可以传入字符串数值
inputMaxlength() {
......@@ -237,7 +274,7 @@
// 处理外层样式的style
boxStyle() {
return `color:${this.inputBorder && this.msg?'#e43d33':this.styles.color};`
return `color:${this.inputBorder && this.msg ? '#e43d33' : this.styles.color};`;
},
// input 内容的类和样式处理
inputContentClass() {
......@@ -246,53 +283,53 @@
'is-input-error-border': this.inputBorder && this.msg,
'is-textarea': this.type === 'textarea',
'is-disabled': this.disabled
})
});
},
inputContentStyle() {
const focusColor = this.focusShow ? this.primaryColor : this.styles.borderColor
const borderColor = this.inputBorder && this.msg ? '#dd524d' : focusColor
const focusColor = this.focusShow ? this.primaryColor : 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'
})
'background-color': this.disabled ? this.styles.disableColor : this.styles.backgroundColor
});
},
// input右侧样式
inputStyle() {
const paddingRight = this.type === 'password' || this.clearable || this.prefixIcon ? '' : '10px'
const paddingRight = this.type === 'password' || this.clearable || this.prefixIcon ? '' : '10px';
return obj2strStyle({
'padding-right': paddingRight,
'padding-left': this.prefixIcon ? '' : '10px'
})
});
}
},
watch: {
value(newVal) {
this.val = newVal
this.val = newVal;
},
modelValue(newVal) {
this.val = newVal
this.val = newVal;
},
focus(newVal) {
this.$nextTick(() => {
this.focused = this.focus
this.focusShow = this.focus
})
this.focused = this.focus;
this.focusShow = this.focus;
});
}
},
created() {
this.init()
this.init();
// TODO 处理头条vue3 computed 不监听 inject 更改的问题(formItem.errMsg)
if (this.form && this.formItem) {
this.$watch('formItem.errMsg', (newVal) => {
this.localMsg = newVal
})
this.$watch('formItem.errMsg', newVal => {
this.localMsg = newVal;
});
}
},
mounted() {
this.$nextTick(() => {
this.focused = this.focus
this.focusShow = this.focus
})
this.focused = this.focus;
this.focusShow = this.focus;
});
},
methods: {
/**
......@@ -300,11 +337,11 @@
*/
init() {
if (this.value || this.value === 0) {
this.val = this.value
this.val = this.value;
} else if (this.modelValue || this.modelValue === 0) {
this.val = this.modelValue
this.val = this.modelValue;
} else {
this.val = null
this.val = null;
}
},
......@@ -313,15 +350,15 @@
* @param {Object} type
*/
onClickIcon(type) {
this.$emit('iconClick', type)
this.$emit('iconClick', type);
},
/**
* 显示隐藏内容,密码框时生效
*/
onEyes() {
this.showPassword = !this.showPassword
this.$emit('eyes', this.showPassword)
this.showPassword = !this.showPassword;
this.$emit('eyes', this.showPassword);
},
/**
......@@ -332,19 +369,19 @@
let value = event.detail.value;
// 判断是否去除空格
if (this.trim) {
if (typeof(this.trim) === 'boolean' && this.trim) {
value = this.trimStr(value)
if (typeof this.trim === 'boolean' && this.trim) {
value = this.trimStr(value);
}
if (typeof(this.trim) === 'string') {
value = this.trimStr(value, this.trim)
if (typeof this.trim === 'string') {
value = this.trimStr(value, this.trim);
}
};
if (this.errMsg) this.errMsg = ''
this.val = value
}
if (this.errMsg) this.errMsg = '';
this.val = value;
// TODO 兼容 vue2
this.$emit('input', value);
// TODO 兼容 vue3
this.$emit('update:modelValue', value)
this.$emit('update:modelValue', value);
},
/**
......@@ -354,13 +391,13 @@
*/
onFocus() {
this.$nextTick(() => {
this.focused = true
})
this.focused = true;
});
this.$emit('focus', null);
},
_Focus(event) {
this.focusShow = true
this.focusShow = true;
this.$emit('focus', event);
},
......@@ -370,24 +407,22 @@
* @param {Object} event
*/
onBlur() {
this.focused = false
this.focused = false;
this.$emit('focus', null);
},
_Blur(event) {
let value = event.detail.value;
this.focusShow = false
this.focusShow = false;
this.$emit('blur', event);
// 根据类型返回值,在event中获取的值理论上讲都是string
if (this.isEnter === false) {
this.$emit('change', this.val)
this.$emit('change', this.val);
}
// 失去焦点时参与表单校验
if (this.form && this.formItem) {
const {
validateTrigger
} = this.form
const { validateTrigger } = this.form;
if (validateTrigger === 'blur') {
this.formItem.onFieldChange()
this.formItem.onFieldChange();
}
}
},
......@@ -399,10 +434,10 @@
onConfirm(e) {
this.$emit('confirm', this.val);
this.isEnter = true;
this.$emit('change', this.val)
this.$emit('change', this.val);
this.$nextTick(() => {
this.isEnter = false
})
this.isEnter = false;
});
},
/**
......@@ -415,9 +450,9 @@
this.$emit('input', '');
// TODO 兼容 vue2
// TODO 兼容 vue3
this.$emit('update:modelValue', '')
this.$emit('update:modelValue', '');
// 点击叉号触发
this.$emit('clear')
this.$emit('clear');
},
/**
......@@ -431,9 +466,9 @@
} else if (pos === 'right') {
return str.trimRight();
} else if (pos === 'start') {
return str.trimStart()
return str.trimStart();
} else if (pos === 'end') {
return str.trimEnd()
return str.trimEnd();
} else if (pos === 'all') {
return str.replace(/\s+/g, '');
} else if (pos === 'none') {
......@@ -442,14 +477,14 @@
return str;
}
}
};
};
</script>
<style lang="scss">
$uni-error: #e43d33;
$uni-border-1: #DCDFE6 !default;
$uni-error: #e43d33;
$uni-border-1: #dcdfe6 !default;
.uni-easyinput {
.uni-easyinput {
/* #ifndef APP-NVUE */
width: 100%;
/* #endif */
......@@ -458,9 +493,9 @@
text-align: left;
color: #333;
font-size: 14px;
}
}
.uni-easyinput__content {
.uni-easyinput__content {
flex: 1;
/* #ifndef APP-NVUE */
width: 100%;
......@@ -474,9 +509,9 @@
border-color: #fff;
transition-property: border-color;
transition-duration: 0.3s;
}
}
.uni-easyinput__content-input {
.uni-easyinput__content-input {
/* #ifndef APP-NVUE */
width: auto;
/* #endif */
......@@ -487,23 +522,23 @@
font-size: 14px;
height: 35px;
// min-height: 36px;
}
}
.uni-easyinput__placeholder-class {
.uni-easyinput__placeholder-class {
color: #999;
font-size: 12px;
// font-weight: 200;
}
}
.is-textarea {
.is-textarea {
align-items: flex-start;
}
}
.is-textarea-icon {
.is-textarea-icon {
margin-top: 5px;
}
}
.uni-easyinput__content-textarea {
.uni-easyinput__content-textarea {
position: relative;
overflow: hidden;
flex: 1;
......@@ -517,23 +552,23 @@
min-height: 80px;
width: auto;
/* #endif */
}
}
.input-padding {
.input-padding {
padding-left: 10px;
}
}
.content-clear-icon {
.content-clear-icon {
padding: 0 5px;
}
}
.label-icon {
.label-icon {
margin-right: 5px;
margin-top: -1px;
}
}
// 显示边框
.is-input-border {
// 显示边框
.is-input-border {
/* #ifndef APP-NVUE */
display: flex;
box-sizing: border-box;
......@@ -545,9 +580,9 @@
/* #ifdef MP-ALIPAY */
overflow: hidden;
/* #endif */
}
}
.uni-error-message {
.uni-error-message {
position: absolute;
bottom: -17px;
left: 0;
......@@ -555,51 +590,49 @@
color: $uni-error;
font-size: 12px;
text-align: left;
}
}
.uni-error-msg--boeder {
.uni-error-msg--boeder {
position: relative;
bottom: 0;
line-height: 22px;
}
}
.is-input-error-border {
.is-input-error-border {
border-color: $uni-error;
.uni-easyinput__placeholder-class {
color: mix(#fff, $uni-error, 50%);
;
}
}
}
.uni-easyinput--border {
.uni-easyinput--border {
margin-bottom: 0;
padding: 10px 15px;
// padding-bottom: 0;
border-top: 1px #eee solid;
}
}
.uni-easyinput-error {
.uni-easyinput-error {
padding-bottom: 0;
}
}
.is-first-border {
.is-first-border {
/* #ifndef APP-NVUE */
border: none;
/* #endif */
/* #ifdef APP-NVUE */
border-width: 0;
/* #endif */
}
}
.is-disabled {
background-color: #F7F6F6;
color: #D5D5D5;
.is-disabled {
background-color: #f7f6f6;
color: #d5d5d5;
.uni-easyinput__placeholder-class {
color: #D5D5D5;
color: #d5d5d5;
font-size: 12px;
}
}
}
</style>
{
"id": "uni-easyinput",
"displayName": "uni-easyinput 增强输入框",
"version": "1.1.3",
"version": "1.1.4",
"description": "Easyinput 组件是对原生input组件的增强",
"keywords": [
"uni-ui",
......
## 1.0.30(2022-11-11)
- uni-id-co 修复 用户只有openid时绑定微信/QQ报错
## 1.0.29(2022-11-10)
- uni-id-co 支持URL化方式请求 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#adapter-http)
## 1.0.28(2022-11-09)
- uni-id-co 升级密码加密算法,支持hmac-sha256加密 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id-summary.html#password-safe)
- uni-id-co 新增 开发者可以自定义密码加密规则 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id-summary.html#custom-password-encrypt)
- uni-id-co 新增 支持将其他系统用户迁移至uni-id [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id-summary.html#move-users-to-uni-id)
## 1.0.27(2022-10-26)
- uni-id-co 新增 secureNetworkHandshakeByWeixin 接口,用于建立和微信小程序的安全网络连接
## 1.0.26(2022-10-18)
- 修复 uni-id-pages 导入时异常的Bug
## 1.0.25(2022-10-14)
......
......@@ -56,7 +56,6 @@ export default async function() {
}
// #endif
/* 注释此代码块原因:与uni-starter中的appinit逻辑一致
//3. 绑定clientDB错误事件
// clientDB对象
const db = uniCloud.database()
......@@ -73,9 +72,9 @@ export default async function() {
}
// 解绑clientDB错误事件
//db.off('error', onDBError)
*/
//4. 同步客户端push_clientid至uni-id-device表
//4. 同步客户端push_clientid至device表
if (uniCloud.onRefreshToken) {
uniCloud.onRefreshToken(() => {
console.log('onRefreshToken');
......@@ -91,7 +90,7 @@ export default async function() {
console.log('getPushClientId', res);
},
fail(e) {
console.error(e,'更多详情:https://uniapp.dcloud.net.cn/uniCloud/uni-starter.html#%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9')
console.log(e)
}
})
}
......
{
"id": "uni-id-pages",
"displayName": "uni-id-pages",
"version": "1.0.26",
"version": "1.0.30",
"description": "云端一体简单、统一、可扩展的用户中心页面模版",
"keywords": [
"用户管理",
......
......@@ -6,6 +6,8 @@ const verifyCollectionName = 'opendb-verify-codes'
const verifyCollection = db.collection(verifyCollectionName)
const deviceCollectionName = 'uni-id-device'
const deviceCollection = db.collection(deviceCollectionName)
const openDataCollectionName = 'opendb-open-data'
const openDataCollection = db.collection(openDataCollectionName)
const USER_IDENTIFIER = {
username: 'username',
......@@ -78,6 +80,7 @@ module.exports = {
userCollection,
verifyCollection,
deviceCollection,
openDataCollection,
USER_IDENTIFIER,
USER_STATUS,
CAPTCHA_SCENE,
......
......@@ -35,7 +35,8 @@ const ERROR = {
UNBIND_NOT_SUPPORTED: 'uni-id-unbind-not-supported',
UNBIND_UNIQUE_LOGIN: 'uni-id-unbind-unique-login',
UNBIND_PASSWORD_NOT_EXISTS: 'uni-id-unbind-password-not-exists',
UNBIND_MOBILE_NOT_EXISTS: 'uni-id-unbind-mobile-not-exists'
UNBIND_MOBILE_NOT_EXISTS: 'uni-id-unbind-mobile-not-exists',
UNSUPPORTED_REQUEST: 'uni-id-unsupported-request'
}
function isUniIdError (errCode) {
......
const { ERROR } = require('./error')
function getHttpClientInfo () {
const requestId = this.getUniCloudRequestId()
const { clientIP, userAgent, source, secretType = 'none' } = this.getClientInfo()
const { clientInfo = {} } = JSON.parse(this.getHttpInfo().body)
return {
...clientInfo,
clientIP,
userAgent,
source,
secretType,
requestId
}
}
function getHttpUniIdToken () {
const { uniIdToken = '' } = JSON.parse(this.getHttpInfo().body)
return uniIdToken
}
function verifyHttpMethod () {
const { headers, httpMethod } = this.getHttpInfo()
if (!/^application\/json/.test(headers['content-type']) || httpMethod.toUpperCase() !== 'POST') {
throw {
errCode: ERROR.UNSUPPORTED_REQUEST,
errMsg: 'unsupported request'
}
}
}
function universal () {
if (this.getClientInfo().source === 'http') {
verifyHttpMethod.call(this)
this.getParams()[0] = JSON.parse(this.getHttpInfo().body).params
this.getUniversalClientInfo = getHttpClientInfo.bind(this)
this.getUniversalUniIdToken = getHttpUniIdToken.bind(this)
} else {
this.getUniversalClientInfo = this.getClientInfo
this.getUniversalUniIdToken = this.getUniIdToken
}
}
module.exports = universal
......@@ -12,6 +12,7 @@ const {
isUniIdError
} = require('./common/error')
const middleware = require('./middleware/index')
const universal = require('./common/universal')
const {
registerAdmin,
......@@ -63,7 +64,8 @@ const {
} = require('./module/verify/index')
const {
refreshToken,
setPushCid
setPushCid,
secureNetworkHandshakeByWeixin
} = require('./module/utils/index')
const {
getInvitedUser,
......@@ -80,7 +82,10 @@ const {
module.exports = {
async _before () {
const clientInfo = this.getClientInfo()
// 支持 callFunction 与 URL化
universal.call(this)
const clientInfo = this.getUniversalClientInfo()
/**
* 检查clientInfo,无appId和uniPlatform时本云对象无法正常运行
* 此外需要保证用到的clientInfo字段均经过类型检查
......@@ -576,5 +581,9 @@ module.exports = {
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#unbind-apple
* @returns
*/
unbindApple
unbindApple,
/**
* 安全网络握手,目前仅处理微信小程序安全网络握手
*/
secureNetworkHandshakeByWeixin
}
......@@ -40,7 +40,8 @@ const sentence = {
'uni-id-unbind-failed': 'Please bind first and then unbind',
'uni-id-unbind-not-supported': 'Unbinding is not supported',
'uni-id-unbind-mobile-not-exists': 'This is the only way to login at the moment, please bind your phone number and then try to unbind',
'uni-id-unbind-password-not-exists': 'Please set a password first'
'uni-id-unbind-password-not-exists': 'Please set a password first',
'uni-id-unsupported-request': 'Unsupported request'
}
module.exports = {
......
......@@ -40,7 +40,8 @@ const sentence = {
'uni-id-unbind-failed': '请先绑定后再解绑',
'uni-id-unbind-not-supported': '不支持解绑',
'uni-id-unbind-mobile-not-exists': '这是当前唯一登录方式,请绑定手机号后再尝试解绑',
'uni-id-unbind-password-not-exists': '请先设置密码在尝试解绑'
'uni-id-unbind-password-not-exists': '请先设置密码在尝试解绑',
'uni-id-unsupported-request': '不支持的请求方式'
}
module.exports = {
......
......@@ -39,7 +39,7 @@ async function getNeedCaptcha ({
const {
data: recentRecord
} = await uniIdLogCollection.where({
ip: this.getClientInfo().clientIP,
ip: this.getUniversalClientInfo().clientIP,
...userIdentifier,
type,
create_date: dbCmd.gt(now - limitDuration)
......@@ -61,7 +61,7 @@ async function verifyCaptcha (params = {}) {
}
}
const payload = await this.uniCaptcha.verify({
deviceId: this.getClientInfo().deviceId,
deviceId: this.getUniversalClientInfo().deviceId,
captcha,
scene
})
......
......@@ -17,7 +17,7 @@ async function realPreLogin (params = {}) {
const {
user
} = params
const appId = this.getClientInfo().appId
const appId = this.getUniversalClientInfo().appId
const userMatched = await findUser({
userQuery: user,
authorizedApp: appId
......@@ -66,7 +66,7 @@ async function preLoginWithPassword (params = {}) {
} = this.config
const {
clientIP
} = this.getClientInfo()
} = this.getUniversalClientInfo()
// 根据ip地址,密码错误次数过多,锁定登录
let loginIPLimit = userRecord.login_ip_limit || []
// 清理无用记录
......@@ -78,6 +78,8 @@ async function preLoginWithPassword (params = {}) {
}
}
const passwordUtils = new PasswordUtils({
userRecord,
clientInfo: this.getUniversalClientInfo(),
passwordSecret: this.config.passwordSecret
})
......@@ -85,9 +87,7 @@ async function preLoginWithPassword (params = {}) {
success: checkPasswordSuccess,
refreshPasswordInfo
} = passwordUtils.checkUserPassword({
password,
passwordHash: userRecord.password,
passwordSecretVersion: userRecord.password_secret_version
password
})
if (!checkPasswordSuccess) {
// 更新用户ip对应的密码错误记录
......@@ -179,7 +179,7 @@ async function postLogin (params = {}) {
const {
clientIP,
uniIdToken
} = this.getClientInfo()
} = this.getUniversalClientInfo()
const uid = user._id
const updateData = {
last_login_date: Date.now(),
......
......@@ -5,11 +5,11 @@ const {
userCollection
} = require('../../common/constants')
async function logout() {
async function logout () {
const {
uniIdToken,
deviceId
} = this.getClientInfo()
} = this.getUniversalClientInfo()
const {
uid
} = await this.uniIdCommon.checkToken(
......
......@@ -2,37 +2,160 @@ const {
getType
} = require('../../common/utils')
const crypto = require('crypto')
const createConfig = require('uni-config-center')
const shareConfig = createConfig({
pluginId: 'uni-id'
})
let customPassword = {}
if (shareConfig.hasFile('custom-password.js')) {
customPassword = shareConfig.requireFile('custom-password.js') || {}
}
const passwordAlgorithmMap = {
UNI_ID_HMAC_SHA1: 'hmac-sha1',
UNI_ID_HMAC_SHA256: 'hmac-sha256',
UNI_ID_CUSTOM: 'custom'
}
const passwordAlgorithmKeyMap = Object.keys(passwordAlgorithmMap).reduce((res, item) => {
res[passwordAlgorithmMap[item]] = item
return res
}, {})
const passwordExtMethod = {
[passwordAlgorithmMap.UNI_ID_HMAC_SHA1]: {
verify ({ password }) {
const { password_secret_version: passwordSecretVersion } = this.userRecord
const passwordSecret = this._getSecretByVersion({
version: passwordSecretVersion
})
const { passwordHash } = this.encrypt({
password,
passwordSecret
})
const PasswordHashMethod = {
'hmac-sha1': function (content, secret) {
return passwordHash === this.userRecord.password
},
encrypt ({ password, passwordSecret }) {
const { value: secret, version } = passwordSecret
const hmac = crypto.createHmac('sha1', secret.toString('ascii'))
hmac.update(content)
return hmac.digest('hex')
hmac.update(password)
return {
passwordHash: hmac.digest('hex'),
version
}
}
},
[passwordAlgorithmMap.UNI_ID_HMAC_SHA256]: {
verify ({ password }) {
const parse = this._parsePassword()
const passwordHash = crypto.createHmac(parse.algorithm, parse.salt).update(password).digest('hex')
return passwordHash === parse.hash
},
encrypt ({ password, passwordSecret }) {
const { version } = passwordSecret
// 默认使用 sha256 加密算法
const salt = crypto.randomBytes(10).toString('hex')
const sha256Hash = crypto.createHmac(passwordAlgorithmMap.UNI_ID_HMAC_SHA256.substring(5), salt).update(password).digest('hex')
const algorithm = passwordAlgorithmKeyMap[passwordAlgorithmMap.UNI_ID_HMAC_SHA256]
// B 为固定值,对应 PasswordMethodMaps 中的 sha256算法
// hash 格式 $[PasswordMethodFlagMapsKey]$[salt size]$[salt][Hash]
const passwordHash = `$${algorithm}$${salt.length}$${salt}${sha256Hash}`
return {
passwordHash,
version
}
}
},
[passwordAlgorithmMap.UNI_ID_CUSTOM]: {
verify ({ password, passwordSecret }) {
if (!customPassword.verifyPassword) throw new Error('verifyPassword method not found in custom password file')
// return true or false
return customPassword.verifyPassword({
password,
passwordSecret,
userRecord: this.userRecord,
clientInfo: this.clientInfo
})
},
encrypt ({ password, passwordSecret }) {
if (!customPassword.encryptPassword) throw new Error('encryptPassword method not found in custom password file')
// return object<{passwordHash: string, version: number}>
return customPassword.encryptPassword({
password,
passwordSecret,
clientInfo: this.clientInfo
})
}
}
}
class PasswordUtils {
constructor ({
userRecord = {},
clientInfo,
passwordSecret
} = {}) {
const passwordSecretType = getType(passwordSecret)
if (passwordSecretType === 'array') {
this.passwordSecret = passwordSecret.sort((a, b) => {
return a.version - b.version
if (!clientInfo) throw new Error('Invalid clientInfo')
if (!passwordSecret) throw new Error('Invalid password secret')
this.clientInfo = clientInfo
this.userRecord = userRecord
this.passwordSecret = this.prePasswordSecret(passwordSecret)
}
/**
* passwordSecret 预处理
* @param passwordSecret
* @return {*[]}
*/
prePasswordSecret (passwordSecret) {
const newPasswordSecret = []
if (getType(passwordSecret) === 'string') {
newPasswordSecret.push({
value: passwordSecret,
type: passwordAlgorithmMap.UNI_ID_HMAC_SHA1
})
} else if (getType(passwordSecret) === 'array') {
for (const secret of passwordSecret.sort((a, b) => a.version - b.version)) {
newPasswordSecret.push({
...secret,
// 没有 type 设置默认 type hmac-sha1
type: secret.type || passwordAlgorithmMap.UNI_ID_HMAC_SHA1
})
} else if (passwordSecretType === 'string') {
this.passwordSecret = [{ value: passwordSecret }]
}
} else {
throw new Error('Invalid password secret')
}
return newPasswordSecret
}
getSecretByVersion (params = {}) {
const {
version
} = params
/**
* 获取最新加密密钥
* @return {*}
* @private
*/
_getLastestSecret () {
return this.passwordSecret[this.passwordSecret.length - 1]
}
_getOldestSecret () {
return this.passwordSecret[0]
}
_getSecretByVersion ({ version } = {}) {
if (!version && version !== 0) {
return this.getOldestSecret()
return this._getOldestSecret()
}
if (this.passwordSecret.length === 1) {
return this.passwordSecret[0]
......@@ -40,75 +163,97 @@ class PasswordUtils {
return this.passwordSecret.find(item => item.version === version)
}
getLastestSecret () {
return this.passwordSecret[this.passwordSecret.length - 1]
/**
* 获取密码的验证/加密方法
* @param passwordSecret
* @return {*[]}
* @private
*/
_getPasswordExt (passwordSecret) {
const ext = passwordExtMethod[passwordSecret.type]
if (!ext) {
throw new Error(`暂不支持 ${passwordSecret.type} 类型的加密算法`)
}
getOldestSecret () {
return this.passwordSecret[0]
const passwordExt = Object.create(null)
for (const key in ext) {
passwordExt[key] = ext[key].bind(Object.assign(this, Object.keys(ext).reduce((res, item) => {
if (item !== key) {
res[item] = ext[item].bind(this)
}
return res
}, {})))
}
checkUserPassword (params = {}) {
const {
password,
passwordHash: passwordHashToCheck,
passwordSecretVersion,
autoRefresh = true
} = params
const currentPasswordSecret = this.getSecretByVersion({
version: passwordSecretVersion
})
if (!currentPasswordSecret) {
throw new Error('Invalid password version')
}
const {
value: passwordSecret
} = currentPasswordSecret
const {
passwordHash
} = this.generatePasswordHash({
password,
passwordSecret,
passwordSecretVersion
})
if (passwordHashToCheck !== passwordHash) {
return passwordExt
}
_parsePassword () {
const [algorithmKey = '', cost = 0, hashStr = ''] = this.userRecord.password.split('$').filter(key => key)
const algorithm = passwordAlgorithmMap[algorithmKey] ? passwordAlgorithmMap[algorithmKey].substring(5) : null
const salt = hashStr.substring(0, Number(cost))
const hash = hashStr.substring(Number(cost))
return {
success: false
algorithm,
salt,
hash
}
}
let refreshPasswordInfo
if (autoRefresh && passwordSecretVersion !== this.getLastestSecret().version) {
refreshPasswordInfo = this.generatePasswordHash({
password
/**
* 生成加密后的密码
* @param {String} password 密码
*/
generatePasswordHash ({ password }) {
if (!password) throw new Error('Invalid password')
const passwordSecret = this._getLastestSecret()
const ext = this._getPasswordExt(passwordSecret)
const { passwordHash, version } = ext.encrypt({
password,
passwordSecret
})
}
return {
success: true,
refreshPasswordInfo
passwordHash,
version
}
}
generatePasswordHash (params = {}) {
let {
password,
passwordSecret,
passwordSecretVersion
} = params
if (getType(password) !== 'string') {
throw new Error('Invalid password')
/**
* 密码校验
* @param {String} password
* @param {Boolean} autoRefresh
* @return {{refreshPasswordInfo: {version: *, passwordHash: *}, success: boolean}|{success: boolean}}
*/
checkUserPassword ({ password, autoRefresh = true }) {
if (!password) throw new Error('Invalid password')
const { password_secret_version: passwordSecretVersion } = this.userRecord
const passwordSecret = this._getSecretByVersion({
version: passwordSecretVersion
})
const ext = this._getPasswordExt(passwordSecret)
const success = ext.verify({ password, passwordSecret })
if (!success) {
return {
success: false
}
password = password && password.trim()
if (!password) {
throw new Error('Invalid password')
}
if (!passwordSecret) {
const lastestSecret = this.getLastestSecret()
passwordSecret = lastestSecret.value
passwordSecretVersion = lastestSecret.version
let refreshPasswordInfo
if (autoRefresh && passwordSecretVersion !== this._getLastestSecret().version) {
refreshPasswordInfo = this.generatePasswordHash({ password })
}
return {
passwordHash: PasswordHashMethod['hmac-sha1'](password, passwordSecret),
version: passwordSecretVersion
success: true,
refreshPasswordInfo
}
}
}
......
......@@ -5,7 +5,7 @@ const {
ERROR
} = require('../../common/error')
function getQQPlatform() {
function getQQPlatform () {
const platform = this.clientPlatform
switch (platform) {
case 'app':
......@@ -18,7 +18,7 @@ function getQQPlatform() {
}
}
async function saveQQUserKey({
async function saveQQUserKey ({
openid,
sessionKey, // QQ小程序用户sessionKey
accessToken, // App端QQ用户accessToken
......@@ -26,7 +26,7 @@ async function saveQQUserKey({
} = {}) {
// 微信公众平台、开放平台refreshToken有效期均为30天(微信没有在网络请求里面返回30天这个值,务必注意未来可能出现调整,需及时更新此处逻辑)。
// 此前QQ开放平台有调整过accessToken的过期时间:[access_token有效期由90天缩短至30天](https://wiki.connect.qq.com/%E3%80%90qq%E4%BA%92%E8%81%94%E3%80%91access_token%E6%9C%89%E6%95%88%E6%9C%9F%E8%B0%83%E6%95%B4)
const appId = this.getClientInfo().appId
const appId = this.getUniversalClientInfo().appId
const qqPlatform = getQQPlatform.call(this)
const keyObj = {
dcloudAppid: appId,
......@@ -45,9 +45,9 @@ async function saveQQUserKey({
await this.uniOpenBridge.setUserAccessToken(keyObj, {
access_token: accessToken,
access_token_expired: accessTokenExpired
}, accessTokenExpired ?
Math.floor((accessTokenExpired - Date.now()) / 1000) :
30 * 24 * 60 * 60
}, accessTokenExpired
? Math.floor((accessTokenExpired - Date.now()) / 1000)
: 30 * 24 * 60 * 60
)
break
default:
......@@ -55,7 +55,7 @@ async function saveQQUserKey({
}
}
function generateQQCache({
function generateQQCache ({
sessionKey, // QQ小程序用户sessionKey
accessToken, // App端QQ用户accessToken
accessTokenExpired // App端QQ用户accessToken过期时间
......@@ -84,11 +84,11 @@ function generateQQCache({
}
}
function getQQOpenid({
function getQQOpenid ({
userRecord
} = {}) {
const qqPlatform = getQQPlatform.call(this)
const appId = this.getClientInfo().appId
const appId = this.getUniversalClientInfo().appId
const qqOpenidObj = userRecord.qq_openid
if (!qqOpenidObj) {
return
......@@ -96,7 +96,7 @@ function getQQOpenid({
return qqOpenidObj[`${qqPlatform}_${appId}`] || qqOpenidObj[qqPlatform]
}
async function getQQCacheFallback({
async function getQQCacheFallback ({
userRecord,
key
} = {}) {
......@@ -109,13 +109,13 @@ async function getQQCacheFallback({
return qqCache && qqCache[key]
}
async function getQQCache({
async function getQQCache ({
uid,
userRecord,
key
} = {}) {
const qqPlatform = getQQPlatform.call(this)
const appId = this.getClientInfo().appId
const appId = this.getUniversalClientInfo().appId
if (!userRecord) {
const getUserRes = await userCollection.doc(uid).get()
......
......@@ -18,13 +18,13 @@ const {
const PasswordUtils = require('./password')
const merge = require('lodash.merge')
async function realPreRegister(params = {}) {
async function realPreRegister (params = {}) {
const {
user
} = params
const userMatched = await findUser({
userQuery: user,
authorizedApp: this.getClientInfo().appId
authorizedApp: this.getUniversalClientInfo().appId
})
if (userMatched.length > 0) {
throw {
......@@ -33,7 +33,7 @@ async function realPreRegister(params = {}) {
}
}
async function preRegister(params = {}) {
async function preRegister (params = {}) {
try {
await realPreRegister.call(this, params)
} catch (error) {
......@@ -45,7 +45,7 @@ async function preRegister(params = {}) {
}
}
async function preRegisterWithPassword(params = {}) {
async function preRegisterWithPassword (params = {}) {
const {
user,
password
......@@ -54,6 +54,7 @@ async function preRegisterWithPassword(params = {}) {
user
})
const passwordUtils = new PasswordUtils({
clientInfo: this.getUniversalClientInfo(),
passwordSecret: this.config.passwordSecret
})
const {
......@@ -72,7 +73,7 @@ async function preRegisterWithPassword(params = {}) {
}
}
async function thirdPartyRegister({
async function thirdPartyRegister ({
user = {}
} = {}) {
return {
......@@ -81,7 +82,7 @@ async function thirdPartyRegister({
}
}
async function postRegister(params = {}) {
async function postRegister (params = {}) {
const {
user,
extraData = {},
......@@ -98,7 +99,7 @@ async function postRegister(params = {}) {
clientIP,
osName,
uniIdToken
} = this.getClientInfo()
} = this.getUniversalClientInfo()
merge(user, extraData)
......@@ -162,7 +163,7 @@ async function postRegister(params = {}) {
if (beforeRegister) {
userRecord = await beforeRegister({
userRecord,
clientInfo: this.getClientInfo()
clientInfo: this.getUniversalClientInfo()
})
}
......
......@@ -29,7 +29,7 @@ async function preBind ({
} = {}) {
const userMatched = await findUser({
userQuery: bindAccount,
authorizedApp: this.getClientInfo().appId
authorizedApp: this.getUniversalClientInfo().appId
})
if (userMatched.length > 0) {
await this.middleware.uniIdLog({
......
......@@ -17,7 +17,7 @@ async function realPreUnifiedLogin (params = {}) {
user,
type
} = params
const appId = this.getClientInfo().appId
const appId = this.getUniversalClientInfo().appId
const userMatched = await findUser({
userQuery: user,
authorizedApp: appId
......
......@@ -13,7 +13,7 @@ async function getPhoneNumber ({
}
return uniCloud.getPhoneNumber({
provider: 'univerify',
appid: this.getClientInfo().appId,
appid: this.getUniversalClientInfo().appId,
apiKey: univerifyConfig.apiKey,
apiSecret: univerifyConfig.apiSecret,
// eslint-disable-next-line camelcase
......
let redisEnable = null
function getRedisEnable() {
// 未用到的时候不调用redis接口,节省一些连接数
if (redisEnable !== null) {
return redisEnable
}
try {
uniCloud.redis()
redisEnable = true
} catch (error) {
redisEnable = false
}
return redisEnable
}
module.exports = {
getRedisEnable
}
\ No newline at end of file
......@@ -23,7 +23,7 @@ async function setVerifyCode ({
scene,
code: code || getVerifyCode(),
state: 0,
ip: this.getClientInfo().clientIP,
ip: this.getUniversalClientInfo().clientIP,
created_date: now,
expired_date: now + expiresIn * 1000
}
......
......@@ -5,6 +5,12 @@ const {
const {
ERROR
} = require('../../common/error')
const {
getRedisEnable
} = require('./utils')
const {
openDataCollection
} = require('../../common/constants')
function decryptWeixinData ({
encryptedData,
......@@ -33,7 +39,7 @@ function decryptWeixinData ({
function getWeixinPlatform () {
const platform = this.clientPlatform
const userAgent = this.getClientInfo().userAgent
const userAgent = this.getUniversalClientInfo().userAgent
switch (platform) {
case 'app':
case 'app-plus':
......@@ -58,7 +64,7 @@ async function saveWeixinUserKey ({
// 微信公众平台、开放平台refreshToken有效期均为30天(微信没有在网络请求里面返回30天这个值,务必注意未来可能出现调整,需及时更新此处逻辑)。
// 此前QQ开放平台有调整过accessToken的过期时间:[access_token有效期由90天缩短至30天](https://wiki.connect.qq.com/%E3%80%90qq%E4%BA%92%E8%81%94%E3%80%91access_token%E6%9C%89%E6%95%88%E6%9C%9F%E8%B0%83%E6%95%B4)
const appId = this.getClientInfo().appId
const appId = this.getUniversalClientInfo().appId
const weixinPlatform = getWeixinPlatform.call(this)
const keyObj = {
dcloudAppid: appId,
......@@ -85,6 +91,35 @@ async function saveWeixinUserKey ({
}
}
async function saveSecureNetworkCache ({
code,
openid,
unionid,
sessionKey
}) {
const {
appId
} = this.getUniversalClientInfo()
const key = `uni-id:${appId}:weixin-mp:code:${code}:secure-network-cache`
const value = JSON.stringify({
openid,
unionid,
session_key: sessionKey
})
// 此处存储的是code的缓存,有效期两天即可
const expiredSeconds = 2 * 24 * 60 * 60
await openDataCollection.doc(key).set({
value,
expired: Date.now() + expiredSeconds * 1000
})
const isRedisEnable = getRedisEnable()
if (isRedisEnable) {
const redis = uniCloud.redis()
await redis.set(key, value, 'EX', expiredSeconds)
}
}
function generateWeixinCache ({
sessionKey, // 微信小程序用户sessionKey
accessToken, // App端微信用户accessToken
......@@ -122,7 +157,7 @@ function getWeixinOpenid ({
userRecord
} = {}) {
const weixinPlatform = getWeixinPlatform.call(this)
const appId = this.getClientInfo().appId
const appId = this.getUniversalClientInfo().appId
const wxOpenidObj = userRecord.wx_openid
if (!wxOpenidObj) {
return
......@@ -149,7 +184,7 @@ async function getWeixinCache ({
key
} = {}) {
const weixinPlatform = getWeixinPlatform.call(this)
const appId = this.getClientInfo().appId
const appId = this.getUniversalClientInfo().appId
if (!userRecord) {
const getUserRes = await userCollection.doc(uid).get()
userRecord = getUserRes.data[0]
......@@ -179,7 +214,7 @@ async function getWeixinCache ({
async function getWeixinAccessToken () {
const weixinPlatform = getWeixinPlatform.call(this)
const appId = this.getClientInfo().appId
const appId = this.getUniversalClientInfo().appId
const cache = await this.uniOpenBridge.getAccessToken({
dcloudAppid: appId,
......@@ -194,5 +229,6 @@ module.exports = {
generateWeixinCache,
getWeixinCache,
saveWeixinUserKey,
getWeixinAccessToken
getWeixinAccessToken,
saveSecureNetworkCache
}
......@@ -2,7 +2,7 @@ module.exports = async function () {
if (this.authInfo) { // 多次执行auth时如果第一次成功后续不再执行
return
}
const token = this.getUniIdToken()
const token = this.getUniversalUniIdToken()
const payload = await this.uniIdCommon.checkToken(token)
if (payload.errCode) {
throw payload
......
......@@ -19,7 +19,7 @@ module.exports = async function ({
clientIP,
deviceId,
userAgent
} = this.getClientInfo()
} = this.getUniversalClientInfo()
const logData = {
appid: appId,
device_id: deviceId,
......
......@@ -79,7 +79,7 @@ module.exports = async function (params = {}) {
userQuery: {
email
},
authorizedApp: [this.getClientInfo().appId]
authorizedApp: [this.getUniversalClientInfo().appId]
})
if (userMatched.length === 0) {
throw {
......@@ -95,6 +95,7 @@ module.exports = async function (params = {}) {
passwordHash,
version
} = new PasswordUtils({
clientInfo: this.getUniversalClientInfo(),
passwordSecret: this.config.passwordSecret
}).generatePasswordHash({
password
......
......@@ -79,7 +79,7 @@ module.exports = async function (params = {}) {
userQuery: {
mobile
},
authorizedApp: [this.getClientInfo().appId]
authorizedApp: [this.getUniversalClientInfo().appId]
})
if (userMatched.length === 0) {
throw {
......@@ -95,6 +95,7 @@ module.exports = async function (params = {}) {
passwordHash,
version
} = new PasswordUtils({
clientInfo: this.getUniversalClientInfo(),
passwordSecret: this.config.passwordSecret
}).generatePasswordHash({
password
......
......@@ -32,16 +32,18 @@ module.exports = async function (params = {}) {
newPassword
} = params
const passwordUtils = new PasswordUtils({
userRecord,
clientInfo: this.getUniversalClientInfo(),
passwordSecret: this.config.passwordSecret
})
const {
success: checkPasswordSuccess
} = passwordUtils.checkUserPassword({
password: oldPassword,
passwordHash: userRecord.password,
passwordSecretVersion: userRecord.password_secret_version,
autoRefresh: false
})
if (!checkPasswordSuccess) {
throw {
errCode: ERROR.PASSWORD_ERROR
......
......@@ -83,6 +83,7 @@ module.exports = async function (params = {}) {
}
}
const passwordUtils = new PasswordUtils({
clientInfo: this.getUniversalClientInfo(),
passwordSecret: this.config.passwordSecret
})
const {
......
......@@ -107,6 +107,7 @@ module.exports = async function (params = {}) {
if (password) {
const passwordUtils = new PasswordUtils({
clientInfo: this.getUniversalClientInfo(),
passwordSecret: this.config.passwordSecret
})
const {
......
......@@ -56,7 +56,7 @@ module.exports = async function (params = {}) {
} = params
const {
appId
} = this.getClientInfo()
} = this.getUniversalClientInfo()
const qqApi = initQQ.call(this)
const qqPlatform = getQQPlatform.call(this)
let apiName
......
......@@ -2,10 +2,7 @@ const {
initWeixin
} = require('../../lib/third-party/index')
const {
getWeixinPlatform,
getWeixinAccessToken,
generateWeixinCache,
saveWeixinUserKey
getWeixinAccessToken
} = require('../../lib/utils/weixin')
const {
ERROR
......
......@@ -11,7 +11,8 @@ const {
const {
generateWeixinCache,
getWeixinPlatform,
saveWeixinUserKey
saveWeixinUserKey,
saveSecureNetworkCache
} = require('../../lib/utils/weixin')
const {
LOG_TYPE
......@@ -37,11 +38,13 @@ module.exports = async function (params = {}) {
this.middleware.validate(params, schema)
const {
code,
inviteCode
inviteCode,
// 内部参数,暂不暴露
secureNetworkCache = false
} = params
const {
appId
} = this.getClientInfo()
} = this.getUniversalClientInfo()
const weixinApi = initWeixin.call(this)
const weixinPlatform = getWeixinPlatform.call(this)
let apiName
......@@ -81,6 +84,18 @@ module.exports = async function (params = {}) {
expired: accessTokenExpired // App端微信用户accessToken过期时间
} = getWeixinAccountResult
if (secureNetworkCache) {
if (weixinPlatform !== 'mp') {
throw new Error('Unsupported weixin platform, expect mp-weixin')
}
await saveSecureNetworkCache({
code,
openid,
unionid,
sessionKey
})
}
const {
type,
user
......
......@@ -38,7 +38,7 @@ module.exports = async function (params = {}) {
}).limit(1).get()
if (getAdminRes.data.length > 0) {
const [admin] = getAdminRes.data
const appId = this.getClientInfo().appId
const appId = this.getUniversalClientInfo().appId
if (!admin.dcloud_appid || (admin.dcloud_appid && admin.dcloud_appid.includes(appId))) {
return {
......
......@@ -49,7 +49,7 @@ module.exports = async function (params = {}) {
accessTokenExpired
} = params
const qqPlatform = getQQPlatform.call(this)
const appId = this.getClientInfo().appId
const appId = this.getUniversalClientInfo().appId
const qqApi = initQQ.call(this)
const clientPlatform = this.clientPlatform
const apiName = clientPlatform === 'mp-qq' ? 'code2Session' : 'getOpenidByToken'
......@@ -78,7 +78,7 @@ module.exports = async function (params = {}) {
const bindAccount = {
qq_openid: {
[clientPlatform]: openid
[qqPlatform]: openid
},
qq_unionid: unionid
}
......
......@@ -34,7 +34,7 @@ module.exports = async function (params = {}) {
code
} = params
const weixinPlatform = getWeixinPlatform.call(this)
const appId = this.getClientInfo().appId
const appId = this.getUniversalClientInfo().appId
const weixinApi = initWeixin.call(this)
const clientPlatform = this.clientPlatform
......@@ -64,7 +64,7 @@ module.exports = async function (params = {}) {
const bindAccount = {
wx_openid: {
[clientPlatform]: openid
[weixinPlatform]: openid
},
wx_unionid: unionid
}
......
......@@ -16,7 +16,7 @@ const {
*/
module.exports = async function () {
const { uid } = this.authInfo
const { appId } = this.getClientInfo()
const { appId } = this.getUniversalClientInfo()
const qqPlatform = getQQPlatform.call(this)
await preUnBind.call(this, {
......
module.exports = {
refreshToken: require('./refresh-token'),
setPushCid: require('./set-push-cid')
setPushCid: require('./set-push-cid'),
secureNetworkHandshakeByWeixin: require('./secure-network-handshake-by-weixin')
}
......@@ -7,7 +7,7 @@ module.exports = async function () {
token,
tokenExpired
} = await this.uniIdCommon.refreshToken({
token: this.getUniIdToken()
token: this.getUniversalUniIdToken()
})
return {
errCode: 0,
......
const {
ERROR
} = require('../../common/error')
const {
initWeixin
} = require('../../lib/third-party/index')
const {
saveWeixinUserKey,
saveSecureNetworkCache
} = require('../../lib/utils/weixin')
const loginByWeixin = require('../login/login-by-weixin')
/**
* 微信安全网络握手
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-id-pages.html#set-push-cid
* @param {object} params
* @param {string} params.code 微信登录返回的code
* @param {boolean} params.callLoginByWeixin 是否同时调用一次微信登录
* @returns
*/
module.exports = async function (params = {}) {
const schema = {
code: 'string',
callLoginByWeixin: {
type: 'boolean',
required: false
}
}
this.middleware.validate(params, schema)
let platform = this.clientPlatform
if (platform !== 'mp-weixin') {
throw new Error(`[secureNetworkHandshake] platform ${platform} is not supported`)
}
const {
code,
callLoginByWeixin = false
} = params
if (callLoginByWeixin) {
return loginByWeixin.call(this, {
code,
secureNetworkCache: true
})
}
const weixinApi = initWeixin.call(this)
let getWeixinAccountResult
try {
getWeixinAccountResult = await weixinApi.code2Session(code)
} catch (error) {
console.error(error)
throw {
errCode: ERROR.GET_THIRD_PARTY_ACCOUNT_FAILED
}
}
const {
openid,
unionid,
sessionKey // 微信小程序用户sessionKey
} = getWeixinAccountResult
await saveSecureNetworkCache.call(this, {
code,
openid,
unionid,
sessionKey
})
await saveWeixinUserKey.call(this, {
openid,
sessionKey
})
return {
errCode: 0
}
}
......@@ -25,7 +25,7 @@ async function setOpendbDevice ({
screenHeight,
romName,
romVersion
} = this.getClientInfo()
} = this.getUniversalClientInfo()
const platform = this.clientPlatform
const now = Date.now()
......@@ -80,7 +80,7 @@ module.exports = async function (params = {}) {
deviceId,
appId,
osName
} = this.getClientInfo()
} = this.getUniversalClientInfo()
let platform = this.clientPlatform
if (platform === 'app') {
platform += osName
......
......@@ -18,7 +18,7 @@ module.exports = async function (params = {}) {
}
this.middleware.validate(params, schema)
const deviceId = this.getClientInfo().deviceId
const deviceId = this.getUniversalClientInfo().deviceId
const {
scene
} = params
......
......@@ -18,7 +18,7 @@ module.exports = async function (params = {}) {
}
this.middleware.validate(params, schema)
const deviceId = this.getClientInfo().deviceId
const deviceId = this.getUniversalClientInfo().deviceId
const {
scene
} = params
......
{
"name": "uni-id-co",
"version": "1.0.26",
"version": "1.0.30",
"description": "",
"main": "index.js",
"keywords": [],
......@@ -14,6 +14,10 @@
"uni-open-bridge-common": "file:../../../../uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common"
},
"extensions": {
"uni-cloud-sms": {}
"uni-cloud-sms": {},
"uni-cloud-redis": {}
},
"cloudfunction-config": {
"keepRunningAfterReturn": false
}
}
## 3.3.29(2022-10-20)
- 使用[uni-open-bridge-common](https://uniapp.dcloud.net.cn/uniCloud/uni-open-bridge.html#common)存储用户三方凭证,以便其他服务使用。
## 3.3.28(2022-07-27)
- 修复 app端微信登录返回的accessToken过期时间(expired)不正确的Bug
## 3.3.27(2022-07-27)
......
{
"id": "uni-id",
"displayName": "uni-id",
"version": "3.3.28",
"version": "3.3.29",
"description": "简单、统一、可扩展的用户中心",
"keywords": [
"uniid",
......@@ -14,7 +14,7 @@
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
......@@ -35,7 +35,7 @@
"type": "unicloud-template-function"
},
"uni_modules": {
"dependencies": ["uni-config-center"],
"dependencies": ["uni-config-center", "uni-open-bridge-common"],
"encrypt": [],
"platforms": {
"cloud": {
......
{
"name": "uni-id",
"version": "3.3.17",
"version": "3.3.29",
"description": "uni-id for uniCloud",
"main": "index.js",
"homepage": "https://uniapp.dcloud.io/uniCloud/uni-id",
......@@ -11,6 +11,7 @@
"author": "",
"license": "Apache-2.0",
"dependencies": {
"uni-config-center": "file:../../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
"uni-config-center": "file:../../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center",
"uni-open-bridge-common": "file:../../../../../uni-open-bridge-common/uniCloud/cloudfunctions/common/uni-open-bridge-common"
}
}
\ No newline at end of file
## 1.2.3(2022-11-14)
- uni-list-chat 新增 avatar 支持 fileId
## 1.2.2(2022-11-11)
- uni-list 新增属性 render-reverse 详情参考:[https://uniapp.dcloud.net.cn/component/list.html](https://uniapp.dcloud.net.cn/component/list.html)
- uni-list-chat note属性 支持:“草稿”字样 加红显示 详情参考uni-im:[https://ext.dcloud.net.cn/plugin?name=uni-im](https://ext.dcloud.net.cn/plugin?name=uni-im)
- uni-list-item 新增属性 customStyle 支持设置padding、backgroundColor
## 1.2.1(2022-03-30)
- 删除无用文件
## 1.2.0(2021-11-23)
......
......@@ -7,7 +7,7 @@
<view class="uni-list-chat__container">
<view class="uni-list-chat__header-warp">
<view v-if="avatarCircle || avatarList.length === 0" class="uni-list-chat__header" :class="{ 'header--circle': avatarCircle }">
<image class="uni-list-chat__header-image" :class="{ 'header--circle': avatarCircle }" :src="avatar" mode="aspectFill"></image>
<image class="uni-list-chat__header-image" :class="{ 'header--circle': avatarCircle }" :src="avatarUrl" mode="aspectFill"></image>
</view>
<!-- 头像组 -->
<view v-else class="uni-list-chat__header">
......@@ -24,7 +24,10 @@
<view class="uni-list-chat__content">
<view class="uni-list-chat__content-main">
<text class="uni-list-chat__content-title uni-ellipsis">{{ title }}</text>
<text class="uni-list-chat__content-note uni-ellipsis">{{ note }}</text>
<view style="flex-direction: row;">
<text class="draft" v-if="isDraft">[草稿]</text>
<text class="uni-list-chat__content-note uni-ellipsis">{{isDraft?note.slice(14,-1):note}}</text>
</view>
</view>
<view class="uni-list-chat__content-extra">
<slot>
......@@ -121,6 +124,9 @@
},
// inject: ['list'],
computed: {
isDraft(){
return this.note.slice(0,14) == '[uni-im-draft]'
},
isSingle() {
if (this.badgeText === 'dot') {
return 'uni-badge--dot';
......@@ -146,12 +152,32 @@
}
}
},
watch: {
avatar:{
handler(avatar) {
if(avatar.includes('://')){
uniCloud.getTempFileURL({
fileList: [avatar]
}).then(res=>{
// console.log(res);
// 兼容uniCloud私有化部署
let fileList = res.fileList || res.result.fileList
this.avatarUrl = fileList[0].tempFileURL
})
}else{
this.avatarUrl = avatar
}
},
immediate: true
}
},
data() {
return {
isFirstChild: false,
border: true,
// avatarList: 3,
imageWidth: 50
imageWidth: 50,
avatarUrl:''
};
},
mounted() {
......@@ -445,13 +471,20 @@
overflow: hidden;
}
.uni-list-chat__content-note {
.draft ,.uni-list-chat__content-note {
margin-top: 3px;
color: $note-color;
font-size: $note-size;
font-weight: $title-weight;
overflow: hidden;
}
.draft{
color: #eb3a41;
/* #ifndef APP-NVUE */
flex-shrink: 0;
/* #endif */
padding-right: 3px;
}
.uni-list-chat__content-extra {
/* #ifndef APP-NVUE */
......
<template>
<!-- #ifdef APP-NVUE -->
<cell>
<cell keep-scroll-position>
<!-- #endif -->
<view :class="{ 'uni-list-item--disabled': disabled }"
<view :class="{ 'uni-list-item--disabled': disabled }" :style="{'background-color':customStyle.backgroundColor}"
:hover-class="(!clickable && !link) || disabled || showSwitch ? '' : 'uni-list-item--hover'"
class="uni-list-item" @click="onClick">
<view v-if="!isFirstChild" class="border--left" :class="{ 'uni-list--border': border }"></view>
<view class="uni-list-item__container"
:class="{ 'container--right': showArrow || link, 'flex--direction': direction === 'column' }">
:class="{ 'container--right': showArrow || link, 'flex--direction': direction === 'column'}"
:style="{padding:customStyle.padding}">
<slot name="header">
<view class="uni-list-item__header">
<view v-if="thumb" class="uni-list-item__icon">
......@@ -96,7 +96,7 @@
default: ''
},
ellipsis: {
type: [Number,String],
type: [Number, String],
default: 0
},
disabled: {
......@@ -139,7 +139,7 @@
type: String,
default: 'success'
},
badgeStyle:{
badgeStyle: {
type: Object,
default () {
return {}
......@@ -174,6 +174,15 @@
border: {
type: Boolean,
default: true
},
customStyle: {
type: Object,
default () {
return {
padding: '',
backgroundColor: '#FFFFFF'
}
}
}
},
// inject: ['list'],
......@@ -275,6 +284,7 @@
$uni-bg-color-hover:#f1f1f1;
$uni-text-color-grey:#999;
$list-item-pd: $uni-spacing-col-lg $uni-spacing-row-lg;
.uni-list-item {
/* #ifndef APP-NVUE */
display: flex;
......@@ -289,12 +299,15 @@
cursor: pointer;
/* #endif */
}
.uni-list-item--disabled {
opacity: 0.3;
}
.uni-list-item--hover {
background-color: $uni-bg-color-hover;
}
.uni-list-item__container {
position: relative;
/* #ifndef APP-NVUE */
......@@ -307,9 +320,11 @@
overflow: hidden;
// align-items: center;
}
.container--right {
padding-right: 0;
}
// .border--left {
// margin-left: $uni-spacing-row-lg;
// }
......@@ -324,6 +339,7 @@
border-top-width: 0.5px;
/* #endif */
}
/* #ifndef APP-NVUE */
.uni-list--border:after {
position: absolute;
......@@ -336,6 +352,7 @@
transform: scaleY(0.5);
background-color: $uni-border-color;
}
/* #endif */
.uni-list-item__content {
/* #ifndef APP-NVUE */
......@@ -349,20 +366,24 @@
justify-content: space-between;
overflow: hidden;
}
.uni-list-item__content--center {
justify-content: center;
}
.uni-list-item__content-title {
font-size: $uni-font-size-base;
color: #3b4144;
overflow: hidden;
}
.uni-list-item__content-note {
margin-top: 6rpx;
color: $uni-text-color-grey;
font-size: $uni-font-size-sm;
overflow: hidden;
}
.uni-list-item__extra {
// width: 25%;
/* #ifndef APP-NVUE */
......@@ -372,6 +393,7 @@
justify-content: flex-end;
align-items: center;
}
.uni-list-item__header {
/* #ifndef APP-NVUE */
display: flex;
......@@ -379,12 +401,14 @@
flex-direction: row;
align-items: center;
}
.uni-list-item__icon {
margin-right: 18rpx;
flex-direction: row;
justify-content: center;
align-items: center;
}
.uni-list-item__icon-img {
/* #ifndef APP-NVUE */
display: block;
......@@ -393,6 +417,7 @@
width: $uni-img-size-base;
margin-right: 10px;
}
.uni-icon-wrapper {
/* #ifndef APP-NVUE */
display: flex;
......@@ -400,33 +425,40 @@
align-items: center;
padding: 0 10px;
}
.flex--direction {
flex-direction: column;
/* #ifndef APP-NVUE */
align-items: initial;
/* #endif */
}
.flex--justify {
/* #ifndef APP-NVUE */
justify-content: initial;
/* #endif */
}
.uni-list--lg {
height: $uni-img-size-lg;
width: $uni-img-size-lg;
}
.uni-list--base {
height: $uni-img-size-base;
width: $uni-img-size-base;
}
.uni-list--sm {
height: $uni-img-size-sm;
width: $uni-img-size-sm;
}
.uni-list-item__extra-text {
color: $uni-text-color-grey;
font-size: $uni-font-size-sm;
}
.uni-ellipsis-1 {
/* #ifndef APP-NVUE */
overflow: hidden;
......@@ -435,9 +467,10 @@
/* #endif */
/* #ifdef APP-NVUE */
lines: 1;
text-overflow:ellipsis;
text-overflow: ellipsis;
/* #endif */
}
.uni-ellipsis-2 {
/* #ifndef APP-NVUE */
overflow: hidden;
......@@ -448,7 +481,7 @@
/* #endif */
/* #ifdef APP-NVUE */
lines: 2;
text-overflow:ellipsis;
text-overflow: ellipsis;
/* #endif */
}
</style>
......@@ -7,18 +7,21 @@
</view>
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
<list class="uni-list" :class="{ 'uni-list--border': border }" :enableBackToTop="enableBackToTop" loadmoreoffset="15"><slot /></list>
<list :bounce="false" :scrollable="true" show-scrollbar :render-reverse="renderReverse" @scroll="scroll" class="uni-list" :class="{ 'uni-list--border': border }" :enableBackToTop="enableBackToTop"
loadmoreoffset="15">
<slot />
</list>
<!-- #endif -->
</template>
<script>
/**
/**
* List 列表
* @description 列表组件
* @tutorial https://ext.dcloud.net.cn/plugin?id=24
* @property {String} border = [true|false] 标题
*/
export default {
export default {
name: 'uniList',
'mp-weixin': {
options: {
......@@ -26,6 +29,10 @@ export default {
}
},
props: {
stackFromEnd:{
type: Boolean,
default:false
},
enableBackToTop: {
type: [Boolean, String],
default: false
......@@ -37,6 +44,10 @@ export default {
border: {
type: Boolean,
default: true
},
renderReverse:{
type: Boolean,
default: false
}
},
// provide() {
......@@ -50,23 +61,27 @@ export default {
methods: {
loadMore(e) {
this.$emit('scrolltolower');
},
scroll(e) {
this.$emit('scroll', e);
}
}
};
};
</script>
<style lang="scss" >
$uni-bg-color:#ffffff;
$uni-border-color:#e5e5e5;
.uni-list {
<style lang="scss">
$uni-bg-color:#ffffff;
$uni-border-color:#e5e5e5;
.uni-list {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
background-color: $uni-bg-color;
position: relative;
flex-direction: column;
}
}
.uni-list--border {
.uni-list--border {
position: relative;
/* #ifdef APP-NVUE */
border-top-color: $uni-border-color;
......@@ -77,11 +92,11 @@ $uni-border-color:#e5e5e5;
border-bottom-width: 0.5px;
/* #endif */
z-index: -1;
}
}
/* #ifndef APP-NVUE */
/* #ifndef APP-NVUE */
.uni-list--border-top {
.uni-list--border-top {
position: absolute;
top: 0;
right: 0;
......@@ -91,9 +106,9 @@ $uni-border-color:#e5e5e5;
transform: scaleY(0.5);
background-color: $uni-border-color;
z-index: 1;
}
}
.uni-list--border-bottom {
.uni-list--border-bottom {
position: absolute;
bottom: 0;
right: 0;
......@@ -102,7 +117,7 @@ $uni-border-color:#e5e5e5;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
background-color: $uni-border-color;
}
}
/* #endif */
/* #endif */
</style>
{
"id": "uni-list",
"displayName": "uni-list 列表",
"version": "1.2.1",
"version": "1.2.3",
"description": "List 组件 ,帮助使用者快速构建列表。",
"keywords": [
"",
......@@ -18,11 +18,7 @@
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
......@@ -39,7 +35,8 @@
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": [
......
## 1.3.9(2022-10-13)
- 修复 条件编译错误的bug
## 1.3.8(2022-10-12)
- 修复 nvue 环境 fixed 为 true 的情况下,无法置顶的 bug
## 1.3.7(2022-08-11)
- 修复 nvue 环境下 fixed 为 true 的情况下,无法置顶的 bug
## 1.3.6(2022-06-30)
- 修复 组件示例中插槽用法无法显示内容的bug
## 1.3.5(2022-05-24)
......
<template>
<view class="uni-navbar" :class="{'uni-dark':dark}">
<view :class="{ 'uni-navbar--fixed': fixed, 'uni-navbar--shadow': shadow, 'uni-navbar--border': border }"
:style="{ 'background-color': themeBgColor }" class="uni-navbar__content">
<view class="uni-navbar" :class="{'uni-dark':dark, 'uni-nvue-fixed': fixed}">
<view class="uni-navbar__content" :class="{ 'uni-navbar--fixed': fixed, 'uni-navbar--shadow': shadow, 'uni-navbar--border': border }"
:style="{ 'background-color': themeBgColor }" >
<status-bar v-if="statusBar" />
<view :style="{ color: themeColor,backgroundColor: themeBgColor ,height:navbarHeight}"
class="uni-navbar__header">
......@@ -38,10 +38,12 @@
</view>
</view>
</view>
<!-- #ifndef APP-NVUE -->
<view class="uni-navbar__placeholder" v-if="fixed">
<status-bar v-if="statusBar" />
<view class="uni-navbar__placeholder-view" :style="{ height:navbarHeight}" />
</view>
<!-- #endif -->
</view>
</template>
......@@ -50,6 +52,8 @@
const getVal = (val) => typeof val === 'number' ? val + 'px' : val;
/**
*
*
* NavBar 自定义导航栏
* @description 导航栏组件,主要用于头部导航
* @tutorial https://ext.dcloud.net.cn/plugin?id=52
......@@ -196,6 +200,11 @@
<style lang="scss" scoped>
$nav-height: 44px;
.uni-nvue-fixed {
/* #ifdef APP-NVUE */
position: sticky;
/* #endif */
}
.uni-navbar {
// box-sizing: border-box;
}
......
{
"id": "uni-nav-bar",
"displayName": "uni-nav-bar 自定义导航栏",
"version": "1.3.6",
"version": "1.3.9",
"description": "自定义导航栏组件,主要用于头部导航。",
"keywords": [
"uni-ui",
......@@ -16,11 +16,7 @@
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
......@@ -37,7 +33,8 @@
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": [
......
{
"name" : "",
"appid" : "",
"description": "应用描述",
"versionName": "1.0.0",
"versionCode": "100",
"transformPx": false,
/* 5+App特有相关 */
"app-plus": {
"usingComponents": true,
"splashscreen": {
"alwaysShowBeforeRender": true,
"waiting": true,
"autoclose": true,
"delay": 0
},
"modules": {
"OAuth": {},
"Payment": {},
"Push": {},
"Share": {},
"Speech": {},
"VideoPlayer": {}
},
/* 应用发布信息 */
"distribute": {
/* android打包配置 */
"android": {
"permissions": [
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_MOCK_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.CALL_PHONE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.GET_TASKS\"/>",
"<uses-permission android:name=\"android.permission.INTERNET\"/>",
"<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.READ_CONTACTS\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.READ_SMS\"/>",
"<uses-permission android:name=\"android.permission.RECEIVE_BOOT_COMPLETED\"/>",
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
"<uses-permission android:name=\"android.permission.SEND_SMS\"/>",
"<uses-permission android:name=\"android.permission.SYSTEM_ALERT_WINDOW\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>",
"<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SMS\"/>",
"<uses-permission android:name=\"android.permission.RECEIVE_USER_PRESENT\"/>"
]
},
/* ios打包配置 */
"ios": {
"UIBackgroundModes": ["audio"]
},
/* SDK配置 */
"sdkConfigs": {
"speech": {
"ifly": {}
}
},
"orientation": ["portrait-primary"]
}
},
/* 快应用特有相关 */
"quickapp": {},
/* 小程序特有相关 */
"mp-weixin": {
"appid": "",
"setting": {
"urlCheck": false
},
"usingComponents": true
},
"h5": {
"template": "template.h5.html",
"router": {
"mode": "history",
"base": "/h5/"
}
}
}
## 1.4.23(2022-10-25)
- uni-datetime-picker 修复,支付宝小程序样式错乱,[详情](https://github.com/dcloudio/uni-app/issues/3861)
- uni-nav-bar 修复 条件编译错误的bug
- uni-nav-bar 修复 nvue 环境 fixed 为 true 的情况下,无法置顶的 bug
## 1.4.22(2022-09-19)
- 优化 部分组件适配 uni-scss 主题色
- uni-badge 修复 当 text 超过 max-num 时,badge 的宽度计算是根据 text 的长度计算,更改为 css 计算实际展示宽度,详见:[https://ask.dcloud.net.cn/question/150473](https://ask.dcloud.net.cn/question/150473)
......
{
"id": "uni-ui",
"displayName": "uni-ui",
"version": "1.4.22",
"version": "1.4.23",
"description": "uni-ui 是基于uni-app的、全端兼容的、高性能UI框架",
"keywords": [
"uni-ui",
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册