源文件

This commit is contained in:
gyq
2024-05-23 14:39:33 +08:00
commit a1128dd791
2997 changed files with 500069 additions and 0 deletions

View File

@@ -0,0 +1,27 @@
<!--
App.vue本身不是页面这里不能编写视图元素也就是没有<template>
-->
<script setup>
import { ref, reactive } from 'vue';
import appConfig from '@/config/appConfig.js';
import { onLaunch } from '@dcloudio/uni-app';
import { checkCurrVersion, getExtStoreId } from '@/commons/utils/versionManage.js';
onLaunch(() => {
// console.log(uni.getExtConfigSync(),'uni.getExtConfigSync()')
getExtStoreId();
// appConfig.env.JEEPAY_BASE_URL = "https://b.rscygroup.com"
// 检查版本
checkCurrVersion();
});
</script>
<style lang="scss">
/** 每个页面公共css */
@import '@/commons/style/global.scss';
/** uni 组件样式覆盖 */
@import '@/commons/style/uni-overwrite.scss';
</style>

View File

@@ -0,0 +1,27 @@
{
"version" : "1",
"prompt" : "template",
"title" : "服务协议和隐私政策",
"message" : "  请你务必审慎阅读、充分理解“服务协议”和“隐私政策”各条款,包括但不限于:为了更好的向你提供服务,我们需要收集你的设备标识、操作日志等信息用于分析、优化应用性能。<br/>  你可阅读<a href=\"static/service.html\">《服务协议》</a>和<a href=\"static/privacy.html\">《隐私政策》</a>了解详细信息。如果你同意,请点击下面按钮开始接受我们的服务。",
"buttonAccept" : "同意并接受",
"buttonRefuse" : "暂不同意",
"second" : {
"title" : "确认提示",
"message" : "  进入应用前,你需先同意<a href=\"static/service.html\">《服务协议》</a>和<a href=\"static/privacy.html\">《隐私政策》</a>,否则将退出应用。",
"buttonAccept" : "同意并继续",
"buttonRefuse" : "退出应用"
},
"styles" : {
"backgroundColor" : "#FFFFFF",
"borderRadius" : "5px",
"title" : {
"color" : "#262626"
},
"buttonAccept" : {
"color" : "#3981FF"
},
"buttonRefuse" : {
"color" : "#A6A6A6"
}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,14 @@
该文件夹内放置: 项目自建资源, 比如 公共样式文件, 和 工具包等文件。
目录结构:
commons
style
utils
知识点: 样式文件不应该放置到 static文件夹内css、less/scss 等资源不要放在 static 目录下,建议这些公用的资源放在自建的 common 目录下。), 详见:
https://uniapp.dcloud.net.cn/tutorial/project.html

View File

@@ -0,0 +1,230 @@
/**
* 系统级别: 全局样式
*
* @site https://www.jeequan.com
* @date 2022/11/22 07:29
*/
/** 已整理 **/
// 通用 列表页样式
.page-wrapper {
min-height: calc(100vh - 70rpx); /** 最小高度 **/
padding-bottom: 70rpx; /** 安全距离防止home条遮挡文字 **/
background-color: $v-color-bgrey; /** 全局背景灰 **/
}
// 底部 固定的按钮, 比如: 创建门店, 创建员工等按钮。
.list-footer{
height: 100rpx;
background: transparent;
.button-wrapper {
border-top: 1rpx solid rgba(0,0,0, 0.07);
background-color: rgba(252, 252, 252, 0.85);
backdrop-filter: blur(20rpx);
position: fixed;
left: 0;
right: 0;
bottom: 0;
padding: 30rpx;
}
}
/** 详情页 覆写list-item */
.list-item-by-detail {
padding: 60rpx 60rpx 10rpx 60rpx !important;
background-color: transparent !important; /** 背景透明 **/
.list-title{
color: #fff !important;
}
.list-subtitle{
color: rgba(251,252,253,0.7) !important;
}
.list-info {
image {
margin-right: 0 !important;
}
}
}
/**列表条目渲染, 比如: 头像、 主标题, **/
.list-item {
display: flex;
align-items: center;
padding: 0 40rpx;
height: 170rpx;
background-color: #FFF; /** 背景 白色 **/
image {
flex-shrink: 0;
flex-grow: 0;
width: 100rpx;
height: 100rpx;
margin-right: 30rpx;
}
.list-info {
flex: 1;
.list-title {
display: flex;
justify-content: space-between;
align-items: center;
height: 40rpx;
color: rgba(77,77,77,1);
.list-name {
width: auto;
flex: 1;
display: flex;
align-items: center;
font-size: 30rpx;
font-weight: 400;
}
}
.list-subtitle {
color: rgba(153, 153, 153, 1);
margin-top: 25rpx;
font-size: 26rpx;
font-weight: 400;
width: 430rpx;
}
}
}
/** 状态小圆点 **/
.state-dot {
display: flex;
align-items: center;
font-size: 30rpx;
font-weight: 400;
&::after {
content: '';
display: block;
margin-left: 20rpx;
width: 20rpx;
height: 20rpx;
border-radius: 50%;
}
}
/** 状态小圆点 **/
.state-dot-enable {
&::after {
background-color: #168FFF;
}
}
/** 状态小圆点 **/
.state-dot-disable {
&::after {
background-color: #D9D9D9;
}
}
/** 状态小圆点 **/
.state-dot-error {
&::after {
background-color: red;
}
}
/**
* 描述预览图, 参考: app详情
* 第一个最后一个距离上下 40rpx, 中间间距20rpx
*/
.desc-view {
.desc-view-item {
display: flex;
justify-content: space-between;
padding: 20rpx 40rpx;
font-size: 30rpx;
font-weight: 400;
.title {
color: #808080;
}
.desc {
color: #000;
}
&:first-child {
margin-top: 20rpx;
}
&:last-child {
margin-bottom: 20rpx;
}
}
}
/** 已整理 **/
//容器内部元素上下左右居中
.flex-center {
display: flex;
justify-content: center;
align-items: center;
}
// 触摸反馈样式
.touch-hover {
background-color: #f7f8fa !important ;
}
.touch-button {
opacity: 0.5;
}
/* 单行文本超出省略号 */
.single-text-beyond {
overflow: hidden; /*超出部分隐藏*/
white-space: nowrap; /*禁止换行*/
text-overflow: ellipsis; /*省略号*/
}
// 按钮背景样式
.footer-button-style {
border-top: 1rpx solid #fcfcfc;
background-color: rgba(252, 252, 252, 0.85);
backdrop-filter: blur(20rpx);
}
// 搜素框 提示语样式
.input-placeholder {
font-size: 32rpx;
color: rgba(0, 0, 0, 0.35);
}
// 表单分割线
.line {
height: 20rpx;
background-color: $v-color-bgrey;
}
/**
* jeepay-btn 通用btn样式。
* 用法: <button class='jeepay-btn' hover-class="hover-button" @tap="loginFunc">登录</button>
*/
.jeepay-btn {
display: flex;
justify-content: center;
align-items: center;
height: 110rpx;
font-size: 33rpx;
font-weight: 500;
color: $J-color-tff;
border-radius: 20rpx;
background: linear-gradient(270deg, rgba(35,143,252,1) 0%, rgba(26,102,255,1) 100%);
box-shadow: 0 20rpx 60rpx -20rpx rgba(0,84,210,0.5);
&.hover-button {
opacity: 0.5;
}
}
/** 输入框icon **/
.input-icon{
width: 36rpx;
height: 36rpx;
}

View File

@@ -0,0 +1,181 @@
/**
* 系统级别:覆写 uni样式
*
* @site https://www.jeequan.com
* @date 2022/11/22 07:29
*/
/* 去除开关右侧边距 */
.uni-switch-input {
margin-right: 0 !important;
}
/* .uni-navbar {
position: relative;
z-index: 10;
} */
.uni-popup{
z-index: 998 !important;
}
// 表单组件样式 覆写
.uni-forms-item {
display: flex;
align-items: center;
min-height: 120rpx;
background-color: $J-bg-ff;
font-size: 32rpx;
font-weight: 400;
}
.uni-forms-item ::v-deep .uni-forms-item__label {
font-size: 32rpx !important;
font-weight: 400;
text-indent: 40rpx;
color: #4d4d4d;
height: auto !important;
}
// uni-form-item 表单校验, 如果校验不通过, 显示的文字占位, 并且添加下边距。
.uni-forms-item__error.msg--active{
position: relative !important;
margin-bottom: 30rpx;
}
.is-input-error-border .uni-easyinput__placeholder-class{
color: #f56c6c !important
}
// 去掉按钮边框
button:after {
border: none !important;
}
// 去点导航栏组件center&right
::v-deep.uni-navbar__header {
.uni-navbar__header-container,
.uni-navbar__header-btns-right {
display: none !important;
}
}
// 修改 uuni-easyinput
// form 外层必须包裹一个 view class="jeepay-form"
.jeepay-form {
.uni-easyinput {
.uni-easyinput__content {
border: 2px solid transparent !important;
height: 110rpx;
padding: 0 30rpx;
margin-bottom: 50rpx;
box-sizing: border-box !important;
border-radius: 20rpx !important;
background-color: rgba(247, 247, 247, 1) !important;
}
.uni-easyinput__content-input {
color: rgba(0, 0, 0, 1);
font-size: 32rpx !important;
font-weight: 400;
}
.is-foucs {
border: 2px solid #1d79fd !important;
background-color: white !important;
}
.uni-input-placeholder
/* #ifdef MP-WEIXIN */
,.uni-easyinput__placeholder-class
/* #endif */ {
font-size: 32rpx !important;
color: #B3B3B3 !important;
}
}
}
.jeepay-edit-form .uni-easyinput__content-input {
padding-left: 0 !important;
.uni-input-placeholder {
font-size: 32rpx !important;
}
}
// 设置新密码覆盖form默认样式
.new-password {
.uni-forms-item.is-direction-left {
padding: 0 40rpx;
.uni-forms-item__label {
width: 190rpx !important;
font-size: 32rpx !important;
font-weight: 400;
white-space: nowrap;
color: rgba(102, 102, 102, 1);
text-indent: 0 !important;
}
.uni-easyinput__placeholder-class {
font-size: 32rpx !important;
font-weight: 400 !important;
}
}
}
// 搜索栏覆盖默认样式
/* #ifdef MP-WEIXIN */
.input-main {
button {
font-size: 32rpx;
color: rgba(29,121,253,1);
background: rgba(255,255,255,1);
}
.uni-easyinput {
.uni-easyinput__content {
background-color: $J-bg-f5 !important;
border-radius: $J-b-r12;
.uni-easyinput__content-input {
padding-left: 0 !important;
.uni-input-input {
border-radius: $J-b-r12 !important;
overflow: hidden !important;
}
}
.uni-input-placeholder {
font-size: 27rpx;
}
.uni-icons {
color: rgba(230,230,230,1) !important;
}
}
}
}
/* #endif */
// 搜索栏覆盖默认样式
/* #ifdef MP-WEIXIN */
button[is="components/Button/Button"] {
padding: 0;
background-color: transparent !important;
}
/* #endif */
// 修改时间选择器按钮颜色
.xp-button--confirm {
background: $jeepay-bg-primary;
}
.xp-button--cancel {
color: rgba(0, 0, 0, 0.5) !important;
}
// label 样式
.f-label{
width: 280rpx;
align-self: start;
padding-top: 40rpx;
font-size: 32rpx ;
font-weight: 400;
text-indent: 40rpx;
color: #4d4d4d;
}

View File

@@ -0,0 +1,39 @@
/**
* 系统级别: 自定义变量
*
* @site https://www.jeequan.com
* @date 2022/11/22 07:29
*/
// $v : 表示: variables简写。 uni的默认都有特殊开头 一般不会重复。
$v-color-t21: #217dfe;
// 全局通用: 背景灰 background grey
$v-color-bgrey: #F7F7F7;
// 背景色
$J-bg-f7: #f7f7f7; //页面背景色
$J-bg-ff: #fff;
$J-bg-f5: #f5f5f5; //输入框背景色
// 文字颜色
$J-color-t80: #808080; //常用于 未选中文字颜色
$J-color-t21: #217dfe; //卡片文字选中 统计报表
$J-color-tff: #fff;
$J-color-tSff: rgba(255, 255, 255, 0.7);
$J-color-t29: #2980fd; //选中 文字颜色 搜索
$J-color-ta6: #a6a6a6;
$J-color-t4d: #4d4d4d; //标题颜色
$J-color-t99: #999;
$J-color-t8c: #8c8c8c; // 卡片列表 标题文字颜色
//圆角相关变量
$J-b-r32: 32rpx;
$J-b-r12: 12rpx;
$J-b-r10: 10rpx;
$v-b-r20: 20rpx;
// 文字大小
$J-f-size30: 30rpx;
// 常用边框颜色
$v-b-color-ed: #ededed;

View File

@@ -0,0 +1,28 @@
/**
* 统一工具类 (all utils x ) aux ( 谐音 ) [ window 无法创建此命名的文件 放弃。 = = ]
* 统一使用: all Kit 简称 ak.
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2022/12/12 18:38
*/
import go from './go.js'
import emit from './emit.js'
import ent from './ent.js'
import cal from './cal.js'
import dataKit from './dataKit.js'
import timer from './timer.js'
import infoBox from './infoBox.js'
const ak = {
go: go,
emit: emit,
ent: ent,
cal: cal,
timer: timer,
infoBox: infoBox,
}
export default ak

View File

@@ -0,0 +1,37 @@
/**
* 数字, 计算相关函数
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2022/11/22 10:38
*/
/**
* 保留小数n位不进行四舍五入
* num你传递过来的数字,
* decimal你保留的几位,默认保留小数后两位
*/
const formatDecimal = function(num, decimal = 2) {
num = num.toString()
const index = num.indexOf('.')
if (index !== -1) {
num = num.substring(0, decimal + index + 1)
} else {
num = num.substring(0)
}
//截取后保留两位小数
return parseFloat(num).toFixed(decimal)
}
const model = {
// 分转元
// amount - 金额 parseFloat - 是否转换为数字格式, 默认String
cert2Dollar(amount, needParseFloat = false) {
if (needParseFloat) { // parseFlot
return formatDecimal(amount / 100)
}
return formatDecimal(amount / 100)
}
}
export default model

View File

@@ -0,0 +1,38 @@
/**
* 数据 工具类
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2022/11/30 14:18
*/
const model = {
// 递归遍历树状结构数据 matchFunc 匹配结果, true表示匹配成功 否则继续匹配。
// pnode 可不传入
// 返回结构: [当前数据, 上级数据 ]
recursionTreeData: (treeData, matchFunc, childrenName = 'children', pnode = null ) => {
for (let i = 0; i < treeData.length; i++) {
const item = treeData[i]
// 匹配成功
if(matchFunc(item)){
return [item, pnode]
}
if (item[childrenName] && item[childrenName].length > 0) {
let res = model.recursionTreeData(item[childrenName], matchFunc, childrenName, item)
if(res){
return res
}
}
}
}
}
export default model

View File

@@ -0,0 +1,164 @@
/**
* datamap , 数据字典, 存放常用的配置常量信息。 如订单类型的判断, 用户类型的判断。
*
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2022/11/28 16:16
*/
const payOrderStateMap = {
0: {
text: '订单生成',
color: '#2980FD'
},
1: {
text: '支付中',
color: '#FFAA33'
},
2: {
text: '支付成功',
color: '#09BB07'
},
3: {
text: '支付失败',
color: '#CB2972'
},
4: {
text: '已撤销',
color: '#808080'
},
5: {
text: '已退款',
color: '#FF5B4C'
},
6: {
text: '订单关闭',
color: '#D9D9D9'
},
}
// 订单 图片 和背景颜色
const payOrderImageMap = {
WECHAT: {
title: '微信',
imgUrl: '/static/orderImg/wechat.svg', //微信支付
bgColor: '#09BB07',
},
ALIPAY: {
title: '支付宝',
imgUrl: '/static/orderImg/zfb.svg', //支付宝支付
bgColor: '#458FFF',
},
YSFPAY: {
title: '云闪付',
imgUrl: '/static/orderImg/ysf.svg', // 云闪付支付
bgColor: '#FF5B4C',
},
UNIONPAY: {
title: '银联',
imgUrl: '/static/orderImg/union-pay.svg', //银联支付
bgColor: '#0D131A',
},
OTHER: {
title: '其他',
imgUrl: '/static/orderImg/default-pay.svg', //其他支付
bgColor: '#5F36C4',
},
}
// 1-超级管理员 2-普通用户 3-拓展员, 11-店长, 12-店员
const userTypeMap = {
1: {
text: '超管',
bgColor: 'linear-gradient(270deg, rgba(35,161,252,1) 0%, rgba(26,102,255,1) 100%)',
type: 'blue'
},
2: {
text: '普通用户',
bgColor: 'linear-gradient(270deg, rgba(35,161,252,1) 0%, rgba(26,102,255,1) 100%)'
},
3: {
text: '拓展员',
bgColor: 'linear-gradient(270deg, rgba(35,161,252,1) 0%, rgba(26,102,255,1) 100%)'
},
11: {
text: '店长',
bgColor: 'linear-gradient(270deg, rgba(220,61,138,1) 0%, rgba(187,23,92,1) 100%)',
type: 'purple'
},
12: {
text: '店员',
bgColor: ' linear-gradient(270deg, rgba(61,220,68,1) 0%, rgba(23,187,118,1) 100%)',
type: 'green'
},
}
// 设备厂商
const devProvider = {
zgwl: '智谷物联',
bsj: '博实结',
fe: '飞鹅',
ps: '品生',
clj: '财来聚',
wsy: '微收银',
xjl: '小精灵',
lmspay: '立码收',
lkls: '拉卡拉',
zw: '智网'
}
const rechargeStateMap = {
0: {
text: '初始化',
color: '#2980FD'
},
1: {
text: '充值中',
color: '#FFAA33'
},
2: {
text: '充值成功',
color: '#09BB07'
},
3: {
text: '充值失败',
color: '#CB2972'
}
}
const model = {
// 订单状态文本和color
payOrderState: (state) => {
// 避免循环判断,影响性能。
if (payOrderStateMap[state]) {
return payOrderStateMap[state]
}
return {
text: '未知',
color: '#D9D9D9'
}
},
// 订单图片 和图片背景颜色
payOrderImage: (state) => {
// 取值 找到 返回当前值 未找到返回空对象
return payOrderImageMap[state] || {}
},
// 用户类型的判断
userType: (userTypeVal) => {
return userTypeMap[userTypeVal] || {}
},
// 查找设备厂商 找到就返回 找不到据返回空
provider: (state) => {
return devProvider[state] || '未知'
},
// 会员充值订单图片 和图片背景颜色
rechargeRecordImage: (state) => {
// 取值 找到 返回当前值 未找到返回空对象
return rechargeStateMap[state] || {}
},
}
export default model

View File

@@ -0,0 +1,138 @@
/**
* 页面通讯工具类uni.emit的封装
*
* @author terrfly
* @site https://www.jeequan.com
* @date 2022/11/24 15:54
*/
const model = {
// 定义监听器的名字, 应该是一个页面一个, 不可两个页面公共一个, 否则会导致: 一个页面onUnload 移除时影响另一个页面的正常接收。
// 业务页面 监听, onUnload应该移除掉。
// 通用搜索页
ENAME_REF_SEARCH_PAGE : 'ENAME_REF_SEARCH_PAGE',
// 应用列表页的刷新
ENAME_REF_TABLE_MCH_APP : 'ENAME_REF_TABLE_MCH_APP',
// 应用详情
ENAME_REF_TABLE_MCH_APP_DETAILS : 'ENAME_REF_TABLE_MCH_APP_DETAILS',
// 调起扫一扫
ENAME_E_PAY_SCAN : 'ENAME_E_PAY_SCAN',
// 重置 金额
ENAME_RESET_PAY_AMOUNT: 'ENAME_RESET_PAY_AMOUNT',
// 更新支付订单信息
ENAME_REF_PAY_ORDER : 'ENAME_REF_PAY_ORDER',
// 更新 门店列表
ENAME_REF_STORE_LIST : 'ENAME_REF_STORE_LIST',
// 更新 门店详情
ENAME_REF_STORE_DETAIL : 'ENAME_REF_STORE_DETAIL',
// 更新 员工列表
ENAME_REF_SYS_USER_LIST : 'ENAME_REF_SYS_USER_LIST',
// 更新 员工详情
ENAME_REF_SYS_USER_DETAIL : 'ENAME_REF_SYS_USER_DETAIL',
// 更新 通道列表
ENAME_REF_PAY_PASSAGE_LIST : 'ENAME_REF_PAY_PASSAGE_LIST',
// 更新 辅助终端信息
ENAME_REF_TERMINAL_LIST : 'ENAME_REF_TERMINAL_LIST',
// 更新 辅助终端 详情页
ENAME_REF_TERMINAL_DETAIL : 'ENAME_REF_TERMINAL_DETAIL',
// 更新 智能pos 信息
ENAME_REF_AUTOPOS_LIST : 'ENAME_REF_AUTOPOS_LIST',
// 更新 智能pos 详情页
ENAME_REF_AUTOPOS_DETAIL : 'ENAME_REF_AUTOPOS_DETAIL',
// 更新 扫码pos 信息
ENAME_REF_SCANPOS_LIST : 'ENAME_REF_SCANPOS_LIST',
// 更新 扫码pos 详情页
ENAME_REF_SCANPOS_DETAIL : 'ENAME_REF_SCANPOS_DETAIL',
// 码牌列表
ENAME_REF_QRC_LIST : 'ENAME_REF_QRC_LIST',
// 码牌详情
ENAME_REF_QRC_DETAIL : 'ENAME_REF_QRC_DETAIL',
// 更新 打印机 信息
ENAME_REF_PRINTER_LIST : 'ENAME_REF_PRINTER_LIST',
// 更新 打印机 详情页
ENAME_REF_PRINTER_DETAIL : 'ENAME_REF_PRINTER_DETAIL',
// 云喇叭列表
ENAME_REF_SPEAKER_LIST : 'ENAME_REF_SPEAKER_LIST',
// 云喇叭详情
ENAME_REF_SPEAKER_DETAIL : 'ENAME_REF_SPEAKER_DETAIL',
// 进件列表
ENAME_REF_APPLYMENT_LIST : 'ENAME_REF_APPLYMENT_LIST',
// 刷脸设备列表
ENAME_REF_FACE_LIST : 'ENAME_REF_FACE_LIST',
// 刷脸设备详情
ENAME_REF_FACE_DETAIL : 'ENAME_REF_FACE_DETAIL',
// 刷脸广告列表
ENAME_REF_AD_LIST : 'ENAME_REF_AD_LIST',
// 刷脸广告 详情
ENAME_REF_AD_DETAILS : 'ENAME_REF_AD_DETAILS',
// 更新 会员列表
ENAME_REF_MEMBER_LIST : 'ENAME_REF_MEMBER_LIST',
// 更新 会员详情
ENAME_REF_MEMBER_DETAIL : 'ENAME_REF_MEMBER_DETAIL',
// 更新 会员账户流水列表
ENAME_REF_MEMBER_ACCOUNT_HISTORY_LIST : 'ENAME_REF_MEMBER_ACCOUNT_HISTORY_LIST',
// 更新 会员账户流水详情
ENAME_REF_MEMBER_ACCOUNT_HISTORY_DETAIL : 'ENAME_REF_MEMBER_ACCOUNT_HISTORY_DETAIL',
// 更新 会员充值记录列表
ENAME_REF_MEMBER_RECHARGE_RECORD_LIST : 'ENAME_REF_MEMBER_RECHARGE_RECORD_LIST',
// 更新 会员充值记录详情
ENAME_REF_MEMBER_RECHARGE_RECORD_DETAIL : 'ENAME_REF_MEMBER_RECHARGE_RECORD_DETAIL',
// 更新 充值规则列表
ENAME_REF_RECHARGE_RULE_LIST : 'ENAME_REF_RECHARGE_RULE_LIST',
// 刷新页面的 发射事件 , 更新页面 && 更新搜索页面
refPageAndSearchEmit: (refEmitEventName, data = {} ) => {
model.pageEmit(refEmitEventName, data)
model.pageEmit(model.ENAME_REF_SEARCH_PAGE, data)
},
// 自定义
pageEmit: (refEmitEventName, data) => {
uni.$emit(refEmitEventName, data )
},
// 废弃该函数。 ( 因为: 页面只监听一次, 使用该函数则无法再次监听, 需要再每个页面写入: uni.$on 保证正常接收。 )
// 监听页面刷新函数
on: (refEmitEventName) => {
return new Promise( (resolve) => {
uni.$on(refEmitEventName, function(data){
resolve( data )
})
})
}
}
export default model

View File

@@ -0,0 +1,72 @@
/**
* 加解密工具包
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2021/5/16 17:35
*/
import { SM4 } from 'gm-crypto'
import appConfig from '@/config/appConfig.js'
let HEX_KEY = null
// 字符串转16进制
function str2hex(str) {
var val = ''
for (var i = 0; i < str.length; i++) {
if (val == '')
val = str.charCodeAt(i).toString(16)
else
val += str.charCodeAt(i).toString(16)
}
val += ''
return val
}
// 获取hex秘钥
function getHexKey(){
if(!HEX_KEY){
HEX_KEY = str2hex(appConfig.encryptKey)
}
return HEX_KEY
}
// 解密 (http响应数据 做通用处理)
export function sm4DecryptByResData(data){
if(!data){
return data
}
let res = SM4.decrypt(data, getHexKey(), {
inputEncoding: 'base64',
outputEncoding: 'utf8'
})
if(!res){
return res
}
return JSON.parse(res)['originData']
}
// 加密 (http响应数据 做通用处理)
export function sm4EncryptByReqData(data){
if(!data){
return data
}
// 加密处理
let encryptData = SM4.encrypt(JSON.stringify(data), getHexKey(), {
inputEncoding: 'utf8',
outputEncoding: 'base64'
})
return {encryptData : encryptData}
}

View File

@@ -0,0 +1,21 @@
/**
* 权限判断
*
* @author terrfly
* @site https://www.jeequan.com
* @date 2022/11/22 14:29
*/
import storageManage from '@/commons/utils/storageManage.js'
const model = {
// 判断是否包含该权限
has(entId){
let userInfo = storageManage.userInfo()
if(userInfo && userInfo.entIdList && userInfo.entIdList.indexOf(entId) >= 0){
return true;
}
return false;
}
}
export default model

View File

@@ -0,0 +1,101 @@
/**
* form 验证 工具类
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2022/11/25 10:58
*/
import infoBox from '@/commons/utils/infoBox.js'
const model = {
// 正则表达式
regexp: {
// 手机号验证规则
mobile: /^1\d{10}$/,
// 登录用户名: 字母开头 6 -18位。
loginUsername: /^[a-zA-Z][a-zA-Z0-9]{5,17}$/,
},
// 验证规则:
rules: {
// showText 为空, 说明是 input的placeHoloder 直接飘红,
// showText 有值: 在文本框下方提示。
// 只有input 才有这种特殊判断。
requiredInput: (showText, type = 'string') => {
return {format: type, required: true, errorMessage: showText ? ('请输入' + showText) : ' ' }
},
requiredSelect: (showText, type = 'string') => {
return {format: type, required: true, errorMessage: '请选择' + showText }
},
requiredUpload: (showText, type = 'string') => {
return {format: type, required: true, errorMessage: '请上传' + showText }
},
// 正则验证 请注意: 该规则需要在required后面 此处不可包含required
patternRule: (showText, p) => {
return {pattern: p, errorMessage: '请输入正确的' + showText }
},
requiredInputShowToast: (showText, type = 'string') => {
return {format: type, required: true, errorMessage:' ', toastErrorMessage: '请输入' + showText }
},
requiredSelectShowToast: (showText, type = 'string') => {
return {format: type, required: true, errorMessage:' ', toastErrorMessage: '请选择' + showText }
},
requiredUploadShowToast: (showText, type = 'string') => {
return {format: type, required: true, errorMessage:' ', toastErrorMessage: '请上传' + showText }
},
patternRuleShowToast: (showText, p) => {
return {pattern: p, errorMessage:' ', toastErrorMessage: '请输入正确的' + showText }
},
},
// 支持 提示信息
// 类型如下:
// {
// required: true,
// toastErrorMessage: "请输入去去去去群",
// errorMessage: ' ', // 不会显示在下部, 需要空格占位
// }
validate: (form) => {
return form.validate().catch(e => {
if(!e || e.length <= 0){
return Promise.reject()
}
for(let i = 0; i < e.length; i++){
let k = e[i].key
let rules = form.rules[k].rules
for(let j = 0; j < rules.length; j++){
if(rules[j].toastErrorMessage && rules[j].required){
infoBox.showToast(rules[j].toastErrorMessage) // 仅提示一次即可
return Promise.reject(e)
}
}
}
return Promise.reject(e)
})
},
}
export default model

View File

@@ -0,0 +1,111 @@
/**
* 页面跳转工具类
*
* @author terrfly
* @site https://www.jeequan.com
* @date 2022/11/14 17:12
*/
// 引入 pages用于解析 pageId 变量 , 注意 需要在 pages.json 定义pageId 参数。
// 使用方法: go.to(""), 优点: 扩展分包可任意路径业务调用不再传入固定URL, 传入的是pageId变量。
import pagesJSON from '@/pages.json'
import emit from './emit.js'
// 获取到全部的页面路径 和 ID
const ALL_PAGES = { }
// 添加到 ALL_PAGES
function addPages(pagesRoot){
let rootUrl = pagesRoot.root ? `/${pagesRoot.root}/` : '/'
pagesRoot.pages.forEach(r => {
if(r.pageId){
ALL_PAGES[r.pageId] = rootUrl + r.path
}
})
}
// 1. 添加 主目录
addPages(pagesJSON)
// 2. 添加分包文件 目录
if(pagesJSON.subPackages){
pagesJSON.subPackages.forEach(r => addPages(r))
}
const model = {
// 跳转类型
GO_TYPE_TO: 'navigateTo',
GO_TYPE_REDIRECT: 'redirect',
GO_TYPE_RELAUNCH: 'reLaunch',
GO_TYPE_SWITCHTAB: 'switchTab',
// 对象转换url参数
object2param: (obj) => {
if(!obj || Object.keys(obj).length <= 0){
return ""
}
let result = "?"
Object.keys(obj).forEach(k => {
let val = obj[k]
// H5需要转码 其他平台不需要
// #ifdef H5
val = encodeURIComponent(val)
// #endif
result += `${k}=${val}&`
})
return result.substr(0, (result.length - 1));
},
// uni.navigateTo函数的封装 保留当前页面跳转到应用内的某个页面使用uni.navigateBack可以返回到原页面。
// 参数: pagesIdOrUrl(路径或者pageId), 页面参数, 扩展参数
to: (pagesIdOrUrl, params = {}, type = model.GO_TYPE_TO, extObject = {}) => {
// 使用ID作为标识
if(pagesIdOrUrl.indexOf('PAGES_') == 0){
pagesIdOrUrl = ALL_PAGES[pagesIdOrUrl]
}
pagesIdOrUrl += model.object2param(params)
if(type == model.GO_TYPE_TO){
uni.navigateTo(Object.assign({ url: pagesIdOrUrl }, extObject))
}
if(type == model.GO_TYPE_REDIRECT){
uni.redirectTo(Object.assign({ url: pagesIdOrUrl }, extObject))
}
if(type == model.GO_TYPE_RELAUNCH){
uni.reLaunch(Object.assign({ url: pagesIdOrUrl }, extObject))
}
if(type == model.GO_TYPE_SWITCHTAB){
uni.switchTab(Object.assign({ url: pagesIdOrUrl }, extObject))
}
},
// 跳转到通用搜索页面
toSearchPage: (pageType, extObject = {}) => {
model.to("PAGES_LIST_SEARCH", Object.assign({type: pageType}, extObject))
},
// delta 返回到第几页, refEmitEventName: 触发全局更新函数 (更新 list and search )
back: (delta = 1, refEmitEventName) => {
if(refEmitEventName){
emit.refPageAndSearchEmit(refEmitEventName)
}
uni.navigateBack(delta)
}
}
export default model

View File

@@ -0,0 +1,62 @@
/**
* 提示信息公共文件
*
* @author terrfly
* @site https://www.jeequan.com
* @date 2022/11/14 15:29
*/
const model = {
// uni.showToast的封装
// 参数: 标题、 显示时长(单位: 秒) 扩展参数
// 返回: promise对象 当提示消失后调用 resolve()
showToast: (title, duration = 1.5, extObject) => {
return new Promise((resolve, reject) => {
uni.showToast(Object.assign({ title: title, icon: 'none', mask: true, duration: (duration * 1000) }, extObject))
setTimeout(resolve, (duration * 1000));
})
},
// success类型的提示
showSuccessToast: (title, duration) => {
return model.showToast(title, duration, {icon: 'success'})
},
// error类型的提示
showErrorToast: (title, duration) => {
return model.showToast(title, duration, {icon: 'error'})
},
showLoading: (title = '请稍后' ) => {
return uni.showLoading({ title: title, mask: true })
},
hideLoading: () => {
return uni.hideLoading()
},
// 返回 Promise 点击确定和取消
// APP安卓原生提示比较丑 APP 不推荐使用该函数 。
showModal: (title, confirmText = '确定', cancalText = '取消', extObject) => {
return new Promise((resolve, reject) => {
uni.showModal( Object.assign({
title: title,
confirmText: confirmText,
showCancel: cancalText ? true : false,
cancelText: cancalText,
success: function(r) {
if (r.confirm) {
resolve()
}else if (r.cancel) {
reject()
}
}
}, extObject ));
});
},
}
export default model

View File

@@ -0,0 +1,143 @@
import { $getBaiduToken } from '@/http/apiManager.js';
const audioTeam = [];
let audioStartSwitch = false;
const getAudioUrl = 'https://tsn.baidu.com/text2audio';
export default function openVoice(objs) { // 传入需转为语音的文本内容
let lineUp = false;
let returnAudio = false;
if (typeof(objs) !== 'string') {
if (objs && objs.lineUp === true) {
lineUp = true;
}
if (objs && objs.returnAudio === true) {
returnAudio = true;
}
}
if(returnAudio) {
return new Promise((resolve, reject)=>{
openVoiceFc(objs, returnAudio).then(res=>{
resolve(res);
}).catch(err=>{
reject(err)
});
})
}
if (!audioStartSwitch || lineUp) {
audioStartSwitch = true;
openVoiceFc(objs);
} else {
audioTeam.push(objs);
}
}
function openVoiceFc(objs, returnAudio) {
if(returnAudio) {
return new Promise((resolve, reject)=>{
$getBaiduToken().then(({bizData}) => {
if (bizData) {
resolve(tts(objs, bizData, returnAudio));
} else {
reject('获取语音tok接口为空');
}
})
})
}else{
$getBaiduToken().then(({bizData}) => {
if (bizData) {
tts(objs, bizData);
} else {
}
})
}
}
function tts(objs, tok, returnAudio) {
if(typeof(objs)=='string')
objs = {voiceSet: {tex: objs}};
const data = {
tok,
cuid: tok,
ctp: 1,
lan: 'zh',
...objs.voiceSet
}
if(returnAudio)
return btts( data, objs.audioSet, objs.audioCallback, objs.lineUp, returnAudio);
btts( data, objs.audioSet, objs.audioCallback, objs.lineUp, returnAudio);
}
function setAudioSet(options, audio) {
if (options) {
audio.volume = options.volume || 1;
audio.startTime = options.startTime || 0;
audio.loop = options.loop || false;
audio.obeyMuteSwitch = options.obeyMuteSwitch && typeof(options.obeyMuteSwitch) == 'boolean' ? options.obeyMuteSwitch :
true; //支持微信小程序、百度小程序、头条小程序
}
}
function btts(param, options, audioCallback, lineUp, returnAudio) {
let audio = uni.createInnerAudioContext();
setAudioSet(options, audio);
// 序列化参数列表
let fd = [];
for (let k in param) {
fd.push(k + '=' + encodeURIComponent(encodeURIComponent(param[k])));
}
audio.src = `${getAudioUrl}?${fd.join('&')}`;
if(returnAudio) {
audio.onEnded(() => {
audio.destroy(); //销毁音频实例
audio = null;
})
audio.onError((e)=>{
if (audioCallback && audioCallback.onError && typeof(audioCallback.onError) == 'function') audioCallback.onError(e);
audio.destroy(); //销毁音频实例
audio = null;
})
return audio;
}
audio.onPlay(() => {
if (audioCallback && audioCallback.onPlay && typeof(audioCallback.onPlay) == 'function') audioCallback.onPlay();
})
audio.onPause(()=>{
if (audioCallback && audioCallback.onPause && typeof(audioCallback.onPause) == 'function') audioCallback.onPause();
})
audio.onWaiting(()=>{
if (audioCallback && audioCallback.onWaiting && typeof(audioCallback.onWaiting) == 'function') audioCallback.onWaiting();
})
audio.onStop(()=>{
if (audioCallback && audioCallback.onStop && typeof(audioCallback.onStop) == 'function') audioCallback.onStop();
})
audio.onTimeUpdate(()=>{
if (audioCallback && audioCallback.onTimeUpdate && typeof(audioCallback.onTimeUpdate) == 'function') audioCallback.onTimeUpdate();
})
audio.onSeeking(()=>{
if (audioCallback && audioCallback.onSeeking && typeof(audioCallback.onSeeking) == 'function') audioCallback.onSeeking();
})
audio.onSeeked(()=>{
if (audioCallback && audioCallback.onSeeked && typeof(audioCallback.onSeeked) == 'function') audioCallback.onSeeked();
})
audio.onEnded(() => {
audio.destroy(); //销毁音频实例
audio = null;
if (audioCallback && audioCallback.onEnded && typeof(audioCallback.onEnded) == 'function') audioCallback.onEnded();
if (lineUp !== false) {
if (audioTeam.length > 0) {
openVoiceFc(audioTeam[0]);
audioTeam.splice(0, 1);
} else {
audioStartSwitch = false;
}
}
})
audio.onError((e)=>{
if (audioCallback && audioCallback.onError && typeof(audioCallback.onError) == 'function') audioCallback.onError(e);
audio.destroy(); //销毁音频实例
audio = null;
})
audio.play();
}

View File

@@ -0,0 +1,71 @@
import storageManage from '@/commons/utils/storageManage.js'
import dayjs from 'dayjs'
import baiduyy from './QS-baiduyy.js'; // 百度语音合成
// #ifdef MP-WEIXIN
import wxTextToSpeach from './wxTextToSpeach.js'; // 微信小程序插件语音合成
// #endif
const model = {
// 监听推送通知
addPushMsgEventListener: function(){
console.log("监听推送")
// #ifdef APP-PLUS
// unipush1.0监听消息
if(plus && plus.push) {
plus.push.addEventListener('receive', model.handlePush)
}
// #endif
// unipush2.0监听消息
model.uniPushListener2()
},
// uniPush2.0 接收推送消息
uniPushListener2: function() {
uni.onPushMessage((res) => {
console.log("uniPush2.0 收到推送消息:", res.data) //监听推送消息
model.handlePush(res.data)
})
},
// 语音播报
handlePush: function(message) {
// 没有token信息
if(!storageManage.token()){
return false;
}
// 信息不存在
if(!message || !message.content) {
return false;
}
const content = JSON.parse(message.content)
console.log("消息内容:", content)
// 支付成功
if (content && content.type == 'paySuccess') {
// 在过期时间之内, 则调起语音播报。
if( dayjs(content.expiredTime).isAfter(dayjs()) ){
console.log('执行消息播报');
// #ifdef MP-WEIXIN
wxTextToSpeach(content.msg)
// #endif
// #ifndef MP-WEIXIN
baiduyy(content.msg)
// #endif
uni.vibrateLong({});
}
}
}
}
export default model

View File

@@ -0,0 +1,50 @@
import {
$pushInfoRegister
} from '@/http/apiManager.js'
import storageManage from '@/commons/utils/storageManage.js'
// 默认导出 方法 注册 push连接
export default async function() {
let cid1 = undefined // unipush1.0 客户端CID
let cid2 = undefined // unipush2.0 客户端CID
let orgCid = undefined // 原始cid如果获取的cid和新的cid不相同赋值原始cid后端会根据原始cid更新
let cidType = undefined //传递类型 是 app 还是 微信
// #ifdef APP-PLUS
cidType = 'app_plus'
// #endif
// #ifdef MP-WEIXIN
cidType = 'mp_weixin'
// #endif
// #ifdef APP-PLUS
if (!plus) {
cid1 = plus.push.getClientInfo().clientid
}
// #endif
const data = await uni.getPushClientId()
console.log('客户端推送标识:', data.cid)
cid2 = data.cid
// 如果不存 cid 本地存储 写入 cid
if (!storageManage.uniPush2Cid()) {
storageManage.uniPush2Cid(data.cid)
} else if (cid2 !== storageManage.uniPush2Cid()) { // 否则进行 cid 对比 判断 是否相等 不相等 赋值 orgCid
orgCid = storageManage.uniPush2Cid() //赋值原始cid
storageManage.uniPush2Cid(data.cid) //重新写入 cid
}
if (cid1) {
pushInfoRegister(cid1)
} else {
pushInfoRegister(cid1, cid2, orgCid, cidType)
}
function pushInfoRegister(cid1 = '', cid2 = '', org = '', cidType = '') {
$pushInfoRegister({
cid1,
cid2,
orgCid: org,
cidType
}).then(res => {
orgCid = '' //重置 数据
})
}
}

View File

@@ -0,0 +1,87 @@
import { $mchConfig } from "@/http/apiManager"
import storageManage from '@/commons/utils/storageManage.js'
let num = 0 //计算错误此时 超过5次错误 不在出触发
const plugin = requirePlugin("WechatSI")
const pushMsgArr = [] //维护一个消息队列
let backgroundAudioManager = undefined //获取背景音频实例
let audioMp3 = ''
console.log('执行创建 语音播报逻辑');
// 获取配置项 判断是否 开启 小程序 语音推送
export function getPushStatus () {
if (!storageManage.token()) return //未登录 不播放
$mchConfig('orderConfig').then(({ bizData = [] }) => {
const weChat = bizData.find(v => v.configKey == "weChatVoice")
if (weChat && weChat?.configVal == 1) {
createBgMusice()
}
})
}
getPushStatus()
// 创建 背景音乐
function createBgMusice (file) {
backgroundAudioManager = wx.getBackgroundAudioManager()
backgroundAudioManager.title = '订单通知'
if (!audioMp3) {
createFile()
} else {
backgroundAudioManager.src = audioMp3
}
// 监听 音频播放失败事件
backgroundAudioManager.onError(function (res) {
console.log('音频播放失败', res, num);
if (num >= 5) return
createFile()
num++
})
// 监听 音频播放结束事件
onBgMusiceEnd()
}
// 监听bei背景音乐播放状态
export function onBgMusiceEnd () {
backgroundAudioManager.onEnded(() => {
if (pushMsgArr.length > 0) return broadcast(pushMsgArr.pop()) //如果有消息 则继续播放
backgroundAudioManager.src = audioMp3 //否则播放默认背景音乐
})
}
export function startOrEndMusice (flag) {
if (!flag && !!backgroundAudioManager) return backgroundAudioManager.stop() //关闭背景音乐 地址指向空即可
if (!backgroundAudioManager) return createBgMusice() // 如果一开始是关闭状态 则创建背景音乐实例
backgroundAudioManager.src = audioMp3 // 否则重新赋值背景音地址即可
}
export default function (message) {
if (!backgroundAudioManager) return
pushMsgArr.unshift(message) //将消息添加到消息队列头部 背景音乐播放结束后 会对消息队列 进行校验 如果消息队列有消息 会进行播放 否则继续循环背景音乐
}
// 播放订单
function broadcast (msg) {
plugin.textToSpeech({
lang: "zh_CN",
tts: true,
content: msg,
success: function (res) {
backgroundAudioManager.src = res.filename;
onBgMusiceEnd()
},
fail: function (res) {
console.log("fail tts", res)
}
})
}
// 创建文件
export function createFile (file) {
const fs = wx.getFileSystemManager()
fs.copyFile({
srcPath: `static/noiseless.mp3`,
destPath: `${wx.env.USER_DATA_PATH}/noiseless.mp3`,
success (res) {
console.log(res, `${wx.env.USER_DATA_PATH}/noiseless.mp3`)
audioMp3 = `${wx.env.USER_DATA_PATH}/noiseless.mp3`
backgroundAudioManager.src = audioMp3
},
fail (res) {
console.error(res)
}
})
}

View File

@@ -0,0 +1,10 @@
创建 云空间 上传云函数 云函数 url化 运营平台 配置云函数地址
打包时 勾选云push 2.0 push1.0 仅支持 app 推送
注意配置 百度语音相关参数
使用push2.0 请将 static\noiseless.mp3 文件 配置到运营平台 系统 配置 通知配置 push2.0 uniPush语音播报音频文件(小程序播报必填) 下
注意扩展库依赖3张opendb表opendb-tempdata,opendb-device,uni-id-device。公测版uniCloud执行扩展库会自动创建。如果你使用的是uniCloud正式版需要自己创建这3张表。

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,38 @@
// 保存图片
export function saveHeadImgFile(base64, quality) {
const bitmap = new plus.nativeObj.Bitmap("test");
return new Promise((resolve, reject) => {
// 从本地加载Bitmap图片
bitmap.loadBase64Data(base64, function() {
const url = "_doc/" + getTimeStamps() + ".png"; // url为时间戳命名方式
bitmap.save(url, {
overwrite: true, // 是否覆盖
quality: quality // 图片清晰度
}, (i) => {
console.log(url)
uni.saveImageToPhotosAlbum({
filePath: url,
success: function() {
resolve({
code: 0,
msg: '保存成功',
filePath: url
});
},
fail:function(){
msg:'保存失败'
}
});
}, (e) => {
reject('保存图片失败:' + JSON.stringify(e));
});
}, (e) => {
reject('加载图片失败:' + JSON.stringify(e));
});
})
}
function getTimeStamps (){
return (new Date()).valueOf()
}

View File

@@ -0,0 +1,175 @@
/**
* 存储管理对象
* 目标:将现有系统的所有需要存储的数据,统一管理
*
* @author terrfly
* @site https://www.jeequan.com
* @date 2022/04/13 07:18
*/
import appConfig from "@/config/appConfig.js"
// 应用级vue级别缓存 当存在时则读取此值, 否则读取storage中的值。 vue线程内的缓存不必要每次读取应用数据影响性能
const appCache = {
tokenVal: null, // token取值
currentUser: null, // 当前商户信息
}
const model = {
// 退出清空所有的缓存数据。 (不包含 环境相关)
cleanByLogout: () => {
// 1. 清空app级别缓存。
Object.keys(appCache).forEach(k => appCache[k] = null)
let envName = model.env() // 获取到当前的环境变量
uni.clearStorageSync() // 清除所有的缓存信息
model.env(envName) // 重置env
},
// 获取和放置token
token: (val, isDelete = false) => {
if (isDelete) {
appCache.tokenVal = ""
return uni.removeStorageSync(appConfig.tokenKey)
}
if (val) {
// 有值,为放置
appCache.tokenVal = val
uni.setStorageSync(appConfig.tokenKey, val)
} else {
// 否则为获取
if (!appCache.tokenVal) {
//缓存取不到,获取应用本地信息
appCache.tokenVal = uni.getStorageSync(appConfig.tokenKey)
}
return appCache.tokenVal
}
},
// 已经登录的用户记录
loggedInUser: (addUserName = null, removeUserName = null) => {
let key = "loggedInUserList"
// 删除
if (removeUserName) {
let nameList = uni.getStorageSync(key) || []
if (nameList.length <= 0) {
//不存在数据
return false
}
let hasUserIndex = nameList.indexOf(removeUserName)
if (hasUserIndex >= 0) {
nameList.splice(hasUserIndex, 1) //删除
uni.setStorageSync(key, nameList)
}
return false
}
// 有新插入的记录
if (addUserName) {
let nameList = uni.getStorageSync(key) || []
let hasUser = false
for (let i = 0; i < nameList.length; i++) {
if (nameList[i] == addUserName) {
hasUser = true
}
}
// 包含记录
if (hasUser) {
return false
}
// 最多存储 5 个
if (nameList.length >= 5) {
nameList.splice(0, 1) //删除第一个
}
nameList.push(addUserName)
uni.setStorageSync(key, nameList)
//获取
} else {
return uni.getStorageSync(key) || [] //默认空数组
}
},
// 用户信息
userInfo: (currentUserInfo) => {
if (currentUserInfo) {
// 仅保存基础数据
let saveUser = {
sysUserId: currentUserInfo.sysUserId, // 用户ID
realname: currentUserInfo.realname, // 用户姓名
avatarUrl: currentUserInfo.avatarUrl, // 头像
telphone: currentUserInfo.telphone, // 手机号
userType: currentUserInfo.userType, // 用户类型
mchNo: currentUserInfo.userNo, // 商户No
mchShortName: currentUserInfo.shortName, // 商户简称
mchType: currentUserInfo.mchType, // 商户类型
mchLevel: currentUserInfo.mchLevel, // 商户级别
isHasMemberEnt:currentUserInfo.isHasMemberEnt,// 是否购买会员模块
entIdList: currentUserInfo.entIdList, // 权限集合List
}
uni.setStorageSync("currentUserInfo", saveUser) // 改变存储
appCache.currentUser = null
}
if(!appCache.currentUser){ // 获取缓存数据
appCache.currentUser = uni.getStorageSync("currentUserInfo")
}
return appCache.currentUser
},
// 项目环境变量:(测试、 生产的切换)
env: (envMode) => {
if (envMode) {
uni.setStorageSync(appConfig.storeEnvEnumKey, envMode) // 改变存储
}
return uni.getStorageSync(appConfig.storeEnvEnumKey)
},
// push 状态是否开启
pushIsOpen: (pushFlag) => {
if (pushFlag) {
uni.setStorageSync('pushFlag', pushFlag) // 改变存储
}
return uni.getStorageSync('pushFlag')
},
// 网站信息
siteInfos: (siteInfos) => {
if (siteInfos) {
uni.setStorageSync("siteInfos", siteInfos) // 改变存储
}
return uni.getStorageSync("siteInfos")
},
// unipush2 cid
uniPush2Cid: (uniPush2Cid) => {
if (uniPush2Cid) {
uni.setStorageSync("uniPush2Cid", uniPush2Cid) // 改变存储
}
return uni.getStorageSync("uniPush2Cid")
},
uploadImgSize: (uploadImgSize) => {
if (uploadImgSize) {
uni.setStorageSync("uploadImgSize", uploadImgSize) // 存储 上传 图片大小限制
}
return uni.getStorageSync("uploadImgSize")
},
}
export default model

View File

@@ -0,0 +1,14 @@
import dayjs from 'dayjs' //时间格式库
import infoBox from '@/commons/utils/infoBox.js'
// 时间校验 用于筛选 开始 时间不能大于结束 开始结束时间必选 时间 返回值 true 和 false
export const startAndEndTime = (start, end) => {
if (!start || !end) {
infoBox.showToast('请选择开始或结束时间')
return false
}
if (dayjs(start).isAfter(end)) {
infoBox.showToast('开始时间不能大于结束时间')
return false
}
return true
}

View File

@@ -0,0 +1,54 @@
/**
* 任务执行器
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2022/11/15 16:30
*/
const model = {
// 任务启动工具, 每 xxs调用一次 知道多次x次为止。
// 注意: 启动后将立马调用一次, 而不是1s后再调用。
// 参数:
// allCount: 全部的次数 支持promise 并不一定是 秒)
// stepSecond 步伐(单位: 秒)
// callbackFunc 回调函数, 支持返回 boolean 或者 promise ,
// 注意: boolean类型 true 进入下一次循环, false: 停止任务。
// promise类型 then 进入下一次循环, catch 停止任务。
startTimeoutTask: (stepSecond, allCount, callbackFunc) => {
// 不存在回调函数
if(!callbackFunc){
return false;
}
let callbackResult = callbackFunc(allCount)
// 明确返回false, 说明不再循环
if(callbackResult === false){
return false
}
// 不包含剩余次数了。
if(allCount <= 0){
return false
}
// promise
if(typeof callbackResult == 'object'){
callbackResult.then(() => {
setTimeout(() => model.startTimeoutTask(stepSecond, --allCount, callbackFunc), (stepSecond * 1000) )
})
}else{ // 其他boolean类型 或返回不明确, 继续下一次任务。
setTimeout(() => model.startTimeoutTask(stepSecond, --allCount, callbackFunc), (stepSecond * 1000) )
}
}
}
export default model

View File

@@ -0,0 +1,62 @@
/**
* 统一扫码, 支持登录、 码牌绑定、 打印机、 等
*
* @author terrfly
* @site https://www.jeepay.vip
* @date 2022/11/22 10:38
*/
import { $parseQrCodeUrl } from '@/http/apiManager.js'
const model = {
// 扫码结果类型
QR_TYPE_LOGIN: 'QR_TYPE_LOGIN', // 登录
QR_TYPE_QRC: 'QR_TYPE_QRC', // 码牌
QR_TYPE_PRINTER: 'QR_TYPE_PRINTER', // 打印机
QR_TYPE_OTHER: 'QR_TYPE_OTHER', // 其他
returnFunc: (type, bizValue, originQrVal) => {
return { type: type, bizValue: bizValue, originQrVal: originQrVal}
},
// 解析 码牌
parseQrc: (originQrVal) => {
return $parseQrCodeUrl(originQrVal).then( ({bizData}) => {
return model.returnFunc(model.QR_TYPE_QRC, bizData, originQrVal)
})
},
// 返回 类型 和 扫码的值
// 参数: 是否解析qrc , 默认不解析
scan: (isParseQRC = false) => {
return uni.scanCode().then(({ result }) => {
// 登录类型
if(result.startsWith("JEEPAY_LOGIN_QR_")){
return model.returnFunc(model.QR_TYPE_LOGIN, result.substring(16), result)
}
if(isParseQRC){
return model.parseQrc(result).then( (res) => {
return res;
}).catch(() => {
return model.returnFunc(model.QR_TYPE_OTHER, result, result)
})
}else{
return model.returnFunc(model.QR_TYPE_OTHER, result, result)
}
})
}
}
export default model

View File

@@ -0,0 +1,121 @@
import { $versionDetection } from '@/http/apiManager.js';
import appConfig from '@/config/appConfig.js'
// app更新
function appPlusUpdate(sign){
// 获取版本号
plus.runtime.getProperty(plus.runtime.appid, function(inf) {
// 调起接口查询
$versionDetection({versionNumber: inf.versionCode})
.then(({ bizData }) => {
//返回data为空或者版本号一致
if (!bizData || Object.keys(bizData).length == 0 || !bizData.versionSerialNumber || bizData.versionSerialNumber == inf.versionCode) {
if(sign === 'checked') {
uni.showToast({
title: '已是最新版本'
})
}
return false;
}
// 是否强制更新
let isForceUpdate = bizData.forceUpdate == 1
uni.showModal({
title: '发现新版本:' + bizData.versionName,
showCancel: !isForceUpdate ,
content: bizData.versionDesc,
confirmText: '立即更新',
confirmColor: '#108EE9',
success: function(r) {
if (r.confirm) {
downLoad(bizData.downloadUrl);
}else{
// 强制更新也需要更新
if(isForceUpdate){
downLoad(bizData.downloadUrl);
}
}
}
});
});
});
}
//下载更新包
function downLoad(url) {
if (!url) {
uni.showToast({icon: 'none',title: '下载地址错误'});
return false;
}
// 下载文件
uni.showLoading({title: '更新中'});
uni.downloadFile({
url: url,
success: res => {
uni.hideLoading();
if (res.statusCode === 200) {
uni.showToast({title: '下载成功'});
plus.runtime.install(res.tempFilePath);
}
},
fail: res => {
uni.hideLoading();
}
});
}
// 获取当前版本 & 检查
export function getCurrentVersionPromise() {
let isApp = false
// #ifdef APP-PLUS
isApp = true
// #endif
if(!isApp){
return Promise.reject()
}
// 获取版本号
return new Promise((resolve, reject) => {
plus.runtime.getProperty(plus.runtime.appid, function(inf) {
resolve(inf)
})
})
}
// 获取当前版本 & 检查
export function checkCurrVersion(sign) {
// #ifdef APP-PLUS
switch(uni.getSystemInfoSync().platform){
case 'android':
appPlusUpdate(sign) //仅安卓更新
break;
case 'ios':
break;
default:
break;
}
// #endif
}
//同步取出ext.json对象
export function getExtStoreId(){
try{
const extConfig = uni.getExtConfigSync()
if(extConfig.domain){
appConfig.env.JEEPAY_BASE_URL = extConfig.domain
}
// uni.showToast({title: JSON.stringify(extConfig),icon:"none",duration:3000});
console.log(extConfig,'extJson对象');
return extConfig;
}catch(err){
console.log(err,'getExtStoreId__error')
}
}

View File

@@ -0,0 +1,37 @@
<!--
组件功能 对button的封装
@author terrfly
@site https://www.jeequan.com
@date 2022/12/05 14:39
-->
<template>
<button class="jeepay-btn" hover-class="jeepay-hover-button">
<slot />
</button>
</template>
<script setup>
const props = defineProps({
})
</script>
<style lang="scss" scoped>
.jeepay-btn {
display: flex;
justify-content: center;
align-items: center;
height: 110rpx;
font-size: 33rpx;
font-weight: 500;
color: $J-color-tff;
border-radius: 20rpx;
background: $jeepay-bg-primary;
box-shadow: 0 20rpx 60rpx -20rpx rgba(0,84,210,0.5);
&.jeepay-hover-button {
opacity: 0.5;
}
}
</style>

View File

@@ -0,0 +1,133 @@
<template>
<uni-popup ref="refPopup" type="center" @change='change' :safe-area="false">
<view class="popup-wrapper">
<view class="title">用户服务协议与隐私政策</view>
<view class="content">
如需登录请先认真阅读并同意{{ $appName }}
<text class="info" @tap="go.to(service)">用户服务协议</text>
<text class="info" @tap="toPrivacy">隐私政策</text>
</view>
<view class="but-wrapper">
<view class="but-item" hover-class="touch-button" @tap="disagreeClose">拒绝</view>
<button class="but-item agree" id="agree-btn" open-type="agreePrivacyAuthorization"
@agreeprivacyauthorization="handAgree">同意</button>
</view>
</view>
</uni-popup>
</template>
<script setup>
import { reactive, ref, onMounted } from 'vue'
import go from '@/commons/utils/go.js'
const props = defineProps({
service: { type: String },
privacy: { type: String }
})
const refPopup = ref(null)
const emits = defineEmits(['agree'])
const open = () => {
getPrivacy()
refPopup.value.open()
}
const vdata = reactive({})
const close = () => refPopup.value.close()
const toPrivacy = () => {
// #ifdef APP-PLUS
if (props.privacy) return go.to(props.privacy)
// #endif
// 打开小程序隐私政策
// #ifdef MP-WEIXIN
wx.openPrivacyContract(
{
fail: () => {
uni.showToast({
title: '打开失败请稍后重试', // 打开失败
icon: 'none'
})
},
}
)
// #endif
}
function disagreeClose () {
close()
}
// 获取微信你用户是否同意过隐私政策
const getPrivacy = () => {
wx.getPrivacySetting({
success: (r) => {
Object.assign(vdata, r)
if (vdata.needAuthorization) {
wx.onNeedPrivacyAuthorization(res => {
vdata.resolve = res
})
}
}
})
}
const handAgree = () => {
if (vdata.needAuthorization) {
vdata.resolve({ buttonId: 'agree-btn', event: 'agree' })
}
emits('agree')
close()
}
defineExpose({ open, close })
</script>
<style lang="scss" scoped>
.popup-wrapper {
width: 600rpx;
border-radius: 20rpx;
background: #FFF;
overflow: hidden;
.title {
display: flex;
justify-content: center;
align-items: center;
padding: 30rpx 0 50rpx;
text-align: center;
color: rgba(0, 0, 0, 0.50);
font-size: 28rpx;
}
.content {
padding-bottom: 70rpx;
margin: 0 auto;
width: 500rpx;
color: #000;
font-size: 28rpx;
line-height: 1.5;
.info {
color: $v-color-t21;
}
}
.but-wrapper {
display: flex;
.but-item {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
height: 110rpx;
color: rgba(0, 0, 0, 0.70);
font-size: 32rpx;
font-weight: 400;
border-radius: 0;
}
.agree {
background: $jeepay-bg-primary;
color: #fff;
padding: 0;
}
}
}
</style>

View File

@@ -0,0 +1,185 @@
<template>
<!-- 修改应用弹窗 -->
<uni-popup ref="popup" type="bottom" mask-background-color="rgba(0,0,0,.5)" :safe-area="false" @maskClick="emits('cancel')">
<view class="card-wrapper">
<view class="input-search">
<uni-easyinput
v-model="vadta.searchInfo"
prefixIcon="search"
:inputBorder="false"
:clearable="false"
:styles="styles"
type="text"
placeholder="搜索门店名称、ID"
placeholderStyle=" color: rgba(0,0,0,0.85);font-size: 30rpx;"
/>
<view class="search-button">搜索</view>
</view>
<!-- 循环开始部分 -->
<view class="store">
<view class="left">
<view class="store-dot"></view>
<view class="app-info">
<view class="app-name">应用名称</view>
<view class="app-code">62a55637e4b07ea5b8717ca3</view>
</view>
</view>
</view>
<!-- 循环结束部分 -->
<view class="footer-wrapper">
<view class="footer-main">
<view class="tips" v-if="tips">{{ tips }}</view>
<view class="footer-button">
<view class="flex-center" hover-class="touch-button" @tap="close">取消</view>
<view class="confirm flex-center" hover-class="touch-button">确认</view>
</view>
</view>
</view>
</view>
</uni-popup>
</template>
<script setup>
import { onMounted, reactive, ref } from 'vue'
const emits = defineEmits(['cancel', 'confirm'])
const props = defineProps({
isAll: { type: Boolean, default: false }, //是否展示全部门店 默认false
tips: { type: String }, //提示信息 传入则展示
})
const styles = reactive({
backgroundColor: '#f7f7f7',
height: '90rpx',
borderRadius: '10rpx',
color: '#000',
})
const vadta = reactive({
searchInfo: '',
})
const popup = ref(null)
const open = (val) => {
popup.value.open()
}
const close = () => {
emits('cancel')
popup.value.close()
}
const confirm = () => {}
onMounted(() => {
open()
})
defineExpose({ open })
</script>
<style lang="scss" scoped>
.card-wrapper {
border-radius: 32rpx 32rpx 0 0;
background-color: #fff;
overflow: hidden;
min-height: 70vh;
.input-search {
display: flex;
align-items: center;
padding-left: 10rpx;
margin: 30rpx;
border-radius: 10rpx;
background-color: #f7f7f7;
.search-button {
padding: 24rpx 30rpx;
font-size: 32rpx;
font-weight: 500;
color: #2980fd;
}
}
.store {
position: relative;
display: flex;
align-items: center;
padding: 0 40rpx;
height: 170rpx;
font-size: 30rpx;
.left {
display: flex;
align-items: center;
.store-dot {
align-self: start;
position: relative;
top: 2rpx;
margin-right: 20rpx;
width: 36rpx;
height: 36rpx;
background-color: #d7d8d9;
border-radius: 50%;
&::after {
content: '';
display: block;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 50%;
height: 50%;
border-radius: 50%;
background-color: #fff;
}
}
.active-dot {
background-color: #2980fd;
}
}
.app-info {
.app-code {
margin-top: 15rpx;
font-size: 26rpx;
color: #999;
}
}
}
.all-store::after {
content: '';
display: block;
position: absolute;
bottom: 0;
left: 40rpx;
right: 40rpx;
height: 1rpx;
background-color: #ededed;
}
.footer-wrapper {
height: 186rpx;
.footer-main {
position: fixed;
left: 0;
right: 0;
bottom: env(safe-area-inset-bottom);
border-top: 1rpx solid #ededed;
.tips {
margin: 20rpx;
text-align: center;
font-size: 27rpx;
color: #a6a6a6;
}
.footer-button {
padding: 0 30rpx;
margin-top: 30rpx;
padding-bottom: 30rpx;
display: flex;
justify-content: space-between;
view {
width: 330rpx;
height: 110rpx;
font-size: 33rpx;
font-weight: 500;
color: rgba(0, 0, 0, 0.5);
border-radius: 20rpx;
background-color: #f7f7f7;
}
.confirm {
color: #fff;
background: jeepay-bg-primary;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,57 @@
<template>
<!-- 单元格布局 -->
<view
class="cell-main"
:hover-class="isTouch ? 'touch-hover' : ''"
:style="{ '--border-width': borderWidth, marginTop: mT + 'rpx', '--m-rl': mRL + 'rpx', backgroundColor: bgColor }"
>
<view class="title" :style="{ color: color }"> {{ title }} </view>
<slot name="right">
<image src="/static/iconImg/icon-arrow-right.svg" mode="scaleToFill" />
</slot>
</view>
</template>
<script setup>
const props = defineProps({
title: { type: String }, //标题
mT: { type: Number }, //上边距
mRL: { type: Number }, //左右两侧边距
bgColor: { type: String }, //背景颜色
color: { type: String }, //标题字体颜色
borderWidth: { type: String, default: '100vw' }, //边框距离左侧距离 默认100vw 没有边框
isTouch: { type: Boolean, default: true }, //是否有触摸反馈
})
</script>
<style lang="scss" scoped>
.cell-main {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
margin-left: var(--m-rl);
margin-right: var(--m-rl);
height: 120rpx;
.title {
margin-left: 40rpx;
font-size: 32rpx;
font-weight: 400;
}
image {
width: 120rpx;
height: 120rpx;
}
background-color: #fff;
&::after {
content: '';
position: absolute;
bottom: 0;
right: 0;
z-index: 10;
width: calc(100vw - var(--border-width));
height: 1rpx;
background-color: #ededed;
}
}
</style>

View File

@@ -0,0 +1,65 @@
<template>
<template v-if="subTitle">
<view class="details-wrapper" style="align-items: flex-start; padding-top: 40rpx">
<view class="details-title">{{ title }}</view>
<view>
<view class="sub-title">
<view class="details-info single-text-beyond"> {{ subTitle }}</view>
<image src="/static/iconImg/icon-arrow-small.svg" mode="scaleToFill" />
</view>
<view class="sub-info details-info single-text-beyond">{{ info }}</view>
</view>
</view>
</template>
<template v-else>
<view class="details-wrapper">
<view class="details-title">{{ title }}</view>
<view class="details-info single-text-beyond">{{ info }}</view>
<image src="/static/iconImg/icon-arrow-right.svg" mode="scaleToFill" />
</view>
</template>
</template>
<script setup>
const props = defineProps({
title: { type: String }, //标题
info: [String, Number], //内容
subTitle: { type: String }, //副标题
})
</script>
<style lang="scss" scoped>
.details-wrapper {
display: flex;
align-items: center;
padding-left: 40rpx;
min-height: 120rpx;
font-size: 30rpx;
background-color: #fff;
.details-title {
width: 200rpx;
color: #4c4c4c;
}
.details-info {
flex: 1;
width: 334rpx;
}
image {
width: 108rpx;
height: 120rpx;
}
}
.sub-title {
display: flex;
image {
width: 108rpx;
height: 40rpx;
}
}
.sub-info {
margin-top: 20rpx;
font-size: 30rpx;
color: #a1a1a1;
padding-bottom: 40rpx;
}
</style>

View File

@@ -0,0 +1,36 @@
<template>
<!-- 详情页 带开关卡片布局 -->
<view class="s-wrapper">
<view class="s-title">
<text>{{ title }}</text>
<slot name="titleRight" />
</view>
<view class="s-tips">{{ subTitle }}</view>
</view>
</template>
<script setup>
const props = defineProps({
title: { type: String }, //标题
subTitle: { type: String }, //提示信息
})
</script>
<style lang="scss" scoped>
.s-wrapper {
padding: 40rpx;
margin: 30rpx 35rpx;
border-radius: $J-b-r32;
background-color: #fff;
.s-title {
display: flex;
justify-content: space-between;
font-size: 30rpx;
color: #4d4d4d;
}
.s-tips {
font-size: 25rpx;
color: #808080;
}
}
</style>

View File

@@ -0,0 +1,164 @@
<template>
<view class="k-wrapper">
<view class="k-store-name">
<view class="k-store-title flex-center">收款门店</view>
<view class="flex-center" @tap="emits('selectedStore')"> {{ storeName || '选择门店' }} <image src="/static/iconImg/icon-arrow-black.svg" mode="scaleToFill" /></view>
</view>
<view class="k-main">
<block v-for="(v, i) in keyList" :key="i">
<view :class="[v.clsName]" hover-class="touch-number" hover-stay-time="150" v-if="v.type == 'number'" @tap="boardDown(v.value)">{{ v.value }}</view>
<view :class="[v.clsName]" hover-class="touch-number" hover-stay-time="150" v-if="v.type == 'image'" @tap="boardDown(v.value)">
<image :src="v.imgUrl" mode="scaleToFill" />
</view>
<view :class="[v.clsName]" hover-class="touch-button" hover-stay-time="150" v-if="v.type == 'button'" @tap="boardDown(v.value)">
<image :src="v.imgUrl" mode="scaleToFill" />
<text>{{ v.text }}</text>
</view>
</block>
</view>
</view>
</template>
<script setup>
import { reactive, ref } from 'vue'
const emits = defineEmits(['boardDown', 'selectedStore',"update:value"])
/***
* 键盘渲染集合
* boardDown 回调事件 回调参数 value
* selectedStore 回调事件 选择门店 回调参数 无
* storeName 门店名称
* */
const props = defineProps({
storeName: { type: String }, //门店名称
point: { type: Number, default: 2 }, //限制小数点后几位 默认两位
maxLength: { type: Number, default: 10 }, //最大输入长度 包含小数点
value:{type:[String,Number],default:'0'}
})
const keyList = reactive([
{ type: 'number', clsName: 'k-number', value: '1' },
{ type: 'number', clsName: 'k-number', value: '2' },
{ type: 'number', clsName: 'k-number', value: '3' },
{ type: 'number', clsName: 'k-number', value: '4' },
{ type: 'number', clsName: 'k-number', value: '5' },
{ type: 'number', clsName: 'k-number', value: '6' },
{ type: 'number', clsName: 'k-number', value: '7' },
{ type: 'number', clsName: 'k-number', value: '8' },
{ type: 'number', clsName: 'k-number', value: '9' },
{ type: 'number', clsName: 'k-number', value: '.' },
{ type: 'number', clsName: 'k-number k-zero', value: '0' },
{ type: 'image', clsName: 'k-number', value: 'deleted', imgUrl: '/static/iconImg/icon-delete.svg' },
{ type: 'button', clsName: 'k-button', value: 'scan', imgUrl: '/static/iconImg/icon-scan.svg', text: '扫一扫' },
{ type: 'button', clsName: 'k-button k-button-code', value: 'code', imgUrl: '/static/iconImg/icon-code.svg', text: '聚合码' },
])
const deletedNumber = () => {
emits('update:value', props.value.toString().slice(0, props.value.length - 1))
}
const boardDown = (val) => {
// 如果 点击 扫一扫 或聚合码 直接回调事件
if (val == 'scan' || val == 'code') return emits('boardDown', val)
// 点击删除 调用函数
if (val == 'deleted') return deletedNumber()
// 如果值已经是0并且按下不是小数点 直接替换值
if (props.value.toString().length == 1 && props.value == 0 && val != '.') return emits('update:value', val)
// 只能包含一个小数点
if (props.value.toString().includes('.') && val == '.') return
// 限制小数点位数
if (props.value.toString().includes('.') && props.value.split('.')[1].length >= props.point) return
// 长度只有 一位 并且按下结果是小数点 直接 return
if (props.value.toString().length == 1 && props.value == 0 && val != '.') return
// 如果 清空后直接按下小数点 直接 return
if ((props.value === '' || props.value == undefined) && val == '.') return emits('update:value', '0' + val)
// 超出最大输入长度 直接 return
if (props.value.toString().length >= props.maxLength) return
// 回调结果
emits('update:value', props.value + val)
}
</script>
<style lang="scss" scoped>
.k-wrapper {
padding: 0 10rpx;
border-radius: 72rpx 72rpx 0 0;
border-top: 1rps solid rgba($color: #000000, $alpha: 0.1);
box-shadow: 0 -30rpx 80rpx -20rpx rgba(0, 0, 0, 0.05);
.k-store-name {
display: flex;
align-items: center;
justify-content: space-between;
height: 110rpx;
margin: 10rpx 0;
padding: 0 60rpx;
view {
flex-grow: 0;
height: 100%;
font-size: 30rpx;
font-weight: 500;
image {
width: 40rpx;
height: 40rpx;
transform: rotate(180deg);
margin-left: 10rpx;
}
}
.k-store-title {
white-space: nowrap;
font-size: 30rpx;
font-weight: 400;
color: $J-color-t4d;
}
}
.k-main {
display: grid;
grid-template-columns: repeat(3, 200rpx);
grid-template-rows: repeat(5, 120rpx);
grid-gap: 10rpx 20rpx;
padding: 0 50rpx;
font-size: 60rpx;
border-radius: 30rpx 30rpx 0 0;
overflow: hidden;
.k-number {
display: flex;
justify-content: center;
align-items: center;
border-radius: 10rpx;
image {
width: 60rpx;
height: 60rpx;
}
}
.k-button {
grid-column: 1 / span 2;
grid-row: 5 / span 2;
display: flex;
align-items: center;
justify-content: center;
margin-top: 20rpx;
width: 315rpx;
height: 110rpx;
background: $jeepay-bg-primary;
font-size: 26rpx;
border-radius: 10rpx;
color: #fff;
image {
width: 40rpx;
height: 40rpx;
margin-right: 10rpx;
}
}
.k-button-code {
transform: translateX(335rpx);
background: linear-gradient(270deg, rgba(72, 192, 255, 1) 0%, rgba(51, 157, 255, 1) 100%);
}
.touch-number {
border-radius: 80rpx;
background-color: #e9eaeb;
}
.touch-button {
opacity: 0.5;
}
}
}
</style>

View File

@@ -0,0 +1,97 @@
<template>
<view class="p-wrapper">
<input class="p-input" type="number" v-model="info" :focus="vdata.isFoucs" :maxlength="num" @input="inputChange" />
<view class="p-main" :style="{ margin: margin }">
<div class="p-number flex-center" v-for="v in num" :key="v" :class="{ 'p-active': v <= info.length }"></div>
</view>
</view>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
const emits = defineEmits(['inputChange'])
const props = defineProps({
focus: { type: Boolean, default: false }, //是否自动获取焦点 默认false
num: { type: Number, default: 6 }, //密码框数量
margin: { type: String }, //边距
})
const info = ref('')
const vdata = reactive({
isFoucs: false
})
onMounted(() => {
// 解决无法聚焦的问题, 怀疑页面没有渲染好导致。 nextTick也不好使。
// 偶尔出现: 1. 键盘不弹出, 2.键盘弹出, 输入无法聚焦的input.
if(props.focus){
setTimeout(() => {vdata.isFoucs = true}, 500)
}
})
const clearInput = () => (info.value = '')
const inputChange = () => {
emits('inputChange', info.value)
}
defineExpose({ clearInput })
</script>
<style lang="scss" scoped>
.p-wrapper {
position: relative;
width: 100%;
height: 80rpx;
overflow: hidden;
&::after {
content: '';
display: block;
position: absolute;
top: 0;
left: 0;
z-index: 10;
width: 95rpx;
height: 100%;
}
&::before {
content: '';
display: block;
position: absolute;
top: 0;
right: 0;
z-index: 10;
width: 95rpx;
height: 100%;
}
.p-input {
position: absolute;
top: 10%;
left: -100vw;
right: 0;
opacity: 0;
color: transparent;
caret-color: transparent;
}
.p-main {
display: flex;
justify-content: space-between;
margin: 0 95rpx;
.p-number {
width: 80rpx;
height: 80rpx;
border-radius: 12rpx;
background-color: #f2f2f2;
}
.p-active::after {
content: '';
display: block;
width: 20rpx;
height: 20rpx;
border-radius: 50%;
background-color: #000;
}
}
}
</style>

View File

@@ -0,0 +1,53 @@
<template>
<view class="search-wrapper" :style="{ paddingTop: pTop + 'rpx', backgroundColor: bgColor }">
<view class="search-main" @tap="emits('click')">
<image src="/static/iconImg/icon-search.svg" mode="scaleToFill" />
<input type="text" :placeholder="place" placeholder-style="color: rgba(0,0,0,0.35);" :style="{ fontSize: size + 'rpx' }" disabled />
</view>
<slot name="right" />
</view>
</template>
<script setup>
const props = defineProps({
place: { type: String }, //提示语
pTop: { type: Number }, //距离上边距离 使用padding 为了有背景色
bgColor: { type: String }, //背景色
size: { type: Number, default: 27 }, //搜素框文字大小
})
const emits = defineEmits(['click'])
</script>
<style lang="scss" scoped>
.search-wrapper {
display: flex;
align-items: center;
height: 110rpx;
background-color: $J-bg-ff;
padding: 0 30rpx;
.search-main {
flex: 1;
display: flex;
align-items: center;
height: 70rpx;
background-color: $J-bg-f5;
border-radius: $J-b-r12;
image {
padding: 0 22rpx;
width: 26rpx;
height: 26rpx;
}
input {
flex: 1;
}
.search-text {
padding: 0 30rpx;
font-size: 32rpx;
font-weight: 500;
color: $J-color-t29;
}
}
}
</style>

View File

@@ -0,0 +1,116 @@
<template>
<uni-popup ref="popup" type="bottom" mask-background-color="rgba(0,0,0,.5)" @change="change" :safe-area="false">
<view class="tips-wrapper">
<view class="tips-text flex-center" v-if="title">{{ title }}</view>
<scroll-view scroll-y class="list-wrap">
<block v-for="v in list" :key="v[value]">
<view
class="single-text flex-center"
hover-class="u-cell-hover"
hover-stay-time="150"
:style="{ color: v[value] == selected ? activeColor : v.color }"
@tap="confirm(v)"
>
{{ v[label] }}
</view>
</block>
</scroll-view>
<view class="line"></view>
<view class="tips-text tips-cancel flex-center" hover-class="u-cell-hover" hover-stay-time="150" @tap="popup.close()">取消</view>
</view>
</uni-popup>
<JeepayPopupConfirm ref="jeepayPopupConfirm" />
</template>
<script setup>
import { reactive, ref, inject } from 'vue';
const emits = defineEmits(['confirm']);
const props = defineProps({
title: { type: String }, //标题 传入展示 不传则隐藏
activeColor: { type: String, default: '#2980FD' }, //选中后文字颜色
textColor: { type: String }, //列表文字颜色
textSize: { type: String }, //列表文字大小
titleColor: { type: String }, //标题文字颜色
titleSize: { type: String }, //标题文字大小
list: { type: Array }, //渲染单选列表集合
label: { type: String, default: 'label' }, //展示文字的键名 可传值复写
value: { type: String, default: 'value' } //展示文字的key 可传值复写
});
const selected = ref(undefined);
const popup = ref(null);
const jeepayPopupConfirm = ref();
// 打开弹窗 val 选中数据的key
const open = (val) => {
selected.value = val;
popup.value.open();
};
const confirm = (listItem) => {
popup.value.close();
// 包含回调函数
if (listItem.fun) {
// 包含确认弹层。
if (listItem.confirmText) {
jeepayPopupConfirm.value
.open(listItem.confirmText)
.then(() => {
listItem.fun();
})
.catch(() => {
popup.value.open();
});
} else {
listItem.fun();
}
} else {
1;
emits('confirm', listItem);
}
};
let changePageMetaOverflowFunc = inject('changePageMetaOverflowFunc');
const change = (e) => {
if (changePageMetaOverflowFunc) {
changePageMetaOverflowFunc(!e.show);
}
};
const close = () => popup.value.close();
defineExpose({ open, close });
</script>
<style lang="scss" scoped>
.list-wrap {
max-height: 60vh;
}
.tips-wrapper {
// overflow: hidden;
border-radius: 32rpx 32rpx 0 0;
padding-top: 20rpx;
padding-bottom: 60rpx;
background-color: #fff;
.tips-text {
text-align: center;
height: 90rpx;
font-size: 30rpx;
border-bottom: 1rpx solid rgba(0, 0, 0, 0.07);
}
.single-text {
height: 120rpx;
}
.line {
height: 20rpx;
background-color: rgba(0, 0, 0, 0.07);
}
.tips-cancel {
height: 110rpx;
color: $J-color-t80;
font-size: 33rpx;
border: none;
}
.u-cell-hover {
background-color: #f8f9fa;
}
}
</style>

View File

@@ -0,0 +1,45 @@
<template>
<switch :checked="flag" :disabled="disabled" color="#238FFC" :style="{ transform: 'scale(' + scale + ')', margin: margin }" @change="change" @tap="emits('click')" />
<JeepayPopupConfirm ref="jeepayPopupConfirmRef" />
</template>
<script setup>
import { ref, watchEffect } from 'vue'
const props = defineProps({
scale: { type: Number, default: 0.8 }, //控制开关大小 倍数 默认.8
margin: { type: String, default: '0' }, // 控制开关外边距默认0
tips: { type: String }, //修改提示语不传使用 默认tips弹出层提示语
bol: { type: Boolean }, //开关状态
index: { type: Number, default: 0 }, // 列表渲染索引值
confirmTips: { type: Boolean, default: true }, //是否需要提示弹窗默认 需要
disabled: { type: Boolean, default: false }, // 是否禁用 默认否
})
const emits = defineEmits(['confirm', 'cancel', 'click'])
const flag = ref()
watchEffect(() => {
flag.value = props.bol
})
const jeepayPopupConfirmRef = ref() //提示弹窗
const change = (e) => {
flag.value = e.detail.value
if (!props.confirmTips) return confirm()
jeepayPopupConfirmRef.value
.open(props.tips || '确定修改状态?')
.then(() => {
confirm()
})
.catch(() => {
flag.value = !flag.value
emits('cancel', flag.value)
})
}
const confirm = () => {
emits('confirm', flag.value)
}
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,55 @@
<template>
<view class="manage-main" :style="{ '--border-width': borderWidth }">
<view class="manage-title" :style="{ fontSize: titleSize, color: titleColor }">
{{ title }}
<slot name="right"></slot>
</view>
<view class="manage-info" :style="{ width: tipsWidth + 'rpx', fontSize: titleSize, color: titleColor }">{{ tips }}</view>
</view>
</template>
<script setup>
const props = defineProps({
title: { type: String }, //标题
tips: { type: String }, //提示信息
tipsWidth: { type: Number }, // 提示信息宽度 默认500rpx
titleSize: { type: Number }, //标题字体大小
tipsSize: { type: Number }, //提示信息字体大小
titleColor: { type: String }, //标题文字颜色
tipsColor: { type: String }, //提示信息文字颜色
borderWidth: { type: String, default: '40rpx' }, //边框距离左侧距离 默认80rpx
})
</script>
<style lang="scss" scoped>
.manage-main {
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
padding: 0 40rpx;
height: 198rpx;
background-color: #fff;
&::after {
content: '';
position: absolute;
bottom: 0;
right: 0;
z-index: 10;
width: calc(100vw - var(--border-width));
height: 1rpx;
background-color: #ededed;
}
.manage-title {
display: flex;
align-items: center;
justify-content: space-between;
color: #4d4d4d;
}
.manage-info {
margin-top: 12rpx;
width: 500rpx;
font-size: 25rpx;
color: #999999;
}
}
</style>

View File

@@ -0,0 +1,117 @@
<template>
<scroll-view
class="table-list"
scroll-y
:style="{ '--height': height }"
@scrolltolower="addNext"
@refresherrefresh="openRefresh"
:refresher-enabled="refresh"
:refresher-triggered="vdata.isRefresh"
>
<template v-for="(record, i) in vdata.allData" :key="i">
<slot name="tableBody" :record="record" :index="i" />
</template>
<view class="list-null" v-if="!vdata.apiResData.hasNext && showListNull">暂无更多数据</view>
</scroll-view>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
import { onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app';
// 定义传入属性
const props = defineProps({
reqTableDataFunc: { type: Function, default: () => {} },
searchData: { type: Object, default: () => {} }, // 搜索条件参数
pageSize: { type: Number, default: 10 }, // 默认每页条数
initData: { type: Boolean, default: true }, // 初始化列表数据, 默认true
showListNull: { type: Boolean, default: true }, //是否显示缺省 默认显示
height: { type: String, default: '88rpx' }, // 高度 这里的高度 为 外部使用高度 scroll-view 高度 为 100vh - 传入高度
refresh: { type: Boolean, default: false } //是否开启下拉刷新 默认不开启
});
const vdata = reactive({
allData: [], // app与web不同 app是每次查询到数据会拼接到后面
// 接口返回的数据
apiResData: { total: 0, records: [] },
// 分页参数
iPage: { pageNumber: 1, pageSize: props.pageSize },
// 下拉刷新开启状态
isRefresh: false
});
onMounted(() => {
//初始化表数据
props.initData ? refTable(true) : undefined;
});
// 查询表格数据
function refTable(isToFirst = false) {
if (isToFirst) {
// 重新搜索, 第一页。
vdata.iPage.pageNumber = 1;
vdata.allData = []; //清空数据
}
// 更新检索数据
return props.reqTableDataFunc(Object.assign({}, vdata.iPage, props.searchData)).then(({ bizData }) => {
Object.assign(vdata.apiResData, bizData); // 列表数据更新
if (bizData.records) {
bizData.records.forEach((r) => vdata.allData.push(r));
}
});
}
/** 追加下一页数据 **/
function addNext() {
// console.log('加载下一页')
// 包含下一页
if (vdata.apiResData.hasNext) {
vdata.iPage.pageNumber++;
refTable(false);
}
}
// 下拉刷新
const openRefresh = () => {
vdata.isRefresh = true;
refTable(true).then(() => {
setTimeout(() => {
vdata.isRefresh = false;
}, 300);
});
};
// 将表格事件暴露出去 https://www.jianshu.com/p/39d14c25c987
defineExpose({ refTable, addNext });
</script>
<style lang="scss" scoped>
.table-list {
height: calc(75vh - var(--height));
}
.list-null {
position: relative;
height: 110rpx;
display: flex;
align-items: center;
justify-content: center;
padding-bottom: 70rpx;
gap: 20rpx;
font-size: 26rpx;
color: #a6a6a6;
&::after,
&::before {
content: '';
display: block;
width: 30%;
height: 2rpx;
background-color: #ededed;
transform: translateY(-50%);
}
}
</style>

View File

@@ -0,0 +1,58 @@
<template>
<uni-popup ref="popup" type="bottom" mask-background-color="rgba(0,0,0,.5)" @change='change' :safe-area="false" @maskClick="emits('cancel')">
<!-- 通用提示弹窗 用于提示用户 数据含义 -->
<view class="card-wrapper">
<view class="card-title flex-center">{{ title }}</view>
<slot />
<view class="card-button flex-center" hover-class="touch-hover" @tap="confirm"> {{ buttonText }}</view>
</view>
</uni-popup>
</template>
<script setup>
import { onMounted, reactive, ref,inject } from 'vue'
const emits = defineEmits(['cancel'])
const props = defineProps({
title: { type: String }, //标题
buttonText: { type: String }, //按钮文字
})
const popup = ref(null)
const open = (val) => {
popup.value.open()
}
const confirm = () => {
emits('cancel')
popup.value.close()
}
let changePageMetaOverflowFunc = inject('changePageMetaOverflowFunc')
const change = (e)=>{
if(changePageMetaOverflowFunc){
changePageMetaOverflowFunc(!e.show)
}
}
defineExpose({ open })
</script>
<style lang="scss" scoped>
.card-wrapper {
border-radius: 32rpx 32rpx 0 0;
background-color: #fff;
padding-bottom: 60rpx;
max-height: 70vh;
.card-title {
margin-bottom: 20rpx;
height: 110rpx;
font-size: 30rpx;
font-weight: 400;
border-bottom: 1rpx solid rgba(0, 0, 0, 0.07);
}
.card-button {
margin-top: 20rpx;
height: 110rpx;
font-size: 32rpx;
color: #2980fd;
border-top: 20rpx solid #f7f7f7;
}
}
</style>

View File

@@ -0,0 +1,53 @@
<template>
<!-- 单张广告 -->
<view class="ad-wrapper" v-if="list.length == 1">
<image :src="list[0].imgUrl" mode="aspectFill" @tap='toH5(list[0].linkUrl)' />
</view>
<!-- 多张广告 -->
<view class="ads-wrapper" v-else>
<block v-for="(v, i) in list" :key="i">
<view>
<image :src="v.imgUrl" mode="aspectFill" @tap='toH5(v.linkUrl)' />
</view>
</block>
</view>
</template>
<script setup>
const props = defineProps({
list: { type: Array, default: [] }
})
const toH5 = (url) => {
if (!url) return
uni.navigateTo({
url: '/pages/adH5/adH5?url=' + url
})
}
</script>
<style lang="scss" scoped>
.ad-wrapper {
margin: 40rpx;
max-height: 680rpx;
border-radius: 30rpx;
overflow: hidden;
}
.ads-wrapper {
display: flex;
justify-content: space-between;
margin: 30rpx;
view {
width: calc(50% - 20rpx);
max-height: 400rpx;
border-radius: 30rpx;
overflow: hidden;
}
}
image {
width: 100%;
}
</style>

View File

@@ -0,0 +1,237 @@
<template>
<view class="start" v-if="show">
<view class="skip" :style="skipPositionStyle" @click="onSkip">跳过 {{ time2 }}</view>
<swiper class="swiper" :interval="interval" :autoplay="list.length > 1" @change="onChangeSwiper">
<swiper-item v-for="(item, index) in list" :key="index">
<view class="swiper-item" :style="bgStyle">
<image class="image" :src="item.imgUrl" mode="aspectFill" @tap="toAdH5(item)"></image>
<view class="after" :style="afterStyle"></view>
</view>
</swiper-item>
<!-- autoplay -->
</swiper>
<view class="swiper-dot" v-if="list.length > 1">
<view class="view" :style="index === current ? currentStyle : ''" :class="{ active: index === current }"
v-for="(item, index) in list" :key="index" />
</view>
</view>
</template>
<script>
export default {
props: {
list: {
type: Array,
default() {
return []
},
required: true,
},
hasTabbar: {
type: Boolean,
default: false,
},
hasNavbar: {
type: Boolean,
default: false,
},
interval: {
type: Number,
default: 3000,
},
time: {
type: Number,
default: 3,
},
url: {
type: String,
default: '',
},
bgColor: {
type: String,
default: '#fff',
},
afterColor: {
type: String,
default: '',
},
currentColor: {
type: String,
default: '#BB1219',
},
},
data() {
return {
current: 0,
show: true,
timer: null,
time2: this.time,
}
},
watch: {
time2(val) {
if (val <= 0) {
this.onSkip()
}
},
},
computed: {
bgStyle() {
return this.obj2strStyle({
'background-color': this.bgColor,
})
},
afterStyle() {
return this.obj2strStyle({
'background-color': this.afterColor,
})
},
currentStyle() {
return this.obj2strStyle({
'background-color': this.currentColor,
})
},
skipPositionStyle() {
const { statusBarHeight } = uni.getSystemInfoSync()
if (!this.hasNavbar) {
return this.obj2strStyle({
top: `${statusBarHeight * 2 + 88 + 30}rpx`,
})
}
return this.obj2strStyle({
top: '30rpx',
})
},
},
created() {
this.$emit('openInt', this.onStart)
},
mounted() {
if (this.hasTabbar) {
uni.hideTabBar()
}
},
methods: {
onStart() {
this.timer = setInterval(() => {
this.time2--
}, 1000)
},
onPause() {
clearInterval(this.timer)
},
obj2strStyle(obj) {
let style = ''
for (let key in obj) {
style += `${key}:${obj[key]};`
}
return style
},
onSkip() {
const { url, hasTabbar, timer } = this
clearTimeout(timer)
this.show = false
if (hasTabbar) {
uni.showTabBar()
}
if (url) {
uni.reLaunch({
url: url,
})
}
},
onChangeSwiper(e) {
this.current = e.detail.current
},
toAdH5(ite) {
if (!ite.linkUrl) return
clearInterval(this.timer)
uni.navigateTo({
url: '/pages/adH5/adH5?url=' + ite.linkUrl,
})
},
},
}
</script>
<style scoped>
/* $nav-height: 44px; */
.start {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 9999;
}
.skip {
position: absolute;
z-index: 2;
background-color: rgba(0, 0, 0, 0.1);
color: #fff;
right: 30rpx;
font-size: 28rpx;
width: 133rpx;
height: 60rpx;
border-radius: 44rpx;
display: flex;
align-items: center;
justify-content: center;
}
.swiper {
height: 100vh;
width: 100vw;
}
.swiper-item {
height: 100vh;
width: 100vw;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
background-color: transparent;
}
.swiper-item .after {
width: 100vw;
height: 500rpx;
position: absolute;
left: 0;
bottom: 0;
z-index: 1;
}
.swiper-item .image {
height: 100vh;
width: 100vw;
display: block;
position: relative;
z-index: 2;
}
.swiper-dot {
position: absolute;
width: 100vw;
left: 0;
bottom: 100rpx;
z-index: 3;
display: flex;
justify-content: center;
}
.swiper-dot .view {
width: 16rpx;
height: 16rpx;
border-radius: 100%;
background-color: rgba(0, 0, 0, 0.2);
margin: 0 12rpx;
}
.swiper-dot .view.active {
width: 30rpx;
border-radius: 24rpx;
}
</style>

View File

@@ -0,0 +1,137 @@
<!--
组件功能 省市县三级联动选择
@author terrfly
@site https://www.jeequan.com
@date 2022/11/30 13:25
-->
<template>
<uni-data-picker :localdata=" type=='mccAli'? mccAli : areaCodeData" v-model="vdata.selectedVal" @change="changeFunc">
<template #default="{data, error, options}">
<view v-if="error" class="error">
<text>{{error}}</text>
</view>
<view v-else-if="data.length" class="selected">
<text style="font-size: 32rpx;">{{data.map(v=>v.text).join('/')}}</text>
</view>
<view v-else>
<text :class="{'place-style':!vdata.selectedVal}" style="font-size: 32rpx;">请选择</text>
</view>
</template>
</uni-data-picker>
</template>
<script setup>
import { reactive, ref, onMounted, watch } from 'vue'
import dataKit from '@/commons/utils/dataKit.js'
import areaCodeData from "./areaCodeData.json" // 地址
import mccAli from "@/commons/JSON/mccAli.json"
// emit 父组件使用: v-model="val" 进行双向绑定。
const emit = defineEmits(['update:areacodeList'])
// 定义组件参数
const props = defineProps({
// 省市县, 三级 数组 支持双向绑定。
areacodeList: { type: Array, default: () => [] },
// json 类型
type:{type:String}
})
// 当前组件参数
const vdata = reactive({
selectedVal: '',
})
onMounted(() => {
if(props.areacodeList.length > 0 ){
vdata.selectedVal = props.areacodeList[2]
}
})
// 切换时
watch(() => props.areacodeList, (newVal, oldVal) => {
if(Array.isArray(newVal)){
vdata.selectedVal = newVal[newVal.length - 1]
}else if(typeof newVal =='string'){
vdata.selectedVal = [newVal]
}
})
function changeFunc(e){
let data = []
if(e.detail.value && e.detail.value.length > 0 ){
emit("update:areacodeList",e.detail.value.map(v=> v.value) )
}else{
emit("update:areacodeList", [] )
}
}
// 根据最后一位的(县级编码 搜索展开全部内容
function resetBySingleAreacode(areacode){
if(!areacode){
return
}
let p3 = dataKit.recursionTreeData(areaCodeData, (r => r.value == areacode))
if(!p3){
return false
}
let p2 = dataKit.recursionTreeData(areaCodeData, (r => r.value == p3[1].value))
if(!p2){
return false
}
let p1 = dataKit.recursionTreeData(areaCodeData, (r => r.value == p2[1].value))
if(!p1){
return false
}
emit("update:areacodeList", [p1[0].value, p2[0].value, p3[0].value])
}
/** 根据省市县areaCode 获取省市县名称数据 **/
function getNameListBySingleAreacode(areacode){
if(!areacode){
return
}
let p3 = dataKit.recursionTreeData(areaCodeData, (r => r.value == areacode))
if(!p3){
return false
}
let p2 = dataKit.recursionTreeData(areaCodeData, (r => r.value == p3[1].value))
if(!p2){
return false
}
let p1 = dataKit.recursionTreeData(areaCodeData, (r => r.value == p2[1].value))
if(!p1){
return false
}
return [p1[0].text, p2[0].text, p3[0].text]
}
defineExpose({resetBySingleAreacode, getNameListBySingleAreacode})
</script>
<style scoped lang="scss">
.place-style{
color: #b3b3b3;
}
</style>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,107 @@
<!--
背景模板
使用方式
A. 背景图片的通用外部view APP不支持背景图片的变量动态替换 所以只能业务页面来写入背景图片的地址
<JeepayBackground :bgImgView="true">页面内容</JeepayBackground>
<style scoped>
/deep/ .bg-img-view { background: url('/static/indexImg/user-bg.svg'); }
</style>
B. 背景是自定义颜色的外部view ( 默认蓝色渐变 )
<JeepayBackground :bgColorStyle="{}">页面内容</JeepayBackground>
@author terrfly
@site https://www.jeequan.com
@date 2022/11/19 08:08
-->
<template>
<!-- 微信小程序 <page-meta>: 只能是页面内的第一个节点且不能被 wx:if wx:for 动态变更
uniapp: 打包到小程序 会添加 <jeepay-background>标签 导致该问题 无奈 小程序不可使用 <page-meta> 标签 实现方式为 page-wrapper height方式
-->
<!-- <page-meta :pageStyle="'overflow:' + (vdata.pageMetaOverflow ? 'visible' : 'hidden')"></page-meta> -->
<!-- #ifdef APP-PLUS-->
<page-meta :pageStyle="'overflow:' + (vdata.pageMetaOverflow ? 'visible' : 'hidden')"></page-meta>
<!-- #endif -->
<!-- page-wrapper 包裹 -->
<view class="page-wrapper" :style="{overflow:vdata.pageMetaOverflow ? 'visible' : 'hidden', height:vdata.pageMetaOverflow ? 'auto' : '90vh'}">
<!-- 背景图片view -->
<view class="bg-img-view" :style="vdata.bgImgStyle">
<!-- 背景颜色view -->
<view class="bg-color-view" :style="vdata.bgColorStyle">
</view>
<!-- 解决定位层级问题 -->
<view class="bg-main">
<slot></slot>
</view>
</view>
</view>
</template>
<script setup>
import { reactive, ref, onMounted, provide, computed } from 'vue'
// 选择是图片风格 还是 背景颜色风格。
const props = defineProps({
// 背景图片
bgImgView: { type: Boolean }, // APP 不支持背景图片的动态替换。。。
// 背景颜色style
bgColorStyle: { type: Object },
// 禁止滚动穿透
})
function changePageMetaOverflowFunc(v){
vdata.pageMetaOverflow = v
}
provide('changePageMetaOverflowFunc', changePageMetaOverflowFunc)
const vdata = reactive({
bgImgStyle : { }, // 注意: style变量需要全部替换 不可使用Object.assign修改里面的值, 否则 APP不生效
bgColorStyle : { },
pageMetaOverflow: true
})
provide('pageMetaOverflow',computed(()=>vdata.pageMetaOverflow))
onMounted(() => {
// 包含背景图片
if(props.bgImgView){
vdata.bgImgStyle = {
position: "absolute",
top: 0,
left: 0,
right: 0,
backgroundRepeat: 'no-repeat'
}
}
// 包含背景色
if(props.bgColorStyle){
let defStyle = { // 默认颜色
position: "absolute",
top: 0,
left: 0,
right: 0,
height: '550rpx',
borderRadius: '0 0 32rpx 32rpx',
background: 'linear-gradient(270deg, rgba(72, 192, 255, 1) 0%, rgba(51, 157, 255, 1) 100%)',
}
vdata.bgColorStyle = Object.assign(defStyle, props.bgColorStyle)
}
})
</script>
<style scoped lang="scss">
.bg-main{
position: relative;
z-index: 10;
}
</style>

View File

@@ -0,0 +1,55 @@
<template>
<swiper
class="swiper"
circular
:indicator-dots="true"
autoplay
:interval="interval * 1000"
:duration="500"
>
<block v-for="(v, i) in list" :key="i">
<swiper-item>
<image :src="v.imgUrl" mode="aspectFill" @tap="toH5(v.linkUrl)" />
</swiper-item>
</block>
</swiper>
</template>
<script setup>
const props = defineProps({
list: { type: Array, default: [] },
interval: { type: [String, Number] },
});
const toH5 = (url) => {
if (!url) return;
uni.navigateTo({
url: '/pages/adH5/adH5?url=' + url,
});
};
</script>
<style lang="scss" scoped>
.uni-margin-wrap {
width: 690rpx;
width: 100%;
}
.swiper {
margin: 40rpx;
height: 300rpx;
border-radius: 30rpx;
overflow: hidden;
}
.swiper-item {
display: block;
height: 300rpx;
line-height: 300rpx;
text-align: center;
}
image {
width: 100%;
}
</style>

View File

@@ -0,0 +1,143 @@
<!--
Jeepay 门店选择 / 应用选择
@author terrfly
@site https://www.jeequan.com
@date 2022/12/06 12:55
-->
<template>
<!-- 选择门店 -->
<JeepayPopupListSelect
ref="jeepayPopupListSelectByBizinfos"
:reqTableDataFunc="reqTableDataByBizsFunc"
:searchInputName="getSearchInputName()"
:fields="getField()"
:isCheckbox="props.isCheckbox"
:addUse="props.addUse"
@confirm="confirm"
/>
</template>
<script setup>
import { reactive, ref } from 'vue';
import { reqLoad, API_URL_MCH_STORE_LIST, API_URL_MCH_APP_LIST, API_URL_MCH_ISV_LIST } from '@/http/apiManager.js';
// 定义组件参数
const props = defineProps({
configMode: '',
// 是否多选框, 默认单选。
isCheckbox: { type: Boolean, default: false },
// 自动关闭, 点击确定, 自动关闭
autoClose: { type: Boolean, default: true },
// 业务类型: 默认门店
bizType: { type: String, default: 'store' },
range: { type: String, default: 0 },
ifCode: { type: String, default: '' },
// 是否支持选择 【 全部门店 、 全部应用 】
isShowAllBiz: { type: Boolean, default: false },
// 特殊参数处理
params: { type: Object, default: () => ({}) },
addUse: { type: Boolean, default: false }
});
const jeepayPopupListSelectByBizinfos = ref();
const emits = defineEmits(['confirm']);
function confirm(data) {
console.log(data);
emits('confirmData', data);
}
console.log(props.isCheckbox);
function getField() {
if (props.bizType == 'store') {
return { key: 'storeId', left: 'storeName', right: 'storeId' };
}
if (props.bizType == 'mchApp') {
return { key: 'appId', left: 'appName', right: 'appId' };
}
if (props.bizType == 'isvApp') {
return { key: 'isvNo', left: 'isvName', right: 'isvNo' };
}
}
function getSearchInputName() {
if (props.bizType == 'store') {
return 'storeName';
}
if (props.bizType == 'mchApp') {
return 'appName';
}
if (props.bizType == 'isvApp') {
return 'isvName';
}
}
function reqTableDataByBizsFunc(params) {
Object.assign(params, props.params);
console.log(params);
let apiResult = null;
if (props.bizType == 'store') {
apiResult = reqLoad.list(API_URL_MCH_STORE_LIST, params);
}
if (props.bizType == 'mchApp') {
apiResult = reqLoad.list(API_URL_MCH_APP_LIST, params);
}
if (props.bizType == 'isvApp') {
params.ifCode = props.ifCode;
params.range = props.range;
apiResult = reqLoad.list(API_URL_MCH_ISV_LIST, params);
}
return apiResult.then((apiRes) => {
return processApiResBizData(params, apiRes);
});
}
// 选择门店
function open(defaultVal) {
console.log(defaultVal, 'defaultValdefaultVal');
return jeepayPopupListSelectByBizinfos.value.open(defaultVal).then((selected) => {
// 自动关闭
if (props.autoClose) {
close();
}
return selected;
});
}
function close() {
jeepayPopupListSelectByBizinfos.value.close(); //自行关闭
}
function processApiResBizData(params, apiRes) {
// 第一页 拼接全部门店 (index = 0 )
if (params.pageNumber == 1 && props.isShowAllBiz) {
if (props.bizType == 'store') {
apiRes.bizData.records.unshift({ storeName: '全部门店', storeId: '' });
}
if (props.bizType == 'mchApp') {
apiRes.bizData.records.unshift({ appName: '全部应用', appId: '' });
}
if (props.bizType == 'isvApp') {
apiRes.bizData.records.unshift({ appName: '全部渠道', appId: '' });
}
}
return apiRes;
}
// 将表格事件暴露出去 https://www.jianshu.com/p/39d14c25c987
defineExpose({ open, close });
</script>
<style></style>

View File

@@ -0,0 +1,144 @@
<!--
通用的选择某项业务 一般用作修改页
目前支持 门店 应用
@author terrfly
@site https://www.jeepay.vip
@date 2022/12/08 14:18
-->
<template>
<view class="details-wrapper" style="align-items: flex-start; padding-top: 40rpx">
<view v-if="props.hasTitle" class="details-title">
<slot name="title">{{ bizTypeMap[props.bizType].title }}</slot>
</view>
<view @tap="show">
<view class="sub-title">
<view class="details-info single-text-beyond">
<text :class="{ 'text-gray': vdata.selectedRow[bizTypeMap[props.bizType].nameKey] ? false : true }">
{{ vdata.selectedRow[bizTypeMap[props.bizType].nameKey] || bizTypeMap[props.bizType].nameDef }}
</text>
</view>
<image src="/static/iconImg/icon-arrow-small.svg" mode="scaleToFill" v-if="isIcon" />
</view>
<view class="sub-info details-info single-text-beyond">{{ vdata.selectedRow[bizTypeMap[props.bizType].id] }}</view>
</view>
</view>
<view>
<JeepayBizinfoSelect ref="jeepayBizinfoSelectRef" :bizType="props.bizType" :isCheckbox="false" :range="props.range" :ifCode="props.ifCode" :addUse="props.addUse" />
<JeepayIncomingBizinfoSelect ref="jeepayIncomingBizinfoSelectRef" :bizType="props.bizType" :range="props.range" :ifCode="props.ifCode" />
</view>
</template>
<script setup>
import { ref, reactive, nextTick, watch, onMounted } from 'vue';
const jeepayBizinfoSelectRef = ref();
const jeepayIncomingBizinfoSelectRef = ref();
const bizTypeMap = {
store: { title: '选择门店', nameKey: 'storeName', nameDef: '请选择门店', id: 'storeId' },
mchApp: { title: '选择应用', nameKey: 'appName', nameDef: '请选择应用', id: 'appId' },
isvApp: { title: '选择渠道', nameKey: 'isvName', nameDef: '请选择渠道', id: 'isvNo' },
incomingApp: { title: '发起进件', nameKey: 'name', nameDef: '请选择场景', id: 'range' }
};
// emit 父组件使用: v-model:value="val" 进行双向绑定。
const emits = defineEmits(['update:value', 'update:showName', 'change']);
const props = defineProps({
// 是否包含 标题部分
hasTitle: { type: Boolean, default: true },
//绑定的值, 双向绑定
value: { type: [String, Number] },
// 仅仅用作修改的回显
showName: { type: [String, Number] },
// 业务类型: 默认门店
bizType: { type: String, default: 'store' },
range: { type: String, default: 0 },
ifCode: { type: String, default: '' },
isIcon: { type: Boolean, default: true },
addUse: { type: Boolean, default: false }
});
onMounted(() => {
if (props.value) {
vdata.selectedRow[bizTypeMap[props.bizType].id] = props.value;
}
if (props.showName) {
vdata.selectedRow[bizTypeMap[props.bizType].nameKey] = props.showName;
}
});
// 切换时
watch(
() => props.value,
(newVal, oldVal) => {
vdata.selectedRow[bizTypeMap[props.bizType].id] = newVal;
}
);
// 切换时
watch(
() => props.showName,
(newVal, oldVal) => {
vdata.selectedRow[bizTypeMap[props.bizType].nameKey] = newVal;
}
);
const vdata = reactive({
// 当前选择行
selectedRow: {}
});
function show() {
jeepayBizinfoSelectRef.value.open().then((selectedRow) => {
vdata.selectedRow = selectedRow || {};
emits('update:value', vdata.selectedRow[bizTypeMap[props.bizType].id]);
emits('update:showName', vdata.selectedRow[bizTypeMap[props.bizType].nameKey]);
emits('change', vdata.selectedRow[bizTypeMap[props.bizType].id]);
});
}
</script>
<style lang="scss" scoped>
.details-wrapper {
display: flex;
align-items: center;
font-size: 32rpx;
background-color: #fff;
.details-title {
width: 180rpx;
color: #4c4c4c;
}
.details-info {
flex-grow: 1;
width: 405rpx;
}
image {
width: 108rpx;
height: 100rpx;
}
.sub-title {
flex: 1;
display: flex;
image {
width: 108rpx;
height: 40rpx;
}
}
.sub-info {
margin-top: 20rpx;
font-size: 30rpx;
color: #a1a1a1;
padding-bottom: 20rpx;
}
.text-gray {
color: #b3b3b3;
}
}
</style>

View File

@@ -0,0 +1,90 @@
<!--
组件功能 卡片布局
标题 副标题 和编辑按钮及插槽
注意 不包含 中间的内容
@author terrfly
@site https://www.jeequan.com
@date 2022/12/06 08:06
-->
<template>
<view class="c-card-wrapper" :style="props.viewStyle">
<view v-if="props.title" class="card-title" :class="{'border-none':!isTitleBorder}" @tap="emits('clickTitle')">
<view class="c-card-title"> {{ props.title }} <view><slot name="titleRight"/> </view> </view>
<view class="sub-title" v-if="props.subtitle">{{ props.subtitle }}</view>
</view>
<!-- 默认插槽位置 -->
<slot></slot>
<!-- 编辑区域 -->
<slot name="editContent">
<view v-if="props.editText" class="card-edit flex-center" hover-class="touch-hover" @tap="emits('editTap')">
<image src="/pageDevice/static/devIconImg/icon-edit.svg" mode="scaleToFill" /> {{ props.editText }}
</view>
</slot>
</view>
</template>
<script setup>
import { reactive, ref, onMounted, watch } from 'vue'
const emits = defineEmits(['editTap','clickTitle'])
const props = defineProps({
viewStyle: { type: [String, Object] }, // 样式透传, 小程序不支持再组件上加style(不生效)
title: { type: String }, //标题文字 传值则显示
subtitle: { type: String }, //副标题
editText: { type: String }, // 编辑文本, 若存在请通过 editTap 事件接收点击事件。
isTitleBorder:{type:Boolean, default:true} //是否存在标题下边框
})
</script>
<style lang="scss" scoped>
.c-card-wrapper {
margin: 0 35rpx;
border-radius: 32rpx;
background-color: #fff;
overflow: hidden;
.card-title {
display: flex;
flex-direction: column;
justify-content: center;
padding: 30rpx 40rpx;
min-height: 42rpx;
font-size: 32rpx;
font-weight: 500;
border-bottom: 1rpx solid #ededed;
.c-card-title{
display: flex;
justify-content: space-between;
}
.sub-title {
margin-top: 15rpx;
font-size: 25rpx;
color: #808080;
}
}
.card-edit {
height: 110rpx;
border-top: 1rpx solid #ededed;
color: #2980fd;
font-size: 33rpx;
font-weight: 400;
image {
width: 44rpx;
height: 44rpx;
margin-right: 10rpx;
}
}
}
.border-none{
border: none !important;
}
</style>

View File

@@ -0,0 +1,80 @@
<!--
组件功能 解决原生组件双向绑定问题
@author terrfly
@site https://www.jeequan.com
@date 2022/11/18 14:32
-->
<template>
<view class="select-box" @click="changeFunc">
<button class="agree-item" id="agree-btn" open-type="agreePrivacyAuthorization"
@agreeprivacyauthorization="handAgree">
<image v-if="props.checked" class="select-img" src="@/static/login/selected.svg"></image>
<image v-else class="select-img" src="@/static/login/select.svg"></image>
</button>
</view>
</template>
<script setup>
import { reactive, ref, onMounted } from 'vue'
// emit 父组件使用: v-model="val" 进行双向绑定。
const emit = defineEmits(['update:checked'])
onMounted(() => {
// #ifdef MP-WEIXIN
getPrivacy()
// #endif
})
// 定义组件参数
const props = defineProps({
checked: { type: Boolean, default: false },
})
const vdata = reactive({})
function changeFunc () {
emit('update:checked', !props.checked)
}
// 获取微信你用户是否同意过隐私政策
const getPrivacy = () => {
wx.getPrivacySetting({
success: (r) => {
Object.assign(vdata, r)
if (vdata.needAuthorization) {
wx.onNeedPrivacyAuthorization(res => {
vdata.resolve = res
})
}
}
})
}
const handAgree = () => {
// #ifdef MP-WEIXIN
if (vdata.needAuthorization) {
vdata.resolve({ buttonId: 'agree-btn', event: 'agree' })
}
// #endif
}
</script>
<style scoped lang="scss">
.select-box {
display: inline;
.select-img {
width: 46rpx;
height: 46rpx;
}
}
.agree-item {
padding: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
padding-bottom: 0;
line-height: 0;
width: 46rpx;
height: 46rpx;
border-radius: 0;
background-color: transparent;
}
</style>

View File

@@ -0,0 +1,159 @@
<!--
组件功能 自定义导航栏包裹 , 基于 uni-nav-bar 的封装
1. pages.json : "navigationStyle": "custom"
2. 页面引入该组件
3. 如果背景透明并且需要下滑变默认背景色 需要在页面实现 onPageScroll函数可以空函数 否则该组件无法正常监听
示例
背景通铺效果
<JeepayCustomNavbar :transparent="true" bgDefaultColor='#b6a3a3' title="背景通铺效果" backCtrl="back" ></JeepayCustomNavbar>
import { onPageScroll } from '@dcloudio/uni-app'
onPageScroll(() => {})
纯色导航条
<JeepayCustomNavbar :transparent="false" bgDefaultColor='#ffaa7f' title="纯色导航条" backCtrl="back"></JeepayCustomNavbar>
自定义左上角 效果
<JeepayCustomNavbar :transparent="true" bgDefaultColor='#ff557f' title="自定义左上角" backCtrl="back">
<view> 选择门店自定义导航条内容 </view>
</JeepayCustomNavbar>
@author terrfly
@site https://www.jeequan.com
@date 2022/11/18 14:32
-->
<template>
<view>
<uni-nav-bar
fixed
:statusBar="true"
:border="false"
:height="props.height"
:backgroundColor="vdata.backgroundColor"
leftWidth="100%"
>
<!-- 使用左侧插槽 100%宽度 -->
<template #left>
<view class="left-slot-view">
<slot>
<view class="nav-bar-box">
<view>
<!-- 左侧按钮 -->
<uni-icons v-if="props.backCtrl" class="left-back" @click="backFunc" :type="props.backIcon" size="20" :color="props.textColor"></uni-icons>
</view>
<!-- 标题 -->
<view class="text" :style="{ color: props.textColor }"> {{props.title}} </view>
</view>
</slot>
</view>
</template>
</uni-nav-bar>
</view>
</template>
<script setup>
import { onPageScroll } from '@dcloudio/uni-app'
import { reactive, ref, onMounted } from 'vue'
import ak from '@/commons/utils/ak.js'
// 定义组件参数
const props = defineProps({
// 标题
title: { type: String, default: '' },
// 文字颜色
textColor: { type: String, default: 'black' },
// 返回按钮类型
backIcon: { type: String, default: 'back' },
// 高度: 默认与uni-nav-bar 保持一致。
height: { type: Number, default: 44 },
// 默认背景色:
bgDefaultColor: { type: String, default: 'white' },
// 是否默认透明(此时可自定义背景), 当滑动时将切换为默认背景色。
transparent: { type: Boolean, default: false },
// 返回按钮的操作项: null-无返回按钮, back 返回上一页, pageUrl: '', 也可以是回调函数。
backCtrl: [ String, Function ],
})
// 只有透明色时 才需要监听
if(props.transparent){
onPageScroll((e) => { // 切换背景颜色 透明 or 主题色。
if(e.scrollTop > (props.height / 2.5) ){
vdata.backgroundColor = props.bgDefaultColor
}else{
vdata.backgroundColor = 'transparent'
}
})
}
// 颜色
const vdata = reactive({
backgroundColor: 'transparent' // 默认透明色
})
onMounted(() => {
// 当非透明色, 需要改为默认颜色
if(!props.transparent){
vdata.backgroundColor = props.bgDefaultColor + '';
}
})
// 点击返回按钮,
function backFunc(){
if(!props.backCtrl){
return false;
}
if(typeof props.backCtrl == 'function'){
return props.backCtrl()
}
if(props.backCtrl === 'back'){
return ak.go.back()
}
}
</script>
<style scoped lang="scss">
.left-slot-view {
display: flex;
align-items: center;
width: 100%;
height: 100%;
line-height: 100%;
font-size: 33rpx;
font-weight: 500;
.nav-bar-box {
display: flex;
flex-grow: 1;
justify-content: space-between;
.left-back {
position: absolute;
}
&::after {
content: "";
}
}
}
::v-deep.uni-navbar__header {
.uni-navbar__header-container,
.uni-navbar__header-btns-right {
display: none !important;
}
}
</style>

View File

@@ -0,0 +1,32 @@
<!--
描述信息展示
通用信息
@author terrfly
@site https://www.jeequan.com
@date 2022/12/06 10:11
-->
<template>
<view class="c-desc-view">
<slot></slot>
</view>
</template>
<script setup>
const props = defineProps({
})
</script>
<style lang="scss" scoped>
.c-desc-view {
}
</style>

View File

@@ -0,0 +1,77 @@
<!--
描述信息展示
通用信息
@author terrfly
@site https://www.jeequan.com
@date 2022/12/06 10:11
-->
<template>
<view :class="{ 'c-desc-view-item': true, 'bottom-border': props.bottomBorder }">
<view class="title">
<slot name="title">{{ props.title }}</slot>
</view>
<view class="desc">
<slot name="desc">
<image v-if="props.descIsImg && props.desc" :src="props.desc" mode="scaleToFill" class="default-img" />
<template v-else>{{ props.desc }}</template>
</slot>
</view>
</view>
</template>
<script setup>
const props = defineProps({
// 标题
title: { type: String },
// 描述
desc: { type: String },
// 描述是否是图片
descIsImg: { type: Boolean, default: false },
// 下边框
bottomBorder: { type: Boolean, default: false }
});
</script>
<style lang="scss" scoped>
.c-desc-view-item {
display: flex;
justify-content: space-between;
padding: 20rpx 40rpx;
font-size: 30rpx;
font-weight: 400;
.title {
display: flex;
flex-direction: column;
justify-content: flex-start;
width: 200rpx;
color: #808080;
}
.desc {
flex: 1;
text-align: right;
color: #000;
}
&:first-child {
margin-top: 20rpx;
}
&:last-child {
margin-bottom: 20rpx;
}
.default-img {
width: 150rpx;
height: 150rpx;
background-color: #f7f7f7;
}
}
.bottom-border {
padding-bottom: 40rpx;
border-bottom: 1rpx solid #ededed;
}
</style>

View File

@@ -0,0 +1,151 @@
<!-- 待完成 -->
<template>
<view>
<slot></slot>
</view>
</template>
<script setup>
import { reactive, ref, onMounted, provide, computed } from 'vue'
import infoBox from '@/commons/utils/infoBox.js'
// 定义组件参数
const props = defineProps({
// 验证规则
rules: { type: Object, default: () => { } },
// 用作表单验证
model: { type: Object, default: () => { } },
})
const vdata = reactive({
formNameList: [], // form 表单的已经注册的名字集合
errorListByText: [], // 错误信息集合 ( 需要显示的 )
errorListByToast: [], // 错误信息集合 ( alert 提示的 )
})
// 组件注册 和 取消
function itemSign(name){
if(vdata.formNameList.indexOf(name) < 0){
vdata.formNameList.push(name)
}
}
// 组件注册 和 取消
function unItemSign(name){
if(vdata.formNameList.indexOf(name) >= 0){
vdata.formNameList.splice(vdata.formNameList.indexOf(name), 1)
}
}
// { type: String, required: true, message: '', showToastError: false, validator: (rule, value) => {} }
// showTextError : 是否显示文本错误, 默认为true
// showToastError: 是否alert信息 默认为false
// 错误条目数据结构: {name, value, rule, errorMsg}
// 注意: errorMsg 跟 rule里的 message不是一回事 errorMsg 可能是 回调函数自定义。
function pushError(name, value, errorMsg, rule){
// 明确是 false, 则不加入文本错误信息, 未定义等都是true(默认值)
if(rule.showTextError === false){
}else{
vdata.errorListByText.push({name, value, rule, errorMsg})
}
// alert 默认为 false
if(rule.showToastError === true){
vdata.errorListByToast.push({name, value, rule, errorMsg})
}
}
function validateFormItem(name){
// 不存在
if(!name || !props.rules[name] ){
return false
}
// model的值
const value = props.model[name]
props.rules[name].forEach( rule => {
// 必填 && 验证失败
if(rule.required && (value == null || value == '' || typeof value == 'undefined')){
pushError(name, value, rule.message, rule)
}else{ // 非必填 || 必填验证通过
if(rule.validator){ // 包含验证自定义函数, 暂时先不支持promise
let validatorResult = rule.validator(rule, value)
if( validatorResult == true){ // 验证通过
}else{ // 验证失败
pushError(name, value, validatorResult, rule)
}
}
}
})
return false;
}
// 表单验证 , 返回Promise
function validate(){
// 清空错误信息
vdata.errorListByText = []
vdata.errorListByToast = []
vdata.formNameList.forEach(name => validateFormItem(name) )
if(vdata.errorListByToast && vdata.errorListByToast.length > 0){
infoBox.showToast(vdata.errorListByToast[0].errorMsg)
}
console.log(vdata.formNameList);
console.log(vdata.errorListByText);
console.log(vdata.errorListByToast);
if(vdata.errorListByText.length <= 0 && vdata.errorListByToast.length <= 0){
alert('成功')
}
}
// 向下注册的组件
const provideModel = {
itemSign: itemSign,
unItemSign: unItemSign,
}
provide('jeepayForm', computed( ()=> provideModel ) )
// provideModel 写入: errorListByText 监听不到。
provide('jeepayErrorListByText', computed( ()=> vdata.errorListByText ) )
defineExpose({ validate })
</script>
<style>
</style>

View File

@@ -0,0 +1,71 @@
<template>
<view>
{{props.label}} : <slot></slot>
<view v-if="props.showErrorText && vdata.errorMsg">{{ vdata.errorMsg }}</view>
</view>
</template>
<script setup>
import { reactive, ref, onMounted, onUnmounted, provide, computed, inject, watch } from 'vue'
const jeepayFormInject = inject("jeepayForm")
const jeepayForm = jeepayFormInject.value
const jeepayErrorListByTextInject = inject("jeepayErrorListByText")
onMounted(() => {
jeepayForm.itemSign(props.name)
})
onUnmounted(() => {
jeepayForm.unItemSign(props.name)
})
// 定义组件参数
const props = defineProps({
name: { type: String },
label: { type: String },
// 是否显示 错误信息占位
showErrorText: { type: Boolean, default: true },
})
const vdata = reactive({
errorMsg: null, // 错误提示信息
})
// 关于本条的数据
function changeItemErrorMsg(itemErrorListByText){
vdata.errorMsg = null;
if(itemErrorListByText && itemErrorListByText.length > 0){
vdata.errorMsg = itemErrorListByText[0].errorMsg
}
}
watch(() => jeepayErrorListByTextInject.value, (newVal, oldVal) => {
if(!newVal){
return false;
}
changeItemErrorMsg(newVal.filter(r=> r.name == props.name))
}
)
// 错误信息的传递
provide('jeepayFormItemErrorMsg', computed( ()=> vdata.errorMsg ) )
</script>
<style>
</style>

View File

@@ -0,0 +1,42 @@
<template>
<uni-easyinput
v-model="props.value" :type="props.type"
:placeholder="vdata.errorMsg || props.placeholder"
/>
</template>
<script setup>
import { reactive, ref, onMounted, onUnmounted, provide, computed, inject, watch } from 'vue'
const jeepayFormItemErrorMsgInject = inject("jeepayFormItemErrorMsg")
// 定义组件参数
const props = defineProps({
placeholder: { type: String },
label: { type: String },
type: { type: String },
// 双向绑定
value: { type: [String, Number] }
})
const vdata = reactive({
errorMsg: null, // 错误提示信息
})
watch(() => jeepayFormItemErrorMsgInject.value, (newVal, oldVal) => {
vdata.errorMsg = newVal
}
)
</script>
<style>
</style>

View File

@@ -0,0 +1,91 @@
<!--
信息列表的预览页面 比如 通知消息的前几条等
通用信息
@author terrfly
@site https://www.jeequan.com
@date 2022/11/15 06:18
-->
<template>
<view class="notice-wrapper">
<view class="notice-title" v-if="props.tableTitle">
<view class="notice-news">{{ props.tableTitle }}</view>
<view class="notice-more flex-center" v-if="props.isShowMoreBtn" @tap="listviewClickFunc(true, null)">
更多<image src="/static/iconImg/icon-arrow-black.svg" mode="scaleToFill" /></view>
</view>
<block v-for="(v, i) in props.dataList" :key="i">
<view class="notice-main" hover-class="touch-hover" hover-stay-time="150" @tap="listviewClickFunc(false, v)">
<view class="notice-content single-text-beyond">{{ v[props.fields.title] }}</view>
<view class="notice-time">{{ v[props.fields.subtitle] }}</view>
</view>
</block>
</view>
</template>
<script setup>
const props = defineProps({
//列表标题 传则展示 不传则移除
tableTitle: { type: String },
// 数据列表, 默认是按照: { title, subtitle }
dataList: { type: Array, default: () => [] },
// 约定的字段
fields: { type: Object, default: {title: 'title', subtitle: 'subtitle'} },
// 是否显示更多按钮
isShowMoreBtn: { type: Boolean, default: true },
})
// touchDown 列表点击回调 touchMore点击更多回调
const emits = defineEmits(["click"])
function listviewClickFunc (isClickMore, record) {
emits('click', isClickMore, record)
}
</script>
<style lang="scss" scoped>
.notice-wrapper {
width: 680rpx;
margin: 0 auto;
background-color: $J-bg-ff;
border-radius: $J-b-r32;
.notice-title {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 30rpx;
height: 102rpx;
border-bottom: 1rpx solid #ededed;
.notice-news {
font-size: 32rpx;
font-weight: 500;
}
.notice-more {
font-size: 32rpx;
color: $J-color-t80;
image {
width: 42rpx;
height: 42rpx;
transform: rotate(90deg);
margin-left: 5rpx;
}
}
}
.notice-main {
padding: 30rpx;
height: 90rpx;
.notice-content {
margin-bottom: 16rpx;
font-size: 30rpx;
}
.notice-time {
font-size: 26rpx;
color: $J-color-ta6;
}
}
}
</style>

View File

@@ -0,0 +1,131 @@
<!--
登录页专用文字上移样式行高宽度密码框与通用样式不一致
autoComplete="new-password"
-->
<template>
<div class="jee-input">
<div class="jee-text-up">
<a-input
ref="inputRef"
:type="inputPassword"
:value="value"
:required="isGoogle"
:disabled="props.disabled"
autoComplete="new-password"
@change="onChange($event)"
/>
<label @click="inputRef.focus()">{{ placeholder }}</label>
</div>
<div v-if="isPassword == 'password'" class="eyes" @click="changeEyes">
<!-- 密码框的小眼睛 -->
<eye-outlined v-if="!eyes" />
<eye-invisible-outlined v-else />
</div>
</div>
</template>
<script lang="ts" setup>
import { defineProps, ref } from 'vue'
// 定义input对象
const inputRef = ref()
// 定义父组件的传参和类型
const props = defineProps({
value: { type: String, default: null },
placeholder: { type: String, default: '' },
isPassword: { type: String, default: 'text' }, // 是否为密码框
isGoogle: { type: String, default: 'required' }, // 谷歌验证码是否必填
disabled: { type: Boolean, default: false } // 是否禁用
})
// nextTick(() => {
// // inputRef.value.focus()
// if(!props.value) {
// inputRef.value.blur()
// }
// // watch(() => props.value, () => {
// // })
// })
// 密码的显示与隐藏
let eyes = ref(true)
let inputPassword = ref(props.isPassword)
const changeEyes = () => {
eyes.value = !eyes.value
eyes.value
? (inputPassword.value = 'password')
: (inputPassword.value = 'text')
}
const emit = defineEmits(['update:value'])
const onChange = (e) => emit('update:value', e.target.value)
</script>
<style scoped lang="less">
.jee-input {
position: relative;
.eyes {
position: absolute;
right: 0;
top: 0;
width: 40px;
height: 40px;
display: flex;
justify-content: center;
align-items: center;
z-index: 99;
}
}
// 文字上移 效果
.jee-text-up {
position: relative;
input {
outline: 0;
transition: all 0.2s ease;
height: 40px;
max-width: 300px;
}
input::-webkit-input-placeholder {
color: transparent;
font-size: 13px;
}
.placeShow::-webkit-input-placeholder {
color: #bfbfbf;
}
label {
margin-left: 6px;
position: absolute;
left: 0;
bottom: 13px;
padding: 0px 5px;
color: #bfbfbf;
font-size: 13px;
transition: all 0.2s ease;
border-radius: 3px;
line-height: 1;
display: flex;
justify-content: center;
align-items: center;
cursor: initial;
}
input:focus + label,
input:active + label,
input:valid + label {
font-weight: normal;
font-size: 12px;
letter-spacing: 0.05em;
text-align: left;
color: var(--ant-primary-color);
background: #fff; // 更换背景色
transform: translateY(-22px);
}
input:not(:focus) + label {
color: #b0afb3;
background: #fff;
}
}
</style>

View File

@@ -0,0 +1,159 @@
<!--
Jeepay导航卡片 支持4种风格
@author terrfly
@site https://www.jeequan.com
@date 2022/11/16 11:45
-->
<template>
<!-- 宫格类型 -->
<template v-if="props.type == 'grid'">
<view class="grid-card-wrapper" :style="{ margin: calcPadding(), '--space-w': space + 'rpx' }">
<block v-for="(v, i) in navListComputed" :key="i">
<view
class="card-main"
:style="{ margin: calcMargin(), '--radius-size': radiusSize + 'rpx' }"
hover-class="touch-hover"
hover-stay-time="150"
:class="{ 'card-big-main': navListComputed.length == 4 || navListComputed.length <= 2 }"
@tap="clickFunc(v)"
>
<image :src="v.icon" mode="scaleToFill" />
<view class="card-title">{{ v.title }}</view>
</view>
</block>
</view>
</template>
<!-- 列表类型 -->
<template v-if="props.type == 'list'">
<view class="list-nav-wrapper" :style="{ '--radius-size': radiusSize + 'rpx' }">
<block v-for="(v, i) in navListComputed" :key="i">
<view class="nav-main" hover-class="touch-hover" hover-stay-time="150" @tap="clickFunc(v)">
<image :src="v.icon" mode="scaleToFill" />
<view class="nav-text">{{ v.title }}</view>
<image class="nav-right" src="/static/iconImg/icon-arrow-right.svg" mode="scaleToFill" />
</view>
</block>
</view>
</template>
</template>
<script setup>
import { reactive, ref, computed } from "vue"
import go from "@/commons/utils/go.js"
import ent from '@/commons/utils/ent.js'
// 定义组件参数
const props = defineProps({
//显示类型: 支持 grid-宫格 list-列表
type: { type: String, default: "list" },
// 圆角矩形大小 为0 则 无圆角
radiusSize: { type: Number, default: 32 },
//间隙类型: 仅grid-宫格时生效, 支持 0 和 25
space: { type: Number, default: 25 },
// 导航列表, 格式:{ icon, title, pageUrl, clickFunc, entId }
navList: { type: Array, default: () => [] },
})
// 点击事件
function clickFunc(nav) {
// 包含回调事件
if (nav.clickFunc) {
return nav.clickFunc(nav)
}
// 包含URL
if (nav.pageUrl) {
return go.to(nav.pageUrl)
}
}
const calcPadding = () => `${50 - props.space}rpx 35rpx 50rpx ${35 - props.space}rpx`
const calcMargin = () => `${props.space}rpx 0 0 ${props.space}rpx`
// 计算属性
let navListComputed = computed(() => {
return props.navList.filter(r => hasEnt(r.entId))
})
function hasEnt(entId){
// 不包含: 说明无需隐藏
if(!entId){
return true
}
return ent.has(entId)
}
</script>
<style lang="scss" scoped>
.grid-card-wrapper {
display: flex;
flex-wrap: wrap;
background-color: $v-color-bgrey;
padding-top: 25rpx;
.card-main {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin: 25rpx 0 0 25rpx;
width: calc(33.3% - var(--space-w));
height: 210rpx;
border-radius: var(--radius-size);
background-color: $J-bg-ff;
image {
width: 72rpx;
height: 72rpx;
}
.card-title {
margin-top: 22rpx;
color: $J-color-t80;
font-size: $J-f-size30;
}
}
.card-big-main {
width: calc(50% - var(--space-w));
}
}
.list-nav-wrapper {
margin: 0 auto;
width: 680rpx;
border-radius: var(--radius-size);
overflow: hidden;
.nav-main {
display: flex;
align-items: center;
padding-left: 30rpx;
height: 120rpx;
background-color: $J-bg-ff;
image {
width: 40rpx;
height: 40rpx;
vertical-align: text-bottom;
margin: 0 20rpx 0 10rpx;
}
.nav-text {
flex: 1;
font-size: 32rpx;
}
.nav-right {
margin: 0;
width: 120rpx;
height: 120rpx;
}
}
.nav-setup {
margin: 30rpx auto;
width: 680rpx;
border-radius: $J-b-r32;
box-sizing: border-box;
}
}
</style>

View File

@@ -0,0 +1,110 @@
<!--
通用 确认弹层
@author terrfly
@site https://www.jeequan.com
@date 2022/11/22 07:30
-->
<template>
<uni-popup ref="popup" type="bottom" @maskClick="cancelFunc" mask-background-color="rgba(0,0,0,.5)" @change='change' :safe-area="false">
<view class="tips-wrapper">
<view class="tips-text" :style="{ textIndent: vdata.title.length > 20 ? '2em' : 0 }"> {{ vdata.title }} </view>
<view class="tips-text tips-confirm" hover-class="u-cell-hover" hover-stay-time="150" :style="{color: vdata.confirmColor }" @tap="confirmFunc">
{{ vdata.confirmText || "确认" }}
</view>
<view class="line"></view>
<view class="tips-text tips-cancel" hover-class="u-cell-hover" hover-stay-time="150" @tap="cancelFunc">
{{ vdata.cancelText || "取消" }}
</view>
</view>
</uni-popup>
</template>
<script setup>
import { ref, reactive,inject } from "vue"
const popup = ref(null) //弹窗实例
const tips = ref("") // 提示语
const vdata = reactive({
title: '',
confirmText: '',
cancelText: '',
confirmColor: '',
promiseObject: { },
})
function confirmFunc(){
popup.value.close()
vdata.promiseObject.resolve()
}
function cancelFunc(){
popup.value.close()
vdata.promiseObject.reject()
}
function open (title, confirmTextOrExtObject , cancelText ) {
vdata.title = title
vdata.confirmText = typeof confirmTextOrExtObject == "string" ? confirmTextOrExtObject : ''
vdata.cancelText = cancelText
if(typeof confirmTextOrExtObject == "object" ) {
Object.assign(vdata,confirmTextOrExtObject)
}
popup.value.open()
return new Promise( (resolve, reject) => {
vdata.promiseObject.resolve = resolve
vdata.promiseObject.reject = reject
})
}
let changePageMetaOverflowFunc = inject('changePageMetaOverflowFunc')
const change = (e)=>{
if(changePageMetaOverflowFunc){
changePageMetaOverflowFunc(!e.show)
}
}
defineExpose({ open })
</script>
<style lang="scss" scoped>
.tips-wrapper {
overflow: hidden;
border-radius: 32rpx 32rpx 0 0;
padding-bottom: 60rpx;
background-color: #fff;
.tips-text {
display: flex;
align-items: center;
justify-content: center;
text-align: center;
min-height: 110rpx;
line-height: 1.8;
padding: 30rpx;
box-sizing: border-box;
font-size: 30rpx;
}
.line {
height: 20rpx;
background-color: rgba(0, 0, 0, 0.07);
}
.tips-confirm {
display: flex;
justify-content: center;
align-items: center;
border-top: 1rpx solid rgba(0, 0, 0, 0.07);
color: #2980fd;
font-size: 33rpx;
}
.tips-cancel {
justify-content: center;
align-items: center;
color: $J-color-t80;
font-size: 33rpx;
}
.u-cell-hover {
background-color: #f8f9fa;
}
}
</style>

View File

@@ -0,0 +1,91 @@
<!--
组件作用 单个输入框的弹层
输入框的弹出框 uni必须使用的是 组件方式 无法直接JS调起
// 要求, 输入空也可以点击确定。
示例
A: 一般用法
<JeepayPopupInput ref="ref" label="备注" :maxLength="3" v-model:value="vdata.payRemark" />
B: 自定义 校验规则
<JeepayPopupInput ref="ref" label="备注" :maxLength="3" v-model:value="vdata.payRemark" :rules="[ {required: true }, {pattern: '^(123)+$' } ]" />
@author terrfly
@site https://www.jeequan.com
@date 2022/11/17 10:46
-->
<template>
<uni-popup ref="popupRef" type="dialog">
<uni-popup-dialog :before-close="true" mode="input" :title="`请输入${props.label}`" @confirm="confirmFunc" @close="popupRef.close()">
<template #default>
<uni-forms ref="formRef" :rules="vdata.rules" :modelValue="vdata.formData">
<uni-forms-item label="" name="singleInputVal">
<uni-easyinput :inputBorder="false" type="text" v-model="vdata.formData.singleInputVal" :placeholder="`最多输入${props.maxLength}个字`" :maxlength="props.maxLength" />
</uni-forms-item>
</uni-forms>
</template>
</uni-popup-dialog>
</uni-popup>
</template>
<script setup>
import { reactive, ref, watch } from 'vue'
// 定义组件参数
const props = defineProps({
// 显示文本
label: { type: String, default: '' },
// 操作对象
value: [Number, String],
// 输入框最大位数
maxLength: { type: Number, default: 10 },
rules: { type: Array, default: () => [] },
})
// emit 父组件使用: v-model="val" 进行双向绑定。
const emit = defineEmits(['update:value'])
// 重置按钮不能直接重置时间选择需要通过watch监听进行重置
watch(
() => props.value,
(newVal, oldVal) => {
vdata.formData.singleInputVal = newVal
}
)
const popupRef = ref()
const formRef = ref()
const vdata = reactive({
// 表单的值
formData: { singleInputVal: '' },
// 验证规则
rules: {
singleInputVal: { rules: props.rules },
},
})
// 点击确认按钮。
function confirmFunc() {
formRef.value.validate().then(() => {
emit('update:value', vdata.formData.singleInputVal)
popupRef.value.close()
})
}
// 显示弹层
function open() {
vdata.formData.singleInputVal = props.value
popupRef.value.open()
}
// 将表格事件暴露出去 https://www.jianshu.com/p/39d14c25c987
defineExpose({ open })
</script>
<style lang="scss" scoped></style>

View File

@@ -0,0 +1,361 @@
<!--
组件作用 弹层 列表支持 单选 多选
使用方法
<JeepayPopupListSelect ref="jeepayPopupListSelect" :reqTableDataFunc="reqTableDataFunc" :fields="{ key: 'articleId', left: 'articleId', right: 'title'}"/>
jeepayPopupListSelect.value.open().then((selected) => {
console.log(selected);
jeepayPopupListSelect.value.close() //自行关闭
})
@author terrfly
@site https://www.jeequan.com
@date 2022/11/26 16:24
-->
<template>
<uni-popup ref="popupRef" type="bottom" @maskClick="close" @change="change" mask-background-color="rgba(0,0,0,.5)">
<view class="card-wrapper">
<view class="selected-title" v-if="title">
{{ title }}
</view>
<view v-if="props.searchInputName" class="input-search">
<uni-easyinput
v-model="vdata.searchData[props.searchInputName]"
prefixIcon="search"
:inputBorder="false"
:clearable="false"
:styles="{
backgroundColor: 'transparent'
}"
type="text"
placeholder="搜索"
placeholderStyle=" color: rgba(0,0,0,0.85);font-size: 30rpx;"
/>
<view class="search-button" @tap="searchFunc">搜索</view>
</view>
<!-- 数据渲染 -->
<JTableList ref="jeepayTableListRef" :reqTableDataFunc="reqTableDataFuncWrapper" :searchData="vdata.searchData" :initData="false" height="406rpx">
<template #tableBody="{ record }">
<view class="store" @tap="selectFunc(record)">
<template v-if="isCheckbox">
<view class="more-selected" :class="{ 'more-disabled': record.hasDirector, 'more-active': hasSelected(record) }">
<image :src="record.hasDirector ? '/static/iconImg/icon-disable.svg' : '/static/iconImg/icon-check.svg'" mode="scaleToFill" />
</view>
</template>
<view v-else :class="{ 'store-dot-def': true, 'active-dot': hasSelected(record) }"></view>
<slot name="content" :record="record">
<view class="store-inner-slot">
<view class="left">
<text>{{ record[props.fields.left] }}</text>
</view>
<view class="right">{{ record[props.fields.right] }}</view>
</view>
</slot>
</view>
</template>
</JTableList>
<!-- <button v-if="vdata.hasNext" @tap="jeepayTableListRef.addNext()">下一页</button> -->
<view class="footer-wrapper">
<view class="footer-main">
<view class="footer-button">
<view class="flex-center" hover-class="touch-button" @tap="close">取消</view>
<view @tap="confirmFunc" class="confirm flex-center" hover-class="touch-button">确认</view>
</view>
</view>
</view>
</view>
</uni-popup>
</template>
<script setup>
import { inject, nextTick, reactive, ref, render, watch } from 'vue';
const jeepayTableListRef = ref();
// 定义组件参数
const props = defineProps({
configMode: { type: String, default: '' }, // 搜索时仅展示商户号
// 请求业务数据, 参数可自行控制。
reqTableDataFunc: { type: Function, default: () => {} },
// 搜索输入框的name 不传入则不显示搜索。
searchInputName: { type: String },
// 约定的字段 , 若不合适请通过插槽自行插入。
fields: { type: Object, default: { key: 'id', left: 'left', right: 'right' } },
// 是否多选框, 默认单选。
isCheckbox: { type: Boolean, default: false },
// 标题有则显示无则隐藏
title: [String, Number]
});
let changePageMetaOverflowFunc = inject('changePageMetaOverflowFunc');
const emits = defineEmits(['confirm']);
const popupRef = ref();
console.log(props, 'propsprops');
const vdata = reactive({
searchData: {
}, //当前页的搜索条件
hasNext: false, // 是否包含下一页数据
selectedList: [], // 选择的值
promiseObject: {}
});
// 点击搜索
function searchFunc() {
jeepayTableListRef.value.refTable(true);
}
/** reqTableDataFunc 处理函数 */
function reqTableDataFuncWrapper(params) {
return props.reqTableDataFunc(params).then(({ bizData }) => {
vdata.hasNext = bizData.hasNext; // 是否包含下一页
return Promise.resolve({ bizData });
});
}
// 是否选中
function hasSelected(record) {
return vdata.selectedList.filter((r) => r[props.fields.key] == record[props.fields.key]).length > 0;
}
/** 选择函数 **/
function selectFunc(record) {
// 判断是否已存在
if (hasSelected(record)) {
// 多选需删除
if (props.isCheckbox) {
vdata.selectedList.splice(
vdata.selectedList.findIndex((r) => r[props.fields.key] == record[props.fields.key]),
1
);
}
// 单选无需操作
return false;
}
// 多选直接添加
if (props.isCheckbox) {
if (record.hasDirector) return; //如果被其他店长绑定 禁止选中
return vdata.selectedList.push(record);
} else {
// 单选
return (vdata.selectedList = [record]);
}
}
// 点击确定事件
function confirmFunc() {
// 多选
if (props.isCheckbox) {
emits('confirm', vdata.selectedList);
return vdata.promiseObject.resolve(vdata.selectedList);
}
// 单选 仅返回第一个即可。
emits('confirm', vdata.selectedList.length > 0 ? vdata.selectedList[0] : null);
vdata.promiseObject.resolve(vdata.selectedList.length > 0 ? vdata.selectedList[0] : null);
}
// 显示弹层, 并且返回一个promise
// promise.then(selected)
// 注意: 此Promise 只会回调一次, 如需要验证是否选中正确, 需要使用@confirm事件。
// 如果对拿到的值不做校验,获取到然后关闭。 那么可以直接使用.then() 然后立马关闭弹层。
function open(defaultVal) {
console.log(111111111111111);
// 清空数据
vdata.selectedList = [];
vdata.searchData[props.searchInputName] = '';
// 默认选中。
if (defaultVal) {
if (Array.isArray(defaultVal)) {
vdata.selectedList = defaultVal;
} else {
vdata.selectedList = [defaultVal];
}
}
popupRef.value.open();
nextTick(() => {
jeepayTableListRef.value.refTable(true);
uni.hideTabBar(); // 可能报错, 在nextTick中 不影响其他业务。
});
return new Promise((resolve, reject) => {
vdata.promiseObject.resolve = resolve;
vdata.promiseObject.reject = reject;
});
}
// 关闭弹层
function close() {
popupRef.value.close();
uni.showTabBar();
}
const change = (e) => {
if (changePageMetaOverflowFunc) {
changePageMetaOverflowFunc(!e.show);
}
};
// 将表格事件暴露出去 https://www.jianshu.com/p/39d14c25c987
defineExpose({ open, close });
</script>
<style lang="scss" scoped>
.card-wrapper {
border-radius: 32rpx 32rpx 0 0;
background-color: #fff;
overflow: hidden;
min-height: 70vh;
max-height: 80vh;
overflow-y: auto;
.selected-title {
height: 110rpx;
line-height: 110rpx;
text-align: center;
font-size: 30rpx;
font-weight: 500;
border-bottom: 1rpx solid #ededed;
}
.input-search {
display: flex;
align-items: center;
padding-left: 10rpx;
margin: 30rpx;
border-radius: 10rpx;
background-color: #f7f7f7;
.search-button {
padding: 24rpx 30rpx;
font-size: 32rpx;
font-weight: 500;
color: #2980fd;
}
}
.store {
position: relative;
display: flex;
justify-content: flex-start;
align-items: center;
padding: 0 40rpx;
height: 120rpx;
font-size: 30rpx;
.more-selected {
width: 36rpx;
height: 36rpx;
border-radius: 6rpx;
margin-right: 20rpx;
border: 2rpx solid rgba(217, 217, 217, 1);
image {
width: 100%;
height: 100%;
}
}
.more-disabled {
background-color: #f7f7f7;
}
.more-active {
background-color: #2980fd;
}
.store-dot-def {
position: relative;
margin-right: 20rpx;
width: 36rpx;
height: 36rpx;
background-color: #d7d8d9;
border-radius: 50%;
&::after {
content: '';
display: block;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 50%;
height: 50%;
border-radius: 50%;
background-color: #fff;
}
}
.active-dot {
background-color: #2980fd;
}
.store-inner-slot {
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
.left {
display: flex;
align-items: center;
}
.right {
color: #999999;
}
}
}
.all-store::after {
content: '';
display: block;
position: absolute;
bottom: 0;
left: 40rpx;
right: 40rpx;
height: 1rpx;
background-color: #ededed;
}
.footer-wrapper {
height: 186rpx;
.footer-main {
position: fixed;
left: 0;
right: 0;
bottom: env(safe-area-inset-bottom);
border-top: 1rpx solid #ededed;
.tips {
margin: 20rpx;
text-align: center;
font-size: 27rpx;
color: #a6a6a6;
}
.footer-button {
padding: 0 30rpx;
margin-top: 30rpx;
padding-bottom: 30rpx;
display: flex;
justify-content: space-between;
view {
width: 330rpx;
height: 110rpx;
font-size: 33rpx;
font-weight: 500;
color: rgba(0, 0, 0, 0.5);
border-radius: 20rpx;
background-color: #f7f7f7;
}
.confirm {
color: #fff;
background: $jeepay-bg-primary;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,387 @@
<!--
组件作用 弹层 列表支持 单选 多选
使用方法
<JeepayPopupListSelect ref="jeepayPopupListSelect" :reqTableDataFunc="reqTableDataFunc" :fields="{ key: 'articleId', left: 'articleId', right: 'title'}"/>
jeepayPopupListSelect.value.open().then((selected) => {
console.log(selected);
jeepayPopupListSelect.value.close() //自行关闭
})
@author terrfly
@site https://www.jeequan.com
@date 2022/11/26 16:24
-->
<template>
<uni-popup ref="popupRef" type="bottom" @maskClick="close" @change="change" mask-background-color="rgba(0,0,0,.5)" :safe-area="false">
<view class="card-wrapper">
<view class="selected-title" v-if="title">
{{ title }}
</view>
<view class="search-wrap" v-if="props.searchInputName">
<view class="input-search">
<uni-easyinput
v-model="vdata.searchData[props.searchInputName]"
prefixIcon="search"
:inputBorder="false"
:clearable="false"
:styles="{
backgroundColor: 'transparent'
}"
type="text"
placeholder="搜索"
placeholderStyle=" color: rgba(0,0,0,0.85);font-size: 30rpx;"
/>
<view class="search-button" @tap="searchFunc">搜索</view>
</view>
<!-- <view class="add-btn" hover-class="touch-button" v-if="props.addUse" @tap="">新增应用</view> -->
</view>
<!-- 数据渲染 -->
<JTableList ref="jeepayTableListRef" :reqTableDataFunc="reqTableDataFuncWrapper" :searchData="vdata.searchData" :initData="false" height="406rpx">
<template #tableBody="{ record }">
<view class="store" @tap="selectFunc(record)">
<template v-if="isCheckbox">
<view class="more-selected" :class="{ 'more-disabled': record.hasDirector, 'more-active': hasSelected(record) }">
<image :src="record.hasDirector ? '/static/iconImg/icon-disable.svg' : '/static/iconImg/icon-check.svg'" mode="scaleToFill" />
</view>
</template>
<view v-else :class="{ 'store-dot-def': true, 'active-dot': hasSelected(record) }"></view>
<slot name="content" :record="record">
<view class="store-inner-slot">
<view class="left">
<!-- <text>{{ record[props.fields.left] }}</text> -->
<text class="tit">{{ record[props.fields.left] }}</text>
<text class="ino" v-if="record.intro">{{ record.intro }}</text>
</view>
<view class="right">{{ record[props.fields.right] }}</view>
</view>
</slot>
</view>
</template>
</JTableList>
<!-- <button v-if="vdata.hasNext" @tap="jeepayTableListRef.addNext()">下一页</button> -->
<view class="footer-wrapper">
<view class="footer-main">
<view class="footer-button">
<view class="flex-center" hover-class="touch-button" @tap="close">取消</view>
<view @tap="confirmFunc" class="confirm flex-center" hover-class="touch-button">确认</view>
</view>
</view>
</view>
</view>
</uni-popup>
</template>
<script setup>
import { inject, nextTick, reactive, ref, render, watch } from 'vue';
const jeepayTableListRef = ref();
// 定义组件参数
const props = defineProps({
configMode: { type: String, default: '' }, // 搜索时仅展示商户号
// 请求业务数据, 参数可自行控制。
reqTableDataFunc: { type: Function, default: () => {} },
// 搜索输入框的name 不传入则不显示搜索。
searchInputName: { type: String },
// 约定的字段 , 若不合适请通过插槽自行插入。
fields: { type: Object, default: { key: 'id', left: 'left', right: 'right' } },
// 是否多选框, 默认单选。
isCheckbox: { type: Boolean, default: false },
// 标题有则显示无则隐藏
title: [String, Number],
// 是否显示增加引用按钮
addUse: { type: Boolean, default: false }
});
let changePageMetaOverflowFunc = inject('changePageMetaOverflowFunc');
const emits = defineEmits(['confirm']);
const popupRef = ref();
console.log(props, 'propsprops');
const vdata = reactive({
searchData: {}, //当前页的搜索条件
hasNext: false, // 是否包含下一页数据
selectedList: [], // 选择的值
promiseObject: {}
});
// 点击搜索
function searchFunc() {
jeepayTableListRef.value.refTable(true);
}
/** reqTableDataFunc 处理函数 */
function reqTableDataFuncWrapper(params) {
return props.reqTableDataFunc(params).then(({ bizData }) => {
vdata.hasNext = bizData.hasNext; // 是否包含下一页
return Promise.resolve({ bizData });
});
}
// 是否选中
function hasSelected(record) {
return vdata.selectedList.filter((r) => r[props.fields.key] == record[props.fields.key]).length > 0;
}
/** 选择函数 **/
function selectFunc(record) {
// 判断是否已存在
if (hasSelected(record)) {
// 多选需删除
if (props.isCheckbox) {
vdata.selectedList.splice(
vdata.selectedList.findIndex((r) => r[props.fields.key] == record[props.fields.key]),
1
);
}
// 单选无需操作
return false;
}
// 多选直接添加
if (props.isCheckbox) {
if (record.hasDirector) return; //如果被其他店长绑定 禁止选中
return vdata.selectedList.push(record);
} else {
// 单选
return (vdata.selectedList = [record]);
}
}
// 点击确定事件
function confirmFunc() {
// 多选
if (props.isCheckbox) {
emits('confirm', vdata.selectedList);
return vdata.promiseObject.resolve(vdata.selectedList);
}
// 单选 仅返回第一个即可。
emits('confirm', vdata.selectedList.length > 0 ? vdata.selectedList[0] : null);
vdata.promiseObject.resolve(vdata.selectedList.length > 0 ? vdata.selectedList[0] : null);
}
// 显示弹层, 并且返回一个promise
// promise.then(selected)
// 注意: 此Promise 只会回调一次, 如需要验证是否选中正确, 需要使用@confirm事件。
// 如果对拿到的值不做校验,获取到然后关闭。 那么可以直接使用.then() 然后立马关闭弹层。
function open(defaultVal) {
// 清空数据
vdata.selectedList = [];
vdata.searchData[props.searchInputName] = '';
// 默认选中。
if (defaultVal) {
if (Array.isArray(defaultVal)) {
vdata.selectedList = defaultVal;
} else {
vdata.selectedList = [defaultVal];
}
}
popupRef.value.open();
nextTick(() => {
jeepayTableListRef.value.refTable(true);
uni.hideTabBar(); // 可能报错, 在nextTick中 不影响其他业务。
});
return new Promise((resolve, reject) => {
vdata.promiseObject.resolve = resolve;
vdata.promiseObject.reject = reject;
});
}
// 关闭弹层
function close() {
popupRef.value.close();
uni.showTabBar();
}
const change = (e) => {
if (changePageMetaOverflowFunc) {
changePageMetaOverflowFunc(!e.show);
}
};
// 将表格事件暴露出去 https://www.jianshu.com/p/39d14c25c987
defineExpose({ open, close });
</script>
<style lang="scss" scoped>
.card-wrapper {
border-radius: 32rpx 32rpx 0 0;
background-color: #fff;
overflow: hidden;
min-height: 70vh;
max-height: 80vh;
overflow-y: auto;
.selected-title {
height: 110rpx;
line-height: 110rpx;
text-align: center;
font-size: 30rpx;
font-weight: 500;
border-bottom: 1rpx solid #ededed;
}
.search-wrap {
padding: 30rpx;
display: flex;
.input-search {
flex: 1;
display: flex;
align-items: center;
padding-left: 10rpx;
border-radius: 10rpx;
background-color: #f7f7f7;
.search-button {
padding: 24rpx 30rpx;
font-size: 32rpx;
font-weight: 500;
color: #2980fd;
}
}
.add-btn {
border-radius: 10rpx;
color: #fff;
padding: 0 20upx;
display: flex;
align-items: center;
justify-content: center;
background: $jeepay-bg-primary;
margin-left: 30rpx;
}
}
.store {
position: relative;
display: flex;
justify-content: flex-start;
align-items: center;
margin: 40rpx;
font-size: 30rpx;
background-color: #f5f5f5;
padding: 20rpx;
border-radius: 12rpx;
.more-selected {
flex-shrink: 0;
width: 36rpx;
height: 36rpx;
border-radius: 6rpx;
margin-right: 20rpx;
border: 2rpx solid rgba(217, 217, 217, 1);
image {
width: 100%;
height: 100%;
}
}
.more-disabled {
background-color: #f7f7f7;
}
.more-active {
background-color: #2980fd;
}
.store-dot-def {
position: relative;
margin-right: 20rpx;
width: 36rpx;
height: 36rpx;
background-color: #d7d8d9;
border-radius: 50%;
&::after {
content: '';
display: block;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 50%;
height: 50%;
border-radius: 50%;
background-color: #fff;
}
}
.active-dot {
background-color: #2980fd;
}
.store-inner-slot {
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
.left {
display: flex;
flex-direction: column;
.ino {
color: #999;
padding-top: 12upx;
font-size: 24upx;
}
}
.right {
color: #999999;
}
}
}
.all-store::after {
content: '';
display: block;
position: absolute;
bottom: 0;
left: 40rpx;
right: 40rpx;
height: 1rpx;
background-color: #ededed;
}
.footer-wrapper {
height: 186rpx;
.footer-main {
position: fixed;
left: 0;
right: 0;
bottom: env(safe-area-inset-bottom);
border-top: 1rpx solid #ededed;
background-color: #fff;
.tips {
margin: 20rpx;
text-align: center;
font-size: 27rpx;
color: #a6a6a6;
}
.footer-button {
padding: 0 30rpx;
margin-top: 30rpx;
padding-bottom: 30rpx;
display: flex;
justify-content: space-between;
view {
width: 330rpx;
height: 110rpx;
font-size: 33rpx;
font-weight: 500;
color: rgba(0, 0, 0, 0.5);
border-radius: 20rpx;
background-color: #f7f7f7;
}
.confirm {
color: #fff;
background: $jeepay-bg-primary;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,116 @@
<!--
单选 view 一般用作 form表单内使用
@author terrfly
@site https://www.jeepay.vip
@date 2022/12/01 16:18
-->
<template>
<view>
<view @tap="show">
<!-- 插槽自定义内容 -->
<slot name="view" :record="vdata.checkedData">
<view class="selected-radio">
<view v-if="hasVal()" style="color: black">{{vdata.checkedData?.label}}</view>
<view v-else>{{props.label}}</view>
<image style="width: 30rpx;height: 30rpx" src="/static/right.svg" mode="scaleToFill" />
</view>
</slot>
</view>
<!-- popup tap不能放置在同一个 view下 -->
<view>
<JSinglePopup ref="popupRef" :list="props.list" @confirm="confirmFunc" />
</view>
</view>
</template>
<script setup>
import { ref, reactive, nextTick, watch, onMounted, inject } from "vue"
const popupRef = ref()
// emit 父组件使用: v-model:value="val" 进行双向绑定。
const emits = defineEmits(['update:value', 'change'])
const props = defineProps({
//绑定的值, 双向绑定
value: { type: [String, Number] },
// 显示的名字
label: { type: [String, Number], default: "请选择" },
// 数组
list: { type: Array, default: () => [] },
// 是否多选框, 默认单选。
isCheckbox: { type: Boolean, default: false },
})
console.log(props.isCheckbox,'isCheckboxisCheckboxisCheckboxisCheckbox');
onMounted(()=>{
changePropsVal(props.value)
})
// 切换时
watch(() => props.value, (newVal, oldVal) => {
changePropsVal(newVal)
}
)
function changePropsVal(newVal){
let list = props.list.filter(r => r.value == newVal)
vdata.checkedData = list.length > 0 ? list[0] : { }
}
const vdata = reactive({
checkedData: { } // 当前选择的值
})
function show(){
popupRef.value.open(vdata.checkedData.value)
}
// 选择用户类型完毕
function confirmFunc(v){
vdata.checkedData = v
emits("update:value", v.value)
if(v.value!==1){
emits("change", v.value)
}
}
function hasVal(){
return vdata.checkedData && vdata.checkedData.label ? true : false
}
</script>
<style lang="scss" scoped>
.selected-radio {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 32rpx;
color: #b3b3b3;
image {
width: 120rpx;
height: 120rpx;
}
}
</style>

View File

@@ -0,0 +1,134 @@
<!--
Jeepay 门店选择 / 应用选择
@author terrfly
@site https://www.jeequan.com
@date 2022/12/06 12:55
-->
<template>
<!-- 选择门店 -->
<JeepayPopupListSelect
ref="jeepayRedPopupListSelectByBizinfos"
:reqTableDataFunc="reqTableDataByBizsFunc"
:searchInputName="getSearchInputName()"
:fields="getField()"
/>
</template>
<script setup>
import { reactive, ref } from 'vue'
import { reqLoad, API_URL_MCH_STORE_LIST, API_URL_MCH_APP_LIST } from '@/http/apiManager.js'
// 定义组件参数
const props = defineProps({
configMode: { type: String, default: ''}, // 搜索时仅展示商户号
// 是否多选框, 默认单选。
isCheckbox: { type: Boolean, default: false },
// 自动关闭, 点击确定, 自动关闭
autoClose: { type: Boolean, default: true },
// 业务类型: 默认门店
bizType: { type: String, default: 'store' },
// 是否支持选择 【 全部门店 、 全部应用 】
isShowAllBiz: { type: Boolean, default: false },
// 特殊参数处理
params:{type:Object,default:()=>({})}
})
const jeepayRedPopupListSelectByBizinfos = ref()
function getField(){
if(props.bizType == 'store'){
return { key: 'storeId', left: 'storeName', right: 'storeId' }
}
if(props.bizType == 'mchApp'){
return { key: 'appId', left: 'appName', right: 'appId' }
}
}
function getSearchInputName(){
if(props.bizType == 'store'){
return "storeName"
}
if(props.bizType == 'mchApp'){
return "appName"
}
}
function reqTableDataByBizsFunc(params) {
Object.assign(params,props.params)
console.log(params);
let apiResult = null;
if(props.bizType == 'store'){
apiResult = reqLoad.list(API_URL_MCH_STORE_LIST, params)
}
if(props.bizType == 'mchApp'){
apiResult = reqLoad.list(API_URL_MCH_APP_LIST, params)
}
return apiResult.then((apiRes) => {
return processApiResBizData(params, apiRes)
})
}
// 选择门店
function open (defaultVal){
console.log(defaultVal,'defaultValdefaultVal')
return jeepayRedPopupListSelectByBizinfos.value.open(defaultVal).then((selected) => {
// 自动关闭
if(props.autoClose){
close()
}
return selected;
})
}
function close(){
jeepayRedPopupListSelectByBizinfos.value.close() //自行关闭
}
function processApiResBizData(params, apiRes){
// 第一页 拼接全部门店 (index = 0 )
if(params.pageNumber == 1 && props.isShowAllBiz){
if(props.bizType == 'store'){
apiRes.bizData.records.unshift({storeName: '全部门店', storeId: ''})
}
if(props.bizType == 'mchApp'){
apiRes.bizData.records.unshift({appName: '全部应用', appId: ''})
}
}
return apiRes
}
// 将表格事件暴露出去 https://www.jianshu.com/p/39d14c25c987
defineExpose({ open, close })
</script>
<style>
</style>

View File

@@ -0,0 +1,355 @@
<!--
组件作用 弹层 列表支持 单选 多选
使用方法
<JeepayPopupListSelect ref="jeepayPopupListSelect" :reqTableDataFunc="reqTableDataFunc" :fields="{ key: 'articleId', left: 'articleId', right: 'title'}"/>
jeepayPopupListSelect.value.open().then((selected) => {
console.log(selected);
jeepayPopupListSelect.value.close() //自行关闭
})
@author terrfly
@site https://www.jeequan.com
@date 2022/11/26 16:24
-->
<template>
<uni-popup ref="popupRef" type="bottom" @maskClick="close" @change="change" mask-background-color="rgba(0,0,0,.5)" :safe-area="false">
<view class="card-wrapper">
<view class="selected-title" v-if="title">
{{ title }}
</view>
<view v-if="props.searchInputName" class="input-search">
<uni-easyinput
v-model="vdata.searchData[props.searchInputName]"
prefixIcon="search"
:inputBorder="false"
:clearable="false"
:styles="{
backgroundColor: 'transparent',
}"
type="text"
placeholder="搜索"
placeholderStyle=" color: rgba(0,0,0,0.85);font-size: 30rpx;"
/>
<view class="search-button" @tap="searchFunc">搜索</view>
</view>
<!-- 数据渲染 -->
<JTableList ref="jeepayTableListRef" :reqTableDataFunc="reqTableDataFuncWrapper" :searchData="vdata.searchData" :initData="false" height='406rpx'>
<template #tableBody="{ record }">
<view class="store" @tap="selectFunc(record)">
<template v-if="isCheckbox">
<view class="more-selected" :class="{ 'more-disabled': record.hasDirector, 'more-active': hasSelected(record) }">
<image :src="record.hasDirector ? '/static/iconImg/icon-disable.svg' : '/static/iconImg/icon-check.svg'" mode="scaleToFill" />
</view>
</template>
<view v-else :class="{ 'store-dot-def': true, 'active-dot': hasSelected(record) }"></view>
<slot name="content" :record="record">
<view class="store-inner-slot">
<view class="left">
<text>{{ record[props.fields.left] }}</text>
</view>
<view class="right">{{ record[props.fields.right] }}</view>
</view>
</slot>
</view>
</template>
</JTableList>
<!-- <button v-if="vdata.hasNext" @tap="jeepayTableListRef.addNext()">下一页</button> -->
<view class="footer-wrapper">
<view class="footer-main">
<view class="footer-button">
<view class="flex-center" hover-class="touch-button" @tap="close">取消</view>
<view @tap="confirmFunc" class="confirm flex-center" hover-class="touch-button">确认</view>
</view>
</view>
</view>
</view>
</uni-popup>
</template>
<script setup>
import { inject, nextTick, reactive, ref, render, watch } from 'vue'
const jeepayTableListRef = ref()
// 定义组件参数
const props = defineProps({
// 请求业务数据, 参数可自行控制。
reqTableDataFunc: { type: Function, default: () => {} },
// 搜索输入框的name 不传入则不显示搜索。
searchInputName: { type: String },
// 约定的字段 , 若不合适请通过插槽自行插入。
fields: { type: Object, default: { key: 'id', left: 'left', right: 'right' } },
// 是否多选框, 默认单选。
isCheckbox: { type: Boolean, default: false },
// 标题有则显示无则隐藏
title: [String, Number],
})
let changePageMetaOverflowFunc = inject('changePageMetaOverflowFunc')
const emits = defineEmits(['confirm'])
const popupRef = ref()
const vdata = reactive({
searchData: {}, //当前页的搜索条件
hasNext: false, // 是否包含下一页数据
selectedList: [], // 选择的值
promiseObject: {},
})
// 点击搜索
function searchFunc() {
jeepayTableListRef.value.refTable(true)
}
/** reqTableDataFunc 处理函数 */
function reqTableDataFuncWrapper(params) {
return props.reqTableDataFunc(params).then(({ bizData }) => {
vdata.hasNext = bizData.hasNext // 是否包含下一页
return Promise.resolve({ bizData })
})
}
// 是否选中
function hasSelected(record) {
return vdata.selectedList.filter((r) => r[props.fields.key] == record[props.fields.key]).length > 0
}
/** 选择函数 **/
function selectFunc(record) {
// 判断是否已存在
if (hasSelected(record)) {
// 多选需删除
if (props.isCheckbox) {
vdata.selectedList.splice(
vdata.selectedList.findIndex((r) => r[props.fields.key] == record[props.fields.key]),
1
)
}
// 单选无需操作
return false
}
// 多选直接添加
if (props.isCheckbox) {
if (record.hasDirector) return //如果被其他店长绑定 禁止选中
return vdata.selectedList.push(record)
} else {
// 单选
return (vdata.selectedList = [record])
}
}
// 点击确定事件
function confirmFunc() {
// 多选
if (props.isCheckbox) {
emits('confirm', vdata.selectedList)
return vdata.promiseObject.resolve(vdata.selectedList)
}
// 单选 仅返回第一个即可。
emits('confirm', vdata.selectedList.length > 0 ? vdata.selectedList[0] : null)
vdata.promiseObject.resolve(vdata.selectedList.length > 0 ? vdata.selectedList[0] : null)
}
// 显示弹层, 并且返回一个promise
// promise.then(selected)
// 注意: 此Promise 只会回调一次, 如需要验证是否选中正确, 需要使用@confirm事件。
// 如果对拿到的值不做校验,获取到然后关闭。 那么可以直接使用.then() 然后立马关闭弹层。
function open(defaultVal) {
// 清空数据
vdata.selectedList = []
vdata.searchData[props.searchInputName] = ''
// 默认选中。
if (defaultVal) {
if (Array.isArray(defaultVal)) {
vdata.selectedList = defaultVal
} else {
vdata.selectedList = [defaultVal]
}
}
popupRef.value.open()
nextTick(() => {
jeepayTableListRef.value.refTable(true)
uni.hideTabBar() // 可能报错, 在nextTick中 不影响其他业务。
})
return new Promise((resolve, reject) => {
vdata.promiseObject.resolve = resolve
vdata.promiseObject.reject = reject
})
}
// 关闭弹层
function close() {
popupRef.value.close()
uni.showTabBar()
}
const change = (e) => {
if (changePageMetaOverflowFunc) {
changePageMetaOverflowFunc(!e.show)
}
}
// 将表格事件暴露出去 https://www.jianshu.com/p/39d14c25c987
defineExpose({ open, close })
</script>
<style lang="scss" scoped>
.card-wrapper {
border-radius: 32rpx 32rpx 0 0;
background-color: #fff;
overflow: hidden;
min-height: 70vh;
max-height: 80vh;
overflow-y: auto;
.selected-title {
height: 110rpx;
line-height: 110rpx;
text-align: center;
font-size: 30rpx;
font-weight: 500;
border-bottom: 1rpx solid #ededed;
}
.input-search {
display: flex;
align-items: center;
padding-left: 10rpx;
margin: 30rpx;
border-radius: 10rpx;
background-color: #f7f7f7;
.search-button {
padding: 24rpx 30rpx;
font-size: 32rpx;
font-weight: 500;
color: #2980fd;
}
}
.store {
position: relative;
display: flex;
justify-content: flex-start;
align-items: center;
padding: 0 40rpx;
height: 120rpx;
font-size: 30rpx;
.more-selected {
width: 36rpx;
height: 36rpx;
border-radius: 6rpx;
margin-right: 20rpx;
border: 2rpx solid rgba(217, 217, 217, 1);
image {
width: 100%;
height: 100%;
}
}
.more-disabled {
background-color: #f7f7f7;
}
.more-active {
background-color: #2980fd;
}
.store-dot-def {
position: relative;
margin-right: 20rpx;
width: 36rpx;
height: 36rpx;
background-color: #d7d8d9;
border-radius: 50%;
&::after {
content: '';
display: block;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 50%;
height: 50%;
border-radius: 50%;
background-color: #fff;
}
}
.active-dot {
background-color: #2980fd;
}
.store-inner-slot {
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
.left {
display: flex;
align-items: center;
}
.right {
color: #999999;
}
}
}
.all-store::after {
content: '';
display: block;
position: absolute;
bottom: 0;
left: 40rpx;
right: 40rpx;
height: 1rpx;
background-color: #ededed;
}
.footer-wrapper {
height: 186rpx;
.footer-main {
position: fixed;
left: 0;
right: 0;
bottom: env(safe-area-inset-bottom);
border-top: 1rpx solid #ededed;
.tips {
margin: 20rpx;
text-align: center;
font-size: 27rpx;
color: #a6a6a6;
}
.footer-button {
padding: 0 30rpx;
margin-top: 30rpx;
padding-bottom: 30rpx;
display: flex;
justify-content: space-between;
view {
width: 330rpx;
height: 110rpx;
font-size: 33rpx;
font-weight: 500;
color: rgba(0, 0, 0, 0.5);
border-radius: 20rpx;
background-color: #f7f7f7;
}
.confirm {
color: #fff;
background: $jeepay-bg-primary;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,74 @@
<!--
组件作用 状态筛选器 一般用作搜索栏右侧
使用方法
@author terrfly
@site https://www.jeequan.com
@date 2022/11/26 16:24
-->
<template>
<view>
<view class="code-state" @tap="statePopup.open(props.bizType)">
{{ props.list.find(i => {
return i.value == props.bizType
}).label }}
<image src="/pageDevice/static/devIconImg/icon-arrow-down.svg" mode="scaleToFill" />
</view>
</view>
<view>
<JSinglePopup :list="props.list" :title="props.title" ref="statePopup" @confirm="confirmState" />
</view>
</template>
<script setup>
import { reactive, ref } from 'vue'
const emits = defineEmits(['update:bizType', 'change'])
// 定义组件参数
const props = defineProps({
// 双向绑定
bizType: { type: [Number, String] },
// 搜索数据
list: { type: Array },
title: { type: String }
})
const vdata = reactive({
selected: {} // 当前选择对象
})
const statePopup = ref(null)
//按状态筛选
function confirmState(r){
vdata.selected = r || { }
emits('update:bizType', vdata.selected.value)
emits('change', vdata.selected)
}
</script>
<style lang="scss" scoped>
.code-state {
display: flex;
align-items: center;
margin-left: 40rpx;
font-size: 30rpx;
color: #222425;
image {
margin-left: 10rpx;
width: 40rpx;
height: 40rpx;
}
}
</style>

View File

@@ -0,0 +1,79 @@
<!--
组件作用 状态筛选器 一般用作搜索栏右侧
使用方法
@author terrfly
@site https://www.jeequan.com
@date 2022/11/26 16:24
-->
<template>
<view>
<view class="code-state" @tap="statePopup.open(props.state)">
{{ vdata.selected.label || '全部状态' }}
<image src="/pageDevice/static/devIconImg/icon-arrow-down.svg" mode="scaleToFill" />
</view>
</view>
<view>
<JSinglePopup :list="stateList" title="按设备状态筛选" ref="statePopup" @confirm="confirmState" />
</view>
</template>
<script setup>
import { reactive, ref } from 'vue'
const emits = defineEmits(['update:state', 'change'])
// 定义组件参数
const props = defineProps({
// 双向绑定
state: { type: [Number, String] },
})
const vdata = reactive({
selected: {} , // 当前选择对象
})
const statePopup = ref(null)
const stateList = reactive([
{ label: '全部状态', value: '' },
{ label: '启用', value: '1' },
{ label: '禁用', value: '0' },
])
//按状态筛选
function confirmState(r){
vdata.selected = r || { }
emits('update:state', vdata.selected.value)
emits('change', vdata.selected.value)
}
</script>
<style lang="scss" scoped>
.code-state {
display: flex;
align-items: center;
margin-left: 40rpx;
font-size: 30rpx;
color: #222425;
image {
margin-left: 10rpx;
width: 40rpx;
height: 40rpx;
}
}
</style>

View File

@@ -0,0 +1,119 @@
<!--
Jeepay 通用状态切换按钮 支持switch和badge两个格式 根据权限进行判断
参考 jeepay-ui组件
@author terrfly
@site https://www.jeepay.vip
@date 2021/5/8 07:18
-->
<template>
<view>
<template v-if="props.showSwitchType" >
<switch v-if="vdata.isShowSwitchFlag" :checked="vdata.switchChecked" color="#238FFC" :style="{ transform: 'scale(' + scale + ')', margin: margin }" @change="changeFunc" />
</template>
<template v-else>
<image v-if="vdata.switchChecked == 1" class="default-image" src="/pageDevice/static/devIconImg/icon-default.svg" mode="scaleToFill" />
<image v-if="vdata.switchChecked == 0" class="default-image" src="/pageDevice/static/devIconImg/icon-noDefault.svg" mode="scaleToFill" />
</template>
</view>
<JeepayPopupConfirm ref="jeepayPopupConfirmRef" />
</template>
<script setup>
import { ref, reactive, nextTick, watch, onMounted } from "vue"
const props = defineProps({
// 样式参数
scale: { type: Number, default: 0.8 }, //控制开关大小 倍数 默认.8
margin: { type: String, default: "0" }, // 控制开关外边距默认0
showSwitchType: { type: Boolean, default: false }, // 默认 badge
//开关状态, 0-关闭, 1-开启
state: { type: [Number,String], default: 1 },
// 是否显示二次确认
confirm: { type: Boolean, default: true },
confirmTitle: { type: String, default: '确定修改状态?' }, // 二次确认提示信息
// updateStateFunc回调事件. 需返回promise
updateStateFunc: { type: Function },
})
const jeepayPopupConfirmRef = ref() //提示弹窗
const emits = defineEmits(["update:state"])
onMounted(()=>{
vdata.switchChecked = props.state == 1
})
const vdata = reactive({
isShowSwitchFlag: true , // 用于重新加载组件
switchChecked: true, // 是否选中
})
// 监听 props属性
watch(() => props.state, function(o, n){
vdata.switchChecked = props.state == 1
}
)
function changeFunc(e){
let changeVal = e.detail.value
// 显示弹层
if(props.confirm){
jeepayPopupConfirmRef.value.open(props.confirmTitle).then(() => {
return propsUpdateStateFunc(changeVal ? 1 : 0)
}).then(() => {
emits("update:state", changeVal ? 1 : 0)
reloadSwitch(changeVal)
}).catch(() => {
reloadSwitch(!changeVal)
})
}else{ // 调起更新函数
propsUpdateStateFunc(changeVal ? 1 : 0).then(() => {
emits("update:state", changeVal ? 1 : 0)
reloadSwitch(changeVal)
}).catch(() => {
reloadSwitch(!changeVal)
})
}
}
// uniapp-switch 组件存在问题, 当用户出发了切换, 那么v-model:checked 绑定的元素不在生效了。
function reloadSwitch(changeVal){
vdata.switchChecked = changeVal
vdata.isShowSwitchFlag = false
nextTick(() => vdata.isShowSwitchFlag = true)
}
// props. default app 和小程序有出入,此函数用作兼容。
// APP default : () => { return (state) => {Promie.resole()} } (小层序认为 default即函数 )
function propsUpdateStateFunc(state){
if(props.updateStateFunc){
return props.updateStateFunc(state)
}
return Promise.resolve()
}
</script>
<style lang="scss" scoped>
.default-image {
width: 50rpx;
height: 50rpx;
}
</style>

View File

@@ -0,0 +1,119 @@
<!--
Jeepay 表格列表 支持 下滑 上滑刷新
业务页面最好也监听下 触底函数 否则H5无法监听到
import { onReachBottom } from '@dcloudio/uni-app'
onReachBottom(() => { })
@author terrfly
@site https://www.jeequan.com
@date 2022/11/16 15:55
-->
<template>
<view>
<template v-for="(record, i) in vdata.allData" :key="i">
<slot name="tableBody" :record="record" :index="i" />
</template>
</view>
<view class="list-null" v-if="!vdata.apiResData.hasNext && showListNull">暂无更多数据</view>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
import { onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app';
// 定义传入属性
const props = defineProps({
reqTableDataFunc: { type: Function, default: () => {} },
searchData: { type: Object, default: () => {} }, // 搜索条件参数
pageSize: { type: Number, default: 10 }, // 默认每页条数
initData: { type: Boolean, default: true }, // 初始化列表数据, 默认true
showListNull: { type: Boolean, default: true } //是否显示缺省 默认显示
});
const vdata = reactive({
allData: [], // app与web不同 app是每次查询到数据会拼接到后面
// 接口返回的数据
apiResData: { total: 0, records: [] },
// 分页参数
iPage: { pageNumber: 1, pageSize: props.pageSize }
});
onMounted(() => {
//初始化表数据
props.initData ? refTable(true) : undefined;
});
// 查询表格数据
function refTable(isToFirst = false) {
if (isToFirst) {
// 重新搜索, 第一页。
vdata.iPage.pageNumber = 1;
vdata.allData = []; //清空数据
}
// 更新检索数据
return props.reqTableDataFunc(Object.assign({}, vdata.iPage, props.searchData)).then(({ bizData }) => {
Object.assign(vdata.apiResData, bizData); // 列表数据更新
if (bizData.records) {
vdata.allData.push(...bizData.records); // 利用展开语法代替forEach
}
});
}
/** 追加下一页数据 **/
function addNext() {
// 包含下一页
if (vdata.apiResData.hasNext) {
vdata.iPage.pageNumber++;
refTable(false);
}
}
// 下拉刷新
onPullDownRefresh(() => {
refTable(true).then(() => {
uni.stopPullDownRefresh();
});
});
// 监听,触底事件。 查询下一页
onReachBottom(() => {
addNext();
});
// 将表格事件暴露出去 https://www.jianshu.com/p/39d14c25c987
defineExpose({ refTable, addNext });
</script>
<style lang="scss" scoped>
.list-null {
position: relative;
line-height: 110rpx;
text-align: center;
font-size: 26rpx;
color: #a6a6a6;
&::after,
&::before {
content: '';
display: block;
position: absolute;
top: 50%;
width: 30%;
height: 2rpx;
background-color: #ededed;
transform: translateY(-50%);
}
&::after {
left: 40rpx;
}
&::before {
right: 40rpx;
}
}
</style>

View File

@@ -0,0 +1,90 @@
<!--
Jeepay 通用列表条目 包含 头像 主标题 副标题
@author terrfly
@site https://www.jeequan.com
@date 2022/11/16 15:55
-->
<template>
<view :class="`list-item ${props.viewClass}`">
<image :style="props.logoStyle" v-if="props.logo" :src="props.logo" mode="scaleToFill" />
<view class="list-info">
<view class="list-title">
<view class="list-name">
<slot name="title">{{ props.title }} </slot>
</view>
<slot name="titleRight">
<!-- 直接写 typeof 页面报错 -->
<template v-if="isShowState()" >
<view v-if="props.state == 1" class="state-dot state-dot-enable"></view>
<view v-else class="state-dot state-dot-disable"></view>
</template>
<template v-if="navListComputed">
<image style="width: 70rpx; height: 70rpx" src="/pageDevice/static/devIconImg/icon-more-white.svg" mode="scaleToFill" @tap="single.open()" />
</template>
</slot>
</view>
<view class="list-subtitle"><slot name="subtitle">{{ props.subtitle }} </slot></view>
</view>
<JSinglePopup ref="single" :list="navListComputed" activeColor="#FF5B4C" />
</view>
</template>
<script setup>
import {ref, reactive, onMounted, computed } from 'vue'
import ak from '@/commons/utils/ak.js'
// 弹层
const single = ref()
// 定义传入属性
const props = defineProps({
title: { type: [String, Number] }, // 标题
subtitle: { type: [String, Number] }, // 副标题
logo: { type: String }, // 图标
logoStyle: { type: Object } , // logo颜色背景图
moreBtnList: { type: Array }, //更多按钮
// 状态开启蓝色or 关闭(置灰) 【 注意state 和 moreBtnList 二选一, 或者请使用插槽覆写 titleRight 】
state: { type: [Number, String] },
viewClass: { type: String, default: '' }, // 样式透传, 小程序不支持再组件上加class(不生效), 需要特殊定义,特殊传入。
})
function isShowState(){
return typeof(props.state) != 'undefined'
}
// 计算属性
let navListComputed = computed(() => {
if(!props.moreBtnList){
return props.moreBtnList
}
return props.moreBtnList.filter(r => hasEnt(r.entId))
})
function hasEnt(entId){
// 不包含: 说明无需隐藏
if(!entId){
return true
}
return ak.ent.has(entId)
}
</script>
<style>
</style>

View File

@@ -0,0 +1,56 @@
<template>
<view class="tag-wrapper" :class="[calcType()]" :style="styles">
<slot />
</view>
</template>
<script setup>
import { reactive } from 'vue'
const props = defineProps({
type: { type: [String, Object], default: 'green' },
styles: { type: Object, default: () => ({}) },
})
const classList = ['purple', 'green', 'blue', 'green-rgba']
const calcType = () => {
try {
// 如果传入样式对象覆写样式
if (Object.keys(props.styles).length > 0) return ''
//如果预设样式类型中包含样式 使用预设样式类型
if (classList.includes(props.type)) return props.type
throw `预设样式类型中未包含此字段 请使使用style字段传入样式对象 自定义样式 注意样式名驼峰语法 目前预设样式字段有 ${classList.join(',')} `
} catch (err) {
console.error('error', err)
}
}
</script>
<style lang="scss" scoped>
.tag-wrapper {
display: inline-block;
margin-left: 15rpx;
padding: 0 15rpx;
height: 40rpx;
line-height: 40rpx;
border-radius: 6rpx;
text-align: center;
font-size: 23rpx;
color: #fff;
}
.purple {
background: linear-gradient(270deg, rgba(220, 61, 138, 1) 0%, rgba(187, 23, 92, 1) 100%);
}
.green {
background: linear-gradient(270deg, rgba(61, 220, 68, 1) 0%, rgba(23, 187, 118, 1) 100%);
}
.blue {
background: linear-gradient(270deg, rgba(35, 161, 252, 1) 0%, rgba(26, 102, 255, 1) 100%);
}
.green-rgba{
color: rgba(23, 188, 118, 1);
background: linear-gradient(270deg, rgba(61, 220, 68, 0.3) 0%, rgba(23, 187, 118, 0.3) 100%);
}
</style>

View File

@@ -0,0 +1,167 @@
<!--
图片上传组件
@author terrfly
@site https://www.jeequan.com
@date 2022/11/30 18:18
-->
<template>
<view style="flex-grow: 1">
<!-- 图片内 x 号的模式 -->
<template v-if="props.mode == 'img'">
<!-- 包含图片 -->
<template v-if="props.src">
<view class="image-wrapper">
<image v-if="!props.readonly" @tap="delImg" class="del-image" src="/static/iconImg/icon-x-white.svg" mode="scaleToFill" />
<image class="default-img" :src="props.src" @tap="preview"></image>
</view>
</template>
<template v-else>
<!-- 不包含图片 -->
<view @tap="chooseImageAndUpload" style="flex-grow: 1; display: flex; justify-content: space-between; align-items: center">
<image
v-if="!props.readonly"
style="width: 150rpx; height: 150rpx; background-color: #f7f7f7; border-radius: 15rpx"
src="/static/iconImg/default-img.svg"
mode="scaleToFill"
/>
<image src="/pageDevice/static/devIconImg/icon-arrow-sex.svg" mode="scaleToFill" style="width: 120rpx; height: 120rpx" />
</view>
</template>
</template>
<!-- 页面图片 加入 切换按钮 -->
<template v-if="props.mode == 'viewbtn'">
<image class="default-img" :src="props.src" @tap="preview" mode="aspectFill"></image>
<!-- 图片预览手写非原生 -->
<enlarge v-if="vdata.showEnlarge" :imgs="props.src" :changeIsShow="!props.readonly" @chooseImg="chooseImageAndUpload" @enlargeClose="vdata.showEnlarge = false" />
</template>
</view>
</template>
<script setup>
import { ref, reactive } from 'vue';
import http from '@/http/http.js';
import infoBox from '@/commons/utils/infoBox.js';
import enlarge from './enlarge.vue'; // 图片预览
import { API_URL_SINGLE_FILE_UPLOAD, $ossFilesForm } from '@/http/apiManager.js';
// emit 父组件使用: v-model:src="val" 进行双向绑定。
const emit = defineEmits(['update:src', 'change']);
// 定义 父组件传参
const props = defineProps({
src: { type: String, default: '' }, // 双向绑定 文件地址
bizType: { type: String, default: '' }, // 业务类型
imgSize: { type: Number, default: 5 }, // 上传图片大小限制 默认 5 M
// 两种模式: img - 图片包含删除按钮支持删除, viewbtn-图片内支预览按钮切换。
mode: { type: String, default: 'img' },
// 预览模式, 不支持切换图片。
readonly: { type: Boolean, default: false }
});
// 定义响应式数据
const vdata = reactive({
showEnlarge: false,
action: '', // 文件form表单请求地址
uploadForm: {
action: '', // 请求地址
header: {}, // 请求头
params: {} // 参数
}
});
// 预览图片
function preview() {
if (props.mode == 'img') {
// 原生图片预览
uni.previewImage({ urls: [props.src] });
} else {
// 组件模式
vdata.showEnlarge = true;
}
}
// 删除图片
function delImg() {
emit('update:src', '');
emit('change', '');
}
// 选择图片 and 上传
function chooseImageAndUpload() {
// 最多选择一张图片 && 压缩图片 && 支持相册 和 相机
uni.chooseImage({ count: 1, sizeType: ['compressed'], sourceType: ['album', 'camera'] }).then((res) => {
let file = res.tempFiles[0];
// 预先检查
if (!beforeCheck(file)) {
return false;
}
// 检查通过
$ossFilesForm(props.bizType, file).then(({ bizData }) => {
// 本地方式
if (bizData.formActionUrl === 'LOCAL_SINGLE_FILE_URL') {
return http.upload(API_URL_SINGLE_FILE_UPLOAD, { bizType: props.bizType }, file).then(({ bizData }) => {
emit('update:src', bizData);
emit('change', bizData);
});
}
// oss 直传
uni.uploadFile({ url: bizData.formActionUrl, filePath: file.path, name: 'file', formData: bizData.formParams }).then((ossRes) => {
if (ossRes.statusCode == 200) {
// 上传成功
emit('update:src', bizData.ossFileUrl);
emit('change', bizData.ossFileUrl);
return false;
}
infoBox.showToast('oss服务响应异常');
});
});
});
}
function beforeCheck(file) {
return true;
}
defineExpose({
preview
});
</script>
<style lang="scss" scoped>
.default-img {
display: block;
width: 150rpx;
height: 150rpx;
background-color: #f7f7f7;
border-radius: 15rpx;
}
.image-wrapper {
position: relative;
width: 150rpx;
height: 150rpx;
margin-bottom: 20upx;
.del-image {
position: absolute;
top: -20rpx;
right: -20rpx;
z-index: 10;
width: 40rpx;
height: 40rpx;
border-radius: 50%;
background-color: tomato;
}
}
</style>

View File

@@ -0,0 +1,143 @@
<template>
<view class="previewImage" :style="{ 'background-color': 'rgba(0,0,0,' + opacity + ')' }" @tap.stop="close">
<movable-area class="marea" scale-area>
<movable-view
:id="'movable-view-' + i"
:key="'movable-view-' + i"
class="mview"
direction="all"
:out-of-bounds="false"
:inertia="true"
damping="90"
friction="2"
scale="true"
scale-min="1"
scale-max="4"
:scale-value="scale"
>
<image
:id="'image-' + i"
:key="'movable-view' + i"
class="image"
:src="imgs"
:data-index="i"
:data-src="img"
mode="widthFix"
/>
</movable-view>
</movable-area>
<view v-if="changeIsShow" class="change-img" @click="chooseImg">更换图片</view>
</view>
</template>
<script>
export default {
name: "ksj-previewImage", //插件名称
props: {
imgs: {
//图片列表
type: String,
required: true,
default: "",
},
//透明度,0到1之间。
opacity: {
type: Number,
default: 1,
},
changeIsShow: { type: Boolean, default: true },
},
data() {
return {
swiper: false, //是否禁用
show: false, //显示状态
index: 0, //当前页
deg: 0, //旋转角度
time: 0, //定时器
interval: 1000, //长按事件
scale: 1, //缩放比例
}
},
methods: {
chooseImg() {
this.$emit("chooseImg")
},
//旋转
rotate(e) {
this.deg = this.deg == 270 ? 0 : this.deg + 90
},
close() {
this.$emit("enlargeClose")
},
},
}
</script>
<!--使用scss,只在本组件生效-->
<style lang="scss" scoped>
.previewImage {
z-index: 25;
position: fixed;
top: 0;
left: 0;
z-index: 999;
width: 100%;
height: 100%;
background-color: #000000;
user-select: none;
.marea {
height: 100%;
width: 100%;
position: fixed;
overflow: hidden;
.mview {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: auto;
min-height: 100%;
.image {
width: 100%;
}
}
}
.rotate {
position: absolute;
right: 10rpx;
width: 120rpx;
height: 56rpx;
bottom: 10rpx;
text-align: center;
padding: 10rpx;
.text {
background-color: rgba(0, 0, 0, 0.5);
color: #fff;
font-size: 30rpx;
border-radius: 20rpx;
border: 1rpx solid #f1f1f1;
padding: 6rpx 22rpx;
user-select: none;
}
.text:active {
background-color: rgba(100, 100, 100, 0.5);
}
}
}
.change-img {
position: fixed;
width: 300rpx;
bottom: 5%;
left: 50%;
margin-left: -150rpx;
text-align: center;
z-index: 30;
color: #fff;
padding: 30rpx;
box-sizing: border-box;
background-color: #0041c4;
border-radius: 10rpx;
}
</style>

View File

@@ -0,0 +1,135 @@
<template>
<view v-show="isShow">
<view class="shade" @tap="hide"></view>
<view class="pop">
<view class="flex_col" style="margin-bottom: 20rpx;">
<view class="preview" :style="{'backgroundColor':pickerColor}"></view>
<view class="value">
<text v-if="pickerColor">颜色值{{pickerColor}}</text>
</view>
<view class="ok" @tap="setColor">确定</view>
</view>
<view class="list flex_col" v-for="(item,index) in colorArr" :key="index">
<view v-for="(v,i) in item" :key="i"
:style="{'backgroundColor':v}"
:data-color="v"
:data-index="index"
:data-i="i"
:class="{'active':(index==pickerArr[0] && i==pickerArr[1])}"
@tap="picker"></view>
</view>
<view :style="{'height':(bottom+'px')}"></view>
</view>
</view>
</template>
<script>
export default {
name:'picker-color',
props:{
isShow: {
type: Boolean,
default: false,
},
bottom:{
type: Number,
default: 0,
}
},
data() {
return {
colorArr:[
['#000000','#111111','#222222','#333333','#444444','#666666','#999999','#CCCCCC','#EEEEEE','#FFFFFF'],
['#ff0000','#ff0033','#ff3399','#ff33cc','#cc00ff','#9900ff','#cc00cc','#cc0099','#cc3399','#cc0066'],
['#cc3300','#cc6600','#ff9933','#ff9966','#ff9999','#ff99cc','#ff99ff','#cc66ff','#9966ff','#cc33ff'],
['#663300','#996600','#996633','#cc9900','#a58800','#cccc00','#ffff66','#ffff99','#ffffcc','#ffcccc'],
['#336600','#669900','#009900','#009933','#00cc00','#66ff66','#339933','#339966','#009999','#33cccc'],
['#003366','#336699','#3366cc','#0099ff','#000099','#0000cc','#660066','#993366','#993333','#800000']
],
pickerColor:'',
pickerArr:[-1,-1]
};
},
methods: {
picker(e) {
let data=e.currentTarget.dataset;
this.pickerColor=data.color;
this.pickerArr=[data.index,data.i];
},
hide(){
this.$emit("callback",'');
},
setColor(){
this.$emit("callback",this.pickerColor);
}
},
}
</script>
<style scoped>
.shade{
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: rgba(0,0,0,0.5);
z-index: 99;
}
.pop{
position: fixed;
right: 0;
bottom: 0;
left: 0;
background-color: #fff;
z-index: 100;
padding: 20upx 20upx 10upx 20upx;
font-size: 32upx;
}
.flex_col{
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: flex-start;
align-items: center;
align-content: center;
}
.list{
justify-content: space-between;
}
.list>view{
width: 60upx;
height: 60upx;
margin-bottom: 10upx;
box-sizing: border-box;
border-radius: 3px;
box-shadow: 0 0 2px #ccc;
}
.list .active{
box-shadow: 0 0 2px #09f;
transform:scale(1.05,1.05);
}
.preview{
width: 180upx;
height: 60upx;
}
.value{
margin: 0 40upx;
flex-grow: 1;
}
.ok{
width: 160upx;
height: 60upx;
line-height: 60upx;
text-align: center;
background-color: #ff9933;
color: #fff;
border-radius: 4px;
letter-spacing: 3px;
font-size: 32upx;
}
.ok:active{
background-color: rgb(255, 107, 34);
}
</style>

View File

@@ -0,0 +1,977 @@
<template>
<view class="yhdsl" v-show="isShow">
<view>
<view class="cropper-content">
<view v-if="isShowImg" class="uni-corpper"
:style="'width:' + cropperInitW + 'px;height:' + cropperInitH + 'px;background:#000'">
<view class="uni-corpper-content" :style="
'width:' +
cropperW +
'px;height:' +
cropperH +
'px;left:' +
cropperL +
'px;top:' +
cropperT +
'px'
">
<image :src="imageSrc" :style="'width:' + cropperW + 'px;height:' + cropperH + 'px'"></image>
<view class="uni-corpper-crop-box" @touchstart.stop="contentStartMove"
@touchmove.stop="contentMoveing" @touchend.stop="contentTouchEnd" :style="
'left:' + cutL + 'px;top:' + cutT + 'px;right:' + cutR + 'px;bottom:' + cutB + 'px'
">
<view class="uni-cropper-view-box">
<view class="uni-cropper-dashed-h"></view>
<view class="uni-cropper-dashed-v"></view>
<!-- 截图区域顶部 -->
<view class="uni-cropper-line-t" data-drag="top" @touchstart.stop="dragStart"
@touchmove.stop="dragMove"></view>
<!-- 截图区域右侧 -->
<view class="uni-cropper-line-r" data-drag="right" @touchstart.stop="dragStart"
@touchmove.stop="dragMove"></view>
<!-- 截图区域底部 -->
<view class="uni-cropper-line-b" data-drag="bottom" @touchstart.stop="dragStart"
@touchmove.stop="dragMove"></view>
<!-- 截图区域左侧 -->
<view class="uni-cropper-line-l" data-drag="left" @touchstart.stop="dragStart"
@touchmove.stop="dragMove"></view>
<!-- 右下角截图滑块 -->
<view class="uni-cropper-point point-t" data-drag="top" @touchstart.stop="dragStart"
@touchmove.stop="dragMove"></view>
<view class="uni-cropper-point point-tr" data-drag="topTight"></view>
<view class="uni-cropper-point point-r" data-drag="right" @touchstart.stop="dragStart"
@touchmove.stop="dragMove"></view>
<view class="uni-cropper-point point-rb" data-drag="rightBottom"
@touchstart.stop="dragStart" @touchmove.stop="dragMove"></view>
<view class="uni-cropper-point point-b" data-drag="bottom" @touchstart.stop="dragStart"
@touchmove.stop="dragMove" @touchend.stop="dragEnd"></view>
<view class="uni-cropper-point point-bl" data-drag="bottomLeft"></view>
<view class="uni-cropper-point point-l" data-drag="left" @touchstart.stop="dragStart"
@touchmove.stop="dragMove"></view>
<view class="uni-cropper-point point-lt" data-drag="leftTop"></view>
</view>
</view>
</view>
</view>
</view>
<view class="cropper-config">
<view class="button-box">
<button type="warn" @click="chooseImage">重选</button>
<button type="warn" @click="previewImg">预览</button>
<button type="warn" @click="finish">完成</button>
</view>
</view>
<canvas canvas-id="myCanvas" :style="
'position:absolute;border: 2px solid red; width:' +
imageW +
'px;height:' +
imageH +
'px;top:-9999px;left:-9999px;'
"></canvas>
</view>
</view>
</template>
<script>
let sysInfo = uni.getSystemInfoSync();
let SCREEN_WIDTH = sysInfo.screenWidth;
let SCREEN_HEIGHT = sysInfo.windowHeight + 40;
let PAGE_X, // 手按下的x位置
PAGE_Y, // 手按下y的位置
PR = sysInfo.pixelRatio, // dpi
T_PAGE_X, // 手移动的时候x的位置
T_PAGE_Y, // 手移动的时候Y的位置
CUT_L, // 初始化拖拽元素的left值
CUT_T, // 初始化拖拽元素的top值
CUT_R, // 初始化拖拽元素的
CUT_B, // 初始化拖拽元素的
CUT_W, // 初始化拖拽元素的宽度
CUT_H, // 初始化拖拽元素的高度
IMG_RATIO, // 图片比例
IMG_REAL_W, // 图片实际的宽度
IMG_REAL_H, // 图片实际的高度
DRAFG_MOVE_RATIO = 1, //移动时候的比例,
INIT_DRAG_POSITION = 100, // 初始化屏幕宽度和裁剪区域的宽度之差,用于设置初始化裁剪的宽度
DRAW_IMAGE_W = sysInfo.screenWidth; // 设置生成的图片宽度
export default {
/**
* 页面的初始数据
*/
data() {
return {
imageSrc: "",
isShow: false,
isShowImg: false,
// 初始化的宽高
cropperInitW: SCREEN_WIDTH,
cropperInitH: SCREEN_HEIGHT,
// 动态的宽高
cropperW: SCREEN_WIDTH,
cropperH: SCREEN_HEIGHT - 100,
// 动态的left top值
cropperL: 0,
cropperT: 0,
transL: 0,
transT: 0,
// 图片缩放值
scaleP: 0,
imageW: 0,
imageH: 0,
// 裁剪框 宽高
cutL: 0,
cutT: 0,
cutB: SCREEN_WIDTH,
cutR: "100%",
qualityWidth: DRAW_IMAGE_W,
innerAspectRadio: DRAFG_MOVE_RATIO,
};
},
props: {
/* 截图质量,压缩比 */
quality: {
type: Number | String,
default: 1,
},
src: String,
fileType: {
type: String,
default: "png",
validator: function(t) {
// 这个值必须匹配下列字符串中的一个
return t === "png" || t === "jpg";
},
},
/* 截取类型自由截取free;固定比例截取正方形fixed */
mode: {
type: String,
default: "free",
validator: function(t) {
// 这个值必须匹配下列字符串中的一个
return t === "free" || t === "fixed" || t === "scale";
},
},
scale: {
type: Number,
default: 1,
},
},
created() {
if (this.src) {
this.imageSrc = this.src;
this.loadImage();
this.isShow = true;
this.isShowImg = true;
}
},
methods: {
setData: function(obj) {
let that = this;
Object.keys(obj).forEach(function(key) {
that.$set(that.$data, key, obj[key]);
});
},
/* 选择图片 */
chooseImage: function() {
var _this = this;
uni.chooseImage({
count: 1,
success: function(res) {
if(res.tempFiles[0].size / 1024 < 1024) {
_this.setData({
imageSrc: res.tempFilePaths[0],
});
_this.loadImage();
_this.isShow = true;
} else {
uni.showToast({
title: '图片大小不能超过1024KB当前大小' + (res.tempFiles[0].size / 1024).toFixed(2) + 'KB',
icon: 'none'
})
}
},
});
},
/* 将图片加载到画布 */
loadImage: function() {
var _this = this;
uni.showLoading({
title: "图片加载中...",
});
/* 获取图片信息 */
uni.getImageInfo({
src: _this.imageSrc,
success: function success(res) {
let imgH = res.height;
let imgW = res.width;
// let IMG_SCR_H_R = SCREEN_HEIGHT / imgH;
// let IMG_SCR_W_R = SCREEN_WIDTH / imgW;
/* 图片的宽高比 */
IMG_RATIO = imgW / imgH;
/**
* 如果图片更高一些,为确保图片能够完整在视窗内显示需如下处理
* 1. 缩放图片的高为 视窗高度减去底部菜单按钮高度120
* 2. 根据图片缩放后的高度,根据图片宽高比计算图片的宽度
* 3. 如果步骤2计算的图片宽度大于屏幕宽度则需要再次调整图片宽度为视窗宽度-margin(10)
* 4. 根据步骤3的宽度结合图片宽高比重新计算图片的高度
*/
if (IMG_RATIO < 1 && (SCREEN_HEIGHT - 120) * IMG_RATIO < SCREEN_WIDTH - 10) {
IMG_REAL_W = (SCREEN_HEIGHT - 120) * IMG_RATIO;
IMG_REAL_H = SCREEN_HEIGHT - 120;
} else {
IMG_REAL_W = SCREEN_WIDTH - 10;
IMG_REAL_H = IMG_REAL_W / IMG_RATIO;
}
/* 初始化裁剪区域的位置和形状 */
let [cutT, cutB, cutL, cutR] = _this.initCutArea(IMG_RATIO, IMG_REAL_H,
IMG_REAL_W);
_this.setData({
/* 裁剪区域的宽高同图片尺寸 */
cropperW: IMG_REAL_W,
cropperH: IMG_REAL_H,
/* 上下左右各留一定的margin已便更好的拖动裁剪区域 */
cropperL: Math.ceil((SCREEN_WIDTH - IMG_REAL_W) / 2),
/* 留出底部操作按钮位置 70 */
cropperT: Math.ceil((SCREEN_HEIGHT - IMG_REAL_H - 90) / 2),
cutL: cutL,
cutT: cutT,
cutR: cutR,
cutB: cutB,
// 图片缩放值
imageW: IMG_REAL_W,
imageH: IMG_REAL_H,
scaleP: IMG_REAL_W / SCREEN_WIDTH,
qualityWidth: DRAW_IMAGE_W,
innerAspectRadio: IMG_RATIO,
});
_this.setData({
isShowImg: true,
});
uni.hideLoading();
},
});
},
initCutArea(IMG_RATIO, IMG_REAL_H, IMG_REAL_W) {
let _this = this;
/* 自由裁剪裁剪区域默认覆盖整个图片 */
let cutT = 0;
let cutB = 0;
let cutL = 0;
let cutR = 0;
/* 正方形裁剪,初始化裁剪区域为正方形并居中 */
if (_this.mode == "fixed") {
if (IMG_RATIO < 1) {
/* 图片比较高 */
cutT = (IMG_REAL_H - IMG_REAL_W) / 2;
cutB = (IMG_REAL_H - IMG_REAL_W) / 2;
} else {
/* 图片比较宽 */
cutL = (IMG_REAL_W - IMG_REAL_H) / 2;
cutR = (IMG_REAL_W - IMG_REAL_H) / 2;
}
}
/* 固定比例裁剪,初始化裁剪区域比例和设定值相同 */
if (_this.mode == "scale") {
let scale = +_this.scale;
if (IMG_RATIO < 1) {
/* 图片比较高 */
if (IMG_REAL_W / scale > IMG_REAL_H) {
cutT = cutB = 0;
cutL = cutR = (IMG_REAL_W - IMG_REAL_H * scale) / 2;
} else {
cutR = cutL = 0;
cutT = cutB = (IMG_REAL_H - IMG_REAL_W / scale) / 2;
}
} else {
/* 图片比较宽 */
if (IMG_REAL_H * scale > IMG_REAL_W) {
cutL = cutR = 0;
cutB = cutT = (IMG_REAL_H - IMG_REAL_W / scale) / 2;
} else {
cutT = cutB = 0;
cutL = cutR = (IMG_REAL_W - IMG_REAL_H * scale) / 2;
}
}
}
return [cutT, cutB, cutL, cutR];
},
// 拖动时候触发的touchStart事件
contentStartMove(e) {
PAGE_X = e.touches[0].pageX;
PAGE_Y = e.touches[0].pageY;
},
// 拖动时候触发的touchMove事件
contentMoveing(e) {
var _this = this;
var dragLengthX = (PAGE_X - e.touches[0].pageX) * DRAFG_MOVE_RATIO;
var dragLengthY = (PAGE_Y - e.touches[0].pageY) * DRAFG_MOVE_RATIO;
// 左移
if (dragLengthX > 0) {
if (this.cutL - dragLengthX < 0) dragLengthX = this.cutL;
} else {
if (this.cutR + dragLengthX < 0) dragLengthX = -this.cutR;
}
if (dragLengthY > 0) {
if (this.cutT - dragLengthY < 0) dragLengthY = this.cutT;
} else {
if (this.cutB + dragLengthY < 0) dragLengthY = -this.cutB;
}
this.setData({
cutL: this.cutL - dragLengthX,
cutT: this.cutT - dragLengthY,
cutR: this.cutR + dragLengthX,
cutB: this.cutB + dragLengthY,
});
PAGE_X = e.touches[0].pageX;
PAGE_Y = e.touches[0].pageY;
},
contentTouchEnd() {},
// 获取图片尺寸信息
previewImg() {
try {
var _this = this;
uni.showLoading({
title: "图片生成中...",
});
// 将图片写入画布
const ctx = uni.createCanvasContext("myCanvas", _this);
ctx.drawImage(_this.imageSrc, 0, 0, IMG_REAL_W, IMG_REAL_H);
ctx.draw(true, () => {
// 获取画布要裁剪的位置和宽度 均为百分比 * 画布中图片的宽度 保证了在微信小程序中裁剪的图片模糊 位置不对的问题 canvasT = (_this.cutT / _this.cropperH) * (_this.imageH / pixelRatio)
var canvasW = ((_this.cropperW - _this.cutL - _this.cutR) / _this.cropperW) * IMG_REAL_W;
var canvasH = ((_this.cropperH - _this.cutT - _this.cutB) / _this.cropperH) * IMG_REAL_H;
var canvasL = (_this.cutL / _this.cropperW) * IMG_REAL_W;
var canvasT = (_this.cutT / _this.cropperH) * IMG_REAL_H;
uni.canvasToTempFilePath({
x: canvasL,
y: canvasT,
width: canvasW,
height: canvasH,
// destWidth: canvasW,
// destHeight: canvasH,
quality: +this.quality,
fileType: this.fileType,
canvasId: "myCanvas",
success: function(res) {
uni.hideLoading();
// 成功获得地址的地方
uni.previewImage({
current: "", // 当前显示图片的http链接
urls: [res.tempFilePath], // 需要预览的图片http链接列表
});
},
fail: function(err) {
uni.hideLoading();
uni.showToast({
title: "图片截取失败!",
icon: "none",
});
},
},
_this
);
});
} catch (e) {}
},
/* 完成裁剪,输出裁剪后的图片路径 */
finish: function() {
var _this = this;
uni.showLoading({
title: "图片生成中...",
});
// 将图片写入画布
const ctx = uni.createCanvasContext("myCanvas", _this);
ctx.drawImage(_this.imageSrc, 0, 0, IMG_REAL_W, IMG_REAL_H);
ctx.draw(true, () => {
// 获取画布要裁剪的位置和宽度 均为百分比 * 画布中图片的宽度 保证了在微信小程序中裁剪的图片模糊 位置不对的问题 canvasT = (_this.cutT / _this.cropperH) * (_this.imageH / pixelRatio)
var canvasW = ((_this.cropperW - _this.cutL - _this.cutR) / _this.cropperW) * IMG_REAL_W;
var canvasH = ((_this.cropperH - _this.cutT - _this.cutB) / _this.cropperH) * IMG_REAL_H;
var canvasL = (_this.cutL / _this.cropperW) * IMG_REAL_W;
var canvasT = (_this.cutT / _this.cropperH) * IMG_REAL_H;
console.log(canvasW,canvasH,'canvasHcanvasHcanvasHcanvasH')
uni.canvasToTempFilePath({
x: canvasL,
y: canvasT,
// width: canvasW,
// height: canvasH,
width: 800,
height: 960,
destWidth: 800,
destHeight: 960,
quality: +this.quality,
// fileType: this.fileType,
fileType: 'jpg',
canvasId: "myCanvas",
success: function(res) {
uni.hideLoading();
console.log(res,'resresres')
// 成功获得地址的地方
_this.$emit("uploadImg", res.tempFilePath);
_this.isShow = false;
},
fail: function(err) {
uni.hideLoading();
uni.showToast({
title: "图片截取失败!",
icon: "none",
});
},
},
_this
);
});
},
// 设置大小的时候触发的touchStart事件
dragStart(e) {
T_PAGE_X = e.touches[0].pageX;
T_PAGE_Y = e.touches[0].pageY;
CUT_L = this.cutL;
CUT_R = this.cutR;
CUT_B = this.cutB;
CUT_T = this.cutT;
},
// 设置大小的时候触发的touchMove事件
dragMove(e) {
// this.mode == "fixed" ? this.fixedScaleDrag(e) : this.freeDrag(e);
this[this.mode + "Drag"](e);
},
/* 固定比例(正方形)截取 ,只有右下角裁剪滑动拖动有效*/
fixedDrag(e) {
var _this = this;
var dragType = e.target.dataset.drag;
switch (dragType) {
case "rightBottom":
var dragLengthX = (T_PAGE_X - e.touches[0].pageX) * DRAFG_MOVE_RATIO;
// var dragLengthY = (T_PAGE_Y - e.touches[0].pageY) * DRAFG_MOVE_RATIO;
// if (CUT_B + dragLengthY < 0) dragLengthY = -CUT_B;
if (CUT_R + dragLengthX < 0) dragLengthX = -CUT_R;
/* 右侧和底部同比变化 */
let cutB = CUT_B + dragLengthX;
let cutR = CUT_R + dragLengthX;
/* 越界判断 */
if (_this.cutB == 0 && cutB < 0) return;
if (_this.cutR == 0 && cutR < 0) return;
(_this.cutB > 0 || CUT_B == 0) &&
this.setData({
cutB: cutB < 0 ? 0 : cutB,
cutR: cutR,
});
break;
default:
break;
}
},
/* 等比例截图,只能通过右下角的滑块改变截图区域 */
scaleDrag(e) {
var _this = this;
var dragType = e.target.dataset.drag;
switch (dragType) {
case "rightBottom":
var dragLengthX = (T_PAGE_X - e.touches[0].pageX) * DRAFG_MOVE_RATIO;
// var dragLengthY = (T_PAGE_Y - e.touches[0].pageY) * DRAFG_MOVE_RATIO;
// if (CUT_B + dragLengthY < 0) dragLengthY = -CUT_B;
if (CUT_R + dragLengthX < 0) dragLengthX = -CUT_R;
/* 右侧和底部同比变化 */
let cutB = CUT_B + dragLengthX / _this.scale;
let cutR = CUT_R + dragLengthX;
/* 越界判断 */
if (_this.cutB == 0 && cutB < 0) return;
if (_this.cutR == 0 && cutR < 0) return;
(_this.cutB > 0 || CUT_B == 0) &&
this.setData({
cutB: cutB < 0 ? 0 : cutB,
cutR: cutR,
});
break;
default:
break;
}
},
/* 自由截取,整个裁剪边框均能拖动 */
freeDrag(e) {
var _this = this;
var dragType = e.target.dataset.drag;
switch (dragType) {
case "right":
var dragLength = (T_PAGE_X - e.touches[0].pageX) * DRAFG_MOVE_RATIO;
if (CUT_R + dragLength < 0) dragLength = -CUT_R;
this.setData({
cutR: CUT_R + dragLength,
});
break;
case "left":
var dragLength = (T_PAGE_X - e.touches[0].pageX) * DRAFG_MOVE_RATIO;
if (CUT_L - dragLength < 0) dragLength = CUT_L;
if (CUT_L - dragLength > this.cropperW - this.cutR)
dragLength = CUT_L - (this.cropperW - this.cutR);
this.setData({
cutL: CUT_L - dragLength,
});
break;
case "top":
var dragLength = (T_PAGE_Y - e.touches[0].pageY) * DRAFG_MOVE_RATIO;
if (CUT_T - dragLength < 0) dragLength = CUT_T;
if (CUT_T - dragLength > this.cropperH - this.cutB)
dragLength = CUT_T - (this.cropperH - this.cutB);
this.setData({
cutT: CUT_T - dragLength,
});
break;
case "bottom":
var dragLength = (T_PAGE_Y - e.touches[0].pageY) * DRAFG_MOVE_RATIO;
if (CUT_B + dragLength < 0) dragLength = -CUT_B;
this.setData({
cutB: CUT_B + dragLength,
});
break;
case "rightBottom":
var dragLengthX = (T_PAGE_X - e.touches[0].pageX) * DRAFG_MOVE_RATIO;
var dragLengthY = (T_PAGE_Y - e.touches[0].pageY) * DRAFG_MOVE_RATIO;
if (CUT_B + dragLengthY < 0) dragLengthY = -CUT_B;
if (CUT_R + dragLengthX < 0) dragLengthX = -CUT_R;
let cutB = CUT_B + dragLengthY;
let cutR = CUT_R + dragLengthX;
this.setData({
cutB: cutB,
cutR: cutR,
});
break;
default:
break;
}
},
},
};
</script>
<style>
/* pages/uni-cropper/index.wxss */
.yhdsl {
background-color: #fff;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 50px;
display: block;
align-items: center;
flex-direction: column;
z-index: 998;
height: 100vh;
}
.cropper-config {
position: fixed;
z-index: 999;
bottom: 10px;
left: 0;
right: 0;
width: 90%;
margin: 0 auto;
/* padding: 20upx 40upx; */
}
.button-box {
position: absolute;
bottom: 0;
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
}
.button-box button {
width: 25%;
line-height: 35px;
height: 35px;
background-color: #509cff;
}
.cropper-content {
width: 100%;
min-height: 750upx;
}
.uni-corpper {
position: relative;
overflow: hidden;
box-sizing: border-box;
margin: 0 auto;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-tap-highlight-color: transparent;
-webkit-touch-callout: none;
}
.uni-corpper-content {
position: relative;
}
.uni-corpper-content image {
display: block;
width: 100%;
min-width: 0 !important;
max-width: none !important;
/* height: 100%; */
min-height: 0 !important;
/* max-height: none !important; */
max-height: calc(100vh - 100upx);
margin: 0 auto;
image-orientation: 0deg !important;
}
/* 移动图片效果 */
.uni-cropper-drag-box {
position: absolute;
z-index: 1;
top: 0;
right: 0;
bottom: 0;
left: 0;
cursor: move;
background: rgba(0, 0, 0, 0.479);
}
/* 内部的信息 */
.uni-corpper-crop-box {
position: absolute;
z-index: 2;
max-height: calc(100vh - 100upx);
background: rgba(56, 50, 50, 0.479);
}
.uni-corpper-crop-box .uni-cropper-view-box {
position: relative;
display: block;
overflow: visible;
width: 100%;
height: 100%;
max-height: calc(100vh - 100upx);
outline: 5upx solid rgb(43, 89, 255);
outline-color: rgba(255, 255 255, 1);
}
/* 横向虚线 */
.uni-cropper-dashed-h {
position: absolute;
top: 33.33333333%;
left: 0;
width: 100%;
height: 33.33333333%;
border-top: 1upx dashed rgba(255, 255, 255, 0.5);
border-bottom: 1upx dashed rgba(255, 255, 255, 0.5);
}
/* 纵向虚线 */
.uni-cropper-dashed-v {
position: absolute;
top: 0;
left: 33.33333333%;
width: 33.33333333%;
height: 100%;
border-right: 1upx dashed rgba(255, 255, 255, 0.5);
border-left: 1upx dashed rgba(255, 255, 255, 0.5);
}
/* 四个方向的线 为了之后的拖动事件*/
.uni-cropper-line-t {
position: absolute;
top: 0;
left: 0;
display: block;
width: 100%;
height: 3upx;
cursor: n-resize;
opacity: 0.1;
background-color: white;
}
.uni-cropper-line-t::before {
position: absolute;
z-index: 11;
top: 50%;
right: 0upx;
bottom: 0;
width: 100%;
height: 41upx;
content: "";
-webkit-transform: translate3d(0, -50%, 0);
transform: translate3d(0, -50%, 0);
background: transparent;
}
.uni-cropper-line-r {
position: absolute;
top: 0;
right: 0upx;
display: block;
width: 3upx;
height: 100%;
cursor: e-resize;
opacity: 0.1;
background-color: white;
}
.uni-cropper-line-r::before {
position: absolute;
z-index: 11;
top: 0;
bottom: 0;
left: 50%;
width: 41upx;
height: 100%;
content: "";
-webkit-transform: translate3d(-50%, 0, 0);
transform: translate3d(-50%, 0, 0);
background: transparent;
}
.uni-cropper-line-b {
position: absolute;
bottom: 0;
left: 0;
display: block;
width: 100%;
height: 3upx;
cursor: s-resize;
opacity: 0.1;
background-color: white;
}
.uni-cropper-line-b::before {
position: absolute;
z-index: 11;
top: 50%;
right: 0upx;
bottom: 0;
width: 100%;
height: 41upx;
content: "";
-webkit-transform: translate3d(0, -50%, 0);
transform: translate3d(0, -50%, 0);
background: transparent;
}
.uni-cropper-line-l {
position: absolute;
top: 0;
left: 0;
display: block;
width: 3upx;
height: 100%;
cursor: w-resize;
opacity: 0.1;
background-color: white;
}
.uni-cropper-line-l::before {
position: absolute;
z-index: 11;
top: 0;
bottom: 0;
left: 50%;
width: 41upx;
height: 100%;
content: "";
-webkit-transform: translate3d(-50%, 0, 0);
transform: translate3d(-50%, 0, 0);
background: transparent;
}
.uni-cropper-point {
position: absolute;
z-index: 3;
width: 5upx;
height: 5upx;
opacity: 0.75;
background-color: rgb(145, 132, 132);
}
.point-t {
top: -3upx;
left: 50%;
margin-left: -3upx;
cursor: n-resize;
}
.point-tr {
top: -3upx;
left: 100%;
margin-left: -3upx;
cursor: n-resize;
}
.point-r {
top: 50%;
left: 100%;
margin-top: -3upx;
margin-left: -3upx;
cursor: n-resize;
}
.point-rb {
position: absolute;
z-index: 1112;
top: 100%;
left: 100%;
width: 30upx;
height: 30upx;
border-radius: 50%;
cursor: n-resize;
-webkit-transform: translate3d(-50%, -50%, 0);
transform: translate3d(-50%, -50%, 0);
opacity: 1;
background-color: rgb(231, 222, 222);
background-color: #509cff;
}
.point-b {
top: 100%;
left: 50%;
margin-top: -3upx;
margin-left: -3upx;
cursor: n-resize;
}
.point-bl {
top: 100%;
left: 0;
margin-top: -3upx;
margin-left: -3upx;
cursor: n-resize;
}
.point-l {
top: 50%;
left: 0;
margin-top: -3upx;
margin-left: -3upx;
cursor: n-resize;
}
.point-lt {
top: 0;
left: 0;
margin-top: -3upx;
margin-left: -3upx;
cursor: n-resize;
}
/* 裁剪框预览内容 */
.uni-cropper-viewer {
position: relative;
overflow: hidden;
width: 100%;
height: 100%;
}
.uni-cropper-viewer image {
position: absolute;
z-index: 2;
}
</style>

View File

@@ -0,0 +1,26 @@
const appConfig = {
// 项目名称
appName: '银收客',
// token取值key
tokenKey: 'iToken',
// 环境变量相关
env: {},
// 环境变量常量
ENV_ENUM: {
DEVELOPMENT: 'development', // 本地调试地址
TEST: 'test', // 测试地址
DEMO: 'demo', // 演示环境
PRODUCTION: 'production' // 生产环境
},
storeEnvEnumKey: 'currentEnvEnum', // 本地存储的envkey的值
encryptKey: '1234567890123456' // http数据加解密的key
}
export default appConfig;

45
jeepay-ui-uapp-merchant/env/config.js vendored Normal file
View File

@@ -0,0 +1,45 @@
/**
* ENV 的封装
* 由于uniapp 无法设置.env等打包属性实现线上线下多版本配置属性的无缝切换。
* process.env.NODE_ENV 将在打包环境通过编译器进行替换, 线上打包后无法获取到 process对象。 所以将配置属性统一封装到appConfig中。
*
* 环境地址环境枚举详见: appConfig.js
*
* @author terrfly
* @site https://www.jeequan.com
* @date 2021/12/16 17:57
*/
import appConfig from '@/config/appConfig.js';
// uni-app 暂时不支持动态import导包app报错, 需要将所有的env全部导入 = = 、
import development from './env.development.js';
import test from './env.test.js';
import demo from './env.demo.js';
import production from './env.production.js';
const allEnvMap = {
development: development,
test: test,
demo: demo,
production: production
}
// 获取当前环境变量
const processEnv = process.env.NODE_ENV
// 改变env环境
function changeEnv(envMode){
appConfig.env = allEnvMap[envMode || processEnv]
// // 动态导包的方式设置全局env配置项目 : 当参数不存在, 那么获取node环境
// import(`./env.${envMode || processEnv}.js`).then(module => {
// appConfig.env = module.default
// }).catch(() => {
// appConfig.env = production //当出现错误, 比如本地没有复制dev文件时 默认使用生产环境
// })
}
export default { changeEnv: changeEnv }

View File

@@ -0,0 +1,6 @@
export default {
'JEEPAY_BASE_URL': 'https://b.rscygroup.com', // 请求URL生产环境
'JEEPAY_BASE_URL_H5': 'https://b.rscygroup.com'
// 'JEEPAY_BASE_URL': 'http://192.168.1.8:9218' // 请求URL测试环境
// 'JEEPAY_BASE_URL': 'https://b.qilinshuzi.com' //支付网关URL
}

View File

@@ -0,0 +1,5 @@
export default {
// 'JEEPAY_BASE_URL': 'https://b.shouyinbei.com' // 请求URL测试地址
'JEEPAY_BASE_URL_H5': 'https://b.rscygroup.com',
'JEEPAY_BASE_URL': 'https://b.rscygroup.com' //支付网关URL
}

View File

@@ -0,0 +1,8 @@
{
"extEnable": true,
"extAppid":"wx9b4f8ff05754599f",
"directCommit": false,
"ext": {
"name": "wechat"
}
}

View File

@@ -0,0 +1,624 @@
/**
* api接口管理 全部以$开头
* @author terrfly
* @site https://www.jeequan.com
* @date 2021/12/17 09:57
*/
import http from './http.js'
import appConfig from '@/config/appConfig.js'
import {
Base64
} from 'js-base64'
import infoBox from '@/commons/utils/infoBox.js'
export const req = {
list: (uri, params) => {
return http.req(uri, params, 'GET', false)
},
add: (uri, data) => {
return http.req(uri, data, 'POST', false)
},
getById: (uri, bizId) => {
return http.req(`${uri}/${bizId}`, {}, 'GET', false)
},
updateById: (uri, bizId, data) => {
return http.req(`${uri}/${bizId}`, data, 'PUT', false)
},
delById: (uri, bizId) => {
return http.req(`${uri}/${bizId}`, {}, 'DELETE', false)
},
}
export const reqLoad = {
list: (uri, params) => {
return http.req(uri, params, 'GET')
},
add: (uri, data) => {
return http.req(uri, data, 'POST')
},
getById: (uri, bizId) => {
return http.req(`${uri}/${bizId}`, {}, 'GET')
},
updateById: (uri, bizId, data) => {
return http.req(`${uri}/${bizId}`, data, 'PUT')
},
delById: (uri, bizId) => {
return http.req(`${uri}/${bizId}`, {}, 'DELETE')
},
// 通用 新增 or 更新
addOrUpdate: (bizId, url, data, showMessage = true) => {
//包含bizId 请求的是修改接口
if (bizId) {
return reqLoad.updateById(url, bizId, data).then((res) => {
if (showMessage) {
return infoBox.showSuccessToast('更新成功').then(() => res)
}
return Promise.resolve(res)
})
} else {
return reqLoad.add(url, data).then((res) => {
if (showMessage) {
return infoBox.showSuccessToast('新增成功').then(() => res)
}
return Promise.resolve(res)
})
}
}
}
/** 上传文件 url **/
export const API_URL_SINGLE_FILE_UPLOAD = '/api/ossFiles/singleFile'
/** 公告 **/
export const API_URL_SYS_ARTICLES = '/api/sysArticles'
/** 支付订单列表 */
export const API_URL_PAY_ORDER_LIST = '/api/payOrder'
/** 退款订单列表 */
export const API_URL_REFUND_LIST = '/api/refundOrder'
/** 商户应用 */
export const API_URL_MCH_APP_LIST = '/api/mchApps'
/** 门店列表 */
export const API_URL_MCH_STORE_LIST = '/api/mchStore'
/** 通知接收人列表 */
export const API_URL_WXMP_USER_LIST = '/api/wxmp'
/** 进件管理 */
export const API_URL_MCH_APPLYMENT_LIST = '/api/mchApplyments'
/** 员工管理 */
export const API_URL_SYS_USER_LIST = '/api/sysUsers'
/** 码牌管理 */
export const API_URL_SYS_CODE_LIST = '/api/mchQrCodes'
/** 通用设备管理 deviceType 1-喇叭 2-打印机 3-扫码POS 4-智能POS 6-刷脸设备*/
export const API_URL_SYS_DEVICE_LIST = '/api/store/device'
/** 辅助终端管理*/
export const API_URL_SYS_TERMINALS = '/api/mchTerminals'
/** 用户绑定门店列表 */
export const API_URL_USER_BIND_STORE_LIST = '/api/mchStore/userStoreRelas'
/** 商户支付通道列表 */
export const API_URL_PAY_PASSAGE_LIST = '/api/mch/payPassages'
/** 广告列表 */
export const API_URI_PAY_AD_LIST = '/api/advert'
/** 如意门店操作 */
export const API_URI_DEV_RUYI = '/api/alipayShop'
/** 会员管理 */
export const API_URL_MEMBERS = '/api/member'
/** 会员充值记录 */
export const API_URL_MEMBER_RECHARGE_RECORDS = '/api/member/rechargeRecord'
/** 会员充值规则管理 */
export const API_URL_MEMBER_RECHARGE_RULES = '/api/member/rechargeRule'
/** 会员账户流水 */
export const API_URL_MEMBER_ACCOUNT_HISTORY = '/api/member/accountHistory'
//营销红包开始
//红包规则
export const API_URL_MCH_REF_PACKET_RULE_LIST = '/api/redPacketRule'
//新增红包规则
export const API_URL_MCH_REF_PACKET_RULE_ADD = '/api/redPacketRule'
//顾客统计
export const API_URL_MCH_REF_PACKET_RULE_COUNT = '/api/redPacketUser/count'
//顾客列表
export const API_URL_MCH_REF_PACKET_USER_LIST = '/api/redPacketUser'
//顾客红包
export const API_URL_MCH_REF_PACKET_INFO_LIST = '/api/redPacketInfo'
//红包查询规则详情
export const API_URL_MCH_REF_PACKET_RULE_QUERY = '/api/redPacketRule'
//红包修改规则
export const API_URL_MCH_REF_PACKET_RULE_EDIT = '/api/redPacketRule/edit'
//红包余额变动明细列表
export const API_URL_MCH_REF_PACKET_CHANGE = '/api/redPacketChange'
//渠道列表
export const API_URL_MCH_ISV_LIST = '/api/isvInfo/isvInfoList'
/* 密码登录 */
export function $loginByPwd(username, pwd, safetyCode) {
// 登录类型
let lt = ''
// #ifdef APP-PLUS
lt = Base64.encode('APP')
// #endif
// #ifdef H5 || MP-WEIXIN
lt = Base64.encode('LITE')
// #endif
let data = {
ia: Base64.encode(username),
ip: Base64.encode(pwd),
mc: safetyCode ? Base64.encode(safetyCode) : null,
lt: lt,
}
return http.req('/api/anon/auth/validate', data, 'POST')
}
/* 验证码登录 */
export function $phoneCodeLogin(phone, code) {
// 登录类型
let lt = ''
// #ifdef APP-PLUS
lt = Base64.encode('APP')
// #endif
// #ifdef H5 || MP-WEIXIN
lt = Base64.encode('LITE')
// #endif
let data = {
phone: Base64.encode(phone),
code: Base64.encode(code),
lt: lt,
}
return http.req('/api/anon/auth/phoneCode', data, 'POST')
}
// 找回密码
export function $retrieve({
phone,
code,
newPwd
}) {
let data = {
phone: Base64.encode(phone),
code: Base64.encode(code),
newPwd: newPwd ? Base64.encode(newPwd) : ''
}
return http.req('/api/anon/cipher/retrieve', data, 'POST')
}
/* 修改密码 */
export function $modifyPwd({
originalPwd,
confirmPwd
}) {
let data = {
originalPwd: Base64.encode(originalPwd),
confirmPwd: Base64.encode(confirmPwd)
}
return http.req("/api/current/modifyPwd", data, "PUT")
}
/* 注册新商户 */
export function $mchRegister({
mchName,
confirmPwd,
inviteCode,
phone,
code
}) {
let data = {
mchName: mchName, // 用户名称
confirmPwd: Base64.encode(confirmPwd), // 确认密码
inviteCode, // 邀请码
phone: Base64.encode(phone), // 手机号
code: Base64.encode(code), // 短信验证码
}
return http.req('/api/anon/register/mchRegister', data, 'POST')
}
/* 发送短信验证码 */
export function $sendSms(data, smsType) {
return http.req('/api/anon/sms/code', {
phone: data.phone,
vercode: data.vercode,
vercodeToken: data.vercodeToken,
smsType: smsType
}, 'POST')
}
/* 获取个人信息 */
export function $userInfo(cid1, cid2) {
return http.req('/api/current/user', {
cid1,
cid2
}, 'GET')
}
/* 获取公司信息 */
export function $getSiteInfos() {
return http.req('/api/anon/siteInfos', {}, 'GET')
}
// 隐私政策与服务协议
export function $getTreaty() {
return http.req('/api/anon/treaty', {}, 'GET')
}
/* 快捷收银 */
export function $appPay(storeId, amount, sellerRemark, wayCode, authCode) {
let data = {
storeId,
amount,
sellerRemark,
wayCode,
authCode
}
// 二维码, 返回结果为 图片地址
if (wayCode == 'QR_CASHIER') {
data.payDataType = 'codeImgUrl'
}
return http.req('/api/pay/app', data, 'POST')
}
/* 当前商户信息 */
export function $getMchInfo() {
return http.req('/api/mainChart', {}, 'GET')
}
/* 订单打印 */
export function $payOrderPrint(orderId) {
return http.req('/api/payOrder/print/' + orderId, {}, 'GET')
}
/* 订单退款 */
export function $payOrderRefund(orderId, refundAmount, refundReason, refundPassword) {
return http.req(
'/api/payOrder/refunds/' + orderId, {
refundAmount: refundAmount,
refundReason: refundReason,
refundPassword: Base64.encode(refundPassword),
refundModel: 1
},
'POST'
)
}
// // 首页统计信息
// export function $indexStatistics (queryDateRange) {
// return http.req('/api/mainChart/payDayCount', { queryDateRange }, 'GET')
// }
// 首页统计信息
export function $indexStatistics(queryDateRange) {
return http.req('/api/payOrder/count?queryDateRange', {
queryDateRange
}, 'GET')
}
// 统计报表
export function $getStatInfo(data) {
return http.req('/api/statistic/total', data, 'GET', false)
}
// 统计报表设备 门店 支付方式统计
export function $getStatistic(data) {
return http.req('/api/statistic', data, 'GET')
}
/* 经纬度转换为areacode */
export function $location2areacode(location) {
return http.req('/api/mchStore/location2areacode/', {
location: location
}, 'GET')
}
/* 获取上传form信息 */
export function $ossFilesForm(bizType, file) {
let postData = {
bizType: bizType,
sourceFileName: file.name || file.path, // 如果没有name 则使用pach替换
sourceFileSize: file.size,
}
return http.req('/api/ossFiles/form', postData, 'POST')
}
/* 系统设置 */
// 获取语音播报开关信息
export function $mchConfig(groupKey) {
return http.req('/api/mchConfig', {
groupKey: groupKey
}, 'GET')
}
// 修改语音播报开关信息
export function $orderConfig(configData, mchNo) {
console.log('mchNo', mchNo);
return http.req('/api/mchConfig/orderConfig', {
configData: configData,
mchNo: mchNo
}, 'PUT')
}
/* 修改个人信息 */
export function $modifyUser({
realname,
avatarUrl
}) {
return http.req("/api/current/user", {
realname,
avatarUrl
}, "PUT")
}
/* 获取密码规则 */
export function $getPasswordRules() {
return http.req("/api/anon/cipher/pwdRulesRegexp", "GET")
}
// 验证是否有密码
export function $isSipw() {
return http.req("/api/mchConfig/hasSipwValidate", "GET")
}
// 验证原支付密码是否正确
export function $isMchSipw(originalPwd) {
return http.req("/api/mchConfig/mchSipwValidate", {
originalPwd: Base64.encode(originalPwd)
}, "POST")
}
/* 更新支付密码 */
export function $updateMchSipw({
originalPwd,
confirmPwd
}) {
let data = {
originalPwd: Base64.encode(originalPwd),
confirmPwd: Base64.encode(confirmPwd)
}
return http.req("/api/mchConfig/mchSipw", data, "PUT")
}
/* 版本检测 */
export function $versionDetection({
versionNumber
}) {
return http.req("/api/anon/clientVersion/versionInfo", {
versionNumber: versionNumber
}, "GET")
}
/* 查询用户权限 */
export function $getUserEntRoles(sysUserId) {
return http.req("/api/sysUsers/userEntRoles/" + sysUserId, {}, "GET")
}
/* 更新用户权限 */
export function $updateUserEntRoles(sysUserId, ruleName, state) {
return http.req("/api/sysUsers/userEntRoles/" + sysUserId, {
ruleName,
state
}, "PUT")
}
/* 根据支付方式查询可配置的支付接口 */
export function $getAvailablePayInterface(appId, wayCode) {
return http.req(`/api/mch/payPassages/availablePayInterface/${appId}/${wayCode}`, "GET")
}
/* 支付方式配置支付接口 */
export function $wayCodeConfigIfCode(appId, wayCode, ifCode) {
let reqData = {
appId: appId,
wayCode: wayCode,
ifCode: ifCode,
state: 1
}
return http.req(`/api/mch/payPassages/mchPassage`, reqData, "POST")
}
/*码牌扫码 二维码解析*/
export function $parseQrCodeUrl(qrUrl) {
return http.req('/api/mchQrCodes/parseQrCodeUrl', {
qrUrl
}, 'GET')
}
/*码牌绑定*/
export function $bindEmptyQR(qrcInfo) {
return http.req('/api/mchQrCodes/bindEmptyQR', qrcInfo, 'POST')
}
/*解绑码牌*/
export function $unBindQrc(qrcId) {
return http.req('/api/mchQrCodes/unbind/' + qrcId, {}, 'POST')
}
/*码牌查询受支持的云喇叭设备*/
export function $getHornList(qrcId) {
return http.req('/api/mchQrCodes/bindDevice/' + qrcId, 'GET')
}
/* 更新默认 */
export function $updateTrmDefault(trmId, defaultFlag) {
return http.req('/api/mchTerminals/trmDefaults', {
trmId,
defaultFlag
}, 'PUT')
}
/* 设备解绑 */
export function $deviceUnbind(deviceId) {
return http.req('/api/store/device/unbind/' + deviceId, {}, 'POST')
}
/* 渠道报备列表 */
export function $terminalChannelBindList(trmId) {
return http.req('/api/mchTerminals/channelBindInfos/' + trmId, {}, 'GET')
}
/* 渠道报备 */
export function $terminalChannelBindSend(trmId, ifCode) {
return http.req('/api/mchTerminals/channelSendup/' + trmId, {
ifCode: ifCode,
state: 1
}, 'POST')
}
// 扫码登录
export function $scanCodeLogin(qrcodeNo, qrcodeStatus) {
return http.req("/api/current/qrcode/login", {
qrcodeNo,
qrcodeStatus
}, "POST")
}
// 云喇叭 播报测试
export function $speakTest(devId, amount) {
return http.req("/api/store/device/speak/" + devId, amount, "POST")
}
// 云打印 打印测试
export function $printTest(devId, amount) {
return http.req(`/api/store/device/print/${devId}`, {
amount
}, "POST")
}
/* 获取百度语音播报token */
export function $getBaiduToken() {
return http.req("/api/pushinfo/getBaiduToken", {}, "GET")
}
// 获取通知接收人二维码图片
export function $getWxMpInfo() {
return http.req("/api/wxmp/getWxmpInfo", {}, "GET")
}
/* 获取进件通道列表 */
export function $getAllAllowApplymentIfCodeList() {
return http.req("/api/payConfig/ifCodes", {
infoId: 'CURRENT',
configMode: 'mchApplyment'
}, "GET")
}
/* 查询店长可以绑定的门店 */
export function $getBindStoreList(params) {
return http.req('/api/mchStore/bindStoreList', params, "GET")
}
/* 提交unipush cid */
export function $pushInfoRegister(data) {
console.log('push cid', data);
return http.req("/api/pushinfo/register", data, "POST")
}
/* 获取广告接口*/
export function $adList(params) {
return http.req('/api/advert/appAdvert', params, 'GET')
}
/** 如意Lite 绑定解绑接口 */
export function $BindLite(data) {
return http.req('/api/alipayIot/bind/' + data.deviceId, data, "POST")
}
// 获取支付宝扫码授权
export function $aliPayQrCodeOrApply(account = '', type = 'queryResult') {
return http.req(`/api/alipaySpOperation/${type}`, {
alipayAccount: account
}, type == 'apply' ? 'POST' : 'GET')
}
// 获取支付宝授权信息
export function $aliAccountInfo() {
return http.req('/api/alipaySpOperation/authInfo', {}, 'GET')
}
// 查询蚂蚁店铺状态
export function $queryAliStore(storeId) {
return req.getById('/api/alipayShop/createResult', storeId)
}
// 获取上传图片 大小
export function $getUploadImgSize() {
return http.req('/api/defaultConfig', {}, "GET")
}
// 会员调账
export function $memberManual(params) {
return http.req('/api/member/manual/' + params.memberId, params, "POST")
}
//查询或修改会员配置信息
export function $findOrEditMemberConfig(data, mode = 'GET', uri = '') {
return http.req('/api/mchConfig' + uri, data, mode)
}
/* 会员数据统计 */
export function $memberInfoCount(params) {
return http.req('/api/member/count', params, 'GET')
}
/* 查询商户支付应用 费率参数是否配置 */
export function $getMchPayPassage(appId, ifCode) {
return http.req(`/api/payConfig/existPayParams/${appId}/${ifCode}`, {}, 'GET')
}
/* 获取进件渠道列表 */
export function $getAllIsvInfoList(ifCode, range = 0) {
return http.req("/api/isvInfo/isvInfoList", {
pageNumber: "-1",
range: range,
ifCode: ifCode
}, "GET")
}
// 请求验证码
export function $isCode() {
return http.req("/api/anon/auth/vercode", "GET")
}
export function $pmdfb(data) {
return http.req("/api/store/device/setMarqueeInfo", data, "POST")
}
export function $getImgWH(params) {
return http.req(`/api/store/device/getMarketPoint/${params}`, "GET")
}
export function $initializeImg(params) {
return http.req(`/api/store/device/getMarketPic/${params}`, "GET")
}
export function $uploadImg(data) {
return http.req(`/api/store/device/setMarketPic`, data, "POST")
}
/* 获取上传form信息 */
export function $NewOssFilesForm(data) {
return http.req("/api/ossFiles/form", data, "POST")
}

View File

@@ -0,0 +1,153 @@
/**
* HTTP的封装 基于uni.request
* 包括: 通用响应结果的处理 和 业务的增删改查函数
*
* @author terrfly
* @site https://www.jeequan.com
* @date 2021/12/16 18:35
*/
// 导入全局属性
import appConfig from '@/config/appConfig.js'
import storageManage from '@/commons/utils/storageManage.js'
import { sm4DecryptByResData } from '@/commons/utils/encryptUtil.js'
import infoBox from "@/commons/utils/infoBox.js"
import go from '@/commons/utils/go.js';
// 多少 ms 以内, 不提示loading
const loadingShowTime = 200
// 通用处理逻辑
function commonsProcess(showLoading, httpReqCallback){
// 判断是否请求完成(用作 是否loading
// 包括: 'ing', 'ingLoading', 'finish'
let reqState = 'ing'
// 是否已经提示的错误信息
let isShowErrorToast = false
// 请求完成, 需要处理的动作
let reqFinishFunc = () => {
if(reqState == 'ingLoading'){ // 关闭loading弹层
infoBox.hideLoading()
}
reqState = 'finish' // 请求完毕
}
// 明确显示loading
if(showLoading){
// xx ms内响应完成不提示loading
setTimeout(() => {
if(reqState == 'ing'){
reqState = 'ingLoading'
infoBox.showLoading()
}
}, loadingShowTime)
}
return httpReqCallback().then((httpData) => {
reqFinishFunc(); // 请求完毕的动作
// 从http响应数据中解构响应数据 [ 响应码、 bodyData ]
let { statusCode, data } = httpData
// 避免混淆重新命名
let bodyData = data
if(statusCode == 401){
// 清楚 token
storageManage.token(null, true)
// 提示信息
isShowErrorToast = true
infoBox.showErrorToast('请登录').then(() => {
go.to("PAGES_LOGIN", {}, go.GO_TYPE_RELAUNCH)
})
return Promise.reject(bodyData) // 跳转到catch函数
}
// http响应码不正确
if(statusCode != 200){
isShowErrorToast = true
infoBox.showErrorToast('服务器异常')
return Promise.reject(bodyData) // 跳转到catch函数
}
// 业务响应异常
if(bodyData.code != 0){
isShowErrorToast = true
infoBox.showToast(bodyData.msg)
if(bodyData.code == 5005){ // 密码已过期, 直接跳转到更改密码页面
uni.reLaunch({url: '/pageUser/setting/updatePwd'})
}
return Promise.reject(bodyData)
}
// 加密数据
if(!bodyData.data && bodyData.encryptData){
return Promise.resolve({ bizData: sm4DecryptByResData(bodyData.encryptData), code: bodyData.code })
}
// 构造请求成功的响应数据
return Promise.resolve({ bizData: bodyData.data, code: bodyData.code })
}).catch( res => {
reqFinishFunc(); // 请求完毕的动作
// 如果没有提示错误, 那么此处提示 异常。
if(!isShowErrorToast){
infoBox.showErrorToast(`请求网络异常`)
}
return Promise.reject(res)
}).finally(() => { // finally 是 then结束后再执行, 此处不适用。 需要在请求完成后立马调用: reqFinishFunc()
});
}
// 默认 显示loading(控制 xxs 内 不提示loading )
function req(uri, data, method = "GET", showLoading = true, extParams = {}){
// 放置token
let headerObject = {}
headerObject[appConfig.tokenKey] = storageManage.token()
return commonsProcess(showLoading, () => {
return uni.request(
Object.assign({url: appConfig.env.JEEPAY_BASE_URL + uri, data: data, method: method, header: headerObject}, extParams )
)
}
)
}
// 上传
function upload(uri, data, file, showLoading = true, extParams = {}){
// 放置token
let headerObject = {}
headerObject[appConfig.tokenKey] = storageManage.token()
return commonsProcess(showLoading, () => {
return uni.uploadFile(
Object.assign({url: appConfig.env.JEEPAY_BASE_URL + uri, formData: data, name: "file", filePath: file.path, header: headerObject}, extParams )
).then((httpData) => {
// uni.upload 返回bodyData 的是 string类型。 需要解析。
httpData.data = JSON.parse(httpData.data)
return Promise.resolve(httpData)
})
}
)
}
export default {
req : req,
upload : upload
}

View File

@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/main.js"></script>
</body>
</html>

View File

@@ -0,0 +1,44 @@
/*
* $Id: dankogai.js,v 0.4 2012/08/24 05:23:18 dankogai Exp dankogai $
*
* use mocha to test me
* http://visionmedia.github.com/mocha/
*/
var assert = assert || require("assert");
var Base64 = Base64 || require('../base64.js').Base64;
var is = function (a, e, m) {
return function () {
assert.equal(a, e, m)
}
};
describe('basic', function () {
it('d', is(Base64.encode('d'), 'ZA=='));
it('da', is(Base64.encode('da'), 'ZGE='));
it('dan', is(Base64.encode('dan'), 'ZGFu'));
it('ZA==', is(Base64.decode('ZA=='), 'd' ));
it('ZGE=', is(Base64.decode('ZGE='), 'da' ));
it('ZGFu', is(Base64.decode('ZGFu'), 'dan' ));
});
describe('whitespace', function () {
it('Z A==', is(Base64.decode('ZA =='), 'd' ));
it('ZG E=', is(Base64.decode('ZG E='), 'da' ));
it('ZGF u', is(Base64.decode('ZGF u'), 'dan' ));
});
describe('null', function () {
it('\\0', is(Base64.encode('\0'), 'AA=='));
it('\\0\\0', is(Base64.encode('\0\0'), 'AAA='));
it('\\0\\0\\0', is(Base64.encode('\0\0\0'), 'AAAA'));
it('AA==', is(Base64.decode('AA=='), '\0' ));
it('AAA=', is(Base64.decode('AAA='), '\0\0' ));
it('AAAA', is(Base64.decode('AAAA'), '\0\0\0'));
});
describe('Base64', function () {
it('.encode', is(Base64.encode('小飼弾'), '5bCP6aO85by+'));
it('.encodeURI', is(Base64.encodeURI('小飼弾'), '5bCP6aO85by-'));
it('.decode', is(Base64.decode('5bCP6aO85by+'), '小飼弾'));
it('.decode', is(Base64.decode('5bCP6aO85by-'), '小飼弾'));
});

View File

@@ -0,0 +1,24 @@
/*
* $Id: es5.js,v 0.1 2012/08/23 19:43:17 dankogai Exp dankogai $
*
* use mocha to test me
* http://visionmedia.github.com/mocha/
*/
var assert = assert || require("assert");
var Base64 = Base64 || require('../base64.js').Base64;
var is = function (a, e, m) {
return function () {
assert.equal(a, e, m)
}
};
if ('extendString' in Base64){
Base64.extendString();
describe('String', function () {
it('.toBase64', is('小飼弾'.toBase64(), '5bCP6aO85by+'));
it('.toBase64', is('小飼弾'.toBase64(true), '5bCP6aO85by-'));
it('.toBase64URI', is('小飼弾'.toBase64URI(), '5bCP6aO85by-'));
it('.fromBase64', is('5bCP6aO85by+'.fromBase64(), '小飼弾'));
it('.fromBase64', is('5bCP6aO85by-'.fromBase64(), '小飼弾'));
});
}

View File

@@ -0,0 +1,25 @@
/*
* $Id: es6.js,v 0.1 2017/11/29 21:43:17 ufolux Exp ufolux $
*
* use mocha to test me
* http://visionmedia.github.com/mocha/
*/
import {Base64} from '../base64'
var assert = assert || require("assert");
var is = function (a, e, m) {
return function () {
assert.equal(a, e, m)
}
};
if ('extendString' in Base64){
Base64.extendString();
describe('String', function () {
it('.toBase64', is('小飼弾'.toBase64(), '5bCP6aO85by+'));
it('.toBase64', is('小飼弾'.toBase64(true), '5bCP6aO85by-'));
it('.toBase64URI', is('小飼弾'.toBase64URI(), '5bCP6aO85by-'));
it('.fromBase64', is('5bCP6aO85by+'.fromBase64(), '小飼弾'));
it('.fromBase64', is('5bCP6aO85by-'.fromBase64(), '小飼弾'));
});
}

View File

@@ -0,0 +1,40 @@
<html>
<head>
<meta charset="utf-8">
<title>Mocha Tests</title>
<link href="https://cdn.rawgit.com/mochajs/mocha/2.2.5/mocha.css" rel="stylesheet" />
</head>
<body>
<div id="mocha"></div>
<script src="https://cdn.rawgit.com/jquery/jquery/2.1.4/dist/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/Automattic/expect.js/0.3.1/index.js"></script>
<script src="https://cdn.rawgit.com/mochajs/mocha/2.2.5/mocha.js"></script>
<script>
mocha.setup('bdd');
</script>
<script src="./moment.js"></script>
<script src="../base64.js"></script>
<script>
var assert = function(expr, msg) {
if (!expr) throw new Error(msg || 'failed');
};
assert.equal = function(a, b, msg) {
if (a !== b) throw new Error(msg || ('failed : '+a+','+b));
};
</script>
<script src="./dankogai.js"></script>
<script src="./es5.js"></script>
<script src="./large.js"></script>
<script src="./yoshinoya.js"></script>
<script>
$(function() {
mocha.run();
});
</script>
</head>
<body>
$Id: index.html,v 0.3 2017/09/11 08:43:43 dankogai Exp dankogai $
<div id="mocha"></div>
</body>
</html>

View File

@@ -0,0 +1,25 @@
/*
* $Id: large.js,v 0.3 2012/08/23 19:14:37 dankogai Exp dankogai $
*
* use mocha to test me
* http://visionmedia.github.com/mocha/
*/
var assert = assert || require("assert");
var Base64 = Base64 || require('../base64.js').Base64;
var is = function (a, e, m) {
return function () {
assert.equal(a, e, m)
}
};
var seed = function () {
var a, i;
for (a = [], i = 0; i < 256; i++) {
a.push(String.fromCharCode(i));
}
return a.join('');
}();
describe('Base64', function () {
for (var i = 0, str = seed; i < 16; str += str, i++) {
it(''+str.length, is(Base64.decode(Base64.encode(str)), str));
}
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,19 @@
/*
* use mocha to test me
* http://visionmedia.github.com/mocha/
*/
var assert = assert || require("assert");
var Base64 = Base64 || require('../base64.js').Base64;
var is = function (a, e, m) {
return function () {
assert.equal(a, e, m)
}
};
describe('Yoshinoya', function () {
it('.encode', is(Base64.encode('𠮷野家'), '8KCut+mHjuWutg=='));
it('.encodeURI', is(Base64.encodeURI('𠮷野家'), '8KCut-mHjuWutg'));
it('.decode', is(Base64.decode('8KCut+mHjuWutg=='), '𠮷野家'));
it('.decode', is(Base64.decode('8KCut-mHjuWutg'), '𠮷野家'));
/* it('.decode', is(Base64.decode('7aGC7b636YeO5a62'), '𠮷野家')); */
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View File

@@ -0,0 +1,27 @@
Copyright (c) 2014, Dan Kogai
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of {{{project}}} nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,94 @@
[![build status](https://secure.travis-ci.org/dankogai/js-base64.png)](http://travis-ci.org/dankogai/js-base64)
# base64.js
Yet another Base64 transcoder
## Install
```javascript
$ npm install --save js-base64
```
If you are using it on ES6 transpilers, you may also need:
```javascript
$ npm install --save babel-preset-env
```
Note `js-base64` itself is stand-alone so its `package.json` has no `dependencies`.  However, it is also tested on ES6 environment so `"babel-preset-env": "^1.7.0"` is on `devDependencies`.
## Usage
### In Browser
```html
<script src="base64.js"></script>
```
### node.js
```javascript
var Base64 = require('js-base64').Base64;
```
## es6+
```javascript
import { Base64 } from 'js-base64';
```
## SYNOPSIS
```javascript
Base64.encode('dankogai'); // ZGFua29nYWk=
Base64.encode('小飼弾'); // 5bCP6aO85by+
Base64.encodeURI('小飼弾'); // 5bCP6aO85by-
Base64.decode('ZGFua29nYWk='); // dankogai
Base64.decode('5bCP6aO85by+'); // 小飼弾
// note .decodeURI() is unnecessary since it accepts both flavors
Base64.decode('5bCP6aO85by-'); // 小飼弾
```
### String Extension for ES5
```javascript
if (Base64.extendString) {
// you have to explicitly extend String.prototype
Base64.extendString();
// once extended, you can do the following
'dankogai'.toBase64(); // ZGFua29nYWk=
'小飼弾'.toBase64(); // 5bCP6aO85by+
'小飼弾'.toBase64(true); // 5bCP6aO85by-
'小飼弾'.toBase64URI(); // 5bCP6aO85by-
'ZGFua29nYWk='.fromBase64(); // dankogai
'5bCP6aO85by+'.fromBase64(); // 小飼弾
'5bCP6aO85by-'.fromBase64(); // 小飼弾
}
```
### TypeScript
TypeScript 2.0 type definition was added to the [DefinitelyTyped repository](https://github.com/DefinitelyTyped/DefinitelyTyped).
```bash
$ npm install --save @types/js-base64
```
## `.decode()` vs `.atob` (and `.encode()` vs `btoa()`)
Suppose you have:
```
var pngBase64 =
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=";
```
Which is a Base64-encoded 1x1 transparent PNG, **DO NOT USE** `Base64.decode(pngBase64)`.  Use `Base64.atob(pngBase64)` instead.  `Base64.decode()` decodes to UTF-8 string while `Base64.atob()` decodes to bytes, which is compatible to browser built-in `atob()` (Which is absent in node.js).  The same rule applies to the opposite direction.
## SEE ALSO
+ http://en.wikipedia.org/wiki/Base64

View File

@@ -0,0 +1,47 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<!-- $Id: base64.html,v 1.1 2009/03/01 22:00:28 dankogai Exp dankogai $ -->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Demo for base64.js</title>
</head>
<body>
<h1>Demo for base64.js</h1>
<p>$Id: base64.html,v 1.1 2009/03/01 22:00:28 dankogai Exp dankogai $</p>
<table width="640"><tbody>
<tr><th width="50%">Text</th><th>Base64
(URL Safe <input id="encodeURI" type="checkbox" onclick="doit()">)</th></tr>
<tr>
<th><textarea id="srctxt" cols="32" rows="4" onkeyup="doit()">
</textarea></th>
<th><textarea id="base64" cols="32" rows="4" onkeyup="
$('srctxt').value = Base64.decode(this.value);
doit();
if (1 /*@cc_on -1 @*/) $('data').src = 'data:text/plain;base64,' + this.value;
"></textarea></th>
</tr>
<tr><th width="50%">Roundtrip</th><th>iframe w/ data: (no IE)</th></tr>
<tr>
<th><textarea id="roundtrip" cols=32" rows="4" disabled></textarea></th>
<th><iframe id="data" width="80%" height="64"></iframe></th>
</tr>
</tbody></table>
<script src="./base64.js"></script>
<script>
$ = function(id){ return document.getElementById(id) };
function doit(){
var encoded = Base64[
'encode' + ($('encodeURI').checked ? 'URI' : '')
]($('srctxt').value);
$('base64').value = encoded;
if (1 /*@cc_on -1 @*/) {
$('data').src = 'data:text/plain;base64,'
+ Base64.encode(Base64.decode(encoded));
}
$('roundtrip').value = Base64.decode(encoded);
}
</script>
</body>
</html>

View File

@@ -0,0 +1,231 @@
/*
* base64.js
*
* Licensed under the BSD 3-Clause License.
* http://opensource.org/licenses/BSD-3-Clause
*
* References:
* http://en.wikipedia.org/wiki/Base64
*/
;(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
? module.exports = factory(global)
: typeof define === 'function' && define.amd
? define(factory) : factory(global)
}((
typeof self !== 'undefined' ? self
: typeof window !== 'undefined' ? window
: typeof global !== 'undefined' ? global
: this
), function(global) {
'use strict';
// existing version for noConflict()
var _Base64 = global.Base64;
var version = "2.4.9";
// if node.js and NOT React Native, we use Buffer
var buffer;
if (typeof module !== 'undefined' && module.exports) {
try {
buffer = eval("require('buffer').Buffer");
} catch (err) {
buffer = undefined;
}
}
// constants
var b64chars
= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
var b64tab = function(bin) {
var t = {};
for (var i = 0, l = bin.length; i < l; i++) t[bin.charAt(i)] = i;
return t;
}(b64chars);
var fromCharCode = String.fromCharCode;
// encoder stuff
var cb_utob = function(c) {
if (c.length < 2) {
var cc = c.charCodeAt(0);
return cc < 0x80 ? c
: cc < 0x800 ? (fromCharCode(0xc0 | (cc >>> 6))
+ fromCharCode(0x80 | (cc & 0x3f)))
: (fromCharCode(0xe0 | ((cc >>> 12) & 0x0f))
+ fromCharCode(0x80 | ((cc >>> 6) & 0x3f))
+ fromCharCode(0x80 | ( cc & 0x3f)));
} else {
var cc = 0x10000
+ (c.charCodeAt(0) - 0xD800) * 0x400
+ (c.charCodeAt(1) - 0xDC00);
return (fromCharCode(0xf0 | ((cc >>> 18) & 0x07))
+ fromCharCode(0x80 | ((cc >>> 12) & 0x3f))
+ fromCharCode(0x80 | ((cc >>> 6) & 0x3f))
+ fromCharCode(0x80 | ( cc & 0x3f)));
}
};
var re_utob = /[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g;
var utob = function(u) {
return u.replace(re_utob, cb_utob);
};
var cb_encode = function(ccc) {
var padlen = [0, 2, 1][ccc.length % 3],
ord = ccc.charCodeAt(0) << 16
| ((ccc.length > 1 ? ccc.charCodeAt(1) : 0) << 8)
| ((ccc.length > 2 ? ccc.charCodeAt(2) : 0)),
chars = [
b64chars.charAt( ord >>> 18),
b64chars.charAt((ord >>> 12) & 63),
padlen >= 2 ? '=' : b64chars.charAt((ord >>> 6) & 63),
padlen >= 1 ? '=' : b64chars.charAt(ord & 63)
];
return chars.join('');
};
var btoa = global.btoa ? function(b) {
return global.btoa(b);
} : function(b) {
return b.replace(/[\s\S]{1,3}/g, cb_encode);
};
var _encode = buffer ?
buffer.from && Uint8Array && buffer.from !== Uint8Array.from
? function (u) {
return (u.constructor === buffer.constructor ? u : buffer.from(u))
.toString('base64')
}
: function (u) {
return (u.constructor === buffer.constructor ? u : new buffer(u))
.toString('base64')
}
: function (u) { return btoa(utob(u)) }
;
var encode = function(u, urisafe) {
return !urisafe
? _encode(String(u))
: _encode(String(u)).replace(/[+\/]/g, function(m0) {
return m0 == '+' ? '-' : '_';
}).replace(/=/g, '');
};
var encodeURI = function(u) { return encode(u, true) };
// decoder stuff
var re_btou = new RegExp([
'[\xC0-\xDF][\x80-\xBF]',
'[\xE0-\xEF][\x80-\xBF]{2}',
'[\xF0-\xF7][\x80-\xBF]{3}'
].join('|'), 'g');
var cb_btou = function(cccc) {
switch(cccc.length) {
case 4:
var cp = ((0x07 & cccc.charCodeAt(0)) << 18)
| ((0x3f & cccc.charCodeAt(1)) << 12)
| ((0x3f & cccc.charCodeAt(2)) << 6)
| (0x3f & cccc.charCodeAt(3)),
offset = cp - 0x10000;
return (fromCharCode((offset >>> 10) + 0xD800)
+ fromCharCode((offset & 0x3FF) + 0xDC00));
case 3:
return fromCharCode(
((0x0f & cccc.charCodeAt(0)) << 12)
| ((0x3f & cccc.charCodeAt(1)) << 6)
| (0x3f & cccc.charCodeAt(2))
);
default:
return fromCharCode(
((0x1f & cccc.charCodeAt(0)) << 6)
| (0x3f & cccc.charCodeAt(1))
);
}
};
var btou = function(b) {
return b.replace(re_btou, cb_btou);
};
var cb_decode = function(cccc) {
var len = cccc.length,
padlen = len % 4,
n = (len > 0 ? b64tab[cccc.charAt(0)] << 18 : 0)
| (len > 1 ? b64tab[cccc.charAt(1)] << 12 : 0)
| (len > 2 ? b64tab[cccc.charAt(2)] << 6 : 0)
| (len > 3 ? b64tab[cccc.charAt(3)] : 0),
chars = [
fromCharCode( n >>> 16),
fromCharCode((n >>> 8) & 0xff),
fromCharCode( n & 0xff)
];
chars.length -= [0, 0, 2, 1][padlen];
return chars.join('');
};
var atob = global.atob ? function(a) {
return global.atob(a);
} : function(a){
return a.replace(/[\s\S]{1,4}/g, cb_decode);
};
var _decode = buffer ?
buffer.from && Uint8Array && buffer.from !== Uint8Array.from
? function(a) {
return (a.constructor === buffer.constructor
? a : buffer.from(a, 'base64')).toString();
}
: function(a) {
return (a.constructor === buffer.constructor
? a : new buffer(a, 'base64')).toString();
}
: function(a) { return btou(atob(a)) };
var decode = function(a){
return _decode(
String(a).replace(/[-_]/g, function(m0) { return m0 == '-' ? '+' : '/' })
.replace(/[^A-Za-z0-9\+\/]/g, '')
);
};
var noConflict = function() {
var Base64 = global.Base64;
global.Base64 = _Base64;
return Base64;
};
// export Base64
global.Base64 = {
VERSION: version,
atob: atob,
btoa: btoa,
fromBase64: decode,
toBase64: encode,
utob: utob,
encode: encode,
encodeURI: encodeURI,
btou: btou,
decode: decode,
noConflict: noConflict,
__buffer__: buffer
};
// if ES5 is available, make Base64.extendString() available
if (typeof Object.defineProperty === 'function') {
var noEnum = function(v){
return {value:v,enumerable:false,writable:true,configurable:true};
};
global.Base64.extendString = function () {
Object.defineProperty(
String.prototype, 'fromBase64', noEnum(function () {
return decode(this)
}));
Object.defineProperty(
String.prototype, 'toBase64', noEnum(function (urisafe) {
return encode(this, urisafe)
}));
Object.defineProperty(
String.prototype, 'toBase64URI', noEnum(function () {
return encode(this, true)
}));
};
}
//
// export Base64 to the namespace
//
if (global['Meteor']) { // Meteor.js
Base64 = global.Base64;
}
// module.exports and AMD are mutually exclusive.
// module.exports has precedence.
if (typeof module !== 'undefined' && module.exports) {
module.exports.Base64 = global.Base64;
}
else if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], function(){ return global.Base64 });
}
// that's it!
return {Base64: global.Base64}
}));

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