第一次提交

This commit is contained in:
gxwebsoft
2023-08-04 13:04:21 +08:00
commit 7a73b38bd5
764 changed files with 166417 additions and 0 deletions

182
uni_modules/mp-html/README.md Executable file
View File

@@ -0,0 +1,182 @@
## 功能介绍
- 全端支持(含 `v3、NVUE`
- 支持丰富的标签(包括 `table``video``svg` 等)
- 支持丰富的事件效果(自动预览图片、链接处理等)
- 支持设置占位图(加载中、出错时、预览时)
- 支持锚点跳转、长按复制等丰富功能
- 支持大部分 *html* 实体
- 丰富的插件(关键词搜索、内容 **编辑** 等)
- 效率高、容错性强且轻量化
查看 [功能介绍](https://jin-yufeng.gitee.io/mp-html/#/overview/feature) 了解更多
## 使用方法
- `uni_modules` 方式
1. 点击右上角的 `使用 HBuilder X 导入插件` 按钮直接导入项目或点击 `下载插件 ZIP` 按钮下载插件包并解压到项目的 `uni_modules/mp-html` 目录下
2. 在需要使用页面的 `(n)vue` 文件中添加
```html
<!-- 不需要引入,可直接使用 -->
<mp-html :content="html" />
```
```javascript
export default {
data() {
return {
html: '<div>Hello World!</div>'
}
}
}
```
3. 需要更新版本时在 `HBuilder X` 中右键 `uni_modules/mp-html` 目录选择 `从插件市场更新` 即可
- 源码方式
1. 从 [github](https://github.com/jin-yufeng/mp-html/tree/master/dist/uni-app) 或 [gitee](https://gitee.com/jin-yufeng/mp-html/tree/master/dist/uni-app) 下载源码
插件市场的 **非 uni_modules 版本** 无法更新,不建议从插件市场获取
2. 在需要使用页面的 `(n)vue` 文件中添加
```html
<mp-html :content="html" />
```
```javascript
import mpHtml from '@/components/mp-html/mp-html'
export default {
// HBuilderX 2.5.5+ 可以通过 easycom 自动引入
components: {
mpHtml
},
data() {
return {
html: '<div>Hello World!</div>'
}
}
}
```
- npm 方式
1. 在项目根目录下执行
```bash
npm install mp-html
```
2. 在需要使用页面的 `(n)vue` 文件中添加
```html
<mp-html :content="html" />
```
```javascript
import mpHtml from 'mp-html/dist/uni-app/components/mp-html/mp-html'
export default {
// 不可省略
components: {
mpHtml
},
data() {
return {
html: '<div>Hello World!</div>'
}
}
}
```
3. 需要更新版本时执行以下命令即可
```bash
npm update mp-html
```
使用 *cli* 方式运行的项目,通过 *npm* 方式引入时,需要在 *vue.config.js* 中配置 *transpileDependencies*,详情可见 [#330](https://github.com/jin-yufeng/mp-html/issues/330#issuecomment-913617687)
如果在 **nvue** 中使用还要将 `dist/uni-app/static` 目录下的内容拷贝到项目的 `static` 目录下,否则无法运行
查看 [快速开始](https://jin-yufeng.gitee.io/mp-html/#/overview/quickstart) 了解更多
## 组件属性
| 属性 | 类型 | 默认值 | 说明 |
|:---:|:---:|:---:|---|
| container-style | String | | 容器的样式([2.1.0+](https://jin-yufeng.gitee.io/mp-html/#/changelog/changelog#v210) |
| content | String | | 用于渲染的 html 字符串 |
| copy-link | Boolean | true | 是否允许外部链接被点击时自动复制 |
| domain | String | | 主域名(用于链接拼接) |
| error-img | String | | 图片出错时的占位图链接 |
| lazy-load | Boolean | false | 是否开启图片懒加载 |
| loading-img | String | | 图片加载过程中的占位图链接 |
| pause-video | Boolean | true | 是否在播放一个视频时自动暂停其他视频 |
| preview-img | Boolean | true | 是否允许图片被点击时自动预览 |
| scroll-table | Boolean | false | 是否给每个表格添加一个滚动层使其能单独横向滚动 |
| selectable | Boolean | false | 是否开启文本长按复制 |
| set-title | Boolean | true | 是否将 title 标签的内容设置到页面标题 |
| show-img-menu | Boolean | true | 是否允许图片被长按时显示菜单 |
| tag-style | Object | | 设置标签的默认样式 |
| use-anchor | Boolean | false | 是否使用锚点链接 |
查看 [属性](https://jin-yufeng.gitee.io/mp-html/#/basic/prop) 了解更多
## 组件事件
| 名称 | 触发时机 |
|:---:|---|
| load | dom 树加载完毕时 |
| ready | 图片加载完毕时 |
| error | 发生渲染错误时 |
| imgtap | 图片被点击时 |
| linktap | 链接被点击时 |
查看 [事件](https://jin-yufeng.gitee.io/mp-html/#/basic/event) 了解更多
## api
组件实例上提供了一些 `api` 方法可供调用
| 名称 | 作用 |
|:---:|---|
| in | 将锚点跳转的范围限定在一个 scroll-view 内 |
| navigateTo | 锚点跳转 |
| getText | 获取文本内容 |
| getRect | 获取富文本内容的位置和大小 |
| setContent | 设置富文本内容 |
| imgList | 获取所有图片的数组 |
查看 [api](https://jin-yufeng.gitee.io/mp-html/#/advanced/api) 了解更多
## 插件扩展
除基本功能外,本组件还提供了丰富的扩展,可按照需要选用
| 名称 | 作用 |
|:---:|---|
| audio | 音乐播放器 |
| editable | 富文本 **编辑**[示例项目](https://6874-html-foe72-1259071903.tcb.qcloud.la/editable.zip?sign=cc0017be203fb3dbca62d33a0c15792e&t=1608447445) |
| emoji | 解析 emoji |
| highlight | 代码块高亮显示 |
| markdown | 渲染 markdown |
| search | 关键词搜索 |
| style | 匹配 style 标签中的样式 |
| txv-video | 使用腾讯视频 |
| img-cache | 图片缓存 by [@PentaTea](https://github.com/PentaTea) |
从插件市场导入的包中 **不含有** 扩展插件,需要使用插件参考以下方法:
1. 获取完整组件包
```bash
npm install mp-html
```
2. 编辑 `tools/config.js` 中的 `plugins` 项,选择需要的插件
3. 生成新的组件包
在 `node_modules/mp-html` 目录下执行
```bash
npm install
npm run build:uni-app
```
4. 拷贝 `dist/uni-app` 中的内容到项目根目录
查看 [插件](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin) 了解更多
## 示例体验
![富文本插件](https://gitee.com/jin-yufeng/mp-html/raw/master/docs/assets/case/富文本插件.jpg)
## 关于 nvue
`nvue` 使用原生渲染,不支持部分 `css` 样式,为实现和 `html` 相同的效果,组件内部通过 `web-view` 进行渲染,性能上差于原生,根据 `weex` 官方建议,`web` 标签仅应用在非常规的降级场景。因此,如果通过原生的方式(如 `richtext`)能够满足需要,则不建议使用本组件,如果有较多的富文本内容,则可以直接使用 `vue` 页面
由于渲染方式与其他端不同,有以下限制:
1. 不支持 `lazy-load` 属性
2. 视频不支持全屏播放
纯 `nvue` 模式下,[此问题](https://ask.dcloud.net.cn/question/119678) 修复前,不支持通过 `uni_modules` 引入,需要本地引入(将 [dist/uni-app](https://github.com/jin-yufeng/mp-html/tree/master/dist/uni-app) 中的内容拷贝到项目根目录下)
## 问题反馈
遇到问题时,请先查阅 [常见问题](https://jin-yufeng.gitee.io/mp-html/#/question/faq) 和 [issue](https://github.com/jin-yufeng/mp-html/issues) 中是否已有相同的问题
可通过 [issue](https://github.com/jin-yufeng/mp-html/issues/new/choose) 、插件问答或发送邮件到 [mp_html@126.com](mailto:mp_html@126.com) 提问,不建议在评论区提问(不方便回复)
提问请严格按照 [issue 模板](https://github.com/jin-yufeng/mp-html/issues/new/choose) ,描述清楚使用环境、`html` 内容或可复现的 `demo` 项目以及复现方式,对于 **描述不清**、**无法复现** 或重复的问题将不予回复
查看 [问题反馈](https://jin-yufeng.gitee.io/mp-html/#/question/feedback) 了解更多

View File

@@ -0,0 +1,69 @@
## v2.2.12021-12-24
1. `A` `editable` 插件增加上下移动标签功能
2. `U` `editable` 插件支持在文本中间光标处插入内容
3. `F` 修复了 `nvue` 端设置 `margin` 后可能导致高度不正确的问题
4. `F` 修复了 `highlight` 插件使用压缩版的 `prism.css` 可能导致背景失效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/367)
5. `F` 修复了编辑状态下使用 `emoji` 插件内容为空时可能报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/371)
6. `F` 修复了使用 `editable` 插件后将 `selectable` 属性设置为 `force` 不生效的问题
## v2.2.02021-10-12
1. `A` 增加 `customElements` 配置项,便于添加自定义功能性标签 [详细](https://github.com/jin-yufeng/mp-html/issues/350)
2. `A` `editable` 插件增加切换音视频自动播放状态的功能 [详细](https://github.com/jin-yufeng/mp-html/pull/341) by [@leeseett](https://github.com/leeseett)
3. `A` `editable` 插件删除媒体标签时触发 `remove` 事件,便于删除已上传的文件
4. `U` `editable` 插件 `insertImg` 方法支持同时插入多张图片 [详细](https://github.com/jin-yufeng/mp-html/issues/342)
5. `U` `editable` 插入图片和音视频时支持拼接 `domian` 主域名
6. `F` 修复了内部链接参数中包含 `://` 时被认为是外部链接的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/356)
7. `F` 修复了部分 `svg` 标签名或属性名大小写不正确时不生效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/351)
8. `F` 修复了 `nvue` 页面运行到非 `app` 平台时可能样式错误的问题
## v2.1.52021-08-13
1. `A` 增加支持标签的 `dir` 属性
2. `F` 修复了 `ruby` 标签文字与拼音没有居中对齐的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/325)
3. `F` 修复了音视频标签内有 `a` 标签时可能无法播放的问题
4. `F` 修复了 `externStyle` 中的 `class` 名包含下划线或数字时可能失效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/326)
5. `F` 修复了 `h5` 端引入 `externStyle` 可能不生效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/326)
## v2.1.42021-07-14
1. `F` 修复了 `rt` 标签无法设置样式的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/318)
2. `F` 修复了表格中有单元格同时合并行和列时可能显示不正确的问题
3. `F` 修复了 `app` 端无法关闭图片长按菜单的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/322)
4. `F` 修复了 `editable` 插件只能添加图片链接不能修改的问题 [详细](https://github.com/jin-yufeng/mp-html/pull/312) by [@leeseett](https://github.com/leeseett)
## v2.1.32021-06-12
1. `A` `editable` 插件增加 `insertTable` 方法
2. `U` `editable` 插件支持编辑表格中的空白单元格 [详细](https://github.com/jin-yufeng/mp-html/issues/310)
3. `F` 修复了 `externStyle` 中使用伪类可能失效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/298)
4. `F` 修复了多个组件同时使用时 `tag-style` 属性时可能互相影响的问题 [详细](https://github.com/jin-yufeng/mp-html/pull/305) by [@woodguoyu](https://github.com/woodguoyu)
5. `F` 修复了包含 `linearGradient``svg` 可能无法显示的问题
6. `F` 修复了编译到头条小程序时可能报错的问题
7. `F` 修复了 `nvue` 端不触发 `click` 事件的问题
8. `F` 修复了 `editable` 插件尾部插入时无法撤销的问题
9. `F` 修复了 `editable` 插件的 `insertHtml` 方法只能在末尾插入的问题
10. `F` 修复了 `editable` 插件插入音频不显示的问题
## v2.1.22021-04-24
1. `A` 增加了 [img-cache](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#img-cache) 插件,可以在 `app` 端缓存图片 [详细](https://github.com/jin-yufeng/mp-html/issues/292) by [@PentaTea](https://github.com/PentaTea)
2. `U` 支持通过 `container-style` 属性设置 `white-space` 来保留连续空格和换行符 [详细](https://jin-yufeng.gitee.io/mp-html/#/question/faq#space)
3. `U` 代码风格符合 [standard](https://standardjs.com) 标准
4. `U` `editable` 插件编辑状态下支持预览视频 [详细](https://github.com/jin-yufeng/mp-html/issues/286)
5. `F` 修复了 `svg` 标签内嵌 `svg` 时无法显示的问题
6. `F` 修复了编译到支付宝和头条小程序时部分区域不可复制的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/291)
## v2.1.12021-04-09
1. 修复了对 `p` 标签设置 `tag-style` 可能不生效的问题
2. 修复了 `svg` 标签中的文本无法显示的问题
3. 修复了使用 `editable` 插件编辑表格时可能报错的问题
4. 修复了使用 `highlight` 插件运行到头条小程序时可能没有样式的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/280)
5. 修复了使用 `editable` 插件 `editable` 属性为 `false` 时会报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/284)
6. 修复了 `style` 插件连续子选择器失效的问题
7. 修复了 `editable` 插件无法修改图片和字体大小的问题
## v2.1.0.22021-03-21
修复了 `nvue` 端使用可能报错的问题
## v2.1.02021-03-20
1. `A` 增加了 [container-style](https://jin-yufeng.gitee.io/mp-html/#/basic/prop#container-style) 属性 [详细](https://gitee.com/jin-yufeng/mp-html/pulls/1)
2. `A` 增加支持 `strike` 标签
3. `A` `editable` 插件增加 `placeholder` 属性 [详细](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#editable)
4. `A` `editable` 插件增加 `insertHtml` 方法 [详细](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#editable)
5. `U` 外部样式支持标签名选择器 [详细](https://jin-yufeng.gitee.io/mp-html/#/overview/quickstart#setting)
6. `F` 修复了 `nvue` 端部分情况下可能不显示的问题
## v2.0.52021-03-12
1. `U` [linktap](https://jin-yufeng.gitee.io/mp-html/#/basic/event#linktap) 事件增加返回内部文本内容 `innerText` [详细](https://github.com/jin-yufeng/mp-html/issues/271)
2. `U` [selectable](https://jin-yufeng.gitee.io/mp-html/#/basic/prop#selectable) 属性设置为 `force` 时能够在微信 `iOS` 端生效(文本块会变成 `inline-block` [详细](https://github.com/jin-yufeng/mp-html/issues/267)
3. `F` 修复了部分情况下竖向无法滚动的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/182)
4. `F` 修复了多次修改富文本数据时部分内容可能不显示的问题
5. `F` 修复了 [腾讯视频](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#txv-video) 插件可能无法播放的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/265)
6. `F` 修复了 [highlight](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#highlight) 插件没有设置高亮语言时没有应用默认样式的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/276) by [@fuzui](https://github.com/fuzui)

View File

@@ -0,0 +1,435 @@
<template>
<view id="_root" :class="(selectable?'_select ':'')+'_root'" :style="containerStyle">
<slot v-if="!nodes[0]" />
<!-- #ifndef APP-PLUS-NVUE -->
<node v-else :childs="nodes" :opts="[lazyLoad,loadingImg,errorImg,showImgMenu]" name="span" />
<!-- #endif -->
<!-- #ifdef APP-PLUS-NVUE -->
<web-view ref="web" src="/uni_modules/mp-html/static/app-plus/mp-html/local.html" :style="'margin-top:-2px;height:' + height + 'px'" @onPostMessage="_onMessage" />
<!-- #endif -->
</view>
</template>
<script>
/**
* mp-html v2.2.1
* @description 富文本组件
* @tutorial https://github.com/jin-yufeng/mp-html
* @property {String} container-style 容器的样式
* @property {String} content 用于渲染的 html 字符串
* @property {Boolean} copy-link 是否允许外部链接被点击时自动复制
* @property {String} domain 主域名,用于拼接链接
* @property {String} error-img 图片出错时的占位图链接
* @property {Boolean} lazy-load 是否开启图片懒加载
* @property {string} loading-img 图片加载过程中的占位图链接
* @property {Boolean} pause-video 是否在播放一个视频时自动暂停其他视频
* @property {Boolean} preview-img 是否允许图片被点击时自动预览
* @property {Boolean} scroll-table 是否给每个表格添加一个滚动层使其能单独横向滚动
* @property {Boolean | String} selectable 是否开启长按复制
* @property {Boolean} set-title 是否将 title 标签的内容设置到页面标题
* @property {Boolean} show-img-menu 是否允许图片被长按时显示菜单
* @property {Object} tag-style 标签的默认样式
* @property {Boolean | Number} use-anchor 是否使用锚点链接
* @event {Function} load dom 结构加载完毕时触发
* @event {Function} ready 所有图片加载完毕时触发
* @event {Function} imgTap 图片被点击时触发
* @event {Function} linkTap 链接被点击时触发
* @event {Function} error 媒体加载出错时触发
*/
// #ifndef APP-PLUS-NVUE
import node from './node/node'
// #endif
const plugins=[]
const Parser = require('./parser')
// #ifdef APP-PLUS-NVUE
const dom = weex.requireModule('dom')
// #endif
export default {
name: 'mp-html',
data () {
return {
nodes: [],
// #ifdef APP-PLUS-NVUE
height: 3
// #endif
}
},
props: {
containerStyle: {
type: String,
default: ''
},
content: {
type: String,
default: ''
},
copyLink: {
type: [Boolean, String],
default: true
},
domain: String,
errorImg: {
type: String,
default: ''
},
lazyLoad: {
type: [Boolean, String],
default: false
},
loadingImg: {
type: String,
default: ''
},
pauseVideo: {
type: [Boolean, String],
default: true
},
previewImg: {
type: [Boolean, String],
default: true
},
scrollTable: [Boolean, String],
selectable: [Boolean, String],
setTitle: {
type: [Boolean, String],
default: true
},
showImgMenu: {
type: [Boolean, String],
default: true
},
tagStyle: Object,
useAnchor: [Boolean, Number]
},
// #ifndef APP-PLUS-NVUE
components: {
node
},
// #endif
watch: {
content (content) {
this.setContent(content)
}
},
created () {
this.plugins = []
for (let i = plugins.length; i--;) {
this.plugins.push(new plugins[i](this))
}
},
mounted () {
if (this.content && !this.nodes.length) {
this.setContent(this.content)
}
},
beforeDestroy () {
this._hook('onDetached')
clearInterval(this._timer)
},
methods: {
/**
* @description 将锚点跳转的范围限定在一个 scroll-view 内
* @param {Object} page scroll-view 所在页面的示例
* @param {String} selector scroll-view 的选择器
* @param {String} scrollTop scroll-view scroll-top 属性绑定的变量名
*/
in (page, selector, scrollTop) {
// #ifndef APP-PLUS-NVUE
if (page && selector && scrollTop) {
this._in = {
page,
selector,
scrollTop
}
}
// #endif
},
/**
* @description 锚点跳转
* @param {String} id 要跳转的锚点 id
* @param {Number} offset 跳转位置的偏移量
* @returns {Promise}
*/
navigateTo (id, offset) {
return new Promise((resolve, reject) => {
if (!this.useAnchor) {
reject(Error('Anchor is disabled'))
return
}
offset = offset || parseInt(this.useAnchor) || 0
// #ifdef APP-PLUS-NVUE
if (!id) {
dom.scrollToElement(this.$refs.web, {
offset
})
resolve()
} else {
this._navigateTo = {
resolve,
reject,
offset
}
this.$refs.web.evalJs('uni.postMessage({data:{action:"getOffset",offset:(document.getElementById(' + id + ')||{}).offsetTop}})')
}
// #endif
// #ifndef APP-PLUS-NVUE
let deep = ' '
// #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO
deep = '>>>'
// #endif
const selector = uni.createSelectorQuery()
// #ifndef MP-ALIPAY
.in(this._in ? this._in.page : this)
// #endif
.select((this._in ? this._in.selector : '._root') + (id ? `${deep}#${id}` : '')).boundingClientRect()
if (this._in) {
selector.select(this._in.selector).scrollOffset()
.select(this._in.selector).boundingClientRect()
} else {
// 获取 scroll-view 的位置和滚动距离
selector.selectViewport().scrollOffset() // 获取窗口的滚动距离
}
selector.exec(res => {
if (!res[0]) {
reject(Error('Label not found'))
return
}
const scrollTop = res[1].scrollTop + res[0].top - (res[2] ? res[2].top : 0) + offset
if (this._in) {
// scroll-view 跳转
this._in.page[this._in.scrollTop] = scrollTop
} else {
// 页面跳转
uni.pageScrollTo({
scrollTop,
duration: 300
})
}
resolve()
})
// #endif
})
},
/**
* @description 获取文本内容
* @return {String}
*/
getText (nodes) {
let text = '';
(function traversal (nodes) {
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i]
if (node.type === 'text') {
text += node.text.replace(/&amp;/g, '&')
} else if (node.name === 'br') {
text += '\n'
} else {
// 块级标签前后加换行
const isBlock = node.name === 'p' || node.name === 'div' || node.name === 'tr' || node.name === 'li' || (node.name[0] === 'h' && node.name[1] > '0' && node.name[1] < '7')
if (isBlock && text && text[text.length - 1] !== '\n') {
text += '\n'
}
// 递归获取子节点的文本
if (node.children) {
traversal(node.children)
}
if (isBlock && text[text.length - 1] !== '\n') {
text += '\n'
} else if (node.name === 'td' || node.name === 'th') {
text += '\t'
}
}
}
})(nodes || this.nodes)
return text
},
/**
* @description 获取内容大小和位置
* @return {Promise}
*/
getRect () {
return new Promise((resolve, reject) => {
uni.createSelectorQuery()
// #ifndef MP-ALIPAY
.in(this)
// #endif
.select('#_root').boundingClientRect().exec(res => res[0] ? resolve(res[0]) : reject(Error('Root label not found')))
})
},
/**
* @description 设置内容
* @param {String} content html 内容
* @param {Boolean} append 是否在尾部追加
*/
setContent (content, append) {
if (!append || !this.imgList) {
this.imgList = []
}
const nodes = new Parser(this).parse(content)
// #ifdef APP-PLUS-NVUE
if (this._ready) {
this._set(nodes, append)
}
// #endif
this.$set(this, 'nodes', append ? (this.nodes || []).concat(nodes) : nodes)
// #ifndef APP-PLUS-NVUE
this._videos = []
this.$nextTick(() => {
this._hook('onLoad')
this.$emit('load')
})
// 等待图片加载完毕
let height
clearInterval(this._timer)
this._timer = setInterval(() => {
this.getRect().then(rect => {
// 350ms 总高度无变化就触发 ready 事件
if (rect.height === height) {
this.$emit('ready', rect)
clearInterval(this._timer)
}
height = rect.height
}).catch(() => { })
}, 350)
// #endif
},
/**
* @description 调用插件钩子函数
*/
_hook (name) {
for (let i = plugins.length; i--;) {
if (this.plugins[i][name]) {
this.plugins[i][name]()
}
}
},
// #ifdef APP-PLUS-NVUE
/**
* @description 设置内容
*/
_set (nodes, append) {
this.$refs.web.evalJs('setContent(' + JSON.stringify(nodes) + ',' + JSON.stringify([this.containerStyle.replace(/(?:margin|padding)[^;]+/g, ''), this.errorImg, this.loadingImg, this.pauseVideo, this.scrollTable, this.selectable]) + ',' + append + ')')
},
/**
* @description 接收到 web-view 消息
*/
_onMessage (e) {
const message = e.detail.data[0]
switch (message.action) {
// web-view 初始化完毕
case 'onJSBridgeReady':
this._ready = true
if (this.nodes) {
this._set(this.nodes)
}
break
// 内容 dom 加载完毕
case 'onLoad':
this.height = message.height
this._hook('onLoad')
this.$emit('load')
break
// 所有图片加载完毕
case 'onReady':
this.getRect().then(res => {
this.$emit('ready', res)
}).catch(() => { })
break
// 总高度发生变化
case 'onHeightChange':
this.height = message.height
break
// 图片点击
case 'onImgTap':
this.$emit('imgtap', message.attrs)
if (this.previewImg) {
uni.previewImage({
current: parseInt(message.attrs.i),
urls: this.imgList
})
}
break
// 链接点击
case 'onLinkTap': {
const href = message.attrs.href
this.$emit('linktap', message.attrs)
if (href) {
// 锚点跳转
if (href[0] === '#') {
if (this.useAnchor) {
dom.scrollToElement(this.$refs.web, {
offset: message.offset
})
}
} else if (href.includes('://')) {
// 打开外链
if (this.copyLink) {
plus.runtime.openWeb(href)
}
} else {
uni.navigateTo({
url: href,
fail () {
uni.switchTab({
url: href
})
}
})
}
}
break
}
// 获取到锚点的偏移量
case 'getOffset':
if (typeof message.offset === 'number') {
dom.scrollToElement(this.$refs.web, {
offset: message.offset + this._navigateTo.offset
})
this._navigateTo.resolve()
} else {
this._navigateTo.reject(Error('Label not found'))
}
break
// 点击
case 'onClick':
this.$emit('tap')
this.$emit('click')
break
// 出错
case 'onError':
this.$emit('error', {
source: message.source,
attrs: message.attrs
})
}
}
// #endif
}
}
</script>
<style>
/* #ifndef APP-PLUS-NVUE */
/* 根节点样式 */
._root {
padding: 1px 0;
overflow-x: auto;
overflow-y: hidden;
-webkit-overflow-scrolling: touch;
}
/* 长按复制 */
._select {
user-select: text;
}
/* #endif */
</style>

View File

@@ -0,0 +1,525 @@
<template>
<view :id="attrs.id" :class="'_block _'+name+' '+attrs.class" :style="attrs.style">
<block v-for="(n, i) in childs" v-bind:key="i">
<!-- 图片 -->
<!-- 占位图 -->
<image v-if="n.name==='img'&&((opts[1]&&!ctrl[i])||ctrl[i]<0)" class="_img" :style="n.attrs.style" :src="ctrl[i]<0?opts[2]:opts[1]" mode="widthFix" />
<!-- 显示图片 -->
<!-- #ifdef H5 || APP-PLUS -->
<img v-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+n.attrs.style" :src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap"/>
<!-- #endif -->
<!-- #ifndef H5 || APP-PLUS -->
<image v-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;height:1px;'+n.attrs.style" :src="n.attrs.src" :mode="n.h?'':'widthFix'" :lazy-load="opts[0]" :webp="n.webp" :show-menu-by-longpress="opts[3]&&!n.attrs.ignore" :image-menu-prevent="!opts[3]||n.attrs.ignore" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
<!-- #endif -->
<!-- 文本 -->
<!-- #ifndef MP-BAIDU || MP-ALIPAY || MP-TOUTIAO -->
<text v-else-if="n.text" :user-select="n.us" decode>{{n.text}}</text>
<!-- #endif -->
<text v-else-if="n.name==='br'">\n</text>
<!-- 链接 -->
<view v-else-if="n.name==='a'" :id="n.attrs.id" :class="(n.attrs.href?'_a ':'')+n.attrs.class" hover-class="_hover" :style="'display:inline;'+n.attrs.style" :data-i="i" @tap.stop="linkTap">
<node name="span" :childs="n.children" :opts="opts" style="display:inherit" />
</view>
<!-- 视频 -->
<!-- #ifdef APP-PLUS -->
<view v-else-if="n.html" :id="n.attrs.id" :class="'_video '+n.attrs.class" :style="n.attrs.style" v-html="n.html" />
<!-- #endif -->
<!-- #ifndef APP-PLUS -->
<video v-else-if="n.name==='video'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :autoplay="n.attrs.autoplay" :controls="n.attrs.controls" :loop="n.attrs.loop" :muted="n.attrs.muted" :poster="n.attrs.poster" :src="n.src[ctrl[i]||0]" :data-i="i" @play="play" @error="mediaError" />
<!-- #endif -->
<!-- #ifdef H5 || APP-PLUS -->
<iframe v-else-if="n.name==='iframe'" :style="n.attrs.style" :allowfullscreen="n.attrs.allowfullscreen" :frameborder="n.attrs.frameborder" :src="n.attrs.src" />
<embed v-else-if="n.name==='embed'" :style="n.attrs.style" :src="n.attrs.src" />
<!-- #endif -->
<!-- #ifndef MP-TOUTIAO -->
<!-- 音频 -->
<audio v-else-if="n.name==='audio'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :author="n.attrs.author" :controls="n.attrs.controls" :loop="n.attrs.loop" :name="n.attrs.name" :poster="n.attrs.poster" :src="n.src[ctrl[i]||0]" :data-i="i" @play="play" @error="mediaError" />
<!-- #endif -->
<view v-else-if="(n.name==='table'&&n.c)||n.name==='li'" :id="n.attrs.id" :class="'_'+n.name+' '+n.attrs.class" :style="n.attrs.style">
<node v-if="n.name==='li'" :childs="n.children" :opts="opts" />
<view v-else v-for="(tbody, x) in n.children" v-bind:key="x" :class="'_'+tbody.name+' '+tbody.attrs.class" :style="tbody.attrs.style">
<node v-if="tbody.name==='td'||tbody.name==='th'" :childs="tbody.children" :opts="opts" />
<block v-else v-for="(tr, y) in tbody.children" v-bind:key="y">
<view v-if="tr.name==='td'||tr.name==='th'" :class="'_'+tr.name+' '+tr.attrs.class" :style="tr.attrs.style">
<node :childs="tr.children" :opts="opts" />
</view>
<view v-else :class="'_'+tr.name+' '+tr.attrs.class" :style="tr.attrs.style">
<view v-for="(td, z) in tr.children" v-bind:key="z" :class="'_'+td.name+' '+td.attrs.class" :style="td.attrs.style">
<node :childs="td.children" :opts="opts" />
</view>
</view>
</block>
</view>
</view>
<!-- 富文本 -->
<!-- #ifdef H5 || MP-WEIXIN || MP-QQ || APP-PLUS || MP-360 -->
<rich-text v-else-if="handler.use(n)" :id="n.attrs.id" :style="n.f" :nodes="[n]" />
<!-- #endif -->
<!-- #ifndef H5 || MP-WEIXIN || MP-QQ || APP-PLUS || MP-360 -->
<rich-text v-else-if="!n.c" :id="n.attrs.id" :style="n.f+';display:inline'" :preview="false" :nodes="[n]" />
<!-- #endif -->
<!-- 继续递归 -->
<view v-else-if="n.c===2" :id="n.attrs.id" :class="'_block _'+n.name+' '+n.attrs.class" :style="n.f+';'+n.attrs.style">
<node v-for="(n2, j) in n.children" v-bind:key="j" :style="n2.f" :name="n2.name" :attrs="n2.attrs" :childs="n2.children" :opts="opts" />
</view>
<node v-else :style="n.f" :name="n.name" :attrs="n.attrs" :childs="n.children" :opts="opts" />
</block>
</view>
</template>
<script module="handler" lang="wxs">
// 行内标签列表
var inlineTags = {
abbr: true,
b: true,
big: true,
code: true,
del: true,
em: true,
i: true,
ins: true,
label: true,
q: true,
small: true,
span: true,
strong: true,
sub: true,
sup: true
}
/**
* @description 是否使用 rich-text 显示剩余内容
*/
module.exports = {
use: function (item) {
if (item.c) return false
// 微信和 QQ 的 rich-text inline 布局无效
return !inlineTags[item.name] && (item.attrs.style || '').indexOf('display:inline') == -1
}
}
</script>
<script>
import node from './node'
export default {
name: 'node',
options: {
// #ifdef MP-WEIXIN
virtualHost: true,
// #endif
// #ifdef MP-TOUTIAO
addGlobalClass: false
// #endif
},
data () {
return {
ctrl: {}
}
},
props: {
name: String,
attrs: {
type: Object,
default () {
return {}
}
},
childs: Array,
opts: Array
},
components: {
node
},
mounted () {
this.$nextTick(() => {
for (this.root = this.$parent; this.root.$options.name !== 'mp-html'; this.root = this.root.$parent);
})
// #ifdef H5 || APP-PLUS
if (this.opts[0]) {
let i
for (i = this.childs.length; i--;) {
if (this.childs[i].name === 'img') break
}
if (i !== -1) {
this.observer = uni.createIntersectionObserver(this).relativeToViewport({
top: 500,
bottom: 500
})
this.observer.observe('._img', res => {
if (res.intersectionRatio) {
this.$set(this.ctrl, 'load', 1)
this.observer.disconnect()
}
})
}
}
// #endif
},
beforeDestroy () {
// #ifdef H5 || APP-PLUS
if (this.observer) {
this.observer.disconnect()
}
// #endif
},
methods:{
// #ifdef MP-WEIXIN
toJSON () { },
// #endif
/**
* @description 播放视频事件
* @param {Event} e
*/
play (e) {
// #ifndef APP-PLUS
if (this.root.pauseVideo) {
let flag = false; const id = e.target.id
for (let i = this.root._videos.length; i--;) {
if (this.root._videos[i].id === id) {
flag = true
} else {
this.root._videos[i].pause() // 自动暂停其他视频
}
}
// 将自己加入列表
if (!flag) {
const ctx = uni.createVideoContext(id
// #ifndef MP-BAIDU
, this
// #endif
)
ctx.id = id
this.root._videos.push(ctx)
}
}
// #endif
},
/**
* @description 图片点击事件
* @param {Event} e
*/
imgTap (e) {
const node = this.childs[e.currentTarget.dataset.i]
if (node.a) {
this.linkTap(node.a)
return
}
if (node.attrs.ignore) return
// #ifdef H5 || APP-PLUS
node.attrs.src = node.attrs.src || node.attrs['data-src']
// #endif
this.root.$emit('imgtap', node.attrs)
// 自动预览图片
if (this.root.previewImg) {
uni.previewImage({
current: parseInt(node.attrs.i),
urls: this.root.imgList
})
}
},
/**
* @description 图片长按
*/
imgLongTap (e) {
// #ifdef APP-PLUS
const attrs = this.childs[e.currentTarget.dataset.i].attrs
if (this.opts[3] && !attrs.ignore) {
uni.showActionSheet({
itemList: ['保存图片'],
success: () => {
const save = path => {
uni.saveImageToPhotosAlbum({
filePath: path,
success () {
uni.showToast({
title: '保存成功'
})
}
})
}
if (this.root.imgList[attrs.i].startsWith('http')) {
uni.downloadFile({
url: this.root.imgList[attrs.i],
success: res => save(res.tempFilePath)
})
} else {
save(this.root.imgList[attrs.i])
}
}
})
}
// #endif
},
/**
* @description 图片加载完成事件
* @param {Event} e
*/
imgLoad (e) {
const i = e.currentTarget.dataset.i
/* #ifndef H5 || APP-PLUS */
if (!this.childs[i].w) {
// 设置原宽度
this.$set(this.ctrl, i, e.detail.width)
} else /* #endif */ if ((this.opts[1] && !this.ctrl[i]) || this.ctrl[i] === -1) {
// 加载完毕,取消加载中占位图
this.$set(this.ctrl, i, 1)
}
},
/**
* @description 链接点击事件
* @param {Event} e
*/
linkTap (e) {
const node = e.currentTarget ? this.childs[e.currentTarget.dataset.i] : {}
const attrs = node.attrs || e
const href = attrs.href
this.root.$emit('linktap', Object.assign({
innerText: this.root.getText(node.children || []) // 链接内的文本内容
}, attrs))
if (href) {
if (href[0] === '#') {
// 跳转锚点
this.root.navigateTo(href.substring(1)).catch(() => { })
} else if (href.split('?')[0].includes('://')) {
// 复制外部链接
if (this.root.copyLink) {
// #ifdef H5
window.open(href)
// #endif
// #ifdef MP
uni.setClipboardData({
data: href,
success: () =>
uni.showToast({
title: '链接已复制'
})
})
// #endif
// #ifdef APP-PLUS
plus.runtime.openWeb(href)
// #endif
}
} else {
// 跳转页面
uni.navigateTo({
url: '/' + href,
fail () {
uni.switchTab({
url: '/' + href,
fail () { }
})
}
})
}
}
},
/**
* @description 错误事件
* @param {Event} e
*/
mediaError (e) {
const i = e.currentTarget.dataset.i
const node = this.childs[i]
// 加载其他源
if (node.name === 'video' || node.name === 'audio') {
let index = (this.ctrl[i] || 0) + 1
if (index > node.src.length) {
index = 0
}
if (index < node.src.length) {
this.$set(this.ctrl, i, index)
return
}
} else if (node.name === 'img' && this.opts[2]) {
// 显示错误占位图
this.$set(this.ctrl, i, -1)
}
if (this.root) {
this.root.$emit('error', {
source: node.name,
attrs: node.attrs,
errMsg: e.detail.errMsg
})
}
}
}
}
</script>
<style>
/* a 标签默认效果 */
._a {
padding: 1.5px 0 1.5px 0;
color: #3284e6;
word-break: break-all;
}
/* a 标签点击态效果 */
._hover {
text-decoration: underline;
opacity: 0.7;
}
/* 图片默认效果 */
._img {
display: block;
max-width: 100%;
-webkit-touch-callout: none;
}
/* 内部样式 */
._block {
display: block;
}
._b,
._strong {
font-weight: bold;
}
._code {
font-family: monospace;
}
._del {
text-decoration: line-through;
}
._em,
._i {
font-style: italic;
}
._h1 {
font-size: 2em;
}
._h2 {
font-size: 1.5em;
}
._h3 {
font-size: 1.17em;
}
._h5 {
font-size: 0.83em;
}
._h6 {
font-size: 0.67em;
}
._h1,
._h2,
._h3,
._h4,
._h5,
._h6 {
display: block;
font-weight: bold;
}
._image {
height: 1px;
}
._ins {
text-decoration: underline;
}
._li {
display: list-item;
}
._ol {
list-style-type: decimal;
}
._ol,
._ul {
display: block;
padding-left: 40px;
margin: 1em 0;
}
._q::before {
content: '"';
}
._q::after {
content: '"';
}
._sub {
font-size: smaller;
vertical-align: sub;
}
._sup {
font-size: smaller;
vertical-align: super;
}
._thead,
._tbody,
._tfoot {
display: table-row-group;
}
._tr {
display: table-row;
}
._td,
._th {
display: table-cell;
vertical-align: middle;
}
._th {
font-weight: bold;
text-align: center;
}
._ul {
list-style-type: disc;
}
._ul ._ul {
margin: 0;
list-style-type: circle;
}
._ul ._ul ._ul {
list-style-type: square;
}
._abbr,
._b,
._code,
._del,
._em,
._i,
._ins,
._label,
._q,
._span,
._strong,
._sub,
._sup {
display: inline;
}
/* #ifdef APP-PLUS */
._video {
width: 300px;
height: 225px;
}
/* #endif */
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,79 @@
{
"id": "mp-html",
"displayName": "mp-html 富文本组件【全端支持,可编辑】",
"version": "v2.2.1",
"description": "一个强大的富文本组件,高效轻量,功能丰富",
"keywords": [
"富文本",
"编辑器",
"html",
"rich-text",
"editor"
],
"repository": "https://github.com/jin-yufeng/mp-html",
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/mp-html"
},
"uni_modules": {
"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": "u",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "y",
"联盟": "y"
},
"Vue": {
"vue2": "y",
"vue3": "u"
}
}
}
}
}

View File

@@ -0,0 +1 @@
"use strict";function t(t){for(var e=Object.create(null),n=t.attributes.length;n--;)e[t.attributes[n].name]=t.attributes[n].value;return e}function e(){o[1]&&(this.src=o[1],this.onerror=null),this.onclick=null,this.ontouchstart=null,uni.postMessage({data:{action:"onError",source:"img",attrs:t(this)}})}function n(r,i,s){for(var c=0;c<r.length;c++)!function(c){var u=r[c],l=void 0;if(u.type&&"node"!==u.type)l=document.createTextNode(u.text.replace(/&amp;/g,"&"));else{var d=u.name;"svg"===d&&(s="http://www.w3.org/2000/svg"),"html"!==d&&"body"!==d||(d="div"),l=s?document.createElementNS(s,d):document.createElement(d);for(var g in u.attrs)l.setAttribute(g,u.attrs[g]);if(u.children&&n(u.children,l,s),"img"===d){if(!l.src&&l.getAttribute("data-src")&&(l.src=l.getAttribute("data-src")),u.attrs.ignore||(l.onclick=function(e){e.stopPropagation(),uni.postMessage({data:{action:"onImgTap",attrs:t(this)}})}),o[2]){var p=new Image;p.src=l.src,l.src=o[2],p.onload=function(){l.src=this.src},p.onerror=function(){l.onerror()}}l.onerror=e}else if("a"===d)l.addEventListener("click",function(e){e.stopPropagation(),e.preventDefault();var n,o=this.getAttribute("href");o&&"#"===o[0]&&(n=(document.getElementById(o.substr(1))||{}).offsetTop),uni.postMessage({data:{action:"onLinkTap",attrs:t(this),offset:n}})},!0);else if("video"===d||"audio"===d)a.push(l),u.attrs.autoplay||u.attrs.controls||l.setAttribute("controls","true"),o[3]&&(l.onplay=function(){for(var t=0;t<a.length;t++)a[t]!==this&&a[t].pause()}),l.onerror=function(){uni.postMessage({data:{action:"onError",source:d,attrs:t(this)}})};else if("table"===d&&o[4]&&!l.style.cssText.includes("inline")){var h=document.createElement("div");h.style.overflow="auto",h.appendChild(l),l=h}else"svg"===d&&(s=void 0)}i.appendChild(l)}(c)}document.addEventListener("UniAppJSBridgeReady",function(){document.body.onclick=function(){return uni.postMessage({data:{action:"onClick"}})},uni.postMessage({data:{action:"onJSBridgeReady"}})});var o,a=[];window.setContent=function(t,e,r){var i=document.getElementById("content");e[0]&&(document.body.style.cssText=e[0]),e[5]||(i.style.userSelect="none"),r||(i.innerHTML="",a=[]),o=e;var s=document.createDocumentFragment();n(t,s),i.appendChild(s);var c=i.scrollHeight;uni.postMessage({data:{action:"onLoad",height:c}}),clearInterval(window.timer);var u=!1;window.timer=setInterval(function(){i.scrollHeight!==c?(c=i.scrollHeight,uni.postMessage({data:{action:"onHeightChange",height:c}})):u||(u=!0,uni.postMessage({data:{action:"onReady"}}))},350)},window.onunload=function(){clearInterval(window.timer)};

View File

@@ -0,0 +1 @@
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(e=e||self).uni=n()}(this,(function(){"use strict";try{var e={};Object.defineProperty(e,"passive",{get:function(){!0}}),window.addEventListener("test-passive",null,e)}catch(e){}var n=Object.prototype.hasOwnProperty;function t(e,t){return n.call(e,t)}var i=[],a=function(e,n){var t={options:{timestamp:+new Date},name:e,arg:n};if(window.__dcloud_weex_postMessage||window.__dcloud_weex_){if("postMessage"===e){var a={data:[n]};return window.__dcloud_weex_postMessage?window.__dcloud_weex_postMessage(a):window.__dcloud_weex_.postMessage(JSON.stringify(a))}var o={type:"WEB_INVOKE_APPSERVICE",args:{data:t,webviewIds:i}};window.__dcloud_weex_postMessage?window.__dcloud_weex_postMessageToService(o):window.__dcloud_weex_.postMessageToService(JSON.stringify(o))}if(!window.plus)return window.parent.postMessage({type:"WEB_INVOKE_APPSERVICE",data:t,pageId:""},"*");if(0===i.length){var r=plus.webview.currentWebview();if(!r)throw new Error("plus.webview.currentWebview() is undefined");var d=r.parent(),s="";s=d?d.id:r.id,i.push(s)}if(plus.webview.getWebviewById("__uniapp__service"))plus.webview.postMessageToUniNView({type:"WEB_INVOKE_APPSERVICE",args:{data:t,webviewIds:i}},"__uniapp__service");else{var w=JSON.stringify(t);plus.webview.getLaunchWebview().evalJS('UniPlusBridge.subscribeHandler("'.concat("WEB_INVOKE_APPSERVICE",'",').concat(w,",").concat(JSON.stringify(i),");"))}},o={navigateTo:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;a("navigateTo",{url:encodeURI(n)})},navigateBack:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.delta;a("navigateBack",{delta:parseInt(n)||1})},switchTab:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;a("switchTab",{url:encodeURI(n)})},reLaunch:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;a("reLaunch",{url:encodeURI(n)})},redirectTo:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;a("redirectTo",{url:encodeURI(n)})},getEnv:function(e){window.plus?e({plus:!0}):e({h5:!0})},postMessage:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};a("postMessage",e.data||{})}},r=/uni-app/i.test(navigator.userAgent),d=/Html5Plus/i.test(navigator.userAgent),s=/complete|loaded|interactive/;var w=window.my&&navigator.userAgent.indexOf("AlipayClient")>-1;var u=window.swan&&window.swan.webView&&/swan/i.test(navigator.userAgent);var c=window.qq&&window.qq.miniProgram&&/QQ/i.test(navigator.userAgent)&&/miniProgram/i.test(navigator.userAgent);var g=window.tt&&window.tt.miniProgram&&/toutiaomicroapp/i.test(navigator.userAgent);var v=window.wx&&window.wx.miniProgram&&/micromessenger/i.test(navigator.userAgent)&&/miniProgram/i.test(navigator.userAgent);var p=window.qa&&/quickapp/i.test(navigator.userAgent);for(var l,_=function(){window.UniAppJSBridge=!0,document.dispatchEvent(new CustomEvent("UniAppJSBridgeReady",{bubbles:!0,cancelable:!0}))},f=[function(e){if(r||d)return window.__dcloud_weex_postMessage||window.__dcloud_weex_?document.addEventListener("DOMContentLoaded",e):window.plus&&s.test(document.readyState)?setTimeout(e,0):document.addEventListener("plusready",e),o},function(e){if(v)return window.WeixinJSBridge&&window.WeixinJSBridge.invoke?setTimeout(e,0):document.addEventListener("WeixinJSBridgeReady",e),window.wx.miniProgram},function(e){if(c)return window.QQJSBridge&&window.QQJSBridge.invoke?setTimeout(e,0):document.addEventListener("QQJSBridgeReady",e),window.qq.miniProgram},function(e){if(w){document.addEventListener("DOMContentLoaded",e);var n=window.my;return{navigateTo:n.navigateTo,navigateBack:n.navigateBack,switchTab:n.switchTab,reLaunch:n.reLaunch,redirectTo:n.redirectTo,postMessage:n.postMessage,getEnv:n.getEnv}}},function(e){if(u)return document.addEventListener("DOMContentLoaded",e),window.swan.webView},function(e){if(g)return document.addEventListener("DOMContentLoaded",e),window.tt.miniProgram},function(e){if(p){window.QaJSBridge&&window.QaJSBridge.invoke?setTimeout(e,0):document.addEventListener("QaJSBridgeReady",e);var n=window.qa;return{navigateTo:n.navigateTo,navigateBack:n.navigateBack,switchTab:n.switchTab,reLaunch:n.reLaunch,redirectTo:n.redirectTo,postMessage:n.postMessage,getEnv:n.getEnv}}},function(e){return document.addEventListener("DOMContentLoaded",e),o}],m=0;m<f.length&&!(l=f[m](_));m++);l||(l={});var E="undefined"!=typeof uni?uni:{};if(!E.navigateTo)for(var b in l)t(l,b)&&(E[b]=l[b]);return E.webView=l,E}));

View File

@@ -0,0 +1 @@
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"><style>body,html{width:100%;height:100%;overflow-x:scroll;overflow-y:hidden}body{margin:0}video{width:300px;height:225px}img{max-width:100%;-webkit-touch-callout:none}</style></head><body><div id="content" style="overflow:hidden"></div><script type="text/javascript" src="./js/uni.webview.min.js"></script><script type="text/javascript" src="./js/handler.js"></script></body>

View File

@@ -0,0 +1,31 @@
## 1.2.12022-09-05
- 修复 当 text 超过 max-num 时badge 的宽度计算是根据 text 的长度计算,更改为 css 计算实际展示宽度,详见:[https://ask.dcloud.net.cn/question/150473](https://ask.dcloud.net.cn/question/150473)
## 1.2.02021-11-19
- 优化 组件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)
## 1.1.72021-11-08
- 优化 升级ui
- 修改 size 属性默认值调整为 small
- 修改 type 属性,默认值调整为 errorinfo 替换 default
## 1.1.62021-09-22
- 修复 在字节小程序上样式不生效的 bug
## 1.1.52021-07-30
- 组件兼容 vue3如何创建vue3项目详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.1.42021-07-29
- 修复 去掉 nvue 不支持css 的 align-self 属性nvue 下不暂支持 absolute 属性
## 1.1.32021-06-24
- 优化 示例项目
## 1.1.12021-05-12
- 新增 组件示例地址
## 1.1.02021-05-12
- 新增 uni-badge 的 absolute 属性,支持定位
- 新增 uni-badge 的 offset 属性,支持定位偏移
- 新增 uni-badge 的 is-dot 属性,支持仅显示有一个小点
- 新增 uni-badge 的 max-num 属性,支持自定义封顶的数字值,超过 99 显示99+
- 优化 uni-badge 属性 custom-style 支持以对象形式自定义样式
## 1.0.72021-05-07
- 修复 uni-badge 在 App 端数字小于10时不是圆形的bug
- 修复 uni-badge 在父元素不是 flex 布局时宽度缩小的bug
- 新增 uni-badge 属性 custom-style 支持自定义样式
## 1.0.62021-02-04
- 调整为uni_modules目录规范

View File

@@ -0,0 +1,268 @@
<template>
<view class="uni-badge--x">
<slot />
<text v-if="text" :class="classNames" :style="[positionStyle, customStyle, dotStyle]"
class="uni-badge" @click="onClick()">{{displayValue}}</text>
</view>
</template>
<script>
/**
* Badge 数字角标
* @description 数字角标一般和其它控件列表、9宫格等配合使用用于进行数量提示默认为实心灰色背景
* @tutorial https://ext.dcloud.net.cn/plugin?id=21
* @property {String} text 角标内容
* @property {String} size = [normal|small] 角标内容
* @property {String} type = [info|primary|success|warning|error] 颜色类型
* @value info 灰色
* @value primary 蓝色
* @value success 绿色
* @value warning 黄色
* @value error 红色
* @property {String} inverted = [true|false] 是否无需背景颜色
* @property {Number} maxNum 展示封顶的数字值,超过 99 显示 99+
* @property {String} absolute = [rightTop|rightBottom|leftBottom|leftTop] 开启绝对定位, 角标将定位到其包裹的标签的四角上
* @value rightTop 右上
* @value rightBottom 右下
* @value leftTop 左上
* @value leftBottom 左下
* @property {Array[number]} offset 距定位角中心点的偏移量,只有存在 absolute 属性时有效,例如:[-10, -10] 表示向外偏移 10px[10, 10] 表示向 absolute 指定的内偏移 10px
* @property {String} isDot = [true|false] 是否显示为一个小点
* @event {Function} click 点击 Badge 触发事件
* @example <uni-badge text="1"></uni-badge>
*/
export default {
name: 'UniBadge',
emits: ['click'],
props: {
type: {
type: String,
default: 'error'
},
inverted: {
type: Boolean,
default: false
},
isDot: {
type: Boolean,
default: false
},
maxNum: {
type: Number,
default: 99
},
absolute: {
type: String,
default: ''
},
offset: {
type: Array,
default () {
return [0, 0]
}
},
text: {
type: [String, Number],
default: ''
},
size: {
type: String,
default: 'small'
},
customStyle: {
type: Object,
default () {
return {}
}
}
},
data() {
return {};
},
computed: {
width() {
return String(this.text).length * 8 + 12
},
classNames() {
const {
inverted,
type,
size,
absolute
} = this
return [
inverted ? 'uni-badge--' + type + '-inverted' : '',
'uni-badge--' + type,
'uni-badge--' + size,
absolute ? 'uni-badge--absolute' : ''
].join(' ')
},
positionStyle() {
if (!this.absolute) return {}
let w = this.width / 2,
h = 10
if (this.isDot) {
w = 5
h = 5
}
const x = `${- w + this.offset[0]}px`
const y = `${- h + this.offset[1]}px`
const whiteList = {
rightTop: {
right: x,
top: y
},
rightBottom: {
right: x,
bottom: y
},
leftBottom: {
left: x,
bottom: y
},
leftTop: {
left: x,
top: y
}
}
const match = whiteList[this.absolute]
return match ? match : whiteList['rightTop']
},
dotStyle() {
if (!this.isDot) return {}
return {
width: '10px',
minWidth: '0',
height: '10px',
padding: '0',
borderRadius: '10px'
}
},
displayValue() {
const {
isDot,
text,
maxNum
} = this
return isDot ? '' : (Number(text) > maxNum ? `${maxNum}+` : text)
}
},
methods: {
onClick() {
this.$emit('click');
}
}
};
</script>
<style lang="scss" >
$uni-primary: #2979ff !default;
$uni-success: #4cd964 !default;
$uni-warning: #f0ad4e !default;
$uni-error: #dd524d !default;
$uni-info: #909399 !default;
$bage-size: 12px;
$bage-small: scale(0.8);
.uni-badge--x {
/* #ifdef APP-NVUE */
// align-self: flex-start;
/* #endif */
/* #ifndef APP-NVUE */
display: inline-block;
/* #endif */
position: relative;
}
.uni-badge--absolute {
position: absolute;
}
.uni-badge--small {
transform: $bage-small;
transform-origin: center center;
}
.uni-badge {
/* #ifndef APP-NVUE */
display: flex;
overflow: hidden;
box-sizing: border-box;
/* #endif */
justify-content: center;
flex-direction: row;
height: 20px;
min-width: 20px;
padding: 0 4px;
line-height: 18px;
color: #fff;
border-radius: 100px;
background-color: $uni-info;
background-color: transparent;
border: 1px solid #fff;
text-align: center;
font-family: 'Helvetica Neue', Helvetica, sans-serif;
font-feature-settings: "tnum";
font-size: $bage-size;
/* #ifdef H5 */
z-index: 999;
cursor: pointer;
/* #endif */
&--info {
color: #fff;
background-color: $uni-info;
}
&--primary {
background-color: $uni-primary;
}
&--success {
background-color: $uni-success;
}
&--warning {
background-color: $uni-warning;
}
&--error {
background-color: $uni-error;
}
&--inverted {
padding: 0 5px 0 0;
color: $uni-info;
}
&--info-inverted {
color: $uni-info;
background-color: transparent;
}
&--primary-inverted {
color: $uni-primary;
background-color: transparent;
}
&--success-inverted {
color: $uni-success;
background-color: transparent;
}
&--warning-inverted {
color: $uni-warning;
background-color: transparent;
}
&--error-inverted {
color: $uni-error;
background-color: transparent;
}
}
</style>

View File

@@ -0,0 +1,85 @@
{
"id": "uni-badge",
"displayName": "uni-badge 数字角标",
"version": "1.2.1",
"description": "数字角标(徽章)组件,在元素周围展示消息提醒,一般用于列表、九宫格、按钮等地方。",
"keywords": [
"",
"badge",
"uni-ui",
"uniui",
"数字角标",
"徽章"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": ["uni-scss"],
"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"
}
}
}
}
}

View File

@@ -0,0 +1,10 @@
## Badge 数字角标
> **组件名uni-badge**
> 代码块: `uBadge`
数字角标一般和其它控件列表、9宫格等配合使用用于进行数量提示默认为实心灰色背景
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-badge)
#### 如使用过程中有任何问题或者您对uni-ui有一些好的建议欢迎加入 uni-ui 交流群871950839

View File

@@ -0,0 +1,6 @@
## 0.0.32022-11-11
- 修复 config 方法获取根节点为数组格式配置时错误的转化为了对象的Bug
## 0.0.22021-04-16
- 修改插件package信息
## 0.0.12021-03-15
- 初始化项目

View File

@@ -0,0 +1,81 @@
{
"id": "uni-config-center",
"displayName": "uni-config-center",
"version": "0.0.3",
"description": "uniCloud 配置中心",
"keywords": [
"配置",
"配置中心"
],
"repository": "",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "",
"type": "unicloud-template-function"
},
"directories": {
"example": "../../../scripts/dist"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "u",
"app-nvue": "u"
},
"H5-mobile": {
"Safari": "u",
"Android Browser": "u",
"微信浏览器(Android)": "u",
"QQ浏览器(Android)": "u"
},
"H5-pc": {
"Chrome": "u",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "u",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "u"
}
}
}
}
}

View File

@@ -0,0 +1,93 @@
# 为什么使用uni-config-center
实际开发中很多插件需要配置文件才可以正常运行,如果每个插件都单独进行配置的话就会产生下面这样的目录结构
```bash
cloudfunctions
└─────common 公共模块
├─plugin-a // 插件A对应的目录
│ ├─index.js
│ ├─config.json // plugin-a对应的配置文件
│ └─other-file.cert // plugin-a依赖的其他文件
└─plugin-b // plugin-b对应的目录
├─index.js
└─config.json // plugin-b对应的配置文件
```
假设插件作者要发布一个项目模板,里面使用了很多需要配置的插件,无论是作者发布还是用户使用都是一个大麻烦。
uni-config-center就是用了统一管理这些配置文件的使用uni-config-center后的目录结构如下
```bash
cloudfunctions
└─────common 公共模块
├─plugin-a // 插件A对应的目录
│ └─index.js
├─plugin-b // plugin-b对应的目录
│ └─index.js
└─uni-config-center
├─index.js // config-center入口文件
├─plugin-a
│ ├─config.json // plugin-a对应的配置文件
│ └─other-file.cert // plugin-a依赖的其他文件
└─plugin-b
└─config.json // plugin-b对应的配置文件
```
使用uni-config-center后的优势
- 配置文件统一管理,分离插件主体和配置信息,更新插件更方便
- 支持对config.json设置schema插件使用者在HBuilderX内编写config.json文件时会有更好的提示后续HBuilderX会提供支持
# 用法
在要使用uni-config-center的公共模块或云函数内引入uni-config-center依赖请参考[使用公共模块](https://uniapp.dcloud.net.cn/uniCloud/cf-common)
```js
const createConfig = require('uni-config-center')
const uniIdConfig = createConfig({
pluginId: 'uni-id', // 插件id
defaultConfig: { // 默认配置
tokenExpiresIn: 7200,
tokenExpiresThreshold: 600,
},
customMerge: function(defaultConfig, userConfig) { // 自定义默认配置和用户配置的合并规则,不设置的情况侠会对默认配置和用户配置进行深度合并
// defaudltConfig 默认配置
// userConfig 用户配置
return Object.assign(defaultConfig, userConfig)
}
})
// 以如下配置为例
// {
// "tokenExpiresIn": 7200,
// "passwordErrorLimit": 6,
// "bindTokenToDevice": false,
// "passwordErrorRetryTime": 3600,
// "app-plus": {
// "tokenExpiresIn": 2592000
// },
// "service": {
// "sms": {
// "codeExpiresIn": 300
// }
// }
// }
// 获取配置
uniIdConfig.config() // 获取全部配置注意uni-config-center内不存在对应插件目录时会返回空对象
uniIdConfig.config('tokenExpiresIn') // 指定键值获取配置返回7200
uniIdConfig.config('service.sms.codeExpiresIn') // 指定键值获取配置返回300
uniIdConfig.config('tokenExpiresThreshold', 600) // 指定键值获取配置如果不存在则取传入的默认值返回600
// 获取文件绝对路径
uniIdConfig.resolve('custom-token.js') // 获取uni-config-center/uni-id/custom-token.js文件的路径
// 引用文件require
uniIDConfig.requireFile('custom-token.js') // 使用require方式引用uni-config-center/uni-id/custom-token.js文件。文件不存在时返回undefined文件内有其他错误导致require失败时会抛出错误。
// 判断是否包含某文件
uniIDConfig.hasFile('custom-token.js') // 配置目录是否包含某文件true: 文件存在false: 文件不存在
```

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,9 @@
{
"name": "uni-config-center",
"version": "0.0.3",
"description": "配置中心",
"main": "index.js",
"keywords": [],
"author": "DCloud",
"license": "Apache-2.0"
}

View File

@@ -0,0 +1,58 @@
{
"passwordSecret": "passwordSecret-demo",
"tokenSecret": "tokenSecret-demo",
"tokenExpiresIn": 604800,
"tokenExpiresThreshold": 3600,
"passwordErrorLimit": 6,
"bindTokenToDevice": false,
"passwordErrorRetryTime": 3600,
"autoSetInviteCode": true,
"forceInviteCode": false,
"app": {
"tokenExpiresIn": 604800,
"oauth": {
"weixin": {
"appid": "",
"appsecret": ""
}
}
},
"mp-weixin": {
"oauth": {
"weixin": {
"appid": "",
"appsecret": ""
}
}
},
"mp-alipay": {
"oauth": {
"alipay": {
"appid": "2021003156628508",
"privateKey": "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCB0yG7GKPjNIEhpjS6T4um1W92BcgLKh2p28vB12dapWFB9eg3a1/jh66egKEyPR0NowZGvcaWOAZ/HXyCcDnpnFix/s/Yqb85Uwq9uFrkmB3ms2RHm3TM+WNnENcLy0wVCekcR1kcZZuYVWs/xHar/omoSAAhWdMWApVAwrFFCHZxCuIw0VaJbmlfFQedlVbfXgE94n+lNi5rG0JSnMo6MQimBCaeIfsZlWbKFBH9mrEpkte+Q4vJDH6p6odpG/hkhC+9VascMLwhW4N3+5g7GDRj1QGM7HjWou2cQ36w5LCU8xk55ur50c6HcxoQqZjFUvykfCYSNMThUhLHrnnHAgMBAAECggEACECplkEcueauhrsQAv/nerV6nmADtWH5/MAyFaJhuYtlwVGmb09uCwKnAQBgPtdPr3w7e+e4ZfgtwYrYTVpg9A6yPK5b/APeWgCDYEgFzx40WGPy7fJd3GHGBdk2MlO8BGJa5SdL7NgwqeBULvuIJ6rIiV/6UiRpnK6RWkqGBkNKO9AqQSGbvTI8jJ+NBAqmLeIk7Os9kCOiwZLqwPI9XVd+/exeOwsMTo/K5mKqpc1MltAhXYoeuzHGYtOyPCsgOOu3EBbivSCBN5gqy87JohJAScVmCyPTmDq1E2N6JkSujrBpS70VstnAt6g4fZQoidC9aSqF/i8xID8a0rI8wQKBgQDBsG/MQnLvvuonTLeHd7A737V4qRWTtntpxXJ8VWTl+M9PYoA7J4SKRicxvCHka2Cr1ITL62bEfl1MRFmz9BsKlpBVxhsOT49CPMIedpq3e92iOR/SqF161A1DBSVNYziTIbE6h5I1gZp+ZcFLKXXtsu0LpzNgYJlnS6V0e66UMQKBgQCrlwqNL+H5PPAOhiFswUK9ztjoAks7nBE8B//EJ29M3mUFf0isNMj8ta7DPQsQK1zmej/PQSKRoG/t55ao8Svrd29d2V4UYUNQWMb3YWN+QGdIy1ta25hJlwdkKbQJIYAQ8/8i5Y6BnCLwLuj7KMpWBuPWkl2YDfRjJlAhz85HdwKBgQCweVFjiieuyQQPSpbtlt+7rdhqV6SRMXLArGXjYurLnidE7Tpoq1jXo5OSfRdkR3GNHdTg91prLbdUBfK1Q3Rf8U8Q169PGq4sa69ykh3lj7YgWGvmRADoKMzsg4O5Pu4NIGWaLmvI1I0vHQdAtEX+jUftlin5ZgpfU00tFIO8AQKBgQClK45Hm9js3sDBalHQazQAm5TluBeNOMzKOXT073TOzKD3qq9cvK7fu0+PtjnpBaS2YuT7btqEzagQnMXEt+osDdrQvwU1nu558AsOY9uu0vXY949npUwxQkUmIJKh23J4Xzav88K6dn6XLsCry3cBWj6E9H1NedlOe7nU4kDRPwKBgQC1oK7oeO0qSNGE8bhnZLAyRnmWLGGSPMf+oBocox4nixcknrPjsWBEJcI0keGanaiam94VkftFnl2c6ZW8229bpimdYj9891VN+8NvBSYTR7lOuTXRZUtTLu9ocgJ/aGQJRXHs+hwZeHcY1PnEkCi6IJXb6e2+Kq2mbdz30mqOww=="
}
}
},
"mp-qq": {
"oauth": {
"qq": {
"appid": "",
"appsecret": ""
}
}
},
"service": {
"sms": {
"name": "重要",
"codeExpiresIn": 180,
"smsKey": "",
"smsSecret": "",
"templateId": ""
},
"univerify": {
"appid": "",
"apiKey": "",
"apiSecret": ""
}
}
}

View File

@@ -0,0 +1,43 @@
-----BEGIN CERTIFICATE-----
MIIDsjCCApqgAwIBAgIQICISFJ0q3ljWeIAX7m9HqTANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UE
BhMCQ04xFjAUBgNVBAoMDUFudCBGaW5hbmNpYWwxIDAeBgNVBAsMF0NlcnRpZmljYXRpb24gQXV0
aG9yaXR5MTkwNwYDVQQDDDBBbnQgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IENs
YXNzIDIgUjEwHhcNMjIxMjE0MDgwNDU0WhcNMjcxMjEzMDgwNDU0WjCBkjELMAkGA1UEBhMCQ04x
LTArBgNVBAoMJOi0tea4r+mAn+WNmuiDvea6kOenkeaKgOaciemZkOWFrOWPuDEPMA0GA1UECwwG
QWxpcGF5MUMwQQYDVQQDDDrmlK/ku5jlrp0o5Lit5Zu9Kee9kee7nOaKgOacr+aciemZkOWFrOWP
uC0yMDg4NDMxODA1NTI0NDIyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmjyz+57i
qWQE7MbqA2/OIsuEETKA2+qJbc84IeiOdjar9vQegsGxCjYqGTUMJRt/iG1JvwEbvxhVZ1WUyE8K
8dX1x79zr7ZjMkbz+sVsFUd2jsn90NRd8mElqOKs7Mf2vjBDveHOOPQTg975855AB8RhFVYtc5RE
cjjAqGsOYjuXFPq9VNsQkwC+QM/WJ3APENmcozNYog1uIqPMcN4xXTYVUQSRfqonUI/+jD+Fun7+
Qek27JPg6qpG0MjIxAIf5Q1UTmsQh8TMEgyyhcKATTqKbpSxIURju6Nt+7sLa2lJaav+lw/EK4LZ
CnQwylhxOu7NAxJ/DAXIFtFslfMwtwIDAQABoxIwEDAOBgNVHQ8BAf8EBAMCA/gwDQYJKoZIhvcN
AQELBQADggEBACTkpajSwNhyiHuKKlcupqD6A/J4lynGeGGuuy33IIp7bJ4gcTIf90R0Tn1JlZWv
mmDW2+7NZx1SnTDmj6Ten9ud1jJF3713vcwB9pUyuthiznu8ANOKOHsvsgCi7aQRPLTpOpCIIZj1
rsuVqIATxP4SMSDxC9X8cGEw2jUCIrXZMi2Q8WRsrGhxKW1qhNS9v7F9mnhn3a6ESxF4GDH2mxbs
qKex3D331yQh2i9E6f1016JKhJMuRfu3qTbzG6f58kFp/M8uGt5Crfw+1ePsHRKODJ8PlMxefUap
ZvoDgaQgS+FEEjNNUiOJhyzTgjRigwk87Tnkpa5EWZXt9kR7eeY=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIE4jCCAsqgAwIBAgIIYsSr5bKAMl8wDQYJKoZIhvcNAQELBQAwejELMAkGA1UEBhMCQ04xFjAU
BgNVBAoMDUFudCBGaW5hbmNpYWwxIDAeBgNVBAsMF0NlcnRpZmljYXRpb24gQXV0aG9yaXR5MTEw
LwYDVQQDDChBbnQgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFIxMB4XDTE4MDMy
MjE0MzQxNVoXDTM3MTEyNjE0MzQxNVowgYIxCzAJBgNVBAYTAkNOMRYwFAYDVQQKDA1BbnQgRmlu
YW5jaWFsMSAwHgYDVQQLDBdDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTE5MDcGA1UEAwwwQW50IEZp
bmFuY2lhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSBDbGFzcyAyIFIxMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEAsLMfYaoRoPRbmDcAfXPCmKf43pWRN5yTXa/KJWO0l+mrgQvs89bA
NEvbDUxlkGwycwtwi5DgBuBgVhLliXu+R9CYgr2dXs8D8Hx/gsggDcyGPLmVrDOnL+dyeauheARZ
fA3du60fwEwwbGcVIpIxPa/4n3IS/ElxQa6DNgqxh8J9Xwh7qMGl0JK9+bALuxf7B541Gr4p0WEN
G8fhgjBV4w4ut9eQLOoa1eddOUSZcy46Z7allwowwgt7b5VFfx/P1iKJ3LzBMgkCK7GZ2kiLrL7R
iqV+h482J7hkJD+ardoc6LnrHO/hIZymDxok+VH9fVeUdQa29IZKrIDVj65THQIDAQABo2MwYTAf
BgNVHSMEGDAWgBRfdLQEwE8HWurlsdsio4dBspzhATAdBgNVHQ4EFgQUSqHkYINtUSAtDPnS8Xoy
oP9p7qEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIB
AIQ8TzFy4bVIVb8+WhHKCkKNPcJe2EZuIcqvRoi727lZTJOfYy/JzLtckyZYfEI8J0lasZ29wkTt
a1IjSo+a6XdhudU4ONVBrL70U8Kzntplw/6TBNbLFpp7taRALjUgbCOk4EoBMbeCL0GiYYsTS0mw
7xdySzmGQku4GTyqutIGPQwKxSj9iSFw1FCZqr4VP4tyXzMUgc52SzagA6i7AyLedd3tbS6lnR5B
L+W9Kx9hwT8L7WANAxQzv/jGldeuSLN8bsTxlOYlsdjmIGu/C9OWblPYGpjQQIRyvs4Cc/mNhrh+
14EQgwuemIIFDLOgcD+iISoN8CqegelNcJndFw1PDN6LkVoiHz9p7jzsge8RKay/QW6C03KNDpWZ
EUCgCUdfHfo8xKeR+LL1cfn24HKJmZt8L/aeRZwZ1jwePXFRVtiXELvgJuM/tJDIFj2KD337iV64
fWcKQ/ydDVGqfDZAdcU4hQdsrPWENwPTQPfVPq2NNLMyIH9+WKx9Ed6/WzeZmIy5ZWpX1TtTolo6
OJXQFeItMAjHxW/ZSZTok5IS3FuRhExturaInnzjYpx50a6kS34c5+c8hYq7sAtZ/CNLZmBnBCFD
aMQqT8xFZJ5uolUaSeXxg7JFY1QsYp5RKvj4SjFwCGKJ2+hPPe9UyyltxOidNtxjaknOCeBHytOr
-----END CERTIFICATE-----

View File

@@ -0,0 +1,88 @@
-----BEGIN CERTIFICATE-----
MIIBszCCAVegAwIBAgIIaeL+wBcKxnswDAYIKoEcz1UBg3UFADAuMQswCQYDVQQG
EwJDTjEOMAwGA1UECgwFTlJDQUMxDzANBgNVBAMMBlJPT1RDQTAeFw0xMjA3MTQw
MzExNTlaFw00MjA3MDcwMzExNTlaMC4xCzAJBgNVBAYTAkNOMQ4wDAYDVQQKDAVO
UkNBQzEPMA0GA1UEAwwGUk9PVENBMFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE
MPCca6pmgcchsTf2UnBeL9rtp4nw+itk1Kzrmbnqo05lUwkwlWK+4OIrtFdAqnRT
V7Q9v1htkv42TsIutzd126NdMFswHwYDVR0jBBgwFoAUTDKxl9kzG8SmBcHG5Yti
W/CXdlgwDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFEwysZfZ
MxvEpgXBxuWLYlvwl3ZYMAwGCCqBHM9VAYN1BQADSAAwRQIgG1bSLeOXp3oB8H7b
53W+CKOPl2PknmWEq/lMhtn25HkCIQDaHDgWxWFtnCrBjH16/W3Ezn7/U/Vjo5xI
pDoiVhsLwg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIF0zCCA7ugAwIBAgIIH8+hjWpIDREwDQYJKoZIhvcNAQELBQAwejELMAkGA1UE
BhMCQ04xFjAUBgNVBAoMDUFudCBGaW5hbmNpYWwxIDAeBgNVBAsMF0NlcnRpZmlj
YXRpb24gQXV0aG9yaXR5MTEwLwYDVQQDDChBbnQgRmluYW5jaWFsIENlcnRpZmlj
YXRpb24gQXV0aG9yaXR5IFIxMB4XDTE4MDMyMTEzNDg0MFoXDTM4MDIyODEzNDg0
MFowejELMAkGA1UEBhMCQ04xFjAUBgNVBAoMDUFudCBGaW5hbmNpYWwxIDAeBgNV
BAsMF0NlcnRpZmljYXRpb24gQXV0aG9yaXR5MTEwLwYDVQQDDChBbnQgRmluYW5j
aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFIxMIICIjANBgkqhkiG9w0BAQEF
AAOCAg8AMIICCgKCAgEAtytTRcBNuur5h8xuxnlKJetT65cHGemGi8oD+beHFPTk
rUTlFt9Xn7fAVGo6QSsPb9uGLpUFGEdGmbsQ2q9cV4P89qkH04VzIPwT7AywJdt2
xAvMs+MgHFJzOYfL1QkdOOVO7NwKxH8IvlQgFabWomWk2Ei9WfUyxFjVO1LVh0Bp
dRBeWLMkdudx0tl3+21t1apnReFNQ5nfX29xeSxIhesaMHDZFViO/DXDNW2BcTs6
vSWKyJ4YIIIzStumD8K1xMsoaZBMDxg4itjWFaKRgNuPiIn4kjDY3kC66Sl/6yTl
YUz8AybbEsICZzssdZh7jcNb1VRfk79lgAprm/Ktl+mgrU1gaMGP1OE25JCbqli1
Pbw/BpPynyP9+XulE+2mxFwTYhKAwpDIDKuYsFUXuo8t261pCovI1CXFzAQM2w7H
DtA2nOXSW6q0jGDJ5+WauH+K8ZSvA6x4sFo4u0KNCx0ROTBpLif6GTngqo3sj+98
SZiMNLFMQoQkjkdN5Q5g9N6CFZPVZ6QpO0JcIc7S1le/g9z5iBKnifrKxy0TQjtG
PsDwc8ubPnRm/F82RReCoyNyx63indpgFfhN7+KxUIQ9cOwwTvemmor0A+ZQamRe
9LMuiEfEaWUDK+6O0Gl8lO571uI5onYdN1VIgOmwFbe+D8TcuzVjIZ/zvHrAGUcC
AwEAAaNdMFswCwYDVR0PBAQDAgEGMAwGA1UdEwQFMAMBAf8wHQYDVR0OBBYEFF90
tATATwda6uWx2yKjh0GynOEBMB8GA1UdIwQYMBaAFF90tATATwda6uWx2yKjh0Gy
nOEBMA0GCSqGSIb3DQEBCwUAA4ICAQCVYaOtqOLIpsrEikE5lb+UARNSFJg6tpkf
tJ2U8QF/DejemEHx5IClQu6ajxjtu0Aie4/3UnIXop8nH/Q57l+Wyt9T7N2WPiNq
JSlYKYbJpPF8LXbuKYG3BTFTdOVFIeRe2NUyYh/xs6bXGr4WKTXb3qBmzR02FSy3
IODQw5Q6zpXj8prYqFHYsOvGCEc1CwJaSaYwRhTkFedJUxiyhyB5GQwoFfExCVHW
05ZFCAVYFldCJvUzfzrWubN6wX0DD2dwultgmldOn/W/n8at52mpPNvIdbZb2F41
T0YZeoWnCJrYXjq/32oc1cmifIHqySnyMnavi75DxPCdZsCOpSAT4j4lAQRGsfgI
kkLPGQieMfNNkMCKh7qjwdXAVtdqhf0RVtFILH3OyEodlk1HYXqX5iE5wlaKzDop
PKwf2Q3BErq1xChYGGVS+dEvyXc/2nIBlt7uLWKp4XFjqekKbaGaLJdjYP5b2s7N
1dM0MXQ/f8XoXKBkJNzEiM3hfsU6DOREgMc1DIsFKxfuMwX3EkVQM1If8ghb6x5Y
jXayv+NLbidOSzk4vl5QwngO/JYFMkoc6i9LNwEaEtR9PhnrdubxmrtM+RjfBm02
77q3dSWFESFQ4QxYWew4pHE0DpWbWy/iMIKQ6UZ5RLvB8GEcgt8ON7BBJeMc+Dyi
kT9qhqn+lw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICiDCCAgygAwIBAgIIQX76UsB/30owDAYIKoZIzj0EAwMFADB6MQswCQYDVQQG
EwJDTjEWMBQGA1UECgwNQW50IEZpbmFuY2lhbDEgMB4GA1UECwwXQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkxMTAvBgNVBAMMKEFudCBGaW5hbmNpYWwgQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkgRTEwHhcNMTkwNDI4MTYyMDQ0WhcNNDkwNDIwMTYyMDQ0
WjB6MQswCQYDVQQGEwJDTjEWMBQGA1UECgwNQW50IEZpbmFuY2lhbDEgMB4GA1UE
CwwXQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxMTAvBgNVBAMMKEFudCBGaW5hbmNp
YWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRTEwdjAQBgcqhkjOPQIBBgUrgQQA
IgNiAASCCRa94QI0vR5Up9Yr9HEupz6hSoyjySYqo7v837KnmjveUIUNiuC9pWAU
WP3jwLX3HkzeiNdeg22a0IZPoSUCpasufiLAnfXh6NInLiWBrjLJXDSGaY7vaokt
rpZvAdmjXTBbMAsGA1UdDwQEAwIBBjAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBRZ
4ZTgDpksHL2qcpkFkxD2zVd16TAfBgNVHSMEGDAWgBRZ4ZTgDpksHL2qcpkFkxD2
zVd16TAMBggqhkjOPQQDAwUAA2gAMGUCMQD4IoqT2hTUn0jt7oXLdMJ8q4vLp6sg
wHfPiOr9gxreb+e6Oidwd2LDnC4OUqCWiF8CMAzwKs4SnDJYcMLf2vpkbuVE4dTH
Rglz+HGcTLWsFs4KxLsq7MuU+vJTBUeDJeDjdA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIUEMdk6dVgOEIS2cCP0Q43P90Ps5YwDQYJKoZIhvcNAQEF
BQAwajELMAkGA1UEBhMCQ04xEzARBgNVBAoMCmlUcnVzQ2hpbmExHDAaBgNVBAsM
E0NoaW5hIFRydXN0IE5ldHdvcmsxKDAmBgNVBAMMH2lUcnVzQ2hpbmEgQ2xhc3Mg
MiBSb290IENBIC0gRzMwHhcNMTMwNDE4MDkzNjU2WhcNMzMwNDE4MDkzNjU2WjBq
MQswCQYDVQQGEwJDTjETMBEGA1UECgwKaVRydXNDaGluYTEcMBoGA1UECwwTQ2hp
bmEgVHJ1c3QgTmV0d29yazEoMCYGA1UEAwwfaVRydXNDaGluYSBDbGFzcyAyIFJv
b3QgQ0EgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOPPShpV
nJbMqqCw6Bz1kehnoPst9pkr0V9idOwU2oyS47/HjJXk9Rd5a9xfwkPO88trUpz5
4GmmwspDXjVFu9L0eFaRuH3KMha1Ak01citbF7cQLJlS7XI+tpkTGHEY5pt3EsQg
wykfZl/A1jrnSkspMS997r2Gim54cwz+mTMgDRhZsKK/lbOeBPpWtcFizjXYCqhw
WktvQfZBYi6o4sHCshnOswi4yV1p+LuFcQ2ciYdWvULh1eZhLxHbGXyznYHi0dGN
z+I9H8aXxqAQfHVhbdHNzi77hCxFjOy+hHrGsyzjrd2swVQ2iUWP8BfEQqGLqM1g
KgWKYfcTGdbPB1MCAwEAAaNjMGEwHQYDVR0OBBYEFG/oAMxTVe7y0+408CTAK8hA
uTyRMB8GA1UdIwQYMBaAFG/oAMxTVe7y0+408CTAK8hAuTyRMA8GA1UdEwEB/wQF
MAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQBLnUTfW7hp
emMbuUGCk7RBswzOT83bDM6824EkUnf+X0iKS95SUNGeeSWK2o/3ALJo5hi7GZr3
U8eLaWAcYizfO99UXMRBPw5PRR+gXGEronGUugLpxsjuynoLQu8GQAeysSXKbN1I
UugDo9u8igJORYA+5ms0s5sCUySqbQ2R5z/GoceyI9LdxIVa1RjVX8pYOj8JFwtn
DJN3ftSFvNMYwRuILKuqUYSHc2GPYiHVflDh5nDymCMOQFcFG3WsEuB+EYQPFgIU
1DHmdZcz7Llx8UOZXX2JupWCYzK1XhJb+r4hK5ncf/w8qGtYlmyJpxk3hr1TfUJX
Yf4Zr0fJsGuv
-----END CERTIFICATE-----

View File

@@ -0,0 +1,24 @@
-----BEGIN CERTIFICATE-----
MIIEsTCCA5mgAwIBAgIQICISFPsd7d6S/92S3ulaADANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UE
BhMCQ04xFjAUBgNVBAoMDUFudCBGaW5hbmNpYWwxIDAeBgNVBAsMF0NlcnRpZmljYXRpb24gQXV0
aG9yaXR5MTkwNwYDVQQDDDBBbnQgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IENs
YXNzIDEgUjEwHhcNMjIxMjE0MDgwNDUzWhcNMjcxMjEzMDgwNDUzWjB5MQswCQYDVQQGEwJDTjEt
MCsGA1UECgwk6LS15riv6YCf5Y2a6IO95rqQ56eR5oqA5pyJ6ZmQ5YWs5Y+4MQ8wDQYDVQQLDAZB
bGlwYXkxKjAoBgNVBAMMITIwODg0MzE4MDU1MjQ0MjItMjAyMTAwMzE1NjYyODUwODCCASIwDQYJ
KoZIhvcNAQEBBQADggEPADCCAQoCggEBAIHTIbsYo+M0gSGmNLpPi6bVb3YFyAsqHanby8HXZ1ql
YUH16DdrX+OHrp6AoTI9HQ2jBka9xpY4Bn8dfIJwOemcWLH+z9ipvzlTCr24WuSYHeazZEebdMz5
Y2cQ1wvLTBUJ6RxHWRxlm5hVaz/Edqv+iahIACFZ0xYClUDCsUUIdnEK4jDRVoluaV8VB52VVt9e
AT3if6U2LmsbQlKcyjoxCKYEJp4h+xmVZsoUEf2asSmS175Di8kMfqnqh2kb+GSEL71VqxwwvCFb
g3f7mDsYNGPVAYzseNai7ZxDfrDksJTzGTnm6vnRzodzGhCpmMVS/KR8JhI0xOFSEseueccCAwEA
AaOCASkwggElMB8GA1UdIwQYMBaAFHEH4gRhFuTl8mXrMQ/J4PQ8mtWRMB0GA1UdDgQWBBQl97aw
SWQ8zp5+e9RD6afVEnbJYTBABgNVHSAEOTA3MDUGB2CBHAFuAQEwKjAoBggrBgEFBQcCARYcaHR0
cDovL2NhLmFsaXBheS5jb20vY3BzLnBkZjAOBgNVHQ8BAf8EBAMCBsAwLwYDVR0fBCgwJjAkoCKg
IIYeaHR0cDovL2NhLmFsaXBheS5jb20vY3JsNzIuY3JsMGAGCCsGAQUFBwEBBFQwUjAoBggrBgEF
BQcwAoYcaHR0cDovL2NhLmFsaXBheS5jb20vY2E2LmNlcjAmBggrBgEFBQcwAYYaaHR0cDovL2Nh
LmFsaXBheS5jb206ODM0MC8wDQYJKoZIhvcNAQELBQADggEBAKV9NyKZ16oEm0VqRSm3hacwRYml
8ViSNbQU5RGh/moWWAAzOkeMRP/CGpYMAh9LhipdyGgSlYioeKA+tQomF+zkcotWkqXP4sy0BnqG
UpXdrBNFytU7RiAPt3GzPiKer9ed4A4JVnkxtfDNgPx9/NvalFKvjS+ZNTJgijaYxRtIgCf1E6wL
uVSSt4LT7+0zCNVSUUC+JVM5889jkO32L4YOP5I3vEpVJX2+2Y4jCp/H08rZ+6IAt7cdUlNPD8mr
1qC35DuoKDoay7b3VO56cgEC2ee3dSFemMYI3LAbz75PXgEEvzoyKGsD29lPTu6wTko8bev7P+mH
Rui9hJcXkCk=
-----END CERTIFICATE-----

View File

@@ -0,0 +1,23 @@
const fs = require('fs');
const path = require('path')
module.exports = {
// 统一 - 支付回调地址,格式为 "服务空间ID":"URL化地址"
"notifyUrl": {
// 本地开发环境-支付回调地址
"mp-3a96d416-40a3-47e3-8433-4e0760422059": "mp-3a96d416-40a3-47e3-8433-4e0760422059.bspapp.com/uni-pay-co",
// 线上正式环境-支付回调地址
"mp-3a96d416-40a3-47e3-8433-4e0760422059": "https://mp-3a96d416-40a3-47e3-8433-4e0760422059.bspapp.com/uni-pay-co",
},
// 支付宝相关证书记得选java版本
"alipay": {
"enable": true, // 是否启用支付宝支付
// 支付宝 - 小程序支付配置
"mp": {
"appId": "2021003156628508", // 支付宝小程序appid
"privateKey": "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCB0yG7GKPjNIEhpjS6T4um1W92BcgLKh2p28vB12dapWFB9eg3a1/jh66egKEyPR0NowZGvcaWOAZ/HXyCcDnpnFix/s/Yqb85Uwq9uFrkmB3ms2RHm3TM+WNnENcLy0wVCekcR1kcZZuYVWs/xHar/omoSAAhWdMWApVAwrFFCHZxCuIw0VaJbmlfFQedlVbfXgE94n+lNi5rG0JSnMo6MQimBCaeIfsZlWbKFBH9mrEpkte+Q4vJDH6p6odpG/hkhC+9VascMLwhW4N3+5g7GDRj1QGM7HjWou2cQ36w5LCU8xk55ur50c6HcxoQqZjFUvykfCYSNMThUhLHrnnHAgMBAAECggEACECplkEcueauhrsQAv/nerV6nmADtWH5/MAyFaJhuYtlwVGmb09uCwKnAQBgPtdPr3w7e+e4ZfgtwYrYTVpg9A6yPK5b/APeWgCDYEgFzx40WGPy7fJd3GHGBdk2MlO8BGJa5SdL7NgwqeBULvuIJ6rIiV/6UiRpnK6RWkqGBkNKO9AqQSGbvTI8jJ+NBAqmLeIk7Os9kCOiwZLqwPI9XVd+/exeOwsMTo/K5mKqpc1MltAhXYoeuzHGYtOyPCsgOOu3EBbivSCBN5gqy87JohJAScVmCyPTmDq1E2N6JkSujrBpS70VstnAt6g4fZQoidC9aSqF/i8xID8a0rI8wQKBgQDBsG/MQnLvvuonTLeHd7A737V4qRWTtntpxXJ8VWTl+M9PYoA7J4SKRicxvCHka2Cr1ITL62bEfl1MRFmz9BsKlpBVxhsOT49CPMIedpq3e92iOR/SqF161A1DBSVNYziTIbE6h5I1gZp+ZcFLKXXtsu0LpzNgYJlnS6V0e66UMQKBgQCrlwqNL+H5PPAOhiFswUK9ztjoAks7nBE8B//EJ29M3mUFf0isNMj8ta7DPQsQK1zmej/PQSKRoG/t55ao8Svrd29d2V4UYUNQWMb3YWN+QGdIy1ta25hJlwdkKbQJIYAQ8/8i5Y6BnCLwLuj7KMpWBuPWkl2YDfRjJlAhz85HdwKBgQCweVFjiieuyQQPSpbtlt+7rdhqV6SRMXLArGXjYurLnidE7Tpoq1jXo5OSfRdkR3GNHdTg91prLbdUBfK1Q3Rf8U8Q169PGq4sa69ykh3lj7YgWGvmRADoKMzsg4O5Pu4NIGWaLmvI1I0vHQdAtEX+jUftlin5ZgpfU00tFIO8AQKBgQClK45Hm9js3sDBalHQazQAm5TluBeNOMzKOXT073TOzKD3qq9cvK7fu0+PtjnpBaS2YuT7btqEzagQnMXEt+osDdrQvwU1nu558AsOY9uu0vXY949npUwxQkUmIJKh23J4Xzav88K6dn6XLsCry3cBWj6E9H1NedlOe7nU4kDRPwKBgQC1oK7oeO0qSNGE8bhnZLAyRnmWLGGSPMf+oBocox4nixcknrPjsWBEJcI0keGanaiam94VkftFnl2c6ZW8229bpimdYj9891VN+8NvBSYTR7lOuTXRZUtTLu9ocgJ/aGQJRXHs+hwZeHcY1PnEkCi6IJXb6e2+Kq2mbdz30mqOww==", // 支付宝商户私钥
"appCertPath": path.join(__dirname, 'alipay/appCertPublicKey.crt'), // 支付宝商户公钥路径
"alipayPublicCertPath": path.join(__dirname, 'alipay/alipayCertPublicKey_RSA2.crt'), // 支付宝公钥路径
"alipayRootCertPath": path.join(__dirname, 'alipay/alipayRootCert.crt'), // 支付宝根证书路径
},
}
}

View File

@@ -0,0 +1,3 @@
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----

View File

@@ -0,0 +1,3 @@
-----BEGIN PRIVATE KEY-----
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,137 @@
# uni-pay配置说明
## 目录结构
```
.
├── alipay──────────────────────────────# 支付宝证书目录
│ └── alipayCertPublicKey_RSA2.crt────────────────# 支付宝商户公钥证书
│ └── alipayRootCert.crt──────────────────────────# 支付宝根证书
│ └── appCertPublicKey.crt────────────────────────# 支付宝公钥证书
├── wxpay───────────────────────────────# 微信支付证书目录
│ └── apiclient_cert.pem─────────────────────────# 微信商户公钥证书
│ └── apiclient_key.pem──────────────────────────# 微信商户私钥证书
│ └── apiclient_cert.p12──────────────────────────────────# 微信商户p12格式的证书
├── config.js──────────────────────────# 支付配置文件
```
**注意即使你不需要证书也不要删除默认的空白证书否则会报fs.readFileSync(__dirname+'/wxpay/apiclient_cert.p12') 找不到文件的错误。)**
## config.js 介绍
```js
const fs = require('fs');
const path = require('path')
module.exports = {
// 统一 - 支付回调地址,格式为 "服务空间ID":"URL化地址"
"notifyUrl": {
// 本地开发环境-支付回调地址
"b267e273-19a7-4288-99c7-f6f27f9c5b77": "https://b267e273-19a7-4288-99c7-f6f27f9c5b77.bspapp.com/uni-pay-co",
// 线上正式环境-支付回调地址
"499e2a37-0c77-418a-82aa-3e5820ecb057": "https://499e2a37-0c77-418a-82aa-3e5820ecb057.bspapp.com/uni-pay-co",
},
"notifyKey":"5FB2CD73C7B53918728417C50762E6D45FB2CD73C7B53918728417C50762E6D4", // 跨云函数通信时的加密密钥建议手动改下不要使用默认的密钥长度保持在64位以上即可
// 微信支付相关
"wxpay": {
"enable": true, // 是否启用微信支付
// 微信 - 小程序支付
"mp": {
"appId": "", // 小程序的appid
"secret": "", // 小程序的secret
"mchId": "", // 商户id
"key": "", // v2的api key
"pfx": fs.readFileSync(__dirname + '/wxpay/wxpay.p12'), // v2需要用到的证书
"v3Key": "", // v3的api key
"appCertPath": path.join(__dirname, 'wxpay/apiclient_cert.pem'), // v3需要用到的证书
"appPrivateKeyPath": path.join(__dirname, 'wxpay/apiclient_key.pem'), // v3需要用到的证书
"version": 2, // 启用支付的版本 2代表v2版本 3 代表v3版本
},
// 微信 - APP支付
"app": {
"appId": "", // app开放平台下的应用的appid
"secret": "", // app开放平台下的应用的secret
"mchId": "", // 商户id
"key": "", // v2的api key
"pfx": fs.readFileSync(__dirname + '/wxpay/wxpay.p12'), // v2需要用到的证书
"v3Key": "", // v3的api key
"appCertPath": path.join(__dirname, 'wxpay/apiclient_cert.pem'), // v3需要用到的证书
"appPrivateKeyPath": path.join(__dirname, 'wxpay/apiclient_key.pem'), // v3需要用到的证书
"version": 2, // 启用支付的版本 2代表v2版本 3 代表v3版本
},
// 微信 - 扫码支付
"native": {
"appId": "", // 可以是小程序或公众号或app开放平台下的应用的任意一个appid
"secret": "", // secret
"mchId": "", // 商户id
"key": "", // v2的api key
"pfx": fs.readFileSync(__dirname + '/wxpay/wxpay.p12'), // v2需要用到的证书
"v3Key": "", // v3的api key
"appCertPath": path.join(__dirname, 'wxpay/apiclient_cert.pem'), // v3需要用到的证书
"appPrivateKeyPath": path.join(__dirname, 'wxpay/apiclient_key.pem'), // v3需要用到的证书
"version": 2, // 启用支付的版本 2代表v2版本 3 代表v3版本
},
// 微信 - 公众号支付
"jsapi": {
"appId": "", // 公众号的appid
"secret": "", // 公众号的secret
"mchId": "", // 商户id
"key": "", // v2的api key
"pfx": fs.readFileSync(__dirname + '/wxpay/wxpay.p12'), // v2需要用到的证书
"v3Key": "", // v3的api key
"appCertPath": path.join(__dirname, 'wxpay/apiclient_cert.pem'), // v3需要用到的证书
"appPrivateKeyPath": path.join(__dirname, 'wxpay/apiclient_key.pem'), // v3需要用到的证书
"version": 2, // 启用支付的版本 2代表v2版本 3 代表v3版本
},
// 微信 - 手机外部浏览器H5支付
"mweb": {
"appId": "", // 可以是小程序或公众号或app开放平台下的应用的任意一个appid
"secret": "", // secret
"mchId": "", // 商户id
"key": "", // v2的api key
"pfx": fs.readFileSync(__dirname + '/wxpay/wxpay.p12'), // v2需要用到的证书
"v3Key": "", // v3的api key
"appCertPath": path.join(__dirname, 'wxpay/apiclient_cert.pem'), // v3需要用到的证书
"appPrivateKeyPath": path.join(__dirname, 'wxpay/apiclient_key.pem'), // v3需要用到的证书
"version": 2, // 启用支付的版本 2代表v2版本 3 代表v3版本
// 场景信息,必填
"sceneInfo": {
"h5_info": {
"type": "Wap", // 此值固定Wap
"wap_url": "", // 你的H5首页地址必须和你发起支付的页面的域名一致。
"wap_name": "", // 你的H5网站名称
}
}
},
},
// 支付宝相关证书记得选java版本
"alipay": {
"enable": true, // 是否启用支付宝支付
// 支付宝 - 小程序支付配置
"mp": {
"appId": "", // 支付宝小程序appid
"privateKey": "", // 支付宝商户私钥
"appCertPath": path.join(__dirname, 'alipay/appCertPublicKey.crt'), // 支付宝商户公钥路径
"alipayPublicCertPath": path.join(__dirname, 'alipay/alipayCertPublicKey_RSA2.crt'), // 支付宝公钥路径
"alipayRootCertPath": path.join(__dirname, 'alipay/alipayRootCert.crt'), // 支付宝根证书路径
},
// 支付宝 - APP支付配置
"app": {
"appId": "", // 支付宝开放平台下应用的appid
"privateKey": "", // 支付宝商户私钥
"appCertPath": path.join(__dirname, 'alipay/appCertPublicKey.crt'), // 支付宝商户公钥路径
"alipayPublicCertPath": path.join(__dirname, 'alipay/alipayCertPublicKey_RSA2.crt'), // 支付宝公钥路径
"alipayRootCertPath": path.join(__dirname, 'alipay/alipayRootCert.crt'), // 支付宝根证书路径
},
// 支付宝 - H5支付配置包含网站二维码、手机H5需申请支付宝当面付接口权限
"native": {
"appId": "", // 支付宝开放平台下应用的appid
"privateKey": "", // 支付宝商户私钥
"appCertPath": path.join(__dirname, 'alipay/appCertPublicKey.crt'), // 支付宝商户公钥路径
"alipayPublicCertPath": path.join(__dirname, 'alipay/alipayCertPublicKey_RSA2.crt'), // 支付宝公钥路径
"alipayRootCertPath": path.join(__dirname, 'alipay/alipayRootCert.crt'), // 支付宝根证书路径
}
}
}
```

View File

@@ -0,0 +1,22 @@
## 1.3.52022-01-24
- 优化 size 属性可以传入不带单位的字符串数值
## 1.3.42022-01-24
- 优化 size 支持其他单位
## 1.3.32022-01-17
- 修复 nvue 有些图标不显示的bug兼容老版本图标
## 1.3.22021-12-01
- 优化 示例可复制图标名称
## 1.3.12021-11-23
- 优化 兼容旧组件 type 值
## 1.3.02021-11-19
- 新增 更多图标
- 优化 自定义图标使用方式
- 优化 组件UI并提供设计资源详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-icons](https://uniapp.dcloud.io/component/uniui/uni-icons)
## 1.1.72021-11-08
## 1.2.02021-07-30
- 组件兼容 vue3如何创建vue3项目详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.1.52021-05-12
- 新增 组件示例地址
## 1.1.42021-02-05
- 调整为uni_modules目录规范

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,96 @@
<template>
<!-- #ifdef APP-NVUE -->
<text :style="{ color: color, 'font-size': iconSize }" class="uni-icons" @click="_onClick">{{unicode}}</text>
<!-- #endif -->
<!-- #ifndef APP-NVUE -->
<text :style="{ color: color, 'font-size': iconSize }" class="uni-icons" :class="['uniui-'+type,customPrefix,customPrefix?type:'']" @click="_onClick"></text>
<!-- #endif -->
</template>
<script>
import icons from './icons.js';
const getVal = (val) => {
const reg = /^[0-9]*$/g
return (typeof val === 'number' || reg.test(val) )? val + 'px' : val;
}
// #ifdef APP-NVUE
var domModule = weex.requireModule('dom');
import iconUrl from './uniicons.ttf'
domModule.addRule('fontFace', {
'fontFamily': "uniicons",
'src': "url('"+iconUrl+"')"
});
// #endif
/**
* Icons 图标
* @description 用于展示 icons 图标
* @tutorial https://ext.dcloud.net.cn/plugin?id=28
* @property {Number} size 图标大小
* @property {String} type 图标图案,参考示例
* @property {String} color 图标颜色
* @property {String} customPrefix 自定义图标
* @event {Function} click 点击 Icon 触发事件
*/
export default {
name: 'UniIcons',
emits:['click'],
props: {
type: {
type: String,
default: ''
},
color: {
type: String,
default: '#333333'
},
size: {
type: [Number, String],
default: 16
},
customPrefix:{
type: String,
default: ''
}
},
data() {
return {
icons: icons.glyphs
}
},
computed:{
unicode(){
let code = this.icons.find(v=>v.font_class === this.type)
if(code){
return unescape(`%u${code.unicode}`)
}
return ''
},
iconSize(){
return getVal(this.size)
}
},
methods: {
_onClick() {
this.$emit('click')
}
}
}
</script>
<style lang="scss">
/* #ifndef APP-NVUE */
@import './uniicons.css';
@font-face {
font-family: uniicons;
src: url('./uniicons.ttf') format('truetype');
}
/* #endif */
.uni-icons {
font-family: uniicons;
text-decoration: none;
text-align: center;
}
</style>

View File

@@ -0,0 +1,663 @@
.uniui-color:before {
content: "\e6cf";
}
.uniui-wallet:before {
content: "\e6b1";
}
.uniui-settings-filled:before {
content: "\e6ce";
}
.uniui-auth-filled:before {
content: "\e6cc";
}
.uniui-shop-filled:before {
content: "\e6cd";
}
.uniui-staff-filled:before {
content: "\e6cb";
}
.uniui-vip-filled:before {
content: "\e6c6";
}
.uniui-plus-filled:before {
content: "\e6c7";
}
.uniui-folder-add-filled:before {
content: "\e6c8";
}
.uniui-color-filled:before {
content: "\e6c9";
}
.uniui-tune-filled:before {
content: "\e6ca";
}
.uniui-calendar-filled:before {
content: "\e6c0";
}
.uniui-notification-filled:before {
content: "\e6c1";
}
.uniui-wallet-filled:before {
content: "\e6c2";
}
.uniui-medal-filled:before {
content: "\e6c3";
}
.uniui-gift-filled:before {
content: "\e6c4";
}
.uniui-fire-filled:before {
content: "\e6c5";
}
.uniui-refreshempty:before {
content: "\e6bf";
}
.uniui-location-filled:before {
content: "\e6af";
}
.uniui-person-filled:before {
content: "\e69d";
}
.uniui-personadd-filled:before {
content: "\e698";
}
.uniui-back:before {
content: "\e6b9";
}
.uniui-forward:before {
content: "\e6ba";
}
.uniui-arrow-right:before {
content: "\e6bb";
}
.uniui-arrowthinright:before {
content: "\e6bb";
}
.uniui-arrow-left:before {
content: "\e6bc";
}
.uniui-arrowthinleft:before {
content: "\e6bc";
}
.uniui-arrow-up:before {
content: "\e6bd";
}
.uniui-arrowthinup:before {
content: "\e6bd";
}
.uniui-arrow-down:before {
content: "\e6be";
}
.uniui-arrowthindown:before {
content: "\e6be";
}
.uniui-bottom:before {
content: "\e6b8";
}
.uniui-arrowdown:before {
content: "\e6b8";
}
.uniui-right:before {
content: "\e6b5";
}
.uniui-arrowright:before {
content: "\e6b5";
}
.uniui-top:before {
content: "\e6b6";
}
.uniui-arrowup:before {
content: "\e6b6";
}
.uniui-left:before {
content: "\e6b7";
}
.uniui-arrowleft:before {
content: "\e6b7";
}
.uniui-eye:before {
content: "\e651";
}
.uniui-eye-filled:before {
content: "\e66a";
}
.uniui-eye-slash:before {
content: "\e6b3";
}
.uniui-eye-slash-filled:before {
content: "\e6b4";
}
.uniui-info-filled:before {
content: "\e649";
}
.uniui-reload:before {
content: "\e6b2";
}
.uniui-micoff-filled:before {
content: "\e6b0";
}
.uniui-map-pin-ellipse:before {
content: "\e6ac";
}
.uniui-map-pin:before {
content: "\e6ad";
}
.uniui-location:before {
content: "\e6ae";
}
.uniui-starhalf:before {
content: "\e683";
}
.uniui-star:before {
content: "\e688";
}
.uniui-star-filled:before {
content: "\e68f";
}
.uniui-calendar:before {
content: "\e6a0";
}
.uniui-fire:before {
content: "\e6a1";
}
.uniui-medal:before {
content: "\e6a2";
}
.uniui-font:before {
content: "\e6a3";
}
.uniui-gift:before {
content: "\e6a4";
}
.uniui-link:before {
content: "\e6a5";
}
.uniui-notification:before {
content: "\e6a6";
}
.uniui-staff:before {
content: "\e6a7";
}
.uniui-vip:before {
content: "\e6a8";
}
.uniui-folder-add:before {
content: "\e6a9";
}
.uniui-tune:before {
content: "\e6aa";
}
.uniui-auth:before {
content: "\e6ab";
}
.uniui-person:before {
content: "\e699";
}
.uniui-email-filled:before {
content: "\e69a";
}
.uniui-phone-filled:before {
content: "\e69b";
}
.uniui-phone:before {
content: "\e69c";
}
.uniui-email:before {
content: "\e69e";
}
.uniui-personadd:before {
content: "\e69f";
}
.uniui-chatboxes-filled:before {
content: "\e692";
}
.uniui-contact:before {
content: "\e693";
}
.uniui-chatbubble-filled:before {
content: "\e694";
}
.uniui-contact-filled:before {
content: "\e695";
}
.uniui-chatboxes:before {
content: "\e696";
}
.uniui-chatbubble:before {
content: "\e697";
}
.uniui-upload-filled:before {
content: "\e68e";
}
.uniui-upload:before {
content: "\e690";
}
.uniui-weixin:before {
content: "\e691";
}
.uniui-compose:before {
content: "\e67f";
}
.uniui-qq:before {
content: "\e680";
}
.uniui-download-filled:before {
content: "\e681";
}
.uniui-pyq:before {
content: "\e682";
}
.uniui-sound:before {
content: "\e684";
}
.uniui-trash-filled:before {
content: "\e685";
}
.uniui-sound-filled:before {
content: "\e686";
}
.uniui-trash:before {
content: "\e687";
}
.uniui-videocam-filled:before {
content: "\e689";
}
.uniui-spinner-cycle:before {
content: "\e68a";
}
.uniui-weibo:before {
content: "\e68b";
}
.uniui-videocam:before {
content: "\e68c";
}
.uniui-download:before {
content: "\e68d";
}
.uniui-help:before {
content: "\e679";
}
.uniui-navigate-filled:before {
content: "\e67a";
}
.uniui-plusempty:before {
content: "\e67b";
}
.uniui-smallcircle:before {
content: "\e67c";
}
.uniui-minus-filled:before {
content: "\e67d";
}
.uniui-micoff:before {
content: "\e67e";
}
.uniui-closeempty:before {
content: "\e66c";
}
.uniui-clear:before {
content: "\e66d";
}
.uniui-navigate:before {
content: "\e66e";
}
.uniui-minus:before {
content: "\e66f";
}
.uniui-image:before {
content: "\e670";
}
.uniui-mic:before {
content: "\e671";
}
.uniui-paperplane:before {
content: "\e672";
}
.uniui-close:before {
content: "\e673";
}
.uniui-help-filled:before {
content: "\e674";
}
.uniui-paperplane-filled:before {
content: "\e675";
}
.uniui-plus:before {
content: "\e676";
}
.uniui-mic-filled:before {
content: "\e677";
}
.uniui-image-filled:before {
content: "\e678";
}
.uniui-locked-filled:before {
content: "\e668";
}
.uniui-info:before {
content: "\e669";
}
.uniui-locked:before {
content: "\e66b";
}
.uniui-camera-filled:before {
content: "\e658";
}
.uniui-chat-filled:before {
content: "\e659";
}
.uniui-camera:before {
content: "\e65a";
}
.uniui-circle:before {
content: "\e65b";
}
.uniui-checkmarkempty:before {
content: "\e65c";
}
.uniui-chat:before {
content: "\e65d";
}
.uniui-circle-filled:before {
content: "\e65e";
}
.uniui-flag:before {
content: "\e65f";
}
.uniui-flag-filled:before {
content: "\e660";
}
.uniui-gear-filled:before {
content: "\e661";
}
.uniui-home:before {
content: "\e662";
}
.uniui-home-filled:before {
content: "\e663";
}
.uniui-gear:before {
content: "\e664";
}
.uniui-smallcircle-filled:before {
content: "\e665";
}
.uniui-map-filled:before {
content: "\e666";
}
.uniui-map:before {
content: "\e667";
}
.uniui-refresh-filled:before {
content: "\e656";
}
.uniui-refresh:before {
content: "\e657";
}
.uniui-cloud-upload:before {
content: "\e645";
}
.uniui-cloud-download-filled:before {
content: "\e646";
}
.uniui-cloud-download:before {
content: "\e647";
}
.uniui-cloud-upload-filled:before {
content: "\e648";
}
.uniui-redo:before {
content: "\e64a";
}
.uniui-images-filled:before {
content: "\e64b";
}
.uniui-undo-filled:before {
content: "\e64c";
}
.uniui-more:before {
content: "\e64d";
}
.uniui-more-filled:before {
content: "\e64e";
}
.uniui-undo:before {
content: "\e64f";
}
.uniui-images:before {
content: "\e650";
}
.uniui-paperclip:before {
content: "\e652";
}
.uniui-settings:before {
content: "\e653";
}
.uniui-search:before {
content: "\e654";
}
.uniui-redo-filled:before {
content: "\e655";
}
.uniui-list:before {
content: "\e644";
}
.uniui-mail-open-filled:before {
content: "\e63a";
}
.uniui-hand-down-filled:before {
content: "\e63c";
}
.uniui-hand-down:before {
content: "\e63d";
}
.uniui-hand-up-filled:before {
content: "\e63e";
}
.uniui-hand-up:before {
content: "\e63f";
}
.uniui-heart-filled:before {
content: "\e641";
}
.uniui-mail-open:before {
content: "\e643";
}
.uniui-heart:before {
content: "\e639";
}
.uniui-loop:before {
content: "\e633";
}
.uniui-pulldown:before {
content: "\e632";
}
.uniui-scan:before {
content: "\e62a";
}
.uniui-bars:before {
content: "\e627";
}
.uniui-cart-filled:before {
content: "\e629";
}
.uniui-checkbox:before {
content: "\e62b";
}
.uniui-checkbox-filled:before {
content: "\e62c";
}
.uniui-shop:before {
content: "\e62f";
}
.uniui-headphones:before {
content: "\e630";
}
.uniui-cart:before {
content: "\e631";
}

View File

@@ -0,0 +1,86 @@
{
"id": "uni-icons",
"displayName": "uni-icons 图标",
"version": "1.3.5",
"description": "图标组件,用于展示移动端常见的图标,可自定义颜色、大小。",
"keywords": [
"uni-ui",
"uniui",
"icon",
"图标"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": "^3.2.14"
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
},
"uni_modules": {
"dependencies": ["uni-scss"],
"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"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}

View File

@@ -0,0 +1,8 @@
## Icons 图标
> **组件名uni-icons**
> 代码块: `uIcons`
用于展示 icons 图标 。
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-icons)
#### 如使用过程中有任何问题或者您对uni-ui有一些好的建议欢迎加入 uni-ui 交流群871950839

View File

@@ -0,0 +1,26 @@
## 1.0.132022-07-21
- 修复 创建token时未传角色权限信息生成的token不正确的bug
## 1.0.122022-07-15
- 提升与旧版本uni-id的兼容性补充读取配置文件时回退平台app-plus、h5但是仍推荐使用新平台名进行配置app、web
## 1.0.112022-07-14
- 修复 部分情况下报`read property 'reduce' of undefined`的错误
## 1.0.102022-07-11
- 将token存储在用户表的token字段内与旧版本uni-id保持一致
## 1.0.92022-07-01
- checkToken兼容token内未缓存角色权限的情况此时将查库获取角色权限
## 1.0.82022-07-01
- 修复clientDB默认依赖时部分情况下获取不到uni-id配置的Bug
## 1.0.72022-06-30
- 修复config文件不合法时未抛出具体错误的Bug
## 1.0.62022-06-28
- 移除插件内的数据表schema
## 1.0.52022-06-27
- 修复使用多应用配置时报`Cannot read property 'appId' of undefined`的Bug
## 1.0.42022-06-27
- 修复使用自定义token内容功能报错的Bug [详情](https://ask.dcloud.net.cn/question/147945)
## 1.0.22022-06-23
- 对齐旧版本uni-id默认配置
## 1.0.12022-06-22
- 补充对uni-config-center的依赖
## 1.0.02022-06-21
- 提供uni-id token创建、校验、刷新接口简化旧版uni-id公共模块

View File

@@ -0,0 +1,87 @@
{
"id": "uni-id-common",
"displayName": "uni-id-common",
"version": "1.0.13",
"description": "包含uni-id token生成、校验、刷新功能的云函数公共模块",
"keywords": [
"uni-id-common",
"uniCloud",
"token",
"权限"
],
"repository": "https://gitcode.net/dcloud/uni-id-common",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"category": [
"uniCloud",
"云函数模板"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": ["uni-config-center"],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"Vue": {
"vue2": "u",
"vue3": "u"
},
"App": {
"app-vue": "u",
"app-nvue": "u"
},
"H5-mobile": {
"Safari": "u",
"Android Browser": "u",
"微信浏览器(Android)": "u",
"QQ浏览器(Android)": "u"
},
"H5-pc": {
"Chrome": "u",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "u",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u",
"钉钉": "u",
"快手": "u",
"飞书": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}

View File

@@ -0,0 +1,3 @@
# uni-id-common
文档请参考:[uni-id-common](https://uniapp.dcloud.net.cn/uniCloud/uni-id-common.html)

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,16 @@
{
"name": "uni-id-common",
"version": "1.0.13",
"description": "uni-id token生成、校验、刷新",
"main": "index.js",
"homepage": "https://uniapp.dcloud.io/uniCloud/uni-id-common.html",
"repository": {
"type": "git",
"url": "git+https://gitee.com/dcloud/uni-id-common.git"
},
"author": "DCloud",
"license": "Apache-2.0",
"dependencies": {
"uni-config-center": "file:../../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center"
}
}

View File

@@ -0,0 +1,40 @@
## 1.2.102022-11-23
修复 uni-list-item 组件 keep-scroll-position 属性 无法设置为false的错误
## 1.2.92022-11-22
- 修复 uni-list-chat 在vue3下跳转报错的bug
## 1.2.82022-11-21
- 修复 uni-list-chat avatar属性 值为本地路径时错误的问题
## 1.2.72022-11-21
- 修复 uni-list-chat avatar属性 在腾讯云版uniCloud下错误的问题
## 1.2.62022-11-18
- 修复 uni-list-chat note属性 支持:“草稿”字样功能 文本少1位的问题
## 1.2.52022-11-15
- 修复 uni-list-item 的 customStyle 属性 padding值在 H5端 无效的bug
## 1.2.42022-11-15
- 修复 uni-list-item 的 customStyle 属性 padding值在nvuevue2下无效的bug
## 1.2.32022-11-14
- uni-list-chat 新增 avatar 支持 fileId
## 1.2.22022-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.12022-03-30
- 删除无用文件
## 1.2.02021-11-23
- 优化 组件UI并提供设计资源详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-list](https://uniapp.dcloud.io/component/uniui/uni-list)
## 1.1.32021-08-30
- 修复 在vue3中to属性在发行应用的时候报错的bug
## 1.1.22021-07-30
- 优化 vue3下事件警告的问题
## 1.1.12021-07-21
- 修复 与其他组件嵌套使用时点击失效的Bug
## 1.1.02021-07-13
- 组件兼容 vue3如何创建vue3项目详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.0.172021-05-12
- 新增 组件示例地址
## 1.0.162021-02-05
- 优化 组件引用关系通过uni_modules引用组件
## 1.0.152021-02-05
- 调整为uni_modules目录规范
- 修复 uni-list-chat 角标显示不正常的问题

View File

@@ -0,0 +1,107 @@
<template>
<!-- #ifdef APP-NVUE -->
<cell>
<!-- #endif -->
<view class="uni-list-ad">
<view v-if="borderShow" :class="{'uni-list--border':border,'uni-list-item--first':isFirstChild}"></view>
<ad style="width: 200px;height: 300px;border-width: 1px;border-color: red;border-style: solid;" adpid="1111111111"
unit-id="" appid="" apid="" type="feed" @error="aderror" @close="closeAd"></ad>
</view>
<!-- #ifdef APP-NVUE -->
</cell>
<!-- #endif -->
</template>
<script>
// #ifdef APP-NVUE
const dom = uni.requireNativePlugin('dom');
// #endif
export default {
name: 'UniListAd',
props: {
title: {
type: String,
default: '',
}
},
// inject: ['list'],
data() {
return {
isFirstChild: false,
border: false,
borderShow: true,
}
},
mounted() {
this.list = this.getForm()
if (this.list) {
if (!this.list.firstChildAppend) {
this.list.firstChildAppend = true
this.isFirstChild = true
}
this.border = this.list.border
}
},
methods: {
/**
* 获取父元素实例
*/
getForm(name = 'uniList') {
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;
},
aderror(e) {
console.log("aderror: " + JSON.stringify(e.detail));
},
closeAd(e) {
this.borderShow = false
}
}
}
</script>
<style lang="scss" >
.uni-list-ad {
position: relative;
border: 1px red solid;
}
.uni-list--border {
position: relative;
padding-bottom: 1px;
/* #ifdef APP-PLUS */
border-top-color: $uni-border-color;
border-top-style: solid;
border-top-width: 0.5px;
/* #endif */
margin-left: $uni-spacing-row-lg;
}
/* #ifndef APP-NVUE */
.uni-list--border:after {
position: absolute;
top: 0;
right: 0;
left: 0;
height: 1px;
content: '';
-webkit-transform: scaleY(.5);
transform: scaleY(.5);
background-color: $uni-border-color;
}
.uni-list-item--first:after {
height: 0px;
}
/* #endif */
</style>

View File

@@ -0,0 +1,58 @@
/**
* 这里是 uni-list 组件内置的常用样式变量
* 如果需要覆盖样式,这里提供了基本的组件样式变量,您可以尝试修改这里的变量,去完成样式替换,而不用去修改源码
*
*/
// 背景色
$background-color : #fff;
// 分割线颜色
$divide-line-color : #e5e5e5;
// 默认头像大小,如需要修改此值,注意同步修改 js 中的值 const avatarWidth = xx ,目前只支持方形头像
// nvue 页面不支持修改头像大小
$avatar-width : 45px ;
// 头像边框
$avatar-border-radius: 5px;
$avatar-border-color: #eee;
$avatar-border-width: 1px;
// 标题文字样式
$title-size : 16px;
$title-color : #3b4144;
$title-weight : normal;
// 描述文字样式
$note-size : 12px;
$note-color : #999;
$note-weight : normal;
// 右侧额外内容默认样式
$right-text-size : 12px;
$right-text-color : #999;
$right-text-weight : normal;
// 角标样式
// nvue 页面不支持修改圆点位置以及大小
// 角标在左侧时,角标的位置,默认为 0 ,负数左/下移动,正数右/上移动
$badge-left: 0px;
$badge-top: 0px;
// 显示圆点时,圆点大小
$dot-width: 10px;
$dot-height: 10px;
// 显示角标时,角标大小和字体大小
$badge-size : 18px;
$badge-font : 12px;
// 显示角标时,角标前景色
$badge-color : #fff;
// 显示角标时,角标背景色
$badge-background-color : #ff5a5f;
// 显示角标时,角标左右间距
$badge-space : 6px;
// 状态样式
// 选中颜色
$hover : #f5f5f5;

View File

@@ -0,0 +1,571 @@
<template>
<!-- #ifdef APP-NVUE -->
<cell>
<!-- #endif -->
<view :hover-class="!clickable && !link ? '' : 'uni-list-chat--hover'" class="uni-list-chat" @click.stop="onClick">
<view :class="{ 'uni-list--border': border, 'uni-list-chat--first': isFirstChild }"></view>
<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="avatarUrl" mode="aspectFill"></image>
</view>
<!-- 头像组 -->
<view v-else class="uni-list-chat__header">
<view v-for="(item, index) in avatarList" :key="index" class="uni-list-chat__header-box" :class="computedAvatar"
:style="{ width: imageWidth + 'px', height: imageWidth + 'px' }">
<image class="uni-list-chat__header-image" :style="{ width: imageWidth + 'px', height: imageWidth + 'px' }" :src="item.url"
mode="aspectFill"></image>
</view>
</view>
</view>
<view v-if="badgeText && badgePositon === 'left'" class="uni-list-chat__badge uni-list-chat__badge-pos" :class="[isSingle]">
<text class="uni-list-chat__badge-text">{{ badgeText === 'dot' ? '' : badgeText }}</text>
</view>
<view class="uni-list-chat__content">
<view class="uni-list-chat__content-main">
<text class="uni-list-chat__content-title uni-ellipsis">{{ title }}</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):note}}</text>
</view>
</view>
<view class="uni-list-chat__content-extra">
<slot>
<text class="uni-list-chat__content-extra-text">{{ time }}</text>
<view v-if="badgeText && badgePositon === 'right'" class="uni-list-chat__badge" :class="[isSingle, badgePositon === 'right' ? 'uni-list-chat--right' : '']">
<text class="uni-list-chat__badge-text">{{ badgeText === 'dot' ? '' : badgeText }}</text>
</view>
</slot>
</view>
</view>
</view>
</view>
<!-- #ifdef APP-NVUE -->
</cell>
<!-- #endif -->
</template>
<script>
// 头像大小
const avatarWidth = 45;
/**
* ListChat 聊天列表
* @description 聊天列表,用于创建聊天类列表
* @tutorial https://ext.dcloud.net.cn/plugin?id=24
* @property {String} title 标题
* @property {String} note 描述
* @property {Boolean} clickable = [true|false] 是否开启点击反馈默认为false
* @property {String} badgeText 数字角标内容
* @property {String} badgePositon = [left|right] 角标位置,默认为 right
* @property {String} link = [falsenavigateTo|redirectTo|reLaunch|switchTab] 是否展示右侧箭头并开启点击反馈默认为false
* @value false 不开启
* @value navigateTo 同 uni.navigateTo()
* @value redirectTo 同 uni.redirectTo()
* @value reLaunch 同 uni.reLaunch()
* @value switchTab 同 uni.switchTab()
* @property {String | PageURIString} to 跳转目标页面
* @property {String} time 右侧时间显示
* @property {Boolean} avatarCircle = [true|false] 是否显示圆形头像默认为false
* @property {String} avatar 头像地址avatarCircle 不填时生效
* @property {Array} avatarList 头像组,格式为 [{url:''}]
* @event {Function} click 点击 uniListChat 触发事件
*/
export default {
name: 'UniListChat',
emits:['click'],
props: {
title: {
type: String,
default: ''
},
note: {
type: String,
default: ''
},
clickable: {
type: Boolean,
default: false
},
link: {
type: [Boolean, String],
default: false
},
to: {
type: String,
default: ''
},
badgeText: {
type: [String, Number],
default: ''
},
badgePositon: {
type: String,
default: 'right'
},
time: {
type: String,
default: ''
},
avatarCircle: {
type: Boolean,
default: false
},
avatar: {
type: String,
default: ''
},
avatarList: {
type: Array,
default () {
return [];
}
}
},
// inject: ['list'],
computed: {
isDraft(){
return this.note.slice(0,14) == '[uni-im-draft]'
},
isSingle() {
if (this.badgeText === 'dot') {
return 'uni-badge--dot';
} else {
const badgeText = this.badgeText.toString();
if (badgeText.length > 1) {
return 'uni-badge--complex';
} else {
return 'uni-badge--single';
}
}
},
computedAvatar() {
if (this.avatarList.length > 4) {
this.imageWidth = avatarWidth * 0.31;
return 'avatarItem--3';
} else if (this.avatarList.length > 1) {
this.imageWidth = avatarWidth * 0.47;
return 'avatarItem--2';
} else {
this.imageWidth = avatarWidth;
return 'avatarItem--1';
}
}
},
watch: {
avatar:{
handler(avatar) {
if(avatar.substr(0,8) == 'cloud://'){
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,
avatarUrl:''
};
},
mounted() {
this.list = this.getForm()
if (this.list) {
if (!this.list.firstChildAppend) {
this.list.firstChildAppend = true;
this.isFirstChild = true;
}
this.border = this.list.border;
}
},
methods: {
/**
* 获取父元素实例
*/
getForm(name = 'uniList') {
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;
},
onClick() {
if (this.to !== '') {
this.openPage();
return;
}
if (this.clickable || this.link) {
this.$emit('click', {
data: {}
});
}
},
openPage() {
if (['navigateTo', 'redirectTo', 'reLaunch', 'switchTab'].indexOf(this.link) !== -1) {
this.pageApi(this.link);
} else {
this.pageApi('navigateTo');
}
},
pageApi(api) {
uni[api]({
url: this.to,
success: res => {
this.$emit('click', {
data: res
});
},
fail: err => {
this.$emit('click', {
data: err
});
console.error(err.errMsg);
}
});
}
}
};
</script>
<style lang="scss" >
$uni-font-size-lg:16px;
$uni-spacing-row-sm: 5px;
$uni-spacing-row-base: 10px;
$uni-spacing-row-lg: 15px;
$background-color: #fff;
$divide-line-color: #e5e5e5;
$avatar-width: 45px;
$avatar-border-radius: 5px;
$avatar-border-color: #eee;
$avatar-border-width: 1px;
$title-size: 16px;
$title-color: #3b4144;
$title-weight: normal;
$note-size: 12px;
$note-color: #999;
$note-weight: normal;
$right-text-size: 12px;
$right-text-color: #999;
$right-text-weight: normal;
$badge-left: 0px;
$badge-top: 0px;
$dot-width: 10px;
$dot-height: 10px;
$badge-size: 18px;
$badge-font: 12px;
$badge-color: #fff;
$badge-background-color: #ff5a5f;
$badge-space: 6px;
$hover: #f5f5f5;
.uni-list-chat {
font-size: $uni-font-size-lg;
position: relative;
flex-direction: column;
justify-content: space-between;
background-color: $background-color;
}
// .uni-list-chat--disabled {
// opacity: 0.3;
// }
.uni-list-chat--hover {
background-color: $hover;
}
.uni-list--border {
position: relative;
margin-left: $uni-spacing-row-lg;
/* #ifdef APP-PLUS */
border-top-color: $divide-line-color;
border-top-style: solid;
border-top-width: 0.5px;
/* #endif */
}
/* #ifndef APP-NVUE */
.uni-list--border:after {
position: absolute;
top: 0;
right: 0;
left: 0;
height: 1px;
content: '';
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
background-color: $divide-line-color;
}
.uni-list-item--first:after {
height: 0px;
}
/* #endif */
.uni-list-chat--first {
border-top-width: 0px;
}
.uni-ellipsis {
/* #ifndef APP-NVUE */
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
/* #endif */
/* #ifdef APP-NVUE */
lines: 1;
/* #endif */
}
.uni-ellipsis-2 {
/* #ifndef APP-NVUE */
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
/* #endif */
/* #ifdef APP-NVUE */
lines: 2;
/* #endif */
}
.uni-list-chat__container {
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
flex: 1;
padding: $uni-spacing-row-base $uni-spacing-row-lg;
position: relative;
overflow: hidden;
}
.uni-list-chat__header-warp {
position: relative;
}
.uni-list-chat__header {
/* #ifndef APP-NVUE */
display: flex;
align-content: center;
/* #endif */
flex-direction: row;
justify-content: center;
align-items: center;
flex-wrap: wrap-reverse;
/* #ifdef APP-NVUE */
width: 50px;
height: 50px;
/* #endif */
/* #ifndef APP-NVUE */
width: $avatar-width;
height: $avatar-width;
/* #endif */
border-radius: $avatar-border-radius;
border-color: $avatar-border-color;
border-width: $avatar-border-width;
border-style: solid;
overflow: hidden;
}
.uni-list-chat__header-box {
/* #ifndef APP-PLUS */
box-sizing: border-box;
display: flex;
width: $avatar-width;
height: $avatar-width;
/* #endif */
/* #ifdef APP-NVUE */
width: 50px;
height: 50px;
/* #endif */
overflow: hidden;
border-radius: 2px;
}
.uni-list-chat__header-image {
margin: 1px;
/* #ifdef APP-NVUE */
width: 50px;
height: 50px;
/* #endif */
/* #ifndef APP-NVUE */
width: $avatar-width;
height: $avatar-width;
/* #endif */
}
/* #ifndef APP-NVUE */
.uni-list-chat__header-image {
display: block;
width: 100%;
height: 100%;
}
.avatarItem--1 {
width: 100%;
height: 100%;
}
.avatarItem--2 {
width: 47%;
height: 47%;
}
.avatarItem--3 {
width: 32%;
height: 32%;
}
/* #endif */
.header--circle {
border-radius: 50%;
}
.uni-list-chat__content {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
flex: 1;
overflow: hidden;
padding: 2px 0;
}
.uni-list-chat__content-main {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: space-between;
padding-left: $uni-spacing-row-base;
flex: 1;
overflow: hidden;
}
.uni-list-chat__content-title {
font-size: $title-size;
color: $title-color;
font-weight: $title-weight;
overflow: hidden;
}
.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 */
flex-shrink: 0;
display: flex;
/* #endif */
flex-direction: column;
justify-content: space-between;
align-items: flex-end;
margin-left: 5px;
}
.uni-list-chat__content-extra-text {
color: $right-text-color;
font-size: $right-text-size;
font-weight: $right-text-weight;
overflow: hidden;
}
.uni-list-chat__badge-pos {
position: absolute;
/* #ifdef APP-NVUE */
left: 55px;
top: 3px;
/* #endif */
/* #ifndef APP-NVUE */
left: calc(#{$avatar-width} + 10px - #{$badge-space} + #{$badge-left});
top: calc(#{$uni-spacing-row-base}/ 2 + 1px + #{$badge-top});
/* #endif */
}
.uni-list-chat__badge {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
justify-content: center;
align-items: center;
border-radius: 100px;
background-color: $badge-background-color;
}
.uni-list-chat__badge-text {
color: $badge-color;
font-size: $badge-font;
}
.uni-badge--single {
/* #ifndef APP-NVUE */
// left: calc(#{$avatar-width} + 7px + #{$badge-left});
/* #endif */
width: $badge-size;
height: $badge-size;
}
.uni-badge--complex {
/* #ifdef APP-NVUE */
left: 50px;
/* #endif */
/* #ifndef APP-NVUE */
width: auto;
/* #endif */
height: $badge-size;
padding: 0 $badge-space;
}
.uni-badge--dot {
/* #ifdef APP-NVUE */
left: 60px;
top: 6px;
/* #endif */
/* #ifndef APP-NVUE */
left: calc(#{$avatar-width} + 15px - #{$dot-width}/ 2 + 1px + #{$badge-left});
/* #endif */
width: $dot-width;
height: $dot-height;
padding: 0;
}
.uni-list-chat--right {
/* #ifdef APP-NVUE */
left: 0;
/* #endif */
}
</style>

View File

@@ -0,0 +1,530 @@
<template>
<!-- #ifdef APP-NVUE -->
<cell :keep-scroll-position="keepScrollPosition">
<!-- #endif -->
<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'}"
:style="{paddingTop:padding.top,paddingLeft:padding.left,paddingRight:padding.right,paddingBottom:padding.bottom}">
<slot name="header">
<view class="uni-list-item__header">
<view v-if="thumb" class="uni-list-item__icon">
<image :src="thumb" class="uni-list-item__icon-img" :class="['uni-list--' + thumbSize]" />
</view>
<view v-else-if="showExtraIcon" class="uni-list-item__icon">
<uni-icons :color="extraIcon.color" :size="extraIcon.size" :type="extraIcon.type" />
</view>
</view>
</slot>
<slot name="body">
<view class="uni-list-item__content"
:class="{ 'uni-list-item__content--center': thumb || showExtraIcon || showBadge || showSwitch }">
<text v-if="title" class="uni-list-item__content-title"
:class="[ellipsis !== 0 && ellipsis <= 2 ? 'uni-ellipsis-' + ellipsis : '']">{{ title }}</text>
<text v-if="note" class="uni-list-item__content-note">{{ note }}</text>
</view>
</slot>
<slot name="footer">
<view v-if="rightText || showBadge || showSwitch" class="uni-list-item__extra"
:class="{ 'flex--justify': direction === 'column' }">
<text v-if="rightText" class="uni-list-item__extra-text">{{ rightText }}</text>
<uni-badge v-if="showBadge" :type="badgeType" :text="badgeText" :custom-style="badgeStyle" />
<switch v-if="showSwitch" :disabled="disabled" :checked="switchChecked"
@change="onSwitchChange" />
</view>
</slot>
</view>
<uni-icons v-if="showArrow || link" :size="16" class="uni-icon-wrapper" color="#bbb" type="arrowright" />
</view>
<!-- #ifdef APP-NVUE -->
</cell>
<!-- #endif -->
</template>
<script>
/**
* ListItem 列表子组件
* @description 列表子组件
* @tutorial https://ext.dcloud.net.cn/plugin?id=24
* @property {String} title 标题
* @property {String} note 描述
* @property {String} thumb 左侧缩略图若thumb有值则不会显示扩展图标
* @property {String} thumbSize = [lg|base|sm] 略缩图大小
* @value lg 大图
* @value base 一般
* @value sm 小图
* @property {String} badgeText 数字角标内容
* @property {String} badgeType 数字角标类型,参考[uni-icons](https://ext.dcloud.net.cn/plugin?id=21)
* @property {Object} badgeStyle 数字角标样式
* @property {String} rightText 右侧文字内容
* @property {Boolean} disabled = [true|false] 是否禁用
* @property {Boolean} clickable = [true|false] 是否开启点击反馈
* @property {String} link = [navigateTo|redirectTo|reLaunch|switchTab] 是否展示右侧箭头并开启点击反馈
* @value navigateTo 同 uni.navigateTo()
* @value redirectTo 同 uni.redirectTo()
* @value reLaunch 同 uni.reLaunch()
* @value switchTab 同 uni.switchTab()
* @property {String | PageURIString} to 跳转目标页面
* @property {Boolean} showBadge = [true|false] 是否显示数字角标
* @property {Boolean} showSwitch = [true|false] 是否显示Switch
* @property {Boolean} switchChecked = [true|false] Switch是否被选中
* @property {Boolean} showExtraIcon = [true|false] 左侧是否显示扩展图标
* @property {Object} extraIcon 扩展图标参数,格式为 {color: '#4cd964',size: '22',type: 'spinner'}
* @property {String} direction = [row|column] 排版方向
* @value row 水平排列
* @value column 垂直排列
* @event {Function} click 点击 uniListItem 触发事件
* @event {Function} switchChange 点击切换 Switch 时触发
*/
export default {
name: 'UniListItem',
emits: ['click', 'switchChange'],
props: {
direction: {
type: String,
default: 'row'
},
title: {
type: String,
default: ''
},
note: {
type: String,
default: ''
},
ellipsis: {
type: [Number, String],
default: 0
},
disabled: {
type: [Boolean, String],
default: false
},
clickable: {
type: Boolean,
default: false
},
showArrow: {
type: [Boolean, String],
default: false
},
link: {
type: [Boolean, String],
default: false
},
to: {
type: String,
default: ''
},
showBadge: {
type: [Boolean, String],
default: false
},
showSwitch: {
type: [Boolean, String],
default: false
},
switchChecked: {
type: [Boolean, String],
default: false
},
badgeText: {
type: String,
default: ''
},
badgeType: {
type: String,
default: 'success'
},
badgeStyle: {
type: Object,
default () {
return {}
}
},
rightText: {
type: String,
default: ''
},
thumb: {
type: String,
default: ''
},
thumbSize: {
type: String,
default: 'base'
},
showExtraIcon: {
type: [Boolean, String],
default: false
},
extraIcon: {
type: Object,
default () {
return {
type: '',
color: '#000000',
size: 20
};
}
},
border: {
type: Boolean,
default: true
},
customStyle: {
type: Object,
default () {
return {
padding: '',
backgroundColor: '#FFFFFF'
}
}
},
keepScrollPosition: {
type: Boolean,
default: false
}
},
watch: {
'customStyle.padding': {
handler(padding) {
if(typeof padding == 'number'){
padding += ''
}
let paddingArr = padding.split(' ')
if (paddingArr.length === 1) {
this.padding = {
"top": padding,
"right": padding,
"bottom": padding,
"left": padding
}
} else if (paddingArr.length === 2) {
this.padding = {
"top": padding[0],
"right": padding[1],
"bottom": padding[0],
"left": padding[1]
}
} else if (paddingArr.length === 4) {
this.padding = {
"top": padding[0],
"right": padding[1],
"bottom": padding[2],
"left": padding[3]
}
}
},
immediate: true
}
},
// inject: ['list'],
data() {
return {
isFirstChild: false,
padding: {
top: "",
right: "",
bottom: "",
left: ""
}
};
},
mounted() {
this.list = this.getForm()
// 判断是否存在 uni-list 组件
if (this.list) {
if (!this.list.firstChildAppend) {
this.list.firstChildAppend = true;
this.isFirstChild = true;
}
}
},
methods: {
/**
* 获取父元素实例
*/
getForm(name = 'uniList') {
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;
},
onClick() {
if (this.to !== '') {
this.openPage();
return;
}
if (this.clickable || this.link) {
this.$emit('click', {
data: {}
});
}
},
onSwitchChange(e) {
this.$emit('switchChange', e.detail);
},
openPage() {
if (['navigateTo', 'redirectTo', 'reLaunch', 'switchTab'].indexOf(this.link) !== -1) {
this.pageApi(this.link);
} else {
this.pageApi('navigateTo');
}
},
pageApi(api) {
let callback = {
url: this.to,
success: res => {
this.$emit('click', {
data: res
});
},
fail: err => {
this.$emit('click', {
data: err
});
}
}
switch (api) {
case 'navigateTo':
uni.navigateTo(callback)
break
case 'redirectTo':
uni.redirectTo(callback)
break
case 'reLaunch':
uni.reLaunch(callback)
break
case 'switchTab':
uni.switchTab(callback)
break
default:
uni.navigateTo(callback)
}
}
}
};
</script>
<style lang="scss">
$uni-font-size-sm:12px;
$uni-font-size-base:14px;
$uni-font-size-lg:16px;
$uni-spacing-col-lg: 12px;
$uni-spacing-row-lg: 15px;
$uni-img-size-sm:20px;
$uni-img-size-base:26px;
$uni-img-size-lg:40px;
$uni-border-color:#e5e5e5;
$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;
/* #endif */
font-size: $uni-font-size-lg;
position: relative;
justify-content: space-between;
align-items: center;
background-color: #fff;
flex-direction: row;
/* #ifdef H5 */
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 */
display: flex;
/* #endif */
flex-direction: row;
padding: $list-item-pd;
padding-left: $uni-spacing-row-lg;
flex: 1;
overflow: hidden;
// align-items: center;
}
.container--right {
padding-right: 0;
}
// .border--left {
// margin-left: $uni-spacing-row-lg;
// }
.uni-list--border {
position: absolute;
top: 0;
right: 0;
left: 0;
/* #ifdef APP-NVUE */
border-top-color: $uni-border-color;
border-top-style: solid;
border-top-width: 0.5px;
/* #endif */
}
/* #ifndef APP-NVUE */
.uni-list--border:after {
position: absolute;
top: 0;
right: 0;
left: 0;
height: 1px;
content: '';
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
background-color: $uni-border-color;
}
/* #endif */
.uni-list-item__content {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
padding-right: 8px;
flex: 1;
color: #3b4144;
// overflow: hidden;
flex-direction: column;
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 */
display: flex;
/* #endif */
flex-direction: row;
justify-content: flex-end;
align-items: center;
}
.uni-list-item__header {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
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;
/* #endif */
height: $uni-img-size-base;
width: $uni-img-size-base;
margin-right: 10px;
}
.uni-icon-wrapper {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
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;
white-space: nowrap;
text-overflow: ellipsis;
/* #endif */
/* #ifdef APP-NVUE */
lines: 1;
text-overflow: ellipsis;
/* #endif */
}
.uni-ellipsis-2 {
/* #ifndef APP-NVUE */
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
/* #endif */
/* #ifdef APP-NVUE */
lines: 2;
text-overflow: ellipsis;
/* #endif */
}
</style>

View File

@@ -0,0 +1,123 @@
<template>
<!-- #ifndef APP-NVUE -->
<view class="uni-list uni-border-top-bottom">
<view v-if="border" class="uni-list--border-top"></view>
<slot />
<view v-if="border" class="uni-list--border-bottom"></view>
</view>
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
<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 {
name: 'uniList',
'mp-weixin': {
options: {
multipleSlots: false
}
},
props: {
stackFromEnd:{
type: Boolean,
default:false
},
enableBackToTop: {
type: [Boolean, String],
default: false
},
scrollY: {
type: [Boolean, String],
default: false
},
border: {
type: Boolean,
default: true
},
renderReverse:{
type: Boolean,
default: false
}
},
// provide() {
// return {
// list: this
// };
// },
created() {
this.firstChildAppend = false;
},
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 {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
background-color: $uni-bg-color;
position: relative;
flex-direction: column;
}
.uni-list--border {
position: relative;
/* #ifdef APP-NVUE */
border-top-color: $uni-border-color;
border-top-style: solid;
border-top-width: 0.5px;
border-bottom-color: $uni-border-color;
border-bottom-style: solid;
border-bottom-width: 0.5px;
/* #endif */
z-index: -1;
}
/* #ifndef APP-NVUE */
.uni-list--border-top {
position: absolute;
top: 0;
right: 0;
left: 0;
height: 1px;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
background-color: $uni-border-color;
z-index: 1;
}
.uni-list--border-bottom {
position: absolute;
bottom: 0;
right: 0;
left: 0;
height: 1px;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
background-color: $uni-border-color;
}
/* #endif */
</style>

View File

@@ -0,0 +1,65 @@
<template>
<!-- #ifdef APP-NVUE -->
<refresh :display="display" @refresh="onrefresh" @pullingdown="onpullingdown">
<slot />
</refresh>
<!-- #endif -->
<!-- #ifndef APP-NVUE -->
<view ref="uni-refresh" class="uni-refresh" v-show="isShow">
<slot />
</view>
<!-- #endif -->
</template>
<script>
export default {
name: 'UniRefresh',
props: {
display: {
type: [String],
default: "hide"
}
},
data() {
return {
pulling: false
}
},
computed: {
isShow() {
if (this.display === "show" || this.pulling === true) {
return true;
}
return false;
}
},
created() {},
methods: {
onchange(value) {
this.pulling = value;
},
onrefresh(e) {
this.$emit("refresh", e);
},
onpullingdown(e) {
// #ifdef APP-NVUE
this.$emit("pullingdown", e);
// #endif
// #ifndef APP-NVUE
var detail = {
viewHeight: 90,
pullingDistance: e.height
}
this.$emit("pullingdown", detail);
// #endif
}
}
}
</script>
<style>
.uni-refresh {
height: 0;
overflow: hidden;
}
</style>

View File

@@ -0,0 +1,87 @@
var pullDown = {
threshold: 95,
maxHeight: 200,
callRefresh: 'onrefresh',
callPullingDown: 'onpullingdown',
refreshSelector: '.uni-refresh'
};
function ready(newValue, oldValue, ownerInstance, instance) {
var state = instance.getState()
state.canPullDown = newValue;
// console.log(newValue);
}
function touchStart(e, instance) {
var state = instance.getState();
state.refreshInstance = instance.selectComponent(pullDown.refreshSelector);
state.canPullDown = (state.refreshInstance != null && state.refreshInstance != undefined);
if (!state.canPullDown) {
return
}
// console.log("touchStart");
state.height = 0;
state.touchStartY = e.touches[0].pageY || e.changedTouches[0].pageY;
state.refreshInstance.setStyle({
'height': 0
});
state.refreshInstance.callMethod("onchange", true);
}
function touchMove(e, ownerInstance) {
var instance = e.instance;
var state = instance.getState();
if (!state.canPullDown) {
return
}
var oldHeight = state.height;
var endY = e.touches[0].pageY || e.changedTouches[0].pageY;
var height = endY - state.touchStartY;
if (height > pullDown.maxHeight) {
return;
}
var refreshInstance = state.refreshInstance;
refreshInstance.setStyle({
'height': height + 'px'
});
height = height < pullDown.maxHeight ? height : pullDown.maxHeight;
state.height = height;
refreshInstance.callMethod(pullDown.callPullingDown, {
height: height
});
}
function touchEnd(e, ownerInstance) {
var state = e.instance.getState();
if (!state.canPullDown) {
return
}
state.refreshInstance.callMethod("onchange", false);
var refreshInstance = state.refreshInstance;
if (state.height > pullDown.threshold) {
refreshInstance.callMethod(pullDown.callRefresh);
return;
}
refreshInstance.setStyle({
'height': 0
});
}
function propObserver(newValue, oldValue, instance) {
pullDown = newValue;
}
module.exports = {
touchmove: touchMove,
touchstart: touchStart,
touchend: touchEnd,
propObserver: propObserver
}

View File

@@ -0,0 +1,88 @@
{
"id": "uni-list",
"displayName": "uni-list 列表",
"version": "1.2.10",
"description": "List 组件 ,帮助使用者快速构建列表。",
"keywords": [
"",
"uni-ui",
"uniui",
"列表",
"",
"list"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": [
"uni-badge",
"uni-icons"
],
"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"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}

View File

@@ -0,0 +1,346 @@
## List 列表
> **组件名uni-list**
> 代码块: `uList`、`uListItem`
> 关联组件:`uni-list-item`、`uni-badge`、`uni-icons`、`uni-list-chat`、`uni-list-ad`
List 列表组件,包含基本列表样式、可扩展插槽机制、长列表性能优化、多端兼容。
在vue页面里它默认使用页面级滚动。在app-nvue页面里它默认使用原生list组件滚动。这样的长列表在滚动出屏幕外后系统会回收不可见区域的渲染内存资源不会造成滚动越长手机越卡的问题。
uni-list组件是父容器里面的核心是uni-list-item子组件它代表列表中的一个可重复行子组件可以无限循环。
uni-list-item有很多风格uni-list-item组件通过内置的属性满足一些常用的场景。当内置属性不满足需求时可以通过扩展插槽来自定义列表内容。
内置属性可以覆盖的场景包括:导航列表、设置列表、小图标列表、通信录列表、聊天记录列表。
涉及很多大图或丰富内容的列表,比如类今日头条的新闻列表、类淘宝的电商列表,需要通过扩展插槽实现。
下文均有样例给出。
uni-list不包含下拉刷新和上拉翻页。上拉翻页另见组件[uni-load-more](https://ext.dcloud.net.cn/plugin?id=29)
### 安装方式
本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`
如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55)
> **注意事项**
> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。
> - 组件需要依赖 `sass` 插件 ,请自行手动安装
> - 组件内部依赖 `'uni-icons'` 、`uni-badge` 组件
> - `uni-list` 和 `uni-list-item` 需要配套使用,暂不支持单独使用 `uni-list-item`
> - 只有开启点击反馈后,会有点击选中效果
> - 使用插槽时,可以完全自定义内容
> - note 、rightText 属性暂时没做限制,不支持文字溢出隐藏,使用时应该控制长度显示或通过默认插槽自行扩展
> - 支付宝小程序平台需要在支付宝小程序开发者工具里开启 component2 编译模式,开启方式: 详情 --> 项目配置 --> 启用 component2 编译
> - 如果需要修改 `switch`、`badge` 样式,请使用插槽自定义
> - 在 `HBuilderX` 低版本中,可能会出现组件显示 `undefined` 的问题,请升级最新的 `HBuilderX` 或者 `cli`
> - 如使用过程中有任何问题或者您对uni-ui有一些好的建议欢迎加入 uni-ui 交流群871950839
### 基本用法
- 设置 `title` 属性,可以显示列表标题
- 设置 `disabled` 属性,可以禁用当前项
```html
<uni-list>
<uni-list-item title="列表文字" ></uni-list-item>
<uni-list-item :disabled="true" title="列表禁用状态" ></uni-list-item>
</uni-list>
```
### 多行内容显示
- 设置 `note` 属性 ,可以在第二行显示描述文本信息
```html
<uni-list>
<uni-list-item title="列表文字" note="列表描述信息"></uni-list-item>
<uni-list-item :disabled="true" title="列表文字" note="列表禁用状态"></uni-list-item>
</uni-list>
```
### 右侧显示角标、switch
- 设置 `show-badge` 属性 ,可以显示角标内容
- 设置 `show-switch` 属性,可以显示 switch 开关
```html
<uni-list>
<uni-list-item title="列表右侧显示角标" :show-badge="true" badge-text="12" ></uni-list-item>
<uni-list-item title="列表右侧显示 switch" :show-switch="true" @switchChange="switchChange" ></uni-list-item>
</uni-list>
```
### 左侧显示略缩图、图标
- 设置 `thumb` 属性 ,可以在列表左侧显示略缩图
- 设置 `show-extra-icon` 属性,并指定 `extra-icon` 可以在左侧显示图标
```html
<uni-list>
<uni-list-item title="列表左侧带略缩图" note="列表描述信息" thumb="https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png"
thumb-size="lg" rightText="右侧文字"></uni-list-item>
<uni-list-item :show-extra-icon="true" :extra-icon="extraIcon1" title="列表左侧带扩展图标" ></uni-list-item>
</uni-list>
```
### 开启点击反馈和右侧箭头
- 设置 `clickable``true` ,则表示这是一个可点击的列表,会默认给一个点击效果,并可以监听 `click` 事件
- 设置 `link` 属性,会自动开启点击反馈,并给列表右侧添加一个箭头
- 设置 `to` 属性,可以跳转页面,`link` 的值表示跳转方式,如果不指定,默认为 `navigateTo`
```html
<uni-list>
<uni-list-item title="开启点击反馈" clickable @click="onClick" ></uni-list-item>
<uni-list-item title="默认 navigateTo 方式跳转页面" link to="/pages/vue/index/index" @click="onClick($event,1)" ></uni-list-item>
<uni-list-item title="reLaunch 方式跳转页面" link="reLaunch" to="/pages/vue/index/index" @click="onClick($event,1)" ></uni-list-item>
</uni-list>
```
### 聊天列表示例
- 设置 `clickable``true` ,则表示这是一个可点击的列表,会默认给一个点击效果,并可以监听 `click` 事件
- 设置 `link` 属性,会自动开启点击反馈,`link` 的值表示跳转方式,如果不指定,默认为 `navigateTo`
- 设置 `to` 属性,可以跳转页面
- `time` 属性,通常会设置成时间显示,但是这个属性不仅仅可以设置时间,你可以传入任何文本,注意文本长度可能会影响显示
- `avatar``avatarList` 属性同时只会有一个生效,同时设置的话,`avatarList` 属性的长度大于1 `avatar` 属性将失效
- 可以通过默认插槽自定义列表右侧内容
```html
<uni-list>
<uni-list :border="true">
<!-- 显示圆形头像 -->
<uni-list-chat :avatar-circle="true" title="uni-app" avatar="https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png" note="您收到一条新的消息" time="2020-02-02 20:20" ></uni-list-chat>
<!-- 右侧带角标 -->
<uni-list-chat title="uni-app" avatar="https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png" note="您收到一条新的消息" time="2020-02-02 20:20" badge-text="12" :badge-style="{backgroundColor:'#FF80AB'}"></uni-list-chat>
<!-- 头像显示圆点 -->
<uni-list-chat title="uni-app" avatar="https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png" note="您收到一条新的消息" time="2020-02-02 20:20" badge-positon="left" badge-text="dot"></uni-list-chat>
<!-- 头像显示角标 -->
<uni-list-chat title="uni-app" avatar="https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png" note="您收到一条新的消息" time="2020-02-02 20:20" badge-positon="left" badge-text="99"></uni-list-chat>
<!-- 显示多头像 -->
<uni-list-chat title="uni-app" :avatar-list="avatarList" note="您收到一条新的消息" time="2020-02-02 20:20" badge-positon="left" badge-text="dot"></uni-list-chat>
<!-- 自定义右侧内容 -->
<uni-list-chat title="uni-app" :avatar-list="avatarList" note="您收到一条新的消息" time="2020-02-02 20:20" badge-positon="left" badge-text="dot">
<view class="chat-custom-right">
<text class="chat-custom-text">刚刚</text>
<!-- 需要使用 uni-icons 请自行引入 -->
<uni-icons type="star-filled" color="#999" size="18"></uni-icons>
</view>
</uni-list-chat>
</uni-list>
</uni-list>
```
```javascript
export default {
components: {},
data() {
return {
avatarList: [{
url: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png'
}, {
url: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png'
}, {
url: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png'
}]
}
}
}
```
```css
.chat-custom-right {
flex: 1;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: space-between;
align-items: flex-end;
}
.chat-custom-text {
font-size: 12px;
color: #999;
}
```
## API
### List Props
属性名 |类型 |默认值 | 说明
:-: |:-: |:-: | :-:
border |Boolean |true | 是否显示边框
### ListItem Props
属性名 |类型 |默认值 | 说明
:-: |:-: |:-: | :-:
title |String |- | 标题
note |String |- | 描述
ellipsis |Number |0 | title 是否溢出隐藏可选值0:默认; 1:显示一行; 2:显示两行;【nvue 暂不支持】
thumb |String |- | 左侧缩略图若thumb有值则不会显示扩展图标
thumbSize |String |medium | 略缩图尺寸可选值lg:大图; medium:一般; sm:小图;
showBadge |Boolean |false | 是否显示数字角标
badgeText |String |- | 数字角标内容
badgeType |String |- | 数字角标类型,参考[uni-icons](https://ext.dcloud.net.cn/plugin?id=21)
badgeStyle |Object |- | 数字角标样式使用uni-badge的custom-style参数
rightText |String |- | 右侧文字内容
disabled |Boolean |false | 是否禁用
showArrow |Boolean |true | 是否显示箭头图标
link |String |navigateTo | 新页面跳转方式,可选值见下表
to |String |- | 新页面跳转地址如填写此属性click 会返回页面是否跳转成功
clickable |Boolean |false | 是否开启点击反馈
showSwitch |Boolean |false | 是否显示Switch
switchChecked |Boolean |false | Switch是否被选中
showExtraIcon |Boolean |false | 左侧是否显示扩展图标
extraIcon |Object |- | 扩展图标参数,格式为 ``{color: '#4cd964',size: '22',type: 'spinner'}``,参考 [uni-icons](https://ext.dcloud.net.cn/plugin?id=28)
direction | String |row | 排版方向可选值row:水平排列; column:垂直排列; 3个插槽是水平排还是垂直排也受此属性控制
#### Link Options
属性名 | 说明
:-: | :-:
navigateTo | 同 uni.navigateTo()
redirectTo | 同 uni.reLaunch()
reLaunch | 同 uni.reLaunch()
switchTab | 同 uni.switchTab()
### ListItem Events
事件称名 |说明 |返回参数
:-: |:-: |:-:
click |点击 uniListItem 触发事件,需开启点击反馈 |-
switchChange |点击切换 Switch 时触发,需显示 switch |e={value:checked}
### ListItem Slots
名称 | 说明
:-: | :-:
header | 左/上内容插槽,可完全自定义默认显示
body | 中间内容插槽,可完全自定义中间内容
footer | 右/下内容插槽,可完全自定义右侧内容
> **通过插槽扩展**
> 需要注意的是当使用插槽时,内置样式将会失效,只保留排版样式,此时的样式需要开发者自己实现
> 如果 `uni-list-item` 组件内置属性样式无法满足需求可以使用插槽来自定义uni-list-item里的内容。
> uni-list-item提供了3个可扩展的插槽`header`、`body`、`footer`
> - 当 `direction` 属性为 `row` 时表示水平排列,此时 `header` 表示列表的左边部分,`body` 表示列表的中间部分,`footer` 表示列表的右边部分
> - 当 `direction` 属性为 `column` 时表示垂直排列,此时 `header` 表示列表的上边部分,`body` 表示列表的中间部分,`footer` 表示列表的下边部分
> 开发者可以只用1个插槽也可以3个一起使用。在插槽中可自主编写view标签实现自己所需的效果。
**示例**
```html
<uni-list>
<uni-list-item title="自定义右侧插槽" note="列表描述信息" link>
<template slot="header">
<image class="slot-image" src="/static/logo.png" mode="widthFix"></image>
</template>
</uni-list-item>
<uni-list-item>
<!-- 自定义 header -->
<view slot="header" class="slot-box"><image class="slot-image" src="/static/logo.png" mode="widthFix"></image></view>
<!-- 自定义 body -->
<text slot="body" class="slot-box slot-text">自定义插槽</text>
<!-- 自定义 footer-->
<template slot="footer">
<image class="slot-image" src="/static/logo.png" mode="widthFix"></image>
</template>
</uni-list-item>
</uni-list>
```
### ListItemChat Props
属性名 |类型 |默认值 | 说明
:-: |:-: |:-: | :-:
title |String |- | 标题
note |String |- | 描述
clickable |Boolean |false | 是否开启点击反馈
badgeText |String |- | 数字角标内容,设置为 `dot` 将显示圆点
badgePositon |String |right | 角标位置
link |String |navigateTo | 是否展示右侧箭头并开启点击反馈,可选值见下表
clickable |Boolean |false | 是否开启点击反馈
to |String |- | 跳转页面地址如填写此属性click 会返回页面是否跳转成功
time |String |- | 右侧时间显示
avatarCircle |Boolean |false | 是否显示圆形头像
avatar |String |- | 头像地址avatarCircle 不填时生效
avatarList |Array |- | 头像组,格式为 [{url:''}]
#### Link Options
属性名 | 说明
:-: | :-:
navigateTo | 同 uni.navigateTo()
redirectTo | 同 uni.reLaunch()
reLaunch | 同 uni.reLaunch()
switchTab | 同 uni.switchTab()
### ListItemChat Slots
名称 | 说明
:- | :-
default | 自定义列表右侧内容(包括时间和角标显示)
### ListItemChat Events
事件称名 | 说明 | 返回参数
:-: | :-: | :-:
@click | 点击 uniListChat 触发事件 | {data:{}} ,如有 to 属性,会返回页面跳转信息
## 基于uni-list扩展的页面模板
通过扩展插槽,可实现多种常见样式的列表
**新闻列表类**
1. 云端一体混合布局:[https://ext.dcloud.net.cn/plugin?id=2546](https://ext.dcloud.net.cn/plugin?id=2546)
2. 云端一体垂直布局,大图模式:[https://ext.dcloud.net.cn/plugin?id=2583](https://ext.dcloud.net.cn/plugin?id=2583)
3. 云端一体垂直布局,多行图文混排:[https://ext.dcloud.net.cn/plugin?id=2584](https://ext.dcloud.net.cn/plugin?id=2584)
4. 云端一体垂直布局,多图模式:[https://ext.dcloud.net.cn/plugin?id=2585](https://ext.dcloud.net.cn/plugin?id=2585)
5. 云端一体水平布局,左图右文:[https://ext.dcloud.net.cn/plugin?id=2586](https://ext.dcloud.net.cn/plugin?id=2586)
6. 云端一体水平布局,左文右图:[https://ext.dcloud.net.cn/plugin?id=2587](https://ext.dcloud.net.cn/plugin?id=2587)
7. 云端一体垂直布局,无图模式,主标题+副标题:[https://ext.dcloud.net.cn/plugin?id=2588](https://ext.dcloud.net.cn/plugin?id=2588)
**商品列表类**
1. 云端一体列表/宫格视图互切:[https://ext.dcloud.net.cn/plugin?id=2651](https://ext.dcloud.net.cn/plugin?id=2651)
2. 云端一体列表(宫格模式):[https://ext.dcloud.net.cn/plugin?id=2671](https://ext.dcloud.net.cn/plugin?id=2671)
3. 云端一体列表(列表模式):[https://ext.dcloud.net.cn/plugin?id=2672](https://ext.dcloud.net.cn/plugin?id=2672)
## 组件示例
点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/list/list](https://hellouniapp.dcloud.net.cn/pages/extUI/list/list)

View File

@@ -0,0 +1,50 @@
## 2.0.22022-12-12
- 【修复】`createOrder`接口传了`other`参数后可能会报`snake2camelJson is not function`的问题。
## 2.0.12022-12-12
- 【优化】补全package.json内的uni_modules依赖
## 2.0.02022-12-05
- 【重要】uni-pay 2.0从公共模块升级为包含前端页面、uni-pay-co云对象让支付更加简单省心 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-pay.html)
## 1.1.12022-09-22
- 修复 微信支付V3提示 appCert 不存在的bug
- 新增 微信支付V3可以通过证书字符串方式导入证书
## 1.1.02022-09-22
- 新增 微信支付V3接口 [详情](https://uniapp.dcloud.io/uniCloud/unipay?id=微信支付v3)
## 1.0.292022-06-14
- 修复app平台PLATFORM变更引起的支付报错的Bug
## 1.0.282022-01-10
- 支付宝下单接口返回详细错误信息
- 优化发行包体积
## 1.0.272021-11-02
- 新增 苹果应用内购买凭证校验接口 [详情](https://uniapp.dcloud.io/uniCloud/unipay?id=verifyreceipt)
## 1.0.262021-11-01
- 新增 苹果内购凭证校验接口
## 1.0.252021-10-18
- 修复微信子商户id参数错误的Bug
## 1.0.242021-09-23
- 新增 微信外部浏览器支付H5支付
## 1.0.232021-09-22
- 修复微信支付部分值被转化为NaN导致无法直接入库的错误
## 1.0.222021-08-26
- 修复 支付宝用户未支付状态下查询订单状态orderQuery报错的Bug
## 1.0.212021-08-19
- 修复1.0.18版本引出的微信退款通知验签失败的bug
## 1.0.202021-08-04
- 修复1.0.19版本引出的微信支付签名错误问题
## 1.0.192021-08-03
- 修复timeStamp大小写导致的微信公众号支付失败
## 1.0.182021-07-16
- 通知类型不匹配时返回校验未通过
## 1.0.172021-07-16
- 新增 支付宝退款通知回调 [详情](https://uniapp.dcloud.io/uniCloud/unipay?id=verify-refund-notify)
- 新增 判断通知类型接口 [详情](https://uniapp.dcloud.io/uniCloud/unipay?id=check-notify-type)
## 1.0.162021-07-14
- 修复APP微信支付报签名错误的Bug
## 1.0.152021-07-13
- 修复1.0.14版本引出的微信支付使用pfx时报错的Bug
## 1.0.142021-07-12
- 支持使用微信子商户号,[详情](https://uniapp.dcloud.net.cn/uniCloud/unipay?id=init),感谢[studytime](https://gitee.com/studytime)
- 修复支付宝支付传入encode后的passbackParams参数导致验签无法通过的Bug
## 1.0.132021-03-25
- 修复 微信退款通知解析报错的Bug
## 1.0.122021-02-03
- 调整为uni_modules目录规范

View File

@@ -0,0 +1,829 @@
<template>
<view class="uni-pay" >
<!-- PC版收银台弹窗开始 -->
<uni-popup v-if="modeCom === 'pc'" ref="payPopup" type="center" :safe-area="false">
<view class="pc-pay-popup">
<view class="pc-pay-popup-title">收银台</view>
<view class="pc-pay-popup-flex">
<view class="pc-pay-popup-qrcode-box">
<image class="pc-pay-popup-qrcode-image" :src="res.qr_code_image"></image>
<view class="pc-pay-popup-amount-box">
<view class="pc-pay-popup-amount-tips">扫一扫付款</view>
<view class="pc-pay-popup-amount">{{ (options.total_fee / 100).toFixed(2) }}</view>
</view>
<view class="pc-pay-popup-complete-button" v-if="res.qr_code_image">
<button type="primary" @click="_getOrder()">我已完成支付</button>
</view>
</view>
<view class="pc-pay-popup-provider-list">
<view class="pc-pay-popup-provider-item" v-if="currentProviders.indexOf('wxpay') > -1" :class="options.provider == 'wxpay' ? 'active' : ''" @click="_pcChooseProvider('wxpay')">
<image :src="images.wxpay" class="pc-pay-popup-provider-image"></image>
<text class="pc-pay-popup-provider-text">微信支付</text>
</view>
<view class="pc-pay-popup-provider-item" v-if="currentProviders.indexOf('alipay') > -1" :class="options.provider == 'alipay' ? 'active' : ''" @click="_pcChooseProvider('alipay')">
<image :src="images.alipay" class="pc-pay-popup-provider-image"></image>
<text class="pc-pay-popup-provider-text">支付宝支付</text>
</view>
<view class="pc-pay-popup-logo">
<image :src="logo" mode="widthFix"></image>
</view>
</view>
</view>
</view>
</uni-popup>
<!-- PC版收银台弹窗结束 -->
<!-- 手机版收银台弹窗开始 -->
<uni-popup v-else ref="payPopup" type="bottom" :safe-area="false">
<view class="mobile-pay-popup" :style="'min-height: '+height+';'">
<view class="mobile-pay-popup-title">收银台</view>
<view class="mobile-pay-popup-amount-box">
<view>待支付金额</view>
<view class="mobile-pay-popup-amount">{{ (options.total_fee / 100).toFixed(2) }}</view>
</view>
<view class="mobile-pay-popup-provider-list">
<uni-list>
<!-- #ifdef MP-WEIXIN || H5 || APP -->
<uni-list-item v-if="currentProviders.indexOf('wxpay') > -1" :thumb="images.wxpay" title="微信支付" @click="createOrder({ provider: 'wxpay' })" clickable link></uni-list-item>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY || H5 || APP -->
<uni-list-item v-if="currentProviders.indexOf('alipay') > -1" :thumb="images.alipay" title="支付宝" @click="createOrder({ provider: 'alipay' })" clickable link></uni-list-item>
<!-- #endif -->
</uni-list>
</view>
</view>
</uni-popup>
<!-- 手机版收银台弹窗结束 -->
<!-- 二维码支付弹窗开始 -->
<uni-popup ref="qrcodePopup" type="center" :safe-area="false" :animation="false" :mask-click="false" @close="clearQrcode">
<view class="qrcode-popup-content">
<image :src="res.qr_code_image" class="qrcode-image"></image>
<view class="qrcode-popup-info">
<view>
<text class="qrcode-popup-info-fee">{{ (options.total_fee / 100).toFixed(2) }}</text>
<text></text>
</view>
<view v-if="options.provider == 'wxpay'">请用微信扫码支付</view>
<view v-else-if="options.provider == 'alipay'">请用支付宝扫码支付</view>
</view>
<button type="primary" @click="_getOrder()">我已完成支付</button>
<view class="qrcode-popup-cancel" @click="clearQrcodePopup">暂不支付</view>
</view>
</uni-popup>
<!-- 二维码支付弹窗结束 -->
<!-- 外部浏览器确认支付弹窗开始 -->
<uni-popup ref="payConfirmPopup" type="center" :safe-area="false" :animation="false" :mask-click="false">
<view class="pay-confirm-popup-content">
<view class="pay-confirm-popup-title">请确认支付是否已完成</view>
<view><button type="primary" @click="_getOrder()">已完成支付</button></view>
<view class="pay-confirm-popup-refresh"><button type="default" @click="_afreshPayment()">支付遇到问题重新支付</button></view>
<view class="pay-confirm-popup-cancel" @click="clearPayConfirmPopup">暂不支付</view>
</view>
</uni-popup>
<!-- 外部浏览器确认支付弹窗结束 -->
</view>
</template>
<script>
// 引入支付云对象
const uniPayCo = uniCloud.importObject("uni-pay-co");
import jsSdk from "../../js_sdk/js_sdk.js"
var myOpenid; // 将openid临时缓存避免重复获取openid
// #ifdef APP
import appleiapSdk from "../../js_sdk/appleiap.js"
// #endif
export default {
name: "uni-pay",
emits: ["success", "cancel", "fail", "create", "mounted"],
props: {
/**
* Banner广告位id
*/
adpid: {
Type: String,
default: ""
},
/**
* 是否自动跳转到插件内置的支付成功页面具有看广告功能可以增加开发者收益默认true
*/
toSuccessPage:{
Type: Boolean,
default: true
},
/**
* 支付成功后,点击查看订单按钮时跳转的页面地址
*/
returnUrl:{
Type: String,
default: ""
},
/**
* 支付结果页主色调,默认支付宝小程序为#108ee9其他端均为#01be6e
* 建议:绿色系 #01be6e 蓝色系 #108ee9 咖啡色 #816a4e 粉红 #fe4070 橙黄 #ffac0c 橘黄 #ff7100
*/
mainColor:{
Type: String,
default: ""
},
/**
* 收银台模式
* mobile 手机版
* pc 电脑版
*/
mode:{
Type: String,
default: ""
},
/**
* PC收银台模式时展示的logo
*/
logo:{
Type: String,
default: "/static/logo.png"
},
/**
* 收银台高度默认70vh
*/
height: {
Type: [String],
default: "70vh"
},
/**
* 是否打印运行过程日志
*/
debug: {
Type: Boolean,
default: false
}
},
data() {
return {
// 支付参数
options: {},
// 支付云对象返回结果
res: {},
images: {
wxpay: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAABC9JREFUeF7tWk1a20AMlUzv0bDr13AAYAOcpLCBcoqQU1DYEE6C2QAHIP26q3sPPOqniU2cZMYj+SeGxN5kEXlm9ObpjaQxwpY/uOX+Qw9Az4AtR6APgS0nQC+CfQi0FQLfrvcHXwAGPP4bQMK/fy5f7O9HehphwPfb/dOIogEhHQHBcamDCDESPoIxMQPTNSi1ABj+OrwDpNMaO5og4P2bMZOugFADwNTewWhU0/FVzAgnKZnxuoFQAbB3vX9MET7U2PHgq4R09vv8ZRI0bMhADMDw9uAhGN8NLQrWyAYRAGt1PgcRIU5TOms7JIIAdOL8nElJauikTRBKAdi7ObwioFFTzHaMw3mBzRV8DwKOXy+ertpagxcAq/YR/g2d6TlNrUDu4EiiE0Why4T1rgyINoXRC4DgjE+mF8+7RYAkp4RrRyVztRUKTgCkuz89fz4pAiB5z7WbklBrKxScAEgWxI6joZPXy5c4B0H0nkPdhzcHFIxxhHgZ8OA7AgMnAMObA479UnF6H5twQpF5RBMdibPDvB4AAAL6IZ0rNbTb9IngAyC8IwJ0K5okQBgzqFEKSV4wcXg17bxl8fIiJXFc0bHAgYLjYlHEFaZlVUQDoAIbcVZaN1VRrgAgUfImASiKW6Yh4pAohmHVQqpLABI0dMYiKhJPCeoV0ueuQsDmEJrkSeJ/bqNJnOqfApqVzWznzrdYWkvzhnUDYGnPKLTdV5gpfLiOqJUIaTefF8RKH6wxtAOX2IdA8NcCmmRItmBLfVF5jRBnR58kGQtWlGUJlBeAxpQ5A4eFKTu/ufLzPQv1f2mRRDiZ/nyyYwYrypI0OlQOc/9PsgshDsh2v+BUwTFnD3K5DglVlD4WlDZEsqywNgiK2F9gQBkLi7EtyV59WhBsiTURCjy5QZMgYRn9cxbZWgCQ+IKlnH2sFQYTURHmCYMgAJaKs9aYPkXNXGK6QhQdt9xeC4UhTC+eV/wVASASmrKj6IMA4NIBMQDsX1VN4IlbuU0K7vmiQS0G5EOpmiW6I1Dpjtp8pYc5yxYVj0RtXcMJcwDFSiqYLh2x+QgqAJwnAuEEydxbkZtdj+fKPVfwbPIq7KngqvMVX4WoAmDBAcH9HTMmXw23s0LJSlPOOsZx0l8VAu/0Fzjuc2Td3aY5zf1VoZgBvPgmvuhoIrFSMSXQThcDoJo0YLxGLfBSv5IINgVC1XxCOb/oZrkTBtRJqkQgKG6ROgPgPbGq/6HVIiYK51WngAj5ikbBhoZi3FALbHmozhlQXFChTc75g6wRM2ufzb9N/IwMcG0wg8HZJf9HBF/tFZnBBBH+cW/BpBDnd4XLDNJcon4oBiiY7jS194mEI0IaSz+12ygAclSYEcXvFsqA3UgANEzqAdCgtYm2PQM2cVc1PvUM0KC1ibY9AzZxVzU+bT0D/gPs/oxfcUEcJAAAAABJRU5ErkJggg==",
alipay: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAA2FJREFUeF7tmU122jAQx2cMB2hp9yULeK+naHISwhJyiIRDBJaQkwRO0fdgEWff0h4ANH0yFc+m+hhbckKNvJUsaX76z4ckhAv/8MLthwggKuDCCUQXuHABxCAYXSC6wIUTiC7wFgL4MPveTaj9optrO+696ya8yeQRQFRAdIEYAxoVBD/PNtdCwHWpDIJwr+1PMCk1DgAkCSx/jHrLsv/p+lfKAp3HzQOYDAqxKtcYBJPtXe/B1Y3TXgmATGst0WIrgAC7JmBINOQsNN8HE0zfVQFlFxzrgFgHNLQOkNJuQ7vrcgkS1CXEua5fgnDj+l+172CX/h59Tbn9Of0qBUE1cGe2ngPhLWeiEH0krFDBT63HC8Cnx/VtFuFDfgl90UOldDvuX4WcSo7lBSD0YuR4H6ebZwRNkRUw9xdSah1G+IzZmW5IW7ERDX/e9Rc+YwerBEMvQo1nrhfqkf/ZuYCxxK5J/t4AjkFQBi71CXxFoFSWq2XTlkn+AndXodNf5SwgT4J7gnttoNL6BqUEmCZET/tkvzQZYj5g1Sf/0goIkfcJYIkEK5HsFnkYnen6BXQptUb5lwJgMz4zCihXD/BqAwVDuoy+Uqx399kACrkZaYECVjY5qxJZXpoQwrcDHB6UghfVvPssAMo35W4R7oZVg5EMmAJxUCZ2CNzfVJ2Pm6qdleDBNwFClaHZdRrQAEhWe25VSPCuAMo1tnQhdIzMNUixfDYBqAOGVQEKQB15OFMCwXPV3QsFww7g73E39Pudr/Gn0EyplQPXCkBF/5AKsBtPKRA+AdKAEx/0BhYLL9nHFkhZLiBvbkOcxFzG5wPtoe7gBUrrTiMttqO+8ebZCkAtWErs17jHvrrSLcj+lkCpKeV5g/ABIA05lqgVM4Er2nPhZgev7DHGnToLG+ALIC9budgWwoRzyuMUPlzj8waVBuELIFOB5iksi7xIKQh8PS4wu8/j+a3vBScbRAgABVfg5BZbH6SFgP0kVIl7UCjNja4RCkAGwecaPLDhp4yNsSYkADlp/mncdNLLu8fpud9XQK7//wERGoBrAefSfgRBsLI9pTtPg+diUNV1yLuJypVg1Un/p/8arwDXZkQALkJNb48KaPoOu+yLCnARanp7VEDTd9hlX1SAi1DT2/8AaakVXysj5qkAAAAASUVORK5CYII="
},
originalRroviders: ["wxpay","alipay"],
currentProviders: ["wxpay","alipay"],
}
},
async mounted() {
let code;
let res;
if (!myOpenid) {
// #ifdef MP-WEIXIN
code = await this.getCode();
res = await this.getOpenid({
provider: "wxpay",
code
});
if (res) myOpenid = res.openid;
// #endif
// #ifdef MP-ALIPAY
code = await this.getCode();
res = await this.getOpenid({
provider: "alipay",
code
});
if (res) myOpenid = res.openid;
// #endif
}
// #ifndef MP
// 如果不是小程序,则请求云端获取支持的支付方式
let getPayProviderFromCloudRes = await this.getPayProviderFromCloud();
if (getPayProviderFromCloudRes.errCode === 0) {
this.originalRroviders = getPayProviderFromCloudRes.provider;
this.currentProviders = JSON.parse(JSON.stringify(this.originalRroviders));
}
// #endif
// #ifdef MP-WEIXIN
// 如果是微信小程序,则设置只支持微信支付
this.originalRroviders = ["wxpay"];
this.currentProviders = JSON.parse(JSON.stringify(this.originalRroviders));
// #endif
// #ifdef MP-ALIPAY
// 如果是支付宝小程序,则设置只支持支付宝支付
this.originalRroviders = ["alipay"];
this.currentProviders = JSON.parse(JSON.stringify(this.originalRroviders));
// #endif
this.$emit("mounted", {
images: this.images,
originalRroviders: this.originalRroviders,
currentProviders: this.currentProviders,
// #ifdef APP
appleiapSdk: appleiapSdk,
// #endif
});
},
methods: {
// 发起支付 - 打开支付选项弹窗
async open(options = {}) {
if (options.provider) {
let providers = [];
this.originalRroviders.map((item, index) => {
if (options.provider.indexOf(item) > -1) {
providers.push(item);
}
});
this.currentProviders = providers;
delete options.provider;
} else {
this.currentProviders = JSON.parse(JSON.stringify(this.originalRroviders));
}
this.options = options;
if (this.currentProviders.length === 1) {
this.createOrder({ provider: this.currentProviders[0] });
} else {
if (this.modeCom === "pc") {
await this._pcChooseProvider(this.currentProviders[0]);
}
this.$refs.payPopup.open();
}
},
// 创建支付
async createOrder(data = {}) {
let { options } = this;
Object.assign(options, data);
if (options.provider === "appleiap") {
// ios内购走特殊逻辑
return this._appleiapCreateOrder(options);
}
// #ifdef H5
// 判断如果是pc访问则强制扫码模式
if (jsSdk.checkPlatform() === "pc") {
options.qr_code = true;
}
// #endif
let createOrderData = {
provider: options.provider,
total_fee: options.total_fee,
openid: myOpenid,
order_no: options.order_no || this.res.order_no,
out_trade_no: options.out_trade_no || this.res.out_trade_no,
description: options.description,
type: options.type,
qr_code: options.qr_code,
custom: options.custom,
other: options.other,
};
if (myOpenid) {
createOrderData.openid = myOpenid;
}
// #ifdef H5
if (options.openid && options.provider === "wxpay") createOrderData.openid = options.openid;
// #endif
let res = await uniPayCo.createOrder(createOrderData);
if (res.errCode === 0) {
this.$emit("create", res);
this.res = res;
if (res.qr_code) {
if (!options.cancel_popup) {
// 展示组件自带的二维码弹窗
if (this.modeCom === "pc") {
this.$refs.payPopup.open();
this._pcChooseProvider(options.provider);
} else {
this.$refs.qrcodePopup.open();
}
}
} else if (res.order) {
// #ifdef H5
if (res.provider_pay_type === "jsapi") {
// 微信公众号支付
WeixinJSBridge.invoke("getBrandWCPayRequest", res.order, (res) => {
if (res.err_msg == "get_brand_wcpay_request:ok") {
// 用户支付成功回调
this._getOrder();
} else if (res.err_msg == "get_brand_wcpay_request:cancel") {
// 用户取消支付回调
this.$emit("cancel", res);
} else if (res.err_msg == "get_brand_wcpay_request:fail") {
// 用户支付失败回调
console.error('getBrandWCPayRequest-fail: ', res);
this.$emit("fail", res);
}
});
} else {
// 外部浏览器支付
let codeUrl = res.order.codeUrl;
let mwebUrl = res.order.mwebUrl || res.order.mweb_url;
setTimeout(() => {
this.$refs.payConfirmPopup.open();
window.location.href = codeUrl || mwebUrl;
}, 200);
}
// #endif
// #ifndef H5
uni.requestPayment({
// #ifdef APP-PLUS
provider: res.provider, // App端此参数必填可以通过uni.getProvider获取
// #endif
// #ifdef MP-WEIXIN
...res.order,
// #endif
// #ifdef APP-PLUS || MP-ALIPAY
orderInfo: res.order,
// #endif
...res.order,
success:(res)=>{
this._getOrder();
},
fail:(err)=>{
if (err.errMsg.indexOf("fail cancel") == -1) {
// 发起支付失败
console.error("uni.requestPayment:fail", err);
this.$emit("fail", err);
} else {
// 用户取消支付
this.$emit("cancel", err);
}
}
});
// #endif
}
}
},
// 查询订单(查询支付情况)
async getOrder(data = {}) {
try {
let res = await uniPayCo.getOrder(data);
if (typeof data.success === "function") data.success(res);
return res;
} catch (err) {
if (typeof data.fail === "function") data.fail(err);
}
},
// 发起退款此接口需要admin角色才可以访问
async refund(data = {}) {
try {
let res = await uniPayCo.refund(data);
if (typeof data.success === "function") data.success(res);
return res;
} catch (err) {
if (typeof data.fail === "function") data.fail(err);
}
},
// 查询退款(查询退款情况)
async getRefund(data = {}) {
try {
let res = await uniPayCo.getRefund(data);
if (typeof data.success === "function") data.success(res);
return res;
} catch (err) {
if (typeof data.fail === "function") data.fail(err);
}
},
// 关闭订单
async closeOrder(data = {}) {
try {
let res = await uniPayCo.closeOrder(data);
if (typeof data.success === "function") data.success(res);
return res;
} catch (err) {
if (typeof data.fail === "function") data.fail(err);
}
},
// 获取支持的支付供应商
async getPayProviderFromCloud(data = {}) {
try {
let res = await uniPayCo.getPayProviderFromCloud(data);
if (typeof data.success === "function") data.success(res);
return res;
} catch (err) {
if (typeof data.fail === "function") data.fail(err);
}
},
// 获取支付配置内的appid主要用于获取获取微信公众号的appid用以获取code
async getProviderAppId(data = {}) {
try {
let res = await uniPayCo.getProviderAppId(data);
if (typeof data.success === "function") data.success(res);
return res;
} catch (err) {
if (typeof data.fail === "function") data.fail(err);
}
},
// 根据code获取openid
async getOpenid(data = {}) {
try {
let res = await uniPayCo.getOpenid(data);
if (typeof data.success === "function") data.success(res);
return res;
} catch (err) {
if (typeof data.fail === "function") data.fail(err);
}
},
// 验证iosIap苹果内购支付凭据
async verifyReceiptFromAppleiap(data = {}) {
try {
let res = await uniPayCo.verifyReceiptFromAppleiap(data);
if (typeof data.success === "function") data.success(res);
return res;
} catch (err) {
if (typeof data.fail === "function") data.fail(err);
}
},
// 获取code
async getCode() {
// #ifdef MP-WEIXIN
return jsSdk.getWeixinCode();
// #endif
// #ifdef MP-ALIPAY
return jsSdk.getAlipayCode();
// #endif
},
// 支付成功后的逻辑
paySuccess(res={}) {
this.$refs.payPopup.close();
this.$refs.payConfirmPopup.close();
this.clearQrcode();
if (this.toSuccessPage){
// 跳转到支付成功的内置页面
this.pageToSuccess(res);
}
this.$emit("success", res);
},
pageToSuccess(res){
if (this.modeCom !== "pc") {
uni.navigateTo({
url:`/uni_modules/uni-pay/pages/success/success?out_trade_no=${res.out_trade_no}&order_no=${res.pay_order.order_no}&pay_date=${res.pay_order.pay_date}&total_fee=${res.pay_order.total_fee}&adpid=${this.adpid}&return_url=${this.returnUrl}&main_color=${this.mainColor}`
});
} else {
if (this.returnUrl) {
let url = this.returnUrl + `?out_trade_no=${res.out_trade_no}&order_no=${res.pay_order.order_no}`;
if (url.indexOf("/") !== 0) url = `/${url}`;
uni.navigateTo({
url,
});
}
}
},
// 监听 - 关闭二维码弹窗
clearQrcode() {
this.res.codeUrl = "";
this.res.qr_code_image = "";
},
// 内部函数查询支付状态
async _getOrder() {
this.getOrder({
out_trade_no: this.res.out_trade_no,
await_notify: true,
success: (res) => {
if (res.has_paid) {
this.$refs.qrcodePopup.close();
this.paySuccess(res);
}
}
});
},
// 关闭二维码支付弹窗
clearQrcodePopup(){
this.$refs.qrcodePopup.close();
},
// 重新发起支付
_afreshPayment(){
this.createOrder();
},
// 关闭确认弹出
clearPayConfirmPopup(){
this.$refs.payConfirmPopup.close();
},
// pc版弹窗选择支付方式
_pcChooseProvider(provider){
if (provider === this.options.provider) {
return;
}
return this.createOrder({ provider: provider })
},
// ios内购支付逻辑
async _appleiapCreateOrder(options){
// 初始化ios内购商品
let appleiap = new appleiapSdk.Iap({
// products为苹果开发者后台的商品id数组
products: [options.productid]
});
uni.showLoading({
title: '加载中...'
});
// 初始化获取iap支付通道
await appleiap.init();
// 从苹果服务器获取产品列表
let productList = await appleiap.getProduct();
let productInfo = productList[0];
options.total_fee = productInfo.price * 100;
options.description = productInfo.description;
let createOrderData = {
provider: options.provider,
total_fee: options.total_fee,
order_no: options.order_no || this.res.order_no,
out_trade_no: options.out_trade_no || this.res.out_trade_no,
description: options.description,
type: options.type,
custom: options.custom,
};
let res = await uniPayCo.createOrder(createOrderData);
if (res.errCode === 0) {
this.$emit("create", res);
this.res = res;
uni.showLoading({
title: '支付请求中...'
});
try {
// 请求苹果支付
if (this.debug) console.log("正在请求苹果服务器", options.productid, res.out_trade_no);
let requestPaymentRes = await appleiap.requestPayment({
productid: options.productid,
username: res.out_trade_no
});
if (this.debug) console.log('用户支付成功', requestPaymentRes);
uni.showLoading({
title: '正在处理支付结果...'
});
// 云端请求苹果服务器验证票据
let verifyRes = await this.verifyReceiptFromAppleiap({
out_trade_no: requestPaymentRes.payment.username,
transaction_receipt: requestPaymentRes.transactionReceipt,
transaction_identifier: requestPaymentRes.transactionIdentifier
});
if (verifyRes.errCode === 0) {
// 完结订单
await appleiap.finishTransaction(requestPaymentRes);
uni.hideLoading();
this.paySuccess(verifyRes);
}
} catch (err) {
let code = err.errCode || err.code;
if (code === 2) {
// 用户取消支付
if (this.debug) console.log("用户取消支付");
this.$emit("cancel", err);
} else {
// 发起支付失败
console.error("appleiapCreateOrder:fail", err);
this.$emit("fail", err);
}
uni.hideLoading();
}
}
},
// ios内购支付漏单重试
async appleiapRestore(){
uni.showLoading({
title: '检测支付环境...'
});
// 初始化
let appleiap = new appleiapSdk.Iap();
// 初始化获取iap支付通道
await appleiap.init();
try {
if (this.debug) console.log("正在查询是否有漏单信息");
const transactions = await appleiap.restoreCompletedTransactions({
username: ""
});
if (this.debug) console.log('漏单查询结果:' + (transactions.length === 0 ? '未漏单' : "有漏单"), transactions);
if (!transactions.length) {
return;
}
// 开发者业务逻辑,从服务器获取当前用户未完成的订单列表,和本地的比较
for (let i = 0; i < transactions.length; i++) {
let requestPaymentRes = transactions[i];
switch (transaction.transactionState) {
case appleiapSdk.IapTransactionState.purchased:
// 云端请求苹果服务器验证票据
let verifyRes = await this.verifyReceiptFromAppleiap({
out_trade_no: requestPaymentRes.payment.username,
transaction_receipt: requestPaymentRes.transactionReceipt,
transaction_identifier: requestPaymentRes.transactionIdentifier
});
if (verifyRes.errCode === 0) {
// 完结订单
await appleiap.finishTransaction(requestPaymentRes);
}
break;
case appleiapSdk.IapTransactionState.failed:
// 关闭未支付的订单
await appleiap.finishTransaction(requestPaymentRes);
break;
default:
break;
}
}
} catch (e) {
console.error(e)
} finally {
uni.hideLoading();
}
}
},
watch: {
},
computed: {
modeCom(){
if (this.mode) return this.mode;
let systemInfo = uni.getSystemInfoSync();
return systemInfo && systemInfo.deviceType === "pc" ? "pc" : "mobile";
}
},
}
</script>
<style lang="scss" scoped>
.uni-pay {
--bgcolor: #f3f3f3;
}
/* 手机版收银台弹窗开始 */
.mobile-pay-popup {
min-height: 70vh;
background-color: var(--bgcolor);
border-radius: 30rpx 30rpx 0 0;
overflow: hidden;
.mobile-pay-popup-title {
background-color: #ffffff;
text-align: center;
font-weight: bold;
font-size: 40rpx;
padding: 20rpx;
}
.mobile-pay-popup-amount-box {
background-color: #ffffff;
padding: 30rpx;
.mobile-pay-popup-amount {
color: #e43d33;
font-size: 60rpx;
margin-top: 20rpx;
}
}
.mobile-pay-popup-provider-list {
background-color: #ffffff;
margin-top: 20rpx;
}
}
/* 手机版收银台弹窗结束 */
/* PC版收银台弹窗开始 */
.pc-pay-popup {
width: 800px;
height: 600px;
background-color: var(--bgcolor);
border-radius: 10px;
overflow: hidden;
.pc-pay-popup-title{
background-color: #ffffff;
text-align: center;
font-weight: bold;
font-size: 20px;
height: 66px;
line-height: 66px;
}
.pc-pay-popup-flex{
width: 100%;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
.pc-pay-popup-qrcode-box{
height: calc(600px - 66px);
flex: 1;
background-color: #ffffff;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: center;
align-items: center;
.pc-pay-popup-qrcode-image{
width: 225px;
height: 225px;
}
.pc-pay-popup-amount-box{
text-align: center;
.pc-pay-popup-amount-tips{
color: #333;
font-size: 20px;
margin-top: 20px;
}
.pc-pay-popup-amount{
color: #dd524d;
font-weight: bold;
font-size: 32px;
margin-top: 20px;
}
}
.pc-pay-popup-complete-button{
margin-top: 20px;
}
}
.pc-pay-popup-provider-list{
width: 300px;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
.pc-pay-popup-provider-item{
padding: 20px;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
.pc-pay-popup-provider-image{
width: 60px;
height: 60px;
}
.pc-pay-popup-provider-text{
color: #333;
font-size: 20px;
margin-left: 10px;
}
}
.pc-pay-popup-provider-item.active{
background-color: #ffffff;
}
.pc-pay-popup-provider-item:hover{
background-color: #ffffff;
cursor: pointer;
}
.pc-pay-popup-logo{
flex: 1;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
justify-content: center;
image{
width: 120px;
}
}
}
}
}
/* PC版收银台弹窗结束 */
/* 二维码支付弹窗开始 */
.qrcode-popup-content {
width: 600rpx;
background-color: #ffffff;
border-radius: 10rpx;
padding: 40rpx;
box-sizing: border-box;
text-align: center;
.qrcode-image {
width: 450rpx;
height: 450rpx;
}
.qrcode-popup-info {
text-align: center;
padding: 20rpx;
.qrcode-popup-info-fee {
color: red;
font-size: 60rpx;
font-weight: bold;
}
}
.qrcode-popup-cancel{
margin-top: 20rpx;
text-align: center;
}
}
/* 二维码支付弹窗结束 */
/* 外部浏览器H5支付弹窗确认开始 */
.pay-confirm-popup-content {
width: 550rpx;
background-color: #ffffff;
border-radius: 10rpx;
padding: 40rpx;
.pay-confirm-popup-title {
text-align: center;
padding: 20rpx 0;
margin-bottom: 30rpx;
}
.pay-confirm-popup-refresh{
margin-top: 20rpx;
}
.pay-confirm-popup-cancel{
margin-top: 20rpx;
text-align: center;
}
}
/* 外部浏览器H5支付弹窗确认结束 */
</style>

View File

@@ -0,0 +1,123 @@
// uni iap
const IapTransactionState = {
purchasing: "0", // A transaction that is being processed by the App Store.
purchased: "1", // A successfully processed transaction.
failed: "2", // A failed transaction.
restored: "3", // A transaction that restores content previously purchased by the user.
deferred: "4" // A transaction that is in the queue, but its final status is pending external action such as Ask to Buy.
};
class Iap {
constructor(data={}) {
this._productIds = data.products || [];
this._channel = null;
this._channelError = null;
this.ready = false;
}
init() {
return new Promise((resolve, reject) => {
this.getChannels((channel) => {
this.ready = true;
resolve(channel);
}, (err) => {
reject(err);
})
})
}
getProduct(productIds) {
return new Promise((resolve, reject) => {
this._channel.requestProduct(productIds || this._productIds, (res) => {
resolve(res);
}, (err) => {
reject(err);
})
});
}
requestPayment(orderInfo) {
return new Promise((resolve, reject) => {
uni.requestPayment({
provider: "appleiap",
orderInfo: {
quantity: 1,
manualFinishTransaction: true,
...orderInfo
},
success: (res) => {
resolve(res);
},
fail: (err) => {
//console.log('requestPayment-err: ', err)
reject(err);
}
});
});
}
restoreCompletedTransactions(username) {
return new Promise((resolve, reject) => {
this._channel.restoreCompletedTransactions({
manualFinishTransaction: true,
username
}, (res) => {
resolve(res);
}, (err) => {
console.log('restoreCompletedTransactions-err: ', err)
reject(err);
})
});
}
finishTransaction(transaction) {
return new Promise((resolve, reject) => {
this._channel.finishTransaction(transaction, (res) => {
resolve(res);
}, (err) => {
reject(err);
});
});
}
getChannels(success, fail) {
if (this._channel !== null) {
success(this._channel)
return
}
if (this._channelError !== null) {
fail(this._channelError)
return
}
uni.getProvider({
service: 'payment',
success: (res) => {
this._channel = res.providers.find((channel) => {
return (channel.id === 'appleiap')
})
if (this._channel) {
success(this._channel)
} else {
this._channelError = {
errMsg: 'paymentContext:fail iap service not found'
}
fail(this._channelError)
}
}
});
}
get channel() {
return this._channel;
}
}
export default {
Iap,
IapTransactionState
};

View File

@@ -0,0 +1,158 @@
var util = {};
/*
* 此方法不支持微信公众号
util.getWeixinCode().then((code) => {
});
*/
util.getWeixinCode = function() {
return new Promise((resolve, reject) => {
// #ifdef MP-WEIXIN
uni.login({
provider: 'weixin',
success(res) {
resolve(res.code)
},
fail(err) {
reject(new Error('获取微信code失败'))
}
})
// #endif
// #ifdef APP-PLUS
plus.oauth.getServices((services) => {
let weixinAuthService = services.find((service) => {
return service.id === 'weixin';
});
if (weixinAuthService) {
weixinAuthService.authorize(function(res) {
resolve(res.code);
}, function(err) {
console.log(err);
reject(new Error('获取微信code失败'));
});
}
});
// #endif
})
};
util.getAlipayCode = function() {
// #ifdef APP-PLUS || MP-ALIPAY
return new Promise((resolve, reject) => {
uni.login({
provider: 'alipay',
success(res) {
resolve(res.code);
},
fail(err) {
reject(new Error('获取支付宝code失败可能是没有关联appid或你的支付宝开发者工具还没有登录'));
}
});
});
// #endif
};
util.checkPlatform = function() {
// #ifdef H5
let system = {
win: false,
mac: false,
xll: false
};
let p = navigator.platform;
system.win = p.indexOf("Win") == 0;
system.mac = p.indexOf("Mac") == 0;
system.x11 = p == "X11" || p.indexOf("Linux") == 0;
if (system.win || system.mac || system.xll) {
let ua = navigator.userAgent.toLowerCase();
if (ua.indexOf("micromessenger") > -1) {
// 微信开发者工具下访问(注意微信开发者工具下无法唤起微信公众号支付)
return "pc-weixin";
} else {
return "pc";
}
} else {
if (p.indexOf("iPhone") > -1 || p.indexOf("iPad") > -1) {
return "ios";
} else {
return "android";
}
}
// #endif
};
/**
* 获取当前H5所在的环境
*/
util.getH5Env = function() {
let ua = window.navigator.userAgent.toLowerCase();
if (ua.match(/MicroMessenger/i) == 'micromessenger' && (ua.match(/miniprogram/i) == 'miniprogram')) {
// 微信小程序
return "mp-weixin";
}
if (ua.match(/MicroMessenger/i) == 'micromessenger') {
// 微信公众号
return "h5-weixin";
}
if (ua.match(/alipay/i) == 'alipay' && ua.match(/miniprogram/i) == 'miniprogram') {
return "mp-alipay";
}
if (ua.match(/alipay/i) == 'alipay') {
return "h5-alipay";
}
// 外部 H5
return "h5";
};
/**
* 日期格式化
* @params {Date || Number} date 需要格式化的时间
* timeFormat(new Date(),"yyyy-MM-dd hh:mm:ss");
*/
util.timeFormat = function(time, fmt = 'yyyy-MM-dd hh:mm:ss', targetTimezone = 8) {
try {
if (!time) {
return "";
}
if (typeof time === "string" && !isNaN(time)) time = Number(time);
// 其他更多是格式化有如下:
// yyyy-MM-dd hh:mm:ss|yyyy年MM月dd日 hh时MM分等,可自定义组合
let date;
if (typeof time === "number") {
if (time.toString().length == 10) time *= 1000;
date = new Date(time);
} else {
date = time;
}
const dif = date.getTimezoneOffset();
const timeDif = dif * 60 * 1000 + (targetTimezone * 60 * 60 * 1000);
const east8time = date.getTime() + timeDif;
date = new Date(east8time);
let opt = {
"M+": date.getMonth() + 1, //月份
"d+": date.getDate(), //日
"h+": date.getHours(), //小时
"m+": date.getMinutes(), //分
"s+": date.getSeconds(), //秒
"q+": Math.floor((date.getMonth() + 3) / 3), //季度
"S": date.getMilliseconds() //毫秒
};
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
}
for (let k in opt) {
if (new RegExp("(" + k + ")").test(fmt)) {
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (opt[k]) : (("00" + opt[k]).substr(("" + opt[k]).length)));
}
}
return fmt;
} catch (err) {
// 若格式错误,则原值显示
return time;
}
};
export default util;

View File

@@ -0,0 +1,81 @@
{
"id": "uni-pay",
"displayName": "uni-pay",
"version": "2.0.2",
"description": "更简单的支付接口调用方式、拉齐不同支付平台",
"keywords": [
"unipay",
"uni-pay",
"微信支付",
"支付宝",
"ios内购"
],
"repository": "https://gitee.com/dcloud/uniPay.git",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "",
"type": "unicloud-template-page"
},
"uni_modules": {
"dependencies": ["uni-config-center","uni-id-common","uni-popup","uni-list"],
"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": "u",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "u",
"字节跳动": "u",
"QQ": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}

View File

@@ -0,0 +1,18 @@
<template>
<web-view :src="url"></web-view>
</template>
<script>
export default {
data() {
return {
url: ''
}
},
onLoad(options) {
if (options && options.url) {
this.url = decodeURIComponent(options.url);
}
}
}
</script>

View File

@@ -0,0 +1,116 @@
<template>
<!-- 自定义收银台页面模式 -->
<view class="uni-pay">
<view class="mobile-pay-popup" v-if="insideData && insideData.currentProviders">
<view class="mobile-pay-popup-amount-box">
<view>待支付金额</view>
<view class="mobile-pay-popup-amount">{{ (options.total_fee / 100).toFixed(2) }}</view>
</view>
<view class="mobile-pay-popup-provider-list">
<uni-list>
<!-- #ifdef MP-WEIXIN || H5 || APP -->
<uni-list-item v-if="insideData.currentProviders.indexOf('wxpay') > -1" :thumb="insideData.images.wxpay" title="微信支付" @click="createOrder({ provider: 'wxpay' })" clickable link></uni-list-item>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY || H5 || APP -->
<uni-list-item v-if="insideData.currentProviders.indexOf('alipay') > -1" :thumb="insideData.images.alipay" title="支付宝" @click="createOrder({ provider: 'alipay' })" clickable link></uni-list-item>
<!-- #endif -->
</uni-list>
</view>
</view>
<!-- 挂载支付组件 -->
<uni-pay ref="uniPay" :to-success-page="false" @mounted="onMounted" @success="onSuccess"></uni-pay>
</view>
</template>
<script>
export default {
data() {
return {
options: {
total_fee: "",
},
insideData: {}, // uni-pay组件mounted事件获得的数据
adpid: "", // 广告id
return_url: "", // 支付成功后点击查看订单跳转的订单详情页面地址
main_color: "", // 支付成功页面的主色调
}
},
// 监听 - 页面每次【加载时】执行(如:前进)
onLoad(options = {}) {
options = JSON.parse(decodeURI(options.options));
//console.log('options: ', options)
this.options = options;
},
// 监听 - 页面【首次渲染完成时】执行。注意如果渲染速度快,会在页面进入动画完成前触发
onReady(){},
// 监听 - 页面每次【显示时】执行(如:前进和返回) (页面每次出现在屏幕上都触发,包括从下级页面点返回露出当前页面)
onShow() {},
// 监听 - 页面每次【隐藏时】执行(如:返回)
onHide() {},
// 函数
methods: {
// 监听 - 支付组件加载完毕事件
onMounted(insideData){
this.insideData = insideData;
},
// 发起支付
createOrder(provider){
Object.assign(this.options, provider);
this.$refs.uniPay.createOrder(this.options);
},
// 监听事件 - 支付成功
onSuccess(res){
console.log('success: ', res);
if (res.user_order_success) {
// 代表用户已付款,且你自己写的回调成功并正确执行了
uni.redirectTo({
url:`/uni_modules/uni-pay/pages/success/success?out_trade_no=${res.out_trade_no}&order_no=${res.pay_order.order_no}&pay_date=${res.pay_order.pay_date}&total_fee=${res.pay_order.total_fee}&adpid=${this.adpid}&return_url=${this.return_url}&main_color=${this.main_color}`
});
} else {
// 代表用户已付款,但你自己写的回调执行成功(通常是因为你的回调代码有问题)
}
},
},
// 监听器
watch:{
},
// 计算属性
computed:{
}
}
</script>
<style lang="scss" scoped>
.mobile-pay-popup {
min-height: calc(100vh - var(--window-bottom) - var(--window-top));
background-color: #f3f3f3;
border-radius: 30rpx 30rpx 0 0;
overflow: hidden;
.mobile-pay-popup-title {
background-color: #ffffff;
text-align: center;
font-weight: bold;
font-size: 40rpx;
padding: 20rpx;
}
.mobile-pay-popup-amount-box {
background-color: #ffffff;
padding: 30rpx;
.mobile-pay-popup-amount {
color: #e43d33;
font-size: 60rpx;
margin-top: 20rpx;
}
}
.mobile-pay-popup-provider-list {
background-color: #ffffff;
margin-top: 20rpx;
}
}
</style>

View File

@@ -0,0 +1,233 @@
<template>
<view class="app" :style="styleCom">
<view class="header">
<image :src="images.success" class="success-image"></image>
<view class="success-title">支付成功</view>
<view class="hr"></view>
</view>
<view class="info-box">
<view class="info-amount">¥ {{ (options.total_fee / 100).toFixed(2) }}</view>
<view class="left-circle"></view>
<view class="right-circle"></view>
<view class="info-main">
<view class="info-cell">
<view class="left">订单编号</view>
<view class="right">{{ options.order_no }}</view>
</view>
<view class="info-cell">
<view class="left">付款时间</view>
<view class="right">{{ timeFormat(options.pay_date,'yyyy-MM-dd hh:mm:ss') }}</view>
</view>
</view>
</view>
<!-- 广告位开始 -->
<view class="uni-ad">
<!-- 红包广告-->
<ad-interactive v-if="options.adpid" :adpid="options.adpid" v-slot:default="{data, loading, error}" open-page-path="/uni_modules/uni-pay/pages/ad-interactive-webview/ad-interactive-webview" @error="onaderror">
<view v-if="data" class="ad-interactive">
<!-- 可以自定义此图片组件提供了默认素材通过 uni-ad 后台配置 -->
<image :src="data.imgUrl" mode="widthFix"></image>
</view>
</ad-interactive>
<!-- #ifndef MP-WEIXIN -->
<!-- 注意h5下的广告出来有延迟后续要优化 -->
<!-- <ad v-if="options.adpid" :adpid="options.adpid" type="banner" @error="onaderror"></ad> -->
<!-- #endif -->
</view>
<!-- 广告位结束 -->
<view v-if="options.return_url" class="button-query" @click="queryOrder">查看订单</view>
<view class="footer-hr"></view>
</view>
</template>
<script>
import jsSdk from "../../js_sdk/js_sdk.js"
export default {
data() {
return {
options:{
adpid:"",
main_color:"",
order_no:"",
out_trade_no:"",
total_fee:"",
pay_date:"",
return_url:""
},
images:{
success:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABAEAYAAAD6+a2dAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlzAAAASAAAAEgARslrPgAACAlJREFUeNrtnVlszF8Ux++d4B+JWhqERGJvlRJLJQ1RHsQeFEXFUmt5scWWiPVB2qKERIglSpEg9vJAYn8RQlRFI5ryYm+prbZ7/g/fOdPOtNPfdDrjzEx/n5dvf3fuTM895869v3t/997RKswhQ4ZM69a4SkpSWmmle/TAdbdu0JgYaIsW0ObNoU2auH/a16/QT5+gpaXQwkLos2fQggJFihTdvq0d2qEd795J+yHiQaD794dmZxMREeXnQ40hEfj/5ufDru3boQkJ0v4KW+DApk2hK1bAwU+fygS4rhQUuMphyJCJipL2b8gBR0VHQzdtgpaUSIcuOHC5Nm6EcpdUj0DBtcY3YuZMXL97Jx0aGT5+hC5ZAnU4pOMTvMAbMmS6dEFB796Vdn1ocucOtHNn6XgFLvBERJScDC0tlXZxeFBWBp06VTp+fgbc4YDu2CHtyrCHRxdEFNJdBAxt1AiGHj8u7bfI5Ngx+Llhw0DFTQcq8JiAOXsWqaNGyVXF+kBeHiaikpMxEfX7t7+f5HeTghqpNQK/fz9S7cD/G0aPht9zchAH/7uGOvYp2dnQmTOlXVI/SU1FS5CZ6e8n1LoLQJOfkoIaePKktAtsKjNtmtZaa33ihK/v8LkCoKnh8emDB9BmzaSLbFOZz5+hffuiIhQVWb3Dsgtw9fVKKaWOHIHagQ9NOC6HD7vHzTvW9wCkSNHcubgYMEC6iDa+MGgQ1PrezGsNQQ2KjkYFKCxEn9+ypXTRbGoDr1OIjUWXwOscKrBoAZYutQMfzvBCmcWLveWo0gLw83gEvrgYqfXwsWVEUVKClrxDB0wcffnCr3hpARYsgNqBjwyio/GFnj/f85WqFUArrfScOdIm1w/KyqA8hR5sZs/2THFVAF5zh6u4OGnXRDbfv0PHjoWmpEDPnQvu/42Px819nz6c4tECpKZKuyay+fEDOmYM7spv3oT+/Yv06dOhL18GzQRSpGjaNL6sqABaaaWHDZN2UWTy6xc0JQUBv369+ny8AKR9++DaM3Qo/6Vd6+q10kq/eeNMrvNjYhullOLHtJMmIfAXLnjmQJM8YgSuLl6ENmgQXLuI0BK0bu1sAZKSoHbgA8OfP9CpU2sOPPfF/FAt2IFn+DF+UpIDf8THS7ssMuC+PC0NgT9zxjMHWtx27XDFFUNqv0CPHs4WIDZWxoBIgZvURYsQ+GPHquQwZMg0a4Yv3OXLSOWKIEVsrLMCdO0qa0i4woFPT8cMG6+MqpTDkCHz338I/PnzSO3ZU9pyEBPj7HPsuf5aQ4oUrVrlNfBuj2MPHIAOHixttjstWzpbAM9dslIcOQLH8i7cUGXFCgR+27aa82VkQHl8H0KQIkVRUc6a+vOn7HLne/fcl5W3aIHr69dl7fJk7VpLvxoyZNLTpS31jfJy4QrAO4U6dqzekVwhcnNlHbV5s2+BHzMG+f/8kbXXV1wV4MMHGQNmzbJ0LBFVbCbdsuWfmWbIkMnK8s2+xETot28yfvS3fO/fOwtQXCxjxdOn7uNiH7ouIiKaMwf661dw7Nq1yzc7OnWCvn0r47+6UlTkLMj9+2I2GDJkXrzARadOtasII0dCv3wJjDF790K9z4jC3latkO/5czG/BYR795yODJW9fK9fw8G9evlcEQwZMj174v2vXvn3f3kVrfcdNni9cWNopGxvP3rUWWA+BEmaNm0wYXLjBgxMTLR6B4Zj+fm4SkzE8ObRI+uao0jRqVPQefMwg2dM9YF3OJAvNxepEbA6mhf7ooATJ0rXxerhffJDhvhcLueUK/Tateo/99QpvG798AX5d++W9kRwSE52HbOGBKnTtqwoL3cZXKuK0KAB3rdvH/TKFdfUrE+BX7VKuuTB4e9f+KHSDDBeePxY2rSa4bt+31cuIT8PI6331SP/lCkuR0UahgyZhw+5vBU3PaRI0bVrvjpWBg4gTwwtXGj1DvTtRFb76OEYXheRk+N0T+ieyFEnrl714oCEBOkK6h8bNvjrCrw/Lg4aqcfTedK7t4VD+ATOcCMjA2q9sgn52raFSk2E/WsKCjz9ULWJI0WKuAkMN1avhu7ZgwJXbcJdJ3aSIkV5eUgN9iLMUOHgQc8UL1vDoqIwHuflyeG6Q+jECQS60jMHrbTSvPhy+HBpC/8N3reGVRkHcwZ8g3hO3P8+VpbUVASc1zt8+ACtL4Fndu70DDxjsT2cv/k8U9iqlXRRbGrD27f45sfGogLwCSIVeB3mYPjE5+WvWSNdFJtaQooUrVzpLfCMj3fLfFd9+zZ04EDp8tnUxK1b0CFDeB7EW077kKiIgk8A6dcvYIdEMfjAFy/46Zl0UW2qY+5cXwPP1HqqE33K6dOoCHxQpI0sWVnediJZ4fdeQPd7g0OHoGlp0q6oXxw/Dp0xw9t6BisCdFh0w4YYb/MBB/aZwcHl0iW0wBMmiB0WzbgMIEWKxo2DcotgE1hycwMV+KDBXQNahq1bpR9/hDe8QCczExqG2/dh+Pjx0PryuLWufP4MnTxZOn4Brgi8jv7WLWkXhyY3b0Kr7pSKGFBAz5+NC9eNFXWFfzZuwQL2i3R8hCpE8+bQ9evdHRNp8Ba8detcB0bYuAPHNGkCRy1fDn3yRDp0/sErq5Yt43JJ+zdsgSP79HEfXTx6BJVavcvLqx8+5M2kSK9hzV2IEfZ9jmt9u/PUK4yTu3fHdVwcrvnn4/nM3Bp+Pp4UKeKHKiUlyF9Y6Dq4QiuttOfPx/NCk/Djf0hQD04eJaNOAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIyLTEwLTI3VDE0OjAzOjAyKzA4OjAwisT1owAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMi0xMC0yN1QxNDowMzowMiswODowMPuZTR8AAABQdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWRtaW4vaWNvbi1mb250L3RtcC9pY29uX3ZwM212emVpcjcvemhpZnVjaGVuZ2dvbmcuc3ZntdPldAAAAABJRU5ErkJggg=="
},
// 默认颜色
color:{
wxpay:"#01be6e",
alipay:"#108ee9"
}
}
},
// 监听 - 页面每次【加载时】执行(如:前进)
onLoad(options = {}) {
this.options = options;
},
// 监听 - 页面【首次渲染完成时】执行。注意如果渲染速度快,会在页面进入动画完成前触发
onReady(){},
// 监听 - 页面每次【显示时】执行(如:前进和返回) (页面每次出现在屏幕上都触发,包括从下级页面点返回露出当前页面)
onShow() {},
// 监听 - 页面每次【隐藏时】执行(如:返回)
onHide() {},
// 函数
methods: {
timeFormat: jsSdk.timeFormat,
queryOrder(){
let url = this.options.return_url + `?out_trade_no=${this.options.out_trade_no}&order_no=${this.options.order_no}`;
if (url.indexOf("/") !== 0) url = `/${url}`;
uni.navigateTo({
url,
});
},
onaderror(e) {
console.log("ad-error", e);
},
},
// 监听器
watch:{
"mainColorCom":{
immediate:true,
handler(newVal, oldVal){
// 动态改变导航栏颜色
setTimeout(function(){
uni.setNavigationBarColor({
frontColor: "#ffffff",
backgroundColor: newVal
});
}, 0);
}
}
},
// 计算属性
computed:{
mainColorCom(){
let color = "";
// #ifdef MP-ALIPAY
color = this.options.main_color || this.color.alipay;
// #endif
// #ifndef MP-ALIPAY
color = this.options.main_color || this.color.wxpay
// #endif
return color;
},
styleCom(){
return `--main:${this.mainColorCom};`;
}
}
}
</script>
<style lang="scss" scoped>
.app{
--bgcolor: #f3f3f3;
background-color: var(--bgcolor);
min-height: calc(100vh - var(--window-bottom) - var(--window-top));
}
.header{
background-color: var(--main);
text-align: center;
color: #ffffff;
padding: 80rpx 30rpx 50rpx 30rpx;
.success-image{
width: 120rpx;
height: 120rpx;
}
.success-title{
font-size: 34rpx;
margin-top: 40rpx;
font-weight: bold;
}
.hr{
margin-top: 40rpx;
width: 100%;
height: 30rpx;
border-radius: 20rpx;
opacity: 0.1;
background-color: #000000;
}
}
.info-box{
width: calc(100% - 100rpx);
margin: 0 50rpx;
position: relative;
margin-top: -64rpx;
background-color: #ffffff;
.info-amount{
height: 150rpx;
line-height: 150rpx;
text-align: center;
color: var(--main);
font-weight: bold;
font-size: 60rpx;
border-bottom: 4rpx dashed #f3f3f3;
}
.left-circle{
background-color: var(--bgcolor);
position: absolute;
width: 40rpx;
height: 40rpx;
border-radius: 50%;
top:calc(150rpx - 20rpx);
left:-20rpx;
}
.right-circle{
background-color: var(--bgcolor);
position: absolute;
width: 40rpx;
height: 40rpx;
border-radius: 50%;
top:calc(150rpx - 20rpx);
right:-20rpx;
}
.info-main{
padding: 30rpx;
font-size: 26rpx;
color: #333333;
.info-cell{
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
line-height: 50rpx;
.left{
width: 200rpx;
text-align: left;
}
.right{
flex: 1;
text-align: right;
}
}
}
}
.uni-ad{
margin-top: 50rpx;
min-height: 100rpx;
.ad-interactive{
text-align: center;
}
}
.button-query{
background-color: var(--main);
color: #ffffff;
width: calc(100% - 120rpx);
margin: 50rpx 60rpx 0 60rpx;
padding: 20rpx 30rpx;
border-radius: 50rpx;
text-align: center;
box-sizing: border-box;
}
.button-query:active{
opacity: 0.7;
}
.footer-hr{
height: 100rpx;
display: block;
}
</style>

View File

@@ -0,0 +1,31 @@
`uni-pay`已升级为`uni-pay 2.x`从公共模块升级为包含前端页面、uni-pay-co云对象让支付更加简单省心 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-pay.html)
## 简介
支付,重要的变现手段,但开发复杂。在不同端,对接微信支付、支付宝等渠道,前端后端都要写不少代码。
涉及金额可不是小事,生成业务订单、获取收银台、发起支付、支付状态查询、支付异步回调、失败处理、发起退款、退款状态查询、支付统计...众多环节,代码量多,出错率高。
为什么不能有一个开源的、高质量的项目即可以避免大家重复开发又可以安心使用不担心自己从头写产生Bug。
`uni-pay`应需而生。
之前`uni-pay 1.x`版本,仅是一个公共模块,它让开发者无需研究支付宝、微信等支付平台的后端开发、无需为它们编写不同代码,拿来即用,屏蔽差异。
但开发者还是需要自己编写前端页面和云函数,还是有一定的开发难度和工作量的,特别对于新手来说,门槛高、易出错。
`uni-pay 2.0` 起,补充了前端页面和云对象,让开发者开箱即用。
**注意:`uni-pay 2` 仍内置了uni-pay公共模块向下兼容`uni-pay 1.x`,即从`uni-pay 1.x`可以一键升级到`uni-pay 2.x`,且不会对你的老项目造成影响。**
开发者在项目中引入 `uni-pay` 后,微信支付、支付宝支付等功能无需自己再开发。由于源码的开放性和层次结构清晰,有二次开发需求也很方便调整。
> 下载地址:[https://ext.dcloud.net.cn/plugin?name=uni-pay](https://ext.dcloud.net.cn/plugin?name=uni-pay)
> 开发文档:[https://uniapp.dcloud.io/uniCloud/uni-pay](https://uniapp.dcloud.io/uniCloud/uni-pay)
**线上体验地址**
注意:线上体验地址用的是阿里云免费版,免费版请求次数有限,如请求失败为正常现象,可直接导入示例项目绑定自己的空间体验。
![](https://f184e7c3-1912-41b2-b81f-435d1b37c7b4.cdn.bspapp.com/VKCEYUGU-f184e7c3-1912-41b2-b81f-435d1b37c7b4/a9489bd6-37d6-4664-9e0b-346f8859534c.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 935 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,201 @@
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.

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,16 @@
{
"name": "uni-pay",
"version": "2.0.0",
"description": "unipay for uniCloud",
"main": "index.js",
"homepage": "https://uniapp.dcloud.io/uniCloud/unipay",
"repository": {
"type": "git",
"url": "git+https://gitee.com/dcloud/uniPay.git"
},
"scripts": {},
"keywords": [],
"author": "",
"license": "Apache-2.0",
"devDependencies": {}
}

View File

@@ -0,0 +1,64 @@
/**
* 全局错误码
*/
const ERROR = {
50403: 50403,
// 参数错误
51001: 51001,
51002: 51002,
51003: 51003,
51004: 51004,
51005: 51005,
51006: 51006,
51007: 51007,
51008: 51008,
51009: 51009,
51010: 51010,
// 数据不存在
52001: 52001,
52002: 52002,
// 运行错误
53001: 53001,
53002: 53002,
53003: 53003,
53004: 53004,
53005: 53005,
}
const errSubject = "uni-pay";
function isUniPayError(errCode) {
return Object.values(ERROR).includes(errCode);
}
class UniCloudError extends Error {
constructor(options = {}) {
super(options.message);
this.errMsg = options.message || '';
this.code = this.errCode = options.code;
this.errSubject = options.subject || errSubject;
this.forceReturn = options.forceReturn || false;
this.cause = options.cause;
}
toJson(level = 0) {
if (level >= 10) {
return
}
level++
return {
errCode: this.errCode,
errMsg: this.errMsg,
code: this.errCode,
message: this.message,
errSubject: this.errSubject,
cause: this.cause && this.cause.toJson ? this.cause.toJson(level) : this.cause
}
}
}
module.exports = {
ERROR,
isUniPayError,
UniCloudError
}

View File

@@ -0,0 +1,9 @@
// 各接口权限配置,未配置接口表示允许任何用户访问(包括未登录用户)
module.exports = {
refund: {
// auth: true // 已登录用户方可操作,配置角色或权限时此项可不写
role: ['admin'] // 允许进行此操作的角色,包含任一角色均可操作。
// permission: [] // 允许进行此操作的权限,包含任一权限均可操作。
// 权限角色均配置时,用户拥有任一权限或任一角色均可操作
}
}

View File

@@ -0,0 +1,6 @@
const uniPayOrders = require("./uniPayOrders");
const uniIdUsers = require("./uniIdUsers");
module.exports = {
uniPayOrders,
uniIdUsers
};

View File

@@ -0,0 +1 @@
dao名词解释Data Access Object

View File

@@ -0,0 +1,25 @@
const dbName = {
user: "uni-id-users"
}
const db = uniCloud.database();
const _ = db.command;
var dao = {};
/**
* 获取 - 第三方支付订单数据
let userInfo = await dao.uniIdUsers.findById(id);
*/
dao.findById = async (id) => {
let res = await db.collection(dbName.user).doc(id).get();
if (res.data && res.data.length > 0) {
return res.data[0];
} else {
return null;
}
return res;
};
module.exports = dao;

View File

@@ -0,0 +1,108 @@
const dbName = {
payOrders: "uni-pay-orders" // 数据库表名 - 第三方支付订单表
}
const db = uniCloud.database();
const _ = db.command;
var dao = {};
/**
* 添加 - 第三方支付订单数据
await dao.uniPayOrders.add({
});
*/
dao.add = async (dataJson = {}) => {
// 数据库操作开始-----------------------------------------------------------
let res = await db.collection(dbName.payOrders).add(dataJson);
// 数据库操作结束-----------------------------------------------------------
return res.id ? res.id : null;
};
/**
* 获取 - 第三方支付订单数据
let payOrderInfo = await dao.uniPayOrders.find({
order_no,
out_trade_no
});
*/
dao.find = async (where) => {
let res = await db.collection(dbName.payOrders)
.where(where)
.limit(1)
.get();
if (res.data && res.data.length > 0) {
return res.data[0];
} else {
return null;
}
return res;
};
/**
* 修改 - 第三方支付订单数据
await dao.uniPayOrders.updateById(id, {
});
*/
dao.updateById = async (id = "___", dataJson) => {
// 数据库操作开始-----------------------------------------------------------
let res = await db.collection(dbName.payOrders).doc(id).update(dataJson);
// 数据库操作结束-----------------------------------------------------------
return res ? res.updated : 0;
};
/**
* 修改 - 第三方支付订单数据
await dao.uniPayOrders.update({
whereJson:{
},
dataJson:{
}
});
*/
dao.update = async (obj) => {
let { whereJson, dataJson } = obj;
// 数据库操作开始-----------------------------------------------------------
let res = await db.collection(dbName.payOrders).where(whereJson).update(dataJson);
// 数据库操作结束-----------------------------------------------------------
return res ? res.updated : 0;
};
/**
* 修改 - 第三方支付订单数据
await dao.uniPayOrders.updateAndReturn({
whereJson:{
},
dataJson:{
}
});
*/
dao.updateAndReturn = async (obj) => {
let { whereJson, dataJson } = obj;
// 数据库操作开始-----------------------------------------------------------
let res = await db.collection(dbName.payOrders).where(whereJson).updateAndReturn(dataJson);
// 数据库操作结束-----------------------------------------------------------
return res.doc ? res.doc : null;
};
/**
* 删除超过3天还未支付款的订单
await dao.uniPayOrders.deleteExpPayOrders();
*/
dao.deleteExpPayOrders = async () => {
// 数据库操作开始-----------------------------------------------------------
let time = Date.now() - 1000 * 3600 * 24 * 3;
let res = await db.collection(dbName.payOrders)
.where({
status: _.in([-1,0]),
create_date: _.lt(time)
})
.remove();
// 数据库操作结束-----------------------------------------------------------
return res ? res.updated : 0;
};
module.exports = dao;

View File

@@ -0,0 +1,268 @@
/**
* uni-pay-co 统一支付云对象
*/
// 加载服务
const service = require('./service');
// 加载全局错误码
const { UniCloudError, isUniPayError, ERROR } = require('./common/error');
// 加载全局中间件
const middleware = require('./middleware/index');
// 加载uniId公共模块
const uniIdCommon = require('uni-id-common');
module.exports = {
/**
* 中间件(请求前执行)
*/
async _before() {
const params = this.getParams();
let clientInfo;
if (params && params[0] && params[0].clientInfo) {
clientInfo = params[0].clientInfo;
} else {
clientInfo = this.getClientInfo();
}
// 挂载uni-id实例到this上方便后续调用
this.uniIdCommon = uniIdCommon.createInstance({
clientInfo
});
// 国际化开始
const i18n = uniCloud.initI18n({
locale: clientInfo.locale || 'zh-Hans',
fallbackLocale: 'zh-Hans',
messages: require('./lang/index')
})
this.t = i18n.t.bind(i18n);
// 国际化结束
// 挂载中间件
this.middleware = {}
for (const mwName in middleware) {
this.middleware[mwName] = middleware[mwName].bind(this);
}
// 尝试从token获取用户信息
await this.middleware.auth(false);
// 通用权限校验模块
await this.middleware.accessControl();
// 设置全局获取userId公共函数可在此云对象的任意其他函数内通过 this.getUserId() 获取当前登录用户的id
this.getUserId = () => {
return this.authInfo && this.authInfo.uid ? this.authInfo.uid : undefined;
}
},
/**
* 中间件(请求后执行)
*/
_after(error, result) {
if (error) {
if (error.errCode) {
let errCode = error.errCode
if (!isUniPayError(errCode)) {
// 如果不是插件预设的错误码,则原样返回错误信息
return error;
}
return new UniCloudError({
code: errCode,
message: error.errMsg || this.t(errCode, error.errMsgValue),
});
}
throw error
}
return result;
},
/**
* 创建支付订单
*/
async createOrder(data) {
let {
provider, // 支付供应商 如 wxpay alipay 参考 https://uniapp.dcloud.net.cn/api/plugins/provider.html#
total_fee, // 订单总金额单位为分100等于1元
openid, // 发起支付的用户openid
order_no, // 业务系统订单号 建议控制在20-28位(不可以是24位,24位在阿里云空间可能会有问题)(可重复,代表1个业务订单会有多次付款的情况)
out_trade_no, // 支付插件订单号(需控制唯一,不传则由插件自动生成)
description, // 支付描述uniCloud个人版包月套餐
type, // 订单类型 goods订单付款 recharge余额充值付款 vipvip充值付款 等等,可自定义
qr_code, // true 强制开启二维码支付模式
custom, // 自定义参数(不会发送给第三方支付服务器)
other, // 其他请求参数(会发送给第三方支付服务器),
clientInfo, // 兼容云对象调用云对象模式
cloudInfo, // 兼容云对象调用云对象模式
} = data;
if (!clientInfo) clientInfo = this.getClientInfo();
if (!cloudInfo) cloudInfo = this.getCloudInfo();
// 获取当前登录的user_id
let user_id = this.getUserId();
let res = await service.pay.createOrder({
provider,
total_fee,
user_id,
openid,
order_no,
out_trade_no,
description,
type,
qr_code,
custom,
other,
clientInfo,
cloudInfo,
});
return res;
},
/**
* 接收支付异步通知
*/
async payNotify(data) {
const httpInfo = this.getHttpInfo();
return service.pay.paymentNotify({
httpInfo,
});
},
/**
* 查询支付状态
*/
async getOrder(data) {
let {
out_trade_no, // 插件订单号
transaction_id, // 第三方支付交易单号
await_notify = false, // 是否需要等待异步通知执行完成若为了响应速度可以设置为false若需要等待异步回调执行完成则设置为true
} = data;
res = await service.pay.getOrder({
out_trade_no,
transaction_id,
await_notify
});
return res;
},
/**
* 发起退款
* 此api只有admin角色可以访问
*/
async refund(data) {
let {
out_trade_no, // 插件订单号
out_refund_no, // 插件退款订单号
refund_desc, // 退款描述
refund_fee, // 退款金额
} = data;
res = await service.pay.refund({
out_trade_no,
out_refund_no,
refund_desc,
refund_fee,
});
return res;
},
/**
* 查询退款(查询退款情况)
*/
async getRefund(data) {
let {
out_trade_no, // 插件订单号
} = data;
res = await service.pay.getRefund({
out_trade_no,
});
return res;
},
/**
* 关闭订单(只有订单未支付时,才可以关闭,关闭后,用户即使在付款页面也无法付款)
*/
async closeOrder(data) {
let {
out_trade_no, // 插件订单号
} = data;
res = await service.pay.closeOrder({
out_trade_no,
});
return res;
},
/**
* 根据code获取openid
*/
async getOpenid(data = {}) {
let {
provider,
code,
clientInfo, // 兼容云对象调用云对象模式
} = data;
if (!clientInfo) clientInfo = this.getClientInfo();
res = await service.pay.getOpenid({
provider,
code,
clientInfo
});
return res;
},
/**
* 获取当前支持的支付方式
*/
async getPayProviderFromCloud() {
return await service.pay.getPayProviderFromCloud();
},
/**
* 获取支付配置内的appid主要用于获取获取微信公众号的appid用以获取code
*/
async getProviderAppId(data) {
let {
provider,
provider_pay_type
} = data;
// 注意,前往不要直接把 conifg 内的所有内容返回给前端
let conifg = service.pay.getConfig();
try {
return {
errorCode: 0,
appid: conifg[provider][provider_pay_type].appId,
}
} catch (err) {
return {
errorCode: 0,
appid: null
};
}
},
/**
* 验证iosIap苹果内购支付凭据
*/
async verifyReceiptFromAppleiap(data) {
let {
out_trade_no,
transaction_receipt,
transaction_identifier,
} = data;
return await service.pay.verifyReceiptFromAppleiap({
out_trade_no,
transaction_receipt,
transaction_identifier
});
},
}

View File

@@ -0,0 +1,29 @@
const word = {
};
const sentence = {
50403: 'Permission denied',
51001: 'Invalid out_trade_no',
51002: 'Invalid code',
51003: 'Invalid order_no',
51004: 'Invalid type',
51005: 'Invalid total_fee',
51006: 'Invalid description',
51007: 'Invalid provider',
51008: 'Invalid clientInfo',
51009: 'Invalid cloudInfo',
51010: 'Invalid out_trade_no or transaction_id',
52001: 'NotExist payOrder',
52002: 'NotExist notifyUrl',
53001: 'Create payment error',
53002: 'Refund error',
53003: 'Query refund error',
53004: 'Close order error',
53005: 'Cert verify error',
};
module.exports = {
...word,
...sentence
}

View File

@@ -0,0 +1,22 @@
let lang = {
'zh-Hans': require('./zh-hans'),
en: require('./en')
}
function mergeLanguage(lang1, lang2) {
const localeList = Object.keys(lang1)
localeList.push(...Object.keys(lang2))
const result = {}
for (let i = 0; i < localeList.length; i++) {
const locale = localeList[i]
result[locale] = Object.assign({}, lang1[locale], lang2[locale])
}
return result
}
try {
const langPath = require.resolve('uni-config-center/uni-id/lang/index.js')
lang = mergeLanguage(lang, require(langPath))
} catch (error) {}
module.exports = lang

View File

@@ -0,0 +1,29 @@
const word = {
};
const sentence = {
50403: '权限错误',
51001: '支付单号out_trade_no不能为空',
51002: 'code不能为空',
51003: '订单号order_no不能为空',
51004: '回调类型type不能为空如设置为goods代表商品订单',
51005: '支付金额total_fee必须为正整数>0的整数注意100=1元',
51006: '支付描述description不能为空',
51007: '支付供应商provider不能为空',
51008: 'clientInfo不能为空',
51009: 'cloudInfo不能为空',
51010: '支付单号或第三方交易单号不能同时为空',
52001: '支付订单不存在',
52002: '请先配置正确的异步回调URL',
53001: '获取支付信息失败,请稍后再试',
53002: '退款失败',
53003: '查询退款信息失败,请稍后再试',
53004: '关闭订单失败,请稍后再试',
53005: '证书错误,请检查支付证书',
};
module.exports = {
...word,
...sentence
}

View File

@@ -0,0 +1,145 @@
/**
* 支付宝相关函数
*/
const common = require('./common');
const crypto = require('crypto');
const ALIPAY_ALGORITHM_MAPPING = {
RSA: 'RSA-SHA1',
RSA2: 'RSA-SHA256'
}
var alipay = {
/**
* 获取openid
*/
async getOpenid(data) {
let {
config={},
code,
} = data;
if (!config.appId) throw new Error("uni-pay配置alipay.mp节点下的appId不能为空");
if (!config.privateKey) throw new Error("uni-pay配置alipay.mp节点下的privateKey不能为空");
let timestamp = common.timeFormat(new Date(), "yyyy-MM-dd hh:mm:ss");
let method = "alipay.system.oauth.token";
let params = {
timestamp,
code,
grant_type: "authorization_code"
};
let signData = this._getSign(method, params, config);
// 格式化url和请求参数
const { url, execParams } = this._formatUrl(signData);
let res = await uniCloud.httpclient.request(url, {
method: 'POST',
data: execParams,
dataType: 'text',
});
const result = JSON.parse(res.data)
let response = result.alipay_system_oauth_token_response;
if (res.status === 200 && response.user_id) {
return {
errCode: 0,
errMsg: 'ok',
openid: response.user_id,
user_id: response.user_id,
}
} else {
return {
errCode: -1,
errMsg: result.error_response.sub_msg
}
}
},
/**
* 签名
* @param {String} method 方法名
* @param {Object} params 参数
* @param {Object} config 配置
*/
_getSign(method, params, config) {
let signParams = Object.assign({
method,
app_id: config.appId,
charset: config.charset || "utf-8",
version: config.version || "1.0",
sign_type: config.signType || "RSA2",
}, params);
if (config.appCertSn && config.alipayRootCertSn) {
signParams = Object.assign({
app_cert_sn: config.appCertSn,
alipay_root_cert_sn: config.alipayRootCertSn,
}, signParams);
}
const bizContent = params.biz_content;
if (bizContent) {
signParams.biz_content = JSON.stringify(bizContent);
}
// 排序
const decamelizeParams = this._sortObj(signParams);
// 拼接url参数
let signStr = this._objectToUrl(decamelizeParams);
let keyType = config.keyType || 'PKCS8';
const privateKeyType = keyType === 'PKCS8' ? 'PRIVATE KEY' : 'RSA PRIVATE KEY'
let privateKey = this._formatKey(config.privateKey, privateKeyType);
// 计算签名
const sign = crypto.createSign(ALIPAY_ALGORITHM_MAPPING[signParams.sign_type])
.update(signStr, 'utf8').sign(privateKey, 'base64');
return Object.assign(decamelizeParams, { sign });
},
_formatKey(key, type) {
return `-----BEGIN ${type}-----\n${key}\n-----END ${type}-----`
},
_sortObj(params) {
let keysArr = Object.keys(params).sort();
let sortObj = {};
for (let i in keysArr) {
sortObj[keysArr[i]] = (params[keysArr[i]]);
}
return sortObj;
},
_objectToUrl(obj) {
let str = "";
for (let key in obj) {
if (obj[key]) {
str += `&${key}=${obj[key]}`;
}
}
if (str) str = str.substring(1);
return str;
},
_formatUrl(params, url = "https://openapi.alipay.com/gateway.do") {
let requestUrl = url;
// 需要放在 url 中的参数列表
const urlArgs = [
'app_id',
'method',
'format',
'charset',
'sign_type',
'sign',
'timestamp',
'version',
'notify_url',
'return_url',
'auth_token',
'app_auth_token'
]
for (const key in params) {
if (urlArgs.indexOf(key) > -1) {
const val = encodeURIComponent(params[key])
requestUrl = `${requestUrl}${requestUrl.includes('?') ? '&' : '?'
}${key}=${val}`
// 删除 postData 中对应的数据
delete params[key]
}
}
return { execParams: params, url: requestUrl }
}
};
module.exports = alipay;

View File

@@ -0,0 +1,293 @@
/**
* 通用公共函数
*/
var common = {};
/**
* 日期格式化
*/
common.timeFormat = function(time, fmt = 'yyyy-MM-dd hh:mm:ss', targetTimezone = 8) {
try {
if (!time) {
return "";
}
if (typeof time === "string" && !isNaN(time)) time = Number(time);
// 其他更多是格式化有如下:
// yyyy-MM-dd hh:mm:ss|yyyy年MM月dd日 hh时MM分等,可自定义组合
let date;
if (typeof time === "number") {
if (time.toString().length == 10) time *= 1000;
date = new Date(time);
} else {
date = time;
}
const dif = date.getTimezoneOffset();
const timeDif = dif * 60 * 1000 + (targetTimezone * 60 * 60 * 1000);
const east8time = date.getTime() + timeDif;
date = new Date(east8time);
let opt = {
"M+": date.getMonth() + 1, //月份
"d+": date.getDate(), //日
"h+": date.getHours(), //小时
"m+": date.getMinutes(), //分
"s+": date.getSeconds(), //秒
"q+": Math.floor((date.getMonth() + 3) / 3), //季度
"S": date.getMilliseconds() //毫秒
};
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
}
for (let k in opt) {
if (new RegExp("(" + k + ")").test(fmt)) {
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (opt[k]) : (("00" + opt[k]).substr(("" + opt[
k]).length)));
}
}
return fmt;
} catch (err) {
// 若格式错误,则原值显示
return time;
}
};
/**
* 产生订单号,不依赖数据库,高并发时性能高(理论上会重复,但概率非常非常低)
*/
common.createOrderNo = function(prefix = "", num = 25) {
// 获取当前时间字符串格式如20200803093000123
let timeStr = common.timeFormat(Date.now(), "yyyyMMddhhmmssS");
timeStr = timeStr.substring(2);
let randomNum = num - (prefix + timeStr).length;
return prefix + timeStr + common.random(randomNum, "123456789");
};
/**
* 产生随机数
*/
common.random = function(length, list = "123456789") {
let s = "";
for (let i = 0; i < length; i++) {
let code = list[Math.floor(Math.random() * list.length)];
s += code;
}
return s;
};
/**
* 休眠,等待(单位毫秒)
* @param {Number} ms 毫秒
* await common.sleep(1000);
*/
common.sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
/**
* 获取platform
* let provider_pay_type = common.getPlatform(platform);
*/
common.getPlatform = function(platform) {
if (["h5", "web"].indexOf(platform) > -1) {
platform = "web";
} else if (["app", "app-plus"].indexOf(platform) > -1) {
platform = "app";
}
return platform;
};
/**
* 获取 provider_pay_type
let provider_pay_type = common.getProviderPayType({
platform,
provider,
ua,
qr_code
});
*/
common.getProviderPayType = function(data) {
let {
platform,
provider,
ua,
qr_code
} = data;
// 扫码支付
if (qr_code) return "native";
// 小程序支付
if (platform.indexOf("mp") > -1) return "mp";
// APP支付
if (platform.indexOf("app") > -1) return "app";
// 微信公众号支付
if (platform === "web" && provider === "wxpay" && ua.toLowerCase().indexOf("micromessenger") > -1) return "jsapi";
// 微信外部浏览器支付
if (platform === "web" && provider === "wxpay" && ua.toLowerCase().indexOf("micromessenger") === -1) return "mweb";
if (platform === "web" && provider === "alipay") return "native";
throw new Error(`不支持的支付方式${provider}-${platform}`);
};
/**
* 获取uniPay交易类型
let tradeType = common.getTradeType({ provider, provider_pay_type });
*/
common.getTradeType = function(data) {
let { provider, provider_pay_type } = data;
let pay_type = `${provider}_${provider_pay_type}`;
let obj = {
// 微信
"wxpay_app": "APP", // 微信app支付
"wxpay_mp": "JSAPI", // 微信小程序支付
"wxpay_native": "NATIVE", // 微信扫码支付
"wxpay_mweb": "MWEB", // 微信外部浏览器支付
"wxpay_jsapi": "JSAPI", // 微信公众号支付
// 支付宝
"alipay_app": "APP", // 支付宝app支付
"alipay_mp": "JSAPI", // 支付宝小程序支付
"alipay_native": "NATIVE", // 支付宝扫码支付
"alipay_mweb": "NATIVE", // 支付宝外部浏览器支付
};
return obj[pay_type];
};
/**
* 给第三方服务器返回成功通知
*/
common.returnNotifySUCCESS = function(data) {
let { provider, provider_pay_type } = data;
if (provider === "wxpay") {
// 微信支付需返回 xml 格式的字符串
return {
mpserverlessComposedResponse: true,
statusCode: 200,
headers: {
'content-type': 'text/xml;charset=utf-8'
},
body: "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>"
};
} else if (provider === "alipay") {
// 支付宝支付直接返回 success 字符串
return {
mpserverlessComposedResponse: true,
statusCode: 200,
headers: {
'content-type': 'text/plain'
},
body: "success"
}
}
return "success";
};
// 获取异步通知的参数,并转成json对象
common.getNotifyData = function(data) {
let {
provider,
httpInfo
} = data;
let json = {};
let body = httpInfo.body;
if (httpInfo.isBase64Encoded) {
body = Buffer.from(body, 'base64').toString('utf-8');
}
if (provider === "wxpay") {
if (body.indexOf("<xml>") > -1) {
// 微信支付v2
json = common.parseXML(body);
} else {
// 微信支付v3
json = common.urlStringToJson(body);
}
} else if (provider === "alipay") {
// 支付宝支付
json = common.urlStringToJson(body);
}
return json;
};
// 简易版XML转Object只可在微信支付时使用不支持嵌套
common.parseXML = function(xml) {
const xmlReg = /<(?:xml|root).*?>([\s|\S]*)<\/(?:xml|root)>/
const str = xmlReg.exec(xml)[1]
const obj = {}
const nodeReg = /<(.*?)>(?:<!\[CDATA\[){0,1}(.*?)(?:\]\]>){0,1}<\/.*?>/g
let matches = null
// eslint-disable-next-line no-cond-assign
while ((matches = nodeReg.exec(str))) {
obj[matches[1]] = matches[2]
}
return obj
};
// url参数转json
common.urlStringToJson = function(str) {
let json = {};
if (str != "" && str != undefined && str != null) {
let arr = str.split("&");
for (let i = 0; i < arr.length; i++) {
let arrstr = arr[i].split("=");
let k = arrstr[0];
let v = arrstr[1];
json[k] = v;
}
}
return json;
};
const isSnakeCase = new RegExp('_(\\w)', 'g');
const isCamelCase = new RegExp('[A-Z]', 'g');
function parseObjectKeys(obj, type) {
let parserReg;
let parser;
switch (type) {
case 'snake2camel':
parser = common.snake2camel
parserReg = isSnakeCase
break
case 'camel2snake':
parser = common.camel2snake
parserReg = isCamelCase
break
}
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
if (parserReg.test(key)) {
const keyCopy = parser(key)
obj[keyCopy] = obj[key]
delete obj[key]
if (Object.prototype.toString.call((obj[keyCopy])) === '[object Object]') {
obj[keyCopy] = parseObjectKeys(obj[keyCopy], type)
} else if (Array.isArray(obj[keyCopy])) {
obj[keyCopy] = obj[keyCopy].map((item) => {
return parseObjectKeys(item, type)
})
}
}
}
}
return obj
}
common.snake2camel = function(value) {
return value.replace(isSnakeCase, (_, c) => (c ? c.toUpperCase() : ''))
}
common.camel2snake = function(value) {
return value.replace(isCamelCase, str => '_' + str.toLowerCase())
}
// 转驼峰
common.snake2camelJson = function(obj) {
return parseObjectKeys(obj, 'snake2camel');
};
// 转蛇形
common.camel2snakeJson = function(obj) {
return parseObjectKeys(obj, 'camel2snake');
};
module.exports = common;

View File

@@ -0,0 +1,82 @@
/**
* 加密模块
*/
/*
加密解密示例
const payCrypto = require('../libs/crypto.js'); // 获取加密服务(注意文件所在相对路径)
let ciphertext = { a:1,b:2 };
let encrypted = payCrypto.aes.encrypt({
data: ciphertext, // 待加密的原文
});
let decrypted = payCrypto.aes.decrypt({
data: encrypted, // 待解密的原文
});
// 最终解密得出 decrypted = { a:1,b:2 }
*/
const configCenter = require("uni-config-center");
const config = configCenter({ pluginId: 'uni-pay' }).requireFile('config.js');
const crypto = require("crypto");
var util = {};
util.aes = {};
/**
* aes加密
* @param {Object} data 待加密的原文
* @param {Object} key 密钥如不传自动取config
* 调用示例
let encrypted = crypto.aes.encrypt({
data: "", // 待加密的原文
});
*/
util.aes.encrypt = function(obj) {
let {
data, // 待加密的原文
key, // 密钥如不传自动取config
} = obj;
if (!key) key = config.notifyKey;
if (typeof data === "object") data = JSON.stringify(data);
const cipher = crypto.createCipher('aes192', key);
let encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex');
// encrypted 为加密后的内容
return encrypted;
};
/**
* aes解密
* @param {Object} data 待解密的原文
* @param {Object} key 密钥如不传自动取config
* 调用示例
let decrypted = crypto.aes.decrypt({
data: "", // 待解密的原文
});
*/
util.aes.decrypt = function(obj) {
let {
data, // 待解密的原文
key, // 密钥如不传自动取config
} = obj;
if (typeof data === "undefined") {
throw "待解密原文不能为空";
}
if (!key) key = config.notifyKey;
// 解密
let decrypted;
try {
const decipher = crypto.createDecipher('aes192', key);
decrypted = decipher.update(data, 'hex', 'utf8');
decrypted += decipher.final('utf8');
try {
decrypted = JSON.parse(decrypted);
} catch (err) {}
} catch (err) {
throw "解密失败";
}
// decrypted 为解密后的内容即最开始需要加密的原始数据文本data
return decrypted;
};
module.exports = util;

View File

@@ -0,0 +1,13 @@
const wxpay = require('./wxpay');
const alipay = require('./alipay');
const common = require('./common');
const qrcode = require('./qrcode'); // 此源码为npm i qrcode的压缩版本
const crypto = require('./crypto');
module.exports = {
wxpay,
alipay,
common,
qrcode,
crypto
};

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,82 @@
/**
* 微信支付相关函数
*/
var wxpay = {
async getOpenid(data) {
let {
provider_pay_type
} = data;
if (provider_pay_type === "jsapi") {
return this._getJsOpenid(data);
} else {
return this._getMpOpenid(data);
}
},
async _getMpOpenid(data) {
let {
config = {},
code,
provider_pay_type
} = data;
if (!config.appId) throw new Error("uni-pay配置wxpay.mp节点下的appId不能为空");
if (!config.secret) throw new Error("uni-pay配置wxpay.mp节点下的secret不能为空");
let res = await uniCloud.httpclient.request("https://api.weixin.qq.com/sns/jscode2session", {
method: 'GET',
data: {
appid: config.appId,
secret: config.secret,
js_code: code,
grant_type: "authorization_code"
},
contentType: 'json', // 指定以application/json发送data内的数据
dataType: 'json' // 指定返回值为json格式自动进行parse
});
if (res.data && !res.data.errcode && res.data.openid) {
return {
errCode: 0,
errMsg: 'ok',
openid: res.data.openid,
unionid: res.data.unionid,
}
} else {
return {
errCode: -1,
errMsg: res.data.errmsg
}
}
},
async _getJsOpenid(data) {
let {
config = {},
code,
provider_pay_type
} = data;
if (!config.appId) throw new Error("uni-pay配置wxpay.jsapi节点下的appId不能为空");
if (!config.secret) throw new Error("uni-pay配置wxpay.jsapi节点下的secret不能为空");
let res = await uniCloud.httpclient.request("https://api.weixin.qq.com/sns/oauth2/access_token", {
method: 'GET',
data: {
appid: config.appId,
secret: config.secret,
code: code,
grant_type: "authorization_code"
},
contentType: 'json', // 指定以application/json发送data内的数据
dataType: 'json' // 指定返回值为json格式自动进行parse
});
if (res.data && !res.data.errcode && res.data.openid) {
return {
errCode: 0,
errMsg: 'ok',
openid: res.data.openid,
unionid: res.data.unionid,
}
} else {
return {
errCode: -1,
errMsg: res.data.errmsg
}
}
}
};
module.exports = wxpay;

View File

@@ -0,0 +1,50 @@
/**
* 权限验证中间件,一般情况下,无需修改此处的代码
*/
const methodPermission = require('../config/permission');
const { ERROR } = require('../common/error');
function isAccessAllowed(user = {}, setting) {
const {
role: userRole = [],
permission: userPermission = []
} = user
const {
role: settingRole = [],
permission: settingPermission = []
} = setting
if (userRole.includes('admin')) {
return;
}
if (settingRole.length > 0 && settingRole.every(item => !userRole.includes(item))) {
throw { errCode: ERROR[50403] };
}
if (settingPermission.length > 0 && settingPermission.every(item => !userPermission.includes(item))) {
throw { errCode: ERROR[50403] };
}
}
module.exports = async function() {
const methodName = this.getMethodName();
if (!(methodName in methodPermission)) {
return;
}
const {
auth,
role,
permission
} = methodPermission[methodName];
if (auth || role || permission) {
await this.middleware.auth();
}
if (role && role.length === 0) {
throw new Error('[AccessControl]Empty role array is not supported');
}
if (permission && permission.length === 0) {
throw new Error('[AccessControl]Empty permission array is not supported');
}
return isAccessAllowed(this.authInfo, {
role,
permission
})
}

View File

@@ -0,0 +1,21 @@
module.exports = async function(key = true) {
if (this.authInfo) { // 多次执行auth时如果第一次成功后续不再执行
return;
}
const token = this.getUniIdToken();
const payload = await this.uniIdCommon.checkToken(token);
if (payload.errCode) {
if (key) {
throw payload;
} else {
return;
}
}
this.authInfo = payload;
if (payload.token && typeof this.response === "object") {
this.response.newToken = {
token: payload.token,
tokenExpired: payload.tokenExpired
}
}
}

View File

@@ -0,0 +1,7 @@
const accessControl = require("./access-control");
const auth = require("./auth");
module.exports = {
accessControl,
auth
}

View File

@@ -0,0 +1,78 @@
'use strict';
/**
* 此处建议只改下订单状态,保证能及时返回给第三方支付服务器成功状态
* 限制4秒内必须执行完全部的异步回调逻辑建议将消息发送、返佣、业绩结算等业务逻辑异步处理如用定时任务去处理这些异步逻辑
*/
const payCrypto = require('../libs/crypto.js'); // 获取加密服务
module.exports = async (obj) => {
let user_order_success = true;
let { data = {} } = obj;
let {
order_no,
out_trade_no,
total_fee
} = data; // uni-pay-orders 表内的数据均可获取到
// 此处写你自己的支付成功逻辑开始-----------------------------------------------------------
// 有三种方式
// 方式一:直接写数据库操作
// 方式二:使用 await uniCloud.callFunction 调用其他云函数或云对象,云对象则使用 uniCloud.importObject('云对象名称')来请求
// 方式三:使用 await uniCloud.httpclient.request 调用http接口地址
/*
// 方式二安全模式一(加密)
let encrypted = payCrypto.aes.encrypt({
data: data, // 待加密的原文
});
await uniCloud.callFunction({
name: "你的云函数名称",
data: {
encrypted, // 传输加密数据
},
});
*/
/*
// 方式二安全模式二(只传一个订单号 out_trade_no你自己的回调里查数据库表 uni-pay-orders 判断 status是否等于1来判断是否真的支付了
await uniCloud.callFunction({
name: "你的云函数名称",
data: {
out_trade_no, // 支付插件订单号
},
});
*/
/*
// 方式三安全模式一(加密)
let encrypted = payCrypto.aes.encrypt({
data: data, // 待加密的原文
});
await uniCloud.httpclient.request("你的服务器接口请求地址", {
method: "POST",
data: {
encrypted, // 传输加密数据
},
});
*/
/*
// 方式三安全模式二(只传一个订单号 out_trade_no你自己的回调里执行url请求来请求 uni-pay-co 云对象的 getOrder 接口来判断订单是否真的支付了)
await uniCloud.httpclient.request("你的服务器接口请求地址", {
method: "POST",
data: {
out_trade_no, // 支付插件订单号
},
});
*/
// 此处写你自己的支付成功逻辑结束-----------------------------------------------------------
// user_order_success = true 代表你自己的逻辑处理成功 返回 false 代表你自己的处理逻辑失败。
return user_order_success;
};

View File

@@ -0,0 +1,78 @@
'use strict';
/**
* 此处建议只改下订单状态,保证能及时返回给第三方支付服务器成功状态
* 限制4秒内必须执行完全部的异步回调逻辑建议将消息发送、返佣、业绩结算等业务逻辑异步处理如用定时任务去处理这些异步逻辑
*/
const payCrypto = require('../libs/crypto.js'); // 获取加密服务
module.exports = async (obj) => {
let user_order_success = true;
let { data = {} } = obj;
let {
order_no,
out_trade_no,
total_fee
} = data; // uni-pay-orders 表内的数据均可获取到
// 此处写你自己的支付成功逻辑开始-----------------------------------------------------------
// 有三种方式
// 方式一:直接写数据库操作
// 方式二:使用 await uniCloud.callFunction 调用其他云函数或云对象,云对象则使用 uniCloud.importObject('云对象名称')来请求
// 方式三:使用 await uniCloud.httpclient.request 调用http接口地址
/*
// 方式二安全模式一(加密)
let encrypted = payCrypto.aes.encrypt({
data: data, // 待加密的原文
});
await uniCloud.callFunction({
name: "你的云函数名称",
data: {
encrypted, // 传输加密数据
},
});
*/
/*
// 方式二安全模式二(只传一个订单号 out_trade_no你自己的回调里查数据库表 uni-pay-orders 判断 status是否等于1来判断是否真的支付了
await uniCloud.callFunction({
name: "你的云函数名称",
data: {
out_trade_no, // 支付插件订单号
},
});
*/
/*
// 方式三安全模式一(加密)
let encrypted = payCrypto.aes.encrypt({
data: data, // 待加密的原文
});
await uniCloud.httpclient.request("你的服务器接口请求地址", {
method: "POST",
data: {
encrypted, // 传输加密数据
},
});
*/
/*
// 方式三安全模式二(只传一个订单号 out_trade_no你自己的回调里执行url请求来请求 uni-pay-co 云对象的 getOrder 接口来判断订单是否真的支付了)
await uniCloud.httpclient.request("你的服务器接口请求地址", {
method: "POST",
data: {
out_trade_no, // 支付插件订单号
},
});
*/
// 此处写你自己的支付成功逻辑结束-----------------------------------------------------------
// user_order_success = true 代表你自己的逻辑处理成功 返回 false 代表你自己的处理逻辑失败。
return user_order_success;
};

View File

@@ -0,0 +1,51 @@
# 异步通知回调执行逻辑目录
**提示:异步通知写在 `uni-pay-co/notify` 目录下在此目录新建2个js文件分别为 `recharge.js`、`goods.js` 文件同时复制以下代码要你新建的2个js文件里。**
**注意**
为什么要你自己创建.js文件而不是插件默认给你创建好这是因为后面当插件更新时你写的代码会被插件更新的代码覆盖一键合并功能因此只要插件这里没有文件而是你自己新建的文件那么插件更新时不会覆盖你自己新建的文件内的代码。
```js
'use strict';
/**
* 此处建议只改下订单状态,保证能及时返回给第三方支付服务器成功状态
* 且where条件可以增加判断服务器推送过来的金额和订单表中订单需支付金额是否一致
* 将消息发送、返佣、业绩结算等业务逻辑异步处理(写入异步任务队列表)
* 如开启定时器每隔5秒触发一次处理订单
*/
module.exports = async (obj) => {
let user_order_success = true;
let { data = {} } = obj;
let {
order_no,
out_trade_no,
total_fee
} = data; // uni-pay-orders 表内的数据均可获取到
// 此处写你自己的支付成功逻辑开始-----------------------------------------------------------
// 有三种方式
// 方式一:直接写数据库操作
// 方式二:使用 await uniCloud.callFunction 调用其他云函数
// 方式三:使用 await uniCloud.httpclient.request 调用http接口地址
// 此处写你自己的支付成功逻辑结束-----------------------------------------------------------
// user_order_success = true 代表你自己的逻辑处理成功 返回 false 代表你自己的处理逻辑失败。
return user_order_success;
};
```
其中
- `recharge.js` 内可以写余额充值相关的回调逻辑
- `goods.js` 内可以写商品订单付款成功后的回调逻辑
最终调用哪个回调逻辑是根据你创建支付订单时,`type` 参数填的什么,`type` 如果填 `recharge` 则支付成功后就会执行 `recharge.js` 内的代码逻辑。
即前端调用支付时的这个 `type` 参数
```js
// 打开支付收银台
this.$refs.uniPay.open({
type: "recharge", // 支付回调类型 recharge 代表余额充值(当然你可以自己自定义)
});
```

View File

@@ -0,0 +1,17 @@
{
"name": "uni-pay-co",
"dependencies": {
"uni-config-center": "file:../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center",
"uni-id-common": "file:../../../../uni-id-common/uniCloud/cloudfunctions/common/uni-id-common",
"uni-pay": "file:../common/uni-pay"
},
"extensions": {},
"cloudfunction-config": {
"concurrency": 1,
"memorySize": 128,
"path": "/uni-pay-co",
"timeout": 60,
"triggers": [],
"runtime": "Nodejs8"
}
}

View File

@@ -0,0 +1,5 @@
const pay = require("./pay");
module.exports = {
pay
};

View File

@@ -0,0 +1,865 @@
/**
* uni-pay-co 统一支付服务实现
*/
const crypto = require("crypto");
const uniPay = require("uni-pay");
const configCenter = require("uni-config-center");
const config = configCenter({ pluginId: 'uni-pay' }).requireFile('config.js');
const dao = require('../dao');
const libs = require('../libs');
const { UniCloudError, isUniPayError, ERROR } = require('../common/error')
const db = uniCloud.database();
const _ = db.command;
const notifyPath = "/payNotify/";
class service {
constructor(obj) {
}
/**
* 获取支付插件的完整配置
*/
getConfig() {
return config;
}
/**
* 支付成功 - 异步通知
*/
async paymentNotify(data = {}) {
let {
httpInfo,
} = data;
let path = httpInfo.path;
let pay_type = path.substring(notifyPath.length);
let provider = pay_type.split("-")[0]; // 获取支付供应商
let provider_pay_type = pay_type.split("-")[1]; // 获取支付方式
let original_data = libs.common.getNotifyData({ httpInfo, provider }); // 获取原始回调数据
// 初始化uniPayInstance
let uniPayInstance = await this.initUniPayInstance({ provider, provider_pay_type });
let notifyType = await uniPayInstance.checkNotifyType(httpInfo);
if (notifyType !== "payment") {
// 由于支付宝部分退款会触发支付成功的回调,但同时签名验证是算未通过的,为了避免支付宝重复推送,这里可以直接返回成功告知支付宝服务器,不用再推送过来了。
return libs.common.returnNotifySUCCESS({ provider, provider_pay_type });
}
let verifyResult = await uniPayInstance.verifyPaymentNotify(httpInfo);
if (!verifyResult) {
console.log('---------!签名验证未通过!---------');
console.log('---------!签名验证未通过!---------');
console.log('---------!签名验证未通过!---------');
return {}
}
console.log('---------!签名验证通过!---------');
verifyResult = JSON.parse(JSON.stringify(verifyResult)); // 这一句代码有用,请勿删除。
let {
outTradeNo,
totalFee,
transactionId,
resultCode, // 微信支付v2和支付宝支付判断成功的字段
openid,
appId,
tradeState, // 微信支付v3判断支付成功的字段
} = verifyResult;
//console.log('verifyResult: ', verifyResult)
if (resultCode == "SUCCESS" || tradeState === "SUCCESS") {
let time = Date.now();
let payOrderInfo = await dao.uniPayOrders.updateAndReturn({
whereJson: {
status: 0, // status:0 为必须条件,防止重复推送时的错误
out_trade_no: outTradeNo, // 商户订单号
},
dataJson: {
status: 1, // 设置为已付款
transaction_id: transactionId, // 第三方支付单号
pay_date: time,
notify_date: time,
openid,
original_data,
}
});
//console.log('payOrderInfo: ', payOrderInfo)
if (payOrderInfo) {
// 只有首次推送才执行用户自己的逻辑处理。
// 用户自己的逻辑处理 开始-----------------------------------------------------------
let userOrderSuccess = false;
let orderPaySuccess;
try {
// 加载自定义异步回调函数
orderPaySuccess = require(`../notify/${payOrderInfo.type}`);
} catch (err) {
console.log(err);
}
if (typeof orderPaySuccess === "function") {
console.log('用户自己的回调逻辑 - 开始执行');
userOrderSuccess = await orderPaySuccess({
verifyResult,
data: payOrderInfo,
});
console.log('用户自己的回调逻辑 - 执行完成');
}
console.log('userOrderSuccess', userOrderSuccess);
// 用户自己的逻辑处理 结束-----------------------------------------------------------
await dao.uniPayOrders.updateAndReturn({
whereJson: {
status: 1,
out_trade_no: outTradeNo,
},
dataJson: {
user_order_success: userOrderSuccess,
}
});
} else {
console.log('---------!注意:本次回调非首次回调,已被插件拦截,插件不会执行你的回调函数!---------');
console.log('---------!注意:本次回调非首次回调,已被插件拦截,插件不会执行你的回调函数!---------');
console.log('---------!注意:本次回调非首次回调,已被插件拦截,插件不会执行你的回调函数!---------');
console.log('verifyResult:', verifyResult);
}
} else {
console.log('verifyResult:', verifyResult);
}
return libs.common.returnNotifySUCCESS({ provider, provider_pay_type });
}
/**
* 统一支付 - 创建支付订单
*/
async createOrder(data = {}) {
let {
provider, // 支付供应商
total_fee, // 支付金额
user_id, // 用户user_id统计需要
openid, // 用户openid
order_no, // 订单号
out_trade_no, // 支付插件订单号
description, // 订单描述
type, // 回调类型
qr_code, // 是否强制使用扫码支付
clientInfo, // 客户端信息
cloudInfo, // 云端信息
custom, // 自定义参数(不会发送给第三方支付服务器)
other, // 其他请求参数(会发送给第三方支付服务器)
} = data;
let subject = description;
let body = description;
if (!out_trade_no) out_trade_no = libs.common.createOrderNo();
if (!order_no || typeof order_no !== "string") {
throw { errCode: ERROR[51003] };
}
if (!type || typeof type !== "string") {
throw { errCode: ERROR[51004] };
}
if (typeof total_fee !== "number" || total_fee <= 0 || total_fee % 1 !== 0) {
throw { errCode: ERROR[51005] };
}
if (!description || typeof description !== "string") {
throw { errCode: ERROR[51006] };
}
if (!provider || typeof provider !== "string") {
throw { errCode: ERROR[51007] };
}
if (!clientInfo) {
throw { errCode: ERROR[51008] };
}
if (!cloudInfo) {
throw { errCode: ERROR[51009] };
}
let res = { errCode: 0, errMsg: 'ok', order_no, out_trade_no, provider };
let {
clientIP: client_ip,
userAgent: ua,
appId: appid,
deviceId: device_id,
platform
} = clientInfo;
let {
spaceId, // 服务空间ID
} = cloudInfo;
let {
notifyUrl = {}
} = config;
// 业务逻辑开始-----------------------------------------------------------
let currentNotifyUrl = notifyUrl[spaceId] || notifyUrl["default"]; // 异步回调地址
if (!currentNotifyUrl || currentNotifyUrl.indexOf("http") !== 0) {
throw { errCode: ERROR[52002] };
}
platform = libs.common.getPlatform(platform);
// 如果需要二维码支付模式则清空下openid
if (qr_code) {
openid = undefined;
res.qr_code = qr_code;
}
// 获取并自动匹配支付供应商的支付类型
let provider_pay_type = libs.common.getProviderPayType({
platform,
provider,
ua,
qr_code
});
res.provider_pay_type = provider_pay_type;
// 拼接实际异步回调地址
let finalNotifyUrl = `${currentNotifyUrl}${notifyPath}${provider}-${provider_pay_type}`;
// 获取uniPay交易类型
let tradeType = libs.common.getTradeType({ provider, provider_pay_type });
let uniPayConifg = await this.getUniPayConfig({ provider, provider_pay_type });
// 初始化uniPayInstance
let uniPayInstance = await this.initUniPayInstance({ provider, provider_pay_type });
// 获取支付信息
let getOrderInfoParam = {
openid: openid,
subject: subject,
body: body,
outTradeNo: out_trade_no,
totalFee: total_fee,
notifyUrl: finalNotifyUrl,
tradeType: tradeType
};
if (provider === "wxpay" && provider_pay_type === "mweb") {
getOrderInfoParam.spbillCreateIp = client_ip;
if (uniPayConifg.version !== 3) {
// v2版本
getOrderInfoParam.sceneInfo = uniPayConifg.sceneInfo;
} else {
// v3版本特殊处理
getOrderInfoParam.sceneInfo = JSON.parse(JSON.stringify(uniPayConifg.sceneInfo));
if (getOrderInfoParam.sceneInfo.h5_info.wap_url) {
getOrderInfoParam.sceneInfo.h5_info.app_url = getOrderInfoParam.sceneInfo.h5_info.wap_url;
delete getOrderInfoParam.sceneInfo.h5_info.wap_url;
}
if (getOrderInfoParam.sceneInfo.h5_info.wap_name) {
getOrderInfoParam.sceneInfo.h5_info.app_name = getOrderInfoParam.sceneInfo.h5_info.wap_name;
delete getOrderInfoParam.sceneInfo.h5_info.wap_name;
}
}
}
try {
// 如果是苹果内购不需要执行uniPayInstance.getOrderInfo等操作
if (provider !== "appleiap") {
// 第三方支付服务器返回的订单信息
let orderInfo;
if (other) {
// other 内的键名转驼峰
other = libs.common.snake2camelJson(other);
getOrderInfoParam = Object.assign(getOrderInfoParam, other);
}
getOrderInfoParam = JSON.parse(JSON.stringify(getOrderInfoParam)); // 此为去除undefined的参数
orderInfo = await uniPayInstance.getOrderInfo(getOrderInfoParam);
if (qr_code && orderInfo.codeUrl) {
res.qr_code_image = await libs.qrcode.toDataURL(orderInfo.codeUrl, {
type: "image/png",
width: 200,
margin: 1,
scale: 1,
color: {
dark: "#000000",
light: "#ffffff",
},
errorCorrectionLevel: "Q",
quality: 1
});
}
// 支付宝支付参数特殊处理
if (provider === "alipay") {
if (typeof orderInfo === "object" && orderInfo.code && orderInfo.code !== "10000") {
res.errCode = orderInfo.code;
res.errMsg = orderInfo.subMsg;
}
}
res.order = orderInfo;
}
} catch (err) {
let errMsg = err.errorMessage || err.message;
console.error("data: ", data);
console.error("getOrderInfoParam: ", getOrderInfoParam);
console.error("err: ", err);
console.error("errMsg: ", errMsg);
throw { errCode: ERROR[53001], errMsg };
}
// 尝试获取下订单信息
let payOrderInfo = await dao.uniPayOrders.find({
order_no,
out_trade_no
});
let create_date = Date.now();
// 如果订单不存在,则添加
if (!payOrderInfo) {
// 添加数据库(数据库的out_trade_no字段需设置为唯一索引)
let stat_platform = clientInfo.platform;
if (stat_platform === "app") {
stat_platform = clientInfo.os;
}
let nickname;
if (user_id) {
// 获取nickname冗余昵称
let userInfo = await dao.uniIdUsers.findById(user_id);
if (userInfo) nickname = userInfo.nickname;
}
await dao.uniPayOrders.add({
provider,
provider_pay_type,
uni_platform: platform,
status: 0,
type,
order_no,
out_trade_no,
user_id,
nickname,
device_id,
client_ip,
openid,
description,
total_fee,
refund_fee: 0,
refund_count: 0,
provider_appid: uniPayConifg.appId,
appid,
custom,
create_date,
stat_data: {
platform: stat_platform,
app_version: clientInfo.appVersion,
app_version_code: clientInfo.appVersionCode,
app_wgt_version: clientInfo.appWgtVersion,
os: clientInfo.os,
ua: clientInfo.ua,
channel: clientInfo.channel ? clientInfo.channel : String(clientInfo.scene),
scene: clientInfo.scene
}
});
} else {
// 如果订单已经存在,则修改下支付方式(用户可能先点微信支付,未付款,又点了支付宝支付)
await dao.uniPayOrders.updateById(payOrderInfo._id, {
provider,
provider_pay_type,
});
}
// 自动删除3天前的订单未付款订单
// await dao.uniPayOrders.deleteExpPayOrders();
// 业务逻辑结束-----------------------------------------------------------
return res;
}
/**
* 统一支付结果查询
* @description 根据商户订单号或者平台订单号查询订单信息,主要用于未接收到支付通知时可以使用此接口进行支付结果验证
*/
async getOrder(data = {}) {
let {
out_trade_no, // 支付插件订单号
transaction_id, // 支付平台的交易单号
await_notify = false, // 是否需要等待异步通知执行完成才返回前端支付结果
} = data;
let res = { errCode: 0, errMsg: 'ok' };
// 业务逻辑开始-----------------------------------------------------------
if (!out_trade_no && !transaction_id) {
throw { errCode: ERROR[51010] };
}
let payOrderInfo;
if (transaction_id) {
payOrderInfo = await dao.uniPayOrders.find({
transaction_id
});
} else if (out_trade_no) {
payOrderInfo = await dao.uniPayOrders.find({
out_trade_no
});
}
if (!payOrderInfo) {
throw { errCode: ERROR[52001] };
}
// 初始化uniPayInstance
let uniPayInstance = await this.initUniPayInstance(payOrderInfo);
let orderQueryJson = {};
if (out_trade_no) {
orderQueryJson.outTradeNo = out_trade_no;
} else {
orderQueryJson.transactionId = transaction_id;
}
let queryRes = await uniPayInstance.orderQuery(orderQueryJson);
if (queryRes.tradeState === 'SUCCESS' || queryRes.tradeState === 'FINISHED') {
if (typeof payOrderInfo.user_order_success == "undefined" && await_notify) {
let whileTime = 0; // 当前循环已执行的时间(毫秒)
let whileInterval = 500; // 每次循环间隔时间(毫秒)
let maxTime = 5000; // 循环执行时间超过此值则退出循环(毫秒)
while (typeof payOrderInfo.user_order_success == "undefined" && whileTime <= maxTime) {
await libs.common.sleep(whileInterval);
whileTime += whileInterval;
payOrderInfo = await dao.uniPayOrders.find({
out_trade_no
});
}
}
res = {
errCode: 0,
errMsg: "ok",
has_paid: true, // 标记用户是否已付款成功(此参数只能表示用户确实付款了,但系统的异步回调逻辑可能还未执行完成)
out_trade_no, // 支付插件订单号
transaction_id, // 支付平台订单号
status: payOrderInfo.status, // 标记当前支付订单状态 -1已关闭 0未支付 1已支付 2已部分退款 3已全额退款
user_order_success: payOrderInfo.user_order_success, // 用户异步通知逻辑是否全部执行完成且无异常建议前端通过此参数是否为true来判断是否支付成功
pay_order: payOrderInfo,
}
} else {
let errMsg = queryRes.tradeStateDesc || "未支付或已退款";
if (errMsg.indexOf("订单发生过退款") > -1) {
errMsg = "订单已退款";
}
res = {
errCode: -1,
errMsg: errMsg,
has_paid: false,
out_trade_no, // 支付插件订单号
transaction_id, // 支付平台订单号
}
}
// 业务逻辑结束-----------------------------------------------------------
return res;
}
/**
* 统一退款
* @description 当交易发生之后一段时间内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付款退还给买家。
*/
async refund(data = {}) {
let {
out_trade_no, // 插件支付单号
out_refund_no, // 退款单号(若不传,则自动生成)
refund_desc = "用户申请退款",
refund_fee: myRefundFee,
refund_fee_type = "CNY"
} = data;
let res = { errCode: 0, errMsg: 'ok' };
// 业务逻辑开始-----------------------------------------------------------
if (!out_trade_no) {
throw { errCode: ERROR[51001] };
}
let payOrderInfo = await dao.uniPayOrders.find({
out_trade_no
});
if (!payOrderInfo) {
throw { errCode: ERROR[52001] };
}
let refund_count = payOrderInfo.refund_count || 0;
refund_count++;
// 生成退款订单号
let outRefundNo = out_refund_no ? out_refund_no : `${out_trade_no}-${refund_count}`;
// 订单总金额
let totalFee = payOrderInfo.total_fee;
// 退款总金额
let refundFee = myRefundFee || totalFee;
let provider = payOrderInfo.provider;
let uniPayConifg = await this.getUniPayConfig(payOrderInfo);
let uniPayInstance = await this.initUniPayInstance(payOrderInfo);
console.log(`---- ${out_trade_no} -- ${outRefundNo} -- ${totalFee/100} -- ${refundFee/100}`)
// 退款操作
try {
res.result = await uniPayInstance.refund({
outTradeNo: out_trade_no,
outRefundNo,
totalFee,
refundFee,
refundDesc: refund_desc,
refundFeeType: refund_fee_type
});
} catch (err) {
console.error(err);
let errMsg = err.message;
if (errMsg && errMsg.indexOf("verify failure") > -1) {
throw { errCode: ERROR[53005] };
}
return { errCode: -1, errMsg: errMsg, err }
}
if (res.result.refundFee) {
res.errCode = 0;
res.errMsg = "ok";
// 修改数据库
try {
// 修改订单状态
payOrderInfo = await dao.uniPayOrders.updateAndReturn({
whereJson: {
_id: payOrderInfo._id
},
dataJson: {
status: 2,
refund_fee: _.inc(refundFee),
refund_count: refund_count,
// 记录每次的退款详情
refund_list: _.unshift({
refund_date: Date.now(),
refund_fee: refundFee,
out_refund_no: outRefundNo,
refund_desc
})
}
});
if (payOrderInfo && payOrderInfo.refund_fee >= payOrderInfo.total_fee) {
// 修改订单状态为已全额退款
await dao.uniPayOrders.updateById(payOrderInfo._id, {
status: 3,
refund_fee: payOrderInfo.total_fee,
});
}
} catch (err) {
console.error(err);
}
} else {
throw { errCode: ERROR[53002] };
}
// 业务逻辑结束-----------------------------------------------------------
return res;
}
/**
* 查询退款(查询退款情况)
* @description 提交退款申请后,通过调用该接口查询退款状态。
*/
async getRefund(data = {}) {
let {
out_trade_no, // 插件支付单号
} = data;
if (!out_trade_no) {
throw { errCode: ERROR[51001] };
}
let payOrderInfo = await dao.uniPayOrders.find({
out_trade_no
});
if (!payOrderInfo) {
throw { errCode: ERROR[52001] };
}
let provider = payOrderInfo.provider;
let uniPayInstance = await this.initUniPayInstance(payOrderInfo);
let queryRes;
try {
let refundQueryJson = {
outTradeNo: out_trade_no,
outRefundNo: payOrderInfo.refund_list[0].out_refund_no
};
queryRes = await uniPayInstance.refundQuery(refundQueryJson);
} catch (err) {
throw { errCode: ERROR[53003], errMsg: err.errMsg };
}
let orderInfo = {
total_fee: payOrderInfo.total_fee,
refund_fee: payOrderInfo.refund_fee,
refund_count: payOrderInfo.refund_count,
refund_list: payOrderInfo.refund_list,
provider: payOrderInfo.provider,
provider_pay_type: payOrderInfo.provider_pay_type,
status: payOrderInfo.status,
type: payOrderInfo.type,
out_trade_no: payOrderInfo.out_trade_no,
transaction_id: payOrderInfo.transaction_id,
};
if (queryRes.refundFee > 0) {
let msg = "ok";
if (payOrderInfo.refund_list && payOrderInfo.refund_list.length > 0) {
msg = `合计退款 ${payOrderInfo.refund_fee/100}\r\n`;
for (let i in payOrderInfo.refund_list) {
let item = payOrderInfo.refund_list[i];
let index = Number(i) + 1;
let timeStr = libs.common.timeFormat(item.refund_date, "yyyy-MM-dd hh:mm:ss");
msg += `${index}${timeStr} \r\n退款 ${item.refund_fee/100} \r\n`;
}
}
return {
errCode: 0,
errMsg: msg,
pay_order: orderInfo,
result: queryRes
}
} else {
throw { errCode: ERROR[53003] };
}
}
/**
* 关闭订单
* @description 用于交易创建后,用户在一定时间内未进行支付,可调用该接口直接将未付款的交易进行关闭,避免重复支付。
* 注意
* 微信支付:订单生成后不能马上调用关单接口,最短调用时间间隔为 5 分钟。
*/
async closeOrder(data = {}) {
let {
out_trade_no, // 插件支付单号
} = data;
if (!out_trade_no) {
throw { errCode: ERROR[51001] };
}
let payOrderInfo = await dao.uniPayOrders.find({
out_trade_no
});
if (!payOrderInfo) {
throw { errCode: ERROR[52001] };
}
let { provider } = payOrderInfo;
let uniPayInstance = await this.initUniPayInstance(payOrderInfo);
let closeOrderRes = await uniPayInstance.closeOrder({
outTradeNo: out_trade_no
});
if ((provider === "wxpay" && closeOrderRes.resultCode === "SUCCESS") || (provider === "alipay" && closeOrderRes.code ===
"10000")) {
// 修改订单状态为已取消
await dao.uniPayOrders.update({
whereJson: {
_id: payOrderInfo._id,
status: 0
},
dataJson: {
status: -1,
cancel_date: Date.now()
}
});
return {
errCode: 0,
errMsg: "订单关闭成功",
result: closeOrderRes
}
} else {
throw { errCode: ERROR[53004] };
}
}
/**
* 根据code获取openid
*/
async getOpenid(data = {}) {
let {
provider, // 支付供应商
code, // 用户登录获取的code
clientInfo, // 客户端环境
} = data;
if (!code) {
throw { errCode: ERROR[51002] };
}
let { platform, ua } = clientInfo;
// 获取并自动匹配支付供应商的支付类型
let provider_pay_type = libs.common.getProviderPayType({
provider,
platform,
ua
});
let uniPayConifg = await this.getUniPayConfig({ provider, provider_pay_type });
if (provider === "wxpay") {
return await libs.wxpay.getOpenid({
config: uniPayConifg,
code,
provider_pay_type,
});
} else if (provider === "alipay") {
return await libs.alipay.getOpenid({
config: uniPayConifg,
code,
});
}
}
/**
* 获取支持的支付方式
* let payTypes = await service.pay.getPayProviderFromCloud();
*/
async getPayProviderFromCloud() {
let wxpay = config.wxpay && config.wxpay.enable ? true : false;
let alipay = config.alipay && config.alipay.enable ? true : false;
let provider = [];
if (wxpay) provider.push("wxpay");
if (alipay) provider.push("alipay");
return {
errCode: 0,
errMsg: "ok",
wxpay,
alipay,
provider
};
}
/**
* 验证iosIap苹果内购支付凭据
* let payTypes = await service.pay.verifyReceiptFromAppleiap();
*/
async verifyReceiptFromAppleiap(data) {
let {
out_trade_no,
transaction_receipt,
transaction_identifier,
} = data;
if (!out_trade_no) {
throw { errCode: ERROR[51001] };
}
// 初始化uniPayInstance
let uniPayInstance = await this.initUniPayInstance({ provider: "appleiap", provider_pay_type: "app" });
let verifyReceiptRes = await uniPayInstance.verifyReceipt({
receiptData: transaction_receipt
});
let transaction_id;
let userOrderSuccess = false;
let pay_date;
if (verifyReceiptRes.tradeState !== "SUCCESS") {
return {
errCode: -1,
errMsg: "未支付"
};
}
// 支付成功
pay_date = verifyReceiptRes.receipt.receipt_creation_date_ms;
let inApp = verifyReceiptRes.receipt.in_app[0];
let quantity = inApp.quantity; // 购买数量
let product_id = inApp.product_id; // 对应的内购产品id
transaction_id = inApp.transaction_id; // 本次交易id
if (transaction_identifier !== transaction_id) {
// 校验不通过
return {
errCode: -1,
errMsg: "ios内购凭据校验不通过"
};
}
if ((Date.now() - 1000 * 3600 * 24) > pay_date) {
// 订单已超24小时不做处理通知前端直接关闭订单。
return {
errCode: 0,
errMsg: "ok"
};
}
// 查询该transaction_id是否使用过如果已使用则不做处理通知前端直接关闭订单。
let findOrderInfo = await dao.uniPayOrders.find({
transaction_id,
});
if (findOrderInfo) {
return {
errCode: 0,
errMsg: "ok"
};
}
// 否则,执行用户回调
// 用户自己的逻辑处理 开始-----------------------------------------------------------
let orderPaySuccess;
let payOrderInfo;
try {
// 加载自定义异步回调函数
orderPaySuccess = require(`../notify/appleiap`);
} catch (err) {
console.log(err);
}
if (typeof orderPaySuccess === "function") {
payOrderInfo = await dao.uniPayOrders.updateAndReturn({
whereJson: {
status: 0, // status:0 为必须条件,防止重复推送时的错误
out_trade_no: out_trade_no, // 商户订单号
},
dataJson: {
status: 1, // 设置为已付款
transaction_id: transaction_id, // 第三方支付单号
pay_date: pay_date,
notify_date: pay_date,
original_data: verifyReceiptRes
}
});
console.log('用户自己的回调逻辑 - 开始执行');
userOrderSuccess = await orderPaySuccess({
verifyResult: verifyReceiptRes,
data: payOrderInfo,
});
console.log('用户自己的回调逻辑 - 执行完成');
await dao.uniPayOrders.updateAndReturn({
whereJson: {
status: 1,
out_trade_no,
},
dataJson: {
user_order_success: userOrderSuccess,
}
});
} else {
payOrderInfo = await dao.uniPayOrders.find({
out_trade_no,
});
}
console.log('userOrderSuccess', userOrderSuccess);
// 用户自己的逻辑处理 结束-----------------------------------------------------------
//console.log('verifyReceiptRes: ', verifyReceiptRes);
return {
errCode: 0,
errMsg: "ok",
has_paid: true, // 标记用户是否已付款成功(此参数只能表示用户确实付款了,但系统的异步回调逻辑可能还未执行完成)
out_trade_no, // 支付插件订单号
transaction_id, // 支付平台订单号
status: payOrderInfo.status, // 标记当前支付订单状态 -1已关闭 0未支付 1已支付 2已部分退款 3已全额退款
user_order_success: payOrderInfo.user_order_success, // 用户异步通知逻辑是否全部执行完成且无异常建议前端通过此参数是否为true来判断是否支付成功
pay_order: payOrderInfo,
};
}
/**
* 获取对应支付配置
* let uniPayConifg = await this.getUniPayConfig({ provider, provider_pay_type });
*/
async getUniPayConfig(data = {}) {
let {
provider,
provider_pay_type,
} = data;
if (config && config[provider] && config[provider][provider_pay_type]) {
let uniPayConfig = config[provider][provider_pay_type];
if (!uniPayConfig.appId && provider !== "appleiap") {
throw new Error(`uni-pay配置${provider}.${provider_pay_type}节点下的appId不能为空`);
}
return uniPayConfig;
} else {
throw new Error(`${provider}_${provider_pay_type} : 商户支付配置错误`);
}
}
/**
* 初始化uniPayInstance
* let uniPayInstance = await service.pay.initUniPayInstance({ provider, provider_pay_type });
*/
async initUniPayInstance(data = {}) {
let {
provider,
} = data;
let uniPayConifg = await this.getUniPayConfig(data);
let uniPayInstance;
if (provider === "wxpay") {
// 微信
if (uniPayConifg.version === 3) {
uniPayInstance = uniPay.initWeixinV3(uniPayConifg);
} else {
uniPayInstance = uniPay.initWeixin(uniPayConifg);
}
} else if (provider === "alipay") {
// 支付宝
uniPayInstance = uniPay.initAlipay(uniPayConifg);
} else if (provider === "appleiap") {
// ios内购
uniPayInstance = uniPay.initAppleIapPayment(uniPayConifg);
} else {
throw new Error(`${pay_type} : 不支持的支付方式`);
}
return uniPayInstance;
}
}
module.exports = new service();

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,38 @@
{
"uni-id-users": {},
"uni-pay-orders": {
"data": [],
"index": [{
"IndexName": "order_no",
"MgoKeySchema": { "MgoIndexKeys": [{ "Name": "order_no", "Direction": "1" }], "MgoIsUnique": false }
},
{
"IndexName": "out_trade_no",
"MgoKeySchema": { "MgoIndexKeys": [{ "Name": "out_trade_no", "Direction": "1" }], "MgoIsUnique": true }
},
{
"IndexName": "transaction_id",
"MgoKeySchema": { "MgoIndexKeys": [{ "Name": "transaction_id", "Direction": "1" }], "MgoIsUnique": false }
},
{
"IndexName": "create_date",
"MgoKeySchema": { "MgoIndexKeys": [{ "Name": "create_date", "Direction": "1" }], "MgoIsUnique": false }
},
{
"IndexName": "pay_date",
"MgoKeySchema": { "MgoIndexKeys": [{ "Name": "pay_date", "Direction": "1" }], "MgoIsUnique": false }
},
{
"IndexName": "total_fee",
"MgoKeySchema": { "MgoIndexKeys": [{ "Name": "total_fee", "Direction": "1" }], "MgoIsUnique": false }
},
{
"IndexName": "user_id",
"MgoKeySchema": { "MgoIndexKeys": [{ "Name": "user_id", "Direction": "1" }], "MgoIsUnique": false }
},
{
"IndexName": "appid",
"MgoKeySchema": { "MgoIndexKeys": [{ "Name": "appid", "Direction": "1" }], "MgoIsUnique": false }
}
]
}

View File

@@ -0,0 +1,64 @@
## 1.8.12022-12-01
- 修复 nvue 下 v-show 报错
## 1.8.02022-11-29
- 优化 主题样式
## 1.7.92022-04-02
- 修复 弹出层内部无法滚动的bug
## 1.7.82022-03-28
- 修复 小程序中高度错误的bug
## 1.7.72022-03-17
- 修复 快速调用open出现问题的Bug
## 1.7.62022-02-14
- 修复 safeArea 属性不能设置为false的bug
## 1.7.52022-01-19
- 修复 isMaskClick 失效的bug
## 1.7.42022-01-19
- 新增 cancelText \ confirmText 属性 ,可自定义文本
- 新增 maskBackgroundColor 属性 ,可以修改蒙版颜色
- 优化 maskClick属性 更新为 isMaskClick ,解决微信小程序警告的问题
## 1.7.32022-01-13
- 修复 设置 safeArea 属性不生效的bug
## 1.7.22021-11-26
- 优化 组件示例
## 1.7.12021-11-26
- 修复 vuedoc 文字错误
## 1.7.02021-11-19
- 优化 组件UI并提供设计资源详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-popup](https://uniapp.dcloud.io/component/uniui/uni-popup)
## 1.6.22021-08-24
- 新增 支持国际化
## 1.6.12021-07-30
- 优化 vue3下事件警告的问题
## 1.6.02021-07-13
- 组件兼容 vue3如何创建vue3项目详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.5.02021-06-23
- 新增 mask-click 遮罩层点击事件
## 1.4.52021-06-22
- 修复 nvue 平台中间弹出后点击内容再点击遮罩无法关闭的Bug
## 1.4.42021-06-18
- 修复 H5平台中间弹出后点击内容再点击遮罩无法关闭的Bug
## 1.4.32021-06-08
- 修复 错误的 watch 字段
- 修复 safeArea 属性不生效的问题
- 修复 点击内容再点击遮罩无法关闭的Bug
## 1.4.22021-05-12
- 新增 组件示例地址
## 1.4.12021-04-29
- 修复 组件内放置 input 、textarea 组件,无法聚焦的问题
## 1.4.0 2021-04-29
- 新增 type 属性的 left\right 值,支持左右弹出
- 新增 open(String:type) 方法参数 ,可以省略 type 属性 ,直接传入类型打开指定弹窗
- 新增 backgroundColor 属性,可定义主窗口背景色,默认不显示背景色
- 新增 safeArea 属性,是否适配底部安全区
- 修复 App\h5\微信小程序底部安全区占位不对的Bug
- 修复 App 端弹出等待的Bug
- 优化 提升低配设备性能,优化动画卡顿问题
- 优化 更简单的组件自定义方式
## 1.2.92021-02-05
- 优化 组件引用关系通过uni_modules引用组件
## 1.2.82021-02-05
- 调整为uni_modules目录规范
## 1.2.72021-02-05
- 调整为uni_modules目录规范
- 新增 支持 PC 端
- 新增 uni-popup-message 、uni-popup-dialog扩展组件支持 PC 端

View File

@@ -0,0 +1,45 @@
// #ifdef H5
export default {
name: 'Keypress',
props: {
disable: {
type: Boolean,
default: false
}
},
mounted () {
const keyNames = {
esc: ['Esc', 'Escape'],
tab: 'Tab',
enter: 'Enter',
space: [' ', 'Spacebar'],
up: ['Up', 'ArrowUp'],
left: ['Left', 'ArrowLeft'],
right: ['Right', 'ArrowRight'],
down: ['Down', 'ArrowDown'],
delete: ['Backspace', 'Delete', 'Del']
}
const listener = ($event) => {
if (this.disable) {
return
}
const keyName = Object.keys(keyNames).find(key => {
const keyName = $event.key
const value = keyNames[key]
return value === keyName || (Array.isArray(value) && value.includes(keyName))
})
if (keyName) {
// 避免和其他按键事件冲突
setTimeout(() => {
this.$emit(keyName, {})
}, 0)
}
}
document.addEventListener('keyup', listener)
this.$once('hook:beforeDestroy', () => {
document.removeEventListener('keyup', listener)
})
},
render: () => {}
}
// #endif

View File

@@ -0,0 +1,276 @@
<template>
<view class="uni-popup-dialog">
<view class="uni-dialog-title">
<text class="uni-dialog-title-text" :class="['uni-popup__'+dialogType]">{{titleText}}</text>
</view>
<view v-if="mode === 'base'" class="uni-dialog-content">
<slot>
<text class="uni-dialog-content-text">{{content}}</text>
</slot>
</view>
<view v-else class="uni-dialog-content">
<slot>
<input class="uni-dialog-input" v-model="val" type="text" :placeholder="placeholderText" :focus="focus" >
</slot>
</view>
<view class="uni-dialog-button-group">
<view class="uni-dialog-button" @click="closeDialog">
<text class="uni-dialog-button-text">{{closeText}}</text>
</view>
<view class="uni-dialog-button uni-border-left" @click="onOk">
<text class="uni-dialog-button-text uni-button-color">{{okText}}</text>
</view>
</view>
</view>
</template>
<script>
import popup from '../uni-popup/popup.js'
import {
initVueI18n
} from '@dcloudio/uni-i18n'
import messages from '../uni-popup/i18n/index.js'
const { t } = initVueI18n(messages)
/**
* PopUp 弹出层-对话框样式
* @description 弹出层-对话框样式
* @tutorial https://ext.dcloud.net.cn/plugin?id=329
* @property {String} value input 模式下的默认值
* @property {String} placeholder input 模式下输入提示
* @property {String} type = [success|warning|info|error] 主题样式
* @value success 成功
* @value warning 提示
* @value info 消息
* @value error 错误
* @property {String} mode = [base|input] 模式、
* @value base 基础对话框
* @value input 可输入对话框
* @property {String} content 对话框内容
* @property {Boolean} beforeClose 是否拦截取消事件
* @event {Function} confirm 点击确认按钮触发
* @event {Function} close 点击取消按钮触发
*/
export default {
name: "uniPopupDialog",
mixins: [popup],
emits:['confirm','close'],
props: {
value: {
type: [String, Number],
default: ''
},
placeholder: {
type: [String, Number],
default: ''
},
type: {
type: String,
default: 'error'
},
mode: {
type: String,
default: 'base'
},
title: {
type: String,
default: ''
},
content: {
type: String,
default: ''
},
beforeClose: {
type: Boolean,
default: false
},
cancelText:{
type: String,
default: ''
},
confirmText:{
type: String,
default: ''
}
},
data() {
return {
dialogType: 'error',
focus: false,
val: ""
}
},
computed: {
okText() {
return this.confirmText || t("uni-popup.ok")
},
closeText() {
return this.cancelText || t("uni-popup.cancel")
},
placeholderText() {
return this.placeholder || t("uni-popup.placeholder")
},
titleText() {
return this.title || t("uni-popup.title")
}
},
watch: {
type(val) {
this.dialogType = val
},
mode(val) {
if (val === 'input') {
this.dialogType = 'info'
}
},
value(val) {
this.val = val
}
},
created() {
// 对话框遮罩不可点击
this.popup.disableMask()
// this.popup.closeMask()
if (this.mode === 'input') {
this.dialogType = 'info'
this.val = this.value
} else {
this.dialogType = this.type
}
},
mounted() {
this.focus = true
},
methods: {
/**
* 点击确认按钮
*/
onOk() {
if (this.mode === 'input'){
this.$emit('confirm', this.val)
}else{
this.$emit('confirm')
}
if(this.beforeClose) return
this.popup.close()
},
/**
* 点击取消按钮
*/
closeDialog() {
this.$emit('close')
if(this.beforeClose) return
this.popup.close()
},
close(){
this.popup.close()
}
}
}
</script>
<style lang="scss" >
$uni-primary: #007aff !default;
$uni-success: #4cd964 !default;
$uni-warning: #f0ad4e !default;
$uni-error: #dd524d !default;
.uni-popup-dialog {
width: 300px;
border-radius: 11px;
background-color: #fff;
}
.uni-dialog-title {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: center;
padding-top: 25px;
}
.uni-dialog-title-text {
font-size: 16px;
font-weight: 500;
}
.uni-dialog-content {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: center;
align-items: center;
padding: 20px;
}
.uni-dialog-content-text {
font-size: 14px;
color: #6C6C6C;
}
.uni-dialog-button-group {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
border-top-color: #f5f5f5;
border-top-style: solid;
border-top-width: 1px;
}
.uni-dialog-button {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex: 1;
flex-direction: row;
justify-content: center;
align-items: center;
height: 45px;
}
.uni-border-left {
border-left-color: #f0f0f0;
border-left-style: solid;
border-left-width: 1px;
}
.uni-dialog-button-text {
font-size: 16px;
color: #333;
}
.uni-button-color {
color: $uni-primary;
}
.uni-dialog-input {
flex: 1;
font-size: 14px;
border: 1px #eee solid;
height: 40px;
padding: 0 10px;
border-radius: 5px;
color: #555;
}
.uni-popup__success {
color: $uni-success;
}
.uni-popup__warn {
color: $uni-warning;
}
.uni-popup__error {
color: $uni-error;
}
.uni-popup__info {
color: #909399;
}
</style>

View File

@@ -0,0 +1,143 @@
<template>
<view class="uni-popup-message">
<view class="uni-popup-message__box fixforpc-width" :class="'uni-popup__'+type">
<slot>
<text class="uni-popup-message-text" :class="'uni-popup__'+type+'-text'">{{message}}</text>
</slot>
</view>
</view>
</template>
<script>
import popup from '../uni-popup/popup.js'
/**
* PopUp 弹出层-消息提示
* @description 弹出层-消息提示
* @tutorial https://ext.dcloud.net.cn/plugin?id=329
* @property {String} type = [success|warning|info|error] 主题样式
* @value success 成功
* @value warning 提示
* @value info 消息
* @value error 错误
* @property {String} message 消息提示文字
* @property {String} duration 显示时间,设置为 0 则不会自动关闭
*/
export default {
name: 'uniPopupMessage',
mixins:[popup],
props: {
/**
* 主题 success/warning/info/error 默认 success
*/
type: {
type: String,
default: 'success'
},
/**
* 消息文字
*/
message: {
type: String,
default: ''
},
/**
* 显示时间,设置为 0 则不会自动关闭
*/
duration: {
type: Number,
default: 3000
},
maskShow:{
type:Boolean,
default:false
}
},
data() {
return {}
},
created() {
this.popup.maskShow = this.maskShow
this.popup.messageChild = this
},
methods: {
timerClose(){
if(this.duration === 0) return
clearTimeout(this.timer)
this.timer = setTimeout(()=>{
this.popup.close()
},this.duration)
}
}
}
</script>
<style lang="scss" >
.uni-popup-message {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: center;
}
.uni-popup-message__box {
background-color: #e1f3d8;
padding: 10px 15px;
border-color: #eee;
border-style: solid;
border-width: 1px;
flex: 1;
}
@media screen and (min-width: 500px) {
.fixforpc-width {
margin-top: 20px;
border-radius: 4px;
flex: none;
min-width: 380px;
/* #ifndef APP-NVUE */
max-width: 50%;
/* #endif */
/* #ifdef APP-NVUE */
max-width: 500px;
/* #endif */
}
}
.uni-popup-message-text {
font-size: 14px;
padding: 0;
}
.uni-popup__success {
background-color: #e1f3d8;
}
.uni-popup__success-text {
color: #67C23A;
}
.uni-popup__warn {
background-color: #faecd8;
}
.uni-popup__warn-text {
color: #E6A23C;
}
.uni-popup__error {
background-color: #fde2e2;
}
.uni-popup__error-text {
color: #F56C6C;
}
.uni-popup__info {
background-color: #F2F6FC;
}
.uni-popup__info-text {
color: #909399;
}
</style>

View File

@@ -0,0 +1,187 @@
<template>
<view class="uni-popup-share">
<view class="uni-share-title"><text class="uni-share-title-text">{{shareTitleText}}</text></view>
<view class="uni-share-content">
<view class="uni-share-content-box">
<view class="uni-share-content-item" v-for="(item,index) in bottomData" :key="index" @click.stop="select(item,index)">
<image class="uni-share-image" :src="item.icon" mode="aspectFill"></image>
<text class="uni-share-text">{{item.text}}</text>
</view>
</view>
</view>
<view class="uni-share-button-box">
<button class="uni-share-button" @click="close">{{cancelText}}</button>
</view>
</view>
</template>
<script>
import popup from '../uni-popup/popup.js'
import {
initVueI18n
} from '@dcloudio/uni-i18n'
import messages from '../uni-popup/i18n/index.js'
const { t } = initVueI18n(messages)
export default {
name: 'UniPopupShare',
mixins:[popup],
emits:['select'],
props: {
title: {
type: String,
default: ''
},
beforeClose: {
type: Boolean,
default: false
}
},
data() {
return {
bottomData: [{
text: '微信',
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/c2b17470-50be-11eb-b680-7980c8a877b8.png',
name: 'wx'
},
{
text: '支付宝',
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/d684ae40-50be-11eb-8ff1-d5dcf8779628.png',
name: 'wx'
},
{
text: 'QQ',
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/e7a79520-50be-11eb-b997-9918a5dda011.png',
name: 'qq'
},
{
text: '新浪',
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/0dacdbe0-50bf-11eb-8ff1-d5dcf8779628.png',
name: 'sina'
},
// {
// text: '百度',
// icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/1ec6e920-50bf-11eb-8a36-ebb87efcf8c0.png',
// name: 'copy'
// },
// {
// text: '其他',
// icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/2e0fdfe0-50bf-11eb-b997-9918a5dda011.png',
// name: 'more'
// }
]
}
},
created() {},
computed: {
cancelText() {
return t("uni-popup.cancel")
},
shareTitleText() {
return this.title || t("uni-popup.shareTitle")
}
},
methods: {
/**
* 选择内容
*/
select(item, index) {
this.$emit('select', {
item,
index
})
this.close()
},
/**
* 关闭窗口
*/
close() {
if(this.beforeClose) return
this.popup.close()
}
}
}
</script>
<style lang="scss" >
.uni-popup-share {
background-color: #fff;
border-top-left-radius: 11px;
border-top-right-radius: 11px;
}
.uni-share-title {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
justify-content: center;
height: 40px;
}
.uni-share-title-text {
font-size: 14px;
color: #666;
}
.uni-share-content {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
justify-content: center;
padding-top: 10px;
}
.uni-share-content-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
flex-wrap: wrap;
width: 360px;
}
.uni-share-content-item {
width: 90px;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: center;
padding: 10px 0;
align-items: center;
}
.uni-share-content-item:active {
background-color: #f5f5f5;
}
.uni-share-image {
width: 30px;
height: 30px;
}
.uni-share-text {
margin-top: 10px;
font-size: 14px;
color: #3B4144;
}
.uni-share-button-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
padding: 10px 15px;
}
.uni-share-button {
flex: 1;
border-radius: 50px;
color: #666;
font-size: 16px;
}
.uni-share-button::after {
border-radius: 50px;
}
</style>

View File

@@ -0,0 +1,7 @@
{
"uni-popup.cancel": "cancel",
"uni-popup.ok": "ok",
"uni-popup.placeholder": "pleace enter",
"uni-popup.title": "Hint",
"uni-popup.shareTitle": "Share to"
}

View File

@@ -0,0 +1,8 @@
import en from './en.json'
import zhHans from './zh-Hans.json'
import zhHant from './zh-Hant.json'
export default {
en,
'zh-Hans': zhHans,
'zh-Hant': zhHant
}

View File

@@ -0,0 +1,7 @@
{
"uni-popup.cancel": "取消",
"uni-popup.ok": "确定",
"uni-popup.placeholder": "请输入",
"uni-popup.title": "提示",
"uni-popup.shareTitle": "分享到"
}

View File

@@ -0,0 +1,7 @@
{
"uni-popup.cancel": "取消",
"uni-popup.ok": "確定",
"uni-popup.placeholder": "請輸入",
"uni-popup.title": "提示",
"uni-popup.shareTitle": "分享到"
}

View File

@@ -0,0 +1,45 @@
// #ifdef H5
export default {
name: 'Keypress',
props: {
disable: {
type: Boolean,
default: false
}
},
mounted () {
const keyNames = {
esc: ['Esc', 'Escape'],
tab: 'Tab',
enter: 'Enter',
space: [' ', 'Spacebar'],
up: ['Up', 'ArrowUp'],
left: ['Left', 'ArrowLeft'],
right: ['Right', 'ArrowRight'],
down: ['Down', 'ArrowDown'],
delete: ['Backspace', 'Delete', 'Del']
}
const listener = ($event) => {
if (this.disable) {
return
}
const keyName = Object.keys(keyNames).find(key => {
const keyName = $event.key
const value = keyNames[key]
return value === keyName || (Array.isArray(value) && value.includes(keyName))
})
if (keyName) {
// 避免和其他按键事件冲突
setTimeout(() => {
this.$emit(keyName, {})
}, 0)
}
}
document.addEventListener('keyup', listener)
// this.$once('hook:beforeDestroy', () => {
// document.removeEventListener('keyup', listener)
// })
},
render: () => {}
}
// #endif

Some files were not shown because too many files have changed in this diff Show More