Parcourir la source

feature: 银行卡详情,删除cid详情跳转

ljc il y a 5 mois
Parent
commit
c140aa9569

BIN
src/assets/image/card/icon-visa.png


+ 5 - 0
src/enum/card/recharge.ts

@@ -0,0 +1,5 @@
+export const rechargeTypes = {
+  '1': 'Ucard.Recharge.r1',
+  '2': 'Ucard.Recharge.r2',
+  '3': 'Ucard.Recharge.r3',
+}

+ 9 - 8
src/routers/modules/uCard.ts

@@ -192,14 +192,15 @@ const uCardRoute = {
     //     name: 'R-Recharge',
     //     component: () => import(/* webpackChunkName: "Recharge" */ '@/views/card/Recharge.vue'),
     //   },
-    //   {
-    //     meta: {
-    //       OnBreadCrumb: true,
-    //     },
-    //     path: 'detail/:cardNumber',
-    //     name: 'R-CardDetail',
-    //     component: () => import(/* webpackChunkName: "CardDetail" */ '@/views/card/CardDetail.vue'),
-    //   },
+    {
+      meta: {
+        OnBreadCrumb: true,
+      },
+      path: 'detail/:cardNumber',
+      name: 'R-CardDetail',
+      component: () =>
+        import(/* webpackChunkName: "CardDetail" */ '@/views/card/CardDetail/index.vue'),
+    },
   ],
 }
 export default uCardRoute

+ 12 - 0
src/service/ucard.ts

@@ -355,6 +355,18 @@ class UCardService extends Service {
   async getBlockchainTransactionPage(params = {}) {
     return await this.post('/wasabi/encrypted/wallet/transaction/page', params)
   }
+  // 银行卡详情
+  async getCardDetails(params = {}) {
+    return await this.post('/wasabi/card/info/query', params)
+  }
+  // 充值记录分页查询
+  async rechargeList(params = {}) {
+    return await this.post('/wasabi/card/recharge/page', params)
+  }
+  // 扣款交易记录列表
+  async getCardWithdrawTransactionPage(params = {}) {
+    return await this.post('/wasabi/card/withdraw/page', params)
+  }
 }
 
 export default new UCardService()

+ 118 - 0
src/utils/filter.ts

@@ -0,0 +1,118 @@
+export const PercentFormat = (value) => {
+  if (value == null) {
+    return '+0%'
+  } else if (value < 0) {
+    return value + '%'
+  } else if (value == '+∞') {
+    return value
+  } else if (value == '-∞') {
+    return value
+  } else {
+    return '+' + value + '%'
+  }
+}
+
+export const NumberFormat = (value) => {
+  if (isNaN(value)) {
+    return 0
+  }
+  const num = value.toString().split('.')
+  const interCount = num[0].length
+  if (interCount < 3) {
+    return value
+  }
+  let index = 0
+  let inter = ''
+  for (let i = interCount - 3; i >= 0; i -= 3) {
+    inter = num[0].substr(i, 3) + (inter == '' ? '' : ',') + inter
+    index = i
+  }
+  if (index > 0) {
+    inter = num[0].substr(0, index) + (inter == '' ? '' : ',') + inter
+  }
+  return inter + (num.length == 1 ? '' : '.' + num[1])
+}
+
+export const PwdFormat = (value) => {
+  if (value == null) {
+    return 'Null'
+  } else {
+    const len = value.toString().length
+    let val = ''
+    for (let i = 0; i < len; i++) {
+      val = val + '*'
+    }
+    return val
+  }
+}
+export const NumberDecimal = (value) => {
+  let realVal = ''
+  if (!isNaN(value) && value !== '') {
+    // 截取当前数据到小数点后两位
+    realVal = parseFloat(value).toFixed(2)
+  } else {
+    realVal = '--'
+  }
+  return realVal
+}
+/**
+ * 格式化数字,添加千位分隔符
+ * @param {string|number} value - 要格式化的值
+ * @returns {string} 格式化后的字符串
+ */
+export const NumberFormatAll = (value) => {
+  // 处理特殊值
+  if (value === '***') {
+    return '***'
+  }
+
+  const numValue = Number(value)
+  if (isNaN(numValue)) {
+    return '0'
+  }
+
+  // 处理0
+  if (numValue === 0) {
+    return '0'
+  }
+
+  const isNegative = numValue < 0
+  const numStr = Math.abs(numValue).toString()
+  const [integerPart, decimalPart] = numStr.split('.')
+
+  // 如果整数部分长度小于3,直接返回
+  if (integerPart.length < 3) {
+    return (isNegative ? '-' : '') + numStr
+  }
+
+  // 格式化整数部分
+  const formattedInteger = formatIntegerWithCommas(integerPart)
+
+  // 组合结果
+  const result = decimalPart ? `${formattedInteger}.${decimalPart}` : formattedInteger
+
+  return (isNegative ? '-' : '') + result
+}
+
+/**
+ * 为整数部分添加千位分隔符
+ * @param {string} integerStr - 整数部分的字符串
+ * @returns {string} 添加了千位分隔符的字符串
+ */
+const formatIntegerWithCommas = (integerStr) => {
+  const parts = []
+  let remaining = integerStr
+
+  // 从右向左每3位一组
+  while (remaining.length > 3) {
+    parts.unshift(remaining.slice(-3))
+    remaining = remaining.slice(0, -3)
+  }
+
+  // 添加剩余的部分
+  if (remaining.length > 0) {
+    parts.unshift(remaining)
+  }
+
+  return parts.join(',')
+}

+ 18 - 0
src/utils/statusColor.ts

@@ -0,0 +1,18 @@
+export const statusColor = (status) => {
+  let color = 'crm_state_blue'
+  switch (status) {
+    case 'wait_process':
+      color = 'crm_state_yellow'
+      break
+    case 'processing':
+      color = 'crm_state_orange'
+      break
+    case 'success':
+      color = 'crm_state_blue'
+      break
+    case 'fail':
+      color = 'crm_state_gray'
+      break
+  }
+  return `state ${color}`
+}

+ 1 - 12
src/views/card/CardBusiness/index.vue

@@ -87,14 +87,7 @@
         <el-table :data="businessList" stripe style="margin-top: 20px; width: 100%">
           <el-table-column prop="cId" :label="$t('Ucard.Business.item1')">
             <template #default="scope">
-              <span
-                v-if="scope.row.cId"
-                class="crm-text-underline"
-                style="cursor: pointer"
-                @click="accountOpen(scope.row.cId)"
-                >{{ scope.row.cId }}</span
-              >
-              <span v-else>--</span>
+              <span>{{ scope.row.cId || '--' }}</span>
             </template>
           </el-table-column>
           <el-table-column :label="$t('Ucard.Business.item5')">
@@ -1474,10 +1467,6 @@
     }
   }
 
-  const accountOpen = (cId) => {
-    router.push({ name: 'R-CustomerDetail', params: { cId: cId } })
-  }
-
   const closeCidDialog = (val) => {
     dialogInfoCid.value = val
   }

+ 224 - 0
src/views/card/CardDetail/index.scss

@@ -0,0 +1,224 @@
+#CardDetail.InfoBox {
+  width: 100%;
+  height: 100%;
+  padding: 15px 0;
+  border-radius: 2px;
+  -webkit-box-sizing: border-box;
+  box-sizing: border-box;
+  background-color: #ffffff;
+  overflow: hidden;
+  overflow-y: auto;
+
+  .business-edit-form {
+    .el-form-item {
+      margin-bottom: 18px;
+    }
+
+    .el-input,
+    .el-select,
+    .el-date-picker {
+      width: 100%;
+    }
+
+    .el-form-item__label {
+      font-weight: 500;
+    }
+
+    .el-row {
+      margin-bottom: 0;
+    }
+
+    .el-col {
+      margin-bottom: 0;
+    }
+  }
+
+  // 全局滚动条样式
+  &::-webkit-scrollbar {
+    width: 8px;
+  }
+
+  &::-webkit-scrollbar-track {
+    background: #f1f1f1;
+    border-radius: 4px;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    background: #c1c1c1;
+    border-radius: 4px;
+
+    &:hover {
+      background: #a8a8a8;
+    }
+  }
+
+  .flex1{
+    flex: 1;
+  }
+  .ml5{
+    margin-left: 5px;
+  }
+  .cur-p{
+    cursor: pointer;
+  }
+  .info{
+    width: 100%;
+    height: 220px;
+    -webkit-box-sizing: border-box;
+    box-sizing: border-box;
+    //width: 0;
+    display: flex;
+    align-content: center;
+    justify-content: center;
+    padding: 0 20px;
+    .card{
+      border-radius: 10px;
+    }
+  }
+  .card-info{
+    width: 400px;
+    .card-icon{
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      margin-bottom: 30px;
+      .icon-visa{
+        width: 50px;
+      }
+      .icon-type{
+        font-weight: bold;
+        font-size: 20px;
+      }
+    }
+    .card-number{
+      font-weight: bold;
+      font-size: 24px;
+      text-align: left;
+      span{
+        font-weight: 400;
+        font-size: 20px;
+      }
+    }
+    .card-other-info{
+      margin-top: 25px;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      .other-title{
+        font-size: 12px;
+        color: #999;
+      }
+      .other-info{
+        margin-top: 5px;
+        .cvv{
+          cursor: pointer;
+        }
+        .can-see{
+          font-weight: bold;
+          font-size: 12px;
+          color: #999;
+          cursor: pointer;
+        }
+      }
+      .card-user-name{
+        //line-height: 30px;
+      }
+    }
+    .card-type{
+      margin-top: 20px;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      .state_blue{
+        color: #368FEC;
+      }
+      .state_gray{
+        color: #A1A1A1;
+      }
+      .state_orange{
+        color: #FF9800;
+      }
+      .state_yellow{
+        color: rgb(231, 211, 31);
+      }
+    }
+  }
+  .base-info{
+    flex: 1;
+    margin-left: 10px;
+    .card-base-title{
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      .title{
+        font-size: 18px;
+        font-weight: bold;
+      }
+      .card-tag{
+        .state{
+          padding: 0 5px;
+          line-height: 18px;
+          font-size: 10px;
+          border-radius: 4px;
+          color: #fff;
+        }
+      }
+    }
+    .card-base-info{
+      margin-top: 15px;
+      display: flex;
+      align-items: center;
+      align-content: center;
+      .mb20{
+        margin-bottom: 20px;
+      }
+      .mr10{
+        margin-right: 10px;
+      }
+      .money{
+        box-sizing: border-box;
+        height: 90px;
+        background-color: #eae9e9;
+        border-radius: 10px;
+        padding: 10px;
+        .money-title{
+          display: flex;
+          align-items: center;
+          justify-content: space-between;
+
+        }
+        .icon-refresh{
+          cursor: pointer;
+          color: #87D068;
+        }
+        .money-num{
+          margin-top: 15px;
+          font-size: 24px;
+          font-weight: bold;
+          color: #333;
+          text-align: left;
+        }
+      }
+      .info1{
+        height: 90px;
+        margin-left: 10px;
+        display: flex;
+        flex-direction: column;
+        justify-content: space-between;
+      }
+      .info-box{
+        text-align: left;
+
+      }
+      .info-title{
+        font-size: 12px;
+        color: #b3b3b3;
+        margin-bottom: 10px;
+      }
+      .info-text{
+        display: inline-flex;
+        font-size: 12px;
+      }
+    }
+  }
+}

+ 372 - 0
src/views/card/CardDetail/index.vue

@@ -0,0 +1,372 @@
+<template>
+  <div id="CardDetail" class="InfoBox">
+    <!-- 银行卡基础信息 -->
+    <div class="info">
+      <el-card class="card card-info" shadow="hover">
+        <div class="card-icon">
+          <div>
+            <img class="icon-visa" src="@/assets/image/card/icon-visa.png" />
+          </div>
+          <!-- 可能会有图标 -->
+          <div class="icon-type">{{ detail.type }}</div>
+        </div>
+        <div class="card-number">
+          {{ detail.cardNumber }}
+          <span>
+            <el-icon class="cur-p ml5" @click="copy(detail.cardNumber)"><Document /></el-icon>
+          </span>
+        </div>
+        <div class="card-other-info">
+          <div>
+            <div class="other-title">{{ t('card.Detail.thru') }}</div>
+            <div class="other-info">{{ detail.expireDate }}</div>
+          </div>
+          <div>
+            <div class="other-title">CVV</div>
+            <div class="other-info">
+              <span v-if="detail.expireDate" class="cvv" @click="getQueryCvv()">
+                ***
+                <el-icon><View /></el-icon>
+                <i class="el-icon-view" />
+              </span>
+            </div>
+          </div>
+          <div class="card-user-name">{{ `${detail.firstName} ${detail.lastName}` }}</div>
+        </div>
+        <div class="card-type">
+          <i
+            v-if="detail.freezeType == 1 && detail.freezeStatus != 'success'"
+            class="el-icon-lock state_yellow"
+            style="margin-right: 5px"
+          />
+          <i v-else class="el-icon-unlock state_blue" style="margin-right: 5px" />
+          <template v-if="detail.freezeType == 1">
+            <!-- 正常 -->
+            <span v-if="detail.freezeStatus == 'success'" class="state_blue">
+              {{ t('Ucard.VirtualCard.t7') }}
+            </span>
+            <!-- 解冻失败 -->
+            <span v-else-if="detail.freezeStatus == 'fail'" class="state_yellow">
+              {{ t('card.Btn.b19') }}
+            </span>
+            <!-- 解冻处理中 -->
+            <span v-else class="state_yellow">
+              {{ t('card.Btn.b21') }}
+            </span>
+          </template>
+          <template v-else>
+            <!-- 冻结 -->
+            <span v-if="detail.freezeStatus == 'success'" class="state_yellow">
+              {{ t('Ucard.VirtualCard.t8') }}
+            </span>
+            <!-- 解冻失败 -->
+            <span v-else-if="detail.freezeStatus == 'fail'" class="state_yellow">
+              {{ t('card.Btn.b20') }}
+            </span>
+            <!-- 冻结处理中 -->
+            <span v-else class="state_yellow">
+              {{ t('card.Btn.b22') }}
+            </span>
+          </template>
+        </div>
+      </el-card>
+      <el-card class="card base-info" shadow="hover">
+        <div class="card-base-title">
+          <div class="title">{{ t('Ucard.PaymentTransfer.item6') }}</div>
+          <div class="card-tag">
+            <div v-if="detail.status === 'unactivate'" class="state crm_state_gray">
+              {{ t('Ucard.VirtualCard.t9') }}
+            </div>
+            <div v-else-if="detail.status === 'success'" class="state crm_state_blue">
+              {{ t('Ucard.VirtualCard.t6') }}
+            </div>
+            <div v-else-if="detail.status === 'fail'" class="state crm_state_gray">
+              {{ t('Ucard.VirtualCard.t10') }}
+            </div>
+          </div>
+        </div>
+        <div class="card-base-info">
+          <div class="money flex1">
+            <div class="money-title">
+              <div>{{ t('Documentary.console.item15') }}</div>
+              <span>
+                <i
+                  class="iconfont iconshuaxin1 icon-refresh"
+                  style="font-weight: bold; font-size: 20px"
+                  @click="getMoney"
+                ></i>
+              </span>
+            </div>
+            <div class="money-num">$ {{ NumberDecimal(canUseMoney) }}</div>
+          </div>
+          <div class="info1 flex1">
+            <div class="info-box">
+              <div class="info-title">
+                {{ t('card.Detail.cardUserName') }}
+              </div>
+              <div class="info-text mr10">
+                {{ detail.firstName }}
+                <span>
+                  <el-icon class="cur-p ml5" @click="copy(detail.firstName)">
+                    <CopyDocument />
+                  </el-icon>
+                </span>
+              </div>
+              <div class="info-text">
+                {{ detail.lastName }}
+                <span>
+                  <el-icon class="cur-p ml5" @click="copy(detail.lastName)">
+                    <CopyDocument />
+                  </el-icon>
+                </span>
+              </div>
+            </div>
+            <div class="info-box">
+              <div class="info-title">
+                {{ t('card.Detail.address') }}
+              </div>
+              <div class="info-text">
+                {{ detail.realAddress }}
+                <span>
+                  <el-icon class="cur-p ml5" @click="copy(detail.realAddress)">
+                    <CopyDocument />
+                  </el-icon>
+                </span>
+              </div>
+            </div>
+          </div>
+          <div class="info1 flex1">
+            <div class="info-box">
+              <div class="info-title">
+                {{ t('card.Detail.email') }}
+              </div>
+              <div class="info-text">
+                {{ detail.email }}
+              </div>
+            </div>
+            <div class="info-box">
+              <div class="info-title">
+                {{ t('card.Detail.phone') }}
+              </div>
+              <div class="info-text">
+                {{ `${detail.areaCode} ${detail.mobile}` }}
+              </div>
+            </div>
+          </div>
+        </div>
+      </el-card>
+    </div>
+
+    <!-- cvv弹窗 -->
+    <el-dialog v-model="dialogCheckCvv" title="CVV" center class="dialog_header_w">
+      <div class="dia-content">
+        <el-form
+          :rules="rules"
+          :model="cvvForm"
+          label-width="130px"
+          label-position="right"
+          class="business-edit-form"
+        >
+          <el-form-item prop="gaCode" :label="t('getCode.R-GoogleSecretKey')">
+            <el-input
+              v-model="cvvForm.gaCode"
+              size="small"
+              :placeholder="t('Placeholder.Input')"
+            ></el-input>
+          </el-form-item>
+          <el-form-item v-if="cvvForm.cvv" prop="cvv" label="CVV">
+            <el-input
+              v-model="cvvForm.cvv"
+              size="small"
+              :placeholder="t('Placeholder.Input')"
+            ></el-input>
+          </el-form-item>
+        </el-form>
+      </div>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="cancel1">{{ t('Btn.Cancel') }}</el-button>
+          <el-button v-if="!cvvForm.cvv" type="primary" @click="queryCvv()">
+            {{ t('Btn.Confirm') }}
+          </el-button>
+          <el-button v-if="cvvForm.cvv" type="primary" @click="copyCvv()">
+            {{ t('Label.Copy') }}
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <div style="padding: 0 20px; margin-top: 20px">
+      <el-card class="financial-tab-card" shadow="hover">
+        <div class="tab-content">
+          <el-tabs v-model="activeTab" type="card" @tab-click="handleTabClick">
+            <el-tab-pane v-for="tab in tabs" :key="tab.name" :label="tab.label" :name="tab.name">
+              <component
+                :is="tab.component"
+                v-if="activeTab === tab.name && detail.cardNumber"
+                :card-number="detail.cardNumber"
+              />
+            </el-tab-pane>
+          </el-tabs>
+        </div>
+      </el-card>
+    </div>
+  </div>
+</template>
+
+<script setup>
+  import { ref, reactive, computed, onMounted, inject, defineAsyncComponent } from 'vue'
+  import { useI18n } from 'vue-i18n'
+  import { useRoute } from 'vue-router'
+  import { ElMessage } from 'element-plus'
+  import Service from '@/service/ucard'
+  import Config from '@/config/index'
+  import { copyText } from '@/utils/utils'
+  import { CopyDocument, Document, View } from '@element-plus/icons-vue'
+  import { NumberDecimal } from '@/utils/filter'
+
+  const { Code } = Config
+  const { t } = useI18n()
+  const route = useRoute()
+  const Session = inject('session')
+  const pigeon = inject('pigeon')
+
+  // 响应式数据
+  const detail = ref({})
+  const activeTab = ref('Recharge')
+  const dialogCheckCvv = ref(false)
+  const canUseMoney = ref('')
+  const cvvForm = reactive({
+    gaCode: undefined,
+    cvv: undefined,
+    id: undefined,
+  })
+
+  // 计算属性
+  const display = computed(() => {
+    return JSON.parse(Session.Get('display', true))
+  })
+
+  const user = computed(() => {
+    return JSON.parse(Session.Get('user', true))
+  })
+
+  const rules = computed(() => ({
+    gaCode: [
+      {
+        required: true,
+        message: t('vaildate.input.empty'),
+        trigger: 'blur',
+      },
+    ],
+  }))
+
+  const tabs = computed(() => [
+    {
+      name: 'Recharge',
+      label: t('card.tab7'),
+      component: defineAsyncComponent(() =>
+        import('@/views/card/components/CardDetailRecharge/index.vue')
+      ),
+    },
+    {
+      name: 'Transaction',
+      label: t('card.Detail.tab1'),
+      component: defineAsyncComponent(() =>
+        import('@/views/card/components/CardDetailTransactions/index.vue')
+      ),
+    },
+    {
+      name: 'Withdraw',
+      label: t('card.Detail.tab3'),
+      component: defineAsyncComponent(() =>
+        import('@/views/card/components/CardDetailWithdraw/index.vue')
+      ),
+    },
+  ])
+
+  // 方法
+  const cancel1 = () => {
+    dialogCheckCvv.value = false
+  }
+
+  // 获取银行卡详情
+  const getDetailByCardNumber = async (cardNumber) => {
+    const res = await Service.getCardDetails({ cardNumber })
+    if (res.code == Code.StatusOK) {
+      const { data } = res
+      detail.value = {
+        realAddress:
+          Session.Get('lang') == 'cn'
+            ? `${data.countryCnName} ${data.addressCn}`
+            : `${data.countryEnName} ${data.address}`,
+        ...data,
+      }
+      getMoney()
+    }
+  }
+
+  const getMoney = async () => {
+    if (!detail.value.uniqueId || !detail.value.cardNo) return
+
+    const params = { uniqueId: detail.value.uniqueId, cardNo: detail.value.cardNo }
+    const res = await Service.ucardBalance(params)
+    if (res.code == Code.StatusOK) {
+      canUseMoney.value = res.data.amount
+    }
+  }
+
+  const getQueryCvv = () => {
+    dialogCheckCvv.value = true
+    Object.assign(cvvForm, {
+      gaCode: undefined,
+      cvv: undefined,
+      id: detail.value.id,
+    })
+  }
+
+  // 查询cvv
+  const queryCvv = async () => {
+    const res = await Service.queryCvv({ ...cvvForm })
+    if (res.code == Code.StatusOK) {
+      cvvForm.cvv = res.data
+    } else {
+      ElMessage.error(res.msg)
+    }
+  }
+
+  const copyCvv = () => {
+    copy(cvvForm.cvv)
+  }
+
+  const handleTabClick = (e) => {
+    console.log(e, 12)
+  }
+
+  // 复制到剪切板
+  const copy = (str) => {
+    copyText(str)
+    ElMessage.success(t('Dashboard.Profile.CopySuccess'))
+  }
+
+  // 生命周期
+  onMounted(() => {
+    // 如果是页面形式,自动根据路由参数加载详情
+    if (route?.params?.cardNumber) {
+      getDetailByCardNumber(route.params.cardNumber)
+    }
+  })
+</script>
+
+<style lang="scss" scoped>
+  @import 'index.scss';
+</style>
+<style lang="scss">
+  #CardDetail.InfoBox {
+    .dialog_header_w {
+      .crm_search_down {
+        width: 400px;
+      }
+    }
+  }
+</style>

+ 1 - 7
src/views/card/CardKycAuth/index.vue

@@ -119,13 +119,7 @@
         <el-table :data="mock_tableData" stripe style="margin-top: 20px; width: 100%">
           <el-table-column prop="" align="left" :label="$t('Label.CidAccount')">
             <template #default="scope">
-              <span
-                v-if="scope.row.cId && display['R-KycAuth-Single']?.show"
-                class="crm-text-underline"
-                @click="accountOpen(scope.row.cId)"
-                >{{ scope.row.cId || '--' }}</span
-              >
-              <span v-else>{{ scope.row.cId || '--' }}</span>
+              <span>{{ scope.row.cId || '--' }}</span>
             </template>
           </el-table-column>
           <el-table-column prop="" align="left" :label="$t('Label.Name')">

+ 1 - 13
src/views/card/CardOperate/index.vue

@@ -91,14 +91,7 @@
         <el-table :data="mock_tableData" stripe style="margin-top: 20px; width: 100%">
           <el-table-column prop="cId" align="left" :label="$t('Ucard.UserOrder.item1')">
             <template #default="scope">
-              <span
-                v-if="scope.row.cId"
-                class="crm-text-underline"
-                style="cursor: pointer"
-                @click="accountOpen(scope.row.cId)"
-                >{{ scope.row.cId }}</span
-              >
-              <span v-else>--</span>
+              <span>{{ scope.row.cId || '--' }}</span>
             </template>
           </el-table-column>
           <el-table-column :label="$t('Ucard.UserOrder.item14')">
@@ -340,11 +333,6 @@
     searchFunc()
   }
 
-  // 打开账户详情
-  const accountOpen = (cId) => {
-    router.push({ name: 'R-CustomerDetail', params: { cId: cId } })
-  }
-
   const closeCidDialog = (val) => {
     dialogInfoCid.value = val
   }

+ 1 - 12
src/views/card/CardOrder/index.vue

@@ -114,13 +114,7 @@
         <el-table :data="mock_tableData" stripe style="margin-top: 20px; width: 100%">
           <el-table-column prop="" align="left" :label="$t('Label.CidAccount')">
             <template #default="scope">
-              <span
-                v-if="scope.row.cId && display['R-CardOrder-Btn1'].show"
-                class="crm-text-underline"
-                @click="accountOpen(scope.row.cId)"
-                >{{ scope.row.cId || '--' }}</span
-              >
-              <span v-else>{{ scope.row.cId || '--' }}</span>
+              <span>{{ scope.row.cId || '--' }}</span>
             </template>
           </el-table-column>
           <el-table-column prop="" align="left" :label="$t('Label.Name')">
@@ -432,11 +426,6 @@
     }
   }
 
-  // 详细信息cid
-  const accountOpen = (cId) => {
-    router.push({ name: 'R-CustomerDetail', params: { cId: cId } })
-  }
-
   const searchSingleCid = async (cId) => {
     const res = await Service1.cidRealSingle({
       id: cId,

+ 1 - 12
src/views/card/CardTransactions/index.vue

@@ -122,13 +122,7 @@
         <el-table :data="mock_tableData" stripe style="margin-top: 20px; width: 100%">
           <el-table-column prop="" align="left" :label="$t('Label.CidAccount')">
             <template #default="scope">
-              <span
-                v-if="scope.row.cId && display['R-Transactions-Btn1'].show"
-                class="crm-text-underline"
-                @click="accountOpen(scope.row.cId)"
-                >{{ scope.row.cId || '--' }}</span
-              >
-              <span v-else>{{ scope.row.cId || '--' }}</span>
+              <span>{{ scope.row.cId || '--' }}</span>
             </template>
           </el-table-column>
           <el-table-column prop="" align="left" :label="$t('Label.Name')">
@@ -306,11 +300,6 @@
     router.push({ name: 'R-CardDetail', params: { cardNumber: cardNumber } })
   }
 
-  // 详细信息cid
-  const accountOpen = (cId) => {
-    router.push({ name: 'R-CustomerDetail', params: { cId: cId } })
-  }
-
   const searchSingleCid = async (cId) => {
     const res = await Service1.cidRealSingle({
       id: cId,

+ 1 - 13
src/views/card/CardUserOrder/index.vue

@@ -120,14 +120,7 @@
         <el-table :data="mock_tableData" stripe style="margin-top: 20px; width: 100%">
           <el-table-column prop="cId" align="left" :label="$t('Ucard.UserOrder.item1')">
             <template #default="scope">
-              <span
-                v-if="scope.row.cId"
-                class="crm-text-underline"
-                style="cursor: pointer"
-                @click="accountOpen(scope.row.cId)"
-                >{{ scope.row.cId }}</span
-              >
-              <span v-else>--</span>
+              <span>{{ scope.row.cId || '--' }}</span>
             </template>
           </el-table-column>
           <el-table-column :label="$t('Ucard.UserOrder.item14')">
@@ -330,11 +323,6 @@
     searchFunc()
   }
 
-  // 详细信息cid
-  const accountOpen = (cId) => {
-    router.push({ name: 'R-CustomerDetail', params: { cId: cId } })
-  }
-
   const closeCidDialog = (val) => {
     dialogInfoCid.value = val
   }

+ 1 - 11
src/views/card/CardVirtualCard/index.vue

@@ -135,13 +135,7 @@
         <el-table :data="mock_tableData" stripe style="margin-top: 20px; width: 100%">
           <el-table-column prop="" align="left" :label="$t('Label.CidAccount')">
             <template #default="scope">
-              <span
-                v-if="scope.row.cId && display['R-VirtualCard-Btn1'].show"
-                class="crm-text-underline"
-                @click="accountOpen(scope.row.cId)"
-                >{{ scope.row.cId || '--' }}</span
-              >
-              <span v-else>{{ scope.row.cId || '--' }}</span>
+              <span>{{ scope.row.cId || '--' }}</span>
             </template>
           </el-table-column>
           <el-table-column prop="" align="left" :label="$t('Label.Name')">
@@ -570,10 +564,6 @@
     exportExcel(pigeon, '/wasabi/card/list/export', { ...search }, 'Bank_Cards_List')
   }
 
-  const accountOpen = (cId) => {
-    router.push({ name: 'R-CustomerDetail', params: { cId: cId } })
-  }
-
   const cardOpen = (cardNumber) => {
     router.push({
       name: 'R-CardDetail',

+ 1 - 1
src/views/card/CardWithdrawTransaction/index.vue

@@ -189,7 +189,7 @@
   })
 
   const display = computed(() => {
-    return JSON.parse(sessionStorage.getItem('display') || '{}')
+    return JSON.parse(Session.Get('display', true))
   })
 
   const tagOptions = computed(() => [

+ 12 - 0
src/views/card/components/CardDetailRecharge/const.ts

@@ -0,0 +1,12 @@
+export const approveStatusOptions = [
+  { label: 'Ucard.UserOrder.t11', value: 1 },
+  { label: 'Ucard.UserOrder.t12', value: 2 },
+  { label: 'Ucard.UserOrder.t13', value: 3 },
+]
+
+export const statusOptions = [
+  { label: 'Ucard.Recharge.t1', value: 'success' },
+  { label: 'Ucard.Recharge.t2', value: 'fail' },
+  { label: 'Ucard.VirtualCard.t11', value: 'wait_process' },
+  { label: 'Ucard.Recharge.t3', value: 'processing' },
+]

+ 372 - 0
src/views/card/components/CardDetailRecharge/index.vue

@@ -0,0 +1,372 @@
+<template>
+  <div
+    id="review_Email"
+    v-loading="pictLoading"
+    class="view"
+    :element-loading-background="'rgba(43, 48, 67, 0.65)'"
+    :element-loading-spinner="'el-icon-loading'"
+  >
+    <div class="crm_search">
+      <el-form ref="formRef" :model="search" label-width="">
+        <el-row>
+          <el-col :span="24" :md="24" :lg="24">
+            <el-form-item style="margin-right: 10px">
+              <el-select
+                v-model="search.approveStatus"
+                class="crm-border-radius-no"
+                clearable
+                :placeholder="t('R-Review-Status')"
+                @change="toSearch"
+              >
+                <el-option
+                  v-for="option in approveStatusOptions"
+                  :key="option.value"
+                  :label="t(option.label)"
+                  :value="option.value"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item>
+              <el-select
+                v-model="search.status"
+                class="crm-border-radius-no"
+                clearable
+                :placeholder="t('Ucard.Recharge.item10')"
+                @change="toSearch"
+              >
+                <el-option
+                  v-for="option in statusOptions"
+                  :key="option.value"
+                  :label="t(option.label)"
+                  :value="option.value"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item>
+              <el-button
+                class="crm-border-radius-no crm-border-left-no"
+                :icon="Search"
+                @click="toSearch"
+              ></el-button>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-form-item>
+          <el-button
+            v-if="display['R-Recharge-Export']?.show"
+            type="primary"
+            style="margin-left: 8px"
+            @click="exportAgents"
+          >
+            {{ t('Btn.Export') }}
+          </el-button>
+        </el-form-item>
+      </el-form>
+
+      <div class="card-mock-demo" style="margin: 30px 0">
+        <el-table
+          :data="mock_tableData"
+          show-summary
+          :summary-method="getSummaries1"
+          stripe
+          style="margin-top: 20px; width: 100%"
+        >
+          <el-table-column prop="" align="left" :label="t('Label.CidAccount')">
+            <template #default="{ row }">
+              <span>{{ row.cId || '--' }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="" align="left" :label="t('Label.Name')">
+            <template #default="{ row }">
+              <span v-if="row.firstName">{{ row.firstName + ' ' }}</span>
+              <span v-if="row.middle">{{ row.middle + ' ' }}</span>
+              <span v-if="row.lastName">{{ row.lastName }}</span>
+              <span v-if="!row.firstName && !row.lastName && !row.middle">
+                {{ '--' }}
+              </span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="" align="left" :label="t('Label.Email')">
+            <template #default="{ row }">
+              {{ row.email || '--' }}
+            </template>
+          </el-table-column>
+          <el-table-column prop="mobile" align="left" :label="t('Ucard.KycAuth.item2')">
+            <template #default="{ row }"> {{ row.areaCode }} {{ row.mobile }} </template>
+          </el-table-column>
+          <el-table-column prop="cardNumber" align="left" :label="t('Ucard.Recharge.item2')" />
+          <el-table-column prop="currency" align="left" :label="t('Ucard.Recharge.item3')" />
+          <el-table-column
+            prop="amount"
+            width="120"
+            align="left"
+            :label="t('Ucard.Recharge.item4')"
+          />
+          <el-table-column prop="fee" width="120" align="left" :label="t('Ucard.Recharge.item5')" />
+          <el-table-column prop="receivedAmount" align="left" :label="t('Ucard.Recharge.item6')" />
+          <el-table-column
+            prop="receivedCurrency"
+            align="left"
+            :label="t('Ucard.Recharge.item7')"
+          />
+          <el-table-column prop="rechargeType" align="left" :label="$t('Ucard.Recharge.item11')">
+            <template #default="scope">
+              {{ t(rechargeTypes[scope.row.rechargeType]) }}
+            </template>
+          </el-table-column>
+          <el-table-column prop="approveStatus" align="left" :label="t('R-Review-Status')">
+            <template #default="{ row }">
+              <span v-if="row.approveStatus == '2'" class="state crm_state_blue">
+                {{ t('Ucard.UserOrder.t12') }}
+              </span>
+              <span v-else-if="row.approveStatus == '3'" class="state crm_state_gray">
+                {{ t('Ucard.UserOrder.t13') }}
+              </span>
+              <span v-else class="state crm_state_yellow">
+                {{ t('Ucard.UserOrder.t11') }}
+              </span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="status" align="left" :label="t('Ucard.Recharge.item10')">
+            <template #default="{ row }">
+              <span v-if="row.status === 'success'" class="state crm_state_blue">
+                {{ t('Ucard.Recharge.t1') }}
+              </span>
+              <span v-else-if="row.status === 'fail'" class="state crm_state_gray">
+                {{ t('Ucard.Recharge.t2') }}
+              </span>
+              <span v-else-if="row.status === 'wait_process'" class="state crm_state_yellow">
+                {{ t('Ucard.VirtualCard.t11') }}
+              </span>
+              <span v-else class="state crm_state_orange">
+                {{ t('Ucard.Recharge.t3') }}
+              </span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="transactionTime" align="left" :label="t('Ucard.Recharge.item9')" />
+        </el-table>
+      </div>
+    </div>
+    <div v-if="pagerInfo.rowTotal" class="crm_pagination">
+      <div class="crm_page_total">
+        <span>{{ t('Page.total.item1') }}</span>
+        <span>{{ pagerInfo.rowTotal }}</span>
+        <span>{{ t('Page.total.item2') }}</span>
+      </div>
+      <el-pagination
+        :current-page="pagerInfo.current"
+        class="page"
+        background
+        layout="sizes, prev, pager, next"
+        :page-sizes="[10, 20, 50, 100]"
+        :page-size="pagerInfo.row"
+        :total="pagerInfo.rowTotal"
+        @current-change="handleCurrentChange"
+        @size-change="handleSizeChange"
+      >
+      </el-pagination>
+    </div>
+  </div>
+</template>
+
+<script setup>
+  import { ref, reactive, computed, onMounted, watch, inject } from 'vue'
+  import { useI18n } from 'vue-i18n'
+  import { useRouter } from 'vue-router'
+  import { ElMessage } from 'element-plus'
+  import Service from '@/service/ucard'
+  import Config from '@/config'
+  import Service2 from '@/service/apply'
+  import { exportExcel } from '@/utils/export'
+  import { approveStatusOptions } from '@/views/card/components/CardDetailRecharge/const'
+  import { statusOptions } from '@/views/card/CardUserOrder/const'
+  import { rechargeTypes } from '@/enum/card/recharge'
+  import { NumberFormatAll } from '@/utils/filter'
+  import { Search } from '@element-plus/icons-vue'
+
+  const { t } = useI18n()
+  const { Code } = Config
+  const router = useRouter()
+  const Session = inject('session')
+  const pigeon = inject('pigeon')
+
+  // props
+  const props = defineProps({
+    cardNumber: {
+      type: String,
+      default: '',
+    },
+  })
+
+  // 响应式数据
+  const pictLoading = ref(false)
+  const mock_tableData = ref([])
+  const allData = ref({ fee: 0, amount: 0 })
+  const reasons = ref({})
+  const formRef = ref(null)
+
+  const search = reactive({
+    tag: 1,
+    cardNumber: '',
+    mobile: '',
+    cId: '',
+    email: '',
+    currency: '',
+    approveStatus: '',
+    status: '',
+  })
+
+  const pagerInfo = reactive({
+    row: 10,
+    current: 1,
+    pageTotal: 0,
+    rowTotal: 0,
+  })
+
+  // 计算属性
+  const display = computed(() => {
+    return JSON.parse(Session.Get('display', true))
+  })
+
+  const user = computed(() => {
+    return JSON.parse(Session.Get('user', true))
+  })
+
+  // 导出
+  const exportAgents = async () => {
+    try {
+      await exportExcel(
+        pigeon,
+        '/wasabi/card/recharge/list/export',
+        { ...search, cardNumber: props.cardNumber },
+        'CWG_Card_Recharges'
+      )
+    } catch (error) {
+      ElMessage.error(t('Msg.ExportFail'))
+    }
+  }
+
+  const getSummaries1 = () => {
+    return [
+      t('Label.Total'),
+      '',
+      '',
+      '',
+      '',
+      '',
+      allData.value.amount ? NumberFormatAll(allData.value.amount) : 0,
+      allData.value.fee ? NumberFormatAll(allData.value.fee) : 0,
+      '',
+      '',
+      '',
+      '',
+    ]
+  }
+
+  const toSearch = () => {
+    pagerInfo.current = 1
+    searchFunc()
+  }
+
+  // 列表
+  const searchFunc = async () => {
+    pictLoading.value = true
+    if (!display.value['R-Recharge-Search']?.show) {
+      ElMessage.warning(t('Msg.NotDisplay'))
+      pictLoading.value = false
+      return
+    }
+
+    const res = await Service.rechargeList({
+      ...search,
+      cardNumber: props.cardNumber,
+      page: {
+        current: pagerInfo.current,
+        row: pagerInfo.row,
+      },
+    })
+
+    if (res.code == Code.StatusOK) {
+      mock_tableData.value = res.data
+      if (res.data?.length) {
+        allData.value = res.sum || { fee: 0, amount: 0 }
+      }
+
+      if (res.page != null) {
+        pagerInfo.rowTotal = res.page.rowTotal
+        pagerInfo.pageTotal = res.page.pageTotal
+      } else {
+        pagerInfo.rowTotal = 0
+      }
+
+      ElMessage.success(t('Msg.SearchSuccess'))
+    } else {
+      ElMessage.error(res.msg)
+    }
+    pictLoading.value = false
+  }
+
+  const handleSizeChange = (val) => {
+    pagerInfo.row = val
+    searchFunc()
+  }
+
+  const handleCurrentChange = (val) => {
+    pagerInfo.current = val
+    searchFunc()
+  }
+
+  // 生命周期
+  onMounted(() => {
+    searchFunc()
+  })
+
+  // 监听器
+  watch(
+    () => search.tag,
+    () => {
+      search.mobile = ''
+      search.email = ''
+      search.cId = ''
+      search.cardNumber = ''
+      search.currency = ''
+      search.approveStatus = ''
+    }
+  )
+</script>
+
+<style scoped lang="scss">
+  #review_Email {
+    .crm_search {
+      .search_action_btn {
+        .delete {
+          background-color: #a1a1a1;
+        }
+
+        .delete.active {
+          background-color: #368fec;
+        }
+      }
+    }
+
+    .el-table .state {
+      display: inline-block;
+      min-width: 80px;
+      max-width: 150px;
+      box-sizing: border-box;
+      line-height: 1.5;
+      border-radius: 2px;
+      padding: 2px 10px;
+      color: #ffffff;
+    }
+  }
+</style>
+<style lang="scss">
+  #review_Email {
+    .dialog_header_w {
+      .crm_search_down {
+        width: 400px;
+      }
+    }
+  }
+</style>

+ 16 - 0
src/views/card/components/CardDetailTransactions/const.ts

@@ -0,0 +1,16 @@
+export const typeOptions = [
+  { label: 'Ucard.Transactions.t1', value: 'auth' },
+  { label: 'Ucard.Transactions.t2', value: 'refund' },
+  { label: 'Ucard.Transactions.t3', value: 'verification' },
+  { label: 'Ucard.Transactions.t4', value: 'Void' },
+  { label: 'R-VirtualCard-Btn11', value: 'withdraw' },
+  { label: 'Ucard.Transactions.t5', value: 'maintain_fee' },
+]
+
+export const statusOptions = [
+  { label: 'Ucard.Transactions.t18', value: 'success' },
+  { label: 'Ucard.Transactions.t25', value: 'fail' },
+  { label: 'card.Status.t5', value: 'wait_process' },
+  { label: 'card.Status.t3', value: 'processing' },
+  { label: 'Ucard.Transactions.t24', value: 'authorized' },
+]

+ 330 - 0
src/views/card/components/CardDetailTransactions/index.vue

@@ -0,0 +1,330 @@
+<template>
+  <div
+    id="review_Email"
+    v-loading="pictLoading"
+    class="view"
+    :element-loading-background="'rgba(43, 48, 67, 0.65)'"
+    :element-loading-spinner="'el-icon-loading'"
+  >
+    <div class="crm_search">
+      <el-form ref="formRef" label-position="" :model="search" label-width="">
+        <el-row>
+          <el-col :span="24" :md="24" :lg="24">
+            <el-form-item style="margin-right: 10px">
+              <el-select
+                v-model="search.type"
+                class="crm-border-radius-no"
+                clearable
+                :placeholder="t('Ucard.Transactions.s3')"
+                @change="toSearch"
+              >
+                <el-option
+                  v-for="option in typeOptions"
+                  :key="option.value"
+                  :label="t(option.label)"
+                  :value="option.value"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item>
+              <el-select
+                v-model="search.status"
+                class="crm-border-radius-no"
+                clearable
+                :placeholder="t('Ucard.Transactions.s5')"
+                @change="toSearch"
+              >
+                <el-option
+                  v-for="option in statusOptions"
+                  :key="option.value"
+                  :label="t(option.label)"
+                  :value="option.value"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item>
+              <el-button
+                class="crm-border-radius-no crm-border-left-no"
+                :icon="Search"
+                @click="toSearch"
+              ></el-button>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-form-item>
+          <el-button
+            v-if="display['R-Transactions-Export']?.show"
+            type="primary"
+            style="margin-left: 8px"
+            @click="exportAgents"
+          >
+            {{ t('Btn.Export') }}
+          </el-button>
+        </el-form-item>
+      </el-form>
+
+      <div class="card-mock-demo" style="margin: 30px 0">
+        <el-table :data="mock_tableData" stripe style="margin-top: 20px; width: 100%">
+          <el-table-column prop="" align="left" :label="t('Label.CidAccount')">
+            <template #default="{ row }">
+              <span>{{ row.cId || '--' }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="" align="left" :label="t('Label.Name')">
+            <template #default="{ row }">
+              <span v-if="row.firstName">{{ row.firstName + ' ' }}</span>
+              <span v-if="row.middle">{{ row.middle + ' ' }}</span>
+              <span v-if="row.lastName">{{ row.lastName }}</span>
+              <span v-if="!row.firstName && !row.lastName && !row.middle">
+                {{ '--' }}
+              </span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="" align="left" :label="t('Label.Email')">
+            <template #default="{ row }">
+              {{ row.email || '--' }}
+            </template>
+          </el-table-column>
+          <el-table-column prop="mobile" align="left" :label="t('Ucard.KycAuth.item2')">
+            <template #default="{ row }"> {{ row.areaCode }} {{ row.mobile }} </template>
+          </el-table-column>
+          <el-table-column prop="cardNumber" align="left" :label="t('Ucard.Transactions.item2')" />
+          <el-table-column prop="currency" align="left" :label="t('Ucard.Transactions.item3')" />
+          <el-table-column prop="amount" align="left" :label="t('Ucard.Transactions.item4')" />
+          <el-table-column prop="fee" align="left" :label="t('Ucard.Transactions.item5')" />
+          <el-table-column prop="tradeNo" align="left" :label="t('Ucard.Transactions.item8')" />
+          <el-table-column prop="type" align="left" :label="t('Ucard.Transactions.item9')">
+            <template #default="{ row }">
+              <span v-if="row.type === 'auth'">
+                {{ t('Ucard.Transactions.t1') }}
+              </span>
+              <span v-else-if="row.type === 'refund'">
+                {{ t('Ucard.Transactions.t2') }}
+              </span>
+              <span v-else-if="row.type === 'verification'">
+                {{ t('Ucard.Transactions.t3') }}
+              </span>
+              <span v-else-if="row.type === 'Void'">
+                {{ t('Ucard.Transactions.t4') }}
+              </span>
+              <span v-else-if="row.type === 'withdraw'">
+                {{ t('R-VirtualCard-Btn11') }}
+              </span>
+              <span v-else-if="row.type === 'maintain_fee'">
+                {{ t('Ucard.Transactions.t5') }}
+              </span>
+              <span v-else>{{ row.type || '--' }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="status" align="left" :label="t('Ucard.Transactions.item11')">
+            <template #default="{ row }">
+              <span
+                v-if="row.status === 'succeed' || row.status === 'success'"
+                class="state crm_state_blue"
+              >
+                {{ t('Ucard.Transactions.t18') }}
+              </span>
+              <span
+                v-else-if="row.status === 'failed' || row.status === 'fail'"
+                class="state crm_state_gray"
+              >
+                {{ t('Ucard.Transactions.t25') }}
+              </span>
+              <span v-else-if="row.status === 'processing'" class="state crm_state_orange">
+                {{ t('card.Status.t3') }}
+              </span>
+              <span v-else-if="row.status === 'wait_process'" class="state crm_state_yellow">
+                {{ t('card.Status.t5') }}
+              </span>
+              <span v-else-if="row.status === 'authorized'" class="state crm_state_green">
+                {{ t('Ucard.Transactions.t24') }}
+              </span>
+              <span v-else class="state crm_state_green">
+                {{ row.status || '--' }}
+              </span>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+    </div>
+    <div v-if="pagerInfo.rowTotal" class="crm_pagination">
+      <div class="crm_page_total">
+        <span>{{ t('Page.total.item1') }}</span>
+        <span>{{ pagerInfo.rowTotal }}</span>
+        <span>{{ t('Page.total.item2') }}</span>
+      </div>
+      <el-pagination
+        v-model:current-page="pagerInfo.current"
+        class="page"
+        background
+        layout="sizes, prev, pager, next"
+        :page-sizes="[10, 20, 50, 100]"
+        :page-size="pagerInfo.row"
+        :total="pagerInfo.rowTotal"
+        @current-change="handleCurrentChange"
+        @size-change="handleSizeChange"
+      >
+      </el-pagination>
+    </div>
+  </div>
+</template>
+
+<script setup>
+  import { ref, reactive, computed, onMounted, inject } from 'vue'
+  import { useI18n } from 'vue-i18n'
+  import { useRouter } from 'vue-router'
+  import { ElMessage } from 'element-plus'
+  import Service from '@/service/ucard'
+  import Config from '@/config/index'
+  import { exportExcel } from '@/utils/export'
+  import { Search } from '@element-plus/icons-vue'
+  import { statusOptions, typeOptions } from '@/views/card/components/CardDetailTransactions/const'
+
+  const { t } = useI18n()
+  const { Code } = Config
+  const router = useRouter()
+  const Session = inject('session')
+  const pigeon = inject('pigeon')
+
+  // props
+  const props = defineProps({
+    cardNumber: {
+      type: String,
+      default: '',
+    },
+  })
+
+  // 响应式数据
+  const pictLoading = ref(false)
+  const mock_tableData = ref([])
+  const formRef = ref(null)
+
+  const search = reactive({
+    tag: 1,
+    mobile: '',
+    cId: '',
+    email: '',
+    cardNumber: '',
+    currency: '',
+    type: '',
+    dataType: '',
+    status: '',
+  })
+
+  const pagerInfo = reactive({
+    row: 10,
+    current: 1,
+    pageTotal: 0,
+    rowTotal: 0,
+  })
+
+  // 计算属性
+  const display = computed(() => {
+    return JSON.parse(Session.Get('display', true))
+  })
+
+  // 方法
+  // 导出
+  const exportAgents = async () => {
+    try {
+      await exportExcel(
+        pigeon,
+        '/wasabi/card/transac/record/export',
+        { ...search, cardNumber: props.cardNumber },
+        'Bank_Card_Transactions'
+      )
+    } catch (error) {
+      ElMessage.error(t('Msg.ExportFail'))
+    }
+  }
+
+  const toSearch = () => {
+    pagerInfo.current = 1
+    searchFunc()
+  }
+
+  // 列表
+  const searchFunc = async () => {
+    pictLoading.value = true
+    if (!display.value['R-Transactions-Search']?.show) {
+      ElMessage.warning(t('Msg.NotDisplay'))
+      pictLoading.value = false
+      return
+    }
+
+    const res = await Service.transactionsList({
+      ...search,
+      cardNumber: props.cardNumber,
+      page: {
+        current: pagerInfo.current,
+        row: pagerInfo.row,
+      },
+    })
+
+    if (res.code == Code.StatusOK) {
+      mock_tableData.value = res.data
+      if (res.page != null) {
+        pagerInfo.rowTotal = res.page.rowTotal
+        pagerInfo.pageTotal = res.page.pageTotal
+      } else {
+        pagerInfo.rowTotal = 0
+      }
+
+      ElMessage.success(t('Msg.SearchSuccess'))
+    } else {
+      ElMessage.error(res.msg)
+    }
+    pictLoading.value = false
+  }
+
+  const handleSizeChange = (val) => {
+    pagerInfo.row = val
+    searchFunc()
+  }
+
+  const handleCurrentChange = (val) => {
+    pagerInfo.current = val
+    searchFunc()
+  }
+
+  // 生命周期
+  onMounted(() => {
+    searchFunc()
+  })
+</script>
+
+<style scoped lang="scss">
+  #review_Email {
+    .crm_search {
+      .search_action_btn {
+        .delete {
+          background-color: #a1a1a1;
+        }
+
+        .delete.active {
+          background-color: #368fec;
+        }
+      }
+    }
+
+    .el-table .state {
+      display: inline-block;
+      min-width: 80px;
+      max-width: 150px;
+      box-sizing: border-box;
+      line-height: 1.5;
+      border-radius: 2px;
+      padding: 2px 10px;
+      color: #ffffff;
+    }
+  }
+</style>
+<style lang="scss">
+  #review_Email {
+    .dialog_header_w {
+      .crm_search_down {
+        width: 400px;
+      }
+    }
+  }
+</style>

+ 306 - 0
src/views/card/components/CardDetailWithdraw/index.vue

@@ -0,0 +1,306 @@
+<template>
+  <div
+    id="review_Email"
+    v-loading="pictLoading"
+    class="view"
+    :element-loading-background="'rgba(43, 48, 67, 0.65)'"
+    :element-loading-spinner="'el-icon-loading'"
+  >
+    <div class="crm_search">
+      <el-form ref="formRef" :model="search" label-width="">
+        <el-row>
+          <el-col :span="24" :md="24" :lg="24">
+            <el-form-item style="margin-right: 10px">
+              <el-select
+                v-model="search.type"
+                class="crm-border-radius-no"
+                clearable
+                :placeholder="t('Ucard.Transactions.s3')"
+                @change="toSearch"
+              >
+                <el-option
+                  v-for="(value, key) in types"
+                  :key="key"
+                  :label="t(value)"
+                  :value="key"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item>
+              <el-select
+                v-model="search.status"
+                class="crm-border-radius-no"
+                clearable
+                :placeholder="t('Label.State')"
+                @change="toSearch"
+              >
+                <el-option
+                  v-for="(value, key) in statusOptions"
+                  :key="key"
+                  :label="t(value)"
+                  :value="key"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item>
+              <el-button
+                class="crm-border-radius-no crm-border-left-no"
+                :icon="Search"
+                @click="toSearch"
+              ></el-button>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-form-item>
+          <el-button
+            v-if="display['R-Card-WithdrawTransaction-Export']?.show"
+            type="primary"
+            style="margin-left: 8px"
+            @click="exportAgents"
+          >
+            {{ t('Btn.Export') }}
+          </el-button>
+        </el-form-item>
+      </el-form>
+
+      <div class="card-mock-demo" style="margin: 30px 0">
+        <el-table :data="mock_tableData" stripe style="margin-top: 20px; width: 100%">
+          <el-table-column prop="" align="left" :label="t('Label.CidAccount')">
+            <template #default="{ row }">
+              <span>{{ row.cId || '--' }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="" align="left" :label="t('Label.Name')">
+            <template #default="{ row }">
+              <span v-if="row.firstName">
+                {{ row.firstName + ' ' }}
+              </span>
+              <span v-if="row.middle">{{ row.middle + ' ' }}</span>
+              <span v-if="row.lastName">{{ row.lastName }}</span>
+              <span v-if="!row.firstName && !row.lastName && !row.middle">
+                {{ '--' }}
+              </span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="" align="left" :label="t('Label.Email')">
+            <template #default="{ row }">
+              {{ row.email || '--' }}
+            </template>
+          </el-table-column>
+          <el-table-column prop="mobile" align="left" :label="t('Label.Mobile')">
+            <template #default="{ row }"> {{ row.areaCode }} {{ row.mobile }} </template>
+          </el-table-column>
+          <el-table-column prop="cardNumber" align="left" :label="t('Ucard.Transactions.item2')">
+            <template #default="{ row }">
+              <span>{{ row.cardNumber || '--' }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="currency" align="left" :label="t('Ucard.Transactions.item3')" />
+          <el-table-column prop="amount" align="left" :label="t('Ucard.Transactions.item4')" />
+          <el-table-column prop="fee" align="left" :label="t('Ucard.Transactions.item5')" />
+          <el-table-column prop="type" align="left" :label="t('Ucard.Transactions.item9')">
+            <template #default="{ row }">
+              <span>{{ t(types[row.type]) || '--' }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="status" align="left" :label="t('Ucard.Transactions.item11')">
+            <template #default="{ row }">
+              <span :class="statusColor(row.status)">
+                {{ t(statusOptions[row.status]) || '--' }}
+              </span>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+    </div>
+    <PagePagination
+      :pager-info="pagerInfo"
+      @current-change="handleCurrentChange"
+      @size-change="handleSizeChange"
+    />
+  </div>
+</template>
+
+<script setup>
+  import { ref, reactive, computed, onMounted, watch, inject } from 'vue'
+  import { useI18n } from 'vue-i18n'
+  import { useRouter } from 'vue-router'
+  import { ElMessage } from 'element-plus'
+  import Service from '@/service/ucard'
+  import Config from '@/config/index'
+  import { exportExcel } from '@/utils/export'
+  import PagePagination from '@/components/pagePagination/index.vue'
+  import { statusObj } from '@/enum/enum'
+  import { statusColor } from '@/utils/statusColor'
+  import { Search } from '@element-plus/icons-vue'
+
+  const { t } = useI18n()
+  const { Code } = Config
+  const router = useRouter()
+  const Session = inject('session')
+  const pigeon = inject('pigeon')
+
+  // props
+  const props = defineProps({
+    cardNumber: {
+      type: String,
+      default: '',
+    },
+  })
+
+  // 响应式数据
+  const pictLoading = ref(false)
+  const mock_tableData = ref([])
+  const formRef = ref(null)
+
+  const search = reactive({
+    tag: 1,
+    mobile: '',
+    cId: '',
+    email: '',
+    currency: '',
+    type: '',
+    status: '',
+  })
+
+  const pagerInfo = reactive({
+    row: 10,
+    current: 1,
+    pageTotal: 0,
+    rowTotal: 0,
+  })
+
+  const types = {
+    1: 'Ucard.WithDrawTransaction.s1',
+    2: 'Ucard.WithDrawTransaction.s2',
+    3: 'Ucard.WithDrawTransaction.s3',
+  }
+
+  // 计算属性
+  const display = computed(() => {
+    return JSON.parse(Session.Get('display', true))
+  })
+
+  const statusOptions = computed(() => statusObj)
+
+  // 方法
+  // 导出
+  const exportAgents = async () => {
+    try {
+      await exportExcel(
+        pigeon,
+        '/wasabi/card/withdraw/list/export',
+        { ...search },
+        'Card_Withdraw_Transactions'
+      )
+    } catch (error) {
+      ElMessage.error(t('Msg.ExportFail'))
+    }
+  }
+
+  // 银行卡详情
+  const cardOpen = (cardNumber) => {
+    router.push({ name: 'R-CardDetail', params: { cardNumber: cardNumber } })
+  }
+
+  const toSearch = () => {
+    pagerInfo.current = 1
+    searchFunc()
+  }
+
+  // 列表
+  const searchFunc = async () => {
+    pictLoading.value = true
+    if (!display.value['R-Card-WithdrawTransaction-Page']?.show) {
+      ElMessage.warning(t('Msg.NotDisplay'))
+      pictLoading.value = false
+      return
+    }
+
+    const res = await Service.getCardWithdrawTransactionPage({
+      ...search,
+      cardNumber: props.cardNumber,
+      page: {
+        current: pagerInfo.current,
+        row: pagerInfo.row,
+      },
+    })
+
+    if (res.code == Code.StatusOK) {
+      mock_tableData.value = res.data
+      if (res.page != null) {
+        pagerInfo.rowTotal = res.page.rowTotal
+        pagerInfo.pageTotal = res.page.pageTotal
+      } else {
+        pagerInfo.rowTotal = 0
+      }
+
+      ElMessage.success(t('Msg.SearchSuccess'))
+    } else {
+      ElMessage.error(res.msg)
+    }
+    pictLoading.value = false
+  }
+
+  const handleSizeChange = (val) => {
+    pagerInfo.row = val
+    searchFunc()
+  }
+
+  const handleCurrentChange = (val) => {
+    pagerInfo.current = val
+    searchFunc()
+  }
+
+  // 生命周期
+  onMounted(() => {
+    searchFunc()
+  })
+
+  // 监听器
+  watch(
+    () => search.tag,
+    () => {
+      search.mobile = ''
+      search.email = ''
+      search.cId = ''
+      search.type = ''
+      search.status = ''
+    }
+  )
+</script>
+<style scoped lang="scss">
+  #review_Email {
+    .crm_search {
+      .search_action_btn {
+        .delete {
+          background-color: #a1a1a1;
+        }
+
+        .delete.active {
+          background-color: #368fec;
+        }
+      }
+    }
+
+    .el-table .state {
+      display: inline-block;
+      min-width: 80px;
+      max-width: 150px;
+      box-sizing: border-box;
+      line-height: 1.5;
+      border-radius: 2px;
+      padding: 2px 10px;
+      color: #ffffff;
+    }
+  }
+</style>
+<style lang="scss">
+  #review_Email {
+    .dialog_header_w {
+      .crm_search_down {
+        width: 400px;
+      }
+    }
+  }
+</style>