From 988837b3fbffb8abea06dcb875f5746b9fce3c57 Mon Sep 17 00:00:00 2001
From: zhengyiming <540361168@qq.com>
Date: 星期四, 20 二月 2025 18:22:20 +0800
Subject: [PATCH] fix: 页面

---
 apps/taro/src/app.config.ts                                                      |    4 
 packages/components/src/components/RechargeTipsView/RechargeTipsView.vue         |   27 ++
 apps/taro/src/constants/router.ts                                                |    2 
 apps/taro/src/subpackages/recharge/phoneBillRecharge/InnerPage.vue               |   13 +
 packages/components/src/views/PhoneBillRecharge/PhoneBillRecharge.vue            |  121 +++++++++
 packages/components/src/components/Dialog/ConfirmDialogInfoItem.vue              |   20 +
 apps/taro/src/app.ts                                                             |    7 
 packages/components/src/styles/nut.scss                                          |  173 +++++++++++++
 packages/components/src/utils/index.ts                                           |    2 
 packages/components/src/styles/components.scss                                   |   70 +++++
 packages/components/src/index.ts                                                 |    2 
 packages/components/src/styles/index.scss                                        |    3 
 packages/components/src/styles/rechargeGrid.scss                                 |  128 +++++++++
 packages/components/src/views/RechargeGrid/RechargeGrid.vue                      |   12 
 packages/components/src/utils/plugin.ts                                          |   25 +
 apps/taro/src/index.html                                                         |   30 +-
 apps/taro/src/subpackages/recharge/phoneBillRecharge/phoneBillRecharge.vue       |   14 +
 packages/components/src/styles/var.scss                                          |    1 
 packages/components/src/components/Radio/Radio.vue                               |   17 +
 packages/components/src/constants/index.ts                                       |   14 +
 apps/taro/src/pages/home/index.vue                                               |   28 -
 packages/components/src/components/Dialog/ConfirmDialog.vue                      |   24 +
 packages/components/src/utils/lifeRecharge.ts                                    |    5 
 apps/taro/src/subpackages/recharge/phoneBillRecharge/phoneBillRecharge.config.ts |    3 
 apps/taro/config/index.js                                                        |    3 
 25 files changed, 714 insertions(+), 34 deletions(-)

diff --git a/apps/taro/config/index.js b/apps/taro/config/index.js
index f29f8a6..af0023b 100644
--- a/apps/taro/config/index.js
+++ b/apps/taro/config/index.js
@@ -193,6 +193,9 @@
   h5: {
     publicPath: '/',
     staticDirectory: 'static',
+    router: {
+      mode: 'browser',
+    },
     postcss: {
       autoprefixer: {
         enable: true,
diff --git a/apps/taro/src/app.config.ts b/apps/taro/src/app.config.ts
index b10e3dd..c55c549 100644
--- a/apps/taro/src/app.config.ts
+++ b/apps/taro/src/app.config.ts
@@ -51,6 +51,10 @@
         'registerForm/registerForm',
       ],
     },
+    {
+      root: 'subpackages/recharge',
+      pages: ['phoneBillRecharge/phoneBillRecharge'],
+    },
   ],
   // preloadRule: {
   //   'pages/mine/index': {
diff --git a/apps/taro/src/app.ts b/apps/taro/src/app.ts
index 5039280..97fc876 100644
--- a/apps/taro/src/app.ts
+++ b/apps/taro/src/app.ts
@@ -10,6 +10,7 @@
 import Taro from '@tarojs/taro';
 import { VueQueryPlugin, VueQueryPluginOptions } from '@tanstack/vue-query';
 import { myClient } from '@/constants/query';
+import { VueLifeRechargePlugin, BlLifeRecharge } from '@life-payment/components';
 
 window.uni = Taro;
 
@@ -82,4 +83,10 @@
 
 App.use(VueQueryPlugin, vueQueryPluginOptions);
 
+const blLifeRecharge = new BlLifeRecharge();
+
+App.use(VueLifeRechargePlugin, {
+  blLifeRecharge,
+});
+
 export default App;
diff --git a/apps/taro/src/constants/router.ts b/apps/taro/src/constants/router.ts
index 6c45c71..95b1024 100644
--- a/apps/taro/src/constants/router.ts
+++ b/apps/taro/src/constants/router.ts
@@ -7,4 +7,6 @@
   registerForm = '/subpackages/login/registerForm/registerForm',
   home = '/pages/home/index',
   mine = '/pages/mine/index',
+
+  phoneBillRecharge = '/subpackages/recharge/phoneBillRecharge/phoneBillRecharge',
 }
diff --git a/apps/taro/src/index.html b/apps/taro/src/index.html
index 46e4d7c..49a37be 100644
--- a/apps/taro/src/index.html
+++ b/apps/taro/src/index.html
@@ -1,17 +1,19 @@
 <!DOCTYPE html>
 <html>
-<head>
-  <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
-  <meta content="width=device-width,initial-scale=1,user-scalable=no" name="viewport">
-  <meta name="apple-mobile-web-app-capable" content="yes">
-  <meta name="apple-touch-fullscreen" content="yes">
-  <meta name="format-detection" content="telephone=no,address=no">
-  <meta name="apple-mobile-web-app-status-bar-style" content="white">
-  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" >
-  <title>vue-mini</title>
-  <script><%= htmlWebpackPlugin.options.script %></script>
-</head>
-<body>
-  <div id="app"></div>
-</body>
+  <head>
+    <meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
+    <meta content="width=device-width,initial-scale=1,user-scalable=no" name="viewport" />
+    <meta name="apple-mobile-web-app-capable" content="yes" />
+    <meta name="apple-touch-fullscreen" content="yes" />
+    <meta name="format-detection" content="telephone=no,address=no" />
+    <meta name="apple-mobile-web-app-status-bar-style" content="white" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+    <title>鐢熸椿缂磋垂</title>
+    <script>
+      <%= htmlWebpackPlugin.options.script %>
+    </script>
+  </head>
+  <body>
+    <div id="app"></div>
+  </body>
 </html>
diff --git a/apps/taro/src/pages/home/index.vue b/apps/taro/src/pages/home/index.vue
index 42fca5c..44c8fdb 100644
--- a/apps/taro/src/pages/home/index.vue
+++ b/apps/taro/src/pages/home/index.vue
@@ -1,7 +1,7 @@
 <template>
   <PageLayoutWithBg class="index-page-wrapper" :title="'鐢熸椿缂磋垂'" :need-auth="false">
     <ContentView>
-      <RechargeGrid />
+      <RechargeGrid @phoneBillRecharge="goPhoneBillRecharge" />
     </ContentView>
   </PageLayoutWithBg>
 </template>
@@ -10,25 +10,21 @@
 import { useUser, useInfiniteLoading } from '@/hooks';
 import { useUserStore } from '@/stores/modules/user';
 import Taro from '@tarojs/taro';
-import _ from 'lodash';
 import IconLogo from '@/assets/home/icon-logo.png';
 import { OrderInputType } from '@life-payment/constants';
 import { RechargeGrid } from '@life-payment/components';
 
-const { locationCity } = useUser();
-
-const userStore = useUserStore();
-
-const queryState = reactive({});
-
-const list = ref([
-  'https://storage.360buyimg.com/jdc-article/NutUItaro34.jpg',
-  'https://storage.360buyimg.com/jdc-article/NutUItaro2.jpg',
-  'https://storage.360buyimg.com/jdc-article/welcomenutui.jpg',
-  'https://storage.360buyimg.com/jdc-article/fristfabu.jpg',
-]);
-
-const selectItem = ref();
+function goPhoneBillRecharge() {
+  Taro.navigateTo({
+    url: RouterPath.phoneBillRecharge,
+  })
+    .then(() => {
+      console.log('Navigate successfully');
+    })
+    .catch((err) => {
+      console.error('Navigation failed:', err);
+    });
+}
 </script>
 
 <style lang="scss">
diff --git a/apps/taro/src/subpackages/recharge/phoneBillRecharge/InnerPage.vue b/apps/taro/src/subpackages/recharge/phoneBillRecharge/InnerPage.vue
new file mode 100644
index 0000000..f77691f
--- /dev/null
+++ b/apps/taro/src/subpackages/recharge/phoneBillRecharge/InnerPage.vue
@@ -0,0 +1,13 @@
+<template>
+  <ContentScrollView :paddingH="false">
+    <PhoneBillRecharge />
+  </ContentScrollView>
+</template>
+
+<script setup lang="ts">
+import { PhoneBillRecharge } from '@life-payment/components';
+
+defineOptions({
+  name: 'InnerPage',
+});
+</script>
diff --git a/apps/taro/src/subpackages/recharge/phoneBillRecharge/phoneBillRecharge.config.ts b/apps/taro/src/subpackages/recharge/phoneBillRecharge/phoneBillRecharge.config.ts
new file mode 100644
index 0000000..305fdb1
--- /dev/null
+++ b/apps/taro/src/subpackages/recharge/phoneBillRecharge/phoneBillRecharge.config.ts
@@ -0,0 +1,3 @@
+export default definePageConfig({
+  disableScroll: true,
+});
diff --git a/apps/taro/src/subpackages/recharge/phoneBillRecharge/phoneBillRecharge.vue b/apps/taro/src/subpackages/recharge/phoneBillRecharge/phoneBillRecharge.vue
new file mode 100644
index 0000000..7b21ce8
--- /dev/null
+++ b/apps/taro/src/subpackages/recharge/phoneBillRecharge/phoneBillRecharge.vue
@@ -0,0 +1,14 @@
+<template>
+  <PageLayout title="璇濊垂鍏呭��" class="phoneBillRecharge-page-wrapper" hasBorder :need-auth="false">
+    <InnerPage />
+  </PageLayout>
+</template>
+
+<script setup lang="ts">
+import { PageLayout } from '@/components';
+import InnerPage from './InnerPage.vue';
+
+defineOptions({
+  name: 'phoneBillRecharge',
+});
+</script>
diff --git a/packages/components/src/components/Dialog/ConfirmDialog.vue b/packages/components/src/components/Dialog/ConfirmDialog.vue
new file mode 100644
index 0000000..abea7ef
--- /dev/null
+++ b/packages/components/src/components/Dialog/ConfirmDialog.vue
@@ -0,0 +1,24 @@
+<template>
+  <nut-dialog title="璇锋牳瀵瑰厖鍊间俊鎭苟鏀粯" v-model:visible="visible">
+    <div class="confirm-dialog-content">
+      <div class="confirm-dialog-content-tips">
+        璇ヤ骇鍝佷负鎱㈠厖妯″紡锛�0-24灏忔椂鍐呭埌璐︼紝浠嬫剰璇峰嬁浠樻锛� 鍏呭�煎墠璇蜂粩缁嗛槄璇诲厖鍊奸』鐭ワ紒
+      </div>
+      <div class="confirm-dialog-content-info">
+        <slot name="info"></slot>
+      </div>
+    </div>
+  </nut-dialog>
+</template>
+
+<script setup lang="ts">
+defineOptions({
+  name: 'ConfirmDialog',
+});
+
+// type Props = {};
+
+// const props = withDefaults(defineProps<Props>(), {});
+
+const visible = defineModel<boolean>('visible');
+</script>
diff --git a/packages/components/src/components/Dialog/ConfirmDialogInfoItem.vue b/packages/components/src/components/Dialog/ConfirmDialogInfoItem.vue
new file mode 100644
index 0000000..17a7681
--- /dev/null
+++ b/packages/components/src/components/Dialog/ConfirmDialogInfoItem.vue
@@ -0,0 +1,20 @@
+<template>
+  <div class="confirm-dialog-content-info-item" :class="{ danger }">
+    <div class="confirm-dialog-content-info-item-label">{{ label }}</div>
+    <div class="confirm-dialog-content-info-item-content">{{ content }}</div>
+  </div>
+</template>
+
+<script setup lang="ts">
+defineOptions({
+  name: 'ConfirmDialogInfoItem',
+});
+
+type Props = {
+  label?: string;
+  content?: string;
+  danger?: boolean;
+};
+
+const props = withDefaults(defineProps<Props>(), {});
+</script>
diff --git a/packages/components/src/components/Radio/Radio.vue b/packages/components/src/components/Radio/Radio.vue
new file mode 100644
index 0000000..cba0b16
--- /dev/null
+++ b/packages/components/src/components/Radio/Radio.vue
@@ -0,0 +1,17 @@
+<template>
+  <Radio class="bl-radio">
+    <template #default><slot /> </template>
+    <template #checkedIcon>
+      <IconFont name="checked" color="var(--bole-color-primary)" />
+    </template>
+  </Radio>
+</template>
+
+<script setup lang="ts">
+import { IconFont } from '@nutui/icons-vue-taro';
+import { Radio } from '@nutui/nutui-taro';
+
+defineOptions({
+  name: 'bl-radio',
+});
+</script>
diff --git a/packages/components/src/components/RechargeTipsView/RechargeTipsView.vue b/packages/components/src/components/RechargeTipsView/RechargeTipsView.vue
new file mode 100644
index 0000000..ac45de4
--- /dev/null
+++ b/packages/components/src/components/RechargeTipsView/RechargeTipsView.vue
@@ -0,0 +1,27 @@
+<template>
+  <div class="recharge-tips-view">
+    <div class="recharge-tips-title">鍏呭�奸』鐭�</div>
+    <div class="recharge-tips-content">
+      <div class="recharge-tips-top">
+        *鍚屼竴鍙风爜鍏呭�兼湡闂淬�愬垏鍕垮骞冲彴閲嶅鍏呭�笺�戯紒锛侊紒鍦ㄤ笅鍗曞墠锛岃鍔″繀浠旂粏闃呰鍏憡鍐呭锛侊紒锛佽嫢鎺ュ埌闄岀敓鏉ョ數锛岃鍕胯交淇★紒锛侊紒
+      </div>
+      <div class="recharge-tips-list">
+        <div class="recharge-tips-item" v-for="(item, index) in props.tips" :key="index">
+          {{ index + 1 }}.{{ item }}
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+defineOptions({
+  name: 'RechargeTipsView',
+});
+
+type Props = {
+  tips: string[];
+};
+
+const props = withDefaults(defineProps<Props>(), {});
+</script>
diff --git a/packages/components/src/constants/index.ts b/packages/components/src/constants/index.ts
new file mode 100644
index 0000000..a3916b6
--- /dev/null
+++ b/packages/components/src/constants/index.ts
@@ -0,0 +1,14 @@
+export enum IspCode {
+  /**涓浗绉诲姩 */
+  yidong = 'yidong',
+  /**涓浗鐢典俊 */
+  dianxin = 'dianxin',
+  /**涓浗鑱旈�� */
+  liantong = 'liantong',
+}
+
+export const IspCodeText = {
+  [IspCode.yidong]: '涓浗绉诲姩',
+  [IspCode.dianxin]: '涓浗鐢典俊',
+  [IspCode.liantong]: '涓浗鑱旈��',
+};
diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts
index 1bb8f4c..d7d994b 100644
--- a/packages/components/src/index.ts
+++ b/packages/components/src/index.ts
@@ -1 +1,3 @@
 export { default as RechargeGrid } from './views/RechargeGrid/RechargeGrid.vue';
+export { default as PhoneBillRecharge } from './views/PhoneBillRecharge/PhoneBillRecharge.vue';
+export * from './utils';
diff --git a/packages/components/src/styles/components.scss b/packages/components/src/styles/components.scss
new file mode 100644
index 0000000..9d54699
--- /dev/null
+++ b/packages/components/src/styles/components.scss
@@ -0,0 +1,70 @@
+/*postcss-pxtransform disable*/
+@use './common.scss' as *;
+
+.recharge-tips-view {
+  padding: 16px;
+  border-radius: 12px;
+  background: linear-gradient(0deg, rgba(10, 90, 255, 0.04), rgba(10, 90, 255, 0.04)), #fff;
+
+  .recharge-tips-title {
+    color: #1f2229;
+    font-size: 14px;
+    font-weight: 700;
+    line-height: 18px;
+    padding: 4px 0 12px 0;
+    border-bottom: 1px solid boleGetCssVar('color', 'primary');
+  }
+
+  .recharge-tips-content {
+    color: boleGetCssVar('text-color', 'regular');
+    font-size: 13px;
+    font-weight: 400;
+    line-height: 21px;
+    padding-top: 16px;
+
+    .recharge-tips-top {
+      color: #e03e2d;
+      font-size: 18px;
+    }
+  }
+}
+
+.confirm-dialog-content {
+  .confirm-dialog-content-tips {
+    margin-bottom: 16px;
+  }
+
+  .confirm-dialog-content-info {
+    padding: 12px;
+    border-radius: 8px;
+    background: #f0f3fa;
+
+    .confirm-dialog-content-info-item {
+      display: flex;
+      margin-bottom: 10px;
+
+      &:last-child {
+        margin-bottom: 0;
+      }
+
+      .confirm-dialog-content-info-item-label {
+        color: boleGetCssVar('text-color', 'regular');
+        margin-right: 6px;
+      }
+
+      .confirm-dialog-content-info-item-content {
+        flex: 1;
+        min-width: 0;
+        @include ellipsis;
+        color: boleGetCssVar('text-color', 'primary');
+        text-align: left;
+      }
+
+      &.danger {
+        .confirm-dialog-content-info-item-content {
+          color: boleGetCssVar('color', 'danger');
+        }
+      }
+    }
+  }
+}
diff --git a/packages/components/src/styles/index.scss b/packages/components/src/styles/index.scss
index 878dc2d..e60a34e 100644
--- a/packages/components/src/styles/index.scss
+++ b/packages/components/src/styles/index.scss
@@ -1,7 +1,10 @@
 @use 'sass:map';
 @use './var.scss' as *;
 @use './function.scss' as *;
+@use './nut.scss' as *;
 @use './layout.scss' as *;
+@use './rechargeGrid.scss' as *;
+@use './components.scss' as *;
 
 :root,
 page {
diff --git a/packages/components/src/styles/nut.scss b/packages/components/src/styles/nut.scss
new file mode 100644
index 0000000..f3da60f
--- /dev/null
+++ b/packages/components/src/styles/nut.scss
@@ -0,0 +1,173 @@
+/*postcss-pxtransform disable*/
+
+@use './function.scss' as *;
+@use './hairline.scss' as *;
+
+:root,
+page {
+  .category-searchbar-container {
+    padding: 30px;
+  }
+
+  .h5-view.nut-popup.nut-popup--bottom {
+    padding-bottom: constant(safe-area-inset-bottom);
+    padding-bottom: env(safe-area-inset-bottom);
+  }
+
+  .nut-button.dark-btn {
+    color: boleGetCssVar('text-color', 'primary') !important;
+  }
+
+  .nut-form {
+    .nut-cell-group__wrap {
+      margin-top: 0;
+      border-radius: 0;
+      box-shadow: none;
+    }
+
+    .nut-cell.bole-form-item {
+      --nut-form-item-label-width: 120px;
+      &:not(.alignTop) {
+        align-items: center !important;
+      }
+
+      &.labelTop {
+        display: block;
+      }
+
+      padding: 30rpx;
+
+      &::after {
+        border-bottom: 1px solid #f0f0f0;
+        left: 30rpx;
+        right: 30rpx;
+        display: none;
+      }
+
+      .nut-form-item__body__tips {
+        position: absolute;
+        bottom: 0;
+      }
+      &:not(.bole-form-item-phone) {
+        .nut-form-item__body__tips {
+          right: 30rpx;
+        }
+      }
+
+      .nut-form-item__label {
+        font-size: 28rpx;
+        color: boleGetCssVar('text-color', 'primary');
+      }
+
+      &.vertical {
+        flex-direction: column;
+        align-items: flex-start !important;
+      }
+
+      .nut-form-item__label.required::before {
+        position: absolute;
+        left: 12rpx;
+      }
+
+      .nut-radio-group--horizontal {
+        .nut-radio {
+          margin-bottom: 0;
+          vertical-align: middle;
+        }
+      }
+
+      .nut-rate {
+        vertical-align: middle;
+      }
+
+      &.hidden-label {
+        .nut-form-item__label {
+          opacity: 0;
+        }
+      }
+
+      .bole-uploader {
+        padding-top: 16rpx;
+
+        &.nopaddingtop {
+          padding-top: 0;
+        }
+      }
+    }
+
+    .bole-form-item-phone {
+      padding: 24rpx 30rpx;
+    }
+
+    .nut-uploader {
+      // justify-content: flex-end;
+    }
+
+    .nut-form-item__top {
+      .nut-form-item__label {
+        margin-right: 0;
+      }
+
+      .nut-form-item__body {
+        width: 100%;
+      }
+    }
+  }
+
+  .bole-input-text:not(.nut-input--disabled) {
+    .h5-input {
+      color: boleGetCssVar('text-color', 'primary') !important;
+      display: block;
+      font-size: 26rpx;
+    }
+
+    .input-placeholder {
+      color: boleGetCssVar('text-color', 'placeholder') !important;
+      font-size: 26rpx;
+    }
+  }
+
+  .bole-input-textarea:not(.nut-input--disabled) {
+    color: boleGetCssVar('text-color', 'primary') !important;
+    height: 100rpx;
+  }
+
+  .bole-input-text-placeholder {
+    color: boleGetCssVar('text-color', 'placeholder') !important;
+    font-size: 26rpx;
+    line-height: 20rpx;
+  }
+
+  .form-item-divider {
+    margin: 0 28px;
+    width: calc(100% - 56px);
+    color: #f0f0f0;
+  }
+
+  // .nut-overlay {
+  //   z-index: 20022221 !important;
+  // }
+
+  // .nut-popup {
+  //   z-index: 2222222222 !important;
+  // }
+
+  .nut-tabs__titles-item__smile {
+    // display: none;
+    opacity: 0;
+    transition: width 0.3s ease;
+  }
+
+  .nut-tabs__titles.smile .nut-tabs__titles-item.active .nut-tabs__titles-item__smile {
+    // display: block;
+    opacity: 1;
+  }
+
+  .nut-toast {
+    z-index: 99999999999;
+  }
+
+  .pro-form-item-tips {
+    word-break: break-all;
+  }
+}
diff --git a/packages/components/src/styles/rechargeGrid.scss b/packages/components/src/styles/rechargeGrid.scss
new file mode 100644
index 0000000..1099404
--- /dev/null
+++ b/packages/components/src/styles/rechargeGrid.scss
@@ -0,0 +1,128 @@
+/*postcss-pxtransform disable*/
+@use './common.scss' as *;
+
+.recharge-grid-wrapper {
+  padding-left: 0 !important;
+  margin-top: 20px;
+
+  .recharge-grid-item {
+    .nut-grid-item__text {
+      font-size: 18px;
+      color: boleGetCssVar('text-color', 'primary');
+    }
+  }
+}
+
+.common-content {
+  padding: 0 boleGetCssVar('size', 'body-padding-h2');
+}
+
+.parValue-radio-group {
+  width: 100%;
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  grid-gap: 10px;
+
+  .parValue-item {
+    margin-right: 0;
+
+    .nut-radio__button {
+      width: 100%;
+      padding: 8px 0;
+      background-color: transparent;
+      border: 0.5px solid #e2e5eb;
+      text-align: center;
+      border-radius: 4px;
+      position: relative;
+    }
+
+    .nut-radio__button--active {
+      color: #fb5100;
+      border-color: #fb5100;
+      overflow: hidden;
+
+      &::after {
+        border-radius: 4px;
+        background-color: rgba(251, 81, 0, 0.08);
+        opacity: 1;
+      }
+
+      .parValue-item-inner {
+        .discountTag {
+          display: block;
+        }
+      }
+    }
+
+    .parValue-item-inner {
+      width: 100%;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+
+      .amount-wrapper {
+        display: flex;
+        align-items: flex-end;
+        margin-bottom: 2px;
+
+        .amount {
+          font-size: 20px;
+          font-weight: 400;
+          line-height: 22px;
+        }
+        .unit {
+          font-size: 12px;
+          font-weight: 400;
+          line-height: 16px;
+        }
+      }
+
+      .price-wrapper {
+        display: flex;
+        color: #858d98;
+        font-size: 12px;
+        font-weight: 400;
+        line-height: 16px;
+
+        .price {
+          color: #fb5100;
+          margin-left: 3px;
+        }
+      }
+
+      .discountTag {
+        display: none;
+        position: absolute;
+        padding: 1px 4px;
+        border-radius: 0px 0 10px 0;
+        border: 0.5px solid #fb5100;
+        background: linear-gradient(
+          186deg,
+          rgba(255, 129, 45, 0.08) 14.82%,
+          rgba(238, 67, 67, 0.08) 91.5%
+        );
+        color: #fb5100;
+        font-size: 11px;
+        font-weight: 700;
+        line-height: 14px;
+        position: absolute;
+        top: -1px;
+        left: -1px;
+      }
+    }
+  }
+}
+
+.phone-bill-recharge {
+  .recharge-button {
+    width: 100%;
+    margin: 20px 0;
+
+    .recharge-button-inner {
+      display: flex;
+      .recharge-button-text {
+        margin-left: 10px;
+      }
+    }
+  }
+}
diff --git a/packages/components/src/styles/var.scss b/packages/components/src/styles/var.scss
index 764e2db..1348eaf 100644
--- a/packages/components/src/styles/var.scss
+++ b/packages/components/src/styles/var.scss
@@ -27,6 +27,7 @@
 $bole-size: map.deep-merge(
   (
     'body-padding-h': 28px,
+    'body-padding-h2': 14px,
   ),
   $bole-size
 );
diff --git a/packages/components/src/utils/index.ts b/packages/components/src/utils/index.ts
new file mode 100644
index 0000000..1fa48cb
--- /dev/null
+++ b/packages/components/src/utils/index.ts
@@ -0,0 +1,2 @@
+export * from './lifeRecharge';
+export * from './plugin';
diff --git a/packages/components/src/utils/lifeRecharge.ts b/packages/components/src/utils/lifeRecharge.ts
new file mode 100644
index 0000000..0ef5a02
--- /dev/null
+++ b/packages/components/src/utils/lifeRecharge.ts
@@ -0,0 +1,5 @@
+export class BlLifeRecharge {
+  getRechargeParValue(amount: number, rate: number) {
+    return (amount * rate).toFixed(2);
+  }
+}
diff --git a/packages/components/src/utils/plugin.ts b/packages/components/src/utils/plugin.ts
new file mode 100644
index 0000000..519302a
--- /dev/null
+++ b/packages/components/src/utils/plugin.ts
@@ -0,0 +1,25 @@
+import { BlLifeRecharge } from './lifeRecharge';
+import { inject, shallowReactive } from 'vue';
+import type { InjectionKey, UnwrapNestedRefs, Ref } from 'vue';
+
+export type LifeRechargeContext = {
+  blLifeRecharge: UnwrapNestedRefs<BlLifeRecharge>;
+};
+
+const clientKey: InjectionKey<LifeRechargeContext> = Symbol('blLifeRecharge');
+
+export type VueLifeRechargePluginOptions = {
+  blLifeRecharge: BlLifeRecharge;
+};
+
+export const VueLifeRechargePlugin = {
+  install: (app: any, { blLifeRecharge }: VueLifeRechargePluginOptions) => {
+    app.provide(clientKey, {
+      blLifeRecharge: shallowReactive(blLifeRecharge),
+    });
+  },
+};
+
+export function useLifeRechargeContext() {
+  return inject(clientKey);
+}
diff --git a/packages/components/src/views/PhoneBillRecharge/PhoneBillRecharge.vue b/packages/components/src/views/PhoneBillRecharge/PhoneBillRecharge.vue
new file mode 100644
index 0000000..e629150
--- /dev/null
+++ b/packages/components/src/views/PhoneBillRecharge/PhoneBillRecharge.vue
@@ -0,0 +1,121 @@
+<template>
+  <Form
+    :model-value="form"
+    ref="formRef"
+    :rules="rules"
+    label-position="top"
+    class="phone-bill-recharge"
+  >
+    <FormItem label="閫夋嫨杩愯惀鍟�:" class="bole-form-item" prop="ispCode" required>
+      <RadioGroup v-model="form.ispCode" direction="horizontal">
+        <BlRadio :label="key" v-for="(val, key) in IspCodeText" :key="key">{{ val }}</BlRadio>
+      </RadioGroup>
+    </FormItem>
+    <FormItem label="鍏呭�兼墜鏈哄彿" class="bole-form-item" prop="phone" required>
+      <Input
+        v-model.trim="form.phone"
+        class="bole-input-text"
+        placeholder="璇峰~鍐欐偍闇�瑕佸厖鍊肩殑鎵嬫満鍙风爜"
+        type="text"
+      />
+    </FormItem>
+    <FormItem label="閫夋嫨鍏呭�奸噾棰�" class="bole-form-item" prop="parValue" required>
+      <RadioGroup v-model="form.parValue" direction="horizontal" class="parValue-radio-group">
+        <Radio
+          :label="item"
+          :key="item"
+          shape="button"
+          v-for="item in parValueList"
+          class="parValue-item"
+        >
+          <div class="parValue-item-inner">
+            <div class="amount-wrapper">
+              <div class="amount">{{ item }}</div>
+              <div class="unit">鍏�</div>
+            </div>
+            <div class="price-wrapper">
+              <div class="price-text">鎶樺悗</div>
+              <div class="price">{{ blLifeRecharge.getRechargeParValue(item, rate) }}鍏�</div>
+            </div>
+            <div class="discountTag">{{ rate * 100 }}鎶�</div>
+          </div>
+        </Radio>
+      </RadioGroup>
+    </FormItem>
+    <div class="common-content">
+      <nut-button class="recharge-button" type="primary" @click="recharge">
+        <div class="recharge-button-inner">
+          <div>锟{ form.parValue }}</div>
+          <div class="recharge-button-text">绔嬪嵆鍏呭��</div>
+        </div>
+      </nut-button>
+      <RechargeTipsView :tips="tips" />
+    </div>
+    <ConfirmDialog v-model:visible="confirmDialogVisible">
+      <template #info>
+        <ConfirmDialogInfoItem label="鍏呭�艰处鍙�" content="18858418480" />
+        <ConfirmDialogInfoItem label="鍏呭�奸噾棰�" :content="`锟�${form.parValue}`" danger />
+        <ConfirmDialogInfoItem label="浼樻儬閲戦" :content="`锟�${discountParValue}`" />
+        <ConfirmDialogInfoItem label="瀹炰粯閲戦" :content="`锟�${realParValue}`" danger />
+      </template>
+    </ConfirmDialog>
+  </Form>
+</template>
+
+<script setup lang="ts">
+import { Form, FormItem, RadioGroup, Radio, Input, Button as NutButton } from '@nutui/nutui-taro';
+import { FormRules } from '@nutui/nutui-taro/dist/types/__VUE/form/types';
+import { reactive, ref, computed } from 'vue';
+import BlRadio from '../../components/Radio/Radio.vue';
+import { IspCodeText, IspCode } from '../../constants';
+import { useLifeRechargeContext } from '../../utils';
+import RechargeTipsView from '../../components/RechargeTipsView/RechargeTipsView.vue';
+import ConfirmDialog from '../../components/Dialog/ConfirmDialog.vue';
+import ConfirmDialogInfoItem from '../../components/Dialog/ConfirmDialogInfoItem.vue';
+
+defineOptions({
+  name: 'PhoneBillRecharge',
+});
+
+const form = reactive({
+  ispCode: IspCode.yidong,
+  phone: '',
+  parValue: 100,
+});
+
+const rate = 0.96;
+
+const parValueList = [50, 100, 200];
+
+const realParValue = computed(() => blLifeRecharge.getRechargeParValue(form.parValue, rate));
+const discountParValue = computed(() => form.parValue - Number(realParValue.value));
+
+const { blLifeRecharge } = useLifeRechargeContext();
+
+const rules = reactive<FormRules>({});
+
+const formRef = ref<any>(null);
+
+function handleSubmit() {
+  if (!formRef.value) return;
+  formRef.value.validate().then(({ valid, errors }: any) => {
+    if (valid) {
+    }
+  });
+}
+
+const tips = [
+  '骞冲彴鎻愪緵鎱㈠厖鏈嶅姟锛岃鍗曟彁浜ゅ悗锛岃瘽璐瑰皢浜�0 - 24灏忔椂鍐呭埌璐︺�傝嫢鏈兘鎸夋椂鍒拌处锛岀郴缁熷皢鑷姩鍙戣捣閫�娆俱��',
+  '鍏呭�兼湡闂达紝鑻ュ悓涓�鍙风爜娆鹃」鏈埌璐︼紝璇峰嬁鍦ㄥ叾浠栧钩鍙伴噸澶嶅厖鍊硷紱涓诲壇鍗′笉鍙悓鏃跺厖鍊笺�傚洜涓婅堪鎿嶄綔瀵艰嚧鐨勮祫閲戞崯澶憋紝鐢辩敤鎴疯嚜琛屾壙鎷呫��',
+  '鏈钩鍙拌瘽璐瑰厖鍊兼湇鍔′笉閫傜敤浜庡凡鍋滄満鍙风爜銆傜數淇″彿鐮佽嫢鏈夋瑺璐癸紝涔熸棤娉曞畬鎴愬厖鍊笺�傜數淇″凡瀹屾垚缁存姢鐨勫尯鍩熷寘鎷細骞夸笢銆佹睙鑻忋�佹箹鍖椼�佸洓宸濄�佹睙瑗裤�佹渤鍖椼�佹渤鍗椼�佺寤恒�佽窘瀹併�傚叾瀹冨尯鍩熸鍦ㄥ垎鎵规杩涜缁存姢涓紝鍦ㄦ鏈熼棿鍙兘浼氬嚭鐜板厖鍊间笉鎴愬姛骞惰嚜鍔ㄩ��娆剧殑鎯呭喌锛岃鎮ㄨ皡瑙c��',
+  '濡傛帴鍒伴檶鐢熸潵鐢碉紝瀵规柟浠ョ即璐规垨璇搷浣滅瓑鐞嗙敱瑕佹眰澶勭悊娆鹃」锛屽姟蹇呯珛鍗虫媺榛戯紝璋ㄩ槻璇堥獥銆�',
+  '鍞悗鏈嶅姟鏈熶负鍏呭�煎畬鎴愪箣鏃ヨ捣3澶┿�傜敵璇峰敭鍚庢湇鍔℃椂锛岄渶鎻愪緵褰曞睆璇佹嵁锛岃纭鎺ュ彈姝よ姹傚悗鍐嶄笅鍗曪紝閫炬湡骞冲彴涓嶅啀鍙楃悊鍞悗鐢宠銆�',
+  '鍏呭�煎彂绁ㄧ敱杩愯惀鍟嗘彁渚涳紝鎮ㄥ彲鐧诲綍缃戜笂钀ヤ笟鍘呬笅杞界數瀛愬彂绁ㄣ��',
+];
+
+const confirmDialogVisible = ref(false);
+
+function recharge() {
+  confirmDialogVisible.value = true;
+}
+</script>
diff --git a/packages/components/src/views/RechargeGrid/RechargeGrid.vue b/packages/components/src/views/RechargeGrid/RechargeGrid.vue
index cb56872..b9105ce 100644
--- a/packages/components/src/views/RechargeGrid/RechargeGrid.vue
+++ b/packages/components/src/views/RechargeGrid/RechargeGrid.vue
@@ -1,10 +1,10 @@
 <template>
   <MainCell title="鐢熸椿缂磋垂">
-    <Grid square :gutter="20" :column-num="2">
-      <GridItem class="publish-circle-friend-file-grid-item" text="璇濊垂">
+    <Grid square :gutter="20" :column-num="2" class="recharge-grid-wrapper">
+      <GridItem class="recharge-grid-item" text="璇濊垂" @click="emit('phoneBillRecharge')">
         <Dongdong />
       </GridItem>
-      <GridItem class="publish-circle-friend-file-grid-item" text="鐢佃垂">
+      <GridItem class="recharge-grid-item" text="鐢佃垂" @click="emit('electricityBillRecharge')">
         <Dongdong />
       </GridItem>
     </Grid>
@@ -13,7 +13,6 @@
 
 <script setup lang="ts">
 import MainCell from '../../components/Layout/MainCell.vue';
-// import Grid from '../../components/Grid/Grid.vue';
 import { Grid, GridItem } from '@nutui/nutui-taro';
 import { Dongdong } from '@nutui/icons-vue-taro';
 
@@ -24,4 +23,9 @@
 // type Props = {};
 
 // const props = withDefaults(defineProps<Props>(), {});
+
+const emit = defineEmits<{
+  (e: 'phoneBillRecharge'): void;
+  (e: 'electricityBillRecharge'): void;
+}>();
 </script>

--
Gitblit v1.9.1