Ver Fonte

Develop app selection in start charging pages
https://dev.wormwood.com.sg/zentao/task-view-141.html

vbea há 2 anos atrás
pai
commit
0dcbc953b9
50 ficheiros alterados com 2426 adições e 154 exclusões
  1. 6 3
      Strides-APP/app.json
  2. 10 2
      Strides-APP/app/api/apiVoucher.js
  3. 3 0
      Strides-APP/app/components/BadgeSelectItem.js
  4. 3 3
      Strides-APP/app/components/Dropdown.js
  5. 13 3
      Strides-APP/app/i18n/locales/en.js
  6. 11 1
      Strides-APP/app/i18n/locales/zh-TW.js
  7. 11 1
      Strides-APP/app/i18n/locales/zh.js
  8. BIN
      Strides-APP/app/images/charge3/anim-charging.png
  9. BIN
      Strides-APP/app/images/charge3/anim-loading.png
  10. BIN
      Strides-APP/app/images/charge3/status-auth.png
  11. BIN
      Strides-APP/app/images/charge3/status-charge.png
  12. BIN
      Strides-APP/app/images/charge3/status-default.png
  13. BIN
      Strides-APP/app/images/charge3/status-init.png
  14. BIN
      Strides-APP/app/images/charge3/stop-check.png
  15. 13 1
      Strides-APP/app/pages/Router.js
  16. 1 1
      Strides-APP/app/pages/alert/AlertUtil.js
  17. 3 2
      Strides-APP/app/pages/bookmark/Bookmarks.js
  18. 1 0
      Strides-APP/app/pages/chargeV2/ChargeAdapter.js
  19. 2 1
      Strides-APP/app/pages/chargeV2/Charging.js
  20. 17 2
      Strides-APP/app/pages/chargeV2/PagerUtil.js
  21. 20 6
      Strides-APP/app/pages/chargeV2/PaymentListV2.js
  22. 69 4
      Strides-APP/app/pages/chargeV2/TabCharge.js
  23. 91 11
      Strides-APP/app/pages/charging/StepChargeView.js
  24. 20 5
      Strides-APP/app/pages/chargingV2/ChargingPage.js
  25. 99 17
      Strides-APP/app/pages/chargingV2/StepAuth.js
  26. 80 13
      Strides-APP/app/pages/chargingV2/StepCharging.js
  27. 95 12
      Strides-APP/app/pages/chargingV2/StepStart.js
  28. 495 0
      Strides-APP/app/pages/chargingV3/ChargingPage.js
  29. 156 0
      Strides-APP/app/pages/chargingV3/ConnectorInfo.js
  30. 42 0
      Strides-APP/app/pages/chargingV3/DiscountView.js
  31. 168 0
      Strides-APP/app/pages/chargingV3/StatusImage.js
  32. 224 0
      Strides-APP/app/pages/chargingV3/StepAuth.js
  33. 234 0
      Strides-APP/app/pages/chargingV3/StepCharging.js
  34. 65 0
      Strides-APP/app/pages/chargingV3/StepStart.js
  35. 129 0
      Strides-APP/app/pages/chargingV3/StepStop.js
  36. 2 1
      Strides-APP/app/pages/home/Drawer.js
  37. 16 2
      Strides-APP/app/pages/home/DrawerV2.js
  38. 2 1
      Strides-APP/app/pages/home/maps/BottomSiteInfo.js
  39. 11 5
      Strides-APP/app/pages/my/EditProfile.js
  40. 0 19
      Strides-APP/app/pages/my/ProfileV2.js
  41. 2 1
      Strides-APP/app/pages/search/Search.js
  42. 2 1
      Strides-APP/app/pages/search/SearchV2.js
  43. 12 4
      Strides-APP/app/pages/sign/ResetPasswordV2.js
  44. 3 2
      Strides-APP/app/pages/vouchers/ListPoints.js
  45. 4 22
      Strides-APP/app/pages/vouchers/ListVoucher.js
  46. 1 1
      Strides-APP/app/pages/vouchers/ViewRedeem.js
  47. 255 0
      Strides-APP/app/pages/vouchers/VoucherSelect.js
  48. 22 6
      Strides-APP/app/pages/vouchers/VoucherType.js
  49. 1 1
      Strides-APP/app/pages/wallet/History.js
  50. 12 0
      Strides-APP/app/utils/utils.js

+ 6 - 3
Strides-APP/app.json

@@ -12,7 +12,6 @@
     "apply_phv": false,
     "membership": true,
     "nationally": false,
-    "vouchers": false,
     "topup_payment_type": false,
     "support": {
       "phone": "+6564775355",
@@ -29,10 +28,14 @@
   "company": "Strides YTL Pte. Ltd.",
   "v3": {
     "drawer": true,
-    "summary": true,
     "overview": false,
+    "vouchers": false,
+  },
+  "charge": {
+    "version": 3,
+    "newSummary": false,
     "anzPayment": false,
-    "paymentMethod": false
+    "paymentMethod": true
   },
   "vehicle": {
     "newVersionPage": false,

+ 10 - 2
Strides-APP/app/api/apiVoucher.js

@@ -21,7 +21,7 @@ export default apiVoucher = {
   },
   /**
    * 分页查询我的优惠券列表
-   * @param {Object*} {lastVoucherId,voucherType} data 
+   * @param {Object} data {lastVoucherId,voucherType}
    * @returns Promise
    */
   getMyVouchers(data) {
@@ -29,7 +29,7 @@ export default apiVoucher = {
   },
   /**
    * 分页查询优惠券贩卖列表
-   * @param {Object*} {lastVoucherId,voucherType} data 
+   * @param {Object} data {lastVoucherId,voucherType}
    * @returns Promise
    */
   getDealVouchers(data) {
@@ -41,5 +41,13 @@ export default apiVoucher = {
    */
   getVoucherTypeOptions() {
     return get(prefix + "voucher-type-select")
+  },
+  /**
+   * 获取充电桩可用的优惠券
+   * @param {Object} data {chargeBoxId,connectorId,voucherType}
+   * @returns Promise
+   */
+  getSelectionVoucher(data) {
+    return get(prefix + "charge-can-use-vouchers", data)
   }
 }

+ 3 - 0
Strides-APP/app/components/BadgeSelectItem.js

@@ -42,6 +42,9 @@ const mergeStyle = (style, color) => {
     style = res;
   }
   var s = Object.assign(def, style);
+  if (color) {
+    s.borderColor = color;
+  }
   return s;
 }
 

+ 3 - 3
Strides-APP/app/components/Dropdown.js

@@ -48,11 +48,11 @@ export default Dropdown = ({
     if (autoSelect && list.length > 0) {
       if (value == undefined) {
         const item = list[0];
-        /*if (nameKey) {
-          changeValue(item[nameKey]);
+        if (nameKey) {
+          changeValue(prefixText+item[nameKey]+suffixText);
         } else {
           changeValue(item);
-        }*/
+        }
         setChange(valueKey ? item[valueKey] : item, 0);
       } else {
         changeItem(true);

+ 13 - 3
Strides-APP/app/i18n/locales/en.js

@@ -265,6 +265,8 @@ export default {
     btnReserve: "Reserve",
     btnStartCharging: "START CHARGING",
     btnStopCharging: "STOP CHARGING",
+    btnHome: "HOME",
+    btnViewReceipt: "VIEW RECEIPT",
     cancelReservation: "Cancel Reservation",
     chargingInProgress: "Charging In Progress...",
     chooseConnector: "Choose Connector",
@@ -327,6 +329,8 @@ export default {
     stepInitializingDesc: "Please wait while we prepare to start charging...",
     stepStoppingCharge: "Stopping Charge",
     stepStoppingChargeDesc: "Please wait while we stop your charging...",
+    stepStoppedCharge: "Charge Has Ended",
+    stepStoppedChargeDesc: "Select VIEW RECEIPT for transaction summary\nOr\nSelect HOME to return",
     stepInsertConnector: "Insert Charging Cable",
     stepInsertConnectorDesc: "Insert charging cable to EV and press AUTHENTICATE.",
     tabCharge: "CHARGE",
@@ -549,9 +553,15 @@ export default {
     btnGetFor: "GET FOR",
     btnPointsHint: "",
     btnPoints: " POINTS",
-    noData: "No Data",
-    noMore: "No More",
+    noData: "No data",
+    noMore: "No more",
     voucherType: "Voucher Type",
-    plsinputPromoCode: "Please input promo code"
+    selectVoucher: "Select Voucher",
+    plsInputPromoCode: "Please input promo code",
+    typeOptions: {
+      all: "All",
+      valueOffset: "Value Offset",
+      percentOffset: "Percent Offset"
+    }
   }
 }

+ 11 - 1
Strides-APP/app/i18n/locales/zh-TW.js

@@ -265,6 +265,8 @@ export default {
     btnReserve: "預定",
     btnStartCharging: "開始充電",
     btnStopCharging: "停止充電",
+    btnHome: "首頁",
+    btnViewReceipt: "查看收據",
     cancelReservation: "取消預訂",
     chargingInProgress: "正在充電中……",
     chooseConnector: "選擇連結器",
@@ -327,6 +329,8 @@ export default {
     stepInitializingDesc: "正在準備開始充電,請稍候……",
     stepStoppingCharge: "正在停止充電",
     stepStoppingChargeDesc: "正在停止充電,請稍候……",
+    stepStoppedCharge: "已停止充電",
+    stepStoppedChargeDesc: "點擊“查看收據”查看交易詳情或選擇“首頁”返囬",
     stepInsertConnector: "等待插入充電器",
     stepInsertConnectorDesc: "請將充電槍插入車輛,然後點擊驗證按鈕",
     tabCharge: "充電",
@@ -552,6 +556,12 @@ export default {
     noData: "沒有優惠券數據",
     noMore: "已經到底了",
     voucherType: "優惠券類別",
-    plsinputPromoCode: "請鍵入兌換代碼"
+    selectVoucher: "選擇優惠券",
+    plsInputPromoCode: "請鍵入兌換代碼",
+    typeOptions: {
+      all: "所有",
+      valueOffset: "固定額折扣",
+      percentOffset: "百分比折扣"
+    }
   }
 }

+ 11 - 1
Strides-APP/app/i18n/locales/zh.js

@@ -265,6 +265,8 @@ export default {
     btnReserve: "预定",
     btnStartCharging: "开始充电",
     btnStopCharging: "停止充电",
+    btnHome: "首页",
+    btnViewReceipt: "查看收据",
     cancelReservation: "取消预订",
     chargingInProgress: "正在充电中……",
     chooseConnector: "选择连接器",
@@ -327,6 +329,8 @@ export default {
     stepInitializingDesc: "正在准备开始充电,请稍候……",
     stepStoppingCharge: "正在停止充电",
     stepStoppingChargeDesc: "正在停止充电,请稍候……",
+    stepStoppedCharge: "已停止充电",
+    stepStoppedChargeDesc: "选择“查看收据”查看交易详情或选择“首页”返回",
     stepInsertConnector: "等待插入充电器",
     stepInsertConnectorDesc: "请将充电枪插入车辆,然后点击验证按钮",
     tabCharge: "充电",
@@ -552,6 +556,12 @@ export default {
     noData: "没有代金券数据",
     noMore: "到底了",
     voucherType: "代金券类型",
-    plsinputPromoCode: "请输入兑换码"
+    selectVoucher: "选择代金券",
+    plsInputPromoCode: "请输入兑换码",
+    typeOptions: {
+      all: "所有",
+      valueOffset: "固定额折扣",
+      percentOffset: "百分比折扣"
+    }
   }
 }

BIN
Strides-APP/app/images/charge3/anim-charging.png


BIN
Strides-APP/app/images/charge3/anim-loading.png


BIN
Strides-APP/app/images/charge3/status-auth.png


BIN
Strides-APP/app/images/charge3/status-charge.png


BIN
Strides-APP/app/images/charge3/status-default.png


BIN
Strides-APP/app/images/charge3/status-init.png


BIN
Strides-APP/app/images/charge3/stop-check.png


+ 13 - 1
Strides-APP/app/pages/Router.js

@@ -52,6 +52,7 @@ import PaymentWeb from './payment/PaymentWeb';
 import Settings from './Settings';
 import ChargeAdapter from './chargeV2/ChargeAdapter';
 import ChargingPage from './chargingV2/ChargingPage';
+import ChargingPageV4 from './chargingV3/ChargingPage';
 import { BridgePage } from '../utils/routeUtil';
 import HeaderTitle from '../components/HeaderTitle';
 import Bookmarks from './bookmark/Bookmarks';
@@ -66,6 +67,7 @@ import ViewArticle from './alert/ViewArticle';
 import VehicleListV2 from './vehicles/VehicleListV2';
 import HistoryList from './wallet/HistoryList';
 import VoucherPage from './vouchers/VoucherPage';
+import VoucherSelect from './vouchers/VoucherSelect';
 
 export var PageList = {
   'splash': {
@@ -116,6 +118,11 @@ export var PageList = {
     titleScope: 'route.charging',
     component: ChargingPage
   },
+  'chargingPageV4': {
+    title: 'Charging',
+    titleScope: 'route.charging',
+    component: ChargingPageV4
+  },
   'scanqr': {
     title: 'QR Scan',
     titleScope: 'route.qrScan',
@@ -154,7 +161,7 @@ export var PageList = {
   'summary': {
     title: 'Summary',
     titleScope: 'receipt.receipt',
-    component: app.v3.summary ? SummaryV3 : Summary
+    component: app.charge.newSummary ? SummaryV3 : Summary
   },
   'rating': {
     title: 'Your Rating',
@@ -359,6 +366,11 @@ export var PageList = {
     titleScope: 'route.vouchers',
     component: VoucherPage
   },
+  'selectVoucher': {
+    title: "Vouchers",
+    titleScope: 'route.vouchers',
+    component: VoucherSelect
+  },
   'settings': {
     title: 'Settings',
     titleScope: 'route.settings',

+ 1 - 1
Strides-APP/app/pages/alert/AlertUtil.js

@@ -1,5 +1,5 @@
 export default {
-  isPromotion: false,
+  isPromotion: false,//true显示Promotions,false显示Articles
   setBadgeCount(info={}) {
     global.alertBadgeCountInfo = info;
   },

+ 3 - 2
Strides-APP/app/pages/bookmark/Bookmarks.js

@@ -32,7 +32,7 @@ export default class Bookmarks extends Component {
     this.setState({
       isSearch: true
     });
-    this.getGeoLocation();
+    this.searchStation(this.state.latlng);
   }
 
   getGeoLocation() {
@@ -110,7 +110,8 @@ export default class Bookmarks extends Component {
     if (info.upcoming) {
       toastShort($t("home.upcoming"))
     } else {
-      startPage(PageList.chargeDetailPage, {stationInfo: info, action: "bookmarks"});
+      utils.toChargeDetailPage(info.id, 'bookmarks', PageList.bookmarks);
+      //startPage(PageList.chargeDetailPage, {stationInfo: info, action: "bookmarks"});
     }
   }
 

+ 1 - 0
Strides-APP/app/pages/chargeV2/ChargeAdapter.js

@@ -92,6 +92,7 @@ export default class ChargeAdapter extends Component {
     }
     BackHandler.addEventListener('hardwareBackPress', this.backPage)
     PagerUtil.addOnRefresh(this);
+    PagerUtil.setSelectedVoucher({});
     // setTimeout(() => {
     //   this.changeTab(1)
     // }, 200);

+ 2 - 1
Strides-APP/app/pages/chargeV2/Charging.js

@@ -522,7 +522,8 @@ const styles = StyleSheet.create({
     bottom: 0,
     borderRadius: 6,
     overflow: 'hidden',
-    position: "absolute",  
+    position: "absolute",
+    backgroundColor: "#3CDB2B"
   },
   dashboardTitle: {
     color: textPrimary,

+ 17 - 2
Strides-APP/app/pages/chargeV2/PagerUtil.js

@@ -1,6 +1,7 @@
 import { getFocusedRouteNameFromRoute } from "@react-navigation/core";
 import { PagerList } from "./ChargeAdapter";
 import app from '../../../app.json';
+import { PageList } from "../Router";
 
 var chargeInfoState = global.chargeInfoState
 var refreshListener = [];
@@ -8,7 +9,7 @@ var refreshListener = [];
 const DEBUG = app.debug && !app.product;
 
 export default PagerUtil = {
-  ENABLE_NEW_UI: true, //是否启用新的充电页面
+  ENABLE_NEW_UI: app.charge.version > 2, //是否启用新的充电页面
   getStationInfo: () => {
     return chargeInfoState.stationInfo ?? {}
   },
@@ -85,5 +86,19 @@ export default PagerUtil = {
   },
   getCurrentRoute: (props) => (
     getFocusedRouteNameFromRoute(props.route)
-  )
+  ),
+  isSelectVoucher: () => {
+    return global.chargeSelectVoucher;
+  },
+  toSelectVoucher: () => {
+    global.chargeSelectVoucher = true;
+    startPage(PageList.selectVoucher);
+  },
+  setSelectedVoucher: (voucher) => {
+    global.chargeSelectedVoucher = voucher;
+  },
+  getSelectedVoucher: () => {
+    global.chargeSelectVoucher = false;
+    return global.chargeSelectedVoucher || {};
+  }
 }

+ 20 - 6
Strides-APP/app/pages/chargeV2/PaymentListV2.js

@@ -12,6 +12,7 @@ import TextView from '../../components/TextView';
 import apiCharge from '../../api/apiCharge';
 import Button from '../../components/Button';
 import Dialog from '../../components/Dialog';
+import app from '../../../app.json';
 
 export default class PaymentListV2 extends Component {
   constructor(props) {
@@ -114,15 +115,27 @@ export default class PaymentListV2 extends Component {
     return (
       <View>
         <BadgeSelectItem
-          style={ChargeStyle.stationInfoView}
+          style={this.props.style ?? ChargeStyle.stationInfoView}
+          borderColor={this.props.borderColor}
           checked={false}
           onPress={() => this.showDialog(true)}>
-          <PaymentIcon
-            method={"WALLET"}
-            checked={true}/>
+          { app.charge.version >= 4
+          ? <MaterialIcons
+              name="wallet"
+              size={32}
+              color={textPrimary}/>
+          : <PaymentIcon
+              method={"WALLET"}
+              checked={true}
+              size={35}/>
+          }
           <View style={styles.paymentView}>
-            <TextView style={styles.valueText}>{this.state.selectOptions?.name}</TextView>
-            <TextView style={styles.paymentText}>{this.state.selectOptions?.desc}</TextView>
+            <TextView
+              style={styles.valueText}
+              numberOfLines={1}>{this.state.selectOptions?.name}</TextView>
+            <TextView
+              style={styles.paymentText}
+              numberOfLines={1}>{this.state.selectOptions?.desc}</TextView>
           </View>
           { this.state.selectOptions?.def &&
             <TextView style={styles.textDefault}>Default</TextView>
@@ -184,6 +197,7 @@ export default class PaymentListV2 extends Component {
 const styles = StyleSheet.create({
   paymentView: {
     flex: 1,
+    flexWrap: 'wrap',
     alignItems: 'center',
     flexDirection: 'row',
     justifyContent: 'space-around'

+ 69 - 4
Strides-APP/app/pages/chargeV2/TabCharge.js

@@ -14,6 +14,7 @@ import PagerUtil from './PagerUtil';
 import StepStartView from '../charging/StepStartView';
 import StepChargeView from '../charging/StepChargeView';
 import utils from '../../utils/utils';
+import app from '../../../app.json';
 import { PaymentDefault } from '../payment/PaymentConfig';
 import { MyRefreshProps } from '../../components/ThemesConfig';
 
@@ -41,7 +42,8 @@ export default class TabCharge extends Component {
       //currentPayment: PAYTYPE.CREDIT_WALLET,
       //currentPaytype: "Credit Wallet",
       currentPayment: PaymentDefault.DEFAULT.payType,
-      currentPaytype: PaymentDefault.DEFAULT.payName
+      currentPaytype: PaymentDefault.DEFAULT.payName,
+      selectedVoucher: {}
     };
     this.changeMethod = false;
     this.canAutoRefresh = false;
@@ -78,6 +80,11 @@ export default class TabCharge extends Component {
 
   init() {
     console.log("Charge刷新", "init");
+    if (PagerUtil.isSelectVoucher) {
+      this.setState({
+        selectedVoucher: PagerUtil.getSelectedVoucher()
+      })
+    }
     this.onMethodChanged();
     this.refreshAvailable();
     if (QRResult.haveResult()) {
@@ -141,7 +148,7 @@ export default class TabCharge extends Component {
 
   toChargingPage() {
     this.autoIntoCharging = false;
-    startPage(PageList.chargingPage, {
+    utils.toChargingPage({
       id: this.state.stationInfo.id,
       name: this.state.stationInfo.name,
       address: this.state.stationInfo.address,
@@ -429,11 +436,16 @@ export default class TabCharge extends Component {
     if (this.state.connectorInfo.isCheckThrough) {
       Dialog.showProgressDialog();
       const params = {
+        sitePk: this.state.stationInfo.id,
         chargeBoxId: this.state.connectorInfo.chargeBoxId,
         connectorId: this.state.connectorInfo.connectorId
       }
+      if (app.charge.paymentMethod) { //V3版本开始充电
+        this.onStartChargeV3(params);
+        return;
+      }
       apiCharge.startCharge(params).then(res => {
-        console.log(res);
+        console.log("开始充电返回", res);
         setTimeout(() => {
           this.setState({
             isStart: true,
@@ -474,6 +486,56 @@ export default class TabCharge extends Component {
       });
     }
   }
+  onStartChargeV3(params) {
+    if (this.state.currentPayment?.code) {
+      params.paymentMethod = this.state.currentPayment?.code
+    }
+    if (app.v3.vouchers && this.state.selectedVoucher?.userVoucherId) {
+      params.userVoucherIds = [this.state.selectedVoucher.userVoucherId]
+    }
+    console.log("[开始充电V3-params]", params);
+    apiCharge.startChargeV3(params).then(res => {
+      console.log("[开始充电V3-response]", res);
+      if (res.data.webPaymentUrl) {
+        this.setState({
+          currentPerUse: "Pending"
+        })
+        startPage(PageList.paymentWeb, { amount: params.sitePk, url: res.data.webPaymentUrl, type: 'PayPerUse' });
+      } else {
+        setTimeout(() => {
+          this.setState({
+            isStart: true,
+            isPending: true,
+            isCharging: true
+          });
+          this.canAutoRefresh = true;
+          this.autoCheckChargeStatus();
+          Dialog.dismissLoading();
+          if (res.msg) {
+            Dialog.showResultDialog(res.msg)
+          }
+          //this.autoCheckIsCharge();
+        }, 3000);
+      }
+    }).catch(({err, code, data}) => {
+      //toastShort(err);
+      console.log("[开始充电V3-错误]", err, code);
+      Dialog.dismissLoading();
+      if (code == 5200) {
+        this.setState({
+          errorCode: 'none',
+          showErrorDialog: true,
+          errorMessage: "(" + data.transactionPk + ') ' + err
+        });
+      } else {
+        this.setState({
+          errorCode: 'A4',
+          showErrorDialog: true,
+          errorMessage: ''+err
+        });
+      }
+    });
+  }
   //开始充电-end
 
   //停止充电-start
@@ -506,8 +568,10 @@ export default class TabCharge extends Component {
           this.setState({
             isStart: false,
             isPending: false,
-            isCharging: false
+            isCharging: false,
+            selectedVoucher: {}
           });
+          PagerUtil.setSelectedVoucher({});
           startPage(PageList.summary, { 
             chargingPk: res.data.chargingPk,
             id: this.state.stationInfo.id,
@@ -566,6 +630,7 @@ export default class TabCharge extends Component {
             lastUpdated={this.state.lastUpdated}
             connectorInfo={this.state.connectorInfo}
             currentPayment={this.state.currentPayment}
+            selectedVoucher={this.state.selectedVoucher}
             onStartCharge={() => this.startCharge()}
             onStopCharge={() => this.onStopCharge()}
             onPaymentMethodChanged={(type) => this.onPaymentMethodChanged(type)}/>

+ 91 - 11
Strides-APP/app/pages/charging/StepChargeView.js

@@ -3,14 +3,18 @@
  * @邠心vbe on 2023/03/10
  */
 import React from 'react';
-import { StyleSheet, Text, View } from 'react-native';
-import Button from '../../components/Button';
+import { Pressable, StyleSheet, View } from 'react-native';
+import Button, {ElevationObject} from '../../components/Button';
 import TextView from '../../components/TextView';
 import { circleSize, DashboardView } from '../chargeV2/Charging';
 import { PaymentList } from '../chargeV2/Payment';
 import StationInfoView from './StationInfoView';
+import PaymentListV2 from '../chargeV2/PaymentListV2';
+import app from '../../../app.json';
+import PagerUtil from '../chargeV2/PagerUtil';
+import utils from '../../utils/utils';
  
-export default StepStartView = ({
+export default StepChargeView = ({
   isStart=false,
   isPending,
   isCharging,
@@ -20,6 +24,7 @@ export default StepStartView = ({
   lastUpdated,
   onStartCharge,
   onStopCharge,
+  selectedVoucher={},
   onPaymentMethodChanged
 }) => (
   <>
@@ -52,18 +57,57 @@ export default StepStartView = ({
         </ImageBackground>
       </View> */}
       <TextView style={styles.title}>{$t('charging.selectPaymentMethod')}</TextView>
-      <PaymentList
+      { app.charge.paymentMethod
+      ? <PaymentListV2
+          payType={currentPayment}
+          isSelect={!isPending && !isCharging}
+          chargeBoxId={connectorInfo.chargeBoxId}
+          onMethodChange={onPaymentMethodChanged}/>
+      : <PaymentList
+          payType={currentPayment}
+          payPerUse={curerntPerUser}
+          onMethodChange={onPaymentMethodChanged}/>
+      }
+      {/* <PaymentList
         payType={currentPayment}
         payPerUse={curerntPerUser}
         onMethodChange={onPaymentMethodChanged}
-      />
-      {/* <Payment 
-        refreshId={this.state.refreshId}
-        payType={this.state.currentPaytype}
-        balance={this.state.curerntPerUser}
-        isPayPerUse={this.state.currentPayment == PAYTYPE.PAY_PER_USE}
-        onMethodChange={() => this.onMethodChange()}
       /> */}
+      { (app.v3.vouchers && (!isPending || !isCharging || utils.isNotEmpty(selectedVoucher.userVoucherId))) && <>
+        <TextView style={styles.title}>{$t('voucher.selectVoucher')}</TextView>
+        <Pressable
+          style={styles.voucherLayout}
+          onPress={() => (!isPending && !isCharging) && PagerUtil.toSelectVoucher()}>
+          <MaterialCommunityIcons
+            name="ticket-percent"
+            size={35}
+            color={colorAccent}/>
+          { utils.isNotEmpty(selectedVoucher.userVoucherId)
+            ? <View style={styles.vouchersView}>
+                <TextView
+                  style={styles.voucherName}
+                  numberOfLines={1}>
+                  {selectedVoucher.voucherName}
+                </TextView>
+                <TextView
+                  style={styles.voucherDesc}
+                  numberOfLines={1}>
+                  {selectedVoucher.voucherDesc}
+                </TextView>
+              </View>
+            : <View style={styles.vouchersView}>
+                <TextView style={styles.selectText}>{$t("voucher.selectVoucher")}</TextView>
+              </View>
+            }
+            { (!isPending && !isCharging) &&
+              <FontAwesome6
+                name={"angle-right"}
+                size={16}
+                color={colorCancel}
+              />
+            }
+          </Pressable>
+      </>}
       { lastUpdated
         ? <TextView style={styles.updateTip}>{$t('charging.lastUpdatedAt') + lastUpdated + '\n' + $t('charging.pullDownRefresh')}</TextView>
         : <></>
@@ -114,5 +158,41 @@ const styles = StyleSheet.create({
     height: circleSize,
     alignItems: 'center',
     justifyContent: 'center'
+  },
+  voucherLayout: {
+    padding: 12,
+    borderRadius: 10,
+    marginBottom: 12,
+    alignItems: 'center',
+    flexDirection: 'row',
+    ...ElevationObject(5),
+    backgroundColor: colorLight,
+  },
+  vouchersView: {
+    flex: 1,
+    paddingLeft: 8,
+    flexWrap: 'wrap',
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  selectText: {
+    flex: 1,
+    color: textSecondary,
+    fontSize: 15,
+    fontWeight: 'bold'
+  },
+  voucherName: {
+    flex: 1,
+    color: textPrimary,
+    fontSize: 15,
+    paddingLeft: 8,
+    fontWeight: 'bold'
+  },
+  voucherDesc: {
+    flex: 1,
+    color: textSecondary,
+    fontSize: 12,
+    paddingLeft: 8,
+    paddingRight: 16
   }
 })

+ 20 - 5
Strides-APP/app/pages/chargingV2/ChargingPage.js

@@ -15,6 +15,7 @@ import StepAuth from './StepAuth';
 import StepCharging from './StepCharging';
 import StepStart from './StepStart';
 import StepStop from './StepStop';
+import PagerUtil from '../chargeV2/PagerUtil';
 
 export default class ChargingPage extends Component {
   constructor(props) {
@@ -32,7 +33,8 @@ export default class ChargingPage extends Component {
       showStationDialog: false,
       currentPerUse: "",
       currentPayment: PaymentDefault.DEFAULT.payType,
-      currentPaytype: PaymentDefault.DEFAULT.payName
+      currentPaytype: PaymentDefault.DEFAULT.payName,
+      selectedVoucher: {}
     };
     this.isPageShow = true;
     this.waitStartCharging = false;
@@ -65,6 +67,11 @@ export default class ChargingPage extends Component {
         this.isPageShow = true;
         //this.canShowLoginDialog();
       }
+      if (PagerUtil.isSelectVoucher) {
+        this.setState({
+          selectedVoucher: PagerUtil.getSelectedVoucher()
+        })
+      }
     });
     this.props.navigation.addListener('blur', () => {
       this.isPageShow = false;
@@ -108,7 +115,7 @@ export default class ChargingPage extends Component {
       connectorId: this.state.stationInfo.connectorId,
       paymentOption: this.state.currentPayment,
     }
-    if (app.v3.paymentMethod && this.state.currentPayment?.code) {
+    if (app.charge.paymentMethod && this.state.currentPayment?.code) {
       params.paymentMethod = this.state.currentPayment?.code
     }
     console.log("参数", params);
@@ -121,7 +128,7 @@ export default class ChargingPage extends Component {
           connectorInfo: {}
         }
         state.connectorInfo = res.data;
-        if (app.v3.paymentMethod && res.data.currentPaymentMethod) {
+        if (app.charge.paymentMethod && res.data.currentPaymentMethod) {
           //V3版获取当前支付方式
           state.currentPayment = {
             code: res.data.currentPaymentMethod
@@ -257,7 +264,7 @@ export default class ChargingPage extends Component {
       isCharging: true
     });
     this.waitStartCharging = true;
-    if (app.v3.paymentMethod) { //V3版本开始充电
+    if (app.charge.paymentMethod) { //V3版本开始充电
       this.onStartChargeV3();
       return;
     }
@@ -323,6 +330,9 @@ export default class ChargingPage extends Component {
     if (this.state.currentPayment?.code) {
       params.paymentMethod = this.state.currentPayment?.code
     }
+    if (app.v3.vouchers && this.state.selectedVoucher?.userVoucherId) {
+      params.userVoucherIds = [this.state.selectedVoucher.userVoucherId]
+    }
     console.log("[开始充电V3-params]", params);
     apiCharge.startChargeV3(params).then(res => {
       console.log("[开始充电V3-response]", res);
@@ -382,8 +392,10 @@ export default class ChargingPage extends Component {
           }
           this.setState({
             isCharging: false,
-            isAuthentic: false
+            isAuthentic: false,
+            selectedVoucher: {}
           });
+          PagerUtil.setSelectedVoucher({});
           //this.init();
           startPage(PageList.summary, { 
             chargingPk: res.data.chargingPk,
@@ -446,6 +458,7 @@ export default class ChargingPage extends Component {
               connectorInfo={this.state.connectorInfo}
               currentPayment={this.state.currentPayment}
               onStopCharge={() => this.onStopCharge()}
+              selectedVoucher={this.state.selectedVoucher}
             />
           : ( this.state.isAuthentic
             ? <StepAuth
@@ -453,12 +466,14 @@ export default class ChargingPage extends Component {
                 chargeBoxId={this.state.stationInfo.chargeBoxId}
                 currentPayment={this.state.currentPayment}
                 onStartCharge={() => this.onStartCharge()}
+                selectedVoucher={this.state.selectedVoucher}
                 onPaymentMethodChanged={(type) => this.onPaymentMethodChanged(type)}
               />
             : <StepStart
                 connectorInfo={this.state.connectorInfo}
                 currentPayment={this.state.currentPayment}
                 onAuthenticate={() => this.onAuthenticate()}
+                selectedVoucher={this.state.selectedVoucher}
                 onPaymentMethodChanged={(type) => this.onPaymentMethodChanged(type)}
               />
             )

+ 99 - 17
Strides-APP/app/pages/chargingV2/StepAuth.js

@@ -3,18 +3,20 @@
  * @邠心vbe on 2023/06/20
  */
 import React, { useEffect, useState } from 'react';
-import { View, Text, Image, StyleSheet } from 'react-native';
+import { View, Text, Image, StyleSheet, Pressable } from 'react-native';
 import app from '../../../app.json';
-import Button from '../../components/Button';
+import Button, { ElevationObject } from '../../components/Button';
 import TextView from '../../components/TextView';
 import { PaymentList } from '../chargeV2/Payment';
 import PaymentListV2 from '../chargeV2/PaymentListV2';
+import utils from '../../utils/utils';
 
 export default StepAuth = ({
   status="",
   currentPayment,
   onStartCharge,
   chargeBoxId,
+  selectedVoucher={},
   onPaymentMethodChanged
 }) => {
   const [loadingEmps, setEmps] = useState("");
@@ -68,22 +70,65 @@ export default StepAuth = ({
         <TextView style={styles.stepDesc}>{$t(isAuthentic ? 'charging.stepAuthenticatedDesc' : 'charging.stepAuthenticatingDesc')}</TextView>
       </View>
       <View style={styles.bottomView}>
-        <TextView style={styles.label}>{$t('charging.paymentMethod')}</TextView>
-        {/* <PaymentList
-          payType={currentPayment}
-          onMethodChange={onPaymentMethodChanged}
-        /> */}
-        { app.v3.paymentMethod
-        ? <PaymentListV2
-            isSelect={isAuthentic}
-            payType={currentPayment}
-            chargeBoxId={chargeBoxId}
-            onMethodChange={onPaymentMethodChanged}/>
-        : <PaymentList
-            isSelect={isAuthentic}
-            payType={currentPayment}
-            onMethodChange={onPaymentMethodChanged}/>
+        <View style={ui.flexc}>
+          <TextView style={[styles.label, ui.flex1]}>{$t('charging.paymentMethod')}</TextView>
+          { app.v3.vouchers &&
+            <TextView style={[styles.label, {flex: 1, paddingLeft: 16}]}>{$t('voucher.vouchers')}</TextView>
+          }
+        </View>
+        <View style={ui.flexc}>
+          <View style={ui.flex1}>
+            {/* <PaymentList
+              payType={currentPayment}
+              onMethodChange={onPaymentMethodChanged}
+            /> */}
+            { app.charge.paymentMethod
+            ? <PaymentListV2
+                isSelect={isAuthentic}
+                payType={currentPayment}
+                chargeBoxId={chargeBoxId}
+                onMethodChange={onPaymentMethodChanged}/>
+            : <PaymentList
+                isSelect={isAuthentic}
+                payType={currentPayment}
+                onMethodChange={onPaymentMethodChanged}/>
+            }
+          </View>
+          { app.v3.vouchers &&
+          <Pressable
+            style={styles.voucherLayout}
+            onPress={() => isAuthentic && PagerUtil.toSelectVoucher()}>
+            <MaterialCommunityIcons
+              name="ticket-percent"
+              size={35}
+              color={colorAccent}/>
+            { utils.isNotEmpty(selectedVoucher.userVoucherId)
+              ? <View style={styles.vouchersView}>
+                  <TextView
+                    style={styles.voucherName}
+                    numberOfLines={1}>
+                    {selectedVoucher.voucherName}
+                  </TextView>
+                  <TextView
+                    style={styles.voucherDesc}
+                    numberOfLines={1}>
+                    {selectedVoucher.voucherDesc}
+                  </TextView>
+                </View>
+              : <View style={styles.vouchersView}>
+                  <TextView style={styles.selectText}>{$t("voucher.selectVoucher")}</TextView>
+                </View>
+              }
+              { isAuthentic &&
+                <FontAwesome6
+                  name={"angle-right"}
+                  size={16}
+                  color={colorCancel}
+                />
+              }
+          </Pressable>
         }
+        </View>
         { isAuthentic
         ? <Button
             style={styles.buttonView}
@@ -136,5 +181,42 @@ const styles = StyleSheet.create({
   },
   buttonView: {
     marginTop: 8
+  },
+  voucherLayout: {
+    flex: 1,
+    padding: 12,
+    maxWidth: $vw(50) - 24,
+    borderRadius: 10,
+    marginLeft: 16,
+    marginBottom: 12,
+    alignItems: 'center',
+    flexDirection: 'row',
+    ...ElevationObject(5),
+    backgroundColor: colorLight,
+  },
+  vouchersView: {
+    flex: 1,
+    paddingLeft: 8,
+    flexWrap: 'wrap',
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  selectText: {
+    flex: 1,
+    color: textSecondary,
+    fontSize: 15,
+    fontWeight: 'bold'
+  },
+  voucherName: {
+    color: textPrimary,
+    fontSize: 15,
+    paddingLeft: 8,
+    fontWeight: 'bold'
+  },
+  voucherDesc: {
+    color: textSecondary,
+    fontSize: 12,
+    paddingLeft: 8,
+    paddingRight: 16
   }
 })

+ 80 - 13
Strides-APP/app/pages/chargingV2/StepCharging.js

@@ -15,7 +15,8 @@ import PaymentListV2 from '../chargeV2/PaymentListV2';
 const StepCharging = ({
   connectorInfo={},
   currentPayment,
-  onStopCharge
+  onStopCharge,
+  selectedVoucher={}
 }) => {
   const [isCharging, setCharging] = useState(false);
   const [loadingEmps, setEmps] = useState("");
@@ -158,15 +159,44 @@ const StepCharging = ({
         </View>
       </Animated.View>
       <EndView/>
-      <TextView style={styles.label}>{$t('charging.paymentMethod')}</TextView>
-      { app.v3.paymentMethod
-      ? <PaymentListV2
-          isSelect={false}
-          payType={currentPayment}/>
-      : <PaymentList
-          isSelect={false}
-          payType={currentPayment}/>
-      }
+      <View style={ui.flexc}>
+        <TextView style={[styles.label, ui.flex1]}>{$t('charging.paymentMethod')}</TextView>
+        { utils.isNotEmpty(selectedVoucher.userVoucherId) &&
+          <TextView style={[styles.label, {flex: 1, paddingLeft: 16}]}>{$t('voucher.vouchers')}</TextView>
+        }
+      </View>
+      <View style={ui.flexc}>
+        <View style={ui.flex1}>
+          { app.charge.paymentMethod
+          ? <PaymentListV2
+              isSelect={false}
+              payType={currentPayment}/>
+          : <PaymentList
+              isSelect={false}
+              payType={currentPayment}/>
+          }
+        </View>
+        { utils.isNotEmpty(selectedVoucher.userVoucherId) &&
+          <View style={styles.voucherLayout}>
+            <MaterialCommunityIcons
+              name="ticket-percent"
+              size={35}
+              color={colorAccent}/>
+            <View style={styles.vouchersView}>
+              <TextView
+                style={styles.voucherName}
+                numberOfLines={1}>
+                {selectedVoucher.voucherName}
+              </TextView>
+              <TextView
+                style={styles.voucherDesc}
+                numberOfLines={1}>
+                {selectedVoucher.voucherDesc}
+              </TextView>
+            </View>
+          </View>
+        }
+      </View>
       <Button
         style={styles.buttonView}
         text={$t('charging.btnStopCharging')}
@@ -190,9 +220,9 @@ const StepCharging = ({
         </View>
         <TextView style={styles.stepDesc}>{$t('charging.stepInitializingDesc')}</TextView>
       </Animated.View>
-      <View style={styles.bottomView}>
+      {/* <View style={styles.bottomView}>
         <TextView style={styles.label}>{$t('charging.paymentMethod')}</TextView>
-        { app.v3.paymentMethod
+        { app.charge.paymentMethod
         ? <PaymentListV2
             isSelect={false}
             payType={currentPayment}/>
@@ -201,7 +231,7 @@ const StepCharging = ({
             payType={currentPayment}/>
         }
         <View style={styles.buttonView}></View>
-      </View>
+      </View> */}
     </View>
   );
 }
@@ -305,5 +335,42 @@ const styles = StyleSheet.create({
   buttonView: {
     marginTop: 16,
     marginBottom: 16
+  },
+  voucherLayout: {
+    flex: 1,
+    padding: 12,
+    maxWidth: $vw(50) - 24,
+    borderRadius: 10,
+    marginLeft: 16,
+    marginBottom: 12,
+    alignItems: 'center',
+    flexDirection: 'row',
+    ...ElevationObject(5),
+    backgroundColor: colorLight,
+  },
+  vouchersView: {
+    flex: 1,
+    paddingLeft: 8,
+    flexWrap: 'wrap',
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  selectText: {
+    flex: 1,
+    color: textSecondary,
+    fontSize: 15,
+    fontWeight: 'bold'
+  },
+  voucherName: {
+    color: textPrimary,
+    fontSize: 15,
+    paddingLeft: 8,
+    fontWeight: 'bold'
+  },
+  voucherDesc: {
+    color: textSecondary,
+    fontSize: 12,
+    paddingLeft: 8,
+    paddingRight: 16
   }
 })

+ 95 - 12
Strides-APP/app/pages/chargingV2/StepStart.js

@@ -3,7 +3,7 @@
  * @邠心vbe on 2023/06/20
  */
 import React from 'react';
-import { View, Image, StyleSheet, ScrollView } from 'react-native';
+import { View, Image, StyleSheet, ScrollView, Pressable } from 'react-native';
 import app from '../../../app.json';
 import Button, { ElevationObject } from '../../components/Button';
 import TextView from '../../components/TextView';
@@ -11,12 +11,15 @@ import { ChargeStyle } from '../chargeV2/Charging';
 import { PaymentList } from '../chargeV2/Payment';
 import DiscountView from './DiscountView';
 import PaymentListV2 from '../chargeV2/PaymentListV2';
+import PagerUtil from '../chargeV2/PagerUtil';
+import utils from '../../utils/utils';
 
 export default StepStart = ({
   connectorInfo={},
   currentPayment,
   onPaymentMethodChanged,
-  onAuthenticate
+  onAuthenticate,
+  selectedVoucher={}
 }) => {
   return (
     <ScrollView
@@ -77,16 +80,59 @@ export default StepStart = ({
       </View>
       <EndView/>
       <EndView half/>
-      <TextView style={styles.label}>{$t('charging.paymentMethod')}</TextView>
-      { app.v3.paymentMethod
-      ? <PaymentListV2
-          payType={currentPayment}
-          chargeBoxId={connectorInfo.chargeBoxId}
-          onMethodChange={onPaymentMethodChanged}/>
-      : <PaymentList
-          payType={currentPayment}
-          onMethodChange={onPaymentMethodChanged}/>
-      }
+      <View style={ui.flexc}>
+        <TextView style={[styles.label, ui.flex1]}>{$t('charging.paymentMethod')}</TextView>
+        { app.v3.vouchers &&
+          <TextView style={[styles.label, {flex: 1, paddingLeft: 16}]}>{$t('voucher.vouchers')}</TextView>
+        }
+      </View>
+      
+      <View style={ui.flexc}>
+        <View style={ui.flex1}>
+          { app.charge.paymentMethod
+          ? <PaymentListV2
+              payType={currentPayment}
+              chargeBoxId={connectorInfo.chargeBoxId}
+              onMethodChange={onPaymentMethodChanged}/>
+          : <PaymentList
+              payType={currentPayment}
+              onMethodChange={onPaymentMethodChanged}/>
+          }
+        </View>
+        { app.v3.vouchers &&
+          <Pressable
+            style={styles.voucherLayout}
+            onPress={() => PagerUtil.toSelectVoucher()}>
+            <MaterialCommunityIcons
+              name="ticket-percent"
+              size={35}
+              color={colorAccent}/>
+            { utils.isNotEmpty(selectedVoucher.userVoucherId)
+              ? <View style={styles.vouchersView}>
+                  <TextView
+                    style={styles.voucherName}
+                    numberOfLines={1}>
+                    {selectedVoucher.voucherName}
+                  </TextView>
+                  <TextView
+                    style={styles.voucherDesc}
+                    numberOfLines={1}>
+                    {selectedVoucher.voucherDesc}
+                  </TextView>
+                </View>
+              : <View style={styles.vouchersView}>
+                  <TextView style={styles.selectText}>{$t("voucher.selectVoucher")}</TextView>
+                </View>
+              }
+              <FontAwesome6
+                name={"angle-right"}
+                size={16}
+                color={colorCancel}
+              />
+          </Pressable>
+        }
+      </View>
+      
       <Button
         style={styles.buttonView}
         text={$t('charging.btnAuthenticate')}
@@ -163,5 +209,42 @@ const styles = StyleSheet.create({
   buttonView: {
     marginTop: 8,
     marginBottom: 16
+  },
+  voucherLayout: {
+    flex: 1,
+    padding: 12,
+    maxWidth: $vw(50) - 24,
+    borderRadius: 10,
+    marginLeft: 16,
+    marginBottom: 12,
+    alignItems: 'center',
+    flexDirection: 'row',
+    ...ElevationObject(5),
+    backgroundColor: colorLight,
+  },
+  vouchersView: {
+    flex: 1,
+    paddingLeft: 8,
+    flexWrap: 'wrap',
+    alignItems: 'center',
+    flexDirection: 'row'
+  },
+  selectText: {
+    flex: 1,
+    color: textSecondary,
+    fontSize: 15,
+    fontWeight: 'bold'
+  },
+  voucherName: {
+    color: textPrimary,
+    fontSize: 15,
+    paddingLeft: 8,
+    fontWeight: 'bold'
+  },
+  voucherDesc: {
+    color: textSecondary,
+    fontSize: 12,
+    paddingLeft: 8,
+    paddingRight: 16
   }
 })

+ 495 - 0
Strides-APP/app/pages/chargingV3/ChargingPage.js

@@ -0,0 +1,495 @@
+/**
+ * 新充电流程:充电主页
+ * @邠心vbe on 2023/06/20
+ */
+import React, { Component } from 'react';
+import { View } from 'react-native';
+import app from '../../../app.json';
+import apiCharge from '../../api/apiCharge';
+import apiWallet from '../../api/apiWallet';
+import Dialog from '../../components/Dialog';
+import { ErrorDialog } from '../chargeV2/InfoDialog';
+import { PaymentDefault, PAYTYPE } from '../payment/PaymentConfig';
+import { PageList } from '../Router';
+import StepAuth from './StepAuth';
+import StepCharging from './StepCharging';
+import StepStart from './StepStart';
+import StepStop from './StepStop';
+import PagerUtil from '../chargeV2/PagerUtil';
+
+export default class ChargingPageV4 extends Component {
+  constructor(props) {
+    super(props);
+    this.state = {
+      isStoping: false,
+      isCharging: false,
+      isAuthentic: false,
+      chargingPk: "",
+      stationInfo: {},
+      connectorInfo: {},
+      errorCode: 'A9',
+      errorMessage: '',
+      lastUpdated: '',
+      showErrorDialog: false,
+      showStationDialog: false,
+      currentPerUse: "",
+      currentPayment: PaymentDefault.DEFAULT.payType,
+      currentPaytype: PaymentDefault.DEFAULT.payName,
+      selectedVoucher: {}
+    };
+    this.isPageShow = true;
+    this.waitStartCharging = false;
+  }
+
+  componentDidMount() {
+    this.init();
+    this.isPageShow = true;
+    console.log("参数", this.props.route.params);
+    if (this.props.route.params.connectorId && this.props.route.params.chargeBoxId) {
+      this.setState({
+        stationInfo: this.props.route.params
+      }, () => {
+        //测试进入
+        //this.testInit();
+        //正常进入
+        this.getConnectorInfo();
+      })
+    }
+    this.props.navigation.addListener('focus', () => {
+      //console.log("充电流程页面获取焦点" + this.isPageShow, this.state.currentPerUse);
+      if (!this.isPageShow && this.state.currentPerUse == "Pending") {
+        this.isPageShow = true;
+        //console.log("继续充电流程");
+        this.setState({
+          currentPerUse: "Paid"
+        })
+        this.refreshChargeData();
+      } else {
+        this.isPageShow = true;
+        //this.canShowLoginDialog();
+      }
+      if (PagerUtil.isSelectVoucher) {
+        this.setState({
+          selectedVoucher: PagerUtil.getSelectedVoucher()
+        })
+      }
+    });
+    this.props.navigation.addListener('blur', () => {
+      this.isPageShow = false;
+      //console.log("充电流程页面失去焦点");
+    });
+  }
+
+  componentWillUnmount() {
+    this.isPageShow = false;
+  }
+
+  testInit() {
+    this.setState({
+      isCharging: true,
+      connectorInfo: {
+        status: "Initiating"
+      }
+    }, () => {
+      setTimeout(() => {
+        this.getConnectorInfo();
+      }, 2000);
+    })
+  }
+
+  init() {
+    this.setState({
+      isStoping: false,
+      isCharging: false,
+      isAuthentic: false
+    });
+    this.waitAuthentic = false;
+    this.waitStartCharging = false;
+  }
+
+  getConnectorInfo() {
+    if (!this.isPageShow) return;
+    //this.init();
+    const params = {
+      sitePk: this.state.stationInfo.id,
+      chargeBoxId: this.state.stationInfo.chargeBoxId,
+      connectorId: this.state.stationInfo.connectorId,
+      paymentOption: this.state.currentPayment,
+    }
+    if (app.charge.paymentMethod && this.state.currentPayment?.code) {
+      params.paymentMethod = this.state.currentPayment?.code
+    }
+    console.log("参数", params);
+    apiCharge.getConnectorDetail(params).then(res => {
+      if (res.data.status && !this.state.isStoping) {
+        const state = {
+          isStoping: false,
+          isCharging: false,
+          isAuthentic: false,
+          connectorInfo: {}
+        }
+        state.connectorInfo = res.data;
+        if (app.charge.paymentMethod && res.data.currentPaymentMethod) {
+          //V3版获取当前支付方式
+          state.currentPayment = {
+            code: res.data.currentPaymentMethod
+          }
+        } else if (res.data.currentPaymentType && res.data.currentPaymentType == PAYTYPE.PAY_PER_USE) {
+          //V2版获取当前支付方式
+          state.currentPayment = PAYTYPE.PAY_PER_USE
+        }
+        console.log("状态", res.data.status);
+        switch (res.data.status) {
+          case 'Available': //可用的
+            if (this.waitAuthentic) {
+              state.isAuthentic = true;
+              this.refreshChargeData(3000);
+            } else {
+              state.isAuthentic = false;
+            }
+            break;
+          case 'Preparing': //已插入
+            this.waitAuthentic = false;
+            if (this.waitStartCharging) {
+              state.isCharging = true;
+              if (res.data.payPerUsePaymentStatus) {
+                //等待PayPerUse支付-初始化充电
+                if (res.data.payPerUsePaymentStatus == "PENDING" || res.data.payPerUsePaymentStatus == "PAID") {
+                  this.refreshChargeData(3000);
+                } else {
+                  this.showErrorDialog('A4', $t('charging.errPayperusePayment') + res.data.payPerUsePaymentStatus);
+                  state.isCharging = false;
+                  state.isAuthentic = true;
+                }
+              } else {
+                //普通充电-初始化充电
+                this.refreshChargeData(3000);
+              }
+            } else {
+              state.isAuthentic = true;
+              //this.checkIsCharge();
+            }
+            break;
+          case 'Charging': //正在充电
+            state.isCharging = true;
+            this.waitStartCharging = false;
+            this.refreshChargeData(10000);
+            break;
+          case 'Initiating': //充电确认中
+            state.isCharging = true;
+            if (res.data.payPerUsePaymentStatus) {
+              //等待PayPerUse支付-初始化充电
+              if (res.data.payPerUsePaymentStatus == "PENDING" || res.data.payPerUsePaymentStatus == "PAID") {
+                this.refreshChargeData();
+              } else {
+                this.showErrorDialog('A4', $t('charging.errPayperusePayment') + res.data.payPerUsePaymentStatus);
+                state.isCharging = false;
+                state.isAuthentic = false;
+              }
+            } else {
+              //普通充电-初始化充电
+              this.refreshChargeData();
+            }
+            break;
+          case 'SuspendedEVSE':
+            this.showErrorDialog('A5', $t('charging.errUnable2Charge'));
+            break;
+          case 'SuspendedEV': //已连接上但未充电
+            state.isAuthentic = true;
+            //this.refreshChargeData();
+            break;
+          case 'Reserved': //预定中
+            this.showErrorDialog('A5', $t('charging.errUnable2Reserved'));
+            break;
+          case 'Finishing': //已完成
+            if (res.data.chargingPk) {
+              Dialog.showProgressDialog();
+              setTimeout(() => {
+                Dialog.dismissLoading();
+                this.setState({
+                  isStart: false,
+                  isPending: false,
+                  isCharging: false
+                });
+                startPage(PageList.summary, { 
+                  chargingPk: res.data.chargingPk,
+                  id: this.state.stationInfo.id,
+                  name: this.state.stationInfo.name,
+                  address: this.state.stationInfo.address
+                });
+              }, 2000);
+            } else {
+              goBack();
+            }
+            break;
+          default:
+            this.showErrorDialog('A4', $t('charging.errNotChargeE0'));
+            break;
+        }
+        this.setState(state)
+      }
+    }).catch(err => {
+      Dialog.showResultDialog("An error occurred:\n" + err, "Retry", () => {
+        this.getConnectorInfo();
+      })
+      //toastShort(err)
+    })
+  }
+
+  refreshChargeData(time=2000) {
+    if (this.isPageShow) {
+      //console.log("[刷新获取充电信息]", time);
+      setTimeout(() => {
+        this.getConnectorInfo();
+      }, time);
+    }
+  }
+
+  onPaymentMethodChanged(payment) {
+    this.setState({
+      currentPayment: payment
+    })
+  }
+
+  onAuthenticate() {
+    this.waitAuthentic = true;
+    this.setState({
+      isAuthentic: true
+    }, () => {
+      this.refreshChargeData()
+    })
+  }
+
+  onStartCharge() {
+    this.setState({
+      isCharging: true
+    });
+    this.waitStartCharging = true;
+    if (app.charge.paymentMethod) { //V3版本开始充电
+      this.onStartChargeV3();
+      return;
+    }
+    if (this.state.currentPayment == PAYTYPE.PAY_PER_USE) { //V2版本PayPerUse
+      this.onStartChargePerUse();
+      return;
+    }
+    apiCharge.startCharge(this.state.stationInfo).then(res => {
+      console.log("[开始充电-onStartCharge]", res);
+      setTimeout(() => {
+        this.canAutoRefresh = true;
+        this.refreshChargeData(500);
+        //Dialog.dismissLoading();
+        if (res.msg) {
+          //Dialog.showResultDialog(res.msg)
+          toastShort(res.msg)
+        }
+        //this.autoCheckIsCharge();
+      }, 3000);
+    }).catch(({err, code, data}) => {
+      //toastShort(err);
+      console.log("[开始充电错误]", err, code);
+      //Dialog.dismissLoading();
+      if (code == 5200) {
+        this.showErrorDialog('none', "(" + data.transactionPk + ') ' + err);
+      } else {
+        this.showErrorDialog('A4', err);
+      }
+      this.setState({
+        isCharging: false
+      });
+    });
+  }
+
+  onStartChargePerUse() {
+    const params = {
+      paymentOption: this.state.currentPayment,
+      ...this.state.stationInfo
+    }
+    apiWallet.doPaymentV2(params).then(res => {
+      if (res.data.webPaymentUrl) {
+        this.setState({
+          currentPerUse: "Pending"
+        })
+        startPage(PageList.paymentWeb, { amount: params.payAmount, url: res.data.webPaymentUrl, type: 'PayPerUse' });
+      } else {
+        toastShort('Error 0')
+      }
+    }).catch(({err}) => {
+      this.showErrorDialog('A9', err);
+      this.setState({
+        isCharging: false
+      });
+    });
+  }
+
+  onStartChargeV3() {
+    const params = {
+      sitePk: this.state.stationInfo.id,
+      chargeBoxId: this.state.stationInfo.chargeBoxId,
+      connectorId: this.state.stationInfo.connectorId
+    }
+    if (this.state.currentPayment?.code) {
+      params.paymentMethod = this.state.currentPayment?.code
+    }
+    if (app.v3.vouchers && this.state.selectedVoucher?.userVoucherId) {
+      params.userVoucherIds = [this.state.selectedVoucher.userVoucherId]
+    }
+    console.log("[开始充电V3-params]", params);
+    apiCharge.startChargeV3(params).then(res => {
+      console.log("[开始充电V3-response]", res);
+      if (res.data.webPaymentUrl) {
+        this.setState({
+          currentPerUse: "Pending"
+        })
+        startPage(PageList.paymentWeb, { amount: params.sitePk, url: res.data.webPaymentUrl, type: 'PayPerUse' });
+      } else {
+        setTimeout(() => {
+          this.canAutoRefresh = true;
+          this.refreshChargeData(500);
+          if (res.msg) {
+            toastShort(res.msg)
+          }
+        }, 3000);
+      }
+    }).catch(({err, code, data}) => {
+      //toastShort(err);
+      console.log("[开始充电V3-错误]", err, code);
+      //Dialog.dismissLoading();
+      if (code == 5200) {
+        this.showErrorDialog('none', "(" + data.transactionPk + ') ' + err);
+      } else {
+        this.showErrorDialog('A4', err);
+      }
+      this.setState({
+        isCharging: false
+      });
+    });
+  }
+
+  onStopCharge() {
+    Dialog.showDialog({
+      title: $t('charging.titleStopCharging'),
+      message: $t('charging.confirmStopCharging'),
+      ok: $t('nav.confirm'),
+      callback: ok => {
+        if (ok == Dialog.BUTTON_OK) {
+          this.stopCharge();
+        }
+      }
+    });
+  }
+
+  stopCharge() {
+    this.setState({
+      isStoping: true
+    })
+    //Dialog.showProgressDialog();
+    apiCharge.stopCharge().then(res => {
+      if (res.data.chargingPk) {
+        setTimeout(() => {
+          //Dialog.dismissLoading();
+          if (res.msg) {
+            toastShort(res.msg)
+          }
+          this.setState({
+            chargingPk: res.data.chargingPk,
+            selectedVoucher: {}
+            //isCharging: false,
+            //isAuthentic: false
+          });
+          PagerUtil.setSelectedVoucher({});
+          /*this.init();
+          startPage(PageList.summary, { 
+            chargingPk: res.data.chargingPk,
+            id: this.state.stationInfo.id,
+            name: this.state.stationInfo.name,
+            address: this.state.stationInfo.address
+          });*/
+        }, 3000);
+      } else {
+        if (res.msg) {
+          toastShort(res.msg)
+        } else {
+          toastShort($t('charging.errDetected'));
+        }
+        this.refreshChargeData(500);
+      }
+    }).catch((err) => {
+      //Dialog.dismissLoading();
+      toastShort(err);
+      this.setState({
+        isStart: false,
+        isPending: false,
+        isCharging: false,
+        isStoping: false
+      });
+      //模拟进入结算页
+      /*startPage(PageList.summary, {
+        chargingPk: 1,
+        id: this.state.stationInfo.id,
+        name: this.state.stationInfo.name,
+        address: this.state.stationInfo.address
+      });*/
+    });
+  }
+
+  showErrorDialog(code, msg) {
+    this.setState({
+      errorCode: code,
+      showErrorDialog: true,
+      errorMessage: ''+msg
+    });
+  }
+
+  closeError() {
+    this.setState({
+      showErrorDialog: false,
+      showStationDialog: false
+    });
+  }
+
+  render() {
+    return (
+      <View style={ui.flex1}>
+        { this.state.isStoping
+        ? <StepStop
+            chargingPk={this.state.chargingPk}
+            stationInfo={this.state.stationInfo}
+          />
+        : ( this.state.isCharging 
+          ? <StepCharging
+              connectorInfo={this.state.connectorInfo}
+              currentPayment={this.state.currentPayment}
+              onStopCharge={() => this.onStopCharge()}
+              selectedVoucher={this.state.selectedVoucher}
+            />
+          : ( this.state.isAuthentic
+            ? <StepAuth
+                status={this.state.connectorInfo?.status}
+                connectorInfo={this.state.connectorInfo}
+                chargeBoxId={this.state.stationInfo.chargeBoxId}
+                currentPayment={this.state.currentPayment}
+                onStartCharge={() => this.onStartCharge()}
+                selectedVoucher={this.state.selectedVoucher}
+                onPaymentMethodChanged={(type) => this.onPaymentMethodChanged(type)}
+              />
+            : <StepStart
+                connectorInfo={this.state.connectorInfo}
+                currentPayment={this.state.currentPayment}
+                onAuthenticate={() => this.onAuthenticate()}
+                onPaymentMethodChanged={(type) => this.onPaymentMethodChanged(type)}
+              />
+            )
+          )
+        }
+        <ErrorDialog
+          visible={this.state.showErrorDialog}
+          code={this.state.errorCode}
+          message={this.state.errorMessage}
+          onClose={() => {
+            this.closeError();
+          }}/>
+      </View>
+    );
+  }
+}

+ 156 - 0
Strides-APP/app/pages/chargingV3/ConnectorInfo.js

@@ -0,0 +1,156 @@
+import React from 'react';
+import { StyleSheet, View } from 'react-native';
+import DiscountView from './DiscountView';
+import TextView from '../../components/TextView';
+import { ChargeStyle } from '../chargeV2/Charging';
+import app from '../../../app.json';
+import utils from '../../utils/utils';
+
+export default ConnectorInfo = ({
+  isCharging=false,
+  connectorInfo={}
+}) => {
+  return (
+    isCharging
+    ? <View>
+        <View style={styles.infoRow}>
+          <View style={styles.infoCard}>
+            <TextView style={styles.infoTitle}>{$t('charging.labelTimeElapsed')}</TextView>
+            <TextView
+              numberOfLines={1}
+              ellipsizeMode="tail"
+              style={styles.infoText}>{utils.minutes2HHMM(connectorInfo?.timeElapsed ?? 0)}</TextView>
+            <TextView style={styles.infoDesc}></TextView>
+          </View>
+          <View style={styles.infoCard}>
+            <TextView style={styles.infoTitle}>{$t('charging.labelTotalkWh')}</TextView>
+            <TextView
+              numberOfLines={1}
+              ellipsizeMode="tail"
+              style={styles.infoText}>{connectorInfo.totalKWhDelivered || "0"} kWh</TextView>
+            <TextView style={styles.infoDesc}></TextView>
+          </View>
+        </View>
+        <View style={styles.infoRow}>
+          <View style={styles.infoCard}>
+            <DiscountView visible={connectorInfo.hasDiscount}/>
+            <TextView style={styles.infoTitle}>{$t('charging.labelRate')}</TextView>
+            <TextView
+              numberOfLines={2}
+              ellipsizeMode="tail"
+              style={styles.infoText}>{connectorInfo.rates || "S$0.00/kWh"}</TextView>
+            {(app.modules.nationally && connectorInfo.rates != connectorInfo.userRates) && (
+              <TextView
+                numberOfLines={1}
+                ellipsizeMode="tail"
+                style={styles.infoDesc}>({connectorInfo.userRates || "S$0.00/kWh"})</TextView>)
+            }
+          </View>
+          <View style={styles.infoCard}>
+            <TextView style={styles.infoTitle}>{$t('charging.labelTotalCharges')}</TextView>
+            <TextView
+              numberOfLines={2}
+              ellipsizeMode="tail"
+              style={styles.infoText}>{connectorInfo.totalCharges || "S$ 0.0"}</TextView>
+            {(app.modules.nationally && connectorInfo.totalCharges != connectorInfo.userTotalCharges) && (
+              <TextView
+                numberOfLines={1}
+                ellipsizeMode="tail"
+                style={styles.infoDesc}>({connectorInfo.userTotalCharges || "S$ 0.0"})</TextView>)
+            }
+          </View>
+        </View>
+      </View>
+    : <View>
+      <View style={styles.infoRow}>
+        <View style={styles.infoCard}>
+          <TextView style={styles.infoTitle}>{$t('charging.labelType')}</TextView>
+          <TextView
+            numberOfLines={1}
+            ellipsizeMode="tail"
+            style={styles.infoText}>{connectorInfo.chargeType || "AC"}</TextView>
+          <TextView style={styles.infoDesc}></TextView>
+        </View>
+        <View style={styles.infoCard}>
+          <TextView style={styles.infoTitle}>{$t('charging.labelPower')}</TextView>
+          <TextView
+            numberOfLines={1}
+            ellipsizeMode="tail"
+            style={styles.infoText}>{connectorInfo.wattage || "0"} kW{/*connectorInfo.rateType*/}</TextView>
+          <TextView style={styles.infoDesc}></TextView>
+        </View>
+      </View>
+      <View style={styles.infoRow}>
+        <View style={styles.infoCard}>
+          <DiscountView visible={connectorInfo.hasDiscount}/>
+          <TextView style={styles.infoTitle}>{$t('charging.labelRate')}</TextView>
+          {/* <Text
+            numberOfLines={1}
+            ellipsizeMode="tail"
+            style={styles.infoText}>{connectorInfo.rate || "0.00"}/{connectorInfo.rateType || "kWh"}</Text> */}
+          <TextView
+            numberOfLines={2}
+            ellipsizeMode="tail"
+            style={styles.infoText}>{connectorInfo.rates || "S$0.00/kWh"}</TextView>
+          {(app.modules.nationally && connectorInfo.rates != connectorInfo.userRates) && (
+            <TextView
+              numberOfLines={1}
+              ellipsizeMode="tail"
+              style={styles.infoDesc}>({connectorInfo.userRates || "S$0.00/kWh"})</TextView>)
+          }
+        </View>
+        <View style={styles.infoCard}>
+          <TextView style={styles.infoTitle}>{$t('charging.labelStatus')}</TextView>
+          <TextView
+            style={[ChargeStyle.statusAuthenticated, styles.infoStatus]}>
+            {/*$t('charging.statusAvailable')*/}
+            {connectorInfo.status}
+          </TextView>
+        </View>
+      </View>
+    </View>
+)}
+
+const styles = StyleSheet.create({
+  infoRow: {
+    marginLeft: 24,
+    marginBottom: 24,
+    flexDirection: 'row'
+  },
+  infoCard: {
+    flex: 1,
+    paddingTop: 12,
+    paddingBottom: 12,
+    borderRadius: 10,
+    marginRight: 32,
+    overflow: 'hidden',
+    alignItems: 'center',
+    borderWidth: 1,
+    borderColor: textCancel,
+    //...ElevationObject(5),
+    backgroundColor: colorLight,
+  },
+  infoTitle: {
+    color: textPrimary,
+    fontSize: 12,
+    paddingTop: 1
+  },
+  infoText: {
+    color: textPrimary,
+    fontSize: 15,
+    textAlign: 'center',
+    fontWeight: 'bold',
+    ...$padding(12, 6)
+  },
+  infoDesc: {
+    color: textPrimary,
+    fontSize: 12,
+    marginTop: -12,
+    paddingBottom: 8
+  },
+  infoStatus: {
+    fontSize: 16,
+    fontWeight: 'bold',
+    ...$padding(16, 8)
+  }
+})

+ 42 - 0
Strides-APP/app/pages/chargingV3/DiscountView.js

@@ -0,0 +1,42 @@
+import React from 'react';
+import { View, StyleSheet } from 'react-native';
+import Svg, { Path } from 'react-native-svg';
+
+export default DiscountView = ({visible=false}) => {
+  if (visible) {
+    return (<>
+      <Svg
+        width={35}
+        height={35}
+        viewBox="0 0 40 40"
+        style={{top: 0, right: 0, zIndex: 2, position: 'absolute'}}>
+        <Path
+          fill={colorAccent}
+          d="M28 0H0L40 40V12C40 5.37258 34.6274 -30 28 0Z" />
+        <MaterialCommunityIcons
+          name="brightness-percent"
+          size={15}
+          color={"white"}
+          style={{top: 3, right: 3, position: 'absolute'}}
+        />
+      </Svg>
+      <View style={styles.cardDiscount}></View>
+    </>);
+  } else {
+    return <></>;
+  }
+};
+
+const styles = StyleSheet.create({
+  cardDiscount: {
+    top: 0,
+    left: 0,
+    right: 0,
+    bottom: 0,
+    borderWidth: 3,
+    borderRadius: 10,
+    position: 'absolute',
+    borderColor: colorAccent,
+    borderStyle: 'solid'
+  }
+})

+ 168 - 0
Strides-APP/app/pages/chargingV3/StatusImage.js

@@ -0,0 +1,168 @@
+import React, { useEffect, useRef } from 'react';
+import { Animated, Easing, Image, StyleSheet, Text, View } from 'react-native';
+import utils from '../../utils/utils';
+import TextView from '../../components/TextView';
+
+const size = $vw(35) > 280 ? 280 : $vw(35);
+
+export default StatusImage = ({
+  isLoading=false,
+  isAuthentic=false,
+  isInitial=false,
+  isCharging=false,
+  isStop=false,
+  soc=-1
+}) => {
+  var rotate = useRef(new Animated.Value(0)).current;
+  const spins = () => {
+    Animated.loop(
+      Animated.timing(rotate, {
+        toValue: 1,
+        duration: 5000,
+        easing: Easing.linear,
+        useNativeDriver: true
+      })
+    ).start();
+  }
+  useEffect(() => {
+    if (isLoading) {
+      spins();
+    }
+  }, [rotate]);
+  const spin = rotate.interpolate({
+    inputRange: [0, 1],
+    outputRange: ['0deg', '360deg']
+  })
+  return (
+    <View style={styles.chargingHeader}>
+      <View style={styles.statusView}>
+        { isAuthentic
+        ? <Image
+            style={styles.stepImage}
+            resizeMode="contain"
+            source={require('../../images/charge3/status-auth.png')}/>
+        : (isInitial 
+        ? <Image
+            style={styles.stepImage}
+            resizeMode="contain"
+            source={require('../../images/charge3/status-init.png')}/>
+        : (isCharging
+        ? <Image
+            style={styles.stepImage}
+            resizeMode="contain"
+            source={require('../../images/charge3/status-charge.png')}/>
+        : <Image
+            style={styles.stepImage}
+            resizeMode="contain"
+            source={require('../../images/charge3/status-default.png')}/>
+        ))}
+      </View>
+      { isLoading &&
+        <Animated.View style={[
+          styles.loadingBorder, {
+            transform: [{ 
+              rotate: spin 
+            }]
+          }
+        ]}>
+          { isCharging
+          ? <Image
+              style={styles.loadingImage}
+              resizeMode="contain"
+              source={require('../../images/charge3/anim-charging.png')}/>
+          : <Image
+              style={styles.loadingImage}
+              resizeMode="contain"
+              source={require('../../images/charge3/anim-loading.png')}/>
+          }
+        </Animated.View>
+      }
+      { (isAuthentic || isStop) &&
+        <View style={styles.authBorder}></View>
+      }
+      { isStop &&
+        <View style={styles.centerView}>
+          <Image
+            style={styles.checkedImage}
+            resizeMode="contain"
+            source={require('../../images/charge3/stop-check.png')}/>
+        </View>
+      }
+      { soc >= 0 &&
+        <View style={styles.socView}>
+          <TextView style={styles.socText}>{soc}%</TextView>
+        </View>
+      }
+    </View>
+  );
+}
+
+const styles = StyleSheet.create({
+  chargingHeader: {
+    margin: 16,
+    alignItems: 'center'
+  },
+  statusView: {
+    width: size,
+    height: size,
+    padding: 4,
+    alignItems: 'center',
+    justifyContent: 'center'
+  },
+  stepImage: {
+    width: '100%',
+    height: '100%'
+  },
+  authBorder: {
+    top: 0,
+    width: size,
+    height: size,
+    position: 'absolute',
+    borderWidth: 6,
+    borderRadius: size,
+    borderColor: '#00FF19'
+  },
+  loadingBorder: {
+    top: 0,
+    position: 'absolute',
+    alignItems: 'center',
+    justifyContent: 'center'
+  },
+  loadingImage: {
+    width: size,
+    height: size
+  },
+  centerView: {
+    top: 0,
+    width: size,
+    height: size,
+    position: 'absolute',
+    alignItems: 'center',
+    justifyContent: 'center'
+  },
+  checkedImage: {
+    width: size / 2.3,
+    height: size / 2.3
+  },
+  socView: {
+    width: size,
+    height: size,
+    position: 'absolute',
+    alignItems: 'center',
+    justifyContent: 'center'
+  },
+  socText: {
+    width: 38,
+    height: 38,
+    marginTop: size / -2.5,
+    marginLeft: size / -4,
+    fontSize: 13,
+    fontWeight: 'bold',
+    borderWidth: 2,
+    borderRadius: 38,
+    alignItems: 'center',
+    justifyContent: 'center',
+    borderColor: colorAccent,
+    backgroundColor: colorLight
+  }
+})

+ 224 - 0
Strides-APP/app/pages/chargingV3/StepAuth.js

@@ -0,0 +1,224 @@
+/**
+ * 新充电流程:验证插头模块
+ * @邠心vbe on 2023/06/20
+ */
+import React, { useEffect, useState } from 'react';
+import { View, Text, Image, StyleSheet, ScrollView, Pressable } from 'react-native';
+import app from '../../../app.json';
+import Button from '../../components/Button';
+import TextView from '../../components/TextView';
+import { PaymentList } from '../chargeV2/Payment';
+import PaymentListV2 from '../chargeV2/PaymentListV2';
+import StatusImage from './StatusImage';
+import ConnectorInfo from './ConnectorInfo';
+import utils from '../../utils/utils';
+import PagerUtil from '../chargeV2/PagerUtil';
+
+export default StepAuth = ({
+  status="",
+  currentPayment,
+  onStartCharge,
+  chargeBoxId,
+  connectorInfo,
+  selectedVoucher={},
+  onPaymentMethodChanged
+}) => {
+  const [loadingEmps, setEmps] = useState("");
+  const [isAuthentic, setAuthentic] = useState(false)
+  useEffect(() => {
+    if (status == "Preparing") {
+      setAuthentic(true);
+    } else {
+      changeEmps();
+    }
+  }, []);
+
+  useEffect(() => {
+    if (status == "Preparing") {
+      setAuthentic(true);
+    } else {
+      setTimeout(() => {
+        changeEmps();
+      }, 500);
+    }
+  }, [loadingEmps, status]);
+
+  const changeEmps = () => {
+    let emp = loadingEmps;
+    if (loadingEmps.length == 3) {
+      emp = "";
+    } else {
+      emp += ".";
+    }
+    setEmps(emp);
+  }
+
+  return (
+    <View style={styles.container}>
+      <ScrollView
+        style={styles.container}
+        contentContainerStyle={$padding(16)}>
+        <View style={styles.content}>
+          <StatusImage
+            isAuthentic={isAuthentic}
+            isLoading={true}/>
+          { isAuthentic
+          ? <TextView style={styles.stepTitle}>{$t('charging.stepAuthenticated')}</TextView>
+          : <View style={ui.flexcc}>
+              <TextView style={styles.stepTitle}>{$t('charging.stepAuthenticating')}</TextView>
+              <TextView style={[styles.stepTitle, {width: 30, marginRight: -10}]}>{loadingEmps}</TextView>
+            </View>
+          }
+          <TextView style={styles.stepDesc}>{$t(isAuthentic ? 'charging.stepAuthenticatedDesc' : 'charging.stepAuthenticatingDesc')}</TextView>
+        </View>
+        { isAuthentic &&
+          <>
+            <ConnectorInfo
+              connectorInfo={connectorInfo}/>
+            <View style={styles.bottomView}>
+              <TextView style={styles.label}>{$t('charging.paymentMethod')}</TextView>
+              { app.charge.paymentMethod
+              ? <PaymentListV2
+                  style={styles.paymentView}
+                  isSelect={isAuthentic}
+                  payType={currentPayment}
+                  chargeBoxId={chargeBoxId}
+                  borderColor={textCancel}
+                  onMethodChange={onPaymentMethodChanged}/>
+              : <PaymentList
+                  isSelect={isAuthentic}
+                  payType={currentPayment}
+                  onMethodChange={onPaymentMethodChanged}/>
+              }
+              <EndView half/>
+              { app.v3.vouchers && <>
+                <TextView style={styles.label}>{$t('voucher.vouchers')}</TextView>
+                <Pressable
+                  style={styles.paymentView}
+                  onPress={() => PagerUtil.toSelectVoucher()}>
+                  <MaterialCommunityIcons
+                    name="ticket-percent-outline"
+                    size={32}
+                    color={textPrimary}/>
+                  { utils.isNotEmpty(selectedVoucher.userVoucherId)
+                  ? <View style={styles.vouchersView}>
+                      <TextView
+                        style={styles.voucherName}
+                        numberOfLines={1}>
+                        {selectedVoucher.voucherName}
+                      </TextView>
+                      <TextView
+                        style={styles.voucherDesc}
+                        numberOfLines={1}>
+                        {selectedVoucher.voucherDesc}
+                      </TextView>
+                    </View>
+                  : <View style={styles.vouchersView}>
+                      <TextView style={styles.selectText}>{$t("voucher.selectVoucher")}</TextView>
+                    </View>
+                  }
+                  <FontAwesome6
+                    name={"angle-right"}
+                    size={16}
+                    color={colorCancel}
+                  />
+                </Pressable>
+              </>}
+            </View>
+            <View style={{height: 56}}></View>
+          </>
+        }
+      </ScrollView>
+      { isAuthentic &&
+        <Button
+          style={styles.buttonView}
+          text={$t('charging.btnStartCharging')}
+          elevation={1.5}
+          borderRadius={6}
+          onClick={onStartCharge}/>
+      }
+    </View>
+  );
+}
+
+const styles = StyleSheet.create({
+  container: {
+    flex: 1
+  },
+  content: {
+    flex: 1,
+    alignItems: 'center',
+    justifyContent: 'center'
+  },
+  stepImage: {
+    width: $vw(70),
+    height: $vw(16),
+    margin: 16
+  },
+  stepTitle: {
+    fontSize: 24,
+    fontWeight: 'bold',
+    color: colorAccent
+  },
+  stepDesc: {
+    color: textPrimary,
+    fontSize: 16,
+    textAlign: 'center',
+    ...$padding(0, 32, 32)
+  },
+  label: {
+    color: '#000',
+    fontSize: 14,
+    fontWeight: 'bold',
+    paddingBottom: 12
+  },
+  bottomView: {
+    paddingLeft: 16,
+    paddingRight: 16,
+    paddingBottom: 16
+  },
+  buttonView: {
+    left: 16,
+    right: 16,
+    bottom: 24,
+    position: 'absolute'
+  },
+  paymentView: {
+    ...$padding(10, 12),
+    borderWidth: 1,
+    borderRadius: 10,
+    borderColor: textCancel,
+    marginBottom: 12,
+    alignItems: 'center',
+    flexDirection: 'row',
+    backgroundColor: colorLight,
+    justifyContent: 'space-between'
+  },
+  vouchersView: {
+    flex: 1,
+    paddingLeft: 8,
+    alignItems: 'center',
+    flexDirection: 'row',
+    justifyContent: 'space-around'
+  },
+  selectText: {
+    flex: 1,
+    color: textSecondary,
+    fontSize: 15,
+    fontWeight: 'bold'
+  },
+  voucherName: {
+    flex: 1,
+    color: textPrimary,
+    fontSize: 15,
+    paddingLeft: 8,
+    fontWeight: 'bold'
+  },
+  voucherDesc: {
+    flex: 1,
+    color: textSecondary,
+    fontSize: 12,
+    paddingLeft: 8,
+    paddingRight: 16
+  }
+})

+ 234 - 0
Strides-APP/app/pages/chargingV3/StepCharging.js

@@ -0,0 +1,234 @@
+/**
+ * 新充电流程:正在充电模块
+ * @邠心vbe on 2023/06/20
+ */
+import React, { useEffect, useState } from 'react';
+import { ScrollView, StyleSheet, View } from 'react-native';
+import app from '../../../app.json';
+import Button, { ElevationObject } from '../../components/Button';
+import TextView from '../../components/TextView';
+import utils from '../../utils/utils';
+import { PaymentList } from '../chargeV2/Payment';
+import PaymentListV2 from '../chargeV2/PaymentListV2';
+import StatusImage from './StatusImage';
+import ConnectorInfo from './ConnectorInfo';
+
+const StepCharging = ({
+  connectorInfo={},
+  currentPayment,
+  onStopCharge,
+  selectedVoucher={}
+}) => {
+  const [isCharging, setCharging] = useState(false);
+  const [loadingEmps, setEmps] = useState("");
+
+  useEffect(() => {
+    const isCharge = (connectorInfo.status == "Charging");
+    setCharging(isCharge);
+    if (!isCharge) {
+      changeEmps();
+    }
+  }, [])
+
+  useEffect(() => {
+    const isCharge = (connectorInfo.status == "Charging");
+    setCharging(isCharge);
+    if (!isCharge) {
+      setTimeout(() => {
+        changeEmps();
+      }, 500);
+    }
+  }, [connectorInfo,loadingEmps])
+
+  const changeEmps = () => {
+    let emp = loadingEmps;
+    if (loadingEmps.length == 3) {
+      emp = "";
+    } else {
+      emp += ".";
+    }
+    setEmps(emp);
+  }
+
+  return (
+  isCharging
+  ? <View style={ui.flex1}>
+      <ScrollView
+        style={ui.flex1}
+        contentContainerStyle={$padding(16)}>
+        <View style={ui.center}>
+          <StatusImage
+            isCharging={true}
+            isLoading={true}
+            soc={connectorInfo.batteryPercent}/>
+          <TextView style={styles.stepTitle}>{$t('charging.statusCharging')}</TextView>
+          <TextView style={styles.stepDesc}>{$t('charging.stepChargingDesc')}</TextView>
+        </View>
+        <ConnectorInfo
+          isCharging={true}
+          connectorInfo={connectorInfo}/>
+        <View style={styles.bottomSelectView}>
+          <TextView style={styles.label}>{$t('charging.paymentMethod')}</TextView>
+          { app.charge.paymentMethod
+          ? <PaymentListV2
+              isSelect={false}
+              payType={currentPayment}
+              borderColor={textCancel}
+              style={styles.paymentView}/>
+          : <PaymentList
+              isSelect={false}
+              payType={currentPayment}/>
+          }
+          { utils.isNotEmpty(selectedVoucher.userVoucherId) && <>
+            <TextView style={styles.label}>{$t('voucher.vouchers')}</TextView>
+            <View
+              style={styles.paymentView}>
+              <MaterialCommunityIcons
+                name="ticket-percent-outline"
+                size={32}
+                color={textPrimary}/>
+              <View style={styles.vouchersView}>
+                <TextView
+                  style={styles.voucherName}
+                  numberOfLines={1}>
+                  {selectedVoucher.voucherName}
+                </TextView>
+                <TextView
+                  style={styles.voucherDesc}
+                  numberOfLines={1}>
+                  {selectedVoucher.voucherDesc}
+                </TextView>
+              </View>
+            </View>
+          </> }
+          <View style={{height: 56}}></View>
+        </View>
+      </ScrollView>
+      <Button
+        style={styles.buttonView}
+        text={$t('charging.btnStopCharging')}
+        elevation={1.5}
+        borderRadius={6}
+        onClick={onStopCharge}/>
+    </View>
+  : <View style={ui.flex1}>
+      <View style={styles.content}>
+        <StatusImage
+          isInitial={true}
+          isLoading={true}/>
+        <View style={ui.flexcc}>
+          <TextView style={styles.stepTitle}>{$t('charging.stepInitializing')}</TextView>
+          <TextView style={[styles.stepTitle, {width: 30, marginRight: -10}]}>{loadingEmps}</TextView>
+        </View>
+        <TextView style={styles.stepDesc}>{$t('charging.stepInitializingDesc')}</TextView>
+      </View>
+      {/* <View style={styles.bottomView}>
+        <TextView style={styles.label}>{$t('charging.paymentMethod')}</TextView>
+        { app.charge.paymentMethod
+        ? <PaymentListV2
+            isSelect={false}
+            payType={currentPayment}/>
+        : <PaymentList
+            isSelect={false}
+            payType={currentPayment}/>
+        }
+        <View style={styles.buttonView}></View>
+      </View> */}
+    </View>
+  );
+}
+
+export default StepCharging;
+
+const styles = StyleSheet.create({
+  content: {
+    flex: 1,
+    padding: 16,
+    alignItems: 'center'
+  },
+  stepTitle: {
+    fontSize: 24,
+    fontWeight: 'bold',
+    color: colorAccent
+  },
+  stepDesc: {
+    color: textPrimary,
+    fontSize: 16,
+    textAlign: 'center',
+    ...$padding(0, 32, 32)
+  },
+  infoCarded: {
+    flex: 1,
+    paddingTop: 12,
+    paddingBottom: 12,
+    borderRadius: 10,
+    marginRight: 16,
+    overflow: 'hidden',
+    alignItems: 'center',
+    ...ElevationObject(5),
+    backgroundColor: colorLight
+  },
+  label: {
+    color: '#000',
+    fontSize: 14,
+    fontWeight: 'bold',
+    paddingTop: 16,
+    paddingBottom: 8
+  },
+  bottomSelectView: {
+    paddingLeft: 16,
+    paddingRight: 16,
+    paddingBottom: 16
+  },
+  bottomView: {
+    left: 0,
+    right: 0,
+    bottom: 32,
+    padding: 16,
+    position: 'absolute'
+  },
+  paymentView: {
+    ...$padding(10, 12),
+    borderWidth: 1,
+    borderRadius: 10,
+    borderColor: textCancel,
+    marginBottom: 12,
+    alignItems: 'center',
+    flexDirection: 'row',
+    backgroundColor: colorLight,
+    justifyContent: 'space-between'
+  },
+  buttonView: {
+    left: 16,
+    right: 16,
+    bottom: 24,
+    position: 'absolute'
+  },
+  vouchersView: {
+    flex: 1,
+    paddingLeft: 8,
+    alignItems: 'center',
+    flexDirection: 'row',
+    justifyContent: 'space-around'
+  },
+  selectText: {
+    flex: 1,
+    color: textSecondary,
+    fontSize: 15,
+    fontWeight: 'bold'
+  },
+  voucherName: {
+    flex: 1,
+    color: textPrimary,
+    fontSize: 15,
+    paddingLeft: 8,
+    fontWeight: 'bold'
+  },
+  voucherDesc: {
+    flex: 1,
+    color: textSecondary,
+    fontSize: 12,
+    paddingLeft: 8,
+    paddingRight: 16
+  }
+})

+ 65 - 0
Strides-APP/app/pages/chargingV3/StepStart.js

@@ -0,0 +1,65 @@
+/**
+ * 新充电流程:充电前的模块
+ * @邠心vbe on 2023/06/20
+ */
+import React from 'react';
+import { View, StyleSheet, ScrollView } from 'react-native';
+import Button from '../../components/Button';
+import TextView from '../../components/TextView';
+import StatusImage from './StatusImage';
+import ConnectorInfo from './ConnectorInfo';
+
+export default StepStart = ({
+  connectorInfo={},
+  currentPayment,
+  onPaymentMethodChanged,
+  onAuthenticate
+}) => {
+  return (
+    <View style={ui.flex1}>
+      <ScrollView
+        style={ui.flex1}
+        contentContainerStyle={$padding(16)}>
+        <View style={ui.center}>
+          <StatusImage
+            isAuthentic={false}/>
+          <TextView style={styles.stepTitle}>{$t('charging.stepInsertConnector')}</TextView>
+          <TextView style={styles.stepDesc}>{$t('charging.stepInsertConnectorDesc')}</TextView>
+        </View>
+        <ConnectorInfo connectorInfo={connectorInfo}/>
+        <View style={{height: 56}}></View>
+      </ScrollView>
+      <Button
+        style={styles.buttonView}
+        text={$t('charging.btnAuthenticate')}
+        elevation={1.5}
+        borderRadius={6}
+        onClick={onAuthenticate}/>
+    </View>
+  );
+}
+
+const styles = StyleSheet.create({
+  stepImage: {
+    width: $vw(70),
+    height: $vw(16),
+    margin: 16
+  },
+  stepTitle: {
+    fontSize: 24,
+    fontWeight: 'bold',
+    color: colorAccent
+  },
+  stepDesc: {
+    color: textPrimary,
+    fontSize: 16,
+    textAlign: 'center',
+    ...$padding(0, 32, 32)
+  },
+  buttonView: {
+    left: 16,
+    right: 16,
+    bottom: 24,
+    position: 'absolute'
+  }
+})

+ 129 - 0
Strides-APP/app/pages/chargingV3/StepStop.js

@@ -0,0 +1,129 @@
+/**
+ * 新充电流程:停止充电模块
+ * @邠心vbe on 2023/06/20
+ */
+import React, { useEffect, useState } from 'react';
+import { View, Text, Image, StyleSheet } from 'react-native';
+import TextView from '../../components/TextView';
+import StatusImage from './StatusImage';
+import { PageList } from '../Router';
+
+export default StepStop = ({
+  chargingPk="",
+  stationInfo={}
+}) => {
+  const [isStoping, setStoping] = useState(false);
+  const [loadingEmps, setEmps] = useState("");
+
+  useEffect(() => {
+    if (chargingPk) {
+      setStoping(true);
+    } else {
+      changeEmps();
+    }
+  }, [chargingPk]);
+
+  useEffect(() => {
+    if (!isStoping) {
+      setTimeout(() => {
+        changeEmps();
+      }, 500);
+    }
+  }, [loadingEmps]);
+
+  const changeEmps = () => {
+    let emp = loadingEmps;
+    if (loadingEmps.length == 3) {
+      emp = "";
+    } else {
+      emp += ".";
+    }
+    setEmps(emp);
+  }
+
+  return (
+    <View style={styles.container}>
+      <View style={styles.content}>
+        <StatusImage
+          isStop={isStoping}
+          isLoading={!isStoping}
+        />
+        <View style={ui.flexcc}>
+          <TextView style={styles.stepTitle}>{$t(isStoping ? 'charging.stepStoppedCharge' : 'charging.stepStoppingCharge')}</TextView>
+          { !isStoping &&
+            <TextView style={[styles.stepTitle, {width: 30, marginRight: -10}]}>{loadingEmps}</TextView>
+          }
+        </View>
+        <TextView style={styles.stepDesc}>{$t(isStoping ? 'charging.stepStoppedChargeDesc' : 'charging.stepStoppingChargeDesc')}</TextView>
+      </View>
+      { isStoping &&
+        <View style={styles.bottomView}>
+          <Button
+            style={styles.buttonView}
+            text={$t('charging.btnViewReceipt')}
+            elevation={1.5}
+            borderRadius={6}
+            onClick={() => {
+              startPage(PageList.summary, { 
+                chargingPk: chargingPk,
+                id: stationInfo.id,
+                name: stationInfo.name,
+                address: stationInfo.address
+              });
+            }}/>
+          <Button
+            style={styles.buttonHomeView}
+            text={$t('charging.btnHome')}
+            elevation={1.5}
+            borderRadius={6}
+            onClick={() => startPage(PageList.home)}/>
+        </View>
+      }
+    </View>
+  )
+}
+
+const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+    padding: 16
+  },
+  content: {
+    flex: 1,
+    alignItems: 'center'
+  },
+  stepImage: {
+    width: $vw(70),
+    height: $vw(16),
+    margin: 16
+  },
+  stepTitle: {
+    fontSize: 24,
+    fontWeight: 'bold',
+    color: colorAccent
+  },
+  stepDesc: {
+    color: textPrimary,
+    fontSize: 16,
+    textAlign: 'center',
+    ...$padding(0, 32, 48)
+  },
+  label: {
+    color: '#000',
+    fontSize: 14,
+    fontWeight: 'bold',
+    paddingTop: 16,
+    paddingBottom: 8
+  },
+  bottomView: {
+    paddingBottom: 8
+  },
+  buttonView: {
+    marginTop: 8
+  },
+  buttonHomeView: {
+    marginTop: 12,
+    marginBottom: 8,
+    backgroundColor: colorPrimary
+  }
+})

+ 2 - 1
Strides-APP/app/pages/home/Drawer.js

@@ -22,7 +22,8 @@ export default DrawerView = ({isLogin=false, userInfo, onLogout, notificationCou
     apiCharge.getUserCharging().then(res => {
       Dialog.dismissLoading();
       if (res.data.sitePk) {
-        startPage(PageList.chargeDetailPage, {stationInfo: {id: res.data.sitePk}, action: 'view', from: PageList.home});
+        utils.toChargeDetailPage(res.data.sitePk, 'view', PageList.home);
+        //startPage(PageList.chargeDetailPage, {stationInfo: {id: res.data.sitePk}, action: 'view', from: PageList.home});
         //startPage(PageList.chargeDetail, { stationInfo: {id: res.data.sitePk}, action: 'view'});
       } else if (res.msg) {
         toastShort(res.msg);

+ 16 - 2
Strides-APP/app/pages/home/DrawerV2.js

@@ -24,7 +24,8 @@ export default DrawerV2 = ({isLogin=false, userInfo, onLogout, sideCountInfo={},
     apiCharge.getUserCharging().then(res => {
       Dialog.dismissLoading();
       if (res.data.sitePk) {
-        startPage(PageList.chargeDetailPage, {stationInfo: {id: res.data.sitePk}, action: 'view', from: PageList.home});
+        utils.toChargeDetailPage(res.data.sitePk, 'view', PageList.home);
+        //startPage(PageList.chargeDetailPage, {stationInfo: {id: res.data.sitePk}, action: 'view', from: PageList.home});
         //startPage(PageList.chargeDetail, { stationInfo: {id: res.data.sitePk}, action: 'view'});
       } else if (res.msg) {
         toastShort(res.msg);
@@ -173,6 +174,19 @@ export default DrawerV2 = ({isLogin=false, userInfo, onLogout, sideCountInfo={},
         </Button></>
       }
 
+      { (app.v3.vouchers && isLogin) &&
+          <Button
+            style={styles.itemButton}
+            viewStyle={styles.itemView}
+            onClick={() => startPage(PageList.myVoucher)}>
+            <MaterialIcons
+              style={styles.icon}
+              name="discount"
+              color={textPrimary}
+              size={24}/>
+            <TextView style={styles.label}>{$t('voucher.vouchers')}</TextView>
+          </Button>
+        }
       { (app.notifications.enable && isLogin) &&
         <Button
           style={styles.itemButton}
@@ -300,7 +314,7 @@ export default DrawerV2 = ({isLogin=false, userInfo, onLogout, sideCountInfo={},
           style={styles.itemButton}
           viewStyle={styles.itemView}
           onClick={() => {
-            startPage(PageList.paymentWeb);
+            startPage(PageList.selectVoucher);
           }}>
           <MaterialCommunityIcons
             style={styles.icon}

+ 2 - 1
Strides-APP/app/pages/home/maps/BottomSiteInfo.js

@@ -56,7 +56,8 @@ export default BottomSiteInfo = ({
     if (stationInfo.upcoming) {
       toastShort($t("home.upcoming"))
     } else {
-      startPage(PageList.chargeDetailPage, {stationInfo: stationInfo, action: 'view', from: PageList.home});
+      utils.toChargeDetailPage(stationInfo.id, 'view', PageList.home);
+      //startPage(PageList.chargeDetailPage, {stationInfo: stationInfo, action: 'view', from: PageList.home});
     }
   }
 

+ 11 - 5
Strides-APP/app/pages/my/EditProfile.js

@@ -2,7 +2,7 @@
  * Edit Profile页面
  * @邠心vbe on 2021/05/07
  */
-import React, { Component } from 'react';
+import React, { Component, useEffect, useState } from 'react';
 import { View, Text, StyleSheet, Pressable, Image, TextInput } from 'react-native';
 import ImagePicker from 'react-native-image-crop-picker';
 import Modal from 'react-native-modal';
@@ -60,9 +60,14 @@ const Divide = () => {
 }
 
 //编辑弹窗
-const EditDialog = ({visible, title, value, keyType, countryList, areaNo, showCalling=false, onChangedText}) => {
+const EditDialog = ({visible, title, value, keyType, countryList, areaNo="", showCalling=false, onChangedText}) => {
   var changedText = value;
-  var callingCode = areaNo;
+  var [callingCode, setCalling] = useState();
+  useEffect(() => {
+    if (visible && areaNo) {
+      setCalling(areaNo)
+    }
+  }, [visible])
   return (
     <Modal
       isVisible={visible}
@@ -79,10 +84,11 @@ const EditDialog = ({visible, title, value, keyType, countryList, areaNo, showCa
               value={callingCode}   
               nameKey='countryNum'
               valueKey='countryNum'
+              autoSelect={false}
               style={styles.countryView}
               textStyle={styles.selectText}
               onChange={(value, index)=> {
-                callingCode = value;
+                setCalling(value);
               }}
               customerItemView={
                 (item, index, onClick) => 
@@ -474,7 +480,7 @@ const styles = StyleSheet.create({
     position: "absolute"
   },
   countryView: {
-    width: 80,
+    width: 85,
     marginTop: 24,
     marginRight: 8,
     minHeight: 43,

+ 0 - 19
Strides-APP/app/pages/my/ProfileV2.js

@@ -170,25 +170,6 @@ export default class ProfileV2 extends Component {
             color={textCancel}
             name='angle-right'/>
         </Button>
-        { app.modules.vouchers &&
-          <Button
-            style={styles.cardView}
-            viewStyle={styles.profileItem}
-            onClick={() => startPage(PageList.myVoucher)}>
-            <MaterialIcons
-              style={styles.cardIcon}
-              name="discount"
-              size={32}
-              color="#00638C"/>
-            <View style={styles.cardInfo}>
-              <TextView style={styles.cardLabel}>{$t('voucher.myVouchers')}</TextView>
-            </View>
-            <FontAwesome
-              size={28}
-              color={textCancel}
-              name='angle-right'/>
-          </Button>
-        }
         {/* Vehicle List */}
         {/* <View style={styles.titleView}>
           <Text style={styles.title}>{$t('profile.myVehicles')}</Text>

+ 2 - 1
Strides-APP/app/pages/search/Search.js

@@ -72,7 +72,8 @@ export default class Search extends Component {
   }
 
   intoStation(info) {
-    startPage(PageList.chargeDetail, {stationInfo: info, action: 'search'});
+    utils.toChargeDetailPage(info.id, 'search', PageList.search);
+    //startPage(PageList.chargeDetail, {stationInfo: info, action: 'search'});
   }
 
   listItem = (props) => {

+ 2 - 1
Strides-APP/app/pages/search/SearchV2.js

@@ -108,7 +108,8 @@ export default class Search extends Component {
     if (info.upcoming) {
       toastShort($t("home.upcoming"))
     } else {
-      startPage(PageList.chargeDetailPage, {stationInfo: info, action: 'search', from: PageList.search});
+      utils.toChargeDetailPage(info.id, 'search', PageList.search);
+      //startPage(PageList.chargeDetailPage, {stationInfo: info, action: 'search', from: PageList.search});
     }
   }
 

+ 12 - 4
Strides-APP/app/pages/sign/ResetPasswordV2.js

@@ -20,6 +20,7 @@ export default class ResetPassword extends Component {
     this.StrengthView = StrengthView.V2
     this.strengthColor = ["#F5F5F5", "#F7C4CD", "#F2F8AC", "#F8DBAC", "#ACF8F6", "#A6E782"]
     this.state = {
+      email: '',
       strength: 0,
       password: '',
       wrongCount: true,
@@ -42,6 +43,11 @@ export default class ResetPassword extends Component {
         this.requestLogout();
       }, 1000);*/
     }
+    const email = userInfo.email;
+    this.formInfo.email = email;
+    this.setState({
+      email: email
+    });
   }
 
   componentWillUnmount() {
@@ -135,6 +141,10 @@ export default class ResetPassword extends Component {
       toastShort($t('sign.errEmailFormat'));
       return;
     }
+    if (!info.verificationCode) {
+      toastShort($t('sign.plsInputOTP'));
+      return;
+    }
     if (!this.state.password) {
       toastShort($t('sign.plsInputPassword'));
       return;
@@ -151,10 +161,6 @@ export default class ResetPassword extends Component {
       toastShort($t('sign.errPasswordConfirm'));
       return;
     }
-    if (!info.verificationCode) {
-      toastShort($t('sign.plsInputOTP'));
-      return;
-    }
     Dialog.showProgressDialog()
     apiUser.updatePassword(this.formInfo).then(res => {
       Dialog.dismissLoading()
@@ -223,6 +229,8 @@ export default class ResetPassword extends Component {
                 style={styles.inputView}
                 placeholder={$t('sign.labelEmail')}
                 placeholderTextColor={textPlacehoder}
+                value={this.state.email}
+                editable={false}
                 maxLength={50}
                 keyboardType="email-address"
                 textContentType='emailAddress'

+ 3 - 2
Strides-APP/app/pages/vouchers/ListPoints.js

@@ -67,7 +67,7 @@ export default class ListPoints extends Component {
         } else {
           this.setState({
             dataList: res.data,
-            hasMore: true
+            hasMore: res.data.length >= 10
           });
         }
       } else {
@@ -176,7 +176,7 @@ export default class ListPoints extends Component {
   }
 
   bottomView = () => {
-    if (!this.state.hasMore) {
+    if (this.state.dataList.length > 0 && !this.state.hasMore) {
       return (<Text style={styles.noMore}>{$t('voucher.noMore')}</Text>)
     } else {
       return null
@@ -194,6 +194,7 @@ export default class ListPoints extends Component {
         onEndReached={() => this.getDataListPage()}
         onEndReachedThreshold={0.3}
         ListEmptyComponent={<Text style={styles.noData}>{$t('voucher.noData')}</Text>}
+        ListFooterComponent={this.bottomView}
         refreshControl={
           <RefreshControl
             {...MyRefreshProps()}

+ 4 - 22
Strides-APP/app/pages/vouchers/ListVoucher.js

@@ -4,10 +4,11 @@
  */
 import React, { Component } from 'react';
 import { View, Text, StyleSheet, RefreshControl, FlatList } from 'react-native';
-import ViewRedeem from './ViewRedeem';
-import apiVoucher from '../../api/apiVoucher';
 import { MyRefreshProps } from '../../components/ThemesConfig';
 import TextView from '../../components/TextView';
+import ViewRedeem from './ViewRedeem';
+import VoucherType from './VoucherType';
+import apiVoucher from '../../api/apiVoucher';
 
 export default class ListVoucher extends Component {
   constructor(props) {
@@ -164,6 +165,7 @@ export default class ListVoucher extends Component {
         onEndReached={() => this.getDataListPage()}
         onEndReachedThreshold={0.3}
         ListEmptyComponent={<Text style={styles.noData}>{$t('voucher.noData')}</Text>}
+        ListFooterComponent={this.bottomView}
         refreshControl={
           <RefreshControl
             {...MyRefreshProps()}
@@ -209,17 +211,6 @@ const styles = StyleSheet.create({
     color: textCancel,
     fontSize: 12
   },
-  purchaseButton: {
-    padding: 4,
-    minWidth: 75,
-    alignItems: 'center'
-  },
-  claimeButton: {
-    borderWidth: 1,
-    borderColor: colorAccent,
-    borderRadius: 6,
-    backgroundColor: colorLight
-  },
   statusButton: {
     color: colorAccent,
     padding: 8,
@@ -232,15 +223,6 @@ const styles = StyleSheet.create({
     textTransform: 'uppercase',
     backgroundColor: colorLight
   },
-  getForText: {
-    color: textLight,
-    fontSize: 12,
-    fontWeight: 'bold'
-  },
-  getValueText: {
-    color: textLight,
-    fontSize: 10
-  },
   noData: {
     color: textPlacehoder,
     fontSize: 14,

+ 1 - 1
Strides-APP/app/pages/vouchers/ViewRedeem.js

@@ -48,7 +48,7 @@ export default ViewRedeem = ({
         })
       }, 300);
     } else {
-      toastLong($t("voucher.plsinputPromoCode"))
+      toastLong($t("voucher.plsInputPromoCode"))
     }
   }
   return (

+ 255 - 0
Strides-APP/app/pages/vouchers/VoucherSelect.js

@@ -0,0 +1,255 @@
+import React, { Component } from 'react';
+import { View, Text, StyleSheet, RefreshControl, FlatList, Pressable } from 'react-native';
+import VoucherType from './VoucherType';
+import apiVoucher from '../../api/apiVoucher';
+import { MyRefreshProps } from '../../components/ThemesConfig';
+import TextView from '../../components/TextView';
+import BadgeSelectItem from '../../components/BadgeSelectItem';
+import Button from '../../components/Button';
+import PagerUtil from '../chargeV2/PagerUtil';
+
+export default class VoucherSelect extends Component {
+  constructor(props) {
+    super(props);
+    this.state = {
+      dataList: [],
+      voucherType: "",
+      hasMore: true,
+      refreshing: false,
+      selectedVoucher: {}
+    };
+  }
+
+  componentDidMount() {
+    this.getDataList();
+    this.props.navigation.addListener('beforeRemove', (e) => {
+      PagerUtil.setSelectedVoucher(this.state.selectedVoucher);
+    });
+  }
+
+  onRefresh() {
+    this.setState({
+      refreshing: true
+    })
+    this.getDataList();
+  }
+
+  getDataList(lastId) {
+    apiVoucher.getSelectionVoucher({
+      chargeBoxId: "LUMI-TEST",
+      connectorId: "1",
+      voucherType: this.state.voucherType
+    }).then(res => {
+      if (res.data) {
+        if (lastId) {
+          if (res.data.length > 0) {
+            const list = this.state.dataList;
+            this.setState({
+              dataList: list.concat(res.data),
+              hasMore: true
+            });
+          } else {
+            this.setState({
+              hasMore: false
+            })
+          }
+        } else {
+          this.setState({
+            dataList: res.data,
+            hasMore: res.data.length == 0
+          });
+        }
+      } else {
+        this.setState({
+          dataList: [],
+          hasMore: true
+        });
+      }
+    }).catch(err => {
+      toastShort(err)
+      this.setState({
+        dataList: [],
+        hasMore: true
+      });
+    }).finally(() => {
+      this.setState({
+        refreshing: false,
+        selectedVoucher: {}
+      });
+    });
+  }
+
+  getDataListPage() {
+    if (this.state.dataList.length > 0 && this.state.hasMore) {
+      const last = this.state.dataList[this.state.dataList.length-1]
+      this.getDataList(last.voucherId);
+    }
+  }
+
+  getColorByStatus(status) {
+    let color = colorAccent;
+    switch (status) {
+      case "Used":
+      case "Expired":
+        color = "#434343";
+        break;
+      case "Expiring":
+        color = "#ED3F3F"
+        break;
+    }
+    return color;
+  }
+
+  changeType(type) {
+    this.setState({
+      voucherType: type
+    });
+    this.getDataList();
+  }
+
+  onSetectVoucher(item) {
+    if (item.userVoucherId != this.state.selectedVoucher.userVoucherId) {
+      this.setState({
+        selectedVoucher: item
+      })
+    } else {
+      this.setState({
+        selectedVoucher: {}
+      })
+    }
+  }
+
+  listItem = ({item, index, separators}) => {
+    return (
+      <BadgeSelectItem
+        style={styles.itemView}
+        borderColor='#DADADA'
+        checked={this.state.selectedVoucher.userVoucherId == item.userVoucherId}
+        onPress={() => this.onSetectVoucher(item)}>
+        <View style={styles.itemContent}>
+          <TextView style={styles.voucherTitle}>{item.voucherName}</TextView>
+          <TextView style={styles.voucherDesc}>{item.voucherDesc}</TextView>
+          <TextView style={styles.expireDate}>{$t("voucher.expiresOn") + item.expiresOn}</TextView>
+        </View>
+        <TextView
+          style={[
+            styles.statusButton, {
+              color: this.getColorByStatus(item.userVoucherStatus),
+              borderColor: this.getColorByStatus(item.userVoucherStatus)
+            }
+          ]}>
+          {item.userVoucherStatus}
+        </TextView>
+      </BadgeSelectItem>
+    )
+  }
+
+  topView = (props) => {
+    return (
+      <VoucherType
+        type={this.state.voucherType}
+        onChange={type => this.changeType(type)}
+      />
+    );
+  }
+
+  bottomView = () => {
+    if (!this.state.hasMore) {
+      return (<Text style={styles.noMore}>{$t('voucher.noMore')}</Text>)
+    } else {
+      return null
+    }
+  }
+
+  render() {
+    return (
+      <View style={styles.container}>
+        <FlatList
+          style={ui.flex1}
+          data={this.state.dataList}
+          renderItem={this.listItem}
+          ListHeaderComponent={this.topView}
+          keyExtractor={item => item.userVoucherId}
+          onEndReached={() => this.getDataListPage()}
+          onEndReachedThreshold={0.3}
+          ListEmptyComponent={<Text style={styles.noData}>{$t('voucher.noData')}</Text>}
+          ListFooterComponent={this.bottomView}
+          refreshControl={
+            <RefreshControl
+              {...MyRefreshProps()}
+              refreshing={this.state.refreshing}
+              onRefresh={() => this.onRefresh()}
+            />
+          }/>
+        <Button
+          text={$t("nav.confirm")}
+          borderRadius={6}
+          onClick={() => goBack()}
+        />
+      </View>
+    );
+  }
+}
+
+const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+    paddingLeft: 16,
+    paddingRight: 16,
+    paddingBottom: 16
+  },
+  itemView: {
+    padding: 16,
+    marginTop: 16,
+    borderWidth: 1,
+    borderColor: '#DADADA',
+    borderRadius: 4,
+    alignItems: 'center',
+    flexDirection: 'row',
+    backgroundColor: colorLight
+  },
+  itemContent: {
+    flex: 1,
+    paddingRight: 16
+  },
+  voucherTitle: {
+    color: textPrimary,
+    fontSize: 16,
+    fontWeight: 'bold'
+  },
+  voucherDesc: {
+    color: textPrimary,
+    fontSize: 14,
+    paddingTop: 2,
+    paddingBottom: 4
+  },
+  expireDate: {
+    color: textCancel,
+    fontSize: 12
+  },
+  statusButton: {
+    color: colorAccent,
+    padding: 8,
+    minWidth: 73,
+    fontSize: 12,
+    textAlign: 'center',
+    borderWidth: 1,
+    borderColor: colorAccent,
+    borderRadius: 6,
+    textTransform: 'uppercase',
+    backgroundColor: colorLight
+  },
+  noData: {
+    color: textPlacehoder,
+    fontSize: 14,
+    padding: 20,
+    marginTop: 16,
+    textAlign: 'center'
+  },
+  noMore: {
+    color: textPlacehoder,
+    fontSize: 14,
+    padding: 16,
+    textAlign: 'center'
+  }
+})

+ 22 - 6
Strides-APP/app/pages/vouchers/VoucherType.js

@@ -9,10 +9,20 @@ export default VoucherType = ({
   onChange
 }) => {
   const [options, setOption] = useState([])
+  const [label, setLabel] = useState([])
   useEffect(() => {
     apiVoucher.getVoucherTypeOptions().then(res => {
       if (res.data) {
-        setOption(res.data)
+        const opt = [];
+        const names = $t("voucher.typeOptions")
+        res.data.forEach(item => {
+          opt.push({
+            label: names[item.key],
+            value: item.value
+          })
+        });
+        setOption(opt);
+        setLabel(opt[0].label);
       }
     }).catch(err => {
       setOption([])
@@ -21,24 +31,30 @@ export default VoucherType = ({
   return (
     <View style={styles.typeView}>
       <View style={ui.flex1}></View>
-      <TextView style={styles.typeText}>{type}</TextView>
+      <TextView style={styles.typeText}>{label}</TextView>
+      { options.length > 0 &&
       <Dropdown
         style={styles.selectView}
         textStyle={styles.selectText}
         rippleStyle={{}}
         title={$t('voucher.voucherType')}
+        prefixText="+"
         list={options}
         value={type}
-        autoSelect={true}
-        //valueKey='groupPk'
-        //nameKey='groupName'
-        onChange={(value, index)=> onChange(value)}/>
+        nameKey='label'
+        valueKey='value'
+        onChange={(value, index)=> {
+          setLabel(options[index].label)
+          onChange(value)
+        }}/>
+      }
     </View>
   )
 };
 
 const styles = StyleSheet.create({
   typeView: {
+    marginRight: -8,
     flexDirection: 'row',
     justifyContent: 'flex-end'
   },

+ 1 - 1
Strides-APP/app/pages/wallet/History.js

@@ -64,7 +64,7 @@ export default class History extends Component {
       this.stopRefresh();
       if (res.data && res.data.length) {
         this.setState({
-          historyList: res.data.slice(10)
+          historyList: res.data.slice(0, 10)
         });
       } else {
         this.setState({

+ 12 - 0
Strides-APP/app/utils/utils.js

@@ -4,6 +4,8 @@ import { host } from '../api/http';
 import apiUser from "../api/apiUser";
 import { getStorageSync, setStorage } from "./storage";
 import { PERMISSIONS } from "react-native-permissions";
+import app from '../../app.json';
+import { PageList } from "../pages/Router";
 
 /**
  * 工具集
@@ -303,5 +305,15 @@ export default {
     } else {
       return PERMISSIONS.ANDROID.WRITE_EXTERNAL_STORAGE;
     }
+  },
+  toChargeDetailPage(sitePk, action, form) {
+    if (app.charge.version == 1) {
+      startPage(PageList.chargeDetail, { stationInfo: {id: sitePk}, action: action, from: form});
+    } else {
+      startPage(PageList.chargeDetailPage, {stationInfo: {id: sitePk}, action: action, from: form});
+    }
+  },
+  toChargingPage(connectorInfo) {
+    startPage(app.charge.version == 4 ? PageList.chargingPageV4 : PageList.chargingPage, connectorInfo);
   }
 }