代码更新

This commit is contained in:
GaoHao
2025-02-26 19:46:20 +08:00
parent 7519ffced3
commit b4a0393d2d
413 changed files with 7483 additions and 60762 deletions

View File

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

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

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

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

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

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

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

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

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

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

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

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

View File

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

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

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

@@ -1,118 +0,0 @@
<!--
背景模板
使用方式
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

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

@@ -1,143 +0,0 @@
<!--
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

@@ -1,144 +0,0 @@
<!--
通用的选择某项业务 一般用作修改页
目前支持 门店 应用
@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

@@ -1,90 +0,0 @@
<!--
组件功能 卡片布局
标题 副标题 和编辑按钮及插槽
注意 不包含 中间的内容
@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

@@ -1,80 +0,0 @@
<!--
组件功能 解决原生组件双向绑定问题
@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

@@ -1,159 +0,0 @@
<!--
组件功能 自定义导航栏包裹 , 基于 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

@@ -1,32 +0,0 @@
<!--
描述信息展示
通用信息
@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

@@ -1,77 +0,0 @@
<!--
描述信息展示
通用信息
@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

@@ -1,151 +0,0 @@
<!-- 待完成 -->
<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

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

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

@@ -1,91 +0,0 @@
<!--
信息列表的预览页面 比如 通知消息的前几条等
通用信息
@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

@@ -1,131 +0,0 @@
<!--
登录页专用文字上移样式行高宽度密码框与通用样式不一致
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

@@ -1,110 +0,0 @@
<!--
通用 确认弹层
@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

@@ -1,91 +0,0 @@
<!--
组件作用 单个输入框的弹层
输入框的弹出框 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

@@ -1,361 +0,0 @@
<!--
组件作用 弹层 列表支持 单选 多选
使用方法
<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

@@ -1,387 +0,0 @@
<!--
组件作用 弹层 列表支持 单选 多选
使用方法
<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

@@ -1,116 +0,0 @@
<!--
单选 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

@@ -1,134 +0,0 @@
<!--
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

@@ -1,355 +0,0 @@
<!--
组件作用 弹层 列表支持 单选 多选
使用方法
<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

@@ -1,74 +0,0 @@
<!--
组件作用 状态筛选器 一般用作搜索栏右侧
使用方法
@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

@@ -1,79 +0,0 @@
<!--
组件作用 状态筛选器 一般用作搜索栏右侧
使用方法
@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

@@ -1,119 +0,0 @@
<!--
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

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

View File

@@ -1,90 +0,0 @@
<!--
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

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

@@ -1,167 +0,0 @@
<!--
图片上传组件
@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

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

View File

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

@@ -5,9 +5,8 @@
</template>
<script setup>
import {
$uploadFile
} from '@/http/yskApi/file.js'
import { uploadFile } from '@/api/index.js'
import {
reactive,
ref,
@@ -64,10 +63,11 @@
function FileUploadselect(e) {
for (let i in e.tempFiles) {
const file = e.tempFiles[i]
$uploadFile(file).then(res => {
console.log(e)
uploadFile(file).then(res => {
console.log(res);
imgList.value.push({
url: res.data[0],
url: res,
path: file.path
})
}).catch(res=>{