| | |
| | | 'electricBillRecharge/electricBillRecharge', |
| | | 'selectPayType/selectPayType', |
| | | 'rechargeResult/rechargeResult', |
| | | 'rechargeElectricResult/rechargeElectricResult', |
| | | ], |
| | | }, |
| | | { |
New file |
| | |
| | | <template> |
| | | <view class="nut-tab-pane" v-if="shouldBeRender" :style="paneStyle"> |
| | | <slot></slot> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { CSSProperties, inject, ref, computed, watch } from 'vue'; |
| | | |
| | | defineOptions({ |
| | | name: 'ProTabPane', |
| | | }); |
| | | |
| | | const props = defineProps({ |
| | | title: { |
| | | type: [String, Number], |
| | | default: '', |
| | | }, |
| | | subTitle: { |
| | | type: [String, Number], |
| | | default: '', |
| | | }, |
| | | paneKey: { |
| | | type: [String, Number], |
| | | default: '', |
| | | }, |
| | | disabled: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | lazy: { |
| | | type: Boolean, |
| | | default: true, |
| | | }, |
| | | badgeValue: { |
| | | type: Number, |
| | | default: 0, |
| | | }, |
| | | }); |
| | | |
| | | const parentOption = inject('tabsOpiton') as any; |
| | | |
| | | const paneStyle = computed(() => { |
| | | return { |
| | | display: |
| | | parentOption.animatedTime.value === 0 && props.paneKey !== parentOption.activeKey.value |
| | | ? 'none' |
| | | : undefined, |
| | | } as CSSProperties; |
| | | }); |
| | | |
| | | const active = computed(() => props.paneKey === parentOption.activeKey.value); |
| | | |
| | | const loaded = ref(active.value); |
| | | |
| | | const shouldBeRender = computed(() => !props.lazy || loaded.value || active.value); |
| | | |
| | | watch(active, (val) => { |
| | | if (val) loaded.value = true; |
| | | }); |
| | | </script> |
New file |
| | |
| | | <template> |
| | | <view |
| | | ref="container" |
| | | class="nut-tabs" |
| | | :class="[direction, { fullHeight, noContent: !showPaneContent, flexTitle }]" |
| | | > |
| | | <div :class="['pro-tabs__titles_wrapper', { isTransparent }]"> |
| | | <scroll-view |
| | | :id="`nut-tabs__titles_${name}`" |
| | | :scroll-x="getScrollX" |
| | | :scroll-y="getScrollY" |
| | | :scroll-with-animation="true" |
| | | :scroll-left="scrollLeft" |
| | | :scroll-top="scrollTop" |
| | | :enable-flex="true" |
| | | class="nut-tabs__titles tabs-scrollview" |
| | | :class="{ |
| | | [type]: type, |
| | | scrollable: titleScroll, |
| | | 'scroll-vertical': getScrollY, |
| | | [size]: size, |
| | | }" |
| | | :style="tabsNavStyle" |
| | | enhanced |
| | | :show-scrollbar="false" |
| | | @scroll="onScroll" |
| | | > |
| | | <view class="nut-tabs__list"> |
| | | <slot v-if="$slots.titles" name="titles"></slot> |
| | | <template v-else> |
| | | <view |
| | | v-for="(item, index) in titles" |
| | | :key="item.paneKey" |
| | | class="nut-tabs__titles-item taro" |
| | | :style="titleStyle" |
| | | :class="{ active: item.paneKey == modelValue, disabled: item.disabled }" |
| | | @click="tabMethods.tabChange(item, index)" |
| | | > |
| | | <view |
| | | v-if="type == 'line'" |
| | | class="nut-tabs__titles-item__line" |
| | | :style="tabsActiveStyle" |
| | | ></view> |
| | | <view |
| | | v-if="type == 'smile'" |
| | | class="nut-tabs__titles-item__smile" |
| | | :style="tabsActiveStyle" |
| | | > |
| | | <JoySmile :color="color" /> |
| | | </view> |
| | | <nut-badge |
| | | right="-12" |
| | | top="4" |
| | | :value="item.badgeValue" |
| | | :max="99" |
| | | :hidden="!item.badgeValue" |
| | | color="#FC0B0B" |
| | | > |
| | | <view class="nut-tabs__titles-item__text" :class="{ ellipsis: ellipsis }" |
| | | >{{ item.title }} |
| | | </view> |
| | | </nut-badge> |
| | | <view class="nut-tabs__sub-titles-item__text" :class="{ ellipsis: ellipsis }" |
| | | >{{ item.subTitle }} |
| | | </view> |
| | | </view> |
| | | <view |
| | | v-if="canShowLabel" |
| | | class="nut-tabs__titles-item nut-tabs__titles-placeholder" |
| | | ></view> |
| | | </template> |
| | | </view> |
| | | </scroll-view> |
| | | <slot name="right"></slot> |
| | | </div> |
| | | |
| | | <view |
| | | :id="'tabsContentRef-' + refRandomId" |
| | | ref="tabsContentRef" |
| | | class="nut-tabs__content" |
| | | :style="contentStyle" |
| | | @touchstart="touchMethods.onTouchStart" |
| | | @touchmove="touchMethods.onTouchMove" |
| | | @touchend="touchMethods.onTouchEnd" |
| | | @touchcancel="touchMethods.onTouchEnd" |
| | | v-show="showPaneContent" |
| | | > |
| | | <slot name="default"></slot> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { JoySmile } from '@nutui/icons-vue-taro'; |
| | | import { tabsProps, Title, TypeOfFun, RectItem, raf, pxCheck, useTabContentTouch } from './tabs'; |
| | | import Taro from '@tarojs/taro'; |
| | | import { |
| | | CSSProperties, |
| | | useSlots, |
| | | computed, |
| | | ref, |
| | | onMounted, |
| | | provide, |
| | | VNode, |
| | | Ref, |
| | | nextTick, |
| | | watch, |
| | | onActivated, |
| | | } from 'vue'; |
| | | import { useTaroRect, useScrollDistance } from 'senin-mini/hooks'; |
| | | |
| | | defineOptions({ |
| | | name: 'ProTabs', |
| | | }); |
| | | |
| | | const props = defineProps(tabsProps); |
| | | |
| | | const emit = defineEmits<{ |
| | | (e: 'update:modelValue', value: string | number): void; |
| | | (e: 'click', item: Title): void; |
| | | (e: 'change', item: Title): void; |
| | | }>(); |
| | | |
| | | const slots = useSlots(); |
| | | |
| | | const container = ref(null); |
| | | provide('tabsOpiton', { |
| | | activeKey: computed(() => props.modelValue || '0'), |
| | | autoHeight: computed(() => props.autoHeight), |
| | | animatedTime: computed(() => props.animatedTime), |
| | | }); |
| | | const titles: Ref<Title[]> = ref([]); |
| | | const renderTitles = (vnodes: VNode[]) => { |
| | | vnodes.forEach((vnode: VNode, index: number) => { |
| | | let type = vnode.type; |
| | | type = (type as any).name || type; |
| | | if (type === 'nut-tab-pane' || type === 'ProTabPane') { |
| | | let title = new Title(); |
| | | if (vnode.props?.title || vnode.props?.['pane-key'] || vnode.props?.['paneKey']) { |
| | | let paneKeyType = TypeOfFun(vnode.props?.['pane-key']); |
| | | let paneIndex = |
| | | paneKeyType === 'number' || paneKeyType === 'string' |
| | | ? String(vnode.props?.['pane-key']) |
| | | : null; |
| | | let camelPaneKeyType = TypeOfFun(vnode.props?.['paneKey']); |
| | | let camelPaneIndex = |
| | | camelPaneKeyType === 'number' || camelPaneKeyType === 'string' |
| | | ? String(vnode.props?.['paneKey']) |
| | | : null; |
| | | title.title = vnode.props?.title; |
| | | title.subTitle = vnode.props?.subTitle; |
| | | title.badgeValue = vnode.props?.badgeValue; |
| | | title.paneKey = paneIndex || camelPaneIndex || String(index); |
| | | title.disabled = vnode.props?.disabled; |
| | | } else { |
| | | // title.titleSlot = vnode.children?.title() as VNode[]; |
| | | } |
| | | titles.value.push(title); |
| | | } else { |
| | | if (vnode.children === ' ') { |
| | | return; |
| | | } |
| | | renderTitles(vnode.children as VNode[]); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | const currentIndex = ref((props.modelValue as number) || 0); |
| | | const findTabsIndex = (value: string | number) => { |
| | | let index = titles.value.findIndex((item) => item.paneKey === value); |
| | | if (titles.value.length === 0) { |
| | | // console.warn('[NutUI] <Tabs> 当前未找到 TabPane 组件元素 , 请检查 .'); |
| | | } else if (index === -1) { |
| | | // console.warn('[NutUI] <Tabs> 请检查 v-model 值是否为 paneKey ,如 paneKey 未设置,请采用下标控制 .'); |
| | | } else { |
| | | currentIndex.value = index; |
| | | } |
| | | }; |
| | | const getScrollX = computed(() => { |
| | | return props.titleScroll && props.direction === 'horizontal'; |
| | | }); |
| | | const getScrollY = computed(() => { |
| | | return props.titleScroll && props.direction === 'vertical'; |
| | | }); |
| | | const titleRef = ref([]) as Ref<HTMLElement[]>; |
| | | // const scrollLeft = ref(0); |
| | | const scrollTop = ref(0); |
| | | const scrollWithAnimation = ref(false); |
| | | const getRect = (selector: string) => { |
| | | return new Promise((resolve) => { |
| | | Taro.createSelectorQuery() |
| | | .select(selector) |
| | | .boundingClientRect() |
| | | .exec((rect = []) => { |
| | | resolve(rect[0]); |
| | | }); |
| | | }); |
| | | }; |
| | | const getAllRect = (selector: string) => { |
| | | return new Promise((resolve) => { |
| | | Taro.createSelectorQuery() |
| | | .selectAll(selector) |
| | | .boundingClientRect() |
| | | .exec((rect = []) => resolve(rect[0])); |
| | | }); |
| | | }; |
| | | const navRectRef = ref(); |
| | | const titleRectRef = ref<RectItem[]>([]); |
| | | const canShowLabel = ref(false); |
| | | const scrollIntoView = () => { |
| | | if (!props.name) return; |
| | | |
| | | raf(() => { |
| | | Promise.all([ |
| | | getRect(`#nut-tabs__titles_${props.name}`), |
| | | getAllRect(`#nut-tabs__titles_${props.name} .nut-tabs__titles-item`), |
| | | ]).then(([navRect, titleRects]: any) => { |
| | | navRectRef.value = navRect; |
| | | titleRectRef.value = titleRects; |
| | | |
| | | if (navRectRef.value) { |
| | | if (props.direction === 'vertical') { |
| | | const titlesTotalHeight = titleRects.reduce( |
| | | (prev: number, curr: RectItem) => prev + curr.height, |
| | | 0 |
| | | ); |
| | | if (titlesTotalHeight > navRectRef.value.height) { |
| | | canShowLabel.value = true; |
| | | } else { |
| | | canShowLabel.value = false; |
| | | } |
| | | } else { |
| | | const titlesTotalWidth = titleRects.reduce( |
| | | (prev: number, curr: RectItem) => prev + curr.width, |
| | | 0 |
| | | ); |
| | | if (titlesTotalWidth > navRectRef.value.width) { |
| | | canShowLabel.value = true; |
| | | } else { |
| | | canShowLabel.value = false; |
| | | } |
| | | } |
| | | } |
| | | |
| | | const titleRect: RectItem = titleRectRef.value[currentIndex.value]; |
| | | |
| | | let to = 0; |
| | | if (props.direction === 'vertical') { |
| | | const DEFAULT_PADDING = 11; |
| | | const top = titleRects |
| | | .slice(0, currentIndex.value) |
| | | .reduce((prev: number, curr: RectItem) => prev + curr.height + 0, DEFAULT_PADDING); |
| | | to = top - (navRectRef.value.height - titleRect.height) / 2; |
| | | } else { |
| | | const DEFAULT_PADDING = 31; |
| | | const left = titleRects |
| | | .slice(0, currentIndex.value) |
| | | .reduce((prev: number, curr: RectItem) => prev + curr.width + 20, DEFAULT_PADDING); |
| | | to = left - (navRectRef.value?.width ?? 0 - titleRect?.width ?? 0) / 2; |
| | | } |
| | | |
| | | nextTick(() => { |
| | | scrollWithAnimation.value = true; |
| | | }); |
| | | |
| | | scrollDirection(to, props.direction); |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | const { |
| | | onScroll, |
| | | scrollDistance: scrollLeft, |
| | | setScrollDistance: setScrollLeft, |
| | | } = useScrollDistance({ direction: 'horizontal' }); |
| | | |
| | | const scrollDirection = (to: number, direction: 'horizontal' | 'vertical') => { |
| | | let count = 0; |
| | | const from = direction === 'horizontal' ? scrollLeft.value : scrollTop.value; |
| | | const frames = 1; |
| | | |
| | | function animate() { |
| | | if (direction === 'horizontal') { |
| | | scrollLeft.value = Math.max(0.1, to); |
| | | } else { |
| | | scrollTop.value += (to - from) / frames; |
| | | } |
| | | |
| | | if (++count < frames) { |
| | | raf(animate); |
| | | } |
| | | } |
| | | |
| | | if (direction === 'horizontal') { |
| | | setScrollLeft(Math.max(0.1, to)); |
| | | } else { |
| | | animate(); |
| | | } |
| | | }; |
| | | const init = (vnodes: VNode[] = slots.default?.()) => { |
| | | titles.value = []; |
| | | vnodes = vnodes?.filter((item) => typeof item.children !== 'string'); |
| | | if (vnodes && vnodes.length) { |
| | | renderTitles(vnodes); |
| | | } |
| | | findTabsIndex(props.modelValue); |
| | | setTimeout(() => { |
| | | scrollIntoView(); |
| | | }, 500); |
| | | }; |
| | | |
| | | watch( |
| | | () => slots.default?.(), |
| | | (vnodes: VNode[]) => { |
| | | init(vnodes); |
| | | } |
| | | ); |
| | | watch( |
| | | () => props.modelValue, |
| | | (value: string | number) => { |
| | | findTabsIndex(value); |
| | | setTimeout(() => { |
| | | scrollIntoView(); |
| | | }, 500); |
| | | } |
| | | ); |
| | | onMounted(init); |
| | | onActivated(init); |
| | | const tabMethods = { |
| | | isBegin: () => { |
| | | return currentIndex.value === 0; |
| | | }, |
| | | isEnd: () => { |
| | | return currentIndex.value === titles.value.length - 1; |
| | | }, |
| | | next: () => { |
| | | currentIndex.value += 1; |
| | | tabMethods.updateValue(titles.value[currentIndex.value]); |
| | | }, |
| | | prev: () => { |
| | | currentIndex.value -= 1; |
| | | tabMethods.updateValue(titles.value[currentIndex.value]); |
| | | }, |
| | | updateValue: (item: Title) => { |
| | | emit('update:modelValue', item.paneKey); |
| | | emit('change', item); |
| | | }, |
| | | tabChange: (item: Title, index: number) => { |
| | | emit('click', item); |
| | | if (item.disabled || currentIndex.value === index) { |
| | | return; |
| | | } |
| | | currentIndex.value = index; |
| | | tabMethods.updateValue(item); |
| | | }, |
| | | setTabItemRef: (el: HTMLElement, index: number) => { |
| | | titleRef.value[index] = el; |
| | | }, |
| | | }; |
| | | const { tabsContentRef, touchState, touchMethods } = useTabContentTouch( |
| | | props, |
| | | tabMethods, |
| | | Taro, |
| | | useTaroRect |
| | | ); |
| | | const contentStyle = computed(() => { |
| | | let offsetPercent = currentIndex.value * 100; |
| | | if (touchState.moving) { |
| | | offsetPercent += touchState.offset; |
| | | } |
| | | let style: CSSProperties = { |
| | | transform: |
| | | props.direction === 'horizontal' |
| | | ? `translate3d(-${offsetPercent}%, 0, 0)` |
| | | : `translate3d( 0,-${offsetPercent}%, 0)`, |
| | | transitionDuration: touchState.moving ? undefined : `${props.animatedTime}ms`, |
| | | }; |
| | | if (props.animatedTime === 0) { |
| | | style = {}; |
| | | } |
| | | return style; |
| | | }); |
| | | const tabsNavStyle = computed(() => { |
| | | return { |
| | | background: props.background, |
| | | }; |
| | | }); |
| | | const tabsActiveStyle = computed(() => { |
| | | return { |
| | | color: props.type === 'smile' ? props.color : '', |
| | | background: props.type === 'line' ? props.color : '', |
| | | }; |
| | | }); |
| | | const titleStyle = computed(() => { |
| | | if (!props.titleGutter) return {}; |
| | | const px = pxCheck(props.titleGutter); |
| | | if (props.direction === 'vertical') { |
| | | return { marginTop: px, marginBottom: px }; |
| | | } |
| | | return { marginLeft: px, marginRight: px }; |
| | | }); |
| | | const refRandomId = Math.random().toString(36).slice(-8); |
| | | </script> |
| | | |
| | | <style lang="scss"> |
| | | @import '@/styles/common.scss'; |
| | | |
| | | .pro-tabs__titles_wrapper { |
| | | display: flex; |
| | | align-items: center; |
| | | background-color: #fff; |
| | | position: relative; |
| | | z-index: 10; |
| | | box-shadow: none; |
| | | padding: 0 boleGetCssVar('size', 'body-padding-h'); |
| | | margin-bottom: 8px; |
| | | |
| | | &.isTransparent { |
| | | background-color: transparent; |
| | | margin-bottom: 0; |
| | | } |
| | | } |
| | | |
| | | .nut-tabs.noContent { |
| | | box-shadow: none; |
| | | position: relative; |
| | | z-index: 9; |
| | | } |
| | | |
| | | .flexTitle { |
| | | box-shadow: none; |
| | | position: relative; |
| | | z-index: 9; |
| | | |
| | | .pro-tabs__titles_wrapper { |
| | | padding: 0; |
| | | |
| | | .nut-tabs__titles.tabs-scrollview .nut-tabs__list .nut-tabs__titles-item { |
| | | flex: 1; |
| | | margin: 0; |
| | | min-width: 0; |
| | | flex-shrink: 0; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .nut-tabs__titles.tabs-scrollview { |
| | | background-color: transparent; |
| | | flex: 1; |
| | | min-width: 0; |
| | | box-sizing: border-box; |
| | | height: 104rpx; |
| | | @include hiddenScrollBar; |
| | | |
| | | .nut-tabs__list { |
| | | height: 104rpx; |
| | | justify-content: flex-start; |
| | | |
| | | .nut-tabs__titles-item { |
| | | flex: none; |
| | | width: auto; |
| | | margin-right: 78rpx; |
| | | flex-direction: column; |
| | | font-size: 26rpx; |
| | | color: boleGetCssVar('text-color', 'secondary'); |
| | | align-items: center; |
| | | transition: all 0.3s ease; |
| | | min-width: 0; |
| | | |
| | | .nut-tabs__sub-titles-item__text { |
| | | font-size: 20rpx; |
| | | color: boleGetCssVar('text-color', 'regular'); |
| | | } |
| | | |
| | | .nut-tabs__titles-item__text { |
| | | font-size: 28rpx; |
| | | color: boleGetCssVar('text-color', 'regular'); |
| | | line-height: 40rpx; |
| | | } |
| | | |
| | | &.active { |
| | | color: #222; |
| | | font-weight: 700; |
| | | font-size: 30rpx; |
| | | |
| | | .nut-tabs__sub-titles-item__text { |
| | | color: boleGetCssVar('color', 'primary'); |
| | | } |
| | | |
| | | .nut-tabs__titles-item__line { |
| | | width: 100%; |
| | | } |
| | | |
| | | .nut-tabs__titles-item__text { |
| | | color: boleGetCssVar('text-color', 'primary'); |
| | | } |
| | | } |
| | | |
| | | &:last-child { |
| | | margin-right: 0; |
| | | } |
| | | |
| | | &:first-child { |
| | | margin-left: 0 !important; |
| | | } |
| | | |
| | | .nut-tabs__titles-item__line { |
| | | // width: 100%; |
| | | background: boleGetCssVar('color', 'primary'); |
| | | bottom: 0; |
| | | height: 4rpx; |
| | | max-width: 40rpx; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .nut-tabs.fullHeight { |
| | | height: 100%; |
| | | display: flex; |
| | | flex-direction: column; |
| | | |
| | | .nut-tabs__content { |
| | | flex: 1; |
| | | min-height: 0; |
| | | |
| | | .nut-tab-pane { |
| | | padding: 0; |
| | | background-color: transparent; |
| | | overflow: hidden; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | } |
| | | } |
| | | </style> |
New file |
| | |
| | | import { ref, PropType, onMounted, reactive } from 'vue'; |
| | | |
| | | export type TabsSize = 'large' | 'normal' | 'small'; |
| | | |
| | | export class Title { |
| | | title = ''; |
| | | subTitle = ''; |
| | | //@ts-ignore |
| | | titleSlot?: VNode[]; |
| | | paneKey = ''; |
| | | disabled = false; |
| | | badgeValue = 0; |
| | | constructor() {} |
| | | } |
| | | |
| | | export const tabsProps = { |
| | | modelValue: { |
| | | type: [String, Number], |
| | | default: 0, |
| | | }, |
| | | color: { |
| | | type: String, |
| | | default: '', |
| | | }, |
| | | direction: { |
| | | type: String as PropType<'horizontal' | 'vertical'>, |
| | | default: 'horizontal', //vertical |
| | | }, |
| | | size: { |
| | | type: String as PropType<TabsSize>, |
| | | default: 'normal', |
| | | }, |
| | | type: { |
| | | type: String, |
| | | default: 'smile', //card、line、smile |
| | | }, |
| | | titleScroll: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | ellipsis: { |
| | | type: Boolean, |
| | | default: true, |
| | | }, |
| | | swipeable: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | autoHeight: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | background: { |
| | | type: String, |
| | | default: '', |
| | | }, |
| | | animatedTime: { |
| | | type: [Number, String], |
| | | default: 0, |
| | | }, |
| | | titleGutter: { |
| | | type: [Number, String], |
| | | default: 0, |
| | | }, |
| | | sticky: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | top: { |
| | | type: Number, |
| | | default: 0, |
| | | }, |
| | | name: { |
| | | type: String, |
| | | default: '', |
| | | }, |
| | | fullHeight: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | showPaneContent: { |
| | | type: Boolean, |
| | | default: true, |
| | | }, |
| | | flexTitle: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | isTransparent: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | }; |
| | | |
| | | export const TypeOfFun = (value: any) => { |
| | | if (null === value) { |
| | | return 'null'; |
| | | } |
| | | |
| | | const type = typeof value; |
| | | if ('undefined' === type || 'string' === type) { |
| | | return type; |
| | | } |
| | | |
| | | const typeString = toString.call(value); |
| | | switch (typeString) { |
| | | case '[object Array]': |
| | | return 'array'; |
| | | case '[object Date]': |
| | | return 'date'; |
| | | case '[object Boolean]': |
| | | return 'boolean'; |
| | | case '[object Number]': |
| | | return 'number'; |
| | | case '[object Function]': |
| | | return 'function'; |
| | | case '[object RegExp]': |
| | | return 'regexp'; |
| | | case '[object Object]': |
| | | if (undefined !== value.nodeType) { |
| | | if (3 === value.nodeType) { |
| | | return /\S/.test(value.nodeValue) ? 'textnode' : 'whitespace'; |
| | | } else { |
| | | return 'element'; |
| | | } |
| | | } else { |
| | | return 'object'; |
| | | } |
| | | default: |
| | | return 'unknow'; |
| | | } |
| | | }; |
| | | |
| | | export type RectItem = { |
| | | bottom: number; |
| | | dataset: { sid: string }; |
| | | height: number; |
| | | id: string; |
| | | left: number; |
| | | right: number; |
| | | top: number; |
| | | width: number; |
| | | }; |
| | | |
| | | const _window = window as any; |
| | | |
| | | export function requestAniFrame() { |
| | | if (typeof _window !== 'undefined') { |
| | | return ( |
| | | _window.requestAnimationFrame || |
| | | _window.webkitRequestAnimationFrame || |
| | | function (callback: Function) { |
| | | _window.setTimeout(callback, 1000 / 60); |
| | | } |
| | | ); |
| | | } else { |
| | | return function (callback: Function) { |
| | | setTimeout(callback, 1000 / 60); |
| | | }; |
| | | } |
| | | } |
| | | |
| | | export const raf = requestAniFrame(); |
| | | |
| | | export const pxCheck = (value: string | number): string => { |
| | | return isNaN(Number(value)) ? String(value) : `${value}px`; |
| | | }; |
| | | |
| | | const MIN_DISTANCE = 10; |
| | | |
| | | type Direction = '' | 'vertical' | 'horizontal'; |
| | | |
| | | function getDirection(x: number, y: number) { |
| | | if (x > y && x > MIN_DISTANCE) { |
| | | return 'horizontal'; |
| | | } |
| | | if (y > x && y > MIN_DISTANCE) { |
| | | return 'vertical'; |
| | | } |
| | | return ''; |
| | | } |
| | | |
| | | type TouchPosition = 'left' | 'right' | 'top' | 'bottom' | ''; |
| | | |
| | | export function useTouch() { |
| | | const startX = ref(0); |
| | | const startY = ref(0); |
| | | const moveX = ref(0); |
| | | const moveY = ref(0); |
| | | const deltaX = ref(0); |
| | | const deltaY = ref(0); |
| | | const offsetX = ref(0); |
| | | const offsetY = ref(0); |
| | | const direction = ref<Direction>(''); |
| | | |
| | | const isVertical = () => direction.value === 'vertical'; |
| | | const isHorizontal = () => direction.value === 'horizontal'; |
| | | |
| | | const reset = () => { |
| | | deltaX.value = 0; |
| | | deltaY.value = 0; |
| | | offsetX.value = 0; |
| | | offsetY.value = 0; |
| | | direction.value = ''; |
| | | }; |
| | | |
| | | const start = (event: TouchEvent) => { |
| | | reset(); |
| | | startX.value = event.touches[0].clientX; |
| | | startY.value = event.touches[0].clientY; |
| | | }; |
| | | |
| | | const move = (event: TouchEvent) => { |
| | | const touch = event.touches[0]; |
| | | deltaX.value = touch.clientX - startX.value; |
| | | deltaY.value = touch.clientY - startY.value; |
| | | moveX.value = touch.clientX; |
| | | moveY.value = touch.clientY; |
| | | offsetX.value = Math.abs(deltaX.value); |
| | | offsetY.value = Math.abs(deltaY.value); |
| | | |
| | | if (!direction.value) { |
| | | direction.value = getDirection(offsetX.value, offsetY.value); |
| | | } |
| | | }; |
| | | |
| | | return { |
| | | move, |
| | | start, |
| | | reset, |
| | | startX, |
| | | startY, |
| | | moveX, |
| | | moveY, |
| | | deltaX, |
| | | deltaY, |
| | | offsetX, |
| | | offsetY, |
| | | direction, |
| | | isVertical, |
| | | isHorizontal, |
| | | }; |
| | | } |
| | | |
| | | export const useTabContentTouch = (props: any, tabMethods: any, taro?: any, useTaroRect?: any) => { |
| | | const tabsContentRef = ref<HTMLElement>(); |
| | | |
| | | const tabsContentRefRect = ref({ width: 0, height: 0 }); |
| | | const initTaroWidth = () => { |
| | | if (taro && taro.getEnv() !== taro.ENV_TYPE.WEB) { |
| | | useTaroRect(tabsContentRef).then( |
| | | (rect: any) => { |
| | | tabsContentRefRect.value.width = rect?.width || 0; |
| | | tabsContentRefRect.value.height = rect?.height || 0; |
| | | }, |
| | | () => {} |
| | | ); |
| | | } else { |
| | | tabsContentRefRect.value.width = tabsContentRef.value?.clientWidth || 0; |
| | | tabsContentRefRect.value.height = tabsContentRef.value?.clientHeight || 0; |
| | | } |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | setTimeout(() => { |
| | | initTaroWidth(); |
| | | }, 100); |
| | | }); |
| | | const touchState = reactive({ |
| | | offset: 0, |
| | | moving: false, |
| | | }); |
| | | const touch = useTouch(); |
| | | let position: TouchPosition = ''; |
| | | const setoffset = (deltaX: number, deltaY: number) => { |
| | | let offset = deltaX; |
| | | if (props.direction === 'horizontal') { |
| | | position = deltaX > 0 ? 'right' : 'left'; |
| | | // 计算拖拽 百分比 |
| | | offset = (Math.abs(offset) / tabsContentRefRect.value.width) * 100; |
| | | } else { |
| | | position = deltaY > 0 ? 'bottom' : 'top'; |
| | | offset = deltaY; |
| | | // 计算拖拽 百分比 |
| | | offset = (Math.abs(offset) / tabsContentRefRect.value?.height) * 100; |
| | | } |
| | | // 拖拽阈值 85% |
| | | if (offset > 85) { |
| | | offset = 85; |
| | | } |
| | | switch (position) { |
| | | case 'left': |
| | | case 'top': |
| | | // 起始tab拖拽拦截 |
| | | if (tabMethods.isEnd()) { |
| | | offset = 0; |
| | | touchState.moving = false; |
| | | } |
| | | break; |
| | | case 'right': |
| | | case 'bottom': |
| | | offset = -offset; |
| | | // 末位tab拖拽拦截 |
| | | if (tabMethods.isBegin()) { |
| | | offset = 0; |
| | | touchState.moving = false; |
| | | } |
| | | break; |
| | | } |
| | | touchState.offset = offset; |
| | | }; |
| | | const touchMethods = { |
| | | onTouchStart(event: TouchEvent) { |
| | | if (!props.swipeable) return; |
| | | touch.start(event); |
| | | }, |
| | | onTouchMove(event: TouchEvent) { |
| | | if (!props.swipeable) return; |
| | | touch.move(event); |
| | | touchState.moving = true; |
| | | setoffset(touch.deltaX.value, touch.deltaY.value); |
| | | |
| | | if (props.direction === 'horizontal' && touch.isHorizontal()) { |
| | | event.preventDefault(); |
| | | event.stopPropagation(); |
| | | } |
| | | if (props.direction === 'vertical' && touch.isVertical()) { |
| | | event.preventDefault(); |
| | | event.stopPropagation(); |
| | | } |
| | | }, |
| | | onTouchEnd() { |
| | | if (touchState.moving) { |
| | | touchState.moving = false; |
| | | switch (position) { |
| | | case 'left': |
| | | case 'top': |
| | | // 大于 35%阈值 切换至下一 Tab |
| | | if (touchState.offset > 35) { |
| | | tabMethods.next(); |
| | | } |
| | | break; |
| | | case 'right': |
| | | case 'bottom': |
| | | if (touchState.offset < -35) { |
| | | tabMethods.prev(); |
| | | } |
| | | break; |
| | | } |
| | | } |
| | | }, |
| | | }; |
| | | return { touchMethods, touchState, tabsContentRef }; |
| | | }; |
| | |
| | | order = '/subpackages/order/order/order', |
| | | selectPayType = '/subpackages/recharge/selectPayType/selectPayType', |
| | | rechargeResult = '/subpackages/recharge/rechargeResult/rechargeResult', |
| | | rechargeElectricResult = '/subpackages/recharge/rechargeElectricResult/rechargeElectricResult', |
| | | } |
| | |
| | | Message.confirm({ message: '请前往登录' }) |
| | | .then(() => { |
| | | Taro.navigateTo({ |
| | | url: `${RouterPath.authorization}?redirect=${url}`, |
| | | url: `${RouterPath.loginByForm}?redirect=${url}`, |
| | | }); |
| | | }) |
| | | .finally(() => { |
| | |
| | | Message.confirm({ message: '请前往登录' }) |
| | | .then(() => { |
| | | Taro.navigateTo({ |
| | | url: `${RouterPath.authorization}?redirect=${RouterPath.home}`, |
| | | url: `${RouterPath.loginByForm}?redirect=${RouterPath.home}`, |
| | | }); |
| | | }) |
| | | .finally(() => { |
| | |
| | | export function useUser() { |
| | | const userStore = useUserStore(); |
| | | |
| | | const { userDetail, userInfo, locationCity, virtualUserId } = storeToRefs(userStore); |
| | | const { userDetail, userInfo, locationCity, virtualUserId, virtualPhoneNumber } = |
| | | storeToRefs(userStore); |
| | | |
| | | function updateUserInfo() { |
| | | return userStore.getCurrentUserInfo(); |
| | |
| | | updateUserInfo, |
| | | locationCity, |
| | | virtualUserId, |
| | | virtualPhoneNumber, |
| | | }; |
| | | } |
| | | |
| | |
| | | } |
| | | if (needAuth && !isLogin.value) { |
| | | Taro.navigateTo({ |
| | | url: `${RouterPath.authorization}?redirect=${router.path}&${object2query(router.params)}`, |
| | | url: `${RouterPath.loginByForm}?redirect=${router.path}&${object2query(router.params)}`, |
| | | }); |
| | | } |
| | | }); |
| | |
| | | <div class="mine-page-top-view" @click="goLogin"> |
| | | <img class="mine-avatar" :src="DefaultAvatar" alt="" /> |
| | | <div class="user-info"> |
| | | <div class="user-info-name" v-if="isLogin">{{ userDetail?.userName ?? '123' }}</div> |
| | | <div class="user-info-name" v-if="isLogin">{{ virtualPhoneNumber }}</div> |
| | | <div class="mine-go-login" v-else>登录</div> |
| | | </div> |
| | | </div> |
| | |
| | | import { useUserStore } from '@/stores/modules/user'; |
| | | import { useUserStoreWithOut } from '@/stores/modules/user'; |
| | | |
| | | const { userDetail } = useUser(); |
| | | const { userDetail, virtualPhoneNumber } = useUser(); |
| | | const isLogin = useIsLogin(); |
| | | const systemStore = useSystemStore(); |
| | | const userStore = useUserStore(); |
| | |
| | | setStorageVirtualUserId, |
| | | getStorageVirtualUserId, |
| | | removeStorageVirtualUserId, |
| | | LoginVirtualRes, |
| | | } from '@/utils/storage/auth'; |
| | | import * as accountServices from '@life-payment/services/api/Account'; |
| | | import * as userServices from '@life-payment/services/api/User'; |
| | |
| | | firstSetLocation?: boolean; |
| | | |
| | | virtualUserId?: string; |
| | | virtualPhoneNumber?: string; |
| | | } |
| | | |
| | | const goAuthorization = debounce( |
| | |
| | | state: (): UserState => { |
| | | const userInfo = getCacheUserInfo(); |
| | | const userDetail = getUserDetail(); |
| | | const storageVirtualUser = getStorageVirtualUserId(); |
| | | |
| | | return { |
| | | // user info |
| | |
| | | userDetail: userDetail, |
| | | firstGetUserDetail: true, |
| | | |
| | | virtualUserId: getStorageVirtualUserId() ?? '', |
| | | virtualUserId: storageVirtualUser?.virtualUserId ?? '', |
| | | virtualPhoneNumber: storageVirtualUser?.virtualPhoneNumber ?? '', |
| | | }; |
| | | }, |
| | | getters: { |
| | |
| | | ); |
| | | |
| | | if (res) { |
| | | this.loginVirtualSuccess(res); |
| | | this.loginVirtualSuccess({ |
| | | virtualUserId: res, |
| | | virtualPhoneNumber: data.phoneNumber, |
| | | }); |
| | | } |
| | | return res; |
| | | }, |
| | |
| | | } catch (error) {} |
| | | }, |
| | | |
| | | async loginVirtualSuccess(virtualUserId: string) { |
| | | async loginVirtualSuccess(virtualUserRes: LoginVirtualRes) { |
| | | try { |
| | | this.setVirtualUserId(virtualUserId); |
| | | this.setVirtualUserId(virtualUserRes); |
| | | } catch (error) {} |
| | | }, |
| | | |
| | | setVirtualUserId(virtualUserId: string) { |
| | | this.virtualUserId = virtualUserId; |
| | | setStorageVirtualUserId(virtualUserId); |
| | | setVirtualUserId(virtualUserRes: LoginVirtualRes) { |
| | | this.virtualUserId = virtualUserRes.virtualUserId; |
| | | this.virtualPhoneNumber = virtualUserRes.virtualPhoneNumber; |
| | | setStorageVirtualUserId(virtualUserRes); |
| | | }, |
| | | |
| | | async wxMiniAppUserLoginFromScan(wxIndentityRes: API.WxMiniAppIndentityInfo, uuid: string) { |
| | |
| | | showLoading: false, |
| | | } |
| | | ); |
| | | userStore.loginVirtualSuccess(res); |
| | | userStore.loginVirtualSuccess({ |
| | | virtualPhoneNumber: form.phoneNumber, |
| | | virtualUserId: res, |
| | | }); |
| | | jump(); |
| | | } |
| | | } else { |
| | |
| | | <template> |
| | | <ContentScrollView :paddingH="false"> |
| | | <Order /> |
| | | </ContentScrollView> |
| | | <ProTabs v-model="orderType" name="user-home-tabs" class="user-home-tabs" flexTitle fullHeight> |
| | | <ProTabPane title="话费订单" pane-key="1"> |
| | | <PhoneOrder /> |
| | | </ProTabPane> |
| | | <ProTabPane title="电费订单" pane-key="2"> |
| | | <ElectricOrder /> |
| | | </ProTabPane> |
| | | </ProTabs> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { Order } from '@life-payment/components'; |
| | | import { PhoneOrder, ElectricOrder } from '@life-payment/components'; |
| | | import Taro from '@tarojs/taro'; |
| | | |
| | | defineOptions({ |
| | | name: 'InnerPage', |
| | | }); |
| | | |
| | | const orderType = ref('1'); |
| | | |
| | | function goPay() { |
| | | Taro.navigateTo({ |
| | | url: RouterPath.selectPayType, |
| | |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { PhoneBillRecharge } from '@life-payment/components'; |
| | | import { PhoneBillRecharge, BlLifeRecharge } from '@life-payment/components'; |
| | | import Taro from '@tarojs/taro'; |
| | | |
| | | defineOptions({ |
| | |
| | | |
| | | function goPay(orderNo: string) { |
| | | Taro.navigateTo({ |
| | | url: `${RouterPath.selectPayType}?orderNo=${orderNo}`, |
| | | url: `${RouterPath.selectPayType}?orderNo=${orderNo}&lifePayOrderType=${BlLifeRecharge.constants.LifePayOrderTypeEnum.话费订单}`, |
| | | }); |
| | | } |
| | | </script> |
New file |
| | |
| | | export default definePageConfig({ |
| | | disableScroll: true, |
| | | }); |
New file |
| | |
| | | <template> |
| | | <PageLayout title="充值成功" class="rechargeElectricResult-page-wrapper" hasBorder> |
| | | <ContentScrollView> |
| | | <RechargeResultView |
| | | style="margin-top: 40px" |
| | | @go-back-home="goHome()" |
| | | title="支付成功,充值款将在0-72小时内到账" |
| | | /> |
| | | </ContentScrollView> |
| | | </PageLayout> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { PageLayout } from '@/components'; |
| | | import { goHome } from '@/utils'; |
| | | import { RechargeResultView } from '@life-payment/components'; |
| | | |
| | | defineOptions({ |
| | | name: 'rechargeElectricResult', |
| | | }); |
| | | </script> |
| | |
| | | export default definePageConfig({ |
| | | disableScroll: true, |
| | | }); |
| | |
| | | <template> |
| | | <ContentScrollView> |
| | | <SelectPayTypeView style="margin-top: 40px" /> |
| | | <SelectPayTypeView style="margin-top: 40px" @paySuccess="handePaySuccess" /> |
| | | </ContentScrollView> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { SelectPayTypeView } from '@life-payment/components'; |
| | | import { SelectPayTypeView, LifeRechargeConstants } from '@life-payment/components'; |
| | | import Taro from '@tarojs/taro'; |
| | | |
| | | defineOptions({ |
| | | name: 'selectPayType', |
| | | }); |
| | | |
| | | function handePaySuccess( |
| | | orderNo: string, |
| | | lifePayOrderType: LifeRechargeConstants.LifePayOrderTypeEnum |
| | | ) { |
| | | Taro.navigateTo({ |
| | | url: `${RouterPath.rechargeResult}?orderNo=${orderNo}&lifePayOrderType=${lifePayOrderType}`, |
| | | }); |
| | | } |
| | | </script> |
| | |
| | | |
| | | export const blLifeRecharge = new BlLifeRecharge({ |
| | | request, |
| | | userId: getStorageVirtualUserId() ?? '', |
| | | userId: getStorageVirtualUserId()?.virtualUserId ?? '', |
| | | phoneNumber: getStorageVirtualUserId()?.virtualPhoneNumber ?? '', |
| | | }); |
| | |
| | | return storageLocal.removeItem(StorageKey.USER_DETAIL_KEY); |
| | | } |
| | | |
| | | export type LoginVirtualRes = { |
| | | virtualUserId: string; |
| | | virtualPhoneNumber: string; |
| | | }; |
| | | |
| | | export function getStorageVirtualUserId() { |
| | | return storageLocal.getItem<string>(StorageKey.VirtualUserId_KEY); |
| | | return storageLocal.getItem<LoginVirtualRes>(StorageKey.VirtualUserId_KEY); |
| | | } |
| | | |
| | | export function setStorageVirtualUserId(virtualUserId: string) { |
| | | return storageLocal.setItem(StorageKey.VirtualUserId_KEY, virtualUserId); |
| | | export function setStorageVirtualUserId(res: LoginVirtualRes) { |
| | | return storageLocal.setItem(StorageKey.VirtualUserId_KEY, res); |
| | | } |
| | | |
| | | export function removeStorageVirtualUserId() { |
| | |
| | | import { FetchNextPageOptions } from '@tanstack/vue-query'; |
| | | import { Loading } from '@nutui/icons-vue-taro'; |
| | | import { useScrollDistance } from 'senin-mini/hooks'; |
| | | import IconBackTop from '@/assets/components/icon-back-top.png'; |
| | | import IconBackTop from '../../../assets/icon-back-top.png'; |
| | | |
| | | defineOptions({ |
| | | name: 'InfiniteLoading', |
New file |
| | |
| | | <template> |
| | | <div v-if="loading" class="loading-layout-loading-content-wrapper"> |
| | | <IconFont name="loading" /> |
| | | <div class="list-empty-hint-text">数据加载中......</div> |
| | | </div> |
| | | <Empty |
| | | v-else-if="error" |
| | | class="loading-layout-error-wrapper" |
| | | status="error" |
| | | description="加载失败" |
| | | > |
| | | <div :style="{ marginTop: '10px' }"> |
| | | <nut-button type="primary" @click="loadError"> 重试 </nut-button> |
| | | </div> |
| | | </Empty> |
| | | <template v-else> |
| | | <NoData v-if="showNoData" /> |
| | | <slot v-else></slot> |
| | | </template> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { loadingLayoutProps } from './layout'; |
| | | import { IconFont } from '@nutui/icons-vue-taro'; |
| | | import NoData from '../NoData/NoData.vue'; |
| | | import { Button as NutButton, Empty } from '@nutui/nutui-taro'; |
| | | |
| | | defineOptions({ |
| | | name: 'LoadingLayout', |
| | | }); |
| | | |
| | | const props = defineProps(loadingLayoutProps); |
| | | </script> |
New file |
| | |
| | | import { PropType } from 'vue'; |
| | | |
| | | export const loadingLayoutProps = { |
| | | loading: { |
| | | type: Boolean, |
| | | }, |
| | | error: { |
| | | type: Boolean, |
| | | }, |
| | | showNoData: { |
| | | type: Boolean, |
| | | }, |
| | | loadError: { |
| | | type: Function as PropType<(...args: any[]) => any>, |
| | | }, |
| | | id: { |
| | | type: String, |
| | | }, |
| | | }; |
New file |
| | |
| | | <template> |
| | | <div class="no-data-wrapper"> |
| | | <img class="no-data-img" :src="NoDataImage" alt="" /> |
| | | <span class="no-data-text">暂无数据</span> |
| | | </div> |
| | | </template> |
| | | |
| | | <script lang="ts"> |
| | | import { defineComponent } from 'vue'; |
| | | |
| | | const NoDataImage = `'https://parkmanagement.oss-cn-hangzhou.aliyuncs.com/mini/assets/no-data.png`; |
| | | |
| | | export default defineComponent({ |
| | | name: 'NoData', |
| | | |
| | | setup() { |
| | | return { |
| | | NoDataImage, |
| | | }; |
| | | }, |
| | | }); |
| | | </script> |
| | |
| | | [IspCode.dianxin]: '中国电信', |
| | | [IspCode.liantong]: '中国联通', |
| | | }; |
| | | |
| | | export enum OrderInputType { |
| | | /** |
| | | * 升序 |
| | | */ |
| | | Asc, |
| | | /** |
| | | * 降序 |
| | | */ |
| | | Desc, |
| | | } |
| | |
| | | LifePayRateListOutput, |
| | | PhoneParValueOutput, |
| | | PhoneParValueResponse, |
| | | QueryLifePayOrderListInput, |
| | | LifeRechargeConstants, |
| | | } from '../utils'; |
| | | import { useQuery } from '@tanstack/vue-query'; |
| | | import { computed } from 'vue'; |
| | | import { computed, MaybeRef, reactive, unref } from 'vue'; |
| | | import { useInfiniteLoading } from './infiniteLoading'; |
| | | import { OrderInputType } from '../constants'; |
| | | |
| | | export function useGetRate() { |
| | | const { blLifeRecharge } = useLifeRechargeContext(); |
| | |
| | | phoneParValueList, |
| | | }; |
| | | } |
| | | |
| | | export type UseGetUserLifePayOrderPageOptions = { |
| | | lifePayOrderType?: MaybeRef<LifeRechargeConstants.LifePayOrderTypeEnum>; |
| | | }; |
| | | |
| | | export function useGetUserLifePayOrderPage(options: UseGetUserLifePayOrderPageOptions = {}) { |
| | | const { lifePayOrderType } = options; |
| | | |
| | | const { blLifeRecharge } = useLifeRechargeContext(); |
| | | |
| | | // const queryState = reactive({ |
| | | // lifePayOrderType: LifeRechargeConstants.LifePayOrderTypeEnum, |
| | | // }); |
| | | |
| | | const { infiniteLoadingProps } = useInfiniteLoading( |
| | | ({ pageParam }) => { |
| | | let params: QueryLifePayOrderListInput = { |
| | | pageModel: { |
| | | rows: 20, |
| | | page: pageParam, |
| | | orderInput: [{ property: 'id', order: OrderInputType.Desc }], |
| | | }, |
| | | lifePayOrderType: unref(lifePayOrderType), |
| | | userId: blLifeRecharge.accountModel.userId, |
| | | }; |
| | | |
| | | return blLifeRecharge.services.getUserLifePayOrderPage(params, { |
| | | showLoading: false, |
| | | }); |
| | | }, |
| | | { |
| | | queryKey: [ |
| | | 'blLifeRecharge/getUserLifePayOrderPage', |
| | | { |
| | | lifePayOrderType, |
| | | userId: blLifeRecharge.accountModel.userId, |
| | | }, |
| | | ], |
| | | } |
| | | ); |
| | | |
| | | return { |
| | | infiniteLoadingProps, |
| | | }; |
| | | } |
New file |
| | |
| | | import { |
| | | useInfiniteQuery, |
| | | QueryKey, |
| | | useMutation, |
| | | useQueryClient, |
| | | InfiniteData, |
| | | RefetchOptions, |
| | | RefetchQueryFilters, |
| | | } from '@tanstack/vue-query'; |
| | | import { UnwrapNestedRefs, Ref, ref, reactive, computed } from 'vue'; |
| | | import Taro from '@tarojs/taro'; |
| | | |
| | | export type BaseData<T = any> = { |
| | | data?: T[]; |
| | | objectData?: any; |
| | | pageModel?: { |
| | | rows?: number; |
| | | page?: number; |
| | | totalCount?: number; |
| | | totalPage?: number; |
| | | }; |
| | | }; |
| | | |
| | | export interface OrderInput { |
| | | property?: string; |
| | | order?: number; |
| | | } |
| | | |
| | | export type ExtraParams = { |
| | | [key: string]: any; |
| | | orderByProperty?: string; |
| | | }; |
| | | |
| | | export type ServiceContext = { |
| | | pageParam?: any; |
| | | signal?: AbortSignal; |
| | | }; |
| | | |
| | | export type InfiniteGroupOptions = { |
| | | groupIndex?: number; |
| | | itemIndex?: number; |
| | | }; |
| | | |
| | | type Service<TData, TExtraParams extends ExtraParams> = ( |
| | | context: ServiceContext, |
| | | extraParamsState: UnwrapNestedRefs<TExtraParams> |
| | | ) => Promise<TData>; |
| | | |
| | | type UseInfiniteLoadingOptions<T, TExtraParams extends ExtraParams> = { |
| | | /** |
| | | * @deprecated |
| | | */ |
| | | defaultExtraParams?: TExtraParams; |
| | | enabled?: Ref<boolean> | boolean; |
| | | queryKey?: QueryKey; |
| | | onSuccess?: (data: InfiniteData<BaseData<T>>) => void; |
| | | onRefetch?: () => any; |
| | | useLocalData?: boolean; |
| | | refeshDidShow?: boolean; |
| | | select?: (data: InfiniteData<BaseData<T>>) => InfiniteData<BaseData<T>>; |
| | | }; |
| | | // QueryFunction<BaseData<T>, (string | DeepUnwrapRef<UnwrapNestedRefs<TExtraParams>>)[], any> |
| | | export function useInfiniteLoading<T, TExtraParams extends ExtraParams>( |
| | | service: Service<BaseData<T>, TExtraParams>, |
| | | options: UseInfiniteLoadingOptions<T, TExtraParams> = {} |
| | | ) { |
| | | const { |
| | | defaultExtraParams = {} as TExtraParams, |
| | | enabled = ref(true), |
| | | queryKey, |
| | | onSuccess, |
| | | onRefetch: _onRefetch, |
| | | useLocalData = false, |
| | | refeshDidShow = true, |
| | | select, |
| | | } = options; |
| | | |
| | | const extraParamState = reactive({ |
| | | ...defaultExtraParams, |
| | | }); |
| | | |
| | | const localData = ref({ |
| | | pageParams: [], |
| | | pages: [], |
| | | }); |
| | | |
| | | const { |
| | | data, |
| | | error, |
| | | fetchNextPage, |
| | | hasNextPage, |
| | | isFetching, |
| | | isFetchingNextPage, |
| | | isLoading, |
| | | isError, |
| | | refetch, |
| | | isInitialLoading, |
| | | fetchPreviousPage, |
| | | hasPreviousPage, |
| | | } = useInfiniteQuery({ |
| | | queryKey: queryKey, |
| | | queryFn: ({ pageParam = 1, signal }) => { |
| | | return service({ pageParam, signal }, extraParamState); |
| | | }, |
| | | // initialPageParam: 1, |
| | | getNextPageParam: (lastPage, pages) => { |
| | | if (!lastPage) return 1; |
| | | if ( |
| | | (lastPage.pageModel.page - 1) * lastPage.pageModel.rows + lastPage.data.length < |
| | | lastPage.pageModel.totalCount && |
| | | lastPage.pageModel.totalCount > 0 |
| | | ) { |
| | | return lastPage.pageModel.page + 1; |
| | | } |
| | | }, |
| | | getPreviousPageParam: (firstPage, pages) => { |
| | | if (!firstPage) return 1; |
| | | if ( |
| | | (firstPage.pageModel.page - 1) * firstPage.pageModel.rows + firstPage.data.length < |
| | | firstPage.pageModel.totalCount && |
| | | firstPage.pageModel.totalCount > 0 |
| | | ) { |
| | | return firstPage.pageModel.page + 1; |
| | | } |
| | | }, |
| | | enabled: enabled, |
| | | onSuccess(data) { |
| | | // console.log('data2: ', data); |
| | | localData.value.pageParams = data.pageParams; |
| | | //@ts-ignore |
| | | localData.value.pages = data.pages; |
| | | onSuccess?.(data); |
| | | }, |
| | | select(data) { |
| | | return select ? select(data) : data; |
| | | }, |
| | | }); |
| | | |
| | | const _data = computed<InfiniteData<BaseData<T>>>(() => |
| | | useLocalData ? (localData.value as any) : data.value |
| | | ); |
| | | |
| | | const queryClient = useQueryClient(); |
| | | |
| | | const { mutateAsync } = useMutation({ |
| | | mutationFn: ({ pageParam }: ServiceContext) => fetchNextPage(pageParam), |
| | | |
| | | // onSettled: async () => { |
| | | // return await queryClient.invalidateQueries({ queryKey: queryKey }); |
| | | // }, |
| | | }); |
| | | |
| | | const { mutate: setListItem } = useMutation({ |
| | | mutationFn: (data: { dataKey?: string; data: Partial<T> }) => { |
| | | return Promise.resolve(data); |
| | | }, |
| | | onSuccess: ({ dataKey = 'id', data }) => { |
| | | console.log('dataKey: ', data); |
| | | const pagesArray: InfiniteData<BaseData<T>> = useLocalData |
| | | ? localData.value |
| | | : queryClient.getQueryData(queryKey); |
| | | console.log('pagesArray: ', pagesArray); |
| | | const newPagesArray = |
| | | pagesArray?.pages.map((page) => { |
| | | return { |
| | | ...page, |
| | | data: page.data.map((item) => { |
| | | if (item[dataKey] === data[dataKey]) { |
| | | return { |
| | | ...item, |
| | | ...data, |
| | | }; |
| | | } else { |
| | | return item; |
| | | } |
| | | }), |
| | | }; |
| | | }) ?? []; |
| | | if (useLocalData) { |
| | | console.log('newPagesArray: ', newPagesArray); |
| | | localData.value.pageParams = pagesArray.pageParams; |
| | | localData.value.pages = newPagesArray; |
| | | } else { |
| | | queryClient.setQueryData(queryKey, () => ({ |
| | | pages: newPagesArray, |
| | | pageParams: pagesArray.pageParams, |
| | | })); |
| | | } |
| | | }, |
| | | }); |
| | | |
| | | function onRefetch(options?: RefetchOptions & RefetchQueryFilters<unknown>) { |
| | | _onRefetch?.(); |
| | | return refetch(options); |
| | | } |
| | | |
| | | const infiniteLoadingProps = computed(() => ({ |
| | | fetchNextPage, |
| | | listData: _data.value, |
| | | flattenListData: flattenListData.value, |
| | | // error: error.value, |
| | | hasMore: hasNextPage.value, |
| | | isFetching: isFetching.value, |
| | | isFetchingNextPage: isFetchingNextPage.value, |
| | | isLoading: isLoading.value, |
| | | isError: !!isError.value, |
| | | refetch: onRefetch, |
| | | })); |
| | | |
| | | const flattenListData = computed(() => { |
| | | let list: BaseData<T>['data'] = []; |
| | | if (data && _data.value) { |
| | | _data.value?.pages.forEach((group) => { |
| | | group.data.forEach((item) => { |
| | | list.push(item); |
| | | }); |
| | | }); |
| | | } |
| | | return list; |
| | | }); |
| | | |
| | | if (refeshDidShow) { |
| | | useRefeshDidShow({ queryKey: queryKey }); |
| | | } |
| | | |
| | | const infiniteLoadingRef = ref<{ backToTop(): void; scrollToBottom(dis?: number): void }>(); |
| | | |
| | | function setByIndex(options: InfiniteGroupOptions = {}) { |
| | | refetch({ refetchPage: (page, index) => index === options.groupIndex, type: 'inactive' }); |
| | | } |
| | | |
| | | function updatePageByIndex(options: InfiniteGroupOptions = {}) { |
| | | refetch({ refetchPage: (page, index) => index === options.groupIndex, type: 'inactive' }); |
| | | } |
| | | |
| | | function remove() { |
| | | queryClient.invalidateQueries({ queryKey: queryKey }); |
| | | } |
| | | |
| | | function invalidateQueries() { |
| | | return queryClient.invalidateQueries({ queryKey: queryKey }); |
| | | } |
| | | |
| | | const listActions = { |
| | | setByIndex, |
| | | updatePageByIndex, |
| | | remove, |
| | | }; |
| | | |
| | | return { |
| | | infiniteLoadingProps, |
| | | flattenListData, |
| | | extraParamState, |
| | | mutateAsync, |
| | | setListItem, |
| | | cancel: () => queryClient.cancelQueries(queryKey), |
| | | infiniteLoadingRef, |
| | | listActions, |
| | | invalidateQueries, |
| | | fetchPreviousPage, |
| | | hasPreviousPage, |
| | | }; |
| | | } |
| | | |
| | | export type ListActionsType = { |
| | | setByIndex: (options?: InfiniteGroupOptions) => void; |
| | | updatePageByIndex: (options?: InfiniteGroupOptions) => void; |
| | | remove: () => void; |
| | | }; |
| | | |
| | | type UseRefeshDidShowOptions = { |
| | | queryKey: QueryKey; |
| | | }; |
| | | |
| | | export function useRefeshDidShow({ queryKey }: UseRefeshDidShowOptions) { |
| | | const queryClient = useQueryClient(); |
| | | const showUpdate = ref(false); |
| | | |
| | | Taro.useDidShow(() => { |
| | | if (showUpdate.value) { |
| | | queryClient.invalidateQueries({ |
| | | queryKey: queryKey, |
| | | }); |
| | | } |
| | | }); |
| | | |
| | | Taro.useDidHide(() => { |
| | | showUpdate.value = true; |
| | | }); |
| | | |
| | | Taro.useUnload(() => { |
| | | showUpdate.value = false; |
| | | }); |
| | | } |
| | |
| | | export { default as RechargeGrid } from './views/RechargeGrid/RechargeGrid.vue'; |
| | | export { default as Order } from './views/Order/Order.vue'; |
| | | export { default as PhoneBillRecharge } from './views/PhoneBillRecharge/PhoneBillRecharge.vue'; |
| | | export { default as electricBillRecharge } from './views/electricBillRecharge/electricBillRecharge.vue'; |
| | | export { default as SelectPayTypeView } from './views/SelectPayTypeView/SelectPayTypeView.vue'; |
| | | export { default as RechargeResultView } from './views/RechargeResultView/RechargeResultView.vue'; |
| | | export { default as PhoneOrder } from './views/Order/components/PhoneOrder.vue'; |
| | | export { default as ElectricOrder } from './views/Order/components/ElectricOrder.vue'; |
| | | export * from './utils'; |
| | |
| | | @use './layout.scss' as *; |
| | | @use './rechargeGrid.scss' as *; |
| | | @use './components.scss' as *; |
| | | @use './loading.scss' as *; |
| | | |
| | | :root, |
| | | page { |
New file |
| | |
| | | @use './common.scss' as *; |
| | | |
| | | .no-data-wrapper { |
| | | display: flex; |
| | | width: 100%; |
| | | height: 100%; |
| | | justify-content: center; |
| | | align-items: center; |
| | | flex-direction: column; |
| | | |
| | | .no-data-img { |
| | | width: 284px; |
| | | height: 202px; |
| | | } |
| | | |
| | | .no-data-text { |
| | | font-size: 20px; |
| | | color: #333333; |
| | | line-height: 2.4; |
| | | } |
| | | } |
| | | |
| | | .loading-layout-loading-content-wrapper { |
| | | padding: 40px; |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | |
| | | .nut-icon { |
| | | color: boleGetCssVar('text-color', 'primary'); |
| | | margin-right: 8px; |
| | | } |
| | | |
| | | .list-empty-hint-text { |
| | | line-height: 1; |
| | | font-size: 30px; |
| | | color: boleGetCssVar('text-color', 'primary'); |
| | | } |
| | | } |
| | | |
| | | .loading-layout-error-wrapper { |
| | | height: 100%; |
| | | } |
| | |
| | | |
| | | .parValue-radio-group { |
| | | width: 100%; |
| | | display: grid; |
| | | display: grid !important; |
| | | grid-template-columns: repeat(3, 1fr); |
| | | grid-gap: 10px; |
| | | |
| | |
| | | } from './lifeRechargeServices'; |
| | | import { IRequest, BlLifeRechargeOptions } from './types'; |
| | | import { LifeRechargeConstants } from './lifeRechargeConstants'; |
| | | import { BlLifeRechargeAccountModel } from './lifeRechargeAccountModel'; |
| | | |
| | | export class BlLifeRecharge<T extends IRequest = IRequest> { |
| | | services: BlLifeRechargeServices<T>; |
| | | userId = ''; |
| | | accountModel: BlLifeRechargeAccountModel; |
| | | |
| | | static constants = LifeRechargeConstants; |
| | | constants = LifeRechargeConstants; |
| | | |
| | | constructor(options: BlLifeRechargeOptions<T>) { |
| | | this.services = new BlLifeRechargeServices(options); |
| | | this.userId = options.userId || ''; |
| | | this.accountModel = new BlLifeRechargeAccountModel({ |
| | | userId: options.userId, |
| | | phoneNumber: options.phoneNumber, |
| | | }); |
| | | } |
| | | |
| | | async login(body: PhoneMesssageCodeLoginInput, options?: RequestConfig) { |
| | | let res = await this.services.lifePayPhoneMesssageCodeLogin(body, options); |
| | | this.userId = res; |
| | | this.accountModel.setUserId(res); |
| | | this.accountModel.setPhoneNumber(body.phoneNumber); |
| | | return res; |
| | | } |
| | | |
| | | loginout() { |
| | | this.userId = ''; |
| | | this.accountModel.setUserId(''); |
| | | this.accountModel.setPhoneNumber(''); |
| | | } |
| | | |
| | | isLogin() { |
| | | return !!this.userId; |
| | | return !!this.accountModel.userId; |
| | | } |
| | | |
| | | getRechargeParValue(amount: number, rate: number) { |
New file |
| | |
| | | import { BlLifeRechargeAccountModelOptions } from './types'; |
| | | |
| | | export class BlLifeRechargeAccountModel { |
| | | userId = ''; |
| | | phoneNumber = ''; |
| | | |
| | | constructor(options: BlLifeRechargeAccountModelOptions = {}) { |
| | | const { userId, phoneNumber } = options; |
| | | this.setUserId(userId); |
| | | this.setPhoneNumber(phoneNumber); |
| | | } |
| | | |
| | | setUserId(userId: string) { |
| | | this.userId = userId; |
| | | } |
| | | |
| | | setPhoneNumber(phoneNumber: string) { |
| | | this.phoneNumber = phoneNumber; |
| | | } |
| | | } |
| | |
| | | ...(options || {}), |
| | | }); |
| | | } |
| | | |
| | | /** 获取我的订单分页数据 POST /api/LifePay/GetUserLifePayOrderPage */ |
| | | async getUserLifePayOrderPage(body: QueryLifePayOrderListInput, options?: RequestConfig) { |
| | | return this.request<UserLifePayOrderOutputPageOutput>('/api/LifePay/GetUserLifePayOrderPage', { |
| | | method: 'POST', |
| | | headers: { |
| | | 'Content-Type': 'application/json', |
| | | }, |
| | | data: body, |
| | | ...(options || {}), |
| | | }); |
| | | } |
| | | } |
| | | |
| | | export interface PhoneMesssageCodeLoginInput { |
| | |
| | | orderNo: string; |
| | | lifePayType?: LifeRechargeConstants.LifePayTypeEnum; |
| | | } |
| | | |
| | | export interface QueryLifePayOrderListInput { |
| | | pageModel?: Pagination; |
| | | lifePayOrderType?: LifeRechargeConstants.LifePayOrderTypeEnum; |
| | | /** 开始支付时间 */ |
| | | beginPayTime?: string; |
| | | /** 结束支付时间 */ |
| | | endPayTime?: string; |
| | | payStatus?: LifeRechargeConstants.LifePayStatusEnum; |
| | | lifePayOrderStatus?: LifeRechargeConstants.LifePayOrderStatusEnum; |
| | | /** 开始完成时间 */ |
| | | beginFinishTime?: string; |
| | | /** 结束完成时间 */ |
| | | endFinishTime?: string; |
| | | /** 用户Id */ |
| | | userId?: string; |
| | | } |
| | | |
| | | export interface Pagination { |
| | | rows?: number; |
| | | page?: number; |
| | | orderInput?: OrderInput[]; |
| | | totalCount?: number; |
| | | totalPage?: number; |
| | | } |
| | | |
| | | export interface OrderInput { |
| | | property?: string; |
| | | order?: any; |
| | | } |
| | | |
| | | export interface UserLifePayOrderOutputPageOutput { |
| | | pageModel?: Pagination; |
| | | objectData?: any; |
| | | data?: UserLifePayOrderOutput[]; |
| | | } |
| | | |
| | | export interface UserLifePayOrderOutput { |
| | | id?: string; |
| | | lifePayType?: LifeRechargeConstants.LifePayTypeEnum; |
| | | lifePayOrderType?: LifeRechargeConstants.LifePayOrderTypeEnum; |
| | | /** 订单号 */ |
| | | orderNo?: string; |
| | | /** 充值金额 */ |
| | | rechargeAmount?: number; |
| | | /** 优惠金额 */ |
| | | discountAmount?: number; |
| | | /** 实付金额 */ |
| | | payAmount?: number; |
| | | /** 支付时间 */ |
| | | payTime?: string; |
| | | payStatus?: LifeRechargeConstants.LifePayStatusEnum; |
| | | lifePayOrderStatus?: LifeRechargeConstants.LifePayOrderStatusEnum; |
| | | /** 完成时间 */ |
| | | finishTime?: string; |
| | | /** 订单详细数据 */ |
| | | orderParamDetailJsonStr?: string; |
| | | } |
| | |
| | | request: T; |
| | | }; |
| | | |
| | | export type BlLifeRechargeOptions<T extends IRequest> = BlLifeRechargeServicesOptions<T> & { |
| | | export type BlLifeRechargeAccountModelOptions = { |
| | | userId?: string; |
| | | phoneNumber?: string; |
| | | }; |
| | | |
| | | export type BlLifeRechargeOptions<T extends IRequest> = BlLifeRechargeServicesOptions<T> & |
| | | BlLifeRechargeAccountModelOptions & {}; |
| | |
| | | <template> |
| | | <!-- <InfiniteLoading scrollViewClassName="common-infinite-scroll-list" v-bind="infiniteLoadingProps"> |
| | | <template #renderItem="{ item }"> --> |
| | | <OrderCard title="话费充值" status="待支付"> |
| | | <OrderCardItem label="充值账号:" :value="'18888888888'" /> |
| | | <OrderCardItem label="下单时间:" :value="'18888888888'" /> |
| | | <OrderCardItem label="充值金额:" :value="'18888888888'" /> |
| | | <OrderCardItem label="优惠金额:" :value="'18888888888'" /> |
| | | <OrderCardItem label="实付金额:" :value="'18888888888'" /> |
| | | <OrderCardItem label="支付时间:" :value="'2025-02-19 17:15:54'" /> |
| | | <OrderCardItem label="完成时间:" :value="'2025-02-19 17:15:54'" /> |
| | | </OrderCard> |
| | | <!-- </template> |
| | | </InfiniteLoading> --> |
| | | <InfiniteLoading scrollViewClassName="common-infinite-scroll-list" v-bind="infiniteLoadingProps"> |
| | | <template #renderItem="{ item }"> |
| | | <OrderCard title="话费充值" status="待支付"> |
| | | <OrderCardItem label="充值账号:" :value="'18888888888'" /> |
| | | <OrderCardItem label="下单时间:" :value="'18888888888'" /> |
| | | <OrderCardItem label="充值金额:" :value="'18888888888'" /> |
| | | <OrderCardItem label="优惠金额:" :value="'18888888888'" /> |
| | | <OrderCardItem label="实付金额:" :value="'18888888888'" /> |
| | | <OrderCardItem label="支付时间:" :value="'2025-02-19 17:15:54'" /> |
| | | <OrderCardItem label="完成时间:" :value="'2025-02-19 17:15:54'" /> |
| | | </OrderCard> |
| | | </template> |
| | | </InfiniteLoading> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import InfiniteLoading from '../../../components/InfiniteLoading/InfiniteLoading.vue'; |
| | | import OrderCard from '../../../components/Card/OrderCard.vue'; |
| | | import OrderCardItem from '../../../components/Card/OrderCardItem.vue'; |
| | | import { useGetUserLifePayOrderPage } from '../../../hooks'; |
| | | import { BlLifeRecharge } from '../../../utils'; |
| | | |
| | | defineOptions({ |
| | | name: 'PhoneOrder', |
| | |
| | | |
| | | // const props = withDefaults(defineProps<Props>(), {}); |
| | | |
| | | const infiniteLoadingProps = {}; |
| | | const { infiniteLoadingProps } = useGetUserLifePayOrderPage({ |
| | | lifePayOrderType: BlLifeRecharge.constants.LifePayOrderTypeEnum.话费订单, |
| | | }); |
| | | </script> |
| | | |
| | | <style lang="scss"></style> |
| | |
| | | async function goPay() { |
| | | try { |
| | | let params: LifePhoneDataCreateLifePayOrderInput = { |
| | | userId: blLifeRecharge.userId, |
| | | userId: blLifeRecharge.accountModel.userId, |
| | | productData: { |
| | | ispCode: form.ispCode, |
| | | parValue: 0.1, |
| | |
| | | <template> |
| | | <div class="recharge-result-view"> |
| | | <div class="recharge-result-view-title">支付成功,充值款将在0-24小时内到账</div> |
| | | <div class="recharge-result-view-title">{{ title }}</div> |
| | | <div class="recharge-result-view-tips"> |
| | | 同一号码充值期间,未到账前切勿在其他任何平台再次充值。因此造成的资金损失须用户自行承担!!! |
| | | </div> |
| | |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import Taro from '@tarojs/taro'; |
| | | import { BlLifeRecharge } from '@life-payment/components'; |
| | | |
| | | defineOptions({ |
| | | name: 'RechargeResultView', |
| | | }); |
| | | |
| | | // type Props = {}; |
| | | type Props = { |
| | | title?: string; |
| | | }; |
| | | |
| | | // const props = withDefaults(defineProps<Props>(), {}); |
| | | const props = withDefaults(defineProps<Props>(), { |
| | | title: '支付成功,充值款将在0-24小时内到账', |
| | | }); |
| | | |
| | | const router = Taro.useRouter(); |
| | | const orderNo = router.params?.orderNo ?? ''; |
| | | const lifePayOrderType = Number(router.params?.lifePayOrderType ?? ''); |
| | | |
| | | const emit = defineEmits<{ |
| | | (e: 'goBackHome'): void; |
| | |
| | | // const props = withDefaults(defineProps<Props>(), {}); |
| | | |
| | | const emit = defineEmits<{ |
| | | (e: 'paySuccess', id: number): void; |
| | | ( |
| | | e: 'paySuccess', |
| | | orderNo: string, |
| | | lifePayOrderType: LifeRechargeConstants.LifePayOrderTypeEnum |
| | | ): void; |
| | | }>(); |
| | | |
| | | const router = Taro.useRouter(); |
| | | const orderNo = router.params?.orderNo ?? ''; |
| | | const lifePayOrderType = Number(router.params?.lifePayOrderType ?? ''); |
| | | |
| | | const { blLifeRecharge } = useLifeRechargeContext(); |
| | | |
| | |
| | | } catch (error) {} |
| | | } |
| | | |
| | | // useQuery({ |
| | | // queryKey: ['platformServicePayServices/getPlaformServicePayQRCode', orderNo], |
| | | // queryFn: async () => { |
| | | // return await blLifeRecharge.services.getPayStatusByOrderNo( |
| | | // { |
| | | // orderNo, |
| | | // }, |
| | | // { |
| | | // showLoading: false, |
| | | // } |
| | | // ); |
| | | // }, |
| | | // onSuccess(data) { |
| | | // if (data === blLifeRecharge.constants.LifePayStatusEnum.已支付) { |
| | | // } |
| | | // }, |
| | | // refetchInterval: 1000 * 3, |
| | | // }); |
| | | useQuery({ |
| | | queryKey: ['platformServicePayServices/getPayStatusByOrderNo', orderNo], |
| | | queryFn: async () => { |
| | | return await blLifeRecharge.services.getPayStatusByOrderNo( |
| | | { |
| | | orderNo, |
| | | }, |
| | | { |
| | | showLoading: false, |
| | | } |
| | | ); |
| | | }, |
| | | onSuccess(data) { |
| | | if (data === blLifeRecharge.constants.LifePayStatusEnum.已支付) { |
| | | emit('paySuccess', orderNo, lifePayOrderType); |
| | | } |
| | | }, |
| | | refetchInterval: 1000 * 3, |
| | | }); |
| | | </script> |