From 797fa07355a312a06541ca105a00928e95dbded6 Mon Sep 17 00:00:00 2001 From: zym2525 <540361168@qq.com> Date: 星期日, 23 二月 2025 14:39:44 +0800 Subject: [PATCH] fix: some --- packages/components/src/components/NoData/NoData.vue | 22 packages/components/src/components/InfiniteLoading/InfiniteLoading.vue | 2 packages/components/src/views/Order/components/PhoneOrder.vue | 34 apps/taro/src/stores/modules/user.ts | 22 apps/taro/src/app.config.ts | 1 packages/components/src/hooks/infiniteLoading.ts | 294 +++++++++ apps/taro/src/constants/router.ts | 1 packages/components/src/utils/types.ts | 6 apps/taro/src/components/Tabs/ProTabPane.vue | 61 ++ apps/taro/src/subpackages/recharge/phoneBillRecharge/InnerPage.vue | 4 apps/taro/src/components/Tabs/ProTabs.vue | 536 +++++++++++++++++ packages/components/src/views/PhoneBillRecharge/PhoneBillRecharge.vue | 2 apps/taro/src/hooks/user.ts | 6 apps/taro/src/utils/storage/auth.ts | 11 packages/components/src/index.ts | 3 packages/components/src/styles/loading.scss | 43 + packages/components/src/styles/index.scss | 1 packages/components/src/views/SelectPayTypeView/SelectPayTypeView.vue | 44 apps/taro/src/subpackages/recharge/selectPayType/InnerPage.vue | 14 packages/components/src/components/Layout/layout.ts | 19 apps/taro/src/utils/blLifeRecharge.ts | 3 packages/components/src/styles/rechargeGrid.scss | 2 packages/components/src/components/Layout/LoadingLayout.vue | 33 + packages/components/src/utils/lifeRechargeAccountModel.ts | 20 apps/taro/src/components/Tabs/tabs.ts | 354 +++++++++++ apps/taro/src/subpackages/order/order/InnerPage.vue | 15 apps/taro/src/subpackages/recharge/rechargeElectricResult/rechargeElectricResult.vue | 21 apps/taro/src/pages/mine/index.vue | 4 apps/taro/src/subpackages/recharge/rechargeElectricResult/rechargeElectricResult.config.ts | 3 apps/taro/src/subpackages/login/loginByForm/verificationCodeLoginForm.vue | 5 apps/taro/src/custom-tab-bar/index.tsx | 4 apps/taro/src/subpackages/recharge/rechargeResult/rechargeResult.config.ts | 3 packages/components/assets/icon-back-top.png | 0 packages/components/src/constants/index.ts | 11 packages/components/src/utils/lifeRecharge.ts | 16 packages/components/src/utils/lifeRechargeServices.ts | 70 ++ packages/components/src/views/RechargeResultView/RechargeResultView.vue | 17 packages/components/src/hooks/index.ts | 51 + 38 files changed, 1,683 insertions(+), 75 deletions(-) diff --git a/apps/taro/src/app.config.ts b/apps/taro/src/app.config.ts index 1f44a78..b6e2fa8 100644 --- a/apps/taro/src/app.config.ts +++ b/apps/taro/src/app.config.ts @@ -58,6 +58,7 @@ 'electricBillRecharge/electricBillRecharge', 'selectPayType/selectPayType', 'rechargeResult/rechargeResult', + 'rechargeElectricResult/rechargeElectricResult', ], }, { diff --git a/apps/taro/src/components/Tabs/ProTabPane.vue b/apps/taro/src/components/Tabs/ProTabPane.vue new file mode 100644 index 0000000..345f7db --- /dev/null +++ b/apps/taro/src/components/Tabs/ProTabPane.vue @@ -0,0 +1,61 @@ +<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> diff --git a/apps/taro/src/components/Tabs/ProTabs.vue b/apps/taro/src/components/Tabs/ProTabs.vue new file mode 100644 index 0000000..b2c7c8c --- /dev/null +++ b/apps/taro/src/components/Tabs/ProTabs.vue @@ -0,0 +1,536 @@ +<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> diff --git a/apps/taro/src/components/Tabs/tabs.ts b/apps/taro/src/components/Tabs/tabs.ts new file mode 100644 index 0000000..4647f84 --- /dev/null +++ b/apps/taro/src/components/Tabs/tabs.ts @@ -0,0 +1,354 @@ +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銆乴ine銆乻mile + }, + 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 }; +}; diff --git a/apps/taro/src/constants/router.ts b/apps/taro/src/constants/router.ts index 35682a6..1445fda 100644 --- a/apps/taro/src/constants/router.ts +++ b/apps/taro/src/constants/router.ts @@ -13,4 +13,5 @@ order = '/subpackages/order/order/order', selectPayType = '/subpackages/recharge/selectPayType/selectPayType', rechargeResult = '/subpackages/recharge/rechargeResult/rechargeResult', + rechargeElectricResult = '/subpackages/recharge/rechargeElectricResult/rechargeElectricResult', } diff --git a/apps/taro/src/custom-tab-bar/index.tsx b/apps/taro/src/custom-tab-bar/index.tsx index e2b4683..4c50faf 100644 --- a/apps/taro/src/custom-tab-bar/index.tsx +++ b/apps/taro/src/custom-tab-bar/index.tsx @@ -73,7 +73,7 @@ Message.confirm({ message: '璇峰墠寰�鐧诲綍' }) .then(() => { Taro.navigateTo({ - url: `${RouterPath.authorization}?redirect=${url}`, + url: `${RouterPath.loginByForm}?redirect=${url}`, }); }) .finally(() => { @@ -93,7 +93,7 @@ Message.confirm({ message: '璇峰墠寰�鐧诲綍' }) .then(() => { Taro.navigateTo({ - url: `${RouterPath.authorization}?redirect=${RouterPath.home}`, + url: `${RouterPath.loginByForm}?redirect=${RouterPath.home}`, }); }) .finally(() => { diff --git a/apps/taro/src/hooks/user.ts b/apps/taro/src/hooks/user.ts index 29a6ef4..f1308ee 100644 --- a/apps/taro/src/hooks/user.ts +++ b/apps/taro/src/hooks/user.ts @@ -11,7 +11,8 @@ 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(); @@ -23,6 +24,7 @@ updateUserInfo, locationCity, virtualUserId, + virtualPhoneNumber, }; } @@ -52,7 +54,7 @@ } if (needAuth && !isLogin.value) { Taro.navigateTo({ - url: `${RouterPath.authorization}?redirect=${router.path}&${object2query(router.params)}`, + url: `${RouterPath.loginByForm}?redirect=${router.path}&${object2query(router.params)}`, }); } }); diff --git a/apps/taro/src/pages/mine/index.vue b/apps/taro/src/pages/mine/index.vue index 2717095..d7d2380 100644 --- a/apps/taro/src/pages/mine/index.vue +++ b/apps/taro/src/pages/mine/index.vue @@ -13,7 +13,7 @@ <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> @@ -37,7 +37,7 @@ 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(); diff --git a/apps/taro/src/stores/modules/user.ts b/apps/taro/src/stores/modules/user.ts index b101a3d..8525d95 100644 --- a/apps/taro/src/stores/modules/user.ts +++ b/apps/taro/src/stores/modules/user.ts @@ -11,6 +11,7 @@ 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'; @@ -34,6 +35,7 @@ firstSetLocation?: boolean; virtualUserId?: string; + virtualPhoneNumber?: string; } const goAuthorization = debounce( @@ -57,6 +59,7 @@ state: (): UserState => { const userInfo = getCacheUserInfo(); const userDetail = getUserDetail(); + const storageVirtualUser = getStorageVirtualUserId(); return { // user info @@ -68,7 +71,8 @@ userDetail: userDetail, firstGetUserDetail: true, - virtualUserId: getStorageVirtualUserId() ?? '', + virtualUserId: storageVirtualUser?.virtualUserId ?? '', + virtualPhoneNumber: storageVirtualUser?.virtualPhoneNumber ?? '', }; }, getters: { @@ -129,7 +133,10 @@ ); if (res) { - this.loginVirtualSuccess(res); + this.loginVirtualSuccess({ + virtualUserId: res, + virtualPhoneNumber: data.phoneNumber, + }); } return res; }, @@ -158,15 +165,16 @@ } 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) { diff --git a/apps/taro/src/subpackages/login/loginByForm/verificationCodeLoginForm.vue b/apps/taro/src/subpackages/login/loginByForm/verificationCodeLoginForm.vue index ea7179d..e95a502 100644 --- a/apps/taro/src/subpackages/login/loginByForm/verificationCodeLoginForm.vue +++ b/apps/taro/src/subpackages/login/loginByForm/verificationCodeLoginForm.vue @@ -102,7 +102,10 @@ showLoading: false, } ); - userStore.loginVirtualSuccess(res); + userStore.loginVirtualSuccess({ + virtualPhoneNumber: form.phoneNumber, + virtualUserId: res, + }); jump(); } } else { diff --git a/apps/taro/src/subpackages/order/order/InnerPage.vue b/apps/taro/src/subpackages/order/order/InnerPage.vue index 0bdfd9d..eac44ba 100644 --- a/apps/taro/src/subpackages/order/order/InnerPage.vue +++ b/apps/taro/src/subpackages/order/order/InnerPage.vue @@ -1,17 +1,24 @@ <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, diff --git a/apps/taro/src/subpackages/recharge/phoneBillRecharge/InnerPage.vue b/apps/taro/src/subpackages/recharge/phoneBillRecharge/InnerPage.vue index f44d4e2..f6549ba 100644 --- a/apps/taro/src/subpackages/recharge/phoneBillRecharge/InnerPage.vue +++ b/apps/taro/src/subpackages/recharge/phoneBillRecharge/InnerPage.vue @@ -5,7 +5,7 @@ </template> <script setup lang="ts"> -import { PhoneBillRecharge } from '@life-payment/components'; +import { PhoneBillRecharge, BlLifeRecharge } from '@life-payment/components'; import Taro from '@tarojs/taro'; defineOptions({ @@ -14,7 +14,7 @@ function goPay(orderNo: string) { Taro.navigateTo({ - url: `${RouterPath.selectPayType}?orderNo=${orderNo}`, + url: `${RouterPath.selectPayType}?orderNo=${orderNo}&lifePayOrderType=${BlLifeRecharge.constants.LifePayOrderTypeEnum.璇濊垂璁㈠崟}`, }); } </script> diff --git a/apps/taro/src/subpackages/recharge/rechargeElectricResult/rechargeElectricResult.config.ts b/apps/taro/src/subpackages/recharge/rechargeElectricResult/rechargeElectricResult.config.ts new file mode 100644 index 0000000..305fdb1 --- /dev/null +++ b/apps/taro/src/subpackages/recharge/rechargeElectricResult/rechargeElectricResult.config.ts @@ -0,0 +1,3 @@ +export default definePageConfig({ + disableScroll: true, +}); diff --git a/apps/taro/src/subpackages/recharge/rechargeElectricResult/rechargeElectricResult.vue b/apps/taro/src/subpackages/recharge/rechargeElectricResult/rechargeElectricResult.vue new file mode 100644 index 0000000..b073adc --- /dev/null +++ b/apps/taro/src/subpackages/recharge/rechargeElectricResult/rechargeElectricResult.vue @@ -0,0 +1,21 @@ +<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> diff --git a/apps/taro/src/subpackages/recharge/rechargeResult/rechargeResult.config.ts b/apps/taro/src/subpackages/recharge/rechargeResult/rechargeResult.config.ts index e69de29..305fdb1 100644 --- a/apps/taro/src/subpackages/recharge/rechargeResult/rechargeResult.config.ts +++ b/apps/taro/src/subpackages/recharge/rechargeResult/rechargeResult.config.ts @@ -0,0 +1,3 @@ +export default definePageConfig({ + disableScroll: true, +}); diff --git a/apps/taro/src/subpackages/recharge/selectPayType/InnerPage.vue b/apps/taro/src/subpackages/recharge/selectPayType/InnerPage.vue index c5c7245..d1ea8c1 100644 --- a/apps/taro/src/subpackages/recharge/selectPayType/InnerPage.vue +++ b/apps/taro/src/subpackages/recharge/selectPayType/InnerPage.vue @@ -1,13 +1,23 @@ <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> diff --git a/apps/taro/src/utils/blLifeRecharge.ts b/apps/taro/src/utils/blLifeRecharge.ts index a030b56..c4441f3 100644 --- a/apps/taro/src/utils/blLifeRecharge.ts +++ b/apps/taro/src/utils/blLifeRecharge.ts @@ -4,5 +4,6 @@ export const blLifeRecharge = new BlLifeRecharge({ request, - userId: getStorageVirtualUserId() ?? '', + userId: getStorageVirtualUserId()?.virtualUserId ?? '', + phoneNumber: getStorageVirtualUserId()?.virtualPhoneNumber ?? '', }); diff --git a/apps/taro/src/utils/storage/auth.ts b/apps/taro/src/utils/storage/auth.ts index 85c6baf..7533669 100644 --- a/apps/taro/src/utils/storage/auth.ts +++ b/apps/taro/src/utils/storage/auth.ts @@ -41,12 +41,17 @@ 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() { diff --git a/packages/components/assets/icon-back-top.png b/packages/components/assets/icon-back-top.png new file mode 100644 index 0000000..0b64e13 --- /dev/null +++ b/packages/components/assets/icon-back-top.png Binary files differ diff --git a/packages/components/src/components/InfiniteLoading/InfiniteLoading.vue b/packages/components/src/components/InfiniteLoading/InfiniteLoading.vue index 5a8aebe..bc55a9d 100644 --- a/packages/components/src/components/InfiniteLoading/InfiniteLoading.vue +++ b/packages/components/src/components/InfiniteLoading/InfiniteLoading.vue @@ -64,7 +64,7 @@ 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', diff --git a/packages/components/src/components/Layout/LoadingLayout.vue b/packages/components/src/components/Layout/LoadingLayout.vue new file mode 100644 index 0000000..ff721f1 --- /dev/null +++ b/packages/components/src/components/Layout/LoadingLayout.vue @@ -0,0 +1,33 @@ +<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> diff --git a/packages/components/src/components/Layout/layout.ts b/packages/components/src/components/Layout/layout.ts new file mode 100644 index 0000000..a43fe7f --- /dev/null +++ b/packages/components/src/components/Layout/layout.ts @@ -0,0 +1,19 @@ +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, + }, +}; diff --git a/packages/components/src/components/NoData/NoData.vue b/packages/components/src/components/NoData/NoData.vue new file mode 100644 index 0000000..ce0511e --- /dev/null +++ b/packages/components/src/components/NoData/NoData.vue @@ -0,0 +1,22 @@ +<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> diff --git a/packages/components/src/constants/index.ts b/packages/components/src/constants/index.ts index a3916b6..70fec56 100644 --- a/packages/components/src/constants/index.ts +++ b/packages/components/src/constants/index.ts @@ -12,3 +12,14 @@ [IspCode.dianxin]: '涓浗鐢典俊', [IspCode.liantong]: '涓浗鑱旈��', }; + +export enum OrderInputType { + /** + * 鍗囧簭 + */ + Asc, + /** + * 闄嶅簭 + */ + Desc, +} diff --git a/packages/components/src/hooks/index.ts b/packages/components/src/hooks/index.ts index 8513ce7..687cb7c 100644 --- a/packages/components/src/hooks/index.ts +++ b/packages/components/src/hooks/index.ts @@ -3,9 +3,13 @@ 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(); @@ -57,3 +61,48 @@ 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, + }; +} diff --git a/packages/components/src/hooks/infiniteLoading.ts b/packages/components/src/hooks/infiniteLoading.ts new file mode 100644 index 0000000..d7b0fe1 --- /dev/null +++ b/packages/components/src/hooks/infiniteLoading.ts @@ -0,0 +1,294 @@ +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; + }); +} diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index 83f40ea..5806c92 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -1,7 +1,8 @@ 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'; diff --git a/packages/components/src/styles/index.scss b/packages/components/src/styles/index.scss index e60a34e..37b4cc0 100644 --- a/packages/components/src/styles/index.scss +++ b/packages/components/src/styles/index.scss @@ -5,6 +5,7 @@ @use './layout.scss' as *; @use './rechargeGrid.scss' as *; @use './components.scss' as *; +@use './loading.scss' as *; :root, page { diff --git a/packages/components/src/styles/loading.scss b/packages/components/src/styles/loading.scss new file mode 100644 index 0000000..b5dacbe --- /dev/null +++ b/packages/components/src/styles/loading.scss @@ -0,0 +1,43 @@ +@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%; +} diff --git a/packages/components/src/styles/rechargeGrid.scss b/packages/components/src/styles/rechargeGrid.scss index a4a127f..6bff098 100644 --- a/packages/components/src/styles/rechargeGrid.scss +++ b/packages/components/src/styles/rechargeGrid.scss @@ -24,7 +24,7 @@ .parValue-radio-group { width: 100%; - display: grid; + display: grid !important; grid-template-columns: repeat(3, 1fr); grid-gap: 10px; diff --git a/packages/components/src/utils/lifeRecharge.ts b/packages/components/src/utils/lifeRecharge.ts index 5afe980..1cdaf07 100644 --- a/packages/components/src/utils/lifeRecharge.ts +++ b/packages/components/src/utils/lifeRecharge.ts @@ -5,31 +5,37 @@ } 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) { diff --git a/packages/components/src/utils/lifeRechargeAccountModel.ts b/packages/components/src/utils/lifeRechargeAccountModel.ts new file mode 100644 index 0000000..67f1d9a --- /dev/null +++ b/packages/components/src/utils/lifeRechargeAccountModel.ts @@ -0,0 +1,20 @@ +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; + } +} diff --git a/packages/components/src/utils/lifeRechargeServices.ts b/packages/components/src/utils/lifeRechargeServices.ts index d45d643..9b6957e 100644 --- a/packages/components/src/utils/lifeRechargeServices.ts +++ b/packages/components/src/utils/lifeRechargeServices.ts @@ -99,6 +99,18 @@ ...(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 { @@ -210,3 +222,61 @@ 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; +} diff --git a/packages/components/src/utils/types.ts b/packages/components/src/utils/types.ts index f28bcae..eec3753 100644 --- a/packages/components/src/utils/types.ts +++ b/packages/components/src/utils/types.ts @@ -10,6 +10,10 @@ 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 & {}; diff --git a/packages/components/src/views/Order/components/PhoneOrder.vue b/packages/components/src/views/Order/components/PhoneOrder.vue index 530a7cf..782d440 100644 --- a/packages/components/src/views/Order/components/PhoneOrder.vue +++ b/packages/components/src/views/Order/components/PhoneOrder.vue @@ -1,23 +1,25 @@ <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', @@ -27,7 +29,7 @@ // const props = withDefaults(defineProps<Props>(), {}); -const infiniteLoadingProps = {}; +const { infiniteLoadingProps } = useGetUserLifePayOrderPage({ + lifePayOrderType: BlLifeRecharge.constants.LifePayOrderTypeEnum.璇濊垂璁㈠崟, +}); </script> - -<style lang="scss"></style> diff --git a/packages/components/src/views/PhoneBillRecharge/PhoneBillRecharge.vue b/packages/components/src/views/PhoneBillRecharge/PhoneBillRecharge.vue index 43fb296..191f9fc 100644 --- a/packages/components/src/views/PhoneBillRecharge/PhoneBillRecharge.vue +++ b/packages/components/src/views/PhoneBillRecharge/PhoneBillRecharge.vue @@ -176,7 +176,7 @@ async function goPay() { try { let params: LifePhoneDataCreateLifePayOrderInput = { - userId: blLifeRecharge.userId, + userId: blLifeRecharge.accountModel.userId, productData: { ispCode: form.ispCode, parValue: 0.1, diff --git a/packages/components/src/views/RechargeResultView/RechargeResultView.vue b/packages/components/src/views/RechargeResultView/RechargeResultView.vue index d83124d..4d89ab3 100644 --- a/packages/components/src/views/RechargeResultView/RechargeResultView.vue +++ b/packages/components/src/views/RechargeResultView/RechargeResultView.vue @@ -1,6 +1,6 @@ <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> @@ -14,13 +14,24 @@ </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; diff --git a/packages/components/src/views/SelectPayTypeView/SelectPayTypeView.vue b/packages/components/src/views/SelectPayTypeView/SelectPayTypeView.vue index 6df3a0e..1511491 100644 --- a/packages/components/src/views/SelectPayTypeView/SelectPayTypeView.vue +++ b/packages/components/src/views/SelectPayTypeView/SelectPayTypeView.vue @@ -31,11 +31,16 @@ // 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(); @@ -58,22 +63,23 @@ } 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> -- Gitblit v1.9.1