同步代码

This commit is contained in:
GaoHao 2025-02-07 14:49:20 +08:00
commit 0740c3f349
1141 changed files with 167372 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
unpackage/cache
unpackage/dist
unpackage/res
unpackage/resources
node_modules/
/node_modules/

179
App.vue Normal file
View File

@ -0,0 +1,179 @@
<!--
App.vue本身不是页面这里不能编写视图元素也就是没有<template>
-->
<script setup>
import {
onLaunch
} from '@dcloudio/uni-app';
import { getFindBySource } from '@/http/yskApi/version.js'
onLaunch(() => {
let that = this
uni.hideTabBar()
// #ifdef MP-WEIXIN
const updateManager = wx.getUpdateManager() //
updateManager.onCheckForUpdate(function(res) {
//
})
updateManager.onUpdateReady(function() {
// applyUpdate
updateManager.applyUpdate()
})
updateManager.onUpdateFailed(function() {
//
})
// #endif
//#ifdef APP-PLUS
//
plus.runtime.getProperty(plus.runtime.appid, widgetInfo => {
//
console.log("widgetInfo==",widgetInfo)
getFindBySource({source:'APP'}).then(res => {
console.log("selectNewApp==",res)
console.log("version===",res.url && widgetInfo.version < res.version)
if (res.url && widgetInfo.version < res.version) {
console.log("version===222")
let downloadLink = res.url;
// let downloadLink = "https://short-video.hnsiyao.cn/app/sy-duanju.apk";
console.log(downloadLink)
// let androidLink = res.androidWgtUrl;
// let iosLink = res.iosWgtUrl;
let ready = false;
//
if (res.isUp == 1) {
uni.showModal({
showCancel: false,
title: '发现新版本',
confirmText: '立即更新',
content: res.message,
success: res => {
if (res.confirm) {
uni.showLoading('下载中...');
if (uni.getSystemInfoSync().platform ==
'android') {
uni.downloadFile({
url: downloadLink,
success: downloadResult => {
if (downloadResult.statusCode === 200) {
plus.io.resolveLocalFileSystemURL(downloadResult.tempFilePath, entry => {
entry.getParent(_oldFile=>{
entry.moveTo(_oldFile,'.apk',newFilePath=>{
console.log('newFilePath',newFilePath.fullPath)
plus.runtime
.install(newFilePath.fullPath, { force: false },
d => {
console
.log(
'install success...'
);
plus.runtime
.restart();
},
e => {
console.log(e)
console
.error(
'install fail...'
);
}
);
})
})
})
}
}
});
}
if (uni.getSystemInfoSync().platform ==
'ios') {
plus.runtime.openURL(downloadLink, function(
res) {});
}
} else if (res.cancel) {
console.log('取消');
}
}
});
} else {
uni.showModal({
title: '发现新版本',
confirmText: '立即更新',
cancelText: '下次更新',
content: res.message,
success: res => {
if (res.confirm) {
uni.showLoading('下载中...');
if (uni.getSystemInfoSync().platform ==
'android') {
uni.downloadFile({
url: downloadLink,
success: downloadResult => {
if (downloadResult
.statusCode ===
200) {
plus.io.resolveLocalFileSystemURL(downloadResult.tempFilePath, entry => {
entry.getParent(_oldFile=>{
entry.moveTo(_oldFile,'.apk',newFilePath=>{
console.log('newFilePath',newFilePath.fullPath)
plus.runtime
.install(newFilePath.fullPath, { force: false },
d => {
console
.log(
'install success...'
);
plus.runtime
.restart();
},
e => {
console.log(e)
console
.error(
'install fail...'
);
}
);
})
})
})
}
}
});
}
if (uni.getSystemInfoSync().platform ==
'ios') {
plus.runtime.openURL(downloadLink, function(
res) {});
}
} else if (res.cancel) {
console.log('取消');
}
}
});
}
}
}).catch((res)=>{
console.log(res)
})
});
// #endif
});
</script>
<style lang="scss">
/** 每个页面公共css */
@import '@/commons/style/global.scss';
@import '@/commons/style/common.scss';
/** uni 组件样式覆盖 */
@import '@/commons/style/uni-overwrite.scss';
@import "uview-plus/index.scss";
</style>

27
androidPrivacy.json Normal file
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"
}
}
}

1
commons/JSON/mccAli.json Normal file

File diff suppressed because one or more lines are too long

48
commons/class/list.js Normal file
View File

@ -0,0 +1,48 @@
import {
reactive, ref
} from 'vue';
function isSameType(a, b) {
return a instanceof b === true || b instanceof a === true;
}
class LIST{
constructor(data) {
this.data=reactive({
page:0,
totalPage:0,
total:0,
list:[],
hasAjax:false,
status:'',//loading fail success
query:{
}
})
Object.assign(this.data, data)
}
add(item){
this.data.list.push(item)
}
del(index){
this.data.list.splice(index,1)
}
update(index,item){
this.data.list[index]=item
}
get(index){
return this.data.list[index]
}
getVal(key){
return this.data[key]
}
setQuery(key,val){
this.data.query[key]=val
}
setVal(key,val){
this.data[key]=val
if(key=='page'){
this.data['page']=val
}
}
}
export default LIST

7
commons/color.js Normal file
View File

@ -0,0 +1,7 @@
export const ColorMain= '#318AFE';
export const ColorRed= '#F02C45';
export default{
ColorMain,ColorRed
}

74
commons/goodsData.js Normal file
View File

@ -0,0 +1,74 @@
import dayjs from "dayjs";
export const $types = [{
title: "计量商品",
desc: '单价购买',
value: 'normal'
},
{
title: "多规格",
desc: '多种不同规格',
value: 'sku'
},
{
title: "套餐组合",
desc: '选择多种组合',
value: 'group'
},
{
title: "称重商品",
desc: '按重量售卖',
value: 'weight'
},
{
title: "时价商品",
desc: '收银端可更改价格',
value: 'currentPrice'
}
]
// 商品默认sku
export const $defaultSku = {
salePrice: '',
memberPrice: '',
costPrice: '',
originPrice: '',
// stockNumber: '',
firstShared: '',
suit: 1,
barCode: `${uni.getStorageSync("shopId")}${dayjs().valueOf()}`,
}
// 库存记录筛选类型
export const $invoicingType = [{
text: '全部',
value: ''
},
{
text: '供应商入库',
value: 'purveyor'
},
{
text: '供应商退货',
value: 'reject'
},
{
text: '其他入库',
value: 'purchase'
},
{
text: '其他出库',
value: 'other-out'
}
]
// 页面常用数据
export const $pageData = {
query: {
page: 0,
size: 10
},
totalElements: 0,
list: [],
hasAjax: false,
}

84
commons/orderEnum.js Normal file
View File

@ -0,0 +1,84 @@
export default {
status: [
{
key: 'unpaid',
label: '待支付'
},
{
key: 'unsend',
label: '待发货'
},
{
key: 'closed',
label: '订单完成'
},
{
key: 'send',
label: '已发'
},
{
key: 'refunding',
label: '申请退单'
},
{
key: 'refund',
label: '退单'
},
{
key: 'cancelled',
label: '取消订单'
},
{
key: 'merge',
label: '合台'
},
{
key: 'pending',
label: '挂单'
},
{
key: 'activate',
label: '激活'
},
{
key: 'paying',
label: '支付中'
}
],
sendType: [
{
key: 'post',
label: '快递'
},
{
key: 'takeaway',
label: '外卖'
},
{
key: 'takeself',
label: '自提'
},
{
key: 'table',
label: '堂食'
}
],
orderType: [
{
key: 'cash',
label: '收银'
},
{
key: 'miniapp',
label: '小程序'
},
{
key: 'offline',
label: '线下'
},
{
key: 'return',
label: '退单'
}
]
}

14
commons/readme.txt Normal file
View File

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

406
commons/style/common.scss Normal file
View File

@ -0,0 +1,406 @@
.u-relative,
.u-rela {
position: relative;
}
.u-absolute,
.u-abso {
position: absolute;
}
.u-fixed,.u-fix{
position: fixed;
}
.left-top{
left: 0;
top: 0;
}
.u-overflow-hide{
overflow: hidden;
}
// nvue不能用标签命名样式不能放在微信组件中否则微信开发工具会报警告无法使用标签名当做选择器
/* #ifndef APP-NVUE */
image {
display: inline-block;
}
// 在weex也即nvue中所有元素默认为border-box
view,
text {
box-sizing: border-box;
}
/* #endif */
.u-font-xs {
font-size: 22rpx;
}
.u-font-sm {
font-size: 26rpx;
}
.u-font-md {
font-size: 28rpx;
}
.u-font-lg {
font-size: 30rpx;
}
.u-font-xl {
font-size: 34rpx;
}
.u-flex {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
}
.u-flex-wrap {
flex-wrap: wrap;
}
.u-flex-nowrap {
flex-wrap: nowrap;
}
.u-col-center {
align-items: center;
}
.u-col-top {
align-items: flex-start;
}
.u-col-bottom {
align-items: flex-end;
}
.u-row-center {
justify-content: center;
}
.u-row-left {
justify-content: flex-start;
}
.u-row-right {
justify-content: flex-end;
}
.u-row-between {
justify-content: space-between;
}
.u-row-around {
justify-content: space-around;
}
.u-text-left {
text-align: left;
}
.u-text-center {
text-align: center;
}
.u-text-right {
text-align: right;
}
.u-flex-col {
/* #ifndef APP-NVUE */
display: flex!important;
/* #endif */
flex-direction: column!important;
}
// 定义flex等分
@for $i from 0 through 12 {
.u-flex-#{$i} {
flex: $i;
}
}
// 定义字体(px)单位小于20都为px单位字体
@for $i from 9 to 20 {
.u-font-#{$i} {
font-size: $i + px;
}
}
// 定义字体(rpx)单位大于或等于20的都为rpx单位字体
@for $i from 20 through 40 {
.u-font-#{$i} {
font-size: $i + rpx;
}
}
// 定义内外边距历遍1-80
@for $i from 0 through 80 {
// 只要双数和能被5除尽的数
@if $i % 2 == 0 or $i % 5 == 0 {
// 得出u-margin-30或者u-m-30
.u-margin-#{$i}, .u-m-#{$i} {
margin: $i + rpx!important;
}
// 得出u-padding-30或者u-p-30
.u-padding-#{$i}, .u-p-#{$i} {
padding: $i + rpx!important;
}
@each $short, $long in l left, t top, r right, b bottom {
// 缩写版结果如 u-m-l-30
// 定义外边距
.u-m-#{$short}-#{$i} {
margin-#{$long}: $i + rpx!important;
}
// 定义内边距
.u-p-#{$short}-#{$i} {
padding-#{$long}: $i + rpx!important;
}
// 完整版结果如u-margin-left-30
// 定义外边距
.u-margin-#{$long}-#{$i} {
margin-#{$long}: $i + rpx!important;
}
// 定义内边距
.u-padding-#{$long}-#{$i} {
padding-#{$long}: $i + rpx!important;
}
}
}
}
// 重置nvue的默认关于flex的样式
.u-reset-nvue {
flex-direction: row;
align-items: center;
}
/* start--文本行数限制--start */
.u-line-1 {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.u-line-2 {
-webkit-line-clamp: 2;
}
.u-line-3 {
-webkit-line-clamp: 3;
}
.u-line-4 {
-webkit-line-clamp: 4;
}
.u-line-5 {
-webkit-line-clamp: 5;
}
.u-line-2, .u-line-3, .u-line-4, .u-line-5 {
overflow: hidden;
word-break: break-all;
text-overflow: ellipsis;
display: -webkit-box; // 弹性伸缩盒
-webkit-box-orient: vertical; // 设置伸缩盒子元素排列方式
}
/* end--文本行数限制--end */
/* start--不同颜色文字--start */
.color-333{
color: #333;
}
.color-666{
color: #666;
}
.color-999{
color: #999;
}
.color-red{
color: $my-red-color;
}
.color-main{
color:$my-main-color
}
/* end--不同颜色文字--end */
.tranistion{
transition: all .3s ease-in-out;
}
.tranistion-1{
transition: all .1s ease-in-out;
}
.tranistion-2{
transition: all .2s ease-in-out;
}
.font-bold{
font-weight: 700;
}
/* start--不同颜色背景--start */
.my-bg-main{
background-color:$my-main-color
}
/* end--不同颜色背景--end */
.safe-page{
padding-bottom: 60rpx!important;
}
::v-deep .uni-switch-input.uni-switch-input-checked{
border-color: $my-main-color;
background-color: $my-main-color;
}
.min-page{
/* #ifdef H5 */
min-height: calc(100vh - 44px);
/* #endif */
/* #ifndef H5 */
min-height: 100vh;
/* #endif */
}
.w-full{
width: 100%;
}
.gap-20{
gap: 20rpx;
}
.color-000{
color: #000;
}
.color-fff{
color: #fff;
}
.bg-fff{
background-color: #fff;
}
.bg-gray{
background-color: #F9F9F9;
}
.overflow-hide{
/* #ifdef H5 */
height: calc(100vh - 44px);
/* #endif */
/* #ifndef H5 */
height: 100vh;
/* #endif */
overflow: hidden;
}
.no-wrap{
white-space: nowrap;
}
.border-r-12{
border-radius: 12rpx;
}
.border-r-18{
border-radius: 18rpx;
}
.border-top{
border-top: 1px solid #E5E5E5;
}
.border-bottom{
border-bottom: 1px solid #E5E5E5;
}
.scale7{
transform: scale(0.7);
}
.page-gray {
min-height: calc(100vh);
/* #ifdef H5 */
min-height: calc(100vh - var(--window-top));
/* #endif */
display: flex;
flex-direction: column;
background: #F9F9F9;
}
.box-shadow{
box-shadow: 0 0 5px #eee;
}
.safe-bottom{
padding-bottom: env(safe-area-inset-bottom);
/* #ifdef H5 */
padding-bottom: 28rpx;
/* #endif */
}
.position-all{
left: 0;
right: 0;
top: 0;
bottom: 0;
}
.fixed-top{
position: fixed;
/* #ifdef H5 */
top: 44px;
/* #endif */
/* #ifndef H5 */
top: 0;
/* #endif */
left: 0;
right: 0;
}
.lh30 {
line-height: 30px;
}
.default-box-padding{
padding: 32rpx 28rpx;
}
.icon-arrow-down-fill {
width: 16rpx;
height: 10rpx;
}
.zIndex-999{
z-index: 999;
}
.icon-default-size{
width: 28rpx;
height: 28rpx;
}
::v-deep.uni-easyinput__placeholder-class{
font-size: 28rpx!important;
}
.filter-gray{
filter: grayscale(1);
}
// .u-font-20{
// font-size: 10px;
// }
// .u-font-24{
// font-size: 12px;
// }
// .u-font-28{
// font-size: 14px;
// }
// .u-font-32{
// font-size: 16px;
// }
.line-th{
text-decoration: line-through;
}
//覆盖u-view-plus 颜色
.u-primary-light {
color: $my-main-color;
}
::v-deep .u-border{
border-width: 1px!important;
}
::v-deep .u-m-t-16 .u-textarea{
border-width: 1px!important;
}

230
commons/style/global.scss Normal file
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,6 @@
::v-deep .uni-easyinput__content-input{
height: initial;
}
::v-deep .uni-forms-item{
min-height: initial;
}

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;
}

77
commons/style/vars.scss Normal file
View File

@ -0,0 +1,77 @@
/**
* 系统级别 自定义变量
*
* @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;
//common.scss 分包页面以及组件里所用的颜色
$my-main-color:#318AFE;
$my-red-color:#F02C45;
//my-components
$u-main-color: #303133;
$u-content-color: #606266;
$u-tips-color: #909193;
$u-light-color: #c0c4cc;
$u-border-color: #dadbde;
$u-bg-color: #f3f4f6;
$u-disabled-color: #c8c9cc;
$u-primary: #3c9cff;
$u-primary-dark: #398ade;
$u-primary-disabled: #9acafc;
$u-primary-light: #ecf5ff;
$u-warning: #f9ae3d;
$u-warning-dark: #f1a532;
$u-warning-disabled: #f9d39b;
$u-warning-light: #fdf6ec;
$u-success: #5ac725;
$u-success-dark: #53c21d;
$u-success-disabled: #a9e08f;
$u-success-light: #f5fff0;
$u-error: #f56c6c;
$u-error-dark: #e45656;
$u-error-disabled: #f7b2b2;
$u-error-light: #fef0f0;
$u-info: #909399;
$u-info-dark: #767a82;
$u-info-disabled: #c4c6c9;
$u-info-light: #f4f4f5;

38
commons/table-status.js Normal file
View File

@ -0,0 +1,38 @@
export const $status = {
pending: {
label: "挂单中",
type: "#E6A23C",
},
using: {
label: "开台中",
type: "#fa5555",
},
paying: {
label: "结算中",
type: "#E6A23C",
},
idle: {
label: "空闲",
type: "#3F9EFF",
},
subscribe: {
label: "预定",
type: "rgb(34, 191, 100)",
},
closed: {
label: "关台",
type: "rgb(221,221,221)",
},
// opening: {
// label: "开台中",
// type: "#67C23A",
// },
cleaning: {
label: "待清台",
type: "#FAAD14",
},
unbind: {
label: "未绑定",
type: "rgb(221,221,221)",
}
}

0
commons/utils/.keep Normal file
View File

28
commons/utils/ak.js Normal file
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

37
commons/utils/cal.js Normal file
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

38
commons/utils/dataKit.js Normal file
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

164
commons/utils/datamap.js Normal file
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,30 @@
import dayjs from 'dayjs';
// 获取今天的开始和结束时间
export function getTodayTimestamps() {
const start = dayjs().startOf('day').format('YYYY-MM-DD HH:mm:ss');
const end = dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss');
return { start, end ,label:'今日'};
}
// 获取昨天的开始和结束时间
export function getYesterdayTimestamps() {
const start = dayjs().subtract(1, 'day').startOf('day').format('YYYY-MM-DD HH:mm:ss');
const end = dayjs().subtract(1, 'day').endOf('day').format('YYYY-MM-DD HH:mm:ss');
return { start, end ,label:'昨日'};
}
// 获取本周的开始和结束时间
export function getThisWeekTimestamps() {
const start = dayjs().startOf('week').format('YYYY-MM-DD HH:mm:ss');
const end = dayjs().endOf('week').format('YYYY-MM-DD HH:mm:ss');
return { start, end,label:'本周' };
}
// 获取本月的开始和结束时间
export function getThisMonthTimestamps() {
const start = dayjs().startOf('month').format('YYYY-MM-DD HH:mm:ss');
const end = dayjs().endOf('month').format('YYYY-MM-DD HH:mm:ss');
return { start, end ,label:'本月'};
}

25
commons/utils/debounce.js Normal file
View File

@ -0,0 +1,25 @@
/**
* @desc 函数防抖
* @param func 目标函数
* @param wait 延迟执行毫秒数
* @param immediate true - 立即执行 false - 延迟执行
*/
export const debounce = function(func, wait = 1000, immediate = true) {
let timer;
return function() {
let context = this,
args = arguments;
if (timer) clearTimeout(timer);
if (immediate) {
let callNow = !timer;
timer = setTimeout(() => {
timer = null;
}, wait);
if (callNow) func.apply(context, args);
} else {
timer = setTimeout(() => {
func.apply(context, args);
}, wait)
}
}
}

138
commons/utils/emit.js Normal file
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}
}

21
commons/utils/ent.js Normal file
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

101
commons/utils/formUtil.js Normal file
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

55
commons/utils/format.js Normal file
View File

@ -0,0 +1,55 @@
/**
* 格式化价格函数将价格限定在指定的最小值和最大值范围内并保留两位小数
*
* @param {number} price - 需要格式化的价格
* @param {number} min - 价格的最小值
* @param {number} max - 价格的最大值默认为100000000
* @param {Boolean} returnIsArea - 是否返回值符合范围区间默认为false
* @returns {number} - 返回格式化后的价格如果超出范围则返回最小值或最大值
*/
export const formatPrice = (price,min=-Infinity, max = 100000000,returnIsArea=false,isRerturnNullString=false) => {
if(price === undefined || price === null||price===''){
return isRerturnNullString?'':0
}
// 将价格转换为浮点数并保留两位小数
const newval = parseFloat((Math.floor(price * 100) / 100).toFixed(2))
// 如果价格大于最大值,返回最大值
if (newval > max) {
return returnIsArea?{value:max,error:true}:max
}
// 如果价格小于最小值,返回最小值
if (newval < min) {
return returnIsArea?{value:min,error:true}:min
}
// 如果价格小于最小值,返回最小值
if (newval < min) {
return min
}
// 返回格式化后的价格
return newval
}
export function returnReverseVal(val, isReturnString = true) {
const isBol = typeof val === "boolean";
const isString = typeof val === "string";
let reverseNewval = "";
if (isBol) {
reverseNewval = !val;
}
if (isString) {
reverseNewval = val === "true" ? "false" : "true";
}
return reverseNewval;
}
export function returnBoolean(val) {
const isBol = typeof val === "boolean";
const isString = typeof val === "string";
let newval = "";
if (isBol) {
newval = val;
}
if (isString) {
newval = val === "true" ? true : false;
}
return newval;
}

View File

@ -0,0 +1,32 @@
function getDayArea(date = new Date(), type) {
const now = date
if (type === 'start') {
const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate());
return startOfDay
}
if (type === 'end') {
const endOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999);
return endOfDay;
}
return `${startOfDay}-${endOfDay}`
}
function getMonthArea(date = new Date(), type) {
let now = date
let currentMonthStart = new Date(now.getFullYear(), now.getMonth(), 1);
let currentMonthEnd = new Date(now.getFullYear(), now.getMonth() + 1, 0 , 23, 59, 59, 999);
if (type === 'start') {
return currentMonthStart
}
if (type === 'end') {
return currentMonthEnd;
}
return {
start: currentMonthStart,
end: currentMonthEnd
};
}
export default {
getDayArea, getMonthArea
}

View File

@ -0,0 +1,13 @@
/**
* 获取url链接参数
* @param {Object} url
* @param {Object} name
*/
export function getQueryString(url, name) {
var reg = new RegExp('(^|&|/?)' + name + '=([^&|/?]*)(&|/?|$)', 'i')
var r = url.substr(1).match(reg)
if (r != null) {
return r[2]
}
return null;
}

109
commons/utils/go.js Normal file
View File

@ -0,0 +1,109 @@
/**
* 页面跳转工具类
*
* @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,37 @@
export function canComputedPackFee(v) {
return v.pack && v.status != 'return' && v.status != 'refund' && v.status != 'refunding'
}
export function returnCanComputedGoodsArr(arr) {
return arr.filter(v => canComputedPackFee(v))
}
export function returnPackFee(arr) {
return arr.reduce((prve, cur) => {
return prve + cur.packAmount
}, 0).toFixed(2)
}
export function canTuicai(orderInfo, item) {
if (orderInfo.status == 'unpaid' && orderInfo.isPostpaid !== null && orderInfo.isPostpaid == 0) {
return false
}
return orderInfo.status == 'unpaid' && orderInfo.useType != 'dine-in-before' && item.status != 'return'
}
export function canTuiKuan(orderInfo, item) {
return orderInfo.status == 'closed' && item.status != 'return' && item.status != 'refund' && item.status !=
'refunding'
}
export function isTuiCai(item) {
return item.status == 'return'
}
export function isTui(item) {
return item.status == 'return' || item.status == 'refund' || item.status == 'refunding'
}
export function isGift(item) {
return !isTui(item) && item.gift
}
export function numSum(arr) {
const sum = arr.reduce((a, b) => {
return a + b * 100
}, 0)
return (sum / 100).toFixed(2)
}

View File

@ -0,0 +1,163 @@
import {
$hasPermission
} from '@/http/yskApi/shop.js'
import infoBox from '@/commons/utils/infoBox.js'
const $PermissionObj = {
data: [{
key: 'yun_xu_cha_kan_jing_ying_shu_ju',
text: '允许查看经营数据'
},
{
key: 'yun_xu_cha_kan_suo_you_jiao_ban_ji_lu',
text: '允许查看所有交班记录'
}
],
default: [{
key: 'yun_xu_xia_dan',
text: '允许下单'
},
{
key: 'yun_xu_shou_kuan',
text: '允许收款'
},
{
key: 'yun_xu_tui_kuan',
text: '允许退款'
},
{
key: 'yun_xu_jiao_ban',
text: '允许交班'
}
],
goods: [{
key: 'yun_xu_xiu_gai_shang_pin',
text: '允许修改商品'
},
{
key: 'yun_xu_shang_xia_jia_shang_pin',
text: '允许上下架商品'
},
{
key: 'yun_xu_xiu_gai_fen_lei',
text: '允许修改分类'
},
{
key: 'yun_xu_xiu_gai_fen_zu',
text: '允许修改分组'
}
],
discount:[
{
key: 'yun_xu_da_zhe',
text: '允许打折'
}
],
vip:[
{
text: '允许管理会员信息',
key: 'yun_xu_guan_li_hui_yuan_xin_xi'
},
{
key: 'yun_xu_xiu_gai_hui_yuan_yu_e',
text: '允许修改会员余额'
}
],
stock:[
{
text: '允许提交报损',
key: 'yun_xu_ti_jiao_bao_sun'
},
{
text: '允许沽清',
key: 'yun_xu_gu_qing'
},
{
text: '允许售罄商品',
key: 'yun_xu_shou_qing_shang_pin'
},
{
text:'允许修改商品库存',
key:'yun_xu_xiu_gai_shang_pin_ku_cun'
},
{
text: '允许耗材入库',
key: 'yun_xu_hao_cai_ru_ku'
},
{
text: '允许耗材出库',
key: 'yun_xu_hao_cai_chu_ku'
},
{
text: '允许耗材盘点',
key: 'yun_xu_hao_cai_pan_dian'
}
]
}
function isChinese(str) {
for (var i = 0; i < str.length; i++) {
if (str.charCodeAt(i) >= 0x4E00 && str.charCodeAt(i) <= 0x9FFF) {
return true; // 是中文
}
}
return false; // 不是中文,全是英文或其他
}
function isObjectButNotArray(value) {
return typeof value === 'object' && Array.isArray(value) === false;
}
function findPermissionObj(str) {
for (let i in $PermissionObj) {
const obj = $PermissionObj[i].find(v => v.key == str || v.text == str)
if (obj) {
return obj
break
}
}
console.error('未找到相关权限配置请检查权限配置文件commons/utils/hasPermission.js文件进行修改或增加')
return false
}
function returnFormatParams(params) {
if (typeof params === 'string') {
return findPermissionObj(params)
} else {
if (isObjectButNotArray(params)) {
const obj=findPermissionObj(params.key || params.text)
return {...params,...obj}
} else {
console.error('参数只能是字符串或者对象,不能是数组')
}
}
}
/**
* 判断是否有某权限
* @param {Object|String} params
*/
export async function hasPermission(params) {
//如果是商户默认拥有全部权限
const loginType=uni.getStorageSync('loginType')
if(loginType=='merchant'){
return true
}
params = returnFormatParams(params)
if (!params) {
return infoBox.showToast('未找到相关权限请检查代码或在权限配置文件commons/utils/hasPermission.js文件进行修改或增加')
}
const option = Object.assign({
tips: true,
key: '',
text: ''
}, params)
const res = await $hasPermission({
code: params.key
})
if (!res && option.tips) {
infoBox.showToast('您没有' + params.text + '权限!')
}
return res
}

62
commons/utils/infoBox.js Normal file
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: false, 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张表。

1625
commons/utils/qrCode.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
export const objToArrary = (obj,keyNewName) => {
return Object.entries(obj).map(([key, value]) => ({
key,
[keyNewName]:key,
...value,
}))
}

View File

@ -0,0 +1,14 @@
import JSEncrypt from 'jsencrypt/bin/jsencrypt.min'
// 密钥对生成 http://web.chacuo.net/netrsakeypair
const publicKey = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANL378k3RiZHWx5AfJqdH9xRNBmD9wGD\n' +
'2iRe41HdTNF8RUhNnHit5NpMNtGL0NPTSSpPjjI1kJfVorRvaQerUgkCAwEAAQ=='
// 加密
export function encrypt(txt) {
const encryptor = new JSEncrypt()
encryptor.setPublicKey(publicKey) // 设置公钥
return encryptor.encrypt(txt) // 对需要加密的数据进行加密
}

View File

@ -0,0 +1,35 @@
import {
getCurrentInstance,
} from 'vue';
export async function getElRect(elClass, instance,option) {
instance = instance ? instance : getCurrentInstance();
const query = uni.createSelectorQuery().in(instance.proxy);
try{
const res= await getEle(query,elClass,option)
return res
}catch(e){
console.log(e);
}
}
async function getEle(query,elClass,option){
return new Promise((resolve, reject)=>{
query.select('.' + elClass).fields({
size: true,
...option
}, res => {
// 如果节点尚未生成res值为null循环调用执行
if (!res) {
return setTimeout(() => {
getEle(query,elClass,option);
}, 10);
}
resolve(res);
}).exec();
})
}
export async function getSafeBottomHeight(className, height = 16) {
const bottomEle = await getElRect(className)
return bottomEle.height + height
}

38
commons/utils/saveImg.js Normal file
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,290 @@
/**
* 存储管理对象
* 目标将现有系统的所有需要存储的数据统一管理
*
* @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 = {
setLogin(res){
const user=res.user.user
uni.setStorageSync('logoutHandle',false)
uni.setStorageSync('shopId', res.shopId)
uni.setStorageSync('shopName',res.shopName)
uni.setStorageSync('logo',res.logo)
uni.setStorageSync('loginType',res.loginType)
uni.setStorageSync('shopUserId',user.id)
if(res.loginType=='staff'){
uni.setStorageSync('merchantName',user.createBy||user.updateBy)
}
},
// 退出清空所有的缓存数据。 (不包含 环境相关)
cleanByLogout: () => {
// 1. 清空app级别缓存。
Object.keys(appCache).forEach(k => appCache[k] = null)
const merchantName=uni.getStorageSync('merchantName')
const MerchantId=uni.getStorageSync('MerchantId')
let envName = model.env() // 获取到当前的环境变量
uni.clearStorageSync() // 清除所有的缓存信息
uni.setStorageSync('merchantName',merchantName)
uni.setStorageSync('MerchantId',MerchantId)
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
}
},
// 获取和放置shopId
shopId: (val, isDelete = false) => {
if (isDelete) {
appCache.shopId = ""
return uni.removeStorageSync('shopId')
}
if (val) {
// 有值,为放置
appCache.shopId = val
uni.setStorageSync('shopId', val)
} else {
// 否则为获取
if (!appCache.shopId) {
//缓存取不到,获取应用本地信息
appCache.shopId = uni.getStorageSync('shopId')
}
return appCache.shopId
}
},
// 获取和放置店铺员工id
shopUserId: (val, isDelete = false) => {
if (isDelete) {
appCache.shopUserId = ""
return uni.removeStorageSync('shopUserId')
}
if (val) {
// 有值,为放置
appCache.shopUserId = val
uni.setStorageSync('shopUserId', val)
} else {
// 否则为获取
if (!appCache.shopUserId) {
//缓存取不到,获取应用本地信息
appCache.shopUserId = uni.getStorageSync('shopUserId')
}
return appCache.shopUserId
}
},
// 获取和放置useType就餐类型
useType: (val, isDelete = false) => {
if (isDelete) {
appCache.useType = ""
return uni.removeStorageSync('useType')
}
if (val) {
// 有值,为放置
appCache.useType = val
uni.setStorageSync('useType', val)
} else {
// 否则为获取
if (!appCache.useType) {
//缓存取不到,获取应用本地信息
appCache.useType = uni.getStorageSync('useType')
}
return appCache.useType
}
},
// 缓存代客下单商品
cacheGoods: (val, isDelete = false) => {
if (isDelete) {
appCache.cacheGoods = ""
return uni.removeStorageSync('cacheGoods')
}
if (val) {
// 有值,为放置
appCache.cacheGoods = val
uni.setStorageSync('cacheGoods', val)
} else {
// 否则为获取
if (!appCache.cacheGoods) {
//缓存取不到,获取应用本地信息
appCache.cacheGoods = uni.getStorageSync('cacheGoods')
}
return appCache.cacheGoods
}
},
// 缓存代客下单商品节点信息缓存
cacheGoodsNode: (val, isDelete = false) => {
if (isDelete) {
appCache.cacheGoodsNode = ""
return uni.removeStorageSync('cacheGoodsNode')
}
if (val) {
// 有值,为放置
appCache.cacheGoodsNode = val
uni.setStorageSync('cacheGoodsNode', val)
} else {
// 否则为获取
if (!appCache.cacheGoodsNode) {
//缓存取不到,获取应用本地信息
appCache.cacheGoodsNode = uni.getStorageSync('cacheGoodsNode')
}
return appCache.cacheGoodsNode
}
},
// 已经登录的用户记录
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

29
commons/utils/throttle.js Normal file
View File

@ -0,0 +1,29 @@
/**
* @desc 函数节流
* @param func 函数
* @param wait 延迟执行毫秒数
* @param type 1 使用表时间戳在时间段开始的时候触发 2 使用表定时器在时间段结束的时候触发
*/
export const throttle = (func, wait = 1000, type = 1) => {
let previous = 0;
let timeout;
return function() {
let context = this;
let args = arguments;
if (type === 1) {
let now = Date.now();
if (now - previous > wait) {
func.apply(context, args);
previous = now;
}
} else if (type === 2) {
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
func.apply(context, args)
}, wait)
}
}
}
}

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
}

54
commons/utils/timer.js Normal file
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,14 @@
export default {
get(key) {
return uni.getStorageSync(key)
},
set(key, value) {
uni.setStorageSync(key, value)
},
del(key) {
uni.removeStorageSync(key)
},
clear() {
uni.clearStorageSync()
}
}

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')
}
}

1
commons/week.js Normal file
View File

@ -0,0 +1 @@
export const $weeks=['周一','周二','周三','周四','周五','周六','周日'];

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: [], // appweb 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,118 @@
<!--
背景模板
使用方式
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" style="border-radius:0;background-color: #318AFE!important;">
<view class="bgbottomStyle">
</view>
</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;
}
.bgbottomStyle{
position: absolute;
bottom: -2rpx;
left: 0;
width: 750rpx;
height: 74rpx;
background: linear-gradient( 180deg, rgba(195,215,235,0) 0%, #F9F9F9 100%);
}
</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,165 @@
<!--
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'
import {
hasPermission
} from '@/commons/utils/hasPermission.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: () => [] },
})
//
async function clickFunc(nav) {
if(nav.pageUrl=="PAGES_SALES_SUMMARY"){
let res =await hasPermission('允许查看经营数据')
if(!res) return
}
//
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: [], // appweb 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: "",
},
//,01
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,212 @@
<template>
<my-model ref="model" :title="title" iconColor="#000" @close="resetForm">
<template #desc>
<view class="u-text-left u-p-30 color-666">
<view class="u-m-t-32 u-flex ">
<view>应付金额</view>
<view class="u-m-l-32">
{{form.price}}
</view>
</view>
<view class="u-m-t-40 u-flex ">
<view>实收金额</view>
<view class="u-m-l-32 border u-p-l-10 u-p-r-10 u-flex-1">
<uni-easyinput type="number" @input="currentPriceInput" @change="currentPriceChange" paddingNone :inputBorder="false"
v-model="form.currentPrice"
placeholder="输入实际金额"></uni-easyinput>
</view>
</view>
<view class="u-m-t-54 u-flex ">
<view>优惠折扣</view>
<view class="u-m-l-32 u-flex-1 u-flex border u-p-l-10 u-p-r-10">
<view class="u-flex-1">
<uni-easyinput type="number" @input="discountInput" @change="discountChange" paddingNone :inputBorder="false"
v-model="form.discount"
placeholder="输入折扣"></uni-easyinput>
</view>
<view class="u-font-32 color-333">%</view>
</view>
</view>
</view>
</template>
<template #btn>
<view class="u-p-30">
<view class="u-m-t-10">
<my-button @tap="confirm" shape="circle" fontWeight="700" >修改</my-button>
<view class="">
<my-button @tap="close" type="cancel" bgColor="#fff" >取消</my-button>
</view>
</view>
</view>
</template>
</my-model>
</template>
<script setup>
import {
reactive,
nextTick,
ref,watch
} from 'vue';
import myModel from '@/components/my-components/my-model.vue'
import myButton from '@/components/my-components/my-button.vue'
import myTabs from '@/components/my-components/my-tabs.vue'
import infoBox from '@/commons/utils/infoBox.js'
const props = defineProps({
title: {
type: String,
default: ''
},
data: {
type: Array,
default: []
},
discount:{
type: [Number,String],
default:100
},
price: {
type: [Number,String],
default: 0
},
nowPrice:{
type: [Number,String],
default: 0
}
})
function currentPriceInput(newval){
form.discount=(newval*100/form.price)
}
function discountInput(newval){
form.currentPrice=(form.price*newval/100).toFixed(2)
}
function currentPriceChange(newval){
if(newval<0){
form.currentPrice=0
form.discount=100
return infoBox.showToast('实收金额不能小于0')
}
if(newval>props.price){
form.currentPrice=props.price
form.discount=0
return infoBox.showToast('实收金额不能大于应付金额')
}
}
function discountChange(newval){
if(newval<0){
form.currentPrice=props.price
form.discount=0
return infoBox.showToast('优惠折扣不能小于0')
}
if(newval>100){
form.discount=100
form.currentPrice=0
return infoBox.showToast('优惠折扣不能大于100')
}
}
const $form = {
price:props.price,
currentPrice: props.price,
discount: 100
}
const form = reactive({
...$form
})
watch(()=>props.price,(newval)=>{
console.log(newval);
form.price=newval
form.currentPrice=newval
})
function resetForm() {
Object.assign(form, {
...$form
})
}
const model = ref(null)
function open() {
model.value.open()
form.price=props.price
form.discount=props.discount
form.currentPrice=(props.discount*props.price/100).toFixed(2)
}
function close() {
model.value.close()
}
const emits = defineEmits(['confirm'])
function confirm() {
console.log(form);
emits('confirm',{...form,currentPrice:Number(form.currentPrice).toFixed(2)})
close()
}
defineExpose({
open,
close
})
</script>
<style lang="scss" scoped>
.border{
border-radius: 8rpx;
overflow: hidden;
border-color: #999;
}
.lh34 {
line-height: 34rpx;
}
.tag {
background-color: #fff;
border: 1px solid #E5E5E5;
line-height: inherit;
font-size: 24rpx;
color: #666666;
padding: 6rpx 20rpx;
border-radius: 8rpx;
&.active {
border-color: #E6F0FF;
color: $my-main-color;
}
}
.hover-class {
background-color: #E5E5E5;
}
.discount {
.u-absolute {
top: 0;
bottom: 0;
right: 0;
}
}
.bg1 {
background: #F7F7FA;
}
.tab {
padding: 0 80rpx;
}
.border {
border: 1px solid #E5E5E5;
border-radius: 4rpx;
}
.input-box {
padding: 22rpx 32rpx;
font-size: 28rpx;
color: #666;
}
.placeholder-class {
font-size: 28rpx;
}
</style>

View File

@ -0,0 +1,107 @@
<template>
<view class="action-sheet" @tap="close" v-if="show">
<view class="box">
<slot name="title">
</slot>
<view class="item" @tap.stop="itemClick(index)" v-for="(item,index) in props.list" :key="index">
<button class="bg-fff btn" hover-class="btn-hover-class" :class="{'color-main':active==index}">{{item}}</button>
</view>
<view class="bock-gary"></view>
<view class="cancel-btn" @tap="close">
<button>取消</button>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
const props=defineProps({
//
active:{
type:Number,
default:-1
},
autoClose:{
type:Boolean,
default:true
},
title:{
type:String,
default:''
},
list:{
type:Array,
default:['编辑','删除']
},
show:{
type:Boolean,
default:false
}
})
const emits=defineEmits(['itemClick','close'])
let show=ref(false)
function open(){
show.value=true
}
function close(){
show.value=false
emits('close')
}
function itemClick(index){
if(props.autoClose){
close()
}
emits('itemClick',index)
}
defineExpose({
open,close
})
</script>
<style lang="scss">
$bg:rgb(240, 240, 240);
.action-sheet{
position: fixed;
left: 0;
right: 0;
bottom: 0;
top: 0;
background-color: rgba(51,51,51,.5);
z-index: 999;
.box{
position: absolute;
left: 0;
right: 0;
bottom: 0;
border-radius: 20rpx 20rpx 0rpx 0rpx;
overflow: hidden;
background-color: #fff;
/* #ifndef H5 */
padding-bottom: calc(24rpx + constant(safe-area-inset-bottom));
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
/* #endif */
.item{
text-align: center;
font-size: 32rpx;
border-bottom: 1px solid $bg;
.btn{
border-radius: 0;
}
}
}
}
.bock-gary{
background-color: $bg;
height: 20rpx;
}
.cancel-btn{
text-align: center;
font-size: 32rpx;
}
.btn-hover-class{
background-color: rgb(222, 222, 222);
}
</style>

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