wupengfei
2025-02-21 64b52fa928e11640e8d6aad49bd39cd27c896543
feat: 订单
7个文件已修改
9个文件已添加
556 ■■■■■ 已修改文件
apps/taro/src/app.config.ts 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
apps/taro/src/constants/router.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
apps/taro/src/pages/mine/index.vue 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
apps/taro/src/subpackages/order/order/InnerPage.vue 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
apps/taro/src/subpackages/order/order/order.config.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
apps/taro/src/subpackages/order/order/order.vue 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/components/src/components/Card/OrderCard.vue 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/components/src/components/Card/OrderCardItem.vue 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/components/src/components/InfiniteLoading/InfiniteLoading.vue 235 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/components/src/index.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/components/src/utils/lifeRechargeConstants.ts 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/components/src/views/Order/Order.vue 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/components/src/views/Order/components/ElectricOrder.vue 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/components/src/views/Order/components/PhoneOrder.vue 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/components/src/views/electricBillRecharge/electricBillRecharge.vue 41 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
packages/services/api/LifePay.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
apps/taro/src/app.config.ts
@@ -60,6 +60,10 @@
        'rechargeResult/rechargeResult',
      ],
    },
    {
      root: 'subpackages/order',
      pages: ['order/order'],
    },
  ],
  // preloadRule: {
  //   'pages/mine/index': {
apps/taro/src/constants/router.ts
@@ -10,6 +10,7 @@
  phoneBillRecharge = '/subpackages/recharge/phoneBillRecharge/phoneBillRecharge',
  electricBillRecharge = '/subpackages/recharge/electricBillRecharge/electricBillRecharge',
  order = '/subpackages/order/order/order',
  selectPayType = '/subpackages/recharge/selectPayType/selectPayType',
  rechargeResult = '/subpackages/recharge/rechargeResult/rechargeResult',
}
apps/taro/src/pages/mine/index.vue
@@ -18,9 +18,9 @@
      </div>
    </div>
    <ContentScrollView>
      <List class="mine-list-wrapper" v-if="isLogin">
      <List class="mine-list-wrapper">
        <ListItem title="订单管理" @click="goOrderManage"></ListItem>
        <ListItem title="退出登录" @click="goLogout"></ListItem>
        <ListItem v-if="isLogin" title="退出登录" @click="goLogout"></ListItem>
      </List>
    </ContentScrollView>
  </PageLayoutWithBg>
@@ -28,7 +28,7 @@
<script setup lang="ts">
import { TransparentNavigationBar, List, ListItem } from '@/components';
import { useUser, useIsLogin, useGoLogin } from '@/hooks';
import { useUser, useIsLogin, useGoLogin, useAccessLogin } from '@/hooks';
import Taro from '@tarojs/taro';
import { RouterPath, OssAssets } from '@/constants';
import DefaultAvatar from '@/assets/components/icon-default-avatar.png';
@@ -69,7 +69,7 @@
  };
});
function goOrderManage() {}
const goOrderManage = useAccessLogin(() => goPage(RouterPath.order));
function goLogout() {
  userStore.logout();
apps/taro/src/subpackages/order/order/InnerPage.vue
New file
@@ -0,0 +1,20 @@
<template>
  <ContentScrollView :paddingH="false">
    <Order />
  </ContentScrollView>
</template>
<script setup lang="ts">
import { Order } from '@life-payment/components';
import Taro from '@tarojs/taro';
defineOptions({
  name: 'InnerPage',
});
function goPay() {
  Taro.navigateTo({
    url: RouterPath.selectPayType,
  });
}
</script>
apps/taro/src/subpackages/order/order/order.config.ts
New file
@@ -0,0 +1,3 @@
export default definePageConfig({
  disableScroll: true,
});
apps/taro/src/subpackages/order/order/order.vue
New file
@@ -0,0 +1,14 @@
<template>
  <PageLayout title="订单管理" class="order-page-wrapper" hasBorder>
    <InnerPage />
  </PageLayout>
</template>
<script setup lang="ts">
import { PageLayout } from '@/components';
import InnerPage from './InnerPage.vue';
defineOptions({
  name: 'order',
});
</script>
packages/components/src/components/Card/OrderCard.vue
New file
@@ -0,0 +1,59 @@
<template>
  <div class="order-card">
    <div class="order-card-title">
      <div class="order-card-title-top">
        <div class="order-card-title-text">{{ title }}</div>
        <div class="order-card-title-status">{{ status }}</div>
      </div>
      <div class="order-card-title-ordernum">
        {{ `订单编号:${'JF202502191515350002'}` }}
      </div>
    </div>
    <div class="order-card-content">
      <slot></slot>
    </div>
  </div>
</template>
<script setup lang="ts">
defineOptions({
  name: 'OrderCard',
});
type Props = {
  title: string;
  status: string;
};
const props = withDefaults(defineProps<Props>(), {});
</script>
<style lang="scss">
.order-card {
  border: 1px solid #e8e8e8;
  border-radius: 12px;
  padding: 20px;
  .order-card-title {
    display: flex;
    flex-direction: column;
    border-bottom: 1px solid #e8e8e8;
    padding-bottom: 20px;
    margin-bottom: 20px;
    .order-card-title-top {
      display: flex;
      justify-content: space-between;
      color: #333333;
      font-size: 28px;
      line-height: 40px;
      font-weight: 600;
      margin-bottom: 12px;
    }
    .order-card-title-ordernum {
      font-size: 24px;
      color: #999999;
    }
  }
}
</style>
packages/components/src/components/Card/OrderCardItem.vue
New file
@@ -0,0 +1,47 @@
<template>
  <div class="order-card-item">
    <div class="order-card-item-label" :style="{ width: labelWidth, textAlign: textAlign }">
      <slot name="label">{{ label }}</slot>
    </div>
    <div class="order-card-item-value">
      <slot name="value">{{ value }}</slot>
    </div>
  </div>
</template>
<script setup lang="ts">
defineOptions({
  name: 'OrderCardItem',
});
type Props = {
  label: string;
  value: string;
  labelWidth?: any;
  textAlign?: any;
};
const props = withDefaults(defineProps<Props>(), {
  labelWidth: '80px',
  textAlign: 'left',
});
</script>
<style lang="scss">
.order-card-item {
  display: flex;
  font-size: 28px;
  line-height: 40px;
  margin-bottom: 15px;
  .order-card-item-label {
    color: #333333;
  }
  .order-card-item-value {
    color: #666666;
    flex: 1;
    min-width: 0;
  }
}
</style>
packages/components/src/components/InfiniteLoading/InfiniteLoading.vue
New file
@@ -0,0 +1,235 @@
<template>
  <scroll-view
    :scroll-y="true"
    :refresher-background="'transparent'"
    :lowerThreshold="100"
    @scrolltolower="lower"
    :refresherEnabled="refresherEnabled"
    :refresherTriggered="state.triggered"
    @refresherrefresh="onRefresherRefresh"
    :show-scrollbar="false"
    :style="scrollViewStyle"
    :enable-back-to-top="true"
    :class="[scrollViewClassName]"
    @scroll="onScroll"
    :scroll-top="scrollDistance"
    enhanced
    :scroll-with-animation="true"
    v-bind="{ ...$attrs }"
  >
    <slot name="header"></slot>
    <LoadingLayout :loading="isLoading" :error="isError" :loadError="() => refetch?.()">
      <NoData v-if="hasNoData" />
      <div
        :class="['infinite-list-inner', { noShowMoreText: !showMoreText, hasPaddingTop }]"
        v-else
      >
        <slot name="extra" />
        <slot v-if="$slots.default" />
        <template v-else>
          <template v-for="(group, index) in listData.pages" :key="index">
            <template v-for="(item, i) in group.data" :key="i">
              <slot
                name="renderItem"
                :item="item"
                :groupIndex="index"
                :itemIndex="i"
                :index="findDataIndex(item)"
              />
            </template>
          </template>
        </template>
      </div>
      <div
        v-if="!hasNoData && showMoreText && listData?.pages?.length > 0 && !hasMore"
        class="loading-more-tips"
      >
        {{ noMoreText }}
      </div>
      <div v-if="isFetching && hasMore && enabledLoadingMore" class="infiniting-tips">
        <Loading class="infiniting-tips-icon"></Loading>数据加载中...
      </div>
    </LoadingLayout>
  </scroll-view>
  <div class="back-top-wrapper" @click.stop="backToTop" v-show="oldScrollDistance > 100">
    <img class="back-top-img" :src="IconBackTop" />
    <div class="back-top-text">返回顶部</div>
  </div>
</template>
<script lang="ts" setup generic="T">
import { VNode, CSSProperties, computed, reactive } from 'vue';
import NoData from '../NoData/NoData.vue';
import LoadingLayout from '../Layout/LoadingLayout.vue';
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';
defineOptions({
  name: 'InfiniteLoading',
});
type Page = {
  data?: T[];
  pageModel?: {
    rows?: number;
    page?: number;
    totalCount?: number;
    totalPage?: number;
  };
  [key: string]: any;
};
type TData = {
  pages: Page[];
};
type Props = {
  // list?: TData;
  listData?: TData;
  flattenListData?: T[];
  renderItem?: (item: T, index: number) => VNode;
  refresherEnabled?: boolean;
  hasMore?: boolean;
  isLoading?: boolean;
  isError?: boolean;
  isFetching?: boolean;
  isFetchingNextPage?: boolean;
  refetch?: (options?: any) => Promise<any>;
  scrollViewStyle?: string | CSSProperties;
  scrollViewClassName?: string;
  fetchNextPage?: (options?: FetchNextPageOptions) => Promise<any>;
  showMoreText?: boolean;
  hasPaddingTop?: boolean;
  noMoreText?: string;
  enabledLoadingMore?: boolean;
  customNoData?: boolean;
};
const props = withDefaults(defineProps<Props>(), {
  renderItem: () => () => null,
  refresherEnabled: true,
  showMoreText: true,
  hasPaddingTop: false,
  noMoreText: '没有更多内容了~',
  enabledLoadingMore: true,
  customNoData: undefined,
});
const emit = defineEmits<{
  (e: 'refresh', done: () => any): void;
  (e: 'loadMore'): void;
}>();
const hasNoData = computed(() => {
  if (props.customNoData !== undefined) return props.customNoData;
  if (props.listData?.pages?.length) {
    return props.listData?.pages[0].data.length === 0;
  }
  return true;
});
const state = reactive({
  triggered: false,
});
function lower() {
  if (props.hasMore && props.enabledLoadingMore) {
    props.fetchNextPage?.();
    emit('loadMore');
  }
}
// eslint-disable-next-line no-unused-vars
async function onRefresherRefresh() {
  try {
    // 正处于刷新状态
    if (state.triggered) return;
    state.triggered = true;
    await props.refetch?.();
    state.triggered = false;
  } catch (error) {}
}
const { onScroll, scrollDistance, oldScrollDistance, setScrollDistance } = useScrollDistance();
function backToTop() {
  setScrollDistance(0);
}
const scrollToBottom = (dis = 300) => {
  setScrollDistance(scrollDistance.value + dis);
};
function findDataIndex(item: T) {
  return props.flattenListData?.findIndex((data) => data === item);
}
defineExpose({ backToTop, scrollToBottom });
</script>
<style lang="scss">
@import '@/styles/common.scss';
.loading-more-tips {
  color: boleGetCssVar('text-color', 'primary');
  padding: 18px 10px;
  width: auto;
  font-size: 24px;
  text-align: center;
}
.infinite-list-inner {
  // padding: 30px 30px 0;
  &.hasPaddingTop {
    padding-top: 20px;
  }
  &.noShowMoreText {
    padding-bottom: 30px;
  }
}
.infiniting-tips {
  color: boleGetCssVar('text-color', 'primary');
  padding: 18px 10px;
  width: auto;
  font-size: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  .infiniting-tips-icon {
    margin-right: 10px;
  }
}
.back-top-wrapper {
  width: 92px;
  height: 92px;
  background: #ffffff;
  box-shadow: 0px 0px 28px 0px rgba(0, 0, 0, 0.18);
  position: fixed;
  border-radius: 50%;
  right: boleGetCssVar('size', 'body-padding-h');
  bottom: 390px;
  .back-top-img {
    width: 44px;
    height: 44px;
    margin: 12px auto 2px;
  }
  .back-top-text {
    font-weight: 400;
    font-size: 16px;
    color: boleGetCssVar('text-color', 'regular');
    line-height: 22px;
    text-align: center;
  }
}
</style>
packages/components/src/index.ts
@@ -1,4 +1,5 @@
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';
packages/components/src/utils/lifeRechargeConstants.ts
@@ -46,4 +46,34 @@
    [IspCode.dianxin]: '中国电信',
    [IspCode.liantong]: '中国联通',
  };
  export enum ElectricType {
    /**国家电网 */
    guowang = 'guowang',
    /**南方电网 */
    nanwang = 'nanwang',
  }
  export const ElectricTypeText = {
    [ElectricType.guowang]: '国家电网',
    [ElectricType.nanwang]: '南方电网',
  };
  export enum ElectricAccountType {
    /**住宅 */
    house = 'house',
    /**企事业 */
    enterprise = 'enterprise',
    /**店铺 */
    shop = 'shop',
    /**店铺-不建议使用 */
    nanwashopng = 'nanwashopng',
  }
  export const ElectricAccountTypeText = {
    [ElectricAccountType.house]: '住宅',
    [ElectricAccountType.enterprise]: '企事业',
    [ElectricAccountType.shop]: '店铺',
    [ElectricAccountType.nanwashopng]: '店铺-不建议使用',
  };
}
packages/components/src/views/Order/Order.vue
New file
@@ -0,0 +1,24 @@
<template>
  <div class="order-wrapper">
    <nut-tabs v-model="orderType">
      <nut-tab-pane title="话费订单" pane-key="1">
        <PhoneOrder></PhoneOrder>
      </nut-tab-pane>
      <nut-tab-pane title="电费订单" pane-key="2">
        <ElectricOrder></ElectricOrder>
      </nut-tab-pane>
    </nut-tabs>
  </div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import PhoneOrder from './components/PhoneOrder.vue';
import ElectricOrder from './components/ElectricOrder.vue';
defineOptions({
  name: 'Order',
});
const orderType = ref(1);
</script>
packages/components/src/views/Order/components/ElectricOrder.vue
New file
@@ -0,0 +1,34 @@
<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="'2025-02-19  17:15:54'" />
    <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';
defineOptions({
  name: 'ElectricOrder',
});
// type Props = {};
// const props = withDefaults(defineProps<Props>(), {});
const infiniteLoadingProps = {};
</script>
<style lang="scss"></style>
packages/components/src/views/Order/components/PhoneOrder.vue
New file
@@ -0,0 +1,33 @@
<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> -->
</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';
defineOptions({
  name: 'PhoneOrder',
});
// type Props = {};
// const props = withDefaults(defineProps<Props>(), {});
const infiniteLoadingProps = {};
</script>
<style lang="scss"></style>
packages/components/src/views/electricBillRecharge/electricBillRecharge.vue
@@ -6,32 +6,46 @@
    label-position="top"
    class="order-bill-recharge electric"
  >
    <FormItem label="所在城市" class="bole-form-item" prop="type" required>
    <FormItem label="所在城市" class="bole-form-item" prop="province" required>
      <ChooseInputWithPicker
        v-model="form.type"
        v-model="form.province"
        placeholder="请选择城市"
        :value-enum="IspCodeText"
      />
    </FormItem>
    <FormItem label="电网类型" class="bole-form-item" prop="type" required>
    <FormItem label="电网类型" class="bole-form-item" prop="electricType" required>
      <ChooseInputWithPicker
        v-model="form.type"
        v-model="form.electricType"
        placeholder="请选择电网类型"
        :value-enum="IspCodeText"
        :value-enum="blLifeRecharge.constants.ElectricTypeText"
      />
    </FormItem>
    <FormItem label="电费类型" class="bole-form-item" prop="type" required>
    <FormItem label="电费类型" class="bole-form-item" prop="electricAccountType" required>
      <ChooseInputWithPicker
        v-model="form.type"
        v-model="form.electricAccountType"
        placeholder="请选择电费类型"
        :value-enum="IspCodeText"
        :value-enum="blLifeRecharge.constants.ElectricAccountTypeText"
      />
    </FormItem>
    <FormItem label="电网户号" class="bole-form-item" prop="phone" required>
    <FormItem label="电网户号" class="bole-form-item" prop="electricAccount" required>
      <Input
        v-model.trim="form.phone"
        v-model.trim="form.electricAccount"
        class="bole-input-text"
        placeholder="请输入13位数字编号"
        type="text"
      />
    </FormItem>
    <FormItem
      v-if="form.electricType === blLifeRecharge.constants.ElectricType.nanwang"
      label="身份证后六位"
      class="bole-form-item"
      prop="sixID"
      required
    >
      <Input
        v-model.trim="form.sixID"
        class="bole-input-text"
        placeholder="请输入身份证后六位"
        type="text"
      />
    </FormItem>
@@ -107,9 +121,12 @@
const form = reactive({
  ispCode: IspCode.yidong,
  phone: '',
  parValue: 100,
  type: IspCodeText.yidong,
  electricAccount: '',
  electricType: '',
  electricAccountType: '',
  province: '',
  sixID: '',
});
const rate = 0.96;
packages/services/api/LifePay.ts
@@ -118,7 +118,7 @@
  body: API.RefundLifePayOrderInput,
  options?: API.RequestConfig
) {
  return request<any>('/api/LifePay/RefundLifePayOrder', {
  return request<number>('/api/LifePay/RefundLifePayOrder', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',