同步代码
This commit is contained in:
37
components/Button/Button.vue
Normal file
37
components/Button/Button.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<!--
|
||||
组件功能: 对button的封装
|
||||
@author terrfly
|
||||
@site https://www.jeequan.com
|
||||
@date 2022/12/05 14:39
|
||||
-->
|
||||
|
||||
<template>
|
||||
<button class="jeepay-btn" hover-class="jeepay-hover-button">
|
||||
<slot />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
const props = defineProps({
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.jeepay-btn {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 110rpx;
|
||||
font-size: 33rpx;
|
||||
font-weight: 500;
|
||||
color: $J-color-tff;
|
||||
border-radius: 20rpx;
|
||||
background: $jeepay-bg-primary;
|
||||
box-shadow: 0 20rpx 60rpx -20rpx rgba(0,84,210,0.5);
|
||||
&.jeepay-hover-button {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
133
components/JAgree/JAgree.vue
Normal file
133
components/JAgree/JAgree.vue
Normal file
@@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<uni-popup ref="refPopup" type="center" @change='change' :safe-area="false">
|
||||
<view class="popup-wrapper">
|
||||
<view class="title">用户服务协议与隐私政策</view>
|
||||
<view class="content">
|
||||
如需登录,请先认真阅读并同意{{ $appName }}
|
||||
<text class="info" @tap="go.to(service)">《用户服务协议》</text>
|
||||
与<text class="info" @tap="toPrivacy">《隐私政策》</text>。
|
||||
</view>
|
||||
<view class="but-wrapper">
|
||||
<view class="but-item" hover-class="touch-button" @tap="disagreeClose">拒绝</view>
|
||||
<button class="but-item agree" id="agree-btn" open-type="agreePrivacyAuthorization"
|
||||
@agreeprivacyauthorization="handAgree">同意</button>
|
||||
</view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, onMounted } from 'vue'
|
||||
import go from '@/commons/utils/go.js'
|
||||
|
||||
const props = defineProps({
|
||||
service: { type: String },
|
||||
privacy: { type: String }
|
||||
})
|
||||
const refPopup = ref(null)
|
||||
const emits = defineEmits(['agree'])
|
||||
const open = () => {
|
||||
getPrivacy()
|
||||
refPopup.value.open()
|
||||
}
|
||||
const vdata = reactive({})
|
||||
const close = () => refPopup.value.close()
|
||||
const toPrivacy = () => {
|
||||
// #ifdef APP-PLUS
|
||||
if (props.privacy) return go.to(props.privacy)
|
||||
// #endif
|
||||
// 打开小程序隐私政策
|
||||
// #ifdef MP-WEIXIN
|
||||
wx.openPrivacyContract(
|
||||
{
|
||||
fail: () => {
|
||||
uni.showToast({
|
||||
title: '打开失败请稍后重试', // 打开失败
|
||||
icon: 'none'
|
||||
})
|
||||
},
|
||||
}
|
||||
)
|
||||
// #endif
|
||||
|
||||
}
|
||||
function disagreeClose () {
|
||||
close()
|
||||
}
|
||||
// 获取微信你用户是否同意过隐私政策
|
||||
const getPrivacy = () => {
|
||||
wx.getPrivacySetting({
|
||||
success: (r) => {
|
||||
Object.assign(vdata, r)
|
||||
if (vdata.needAuthorization) {
|
||||
wx.onNeedPrivacyAuthorization(res => {
|
||||
vdata.resolve = res
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
const handAgree = () => {
|
||||
if (vdata.needAuthorization) {
|
||||
vdata.resolve({ buttonId: 'agree-btn', event: 'agree' })
|
||||
}
|
||||
emits('agree')
|
||||
close()
|
||||
}
|
||||
defineExpose({ open, close })
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.popup-wrapper {
|
||||
width: 600rpx;
|
||||
border-radius: 20rpx;
|
||||
background: #FFF;
|
||||
overflow: hidden;
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 30rpx 0 50rpx;
|
||||
text-align: center;
|
||||
color: rgba(0, 0, 0, 0.50);
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding-bottom: 70rpx;
|
||||
margin: 0 auto;
|
||||
width: 500rpx;
|
||||
color: #000;
|
||||
font-size: 28rpx;
|
||||
line-height: 1.5;
|
||||
|
||||
.info {
|
||||
color: $v-color-t21;
|
||||
}
|
||||
}
|
||||
|
||||
.but-wrapper {
|
||||
display: flex;
|
||||
|
||||
.but-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 110rpx;
|
||||
color: rgba(0, 0, 0, 0.70);
|
||||
font-size: 32rpx;
|
||||
font-weight: 400;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.agree {
|
||||
background: $jeepay-bg-primary;
|
||||
color: #fff;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
185
components/JAppPopup/JAppPopup.vue
Normal file
185
components/JAppPopup/JAppPopup.vue
Normal file
@@ -0,0 +1,185 @@
|
||||
<template>
|
||||
<!-- 修改应用弹窗 -->
|
||||
<uni-popup ref="popup" type="bottom" mask-background-color="rgba(0,0,0,.5)" :safe-area="false" @maskClick="emits('cancel')">
|
||||
<view class="card-wrapper">
|
||||
<view class="input-search">
|
||||
<uni-easyinput
|
||||
v-model="vadta.searchInfo"
|
||||
prefixIcon="search"
|
||||
:inputBorder="false"
|
||||
:clearable="false"
|
||||
:styles="styles"
|
||||
type="text"
|
||||
placeholder="搜索门店名称、ID"
|
||||
placeholderStyle=" color: rgba(0,0,0,0.85);font-size: 30rpx;"
|
||||
/>
|
||||
<view class="search-button">搜索</view>
|
||||
</view>
|
||||
<!-- 循环开始部分 -->
|
||||
<view class="store">
|
||||
<view class="left">
|
||||
<view class="store-dot"></view>
|
||||
<view class="app-info">
|
||||
<view class="app-name">应用名称</view>
|
||||
<view class="app-code">62a55637e4b07ea5b8717ca3</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 循环结束部分 -->
|
||||
<view class="footer-wrapper">
|
||||
<view class="footer-main">
|
||||
<view class="tips" v-if="tips">{{ tips }}</view>
|
||||
<view class="footer-button">
|
||||
<view class="flex-center" hover-class="touch-button" @tap="close">取消</view>
|
||||
<view class="confirm flex-center" hover-class="touch-button">确认</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
const emits = defineEmits(['cancel', 'confirm'])
|
||||
const props = defineProps({
|
||||
isAll: { type: Boolean, default: false }, //是否展示全部门店 默认false
|
||||
tips: { type: String }, //提示信息 传入则展示
|
||||
})
|
||||
const styles = reactive({
|
||||
backgroundColor: '#f7f7f7',
|
||||
height: '90rpx',
|
||||
borderRadius: '10rpx',
|
||||
color: '#000',
|
||||
})
|
||||
const vadta = reactive({
|
||||
searchInfo: '',
|
||||
})
|
||||
const popup = ref(null)
|
||||
const open = (val) => {
|
||||
popup.value.open()
|
||||
}
|
||||
const close = () => {
|
||||
emits('cancel')
|
||||
popup.value.close()
|
||||
}
|
||||
const confirm = () => {}
|
||||
onMounted(() => {
|
||||
open()
|
||||
})
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.card-wrapper {
|
||||
border-radius: 32rpx 32rpx 0 0;
|
||||
background-color: #fff;
|
||||
overflow: hidden;
|
||||
min-height: 70vh;
|
||||
.input-search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 10rpx;
|
||||
margin: 30rpx;
|
||||
border-radius: 10rpx;
|
||||
background-color: #f7f7f7;
|
||||
.search-button {
|
||||
padding: 24rpx 30rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #2980fd;
|
||||
}
|
||||
}
|
||||
.store {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 40rpx;
|
||||
height: 170rpx;
|
||||
font-size: 30rpx;
|
||||
.left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.store-dot {
|
||||
align-self: start;
|
||||
position: relative;
|
||||
top: 2rpx;
|
||||
margin-right: 20rpx;
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
background-color: #d7d8d9;
|
||||
border-radius: 50%;
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 50%;
|
||||
height: 50%;
|
||||
border-radius: 50%;
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
.active-dot {
|
||||
background-color: #2980fd;
|
||||
}
|
||||
}
|
||||
.app-info {
|
||||
.app-code {
|
||||
margin-top: 15rpx;
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
.all-store::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 40rpx;
|
||||
right: 40rpx;
|
||||
height: 1rpx;
|
||||
background-color: #ededed;
|
||||
}
|
||||
.footer-wrapper {
|
||||
height: 186rpx;
|
||||
.footer-main {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: env(safe-area-inset-bottom);
|
||||
border-top: 1rpx solid #ededed;
|
||||
.tips {
|
||||
margin: 20rpx;
|
||||
text-align: center;
|
||||
font-size: 27rpx;
|
||||
color: #a6a6a6;
|
||||
}
|
||||
.footer-button {
|
||||
padding: 0 30rpx;
|
||||
margin-top: 30rpx;
|
||||
padding-bottom: 30rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
view {
|
||||
width: 330rpx;
|
||||
height: 110rpx;
|
||||
font-size: 33rpx;
|
||||
font-weight: 500;
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
border-radius: 20rpx;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
.confirm {
|
||||
color: #fff;
|
||||
background: jeepay-bg-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
57
components/JCell/JCell.vue
Normal file
57
components/JCell/JCell.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<!-- 单元格布局 -->
|
||||
<view
|
||||
class="cell-main"
|
||||
:hover-class="isTouch ? 'touch-hover' : ''"
|
||||
:style="{ '--border-width': borderWidth, marginTop: mT + 'rpx', '--m-rl': mRL + 'rpx', backgroundColor: bgColor }"
|
||||
>
|
||||
<view class="title" :style="{ color: color }"> {{ title }} </view>
|
||||
<slot name="right">
|
||||
<image src="/static/iconImg/icon-arrow-right.svg" mode="scaleToFill" />
|
||||
</slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
title: { type: String }, //标题
|
||||
mT: { type: Number }, //上边距
|
||||
mRL: { type: Number }, //左右两侧边距
|
||||
bgColor: { type: String }, //背景颜色
|
||||
color: { type: String }, //标题字体颜色
|
||||
borderWidth: { type: String, default: '100vw' }, //边框距离左侧距离 默认100vw 没有边框
|
||||
isTouch: { type: Boolean, default: true }, //是否有触摸反馈
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cell-main {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-left: var(--m-rl);
|
||||
margin-right: var(--m-rl);
|
||||
height: 120rpx;
|
||||
.title {
|
||||
margin-left: 40rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 400;
|
||||
}
|
||||
image {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
}
|
||||
background-color: #fff;
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
width: calc(100vw - var(--border-width));
|
||||
height: 1rpx;
|
||||
background-color: #ededed;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
65
components/JDetailsCell/JDetailsCell.vue
Normal file
65
components/JDetailsCell/JDetailsCell.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<template v-if="subTitle">
|
||||
<view class="details-wrapper" style="align-items: flex-start; padding-top: 40rpx">
|
||||
<view class="details-title">{{ title }}</view>
|
||||
<view>
|
||||
<view class="sub-title">
|
||||
<view class="details-info single-text-beyond"> {{ subTitle }}</view>
|
||||
<image src="/static/iconImg/icon-arrow-small.svg" mode="scaleToFill" />
|
||||
</view>
|
||||
<view class="sub-info details-info single-text-beyond">{{ info }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<template v-else>
|
||||
<view class="details-wrapper">
|
||||
<view class="details-title">{{ title }}</view>
|
||||
<view class="details-info single-text-beyond">{{ info }}</view>
|
||||
<image src="/static/iconImg/icon-arrow-right.svg" mode="scaleToFill" />
|
||||
</view>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
title: { type: String }, //标题
|
||||
info: [String, Number], //内容
|
||||
subTitle: { type: String }, //副标题
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.details-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 40rpx;
|
||||
min-height: 120rpx;
|
||||
font-size: 30rpx;
|
||||
background-color: #fff;
|
||||
.details-title {
|
||||
width: 200rpx;
|
||||
color: #4c4c4c;
|
||||
}
|
||||
.details-info {
|
||||
flex: 1;
|
||||
width: 334rpx;
|
||||
}
|
||||
image {
|
||||
width: 108rpx;
|
||||
height: 120rpx;
|
||||
}
|
||||
}
|
||||
.sub-title {
|
||||
display: flex;
|
||||
image {
|
||||
width: 108rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
}
|
||||
.sub-info {
|
||||
margin-top: 20rpx;
|
||||
font-size: 30rpx;
|
||||
color: #a1a1a1;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
</style>
|
||||
36
components/JDetailsSwitch/JDetailsSwitch.vue
Normal file
36
components/JDetailsSwitch/JDetailsSwitch.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<!-- 详情页 带开关卡片布局 -->
|
||||
<view class="s-wrapper">
|
||||
<view class="s-title">
|
||||
<text>{{ title }}</text>
|
||||
<slot name="titleRight" />
|
||||
</view>
|
||||
<view class="s-tips">{{ subTitle }}</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
title: { type: String }, //标题
|
||||
subTitle: { type: String }, //提示信息
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.s-wrapper {
|
||||
padding: 40rpx;
|
||||
margin: 30rpx 35rpx;
|
||||
border-radius: $J-b-r32;
|
||||
background-color: #fff;
|
||||
.s-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 30rpx;
|
||||
color: #4d4d4d;
|
||||
}
|
||||
.s-tips {
|
||||
font-size: 25rpx;
|
||||
color: #808080;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
164
components/JKeyboard/JKeyboard.vue
Normal file
164
components/JKeyboard/JKeyboard.vue
Normal file
@@ -0,0 +1,164 @@
|
||||
<template>
|
||||
<view class="k-wrapper">
|
||||
<view class="k-store-name">
|
||||
<view class="k-store-title flex-center">收款门店:</view>
|
||||
<view class="flex-center" @tap="emits('selectedStore')"> {{ storeName || '选择门店' }} <image src="/static/iconImg/icon-arrow-black.svg" mode="scaleToFill" /></view>
|
||||
</view>
|
||||
<view class="k-main">
|
||||
<block v-for="(v, i) in keyList" :key="i">
|
||||
<view :class="[v.clsName]" hover-class="touch-number" hover-stay-time="150" v-if="v.type == 'number'" @tap="boardDown(v.value)">{{ v.value }}</view>
|
||||
<view :class="[v.clsName]" hover-class="touch-number" hover-stay-time="150" v-if="v.type == 'image'" @tap="boardDown(v.value)">
|
||||
<image :src="v.imgUrl" mode="scaleToFill" />
|
||||
</view>
|
||||
<view :class="[v.clsName]" hover-class="touch-button" hover-stay-time="150" v-if="v.type == 'button'" @tap="boardDown(v.value)">
|
||||
<image :src="v.imgUrl" mode="scaleToFill" />
|
||||
<text>{{ v.text }}</text>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
const emits = defineEmits(['boardDown', 'selectedStore',"update:value"])
|
||||
/***
|
||||
* 键盘渲染集合
|
||||
* boardDown 回调事件 回调参数 value
|
||||
* selectedStore 回调事件 选择门店 回调参数 无
|
||||
* storeName 门店名称
|
||||
* */
|
||||
const props = defineProps({
|
||||
storeName: { type: String }, //门店名称
|
||||
|
||||
point: { type: Number, default: 2 }, //限制小数点后几位 默认两位
|
||||
maxLength: { type: Number, default: 10 }, //最大输入长度 包含小数点
|
||||
value:{type:[String,Number],default:'0'}
|
||||
})
|
||||
const keyList = reactive([
|
||||
{ type: 'number', clsName: 'k-number', value: '1' },
|
||||
{ type: 'number', clsName: 'k-number', value: '2' },
|
||||
{ type: 'number', clsName: 'k-number', value: '3' },
|
||||
{ type: 'number', clsName: 'k-number', value: '4' },
|
||||
{ type: 'number', clsName: 'k-number', value: '5' },
|
||||
{ type: 'number', clsName: 'k-number', value: '6' },
|
||||
{ type: 'number', clsName: 'k-number', value: '7' },
|
||||
{ type: 'number', clsName: 'k-number', value: '8' },
|
||||
{ type: 'number', clsName: 'k-number', value: '9' },
|
||||
{ type: 'number', clsName: 'k-number', value: '.' },
|
||||
{ type: 'number', clsName: 'k-number k-zero', value: '0' },
|
||||
{ type: 'image', clsName: 'k-number', value: 'deleted', imgUrl: '/static/iconImg/icon-delete.svg' },
|
||||
{ type: 'button', clsName: 'k-button', value: 'scan', imgUrl: '/static/iconImg/icon-scan.svg', text: '扫一扫' },
|
||||
{ type: 'button', clsName: 'k-button k-button-code', value: 'code', imgUrl: '/static/iconImg/icon-code.svg', text: '聚合码' },
|
||||
])
|
||||
|
||||
const deletedNumber = () => {
|
||||
emits('update:value', props.value.toString().slice(0, props.value.length - 1))
|
||||
}
|
||||
const boardDown = (val) => {
|
||||
// 如果 点击 扫一扫 或聚合码 直接回调事件
|
||||
if (val == 'scan' || val == 'code') return emits('boardDown', val)
|
||||
// 点击删除 调用函数
|
||||
if (val == 'deleted') return deletedNumber()
|
||||
// 如果值已经是0并且按下不是小数点 直接替换值
|
||||
if (props.value.toString().length == 1 && props.value == 0 && val != '.') return emits('update:value', val)
|
||||
// 只能包含一个小数点
|
||||
if (props.value.toString().includes('.') && val == '.') return
|
||||
// 限制小数点位数
|
||||
if (props.value.toString().includes('.') && props.value.split('.')[1].length >= props.point) return
|
||||
// 长度只有 一位 并且按下结果是小数点 直接 return
|
||||
if (props.value.toString().length == 1 && props.value == 0 && val != '.') return
|
||||
// 如果 清空后直接按下小数点 直接 return
|
||||
if ((props.value === '' || props.value == undefined) && val == '.') return emits('update:value', '0' + val)
|
||||
// 超出最大输入长度 直接 return
|
||||
if (props.value.toString().length >= props.maxLength) return
|
||||
// 回调结果
|
||||
emits('update:value', props.value + val)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.k-wrapper {
|
||||
padding: 0 10rpx;
|
||||
border-radius: 72rpx 72rpx 0 0;
|
||||
border-top: 1rps solid rgba($color: #000000, $alpha: 0.1);
|
||||
box-shadow: 0 -30rpx 80rpx -20rpx rgba(0, 0, 0, 0.05);
|
||||
.k-store-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 110rpx;
|
||||
margin: 10rpx 0;
|
||||
padding: 0 60rpx;
|
||||
view {
|
||||
flex-grow: 0;
|
||||
height: 100%;
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
image {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
transform: rotate(180deg);
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
}
|
||||
.k-store-title {
|
||||
white-space: nowrap;
|
||||
font-size: 30rpx;
|
||||
font-weight: 400;
|
||||
color: $J-color-t4d;
|
||||
}
|
||||
}
|
||||
.k-main {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 200rpx);
|
||||
grid-template-rows: repeat(5, 120rpx);
|
||||
grid-gap: 10rpx 20rpx;
|
||||
padding: 0 50rpx;
|
||||
font-size: 60rpx;
|
||||
border-radius: 30rpx 30rpx 0 0;
|
||||
overflow: hidden;
|
||||
.k-number {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 10rpx;
|
||||
image {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.k-button {
|
||||
grid-column: 1 / span 2;
|
||||
grid-row: 5 / span 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 20rpx;
|
||||
width: 315rpx;
|
||||
height: 110rpx;
|
||||
background: $jeepay-bg-primary;
|
||||
font-size: 26rpx;
|
||||
border-radius: 10rpx;
|
||||
color: #fff;
|
||||
image {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
}
|
||||
.k-button-code {
|
||||
transform: translateX(335rpx);
|
||||
background: linear-gradient(270deg, rgba(72, 192, 255, 1) 0%, rgba(51, 157, 255, 1) 100%);
|
||||
}
|
||||
.touch-number {
|
||||
border-radius: 80rpx;
|
||||
background-color: #e9eaeb;
|
||||
}
|
||||
.touch-button {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
97
components/JPasswordInput/JPasswordInput.vue
Normal file
97
components/JPasswordInput/JPasswordInput.vue
Normal file
@@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<view class="p-wrapper">
|
||||
<input class="p-input" type="number" v-model="info" :focus="vdata.isFoucs" :maxlength="num" @input="inputChange" />
|
||||
<view class="p-main" :style="{ margin: margin }">
|
||||
<div class="p-number flex-center" v-for="v in num" :key="v" :class="{ 'p-active': v <= info.length }"></div>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
const emits = defineEmits(['inputChange'])
|
||||
const props = defineProps({
|
||||
focus: { type: Boolean, default: false }, //是否自动获取焦点 默认false
|
||||
num: { type: Number, default: 6 }, //密码框数量
|
||||
margin: { type: String }, //边距
|
||||
})
|
||||
const info = ref('')
|
||||
|
||||
const vdata = reactive({
|
||||
isFoucs: false
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// 解决无法聚焦的问题, 怀疑页面没有渲染好导致。 nextTick也不好使。
|
||||
// 偶尔出现: 1. 键盘不弹出, 2.键盘弹出, 输入无法聚焦的input.
|
||||
if(props.focus){
|
||||
setTimeout(() => {vdata.isFoucs = true}, 500)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const clearInput = () => (info.value = '')
|
||||
const inputChange = () => {
|
||||
emits('inputChange', info.value)
|
||||
}
|
||||
|
||||
|
||||
defineExpose({ clearInput })
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.p-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
overflow: hidden;
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 10;
|
||||
width: 95rpx;
|
||||
height: 100%;
|
||||
}
|
||||
&::before {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
width: 95rpx;
|
||||
height: 100%;
|
||||
}
|
||||
.p-input {
|
||||
position: absolute;
|
||||
top: 10%;
|
||||
left: -100vw;
|
||||
right: 0;
|
||||
opacity: 0;
|
||||
color: transparent;
|
||||
caret-color: transparent;
|
||||
}
|
||||
.p-main {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 0 95rpx;
|
||||
.p-number {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 12rpx;
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
.p-active::after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
53
components/JSearchTitle/JSearchTitle.vue
Normal file
53
components/JSearchTitle/JSearchTitle.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<view class="search-wrapper" :style="{ paddingTop: pTop + 'rpx', backgroundColor: bgColor }">
|
||||
<view class="search-main" @tap="emits('click')">
|
||||
<image src="/static/iconImg/icon-search.svg" mode="scaleToFill" />
|
||||
<input type="text" :placeholder="place" placeholder-style="color: rgba(0,0,0,0.35);" :style="{ fontSize: size + 'rpx' }" disabled />
|
||||
</view>
|
||||
<slot name="right" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
place: { type: String }, //提示语
|
||||
pTop: { type: Number }, //距离上边距离 使用padding 为了有背景色
|
||||
bgColor: { type: String }, //背景色
|
||||
size: { type: Number, default: 27 }, //搜素框文字大小
|
||||
})
|
||||
const emits = defineEmits(['click'])
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.search-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 110rpx;
|
||||
background-color: $J-bg-ff;
|
||||
padding: 0 30rpx;
|
||||
.search-main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 70rpx;
|
||||
background-color: $J-bg-f5;
|
||||
border-radius: $J-b-r12;
|
||||
image {
|
||||
padding: 0 22rpx;
|
||||
width: 26rpx;
|
||||
height: 26rpx;
|
||||
}
|
||||
input {
|
||||
flex: 1;
|
||||
}
|
||||
.search-text {
|
||||
padding: 0 30rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: $J-color-t29;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
116
components/JSinglePopup/JSinglePopup.vue
Normal file
116
components/JSinglePopup/JSinglePopup.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<uni-popup ref="popup" type="bottom" mask-background-color="rgba(0,0,0,.5)" @change="change" :safe-area="false">
|
||||
<view class="tips-wrapper">
|
||||
<view class="tips-text flex-center" v-if="title">{{ title }}</view>
|
||||
<scroll-view scroll-y class="list-wrap">
|
||||
<block v-for="v in list" :key="v[value]">
|
||||
<view
|
||||
class="single-text flex-center"
|
||||
hover-class="u-cell-hover"
|
||||
hover-stay-time="150"
|
||||
:style="{ color: v[value] == selected ? activeColor : v.color }"
|
||||
@tap="confirm(v)"
|
||||
>
|
||||
{{ v[label] }}
|
||||
</view>
|
||||
</block>
|
||||
</scroll-view>
|
||||
<view class="line"></view>
|
||||
<view class="tips-text tips-cancel flex-center" hover-class="u-cell-hover" hover-stay-time="150" @tap="popup.close()">取消</view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
|
||||
<JeepayPopupConfirm ref="jeepayPopupConfirm" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, inject } from 'vue';
|
||||
const emits = defineEmits(['confirm']);
|
||||
const props = defineProps({
|
||||
title: { type: String }, //标题 传入展示 不传则隐藏
|
||||
activeColor: { type: String, default: '#2980FD' }, //选中后文字颜色
|
||||
textColor: { type: String }, //列表文字颜色
|
||||
textSize: { type: String }, //列表文字大小
|
||||
titleColor: { type: String }, //标题文字颜色
|
||||
titleSize: { type: String }, //标题文字大小
|
||||
list: { type: Array }, //渲染单选列表集合
|
||||
label: { type: String, default: 'label' }, //展示文字的键名 可传值复写
|
||||
value: { type: String, default: 'value' } //展示文字的key 可传值复写
|
||||
});
|
||||
const selected = ref(undefined);
|
||||
const popup = ref(null);
|
||||
|
||||
const jeepayPopupConfirm = ref();
|
||||
|
||||
// 打开弹窗 val 选中数据的key
|
||||
const open = (val) => {
|
||||
selected.value = val;
|
||||
popup.value.open();
|
||||
};
|
||||
const confirm = (listItem) => {
|
||||
popup.value.close();
|
||||
|
||||
// 包含回调函数
|
||||
if (listItem.fun) {
|
||||
// 包含确认弹层。
|
||||
if (listItem.confirmText) {
|
||||
jeepayPopupConfirm.value
|
||||
.open(listItem.confirmText)
|
||||
.then(() => {
|
||||
listItem.fun();
|
||||
})
|
||||
.catch(() => {
|
||||
popup.value.open();
|
||||
});
|
||||
} else {
|
||||
listItem.fun();
|
||||
}
|
||||
} else {
|
||||
1;
|
||||
emits('confirm', listItem);
|
||||
}
|
||||
};
|
||||
let changePageMetaOverflowFunc = inject('changePageMetaOverflowFunc');
|
||||
const change = (e) => {
|
||||
if (changePageMetaOverflowFunc) {
|
||||
changePageMetaOverflowFunc(!e.show);
|
||||
}
|
||||
};
|
||||
const close = () => popup.value.close();
|
||||
defineExpose({ open, close });
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.list-wrap {
|
||||
max-height: 60vh;
|
||||
}
|
||||
.tips-wrapper {
|
||||
// overflow: hidden;
|
||||
border-radius: 32rpx 32rpx 0 0;
|
||||
padding-top: 20rpx;
|
||||
padding-bottom: 60rpx;
|
||||
background-color: #fff;
|
||||
.tips-text {
|
||||
text-align: center;
|
||||
height: 90rpx;
|
||||
font-size: 30rpx;
|
||||
border-bottom: 1rpx solid rgba(0, 0, 0, 0.07);
|
||||
}
|
||||
.single-text {
|
||||
height: 120rpx;
|
||||
}
|
||||
.line {
|
||||
height: 20rpx;
|
||||
background-color: rgba(0, 0, 0, 0.07);
|
||||
}
|
||||
.tips-cancel {
|
||||
height: 110rpx;
|
||||
color: $J-color-t80;
|
||||
font-size: 33rpx;
|
||||
border: none;
|
||||
}
|
||||
.u-cell-hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
45
components/JSwitch/JSwitch.vue
Normal file
45
components/JSwitch/JSwitch.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<switch :checked="flag" :disabled="disabled" color="#238FFC" :style="{ transform: 'scale(' + scale + ')', margin: margin }" @change="change" @tap="emits('click')" />
|
||||
<JeepayPopupConfirm ref="jeepayPopupConfirmRef" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watchEffect } from 'vue'
|
||||
const props = defineProps({
|
||||
scale: { type: Number, default: 0.8 }, //控制开关大小 倍数 默认.8
|
||||
margin: { type: String, default: '0' }, // 控制开关外边距默认0
|
||||
tips: { type: String }, //修改提示语不传使用 默认tips弹出层提示语
|
||||
bol: { type: Boolean }, //开关状态
|
||||
index: { type: Number, default: 0 }, // 列表渲染索引值
|
||||
confirmTips: { type: Boolean, default: true }, //是否需要提示弹窗默认 需要
|
||||
disabled: { type: Boolean, default: false }, // 是否禁用 默认否
|
||||
})
|
||||
const emits = defineEmits(['confirm', 'cancel', 'click'])
|
||||
const flag = ref()
|
||||
|
||||
watchEffect(() => {
|
||||
flag.value = props.bol
|
||||
})
|
||||
|
||||
const jeepayPopupConfirmRef = ref() //提示弹窗
|
||||
|
||||
const change = (e) => {
|
||||
flag.value = e.detail.value
|
||||
if (!props.confirmTips) return confirm()
|
||||
jeepayPopupConfirmRef.value
|
||||
.open(props.tips || '确定修改状态?')
|
||||
.then(() => {
|
||||
confirm()
|
||||
})
|
||||
.catch(() => {
|
||||
flag.value = !flag.value
|
||||
emits('cancel', flag.value)
|
||||
})
|
||||
}
|
||||
|
||||
const confirm = () => {
|
||||
emits('confirm', flag.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
55
components/JSwitchCard/JSwitchCard.vue
Normal file
55
components/JSwitchCard/JSwitchCard.vue
Normal file
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<view class="manage-main" :style="{ '--border-width': borderWidth }">
|
||||
<view class="manage-title" :style="{ fontSize: titleSize, color: titleColor }">
|
||||
{{ title }}
|
||||
<slot name="right"></slot>
|
||||
</view>
|
||||
<view class="manage-info" :style="{ width: tipsWidth + 'rpx', fontSize: titleSize, color: titleColor }">{{ tips }}</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
title: { type: String }, //标题
|
||||
tips: { type: String }, //提示信息
|
||||
tipsWidth: { type: Number }, // 提示信息宽度 默认500rpx
|
||||
titleSize: { type: Number }, //标题字体大小
|
||||
tipsSize: { type: Number }, //提示信息字体大小
|
||||
titleColor: { type: String }, //标题文字颜色
|
||||
tipsColor: { type: String }, //提示信息文字颜色
|
||||
borderWidth: { type: String, default: '40rpx' }, //边框距离左侧距离 默认80rpx
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.manage-main {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 0 40rpx;
|
||||
height: 198rpx;
|
||||
background-color: #fff;
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
width: calc(100vw - var(--border-width));
|
||||
height: 1rpx;
|
||||
background-color: #ededed;
|
||||
}
|
||||
.manage-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
color: #4d4d4d;
|
||||
}
|
||||
.manage-info {
|
||||
margin-top: 12rpx;
|
||||
width: 500rpx;
|
||||
font-size: 25rpx;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
117
components/JTableList/JTableList.vue
Normal file
117
components/JTableList/JTableList.vue
Normal file
@@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<scroll-view
|
||||
class="table-list"
|
||||
scroll-y
|
||||
:style="{ '--height': height }"
|
||||
@scrolltolower="addNext"
|
||||
@refresherrefresh="openRefresh"
|
||||
:refresher-enabled="refresh"
|
||||
:refresher-triggered="vdata.isRefresh"
|
||||
>
|
||||
<template v-for="(record, i) in vdata.allData" :key="i">
|
||||
<slot name="tableBody" :record="record" :index="i" />
|
||||
</template>
|
||||
<view class="list-null" v-if="!vdata.apiResData.hasNext && showListNull">暂无更多数据</view>
|
||||
</scroll-view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app';
|
||||
|
||||
// 定义传入属性
|
||||
const props = defineProps({
|
||||
reqTableDataFunc: { type: Function, default: () => {} },
|
||||
searchData: { type: Object, default: () => {} }, // 搜索条件参数
|
||||
pageSize: { type: Number, default: 10 }, // 默认每页条数
|
||||
initData: { type: Boolean, default: true }, // 初始化列表数据, 默认true
|
||||
showListNull: { type: Boolean, default: true }, //是否显示缺省 默认显示
|
||||
height: { type: String, default: '88rpx' }, // 高度 这里的高度 为 外部使用高度 scroll-view 高度 为 100vh - 传入高度
|
||||
refresh: { type: Boolean, default: false } //是否开启下拉刷新 默认不开启
|
||||
});
|
||||
|
||||
const vdata = reactive({
|
||||
allData: [], // app与web不同, app是每次查询到数据会拼接到后面
|
||||
|
||||
// 接口返回的数据
|
||||
apiResData: { total: 0, records: [] },
|
||||
|
||||
// 分页参数
|
||||
iPage: { pageNumber: 1, pageSize: props.pageSize },
|
||||
|
||||
// 下拉刷新开启状态
|
||||
isRefresh: false
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
//初始化表数据
|
||||
props.initData ? refTable(true) : undefined;
|
||||
});
|
||||
|
||||
// 查询表格数据
|
||||
function refTable(isToFirst = false) {
|
||||
if (isToFirst) {
|
||||
// 重新搜索, 第一页。
|
||||
vdata.iPage.pageNumber = 1;
|
||||
vdata.allData = []; //清空数据
|
||||
}
|
||||
|
||||
// 更新检索数据
|
||||
return props.reqTableDataFunc(Object.assign({}, vdata.iPage, props.searchData)).then(({ bizData }) => {
|
||||
Object.assign(vdata.apiResData, bizData); // 列表数据更新
|
||||
|
||||
if (bizData.records) {
|
||||
bizData.records.forEach((r) => vdata.allData.push(r));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** 追加下一页数据 **/
|
||||
function addNext() {
|
||||
// console.log('加载下一页')
|
||||
// 包含下一页
|
||||
if (vdata.apiResData.hasNext) {
|
||||
vdata.iPage.pageNumber++;
|
||||
refTable(false);
|
||||
}
|
||||
}
|
||||
|
||||
// 下拉刷新
|
||||
const openRefresh = () => {
|
||||
vdata.isRefresh = true;
|
||||
refTable(true).then(() => {
|
||||
setTimeout(() => {
|
||||
vdata.isRefresh = false;
|
||||
}, 300);
|
||||
});
|
||||
};
|
||||
|
||||
// 将表格事件暴露出去 https://www.jianshu.com/p/39d14c25c987
|
||||
defineExpose({ refTable, addNext });
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.table-list {
|
||||
height: calc(75vh - var(--height));
|
||||
}
|
||||
.list-null {
|
||||
position: relative;
|
||||
height: 110rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-bottom: 70rpx;
|
||||
gap: 20rpx;
|
||||
font-size: 26rpx;
|
||||
color: #a6a6a6;
|
||||
&::after,
|
||||
&::before {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 30%;
|
||||
height: 2rpx;
|
||||
background-color: #ededed;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
58
components/JTipsPopupContent/JTipsPopupContent.vue
Normal file
58
components/JTipsPopupContent/JTipsPopupContent.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<uni-popup ref="popup" type="bottom" mask-background-color="rgba(0,0,0,.5)" @change='change' :safe-area="false" @maskClick="emits('cancel')">
|
||||
<!-- 通用提示弹窗 用于提示用户 数据含义 -->
|
||||
<view class="card-wrapper">
|
||||
<view class="card-title flex-center">{{ title }}</view>
|
||||
<slot />
|
||||
<view class="card-button flex-center" hover-class="touch-hover" @tap="confirm"> {{ buttonText }}</view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref,inject } from 'vue'
|
||||
const emits = defineEmits(['cancel'])
|
||||
const props = defineProps({
|
||||
title: { type: String }, //标题
|
||||
buttonText: { type: String }, //按钮文字
|
||||
})
|
||||
const popup = ref(null)
|
||||
|
||||
const open = (val) => {
|
||||
popup.value.open()
|
||||
}
|
||||
const confirm = () => {
|
||||
emits('cancel')
|
||||
popup.value.close()
|
||||
}
|
||||
let changePageMetaOverflowFunc = inject('changePageMetaOverflowFunc')
|
||||
const change = (e)=>{
|
||||
if(changePageMetaOverflowFunc){
|
||||
changePageMetaOverflowFunc(!e.show)
|
||||
}
|
||||
}
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.card-wrapper {
|
||||
border-radius: 32rpx 32rpx 0 0;
|
||||
background-color: #fff;
|
||||
padding-bottom: 60rpx;
|
||||
max-height: 70vh;
|
||||
.card-title {
|
||||
margin-bottom: 20rpx;
|
||||
height: 110rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: 400;
|
||||
border-bottom: 1rpx solid rgba(0, 0, 0, 0.07);
|
||||
}
|
||||
.card-button {
|
||||
margin-top: 20rpx;
|
||||
height: 110rpx;
|
||||
font-size: 32rpx;
|
||||
color: #2980fd;
|
||||
border-top: 20rpx solid #f7f7f7;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
53
components/JeepayAdCard/JeepayAdCard.vue
Normal file
53
components/JeepayAdCard/JeepayAdCard.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<!-- 单张广告 -->
|
||||
<view class="ad-wrapper" v-if="list.length == 1">
|
||||
<image :src="list[0].imgUrl" mode="aspectFill" @tap='toH5(list[0].linkUrl)' />
|
||||
</view>
|
||||
<!-- 多张广告 -->
|
||||
<view class="ads-wrapper" v-else>
|
||||
<block v-for="(v, i) in list" :key="i">
|
||||
<view>
|
||||
<image :src="v.imgUrl" mode="aspectFill" @tap='toH5(v.linkUrl)' />
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
list: { type: Array, default: [] }
|
||||
})
|
||||
const toH5 = (url) => {
|
||||
if (!url) return
|
||||
uni.navigateTo({
|
||||
url: '/pages/adH5/adH5?url=' + url
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ad-wrapper {
|
||||
margin: 40rpx;
|
||||
|
||||
max-height: 680rpx;
|
||||
border-radius: 30rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ads-wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 30rpx;
|
||||
|
||||
view {
|
||||
width: calc(50% - 20rpx);
|
||||
max-height: 400rpx;
|
||||
border-radius: 30rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
image {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
237
components/JeepayAdStart/JeepayAdStart.vue
Normal file
237
components/JeepayAdStart/JeepayAdStart.vue
Normal 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>
|
||||
118
components/JeepayBackground/JeepayBackground.vue
Normal file
118
components/JeepayBackground/JeepayBackground.vue
Normal file
@@ -0,0 +1,118 @@
|
||||
<!--
|
||||
背景模板
|
||||
|
||||
使用方式:
|
||||
|
||||
A. 背景图片的通用外部view ( APP不支持背景图片的变量动态替换, 所以只能业务页面来写入背景图片的地址。 )
|
||||
<JeepayBackground :bgImgView="true">页面内容</JeepayBackground>
|
||||
<style scoped>
|
||||
/deep/ .bg-img-view { background: url('/static/indexImg/user-bg.svg'); }
|
||||
</style>
|
||||
|
||||
B. 背景是自定义颜色的外部view ( 默认蓝色渐变 )
|
||||
<JeepayBackground :bgColorStyle="{}">页面内容</JeepayBackground>
|
||||
|
||||
@author terrfly
|
||||
@site https://www.jeequan.com
|
||||
@date 2022/11/19 08:08
|
||||
-->
|
||||
<template>
|
||||
|
||||
<!-- 微信小程序: <page-meta>: 只能是页面内的第一个节点,且不能被 wx:if 或 wx:for 动态变更
|
||||
uniapp: 打包到小程序, 会添加 <jeepay-background>标签, 导致该问题。 无奈 小程序不可使用: <page-meta> 标签, 实现方式为: page-wrapper height方式。
|
||||
-->
|
||||
<!-- <page-meta :pageStyle="'overflow:' + (vdata.pageMetaOverflow ? 'visible' : 'hidden')"></page-meta> -->
|
||||
<!-- #ifdef APP-PLUS-->
|
||||
<page-meta :pageStyle="'overflow:' + (vdata.pageMetaOverflow ? 'visible' : 'hidden')"></page-meta>
|
||||
<!-- #endif -->
|
||||
<!-- page-wrapper 包裹 -->
|
||||
<view class="page-wrapper" :style="{overflow:vdata.pageMetaOverflow ? 'visible' : 'hidden', height:vdata.pageMetaOverflow ? 'auto' : '90vh'}">
|
||||
|
||||
<!-- 背景图片view -->
|
||||
<view class="bg-img-view" :style="vdata.bgImgStyle">
|
||||
|
||||
<!-- 背景颜色view -->
|
||||
<view class="bg-color-view" :style="vdata.bgColorStyle" style="border-radius:0;background-color: #318AFE!important;">
|
||||
<view class="bgbottomStyle">
|
||||
</view>
|
||||
</view>
|
||||
<!-- 解决定位层级问题 -->
|
||||
<view class="bg-main">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, onMounted, provide, computed } from 'vue'
|
||||
|
||||
// 选择是图片风格 还是 背景颜色风格。
|
||||
const props = defineProps({
|
||||
// 背景图片
|
||||
bgImgView: { type: Boolean }, // APP 不支持背景图片的动态替换。。。
|
||||
|
||||
// 背景颜色style
|
||||
bgColorStyle: { type: Object },
|
||||
// 禁止滚动穿透
|
||||
|
||||
})
|
||||
|
||||
|
||||
function changePageMetaOverflowFunc(v){
|
||||
vdata.pageMetaOverflow = v
|
||||
}
|
||||
provide('changePageMetaOverflowFunc', changePageMetaOverflowFunc)
|
||||
|
||||
|
||||
const vdata = reactive({
|
||||
|
||||
bgImgStyle : { }, // 注意: style变量需要全部替换, 不可使用Object.assign修改里面的值, 否则 APP不生效!
|
||||
bgColorStyle : { },
|
||||
pageMetaOverflow: true
|
||||
})
|
||||
provide('pageMetaOverflow',computed(()=>vdata.pageMetaOverflow))
|
||||
onMounted(() => {
|
||||
|
||||
// 包含背景图片
|
||||
if(props.bgImgView){
|
||||
vdata.bgImgStyle = {
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
backgroundRepeat: 'no-repeat'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 包含背景色
|
||||
if(props.bgColorStyle){
|
||||
let defStyle = { // 默认颜色
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: '550rpx',
|
||||
borderRadius: '0 0 32rpx 32rpx',
|
||||
// background: 'linear-gradient(270deg, rgba(72, 192, 255, 1) 0%, rgba(51, 157, 255, 1) 100%)',
|
||||
}
|
||||
vdata.bgColorStyle = Object.assign(defStyle, props.bgColorStyle)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.bg-main{
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
.bgbottomStyle{
|
||||
position: absolute;
|
||||
bottom: -2rpx;
|
||||
left: 0;
|
||||
|
||||
width: 750rpx;
|
||||
height: 74rpx;
|
||||
background: linear-gradient( 180deg, rgba(195,215,235,0) 0%, #F9F9F9 100%);
|
||||
}
|
||||
</style>
|
||||
55
components/JeepayBanner/JeepayBanner.vue
Normal file
55
components/JeepayBanner/JeepayBanner.vue
Normal file
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<swiper
|
||||
class="swiper"
|
||||
circular
|
||||
:indicator-dots="true"
|
||||
autoplay
|
||||
:interval="interval * 1000"
|
||||
:duration="500"
|
||||
>
|
||||
<block v-for="(v, i) in list" :key="i">
|
||||
<swiper-item>
|
||||
<image :src="v.imgUrl" mode="aspectFill" @tap="toH5(v.linkUrl)" />
|
||||
</swiper-item>
|
||||
</block>
|
||||
</swiper>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
list: { type: Array, default: [] },
|
||||
interval: { type: [String, Number] },
|
||||
});
|
||||
const toH5 = (url) => {
|
||||
if (!url) return;
|
||||
uni.navigateTo({
|
||||
url: '/pages/adH5/adH5?url=' + url,
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.uni-margin-wrap {
|
||||
width: 690rpx;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.swiper {
|
||||
margin: 40rpx;
|
||||
height: 300rpx;
|
||||
border-radius: 30rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.swiper-item {
|
||||
display: block;
|
||||
|
||||
height: 300rpx;
|
||||
line-height: 300rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
image {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
143
components/JeepayBizinfoSelect/JeepayBizinfoSelect.vue
Normal file
143
components/JeepayBizinfoSelect/JeepayBizinfoSelect.vue
Normal file
@@ -0,0 +1,143 @@
|
||||
<!--
|
||||
Jeepay 门店选择 / 应用选择
|
||||
|
||||
@author terrfly
|
||||
@site https://www.jeequan.com
|
||||
@date 2022/12/06 12:55
|
||||
-->
|
||||
<template>
|
||||
<!-- 选择门店 -->
|
||||
<JeepayPopupListSelect
|
||||
ref="jeepayPopupListSelectByBizinfos"
|
||||
:reqTableDataFunc="reqTableDataByBizsFunc"
|
||||
:searchInputName="getSearchInputName()"
|
||||
:fields="getField()"
|
||||
:isCheckbox="props.isCheckbox"
|
||||
:addUse="props.addUse"
|
||||
@confirm="confirm"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import { reqLoad, API_URL_MCH_STORE_LIST, API_URL_MCH_APP_LIST, API_URL_MCH_ISV_LIST } from '@/http/apiManager.js';
|
||||
|
||||
// 定义组件参数
|
||||
const props = defineProps({
|
||||
configMode: '',
|
||||
// 是否多选框, 默认单选。
|
||||
isCheckbox: { type: Boolean, default: false },
|
||||
|
||||
// 自动关闭, 点击确定, 自动关闭
|
||||
autoClose: { type: Boolean, default: true },
|
||||
|
||||
// 业务类型: 默认门店
|
||||
bizType: { type: String, default: 'store' },
|
||||
range: { type: String, default: 0 },
|
||||
ifCode: { type: String, default: '' },
|
||||
|
||||
// 是否支持选择 【 全部门店 、 全部应用 】
|
||||
isShowAllBiz: { type: Boolean, default: false },
|
||||
// 特殊参数处理
|
||||
params: { type: Object, default: () => ({}) },
|
||||
addUse: { type: Boolean, default: false }
|
||||
});
|
||||
|
||||
const jeepayPopupListSelectByBizinfos = ref();
|
||||
const emits = defineEmits(['confirm']);
|
||||
function confirm(data) {
|
||||
console.log(data);
|
||||
emits('confirmData', data);
|
||||
}
|
||||
console.log(props.isCheckbox);
|
||||
function getField() {
|
||||
if (props.bizType == 'store') {
|
||||
return { key: 'storeId', left: 'storeName', right: 'storeId' };
|
||||
}
|
||||
|
||||
if (props.bizType == 'mchApp') {
|
||||
return { key: 'appId', left: 'appName', right: 'appId' };
|
||||
}
|
||||
|
||||
if (props.bizType == 'isvApp') {
|
||||
return { key: 'isvNo', left: 'isvName', right: 'isvNo' };
|
||||
}
|
||||
}
|
||||
|
||||
function getSearchInputName() {
|
||||
if (props.bizType == 'store') {
|
||||
return 'storeName';
|
||||
}
|
||||
|
||||
if (props.bizType == 'mchApp') {
|
||||
return 'appName';
|
||||
}
|
||||
if (props.bizType == 'isvApp') {
|
||||
return 'isvName';
|
||||
}
|
||||
}
|
||||
|
||||
function reqTableDataByBizsFunc(params) {
|
||||
Object.assign(params, props.params);
|
||||
console.log(params);
|
||||
|
||||
let apiResult = null;
|
||||
|
||||
if (props.bizType == 'store') {
|
||||
apiResult = reqLoad.list(API_URL_MCH_STORE_LIST, params);
|
||||
}
|
||||
if (props.bizType == 'mchApp') {
|
||||
apiResult = reqLoad.list(API_URL_MCH_APP_LIST, params);
|
||||
}
|
||||
|
||||
if (props.bizType == 'isvApp') {
|
||||
params.ifCode = props.ifCode;
|
||||
params.range = props.range;
|
||||
apiResult = reqLoad.list(API_URL_MCH_ISV_LIST, params);
|
||||
}
|
||||
return apiResult.then((apiRes) => {
|
||||
return processApiResBizData(params, apiRes);
|
||||
});
|
||||
}
|
||||
|
||||
// 选择门店
|
||||
function open(defaultVal) {
|
||||
console.log(defaultVal, 'defaultValdefaultVal');
|
||||
return jeepayPopupListSelectByBizinfos.value.open(defaultVal).then((selected) => {
|
||||
// 自动关闭
|
||||
if (props.autoClose) {
|
||||
close();
|
||||
}
|
||||
|
||||
return selected;
|
||||
});
|
||||
}
|
||||
|
||||
function close() {
|
||||
jeepayPopupListSelectByBizinfos.value.close(); //自行关闭
|
||||
}
|
||||
|
||||
function processApiResBizData(params, apiRes) {
|
||||
// 第一页 拼接全部门店 (index = 0 )
|
||||
if (params.pageNumber == 1 && props.isShowAllBiz) {
|
||||
if (props.bizType == 'store') {
|
||||
apiRes.bizData.records.unshift({ storeName: '全部门店', storeId: '' });
|
||||
}
|
||||
|
||||
if (props.bizType == 'mchApp') {
|
||||
apiRes.bizData.records.unshift({ appName: '全部应用', appId: '' });
|
||||
}
|
||||
|
||||
if (props.bizType == 'isvApp') {
|
||||
apiRes.bizData.records.unshift({ appName: '全部渠道', appId: '' });
|
||||
}
|
||||
}
|
||||
|
||||
return apiRes;
|
||||
}
|
||||
|
||||
// 将表格事件暴露出去 https://www.jianshu.com/p/39d14c25c987
|
||||
defineExpose({ open, close });
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
144
components/JeepayBizsPopupView/JeepayBizsPopupView.vue
Normal file
144
components/JeepayBizsPopupView/JeepayBizsPopupView.vue
Normal file
@@ -0,0 +1,144 @@
|
||||
<!--
|
||||
通用的选择某项业务, 一般用作修改页。
|
||||
目前支持: 门店 和 应用
|
||||
@author terrfly
|
||||
@site https://www.jeepay.vip
|
||||
@date 2022/12/08 14:18
|
||||
-->
|
||||
<template>
|
||||
<view class="details-wrapper" style="align-items: flex-start; padding-top: 40rpx">
|
||||
<view v-if="props.hasTitle" class="details-title">
|
||||
<slot name="title">{{ bizTypeMap[props.bizType].title }}</slot>
|
||||
</view>
|
||||
<view @tap="show">
|
||||
<view class="sub-title">
|
||||
<view class="details-info single-text-beyond">
|
||||
<text :class="{ 'text-gray': vdata.selectedRow[bizTypeMap[props.bizType].nameKey] ? false : true }">
|
||||
{{ vdata.selectedRow[bizTypeMap[props.bizType].nameKey] || bizTypeMap[props.bizType].nameDef }}
|
||||
</text>
|
||||
</view>
|
||||
<image src="/static/iconImg/icon-arrow-small.svg" mode="scaleToFill" v-if="isIcon" />
|
||||
</view>
|
||||
<view class="sub-info details-info single-text-beyond">{{ vdata.selectedRow[bizTypeMap[props.bizType].id] }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view>
|
||||
<JeepayBizinfoSelect ref="jeepayBizinfoSelectRef" :bizType="props.bizType" :isCheckbox="false" :range="props.range" :ifCode="props.ifCode" :addUse="props.addUse" />
|
||||
<JeepayIncomingBizinfoSelect ref="jeepayIncomingBizinfoSelectRef" :bizType="props.bizType" :range="props.range" :ifCode="props.ifCode" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, nextTick, watch, onMounted } from 'vue';
|
||||
|
||||
const jeepayBizinfoSelectRef = ref();
|
||||
const jeepayIncomingBizinfoSelectRef = ref();
|
||||
|
||||
const bizTypeMap = {
|
||||
store: { title: '选择门店', nameKey: 'storeName', nameDef: '请选择门店', id: 'storeId' },
|
||||
mchApp: { title: '选择应用', nameKey: 'appName', nameDef: '请选择应用', id: 'appId' },
|
||||
isvApp: { title: '选择渠道', nameKey: 'isvName', nameDef: '请选择渠道', id: 'isvNo' },
|
||||
incomingApp: { title: '发起进件', nameKey: 'name', nameDef: '请选择场景', id: 'range' }
|
||||
};
|
||||
|
||||
// emit 父组件使用: v-model:value="val" 进行双向绑定。
|
||||
const emits = defineEmits(['update:value', 'update:showName', 'change']);
|
||||
|
||||
const props = defineProps({
|
||||
// 是否包含 标题部分
|
||||
hasTitle: { type: Boolean, default: true },
|
||||
|
||||
//绑定的值, 双向绑定
|
||||
value: { type: [String, Number] },
|
||||
|
||||
// 仅仅用作修改的回显
|
||||
showName: { type: [String, Number] },
|
||||
|
||||
// 业务类型: 默认门店
|
||||
bizType: { type: String, default: 'store' },
|
||||
range: { type: String, default: 0 },
|
||||
ifCode: { type: String, default: '' },
|
||||
isIcon: { type: Boolean, default: true },
|
||||
addUse: { type: Boolean, default: false }
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
if (props.value) {
|
||||
vdata.selectedRow[bizTypeMap[props.bizType].id] = props.value;
|
||||
}
|
||||
if (props.showName) {
|
||||
vdata.selectedRow[bizTypeMap[props.bizType].nameKey] = props.showName;
|
||||
}
|
||||
});
|
||||
|
||||
// 切换时
|
||||
watch(
|
||||
() => props.value,
|
||||
(newVal, oldVal) => {
|
||||
vdata.selectedRow[bizTypeMap[props.bizType].id] = newVal;
|
||||
}
|
||||
);
|
||||
|
||||
// 切换时
|
||||
watch(
|
||||
() => props.showName,
|
||||
(newVal, oldVal) => {
|
||||
vdata.selectedRow[bizTypeMap[props.bizType].nameKey] = newVal;
|
||||
}
|
||||
);
|
||||
|
||||
const vdata = reactive({
|
||||
// 当前选择行
|
||||
selectedRow: {}
|
||||
});
|
||||
|
||||
function show() {
|
||||
jeepayBizinfoSelectRef.value.open().then((selectedRow) => {
|
||||
vdata.selectedRow = selectedRow || {};
|
||||
|
||||
emits('update:value', vdata.selectedRow[bizTypeMap[props.bizType].id]);
|
||||
emits('update:showName', vdata.selectedRow[bizTypeMap[props.bizType].nameKey]);
|
||||
emits('change', vdata.selectedRow[bizTypeMap[props.bizType].id]);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.details-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 32rpx;
|
||||
background-color: #fff;
|
||||
.details-title {
|
||||
width: 180rpx;
|
||||
color: #4c4c4c;
|
||||
}
|
||||
.details-info {
|
||||
flex-grow: 1;
|
||||
width: 405rpx;
|
||||
}
|
||||
image {
|
||||
width: 108rpx;
|
||||
height: 100rpx;
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
image {
|
||||
width: 108rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
}
|
||||
.sub-info {
|
||||
margin-top: 20rpx;
|
||||
font-size: 30rpx;
|
||||
color: #a1a1a1;
|
||||
padding-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.text-gray {
|
||||
color: #b3b3b3;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
90
components/JeepayCard/JeepayCard.vue
Normal file
90
components/JeepayCard/JeepayCard.vue
Normal file
@@ -0,0 +1,90 @@
|
||||
<!--
|
||||
组件功能: 卡片布局
|
||||
标题, 副标题, 和编辑按钮及插槽。
|
||||
注意: 不包含 中间的内容
|
||||
@author terrfly
|
||||
@site https://www.jeequan.com
|
||||
@date 2022/12/06 08:06
|
||||
-->
|
||||
<template>
|
||||
<view class="c-card-wrapper" :style="props.viewStyle">
|
||||
|
||||
<view v-if="props.title" class="card-title" :class="{'border-none':!isTitleBorder}" @tap="emits('clickTitle')">
|
||||
<view class="c-card-title"> {{ props.title }} <view><slot name="titleRight"/> </view> </view>
|
||||
<view class="sub-title" v-if="props.subtitle">{{ props.subtitle }}</view>
|
||||
</view>
|
||||
<!-- 默认插槽位置 -->
|
||||
<slot></slot>
|
||||
|
||||
<!-- 编辑区域 -->
|
||||
<slot name="editContent">
|
||||
<view v-if="props.editText" class="card-edit flex-center" hover-class="touch-hover" @tap="emits('editTap')">
|
||||
<image src="/pageDevice/static/devIconImg/icon-edit.svg" mode="scaleToFill" /> {{ props.editText }}
|
||||
</view>
|
||||
</slot>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, onMounted, watch } from 'vue'
|
||||
|
||||
const emits = defineEmits(['editTap','clickTitle'])
|
||||
|
||||
const props = defineProps({
|
||||
|
||||
viewStyle: { type: [String, Object] }, // 样式透传, 小程序不支持再组件上加style(不生效)
|
||||
|
||||
title: { type: String }, //标题文字 传值则显示
|
||||
|
||||
subtitle: { type: String }, //副标题
|
||||
|
||||
editText: { type: String }, // 编辑文本, 若存在请通过 editTap 事件接收点击事件。
|
||||
isTitleBorder:{type:Boolean, default:true} //是否存在标题下边框
|
||||
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.c-card-wrapper {
|
||||
margin: 0 35rpx;
|
||||
border-radius: 32rpx;
|
||||
background-color: #fff;
|
||||
overflow: hidden;
|
||||
.card-title {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 30rpx 40rpx;
|
||||
min-height: 42rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
border-bottom: 1rpx solid #ededed;
|
||||
.c-card-title{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.sub-title {
|
||||
margin-top: 15rpx;
|
||||
font-size: 25rpx;
|
||||
color: #808080;
|
||||
}
|
||||
}
|
||||
.card-edit {
|
||||
height: 110rpx;
|
||||
border-top: 1rpx solid #ededed;
|
||||
color: #2980fd;
|
||||
font-size: 33rpx;
|
||||
font-weight: 400;
|
||||
image {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
.border-none{
|
||||
border: none !important;
|
||||
}
|
||||
</style>
|
||||
80
components/JeepayCheckbox/JeepayCheckbox.vue
Normal file
80
components/JeepayCheckbox/JeepayCheckbox.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<!--
|
||||
组件功能: 解决原生组件,双向绑定问题。
|
||||
@author terrfly
|
||||
@site https://www.jeequan.com
|
||||
@date 2022/11/18 14:32
|
||||
-->
|
||||
<template>
|
||||
<view class="select-box" @click="changeFunc">
|
||||
<button class="agree-item" id="agree-btn" open-type="agreePrivacyAuthorization"
|
||||
@agreeprivacyauthorization="handAgree">
|
||||
<image v-if="props.checked" class="select-img" src="@/static/login/selected.svg"></image>
|
||||
<image v-else class="select-img" src="@/static/login/select.svg"></image>
|
||||
</button>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, onMounted } from 'vue'
|
||||
|
||||
// emit 父组件使用: v-model="val" 进行双向绑定。
|
||||
const emit = defineEmits(['update:checked'])
|
||||
onMounted(() => {
|
||||
// #ifdef MP-WEIXIN
|
||||
getPrivacy()
|
||||
// #endif
|
||||
})
|
||||
// 定义组件参数
|
||||
const props = defineProps({
|
||||
|
||||
checked: { type: Boolean, default: false },
|
||||
|
||||
})
|
||||
const vdata = reactive({})
|
||||
function changeFunc () {
|
||||
emit('update:checked', !props.checked)
|
||||
}
|
||||
// 获取微信你用户是否同意过隐私政策
|
||||
const getPrivacy = () => {
|
||||
wx.getPrivacySetting({
|
||||
success: (r) => {
|
||||
Object.assign(vdata, r)
|
||||
if (vdata.needAuthorization) {
|
||||
wx.onNeedPrivacyAuthorization(res => {
|
||||
vdata.resolve = res
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
const handAgree = () => {
|
||||
// #ifdef MP-WEIXIN
|
||||
if (vdata.needAuthorization) {
|
||||
vdata.resolve({ buttonId: 'agree-btn', event: 'agree' })
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.select-box {
|
||||
display: inline;
|
||||
|
||||
.select-img {
|
||||
width: 46rpx;
|
||||
height: 46rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.agree-item {
|
||||
padding: 0;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
line-height: 0;
|
||||
width: 46rpx;
|
||||
height: 46rpx;
|
||||
border-radius: 0;
|
||||
background-color: transparent;
|
||||
}
|
||||
</style>
|
||||
159
components/JeepayCustomNavbar/JeepayCustomNavbar.vue
Normal file
159
components/JeepayCustomNavbar/JeepayCustomNavbar.vue
Normal file
@@ -0,0 +1,159 @@
|
||||
<!--
|
||||
组件功能: 自定义导航栏包裹 , 基于: uni-nav-bar 的封装。
|
||||
1. pages.json : "navigationStyle": "custom"
|
||||
2. 页面引入该组件。
|
||||
3. 如果背景透明并且需要下滑变默认背景色。 需要在页面实现: onPageScroll函数(可以空函数), 否则该组件无法正常监听。
|
||||
|
||||
|
||||
示例:
|
||||
|
||||
背景通铺效果
|
||||
<JeepayCustomNavbar :transparent="true" bgDefaultColor='#b6a3a3' title="背景通铺效果" backCtrl="back" ></JeepayCustomNavbar>
|
||||
import { onPageScroll } from '@dcloudio/uni-app'
|
||||
onPageScroll(() => {})
|
||||
|
||||
纯色导航条
|
||||
<JeepayCustomNavbar :transparent="false" bgDefaultColor='#ffaa7f' title="纯色导航条" backCtrl="back"></JeepayCustomNavbar>
|
||||
|
||||
自定义左上角 效果
|
||||
<JeepayCustomNavbar :transparent="true" bgDefaultColor='#ff557f' title="自定义左上角" backCtrl="back">
|
||||
<view> 选择门店(自定义导航条内容) </view>
|
||||
</JeepayCustomNavbar>
|
||||
|
||||
@author terrfly
|
||||
@site https://www.jeequan.com
|
||||
@date 2022/11/18 14:32
|
||||
-->
|
||||
<template>
|
||||
<view>
|
||||
<uni-nav-bar
|
||||
fixed
|
||||
:statusBar="true"
|
||||
:border="false"
|
||||
:height="props.height"
|
||||
:backgroundColor="vdata.backgroundColor"
|
||||
leftWidth="100%"
|
||||
>
|
||||
<!-- 使用左侧插槽 100%宽度 -->
|
||||
<template #left>
|
||||
<view class="left-slot-view">
|
||||
<slot>
|
||||
<view class="nav-bar-box">
|
||||
<view>
|
||||
<!-- 左侧按钮 -->
|
||||
<uni-icons v-if="props.backCtrl" class="left-back" @click="backFunc" :type="props.backIcon" size="20" :color="props.textColor"></uni-icons>
|
||||
</view>
|
||||
|
||||
<!-- 标题 -->
|
||||
<view class="text" :style="{ color: props.textColor }"> {{props.title}} </view>
|
||||
</view>
|
||||
</slot>
|
||||
</view>
|
||||
</template>
|
||||
</uni-nav-bar>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onPageScroll } from '@dcloudio/uni-app'
|
||||
import { reactive, ref, onMounted } from 'vue'
|
||||
import ak from '@/commons/utils/ak.js'
|
||||
|
||||
// 定义组件参数
|
||||
const props = defineProps({
|
||||
|
||||
// 标题
|
||||
title: { type: String, default: '' },
|
||||
|
||||
// 文字颜色
|
||||
textColor: { type: String, default: 'black' },
|
||||
|
||||
// 返回按钮类型
|
||||
backIcon: { type: String, default: 'back' },
|
||||
|
||||
// 高度: 默认与uni-nav-bar 保持一致。
|
||||
height: { type: Number, default: 44 },
|
||||
|
||||
// 默认背景色:
|
||||
bgDefaultColor: { type: String, default: 'white' },
|
||||
|
||||
// 是否默认透明(此时可自定义背景), 当滑动时将切换为默认背景色。
|
||||
transparent: { type: Boolean, default: false },
|
||||
|
||||
// 返回按钮的操作项: null-无返回按钮, back 返回上一页, pageUrl: '', 也可以是回调函数。
|
||||
backCtrl: [ String, Function ],
|
||||
|
||||
})
|
||||
|
||||
// 只有透明色时 才需要监听
|
||||
if(props.transparent){
|
||||
|
||||
onPageScroll((e) => { // 切换背景颜色 透明 or 主题色。
|
||||
if(e.scrollTop > (props.height / 2.5) ){
|
||||
vdata.backgroundColor = props.bgDefaultColor
|
||||
}else{
|
||||
vdata.backgroundColor = 'transparent'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 颜色
|
||||
const vdata = reactive({
|
||||
backgroundColor: 'transparent' // 默认透明色
|
||||
})
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
// 当非透明色, 需要改为默认颜色
|
||||
if(!props.transparent){
|
||||
vdata.backgroundColor = props.bgDefaultColor + '';
|
||||
}
|
||||
})
|
||||
|
||||
// 点击返回按钮,
|
||||
function backFunc(){
|
||||
|
||||
if(!props.backCtrl){
|
||||
return false;
|
||||
}
|
||||
|
||||
if(typeof props.backCtrl == 'function'){
|
||||
return props.backCtrl()
|
||||
}
|
||||
|
||||
if(props.backCtrl === 'back'){
|
||||
return ak.go.back()
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.left-slot-view {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
line-height: 100%;
|
||||
font-size: 33rpx;
|
||||
font-weight: 500;
|
||||
.nav-bar-box {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
justify-content: space-between;
|
||||
.left-back {
|
||||
position: absolute;
|
||||
}
|
||||
&::after {
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
::v-deep.uni-navbar__header {
|
||||
.uni-navbar__header-container,
|
||||
.uni-navbar__header-btns-right {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
32
components/JeepayDescview/JeepayDescview.vue
Normal file
32
components/JeepayDescview/JeepayDescview.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<!--
|
||||
描述信息展示
|
||||
通用信息
|
||||
@author terrfly
|
||||
@site https://www.jeequan.com
|
||||
@date 2022/12/06 10:11
|
||||
-->
|
||||
<template>
|
||||
|
||||
<view class="c-desc-view">
|
||||
<slot></slot>
|
||||
</view>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
|
||||
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.c-desc-view {
|
||||
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
77
components/JeepayDescviewItem/JeepayDescviewItem.vue
Normal file
77
components/JeepayDescviewItem/JeepayDescviewItem.vue
Normal file
@@ -0,0 +1,77 @@
|
||||
<!--
|
||||
描述信息展示
|
||||
通用信息
|
||||
@author terrfly
|
||||
@site https://www.jeequan.com
|
||||
@date 2022/12/06 10:11
|
||||
-->
|
||||
<template>
|
||||
<view :class="{ 'c-desc-view-item': true, 'bottom-border': props.bottomBorder }">
|
||||
<view class="title">
|
||||
<slot name="title">{{ props.title }}</slot>
|
||||
</view>
|
||||
<view class="desc">
|
||||
<slot name="desc">
|
||||
<image v-if="props.descIsImg && props.desc" :src="props.desc" mode="scaleToFill" class="default-img" />
|
||||
<template v-else>{{ props.desc }}</template>
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
// 标题
|
||||
title: { type: String },
|
||||
|
||||
// 描述
|
||||
desc: { type: String },
|
||||
|
||||
// 描述是否是图片
|
||||
descIsImg: { type: Boolean, default: false },
|
||||
|
||||
// 下边框
|
||||
bottomBorder: { type: Boolean, default: false }
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.c-desc-view-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx 40rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: 400;
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
width: 200rpx;
|
||||
color: #808080;
|
||||
}
|
||||
.desc {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
&:last-child {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.default-img {
|
||||
width: 150rpx;
|
||||
height: 150rpx;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-border {
|
||||
padding-bottom: 40rpx;
|
||||
border-bottom: 1rpx solid #ededed;
|
||||
}
|
||||
</style>
|
||||
151
components/JeepayForm/JeepayForm.vue
Normal file
151
components/JeepayForm/JeepayForm.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<!-- 待完成 -->
|
||||
<template>
|
||||
|
||||
<view>
|
||||
<slot></slot>
|
||||
</view>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, onMounted, provide, computed } from 'vue'
|
||||
import infoBox from '@/commons/utils/infoBox.js'
|
||||
|
||||
|
||||
// 定义组件参数
|
||||
const props = defineProps({
|
||||
|
||||
// 验证规则
|
||||
rules: { type: Object, default: () => { } },
|
||||
|
||||
// 用作表单验证
|
||||
model: { type: Object, default: () => { } },
|
||||
|
||||
|
||||
|
||||
})
|
||||
|
||||
const vdata = reactive({
|
||||
formNameList: [], // form 表单的已经注册的名字集合
|
||||
|
||||
errorListByText: [], // 错误信息集合 ( 需要显示的 )
|
||||
errorListByToast: [], // 错误信息集合 ( alert 提示的 )
|
||||
|
||||
|
||||
})
|
||||
|
||||
|
||||
// 组件注册 和 取消
|
||||
function itemSign(name){
|
||||
|
||||
if(vdata.formNameList.indexOf(name) < 0){
|
||||
vdata.formNameList.push(name)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 组件注册 和 取消
|
||||
function unItemSign(name){
|
||||
if(vdata.formNameList.indexOf(name) >= 0){
|
||||
vdata.formNameList.splice(vdata.formNameList.indexOf(name), 1)
|
||||
}
|
||||
}
|
||||
|
||||
// { type: String, required: true, message: '', showToastError: false, validator: (rule, value) => {} }
|
||||
|
||||
// showTextError : 是否显示文本错误, 默认为true
|
||||
// showToastError: 是否alert信息, 默认为:false
|
||||
|
||||
// 错误条目数据结构: {name, value, rule, errorMsg}
|
||||
// 注意: errorMsg 跟 rule里的 message不是一回事, errorMsg 可能是 回调函数自定义。
|
||||
function pushError(name, value, errorMsg, rule){
|
||||
|
||||
// 明确是 false, 则不加入文本错误信息, 未定义等都是true(默认值)
|
||||
if(rule.showTextError === false){
|
||||
}else{
|
||||
vdata.errorListByText.push({name, value, rule, errorMsg})
|
||||
}
|
||||
|
||||
// alert 默认为 false
|
||||
if(rule.showToastError === true){
|
||||
vdata.errorListByToast.push({name, value, rule, errorMsg})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function validateFormItem(name){
|
||||
|
||||
// 不存在
|
||||
if(!name || !props.rules[name] ){
|
||||
return false
|
||||
}
|
||||
|
||||
// model的值
|
||||
const value = props.model[name]
|
||||
|
||||
props.rules[name].forEach( rule => {
|
||||
|
||||
// 必填 && 验证失败
|
||||
if(rule.required && (value == null || value == '' || typeof value == 'undefined')){
|
||||
pushError(name, value, rule.message, rule)
|
||||
|
||||
}else{ // 非必填 || 必填验证通过
|
||||
|
||||
if(rule.validator){ // 包含验证自定义函数, 暂时先不支持promise
|
||||
|
||||
let validatorResult = rule.validator(rule, value)
|
||||
if( validatorResult == true){ // 验证通过
|
||||
|
||||
}else{ // 验证失败
|
||||
pushError(name, value, validatorResult, rule)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 表单验证 , 返回Promise
|
||||
function validate(){
|
||||
|
||||
// 清空错误信息
|
||||
vdata.errorListByText = []
|
||||
vdata.errorListByToast = []
|
||||
|
||||
vdata.formNameList.forEach(name => validateFormItem(name) )
|
||||
|
||||
if(vdata.errorListByToast && vdata.errorListByToast.length > 0){
|
||||
infoBox.showToast(vdata.errorListByToast[0].errorMsg)
|
||||
}
|
||||
|
||||
console.log(vdata.formNameList);
|
||||
console.log(vdata.errorListByText);
|
||||
console.log(vdata.errorListByToast);
|
||||
|
||||
if(vdata.errorListByText.length <= 0 && vdata.errorListByToast.length <= 0){
|
||||
alert('成功')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 向下注册的组件
|
||||
const provideModel = {
|
||||
itemSign: itemSign,
|
||||
unItemSign: unItemSign,
|
||||
}
|
||||
|
||||
|
||||
provide('jeepayForm', computed( ()=> provideModel ) )
|
||||
|
||||
// provideModel 写入: errorListByText 监听不到。
|
||||
provide('jeepayErrorListByText', computed( ()=> vdata.errorListByText ) )
|
||||
|
||||
defineExpose({ validate })
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
71
components/JeepayFormItem/JeepayFormItem.vue
Normal file
71
components/JeepayFormItem/JeepayFormItem.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<view>
|
||||
{{props.label}} : <slot></slot>
|
||||
|
||||
<view v-if="props.showErrorText && vdata.errorMsg">{{ vdata.errorMsg }}</view>
|
||||
</view>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, onMounted, onUnmounted, provide, computed, inject, watch } from 'vue'
|
||||
|
||||
|
||||
const jeepayFormInject = inject("jeepayForm")
|
||||
const jeepayForm = jeepayFormInject.value
|
||||
|
||||
const jeepayErrorListByTextInject = inject("jeepayErrorListByText")
|
||||
|
||||
onMounted(() => {
|
||||
jeepayForm.itemSign(props.name)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
|
||||
jeepayForm.unItemSign(props.name)
|
||||
})
|
||||
|
||||
// 定义组件参数
|
||||
const props = defineProps({
|
||||
|
||||
name: { type: String },
|
||||
|
||||
label: { type: String },
|
||||
|
||||
// 是否显示 错误信息占位
|
||||
showErrorText: { type: Boolean, default: true },
|
||||
|
||||
})
|
||||
|
||||
const vdata = reactive({
|
||||
|
||||
errorMsg: null, // 错误提示信息
|
||||
|
||||
})
|
||||
|
||||
// 关于本条的数据
|
||||
function changeItemErrorMsg(itemErrorListByText){
|
||||
|
||||
vdata.errorMsg = null;
|
||||
if(itemErrorListByText && itemErrorListByText.length > 0){
|
||||
vdata.errorMsg = itemErrorListByText[0].errorMsg
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
watch(() => jeepayErrorListByTextInject.value, (newVal, oldVal) => {
|
||||
|
||||
if(!newVal){
|
||||
return false;
|
||||
}
|
||||
changeItemErrorMsg(newVal.filter(r=> r.name == props.name))
|
||||
}
|
||||
)
|
||||
|
||||
// 错误信息的传递
|
||||
provide('jeepayFormItemErrorMsg', computed( ()=> vdata.errorMsg ) )
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
42
components/JeepayFormItemInput/JeepayFormItemInput.vue
Normal file
42
components/JeepayFormItemInput/JeepayFormItemInput.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<uni-easyinput
|
||||
v-model="props.value" :type="props.type"
|
||||
:placeholder="vdata.errorMsg || props.placeholder"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, onMounted, onUnmounted, provide, computed, inject, watch } from 'vue'
|
||||
|
||||
const jeepayFormItemErrorMsgInject = inject("jeepayFormItemErrorMsg")
|
||||
|
||||
|
||||
// 定义组件参数
|
||||
const props = defineProps({
|
||||
|
||||
placeholder: { type: String },
|
||||
|
||||
label: { type: String },
|
||||
|
||||
type: { type: String },
|
||||
|
||||
// 双向绑定
|
||||
value: { type: [String, Number] }
|
||||
|
||||
})
|
||||
|
||||
const vdata = reactive({
|
||||
|
||||
errorMsg: null, // 错误提示信息
|
||||
|
||||
})
|
||||
|
||||
watch(() => jeepayFormItemErrorMsgInject.value, (newVal, oldVal) => {
|
||||
vdata.errorMsg = newVal
|
||||
}
|
||||
)
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
91
components/JeepayListview/JeepayListview.vue
Normal file
91
components/JeepayListview/JeepayListview.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<!--
|
||||
信息列表的预览页面, 比如 通知消息的前几条等。
|
||||
通用信息
|
||||
@author terrfly
|
||||
@site https://www.jeequan.com
|
||||
@date 2022/11/15 06:18
|
||||
-->
|
||||
<template>
|
||||
<view class="notice-wrapper">
|
||||
<view class="notice-title" v-if="props.tableTitle">
|
||||
<view class="notice-news">{{ props.tableTitle }}</view>
|
||||
<view class="notice-more flex-center" v-if="props.isShowMoreBtn" @tap="listviewClickFunc(true, null)">
|
||||
更多<image src="/static/iconImg/icon-arrow-black.svg" mode="scaleToFill" /></view>
|
||||
</view>
|
||||
<block v-for="(v, i) in props.dataList" :key="i">
|
||||
<view class="notice-main" hover-class="touch-hover" hover-stay-time="150" @tap="listviewClickFunc(false, v)">
|
||||
<view class="notice-content single-text-beyond">{{ v[props.fields.title] }}</view>
|
||||
<view class="notice-time">{{ v[props.fields.subtitle] }}</view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
|
||||
//列表标题 传则展示 不传则移除
|
||||
tableTitle: { type: String },
|
||||
|
||||
// 数据列表, 默认是按照: { title, subtitle }
|
||||
dataList: { type: Array, default: () => [] },
|
||||
|
||||
// 约定的字段
|
||||
fields: { type: Object, default: {title: 'title', subtitle: 'subtitle'} },
|
||||
|
||||
// 是否显示更多按钮
|
||||
isShowMoreBtn: { type: Boolean, default: true },
|
||||
|
||||
})
|
||||
// touchDown 列表点击回调 touchMore点击更多回调
|
||||
const emits = defineEmits(["click"])
|
||||
|
||||
|
||||
function listviewClickFunc (isClickMore, record) {
|
||||
emits('click', isClickMore, record)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.notice-wrapper {
|
||||
width: 680rpx;
|
||||
margin: 0 auto;
|
||||
background-color: $J-bg-ff;
|
||||
border-radius: $J-b-r32;
|
||||
.notice-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 30rpx;
|
||||
height: 102rpx;
|
||||
border-bottom: 1rpx solid #ededed;
|
||||
.notice-news {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
.notice-more {
|
||||
font-size: 32rpx;
|
||||
color: $J-color-t80;
|
||||
image {
|
||||
width: 42rpx;
|
||||
height: 42rpx;
|
||||
transform: rotate(90deg);
|
||||
margin-left: 5rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
.notice-main {
|
||||
padding: 30rpx;
|
||||
height: 90rpx;
|
||||
.notice-content {
|
||||
margin-bottom: 16rpx;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
.notice-time {
|
||||
font-size: 26rpx;
|
||||
color: $J-color-ta6;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
131
components/JeepayLogin/LoginTextUp.vue
Normal file
131
components/JeepayLogin/LoginTextUp.vue
Normal 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>
|
||||
165
components/JeepayNavigation/JeepayNavigation.vue
Normal file
165
components/JeepayNavigation/JeepayNavigation.vue
Normal file
@@ -0,0 +1,165 @@
|
||||
<!--
|
||||
Jeepay导航卡片, 支持4种风格
|
||||
@author terrfly
|
||||
@site https://www.jeequan.com
|
||||
@date 2022/11/16 11:45
|
||||
-->
|
||||
<template>
|
||||
<!-- 宫格类型 -->
|
||||
<template v-if="props.type == 'grid'">
|
||||
<view class="grid-card-wrapper" :style="{ margin: calcPadding(), '--space-w': space + 'rpx' }">
|
||||
<block v-for="(v, i) in navListComputed" :key="i">
|
||||
<view
|
||||
class="card-main"
|
||||
:style="{ margin: calcMargin(), '--radius-size': radiusSize + 'rpx' }"
|
||||
hover-class="touch-hover"
|
||||
hover-stay-time="150"
|
||||
:class="{ 'card-big-main': navListComputed.length == 4 || navListComputed.length <= 2 }"
|
||||
@tap="clickFunc(v)"
|
||||
>
|
||||
<image :src="v.icon" mode="scaleToFill" />
|
||||
<view class="card-title">{{ v.title }}</view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 列表类型 -->
|
||||
<template v-if="props.type == 'list'">
|
||||
<view class="list-nav-wrapper" :style="{ '--radius-size': radiusSize + 'rpx' }">
|
||||
<block v-for="(v, i) in navListComputed" :key="i">
|
||||
<view class="nav-main" hover-class="touch-hover" hover-stay-time="150" @tap="clickFunc(v)">
|
||||
<image :src="v.icon" mode="scaleToFill" />
|
||||
<view class="nav-text">{{ v.title }}</view>
|
||||
<image class="nav-right" src="/static/iconImg/icon-arrow-right.svg" mode="scaleToFill" />
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, computed } from "vue"
|
||||
import go from "@/commons/utils/go.js"
|
||||
import ent from '@/commons/utils/ent.js'
|
||||
import {
|
||||
hasPermission
|
||||
} from '@/commons/utils/hasPermission.js';
|
||||
// 定义组件参数
|
||||
const props = defineProps({
|
||||
//显示类型: 支持 grid-宫格 list-列表
|
||||
type: { type: String, default: "list" },
|
||||
|
||||
// 圆角矩形大小 为0 则 无圆角
|
||||
radiusSize: { type: Number, default: 32 },
|
||||
|
||||
//间隙类型: 仅grid-宫格时生效, 支持 0 和 25
|
||||
space: { type: Number, default: 25 },
|
||||
|
||||
// 导航列表, 格式:{ icon, title, pageUrl, clickFunc, entId }
|
||||
navList: { type: Array, default: () => [] },
|
||||
})
|
||||
|
||||
// 点击事件
|
||||
async function clickFunc(nav) {
|
||||
if(nav.pageUrl=="PAGES_SALES_SUMMARY"){
|
||||
let res =await hasPermission('允许查看经营数据')
|
||||
if(!res) return
|
||||
}
|
||||
// 包含回调事件
|
||||
if (nav.clickFunc) {
|
||||
return nav.clickFunc(nav)
|
||||
}
|
||||
|
||||
// 包含URL
|
||||
if (nav.pageUrl) {
|
||||
return go.to(nav.pageUrl)
|
||||
}
|
||||
}
|
||||
const calcPadding = () => `${50 - props.space}rpx 35rpx 50rpx ${35 - props.space}rpx`
|
||||
const calcMargin = () => `${props.space}rpx 0 0 ${props.space}rpx`
|
||||
|
||||
|
||||
// 计算属性
|
||||
let navListComputed = computed(() => {
|
||||
return props.navList.filter(r => hasEnt(r.entId))
|
||||
})
|
||||
|
||||
function hasEnt(entId){
|
||||
|
||||
// 不包含: 说明无需隐藏
|
||||
if(!entId){
|
||||
return true
|
||||
}
|
||||
return ent.has(entId)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.grid-card-wrapper {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
background-color: $v-color-bgrey;
|
||||
padding-top: 25rpx;
|
||||
.card-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 25rpx 0 0 25rpx;
|
||||
width: calc(33.3% - var(--space-w));
|
||||
height: 210rpx;
|
||||
border-radius: var(--radius-size);
|
||||
background-color: $J-bg-ff;
|
||||
image {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
}
|
||||
.card-title {
|
||||
margin-top: 22rpx;
|
||||
color: $J-color-t80;
|
||||
font-size: $J-f-size30;
|
||||
}
|
||||
}
|
||||
.card-big-main {
|
||||
width: calc(50% - var(--space-w));
|
||||
}
|
||||
}
|
||||
|
||||
.list-nav-wrapper {
|
||||
margin: 0 auto;
|
||||
width: 680rpx;
|
||||
border-radius: var(--radius-size);
|
||||
overflow: hidden;
|
||||
|
||||
.nav-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 30rpx;
|
||||
height: 120rpx;
|
||||
background-color: $J-bg-ff;
|
||||
image {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
vertical-align: text-bottom;
|
||||
margin: 0 20rpx 0 10rpx;
|
||||
}
|
||||
.nav-text {
|
||||
flex: 1;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
.nav-right {
|
||||
margin: 0;
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
}
|
||||
}
|
||||
.nav-setup {
|
||||
margin: 30rpx auto;
|
||||
width: 680rpx;
|
||||
border-radius: $J-b-r32;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
110
components/JeepayPopupConfirm/JeepayPopupConfirm.vue
Normal file
110
components/JeepayPopupConfirm/JeepayPopupConfirm.vue
Normal file
@@ -0,0 +1,110 @@
|
||||
<!--
|
||||
通用 确认弹层。
|
||||
@author terrfly
|
||||
@site https://www.jeequan.com
|
||||
@date 2022/11/22 07:30
|
||||
-->
|
||||
|
||||
<template>
|
||||
<uni-popup ref="popup" type="bottom" @maskClick="cancelFunc" mask-background-color="rgba(0,0,0,.5)" @change='change' :safe-area="false">
|
||||
<view class="tips-wrapper">
|
||||
<view class="tips-text" :style="{ textIndent: vdata.title.length > 20 ? '2em' : 0 }"> {{ vdata.title }} </view>
|
||||
<view class="tips-text tips-confirm" hover-class="u-cell-hover" hover-stay-time="150" :style="{color: vdata.confirmColor }" @tap="confirmFunc">
|
||||
{{ vdata.confirmText || "确认" }}
|
||||
</view>
|
||||
<view class="line"></view>
|
||||
<view class="tips-text tips-cancel" hover-class="u-cell-hover" hover-stay-time="150" @tap="cancelFunc">
|
||||
{{ vdata.cancelText || "取消" }}
|
||||
</view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive,inject } from "vue"
|
||||
|
||||
const popup = ref(null) //弹窗实例
|
||||
const tips = ref("") // 提示语
|
||||
|
||||
const vdata = reactive({
|
||||
title: '',
|
||||
confirmText: '',
|
||||
cancelText: '',
|
||||
confirmColor: '',
|
||||
promiseObject: { },
|
||||
})
|
||||
|
||||
function confirmFunc(){
|
||||
popup.value.close()
|
||||
vdata.promiseObject.resolve()
|
||||
}
|
||||
|
||||
function cancelFunc(){
|
||||
popup.value.close()
|
||||
vdata.promiseObject.reject()
|
||||
}
|
||||
function open (title, confirmTextOrExtObject , cancelText ) {
|
||||
vdata.title = title
|
||||
vdata.confirmText = typeof confirmTextOrExtObject == "string" ? confirmTextOrExtObject : ''
|
||||
vdata.cancelText = cancelText
|
||||
if(typeof confirmTextOrExtObject == "object" ) {
|
||||
Object.assign(vdata,confirmTextOrExtObject)
|
||||
}
|
||||
popup.value.open()
|
||||
|
||||
return new Promise( (resolve, reject) => {
|
||||
vdata.promiseObject.resolve = resolve
|
||||
vdata.promiseObject.reject = reject
|
||||
})
|
||||
}
|
||||
|
||||
let changePageMetaOverflowFunc = inject('changePageMetaOverflowFunc')
|
||||
const change = (e)=>{
|
||||
if(changePageMetaOverflowFunc){
|
||||
changePageMetaOverflowFunc(!e.show)
|
||||
}
|
||||
}
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tips-wrapper {
|
||||
overflow: hidden;
|
||||
border-radius: 32rpx 32rpx 0 0;
|
||||
padding-bottom: 60rpx;
|
||||
background-color: #fff;
|
||||
.tips-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
min-height: 110rpx;
|
||||
|
||||
line-height: 1.8;
|
||||
padding: 30rpx;
|
||||
box-sizing: border-box;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
.line {
|
||||
height: 20rpx;
|
||||
background-color: rgba(0, 0, 0, 0.07);
|
||||
}
|
||||
.tips-confirm {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-top: 1rpx solid rgba(0, 0, 0, 0.07);
|
||||
color: #2980fd;
|
||||
font-size: 33rpx;
|
||||
}
|
||||
.tips-cancel {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: $J-color-t80;
|
||||
font-size: 33rpx;
|
||||
}
|
||||
.u-cell-hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
91
components/JeepayPopupInput/JeepayPopupInput.vue
Normal file
91
components/JeepayPopupInput/JeepayPopupInput.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<!--
|
||||
组件作用: 单个输入框的弹层。
|
||||
输入框的弹出框, uni必须使用的是: 组件方式, 无法直接JS调起。
|
||||
// 要求, 输入空也可以点击确定。
|
||||
示例:
|
||||
|
||||
A: 一般用法:
|
||||
<JeepayPopupInput ref="ref" label="备注" :maxLength="3" v-model:value="vdata.payRemark" />
|
||||
|
||||
B: 自定义 校验规则
|
||||
<JeepayPopupInput ref="ref" label="备注" :maxLength="3" v-model:value="vdata.payRemark" :rules="[ {required: true }, {pattern: '^(123)+$' } ]" />
|
||||
|
||||
@author terrfly
|
||||
@site https://www.jeequan.com
|
||||
@date 2022/11/17 10:46
|
||||
-->
|
||||
|
||||
<template>
|
||||
<uni-popup ref="popupRef" type="dialog">
|
||||
<uni-popup-dialog :before-close="true" mode="input" :title="`请输入${props.label}`" @confirm="confirmFunc" @close="popupRef.close()">
|
||||
<template #default>
|
||||
<uni-forms ref="formRef" :rules="vdata.rules" :modelValue="vdata.formData">
|
||||
<uni-forms-item label="" name="singleInputVal">
|
||||
<uni-easyinput :inputBorder="false" type="text" v-model="vdata.formData.singleInputVal" :placeholder="`最多输入${props.maxLength}个字`" :maxlength="props.maxLength" />
|
||||
</uni-forms-item>
|
||||
</uni-forms>
|
||||
</template>
|
||||
</uni-popup-dialog>
|
||||
</uni-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, watch } from 'vue'
|
||||
|
||||
// 定义组件参数
|
||||
const props = defineProps({
|
||||
// 显示文本
|
||||
label: { type: String, default: '' },
|
||||
|
||||
// 操作对象
|
||||
value: [Number, String],
|
||||
|
||||
// 输入框最大位数
|
||||
maxLength: { type: Number, default: 10 },
|
||||
|
||||
rules: { type: Array, default: () => [] },
|
||||
})
|
||||
|
||||
// emit 父组件使用: v-model="val" 进行双向绑定。
|
||||
const emit = defineEmits(['update:value'])
|
||||
|
||||
// 重置按钮,不能直接重置时间选择,需要通过watch监听,进行重置
|
||||
watch(
|
||||
() => props.value,
|
||||
(newVal, oldVal) => {
|
||||
vdata.formData.singleInputVal = newVal
|
||||
}
|
||||
)
|
||||
|
||||
const popupRef = ref()
|
||||
const formRef = ref()
|
||||
|
||||
const vdata = reactive({
|
||||
// 表单的值
|
||||
formData: { singleInputVal: '' },
|
||||
|
||||
// 验证规则
|
||||
rules: {
|
||||
singleInputVal: { rules: props.rules },
|
||||
},
|
||||
})
|
||||
|
||||
// 点击确认按钮。
|
||||
function confirmFunc() {
|
||||
formRef.value.validate().then(() => {
|
||||
emit('update:value', vdata.formData.singleInputVal)
|
||||
popupRef.value.close()
|
||||
})
|
||||
}
|
||||
|
||||
// 显示弹层
|
||||
function open() {
|
||||
vdata.formData.singleInputVal = props.value
|
||||
popupRef.value.open()
|
||||
}
|
||||
|
||||
// 将表格事件暴露出去 https://www.jianshu.com/p/39d14c25c987
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,361 @@
|
||||
<!--
|
||||
组件作用: 弹层, 列表支持 单选, 多选。
|
||||
|
||||
使用方法:
|
||||
|
||||
<JeepayPopupListSelect ref="jeepayPopupListSelect" :reqTableDataFunc="reqTableDataFunc" :fields="{ key: 'articleId', left: 'articleId', right: 'title'}"/>
|
||||
jeepayPopupListSelect.value.open().then((selected) => {
|
||||
console.log(selected);
|
||||
jeepayPopupListSelect.value.close() //自行关闭
|
||||
})
|
||||
|
||||
@author terrfly
|
||||
@site https://www.jeequan.com
|
||||
@date 2022/11/26 16:24
|
||||
-->
|
||||
<template>
|
||||
<uni-popup ref="popupRef" type="bottom" @maskClick="close" @change="change" mask-background-color="rgba(0,0,0,.5)">
|
||||
<view class="card-wrapper">
|
||||
<view class="selected-title" v-if="title">
|
||||
{{ title }}
|
||||
</view>
|
||||
<view v-if="props.searchInputName" class="input-search">
|
||||
<uni-easyinput
|
||||
v-model="vdata.searchData[props.searchInputName]"
|
||||
prefixIcon="search"
|
||||
:inputBorder="false"
|
||||
:clearable="false"
|
||||
:styles="{
|
||||
backgroundColor: 'transparent'
|
||||
}"
|
||||
type="text"
|
||||
placeholder="搜索"
|
||||
placeholderStyle=" color: rgba(0,0,0,0.85);font-size: 30rpx;"
|
||||
/>
|
||||
<view class="search-button" @tap="searchFunc">搜索</view>
|
||||
</view>
|
||||
|
||||
<!-- 数据渲染 -->
|
||||
<JTableList ref="jeepayTableListRef" :reqTableDataFunc="reqTableDataFuncWrapper" :searchData="vdata.searchData" :initData="false" height="406rpx">
|
||||
<template #tableBody="{ record }">
|
||||
<view class="store" @tap="selectFunc(record)">
|
||||
<template v-if="isCheckbox">
|
||||
<view class="more-selected" :class="{ 'more-disabled': record.hasDirector, 'more-active': hasSelected(record) }">
|
||||
<image :src="record.hasDirector ? '/static/iconImg/icon-disable.svg' : '/static/iconImg/icon-check.svg'" mode="scaleToFill" />
|
||||
</view>
|
||||
</template>
|
||||
<view v-else :class="{ 'store-dot-def': true, 'active-dot': hasSelected(record) }"></view>
|
||||
<slot name="content" :record="record">
|
||||
<view class="store-inner-slot">
|
||||
<view class="left">
|
||||
<text>{{ record[props.fields.left] }}</text>
|
||||
</view>
|
||||
<view class="right">{{ record[props.fields.right] }}</view>
|
||||
</view>
|
||||
</slot>
|
||||
</view>
|
||||
</template>
|
||||
</JTableList>
|
||||
|
||||
<!-- <button v-if="vdata.hasNext" @tap="jeepayTableListRef.addNext()">下一页</button> -->
|
||||
|
||||
<view class="footer-wrapper">
|
||||
<view class="footer-main">
|
||||
<view class="footer-button">
|
||||
<view class="flex-center" hover-class="touch-button" @tap="close">取消</view>
|
||||
<view @tap="confirmFunc" class="confirm flex-center" hover-class="touch-button">确认</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { inject, nextTick, reactive, ref, render, watch } from 'vue';
|
||||
|
||||
const jeepayTableListRef = ref();
|
||||
|
||||
// 定义组件参数
|
||||
const props = defineProps({
|
||||
configMode: { type: String, default: '' }, // 搜索时仅展示商户号
|
||||
// 请求业务数据, 参数可自行控制。
|
||||
reqTableDataFunc: { type: Function, default: () => {} },
|
||||
|
||||
// 搜索输入框的name , 不传入则不显示搜索。
|
||||
searchInputName: { type: String },
|
||||
|
||||
// 约定的字段 , 若不合适请通过插槽自行插入。
|
||||
fields: { type: Object, default: { key: 'id', left: 'left', right: 'right' } },
|
||||
|
||||
// 是否多选框, 默认单选。
|
||||
isCheckbox: { type: Boolean, default: false },
|
||||
// 标题有则显示无则隐藏
|
||||
title: [String, Number]
|
||||
});
|
||||
let changePageMetaOverflowFunc = inject('changePageMetaOverflowFunc');
|
||||
const emits = defineEmits(['confirm']);
|
||||
|
||||
const popupRef = ref();
|
||||
|
||||
console.log(props, 'propsprops');
|
||||
const vdata = reactive({
|
||||
searchData: {
|
||||
|
||||
}, //当前页的搜索条件
|
||||
|
||||
hasNext: false, // 是否包含下一页数据
|
||||
|
||||
selectedList: [], // 选择的值
|
||||
|
||||
promiseObject: {}
|
||||
});
|
||||
|
||||
// 点击搜索
|
||||
function searchFunc() {
|
||||
jeepayTableListRef.value.refTable(true);
|
||||
}
|
||||
|
||||
/** reqTableDataFunc 处理函数 */
|
||||
function reqTableDataFuncWrapper(params) {
|
||||
return props.reqTableDataFunc(params).then(({ bizData }) => {
|
||||
vdata.hasNext = bizData.hasNext; // 是否包含下一页
|
||||
return Promise.resolve({ bizData });
|
||||
});
|
||||
}
|
||||
|
||||
// 是否选中
|
||||
function hasSelected(record) {
|
||||
return vdata.selectedList.filter((r) => r[props.fields.key] == record[props.fields.key]).length > 0;
|
||||
}
|
||||
|
||||
/** 选择函数 **/
|
||||
function selectFunc(record) {
|
||||
// 判断是否已存在
|
||||
if (hasSelected(record)) {
|
||||
// 多选需删除
|
||||
if (props.isCheckbox) {
|
||||
vdata.selectedList.splice(
|
||||
vdata.selectedList.findIndex((r) => r[props.fields.key] == record[props.fields.key]),
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
// 单选无需操作
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 多选直接添加
|
||||
if (props.isCheckbox) {
|
||||
if (record.hasDirector) return; //如果被其他店长绑定 禁止选中
|
||||
return vdata.selectedList.push(record);
|
||||
} else {
|
||||
// 单选
|
||||
|
||||
return (vdata.selectedList = [record]);
|
||||
}
|
||||
}
|
||||
|
||||
// 点击确定事件
|
||||
function confirmFunc() {
|
||||
// 多选
|
||||
if (props.isCheckbox) {
|
||||
emits('confirm', vdata.selectedList);
|
||||
return vdata.promiseObject.resolve(vdata.selectedList);
|
||||
}
|
||||
|
||||
// 单选 仅返回第一个即可。
|
||||
emits('confirm', vdata.selectedList.length > 0 ? vdata.selectedList[0] : null);
|
||||
vdata.promiseObject.resolve(vdata.selectedList.length > 0 ? vdata.selectedList[0] : null);
|
||||
}
|
||||
|
||||
// 显示弹层, 并且返回一个promise
|
||||
// promise.then(selected)
|
||||
// 注意: 此Promise 只会回调一次, 如需要验证是否选中正确, 需要使用@confirm事件。
|
||||
// 如果对拿到的值不做校验,获取到然后关闭。 那么可以直接使用.then() 然后立马关闭弹层。
|
||||
function open(defaultVal) {
|
||||
console.log(111111111111111);
|
||||
// 清空数据
|
||||
vdata.selectedList = [];
|
||||
vdata.searchData[props.searchInputName] = '';
|
||||
|
||||
// 默认选中。
|
||||
if (defaultVal) {
|
||||
if (Array.isArray(defaultVal)) {
|
||||
vdata.selectedList = defaultVal;
|
||||
} else {
|
||||
vdata.selectedList = [defaultVal];
|
||||
}
|
||||
}
|
||||
|
||||
popupRef.value.open();
|
||||
|
||||
nextTick(() => {
|
||||
jeepayTableListRef.value.refTable(true);
|
||||
uni.hideTabBar(); // 可能报错, 在nextTick中, 不影响其他业务。
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
vdata.promiseObject.resolve = resolve;
|
||||
vdata.promiseObject.reject = reject;
|
||||
});
|
||||
}
|
||||
|
||||
// 关闭弹层
|
||||
function close() {
|
||||
popupRef.value.close();
|
||||
uni.showTabBar();
|
||||
}
|
||||
const change = (e) => {
|
||||
if (changePageMetaOverflowFunc) {
|
||||
changePageMetaOverflowFunc(!e.show);
|
||||
}
|
||||
};
|
||||
// 将表格事件暴露出去 https://www.jianshu.com/p/39d14c25c987
|
||||
defineExpose({ open, close });
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.card-wrapper {
|
||||
border-radius: 32rpx 32rpx 0 0;
|
||||
background-color: #fff;
|
||||
overflow: hidden;
|
||||
min-height: 70vh;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
.selected-title {
|
||||
height: 110rpx;
|
||||
line-height: 110rpx;
|
||||
text-align: center;
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
border-bottom: 1rpx solid #ededed;
|
||||
}
|
||||
.input-search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 10rpx;
|
||||
margin: 30rpx;
|
||||
border-radius: 10rpx;
|
||||
background-color: #f7f7f7;
|
||||
.search-button {
|
||||
padding: 24rpx 30rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #2980fd;
|
||||
}
|
||||
}
|
||||
.store {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
padding: 0 40rpx;
|
||||
height: 120rpx;
|
||||
font-size: 30rpx;
|
||||
.more-selected {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
border-radius: 6rpx;
|
||||
margin-right: 20rpx;
|
||||
border: 2rpx solid rgba(217, 217, 217, 1);
|
||||
image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.more-disabled {
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
.more-active {
|
||||
background-color: #2980fd;
|
||||
}
|
||||
.store-dot-def {
|
||||
position: relative;
|
||||
margin-right: 20rpx;
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
background-color: #d7d8d9;
|
||||
border-radius: 50%;
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 50%;
|
||||
height: 50%;
|
||||
border-radius: 50%;
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
.active-dot {
|
||||
background-color: #2980fd;
|
||||
}
|
||||
|
||||
.store-inner-slot {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.right {
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
}
|
||||
.all-store::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 40rpx;
|
||||
right: 40rpx;
|
||||
height: 1rpx;
|
||||
background-color: #ededed;
|
||||
}
|
||||
.footer-wrapper {
|
||||
height: 186rpx;
|
||||
.footer-main {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: env(safe-area-inset-bottom);
|
||||
border-top: 1rpx solid #ededed;
|
||||
.tips {
|
||||
margin: 20rpx;
|
||||
text-align: center;
|
||||
font-size: 27rpx;
|
||||
color: #a6a6a6;
|
||||
}
|
||||
.footer-button {
|
||||
padding: 0 30rpx;
|
||||
margin-top: 30rpx;
|
||||
padding-bottom: 30rpx;
|
||||
display: flex;
|
||||
|
||||
justify-content: space-between;
|
||||
view {
|
||||
width: 330rpx;
|
||||
height: 110rpx;
|
||||
font-size: 33rpx;
|
||||
font-weight: 500;
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
border-radius: 20rpx;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
.confirm {
|
||||
color: #fff;
|
||||
background: $jeepay-bg-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
387
components/JeepayPopupListSelect/JeepayPopupListSelect.vue
Normal file
387
components/JeepayPopupListSelect/JeepayPopupListSelect.vue
Normal file
@@ -0,0 +1,387 @@
|
||||
<!--
|
||||
组件作用: 弹层, 列表支持 单选, 多选。
|
||||
|
||||
使用方法:
|
||||
|
||||
<JeepayPopupListSelect ref="jeepayPopupListSelect" :reqTableDataFunc="reqTableDataFunc" :fields="{ key: 'articleId', left: 'articleId', right: 'title'}"/>
|
||||
jeepayPopupListSelect.value.open().then((selected) => {
|
||||
console.log(selected);
|
||||
jeepayPopupListSelect.value.close() //自行关闭
|
||||
})
|
||||
|
||||
@author terrfly
|
||||
@site https://www.jeequan.com
|
||||
@date 2022/11/26 16:24
|
||||
-->
|
||||
<template>
|
||||
<uni-popup ref="popupRef" type="bottom" @maskClick="close" @change="change" mask-background-color="rgba(0,0,0,.5)" :safe-area="false">
|
||||
<view class="card-wrapper">
|
||||
<view class="selected-title" v-if="title">
|
||||
{{ title }}
|
||||
</view>
|
||||
<view class="search-wrap" v-if="props.searchInputName">
|
||||
<view class="input-search">
|
||||
<uni-easyinput
|
||||
v-model="vdata.searchData[props.searchInputName]"
|
||||
prefixIcon="search"
|
||||
:inputBorder="false"
|
||||
:clearable="false"
|
||||
:styles="{
|
||||
backgroundColor: 'transparent'
|
||||
}"
|
||||
type="text"
|
||||
placeholder="搜索"
|
||||
placeholderStyle=" color: rgba(0,0,0,0.85);font-size: 30rpx;"
|
||||
/>
|
||||
<view class="search-button" @tap="searchFunc">搜索</view>
|
||||
</view>
|
||||
<!-- <view class="add-btn" hover-class="touch-button" v-if="props.addUse" @tap="">新增应用</view> -->
|
||||
</view>
|
||||
<!-- 数据渲染 -->
|
||||
<JTableList ref="jeepayTableListRef" :reqTableDataFunc="reqTableDataFuncWrapper" :searchData="vdata.searchData" :initData="false" height="406rpx">
|
||||
<template #tableBody="{ record }">
|
||||
<view class="store" @tap="selectFunc(record)">
|
||||
<template v-if="isCheckbox">
|
||||
<view class="more-selected" :class="{ 'more-disabled': record.hasDirector, 'more-active': hasSelected(record) }">
|
||||
<image :src="record.hasDirector ? '/static/iconImg/icon-disable.svg' : '/static/iconImg/icon-check.svg'" mode="scaleToFill" />
|
||||
</view>
|
||||
</template>
|
||||
<view v-else :class="{ 'store-dot-def': true, 'active-dot': hasSelected(record) }"></view>
|
||||
<slot name="content" :record="record">
|
||||
<view class="store-inner-slot">
|
||||
<view class="left">
|
||||
<!-- <text>{{ record[props.fields.left] }}</text> -->
|
||||
<text class="tit">{{ record[props.fields.left] }}</text>
|
||||
<text class="ino" v-if="record.intro">{{ record.intro }}</text>
|
||||
</view>
|
||||
<view class="right">{{ record[props.fields.right] }}</view>
|
||||
</view>
|
||||
</slot>
|
||||
</view>
|
||||
</template>
|
||||
</JTableList>
|
||||
|
||||
<!-- <button v-if="vdata.hasNext" @tap="jeepayTableListRef.addNext()">下一页</button> -->
|
||||
|
||||
<view class="footer-wrapper">
|
||||
<view class="footer-main">
|
||||
<view class="footer-button">
|
||||
<view class="flex-center" hover-class="touch-button" @tap="close">取消</view>
|
||||
<view @tap="confirmFunc" class="confirm flex-center" hover-class="touch-button">确认</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { inject, nextTick, reactive, ref, render, watch } from 'vue';
|
||||
|
||||
const jeepayTableListRef = ref();
|
||||
|
||||
// 定义组件参数
|
||||
const props = defineProps({
|
||||
configMode: { type: String, default: '' }, // 搜索时仅展示商户号
|
||||
// 请求业务数据, 参数可自行控制。
|
||||
reqTableDataFunc: { type: Function, default: () => {} },
|
||||
|
||||
// 搜索输入框的name , 不传入则不显示搜索。
|
||||
searchInputName: { type: String },
|
||||
|
||||
// 约定的字段 , 若不合适请通过插槽自行插入。
|
||||
fields: { type: Object, default: { key: 'id', left: 'left', right: 'right' } },
|
||||
|
||||
// 是否多选框, 默认单选。
|
||||
isCheckbox: { type: Boolean, default: false },
|
||||
// 标题有则显示无则隐藏
|
||||
title: [String, Number],
|
||||
// 是否显示增加引用按钮
|
||||
addUse: { type: Boolean, default: false }
|
||||
});
|
||||
let changePageMetaOverflowFunc = inject('changePageMetaOverflowFunc');
|
||||
const emits = defineEmits(['confirm']);
|
||||
|
||||
const popupRef = ref();
|
||||
|
||||
console.log(props, 'propsprops');
|
||||
const vdata = reactive({
|
||||
searchData: {}, //当前页的搜索条件
|
||||
|
||||
hasNext: false, // 是否包含下一页数据
|
||||
|
||||
selectedList: [], // 选择的值
|
||||
|
||||
promiseObject: {}
|
||||
});
|
||||
|
||||
// 点击搜索
|
||||
function searchFunc() {
|
||||
jeepayTableListRef.value.refTable(true);
|
||||
}
|
||||
|
||||
/** reqTableDataFunc 处理函数 */
|
||||
function reqTableDataFuncWrapper(params) {
|
||||
return props.reqTableDataFunc(params).then(({ bizData }) => {
|
||||
vdata.hasNext = bizData.hasNext; // 是否包含下一页
|
||||
return Promise.resolve({ bizData });
|
||||
});
|
||||
}
|
||||
|
||||
// 是否选中
|
||||
function hasSelected(record) {
|
||||
return vdata.selectedList.filter((r) => r[props.fields.key] == record[props.fields.key]).length > 0;
|
||||
}
|
||||
|
||||
/** 选择函数 **/
|
||||
function selectFunc(record) {
|
||||
// 判断是否已存在
|
||||
if (hasSelected(record)) {
|
||||
// 多选需删除
|
||||
if (props.isCheckbox) {
|
||||
vdata.selectedList.splice(
|
||||
vdata.selectedList.findIndex((r) => r[props.fields.key] == record[props.fields.key]),
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
// 单选无需操作
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 多选直接添加
|
||||
if (props.isCheckbox) {
|
||||
if (record.hasDirector) return; //如果被其他店长绑定 禁止选中
|
||||
return vdata.selectedList.push(record);
|
||||
} else {
|
||||
// 单选
|
||||
|
||||
return (vdata.selectedList = [record]);
|
||||
}
|
||||
}
|
||||
|
||||
// 点击确定事件
|
||||
function confirmFunc() {
|
||||
// 多选
|
||||
if (props.isCheckbox) {
|
||||
emits('confirm', vdata.selectedList);
|
||||
return vdata.promiseObject.resolve(vdata.selectedList);
|
||||
}
|
||||
|
||||
// 单选 仅返回第一个即可。
|
||||
emits('confirm', vdata.selectedList.length > 0 ? vdata.selectedList[0] : null);
|
||||
vdata.promiseObject.resolve(vdata.selectedList.length > 0 ? vdata.selectedList[0] : null);
|
||||
}
|
||||
|
||||
// 显示弹层, 并且返回一个promise
|
||||
// promise.then(selected)
|
||||
// 注意: 此Promise 只会回调一次, 如需要验证是否选中正确, 需要使用@confirm事件。
|
||||
// 如果对拿到的值不做校验,获取到然后关闭。 那么可以直接使用.then() 然后立马关闭弹层。
|
||||
function open(defaultVal) {
|
||||
// 清空数据
|
||||
vdata.selectedList = [];
|
||||
vdata.searchData[props.searchInputName] = '';
|
||||
|
||||
// 默认选中。
|
||||
if (defaultVal) {
|
||||
if (Array.isArray(defaultVal)) {
|
||||
vdata.selectedList = defaultVal;
|
||||
} else {
|
||||
vdata.selectedList = [defaultVal];
|
||||
}
|
||||
}
|
||||
|
||||
popupRef.value.open();
|
||||
|
||||
nextTick(() => {
|
||||
jeepayTableListRef.value.refTable(true);
|
||||
uni.hideTabBar(); // 可能报错, 在nextTick中, 不影响其他业务。
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
vdata.promiseObject.resolve = resolve;
|
||||
vdata.promiseObject.reject = reject;
|
||||
});
|
||||
}
|
||||
|
||||
// 关闭弹层
|
||||
function close() {
|
||||
popupRef.value.close();
|
||||
uni.showTabBar();
|
||||
}
|
||||
const change = (e) => {
|
||||
if (changePageMetaOverflowFunc) {
|
||||
changePageMetaOverflowFunc(!e.show);
|
||||
}
|
||||
};
|
||||
// 将表格事件暴露出去 https://www.jianshu.com/p/39d14c25c987
|
||||
defineExpose({ open, close });
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.card-wrapper {
|
||||
border-radius: 32rpx 32rpx 0 0;
|
||||
background-color: #fff;
|
||||
overflow: hidden;
|
||||
min-height: 70vh;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
.selected-title {
|
||||
height: 110rpx;
|
||||
line-height: 110rpx;
|
||||
text-align: center;
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
border-bottom: 1rpx solid #ededed;
|
||||
}
|
||||
.search-wrap {
|
||||
padding: 30rpx;
|
||||
display: flex;
|
||||
.input-search {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 10rpx;
|
||||
border-radius: 10rpx;
|
||||
background-color: #f7f7f7;
|
||||
.search-button {
|
||||
padding: 24rpx 30rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #2980fd;
|
||||
}
|
||||
}
|
||||
.add-btn {
|
||||
border-radius: 10rpx;
|
||||
color: #fff;
|
||||
padding: 0 20upx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: $jeepay-bg-primary;
|
||||
margin-left: 30rpx;
|
||||
}
|
||||
}
|
||||
.store {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
margin: 40rpx;
|
||||
font-size: 30rpx;
|
||||
background-color: #f5f5f5;
|
||||
padding: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
.more-selected {
|
||||
flex-shrink: 0;
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
border-radius: 6rpx;
|
||||
margin-right: 20rpx;
|
||||
border: 2rpx solid rgba(217, 217, 217, 1);
|
||||
image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.more-disabled {
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
.more-active {
|
||||
background-color: #2980fd;
|
||||
}
|
||||
.store-dot-def {
|
||||
position: relative;
|
||||
margin-right: 20rpx;
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
background-color: #d7d8d9;
|
||||
border-radius: 50%;
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 50%;
|
||||
height: 50%;
|
||||
border-radius: 50%;
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
.active-dot {
|
||||
background-color: #2980fd;
|
||||
}
|
||||
|
||||
.store-inner-slot {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.ino {
|
||||
color: #999;
|
||||
padding-top: 12upx;
|
||||
font-size: 24upx;
|
||||
}
|
||||
}
|
||||
.right {
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
}
|
||||
.all-store::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 40rpx;
|
||||
right: 40rpx;
|
||||
height: 1rpx;
|
||||
background-color: #ededed;
|
||||
}
|
||||
.footer-wrapper {
|
||||
height: 186rpx;
|
||||
.footer-main {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: env(safe-area-inset-bottom);
|
||||
border-top: 1rpx solid #ededed;
|
||||
background-color: #fff;
|
||||
.tips {
|
||||
margin: 20rpx;
|
||||
text-align: center;
|
||||
font-size: 27rpx;
|
||||
color: #a6a6a6;
|
||||
}
|
||||
.footer-button {
|
||||
padding: 0 30rpx;
|
||||
margin-top: 30rpx;
|
||||
padding-bottom: 30rpx;
|
||||
display: flex;
|
||||
|
||||
justify-content: space-between;
|
||||
view {
|
||||
width: 330rpx;
|
||||
height: 110rpx;
|
||||
font-size: 33rpx;
|
||||
font-weight: 500;
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
border-radius: 20rpx;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
.confirm {
|
||||
color: #fff;
|
||||
background: $jeepay-bg-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
116
components/JeepayRadioPopupView/JeepayRadioPopupView.vue
Normal file
116
components/JeepayRadioPopupView/JeepayRadioPopupView.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<!--
|
||||
单选 view 一般用作: form表单内使用
|
||||
@author terrfly
|
||||
@site https://www.jeepay.vip
|
||||
@date 2022/12/01 16:18
|
||||
-->
|
||||
<template>
|
||||
<view>
|
||||
<view @tap="show">
|
||||
<!-- 插槽自定义内容 -->
|
||||
<slot name="view" :record="vdata.checkedData">
|
||||
<view class="selected-radio">
|
||||
<view v-if="hasVal()" style="color: black">{{vdata.checkedData?.label}}</view>
|
||||
<view v-else>{{props.label}}</view>
|
||||
<image style="width: 30rpx;height: 30rpx" src="/static/right.svg" mode="scaleToFill" />
|
||||
</view>
|
||||
</slot>
|
||||
</view>
|
||||
<!-- popup 和 tap不能放置在同一个 view下。 -->
|
||||
<view>
|
||||
<JSinglePopup ref="popupRef" :list="props.list" @confirm="confirmFunc" />
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, nextTick, watch, onMounted, inject } from "vue"
|
||||
|
||||
const popupRef = ref()
|
||||
|
||||
// emit 父组件使用: v-model:value="val" 进行双向绑定。
|
||||
const emits = defineEmits(['update:value', 'change'])
|
||||
|
||||
const props = defineProps({
|
||||
|
||||
//绑定的值, 双向绑定
|
||||
value: { type: [String, Number] },
|
||||
|
||||
// 显示的名字
|
||||
label: { type: [String, Number], default: "请选择" },
|
||||
|
||||
// 数组
|
||||
list: { type: Array, default: () => [] },
|
||||
|
||||
// 是否多选框, 默认单选。
|
||||
isCheckbox: { type: Boolean, default: false },
|
||||
})
|
||||
|
||||
console.log(props.isCheckbox,'isCheckboxisCheckboxisCheckboxisCheckbox');
|
||||
|
||||
onMounted(()=>{
|
||||
changePropsVal(props.value)
|
||||
})
|
||||
|
||||
// 切换时
|
||||
watch(() => props.value, (newVal, oldVal) => {
|
||||
changePropsVal(newVal)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
|
||||
function changePropsVal(newVal){
|
||||
let list = props.list.filter(r => r.value == newVal)
|
||||
vdata.checkedData = list.length > 0 ? list[0] : { }
|
||||
}
|
||||
|
||||
|
||||
const vdata = reactive({
|
||||
|
||||
checkedData: { } // 当前选择的值
|
||||
|
||||
})
|
||||
|
||||
function show(){
|
||||
popupRef.value.open(vdata.checkedData.value)
|
||||
}
|
||||
|
||||
|
||||
// 选择用户类型完毕
|
||||
function confirmFunc(v){
|
||||
vdata.checkedData = v
|
||||
emits("update:value", v.value)
|
||||
if(v.value!==1){
|
||||
emits("change", v.value)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function hasVal(){
|
||||
return vdata.checkedData && vdata.checkedData.label ? true : false
|
||||
}
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
|
||||
.selected-radio {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
font-size: 32rpx;
|
||||
color: #b3b3b3;
|
||||
image {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
134
components/JeepayRed/JeepayRedBizinfoSelect.vue
Normal file
134
components/JeepayRed/JeepayRedBizinfoSelect.vue
Normal file
@@ -0,0 +1,134 @@
|
||||
<!--
|
||||
Jeepay 门店选择 / 应用选择
|
||||
|
||||
@author terrfly
|
||||
@site https://www.jeequan.com
|
||||
@date 2022/12/06 12:55
|
||||
-->
|
||||
<template>
|
||||
|
||||
<!-- 选择门店 -->
|
||||
<JeepayPopupListSelect
|
||||
ref="jeepayRedPopupListSelectByBizinfos"
|
||||
:reqTableDataFunc="reqTableDataByBizsFunc"
|
||||
:searchInputName="getSearchInputName()"
|
||||
:fields="getField()"
|
||||
/>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
import { reqLoad, API_URL_MCH_STORE_LIST, API_URL_MCH_APP_LIST } from '@/http/apiManager.js'
|
||||
|
||||
|
||||
// 定义组件参数
|
||||
const props = defineProps({
|
||||
configMode: { type: String, default: ''}, // 搜索时仅展示商户号
|
||||
// 是否多选框, 默认单选。
|
||||
isCheckbox: { type: Boolean, default: false },
|
||||
|
||||
// 自动关闭, 点击确定, 自动关闭
|
||||
autoClose: { type: Boolean, default: true },
|
||||
|
||||
// 业务类型: 默认门店
|
||||
bizType: { type: String, default: 'store' },
|
||||
|
||||
// 是否支持选择 【 全部门店 、 全部应用 】
|
||||
isShowAllBiz: { type: Boolean, default: false },
|
||||
// 特殊参数处理
|
||||
params:{type:Object,default:()=>({})}
|
||||
|
||||
})
|
||||
|
||||
|
||||
const jeepayRedPopupListSelectByBizinfos = ref()
|
||||
|
||||
|
||||
function getField(){
|
||||
|
||||
if(props.bizType == 'store'){
|
||||
return { key: 'storeId', left: 'storeName', right: 'storeId' }
|
||||
}
|
||||
|
||||
if(props.bizType == 'mchApp'){
|
||||
return { key: 'appId', left: 'appName', right: 'appId' }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getSearchInputName(){
|
||||
if(props.bizType == 'store'){
|
||||
return "storeName"
|
||||
}
|
||||
|
||||
if(props.bizType == 'mchApp'){
|
||||
return "appName"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function reqTableDataByBizsFunc(params) {
|
||||
Object.assign(params,props.params)
|
||||
console.log(params);
|
||||
|
||||
let apiResult = null;
|
||||
|
||||
if(props.bizType == 'store'){
|
||||
apiResult = reqLoad.list(API_URL_MCH_STORE_LIST, params)
|
||||
}
|
||||
|
||||
if(props.bizType == 'mchApp'){
|
||||
apiResult = reqLoad.list(API_URL_MCH_APP_LIST, params)
|
||||
}
|
||||
|
||||
return apiResult.then((apiRes) => {
|
||||
return processApiResBizData(params, apiRes)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// 选择门店
|
||||
function open (defaultVal){
|
||||
console.log(defaultVal,'defaultValdefaultVal')
|
||||
return jeepayRedPopupListSelectByBizinfos.value.open(defaultVal).then((selected) => {
|
||||
|
||||
// 自动关闭
|
||||
if(props.autoClose){
|
||||
close()
|
||||
}
|
||||
|
||||
return selected;
|
||||
})
|
||||
}
|
||||
|
||||
function close(){
|
||||
jeepayRedPopupListSelectByBizinfos.value.close() //自行关闭
|
||||
}
|
||||
|
||||
|
||||
function processApiResBizData(params, apiRes){
|
||||
|
||||
// 第一页 拼接全部门店 (index = 0 )
|
||||
if(params.pageNumber == 1 && props.isShowAllBiz){
|
||||
|
||||
if(props.bizType == 'store'){
|
||||
apiRes.bizData.records.unshift({storeName: '全部门店', storeId: ''})
|
||||
}
|
||||
|
||||
if(props.bizType == 'mchApp'){
|
||||
apiRes.bizData.records.unshift({appName: '全部应用', appId: ''})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return apiRes
|
||||
}
|
||||
|
||||
|
||||
// 将表格事件暴露出去 https://www.jianshu.com/p/39d14c25c987
|
||||
defineExpose({ open, close })
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
355
components/JeepayRed/JeepayRedPopupListSelect.vue
Normal file
355
components/JeepayRed/JeepayRedPopupListSelect.vue
Normal file
@@ -0,0 +1,355 @@
|
||||
<!--
|
||||
组件作用: 弹层, 列表支持 单选, 多选。
|
||||
|
||||
使用方法:
|
||||
|
||||
<JeepayPopupListSelect ref="jeepayPopupListSelect" :reqTableDataFunc="reqTableDataFunc" :fields="{ key: 'articleId', left: 'articleId', right: 'title'}"/>
|
||||
jeepayPopupListSelect.value.open().then((selected) => {
|
||||
console.log(selected);
|
||||
jeepayPopupListSelect.value.close() //自行关闭
|
||||
})
|
||||
|
||||
@author terrfly
|
||||
@site https://www.jeequan.com
|
||||
@date 2022/11/26 16:24
|
||||
-->
|
||||
<template>
|
||||
<uni-popup ref="popupRef" type="bottom" @maskClick="close" @change="change" mask-background-color="rgba(0,0,0,.5)" :safe-area="false">
|
||||
<view class="card-wrapper">
|
||||
<view class="selected-title" v-if="title">
|
||||
{{ title }}
|
||||
</view>
|
||||
<view v-if="props.searchInputName" class="input-search">
|
||||
<uni-easyinput
|
||||
v-model="vdata.searchData[props.searchInputName]"
|
||||
prefixIcon="search"
|
||||
:inputBorder="false"
|
||||
:clearable="false"
|
||||
:styles="{
|
||||
backgroundColor: 'transparent',
|
||||
}"
|
||||
type="text"
|
||||
placeholder="搜索"
|
||||
placeholderStyle=" color: rgba(0,0,0,0.85);font-size: 30rpx;"
|
||||
/>
|
||||
<view class="search-button" @tap="searchFunc">搜索</view>
|
||||
</view>
|
||||
<!-- 数据渲染 -->
|
||||
<JTableList ref="jeepayTableListRef" :reqTableDataFunc="reqTableDataFuncWrapper" :searchData="vdata.searchData" :initData="false" height='406rpx'>
|
||||
<template #tableBody="{ record }">
|
||||
<view class="store" @tap="selectFunc(record)">
|
||||
<template v-if="isCheckbox">
|
||||
<view class="more-selected" :class="{ 'more-disabled': record.hasDirector, 'more-active': hasSelected(record) }">
|
||||
<image :src="record.hasDirector ? '/static/iconImg/icon-disable.svg' : '/static/iconImg/icon-check.svg'" mode="scaleToFill" />
|
||||
</view>
|
||||
</template>
|
||||
<view v-else :class="{ 'store-dot-def': true, 'active-dot': hasSelected(record) }"></view>
|
||||
<slot name="content" :record="record">
|
||||
<view class="store-inner-slot">
|
||||
<view class="left">
|
||||
<text>{{ record[props.fields.left] }}</text>
|
||||
</view>
|
||||
<view class="right">{{ record[props.fields.right] }}</view>
|
||||
</view>
|
||||
</slot>
|
||||
</view>
|
||||
</template>
|
||||
</JTableList>
|
||||
|
||||
<!-- <button v-if="vdata.hasNext" @tap="jeepayTableListRef.addNext()">下一页</button> -->
|
||||
|
||||
<view class="footer-wrapper">
|
||||
<view class="footer-main">
|
||||
<view class="footer-button">
|
||||
<view class="flex-center" hover-class="touch-button" @tap="close">取消</view>
|
||||
<view @tap="confirmFunc" class="confirm flex-center" hover-class="touch-button">确认</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { inject, nextTick, reactive, ref, render, watch } from 'vue'
|
||||
|
||||
const jeepayTableListRef = ref()
|
||||
|
||||
// 定义组件参数
|
||||
const props = defineProps({
|
||||
// 请求业务数据, 参数可自行控制。
|
||||
reqTableDataFunc: { type: Function, default: () => {} },
|
||||
|
||||
// 搜索输入框的name , 不传入则不显示搜索。
|
||||
searchInputName: { type: String },
|
||||
|
||||
// 约定的字段 , 若不合适请通过插槽自行插入。
|
||||
fields: { type: Object, default: { key: 'id', left: 'left', right: 'right' } },
|
||||
|
||||
// 是否多选框, 默认单选。
|
||||
isCheckbox: { type: Boolean, default: false },
|
||||
// 标题有则显示无则隐藏
|
||||
title: [String, Number],
|
||||
})
|
||||
let changePageMetaOverflowFunc = inject('changePageMetaOverflowFunc')
|
||||
const emits = defineEmits(['confirm'])
|
||||
|
||||
const popupRef = ref()
|
||||
|
||||
const vdata = reactive({
|
||||
searchData: {}, //当前页的搜索条件
|
||||
|
||||
hasNext: false, // 是否包含下一页数据
|
||||
|
||||
selectedList: [], // 选择的值
|
||||
|
||||
promiseObject: {},
|
||||
})
|
||||
|
||||
// 点击搜索
|
||||
function searchFunc() {
|
||||
jeepayTableListRef.value.refTable(true)
|
||||
}
|
||||
|
||||
/** reqTableDataFunc 处理函数 */
|
||||
function reqTableDataFuncWrapper(params) {
|
||||
return props.reqTableDataFunc(params).then(({ bizData }) => {
|
||||
vdata.hasNext = bizData.hasNext // 是否包含下一页
|
||||
return Promise.resolve({ bizData })
|
||||
})
|
||||
}
|
||||
|
||||
// 是否选中
|
||||
function hasSelected(record) {
|
||||
return vdata.selectedList.filter((r) => r[props.fields.key] == record[props.fields.key]).length > 0
|
||||
}
|
||||
|
||||
/** 选择函数 **/
|
||||
function selectFunc(record) {
|
||||
// 判断是否已存在
|
||||
if (hasSelected(record)) {
|
||||
// 多选需删除
|
||||
if (props.isCheckbox) {
|
||||
vdata.selectedList.splice(
|
||||
vdata.selectedList.findIndex((r) => r[props.fields.key] == record[props.fields.key]),
|
||||
1
|
||||
)
|
||||
}
|
||||
|
||||
// 单选无需操作
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// 多选直接添加
|
||||
if (props.isCheckbox) {
|
||||
if (record.hasDirector) return //如果被其他店长绑定 禁止选中
|
||||
return vdata.selectedList.push(record)
|
||||
} else {
|
||||
// 单选
|
||||
|
||||
return (vdata.selectedList = [record])
|
||||
}
|
||||
}
|
||||
|
||||
// 点击确定事件
|
||||
function confirmFunc() {
|
||||
// 多选
|
||||
if (props.isCheckbox) {
|
||||
emits('confirm', vdata.selectedList)
|
||||
return vdata.promiseObject.resolve(vdata.selectedList)
|
||||
}
|
||||
|
||||
// 单选 仅返回第一个即可。
|
||||
emits('confirm', vdata.selectedList.length > 0 ? vdata.selectedList[0] : null)
|
||||
vdata.promiseObject.resolve(vdata.selectedList.length > 0 ? vdata.selectedList[0] : null)
|
||||
}
|
||||
|
||||
// 显示弹层, 并且返回一个promise
|
||||
// promise.then(selected)
|
||||
// 注意: 此Promise 只会回调一次, 如需要验证是否选中正确, 需要使用@confirm事件。
|
||||
// 如果对拿到的值不做校验,获取到然后关闭。 那么可以直接使用.then() 然后立马关闭弹层。
|
||||
function open(defaultVal) {
|
||||
// 清空数据
|
||||
vdata.selectedList = []
|
||||
vdata.searchData[props.searchInputName] = ''
|
||||
|
||||
// 默认选中。
|
||||
if (defaultVal) {
|
||||
if (Array.isArray(defaultVal)) {
|
||||
vdata.selectedList = defaultVal
|
||||
} else {
|
||||
vdata.selectedList = [defaultVal]
|
||||
}
|
||||
}
|
||||
|
||||
popupRef.value.open()
|
||||
|
||||
nextTick(() => {
|
||||
jeepayTableListRef.value.refTable(true)
|
||||
uni.hideTabBar() // 可能报错, 在nextTick中, 不影响其他业务。
|
||||
})
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
vdata.promiseObject.resolve = resolve
|
||||
vdata.promiseObject.reject = reject
|
||||
})
|
||||
}
|
||||
|
||||
// 关闭弹层
|
||||
function close() {
|
||||
popupRef.value.close()
|
||||
uni.showTabBar()
|
||||
}
|
||||
const change = (e) => {
|
||||
if (changePageMetaOverflowFunc) {
|
||||
changePageMetaOverflowFunc(!e.show)
|
||||
}
|
||||
}
|
||||
// 将表格事件暴露出去 https://www.jianshu.com/p/39d14c25c987
|
||||
defineExpose({ open, close })
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.card-wrapper {
|
||||
border-radius: 32rpx 32rpx 0 0;
|
||||
background-color: #fff;
|
||||
overflow: hidden;
|
||||
min-height: 70vh;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
.selected-title {
|
||||
height: 110rpx;
|
||||
line-height: 110rpx;
|
||||
text-align: center;
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
border-bottom: 1rpx solid #ededed;
|
||||
}
|
||||
.input-search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 10rpx;
|
||||
margin: 30rpx;
|
||||
border-radius: 10rpx;
|
||||
background-color: #f7f7f7;
|
||||
.search-button {
|
||||
padding: 24rpx 30rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #2980fd;
|
||||
}
|
||||
}
|
||||
.store {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
padding: 0 40rpx;
|
||||
height: 120rpx;
|
||||
font-size: 30rpx;
|
||||
.more-selected {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
border-radius: 6rpx;
|
||||
margin-right: 20rpx;
|
||||
border: 2rpx solid rgba(217, 217, 217, 1);
|
||||
image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.more-disabled {
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
.more-active {
|
||||
background-color: #2980fd;
|
||||
}
|
||||
.store-dot-def {
|
||||
position: relative;
|
||||
margin-right: 20rpx;
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
background-color: #d7d8d9;
|
||||
border-radius: 50%;
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 50%;
|
||||
height: 50%;
|
||||
border-radius: 50%;
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
.active-dot {
|
||||
background-color: #2980fd;
|
||||
}
|
||||
|
||||
.store-inner-slot {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.right {
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
}
|
||||
.all-store::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 40rpx;
|
||||
right: 40rpx;
|
||||
height: 1rpx;
|
||||
background-color: #ededed;
|
||||
}
|
||||
.footer-wrapper {
|
||||
height: 186rpx;
|
||||
.footer-main {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: env(safe-area-inset-bottom);
|
||||
border-top: 1rpx solid #ededed;
|
||||
.tips {
|
||||
margin: 20rpx;
|
||||
text-align: center;
|
||||
font-size: 27rpx;
|
||||
color: #a6a6a6;
|
||||
}
|
||||
.footer-button {
|
||||
padding: 0 30rpx;
|
||||
margin-top: 30rpx;
|
||||
padding-bottom: 30rpx;
|
||||
display: flex;
|
||||
|
||||
justify-content: space-between;
|
||||
view {
|
||||
width: 330rpx;
|
||||
height: 110rpx;
|
||||
font-size: 33rpx;
|
||||
font-weight: 500;
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
border-radius: 20rpx;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
.confirm {
|
||||
color: #fff;
|
||||
background: $jeepay-bg-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
74
components/JeepaySearchSelect/JeepaySearchSelect.vue
Normal file
74
components/JeepaySearchSelect/JeepaySearchSelect.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<!--
|
||||
组件作用: 状态筛选器, 一般用作搜索栏右侧。
|
||||
|
||||
使用方法:
|
||||
|
||||
|
||||
@author terrfly
|
||||
@site https://www.jeequan.com
|
||||
@date 2022/11/26 16:24
|
||||
-->
|
||||
|
||||
<template>
|
||||
|
||||
<view>
|
||||
<view class="code-state" @tap="statePopup.open(props.bizType)">
|
||||
{{ props.list.find(i => {
|
||||
return i.value == props.bizType
|
||||
}).label }}
|
||||
<image src="/pageDevice/static/devIconImg/icon-arrow-down.svg" mode="scaleToFill" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view>
|
||||
<JSinglePopup :list="props.list" :title="props.title" ref="statePopup" @confirm="confirmState" />
|
||||
</view>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
|
||||
|
||||
const emits = defineEmits(['update:bizType', 'change'])
|
||||
|
||||
// 定义组件参数
|
||||
const props = defineProps({
|
||||
|
||||
// 双向绑定
|
||||
bizType: { type: [Number, String] },
|
||||
// 搜索数据
|
||||
list: { type: Array },
|
||||
title: { type: String }
|
||||
})
|
||||
|
||||
const vdata = reactive({
|
||||
selected: {} // 当前选择对象
|
||||
})
|
||||
|
||||
const statePopup = ref(null)
|
||||
|
||||
//按状态筛选
|
||||
function confirmState(r){
|
||||
vdata.selected = r || { }
|
||||
emits('update:bizType', vdata.selected.value)
|
||||
emits('change', vdata.selected)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.code-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 40rpx;
|
||||
font-size: 30rpx;
|
||||
color: #222425;
|
||||
image {
|
||||
margin-left: 10rpx;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
79
components/JeepayStateSelect/JeepayStateSelect.vue
Normal file
79
components/JeepayStateSelect/JeepayStateSelect.vue
Normal file
@@ -0,0 +1,79 @@
|
||||
<!--
|
||||
组件作用: 状态筛选器, 一般用作搜索栏右侧。
|
||||
|
||||
使用方法:
|
||||
|
||||
|
||||
@author terrfly
|
||||
@site https://www.jeequan.com
|
||||
@date 2022/11/26 16:24
|
||||
-->
|
||||
|
||||
<template>
|
||||
|
||||
<view>
|
||||
<view class="code-state" @tap="statePopup.open(props.state)">
|
||||
{{ vdata.selected.label || '全部状态' }}
|
||||
<image src="/pageDevice/static/devIconImg/icon-arrow-down.svg" mode="scaleToFill" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view>
|
||||
<JSinglePopup :list="stateList" title="按设备状态筛选" ref="statePopup" @confirm="confirmState" />
|
||||
</view>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
|
||||
|
||||
const emits = defineEmits(['update:state', 'change'])
|
||||
|
||||
// 定义组件参数
|
||||
const props = defineProps({
|
||||
|
||||
// 双向绑定
|
||||
state: { type: [Number, String] },
|
||||
|
||||
})
|
||||
|
||||
const vdata = reactive({
|
||||
selected: {} , // 当前选择对象
|
||||
})
|
||||
|
||||
|
||||
const statePopup = ref(null)
|
||||
|
||||
|
||||
const stateList = reactive([
|
||||
{ label: '全部状态', value: '' },
|
||||
{ label: '启用', value: '1' },
|
||||
{ label: '禁用', value: '0' },
|
||||
])
|
||||
|
||||
|
||||
//按状态筛选
|
||||
function confirmState(r){
|
||||
vdata.selected = r || { }
|
||||
emits('update:state', vdata.selected.value)
|
||||
emits('change', vdata.selected.value)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.code-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 40rpx;
|
||||
font-size: 30rpx;
|
||||
color: #222425;
|
||||
image {
|
||||
margin-left: 10rpx;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
119
components/JeepayStateSwitch/JeepayStateSwitch.vue
Normal file
119
components/JeepayStateSwitch/JeepayStateSwitch.vue
Normal file
@@ -0,0 +1,119 @@
|
||||
<!--
|
||||
Jeepay 通用状态切换按钮, 支持switch和badge两个格式, 根据权限进行判断
|
||||
参考 jeepay-ui组件 。
|
||||
@author terrfly
|
||||
@site https://www.jeepay.vip
|
||||
@date 2021/5/8 07:18
|
||||
-->
|
||||
<template>
|
||||
<view>
|
||||
<template v-if="props.showSwitchType" >
|
||||
<switch v-if="vdata.isShowSwitchFlag" :checked="vdata.switchChecked" color="#238FFC" :style="{ transform: 'scale(' + scale + ')', margin: margin }" @change="changeFunc" />
|
||||
</template>
|
||||
<template v-else>
|
||||
|
||||
<image v-if="vdata.switchChecked == 1" class="default-image" src="/pageDevice/static/devIconImg/icon-default.svg" mode="scaleToFill" />
|
||||
<image v-if="vdata.switchChecked == 0" class="default-image" src="/pageDevice/static/devIconImg/icon-noDefault.svg" mode="scaleToFill" />
|
||||
</template>
|
||||
</view>
|
||||
<JeepayPopupConfirm ref="jeepayPopupConfirmRef" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, nextTick, watch, onMounted } from "vue"
|
||||
const props = defineProps({
|
||||
|
||||
// 样式参数
|
||||
scale: { type: Number, default: 0.8 }, //控制开关大小 倍数 默认.8
|
||||
margin: { type: String, default: "0" }, // 控制开关外边距默认0
|
||||
|
||||
showSwitchType: { type: Boolean, default: false }, // 默认 badge
|
||||
|
||||
//开关状态, 0-关闭, 1-开启
|
||||
state: { type: [Number,String], default: 1 },
|
||||
|
||||
// 是否显示二次确认
|
||||
confirm: { type: Boolean, default: true },
|
||||
|
||||
confirmTitle: { type: String, default: '确定修改状态?' }, // 二次确认提示信息
|
||||
|
||||
// updateStateFunc回调事件. 需返回promise
|
||||
updateStateFunc: { type: Function },
|
||||
|
||||
})
|
||||
|
||||
const jeepayPopupConfirmRef = ref() //提示弹窗
|
||||
const emits = defineEmits(["update:state"])
|
||||
|
||||
onMounted(()=>{
|
||||
vdata.switchChecked = props.state == 1
|
||||
})
|
||||
|
||||
const vdata = reactive({
|
||||
|
||||
isShowSwitchFlag: true , // 用于重新加载组件
|
||||
|
||||
switchChecked: true, // 是否选中
|
||||
})
|
||||
|
||||
// 监听 props属性
|
||||
watch(() => props.state, function(o, n){
|
||||
vdata.switchChecked = props.state == 1
|
||||
}
|
||||
)
|
||||
|
||||
function changeFunc(e){
|
||||
|
||||
let changeVal = e.detail.value
|
||||
|
||||
// 显示弹层
|
||||
if(props.confirm){
|
||||
jeepayPopupConfirmRef.value.open(props.confirmTitle).then(() => {
|
||||
return propsUpdateStateFunc(changeVal ? 1 : 0)
|
||||
}).then(() => {
|
||||
emits("update:state", changeVal ? 1 : 0)
|
||||
reloadSwitch(changeVal)
|
||||
}).catch(() => {
|
||||
reloadSwitch(!changeVal)
|
||||
})
|
||||
|
||||
}else{ // 调起更新函数
|
||||
|
||||
propsUpdateStateFunc(changeVal ? 1 : 0).then(() => {
|
||||
emits("update:state", changeVal ? 1 : 0)
|
||||
reloadSwitch(changeVal)
|
||||
}).catch(() => {
|
||||
reloadSwitch(!changeVal)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// uniapp-switch 组件存在问题, 当用户出发了切换, 那么v-model:checked 绑定的元素不在生效了。
|
||||
function reloadSwitch(changeVal){
|
||||
|
||||
vdata.switchChecked = changeVal
|
||||
vdata.isShowSwitchFlag = false
|
||||
nextTick(() => vdata.isShowSwitchFlag = true)
|
||||
}
|
||||
|
||||
// props. default app 和小程序有出入,此函数用作兼容。
|
||||
// APP default : () => { return (state) => {Promie.resole()} } (小层序认为 default即函数, )
|
||||
function propsUpdateStateFunc(state){
|
||||
|
||||
if(props.updateStateFunc){
|
||||
return props.updateStateFunc(state)
|
||||
}
|
||||
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.default-image {
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
}
|
||||
|
||||
</style>
|
||||
119
components/JeepayTableList/JeepayTableList.vue
Normal file
119
components/JeepayTableList/JeepayTableList.vue
Normal file
@@ -0,0 +1,119 @@
|
||||
<!--
|
||||
Jeepay 表格列表, 支持 下滑, 上滑刷新。
|
||||
|
||||
业务页面最好也监听下 触底函数, 否则H5无法监听到。
|
||||
import { onReachBottom } from '@dcloudio/uni-app'
|
||||
onReachBottom(() => { })
|
||||
|
||||
|
||||
@author terrfly
|
||||
@site https://www.jeequan.com
|
||||
@date 2022/11/16 15:55
|
||||
-->
|
||||
<template>
|
||||
<view>
|
||||
<template v-for="(record, i) in vdata.allData" :key="i">
|
||||
<slot name="tableBody" :record="record" :index="i" />
|
||||
</template>
|
||||
</view>
|
||||
<view class="list-null" v-if="!vdata.apiResData.hasNext && showListNull">暂无更多数据</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app';
|
||||
|
||||
// 定义传入属性
|
||||
const props = defineProps({
|
||||
reqTableDataFunc: { type: Function, default: () => {} },
|
||||
searchData: { type: Object, default: () => {} }, // 搜索条件参数
|
||||
pageSize: { type: Number, default: 10 }, // 默认每页条数
|
||||
initData: { type: Boolean, default: true }, // 初始化列表数据, 默认true
|
||||
showListNull: { type: Boolean, default: true } //是否显示缺省 默认显示
|
||||
});
|
||||
|
||||
const vdata = reactive({
|
||||
allData: [], // app与web不同, app是每次查询到数据会拼接到后面
|
||||
|
||||
// 接口返回的数据
|
||||
apiResData: { total: 0, records: [] },
|
||||
|
||||
// 分页参数
|
||||
iPage: { pageNumber: 1, pageSize: props.pageSize }
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
//初始化表数据
|
||||
props.initData ? refTable(true) : undefined;
|
||||
});
|
||||
|
||||
// 查询表格数据
|
||||
function refTable(isToFirst = false) {
|
||||
if (isToFirst) {
|
||||
// 重新搜索, 第一页。
|
||||
vdata.iPage.pageNumber = 1;
|
||||
vdata.allData = []; //清空数据
|
||||
}
|
||||
|
||||
// 更新检索数据
|
||||
return props.reqTableDataFunc(Object.assign({}, vdata.iPage, props.searchData)).then(({ bizData }) => {
|
||||
Object.assign(vdata.apiResData, bizData); // 列表数据更新
|
||||
if (bizData.records) {
|
||||
vdata.allData.push(...bizData.records); // 利用展开语法代替forEach
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** 追加下一页数据 **/
|
||||
function addNext() {
|
||||
// 包含下一页
|
||||
if (vdata.apiResData.hasNext) {
|
||||
vdata.iPage.pageNumber++;
|
||||
refTable(false);
|
||||
}
|
||||
}
|
||||
|
||||
// 下拉刷新
|
||||
onPullDownRefresh(() => {
|
||||
refTable(true).then(() => {
|
||||
uni.stopPullDownRefresh();
|
||||
});
|
||||
});
|
||||
|
||||
// 监听,触底事件。 查询下一页
|
||||
onReachBottom(() => {
|
||||
addNext();
|
||||
});
|
||||
|
||||
// 将表格事件暴露出去 https://www.jianshu.com/p/39d14c25c987
|
||||
defineExpose({ refTable, addNext });
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.list-null {
|
||||
position: relative;
|
||||
|
||||
line-height: 110rpx;
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
color: #a6a6a6;
|
||||
&::after,
|
||||
&::before {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
|
||||
width: 30%;
|
||||
height: 2rpx;
|
||||
background-color: #ededed;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
&::after {
|
||||
left: 40rpx;
|
||||
}
|
||||
&::before {
|
||||
right: 40rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
90
components/JeepayTableListItem/JeepayTableListItem.vue
Normal file
90
components/JeepayTableListItem/JeepayTableListItem.vue
Normal file
@@ -0,0 +1,90 @@
|
||||
<!--
|
||||
Jeepay 通用列表条目, 包含 头像, 主标题, 副标题
|
||||
|
||||
@author terrfly
|
||||
@site https://www.jeequan.com
|
||||
@date 2022/11/16 15:55
|
||||
-->
|
||||
<template>
|
||||
<view :class="`list-item ${props.viewClass}`">
|
||||
<image :style="props.logoStyle" v-if="props.logo" :src="props.logo" mode="scaleToFill" />
|
||||
<view class="list-info">
|
||||
<view class="list-title">
|
||||
<view class="list-name">
|
||||
<slot name="title">{{ props.title }} </slot>
|
||||
</view>
|
||||
<slot name="titleRight">
|
||||
|
||||
<!-- 直接写 typeof 页面报错。 -->
|
||||
<template v-if="isShowState()" >
|
||||
<view v-if="props.state == 1" class="state-dot state-dot-enable"></view>
|
||||
<view v-else class="state-dot state-dot-disable"></view>
|
||||
</template>
|
||||
|
||||
<template v-if="navListComputed">
|
||||
<image style="width: 70rpx; height: 70rpx" src="/pageDevice/static/devIconImg/icon-more-white.svg" mode="scaleToFill" @tap="single.open()" />
|
||||
</template>
|
||||
</slot>
|
||||
</view>
|
||||
<view class="list-subtitle"><slot name="subtitle">{{ props.subtitle }} </slot></view>
|
||||
</view>
|
||||
<JSinglePopup ref="single" :list="navListComputed" activeColor="#FF5B4C" />
|
||||
</view>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import {ref, reactive, onMounted, computed } from 'vue'
|
||||
import ak from '@/commons/utils/ak.js'
|
||||
|
||||
// 弹层
|
||||
const single = ref()
|
||||
|
||||
// 定义传入属性
|
||||
const props = defineProps({
|
||||
|
||||
title: { type: [String, Number] }, // 标题
|
||||
|
||||
subtitle: { type: [String, Number] }, // 副标题
|
||||
|
||||
logo: { type: String }, // 图标
|
||||
|
||||
logoStyle: { type: Object } , // logo颜色背景图
|
||||
|
||||
moreBtnList: { type: Array }, //更多按钮
|
||||
|
||||
// 状态开启(蓝色),or 关闭(置灰) 【 注意:state 和 moreBtnList 二选一, 或者请使用插槽覆写 titleRight 】
|
||||
state: { type: [Number, String] },
|
||||
|
||||
viewClass: { type: String, default: '' }, // 样式透传, 小程序不支持再组件上加class(不生效), 需要特殊定义,特殊传入。
|
||||
|
||||
})
|
||||
|
||||
function isShowState(){
|
||||
return typeof(props.state) != 'undefined'
|
||||
}
|
||||
|
||||
|
||||
// 计算属性
|
||||
let navListComputed = computed(() => {
|
||||
|
||||
if(!props.moreBtnList){
|
||||
return props.moreBtnList
|
||||
}
|
||||
return props.moreBtnList.filter(r => hasEnt(r.entId))
|
||||
})
|
||||
|
||||
function hasEnt(entId){
|
||||
|
||||
// 不包含: 说明无需隐藏
|
||||
if(!entId){
|
||||
return true
|
||||
}
|
||||
return ak.ent.has(entId)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
56
components/JeepayTag/JeepayTag.vue
Normal file
56
components/JeepayTag/JeepayTag.vue
Normal 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>
|
||||
167
components/JeepayUploadImg/JeepayUploadImg.vue
Normal file
167
components/JeepayUploadImg/JeepayUploadImg.vue
Normal file
@@ -0,0 +1,167 @@
|
||||
<!--
|
||||
图片上传组件
|
||||
@author terrfly
|
||||
@site https://www.jeequan.com
|
||||
@date 2022/11/30 18:18
|
||||
-->
|
||||
<template>
|
||||
<view style="flex-grow: 1">
|
||||
<!-- 图片内 带 x 号的模式。 -->
|
||||
<template v-if="props.mode == 'img'">
|
||||
<!-- 包含图片 -->
|
||||
<template v-if="props.src">
|
||||
<view class="image-wrapper">
|
||||
<image v-if="!props.readonly" @tap="delImg" class="del-image" src="/static/iconImg/icon-x-white.svg" mode="scaleToFill" />
|
||||
<image class="default-img" :src="props.src" @tap="preview"></image>
|
||||
</view>
|
||||
</template>
|
||||
<template v-else>
|
||||
<!-- 不包含图片 -->
|
||||
<view @tap="chooseImageAndUpload" style="flex-grow: 1; display: flex; justify-content: space-between; align-items: center">
|
||||
<image
|
||||
v-if="!props.readonly"
|
||||
style="width: 150rpx; height: 150rpx; background-color: #f7f7f7; border-radius: 15rpx"
|
||||
src="/static/iconImg/default-img.svg"
|
||||
mode="scaleToFill"
|
||||
/>
|
||||
<image src="/pageDevice/static/devIconImg/icon-arrow-sex.svg" mode="scaleToFill" style="width: 120rpx; height: 120rpx" />
|
||||
</view>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<!-- 页面图片 加入 切换按钮 -->
|
||||
<template v-if="props.mode == 'viewbtn'">
|
||||
<image class="default-img" :src="props.src" @tap="preview" mode="aspectFill"></image>
|
||||
|
||||
<!-- 图片预览(手写非原生) -->
|
||||
<enlarge v-if="vdata.showEnlarge" :imgs="props.src" :changeIsShow="!props.readonly" @chooseImg="chooseImageAndUpload" @enlargeClose="vdata.showEnlarge = false" />
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import http from '@/http/http.js';
|
||||
import infoBox from '@/commons/utils/infoBox.js';
|
||||
import enlarge from './enlarge.vue'; // 图片预览
|
||||
import { API_URL_SINGLE_FILE_UPLOAD, $ossFilesForm } from '@/http/apiManager.js';
|
||||
|
||||
// emit 父组件使用: v-model:src="val" 进行双向绑定。
|
||||
const emit = defineEmits(['update:src', 'change']);
|
||||
|
||||
// 定义 父组件传参
|
||||
const props = defineProps({
|
||||
src: { type: String, default: '' }, // 双向绑定 文件地址
|
||||
bizType: { type: String, default: '' }, // 业务类型
|
||||
imgSize: { type: Number, default: 5 }, // 上传图片大小限制 默认 5 M
|
||||
|
||||
// 两种模式: img - 图片包含删除按钮支持删除, viewbtn-图片内支预览按钮切换。
|
||||
mode: { type: String, default: 'img' },
|
||||
|
||||
// 预览模式, 不支持切换图片。
|
||||
readonly: { type: Boolean, default: false }
|
||||
});
|
||||
|
||||
// 定义响应式数据
|
||||
const vdata = reactive({
|
||||
showEnlarge: false,
|
||||
|
||||
action: '', // 文件form表单请求地址
|
||||
|
||||
uploadForm: {
|
||||
action: '', // 请求地址
|
||||
header: {}, // 请求头
|
||||
params: {} // 参数
|
||||
}
|
||||
});
|
||||
|
||||
// 预览图片
|
||||
function preview() {
|
||||
if (props.mode == 'img') {
|
||||
// 原生图片预览
|
||||
|
||||
uni.previewImage({ urls: [props.src] });
|
||||
} else {
|
||||
// 组件模式
|
||||
|
||||
vdata.showEnlarge = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 删除图片
|
||||
function delImg() {
|
||||
emit('update:src', '');
|
||||
emit('change', '');
|
||||
}
|
||||
|
||||
// 选择图片 and 上传
|
||||
function chooseImageAndUpload() {
|
||||
// 最多选择一张图片 && 压缩图片 && 支持相册 和 相机
|
||||
uni.chooseImage({ count: 1, sizeType: ['compressed'], sourceType: ['album', 'camera'] }).then((res) => {
|
||||
let file = res.tempFiles[0];
|
||||
|
||||
// 预先检查
|
||||
if (!beforeCheck(file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查通过
|
||||
|
||||
$ossFilesForm(props.bizType, file).then(({ bizData }) => {
|
||||
// 本地方式
|
||||
if (bizData.formActionUrl === 'LOCAL_SINGLE_FILE_URL') {
|
||||
return http.upload(API_URL_SINGLE_FILE_UPLOAD, { bizType: props.bizType }, file).then(({ bizData }) => {
|
||||
emit('update:src', bizData);
|
||||
emit('change', bizData);
|
||||
});
|
||||
}
|
||||
|
||||
// oss 直传
|
||||
uni.uploadFile({ url: bizData.formActionUrl, filePath: file.path, name: 'file', formData: bizData.formParams }).then((ossRes) => {
|
||||
if (ossRes.statusCode == 200) {
|
||||
// 上传成功
|
||||
emit('update:src', bizData.ossFileUrl);
|
||||
emit('change', bizData.ossFileUrl);
|
||||
return false;
|
||||
}
|
||||
|
||||
infoBox.showToast('oss服务响应异常');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function beforeCheck(file) {
|
||||
return true;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
preview
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.default-img {
|
||||
display: block;
|
||||
width: 150rpx;
|
||||
height: 150rpx;
|
||||
background-color: #f7f7f7;
|
||||
border-radius: 15rpx;
|
||||
}
|
||||
.image-wrapper {
|
||||
position: relative;
|
||||
width: 150rpx;
|
||||
height: 150rpx;
|
||||
margin-bottom: 20upx;
|
||||
.del-image {
|
||||
position: absolute;
|
||||
top: -20rpx;
|
||||
right: -20rpx;
|
||||
z-index: 10;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border-radius: 50%;
|
||||
background-color: tomato;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
143
components/JeepayUploadImg/enlarge.vue
Normal file
143
components/JeepayUploadImg/enlarge.vue
Normal file
@@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<view class="previewImage" :style="{ 'background-color': 'rgba(0,0,0,' + opacity + ')' }" @tap.stop="close">
|
||||
<movable-area class="marea" scale-area>
|
||||
<movable-view
|
||||
:id="'movable-view-' + i"
|
||||
:key="'movable-view-' + i"
|
||||
class="mview"
|
||||
direction="all"
|
||||
:out-of-bounds="false"
|
||||
:inertia="true"
|
||||
damping="90"
|
||||
friction="2"
|
||||
scale="true"
|
||||
scale-min="1"
|
||||
scale-max="4"
|
||||
:scale-value="scale"
|
||||
>
|
||||
<image
|
||||
:id="'image-' + i"
|
||||
:key="'movable-view' + i"
|
||||
class="image"
|
||||
:src="imgs"
|
||||
:data-index="i"
|
||||
:data-src="img"
|
||||
mode="widthFix"
|
||||
/>
|
||||
</movable-view>
|
||||
</movable-area>
|
||||
<view v-if="changeIsShow" class="change-img" @click="chooseImg">更换图片</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "ksj-previewImage", //插件名称
|
||||
props: {
|
||||
imgs: {
|
||||
//图片列表
|
||||
type: String,
|
||||
required: true,
|
||||
default: "",
|
||||
},
|
||||
//透明度,0到1之间。
|
||||
opacity: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
changeIsShow: { type: Boolean, default: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
swiper: false, //是否禁用
|
||||
show: false, //显示状态
|
||||
index: 0, //当前页
|
||||
deg: 0, //旋转角度
|
||||
time: 0, //定时器
|
||||
interval: 1000, //长按事件
|
||||
scale: 1, //缩放比例
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
chooseImg() {
|
||||
this.$emit("chooseImg")
|
||||
},
|
||||
|
||||
//旋转
|
||||
rotate(e) {
|
||||
this.deg = this.deg == 270 ? 0 : this.deg + 90
|
||||
},
|
||||
close() {
|
||||
this.$emit("enlargeClose")
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<!--使用scss,只在本组件生效-->
|
||||
<style lang="scss" scoped>
|
||||
.previewImage {
|
||||
z-index: 25;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 999;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #000000;
|
||||
user-select: none;
|
||||
.marea {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
overflow: hidden;
|
||||
.mview {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
min-height: 100%;
|
||||
.image {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rotate {
|
||||
position: absolute;
|
||||
right: 10rpx;
|
||||
width: 120rpx;
|
||||
height: 56rpx;
|
||||
bottom: 10rpx;
|
||||
text-align: center;
|
||||
padding: 10rpx;
|
||||
.text {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
color: #fff;
|
||||
font-size: 30rpx;
|
||||
border-radius: 20rpx;
|
||||
border: 1rpx solid #f1f1f1;
|
||||
padding: 6rpx 22rpx;
|
||||
user-select: none;
|
||||
}
|
||||
.text:active {
|
||||
background-color: rgba(100, 100, 100, 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
.change-img {
|
||||
position: fixed;
|
||||
width: 300rpx;
|
||||
bottom: 5%;
|
||||
left: 50%;
|
||||
margin-left: -150rpx;
|
||||
text-align: center;
|
||||
z-index: 30;
|
||||
color: #fff;
|
||||
padding: 30rpx;
|
||||
box-sizing: border-box;
|
||||
background-color: #0041c4;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
</style>
|
||||
135
components/helang-pickerColor/helang-pickerColor.vue
Normal file
135
components/helang-pickerColor/helang-pickerColor.vue
Normal 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>
|
||||
212
components/my-components/edit-discount.vue
Normal file
212
components/my-components/edit-discount.vue
Normal file
@@ -0,0 +1,212 @@
|
||||
<template>
|
||||
<my-model ref="model" :title="title" iconColor="#000" @close="resetForm">
|
||||
<template #desc>
|
||||
<view class="u-text-left u-p-30 color-666">
|
||||
<view class="u-m-t-32 u-flex ">
|
||||
<view>应付金额</view>
|
||||
<view class="u-m-l-32">
|
||||
{{form.price}}
|
||||
</view>
|
||||
</view>
|
||||
<view class="u-m-t-40 u-flex ">
|
||||
<view>实收金额</view>
|
||||
<view class="u-m-l-32 border u-p-l-10 u-p-r-10 u-flex-1">
|
||||
<uni-easyinput type="number" @input="currentPriceInput" @change="currentPriceChange" paddingNone :inputBorder="false"
|
||||
v-model="form.currentPrice"
|
||||
placeholder="输入实际金额"></uni-easyinput>
|
||||
</view>
|
||||
</view>
|
||||
<view class="u-m-t-54 u-flex ">
|
||||
<view>优惠折扣</view>
|
||||
<view class="u-m-l-32 u-flex-1 u-flex border u-p-l-10 u-p-r-10">
|
||||
<view class="u-flex-1">
|
||||
<uni-easyinput type="number" @input="discountInput" @change="discountChange" paddingNone :inputBorder="false"
|
||||
v-model="form.discount"
|
||||
placeholder="输入折扣"></uni-easyinput>
|
||||
</view>
|
||||
<view class="u-font-32 color-333">%</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<template #btn>
|
||||
<view class="u-p-30">
|
||||
<view class="u-m-t-10">
|
||||
<my-button @tap="confirm" shape="circle" fontWeight="700" >修改</my-button>
|
||||
<view class="">
|
||||
<my-button @tap="close" type="cancel" bgColor="#fff" >取消</my-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</my-model>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
reactive,
|
||||
nextTick,
|
||||
ref,watch
|
||||
} from 'vue';
|
||||
import myModel from '@/components/my-components/my-model.vue'
|
||||
import myButton from '@/components/my-components/my-button.vue'
|
||||
import myTabs from '@/components/my-components/my-tabs.vue'
|
||||
import infoBox from '@/commons/utils/infoBox.js'
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
data: {
|
||||
type: Array,
|
||||
default: []
|
||||
},
|
||||
discount:{
|
||||
type: [Number,String],
|
||||
default:100
|
||||
},
|
||||
price: {
|
||||
type: [Number,String],
|
||||
default: 0
|
||||
},
|
||||
nowPrice:{
|
||||
type: [Number,String],
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
function currentPriceInput(newval){
|
||||
form.discount=(newval*100/form.price)
|
||||
}
|
||||
function discountInput(newval){
|
||||
form.currentPrice=(form.price*newval/100).toFixed(2)
|
||||
}
|
||||
function currentPriceChange(newval){
|
||||
if(newval<0){
|
||||
form.currentPrice=0
|
||||
form.discount=100
|
||||
return infoBox.showToast('实收金额不能小于0')
|
||||
}
|
||||
if(newval>props.price){
|
||||
form.currentPrice=props.price
|
||||
form.discount=0
|
||||
return infoBox.showToast('实收金额不能大于应付金额')
|
||||
}
|
||||
}
|
||||
function discountChange(newval){
|
||||
if(newval<0){
|
||||
form.currentPrice=props.price
|
||||
form.discount=0
|
||||
return infoBox.showToast('优惠折扣不能小于0')
|
||||
}
|
||||
if(newval>100){
|
||||
form.discount=100
|
||||
form.currentPrice=0
|
||||
return infoBox.showToast('优惠折扣不能大于100')
|
||||
}
|
||||
}
|
||||
|
||||
const $form = {
|
||||
price:props.price,
|
||||
currentPrice: props.price,
|
||||
discount: 100
|
||||
}
|
||||
const form = reactive({
|
||||
...$form
|
||||
})
|
||||
watch(()=>props.price,(newval)=>{
|
||||
console.log(newval);
|
||||
form.price=newval
|
||||
form.currentPrice=newval
|
||||
})
|
||||
function resetForm() {
|
||||
Object.assign(form, {
|
||||
...$form
|
||||
})
|
||||
}
|
||||
|
||||
const model = ref(null)
|
||||
|
||||
function open() {
|
||||
model.value.open()
|
||||
form.price=props.price
|
||||
form.discount=props.discount
|
||||
form.currentPrice=(props.discount*props.price/100).toFixed(2)
|
||||
}
|
||||
|
||||
function close() {
|
||||
model.value.close()
|
||||
}
|
||||
const emits = defineEmits(['confirm'])
|
||||
|
||||
function confirm() {
|
||||
console.log(form);
|
||||
emits('confirm',{...form,currentPrice:Number(form.currentPrice).toFixed(2)})
|
||||
close()
|
||||
}
|
||||
defineExpose({
|
||||
open,
|
||||
close
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.border{
|
||||
border-radius: 8rpx;
|
||||
overflow: hidden;
|
||||
border-color: #999;
|
||||
}
|
||||
.lh34 {
|
||||
line-height: 34rpx;
|
||||
}
|
||||
|
||||
.tag {
|
||||
background-color: #fff;
|
||||
border: 1px solid #E5E5E5;
|
||||
line-height: inherit;
|
||||
font-size: 24rpx;
|
||||
color: #666666;
|
||||
padding: 6rpx 20rpx;
|
||||
border-radius: 8rpx;
|
||||
|
||||
&.active {
|
||||
border-color: #E6F0FF;
|
||||
color: $my-main-color;
|
||||
}
|
||||
}
|
||||
|
||||
.hover-class {
|
||||
background-color: #E5E5E5;
|
||||
}
|
||||
|
||||
.discount {
|
||||
.u-absolute {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.bg1 {
|
||||
background: #F7F7FA;
|
||||
}
|
||||
|
||||
.tab {
|
||||
padding: 0 80rpx;
|
||||
}
|
||||
|
||||
.border {
|
||||
border: 1px solid #E5E5E5;
|
||||
border-radius: 4rpx;
|
||||
}
|
||||
|
||||
.input-box {
|
||||
padding: 22rpx 32rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.placeholder-class {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
</style>
|
||||
107
components/my-components/my-action-sheet.vue
Normal file
107
components/my-components/my-action-sheet.vue
Normal file
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<view class="action-sheet" @tap="close" v-if="show">
|
||||
<view class="box">
|
||||
<slot name="title">
|
||||
</slot>
|
||||
<view class="item" @tap.stop="itemClick(index)" v-for="(item,index) in props.list" :key="index">
|
||||
<button class="bg-fff btn" hover-class="btn-hover-class" :class="{'color-main':active==index}">{{item}}</button>
|
||||
</view>
|
||||
<view class="bock-gary"></view>
|
||||
<view class="cancel-btn" @tap="close">
|
||||
<button>取消</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
const props=defineProps({
|
||||
//那个按钮文字高亮
|
||||
active:{
|
||||
type:Number,
|
||||
default:-1
|
||||
},
|
||||
autoClose:{
|
||||
type:Boolean,
|
||||
default:true
|
||||
},
|
||||
title:{
|
||||
type:String,
|
||||
default:''
|
||||
},
|
||||
list:{
|
||||
type:Array,
|
||||
default:['编辑','删除']
|
||||
},
|
||||
show:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
}
|
||||
})
|
||||
const emits=defineEmits(['itemClick','close'])
|
||||
let show=ref(false)
|
||||
function open(){
|
||||
show.value=true
|
||||
}
|
||||
function close(){
|
||||
show.value=false
|
||||
emits('close')
|
||||
}
|
||||
function itemClick(index){
|
||||
if(props.autoClose){
|
||||
close()
|
||||
}
|
||||
emits('itemClick',index)
|
||||
}
|
||||
defineExpose({
|
||||
open,close
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
$bg:rgb(240, 240, 240);
|
||||
.action-sheet{
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
background-color: rgba(51,51,51,.5);
|
||||
z-index: 999;
|
||||
.box{
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border-radius: 20rpx 20rpx 0rpx 0rpx;
|
||||
overflow: hidden;
|
||||
background-color: #fff;
|
||||
/* #ifndef H5 */
|
||||
padding-bottom: calc(24rpx + constant(safe-area-inset-bottom));
|
||||
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
|
||||
/* #endif */
|
||||
|
||||
.item{
|
||||
text-align: center;
|
||||
font-size: 32rpx;
|
||||
border-bottom: 1px solid $bg;
|
||||
.btn{
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.bock-gary{
|
||||
background-color: $bg;
|
||||
height: 20rpx;
|
||||
}
|
||||
.cancel-btn{
|
||||
text-align: center;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
.btn-hover-class{
|
||||
background-color: rgb(222, 222, 222);
|
||||
}
|
||||
</style>
|
||||
158
components/my-components/my-button.vue
Normal file
158
components/my-components/my-button.vue
Normal file
@@ -0,0 +1,158 @@
|
||||
<template>
|
||||
<view class="">
|
||||
<button class="btn" hover-class="btn-hover-class"
|
||||
@tap="tap"
|
||||
@click="click"
|
||||
:disabled="disabled"
|
||||
:style="computeStyle()"
|
||||
:class="[returnShape,returnType,returnShadow,returnDisabled]"
|
||||
>
|
||||
<view class="u-flex u-row-center u-col-center auto-center">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</button>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
const props=defineProps({
|
||||
disabled:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
},
|
||||
plain:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
},
|
||||
borderRadius:{
|
||||
type:String,
|
||||
default:''
|
||||
},
|
||||
bgColor:{
|
||||
type:String,
|
||||
default:''
|
||||
},
|
||||
color:{
|
||||
type:String
|
||||
},
|
||||
shape:{
|
||||
//circle square
|
||||
type:String,
|
||||
default:'square'
|
||||
},
|
||||
type:{
|
||||
type:String,
|
||||
default:'primary'
|
||||
},
|
||||
width:{
|
||||
type:[String,Number],
|
||||
default:''
|
||||
},
|
||||
height:{
|
||||
type:[String,Number],
|
||||
default:'80'
|
||||
},
|
||||
fontSize:{
|
||||
type:[String,Number],
|
||||
default:'28'
|
||||
},
|
||||
fontWeight:{
|
||||
type:[String,Number],
|
||||
default:'500'
|
||||
},
|
||||
showShadow:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
}
|
||||
})
|
||||
const emits=defineEmits(['click','tap'])
|
||||
function tap(){
|
||||
emits('tap')
|
||||
}
|
||||
function click(){
|
||||
emits('click')
|
||||
}
|
||||
const shapeClassList={circle:'circle',square:'square'}
|
||||
const typeClassList={primary:'primary',default:'default',cancel:'cancel'}
|
||||
const returnShape= computed(()=>{
|
||||
return shapeClassList[props.shape]
|
||||
})
|
||||
const returnShadow=computed(()=>{
|
||||
return props.showShadow?'shadow':''
|
||||
})
|
||||
const returnDisabled=computed(()=>{
|
||||
return props.disabled?'disabled':''
|
||||
})
|
||||
const returnType=computed(()=>{
|
||||
if(props.plain){
|
||||
return 'plain-'+typeClassList[props.type]+' '+' plain'
|
||||
}
|
||||
return typeClassList[props.type]
|
||||
})
|
||||
function computeStyle(){
|
||||
return `
|
||||
line-height:${props.height}rpx;
|
||||
${props.width>=0?('width:'+props.width+'rpx;'):''}
|
||||
${props.plain?('background-color:transparent;'):''}
|
||||
font-size:${props.fontSize}rpx;
|
||||
font-weight:${props.fontWeight};
|
||||
${props.color?('color:'+props.color+';'):''}
|
||||
${props.bgColor?('background-color:'+props.bgColor+';'):''}
|
||||
${props.bgColor?('border-color:'+props.bgColor+';'):''}
|
||||
${props.borderRadius?('border-radius:'+props.borderRadius+';'):''}
|
||||
`
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.auto-center{
|
||||
margin: auto;
|
||||
}
|
||||
.shadow{
|
||||
// box-shadow: 0 0 10px #aaa;
|
||||
box-shadow: 0 20rpx 60rpx -20rpx rgba(0,84,210,0.5);
|
||||
}
|
||||
.btn {
|
||||
font-size: 28rpx;
|
||||
border:1px solid transparent ;
|
||||
&.disabled{
|
||||
color: #bbb!important;
|
||||
border-color: #eee!important;
|
||||
}
|
||||
}
|
||||
.plain{
|
||||
background-color: transparent;
|
||||
}
|
||||
.plain-primary{
|
||||
color: $my-main-color;
|
||||
border-color: $my-main-color;
|
||||
}
|
||||
.plain-default{
|
||||
color: #999;
|
||||
border-color: #999;
|
||||
}
|
||||
.plain-cancel{
|
||||
color: #999;
|
||||
border-color: #999;
|
||||
}
|
||||
.primary{
|
||||
background-color: $my-main-color;
|
||||
color: #fff;
|
||||
border-color: $my-main-color;
|
||||
}
|
||||
.default{
|
||||
background-color: transparent;
|
||||
color: #999;
|
||||
border-color: #999;
|
||||
}
|
||||
.circle{
|
||||
border-radius: 200rpx;
|
||||
}
|
||||
.square{
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
.btn-hover-class {
|
||||
opacity: .6;
|
||||
}
|
||||
</style>
|
||||
605
components/my-components/my-date-pickerview.vue
Normal file
605
components/my-components/my-date-pickerview.vue
Normal file
@@ -0,0 +1,605 @@
|
||||
<template>
|
||||
<view class="mask" v-if="show" @tap="close">
|
||||
<view class="box" @tap.stop="nullFunction">
|
||||
<view class="u-flex u-relative u-row-center u-p-30 top" v-if="props.isArea">
|
||||
<view class="font-bold u-font-32">{{props.title}}</view>
|
||||
<view class="close" @tap="close">
|
||||
<uni-icons type="closeempty" size="24"></uni-icons>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
<view class="u-p-30 u-flex u-flex-wrap gap-20 fastTime" v-if="props.isArea">
|
||||
<view class="item" v-for="(item,index) in fastTime" :key="index" @tap="changeTime(item.key)">
|
||||
{{item.title}}
|
||||
</view>
|
||||
</view>
|
||||
<picker-view :immediate-change="true" @pickend="pickend" :value="value" @change="bindChange"
|
||||
class="picker-view">
|
||||
<template v-if="props.mode==='all'||props.mode==='date'">
|
||||
<picker-view-column>
|
||||
<view class="item" v-for="(item,index) in years" :key="index">{{item}}年</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column>
|
||||
<view class="item" v-for="(item,index) in months" :key="index">{{item}}月</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column>
|
||||
<view class="item" v-for="(item,index) in days" :key="index">{{item}}日</view>
|
||||
</picker-view-column>
|
||||
</template>
|
||||
<template v-if="props.mode==='all'||props.mode==='time'">
|
||||
<picker-view-column>
|
||||
<view class="item" v-for="(item,index) in hours" :key="index">{{item}}时</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column>
|
||||
<view class="item" v-for="(item,index) in minutes" :key="index">{{item}}分</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column>
|
||||
<view class="item" v-for="(item,index) in seconds" :key="index">{{item}}秒</view>
|
||||
</picker-view-column>
|
||||
</template>
|
||||
</picker-view>
|
||||
<template v-if="props.isArea">
|
||||
<view class="u-text-center color-999">至</view>
|
||||
<picker-view :immediate-change="true" :value="value1" @pickend="pickend1" @change="bindChange1"
|
||||
class="picker-view">
|
||||
<template v-if="props.mode==='all'||props.mode==='date'">
|
||||
<picker-view-column>
|
||||
<view class="item" v-for="(item,index) in years" :key="index">{{item}}年</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column>
|
||||
<view class="item" v-for="(item,index) in months" :key="index">{{item}}月</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column>
|
||||
<view class="item" v-for="(item,index) in days1" :key="index">{{item}}日</view>
|
||||
</picker-view-column>
|
||||
</template>
|
||||
<template v-if="props.mode==='all'||props.mode==='time'">
|
||||
<picker-view-column>
|
||||
<view class="item" v-for="(item,index) in hours" :key="index">{{item}}时</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column>
|
||||
<view class="item" v-for="(item,index) in minutes" :key="index">{{item}}分</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column>
|
||||
<view class="item" v-for="(item,index) in seconds" :key="index">{{item}}秒</view>
|
||||
</picker-view-column>
|
||||
</template>
|
||||
|
||||
</picker-view>
|
||||
</template>
|
||||
|
||||
|
||||
<!-- 占位 -->
|
||||
<view style="height: 80px;"></view>
|
||||
<template v-if="props.isArea">
|
||||
<view class="fixed_b" >
|
||||
<my-button shape="circle" @tap="confirm">确定</my-button>
|
||||
</view>
|
||||
</template>
|
||||
<template v-else>
|
||||
<view class="fixed_b u-flex u-row-center">
|
||||
<view class="u-m-r-16">
|
||||
<my-button type="cancel" @tap="close" width="240">
|
||||
<view class="color-999">取消</view>
|
||||
</my-button>
|
||||
</view>
|
||||
<view class="u-m-l-16">
|
||||
<my-button @tap="confirm" width="240">确定</my-button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import myButton from "@/components/my-components/my-button.vue"
|
||||
import {
|
||||
reactive,
|
||||
nextTick,
|
||||
ref
|
||||
} from 'vue';
|
||||
const props = defineProps({
|
||||
selTime:{
|
||||
type: [String,Number],
|
||||
},
|
||||
defaultIndex: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
},
|
||||
defaultTime: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '筛选日期时间'
|
||||
},
|
||||
isArea: {
|
||||
//是否选中范围时间
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
mode: {
|
||||
//all date time
|
||||
type: String,
|
||||
default: 'time'
|
||||
},
|
||||
yearsLen:{
|
||||
type: Number,
|
||||
default:30
|
||||
}
|
||||
})
|
||||
const $nowDate = new Date()
|
||||
const nowDate = {
|
||||
year: $nowDate.getFullYear(),
|
||||
month: $nowDate.getMonth() + 1,
|
||||
day: $nowDate.getDate(),
|
||||
hours: $nowDate.getHours(),
|
||||
minutes: $nowDate.getMinutes(),
|
||||
seconds: $nowDate.getSeconds()
|
||||
}
|
||||
const yearsLen = props.yearsLen
|
||||
|
||||
function returnYears() {
|
||||
if(props.isArea){
|
||||
return new Array(yearsLen).fill(1).map((v, index) => {
|
||||
return nowDate.year - index
|
||||
}).reverse()
|
||||
}else{
|
||||
return new Array(yearsLen).fill(1).map((v, index) => {
|
||||
return nowDate.year-Math.floor(yearsLen/2) + index
|
||||
})
|
||||
}
|
||||
}
|
||||
const years = returnYears()
|
||||
|
||||
const months = new Array(12).fill(1).map((v, index) => {
|
||||
return index + 1
|
||||
})
|
||||
const days = ref(new Array(getMonthArea($nowDate, 'end').getDate()).fill(1).map((v, index) => {
|
||||
return index + 1
|
||||
}))
|
||||
const days1 = ref(new Array(getMonthArea($nowDate, 'end').getDate()).fill(1).map((v, index) => {
|
||||
return index + 1
|
||||
}))
|
||||
const hours = new Array(24).fill(1).map((v, index) => {
|
||||
return index
|
||||
})
|
||||
const minutes = new Array(60).fill(1).map((v, index) => {
|
||||
return index
|
||||
})
|
||||
const seconds = new Array(60).fill(1).map((v, index) => {
|
||||
return index
|
||||
})
|
||||
const fastTime = reactive([{
|
||||
title: '今日',
|
||||
key: 'now'
|
||||
},
|
||||
{
|
||||
title: '昨日',
|
||||
key: 'prve'
|
||||
},
|
||||
{
|
||||
title: '本月',
|
||||
key: 'nowMonth'
|
||||
},
|
||||
{
|
||||
title: '上月',
|
||||
key: 'prveMonth'
|
||||
}
|
||||
])
|
||||
|
||||
|
||||
|
||||
function setPrveDay() {
|
||||
|
||||
}
|
||||
|
||||
function setNowMoneth() {
|
||||
|
||||
}
|
||||
|
||||
function setprveMoneth() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
function setDay(start, end) {
|
||||
value.value = [
|
||||
start.year,
|
||||
start.month,
|
||||
start.day,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
]
|
||||
value1.value = [
|
||||
end.year,
|
||||
end.month,
|
||||
end.day,
|
||||
23,
|
||||
59,
|
||||
59,
|
||||
]
|
||||
}
|
||||
|
||||
function changeTime(key) {
|
||||
const yearIndex = years.findIndex(v => v == nowDate.year)
|
||||
const prveyearIndex = years.findIndex(v => v == nowDate.year) - 1
|
||||
const nowMonthIndex = nowDate.month - 1
|
||||
const nowDayIndex = nowDate.day - 1
|
||||
const dataMap = {
|
||||
now: function() {
|
||||
return {
|
||||
start: {
|
||||
year: yearIndex,
|
||||
month: nowMonthIndex,
|
||||
day: nowDayIndex
|
||||
},
|
||||
end: {
|
||||
year: yearIndex,
|
||||
month: nowMonthIndex,
|
||||
day: nowDayIndex
|
||||
}
|
||||
}
|
||||
},
|
||||
prve: function() {
|
||||
const oneDay = 1000 * 60 * 60 * 24
|
||||
const date = new Date(new Date(nowDate.year, nowDate.month, nowDate.day, 0, 0, 0).getTime() -
|
||||
oneDay)
|
||||
return {
|
||||
start: {
|
||||
year: years.findIndex(v => v == date.getFullYear()),
|
||||
month: date.getMonth() - 1 < 0 ? 11 : date.getMonth() - 1,
|
||||
day: date.getDate() - 1
|
||||
},
|
||||
end: {
|
||||
year: years.findIndex(v => v == date.getFullYear()),
|
||||
month: date.getMonth() - 1 < 0 ? 11 : date.getMonth() - 1,
|
||||
day: date.getDate() - 1
|
||||
}
|
||||
}
|
||||
},
|
||||
nowMonth: function() {
|
||||
return {
|
||||
start: {
|
||||
year: yearIndex,
|
||||
month: nowMonthIndex,
|
||||
day: 0
|
||||
},
|
||||
end: {
|
||||
year: yearIndex,
|
||||
month: nowMonthIndex,
|
||||
day: new Date(nowDate.year, nowDate.month, 0).getDate() - 1
|
||||
}
|
||||
}
|
||||
},
|
||||
prveMonth: function() {
|
||||
const oneDay = 1000 * 60 * 60 * 24
|
||||
const date = new Date(new Date(nowDate.year, nowDate.month - 1, 0, 0, 0).getTime() - oneDay)
|
||||
console.log(date.getMonth());
|
||||
return {
|
||||
start: {
|
||||
year: years.findIndex(v => v == date.getFullYear()),
|
||||
month: date.getMonth(),
|
||||
day: 0
|
||||
},
|
||||
end: {
|
||||
year: years.findIndex(v => v == date.getFullYear()),
|
||||
month: date.getMonth(),
|
||||
day: date.getDate()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const data = dataMap[key]()
|
||||
setDay(data.start, data.end)
|
||||
changeDays(false, value.value)
|
||||
changeDays(true, value1.value)
|
||||
|
||||
console.log(value1.value);
|
||||
const start = returnDateString(value.value)
|
||||
const end = returnDateString(value1.value)
|
||||
|
||||
emits('confirm', {
|
||||
text: `${start}——${end}`,
|
||||
start,
|
||||
end
|
||||
})
|
||||
close()
|
||||
}
|
||||
let value = ref([])
|
||||
let value1 = ref([])
|
||||
initValue()
|
||||
|
||||
|
||||
function returnFindIndex(arr) {
|
||||
const yearIndex = years.findIndex(v => v == arr[0])
|
||||
const monthIndex = arr[1]
|
||||
const dayIndex = arr[2] - 1
|
||||
const hIndex = arr[3]
|
||||
const mIndex = arr[4]
|
||||
const sIndex = arr[5]
|
||||
return [
|
||||
yearIndex, monthIndex, dayIndex, hIndex, mIndex, sIndex
|
||||
]
|
||||
}
|
||||
|
||||
function initValue() {
|
||||
if (props.defaultTime.length && !props.isArea) {
|
||||
value.value = returnFindIndex(props.defaultTime)
|
||||
} else {
|
||||
const yearIndex = years.findIndex(v => v == nowDate.year)
|
||||
value.value = [
|
||||
yearIndex,
|
||||
nowDate.month - 1,
|
||||
nowDate.day - 1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
]
|
||||
value1.value = [
|
||||
yearIndex,
|
||||
nowDate.month - 1,
|
||||
nowDate.day - 1,
|
||||
23,
|
||||
59,
|
||||
59,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
let show = ref(false)
|
||||
const emits = defineEmits('close', 'open', 'confirm')
|
||||
|
||||
function toggle() {
|
||||
show.value = !show.value
|
||||
if (show.value) {
|
||||
open()
|
||||
} else {
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
function close() {
|
||||
show.value = false
|
||||
// emits('close', false)
|
||||
}
|
||||
|
||||
|
||||
function open() {
|
||||
if(typeof props.selTime==='number'){
|
||||
const d=new Date(props.selTime)
|
||||
value.value[0]=years.findIndex(v=>v===d.getFullYear())
|
||||
value.value[1]=months.findIndex(v=>v===d.getMonth())+1
|
||||
value.value[2]=days.value.findIndex(v=>v===d.getDate())
|
||||
}
|
||||
show.value = true
|
||||
// emits('open', true)
|
||||
}
|
||||
|
||||
function returnDateString(arr, isObj) {
|
||||
const year = years[arr[0]]
|
||||
const month = arr[1] + 1
|
||||
const day = arr[2] + 1
|
||||
const hour = ('0' + (arr[3]||0)).slice(-2)
|
||||
console.log(hour)
|
||||
const min = ('0' + (arr[4]||0)).slice(-2)
|
||||
const sen = ('0' + (arr[5]||0)).slice(-2)
|
||||
if (isObj) {
|
||||
return new Date(year, month, day, hour, min, sen)
|
||||
}
|
||||
return `${year}-${month}-${day} ${hour}:${min}:${sen}`
|
||||
}
|
||||
|
||||
|
||||
function confirm(e) {
|
||||
console.log(value.value);
|
||||
const start = returnDateString(value.value)
|
||||
console.log(start);
|
||||
const end = returnDateString(value1.value)
|
||||
if (!props.isArea) {
|
||||
emits('confirm', start)
|
||||
} else {
|
||||
emits('confirm', {
|
||||
text: `${start}——${end}`,
|
||||
start,
|
||||
end
|
||||
})
|
||||
}
|
||||
|
||||
close()
|
||||
}
|
||||
|
||||
function returnMonthStart(arr) {
|
||||
return new Date(years[arr[0]], months[arr[1]] - 1, 1).getDate();
|
||||
}
|
||||
|
||||
function returnMonthEnd(arr) {
|
||||
return new Date(years[arr[0]], months[arr[1]], 0).getDate();
|
||||
}
|
||||
|
||||
//防抖
|
||||
function debounce(fn, wait) {
|
||||
let timeout = null;
|
||||
return function() {
|
||||
let context = this;
|
||||
let args = arguments;
|
||||
if (timeout) clearTimeout(timeout);
|
||||
let callNow = !timeout;
|
||||
timeout = setTimeout(() => {
|
||||
timeout = null;
|
||||
}, wait);
|
||||
if (callNow) fn.apply(context, args);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} isDays1 //是否是结束时间选择
|
||||
* @param {Object} arr
|
||||
*/
|
||||
function changeDays(isDays1, arr) {
|
||||
const end = returnMonthEnd(arr)
|
||||
if (end) {
|
||||
if (isDays1) {
|
||||
days1.value = new Array(end).fill(1).map((v,
|
||||
index) => {
|
||||
return index + 1
|
||||
})
|
||||
} else {
|
||||
days.value = new Array(end).fill(1).map((v,
|
||||
index) => {
|
||||
return index + 1
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function bindChange(e) {
|
||||
const startTotal = returnDateString(e.detail.value, true).getTime()
|
||||
const endTotal = returnDateString(value1.value, true).getTime()
|
||||
value.value = e.detail.value
|
||||
setTimeout(()=>{
|
||||
if (props.isArea) {
|
||||
value.value = startTotal > endTotal ? value1.value : e.detail.value
|
||||
}
|
||||
debounce(changeDays(false, value.value), 100)
|
||||
},10)
|
||||
// nextTick(() => {
|
||||
// if (props.isArea) {
|
||||
// value.value = startTotal > endTotal ? value1.value : e.detail.value
|
||||
// }
|
||||
// console.log(value.value);
|
||||
// debounce(changeDays(false, value.value), 100)
|
||||
// })
|
||||
}
|
||||
|
||||
function bindChange1(e) {
|
||||
const startTotal = returnDateString(value.value, true).getTime()
|
||||
const endTotal = returnDateString(e.detail.value, true).getTime()
|
||||
value1.value = e.detail.value
|
||||
nextTick(() => {
|
||||
if (props.isArea) {
|
||||
value1.value = endTotal < startTotal ? value.value : e.detail.value
|
||||
}
|
||||
debounce(changeDays(true, value1.value), 100)
|
||||
})
|
||||
}
|
||||
|
||||
function getDayDate(date = new Date(), type) {
|
||||
const now = date
|
||||
if (type === 'start') {
|
||||
const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||
return startOfDay
|
||||
}
|
||||
if (type === 'end') {
|
||||
const endOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999);
|
||||
return endOfDay;
|
||||
}
|
||||
}
|
||||
|
||||
function getMonthArea(date = new Date(), type) {
|
||||
let now = date
|
||||
let currentMonthStart = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
let currentMonthEnd = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59, 999);
|
||||
if (type === 'start') {
|
||||
return currentMonthStart
|
||||
}
|
||||
if (type === 'end') {
|
||||
return currentMonthEnd;
|
||||
}
|
||||
return {
|
||||
start: currentMonthStart,
|
||||
end: currentMonthEnd
|
||||
};
|
||||
}
|
||||
|
||||
function nullFunction() {
|
||||
|
||||
}
|
||||
|
||||
function pickend(e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
function pickend1(e) {
|
||||
console.log(e);
|
||||
}
|
||||
defineExpose({
|
||||
close,
|
||||
open,
|
||||
confirm,
|
||||
toggle
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.fastTime {
|
||||
.item {
|
||||
background-color: rgb(247, 247, 247);
|
||||
padding: 6rpx 40rpx;
|
||||
border-radius: 6rpx;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.top {
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.close {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
right: 30rpx;
|
||||
}
|
||||
|
||||
.mask {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
z-index: 999;
|
||||
background-color: rgba(51, 51, 51, .5);
|
||||
|
||||
.item {
|
||||
line-height: 34px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.box {
|
||||
position: absolute;
|
||||
background-color: #fff;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border-radius: 16rpx 16rpx 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.fixed_b {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
padding: 30rpx;
|
||||
z-index: 100;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.picker-view {
|
||||
width: 750rpx;
|
||||
height: 300rpx;
|
||||
}
|
||||
</style>
|
||||
38
components/my-components/my-empty.vue
Normal file
38
components/my-components/my-empty.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<view class="u-flex u-row-center">
|
||||
<view class="empty color-999 u-font-28 u-m-t-60 u-text-center">{{text}}</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props= defineProps({
|
||||
text:{
|
||||
type:String,
|
||||
default:'暂无记录'
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.empty{
|
||||
position: relative;
|
||||
padding: 0 16rpx;
|
||||
&:before,&::after{
|
||||
position: absolute;
|
||||
content: '';
|
||||
display: block;
|
||||
top: 50%;
|
||||
height: 1px;
|
||||
background-color: #E5E5E5;
|
||||
border-radius: 1px;
|
||||
width: 94rpx;
|
||||
}
|
||||
&:before{
|
||||
right: 100%;
|
||||
}
|
||||
&::after{
|
||||
left: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
71
components/my-components/my-icons.vue
Normal file
71
components/my-components/my-icons.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<view class="icon"
|
||||
:class="[computedClass]"
|
||||
></view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
const props=defineProps({
|
||||
type:{
|
||||
type:String
|
||||
}
|
||||
})
|
||||
const classMap={
|
||||
add:'icon-add',
|
||||
reduce:'icon-reduce'
|
||||
}
|
||||
const computedClass=computed(()=>{
|
||||
return classMap[props.type]
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
$icon-size: 34rpx;
|
||||
$icon-line-width: 20rpx;
|
||||
$icon-line-height: 4rpx;
|
||||
.icon {
|
||||
width: $icon-size;
|
||||
height: $icon-size;
|
||||
position: relative;
|
||||
border-radius: 50%;
|
||||
|
||||
&:before,
|
||||
&::after {
|
||||
position: absolute;
|
||||
display: block;
|
||||
content: '';
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-add {
|
||||
background-color: $my-main-color;
|
||||
|
||||
&::before {
|
||||
width: $icon-line-height;
|
||||
height: $icon-line-width;
|
||||
top: calc(($icon-size /2) - ($icon-line-width / 2));
|
||||
left: calc(($icon-size /2) - ($icon-line-height / 2));
|
||||
}
|
||||
|
||||
&::after {
|
||||
width: $icon-line-width;
|
||||
height: 4rpx;
|
||||
top: calc(($icon-size /2) - ($icon-line-height / 2));
|
||||
left: calc(($icon-size /2) - ($icon-line-width / 2));
|
||||
}
|
||||
}
|
||||
|
||||
.icon-reduce {
|
||||
background-color: $my-red-color;
|
||||
|
||||
&::after {
|
||||
width: $icon-line-width;
|
||||
height: $icon-line-height;
|
||||
top: calc(($icon-size /2) - ($icon-line-height / 2));
|
||||
left: calc(($icon-size /2) - ($icon-line-width / 2));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
32
components/my-components/my-img-empty.vue
Normal file
32
components/my-components/my-img-empty.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<view class="u-flex u-flex-col u-row-center u-col-center w-full">
|
||||
<slot>
|
||||
<image :src="img" mode="" class="img"></image>
|
||||
<view class="tips">{{tips}}</view>
|
||||
</slot>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props= defineProps({
|
||||
tips:{
|
||||
type:String,
|
||||
default:'暂无数据'
|
||||
},
|
||||
img:{
|
||||
type:String,
|
||||
default:'/static/icon-empty.svg'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.img{
|
||||
width: 326rpx;
|
||||
height: 336rpx;
|
||||
}
|
||||
.tips{
|
||||
margin-top: 74rpx;
|
||||
}
|
||||
</style>
|
||||
79
components/my-components/my-mask.vue
Normal file
79
components/my-components/my-mask.vue
Normal file
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<view class="mask u-fixed tranistion position-all u-flex u-flex-col u-row-center u-col-center" v-if="show"
|
||||
:style="computedStyle" @tap="maskClick">
|
||||
<view class="w-full" @tap.stop="nullFunction">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
computed,
|
||||
ref,
|
||||
watch
|
||||
} from 'vue';
|
||||
const props = defineProps({
|
||||
show:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
zIndex: {
|
||||
type: [Number, String]
|
||||
},
|
||||
tapClose: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
})
|
||||
const emits = defineEmits(['close', 'toggle', 'open'])
|
||||
watch(()=>props.show,(newval)=>{
|
||||
show.value=newval
|
||||
})
|
||||
let show = ref(props.show)
|
||||
|
||||
function close() {
|
||||
show.value = false
|
||||
emits('close')
|
||||
}
|
||||
|
||||
function open() {
|
||||
show.value = true
|
||||
emits('open')
|
||||
}
|
||||
|
||||
function nullFunction() {
|
||||
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if (show.value) {
|
||||
close()
|
||||
} else {
|
||||
open()
|
||||
}
|
||||
}
|
||||
|
||||
function maskClick() {
|
||||
if (props.tapClose) {
|
||||
close()
|
||||
}
|
||||
}
|
||||
const computedStyle = computed(() => {
|
||||
return `
|
||||
z-index:${props.zIndex};
|
||||
`
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
close,
|
||||
toggle,
|
||||
open
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.mask {
|
||||
background-color: rgba(51, 51, 51, .5);
|
||||
}
|
||||
</style>
|
||||
225
components/my-components/my-model.vue
Normal file
225
components/my-components/my-model.vue
Normal file
@@ -0,0 +1,225 @@
|
||||
<template>
|
||||
<view class="model u-text-center" v-if="show" @tap="modelTap">
|
||||
<div @tap.stop="" class="box u-font-32 tranistion" :class="[returnBoxClass]" :style="[computeBoxStyle,computeStyle()]" >
|
||||
<view class="u-relative">
|
||||
<view class=" color-333 font-bold">{{props.title||'提示'}}</view>
|
||||
<template v-if="showIcon">
|
||||
<slot name="icon">
|
||||
<view class="close" @tap="close">
|
||||
<uni-icons :size="iconSize" :color="iconColor" type="clear"></uni-icons>
|
||||
</view>
|
||||
</slot>
|
||||
</template>
|
||||
|
||||
</view>
|
||||
|
||||
<slot name="desc">
|
||||
<view class=" color-666 u-p-30">{{props.desc}}</view>
|
||||
</slot>
|
||||
<slot name="btn" v-if="showBtn">
|
||||
<view class="btns u-flex">
|
||||
<view class="cancel btn" @tap="cancel">{{props.cancelText}}</view>
|
||||
<view class="confirm btn" @tap="confirm">{{props.confirmText}}</view>
|
||||
</view>
|
||||
</slot>
|
||||
</div>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
computed,
|
||||
ref
|
||||
} from 'vue';
|
||||
|
||||
const emits = defineEmits(['cancel', 'confirm', 'close', 'open'])
|
||||
|
||||
|
||||
function isNumber(value) {
|
||||
return Number(value)==value && !isNaN(value);
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
confirmClickClose:{
|
||||
type:Boolean,
|
||||
default:true
|
||||
},
|
||||
isClickMaskHide:{
|
||||
type:Boolean,
|
||||
default:true
|
||||
},
|
||||
iconSize:{
|
||||
type: Number,
|
||||
default: 26
|
||||
},
|
||||
iconColor:{
|
||||
type: String,
|
||||
default: '#999'
|
||||
},
|
||||
mode:{
|
||||
//center bottom
|
||||
type: String,
|
||||
default: 'center'
|
||||
},
|
||||
//单位rpx
|
||||
borderRadius:{
|
||||
type:[String,Number],
|
||||
default:36
|
||||
},
|
||||
showIcon:{
|
||||
type:Boolean,
|
||||
default:true
|
||||
},
|
||||
showBtn:{
|
||||
type:Boolean,
|
||||
default:true
|
||||
},
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: '取消'
|
||||
},
|
||||
confirmText: {
|
||||
type: String,
|
||||
default: '确定'
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '提示'
|
||||
},
|
||||
desc: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
function computeStyle(){
|
||||
const bottomBorderRadius=`${props.borderRadius}rpx ${props.borderRadius}rpx 0 0`
|
||||
const centerBorderRadius=`${props.borderRadius}rpx`
|
||||
const borderRadius=isNumber(props.borderRadius)?(props.mode==='bottom'?bottomBorderRadius:centerBorderRadius):props.borderRadius
|
||||
return `
|
||||
borderRadius:${borderRadius};
|
||||
`
|
||||
}
|
||||
|
||||
const computedClass=computed(()=>{
|
||||
return show.value?'show':'none'
|
||||
})
|
||||
const returnBoxClass=computed(()=>{
|
||||
if(props.mode==='center'){
|
||||
return ''
|
||||
}
|
||||
return 'bottom-box'
|
||||
})
|
||||
const computeBoxStyle=computed(()=>{
|
||||
if(props.mode==='center'){
|
||||
return ''
|
||||
}
|
||||
})
|
||||
function getComputeClass() {
|
||||
|
||||
}
|
||||
let show = ref(false)
|
||||
|
||||
function open() {
|
||||
show.value = true
|
||||
emits('open')
|
||||
}
|
||||
|
||||
function close() {
|
||||
show.value = false
|
||||
emits('close')
|
||||
}
|
||||
function modelTap(){
|
||||
if(props.isClickMaskHide){
|
||||
close()
|
||||
}
|
||||
}
|
||||
function cancel() {
|
||||
show.value = false
|
||||
emits('cancel')
|
||||
emits('close')
|
||||
}
|
||||
|
||||
function confirm() {
|
||||
emits('confirm')
|
||||
if(props.confirmClickClose){
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
defineExpose({
|
||||
open,
|
||||
close,
|
||||
cancel,
|
||||
confirm
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.show{
|
||||
transform: scale(1);
|
||||
}
|
||||
.none{
|
||||
transform: scale(0);
|
||||
}
|
||||
.close {
|
||||
position: absolute;
|
||||
right: 30rpx;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
// background: #999999;
|
||||
// border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.model {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
background: rgba(51, 51, 51, 0.5);
|
||||
z-index: 980;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0 46rpx;
|
||||
|
||||
.box {
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
border-radius: 36rpx 36rpx 36rpx 36rpx;
|
||||
padding-top: 32rpx;
|
||||
overflow: hidden;
|
||||
.btns {
|
||||
border-top: 2rpx solid #E5E5E5;
|
||||
|
||||
.cancel {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.confirm {
|
||||
color: $my-main-color;
|
||||
}
|
||||
|
||||
&>.btn {
|
||||
padding: 30rpx;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
.bottom-box{
|
||||
position: absolute;left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border-radius: 0;
|
||||
padding-bottom: 100rpx;
|
||||
border-radius: 36rpx 36rpx 0 0;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
150
components/my-components/my-pagination.vue
Normal file
150
components/my-components/my-pagination.vue
Normal file
@@ -0,0 +1,150 @@
|
||||
<template>
|
||||
<view v-if="pagesData.length">
|
||||
<view class="u-flex pagination u-row-center u-font-28">
|
||||
<view class="prve btn" :class="{disabled:currentPage===1}" @tap="btnClick('')">上一页</view>
|
||||
<view>
|
||||
<picker @change="pageChange" :value="defaultPaageIndex" :range="pagesData">
|
||||
<view class="u-flex current-page u-col-center btn">
|
||||
<text class="page color-main font-bold">{{currentPage}}</text>
|
||||
<!-- <text class="page color-main font-bold">{{currentPage}}/{{maxPage}}</text> -->
|
||||
<view class="arrow-down"></view>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
<!-- <view class="u-flex current-page u-col-center btn">
|
||||
|
||||
<text class="page color-main font-bold">{{page}}</text>
|
||||
<view class="arrow-down"></view>
|
||||
</view> -->
|
||||
<view class="next btn" :class="{disabled:currentPage===maxPage}" @tap="btnClick('add')">下一页</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
computed,
|
||||
reactive,
|
||||
ref,
|
||||
watch
|
||||
} from 'vue';
|
||||
import infoBox from '@/commons/utils/infoBox.js'
|
||||
const props = defineProps({
|
||||
size: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
totalElements: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
page: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
pages: {
|
||||
type: [Array, Number],
|
||||
default: [1]
|
||||
},
|
||||
defaultPaageIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
const emits = defineEmits(['change','update:page'])
|
||||
|
||||
let currentPage = ref(props.page === 0 ? 1 : props.page)
|
||||
|
||||
function returnMaxPage() {
|
||||
const result=Math.ceil(props.totalElements / props.size)
|
||||
return result
|
||||
}
|
||||
let maxPage=ref(returnMaxPage())
|
||||
const pagesData = computed(() => {
|
||||
maxPage.value=returnMaxPage()
|
||||
return new Array(returnMaxPage()).fill(1).map((v, index) => index + 1)
|
||||
})
|
||||
|
||||
|
||||
// const pagesData = ref( Array.isArray(props.pages) ? props.pages : new Array(props.pages).fill(1).map((v,index)=>index+1))
|
||||
// const maxPage=ref( Array.isArray(props.pages)? (props.pages[props.pages.length - 1] +1): (props.pages+1))
|
||||
let defaultPaageIndex = ref(props.defaultPaageIndex > 0 ? props.defaultPaageIndex : props.page - 1)
|
||||
|
||||
//设置页码值
|
||||
function setCurrentPage(page) {
|
||||
currentPage.value = page
|
||||
}
|
||||
|
||||
function pageChange(e) {
|
||||
setCurrentPage(e.detail.value * 1 + 1)
|
||||
}
|
||||
//看是否到达临界值来决定是否改变页码
|
||||
function btnClick(isAdd) {
|
||||
let newPage = currentPage.value * 1 + (isAdd == 'add' ? 1 : -1)
|
||||
if (newPage <= 0) {
|
||||
return infoBox.showToast('已经是第一页了')
|
||||
}
|
||||
if (newPage > maxPage.value) {
|
||||
return infoBox.showToast('没有更多页了')
|
||||
}
|
||||
setCurrentPage(newPage)
|
||||
}
|
||||
watch(()=>props.page,(newval)=>{
|
||||
currentPage.value =newval
|
||||
})
|
||||
watch(() => currentPage.value, (newval) => {
|
||||
emits('change', newval)
|
||||
emits('update:page', newval)
|
||||
defaultPaageIndex.value = newval - 1
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.btn {
|
||||
box-sizing: border-box;
|
||||
border: 2px solid $my-main-color;
|
||||
padding: 8rpx 46rpx;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
gap: 30rpx;
|
||||
|
||||
.current-page {
|
||||
position: relative;
|
||||
padding-left: 18rpx;
|
||||
|
||||
.page {
|
||||
margin-right: 74rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.prve,
|
||||
.next {
|
||||
background-color: $my-main-color;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.prve,.next {
|
||||
&.disabled {
|
||||
// background-color: #ccc;
|
||||
// border-color: #ccc;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.arrow-down {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
right: 12rpx;
|
||||
margin-top: 10rpx;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 20rpx solid $my-main-color;
|
||||
border-right: 20rpx solid transparent;
|
||||
border-left: 20rpx solid transparent;
|
||||
border-bottom: 20rpx solid transparent;
|
||||
}
|
||||
</style>
|
||||
344
components/my-components/my-pickerview.vue
Normal file
344
components/my-components/my-pickerview.vue
Normal file
@@ -0,0 +1,344 @@
|
||||
<template>
|
||||
<view class="mask" v-if="show" @tap="close">
|
||||
<view class="box" @tap.stop="nullFunction">
|
||||
<view class="u-flex u-relative u-row-center u-p-30 top" v-if="props.showTitle">
|
||||
<view class="font-bold u-font-32">{{props.title}}</view>
|
||||
<view class="close" @tap="close">
|
||||
<uni-icons type="closeempty" size="24"></uni-icons>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
<!-- <view class="u-p-30 u-flex u-flex-wrap gap-20 fastTime">
|
||||
<view class="item" v-for="(item,index) in fastTime" :key="index" @tap="changeTime(item.key)">
|
||||
{{item.title}}
|
||||
</view>
|
||||
</view> -->
|
||||
<picker-view indicator-class="activeClass" :immediate-change="true" @pickend="pickend" :value="value"
|
||||
@change="bindChange" class="picker-view">
|
||||
<template v-if="list.length">
|
||||
<picker-view-column v-for="(arr,index) in list" :key="index">
|
||||
<view class="item" v-for="(item,itemIndex) in arr" :key="itemIndex">
|
||||
{{props.rangeKey? item[props.rangeKey]:item }}
|
||||
</view>
|
||||
</picker-view-column>
|
||||
</template>
|
||||
|
||||
<!-- <template>
|
||||
<picker-view-column v-if="props.list.length">
|
||||
<view class="item" v-for="(item,index) in props.list" :key="index">
|
||||
{{props.rangeKey? item[props.rangeKey]:item }}</view>
|
||||
</picker-view-column>
|
||||
</template> -->
|
||||
</picker-view>
|
||||
|
||||
<!-- 站位 -->
|
||||
<view style="height: 80px;"></view>
|
||||
<view class="fixed_b u-flex u-row-between">
|
||||
<my-button type="cancel" @tap="close" width="240">
|
||||
<view class="color-999">取消</view>
|
||||
</my-button>
|
||||
<my-button @tap="confirm" width="240">确定</my-button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import myButton from "@/components/my-components/my-button.vue"
|
||||
import {
|
||||
reactive,
|
||||
nextTick,
|
||||
ref
|
||||
} from 'vue';
|
||||
const props = defineProps({
|
||||
autoClear: {
|
||||
//是否自动清除选中
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
defaultIndex: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return [0]
|
||||
}
|
||||
},
|
||||
showTitle: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
list: {
|
||||
//[name:'',value:'',children:[]]
|
||||
type: Object,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
},
|
||||
rangeKey: {
|
||||
type: String
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '筛选日期时间'
|
||||
},
|
||||
isLink: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
mode: {
|
||||
//all date time
|
||||
type: String,
|
||||
default: 'all'
|
||||
},
|
||||
showSeconds:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
function nullFunction() {
|
||||
|
||||
}
|
||||
|
||||
let list = ref([])
|
||||
|
||||
function isTowLvArr(arr) {
|
||||
return arr.some(item => Array.isArray(item));
|
||||
}
|
||||
|
||||
function returnNowArr(arr, indexArr) {
|
||||
try{
|
||||
if(!arr.length){
|
||||
return []
|
||||
}
|
||||
let lv = 0
|
||||
let result = [arr[0]]
|
||||
|
||||
function returnItem(item, selIndex) {
|
||||
if (item.hasOwnProperty('children') && Array.isArray(item['children'])) {
|
||||
lv++
|
||||
result.push(item['children'])
|
||||
returnItem(item['children'][selIndex], indexArr[lv] || 0)
|
||||
}
|
||||
}
|
||||
returnItem(arr[0][indexArr[lv]], indexArr[lv])
|
||||
return result
|
||||
}catch(e){
|
||||
console.error(`请绑定指定样式数据list:[{name:'',value:'',children:[
|
||||
{name:'',value:''}
|
||||
]}]`)
|
||||
//TODO handle the exception
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
function setList() {
|
||||
if (props.mode === 'time') {
|
||||
const length=props.showSeconds?3:2
|
||||
list.value = new Array(length).fill(1).map((v, index) => {
|
||||
//时
|
||||
if (index === 0) {
|
||||
return new Array(24).fill(1).map((h, hIndex) => {
|
||||
return ('0' + hIndex).slice(-2)
|
||||
})
|
||||
}
|
||||
//分
|
||||
if (index === 1) {
|
||||
return new Array(60).fill(1).map((m, hIndex) => {
|
||||
return ('0' + hIndex).slice(-2)
|
||||
})
|
||||
}
|
||||
//秒
|
||||
if (index === 2) {
|
||||
return new Array(60).fill(1).map((s, hIndex) => {
|
||||
return ('0' + hIndex).slice(-2)
|
||||
})
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
const propArr = isTowLvArr(props.list) ? props.list : [props.list]
|
||||
const indexArr = props.defaultIndex
|
||||
const computedArr = props.isLink ? returnNowArr(propArr, indexArr) : propArr
|
||||
list.value = computedArr||[]
|
||||
}
|
||||
setList()
|
||||
|
||||
let value = ref(props.defaultIndex)
|
||||
|
||||
function resetValue() {
|
||||
value.value = value.value.map(v => 0)
|
||||
}
|
||||
|
||||
function setValue() {
|
||||
if(props.mode==='time'){
|
||||
return value.value=props.defaultIndex
|
||||
}
|
||||
const arr = new Array(list.value.length).fill(0).map((v, index) => {
|
||||
return props.defaultIndex[index] !== undefined ? props.defaultIndex[index] : v
|
||||
})
|
||||
|
||||
value.value = props.defaultIndex.length === list.value.length ? props.defaultIndex : arr
|
||||
}
|
||||
setValue()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
let show = ref(false)
|
||||
const emits = defineEmits('close', 'open', 'confirm')
|
||||
|
||||
function toggle() {
|
||||
show.value = !show.value
|
||||
if (show.value) {
|
||||
open()
|
||||
} else {
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
function close() {
|
||||
show.value = false
|
||||
if (props.autoClear) {
|
||||
resetValue()
|
||||
}
|
||||
// emits('close', false)
|
||||
}
|
||||
|
||||
function open() {
|
||||
show.value = true
|
||||
nextTick(()=>{
|
||||
setValue()
|
||||
})
|
||||
// emits('open', true)
|
||||
}
|
||||
|
||||
function getItem() {
|
||||
let result = []
|
||||
for (let i in value.value) {
|
||||
result.push(list.value[i][value.value[i]])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function confirm(e) {
|
||||
const result = getItem()
|
||||
emits('confirm', [...result])
|
||||
close()
|
||||
}
|
||||
|
||||
//防抖
|
||||
function debounce(fn, wait) {
|
||||
let timeout = null;
|
||||
return function() {
|
||||
let context = this;
|
||||
let args = arguments;
|
||||
if (timeout) clearTimeout(timeout);
|
||||
let callNow = !timeout;
|
||||
timeout = setTimeout(() => {
|
||||
timeout = null;
|
||||
}, wait);
|
||||
if (callNow) fn.apply(context, args);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function changeList() {
|
||||
if (!props.isLink) {
|
||||
return
|
||||
}
|
||||
const computedArr = returnNowArr(list.value, value.value)
|
||||
list.value = computedArr
|
||||
}
|
||||
|
||||
function bindChange(e) {
|
||||
value.value = e.detail.value
|
||||
debounce(changeList(), 100)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function pickend(e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
close,
|
||||
open,
|
||||
confirm,
|
||||
toggle,
|
||||
resetValue
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.fastTime {
|
||||
.item {
|
||||
background-color: rgb(247, 247, 247);
|
||||
padding: 6rpx 40rpx;
|
||||
border-radius: 6rpx;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.top {
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.close {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
right: 30rpx;
|
||||
}
|
||||
|
||||
.mask {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
z-index: 999;
|
||||
background-color: rgba(51, 51, 51, .5);
|
||||
|
||||
.activeClass {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.item {
|
||||
line-height: 34px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.box {
|
||||
position: absolute;
|
||||
background-color: #fff;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border-radius: 16rpx 16rpx 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.fixed_b {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
padding: 48rpx 120rpx;
|
||||
padding-bottom: calc(env(safe-area-inset-bottom) + 48rpx);
|
||||
z-index: 100;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.picker-view {
|
||||
width: 750rpx;
|
||||
height: 300rpx;
|
||||
}
|
||||
</style>
|
||||
268
components/my-components/my-popup-table.vue
Normal file
268
components/my-components/my-popup-table.vue
Normal file
@@ -0,0 +1,268 @@
|
||||
<template>
|
||||
<view class="mask u-fixed position-all u-flex u-col-bottom u-font-28" v-if="show" @tap="close">
|
||||
<view class="bg-fff w-full " @tap.stop="nullFunction">
|
||||
<view class="u-p-30">
|
||||
<view class="font-bold u-text-center">选择商品</view>
|
||||
<view class="u-m-t-32 u-flex">
|
||||
<view class=" ">
|
||||
<uni-data-picker :clear-icon="false" :map="{text:'name',value:'id'}" placeholder="请选择分类"
|
||||
popup-title="请选择分类" :localdata="category" v-model="goods.query.categoryId">
|
||||
<view class="u-flex u-font-28" >
|
||||
<text class=" u-line-1"
|
||||
style="max-width: 100rpx;">{{goods.query.categoryId||'分类' }}</text>
|
||||
<up-icon name="arrow-down" size="16"></up-icon>
|
||||
</view>
|
||||
</uni-data-picker>
|
||||
</view>
|
||||
<view class="u-flex-1 u-p-l-16">
|
||||
<up-search @custom="getGoods" v-model="goods.query.name" placeholder="请输入商品名称" @search="getGoods" @clear="getGoods"></up-search>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
<scroll-view :scroll-x="false" scroll-y="true" :style="computedStyle()">
|
||||
<view class="u-p-l-30 u-p-r-30 table">
|
||||
<view class="u-flex u-row-between no-wrap title">
|
||||
<view>
|
||||
<my-radio @change="radioAllChange" v-model="goods.allChecked" shape="square"
|
||||
:size="20"></my-radio>
|
||||
</view>
|
||||
<view>商品信息</view>
|
||||
<view>规格</view>
|
||||
<!-- <view>是否售尽</view> -->
|
||||
<!-- <view>是否分销</view> -->
|
||||
<view>售价</view>
|
||||
<view>销量/库存</view>
|
||||
<view>分类名称</view>
|
||||
</view>
|
||||
<view @click="changeChecked(item)" class="u-m-t-12 u-flex u-p-24 u-row-between row" v-for="(item,index) in goods.list" :key="index">
|
||||
<view class="">
|
||||
<my-radio @change="radioChange($event,item)" v-model="item.checked" shape="square" :size="20"></my-radio>
|
||||
</view>
|
||||
<view class="u-text-left u-flex-1 u-p-l-20">
|
||||
<!-- <view class="u-flex">
|
||||
<image lazy-load class="coverImg" :src="item.coverImg" mode=""></image>
|
||||
</view> -->
|
||||
<view class="">{{item.name}}</view>
|
||||
</view>
|
||||
<view class="u-flex-1 u-p-l-4 u-p-r-4 box-size-border">
|
||||
{{item.typeEnum}}
|
||||
</view>
|
||||
<view class="u-flex-1">
|
||||
¥{{ item.lowPrice }}
|
||||
</view>
|
||||
<view class="u-flex-1">
|
||||
<!-- {{ item.realSalesNumber }}/{{ item.stockNumber }} -->
|
||||
{{ item.stockNumber }}
|
||||
</view>
|
||||
<view class="u-flex-1">
|
||||
{{item.categoryName}}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="u-p-30">
|
||||
<my-pagination :totalElements="goods.totalElements" :size="goods.query.size"
|
||||
@change="pageChange"></my-pagination>
|
||||
<view class="u-m-t-20 u-flex">
|
||||
<view class="u-flex-1 u-p-r-16">
|
||||
<my-button type="cancel" plain @tap="close">取消</my-button>
|
||||
</view>
|
||||
<view class="u-flex-1 u-p-l-16">
|
||||
<my-button @tap="confrim">确定</my-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
$tbProductList
|
||||
} from '@/http/yskApi/goods.js';
|
||||
import {
|
||||
reactive,
|
||||
onMounted,
|
||||
ref,
|
||||
watch
|
||||
} from 'vue';
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
height: {
|
||||
type: [Number, String],
|
||||
default: '50vh'
|
||||
},
|
||||
category: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
}
|
||||
})
|
||||
function changeChecked(item){
|
||||
item.checked=!item.checked
|
||||
if(!item.checked&&$selGoodsMap[item.id]){
|
||||
delete $selGoodsMap[item.id]
|
||||
}else{
|
||||
$selGoodsMap[item.id]=item
|
||||
}
|
||||
goods.allChecked = goods.list.filter(v => v.checked).length != 0
|
||||
}
|
||||
function nullFunction() {
|
||||
|
||||
}
|
||||
|
||||
const show = ref(props.modelValue)
|
||||
|
||||
let selArr=[]
|
||||
|
||||
let $selGoodsMap={}
|
||||
async function open(arr) {
|
||||
show.value = true
|
||||
selArr=arr
|
||||
console.log(arr);
|
||||
for(let i in arr){
|
||||
$selGoodsMap[arr[i].proId
|
||||
]=arr[i]
|
||||
}
|
||||
getGoods()
|
||||
}
|
||||
|
||||
function close() {
|
||||
show.value = false
|
||||
resetQuery()
|
||||
$selGoodsMap={}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function resetQuery() {
|
||||
Object.assign(goods.query, $quey)
|
||||
}
|
||||
|
||||
function computedStyle() {
|
||||
return `height:${typeof props.height==='string'?props.height:props.height+'rpx'};`
|
||||
}
|
||||
|
||||
const emits = defineEmits(['update:modelValue', 'confirm'])
|
||||
|
||||
const $quey = {
|
||||
categoryId: "",
|
||||
createdAt: [],
|
||||
id: "",
|
||||
name: "",
|
||||
sort: "createdAt,desc",
|
||||
type: "",
|
||||
page: 0,
|
||||
size: 10,
|
||||
}
|
||||
const query = reactive({
|
||||
...$quey
|
||||
})
|
||||
const goods = reactive({
|
||||
list: [],
|
||||
allChecked: false,
|
||||
totalElements: 0,
|
||||
query: {
|
||||
...$quey
|
||||
}
|
||||
})
|
||||
getGoods()
|
||||
function getGoods() {
|
||||
const arr=selArr
|
||||
$tbProductList(goods.query).then(res => {
|
||||
let selLen=0;
|
||||
goods.list = res.content.map(v => {
|
||||
const checked=$selGoodsMap[v.id]?true:false
|
||||
selLen+=(checked?1:0)
|
||||
return {
|
||||
...v,
|
||||
checked
|
||||
}
|
||||
})
|
||||
goods.allChecked = selLen==res.content.length?true:false
|
||||
goods.totalElements = res.totalElements
|
||||
})
|
||||
}
|
||||
|
||||
function pageChange(page) {
|
||||
goods.query.page = page - 1
|
||||
getGoods()
|
||||
}
|
||||
|
||||
function radioChange(newval,item) {
|
||||
if(!newval&&$selGoodsMap[item.id]){
|
||||
delete $selGoodsMap[item.id]
|
||||
}else{
|
||||
$selGoodsMap[item.id]=item
|
||||
}
|
||||
goods.allChecked = goods.list.filter(v => v.checked).length != 0
|
||||
}
|
||||
|
||||
function radioAllChange(newval) {
|
||||
goods.list.forEach(i => {
|
||||
i.checked = newval
|
||||
if($selGoodsMap[i.id]&&!newval){
|
||||
delete $selGoodsMap[i.id]
|
||||
}else{
|
||||
$selGoodsMap[i.id]=i
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function confrim() {
|
||||
for(let i in goods.list){
|
||||
const item=goods.list[i]
|
||||
if($selGoodsMap[item.id]&&!item.checked){
|
||||
delete $selGoodsMap[item.id]
|
||||
}
|
||||
}
|
||||
console.log($selGoodsMap);
|
||||
const arr = Object.values($selGoodsMap)
|
||||
emits('confirm', arr)
|
||||
}
|
||||
defineExpose({
|
||||
open,
|
||||
close
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.bg-fff{
|
||||
border-radius: 24rpx 24rpx 0 0 ;
|
||||
}
|
||||
.mask {
|
||||
background: rgba(51, 51, 51, 0.5);
|
||||
z-index: 900;
|
||||
}
|
||||
.box-size-border{
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.coverImg {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
.table {
|
||||
background: #F9F9F9;
|
||||
border-radius: 8rpx;
|
||||
overflow: hidden;
|
||||
|
||||
.title {
|
||||
padding: 12rpx 24rpx 12rpx 24rpx;
|
||||
background: #AEBAD2;
|
||||
border-radius: 8rpx 8rpx 0rpx 0rpx;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.row:nth-of-type(2n+1) {
|
||||
background: #F0F0F0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
96
components/my-components/my-radio.vue
Normal file
96
components/my-components/my-radio.vue
Normal file
@@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<view class="my-radio u-font-28 u-flex color-333" @tap.stop="changeVal">
|
||||
<view class="circle u-flex u-row-center" :style="computedStyle()"
|
||||
:class="{active:modelValue,square:shape==='square'}">
|
||||
<uni-icons type="checkmarkempty" v-if="modelValue" :size="size-4" color="#fff"></uni-icons>
|
||||
</view>
|
||||
<view class="u-m-l-12">
|
||||
<slot>{{text}}</slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script setup>
|
||||
import {
|
||||
ref
|
||||
} from 'vue'
|
||||
import color from '@/commons/color.js'
|
||||
const props = defineProps({
|
||||
disabled: {
|
||||
type: [Boolean],
|
||||
default: false
|
||||
},
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: '#bbb',
|
||||
},
|
||||
size: {
|
||||
//单位px
|
||||
type: Number,
|
||||
default: 16,
|
||||
},
|
||||
// v-modal
|
||||
modelValue: {
|
||||
type: [Number, Boolean],
|
||||
default: false,
|
||||
},
|
||||
shape: {
|
||||
//circle square
|
||||
type: String,
|
||||
default: 'circle',
|
||||
},
|
||||
text: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
function computedStyle() {
|
||||
return `
|
||||
width:${props.size}px;
|
||||
height:${props.size}px;
|
||||
border-color:${props.borderColor};
|
||||
border-color:${props.modelValue?color.ColorMain:props.borderColor};
|
||||
`
|
||||
}
|
||||
const emits = defineEmits(['update:modelValue', 'change'])
|
||||
|
||||
function changeVal() {
|
||||
if (props.disabled) {
|
||||
return
|
||||
}
|
||||
emits('click')
|
||||
let currentVal = props.modelValue
|
||||
let type = typeof currentVal
|
||||
if (type === 'number') {
|
||||
currentVal = currentVal === 0 ? 1 : 0
|
||||
}
|
||||
if (type === 'boolean') {
|
||||
currentVal = !currentVal
|
||||
}
|
||||
emits('update:modelValue', currentVal)
|
||||
emits('change', currentVal)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.my-radio {
|
||||
|
||||
.circle {
|
||||
background: #FFFFFF;
|
||||
|
||||
&.active {
|
||||
background-color: $my-main-color;
|
||||
border-color: $my-main-color;
|
||||
}
|
||||
|
||||
border: 1px solid #707070;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
|
||||
&.square {
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
345
components/my-components/my-reportDamage.vue
Normal file
345
components/my-components/my-reportDamage.vue
Normal file
@@ -0,0 +1,345 @@
|
||||
<template>
|
||||
<up-popup customStyle="overflow: hidden;" :show="show" round="20" mode="bottom" @close="close" @open="open">
|
||||
<view class="reportDamage">
|
||||
<view class="reportDamage_head">
|
||||
<view class="reportDamage_title">{{title}}</view>
|
||||
<up-icon name="close-circle-fill" color="#333" size="25" @tap="close"></up-icon>
|
||||
</view>
|
||||
<view class="reportDamage_content">
|
||||
<view class="reportDamage_cell">
|
||||
<view class="cell_lable">
|
||||
<up-image v-if="type=='product'" class="thumbnail" radius="10" :show-loading="true" :src="item.coverImg"></up-image>
|
||||
<view>{{item.conName}}</view>
|
||||
</view>
|
||||
<view class="cell_value">
|
||||
<up-icon name="minus-circle" color="#999" size="25" @tap="minus"></up-icon>
|
||||
<view class="text">{{vdata.stockNumber}}</view>
|
||||
<up-icon name="plus-circle-fill" color="#318AFE" size="25" @tap="plus"></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view class="reportDamage_cell">
|
||||
<view class="cell_lable">商品单位</view>
|
||||
<view class="cell_value"><view>{{item.conUnit}}</view> <up-icon name="arrow-right" color="#999999" size="15"></up-icon></view>
|
||||
</view>
|
||||
<view class="reportDamage_cell">
|
||||
<view class="cell_lable">报损图片</view>
|
||||
<view class="cell_value file">
|
||||
|
||||
<view class="file_img" v-for="(item,index) in vdata.imgUrlList">
|
||||
<up-image class="file_img_item" :show-loading="true" :src="item"></up-image>
|
||||
<view class="del" @tap="del(index)">
|
||||
<up-icon name="trash" color="#fff" size="25" @tap="plus"></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view class="file" @tap="chooseAndUploadAvatar()">
|
||||
<up-icon name="camera-fill" color="#E5E5E5" size="35"></up-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="reportDamage_footer">
|
||||
<view class="reportDamage_btn" @tap="affirm">确认</view>
|
||||
</view>
|
||||
</view>
|
||||
</up-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
computed,
|
||||
ref,
|
||||
reactive,
|
||||
onMounted,
|
||||
watch
|
||||
} from 'vue';
|
||||
import { $uploadFile } from '@/http/yskApi/file.js'
|
||||
import { consumableBreakage , productBreakage} from '@/http/yskApi/breakage.js'
|
||||
|
||||
const props = defineProps({
|
||||
show:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
type:{
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
title:{
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
item:{
|
||||
type: Object,
|
||||
},
|
||||
|
||||
})
|
||||
const emits = defineEmits(['close','open',"affirm"])
|
||||
const vdata = reactive({
|
||||
stockNumber: 1,
|
||||
imgUrlList: [],
|
||||
})
|
||||
let show = ref(props.show)
|
||||
let type = ref(props.type)
|
||||
let itemData = ref(props.item)
|
||||
watch(()=>props.show,(newval)=>{
|
||||
show.value=newval
|
||||
})
|
||||
onMounted(() => {
|
||||
|
||||
})
|
||||
|
||||
function close() {
|
||||
show.value = false;
|
||||
vdata.imgUrlList = [];
|
||||
vdata.stockNumber = 1;
|
||||
emits('close')
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开报损弹窗
|
||||
*/
|
||||
function open() {
|
||||
show.value = true;
|
||||
emits('open')
|
||||
}
|
||||
|
||||
/**
|
||||
* 报损数量减少
|
||||
*/
|
||||
function minus() {
|
||||
if ( vdata.stockNumber <= 1) {
|
||||
return;
|
||||
}
|
||||
vdata.stockNumber--;
|
||||
}
|
||||
|
||||
/**
|
||||
* 报损数量增加
|
||||
*/
|
||||
function plus() {
|
||||
vdata.stockNumber++;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 删除报损图片
|
||||
*/
|
||||
function del ( index ) {
|
||||
vdata.imgUrlList.splice(index,1)
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传报损图片
|
||||
*/
|
||||
function chooseAndUploadAvatar () {
|
||||
if ( vdata.imgUrlList.length >= 6 ) {
|
||||
uni.showToast({
|
||||
title:'最多只可以上传六张',
|
||||
icon:'none'
|
||||
})
|
||||
return;
|
||||
}
|
||||
// 选择图片
|
||||
uni.chooseImage({
|
||||
count: 1, // 默认为1,只选择一张图片
|
||||
sizeType: ['original', 'compressed'], // 图片质量,原图或压缩
|
||||
sourceType: ['album', 'camera'], // 图片来源,相册或相机
|
||||
success: (res) => {
|
||||
let file = res.tempFiles[0];
|
||||
console.log(res)
|
||||
$uploadFile(file).then(res => {
|
||||
console.log(res);
|
||||
vdata.imgUrlList.push(res.data[0])
|
||||
|
||||
}).catch(res=>{
|
||||
console.log(res);
|
||||
if(res.errMsg){
|
||||
uni.showToast({
|
||||
title:'图片大小超出限制',
|
||||
icon:'error'
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
},
|
||||
fail: chooseImageError => {
|
||||
// 选择图片失败处理逻辑
|
||||
console.log('choose image fail:', chooseImageError);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认
|
||||
*/
|
||||
function affirm () {
|
||||
// emits('affirm')
|
||||
if (vdata.imgUrlList.length <= 0) {
|
||||
uni.showToast({
|
||||
title:'请上传报损图片',
|
||||
icon:'none'
|
||||
})
|
||||
return;
|
||||
}
|
||||
let params = {
|
||||
coverImg: vdata.imgUrlList,
|
||||
}
|
||||
//商品报损
|
||||
if ( type.value == 'consumable') {
|
||||
params.consId = itemData.value.conIn;
|
||||
params.amount = vdata.stockNumber;
|
||||
}
|
||||
|
||||
consumableBreakage(params).then((res) => {
|
||||
show.value = false;
|
||||
vdata.imgUrlList = [];
|
||||
vdata.stockNumber = 1;
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
close,
|
||||
open,
|
||||
affirm
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.mask {
|
||||
background-color: rgba(51, 51, 51, .5);
|
||||
}
|
||||
::v-deep .u-popup__content{
|
||||
// background-color: transparent;
|
||||
|
||||
}
|
||||
.reportDamage{
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.reportDamage_head{
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32rpx;
|
||||
box-sizing: border-box;
|
||||
background: #F4F4F4;
|
||||
.reportDamage_title{
|
||||
font-weight: bold;
|
||||
font-size: 32rpx;
|
||||
color: #333333;
|
||||
}
|
||||
}
|
||||
|
||||
.reportDamage_content{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #fff;
|
||||
|
||||
.reportDamage_cell{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32rpx;
|
||||
box-sizing: border-box;
|
||||
font-family: Source Han Sans CN, Source Han Sans CN;
|
||||
font-weight: 500;
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
border-bottom: 2rpx solid #E5E5E5;
|
||||
.cell_lable{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
.thumbnail{
|
||||
width: 112rpx;
|
||||
height: 112rpx;
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
::v-deep .u-image,.u-image__loading,.u-image__image{
|
||||
width: 100%!important;
|
||||
height: 100%!important;
|
||||
}
|
||||
::v-deep uni-image{
|
||||
width: 112rpx!important;
|
||||
height: 112rpx!important;
|
||||
}
|
||||
}
|
||||
.cell_value{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
flex-wrap: wrap;
|
||||
.text{
|
||||
margin-left: 20rpx;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
.file{
|
||||
padding: 30rpx;
|
||||
border-radius: 8rpx 8rpx 8rpx 8rpx;
|
||||
border: 2rpx solid #E5E5E5;
|
||||
box-sizing: border-box;
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
.file_img{
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
margin-left: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
position: relative;
|
||||
border-radius: 10rpx;
|
||||
overflow: hidden;
|
||||
.del{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.file_img_item{
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
|
||||
}
|
||||
::v-deep .u-image,.u-image__loading,.u-image__image{
|
||||
width: 100%!important;
|
||||
height: 100%!important;
|
||||
}
|
||||
::v-deep uni-image{
|
||||
width: 120rpx!important;
|
||||
height: 120rpx!important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.cell_value.file{
|
||||
// flex-direction: row-reverse;
|
||||
// justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
.reportDamage_cell:last-child{
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
.reportDamage_footer{
|
||||
background-color: #fff;
|
||||
padding: 32rpx;
|
||||
padding-bottom: 68rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.reportDamage_btn{
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
text-align: center;
|
||||
background: #318AFE;
|
||||
border-radius: 40rpx 40rpx 40rpx 40rpx;
|
||||
font-weight: 500;
|
||||
font-size: 32rpx;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
59
components/my-components/my-search.vue
Normal file
59
components/my-components/my-search.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<view class="search-box" :class="{'shape-circle':shape==='circle'}">
|
||||
<view class="search-btn u-flex u-flex-1">
|
||||
<image src="@/static/iconImg/icon-search.svg" class="icon-search" />
|
||||
<view class="u-flex-1 u-p-l-24"><input v-model="search.keyword" @confirm="confirm" type="text"
|
||||
placeholder-style="font-size:28rpx;" :placeholder="placeholder" /></view>
|
||||
|
||||
</view>
|
||||
<view @tap.stop="clear" v-if="search.keyword">
|
||||
<uni-icons type="clear" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
reactive
|
||||
} from 'vue';
|
||||
const props = defineProps({
|
||||
placeholder: {
|
||||
type:String,
|
||||
default:'搜索'
|
||||
},
|
||||
shape:{
|
||||
type:String,
|
||||
default:''
|
||||
}
|
||||
})
|
||||
|
||||
const search = reactive({
|
||||
keyword: "",
|
||||
})
|
||||
|
||||
function clear() {
|
||||
search.keyword = ''
|
||||
}
|
||||
const emits = defineEmits(['confirm'])
|
||||
|
||||
function confirm(e) {
|
||||
emits('confirm', search.keyword)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.search-box {
|
||||
background: #F9F9F9;
|
||||
padding: 10rpx 30rpx 10rpx 30rpx;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
&.shape-circle{
|
||||
border-radius: 100px;
|
||||
}
|
||||
.icon-search {
|
||||
width: 26rpx;
|
||||
height: 26rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
95
components/my-components/my-step.vue
Normal file
95
components/my-components/my-step.vue
Normal file
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<view>
|
||||
<view class="u-flex u-p-b-28 item" :class="{'active':active==index}" v-for="(item,index) in list" :key="index">
|
||||
<view class="left ">
|
||||
<view class="circle"></view>
|
||||
<view class="left-line" :class="{hide:index==list.length-1}"></view>
|
||||
</view>
|
||||
<view class="u-p-l-12 ">
|
||||
<view class="u-font-20">{{formatTitle(item[titleKey]||'') }}</view>
|
||||
<view class="u-font-24 u-m-t-2">{{item[contentKey]||''}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import dayjs from 'dayjs';
|
||||
const props = defineProps({
|
||||
active: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
titleKey: {
|
||||
type: String,
|
||||
default: 'title'
|
||||
},
|
||||
contentKey: {
|
||||
type: String,
|
||||
default: 'content'
|
||||
}
|
||||
})
|
||||
|
||||
function formatTitle(time){
|
||||
return dayjs(time).format('YYYY-M-D HH:MM:ss')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$leftWidth: 12rpx;
|
||||
$circleSize: 12rpx;
|
||||
$lineWidth: 2rpx;
|
||||
$circleLeft:0;
|
||||
$lineLeft: calc($circleSize / 2 - $lineWidth / 2 );
|
||||
|
||||
.item {
|
||||
color: #999;
|
||||
position: relative;
|
||||
|
||||
.left {
|
||||
height: 100%;
|
||||
width: $leftWidth;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: $my-main-color;
|
||||
|
||||
.circle {
|
||||
background: $my-main-color;
|
||||
}
|
||||
|
||||
.left-line {
|
||||
background-color: $my-main-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.circle {
|
||||
position: absolute;
|
||||
top: 6rpx;
|
||||
left: $circleLeft;
|
||||
width: $circleSize;
|
||||
height: $circleSize;
|
||||
border-radius: 50%;
|
||||
background: #999;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.left-line {
|
||||
left: $lineLeft;
|
||||
position: absolute;
|
||||
bottom: 4rpx;
|
||||
top: calc($circleSize + 10rpx);
|
||||
width: $lineWidth;
|
||||
background-color: #999;
|
||||
border-radius: $lineWidth;
|
||||
}
|
||||
|
||||
.hide {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
163
components/my-components/my-switch.vue
Normal file
163
components/my-components/my-switch.vue
Normal file
@@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<view>
|
||||
<view class="q-switch-box" :style="{
|
||||
backgroundColor: modelValue ? activeBgColor : inActiveBgColor,
|
||||
width: width,
|
||||
height: height,
|
||||
borderRadius: `calc(${height} / 2)`,
|
||||
}" @click.stop="changeSwitch"
|
||||
:class="{disabled:disabled&&openDisabledClass}"
|
||||
>
|
||||
<view v-show="!modelValue" class="before" :style="{
|
||||
left: `calc(${margin} * 2)`,
|
||||
}">
|
||||
<slot name="left">{{ inShowText }}</slot>
|
||||
</view>
|
||||
<!-- 圆圈 -->
|
||||
<view class="circle-inner" :style="{
|
||||
margin: margin,
|
||||
width: circleRadius,
|
||||
height: circleRadius,
|
||||
backgroundColor: '#ffffff',
|
||||
...switchAnimation,
|
||||
}"></view>
|
||||
<view v-show="modelValue" class="after" :style="{
|
||||
right: `calc(${margin} * 2)`,
|
||||
}">
|
||||
<slot name="right">{{ showText }}</slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<!-- 自定义Switch:自定义左右内容 -->
|
||||
<script setup>
|
||||
import {
|
||||
computed,
|
||||
ref
|
||||
} from 'vue'
|
||||
const props = defineProps({
|
||||
disabled:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
},
|
||||
openDisabledClass:{
|
||||
type:Boolean,
|
||||
default:true
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '40rpx',
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '78rpx',
|
||||
},
|
||||
margin: {
|
||||
type: String,
|
||||
default: '4rpx',
|
||||
},
|
||||
fontSize: {
|
||||
type: String,
|
||||
default: '32rpx',
|
||||
},
|
||||
// 激活背景色
|
||||
activeBgColor: {
|
||||
type: String,
|
||||
default: '#318AFE',
|
||||
},
|
||||
// 未激活背景色
|
||||
inActiveBgColor: {
|
||||
type: String,
|
||||
default: '#ECECEC',
|
||||
},
|
||||
// 激活文本
|
||||
showText: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
// 未激活文本
|
||||
inShowText: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
// v-modal
|
||||
modelValue: {
|
||||
type: [Boolean,Number],
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
const emits = defineEmits(['update:modelValue', 'change','click'])
|
||||
// 修改switch值
|
||||
function changeSwitch() {
|
||||
if(props.disabled){
|
||||
emits('click')
|
||||
return
|
||||
}
|
||||
let currentVal=props.modelValue
|
||||
let type=typeof currentVal
|
||||
if(type==='number'){
|
||||
currentVal=currentVal===0?1:0
|
||||
}
|
||||
if(type==='boolean'){
|
||||
currentVal=!currentVal
|
||||
}
|
||||
emits('update:modelValue', currentVal)
|
||||
emits('change', currentVal)
|
||||
}
|
||||
// 圆圈半径
|
||||
let circleRadius = computed(() => {
|
||||
return `calc(((${props.height} / 2) - ${props.margin})* 2)`
|
||||
})
|
||||
// 动画效果
|
||||
let switchAnimation = computed(() => {
|
||||
let obj = {
|
||||
transition: `transform 0.3s`,
|
||||
transform: 'translateX(0)',
|
||||
}
|
||||
//激活
|
||||
if (props.modelValue) {
|
||||
let innerRadius = `((${props.height} / 2) - ${props.margin})` //圆圈半径
|
||||
let moveValue =
|
||||
`calc(${props.width} - ${props.margin} * 2 - (${innerRadius} * 2))` //偏移距离 总宽度 - 圆圈左右边距 - 圆圈宽度
|
||||
// console.log('move-value', moveValue)
|
||||
obj.transform = `translateX(${moveValue})`
|
||||
} else {
|
||||
// 未激活
|
||||
obj.transform = 'translateX(0)'
|
||||
}
|
||||
return obj
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.q-switch-box {
|
||||
display: flex;
|
||||
background-color: #eaeaea;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
&.disabled{
|
||||
opacity: .5;
|
||||
}
|
||||
.circle-inner {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.before {
|
||||
position: absolute;
|
||||
color: #bbbbbb;
|
||||
font-size: 28rpx;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.after {
|
||||
position: absolute;
|
||||
font-size: 28rpx;
|
||||
color: #ffffff;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
8
components/my-components/my-table-col.vue
Normal file
8
components/my-components/my-table-col.vue
Normal file
@@ -0,0 +1,8 @@
|
||||
<template>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
76
components/my-components/my-table.vue
Normal file
76
components/my-components/my-table.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<view class=" table u-font-28">
|
||||
<view class="u-flex u-row-between no-wrap title">
|
||||
<slot name="title">
|
||||
<view>
|
||||
<my-radio @change="radioAllChange" v-model="allChecked" shape="square" :size="20"></my-radio>
|
||||
</view>
|
||||
<view>商品信息</view>
|
||||
<view>规格</view>
|
||||
<view>售价</view>
|
||||
<view>销量/库存</view>
|
||||
<view>分类名称</view>
|
||||
</slot>
|
||||
</view>
|
||||
<view @click="changeChecked(item)" class="u-m-t-12 u-flex u-p-24 u-row-between row"
|
||||
v-for="(item,index) in list" :key="index">
|
||||
<view class="">
|
||||
<my-radio @change="radioChange($event,item)" v-model="item.checked" shape="square"
|
||||
:size="20"></my-radio>
|
||||
</view>
|
||||
<view class="u-text-left u-flex-1 u-p-l-20">
|
||||
<view class="">{{item.name}}</view>
|
||||
</view>
|
||||
<view class="u-flex-1 u-p-l-4 u-p-r-4 box-size-border">
|
||||
{{item.typeEnum}}
|
||||
</view>
|
||||
<view class="u-flex-1">
|
||||
¥{{ item.lowPrice }}
|
||||
</view>
|
||||
<view class="u-flex-1">
|
||||
<!-- {{ item.realSalesNumber }}/{{ item.stockNumber }} -->
|
||||
{{ item.stockNumber }}
|
||||
</view>
|
||||
<view class="u-flex-1">
|
||||
{{item.categoryName}}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
reactive, ref
|
||||
} from 'vue';
|
||||
const props=defineProps({
|
||||
list:{
|
||||
type:Array,
|
||||
default:()=>[]
|
||||
}
|
||||
})
|
||||
let allChecked=ref(false)
|
||||
const emits=defineEmits(['selectAllChange'])
|
||||
function radioAllChange(e){
|
||||
emits('selectAllChange')
|
||||
console.log('selectAllChange');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.table {
|
||||
background: #F9F9F9;
|
||||
border-radius: 8rpx;
|
||||
overflow: hidden;
|
||||
|
||||
.title {
|
||||
padding: 12rpx 24rpx 12rpx 24rpx;
|
||||
background: #AEBAD2;
|
||||
border-radius: 8rpx 8rpx 0rpx 0rpx;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.row:nth-of-type(2n+1) {
|
||||
background: #F0F0F0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
144
components/my-components/my-tabs.vue
Normal file
144
components/my-components/my-tabs.vue
Normal file
@@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<view class="u-relative bg border-r-16">
|
||||
|
||||
<view class="u-flex tabs zhanwei u-relative">
|
||||
<view class="active-block" :style="computedBlockStyle">
|
||||
</view>
|
||||
<view class="u-flex-1 u-text-center item" :class="[index===current?'':'active',size]" @tap="changeCurrent(index)"
|
||||
v-for="(item,index) in props.list" :key="index">
|
||||
{{textKey?item[textKey]:item}}
|
||||
</view>
|
||||
|
||||
</view>
|
||||
<view class="u-flex tabs u-absolute position-all">
|
||||
|
||||
<view class="u-flex-1 u-text-center item" :class="{active:index===current}" @tap="changeCurrent(index)"
|
||||
v-for="(item,index) in props.list" :key="index">
|
||||
{{textKey?item[textKey]:item}}
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
computed,
|
||||
ref,
|
||||
watch
|
||||
} from 'vue';
|
||||
const props = defineProps({
|
||||
size:{
|
||||
type: String,
|
||||
default:''
|
||||
},
|
||||
padding: {
|
||||
type: String
|
||||
},
|
||||
list: {
|
||||
type: Array
|
||||
},
|
||||
textKey: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
defaultIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
modelValue: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
const emit = defineEmits(['change', 'update:modelValue'])
|
||||
let current = ref(props.modelValue || props.defaultIndex || 0)
|
||||
|
||||
function changeCurrent(index) {
|
||||
current.value = index
|
||||
}
|
||||
const computedBlockStyle = computed(() => {
|
||||
const oneWidth = 100 / props.list.length
|
||||
const left = current.value * oneWidth
|
||||
return {
|
||||
width: oneWidth + '%',
|
||||
left: left + '%'
|
||||
}
|
||||
})
|
||||
watch(() => props.modelValue, (newval) => {
|
||||
current.value = newval
|
||||
})
|
||||
watch(() => current.value, (newval) => {
|
||||
emit('update:modelValue', newval)
|
||||
emit('change', newval)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.zhanwei {
|
||||
.item {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.bg {
|
||||
background: #E6F0FF;
|
||||
padding: 7rpx 10rpx;
|
||||
}
|
||||
|
||||
.border-r-16 {
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.active-block {
|
||||
background-color: $my-main-color;
|
||||
color: #fff;
|
||||
top: 4rpx;
|
||||
bottom: 4rpx;
|
||||
left: 10rpx;
|
||||
position: absolute;
|
||||
border-radius: 8rpx;
|
||||
transition: all .2s ease-in-out;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
border-radius: 16rpx;
|
||||
font-size: 28rpx;
|
||||
color: #318AFE;
|
||||
z-index: 2;
|
||||
|
||||
.active-block {
|
||||
background-color: $my-main-color;
|
||||
color: #fff;
|
||||
top: 4rpx;
|
||||
bottom: 4rpx;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
border-radius: 8rpx;
|
||||
transition: all .2s ease-in-out;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.item {
|
||||
padding: 9rpx 0;
|
||||
transition: all .2s ease-in-out;
|
||||
position: relative;
|
||||
background-color: transparent;
|
||||
z-index: 2;
|
||||
&.large{
|
||||
padding: 16rpx 0;
|
||||
}
|
||||
}
|
||||
|
||||
.item.active {
|
||||
border-radius: 8rpx;
|
||||
// background-color: $my-main-color;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
9
components/my-components/my-tag.vue
Normal file
9
components/my-components/my-tag.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<view></view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
49
components/my-components/my-time-area-pickerview.vue
Normal file
49
components/my-components/my-time-area-pickerview.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<myPickerview :list="list" :isLink="false" @confirm="confirm" ref="picker"></myPickerview>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
reactive,
|
||||
ref
|
||||
} from "vue";
|
||||
import myPickerview from './my-pickerview'
|
||||
const picker = ref(null)
|
||||
function generateString(length, char) {
|
||||
return char.repeat(length);
|
||||
}
|
||||
function createNumberArr(len) {
|
||||
return new Array(len).fill(1).map((v, index) => {
|
||||
return `${generateString(`${len}`.length-1,'0')}${index}`.slice(-2)
|
||||
})
|
||||
}
|
||||
|
||||
const $HOURS =createNumberArr(24)
|
||||
const $MINUTES = createNumberArr(60)
|
||||
const list = reactive(
|
||||
[
|
||||
$HOURS,
|
||||
[':'],
|
||||
$MINUTES,
|
||||
['至'],
|
||||
$HOURS,
|
||||
[':'],
|
||||
$MINUTES
|
||||
]
|
||||
)
|
||||
const emits=defineEmits(['confirm'])
|
||||
|
||||
function open() {
|
||||
picker.value.open()
|
||||
}
|
||||
function confirm(e){
|
||||
const val=e.join('').replace('至','-')
|
||||
emits('confirm',val)
|
||||
}
|
||||
defineExpose({
|
||||
open
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
118
components/my-components/my-up-upload.vue
Normal file
118
components/my-components/my-up-upload.vue
Normal file
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<up-upload :fileList="images" @afterRead="afterRead" @delete="deletePic" :multiple="multiple" :width="width"
|
||||
:height="height" :maxCount="maxCount"></up-upload>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
$uploadFile
|
||||
} from '@/http/yskApi/file.js'
|
||||
import {
|
||||
ref,
|
||||
watch
|
||||
} from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
width: {
|
||||
type: [String, Number],
|
||||
default: 60
|
||||
},
|
||||
height: {
|
||||
type: [String, Number],
|
||||
default: 60
|
||||
},
|
||||
maxCount: {
|
||||
type: [Number],
|
||||
default: 10
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const emits = defineEmits(['update:modelValue'])
|
||||
const images = ref(props.modelValue)
|
||||
|
||||
|
||||
|
||||
function uploadfile(par){
|
||||
let file=null;
|
||||
// #ifdef H5
|
||||
file= par.file
|
||||
// #endif
|
||||
// #ifndef H5
|
||||
file= par
|
||||
// #endif
|
||||
return $uploadFile(file)
|
||||
}
|
||||
|
||||
function afterRead(e) {
|
||||
console.log(e);
|
||||
if (Array.isArray(e.file)) {
|
||||
for (let i in e.file) {
|
||||
const file = e.file[i]
|
||||
console.log(file);
|
||||
uploadfile(file).then(res => {
|
||||
console.log(res);
|
||||
images.value.push({
|
||||
url: e.file[i].url,
|
||||
serveUrl: res.data[0]
|
||||
})
|
||||
}).catch(res => {
|
||||
console.log(res);
|
||||
if (res.errMsg) {
|
||||
images.value.splice(i, 1)
|
||||
uni.showToast({
|
||||
title: '图片大小超出限制',
|
||||
icon: 'error'
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}else{
|
||||
const i=0
|
||||
uploadfile(e.file).then(res => {
|
||||
console.log(res);
|
||||
images.value.push({
|
||||
url: e.file.url,
|
||||
serveUrl: res.data[0]
|
||||
})
|
||||
}).catch(res => {
|
||||
console.log(res);
|
||||
if (res.errMsg) {
|
||||
images.value.splice(i, 1)
|
||||
uni.showToast({
|
||||
title: '图片大小超出限制',
|
||||
icon: 'error'
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function deletePic(e) {
|
||||
const {
|
||||
index
|
||||
} = e
|
||||
images.value.splice(index, 1)
|
||||
}
|
||||
|
||||
watch(() => images.value, (newval) => {
|
||||
emits('update:modelValue', newval)
|
||||
})
|
||||
watch(() => props.modelValue, (newval) => {
|
||||
images.value = newval
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
116
components/my-components/my-upload-file.vue
Normal file
116
components/my-components/my-upload-file.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<uni-file-picker v-model="imgList" file-mediatype="image" mode="grid" :limit="limit" @progress="FileUploadprogress"
|
||||
:image-styles="imageStyles"
|
||||
@delete="fileDelete" @success="FileUploadsuccess" @fail="FileUploadail" @select="FileUploadselect" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
$uploadFile
|
||||
} from '@/http/yskApi/file.js'
|
||||
import {
|
||||
reactive,
|
||||
ref,
|
||||
watch
|
||||
} from 'vue';
|
||||
const props = defineProps({
|
||||
images: {
|
||||
type: [Array,String],
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
imageStyles:{
|
||||
type:Object,
|
||||
default:()=>{
|
||||
return {
|
||||
border:{
|
||||
radius:'12rpx'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
const emits = defineEmits(['change'])
|
||||
const arr=props.images===''?[]:typeof props.images==='string'? [{path:props.images,url:props.images}]:props.images
|
||||
let imgList = ref(arr)
|
||||
let flag=false
|
||||
const stopWatcher = watch(() => props.images, (newval) => {
|
||||
if(!flag){
|
||||
imgList.value = (typeof newval==='string'&&newval!=='')?[{path:newval,url:newval}]:newval
|
||||
flag=true
|
||||
}
|
||||
})
|
||||
watch(() => imgList.value.length, () => {
|
||||
change()
|
||||
})
|
||||
//图片上传
|
||||
function FileUploadprogress() {
|
||||
|
||||
}
|
||||
|
||||
function FileUploadsuccess() {
|
||||
|
||||
}
|
||||
|
||||
function FileUploadail() {
|
||||
|
||||
}
|
||||
|
||||
function FileUploadselect(e) {
|
||||
for (let i in e.tempFiles) {
|
||||
const file = e.tempFiles[i]
|
||||
$uploadFile(file).then(res => {
|
||||
console.log(res);
|
||||
imgList.value.push({
|
||||
url: res.data[0],
|
||||
path: file.path
|
||||
})
|
||||
}).catch(res=>{
|
||||
console.log(res);
|
||||
if(res.errMsg){
|
||||
imgList.value.splice(i,1)
|
||||
uni.showToast({
|
||||
title:'图片大小超出限制',
|
||||
icon:'error'
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function fileDelete(e) {
|
||||
const index = imgList.value.findIndex(v => e.tempFilePath === v.path)
|
||||
if (index != -1) {
|
||||
imgList.value.splice(index, 1)
|
||||
}
|
||||
console.log(imgList.value);
|
||||
}
|
||||
|
||||
function getFileList() {
|
||||
if (props.limit === 1) {
|
||||
const item=imgList.value[0]
|
||||
return item===undefined?'':typeof item === 'string' ? item : item.url
|
||||
}
|
||||
return imgList.value.map(v => {
|
||||
const url = v.url
|
||||
return typeof v === 'string' ? v : url
|
||||
})
|
||||
}
|
||||
|
||||
function change() {
|
||||
console.log(getFileList());
|
||||
emits('change', getFileList())
|
||||
}
|
||||
defineExpose({
|
||||
getFileList
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
784
components/t-color-picker/t-color-picker.vue
Normal file
784
components/t-color-picker/t-color-picker.vue
Normal file
@@ -0,0 +1,784 @@
|
||||
<template>
|
||||
<view v-show="show" class="t-wrapper" @touchmove.stop.prevent="moveHandle">
|
||||
<view class="t-mask" :class="{active:active}" @click.stop="close"></view>
|
||||
<view class="t-box" :class="{active:active}">
|
||||
<view class="t-header">
|
||||
<view class="t-header-button" @click="close">取消</view>
|
||||
<view class="t-header-button" @click="confirm">确认</view>
|
||||
</view>
|
||||
<view class="t-color__box" :style="{ background: 'rgb(' + bgcolor.r + ',' + bgcolor.g + ',' + bgcolor.b + ')'}">
|
||||
<view class="t-background boxs" @touchstart="touchstart($event, 0)" @touchmove="touchmove($event, 0)" @touchend="touchend($event, 0)">
|
||||
<view class="t-color-mask"></view>
|
||||
<view class="t-pointer" :style="{ top: site[0].top - 8 + 'px', left: site[0].left - 8 + 'px' }"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="t-control__box">
|
||||
<view class="t-control__color">
|
||||
<view class="t-control__color-content" :style="{ background: 'rgba(' + rgba.r + ',' + rgba.g + ',' + rgba.b + ',' + rgba.a + ')' }"></view>
|
||||
</view>
|
||||
<view class="t-control-box__item">
|
||||
<view class="t-controller boxs" @touchstart="touchstart($event, 1)" @touchmove="touchmove($event, 1)" @touchend="touchend($event, 1)">
|
||||
<view class="t-hue">
|
||||
<view class="t-circle" :style="{ left: site[1].left - 12 + 'px' }"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="t-controller boxs" @touchstart="touchstart($event, 2)" @touchmove="touchmove($event, 2)" @touchend="touchend($event, 2)">
|
||||
<view class="t-transparency">
|
||||
<view class="t-circle" :style="{ left: site[2].left - 12 + 'px' }"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="t-result__box">
|
||||
<view v-if="mode" class="t-result__item">
|
||||
<view class="t-result__box-input">{{hex}}</view>
|
||||
<view class="t-result__box-text">HEX</view>
|
||||
</view>
|
||||
<template v-else>
|
||||
<view class="t-result__item">
|
||||
<view class="t-result__box-input">{{rgba.r}}</view>
|
||||
<view class="t-result__box-text">R</view>
|
||||
</view>
|
||||
<view class="t-result__item">
|
||||
<view class="t-result__box-input">{{rgba.g}}</view>
|
||||
<view class="t-result__box-text">G</view>
|
||||
</view>
|
||||
<view class="t-result__item">
|
||||
<view class="t-result__box-input">{{rgba.b}}</view>
|
||||
<view class="t-result__box-text">B</view>
|
||||
</view>
|
||||
<view class="t-result__item">
|
||||
<view class="t-result__box-input">{{rgba.a}}</view>
|
||||
<view class="t-result__box-text">A</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<view class="t-result__item t-select" @click="select">
|
||||
<view class="t-result__box-input">
|
||||
<view>切换</view>
|
||||
<view>模式</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="t-alternative">
|
||||
<view class="t-alternative__item" v-for="(item,index) in colorList" :key="index">
|
||||
<view class="t-alternative__item-content" :style="{ background: 'rgba(' + item.r + ',' + item.g + ',' + item.b + ',' + item.a + ')' }"
|
||||
@click="selectColor(item)">
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
color: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
spareColor: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
active: false,
|
||||
// rgba 颜色
|
||||
rgba: {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 1
|
||||
},
|
||||
// hsb 颜色
|
||||
hsb: {
|
||||
h: 0,
|
||||
s: 0,
|
||||
b: 0
|
||||
},
|
||||
site: [{
|
||||
top: 0,
|
||||
left: 0
|
||||
}, {
|
||||
left: 0
|
||||
}, {
|
||||
left: 0
|
||||
}],
|
||||
index: 0,
|
||||
bgcolor: {
|
||||
r: 255,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 1
|
||||
},
|
||||
hex: '#000000',
|
||||
mode: true,
|
||||
colorList: [{
|
||||
r: 244,
|
||||
g: 67,
|
||||
b: 54,
|
||||
a: 1
|
||||
}, {
|
||||
r: 233,
|
||||
g: 30,
|
||||
b: 99,
|
||||
a: 1
|
||||
}, {
|
||||
r: 156,
|
||||
g: 39,
|
||||
b: 176,
|
||||
a: 1
|
||||
}, {
|
||||
r: 103,
|
||||
g: 58,
|
||||
b: 183,
|
||||
a: 1
|
||||
}, {
|
||||
r: 63,
|
||||
g: 81,
|
||||
b: 181,
|
||||
a: 1
|
||||
}, {
|
||||
r: 33,
|
||||
g: 150,
|
||||
b: 243,
|
||||
a: 1
|
||||
}, {
|
||||
r: 3,
|
||||
g: 169,
|
||||
b: 244,
|
||||
a: 1
|
||||
}, {
|
||||
r: 0,
|
||||
g: 188,
|
||||
b: 212,
|
||||
a: 1
|
||||
}, {
|
||||
r: 0,
|
||||
g: 150,
|
||||
b: 136,
|
||||
a: 1
|
||||
}, {
|
||||
r: 76,
|
||||
g: 175,
|
||||
b: 80,
|
||||
a: 1
|
||||
}, {
|
||||
r: 139,
|
||||
g: 195,
|
||||
b: 74,
|
||||
a: 1
|
||||
}, {
|
||||
r: 205,
|
||||
g: 220,
|
||||
b: 57,
|
||||
a: 1
|
||||
}, {
|
||||
r: 255,
|
||||
g: 235,
|
||||
b: 59,
|
||||
a: 1
|
||||
}, {
|
||||
r: 255,
|
||||
g: 193,
|
||||
b: 7,
|
||||
a: 1
|
||||
}, {
|
||||
r: 255,
|
||||
g: 152,
|
||||
b: 0,
|
||||
a: 1
|
||||
}, {
|
||||
r: 255,
|
||||
g: 87,
|
||||
b: 34,
|
||||
a: 1
|
||||
}, {
|
||||
r: 121,
|
||||
g: 85,
|
||||
b: 72,
|
||||
a: 1
|
||||
}, {
|
||||
r: 158,
|
||||
g: 158,
|
||||
b: 158,
|
||||
a: 1
|
||||
}, {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 0.5
|
||||
}, {
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 0
|
||||
}, ]
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.rgba = this.color;
|
||||
if (this.spareColor.length !== 0) {
|
||||
this.colorList = this.spareColor;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 初始化
|
||||
*/
|
||||
init() {
|
||||
// hsb 颜色
|
||||
this.hsb = this.rgbToHex(this.rgba);
|
||||
// this.setColor();
|
||||
this.setValue(this.rgba);
|
||||
},
|
||||
moveHandle() {},
|
||||
open() {
|
||||
this.show = true;
|
||||
this.$nextTick(() => {
|
||||
this.init();
|
||||
setTimeout(() => {
|
||||
this.active = true;
|
||||
setTimeout(() => {
|
||||
this.getSelectorQuery();
|
||||
}, 350)
|
||||
}, 50)
|
||||
})
|
||||
|
||||
},
|
||||
close() {
|
||||
this.active = false;
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.show = false;
|
||||
}, 500)
|
||||
})
|
||||
},
|
||||
confirm() {
|
||||
this.close();
|
||||
this.$emit('confirm', {
|
||||
rgba: this.rgba,
|
||||
hex: this.hex
|
||||
})
|
||||
},
|
||||
// 选择模式
|
||||
select() {
|
||||
this.mode = !this.mode
|
||||
},
|
||||
// 常用颜色选择
|
||||
selectColor(item) {
|
||||
this.setColorBySelect(item)
|
||||
},
|
||||
touchstart(e, index) {
|
||||
const {
|
||||
pageX,
|
||||
pageY
|
||||
} = e.touches[0];
|
||||
this.pageX = pageX;
|
||||
this.pageY = pageY;
|
||||
this.setPosition(pageX, pageY, index);
|
||||
},
|
||||
touchmove(e, index) {
|
||||
const {
|
||||
pageX,
|
||||
pageY
|
||||
} = e.touches[0];
|
||||
this.moveX = pageX;
|
||||
this.moveY = pageY;
|
||||
this.setPosition(pageX, pageY, index);
|
||||
},
|
||||
touchend(e, index) {},
|
||||
/**
|
||||
* 设置位置
|
||||
*/
|
||||
setPosition(x, y, index) {
|
||||
this.index = index;
|
||||
const {
|
||||
top,
|
||||
left,
|
||||
width,
|
||||
height
|
||||
} = this.position[index];
|
||||
// 设置最大最小值
|
||||
|
||||
this.site[index].left = Math.max(0, Math.min(parseInt(x - left), width));
|
||||
if (index === 0) {
|
||||
this.site[index].top = Math.max(0, Math.min(parseInt(y - top), height));
|
||||
// 设置颜色
|
||||
this.hsb.s = parseInt((100 * this.site[index].left) / width);
|
||||
this.hsb.b = parseInt(100 - (100 * this.site[index].top) / height);
|
||||
this.setColor();
|
||||
this.setValue(this.rgba);
|
||||
} else {
|
||||
this.setControl(index, this.site[index].left);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 设置 rgb 颜色
|
||||
*/
|
||||
setColor() {
|
||||
const rgb = this.HSBToRGB(this.hsb);
|
||||
this.rgba.r = rgb.r;
|
||||
this.rgba.g = rgb.g;
|
||||
this.rgba.b = rgb.b;
|
||||
},
|
||||
/**
|
||||
* 设置二进制颜色
|
||||
* @param {Object} rgb
|
||||
*/
|
||||
setValue(rgb) {
|
||||
this.hex = '#' + this.rgbToHex(rgb);
|
||||
},
|
||||
setControl(index, x) {
|
||||
const {
|
||||
top,
|
||||
left,
|
||||
width,
|
||||
height
|
||||
} = this.position[index];
|
||||
|
||||
if (index === 1) {
|
||||
this.hsb.h = parseInt((360 * x) / width);
|
||||
this.bgcolor = this.HSBToRGB({
|
||||
h: this.hsb.h,
|
||||
s: 100,
|
||||
b: 100
|
||||
});
|
||||
this.setColor()
|
||||
} else {
|
||||
this.rgba.a = (x / width).toFixed(1);
|
||||
}
|
||||
this.setValue(this.rgba);
|
||||
},
|
||||
/**
|
||||
* rgb 转 二进制 hex
|
||||
* @param {Object} rgb
|
||||
*/
|
||||
rgbToHex(rgb) {
|
||||
let hex = [rgb.r.toString(16), rgb.g.toString(16), rgb.b.toString(16)];
|
||||
hex.map(function(str, i) {
|
||||
if (str.length == 1) {
|
||||
hex[i] = '0' + str;
|
||||
}
|
||||
});
|
||||
return hex.join('');
|
||||
},
|
||||
setColorBySelect(getrgb) {
|
||||
const {
|
||||
r,
|
||||
g,
|
||||
b,
|
||||
a
|
||||
} = getrgb;
|
||||
let rgb = {}
|
||||
rgb = {
|
||||
r: r ? parseInt(r) : 0,
|
||||
g: g ? parseInt(g) : 0,
|
||||
b: b ? parseInt(b) : 0,
|
||||
a: a ? a : 0,
|
||||
};
|
||||
this.rgba = rgb;
|
||||
this.hsb = this.rgbToHsb(rgb);
|
||||
this.changeViewByHsb();
|
||||
},
|
||||
changeViewByHsb() {
|
||||
const [a, b, c] = this.position;
|
||||
this.site[0].left = parseInt(this.hsb.s * a.width / 100);
|
||||
this.site[0].top = parseInt((100 - this.hsb.b) * a.height / 100);
|
||||
this.setColor(this.hsb.h);
|
||||
this.setValue(this.rgba);
|
||||
this.bgcolor = this.HSBToRGB({
|
||||
h: this.hsb.h,
|
||||
s: 100,
|
||||
b: 100
|
||||
});
|
||||
|
||||
this.site[1].left = this.hsb.h / 360 * b.width;
|
||||
this.site[2].left = this.rgba.a * c.width;
|
||||
|
||||
},
|
||||
/**
|
||||
* hsb 转 rgb
|
||||
* @param {Object} 颜色模式 H(hues)表示色相,S(saturation)表示饱和度,B(brightness)表示亮度
|
||||
*/
|
||||
HSBToRGB(hsb) {
|
||||
let rgb = {};
|
||||
let h = Math.round(hsb.h);
|
||||
let s = Math.round((hsb.s * 255) / 100);
|
||||
let v = Math.round((hsb.b * 255) / 100);
|
||||
if (s == 0) {
|
||||
rgb.r = rgb.g = rgb.b = v;
|
||||
} else {
|
||||
let t1 = v;
|
||||
let t2 = ((255 - s) * v) / 255;
|
||||
let t3 = ((t1 - t2) * (h % 60)) / 60;
|
||||
if (h == 360) h = 0;
|
||||
if (h < 60) {
|
||||
rgb.r = t1;
|
||||
rgb.b = t2;
|
||||
rgb.g = t2 + t3;
|
||||
} else if (h < 120) {
|
||||
rgb.g = t1;
|
||||
rgb.b = t2;
|
||||
rgb.r = t1 - t3;
|
||||
} else if (h < 180) {
|
||||
rgb.g = t1;
|
||||
rgb.r = t2;
|
||||
rgb.b = t2 + t3;
|
||||
} else if (h < 240) {
|
||||
rgb.b = t1;
|
||||
rgb.r = t2;
|
||||
rgb.g = t1 - t3;
|
||||
} else if (h < 300) {
|
||||
rgb.b = t1;
|
||||
rgb.g = t2;
|
||||
rgb.r = t2 + t3;
|
||||
} else if (h < 360) {
|
||||
rgb.r = t1;
|
||||
rgb.g = t2;
|
||||
rgb.b = t1 - t3;
|
||||
} else {
|
||||
rgb.r = 0;
|
||||
rgb.g = 0;
|
||||
rgb.b = 0;
|
||||
}
|
||||
}
|
||||
return {
|
||||
r: Math.round(rgb.r),
|
||||
g: Math.round(rgb.g),
|
||||
b: Math.round(rgb.b)
|
||||
};
|
||||
},
|
||||
rgbToHsb(rgb) {
|
||||
let hsb = {
|
||||
h: 0,
|
||||
s: 0,
|
||||
b: 0
|
||||
};
|
||||
let min = Math.min(rgb.r, rgb.g, rgb.b);
|
||||
let max = Math.max(rgb.r, rgb.g, rgb.b);
|
||||
let delta = max - min;
|
||||
hsb.b = max;
|
||||
hsb.s = max != 0 ? 255 * delta / max : 0;
|
||||
if (hsb.s != 0) {
|
||||
if (rgb.r == max) hsb.h = (rgb.g - rgb.b) / delta;
|
||||
else if (rgb.g == max) hsb.h = 2 + (rgb.b - rgb.r) / delta;
|
||||
else hsb.h = 4 + (rgb.r - rgb.g) / delta;
|
||||
} else hsb.h = -1;
|
||||
hsb.h *= 60;
|
||||
if (hsb.h < 0) hsb.h = 0;
|
||||
hsb.s *= 100 / 255;
|
||||
hsb.b *= 100 / 255;
|
||||
return hsb;
|
||||
},
|
||||
getSelectorQuery() {
|
||||
const views = uni.createSelectorQuery().in(this);
|
||||
views
|
||||
.selectAll('.boxs')
|
||||
.boundingClientRect(data => {
|
||||
if (!data || data.length === 0) {
|
||||
setTimeout(() => this.getSelectorQuery(), 20)
|
||||
return
|
||||
}
|
||||
this.position = data;
|
||||
// this.site[0].top = data[0].height;
|
||||
// this.site[0].left = 0;
|
||||
// this.site[1].left = data[1].width;
|
||||
// this.site[2].left = data[2].width;
|
||||
this.setColorBySelect(this.rgba);
|
||||
})
|
||||
.exec();
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
spareColor(newVal) {
|
||||
this.colorList = newVal;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.t-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.t-box {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
padding: 30upx 0;
|
||||
padding-top: 0;
|
||||
background: #fff;
|
||||
transition: all 0.3s;
|
||||
transform: translateY(100%);
|
||||
}
|
||||
|
||||
.t-box.active {
|
||||
transform: translateY(0%);
|
||||
}
|
||||
|
||||
.t-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
height: 100upx;
|
||||
border-bottom: 1px #eee solid;
|
||||
box-shadow: 1px 0 2px rgba(0, 0, 0, 0.1);
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.t-header-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 150upx;
|
||||
height: 100upx;
|
||||
font-size: 30upx;
|
||||
color: #666;
|
||||
padding-left: 20upx;
|
||||
}
|
||||
|
||||
.t-header-button:last-child {
|
||||
justify-content: flex-end;
|
||||
padding-right: 20upx;
|
||||
}
|
||||
|
||||
.t-mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
z-index: -1;
|
||||
transition: all 0.3s;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.t-mask.active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.t-color__box {
|
||||
position: relative;
|
||||
height: 400upx;
|
||||
background: rgb(255, 0, 0);
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
margin: 0 20upx;
|
||||
margin-top: 20upx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.t-background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(to right, #fff, rgba(255, 255, 255, 0));
|
||||
}
|
||||
|
||||
.t-color-mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 400upx;
|
||||
background: linear-gradient(to top, #000, rgba(0, 0, 0, 0));
|
||||
}
|
||||
|
||||
.t-pointer {
|
||||
position: absolute;
|
||||
bottom: -8px;
|
||||
left: -8px;
|
||||
z-index: 2;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
border: 1px #fff solid;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.t-show-color {
|
||||
width: 100upx;
|
||||
height: 50upx;
|
||||
}
|
||||
|
||||
.t-control__box {
|
||||
margin-top: 50upx;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
padding-left: 20upx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.t-control__color {
|
||||
flex-shrink: 0;
|
||||
width: 100upx;
|
||||
height: 100upx;
|
||||
border-radius: 50%;
|
||||
background-color: #fff;
|
||||
background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),
|
||||
linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
|
||||
background-size: 36upx 36upx;
|
||||
background-position: 0 0, 18upx 18upx;
|
||||
border: 1px #eee solid;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.t-control__color-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.t-control-box__item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
padding: 0 30upx;
|
||||
}
|
||||
|
||||
.t-controller {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 16px;
|
||||
background-color: #fff;
|
||||
background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),
|
||||
linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
|
||||
background-size: 32upx 32upx;
|
||||
background-position: 0 0, 16upx 16upx;
|
||||
}
|
||||
|
||||
.t-hue {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);
|
||||
}
|
||||
|
||||
.t-transparency {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(to right, rgba(0, 0, 0, 0) 0%, rgb(0, 0, 0));
|
||||
}
|
||||
|
||||
.t-circle {
|
||||
position: absolute;
|
||||
/* right: -10px; */
|
||||
top: -2px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.t-result__box {
|
||||
margin-top: 20upx;
|
||||
padding: 10upx;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.t-result__item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10upx;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.t-result__box-input {
|
||||
padding: 10upx 0;
|
||||
width: 100%;
|
||||
font-size: 28upx;
|
||||
box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.1);
|
||||
color: #999;
|
||||
text-align: center;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.t-result__box-text {
|
||||
margin-top: 10upx;
|
||||
font-size: 28upx;
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
.t-select {
|
||||
flex-shrink: 0;
|
||||
width: 150upx;
|
||||
padding: 0 30upx;
|
||||
}
|
||||
|
||||
.t-select .t-result__box-input {
|
||||
border-radius: 10upx;
|
||||
border: none;
|
||||
color: #999;
|
||||
box-shadow: 1px 1px 2px 1px rgba(0, 0, 0, 0.1);
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.t-select .t-result__box-input:active {
|
||||
box-shadow: 0px 0px 1px 0px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.t-alternative {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
/* justify-content: space-between; */
|
||||
width: 100%;
|
||||
padding-right: 10upx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.t-alternative__item {
|
||||
margin-left: 12upx;
|
||||
margin-top: 10upx;
|
||||
width: 50upx;
|
||||
height: 50upx;
|
||||
border-radius: 10upx;
|
||||
background-color: #fff;
|
||||
background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),
|
||||
linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);
|
||||
background-size: 36upx 36upx;
|
||||
background-position: 0 0, 18upx 18upx;
|
||||
border: 1px #eee solid;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.t-alternative__item-content {
|
||||
width: 50upx;
|
||||
height: 50upx;
|
||||
background: rgba(255, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.t-alternative__item:active {
|
||||
transition: all 0.3s;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
</style>
|
||||
977
components/yhdsl-cropper/yhdsl-cropper.vue
Normal file
977
components/yhdsl-cropper/yhdsl-cropper.vue
Normal file
@@ -0,0 +1,977 @@
|
||||
<template>
|
||||
<view class="yhdsl" v-show="isShow">
|
||||
<view>
|
||||
<view class="cropper-content">
|
||||
<view v-if="isShowImg" class="uni-corpper"
|
||||
:style="'width:' + cropperInitW + 'px;height:' + cropperInitH + 'px;background:#000'">
|
||||
<view class="uni-corpper-content" :style="
|
||||
'width:' +
|
||||
cropperW +
|
||||
'px;height:' +
|
||||
cropperH +
|
||||
'px;left:' +
|
||||
cropperL +
|
||||
'px;top:' +
|
||||
cropperT +
|
||||
'px'
|
||||
">
|
||||
<image :src="imageSrc" :style="'width:' + cropperW + 'px;height:' + cropperH + 'px'"></image>
|
||||
<view class="uni-corpper-crop-box" @touchstart.stop="contentStartMove"
|
||||
@touchmove.stop="contentMoveing" @touchend.stop="contentTouchEnd" :style="
|
||||
'left:' + cutL + 'px;top:' + cutT + 'px;right:' + cutR + 'px;bottom:' + cutB + 'px'
|
||||
">
|
||||
<view class="uni-cropper-view-box">
|
||||
<view class="uni-cropper-dashed-h"></view>
|
||||
<view class="uni-cropper-dashed-v"></view>
|
||||
|
||||
<!-- 截图区域顶部 -->
|
||||
<view class="uni-cropper-line-t" data-drag="top" @touchstart.stop="dragStart"
|
||||
@touchmove.stop="dragMove"></view>
|
||||
|
||||
<!-- 截图区域右侧 -->
|
||||
<view class="uni-cropper-line-r" data-drag="right" @touchstart.stop="dragStart"
|
||||
@touchmove.stop="dragMove"></view>
|
||||
|
||||
<!-- 截图区域底部 -->
|
||||
<view class="uni-cropper-line-b" data-drag="bottom" @touchstart.stop="dragStart"
|
||||
@touchmove.stop="dragMove"></view>
|
||||
|
||||
<!-- 截图区域左侧 -->
|
||||
<view class="uni-cropper-line-l" data-drag="left" @touchstart.stop="dragStart"
|
||||
@touchmove.stop="dragMove"></view>
|
||||
|
||||
<!-- 右下角截图滑块 -->
|
||||
<view class="uni-cropper-point point-t" data-drag="top" @touchstart.stop="dragStart"
|
||||
@touchmove.stop="dragMove"></view>
|
||||
<view class="uni-cropper-point point-tr" data-drag="topTight"></view>
|
||||
<view class="uni-cropper-point point-r" data-drag="right" @touchstart.stop="dragStart"
|
||||
@touchmove.stop="dragMove"></view>
|
||||
<view class="uni-cropper-point point-rb" data-drag="rightBottom"
|
||||
@touchstart.stop="dragStart" @touchmove.stop="dragMove"></view>
|
||||
<view class="uni-cropper-point point-b" data-drag="bottom" @touchstart.stop="dragStart"
|
||||
@touchmove.stop="dragMove" @touchend.stop="dragEnd"></view>
|
||||
<view class="uni-cropper-point point-bl" data-drag="bottomLeft"></view>
|
||||
<view class="uni-cropper-point point-l" data-drag="left" @touchstart.stop="dragStart"
|
||||
@touchmove.stop="dragMove"></view>
|
||||
<view class="uni-cropper-point point-lt" data-drag="leftTop"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="cropper-config">
|
||||
<view class="button-box">
|
||||
<button type="warn" @click="chooseImage">重选</button>
|
||||
<button type="warn" @click="previewImg">预览</button>
|
||||
<button type="warn" @click="finish">完成</button>
|
||||
</view>
|
||||
</view>
|
||||
<canvas canvas-id="myCanvas" :style="
|
||||
'position:absolute;border: 2px solid red; width:' +
|
||||
imageW +
|
||||
'px;height:' +
|
||||
imageH +
|
||||
'px;top:-9999px;left:-9999px;'
|
||||
"></canvas>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
let sysInfo = uni.getSystemInfoSync();
|
||||
let SCREEN_WIDTH = sysInfo.screenWidth;
|
||||
let SCREEN_HEIGHT = sysInfo.windowHeight + 40;
|
||||
let PAGE_X, // 手按下的x位置
|
||||
PAGE_Y, // 手按下y的位置
|
||||
PR = sysInfo.pixelRatio, // dpi
|
||||
T_PAGE_X, // 手移动的时候x的位置
|
||||
T_PAGE_Y, // 手移动的时候Y的位置
|
||||
CUT_L, // 初始化拖拽元素的left值
|
||||
CUT_T, // 初始化拖拽元素的top值
|
||||
CUT_R, // 初始化拖拽元素的
|
||||
CUT_B, // 初始化拖拽元素的
|
||||
CUT_W, // 初始化拖拽元素的宽度
|
||||
CUT_H, // 初始化拖拽元素的高度
|
||||
IMG_RATIO, // 图片比例
|
||||
IMG_REAL_W, // 图片实际的宽度
|
||||
IMG_REAL_H, // 图片实际的高度
|
||||
DRAFG_MOVE_RATIO = 1, //移动时候的比例,
|
||||
INIT_DRAG_POSITION = 100, // 初始化屏幕宽度和裁剪区域的宽度之差,用于设置初始化裁剪的宽度
|
||||
DRAW_IMAGE_W = sysInfo.screenWidth; // 设置生成的图片宽度
|
||||
export default {
|
||||
/**
|
||||
* 页面的初始数据
|
||||
*/
|
||||
data() {
|
||||
return {
|
||||
imageSrc: "",
|
||||
isShow: false,
|
||||
isShowImg: false,
|
||||
// 初始化的宽高
|
||||
cropperInitW: SCREEN_WIDTH,
|
||||
cropperInitH: SCREEN_HEIGHT,
|
||||
// 动态的宽高
|
||||
cropperW: SCREEN_WIDTH,
|
||||
cropperH: SCREEN_HEIGHT - 100,
|
||||
// 动态的left top值
|
||||
cropperL: 0,
|
||||
cropperT: 0,
|
||||
transL: 0,
|
||||
transT: 0,
|
||||
// 图片缩放值
|
||||
scaleP: 0,
|
||||
imageW: 0,
|
||||
imageH: 0,
|
||||
// 裁剪框 宽高
|
||||
cutL: 0,
|
||||
cutT: 0,
|
||||
cutB: SCREEN_WIDTH,
|
||||
cutR: "100%",
|
||||
qualityWidth: DRAW_IMAGE_W,
|
||||
innerAspectRadio: DRAFG_MOVE_RATIO,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
/* 截图质量,压缩比 */
|
||||
quality: {
|
||||
type: Number | String,
|
||||
default: 1,
|
||||
},
|
||||
|
||||
|
||||
src: String,
|
||||
|
||||
fileType: {
|
||||
type: String,
|
||||
default: "png",
|
||||
validator: function(t) {
|
||||
// 这个值必须匹配下列字符串中的一个
|
||||
return t === "png" || t === "jpg";
|
||||
},
|
||||
},
|
||||
|
||||
/* 截取类型,自由截取free;固定比例截取(正方形)fixed */
|
||||
mode: {
|
||||
type: String,
|
||||
default: "free",
|
||||
validator: function(t) {
|
||||
// 这个值必须匹配下列字符串中的一个
|
||||
return t === "free" || t === "fixed" || t === "scale";
|
||||
},
|
||||
},
|
||||
scale: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
},
|
||||
created() {
|
||||
if (this.src) {
|
||||
this.imageSrc = this.src;
|
||||
this.loadImage();
|
||||
this.isShow = true;
|
||||
this.isShowImg = true;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setData: function(obj) {
|
||||
let that = this;
|
||||
Object.keys(obj).forEach(function(key) {
|
||||
that.$set(that.$data, key, obj[key]);
|
||||
});
|
||||
},
|
||||
|
||||
/* 选择图片 */
|
||||
chooseImage: function() {
|
||||
var _this = this;
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
success: function(res) {
|
||||
if(res.tempFiles[0].size / 1024 < 1024) {
|
||||
_this.setData({
|
||||
imageSrc: res.tempFilePaths[0],
|
||||
});
|
||||
_this.loadImage();
|
||||
_this.isShow = true;
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '图片大小不能超过1024KB,当前大小' + (res.tempFiles[0].size / 1024).toFixed(2) + 'KB',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
/* 将图片加载到画布 */
|
||||
loadImage: function() {
|
||||
var _this = this;
|
||||
uni.showLoading({
|
||||
title: "图片加载中...",
|
||||
});
|
||||
|
||||
/* 获取图片信息 */
|
||||
uni.getImageInfo({
|
||||
src: _this.imageSrc,
|
||||
success: function success(res) {
|
||||
let imgH = res.height;
|
||||
let imgW = res.width;
|
||||
|
||||
// let IMG_SCR_H_R = SCREEN_HEIGHT / imgH;
|
||||
// let IMG_SCR_W_R = SCREEN_WIDTH / imgW;
|
||||
|
||||
/* 图片的宽高比 */
|
||||
IMG_RATIO = imgW / imgH;
|
||||
|
||||
/**
|
||||
* 如果图片更高一些,为确保图片能够完整在视窗内显示需如下处理
|
||||
* 1. 缩放图片的高为 视窗高度减去底部菜单按钮高度(120)
|
||||
* 2. 根据图片缩放后的高度,根据图片宽高比计算图片的宽度
|
||||
* 3. 如果步骤2计算的图片宽度大于屏幕宽度,则需要再次调整图片宽度为视窗宽度-margin(10)
|
||||
* 4. 根据步骤3的宽度,结合图片宽高比重新计算图片的高度
|
||||
*/
|
||||
if (IMG_RATIO < 1 && (SCREEN_HEIGHT - 120) * IMG_RATIO < SCREEN_WIDTH - 10) {
|
||||
IMG_REAL_W = (SCREEN_HEIGHT - 120) * IMG_RATIO;
|
||||
IMG_REAL_H = SCREEN_HEIGHT - 120;
|
||||
} else {
|
||||
IMG_REAL_W = SCREEN_WIDTH - 10;
|
||||
IMG_REAL_H = IMG_REAL_W / IMG_RATIO;
|
||||
}
|
||||
|
||||
/* 初始化裁剪区域的位置和形状 */
|
||||
let [cutT, cutB, cutL, cutR] = _this.initCutArea(IMG_RATIO, IMG_REAL_H,
|
||||
IMG_REAL_W);
|
||||
|
||||
_this.setData({
|
||||
/* 裁剪区域的宽高同图片尺寸 */
|
||||
cropperW: IMG_REAL_W,
|
||||
cropperH: IMG_REAL_H,
|
||||
|
||||
/* 上下左右各留一定的margin已便更好的拖动裁剪区域 */
|
||||
cropperL: Math.ceil((SCREEN_WIDTH - IMG_REAL_W) / 2),
|
||||
|
||||
/* 留出底部操作按钮位置 70 */
|
||||
cropperT: Math.ceil((SCREEN_HEIGHT - IMG_REAL_H - 90) / 2),
|
||||
cutL: cutL,
|
||||
cutT: cutT,
|
||||
cutR: cutR,
|
||||
cutB: cutB,
|
||||
// 图片缩放值
|
||||
imageW: IMG_REAL_W,
|
||||
imageH: IMG_REAL_H,
|
||||
scaleP: IMG_REAL_W / SCREEN_WIDTH,
|
||||
qualityWidth: DRAW_IMAGE_W,
|
||||
innerAspectRadio: IMG_RATIO,
|
||||
});
|
||||
_this.setData({
|
||||
isShowImg: true,
|
||||
});
|
||||
uni.hideLoading();
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
initCutArea(IMG_RATIO, IMG_REAL_H, IMG_REAL_W) {
|
||||
let _this = this;
|
||||
/* 自由裁剪裁剪区域默认覆盖整个图片 */
|
||||
let cutT = 0;
|
||||
let cutB = 0;
|
||||
let cutL = 0;
|
||||
let cutR = 0;
|
||||
|
||||
/* 正方形裁剪,初始化裁剪区域为正方形并居中 */
|
||||
if (_this.mode == "fixed") {
|
||||
if (IMG_RATIO < 1) {
|
||||
/* 图片比较高 */
|
||||
cutT = (IMG_REAL_H - IMG_REAL_W) / 2;
|
||||
cutB = (IMG_REAL_H - IMG_REAL_W) / 2;
|
||||
} else {
|
||||
/* 图片比较宽 */
|
||||
cutL = (IMG_REAL_W - IMG_REAL_H) / 2;
|
||||
cutR = (IMG_REAL_W - IMG_REAL_H) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
/* 固定比例裁剪,初始化裁剪区域比例和设定值相同 */
|
||||
if (_this.mode == "scale") {
|
||||
let scale = +_this.scale;
|
||||
if (IMG_RATIO < 1) {
|
||||
/* 图片比较高 */
|
||||
if (IMG_REAL_W / scale > IMG_REAL_H) {
|
||||
cutT = cutB = 0;
|
||||
cutL = cutR = (IMG_REAL_W - IMG_REAL_H * scale) / 2;
|
||||
} else {
|
||||
cutR = cutL = 0;
|
||||
cutT = cutB = (IMG_REAL_H - IMG_REAL_W / scale) / 2;
|
||||
}
|
||||
} else {
|
||||
/* 图片比较宽 */
|
||||
if (IMG_REAL_H * scale > IMG_REAL_W) {
|
||||
cutL = cutR = 0;
|
||||
cutB = cutT = (IMG_REAL_H - IMG_REAL_W / scale) / 2;
|
||||
} else {
|
||||
cutT = cutB = 0;
|
||||
cutL = cutR = (IMG_REAL_W - IMG_REAL_H * scale) / 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [cutT, cutB, cutL, cutR];
|
||||
},
|
||||
// 拖动时候触发的touchStart事件
|
||||
contentStartMove(e) {
|
||||
PAGE_X = e.touches[0].pageX;
|
||||
PAGE_Y = e.touches[0].pageY;
|
||||
},
|
||||
// 拖动时候触发的touchMove事件
|
||||
contentMoveing(e) {
|
||||
var _this = this;
|
||||
var dragLengthX = (PAGE_X - e.touches[0].pageX) * DRAFG_MOVE_RATIO;
|
||||
var dragLengthY = (PAGE_Y - e.touches[0].pageY) * DRAFG_MOVE_RATIO;
|
||||
// 左移
|
||||
if (dragLengthX > 0) {
|
||||
if (this.cutL - dragLengthX < 0) dragLengthX = this.cutL;
|
||||
} else {
|
||||
if (this.cutR + dragLengthX < 0) dragLengthX = -this.cutR;
|
||||
}
|
||||
if (dragLengthY > 0) {
|
||||
if (this.cutT - dragLengthY < 0) dragLengthY = this.cutT;
|
||||
} else {
|
||||
if (this.cutB + dragLengthY < 0) dragLengthY = -this.cutB;
|
||||
}
|
||||
this.setData({
|
||||
cutL: this.cutL - dragLengthX,
|
||||
cutT: this.cutT - dragLengthY,
|
||||
cutR: this.cutR + dragLengthX,
|
||||
cutB: this.cutB + dragLengthY,
|
||||
});
|
||||
PAGE_X = e.touches[0].pageX;
|
||||
PAGE_Y = e.touches[0].pageY;
|
||||
},
|
||||
contentTouchEnd() {},
|
||||
|
||||
// 获取图片尺寸信息
|
||||
previewImg() {
|
||||
try {
|
||||
var _this = this;
|
||||
uni.showLoading({
|
||||
title: "图片生成中...",
|
||||
});
|
||||
// 将图片写入画布
|
||||
const ctx = uni.createCanvasContext("myCanvas", _this);
|
||||
ctx.drawImage(_this.imageSrc, 0, 0, IMG_REAL_W, IMG_REAL_H);
|
||||
ctx.draw(true, () => {
|
||||
// 获取画布要裁剪的位置和宽度 均为百分比 * 画布中图片的宽度 保证了在微信小程序中裁剪的图片模糊 位置不对的问题 canvasT = (_this.cutT / _this.cropperH) * (_this.imageH / pixelRatio)
|
||||
var canvasW = ((_this.cropperW - _this.cutL - _this.cutR) / _this.cropperW) * IMG_REAL_W;
|
||||
var canvasH = ((_this.cropperH - _this.cutT - _this.cutB) / _this.cropperH) * IMG_REAL_H;
|
||||
var canvasL = (_this.cutL / _this.cropperW) * IMG_REAL_W;
|
||||
var canvasT = (_this.cutT / _this.cropperH) * IMG_REAL_H;
|
||||
uni.canvasToTempFilePath({
|
||||
x: canvasL,
|
||||
y: canvasT,
|
||||
width: canvasW,
|
||||
height: canvasH,
|
||||
// destWidth: canvasW,
|
||||
// destHeight: canvasH,
|
||||
quality: +this.quality,
|
||||
fileType: this.fileType,
|
||||
canvasId: "myCanvas",
|
||||
success: function(res) {
|
||||
uni.hideLoading();
|
||||
// 成功获得地址的地方
|
||||
uni.previewImage({
|
||||
current: "", // 当前显示图片的http链接
|
||||
urls: [res.tempFilePath], // 需要预览的图片http链接列表
|
||||
});
|
||||
},
|
||||
fail: function(err) {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: "图片截取失败!",
|
||||
icon: "none",
|
||||
});
|
||||
},
|
||||
},
|
||||
_this
|
||||
);
|
||||
});
|
||||
} catch (e) {}
|
||||
},
|
||||
|
||||
/* 完成裁剪,输出裁剪后的图片路径 */
|
||||
finish: function() {
|
||||
var _this = this;
|
||||
uni.showLoading({
|
||||
title: "图片生成中...",
|
||||
});
|
||||
// 将图片写入画布
|
||||
const ctx = uni.createCanvasContext("myCanvas", _this);
|
||||
ctx.drawImage(_this.imageSrc, 0, 0, IMG_REAL_W, IMG_REAL_H);
|
||||
ctx.draw(true, () => {
|
||||
// 获取画布要裁剪的位置和宽度 均为百分比 * 画布中图片的宽度 保证了在微信小程序中裁剪的图片模糊 位置不对的问题 canvasT = (_this.cutT / _this.cropperH) * (_this.imageH / pixelRatio)
|
||||
var canvasW = ((_this.cropperW - _this.cutL - _this.cutR) / _this.cropperW) * IMG_REAL_W;
|
||||
var canvasH = ((_this.cropperH - _this.cutT - _this.cutB) / _this.cropperH) * IMG_REAL_H;
|
||||
var canvasL = (_this.cutL / _this.cropperW) * IMG_REAL_W;
|
||||
var canvasT = (_this.cutT / _this.cropperH) * IMG_REAL_H;
|
||||
console.log(canvasW,canvasH,'canvasHcanvasHcanvasHcanvasH')
|
||||
uni.canvasToTempFilePath({
|
||||
x: canvasL,
|
||||
y: canvasT,
|
||||
// width: canvasW,
|
||||
// height: canvasH,
|
||||
width: 800,
|
||||
height: 960,
|
||||
destWidth: 800,
|
||||
destHeight: 960,
|
||||
quality: +this.quality,
|
||||
// fileType: this.fileType,
|
||||
fileType: 'jpg',
|
||||
canvasId: "myCanvas",
|
||||
success: function(res) {
|
||||
uni.hideLoading();
|
||||
console.log(res,'resresres')
|
||||
// 成功获得地址的地方
|
||||
_this.$emit("uploadImg", res.tempFilePath);
|
||||
_this.isShow = false;
|
||||
},
|
||||
fail: function(err) {
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: "图片截取失败!",
|
||||
icon: "none",
|
||||
});
|
||||
},
|
||||
},
|
||||
_this
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
// 设置大小的时候触发的touchStart事件
|
||||
dragStart(e) {
|
||||
T_PAGE_X = e.touches[0].pageX;
|
||||
T_PAGE_Y = e.touches[0].pageY;
|
||||
CUT_L = this.cutL;
|
||||
CUT_R = this.cutR;
|
||||
CUT_B = this.cutB;
|
||||
CUT_T = this.cutT;
|
||||
},
|
||||
|
||||
// 设置大小的时候触发的touchMove事件
|
||||
dragMove(e) {
|
||||
// this.mode == "fixed" ? this.fixedScaleDrag(e) : this.freeDrag(e);
|
||||
this[this.mode + "Drag"](e);
|
||||
},
|
||||
|
||||
/* 固定比例(正方形)截取 ,只有右下角裁剪滑动拖动有效*/
|
||||
fixedDrag(e) {
|
||||
var _this = this;
|
||||
var dragType = e.target.dataset.drag;
|
||||
switch (dragType) {
|
||||
case "rightBottom":
|
||||
var dragLengthX = (T_PAGE_X - e.touches[0].pageX) * DRAFG_MOVE_RATIO;
|
||||
// var dragLengthY = (T_PAGE_Y - e.touches[0].pageY) * DRAFG_MOVE_RATIO;
|
||||
// if (CUT_B + dragLengthY < 0) dragLengthY = -CUT_B;
|
||||
|
||||
if (CUT_R + dragLengthX < 0) dragLengthX = -CUT_R;
|
||||
|
||||
/* 右侧和底部同比变化 */
|
||||
let cutB = CUT_B + dragLengthX;
|
||||
let cutR = CUT_R + dragLengthX;
|
||||
|
||||
/* 越界判断 */
|
||||
if (_this.cutB == 0 && cutB < 0) return;
|
||||
if (_this.cutR == 0 && cutR < 0) return;
|
||||
|
||||
(_this.cutB > 0 || CUT_B == 0) &&
|
||||
this.setData({
|
||||
cutB: cutB < 0 ? 0 : cutB,
|
||||
cutR: cutR,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/* 等比例截图,只能通过右下角的滑块改变截图区域 */
|
||||
scaleDrag(e) {
|
||||
var _this = this;
|
||||
var dragType = e.target.dataset.drag;
|
||||
switch (dragType) {
|
||||
case "rightBottom":
|
||||
var dragLengthX = (T_PAGE_X - e.touches[0].pageX) * DRAFG_MOVE_RATIO;
|
||||
// var dragLengthY = (T_PAGE_Y - e.touches[0].pageY) * DRAFG_MOVE_RATIO;
|
||||
// if (CUT_B + dragLengthY < 0) dragLengthY = -CUT_B;
|
||||
|
||||
if (CUT_R + dragLengthX < 0) dragLengthX = -CUT_R;
|
||||
|
||||
/* 右侧和底部同比变化 */
|
||||
let cutB = CUT_B + dragLengthX / _this.scale;
|
||||
let cutR = CUT_R + dragLengthX;
|
||||
|
||||
/* 越界判断 */
|
||||
if (_this.cutB == 0 && cutB < 0) return;
|
||||
if (_this.cutR == 0 && cutR < 0) return;
|
||||
|
||||
(_this.cutB > 0 || CUT_B == 0) &&
|
||||
this.setData({
|
||||
cutB: cutB < 0 ? 0 : cutB,
|
||||
cutR: cutR,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/* 自由截取,整个裁剪边框均能拖动 */
|
||||
freeDrag(e) {
|
||||
var _this = this;
|
||||
var dragType = e.target.dataset.drag;
|
||||
switch (dragType) {
|
||||
case "right":
|
||||
var dragLength = (T_PAGE_X - e.touches[0].pageX) * DRAFG_MOVE_RATIO;
|
||||
if (CUT_R + dragLength < 0) dragLength = -CUT_R;
|
||||
this.setData({
|
||||
cutR: CUT_R + dragLength,
|
||||
});
|
||||
break;
|
||||
case "left":
|
||||
var dragLength = (T_PAGE_X - e.touches[0].pageX) * DRAFG_MOVE_RATIO;
|
||||
if (CUT_L - dragLength < 0) dragLength = CUT_L;
|
||||
if (CUT_L - dragLength > this.cropperW - this.cutR)
|
||||
dragLength = CUT_L - (this.cropperW - this.cutR);
|
||||
this.setData({
|
||||
cutL: CUT_L - dragLength,
|
||||
});
|
||||
break;
|
||||
case "top":
|
||||
var dragLength = (T_PAGE_Y - e.touches[0].pageY) * DRAFG_MOVE_RATIO;
|
||||
if (CUT_T - dragLength < 0) dragLength = CUT_T;
|
||||
if (CUT_T - dragLength > this.cropperH - this.cutB)
|
||||
dragLength = CUT_T - (this.cropperH - this.cutB);
|
||||
this.setData({
|
||||
cutT: CUT_T - dragLength,
|
||||
});
|
||||
break;
|
||||
case "bottom":
|
||||
var dragLength = (T_PAGE_Y - e.touches[0].pageY) * DRAFG_MOVE_RATIO;
|
||||
if (CUT_B + dragLength < 0) dragLength = -CUT_B;
|
||||
this.setData({
|
||||
cutB: CUT_B + dragLength,
|
||||
});
|
||||
break;
|
||||
case "rightBottom":
|
||||
var dragLengthX = (T_PAGE_X - e.touches[0].pageX) * DRAFG_MOVE_RATIO;
|
||||
var dragLengthY = (T_PAGE_Y - e.touches[0].pageY) * DRAFG_MOVE_RATIO;
|
||||
if (CUT_B + dragLengthY < 0) dragLengthY = -CUT_B;
|
||||
if (CUT_R + dragLengthX < 0) dragLengthX = -CUT_R;
|
||||
let cutB = CUT_B + dragLengthY;
|
||||
let cutR = CUT_R + dragLengthX;
|
||||
this.setData({
|
||||
cutB: cutB,
|
||||
cutR: cutR,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* pages/uni-cropper/index.wxss */
|
||||
.yhdsl {
|
||||
background-color: #fff;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 50px;
|
||||
display: block;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
z-index: 998;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.cropper-config {
|
||||
position: fixed;
|
||||
z-index: 999;
|
||||
bottom: 10px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
width: 90%;
|
||||
margin: 0 auto;
|
||||
/* padding: 20upx 40upx; */
|
||||
}
|
||||
|
||||
.button-box {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.button-box button {
|
||||
width: 25%;
|
||||
line-height: 35px;
|
||||
height: 35px;
|
||||
background-color: #509cff;
|
||||
}
|
||||
|
||||
.cropper-content {
|
||||
width: 100%;
|
||||
min-height: 750upx;
|
||||
}
|
||||
|
||||
.uni-corpper {
|
||||
position: relative;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
box-sizing: border-box;
|
||||
margin: 0 auto;
|
||||
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
-webkit-touch-callout: none;
|
||||
}
|
||||
|
||||
.uni-corpper-content {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.uni-corpper-content image {
|
||||
display: block;
|
||||
|
||||
width: 100%;
|
||||
min-width: 0 !important;
|
||||
max-width: none !important;
|
||||
/* height: 100%; */
|
||||
min-height: 0 !important;
|
||||
/* max-height: none !important; */
|
||||
|
||||
max-height: calc(100vh - 100upx);
|
||||
margin: 0 auto;
|
||||
|
||||
image-orientation: 0deg !important;
|
||||
}
|
||||
|
||||
/* 移动图片效果 */
|
||||
.uni-cropper-drag-box {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
|
||||
cursor: move;
|
||||
|
||||
background: rgba(0, 0, 0, 0.479);
|
||||
}
|
||||
|
||||
/* 内部的信息 */
|
||||
.uni-corpper-crop-box {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
max-height: calc(100vh - 100upx);
|
||||
background: rgba(56, 50, 50, 0.479);
|
||||
}
|
||||
|
||||
.uni-corpper-crop-box .uni-cropper-view-box {
|
||||
position: relative;
|
||||
|
||||
display: block;
|
||||
overflow: visible;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
max-height: calc(100vh - 100upx);
|
||||
outline: 5upx solid rgb(43, 89, 255);
|
||||
outline-color: rgba(255, 255 255, 1);
|
||||
}
|
||||
|
||||
/* 横向虚线 */
|
||||
.uni-cropper-dashed-h {
|
||||
position: absolute;
|
||||
top: 33.33333333%;
|
||||
left: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 33.33333333%;
|
||||
|
||||
border-top: 1upx dashed rgba(255, 255, 255, 0.5);
|
||||
border-bottom: 1upx dashed rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
/* 纵向虚线 */
|
||||
.uni-cropper-dashed-v {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 33.33333333%;
|
||||
|
||||
width: 33.33333333%;
|
||||
height: 100%;
|
||||
|
||||
border-right: 1upx dashed rgba(255, 255, 255, 0.5);
|
||||
border-left: 1upx dashed rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
/* 四个方向的线 为了之后的拖动事件*/
|
||||
.uni-cropper-line-t {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
display: block;
|
||||
|
||||
width: 100%;
|
||||
height: 3upx;
|
||||
|
||||
cursor: n-resize;
|
||||
|
||||
opacity: 0.1;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.uni-cropper-line-t::before {
|
||||
position: absolute;
|
||||
z-index: 11;
|
||||
top: 50%;
|
||||
right: 0upx;
|
||||
bottom: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 41upx;
|
||||
|
||||
content: "";
|
||||
-webkit-transform: translate3d(0, -50%, 0);
|
||||
transform: translate3d(0, -50%, 0);
|
||||
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.uni-cropper-line-r {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0upx;
|
||||
|
||||
display: block;
|
||||
|
||||
width: 3upx;
|
||||
height: 100%;
|
||||
|
||||
cursor: e-resize;
|
||||
|
||||
opacity: 0.1;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.uni-cropper-line-r::before {
|
||||
position: absolute;
|
||||
z-index: 11;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
|
||||
width: 41upx;
|
||||
height: 100%;
|
||||
|
||||
content: "";
|
||||
-webkit-transform: translate3d(-50%, 0, 0);
|
||||
transform: translate3d(-50%, 0, 0);
|
||||
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.uni-cropper-line-b {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
|
||||
display: block;
|
||||
|
||||
width: 100%;
|
||||
height: 3upx;
|
||||
|
||||
cursor: s-resize;
|
||||
|
||||
opacity: 0.1;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.uni-cropper-line-b::before {
|
||||
position: absolute;
|
||||
z-index: 11;
|
||||
top: 50%;
|
||||
right: 0upx;
|
||||
bottom: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 41upx;
|
||||
|
||||
content: "";
|
||||
-webkit-transform: translate3d(0, -50%, 0);
|
||||
transform: translate3d(0, -50%, 0);
|
||||
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.uni-cropper-line-l {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
display: block;
|
||||
|
||||
width: 3upx;
|
||||
height: 100%;
|
||||
|
||||
cursor: w-resize;
|
||||
|
||||
opacity: 0.1;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.uni-cropper-line-l::before {
|
||||
position: absolute;
|
||||
z-index: 11;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
|
||||
width: 41upx;
|
||||
height: 100%;
|
||||
|
||||
content: "";
|
||||
-webkit-transform: translate3d(-50%, 0, 0);
|
||||
transform: translate3d(-50%, 0, 0);
|
||||
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.uni-cropper-point {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
|
||||
width: 5upx;
|
||||
height: 5upx;
|
||||
|
||||
opacity: 0.75;
|
||||
background-color: rgb(145, 132, 132);
|
||||
}
|
||||
|
||||
.point-t {
|
||||
top: -3upx;
|
||||
left: 50%;
|
||||
|
||||
margin-left: -3upx;
|
||||
|
||||
cursor: n-resize;
|
||||
}
|
||||
|
||||
.point-tr {
|
||||
top: -3upx;
|
||||
left: 100%;
|
||||
|
||||
margin-left: -3upx;
|
||||
|
||||
cursor: n-resize;
|
||||
}
|
||||
|
||||
.point-r {
|
||||
top: 50%;
|
||||
left: 100%;
|
||||
|
||||
margin-top: -3upx;
|
||||
margin-left: -3upx;
|
||||
|
||||
cursor: n-resize;
|
||||
}
|
||||
|
||||
.point-rb {
|
||||
position: absolute;
|
||||
z-index: 1112;
|
||||
top: 100%;
|
||||
left: 100%;
|
||||
|
||||
width: 30upx;
|
||||
height: 30upx;
|
||||
border-radius: 50%;
|
||||
|
||||
cursor: n-resize;
|
||||
-webkit-transform: translate3d(-50%, -50%, 0);
|
||||
transform: translate3d(-50%, -50%, 0);
|
||||
|
||||
opacity: 1;
|
||||
background-color: rgb(231, 222, 222);
|
||||
background-color: #509cff;
|
||||
}
|
||||
|
||||
.point-b {
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
|
||||
margin-top: -3upx;
|
||||
margin-left: -3upx;
|
||||
|
||||
cursor: n-resize;
|
||||
}
|
||||
|
||||
.point-bl {
|
||||
top: 100%;
|
||||
left: 0;
|
||||
|
||||
margin-top: -3upx;
|
||||
margin-left: -3upx;
|
||||
|
||||
cursor: n-resize;
|
||||
}
|
||||
|
||||
.point-l {
|
||||
top: 50%;
|
||||
left: 0;
|
||||
|
||||
margin-top: -3upx;
|
||||
margin-left: -3upx;
|
||||
|
||||
cursor: n-resize;
|
||||
}
|
||||
|
||||
.point-lt {
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
margin-top: -3upx;
|
||||
margin-left: -3upx;
|
||||
|
||||
cursor: n-resize;
|
||||
}
|
||||
|
||||
/* 裁剪框预览内容 */
|
||||
.uni-cropper-viewer {
|
||||
position: relative;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.uni-cropper-viewer image {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user