feat:转账插件

This commit is contained in:
2026-07-23 14:31:06 +08:00
parent 4916546322
commit d5a6a16ea5
560 changed files with 71777 additions and 44216 deletions
@@ -0,0 +1,36 @@
/*
* @Author : uview-plus
* @Description : calendarStrip default props
* @version : 3.0
* @Date : 2026-05-29 14:40:00
*/
import { t } from '../../libs/i18n'
export default {
calendarStrip: {
modelValue: null,
minDate: 0,
maxDate: 0,
color: '#3c9cff',
weekText: [
t('up.week.one'),
t('up.week.two'),
t('up.week.three'),
t('up.week.four'),
t('up.week.five'),
t('up.week.six'),
t('up.week.seven')
],
fullCalendar: true,
fullCalendarProps: {},
fullMonthNum: 24,
pullDownThreshold: 40,
collapseAfterSelect: true,
readonly: false,
showToday: true,
monthFormat: '',
expandHint: '下拉展开月历',
collapseHint: '上拉收起月历'
}
}
@@ -0,0 +1,70 @@
import { defineMixin } from '../../libs/vue'
import CalendarStripDefaultProps from './calendarStrip'
import { registerComponentProps } from '../../libs/config/props.js'
const defProps = registerComponentProps(CalendarStripDefaultProps)
export const props = defineMixin({
props: {
modelValue: {
type: [String, Number, Date, null],
default: () => defProps.calendarStrip.modelValue
},
minDate: {
type: [String, Number],
default: () => defProps.calendarStrip.minDate
},
maxDate: {
type: [String, Number],
default: () => defProps.calendarStrip.maxDate
},
color: {
type: String,
default: () => defProps.calendarStrip.color
},
weekText: {
type: Array,
default: () => defProps.calendarStrip.weekText
},
fullCalendar: {
type: Boolean,
default: () => defProps.calendarStrip.fullCalendar
},
fullCalendarProps: {
type: Object,
default: () => defProps.calendarStrip.fullCalendarProps
},
fullMonthNum: {
type: [String, Number],
default: () => defProps.calendarStrip.fullMonthNum
},
pullDownThreshold: {
type: [String, Number],
default: () => defProps.calendarStrip.pullDownThreshold
},
collapseAfterSelect: {
type: Boolean,
default: () => defProps.calendarStrip.collapseAfterSelect
},
readonly: {
type: Boolean,
default: () => defProps.calendarStrip.readonly
},
showToday: {
type: Boolean,
default: () => defProps.calendarStrip.showToday
},
monthFormat: {
type: String,
default: () => defProps.calendarStrip.monthFormat
},
expandHint: {
type: String,
default: () => defProps.calendarStrip.expandHint
},
collapseHint: {
type: String,
default: () => defProps.calendarStrip.collapseHint
}
}
})
@@ -0,0 +1,507 @@
<template>
<view class="u-calendar-strip">
<view v-if="!fullCalendar || !innerShowFull">
<view class="u-calendar-strip__header u-border-bottom">
<text
class="u-calendar-strip__header__switch"
:class="{ 'u-calendar-strip__header__switch--disabled': switchPrevDisabled }"
@tap="prevMonth"
>‹</text>
<text class="u-calendar-strip__header__title">{{ monthLabel }}</text>
<text
class="u-calendar-strip__header__switch"
:class="{ 'u-calendar-strip__header__switch--disabled': switchNextDisabled }"
@tap="nextMonth"
>›</text>
<text
v-if="fullCalendar"
class="u-calendar-strip__header__toggle"
@click="toggleFull('button')"
>▾</text>
</view>
<view
class="u-calendar-strip__scroll-wrap"
@touchstart="onTouchStart"
@touchend="onTouchEnd"
>
<scroll-view
class="u-calendar-strip__scroll"
scroll-x
enable-flex
:show-scrollbar="false"
:scroll-into-view="scrollIntoView"
>
<view class="u-calendar-strip__scroll__inner">
<view
v-for="(item, index) in monthDays"
:key="index"
class="u-calendar-strip__day"
:id="getDateId(item.date)"
:class="[
item.disabled && 'u-calendar-strip__day--disabled',
item.selected && 'u-calendar-strip__day--selected',
item.today && showToday && !item.selected && 'u-calendar-strip__day--today',
]"
:style="[dayStyle(item)]"
@tap="onDayTap(item)"
>
<text class="u-calendar-strip__day__date">{{ item.day }}</text>
<text class="u-calendar-strip__day__week">{{ getWeekLabel(item.week) }}</text>
</view>
</view>
</scroll-view>
</view>
</view>
<view
v-if="fullCalendar && innerShowFull"
class="u-calendar-strip__panel-wrap"
@touchstart="onTouchStart"
@touchend="onTouchEnd"
>
<view class="u-calendar-strip__panel">
<u-calendar
:show="true"
:pageInline="true"
:showTitle="false"
:showConfirm="false"
:closeOnClickOverlay="false"
:monthSwitch="true"
mode="single"
:defaultDate="innerSelectedDate"
:minDate="panelMinDate"
:maxDate="panelMaxDate"
:monthNum="panelMonthNum"
:readonly="readonly"
:showToday="showToday"
:color="color"
v-bind="fullCalendarProps"
@confirm="onPanelConfirm"
></u-calendar>
</view>
<view class="u-calendar-strip__hint u-calendar-strip__hint--panel">
<text class="u-calendar-strip__hint__text" @click="toggleFull('hint')">{{ collapseHint }}</text>
</view>
</view>
</view>
</template>
<script>
import { props } from './props'
import { mpMixin } from '../../libs/mixin/mpMixin'
import { mixin } from '../../libs/mixin/mixin'
import dayjs from '../u-datetime-picker/dayjs.esm.min.js'
import test from '../../libs/function/test'
/**
* CalendarStrip 单行日历
* @description 单行横向日期日历,支持切月、下拉展开完整月历
*/
export default {
name: 'u-calendar-strip',
mixins: [mpMixin, mixin, props],
data() {
return {
innerSelectedDate: '',
currentMonth: '',
scrollIntoView: '',
innerShowFull: false,
touchStartX: 0,
touchStartY: 0
}
},
computed: {
innerMaxDate() {
return test.number(this.maxDate) ? Number(this.maxDate) : this.maxDate
},
innerMinDate() {
return test.number(this.minDate) ? Number(this.minDate) : this.minDate
},
rangeChange() {
return [this.innerMinDate, this.innerMaxDate]
},
hasMaxDate() {
return !!this.innerMaxDate && dayjs(this.innerMaxDate).isValid()
},
hasMinDate() {
return !!this.innerMinDate && dayjs(this.innerMinDate).isValid()
},
minDateDay() {
if (!this.hasMinDate) return ''
return dayjs(this.innerMinDate).format('YYYY-MM-DD')
},
maxDateDay() {
if (!this.hasMaxDate) return ''
return dayjs(this.innerMaxDate).format('YYYY-MM-DD')
},
todayDate() {
return dayjs().format('YYYY-MM-DD')
},
monthLabel() {
if (!this.currentMonth) return ''
const date = dayjs(`${this.currentMonth}-01`)
if (this.monthFormat) return date.format(this.monthFormat)
if (uni.getLocale() == 'zh-Hans' || uni.getLocale() == 'zh-Hant') {
return date.format('YYYY年MM月')
}
return date.format('MM/YYYY')
},
monthDays() {
if (!this.currentMonth) return []
const start = dayjs(`${this.currentMonth}-01`)
const days = start.daysInMonth()
return new Array(days).fill(0).map((item, index) => {
const date = start.date(index + 1).format('YYYY-MM-DD')
return {
day: index + 1,
date,
week: start.date(index + 1).day(),
disabled: this.isDateDisabled(date),
selected: this.dateSame(date, this.innerSelectedDate),
today: this.dateSame(date, this.todayDate)
}
})
},
switchPrevDisabled() {
if (!this.hasMinDate || !this.currentMonth) return false
const current = dayjs(`${this.currentMonth}-01`)
const minMonth = dayjs(this.minDateDay).startOf('month')
return current.isSame(minMonth, 'month') || current.isBefore(minMonth, 'month')
},
switchNextDisabled() {
if (!this.hasMaxDate || !this.currentMonth) return false
const current = dayjs(`${this.currentMonth}-01`)
const maxMonth = dayjs(this.maxDateDay).startOf('month')
return current.isSame(maxMonth, 'month') || current.isAfter(maxMonth, 'month')
},
panelMinDate() {
if (this.hasMinDate) return this.minDateDay
const monthNum = Math.max(1, Number(this.fullMonthNum) || 24)
const anchor = this.currentMonth || dayjs().format('YYYY-MM')
return dayjs(`${anchor}-01`).subtract(monthNum - 1, 'month').startOf('month').format('YYYY-MM-DD')
},
panelMaxDate() {
if (this.hasMaxDate) return this.maxDateDay
const monthNum = Math.max(1, Number(this.fullMonthNum) || 24)
const anchor = this.currentMonth || dayjs().format('YYYY-MM')
return dayjs(`${anchor}-01`).add(monthNum - 1, 'month').endOf('month').format('YYYY-MM-DD')
},
panelMonthNum() {
return this.getMonths(this.panelMinDate, this.panelMaxDate)
},
pullHintText() {
return this.innerShowFull ? this.collapseHint : this.expandHint
}
},
watch: {
modelValue: {
immediate: true,
handler(n) {
this.syncByValue(n, false, 'sync')
}
},
rangeChange() {
this.syncByValue(this.innerSelectedDate || this.modelValue, true, 'range')
}
},
emits: ['change', 'confirm', 'monthChange', 'toggleFull', 'update:modelValue'],
methods: {
dateSame(date1, date2) {
if (!date1 || !date2) return false
return dayjs(date1).isSame(dayjs(date2), 'day')
},
normalizeDate(value) {
if (!value) return ''
const parsed = dayjs(value)
if (!parsed.isValid()) return ''
return parsed.format('YYYY-MM-DD')
},
clampDate(date) {
let next = this.normalizeDate(date)
if (!next) return ''
if (this.hasMinDate && dayjs(next).isBefore(dayjs(this.minDateDay), 'day')) {
next = this.minDateDay
}
if (this.hasMaxDate && dayjs(next).isAfter(dayjs(this.maxDateDay), 'day')) {
next = this.maxDateDay
}
return next
},
isDateDisabled(date) {
if (!date) return true
if (this.hasMinDate && dayjs(date).isBefore(dayjs(this.minDateDay), 'day')) {
return true
}
if (this.hasMaxDate && dayjs(date).isAfter(dayjs(this.maxDateDay), 'day')) {
return true
}
return false
},
getDateId(date) {
return `u-calendar-strip-day-${dayjs(date).format('YYYYMMDD')}`
},
getWeekLabel(week) {
const index = week === 0 ? 6 : week - 1
return this.weekText[index] || ''
},
dayStyle(item) {
const style = {}
if (item.selected) {
style.backgroundColor = this.color
}
if (!item.selected && item.today && this.showToday) {
style.borderColor = this.color
}
return style
},
scrollToDate(date) {
if (!date) {
this.scrollIntoView = ''
return
}
const target = this.getDateId(date)
this.scrollIntoView = ''
this.$nextTick(() => {
this.scrollIntoView = target
})
},
syncByValue(value, emit = false, scene = 'sync') {
let next = this.clampDate(value)
if (!next) {
next = this.clampDate(this.todayDate)
}
if (!next) return
this.setSelectedDate(next, scene, emit)
},
setSelectedDate(date, scene = 'tap', emit = true) {
const next = this.clampDate(date)
if (!next || this.isDateDisabled(next)) return
const prevDate = this.innerSelectedDate
const prevMonth = this.currentMonth
this.innerSelectedDate = next
this.currentMonth = dayjs(next).format('YYYY-MM')
this.scrollToDate(next)
if (!emit) return
const payload = {
date: next,
month: this.currentMonth,
scene
}
if (!this.dateSame(prevDate, next)) {
this.$emit('update:modelValue', next)
}
this.$emit('change', payload)
this.$emit('confirm', payload)
if (prevMonth !== this.currentMonth) {
this.$emit('monthChange', {
month: this.currentMonth,
scene
})
}
},
getMonths(minDate, maxDate) {
const minYear = dayjs(minDate).year()
const minMonth = dayjs(minDate).month() + 1
const maxYear = dayjs(maxDate).year()
const maxMonth = dayjs(maxDate).month() + 1
return Math.max(1, (maxYear - minYear) * 12 + (maxMonth - minMonth) + 1)
},
findFirstEnabledDate(month) {
const start = dayjs(`${month}-01`)
const days = start.daysInMonth()
for (let i = 1; i <= days; i++) {
const date = start.date(i).format('YYYY-MM-DD')
if (!this.isDateDisabled(date)) {
return date
}
}
return ''
},
switchMonth(step = 0) {
if (step < 0 && this.switchPrevDisabled) return
if (step > 0 && this.switchNextDisabled) return
const baseMonth = this.currentMonth || dayjs(this.todayDate).format('YYYY-MM')
const target = dayjs(`${baseMonth}-01`).add(step, 'month')
const targetMonth = target.format('YYYY-MM')
const selectedDay = dayjs(this.innerSelectedDate || this.todayDate).date()
const dayInTargetMonth = Math.min(selectedDay, target.daysInMonth())
let next = target.date(dayInTargetMonth).format('YYYY-MM-DD')
next = this.clampDate(next)
if (!next || !dayjs(next).isSame(target, 'month') || this.isDateDisabled(next)) {
next = this.findFirstEnabledDate(targetMonth)
}
if (!next) return
this.setSelectedDate(next, 'switch', true)
},
prevMonth() {
this.switchMonth(-1)
},
nextMonth() {
this.switchMonth(1)
},
onDayTap(item) {
if (this.readonly || item.disabled) return
this.setSelectedDate(item.date, 'tap', true)
},
setFullVisible(show, source = 'button') {
if (!this.fullCalendar) return
if (this.innerShowFull === show) return
this.innerShowFull = show
this.$emit('toggleFull', {
show,
source
})
},
toggleFull(source = 'button') {
this.setFullVisible(!this.innerShowFull, source)
},
onPanelConfirm(e) {
if (!Array.isArray(e) || !e.length) return
this.setSelectedDate(e[0], 'full', true)
if (this.collapseAfterSelect) {
this.setFullVisible(false, 'auto')
}
},
onTouchStart(event) {
if (!this.fullCalendar) return
const point = event?.changedTouches?.[0] || event?.touches?.[0]
if (!point) return
this.touchStartX = point.clientX
this.touchStartY = point.clientY
},
onTouchEnd(event) {
if (!this.fullCalendar) return
const point = event?.changedTouches?.[0] || event?.touches?.[0]
if (!point) return
const deltaX = point.clientX - this.touchStartX
const deltaY = point.clientY - this.touchStartY
const threshold = Number(this.pullDownThreshold) || 40
if (Math.abs(deltaY) < threshold || Math.abs(deltaY) <= Math.abs(deltaX)) {
return
}
if (deltaY > 0 && !this.innerShowFull) {
this.setFullVisible(true, 'pull-down')
}
if (deltaY < 0 && this.innerShowFull) {
this.setFullVisible(false, 'pull-up')
}
}
}
}
</script>
<style lang="scss" scoped>
.u-calendar-strip {
background-color: var(--up-card-bg-color, #ffffff);
border-radius: 10px;
margin: 0 8px;
&__header {
@include flex;
align-items: center;
padding: 0 4px;
height: 40px;
&__switch {
width: 40px;
text-align: center;
font-size: 20px;
line-height: 40px;
color: var(--up-main-color, $u-main-color);
&--disabled {
opacity: 0.35;
}
}
&__title {
flex: 1;
text-align: center;
font-size: 14px;
font-weight: bold;
color: var(--up-main-color, $u-main-color);
}
&__toggle {
width: 40px;
text-align: center;
font-size: 18px;
line-height: 40px;
color: var(--up-content-color, $u-content-color);
}
}
&__scroll-wrap {
padding: 10px 0 6px;
}
&__scroll {
&__inner {
@include flex;
padding: 0 8px;
}
}
&__day {
width: 54px;
height: 62px;
margin-right: 8px;
border-radius: 8px;
border: 1px solid transparent;
@include flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex-shrink: 0;
&__date {
font-size: 18px;
line-height: 22px;
color: var(--up-main-color, $u-main-color);
}
&__week {
margin-top: 4px;
font-size: 12px;
line-height: 16px;
color: var(--up-content-color, $u-content-color);
}
&--selected {
.u-calendar-strip__day__date,
.u-calendar-strip__day__week {
color: #ffffff;
}
}
&--today {
border-style: solid;
}
&--disabled {
opacity: 0.4;
}
}
&__hint {
padding: 4px 0 10px;
text-align: center;
&__text {
font-size: 12px;
color: var(--up-tips-color, $u-tips-color);
}
}
&__panel {
padding: 0 8px 8px;
}
&__panel-wrap {
padding-top: 4px;
}
&__hint--panel {
padding: 0 0 8px;
}
}
</style>