源文件

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

View File

@@ -0,0 +1,137 @@
<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="toService">用户服务协议</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'
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 uni.navigateTo(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()
}
const toService = () => {
uni.navigateTo({ url: props.service })
}
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: $primaryColor;
}
}
.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: $primaryColor;
color: #fff;
padding: 0;
}
}
}
</style>

View File

@@ -0,0 +1,138 @@
<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="toService">用户服务协议</text>
<text class="info" @tap="toPrivacy">隐私政策</text>
<text>如你同意用户服务协议隐私政策请点击同意</text>
<text>开始使用 {{ $appName }}小程序</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'
const props = defineProps({
service: { type: String },
privacy: { type: String }
})
const openFlag=ref(false)
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 uni.navigateTo(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()
}
const toService = () => {
uni.navigateTo({ url: props.service })
}
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: $primaryColor;
}
}
.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: $primaryColor;
color: #fff;
padding: 0;
}
}
}
</style>

View File

@@ -0,0 +1,107 @@
<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>
<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>
<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 可传值复写
})
// 引入依赖 解决滚动穿透问题 配合JeepayWrapper 使用
const wrapperHidden = inject('wrapperHidden')
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)
}
}
const change = (e) => {
if (wrapperHidden) {
wrapperHidden(e.show)
}
}
const close = () => popup.value.close()
defineExpose({ open, close })
</script>
<style lang="scss" scoped>
.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;
color: #4d4d4d;
}
.line {
height: 20rpx;
background-color: rgba(0, 0, 0, 0.07);
}
.tips-cancel {
height: 110rpx;
color: #808080;
font-size: 33rpx;
border: none;
}
.u-cell-hover {
background-color: #f8f9fa;
}
}
</style>

View File

@@ -0,0 +1,123 @@
<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;
line-height: 110rpx;
text-align: center;
font-size: 26rpx;
color: #a6a6a6;
&::after,
&::before {
content: '';
display: block;
position: absolute;
top: 50%;
width: 30%;
height: 2rpx;
background-color: #ededed;
transform: translateY(-50%);
}
&::after {
left: 40rpx;
}
&::before {
right: 40rpx;
}
}
</style>

View File

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

View File

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

View File

@@ -0,0 +1,57 @@
<template>
<swiper
class="swiper"
circular
:indicator-dots="true"
autoplay
:interval="interval * 1000"
:duration="500"
:style="{margin:mg}"
>
<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] },
mg:{type: String, default:'40rpx'}
});
const toH5 = (url) => {
if (!url) return;
uni.navigateTo({
url: '/pages/adH5/adH5?url=' + url,
});
};
</script>
<style lang="scss" scoped>
.uni-margin-wrap {
width: 690rpx;
width: 100%;
}
.swiper {
margin: 40rpx;
height: 300rpx;
border-radius: 30rpx;
overflow: hidden;
}
.swiper-item {
display: block;
height: 300rpx;
line-height: 300rpx;
text-align: center;
}
image {
width: 100%;
}
</style>

View File

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

View File

@@ -0,0 +1,103 @@
<!--
通用 确认弹层
@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"> {{ 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 wrapperHidden = inject('wrapperHidden')
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
})
}
const change = (e) =>{
if(wrapperHidden){
wrapperHidden(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 {
text-align: center;
height: 110rpx;
line-height: 110rpx;
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: #808080;
font-size: 33rpx;
}
.u-cell-hover {
background-color: #f8f9fa;
}
}
</style>

View File

@@ -0,0 +1,358 @@
<!--
组件作用 弹层 列表支持 单选 多选
使用方法
<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({
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) {
// 清空数据
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: 0;
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: $primaryColor;
}
}
}
}
}
</style>

View File

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

View File

@@ -0,0 +1,426 @@
<template>
<view>
<view class="default">
<!-- 删除按钮 -->
<view class="clear" v-if="vdata.hasImg && isShowClear && userIsShowClear">
<!-- -->
<image src="../../static/img/close.svg" @click.stop="logoutPopup.open()" mode="aspectFill"></image>
<!-- <image src="../../static/img/clear.svg" @click.stop="logoutPopup.open()"></image> -->
</view>
<view @click="previewImg" class="img-bg" v-if="vdata.hasImg">
<image :src="vdata.assemblyUrl" class="img-suc" mode="aspectFill"></image>
</view>
<image
v-if="!vdata.hasImg"
class="camera"
@click="chooseImg(['album', 'camera'])"
src="/static/iconImg/upload-picture.svg"
></image>
</view>
<!-- 图片预览手写非原生 -->
<enlarge
v-if="isPreview"
:changeIsShow="props.isShowClear"
:imgs="vdata.assemblyUrl"
@enlargeClose="enlargeClose"
@chooseImg="chooseImg"
/>
<!-- 删除图片二次确认 -->
<uni-popup ref="logoutPopup" type="dialog">
<uni-popup-dialog mode="base" :before-close="true" @close="logoutPopup.close()" @confirm="clear">
<template #title>
<view class="tip-content">确定要删除该图片吗</view>
</template>
</uni-popup-dialog>
</uni-popup>
</view>
</template>
<script setup>
import appConfig from "@/config/appConfig.js"
import { ref, reactive, watch, onMounted, nextTick } from "vue"
import { $ossFilesForm, $imgInfoDetail } from "@/http/apiManager.js"
import storageManage from "@/util/storageManage.js"
import enlarge from "./enlarge.vue" // 图片预览
import useBackPress from "@/hooks/useBackPress.js" // 返回阻断函数
import { sm4DecryptByResData } from "@/util/encryptUtil.js"
const logoutPopup = ref(null) // 二次确认弹框
const props = defineProps({
upLoadUrl: { type: String, default: "/api/ossFiles/singleFile" }, // 默认图片
imgUrl: { type: String, default: "" }, // 默认图片
recogObjectType: { type: String, default: "" }, // 识别对象类型idcard, idcard_back, bankcard, business_license
uploadCount: { type: Number, default: 1 }, // 上传图片张数
size: { type: Number, default: 2 }, // 图片限制大小,单位M
imgTypes: { type: Array, default: () => ["jpg", "jpeg", "png"] }, // 图片类型限制,如: ['jpg', 'jpeg']
isShowClear: { type: Boolean, default: true }, // 进件成功不展示删除按钮与更换按钮
ocrFlag: { type: String, default: "" }, // ocr识别标志图片类型 idCard-身份证bankCard-银行卡, license-营业执照
userIsShowClear: { type: Boolean, default: true }, // 在其余页面中是否展示删除按钮
})
onMounted(() => {
if (props.imgUrl) {
vdata.hasImg = true
vdata.assemblyUrl = props.imgUrl
}
})
// 关闭图片预览
const enlargeClose = () => {
isPreview.value = false
// #ifdef H5 || APP-PLUS
inactive()
// #endif
}
const vdata = reactive({
hasImg: false,
assemblyUrl: "",
})
watch(
() => props.imgUrl,
(newVal, oldVal) => {
nextTick(() => {
newVal ? (vdata.hasImg = true) : (vdata.hasImg = false)
vdata.assemblyUrl = newVal
})
}
)
const emit = defineEmits(["uploadSuccess", "clear"])
// OCR识别函数
function imgInfoDetail(res) {
$imgInfoDetail({
imgUrl: res.data,
type: props.ocrFlag,
}).then(({ bizData }) => {
if (Object.keys(bizData).length < 1) {
// uni.showToast({
// title: '图片识别失败',
// icon: 'none'
// })
} else {
uni.showToast({
title: "图片识别成功,已自动回填内容,请检查核对",
icon: "none",
})
}
res.ocrInfo = bizData
return emit("uploadSuccess", res)
})
}
function chooseImg(index) {
console.log(index)
if (props.isShowClear) {
uni.chooseImage({
count: props.uploadCount,
sizeType: ["compressed"],
sourceType: index,
crop: { quality: 20 },
success: (chooseImageRes) => {
isPreview.value = false //关闭图片预览
const msg = beforeUpload(chooseImageRes)
if (msg != "success") return uni.showToast({ title: msg, icon: "none" })
// 根据form信息判断图片上传地址
// 此处额外验证上传的图片是否含有name属性如果没有则用path代替
let sourceFileName
if (chooseImageRes.tempFiles[0].name) {
sourceFileName = chooseImageRes.tempFiles[0].name
} else {
sourceFileName = chooseImageRes.tempFiles[0].path
}
$ossFilesForm({
bizType: "applyment",
sourceFileName: sourceFileName,
sourceFileSize: chooseImageRes.tempFiles[0].size,
}).then(({ bizData }) => {
let url,
isOss,
ossImgUrl = ""
if (bizData.formActionUrl === "LOCAL_SINGLE_FILE_URL") {
url = appConfig.env.JEEPAY_BASE_URL + props.upLoadUrl
isOss = false
} else {
url = bizData.formActionUrl
ossImgUrl = { data: bizData.ossFileUrl }
isOss = true
}
// 调用图片上传方法
uploadImg(chooseImageRes.tempFilePaths, url, bizData.formParams, isOss, ossImgUrl)
console.log(url,'bizData.formParamsbizData.formParamsbizData.formParams');
})
},
})
}
}
// 上传图片
function uploadImg(tempFilePaths, url, formParams, isOss, ossImgUrl) {
const token = storageManage.token()
var successCount = 0 //多图时,上传成功数量
var qualification = [] //多图存储
uni.showLoading({ title: "上传中..." })
//循环上传图片
tempFilePaths.forEach((tempFilePath) => {
console.log(tempFilePath);
uni.uploadFile({
url: url,
filePath: tempFilePath,
name: "file",
header: {
itoken: token,
},
formData: formParams,
// 代表完成的函数 注:此处可以传入三个函数 success (成功)/ fail失败 / complete (完成)
complete(res) {
uni.hideLoading()
// oss接口特有情况
if (isOss) {
if (res.statusCode != 200) {
return uni.showToast({ title: "请求失败,请重试", icon: "none" })
}
// 如果存在OCR 识别信息那么要调用ocr识别函数
if (props.ocrFlag) return imgInfoDetail(ossImgUrl)
emit("uploadSuccess", ossImgUrl)
return (vdata.hasImg = true)
}
// res 即接口返回数据包
let {
statusCode, // 状态码
data, // 数据
msg, // 信息
errMsg, // 错误信息
} = res
// 接收请求,执行响应操作
if (statusCode != 200) {
uni.showToast({
title: "请求失败请重试statusCode" + statusCode,
icon: "none",
mask: true,
duration: 2000,
})
return
}
if (typeof data == "string") {
data = JSON.parse(data)
}
// 图片上传解密
if (data.encryptData) {
data.data = sm4DecryptByResData(data.encryptData)
}
//状态码为1001(未登录)
if (data.code == 1001) {
uni.showToast({
title: "请登录",
icon: "none",
mask: true,
duration: 1500,
})
setTimeout(function () {
uni.reLaunch({
url: "/pages/login/login",
})
}, 1500)
return
}
if (data.code != 0) {
uni.showToast({
title: data.msg || "请求失败,请重试",
duration: 2000,
mask: true,
icon: "none",
})
return
}
// 如果存在OCR 识别信息那么要调用ocr识别函数
if (props.ocrFlag) return imgInfoDetail(data)
// 没有遇到以上的情况
emit("uploadSuccess", data)
vdata.hasImg = true
},
})
})
}
// 上传图片前的校验
function beforeUpload(chooseImageRes) {
const tempFiles = chooseImageRes.tempFiles
let msg = "success"
if (tempFiles) {
for (let i = 0; i < tempFiles.length; i++) {
// 图片地址
let tempUrl = tempFiles[i].name || tempFiles[i].path
if (!tempUrl) {
msg = "请选择图片"
return msg
}
// 校验图片大小
if (tempFiles[i].size > props.size * 1024 * 1024) {
msg = "单张图片请不超过" + props.size + "MB"
return msg
}
// 校验后缀
if (!checkSuffix(tempUrl)) {
msg = "仅支持【" + props.imgTypes + "】格式的图片"
return msg
}
}
} else {
msg = "请选择图片"
return msg
}
if (chooseImageRes.tempFilePaths.length > props.uploadCount) {
msg = "最多上传" + props.uploadCount + "张图片"
return msg
}
return msg
}
// 校验图片后缀
function checkSuffix(fileName) {
const suffix = getFileSuffix(fileName).toLowerCase()
if (props.imgTypes.includes(suffix)) {
return true
}
return false
}
// 获取图片后缀
function getFileSuffix(fileName) {
if (!fileName || fileName.indexOf(".") < 0 || fileName.length <= 1) {
return ""
}
return fileName.substring(fileName.lastIndexOf(".") + 1)
}
let isPreview = ref(false) // 图片预览是否展示
// 预览图片
function previewImg() {
isPreview.value = true
// #ifdef H5 || APP-PLUS
active()
// #endif
// onceVar.imgView(true)
// let urls = [];
// urls.push(props.imgUrl)
// uni.previewImage({
// urls: urls
// });
}
// 叉号 清除图片
function clear() {
vdata.hasImg = false
emit("clear")
logoutPopup.value.close()
}
// #ifdef H5 || APP-PLUS
const { active, inactive } = useBackPress(enlargeClose) // onBackPress 阻断返回
// #endif
</script>
<style scoped lang="scss">
// 图片预览
.preview-img {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: #000;
z-index: 25;
}
.camera {
width: 50rpx;
height: 50rpx;
}
.default {
position: relative;
width: 120rpx;
height: 120rpx;
background-color: #fff;
border-radius: 5rpx;
display: flex;
align-items: center;
justify-content: center;
border: 1rpx dashed #cccdd9;
}
.img-bg {
position: relative;
width: 120rpx;
height: 120rpx;
background: #f1f1f1;
border-radius: 12rpx;
display: flex;
justify-content: center;
align-items: center;
z-index: 10;
/* margin-right: 30rpx; */
image {
height: 100%;
width: 100%;
}
}
.img-def {
width: 43rpx;
height: 37rpx;
}
.img-suc {
width: 100%;
/* height: 180rpx; */
border-radius: 12rpx;
}
.clear {
position: absolute;
top: -10rpx;
left: 90rpx;
width: 36rpx;
height: 36rpx;
border-radius: 50%;
z-index: 12;
background: rgba(0, 0, 0, 0.7);
display: flex;
align-items: center;
justify-content: center;
image {
width: 30rpx;
height: 30rpx;
}
}
.tip-content {
display: flex;
justify-content: center;
align-items: center;
height: 120rpx;
font-size: 32rpx;
font-weight: 700;
color: #3981ff;
}
</style>

View File

@@ -0,0 +1,171 @@
<template>
<view class="previewImage" :style="{ 'background-color': 'rgba(0,0,0,' + opacity + ')' }" @tap="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="aspectFill"
/>
</movable-view>
</movable-area>
<view v-if="changeIsShow" class="change-img">
<view class="change-item" style="border-bottom: 2rpx solid #1a1a1a" @click="chooseImg(['camera'])">
<image src="/static/iconImg/photograph.svg"></image>
<text>拍照</text>
</view>
<view class="change-item" @click="chooseImg(['album'])">
<image src="/static/iconImg/album.svg"></image>
<text>从相册选择</text>
</view>
</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(type) {
this.$emit("chooseImg", type)
},
//旋转
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;
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: 692rpx;
height: 692rpx;
border-radius: 23rpx;
left: 29rpx;
top: 192rpx;
overflow: hidden;
.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-align: center;
}
.text:active {
background-color: rgba(100, 100, 100, 0.5);
}
}
}
.change-img {
position: fixed;
width: 385rpx;
height: 212rpx;
background: rgba(255, 255, 255, 0.15);
bottom: 5%;
left: 50%;
transform: translateX(-50%);
z-index: 30;
border-radius: 15rpx;
.change-item {
width: 100%;
height: 106rpx;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
image {
width: 31rpx;
height: 31rpx;
margin-right: 10rpx;
}
text {
font-weight: 500;
font-size: 33rpx;
color: #fff;
}
}
}
</style>

View File

@@ -0,0 +1,27 @@
<template>
<view class="main" :class="{ 'main-hidden': vdata.hidden }">
<slot />
</view>
</template>
<script setup>
import { reactive, provide } from 'vue'
const vdata = reactive({
hidden: false,
})
const wrapperHidden = (e) => {
return (vdata.hidden = e)
}
provide('wrapperHidden', wrapperHidden)
</script>
<style lang="scss" scoped>
.main {
min-height: 100vh;
}
.main-hidden {
height: 99vh;
overflow: hidden;
}
</style>

View File

@@ -0,0 +1,120 @@
<template>
<view class="item" :style="{backgroundColor: props.backColor}" :class="[props.border]">
<!-- 左侧内容 即标题 -->
<view class="title">
<text v-if="props.start" class="require">*</text>
<text style="color:#666f80"> {{ props.text }}</text>
</view>
<!-- 右侧内容 父组件替换此插槽可以转变为上传图片等左右结构的布局 -->
<slot>
<input type="text" :value="props.value" @input="onChange" :placeholder="placeholderText" :disabled="props.disabled" />
</slot>
</view>
<view v-if="props.tipText" class="tipText">
({{ props.tipText }})
</view>
</template>
<script setup>
import { ref } from 'vue'
const props = defineProps({
value: {type: String, default: ''},
text: {type: String, default: ''},
tipText: {type: String, default: ''}, // 自定义的提示文字
start: {type: Boolean, default: true }, // 代表必填项的小星星
disabled: {type: Boolean, default: false}, // 是否禁止填写
backColor: {type: String, default: '#fafbfc'}, // 背景颜色
border: {type: String, default: 'top-border'}, // border位置 上或下
tipHolder: {type: String, default: ''} // 自定义placeholder
})
// 自定义placeholder
let placeholderText = props.tipHolder ? props.tipHolder : `请输入${props.text}`
const emit = defineEmits(['update:value'])
const onChange = e => emit('update:value', e.detail.value)
</script>
<style scoped lang="scss">
.item{
box-sizing: border-box;
//height: 110rpx;
width: 100%;
// background-color: #fafbfc;
display: flex;
justify-content: space-between;
align-items: flex-start;
padding: 35rpx 30rpx 35rpx 30rpx;
font-weight: 500;
font-size: 28rpx;
text-align: left;
color: #000;
.title{
text-align: left;
font-weight: 500;
font-size: 28rpx;
color: #666f80;
margin-right: 20rpx;
white-space: nowrap;
}
input, textarea {
flex-grow: 1;
text-align: right;
font-size: 28rpx;
}
.imgupload{
position: relative;
height: 120rpx;
width: 120rpx;
background-color: #E6E9ED;
border-radius: 10rpx;
display: flex;
align-items: center;
justify-content: center;
}
.delate{
position: absolute;
top: 70rpx;
left: -20rpx;
height: 40rpx;
width: 40rpx;
border-radius: 5rpx;
background-color: #ff0000;
}
}
// 星号
.require {
width: 20rpx;
color: red;
}
// 禁止标题换行
.title {
display: flex;
flex-direction: row;
text {
white-space: nowrap;
}
}
.top-border {
border-top: 1rpx solid #e8e8e8;
}
.bottom-border {
border-bottom: 1rpx solid #e8e8e8;
}
.tipText {
margin: 8rpx 20rpx 10rpx;
color: #808080;
}
</style>

View File

@@ -0,0 +1,16 @@
## 进件板块通用组件
## JeePayForm
- 通用的左右结构布局
- 单标签模式适用于普通输入框
- 通过传参,适用于文本域
- 通过插槽,适用于进件板块绝大部分左右结构布局
## termOfValidity
- 选择证件的有效期
- 默认是选结束时间
## dataPicker
- 由uni官方的 uni-data-picker二次封装而来
- 主要适用于进行选择行业mcc 省市区县等级联选择框
- 由于不同进件渠道所需要的值不同,需要根据不同的参数来返回不同的值 有的行业mcc码只要最后一个有的需要用_拼接 (省市区,有的只选择到市,绝大部分需要选择到县)

View File

@@ -0,0 +1,105 @@
<template>
<view>
<!-- 为了兼容微信小程序所以在此根据v-if写了两遍 v-model和value 也写了两边 -->
<uni-data-picker ref="dataPickerRef" v-if="props.code" :localdata="props.localdata" v-model="props.code" :value="props.code" @change="dataChange" v-slot:default="{data, error, options}" @popupopened="popupopened" @popupclosed="popupclosed" :map="props.mapText">
{{ data.length != 0 ? dataText(data) : '请选择' }}
</uni-data-picker>
<uni-data-picker ref="dataPickerRef" v-if="!props.code" :localdata="props.localdata" v-model="props.code" :value="props.code" @change="dataChange" v-slot:default="{data, error, options}" @popupopened="popupopened" @popupclosed="popupclosed" :map="props.mapText">
{{ data.length != 0 ? dataText(data) : '请选择' }}
</uni-data-picker>
</view>
</template>
<script setup>
import { ref } from 'vue'
import useBackPress from '@/hooks/useBackPress.js' // 返回阻断函数
const props = defineProps({
code: {type: String, default: ''}, // 文字回显用到的码
localdata: {type: Array, default: () => [] }, // 级联选择用到的数据
paramType: {type: String, default: 'arr' }, // 参数类型
mapText: {type: Object, default:{text:'text',value:'value'}} // 映射字段
})
const emit = defineEmits(['change'])
// 保持与其他数据参数格式一致,所以也改成 e.detail.value的方式
let data = {
detail: {
value: ''
}
}
const dataPickerRef = ref()
/*
* 参数类型集合 默认为arr
* splicing 拼接模式 适用于支付宝官方行业mccCode 例mccCode":"A0002_B0015"
* arr 数组模式 大部分省市区县选择,都需要数组 例 areaCode":["120000","120100","120112"]
* last 单值模式 适用于盛付通进件mccCode它只需要最后一个code 例mccCode":"4900"
* oneLevel 级联选择 只有一级 适用于云闪付mcc 拉卡拉pos类型等
*/
// 向父组件传递选择后的数据
const dataChange = e => {
let result = '' // 拿code值用
let resText = '' // 拿文字用
// 根据参数不同走不同分支
if (props.paramType === 'splicing') { // splicing 拼接
result = e.detail.value[0].value + '_' + e.detail.value[1].value
}
else if (props.paramType === 'arr') { // arr 数组
result = []
resText = []
e.detail.value.forEach(item => {
result.push(item.value)
resText.push(item.text)
})
}
else if (props.paramType === 'last') { // last 单值
result = e.detail.value[e.detail.value.length - 1].value
}
else if (props.paramType === 'oneLevel') { // oneLevel 只有一级
result = e.detail.value[0].value
}
// 父组件在拿到值之后,可以统一走 e.detail.value的方式拿值了
data.detail.value = result
data.detail.text = resText
emit('change', data)
}
// data-pciker 回显处理函数
const dataText = (data) => {
let result = ''
data.forEach(item => result += (item.text + '/'))
result = result.substr(0, result.length -1 ) // 截取掉最后一个斜杠
return result
}
/*
处理开启弹窗 与 关闭弹窗 的 阻断返回
*/
const popupopened = () => {
// #ifdef H5 || APP-PLUS
active()
// #endif
}
const popupclosed = () => {
// #ifdef H5 || APP-PLUS
inactive()
// #endif
}
const closeEd = () => dataPickerRef.value.hide()
// #ifdef H5 || APP-PLUS
const {active, inactive} = useBackPress(closeEd) // onBackPress 阻断返回
// #endif
</script>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,44 @@
<template>
<view>
<picker mode="date" fields="day" :value="props.defaultDate" @change="bindDateChange" v-show="props.defaultDate != '长期'">
<view class="uni-input" style="text-align: right;">{{props.defaultDate ? props.defaultDate : '请选择'}}</view>
</picker>
<checkbox-group @change="end" v-if="props.isEnd">
<label><checkbox value="长期" :checked="props.defaultDate == '长期' " color="#FFCC33" />长期</label>
</checkbox-group>
</view>
</template>
<script setup>
import { ref } from 'vue'
import useBackPress from '@/hooks/useBackPress.js' // 返回阻断函数
const props = defineProps({
isEnd: { type: Boolean, default: true } , // 默认为选择结束时间
defaultDate: {type: String, default: ''}, // 传入的默认时间
})
const emit = defineEmits(['publicSelect'])
// 保持与其他数据参数格式一致,所以也改成 e.detail.value的方式
let data = {
detail: {
value: ''
}
}
// 普通日期选择
const bindDateChange = e => emit('publicSelect', e)
// 点击长期
const end = e => {
data.detail.value = e.detail.value[0] ? '长期' : ''
emit('publicSelect', data)
}
</script>
<style scoped lang="scss">
</style>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,124 @@
[{
"value": "0323",
"text": "上海华瑞银行"
}, {
"value": "A000",
"text": "境外银行"
}, {
"value": "0102",
"text": "中国工商银行"
}, {
"value": "0103",
"text": "中国农业银行"
}, {
"value": "0104",
"text": "中国银行"
}, {
"value": "0105",
"text": "中国建设银行"
}, {
"value": "0301",
"text": "交通银行"
}, {
"value": "0308",
"text": "招商银行"
}, {
"value": "0309",
"text": "兴业银行"
}, {
"value": "0305",
"text": "中国民生银行"
}, {
"value": "0306",
"text": "广东发展银行"
}, {
"value": "0307",
"text": "平安银行股份有限公司"
}, {
"value": "0310",
"text": "上海浦东发展银行"
}, {
"value": "0304",
"text": "华夏银行"
}, {
"value": "0313",
"text": "其他城市商业银行"
}, {
"value": "0402",
"text": "其他农村信用合作社"
}, {
"value": "0203",
"text": "中国农业发展银行"
}, {
"value": "0314",
"text": "其他农村商业银行"
}, {
"value": "0315",
"text": "恒丰银行"
}, {
"value": "0403",
"text": "中国邮政储蓄银行股份有限公司"
}, {
"value": "0303",
"text": "中国光大银行"
}, {
"value": "0302",
"text": "中信银行"
}, {
"value": "0316",
"text": "浙商银行股份有限公司"
}, {
"value": "0318",
"text": "渤海银行股份有限公司"
}, {
"value": "0319",
"text": "徽商银行"
}, {
"value": "0671",
"text": "香港地区渣打银行"
}, {
"value": "0502",
"text": "东亚银行(中国)有限公司"
}, {
"value": "0317",
"text": "其他农村合作银行"
}, {
"value": "0322",
"text": "上海农村商业银行"
}, {
"value": "0320",
"text": "村镇银行"
}, {
"value": "0501",
"text": "汇丰银行"
}, {
"value": "0503",
"text": "南洋商业银行"
}, {
"value": "0593",
"text": "友利银行"
}, {
"value": "0595",
"text": "新韩银行"
}, {
"value": "0596",
"text": "企业银行"
}, {
"value": "0597",
"text": "韩亚银行"
}, {
"value": "0621",
"text": "华侨银行"
}, {
"value": "0622",
"text": "大华银行"
}, {
"value": "0531",
"text": "花旗银行"
}, {
"value": "0781",
"text": "厦门国际银行"
}, {
"value": "0787",
"text": "华一银行"
}]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,311 @@
{
"micro": [{
"settlementId": 747,
"settlementName": "提供网上交易场所或信息服务的业务、通讯业务、财经类业务及其他平台服务、餐饮、零售、交通出行等实体业务",
"settRule": "费率0.6%入账周期T+1",
"qualification": [{
"qualificationType": "",
"isNeedQualification": false,
"qualificationDesc": "无需特殊资质"
}]
}, {
"settlementId": 777,
"settlementName": "物流快递服务",
"settRule": "费率0.3%入账周期T+1",
"qualification": [{
"qualificationType": "速送",
"isNeedQualification": false,
"qualificationDesc": "无需特殊资质"
}]
}],
"individual": [{
"settlementId": 779,
"settlementName": "物流快递服务",
"settRule": "费率0.3%入账周期T+1",
"qualification": [{
"qualificationType": "速送",
"isNeedQualification": false,
"qualificationDesc": "无需特殊资质"
}]
}, {
"settlementId": 802,
"settlementName": "提供网上交易场所或信息服务的业务、餐饮、零售、交通出行等实体业务",
"settRule": "费率0.6%入账周期T+1",
"qualification": [{
"qualificationType": "餐饮",
"isNeedQualification": false,
"qualificationDesc": "选填,若贵司具备以下资质,主体为餐饮业态,建议提供:《食品经营许可证》或《餐饮服务许可证》"
}, {
"qualificationType": "电商平台",
"isNeedQualification": false,
"qualificationDesc": "无需特殊资质"
}, {
"qualificationType": "零售",
"isNeedQualification": false,
"qualificationDesc": "无需特殊资质,若涉及烟草售卖,需提供《烟草专卖零售许可证》"
}, {
"qualificationType": "食品生鲜",
"isNeedQualification": false,
"qualificationDesc": "选填,若贵司具备以下资质,主体为食品业态,建议提供:《食品经营许可证》或《食品生产许可证》或供销协议+合作方资质"
}, {
"qualificationType": "咨询/娱乐票务",
"isNeedQualification": false,
"qualificationDesc": "无需特殊资质"
}, {
"qualificationType": "房产中介",
"isNeedQualification": false,
"qualificationDesc": "无需特殊资质"
}, {
"qualificationType": "宠物医院",
"isNeedQualification": true,
"qualificationDesc": "《动物诊疗许可证》"
}, {
"qualificationType": "共享服务",
"isNeedQualification": true,
"qualificationDesc": "需提供资金监管协议。协议要求1、主体与商业银行签订2、内容针对交易资金使用和偿付进行监管3、协议须在有效期内4、结算账户须与资金监管账户一致。"
}, {
"qualificationType": "休闲娱乐/旅游服务",
"isNeedQualification": false,
"qualificationDesc": "无需特殊资质"
}, {
"qualificationType": "游艺厅/KTV",
"isNeedQualification": true,
"qualificationDesc": "《娱乐场所经营许可证》或《文化经营许可证》"
}, {
"qualificationType": "网吧",
"isNeedQualification": true,
"qualificationDesc": "《网络文化经营许可证》"
}, {
"qualificationType": "院线影城",
"isNeedQualification": true,
"qualificationDesc": "《电影放映经营许可证》"
}, {
"qualificationType": "演出赛事",
"isNeedQualification": true,
"qualificationDesc": "《营业性演出许可证》"
}, {
"qualificationType": "居民生活服务",
"isNeedQualification": false,
"qualificationDesc": "无需特殊资质"
}, {
"qualificationType": "景区/酒店",
"isNeedQualification": false,
"qualificationDesc": "无需特殊资质"
}, {
"qualificationType": "铁路客运",
"isNeedQualification": true,
"qualificationDesc": "收费授权证明文件(如授权证明书或合同)"
}, {
"qualificationType": "机票/票务代理",
"isNeedQualification": true,
"qualificationDesc": "《航空公司营业执照》或《航空公司机票代理资格证》"
}, {
"qualificationType": "培训机构",
"isNeedQualification": true,
"qualificationDesc": "若贵司具备以下资质建议提供1、《办学许可证》或相关批文2、驾校培训提供有“驾驶员培训”项目的《道路运输经营许可证》"
}, {
"qualificationType": "保健器械/医疗器械/非处方药品",
"isNeedQualification": true,
"qualificationDesc": "互联网售药提供《互联网药品交易服务证》或《互联网药品信息服务资格证书》+《药品经营许可证》;线下门店卖药提供《药品经营许可证》;医疗器械提供《医疗器械经营企业许可证》"
}, {
"qualificationType": "私立/民营医院/诊所",
"isNeedQualification": true,
"qualificationDesc": "《医疗机构执业许可证》中医诊所提供《中医诊所备案证》"
}, {
"qualificationType": "其他缴费",
"isNeedQualification": true,
"qualificationDesc": "收费授权证明文件(如授权证明书或合同)"
}, {
"qualificationType": "停车缴费",
"isNeedQualification": false,
"qualificationDesc": "请提供停车收费资质"
}]
}],
"enterprise": [{
"settlementId": 780,
"settlementName": "保险业务",
"settRule": "费率0.6%入账周期T+1",
"qualification": [{
"qualificationType": "保险业务",
"isNeedQualification": true,
"qualificationDesc": "保险公司提供《经营保险业务许可证》《保险业务法人登记证书》,其他公司提供相关资质"
}]
}, {
"settlementId": 781,
"settlementName": "财经、互联网募捐信息平台服务",
"settRule": "费率0.6%入账周期T+1虚拟限额",
"qualification": [{
"qualificationType": "财经/股票类资讯",
"isNeedQualification": false,
"qualificationDesc": "选填,若有具体的荐股行为,需资质《证券投资咨询业务资格证书》"
}, {
"qualificationType": "互联网募捐信息平台",
"isNeedQualification": true,
"qualificationDesc": "必须符合并提供“慈善组织互联网募捐信息平台公告”截图,且必须提供资金监管协议。"
}]
}, {
"settlementId": 795,
"settlementName": "典当",
"settRule": "费率0.6%入账周期T+1",
"qualification": [{
"qualificationType": "典当",
"isNeedQualification": true,
"qualificationDesc": "典当:《典当经营许可证》"
}]
}, {
"settlementId": 800,
"settlementName": "提供网上交易场所或信息服务的业务、通讯业务、财经类业务及其他平台服务、餐饮、零售、交通出行等实体业务",
"settRule": "费率0.6%入账周期T+1",
"qualification": [{
"qualificationType": "餐饮",
"isNeedQualification": false,
"qualificationDesc": "选填,若贵司具备以下资质,主体为餐饮业态,建议提供:《食品经营许可证》或《餐饮服务许可证》"
}, {
"qualificationType": "电商平台",
"isNeedQualification": false,
"qualificationDesc": "无需特殊资质"
}, {
"qualificationType": "零售",
"isNeedQualification": false,
"qualificationDesc": "无需特殊资质,若涉及烟草售卖,需提供《烟草专卖零售许可证》"
}, {
"qualificationType": "食品生鲜",
"isNeedQualification": false,
"qualificationDesc": "选填,若贵司具备以下资质,主体为食品业态,建议提供:《食品经营许可证》或《食品生产许可证》或供销协议+合作方资质"
}, {
"qualificationType": "咨询/娱乐票务",
"isNeedQualification": false,
"qualificationDesc": "无需特殊资质"
}, {
"qualificationType": "房地产",
"isNeedQualification": true,
"qualificationDesc": "《建设用地规划许可证》《建设工程规划许可证》《建筑工程施工许可证》《国有土地使用证》《商品房预售许可证》缺一不可,证照主体名称要完全一致"
}, {
"qualificationType": "房产中介",
"isNeedQualification": false,
"qualificationDesc": "无需特殊资质"
}, {
"qualificationType": "宠物医院",
"isNeedQualification": true,
"qualificationDesc": "《动物诊疗许可证》"
}, {
"qualificationType": "共享服务",
"isNeedQualification": true,
"qualificationDesc": "需提供资金监管协议。协议要求: 1、主体与商业银行签订 2、内容针对交易资金使用和偿付进行监管 3、协议须在有效期内 4、结算账户须与资金监管账户一致。"
}, {
"qualificationType": "休闲娱乐/旅游服务",
"isNeedQualification": false,
"qualificationDesc": "无需特殊资质"
}, {
"qualificationType": "游艺厅/KTV",
"isNeedQualification": true,
"qualificationDesc": "《娱乐场所经营许可证》或《文化经营许可证》"
}, {
"qualificationType": "网吧",
"isNeedQualification": true,
"qualificationDesc": "《网络文化经营许可证》"
}, {
"qualificationType": "院线影城",
"isNeedQualification": true,
"qualificationDesc": "《电影放映经营许可证》"
}, {
"qualificationType": "演出赛事",
"isNeedQualification": true,
"qualificationDesc": "《营业性演出许可证》"
}, {
"qualificationType": "居民生活服务",
"isNeedQualification": false,
"qualificationDesc": "无需特殊资质"
}, {
"qualificationType": "景区/酒店",
"isNeedQualification": false,
"qualificationDesc": "无需特殊资质"
}, {
"qualificationType": "铁路客运",
"isNeedQualification": true,
"qualificationDesc": "收费授权证明文件(如授权证明书或合同)"
}, {
"qualificationType": "高速公路收费",
"isNeedQualification": true,
"qualificationDesc": "收费授权证明文件(如授权证明书或合同)"
}, {
"qualificationType": "城市公共交通",
"isNeedQualification": true,
"qualificationDesc": "收费授权证明文件(如授权证明书或合同)"
}, {
"qualificationType": "船舶/海运服务",
"isNeedQualification": true,
"qualificationDesc": "《港口经营许可证》"
}, {
"qualificationType": "旅行社",
"isNeedQualification": true,
"qualificationDesc": "《旅行社业务经营许可证》"
}, {
"qualificationType": "机票/票务代理",
"isNeedQualification": true,
"qualificationDesc": "《航空公司营业执照》或《航空公司机票代理资格证》"
}, {
"qualificationType": "培训机构",
"isNeedQualification": true,
"qualificationDesc": "若贵司具备以下资质建议提供1、《办学许可证》或相关批文2、驾校培训提供有“驾驶员培训”项目的《道路运输经营许可证》"
}, {
"qualificationType": "保健器械/医疗器械/非处方药品",
"isNeedQualification": true,
"qualificationDesc": "互联网售药提供《互联网药品交易服务证》或《互联网药品信息服务资格证书》+《药品经营许可证》;线下门店卖药提供《药品经营许可证》;医疗器械提供《医疗器械经营企业许可证》"
}, {
"qualificationType": "私立/民营医院/诊所",
"isNeedQualification": true,
"qualificationDesc": "《医疗机构执业许可证》;中医诊所提供《中医诊所备案证》"
}, {
"qualificationType": "有线电视缴费",
"isNeedQualification": true,
"qualificationDesc": "收费授权证明文件(如授权证明书或合同)"
}, {
"qualificationType": "其他缴费",
"isNeedQualification": true,
"qualificationDesc": "收费授权证明文件(如授权证明书或合同)"
}, {
"qualificationType": "文物经营/文物复制品销售",
"isNeedQualification": false,
"qualificationDesc": "选填,若销售文物,需提供《文物经营许可证》"
}, {
"qualificationType": "停车缴费",
"isNeedQualification": false,
"qualificationDesc": "请提供停车收费资质"
}]
}, {
"settlementId": 806,
"settlementName": "游戏、在线音视频等虚拟业务",
"settRule": "费率1%入账周期T+1虚拟限额",
"qualification": [{
"qualificationType": "在线图书/视频/音乐",
"isNeedQualification": true,
"qualificationDesc": "以下选其一:《互联网出版许可证》、《网络出版服务许可证》、《网络文化经营许可证》、《信息网络传播视听节目许可证》"
}, {
"qualificationType": "门户论坛/网络广告及推广/软件开发/其他互联网服务",
"isNeedQualification": false,
"qualificationDesc": "无需特殊资质"
}, {
"qualificationType": "游戏",
"isNeedQualification": true,
"qualificationDesc": "请提供有效期内的游戏版号(《网络游戏电子出版物审批》)"
}, {
"qualificationType": "网络直播",
"isNeedQualification": true,
"qualificationDesc": "需提供《网络文化经营许可证》且许可证的经营范围应当明确包括网络表演PC/wap网站需要有ICP备案"
}]
}, {
"settlementId": 801,
"settlementName": "物流快递服务",
"settRule": "费率0.3%,入账周期T+1",
"qualification": [{
"qualificationType": "快递",
"isNeedQualification": true,
"qualificationDesc": "需提供《快递业务经营许可证》"
}, {
"qualificationType": "物流",
"isNeedQualification": true,
"qualificationDesc": "需提供《道路运输许可证》,从事网络货运的,提供《增值电信业务许可证》、三级信息系统安全等级保护备案证明、《道路运输许可证》"
}]
}]
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,121 @@
<template>
<view class="dict">
<view>字典管理</view>
<view class="dict-main">
<input
class="dict-input"
v-model="dictText"
placeholder=""
@keydown.enter.native="searchDict"
placeholder-class="input-placeholder"
/>
<view class="dict-but">
<button type="primary" @click="searchDict" size="mini">
搜索
</button></view
>
</view>
<view
>{{ dictValue }} <text class="dict-copy" @click="copy">复制</text
><text class="dict-copy" @click="clearDictValue">清空</text></view
>
</view>
<view class="dict-card-list">
<block v-for="(v, i) in dictLists">
<view class="dict-card-main">
<view
>type{{ v.type }}
<text class="dict-add" @click="addDict(v.type)">添加</text></view
>
<view>value{{ v.value }}</view>
<view>text{{ v.text }}</view>
</view>
</block>
</view>
</template>
<script setup>
import { reactive, ref, watch } from "vue";
import { filterDicts } from "@/hooks/dict";
const dictText = ref("");
const dictLists = ref([]);
let dictValue = ref([]);
function searchDict() {
dictLists.value = filterDicts(dictText.value);
}
function addDict(v) {
dictValue.value.push(v);
}
function copy() {
uni.setClipboardData({
data: toStringDictType(),
});
}
function toStringDictType() {
let text = "";
dictValue.value.forEach((v, i) => {
if (i != dictValue.value.length - 1) {
text += `"${v}",`;
} else {
text += `"${v}"`;
}
});
return `[${text}]`;
}
function clearDictValue() {
dictValue.value = [];
}
</script>
<style lang="scss" scoped>
.dict {
width: 50vw;
margin: auto;
.dict-main {
display: flex;
align-items: center;
.dict-input {
width: 180px;
padding: 2px 0;
margin: 10px 0;
border: 1px solid #ccc;
border-radius: 5px;
text-indent: 1em;
}
.dict-but {
margin-left: 10px;
transform: translateY(1px);
}
}
.dict-copy {
font-size: 12px;
border: 0.5px solid #ccc;
background-color: skyblue;
border-radius: 2px;
padding: 0 5px;
margin: 0 5px;
cursor: pointer;
}
}
.dict-card-list {
display: flex;
flex-wrap: wrap;
.dict-card-main {
padding: 10px;
margin: 10px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12), 0 0 6px rgba(0, 0, 0, 0.04);
view {
padding: 5px;
}
.dict-add {
font-size: 12px;
border: 0.5px solid #ccc;
background-color: skyblue;
border-radius: 2px;
padding: 0 5px;
cursor: pointer;
}
}
}
</style>

View File

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

View File

@@ -0,0 +1,97 @@
<template>
<view>
<uni-popup ref="confirmRef" type="bottom" @maskClick="handleClose">
<JMainCard pd="0" wrapPd="30rpx">
<view class="content bgF2 bdR10">{{ confirmText }}</view>
<view class="confirm" hover-class="u-cell-hover" hover-stay-time="150" @tap="handleConfirm">确认</view>
</JMainCard>
<JButton pd="0 30rpx 30rpx 30rpx" pdTop="0" bgColor="#fff" color="#000" @HandleTouch="handleClose()"
>取消</JButton
>
</uni-popup>
</view>
</template>
<script setup>
import JMainCard from "@/components/newComponents/JMainCard/JMainCard"
import JButton from "@/components/newComponents/JButton/JButton"
/*
用于二次确认弹窗
参数1 确认按钮回调函数
参数2 取消按钮回调函数
参数3 弹框内部提示文字
*/
import { ref } from "vue"
import useBackPress from "@/hooks/useBackPress.js"
const confirmRef = ref()
let confirmText = ref("")
let confirmPop = () => {}
let cancelPop = () => {}
// 处理开启弹窗 与 关闭弹窗 的 阻断返回
const popupopened = () => {
// #ifdef H5 || APP-PLUS
active()
// #endif
}
const popupclosed = () => {
// #ifdef H5 || APP-PLUS
inactive()
// #endif
}
const closeEd = () => {
confirmRef.value.close()
}
// #ifdef H5 || APP-PLUS
const { active, inactive } = useBackPress(closeEd) // onBackPress 阻断返回
// #endif
// 确认按钮
const handleConfirm = () => {
popupclosed()
confirmPop()
confirmRef.value.close()
}
// 取消按钮
const handleClose = () => {
popupclosed()
cancelPop()
confirmRef.value.close()
}
// 开启弹窗,供父组件调用传递函数
const comfirmOpen = (confirm = () => {}, tipText = "您确认要改变状态吗?", cancel = () => {}) => {
popupopened()
confirmPop = confirm
cancelPop = cancel
confirmText.value = tipText
confirmRef.value.open()
}
defineExpose({ comfirmOpen })
</script>
<style scoped lang="scss">
.content {
margin: 30rpx 30rpx 10rpx;
padding: 20rpx;
font-size: 27rpx;
color: #666;
}
.confirm {
display: flex;
justify-content: center;
align-items: center;
height: 110rpx;
font-size: 33rpx;
color: #ff4343;
}
.u-cell-hover {
background-color: rgba($color: #999, $alpha: 0.1);
}
</style>

View File

@@ -0,0 +1,204 @@
<template>
<view>
<view class="picker-box animation-close" :style="{height:(vdata.isA?'232rpx':'120rpx')}">
<view class="date-filter">
<view class="date " :class="vdata.selectFlge ==item.index?'select':''" v-for="(item,index) in vdata.selectList" :key="index" @click="onSelsct(index)">
<text>{{item.text}}</text>
</view>
</view>
<view class="time-picker " >
<xp-picker @confirm="confirm" mode="ymdhi" :startTime="vdata.startDate">{{vdata.startDate}}</xp-picker>
<text></text>
<xp-picker @confirm="confirmEnd" mode="ymdhi" :startTime="vdata.endDate">{{vdata.endDate}}</xp-picker>
</view>
</view>
</view>
</template>
<script setup>
import { ref, reactive,toRaw } from 'vue'
import {getDay} from '@/util/timeInterval.js'
import { onLoad,onShow } from '@dcloudio/uni-app'
const emit = defineEmits(['confirmEnd','onSelsct','confirmStart'])
let params = {} //搜索参数
const vdata = reactive({
isA:false, //动画
selectFlge:1,
selectList:[
{index:0,value:'all',text:'全部'},
{index:1,value:'today',text:'今天'},
{index:2,value:'yesterday',text:'昨天'},
{index:3,value:'mouth',text:'本月'},
{index:4,value:'week',text:'上月'},
{index:5,value:'customer',text:'自定义'},
],
})
onLoad((option) => {
vdata.startDate =`${getDay(0,'-')} 00:00`
vdata.endDate = `${getDay(0,'-')} 23:59`
})
//时间选择器确定
const confirm =(e)=>{
vdata.startDate = e.value
// vdata.endDate = e.value
params.queryDateRange = `customDate_${vdata.startDate}:00_${vdata.endDate}:59`
console.log(params)
emit('confirmStart',params)
}
const confirmEnd=(e)=>{
vdata.endDate = e.value
params.queryDateRange = `customDate_${vdata.startDate}:00_${vdata.endDate}:59`
console.log(params)
emit('confirmEnd',params)
// getDevDetail(params)
}
const switchState = (e, item) =>{
item.state = Number(e.detail.value);
switchStatePopup.value.comfirmOpen(
() => {
$editAgent(item.agentNo, item);
},
undefined,
() => {
item.state = Number(!e.detail.value);
}
);
}
const onSelsct = (index) =>{
vdata.selectFlge = index
switch(index){
case 0:
vdata.isA = false
params.queryDateRange = ''
params.isQueryAll = true
// getDevDetail(params)
emit('onSelsct',params)
break;
case 1:
vdata.isA = false
params.queryDateRange = 'today'
delete params.isQueryAll
// getDevDetail(params)
emit('onSelsct',params)
break;
case 2:
vdata.isA = false
params.queryDateRange = 'yesterday'
// getDevDetail(params)
delete params.isQueryAll
emit('onSelsct',params)
break;
case 3:
vdata.isA = false
params.queryDateRange = 'near2now_30'
delete params.isQueryAll
// getDevDetail(params)
emit('onSelsct',params)
break;
case 4:
vdata.isA = false
params.queryDateRange = 'prevMonth'
delete params.isQueryAll
// getDevDetail(params)
emit('onSelsct',params)
break;
case 5:
vdata.isA = true
break;
}
}
</script>
<style lang="scss">
.animation-close{
transition: height 0.3s;
}
.picker-box{
width: 650rpx;
//height: 252rpx;
background-color: #FFFFFF;
margin: 0rpx auto;
border-radius: 20rpx;
overflow: hidden;
.select{
padding: 10rpx 10rpx;
border-radius: 10rpx;
background: rgba(57, 129, 255, 0.1);
font-weight: 500;
color: #3981ff;
}
.date-filter{
box-sizing: border-box;
height: 90rpx;
width: 650rpx;
margin: 0rpx auto;
margin-bottom: 0;
background-color: #FFFFFF;
// border-bottom: 2rpx solid #FAFBFC;
display: flex;
justify-content: space-between;
align-items: center;
padding-left: 30rpx;
padding-right: 30rpx;
padding-top: 30rpx;
.date{
//width: 92rpx;
//height: 53rpx;
display: flex;
justify-content: center;
align-items: center;
padding: 10rpx 10rpx;
}
.close{
height: 40rpx;
width: 40rpx;
// background-color: #007AFF;
image{
height: 20rpx;
width: 20rpx;
transition: transform .5s;
}
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
}
}
.time-picker{
box-sizing: border-box;
width: 590rpx;
height: 72rpx;
border-radius: 10rpx;
background: #F5F6FC;
margin: 30rpx auto;
display: flex;
justify-content: space-between;
align-items: center;
padding-left: 20rpx;
padding-right: 20rpx;
font-size: 25rpx;
overflow: hidden;
}
</style>

View File

@@ -0,0 +1,48 @@
<template>
<view class="list-null" v-if="isShow">
<view class="null-img">
<image src="/static/img/nomore.svg" mode=""></image>
</view>
<text>{{ text }}</text>
</view>
</template>
<script setup>
import { ref, watchEffect } from "vue"
const props = defineProps({
isShow: { type: Boolean, default: false },
// text: { type: String, default: '暂无更多数据' },
list: { type: Number, default: 0 },
})
let text = ref("")
watchEffect(() => {
props.list === 0 ? (text.value = "暂无数据") : (text.value = "暂无更多数据")
})
</script>
<style lang="scss">
.list-null {
height: 200rpx;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
font-weight: 500;
font-size: 23rpx;
letter-spacing: 0.05em;
text-align: left;
color: #9ea5b3;
.null-img {
margin-top: 30rpx;
height: 80rpx;
width: 124rpx;
//background-color: #2979FF;
image {
height: 100%;
width: 100%;
}
}
}
</style>

View File

@@ -0,0 +1,79 @@
<template>
<view class="content">
<view class="status-bar-box">
<view class="status-bar"></view>
</view>
<view class="navigation-box">
<view class="navigation" :class="props.postion ?'':'navigation-center'">
<text>{{props.title}}</text>
</view>
</view>
</view>
</template>
<script setup>
const props = defineProps({
title: String,
postion:{type:Boolean,default:true}
});
</script>
<style lang="scss" scoped>
.content {
position: relative;
z-index: 99;
.status-bar-box {
height: var(--status-bar-height);
width: 100%;
.status-bar {
position: fixed;
top: 0;
width: 100% !important;
height: var(--status-bar-height);
background: #F5F6FC !important;
z-index: 90;
}
}
.navigation-box {
height: 88rpx !important;
.navigation {
position: fixed;
left: var(--window-left);
right: var(--window-right);
height: 88rpx;
line-height: 88rpx;
display: flex;
justify-content: space-between;
// justify-content: center;
box-sizing: border-box;
padding: 0 36rpx;
background-color: #F5F6FC;
z-index: 90;
text {
font-size: 33rpx;
font-weight: 700;
}
}
.navigation-center {
position: fixed;
left: var(--window-left);
right: var(--window-right);
height: 88rpx;
line-height: 88rpx;
display: flex;
//justify-content: space-between;
justify-content: center;
box-sizing: border-box;
padding: 0 36rpx;
background-color: #F5F6FC;
z-index: 90;
text {
font-size: 33rpx;
font-weight: 700;
}
}
}
}
</style>

View File

@@ -0,0 +1,148 @@
<template>
<view class="search-box">
<view class="jeepay-search" @touchmove.stop.prevent="moveHandle">
<image src="/static/img/list-search.svg" alt="" /><!--放大镜-->
<input type="text" v-model="searchText" :placeholder="placeholder" placeholder-style="font-size:28rpx" @focus="focusHandle" @input="changeHandle($event)" :focus="props.isFocus">
<!-- 清空图片 -->
<image @click="clearText" src="../../static/img/clear-test.svg" class="clear-text" mode="" v-if="searchBtn && clearTextIsShow"></image>
<view class="btn">
<!-- <slot v-if="!searchBtn" name="option"></slot> -->
<view v-if="searchBtn" @click="searchHandle" class="search-text">
搜索
</view>
</view>
</view>
<slot v-if="!searchBtn" name="option" class="slot"></slot>
</view>
<view class="mark" @click="hiddenMask" v-show="searchBtn" @touchmove.stop.prevent="moveHandle">
</view>
</template>
<script setup>
import { ref, nextTick, watch } from 'vue'
import { onShow } from '@dcloudio/uni-app'
const props = defineProps({
value: {type: String, default: null},
placeholder: {type: String, default: '搜索内容'},
isFocus:{type: Boolean, default: false}
})
let searchText = ref('') // 搜索内容
let clearTextIsShow = ref(false) // 清空按钮是否展示
// 搜索按钮
let searchBtn = ref(false)
// 获得焦点
const focusHandle = () => {
searchBtn.value = true
emit('focusHandle')
}
function hiddenMask() {
searchBtn.value = false
}
onShow(() => {
hiddenMask();
})
//监听搜索框中有无内容
watch( () => searchText.value, () => {
if(!searchText.value) {
clearTextIsShow.value = false
} else {
clearTextIsShow.value = true
}
})
watch( () => props.value, () => {
if(props.value === ''){
searchText.value = ''
}
})
// 禁止页码滚动
const moveHandle = () => {}
const emit = defineEmits(['update:value', 'searchHandle','focusHandle','backPressHandel','orderBackPressHandel'])
const changeHandle = (e) => emit('update:value', e.detail.value)
const clearText = () => {
searchText.value = ''
emit('update:value', '')
}
const searchHandle = () => {
searchBtn.value = false
emit('searchHandle')
}
const backPressHandel = ()=>{
if(searchBtn.value){
searchBtn.value = false
return true
}
return false
}
const orderBackPressHandel = ()=>{
if(searchBtn.value){
searchBtn.value = false
}
}
defineExpose({ backPressHandel,orderBackPressHandel });
</script>
<style scoped lang="scss">
.mark {
position: absolute;
width: 100%;
height: calc(100vh - 180rpx);
top: 120rpx;
left: 0;
z-index:99;
background-color: rgba(0,0,0, 0.5);
}
.search-box{
display: flex;
justify-content: space-between;
align-items: center;
background-color: #f2f2f2;
border-bottom: 1rpx solid #e6e6e6;
.jeepay-search {
display: flex;
padding: 30rpx;
box-sizing: border-box;
align-items:center;
position: relative;
height: 70rpx;
width: 100%;
background-color: #FFF;
margin: 20rpx 30rpx 30rpx;
border-radius: 12rpx;
image {
width: 26rpx;
height: 26rpx;
margin-right: 25rpx;
}
input {
flex-grow: 1;
margin-right: 20rpx;
}
.btn{
display: flex;
align-items: center;
justify-content: space-between;
}
}
.search-text {
color: $uni-color-primary;
width: 90rpx;
}
.clear-text {
width: 20rpx;
height: 20rpx;
}
}
</style>

View File

@@ -0,0 +1,91 @@
<template>
<view class="b-wrapper" :class="[buttonSize()]" :style="{ paddingBottom: bottom, marginTop: pdTop, padding: pd }">
<view
@tap.stop="handleTap"
class="b-main"
:style="{
color: color,
backgroundColor: bgColor,
}"
hover-class="u-cell-hover"
hover-stay-time="150"
>
<slot>登录</slot>
</view>
</view>
<view :style="{ height }" v-if="height"></view>
</template>
<script setup>
import { reactive } from "vue"
const props = defineProps({
color: {
type: String,
default: "#fff",
},
bgColor: {
type: String,
default: "$primaryColor",
},
size: {
type: String,
default: "",
},
bottom: {
type: String,
default: "75rpx",
},
pdTop: {
type: String,
default: "30rpx",
},
height: {
type: String,
default: "",
},
pd: {
type: String,
},
})
const size = reactive({
medium: "medium",
max: "max",
})
const buttonSize = () => {
return size[props.size]
}
const emits = defineEmits(["HandleTouch"])
const handleTap = () => {
emits("HandleTouch")
}
</script>
<style lang="scss" scoped>
.b-wrapper {
padding: 50rpx;
.b-main {
position: relative;
font-size: 33rpx;
height: 110rpx;
background-color: $primaryColor;
border-radius: 20rpx;
outline: 0;
border: none;
display: flex;
justify-content: center;
align-items: center;
font-size: 33rpx;
}
.u-cell-hover {
opacity: 0.5;
}
}
.max {
padding: 50rpx;
}
.medium {
padding: 0 50rpx 50rpx 50rpx;
}
</style>

View File

@@ -0,0 +1,37 @@
<template>
<JMainCard pd="30rpx" wrapPd="0 30rpx">
<view class="card-title" @tap="confirm"> {{ title }} </view>
<view class="card-content"> <slot /> </view>
</JMainCard>
</template>
<script setup>
import JMainCard from "@/components/newComponents/JMainCard/JMainCard.vue"
const emits = defineEmits(["confirm"])
const props = defineProps({
title: {
type: String,
default: "确认退出",
},
})
const confirm = () => {
emits("confirm")
}
</script>
<style lang="scss" scoped>
.card-title {
text-align: center;
color: #ff4433;
}
.card-content {
padding: 20rpx;
margin-top: 30rpx;
background-color: #f2f2f2;
font-size: 27rpx;
line-height: 46rpx;
border-radius: 10rpx;
color: #666;
}
</style>

View File

@@ -0,0 +1,22 @@
<template>
<view class="content-wrapper" :style="{ backgroundColor: bgColor, padding: pd }"><slot /></view>
</template>
<script setup>
const props = defineProps({
bgColor: {
type: String,
default: '#fff'
},
pd: {
type: String,
default: '25rpx 50rpx 0 50rpx'
}
})
</script>
<style lang="scss" scoped>
.content-wrapper {
border-radius: 32rpx 32rpx 0 0;
}
</style>

View File

@@ -0,0 +1,74 @@
<template>
<view class="content-wrapper bdR16" :style="{ backgroundColor: bgColor, margin, color }" @tap="taps">
<view class="content-left">
<image :src="leftImg" mode="scaleToFill" />
<view class="content-title"><slot /></view>
</view>
<view class="content-right">
<image src="/static/iconImg/right-arrow.svg" mode="scaleToFill" />
</view>
</view>
<view :style="{ height }" v-if="height != ''"> </view>
</template>
<script setup>
const props = defineProps({
bgColor: {
type: String,
default: '#f7f7f7'
},
margin: {
type: String,
default: '30rpx 0 0 0'
},
leftImg: {
type: String,
default: '/static/iconImg/notice-img.svg'
},
height: {
type: String,
default: ''
},
rightImg: {
type: String,
default: '/static/iconImg/right-arrow.svg'
},
color: {
type: String,
default: '#000'
}
})
const emits = defineEmits(['HandleTouch'])
const taps = () => {
emits('HandleTouch')
}
</script>
<style lang="scss" scoped>
.content-wrapper {
padding: 40rpx 32rpx 40rpx 38rpx;
display: flex;
justify-content: space-between;
align-items: center;
.content-left {
display: flex;
align-items: center;
font-size: 33rpx;
color: #666;
image {
width: 36rpx;
height: 36rpx;
margin-right: 10rpx;
}
.content-title {
vertical-align: top;
}
}
.content-right {
image {
width: 40rpx;
height: 40rpx;
}
}
}
</style>

View File

@@ -0,0 +1,57 @@
<template>
<JPopup ref="popup" @onClose="emits('cancel', 'mask')" @change="change">
<JMainCard pd="0" wrapPd="30rpx">
<view class="content bgF2 bdR10">{{ text }}</view>
<view class="confirm" hover-class="u-cell-hover" hover-stay-time="150" @tap="confirm">确认</view>
</JMainCard>
<JButton pd="0 30rpx 30rpx 30rpx" pdTop="0" bgColor="#fff" color="#000" @HandleTouch="close()">取消</JButton>
</JPopup>
</template>
<script setup>
import { inject, ref } from "vue"
import JPopup from "@/components/newComponents/JPopup/JPopup"
import JMainCard from "@/components/newComponents/JMainCard/JMainCard"
import JButton from "@/components/newComponents/JButton/JButton"
const wrapperHidden = inject('wrapperHidden')
const emits = defineEmits(["confirm", "cancel"])
const text = ref("")
const popup = ref()
const open = (tips = "确认删除吗?一旦删除不可恢复!!!") => {
text.value = tips
popup.value.open()
}
const close = () => {
popup.value.close()
}
const confirm = () => {
emits("confirm")
popup.value.close()
}
const change = (e)=>{
if(wrapperHidden){
wrapperHidden(e.show)
}
}
defineExpose({ open })
</script>
<style lang="scss" scoped>
.content {
margin: 30rpx 30rpx 10rpx;
padding: 20rpx;
font-size: 27rpx;
color: #666;
}
.confirm {
display: flex;
justify-content: center;
align-items: center;
height: 110rpx;
font-size: 33rpx;
color: #ff4343;
}
.u-cell-hover {
background-color: rgba($color: #999, $alpha: 0.1);
}
</style>

View File

@@ -0,0 +1,99 @@
<template>
<view class="equipment-info">
<!-- 背景图片 -->
<image :src="props.bgImg" class="back-img" />
<view class="content">
<image :src="props.icon" class="code" />
<view class="name">{{ props.qrcAlias }}</view>
<view class="no">{{ props.qrcId }}</view>
<view class="edit" @tap="edit" v-if="editIsShow">
<image src="/static/equipmentImg/edit.svg" />
编辑信息
</view>
</view>
</view>
</template>
<script setup>
import { ref, reactive } from "vue"
const props = defineProps({
bgImg: { type: String, default: "" }, // 背景图片
icon: { type: String, default: "" }, // 居中的icon
qrcAlias: { type: String, default: "" }, // 标题
qrcId: { type: String, default: "" }, // iD
info: { type: Object, default: () => {} },
editIsShow: { type: Boolean, default: true }, // 是否展示编辑按钮
})
const emits = defineEmits(["editInfo"])
const edit = () => emits("editInfo")
</script>
<style scoped lang="scss">
.equipment-info {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.back-img {
position: absolute;
right: -30rpx;
top: -20rpx;
width: 100%;
height: 650rpx;
}
.content {
position: relative;
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 10;
.code {
margin-top: 70rpx;
margin-bottom: 20rpx;
width: 93rpx;
height: 93rpx;
}
.name {
width: 500rpx;
margin: 0 auto;
text-align: center;
font-weight: 700;
line-height: 60rpx;
color: #ffffff;
font-size: 33rpx;
}
.no {
font-size: 25rpx;
color: rgba(255, 255, 255, 0.6);
margin-top: 12rpx;
margin-bottom: 30rpx;
}
.edit {
display: flex;
justify-content: center;
align-items: center;
background-color: #fff;
padding: 25rpx 42rpx;
border-radius: 10rpx;
margin-bottom: 50rpx;
color: #7737fe;
font-size: 28rpx;
image {
width: 26rpx;
height: 26rpx;
margin-right: 10rpx;
}
}
}
}
</style>

View File

@@ -0,0 +1,108 @@
<template>
<view class="equipment-info">
<!-- 背景图片 -->
<image src="/static/equipmentImg/big-code.svg" class="back-img" />
<view class="content">
<view class="code" @tap="down">
<image :src="info.qrcodeUrl" />
</view>
<view class="name">{{ info.qrcAlias ? info.qrcAlias : "未命名" }}</view>
<view class="no">{{ info.qrcId }}</view>
<view class="edit" @tap="edit">
<image src="/static/equipmentImg/edit.svg" />
编辑信息
</view>
</view>
</view>
</template>
<script setup>
import { ref, reactive } from "vue"
const vdata = defineProps({
info: { type: Object, default: () => {} },
})
const emits = defineEmits(["downUrl", "editInfo"])
const down = () => emits("downUrl")
const edit = () => emits("editInfo")
</script>
<style scoped lang="scss">
.equipment-info {
// position: relative;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
// overflow: hidden;
.back-img {
position: absolute;
right: -30rpx;
top: 0;
width: 100%;
height: 650rpx;
}
.content {
position: relative;
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 10;
.code {
margin-top: 70rpx;
margin-bottom: 20rpx;
width: 360rpx;
height: 360rpx;
border-radius: 20rpx;
background: #ffffff;
display: flex;
justify-content: center;
align-items: center;
image {
width: 300rpx;
height: 300rpx;
}
}
.name {
width: 500rpx;
margin: 0 auto;
text-align: center;
line-height: 60rpx;
color: #ffffff;
font-size: 33rpx;
font-weight: 700;
}
.no {
font-size: 25rpx;
color: rgba(255, 255, 255, 0.6);
margin-top: 12rpx;
margin-bottom: 30rpx;
}
.edit {
display: flex;
justify-content: center;
align-items: center;
background-color: #fff;
padding: 25rpx 42rpx;
border-radius: 10rpx;
margin-bottom: 50rpx;
color: #7737fe;
font-size: 28rpx;
image {
width: 26rpx;
height: 26rpx;
margin-right: 10rpx;
}
}
}
}
</style>

View File

@@ -0,0 +1,126 @@
<template>
<view
class="wrapper"
:style="{ paddingTop: menu.top + 'px', backgroundColor: bgColor }"
:class="{ isPrimary: props.bgColor == '$primaryColor' }"
>
<!-- #ifdef APP-PLUS || H5 -->
<view class="status-bar"> </view>
<!-- #endif -->
<view
class="header-wrapper"
:style="{ backgroundColor: bgColor, color: color, height: menu.height + 'px', lineHeight: menu.height + 'px' }"
>
<view class="header-left" @tap="navBack" v-if="back">
<image :src="imgUrl" mode="scaleToFill" />
</view>
<view class="header-left logo-out" v-if="logOut" @tap="loginOut">
<image :src="imgUrl" mode="scaleToFill" />退出登录</view
>
<view class="header-text">{{ title }}</view>
</view>
</view>
<!-- <view class="wrapper-block" :style="{ backgroundColor: bgColor, paddingBottom: pdB }"></view> -->
</template>
<script setup>
import { ref, watch } from "vue"
import user from "@/hooks/user.js"
const props = defineProps({
bgColor: {
type: String,
default: "#fff",
},
title: {
type: String,
default: "首页",
},
color: {
type: String,
default: "#000",
},
pdB: {
type: String,
default: "100rpx",
},
imgUrl: {
type: String,
default: "/static/iconImg/left-arrow.svg",
},
back: {
type: Boolean,
default: true,
},
logOut: { type: Boolean, default: false },
})
const emits = defineEmits(["back"])
let menu = { top: 0, height: "" }
// #ifdef MP-WEIXIN
menu = uni.getMenuButtonBoundingClientRect()
// #endif
const navBack = () => {
emits("back")
uni.navigateBack({
fail: (err) => {},
})
}
const loginOut = () => {
user.logout()
uni.showToast({
icon: "none",
title: "退出成功",
})
}
</script>
<style lang="scss" scoped>
.wrapper {
position: sticky;
width: 100%;
left: 0;
right: 0;
top: 0;
padding-bottom: 10rpx;
z-index: 50;
.status-bar {
height: var(--status-bar-height);
width: 100%;
}
.header-wrapper {
position: relative;
height: 88rpx;
line-height: 88rpx;
text-align: center;
font-weight: 700;
.header-left {
position: absolute;
top: 50%;
left: 30rpx;
width: 50rpx;
height: 50rpx;
transform: translateY(-50%);
image {
width: 100%;
height: 100%;
}
}
.logo-out {
display: flex;
align-items: center;
width: 150rpx;
font-size: 25rpx;
image {
width: 40rpx;
height: 40rpx;
}
}
}
}
.wrapper-block {
height: var(--status-bar-height);
}
.isPrimary {
background: $primaryColor !important;
}
</style>

View File

@@ -0,0 +1,118 @@
<template>
<view
class="layout"
:class="{ first: props.isBorder }"
:style="{ padding: props.pd, backgroundColor: props.bgColor, alignItems: align }"
>
<!-- 最左侧插槽用于存放icon -->
<image :src="props.icon" v-if="props.icon" :style="{ width: props.iconSize, height: props.iconSize }" />
<view class="name" :style="{ color: props.textColor, fontSize: props.size }">{{ props.name }}</view>
<view class="input">
<slot>
<input
v-if="props.place != '' || props.value != ''"
:type="type"
:value="props.value"
placeholder-style="color:#A6A6A6;"
:placeholder="(rules != null ? '(必填)' : '') + place"
:style="{ fontSize: size }"
:maxlength="maxlength"
@input="onchange"
:focus="focus"
@focus="emits('focusShow')"
@blur="emits('focusNone')"
/>
<view v-else :style="{ fontSize: props.size, color: props.rightColor }">{{ props.right }}</view>
</slot>
</view>
<image src="/static/equipmentImg/arrow.svg" v-if="props.img" />
</view>
</template>
<script setup>
import { ref, reactive, watchEffect, watch, onDeactivated, onBeforeUnmount, onMounted } from "vue"
import { addRules, clearOneRule } from "@/hooks/rules"
const props = defineProps({
name: { type: String, default: "" }, // 左侧文字
icon: { type: String, default: "" }, // 左侧图标
iconSize: { type: String, default: "40rpx" }, // 左侧图标大小
size: { type: String, default: "33rpx" }, // 文字大小
right: { type: String, default: "" }, // 右侧文字
value: { type: String, default: "" }, // 输入框文字
place: { type: String, default: "" }, // 提示文字
bgColor: { type: String, default: "" }, // 背景颜色
isBorder: { type: Boolean, default: false }, // 是否展示上边框
borderBg: { type: String, default: "#f2f2f2" }, // 上边框颜色
img: { type: Boolean, default: false }, // 是否展示右箭头
pd: { type: String, default: "40rpx 32rpx" },
textColor: { type: String, default: "#000" }, // 左侧文字颜色
rightColor: { type: String, default: "#fff" }, // 右侧文字颜色
size: { type: String, default: "33rpx" }, // 文字大小,
type: { type: String, default: "text" }, //输入框类型
rules: { default: null },
maxlength: { type: Number },
align: { type: String }, //对齐方式
focus: { type: Boolean, default: false },
})
const borderColor = ref(props.borderBg)
const places = ref("")
const emits = defineEmits(["update:value", "focusShow", "focusNone"])
const onchange = (e) => emits("update:value", e.detail.value)
onMounted(() => {
if (props.rules !== null) {
props.rules.Msg = props.name
addRules(props.rules)
}
})
onBeforeUnmount(() => {
if (props.rules !== null) {
clearOneRule(props.rules)
}
})
</script>
<style scoped lang="scss">
page {
background-color: #f2f2f2;
}
.layout {
display: flex;
align-items: flex-start;
position: relative;
box-sizing: border-box;
.name {
white-space: nowrap;
font-size: 33rpx;
// line-height: 46rpx;
}
.input {
margin: 0 10rpx 0 20rpx;
flex-grow: 1;
text-align: right;
}
image {
flex-shrink: 0;
width: 40rpx;
height: 40rpx;
margin-right: 20rpx;
}
&::before {
content: "";
position: absolute;
top: 0;
left: 32rpx;
height: 2rpx;
width: calc(100% - 32rpx);
background-color: v-bind(borderColor);
}
}
.first {
&::before {
height: 0rpx;
}
}
</style>

View File

@@ -0,0 +1,84 @@
<template>
<view class="list-item" :class="{big: props.preBig}">
<view class="left">
<view class="back" :style="{background: props.bgColor}">
<image :src="props.icon" />
</view>
<view :style="{color: props.leftColor}">{{ props.ifName }}</view>
</view>
<view class="right">
<text class="state-color" :style="{background: props.stateColor}"></text>
<text class="state-text" :style="{color: props.rightColor}">{{ props.stateText }}</text>
<image src="/static/equipmentImg/arrow.svg" style="width:40rpx; height:40rpx;" v-if="isImg" />
</view>
</view>
</template>
<script setup>
const props = defineProps({
bgColor: { type: String, default: ''},
preBig: { type: Boolean, default: false}, // 预制颜色1
icon: { type: String, default: ''},
ifName: { type: String, default: ''},
stateColor: { type: String, default: ''},
stateText: { type: String, default: ''},
leftColor: { type: String, default: '#fff'},
rightColor: { type: String, default: 'rgba(255, 255, 255, 0.7)'},
isImg:{type:Boolean,default:true}
})
</script>
<style scoped lang="scss">
.list-item {
box-sizing: border-box;
display: flex;
justify-content: space-between;
align-items: center;
padding: 15rpx 0;
.left {
display: flex;
align-items: center;
color: #fff;
font-size: 33rpx;
}
.back {
width: 60rpx;
height: 60rpx;
border-radius: 5rpx;
margin-right: 20rpx;
display: flex;
justify-content: center;
align-items: center;
flex-shrink: 0;
image {
width: 40rpx;
height: 40rpx;
}
}
.right {
display: flex;
align-items: center;
.state-color {
width: 10rpx;
height: 10rpx;
border-radius: 50%;
margin-right: 16rpx;
}
.state-text {
font-size: 30rpx;
color: rgba(255, 255, 255, 0.7);
margin-right: 16rpx;
}
}
}
// 预制主题1
.big {
background: rgba(0, 0, 0, 0.03);
padding: 30rpx;
box-sizing: border-box;
border-radius: 20rpx;
}
</style>

View File

@@ -0,0 +1,66 @@
<template>
<view class="line" :class="{ 'border-none': props.isBorder }" :style="{ marginLeft: ml, padding: pd }">
<image :src="props.iconOn" class="icon" v-if="props.isSelect && iconOn != ''"></image>
<image :src="props.iconClose" class="icon" v-else-if="iconOn != ''"></image>
<view class="name single-text-beyond" :class="{ color: props.isSelect }">{{ props.name }}</view>
<view class="name right-text" :class="{ color: props.isSelect }">
{{ text }}
<image src="/static/equipmentImg/check.svg" class="select" v-if="props.isSelect"></image>
</view>
</view>
</template>
<script setup>
import { ref, reactive } from "vue"
const props = defineProps({
iconOn: { type: String, default: "" },
iconClose: { type: String, default: "" },
name: { type: String, default: "" },
isSelect: { type: Boolean, default: false },
isBorder: { type: Boolean, default: false },
text: { type: String },
ml: { type: String },
pd: { type: String },
})
</script>
<style scoped lang="scss">
.line {
border-top: 1rpx solid #eee;
display: flex;
justify-content: space-between;
align-items: center;
padding: 32rpx;
box-sizing: border-box;
.icon {
width: 36rpx;
height: 36rpx;
flex-shrink: 0;
}
.name {
max-width: 300rpx;
color: #333333;
font-size: 33rpx;
line-height: 46rpx;
flex-grow: 1;
margin: 0 20rpx;
}
.right-text {
text-align: right;
}
.select {
width: 36rpx;
height: 36rpx;
flex-shrink: 0;
}
}
.color {
color: $primaryColor !important;
}
.border-none {
border: none;
}
</style>

View File

@@ -0,0 +1,29 @@
<template>
<view class="loading-wrapper" v-if="loadingStatus" @tap="hideLoading"></view>
</template>
<script setup>
import { loadingStatus,hideLoading } from '@/hooks/loading'
import { ref } from 'vue'
// let loadingStatus = ref(false)
// const loadingShow = () => {
// loadingStatus.value = true
// setTimeout(() => {
// console.log('执行')
// loadingStatus.value = false
// }, 1000)
// }
// defineExpose({ loadingShow })
</script>
<style lang="scss" scoped>
.loading-wrapper {
position: absolute;
top: 0;
left: 0;
z-index: 9999999999;
width: 100vw;
height: 100vh;
background-color: $primaryColor;
}
</style>

View File

@@ -0,0 +1,31 @@
<template>
<view class="card-wrapper" :style="{ padding: wrapPd }" @tap="emits('click')">
<view class="card-main bdR20" :style="{ backgroundColor: bgColor, padding: pd }">
<slot />
</view>
</view>
</template>
<script setup>
const emits = defineEmits(['click'])
const props = defineProps({
bgColor: {
type: String,
default: "#fff",
},
pd: {
type: String,
default: "50rpx",
},
wrapPd: {
type: String,
default: "50rpx",
},
})
</script>
<style scoped>
.card-main {
overflow: hidden;
}
</style>

View File

@@ -0,0 +1,64 @@
<template>
<uni-popup ref="popup" :type="props.dir" :is-mask-click="false" mask-background-color="rgba(0,0,0,0.5)">
<view class="Mask" id="msk" :style="{ alignItems: position[type] }" @tap.stop="maskClose($event)">
<view class="wrapper" @tap.stop>
<slot></slot>
</view>
</view>
</uni-popup>
</template>
<script setup>
import { ref, reactive } from "vue"
const emits = defineEmits(["onClose", "onOpen"])
const props = defineProps({
dir: {
type: String,
default: "bottom",
},
type: {
type: String,
default: "bottom",
},
mask: {
type: Boolean,
default: false,
},
})
const position = reactive({
bottom: "flex-end",
top: "flex-start",
center: "center",
})
const popup = ref()
const close = () => {
emits("onClose")
popup.value.close()
}
const open = () => {
emits("onOpen")
popup.value.open()
}
const maskClose = (e) => {
if (props.mask) return
if (e.target.id === "msk") return close()
}
defineExpose({ open, close })
</script>
<style lang="scss" scoped>
.Mask {
position: relative;
z-index: 999999999;
display: flex;
justify-content: center;
width: 100vw;
height: 100vh;
// background: rgba(0, 0, 0, 0.5);
// backdrop-filter: blur(30rpx);
.wrapper {
flex: 1;
}
}
</style>

View File

@@ -0,0 +1,262 @@
<template>
<view class="pre-wrapper" :class="{ 'preview-two': props.preStyle == 'one', 'preview-one': props.preStyle == 'two' }">
<view v-if="activeBox" class="active-box" :class="{ disabled: disabled, active: active }" @tap="emits('activeClick')">
<image :src="imageList[disabled ? 0 : 1]" mode="scaleToFill" />
</view>
<view
class="preview-list"
:class="{
'last-list': isLast,
'last-none': props.borderNone,
}"
@tap="emits('click')"
>
<view class="content-box">
<image :src="props.img" class="img" />
<view class="content-describe">
<text class="title single-text-beyond">{{ props.title }}
<slot name="titleTag"/>
</text>
<text>{{ props.qrcId }}</text>
</view>
<slot>
<view class="content-state">
<view class="top">
<view class="spot" :style="{ backgroundColor: props.spot }"></view>
<view class="status">{{ props.status }}</view>
</view>
<text></text>
</view>
</slot>
</view>
<!-- 组件下部具名插槽 默认填充内容图标描述号码 -->
<slot name="bottom">
<view class="info" v-if="mchName || mchNo">
<image src="@/static/equipmentImg/mch-little.svg" mode="aspectFit" class="mch-img" />
<view class="name">{{ props.mchName }}</view>
<view class="number">{{ props.mchNo }}</view>
</view>
</slot>
</view>
</view>
</template>
<script setup>
/*
列表预览组件
默认插槽放到最右边
具名插槽在下部
*/
import { ref, reactive, nextTick, watchEffect } from 'vue'
const emits = defineEmits(['activeClick', 'click'])
const vdata = reactive({
infoIsShow: false, // info板块是否展示
})
const props = defineProps({
img: { type: String, default: '/static/equipmentImg/code-open.svg' },
title: { type: String, default: '' },
qrcId: { type: String, default: '' },
spot: { type: String, default: '#fff' },
status: { type: String, default: '' },
mchName: { type: String, default: '' },
mchNo: { type: String, default: '' },
isLast: { type: Boolean, default: false }, // 最后一个列表,下划线通底
borderNone: { type: Boolean, default: false }, // 最后一个下划线隐藏
preStyle: { type: String, default: '' }, // 预制的主题颜色,在最下方
disabled: { type: Boolean, default: false }, //是否禁用默认否
active: { type: Boolean, default: false }, //是否选中默认未选中
activeBox: { type: Boolean, default: false }, // 是否显示 勾选框 默认不显示
})
const imageList = ['/static/iconImg/icon-disabled.svg', '/static/iconImg/icon-active.svg']
</script>
<style scoped lang="scss">
.pre-wrapper {
display: flex;
align-items: center;
background-color: #fff;
padding: 30rpx;
position: relative;
box-sizing: border-box;
.active-box {
flex-shrink: 0;
flex-grow: 0;
display: flex;
justify-content: center;
align-items: center;
margin-right: 30rpx;
width: 36rpx;
height: 36rpx;
border-radius: 6rpx;
border: 2rpx solid #d9d9d9;
image {
width: 100%;
height: 100%;
}
}
.active {
background-color: #2980fd;
}
.disabled {
background-color: #f2f2f2;
}
}
.preview-list {
flex: 1;
display: flex;
flex-wrap: wrap;
// 下边框线
&::after {
content: '';
position: absolute;
bottom: 0;
right: 0;
height: 1px;
width: calc(100% - 30rpx);
background-color: #d9d9d9;
transform: scaleY(0.5);
}
// 纯图片
.img {
flex-grow: 0;
flex-shrink: 0;
width: 93rpx;
height: 93rpx;
margin-right: 25rpx;
}
// 左侧内容
.content-box {
flex: 1;
display: flex;
justify-content: space-between;
.content-describe {
flex: 1;
color: #8c8c8c;
font-size: 25rpx;
// width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
line-height: 35rpx;
.title {
width: 380rpx;
font-size: 33rpx;
color: #000;
line-height: 46rpx;
}
}
}
// 右侧内容 默认为状态点以及文字
.content-state {
display: flex;
flex-direction: column;
justify-content: space-between;
.top {
display: flex;
align-items: center;
.spot {
width: 10rpx;
height: 10rpx;
border-radius: 50%;
margin-right: 16rpx;
background-color: #000;
}
.status {
font-size: 30rpx;
line-height: 46rpx;
white-space: nowrap;
color: #666;
}
}
}
// 底部样式
.info {
width: 100%;
display: flex;
justify-content: center;
padding: 20rpx;
box-sizing: border-box;
background-color: #f7f7f7;
border-radius: 10rpx;
margin-top: 20rpx;
.mch-img {
width: 40rpx;
height: 40rpx;
flex-shrink: 0;
}
.name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin: 0 10rpx;
color: #000;
font-size: 28rpx;
flex-grow: 1;
}
.number {
white-space: nowrap;
color: #8c8c8c;
font-size: 28rpx;
}
}
}
.last-list {
// 下边框线
&::after {
content: '';
position: absolute;
bottom: 0;
right: 0;
height: 1rpx;
width: 100%;
background-color: #e5e5e5;
}
}
.last-none {
// 下边框线
&::after {
height: 0;
}
}
// 预制样式1 开启状态 标题白色 id 白色0.6
.preview-one {
background: rgba(0, 0, 0, 0.1);
.content-box {
.content-describe {
color: rgba(255, 255, 255, 0.6);
.title {
color: #ffffff;
}
}
}
}
// 预制样式2 关闭状态 标题白色 id 白色0.6
.preview-two {
background: rgba(0, 0, 0, 0.1) !important;
.content-box {
.content-describe {
color: rgba(255, 255, 255, 0.5);
.title {
color: rgba(255, 255, 255, 0.5);
}
}
}
}
</style>

View File

@@ -0,0 +1,156 @@
<template>
<view v-if="vdata.dataList.length > 0">
<view class="bind-title">{{ props.title }}</view>
<view class="layout" >
<scroll-view scroll-y="true" :style="{maxHeight: props.maxH}"
@scrolltoupper="reset" @scrolltolower="refresh"
>
<view v-for="(item, index) in vdata.dataList" :key="index">
<slot :info="item" :index="index"></slot>
</view>
</scroll-view>
</view>
</view>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { onShow , onLoad } from '@dcloudio/uni-app'
const props = defineProps({
title: { type: String, default: '' },
maxH: { type: String, default: '424rpx' }, // 最高高度
pd: { type: String, default: '30rpx 50rpx' },
requestFun: { type: Function, default: () => {} },
params: { type: Object, default: () => ({}) },
flag: { type: Boolean, default: true },
isonshow: { type: Boolean, default: true },
pageSize: { type: Number, default: 10 },
})
const emits = defineEmits(['reset', 'refresh'])
const vdata = reactive({
dataList: [], // 数据列表
})
const listParams = {} // 页面传参对象 getList方法中传的参数
const queryObj = reactive({
pageNumber: 1, // 默认是1
pageSize: props.pageSize, // 默认10条可传参修改
isLoad: false, // 是否为上拉加载
titlePage: '' // 总页数
})
const getList = (listParamObj) => {
// 非下拉刷新才显示加载提示
if (queryObj.isLoad) {
uni.showLoading({ title: '加载中' })
}
// listParamObj 父组件在函数中携带的请求参数与页码页数固定参数合并到一起并且当该参数对象在调用时有具体值会将页码重置为1
let paramObj // 参数合并
if (listParamObj) {
queryObj.pageNumber = 1
Object.keys(listParamObj).forEach((item) => {
listParams[item] = listParamObj[item]
})
vdata.dataList = []
}
paramObj = Object.assign(
{
pageNumber: queryObj.pageNumber,
pageSize: queryObj.pageSize
},
props.params,
listParams
)
props.requestFun(paramObj)
.then(({ bizData }) => {
uni.hideLoading()
queryObj.titlePage = Math.ceil(bizData.total / queryObj.pageSize) // 页码总数
let data = bizData.records ? bizData.records : bizData
// 下拉刷新重置数据, 上拉加载新增数据
if (queryObj.isLoad) {
vdata.dataList.push(...data)
} else {
vdata.dataList = data
}
})
.catch((err) => uni.hideLoading())
}
// 只在onshow时调用一次 请求方法
const onShowGetList = () => {
if (!props.flag) return false
uni.showLoading({ title: '加载中' })
queryObj.pageNumber = 1 // 页码固定为1
let paramObj = Object.assign(
{
// 合并请求参数
pageNumber: 1,
pageSize: queryObj.pageSize
},
props.params
)
props.requestFun(paramObj)
.then(({ bizData }) => {
uni.hideLoading()
queryObj.titlePage = Math.ceil(bizData.total / queryObj.pageSize)
let data = bizData.records ? bizData.records : bizData
vdata.dataList = data // 由于只在onShow 中调用一次,所以数据重新赋值
})
}
onShow(() => {
if (props.isonshow) {
onShowGetList()
}
})
onLoad(() => {
if (!props.isonshow) {
onShowGetList()
}
})
// 下拉刷新
const reset = () => {
queryObj.pageNumber = 1
queryObj.isLoad = false
getList()
}
// 上拉加载
const refresh = () => {
queryObj.pageNumber++
if (queryObj.pageNumber > queryObj.titlePage) {
queryObj.pageNumber = queryObj.pageNumber
return (queryObj.isLoad = false)
}
queryObj.isLoad = true
getList()
}
</script>
<style scoped lang="scss">
.layout {
margin: 30rpx 50rpx;
}
.bind-title {
margin-top: 20rpx;
color: #fff;
text-align: center;
}
</style>

View File

@@ -0,0 +1,109 @@
<template>
<view class="search-wrapper" :style="{ padding: wrapPd }">
<view class="search-input bdR16">
<image src="/static/iconImg/icon-search.svg" mode="scaleToFill" />
<input type="text" :placeholder="place" placeholder-style="#8C8C8C" :focus="isFocus" v-model="searchText" :disabled="isDisabled" @input="change" />
<view class="search-text" v-if="searchText || flag" @tap="search">{{ text }}</view>
</view>
<slot>
<view class="search-screen" v-if="props.screen" @tap="tapScreen">
<image src="/static/iconImg/icon-filter.svg" mode="scaleToFill" />
筛选
</view>
</slot>
</view>
</template>
<script setup>
import { onMounted, ref, watch } from 'vue'
import { debounce } from '@/util/debounce'
const props = defineProps({
screen: {
type: Boolean,
default: false,
},
place: {
type: String,
default: '搜索代理商名、代理商号、联系人手机号',
},
bgColor: {
type: String,
default: '#fff',
},
wrapPd: {
type: String,
default: '30rpx',
},
isFocus: { type: Boolean, default: false },
isDisabled: { type: Boolean, default: false },
})
onMounted(() => {
focus.value = props.isFocus
})
const focus = ref(false)
const text = ref('搜索')
const searchText = ref('')
const flag = ref(false)
// 点击筛选触发回调
const emits = defineEmits(['screen', 'search', 'resetSearch'])
const tapScreen = () => {
emits('screen')
}
const change = () => {
text.value = '搜索'
}
// 点击触发事件
function search() {
flag.value = true
if (text.value === '搜索') {
if (!searchText.value.trim()) return uni.showToast({ title: '请输入搜索条件', icon: 'none' })
text.value = '取消'
emits('search', searchText.value)
} else {
searchText.value = ''
text.value = '搜索'
emits('resetSearch', 'reset')
}
}
defineExpose({ searchText })
</script>
<style lang="scss" scoped>
.search-wrapper {
display: flex;
align-items: center;
.search-input {
flex: 1;
display: flex;
align-items: center;
padding: 21rpx;
background-color: #fff;
input {
flex: 1;
font-size: 27rpx;
}
image {
margin: 0 10rpx 0 10rpx;
width: 30rpx;
height: 30rpx;
}
.search-text {
font-size: 30rxp;
padding: 0 10rpx;
color: $primaryColor;
}
}
.search-screen {
display: flex;
align-items: center;
font-size: 30rpx;
color: rgba(0, 0, 0, 0.6);
image {
width: 36rpx;
height: 36rpx;
margin: 0 15rpx;
}
}
}
</style>

View File

@@ -0,0 +1,105 @@
<template>
<view class="upload-wrapper" :style="{ paddingLeft: pdLeft }">
<view
class="upload-main"
:style="{ padding: pd, borderColor: borderBg }"
:class="[borderNone != undefined ? 'borderNone' : '']"
>
<view class="upload-title" :style="{ color: textColor, fontSize: fontSize }"><slot name="title">{{ name }}</slot> </view>
<view class="upload-img">
<JeepayUpLoad
v-if="imgUrl == ''"
@uploadSuccess="uploadSuccess"
:imgUrl="value"
@clear="clearImg"
></JeepayUpLoad>
<image v-else :src="imgUrl" @tap="previewImage([imgUrl])" mode="aspectFill" />
</view>
</view>
</view>
</template>
<script setup>
import { watch, onBeforeUnmount, ref, onMounted } from "vue"
import JeepayUpLoad from "@/components/JeepayUpLoad/JeepayUpLoad"
import { addRules, clearOneRule } from "@/hooks/rules"
import valid from "@/hooks/validate"
const props = defineProps({
value: {
type: String,
},
pd: {
type: String,
default: " 30rpx 30rpx 30rpx 0",
},
borderNone: {
type: String,
},
pdLeft: {
type: String,
default: "30rpx",
},
imgUrl: {
type: String,
default: "",
},
borderBg: {
type: String,
default: "#f2f2f2",
},
fontSize: { type: String },
textColor: {
type: String,
default: "#000",
},
name: { type: String },
rules: { default: null },
})
onMounted(() => {
if (props.rules !== null) {
props.rules.Msg = "请上传" + props.name
addRules(props.rules)
}
})
const emits = defineEmits(["update:value"])
const uploadSuccess = (res) => {
emits("update:value", res.data)
}
const previewImage = (v) => {
if (!valid.httpOrHttps(v)) return false
uni.previewImage({
indicator: "number",
loop: true,
urls: v,
})
}
const clearImg = () => {
emits("update:value", "")
}
onBeforeUnmount(() => {
if (props.rules !== null) {
clearOneRule(props.rules)
}
})
</script>
<style lang="scss" scoped>
.upload-wrapper {
padding-left: 30rpx;
font-size: 33rpx;
.upload-main {
display: flex;
justify-content: space-between;
border-top: 1rpx solid #f2f2f2;
image {
width: 150rpx;
height: 150rpx;
border-radius: 10rpx;
}
}
}
.borderNone {
border: none !important;
}
</style>

View File

@@ -0,0 +1,100 @@
<template>
<view class="login-wrapper" :style="{ padding: pd }">
<view class="login-main">
<view class="login-title" v-if="title">{{ title }}</view>
<view class="login-input" :style="{ backgroundColor: bgColor }">
<input
:password="password"
:placeholder="place"
:type="type"
placeholder-style="color: #A6A6A6;"
:value="value"
@input="change($event)"
/>
<view class="login-right"> <slot></slot> </view>
</view>
</view>
</view>
</template>
<script setup>
import { watchEffect, onBeforeUnmount, onMounted } from "vue"
import { addRules, clearOneRule } from "@/hooks/rules"
const props = defineProps({
title: {
type: String,
default: "账号",
},
place: {
type: String,
default: "请输入登录名/手机号",
},
type: {
type: String,
default: "text",
},
value: {
type: String,
default: "",
},
type: {
type: String,
default: "text",
},
pd: {
type: String,
default: "50rpx",
},
bgColor: {
type: String,
default: "#f3f2f5",
},
password: { type: Boolean, default: false },
rules: { default: null },
})
const emits = defineEmits(["update:value"])
const change = (e) => {
emits("update:value", e.detail.value)
}
onMounted(() => {
if (props.rules !== null) {
props.rules.Msg = props.title
addRules(props.rules)
}
})
onBeforeUnmount(() => {
if (props.rules !== null) {
clearOneRule(props.rules)
}
})
</script>
<style lang="scss" scoped>
.login-wrapper {
padding: 50rpx;
.login-title {
margin-left: 20rpx;
margin-bottom: 20rpx;
font-size: 30rpx;
}
.login-input {
display: flex;
justify-content: space-between;
align-items: center;
height: 110rpx;
.login-right {
align-self: center;
}
border-radius: 16rpx;
background: #f3f2f5;
input {
flex: 1;
font-size: 33rpx;
height: 110rpx;
margin-left: 32rpx;
}
}
}
</style>

View File

@@ -0,0 +1,169 @@
<template>
<view class="s-wrapper" :style="{ borderRadius: props.bdR, backgroundColor: bgColor }">
<view class="s-screening">
<block v-for="(v, i) in list" :key="v.value">
<view class="s-scr-item" :class="{ selected: i == index }" @tap="onSelect(v, i)"> {{ v.text }} </view>
</block>
</view>
<view class="theCustom" :class="{ customer: index === props.list.findIndex((v) => v.value == 'customer') }">
<view class="time-picker">
<xp-picker
ref="star"
@confirm="confirm"
@cancel="emits('close')"
@tap="isShow"
mode="ymdhi"
:startTime="time.startDate"
>{{ time.startDate }}</xp-picker
>
<text></text>
<xp-picker
ref="end"
@confirm="confirmEnd"
@cancel="emits('close')"
mode="ymdhi"
@tap="isShow"
:startTime="time.endDate"
>{{ time.endDate }}</xp-picker
>
</view>
</view>
</view>
</template>
<script setup>
import { onMounted, reactive, ref } from "vue"
import { getDay } from "@/util/timeInterval.js"
const time = ref({
startDate: `${getDay(0, "-")} 00:00`,
endDate: `${getDay(0, "-")} 23:59`,
})
const emits = defineEmits(["search", "open", "close"])
const props = defineProps({
bdR: {
type: String,
default: "20rpx",
},
bgColor: { type: String },
list: {
type: Array,
default: [
// { value: 'all', text: '全部' },
{ value: "today", text: "今天" },
{ value: "yesterday", text: "昨天" },
{ value: "currMonth", text: "本月" },
{ value: "prevMonth", text: "上月" },
{ value: "customer", text: "自定义" },
],
},
index: {
type: Number,
default: 0,
},
})
const star = ref(null)
const end = ref(null)
const scrInfo = ref(0)
const isOpen = ref(false)
const onSelect = (val, i) => {
if (val.value != "customer") {
emits("search", { val, i })
} else {
emits("search", {
val: {
text: "自定义",
value: "customer",
time: `customDate_${time.value.startDate}:00_${time.value.endDate}:00`,
},
i,
})
props.index = props.list.findIndex((v) => v.value == "customer")
isOpen.value = true
}
}
//时间选择器确定
const confirm = (e) => {
time.value.startDate = e.value
confirmEnd({ value: time.value.endDate })
emits("close")
}
const confirmEnd = (e) => {
time.value.endDate = e.value
let i = props.list.findIndex((v) => v.value == "customer")
emits("search", {
val: {
text: "自定义",
value: `customDate_${time.value.startDate}:00_${time.value.endDate}:00`,
},
i,
})
emits("close")
}
const isShow = (e) => {
if (star.value.pickerVisible || end.value.pickerVisible) {
emits("open")
} else {
emits("close")
}
}
// const endIsShow = () => {
// if (end.value.pickerVisible) {
// emits("open")
// } else {
// emits("close")
// }
// }
</script>
<style lang="scss" scoped>
.s-wrapper {
background: rgba(0, 0, 0, 0.1);
padding: 20rpx 20rpx 20rpx 20rpx;
.s-screening {
display: flex;
justify-content: space-between;
.s-scr-item {
padding: 20rpx;
text-align: center;
font-size: 28rpx;
white-space: nowrap;
border-radius: 7rpx;
color: rgba(255, 255, 255, 0.7);
}
.selected {
background-color: #fff;
color: #7737fe;
}
}
.theCustom {
height: 0;
overflow: hidden;
transition: height 0.3s ease-in;
.time-picker {
display: flex;
justify-content: space-between;
align-items: center;
height: 90rpx;
padding: 0 30rpx;
margin-top: 10rpx;
background: #fff;
font-size: 28rpx;
border-radius: 7rpx;
color: $primaryColor;
text {
width: 40rpx;
height: 2rpx;
background-color: $primaryColor;
}
}
}
.customer {
height: 100rpx;
transition: height 0.3s ease-in;
}
}
</style>

View File

@@ -0,0 +1,59 @@
<template>
<view class="phone-wrapper pd50">
<view class="phone-main bgF bdR20 t-center">
<view class="tips fs27 c333 lh46"> 为了您的账号安全请联系客服进行账号注销处理客服电话 </view>
<view class="phone-Number fb">{{ phoneNumber }} </view>
<view class="phone-button">
<view @tap="cancel">取消</view>
<view class="call-phone fb" @tap="callPhone">拨打电话</view>
</view>
</view>
</view>
</template>
<script setup>
const props = defineProps({
phoneNumber: {
type: String,
default: '186 8866 6688'
}
})
const emits = defineEmits(['cancel'])
const callPhone = () => {
uni.makePhoneCall({
phoneNumber: props.phoneNumber
})
}
const cancel = () => {
emits('cancel')
}
</script>
<style lang="scss" scoped>
.phone-main {
.tips {
padding: 50rpx 50rpx 0 50rpx;
}
.phone-Number {
margin-top: 30rpx;
font-size: 36rpx;
color: #7737fe;
}
.phone-button {
display: flex;
margin-top: 50rpx;
border-top: 1rpx solid rgb(232, 230, 230);
view {
width: 50%;
height: 110rpx;
line-height: 110rpx;
color: #8c8c8c;
font-size: 33rpx;
}
.call-phone {
border-left: 1rpx solid rgb(232, 230, 230);
color: $primaryColor;
}
}
}
</style>

View File

@@ -0,0 +1,209 @@
<template>
<view>
<uni-popup ref="publicPopup" @maskClick="closeEd()">
<view class="store-body">
<view >
<view style="height: 20rpx"></view>
<view class="search">
<input type="text" v-model="params.bankName" placeholder="请搜索银行名称" />
<view @click.stop="searchHandle()">搜索</view>
</view>
</view>
<scroll-view style="height: 800rpx;" scroll-y="true" @scrolltoupper="scrolltoupper" @scrolltolower="scrolltolower">
<view class="jee-list hide-border">
<view class="jee-list-item " v-for="(item, index) in data.dataList" @click.stop="onClick(item, index)" :key="index">
<view class="jee-list-left">{{ item.bank_alias }}</view>
<view class="jee-list-right">
<image v-if="index == data.index" src="@/static/img/select-radio.svg" mode=""></image>
<image v-else src="@/static/img/un-raido.svg" />
</view>
</view>
<jeepayListNull :list="data.dataList"></jeepayListNull>
</view>
<view class="tip-text" v-if="data.dataList.length >0">{{ data.tipText }}</view>
</scroll-view>
</view>
</uni-popup>
</view>
</template>
<script setup>
import jeepayListNull from '@/components/jeepayListNull/jeepayListNull.vue'
import { onLoad, onShow, onBackPress } from '@dcloudio/uni-app';
import { ref, reactive } from 'vue';
import useBackPress from '@/hooks/useBackPress.js'
import { $bankCode } from '@/http/apiManager.js'
const emit = defineEmits(['bankInfo'])
const props = defineProps({
ifCode: ''
})
const data = reactive({
dataList: [],
index: -1,
tipText: '上拉加载更多',
isLoad: false // 是否为上拉加载
})
const publicPopup = ref(null);
// 请求参数
let params = {
settAccountType: '',
bankName: '',
pageSize: 50,
pageNumber: 1,
}
const closeEd = () => {
publicPopup.value.close()
// #ifdef H5 || APP-PLUS
inactive()
// #endif
}
// #ifdef H5 || APP-PLUS
const { active, inactive } = useBackPress(closeEd) // onBackPress 阻断返回
// #endif
const openHandle = type => {
// #ifdef H5 || APP-PLUS
active()
// #endif
params.settAccountType = type
publicPopup.value.open('bottom')
// 每次一打开请求参数重置params
params.pageNumber = 1
getList() // 请求列表数据
}
defineExpose({ openHandle })
const getList = () => {
data.tipText = '加载中';
$bankCode(props.ifCode ,params).then(({ bizData }) => {
data.titlePage = Math.ceil(bizData.total / params.pageSize); // 总页数
data.titlePage == params.pageNumber && data.titlePage != 1 ? (data.tipText = '暂无更多') : false;
if (bizData.records) data.tipText = '暂无更多';
if (data.isLoad) {
data.dataList.push(...bizData.records);
} else {
data.dataList = bizData.records;
}
})
}
// 搜索按钮
const searchHandle = () => {
params.pageNumber = 1
data.isLoad = false
getList()
}
// 滚动到顶部 下拉刷新
const scrolltoupper = () => {
data.isLoad = false
params.pageNumber = 1
getList()
}
// 滚动到底部 上拉加载
const scrolltolower = () => {
params.pageNumber++;
if (params.pageNumber > data.titlePage) {
params.pageNumber = params.pageNumber;
return data.isLoad = false
}
data.isLoad = true;
getList()
}
// 选择完毕后,将所有的信息抛出 由父组件接收
const onClick = (info, index) => {
data.index = index
emit('bankInfo', info)
closeEd()
}
</script>
<style scoped lang="scss">
.store-body {
background-color: #fff;
height: 900rpx;
border-top-left-radius: 20rpx;
border-top-right-radius: 20rpx;
}
.active {
background-color: #5476f1;
}
.search {
height: 80rpx;
background: #f5f6f7;
display: flex;
justify-content: space-between;
align-items: center;
flex-direction: row;
border-radius: $uni-border-radius-base;
margin: 0 20rpx;
padding: 0 20rpx;
input {
width: 80%;
}
view {
width: 120rpx;
height: 60rpx;
line-height: 60rpx;
text-align: center;
border-radius: 5rpx;
color: #175be6;
background: #fff;
}
}
.jee-list {
image {
width: 40rpx;
height: 40rpx;
}
.jee-list-item {
&::after {
width: calc(100% - 70rpx);
}
}
}
.tip-text {
margin: 31rpx 54rpx;
color: #ccc;
text-align: center;
}
.btns {
padding: 0 30rpx 120rpx;
box-sizing: border-box;
display: flex;
flex-direction: row;
& > view {
padding: 24rpx 0;
text-align: center;
border-radius: $uni-border-radius-base;
font-size: 30rpx;
}
.cancel {
width: 270rpx;
color: #808080;
margin-right: 30rpx;
box-shadow: #c5c7cc 0px 0px 1px;
}
.confirm {
flex-grow: 1;
background: #3981FF;
color: #fff;
}
}
</style>

View File

@@ -0,0 +1,83 @@
<template>
<view>
<uni-popup ref="backPopupRef" type="bottom" >
<view class="bank-body">
<scroll-view class="bank-list" scroll-y="true">
<view class="item" v-for="(item, index) in props.bankList" @click="selectHandle(index)">{{ item[props.field] }}</view>
</scroll-view>
</view>
</uni-popup>
</view>
</template>
<script setup>
import { ref, reactive, watch } from 'vue'
const props = defineProps({
bankList: {type: Array, default: () => []},
field: { type: String, default: 'bank_branch_name' }, // 默认渲染的字段
})
const backPopupRef = ref()
// 开启与关闭弹窗 供父组件调用
const open = () => backPopupRef.value.open()
const close = () => backPopupRef.value.close()
const emit = defineEmits(['bankBranchInfo'])
// 向父组件传递已选信息
const selectHandle = index => {
emit('bankBranckInfo', props.bankList[index])
close()
}
defineExpose({ open, close })
</script>
<style scoped lang="scss">
.bank-body {
width: 100%;
height: 70vh;
background-color: #fff;
border-top-right-radius: 20rpx;
border-top-left-radius: 20rpx;
padding: 20rpx 30rpx;
box-sizing: border-box;
position: relative;
}
.search {
width: calc(100% - 80rpx);
position: absolute;
top: 20rpx;
left: 30rpx;
display: flex;
justify-content: space-between;
align-items: center;
border: 1px solid #ddd;
border-radius: 10rpx;
padding: 5rpx 10rpx;
input {
flex-grow: 1;
margin-right: 20rpx;
}
.btn {
padding: 10rpx 30rpx;
text-align: center;
color:#fff;
background: #0041c4;
}
}
.bank-list {
padding-top: 80rpx;
overflow: scroll;
height: 90%;
.item {
padding: 10rpx 0;
}
}
</style>

View File

@@ -0,0 +1,224 @@
<template>
<view class="verification_code">
<view class="verification_code_continor">
<view
class="verification_code_item"
v-for="(item, index) in latticeNum"
:key="index"
:style="
blurShowLocal &&
(inputValues.length === index || (inputValues.length === latticeNum && index === latticeNum - 1))
? borderCheckStyle
: borderStyle
"
@tap="latticeFoc(index)"
>
<block v-if="inputValues[index]">
<view v-if="ciphertextSty == 1" class="point"></view>
<block v-else>{{ ciphertextSty == 2 ? "*" : inputValues[index] }}5</block>
</block>
</view>
</view>
<input
type="number"
:type="inputType"
focus
:maxlength="lattice"
class="input_info"
v-model="text"
@input="inputVal"
@blur="blur"
@focus="focus"
ref="input"
/>
</view>
</template>
<script>
export default {
props: {
// 输入框框框的个数
latticeNum: {
type: Number,
default: 4,
},
// 未选中样式
borderStyle: {
type: String,
default: "border:1rpx solid #DADEE6;border-radius: 12rpx;",
},
// 选中的样式
borderCheckStyle: {
type: String,
default: "border:2rpx solid #7737FE;border-radius: 12rpx;",
},
// input类型
inputType: {
type: String,
default: "number",
},
// 失去焦点后是否继续显示,当前所对焦的输入框(样式)
blurShow: {
type: Boolean,
default: false,
},
// 密文样式 1 圆点样式 2 星号 * 样式 默认为0 无密文
ciphertextSty: {
type: Number,
default: 2,
},
// 是否允许修改/填写某一个框框的值
updateOne: {
type: Boolean,
default: false,
},
},
data() {
return {
inputValues: [], //输入的值
blurShowLocal: true,
// cursor:null
lattice: 6,
keyCodeArr: [],
text: "",
}
},
mounted() {
this.blurShowLocal = this.blurShow
// if (this.updateOne) {
// let arr = [];
// for (let i = 0; i < this.latticeNum; i++) {
// arr.push(' ');
// }
// this.inputValues = arr;
// }
},
methods: {
/**
* 输入框的值
*/
inputVal(e) {
//#ifdef MP-WEIXIN
let keyCode = e.detail.keyCode
if (this.inputValues.length < 6) {
if (keyCode === 8 && this.inputValues.length > 0) {
this.inputValues.pop()
} else {
if (keyCode < 48 || keyCode > 57) {
uni.showToast({
icon: "none",
title: "请输入数字",
duration: 1000,
})
} else {
this.keyCodeArr.push(keyCode)
if (this.keyCodeArr.length < 6) {
this.lattice += 1
} else if (this.keyCodeArr.length >= 6) {
this.keyCodeArr = []
this.lattice = 6
}
let codeObj = {
48: "0",
49: "1",
50: "2",
51: "3",
52: "4",
53: "5",
54: "6",
55: "7",
56: "8",
57: "9",
}
this.inputValues.push(codeObj[keyCode])
this.$emit("getInputVerification", this.inputValues.join(""))
// console.log(this.inputValues)
}
}
} else if (keyCode === 8 && this.inputValues.length > 0) {
this.inputValues.pop()
return
}
//#endif
//#ifdef APP || H5
this.inputValues = this.text
this.$emit("getInputVerification", this.inputValues)
//#endif
},
// 设置验证码的值
/**
* verificationCodeValue 数组
*/
setVerificationCode(verificationCodeValue = []) {
this.inputValues = verificationCodeValue
},
/**
* 清空验证码的值
*/
cleanVal() {
this.inputValues = []
this.text = ""
},
latticeFoc(index) {},
blur() {
!this.blurShow ? (this.blurShowLocal = false) : ""
},
focus() {
!this.blurShow ? (this.blurShowLocal = true) : ""
},
},
}
</script>
<style lang="scss">
.verification_code {
// width: 260rpx;
// margin: 20rpx auto;
position: relative;
overflow: hidden;
.verification_code_continor {
display: flex;
text-align: center;
justify-content: center;
// background-color: beige;
.verification_code_item {
box-sizing: border-box;
width: 80rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
display: flex;
background-color: #fff;
// border-radius: 12rpx;
margin-top: 6rpx;
}
.verification_code_item:not(:first-child) {
margin-left: 20rpx;
}
.point {
width: 20rpx;
height: 20rpx;
background-color: black;
border-radius: 200px;
}
}
.input_info {
width: 200%;
height: 100rpx;
border: 1px solid $primaryColor;
position: absolute;
opacity: 0;
-khtml-opacity: 0;
top: -10rpx;
left: -100%;
}
}
</style>

View File

@@ -0,0 +1,105 @@
const isLeapYear = y => y % 4 == 0 && y % 100 != 0 || y % 100 == 0 && y % 400 == 0
const variables = {
y: {
text: "年",
range: [null, null]
},
m: {
text: "月",
range: [1, 12]
},
d: {
text: "日",
range: [1, 31]
},
h: {
text: "时",
range: [0, 23]
},
i: {
text: "分",
range: [0, 59]
},
s: {
text: "秒",
range: [0, 59]
}
}
export function templateFactory({
mode,
value,
yearRange
}) {
const [start, end] = yearRange
let ret = {}
for (const key of mode) {
ret[key] = variables[key]
}
if (mode.indexOf("y") !== -1) ret['y'].range = [start || 2016, end || new Date().getFullYear()]
if (mode.indexOf("d") !== -1) {
const date = getDate(value || getLocalTime(mode))
ret['d'].range = [1, date]
}
return ret
}
export function getDate(dt) {
const s = dt.substring(0, dt.lastIndexOf("-"))
let year, month
const d = new Date()
switch (s.length) {
case 0:
year = d.getFullYear()
month = d.getMonth() + 1
break;
case 2:
year = d.getFullYear()
month = parseInt(s)
break;
default:
const [y, m] = s.split("-")
year = parseInt(y)
month = parseInt(m)
break;
}
const days = [31, isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
return days[month - 1]
}
export function getLocalTime(fmt) {
if (!fmt) return null
const da = new Date()
const y = fmtNumber(da.getFullYear()),
m = fmtNumber(da.getMonth() + 1),
d = fmtNumber(da.getDate()),
h = fmtNumber(da.getHours()),
i = fmtNumber(da.getMinutes()),
s = fmtNumber(da.getSeconds())
const types = {
'y': `${y}`,
'm': `${m}`,
'd': `${d}`,
'h': `${h}`,
'i': `${i}`,
's': `${s}`,
'ym': `${y}-${m}`,
'md': `${m}-${d}`,
'hi': `${h}:${i}`,
'is': `${i}:${s}`,
'ymd': `${y}-${m}-${d}`,
'his': `${h}:${i}:${s}`,
'mdh': `${m}-${d} ${h}`,
'ymdh': `${y}-${m}-${d} ${h}`,
'mdhi': `${m}-${d} ${h}:${i}`,
'mdhis': `${m}-${d} ${h}:${m}:${s}`,
'yd':`${y}-${d}`,
'ymdhi': `${y}-${m}-${d} ${h}:${i}`,
'ymdhis': `${y}-${m}-${d} ${h}:${i}:${s}`,
}
return types[fmt]
}
export function fmtNumber(n) {
// return n.toString().padStart(2,"0")
return n > 9 ? n + "" : "0" + n
}
export function time2Timestamp(timer) {
return new Date(timer).getTime()
}

View File

@@ -0,0 +1,345 @@
<template>
<view>
<view v-if="hasSlot" @tap="show">
<slot />
</view>
<view class="xp-picker" :style="{ visibility: pickerVisible ? 'visible' : 'hidden' }">
<view
class="xp-picker-mask"
:class="{ 'xp-picker-animation': animation }"
:style="{ opacity: pickerVisible ? 0.6 : 0 }"
@tap="_cancel"
></view>
<view
class="xp-picker-container"
:class="{ 'xp-picker-container--show': pickerVisible, 'xp-picker-animation': animation }"
>
<view v-if="actionPosition === 'top'" class="xp-picker-action">
<view class="xp-picker-action--cancel" @tap="_cancel">取消</view>
<view class="xp-picker-action--confirm" @tap="_confirm">确定</view>
</view>
<view v-if="isError" class="xp-picker-error" :style="{ height: height + 'vh' }">
<text>Errorplease check your configuration</text>
<text>请检查你的配置 查看控制台错误信息</text>
</view>
<picker-view
v-else
:style="{ height: height + 'vh' }"
indicator-style="height:40px;"
:value="selected"
@change="_change"
>
<picker-view-column v-for="(k, i) in modeArr" :key="i" class="xp-picker-column">
<view class="xp-picker-list-item" v-for="(item, index) in cols[i]" :key="index">
{{ item + units[i] }}
</view>
</picker-view-column>
</picker-view>
<view v-if="actionPosition === 'bottom'" class="xp-picker-btns">
<view class="xp-button xp-button--cancel" @tap="_cancel">取消</view>
<view class="xp-button xp-button--confirm" @tap="_confirm">确定</view>
</view>
</view>
</view>
</view>
</template>
<script>
import tool from "@/util/tool.js"
import { templateFactory, getLocalTime, fmtNumber, time2Timestamp, getDate } from "./util.js"
export default {
name: "XpPicker",
data() {
return {
isError: true,
isConfirm: false,
pickerVisible: false,
template: {},
cols: [],
selected: [],
}
},
props: {
startOrEnd: {
type: String,
default: "start",
},
startTime: {
type: String,
default: "",
},
endTime: {
type: String,
default: "",
},
mode: {
type: String,
default: "ymd",
},
animation: {
type: Boolean,
default: true,
},
height: {
type: [Number, String],
default: 35,
},
"action-position": {
type: String,
default: "bottom",
},
"year-range": {
type: Array,
default: () => [2010, 2035],
},
value: {
type: String,
default: null,
},
history: {
type: Boolean,
default: false,
},
},
watch: {
mode() {
this.render()
},
},
computed: {
hasSlot() {
return !!this.$slots["default"]
},
modeArr() {
return this.mode.split("")
},
units() {
const arr = []
for (const k in this.template) {
if (this.mode.indexOf(k) !== -1) arr.push(this.template[k].text)
}
return arr
},
},
created() {
this.render()
},
methods: {
render() {
this.assert() //检查用户配置
this.template = templateFactory(this) //生成所需列 默认模板
this.initCols() //根据模板 初始化列
this.initSelected() //设置默认值
},
assert() {
if ("ymdhis".indexOf(this.mode) === -1) {
throw new Error("render errorillegal 'mode'")
}
if (getLocalTime(this.mode) == undefined) {
throw new Error("render errorthe 'mode' is not found")
}
if (this.value != null) {
const arr = this.value.split(/-|:|\s/)
if (arr.length != this.modeArr.length) {
throw new Error("render errorbecause the 'value' cannot be formatted as 'mode'")
}
}
if (this.yearRange.length !== 2) {
throw new Error("render errorbecause the length of array 'year-rang' must be 2")
}
this.isError = false
},
initCols() {
for (const k of this.mode) {
const range = this.template[k].range
this.fillCol(k, ...range)
}
},
initSelected() {
const v = this.value || getLocalTime(this.mode)
if (this.startOrEnd === "start") {
this.setSelected(this.startTime)
} else {
this.setSelected(this.endTime)
}
},
fillCol(k, s, e) {
const index = this.mode.indexOf(k)
let arr = []
for (let i = s; i <= e; i++) arr.push(fmtNumber(i))
this.$set(this.cols, index, arr)
},
//dt 时间字符串 如 '2020-02-16'
setSelected(dt) {
const arr = dt.split(/-|:|\s/)
const a = this.cols
for (let i = 0; i < a.length; i++) this.$set(this.selected, i, a[i].indexOf(arr[i]))
},
resolveCurrentDt() {
let str = ""
for (let i = 0; i < this.selected.length; i++) str += this.cols[i][this.selected[i]] + this.units[i]
let dt = str
.replace("年", "-")
.replace("月", "-")
.replace("日", " ")
.replace("时", ":")
.replace("分", ":")
.replace("秒", "")
if (!this.mode.endsWith("s")) dt = dt.substring(0, dt.length - 1)
dt = dt.split("undefined").join("00")
return dt
},
show() {
if (this.history) {
if (!this.isConfirm) this.initSelected()
} else this.initSelected()
this.pickerVisible = true
},
_confirm() {
if (!this.isError) this.$emit("confirm", this._getResult())
if (!this.isConfirm) this.isConfirm = true
this.pickerVisible = false
},
_getResult() {
const detail = {
value: this.resolveCurrentDt(),
}
const tp = time2Timestamp(detail.value)
if (!isNaN(tp)) detail.timestamp = tp
return detail
},
_cancel() {
this.$emit("cancel")
this.pickerVisible = false
},
_change(e) {
let col
const newValue = e.detail.value
for (let i = 0; i < newValue.length; i++) {
if (newValue[i] !== this.selected[i]) {
col = this.modeArr[i]
break
}
}
this.selected = newValue
const index = this.mode.indexOf("d")
if (index !== -1 && (col === "y" || col === "m")) {
const currentDt = this.resolveCurrentDt()
this.fillCol("d", 1, getDate(currentDt))
}
},
},
}
</script>
<style scoped lang="scss">
.xp-picker {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 999;
font-size: 30rpx;
}
.xp-picker-container {
position: fixed;
bottom: 0;
transform: translateY(100%);
z-index: 999;
width: 100%;
background-color: #fff;
visibility: hidden;
}
.xp-picker-container--show {
transform: translateY(0);
visibility: visible;
}
.xp-picker-mask {
z-index: 998;
width: 100%;
height: 100%;
background-color: rgb(0, 0, 0);
}
.xp-picker-animation {
transition: all 0.25s;
}
.xp-picker-error {
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #ff0000;
}
.xp-picker-action {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
height: 90rpx;
padding: 0 28rpx;
box-sizing: border-box;
position: relative;
font-size: 34rpx;
border-bottom: 0.5px solid #e5e5e5;
}
.xp-picker-btns {
width: 100%;
display: flex;
justify-content: space-around;
align-items: center;
height: 160rpx;
padding: 0 20rpx;
box-sizing: border-box;
position: relative;
}
.xp-button {
height: 45px;
}
.xp-button--cancel {
width: 115px;
border-radius: 5px;
background: #fff;
border: 0.5px solid #c5c7cc;
}
.xp-button--confirm {
width: 180px;
height: 45px;
border-radius: 5px;
background: #3981ff;
border: 1.5px solid #3981ff;
color: #fff;
}
.xp-button--confirm,
.xp-button--cancel {
display: flex;
flex-wrap: nowrap;
justify-content: center;
align-items: center;
}
.xp-picker-column {
text-align: center;
border: none;
font-size: 34rpx;
}
.xp-picker-list-item {
display: flex;
justify-content: center;
align-items: center;
height: 40px;
}
</style>