源文件

This commit is contained in:
gyq
2024-05-23 14:39:33 +08:00
commit a1128dd791
2997 changed files with 500069 additions and 0 deletions

View File

@@ -0,0 +1,126 @@
<template>
<a-drawer
v-model:visible="vdata.visible"
:title=" true ? '流水详情' : '' "
:body-style="{ paddingBottom: '80px' }"
width="40%"
@close="onClose"
>
<a-row justify="space-between" type="flex">
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="角色名称">
{{ vdata.detailData['infoName'] }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="流水号">
{{ vdata.detailData['hid'] }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="账户类型">
{{ vdata.detailData['opAccountType'] === 1 ? '钱包账户' : '在途账户' }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="关联订单类型">
<template v-if="vdata.detailData['relaBizOrderType'] === 1">支付订单</template>
<template v-if="vdata.detailData['relaBizOrderType'] === 2">退款订单</template>
<template v-if="vdata.detailData['relaBizOrderType'] === 3">提现订单</template>
<template v-if="vdata.detailData['relaBizOrderType'] === 4">转账订单</template>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item v-if="vdata.detailData['relaBizOrderType'] === 1" label="支付订单号">
{{ vdata.detailData['relaBizOrderId'] }}
</a-descriptions-item>
<a-descriptions-item v-if="vdata.detailData['relaBizOrderType'] === 2" label="退款订单号">
{{ vdata.detailData['relaBizOrderId'] }}
</a-descriptions-item>
<a-descriptions-item v-if="vdata.detailData['relaBizOrderType'] === 3" label="提现订单号">
{{ vdata.detailData['relaBizOrderId'] }}
</a-descriptions-item>
<a-descriptions-item v-if="vdata.detailData['relaBizOrderType'] === 4" label="转账订单">
{{ vdata.detailData['relaBizOrderId'] }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="时间">
{{ vdata.detailData['updatedAt'] }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="变动前账户余额">
{{ vdata.detailData['opBeforeAmount'] / 100 }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="变动金额">
{{ vdata.detailData['opAmount'] / 100 }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="变动后账户余额">
{{ vdata.detailData['opAfterAmount'] / 100 }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="24">
<a-descriptions>
<a-descriptions-item label="备注">
{{ vdata.detailData['remark'] }}
</a-descriptions-item>
</a-descriptions>
</a-col>
</a-row>
</a-drawer>
</template>
<script lang="ts" setup>
import { API_URL_WALLET_HISTORY_LIST, req } from '@/api/manage'
import {defineProps,reactive} from 'vue'
const props = defineProps({
callbackFunc: { type: Function,default:null }
})
const vdata = reactive({
btnLoading: false,
detailData: {}, // 数据对象
recordId: null, // 更新对象ID
visible: false, // 是否显示弹层/抽屉
isvList: [], // 服务商下拉列表
isvName: '' // 服务商名称
})
function show (recordId) { // 弹层打开事件
vdata.recordId = recordId
req.getById(API_URL_WALLET_HISTORY_LIST, recordId).then(res => {
vdata.detailData = res
})
vdata.visible = true
}
function onClose () {
vdata.visible = false
}
defineExpose({
show //抛出show函数给父组件
})
</script>

View File

@@ -0,0 +1,17 @@
<template>
<page-header-wrapper>
<HistoryPanel ref="historyPanelRef" :default-query-condition="vdata.defSearchData" />
</page-header-wrapper>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
import HistoryPanel from './HistoryPanel.vue'
const historyPanelRef = ref()
const vdata = reactive({
defSearchData: {infoType: 'PLATFORM', opAccountType: 1}
})
</script>

View File

@@ -0,0 +1,122 @@
<!--
历史记录的通用组件 可抽屉可 页面
@author terrfly
@site https://www.jeequan.com
@date 2022/03/23 20:23
-->
<template>
<div>
<a-card class="table-card">
<JeepaySearchForm v-if="props.showSearch" :searchFunc="searchFunc" :resetFunc="resetFunc">
<a-form-item label="" class="table-search-item">
<JeepayDateRangePicker
ref="dateRangePicker"
v-model:value="vdata.searchData['queryDateRange']"
customDateRangeType="dateTime"
/>
</a-form-item>
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData['opAccountType']" placeholder="选择账户类型">
<a-select-option value="">全部</a-select-option>
<a-select-option :value="1">钱包账户</a-select-option>
<a-select-option :value="2">在途账户</a-select-option>
</a-select>
</a-form-item>
<jeepay-text-up v-model:value="vdata.searchData['hid']" :placeholder="'流水号'" />
</JeepaySearchForm>
<!-- 列表渲染 -->
<JeepayTable
ref="infoTable"
:init-data="false"
:req-table-data-func="reqTableDataFunc"
:table-columns="tableColumns"
:search-data="vdata.searchData"
row-key="hid"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'infoId'">
<span v-if="record.infoType == 'AGENT'">服务商 {{ record.infoName }}({{ record.infoId }})</span>
<span v-if="record.infoId == 'PLATFORM_INACCOUNT'">运营平台入账账户</span>
<span v-if="record.infoId == 'PLATFORM_PROFIT'">运营平台利润账户</span>
</template>
<template v-if="column.key === 'opBeforeAmount'">
<span>{{ record.opBeforeAmount / 100 }}</span>
</template>
<template v-if="column.key === 'opAmount'">
<span>{{ record.opAmount / 100 }}</span>
</template>
<template v-if="column.key === 'opAfterAmount'">
<span>{{ record.opAfterAmount / 100 }}</span>
</template>
<template v-if="column.key === 'operation'">
<!-- 操作列插槽 -->
<JeepayTableColumns>
<a-button type="link" @click="detailFunc(record.hid)">详情</a-button>
</JeepayTableColumns>
</template>
</template>
</JeepayTable>
</a-card>
<!-- 详情页面组件 -->
<InfoDetail ref="infoDetail" :callback-func="searchFunc" />
</div>
</template>
<script setup lang="ts">
import { API_URL_WALLET_HISTORY_LIST, req, reqLoad } from '@/api/manage'
import InfoDetail from './Detail.vue'
import { ref, reactive, defineProps, getCurrentInstance, onMounted } from 'vue'
const { $infoBox,$access } = getCurrentInstance()!.appContext.config.globalProperties
// 定义父组件的传值
const props = defineProps({
defaultQueryCondition: { type: Object, default: () => {} }, // 查询集合的条件
showSearch: { type: Boolean, default: true } //是否显示搜索面板
})
const infoDetail = ref()
const infoTable = ref()
let tableColumns = reactive([
{ key: 'hid', title: '流水号', fixed: 'left', dataIndex: 'hid', },
{ key: 'opBeforeAmount', title: '变动前账户余额', dataIndex: 'opBeforeAmount', },
{ key: 'opAmount', title: '变动金额', dataIndex: 'opAmount', },
{ key: 'opAfterAmount', title: '变动后账户余额', dataIndex: 'opAfterAmount' , },
{ key: 'relaBizOrderId', title: '关联订单号', dataIndex: 'relaBizOrderId' , },
{ key: 'createdAt', title: '时间', dataIndex: 'createdAt' , width: 200,},
{ key: 'operation', title: '操作', fixed: 'right', align: 'center'}
])
const dateRangePicker = ref()
const vdata : any = reactive({
searchData : { queryDateRange: 'today' }
})
onMounted(() => {
Object.assign(vdata.searchData, props.defaultQueryCondition)
infoTable.value.refTable(true)
})
function resetFunc() {
dateRangePicker.value.returnSelectModel()
vdata.searchData = { queryDateRange: 'today', opAccountType: 1 }
}
// 请求table接口数据
function reqTableDataFunc(params: any) {
return req.list(API_URL_WALLET_HISTORY_LIST, params)
}
function searchFunc () { // 点击【查询】按钮点击事件
infoTable.value.refTable(true)
}
function detailFunc (recordId: any) { // 钱包流水详情页
infoDetail.value.show(recordId)
}
</script>

View File

@@ -0,0 +1,39 @@
<!-- 通用抽屉 -->
<template>
<a-drawer
v-model:visible="vdata.visible"
title="流水记录"
:closable="true"
:body-style="{ paddingBottom: '80px' }"
:drawer-style="{ backgroundColor: '#f0f2f5' }"
width="85%"
@close="vdata.visible = false"
>
<div>
<HistoryPanel v-if="vdata.visible" ref="historyPanelRef" :showSearch="vdata.showSearch" :defaultQueryCondition="vdata.defaultQueryCondition" />
</div>
</a-drawer>
</template>
<script lang="ts" setup >
import { ref, reactive, defineExpose } from 'vue'
import HistoryPanel from './HistoryPanel.vue'
const historyPanelRef = ref()
const vdata : any = reactive({
visible: false, // 一级抽屉开关
defaultQueryCondition: { type: Object, default: () => {} }, // 查询集合的条件
showSearch: { type: Boolean, default: true } //是否显示搜索面板
})
// 弹层打开事件
function show (queryObject, isShow) {
vdata.defaultQueryCondition = queryObject
vdata.showSearch = isShow
vdata.visible = true
}
defineExpose({ show })
</script>

View File

@@ -0,0 +1,31 @@
<template>
<div style="background-color: white; min-height: 100%">
<!-- <JeepayPayConfigPanel-->
<!-- ref="jeepayPaywayRatePanelRef"-->
<!-- configMode="agentSelf"-->
<!-- infoId="CURRENTAGENT"-->
<!-- isvNo="CURRENTAGENT"-->
<!-- />-->
<!-- <JeepayPaymentConfigDrawer-->
<!-- ref="jeepayPaywayRatePanelRef"-->
<!-- configMode="agentSelf"-->
<!-- infoId="CURRENTAGENT"-->
<!-- />-->
<JeepayPaymentWayConfig type="0" infoId="CURRENTAGENT" configMode="agentSelf" />
</div>
</template>
<script lang="ts" setup>
import {reactive} from 'vue'
</script>
<style lang="less" scoped>
::v-deep .drawer.cur {
position: relative;
}
::v-deep .drawer-btn-center-payfee{
position: relative !important;
}
</style>

View File

@@ -0,0 +1,689 @@
<template>
<div class="content">
<a-card style="padding: 30px" class="agent-info">
<div class="title">
<b>服务商信息</b>
</div>
<div class="item">
<span class="label">服务商名称</span>
<span class="desc">{{ vdata.agentInfo.agentName }}</span>
</div>
<div class="item">
<span class="label">服务商邀请码</span>
<span class="desc">{{ inviteCode ? inviteCode : "" }} <copy-outlined style="color: #2691ff;" @click="getCopy()"/></span>
</div>
<div class="item">
<span class="label">拓展商户</span>
<span class="desc">
<QrcodeOutlined style="color: #2691ff;" @click="getVisibleAddQrc(0)" />
<copy-outlined style="color: #2691ff;padding-left: 5px" @click="getCopyCode(0)"/>
</span>
</div>
<div class="item">
<span class="label">拓展代理</span>
<span class="desc">
<QrcodeOutlined style="color: #2691ff;" @click="getVisibleAddQrc(1)" />
<copy-outlined style="color: #2691ff;padding-left: 5px" @click="getCopyCode(1)"/>
</span>
</div>
<div class="item">
<span class="label">服务商简称</span>
<span class="desc">{{ vdata.agentInfo.agentShortName }}</span>
</div>
<div class="item">
<span class="label">登录名</span>
<span class="desc">{{ vdata.agentInfo.loginUsername }}</span>
</div>
<div class="item">
<span class="label">服务商号</span>
<span class="desc">{{ vdata.agentInfo.agentNo }}</span>
</div>
<div class="item">
<span class="label">渠道商号</span>
<span class="desc">{{ vdata.agentInfo.isvNo }}</span>
</div>
<div class="item">
<span class="label">上级服务商号</span>
<span class="desc">{{ vdata.agentInfo.pid }}</span>
</div>
<div class="item">
<span class="label">是否允许发展下级服务商</span>
<span class="desc">{{
vdata.agentInfo.addAgentFlag == 1 ? '是' : '否'
}}</span>
</div>
<div class="item">
<span class="label">注册时间</span>
<span class="desc">{{ vdata.agentInfo.createdAt }}</span>
</div>
</a-card>
<a-card class="statistics-box order-statistics order">
<div class="box-flex">
<div class="title">
<b style="flex-grow: 1">订单/商户统计</b>
<a-select
v-model:value="vdata.orderStatic"
placeholder="订单统计"
style="width: 215px; margin-right: 10px"
@change="staticChange"
>
<a-select-option value="1"> 仅统计自己 </a-select-option>
<a-select-option value="2"> 全部服务商 </a-select-option>
<a-select-option
v-for="(item, index) in (vdata.agentList as any)"
:key="index"
:value="item.agentNo"
>
{{ item.agentName }}
</a-select-option>
</a-select>
<JeepayDateRangePicker
v-model:value="orderDate"
class="date"
customDateRangeType="date"
:allTimeIsShow="false"
@update:value="getStatisticsData('order')"
/>
</div>
<div class="other-item" style="margin: 30px 0">
<div class="item">
<p class="item-title">成交金额</p>
<p class="item-text" style="color: #1890ff !important">
{{ (vdata.payAmount / 100).toFixed(2) }}
</p>
</div>
<div class="item">
<p class="item-title">交易笔数</p>
<p class="item-text">{{ vdata.payCount }}</p>
</div>
<div class="item">
<p class="item-title">退款金额</p>
<p class="item-text">{{ (vdata.refundAmount / 100).toFixed(2) }}</p>
</div>
<div class="item">
<p class="item-title">退款笔数</p>
<p class="item-text">{{ vdata.refundCount }}</p>
</div>
<!-- <div class="item">
<p class="item-title">利润</p>
<p class="item-text">{{ vdata.profit }}</p>
</div> -->
</div>
<div class="other-item">
<div class="item">
<p class="item-title">商户总数</p>
<p class="item-text" style="color: #1890ff !important">
{{ vdata.mchAllCount }}
</p>
</div>
<div class="item">
<p class="item-title">新增商户数</p>
<p class="item-text">{{ vdata.mchTodayAddCount }}</p>
</div>
<div class="item">
<p class="item-title">入网商户数</p>
<p class="item-text">{{ vdata.mchOnNetCount }}</p>
</div>
<div class="item">
<p class="item-title">新增入网商户</p>
<p class="item-text">{{ vdata.mchOnNetNewCount }}</p>
</div>
<!-- <div class="item">
<p class="item-title">入网失败商户</p>
<p class="item-text">{{ vdata.mchOnNetFailCount }}</p>
</div> -->
</div>
</div>
</a-card>
<!-- <a-card class="statistics-box order-statistics">
<div class="title">
<b>商户统计</b>
<a-select v-model:value="vdata.mchStatic" placeholder="商户统计" style="width: 200px" @change="staticChange('mch')">
<a-select-option value="1">
仅统计自己
</a-select-option>
<a-select-option value="2">
全部
</a-select-option>
<a-select-option v-for="(item, index) in (vdata.agentList as any)" :key="index" :value="item.agentNo">
{{ item.agentName }}
</a-select-option>
</a-select>
<JeepayDateRangePicker v-model:value="mchDate" class="date" customDateRangeType="date" :allTimeIsShow="false" @update:value="getStatisticsData('merchant')" />
</div>
</a-card> -->
<a-card
v-if="vdata.agentInfo.addAgentFlag == 1"
class="statistics-box agent"
>
<div class="agent-statistics">
<div class="title">
<b>服务商统计</b>
<!-- <JeepayDateRangePicker
v-model:value="agentDate"
class="date"
customDateRangeType="date"
:allTimeIsShow="false"
@update:value="getStatisticsData('agent')"
/> -->
</div>
<div class="main-item">
<p class="item-title">服务商总数</p>
<p class="item-text">{{ vdata.agentAllCount }}</p>
</div>
<div class="other-item">
<div class="item">
<p class="item-title">新增服务商数</p>
<p class="item-text">{{ vdata.agentNewCount }}</p>
</div>
<div class="item">
<p class="item-title">活动服务商数</p>
<p class="item-text">{{ vdata.agentOnCount }}</p>
</div>
</div>
</div>
</a-card>
<a-card class="statistics-box hardware-statistics hardware">
<div class="title">
<b>硬件统计</b>
</div>
<div class="other-item hardware-item">
<div class="item">
<p class="item-title">码牌总数</p>
<p class="item-text item-text-top">{{ vdata.qrCodeCardAllCount }}</p>
</div>
<div class="item">
<p class="item-title">云喇叭总数</p>
<p class="item-text item-text-top">{{ vdata.speakerAllCount }}</p>
</div>
<div class="item">
<p class="item-title">云打印总数</p>
<p class="item-text item-text-top">{{ vdata.printerAllCount }}</p>
</div>
<div class="item">
<p class="item-title">POS机总数</p>
<p class="item-text item-text-top">{{ vdata.posAllCount }}</p>
</div>
</div>
<div class="other-item hardware-item">
<div class="item">
<p class="item-title">空码数量</p>
<p class="item-text">{{ vdata.qrCodeCardUnCount }}</p>
</div>
<div class="item">
<p class="item-title">未绑定云喇叭数</p>
<p class="item-text">{{ vdata.speakerUnCount }}</p>
</div>
<div class="item">
<p class="item-title">未绑定云打印数</p>
<p class="item-text">{{ vdata.printerUnCount }}</p>
</div>
<div class="item">
<p class="item-title">未绑定POS机数</p>
<p class="item-text">{{ vdata.posUnCount }}</p>
</div>
</div>
</a-card>
</div>
<a-modal
v-model:visible="vdata.visibleAddQrc"
:footer="null"
@cancel="handleCancel"
style="top: 30% !important;"
wrap-class-name="full-modal"
width="330px"
:title="vdata.title"
:maskClosable="false"
>
<div class="modal-body" style="height: 280px !important;">
<div style="text-align: center">
<img
v-if="vdata.inviteUrl"
:src='"https://api.pwmqr.com/qrcode/create/?url="+vdata.inviteUrl'
alt=""
style="width: 200px; height: 200px"
>
<div class="zfb-wx" style="margin: 10px 0">
<!-- <img src="/src/assets/svg/alipay.svg" alt="">-->
<!-- <img-->
<!-- src="/src/assets/svg/wechatpay.svg"-->
<!-- alt=""-->
<!-- style="margin: 0 5px"-->
<!-- >-->
<span style="color: grey">支持浏览器微信支付宝扫码</span>
</div>
<a-button
type="primary"
size="large"
block
@click="vdata.visibleAddQrc = false"
>
取消扫码
</a-button>
</div>
</div>
</a-modal>
</template>
<script lang="ts" setup>
import {ref, reactive, onMounted, getCurrentInstance} from 'vue'
import {
$getStatistics,
API_URL_AGENT_LIST,
req,
$getMainUserInfo, $getUserInfo
} from '@/api/manage'
const { $infoBox, $access } = getCurrentInstance()!.appContext.config.globalProperties;
const vdata: any = reactive({
title:"",
inviteUrl:"" as any,
visibleAddQrc:false,
payAmount: 0, //成交金额
payCount: 0, //交易笔数
refundAmount: 0, //退款金额
refundCount: 0, //退款笔数
profit: 0, //利润
mchAllCount: 0, // 商户总数
mchTodayAddCount: 0, // 新增商户数
mchOnNetCount: 0, //入网商户数
mchOnNetNewCount: 0, // 新增商户入网数量
mchOnNetFailCount: 0, //入网失败商户数
agentAllCount: 0, // 服务商总数
agentNewCount: 0, //新增服务商数
agentOnCount: 0, //活动服务商数
orderTodayCount: 0, // 今日订单数量
orderTodaySum: 0, // 今日订单金额
qrCodeCardAllCount: 0, // 码牌总数
speakerAllCount: 0, //云喇叭总数
printerAllCount: 0, //云打印总数
posAllCount: 0, //POS机总数
qrCodeCardUnCount: 0, //空码数量
speakerUnCount: 0, //未绑定云喇叭数
printerUnCount: 0, //未绑定云打印数
posUnCount: 0, //未绑定POS机数
qrCodeCardBindCount: 0, // 已绑定码牌
qrCodeCardUnBindCount: 0, // 剩余未绑定码牌
// 用于下拉框筛选
orderStatic: '2', // 订单统计(商户统计)
countType: '2', // 自己全部或者子代理
agentNo: '', // 服务商编号
agentList: [], // 服务商列表
// 服务商信息
agentInfo: {} as any
})
let statisticsDate = ref('today') // 公共日期
let agentDate = ref('today') // 服务商日期
let mchDate = ref('today') // 商户日期
let orderDate = ref('today') // 订单日期
const inviteCode = ref(); //服务商邀请码
const inviteCodeUrl = ref(); //服务商二维码
const agtInviteCodeUrl = ref(); //服务商二维码
// 获取服务商列表
const getAgentList = () => {
req.list(API_URL_AGENT_LIST, vdata.searchData).then((bizData) => {
vdata.agentList = bizData.records
})
}
onMounted(() => {
getAgentList()
$getUserInfo().then((res) => {
inviteCode.value = res.inviteCode;
inviteCodeUrl.value = res.inviteCodeUrl;
agtInviteCodeUrl.value = res.agtInviteCodeUrl;
});
})
// 获取服务商信息
$getMainUserInfo().then((res) => {
vdata.agentInfo = res
})
// 订单统计切换服务商
const staticChange = (e) => {
// countType 统计数据类型 1-服务商本身数据 2-全部 3-服务商号搜索 agentNo 3类型时必传
let no = e ? e : ''
if (['1', '2'].includes(no)) {
vdata.countType = no
vdata.agentNo = ''
} else {
vdata.countType = 3
vdata.agentNo = no
}
$getStatistics(statisticsDate.value, vdata.countType, vdata.agentNo).then(
(bizData) => {
const { payAmount, payCount, refundAmount, refundCount } =
bizData.orderCount
vdata.payAmount = payAmount
vdata.payCount = payCount
vdata.refundAmount = refundAmount
vdata.refundCount = refundCount
const {
mchAllCount,
mchTodayAddCount,
mchOnNetCount,
mchOnNetNewCount,
mchOnNetFailCount
} = bizData.mchCount
vdata.mchAllCount = mchAllCount
vdata.mchTodayAddCount = mchTodayAddCount
vdata.mchOnNetCount = mchOnNetCount
vdata.mchOnNetNewCount = mchOnNetNewCount
vdata.mchOnNetFailCount = mchOnNetFailCount
const {
agentAllCount,
agentNewCount,
agentOnCount
} = bizData.agentCount
vdata.agentAllCount = agentAllCount
vdata.agentNewCount = agentNewCount
vdata.agentOnCount = agentOnCount
}
)
}
function handleCancel(e) {
vdata.visibleAddQrc = false;
}
// 获取统计数据
function getStatisticsData(sign) {
if (sign === 'order') {
// statisticsDate.value = mchDate.value
statisticsDate.value = orderDate.value
}
$getStatistics(statisticsDate.value, vdata.countType, vdata.agentNo)
.then((res) => {
if (sign === 'order') {
vdata.agentAllCount = res.agentCount.agentAllCount
vdata.agentNewCount = res.agentCount.agentNewCount
vdata.agentOnCount = res.agentCount.agentOnCount
vdata.mchAllCount = res.mchCount.mchAllCount
vdata.mchOnNetNewCount = res.mchCount.mchOnNetNewCount
vdata.mchTodayAddCount = res.mchCount.mchTodayAddCount
vdata.mchOnNetCount = res.mchCount.mchOnNetCount
vdata.mchOnNetFailCount = res.mchCount.mchOnNetFailCount
vdata.payAmount = res.orderCount.payAmount
vdata.payCount = res.orderCount.payCount
vdata.refundAmount = res.orderCount.refundAmount
vdata.refundCount = res.orderCount.refundCount
vdata.profit = res.payProfit
} else {
// 服务商
vdata.agentAllCount = res.agentCount.agentAllCount
vdata.agentNewCount = res.agentCount.agentNewCount
vdata.agentOnCount = res.agentCount.agentOnCount
// 商户
vdata.mchAllCount = res.mchCount.mchAllCount
vdata.mchOnNetCount = res.mchCount.mchOnNetCount
vdata.mchTodayAddCount = res.mchCount.mchTodayAddCount
vdata.mchOnNetNewCount = res.mchCount.mchOnNetNewCount
vdata.mchOnNetFailCount = res.mchCount.mchOnNetFailCount
// 订单
vdata.payAmount = res.orderCount.payAmount
vdata.payCount = res.orderCount.payCount
vdata.refundAmount = res.orderCount.refundAmount
vdata.refundCount = res.orderCount.refundCount
vdata.profit = res.payProfit
// 码牌
vdata.qrCodeCardAllCount = res.qrCodeCardAllCount
vdata.speakerAllCount = res.speakerAllCount
vdata.printerAllCount = res.printerAllCount
vdata.posAllCount = res.posAllCount
vdata.qrCodeCardUnCount = res.qrCodeCardUnBindCount
vdata.speakerUnCount = res.speakerUnCount
vdata.printerUnCount = res.printerUnCount
vdata.posUnCount = res.posUnCount
}
})
.catch((err) => {
console.log(err)
})
}
function getCopyCode(type = 0){
const textarea = document.createElement('textarea');
let text = "拓展商户链接复制成功";
if(type == 1){
text = '拓展服务商链接复制成功'
textarea.value = agtInviteCodeUrl.value;
}else{
textarea.value = inviteCodeUrl.value;
}
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
$infoBox.message.success(text)
}
async function getCopy(){
console.log(inviteCode.value)
// var textarea= document.createElement("textarea"); // 创建textarea对象
// document.body.appendChild(inviteCode.value); // 添加临时实例
// textarea.select(); // 选择实例内容
// document.execCommand("Copy"); // 执行复制
// document.body.removeChild(textarea); // 删除临时实例
const textarea = document.createElement('textarea');
textarea.value = inviteCode.value;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
$infoBox.message.success('复制成功')
}
function getVisibleAddQrc(type = 0){
if(type == 1){
vdata.title="拓展代理"
vdata.inviteUrl = agtInviteCodeUrl.value
}else{
vdata.title="拓展商户"
vdata.inviteUrl = inviteCodeUrl.value
}
console.log(vdata.inviteUrl,'vdata.inviteUrl')
vdata.visibleAddQrc = true
}
Promise.all([
$getStatistics(statisticsDate.value, vdata.countType, vdata.agentNo)
])
.then((res) => {
getStatisticsData('')
})
.catch((error) => {})
</script>
<style lang="less" scoped>
.content {
width: 100%;
display: flex;
flex-wrap: wrap;
}
.statistics-box {
box-sizing: border-box;
padding: 30px;
margin-bottom: 30px;
.main-item,
.other-item {
width: 100%;
.item {
width: 25%;
}
p {
margin: 0 !important;
}
.item-title {
white-space: nowrap;
font-weight: 400;
font-size: 13px;
letter-spacing: 0.05em;
color: rgba(0, 0, 0, 0.6);
}
.item-text {
font-weight: 400;
font-size: 50px;
letter-spacing: 0.05em;
color: var(--ant-primary-color);
}
}
.other-item {
display: flex;
.item-text {
font-weight: 400;
font-size: 25px;
letter-spacing: 0.05em;
color: #000 !important;
}
}
}
.hardware-statistics {
.hardware-item {
display: flex;
flex-wrap: wrap;
.item {
width: 25%;
}
.item-text-top {
margin-bottom: 30px !important;
font-weight: 400;
font-size: 37px;
}
}
}
/deep/ .ant-card-body {
height: 100%;
}
.box-flex {
flex-grow: 1;
display: flex;
flex-wrap: wrap;
height: 100%;
align-content: space-between;
& > div {
width: 100%;
}
}
.title {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
b {
font-size: 14px;
font-weight: 600;
margin-right: 10px;
}
.date {
width: 215px;
}
}
.agent-info {
margin-bottom: 30px;
.desc {
font-weight: 500;
font-size: 14px;
letter-spacing: 0.05em;
color: #262626;
}
.label {
font-weight: 500;
font-size: 14px;
color: #999;
margin-right: 10px;
}
.item {
display: flex;
justify-content: space-between;
padding-bottom: 10px;
&:nth-last-child(1) {
padding: 0;
}
}
}
.agent-statistics {
display: flex;
flex-wrap: wrap;
height: 100%;
align-content: space-between;
& > div {
width: 100%;
}
}
// 响应式部分
.agent-info,
.order,
.agent,
.hardware {
width: 100%;
}
@media screen and (min-width: 1024px) {
.agent-info {
order: 1;
width: 280px;
flex-grow: 1;
}
.agent {
order: 0;
width: 260px;
flex-grow: 1;
margin-right: 30px;
}
.order,
.hardware {
order: 3;
width: 100%;
}
}
@media screen and (min-width: 1430px) {
.agent,
.agent-info {
width: 350px;
margin-right: 30px;
}
.order,
.hardware {
width: 65%;
flex-grow: 1;
}
.agent-info {
order: 0;
}
.order {
order: 1;
}
.agent {
order: 2;
}
.hardware {
order: 3;
}
}
</style>

View File

@@ -0,0 +1,19 @@
<template>
<div style="background-color: white; min-height: 100%">
<JeepayPayIsvTransferConfigPanel type="0" infoId="CURRENTAGENT" configMode="agentSelf" />
</div>
</template>
<script lang="ts" setup>
import {reactive} from 'vue'
</script>
<style lang="less" scoped>
::v-deep .drawer.cur {
position: relative;
}
::v-deep .drawer-btn-center-payfee{
position: relative !important;
}
</style>

View File

@@ -0,0 +1,426 @@
<template>
<div>
<a-card style="width: 100%; margin-bottom: 20px">
<div class="title">
<p>佣金钱包</p>
</div>
<div class="items">
<div class="item">
<div style="display: flex">
<p class="item-title">钱包余额</p>
<!-- <a-tooltip class="tooltip">
<template #title></template>
<i class="bi bi-info-circle" />
</a-tooltip> -->
</div>
<p class="item-detail">{{ vdata.balanceAmountShow }}</p>
</div>
<div class="item">
<div style="display: flex">
<p class="item-title">在途利润</p>
<a-tooltip class="tooltip">
<template #title>处于结算周期中的实际利润金额</template>
<i class="bi bi-info-circle" />
</a-tooltip>
</div>
<p class="item-sub">{{ vdata.auditProfitAmount }}</p>
</div>
<div class="item">
<div style="display: flex">
<p class="item-title">提现中金额</p>
<a-tooltip class="tooltip">
<template #title>处于提现中状态下的金额</template>
<i class="bi bi-info-circle" />
</a-tooltip>
</div>
<p class="item-sub">{{ vdata.unAmountShow }}</p>
</div>
<div class="item">
<div style="display: flex">
<p class="item-title">冻结金额</p>
<a-tooltip class="tooltip">
<template #title>
<span>
冻结金额为不可提现金额当前可提现金额为{{ vdata.allowTakeAmount }}
</span>
<span v-if="vdata.detailData.freezeDesc"><br>冻结原因{{ vdata.detailData.freezeDesc }}请尽快处理</span>
</template>
<i class="bi bi-info-circle" />
</a-tooltip>
</div>
<p class="item-sub">{{ vdata.freezeAmount }}</p>
</div>
<div class="item operate" @click="withdrawFunc">
<a-button type="primary">提现</a-button>
</div>
</div>
</a-card>
<a-card class="table-card">
<JeepaySearchForm
:searchFunc="searchFunc"
:resetFunc="resetFunc"
>
<a-form-item label="" class="table-search-item">
<JeepayDateRangePicker
ref="dateRangePicker"
v-model:value="vdata.searchData['queryDateRange']"
customDateRangeType="dateTime"
/>
</a-form-item>
<jeepay-text-up
v-model:value="vdata.searchData['rid']"
:placeholder="'提现单号'"
/>
<a-form-item label="" class="table-search-item">
<a-select
v-model:value="vdata.searchData['state']"
placeholder="提现状态"
>
<a-select-option value=""> 全部 </a-select-option>
<a-select-option value="1"> 审核中 </a-select-option>
<a-select-option value="2"> 审核失败 </a-select-option>
<a-select-option value="3"> 结算中 </a-select-option>
<a-select-option value="4"> 结算成功 </a-select-option>
<a-select-option value="5"> 结算失败 </a-select-option>
</a-select>
</a-form-item>
</JeepaySearchForm>
<!-- 提现弹出框 -->
<withdraw-modal ref="withdrawModalInfo" :callbackFunc="refreshTable" />
<!-- 列表渲染 -->
<JeepayTable
ref="infoTable"
:init-data="true"
:req-table-data-func="reqTableDataFunc"
:table-columns="tableColumns"
:search-data="vdata.searchData"
row-key="rid"
:statisticsIsShow="$access('ENT_CASHOUT_RECORD_COUNT')"
@btnLoadClose="vdata.btnLoading = false"
>
<template #statistics>
<div class="statistics-list">
<div v-for="(item, index) in tableCount" :key="index" class="item">
<div v-if="item.type == 'line'" class="line" />
<div class="title-count">{{ item.title }}</div>
<div v-if="item.title" class="amount" :style="{color: item.textColor}">
<span class="amount-num">{{ item.content }}</span>
<span v-if="item.isAmount"></span>
</div>
</div>
</div>
</template>
<template #topBtnSlot>
<a-button type="primary" @click="withdrawFunc()">
<plus-outlined />发起提现
</a-button>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key == 'state'">
<a-tag v-if="record.state == 1" color="orange">审核中</a-tag>
<a-tag v-if="record.state == 2" color="red">审核失败</a-tag>
<a-tag v-if="record.state == 3" color="purple">结算中</a-tag>
<a-tag v-if="record.state == 4" color="green">结算成功</a-tag>
<a-tag v-if="record.state == 5" color="red">结算失败</a-tag>
</template>
<template v-if="column.key == 'applyAmount'">
<span>{{ record.applyAmount / 100 }}</span>
</template>
<template v-if="column.key == 'settAmount'">
<span>{{ record.settAmount / 100 }}</span>
</template>
<template v-if="column.key == 'settFeeAmount'">
<span>{{ record.settFeeAmount / 100 }}</span>
</template>
<template v-if="column.key === 'op'">
<a-button type="link" @click="detailFunc(record.rid)">详情</a-button>
<a-button
v-show="record.state === 2"
type="link"
style="color: red"
@click="withdrawAgainFunc(record)"
>
重新提现
</a-button>
</template>
</template>
</JeepayTable>
</a-card>
<!-- 详情页面组件 -->
<InfoDetail ref="infoDetail" :callback-func="searchFunc" />
</div>
</template>
<script lang="ts" setup>
import { message } from 'ant-design-vue'
import withdrawModal from './WithdrawModal.vue' // 提现弹出框
import InfoDetail from './WithdrawDetail.vue'
import { API_URL_WITHDRAWALS_RECORD_LIST, $getMainUserInfo, req, $getStatistics, $cashoutCount } from '@/api/manage'
import { ref, onMounted, reactive, getCurrentInstance } from 'vue'
const { $infoBox, $access } = getCurrentInstance()!.appContext.config.globalProperties // 获取全局函数
const infoTable = ref()
const vdata = reactive({
visible: false,
searchData: {} as any,
detailData: {} as any,
selectedIds: [] as any,
auditProfitAmount: '',
balanceAmount: 0,
balanceAmountShow: '',
unAmountShow: '',
unAmount: 0,
freezeAmount: '', // 冻结金额
allowTakeAmount: '', // 当前可提现金额
btnLoading: false
})
let tableCount:any = ref([]) // 数据统计数组
const tableColumns = reactive([
{
key: 'rid',
title: '提现单号',
fixed: 'left',
dataIndex: 'rid',
},
{
key: 'applyAmount',
title: '申请金额',
dataIndex: 'applyAmount',
},
{
key: 'settFeeAmount',
title: '手续费',
dataIndex: 'settFeeAmount',
},
{
key: 'settAmount',
title: '入账金额',
dataIndex: 'settAmount',
},
{
key: 'state',
title: '提现状态',
dataIndex: 'state',
},
{
key: 'createdAt',
title: '发起时间',
dataIndex: 'createdAt',
},
{
key: 'op',
title: '操作',
fixed: 'right',
align: 'center',
}
])
const withdrawModalInfo = ref()
const infoDetail = ref()
const dateRangePicker = ref()
// 传递的开始提现对象
const sendObject = reactive({
amount: 0,
agentType: '', // 代理商类型
settAccountType: '', //账户类型
settAccountNo: '', //结算账户
settAccountName: '', //账户姓名
settAccountBank: '', //开户行
settAccountSubBank: '', //开户行支行名称
settCertImg: '', //提现凭证
applyRemark: '', //提现备注
settFeeAmount: 0, //提现手续费
contactTel: '', //联系人手机号
contactName: '', //联系人姓名
applyAmount: 0, //申请提现金额
balanceAmount: 0, // 钱包余额
freezeAmount: 0, // 钱包冻结金额
isAgain: false
})
// 数据统计
const getTableCount = () => {
if(!$access('ENT_CASHOUT_RECORD_COUNT')){
return false
}
$cashoutCount(vdata.searchData).then( res => {
tableCount.value = [
{title: '申请金额', symbol: 'add', isAmount: true, textColor: '#1A66FF', content: (res.applyAmount / 100).toFixed(2)},
{type: 'line'},
{title: '手续费', symbol: 'add', isAmount: true, textColor: '#1A66FF', content: (res.settFeeAmount / 100).toFixed(2)},
{type: 'line'},
{title: '入账金额', symbol: 'add', isAmount: true, textColor: '#1A66FF', content: (res.settAmount / 100).toFixed(2)},
]
})
}
onMounted( () => {
getTableCount()
})
// 请求table接口数据
function reqTableDataFunc(params) {
return req.list(API_URL_WITHDRAWALS_RECORD_LIST, params)
}
function detailFunc(recordId) {
infoDetail.value.show(recordId)
}
function resetFunc() {
dateRangePicker.value.returnSelectModel()
vdata.searchData = {}
}
// 获取统计数据
function getStatisticsData() {
$getStatistics('')
.then((res) => {
vdata.detailData = res
vdata.auditProfitAmount = (
parseFloat(res.auditProfitAmount) / 100
).toFixed(2)
vdata.balanceAmount = res.availableBalanceAmount / 100
vdata.unAmount = res.unAmount / 100
vdata.balanceAmountShow = (
parseFloat(res.availableBalanceAmount) / 100
).toFixed(2)
vdata.unAmountShow = (parseFloat(res.unAmount) / 100).toFixed(2)
vdata.freezeAmount = (parseFloat(res.freezeAmount) / 100).toFixed(2)
vdata.allowTakeAmount = (parseFloat(res.allowTakeAmount) / 100).toFixed(2)
})
.catch((err) => {
console.log(err)
})
}
function searchFunc() {
getTableCount()
// 点击【查询】按钮点击事件
infoTable.value.refTable(true)
}
// 获取账户信息
function getAccountInfo() {
$getMainUserInfo()
.then((res) => {
sendObject.agentType = res.agentType
sendObject.contactTel = res.contactTel
sendObject.contactName = res.contactName
sendObject.settAccountName = res.settAccountName
sendObject.settAccountNo = res.settAccountNo
sendObject.settAccountBank = res.settAccountBank
sendObject.settAccountType = res.settAccountType
sendObject.settAccountSubBank = res.settAccountSubBank
})
.catch((err) => {
console.log(err)
})
}
function refreshTable() {
infoTable.value.refTable(true)
getStatisticsData()
}
// 打开提现弹出框
function withdrawFunc() {
if (vdata.balanceAmount <= 0) {
return message.error('无可提现金额')
} else {
if (Number(vdata.allowTakeAmount) <= 0) {
return message.error('无可提现金额')
}
getAccountInfo()
sendObject.settCertImg = ''
sendObject.applyRemark = ''
sendObject.amount = Number(vdata.allowTakeAmount)
sendObject.balanceAmount = Number(vdata.balanceAmount)
sendObject.freezeAmount = Number(vdata.freezeAmount)
withdrawModalInfo.value.show(sendObject)
}
}
// 重新提现
function withdrawAgainFunc(saveObject) {
getAccountInfo()
sendObject.isAgain = true
sendObject.amount = vdata.balanceAmount
sendObject.settFeeAmount = saveObject.settFeeAmount / 100
sendObject.applyRemark = saveObject.applyRemark
sendObject.settCertImg = saveObject.settCertImg
sendObject.applyAmount = saveObject.applyAmount
withdrawModalInfo.value.show(sendObject)
}
Promise.all([getStatisticsData()])
</script>
<style lang="less" scoped>
.title {
box-sizing: border-box;
padding: 16px 0 0px 20px;
margin-bottom: 30px;
border-bottom: 1px solid #e8e8e8;
p {
font-weight: bold;
font-size: 14px;
letter-spacing: 0.07em;
color: #000;
}
}
.items {
display: flex;
justify-content: space-around;
.item {
margin-bottom: 20px;
p {
font-weight: 500;
margin: 0;
}
.item-title {
font-size: 14px;
letter-spacing: 0.05em;
color: #737980;
}
.item-detail,
.item-sub {
font-size: 25px;
color: var(--ant-primary-color);
}
.item-sub {
color: #000000 !important;
}
}
.operate {
display: flex;
justify-content: center;
align-items: center;
width: 100px;
height: 38px;
border-radius: 3px;
margin-top: 15px;
p {
font-size: 13px;
letter-spacing: 0.02em;
color: #fff;
}
}
}
.tooltip {
&:hover {
cursor: pointer;
}
}
.statistics-list .item .title-count {
color: #808080;
margin-bottom: 10px;
}
</style>

View File

@@ -0,0 +1,112 @@
<template>
<a-drawer
v-model:visible="vdata.visible"
:title="true ? '提现详情' : ''"
:body-style="{ paddingBottom: '80px' }"
width="40%"
@close="onClose"
>
<a-row justify="space-between" type="flex">
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="提现金额">{{ vdata.detailData['applyAmount'] / 100 }}</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="提现手续费">{{ vdata.detailData['settFeeAmount'] / 100 }}</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="结算账户类型">{{ vdata.detailData['settAccountType'] === "WX_CASH" ? '个人微信':vdata.detailData['settAccountType'] === "ALIPAY_CASH"? '个人支付宝':vdata.detailData['settAccountType'] === "BANK_PUBLIC"? '对公账户':vdata.detailData['settAccountType'] === "BANK_PRIVATE"? '对私账户':'银行卡' }}</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="结算账号">{{ vdata.detailData['settAccountNo'] }}</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="开户行名称">{{ vdata.detailData['settAccountBank'] }}</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="结算账户姓名">{{ vdata.detailData['settAccountName'] }}</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col v-if="vdata.detailData['auditRemark']" :sm="12">
<a-descriptions>
<a-descriptions-item label="失败原因">{{ vdata.detailData['auditRemark'] }}</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="联系人姓名">{{ vdata.detailData['contactName'] }}</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="结算账户联系人手机号">{{ vdata.detailData['settAccountTelphone'] }}</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="提现备注">{{ vdata.detailData['applyRemark'] }}</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="24">
<a-descriptions>
<a-descriptions-item label="申请资料">
<a-image v-if="vdata.detailData.settCertImg" class="withdraw-img" :src="vdata.detailData.settCertImg" />
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="24">
<a-descriptions>
<a-descriptions-item label="打款凭证">
<a-image v-if="vdata.detailData['transferCertImg'] && vdata.detailData['state'] == 4" class="withdraw-img" :src="vdata.detailData['transferCertImg']" />
</a-descriptions-item>
</a-descriptions>
</a-col>
</a-row>
</a-drawer>
</template>
<script lang="ts" setup>
import { API_URL_WITHDRAWALS_RECORD_LIST, req } from '@/api/manage'
import { defineProps, reactive } from 'vue'
const props = defineProps({
callbackFunc: { type: Function, default: null }
})
const vdata = reactive({
btnLoading: false,
detailData: {
settCertImg: ''//提现凭证
}, // 数据对象
recordId: null, // 更新对象ID
visible: false, // 是否显示弹层/抽屉
})
function show(recordId) { // 弹层打开事件
vdata.recordId = recordId
req.getById(API_URL_WITHDRAWALS_RECORD_LIST, recordId, {originData: 0}).then(res => {
vdata.detailData = res
})
vdata.visible = true
}
function onClose() {
vdata.visible = false
}
defineExpose({
show //抛出show函数给父组件
})
</script>
<style>
.withdraw-img {
width: 200px;
}
</style>

View File

@@ -0,0 +1,355 @@
<template>
<a-drawer
v-model:visible="vdata.visible"
:mask-closable="false"
:title=" vdata.isAgain ? '重新提现' : '发起提现' "
width="40%"
@close="onClose"
>
<a-form v-if="vdata.visible" ref="infoFormModel" :model="vdata.saveObject" layout="vertical" :rules="vdata.rules">
<a-row justify="space-between" type="flex">
<a-col :span="14">
<a-form-item :label="'提现金额(最多可提现:' + vdata.amount + '元)'" name="amount">
<a-input-number
v-model:value="vdata.saveObject.amount"
placeholder="请输入提现金额"
style="width:70%;"
min="0.01"
:max="vdata.amount"
addon-after=""
/>
<span class="jeepay-tip-text">钱包余额{{ vdata.detailData.balanceAmount }}冻结金额{{ vdata.detailData.freezeAmount }} </span>
</a-form-item>
</a-col>
<a-col :span="4">
<a-form-item label=" " name="">
<a-button type="dashed" style="color: #1f8cf5; background-color: #e6f2fd;" :loading="vdata.btnLoading" @click="handleTakeAllFunc">
<PayCircleOutlined />
全部提现
</a-button>
</a-form-item>
</a-col>
<a-col :span="6" />
</a-row>
<a-row justify="space-between" type="flex">
<a-col :span="10">
<a-form-item label="请输入联系人姓名:" name="contactName">
<a-input v-model:value="vdata.saveObject.contactName" placeholder="请输入结算账户姓名" />
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="结算账户联系人手机号:" name="contactTel">
<a-input v-model:value="vdata.saveObject.contactTel" placeholder="结算账户联系人手机号" />
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="提现备注:" name="applyRemark">
<a-input
v-model:value="vdata.saveObject['applyRemark']"
placeholder="请输入提现备注"
/>
</a-form-item>
</a-col>
<a-col :span="10">
<div class="ant-upload-preview">
<div>
<a-form-item label="申请资料" name="settCertImg" />
<img v-show="vdata.saveObject.settCertImg" :src="vdata.saveObject.settCertImg">
</div>
<div>
<JeepayUpload
v-model:src="vdata.saveObject.settCertImg"
bizType="avatar"
:showUploadList="false"
/>
<span class="jeepay-tip-text">请上传发票等凭证图片</span>
</div>
</div>
</a-col>
<a-col :span="24">
<a-col :span="10">
<a-form-item label="支付密码:" name="sipwRaw">
<a-input-password
v-model:value="vdata.saveObject['sipwRaw']"
maxlength="6"
autocomplete="new-password"
placeholder="请输入支付密码"
/>
</a-form-item>
</a-col>
</a-col>
<a-divider orientation="left" />
<a-col :span="10">
<a-descriptions>
<a-descriptions-item label="提现手续费">
{{ vdata.saveObject.settFeeAmount }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :span="10">
<a-descriptions>
<a-descriptions-item label="到账金额">
{{ vdata.receivedAmount }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="服务商类型">
{{ vdata.saveObject.agentType === 1 ? '个人':'企业' }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="收款账户类型">
{{ vdata.saveObject.settAccountType === "WX_CASH" ? '个人微信':vdata.saveObject.settAccountType === "ALIPAY_CASH"? '个人支付宝':vdata.saveObject.settAccountType === "BANK_PUBLIC"? '对公账户':vdata.saveObject.settAccountType === "BANK_PRIVATE"? '对私账户':'银行卡' }}
</a-descriptions-item>
</a-descriptions>
</a-col>
</a-row>
<a-row v-if="vdata.saveObject.settAccountType === 'WX_CASH' || vdata.saveObject.settAccountType === 'ALIPAY_CASH'" justify="space-between" type="flex">
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="账户姓名">
{{ vdata.saveObject.settAccountName }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item :label="vdata.saveObject.settAccountType === 'ALIPAY_CASH'?'支付宝账号':'个人微信号'">
{{ vdata.saveObject.settAccountNo }}
</a-descriptions-item>
</a-descriptions>
</a-col>
</a-row>
<a-row v-if="vdata.saveObject.settAccountType === 'BANK_PRIVATE'" justify="space-between" type="flex">
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="账户姓名">
{{ vdata.saveObject.settAccountName }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="收款银行卡号">
{{ vdata.saveObject.settAccountNo }}
</a-descriptions-item>
</a-descriptions>
</a-col>
</a-row>
<a-row v-if="vdata.saveObject.settAccountType === 'BANK_PUBLIC'" justify="space-between" type="flex">
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="对公账户名称">
{{ vdata.saveObject.settAccountName }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="对公账号">
{{ vdata.saveObject.settAccountNo }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="开户银行名称">
{{ vdata.saveObject.settAccountBank }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="开户行支行名称">
{{ vdata.saveObject.settAccountSubBank }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col v-if="!vdata.saveObject.settAccountNo" :span="10">
<a-form-item label="温馨提示:" name="tips">
<a-tag color="red">您还未配置结算信息请联系运营平台添加</a-tag>
</a-form-item>
</a-col>
</a-row>
</a-form>
<div class="drawer-btn-center">
<a-button :style="{ marginRight: '8px' }" style="margin-right:8px" @click="onClose">
<close-outlined />
取消
</a-button>
<a-button type="primary" :loading="vdata.btnLoading" @click="handleOkFunc">
<check-outlined />
保存
</a-button>
</div>
</a-drawer>
</template>
<script lang="ts" setup>
import { $withdrawalApply, $withdrawalFee } from '@/api/manage'
import { message } from 'ant-design-vue'
import { defineProps, reactive, ref, watch } from 'vue'
import { Base64 } from 'js-base64'
const props = defineProps({
callbackFunc: { type: Function,default:null }
})
const infoFormModel = ref()
const vdata : any = reactive({
feeType: '',//手续费类型
applyLimit: 0,//最低提现金额
freeLimit: 0,//免收手续费额度
fixFee: 0,//固定费用
feeRate: 0,//费率
btnLoading: false,
amount: 0,//可以提现的金额
isAgain: true, // 重新提现 or 提现页面标志
saveObject: {}, // 数据对象
detailData: {} as any, // 数据对象(详情数据)
recordId: null, // 更新对象ID
visible: false, // 是否显示弹层/抽屉
receivedAmount: 0,//到账金额
rules: {
amount: [{ required: true, message: '请输入提现金额', trigger: 'blur' }],
settFeeAmount: [{ required: true, message: '提现手续费', trigger: 'blur' }],
contactName: [{ required: true, message: '请输入联系人姓名', trigger: 'blur' }],
contactTel: [{ required: true, message: '请输入联系人手机号', trigger: 'blur' }],
sipwRaw: [{ required: true, message: '请输入支付密码', trigger: 'blur' }],
}
})
// 计算手续费
function calculationFee() {
$withdrawalFee().then(res => {
vdata.feeType = res.feeType
vdata.applyLimit = res.applyLimit / 100
vdata.freeLimit = res.freeLimit / 100
vdata.fixFee = res.fixFee / 100
vdata.feeRate = res.feeRate
if(res.freeLimit !== 0){ //如果在额度限度里手续费为0
if(vdata.saveObject.amount <= res.freeLimit / 100){
vdata.saveObject.settFeeAmount = 0
} else { //如果超过限制额度
if(res.feeType === 'FIX'){//若是固定额度
vdata.saveObject.settFeeAmount = res.fixFee / 100
}else if(res.feeType === 'SINGLE'){//若是费率
vdata.saveObject.settFeeAmount = (res.feeRate * vdata.saveObject.amount).toFixed(2)
}else if(res.feeType === 'FIXANDRATE'){//若是费率 + 固定
vdata.saveObject.settFeeAmount = ((res.fixFee) / 100 + (res.feeRate * vdata.saveObject.amount)).toFixed(2)
}
}
} else {
//如果额度为0则计算费率
if(res.feeType === 'FIX'){
vdata.saveObject.settFeeAmount = res.fixFee / 100
}else if(res.feeType === 'SINGLE'){ //若是费率
vdata.saveObject.settFeeAmount = (res.feeRate * vdata.saveObject.amount).toFixed(2)
}else if(res.feeType === 'FIXANDRATE'){//若是费率 + 固定
vdata.saveObject.settFeeAmount = ((res.fixFee) / 100 + (res.feeRate * vdata.saveObject.amount)).toFixed(2)
}
}
vdata.receivedAmount = (vdata.saveObject.amount - vdata.saveObject.settFeeAmount).toFixed(2)
if(vdata.receivedAmount <0 ){
vdata.receivedAmount = 0
}
}).catch(err => {
console.log(err)
})
}
function show (params) { // 弹层打开事件
vdata.isAgain = params.isAgain
vdata.amount = params.amount
vdata.saveObject = params
vdata.detailData = params
vdata.btnLoading = false
vdata.visible = true
calculationFee()
if(params.isAgain) {
vdata.saveObject.amount = params.applyAmount / 100
} else {
vdata.saveObject.amount = ''
}
delete vdata.saveObject.applyAmount
delete vdata.saveObject.isAgain
delete vdata.saveObject.sipwRaw
}
// 监听提现金额和手续费的变化
watch([() => vdata.saveObject.settFeeAmount, () => vdata.saveObject.amount, () => vdata.receivedAmount], (values) => {
calculationFee()
})
function handleOkFunc () { // 点击【确认】按钮事件
infoFormModel.value.validate().then(valid =>{
vdata.btnLoading = true
const phoneReg = /^1\d{10}$/
if(!phoneReg.test(vdata.saveObject.contactTel)) {
message.error(`请输入合法手机号`)
vdata.btnLoading = false
}else if(isNaN(vdata.receivedAmount) || vdata.receivedAmount <= 0){
message.error(`到账金额计算有误, 请重新填写`)
vdata.btnLoading = false
}else {
if(vdata.amount >= parseFloat(vdata.saveObject.amount)) {
if(vdata.applyLimit > parseFloat(vdata.saveObject.amount)) {
message.error(`最低提现金额为:${vdata.applyLimit}`)
vdata.btnLoading = false
} else {
// 请求接口
vdata.saveObject.sipw = Base64.encode(vdata.saveObject.sipwRaw) // base 64
$withdrawalApply(vdata.saveObject).then(res => {
if(vdata.isAgain) {
message.success('已重新发起提现')
} else {
message.success('提现已发起')
}
props.callbackFunc() // 刷新列表
vdata.btnLoading = false
}).catch(err => {
console.log(err)
vdata.btnLoading = false
})
vdata.visible = false
}
} else {
message.error(`请输入不大于${vdata.amount}元的金额`)
vdata.btnLoading = false
}
}
}).catch(valid =>{
console.log(valid)
})
}
function handleTakeAllFunc(){
vdata.saveObject.amount = vdata.amount
}
function onClose () {
vdata.visible = false
}
defineExpose({
show
})
</script>
<style lang="less">
.ant-upload-preview {
display: flex;
justify-content: space-between;
img {
width: 100px;
height: 100px;
border: 1px solid rgba(0,0,0,0.08);
}
.upload-icon {
font-size: 1.4rem;
background: rgba(222, 221, 221, 0.7);
border-radius: 50%;
border: 1px solid rgba(0, 0, 0, 0.2);
}
}
</style>

View File

@@ -0,0 +1,557 @@
<template>
<a-drawer
v-model:visible="vdata.visible"
:mask-closable="false"
:title=" vdata.isAdd ? '新增服务商' : '修改服务商' "
:body-style="{ paddingBottom: '80px' }"
width="40%"
class="drawer-width"
@close="onClose"
>
<a-form v-if="vdata.visible" ref="infoFormModel" :model="vdata.saveObject" layout="vertical" :rules="vdata.rules">
<a-row justify="space-between" type="flex">
<a-col :span="10">
<a-form-item label="服务商名称" name="agentName">
<a-input
v-model:value="vdata.saveObject['agentName']"
placeholder="请输入服务商名称"
/>
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="登录名" name="loginUsername">
<a-input
v-model:value="vdata.saveObject['loginUsername']"
placeholder="请输入服务商登录名"
:disabled="!vdata.isAdd"
/>
</a-form-item>
</a-col>
</a-row>
<a-row justify="space-between" type="flex">
<a-col :span="10">
<a-form-item label="服务商简称" name="agentShortName">
<a-input
v-model:value="vdata.saveObject['agentShortName']"
placeholder="请输入服务商简称"
/>
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="联系人姓名" name="contactName">
<a-input
v-model:value="vdata.saveObject['contactName']"
placeholder="请输入联系人姓名"
/>
</a-form-item>
</a-col>
</a-row>
<a-row justify="space-between" type="flex">
<a-col :span="10">
<a-form-item label="联系人邮箱" name="contactEmail">
<a-input
v-model:value="vdata.saveObject['contactEmail']"
placeholder="请输入联系人邮箱"
/>
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="联系人手机号" name="contactTel">
<a-input
v-model:value="vdata.saveObject['contactTel']"
placeholder="请输入联系人手机号"
/>
</a-form-item>
</a-col>
</a-row>
<a-row justify="space-between" type="flex">
<a-col v-if="!vdata.isAdd" :span="10">
<a-form-item label="渠道商号" name="isvNo">
<a-select v-model:value="vdata.saveObject['isvNo']" :disabled="!vdata.isAdd" placeholder="请选择渠道商">
<a-select-option v-for="d in vdata.isvList" :key="d.isvNo" v-model:value="d.isvNo">
{{ d.isvName + " [ ID: " + d.isvNo + " ]" }}
</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="状态" name="state">
<a-radio-group v-model:value="vdata.saveObject['state']" :disabled="vdata.saveObject.state == '2' || vdata.saveObject.state == '3'">
<a-radio :value="1">
启用
</a-radio>
<a-radio :value="0">
禁用
</a-radio>
</a-radio-group>
</a-form-item>
</a-col>
<a-col v-if="vdata.isAdd" :span="10">
<a-form-item label="是否允许发展下级" name="addAgentFlag">
<a-radio-group v-model:value="vdata.saveObject['addAgentFlag']">
<a-radio :value="1">
</a-radio>
<a-radio :value="0">
</a-radio>
</a-radio-group>
</a-form-item>
</a-col>
</a-row>
<!-- <a-row justify="space-between" type="flex">
<a-col :span="24">
<a-form-item label="备注" name="remark">
<a-input v-model:value="vdata.saveObject['remark']" placeholder="请输入备注" type="textarea" />
</a-form-item>
</a-col>
</a-row> -->
<!-- 重置密码板块 -->
<a-row justify="space-between" type="flex">
<a-col :span="24">
<a-divider orientation="left">
<a-tag color="#FF4B33">
账户安全
</a-tag>
</a-divider>
</a-col>
</a-row>
<div v-if="vdata.isAdd">
<a-row justify="space-between" type="flex">
<a-col :span="24">
<a-form-item label="是否发送开通提醒">
<a-radio-group v-model:value="vdata.saveObject['isNotify']">
<a-radio :value="0"></a-radio>
<a-radio :value="1"></a-radio>
</a-radio-group>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="密码设置">
<a-radio-group v-model:value="vdata.saveObject['passwordType']">
<a-radio value="default">默认密码</a-radio>
<a-radio value="custom">自定义密码</a-radio>
</a-radio-group>
</a-form-item>
</a-col>
<a-col v-if="vdata.saveObject.passwordType == 'custom'" :span="12">
<a-form-item label="登录密码" name="loginPassword">
<a-input
v-model:value="vdata.saveObject['loginPassword']"
placeholder="请输入登录密码"
/>
</a-form-item>
<a-button type="primary" ghost @click="randomPassage(false, 6, 0)"><file-sync-outlined />随机生成密码</a-button>
</a-col>
</a-row>
</div>
<div v-else>
<div>
<a-row justify="space-between" type="flex">
<a-col :span="10">
<a-form-item v-if="vdata.resetIsShow" label="">
重置密码<a-checkbox v-model:checked="vdata.sysPassword.resetPass" />
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item v-if="vdata.sysPassword.resetPass" label="">
恢复默认密码<a-checkbox v-model:checked="vdata.sysPassword.defaultPass" @click="isResetPass" />
</a-form-item>
</a-col>
</a-row>
</div>
<div v-if="vdata.sysPassword.resetPass">
<div v-if="!vdata.sysPassword.defaultPass">
<a-row justify="space-between" type="flex">
<a-col :span="10">
<a-form-item label="新密码:" name="newPwd">
<a-input-password v-model:value="vdata.newPwd" autocomplete="new-password" :disabled="vdata.sysPassword.defaultPass" />
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="确认新密码:" name="confirmPwd">
<a-input-password v-model:value="vdata.sysPassword.confirmPwd" autocomplete="new-password" :disabled="vdata.sysPassword.defaultPass" />
</a-form-item>
</a-col>
</a-row>
</div>
</div>
</div>
<div v-if="vdata.isAudit">
<a-row justify="space-between" type="flex">
<a-col :span="24">
<a-divider orientation="left">
<a-tag color="#FF4B33">
账户信息
</a-tag>
</a-divider>
</a-col>
</a-row>
<a-row justify="space-between" type="flex">
<a-col :span="10">
<a-form-item label="服务商类型" name="agentType">
<a-select v-model:value="vdata.saveObject['agentType']" :defaultValue="1">
<a-select-option :value="1">个人</a-select-option>
<a-select-option :value="2">企业</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="收款账户类型" name="settAccountType">
<a-select v-model:value="vdata.saveObject['settAccountType']" defaultValue="ALIPAY_CASH">
<a-select-option value="ALIPAY_CASH">个人支付宝</a-select-option>
<a-select-option value="BANK_PRIVATE">对私账户</a-select-option>
<a-select-option v-if="vdata.saveObject['agentType'] !== 1" value="BANK_PUBLIC">对公账户</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-row v-if="vdata.saveObject['settAccountType'] === 'ALIPAY_CASH'" justify="space-between" type="flex">
<a-col :span="10">
<a-form-item label="账户姓名">
<a-input
v-model:value="vdata.saveObject['settAccountName']"
/>
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="支付宝账号">
<a-input
v-model:value="vdata.saveObject['settAccountNo']"
/>
</a-form-item>
</a-col>
</a-row>
<a-row v-if="vdata.saveObject['settAccountType'] === 'BANK_PRIVATE'" justify="space-between" type="flex">
<a-col :span="10">
<a-form-item label="账户姓名">
<a-input
v-model:value="vdata.saveObject['settAccountName']"
/>
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="开户银行名称">
<a-input
v-model:value="vdata.saveObject['settAccountBank']"
/>
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="收款银行卡号">
<a-input
v-model:value="vdata.saveObject['settAccountNo']"
/>
</a-form-item>
</a-col>
</a-row>
<a-row v-if="vdata.saveObject['settAccountType'] === 'BANK_PUBLIC'" justify="space-between" type="flex">
<a-col :span="10">
<a-form-item label="对公账户名称">
<a-input
v-model:value="vdata.saveObject['settAccountName']"
/>
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="对公账号">
<a-input
v-model:value="vdata.saveObject['settAccountNo']"
/>
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="开户银行名称">
<a-input
v-model:value="vdata.saveObject['settAccountBank']"
/>
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="开户行支行名称">
<a-input
v-model:value="vdata.saveObject['settAccountSubBank']"
/>
</a-form-item>
</a-col>
</a-row>
<a-row justify="space-between" type="flex">
<a-col :span="24">
<a-divider orientation="left">
<a-tag color="#FF4B33">
资料信息
</a-tag>
</a-divider>
</a-col>
</a-row>
<a-row justify="space-between" type="flex">
<a-col v-if="vdata.saveObject['agentType'] == 2" :span="10">
<a-form-item label="营业执照照片" name="licenseImg">
<JeepayUpload v-model:src="vdata.saveObject['licenseImg']" bizType="applyment" />
</a-form-item>
</a-col>
<a-col v-if="vdata.saveObject['agentType'] == 2 && vdata.saveObject['settAccountType'] == 'BANK_PUBLIC'" :span="10">
<a-form-item label="开户许可证照片" name="permitImg">
<JeepayUpload v-model:src="vdata.saveObject['permitImg']" bizType="applyment" />
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item :label="vdata.saveObject['agentType'] == 1?'[联系人]身份证人像面照片':'[法人]身份证人像面照片'" name="idcard1Img">
<JeepayUpload v-model:src="vdata.saveObject['idcard1Img']" bizType="applyment" />
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item :label="vdata.saveObject['agentType'] == 1?'[联系人]身份证国徽面照片':'[法人]身份证国徽面照片'" name="idcard2Img">
<JeepayUpload v-model:src="vdata.saveObject['idcard2Img']" bizType="applyment" />
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item :label="vdata.saveObject['agentType'] == 1?'[联系人]手持承诺函照片':'[法人]手持承诺函照片'" name="idcardInHandImg">
<JeepayUpload v-model:src="vdata.saveObject['idcardInHandImg']" bizType="applyment" />
</a-form-item>
</a-col>
<a-col v-if="vdata.saveObject['settAccountType'] == 'BANK_PRIVATE'" :span="10">
<a-form-item label="银行卡照片" name="bankCardImg">
<JeepayUpload v-model:src="vdata.saveObject['bankCardImg']" bizType="applyment" />
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="手持承诺函照示例:" name="">
<a-image style="width: 100px;height: 120px;" src="https://jeepaypublic.oss-cn-beijing.aliyuncs.com/oem/b1cd1646-fdd2-4ea7-8f1f-02a55669f534.svg" />
<span><a :href="vdata.promiseFile">模板下载</a></span>
</a-form-item>
</a-col>
</a-row>
</div>
</a-form>
<div class="drawer-btn-center">
<a-button :style="{ marginRight: '8px' }" style="margin-right:8px" @click="onClose">
<close-outlined />
取消
</a-button>
<a-button type="primary" :loading="vdata.btnLoading" @click="handleOkFunc">
<check-outlined />
保存
</a-button>
</div>
</a-drawer>
</template>
<script lang="ts" setup>
import { API_URL_AGENT_LIST, API_URL_ISV_LIST, req, $getPasswordRules, $getMainUserInfo } from '@/api/manage'
import { Base64 } from 'js-base64'
import {message} from 'ant-design-vue'
import {defineProps,reactive,ref} from 'vue'
const props = defineProps({
callbackFunc: { type: Function,default:null }
})
const checkIsvNo = (rule, value) => { // 校验是否选择了渠道商
if (!value) {
return Promise.reject(new Error('请选择渠道商'))
}
return Promise.resolve()
}
const infoFormModel = ref()
const infoTable =ref()
const vdata : any = reactive({
newPwd: '', // 新密码
resetIsShow: false, // 重置密码是否展现
passwordRules: /^$/, //密码规则
passwordRulesText: '', //密码规则提示文字
sysPassword: {
resetPass: false, // 重置密码
defaultPass: true, // 使用默认密码
confirmPwd: '' // 确认密码
},
btnLoading: false,
isAdd: true, // 新增 or 修改页面标志
isAudit: false,
saveObject: { isNotify: 0 }, // 数据对象
recordId: null, // 更新对象ID
visible: false, // 是否显示弹层/抽屉
isvList: [] as any, // 渠道商下拉列表
rules: {
agentName: [{ required: true, message: '请输入服务商名称', trigger: 'blur' }],
loginUsername: [{ required: true, pattern: /^[a-zA-Z][a-zA-Z0-9]{5,17}$/, message: '请输入字母开头长度为6-18位的登录名', trigger: 'blur' }],
agentShortName: [{ required: true, message: '请输入服务商简称', trigger: 'blur' }],
contactName: [{ required: true, message: '请输入联系人姓名', trigger: 'blur' }],
isvNo: [{ validator: checkIsvNo, trigger: 'blur' }],
contactEmail: [{ required: false, pattern: /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/, message: '请输入正确的邮箱地址', trigger: 'blur' }],
contactTel: [{ required: true, pattern: /^1\d{10}$/, message: '请输入正确的手机号', trigger: 'blur' }],
newPwd: [{ required: false, trigger: 'blur' }, {
validator: (rule, value) => {
if (!vdata.sysPassword.defaultPass) {
if (!vdata.passwordRules.test(vdata.newPwd)) {
return Promise.reject(vdata.passwordRulesText)
} else if(vdata.newPwd !== vdata.sysPassword.confirmPwd) {
return Promise.reject('新密码与确认密码不一致')
} else return Promise.resolve()
} else {
return Promise.resolve()
}
}
}], // 新密码
confirmPwd: [{ required: false, trigger: 'blur' }, {
validator: (rule, value) => {
if (!vdata.sysPassword.defaultPass) {
// vdata.newPwd === vdata.sysPassword.confirmPwd ? callBack() : callBack('新密码与确认密码不一致')
if(!vdata.passwordRules.test(vdata.sysPassword.confirmPwd)){
return Promise.reject(vdata.passwordRulesText)
} else if(vdata.newPwd !== vdata.sysPassword.confirmPwd) {
return Promise.reject('新密码与确认密码不一致')
} else return Promise.resolve()
} else {
return Promise.resolve()
}
}
}] // 确认新密码
}
})
function getPasswordRules () { // 获取密码规则
$getPasswordRules().then(res => {
vdata.passwordRules = new RegExp(res.regexpRules)
vdata.passwordRulesText = res.errTips
if(res.subIsAudit == '1') {
vdata.saveObject['settAccountType'] = 'ALIPAY_CASH'
vdata.isAudit = true
}
})
}
function show (recordId) { // 弹层打开事件
getPasswordRules()
// 查询手持承诺函模板
getAgentInfo()
vdata.isAdd = !recordId
vdata.saveObject = { 'state': 1, passwordType: 'default', 'addAgentFlag': 1, isNotify: 0, agentType: 1 } // 数据清空
if (infoFormModel.value != undefined) {
infoFormModel.value.resetFields()
}
req.list(API_URL_ISV_LIST, { 'pageSize': -1, 'state': 1 }).then(res => { // 渠道商下拉选择列表
vdata.isvList = res.records
})
if (!vdata.isAdd) { // 修改信息 延迟展示弹层
vdata.resetIsShow = true // 展示重置密码板块
vdata.recordId = recordId
req.getById(API_URL_AGENT_LIST, recordId).then(res => {
vdata.saveObject = res
})
vdata.visible = true
} else {
vdata.visible = true // 立马展示弹层信息
}
}
function handleOkFunc () { // 点击【确认】按钮事件
infoFormModel.value.validate().then(valid =>{
vdata.btnLoading = true
// 请求接口
if (vdata.isAdd) {
// 如果新增密码设置为默认密码,登录密码设置为空
if(vdata.saveObject.passwordType == 'default' || !vdata.saveObject.loginPassword) {
vdata.saveObject.loginPassword = ''
}
if(vdata.saveObject.passwordType == 'custom' && !vdata.passwordRules.test(vdata.saveObject.loginPassword)) {
vdata.btnLoading = false
return message.error(vdata.passwordRulesText)
}
req.add(API_URL_AGENT_LIST, vdata.saveObject).then(res => {
message.success('新增成功')
vdata.visible = false
props.callbackFunc() // 刷新列表
vdata.btnLoading = false
}).catch(res => {
vdata.btnLoading = false
})
} else {
vdata.sysPassword.confirmPwd = Base64.encode(vdata.sysPassword.confirmPwd)
Object.assign(vdata.saveObject, vdata.sysPassword) // 拼接对象
req.updateById(API_URL_AGENT_LIST, vdata.recordId, vdata.saveObject).then(res => {
message.success('修改成功')
vdata.visible = false
props.callbackFunc() // 刷新列表
vdata.btnLoading = false
vdata.resetIsShow = true // 展示重置密码板块
vdata.sysPassword.resetPass = false
vdata.sysPassword.defaultPass = true // 是否使用默认密码默认为true
resetPassEmpty(DataView) // 清空密码
}).catch(res => {
vdata.btnLoading = false
vdata.resetIsShow = true // 展示重置密码板块
vdata.sysPassword.resetPass = false
vdata.sysPassword.defaultPass = true // 是否使用默认密码默认为true
resetPassEmpty(vdata) // 清空密码
})
}
}).catch(valid =>{
})
}
function onClose () {
vdata.visible = false
vdata.resetIsShow = false // 取消重置密码板块展示
vdata.sysPassword.resetPass = false
resetPassEmpty(vdata)
vdata.sysPassword.defaultPass = true// 是否使用默认密码默认为true
}
// 使用默认密码重置是否为true
function isResetPass () {
if (!vdata.sysPassword.defaultPass) {
vdata.newPwd = ''
vdata.sysPassword.confirmPwd = ''
}
}
// 保存后清空密码
function resetPassEmpty (vdata) {
vdata.newPwd = ''
vdata.sysPassword.confirmPwd = ''
}
function randomPassage(randomFlag, min, max) { // 生成6位随机密码
let str = ''
let range = min
const arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
// 随机产生
if (randomFlag) {
range = Math.round(Math.random() * (max - min)) + min
}
for (var i = 0; i < range; i++) {
var pos = Math.round(Math.random() * (arr.length - 1))
str += arr[ pos ]
}
vdata.saveObject['loginPassword'] = str
}
function getAgentInfo() {
// 获取服务商信息
$getMainUserInfo().then((res) => {
vdata.promiseFile = res.promiseFile
})
}
defineExpose({
show
})
</script>
<style lang="less">
.typePopover {
position: absolute;
top: 0;
left:62px;
}
</style>

View File

@@ -0,0 +1,143 @@
<template>
<page-header-wrapper>
<a-card class="table-card">
<JeepaySearchForm :searchFunc="searchFunc" :resetFunc="() => { searchData= {} }">
<jeepay-text-up v-model:value="searchData['agentNo']" :placeholder="'服务商号'" />
<jeepay-text-up v-model:value="searchData['agentName']" :placeholder="'服务商名称'" />
<jeepay-text-up v-model:value="searchData['loginUsername']" :placeholder="'服务商登录名'" />
<a-form-item label="" class="table-search-item">
<a-select v-model:value="searchData['state']" placeholder="服务商状态">
<a-select-option value="">
全部
</a-select-option>
<a-select-option value="0">
禁用
</a-select-option>
<a-select-option value="1">
启用
</a-select-option>
</a-select>
</a-form-item>
</JeepaySearchForm>
<!-- 列表渲染 -->
<JeepayTable
ref="infoTable"
:init-data="true"
:req-table-data-func="reqTableDataFunc"
:table-columns="tableColumns"
:search-data="searchData"
row-key="agentNo"
@btnLoadClose="btnLoading=false"
>
<template #topBtnSlot>
<a-button v-if="$access('ENT_AGENT_INFO_ADD')" type="primary" @click="addFunc">
<plus-outlined />新建
</a-button>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'agentName'">
<a @click="detailFunc(record.agentNo)"><b>{{ record.agentName }}</b></a>
</template>
<template v-if="column.key === 'state'">
<a-badge
:status="record.state === 0?'error':record.state === 1?'processing':record.state === 2?'warning':record.state === 3?'error':'yellow'"
:text="record.state === 0?'禁用':record.state === 1?'启用':record.state === 2?'审核中':record.state === 3?'审核驳回':'未认证'"
/>
</template>
<template v-if="column.key === 'operation'">
<!-- 操作列插槽 -->
<JeepayTableColumns>
<a-button v-if="$access('ENT_AGENT_INFO_EDIT')" type="link" @click="editFunc(record.agentNo)">修改</a-button>
<a-button v-if="$access('ENT_AGENT_INFO_VIEW')" type="link" @click="detailFunc(record.agentNo)">详情</a-button>
<a-button v-if="$access('ENT_AGENT_RATE_CONFIG')" type="link" @click="showFeeConfigList(record.agentNo)">费率配置</a-button>
<a-button v-if="$access('ENT_AGENT_INFO_DEL')" type="link" style="color: red" @click="delFunc(record.agentNo)">删除</a-button>
</JeepayTableColumns>
</template>
</template>
</JeepayTable>
</a-card>
<!-- 新增页面组件 -->
<InfoAddOrEdit ref="infoAddOrEdit" :callback-func="searchFunc" />
<!-- 详情页面组件 -->
<InfoDetail ref="infoDetail" :callback-func="searchFunc" />
<!-- 费率配置页面 -->
<JeepayPaymentConfigDrawer ref="JeepayPaymentConfigDrawerRef" configMode="agentSubagent" />
<!-- <JeepayPayConfigDrawer ref="JeepayPaymentConfigDrawerRef" configMode="agentSubagent" />-->
</page-header-wrapper>
</template>
<script setup lang="ts">
import { API_URL_AGENT_LIST, req, reqLoad } from '@/api/manage'
import InfoAddOrEdit from './AddOrEdit.vue'
import InfoDetail from './Detail.vue'
import { ref, reactive, getCurrentInstance } from 'vue'
import router from '@/router'
const { $infoBox,$access } = getCurrentInstance()!.appContext.config.globalProperties
const infoDetail = ref()
const infoAddOrEdit = ref()
const infoTable = ref()
const JeepayPaymentConfigDrawerRef = ref()
let tableColumns = reactive([
{ key: 'agentName', title: '服务商名称', fixed: 'left', },
{ key: 'agentNo', title: '服务商号', dataIndex: 'agentNo' , },
{ key: 'mchCount', title: '商户数量', dataIndex: 'mchCount', },
{ key: 'state', title: '状态', },
{ key: 'createdAt', dataIndex: 'createdAt', title: '创建日期', },
{ key: 'operation', title: '操作', fixed: 'right', align: 'center'}
])
let btnLoading = ref(false)
let searchData = ref({})
// 请求table接口数据
function reqTableDataFunc(params: any) {
return req.list(API_URL_AGENT_LIST, params)
}
function searchFunc () { // 点击【查询】按钮点击事件
infoTable.value.refTable(true)
}
function tableExportFunc(){
alert('导出!')
}
function addFunc () { // 业务通用【新增】 函数
infoAddOrEdit.value.show()
}
function editFunc (recordId: any) { // 业务通用【修改】 函数
infoAddOrEdit.value.show(recordId)
}
function rateConfigFunc(recordId: any) {
alert('费率配置')
}
function detailFunc (recordId: any) { // 服务商详情页
infoDetail.value.show(recordId)
}
function codeManagementFunc (recordId: any) {
alert('码牌管理')
}
// 删除服务商
function delFunc (recordId: any) {
$infoBox.confirmDanger('确认删除?', '该操作将删除服务商下所有配置及用户信息', () => {
reqLoad.delById(API_URL_AGENT_LIST, recordId).then((res: any) => {
infoTable.value.refTable(true)
$infoBox.message.success('删除成功')
})
})
}
function showFeeConfigList (recordId) { // 费率配置
JeepayPaymentConfigDrawerRef.value.show(recordId)
}
</script>

View File

@@ -0,0 +1,253 @@
<template>
<a-drawer
v-model:visible="vdata.visible"
:title=" true ? '服务商详情' : '' "
:body-style="{ paddingBottom: '80px' }"
width="40%"
@close="onClose"
>
<a-row justify="space-between" type="flex">
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="服务商号">
{{ vdata.detailData['agentNo'] }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="服务商名称">
{{ vdata.detailData['agentName'] }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="登录名">
{{ vdata.detailData['loginUsername'] }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="服务商简称">
{{ vdata.detailData['agentShortName'] }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col v-if="vdata.detailData['type'] === 2" :sm="12">
<a-descriptions>
<a-descriptions-item label="渠道商号">
{{ vdata.detailData['isvNo'] }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col v-if="vdata.detailData['type'] === 2" :sm="12">
<a-descriptions>
<a-descriptions-item label="渠道商名称">
{{ vdata.isvName }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="联系人姓名">
{{ vdata.detailData['contactName'] }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="联系人手机号">
{{ vdata.detailData['contactTel'] }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="状态">
<a-tag :color="vdata.detailData['state'] === 1?'green':'volcano'">
{{ vdata.detailData['state'] === 0?'禁用':vdata.detailData['state'] === 1?'启用'
:vdata.detailData['state'] === 2?'待审核':vdata.detailData['state'] === 3?'审核驳回'
:vdata.detailData['state'] === 4?'未认证':'未知' }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="联系人邮箱">
{{ vdata.detailData['contactEmail'] }}
</a-descriptions-item>
</a-descriptions>
</a-col>
</a-row>
<a-row justify="start" type="flex">
<a-col :sm="24">
<a-form-item label="备注">
<a-input
v-model:value="vdata.detailData['remark']"
type="textarea"
disabled="disabled"
style="height: 50px"
/>
</a-form-item>
</a-col>
</a-row>
<a-row justify="space-between" type="flex">
<a-col :span="24">
<a-divider orientation="left">
<a-tag color="#FF4B33">
账户信息
</a-tag>
</a-divider>
</a-col>
</a-row>
<a-row justify="space-between" type="flex">
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="服务商类型">
{{ vdata.detailData['agentType'] == "1" ? '个人':'企业' }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="收款账户类型">
{{ vdata.detailData['settAccountType'] === "WX_CASH"? '个人微信':vdata.detailData['settAccountType'] === "ALIPAY_CASH"? '个人支付宝':vdata.detailData['settAccountType'] === "BANK_PUBLIC"? '对公账户':vdata.detailData['settAccountType'] === "BANK_PRIVATE"? '对私账户':'银行卡' }}
</a-descriptions-item>
</a-descriptions>
</a-col>
</a-row>
<a-row v-if="vdata.detailData['settAccountType'] === 'WX_CASH' || vdata.detailData['settAccountType'] === 'ALIPAY_CASH'" justify="space-between" type="flex">
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item :label="vdata.detailData['settAccountType'] === 'ALIPAY_CASH'?'支付宝账号':'个人微信号'">
{{ vdata.detailData['settAccountNo'] }}
</a-descriptions-item>
</a-descriptions>
</a-col>
</a-row>
<a-row v-if="vdata.detailData['settAccountType'] === 'BANK_PRIVATE'" justify="space-between" type="flex">
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="收款银行卡号">
{{ vdata.detailData['settAccountNo'] }}
</a-descriptions-item>
</a-descriptions>
</a-col>
</a-row>
<a-row v-if="vdata.detailData['settAccountType'] === 'BANK_PUBLIC'" justify="space-between" type="flex">
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="对公账户名称">
{{ vdata.detailData['settAccountName'] }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="对公账号">
{{ vdata.detailData['settAccountNo'] }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="开户银行名称">
{{ vdata.detailData['settAccountBank'] }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="开户行支行名称">
{{ vdata.detailData['settAccountSubBank'] }}
</a-descriptions-item>
</a-descriptions>
</a-col>
</a-row>
<a-col :sm="24">
<a-divider orientation="left">
<a-tag color="#FF4B33">
资料信息
</a-tag>
</a-divider>
</a-col>
<a-row justify="space-between" type="flex">
<a-col v-if="vdata.detailData['agentType'] == 2" :sm="12">
<a-form-item label="营业执照照片" name="licenseImg">
<JeepayUpload v-if="vdata.detailData['licenseImg']" v-model:src="vdata.detailData['licenseImg']" bizType="applyment" />
</a-form-item>
</a-col>
<a-col v-if="vdata.detailData['agentType'] == 2 && vdata.detailData['settAccountType'] == 'BANK_PUBLIC'" :sm="12">
<a-form-item label="开户许可证照片" name="permitImg">
<JeepayUpload v-if="vdata.detailData['permitImg']" v-model:src="vdata.detailData['permitImg']" bizType="applyment" />
</a-form-item>
</a-col>
<a-col :sm="12">
<a-form-item :label="vdata.detailData['agentType'] == 1?'[联系人]身份证人像面照片':'[法人]身份证人像面照片'" name="idcard1Img">
<JeepayUpload v-if="vdata.detailData['idcard1Img']" v-model:src="vdata.detailData['idcard1Img']" bizType="applyment" />
</a-form-item>
</a-col>
<a-col :sm="12">
<a-form-item :label="vdata.detailData['agentType'] == 1?'[联系人]身份证国徽面照片':'[法人]身份证国徽面照片'" name="idcard2Img">
<JeepayUpload v-if="vdata.detailData['idcard2Img']" v-model:src="vdata.detailData['idcard2Img']" bizType="applyment" />
</a-form-item>
</a-col>
<a-col :sm="12">
<a-form-item label="[联系人]手持身份证照片" name="idcardInHandImg">
<JeepayUpload v-if="vdata.detailData['idcardInHandImg']" v-model:src="vdata.detailData['idcardInHandImg']" bizType="applyment" />
</a-form-item>
</a-col>
<a-col v-if="vdata.detailData['settAccountType'] == 'BANK_PRIVATE'" :sm="12">
<a-form-item label="银行卡照片" name="bankCardImg">
<JeepayUpload v-if="vdata.detailData['bankCardImg']" v-model:src="vdata.detailData['bankCardImg']" bizType="applyment" />
</a-form-item>
</a-col>
</a-row>
</a-drawer>
</template>
<script lang="ts" setup>
import { API_URL_AGENT_LIST, API_URL_ISV_LIST, req } from '@/api/manage'
import {defineProps,reactive} from 'vue'
const props = defineProps({
callbackFunc: { type: Function,default:null }
})
const vdata = reactive({
btnLoading: false,
detailData: {}, // 数据对象
recordId: null, // 更新对象ID
visible: false, // 是否显示弹层/抽屉
isvList: [], // 渠道商下拉列表
isvName: '' // 渠道商名称
})
function show (recordId) { // 弹层打开事件
vdata.detailData = { 'state': 1, 'type': 1 } // 数据清空
vdata.recordId = recordId
req.getById(API_URL_AGENT_LIST, recordId).then(res => {
vdata.detailData = res
})
req.list(API_URL_ISV_LIST, { 'pageSize': null }).then(res => { // 渠道商下拉选择列表
vdata.isvList = res.records
for (let i = 0; i < vdata.isvList.length; i++) {
if (vdata.detailData['isvNo'] === vdata.isvList[i]['isvNo']) {
vdata.isvName = vdata.isvList[i]['isvName']
}
}
})
vdata.visible = true
}
function onClose () {
vdata.visible = false
}
defineExpose({
show //抛出show函数给父组件
})
</script>

View File

@@ -0,0 +1,86 @@
<template>
<div style="background: #fff">
<a-tabs>
<a-tab-pane key="securityConfig" tab="安全管理">
<div v-if="vdata.groupKey == 'securityConfig'">
<a-form ref="sipwFormModel" :model="vdata.sipwObject" :label-col="{span: 3}" :wrapper-col="{span: 6}" :rules="sipwRules">
<a-form-item v-if="flag" label="原支付密码:" name="originalPwd">
<a-input-password v-model:value="vdata.sipwObject.originalPwd" maxlength="6" autocomplete="new-password" placeholder="请输入原密码" />
</a-form-item>
<a-form-item label="新支付密码:" name="newPwd">
<a-input-password v-model:value="vdata.sipwObject.newPwd" maxlength="6" autocomplete="new-password" placeholder="请输入新密码" />
</a-form-item>
<a-form-item label="确认新支付密码:" name="confirmPwd">
<a-input-password v-model:value="vdata.sipwObject.confirmPwd" maxlength="6" autocomplete="new-password" placeholder="确认新密码" />
</a-form-item>
<a-form-item class="bottom-btn">
<a-button :disabled="!$access('ENT_AGENT_CONFIG_EDIT')" type="primary" :loading="vdata.btnLoading" @click="confirmUpdateSipw"><check-circle-outlined />确认更改</a-button>
</a-form-item>
</a-form>
</div>
</a-tab-pane>
</a-tabs>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, getCurrentInstance } from 'vue'
import { $updateAgentSipw, $isSipw } from '@/api/manage'
onMounted(()=>{
isSipw()
})
const flag = ref(true)
const isSipw = ()=>{
$isSipw().then(res=>{
flag.value = res
})
}
const { $infoBox,$access } = getCurrentInstance()!.appContext.config.globalProperties
const sipwFormModel = ref()
const sipwRules = {
originalPwd: [{ required: true, pattern: /^\d{6}$/, message: '请输入原密码(6位数字格式)', trigger: 'blur' }],
newPwd: [{ required: true, pattern: /^\d{6}$/, message: '请输入新支付密码(6位数字格式)', trigger: 'blur' }],
confirmPwd: [
{ required: true, trigger: 'blur', validator: (rule, value) => {
if(vdata.sipwObject.newPwd != value){
return Promise.reject('新密码与确认新密码不一致')
}
return Promise.resolve()
},
}
],
}
const vdata : any = reactive ({
btnLoading: false,
groupKey: 'securityConfig',
sipwObject:{ } // 支付密码保存对象
})
// 更新支付密码
function confirmUpdateSipw(){
sipwFormModel.value.validate().then(() => {
return $updateAgentSipw(vdata.sipwObject.originalPwd, vdata.sipwObject.confirmPwd)
}).then((res) => {
$infoBox.message.success('更新成功')
})
}
</script>
<style lang="less" scoped>
.bottom-btn{
/deep/ div{
display: flex;
justify-content: center;
}
}
.typePopover {
position: absolute;
top: -30px;
left: 93px;
}
</style>

View File

@@ -0,0 +1,187 @@
<template>
<a-modal
v-model:visible="data.visible"
title="修改头像"
:mask-closable="false"
:confirm-loading="data.confirmLoading"
:width="800"
:footer="null"
@cancel="cancelHandel"
>
<a-row>
<a-col :xs="24" :md="12" :style="{height: '350px'}">
<vue-cropper
ref="cropper"
:img="data.options.img"
:info="true"
:auto-crop="data.options.autoCrop"
:auto-crop-width="data.options.autoCropWidth"
:auto-crop-height="data.options.autoCropHeight"
:fixed-box="data.options.fixedBox"
@realTime="realTime"
/>
</a-col>
<a-col :xs="24" :md="12" :style="{height: '350px'}">
<div class="avatar-upload-preview">
<img :src="data.previews.url" :style="data.previews.img">
</div>
</a-col>
</a-row>
<br>
<a-row>
<a-col :lg="2" :md="2">
<a-upload name="file" :before-upload="beforeUpload" :show-upload-list="false">
<a-button>
<upload-outlined />
选择图片
</a-button>
</a-upload>
</a-col>
<a-col :lg="{span: 1, offset: 2}" :md="2">
<a-button icon="plus" @click="changeScale(1)" />
</a-col>
<a-col :lg="{span: 1, offset: 1}" :md="2">
<a-button icon="minus" @click="changeScale(-1)" />
</a-col>
<a-col :lg="{span: 1, offset: 1}" :md="2">
<a-button icon="undo" @click="rotateLeft" />
</a-col>
<a-col :lg="{span: 1, offset: 1}" :md="2">
<a-button icon="redo" @click="rotateRight" />
</a-col>
<a-col :lg="{span: 2, offset: 6}" :md="2">
<a-button type="primary" @click="finish('blob')">
保存
</a-button>
</a-col>
</a-row>
</a-modal>
</template>
<script lang="ts" setup>
import { upload, $updateUserInfo } from '@/api/manage'
import {reactive,ref} from 'vue'
import {message} from 'ant-design-vue'
import {useUserStore} from '@/store/modules/user'
const emit = defineEmits(['ok'])
const cropper = ref()
const data =reactive({
recordId: useUserStore().userInfo.sysUserId, // 拿到ID
userLoad: upload.avatar, // 图片上传地址
visible: false,
id: null,
confirmLoading: false,
fileList: [],
uploading: false,
options: {
// img: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
img:'' as any ,
autoCrop: true,
autoCropWidth: 200,
autoCropHeight: 200,
fixedBox: true
},
previews: {} as any
})
defineExpose({edit,okHandel})
function edit (id) {
data.visible = true
data.id = id
/* 获取原始头像 */
}
function close () {
data.id = null
data.visible = false
}
function cancelHandel () {
close()
}
function changeScale (num) {
num = num || 1
cropper.value.changeScale(num)
}
function rotateLeft () {
cropper.value.rotateLeft()
}
function rotateRight () {
cropper.value.rotateRight()
}
function beforeUpload (file) {
const reader = new FileReader()
// 把Array Buffer转化为blob 如果是base64不需要
// 转化为base64
reader.readAsDataURL(file)
reader.onload = () => {
data.options.img = reader.result
}
// 转化为blob
// reader.readAsArrayBuffer(file)
return false
}
// 上传图片(点击上传按钮)
function finish (type) {
console.log('finish')
// 创建一个空对象
const reqData:any = {}
// 输出
if (type === 'blob') {
cropper.value.getCropBlob((data) => {
// 通过该方法可以获取当前文件的一个内存URL
const img = window.URL.createObjectURL(data)
data.model = true
data.modelSrc = img
reqData.avatarUrl = img
// this.$http.post('https://www.mocky.io/v2/5cc8019d300000980a055e76', reqData, { contentType: false, processData: false, headers: { 'Content-Type': 'application/x-www-form-urlencoded' } })
$updateUserInfo(reqData).then((response) => {
message.success('上传成功')
emit('ok', response.url)
data.visible = false
}).catch(err => {
})
console.log(data.userLoad, data.recordId, data.modelSrc)
})
} else {
cropper.value.getCropData((data) => {
data.model = true
data.modelSrc = data
})
}
}
function okHandel () {
data.confirmLoading = true
setTimeout(() => {
data.confirmLoading = false
close()
message.success('上传头像成功')
}, 2000)
}
function realTime (data) {
data.previews = data
}
</script>
<style lang="less" scoped>
.avatar-upload-preview {
position: absolute;
top: 50%;
transform: translate(50%, -50%);
width: 180px;
height: 180px;
border-radius: 50%;
box-shadow: 0 0 4px #ccc;
overflow: hidden;
img {
width: 100%;
height: 100%;
}
}
</style>

View File

@@ -0,0 +1,741 @@
<template>
<div :key="data.updateKey" style="background: #fff;border-radius:10px">
<a-tabs v-model:activeKey="data.tableKey" @change="selectTabs">
<a-tab-pane key="1" :disabled="data.isPwdExpired || data.isAudit" tab="基本信息">
<div class="account-settings-info-view">
<a-row :gutter="16">
<a-col :md="16" :lg="16">
<a-form ref="infoFormModel" :model="data.saveObject" :label-col="{span: 9}" :wrapper-col="{span: 10}" :rules="data.rules">
<a-form-item label="用户登录名:">
<a-input v-model:value="data.saveObject.loginUsername" disabled />
</a-form-item>
<a-form-item label="用户姓名:" name="realname">
<a-input v-model:value="data.saveObject.realname" />
</a-form-item>
<a-form-item label="手机号:" name="telphone">
<a-input v-model:value="data.saveObject.telphone" disabled />
</a-form-item>
<a-form-item label="请选择性别:">
<a-radio-group v-model:value="data.saveObject.sex">
<a-radio :value="1">
</a-radio>
<a-radio :value="2">
</a-radio>
</a-radio-group>
</a-form-item>
</a-form>
<a-form-item class="bottom-btn">
<a-button type="primary" :loading="data.btnLoading" @click="changeInfo">
<check-circle-outlined />
更新基本信息
</a-button>
</a-form-item>
</a-col>
<a-col :md="8" :lg="8" :style="{ minHeight: '180px',margin:'0 auto' }">
<!-- 原始的头像上传带有图片裁剪功能 -->
<!-- <div class="ant-upload-preview" @click="$refs.modal.edit(1)" > -->
<div class="ant-upload-preview">
<!-- <a-icon type="cloud-upload-o" class="upload-icon"/> -->
<!-- <div class="mask">
<a-icon type="plus" />
</div> -->
<img
:src="data.saveObject.avatarUrl"
style="border: 1px solid rgba(0,0,0,0.08)"
>
<JeepayUpload
v-model:src="data.saveObject.avatarUrl"
bizType="avatar"
style="margin-top:10px"
:showUploadList="false"
/>
</div>
</a-col>
</a-row>
<!-- 图片裁剪组件 <avatar-modal ref="modal" @ok="setavatar"/> -->
</div>
</a-tab-pane>
<a-tab-pane key="2" tab="安全信息" :disabled="data.isAudit">
<a-tabs v-model:activeKey="data.tableKey2" tab-position="left" @change="selectTabsSub">
<a-tab-pane key="password" tab="修改密码">
<div class="account-settings-info-view">
<a-row :gutter="16">
<a-col :md="16" :lg="16">
<a-form ref="pwdFormModel" :model="data.updateObject" :label-col="{span: 9}" :wrapper-col="{span: 10}" :rules="data.rulesPass">
<a-form-item label="原密码:" name="originalPwd">
<a-input-password v-model:value="data.updateObject.originalPwd" autocomplete="new-password" placeholder="请输入原密码" />
</a-form-item>
<a-form-item label="新密码:" name="newPwd">
<a-input-password v-model:value="data.updateObject.newPwd" autocomplete="new-password" placeholder="请输入新密码" />
</a-form-item>
<a-form-item label="确认新密码:" name="confirmPwd">
<a-input-password v-model:value="data.updateObject.confirmPwd" autocomplete="new-password" placeholder="确认新密码" />
</a-form-item>
</a-form>
<a-form-item class="bottom-btn">
<a-button type="primary" :loading="data.btnLoading" @click="confirm">
<check-circle-outlined />
更新密码
</a-button>
</a-form-item>
</a-col>
</a-row>
</div>
</a-tab-pane>
<a-tab-pane key="mfaVer" tab="MFA认证" :disabled="data.isPwdExpired || data.isAudit">
<div class="account-settings-info-view">
<a-row v-if="data.mfaInfo.mfaBindState == '0'" :gutter="12" justify="start">
<a-col :push="2">
<QrcodeVue :value="data.mfaInfo.mfaBindUrl" :size="200" />
</a-col>
<a-col :push="3">
<a-alert message="" type="warning">
<template #description>
1.请使用MFA工具
<a-popover placement="bottom">
<template #content>
<p>推荐使用以下工具</p>
<p>1. 阿里云或腾讯云手机客户端</p>
<p>2. 微信小程序二次验证码</p>
</template>
<question-circle-outlined />
</a-popover>
扫描左侧二维码进行绑定<br><br>
2.扫描完成后输入工具内对应的验证码<br><br>
不能扫码
<a-popover placement="bottom">
<template #content>
<p>输入以下账号秘钥进行绑定</p>
<p>账号{{ data.saveObject.telphone }}</p>
<p>秘钥{{ data.mfaInfo.mfaSecretKey }}</p>
</template>
<question-circle-outlined />
</a-popover>
</template>
</a-alert>
</a-col>
</a-row>
<a-row v-else>
<a-col :push="2">
<a-alert message="" type="success">
<template #description>
已完成MFA认证
</template>
</a-alert>
</a-col>
</a-row>
<a-row :gutter="12" justify="start" type="flex" style="margin-top: 30px;">
<a-col :md="6" :lg="6">
<a-form ref="mfaFormModel" :model="data.mfaInfo" :rules="data.rulesCode">
<a-form-item label="验证码" name="verCode">
<a-input v-model:value="data.mfaInfo.verCode" placeholder="请输入验证码" />
</a-form-item>
</a-form>
<a-form-item class="bottom-btn">
<a-button v-if="data.mfaInfo.mfaBindState=='0'" type="primary" :loading="data.btnLoading" @click="mfaBindConfirm">
<check-circle-outlined />
确认绑定
</a-button>
<a-button v-else type="primary" danger :loading="data.btnLoading" @click="mfaRelieveConfirm">
<check-circle-outlined />
确认解绑
</a-button>
</a-form-item>
</a-col>
</a-row>
</div>
</a-tab-pane>
<a-tab-pane key="msg" tab="预留信息" :disabled="data.isPwdExpired || data.isAudit">
<div class="account-settings-info-view">
<a-row :gutter="16">
<a-col :md="16" :lg="16">
<a-form :label-col="{span: 9}" :wrapper-col="{span: 10}">
<a-form-item label="预留信息:" name="">
<a-input v-model:value="data.safeWord" placeholder="请输入新的预留信息" show-count :maxlength="10" />
</a-form-item>
</a-form>
<a-form-item class="bottom-btn" style="margin-bottom: 90px;">
<a-button type="primary" :loading="data.btnLoading" @click="safeWordUpdate">
<check-circle-outlined />
确认更新
</a-button>
</a-form-item>
</a-col>
</a-row>
</div>
</a-tab-pane>
</a-tabs>
</a-tab-pane>
<a-tab-pane key="3" :disabled="data.isPwdExpired" tab="基本资料">
<a-row :gutter="16" style="margin-left: 10%;">
<a-col :md="16" :lg="16">
<a-form ref="auditFormModel" :model="data.agentInfo" layout="vertical" :rules="data.auditRules">
<div v-if="data.agentInfo.infoState !== 1">
<a-alert v-if="data.agentInfo.state == 4" :message="'请修改默认信息并填写对应资料,提交平台审核。'" type="info" />
<a-alert v-if="data.agentInfo.state == 3" :message="!data.agentInfo.auditRemark?'驳回原因:无':'驳回原因:' + data.agentInfo.auditRemark" type="error" />
<a-alert v-if="data.agentInfo.state == 2" message="请等待平台审核......" type="warning" />
<a-alert v-if="data.agentInfo.state == 1" message="资料已通过审核,请重新登录。" type="success" />
</div>
<a-col :offset="1"><a-divider orientation="left" style="color: #1A66FF;">基本名称</a-divider></a-col>
<a-row justify="space-between" type="flex">
<a-col :span="10">
<a-form-item label="服务商类型" name="agentType">
<a-select v-model:value="data.agentInfo['agentType']" defaultValue="1">
<a-select-option :value="1">个人</a-select-option>
<a-select-option :value="2">企业</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-row justify="space-between" type="flex">
<a-col :span="10">
<a-form-item :label="data.agentInfo['agentType'] == 2? '企业全称':'服务商全称'" name="agentName">
<a-input v-model:value="data.agentInfo['agentName']" />
<span v-if="data.agentInfo['agentType'] == 2" class="jeepay-tip-text">请输入营业执照名称</span>
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item :label="data.agentInfo['agentType'] == 2? '企业简称':'服务商简称'" name="agentShortName">
<a-input v-model:value="data.agentInfo['agentShortName']" />
</a-form-item>
</a-col>
</a-row>
<a-row justify="space-between" type="flex">
<a-col :span="10">
<a-form-item label="联系人姓名" name="contactName">
<a-input v-model:value="data.agentInfo['contactName']" />
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="联系人邮箱" name="contactEmail">
<a-input v-model:value="data.agentInfo['contactEmail']" />
</a-form-item>
</a-col>
</a-row>
<a-col :offset="1"><a-divider orientation="left" style="color: #1A66FF;">账户资料</a-divider></a-col>
<a-row justify="space-between" type="flex">
<a-col :span="10">
<a-form-item label="收款账户类型" name="settAccountType">
<a-select v-model:value="data.agentInfo['settAccountType']" placeholder="请选择账户类型">
<a-select-option value="ALIPAY_CASH">个人支付宝</a-select-option>
<a-select-option value="BANK_PRIVATE">对私账户</a-select-option>
<a-select-option v-if="data.agentInfo['agentType'] !== 1" value="BANK_PUBLIC">对公账户</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-row v-if="data.agentInfo['settAccountType'] === 'ALIPAY_CASH'" justify="space-between" type="flex">
<a-col :span="10">
<a-form-item label="账户姓名" name="settAccountName">
<a-input
v-model:value="data.agentInfo['settAccountName']"
/>
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="支付宝账号" name="settAccountNo">
<a-input
v-model:value="data.agentInfo['settAccountNo']"
placeholder="请输入账号"
/>
</a-form-item>
</a-col>
</a-row>
<a-row v-if="data.agentInfo['settAccountType'] === 'BANK_PRIVATE'" justify="space-between" type="flex">
<a-col :span="10">
<a-form-item label="账户姓名" name="settAccountName">
<a-input
v-model:value="data.agentInfo['settAccountName']"
/>
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="开户银行名称" name="settAccountBank">
<a-input
v-model:value="data.agentInfo['settAccountBank']"
placeholder="请输入银行名称"
/>
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="收款银行卡号" name="settAccountNo">
<a-input
v-model:value="data.agentInfo['settAccountNo']"
placeholder="请输入银行卡号"
/>
</a-form-item>
</a-col>
</a-row>
<a-row v-if="data.agentInfo['settAccountType'] === 'BANK_PUBLIC'" justify="space-between" type="flex">
<a-col :span="10">
<a-form-item label="对公账户名称" name="settAccountName">
<a-input
v-model:value="data.agentInfo['settAccountName']"
placeholder="请输入账户名"
/>
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="对公账号" name="settAccountNo">
<a-input
v-model:value="data.agentInfo['settAccountNo']"
placeholder="请输入银行卡号"
/>
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="开户银行名称" name="settAccountBank">
<a-input
v-model:value="data.agentInfo['settAccountBank']"
placeholder="请输入银行名称"
/>
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="开户行支行名称" name="settAccountSubBank">
<a-input
v-model:value="data.agentInfo['settAccountSubBank']"
placeholder="请输入开户行支行名称"
/>
</a-form-item>
</a-col>
</a-row>
<a-row justify="space-between" type="flex">
<a-col v-if="data.agentInfo['settAccountType'] === 'BANK_PRIVATE'" :span="10">
<a-form-item label="银行卡照片" name="bankCardImg">
<JeepayUpload v-model:src="data.agentInfo['bankCardImg']" bizType="applyment" />
</a-form-item>
</a-col>
<a-col v-if="data.agentInfo['agentType'] !== 1 && data.agentInfo['settAccountType'] === 'BANK_PUBLIC'" :span="10">
<a-form-item label="开户许可证照片" name="permitImg">
<JeepayUpload v-model:src="data.agentInfo['permitImg']" bizType="applyment" />
</a-form-item>
</a-col>
</a-row>
<a-col :offset="1"><a-divider orientation="left" style="color: #1A66FF;">照片资料</a-divider></a-col>
<a-row justify="space-between" type="flex">
<a-col :span="10">
<a-form-item :label="data.agentInfo['agentType'] == 1?'[联系人]身份证人像面照片':'[法人]身份证人像面照片'" name="idcard1Img">
<JeepayUpload v-model:src="data.agentInfo['idcard1Img']" bizType="applyment" />
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item :label="data.agentInfo['agentType'] == 1?'[联系人]身份证国徽面照片':'[法人]身份证国徽面照片'" name="idcard2Img">
<JeepayUpload v-model:src="data.agentInfo['idcard2Img']" bizType="applyment" />
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="[联系人]手持承诺函照片" name="idcardInHandImg">
<JeepayUpload v-model:src="data.agentInfo['idcardInHandImg']" bizType="applyment" />
</a-form-item>
</a-col>
<a-col :span="10">
<a-row justify="space-between" type="flex">
<a-col :span="10">
<a-form-item label="手持承诺函照示例:" name="">
<a-image style="width: 100px;height: 120px;" src="https://jeepaypublic.oss-cn-beijing.aliyuncs.com/oem/b1cd1646-fdd2-4ea7-8f1f-02a55669f534.svg" />
</a-form-item>
</a-col>
<a-col :span="10">
<span><a :href="data.promiseFile">模板下载</a></span>
</a-col>
</a-row>
</a-col>
<a-col v-if="data.agentInfo['agentType'] !== 1" :span="10">
<a-form-item label="营业执照照片" name="licenseImg">
<JeepayUpload v-model:src="data.agentInfo['licenseImg']" bizType="applyment" />
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-col>
</a-row>
<a-form-item v-if="data.agentInfo.state != 1 && data.agentInfo.state != 2" class="bottom-btn">
<a-button type="primary" :loading="data.btnLoading" @click="auditInfo">
<check-circle-outlined />
提交审核
</a-button>
</a-form-item>
</a-tab-pane>
</a-tabs>
</div>
</template>
<script lang="ts" setup>
import { $getUserInfo, $updateUserInfo, $updateUserPass, $getMFAInfo, $mfaBind, $mfaRelieve, upload, $getPasswordRules, $getMainUserInfo, $auditInfo } from '@/api/manage'
import QrcodeVue from 'qrcode.vue'
import ruleGenerator from '@/utils/ruleGenerator'
// import store from '@/store'
import {useUserStore} from '@/store/modules/user'
import { Base64 } from 'js-base64'
import {ref, reactive,onMounted,getCurrentInstance,toRaw} from 'vue'
import {message} from 'ant-design-vue'
import { getCurrentUserInfo } from '@/api/login'
import { useRoute } from 'vue-router'
const infoFormModel = ref()
const pwdFormModel = ref()
const mfaFormModel = ref()
const auditFormModel = ref()
const { $infoBox } = getCurrentInstance()!.appContext.config.globalProperties
const tabKey = ref()
const data = reactive({
action: upload.avatar, // 上传图标地址
btnLoading: false,
groupKey: 'password',
passwordRules: /^$/, //密码规则
passwordRulesText: '', //密码规则提示文字
safeWord:'',//预留信息
saveObject: {
loginUsername: '', // 登录名
realname: '', // 真实姓名
telphone: '',
sex: '',
avatarUrl: '' // 用户头像
},
mfaInfo: {
mfaBindUrl: '',
mfaBindState: '',
mfaSecretKey: '',
verCode: ''
},
updateKey:0, //刷新键
confirmLoading:false,
// avatarUrl: useUserStore().userInfo.avatarImgPath,
updateObject: {
originalPwd: '', // 原密码
newPwd: '', // 新密码
confirmPwd: '' // 确认密码
} as any,
recordId: useUserStore().userInfo.sysUserId, // 拿到ID
rules: {
realname: [{ required: true, message: '请输入真实姓名', trigger: 'blur' }]
},
rulesCode: {
verCode: [{ required: true, message: '请输入验证码', trigger: 'blur' }],
},
rulesPass: {
originalPwd: [{ required: true, message: '请输入原密码', trigger: 'blur' }],
newPwd: [
{ required: false, trigger: 'blur' },{
validator: (rule, value) => {
if (!data.passwordRules.test(data.updateObject.newPwd)) {
return Promise.reject(data.passwordRulesText)
} else if(data.updateObject.newPwd !== data.updateObject.confirmPwd) {
return Promise.reject('新密码与确认新密码不一致')
} else return Promise.resolve()
}
}
],
confirmPwd: [{ required: false, trigger: 'blur' }, {
validator: (rule, value) => {
if (!data.passwordRules.test(data.updateObject.confirmPwd)) {
return Promise.reject(data.passwordRulesText)
} else if(data.updateObject.newPwd !== data.updateObject.confirmPwd) {
return Promise.reject('新密码与确认新密码不一致')
} else return Promise.resolve()
}
}]
},
tableKey: '1',
tableKey2:'password',//第二个tabKey
isPwdExpired: false, //是否强制改密码
isAudit: false, // 是否强制审核
auditRules: {
agentName: [{ required: true, message: '请输入服务商全称', trigger: 'blur' }],
agentShortName: [{ required: true, message: '请输入服务商简称', trigger: 'blur' }],
contactName: [{ required: true, message: '请输入联系人姓名', trigger: 'blur' }],
contactTel: [{ required: true, message: '请输入联系人手机号', trigger: 'blur' }],
// contactEmail: [{ required: true, message: '请输入联系人邮箱', trigger: 'blur' }],
licenseImg: [ ruleGenerator.requiredUpload('营业执照照片') ],
permitImg: [ ruleGenerator.requiredUpload('开户许可证照片') ],
idcard1Img: [ ruleGenerator.requiredUpload('身份证人像面照片') ],
idcard2Img: [ ruleGenerator.requiredUpload('身份证国徽面照片') ],
idcardInHandImg: [ ruleGenerator.requiredUpload('手持身份证照片') ],
bankCardImg: [ ruleGenerator.requiredUpload('银行卡照片') ],
agentType: [ ruleGenerator.requiredSelect('服务商类型', 'number') ],
settAccountType: [ ruleGenerator.requiredSelect('收款账户类型') ],
settAccountNo: [{ required: true, message: '请输入个人账号/银行卡号', trigger: 'blur' }],
settAccountName: [{ required: true, message: '请输入账户名', trigger: 'blur' }],
settAccountBank: [{ required: true, message: '请输入银行名称', trigger: 'blur' }],
// settAccountSubBank: [{ required: true, message: '请输入开户行支行名称', trigger: 'blur' }],
},
agentInfo: {
agentName: '',
agentShortName: '',
contactName: '',
contactTel: '',
contactEmail: '',
licenseImg: '',
idcardImg: '',
idcardInHandImg: '',
bankCardImg: '',
auditRemark: '',
infoState: '' as any,
state: '' as any,
},
previewVisible: false,
previewTitle: '',
previewImage: '',
promiseFile: ''
})
onMounted(()=>{
if(useRoute().query.isPwdExpired == '1'){
data.isPwdExpired = true
data.tableKey = '2'
}
if(useRoute().query.isAudit === '1'){
data.isAudit = true
data.tableKey = '3'
}
detail()
getPasswordRules()
})
function detail () { // 获取基本信息
$getUserInfo().then(res => {
data.saveObject = res
data.safeWord = res.safeWord
})
getAgentInfo()
}
function getPasswordRules () { // 获取密码规则
$getPasswordRules().then(res => {
data.passwordRules = new RegExp(res.regexpRules)
data.passwordRulesText = res.errTips
})
}
function getAgentInfo() {
// 获取服务商信息
$getMainUserInfo().then((res) => {
data.agentInfo = res
data.promiseFile = res.promiseFile
})
}
function auditInfo () { // 更新基本信息事件
auditFormModel.value.validate().then(valid =>{
$infoBox.confirmPrimary('提交前请仔细核对资料信息,认证通过后将不可更改。', '', () => {
data.btnLoading = true // 打开按钮上的 loading
// 请求接口
$auditInfo(toRaw(data.agentInfo)).then(res => {
data.agentInfo.state = '2'
data.btnLoading = false // 关闭按钮刷新
}).then(bizData => {
message.success('提交成功')
}).catch(res => {
data.btnLoading = false
message.error('提交失败')
})
})
})
}
function changeInfo () { // 提交审核
infoFormModel.value.validate().then(valid =>{
$infoBox.confirmPrimary('确认更新信息吗?', '', () => {
data.btnLoading = true // 打开按钮上的 loading
// that.$store.commit('showLoading') // 关闭全局刷新
// 请求接口
console.log(toRaw(data.saveObject))
$updateUserInfo(toRaw(data.saveObject)).then(res => {
data.btnLoading = false // 关闭按钮刷新
//$store.commit('hideLoading') // 关闭全局刷新
return getCurrentUserInfo()
}).then(bizData => {
bizData.avatarUrl = data.saveObject.avatarUrl
bizData.realname = data.saveObject.realname
useUserStore().refUserInfo() // 刷新用户信息
// store.commit('SET_USER_INFO', bizData) // 调用vuex设置用户基本信息
message.success('修改成功')
}).catch(res => {
// that.$store.commit('hideLoading') // 关闭全局刷新
data.btnLoading = false
console.log(res)
message.error('修改失败')
})
})
})
}
function confirm (e) { // 确认更新密码
pwdFormModel.value.validate().then(valid=>{
$infoBox.confirmPrimary('确认更新密码吗?', '', () => {
// 请求接口
data.btnLoading = true // 打开按钮上的 loading
data.confirmLoading = true // 显示loading
data.updateObject.recordId = data.recordId // 用户ID
const copyUpdateObject = JSON.parse(JSON.stringify(data.updateObject))
copyUpdateObject.originalPwd = Base64.encode(copyUpdateObject.originalPwd)
copyUpdateObject.confirmPwd = Base64.encode(copyUpdateObject.confirmPwd)
$updateUserPass(copyUpdateObject).then(res => {
message.success('修改成功')
// 退出登录
useUserStore().logout()
}).catch(res => {
data.confirmLoading = false
data.btnLoading = false
})
})
})
}
function selectTabs () { // 清空必填提示
data.updateObject.originalPwd = ''
data.updateObject.newPwd = ''
data.updateObject.confirmPwd = ''
}
// 上传文件成功回调方法参数value为文件地址name是自定义参数
function uploadSuccess (value, name) {
data.saveObject.avatarUrl = value
console.log(data.saveObject.avatarUrl)
data.updateKey += 1
}
// MFA绑定
function mfaBindConfirm() {
mfaFormModel.value.validate().then(valid =>{
$infoBox.confirmPrimary('确认绑定MFA认证吗', '', () => {
// 请求接口
data.btnLoading = true // 打开按钮上的 loading
data.confirmLoading = true // 显示loading
$mfaBind({verCode: data.mfaInfo.verCode}).then(res => {
data.confirmLoading = false
data.btnLoading = false
getMFAInfo()
message.success('绑定成功')
// 退出登录
// useUserStore().logout()
}).catch(res => {
data.confirmLoading = false
data.btnLoading = false
})
})
})
}
function selectTabsSub (key) { // 清空必填提示
console.log(key)
if (key) {
data.groupKey = key
detail()
}
// 获取MFA信息
if(data.groupKey == 'mfaVer') {
getMFAInfo()
}
}
function getMFAInfo() {
$getMFAInfo().then(res => {
data.mfaInfo = res
})
}
// MFA解绑
function mfaRelieveConfirm() {
mfaFormModel.value.validate().then(valid =>{
$infoBox.confirmPrimary('确认解绑MFA认证吗', '', () => {
// 请求接口
data.btnLoading = true // 打开按钮上的 loading
data.confirmLoading = true // 显示loading
$mfaRelieve({verCode: data.mfaInfo.verCode}).then(res => {
data.confirmLoading = false
data.btnLoading = false
getMFAInfo()
message.success('解绑成功')
// 退出登录
// useUserStore().logout()
}).catch(res => {
data.confirmLoading = false
data.btnLoading = false
})
})
})
}
//更新预留信息
function safeWordUpdate(){
if(!data.safeWord){
return message.error('信息内容不可为空')
}
$updateUserInfo({safeWord:data.safeWord}).then(res =>{
message.success('更新成功')
})
}
</script>
<style lang="less" scoped>
.avatar-upload-wrapper {
height: 200px;
width: 100%;
}
.bottom-btn{
/deep/ div{
display: flex;
justify-content: center;
}
}
.ant-upload-preview {
display: block !important;
text-align:center ;
position: relative;
margin: 0 auto;
width: 100%;
// max-width: 180px;
border-radius: 50%;
// box-shadow: 0 0 4px #ccc;
.upload-icon {
position: absolute;
top: 0;
right: 10px;
font-size: 1.4rem;
padding: 0.5rem;
background: rgba(222, 221, 221, 0.7);
border-radius: 50%;
border: 1px solid rgba(0, 0, 0, 0.2);
}
.mask {
opacity: 0;
position: absolute;
background: rgba(0,0,0,0.4);
cursor: pointer;
transition: opacity 0.4s;
&:hover {
opacity: 1;
}
i {
font-size: 2rem;
position: absolute;
top: 50%;
left: 50%;
margin-left: -1rem;
margin-top: -1rem;
color: #d6d6d6;
}
}
img, .mask {
width: 150px;
height: 150px;
border-radius: 50%;
overflow: hidden;
}
}
</style>

View File

@@ -0,0 +1,93 @@
<template>
<div class="card">
<a-list size="large" :data-source="vdata.noticeList" :pagination="pagination">
<template #renderItem="{ item }">
<a-list-item class="list-ltem" @click="showDrawer(item.articleId)">
<span>{{ item.title }}</span>
<a style="color: #707070;">{{ item.createdAt }} <right-outlined :style="{fontSize: '12px', color: '#707070',marginLeft:'10px'}" /> </a>
</a-list-item>
</template>
<template #header>
<div class="title">全部公告</div>
</template>
</a-list>
</div>
<JeepayNoticeViewer ref="noticeViewer" />
</template>
<script lang="ts" setup>
import { defineComponent,onMounted,reactive,ref } from 'vue'
import { API_URL_NOTICELIST, req } from '@/api/manage'
const vdata = reactive({
noticeList:[],
noticeDate:{} as any
})
const noticeViewer = ref()
const visible = ref<boolean>(false)
onMounted(() =>{
reqTableDataFunc({
pageSize:10,
pageNumber:1
}).then(res =>{
vdata.noticeList = res.records
pagination.total = res.total
})
})
function reqTableDataFunc(params: any) { // 请求table接口数据
return req.list(API_URL_NOTICELIST, Object.assign(params,{articleType:1}))
}
const showDrawer = (articleId) => {
noticeViewer.value.showDrawer(articleId)
}
const pagination = {
onChange: (page: number) => {
let params = {
pageSize:10,
pageNumber:page
}
reqTableDataFunc(params).then(res =>{
vdata.noticeList = res.records
pagination.total = res.total
})
},
pageSize: 10,
total:0
}
</script>
<style lang="less" scoped>
:deep(.ant-list-pagination){
margin-bottom: 10px;
margin-right: 20px;
}
.card{
background-color: #fff;
overflow: hidden;
border-radius: 12px;
.list-ltem{
padding-left: 30px;
font-weight: 400;
font-size: 13px;
color: #666;
height: 60px;
span{
&:nth-child(1){
cursor:pointer;
}
&:hover{
color: #2691FF;
}
}
}
.title{
font-weight: bold;
font-size: 14px;
color: #333;
padding-left: 30px;
}
}
</style>

View File

@@ -0,0 +1,740 @@
<template>
<div id="chart-card">
<div class="amount">
<div style="background-color: rgb(85, 85, 85)">
<a-skeleton :loading="skeletonIsShow" active :paragraph="{ rows: 6 }" />
<div
v-show="!skeletonIsShow"
v-if="$access('ENT_AGENT_MAIN_PAY_DAY_COUNT')"
class="amount-top"
>
<div class="amount-date">
<div
:class="{ 'amount-date-active': amountDate === 'today' }"
@click="amountDateHandle('today')"
>
今日交易
</div>
<div
:class="{ 'amount-date-active': amountDate === 'yesterday' }"
@click="amountDateHandle('yesterday')"
>
昨日交易
</div>
</div>
<p>成交金额()</p>
<a-tooltip placement="top" color="#fff">
<template #title>
<p style="font-size: 20px; margin-bottom: 0; color: #000">
{{ payAmountDataAll.payAmount }}
</p>
</template>
<p
style="
font-size: 40px;
margin-bottom: 35px;
color: #fff;
width: max-content;
"
>
{{ amountHandle(payAmountDataAll.payAmount)
}}<span
v-if="payAmountDataAll.payAmount >= 100000000"
style="
font-size: 20px;
font-weight: 600;
margin-left: 5px;
vertical-align: middle;
"
>亿</span
><span
v-else-if="payAmountDataAll.payAmount >= 10000000"
style="
font-size: 20px;
font-weight: 600;
margin-left: 5px;
vertical-align: middle;
"
></span
>
</p>
</a-tooltip>
<div class="amount-list">
<div>
<p>交易笔数</p>
<span>{{ payAmountDataAll.payCount }}</span>
</div>
<div>
<p>退款金额()</p>
<span>{{ payAmountDataAll.refundAmount }}</span>
</div>
<div>
<p>退款笔数</p>
<span>{{ payAmountDataAll.refundCount }}</span>
</div>
</div>
</div>
<div class="amount-line" />
<a-skeleton v-show="skeletonIsShow" active :paragraph="{ rows: 6 }" />
<div
v-show="!skeletonIsShow"
v-if="$access('ENT_AGENT_MAIN_PAY_TREND_COUNT')"
class="amount-bottom"
style="width: 100%"
>
<div class="echart-title">
<div style="display: flex">
<b style="color: #fff">趋势</b>
<a-tooltip class="tooltip">
<template #title>近期成交金额</template>
<i
class="bi bi-info-circle"
style="color: rgba(255, 255, 255, 0.6)"
/>
</a-tooltip>
</div>
<!-- -->
<a-select
v-model:value="amountSelectDate"
class="date amount-date"
placeholder="近30天"
@change="amountSelectHandle"
>
<a-select-option value="30">近30天</a-select-option>
<a-select-option value="7">近7天</a-select-option>
</a-select>
</div>
<div v-show="!payAmountEmpty" id="pay-amount" style="width: 100%" />
<a-empty v-show="payAmountEmpty" :image="emptyImg" />
</div>
</div>
</div>
<div class="personal">
<a-skeleton :loading="skeletonIsShow" active :paragraph="{ rows: 5 }" />
<div v-show="!skeletonIsShow">
<div class="before-msg">
<p> <span>预留信息</span >
<a style="color: #2691ff; margin-right: 5px" @click="setSafeWord" >{{ safeWord ? safeWord : "未设置" }}</a >
<a-tooltip placement="right" title="此信息为你在本站预留的个性信息,用以鉴别假冒、钓鱼网站。如未看到此信息,请立即停止访问并修改密码。如需修改内容请前往个人中心">
<question-circle-outlined />
</a-tooltip>
</p>
<p><span>服务商简称</span ><span>{{ shortName ? shortName : "" }}</span></p>
<p><span>服务商邀请码</span ><span>{{ inviteCode ? inviteCode : "" }} <copy-outlined style="color: #2691ff;" @click="getCopy()"/></span></p>
<p><span>拓展商户</span >
<QrcodeOutlined style="color: #2691ff;" @click="getVisibleAdd(0)" />
<copy-outlined style="color: #2691ff;padding-left: 5px" @click="getCopyCode(0)"/>
</p>
<p><span>拓展代理</span >
<QrcodeOutlined style="color: #2691ff;" @click="getVisibleAdd(1)" />
<copy-outlined style="color: #2691ff;padding-left: 5px" @click="getCopyCode(1)"/>
</p>
</div>
<div class="personal-line" />
<div class="latest-post">
<div class="title">
<span>最新公告</span>
<a style="color: #2691ff" @click="toList"
>更多
<right-outlined :style="{ fontSize: '12px', color: '#2691ff' }"
/></a>
</div>
<div class="post-list">
<div
v-for="(item, index) in notice.noticeList"
:key="index"
class="post-item"
@click="toDetail(item.articleId)"
>
<span class="title">{{ item.title }}</span>
<span
>{{ item ? item.createdAt.slice(0, 10) : "" }}
<right-outlined :style="{ fontSize: '12px', color: '#707070' }"
/></span>
</div>
</div>
</div>
<div class="personal-line" />
<div class="quick-start">
<p>快速开始</p>
<ul>
<li v-for="menu in quickMenuList" :key="menu.entId">
<router-link :to="menu!.menuUri">{{ menu.entName }}</router-link>
</li>
</ul>
</div>
</div>
</div>
<div class="method">
<a-skeleton :loading="skeletonIsShow" active :paragraph="{ rows: 5 }" />
<div
v-show="!skeletonIsShow"
v-if="$access('ENT_AGENT_MAIN_PAY_TYPE_COUNT')"
>
<div class="echart-title">
<b>支付方式</b>
<JeepayDateRangePicker
v-model:value="payMethodDate"
class="date"
customDateRangeType="date"
:allTimeIsShow="false"
@update:value="payMethodDateHandle"
/>
</div>
<div
v-show="!payMethodEmpty"
id="pay-method"
style="width: 100%; height: 100%"
/>
<a-empty v-show="payMethodEmpty" :image="emptyImg" />
</div>
</div>
<div class="pay-statistics">
<a-skeleton :loading="skeletonIsShow" active :paragraph="{ rows: 5 }" />
<div v-show="!skeletonIsShow" v-if="$access('ENT_AGENT_MAIN_PAY_COUNT')">
<b>交易统计</b>
<JeepayDateRangePicker
v-model:value="payStatisticsDate"
class="date"
customDateRangeType="date"
:statisticalTimeIsShow="true"
@update:value="statisticsHandle"
/>
<div
v-show="!payStatisticsEmpty"
id="pay-statistics"
style="width: 100%; height: 100%"
/>
<a-empty v-show="payStatisticsEmpty" :image="emptyImg" />
</div>
</div>
</div>
<a-modal
v-model:visible="vdata.visibleAdd"
:footer="null"
@cancel="handleCancel"
style="top: 30% !important;"
wrap-class-name="full-modal"
width="330px"
:title="vdata.title"
:maskClosable="false"
>
<div class="modal-body" style="height: 280px !important;">
<div style="text-align: center">
<img
v-if="vdata.inviteUrl"
:src='"https://api.pwmqr.com/qrcode/create/?url="+vdata.inviteUrl'
alt=""
style="width: 200px; height: 200px"
>
<div class="zfb-wx" style="margin: 10px 0">
<!-- <img src="/src/assets/svg/alipay.svg" alt="">-->
<!-- <img-->
<!-- src="/src/assets/svg/wechatpay.svg"-->
<!-- alt=""-->
<!-- style="margin: 0 5px"-->
<!-- >-->
<span style="color: grey">支持浏览器微信支付宝扫码</span>
</div>
<a-button
type="primary"
size="large"
block
@click="vdata.visibleAdd = false"
>
取消扫码
</a-button>
</div>
</div>
</a-modal>
<JeepayNoticeViewer ref="noticeViewer" />
</template>
<script lang="ts" setup>
import {
nextTick,
ref,
computed,
watch,
getCurrentInstance,
onMounted,
reactive,
} from "vue";
import { useRouter, useRoute } from "vue-router";
import * as echarts from "echarts"; // 引入图标组件
import { useUserStore } from "@/store/modules/user";
import {
$getPayTrendCount,
$getNumCount,
$getPayCount,
$getPayType,
$getUserInfo,
API_URL_NOTICELIST,
req,
} from "@/api/manage";
import {
pieEcharts,
statisticsEcharts,
amountEcharts,
} from "./echartsConfig.js"; // 图标配置项组件
import { Empty } from "ant-design-vue"; // 缺省图
const { $infoBox, $access } =
getCurrentInstance()!.appContext.config.globalProperties;
const userStore = useUserStore();
// 获取到路由对象
const router = useRouter(); //这是全部路由
const safeWord = ref(); //预留信息
const shortName = ref(); //服务商简称
const inviteCode = ref(); //服务商简称
const inviteCodeUrl = ref(); //服务商简称
const agtInviteCodeUrl = ref(); //服务商简称
const vdata = reactive({
title:"",
inviteUrl:"" as any,
visibleAdd: false,
qrCodeUrl:"" as any,
})
//公告列表页
const toList = () => {
router.push({
path: "/notices",
});
};
const notice = reactive({
noticeList: [] as any,
});
const noticeViewer = ref(); //公告详情
//设置信息
const setSafeWord = () => {
router.push({
path: "/currentUserinfo",
query: { type: "msg" },
});
};
// $data['url']='https://api.pwmqr.com/qrcode/create/?url='.urlencode($url);
// $data['downUrl']='https://api.pwmqr.com/qrcode/create/?url='.urlencode($url)."&down=1";
onMounted(() => {
$getUserInfo().then((res) => {
safeWord.value = res.safeWord;
shortName.value = res.shortName;
inviteCode.value = res.inviteCode;
agtInviteCodeUrl.value = res.agtInviteCodeUrl;
inviteCodeUrl.value = res.inviteCodeUrl;
});
reqTableDataFunc({
pageSize: 3,
pageNumber: 1,
}).then((res) => {
console.log(res);
notice.noticeList = res.records;
});
});
function reqTableDataFunc(params: any) {
// 请求table接口数据
return req.list(
API_URL_NOTICELIST,
Object.assign(params, { articleType: 1 })
);
}
function handleCancel(e) {
vdata.visibleAdd = false;
}
// 切换今日昨日金额数据
let amountDate = ref("today"); // 默认为今日
const amountDateHandle = (param) => {
amountDate.value = param;
numCountHandle(); // 切换后调用请求
};
let payAmountDataAll: any = ref({}); // 成交金额总数据(今天和昨天)
const payAmountGetDateData = (res) => {
//数据赋值
// 分转元显示
Object.keys(res.dayCount).forEach((item) => {
if (item == "payCount" || item == "refundCount") {
return;
} else {
res.dayCount[item] = (res.dayCount[item] / 100).toFixed(2);
}
});
payAmountDataAll.value = res.dayCount;
};
// 成交金额请求数据
const numCountHandle = () => {
$getNumCount({ queryDateRange: amountDate.value })
.then((res) => {
payAmountGetDateData(res);
})
.then((err) => {
console.log(err);
});
};
let payMethod; // 支付方式饼图
let payMethodEmpty = ref(false); // 支付方式饼图缺省图
let payMethodDate = ref("near2now_30"); // 饼图日期查询
// 当支付方式自定义日期格式中不包含N ,则代表选择了正确的日期格式
const payMethodDateHandle = () => {
let flag = payMethodDate.value.indexOf("N") === -1;
if (payMethodDate.value && flag) {
payMethodHandle(payMethodDate.value);
}
};
// 饼图数据赋值
const payMethodGetData = (res) => {
let payTypeData = [];
res[0].length === 0
? (payMethodEmpty.value = true)
: (payMethodEmpty.value = false);
res[0].forEach((item) => {
payTypeData.push({
value: (item.typeAmount / 100).toFixed(2), // 分转元
name: item.name,
} as never);
});
pieEcharts.series[0].data = payTypeData; // 饼图数据赋值
nextTick(() => {
payMethod = echarts.init(
document.getElementById("pay-method") as HTMLElement
);
payMethod.setOption(pieEcharts); // 数据填充
});
};
// 饼图请求数据
const payMethodHandle = (data) => {
$getPayType(data)
.then((res) => {
payMethodGetData(res);
})
.catch((err) => {
console.log(err);
});
};
let payStatistics; // 交易统计折线图
let payStatisticsDate = ref("near2now_30"); // 交易统计折线图日期查询
let payStatisticsEmpty = ref(false); // 交易统计折线图缺省图
const statisticsHandle = () => {
let flag = payStatisticsDate.value.indexOf("N") === -1;
if (payStatisticsDate.value && flag) {
payStatisticsHandle(payStatisticsDate.value);
}
};
const promise = new Promise((resolve, reject) => {});
// 交易统计折线图数据赋值
const payStatisticsGetData = (res) => {
Object.keys(res).length === 0
? (payStatisticsEmpty.value = true)
: (payStatisticsEmpty.value = false);
statisticsEcharts.xAxis.data = res.resDateArr; // x轴日期
let payAmountArr = [];
res.resPayAmountArr.forEach((item) =>
payAmountArr.push((item / 100).toFixed(2) as never)
); // 成交金额分转元
statisticsEcharts.series[0].data = payAmountArr; // 成交金额
statisticsEcharts.series[1].data = res.resPayCountArr; // 订单笔数
let refAmountArr = [];
res.resRefAmountArr.forEach((item) =>
refAmountArr.push((item / 100).toFixed(2) as never)
); // 退款金额分转元
statisticsEcharts.series[2].data = refAmountArr; // 退款金额
nextTick(() => {
payStatistics = echarts.init(
document.getElementById("pay-statistics") as HTMLElement
);
payStatistics.setOption(statisticsEcharts); // 数据填充
});
};
// 交易统计折线图请求数据
const payStatisticsHandle = (data) => {
$getPayCount(data)
.then((res) => {
payStatisticsGetData(res);
})
.catch((err) => {
console.log(err);
});
};
// 今日金额折线图日期选择
const amountSelectHandle = (value: string) => {
payAmountHandle(value);
};
let payAmount; // 金额板块折线图
let payAmountEmpty = ref(false); // 金额折线图缺省图是否展示
let amountSelectDate = ref("近30天"); // 交易统计折线图日期查询
// 成交金额 数据赋值
const payAmountGetData = (res) => {
amountEcharts.xAxis.data = res.dateList;
res.dateList.length === 0
? (payAmountEmpty.value = true)
: (payAmountEmpty.value = false);
let payAmountList = [];
res.payAmountList.forEach((item) => {
payAmountList.push((item / 100).toFixed(2) as never);
});
amountEcharts.series[0].data = payAmountList; // 数据赋值
nextTick(() => {
payAmount = echarts.init(
document.getElementById("pay-amount") as HTMLElement
);
payAmount.setOption(amountEcharts);
});
};
// 金额板块折线图请求数据
const payAmountHandle = (date) => {
$getPayTrendCount(date)
.then((res) => {
payAmountGetData(res);
})
.catch((err) => {
console.log(err);
});
};
// 缺省图片的样式
const emptyImg = Empty.PRESENTED_IMAGE_SIMPLE;
// 骨架屏是否展示
let skeletonIsShow = ref(true);
// 首页骨架屏隐藏采用 promise all,只有本页面5个请求全部获得数据才会隐藏 请求顺序依次为:成交金额, 趋势折线图, 支付方式, 交易统计
Promise.all([
$getNumCount({ queryDateRange: amountDate.value }),
$getPayTrendCount(30),
$getPayType(payMethodDate.value),
$getPayCount(payStatisticsDate.value),
])
.then((res) => {
skeletonIsShow.value = false; // 骨架屏取消
payAmountGetDateData(res[0]); // 成交金额
payAmountGetData(res[1]); // 趋势折线图
payMethodGetData(res[2]); // 饼图
payStatisticsGetData(res[3]); // 交易统计
})
.catch((error) => {
console.log(error);
});
// 所有图表根据屏幕大小 图表自适应
window.addEventListener("resize", () => {
payMethod.resize();
payStatistics.resize();
payAmount.resize();
});
// 问候语句
const nextAge = computed(() => {
const time = new Date();
const hour = time.getHours();
return hour < 9
? "早上好"
: hour <= 11
? "上午好"
: hour <= 13
? "中午好"
: hour < 20
? "下午好"
: "晚上好";
});
// 快速菜单集合
const quickMenuList: any = computed(() => {
const result = [];
const putResult = function (item) {
for (let i = 0; i < item.length; i++) {
if (item[i].menuUri && item[i].quickJump === 1) {
result.push(item[i] as never);
}
if (item[i].children) {
putResult(item[i].children);
}
}
};
putResult(userStore.userInfo["allMenuRouteTree"]);
return result;
});
// layout传过来的值用于监听菜单的收起与展开此时折线图板块应跟随宽度进行变化
const props = defineProps({
proLayoutObject: { type: Object, default: () => {} },
});
// 因为菜单的收起与展开有一定的过渡时间,所以采用了定时器
watch(
() => props.proLayoutObject.collapsed,
() => {
setTimeout(() => {
payStatistics.resize();
payAmount.resize();
}, 350);
}
);
//公告详情
function toDetail(articleId) {
noticeViewer.value.showDrawer(articleId);
}
// 金额处理 超过1000万时拼接万 超过亿时 拼接亿
const amountHandle = (amount) => {
if (amount >= 100000000) {
return parseFloat((amount / 100000000).toFixed(2));
} else if (amount >= 10000000) {
return parseFloat((amount / 10000).toFixed(2));
} else {
return amount;
}
};
function getCopyCode(type = 0){
const textarea = document.createElement('textarea');
let text = "拓展商户链接复制成功"
if(type == 1){
text = '拓展服务商链接复制成功'
textarea.value = agtInviteCodeUrl.value;
}else{
textarea.value = inviteCodeUrl.value;
}
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
$infoBox.message.success(text)
}
function getVisibleAdd(type = 0){
if(type == 1){
vdata.title="拓展代理"
vdata.inviteUrl = agtInviteCodeUrl.value
}else{
vdata.title="拓展商户"
vdata.inviteUrl = inviteCodeUrl.value
}
console.log(vdata.inviteUrl,'vdata.inviteUrl')
vdata.visibleAdd = true
}
async function getCopy(){
console.log(inviteCode.value)
// var textarea= document.createElement("textarea"); // 创建textarea对象
// document.body.appendChild(inviteCode.value); // 添加临时实例
// textarea.select(); // 选择实例内容
// document.execCommand("Copy"); // 执行复制
// document.body.removeChild(textarea); // 删除临时实例
const textarea = document.createElement('textarea');
textarea.value = inviteCode.value;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
$infoBox.message.success('复制成功')
}
</script>
<style lang="less" scoped>
@import "./index.less"; // 响应式布局
// 成交金额折线图 日期选择器样式
// /deep/ .ant-select-selector {
// background: rgba(255, 255, 255, 0.05) !important;
// color: rgba(255, 255, 255, 0.7) !important;
// }
// /deep/ .ant-select-arrow {
// color: rgba(255, 255, 255, 0.7) !important;
// }
// 个人信息页标题
.personal-title {
display: flex;
img {
width: 50px;
height: 50px;
border-radius: 50%;
margin-right: 15px;
}
& > div {
height: 50px;
display: flex;
flex-direction: column;
justify-content: space-between;
p {
font-size: 15px;
font-weight: 600;
margin: 0;
}
}
}
// 个人信息 快速开始
.quick-start {
p {
margin: 0;
font-weight: bold;
font-size: 14px;
}
ul {
margin: 0;
padding: 0;
}
li {
display: inline-block;
margin-right: 20px;
margin-top: 20px;
}
}
// 通用图表标题
.echart-title {
display: flex;
justify-content: space-between;
align-items: center;
b {
font-size: 14px;
font-weight: 600;
margin-right: 10px;
}
.date {
width: 215px;
}
}
// 交易统计图表标题
.pay-statistics {
& > div {
position: relative;
& > b,
& > .date {
position: absolute;
top: 30px;
z-index: 9;
}
& > b {
font-size: 14px;
font-weight: 600;
margin-right: 10px;
top: 36px;
}
& > .date {
width: 215px;
right: 30px;
}
}
}
.tooltip {
&:hover {
cursor: pointer;
}
}
// 缺省图的样式
/deep/ .ant-empty-normal {
display: flex;
flex-direction: column;
justify-content: center;
height: 100%;
}
#chart-card .amount > div .amount-line[data-v-5391d79e] {
border-left: none !important;
}
</style>

View File

@@ -0,0 +1,206 @@
export const pieEcharts = {
tooltip: {
trigger: 'item'
},
legend: {
itemHeight: 12,
itemWidth: 12,
top: '85%',
left: 'center',
textStyle: {
fontSize: 11
},
},
width: 'auto',
height: 'auto',
color: ['#5470C6', '#91CC75', '#FAC858', '#EE6666', '#73C0DE'],
series: [
{
name: 'Access From',
type: 'pie',
radius: ['40%', '65%'],
center: ['50%', '40%'],
avoidLabelOverlap: false,
itemStyle: {
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: '20',
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: [
{ value: 1048, name: '支付宝支付' },
{ value: 735, name: '微信支付' },
{ value: 580, name: '微信扫码' },
{ value: 484, name: '云闪付支付' },
{ value: 300, name: '支付宝二维码' }
]
}
],
}
export const statisticsEcharts = {
tooltip: {
trigger: 'axis'
},
grid: {
left: '3%',
right: '4%',
bottom: '13%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
dataZoom: [
{
type: 'inside',
start: 0,
end: 100
},
{
start: 0,
end: 100
}
],
legend: {
icon: 'rect',
itemHeight: 12,
itemWidth: 12,
top: '2%',
data: ['成交金额', '支付(成功)笔数','退款金额'],
selected: { // 默认把后两项置灰
'成交金额': true,
'支付(成功)笔数': false,
'退款金额': false
}
},
series: [
{
name: '成交金额',
type: 'line',
areaStyle: {},
symbol: 'none',
smooth: true,
data: [120, 132, 101, 134, 90, 230, 210]
},
{
name: '支付(成功)笔数',
type: 'line',
areaStyle: {},
symbol: 'none',
smooth: true,
data: [220, 182, 191, 234, 290, 330, 310]
},
{
name: '退款金额',
type: 'line',
areaStyle: {},
symbol: 'none',
smooth: true,
data: []
}
],
media: [ // 这里定义了 media query 的逐条规则。
{
query: { maxWidth: 700 }, // 这里写规则。
option: { // 这里写此规则满足下的option。
legend: {
top: '12%',
},
grid: {
top: '20%',
},
}
},
{
query: {minWidth:701, maxWidth: 1024 }, // 这里写规则。
option: { // 这里写此规则满足下的option。
legend: {
top: '2%',
left: '15%'
},
}
},
{
query: {minWidth:1025 }, // 这里写规则。
option: { // 这里写此规则满足下的option。
legend: {
left: '25%'
},
}
}
]
}
export const amountEcharts = {
xAxis: {
type: 'category',
axisLine: true,
onZero: true,
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
axisLabel: {
textStyle: {
color: 'rgba(255, 255, 255, 0.5)',
},
},
offset: 15
},
tooltip: {
trigger: 'axis'
},
grid: {
left: '2%',
bottom: '15%',
right: '2%',
containLabel: false
},
yAxis: {
show: false
},
series: [
{
lineStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 1,
y2: 0,
colorStops: [{
offset: 0, color: '#FFCC74' // 0% 处的颜色
}, {
offset: 1, color: '#FFFFFF' // 100% 处的颜色
}],
global: false // 缺省为 false
},
width: 7
},
data: [820, 932, 901, 934, 1290, 1330, 1320],
type: 'line',
smooth: true,
symbol: 'none',
}
],
}

View File

@@ -0,0 +1,194 @@
/*
1. amount 金额
2.
3. personal 个人信息
4. payMethod 支付方式
5. pay-statistics 交易统计
*/
#chart-card {
width: 100%;
display: flex;
flex-wrap: wrap;
}
#chart-card > div {
padding: 0 15px 30px;
width: 100%;
}
#chart-card > div > div {
width: 100%;
height: 100%;
padding: 30px;
border-radius: 5px;
box-sizing: border-box;
}
#chart-card .amount {
order: 1;
}
#chart-card .amount > div {
background: linear-gradient(103.57deg, #0c2640 0%, #12375e 100%);
display: flex;
flex-direction: column;
}
#chart-card .amount > div .amount-top {
color: rgba(255, 255, 255, 0.6);
min-width: 250px;
}
#chart-card .amount > div .amount-top .amount-date {
width: auto;
border-radius: 3px;
overflow: hidden;
display: inline-block;
color: rgba(255, 255, 255, 0.7);
margin-bottom: 50px;
}
#chart-card .amount > div .amount-top .amount-date div {
display: inline-block;
background: #000;
padding: 9px 20px;
}
#chart-card .amount > div .amount-top .amount-date div:hover {
cursor: pointer;
}
#chart-card .amount > div .amount-top .amount-date .amount-date-active {
background: #2691FF;
color: #fff;
}
#chart-card .amount > div .amount-top .amount-list {
display: flex;
flex-direction: row;
justify-content: space-between;
}
#chart-card .amount > div .amount-top .amount-list p {
margin-bottom: 10px;
}
#chart-card .amount > div .amount-top .amount-list span {
font-size: 20px;
color: #fff;
}
#chart-card .amount > div .amount-line {
width: 100%;
height: 0;
border-top: 1px solid rgba(255, 255, 255, 0.15);
margin: 30px 0;
}
#chart-card .amount > div #pay-amount {
height: 200px;
}
#chart-card .personal {
order: 3;
}
#chart-card .personal > div {
display: flex;
flex-direction: column;
background-color: #fff;
}
#chart-card .personal > div .personal-line {
width: 100%;
border-top: 1px solid #ebeff2;
margin: 25px 0;
}
#chart-card .method {
order: 4;
height: 530px;
}
#chart-card .method > div {
background-color: #fff;
display: flex;
flex-direction: column;
}
#chart-card .pay-statistics {
order: 5;
height: 530px;
}
#chart-card .pay-statistics > div {
position: relative;
background-color: #fff;
}
#chart-card .personal {
order: 0;
}
@media screen and (min-width: 1024px) {
#chart-card .personal {
order: 0;
width: 100%;
}
#chart-card .personal > div {
display: flex;
flex-direction: row;
}
#chart-card .personal > div .personal-line {
width: 0;
height: 100%;
border-left: 1px solid #ebeff2;
margin: 0 25px;
}
#chart-card .amount {
width: 100%;
}
#chart-card .amount > div {
flex-direction: row;
}
#chart-card .amount > div .amount-line {
width: 0;
height: 100%;
border-left: 1px solid rgba(255, 255, 255, 0.15);
margin: 0 30px;
}
#chart-card .amount > div #pay-amount {
height: 100%;
}
#chart-card .method {
width: calc(100% - 285px);
flex-grow: 1;
}
#chart-card .pay-statistics {
width: 100% ;
height: 530px;
}
}
@media screen and (min-width: 1366px) {
#chart-card .personal {
order: 0;
width: 100%;
}
#chart-card .amount {
width: calc(100% - 265px);
flex-grow: 1;
}
#chart-card .method {
width: 460px;
}
#chart-card .pay-statistics {
width: calc(100% - 460px);
flex-grow: 1;
height: 530px;
}
}
@media screen and (min-width: 1600px) {
#chart-card .amount {
order: 1;
width: calc(100% - 270px - 330px);
flex-grow: 1;
}
#chart-card .personal {
order: 3;
width: 330px;
}
#chart-card .personal > div {
display: flex;
flex-direction: column;
}
#chart-card .personal > div .personal-line {
width: 100%;
height: 0;
border-top: 1px solid #ebeff2;
margin: 25px 0;
}
#chart-card .method {
width: 544px;
}
#chart-card .pay-statistics {
width: calc(100% - 544px);
flex-grow: 1;
}
}

View File

@@ -0,0 +1,260 @@
/*
1. amount 金额
2.
3. personal 个人信息
4. payMethod 支付方式
5. pay-statistics 交易统计
*/
@import '../node_modules/ant-design-vue/es/style/themes/default.less';
#chart-card {
width: 100%;
display: flex;
flex-wrap: wrap;
& > div {
padding: 0 15px 30px;
// flex-grow: 1;
width: 100%;
}
& > div > div {
width: 100%;
height: 100%;
padding: 30px;
border-radius: 5px;
box-sizing: border-box;
}
// order 排列顺序 越小越靠前
.amount {
order: 1;
& > div {
background: linear-gradient(103.57deg, #0c2640 0%, #12375e 100%);
display: flex;
flex-direction: column;
.amount-top {
color: rgba(255, 255, 255, 0.6);
min-width: 250px;
.amount-date {
width: auto;
border-radius: 3px;
overflow: hidden;
display: inline-block;
color: rgba(255, 255, 255, 0.7);
margin-bottom: 50px;
div {
display: inline-block;
background: rgba(0, 0, 0, 0.2);
padding: 9px 20px;
&:hover {
cursor: pointer;
}
}
.amount-date-active {
background: var(--ant-primary-color);
color: #fff;
}
}
.amount-list {
display: flex;
flex-direction: row;
justify-content: space-between;
p {
margin-bottom: 10px;
}
span {
font-size: 20px;
color: #fff;
}
}
}
.amount-line {
width: 100%;
height: 0;
border-top: 1px solid rgba(255, 255, 255, 0.15);
margin: 0 10px 0 20px;
}
#pay-amount {
height: 200px;
}
}
}
.personal {
order: 3;
& > div {
display: flex;
flex-direction: column;
background-color: #fff;
.before-msg {
font-weight: 400;
font-size: 14px;
text-align: left;
color: #8f8f8f;
}
.personal-line {
width: 100%;
border-top: 1px solid #ebeff2;
margin: 20px 0;
}
.latest-post {
// min-width: 300px;
.title {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
span {
&:nth-child(1) {
font-weight: bold;
font-size: 14px;
color: #000;
}
&:nth-child(2) {
font-weight: 400;
font-size: 13px;
}
}
}
.post-list {
width: 100%;
padding-top: 10px;
// background-color: rgb(204, 191, 191);
.post-item {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 15px;
cursor: pointer;
.title {
display: block;
width: 60%;
overflow: hidden;
text-overflow: ellipsis; //溢出用省略号显示
white-space: nowrap;
font-weight: 400;
font-size: 13px;
color: #575757;
}
}
}
}
}
}
.method {
order: 4;
height: 530px;
& > div {
background-color: #fff;
display: flex;
flex-direction: column;
}
}
.pay-statistics {
order: 5;
height: 530px;
& > div {
position: relative;
background-color: #fff;
}
}
.personal {
order: 0;
}
}
@media screen and (min-width: 1024px) {
#chart-card {
.personal {
order: 0;
width: 100%;
& > div {
display: flex;
flex-direction: row;
.personal-line {
width: 0;
height: 100%;
border-left: 1px solid #ebeff2;
margin: 0 20px;
}
}
}
.amount {
width: 100%;
& > div {
flex-direction: row;
.amount-line {
width: 0;
height: 100%;
border-left: 1px solid rgba(255, 255, 255, 0.15);
margin: 0 30px;
}
#pay-amount {
height: 100%;
}
}
}
.method {
width: calc(100% - 285px);
flex-grow: 1;
}
.pay-statistics {
width: 100%;
height: 530px;
}
}
}
@media screen and (min-width: 1366px) {
#chart-card {
.personal {
order: 0;
width: 100%;
}
.amount {
width: calc(100% - 265px);
flex-grow: 1;
}
.method {
width: 460px;
}
.pay-statistics {
width: calc(100% - 460px);
flex-grow: 1;
height: 530px;
}
}
}
@media screen and (min-width: 1600px) {
#chart-card {
.amount {
order: 1;
width: calc(100% - 270px - 330px);
flex-grow: 1;
} // order 排列顺序 越小越靠前
.personal {
order: 3;
width: 330px;
& > div {
display: flex;
flex-direction: column;
.personal-line {
width: 100%;
height: 0;
border-top: 1px solid #ebeff2;
margin: 20px 0;
}
.latest-post {
.title {
width: 100%;
display: flex;
justify-content: space-between;
}
}
}
}
.method {
width: 544px;
}
.pay-statistics {
width: calc(100% - 544px);
flex-grow: 1;
}
}
}

View File

@@ -0,0 +1,308 @@
<template>
<page-header-wrapper>
<a-card class="table-card">
<JeepaySearchForm :searchFunc="searchFunc" :resetFunc="onReset">
<jeepay-text-up v-model:value="vdata.searchData['mchServiceName']" :placeholder="'服务商号/名称'" />
<!-- <JeepaySearchInfoInput v-model:value="vdata.searchData['mchUserName']" placeholder="用户号" :textUpStyle="true" :mchNoAndName="true" showType="MCH" />-->
<jeepay-text-up v-model:value="vdata.searchData['mchUserName']" :placeholder="'用户号/名称/手机号'" />
<jeepay-text-up v-model:value="vdata.searchData.storeId" :placeholder="'门店名称/编号'" />
<!-- <jeepay-text-up v-model:value="vdata.searchData.deviceNo" :placeholder="'设备号'" />-->
<jeepay-text-up v-model:value="vdata.searchData['deviceName']" :placeholder="'设备名称/编号'" />
<jeepay-text-up v-model:value="vdata.searchData['mchApplyName']" :placeholder="'商户名称/商户号'" />
<jeepay-text-up v-model:value="vdata.searchData['appId']" :placeholder="'应用名称'" />
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData.provider" placeholder="所属支付接口">
<a-select-option value="">全部</a-select-option>
<a-select-option v-for="item in vdata.ifCodeList" :key="item.ifCode" :value="item.ifCode">{{ item.ifName }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData.state" placeholder="状态">
<a-select-option value="">全部</a-select-option>
<a-select-option value="0">禁用</a-select-option>
<a-select-option value="1">启用</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData['bindState']" placeholder="绑定状态">
<a-select-option value="">全部</a-select-option>
<a-select-option value="0">未绑定</a-select-option>
<a-select-option value="1">已绑定</a-select-option>
</a-select>
</a-form-item>
</JeepaySearchForm>
<!-- 列表渲染 -->
<JeepayTable
ref="infoTable"
:init-data="false"
:req-table-data-func="reqTableDataFunc"
:table-columns="tableColumns"
:search-data="vdata.searchData"
:rowSelection="{ type: 'checkbox', selectedRowKeys: vdata.allotDeviceIdList, onChange: infoTableSelectChangeFunc }"
row-key="deviceId"
@btnLoadClose="btnLoading=false"
>
<template #topBtnSlot>
<a-button type="primary" @click="addFunc"><plus-outlined />申请新设备</a-button>
<a-button v-if="$access('ENT_DEVICE_SPEAKER_DEVICE_ALLOT')" type="primary" @click="allotBatchFunc"><partition-outlined />勾选划拨/收回</a-button>
<!-- <a-button v-if="$access('ENT_DEVICE_SPEAKER_DEVICE_ALLOT')" type="primary" @click="allotByBatchIdFunc"><partition-outlined />批次划拨/收回</a-button> -->
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'appName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>应用名称{{record.appName}}</span><br>
<span>应用ID{{record.appId}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.appName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'mchApplyName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>商户名称{{record.mchApplyName}}</span><br>
<span>商户号{{record.mchApplyId}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.mchApplyName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'mchServiceName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>服务商名称{{record.mchServiceName}}</span><br>
<span>服务商号{{record.agentNo}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.mchServiceName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'storeId'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>门店名称{{record.storeName}}</span><br>
<span>门店编号{{record.storeId}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.storeName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'bindState'">
<template v-if="record.bindState == 0"><exclamation-circle-outlined />未绑定</template>
<template v-else>已绑定商户: {{ record.mchNo }} ({{ record.mchName }})<br>应用 {{ record.appId }}<br>门店 {{ record.storeId }} ({{ record.storeName }})</template>
</template>
<template v-if="column.key === 'state'">
<JeepayTableColState :state="record.state" :showSwitchType="$access('ENT_DEVICE_AUTO_POS_EDIT')" :onChange="(state) => { return updateState(record.deviceId, state)}" />
</template>
<template v-if="column.key === 'mchUserName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>用户名称{{record.mchUserName}}</span><br>
<span>用户手机号{{record.mchUserPhone}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.mchUserName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'provider'">
<a-tag :key="record.provider" color="processing">
{{ (vdata.ifCodeList.find(item => item.ifCode == record.provider) as any)?.ifName || '其他' }}
</a-tag>
</template>
<template v-if="column.key === 'operation'">
<!-- 操作列插槽 -->
<JeepayTableColumns>
<a-button v-if="($access('ENT_DEVICE_AUTO_POS_EDIT') && record.isSelf)" type="link" @click="bindFunc(record)">绑定</a-button>
<a-button v-if="($access('ENT_DEVICE_AUTO_POS_EDIT') && record.bindState == 1 && record.isSelf)" type="link" @click="deBindFunc(record.deviceId)">解绑</a-button>
<a-button v-if="$access('ENT_DEVICE_SPEAKER_DEVICE_ALLOT')" type="link" @click="allotFunc(record)">划拨/收回</a-button>
</JeepayTableColumns>
</template>
</template>
</JeepayTable>
</a-card>
<!-- 批次号划拨弹窗 -->
<a-modal v-model:visible="vdata.allotByBatchIdVisible" title="按批次号划拨设备" @ok="allotOk">
<a-form ref="allotFormModel" :model="vdata.allotObject" layout="vertical" :rules="vdata.allotRules">
<a-form-item label="批次号:" name="batchId">
<a-input v-model:value="vdata.allotObject.batchId" placeholder="请输入批次号" />
</a-form-item>
</a-form>
</a-modal>
<!-- 绑定组件 -->
<Bind ref="bind" :callbackFunc="searchFunc" />
<!-- 选择划拨服务商 -->
<JeepayModelAgentList ref="jeepayModelAgentList" showType="AGENT" @selectFinishFunc="searchAgentFinishFunc" />
</page-header-wrapper>
</template>
<script setup lang="ts">
import { API_URL_STORE_DEVICE, API_URL_IFDEFINES_LIST, req, reqLoad, $unbindDevice, $allotDevice } from '@/api/manage'
import Bind from './Bind.vue'
import { ref, reactive, getCurrentInstance, onMounted } from 'vue'
import { useRoute } from 'vue-router'
const { $infoBox, $access } = getCurrentInstance()!.appContext.config.globalProperties
const bind = ref()
const infoTable = ref()
const jeepayModelAgentList = ref()
const allotFormModel = ref()
const deviceType = 4
let tableColumns = reactive([
// { title: '设备号', fixed: 'left', dataIndex: 'deviceNo', },
// { key: 'provider', title: '所属支付接口', dataIndex: 'provider'},
// { title: '服务商号', dataIndex: 'agentNo', agentEntCol: true},
// { key: 'bindState', title: '绑定商户信息' },
// { key: 'state', title: '状态'},
// { dataIndex: 'createdAt', title: '创建日期', },
// { key: 'operation', title: '操作', fixed: 'right', align: 'center'}
{ title: '设备号', fixed: 'left', dataIndex: 'deviceNo' },
{ key: 'provider', title: '所属支付接口', dataIndex: 'provider', },
{ key: 'mchUserName', title: '用户名称', dataIndex: 'mchUserName'},
{ key: 'mchServiceName', title: '服务商名称', dataIndex: 'mchServiceName' },
{ title: "商户名称", key: "mchApplyName", dataIndex: "mchApplyName" },
{ key: 'storeId',title: '门店名称', dataIndex: 'storeId',},
{ key: 'appName', dataIndex: 'appName', title: '所属应用', },
// { key: 'bindState', title: '绑定状态',},
{ key: 'state', title: '使用状态',},
{ dataIndex: 'createdAt', title: '创建日期',},
{ key: 'operation', title: '操作', fixed: 'right', align: 'center'}
])
let btnLoading = ref(false)
const vdata = reactive({
ifCodeList: [], // 通道列表
searchData: {} as any,
allotObject: {
agentNo: null, // 划拨服务商号
allotType: null, // 划拨类型select-勾选划拨 batch-批次划拨
batchId: null, // 批次号,划拨类型为 batch-批次划拨 时必填
allotDeviceIds: [] as any, // 要划拨的设备ID划拨类型为 select-勾选划拨 时必填
} as any, // 划拨设备对象
allotDeviceIdList: [] as any, // 要划拨的设备ID划拨类型为 select-勾选划拨 时必填
allotRules: {
batchId: [{ required: true, message: '请输入批次号', trigger: 'blur' }]
}
}) as any
onMounted(() => {
vdata.searchData.mchNo = useRoute().query.mchNo
searchFunc()
req.list(API_URL_IFDEFINES_LIST, { 'state': 1 }).then(res => { // 通道下拉选择列表
vdata.ifCodeList = res.filter(r => r.wayCodes.some(s => s.wayCode == 'AUTO_POS' || s.wayCode == 'OUT_TRADE'))
})
})
function reqTableDataFunc(params: any) { // 请求table接口数据
return req.list(API_URL_STORE_DEVICE, Object.assign({ deviceType: deviceType }, params))
}
function searchFunc () { // 点击【查询】按钮点击事件
btnLoading.value = true
infoTable.value.refTable(true)
}
function addFunc () { // 业务通用【新增】 函数
$infoBox.message.success('请联系运营平台申请')
}
function bindFunc (record: any) { // 绑定设备函数
bind.value.show(record)
}
function onReset(){ //重置搜索内容
vdata.searchData = {}
}
function updateState (recordId, state) { // 【更新状态】
const title = state ? '确认启用?' : '确认禁用?'
const content = state ? '' : '禁用后该设备将无法使用!'
return new Promise<void>((resolve, reject) => {
$infoBox.confirmDanger(title, content, () => {
return reqLoad.updateById(API_URL_STORE_DEVICE, recordId, { state: state }).then(res => {
searchFunc()
resolve()
}).catch(err => reject(err))
},
() => {
reject(new Error())
})
})
}
// 解绑设备
function deBindFunc(recordId) {
const title = '确认解绑?'
const content = '解绑后商户将无法使用该设备!'
$infoBox.confirmDanger(title, content, () => {
return $unbindDevice(recordId).then(res => {
infoTable.value.refTable(true)
$infoBox.message.success('解绑成功')
})
})
}
// 表格多选
function infoTableSelectChangeFunc(selectedRowKeys, selectedRows) {
vdata.allotDeviceIdList = selectedRowKeys
}
// 按批次号划拨
function allotByBatchIdFunc() {
vdata.allotObject.allotType = 'batch'
vdata.allotObject.batchId = ''
vdata.allotByBatchIdVisible = true
}
// 按批次号划拨,批次号输入完成
function allotOk() {
allotFormModel.value.validate().then((valid: any) =>{
vdata.allotByBatchIdVisible = false
jeepayModelAgentList.value.show()
})
}
// 勾选划拨设备
function allotBatchFunc() {
if (vdata.allotDeviceIdList.length < 1) {
$infoBox.message.error('请选择设备')
} else {
vdata.allotObject.allotType = 'select'
jeepayModelAgentList.value.show()
}
}
// 划拨单个设备
function allotFunc(record) {
vdata.allotObject.allotType = 'select'
vdata.allotDeviceIdList = []
vdata.allotDeviceIdList.push(record.deviceId)
jeepayModelAgentList.value.show()
}
// 划拨/收回选择完成
function searchAgentFinishFunc(selectObject) {
vdata.allotObject.agentNo = selectObject[0]
vdata.allotObject.allotOrRecover = selectObject[1]
if (vdata.allotObject.allotOrRecover == 'allot' && !vdata.allotObject.agentNo) {
$infoBox.message.error('请选择服务商')
return
}
if (vdata.allotDeviceIdList.length > 0) {
vdata.allotObject.allotDeviceIds = vdata.allotDeviceIdList.join(',')
}
$allotDevice(vdata.allotObject).then(res => {
vdata.allotDeviceIdList = [] // 清空多选
$infoBox.message.success('保存成功')
jeepayModelAgentList.value.close()
searchFunc()
})
}
</script>

View File

@@ -0,0 +1,150 @@
<template>
<a-drawer
v-model:visible="vdata.drawerVisible"
:mask-closable="false"
title="绑定设备"
:body-style="{ paddingBottom: '80px' }"
width="40%"
class="drawer-width"
@close="onClose"
>
<a-form ref="infoFormModel" :model="vdata.saveObject" layout="horizontal" :rules="vdata.rules">
<a-row justify="space-between" type="flex">
<a-col :span="24">
<a-form-item label="绑定状态:">
<a-space>
{{ vdata.saveObject.bindState == 1 ? '已绑定' : '未绑定' }}
<a-button type="primary" @click="showSelectModal">{{ vdata.saveObject.bindState == 1 ? '重新选择' : '选择商户信息' }}</a-button>
</a-space>
</a-form-item>
</a-col>
<a-col v-if="vdata.showBindInfo" :span="24">
<a-form-item label="用户号:">
<span>{{ vdata.saveObject.mchNo }}</span>
</a-form-item>
</a-col>
<a-col v-if="vdata.showBindInfo && vdata.isSelectMchApp" :span="24">
<a-form-item label="应用:">
<span>{{ vdata.saveObject.appId }}</span>
</a-form-item>
</a-col>
<a-col v-if="vdata.showBindInfo" :span="24">
<a-form-item label="门店:">
<span>{{ vdata.saveObject.storeId }}</span>
</a-form-item>
</a-col>
</a-row>
</a-form>
<div class="drawer-btn-center">
<a-button :style="{ marginRight: '8px' }" style="margin-right:8px" @click="onClose"><close-outlined />取消</a-button>
<a-button type="primary" :loading="vdata.btnLoading" @click="handleOkFunc"><check-outlined />保存</a-button>
</div>
<JeepayModelMchList ref="jeepayModelMchListRef" :showType="vdata.isSelectMchApp ? 'MCH_APP_STORE' : 'MCH_STORE'" :mchNoAndName="true" @selectFinishFunc="selectFinishFunc" />
</a-drawer>
</template>
<script lang="ts" setup>
import { API_URL_STORE_DEVICE, req, $bindDevice } from '@/api/manage'
import { defineProps, reactive, ref, getCurrentInstance } from 'vue'
const { $infoBox } = getCurrentInstance()!.appContext.config.globalProperties
const props = defineProps({
callbackFunc: { type: Function, default:null }
})
const infoFormModel = ref()
const jeepayModelMchListRef = ref()
const vdata = reactive({
storeList: [] as any, // 门店列表
btnLoading: false,
showBindInfo: false, // 显示绑定信息
saveObject: {} as any, // 数据对象
isSelectMchApp: false, // 绑定时 是否选择商户应用
recordId: null, // 更新对象ID
drawerVisible: false // 是否显示弹层/抽屉
}) as any
function show (record: any) { // 弹层打开事件
if (infoFormModel.value != undefined) {
infoFormModel.value.resetFields()
}
vdata.recordId = record.deviceId
vdata.isSelectMchApp = record.deviceType != 1
req.getById(API_URL_STORE_DEVICE, vdata.recordId).then((res: any) => {
if(res){
vdata.saveObject = res
if (vdata.saveObject.bindState == 1) {
vdata.showBindInfo = true
} else {
vdata.showBindInfo = false
vdata.saveObject.storeId = ''
}
}
vdata.drawerVisible = true
})
}
function handleOkFunc () { // 点击【确认】按钮事件
infoFormModel.value.validate().then((valid: any) =>{
vdata.btnLoading = true
const { deviceId, mchNo, appId, storeId } = vdata.saveObject
if (!storeId) {
$infoBox.message.error('请选择门店')
return
}
$bindDevice(vdata.recordId, { deviceId, mchNo, appId, storeId }).then((res: any) => {
successFunc('修改成功')
}).catch((err: any) => {
vdata.btnLoading = false
})
})
}
function successFunc(e: string) { // 新增/更新成功
vdata.btnLoading = false
$infoBox.message.success(e)
props.callbackFunc()
vdata.drawerVisible = false
}
// 选择商户、门店弹窗
function showSelectModal(){
jeepayModelMchListRef.value.show()
}
// 选择商户、门店完成
function selectFinishFunc(selectArray){
if(!selectArray[0] || !selectArray[2]){
return $infoBox.message.error('选择信息不完整!')
}
if (vdata.isSelectMchApp && !selectArray[1]) {
return $infoBox.message.error('选择信息不完整!')
}
vdata.showBindInfo = true
vdata.saveObject.mchNo = selectArray[0]
vdata.saveObject.appId = selectArray[1]
vdata.saveObject.storeId = selectArray[2]
jeepayModelMchListRef.value.close()
}
function onClose () { // 关闭抽屉
vdata.drawerVisible = false
}
defineExpose({
show
})
</script>

View File

@@ -0,0 +1,206 @@
<template>
<a-drawer
v-model:visible="vdata.drawerVisible"
:mask-closable="false"
title="绑定设备"
:body-style="{ paddingBottom: '80px' }"
width="40%"
class="drawer-width"
@close="onClose"
>
<a-form ref="infoFormModel" :model="vdata.saveObject" layout="horizontal" :rules="vdata.rules">
<a-row justify="space-between" type="flex">
<a-col :span="24">
<a-form-item label="绑定状态:">
<a-space>
{{ vdata.saveObject.bindState == 1 ? '已绑定' : '未绑定' }}
<a-button type="primary" @click="showSelectModal">{{ vdata.saveObject.bindState == 1 ? '重新选择' : '选择商户信息' }}</a-button>
</a-space>
</a-form-item>
</a-col>
<a-col v-if="vdata.showBindInfo" :span="24">
<a-form-item label="用户号:">
<span>{{ vdata.saveObject.mchNo }}</span>
</a-form-item>
</a-col>
<a-col v-if="vdata.showBindInfo && vdata.deviceType == 3" :span="24">
<a-form-item label="应用:">
<span>{{ vdata.saveObject.appId }}</span>
</a-form-item>
</a-col>
<a-col v-if="vdata.showBindInfo" :span="24">
<a-form-item label="门店:">
<span>{{ vdata.saveObject.storeId }}</span>
</a-form-item>
</a-col>
</a-row>
<a-row justify="space-between" type="flex">
<a-form-item label="绑定类型" name="bindType">
<a-space>
<a-radio-group v-model:value="vdata.saveObject.bindType">
<a-radio :value="0">门店</a-radio>
<a-radio :value="1">码牌</a-radio>
</a-radio-group>
<a-button v-if="vdata.saveObject.bindType == 1" type="primary" @click="showQrcSelectModel">选择码牌</a-button>
</a-space>
</a-form-item>
</a-row>
<a-row v-if="vdata.saveObject.bindType == 1" justify="space-between" type="flex">
<a-col v-if="vdata.saveObject.qrcIdList && vdata.saveObject.qrcIdList.length > 0" :span="24">
<a-form-item label="码牌ID">
<a-textarea v-model:value="vdata.saveObject.qrcIdList" disabled />
</a-form-item>
</a-col>
</a-row>
</a-form>
<div class="drawer-btn-center">
<a-button :style="{ marginRight: '8px' }" style="margin-right:8px" @click="onClose"><close-outlined />取消</a-button>
<a-button type="primary" :loading="vdata.btnLoading" @click="handleOkFunc"><check-outlined />保存</a-button>
</div>
<!-- 选择商户信息组件 -->
<JeepayModelMchList ref="jeepayModelMchListRef" :showType="vdata.deviceType == 3 ? 'MCH_APP_STORE' : 'MCH_STORE'" :mchNoAndName="true" @selectFinishFunc="selectFinishFunc" />
<!-- 选择码牌组件 -->
<JeepayModelQrcList ref="jeepayModelQrcListRef" @selectFinishFunc="selectQrcFinishFunc" />
</a-drawer>
</template>
<script lang="ts" setup>
import { API_URL_STORE_DEVICE, req, $bindDevice } from '@/api/manage'
import { defineProps, reactive, ref, getCurrentInstance, watch } from 'vue'
const { $infoBox } = getCurrentInstance()!.appContext.config.globalProperties
const props = defineProps({
callbackFunc: { type: Function, default:null }
})
const infoFormModel = ref()
const jeepayModelMchListRef = ref()
const jeepayModelQrcListRef = ref()
const vdata = reactive({
storeList: [] as any, // 门店列表
btnLoading: false,
showBindInfo: false, // 显示绑定信息
saveObject: {} as any, // 数据对象
deviceType: 1, // 1-云喇叭 2-云打印 3-扫码POS
recordId: null, // 更新对象ID
drawerVisible: false // 是否显示弹层/抽屉
}) as any
// 监听属性
watch( () => vdata.saveObject.storeId ,(nv, ov)=>{
if (ov && nv != ov) {
vdata.saveObject.qrcIdList = []
}
})
function show (record: any) { // 弹层打开事件
if (infoFormModel.value != undefined) {
infoFormModel.value.resetFields()
}
vdata.recordId = record.deviceId
vdata.deviceType = record.deviceType
req.getById(API_URL_STORE_DEVICE, vdata.recordId).then((res: any) => {
if(res){
vdata.saveObject = res
if (vdata.saveObject.bindState == 1) {
vdata.showBindInfo = true
} else {
vdata.showBindInfo = false
vdata.saveObject.storeId = ''
}
}
vdata.drawerVisible = true
})
}
function handleOkFunc () { // 点击【确认】按钮事件
infoFormModel.value.validate().then((valid: any) =>{
vdata.btnLoading = true
const { deviceId, mchNo, appId, storeId, bindType, qrcIdList } = vdata.saveObject
if (!storeId) {
vdata.btnLoading = false
$infoBox.message.error('请选择门店')
return
}
$bindDevice(vdata.recordId, { deviceId, mchNo, appId, storeId, bindType, qrcIdList }).then((res: any) => {
successFunc('修改成功')
}).catch((err: any) => {
vdata.btnLoading = false
})
})
}
function successFunc(e: string) { // 新增/更新成功
vdata.btnLoading = false
$infoBox.message.success(e)
props.callbackFunc()
vdata.drawerVisible = false
}
// 选择商户、门店弹窗
function showSelectModal(){
jeepayModelMchListRef.value.show()
}
// 选择商户、门店完成
function selectFinishFunc(selectArray){
if(!selectArray[0] || !selectArray[2]){
return $infoBox.message.error('选择信息不完整!')
}
if (vdata.deviceType ==3 && !selectArray[1]) {
return $infoBox.message.error('选择信息不完整!')
}
vdata.showBindInfo = true
vdata.saveObject.mchNo = selectArray[0]
vdata.saveObject.appId = selectArray[1]
vdata.saveObject.storeId = selectArray[2]
jeepayModelMchListRef.value.close()
}
// 选择码牌弹窗
function showQrcSelectModel() {
if (!vdata.saveObject.storeId) {
$infoBox.message.error('请选择商户信息')
}
if (vdata.saveObject.storeId && vdata.saveObject.bindType == 1) {
jeepayModelQrcListRef.value.show(vdata.saveObject.storeId, vdata.saveObject.qrcIdList)
}
}
// 选择码牌完成
function selectQrcFinishFunc (e) {
if (!e || e.length <= 0) {
return $infoBox.message.error('请开启要绑定的码牌!')
}
vdata.saveObject.qrcIdList = e
jeepayModelQrcListRef.value.close()
}
function onClose () { // 关闭抽屉
vdata.drawerVisible = false
}
defineExpose({
show
})
</script>

View File

@@ -0,0 +1,80 @@
<template>
<a-form v-if="vdata.deviceType == 2" ref="formRef" :model="vdata.bizConfig" layout="vertical" :rules="vdata.formRules">
<a-divider orientation="left">
<a-tag color="#FF4B33">其他配置</a-tag>
</a-divider>
<a-row justify="space-between" type="flex">
<a-col v-if="vdata.provider != 'fe'" :span="11">
<a-form-item label="打印机模式" name="printMode">
<a-radio-group v-model:value="vdata.bizConfig.printMode">
<a-radio :value="1">仅打印</a-radio>
<a-radio :value="2">仅播报</a-radio>
<a-radio :value="3">打印并播报</a-radio>
</a-radio-group>
</a-form-item>
</a-col>
<a-col :span="11">
<a-form-item label="打印联数" name="printNum">
<a-radio-group v-model:value="vdata.bizConfig.printNum">
<a-input v-model:value="vdata.bizConfig.printNum" placeholder="请输入打印联数" />
</a-radio-group>
</a-form-item>
</a-col>
</a-row>
</a-form>
</template>
<script lang="ts" setup>
import {reactive, ref, defineExpose} from 'vue'
// 当前的form
const formRef = ref()
const vdata = reactive({
isShow: false,
deviceType: 0, // 1-云喇叭 2-云打印 3-扫码POS
provider: '', // 厂商,飞鹅打印机无播报,特殊处理
// 配置对象
bizConfig: {} as any,
// 表单规则
formRules: {
printMode: [{ required: true, type: 'number', message: '请选择打印机模式', trigger: 'blur' }],
printNum: [{ required: true, message: '请输入打印联数', trigger: 'blur' }],
}
})
// 对外提供的页面的渲染函数 ifDefineArray = 接口的配置定义项数组, bizConfig = 当前配置项
function pageRender(bizConfig: any, deviceType: number, provider: string){
// 赋值
if (bizConfig) {
vdata.bizConfig = bizConfig
}
vdata.deviceType = deviceType
vdata.provider = provider
// 重置form验证
if (formRef.value !== undefined && formRef.value !== null) {
formRef.value.resetFields()
}
vdata.isShow = true
}
// 对外提供的获取配置参数函数 返回JSON类型
function getConfigParams(){
// 云喇叭 扫码POS 暂无业务配置,直接返回
if (vdata.deviceType == 1 || vdata.deviceType == 3) {
vdata.isShow = false
return Promise.resolve()
}
return formRef.value.validate().then( () => {
vdata.isShow = false
return vdata.bizConfig
})
}
defineExpose({ getConfigParams, pageRender })
</script>

View File

@@ -0,0 +1,107 @@
<template>
<a-drawer
v-model:visible="vdata.drawerVisible"
:mask-closable="false"
:title="'修改设备'"
:body-style="{ paddingBottom: '80px' }"
width="40%"
class="drawer-width"
@close="onClose"
>
<a-form v-if="vdata.drawerVisible" ref="infoFormModel" :model="vdata.saveObject" layout="vertical" :rules="vdata.rules">
<a-row justify="space-between" type="flex">
<a-col :span="11">
<a-form-item label="设备号" name="deviceNo">
<a-input v-model:value="vdata.saveObject.deviceNo" :disabled="true" placeholder="请输入设备号" />
</a-form-item>
</a-col>
</a-row>
</a-form>
<div class="drawer-btn-center">
<a-button :style="{ marginRight: '8px' }" style="margin-right:8px" @click="onClose"><close-outlined />取消</a-button>
<a-button type="primary" :loading="vdata.btnLoading" @click="handleOkFunc"><check-outlined />保存</a-button>
</div>
<!-- 业务参数配置组件 -->
<BizConfig ref="bizConfigRef" />
</a-drawer>
</template>
<script lang="ts" setup>
import { API_URL_STORE_DEVICE, req } from '@/api/manage'
import { defineProps, reactive, ref, getCurrentInstance } from 'vue'
import BizConfig from '@/views/device/BizConfig.vue'
const { $infoBox } = getCurrentInstance()!.appContext.config.globalProperties
const props = defineProps({
callbackFunc: { type: Function, default:null }
})
const infoFormModel = ref()
const bizConfigRef = ref()
const vdata = reactive({
btnLoading: false,
saveObject: {} as any, // 数据对象
recordId: null, // 更新对象ID
drawerVisible: false, // 是否显示弹层/抽屉
deviceType: 1 // 1-云喇叭 2-云打印 3-扫码POS
}) as any
function show (record: any) { // 弹层打开事件
vdata.deviceType = record.deviceType
vdata.provider = record.provider
vdata.saveObject = { 'state': 1, 'deviceType': record.deviceType } // 设备类型deviceType 1-云喇叭, 2-云打印
if (infoFormModel.value != undefined) {
infoFormModel.value.resetFields()
}
vdata.recordId = record.deviceId
req.getById(API_URL_STORE_DEVICE, vdata.recordId).then((res: any) => {
if(res){
vdata.saveObject = res
// 业务参数配置组件
let bizConfig = JSON.parse(vdata.saveObject.bizConfigParams || '{}' )
bizConfigRef.value.pageRender(bizConfig, vdata.deviceType, vdata.provider)
}
})
vdata.drawerVisible = true // 立马展示弹层信息
}
function handleOkFunc () { // 点击【确认】按钮事件
infoFormModel.value.validate().then((valid: any) =>{
bizConfigRef.value.getConfigParams().then((bizConfigParams: any) => {
vdata.btnLoading = true
// 赋值
vdata.saveObject.bizConfigParams = bizConfigParams || ''
// 请求接口
req.updateById(API_URL_STORE_DEVICE, vdata.recordId, vdata.saveObject).then((res: any) => {
successFunc('修改成功')
}).catch((err: any) => {
vdata.btnLoading = false
})
})
})
}
function successFunc(e: string) { // 新增/更新成功
vdata.btnLoading = false
$infoBox.message.success(e)
props.callbackFunc()
vdata.drawerVisible = false
}
function onClose () { // 关闭抽屉
vdata.drawerVisible = false
}
defineExpose({
show
})
</script>

View File

@@ -0,0 +1,235 @@
<template>
<page-header-wrapper>
<a-card class="table-card">
<JeepaySearchForm :searchFunc="searchFunc" :resetFunc="onReset">
<jeepay-text-up v-model:value="vdata.searchData['mchServiceName']" :placeholder="'服务商号/名称'" />
<JeepaySearchInfoInput v-model:value="vdata.searchData['mchUserName']" placeholder="用户号/名称" :textUpStyle="true" :mchNoAndName="true" showType="MCH" />
<jeepay-text-up v-model:value="vdata.searchData.deviceNo" :placeholder="'设备号'" />
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData.provider" placeholder="设备厂商">
<a-select-option value="">全部</a-select-option>
<a-select-option value="wxpayQWPro">微信青蛙pro</a-select-option>
<a-select-option value="alipayQT">支付宝蜻蜓</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData.state" placeholder="状态">
<a-select-option value="">全部</a-select-option>
<a-select-option value="0">禁用</a-select-option>
<a-select-option value="1">启用</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData.bindState" placeholder="绑定状态">
<a-select-option value="">全部</a-select-option>
<a-select-option value="0">未绑定</a-select-option>
<a-select-option value="1">已绑定</a-select-option>
</a-select>
</a-form-item>
</JeepaySearchForm>
<!-- 列表渲染 -->
<JeepayTable
ref="infoTable"
:init-data="false"
:req-table-data-func="reqTableDataFunc"
:table-columns="tableColumns"
:search-data="vdata.searchData"
:rowSelection="{ type: 'checkbox', selectedRowKeys: vdata.allotDeviceIdList, onChange: infoTableSelectChangeFunc }"
row-key="deviceId"
@btnLoadClose="btnLoading=false"
>
<template #topBtnSlot>
<a-button v-if="$access('ENT_DEVICE_FACE_APP_ADD')" type="primary" @click="addFunc"><plus-outlined />申请新设备</a-button>
<a-button v-if="$access('ENT_DEVICE_FACE_APP_ALLOT')" type="primary" @click="allotBatchFunc"><partition-outlined />勾选划拨/收回</a-button>
<!-- <a-button v-if="$access('ENT_DEVICE_SPEAKER_DEVICE_ALLOT')" type="primary" @click="allotByBatchIdFunc"><partition-outlined />批次划拨/收回</a-button> -->
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'bindState'">
<template v-if="record.bindState == 0"><exclamation-circle-outlined />未绑定</template>
<template v-else>已绑定商户: {{ record.mchNo }} ({{ record.mchName }})<br>应用 {{ record.appId }}<br>门店 {{ record.storeId }} ({{ record.storeName }})</template>
</template>
<template v-if="column.key === 'state'">
<JeepayTableColState :state="record.state" :showSwitchType="$access('ENT_DEVICE_AUTO_POS_EDIT')" :onChange="(state) => { return updateState(record.deviceId, state)}" />
</template>
<template v-if="column.key === 'provider'">
<a-tag :color="record.provider == 'wxpayQWPro' ? 'green' : 'blue'">
{{ record.provider == 'wxpayQWPro' ? '微信青蛙pro':'支付宝蜻蜓' }}
</a-tag>
</template>
<template v-if="column.key === 'operation'">
<!-- 操作列插槽 -->
<JeepayTableColumns>
<a-button v-if="$access('ENT_DEVICE_FACE_APP_EDIT') && record.isSelf" type="link" @click="bindFunc(record)">绑定</a-button>
<a-button v-if="$access('ENT_DEVICE_FACE_APP_EDIT') && record.bindState == 1 && record.isSelf" type="link" @click="deBindFunc(record.deviceId)">解绑</a-button>
<a-button v-if="$access('ENT_DEVICE_FACE_APP_ALLOT')" type="link" @click="allotFunc(record)">划拨/收回</a-button>
</JeepayTableColumns>
</template>
</template>
</JeepayTable>
</a-card>
<!-- 批次号划拨弹窗 -->
<a-modal v-model:visible="vdata.allotByBatchIdVisible" title="按批次号划拨设备" @ok="allotOk">
<a-form ref="allotFormModel" :model="vdata.allotObject" layout="vertical" :rules="vdata.allotRules">
<a-form-item label="批次号:" name="batchId">
<a-input v-model:value="vdata.allotObject.batchId" placeholder="请输入批次号" />
</a-form-item>
</a-form>
</a-modal>
<!-- 绑定组件 -->
<Bind ref="bind" :callbackFunc="searchFunc" />
<JeepayModelAgentList ref="jeepayModelAgentList" showType="AGENT" @selectFinishFunc="searchAgentFinishFunc" />
</page-header-wrapper>
</template>
<script setup lang="ts">
import { API_URL_STORE_DEVICE, req, reqLoad, $allotDevice, $unbindDevice } from '@/api/manage'
import Bind from './Bind.vue'
import { ref, reactive, getCurrentInstance, onMounted } from 'vue'
import { useRoute } from 'vue-router'
const { $infoBox, $access } = getCurrentInstance()!.appContext.config.globalProperties
const jeepayModelAgentList = ref()
const bind = ref()
const infoTable = ref()
const allotFormModel = ref()
const deviceType = 6
let tableColumns = reactive([
{ title: '设备号', fixed: 'left', dataIndex: 'deviceNo', },
{ key: 'provider', title: '设备厂商', dataIndex: 'provider'},
{ title: '服务商号', dataIndex: 'agentNo', agentEntCol: true},
{ key: 'bindState', title: '绑定商户信息', },
{ key: 'state', title: '状态'},
{ dataIndex: 'createdAt', title: '创建日期',},
{ key: 'operation', title: '操作', fixed: 'right', align: 'center'}
])
let btnLoading = ref(false)
const vdata = reactive({
allotByBatchIdVisible: false, // 批次划拨设备弹窗
searchData: {} as any,
allotObject: {
agentNo: null, // 划拨服务商号
allotType: null, // 划拨类型select-勾选划拨 batch-批次划拨
batchId: null, // 批次号,划拨类型为 batch-批次划拨 时必填
allotDeviceIds: [] as any, // 要划拨的设备ID划拨类型为 select-勾选划拨 时必填
} as any, // 划拨设备对象
allotDeviceIdList: [] as any, // 要划拨的设备ID划拨类型为 select-勾选划拨 时必填
allotRules: {
batchId: [{ required: true, message: '请输入批次号', trigger: 'blur' }]
}
}) as any
onMounted(() => {
vdata.searchData.mchNo = useRoute().query.mchNo
searchFunc()
})
function reqTableDataFunc(params: any) { // 请求table接口数据
return req.list(API_URL_STORE_DEVICE, Object.assign({ deviceType: deviceType }, params))
}
function searchFunc () { // 点击【查询】按钮点击事件
btnLoading.value = true
infoTable.value.refTable(true)
}
function addFunc () { // 业务通用【新增】 函数
$infoBox.message.success('请联系运营平台申请')
}
function bindFunc (record: any) { // 绑定设备函数
bind.value.show(record)
}
function onReset(){ //重置搜索内容
vdata.searchData = {}
}
function updateState (recordId, state) { // 【更新状态】
const title = state ? '确认启用?' : '确认禁用?'
const content = state ? '' : '禁用后该设备将无法使用!'
return new Promise<void>((resolve, reject) => {
$infoBox.confirmDanger(title, content, () => {
return reqLoad.updateById(API_URL_STORE_DEVICE, recordId, { state: state }).then(res => {
searchFunc()
resolve()
}).catch(err => reject(err))
},
() => {
reject(new Error())
})
})
}
// 按批次号划拨
function allotByBatchIdFunc() {
vdata.allotObject.allotType = 'batch'
vdata.allotObject.batchId = ''
vdata.allotByBatchIdVisible = true
}
// 按批次号划拨,批次号输入完成
function allotOk() {
allotFormModel.value.validate().then((valid: any) =>{
vdata.allotByBatchIdVisible = false
jeepayModelAgentList.value.show()
})
}
// 表格多选
function infoTableSelectChangeFunc(selectedRowKeys, selectedRows) {
vdata.allotDeviceIdList = selectedRowKeys
}
// 勾选划拨设备
function allotBatchFunc() {
if (vdata.allotDeviceIdList.length < 1) {
$infoBox.message.error('请选择设备')
} else {
vdata.allotObject.allotType = 'select'
jeepayModelAgentList.value.show()
}
}
// 划拨单个设备
function allotFunc(record) {
vdata.allotObject.allotType = 'select'
vdata.allotDeviceIdList = []
vdata.allotDeviceIdList.push(record.deviceId)
jeepayModelAgentList.value.show()
}
// 解绑设备
function deBindFunc(recordId) {
const title = '确认解绑?'
const content = '解绑后商户将无法使用该设备!'
$infoBox.confirmDanger(title, content, () => {
return $unbindDevice(recordId).then(res => {
infoTable.value.refTable(true)
$infoBox.message.success('解绑成功')
})
})
}
// 服务商选择完成
function searchAgentFinishFunc(selectObject) {
if (vdata.allotDeviceIdList.length > 0) {
vdata.allotObject.allotDeviceIds = vdata.allotDeviceIdList.join(',')
}
vdata.allotObject.agentNo = selectObject[0]
vdata.allotObject.allotOrRecover = selectObject[1]
$allotDevice(vdata.allotObject).then(res => {
vdata.allotDeviceIdList = [] // 清空多选
$infoBox.message.success('保存成功')
jeepayModelAgentList.value.close()
searchFunc()
})
}
</script>

View File

@@ -0,0 +1,257 @@
<template>
<page-header-wrapper>
<a-card class="table-card">
<JeepaySearchForm :searchConditionNum="6" :searchFunc="searchFunc" :resetFunc="onReset">
<jeepay-text-up v-model:value="vdata.searchData['mchServiceName']" :placeholder="'服务商号/名称'" />
<JeepaySearchInfoInput v-model:value="vdata.searchData['mchUserName']" placeholder="用户号/名称" :textUpStyle="true" :mchNoAndName="true" showType="MCH" />
<jeepay-text-up v-model:value="vdata.searchData.deviceNo" :placeholder="'激活码'" />
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData.provider" placeholder="所属厂商">
<a-select-option value="">全部</a-select-option>
<a-select-option v-for="item in pluginList" :key="item.value" :value="item.value">{{ item.text }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData.state" placeholder="激活状态">
<a-select-option value="">全部</a-select-option>
<a-select-option value="0">待激活</a-select-option>
<a-select-option value="1">已激活</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData.bindState" placeholder="绑定状态">
<a-select-option value="">全部</a-select-option>
<a-select-option value="0">未绑定</a-select-option>
<a-select-option value="1">已绑定</a-select-option>
</a-select>
</a-form-item>
</JeepaySearchForm>
<!-- 列表渲染 -->
<JeepayTable
ref="infoTable"
:init-data="false"
:req-table-data-func="reqTableDataFunc"
:table-columns="tableColumns"
:search-data="vdata.searchData"
:rowSelection="{ type: 'checkbox', selectedRowKeys: vdata.allotDeviceIdList, onChange: infoTableSelectChangeFunc }"
row-key="deviceId"
@btnLoadClose="btnLoading=false"
>
<template #topBtnSlot>
<a-button type="primary" @click="addFunc"><plus-outlined />申请新激活码</a-button>
<a-button v-if="$access('ENT_DEVICE_SPEAKER_DEVICE_ALLOT')" type="primary" @click="allotBatchFunc"><partition-outlined />勾选划拨/收回</a-button>
<!-- <a-button v-if="$access('ENT_DEVICE_SPEAKER_DEVICE_ALLOT')" type="primary" @click="allotByBatchIdFunc"><partition-outlined />批次划拨/收回</a-button> -->
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'bindState'">
<template v-if="record.bindState == 0"><exclamation-circle-outlined />未绑定</template>
<template v-else>已绑定商户: {{ record.mchNo }} ({{ record.mchName }})<br>应用 {{ record.appId }}<br>门店 {{ record.storeId }} ({{ record.storeName }})</template>
</template>
<template v-if="column.key === 'provider'">
<a-tag :key="record.provider" color="processing">
{{ (pluginList.find(item => item.value == record.provider) as any).text || '其他' }}
</a-tag>
</template>
<template v-if="column.key === 'state'">
<a-tag v-if="record.state == 0" color="orange">待激活</a-tag>
<a-switch v-else :checked="record.state == 1 ? true : false" @change="(state) => { return updateState(record.deviceId, state)}" />
</template>
<template v-if="column.key === 'expiredTime'">
{{ JSON.parse(record.deviceParams).expiredTime == 0 ? '长期有效' : dateUtil.formatDate(JSON.parse(record.deviceParams).expiredTime) }}
</template>
<template v-if="column.key === 'activeTime'">
<template v-if="record.deviceParams && JSON.parse(record.deviceParams).activeTime">
{{ JSON.parse(record.deviceParams).activeTime }}
</template>
</template>
<template v-if="column.key === 'operation'">
<!-- 操作列插槽 -->
<JeepayTableColumns>
<a-button v-if="($access('ENT_DEVICE_PLUGIN_CDKEY_EDIT') && record.isSelf)" type="link" @click="bindFunc(record)">绑定</a-button>
<a-button v-if="($access('ENT_DEVICE_PLUGIN_CDKEY_EDIT') && record.bindState == 1 && record.isSelf)" type="link" @click="deBindFunc(record.deviceId)">解绑</a-button>
<a-button v-if="$access('ENT_DEVICE_SPEAKER_DEVICE_ALLOT')" type="link" @click="allotFunc(record)">划拨/收回</a-button>
</JeepayTableColumns>
</template>
</template>
</JeepayTable>
</a-card>
<!-- 批次号划拨弹窗 -->
<a-modal v-model:visible="vdata.allotByBatchIdVisible" title="按批次号划拨设备" @ok="allotOk">
<a-form ref="allotFormModel" :model="vdata.allotObject" layout="vertical" :rules="vdata.allotRules">
<a-form-item label="批次号:" name="batchId">
<a-input v-model:value="vdata.allotObject.batchId" placeholder="请输入批次号" />
</a-form-item>
</a-form>
</a-modal>
<!-- 新增页面组件 -->
<InfoAddOrEdit ref="infoAddOrEdit" :callbackFunc="searchFunc" />
<!-- 绑定组件 -->
<Bind ref="bind" :callbackFunc="searchFunc" />
<!-- 选择划拨服务商 -->
<JeepayModelAgentList ref="jeepayModelAgentList" showType="AGENT" @selectFinishFunc="searchAgentFinishFunc" />
</page-header-wrapper>
</template>
<script setup lang="ts">
import { API_URL_STORE_DEVICE, req, reqLoad, $unbindDevice, $allotDevice } from '@/api/manage'
import InfoAddOrEdit from './CommonAddOrEdit.vue'
import Bind from './Bind.vue'
import { ref, reactive, getCurrentInstance, onMounted } from 'vue'
import dateUtil from '@/utils/dateUtil.js'
import { useRoute } from 'vue-router'
import provider from './provider.json'
const pluginList = provider.plugin
const { $infoBox, $access } = getCurrentInstance()!.appContext.config.globalProperties
const infoAddOrEdit = ref()
const bind = ref()
const infoTable = ref()
const deviceType = 5
const jeepayModelAgentList = ref()
const allotFormModel = ref()
let tableColumns = reactive([
{ title: '激活码', fixed: 'left', dataIndex: 'deviceNo', },
{ key: 'provider', title: '厂商', dataIndex: 'provider', },
{ title: '服务商号', dataIndex: 'agentNo', agentEntCol: true, },
{ key: 'bindState', title: '绑定商户信息', },
{ key: 'state', title: '激活状态', },
{ key: 'activeTime', title: '激活时间', },
{ key: 'expiredTime', title: '有效期止', },
{ dataIndex: 'createdAt', title: '创建日期', },
{ key: 'operation', title: '操作', fixed: 'right', align: 'center', }
])
let btnLoading = ref(false)
const vdata = reactive({
searchData: {} as any,
allotObject: {
agentNo: null, // 划拨服务商号
allotType: null, // 划拨类型select-勾选划拨 batch-批次划拨
batchId: null, // 批次号,划拨类型为 batch-批次划拨 时必填
allotDeviceIds: [] as any, // 要划拨的设备ID划拨类型为 select-勾选划拨 时必填
} as any, // 划拨设备对象
allotDeviceIdList: [] as any, // 要划拨的设备ID划拨类型为 select-勾选划拨 时必填
allotRules: {
batchId: [{ required: true, message: '请输入批次号', trigger: 'blur' }]
}
}) as any
onMounted(() => {
vdata.searchData.mchNo = useRoute().query.mchNo
searchFunc()
})
function reqTableDataFunc(params: any) { // 请求table接口数据
return req.list(API_URL_STORE_DEVICE, Object.assign({ deviceType: deviceType }, params))
}
function searchFunc () { // 点击【查询】按钮点击事件
btnLoading.value = true
infoTable.value.refTable(true)
}
function addFunc () { // 业务通用【新增】 函数
$infoBox.message.success('请联系运营平台申请')
}
function bindFunc (record: any) { // 绑定激活码函数
bind.value.show(record)
}
function onReset(){ //重置搜索内容
vdata.searchData = {}
}
function updateState (recordId, state) { // 【更新状态】
return new Promise<void>((resolve, reject) => {
$infoBox.confirmDanger('确认解除激活?', '解除激活后,该激活码将不可用!', () => {
return reqLoad.updateById(API_URL_STORE_DEVICE, recordId, { state: state }).then(res => {
searchFunc()
resolve()
}).catch(err => reject(err))
},
() => {
reject(new Error())
})
})
}
// 解绑激活码
function deBindFunc(recordId) {
const title = '确认解绑?'
const content = '解绑后商户将无法使用该激活码!'
$infoBox.confirmDanger(title, content, () => {
return $unbindDevice(recordId).then(res => {
infoTable.value.refTable(true)
$infoBox.message.success('解绑成功')
})
})
}
// 表格多选
function infoTableSelectChangeFunc(selectedRowKeys, selectedRows) {
vdata.allotDeviceIdList = selectedRowKeys
}
// 按批次号划拨
function allotByBatchIdFunc() {
vdata.allotObject.allotType = 'batch'
vdata.allotObject.batchId = ''
vdata.allotByBatchIdVisible = true
}
// 按批次号划拨,批次号输入完成
function allotOk() {
allotFormModel.value.validate().then((valid: any) =>{
vdata.allotByBatchIdVisible = false
jeepayModelAgentList.value.show()
})
}
// 勾选划拨设备
function allotBatchFunc() {
if (vdata.allotDeviceIdList.length < 1) {
$infoBox.message.error('请选择设备')
} else {
vdata.allotObject.allotType = 'select'
jeepayModelAgentList.value.show()
}
}
// 划拨单个设备
function allotFunc(record) {
vdata.allotObject.allotType = 'select'
vdata.allotDeviceIdList = []
vdata.allotDeviceIdList.push(record.deviceId)
jeepayModelAgentList.value.show()
}
// 划拨/收回选择完成
function searchAgentFinishFunc(selectObject) {
vdata.allotObject.agentNo = selectObject[0]
vdata.allotObject.allotOrRecover = selectObject[1]
if (vdata.allotObject.allotOrRecover == 'allot' && !vdata.allotObject.agentNo) {
$infoBox.message.error('请选择服务商')
return
}
if (vdata.allotDeviceIdList.length > 0) {
vdata.allotObject.allotDeviceIds = vdata.allotDeviceIdList.join(',')
}
$allotDevice(vdata.allotObject).then(res => {
vdata.allotDeviceIdList = [] // 清空多选
$infoBox.message.success('保存成功')
jeepayModelAgentList.value.close()
searchFunc()
})
}
</script>

View File

@@ -0,0 +1,311 @@
<template>
<page-header-wrapper>
<a-card class="table-card">
<JeepaySearchForm :searchConditionNum="6" :searchFunc="searchFunc" :resetFunc="onReset">
<jeepay-text-up v-model:value="vdata.searchData['mchServiceName']" :placeholder="'服务商号/名称'" />
<!-- <JeepaySearchInfoInput v-model:value="vdata.searchData['mchNo']" placeholder="用户号" :textUpStyle="true" :mchNoAndName="true" showType="MCH" />-->
<jeepay-text-up v-model:value="vdata.searchData['mchUserName']" :placeholder="'用户号/名称/手机号'" />
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData['provider']" placeholder="设备厂商">
<a-select-option value="">全部</a-select-option>
<a-select-option v-for="item in posList" :key="item.value" :value="item.value">{{ item.text }}</a-select-option>
</a-select>
</a-form-item>
<jeepay-text-up v-model:value="vdata.searchData['deviceName']" :placeholder="'设备名称/编号'" />
<jeepay-text-up v-model:value="vdata.searchData['storeId']" :placeholder="'门店名称/编号'" />
<jeepay-text-up v-model:value="vdata.searchData['mchApplyName']" :placeholder="'商户名称/商户号'" />
<!-- <jeepay-text-up v-model:value="searchData['deviceNo']" :placeholder="'设备号'" />-->
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData['state']" placeholder="使用状态">
<a-select-option value="">全部</a-select-option>
<a-select-option value="0">禁用</a-select-option>
<a-select-option value="1">启用</a-select-option>
</a-select>
</a-form-item>
</JeepaySearchForm>
<!-- 列表渲染 -->
<JeepayTable
ref="infoTable"
:init-data="false"
:req-table-data-func="reqTableDataFunc"
:table-columns="tableColumns"
:search-data="vdata.searchData"
:rowSelection="{ type: 'checkbox', selectedRowKeys: vdata.allotDeviceIdList, onChange: infoTableSelectChangeFunc }"
row-key="deviceId"
@btnLoadClose="btnLoading=false"
>
<template #topBtnSlot>
<a-button type="primary" @click="addFunc"><plus-outlined />申请新设备</a-button>
<a-button v-if="$access('ENT_DEVICE_SPEAKER_DEVICE_ALLOT')" type="primary" @click="allotBatchFunc"><partition-outlined />勾选划拨/收回</a-button>
<!-- <a-button v-if="$access('ENT_DEVICE_SPEAKER_DEVICE_ALLOT')" type="primary" @click="allotByBatchIdFunc"><partition-outlined />批次划拨/收回</a-button> -->
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'bindState'">
<template v-if="record.bindState == 0"><exclamation-circle-outlined />未绑定</template>
<template v-else>已绑定商户: {{ record.mchNo }} ({{ record.mchName }})<br>应用 {{ record.appId }}<br>门店 {{ record.storeId }} ({{ record.storeName }})</template>
</template>
<template v-if="column.key === 'appName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>应用名称{{record.appName}}</span><br>
<span>应用ID{{record.appId}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.appName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'mchApplyName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>商户名称{{record.mchApplyName}}</span><br>
<span>商户号{{record.mchApplyId}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.mchApplyName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'agentNo'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>服务商名称{{record.agentName}}</span><br>
<span>服务商号{{record.agentNo}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.agentName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'storeId'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>门店名称{{record.storeName}}</span><br>
<span>门店编号{{record.storeId}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.storeName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'state'">
<JeepayTableColState :state="record.state" :showSwitchType="$access('ENT_DEVICE_POS_DEVICE_EDIT')" :onChange="(state) => { return updateState(record.deviceId, state)}" />
</template>
<template v-if="column.key === 'provider'">
<a-tag :key="record.provider" color="processing">
{{ (posList.find(item => item.value == record.provider) as any).text || '其他' }}
</a-tag>
</template>
<template v-if="column.key === 'mchServiceName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>服务商名称{{record.mchServiceName}}</span><br>
<span>服务商号{{record.agentNo}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.mchServiceName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'mchUserName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>用户名称{{record.mchUserName}}</span><br>
<span>用户手机号{{record.mchUserPhone}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.mchUserName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'operation'">
<!-- 操作列插槽 -->
<JeepayTableColumns>
<a-button v-if="($access('ENT_DEVICE_POS_DEVICE_EDIT') && record.isSelf)" type="link" @click="bindFunc(record)">绑定</a-button>
<a-button v-if="($access('ENT_DEVICE_POS_DEVICE_EDIT') && record.bindState == 1 && record.isSelf)" type="link" @click="deBindFunc(record.deviceId)">解绑</a-button>
<a-button v-if="$access('ENT_DEVICE_SPEAKER_DEVICE_ALLOT')" type="link" @click="allotFunc(record)">划拨/收回</a-button>
</JeepayTableColumns>
</template>
</template>
</JeepayTable>
</a-card>
<!-- 批次号划拨弹窗 -->
<a-modal v-model:visible="vdata.allotByBatchIdVisible" title="按批次号划拨设备" @ok="allotOk">
<a-form ref="allotFormModel" :model="vdata.allotObject" layout="vertical" :rules="vdata.allotRules">
<a-form-item label="批次号:" name="batchId">
<a-input v-model:value="vdata.allotObject.batchId" placeholder="请输入批次号" />
</a-form-item>
</a-form>
</a-modal>
<!-- 新增页面组件 -->
<InfoAddOrEdit ref="infoAddOrEdit" :callbackFunc="searchFunc" />
<!-- 绑定组件 -->
<Bind ref="bind" :callbackFunc="searchFunc" />
<!-- 选择划拨服务商 -->
<JeepayModelAgentList ref="jeepayModelAgentList" showType="AGENT" @selectFinishFunc="searchAgentFinishFunc" />
</page-header-wrapper>
</template>
<script setup lang="ts">
import { API_URL_STORE_DEVICE, req, reqLoad, $unbindDevice, $allotDevice } from '@/api/manage'
import InfoAddOrEdit from './CommonAddOrEdit.vue'
import Bind from './Bind.vue'
import { ref, reactive, getCurrentInstance, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import provider from './provider.json'
const posList = provider.pos
const { $infoBox, $access } = getCurrentInstance()!.appContext.config.globalProperties
const infoAddOrEdit = ref()
const bind = ref()
const infoTable = ref()
const deviceType = ref(3)
const jeepayModelAgentList = ref()
const allotFormModel = ref()
let tableColumns = reactive([
// { title: '设备号', fixed: 'left', dataIndex: 'deviceNo', },
// { key: 'provider', title: '设备厂商', dataIndex: 'provider',},
// { title: '服务商号', dataIndex: 'agentNo', agentEntCol: true, },
// { key: 'bindState', title: '绑定商户信息', },
// { key: 'state', title: '状态', },
// { dataIndex: 'createdAt', title: '创建日期', },
// { key: 'operation', title: '操作', fixed: 'right', align: 'center', }
{ key: 'provider', title: '设备厂家', },
{ title: '设备号', dataIndex: 'deviceNo' },
{ title: '设备名称', dataIndex: 'deviceName', },
{ key: 'mchUserName', title: '用户名称', dataIndex: 'mchUserName'},
{ key: 'mchServiceName', title: '服务商名称', dataIndex: 'mchServiceName' },
{ title: "商户名称", key: "mchApplyName", dataIndex: "mchApplyName" },
{ key: 'storeId',title: '门店名称', dataIndex: 'storeId',},
// { key: 'bindState', title: '绑定商户信息', },
{ key: 'state', title: '使用状态',},
{ dataIndex: 'createdAt', title: '创建日期',},
{ key: 'operation', title: '操作', fixed: 'right', align: 'center'}
])
let btnLoading = ref(false)
const vdata = reactive({
searchData: {} as any,
allotObject: {
agentNo: null, // 划拨服务商号
allotType: null, // 划拨类型select-勾选划拨 batch-批次划拨
batchId: null, // 批次号,划拨类型为 batch-批次划拨 时必填
allotDeviceIds: [] as any, // 要划拨的设备ID划拨类型为 select-勾选划拨 时必填
} as any, // 划拨设备对象
allotDeviceIdList: [] as any, // 要划拨的设备ID划拨类型为 select-勾选划拨 时必填
allotRules: {
batchId: [{ required: true, message: '请输入批次号', trigger: 'blur' }]
}
}) as any
onMounted(() => {
vdata.searchData.mchNo = useRoute().query.mchNo
searchFunc()
})
function reqTableDataFunc(params: any) { // 请求table接口数据
return req.list(API_URL_STORE_DEVICE, Object.assign({ deviceType: deviceType.value }, params))
}
function searchFunc () { // 点击【查询】按钮点击事件
btnLoading.value = true
infoTable.value.refTable(true)
}
function addFunc () { // 业务通用【新增】 函数
$infoBox.message.success('请联系运营平台申请')
}
function bindFunc (record: any) { // 绑定设备函数
bind.value.show(record)
}
function onReset(){ //重置搜索内容
vdata.searchData = {}
}
function updateState (recordId, state) { // 【更新状态】
const title = state ? '确认启用?' : '确认禁用?'
const content = state ? '' : '禁用后该设备将无法使用!'
return new Promise<void>((resolve, reject) => {
$infoBox.confirmDanger(title, content, () => {
return reqLoad.updateById(API_URL_STORE_DEVICE, recordId, { state: state }).then(res => {
searchFunc()
resolve()
}).catch(err => reject(err))
},
() => {
reject(new Error())
})
})
}
// 解绑设备
function deBindFunc(recordId) {
const title = '确认解绑?'
const content = '解绑后商户将无法使用该设备!'
$infoBox.confirmDanger(title, content, () => {
return $unbindDevice(recordId).then(res => {
infoTable.value.refTable(true)
$infoBox.message.success('解绑成功')
})
})
}
// 表格多选
function infoTableSelectChangeFunc(selectedRowKeys, selectedRows) {
vdata.allotDeviceIdList = selectedRowKeys
}
// 按批次号划拨
function allotByBatchIdFunc() {
vdata.allotObject.allotType = 'batch'
vdata.allotObject.batchId = ''
vdata.allotByBatchIdVisible = true
}
// 按批次号划拨,批次号输入完成
function allotOk() {
allotFormModel.value.validate().then((valid: any) =>{
vdata.allotByBatchIdVisible = false
jeepayModelAgentList.value.show()
})
}
// 勾选划拨设备
function allotBatchFunc() {
if (vdata.allotDeviceIdList.length < 1) {
$infoBox.message.error('请选择设备')
} else {
vdata.allotObject.allotType = 'select'
jeepayModelAgentList.value.show()
}
}
// 划拨单个设备
function allotFunc(record) {
vdata.allotObject.allotType = 'select'
vdata.allotDeviceIdList = []
vdata.allotDeviceIdList.push(record.deviceId)
jeepayModelAgentList.value.show()
}
// 划拨/收回选择完成
function searchAgentFinishFunc(selectObject) {
vdata.allotObject.agentNo = selectObject[0]
vdata.allotObject.allotOrRecover = selectObject[1]
if (vdata.allotObject.allotOrRecover == 'allot' && !vdata.allotObject.agentNo) {
$infoBox.message.error('请选择服务商')
return
}
if (vdata.allotDeviceIdList.length > 0) {
vdata.allotObject.allotDeviceIds = vdata.allotDeviceIdList.join(',')
}
$allotDevice(vdata.allotObject).then(res => {
vdata.allotDeviceIdList = [] // 清空多选
$infoBox.message.success('保存成功')
jeepayModelAgentList.value.close()
searchFunc()
})
}
</script>

View File

@@ -0,0 +1,351 @@
<template>
<page-header-wrapper>
<a-card class="table-card">
<JeepaySearchForm :searchConditionNum="6" :searchFunc="searchFunc" :resetFunc="onReset">
<jeepay-text-up v-model:value="vdata.searchData['mchServiceName']" :placeholder="'服务商号'" />
<!-- <JeepaySearchInfoInput v-model:value="vdata.searchData['mchUserName']" placeholder="用户号" :textUpStyle="true" :mchNoAndName="true" showType="MCH" />-->
<jeepay-text-up v-model:value="vdata.searchData['mchUserName']" :placeholder="'用户号/名称/手机号'" />
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData['provider']" placeholder="设备厂商">
<a-select-option value="">全部</a-select-option>
<a-select-option v-for="item in printerList" :key="item.value" :value="item.value">{{ item.text }}</a-select-option>
</a-select>
</a-form-item>
<jeepay-text-up v-model:value="vdata.searchData['deviceName']" :placeholder="'设备名称/编号'" />
<jeepay-text-up v-model:value="vdata.searchData['storeId']" :placeholder="'门店名称/编号'" />
<jeepay-text-up v-model:value="vdata.searchData['mchApplyName']" :placeholder="'商户名称/商户号'" />
<!-- <jeepay-text-up v-model:value="vdata.searchData.deviceNo" :placeholder="'设备号'" />-->
<!-- <jeepay-text-up v-model:value="searchData['deviceName']" :placeholder="'应用名称'" />-->
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData['state']" placeholder="使用状态">
<a-select-option value="">全部</a-select-option>
<a-select-option value="0">禁用</a-select-option>
<a-select-option value="1">启用</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData['printMode']" placeholder="打印机模式">
<a-select-option value="1">仅打印</a-select-option>
<a-select-option value="2">仅播报</a-select-option>
<a-select-option value="3">打印并播报</a-select-option>
</a-select>
</a-form-item>
</JeepaySearchForm>
<!-- 列表渲染 -->
<JeepayTable
ref="infoTable"
:init-data="false"
:req-table-data-func="reqTableDataFunc"
:table-columns="tableColumns"
:search-data="vdata.searchData"
:rowSelection="{ type: 'checkbox', selectedRowKeys: vdata.allotDeviceIdList, onChange: infoTableSelectChangeFunc }"
row-key="deviceId"
@btnLoadClose="btnLoading=false"
>
<template #topBtnSlot>
<a-button type="primary" @click="addFunc"><plus-outlined />申请新设备</a-button>
<a-button v-if="$access('ENT_DEVICE_SPEAKER_DEVICE_ALLOT')" type="primary" @click="allotBatchFunc"><partition-outlined />勾选划拨/收回</a-button>
<!-- <a-button v-if="$access('ENT_DEVICE_SPEAKER_DEVICE_ALLOT')" type="primary" @click="allotByBatchIdFunc"><partition-outlined />批次划拨/收回</a-button> -->
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'bindState'">
<template v-if="record.bindState == 0"><exclamation-circle-outlined />未绑定</template>
<template v-else>已绑定商户: {{ record.mchNo }} ({{ record.mchName }})<br>应用 {{ record.appId }}<br>门店 {{ record.storeId }} ({{ record.storeName }})</template>
</template>
<template v-if="column.key === 'state'">
<JeepayTableColState :state="record.state" :showSwitchType="$access('ENT_DEVICE_PRINTER_DEVICE_EDIT')" :onChange="(state) => { return updateState(record.deviceId, state)}" />
</template>
<template v-if="column.key === 'provider'">
<a-tag :key="record.provider" color="processing">
{{ (printerList.find(item => item.value == record.provider) as any).text || '其他' }}
</a-tag>
</template>
<template v-if="column.key === 'jsonBizConfigParams'">
<div v-if="record.jsonBizConfigParams">
<a-tag v-if="record.jsonBizConfigParams.printMode == 1" color="blue">仅打印</a-tag>
<a-tag v-else-if="record.jsonBizConfigParams.printMode == 2" color="orange">仅播报</a-tag>
<a-tag v-else-if="record.jsonBizConfigParams.printMode == 3" color="green">打印并播报</a-tag>
<a-tag v-else>未知</a-tag>
</div>
<div v-else>
<a-tag >未知</a-tag>
</div>
</template>
<template v-if="column.key === 'bizConfigParams'">
{{record.jsonBizConfigParams?record.jsonBizConfigParams.printNum:"--"}}
</template>
<template v-if="column.key === 'appName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>应用名称{{record.appName}}</span><br>
<span>应用ID{{record.appId}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.appName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'mchApplyName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>商户名称{{record.mchApplyName}}</span><br>
<span>商户号{{record.mchApplyId}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.mchApplyName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'mchServiceName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>服务商名称{{record.mchServiceName}}</span><br>
<span>服务商号{{record.agentNo}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.mchServiceName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'mchUserName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>用户名称{{record.mchUserName}}</span><br>
<span>用户手机号{{record.mchUserPhone}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.mchUserName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'storeId'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>门店名称{{record.storeName}}</span><br>
<span>门店编号{{record.storeId}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.storeName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'operation'">
<!-- 操作列插槽 -->
<JeepayTableColumns>
<a-button v-if="$access('ENT_DEVICE_PRINTER_DEVICE_EDIT')" type="link" @click="editFunc(record)">修改</a-button>
<a-button v-if="($access('ENT_DEVICE_PRINTER_DEVICE_EDIT') && record.isSelf)" type="link" @click="bindFunc(record)">绑定</a-button>
<a-button v-if="($access('ENT_DEVICE_PRINTER_DEVICE_EDIT') && record.bindState == 1 && record.isSelf)" type="link" @click="deBindFunc(record.deviceId)">解绑</a-button>
<a-button v-if="$access('ENT_DEVICE_SPEAKER_DEVICE_ALLOT')" type="link" @click="allotFunc(record)">划拨/收回</a-button>
<a-button v-if="$access('ENT_DEVICE_PRINTER_DEVICE_TEST')" type="link" @click="test(record.deviceId)">打印测试</a-button>
<a-button v-if="record.provider === 'fe' && $access('ENT_DEVICE_PRINTER_DEVICE_CLEAR')" type="link" @click="clear(record.deviceId)">清空打印队列</a-button>
</JeepayTableColumns>
</template>
</template>
</JeepayTable>
</a-card>
<!-- 批次号划拨弹窗 -->
<a-modal v-model:visible="vdata.allotByBatchIdVisible" title="按批次号划拨设备" @ok="allotOk">
<a-form ref="allotFormModel" :model="vdata.allotObject" layout="vertical" :rules="vdata.allotRules">
<a-form-item label="批次号:" name="batchId">
<a-input v-model:value="vdata.allotObject.batchId" placeholder="请输入批次号" />
</a-form-item>
</a-form>
</a-modal>
<!-- 新增页面组件 -->
<InfoAddOrEdit ref="infoAddOrEdit" :callbackFunc="searchFunc" />
<!-- 测试设备组件 -->
<TestDevice ref="testDevice" :callbackFunc="searchFunc" />
<!-- 绑定组件 -->
<Bind ref="bind" :callbackFunc="searchFunc" />
<!-- 选择划拨服务商 -->
<JeepayModelAgentList ref="jeepayModelAgentList" showType="AGENT" @selectFinishFunc="searchAgentFinishFunc" />
</page-header-wrapper>
</template>
<script setup lang="ts">
import { API_URL_STORE_DEVICE, req, reqLoad, $clearPrint, $unbindDevice, $allotDevice } from '@/api/manage'
import InfoAddOrEdit from './CommonAddOrEdit.vue'
import TestDevice from './TestDevice.vue'
import { ref, reactive, getCurrentInstance, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import Bind from './Bind.vue'
import provider from './provider.json'
const printerList = provider.printer
const { $infoBox, $access } = getCurrentInstance()!.appContext.config.globalProperties
const infoAddOrEdit = ref()
const testDevice = ref()
const infoTable = ref()
const bind = ref()
const deviceType = ref(2)
const jeepayModelAgentList = ref()
const allotFormModel = ref()
let tableColumns = reactive([
// { title: '设备号', fixed: 'left', dataIndex: 'deviceNo', },
// { key: 'provider', title: '设备厂商', dataIndex: 'provider', },
// { title: '服务商号', dataIndex: 'agentNo', agentEntCol: true, },
// { key: 'bindState', title: '绑定商户信息', },
// { key: 'state', title: '状态', },
// { dataIndex: 'createdAt', title: '创建日期', },
// { key: 'operation', title: '操作', fixed: 'right', align: 'center',}
{ key: 'provider', title: '设备厂商', },
{ title: '设备号', fixed: 'left', dataIndex: 'deviceNo' },
{ title: '设备名称', dataIndex: 'deviceName', },
{ key: 'mchUserName', title: '用户名称', dataIndex: 'mchUserName'},
{ key: 'mchServiceName', title: '服务商名称', dataIndex: 'mchServiceName' },
{ title: "商户名称", key: "mchApplyName", dataIndex: "mchApplyName" },
{ key: 'storeId',title: '门店名称', dataIndex: 'storeId',},
{ title: '服务商号', dataIndex: 'agentNo', agentEntCol: true, },
// { key: 'bindState', title: '绑定商户信息', },
{ key: 'state', title: '使用状态',},
{ key: 'jsonBizConfigParams', title: '打印机模式',},
{ key: 'bizConfigParams', title: '打印联数',},
{ dataIndex: 'createdAt', title: '创建日期',},
{ key: 'operation', title: '操作', fixed: 'right', align: 'center'}
])
let btnLoading = ref(false)
const vdata = reactive({
searchData: {} as any,
allotObject: {
agentNo: null, // 划拨服务商号
allotType: null, // 划拨类型select-勾选划拨 batch-批次划拨
batchId: null, // 批次号,划拨类型为 batch-批次划拨 时必填
allotDeviceIds: [] as any, // 要划拨的设备ID划拨类型为 select-勾选划拨 时必填
} as any, // 划拨设备对象
allotDeviceIdList: [] as any, // 要划拨的设备ID划拨类型为 select-勾选划拨 时必填
allotRules: {
batchId: [{ required: true, message: '请输入批次号', trigger: 'blur' }]
}
}) as any
onMounted(() => {
vdata.searchData.mchNo = useRoute().query.mchNo
searchFunc()
})
function reqTableDataFunc(params: any) { // 请求table接口数据
return req.list(API_URL_STORE_DEVICE, Object.assign({ deviceType: deviceType.value }, params))
}
function searchFunc () { // 点击【查询】按钮点击事件
btnLoading.value = true
infoTable.value.refTable(true)
}
function addFunc () { // 业务通用【新增】 函数
$infoBox.message.success('请联系运营平台申请')
}
function editFunc (record: any) { // 业务通用【修改】 函数
infoAddOrEdit.value.show(record)
}
function onReset(){ //重置搜索内容
vdata.searchData = {}
}
function test(recordId){ //打印测试
testDevice.value.show(recordId, deviceType.value)
}
function bindFunc (record: any) { // 绑定设备函数
bind.value.show(record)
}
function updateState (recordId, state) { // 【更新状态】
const title = state ? '确认启用?' : '确认禁用?'
const content = state ? '' : '禁用后该设备将无法使用!'
return new Promise<void>((resolve, reject) => {
$infoBox.confirmDanger(title, content, () => {
return reqLoad.updateById(API_URL_STORE_DEVICE, recordId, { state: state }).then(res => {
searchFunc()
resolve()
}).catch(err => reject(err))
},
() => {
reject(new Error())
})
})
}
function clear (recordId) { // 【更新状态】
return new Promise<void>((resolve, reject) => {
$infoBox.confirmDanger('', '', () => {
return $clearPrint(recordId).then(res => {
resolve()
}).catch(err => reject(err))
},
() => {
reject(new Error())
})
})
}
// 解绑设备
function deBindFunc(recordId) {
const title = '确认解绑?'
const content = '解绑后商户将无法使用该设备!'
$infoBox.confirmDanger(title, content, () => {
return $unbindDevice(recordId).then(res => {
infoTable.value.refTable(true)
$infoBox.message.success('解绑成功')
})
})
}
// 表格多选
function infoTableSelectChangeFunc(selectedRowKeys, selectedRows) {
vdata.allotDeviceIdList = selectedRowKeys
}
// 按批次号划拨
function allotByBatchIdFunc() {
vdata.allotObject.allotType = 'batch'
vdata.allotObject.batchId = ''
vdata.allotByBatchIdVisible = true
}
// 按批次号划拨,批次号输入完成
function allotOk() {
allotFormModel.value.validate().then((valid: any) =>{
vdata.allotByBatchIdVisible = false
jeepayModelAgentList.value.show()
})
}
// 勾选划拨设备
function allotBatchFunc() {
if (vdata.allotDeviceIdList.length < 1) {
$infoBox.message.error('请选择设备')
} else {
vdata.allotObject.allotType = 'select'
jeepayModelAgentList.value.show()
}
}
// 划拨单个设备
function allotFunc(record) {
vdata.allotObject.allotType = 'select'
vdata.allotDeviceIdList = []
vdata.allotDeviceIdList.push(record.deviceId)
jeepayModelAgentList.value.show()
}
// 划拨/收回选择完成
function searchAgentFinishFunc(selectObject) {
vdata.allotObject.agentNo = selectObject[0]
vdata.allotObject.allotOrRecover = selectObject[1]
if (vdata.allotObject.allotOrRecover == 'allot' && !vdata.allotObject.agentNo) {
$infoBox.message.error('请选择服务商')
return
}
if (vdata.allotDeviceIdList.length > 0) {
vdata.allotObject.allotDeviceIds = vdata.allotDeviceIdList.join(',')
}
$allotDevice(vdata.allotObject).then(res => {
vdata.allotDeviceIdList = [] // 清空多选
$infoBox.message.success('保存成功')
jeepayModelAgentList.value.close()
searchFunc()
})
}
</script>

View File

@@ -0,0 +1,229 @@
<template>
<page-header-wrapper>
<a-card class="table-card">
<JeepaySearchForm :searchFunc="searchFunc" :resetFunc="onReset">
<jeepay-text-up v-model:value="vdata.searchData['mchServiceName']" :placeholder="'服务商号/名称'" />
<JeepaySearchInfoInput v-model:value="vdata.searchData['mchUserName']" placeholder="用户号/名称" :textUpStyle="true" :mchNoAndName="true" showType="MCH" />
<jeepay-text-up v-model:value="vdata.searchData.deviceNo" :placeholder="'设备号'" />
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData.state" placeholder="状态">
<a-select-option value="">全部</a-select-option>
<a-select-option value="0">禁用</a-select-option>
<a-select-option value="1">启用</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData.bindState" placeholder="绑定状态">
<a-select-option value="">全部</a-select-option>
<a-select-option value="0">未绑定</a-select-option>
<a-select-option value="1">已绑定</a-select-option>
</a-select>
</a-form-item>
</JeepaySearchForm>
<!-- 列表渲染 -->
<JeepayTable
ref="infoTable"
:init-data="false"
:req-table-data-func="reqTableDataFunc"
:table-columns="tableColumns"
:search-data="vdata.searchData"
:rowSelection="{ type: 'checkbox', selectedRowKeys: vdata.allotDeviceIdList, onChange: infoTableSelectChangeFunc }"
row-key="deviceId"
@btnLoadClose="btnLoading=false"
>
<template #topBtnSlot>
<a-button v-if="$access('ENT_DEVICE_FACE_APP_ADD')" type="primary" @click="addFunc"><plus-outlined />申请新设备</a-button>
<a-button v-if="$access('ENT_DEVICE_FACE_APP_ALLOT')" type="primary" @click="allotBatchFunc"><partition-outlined />勾选划拨/收回</a-button>
<!-- <a-button v-if="$access('ENT_DEVICE_SPEAKER_DEVICE_ALLOT')" type="primary" @click="allotByBatchIdFunc"><partition-outlined />批次划拨/收回</a-button> -->
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'bindState'">
<template v-if="record.bindState == 0"><exclamation-circle-outlined />未绑定</template>
<template v-else>已绑定商户: {{ record.mchNo }} ({{ record.mchName }})<br>应用 {{ record.appId }}<br>门店 {{ record.storeId }} ({{ record.storeName }})</template>
</template>
<template v-if="column.key === 'state'">
<JeepayTableColState :state="record.state" :showSwitchType="$access('ENT_DEVICE_AUTO_POS_EDIT')" :onChange="(state) => { return updateState(record.deviceId, state)}" />
</template>
<template v-if="column.key === 'provider'">
<a-tag color="blue">
支付宝如意Lite
</a-tag>
</template>
<template v-if="column.key === 'operation'">
<!-- 操作列插槽 -->
<JeepayTableColumns>
<a-button v-if="$access('ENT_DEVICE_RUYI_EDIT') && record.isSelf" type="link" @click="bindFunc(record)">绑定</a-button>
<a-button v-if="$access('ENT_DEVICE_RUYI_EDIT') && record.bindState == 1 && record.isSelf" type="link" @click="deBindFunc(record.deviceId)">解绑</a-button>
<a-button v-if="$access('ENT_DEVICE_RUYI_ALLOT')" type="link" @click="allotFunc(record)">划拨/收回</a-button>
</JeepayTableColumns>
</template>
</template>
</JeepayTable>
</a-card>
<!-- 批次号划拨弹窗 -->
<a-modal v-model:visible="vdata.allotByBatchIdVisible" title="按批次号划拨设备" @ok="allotOk">
<a-form ref="allotFormModel" :model="vdata.allotObject" layout="vertical" :rules="vdata.allotRules">
<a-form-item label="批次号:" name="batchId">
<a-input v-model:value="vdata.allotObject.batchId" placeholder="请输入批次号" />
</a-form-item>
</a-form>
</a-modal>
<!-- 绑定组件 -->
<Bind ref="bind" :callbackFunc="searchFunc" />
<JeepayModelAgentList ref="jeepayModelAgentList" showType="AGENT" @selectFinishFunc="searchAgentFinishFunc" />
</page-header-wrapper>
</template>
<script setup lang="ts">
import { API_URL_STORE_DEVICE, req, reqLoad, $allotDevice, $unbindDevice } from '@/api/manage'
import Bind from './Bind.vue'
import { ref, reactive, getCurrentInstance, onMounted } from 'vue'
import { useRoute } from 'vue-router'
const { $infoBox, $access } = getCurrentInstance()!.appContext.config.globalProperties
const jeepayModelAgentList = ref()
const bind = ref()
const infoTable = ref()
const allotFormModel = ref()
const deviceType = 7
let tableColumns = reactive([
{ title: '设备号', fixed: 'left', dataIndex: 'deviceNo', },
{ key: 'provider', title: '设备厂商', dataIndex: 'provider'},
{ title: '服务商号', dataIndex: 'agentNo', agentEntCol: true},
{ key: 'bindState', title: '绑定商户信息', },
{ key: 'state', title: '状态'},
{ dataIndex: 'createdAt', title: '创建日期',},
{ key: 'operation', title: '操作', fixed: 'right', align: 'center'}
])
let btnLoading = ref(false)
const vdata = reactive({
allotByBatchIdVisible: false, // 批次划拨设备弹窗
searchData: {} as any,
allotObject: {
agentNo: null, // 划拨服务商号
allotType: null, // 划拨类型select-勾选划拨 batch-批次划拨
batchId: null, // 批次号,划拨类型为 batch-批次划拨 时必填
allotDeviceIds: [] as any, // 要划拨的设备ID划拨类型为 select-勾选划拨 时必填
} as any, // 划拨设备对象
allotDeviceIdList: [] as any, // 要划拨的设备ID划拨类型为 select-勾选划拨 时必填
allotRules: {
batchId: [{ required: true, message: '请输入批次号', trigger: 'blur' }]
}
}) as any
onMounted(() => {
vdata.searchData.mchNo = useRoute().query.mchNo
searchFunc()
})
function reqTableDataFunc(params: any) { // 请求table接口数据
return req.list(API_URL_STORE_DEVICE, Object.assign({ deviceType: deviceType }, params))
}
function searchFunc () { // 点击【查询】按钮点击事件
btnLoading.value = true
infoTable.value.refTable(true)
}
function addFunc () { // 业务通用【新增】 函数
$infoBox.message.success('请联系运营平台申请')
}
function bindFunc (record: any) { // 绑定设备函数
bind.value.show(record)
}
function onReset(){ //重置搜索内容
vdata.searchData = {}
}
function updateState (recordId, state) { // 【更新状态】
const title = state ? '确认启用?' : '确认禁用?'
const content = state ? '' : '禁用后该设备将无法使用!'
return new Promise<void>((resolve, reject) => {
$infoBox.confirmDanger(title, content, () => {
return reqLoad.updateById(API_URL_STORE_DEVICE, recordId, { state: state }).then(res => {
searchFunc()
resolve()
}).catch(err => reject(err))
},
() => {
reject(new Error())
})
})
}
// 按批次号划拨
function allotByBatchIdFunc() {
vdata.allotObject.allotType = 'batch'
vdata.allotObject.batchId = ''
vdata.allotByBatchIdVisible = true
}
// 按批次号划拨,批次号输入完成
function allotOk() {
allotFormModel.value.validate().then((valid: any) =>{
vdata.allotByBatchIdVisible = false
jeepayModelAgentList.value.show()
})
}
// 表格多选
function infoTableSelectChangeFunc(selectedRowKeys, selectedRows) {
vdata.allotDeviceIdList = selectedRowKeys
}
// 勾选划拨设备
function allotBatchFunc() {
if (vdata.allotDeviceIdList.length < 1) {
$infoBox.message.error('请选择设备')
} else {
vdata.allotObject.allotType = 'select'
jeepayModelAgentList.value.show()
}
}
// 划拨单个设备
function allotFunc(record) {
vdata.allotObject.allotType = 'select'
vdata.allotDeviceIdList = []
vdata.allotDeviceIdList.push(record.deviceId)
jeepayModelAgentList.value.show()
}
// 解绑设备
function deBindFunc(recordId) {
const title = '确认解绑?'
const content = '解绑后商户将无法使用该设备!'
$infoBox.confirmDanger(title, content, () => {
return $unbindDevice(recordId).then(res => {
infoTable.value.refTable(true)
$infoBox.message.success('解绑成功')
})
})
}
// 服务商选择完成
function searchAgentFinishFunc(selectObject) {
if (vdata.allotDeviceIdList.length > 0) {
vdata.allotObject.allotDeviceIds = vdata.allotDeviceIdList.join(',')
}
vdata.allotObject.agentNo = selectObject[0]
vdata.allotObject.allotOrRecover = selectObject[1]
$allotDevice(vdata.allotObject).then(res => {
vdata.allotDeviceIdList = [] // 清空多选
$infoBox.message.success('保存成功')
jeepayModelAgentList.value.close()
searchFunc()
})
}
</script>

View File

@@ -0,0 +1,359 @@
<template>
<page-header-wrapper>
<a-card class="table-card">
<JeepaySearchForm :searchFunc="searchFunc" :resetFunc="onReset">
<!-- <jeepay-text-up v-model:value="vdata.searchData.agentNo" :placeholder="'服务商号/名称'" />-->
<!-- <JeepaySearchInfoInput v-model:value="vdata.searchData['mchNo']" placeholder="用户号" :textUpStyle="true" :mchNoAndName="true" showType="MCH" />-->
<!-- <jeepay-text-up v-model:value="vdata.searchData['mchNo']" :placeholder="'用户号/名称/手机号'" />-->
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData.qrcType" placeholder="码牌类型">
<a-select-option value="">全部</a-select-option>
<a-select-option value="0">电子码</a-select-option>
<a-select-option value="1">实体码牌</a-select-option>
<a-select-option value="2">实体立牌</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData.entryPage" placeholder="页面类型">
<a-select-option value="">全部</a-select-option>
<a-select-option value="default">默认</a-select-option>
<a-select-option value="h5">H5</a-select-option>
<a-select-option value="lite">小程序</a-select-option>
</a-select>
</a-form-item>
<jeepay-text-up v-model:value="vdata.searchData['mchServiceName']" :placeholder="'服务商号'" />
<JeepaySearchInfoInput v-model:value="vdata.searchData['mchUserName']" placeholder="用户号/名称" :textUpStyle="true" :mchNoAndName="true" showType="MCH" />
<jeepay-text-up v-model:value="vdata.searchData.qrcAlias" placeholder="二维码名称/编号" />
<jeepay-text-up v-model:value="vdata.searchData['mchApplyName']" :placeholder="'商户名称/商户号'" />
<jeepay-text-up v-model:value="vdata.searchData.storeId" placeholder="门店名称/门店编号" />
<jeepay-text-up v-model:value="vdata.searchData.appId" placeholder="应用名称/应用ID" />
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData.bindState" placeholder="绑定状态">
<a-select-option value="">全部</a-select-option>
<a-select-option value="0">未绑定</a-select-option>
<a-select-option value="1">已绑定</a-select-option>
</a-select>
</a-form-item>
</JeepaySearchForm>
<!-- 列表渲染 -->
<JeepayTable
ref="infoTable"
:init-data="false"
:req-table-data-func="reqTableDataFunc"
:table-columns="tableColumns"
:search-data="vdata.searchData"
:rowSelection="{ type: 'checkbox', selectedRowKeys: vdata.allotDeviceIdList, onChange: infoTableSelectChangeFunc }"
row-key="deviceId"
@btnLoadClose="btnLoading=false"
>
<template #topBtnSlot>
<a-button type="primary" @click="addFunc"><plus-outlined />申请新设备</a-button>
<a-button v-if="$access('ENT_DEVICE_SPEAKER_DEVICE_ALLOT')" type="primary" @click="allotBatchFunc"><partition-outlined />勾选划拨/收回</a-button>
<!-- <a-button v-if="$access('ENT_DEVICE_SPEAKER_DEVICE_ALLOT')" type="primary" @click="allotByBatchIdFunc"><partition-outlined />批次划拨/收回</a-button> -->
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'appName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>应用名称{{record.appName}}</span><br>
<span>应用ID{{record.appId}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.appName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'mchApplyName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>商户名称{{record.mchApplyName}}</span><br>
<span>商户号{{record.mchApplyId}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.mchApplyName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'agentNo'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>服务商名称{{record.agentName}}</span><br>
<span>服务商号{{record.agentNo}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.agentName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'storeId'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>门店名称{{record.storeName}}</span><br>
<span>门店编号{{record.storeId}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.storeName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'qrcType'">
<a-tag v-if="record.qrcType == 0" color="blue">电子码牌</a-tag>
<a-tag v-if="record.qrcType == 1" color="orange">实体码牌</a-tag>
<a-tag v-if="record.qrcType == 2" color="green">实体立牌</a-tag>
</template>
<template v-if="column.key === 'bindState'">
<template v-if="record.bindState == 0"><exclamation-circle-outlined />未绑定</template>
<template v-else>已绑定商户: {{ record.mchNo }} ({{ record.mchName }})<br>门店 {{ record.storeId }} ({{ record.storeName }})</template>
</template>
<template v-if="column.key === 'bindType'">
<a-tag v-if="record.bindType == 0" color="green">门店</a-tag>
<a-tag v-if="record.bindType == 1" color="purple">码牌</a-tag>
</template>
<template v-if="column.key === 'state'">
<JeepayTableColState :state="record.state" :showSwitchType="$access('ENT_DEVICE_SPEAKER_DEVICE_EDIT')" :onChange="(state) => { return updateState(record.deviceId, state)}" />
</template>
<template v-if="column.key === 'provider'">
<a-tag :key="record.provider" color="processing">
{{ (speakerList.find(item => item.value == record.provider) as any).text || '其他' }}
</a-tag>
</template>
<template v-if="column.key === 'mchServiceName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>服务商名称{{record.mchServiceName}}</span><br>
<span>服务商号{{record.agentNo}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.mchServiceName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'mchUserName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>用户名称{{record.mchUserName}}</span><br>
<span>用户手机号{{record.mchUserPhone}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.mchUserName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'fixedFlag'">
<span v-if="record.fixedFlag === 0">任意金额</span>
<span v-else>{{ (record.fixedPayAmount / 100).toFixed(2)}}</span>
</template>
<template v-if="column.key === 'operation'">
<!-- 操作列插槽 -->
<JeepayTableColumns>
<a-button v-if="$access('ENT_DEVICE_SPEAKER_DEVICE_EDIT') && record.isSelf" type="link" @click="bindFunc(record)">绑定</a-button>
<a-button v-if="$access('ENT_DEVICE_SPEAKER_DEVICE_EDIT') && record.bindState == 1 && record.isSelf" type="link" @click="unBindFunc(record.deviceId)">解绑</a-button>
<a-button v-if="$access('ENT_DEVICE_SPEAKER_DEVICE_ALLOT')" type="link" @click="allotFunc(record)">划拨/收回</a-button>
<a-button v-if="$access('ENT_DEVICE_SPEAKER_DEVICE_TEST')" type="link" @click="test(record.deviceId)">播报测试</a-button>
</JeepayTableColumns>
</template>
</template>
</JeepayTable>
</a-card>
<!-- 批次号划拨弹窗 -->
<a-modal v-model:visible="vdata.allotByBatchIdVisible" title="按批次号划拨设备" @ok="allotOk">
<a-form ref="allotFormModel" :model="vdata.allotObject" layout="vertical" :rules="vdata.allotRules">
<a-form-item label="批次号:" name="batchId">
<a-input v-model:value="vdata.allotObject.batchId" placeholder="请输入批次号" />
</a-form-item>
</a-form>
</a-modal>
<!-- 新增页面组件 -->
<InfoAddOrEdit ref="infoAddOrEdit" :callbackFunc="searchFunc" />
<!-- 绑定组件 -->
<Bind ref="bind" :callbackFunc="searchFunc" />
<!-- 测试设备组件 -->
<TestDevice ref="testDevice" :callbackFunc="searchFunc" />
<!-- 选择划拨代理商 -->
<JeepayModelAgentList ref="jeepayModelAgentList" showType="AGENT" @selectFinishFunc="searchAgentFinishFunc" />
</page-header-wrapper>
</template>
<script setup lang="ts">
import { API_URL_STORE_DEVICE, req, reqLoad, $unbindDevice, $allotDevice } from '@/api/manage'
import InfoAddOrEdit from './CommonAddOrEdit.vue'
import { ref, reactive, getCurrentInstance, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import Bind from './BindSpeaker.vue'
import TestDevice from './TestDevice.vue'
import provider from './provider.json'
const speakerList = provider.speaker
const { $infoBox, $access } = getCurrentInstance()!.appContext.config.globalProperties
const infoAddOrEdit = ref()
const infoTable = ref()
const bind = ref()
const testDevice = ref()
const deviceType = ref(1)
const jeepayModelAgentList = ref()
const allotFormModel = ref()
let tableColumns = reactive([
// // { title: '设备ID', fixed: 'left', dataIndex: 'deviceId' },
// { title: '设备号', dataIndex: 'deviceNo'},
// // { title: '批次号', dataIndex: 'batchId' },
// { title: '绑定码牌ID', dataIndex: 'bindQrcId' },
// { key: 'provider', title: '设备厂商', dataIndex: 'provider'},
// { title: '代理商号', dataIndex: 'agentNo', agentEntCol: true, },
// { key: 'bindState', title: '绑定商户信息', },
// { key: 'bindType', title: '绑定类型'},
// { key: 'state', title: '状态' },
// { dataIndex: 'createdAt', title: '创建日期', },
// { key: 'operation', title: '操作', fixed: 'right', align: 'center', }
{ key: 'provider', title: '设备厂商', },
{ key: 'deviceNo',title: '设备号', dataIndex: 'deviceNo' },
{ key: 'deviceName',title: '设备名称', dataIndex: 'deviceName', },
{ key: 'bindQrcId', title: '二维码编号', },
{ key: 'mchUserName', title: '用户名称', dataIndex: 'mchUserName'},
{ key: 'mchServiceName', title: '服务商名称', dataIndex: 'mchServiceName' },
{ title: "商户名称", key: "mchApplyName", dataIndex: "mchApplyName" },
{ key: 'storeId', title: '门店名称', dataIndex: 'storeId', },
// { key: 'bindState', title: '绑定商户信息', },
{ key: 'bindType', title: '绑定类型',},
{ key: 'state', title: '使用状态', },
{ key: 'fixedFlag', title: '固定金额', },
{ dataIndex: 'createdAt', title: '创建日期',},
{ key: 'operation', title: '操作', fixed: 'right', align: 'center'}
])
let btnLoading = ref(false)
const vdata = reactive({
searchData: {} as any,
isSupportAgentAllot: false, // 是否支持代理商划拨设备
allotObject: {
agentNo: null, // 划拨代理商号
allotType: null, // 划拨类型select-勾选划拨 batch-批次划拨
batchId: null, // 批次号,划拨类型为 batch-批次划拨 时必填
allotDeviceIds: [] as any, // 要划拨的设备ID划拨类型为 select-勾选划拨 时必填
} as any, // 划拨设备对象
allotDeviceIdList: [] as any, // 要划拨的设备ID划拨类型为 select-勾选划拨 时必填
allotRules: {
batchId: [{ required: true, message: '请输入批次号', trigger: 'blur' }]
}
}) as any
onMounted(() => {
vdata.searchData.mchNo = useRoute().query.mchNo
vdata.searchData.deviceId = useRoute().query.deviceId
searchFunc()
})
function reqTableDataFunc(params: any) { // 请求table接口数据
return req.list(API_URL_STORE_DEVICE, Object.assign({ deviceType: deviceType.value }, params))
}
function searchFunc () { // 点击【查询】按钮点击事件
btnLoading.value = true
infoTable.value.refTable(true)
}
function addFunc () { // 业务通用【新增】 函数
$infoBox.message.success('请联系运营平台申请')
}
function bindFunc (record: any) { // 绑定设备函数
bind.value.show(record)
}
function test(recordId){ // 测试
testDevice.value.show(recordId, deviceType.value)
}
function onReset(){ //重置搜索内容
vdata.searchData = {}
}
function updateState (recordId, state) { // 【更新状态】
const title = state ? '确认启用?' : '确认禁用?'
const content = state ? '' : '禁用后该设备将无法使用!'
return new Promise<void>((resolve, reject) => {
$infoBox.confirmDanger(title, content, () => {
return reqLoad.updateById(API_URL_STORE_DEVICE, recordId, { state: state }).then(res => {
searchFunc()
resolve()
}).catch(err => reject(err))
},
() => {
reject(new Error())
})
})
}
// 解绑设备
function unBindFunc(recordId) {
const title = '确认解绑?'
const content = '解绑后商户将无法使用该设备!'
$infoBox.confirmDanger(title, content, () => {
return $unbindDevice(recordId).then(res => {
infoTable.value.refTable(true)
$infoBox.message.success('解绑成功')
})
})
}
// 表格多选
function infoTableSelectChangeFunc(selectedRowKeys, selectedRows) {
vdata.allotDeviceIdList = selectedRowKeys
}
// 按批次号划拨
function allotByBatchIdFunc() {
vdata.allotObject.allotType = 'batch'
vdata.allotObject.batchId = ''
vdata.allotByBatchIdVisible = true
}
// 按批次号划拨,批次号输入完成
function allotOk() {
allotFormModel.value.validate().then((valid: any) =>{
vdata.allotByBatchIdVisible = false
jeepayModelAgentList.value.show()
})
}
// 勾选划拨设备
function allotBatchFunc() {
if (vdata.allotDeviceIdList.length < 1) {
$infoBox.message.error('请选择设备')
} else {
vdata.allotObject.allotType = 'select'
jeepayModelAgentList.value.show()
}
}
// 划拨单个设备
function allotFunc(record) {
vdata.allotObject.allotType = 'select'
vdata.allotDeviceIdList = []
vdata.allotDeviceIdList.push(record.deviceId)
jeepayModelAgentList.value.show()
}
// 划拨/收回选择完成
function searchAgentFinishFunc(selectObject) {
vdata.allotObject.agentNo = selectObject[0]
vdata.allotObject.allotOrRecover = selectObject[1]
if (vdata.allotObject.allotOrRecover == 'allot' && !vdata.allotObject.agentNo) {
$infoBox.message.error('请选择服务商')
return
}
if (vdata.allotDeviceIdList.length > 0) {
vdata.allotObject.allotDeviceIds = vdata.allotDeviceIdList.join(',')
}
$allotDevice(vdata.allotObject).then(res => {
vdata.allotDeviceIdList = [] // 清空多选
$infoBox.message.success('保存成功')
jeepayModelAgentList.value.close()
searchFunc()
})
}
</script>

View File

@@ -0,0 +1,58 @@
<template>
<a-modal v-model:visible="vdata.isShow" title="测试" @ok="handleOkFunc">
<a-form ref="infoFormModel" :model="vdata.saveObject" :label-col="{span: 6}" :wrapper-col="{span: 15}" :rules="rules">
<a-form-item label="金额:" name="amount">
<a-input v-model:value="vdata.saveObject.amount" />
</a-form-item>
</a-form>
</a-modal>
</template>
<script lang="ts" setup>
import { $speakTest, $printTest }from '@/api/manage'
import { reactive, ref } from 'vue'
const infoFormModel = ref()
const vdata = reactive({
loading: false, // 按钮上的loading
isShow: false, // 是否显示弹层/抽屉
saveObject: {} as any, // 数据对象
recordId: null, // 更新对象ID
deviceType: 1 // 设备类型1-云喇叭 2-云打印
})
const rules = {
amount: [{ required: true, pattern: /^(([1-9]{1}\d{0,6})|(0{1}))(\.\d{1,2})?$/, message: '请输入正确的金额', trigger: 'blur' }]
}
defineExpose({show})
function show (recordId, deviceType) { // 弹层打开事件
if (infoFormModel.value !== undefined) {
infoFormModel.value.resetFields()
}
vdata.isShow = true
vdata.saveObject.amount = ''
vdata.recordId = recordId
vdata.deviceType = deviceType
}
function handleOkFunc () { // 点击【确认】按钮事件
if(vdata.deviceType == 1) {
infoFormModel.value.validate().then(valid =>{
$speakTest(vdata.recordId, vdata.saveObject.amount)
})
} else if(vdata.deviceType == 2) {
infoFormModel.value.validate().then(valid =>{
$printTest(vdata.recordId, vdata.saveObject.amount)
})
}
}
// 点击遮罩层关闭抽屉
function onClose () {
vdata.isShow = false
}
</script>

View File

@@ -0,0 +1 @@
{"all":[{"text":"智谷联","value":"zgwl"},{"text":"品生","value":"ps"},{"text":"博实结","value":"bsj"},{"text":"飞鹅","value":"fe"},{"text":"财来聚","value":"clj"},{"text":"微收银","value":"wsy"},{"text":"小精灵","value":"xjl"},{"text":"智网","value":"zw"},{"text":"零零智能","value":"llzn"}],"speaker":[{"text":"智谷联","value":"zgwl"},{"text":"品生","value":"ps"},{"text":"博实结","value":"bsj"},{"text":"智网","value":"zw"},{"text":"拉卡拉","value":"lkls"}],"printer":[{"text":"智谷联","value":"zgwl"},{"text":"飞鹅","value":"fe"},{"text":"博实结","value":"bsj"},{"text":"智网","value":"zw"}],"pos":[{"text":"智谷联","value":"zgwl"},{"text":"品生","value":"ps"},{"text":"博实结","value":"bsj"},{"text":"零零智能","value":"llzn"}],"plugin":[{"text":"财来聚","value":"clj"},{"text":"微收银","value":"wsy"},{"text":"小精灵","value":"xjl"}]}

View File

@@ -0,0 +1,21 @@
<template>
<div class="result-err">
<img src="~@/assets/svg/403.svg" alt="">
<div>
抱歉您无权访问此页
</div>
<a-button type="primary" style="margin-top:30px" @click="toHome">
返回首页
</a-button>
</div>
</template>
<script setup lang="ts">
import {useRoute} from 'vue-router'
import router from '@/router'
function toHome () {
router.push({ path: '/' })
}
</script>

View File

@@ -0,0 +1,21 @@
<template>
<div class="result-err">
<img src="~@/assets/svg/404.svg" alt="">
<div>
抱歉您访问的页面不存在
</div>
<a-button type="primary" style="margin-top:30px" @click="toHome">
返回首页
</a-button>
</div>
</template>
<script lang="ts" setup>
import {useRoute} from 'vue-router'
import router from '@/router'
function toHome () {
router.push({ path: '/' })
}
</script>

View File

@@ -0,0 +1,21 @@
<template>
<div class="result-err">
<img src="~@/assets/svg/500.svg" alt="">
<div>
对不起服务器错误
</div>
<a-button type="primary" style="margin-top:30px" @click="toHome">
返回首页
</a-button>
</div>
</template>
<script setup lang="ts">
import {useRoute} from 'vue-router'
import router from '@/router'
function toHome () {
router.push({ path: '/' })
}
</script>

View File

@@ -0,0 +1,76 @@
<template>
<div>
<a-card style="width: 500px;padding:30px">
<div>
<p>服务商信息</p>
</div>
<a-divider style="margin: 20px 0 10px 0;" />
<div class="item">
<span class="label">服务商名称</span>
<span class="desc">{{ vdata.agentInfo.agentName }}</span>
</div>
<div class="item">
<span class="label">服务商简称</span>
<span class="desc">{{ vdata.agentInfo.agentShortName }}</span>
</div>
<div class="item">
<span class="label">登录名</span>
<span class="desc">{{ vdata.agentInfo.loginUsername }}</span>
</div>
<div class="item">
<span class="label">服务商号</span>
<span class="desc">{{ vdata.agentInfo.agentNo }}</span>
</div>
<div class="item">
<span class="label">渠道商号</span>
<span class="desc">{{ vdata.agentInfo.isvNo }}</span>
</div>
<div class="item">
<span class="label">上级服务商号</span>
<span class="desc">{{ vdata.agentInfo.pid }}</span>
</div>
<div class="item">
<span class="label">注册时间</span>
<span class="desc">{{ vdata.agentInfo.createdAt }}</span>
</div>
</a-card>
</div>
</template>
<script lang="ts" setup>
import { $getMainUserInfo } from '@/api/manage' // 接口
import {reactive} from 'vue'
const vdata = reactive ({
agentInfo: {} as any
})
$getMainUserInfo().then(res => {
vdata.agentInfo = res
console.log(vdata.agentInfo)
})
</script>
<style lang="less" scoped>
p {
font-size: 16px;
font-weight: 500;
margin:0 5px;
}
.desc {
font-weight: 500;
font-size: 14px;
letter-spacing: 0.05em;
color: #262626;
}
.label {
font-weight: 500;
font-size: 14px;
letter-spacing: 0.05em;
color: #999;
}
.item {
display: flex;
justify-content: space-between;
padding: 8px;
}
</style>

View File

@@ -0,0 +1,88 @@
<template>
<div>
<a-card style="width: 500px;padding:30px">
<div>
<p>商户中心</p>
</div>
<a-divider style="margin: 20px 0 10px 0;" />
<div class="item">
<span class="label">用户名称</span>
<span class="desc">{{ vdata.mchInfo.mchName }}</span>
</div>
<div class="item">
<span class="label">商户简称</span>
<span class="desc">{{ vdata.mchInfo.mchShortName }}</span>
</div>
<div class="item">
<span class="label">登录名</span>
<span class="desc">{{ vdata.mchInfo.loginUsername }}</span>
</div>
<div class="item">
<span class="label">用户号</span>
<span class="desc">{{ vdata.mchInfo.mchNo }}</span>
</div>
<div class="item">
<span class="label">商户类型</span>
<span class="desc">{{ vdata.mchInfo.type==1?'普通商户':'特约商户' }}</span>
</div>
<div class="item">
<span class="label">服务商号</span>
<span class="desc">{{ vdata.mchInfo.isvNo }}</span>
</div>
<!-- <div class="item">
<span class="label">代理商号</span>
<span class="desc">{{ vdata.mchInfo.agentNo }}</span>
</div> -->
<div class="item">
<span class="label">注册时间</span>
<span class="desc">{{ vdata.mchInfo.createdAt }}</span>
</div>
<!-- <div class="item">
<span class="label">签约状态</span>
<span class="desc"><a-tag color="cyan">已签约</a-tag></span>
</div>
<div class="item">
<span class="label">签约到期时间</span>
<span class="desc">{{ vdata.mchInfo.createdAt }}</span>
</div> -->
</a-card>
</div>
</template>
<script lang="ts" setup>
import { $getMainUserInfo } from '@/api/manage' // 接口
import {reactive} from 'vue'
const vdata = reactive ({
mchInfo: {} as any
})
$getMainUserInfo().then(res => {
vdata.mchInfo = res
console.log(vdata.mchInfo)
})
</script>
<style lang="less" scoped>
p {
font-size: 16px;
font-weight: 500;
margin:0 5px;
}
.desc {
font-weight: 500;
font-size: 14px;
letter-spacing: 0.05em;
color: #262626;
}
.label {
font-weight: 500;
font-size: 14px;
letter-spacing: 0.05em;
color: #999;
}
.item {
display: flex;
justify-content: space-between;
padding: 8px;
}
</style>

View File

@@ -0,0 +1,387 @@
<template>
<a-drawer
v-model:visible="vdata.visible"
:mask-closable="false"
:title=" vdata.isAdd ? '新增商户' : '修改商户' "
:body-style="{ paddingBottom: '80px' }"
width="40%"
class="drawer-width"
@close="onClose"
>
<a-form v-if="vdata.visible" ref="infoFormModel" :model="vdata.saveObject" layout="vertical" :rules="vdata.rules">
<a-row justify="space-between" type="flex">
<a-col :span="10">
<a-form-item label="用户名称" name="mchName">
<a-input
v-model:value="vdata.saveObject['mchName']"
placeholder="请输入商户名称"
/>
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="登录名" name="loginUsername">
<a-input
v-model:value="vdata.saveObject['loginUsername']"
placeholder="请输入商户登录名"
:disabled="!vdata.isAdd"
/>
</a-form-item>
</a-col>
</a-row>
<a-row justify="space-between" type="flex">
<a-col :span="10">
<a-form-item label="商户简称" name="mchShortName">
<a-input
v-model:value="vdata.saveObject['mchShortName']"
placeholder="请输入商户简称"
/>
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="联系人姓名" name="contactName">
<a-input
v-model:value="vdata.saveObject['contactName']"
placeholder="请输入联系人姓名"
/>
</a-form-item>
</a-col>
</a-row>
<a-row justify="space-between" type="flex">
<a-col :span="10">
<a-form-item label="联系人邮箱" name="contactEmail">
<a-input
v-model:value="vdata.saveObject['contactEmail']"
placeholder="请输入联系人邮箱"
/>
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="联系人手机号" name="contactTel">
<a-input
v-model:value="vdata.saveObject['contactTel']"
placeholder="请输入联系人手机号"
/>
<span v-if="!vdata.isAdd" class="jeepay-tip-text">同步更改登录手机号</span>
</a-form-item>
</a-col>
</a-row>
<a-row justify="space-between" type="flex">
<!-- <a-col :span="10" style="position:relative">-->
<!-- <a-form-item label="商户级别" name="mchLevel">-->
<!-- &lt;!&ndash; 商户级别 气泡弹窗 &ndash;&gt;-->
<!-- <a-radio-group v-model:value="vdata.saveObject['mchLevel']">-->
<!-- <a-radio :value="'M0'" :disabled="true">M0</a-radio>-->
<!-- <a-radio :value="'M1'">M1</a-radio>-->
<!-- </a-radio-group>-->
<!-- </a-form-item>-->
<!-- <div id="components-popover-demo-placement">-->
<!-- <div class="typePopover">-->
<!-- <a-popover placement="top">-->
<!-- <template #title><span>商户级别</span></template>-->
<!-- <template #content>-->
<!-- <p>M0商户简单模式页面简洁仅基础收款功能</p>-->
<!-- <p>M1商户高级模式支持api调用 支持配置应用及分账转账功能</p>-->
<!-- </template>-->
<!-- <question-circle-outlined />-->
<!-- </a-popover>-->
<!-- </div>-->
<!-- </div>-->
<!-- </a-col>-->
<a-col :span="10">
<a-form-item label="状态" name="state">
<a-radio-group v-model:value="vdata.saveObject['state']">
<a-radio :value="1">
启用
</a-radio>
<a-radio :value="0">
禁用
</a-radio>
</a-radio-group>
</a-form-item>
</a-col>
</a-row>
<a-row justify="space-between" type="flex">
<a-col :span="24">
<a-form-item label="备注" name="remark">
<a-input v-model:value="vdata.saveObject['remark']" placeholder="请输入备注" type="textarea" />
</a-form-item>
</a-col>
</a-row>
<!-- 重置密码板块 -->
<a-row justify="space-between" type="flex">
<a-col :span="24">
<a-divider orientation="left">
<a-tag color="#FF4B33">
账户安全
</a-tag>
</a-divider>
</a-col>
</a-row>
<div v-if="vdata.isAdd">
<a-row justify="space-between" type="flex">
<a-col :span="24">
<a-form-item label="是否发送开通提醒">
<a-radio-group v-model:value="vdata.saveObject['isNotify']">
<a-radio :value="0"></a-radio>
<a-radio :value="1"></a-radio>
</a-radio-group>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="密码设置">
<a-radio-group v-model:value="vdata.saveObject['passwordType']">
<a-radio value="default">默认密码</a-radio>
<a-radio value="custom">自定义密码</a-radio>
</a-radio-group>
</a-form-item>
</a-col>
<a-col v-if="vdata.saveObject.passwordType == 'custom'" :span="12">
<a-form-item label="登录密码" name="loginPassword">
<a-input
v-model:value="vdata.saveObject['loginPassword']"
placeholder="请输入登录密码"
/>
</a-form-item>
<a-button type="primary" ghost @click="randomPassage(false, 6, 0)"><file-sync-outlined />随机生成密码</a-button>
</a-col>
</a-row>
</div>
<div v-else>
<div>
<a-row justify="space-between" type="flex">
<a-col :span="10">
<a-form-item v-if="vdata.resetIsShow" label="">
重置密码<a-checkbox v-model:checked="vdata.sysPassword.resetPass" />
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item v-if="vdata.sysPassword.resetPass" label="">
恢复默认密码<a-checkbox v-model:checked="vdata.sysPassword.defaultPass" @click="isResetPass" />
</a-form-item>
</a-col>
</a-row>
</div>
<div v-if="vdata.sysPassword.resetPass">
<div v-if="!vdata.sysPassword.defaultPass">
<a-row justify="space-between" type="flex">
<a-col :span="10">
<a-form-item label="新密码:" name="newPwd">
<a-input-password v-model:value="vdata.newPwd" autocomplete="new-password" :disabled="vdata.sysPassword.defaultPass" />
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="确认新密码:" name="confirmPwd">
<a-input-password v-model:value="vdata.sysPassword.confirmPwd" autocomplete="new-password" :disabled="vdata.sysPassword.defaultPass" />
</a-form-item>
</a-col>
</a-row>
</div>
</div>
</div>
</a-form>
<div class="drawer-btn-center">
<a-button :style="{ marginRight: '8px' }" style="margin-right:8px" @click="onClose">
<close-outlined />
取消
</a-button>
<a-button type="primary" :loading="vdata.btnLoading" @click="handleOkFunc">
<check-outlined />
保存
</a-button>
</div>
</a-drawer>
</template>
<script lang="ts" setup>
import { API_URL_MCH_LIST, req, $getPasswordRules } from '@/api/manage'
import { Base64 } from 'js-base64'
import {message} from 'ant-design-vue'
import {defineProps,reactive,ref} from 'vue'
const props = defineProps({
callbackFunc: { type: Function,default:null }
})
const infoFormModel = ref()
const infoTable =ref()
const vdata : any = reactive({
newPwd: '', // 新密码
resetIsShow: false, // 重置密码是否展现
passwordRules: /^$/, //密码规则
passwordRulesText: '', //密码规则提示文字
sysPassword: {
resetPass: false, // 重置密码
defaultPass: true, // 使用默认密码
confirmPwd: '' // 确认密码
},
btnLoading: false,
isAdd: true, // 新增 or 修改页面标志
saveObject: {}, // 数据对象
recordId: null, // 更新对象ID
visible: false, // 是否显示弹层/抽屉
rules: {
mchName: [{ required: true, message: '请输入商户名称', trigger: 'blur' }],
loginUsername: [{ required: true, pattern: /^[a-zA-Z][a-zA-Z0-9]{5,17}$/, message: '请输入字母开头长度为6-18位的登录名', trigger: 'blur' }],
mchShortName: [{ required: true, message: '请输入商户简称', trigger: 'blur' }],
contactName: [{ required: true, message: '请输入联系人姓名', trigger: 'blur' }],
contactEmail: [{ required: false, pattern: /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/, message: '请输入正确的邮箱地址', trigger: 'blur' }],
contactTel: [{ required: true, pattern: /^1\d{10}$/, message: '请输入正确的手机号', trigger: 'blur' }],
newPwd: [{ required: false, trigger: 'blur' }, {
validator: (rule, value) => {
if (!vdata.sysPassword.defaultPass) {
if (!vdata.passwordRules.test(vdata.newPwd)) {
return Promise.reject(vdata.passwordRulesText)
} else if(vdata.newPwd !== vdata.sysPassword.confirmPwd) {
return Promise.reject('新密码与确认密码不一致')
} else return Promise.resolve()
} else {
return Promise.resolve()
}
}
}], // 新密码
confirmPwd: [{ required: false, trigger: 'blur' }, {
validator: (rule, value) => {
if (!vdata.sysPassword.defaultPass) {
// vdata.newPwd === vdata.sysPassword.confirmPwd ? callBack() : callBack('新密码与确认密码不一致')
if(!vdata.passwordRules.test(vdata.sysPassword.confirmPwd)){
return Promise.reject(vdata.passwordRulesText)
} else if(vdata.newPwd !== vdata.sysPassword.confirmPwd) {
return Promise.reject('新密码与确认密码不一致')
} else return Promise.resolve()
} else {
return Promise.resolve()
}
}
}] // 确认新密码
}
})
function getPasswordRules () { // 获取密码规则
$getPasswordRules().then(res => {
vdata.passwordRules = new RegExp(res.regexpRules)
vdata.passwordRulesText = res.errTips
})
}
function show (recordId) { // 弹层打开事件
getPasswordRules()
vdata.isAdd = !recordId
vdata.saveObject = { 'state': 1, 'type': 1, mchLevel: 'M1', passwordType: 'default', isNotify: 0, refundMode: ['plat', 'api'] } // 数据清空
if (infoFormModel.value != undefined) {
infoFormModel.value.resetFields()
}
if (!vdata.isAdd) { // 修改信息 延迟展示弹层
vdata.resetIsShow = true // 展示重置密码板块
vdata.recordId = recordId
req.getById(API_URL_MCH_LIST, recordId).then(res => {
vdata.saveObject = res
})
vdata.visible = true
} else {
vdata.visible = true // 立马展示弹层信息
}
}
function handleOkFunc () { // 点击【确认】按钮事件
infoFormModel.value.validate().then(valid =>{
vdata.btnLoading = true
// 请求接口
if (vdata.isAdd) {
// 如果新增密码设置为默认密码,登录密码设置为空
if(vdata.saveObject.passwordType == 'default' || !vdata.saveObject.loginPassword) {
vdata.saveObject.loginPassword = ''
}
if(vdata.saveObject.passwordType == 'custom' && !vdata.passwordRules.test(vdata.saveObject.loginPassword)) {
vdata.btnLoading = false
return message.error(vdata.passwordRulesText)
}
vdata.saveObject.mchLevel = 'M1';
req.add(API_URL_MCH_LIST, vdata.saveObject).then(res => {
message.success('新增成功')
vdata.visible = false
props.callbackFunc() // 刷新列表
vdata.btnLoading = false
}).catch(res => {
vdata.btnLoading = false
})
} else {
vdata.sysPassword.confirmPwd = Base64.encode(vdata.sysPassword.confirmPwd)
Object.assign(vdata.saveObject, vdata.sysPassword) // 拼接对象
req.updateById(API_URL_MCH_LIST, vdata.recordId, vdata.saveObject).then(res => {
message.success('修改成功')
vdata.visible = false
props.callbackFunc() // 刷新列表
vdata.btnLoading = false
vdata.resetIsShow = true // 展示重置密码板块
vdata.sysPassword.resetPass = false
vdata.sysPassword.defaultPass = true // 是否使用默认密码默认为true
resetPassEmpty(DataView) // 清空密码
}).catch(res => {
vdata.btnLoading = false
vdata.resetIsShow = true // 展示重置密码板块
vdata.sysPassword.resetPass = false
vdata.sysPassword.defaultPass = true // 是否使用默认密码默认为true
resetPassEmpty(vdata) // 清空密码
})
}
}).catch(valid =>{
})
}
function onClose () {
vdata.visible = false
vdata.resetIsShow = false // 取消重置密码板块展示
vdata.sysPassword.resetPass = false
resetPassEmpty(vdata)
vdata.sysPassword.defaultPass = true// 是否使用默认密码默认为true
}
function searchFunc () { // 点击【查询】按钮点击事件
infoTable.value.refTable(true)
}
// 使用默认密码重置是否为true
function isResetPass () {
if (!vdata.sysPassword.defaultPass) {
vdata.newPwd = ''
vdata.sysPassword.confirmPwd = ''
}
}
// 保存后清空密码
function resetPassEmpty (vdata) {
vdata.newPwd = ''
vdata.sysPassword.confirmPwd = ''
}
function randomPassage(randomFlag, min, max) { // 生成6位随机密码
let str = ''
let range = min
const arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
// 随机产生
if (randomFlag) {
range = Math.round(Math.random() * (max - min)) + min
}
for (var i = 0; i < range; i++) {
var pos = Math.round(Math.random() * (arr.length - 1))
str += arr[ pos ]
}
vdata.saveObject['loginPassword'] = str
}
defineExpose({
show
})
</script>
<style lang="less">
.typePopover {
position: absolute;
top: 0;
left:62px;
}
</style>

View File

@@ -0,0 +1,148 @@
<template>
<a-drawer
v-model:visible="vdata.visible"
:title=" true ? '商户详情' : '' "
:body-style="{ paddingBottom: '80px' }"
width="40%"
@close="onClose"
>
<a-row justify="space-between" type="flex">
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="用户号">
{{ vdata.detailData['mchNo'] }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="用户名称">
{{ vdata.detailData['mchName'] }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="登录名">
{{ vdata.detailData['loginUsername'] }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商户简称">
{{ vdata.detailData['mchShortName'] }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col v-if="vdata.detailData['type'] === 2" :sm="12">
<a-descriptions>
<a-descriptions-item label="服务商号">
{{ vdata.detailData['isvNo'] }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col v-if="vdata.detailData['type'] === 2" :sm="12">
<a-descriptions>
<a-descriptions-item label="服务商名称">
{{ vdata.isvName }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="联系人姓名">
{{ vdata.detailData['contactName'] }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商户类型">
{{ vdata.detailData['type'] === 1 ? '普通商户': '特约商户' }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="联系人手机号">
{{ vdata.detailData['contactTel'] }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="状态">
<a-tag :color="vdata.detailData['state'] === 1?'green':'volcano'">
{{ vdata.detailData['state'] === 1?'启用':'禁用' }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="联系人邮箱">
{{ vdata.detailData['contactEmail'] }}
</a-descriptions-item>
</a-descriptions>
</a-col>
</a-row>
<a-row justify="start" type="flex">
<a-col :sm="24">
<a-form-item label="备注">
<a-input
v-model:value="vdata.detailData['remark']"
type="textarea"
disabled="disabled"
style="height: 50px"
/>
</a-form-item>
</a-col>
</a-row>
</a-drawer>
</template>
<script lang="ts" setup>
import { API_URL_MCH_LIST, API_URL_ISV_LIST, req } from '@/api/manage'
import {defineProps,reactive} from 'vue'
const props = defineProps({
callbackFunc: { type: Function,default:null }
})
const vdata = reactive({
btnLoading: false,
detailData: {}, // 数据对象
recordId: null, // 更新对象ID
visible: false, // 是否显示弹层/抽屉
isvList: [], // 服务商下拉列表
isvName: '' // 服务商名称
})
function show (recordId) { // 弹层打开事件
vdata.detailData = { 'state': 1, 'type': 1 } // 数据清空
// if (this.$refs.infoFormModel !== undefined) {
// this.$refs.infoFormModel.resetFields()
// }
vdata.recordId = recordId
req.getById(API_URL_MCH_LIST, recordId).then(res => {
vdata.detailData = res
})
req.list(API_URL_ISV_LIST, { 'pageSize': null }).then(res => { // 服务商下拉选择列表
vdata.isvList = res.records
for (let i = 0; i < vdata.isvList.length; i++) {
if (vdata.detailData['isvNo'] === vdata.isvList[i]['isvNo']) {
vdata.isvName = vdata.isvList[i]['isvName']
}
}
})
vdata.visible = true
}
function onClose () {
vdata.visible = false
}
defineExpose({
show //抛出show函数给父组件
})
</script>

View File

@@ -0,0 +1,690 @@
<template>
<a-drawer
v-model:visible="vdata.visible"
:mask-closable="false"
title="商户高级配置"
:body-style="{ paddingBottom: '80px' }"
width="60%"
class="drawer-width"
@close="vdata.visible = false"
>
<div style="background: #fff">
<a-tabs v-if="vdata.visible" @change="selectTabs">
<a-tab-pane key="mchApiEnt" tab="接口权限">
<a-table :dataSource="vdata.mchApiEntTableData" :columns="vdata.mchApiEntTableColumns" :pagination="false" size="small">
<!-- header 标题 插槽 -->
<template #headerCell="{ column }">
<template v-if="column.key === 'batch'">
<a-checkbox @change="(e) => mchApiEntAllCehcked(e.target.checked)" />
</template>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'batch'"><a-checkbox v-model:checked="record.checked" :disabled="record.disabled" /></template>
</template>
<template #title>商户可自调用接口</template>
</a-table>
<a-form-item class="bottom-btn">
<a-button type="primary" :loading="vdata.btnLoading" @click="confirmByApiEnt"><check-circle-outlined />确认更新</a-button>
</a-form-item>
</a-tab-pane>
</a-tabs>
</div>
</a-drawer>
</template>
<script setup lang="ts">
import { API_URL_MCH_CONFIG, API_URL_MCH_LIST, req, API_URL_MCH_APP, API_URL_MCH_STORE_LIST, } from '@/api/manage'
import { ref, reactive, getCurrentInstance } from 'vue'
const { $infoBox, $access } = getCurrentInstance()!.appContext.config.globalProperties
const configFormModel = ref()
const payTestBarCode =ref()
const vdata : any = reactive ({
recordId: null, // 更新对象ID
visible: false, // 是否显示弹层/抽屉
mchAppList: [] as any, // app列表
mchStoreList: [] as any, // 门店列表
appId: '', // 已选择的appId
storeId: '', // 已选择的门店ID
cashierUrl: '', // 收银台地址
isShowCashier: true as any,
btnLoading: false,
groupKey: 'orderConfig',
mchConfigData: [] as any, // 配置保存对象
defaultConfig: [{
configKey: 'appVoice',
configName: '是否启用app订单语音播报',
configVal: '1',
type: 'radio'
},
{
configKey: 'qrcEscaping',
configName: '是否启用码牌防逃单功能',
configVal: '1',
type: 'radio'
},
{
configKey: 'weChatVoice',
configName: '小程序语音推送',
configVal: '0',
type: 'radio'
}
], // 默认配置项
mchPayNotifyUrl: '', // 商户支付回调地址
mchRefundNotifyUrl: '', // 商户退款回调地址
mchNotifyFlag: {
payNotifyFlag: '1',
refundNotifyFlag: '1'
}, // 商户回调开关
mchNotifyPostType: 'POST_BODY', // 发送商户通知的方式 POST_BODY POST_QUERYSTRING POST_JSON
notifyTableColumns: [
{key: 'batch', title: '批量选择' },
{key: 'key', dataIndex: 'key', title: '参数KEY' },
{key: 'name', dataIndex: 'name', title: '参数名称' },
// {key: 'desc', dataIndex: 'desc', title: '参数描述' }
],
notifyTableData: [
{key: 'payOrderId', name: '支付订单号', desc: '', checked: true, disabled: true },
{key: 'mchNo', name: '用户号', desc: '用户号', checked: true, disabled: true },
{key: 'appId', name: '应用ID', desc: '', checked: true, disabled: true },
{key: 'mchOrderNo', name: '商户订单号', desc: '', checked: true, disabled: true },
{key: 'ifCode', name: '支付接口', desc: '', checked: true, disabled: true },
{key: 'wayCode', name: '支付方式', desc: '', checked: true, disabled: true },
{key: 'amount', name: '支付金额', desc: '', checked: true, disabled: true },
{key: 'currency', name: '货币代码', desc: '', checked: true, disabled: true },
{key: 'state', name: '订单状态', desc: '', checked: true, disabled: true },
{key: 'clientIp', name: '客户端IP', desc: '', checked: true, disabled: true },
{key: 'subject', name: '商品标题', desc: '', checked: true, disabled: true },
{key: 'body', name: '商品描述', desc: '', checked: true, disabled: true },
{key: 'channelOrderNo', name: '渠道订单号', desc: '', checked: true, disabled: true },
{key: 'errCode', name: '渠道错误码', desc: '', checked: true, disabled: true },
{key: 'errMsg', name: '渠道错误描述', desc: '', checked: true, disabled: true },
{key: 'extParam', name: '扩展参数', desc: '', checked: true, disabled: true },
{key: 'successTime', name: '支付成功时间', desc: '', checked: true, disabled: true },
{key: 'createdAt', name: '创建时间', desc: '', checked: true, disabled: true },
{key: 'sign', name: '签名', desc: '', checked: true, disabled: true },
{key: 'storeId', name: '门店ID', desc: '', checked: false, disabled: false },
{key: 'lng', name: '经度', desc: '', checked: false, disabled: false },
{key: 'lat', name: '纬度', desc: '', checked: false, disabled: false },
{key: 'qrcId', name: '码牌ID', desc: '', checked: false, disabled: false },
{key: 'wayCodeType', name: '支付方式代码分类', desc: '', checked: false, disabled: false },
{key: 'mchFeeRate', name: '商户手续费费率快照', desc: '', checked: false, disabled: false },
{key: 'mchFeeAmount', name: '商户手续费,单位分', desc: '', checked: false, disabled: false },
{key: 'channelUser', name: '渠道用户标识,如微信openId,支付宝账号', desc: '', checked: false, disabled: false },
{key: 'divisionMode', name: '订单分账模式', desc: '', checked: false, disabled: false },
{key: 'buyerRemark', name: '买家备注', desc: '买家备注', checked: false, disabled: false },
{key: 'sellerRemark', name: '卖家备注', desc: '卖家备注', checked: false, disabled: false },
{key: 'expiredTime', name: '订单失效时间', desc: '', checked: false, disabled: false },
{key: 'platformOrderNo', name: '支付凭证交易单号', desc: '', checked: false, disabled: false },
{key: 'platformMchOrderNo', name: '支付凭证商户单号', desc: '', checked: false, disabled: false },
],
mchApiEntTableColumns: [
{key: 'batch', title: '批量选择' },
{key: 'title', dataIndex: 'title', title: '名称' },
{key: 'key', dataIndex: 'key', title: 'KEY' },
{key: 'apiPath', dataIndex: 'apiPath', title: '路径' },
],
mchApiEntTableData: [
{key: 'API_PAY_ORDER', title: '统一下单', apiPath: '/api/pay/unifiedOrder', checked: false },
{key: 'API_PAY_ORDER_QUERY', title: '查询支付订单', apiPath: '/api/pay/query', checked: false },
{key: 'API_PAY_ORDER_CLOSE', title: '支付订单关闭', apiPath: '/api/pay/close', checked: false },
{key: 'API_CHANNEL_USER', title: '获取渠道用户ID', apiPath: '/api/channelUserId/jump', checked: false },
{key: 'API_REFUND_ORDER', title: '发起支付退款', apiPath: '/api/refund/refundOrder', checked: false },
{key: 'API_REFUND_ORDER_QUERY', title: '查询退款订单', apiPath: '/api/refund/query', checked: false },
{key: 'API_TRANS_ORDER', title: '发起转账订单', apiPath: '/api/transferOrder', checked: false },
{key: 'API_TRANS_ORDER_QUERY', title: '查询转账订单', apiPath: '/api/transfer/query', checked: false },
{key: 'API_TRANS_BALANCE_QUERY', title: '查询转账可用余额', apiPath: '/api/transfer/balance/query', checked: false },
{key: 'API_DIVISION_BIND', title: '绑定分账用户', apiPath: '/api/division/receiver/bind', checked: false },
{key: 'API_DIVISION_EXEC', title: '发起订单分账', apiPath: '/api/division/exec', checked: false },
{key: 'API_DIVISION_CHANNEL_BALANCE', title: '查询分账用户可用余额', apiPath: '/api/division/receiver/channelBalanceQuery', checked: false },
{key: 'API_DIVISION_CHANNEL_CASHOUT', title: '对分账用户的渠道余额发起提现', apiPath: '/api/division/receiver/channelBalanceCashout', checked: false },
],
cashierWxH5Data: [
{ label: '微信H5', value: 'WX_H5', },
{ label: '微信小程序', value: 'WX_LITE', },
],
cashierAliWebData: [
{ label: '支付宝WAP', value: 'ALI_WAP', },
{ label: '支付宝生活号', value: 'ALI_JSAPI', },
{ label: '支付宝小程序', value: 'ALI_LITE', },
],
// 分账管理菜单
divisionConfig: { overrideAutoFlag: 0, autoDivisionRules: { amountLimit: 0, delayTime: 120 }, mchDivisionEntFlag: 1, calBaseAmountType: 'INCOME_AMOUNT' },
// 广告配置
advertConfig: { advertFlag: 0 },
// 便捷收银台配置
selfCashierState : { configKey: 'selfCashierState', configName: '便捷收银台权限开关', configVal: '0', type: 'radio' },
selfCashierSiteInfoType: { configKey: 'selfCashierSiteInfoType', configName: '便捷收银台logo/底部显示配置', configVal: 'DEFAULT', type: 'text' },
selfCashierWxH5Config: { configKey: 'selfCashierWxH5Config', configName: '便捷收银台微信H5配置', configVal: ['WX_H5'], type: 'text' },
selfCashierAliWapConfig: { configKey: 'selfCashierAliWapConfig', configName: '便捷收银台支付宝WAP配置', configVal: ['ALI_WAP'], type: 'text'},
// 商户web收银台配置
wabCashierState: { configKey: 'wabCashierState', configName: 'WEB收银台权限开关', configVal: '0', type: 'radio' },
wabCashierSiteInfoType: { configKey: 'wabCashierSiteInfoType', configName: 'WEB收银台logo/底部显示配置', configVal: 'DEFAULT', type: 'text' },
wabCashierWxH5Config: { configKey: 'wabCashierWxH5Config', configName: 'WEB收银台微信H5配置', configVal: ['WX_H5'], type: 'text' },
wabCashierAliWapConfig: { configKey: 'wabCashierAliWapConfig', configName: 'WEB收银台支付宝WAP配置', configVal: ['ALI_WAP'], type: 'text' },
// 会员配置
memberConfigData: [
{ configKey: 'memberModelState', configName: '会员模块状态开关', configVal: '1', type: 'radio' },
{ configKey: 'memberPayState', configName: '会员支付开关', configVal: '0', type: 'radio' },
{ configKey: 'memberCustomAmountState', configName: '充值自定义金额', configVal: '1', type: 'radio' },
{ configKey: 'mbrMaxBalance', configName: '会员最大储值余额(元), 0表示依据系统配置', configVal: '0', type: 'text' },
],
})
function show (recordId) { // 弹层打开事件
vdata.recordId = recordId
selectTabs('mchApiEnt') //初始化数据
vdata.visible = true
}
function selectTabs (key) { // 清空必填提示
if (key) {
vdata.groupKey = key
if(key == 'orderConfig'){
req.list(API_URL_MCH_CONFIG, {groupKey: vdata.groupKey, mchNo: vdata.recordId}).then(res => {
if (res != null && res.length > 0) {
vdata.mchConfigData = res
// 遍历总配置列表如果configKey不存在则push进该条记录
for (var key in vdata.defaultConfig) {
// configKey是否存在 默认为不存在
var isdefault = true
vdata.mchConfigData.forEach((item) => {
// 遍历到相同key时key存在赋值false
if (item.configKey == vdata.defaultConfig[key].configKey) {
isdefault = false
}
})
// 如果不存在 true 添加该条默认记录
if(isdefault) {
vdata.mchConfigData.push(vdata.defaultConfig[key])
}
}
}else {
vdata.mchConfigData = vdata.defaultConfig
}
})
}else if(key == 'payOrderNotifyExtParams'){
// 恢复初始值
vdata.notifyTableData.filter(r => !r.disabled).forEach(r => {
r. checked = false
})
vdata.mchNotifyPostType = 'POST_BODY'
vdata.mchPayNotifyUrl = ''
vdata.mchRefundNotifyUrl = ''
req.list(API_URL_MCH_CONFIG, {groupKey: vdata.groupKey, mchNo: vdata.recordId}).then(res => {
if (res != null && res.length > 0) {
res.forEach(item => {
if (item.configKey == 'payOrderNotifyExtParams') {
let arr = JSON.parse(item.configVal)
vdata.notifyTableData.filter(r => !r.disabled).forEach(r => {
r.checked = arr.indexOf(r.key) >= 0
})
}else if (item.configKey == 'mchPayNotifyUrl') {
vdata.mchPayNotifyUrl = item.configVal
}else if (item.configKey == 'mchRefundNotifyUrl') {
vdata.mchRefundNotifyUrl = item.configVal
}else if (item.configKey == 'mchNotifyPostType') {
vdata.mchNotifyPostType = item.configVal
}else if (item.configKey == 'mchNotifyFlag') {
if (item.configVal) {
vdata.mchNotifyFlag = JSON.parse(item.configVal)
}
}
})
}
})
}else if(key == 'divisionManage'){
// 分账管理恢复默认
vdata.divisionConfig = { overrideAutoFlag: 0, autoDivisionRules: { amountLimit: 0, delayTime: 120 }, mchDivisionEntFlag: 1, calBaseAmountType: 'INCOME_AMOUNT' }
req.list(API_URL_MCH_CONFIG, {groupKey: vdata.groupKey, mchNo: vdata.recordId}).then(res => {
if (res != null && res.length > 0) {
vdata.divisionConfig = JSON.parse(res[0].configVal)
if(!vdata.divisionConfig.calBaseAmountType){
vdata.divisionConfig.calBaseAmountType = 'INCOME_AMOUNT' // 存量数据为空, 默认为: 入账金额。
}
vdata.divisionConfig.autoDivisionRules.amountLimit = Number.parseFloat((vdata.divisionConfig.autoDivisionRules.amountLimit / 100).toFixed(2))
}
})
}else if(key == 'mchApiEnt'){ // 接口权限
// 恢复初始值
vdata.mchApiEntTableData.forEach(r => {
r. checked = false
})
req.list(API_URL_MCH_CONFIG, {groupKey: vdata.groupKey, mchNo: vdata.recordId}).then(res => {
if (res != null && res.length > 0) {
res.forEach(item => {
if (item.configKey == 'mchApiEntList') { // 商户接口权限集合
let arr = JSON.parse(item.configVal)
vdata.mchApiEntTableData.forEach(r => {
r.checked = arr.indexOf(r.key) >= 0
})
}
})
}
})
}else if(key == 'advertConfig'){ // 广告权限
// 恢复初始值
vdata.mchApiEntTableData.forEach(r => {
r. checked = false
})
req.getById(API_URL_MCH_LIST, vdata.recordId).then(res => {
vdata.advertConfig = res
})
}else if(key == 'selfCashier'){ // 便捷收银台配置
// 初始化配置
vdata.selfCashierState = { configKey: 'selfCashierState', configName: '便捷收银台权限开关', configVal: '0', type: 'radio' },
vdata.selfCashierSiteInfoType = { configKey: 'selfCashierSiteInfoType', configName: '便捷收银台logo/底部显示配置', configVal: 'DEFAULT', type: 'text' },
vdata.selfCashierWxH5Config = { configKey: 'selfCashierWxH5Config', configName: '便捷收银台微信H5配置', configVal: ['WX_H5'], type: 'text' },
vdata.selfCashierAliWapConfig = { configKey: 'selfCashierAliWapConfig', configName: '便捷收银台支付宝WAP配置', configVal: ['ALI_WAP'], type: 'text'},
req.list(API_URL_MCH_CONFIG, {groupKey: vdata.groupKey, mchNo: vdata.recordId}).then(res => {
if (res != null && res.length > 0) {
res.forEach(value => {
if(value.configKey == 'selfCashierState') {
vdata.selfCashierState = value
}else if (value.configKey == 'selfCashierSiteInfoType') {
vdata.selfCashierSiteInfoType = value
}else if (value.configKey == 'selfCashierWxH5Config') {
vdata.selfCashierWxH5Config = value
if (vdata.selfCashierWxH5Config.configVal) {
vdata.selfCashierWxH5Config.configVal = JSON.parse(vdata.selfCashierWxH5Config.configVal)
}
}else if (value.configKey == 'selfCashierAliWapConfig') {
vdata.selfCashierAliWapConfig = value
if (vdata.selfCashierAliWapConfig.configVal) {
vdata.selfCashierAliWapConfig.configVal = JSON.parse(vdata.selfCashierAliWapConfig.configVal)
}
}
})
}
})
vdata.appId = ''
vdata.storeId = ''
// 请求接口获取所有的appid只有此处进行pageSize=-1传参
req.list(API_URL_MCH_APP, { pageSize: -1, mchNo: vdata.recordId, state: 1 }).then(res => {
vdata.mchAppList = res.records
if (vdata.mchAppList.length > 0) {
// 赋予默认值
//vdata.appId = vdata.mchAppList[0].appId
//TODO
if(vdata.appId == '') vdata.appId = vdata.mchAppList[0].appId
// 根据appId的值动态显示支付方式
// appPaywayListHandle(vdata.appId)
one()
}
})
function one (){
// 请求接口获取商户所有门店只有次数进行pageSize=-1传参
req.list(API_URL_MCH_STORE_LIST, { pageSize: -1, mchNo: vdata.recordId }).then(res => {
vdata.mchStoreList = res.records
if (vdata.mchStoreList.length > 0) {
// 赋予默认值
if(vdata.storeId == '') vdata.storeId = vdata.mchStoreList[0].storeId
// two()
}
})
}
// function two (){
// $mchConfigCreateCashier({
// appId: vdata.appId, // appId
// storeId: vdata.storeId, // storeId
// mchNo: vdata.recordId
// }).then(res => {
// vdata.cashierUrl = res
// }).catch(() => {
// payTestBarCode.value.processCatch()
// })
// }
}else if(key == 'webCashier'){ // web收银台配置
// 初始化配置
vdata.webCashierState = { configKey: 'webCashierState', configName: 'WEB收银台权限开关', configVal: '0', type: 'radio' },
vdata.webCashierSiteInfoType = { configKey: 'webCashierSiteInfoType', configName: 'WEB收银台logo/底部显示配置', configVal: 'DEFAULT', type: 'text' },
vdata.webCashierWxH5Config = { configKey: 'webCashierWxH5Config', configName: 'WEB收银台微信H5配置', configVal: ['WX_H5'], type: 'text' },
vdata.webCashierAliWapConfig = { configKey: 'webCashierAliWapConfig', configName: 'WEB收银台支付宝WAP配置', configVal: ['ALI_WAP'], type: 'text'},
req.list(API_URL_MCH_CONFIG, {groupKey: vdata.groupKey, mchNo: vdata.recordId}).then(res => {
if (res != null && res.length > 0) {
res.forEach(value => {
if(value.configKey == 'webCashierState') {
vdata.webCashierState = value
}else if (value.configKey == 'webCashierSiteInfoType') {
vdata.webCashierSiteInfoType = value
}else if (value.configKey == 'webCashierWxH5Config') {
vdata.webCashierWxH5Config = value
if (vdata.webCashierWxH5Config.configVal) {
vdata.webCashierWxH5Config.configVal = JSON.parse(vdata.webCashierWxH5Config.configVal)
}
}else if (value.configKey == 'webCashierAliWapConfig') {
vdata.webCashierAliWapConfig = value
if (vdata.webCashierAliWapConfig.configVal) {
vdata.webCashierAliWapConfig.configVal = JSON.parse(vdata.webCashierAliWapConfig.configVal)
}
}
})
}
})
}else if(key == 'memberConfig'){
req.list(API_URL_MCH_CONFIG, {groupKey: vdata.groupKey, mchNo: vdata.recordId}).then(res => {
if (res != null && res.length > 0) {
vdata.memberConfigData.forEach(item =>{
res.forEach(res => {
if (item.configKey == res.configKey) {
item.configVal = res.configVal
if (item.configKey == 'mbrMaxBalance' && res.configVal) {
item.configVal = Number.parseFloat((res.configVal / 100).toFixed(2))
}
}
})
})
}
})
}
}
}
// 确认更新
function confirm (e) {
configFormModel.value.validate().then(valid => {
$infoBox.confirmPrimary('确认修改支付配置吗?', '', () => {
vdata.btnLoading = true // 打开按钮上的 loading
req.updateById(API_URL_MCH_CONFIG, vdata.groupKey, {mchNo: vdata.recordId, configData: JSON.stringify(vdata.mchConfigData)}).then(res => {
$infoBox.message.success('修改成功')
vdata.btnLoading = false
}).catch(res => {
vdata.btnLoading = false
})
})
})
}
// 确认更新
function confirmByNotify (e) {
$infoBox.confirmPrimary('确认修改回调参数吗?', '更新完成后请尽快检查回调接收地址,避免验签失败造成业务损失!', () => {
vdata.btnLoading = true // 打开按钮上的 loading
let arr : any = []
vdata.notifyTableData.filter(r => !r.disabled).forEach(r => {
if(r.checked){
arr.push(r.key)
}
})
let configItem = [
{ groupKey: vdata.groupKey, configName: '支付订单回调和查单参数', configKey: 'payOrderNotifyExtParams', configVal : JSON.stringify(arr) },
{ groupKey: vdata.groupKey, configName: 'POS支付回调地址', configKey: 'mchPayNotifyUrl', configVal : vdata.mchPayNotifyUrl },
{ groupKey: vdata.groupKey, configName: 'POS退款回调地址', configKey: 'mchRefundNotifyUrl', configVal : vdata.mchRefundNotifyUrl },
{ groupKey: vdata.groupKey, configName: '回调开关', configKey: 'mchNotifyFlag', configVal : vdata.mchNotifyFlag },
{ groupKey: vdata.groupKey, configName: '商户接收通知方式', configKey: 'mchNotifyPostType', configVal : vdata.mchNotifyPostType }
]
req.updateById(API_URL_MCH_CONFIG, vdata.groupKey, {mchNo: vdata.recordId, configData: JSON.stringify(configItem)}).then(res => {
$infoBox.message.success('修改成功')
vdata.btnLoading = false
}).catch(res => {
vdata.btnLoading = false
})
})
}
function confirmByDivisionManage(){
$infoBox.confirmPrimary('确认修改分账设置?', '', () => {
vdata.btnLoading = true // 打开按钮上的 loading
let reqObject : any = JSON.parse(JSON.stringify(vdata.divisionConfig))
reqObject.autoDivisionRules.amountLimit = Number.parseInt((reqObject.autoDivisionRules.amountLimit * 100) + '')
req.updateById(API_URL_MCH_CONFIG, vdata.groupKey, {mchNo: vdata.recordId, configData: [{configKey: 'divisionConfig', configName: '分账管理', configVal: JSON.stringify(reqObject)}]}).then(res => {
$infoBox.message.success('修改成功')
vdata.btnLoading = false
}).catch(res => {
vdata.btnLoading = false
})
})
}
function confirmByAdvertManage(){
$infoBox.confirmPrimary('确认修改广告设置?', '', () => {
vdata.btnLoading = true // 打开按钮上的 loading
let reqObject : any = JSON.parse(JSON.stringify(vdata.advertConfig))
req.updateById(API_URL_MCH_LIST, vdata.recordId, reqObject).then(res => {
$infoBox.message.success('修改成功')
vdata.btnLoading = false
}).catch(res => {
vdata.btnLoading = false
})
})
}
function confirmBySelfCashierManage(){
$infoBox.confirmPrimary('确认修改便捷收银台设置?', '', () => {
vdata.btnLoading = true // 打开按钮上的 loading
let arr : any = []
arr.push(vdata.selfCashierState)
arr.push(vdata.selfCashierSiteInfoType)
arr.push(vdata.selfCashierWxH5Config)
arr.push(vdata.selfCashierAliWapConfig)
let reqObject : any = JSON.stringify(arr)
req.updateById(API_URL_MCH_CONFIG, vdata.groupKey, {mchNo: vdata.recordId, configData: reqObject}).then(res => {
vdata.btnLoading = false
$infoBox.message.success('修改成功')
}).catch(res => {
vdata.btnLoading = false
})
})
}
function confirmByWebCashierManage(){
$infoBox.confirmPrimary('确认修改WEB收银台设置', '', () => {
vdata.btnLoading = true // 打开按钮上的 loading
let arr : any = []
arr.push(vdata.webCashierState)
arr.push(vdata.webCashierSiteInfoType)
arr.push(vdata.webCashierWxH5Config)
arr.push(vdata.webCashierAliWapConfig)
let reqObject : any = JSON.stringify(arr)
req.updateById(API_URL_MCH_CONFIG, vdata.groupKey, {mchNo: vdata.recordId, configData: reqObject}).then(res => {
vdata.btnLoading = false
$infoBox.message.success('修改成功')
}).catch(res => {
vdata.btnLoading = false
})
})
}
// 更新会员配置
function confirmUpdateMemberConfig(){
$infoBox.confirmPrimary('确认修改会员配置吗?', '', () => {
vdata.btnLoading = true // 打开按钮上的 loading
let reqObject : any = JSON.parse(JSON.stringify(vdata.memberConfigData))
// 最大储值金额
reqObject.forEach(item => {
if (item.configKey == 'mbrMaxBalance') {
const mbrMaxBalance = item.configVal ? item.configVal : 0
item.configVal = Number.parseInt((mbrMaxBalance * 100).toFixed(0)).toString()
}
})
req.updateById(API_URL_MCH_CONFIG, vdata.groupKey, {mchNo: vdata.recordId, configData: reqObject}).then(res => {
$infoBox.message.success('修改成功')
vdata.btnLoading = false
selectTabs('memberConfig')
}).catch(res => {
vdata.btnLoading = false
})
})
}
// 确认更新
function confirmByApiEnt (e) {
$infoBox.confirmPrimary('确认修改商户的接口权限?', '', () => {
vdata.btnLoading = true // 打开按钮上的 loading
let arr : any = []
vdata.mchApiEntTableData.forEach(r => {
if(r.checked){
arr.push(r.key)
}
})
let configItem = [{ groupKey: vdata.groupKey, configName: '商户接口权限集合', configKey: 'mchApiEntList', configVal : JSON.stringify(arr) }]
req.updateById(API_URL_MCH_CONFIG, vdata.groupKey, {mchNo: vdata.recordId, configData: JSON.stringify(configItem)}).then(res => {
$infoBox.message.success('修改成功')
vdata.btnLoading = false
}).catch(res => {
vdata.btnLoading = false
})
})
}
// 全选,反选
function notifyAllCehcked(isChekced){
vdata.notifyTableData.filter(r => !r.disabled).forEach(r => {
r.checked = isChekced
})
}
// 全选,反选
function mchApiEntAllCehcked(isChekced){
vdata.mchApiEntTableData.filter(r => !r.disabled).forEach(r => {
r.checked = isChekced
})
}
// 变更 appId的事件
function changeAppId (value) {
immediatelyPay()
}
// 变更 门店ID的事件
function changeStoreId (value) {
immediatelyPay()
}
// 立即支付按钮
function immediatelyPay () {
// 判断是否选择门店
if (vdata.appId === '') {
vdata.isShowCashier = false
return false
}
// 判断是否选择门店
if (vdata.storeId === '') {
vdata.isShowCashier = false
return false
}
vdata.isShowCashier = true
// $mchConfigCreateCashier({
// appId: vdata.appId, // appId
// storeId: vdata.storeId, // storeId
// mchNo: vdata.recordId
// }).then(res => {
// vdata.cashierUrl = res
// }).catch(() => {
// payTestBarCode.value.processCatch()
// })
}
defineExpose({
show
})
</script>
<style lang="less" scoped>
.bottom-btn{
/deep/ div{
display: flex;
justify-content: center;
}
}
.typePopover {
position: absolute;
top: -30px;
left: 93px;
}
</style>

View File

@@ -0,0 +1,240 @@
<template>
<page-header-wrapper>
<a-card class="table-card">
<JeepaySearchForm :searchFunc="searchFunc" :resetFunc="() => { vdata.searchData= {} }">
<JeepaySearchInfoInput v-model:value="vdata.searchData['mchName']" placeholder="用户号/名称/手机号" :textUpStyle="true" :mchNoAndName="true" showType="MCH" />
<jeepay-text-up v-model:value="vdata.searchData['mchServiceName']" :placeholder="'服务商号/名称'" />
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData['state']" placeholder="商户状态">
<a-select-option value="">
全部
</a-select-option>
<a-select-option value="0">
禁用
</a-select-option>
<a-select-option value="1">
启用
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData['type']" placeholder="商户类型">
<a-select-option value="">
全部
</a-select-option>
<a-select-option value="1">
普通商户
</a-select-option>
<a-select-option value="2">
特约商户
</a-select-option>
</a-select>
</a-form-item>
</JeepaySearchForm>
<!-- 列表渲染 -->
<JeepayTable
ref="infoTable"
:init-data="true"
:req-table-data-func="reqTableDataFunc"
:table-columns="tableColumns"
:search-data="vdata.searchData"
row-key="mchNo"
@btnLoadClose="btnLoading=false"
>
<template #topBtnSlot>
<a-button v-if="$access('ENT_MCH_INFO_ADD')" type="primary" @click="addFunc">
<plus-outlined />新建
</a-button>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'mchName'">
<a @click="detailFunc(record.mchNo)"><b>{{ record.mchName }}</b></a>
</template>
<template v-if="column.key === 'state'">
<a-badge :status="record.state === 1?'processing':'error'" :text="record.state === 1?'启用':'禁用'" />
</template>
<template v-if="column.key === 'mchServiceName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>服务商名称{{record.mchServiceName}}</span><br>
<span>服务商号{{record.agentNo}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.mchServiceName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'type'">
<a-tag :color="record.type === 1 ? 'green' : 'orange'">
{{ record.type === 1 ? '普通商户':'特约商户' }}
</a-tag>
</template>
<template v-if="column.key === 'operation'">
<!-- 操作列插槽 -->
<JeepayTableColumns>
<a-button v-if="$access('ENT_MCH_INFO_EDIT')" type="link" @click="editFunc(record.mchNo)">修改</a-button>
<a-button v-if="$access('ENT_MCH_APP_CONFIG')" type="link" @click="mchAppConfig(record.mchNo)">应用配置</a-button>
<a-button v-if="$access('ENT_MCH_APP_CONFIG')" type="link" @click="showPayIfConfigList(record.mchNo)">支付配置</a-button>
<a-button v-if="vdata.configPageIsShow" type="link" @click="mchConfigFunc(record.mchNo)">高级功能配置</a-button>
<a-button v-if="$access('ENT_DEVICE_QRC_LIST')" type="link" @click="mchQrCodeConfig(record.mchNo)">码牌管理</a-button>
<a-button v-if="$access('ENT_DEVICE_SPEAKER_DEVICE')" type="link" @click="speakerConfig(record.mchNo)">云喇叭设备</a-button>
<a-button v-if="$access('ENT_DEVICE_PRINTER_DEVICE')" type="link" @click="printerConfig(record.mchNo)">云打印设备</a-button>
<a-button v-if="$access('ENT_MCH_APPLYMENT_LIST') && record.type == 2" type="link" @click="toMchApplymentPage(record.mchNo)">进件管理</a-button>
<a-button v-if="$access('ENT_MCH_STORE_LIST')" type="link" @click="mchStoreConfig(record.mchNo)">门店管理</a-button>
<!-- <a-button v-if="$access('ENT_MCH_INFO_DEL')" type="link" style="color: red" @click="delFunc(record.mchNo)">删除</a-button>-->
</JeepayTableColumns>
</template>
</template>
</JeepayTable>
</a-card>
<!-- 新增页面组件 -->
<InfoAddOrEdit ref="infoAddOrEdit" :callback-func="searchFunc" />
<!-- 详情页面组件 -->
<InfoDetail ref="infoDetail" :callback-func="searchFunc" />
<!--商户配置 -->
<MchConfig ref="mchConfigRef" />
<JeepayPayConfigUserDrawer ref="jeepayPayConfigUserDrawerRef" configMode="agentMch" />
</page-header-wrapper>
</template>
<script setup lang="ts">
import { API_URL_MCH_LIST, req, reqLoad, $getAgentConfig } from '@/api/manage'
import InfoAddOrEdit from './AddOrEdit.vue'
import InfoDetail from './Detail.vue'
import MchConfig from './MchConfig.vue'
import { ref, reactive, getCurrentInstance, onMounted } from 'vue'
import router from '@/router'
const { $infoBox,$access } = getCurrentInstance()!.appContext.config.globalProperties
const infoDetail = ref()
const infoAddOrEdit = ref()
const infoTable = ref()
const mchConfigRef = ref()
let tableColumns = reactive([
{ key: 'mchName', title: '用户名称', fixed: 'left',},
{ key: 'mchNo', title: '用户号', dataIndex: 'mchNo' , },
{ key: 'contactTel', title: '手机号', dataIndex: 'contactTel' ,},
{ key: 'mchServiceName', title: '服务商', dataIndex: 'mchServiceName', },
{ key: 'state', title: '状态', },
{ key: 'type', title: '商户类型',},
{ key: 'createdAt', dataIndex: 'createdAt', title: '创建日期',},
{ key: 'operation', title: '操作', fixed: 'right', align: 'center'}
])
let btnLoading = ref(false)
const vdata = reactive({
searchData: {} as any,
configPageIsShow: false, // 商户高级配置页面是否展示
})
onMounted(()=>{
// 高级配置页权限
getAgentConfig()
})
function queryFunc () {
btnLoading.value = true
infoTable.value.refTable(true)
}
// 请求table接口数据
function reqTableDataFunc(params: any) {
return req.list(API_URL_MCH_LIST, params)
}
function searchFunc () { // 点击【查询】按钮点击事件
infoTable.value.refTable(true)
}
function addFunc () { // 业务通用【新增】 函数
infoAddOrEdit.value.show()
}
function editFunc (recordId: any) { // 业务通用【修改】 函数
infoAddOrEdit.value.show(recordId)
}
function detailFunc (recordId: any) { // 商户详情页
infoDetail.value.show(recordId)
}
// 删除商户
function delFunc (recordId: any) {
$infoBox.confirmDanger('确认删除?', '该操作将删除商户下所有配置及用户信息', () => {
reqLoad.delById(API_URL_MCH_LIST, recordId).then((res: any) => {
infoTable.value.refTable(true)
$infoBox.message.success('删除成功')
})
})
}
function mchAppConfig (recordId: any) { // 应用配置
router.push({
path: '/apps',
query: { mchNo: recordId }
})
}
function mchStoreConfig (recordId: any) { // 门店管理
router.push({
path: '/store',
query: { mchNo: recordId }
})
}
function mchReceiverGroupConfig (recordId: any) { // 账号分组管理
router.push({
path: '/divisionReceiverGroup',
query: { mchNo: recordId }
})
}
function mchReceiverConfig (recordId: any) { // 账号管理
router.push({
path: '/divisionReceiver',
query: { mchNo: recordId }
})
}
function mchQrCodeConfig (recordId: any) { // 静态码管理
router.push({
path: '/qrc',
query: { mchNo: recordId }
})
}
function speakerConfig(recordId: any) {// 云喇叭管理
router.push({
path: '/speaker/device',
query: { mchNo: recordId }
})
}
function printerConfig(recordId: any) {// 云打印管理
router.push({
path: '/printer/device',
query: { mchNo: recordId }
})
}
function toMchApplymentPage(recordId){
router.push({
path: '/applyments',
query: { mchNo: recordId }
})
}
function mchConfigFunc (recordId: any) { // 配置 函数
mchConfigRef.value.show(recordId)
}
function getAgentConfig() {
$getAgentConfig('subMchPayInterfaceConfigIsUsable').then(config => {
if (config && config.configVal) {
vdata.configPageIsShow = config.configVal=='true'?true:false
}
})
}
const jeepayPayConfigUserDrawerRef = ref()
const showPayIfConfigList = function (recordId) { // 支付参数配置
jeepayPayConfigUserDrawerRef.value.show(recordId)
}
</script>

View File

@@ -0,0 +1,38 @@
<!-- 参数配置 弹层模式 -->
<template>
<a-drawer
v-model:visible="vdata.visible"
title="参数配置"
width="70%"
@close="vdata.visible = false"
>
<JeepayApplymentAppConfig v-if="vdata.visible" ref="jeepayApplymentAppConfig" />
</a-drawer>
</template>
<script lang="ts" setup>
import {ref, reactive, nextTick} from 'vue'
const jeepayApplymentAppConfig = ref()
const vdata : any = reactive({
applyId: null, // 更新对象ID
visible: false, // 是否显示弹层/抽屉
})
function show (applyId) { // 弹层打开事件
vdata.applyId = applyId
vdata.visible = true
nextTick(() => {
jeepayApplymentAppConfig.value.pageRender(applyId)
})
}
defineExpose({ show })
</script>

View File

@@ -0,0 +1,36 @@
<!-- 商户信息变更 弹层模式 -->
<template>
<a-drawer
v-model:visible="vdata.visible"
title="商户信息变更"
width="45%"
@close="vdata.visible = false"
>
<JeepayApplymentAppChangeConfig v-if="vdata.visible" :configMode="'agentApplyment'" ref="jeepayApplymentAppChangeConfigRef" />
</a-drawer>
</template>
<script lang="ts" setup>
import {ref, reactive, nextTick} from 'vue'
const jeepayApplymentAppChangeConfigRef = ref()
const vdata : any = reactive({
applyId: null, // 更新对象ID
visible: false, // 是否显示弹层/抽屉
})
function show (data) { // 弹层打开事件)
vdata.applyId = data.applyId
vdata.visible = true
nextTick(() => {
jeepayApplymentAppChangeConfigRef.value.pageRender(data)
})
}
defineExpose({ show })
</script>

View File

@@ -0,0 +1,325 @@
<template>
<a-drawer
v-model:visible="vdata.visible"
title="变更详情"
:body-style="{ paddingBottom: '80px' }"
width="50%"
@close="onClose"
>
<a-divider class="jeepay-m-divider" orientation="left" style="color: #1A66FF;" v-if="vdata.modifyApplyType == 1">基本信息详情</a-divider>
<a-divider class="jeepay-m-divider" orientation="left" style="color: #1A66FF;" v-if="vdata.modifyApplyType == 2">结算变更详情</a-divider>
<a-divider class="jeepay-m-divider" orientation="left" style="color: #1A66FF;" v-if="vdata.modifyApplyType == 6">结算类型详情</a-divider>
<a-divider class="jeepay-m-divider" orientation="left" style="color: #1A66FF;" v-if="vdata.modifyApplyType == 7">退款账户详情</a-divider>
<a-row justify="space-between" type="flex" v-if="vdata.modifyApplyType == 1 || vdata.modifyApplyType == 6">
<a-col :sm="12" v-if="vdata.modifyApplyType == 1">
<a-descriptions>
<a-descriptions-item label="商户简称">
{{ vdata.detailData.mchShortName }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12" v-if="vdata.modifyApplyType == 6">
<a-descriptions>
<a-descriptions-item label="结算变更类型">
{{ vdata.detailData.settlementType }}
</a-descriptions-item>
</a-descriptions>
</a-col>
</a-row>
<a-row justify="space-between" type="flex" v-if="vdata.modifyApplyType == 2">
<a-col :sm="12" >
<a-descriptions>
<a-descriptions-item label="结算账号类型">
<span v-if="vdata.detailData.settAccountType == 'C'">对私</span>
<span v-else-if="vdata.detailData.settAccountType == 'B'">对公</span>
<span v-else>{{ vdata.detailData.settAccountType || '' }}</span>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12" v-if="vdata.detailData.settAccountType == 'C'">
<a-descriptions>
<a-descriptions-item label="是否法人结算">
<a-radio-group v-model:value="vdata.detailData.illegal" :disabled="true">
<a-radio value="N">法人</a-radio>
<a-radio value="Y">非法人</a-radio>
</a-radio-group>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-form ref="stepForm3Ref" :label-col="{ span: 6 }" :model="vdata.detailData"
:wrapper-col="{ span: 12 }" layout="vertical" >
<a-divider/>
<a-form ref="stepForm2Ref" v-if="vdata.detailData.settAccountType =='C'" :label-col="{ span: 14 }" :model="vdata.detailData"
:wrapper-col="{ span: 14 }" layout="vertical" >
<div v-if="vdata.detailData.illegal === 'Y'">
<a-divider class="jeepay-m-divider" orientation="left" style="color: #1A66FF;">结算人信息</a-divider>
<a-row justify="space-between" type="flex">
<a-col :span="12">
<a-descriptions>
<a-descriptions-item label="结算人身份证人像面照片">
<a-image v-if="vdata.detailData['settAccountIdcard1Img']" class="img" :src="vdata.detailData['settAccountIdcard1Img']" />
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :span="12">
<a-descriptions>
<a-descriptions-item label="结算人身份证国徽面照片">
<a-image v-if="vdata.detailData['settAccountIdcard2Img']" class="img" :src="vdata.detailData['settAccountIdcard2Img']" />
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :span="12">
<a-descriptions >
<a-descriptions-item label="结算人名称">
{{vdata.detailData.settAccountName}}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :span="12">
<a-descriptions >
<a-descriptions-item label="结算卡身份证号">
{{vdata.detailData.settAccountIdcardNo}}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :span="12">
<a-descriptions >
<a-descriptions-item label="身份证有效期开始时间">
{{vdata.detailData.settAccountIdcardEffectBegin}}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :span="12">
<a-descriptions >
<a-descriptions-item label="身份证有效期截止时间">
{{vdata.detailData.settAccountIdcardEffectEnd}}
</a-descriptions-item>
</a-descriptions>
</a-col>
</a-row>
</div>
<a-divider class="jeepay-m-divider" orientation="left" style="color: #1A66FF;">结算账户信息</a-divider>
<a-row justify="space-between" type="flex">
<a-col :span="12">
<a-descriptions>
<a-descriptions-item label="结算卡图片">
<a-image v-if="vdata.detailData['settAccountLicenseImg']" class="img" :src="vdata.detailData['settAccountLicenseImg']" />
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :span="12" v-if="vdata.detailData.illegal === 'Y'">
<a-descriptions >
<a-descriptions-item label="非法人结算授权函">
<a-image v-if="vdata.detailData['nonLegSettleAuthPic']" class="img" :src="vdata.detailData['nonLegSettleAuthPic']" />
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :span="12" v-if="vdata.detailData.illegal === 'N'">
<a-descriptions >
<a-descriptions-item label="结算账户名">
<a-image v-if="vdata.detailData['settAccountName']" class="img" :src="vdata.detailData['settAccountName']" />
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :span="12">
<a-descriptions >
<a-descriptions-item label="结算账号">
{{vdata.detailData.settAccountNo}}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :span="12">
<a-descriptions >
<a-descriptions-item label="银行预留手机号">
{{vdata.detailData.phone}}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :span="12">
<a-descriptions >
<a-descriptions-item label="开户银行名称">
{{vdata.detailData.settAccountBankName}}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :span="12">
<a-descriptions >
<a-descriptions-item label="开户银行名称">
{{vdata.detailData.settAccountBankBranchAreaName}}
</a-descriptions-item>
</a-descriptions>
</a-col>
<!-- <a-col :span="12">-->
<!-- <a-descriptions >-->
<!-- <a-descriptions-item label="开户支行">-->
<!-- {{vdata.detailData.bankSubCode}}-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
<!-- </a-col>-->
</a-row>
</a-form>
<a-form ref="stepForm3Ref" v-if="vdata.detailData.settAccountType =='B'" :label-col="{ span: 12 }" :model="vdata.detailData"
:wrapper-col="{ span: 12 }" layout="vertical" >
<a-divider class="jeepay-m-divider" orientation="left" style="color: #1A66FF;">结算账户信息</a-divider>
<a-col :span="12">
<a-descriptions >
<a-descriptions-item label="开户许可证">
<a-image v-if="vdata.detailData['settAccountLicenseImg']" class="img" :src="vdata.detailData['settAccountLicenseImg']" />
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-row justify="space-between" type="flex">
<a-col :span="12">
<a-descriptions >
<a-descriptions-item label="结算账户名">
{{vdata.detailData.settAccountName}}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :span="12">
<a-descriptions >
<a-descriptions-item label="结算账号">
{{vdata.detailData.settAccountNo}}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :span="12">
<a-descriptions >
<a-descriptions-item label="银行预留手机号">
{{vdata.detailData.phone}}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :span="12">
<a-descriptions >
<a-descriptions-item label="开户行省市县">
{{vdata.detailData.settAccountBankBranchAreaName}}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :span="12">
<a-descriptions >
<a-descriptions-item label="开户银行名称">
{{vdata.detailData.settAccountBankName}}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :span="12">
<a-descriptions >
<a-descriptions-item label="开户支行">
{{vdata.detailData.bankSubCode}}
</a-descriptions-item>
</a-descriptions>
</a-col>
</a-row>
</a-form>
</a-form>
</a-row>
<a-row justify="space-between" type="flex" v-if="vdata.modifyApplyType == 7">
<a-col :sm="24" >
<a-descriptions>
<a-descriptions-item label="结算类型">
{{ vdata.detailData.refundWay || '' }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="24" >
<a-descriptions>
<a-descriptions-item label="收单机构编号">
{{ vdata.detailData.channelMchNo || '' }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="24" >
<a-descriptions>
<a-descriptions-item label="终端号">
{{ vdata.detailData.termNo || '' }}
</a-descriptions-item>
</a-descriptions>
</a-col>
</a-row>
</a-drawer>
</template>
<script lang="ts" setup>
import {API_URL_MCH_APPLYMENT_LIST, req, $getMerchantChangeModifyApply, $getMerchantInformation} from '@/api/manage'
import {defineProps,reactive, getCurrentInstance} from 'vue'
const { $hasAgentEnt, $SYS_NAME_MAP } = getCurrentInstance()!.appContext.config.globalProperties
const props = defineProps({
callbackFunc: { type: Function,default:null }
})
const vdata:any = reactive({
btnLoading: false,
modifyApplyType:0,
detailData: {}, // 数据对象
recordId: null, // 更新对象ID
visible: false, // 是否显示弹层/抽屉
})
function show (recordId) { // 弹层打开事件
vdata.detailData = { } // 数据清空
// if (this.$refs.infoFormModel !== undefined) {
// this.$refs.infoFormModel.resetFields()
// }
vdata.recordId = recordId
$getMerchantChangeModifyApply(recordId).then((res) => {
console.log(res,'resresresresres')
vdata.modifyApplyType = res.modifyApplyType
let applyDetailInfo =''
console.log(res.applyDetailInfo,'res.originDetailInfo')
if(res.applyDetailInfo){
applyDetailInfo = JSON.parse(res.applyDetailInfo)
}
vdata.detailData = applyDetailInfo
console.log(vdata.detailData,'vdata.detailData')
})
vdata.visible = true
}
function onClose () {
vdata.visible = false
}
defineExpose({
show //抛出show函数给父组件
})
</script>
<style>
.img {
width: 50px;
height: 50px;
}
</style>

View File

@@ -0,0 +1,224 @@
<template>
<a-drawer
v-model:visible="vdata.visible"
title="进件信息"
:body-style="{ paddingBottom: '80px' }"
width="40%"
@close="onClose"
>
<a-row justify="space-between" type="flex">
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="用户号">
{{ vdata.detailData.mchNo }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="渠道商号">
{{ vdata.detailData.isvNo }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col v-if="$hasAgentEnt()" :sm="12">
<a-descriptions>
<a-descriptions-item label="服务商号">
{{ vdata.detailData.agentNo }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col v-if="$hasAgentEnt()" :sm="12">
<a-descriptions>
<a-descriptions-item label="顶级服务商号">
{{ vdata.detailData.topAgentNo }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="接口名称">
{{ vdata.detailData.ifName }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="支付接口代码">
{{ vdata.detailData.ifCode }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-divider />
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商户全称">
{{ vdata.detailData.mchFullName }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商户简称">
{{ vdata.detailData.mchShortName }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商户类型">
{{ vdata.detailData.merchantType == 1?'小微':vdata.detailData.merchantType == 2?'个体':vdata.detailData.merchantType == 1?'企业':'其他' }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="进件状态">
<a-tag v-if="vdata.detailData.state === 0" color="default"> 草稿 </a-tag>
<a-tag v-if="vdata.detailData.state === 1" color="warning"> 审核中 </a-tag>
<a-tag v-if="vdata.detailData.state === 2" color="success"> 进件成功 </a-tag>
<a-tag v-if="vdata.detailData.state === 3" color="error"> 驳回待修改 </a-tag>
<a-tag v-if="vdata.detailData.state === 4" color="processing"> 待验证 </a-tag>
<a-tag v-if="vdata.detailData.state === 5" color="processing"> 待签约 </a-tag>
<a-tag v-if="vdata.detailData.state === 6" color="success"> 签约完成 </a-tag>
<a-tag v-if="vdata.detailData.state === 7" color="warning"> 等待系统预审核 </a-tag>
<a-tag v-if="vdata.detailData.state === 8" color="error"> 预审核拒绝 </a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="联系人姓名">
{{ vdata.detailData.contactName }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="联系人手机号">
{{ vdata.detailData.contactPhone }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商户拓展员ID">
{{ vdata.detailData.epUserId }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="进件来源">
<span v-if="vdata.detailData.applyPageType == 'PLATFORM_WEB'">运营平台系统</span>
<span v-else-if="vdata.detailData.applyPageType == 'AGENT_WEB'">服务商系统</span>
<span v-else-if="vdata.detailData.applyPageType == 'AGENT_APP'">{{ $SYS_NAME_MAP.AGENT_APP }}APP</span>
<span v-else-if="vdata.detailData.applyPageType == 'AGENT_LITE'">{{ $SYS_NAME_MAP.AGENT_APP }}小程序</span>
<span v-else-if="vdata.detailData.applyPageType == 'MCH_WEB'">商户系统</span>
<span v-else-if="vdata.detailData.applyPageType == 'MCH_APP'">{{ $SYS_NAME_MAP.MCH_APP }}APP</span>
<span v-else-if="vdata.detailData.applyPageType == 'MCH_LITE'">{{ $SYS_NAME_MAP.MCH_APP }}小程序</span>
<span v-else>{{ vdata.detailData.applyPageType || '' }}</span>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="系统申请单号">
{{ vdata.detailData.applyId }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="渠道申请单号">
{{ vdata.detailData.channelApplyNo }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="渠道自定义商户号">
{{ vdata.detailData.channelDiyMchNo }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="24">
<span style="color: black;">渠道响应参数:</span>
<a-form-item label="">
<a-textarea
v-model:value="vdata.detailData.succResParameter"
disabled="disabled"
style="height: 100px;color: black; margin-top: 10px;"
/>
</a-form-item>
</a-col>
<a-col :sm="24">
<span style="color: black;">渠道拓展参数1:</span>
<a-form-item label="">
<a-textarea
v-model:value="vdata.detailData.channelVar1"
disabled="disabled"
style="height: 100px;color: black; margin-top: 10px;"
/>
</a-form-item>
</a-col>
<a-col :sm="24">
<span style="color: black;">渠道拓展参数2:</span>
<a-form-item label="">
<a-textarea
v-model:value="vdata.detailData.channelVar2"
disabled="disabled"
style="height: 100px;color: black; margin-top: 10px;"
/>
</a-form-item>
</a-col>
<a-col :sm="24">
<span style="color: black;">响应提示信息:</span>
<a-form-item label="">
<a-textarea
v-model:value="vdata.detailData.applyErrorInfo"
disabled="disabled"
style="height: 100px;color: black; margin-top: 10px;"
/>
</a-form-item>
</a-col>
</a-row>
</a-drawer>
</template>
<script lang="ts" setup>
import { API_URL_MCH_APPLYMENT_LIST, req } from '@/api/manage'
import {defineProps,reactive, getCurrentInstance} from 'vue'
const { $hasAgentEnt, $SYS_NAME_MAP } = getCurrentInstance()!.appContext.config.globalProperties
const props = defineProps({
callbackFunc: { type: Function,default:null }
})
const vdata:any = reactive({
btnLoading: false,
detailData: {}, // 数据对象
recordId: null, // 更新对象ID
visible: false, // 是否显示弹层/抽屉
})
function show (recordId) { // 弹层打开事件
vdata.detailData = { 'state': 1, 'type': 1 } // 数据清空
// if (this.$refs.infoFormModel !== undefined) {
// this.$refs.infoFormModel.resetFields()
// }
vdata.recordId = recordId
req.getById(API_URL_MCH_APPLYMENT_LIST, recordId).then(res => {
vdata.detailData = res
})
vdata.visible = true
}
function onClose () {
vdata.visible = false
}
defineExpose({
show //抛出show函数给父组件
})
</script>

View File

@@ -0,0 +1,589 @@
<template>
<page-header-wrapper>
<!-- 列表页 -->
<a-card v-show=" !vdata.currentApplyPage.component" class="table-card">
<JeepaySearchForm :searchFunc="searchFunc" :resetFunc="resetFunc">
<!-- 时间搜索控件 -->
<a-form-item label="" class="table-search-item">
<JeepayDateRangePicker
ref="dateRangePicker"
v-model:value="vdata.searchData['queryDateRange']"
customDateRangeType="date"
/>
</a-form-item>
<!-- <jeepay-text-up v-model:value="vdata.searchData['unionSearchId']" :placeholder="'系统/渠道申请单号'" />-->
<!-- <JeepaySearchInfoInput v-model:value="vdata.searchData['mchUserName']" placeholder="用户号/名称/手机号" :textUpStyle="true" :mchNoAndName="true" showType="MCH" />-->
<!-- <jeepay-text-up v-model:value="vdata.searchData['mchServiceName']" :placeholder="'服务商号/服务商名称'" />-->
<jeepay-text-up v-model:value="vdata.searchData['mchApplyName']" :placeholder="'商户号/商户名称'" />
<jeepay-text-up v-model:value="vdata.searchData['ifName']" :placeholder="'支付通道'" />
<jeepay-text-up v-model:value="vdata.searchData['isvNo']" :placeholder="'渠道名称/渠道号'" />
<jeepay-text-up v-model:value="vdata.searchData['mchUserName']" :placeholder="'用户号/名称/手机号'" />
<jeepay-text-up v-model:value="vdata.searchData['mchServiceName']" :placeholder="'服务商号/服务商名称'" />
<jeepay-text-up v-model:value="vdata.searchData['autoConfigMchAppId']" :placeholder="'应用名称/应用ID'" />
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData.range" placeholder="应用类型" >
<a-select-option value="">全部</a-select-option>
<a-select-option value="0">线下场景</a-select-option>
<a-select-option value="1">线上场景</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData.merchantType" placeholder="商户类型" >
<a-select-option value="">全部</a-select-option>
<a-select-option value="1">小微</a-select-option>
<a-select-option value="2">个体</a-select-option>
<a-select-option value="3">企业</a-select-option>
</a-select>
</a-form-item>
<JeepaySelect ref="stateRef" placeholder="进件状态" :value="vdata.searchData.state" >
<a-select v-model:value="vdata.searchData.state" @change="stateRef.textupHandle()" >
<a-select-option value="">全部</a-select-option>
<a-select-option value="0">草稿</a-select-option>
<a-select-option value="1">审核中</a-select-option>
<a-select-option value="2">进件成功</a-select-option>
<a-select-option value="3">驳回待修改</a-select-option>
<a-select-option value="4">待验证</a-select-option>
<a-select-option value="5">待签约</a-select-option>
<a-select-option value="7">等待预审</a-select-option>
<a-select-option value="8">预审拒绝</a-select-option>
<a-select-option value="20">已风控</a-select-option>
<a-select-option value="21">已冻结</a-select-option>
<a-select-option value="100">进件请求中</a-select-option>
</a-select>
</JeepaySelect>
</JeepaySearchForm>
<!-- 列表渲染 -->
<JeepayTable
ref="infoTable"
:init-data="false"
:req-table-data-func="reqTableDataFunc"
:table-columns="tableColumns"
:search-data="vdata.searchData"
row-key="applyId"
>
<!-- 表格顶部按钮行插槽 -->
<template #topBtnSlot>
<a-button v-if="$access('ENT_MCH_APPLYMENT_ADD')" type="primary" @click="getVisibleAdd()"><plus-outlined />发起进件</a-button>
<!-- <a-button v-if="$access('ENT_MCH_APPLYMENT_ADD')" type="primary" @click="addFunc('')"><plus-outlined />发起进件</a-button>-->
</template>
<!-- body 渲染插槽 -->
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'state'">
<a-badge v-if="record.state == 0" status="default" text="草稿" />
<a-badge v-if="record.state == 1" status="warning" text="等待审核结果" />
<a-badge v-if="record.state == 2" status="success" text="进件成功" />
<a-badge v-if="record.state == 3" status="error" text="驳回待修改" />
<a-badge v-if="record.state == 4" status="processing" text="待验证" />
<a-badge v-if="record.state == 5" status="processing" text="待签约" />
<a-badge v-if="record.state == 7" status="processing" text="等待预审" />
<a-badge v-if="record.state == 8" status="processing" text="预审拒绝" />
<a-badge v-if="record.state == 20" status="error" text="已风控" />
<a-badge v-if="record.state == 21" status="error" text="已冻结" />
<a-badge v-if="record.state == 100" status="warning" text="进件请求中" />
<!-- <a-tooltip v-if="record.applyErrorInfo && record.state != 2" :title="record.applyErrorInfo">-->
<!-- <info-circle-outlined />-->
<!-- </a-tooltip>-->
<!-- <QrcodeOutlined v-if="record.state == 5 && record.ifCode === 'alipay'" style="font-size: 16px;margin-left: 10px;" @click="showQrImgFunc(record)" />-->
<!-- <QrcodeOutlined-->
<!-- v-if="(record.state == 4 || record.state == 5) && (record.ifCode === 'wxpay' || record.ifCode === 'sftpay')"-->
<!-- style="font-size: 16px;margin-left: 10px;"-->
<!-- @click="showQrImgFunc(record)"-->
<!-- />-->
<link-outlined v-if="record.state == 5" @click="nextBizsDrawerRef.show(record.applyId)" style="font-size: 16px; padding-left: 5px;color: #1965FF" />
<a-tooltip v-if="record.applyErrorInfo && record.state == 3 || record.state == 8 " :title="record.applyErrorInfo">
<info-circle-outlined style="font-size: 16px; padding-left: 5px;color: #FF0000"/>
</a-tooltip>
<a-tooltip v-if="record.ifCode == 'kqpay' && record.state == 12" title="快钱通道营业执照商户支持先开通后审核复审期间的交易会在人工审批完成后结算复审时间一般为1个工作日。">
<info-circle-outlined style="font-size: 16px; padding-left: 5px;color: #FF0000"/>
</a-tooltip>
<QrcodeOutlined v-if=" (record.state == 4) && (record.ifCode === 'wxpay' || record.ifCode === 'sftpay') " style="font-size: 16px; margin-left: 10px" @click="showQrImgFunc(record)" />
<reload-outlined
@click="queryChannelState(record.applyId, record.state)"
style="color: #1890ff; margin-left: 10px"
v-if="(record.state == 1 || record.state == 4 || record.state == 7 || record.state == 5 || record.state == 12) && $access('ENT_MCH_APPLYMENT_GET_INFO') " />
</template>
<template v-if="column.key === 'type'">
<a-tag :color="record.type === 1 ? 'green' : 'orange'">
{{ record.type === 1 ? '普通商户':'特约商户' }}
</a-tag>
</template>
<template v-if="column.key === 'applyId'">
<a @click="detailFunc(record.applyId)"><b>{{ record.applyId }}</b></a>
</template>
<template v-if="column.key === 'merchantType'">
{{ record.merchantType === 1 ? '小微':record.merchantType === 2 ? '个体':record.merchantType === 3 ? '企业':'其他组织' }}
</template>
<template v-if="column.key == 'range'">
<a-tag v-if="record.range == 0" color="orange">线下场景</a-tag>
<a-tag v-if="record.range == 1" color="green">线上场景</a-tag>
</template>
<template v-if="column.key === 'mchServiceName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>服务商名称{{record.mchServiceName}}</span><br>
<span>服务商号{{record.agentNo}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.mchServiceName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'mchShortName'">
<a-tooltip overlayClassName="tooltip-box-name">
<template #title>
<span>商户全称{{record.mchFullName}}</span><br>
<span>商户简称{{record.mchShortName}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.mchShortName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'autoConfigMchAppId'">
<a-tooltip overlayClassName="tooltip-box-name">
<template #title>
<span>应用名称{{record.appName}}</span><br>
<span>应用ID{{record.autoConfigMchAppId}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.appName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'mchUserName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>用户名称{{record.mchUserName}}</span><br>
<span>用户手机号{{record.mchUserPhone}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.mchUserName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'applyPageType'">
<span v-if="record.applyPageType == 'PLATFORM_WEB'">运营平台系统</span>
<span v-else-if="record.applyPageType == 'AGENT_WEB'">服务商系统</span>
<span v-else-if="record.applyPageType == 'AGENT_APP'">{{ $SYS_NAME_MAP.AGENT_APP }}APP</span>
<span v-else-if="record.applyPageType == 'AGENT_LITE'">{{ $SYS_NAME_MAP.AGENT_APP }}小程序</span>
<span v-else-if="record.applyPageType == 'MCH_WEB'">商户系统</span>
<span v-else-if="record.applyPageType == 'MCH_APP'">{{ $SYS_NAME_MAP.MCH_APP }}APP</span>
<span v-else-if="record.applyPageType == 'MCH_LITE'">{{ $SYS_NAME_MAP.MCH_APP }}小程序</span>
<span v-else>{{ record.applyPageType || '' }}</span>
</template>
<template v-if="column.key === 'operation'">
<!-- 以下为 运营 服务商通用按钮 -->
<JeepayTableColumns>
<a-button v-if="$access('ENT_MCH_APPLYMENT_EDIT') && (record.state == 3 || record.state == 8)" type="link" @click="editOrViewFunc(record.applyId, false)">修改</a-button>
<a-button v-if="$access('ENT_MCH_APPLYMENT_VIEW') && record.state == 2" type="link" @click="editOrViewFunc(record.applyId, true)">详情</a-button>
<a-button v-if="$access('ENT_MCH_APPLYMENT_EDIT') && record.state == 0" type="link" @click="editOrViewFunc(record.applyId, false)">继续填写</a-button>
<a-button v-if="$access('ENT_MCH_APPLYMENT_VIEW')" type="link" @click="detailFunc(record.applyId)">进件信息</a-button>
<a-button v-if="$access('ENT_MCH_APPLYMENT_ADD')" type="link" @click="toCopySelectIfCodePage(record.applyId, record.mchNo,record.range)">复用信息</a-button>
<!-- 审核中 待验证 签约完成 -->
<a-button
v-if="$access('ENT_MCH_APPLYMENT_GET_INFO') && (record.state == 1 || record.state == 4 || record.state == 5)"
type="link"
@click="queryChannelState(record.applyId, record.state)"
>
获取最新结果
</a-button>
<!-- 进件成功后可发起 参数配置 -->
<!-- <a-button v-if="$access('ENT_MCH_APPLYMENT_PAY_CONFIG') && record.state == 2" type="link" @click="appConfigDrawerRef.show(record.applyId)">参数配置</a-button>-->
<!-- 1审核中 2进件成功 4待验证 5待签约 6签约完成/审核成功 -->
<!-- <a-button-->
<!-- v-if="$access('ENT_MCH_APPLYMENT_SIGN') && (record.state == 1 || record.state == 2 || record.state == 4 || record.state == 5 || record.state == 6)"-->
<!-- type="link"-->
<!-- @click="nextBizsDrawerRef.show(record.applyId)"-->
<!-- >-->
<!-- 签约开通-->
<!-- </a-button>-->
<a-button @click="oauthFuncButton(record.applyId,record.state)" v-if="record.ifCode != 'zftpay' && $access('ENT_MCH_APPLYMENT_SIGN') && ( record.state == 2 || record.state == 4 || record.state == 5 || record.state == 6 || record.state == 100)" type="link" >
<span v-if="record.state == 2" >实名认证</span>
<span v-else >签约开通</span>
</a-button>
</JeepayTableColumns>
</template>
</template>
</JeepayTable>
<a-modal v-model:visible="vdata.qrCodeVisible" title="商户签约二维码" :footer="null" :width="250" @ok="() => vdata.qrCodeVisible = false">
<QrcodeVue :value="vdata.confirmUrl" :size="200" />
</a-modal>
<!-- 微信签约弹窗 -->
<a-modal v-model:visible="vdata.wxQrCodeVisible" title="二维码" :footer="null" :width="250" @ok="() => vdata.wxQrCodeVisible = false">
<span>请超级管理员使用微信扫描二维码根据页面指引完成签约</span>
<img :src="vdata.confirmUrl" style="width: 200px;height: 200px;">
</a-modal>
<!-- 收付通签约弹窗 -->
<a-modal v-model:visible="vdata.sftQrCodeVisible" title="二维码" :footer="null" :width="250" @ok="() => vdata.sftQrCodeVisible = false">
<span>请超级管理员使用微信扫描二维码根据页面指引完成签约</span>
<QrcodeVue :value="vdata.confirmUrl" style="padding-top: 10px;" :size="200" />
</a-modal>
</a-card>
<!-- 定义组件分为 第一步和第二步 -->
<a-card>
<component :is="vdata.currentApplyPage.component" ref="applyPageComponentRef" :listPageSearchMchNo="vdata.searchData.mchNo" @switchApplyPage="switchApplyPage" />
</a-card>
<JeepayModelMchList ref="jeepayModelMchList" showType="MCH" @selectFinishFunc="searchMchFinishFunc" />
<!-- 参数配置 弹层 -->
<AppConfigDrawer ref="appConfigDrawerRef" />
<JeepayMchOauth2 v-show="vdata.visibleOauth2" ref="jeepayMchOauth2Ref" />
<!-- 后续流程 弹层 -->
<NextBizsDrawer ref="nextBizsDrawerRef" />
<!-- 详情页面组件 -->
<InfoDetail ref="infoDetail" :callback-func="searchFunc" />
<a-modal v-model:visible="vdata.visibleAdd" title="" :footer="null" width="50%" @cancel="handleCancel" class="set-img-box" >
<div class="state-box-div" >
<div>
<img :src="vdata.banerapps" alt="" >
<div class="left-bottom">
<a-button type="primary" @click="addFunc('',1)">发起进件 </a-button>
</div>
</div>
</div>
<div class="state-box-div">
<div class="state-box">
<img :src="vdata.xianxiaImg" alt="" >
</div>
<div class="state-box">
<img :src="vdata.xianshangImg" alt="" >
<div class="right-bottom">
<a-button type="primary" @click="addFunc('',2)">发起进件</a-button>
</div>
</div>
</div>
</a-modal>
</page-header-wrapper>
</template>
<script setup lang="ts">
import { API_URL_MCH_APPLYMENT_LIST, req, $getMchApplymentChannelState, $getPayConfigIfcodes } from '@/api/manage'
import {ref, reactive, getCurrentInstance, nextTick, provide, computed, onMounted } from 'vue'
import QrcodeVue from 'qrcode.vue'
import ApplySelectMchAndIfcode from '@/components/link/JeepayUIComponents/JeepayMchApplyment/JeepayMchApplymentSelectMchAndIfcode.vue'
import ApplyMchDetailInfoEntry from '@/components/link/JeepayUIComponents/JeepayMchApplyment/JeepayMchApplymentDetailInfoEntry.vue'
import AppConfigDrawer from './AppConfigDrawer.vue'
import NextBizsDrawer from './NextBizsDrawer.vue'
import InfoDetail from './NewDetail.vue'
import { useRoute,useRouter } from 'vue-router'
const { $infoBox, $access, $SYS_NAME_MAP } = getCurrentInstance()!.appContext.config.globalProperties
/** 定义全部组件 **/
const allApplyPage = {
'list': { name: '列表', component: null },
'applySelectMchAndIfcode': { name: '选择商户及进件渠道', component: ApplySelectMchAndIfcode },
'applyMchDetailInfoEntry': { name: '填写进件资料', component: ApplyMchDetailInfoEntry }
}
// 动态组件
const applyPageComponentRef = ref()
const jeepayModelMchList = ref()
const jeepayMchOauth2Ref = ref();
const appConfigDrawerRef = ref()
const nextBizsDrawerRef = ref()
const infoDetail = ref()
const infoTable = ref()
const dateRangePicker = ref()
let tableColumns = reactive([
{ key: "applyId", dataIndex: "applyId", fixed: "left", title: "商户号", align: "center" },
{ key: "mchShortName", title: "商户简称", dataIndex: "mchShortName" },
{ key: "ifName", title: "支付通道", dataIndex: "ifName" },
{ key: "isvNo", title: "所属渠道", dataIndex: "isvNo" },
{ key: "merchantType", title: "商户类型", dataIndex: "merchantType" },
{ key: "state", title: "进件状态" },
{ key: 'mchUserName', title: '用户名称', dataIndex: 'mchUserName'},
{ key: 'mchServiceName', title: '服务商名称', dataIndex: 'mchServiceName' },
{ key: "autoConfigMchAppId",title: "所属应用", dataIndex: "autoConfigMchAppId",},
{ key: "range", title: "应用类型" },
{ key: "lastApplyAt", dataIndex: "lastApplyAt", title: "最后提交日期" },
{ key: "createdAt", dataIndex: "createdAt", title: "创建日期" },
{ key: "operation", title: "操作", fixed: "right", align: "center" },
])
let vdata : any = reactive({
banerapps:"https://syb-manage.oss-cn-hangzhou.aliyuncs.com/shouyinbei/banerapps.png",
xianxiaImg:"https://syb-manage.oss-cn-hangzhou.aliyuncs.com/shouyinbei/xianxia.png",
xianshangImg:"https://syb-manage.oss-cn-hangzhou.aliyuncs.com/shouyinbei/xianshang.png",
visibleAdd:false,
searchData: { queryDateRange: '' }, // 搜索条件
currentApplyPage: allApplyPage.list, // 当前的步骤组件 null表示列表页
backupApplyPage: null, // 备份: 用作快速恢复
mchApplymentData: { }, //商户进件资料
isView: false, // 是否预览模式
ifCodeList: [],
copyInfoSourceApplyId: '', // 副本资料
})
onMounted(() => {
vdata.searchData['mchNo'] = useRoute().query.mchNo
if($access('ENT_MCH_APPLYMENT')){
$getPayConfigIfcodes('CURRENT', 'agentApplyment', '').then((res) => {
vdata.ifCodeList = res
})
}
searchFunc()
})
// 向所有子组件注入参数 (子组件可直接更改此值)
provide('mchApplymentData', computed( ()=> vdata.mchApplymentData) )
provide('isView', computed( ()=> vdata.isView) )
// 向所有子组件注入参数: 配置模式: mgrApplyment / agentApplyment / mchApplyment
provide('configMode', 'agentApplyment')
// 向所有子组件注入参数 副本资料来源ID
provide('copyInfoSourceApplyId', computed( ()=> vdata.copyInfoSourceApplyId) )
// 请求table接口数据
function reqTableDataFunc(params: any) {
return req.list(API_URL_MCH_APPLYMENT_LIST, params)
}
// 点击【查询】按钮点击事件
function searchFunc () {
infoTable.value.refTable(true)
}
const router = useRouter(); //这是全部路由
// 切换步骤: 不清空数据
function switchApplyPage(page){
if (page == "applyAddApps") {
router.push({
path: "/apps",
});
return false;
}
if (page == "applyMch") {
router.push({
path: "/mch",
});
return false;
}
vdata.backupApplyPage = vdata.currentApplyPage
vdata.currentApplyPage = allApplyPage[page]
if(vdata.currentApplyPage && vdata.currentApplyPage.component){
nextTick(() => { applyPageComponentRef.value.pageRender(vdata.sceneType) })
}else{
delete vdata.searchData.mchNo
//列表页
searchFunc() // 刷新
}
}
function detailFunc (recordId: any) { // 进件详情页
vdata.copyInfoSourceApplyId = '' // 副本清空。
infoDetail.value.show(recordId)
}
// 点击【发起进件】的操作
function addFunc(copyInfoSourceApplyId = '',sceneType=0){
vdata.visibleAdd = false
vdata.sceneType = sceneType
vdata.copyInfoSourceApplyId = copyInfoSourceApplyId
vdata.backupApplyPage = null // 再次点击进件将无法再次恢复
//进件数据重置
vdata.mchApplymentData = {}
req.list(API_URL_MCH_APPLYMENT_LIST, {pageNumber:"1",pageSize:1}).then(res => {
if(res.records.length > 0){
vdata.mchApplymentData = {mchNo:res.records[0].mchNo,sceneType:sceneType}
}
//进件数据重置
vdata.isView = false
switchApplyPage('applySelectMchAndIfcode')
})
// vdata.isView = false
// switchApplyPage('applySelectMchAndIfcode')
}
// 修改资料 然后重新发起进件操作
function editOrViewFunc(applyId, isView){
console.log(applyId, isView);
vdata.copyInfoSourceApplyId = '' // 副本清空。
vdata.backupApplyPage = null // 再次点击进件将无法再次恢复
vdata.isView = isView
req.getById(API_URL_MCH_APPLYMENT_LIST, applyId, { originData: vdata.isView?'0':'1' } ).then(res => {
let { applyId, channelApplyNo, mchNo, ifCode, applyDetailInfo, applyErrorInfo, isvNo, state ,range,autoConfigMchAppId} = res
vdata.mchApplymentData = {applyId, channelApplyNo, mchNo, ifCode, applyDetailInfo, applyErrorInfo,isvNo, state,range,autoConfigMchAppId}
switchApplyPage('applyMchDetailInfoEntry')
})
}
// 查询最新状态
function queryChannelState(applyId, currState){
$getMchApplymentChannelState(applyId).then(res => {
if(currState != res.state){
$infoBox.message.success('状态已更新')
searchFunc() // 更新列表
}else{
$infoBox.message.info('状态无变化')
}
})
}
// 恢复上一次步骤
function recoveryApplyPage(){
vdata.currentApplyPage = vdata.backupApplyPage
}
// 获取下拉框组件
const ifCodeRef = ref()
const stateRef = ref()
let isReset = ref(0) // 下拉搜索框是否重置
// 清空搜索项
const resetFunc = () => {
isReset.value++ // 下拉搜索框重置
dateRangePicker.value.returnSelectModel()
vdata.searchData= {}
}
// 选择商户 / 应用 完成后的操作
function searchMchFinishFunc(selectVal){
if(selectVal[0]){
vdata.searchData.mchNo = selectVal[0]
}
jeepayModelMchList.value.close()
}
// 显示二维码图片
function showQrImgFunc(record){
if (record.ifCode == 'wxpay') {
vdata.wxQrCodeVisible = true
}else if (record.ifCode == 'sftpay') {
vdata.sftQrCodeVisible = true
}else if (record.ifCode == 'fuioupay') {
vdata.qrCodeVisible = true
vdata.confirmUrl = JSON.parse(record.applyErrorInfo).signUrl
return
}else {
vdata.qrCodeVisible = true
}
vdata.confirmUrl = record.applyErrorInfo
}
function oauthFunc(applyId) {
vdata.visibleOauth2 = true;
jeepayMchOauth2Ref.value.show(applyId);
}
// 副本 copySourceApplyId : 需要copy的资料来源。
function toCopySelectIfCodePage(copySourceApplyId, mchNo,range){
const sceneType = parseInt(range) + 1;
vdata.searchData.mchNo = mchNo
addFunc(copySourceApplyId,sceneType)
}
function oauthFuncButton(applyId,state){
if(state == 2){
oauthFunc(applyId)
}else{
nextBizsDrawerRef.value.show(applyId)
}
}
function getVisibleAdd(){
vdata.visibleAdd = true
}
function handleCancel (e) {
vdata.visibleAdd = false
}
</script>
<style>
.state-box-div{
width: 100%;
padding: unset !important;
}
.state-box-div img{
width: 100%;
height: 100%;
}
.set-img-box .ant-modal-body{
padding: unset !important;
}
.state-box{
padding: unset !important;
}
.state-box{
width: 50%;
}
.state-box img{
width: 100%;
height: 100%;
}
.visibleAdd-img{
//display: flex;
}
.ant-modal-content svg{
color: #ffffff;
font-weight: bold;
font-size: 25px;
}
.left-bottom{
border-radius: 10px;
position: absolute;
left: 5%;
bottom: 5%;
}
.left-bottom button{
border-radius: 10px;
}
.right-bottom{
position: absolute;
right: 35%;
bottom: 5%;
}
.right-bottom button{
border-radius: 10px;
}
</style>

View File

@@ -0,0 +1,262 @@
<template>
<page-header-wrapper>
<a-card>
<JeepaySearchForm :searchFunc="searchFunc" :resetFunc="resetFunc">
<!-- 时间搜索控件 -->
<a-form-item label="" class="table-search-item">
<JeepayDateRangePicker ref="dateRangePicker" v-model:value="vdata.searchData['queryDateRange']"
customDateRangeType="date" />
</a-form-item>
<jeepay-text-up v-model:value="vdata.searchData['modifyApplyId']" :placeholder="'申请单号'" />
<jeepay-text-up v-model:value="vdata.searchData['mchShortName']" :placeholder="'商户号/商户名称'" />
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData.modifyApplyType" placeholder="申请单类型">
<a-select-option value="">全部</a-select-option>
<a-select-option value="1">基本资料变更</a-select-option>
<a-select-option value="2">结算信息变更</a-select-option>
<!-- <a-select-option value="3">费率信息变更</a-select-option>-->
<a-select-option value="6">结算类型变更</a-select-option>
<a-select-option value="7">退款账户绑定</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData.state" placeholder="申请单状态">
<a-select-option value="">全部</a-select-option>
<a-select-option value="0">草稿</a-select-option>
<a-select-option value="1">审核中</a-select-option>
<a-select-option value="2">审核成功</a-select-option>
<a-select-option value="3">驳回</a-select-option>
<a-select-option value="5">待签约</a-select-option>
</a-select>
</a-form-item>
</JeepaySearchForm>
<!-- 列表渲染 -->
<JeepayTable
ref="infoTable"
:initData="true"
:reqTableDataFunc="reqTableDataFunc"
:tableColumns="tableColumns"
:searchData="vdata.searchData"
@btnLoadClose="vdata.btnLoading = false"
rowKey="storeId"
>
<!-- body 渲染插槽 -->
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'state'">
<a-badge v-if="record.state == 0" status="default" text="草稿" />
<a-badge v-if="record.state == 1" status="warning" text="审核中" />
<a-badge v-if="record.state == 2" status="success" text="审核成功" />
<a-badge v-if="record.state == 3" status="error" text="驳回" />
<a-badge v-if="record.state == 9" status="warning" text="已通过未生效" />
<a-badge v-if="record.state == 5" status="processing" text="待签约" />
<a-badge v-if="record.state == 30" status="processing" text="待商户授权" />
<!-- <a-badge v-if="record.state == 7" status="processing" text="等待预审" />-->
<!-- <a-badge v-if="record.state == 8" status="processing" text="预审拒绝" />-->
<!-- <a-badge v-if="record.state == 20" status="error" text="已风控" /> -->
<!-- <a-badge v-if="record.state == 21" status="error" text="已冻结" /> -->
<link-outlined v-if="record.state == 5" @click="oauthFuncButton(record.applyId)" style="font-size: 16px; padding-left: 5px;color: #1965FF" />
<reload-outlined @click="queryChannelState(record.modifyApplyId, record.state)" style="color: #1890ff; margin-left: 10px" v-if="record.state == 5" />
<a-tooltip v-if="record.state == 3" :title="record.applyErrorInfo">
<info-circle-outlined style="color:#F00;margin-left: 5px"/>
</a-tooltip>
<a-tooltip v-if="record.state == 9" title="次日生效">
<info-circle-outlined style="color:#F00;margin-left: 5px"/>
</a-tooltip>
</template>
<template v-if="column.key === 'agentName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>服务商名称{{record.agentName}}</span><br>
<span>服务商号{{record.agentNo}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.agentName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'mchUserName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>用户名称{{record.mchUserName}}</span><br>
<span>用户手机号{{record.mchUserPhone}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.mchUserName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'modifyApplyType'">
<a-tag
:key="record.modifyApplyType"
:color="record.modifyApplyType === 1?'blue':record.modifyApplyType === 2?'orange':record.modifyApplyType === 3?'green':record.modifyApplyType === 6?'green':record.modifyApplyType === 7?'cyan':'volcano'"
>
{{ record.modifyApplyType === 1?'基本资料变更':record.modifyApplyType === 2?'结算信息变更':record.modifyApplyType === 3?'费率信息变更':record.modifyApplyType === 6?'结算类型变更':record.modifyApplyType === 7?'退款账户绑定':'其他' }}
</a-tag>
</template>
<template v-if="column.key === 'operation'">
<JeepayTableColumns>
<a-button type="link" @click="getChanageConfigFunc(record.modifyApplyId)" v-if="record.state == 3 || record.state == 0">变更修改</a-button>
<a-button type="link" @click="getChanageDetailFunc(record.modifyApplyId)" >详情</a-button>
</jeepaytablecolumns>
</template>
</template>
</JeepayTable>
</a-card>
</page-header-wrapper>
<!-- 签约开通 弹层 -->
<NextBizsDrawer ref="nextBizsDrawerRef" />
<!-- 详情页面组件 -->
<JeepMchApplyChangeDetail ref="infoDetail" :callback-func="searchFunc" />
<JeepayMchChangeConfig ref="jeepayMchChangeConfigRef" v-if="vdata.visibleMchChange" />
<SigningConfig ref="signingConfigRef" v-show="vdata.visibleSigningConfig" />
</template>
<script lang="ts" setup>
import {
API_URL_MCH_STORE_LIST,
req,
reqLoad,
API_URL_MCH_MODIFY_APPLYMENT_LIST,$getMchApplymentsModifyApplyId,$getMerchantChangeModifyApply
} from "@/api/manage";
import { useUserStore } from "@/store/modules/user";
import {
onMounted,
ref,
reactive,
getCurrentInstance,
nextTick,
provide,
computed,
} from "vue";
import { useRouter, useRoute } from "vue-router";
import NextBizsDrawer from './NextBizsDrawer.vue'
import InfoDetail from './ChangeDetail.vue'
import SigningConfig from "./SigningConfig.vue";
// 获取全局函数
const { $infoBox, $access } =
getCurrentInstance()!.appContext.config.globalProperties;
const userStore = useUserStore();
const router = useRouter(); //这是全部路由
// tableColumns 顺序不可随意修改 (关联下方列显示)
const tableColumns = reactive([
{ key: 'modifyApplyType', dataIndex: 'modifyApplyType', title: '申请单类型' },
{ key: 'modifyApplyId', title: '申请单号', dataIndex: 'modifyApplyId', },
{ key: 'applyId', title: '商户号', dataIndex: 'applyId', },
{ key: 'mchShortName', title: '商户简称', dataIndex: 'mchShortName', },
{ key: 'mchUserName', title: '用户名称', dataIndex: 'mchUserName'},
{ key: 'agentName', title: '服务商名称', dataIndex: 'agentName' },
{ key: 'ifName', title: '支付通道', dataIndex: 'ifName', },
{ key: 'state', title: '申请单状态', fixed: 'right',},
{ key: 'createdAt', dataIndex: 'createdAt', title: '申请时间', },
{ key: 'operation', title: '操作', fixed: 'right', align: 'center' }
]);
// 如果用户类型为店长、店员时
if (
userStore.userInfo["userType"] == 11 ||
userStore.userInfo["userType"] == 12
) {
// 删除是否默认的列展示
tableColumns.splice(2, 1);
}
const infoDetail = ref();
const infoTable = ref();
const jeepayMchChangeConfigRef = ref()
const nextBizsDrawerRef = ref()
const signingConfigRef = ref();
const vdata: any = reactive({
visibleMchChange:false,
visibleSigningConfig:false,
visibleNotes: false,
tableColumns: tableColumns,
searchData: {
mchApplyId: "",
},
btnLoading: false,
notes: {
appid: "",
value: "",
},
});
// 请求table接口数据
function reqTableDataFunc(params) {
return req.list(API_URL_MCH_MODIFY_APPLYMENT_LIST, params);
}
function searchFunc() {
// 点击【查询】按钮点击事件
vdata.btnLoading = true; // 打开查询按钮的loading
infoTable.value.refTable(true);
}
const resetFunc = () => {
vdata.searchData = {};
};
//变更详情
function getChanageConfigFunc(applyId){
vdata.visibleMchChange = true
jeepayMchChangeConfigRef.value.show(applyId)
}
//详情
function getChanageDetailFunc(recordId : any) { // 进件详情页
vdata.copyInfoSourceApplyId = '' // 副本清空。
infoDetail.value.show(recordId)
}
//签约
function oauthFuncButton(applyId){
vdata.visibleChangeConfig = true;
signingConfigRef.value.show({applyId:applyId});
}
// 查询最新状态
// 查询最新状态
function queryChannelState(applyId, currState) {
$getMchApplymentsModifyApplyId(applyId).then((res) => {
$getMerchantChangeModifyApply(applyId).then((resData) => {
if (currState != resData.state) {
$infoBox.message.success("状态已更新");
searchFunc(); // 更新列表
} else {
$infoBox.message.info("状态无变化");
}
})
});
}
</script>
<style>
.two-lines {
color: #1965ff;
max-width: 150px;
display: block;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.my-tooltip .ant-btn {
width: 600px !important;
background-color: #f0f0f0;
color: #333;
border: 1px solid #ddd;
padding: 5px;
border-radius: 4px;
}
.ant-dropdown-menu-item-only-child{
text-align: center;
}
</style>

View File

@@ -0,0 +1,888 @@
<template>
<page-header-wrapper>
<!-- 列表页 -->
<a-card v-show=" !vdata.currentApplyPage.component" class="table-card">
<JeepaySearchForm :searchFunc="searchFunc" :resetFunc="resetFunc">
<!-- 时间搜索控件 -->
<a-form-item label="" class="table-search-item">
<JeepayDateRangePicker
ref="dateRangePicker"
v-model:value="vdata.searchData['queryDateRange']"
customDateRangeType="date"
/>
</a-form-item>
<!-- <JeepaySearchInfoInput v-model:value="vdata.searchData['mchUserName']" placeholder="用户号/名称/手机号" :textUpStyle="true" :mchNoAndName="true" showType="MCH" />-->
<jeepay-text-up v-model:value="vdata.searchData['mchApplyName']" :placeholder="'商户号/商户名称'" />
<jeepay-text-up v-model:value="vdata.searchData['ifName']" :placeholder="'支付通道'" />
<jeepay-text-up v-model:value="vdata.searchData['isvNo']" :placeholder="'渠道名称/渠道号'" />
<jeepay-text-up v-model:value="vdata.searchData['mchUserName']" :placeholder="'用户号/名称/手机号'" />
<jeepay-text-up v-model:value="vdata.searchData['mchServiceName']" :placeholder="'服务商号/服务商名称'" />
<jeepay-text-up v-model:value="vdata.searchData['autoConfigMchAppId']" :placeholder="'应用名称/应用ID'" />
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData.range" placeholder="应用类型" >
<a-select-option value="">全部</a-select-option>
<a-select-option value="0">线下场景</a-select-option>
<a-select-option value="1">线上场景</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData.merchantType" placeholder="商户类型" >
<a-select-option value="">全部</a-select-option>
<a-select-option value="1">小微</a-select-option>
<a-select-option value="2">个体</a-select-option>
<a-select-option value="3">企业</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData.authenticationState" placeholder="认证状态" >
<a-select-option value="4">全部已认证</a-select-option>
<a-select-option value="2">微信未认证</a-select-option>
<a-select-option value="3">支付宝未认证</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData.settlementType" placeholder="结算类型" >
<a-select-option value="T1">T1</a-select-option>
<a-select-option value="D1">D1</a-select-option>
<a-select-option value="D0">D0</a-select-option>
<a-select-option value="定时结算">定时结算</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData.state" placeholder="商户状态" >
<a-select-option value="">全部</a-select-option>
<!-- <a-select-option value="0">草稿</a-select-option> -->
<!-- <a-select-option value="1">审核中</a-select-option> -->
<a-select-option value="2">进件成功</a-select-option>
<!-- <a-select-option value="3">驳回待修改</a-select-option> -->
<!-- <a-select-option value="4">待验证</a-select-option> -->
<!-- <a-select-option value="5">待签约</a-select-option> -->
<!-- <a-select-option value="7">等待预审</a-select-option> -->
<!-- <a-select-option value="8">预审拒绝</a-select-option> -->
<a-select-option value="20">已风控</a-select-option>
<a-select-option value="21">已冻结</a-select-option>
<a-select-option value="22">已注销</a-select-option>
</a-select>
</a-form-item>
</JeepaySearchForm>
<!-- 列表渲染 -->
<JeepayTable
ref="infoTable"
:init-data="false"
:req-table-data-func="reqTableDataFunc"
:table-columns="tableColumns"
:search-data="vdata.searchData"
row-key="applyId"
>
<!-- 表格顶部按钮行插槽 -->
<template #topBtnSlot>
<a-button v-if="$access('ENT_MCH_APPLYMENT_ADD')" type="primary" @click="getVisibleAdd()"><plus-outlined />发起进件</a-button>
<!-- <a-button v-if="$access('ENT_MCH_APPLYMENT_ADD')" type="primary" @click="addFunc('')"><plus-outlined />发起进件</a-button>-->
</template>
<template #headerCell="{ column }">
<template v-if="column.key === 'verify'">
<Tooltip color="#3b4146">
<template #title>
<div style="color: #ffffff">提示内容</div>
</template>
<div style="cursor: pointer; width: 100%">
认证状态
<a-tooltip
title="支付宝微信必须认证后才可以交易,如遇列表认证状态未自动更新,可以点击认证状态展开详情页更新。"
><question-circle-outlined style="padding-left: 10px"
/></a-tooltip>
</div>
</Tooltip>
</template>
<template v-if="column.key === 'settlementType'">
<Tooltip color="#3b4146">
<template #title>
<div style="color: #ffffff">提示内容</div>
</template>
<div style="cursor: pointer; width: 100%">
结算类型
<a-tooltip>
<template #title>
<p>T1工作日次日到账交易资金于周末和法定节假日次日的6点-12点自动结算至银行卡</p>
<p>D1自然日次日到账全年365天不分节假日的交易资金于自然日的次日6点-12点自动结算至银行卡</p>
<p>D0实时到账全年365天24小时不分节假日的交易资金笔笔实时秒到至银行卡更放心更快捷</p>
<p>定时结算在一天24个整点时辰根据入账需求选择结算范围可任意指定一个或多个结算时间点</p>
</template>
<question-circle-outlined style="padding-left: 10px"/>
</a-tooltip>
</div>
</Tooltip>
</template>
</template>
<!-- body 渲染插槽 -->
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'contactName'">
{{ record.contactName }} {{ record.contactPhone }}
</template>
<template v-if="column.key === 'notes'" >
<span @click="remarkFunc(record)" class="two-lines" >
<b>{{ record.remark ?? "--" }}</b>
<EditOutlined @click="remarkFunc(record)" />
</span>
</template>
<template v-if="column.key === 'mchServiceName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>服务商名称{{record.mchServiceName}}</span><br>
<span>服务商号{{record.agentNo}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.mchServiceName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'mchUserName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>用户名称{{record.mchUserName}}</span><br>
<span>用户手机号{{record.mchUserPhone}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.mchUserName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'mchNo'">
<a-tooltip overlayClassName="tooltip-box-name">
<template #title>
<span>用户名称{{record.mchName}}</span><br>
<span>用户号{{record.mchNo}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.mchName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'mchShortName'">
<a-tooltip overlayClassName="tooltip-box-name">
<template #title>
<span>商户全称{{record.mchFullName}}</span><br>
<span>商户简称{{record.mchShortName}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.mchShortName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'settlementType'">
<!-- <a @click="getChangeConfigRef(record)"><b>{{ record.settlementType }}</b>-->
{{ record.settlementType }}
<!-- <EditOutlined @click="getChangeConfigRef(record)" /></a>-->
</template>
<template v-if="column.key == 'range'">
<a-tag v-if="record.range == 0" color="orange">线下场景</a-tag>
<a-tag v-if="record.range == 1" color="green">线上场景</a-tag>
</template>
<template v-if="column.key === 'autoConfigMchAppId'">
<a-tooltip overlayClassName="tooltip-box-name">
<template #title>
<span>应用名称{{record.appName}}</span><br>
<span>应用ID{{record.autoConfigMchAppId}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.appName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'isvNo'">
<a-tooltip overlayClassName="tooltip-box-name">
<template #title>
<span>渠道名称{{record.isvName}}</span><br>
<span>渠道号{{record.isvNo}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.isvName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'applyId'">
<a @click="detailFunc(record.applyId)" ><b>{{ record.applyId }}</b></a>
</template>
<template v-if="column.key === 'merchantType'">
{{
record.merchantType === 1
? "小微"
: record.merchantType === 2
? "个体"
: record.merchantType === 3
? "企业"
: "其他组织"
}}
</template>
<template v-if="column.key === 'state'">
<a-badge v-if="record.state == 0" status="default" text="草稿" />
<a-badge
v-if="record.state == 1"
status="warning"
text="等待审核结果"
/>
<a-badge
v-if="record.state == 2"
status="success"
text="进件成功"
/>
<a-badge
v-if="record.state == 3"
status="error"
text="驳回待修改"
/>
<a-badge
v-if="record.state == 4"
status="processing"
text="待验证"
/>
<a-badge
v-if="record.state == 5"
status="processing"
text="待签约"
/>
<a-badge
v-if="record.state == 7"
status="processing"
text="等待预审"
/>
<a-badge
v-if="record.state == 8"
status="processing"
text="预审拒绝"
/>
<a-badge
v-if="record.state == 20"
status="processing"
text="已被风控"
/>
<a-badge
v-if="record.state == 21"
status="processing"
text="已冻结"
/>
<a-badge
v-if="record.state == 12"
status="warning"
text="待通道复审"
/>
<a-tooltip v-if="record.ifCode == 'kqpay' && record.state == 12" title="快钱通道营业执照商户支持先开通后审核复审期间的交易会在人工审批完成后结算复审时间一般为1个工作日。">
<info-circle-outlined style="font-size: 16px; padding-left: 5px;color: #FF0000"/>
</a-tooltip>
<reload-outlined @click="queryChannelState(record.applyId, record.state)" style="color: #1890ff; margin-left: 5px" v-if="(record.state == 12) && $access('ENT_MCH_APPLYMENT_GET_INFO') " />
</template>
<template v-if="column.key === 'storeNumber'">
<a href="javascript:void(0)" @click="getStore(record.applyId)">{{ record.storeNumber ? record.storeNumber : 0 }}</a>
</template>
<template v-if="column.key === 'verify'">
<div @click="oauthFunc(record.applyId)" v-if="record.ifCode != 'zftpay'">
<p class="verify_box">
微信
<a-badge
v-if="record.wxAuthenticationState == 1"
status="success"
text="已认证"
/>
<a-badge
v-if="record.wxAuthenticationState == 0"
status="default"
text="未认证"
/>
<a-badge
v-if="record.wxAuthenticationState == -1"
status="default"
text="未知"
/>
</p>
<p class="verify_box">
支付宝
<a-badge
v-if="record.zfbAuthenticationState == 1"
status="success"
text="已认证"
/>
<a-badge
v-if="record.zfbAuthenticationState == 0"
status="default"
text="未认证"
/>
<a-badge
v-if="record.zfbAuthenticationState == -1"
status="default"
text="未知"
/>
</p>
</div>
<div v-if="record.ifCode == 'zftpay'">--</div>
</template>
<template v-if="column.key === 'applyPageType'">
<span v-if="record.applyPageType == 'PLATFORM_WEB'">运营平台系统</span>
<span v-else-if="record.applyPageType == 'AGENT_WEB'">服务商系统</span>
<span v-else-if="record.applyPageType == 'AGENT_APP'">{{ $SYS_NAME_MAP.AGENT_APP }}APP</span>
<span v-else-if="record.applyPageType == 'AGENT_LITE'">{{ $SYS_NAME_MAP.AGENT_APP }}小程序</span>
<span v-else-if="record.applyPageType == 'MCH_WEB'">商户系统</span>
<span v-else-if="record.applyPageType == 'MCH_APP'">{{ $SYS_NAME_MAP.MCH_APP }}APP</span>
<span v-else-if="record.applyPageType == 'MCH_LITE'">{{ $SYS_NAME_MAP.MCH_APP }}小程序</span>
<span v-else>{{ record.applyPageType || '' }}</span>
</template>
<template v-if="column.key === 'operation'">
<!-- 以下为 运营 服务商通用按钮 -->
<JeepayTableColumns>
<a-button type="link" @click="getStore(record.applyId)" >门店管理</a-button>
<a-button type="link" @click="deviceFunc(record.applyId)">设备管理</a-button>
<a-button v-if="$access('ENT_MCH_APPLYMENT_VIEW')" type="link" @click="getChangeConfigRef(record)" >变更修改</a-button>
<a-button v-if="$access('ENT_MCH_APPLYMENT_VIEW') && record.ifCode != 'zftpay'" type="link" @click="oauthFunc(record.applyId)" >实名认证</a-button>
<a-button v-if="$access('ENT_MCH_APPLYMENT_VIEW')" type="link" @click="oauthDeployFunc(record.applyId)" >参数配置</a-button >
<a-button v-if="$access('ENT_MCH_APPLYMENT_VIEW')" type="link" @click="getOperator()">添加管理员</a-button>
<a-button v-if="$access('ENT_MCH_APPLYMENT_VIEW')" type="link" @click="editOrViewFunc(record.applyId, true)">详情</a-button>
<!-- <a-button v-if="$access('ENT_MCH_APPLYMENT_VIEW')" type="link" @click="detailFunc(record.applyId)">详细数据</a-button>-->
<!-- <a-button v-if="$access('ENT_MCH_APPLYMENT_EDIT') && record.state == 0" type="link" @click="editOrViewFunc(record.applyId, false)">继续填写</a-button>-->
<!-- <a-button v-if="$access('ENT_MCH_APPLYMENT_EDIT') && record.state == 3" type="link" @click="editOrViewFunc(record.applyId, false)">修改</a-button>-->
<!-- <a-button v-if="$access('ENT_MCH_APPLYMENT_ADD')" type="link" @click="toCopySelectIfCodePage(record.applyId, record.mchNo)">复用信息</a-button>-->
<!-- 审核中 待验证 签约完成 -->
<!-- <a-button-->
<!-- v-if="$access('ENT_MCH_APPLYMENT_GET_INFO') && (record.state == 1 || record.state == 4 || record.state == 5)"-->
<!-- type="link"-->
<!-- @click="queryChannelState(record.applyId, record.state)"-->
<!-- >-->
<!-- 获取最新结果-->
<!-- </a-button>-->
<!-- <a-button v-if="$access('ENT_MCH_APPLYMENT_SIGN')" type="link" @click="oauthFunc(record.applyId)" >实名认证</a-button>-->
<!-- <a-button v-if="$access('ENT_MCH_APPLYMENT_PAY_CONFIG')" type="link" @click="oauthDeployFunc(record.applyId)" >参数配置</a-button >-->
<!-- 进件成功后可发起 参数配置 -->
<!-- <a-button v-if="$access('ENT_MCH_APPLYMENT_PAY_CONFIG') && record.state == 2" type="link" @click="appConfigDrawerRef.show(record.applyId)">参数配置</a-button>-->
<!-- 1审核中 2进件成功 4待验证 5待签约 6签约完成/审核成功 -->
<!-- <a-button-->
<!-- v-if="$access('ENT_MCH_APPLYMENT_SIGN') && (record.state == 1 || record.state == 2 || record.state == 4 || record.state == 5 || record.state == 6)"-->
<!-- type="link"-->
<!-- @click="nextBizsDrawerRef.show(record.applyId)"-->
<!-- >-->
<!-- 签约开通-->
<!-- </a-button>-->
</JeepayTableColumns>
</template>
</template>
</JeepayTable>
<a-modal v-model:visible="vdata.qrCodeVisible" title="商户签约二维码" :footer="null" :width="250" @ok="() => vdata.qrCodeVisible = false">
<QrcodeVue :value="vdata.confirmUrl" :size="200" />
</a-modal>
<!-- 微信签约弹窗 -->
<a-modal v-model:visible="vdata.wxQrCodeVisible" title="二维码" :footer="null" :width="250" @ok="() => vdata.wxQrCodeVisible = false">
<span>请超级管理员使用微信扫描二维码根据页面指引完成签约</span>
<img :src="vdata.confirmUrl" style="width: 200px;height: 200px;">
</a-modal>
<!-- 收付通签约弹窗 -->
<a-modal v-model:visible="vdata.sftQrCodeVisible" title="二维码" :footer="null" :width="250" @ok="() => vdata.sftQrCodeVisible = false">
<span>请超级管理员使用微信扫描二维码根据页面指引完成签约</span>
<QrcodeVue :value="vdata.confirmUrl" style="padding-top: 10px;" :size="200" />
</a-modal>
</a-card>
<!-- 定义组件分为 第一步和第二步 -->
<a-card>
<component :is="vdata.currentApplyPage.component" ref="applyPageComponentRef" :listPageSearchMchNo="vdata.searchData.mchNo" @switchApplyPage="switchApplyPage" />
</a-card>
<JeepayModelMchList ref="jeepayModelMchList" showType="MCH" @selectFinishFunc="searchMchFinishFunc" />
<!-- 参数配置 弹层 -->
<AppConfigDrawer ref="appConfigDrawerRef" />
<!-- 后续流程 弹层 -->
<NextBizsDrawer ref="nextBizsDrawerRef" />
<ChangeConfig ref="changeConfigRef" v-show="vdata.visibleChangeConfig" />
<!-- 详情页面组件 -->
<InfoDetail ref="infoDetail" :callback-func="searchFunc" />
<a-modal v-model:visible="vdata.visibleAdd" title="" :footer="null" width="50%" @cancel="handleCancel" class="set-img-box" >
<div class="state-box-div" >
<div>
<img :src="vdata.banerapps" alt="" >
<div class="left-bottom">
<a-button type="primary" @click="addFunc('',1)">发起进件 </a-button>
</div>
</div>
</div>
<div class="state-box-div">
<div class="state-box">
<img :src="vdata.xianxiaImg" alt="" >
</div>
<div class="state-box">
<img :src="vdata.xianshangImg" alt="" >
<div class="right-bottom">
<a-button type="primary" @click="addFunc('',2)">发起进件</a-button>
</div>
</div>
</div>
</a-modal>
<a-modal
v-model:visible="vdata.visibleNotes"
title="编辑备注"
:footer="null"
width="50%"
@cancel="handleCancel"
>
<div style="height: 20vh; overflow: auto">
<div
class="code code-layout-item"
style="display: flex; justify-content: center"
>
<a-form-item label="备注" required style="width: 80%">
<a-textarea
v-model:value="vdata.notes.value"
placeholder="请输入备注"
:auto-size="{ minRows: 3, maxRows: 5 }"
/>
</a-form-item>
</div>
<div style="text-align: center; margin-top: 34px">
<a-button
type="primary"
:disabled="vdata.isDisabled"
@click="sendNotes()"
>
确定
</a-button>
</div>
</div>
</a-modal>
<JeepayOauth2DeployConfig v-show="vdata.visibleOauth2Deploy" configMode="mchApplyment" ref="jeepayOauth2DeployConfigRef"/>
<JeepayMchOauth2 v-show="vdata.visibleOauth2" ref="jeepayMchOauth2Ref" />
</page-header-wrapper>
</template>
<script setup lang="ts">
import { API_URL_MCH_APPLYMENT_LIST,API_MCH_APPLYMENT_REMARK, req, $getMchApplymentChannelState, $getPayConfigIfcodes } from '@/api/manage'
import {ref, reactive, getCurrentInstance, nextTick, provide, computed, onMounted } from 'vue'
import QrcodeVue from 'qrcode.vue'
import ApplySelectMchAndIfcode from '@/components/link/JeepayUIComponents/JeepayMchApplyment/JeepayMchApplymentSelectMchAndIfcode.vue'
import ApplyMchDetailInfoEntry from '@/components/link/JeepayUIComponents/JeepayMchApplyment/JeepayMchApplymentDetailInfoEntry.vue'
import AppConfigDrawer from './AppConfigDrawer.vue'
import NextBizsDrawer from './NextBizsDrawer.vue'
import ChangeConfig from "./ChangeConfig.vue";
import InfoDetail from './NewDetail.vue'
import { useRouter, useRoute } from "vue-router";
const { $infoBox, $access, $SYS_NAME_MAP } = getCurrentInstance()!.appContext.config.globalProperties
const router = useRouter(); //这是全部路由
/** 定义全部组件 **/
const allApplyPage = {
'list': { name: '列表', component: null },
'applySelectMchAndIfcode': { name: '选择商户及进件渠道', component: ApplySelectMchAndIfcode },
'applyMchDetailInfoEntry': { name: '填写进件资料', component: ApplyMchDetailInfoEntry }
}
// 动态组件
const applyPageComponentRef = ref()
const jeepayModelMchList = ref()
const appConfigDrawerRef = ref()
const nextBizsDrawerRef = ref()
const infoDetail = ref()
const infoTable = ref()
const dateRangePicker = ref()
const changeConfigRef = ref()
const jeepayMchOauth2Ref = ref();
const jeepayOauth2DeployConfigRef = ref();
let tableColumns = reactive([
// { key: 'applyId', title: '申请单号', dataIndex: 'applyId', },
// { key: 'ifName', title: '渠道', dataIndex: 'ifName' , },
// { key: 'merchantType', title: '商户类型', dataIndex: 'merchantType' , },
// { key: 'mchNo', title: '用户号', dataIndex: 'mchNo' , },
// { key: 'mchFullName', title: '进件商户名', dataIndex: 'mchFullName', },
// // { key: 'isvNo', title: '服务商号', dataIndex: 'isvNo', width: 140, minWidth: 140, maxWidth: 170},
// { key: 'state', title: '进件状态', },
// // { key: 'applyPageType', title: '来源', width: 150, minWidth: 150, maxWidth: 150},
// { key: 'createdAt', dataIndex: 'createdAt', title: '创建日期', },
// { key: 'lastApplyAt', dataIndex: 'lastApplyAt', title: '最后提交', },
// { key: 'operation', title: '操作', fixed: 'right', align: 'center'}
{ key: "applyId", dataIndex: "applyId", title: "商户号", width: 100 },
{ key: "mchShortName", title: "商户简称", dataIndex: "mchShortName" },
{ key: "ifName", title: "支付通道", dataIndex: "ifName" },
{ key: "isvNo", title: "所属渠道", dataIndex: "isvNo" },
{ key: "merchantType", title: "商户类型", dataIndex: "merchantType",align: "center" },
{ key: "contactName", title: "商户联系人", dataIndex: "contactName" },
{ key: "verify",title: "认证状态",dataIndex: "verify",align: "center"},
{ key: 'mchUserName', title: '用户名称', dataIndex: 'mchUserName'},
{ key: 'mchServiceName', title: '服务商名称', dataIndex: 'mchServiceName' },
{ key: "autoConfigMchAppId",title: "所属应用", dataIndex: "autoConfigMchAppId",},
{ key: "range", title: "应用类型" },
{ key: "settlementType", title: "结算类型", dataIndex: "settlementType",align: "center" },
{ key: "state", title: "商户状态", dataIndex: "state" },
{ key: "storeNumber", title: "门店总数", dataIndex: "storeNumber",align: "center" },
{ key: "notes", title: "备注", dataIndex: "notes" ,align: "center" },
{ key: "createdAt", dataIndex: "createdAt", title: "创建日期" },
{ key: "operation", title: "操作", fixed: "right", dataIndex: "operation", align: "center", width: 200,},
])
let vdata : any = reactive({
banerapps:"https://syb-manage.oss-cn-hangzhou.aliyuncs.com/shouyinbei/banerapps.png",
xianxiaImg:"https://syb-manage.oss-cn-hangzhou.aliyuncs.com/shouyinbei/xianxia.png",
xianshangImg:"https://syb-manage.oss-cn-hangzhou.aliyuncs.com/shouyinbei/xianshang.png",
visibleAdd:false,
searchData: { queryDateRange: '' }, // 搜索条件
notes: {
appid: "",
value: "",
},
currentApplyPage: allApplyPage.list, // 当前的步骤组件 null表示列表页
backupApplyPage: null, // 备份: 用作快速恢复
mchApplymentData: { }, //商户进件资料
isView: false, // 是否预览模式
ifCodeList: [],
copyInfoSourceApplyId: '', // 副本资料
})
onMounted(() => {
vdata.searchData['mchNo'] = useRoute().query.mchNo
if($access('ENT_MCH_APPLYMENT')){
$getPayConfigIfcodes('CURRENT', 'agentApplyment', '').then((res) => {
vdata.ifCodeList = res
})
}
searchFunc()
})
// 向所有子组件注入参数 (子组件可直接更改此值)
provide('mchApplymentData', computed( ()=> vdata.mchApplymentData) )
provide('isView', computed( ()=> vdata.isView) )
// 向所有子组件注入参数: 配置模式: mgrApplyment / agentApplyment / mchApplyment
provide('configMode', 'agentApplyment')
// 向所有子组件注入参数 副本资料来源ID
provide('copyInfoSourceApplyId', computed( ()=> vdata.copyInfoSourceApplyId) )
// 请求table接口数据
async function reqTableDataFunc(params: any) {
if (!vdata.searchData.state) {
params.state = 99;
}
let res = await req.list(API_URL_MCH_APPLYMENT_LIST, params);
var records = res.records;
for (let i = 0; i < res.records.length; i++) {
res.records[i]["contactName"] = maskedName(res.records[i]["contactName"]);
res.records[i]["contactPhone"] = maskedMobile(
res.records[i]["contactPhone"]
);
}
return res;
}
function maskedMobile(phoneNumber) {
if (phoneNumber.length === 11) {
const prefix = phoneNumber.slice(0, 3);
const suffix = phoneNumber.slice(-4);
const maskedPart = "*".repeat(4);
return prefix + maskedPart + suffix;
}
return phoneNumber;
}
function maskedName(fullName) {
if (fullName.length >= 2) {
const firstName = fullName.charAt(0);
const maskedPart = "*".repeat(fullName.length - 1);
return firstName + maskedPart;
}
return fullName;
}
function getStore(applyId) {
router.push({
path: "/store",
query: { applyId: applyId },
});
}
function deviceFunc(mchApplyId){
router.push({
path: "/qrc",
query:{mchApplyId:mchApplyId}
});
}
function getOperator() {
router.push({
path: "/users",
});
}
// 点击【查询】按钮点击事件
function searchFunc () {
infoTable.value.refTable(true)
}
// 切换步骤: 不清空数据
function switchApplyPage(page){
if (page == "applyAddApps") {
router.push({
path: "/apps",
});
return false;
}
if (page == "applyMch") {
router.push({
path: "/mch",
});
return false;
}
vdata.backupApplyPage = vdata.currentApplyPage
vdata.currentApplyPage = allApplyPage[page]
if(vdata.currentApplyPage && vdata.currentApplyPage.component){
nextTick(() => { applyPageComponentRef.value.pageRender() })
}else{
//列表页
searchFunc() // 刷新
}
}
function detailFunc (recordId: any) { // 进件详情页
vdata.copyInfoSourceApplyId = '' // 副本清空。
infoDetail.value.show(recordId)
}
// 点击【发起进件】的操作
function addFunc(copyInfoSourceApplyId = '',sceneType=0){
vdata.visibleAdd = false
vdata.sceneType = sceneType
vdata.copyInfoSourceApplyId = copyInfoSourceApplyId
vdata.backupApplyPage = null // 再次点击进件将无法再次恢复
//进件数据重置
vdata.mchApplymentData = {}
req.list(API_URL_MCH_APPLYMENT_LIST, {pageNumber:"1",pageSize:1}).then(res => {
if(res.records.length > 0){
vdata.mchApplymentData = {mchNo:res.records[0].mchNo,sceneType:sceneType}
}
//进件数据重置
vdata.isView = false
switchApplyPage('applySelectMchAndIfcode')
})
// vdata.isView = false
// switchApplyPage('applySelectMchAndIfcode')
}
// 修改资料 然后重新发起进件操作
function editOrViewFunc(applyId, isView){
console.log(applyId, isView);
vdata.copyInfoSourceApplyId = '' // 副本清空。
vdata.backupApplyPage = null // 再次点击进件将无法再次恢复
vdata.isView = isView
req.getById(API_URL_MCH_APPLYMENT_LIST, applyId, { originData: vdata.isView?'0':'1' } ).then(res => {
let { applyId, channelApplyNo, mchNo, ifCode, applyDetailInfo, applyErrorInfo, isvNo, state ,range} = res
vdata.mchApplymentData = {applyId, channelApplyNo, mchNo, ifCode, applyDetailInfo, applyErrorInfo,isvNo, state,range}
switchApplyPage('applyMchDetailInfoEntry')
})
}
// 查询最新状态
function queryChannelState(applyId, currState){
$getMchApplymentChannelState(applyId).then(res => {
if(currState != res.state){
$infoBox.message.success('状态已更新')
searchFunc() // 更新列表
}else{
$infoBox.message.info('状态无变化')
}
})
}
function sendNotes() {
req
.add(API_MCH_APPLYMENT_REMARK, {
remark: vdata.notes.value,
applyId: vdata.notes.appid,
})
.then((msg) => {
console.log(msg, "resresres");
$infoBox.message.success("编辑成功");
searchFunc(); // 更新列表
vdata.visibleNotes = false;
});
}
// 恢复上一次步骤
function recoveryApplyPage(){
vdata.currentApplyPage = vdata.backupApplyPage
}
// 获取下拉框组件
const ifCodeRef = ref()
const stateRef = ref()
let isReset = ref(0) // 下拉搜索框是否重置
// 清空搜索项
const resetFunc = () => {
isReset.value++ // 下拉搜索框重置
dateRangePicker.value.returnSelectModel()
vdata.searchData= {}
}
// 选择商户 / 应用 完成后的操作
function searchMchFinishFunc(selectVal){
if(selectVal[0]){
vdata.searchData.mchNo = selectVal[0]
}
jeepayModelMchList.value.close()
}
// 显示二维码图片
function showQrImgFunc(record){
if (record.ifCode == 'wxpay') {
vdata.wxQrCodeVisible = true
}else if (record.ifCode == 'sftpay') {
vdata.sftQrCodeVisible = true
}else if (record.ifCode == 'fuioupay') {
vdata.qrCodeVisible = true
vdata.confirmUrl = JSON.parse(record.applyErrorInfo).signUrl
return
}else {
vdata.qrCodeVisible = true
}
vdata.confirmUrl = record.applyErrorInfo
}
// 副本 copySourceApplyId : 需要copy的资料来源。
function toCopySelectIfCodePage(copySourceApplyId, mchNo){
vdata.searchData.mchNo = mchNo
addFunc(copySourceApplyId)
}
function getVisibleAdd(){
vdata.visibleAdd = true
}
function handleCancel (e) {
vdata.visibleAdd = false
}
function oauthDeployFunc(applyId) {
vdata.visibleOauth2Deploy = true;
jeepayOauth2DeployConfigRef.value.show(applyId);
}
function oauthFunc(applyId) {
vdata.visibleOauth2 = true;
jeepayMchOauth2Ref.value.show(applyId);
}
function getChangeConfigRef(data) {
vdata.visibleChangeConfig = true;
changeConfigRef.value.show(data);
}
function remarkFunc(data) {
vdata.notes.appid = data.applyId;
vdata.notes.value = data.remark ?? "";
vdata.visibleNotes = true;
}
</script>
<style>
.state-box-div{
width: 100%;
padding: unset !important;
}
.state-box-div img{
width: 100%;
height: 100%;
}
.set-img-box .ant-modal-body{
padding: unset !important;
}
.state-box{
padding: unset !important;
}
.state-box{
width: 50%;
}
.state-box img{
width: 100%;
height: 100%;
}
.visibleAdd-img{
//display: flex;
}
.ant-modal-content svg{
color: #ffffff;
font-weight: bold;
font-size: 25px;
}
.left-bottom{
border-radius: 10px;
position: absolute;
left: 5%;
bottom: 5%;
}
.left-bottom button{
border-radius: 10px;
}
.right-bottom{
position: absolute;
right: 35%;
bottom: 5%;
}
.right-bottom button{
border-radius: 10px;
}
</style>
<style lang="less">
.verify_box {
text-align: center;
}
.two-lines {
color: #1965ff;
max-width: 150px;
display: block;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
</style>

View File

@@ -0,0 +1,235 @@
<template>
<a-drawer
v-model:visible="vdata.visible"
title="自主签约"
width="50%"
@close="onClose"
>
<a-form>
<template
v-if="
vdata.bizSignInfo &&
(vdata.bizSignInfo.state || vdata.bizSignInfo.signUrl)
"
>
<a-divider orientation="left" style="color: #1a66ff"
>电子合同签约</a-divider
>
<a-form-item label="签约状态">
{{
vdata.bizSignInfo.state || "异常[" + vdata.bizSignInfo.errInfo + "]"
}}
<a-button
:loading="vdata.bizSignInfoBtnLoading"
size="small"
style="margin-left: 20px"
type="primary"
@click="getMchApplymentChannelSignInfoFunc"
><reload-outlined />刷新</a-button
>
</a-form-item>
<a-form-item v-if="vdata.bizSignInfo.signUrl" label="合同地址">
<p style="margin-top: 5px">
<a :href="vdata.bizSignInfo.signUrl" target="_blank">{{
vdata.bizSignInfo.signUrl
}}</a>
</p>
<p style="margin-top: 20px">合同地址快捷访问二维码:</p>
<QrcodeVue
:value="vdata.bizSignInfo.signUrl"
:size="150"
class="qrcode"
/>
</a-form-item>
</template>
<template
v-if="
vdata.wxOpenInfo &&
(vdata.wxOpenInfo.state || vdata.wxOpenInfo.signUrl)
"
>
<a-divider orientation="left" style="color: #1a66ff"
>微信开户意愿确认</a-divider
>
<a-form-item label="商户确认状态">
{{
vdata.wxOpenInfo.state || "异常[" + vdata.wxOpenInfo.errInfo + "]"
}}
<a-button
:loading="vdata.wxOpenInfoBtnLoading"
size="small"
style="margin-left: 20px"
type="primary"
@click="getMchApplymentWxOpenInfoFunc"
><reload-outlined />刷新</a-button
>
</a-form-item>
<a-form-item v-if="vdata.wxOpenInfo.signUrl" label="">
<p>商户联系人使用已绑定银行卡的微信扫下面的二维码:</p>
<QrcodeVue
v-if="vdata.wxOpenInfo.imgType == 'qrContent'"
:value="vdata.wxOpenInfo.signUrl"
:size="150"
class="qrcode"
/>
<img
v-else
:src="'data:image/jpg;base64,' + vdata.wxOpenInfo.signUrl"
class="qrcode"
style="width: 200px"
/>
<br />
<p class="jeepay-tip-text">
(温馨提示:自助认证不限制谁来操作认证,但建议是商户联系人进行认证,以免后期需要扫码找不到微信认证管理员)
</p>
</a-form-item>
</template>
<template
v-if="vdata.alipayOpenInfo.state || vdata.alipayOpenInfo.signUrl"
>
<a-divider orientation="left" style="color: #1a66ff"
>支付宝实名审核</a-divider
>
<a-form-item label="商户确认状态">
{{ vdata.alipayOpenInfo.state }}
<a-button
:loading="vdata.alipayOpenInfoBtnLoading"
size="small"
style="margin-left: 20px"
type="primary"
@click="getMchApplymentAlipayOpenInfoFunc"
><reload-outlined />刷新</a-button
>
</a-form-item>
<br />
<span v-if="vdata.alipayOpenInfo.errInfo">{{
"异常[" + vdata.alipayOpenInfo.errInfo + "]"
}}</span>
<a-form-item v-if="vdata.alipayOpenInfo.signUrl" label="">
<p>商户联系人使用已绑定银行卡的支付宝扫下面的二维码:</p>
<QrcodeVue
v-if="vdata.alipayOpenInfo.imgType == 'qrContent'"
:value="vdata.alipayOpenInfo.signUrl"
:size="150"
class="qrcode"
/>
<template v-else>
<img
v-if="
vdata.alipayOpenInfo.signUrl &&
vdata.alipayOpenInfo.signUrl.indexOf('http') == 0
"
:src="vdata.alipayOpenInfo.signUrl"
class="qrcode"
style="width: 200px"
/>
<img
v-else
:src="'data:image/jpg;base64,' + vdata.alipayOpenInfo.signUrl"
class="qrcode"
style="width: 200px"
/>
</template>
<br />
<!-- <p class="jeepay-tip-text">(温馨提示:自助认证不限制谁来操作认证,但建议是商户联系人进行认证,以免后期需要扫码找不到微信认证管理员)</p> -->
</a-form-item>
</template>
</a-form>
</a-drawer>
</template>
<script lang="ts" setup>
import {
$getMchApplymentChannelSignInfo,
$getMchApplymentWxOpenInfo,
$getMchApplymentAlipayOpenInfo,
} from "@/api/manage";
import { reactive, getCurrentInstance } from "vue";
import QrcodeVue from "qrcode.vue";
const { $infoBox } = getCurrentInstance()!.appContext.config.globalProperties;
const vdata: any = reactive({
recordId: null, // 更新对象ID
visible: false, // 是否显示弹层/抽屉
bizSignInfo: {}, // 电子合同信息
wxOpenInfo: {}, // 微信开户意愿信息
alipayOpenInfo: {}, // 支付宝开户意愿信息
bizSignInfoBtnLoading: false,
wxOpenInfoBtnLoading: false,
alipayOpenInfoBtnLoading: false,
});
function show(recordId) {
// 弹层打开事件
// 数据的清空
vdata.bizSignInfo = {};
vdata.wxOpenInfo = {};
vdata.alipayOpenInfo = {};
vdata.recordId = recordId;
getMchApplymentChannelSignInfoFunc();
getMchApplymentWxOpenInfoFunc();
getMchApplymentAlipayOpenInfoFunc();
vdata.visible = true;
}
// 电子合同状态查询
function getMchApplymentChannelSignInfoFunc() {
vdata.bizSignInfoBtnLoading = true;
$getMchApplymentChannelSignInfo(vdata.recordId)
.then((res) => {
vdata.bizSignInfo = res;
})
.finally(() => {
vdata.bizSignInfoBtnLoading = false;
});
}
// 电子合同状态查询
function getMchApplymentWxOpenInfoFunc() {
vdata.wxOpenInfoBtnLoading = true;
$getMchApplymentWxOpenInfo(vdata.recordId)
.then((res) => {
vdata.wxOpenInfo = res;
})
.finally(() => {
vdata.wxOpenInfoBtnLoading = false;
});
}
// 电子合同状态查询
function getMchApplymentAlipayOpenInfoFunc() {
vdata.alipayOpenInfoBtnLoading = true;
$getMchApplymentAlipayOpenInfo(vdata.recordId)
.then((res) => {
vdata.alipayOpenInfo = res || {};
})
.finally(() => {
vdata.alipayOpenInfoBtnLoading = false;
});
}
function onClose() {
vdata.visible = false;
}
defineExpose({ show });
</script>
<style scoped>
.form-item-content {
width: 70%;
}
</style>

View File

@@ -0,0 +1,164 @@
<template>
<a-drawer
v-model:visible="vdata.visible"
title="进件信息"
:body-style="{ paddingBottom: '80px' }"
width="50%"
@close="onClose"
>
<a-divider class="jeepay-m-divider" orientation="left" style="color: #1A66FF;">基本信息</a-divider>
<a-row justify="space-between" type="flex">
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商户号">
{{ vdata.detailData.applyId }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="收单机构编号">
{{ vdata.detailData.channelMchNo??"暂未进件"}}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="24" v-if="vdata.detailData.ifCode == 'lklspay'">
<a-descriptions>
<a-descriptions-item label="终端号">
{{ vdata.termNo ??"暂未进件"}}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col v-if="$hasAgentEnt()" :sm="24">
<a-descriptions>
<a-descriptions-item label="拓展服务商">
{{ vdata.detailData.agentNoName }} {{ vdata.detailData.agentPhone }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="进件来源">
<span v-if="vdata.detailData.applyPageType == 'PLATFORM_WEB'">运营平台系统</span>
<span v-else-if="vdata.detailData.applyPageType == 'AGENT_WEB'">代理商系统</span>
<span v-else-if="vdata.detailData.applyPageType == 'AGENT_APP'">{{ $SYS_NAME_MAP.AGENT_APP }}APP</span>
<span v-else-if="vdata.detailData.applyPageType == 'AGENT_LITE'">{{ $SYS_NAME_MAP.AGENT_APP }}小程序</span>
<span v-else-if="vdata.detailData.applyPageType == 'MCH_WEB'">商户系统</span>
<span v-else-if="vdata.detailData.applyPageType == 'MCH_APP'">{{ $SYS_NAME_MAP.MCH_APP }}APP</span>
<span v-else-if="vdata.detailData.applyPageType == 'MCH_LITE'">{{ $SYS_NAME_MAP.MCH_APP }}小程序</span>
<span v-else>{{ vdata.detailData.applyPageType || '' }}</span>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="支付通道">
{{ vdata.detailData.ifName }}
<!-- [ {{ vdata.detailData.ifCode }} ]-->
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="所属渠道">
{{ vdata.detailData.isvName }} [ {{ vdata.detailData.isvNo }} ]
</a-descriptions-item>
</a-descriptions>
</a-col>
<!-- <a-divider />-->
</a-row>
<a-divider class="jeepay-m-divider" orientation="left" style="color: #1A66FF;">商户信息</a-divider>
<a-row justify="space-between" type="flex">
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商户全称">
{{ vdata.detailData.mchFullName }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商户简称">
{{ vdata.detailData.mchShortName }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商户类型">
{{ vdata.detailData.merchantType == 1?'小微':vdata.detailData.merchantType == 2?'个体':vdata.detailData.merchantType == 1?'企业':'其他' }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="结算状态">
{{vdata.detailData.settlementType??"暂未进件"}}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="联系人姓名">
{{ vdata.detailData.contactName }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="联系人手机号">
{{ vdata.detailData.contactPhone }}
</a-descriptions-item>
</a-descriptions>
</a-col>
</a-row>
</a-drawer>
</template>
<script lang="ts" setup>
import { API_URL_MCH_APPLYMENT_LIST, req } from '@/api/manage'
import {defineProps,reactive, getCurrentInstance} from 'vue'
const { $hasAgentEnt, $SYS_NAME_MAP } = getCurrentInstance()!.appContext.config.globalProperties
const props = defineProps({
callbackFunc: { type: Function,default:null }
})
const vdata:any = reactive({
btnLoading: false,
detailData: {}, // 数据对象
recordId: null, // 更新对象ID
visible: false, // 是否显示弹层/抽屉
termNo: false, // 是否显示弹层/抽屉
})
function show (recordId) { // 弹层打开事件
vdata.detailData = { 'state': 1, 'type': 1 } // 数据清空
// if (this.$refs.infoFormModel !== undefined) {
// this.$refs.infoFormModel.resetFields()
// }
vdata.recordId = recordId
req.getById(API_URL_MCH_APPLYMENT_LIST, recordId).then(res => {
if(res.succResParameter){
const succResParameter = JSON.parse(res.succResParameter)
vdata.termNo = succResParameter.termNo
}else{
vdata.termNo = "暂未进件"
}
vdata.detailData = res
})
vdata.visible = true
}
function onClose () {
vdata.visible = false
}
defineExpose({
show //抛出show函数给父组件
})
</script>

View File

@@ -0,0 +1,38 @@
<!-- 签约开通 弹层模式 -->
<template>
<a-drawer
v-model:visible="vdata.visible"
title="签约开通"
width="70%"
@close="vdata.visible = false"
>
<JeepayApplymentNextBizs v-if="vdata.visible" ref="jeepayApplymentNextBizsRef" />
</a-drawer>
</template>
<script lang="ts" setup>
import {ref, reactive, nextTick} from 'vue'
const jeepayApplymentNextBizsRef = ref()
const vdata : any = reactive({
applyId: null, // 更新对象ID
visible: false, // 是否显示弹层/抽屉
})
function show (applyId) { // 弹层打开事件
vdata.applyId = applyId
vdata.visible = true
nextTick(() => {
jeepayApplymentNextBizsRef.value.pageRender(applyId)
})
}
defineExpose({ show })
</script>

View File

@@ -0,0 +1,36 @@
<!-- 商户信息变更 弹层模式 -->
<template>
<a-drawer
v-model:visible="vdata.visible"
title="合同签约"
width="45%"
@close="vdata.visible = false"
>
<JeepayApplymentAppSigningConfig v-if="vdata.visible" ref="jeepayApplymentAppSigningConfigRef" />
</a-drawer>
</template>
<script lang="ts" setup>
import {ref, reactive, nextTick} from 'vue'
const jeepayApplymentAppSigningConfigRef = ref()
const vdata : any = reactive({
applyId: null, // 更新对象ID
visible: false, // 是否显示弹层/抽屉
})
function show (data) { // 弹层打开事件)
vdata.applyId = data.applyId
vdata.visible = true
nextTick(() => {
jeepayApplymentAppSigningConfigRef.value.pageRender(data)
})
}
defineExpose({ show })
</script>

View File

@@ -0,0 +1,114 @@
<template>
<a-drawer
v-model:visible="vdata.visible"
title="参数配置"
width="50%"
@close="onClose"
>
<a-form>
<a-form-item label="用户号:">
<a-tag color="blue">{{ vdata.detailData.mchNo }}</a-tag>
</a-form-item>
<a-form-item label="支付参数:">
<a-textarea
v-model:value="vdata.detailData.succResParameter"
disabled="disabled"
style="height: 100px;color: black"
/>
</a-form-item>
<a-divider orientation="left"><a-tag color="black">应用配置</a-tag></a-divider>
<a-form-item label="参数配置到应用:">
<a-select v-model:value="vdata.configAppId" class="form-item-content" placeholder="选择应用">
<a-select-option key="">请选择商户应用</a-select-option>
<a-select-option v-for="(item) in vdata.mchAppList" :key="item.appId">{{ item.appName }} [{{ item.appId }}]</a-select-option>
</a-select>
<a-button size="small" style="margin-left: 20px;" type="primary" :disabled="!vdata.configAppId" @click="configMchAppIdFunc"><save-outlined />配置到应用</a-button>
</a-form-item>
</a-form>
</a-drawer>
</template>
<script lang="ts" setup>
import { API_URL_MCH_APPLYMENT_LIST, API_URL_MCH_APP, req, $applymentAppConfig, $saveRateConfig } from '@/api/manage'
import {reactive, getCurrentInstance} from 'vue'
const { $infoBox } = getCurrentInstance()!.appContext.config.globalProperties
const vdata : any = reactive({
detailData: {}, // 数据对象
recordId: null, // 更新对象ID
visible: false, // 是否显示弹层/抽屉
mchAppList: [], // 商户app列表
configAppId: ''
})
function show (recordId) { // 弹层打开事件
//重置配置
vdata.configAppId = ''
vdata.mchAppList = []
vdata.recordId = recordId
req.getById(API_URL_MCH_APPLYMENT_LIST, recordId).then( (res) => {
vdata.detailData = res
req.list(API_URL_MCH_APP, { pageSize: -1, mchNo: vdata.detailData.mchNo }).then(res2 => {
vdata.mchAppList = res2.records
if(vdata.mchAppList.length > 0){
vdata.configAppId = vdata.mchAppList[0].appId
}
})
vdata.visible = true
})
}
// 配置本系统的app应用配置项
function configMchAppIdFunc(){
if(!vdata.configAppId){
$infoBox.message.error('请选择商户应用')
return Promise.reject()
}
return $applymentAppConfig(vdata.recordId, vdata.configAppId).then((res) => {
$infoBox.message.success('商户应用配置完成, 如商户支付方式未配置,请进行方式配置。 ')
return Promise.resolve()
})
}
function onClose () {
vdata.visible = false
}
// 配置费率到应用
function configMchRateAppIdFunc(){
if(!vdata.configAppId){
$infoBox.message.error('请选择商户应用')
return Promise.reject()
}
// 请求对象
let configMode = 'agentMch' // 服务商配置商户费率
let reqObject = {infoId: vdata.configAppId, ifCode: vdata.detailData.ifCode, configMode: configMode, MCHRATE: JSON.parse(vdata.detailData.applyDetailInfo).paywayFeeList}
return $saveRateConfig(reqObject).then((res) => {
$infoBox.message.success('费率保存成功')
})
}
defineExpose({ show })
</script>
<style scoped>
.form-item-content{
width: 70%
}
</style>

View File

@@ -0,0 +1,231 @@
<!-- 复制自 运营平台无改动 -->
<template>
<a-drawer
:visible="vdata.visible"
:title=" vdata.isAdd ? '新增应用' : '修改应用'"
width="40%"
:maskClosable="false"
@close="onClose"
>
<a-form ref="infoFormModel" :model="vdata.saveObject" layout="vertical" :rules="vdata.rules">
<a-row :gutter="16">
<a-col v-if="!vdata.isAdd" :span="12">
<a-form-item label="应用 AppId" name="appId">
<a-input v-model:value="vdata.saveObject['appId']" placeholder="请输入" :disabled="!vdata.isAdd" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="用户号" name="mchNo">
<JeepaySearchInfoInput v-model:value="vdata.saveObject['mchNo']" placeholder="请输入" :disabled="!vdata.isAdd" :mchNoAndName="true" :onlyMchName="true" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="状态" name="state">
<a-radio-group v-model:value="vdata.saveObject['state']">
<a-radio :value="1">启用</a-radio>
<a-radio :value="0">停用</a-radio>
</a-radio-group>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="应用名称" name="appName">
<a-input v-model:value="vdata.saveObject['appName']" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="是否设置为默认应用" name="defaultFlag">
<a-radio-group v-model:value="vdata.saveObject['defaultFlag']">
<a-radio :value="1"></a-radio>
<a-radio :value="0"></a-radio>
</a-radio-group>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="备注" name="remark" class="m-b-50 ">
<a-input v-model:value="vdata.saveObject['remark']" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :span="24">
<a-divider orientation="left" style="color: #1A66FF;">签名配置</a-divider>
<a-form-item name="appSignType">
<template #label>
<span>支持的签名方式</span> &nbsp;
<a-popover placement="top">
<template #title><span>签名方式</span></template>
<template #content>
<p>若需要使用系统测试或者{{ $SYS_NAME_MAP.MCH_APP }}APP则必须支持MD5 若仅通过API调用则根据需求进行选择 </p>
</template>
<question-circle-outlined />
</a-popover>
</template>
<a-checkbox-group v-model:value="vdata.saveObject.appSignTypeObject">
<a-checkbox value="MD5">MD5</a-checkbox>
<a-checkbox value="RSA2">RSA2</a-checkbox>
</a-checkbox-group>
</a-form-item>
<a-form-item v-show="vdata.saveObject.appSignTypeObject && vdata.saveObject.appSignTypeObject.indexOf('MD5') >= 0" label="设置MD5秘钥" name="appSecret">
<a-textarea v-model:value="vdata.saveObject['appSecret']" :placeholder="vdata.saveObject['appSecret_ph']" type="textarea" />
<a-button type="primary" style="margin-top: 5px;" ghost @click="randomKey(false, 128, 0)"><file-sync-outlined />随机生成私钥</a-button>
</a-form-item>
<a-form-item v-show="vdata.saveObject.appSignTypeObject && vdata.saveObject.appSignTypeObject.indexOf('RSA2') >= 0" label="设置RSA2应用公钥" name="appRsa2PublicKey">
<a-textarea v-model:value="vdata.saveObject['appRsa2PublicKey']" type="textarea" />
</a-form-item>
<a-form-item v-show="vdata.saveObject.appSignTypeObject && vdata.saveObject.appSignTypeObject.indexOf('RSA2') >= 0" label="支付网关系统公钥(回调验签使用)">
<a-textarea :rows="6" :value="vdata.sysRSA2PublicKey" :disabled="true" type="textarea" />
</a-form-item>
</a-col>
</a-row>
</a-form>
<div class="drawer-btn-center">
<a-button :style="{ marginRight: '8px' }" @click="onClose"><close-outlined />取消</a-button>
<a-button type="primary" :loading="vdata.btnLoading" @click="onSubmit"><check-outlined />保存</a-button>
</div>
</a-drawer>
</template>
<script setup lang="ts">
import { API_URL_MCH_APP, req, $getSysRSA2PublicKey } from '@/api/manage'
import {ref, reactive, getCurrentInstance} from 'vue'
const { $SYS_NAME_MAP } = getCurrentInstance()!.appContext.config.globalProperties
const props = defineProps({
callbackFunc: { type: Function, default: () => () => ({}) }
})
const vdata : any = reactive({
isAdd: true, // 新增 or 修改
visible: false, // 抽屉开关
appId: '', // 应用AppId
saveObject: {}, // 数据对象
btnLoading:false,
sysRSA2PublicKey: '',
rules: {
mchNo: [{ required: true, message: '请输入商户号', trigger: 'blur' }],
appName: [{ required: true, message: '请输入应用名称', trigger: 'blur' }],
appSecret: [{
validator: (rule, value) => {
// 新增 & 选择了MD5 : 必须输入MD5私钥
if(vdata.isAdd && vdata.saveObject.appSignTypeObject.indexOf('MD5') >= 0 && !value){
return Promise.reject('请输入MD5秘钥')
}
return Promise.resolve()
}
}],
appRsa2PublicKey: [{
validator: (rule, value) => {
// 新增 & 选择了MD5 : 必须输入MD5私钥
if(vdata.saveObject.appSignTypeObject.indexOf('RSA2') >= 0 && !value){
return Promise.reject('请输入RSA2应用公钥')
}
return Promise.resolve()
}
}]
}
})
// 表单组件
const infoFormModel = ref()
// 抽屉显示
const show = (mchNo, appId) => {
vdata.isAdd = !appId
// 数据清空
vdata.saveObject = {
'state': 1,
'appSecret': '',
'mchNo': mchNo,
'defaultFlag': 1,
'appSecret_ph': '请输入',
appSignTypeObject: ['MD5']
}
if (infoFormModel.value !== undefined) {
infoFormModel.value.resetFields()
}
if (!vdata.isAdd) { // 修改信息 延迟展示弹层
vdata.appId = appId
// 拉取详情
req.getById(API_URL_MCH_APP, appId).then(res => {
vdata.saveObject = res
vdata.saveObject['appSecret_ph'] = res.appSecret
vdata.saveObject['appSecret'] = ''
vdata.saveObject.appSignTypeObject = []
if(res.appSignType){
vdata.saveObject.appSignTypeObject = JSON.parse(res.appSignType)
}
})
vdata.visible = true
} else {
vdata.visible = true // 展示弹层信息
}
// 查询系统公钥
$getSysRSA2PublicKey().then((res) => {
vdata.sysRSA2PublicKey = res
})
}
// 表单提交
const onSubmit = () => {
// 处理签名方式
vdata.saveObject.appSignType = JSON.stringify(vdata.saveObject.appSignTypeObject)
infoFormModel.value.validate().then(() => {
vdata.btnLoading = true
let reqObject = Object.assign({}, vdata.saveObject) // 请求数据
delete reqObject['appSecret_ph']
if (!vdata.isAdd && reqObject['appSecret'] === '') {
delete reqObject['appSecret']
}
req.addOrUpdate(vdata.isAdd ? null : vdata.appId, API_URL_MCH_APP, reqObject).then(res => {
vdata.visible = false
props.callbackFunc() // 刷新列表
}).finally(() => {
vdata.btnLoading = false
})
})
}
function randomKey(randomFlag, min, max) { // 生成随机128位私钥
let str = ''
let range = min
const arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
// 随机产生
if (randomFlag) {
range = Math.round(Math.random() * (max - min)) + min
}
for (var i = 0; i < range; i++) {
var pos = Math.round(Math.random() * (arr.length - 1))
str += arr[ pos ]
}
vdata.saveObject['appSecret'] = str
}
const onClose = () => {
vdata.visible = false
}
defineExpose({ show })
</script>

View File

@@ -0,0 +1,197 @@
<template>
<page-header-wrapper>
<a-card v-show="vdata.tableType == 'table'">
<JeepaySearchForm :searchFunc="searchFunc" :resetFunc="() => { vdata.searchData= {} }">
<JeepaySearchInfoInput v-model:value="vdata.searchData.mchNo" placeholder="用户号" :textUpStyle="true" :mchNoAndName="true" showType="MCH" />
<jeepay-text-up v-model:value="vdata.searchData.appId" :placeholder="'应用AppId'" />
<jeepay-text-up v-model:value="vdata.searchData.appName" :placeholder="'应用名称'" />
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData.state" placeholder="状态">
<a-select-option value="">全部</a-select-option>
<a-select-option value="0">禁用</a-select-option>
<a-select-option value="1">启用</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData.range" placeholder="应用类型">
<a-select-option value="">全部</a-select-option>
<a-select-option value="0">线下场景</a-select-option>
<a-select-option value="1">线上场景</a-select-option>
</a-select>
</a-form-item>
</JeepaySearchForm>
<!-- 列表渲染 -->
<JeepayTable
ref="infoTable"
:initData="false"
:reqTableDataFunc="reqTableDataFunc"
:tableColumns="tableColumns"
:searchData="vdata.searchData"
rowKey="appId"
@btnLoadClose="vdata.btnLoading=false"
>
<template #topBtnSlot>
<div>
<!-- <a-button v-if="$access('ENT_MCH_APP_ADD')" type="primary" class="mg-b-30" @click="addFunc"><plus-outlined />新建</a-button>-->
<a-button v-if="$access('ENT_MCH_APP_ADD')" type="primary" class="mg-b-30" @click="getAddFunc('appSaveEdit')"><plus-outlined />新建</a-button>
</div>
</template>
<template #bodyCell="{column,record}">
<template v-if="column.key == 'appId'">
<b>{{ record.appId }}</b>
</template> <!-- 自定义插槽 -->
<template v-if="column.key == 'state'">
<a-badge :status="record.state === 0?'error':'processing'" :text="record.state === 0?'禁用':'启用'" />
</template>
<template v-if="column.key === 'defaultFlag'">
<a-badge :status="record.defaultFlag === 0?'error':'processing'" :text="record.defaultFlag === 0?'否':'是'" />
</template>
<template v-if="column.key == 'range'">
<a-tag v-if="record.range == 0" color="orange">线下场景</a-tag>
<a-tag v-if="record.range == 1" color="green">线上场景</a-tag>
</template>
<template v-if="column.key === 'mchServiceName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>服务商名称{{record.mchServiceName}}</span><br>
<span>服务商号{{record.agentNo}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.mchServiceName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'mchUserName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>用户名称{{record.mchUserName}}</span><br>
<span>用户手机号{{record.mchUserPhone}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.mchUserName}}</div>
</a-tooltip>
</template>
<template v-if="column.key == 'op'">
<!-- 操作列插槽 -->
<JeepayTableColumns>
<a-button v-if="$access('ENT_MCH_APP_EDIT')" type="link" @click="editFunc(record.appId)">修改</a-button>
<!-- <a-button v-if="$access('ENT_MCH_APP_PAY_CONFIG')" type="link" @click="showFeeConfigList(record.appId)">支付配置</a-button>-->
<a-button v-if="$access('ENT_MCH_PAY_TEST')" type="link">
<router-link :to="{name:'ENT_MCH_PAY_TEST', params:{appId:record.appId}}">
支付测试
</router-link>
</a-button>
<a-button v-if="$access('ENT_MCH_TRANSFER')" type="link">
<router-link :to="{name:'ENT_MCH_TRANSFER', params:{appId:record.appId}}">
发起转账
</router-link>
</a-button>
<!-- <a-button v-if="$access('ENT_MCH_APP_DEL')" type="link" style="color: red" @click="delFunc(record.appId)">删除</a-button>-->
</JeepayTableColumns>
</template>
</template>
</JeepayTable>
</a-card>
<!-- 新增应用 -->
<MchAppAddOrEdit ref="mchAppAddOrEdit" :callbackFunc="searchFunc" />
<!-- 费率配置页面 -->
<JeepayPayConfigDrawer ref="jeepayPayWayFeeConfigDrawer" configMode="agentMch" />
<a-card v-show="vdata.tableType == 'appSaveEdit'" style="background: #f0f2f5; ">
<JeepayApplicationSave ref="jeepayApplicationSave" :configMode="'agent'" @itemRender="getMchAppSave" style="background: #f0f2f5; "/>
</a-card>
</page-header-wrapper>
</template>
<script lang="ts" setup>
import { API_URL_MCH_APP, req } from '@/api/manage'
import MchAppAddOrEdit from './AddOrEdit.vue'
import {ref, reactive, onMounted, getCurrentInstance } from 'vue'
import { useRoute } from 'vue-router'
const { $infoBox, $access } = getCurrentInstance()!.appContext.config.globalProperties
// eslint-disable-next-line no-unused-vars
const tableColumns = reactive([
{ key: 'appName',fixed: 'left', title: '应用名称', dataIndex: 'appName' },
{ key: 'appId', title: '应用ID', },
{ key: 'range', title: '应用类型',},
{ key: 'mchUserName', title: '用户名称', dataIndex: 'mchUserName'},
{ key: 'mchServiceName', title: '服务商名称', dataIndex: 'mchServiceName' },
{ key: 'state', title: '应用状态',},
// { key: 'defaultFlag', title: '默认',},
{ key: 'createdAt', title: '创建日期', dataIndex: 'createdAt',},
{ key: 'op', title: '操作', fixed: 'right', align: 'center' }
])
onMounted(() => {
vdata.searchData.mchNo = useRoute().query.mchNo
searchFunc()
})
const mchAppAddOrEdit =ref()
const infoTable = ref()
const jeepayApplicationSave = ref()
const jeepayPayWayFeeConfigDrawer = ref()
const vdata = reactive({
tableType:'table',
btnLoading: false,
tableColumns: tableColumns,
searchData: {} as any
})
function getMchAppSave(e){
if(e.type == 1){
vdata.tableType = e.tableType
queryFunc()
}
}
function queryFunc () {
vdata.btnLoading = true
infoTable.value.refTable(true)
}
// 请求table接口数据
function reqTableDataFunc(params){
return req.list(API_URL_MCH_APP, params)
}
function searchFunc () { // 点击【查询】按钮点击事件
infoTable.value.refTable(true)
}
function addFunc() { // 业务通用【新增】 函数
mchAppAddOrEdit.value.show(vdata.searchData['mchNo'])
}
function editFunc (recordId) { // 业务通用【修改】 函数
mchAppAddOrEdit.value.show(vdata.searchData['mchNo'], recordId)
}
function delFunc (appId) {
$infoBox.confirmDanger('确认删除?', '', () => {
req.delById(API_URL_MCH_APP, appId).then(res => {
$infoBox.message.success('删除成功!')
searchFunc()
})
})
}
const showFeeConfigList = function (recordId) { // 支付费率配置
jeepayPayWayFeeConfigDrawer.value.show(recordId)
}
function getAddFunc(type){
vdata.tableType = type
jeepayApplicationSave.value.show()
}
</script>
<style lang="less" scoped>
</style>

View File

@@ -0,0 +1,150 @@
<template>
<div style="background: #fff">
<a-tabs>
<a-tab-pane key="mchConfig" tab="系统配置">
<div class="account-settings-info-view">
<a-form ref="configFormModel" :model="vdata.mchConfigData" layout="vertical">
<a-row justify="start" type="flex" style="margin-top: 20px">
<a-col v-for="(item, config) in vdata.mchConfigData" :key="config" :span="7" :offset="1">
<a-form-item :label="item.configName">
<a-radio-group v-if="item.type === 'radio'" v-model:value="item.configVal">
<a-radio value="1">启用</a-radio>
<a-radio value="0">禁用</a-radio>
</a-radio-group>
</a-form-item>
</a-col>
<!-- <a-col :span="7" :offset="1">
<a-form-item label="是否启用码牌防逃单功能" name="qrcEscaping">
<a-radio-group v-model:value="vdata.mchConfigData.qrcEscaping">
<a-radio :value="1">启用</a-radio>
<a-radio :value="0">禁用</a-radio>
</a-radio-group>
</a-form-item>
</a-col>
<a-col :span="7" :offset="1">
<a-form-item label="是否启用app订单语音播报" name="appVoice">
<a-radio-group v-model:value="vdata.mchConfigData.appVoice">
<a-radio :value="1">启用</a-radio>
<a-radio :value="0">禁用</a-radio>
</a-radio-group>
</a-form-item>
</a-col> -->
</a-row>
<a-form-item class="bottom-btn">
<a-button :disabled="!$access('ENT_MCH_CONFIG_EDIT')" type="primary" :loading="vdata.btnLoading" @click="confirm"><check-circle-outlined />确认更新</a-button>
</a-form-item>
</a-form>
</div>
</a-tab-pane>
<a-tab-pane key="mchConfig2" tab="功能配置">
<div class="account-settings-info-view" style="margin-left: 40px">
<a-form layout="vertical">
<a-row justify="start" type="flex" style="margin-top: 20px">
<a-form-item label="商户等级切换">
<div class="typePopover">
<a-popover placement="top">
<template #title><span>商户级别</span></template>
<template #content>
<p>M0商户简单模式页面简洁仅基础收款功能</p>
<p>M1商户高级模式支持api调用 支持配置应用及分账转账功能</p>
</template>
<question-circle-outlined />
</a-popover>
</div>
<a-radio-group v-model:value="vdata.mchLevel">
<a-radio value="M0">M0</a-radio>
<a-radio value="M1">M1</a-radio>
</a-radio-group>
</a-form-item>
</a-row>
<a-form-item class="bottom-btn">
<a-button :disabled="!$access('ENT_MCH_CONFIG_EDIT')" type="primary" :loading="vdata.btnLoading" @click="confirmUpdateLevel"><check-circle-outlined />确认更新</a-button>
</a-form-item>
</a-form>
</div>
</a-tab-pane>
</a-tabs>
</div>
</template>
<script setup lang="ts">
import { API_URL_MCH_CONFIG, req, $getMainUserInfo, $updateMchLevel } from '@/api/manage'
import { ref, reactive, onMounted, getCurrentInstance } from 'vue'
const { $infoBox,$access } = getCurrentInstance()!.appContext.config.globalProperties
const configFormModel = ref()
const vdata = reactive ({
btnLoading: false,
groupKey: 'orderConfig',
mchConfigData: [] as any, // 配置保存对象
defaultConfig: [{
configKey: 'appVoice',
configName: '是否启用app订单语音播报',
configVal: '1',
type: 'radio'
}, {
configKey: 'qrcEscaping',
configName: '是否启用码牌防逃单功能',
configVal: '1',
type: 'radio'
}], // 默认配置项
mchLevel: '',
})
onMounted(()=>{
req.list(API_URL_MCH_CONFIG, {groupKey: vdata.groupKey}).then(res => {
if (res != null && res.length > 0) {
vdata.mchConfigData = res
}else {
vdata.mchConfigData = vdata.defaultConfig
}
})
$getMainUserInfo().then((res) => {
vdata.mchLevel = res.mchLevel
})
})
// 确认更新
function confirm (e) {
configFormModel.value.validate().then(valid => {
$infoBox.confirmPrimary('确认修改系统配置吗?', '', () => {
vdata.btnLoading = true // 打开按钮上的 loading
req.updateById(API_URL_MCH_CONFIG, vdata.groupKey, {configData: JSON.stringify(vdata.mchConfigData)}).then(res => {
$infoBox.message.success('修改成功')
vdata.btnLoading = false
}).catch(res => {
vdata.btnLoading = false
})
})
})
}
// 更新商户等级
function confirmUpdateLevel(){
$updateMchLevel(vdata.mchLevel).then(() => {
$infoBox.modalWarning('提示', '更新成功,重新登录后将切换功能模式!')
})
}
</script>
<style lang="less" scoped>
.bottom-btn{
/deep/ div{
display: flex;
justify-content: center;
}
}
.typePopover {
position: absolute;
top: -30px;
left: 93px;
}
</style>

View File

@@ -0,0 +1,59 @@
<template>
<a-drawer
v-model:visible="vdata.visible"
title="新建接收人"
width="40%"
:maskClosable="true"
@close="onClose"
>
<a-steps direction="vertical" :current="-1">
<a-step title="第一步,使用【微信扫一扫】扫描下列二维码并点击 [确认授权] 按钮" disabled status="process">
<template #description>
<img :src="vdata.wxmpConfig.authQr" alt="扫码授权" style="width: 200px;">
</template>
</a-step>
<a-step title="第二步,页面提示 [授权成功]。 按照手机端的提示关注公众号完成消息推送的开启操作。" disabled status="process">
<template #description>
<img :src="vdata.wxmpConfig.wxmpQr" alt="关注微信公众号" style="width: 200px;">
</template>
</a-step>
</a-steps>
<a-descriptions style="margin-bottom: 50px;">
<a-descriptions-item label="提示">若未关注公众号则无法正确接收到提示信息</a-descriptions-item>
</a-descriptions>
<div class="drawer-btn-center">
<a-button :style="{ marginRight: '8px' }" @click="onClose"><close-outlined />关闭</a-button>
</div>
</a-drawer>
</template>
<script setup lang="ts">
import { $getWxmpInfo } from '@/api/manage'
import { reactive, ref } from 'vue'
const infoFormModel =ref()
const vdata = reactive({
visible: false, // 抽屉开关
wxmpConfig: {} as any
})
defineExpose({show})
// 抽屉显示
function show () {
// 拉取详情
$getWxmpInfo().then(res => {
vdata.wxmpConfig = res
})
vdata.visible = true // 展示弹层信息~
}
function onClose () {
vdata.visible = false
}
</script>
<style lang="less" scoped>
</style>

View File

@@ -0,0 +1,94 @@
<template>
<page-header-wrapper>
<a-card class="table-card">
<JeepaySearchForm :searchFunc="searchFunc" :resetFunc="onReset">
<jeepay-text-up v-model:value="searchData['nickname']" :placeholder="'微信昵称'" />
</JeepaySearchForm>
<!-- 列表渲染 -->
<JeepayTable
ref="infoTable"
:init-data="true"
:req-table-data-func="reqTableDataFunc"
:table-columns="tableColumns"
:search-data="searchData"
row-key="userId"
@btnLoadClose="btnLoading=false"
>
<template #topBtnSlot>
<a-button v-if="$access('ENT_MCH_WXMP_USER_ADD')" type="primary" @click="addFunc"><plus-outlined />新建接收人</a-button>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'sendStatus'">
<JeepayTableColState :state="record.sendStatus" :showSwitchType="$access('ENT_MCH_WXMP_USER_EDIT')" :onChange="(sendStatus) => { return updateState(record.userId, sendStatus)}" />
</template>
<template v-if="column.key === 'operation'">
<a-button v-if="$access('ENT_MCH_WXMP_USER_DELET')" type="link" style="color: red" @click="delFunc(record.userId)">删除</a-button>
</template>
</template>
</JeepayTable>
</a-card>
<!-- 新增页面组件 -->
<InfoAddOrEdit ref="infoAddOrEdit" :callbackFunc="searchFunc" />
</page-header-wrapper>
</template>
<script setup lang="ts">
import { API_URL_MCH_WXMP_UESR, req, reqLoad } from '@/api/manage'
import InfoAddOrEdit from './AddOrEdit.vue'
import { ref, reactive, getCurrentInstance } from 'vue'
const { $infoBox,$access } = getCurrentInstance()!.appContext.config.globalProperties
const infoAddOrEdit = ref()
const infoTable = ref()
let tableColumns = reactive([
{ key: 'nickname', fixed: 'left', title: '微信昵称', dataIndex: 'nickname'},
{ key: 'wxOpenId', title: 'openId', dataIndex: 'wxOpenId', },
{ key: 'wxAppId', title: 'appId', dataIndex: 'wxAppId'},
{ key: 'sendStatus', title: '状态' },
{ key: 'createdAt', dataIndex: 'createdAt', title: '创建日期',},
{ key: 'operation', title: '操作', fixed: 'right', align: 'center'}
])
let btnLoading = ref(false)
let searchData = ref({})
function reqTableDataFunc(params: any) { // 请求table接口数据
return req.list(API_URL_MCH_WXMP_UESR, params)
}
function searchFunc () { // 点击【查询】按钮点击事件
btnLoading.value = true
infoTable.value.refTable(true)
}
function addFunc () { // 业务通用【新增】 函数
infoAddOrEdit.value.show()
}
function updateState (recordId, state) { // 【更新状态】
return new Promise<void>((resolve, reject) => {
$infoBox.confirmDanger('确认修改?', '', () => {
return reqLoad.updateById(API_URL_MCH_WXMP_UESR, recordId, { sendStatus: state }).then(res => {
searchFunc()
resolve()
}).catch(err => reject(err))
},
() => {
reject(new Error())
})
})
}
function delFunc (recordId: any) { // 业务通用【删除】 函数
$infoBox.confirmDanger('确认删除?', '', () => {
reqLoad.delById(API_URL_MCH_WXMP_UESR, recordId).then((res: any) => {
infoTable.value.refTable(true)
$infoBox.message.success('删除成功')
})
})
}
function onReset(){ //重置搜索内容
searchData.value = {}
}
</script>

View File

@@ -0,0 +1,98 @@
<template>
<a-modal v-model:visible="visible" :footer="null">
<div class="modal-title">入账订单详细</div>
<div class="statistics-list" style="padding-bottom: 20px;">
<div v-for="(item, index) in props.countDetailList[0] as any" :key="index" class="item item-box-title">
<div v-if="item.type == 'line'" class="line" />
<div class="title">{{ item.title }}</div>
<div v-if="item.title" class="amount">
<a-tooltip v-if="item.title == '实付金额'">
<template #title>{{ item.content }}</template>
<span class="amount-num">{{ item.content }}</span><span></span>
</a-tooltip>
<a-tooltip v-else>
<template #title> - {{ item.content }}</template>
<span class="amount-num">-{{ item.content }}</span><span></span>
</a-tooltip>
</div>
<div v-if="item.count >= 0" class="detail">
<span>{{ item.count }}</span>
</div>
<span class="item-box-title-border"></span>
</div>
</div>
<div class="statistics-list" style="padding-bottom: 55px;">
<div v-for="(item, index) in props.countDetailList[1] as any" :key="index" class="item item-box-title">
<div v-if="item.type == 'line'" class="line" />
<div class="title">{{ item.title }}</div>
<div v-if="item.title" class="amount">
<a-tooltip v-if="item.title == '补贴金额'">
<template #title> {{ item.content }}</template>
<span class="amount-num">{{ item.content }}</span><span></span>
</a-tooltip>
<a-tooltip v-else>
<template #title>- {{ item.content }}</template>
<span class="amount-num">-{{ item.content }}</span><span></span>
</a-tooltip>
</div>
<div v-if="item.count >= 0" class="detail">
<span>{{ item.count }}</span>
</div>
<span class="item-box-title-border"></span>
</div>
</div>
<div class="close" @click=" visible = false">
<a-button type="primary">知道了</a-button>
</div>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue'
const visible = ref<boolean>(false)
const props = defineProps({
countDetailList: {type: Array, default: () => []},
sucDetailList: {type: Array, default: () => []}
})
const showModal = () => visible.value = true
defineExpose({showModal})
</script>
<style scoped lang="less">
.modal-title, .modal-describe{
text-align: center;
margin-bottom: 15px;
}
.modal-title {
margin-bottom: 20px;
text-align: center;
font-size: 18px;
font-weight: 600;
}
.close {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
border-top: 1px solid #EFEFEF;
display: flex;
align-items: center;
justify-content: center;
padding: 10px 0;
}
.amount {
//max-width:150px;
//overflow:hidden;
//text-overflow:ellipsis;
//white-space:nowrap;
}
.item-box-title{
//width: 30%;
}
.item-box-title-border{
//border-right: 1px solid #EFEFEF !important;
height: 100px;
}
</style>

View File

@@ -0,0 +1,447 @@
<template>
<a-drawer
v-model:visible="vdata.visible"
width="50%"
placement="right"
:closable="true"
:title="vdata.visible === true? '订单详情':''"
@close="onClose"
>
<a-divider class="jeepay-m-divider" orientation="left" style="color: #1A66FF;">订单信息</a-divider>
<a-row justify="space-between" type="flex">
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="平台订单号">
{{ vdata.detailData.payOrderId }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="通道订单号">
{{ vdata.detailData.channelOrderNo??"--" }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="渠道订单号">
{{ vdata.detailData.platformOrderNo??"--" }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商户订单号">
{{ vdata.detailData.mchOrderNo??"--" }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="落单订单号">
{{ vdata.detailData.platformMchOrderNo??"--" }}
</a-descriptions-item>
</a-descriptions>
</a-col>
</a-row>
<a-divider class="jeepay-m-divider" orientation="left" style="color: #1A66FF;">基础信息</a-divider>
<a-row justify="space-between" type="flex">
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商户简称">
{{ vdata.detailData.mchName }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商户号">
{{ vdata.detailData.mchExtNo }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="门店名称">
{{ vdata.detailData.storeName }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="门店编号">
{{ vdata.detailData.storeId }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="应用名称">
{{ vdata.detailData.appName }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="应用ID">
{{ vdata.detailData.appId }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="用户名称">
{{vdata.detailData.mchUserName}}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="用户ID">
{{ vdata.detailData.mchNo }} [ {{vdata.detailData.mchUserPhone}} ]
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="服务商名称">
{{ vdata.detailData.agentName }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="服务商号">
{{ vdata.detailData.agentNo }} [ {{vdata.detailData.agentContactTel}} ]
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="支付金额(元)">
<a-tag color="green">
{{ vdata.detailData.amount/100 }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="订单状态">
<span v-if="vdata.detailData.state !== 5 ">
<a-tag
:key="vdata.detailData.state"
:color="vdata.detailData.state === 0?'blue':vdata.detailData.state === 1?'orange':vdata.detailData.state === 2?'green':vdata.detailData.state === 6?'':'volcano'"
>
{{ vdata.detailData.state === 0?'订单生成':vdata.detailData.state === 1?'支付中':vdata.detailData.state === 2?'支付成功':vdata.detailData.state === 3?'支付失败':vdata.detailData.state === 4?'已撤销':vdata.detailData.state === 6?'订单关闭':'未知' }}
</a-tag>
</span>
<span v-else-if="vdata.detailData.state === 5 ">
<a-tag
:key="vdata.detailData.refundState"
:color="vdata.detailData.refundState === 0?'red':vdata.detailData.refundState === 1?'orange':vdata.detailData.refundState === 2?'red':'red'"
>
{{ vdata.detailData.refundState === 0?'':vdata.detailData.refundState === 1?'部分退款':vdata.detailData.refundState === 2?'全额退款':'未知' }}
</a-tag>
</span>
</a-descriptions-item>
</a-descriptions>
</a-col>
<!-- <a-col :sm="12">-->
<!-- <a-descriptions><a-descriptions-item label="实际手续费"><a-tag color="pink">{{ vdata.detailData.mchFeeAmount/100 }}</a-tag></a-descriptions-item></a-descriptions>-->
<!-- </a-col>-->
<!-- <a-col :sm="12">-->
<!-- <a-descriptions><a-descriptions-item label="收单手续费"><a-tag color="pink">{{ vdata.detailData.mchOrderFeeAmount/100 }}</a-tag></a-descriptions-item></a-descriptions>-->
<!-- </a-col>-->
<!-- <a-col :sm="12">-->
<!-- <a-descriptions><a-descriptions-item label="商家费率">{{ vdata.detailData.mchFeeRate }}</a-descriptions-item></a-descriptions>-->
<!-- </a-col>-->
<a-col :sm="12">
<a-descriptions><a-descriptions-item label="收单费率">{{ vdata.detailData.mchFeeRateNum }}%</a-descriptions-item></a-descriptions>
</a-col>
<a-col :sm="12">
<!-- <a-descriptions><a-descriptions-item label="收单手续费(元)">{{ (vdata.detailData.findAmt / 100).toFixed(2) }} * {{ vdata.detailData.mchFeeRateNum }} = {{ (vdata.detailData.mchOrderFeeAmount / 100).toFixed(2) }}</a-descriptions-item></a-descriptions>-->
收单手续费
<a-popover placement="top">
<template #content>
<p>收单手续费 = 实付金额 - * 收单费率</p>
</template>
<question-circle-outlined/>
</a-popover>
{{ (vdata.detailData.findAmt / 100).toFixed(2) }} * {{ vdata.detailData.mchFeeRateNum }} = {{ (vdata.detailData.mchOrderFeeAmount / 100).toFixed(2) }}
</a-col>
<a-col :sm="12">
<a-descriptions><a-descriptions-item label="垫资费率">{{ vdata.detailData.cashRate }}%</a-descriptions-item></a-descriptions>
</a-col>
<a-col :sm="12">
垫资手续费
<a-popover placement="top">
<template #content>
<p>垫资手续费 = 实付金额 - * 垫资费率</p>
</template>
<question-circle-outlined/>
</a-popover>
{{ (vdata.detailData.findAmt / 100).toFixed(2) }} * {{ vdata.detailData.cashRate }} = {{ (vdata.detailData.cashFee / 100).toFixed(2) }}
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="退款次数">
{{ vdata.detailData.refundTimes }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="退款总额">
<a-tag v-if="vdata.detailData.refundAmount" color="cyan">
{{ (vdata.detailData.refundAmount / 100).toFixed(2)}}
</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions><a-descriptions-item label="优惠金额">{{ (vdata.detailData.discountAmt / 100).toFixed(2) }}</a-descriptions-item></a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions><a-descriptions-item label="补贴金额">{{ (vdata.detailData.marketAmt / 100).toFixed(2) }}</a-descriptions-item></a-descriptions>
</a-col>
<a-col :sm="12" style="margin-bottom: 12px">
预计入账金额
<!-- <a-tooltip title="入账金额 = 实付金额 - 收单手续费 - 垫资手续费 - 退款总额" >-->
<!-- <question-circle-outlined style="padding-right: 10px"/>-->
<!-- </a-tooltip>-->
<a-popover placement="top">
<template #content>
<p>入账金额 = 实付金额 - 收单手续费 - 垫资手续费 - 退款总额</p>
</template>
<question-circle-outlined/>
</a-popover>
{{vdata.detailData.findAmt / 100}} - {{vdata.detailData.mchOrderFeeAmount / 100}} - {{vdata.detailData.cashFee / 100}} - {{vdata.detailData.refundAmount / 100}} = {{ ((vdata.detailData.findAmt - vdata.detailData.mchOrderFeeAmount - vdata.detailData.cashFee - vdata.detailData.refundAmount + vdata.detailData.marketAmt) / 100).toFixed(2) }}
</a-col>
<a-col :sm="12"></a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="支付错误码">
{{ vdata.detailData.errCode }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="支付错误描述">
{{ vdata.detailData.errMsg }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="买家备注">
{{ vdata.detailData.buyerRemark }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="卖家备注">
{{ vdata.detailData.sellerRemark }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="订单失效时间">
{{ vdata.detailData.expiredTime }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="创建时间">
{{ vdata.detailData.createdAt }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="更新时间">
{{ vdata.detailData.updatedAt }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="支付成功时间">
{{ vdata.detailData.successTime??"--" }}
</a-descriptions-item>
</a-descriptions>
</a-col>
</a-row>
<a-divider class="jeepay-m-divider" orientation="left" style="color: #1A66FF;">其他信息</a-divider>
<a-row justify="space-between" type="flex">
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商品标题">
{{ vdata.detailData.subject }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商品描述">
{{ vdata.detailData.body }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="接口代码">
{{ vdata.detailData.ifCode }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="货币代码">
{{ vdata.detailData.currency }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="支付方式">
{{ vdata.detailData.wayCode }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="客户端IP">
{{ vdata.detailData.clientIp }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="用户标识">
{{ vdata.detailData.channelUser }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="异步通知地址">
{{ vdata.detailData.notifyUrl }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="页面跳转地址">
{{ vdata.detailData.returnUrl }}
</a-descriptions-item>
</a-descriptions>
</a-col>
</a-row>
<a-divider class="jeepay-m-divider" orientation="left" style="color: #1A66FF;">分账信息</a-divider>
<a-row justify="start" type="flex">
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="订单分账模式">
<span v-if="vdata.detailData.divisionMode == 0">该笔订单不允许分账</span>
<span v-else-if="vdata.detailData.divisionMode == 1">支付成功按配置自动完成分账</span>
<span v-else-if="vdata.detailData.divisionMode == 2">商户手动分账(解冻商户金额)</span>
<span v-else>未知</span>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="分账状态">
<a-tag v-if="vdata.detailData.divisionState == 0" color="blue">未发生分账</a-tag>
<a-tag v-else-if="vdata.detailData.divisionState == 1" color="orange">待分账</a-tag>
<a-tag v-else-if="vdata.detailData.divisionState == 2" color="red">分账处理中</a-tag>
<a-tag v-else-if="vdata.detailData.divisionState == 3" color="green">任务已结束</a-tag>
<a-tag v-else color="#f50">未知</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions><a-descriptions-item label="最新分账发起时间">{{ vdata.detailData.divisionLastTime }}</a-descriptions-item></a-descriptions>
</a-col>
</a-row>
<!-- <a-divider class="jeepay-m-divider" orientation="left" style="color: #1A66FF;">扩展信息</a-divider>-->
<!-- <a-row justify="start" type="flex">-->
<!-- <a-col :sm="24">-->
<!-- <span style="color: black;">扩展参数:</span>-->
<!-- <a-form-item>-->
<!-- <a-input-->
<!-- v-model:value="vdata.detailData.extParam"-->
<!-- type="textarea"-->
<!-- disabled="disabled"-->
<!-- style="height: 100px;color: black;margin-top: 10px;"-->
<!-- />-->
<!-- </a-form-item>-->
<!-- </a-col>-->
<!-- </a-row>-->
</a-drawer>
</template>
<script lang="ts" setup>
import { API_URL_MCH_APPLYMENT_LIST, req } from '@/api/manage'
import {defineProps,reactive, getCurrentInstance} from 'vue'
const { $hasAgentEnt, $SYS_NAME_MAP } = getCurrentInstance()!.appContext.config.globalProperties
const props = defineProps({
callbackFunc: { type: Function,default:null }
})
const vdata:any = reactive({
btnLoading: false,
detailData: {}, // 数据对象
recordId: null, // 更新对象ID
visible: false, // 是否显示弹层/抽屉
termNo: false, // 是否显示弹层/抽屉
})
function show (data) { // 弹层打开事件
vdata.detailData = data // 数据清空
vdata.visible = true
}
function onClose () {
vdata.visible = false
}
defineExpose({
show //抛出show函数给父组件
})
</script>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,66 @@
<template>
<a-modal v-model:visible="visible" :footer="null">
<div class="modal-title">成交订单详细</div>
<div class="modal-describe">创建订单金额/笔数 = 成交订单金额/笔数 + 未付款订单金额/笔数</div>
<div class="statistics-list" style="padding-bottom: 55px;">
<div v-for="(item, index) in props.sucDetailList as any" :key="index" class="item">
<div v-if="item.type == 'line'" class="line" />
<div class="title">{{ item.title }}</div>
<div v-if="item.title" class="amount">
<a-tooltip>
<template #title>{{ item.content }}</template>
<span class="amount-num">{{ item.content }}</span><span></span>
</a-tooltip>
</div>
<div v-if="item.count >= 0" class="detail">
<span>{{ item.count }}</span>
</div>
</div>
</div>
<div class="close" @click=" visible = false">
<a-button type="primary">知道了</a-button>
</div>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue'
const visible = ref<boolean>(false)
const props = defineProps({
countDetailList: {type: Array, default: () => []},
sucDetailList: {type: Array, default: () => []}
})
const showModal = () => visible.value = true
defineExpose({showModal})
</script>
<style scoped lang="less">
.modal-title, .modal-describe{
text-align: center;
margin-bottom: 15px;
}
.modal-title {
margin-bottom: 20px;
text-align: center;
font-size: 18px;
font-weight: 600;
}
.close {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
border-top: 1px solid #EFEFEF;
display: flex;
align-items: center;
justify-content: center;
padding: 10px 0;
}
.amount {
max-width:150px;
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
}
</style>

View File

@@ -0,0 +1,170 @@
<template>
<div>
<a-modal
v-model:visible="vdata.visible"
title="退款"
:confirm-loading="vdata.confirmLoading"
:closable="false"
@ok="handleOk"
@cancel="handleCancel"
>
<a-row>
<a-col :sm="24">
<a-descriptions>
<a-descriptions-item label="支付订单号">
<a-tag color="purple">
{{ vdata.detailData.payOrderId }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="24">
<a-descriptions>
<a-descriptions-item label="支付金额">
<a-tag color="green">
{{ vdata.detailData.amount/100 }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="24">
<a-descriptions>
<a-descriptions-item label="可退金额">
<a-tag color="pink">
{{ nowRefundAmount }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
</a-row>
<a-form ref="refundInfo" :rules="rules" :model="vdata.refund">
<a-form-item label="退款金额" name="refundAmount">
<a-input-number v-model:value="vdata.refund.refundAmount" :precision="2" style="width:100%" />
</a-form-item>
<a-form-item label="退款原因" name="refundReason">
<a-input v-model:value="vdata.refund.refundReason" type="textarea" autocomplete="off" />
</a-form-item>
<a-form-item label="支付密码" name="refundPassword">
<a-input v-model:value="vdata.refund.refundPassword" maxlength="6" type="password" autocomplete="off" />
</a-form-item>
</a-form>
</a-modal>
</div>
</template>
<script setup lang="tsx">
import { API_URL_PAY_ORDER_LIST, req, $payOrderRefund } from '@/api/manage'
import {ref, onMounted, reactive, getCurrentInstance,computed} from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
// 获取全局函数
const { $infoBox,$access } = getCurrentInstance()!.appContext.config.globalProperties
const props = defineProps ({
callbackFunc: { type: Function, default: () => () => ({}) }
})
const refundInfo = ref()
const vdata : any = reactive({
refundErrorModal: null, // 退款错误信息的modal对象
recordId: '',
labelCol: { span: 4 },
wrapperCol: { span: 16 },
visible: false,
confirmLoading: false,
detailData: {} as any,
refund: {
refundReason: '', // 退款原因
refundAmount: '' // 退款金额
} as any,
})
const rules = {
refundReason: [{ min: 0, max: 256, required: true, trigger: 'blur', message: '请输入退款原因最长不超过256个字符' }],
refundPassword: [{required: true, trigger: 'blur', message: '请输入支付密码' }],
refundAmount: [{ required: true, message: '请输入金额', trigger: 'blur',type:'number' },
{
validator: (rule, value) => {
console.log(value)
if (value < 0.01 || value > nowRefundAmount.value) {
return Promise.reject('退款金额不能小于0.01并且不能大于可退金额')
}else{
return Promise.resolve()
}
}
}]
}
const nowRefundAmount = computed(()=>{
return (vdata.detailData.amount - vdata.detailData.refundAmount) / 100
})
defineExpose({show})
function show (recordId) {
if (refundInfo.value !== undefined) {
refundInfo.value.resetFields()
}
vdata.recordId = recordId
vdata.visible = true
vdata.refund = {}
req.getById(API_URL_PAY_ORDER_LIST, recordId).then(res => {
vdata.detailData = res
})
}
function handleOk () {
refundInfo.value.validate().then(valid =>{
vdata.confirmLoading = true
// 退款接口
$payOrderRefund(vdata.recordId, vdata.refund.refundAmount, vdata.refund.refundReason, vdata.refund.refundPassword).then(res => {
vdata.visible = false // 关闭弹窗
vdata.confirmLoading = false // 取消按钮转圈
if (res.state === 0 || res.state === 3) { // 订单生成 || 失败
vdata.refundErrorModal = $infoBox.modalError('退款失败', buildModalText(res))
} else if (res.state === 1) { // 退款中
vdata.refundErrorModal = $infoBox.modalWarning('退款中', buildModalText(res))
props.callbackFunc()
} else if (res.state === 2) { // 退款成功
$infoBox.message.success('退款成功')
props.callbackFunc()
} else {
vdata.refundErrorModal = $infoBox.modalWarning('退款状态未知', buildModalText(res))
}
}).catch(() => {
vdata.confirmLoading = false // 取消按钮转圈
})
})
}
function handleCancel (e) {
vdata.visible = false
}
// 跳转到退款列表函数
function toRefundList () {
vdata.refundErrorModal.destroy()
router.push({
path: '/refund',
})
}
function buildModalText (res) {
return <div>
{ res.errCode? <div>错误码{res.errCode} </div> : '' }
{ res.errMsg? <div>错误信息{res.errMsg} </div> : '' }
<div>请到<a onClick={ toRefundList }>退款列表</a>中查看详细信息</div>
</div>
}
</script>
<style scoped lang="less">
</style>

View File

@@ -0,0 +1,368 @@
<template>
<a-drawer
v-model:visible="vdata.visible"
width="60%"
placement="right"
:closable="true"
:title="vdata.visible === true? '退款订单详情':''"
@close="onClose"
>
<a-divider class="jeepay-m-divider" orientation="left" style="color: #1A66FF;">订单信息</a-divider>
<a-row justify="space-between" type="flex">
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="退款订单号">
{{ vdata.detailData.refundOrderId }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="通道订单号">
{{ vdata.detailData.channelPayOrderNo }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="平台订单号">
{{ vdata.detailData.payOrderId }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商户退款订单号">
{{ vdata.detailData.mchRefundNo }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="渠道订单号">
--
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="落单订单号">
--
</a-descriptions-item>
</a-descriptions>
</a-col>
</a-row>
<a-divider class="jeepay-m-divider" orientation="left" style="color: #1A66FF;">其他信息</a-divider>
<a-row justify="space-between" type="flex">
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商户简称">
{{ vdata.detailData.mchName }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商户号">
{{ vdata.detailData.mchExtNo }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="门店名称">
{{ vdata.detailData.storeName }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="门店编号">
{{ vdata.detailData.storeId }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="渠道名称">
{{ vdata.detailData.isvName }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="渠道号">
{{ vdata.detailData.isvNo }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="应用名称">
{{ vdata.detailData.appName }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="应用ID">
{{ vdata.detailData.appId }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="用户名称">
{{vdata.detailData.mchUserName}}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="用户ID">
{{ vdata.detailData.mchNo }} [ {{vdata.detailData.mchUserPhone}} ]
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="服务商名称">
{{ vdata.detailData.agentName }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="服务商号">
{{ vdata.detailData.agentNo }} [ {{vdata.detailData.agentContactTel}} ]
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="支付金额">
<a-tag color="green">
{{ vdata.detailData.payAmount/100 }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="退款金额">
<a-tag color="red">
{{ vdata.detailData.refundAmount/100 }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="手续费退还金额">
<a-tag color="green">
{{ vdata.detailData.refundFeeAmount/100 }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="退款状态">
<a-tag :color="vdata.detailData.state === 0?'blue':vdata.detailData.state === 1?'orange':vdata.detailData.state === 2?'green':'volcano'">
{{ vdata.detailData.state === 0?'订单生成':vdata.detailData.state === 1?'退款中':vdata.detailData.state === 2?'退款成功':vdata.detailData.state === 3?'退款失败':vdata.detailData.state === 4?'任务关闭':'未知' }}
</a-tag>
<a-tooltip :title="vdata.detailData.errMsg" v-if="vdata.detailData.state == 3">
<question-circle-outlined style="color: #FF0000;"/>
</a-tooltip>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="退款类型">
<a-tag
:key="vdata.detailData.refundType"
:color="vdata.detailData.refundType === 0?'blue':vdata.detailData.refundType === 1?'red':vdata.detailData.refundType === 2?'orange':'volcano'"
>
{{ vdata.detailData.refundType === 1?'全额退款':vdata.detailData.refundType === 2?'部分退款':'' }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="退款模式">
<a-tag
:key="vdata.detailData.extParam"
:color="vdata.detailData.extParam === '1'?'blue':vdata.detailData.extParam === '2'?'orange':'volcano'"
>
{{ vdata.detailData.extParam === '1'?'收款商户号':vdata.detailData.extParam === '2'?'退款专用账户':'' }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="24">
<a-descriptions>
<a-descriptions-item label="退款成功时间">
{{ vdata.detailData.successTime }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="创建时间">
{{ vdata.detailData.createdAt }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="更新时间">
{{ vdata.detailData.updatedAt }}
</a-descriptions-item>
</a-descriptions>
</a-col>
</a-row>
<!-- <a-divider />-->
<!-- <a-col :sm="12">-->
<!-- <a-descriptions>-->
<!-- <a-descriptions-item label="接口代码">-->
<!-- {{ vdata.detailData.ifCode }}-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
<!-- </a-col>-->
<!-- <a-col :sm="12">-->
<!-- <a-descriptions>-->
<!-- <a-descriptions-item label="货币代码">-->
<!-- {{ vdata.detailData.currency }}-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
<!-- </a-col>-->
<!-- <a-col :sm="12">-->
<!-- <a-descriptions>-->
<!-- <a-descriptions-item label="方式代码">-->
<!-- {{ vdata.detailData.wayCode }}-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
<!-- </a-col>-->
<!-- <a-col :sm="12">-->
<!-- <a-descriptions>-->
<!-- <a-descriptions-item label="客户端IP">-->
<!-- {{ vdata.detailData.clientIp }}-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
<!-- </a-col>-->
<!-- <a-col :sm="24">-->
<!-- <a-descriptions>-->
<!-- <a-descriptions-item label="异步通知地址">-->
<!-- {{ vdata.detailData.notifyUrl }}-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
<!-- </a-col>-->
<!-- </a-row>-->
<!-- <a-divider />-->
<!-- <a-col :sm="12">-->
<!-- <a-descriptions>-->
<!-- <a-descriptions-item label="渠道订单号">-->
<!-- {{ vdata.detailData.channelOrderNo }}-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
<!-- </a-col>-->
<!-- <a-col :sm="12">-->
<!-- <a-descriptions>-->
<!-- <a-descriptions-item label="渠道错误码">-->
<!-- {{ vdata.detailData.errCode }}-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
<!-- </a-col>-->
<!-- <a-col :sm="12">-->
<!-- <a-descriptions>-->
<!-- <a-descriptions-item label="渠道错误描述">-->
<!-- {{ vdata.detailData.errMsg }}-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
<!-- </a-col>-->
<!-- <a-col :sm="24">-->
<!-- <span style="color: black;">渠道额外参数:</span>-->
<!-- <a-form-item>-->
<!-- <a-input-->
<!-- v-model:value="vdata.detailData.channelExtra"-->
<!-- type="textarea"-->
<!-- disabled="disabled"-->
<!-- style="height: 100px;color: black;margin-top: 10px;"-->
<!-- />-->
<!-- </a-form-item>-->
<!-- </a-col>-->
<!-- <a-divider />-->
<!-- <a-col :sm="24">-->
<!-- <span style="color: black;">扩展参数:</span>-->
<!-- <a-form-item>-->
<!-- <a-input-->
<!-- v-model:value="vdata.detailData.extParam"-->
<!-- type="textarea"-->
<!-- disabled="disabled"-->
<!-- style="height: 100px;color: black;margin-top: 10px;"-->
<!-- />-->
<!-- </a-form-item>-->
<!-- </a-col>-->
<a-col :sm="24">
<span style="color: black;">备注:</span>
<a-form-item>
<a-input
v-model:value="vdata.detailData.remark"
type="textarea"
disabled="disabled"
style="height: 100px;color: black;margin-top: 10px;"
/>
</a-form-item>
</a-col>
</a-drawer>
</template>
<script lang="ts" setup>
import { req } from '@/api/manage'
import {defineProps,reactive, getCurrentInstance} from 'vue'
const { $hasAgentEnt, $SYS_NAME_MAP } = getCurrentInstance()!.appContext.config.globalProperties
const props = defineProps({
callbackFunc: { type: Function,default:null }
})
const vdata:any = reactive({
btnLoading: false,
detailData: {}, // 数据对象
recordId: null, // 更新对象ID
visible: false, // 是否显示弹层/抽屉
termNo: false, // 是否显示弹层/抽屉
})
function show (data) { // 弹层打开事件
vdata.detailData = data // 数据清空
vdata.visible = true
}
function onClose () {
vdata.visible = false
}
defineExpose({
show //抛出show函数给父组件
})
</script>

View File

@@ -0,0 +1,675 @@
<template>
<page-header-wrapper>
<a-card>
<JeepaySearchForm :searchFunc="searchFunc" :resetFunc="onReset">
<a-form-item label="" class="table-search-item">
<JeepayDateRangePicker ref="dateRangePicker" v-model:value="vdata.searchData['queryDateRange']" customDateRangeType="dateTime" />
</a-form-item>
<a-form-item class="table-search-item">
<a-select v-model:value="vdata.searchData['agentName']" placeholder="请选择服务商" @change="handleChange">
<a-select-option value="">全部</a-select-option>
<a-select-option value="onlyOne">仅自己</a-select-option>
<a-select-option v-for="d in vdata.agentList" :key="d.agentNo" v-model:value="d.agentNo">
{{ d.agentName + " [ ID: " + d.agentNo + " ]" }}
</a-select-option>
</a-select>
</a-form-item>
<JeepaySearchInfoInput v-model:value="vdata.searchData['mchUserName']" placeholder="用户号/名称" :textUpStyle="true" :mchNoAndName="true" showType="MCH" />
<!-- <jeepay-text-up v-model:value="vdata.searchData.unionOrderId" :placeholder="'退款/支付/渠道/商户退款号'" />-->
<!-- <jeepay-text-up :placeholder="'退款订单号'" :msg="searchData.refundOrderId" v-model="searchData.refundOrderId" />-->
<!-- <jeepay-text-up :placeholder="'商户退款单号'" :msg="searchData.mchRefundNo" v-model="searchData.mchRefundNo" />-->
<!-- <jeepay-text-up :placeholder="'支付订单号'" :msg="searchData.payOrderId" v-model="searchData.payOrderId" />-->
<!-- <jeepay-text-up :placeholder="'渠道订单号'" :msg="searchData.channelPayOrderNo" v-model="searchData.channelPayOrderNo" />-->
<jeepay-text-up v-model:value="vdata.searchData.unionOrderId" :placeholder="'订单号'" />
<jeepay-text-up v-model:value="vdata.searchData.mchInfo" :placeholder="'商户名称/商户号'" />
<jeepay-text-up v-model:value="vdata.searchData.storeInfo" :placeholder="'门店名称/门店编号'" />
<jeepay-text-up v-model:value="vdata.searchData.ifCode" :placeholder="'支付通道'" />
<jeepay-text-up v-model:value="vdata.searchData.isvNo" :placeholder="'渠道名称/渠道号'" />
<jeepay-text-up v-model:value="vdata.searchData.appId" :placeholder="'应用名称/应用ID'" />
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData.state" placeholder="退款状态">
<a-select-option value="">全部</a-select-option>
<a-select-option value="0">订单生成</a-select-option>
<a-select-option value="1">退款中</a-select-option>
<a-select-option value="2">退款成功</a-select-option>
<a-select-option value="3">退款失败</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData.refundType" placeholder="退款类型">
<a-select-option value="">全部</a-select-option>
<a-select-option value="1">全额退款</a-select-option>
<a-select-option value="2">部分退款</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData.extParam" placeholder="退款模式">
<a-select-option value="">全部</a-select-option>
<a-select-option value="1">收款商户号</a-select-option>
<a-select-option value="2">退款专用账户</a-select-option>
</a-select>
</a-form-item>
</JeepaySearchForm>
<!-- 列表渲染 -->
<JeepayTable
ref="infoTable"
:initData="true"
:closable="true"
:searchData="vdata.searchData"
:reqTableDataFunc="reqTableDataFunc"
:tableColumns="tableColumns"
rowKey="refundOrderId"
:tableRowCrossColor="true"
:statisticsIsShow="$access('ENT_REFUND_ORDER_COUNT')"
:tableExportFunc="tableExportFunc"
@btnLoadClose="vdata.btnLoading=false"
>
<template #statistics>
<div class="statistics-list">
<div v-for="(item, index) in orderCountList" :key="index" class="item">
<div v-if="item.type == 'line'" class="line" />
<div class="title">{{ item.title }}</div>
<div v-if="item.title" class="amount" :style="{color: item.textColor}">
<!-- <span v-if="item.symbol" class="symbol">{{ item.symbol == 'add' ? '+' : '-' }}</span> -->
<span class="amount-num">{{ item.content }}</span>{{ item.unit?item.unit:'元' }}
</div>
<div v-if="item.title" class="amount" >
<!-- <span v-if="item.symbol" class="symbol">{{ item.symbol == 'add' ? '+' : '-' }}</span> -->
<span class="amount-num">{{ item.num }}</span>
</div>
</div>
</div>
</template>
<!-- <template #bodyCell="{column,record}">-->
<!-- <template v-if="column.key == 'payAmount'"><b>{{ record.payAmount/100 }}</b></template>-->
<!-- <template v-if="column.key == 'refundAmount'"><b>{{ record.refundAmount/100 }}</b></template>-->
<!-- <template v-if="column.key == 'refundFeeAmount'"><b>{{ record.refundFeeAmount/100 }}</b></template>-->
<!-- <template v-if="column.key == 'state'">-->
<!-- <div>-->
<!-- <a-tag-->
<!-- :key="record.state"-->
<!-- :color="record.state === 0?'blue':record.state === 1?'orange':record.state === 2?'green':'volcano'"-->
<!-- >-->
<!-- {{ record.state === 0?'订单生成':record.state === 1?'退款中':record.state === 2?'退款成功':record.state === 3?'退款失败':record.state === 4?'任务关闭':'未知' }}-->
<!-- </a-tag>-->
<!-- </div>-->
<!-- </template>-->
<!-- <template v-if="column.key == 'refund'">-->
<!-- <div class="order-list">-->
<!-- <p><span style="color:#729ED5;background:#e7f5f7">支付</span>{{ record.payOrderId }}</p>-->
<!-- <p v-if="record.channelPayOrderNo" style="margin-bottom: 0;">-->
<!-- <span style="color:#fff;background:#E09C4D">渠道</span>-->
<!-- <a-tooltip v-if="record.channelPayOrderNo.length > record.payOrderId.length" placement="bottom" style="font-weight: normal;">-->
<!-- <template #title>-->
<!-- <span>{{ record.channelPayOrderNo }}</span>-->
<!-- </template>-->
<!-- {{ changeStr2ellipsis(record.channelPayOrderNo, record.payOrderId.length) }}-->
<!-- </a-tooltip>-->
<!-- <span v-else style="font-weight: normal;">{{ record.channelPayOrderNo }}</span>-->
<!-- </p>-->
<!-- </div>-->
<!-- </template>-->
<!-- <template v-if="column.key == 'pay'">-->
<!-- <div class="order-list">-->
<!-- <p><span style="color:#729ED5;background:#e7f5f7">退款</span>{{ record.refundOrderId }}</p>-->
<!-- <p style="margin-bottom: 0;">-->
<!-- <span style="color:#56cf56;background:#d8eadf">商户</span>-->
<!-- <a-tooltip v-if="record.mchRefundNo.length > record.refundOrderId.length" placement="bottom" style="font-weight: normal;">-->
<!-- <template #title>-->
<!-- <span>{{ record.mchRefundNo }}</span>-->
<!-- </template>-->
<!-- {{ changeStr2ellipsis(record.mchRefundNo, record.refundOrderId.length) }}-->
<!-- </a-tooltip>-->
<!-- <span v-else style="font-weight: normal;">{{ record.mchRefundNo }}</span>-->
<!-- </p>-->
<!-- </div>-->
<!-- </template>-->
<!-- <template v-if="column.key === 'ifCode'">-->
<!-- <span>{{ record.ifName }} ({{ record.ifCode }})</span>-->
<!-- </template>-->
<!-- <template v-if="column.key == 'op'">-->
<!-- &lt;!&ndash; 操作列插槽 &ndash;&gt;-->
<!-- <a-button v-if="$access('ENT_REFUND_ORDER_VIEW')" type="link" @click="detailFunc(record.refundOrderId)">详情</a-button>-->
<!-- </template>-->
<!-- </template>-->
<template #bodyCell="{column,record}">
<!-- <template v-if="column.key == 'payAmount'"><b>{{ record.payAmount/100 }}</b></template>-->
<template v-if="column.key == 'refundAmount'">
<a-tooltip>
<template #title>
<p>退款金额{{ record.payAmount/100 }}</p>
<p>手续费退还金额{{ record.refundFeeAmount/100 }}</p>
</template>
<b style="color: #1890ff">{{ record.refundAmount/100 }}</b>
</a-tooltip>
</template>
<template v-if="column.key === 'agentName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>服务商名称{{record.agentName}}</span><br>
<span>服务商号{{record.agentNo}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.agentName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'mchUserName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>用户名称{{record.mchUserName}}</span><br>
<span>用户手机号{{record.mchUserPhone}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.mchUserName}}</div>
</a-tooltip>
</template>
<!-- <template v-if="column.key == 'refundFeeAmount'"><b>{{ record.refundFeeAmount/100 }}</b></template>-->
<template v-if="column.key == 'state'">
<div>
<a-tag
:key="record.state"
:color="record.state === 0?'blue':record.state === 1?'orange':record.state === 2?'green':'volcano'"
>
{{ record.state === 0?'订单生成':record.state === 1?'退款中':record.state === 2?'退款成功':record.state === 3?'退款失败':record.state === 4?'任务关闭':'未知' }}
</a-tag>
<a-tooltip :title="record.errMsg" v-if="record.state == 3">
<question-circle-outlined style="color: #FF0000;"/>
</a-tooltip>
<a-tooltip title="快钱通道部分退款类型的退款金额将于第二个工作日凌晨4-5点到账" v-if="record.ifCode == 'kqpay' && record.state==2 && record.refundType ==2">
<question-circle-outlined style="color: #FF0000;"/>
</a-tooltip>
</div>
</template>
<template v-if="column.key == 'refundType'">
<div>
<a-tag
:key="record.state"
:color="record.refundType === 0?'blue':record.refundType === 1?'red':record.refundType === 2?'orange':'volcano'"
>
{{ record.refundType === 1?'全额退款':record.refundType === 2?'部分退款':'' }}
</a-tag>
</div>
</template>
<template v-if="column.key == 'extParam'">
<div>
<a-tag
:key="record.extParam"
:color="record.extParam === '1'?'blue':record.extParam === '2'?'orange':''"
>
{{ record.extParam === "1"?'收款商户号':record.extParam === "2"?'退款专用账户':'' }}
</a-tag>
</div>
</template>
<template v-if="column.key === 'appId'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>应用名称{{record.appName}}</span><br>
<span>应用ID{{record.appId}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.appName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'mchName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>商户名称{{record.mchName}}</span><br>
<span>商户号{{record.mchExtNo}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.mchName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'storeName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>门店名称{{record.storeName}}</span><br>
<span>门店编号{{record.storeId}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.storeName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'isvName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>渠道名称{{record.isvName}}</span><br>
<span>渠道号{{record.isvNo}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.isvName}}</div>
</a-tooltip>
</template>
<template v-if="column.key == 'refund'">
<a-tooltip>
<template #title>
<p>渠道{{ record.channelPayOrderNo }}</p>
</template>
<b style="color: #1890ff">{{ record.payOrderId }}</b>
</a-tooltip>
</template>
<template v-if="column.key == 'pay'">
<a-popover placement="top">
<template #content>
<p>商户退款单号{{ record.mchRefundNo }}</p>
<p>渠道退款单号{{ record.channelPayOrderNo }}</p>
<p>平台订单号{{ record.payOrderId }}</p>
</template>
<b style="color: #1890ff"> {{ record.refundOrderId??"--" }} </b>
</a-popover>
</template>
<template v-if="column.key == 'op'">
<!-- 操作列插槽 -->
<a-button v-if="$access('ENT_REFUND_ORDER_VIEW')" type="link" @click="detailFunc(record)">详情</a-button>
<!-- <a-button v-if="$access('ENT_REFUND_ORDER_VIEW')" type="link" @click="detailFunc(record)">详情</a-button>-->
</template>
</template>
</JeepayTable>
</a-card>
<!-- 日志详情抽屉 -->
<!-- <template>-->
<!-- <a-drawer-->
<!-- v-model:visible="vdata.visible"-->
<!-- width="50%"-->
<!-- placement="right"-->
<!-- :closable="true"-->
<!-- :title="vdata.visible === true? '退款订单详情':''"-->
<!-- @close="onClose"-->
<!-- >-->
<!-- <a-row justify="space-between" type="flex">-->
<!-- <a-col :sm="12">-->
<!-- <a-descriptions>-->
<!-- <a-descriptions-item label="所属系统">-->
<!-- {{ vdata.detailData.mchType === 1?'普通商户':vdata.detailData.mchType === 2?'特约商户':'未知' }}-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
<!-- </a-col>-->
<!-- <a-col :sm="12">-->
<!-- <a-descriptions>-->
<!-- <a-descriptions-item label="渠道商号">-->
<!-- {{ vdata.detailData.isvNo }}-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
<!-- </a-col>-->
<!-- <a-col :sm="12">-->
<!-- <a-descriptions>-->
<!-- <a-descriptions-item label="退款订单号">-->
<!-- <a-tag color="purple">-->
<!-- {{ vdata.detailData.refundOrderId }}-->
<!-- </a-tag>-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
<!-- </a-col>-->
<!-- <a-col :sm="12">-->
<!-- <a-descriptions>-->
<!-- <a-descriptions-item label="用户号">-->
<!-- {{ vdata.detailData.mchNo }}-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
<!-- </a-col>-->
<!-- <a-col :sm="12">-->
<!-- <a-descriptions>-->
<!-- <a-descriptions-item label="支付订单号">-->
<!-- {{ vdata.detailData.payOrderId }}-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
<!-- </a-col>-->
<!-- <a-col :sm="12">-->
<!-- <a-descriptions>-->
<!-- <a-descriptions-item label="商户退款单号">-->
<!-- {{ vdata.detailData.mchRefundNo }}-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
<!-- </a-col>-->
<!-- <a-col :sm="12">-->
<!-- <a-descriptions>-->
<!-- <a-descriptions-item label="渠道支付订单号">-->
<!-- {{ vdata.detailData.channelPayOrderNo }}-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
<!-- </a-col>-->
<!-- <a-col :sm="12">-->
<!-- <a-descriptions>-->
<!-- <a-descriptions-item label="应用APPID">-->
<!-- {{ vdata.detailData.appId }}-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
<!-- </a-col>-->
<!-- <a-col :sm="12">-->
<!-- <a-descriptions>-->
<!-- <a-descriptions-item label="支付金额">-->
<!-- <a-tag color="green">-->
<!-- {{ vdata.detailData.payAmount/100 }}-->
<!-- </a-tag>-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
<!-- </a-col>-->
<!-- <a-col :sm="12">-->
<!-- <a-descriptions>-->
<!-- <a-descriptions-item label="退款金额">-->
<!-- <a-tag color="green">-->
<!-- {{ vdata.detailData.refundAmount/100 }}-->
<!-- </a-tag>-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
<!-- </a-col>-->
<!-- <a-col :sm="12">-->
<!-- <a-descriptions>-->
<!-- <a-descriptions-item label="手续费退还金额">-->
<!-- <a-tag color="green">-->
<!-- {{ vdata.detailData.refundFeeAmount/100 }}-->
<!-- </a-tag>-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
<!-- </a-col>-->
<!-- <a-col :sm="12">-->
<!-- <a-descriptions>-->
<!-- <a-descriptions-item label="订单状态">-->
<!-- <a-tag :color="vdata.detailData.state === 0?'blue':vdata.detailData.state === 1?'orange':vdata.detailData.state === 2?'green':'volcano'">-->
<!-- {{ vdata.detailData.state === 0?'订单生成':vdata.detailData.state === 1?'退款中':vdata.detailData.state === 2?'退款成功':vdata.detailData.state === 3?'退款失败':vdata.detailData.state === 4?'任务关闭':'未知' }}-->
<!-- </a-tag>-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
<!-- </a-col>-->
<!-- <a-col :sm="24">-->
<!-- <a-descriptions>-->
<!-- <a-descriptions-item label="退款成功时间">-->
<!-- {{ vdata.detailData.successTime }}-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
<!-- </a-col>-->
<!-- <a-col :sm="12">-->
<!-- <a-descriptions>-->
<!-- <a-descriptions-item label="创建时间">-->
<!-- {{ vdata.detailData.createdAt }}-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
<!-- </a-col>-->
<!-- <a-col :sm="12">-->
<!-- <a-descriptions>-->
<!-- <a-descriptions-item label="更新时间">-->
<!-- {{ vdata.detailData.updatedAt }}-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
<!-- </a-col>-->
<!-- <a-divider />-->
<!-- <a-col :sm="12">-->
<!-- <a-descriptions>-->
<!-- <a-descriptions-item label="接口代码">-->
<!-- {{ vdata.detailData.ifCode }}-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
<!-- </a-col>-->
<!-- <a-col :sm="12">-->
<!-- <a-descriptions>-->
<!-- <a-descriptions-item label="货币代码">-->
<!-- {{ vdata.detailData.currency }}-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
<!-- </a-col>-->
<!-- <a-col :sm="12">-->
<!-- <a-descriptions>-->
<!-- <a-descriptions-item label="方式代码">-->
<!-- {{ vdata.detailData.wayCode }}-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
<!-- </a-col>-->
<!-- <a-col :sm="12">-->
<!-- <a-descriptions>-->
<!-- <a-descriptions-item label="客户端IP">-->
<!-- {{ vdata.detailData.clientIp }}-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
<!-- </a-col>-->
<!-- <a-col :sm="24">-->
<!-- <a-descriptions>-->
<!-- <a-descriptions-item label="异步通知地址">-->
<!-- {{ vdata.detailData.notifyUrl }}-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
<!-- </a-col>-->
<!-- </a-row>-->
<!-- <a-divider />-->
<!-- <a-col :sm="12">-->
<!-- <a-descriptions>-->
<!-- <a-descriptions-item label="渠道订单号">-->
<!-- {{ vdata.detailData.channelOrderNo }}-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
<!-- </a-col>-->
<!-- <a-col :sm="12">-->
<!-- <a-descriptions>-->
<!-- <a-descriptions-item label="渠道错误码">-->
<!-- {{ vdata.detailData.errCode }}-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
<!-- </a-col>-->
<!-- <a-col :sm="12">-->
<!-- <a-descriptions>-->
<!-- <a-descriptions-item label="渠道错误描述">-->
<!-- {{ vdata.detailData.errMsg }}-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
<!-- </a-col>-->
<!-- <a-col :sm="24">-->
<!-- <span style="color: black;">渠道额外参数:</span>-->
<!-- <a-form-item>-->
<!-- <a-input-->
<!-- v-model:value="vdata.detailData.channelExtra"-->
<!-- type="textarea"-->
<!-- disabled="disabled"-->
<!-- style="height: 100px;color: black;margin-top: 10px;"-->
<!-- />-->
<!-- </a-form-item>-->
<!-- </a-col>-->
<!-- <a-divider />-->
<!-- <a-col :sm="24">-->
<!-- <span style="color: black;">扩展参数:</span>-->
<!-- <a-form-item>-->
<!-- <a-input-->
<!-- v-model:value="vdata.detailData.extParam"-->
<!-- type="textarea"-->
<!-- disabled="disabled"-->
<!-- style="height: 100px;color: black;margin-top: 10px;"-->
<!-- />-->
<!-- </a-form-item>-->
<!-- </a-col>-->
<!-- <a-col :sm="24">-->
<!-- <span style="color: black;">备注:</span>-->
<!-- <a-form-item>-->
<!-- <a-input-->
<!-- v-model:value="vdata.detailData.remark"-->
<!-- type="textarea"-->
<!-- disabled="disabled"-->
<!-- style="height: 100px;color: black;margin-top: 10px;"-->
<!-- />-->
<!-- </a-form-item>-->
<!-- </a-col>-->
<!-- </a-drawer>-->
<!-- </template>-->
<RefundDetail ref="refundDetail" />
</page-header-wrapper>
</template>
<script lang="ts" setup>
import { API_URL_REFUND_ORDER_LIST, API_URL_AGENT_LIST, req, $exportExcel, exportExcelUrl, $getPayConfigIfcodes, $refundOrderCount } from '@/api/manage'
import moment from 'moment'
import fileDownload from 'js-file-download'
import {ref, onMounted, reactive, getCurrentInstance} from 'vue'
import RefundDetail from "./RefundDetail.vue";
const refundDetail = ref() // 对话框
// 获取全局函数
const { $infoBox,$access } = getCurrentInstance()!.appContext.config.globalProperties
// eslint-disable-next-line no-unused-vars
// const tableColumns = reactive([
// { key: 'pay', title: '退款订单号', scopedSlots: { customRender: 'refundOrderSlot' }, width: '260px' },
// { key: 'refund', title: '支付订单号', scopedSlots: { customRender: 'payOrderSlot' }, width: '260px' },
// { key: 'storeName', title: '门店名称', dataIndex: 'storeName', defaultHidden: true },
// { key: 'storeId', title: '门店ID', dataIndex: 'storeId', defaultHidden: true },
// { key: 'payAmount', title: '支付金额' },
// { key: 'refundAmount', title: '退款金额' },
// { key: 'refundFeeAmount', title: '手续费退还金额' },
// { key: 'mchName', title: '用户名称', dataIndex: 'mchName', ellipsis: true, },
// { key: 'ifCode', title: '支付接口'},
// { key: 'state', title: '支付状态', scopedSlots: { customRender: 'stateSlot' } },
// { key: 'createdAt', dataIndex: 'createdAt', title: '创建日期' },
// { key: 'op', title: '操作', width: '100px', fixed: 'right', align: 'center', scopedSlots: { customRender: 'opSlot' } }
// ])
const tableColumns = reactive([
// { key: 'pay', title: '退款订单号', scopedSlots: { customRender: 'refundOrderSlot' } },
{ key: 'pay', title: '退款订单号' },
{ key: 'mchUserName', title: '用户名称', dataIndex: 'mchUserName'},
{ key: 'agentName', title: '服务商名称', dataIndex: 'agentName' },
{ key: 'mchName', title: '商户名称', dataIndex: 'mchName' },
{ key: 'storeName', title: '门店名称', dataIndex: 'storeName' },
// { key: 'refund', title: '支付订单号', scopedSlots: { customRender: 'payOrderSlot' }, width: '260px' },
{ key: 'ifName', title: '支付通道', dataIndex: 'ifName',},
{ key: 'isvName', title: '所属渠道', dataIndex: 'isvName' },
{ key: 'appId', title: '所属应用', dataIndex: 'appId' },
// { key: 'payAmount', title: '支付金额' },
{ key: 'refundAmount', title: '退款金额(元)' },
// { key: 'refundFeeAmount', title: '手续费退还金额' },
// { key: 'refundOrderId', title: '退款订单号', dataIndex: 'refundOrderId' },
// { key: 'mchRefundNo', title: '商户退款单号', dataIndex: 'mchRefundNo' },
// { key: 'payOrderId', title: '支付订单号', dataIndex: 'payOrderId' },
// { key: 'channelPayOrderNo', title: '渠道订单号', dataIndex: 'channelPayOrderNo' },
{ key: 'state', title: '退款状态', scopedSlots: { customRender: 'stateSlot' } },
{ key: 'refundType', title: '退款类型', scopedSlots: { customRender: 'stateSlot' } },
{ key: 'extParam', title: '退款模式' },
{ key: 'createdAt', dataIndex: 'createdAt', title: '创建日期' },
{ key: 'op', title: '操作', width: '100px', fixed: 'right', align: 'center', scopedSlots: { customRender: 'opSlot' } }
])
const ifCodeRef = ref()
const dateRangePicker = ref()
const infoTable = ref()
const vdata :any = reactive({
btnLoading: false,
tableColumns: tableColumns,
searchData: {queryDateRange: 'today'} as any,
createdStart: '', // 选择开始时间
createdEnd: '', // 选择结束时间
visible: false,
detailData: {} as any,
agentList: [] as any, // 服务商下拉列表
})
onMounted(()=>{
if($access('ENT_AGENT_RATE_CONFIG')){
$getPayConfigIfcodes('AGENT', 'agentApplyment', '').then((res) => {
vdata.ifCodeList = res
})
}
getOrderCount()
})
let orderCountList:any = ref([]) // 数据统计数组
// 数据统计
const getOrderCount = () => {
if(!$access('ENT_REFUND_ORDER_COUNT')){
return false
}
$refundOrderCount(vdata.searchData).then( res => {
orderCountList.value = [
{title: '退款金额', symbol: 'add', textColor: '#1A66FF', content: (res.totalAmount / 100).toFixed(2), num:res.allCount },
{type: 'line'},
{title: '退款成功金额',symbol: 'add', textColor: '#389e0d',content: (res.totalRefundAmount / 100).toFixed(2), num:res.succesCount },
{type: 'line'},
{title: '手续费退款金额',symbol: 'add',textColor: '#FF0000', content: (res.totalRefundFeeAmt / 100).toFixed(2), num:res.feeCount},
]
})
}
function queryFunc () {
vdata.btnLoading = true
infoTable.value.refTable(true)
}
// 请求table接口数据
function reqTableDataFunc(params){
return req.list(API_URL_REFUND_ORDER_LIST, params)
}
function searchFunc() { // 点击【查询】按钮点击事件
infoTable.value.refTable(true)
getOrderCount()
}
function detailFunc(recordId) {
refundDetail.value.show(recordId)
// req.getById(API_URL_REFUND_ORDER_LIST, recordId).then(res => {
// vdata.detailData = res
// console.log(res)
//
// })
vdata.visible = true
}
function onClose () {
vdata.visible = false
}
function changeStr2ellipsis (orderNo, baseLength) {
const halfLengh = baseLength / 2
return orderNo.substring(0, halfLengh - 1) + '...' + orderNo.substring(orderNo.length - halfLengh, orderNo.length)
}
function tableExportFunc(){
return $exportExcel(exportExcelUrl.refundOrder, Object.assign({}, vdata.searchData, {'pageSize': -1})).then(res => {
fileDownload(res.data, '退款订单.xlsx')
}).catch ((error) =>{console.log(error)} )
}
function handleChange (value) {
for (let i = 0; i < vdata.agentList.length; i++) {
if (value === vdata.agentList[i]['agentNo']) {
vdata.searchData['isvNo'] = vdata.agentList[i]['isvNo']
}
}
}
req.list(API_URL_AGENT_LIST, { 'pageSize': -1, 'state': 1 }).then(res => { // 服务商下拉选择列表
vdata.agentList = res.records
})
let isReset = ref(0) // 下拉搜索框是否重置
function onReset(){
isReset.value++ // 下拉搜索框重置
//重置搜索内容
dateRangePicker.value.returnSelectModel()
vdata.searchData = { queryDateRange: 'today' }
}
</script>
<style lang="less" scoped>
.order-list {
-webkit-text-size-adjust:none;
font-size: 12px;
display: flex;
flex-direction: column;
p {
white-space:nowrap;
span {
display: inline-block;
font-weight: 800;
height: 16px;
line-height: 16px;
width: 35px;
border-radius: 5px;
text-align: center;
margin-right: 2px;
}
}
}
</style>

View File

@@ -0,0 +1,399 @@
<template>
<page-header-wrapper>
<a-card>
<JeepaySearchForm :searchFunc="searchFunc" :resetFunc="onReset">
<a-form-item label="" class="table-search-item">
<JeepayDateRangePicker ref="dateRangePicker" v-model:value="vdata.searchData['queryDateRange']" customDateRangeType="dateTime" />
</a-form-item>
<jeepay-text-up v-model:value="vdata.searchData.mchExtNo" :placeholder="'商户名称/商户号'" />
<jeepay-text-up v-model:value="vdata.searchData.ifCode" :placeholder="'支付通道'" />
<jeepay-text-up v-model:value="vdata.searchData.isvNo" :placeholder="'渠道名称/渠道号'" />
<jeepay-text-up v-model:value="vdata.searchData.appid" :placeholder="'应用名称/应用ID'" />
<!-- <jeepay-text-up v-model:value="vdata.searchData.nameAndNo" :placeholder="'结算姓名/结算卡号'" />-->
<!-- <a-form-item label="" class="table-search-item">-->
<!-- <a-select v-model:value="vdata.searchData.ifCode" placeholder="支付通道">-->
<!-- <a-select-option v-for="item in vdata.payDefines" :key="item.ifCode" :value="item.ifCode">{{ item.ifName }}</a-select-option>-->
<!-- </a-select>-->
<!-- </a-form-item>-->
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData.settleType" placeholder="结算类型">
<a-select-option value="">全部</a-select-option>
<a-select-option value="D0">D0</a-select-option>
<a-select-option value="D1">D1</a-select-option>
<a-select-option value="T1">T1</a-select-option>
<!-- <a-select-option value="4">定时结算</a-select-option>-->
</a-select>
</a-form-item>
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData.state" placeholder="结算状态">
<a-select-option value="">全部</a-select-option>
<a-select-option value="-1">结算失败</a-select-option>
<a-select-option value="0">待结算</a-select-option>
<a-select-option value="1">结算中</a-select-option>
<a-select-option value="2">结算成功</a-select-option>
<a-select-option value="3">暂缓</a-select-option>
<a-select-option value="4">冻结</a-select-option>
<a-select-option value="5">退票</a-select-option>
<a-select-option value="98">待查看</a-select-option>
<a-select-option value="99">其他</a-select-option>
</a-select>
</a-form-item>
</JeepaySearchForm>
<!-- 列表渲染 -->
<!-- :statisticsIsShow="$access('ENT_ORDER_COUNT')"-->
<JeepayTable
ref="infoTable"
:initData="true"
:reqTableDataFunc="reqTableDataFunc"
:tableColumns="tableColumns"
:searchData="vdata.searchData"
rowKey="payOrderId"
:tableRowCrossColor="true"
:statisticsIsShow="true"
:tableExportFunc="tableExportFunc"
@btnLoadClose="vdata.btnLoading=false"
>
<template #statistics>
<div class="statistics-list">
<div v-for="(item, index) in orderCountList" :key="index" class="item">
<div v-if="item.type == 'line'" class="line" />
<div class="title">{{ item.title }}</div>
<div v-if="item.title" class="amount" :style="{color: item.textColor}">
<span class="amount-num">{{ item.content }}</span>
</div>
<div v-if="item.count >= 0" class="detail">
<span>{{ item.count + '笔' }}</span>
</div>
</div>
</div>
</template>
<template #headerCell="{ column }">
<template v-if="column.key === 'settleType'">
<Tooltip color="#3b4146">
<template #title>
<div style="color: #ffffff">提示内容</div>
</template>
<div style="cursor: pointer; width: 100%">
结算类型
<a-tooltip>
<template #title>
<p>T1工作日次日到账交易资金于周末和法定节假日次日的6点-12点自动结算至银行卡</p>
<p>D1自然日次日到账全年365天不分节假日的交易资金于自然日的次日6点-12点自动结算至银行卡</p>
<p>D0实时到账全年365天24小时不分节假日的交易资金笔笔实时秒到至银行卡更放心更快捷</p>
<p>定时结算在一天24个整点时辰根据入账需求选择结算范围可任意指定一个或多个结算时间点</p>
</template>
<question-circle-outlined style="padding-left: 10px"/>
</a-tooltip>
</div>
</Tooltip>
</template>
</template>
<template #bodyCell="{column,record}">
<template v-if="column.key === 'appid'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>应用名称{{record.appName}}</span><br>
<span>应用ID{{record.appid}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.appName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'agentName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>服务商名称{{record.agentName}}</span><br>
<span>服务商号{{record.agentNo}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.agentName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'mchUserName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>用户名称{{record.mchUserName}}</span><br>
<span>用户手机号{{record.mchUserPhone}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.mchUserName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'mchName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>商户名称{{record.mchName}}</span><br>
<span>商户号{{record.mchExtNo}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.mchName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'isvNo'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>渠道名称{{record.isvName}}</span><br>
<span>渠道号{{record.isvNo}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.isvName}}</div>
</a-tooltip>
</template>
<template v-if="column.key == 'state'">
<a-tag
:key="record.state"
:color="record.state === -1?'red':record.state === 1?'orange':record.state === 2?'green':record.state === 6?'':'volcano'"
>
{{ record.state === -1?'结算失败':record.state === 0?'待结算':record.state === 1?'结算中':record.state === 2?'结算成功':record.state === 3?'暂缓':record.state === 4?'冻结':record.state === 5?'退票':record.state === 98?'待查看':record.state === 99?'待查看':'--' }}
</a-tag>
<a-tooltip :title="record.errMsg" v-if="record.state == 3">
<info-circle-outlined style="color: #FFD080;margin-left: 5px"/>
</a-tooltip>
<a-tooltip :title="record.remark" v-if="record.state == 98 && record.settleType == 'D0'">
<question-circle-outlined style="padding-left: 5px"/>
</a-tooltip>
<a-tooltip v-if="record.state == -1" :title="record.remark">
<info-circle-outlined style="font-size: 16px; padding-left: 5px;color: #FF0000"/>
</a-tooltip>
<!-- <reload-outlined v-if="$access('ENT_SETTLE_SYNC') && record.state < 2" @click="getReload(record)" style="color: #1890ff;margin-left: 5px" />-->
</template>
<template v-if="column.key == 'accountName'">
<!-- 操作列插槽 -->
<!-- <a-tooltip >-->
<!-- <template #title>-->
<!-- 卡号-->
<!-- </template>-->
<!-- -->
<!-- </a-tooltip>-->
{{record.accountName}}<br>
{{record.accountNo}}
</template>
<template v-if="column.key == 'settleAmt'">
<!-- 操作列插槽 -->
<a-tooltip >
<template #title>
<div v-if="record.state == 1 || record.state == 0">
<p>预付金额数据同步中</p>
<p>交易手续费数据同步中</p>
<p>垫资手续费数据同步中</p>
<p>结算金额数据同步中</p>
</div>
<div v-else>
<p>预付金额{{(record.planAmt / 100).toFixed(2)}}</p>
<p>交易手续费{{(record.fee / 100).toFixed(2)}}</p>
<p>垫资手续费{{(record.cashFee / 100).toFixed(2)}}</p>
<p>结算金额{{(record.settleAmt / 100).toFixed(2)}}</p>
</div>
</template>
<b style="color: #1890ff">{{ (record.settleAmt / 100).toFixed(2)}} </b>
</a-tooltip>
</template>
<template v-if="column.key == 'transactionAmount'">
{{ (record.transactionAmount / 100).toFixed(2)}}
</template>
<template v-if="column.key == 'fee'">
{{ (record.fee / 100).toFixed(2)}}
</template>
<template v-if="column.key == 'cashFee'">
{{ (record.cashFee / 100).toFixed(2)}}
</template>
<template v-if="column.key == 'settleName'">
<!-- 操作列插槽 -->
--
</template>
<template v-if="column.key == 'op'">
<!-- 操作列插槽 -->
</template>
</template>
</JeepayTable>
</a-card>
</page-header-wrapper>
</template>
<script lang="ts" setup>
import {
API_URL_MCH_LIST,
API_URL_PAY_ORDER_LIST,
API_URL_PAYWAYS_LIST,
req,
$exportExcel,
exportExcelUrl,
$payOrderCount,
API_MCH_PAY_SETTLE_LOG, API_MCH_PAY_DEFINES, $settleOrderCount, $getPayOrderRefresh, $getSettTypeSync
} from '@/api/manage'
import {ref, onMounted, reactive, getCurrentInstance} from 'vue'
import fileDownload from 'js-file-download'
import { message } from 'ant-design-vue'
// 获取全局函数
const { $hasMemberEnt,$access } = getCurrentInstance()!.appContext.config.globalProperties
const tableColumns = reactive([
{ key: 'mchUserName', title: '用户名称', dataIndex: 'mchUserName'},
{ key: 'agentName', title: '服务商名称', dataIndex: 'agentName' },
{ key: 'mchName', title: '商户名称', dataIndex: 'mchName' },
{ key: 'ifName', title: '支付通道', dataIndex: 'ifName',},
{ key: 'isvNo', title: '所属渠道', dataIndex: 'isvNo',},
{ key: 'appid', title: '应用名称', dataIndex: 'appid' },
{ key: 'accountName', title: '结算账户',fixed: "left", dataIndex: 'accountName', align: 'center'},
// { key: 'transactionNumber', title: '成功交易笔数', dataIndex: 'transactionNumber', align: 'center'},
// { key: 'transactionAmount', title: '成功交易金额', dataIndex: 'transactionAmount', align: 'center'},
// { key: 'fee', title: '交易手续费', dataIndex: 'fee', align: 'center'},
// { key: 'cashFee', title: '垫资手续费', dataIndex: 'cashFee', align: 'center'},
{ key: 'settleAmt', title: '结算金额(元)', dataIndex: 'settleAmt', align: 'center' },
{ key: 'settleType', title: '结算类型', dataIndex: 'settleType', align: 'center'},
// { key: 'remark', title: '附言信息', dataIndex: 'remark' },
{ key: 'updatedAt', dataIndex: 'updatedAt', title: '结算日期' },
// { key: 'createdAt', dataIndex: 'createdAt', title: '结算日期' },
{ key: 'state', title: '结算状态',fixed: "right",},
])
const infoTable = ref()
const dateRangePicker = ref()
const vdata = reactive({
btnLoading: false,
tableColumns: tableColumns,
searchData: {queryDateRange: 'today'} as any,
visible: false,
detailData: {} as any,
payWayList: [] as any,
channelList:[] as any,
payDefines: [] as any,
orderKeyType: 'payOrderId'
})
onMounted(()=>{
if ($access('ENT_PAY_ORDER_SEARCH_PAY_WAY')) {
initPayWay()
}
})
let orderCountList:any = ref([]) // 数据统计数组
let countDetailList: any = ref([]) // 数据统计明细数组
// 数据统计
const getOrderCount = () => {
$settleOrderCount(vdata.searchData).then( res => {
orderCountList.value = [
{title: '成功交易金额',symbol: 'add', textColor: '#1A66FF', content: ((res.successAmount) / 100).toFixed(2), count: res.successAll},
{type: 'line'},
{title: '交易手续费',symbol: 'add',textColor: '#1A66FF', content: (res.feeAmount / 100).toFixed(2), count: res.feeAll},
{type: 'line'},
{title: '垫资手续费',symbol: 'add',textColor: '#1A66FF', content: (res.cashFeeAmount / 100).toFixed(2), count: res.cashFeeAll},
{type: 'line'},
{title: '结算金额', symbol: 'add',textColor: '#1A66FF', content: (res.allAmount / 100).toFixed(2), count: res.allSettle},
{type: 'line'},
{title: '待结算金额',symbol: 'add',textColor: '#1A66FF', content: (res.waitAmount / 100).toFixed(2), count: res.waitAll},
]
})
}
getOrderCount() // 进入页面时调用一次
getChannelList()
payFefines();
//通道
function payFefines() {
req.list(API_MCH_PAY_DEFINES,{ 'pageSize': -1 }).then(res => {
vdata.payDefines = res
})
}
// 请求table接口数据
function reqTableDataFunc(params){
return req.list(API_MCH_PAY_SETTLE_LOG, params)
}
function searchFunc(){ // 点击【查询】按钮点击事件
infoTable.value.refTable(true)
getOrderCount() // 数据统计函数
}
// 打开退款弹出框
function openFunc (record, recordId) {
if (record.refundState === 2) {
return message.error('订单无可退款金额')
}
}
function detailFunc(recordId) {
req.getById(API_MCH_PAY_SETTLE_LOG, recordId).then(res => {
vdata.detailData = res
})
vdata.visible = true
}
function onClose () {
vdata.visible = false
}
function initPayWay () {
req.list(API_URL_PAYWAYS_LIST, { 'pageSize': -1 }).then(res => { // 支付方式下拉列表
vdata.payWayList = res.records
})
}
function changeStr2ellipsis (orderNo, baseLength) {
const halfLengh = baseLength / 2
return orderNo.substring(0, halfLengh - 1) + '...' + orderNo.substring(orderNo.length - halfLengh, orderNo.length)
}
function tableExportFunc(){
return $exportExcel(exportExcelUrl.settleOrder as any, Object.assign({}, vdata.searchData, {'pageSize': -1})).then(res => {
fileDownload(res.data, '结算订单列表.xlsx')
}).catch ((error) =>{console.log(error)} )
}
function onReset(){
//重置搜索内容
dateRangePicker.value.returnSelectModel()
vdata.searchData = { queryDateRange: 'today' }
}
function orderKeyTypeChangeFunc() {
vdata.searchData.payOrderId = ''
vdata.searchData.mchOrderNo = ''
vdata.searchData.channelOrderNo = ''
vdata.searchData.platformMchOrderNo = ''
vdata.searchData.platformOrderNo = ''
}
function getChannelList(){
// req.list(API_URL_MCH_APP, { 'pageSize': -1 }).then(res => { // 支付方式下拉列表
// vdata.channelList = res.records
// })
}
function getReload(data) {
$getSettTypeSync(data.settleNo).then(res => {
message.success('同步成功')
infoTable.value.refTable(true)
})
}
</script>
<style lang="less" scoped>
.order-list {
-webkit-text-size-adjust:none;
font-size: 12px;
display: flex;
flex-direction: column;
p {
white-space:nowrap;
span {
display: inline-block;
font-weight: 800;
height: 16px;
line-height: 16px;
width: 35px;
border-radius: 5px;
text-align: center;
margin-right: 2px;
}
}
}
</style>

View File

@@ -0,0 +1,475 @@
<template>
<div>
<a-card style="box-sizing:border-box;padding:30px">
<!-- 选择下单的应用列表 -->
<a-form>
<div style="display:flex;flex-direction:row">
<!-- <p style="margin-top:9px;margin-right:10px;"></p> -->
<a-form-item label="" class="table-head-layout">
<span>应用</span>
<a-select v-model:value="vdata.appId" style="width:300px" @change="changeAppId">
<a-select-option key="">应用AppId</a-select-option>
<a-select-option v-for="(item) in vdata.mchAppList" :key="item.appId">{{ item.appName }} [{{ item.appId }}]</a-select-option>
</a-select>
<span style="margin-left: 30px;">门店</span>
<a-select v-model:value="vdata.storeId" style="width:300px;">
<a-select-option key="">选择门店</a-select-option>
<a-select-option v-for="(item) in vdata.mchStoreList" :key="item.storeId">{{ item.storeName }} [{{ item.storeId }}]</a-select-option>
</a-select>
</a-form-item>
</div>
</a-form>
<!-- 未配置支付方式提示框 -->
<a-divider v-if="!vdata.appId">请选择应用AppId</a-divider>
<a-divider v-else-if="vdata.noConfigText">您尚未配置任何支付方式</a-divider>
<a-divider v-else />
<!-- 支付测试面板 v-if=""-->
<div v-if="payTestShow()" style="width: 100%;" class="paydemo">
<div class="paydemo-type-content">
<div v-show="showTitle('WX')" class="paydemo-type-name article-title">微信支付</div>
<div class="paydemo-type-body">
<div
v-show="vdata.appPaywayList.indexOf('WX_NATIVE') >= 0"
class="paydemo-type color-change"
:class="{this:(vdata.currentWayCode === 'WX_NATIVE')}"
@click="changeCurrentWayCode('WX_NATIVE', 'codeImgUrl')"
>
<img src="@/assets/payTestImg/wx_native.svg" class="paydemo-type-img"><span class="color-change">微信二维码</span>
</div>
<div
v-show="vdata.appPaywayList.indexOf('WX_BAR') >= 0"
class="paydemo-type color-change"
:class="{this:(vdata.currentWayCode === 'WX_BAR')}"
@click="changeCurrentWayCode('WX_BAR', '')"
>
<img src="@/assets/payTestImg/wx_bar.svg" class="paydemo-type-img"><span class="color-change">微信条码</span>
</div>
<div
v-show="vdata.appPaywayList.indexOf('WX_JSAPI') >= 0"
class="paydemo-type color-change"
:class="{this:(vdata.currentWayCode === 'WX_JSAPI')}"
@click="changeCurrentWayCode('WX_JSAPI', 'codeImgUrl')"
>
<img src="@/assets/payTestImg/wx_jsapi.svg" class="paydemo-type-img"><span class="color-change">公众号/小程序</span>
</div>
<div
v-show="vdata.appPaywayList.indexOf('WX_H5') >= 0"
class="paydemo-type-h5"
:class="{this:(vdata.currentWayCode === 'WX_H5')}"
@click="changeCurrentWayCode('WX_H5', 'payurl')"
>
<img src="@/assets/payTestImg/wx_h5.svg" class="paydemo-type-img"><span class="color-change">微信H5</span>
</div>
</div>
<div v-show="showTitle('ALI')" class="paydemo-type-name article-title">支付宝支付</div>
<div class="paydemo-type-body">
<div
v-show="vdata.appPaywayList.indexOf('ALI_QR') >= 0"
class="paydemo-type color-change"
:class="{this:(vdata.currentWayCode === 'ALI_QR')}"
@click="changeCurrentWayCode('ALI_QR', 'codeImgUrl')"
>
<img src="@/assets/payTestImg/ali_qr.svg" class="paydemo-type-img"><span class="color-change">支付宝二维码</span>
</div>
<div
v-show="vdata.appPaywayList.indexOf('ALI_BAR') >= 0"
class="paydemo-type color-change"
:class="{this:(vdata.currentWayCode === 'ALI_BAR')}"
@click="changeCurrentWayCode('ALI_BAR', '')"
>
<img src="@/assets/payTestImg/ali_bar.svg" class="paydemo-type-img"><span class="color-change">支付宝条码</span>
</div>
<div
v-show="vdata.appPaywayList.indexOf('ALI_JSAPI') >= 0"
class="paydemo-type color-change"
:class="{this:(vdata.currentWayCode === 'ALI_JSAPI')}"
@click="changeCurrentWayCode('ALI_JSAPI', 'codeImgUrl')"
>
<img src="@/assets/payTestImg/ali_jsapi.svg" class="paydemo-type-img"><span class="color-change">支付宝生活号</span>
</div>
<div
v-show="vdata.appPaywayList.indexOf('ALI_PC') >= 0"
class="paydemo-type color-change"
:class="{this:(vdata.currentWayCode === 'ALI_PC')}"
@click="changeCurrentWayCode('ALI_PC', 'payurl')"
>
<img src="@/assets/payTestImg/ali_pc.svg" class="paydemo-type-img"><span class="color-change">支付宝PC网站</span>
</div>
<div>
<div
v-show="vdata.appPaywayList.indexOf('ALI_WAP') >= 0"
class="paydemo-type-h5"
:class="{this:(vdata.currentWayCode === 'ALI_WAP')}"
@click="changeCurrentWayCode('ALI_WAP', 'payurl')"
>
<img src="@/assets/payTestImg/ali_wap.svg" class="paydemo-type-img"><span class="color-change">支付宝WAP</span>
</div>
</div>
</div>
<div v-show="showTitle('WX')" class="paydemo-type-name article-title">银联支付</div>
<div class="paydemo-type-body">
<div
v-show="vdata.appPaywayList.indexOf('UPACP_QR') >= 0"
class="paydemo-type color-change"
:class="{this:(vdata.currentWayCode === 'UPACP_QR')}"
@click="changeCurrentWayCode('UPACP_QR', 'codeImgUrl')"
>
<img src="@/assets/payTestImg/upacp_qr.svg" class="paydemo-type-img"><span class="color-change">银联二维码</span>
</div>
<div
v-show="vdata.appPaywayList.indexOf('UPACP_BAR') >= 0"
class="paydemo-type color-change"
:class="{this:(vdata.currentWayCode === 'UPACP_BAR')}"
@click="changeCurrentWayCode('UPACP_BAR', '')"
>
<img src="@/assets/payTestImg/upacp_bar.svg" class="paydemo-type-img"><span class="color-change">银联条码</span>
</div>
<div
v-show="vdata.appPaywayList.indexOf('UPACP_WAP') >= 0"
class="paydemo-type-h5"
:class="{this:(vdata.currentWayCode === 'UPACP_WAP')}"
@click="changeCurrentWayCode('UPACP_WAP', 'payurl')"
>
<img src="@/assets/payTestImg/upacp_wap.svg" class="paydemo-type-img"><span class="color-change">银联手机网站支付</span>
</div>
<div
v-show="vdata.appPaywayList.indexOf('UPACP_PC') >= 0"
class="paydemo-type color-change"
:class="{this:(vdata.currentWayCode === 'UPACP_PC')}"
@click="changeCurrentWayCode('UPACP_PC', 'payurl')"
>
<img src="@/assets/payTestImg/upacp_pc.svg" class="paydemo-type-img"><span class="color-change">银联网关支付</span>
</div>
<div
v-show="vdata.appPaywayList.indexOf('UPACP_B2B') >= 0"
class="paydemo-type color-change"
:class="{this:(vdata.currentWayCode === 'UPACP_B2B')}"
@click="changeCurrentWayCode('UPACP_B2B', 'payurl')"
>
<img src="@/assets/payTestImg/upacp_b2b.svg" class="paydemo-type-img"><span class="color-change">银联企业网银支付</span>
</div>
</div>
<div v-show="showQtTitle()" class="paydemo-type-name article-title">其它支付</div>
<div class="paydemo-type-body">
<div
v-show="vdata.appPaywayList.indexOf('WX_JSAPI') >= 0 || vdata.appPaywayList.indexOf('ALI_JSAPI') >= 0"
class="paydemo-type color-change"
:class="{this:(vdata.currentWayCode === 'QR_CASHIER')}"
@click="changeCurrentWayCode('QR_CASHIER', 'codeImgUrl')"
>
<img src="@/assets/payTestImg/qr_cashier.svg" class="paydemo-type-img"><span class="color-change">聚合主扫</span>
</div>
<div
v-show="vdata.appPaywayList.indexOf('WX_BAR') >= 0 || vdata.appPaywayList.indexOf('ALI_BAR') >= 0 || vdata.appPaywayList.indexOf('UPACP_BAR') >= 0"
class="paydemo-type color-change"
:class="{this:(vdata.currentWayCode === 'AUTO_BAR')}"
@click="changeCurrentWayCode('AUTO_BAR', 'codeImgUrl')"
>
<img src="@/assets/payTestImg/auto_bar.svg" class="paydemo-type-img"><span class="color-change">聚合被扫</span>
</div>
<div
v-show="vdata.appPaywayList.indexOf('PP_PC') >= 0"
class="paydemo-type color-change"
:class="{this:(vdata.currentWayCode === 'PP_PC')}"
@click="changeCurrentWayCode('PP_PC', 'payurl')"
>
<img src="@/assets/payTestImg/pp_pc.svg" class="paydemo-type-img"><span class="color-change">PayPal支付</span>
</div>
</div>
</div>
<a-divider />
<!-- 订单信息 -->
<div class="paydemo-type-content">
<div class="paydemo-type-name article-title">支付信息</div>
<form class="layui-form">
<div class="paydemo-form-item">
<label>订单编号</label><span id="payMchOrderNo">{{ vdata.mchOrderNo }}</span>
<span class=" paydemo-btn" style="padding:0 3px" @click="randomOrderNo">刷新订单号</span>
</div>
<div class="paydemo-form-item">
<label>订单标题</label>
<a-input v-model:value="vdata.orderTitle" style="width: 200px" />
</div>
<div class="paydemo-form-item">
<label>分账方式</label>
<a-radio-group v-model:value="vdata.divisionMode" style="display:flex">
<div style="display:flex">
<a-radio :value="0">订单不分账</a-radio>
<a-radio :value="1">支付完成自动分账</a-radio>
<a-radio :value="2">手动分账冻结商户资金 只能通过API发起分账后解冻</a-radio>
</div>
</a-radio-group>
</div>
<a-divider />
<div class="paydemo-form-item">
<span>支付金额()</span>
<a-radio-group v-model:value="vdata.divisionMode1" name="radioGroup" :default-value="0.01" style="display:flex">
<div style="display:flex" @click="vdata.amountInput=false">
<a-radio :value="0.01" @click="vdata.paytestAmount=0.01">0.01</a-radio>
<a-radio :value="0.15" @click="vdata.paytestAmount=0.15">0.15</a-radio>
<a-radio :value="0.21" @click="vdata.paytestAmount=0.21">0.21</a-radio>
<a-radio :value="0.29" @click="vdata.paytestAmount=0.29">0.29</a-radio>
<a-radio :value="0.64" @click="vdata.paytestAmount=0.64">0.64</a-radio>
</div>
<a-radio @click="amountInputShow">
<span style="margin-right:3px">自定义金额</span>
<a-input-number
v-show="vdata.amountInput"
ref="amountInputFocus"
v-model:value="vdata.paytestAmount"
:max="100000"
:min="0.01"
:precision="2"
/>
</a-radio>
</a-radio-group>
</div>
<div style="margin-top:20px;text-align: left">
<!-- <span style="color: #FD482C;font-size: 18px;padding-right: 10px;" id="amountShow">{{ paytestAmount }}</span> -->
<a-button v-if = "$access('ENT_MCH_PAY_TEST_DO')" style="padding:5px 20px;background-color: #1953ff;border-radius: 5px;color:#fff" @click="immediatelyPay">立即支付</a-button>
</div>
</form>
</div>
</div>
</a-card>
<!-- 二维码弹窗 -->
<pay-test-modal ref="payTestModal" @closeBarCode="payTestBarCode.value.visible = false" />
<!-- 条码弹框 -->
<pay-test-bar-code ref="payTestBarCode" @barCodeValue="barCodeChange" @CodeAgainChange="testCodeChange" />
</div>
</template>
<script setup lang="ts">
import { API_URL_MCH_APP, API_URL_MCH_STORE_LIST, req, $payTest, $payTestOrder } from '@/api/manage' // 接口
import PayTestModal from './PayTestModal.vue' // 二维码对话框组件
import PayTestBarCode from './PayTestBarCode.vue' // 条码对话框组件
import {ref, onMounted, reactive, getCurrentInstance,nextTick} from 'vue'
import {useRoute} from 'vue-router'
// 获取全局函数
const { $infoBox,$access } = getCurrentInstance()!.appContext.config.globalProperties
const route = useRoute()
const vdata = reactive({
mchAppList: [] as any, // app列表
mchStoreList: [] as any, // 门店列表
appId: '', // 已选择的appId
storeId: '', // 已选择的门店ID
appPaywayList: [] as any, // 商户app支持的支付方式
currentWayCode: '', // 以何种方式进行支付,默认是微信二维码
currentPayDataType: '', // 支付参数
mchOrderNo: '', // 模拟商户订单号
authCode: '', // 条码的值
paytestAmount: 0.01, // 支付金额默认为0.01
amountInput: false, // 自定金额输入框是否展示
noConfigText: false, // 尚无任何配置分割线提示文字
divisionMode: 0, // 订单分账模式
orderTitle: '接口调试', // 订单标题
divisionMode1:0.01 //默认支付金额绑定
})
const payTestBarCode =ref()
const payTestModal = ref()
const amountInputFocus = ref()
onMounted(()=>{
// 获取传入的参数如果参数存在则为appId 重新赋值
const appId = route.params.appId as string
console.log(appId)
if (appId) {
vdata.appId = appId // appId赋值
appPaywayListHandle(appId) // 调用appPaywayListHandle展示支付方式
}
// 请求接口获取所有的appid只有此处进行pageSize=-1传参
req.list(API_URL_MCH_APP, { pageSize: -1 }).then(res => {
vdata.mchAppList = res.records
if (vdata.mchAppList.length > 0) {
// 赋予默认值
//vdata.appId = vdata.mchAppList[0].appId
//TODO
if(vdata.appId == '') vdata.appId = vdata.mchAppList[0].appId
console.log(vdata.appId)
// 根据appId的值动态显示支付方式
appPaywayListHandle(vdata.appId)
}
})
// 请求接口获取商户所有门店只有次数进行pageSize=-1传参
req.list(API_URL_MCH_STORE_LIST, { pageSize: -1 }).then(res => {
vdata.mchStoreList = res.records
if (vdata.mchStoreList.length > 0) {
// 赋予默认值
if(vdata.storeId == '') vdata.storeId = vdata.mchStoreList[0].storeId
}
})
// 在进入页面时刷新订单号
randomOrderNo()
})
// 支付板块是否展示
function payTestShow () {
// 如果未选择appid或者支付方式列表为0则不显示支付体验板块
if (vdata.appId === '' || vdata.appPaywayList.length === 0) {
return false
} else {
return true
}
}
function changeCurrentWayCode (wayCode, currentPayDataType) { // 切换支付方式
vdata.currentWayCode = wayCode
vdata.currentPayDataType = currentPayDataType
}
// 变更 appId的事件
function changeAppId (value) {
appPaywayListHandle(value) // 根据appId的值动态显示支付方式
}
// 刷新订单号
function randomOrderNo () {
vdata.mchOrderNo = 'M' + new Date().getTime() + Math.floor(Math.random() * (9999 - 1000) + 1000)
}
// 获取条码的值
function barCodeChange (value) {
vdata.authCode = value
immediatelyPay()
}
// 根据不同的appId展示不同的支付方式(在下拉框切换时和在携带参数进入页面时调用)
function appPaywayListHandle (value) {
if (!value) {
vdata.appPaywayList = []
return false
}
$payTest(value).then(res => {
vdata.appPaywayList = res
if (res.length === 0) {
vdata.noConfigText = true
} else {
vdata.noConfigText = false
}
})
}
// 立即支付按钮
function immediatelyPay () {
// 判断支付金额是否为0
if (!vdata.paytestAmount || vdata.paytestAmount === 0.00) {
return $infoBox.message.error('请输入支付金额')
}
// 判断是否选择支付方式
if (vdata.currentWayCode === '') {
return $infoBox. message.error('请选择支付方式')
}
// 判断是否选择门店
if (vdata.storeId === '') {
return $infoBox. message.error('请选择一个门店')
}
// 请输入订单标题
if (!vdata.orderTitle || vdata.orderTitle.length > 20) {
return $infoBox. message.error('请输入正确的订单标题[20字以内]')
}
// 判断是否为条码支付
if (!payTestBarCode.value.getVisible() && (vdata.currentWayCode === 'WX_BAR' || vdata.currentWayCode === 'ALI_BAR' || vdata.currentWayCode === 'UPACP_BAR' || vdata.currentWayCode === 'AUTO_BAR')) {
payTestBarCode.value.showModal()
return
}
$payTestOrder({
// jsapi 默认使用聚合二维码支付
wayCode: (vdata.currentWayCode === 'WX_JSAPI' || vdata.currentWayCode === 'ALI_JSAPI') ? 'QR_CASHIER' : vdata.currentWayCode, // 支付方式
amount: vdata.paytestAmount, // 支付金额
appId: vdata.appId, // appId
storeId: vdata.storeId, // storeId
mchOrderNo: vdata.mchOrderNo, // 订单编号
payDataType: vdata.currentPayDataType, // 支付参数(二维码,条码)
authCode: vdata.authCode,
divisionMode: vdata.divisionMode,
orderTitle: vdata.orderTitle
}).then(res => {
payTestModal.value.showModal(vdata.currentWayCode, res) // 打开弹窗
randomOrderNo() // 刷新订单号
}).catch(() => {
payTestBarCode.value.processCatch()
randomOrderNo() // 刷新订单号
})
}
// 此处判断,微信,支付宝,聚合码,哪种支付方式一个都没配置,如果未配置,则不显示该板块,若等于-1 则表示不存在
function showTitle (parameterA) {
if (vdata.appPaywayList.toString().indexOf(parameterA) === -1) {
return false
} else {
return true
}
}
// 其它支付标题显示
function showQtTitle () {
if (vdata.appPaywayList.toString().indexOf('WX') !== -1 || vdata.appPaywayList.toString().indexOf('ALI') !== -1 || vdata.appPaywayList.toString().indexOf('PP') !== -1) {
return true
} else {
return false
}
}
// 自定义金额输入框是否展示
function amountInputShow () {
nextTick(() => { // 输入框默认展示焦点
amountInputFocus.value.focus()
})
vdata.amountInput = true
vdata.paytestAmount = 0
}
// 条码弹窗点击x或者蒙版关闭
function testCodeChange () {
randomOrderNo() // 刷新订单号
}
// handleCloseBarCode () {
// this.$refs.payTestBarCode.visible = false
// }
</script>
<style scoped lang="css">
@import './payTest.css';
</style>

View File

@@ -0,0 +1,67 @@
<template>
<div>
<a-modal
v-model:visible="vdata.visible"
title="条码支付"
:footer="null"
:width="350"
@cancel="handleChose"
>
<div>
<p>请输入用户条形码:</p>
<div style="display:flex;flex-direction:row;margin-bottom:14px;">
<a-input ref="barCodeInput" v-model:value="vdata.barCodeValue" @keyup.enter="handleOk" />
<a-button type="primary" style="margin-left:10px;" :loading="vdata.loading" @click="handleOk">确认支付</a-button>
</div>
<p>或者使用(扫码枪/扫码盒)扫码:</p>
<div style="text-align:center">
<img src="@/assets/payTestImg/scan.svg" alt="">
</div>
</div>
</a-modal>
</div>
</template>
<script lang="ts" setup>
import {ref, onMounted, reactive, getCurrentInstance,nextTick} from 'vue'
// 获取全局函数
const { $infoBox,$access } = getCurrentInstance()!.appContext.config.globalProperties
const barCodeInput =ref()
const emit = defineEmits(['barCodeValue','CodeAgainChange'])
const vdata = reactive({
visible: false,
barCodeValue: '', // 条码的值
loading: false // 按钮的loading状态
})
defineExpose({showModal,getVisible,processCatch})
function showModal () {
vdata.loading = false
vdata.barCodeValue = ''// 清空条码的值
vdata.visible = true
nextTick(() => { // 弹窗展示后,输入框默认展示焦点
barCodeInput.value.focus()
})
}
// 按钮的点击事件,当使用扫码设备扫码后,也会自动吊起该事件
function handleOk () {
if (vdata.barCodeValue === '') {
return
}
// 传递条码值给父组件
vdata.loading = true
emit('barCodeValue', vdata.barCodeValue)
}
function handleChose () {
// 点击×关闭或者点击蒙版关闭时设置父组件barCodeAgain的值为false
emit('CodeAgainChange')
}
function getVisible () {
return vdata.visible
}
function processCatch () {
vdata.loading = false
}
</script>

View File

@@ -0,0 +1,159 @@
<template>
<div>
<a-modal v-model:visible="vdata.visible" title="等待支付" :footer="null" :width="300" @ok="handleClose">
<div style="width:100%;margin-bottom:20px;text-align:center">
<img v-if="vdata.apiRes.payDataType == 'codeImgUrl'" :src="vdata.apiRes.payData" alt="">
<span v-else-if="vdata.apiRes.payDataType == 'payurl'">等待用户支付
<hr> 如浏览器未正确跳转请点击 <a :href="vdata.apiRes.payData" target="_blank">支付地址</a>
<a-button size="small" class="copy-btn" @click="onCopy">复制链接</a-button>
</span>
<span v-else>等待用户支付,请稍后</span>
</div>
<p class="describe">
<img v-show="vdata.wxApp" src="@/assets/payTestImg/wx_app.svg" alt=""><!-- 微信图标 -->
<img v-show="vdata.aliApp" src="@/assets/payTestImg/ali_app.svg" alt=""><!-- 支付宝图标 -->
<span>{{ vdata.payText }}</span>
</p>
</a-modal>
</div>
</template>
<script setup lang="ts">
import ReconnectingWebSocket from 'reconnectingwebsocket'
import { $getWebSocketPrefix } from '@/api/manage'
import {ref, onMounted, reactive, getCurrentInstance} from 'vue'
import { message, Modal } from 'ant-design-vue'
import useClipboard from 'vue-clipboard3'
// 获取全局函数
const { $infoBox,$access } = getCurrentInstance()!.appContext.config.globalProperties
const { toClipboard } = useClipboard()
const vdata = reactive({
visible: false,
payText: '', // 二维码底部描述文字
wxApp: false, // 微信二维码图片是否展示
aliApp: false, // 支付宝二维码图片是否展示
apiRes: {} as any, // 接口返回数据包
payOrderWebSocket: null as any// 支付订单webSocket对象
})
const emit = defineEmits(['closeBarCode'])
function onCopy () {
// $infoBox.message.success('复制成功')
const Msg = vdata.apiRes.payData
copy(Msg)
}
const copy = async (Msg) => {
try {
//复制
await toClipboard(Msg)
console.log(Msg,123)
//下面可以设置复制成功的提示框等操作
$infoBox.message.success('复制成功')
} catch (e) {
//复制失败
console.error(e)
}
}
defineExpose({showModal})
// 二维码以及条码弹窗
function showModal (wayCode, apiRes) {
// 关闭上一个webSocket监听
if (vdata.payOrderWebSocket) {
vdata.payOrderWebSocket.close()
}
vdata.apiRes = apiRes
vdata.wxApp = false
vdata.aliApp = false
vdata.visible = true // 打开弹窗
// 根据不同的支付方式,展示不同的信息
vdata.payText = ''
if (wayCode === 'WX_NATIVE' || wayCode === 'WX_JSAPI') { // 微信二维码
vdata.wxApp = true
vdata.payText = '请使用微信"扫一扫"扫码支付'
} else if (wayCode === 'ALI_QR' || wayCode === 'ALI_JSAPI') { // 支付宝二维码
vdata.aliApp = true
vdata.payText = '请使用支付宝"扫一扫"扫码支付'
} else if (wayCode === 'QR_CASHIER') { // 聚合支付二维码
vdata.wxApp = true
vdata.aliApp = true
vdata.payText = '支持微信、支付宝扫码'
}
// 此处判断接口中返回的orderState值为01 代表支付中直接放行无需处理2 成功 3 失败
if (apiRes.orderState === 2 || apiRes.orderState === 3) {
if (apiRes.orderState === 2) {
handleClose()
const succModal = Modal.success({
title:'支付成功',
content:'2秒后自动关闭'
})
// message.success('支付成功')
setTimeout(() => { succModal.destroy() }, 2000)
emit('closeBarCode') // 关闭条码框
} else if (apiRes.orderState === 3) {
handleClose()
console.log(1111111)
Modal.error({
title:'支付失败',
content:`错误码:${apiRes.errCode}`
})
// message.error(`支付失败,${apiRes.errCode}`)
emit('closeBarCode') // 关闭条码框
}
return
}
// h5 或者 wap
if (wayCode === 'WX_H5' || wayCode === 'ALI_WAP') {
vdata.payText = '请复制链接到手机端打开'
} else {
// 跳转到PC网站
if (apiRes.payDataType === 'payurl') {
window.open(apiRes.payData)
}
}
// 如果上面未关闭条码框则代表进入webScoket那么先在此处关闭条码框
emit('closeBarCode') // 关闭条码框
// 监听响应结果
vdata.payOrderWebSocket = new ReconnectingWebSocket($getWebSocketPrefix() + '/api/anon/ws/payOrder/' + apiRes.payOrderId + '/' + new Date().getTime())
vdata.payOrderWebSocket.onopen = () => {}
vdata.payOrderWebSocket.onmessage = (msgObject) => {
const resMsgObject = JSON.parse(msgObject.data)
if (resMsgObject.state === 2) {
handleClose()
// const succModal = $infoBox.modalSuccess('支付成功', '2s后自动关闭')
const succModal = Modal.success({
title:'支付成功',
content:'2秒后自动关闭'
})
setTimeout(() => { succModal.destroy() }, 2000)
} else {
handleClose()
// TODO 待更改 that.$infoBox.modalError('支付失败', <div><div>错误码:{ apiRes.errCode}</div>
Modal.error({
title:'支付失败',
content:`错误码:${apiRes.errCode}`
})
// message.error(`支付失败,${apiRes.errCode}`)
}
}
}
function handleClose () {
if (vdata.payOrderWebSocket) {
vdata.payOrderWebSocket.close()
}
vdata.visible = false
}
</script>
<style lang="less" scoped>
.describe {
img {
width: 30px;
height: 25px;
}
}
</style>

View File

@@ -0,0 +1,128 @@
.paydemo .content {
max-width: 1120px;
margin: 0 auto;
}
.paydemo .paydemo-type-content {
padding:20px 0;
margin-bottom:20px;
background-color: #FFFFFF;
border-radius: 6px;
}
.paydemo .paydemo-type-name {
font-size: 16px;
margin-bottom: 12px;
display: flex;
align-items: center;
}
.paydemo .paydemo-type-body {
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: 20px;
}
.paydemo .paydemo-type {
padding: 12px;
border: solid 1px #e2e2e2;
margin-right: 10px;
cursor: pointer;
}
.paydemo .paydemo-type-h5 {
padding: 12px;
border: solid 1px #e2e2e2;
margin-right: 10px;
cursor: pointer;
}
.paydemo .codeImg_wx_h5 {
position: absolute;
z-index:1001;
display: flex;
flex-direction: column;
align-items: center;
background-color: #ffffff;
width: 160px;
}
.paydemo .paydemo-type-img {
width: 40px;
height: 40px;
vertical-align: center;
margin-right: 10px;
}
.paydemo .this {
color: #1953ff;
border-color: #1953ff;
}
.paydemo .layui-input {
width: 50%;
display: inline;
font-size: 14px;
}
.paydemo .paydemo-form-item {
height: 38px;
margin-bottom: 5px;
display: flex;
flex-direction: row;
align-items: center;
}
.paydemo .layui-form-radio i:hover, .layui-form-radioed i {
color: #1953ff;
}
.paydemo .layui-anim.layui-icon {
margin-top: 3px;
}
.paydemo .layui-form-radio {
margin-top: 2px;
}
.paydemo .paydemo-scan {
padding-top: 30px;
display: flex;
flex-direction: column;
align-items: center;
}
.paydemo .layui-laypage .layui-laypage-curr .layui-laypage-em {
background-color: #1953ff;
}
.paydemo .layui-laypage a:hover {
color: #1953ff;
}
.paydemo-type-content p {
margin-bottom: 10px;
}
.paydemo .layui-table-view .layui-table {
width: 100%;
}
.paydemo .paydemo-btn {
border: 1px solid #e2e2e2;
background-color: #ffffff;
color:#000000;
margin-left: 8px;
display: inline-flex;
flex-direction: row;
align-items: center;
cursor:pointer;
}
.paydemo #paydemo-amount {
display: flex;
align-items: inherit;
}
.paydemo #paydemo-amount .layui-unselect{
margin-right:15px
}
#randomOrderNo:hover {
cursor:pointer;
}
.paydemo-form-item label {
display: flex;
align-items: center;
margin-right:15px;
flex-wrap:wrap
}
.paydemo-form-item label input {
margin-left:3px;
}
.paydemo .article-title {
font-weight: 600
}

View File

@@ -0,0 +1,405 @@
<template>
<page-header-wrapper>
<a-card class="table-card">
<JeepaySearchForm :searchFunc="searchFunc" :resetFunc="resetFunc">
<a-form-item label="" class="table-search-item">
<JeepayDateRangePicker v-if="vdata.showDateTimeRage" ref="dateRangePicker" v-model:value="vdata.searchData['queryDateRange']" customDateRangeType="date" />
</a-form-item>
<jeepay-text-up v-model:value="vdata.searchData['id']" :placeholder="'申请单号'" />
<jeepay-text-up v-model:value="vdata.searchData['productOrPackageName']" :placeholder="'产品名称'" />
<jeepay-text-up v-model:value="vdata.searchData['contactsName']" :placeholder="'联系人'" />
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData.state" placeholder="开通状态">
<a-select-option value="1">审核中</a-select-option>
<a-select-option value="2">审核失败 </a-select-option>
<a-select-option value="3">已开通</a-select-option>
<a-select-option value="4">已取消</a-select-option>
</a-select>
</a-form-item>
</JeepaySearchForm>
<!-- 列表渲染 -->
<JeepayTable
ref="infoTable"
:init-data="false"
:req-table-data-func="reqTableDataFunc"
:table-columns="tableColumns"
:search-data="vdata.searchData"
row-key="id"
:expandedRowShow="true"
@btnLoadClose="btnLoading=false"
@handleTableChange="handleChange"
>
<template #headerCell="{ column }">
<span v-if="column.tooltipTitle">
{{ column.title }}
<a-tooltip :title="column.tooltipTitle"><info-circle-outlined /></a-tooltip>
</span>
</template>
<template #expandedRowRender="{record}">
<a-table :columns="vdata.innerColumns" :data-source="record.specialCertificationText" :pagination="false" style="margin-top: 5px">
<template #certificationUrl="{ record }">
<a-image :src="record.certificationUrl" style="width: 100px;height: 100px" />
</template>
<template #UserUrl="{ record }">
<a-image v-if="record.UserUrl" :src="record.UserUrl" style="width: 100px;height: 100px" />
<span v-else style="color:#ff4949" >未上传</span>
</template>
</a-table>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'costRule'">
<div v-for="item in record.costRule" :key="item">
{{item.versionName}}
</div>
</template>
<template v-if="column.key === 'actualPrice'">
<div v-for="item in record.costRule" :key="item">
{{item.sellingPrice}}
</div>
</template>
<template v-if="column.key === 'state'">
<a-badge status="warning" v-if="record.state == 1" text="审核中" />
<!-- <a-badge status="processing" text="审核失败" />-->
<!-- <a-badge status="processing" text="待签约" />-->
<a-badge status="success" v-if="record.state == 3" text="审核通过" />
<a-badge status="error" v-if="record.state == 2" text="审核失败" />
<a-badge status="default" v-if="record.state == 4" text="已失效" />
<!-- <a-tooltip :title="record.errMsg" v-if="record.state == 3">-->
<!-- <info-circle-outlined style="color: #FFD080;margin-left: 10px"/>-->
<!-- </a-tooltip>-->
<a-tooltip v-if="record.state == 2" :title="record.stateDesc" >
<info-circle-outlined style="color: #FFD080;margin-left: 10px"/>
</a-tooltip>
</template>
<template v-if="column.key == 'op'">
<!-- 操作列插槽 -->
<!-- <a-button type="link" >付款</a-button>-->
<!-- <a-button type="link" >签约</a-button>-->
<a-button v-if="record.state == 2" type="link" @click="edit(record)" >修改</a-button>
<!-- <a-button type="link" >查看详情</a-button>-->
<!-- <a-button type="link" >续约</a-button>-->
</template>
</template>
</JeepayTable>
</a-card>
<a-drawer
v-if="vdata.visible"
title="修改"
placement="right"
:closable="false"
width="700px"
v-model:visible="vdata.visible"
:after-visible-change="afterVisibleChange"
>
<a-form :model="vdata.editForm" :label-col="{ span: 4 }">
<a-form-item label="关联应用">
<a-select v-model:value="vdata.orderFormState.appId" placeholder="选择应用" show-search
:filter-option="false">
<a-select-option v-for="(item) in vdata.filteredAppInfoList" :key="item.appId">{{ item.appName }}
[{{ item.appId as any }}]
<span v-if="vdata.productDetail.bindingAppList">{{
vdata.productDetail.bindingAppList.indexOf(item.appId as any) === -1 ? '未关联' : '已关联' }}</span>
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="订购信息">
<a-col :span="24" style="margin:10px 0px;">
<a-table :columns="vdata.columns" ref="tableRef" :data-source="vdata.costRule" :pagination="false" style="margin-left: -1px;"
:row-selection="{selectedRowKeys:vdata.selectedRowKeys,type: 'radio', onChange: onSelectionChange }" rowKey="key" >
<template #customTitle>
<span>
产品名称
</span>
</template>
<template #versionName="{ text }">
<div style="display: flex; align-items: center;">
<span>{{ text }}</span>
</div>
</template>
<template #validTime="{ text }">
<div style="display: flex; align-items: center;">
<span>{{ text === -1 ? '长期' : text }}</span>
</div>
</template>
<template #sellingPrice="{ text }">
<span>
{{ text }}
</span>
</template>
<template #lineationPrice="{ text }">
<span>
{{ text }}
</span>
</template>
<template #countPrice="{ record }">
<span style="color: #f00;">{{ record.sellingPrice == '免费' ? '免费' : record.count * record.sellingPrice
+ '元' }}</span>
</template>
</a-table>
</a-col>
</a-form-item>
<a-form-item label="备注(可选)" name="remark">
<a-textarea v-model:value="vdata.orderFormState.remark" :rows="1" />
</a-form-item>
<a-form-item label="姓名" required>
<a-input v-model:value="vdata.orderFormState.contactsName" />
</a-form-item>
<a-form-item label="手机号" required>
<a-input v-model:value="vdata.orderFormState.contactsTel" maxlength="11"
onkeyup="value=value.replace(/\D+/,'')" />
</a-form-item>
<a-form-item label="特殊资质">
<a-col :span="24" style="margin:10px 0px;">
<div style="display: flex;flex-wrap: wrap;">
<div class="upload-box" v-for="(item,index) in vdata.specialCertificationText" :key="index" style="margin-right: 20px;text-align: center;">
<div>{{item.certificationName}}</div>
<JeepayUpload v-model:src="item.UserUrl" bizType="applyment" :imgSize="1" class="upload-box-img"/>
<a class="color-box-span" @click="getImg(item)">查看示例</a>
</div>
</div>
</a-col>
</a-form-item>
<!-- <a-form-item label="特殊资质名称" required>-->
<!-- <a-input v-model:value="vdata.orderFormState.specialCertificationText[0].certificationName" />-->
<!-- </a-form-item>-->
<!-- <a-form-item label="特殊资质图片" required>-->
<!-- <JeepayUpload :src="vdata.orderFormState.specialCertificationText[0].certificationUrl" @update:src="updateSrc" bizType="oem" />-->
<!-- <span class="jeepay-tip-text">建议最小尺寸220*220px不超过2.9M</span>-->
<!-- </a-form-item>-->
</a-form>
<div
:style="{
position: 'absolute',
right: 0,
bottom: 0,
width: '100%',
borderTop: '1px solid #e9e9e9',
padding: '10px 16px',
background: '#fff',
textAlign: 'right',
zIndex: 1,
}"
>
<a-button style="margin-right: 8px" @click="vdata.visible=false">取消</a-button>
<a-button type="primary" @click="onSubmit">确认修改</a-button>
</div>
</a-drawer>
</page-header-wrapper>
</template>
<script lang="ts" setup >
import {req, $exportExcel, exportExcelUrl, API_URL_PACKAGE_ORDER, API_URL_MCH_APP} from '@/api/manage'
import {ref, reactive, onMounted, nextTick, getCurrentInstance} from 'vue'
import fileDownload from 'js-file-download'
import { useRoute } from 'vue-router'
import dateUtil from '@/utils/dateUtil.js'
import $infoBox from "@/utils/infoBox";
const { $viewerApi } = getCurrentInstance()!.appContext.config.globalProperties
const infoTable = ref()
const dateRangePicker = ref()
let tableColumns = reactive([
{ key: 'id', title: '申请单号', dataIndex: 'id', },
{ key: 'productOrPackageName', title: '产品名称', dataIndex: 'productOrPackageName', },
// { key: 'kafaizhe', title: '开发者', dataIndex: 'kafaizhe', },
{ key: 'costRule', title: '订购信息', dataIndex: 'costRule', },
// { key: 'specialCertificationText', title: '特殊资质', dataIndex: 'specialCertificationText', },
{ key: 'actualPrice', title: '总价', dataIndex: 'originPrice', },
{ key: 'state', title: '开通状态', dataIndex: 'state', },
{ key: 'createdAt', title: '创建时间', dataIndex: 'createdAt', },
{ key: 'op', title: '操作', dataIndex: 'op', },
])
let btnLoading = ref(false)
let count:any = ref([]) // 数据统计数组
const vdata: any = reactive({
visible:false, //抽屉标识
orderFormState:{} as any,
showDateTimeRage: false,
searchData: { queryDateRange: 'today',method : 'store', sortField: 'payAmount', sortOrder: 'descend' },
columns: [
{
dataIndex: 'versionName',
key: 'versionName',
slots: { title: 'customTitle', customRender: 'versionName' },
},
{
title: '周期',
dataIndex: 'validTime',
key: 'validTime',
slots: {customRender: 'validTime' },
},
{
title: '单价',
key: 'sellingPrice',
dataIndex: 'sellingPrice',
},
{
title: '划线价',
key: 'lineationPrice',
dataIndex: 'lineationPrice',
},
],
costRule:[],
selectedRowKeys:[],
filteredAppInfoList:[],
mchAppList:[],
productDetail: {
bindingApp: 1,
bindingAppList: [] as any
} as any,
appId:'' as any,
specialCertificationText:[],
certificationColumns:[
{
title: '名称',
key: 'certificationName',
dataIndex: 'certificationName',
},
{
key: 'certificationUrl',
dataIndex: 'certificationUrl',
title: '图片',
slots: { customRender: "certificationUrl" },
},
{
title: '类型',
key: 'inputType',
dataIndex: 'inputType',
},
],
ZZselectedRowKeys:[],
innerColumns:[
{
title: '特殊资质名称',
key: 'certificationName',
dataIndex: 'certificationName',
},
{
title: '图片',
key: 'UserUrl',
dataIndex: 'UserUrl',
slots: { customRender: "UserUrl" },
}, {
title: '示例图',
key: 'certificationUrl',
dataIndex: 'certificationUrl',
slots: { customRender: "certificationUrl" },
},
]
})
function getMchApp() {
req.list(API_URL_MCH_APP, { pageSize: -1, state: 1 }).then(res => {
vdata.mchAppList = res.records
vdata.filteredAppInfoList = res.records
})
console.log(vdata.filteredAppInfoList)
}
function updateSrc(data){
vdata.orderFormState.specialCertificationText[0].certificationUrl=data
}
getMchApp()
onMounted(() => {
searchFunc()
vdata.showDateTimeRage = true
})
function handleChange(sorter) {
vdata.searchData['sortField'] = sorter.columnKey
vdata.searchData['sortOrder'] = sorter.order
}
function afterVisibleChange(){}
const onSelectionChange = (selectedRowKeys, selectedRows) => {
vdata.selectedRowKeys = selectedRowKeys;
vdata.orderFormState.costRule = selectedRows
}
let tableRef=ref()
function edit(record){
vdata.orderFormState=record
vdata.selectedRowKeys[0]=record.costRule[0].key
vdata.ZZselectedRowKeys[0]=record.specialCertificationText[0].key
if(record.packageOrProduct===0){
req.list('/api/product/getProductById',{productId:record.productId}).then((res)=>{
vdata.specialCertificationText=res.specialCertificationText
vdata.costRule=res.productText
})
}else{
req.list('/api/packageInfo/getPackageById',{packageId:record.packageId}).then((res)=>{
vdata.specialCertificationText=res.specialCertificationText
vdata.costRule=res.productText
})
}
vdata.visible=true
}
function resetFunc() {
dateRangePicker.value.returnSelectModel()
vdata.searchData = { method : 'store', queryDateRange: 'today', sortField: 'payAmount', sortOrder: 'descend' }
}
// 请求table接口数据
function reqTableDataFunc(params: any) {
// params.method = 'store'
if (!params.sortField) {
// params.sortField = 'payAmount'
// params.sortOrder = 'descend'
}
return req.list(API_URL_PACKAGE_ORDER, params)
}
function searchFunc () { // 点击【查询】按钮点击事件
infoTable.value.refTable(true)
}
function onSubmit(){
vdata.orderFormState.specialCertificationText=vdata.specialCertificationText
req.updateById('/api/packageInfo',vdata.orderFormState.id,vdata.orderFormState).then((res)=>{
$infoBox.message.success('更新成功')
searchFunc()
vdata.visible=false
})
}
const onSelectioncertificationChange = (selectedRowKeys, selectedRows) => {
vdata.ZZselectedRowKeys = selectedRowKeys;
vdata.orderFormState.specialCertificationText = selectedRows
}
function getImg(item){
$viewerApi({images: [item.certificationUrl]})
}
</script>

View File

@@ -0,0 +1,161 @@
<template>
<page-header-wrapper>
<SybMarketsList configMode="agent" title="解决方案" />
</page-header-wrapper>
</template>
<script setup lang="ts">
import {req} from '@/api/manage'
import { ref, onMounted, reactive, getCurrentInstance, nextTick } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { dataTool } from 'echarts'
const router = useRouter() //这是全部路由
const { $infoBox, $access } = getCurrentInstance()!.appContext.config.globalProperties
const sybProductListRef = ref()
const vdata = reactive({
tableType: 'table',
productList: [] as any, // 产品列表
filteredProductList: [] as any, // 产品列表
tab_index: -1,
tabList: [] as any, // 分类列表
filteredTabList: [] as any, // 分类列表
state_index: -1,
stateList: ['未开通', '审核中', '审核驳回', '已开通', '已关联应用'] as any, // 状态
})
</script>
<style scoped>
.div-top {
background: #ffffff;
padding: 20px 20px;
border-radius: 10px;
}
.div-top-title {
font-size: 17px;
font-weight: 600;
position: relative;
margin-bottom: 10px;
}
.div-top-des {
color: #2C6EF6;
margin-top: 15px;
margin-bottom: 30px;
}
.div-top-line {
border-bottom: 1px solid #EDEDED;
}
.div-top-status {
display: flex;
margin: 18px 0;
font-size: 13px;
}
.div-top-status-title {
padding-right: 10px;
color: #767676;
}
.div-top-status-title-span {
padding: 0 10px;
font-weight: 600;
}
.div-top-active {
color: #2C6EF6;
}
/*列表 */
.product {
background: #ffffff;
padding: 0 20px;
border-radius: 10px;
margin-top: 20px;
;
}
.product-title {
font-size: 17px;
font-weight: 600;
position: relative;
margin-bottom: 10px;
}
.product-list {
width: 100%;
margin: 10px 0;
display: flex;
flex-wrap: wrap;
}
.product-list-li {
background: #F9FAFA;
width: 30%;
padding: 20px;
border-radius: 10px;
margin: 10px;
}
.product-list-li-span1 {
font-size: 16px;
font-weight: 600;
padding-right: 10px;
}
.product-list-li-span2 {
font-size: 13px;
color: #2C6EF6;
padding-left: 15px;
}
.product-list-li-desc {
margin-top: 20px;
}
.product-list-li-span3 {
font-size: 13px;
color: #767676;
overflow: hidden;
text-overflow: ellipsis;
/* 在超出时显示省略号 */
display: -webkit-box;
-webkit-line-clamp: 2;
/* 限制显示的行数为2行 */
-webkit-box-orient: vertical;
}
.color-e3 {
color: #767676;
}
.color-yellow {
color: rgb(250, 118, 0);
}
.color-red {
color: #FF0000;
}
.color-blue {
color: #2C6EF6;
}
.color-cancel {
color: #767676;
}
.table-card-bottom {
margin-top: 10px !important;
}
.table-card-hide {
display: none;
}</style>

View File

@@ -0,0 +1,161 @@
<template>
<page-header-wrapper>
<SybProductList configMode="agent" title="产品中心" />
</page-header-wrapper>
</template>
<script setup lang="ts">
import {req} from '@/api/manage'
import { ref, onMounted, reactive, getCurrentInstance, nextTick } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { dataTool } from 'echarts'
const router = useRouter() //这是全部路由
const { $infoBox, $access } = getCurrentInstance()!.appContext.config.globalProperties
const sybProductListRef = ref()
const vdata = reactive({
tableType: 'table',
productList: [] as any, // 产品列表
filteredProductList: [] as any, // 产品列表
tab_index: -1,
tabList: [] as any, // 分类列表
filteredTabList: [] as any, // 分类列表
state_index: -1,
stateList: ['未开通', '审核中', '审核驳回', '已开通', '已关联应用'] as any, // 状态
})
</script>
<style scoped>
.div-top {
background: #ffffff;
padding: 20px 20px;
border-radius: 10px;
}
.div-top-title {
font-size: 17px;
font-weight: 600;
position: relative;
margin-bottom: 10px;
}
.div-top-des {
color: #2C6EF6;
margin-top: 15px;
margin-bottom: 30px;
}
.div-top-line {
border-bottom: 1px solid #EDEDED;
}
.div-top-status {
display: flex;
margin: 18px 0;
font-size: 13px;
}
.div-top-status-title {
padding-right: 10px;
color: #767676;
}
.div-top-status-title-span {
padding: 0 10px;
font-weight: 600;
}
.div-top-active {
color: #2C6EF6;
}
/*列表 */
.product {
background: #ffffff;
padding: 0 20px;
border-radius: 10px;
margin-top: 20px;
;
}
.product-title {
font-size: 17px;
font-weight: 600;
position: relative;
margin-bottom: 10px;
}
.product-list {
width: 100%;
margin: 10px 0;
display: flex;
flex-wrap: wrap;
}
.product-list-li {
background: #F9FAFA;
width: 30%;
padding: 20px;
border-radius: 10px;
margin: 10px;
}
.product-list-li-span1 {
font-size: 16px;
font-weight: 600;
padding-right: 10px;
}
.product-list-li-span2 {
font-size: 13px;
color: #2C6EF6;
padding-left: 15px;
}
.product-list-li-desc {
margin-top: 20px;
}
.product-list-li-span3 {
font-size: 13px;
color: #767676;
overflow: hidden;
text-overflow: ellipsis;
/* 在超出时显示省略号 */
display: -webkit-box;
-webkit-line-clamp: 2;
/* 限制显示的行数为2行 */
-webkit-box-orient: vertical;
}
.color-e3 {
color: #767676;
}
.color-yellow {
color: rgb(250, 118, 0);
}
.color-red {
color: #FF0000;
}
.color-blue {
color: #2C6EF6;
}
.color-cancel {
color: #767676;
}
.table-card-bottom {
margin-top: 10px !important;
}
.table-card-hide {
display: none;
}</style>

View File

@@ -0,0 +1,190 @@
<template>
<a-drawer
v-model:visible="vdata.visible"
:mask-closable="false"
:title="vdata.isAdd ? '新增二维码' : '修改二维码' "
width="40%"
@close="onClose"
>
<a-form v-if="vdata.visible" ref="infoFormModel" :model="vdata.saveObject" :rules="saveRule">
<a-row v-if="vdata.isAdd">
<a-form-item label="批次号" name="batchId">
<a-input-number v-model:value="vdata.saveObject.batchId" style="width:70%; margin-right: 20px;" />
<a-button type="primary" size="small" @click="resetBatchId">今天</a-button>
<p class="jeepay-tip-text">( 数字格式 二维码编号的前缀 建议采用 YYMMDD+次数表示 )</p>
</a-form-item>
</a-row>
<a-row v-if="vdata.isAdd">
<a-form-item label="创建数量" name="addNum">
<a-input-number v-model:value="vdata.saveObject.addNum" />
</a-form-item>
</a-row>
<a-row v-if="vdata.isAdd">
<a-form-item label="选择模板" name="qrcShellId">
<a-select v-model:value="vdata.saveObject.qrcShellId" placeholder="选择模板" style="width: 200px;">
<a-select-option value=""></a-select-option>
<template v-for="item in vdata.qrcShellList" :key="item.sid">
<a-select-option :value="item.sid">{{ item.shellAlias }} <img :src="item.shellImgViewUrl" style="width: 20px"> </a-select-option>
</template>
</a-select>
</a-form-item>
</a-row>
<a-row>
<a-form-item label="状态" name="qrcState">
<a-radio-group v-model:value="vdata.saveObject.qrcState">
<a-radio :value="1">启用</a-radio>
<a-radio :value="0">禁用</a-radio>
</a-radio-group>
</a-form-item>
</a-row>
<a-row>
<a-form-item label="固定金额" name="fixedFlag">
<a-radio-group v-model:value="vdata.saveObject.fixedFlag">
<a-radio :value="0">任意金额</a-radio>
<a-radio :value="1">固定金额</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item v-if="vdata.saveObject.fixedFlag" label="" name="fixedPayAmount">
<a-input-number v-model:value="vdata.saveObject.fixedPayAmount" :precision="2" style="width: 130px" />
</a-form-item>
</a-row>
<a-row>
<a-form-item v-if="vdata.isAdd" label="选择页面类型" name="entryPage">
谨慎选择 一经填写不可变更
<a-radio-group v-model:value="vdata.saveObject.entryPage">
<a-radio :value="'default'">默认(未指定取决于二维码是否绑定到微信侧)</a-radio>
<a-radio :value="'h5'">固定H5页面</a-radio>
<a-radio :value="'lite'">固定小程序页面</a-radio>
</a-radio-group>
</a-form-item>
</a-row>
<a-row>
<a-form-item label="支付宝支付方式(仅H5呈现时生效)" name="alipayWayCode">
<a-radio-group v-model:value="vdata.saveObject.alipayWayCode">
<a-radio :value="'ALI_JSAPI'">ALI_JSAPI</a-radio>
<a-radio :value="'ALI_WAP'">ALI_WAP</a-radio>
</a-radio-group>
</a-form-item>
</a-row>
</a-form>
<div class="drawer-btn-center">
<a-button :style="{ marginRight: '8px' }" style="margin-right:8px" @click="onClose"><close-outlined />取消</a-button>
<a-button type="primary" :loading="vdata.btnLoading" @click="handleOkFunc"><check-outlined />保存</a-button>
</div>
</a-drawer>
</template>
<script lang="ts" setup>
import {API_URL_MCH_QR_CODE_LIST, API_URL_QRC_SHELL_LIST, req} from '@/api/manage'
import {defineProps, reactive, ref, getCurrentInstance} from 'vue'
import ruleGenerator from '@/utils/ruleGenerator'
import {formatDate} from '@/utils/util'
const { $infoBox, $access } = getCurrentInstance()!.appContext.config.globalProperties
// 定义组件参数
const props = defineProps({
callbackFunc: {type: Function, default: () => {}}
})
// 定义ref对象
const infoFormModel = ref()
const saveRule = {
addNum: [ruleGenerator.requiredInput('创建数量', 'number'), { type: 'number', max: 500, min: 1, message: '数量请介于1-100之间' }],
batchId:[ruleGenerator.requiredInput('批次号', 'number'), { type: 'number', min: 1, message: '请输入正确的批次号' }],
appId: [{ required: true, message: '请选择应用', trigger: 'blur' }],
fixedPayAmount: [{ required: true, message: '请输入固定金额', trigger: 'blur', type: 'number', min: 0.01 }],
}
const vdata : any = reactive({
btnLoading: false,
isAdd: true, // 新增 or 修改页面标志
recordId: null, // 更新对象ID
visible: false, // 是否显示弹层/抽屉
saveObject: {}, // 数据对象
qrcShellList: []
})
// 弹层打开事件
function show(recordId, mchNo) {
// 查询全部模板信息
req.list(API_URL_QRC_SHELL_LIST, {pageSize: -1}).then((res) => {
vdata.qrcShellList = res.records
})
vdata.isAdd = !recordId
vdata.saveObject = {addNum: 1, qrcState: 1, fixedFlag: 0, entryPage: 'default', alipayWayCode: 'ALI_JSAPI' } // 重置清空
resetBatchId()
// 重置表单验证规则
if (infoFormModel.value) {
infoFormModel.value.resetFields()
}
// 修改弹层
if (!vdata.isAdd) { // 修改信息 延迟展示弹层
vdata.recordId = recordId
req.getById(API_URL_MCH_QR_CODE_LIST, recordId).then(res => {
vdata.saveObject = res
vdata.saveObject.fixedPayAmount = vdata.saveObject.fixedPayAmount / 100
vdata.visible = true
})
} else {
vdata.visible = true // 立马展示弹层信息
}
}
// 点击 【保存】按钮的事件
function handleOkFunc(){
vdata.btnLoading = true
infoFormModel.value.validate().then(() => {
if(vdata.isAdd){
req.add(API_URL_MCH_QR_CODE_LIST, vdata.saveObject).then( res => reqSuccessFunc() ).finally(() => vdata.btnLoading = false)
}else{
req.updateById(API_URL_MCH_QR_CODE_LIST, vdata.recordId, vdata.saveObject).then( res => reqSuccessFunc() ).finally(() => vdata.btnLoading = false)
}
})
}
// 接口请求成功的处理函数
function reqSuccessFunc(){
$infoBox.message.success( vdata.isAdd ? '新增成功' : '更新成功')
vdata.visible = false
props.callbackFunc() // 刷新列表
}
function onClose() {
vdata.visible = false
}
function resetBatchId(){
vdata.saveObject.batchId = parseInt(formatDate('YYMMDD00'))
}
defineExpose({show})
</script>

View File

@@ -0,0 +1,128 @@
<template>
<a-drawer
v-model:visible="vdata.visible"
:mask-closable="false"
title="绑定管理"
width="40%"
@close="onClose"
>
<a-form ref="infoFormModel" :model="vdata.record" layout="horizontal" :rules="vdata.rules">
<a-row justify="space-between" type="flex">
<a-col :span="24">
<a-form-item label="绑定状态:">
<a-space>
{{ vdata.record.bindState == 1 ? '已绑定' : '未绑定' }}
<a-button type="primary" @click="showSelectModal">{{ vdata.record.bindState == 1 ? '重新选择' : '选择商户信息' }}</a-button>
</a-space>
</a-form-item>
</a-col>
<a-col v-if="vdata.showBindInfo" :span="24">
<a-form-item label="用户号:">
<span>{{ vdata.record.mchNo }}</span>
</a-form-item>
</a-col>
<a-col v-if="vdata.showBindInfo" :span="24">
<a-form-item label="商户应用:">
<span>{{ vdata.record.appId }}</span>
</a-form-item>
</a-col>
<a-col v-if="vdata.showBindInfo" :span="24">
<a-form-item label="门店:">
<span>{{ vdata.record.storeId }}</span>
</a-form-item>
</a-col>
</a-row>
</a-form>
<JeepayModelMchList ref="jeepayModelMchListRef" showType="MCH_APP_STORE" :mchNoAndName="true" @selectFinishFunc="selectFinishFunc" />
<div class="drawer-btn-center">
<a-button :style="{ marginRight: '8px' }" style="margin-right:8px" @click="onClose"><close-outlined />取消</a-button>
<a-button type="primary" @click="handleOkFunc"><check-outlined />保存</a-button>
</div>
</a-drawer>
</template>
<script lang="ts" setup>
import {API_URL_MCH_QR_CODE_LIST, $bindQrc, req} from '@/api/manage'
import {defineProps, reactive, ref, getCurrentInstance, inject} from 'vue'
import ruleGenerator from '@/utils/ruleGenerator'
const { $infoBox, $access } = getCurrentInstance()!.appContext.config.globalProperties
// 定义组件参数
const props = defineProps({
callbackFunc: {type: Function, default: () => {}}
})
const listPageSearchFunc : any = inject('listPageSearchFunc')
// 定义ref对象
const infoFormModel = ref()
const jeepayModelMchListRef = ref()
const vdata : any = reactive({
recordId: null, // 更新对象ID
visible: false, // 是否显示弹层/抽屉
record: {}, //当前对象数据
showBindInfo: false // 显示绑定信息
})
// 弹层打开事件
function show(recordId) {
vdata.recordId = recordId
req.getById(API_URL_MCH_QR_CODE_LIST, recordId).then(res => {
vdata.record = res
if (vdata.record.bindState == 1) {
vdata.showBindInfo = true
} else {
vdata.showBindInfo = false
}
vdata.visible = true
})
}
function showSelectModal(){
jeepayModelMchListRef.value.show()
}
function selectFinishFunc(selectArray){
if(!selectArray[0] || !selectArray[1] || !selectArray[2]){
return $infoBox.message.error('选择信息不完整!')
}
vdata.record.mchNo = selectArray[0]
vdata.record.appId = selectArray[1]
vdata.record.storeId = selectArray[2]
vdata.showBindInfo = true
jeepayModelMchListRef.value.close()
}
// 点击 【保存】按钮的事件
function handleOkFunc(){
$bindQrc(vdata.recordId, vdata.record).then((res) => {
$infoBox.message.success('更新成功')
onClose()
listPageSearchFunc()
})
}
function onClose() {
vdata.visible = false
}
defineExpose({show})
</script>

View File

@@ -0,0 +1,459 @@
<template>
<page-header-wrapper>
<a-card>
<JeepaySearchForm :searchConditionNum="4" :searchFunc="searchFunc" :resetFunc="onReset">
<a-form-item label="" class="table-search-item">
<JeepayDateRangePicker ref="dateRangePicker" v-model:value="vdata.searchData['queryDateRange']" customDateRangeType="date" />
</a-form-item>
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData.qrcType" placeholder="码牌类型">
<a-select-option value="">全部</a-select-option>
<a-select-option value="0">电子码</a-select-option>
<a-select-option value="1">实体码牌</a-select-option>
<a-select-option value="2">实体立牌</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData.entryPage" placeholder="页面类型">
<a-select-option value="">全部</a-select-option>
<a-select-option value="default">默认</a-select-option>
<a-select-option value="h5">H5</a-select-option>
<a-select-option value="lite">小程序</a-select-option>
</a-select>
</a-form-item>
<jeepay-text-up v-model:value="vdata.searchData['mchUserName']" :placeholder="'用户号/名称/手机号'" />
<jeepay-text-up v-model:value="vdata.searchData['mchServiceName']" :placeholder="'服务商号'" />
<!-- <JeepaySearchInfoInput v-model:value="vdata.searchData['mchNo']" placeholder="用户号" :textUpStyle="true" :mchNoAndName="true" showType="MCH" />-->
<jeepay-text-up v-model:value="vdata.searchData.qrcAlias" placeholder="二维码名称/编号" />
<jeepay-text-up v-model:value="vdata.searchData['mchApplyId']" :placeholder="'商户名称/商户号'" />
<jeepay-text-up v-model:value="vdata.searchData.storeId" placeholder="门店名称/门店编号" />
<jeepay-text-up v-model:value="vdata.searchData.appId" placeholder="应用名称/应用ID" />
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData.bindState" placeholder="绑定状态">
<a-select-option value="">全部</a-select-option>
<a-select-option value="0">未绑定</a-select-option>
<a-select-option value="1">已绑定</a-select-option>
</a-select>
</a-form-item>
</JeepaySearchForm>
<!-- 列表渲染 -->
<JeepayTable
ref="infoTable"
:initData="false"
:reqTableDataFunc="reqTableDataFunc"
:tableColumns="tableColumns"
:searchData="vdata.searchData"
:tableExportFunc="$access('ENT_DEVICE_QRC_EXPORT') ? tableExportFunc : null"
:rowSelection="{ type: 'checkbox', onChange: infoTableSelectChangeFunc }"
rowKey="qrcId"
>
<template #topBtnSlot>
<a-button type="primary" @click="addOrEditFunc()"><PlusOutlined /> 申请新码</a-button>
<!-- <a-button @click="batchChecked(true)">全选</a-button>
<a-button @click="batchChecked(false)">反选</a-button> -->
<a-button v-if="$access('ENT_DEVICE_QRC_ALLOT')" type="primary" @click="allotBatchFunc"><partition-outlined />勾选划拨/收回</a-button>
<!-- <a-button v-if="$access('ENT_DEVICE_QRC_ALLOT')" type="primary" @click="allotByBatchIdFunc">批次划拨</a-button> -->
<a-button v-if="$access('ENT_DEVICE_QRC_EXPORT')" @click="tableExportByBatchSelectFunc">导出选择项</a-button>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'qrcId'">
{{ record.qrcId }}
<QrcodeOutlined style="font-size: 16px" @click="showQrImgFunc(record.qrcId)" />
</template>
<template v-if="column.key === 'bindAppIdName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>应用名称{{record.bindAppIdName}}</span><br>
<span>应用ID{{record.appId}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.bindAppIdName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'mchApplyName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>商户名称{{record.mchApplyName}}</span><br>
<span>商户号{{record.mchApplyId}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.mchApplyName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'storeId'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>门店名称{{record.storeName}}</span><br>
<span>门店编号{{record.storeId}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.storeName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'mchServiceName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>服务商名称{{record.mchServiceName}}</span><br>
<span>服务商号{{record.agentNo}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.mchServiceName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'mchUserName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>用户名称{{record.mchUserName}}</span><br>
<span>用户手机号{{record.mchUserPhone}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.mchUserName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'qrcType'">
<a-tag v-if="record.qrcType == 0" color="blue">电子码牌</a-tag>
<a-tag v-if="record.qrcType == 1" color="orange">实体码牌</a-tag>
<a-tag v-if="record.qrcType == 2" color="green">实体立牌</a-tag>
</template>
<template v-if="column.key === 'bindState'">
<JeepayTableColState :state="record.bindState" :showSwitchType="$access('ENT_MCH_QR_CODE_EDIT')" :onChange="(state) => { return updateState(record.qrcId, state)}" />
</template>
<template v-if="column.key === 'fixedFlag'">
<span v-if="record.fixedFlag === 0">任意金额</span>
<span v-else>{{ (record.fixedPayAmount / 100).toFixed(2)}}</span>
</template>
<template v-if="column.key === 'op'">
<!-- 操作列插槽 -->
<JeepayTableColumns>
<a-button v-if="$access('ENT_DEVICE_QRC_VIEW')" type="link" @click="showQrImgFunc(record.qrcId)">详情</a-button>
<a-button v-if="$access('ENT_DEVICE_QRC_EDIT') && record.isSelf" type="link" @click="showBind(record.qrcId)">绑定</a-button>
<a-button v-if="$access('ENT_DEVICE_QRC_RELIEVE') && record.bindState == 1 && record.isSelf" type="link" @click="unBindFunc(record.qrcId)">解绑</a-button>
<a-button v-if="$access('ENT_DEVICE_QRC_ALLOT')" type="link" @click="allotFunc(record)">划拨/收回</a-button>
<a-button v-if="$access('ENT_DEVICE_QRC_EDIT') && record.bindState == 1" type="link" @click="getBindDeviceFunc(record.qrcId)">受支持设备</a-button>
</JeepayTableColumns>
</template>
</template>
</JeepayTable>
<!-- <AddOrEdit ref="infoAddOrEdit" :callbackFunc="searchFunc" /> -->
<Bind ref="bindRef" :callbackFunc="searchFunc" />
<JeepayQrcDeviceList ref="jeepayQrcDeviceListRef" sysType="AGENT" />
<JeepayModelAgentList ref="jeepayModelAgentList" showType="AGENT" @selectFinishFunc="searchAgentFinishFunc" />
<!-- 批次号划拨弹窗 -->
<a-modal v-model:visible="vdata.allotByBatchIdVisible" title="按批次号划拨" @ok="allotOk">
<a-form ref="allotFormModel" :model="vdata.allotObject" layout="vertical" :rules="vdata.allotRules">
<a-form-item label="批次号:" name="batchId">
<a-input v-model:value="vdata.allotObject.batchId" placeholder="请输入批次号" />
</a-form-item>
</a-form>
</a-modal>
<a-modal v-model:visible="vdata.exportVisible" title="导出选项" :width="500" @ok="tableExportDowonloadFunc">
<a-radio-group v-model:value="vdata.exportModel">
<a-radio value="infoAndUrl" style="margin-bottom: 10px;">Excel 二维码信息并且包含二维码解析URL</a-radio><br>
<a-radio value="templateImg" style="margin-bottom: 10px;">压缩包 模板图片如果有</a-radio> <br>
<a-radio value="qrcode" style="margin-bottom: 10px;">压缩包 二维码图片(不含编号)</a-radio><br>
<a-radio value="qrcodeAndQrcId" style="margin-bottom: 10px;">压缩包 二维码图片(含编号) </a-radio><br>
</a-radio-group>
</a-modal>
</a-card>
</page-header-wrapper>
</template>
<script setup lang="ts">
import Bind from './Bind.vue'
import {API_URL_MCH_QR_CODE_LIST, req, reqLoad, $qrcShellViewByQrc, $exportExcel, exportExcelUrl, $unbindQrc, $allotQrc } from '@/api/manage'
import { ref, reactive, provide, getCurrentInstance, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import fileDownload from 'js-file-download'
import {getCurrentDatetime} from '@/utils/util'
// 导入全局函数
const { $infoBox, $access, $viewerApi } = getCurrentInstance()!.appContext.config.globalProperties
// infoTable组件
const infoTable = ref()
const bindRef = ref()
const jeepayQrcDeviceListRef = ref()
const allotFormModel = ref()
const jeepayModelAgentList = ref()
const dateRangePicker = ref()
// eslint-disable-next-line no-unused-vars
const tableColumns = ref([
// { key: 'qrcId', title: '二维码', },
// { key: 'agentNo', title: '服务商', dataIndex: 'agentNo', agentEntCol: true },
// { key: 'mchNo', title: '绑定商户信息' },
// { key: 'entryPage', title: '扫码页面', customRender: ({ record }) => {return record.entryPage == 'default' ? '默认' : record.entryPage == 'h5' ? 'H5' : '小程序'} , },
// { key: 'qrcState', title: '状态', },
// { key: 'fixedFlag', title: '固定金额', },
// { key: 'createdAt', dataIndex: 'createdAt', title: '创建日期', },
// { key: 'op', title: '操作', fixed: 'right', align: 'center',}
{ key: 'qrcType', title: '码牌类型', },
{ key: 'entryPage', title: '页面类型', customRender: ({ record }) => {return record.entryPage == 'default' ? '默认' : record.entryPage == 'h5' ? 'H5' : '小程序'} ,},
{ key: 'qrcId', title: '二维码编号', },
{ key: 'qrcAlias', dataIndex: 'qrcAlias', title: '二维码名称'},
{ key: 'mchUserName', title: '用户名称', dataIndex: 'mchUserName'},
{ key: 'mchServiceName', title: '服务商名称', dataIndex: 'mchServiceName' },
{ title: "商户名称", key: "mchApplyName", dataIndex: "mchApplyName" },
{ key: 'storeId', title: '门店名称', dataIndex: 'storeId', },
{ key: 'bindAppIdName', dataIndex: 'bindAppIdName', title: '所属应用', },
// { key: 'qrcState', title: '使用状态(无字段)', },
{ key: 'bindState', title: '使用状态', },
{ key: 'fixedFlag', title: '固定金额', },
{ key: 'createdAt', dataIndex: 'createdAt', title: '创建日期', },
{ key: 'op', title: '操作', fixed: 'right', align: 'center', }
])
const vdata : any = reactive({
searchData: {},
exportVisible: false, // 显示导出弹层
exportModel: '', //二维码导出模式
listRecords: [], //当前列表页的缓存数据
listExportIds: null, //列表的批量导出选择项目
allotByBatchIdVisible: false, // 批次划拨设备弹窗
allotObject: {
agentNo: null, // 划拨服务商号
allotType: null, // 划拨类型select-勾选划拨 batch-批次划拨
batchId: null, // 批次号,划拨类型为 batch-批次划拨 时必填
allotIds: [] as any, // 要划拨的码ID划拨类型为 select-勾选划拨 时必填
} as any, // 划拨设备对象
allotRules: {
batchId: [{ required: true, message: '请输入批次号', trigger: 'blur' }]
}
})
onMounted(() => {
vdata.searchData['mchNo'] = useRoute().query.mchNo
vdata.searchData['storeId'] = useRoute().query.storeId
vdata.searchData['mchApplyId'] = useRoute().query.mchApplyId
searchFunc()
})
// 向所有子组件注入刷新事件
provide('listPageSearchFunc', searchFunc)
function searchFunc () {
infoTable.value.refTable(true)
}
// 请求table接口数据
function reqTableDataFunc (params) {
return req.list(API_URL_MCH_QR_CODE_LIST, params).then((res) => {
vdata.listRecords = (res && res.records) || []
return Promise.resolve(res)
})
}
// 新增 or 修改函数
function addOrEditFunc(){
$infoBox.message.success('请联系运营平台申请')
}
// 显示二维码图片
function showQrImgFunc(recordId){
$qrcShellViewByQrc(recordId).then((res) => {
$viewerApi({images: [res]})
})
}
// 显示绑定管理
function showBind(recordId){
bindRef.value.show(recordId)
}
function updateState (recordId, state) { // 【更新状态】
const title = state === 1 ? '确认[启用]' : '确认[停用]'
return $infoBox.confirmDangerPromise(title, '').then(() => {
return reqLoad.updateById(API_URL_MCH_QR_CODE_LIST, recordId, { qrcState: state })
})
}
// 解绑设备
function unBindFunc(recordId) {
const title = '确认解绑?'
const content = '解绑后商户将无法使用该码牌!'
$infoBox.confirmDanger(title, content, () => {
return $unbindQrc(recordId).then(res => {
infoTable.value.refTable(true)
$infoBox.message.success('解绑成功')
})
})
}
// 受支持的设备
function getBindDeviceFunc(recordId) {
jeepayQrcDeviceListRef.value.show(recordId)
}
// function batchChecked(checked){
// vdata.listRecords.forEach(element => {
// element.checked = checked
// })
// }
function infoTableSelectChangeFunc(selectedRowKeys){
// 获取真实的qrcId
let qrcIdList : any = []
selectedRowKeys.forEach(el => qrcIdList.push(el) )
// 遍历 vdata.listRecords 如果selectedRowKeys数组中存在对应的id,则将其checked改为true,反之false
vdata.listRecords.forEach(element => {
qrcIdList.includes(element.qrcId) ? element.checked = true : element.checked = false
})
}
// 第一次点击导出的事件( 筛选条件的导出
function tableExportFunc(){
vdata.exportVisible = true
vdata.exportModel = 'infoAndUrl' //默认excel
vdata.listExportIds = null //批量选择清空
}
// 第一次点击导出的事件( 批量选择的导出
function tableExportByBatchSelectFunc(){
vdata.listExportIds = []
vdata.listRecords.forEach(element => {
if(element.checked){
vdata.listExportIds.push(element.qrcId)
}
})
if(vdata.listExportIds.length <= 0){
return $infoBox.message.error('请选择数据')
}
vdata.exportVisible = true
vdata.exportModel = 'infoAndUrl' //默认excel
}
// 弹层点击确认按钮的事件
function tableExportDowonloadFunc(){
vdata.exportVisible = false
let queryCond :any = { 'exportModel': vdata.exportModel, 'pageSize': -1 } //搜索条件
if(typeof vdata.listExportIds == 'object' && vdata.listExportIds != null){
queryCond.idsStr = ''
vdata.listExportIds.forEach(r => {
queryCond.idsStr += (r + '_')
})
queryCond.idsStr = queryCond.idsStr.substring(0, queryCond.idsStr.length - 1)
}else{
queryCond = Object.assign(queryCond, vdata.searchData)
}
return $exportExcel(exportExcelUrl.qrCodes, queryCond).then(res => {
if(vdata.exportModel == 'infoAndUrl'){
fileDownload(res.data, '码牌信息导出_'+ getCurrentDatetime() +'.xlsx')
}else{
fileDownload(res.data, '码牌图片导出_'+ getCurrentDatetime() +'.zip')
}
}).catch ((error) =>{console.log(error)} )
}
// 按批次号划拨
function allotByBatchIdFunc() {
vdata.allotObject.allotType = 'batch'
vdata.allotObject.batchId = ''
vdata.allotByBatchIdVisible = true
}
// 按批次号划拨,批次号输入完成
function allotOk() {
allotFormModel.value.validate().then((valid: any) =>{
vdata.allotByBatchIdVisible = false
jeepayModelAgentList.value.show()
})
}
// 勾选划拨设备
function allotBatchFunc() {
vdata.listExportIds = []
vdata.listRecords.forEach(element => {
if(element.checked){
vdata.listExportIds.push(element.qrcId)
}
})
if(vdata.listExportIds.length <= 0){
return $infoBox.message.error('请选择数据')
}
vdata.allotObject.allotType = 'select'
jeepayModelAgentList.value.show()
}
// 划拨单个设备
function allotFunc(record) {
vdata.allotObject.allotType = 'select'
vdata.listExportIds = []
vdata.listExportIds.push(record.qrcId)
jeepayModelAgentList.value.show()
}
// 划拨/收回选择完成
function searchAgentFinishFunc(selectObject) {
vdata.allotObject.agentNo = selectObject[0]
vdata.allotObject.allotOrRecover = selectObject[1]
if (vdata.allotObject.allotOrRecover == 'allot' && !vdata.allotObject.agentNo) {
$infoBox.message.error('请选择服务商')
return
}
if (vdata.listExportIds.length > 0) {
vdata.allotObject.allotIds = vdata.listExportIds.join(',')
}
$allotQrc(vdata.allotObject).then(res => {
vdata.listExportIds = [] // 清空多选
$infoBox.message.success('保存成功')
jeepayModelAgentList.value.close()
searchFunc()
})
}
function onReset(){
//重置搜索内容
dateRangePicker.value.returnSelectModel()
vdata.searchData = { queryDateRange: '' }
}
</script>

View File

@@ -0,0 +1,651 @@
<template>
<a-modal
v-model:visible="vdata.visible"
title="快捷收银"
:footer="null"
:maskClosable="false"
@cancel="clear"
>
<div class="select-id">
<!-- <div v-if="$access('ENT_MCH_APP_LIST')">-->
<!-- <p>应用</p>-->
<!-- <a-select v-model:value="vdata.appId" style="margin-right: 10px">-->
<!-- <a-select-option key="">应用AppId</a-select-option>-->
<!-- <a-select-option-->
<!-- v-for="item in vdata.mchAppList"-->
<!-- :key="item.appId"-->
<!-- >-->
<!-- {{ item.appName }} [{{ item.appId }}]-->
<!-- </a-select-option>-->
<!-- </a-select>-->
<!-- </div>-->
<div style="display: ruby">
<div>门店</div>
<div style="min-width: 300px !important;">
<a-select v-model:value="vdata.storeId" dropdownMatchSelectWidth="true" style="width: 90% !important;">
<a-select-option key=""> 选择门店 </a-select-option>
<a-select-option
v-for="item in vdata.mchStoreList"
:key="item.storeId"
>
{{ item.storeName }} [{{ item.storeId }}]
</a-select-option>
</a-select>
</div>
</div>
</div>
<p class="mode">选择收款方式</p>
<!-- 支付方式 -->
<div class="boxs">
<div
v-for="(item, index) in vdata.imgList"
:key="item.key"
class="mode-box"
:style="{
borderColor: vdata.imgIndex == index ? item.color : '#e6e6e6'
}"
@click="chooseType(index, item.key)"
>
<img :src="item.imgSrc" alt="">
<span>{{ item.text }}</span>
</div>
</div>
<!-- 金额 备注 -->
<div class="input">
<span>订单金额</span>
<a-input v-model:value="vdata.params.amount" style="width: 100%" />
<span>备注</span>
<a-input v-model:value="vdata.params.remark" />
</div>
<!-- 软键盘 -->
<div class="keyboard display" style="margin-top: 40px">
<div class="keyboard-num noSelect">
<div
v-for="(item, index) in vdata.numList"
:key="index"
@click="keyNumber(item)"
>
{{ item }}
</div>
</div>
<div class="operate">
<div class="noSelect" @click="delet">
<img src="/src/assets/svg/shcnhu.svg" alt="">
</div>
<div
class="reallybutton noSelect"
style="position: relative"
@click="openSmallVisible"
>
确认支付
</div>
</div>
</div>
</a-modal>
<!-- 小弹窗 -->
<a-modal
v-model:visible="vdata.SmallVisible"
:footer="null"
wrap-class-name="full-modal"
width="330px"
:maskClosable="false"
@cancel="clearSmallModal"
>
<div class="modal-body">
<!-- 扫条码与刷脸 -->
<div v-if="[1, 2].includes(vdata.imgIndex)" class="code">
<img
:src="
vdata.imgIndex === 1
? vdata.imgList[1].imgSrc
: vdata.imgList[2].imgSrc
"
alt=""
>
<div
style="
margin: 30px 0;
display: flex;
flex-direction: column;
align-items: center;
"
>
<a-input
ref="barCodeInput"
v-model:value="vdata.authCode"
style="margin-bottom: 10px"
@keyup.enter="handleOk"
/>
<a-button
type="primary"
:loading="vdata.loading"
@click="handleOk"
>
确认支付
</a-button>
</div>
</div>
<!-- 用户扫聚合码 -->
<div v-if="vdata.imgIndex === 0">
<img
v-if="vdata.apiRes.payData"
:src="vdata.apiRes.payData"
alt=""
style="width: 200px; height: 200px"
>
<div class="zfb-wx" style="margin: 10px 0">
<img src="/src/assets/svg/alipay.svg" alt="">
<img
src="/src/assets/svg/wechatpay.svg"
alt=""
style="margin: 0 5px"
>
<span style="color: grey">支持支付宝与微信支付</span>
</div>
</div>
<!-- 提示文字 来自函数-->
<p style="font-size: 20px; font-weight: 500; color: grey">
{{ modeText(vdata.imgIndex) }}
</p>
<a-button
type="primary"
class="cancel"
size="large"
block
@click="vdata.SmallVisible = false"
>
取消收款
</a-button>
</div>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, getCurrentInstance, nextTick } from 'vue'
import {
API_URL_MCH_APP,
API_URL_MCH_STORE_LIST,
req,
$payTestOrder
} from '@/api/manage' // 接口
import { $getWebSocketPrefix } from '@/api/manage'
import ReconnectingWebSocket from 'reconnectingwebsocket'
import { message, Modal } from 'ant-design-vue'
// @ts-ignore
import smSvg from '@/assets/svg/sm.svg'
// @ts-ignore
import fkSvg from '@/assets/svg/fk.svg'
// @ts-ignore
import slSvg from '@/assets/svg/sl.svg'
// 获取全局函数
const { $infoBox, $access } =
getCurrentInstance()!.appContext.config.globalProperties
const vdata: any = reactive({
currentWayCode: 'QR_CASHIER', // 支付方式
mchAppList: [] as any, // app列表
appId: '', // 已选择的appId
mchStoreList: [] as any, // 门店列表
storeId: '', // 已选择的门店ID
loading: false, // 确认收款按钮loading
visible: false, // 弹出框标识
SmallVisible: false, // 小弹窗标识
orderTitle: '快捷收款', // 用于下单传参,写死即可
payOrderWebSocket: null as any, // 支付订单webSocket对象
numList: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '0', '00'], //键盘数字集合
imgIndex: 0, // 付款方式框高亮索引
authCode: '', // 条码值
apiRes: '', // 支付信息
params: {
// 传参对象
amount: '',
remark: ''
},
imgList: [
// 支付方式列表
{
imgSrc: smSvg,
text: '付款主扫',
color: '#000000',
key: 'QR_CASHIER'
},
{
imgSrc: fkSvg,
text: '付款被扫',
color: '#17c2b2',
key: 'AUTO_BAR'
},
{
imgSrc: slSvg,
text: '刷脸支付',
color: '#4784ff ',
key: 'AUTO_BAR'
}
]
})
// 获取条码输入框
const barCodeInput = ref()
// 关闭外层弹窗,则清空输入框
const clear = () => (vdata.params.amount = vdata.params.remark = '') // 清空输入框
// 关闭内层
const clearSmallModal = () => {
vdata.authCode = '' // 清空输入框
randomOrderNo() // 刷新订单号
}
// 同时关闭内外层弹窗并清空输入框
const closeAllModal = () => {
vdata.visible = false // 关闭大小弹窗
vdata.SmallVisible = false // 关闭大小弹窗
clear()
clearSmallModal()
}
// 小弹窗提示文字
const modeText = (index) => {
let text
switch (index) {
case 0:
text = '请扫描付款码收款'
break
case 1:
text = '请扫描二维码收款'
break
default:
text = '请对准摄像头刷脸支付'
}
return text
}
// 选择付款方式
const chooseType = (index, key) => {
vdata.imgIndex = index
vdata.currentWayCode = key
randomOrderNo() // 刷新单号
}
// 软键盘点击事件
const keyNumber = (item) => {
let currentVal = vdata.params.amount
currentVal == '0' && item != '.' ? (currentVal = item) : (currentVal += item)
if (!/^(([1-9]{1}\d{0,5})|(0{1}))(\.\d{0,2})?$/.test(currentVal)) return
vdata.params.amount = currentVal
}
// 删除按钮
const delet = () => {
let currentVal = vdata.params.amount
!currentVal || currentVal.length <= 1
? (vdata.params.amount = '')
: (vdata.params.amount = vdata.params.amount.substring(
0,
currentVal.length - 1
))
}
// 订单号
const randomOrderNo = () => {
vdata.mchOrderNo =
'M' +
new Date().getTime() +
Math.floor(Math.random() * (9999 - 1000) + 1000)
}
// 开启小弹窗 包含前置校验
const openSmallVisible = () => {
// 判断支付金额是否为0
if (!vdata.params.amount || vdata.params.amount == 0.0)
return $infoBox.message.error('请输入支付金额')
// 判断是否选择应用
if (vdata.appId === '' && $access('ENT_MCH_APP_LIST'))
return $infoBox.message.error('请选择一个应用')
// 判断是否选择门店
if (vdata.storeId === '') return $infoBox.message.error('请选择一个门店')
// 验证金额是否合法
if (!/^(([1-9]{1}\d{0,5})|(0{1}))(\.\d{0,2})?$/.test(vdata.params.amount))
return $infoBox.message.error('请输入合法金额')
vdata.SmallVisible = true // 开启弹窗
nextTick(() => {
// 弹窗展示后,输入框默认展示焦点
if (barCodeInput.value) barCodeInput.value.focus()
})
// 如果是聚合码支付 则直接吊起下单
if (vdata.currentWayCode == 'QR_CASHIER') reallyPay()
}
// 确认收款 下单
const reallyPay = () => {
vdata.loading = true
$payTestOrder({
wayCode: vdata.currentWayCode, // 支付方式
amount: vdata.params.amount, // 支付金额
appId: vdata.appId, // appId
storeId: vdata.storeId, // storeId
mchOrderNo: vdata.mchOrderNo, // 订单编号
payDataType: 'codeImgUrl', // 支付参数(二维码,条码)快捷收银都是走聚合支付
authCode: vdata.authCode, // 条码值
divisionMode: 0, // 分账模式 0代表不分账 快捷收银写死即可
orderTitle: vdata.orderTitle // 订单标题
})
.then((res) => {
vdata.apiRes = res
if (vdata.payOrderWebSocket) vdata.payOrderWebSocket.close() // 关闭上一个webSocket监听
// 此处判断接口中返回的orderState值为01 代表支付中直接放行无需处理2 成功 3 失败
if (res.orderState === 2 || res.orderState === 3) {
if (res.orderState === 2) {
handleClose()
const succModal = Modal.success({
title: '支付成功',
content: '2秒后自动关闭'
})
closeAllModal()
setTimeout(() => {
succModal.destroy()
}, 2000)
} else if (res.orderState === 3) {
handleClose()
Modal.error({
title: '支付失败',
content: `错误码:${res.errCode}`
})
}
vdata.loading = false
return
}
// 监听响应结果
// @ts-ignore
vdata.payOrderWebSocket = new ReconnectingWebSocket(
$getWebSocketPrefix() +
'/api/anon/ws/payOrder/' +
res.payOrderId +
'/' +
new Date().getTime()
)
vdata.payOrderWebSocket.onopen = () => {}
vdata.payOrderWebSocket.onmessage = (msgObject) => {
console.log(msgObject,'msgObjectmsgObject')
const resMsgObject = JSON.parse(msgObject.data)
if (resMsgObject.state === 2) {
handleClose()
const succModal = Modal.success({
title: '支付成功',
content: '2秒后自动关闭'
})
closeAllModal()
setTimeout(() => {
succModal.destroy()
}, 2000)
} else {
handleClose()
Modal.error({
title: '支付失败',
content: `错误码:${res.errCode}`
})
}
}
vdata.loading = false
randomOrderNo() // 刷新订单号
})
.catch(() => {
vdata.loading = false
randomOrderNo() // 刷新订单号
})
}
// 打开弹窗时请求应用列表与门店列表
const getList = () => {
// 是否有应用的权限
if ($access('ENT_MCH_APP_LIST')) {
// 请求接口获取所有的appid只有此处进行pageSize=-1传参
req.list(API_URL_MCH_APP, { pageSize: -1 }).then((res) => {
vdata.mchAppList = res.records
// 赋值默认值
if (vdata.mchAppList.length > 0) {
vdata.appId = vdata.mchAppList[0].appId
}
})
} else {
vdata.appId = '' // 没有权限就传空
}
// 请求接口获取商户所有门店只有次数进行pageSize=-1传参
req.list(API_URL_MCH_STORE_LIST, { pageSize: -1 }).then((res) => {
vdata.mchStoreList = res.records
if (vdata.mchStoreList.length > 0) {
vdata.storeId = vdata.mchStoreList[0].storeId // 赋予默认值
}
})
// 在进入页面时刷新订单号
randomOrderNo()
}
// 扫条码与刷脸确认按钮, 输入框输入内容后自动调用
function handleOk() {
if (!vdata.authCode) return
reallyPay() // 调起下单接口
}
// 清除WebSocket
function handleClose() {
if (vdata.payOrderWebSocket) {
vdata.payOrderWebSocket.close()
}
}
// 开启弹窗,供父组件调用
const showModal = () => {
vdata.visible = true
getList()
}
defineExpose({ showModal })
// 监听键盘事件
document.addEventListener('keydown', (e) => {
// 大弹窗开启,并且小弹窗未开启的情况下,回车掉起支付按钮
if (vdata.visible && !vdata.SmallVisible && e.keyCode === 13)
openSmallVisible()
// f1 f2 f3 快捷切换支付方式 (112 113 114)
})
</script>
<style lang="less">
.mode {
text-align: center;
font-size: 16px;
color: #000;
margin: 16px 0;
}
.boxs {
margin-bottom: 10px;
display: flex;
justify-content: space-between;
.mode-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border: 2px solid #e6e6e6;
border-radius: 10px;
box-sizing: border-box;
width: 96px;
height: 96px;
span {
margin-top: 10px;
}
}
}
.input {
span {
display: inline-block;
margin: 20px 0 10px 0;
}
}
// 键盘
.keyboard {
display: flex;
width: 100%;
// background: #0b6159;
// margin-top: 11px;
box-sizing: border-box;
// padding: 3PX;
.keyboard-num {
display: flex;
flex-flow: row wrap;
flex-grow: 1;
justify-content: flex-start;
align-items: center;
div {
width: 33%;
height: 60px;
background: var(--ant-primary-color);
font-size: 22px;
font-weight: 700;
text-align: center;
color: #ffffff;
line-height: 60px;
border: 2px solid #fff;
box-sizing: border-box;
user-select: none;
border-radius: 5px;
&:hover {
cursor: pointer;
}
&:active {
opacity: 0.8;
}
}
}
.operate {
width: 25%;
margin-right: 1px;
user-select: none;
div:nth-child(1) {
width: 100%;
height: 61px;
background: var(--ant-primary-color);
border: 2px solid #fff;
box-sizing: border-box;
border-radius: 5px;
&:active {
opacity: 0.8;
}
img {
width: 20px;
height: 14px;
margin: 23px 65px;
}
}
div:nth-child(2) {
width: 100%;
border-radius: 5px;
height: 182px;
background: #ffb22d;
border-radius: 0px 0px 5px 0px;
line-height: 184px;
font-size: 22px;
font-family: PingFang SC, PingFang SC-Bold;
font-weight: 700;
text-align: center;
color: #ffffff;
border: 2px solid #fff;
box-sizing: border-box;
&:active {
opacity: 0.8;
}
}
}
}
.noSelect {
display: flex;
align-items: center;
justify-content: center;
&:hover {
cursor: pointer;
}
}
// 小弹窗样式
.full-modal {
.ant-modal {
top: 50%;
padding-bottom: 0;
margin: 0 auto;
margin-top: -200px;
}
.ant-modal-content {
display: flex;
flex-direction: column;
height: 400px;
}
}
.modal-body {
width: 282px;
height: 352px;
display: flex;
position: relative;
padding: 24px;
left: 0;
top: 0;
flex-direction: column;
align-items: center;
justify-content: center;
.code {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
img {
width: 50px;
height: 50px;
margin: 0 auto;
}
.auth-code {
display: flex;
justify-content: space-between;
}
}
}
.cancel {
position: absolute;
bottom: 0;
left: 0;
// left: 50%;
// margin-left: -50px;
}
.select-id {
display: flex;
justify-content: space-between;
& > div {
display: flex;
flex-direction: column;
width: 45%;
flex-grow: 1;
p {
margin-bottom: 10px;
}
}
}
</style>

View File

@@ -0,0 +1,115 @@
<template>
<a-drawer
v-model:visible="data.isShow"
:title=" data.isAdd ? '新增角色' : '修改角色' "
width="600"
:mask-closable="false"
@close="data.isShow = false"
>
<a-form
ref="infoFormModel"
:model="data.saveObject"
:label-col="{span: 4}"
:rules="data.rules"
>
<a-form-item label="角色名称:" name="roleName">
<a-input :disabled="!$access('ENT_UR_ROLE_EDIT')" v-model:value="data.saveObject['roleName']" />
</a-form-item>
</a-form>
<!-- 角色权限分配 -->
<RoleDist ref="roleDist" />
<div class="drawer-btn-center">
<a-button
:style="{ marginRight: '8px' }"
@click="data.isShow = false"
>
<close-outlined />
取消
</a-button>
<a-button
type="primary"
:loading="data.confirmLoading"
@click="handleOkFunc"
>
<check-outlined />
保存
</a-button>
</div>
</a-drawer>
</template>
<script setup lang="ts">
import { API_URL_ROLE_LIST, req } from '@/api/manage'
import RoleDist from './RoleDist.vue'
import {ref,reactive,nextTick,getCurrentInstance} from 'vue'
import {message} from 'ant-design-vue'
const { $infoBox,$access } = getCurrentInstance()!.appContext.config.globalProperties
const props= defineProps ({
callbackFunc: { type: Function,default:null }
})
const roleDist = ref()
const infoFormModel = ref()
const data = reactive({
confirmLoading: false, // 显示确定按钮loading图标
isAdd: true, // 新增 or 修改页面标识
isShow: false, // 是否显示弹层/抽屉
saveObject: {}, // 数据对象
recordId: null, // 更新对象ID
rules: {
roleName: [
{ required: true, message: '请输入角色名称', trigger: 'blur' }
]
}
})
defineExpose({show})
function show(recordId) { // 弹层打开事件
data.isAdd = !recordId
data.saveObject = {} // 数据清空
data.confirmLoading = false // 关闭loading
if (infoFormModel.value !== undefined) {
infoFormModel.value.resetFields()
}
if (!data.isAdd) { // 修改信息 延迟展示弹层
data.recordId = recordId
req.getById(API_URL_ROLE_LIST, recordId).then(res => { data.saveObject = res })
data.isShow = true
} else {
data.isShow = true // 立马展示弹层信息
}
// 初始化角色权限分配功能
nextTick(()=>{
roleDist.value.initTree(recordId)
})
}
function handleOkFunc () { // 点击【确认】按钮事件
infoFormModel.value.validate().then(valid =>{
data.confirmLoading = true // 显示loading
// 保存选择的权限信息
const selectedEntIdList = roleDist.value.getSelectedEntIdList()
data.saveObject['entIdListStr'] = selectedEntIdList ? JSON.stringify(selectedEntIdList) : ''
if (data.isAdd) {
req.add(API_URL_ROLE_LIST, data.saveObject).then(res => {
message.success('新增成功')
data.isShow = false
props.callbackFunc() // 刷新列表
}).catch(res => { data.confirmLoading = false })
} else {
req.updateById(API_URL_ROLE_LIST, data.recordId, data.saveObject).then(res => {
message.success('修改成功')
data.isShow = false
props.callbackFunc() // 刷新列表
}).catch(res => { data.confirmLoading = false })
}
})
}
</script>

View File

@@ -0,0 +1,88 @@
<template>
<div style="padding-bottom:50px;">
<p v-if="vdata.hasEnt">请选择权限 </p>
<!-- 树状结构 -->
<a-tree v-if="vdata.hasEnt" v-model:checkedKeys="vdata.checkedKeys" :tree-data="vdata.treeData" :fieldNames="vdata.replaceFields" :checkable="true" />
</div>
</template>
<script setup lang="ts">
import { $getEntTree, API_URL_ROLE_ENT_RELA_LIST, req } from '@/api/manage'
import { reactive, getCurrentInstance } from 'vue'
// 获取全局函数
const { $access } = getCurrentInstance()!.appContext.config.globalProperties
const vdata = reactive({
hasEnt: $access('ENT_UR_ROLE_DIST'),
recordId: null, // 更新对象ID
treeData: [],
replaceFields: { key: 'entId', title: 'entName' }, // 配置替换字段
checkedKeys: [], // 已选中的节点
allEntList: {} // 由于antd vue关联操作无法直接获取到父ID, 需要内部自行维护一套数据结构 {entId: {pid, children}}
})
defineExpose({initTree,getSelectedEntIdList})
function initTree(recordId) { // 弹层打开事件
// 判断是否有权限访问
if (!vdata.hasEnt) {
return false
}
// 重置数据
vdata.checkedKeys = []
vdata.treeData = []
vdata.allEntList = {}
vdata.recordId = recordId
// 获取全部权限的树状结构
$getEntTree().then(res => {
vdata.treeData = res
// 存储所有的菜单权限集合
recursionTreeData(res, (item) => {
vdata.allEntList[item.entId] = { pid: item.pid, children: item.children || [] }
})
// 查询所有的已分配的权限集合 (默认为 0 无数据)
req.list(API_URL_ROLE_ENT_RELA_LIST, { roleId: recordId || 'NONE', pageSize: -1 }).then(res2 => {
const checkedEntIdList:any = [] // 所有已分配的权限集合兼容antd vue 仅保留子节点)
res2.records.map(item => {
if (vdata.allEntList[item.entId] && vdata.allEntList[item.entId].children.length <= 0) { // 说明是子节点
checkedEntIdList.push(item.entId)
}
})
vdata.checkedKeys = checkedEntIdList
})
})
}
function getSelectedEntIdList() { // 获取已选择的列表集合
// 判断是否有权限访问
if (!vdata.hasEnt) {
return false
}
const reqData:any = []
vdata.checkedKeys.map(item => {
const pidList:any = [] // 当前权限的所有的父节点IDList
getAllPid(item, pidList)
pidList.map(pid => {
if (reqData.indexOf(pid) < 0) {
reqData.push(pid)
}
})
})
return reqData
}
// 递归遍历树状结构数据
function recursionTreeData (entTreeData, func) {
for (let i = 0; i < entTreeData.length; i++) {
const thisEnt = entTreeData[i]
if (thisEnt.children && thisEnt.children.length > 0) {
recursionTreeData(thisEnt.children, func)
}
func(thisEnt)
}
}
function getAllPid (entId, array) { // 获取所有的PID
if (vdata.allEntList[entId] && entId !== 'ROOT') {
array.push(entId)
getAllPid(vdata.allEntList[entId].pid, array)
}
}
</script>

View File

@@ -0,0 +1,116 @@
<template>
<page-header-wrapper>
<a-card>
<JeepaySearchForm :searchFunc="searchFunc" :resetFunc="() => { data.searchData= {} }" v-if = "$access('ENT_UR_ROLE_SEARCH')">
<jeepay-text-up v-model:value="data.searchData['roleId']" :placeholder="'角色ID'" />
<jeepay-text-up v-model:value="data.searchData['roleName']" :placeholder="'角色名称'" />
</JeepaySearchForm>
<!-- 列表渲染 -->
<JeepayTable
ref="infoTable"
:init-data="true"
:req-table-data-func="reqTableDataFunc"
:table-columns="tableColumns"
:search-data="data.searchData"
row-key="roleName"
@btnLoadClose="data.btnLoading=false"
>
<template #topBtnSlot>
<div>
<a-button
v-if="$access('ENT_UR_ROLE_ADD')"
type="primary"
class="mg-b-30"
@click="addFunc"
>
<plus-outlined />
新建
</a-button>
</div>
</template>
<template #bodyCell="{column,record}">
<!-- {{ record }} -->
<template v-if="column.key === 'roleId'">
<b>{{ record.roleId }}</b>
</template>
<template v-if="column.key === 'op'">
<a v-if="$access('ENT_UR_ROLE_EDIT')" style="padding-left: 15px; padding-right: 15px;" @click="editFunc(record.roleId)">修改</a>
<a v-if="$access('ENT_UR_ROLE_DEL')" style="color: red" @click="delFunc(record.roleId)">删除</a>
</template>
</template>
</JeepayTable>
</a-card>
<!-- 新增 / 修改 页面组件 -->
<InfoAddOrEdit ref="infoAddOrEdit" :callback-func="searchFunc" />
</page-header-wrapper>
</template>
<script setup lang="ts">
import { API_URL_ROLE_LIST, req } from '@/api/manage'
import InfoAddOrEdit from './AddOrEdit.vue'
import {ref,reactive,getCurrentInstance} from 'vue'
// eslint-disable-next-line no-unused-vars
import {message} from 'ant-design-vue'
const { $infoBox,$access } = getCurrentInstance()!.appContext.config.globalProperties
const tableColumns =reactive([
{
key: 'roleId', // key为必填项用于标志该列的唯一
title: '角色ID',
sorter: true,
fixed: 'left',
},
{
key: 'roleName',
title: '角色名称',
dataIndex: 'roleName',
sorter: true
},
{
key: 'op',
title: '操作',
width: '200px',
fixed: 'right',
align: 'center',
}
])
const infoTable = ref()
const infoAddOrEdit = ref()
const data = reactive ({
tableColumns: tableColumns,
searchData: {},
btnLoading: false
})
// 请求table接口数据
const reqTableDataFunc =(params) => {
return req.list(API_URL_ROLE_LIST, params)
}
function searchFunc () { // 点击【查询】按钮点击事件
data.btnLoading = true // 打开查询按钮上的loading
infoTable.value.refTable(true)
}
function addFunc () { // 业务通用【新增】 函数
infoAddOrEdit.value.show()
}
function editFunc (recordId) { // 业务通用【修改】 函数
infoAddOrEdit.value.show(recordId)
}
function delFunc (recordId) { // 业务通用【删除】 函数
$infoBox.confirmDanger('确认删除?', '', () => {
// 需要【按钮】loading 请返回 promise对象 不需要请直接返回null
return req.delById(API_URL_ROLE_LIST, recordId).then(res => {
message.success('删除成功!')
infoTable.value.refTable(false)
})
})
}
</script>

View File

@@ -0,0 +1,208 @@
<template>
<page-header-wrapper>
<a-card class="table-card">
<JeepaySearchForm :searchFunc="searchFunc" :resetFunc="resetFunc">
<a-form-item label="" class="table-search-item">
<JeepayDateRangePicker
v-if="vdata.showDateTimeRage"
ref="dateRangePicker"
v-model:value="vdata.searchData['queryDateRange']"
customDateRangeType="date"
/>
</a-form-item>
<jeepay-text-up v-model:value="vdata.searchData['agentNo']" :placeholder="'服务商号'" />
<jeepay-text-up v-model:value="vdata.searchData['agentName']" :placeholder="'服务商名称'" />
</JeepaySearchForm>
<!-- 列表渲染 -->
<JeepayTable
ref="infoTable"
:init-data="false"
:req-table-data-func="reqTableDataFunc"
:table-columns="tableColumns"
:search-data="vdata.searchData"
row-key="agentNo"
:statisticsIsShow="true"
:tableExportFunc="tableExportFunc"
@btnLoadClose="btnLoading=false"
@handleTableChange="handleChange"
>
<template #headerCell="{ column }">
<span v-if="column.tooltipTitle">
{{ column.title }}
<a-tooltip :title="column.tooltipTitle"><info-circle-outlined /></a-tooltip>
</span>
</template>
<template #topBtnSlot>
<a-button type="primary" @click="tableExportFunc">
<plus-outlined />导出表格
</a-button>
</template>
<template #statistics>
<div class="statistics-list">
<div v-for="(item, index) in count" :key="index" class="item">
<div v-if="item.type == 'line'" class="line" />
<div class="title">{{ item.title }}</div>
<div v-if="item.title" class="amount" :style="{color: item.textColor}">
<span class="amount-num">{{ item.content }}</span>
</div>
</div>
</div>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'agentName'">
<a>
<b>{{ record.agentName }}</b>
</a>
</template>
<template v-if="column.key === 'totalSuccAmt'">
<b style="color: #15B86C;">{{ (record.totalSuccAmt / 100).toFixed(2) }}</b>
</template>
<template v-if="column.key === 'totalFinalAmt'">
<b style="color: #15B86C;">{{ (record.totalFinalAmt / 100).toFixed(2) }}</b>
</template>
<template v-if="column.key === 'totalEntryAmt'">
<b style="color: #15B86C;">{{ (record.totalEntryAmt / 100).toFixed(2) }}</b>
</template>
<template v-if="column.key === 'totalRefundAmt'">
<b style="color: #15B86C;">{{ (record.totalRefundAmt / 100).toFixed(2)}}</b>
</template>
<template v-if="column.key === 'totalRefundNum'">
<b style="color: #FF6848;">{{ record.totalRefundNum }}</b>
</template>
<template v-if="column.key === 'totalSuccNum'">
<b style="color: #15B86C;">{{ record.totalSuccNum }}/{{ record.totalNum }}</b>
</template>
<template v-if="column.key === 'totalFeeAmt'">
<a-tooltip overlayClassName="tooltip-box-name">
<template #title>
<span>收单手续费{{ (record.totalFeeAmt / 100).toFixed(2) }}</span><br>
<span>垫资手续费{{ ( record.totalCashFee / 100).toFixed(2) }}</span>
</template>
<b style="color: #FF6848;">{{ ((record.totalFeeAmt + record.totalCashFee) / 100).toFixed(2) }}</b>
</a-tooltip>
</template>
<template v-if="column.key === 'succRate'">
<b style="color: #FF8800;">{{ record.succRate }}%</b>
</template>
<template v-if="column.key === 'operation'">
<JeepayTableColumns>
<a-button v-if="$access('ENT_STATISTIC_MCH')" type="link" @click="toPage(record.agentNo, vdata.parameterDate)">商户统计</a-button>
</JeepayTableColumns>
</template>
</template>
</JeepayTable>
</a-card>
</page-header-wrapper>
</template>
<script setup lang="ts">
import { API_URL_STATISTIC, req, $exportExcel, exportExcelUrl } from '@/api/manage'
import { ref, reactive, onMounted, getCurrentInstance } from 'vue'
import router from '@/router'
import { useRoute } from 'vue-router'
import fileDownload from 'js-file-download'
import dateUtil from '@/utils/dateUtil.js'
const { $infoBox, $access, $hasAgentEnt } = getCurrentInstance()!.appContext.config.globalProperties
const infoDetail = ref()
const infoTable = ref()
let tableColumns = reactive([
{ key: 'agentName', title: '服务商名称', dataIndex: 'agentName', },
{ key: 'agentNo', title: '服务商号', dataIndex: 'agentNo', agentEntCol: true, },
{ key: 'totalSuccAmt', title: '成交金额', tooltipTitle: '支付成功的订单金额,包含部分退款及全额退款的订单', dataIndex: 'totalSuccAmt', },
{ key: 'totalRefundAmt', title: '退款金额', dataIndex: 'totalRefundAmt'},
{ key: 'totalRefundNum', title: '退款笔数', tooltipTitle: '实际退款订单笔数,若一笔已成交订单退款多次,则计多次', dataIndex: 'totalRefundNum'},
{ key: 'totalFeeAmt', title: '手续费', tooltipTitle: '成交订单产生的手续费金额', dataIndex: 'totalFeeAmt'},
{ key: 'totalEntryAmt', title: '入账金额', tooltipTitle: '扣除已退款金额及手续费,入账金额为商户实际到账金额', dataIndex: 'totalEntryAmt'},
{ key: 'totalSuccNum', title: '成交笔数/总交易笔数', dataIndex: 'totalSuccNum', },
{ key: 'succRate', title: '成功率', tooltipTitle: '成交笔数与总订单笔数除得的百分比', dataIndex: 'round',},
// { key: 'operation', title: '操作', fixed: 'right', align: 'center'}
])
let btnLoading = ref(false)
let count:any = ref([]) // 数据统计数组
const dateRangePicker = ref()
const vdata : any = reactive({
searchData: { method : 'agent', queryDateRange: 'today', sortField: 'totalSuccAmt', sortOrder: 'descend' },
showDateTimeRage: false,
parameterDate: 'today'
})
vdata.searchData['isvNo'] = useRoute().query.isvNo
onMounted(() => {
if(useRoute().query.queryDate) {
vdata.searchData['queryDateRange'] = dateUtil.parseQueryStringByRange(useRoute().query.queryDate)
}
searchFunc()
vdata.showDateTimeRage = true
})
function handleChange(sorter) {
vdata.searchData['sortField'] = sorter.columnKey
vdata.searchData['sortOrder'] = sorter.order
}
function resetFunc() {
dateRangePicker.value.returnSelectModel()
vdata.searchData = { method : 'agent', queryDateRange: 'today', sortField: 'totalSuccAmt', sortOrder: 'descend' }
}
function searchFunc() { // 点击【查询】按钮点击事件
vdata.parameterDate = vdata.searchData.queryDateRange
infoTable.value.refTable(true)
}
// 请求table接口数据
function reqTableDataFunc(params: any) {
params.method = 'agent'
if (!params.sortField) {
params.sortField = 'totalSuccAmt'
params.sortOrder = 'descend'
}
reqTableCountFunc(params)
return req.list(API_URL_STATISTIC, params)
}
// 请求table接口数据
function reqTableCountFunc(params: any) {
req.list(API_URL_STATISTIC + '/total', params).then(res => {
count.value = [
{title: '总成交金额', symbol: 'add', textColor: '#1A66FF', content: ((res.totalSuccAmt) / 100).toFixed(2)},
{type: 'line'},
{title: '总成交笔数', content: (res.totalSuccNum)},
{type: 'line'},
{title: '总退款金额', symbol: 'sub', content: ((res.totalRefundAmt) / 100).toFixed(2)},
{type: 'line'},
{title: '总退款笔数', content: (res.totalRefundNum)},
// {type: 'line'},
// {title: '支付成功率', content: (res.round) + '%'},
]
})
}
function tableExportFunc(){
return $exportExcel(exportExcelUrl.statistic, Object.assign({}, vdata.searchData, {})).then(res => {
fileDownload(res.data, '服务商统计.xlsx')
}).catch ((error) =>{console.log(error)} )
}
function toPage(recordId, date) {
if (date.indexOf('_7') > -1) {
date = 7
} else if (date.indexOf('_30') > -1) {
date = 30
} else if (date.indexOf('customDateTime') > -1) {
date = dateUtil.getCustomDateTime(date)
}
router.push({
path: '/statistic/mch',
query: { agentNo: recordId, queryDateRange: date }
})
}
</script>

View File

@@ -0,0 +1,206 @@
<template>
<page-header-wrapper>
<a-card class="table-card">
<JeepaySearchForm :searchFunc="searchFunc" :resetFunc="resetFunc">
<a-form-item label="" class="table-search-item">
<JeepayDateRangePicker
v-if="vdata.showDateTimeRage"
ref="dateRangePicker"
v-model:value="vdata.searchData['queryDateRange']"
customDateRangeType="date"
/>
</a-form-item>
<jeepay-text-up v-model:value="vdata.searchData.deviceNo" :placeholder="'设备号'" />
<JeepaySelect ref="deviceTypeRef" placeholder="设备类型" :reset="isReset">
<a-select v-model:value="vdata.searchData.deviceType" @change="deviceTypeRef.textupHandle()">
<a-select-option value="">全部</a-select-option>
<a-select-option value="0">电子码牌</a-select-option>
<a-select-option value="1">实体码牌</a-select-option>
<a-select-option value="2">实体立牌</a-select-option>
<a-select-option value="3">云音响码牌</a-select-option>
<a-select-option value="scan_pos">扫码POS</a-select-option>
<a-select-option value="auto_pos">智能POS</a-select-option>
<a-select-option value="cash_plugin">收银插件</a-select-option>
</a-select>
</JeepaySelect>
<jeepay-text-up v-model:value="vdata.searchData.agentNo" :placeholder="'服务商号'" />
<jeepay-text-up v-model:value="vdata.searchData.mchNo" :placeholder="'用户号'" />
</JeepaySearchForm>
<!-- 列表渲染 -->
<JeepayTable
ref="infoTable"
:init-data="false"
:req-table-data-func="reqTableDataFunc"
:table-columns="tableColumns"
:search-data="vdata.searchData"
row-key="ifCode"
:statisticsIsShow="true"
:tableExportFunc="tableExportFunc"
@btnLoadClose="btnLoading=false"
@handleTableChange="handleChange"
>
<template #headerCell="{ column }">
<span v-if="column.tooltipTitle">
{{ column.title }}
<a-tooltip :title="column.tooltipTitle"><info-circle-outlined /></a-tooltip>
</span>
</template>
<template #topBtnSlot>
<a-button type="primary" @click="tableExportFunc">
<plus-outlined />导出表格
</a-button>
</template>
<template #statistics>
<div class="statistics-list">
<div v-for="(item, index) in count" :key="index" class="item">
<div v-if="item.type == 'line'" class="line" />
<div class="title">{{ item.title }}</div>
<div v-if="item.title" class="amount" :style="{color: item.textColor}">
<span class="amount-num">{{ item.content }}</span>
</div>
</div>
</div>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'totalSuccAmt'">
<b style="color: #15B86C;">{{ (record.totalSuccAmt / 100).toFixed(2) }}</b>
</template>
<template v-if="column.key === 'totalFinalAmt'">
<b style="color: #15B86C;">{{ (record.totalFinalAmt / 100).toFixed(2) }}</b>
</template>
<template v-if="column.key === 'totalEntryAmt'">
<b style="color: #15B86C;">{{ (record.totalEntryAmt / 100).toFixed(2) }}</b>
</template>
<template v-if="column.key === 'totalRefundAmt'">
<b style="color: #15B86C;">{{ (record.totalRefundAmt / 100).toFixed(2)}}</b>
</template>
<template v-if="column.key === 'totalRefundNum'">
<b style="color: #FF6848;">{{ record.totalRefundNum }}</b>
</template>
<template v-if="column.key === 'totalSuccNum'">
<b style="color: #15B86C;">{{ record.totalSuccNum }}/{{ record.totalNum }}</b>
</template>
<template v-if="column.key === 'succRate'">
<b style="color: #FF8800;">{{ record.succRate }}%</b>
</template>
<template v-if="column.key === 'totalFeeAmt'">
<a-tooltip overlayClassName="tooltip-box-name">
<template #title>
<span>收单手续费{{ (record.totalFeeAmt / 100).toFixed(2) }}</span><br>
<span>垫资手续费{{ ( record.totalCashFee / 100).toFixed(2) }}</span>
</template>
<b style="color: #FF6848;">{{ ((record.totalFeeAmt + record.totalCashFee) / 100).toFixed(2) }}</b>
</a-tooltip>
</template>
<template v-if="column.key === 'deviceType'">
<a-tag v-if="record.deviceType != 'qr_code'" :color="record.deviceType == 'qr_code' ? '#2db7f5' : record.deviceType == 'scan_pos' ? '#87d068' : record.deviceType == 'auto_pos' ? '#531dab' : record.deviceType == 'cash_plugin' ? '#f50' : ''">
{{ record.deviceType == 'qr_code' ? '码牌' : record.deviceType == 'scan_pos' ? '扫码POS' : record.deviceType == 'auto_pos' ? '智能POS' : record.deviceType == 'cash_plugin' ? '收银插件' : '其他' }}
</a-tag>
<a-tag v-else :color="record.deviceType == 'qr_code' ? '#2db7f5' : record.deviceType == 'scan_pos' ? '#87d068' : record.deviceType == 'auto_pos' ? '#531dab' : record.deviceType == 'cash_plugin' ? '#f50' : ''">
{{ record.qrcType == 0 ? '电子码牌' : record.qrcType == 1 ? '实体码牌' : record.qrcType == 2 ? '实体立牌' : record.qrcType == 3 ? '云音响码牌' : '其他' }}
</a-tag>
</template>
</template>
</JeepayTable>
</a-card>
</page-header-wrapper>
</template>
<script setup lang="ts">
import { API_URL_STATISTIC, req, $exportExcel, exportExcelUrl } from '@/api/manage'
import { ref, reactive, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import fileDownload from 'js-file-download'
import dateUtil from '@/utils/dateUtil.js'
const deviceTypeRef = ref()
const infoTable = ref()
let tableColumns = reactive([
{ key: 'deviceType', title: '设备类型', dataIndex: 'deviceType', },
{ key: 'deviceNo', title: '设备号', dataIndex: 'deviceNo', },
{ key: 'deviceName', title: '设备名称', dataIndex: 'deviceName', },
{ key: 'totalSuccAmt', title: '成交金额', tooltipTitle: '支付成功的订单金额,包含部分退款及全额退款的订单', dataIndex: 'totalSuccAmt', },
{ key: 'totalRefundAmt', title: '退款金额', dataIndex: 'totalRefundAmt'},
{ key: 'totalRefundNum', title: '退款笔数', tooltipTitle: '实际退款订单笔数,若一笔已成交订单退款多次,则计多次', dataIndex: 'totalRefundNum'},
{ key: 'totalFeeAmt', title: '手续费', tooltipTitle: '成交订单产生的手续费金额', dataIndex: 'totalFeeAmt'},
{ key: 'totalEntryAmt', title: '入账金额', tooltipTitle: '扣除已退款金额及手续费,入账金额为商户实际到账金额', dataIndex: 'totalEntryAmt'},
{ key: 'totalSuccNum', title: '成交笔数/总交易笔数', dataIndex: 'totalSuccNum', },
{ key: 'succRate', title: '成功率', tooltipTitle: '成交笔数与总订单笔数除得的百分比', dataIndex: 'succRate', },
])
let btnLoading = ref(false)
let count:any = ref([]) // 数据统计数组
const dateRangePicker = ref()
const vdata : any = reactive({
searchData: { method : 'device', queryDateRange: 'today', sortField: 'totalSuccAmt', sortOrder: 'descend' },
showDateTimeRage: false
})
onMounted(() => {
if(useRoute().query.queryDate) {
vdata.searchData['queryDateRange'] = dateUtil.parseQueryStringByRange(useRoute().query.queryDate)
}
searchFunc()
vdata.showDateTimeRage = true
})
function handleChange(sorter) {
vdata.searchData['sortField'] = sorter.columnKey
vdata.searchData['sortOrder'] = sorter.order
}
function resetFunc() {
dateRangePicker.value.returnSelectModel()
vdata.searchData = { method : 'device', queryDateRange: 'today', sortField: 'totalSuccAmt', sortOrder: 'descend' }
}
// 请求table接口数据
function reqTableDataFunc(params: any) {
params.method = 'device'
if (!params.sortField) {
params.sortField = 'totalSuccAmt'
params.sortOrder = 'descend'
}
reqTableCountFunc(params)
return req.list(API_URL_STATISTIC, params)
}
function searchFunc () { // 点击【查询】按钮点击事件
infoTable.value.refTable(true)
}
// 请求table接口数据
function reqTableCountFunc(params: any) {
req.list(API_URL_STATISTIC + '/total', params).then(res => {
count.value = [
{title: '总成交金额', symbol: 'add', textColor: '#1A66FF', content: ((res.totalSuccAmt) / 100).toFixed(2)},
{type: 'line'},
{title: '总成交笔数', content: (res.totalSuccNum)},
{type: 'line'},
{title: '总退款金额', symbol: 'sub', content: ((res.totalRefundAmt) / 100).toFixed(2)},
{type: 'line'},
{title: '总退款笔数', content: (res.totalRefundNum)},
// {type: 'line'},
// {title: '支付成功率', content: (res.round) + '%'},
]
})
}
function tableExportFunc(){
return $exportExcel(exportExcelUrl.statistic, Object.assign({}, vdata.searchData, {})).then(res => {
fileDownload(res.data, '设备统计.xlsx')
}).catch ((error) =>{console.log(error)} )
}
let isReset = ref(0) // 下拉搜索框是否重置
function onReset(){
isReset.value++ // 下拉搜索框重置
//重置搜索内容
vdata.searchData = {}
}
</script>

View File

@@ -0,0 +1,228 @@
<template>
<page-header-wrapper>
<a-card class="table-card">
<JeepaySearchForm :searchFunc="searchFunc" :resetFunc="resetFunc">
<a-form-item label="" class="table-search-item">
<JeepayDateRangePicker
v-if="vdata.showDateTimeRage"
ref="dateRangePicker"
v-model:value="vdata.searchData['queryDateRange']"
customDateRangeType="date"
/>
</a-form-item>
<JeepaySearchInfoInput
v-model:value="vdata.searchData.mchNo"
placeholder="用户号"
:textUpStyle="true"
:mchNoAndName="true"
showType="MCH"
/>
<jeepay-text-up v-model:value="vdata.searchData['mchName']" :placeholder="'用户名称'" />
<jeepay-text-up v-model:value="vdata.searchData['agentNo']" :placeholder="'代理商号'" />
</JeepaySearchForm>
<!-- 列表渲染 -->
<JeepayTable
ref="infoTable"
:init-data="false"
:req-table-data-func="reqTableDataFunc"
:table-columns="tableColumns"
:statisticsIsShow="true"
:search-data="vdata.searchData"
row-key="mchNo"
:tableExportFunc="tableExportFunc"
@btnLoadClose="btnLoading=false"
@handleTableChange="handleChange"
>
<template #headerCell="{ column }">
<span v-if="column.tooltipTitle">
{{ column.title }}
<a-tooltip :title="column.tooltipTitle"><info-circle-outlined /></a-tooltip>
</span>
</template>
<template #topBtnSlot>
<a-button type="primary" @click="tableExportFunc">
<plus-outlined />导出表格
</a-button>
</template>
<template #statistics>
<div class="statistics-list">
<div v-for="(item, index) in count" :key="index" class="item">
<div v-if="item.type == 'line'" class="line" />
<div class="title">{{ item.title }}</div>
<div v-if="item.title" class="amount" :style="{color: item.textColor}">
<span class="amount-num">{{ item.content }}</span>
</div>
</div>
</div>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'mchName'">
<a>
<b>{{ record.mchName }}</b>
</a>
</template>
<template v-if="column.key === 'totalSuccAmt'">
<b style="color: #15B86C;">{{ (record.totalSuccAmt / 100).toFixed(2) }}</b>
</template>
<template v-if="column.key === 'totalFinalAmt'">
<b style="color: #15B86C;">{{ (record.totalFinalAmt / 100).toFixed(2) }}</b>
</template>
<template v-if="column.key === 'totalEntryAmt'">
<b style="color: #15B86C;">{{ (record.totalEntryAmt / 100).toFixed(2) }}</b>
</template>
<template v-if="column.key === 'totalFeeAmt'">
<a-tooltip overlayClassName="tooltip-box-name">
<template #title>
<span>收单手续费{{ (record.totalFeeAmt / 100).toFixed(2) }}</span><br>
<span>垫资手续费{{ ( record.totalCashFee / 100).toFixed(2) }}</span>
</template>
<b style="color: #FF6848;">{{ ((record.totalFeeAmt + record.totalCashFee) / 100).toFixed(2) }}</b>
</a-tooltip>
</template>
<template v-if="column.key === 'totalRefundAmt'">
<b style="color: #15B86C;">{{ (record.totalRefundAmt / 100).toFixed(2)}}</b>
</template>
<template v-if="column.key === 'totalRefundFeeAmt'">
<b style="color: #FF6848;">{{ (record.totalRefundFeeAmt / 100).toFixed(2) }}</b>
</template>
<template v-if="column.key === 'totalRefundNum'">
<b style="color: #FF6848;">{{ record.totalRefundNum }}</b>
</template>
<template v-if="column.key === 'totalSuccNum'">
<b style="color: #15B86C;">{{ record.totalSuccNum }}/{{ record.totalNum }}</b>
</template>
<template v-if="column.key === 'succRate'">
<b style="color: #FF8800;">{{ record.succRate }}%</b>
</template>
<template v-if="column.key === 'operation'">
<JeepayTableColumns>
<a-button type="link" @click="detailFunc(record.mchNo, vdata.parameterDate)">明细</a-button>
</JeepayTableColumns>
</template>
</template>
</JeepayTable>
</a-card>
<!-- 明细页面组件 -->
<billDetail ref="accountDetail" :callback-func="searchFunc" />
<!-- 商户详情页面组件 -->
<InfoDetail ref="infoDetail" :callback-func="searchFunc" />
</page-header-wrapper>
</template>
<script setup lang="ts">
import { API_URL_STATISTIC, req, $exportExcel, exportExcelUrl } from '@/api/manage'
import { ref, reactive, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import billDetail from './accountDetail.vue'
import fileDownload from 'js-file-download'
import dateUtil from '@/utils/dateUtil.js'
const accountDetail = ref()
const infoTable = ref()
let tableColumns = reactive([
{ key: 'mchName', title: '用户名称', dataIndex: 'mchName', },
{ key: 'mchNo', title: '用户号', dataIndex: 'mchNo',},
{ key: 'totalSuccAmt', title: '成交金额', tooltipTitle: '支付成功的订单金额,包含部分退款及全额退款的订单', dataIndex: 'totalSuccAmt', },
{ key: 'totalRefundAmt', title: '退款金额', dataIndex: 'totalRefundAmt'},
{ key: 'totalRefundNum', title: '退款笔数', tooltipTitle: '实际退款订单笔数,若一笔已成交订单退款多次,则计多次', dataIndex: 'totalRefundNum'},
{ key: 'totalFeeAmt', title: '手续费', tooltipTitle: '成交订单所产生的交易手续费和垫资手续费', dataIndex: 'totalFeeAmt'},
{ key: 'totalEntryAmt', title: '入账金额', tooltipTitle: '扣除已退款金额及手续费,入账金额为商户实际到账金额', dataIndex: 'totalEntryAmt'},
{ key: 'totalSuccNum', title: '成交笔数/总交易笔数', dataIndex: 'totalSuccNum', },
{ key: 'succRate', title: '成功率', tooltipTitle: '成交笔数与总订单笔数除得的百分比', dataIndex: 'succRate', },
{ key: 'operation', title: '操作', fixed: 'right', align: 'center', }
])
let btnLoading = ref(false)
let count:any = ref([]) // 数据统计数组
const dateRangePicker = ref()
const vdata : any = reactive({
searchData: { method : 'mch', queryDateRange: 'today', sortField: 'totalSuccAmt', sortOrder: 'descend' },
showDateTimeRage: false, //是否显示时间搜索组件
parameterDate: 'today'
})
vdata.searchData['agentNo'] = useRoute().query.agentNo
vdata.searchData['isvNo'] = useRoute().query.isvNo
onMounted(() => {
if (useRoute().query.queryDate) {
vdata.searchData['queryDateRange'] = dateUtil.parseQueryStringByRange(useRoute().query.queryDate)
} else if (useRoute().query.queryDateRange) {
let date = useRoute().query.queryDateRange as any
if (date == 7) {
date = 'near2now_7'
} else if (date == 30) {
date = 'near2now_30'
} else if (date.indexOf('_') > -1) {
date = dateUtil.parseQueryStringByRange(date)
}
vdata.searchData['queryDateRange'] = date
}
searchFunc()
vdata.showDateTimeRage = true
})
function handleChange(sorter) {
vdata.searchData['sortField'] = sorter.columnKey
vdata.searchData['sortOrder'] = sorter.order
}
function resetFunc() {
dateRangePicker.value.returnSelectModel()
vdata.searchData = { method : 'mch', queryDateRange: 'today', sortField: 'totalSuccAmt', sortOrder: 'descend' }
}
// 请求table接口数据
function reqTableDataFunc(params: any) {
params.method = 'mch'
if (!params.sortField) {
params.sortField = 'totalSuccAmt'
params.sortOrder = 'descend'
}
reqTableCountFunc(params)
return req.list(API_URL_STATISTIC, params)
}
// 请求table接口数据
function reqTableCountFunc(params: any) {
req.list(API_URL_STATISTIC + '/total', params).then(res => {
count.value = [
{title: '总成交金额', symbol: 'add', textColor: '#1A66FF', content: ((res.totalSuccAmt) / 100).toFixed(2)},
{type: 'line'},
{title: '总成交笔数', content: (res.totalSuccNum)},
{type: 'line'},
{title: '总退款金额', symbol: 'sub', content: ((res.totalRefundAmt) / 100).toFixed(2)},
{type: 'line'},
{title: '总退款笔数', content: (res.totalRefundNum)},
// {type: 'line'},
// {title: '支付成功率', content: (res.round) + '%'},
]
})
}
function searchFunc() { // 点击【查询】按钮点击事件
vdata.parameterDate = vdata.searchData.queryDateRange
infoTable.value.refTable(true)
}
function detailFunc(recordId, date) {
accountDetail.value.show(recordId, date)
}
function tableExportFunc(){
return $exportExcel(exportExcelUrl.statistic, Object.assign({}, vdata.searchData, {})).then(res => {
fileDownload(res.data, '商户统计.xlsx')
}).catch ((error) =>{console.log(error)} )
}
// function infoTableSelectChangeFunc(selectedRowKeys){
// // 遍历 vdata.listRecords 如果selectedRowKeys数组中存在对应的id,则将其checked改为true,反之false
// vdata.listRecords.forEach(element => {
// selectedRowKeys.includes(element.qrcId) ? element.checked = true : element.checked = false
// })
// }
</script>

View File

@@ -0,0 +1,85 @@
<template>
<a-drawer
v-model:visible="vdata.visible"
:destroyOnClose="true"
:title="'统计明细'"
:body-style="{ paddingBottom: '80px' }"
width="80%"
@close="onClose"
>
<a-tabs v-model:activeKey="vdata.activeKey" :size="'large'">
<a-tab-pane key="1" tab="门店统计">
<accountDetailPane :method="'store'" :tableColumns="tableStoreColumns" :mchNo="vdata.mchNo" :date="vdata.date" />
</a-tab-pane>
<a-tab-pane key="2" tab="支付方式统计">
<accountDetailPane :method="'wayCode'" :tableColumns="tableWayCodeColumns" :mchNo="vdata.mchNo" :date="vdata.date" />
</a-tab-pane>
<a-tab-pane key="3" tab="通道统计">
<accountDetailPane :method="'wayCodeType'" :tableColumns="tableChannelColumns" :mchNo="vdata.mchNo" :date="vdata.date" />
</a-tab-pane>
</a-tabs>
</a-drawer>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue'
import accountDetailPane from './accountDetailPane.vue'
const vdata: any = reactive({
visible: false,
mchNo: '',
activeKey: '1',
date: ''
})
// 门店表头
let tableStoreColumns = reactive([
{ key: 'storeName', title: '门店名称', dataIndex: 'storeName', },
{ key: 'storeId', title: '门店编号', dataIndex: 'storeId', },
{ key: 'totalSuccAmt', title: '成交金额', tooltipTitle: '支付成功的订单金额,包含部分退款及全额退款的订单', dataIndex: 'totalSuccAmt', },
{ key: 'totalRefundAmt', title: '退款金额', dataIndex: 'totalRefundAmt'},
{ key: 'totalRefundNum', title: '退款笔数', tooltipTitle: '实际退款订单笔数,若一笔已成交订单退款多次,则计多次', dataIndex: 'totalRefundNum'},
{ key: 'totalFeeAmt', title: '手续费', tooltipTitle: '成交订单所产生的交易手续费和垫资手续费', dataIndex: 'totalFeeAmt'},
{ key: 'totalEntryAmt', title: '入账金额', tooltipTitle: '扣除已退款金额及手续费,入账金额为商户实际到账金额', dataIndex: 'totalEntryAmt'},
// { key: 'totalFeeAmt', title: '手续费', tooltipTitle: '成交订单产生的手续费金额' , dataIndex: 'totalFeeAmt', },
// { key: 'totalRefundAmt', title: '退款金额', dataIndex: 'totalRefundAmt', },
// { key: 'totalRefundFeeAmt', title: '手续费回退', tooltipTitle: '退款订单产生的手续费退费金额' , dataIndex: 'totalRefundFeeAmt', },
// { key: 'totalRefundNum', title: '退款笔数', tooltipTitle: '实际退款订单笔数,若一笔已成交订单退款多次,则计多次', dataIndex: 'totalRefundNum', },
{ key: 'totalSuccNum', title: '成交笔数/总交易笔数', dataIndex: 'totalSuccNum', },
{ key: 'succRate', title: '成功率', tooltipTitle: '成交笔数与总订单笔数除得的百分比', dataIndex: 'succRate', },
])
// 支付方式表头
let tableWayCodeColumns = reactive([
{ key: 'name', title: '支付类型',},
{ key: 'totalSuccAmt', title: '成交金额', tooltipTitle: '支付成功的订单金额,包含部分退款及全额退款的订单', dataIndex: 'totalSuccAmt', },
{ key: 'totalRefundAmt', title: '退款金额', dataIndex: 'totalRefundAmt'},
{ key: 'totalRefundNum', title: '退款笔数', tooltipTitle: '实际退款订单笔数,若一笔已成交订单退款多次,则计多次', dataIndex: 'totalRefundNum'},
{ key: 'totalEntryAmt', title: '入账金额', tooltipTitle: '扣除已退款金额及手续费,入账金额为商户实际到账金额', dataIndex: 'totalEntryAmt'},
{ key: 'totalSuccNum', title: '成交笔数/总交易笔数', dataIndex: 'totalSuccNum', },
{ key: 'succRate', title: '成功率', tooltipTitle: '成交笔数与总订单笔数除得的百分比', dataIndex: 'succRate', },
])
// 支付类型(渠道)表头
let tableChannelColumns = reactive([
// { key: 'wayType', title: '支付渠道', dataIndex: 'wayType', },
{ key: 'name', title: '渠道名称',},
{ key: 'totalSuccAmt', title: '成交金额', tooltipTitle: '支付成功的订单金额,包含部分退款及全额退款的订单', dataIndex: 'totalSuccAmt', },
{ key: 'totalRefundAmt', title: '退款金额', dataIndex: 'totalRefundAmt'},
{ key: 'totalRefundNum', title: '退款笔数', tooltipTitle: '实际退款订单笔数,若一笔已成交订单退款多次,则计多次', dataIndex: 'totalRefundNum'},
{ key: 'totalEntryAmt', title: '入账金额', tooltipTitle: '扣除已退款金额及手续费,入账金额为商户实际到账金额', dataIndex: 'totalFinalAmt'},
{ key: 'totalSuccNum', title: '成交笔数/总交易笔数', dataIndex: 'totalSuccNum', },
{ key: 'succRate', title: '成功率', tooltipTitle: '成交笔数与总订单笔数除得的百分比', dataIndex: 'succRate', },
])
function show(recordId, date) { // 弹层打开事件
vdata.activeKey = '1'
vdata.visible = true
vdata.mchNo = recordId
vdata.date = date
}
function onClose() {
vdata.visible = false
}
defineExpose({
show //抛出show函数给父组件
})
</script>

View File

@@ -0,0 +1,167 @@
<template>
<a-card class="table-card">
<JeepaySearchForm :searchFunc="searchFunc" :resetFunc="() => { vdata.searchData = {} }">
<a-form-item label="" class="table-search-item">
<JeepayDateRangePicker
ref="dateRangePicker"
v-model:value="vdata.searchData['queryDateRange']"
customDateRangeType="date"
/>
</a-form-item>
<JeepaySearchInfoInput v-if="props.method == 'store'" v-model:value="vdata.searchData['storeId']" placeholder="门店ID" :textUpStyle="true" showType="MCH_STORE" />
<jeepay-text-up v-if="props.method == 'wayCode'" v-model:value="vdata.searchData['wayCode']" :placeholder="'支付代码'" />
<JeepaySelect>
<a-select v-if="props.method == 'wayCodeType'" v-model:value="vdata.searchData['wayType']" :placeholder="'支付渠道'" defaultValue="">
<a-select-option value="">全部</a-select-option>
<a-select-option value="ALIPAY">支付宝</a-select-option>
<a-select-option value="YSFPAY">云闪付</a-select-option>
<a-select-option value="WECHAT">微信</a-select-option>
<a-select-option value="UNIONPAY">银联</a-select-option>
<a-select-option value="DCEPPAY">数字人民币</a-select-option>
<a-select-option value="OTHER">其他</a-select-option>
</a-select>
</JeepaySelect>
</JeepaySearchForm>
<!-- 列表渲染 -->
<JeepayTable
ref="infoTable"
:init-data="true"
:req-table-data-func="reqTableDataFunc"
:table-columns="props.tableColumns"
:statisticsIsShow="true"
:search-data="vdata.searchData"
row-key="mchNo"
:tableExportFunc="tableExportFunc"
@btnLoadClose="btnLoading=false"
>
<template #headerCell="{ column }">
<span v-if="column.tooltipTitle">
{{ column.title }}
<a-tooltip :title="column.tooltipTitle"><info-circle-outlined /></a-tooltip>
</span>
</template>
<template #topBtnSlot>
<a-button type="primary" @click="tableExportFunc">
<plus-outlined />导出表格
</a-button>
</template>
<template #statistics>
<div class="statistics-list">
<div v-for="(item, index) in count" :key="index" class="item">
<div v-if="item.type == 'line'" class="line" />
<div class="title">{{ item.title }}</div>
<div v-if="item.title" class="amount" :style="{ color: item.textColor }">
<span class="amount-num">{{ item.content }}</span>
</div>
</div>
</div>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'storeName'">
<a>
<b>{{ record.storeName }}</b>
</a>
</template>
<template v-if="column.key === 'totalSuccAmt'">
<b style="color: #15B86C;">{{ (record.totalSuccAmt / 100).toFixed(2) }}</b>
</template>
<template v-if="column.key === 'totalEntryAmt'">
<b style="color: #15B86C;">{{ (record.totalEntryAmt / 100).toFixed(2) }}</b>
</template>
<template v-if="column.key === 'totalFeeAmt'">
<a-tooltip overlayClassName="tooltip-box-name">
<template #title>
<span>收单手续费{{ (record.totalFeeAmt / 100).toFixed(2) }}</span><br>
<span>垫资手续费{{ ( record.totalCashFee / 100).toFixed(2) }}</span>
</template>
<b style="color: #FF6848;">{{ ((record.totalFeeAmt + record.totalCashFee) / 100).toFixed(2) }}</b>
</a-tooltip>
</template>
<template v-if="column.key === 'totalRefundAmt'">
<b style="color: #15B86C;">{{ (record.totalRefundAmt / 100).toFixed(2)}}</b>
</template>
<template v-if="column.key === 'totalRefundFeeAmt'">
<b style="color: #FF6848;">{{ (record.totalRefundFeeAmt / 100).toFixed(2) }}</b>
</template>
<template v-if="column.key === 'totalRefundNum'">
<b style="color: #FF6848;">{{ record.totalRefundNum }}</b>
</template>
<template v-if="column.key === 'totalSuccNum'">
<b style="color: #15B86C;">{{ record.totalSuccNum }}/{{ record.totalNum }}</b>
</template>
<template v-if="column.key === 'succRate'">
<b style="color: #FF8800;">{{ record.succRate }}%</b>
</template>
<!-- <template v-if="column.key === 'wayTypeName'">-->
<!-- {{ record.wayType == 'WECHAT'?'微信':record.wayType == 'ALIPAY'?'支付宝':record.wayType == 'YSFPAY'?'云闪付':record.wayType == 'UNIONPAY'?'银联':record.wayType == 'DCEPPAY'?'数字人民币':'其他' }}-->
<!-- </template>-->
<template v-if="column.key === 'name'">
{{ record.name }}
</template>
</template>
</JeepayTable>
</a-card>
</template>
<script lang="ts" setup>
import { API_URL_STATISTIC, $exportExcel, exportExcelUrl, req } from '@/api/manage'
import { defineProps, reactive, ref } from 'vue'
import fileDownload from 'js-file-download'
const infoTable = ref()
const props = defineProps({
method: { type: String, default: 'store' },
tableColumns: { type: Array, default: [] },
mchNo: { type: String, default: '' },
date: { type: String, default: '' }
})
const vdata: any = reactive({
searchData: { method: 'store', mchNo: '', queryDateRange: props.date },
tableColumns: []
})
let btnLoading = ref(false)
let count: any = ref([]) // 数据统计数组
// 请求table接口数据
function reqTableDataFunc(params: any) {
params.mchNo = props.mchNo
params.method = props.method
params.queryDateRange = props.date
vdata.tableIsShow = true
reqTableCountFunc(params)
return req.list(API_URL_STATISTIC, params)
}
function searchFunc() { // 点击【查询】按钮点击事件
infoTable.value.refTable(true)
}
// 请求table接口数据
function reqTableCountFunc(params: any) {
req.list(API_URL_STATISTIC + '/total', params).then(res => {
count.value = [
{title: '总成交金额', symbol: 'add', textColor: '#1A66FF', content: ((res.totalSuccAmt) / 100).toFixed(2)},
{type: 'line'},
{title: '总成交笔数', content: (res.totalSuccNum)},
{type: 'line'},
{title: '总退款金额', symbol: 'sub', content: ((res.totalRefundAmt) / 100).toFixed(2)},
{type: 'line'},
{title: '总退款笔数', content: (res.totalRefundNum)},
]
})
}
function tableExportFunc() {
return $exportExcel(exportExcelUrl.statistic, Object.assign({}, vdata.searchData, {mchNo: props.mchNo, method: props.method})).then(res => {
fileDownload(res.data, props.method == 'store'?'门店统计.xlsx':props.method == 'wayCode'?'支付方式统计.xlsx':props.method == 'wayCodeType'?'通道统计.xlsx':'商户统计.xlsx')
}).catch((error) => { console.log(error) })
}
</script>
<style scoped>
.table-layer .table-search-item {
width: calc(100% / 6);
}
</style>

View File

@@ -0,0 +1,74 @@
<template>
<a-form-item label="" class="table-search-item">
<a-select :value="props.cycleType" @change="changeType">
<a-select-option value="date">日报</a-select-option>
<a-select-option value="month">月报</a-select-option>
<a-select-option value="year">年报</a-select-option>
</a-select>
</a-form-item>
<a-space direction="vertical" :size="12">
<a-range-picker v-if="props.cycleType === 'date'" v-model:value="vdata.date" :disabled-date="disabledDate" @change="changeDate" />
<a-range-picker v-else-if="props.cycleType === 'month'" v-model:value="vdata.date" picker="month" @change="changeDate" />
<a-range-picker v-else-if="props.cycleType === 'year'" v-model:value="vdata.date" picker="year" @change="changeDate" />
</a-space>
</template>
<script setup lang="ts">
import { reactive, onMounted, watch } from 'vue'
import dayjs, { Dayjs } from 'dayjs'
const emit = defineEmits(['changeDate', 'changeDateType'])
const disabledDate = (current: Dayjs) => {
return current && current > dayjs().subtract(1).startOf('day')
}
const props = defineProps({
cycleType: { type: String, default:'date' },
date: { type: String, default: '' }
})
const vdata = reactive({
type: '日报',
cycleType: 'date',//周期类型
date: [] as any,
startDate: dayjs().subtract(30, 'day'),
endDate: dayjs().subtract(1, 'day')
})
onMounted(() => {
if (!props.date) {
vdata.date = [dayjs(vdata.startDate, 'YYYY-MM-DD'), dayjs(vdata.endDate, 'YYYY-MM-DD')]
}
})
// 重置按钮不能直接重置时间选择需要通过watch监听进行重置
watch( () => props.date ,(newVal,oldVal)=>{
if (!props.date) {
vdata.date = []
}
})
function changeType(e) {
vdata.cycleType = e
vdata.date = []
emit('changeDateType', vdata.cycleType)
// 清空时间具体值,保留时间类型
emit('changeDate', { timeParams: '' })
}
function changeDate(e) {
let unit = vdata.cycleType
if (vdata.cycleType == 'date') {
unit = 'day'
}
let start = e[0].startOf(unit).format('YYYY-MM-DD HH:mm:ss')
let end = e[1].endOf(unit).format('YYYY-MM-DD HH:mm:ss')
let timeParameter = 'customDateTime_' + start + '_' + end
let dateObject = reactive({
startDate: start,
endDate: end,
timeParams: timeParameter
})
emit('changeDate', dateObject)
}
</script>

View File

@@ -0,0 +1,239 @@
<template>
<page-header-wrapper>
<a-card class="table-card">
<JeepaySearchForm :searchFunc="searchFunc" :resetFunc="resetFunc">
<JeepayDateCascadeSelection
:date="vdata.searchData.queryDateRange"
:cycleType="vdata.cycleType"
@changeDate="changeDateHandle"
@changeDateType="changeDateTypeHandle"
/>
<JeepaySearchInfoInput
v-if="$access('ENT_STATISTIC_MCH')"
v-model:value="vdata.searchData.mchNo"
placeholder="用户号"
:textUpStyle="true"
:mchNoAndName="true"
showType="MCH"
/>
<jeepay-text-up v-model:value="vdata.searchData['agentNo']" :placeholder="'代理商号'" />
</JeepaySearchForm>
<!-- 列表渲染 -->
<JeepayTable
ref="infoTable"
:init-data="true"
:req-table-data-func="reqTableDataFunc"
:table-columns="tableColumns"
:search-data="vdata.searchData"
row-key="groupDate"
:statisticsIsShow="true"
:tableExportFunc="tableExportFunc"
@btnLoadClose="btnLoading=false"
>
<template #headerCell="{ column }">
<span v-if="column.tooltipTitle">
{{ column.title }}
<a-tooltip :title="column.tooltipTitle"><info-circle-outlined /></a-tooltip>
</span>
</template>
<template #topBtnSlot>
<a-button type="primary" @click="tableExportFunc">
<plus-outlined />导出表格
</a-button>
</template>
<template #statistics>
<div class="statistics-list">
<div v-for="(item, index) in count" :key="index" class="item">
<div v-if="item.type == 'line'" class="line" />
<div class="title">{{ item.title }}</div>
<div v-if="item.title" class="amount" :style="{color: item.textColor}">
<span class="amount-num">{{ item.content }}</span>
</div>
</div>
</div>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'totalSuccAmt'">
<b style="color: #15B86C;">{{ (record.totalSuccAmt / 100).toFixed(2) }}</b>
</template>
<template v-if="column.key === 'totalFinalAmt'">
<b style="color: #15B86C;">{{ (record.totalFinalAmt / 100).toFixed(2) }}</b>
</template>
<template v-if="column.key === 'totalEntryAmt'">
<b style="color: #15B86C;">{{ (record.totalEntryAmt / 100).toFixed(2) }}</b>
</template>
<template v-if="column.key === 'totalFeeAmt'">
<a-tooltip overlayClassName="tooltip-box-name">
<template #title>
<span>收单手续费{{ (record.totalFeeAmt / 100).toFixed(2) }}</span><br>
<span>垫资手续费{{ ( record.totalCashFee / 100).toFixed(2) }}</span>
</template>
<b style="color: #FF6848;">{{ ((record.totalFeeAmt + record.totalCashFee) / 100).toFixed(2) }}</b>
</a-tooltip>
</template>
<template v-if="column.key === 'totalRefundAmt'">
<b style="color: #15B86C;">{{ (record.totalRefundAmt / 100).toFixed(2)}}</b>
</template>
<template v-if="column.key === 'totalRefundFeeAmt'">
<b style="color: #FF6848;">{{ (record.totalRefundFeeAmt / 100).toFixed(2) }}</b>
</template>
<template v-if="column.key === 'totalRefundNum'">
<b style="color: #FF6848;">{{ record.totalRefundNum }}</b>
</template>
<template v-if="column.key === 'totalSuccNum'">
<b style="color: #15B86C;">{{ record.totalSuccNum }}/{{ record.totalNum }}</b>
</template>
<template v-if="column.key === 'succRate'">
<b style="color: #FF8800;">{{ record.succRate }}%</b>
</template>
<template v-if="column.key === 'operation'">
<JeepayTableColumns>
<a-button v-if="$access('ENT_STATISTIC_MCH')" type="link" @click="toPage('/statistic/mch', record.groupDate)">商户详情</a-button>
<a-button type="link" @click="toPage('/statistic/agent', record.groupDate)">代理商详情</a-button>
</JeepayTableColumns>
</template>
</template>
</JeepayTable>
</a-card>
</page-header-wrapper>
</template>
<script setup lang="ts">
import { API_URL_STATISTIC, req, $exportExcel, exportExcelUrl } from '@/api/manage'
import { ref, reactive, onMounted, getCurrentInstance } from 'vue'
import JeepayDateCascadeSelection from './JeepayDateCascadeSelection.vue'
import router from '@/router'
import dateUtil from '@/utils/dateUtil.js'
import { message } from 'ant-design-vue'
import fileDownload from 'js-file-download'
const { $infoBox, $access, $hasAgentEnt } = getCurrentInstance()!.appContext.config.globalProperties
const infoDetail = ref()
const infoTable = ref()
let tableColumns = reactive([
{ key: 'groupDate', title: '日期', dataIndex: 'groupDate', agentEntCol: true, },
{ key: 'totalSuccAmt', title: '成交金额', tooltipTitle: '支付成功的订单金额,包含部分退款及全额退款的订单', dataIndex: 'totalSuccAmt', },
{ key: 'totalRefundAmt', title: '退款金额', dataIndex: 'totalRefundAmt'},
{ key: 'totalRefundNum', title: '退款笔数', tooltipTitle: '实际退款订单笔数,若一笔已成交订单退款多次,则计多次', dataIndex: 'totalRefundNum'},
{ key: 'totalFeeAmt', title: '手续费', tooltipTitle: '成交订单所产生的交易手续费和垫资手续费', dataIndex: 'totalFeeAmt'},
{ key: 'totalEntryAmt', title: '入账金额', tooltipTitle: '扣除已退款金额及手续费,入账金额为商户实际到账金额', dataIndex: 'totalEntryAmt'},
{ key: 'totalSuccNum', title: '成交笔数/总交易笔数', dataIndex: 'totalSuccNum', },
{ key: 'succRate', title: '成功率', tooltipTitle: '成交笔数与总订单笔数除得的百分比', dataIndex: 'succRate'},
{ key: 'operation', title: '操作', fixed: 'right', align: 'center', }
])
let btnLoading = ref(false)
let count:any = ref([]) // 数据统计数组
const vdata : any = reactive({
searchData: { method : 'transaction', queryDateType: 'day' }
})
// 请求table接口数据
function reqTableDataFunc(params: any) {
params.method = 'transaction'
reqTableCountFunc(params)
return req.list(API_URL_STATISTIC, params)
}
function searchFunc () { // 点击【查询】按钮点击事件
infoTable.value.refTable(true)
}
// 请求table接口数据
function reqTableCountFunc(params: any) {
req.list(API_URL_STATISTIC + '/total', params).then(res => {
count.value = [
{title: '总成交金额', symbol: 'add', textColor: '#1A66FF', content: ((res.totalSuccAmt) / 100).toFixed(2)},
{type: 'line'},
{title: '总成交笔数', content: (res.totalSuccNum)},
{type: 'line'},
{title: '总退款金额', symbol: 'sub', content: ((res.totalRefundAmt) / 100).toFixed(2)},
{type: 'line'},
{title: '总退款笔数', content: (res.totalRefundNum)},
// {type: 'line'},
// {title: '支付成功率', content: (res.round) + '%'},
]
})
}
function resetFunc(){
vdata.searchData = {}
vdata.cycleType = 'date'
}
function tableExportFunc(){
return $exportExcel(exportExcelUrl.statistic, Object.assign({}, vdata.searchData, {})).then(res => {
fileDownload(res.data, '交易报表.xlsx')
}).catch ((error) =>{console.log(error)} )
}
function toPage(path, groupDate) {
let queryString = dateUtil.getMonthRangeQueryString(groupDate)
if (vdata.searchData.queryDateType === 'day') {
console.log('day')
queryString = dateUtil.getDayRangeQueryString(groupDate)
} else if(vdata.searchData.queryDateType === 'month') {
queryString = dateUtil.getMonthRangeQueryString(groupDate)
} else {
queryString = dateUtil.getYearRangeQueryString(groupDate)
}
router.push({
path: path,
query: { queryDate: queryString }
})
}
function dateDiff(startDate, endDate) {
let flag = [1, 3, 5, 7, 8, 10, 12, 4, 6, 9, 11, 2]
let start = new Date(startDate)
let end = new Date(endDate)
let year = end.getFullYear() - start.getFullYear()
let month = end.getMonth() - start.getMonth()
let day = end.getDate() - start.getDate()
if (month < 0) {
year--
month = end.getMonth() + (12 - start.getMonth())
}
if (day < 0) {
month--
let index = flag.findIndex((temp) => { return temp === start.getMonth() + 1 })
let monthLength = 0
if (index <= 6) {
monthLength = 31
} else if (index > 6 && index <= 10) {
monthLength = 30
} else {
monthLength = 28
}
day = end.getDate() + (monthLength - start.getDate())
}
let result = { years: year, months: month, days: day }
return result
}
// 时间选择
function changeDateHandle(e) {
vdata.dateSpan = dateDiff(e.startDate, e.endDate)
vdata.dateObject = { startDate: e.startDate, endDate: e.endDate }
if(vdata.searchData['queryDateType'] === 'day') {
if(vdata.dateSpan.days > 30 || vdata.dateSpan.months > 0) {
message.error('相隔天数最多为30天')
}
} else if(vdata.searchData['queryDateType'] === 'month') {
if(vdata.dateSpan.months > 12 || vdata.dateSpan.years > 0) {
message.error('相隔月数最多为12个月')
}
}
vdata.searchData['queryDateRange'] = e.timeParams
}
// 周期选择
function changeDateTypeHandle(e) {
if(e === 'date') {
vdata.searchData['queryDateType'] = 'day'
} else {
vdata.searchData['queryDateType'] = e
}
vdata['cycleType'] = e
}
</script>

View File

@@ -0,0 +1,210 @@
<!--
复制自 运营平台
差异
-->
<template>
<a-drawer
v-model:visible="vdata.isShow"
:title=" vdata.isAdd ? '新增门店' : '修改门店' "
width="60%"
:mask-closable="false"
@close="vdata.isShow = false"
>
<a-form ref="infoFormModel" :model="vdata.saveObject" :label-col="{span: 6}" :wrapper-col="{span: 15}" :rules="rules">
<a-row justify="space-between" type="flex">
<a-col v-if="vdata.isAdd" :span="12">
<a-form-item label="用户:" name="mchNo">
<JeepaySearchInfoInput v-model:value="vdata.saveObject.mchName" placeholder="用户号" @itemRender="getUserMchNo" :textUpStyle="false" showType="MCH" :onlyMchName="true" />
</a-form-item>
</a-col>
</a-row>
<a-col :span="12">
<a-form-item label="选择商户" name="mchApplyId" >
<a-select :disabled="vdata.isAdd?false : true" v-model:value="vdata.saveObject.mchApplyId" placeholder="请选择商户号" show-search @search="handleBankChange" :filter-option="false">
<a-select-option v-for="(d, index) in vdata.filteredAppInfoList" :key="index" v-model:value="d.applyId"> {{ d.mchShortName }} - {{ d.applyId }} - {{ d.ifName }} </a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-row justify="space-between" type="flex">
<a-col :span="12">
<a-form-item label="门店名称:" name="storeName">
<a-input v-model:value="vdata.saveObject.storeName" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="联系人电话" name="contactPhone">
<a-input v-model:value="vdata.saveObject.contactPhone" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="门店LOGO" name="storeLogo">
<JeepayUpload v-model:src="vdata.saveObject.storeLogo" bizType="applyment" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="门头照" name="storeOuterImg">
<JeepayUpload v-model:src="vdata.saveObject.storeOuterImg" bizType="applyment" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="门店内景照" name="storeInnerImg">
<JeepayUpload v-model:src="vdata.saveObject.storeInnerImg" bizType="applyment" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="备注:" name="remark">
<a-input v-model:value="vdata.saveObject.remark" />
</a-form-item>
</a-col>
</a-row>
<a-divider />
<!-- 引入地图组件 -->
<JeepayAmap ref="jeepayAmapRef" />
</a-form>
<div class="drawer-btn-center">
<a-button :style="{ marginRight: '8px' }" @click="vdata.isShow = false"><close-outlined /> 取消</a-button>
<a-button type="primary" :loading="vdata.confirmLoading" @click="handleOkFunc"><check-outlined /> 保存</a-button>
</div>
</a-drawer>
</template>
<script lang="ts" setup>
import { req, API_URL_MCH_STORE_LIST,API_URL_MCH_APPLYMENT_LIST }from '@/api/manage'
import { reactive, ref, getCurrentInstance, nextTick } from 'vue'
import {message} from "ant-design-vue";
const { $infoBox, $access } = getCurrentInstance()!.appContext.config.globalProperties
const jeepayAmapRef = ref()
const infoFormModel = ref()
const props = defineProps({
callbackFunc: {type: Function, default: () => {} }
})
const vdata = reactive({
appList:[] as any,
filteredAppInfoList:[] as any,
confirmLoading: false, // 显示确定按钮loading图标
isAdd: true, // 新增 or 修改页面标识
isShow: false, // 是否显示弹层/抽屉
saveObject: {} as any, // 数据对象
recordId: null, // 更新对象ID
})
const rules = {
mchNo: [{ required: true, message: '请输入用户名称', trigger: 'blur' }],
storeName: [{ required: true, message: '请输入门店名称', trigger: 'blur' }],
mchApplyId: [{ required: true, message: '请输入商户号', trigger: 'blur' }]
}
function show (mchNo, recordId) { // 弹层打开事件
if (infoFormModel.value !== undefined) {
infoFormModel.value.resetFields()
}
vdata.isAdd = !recordId
// 数据恢复为默认数据
vdata.saveObject = {mchNo: mchNo, lng: '', lat: ''}
vdata.confirmLoading = false // 关闭loading
let initMapData : any = {}
if (!vdata.isAdd) { // 修改信息 延迟展示弹层
vdata.recordId = recordId
req.getById(API_URL_MCH_STORE_LIST, recordId).then(res => {
vdata.saveObject = res
// 门店包含了区域编码
if(res.areaCode && res.areaCode.length){
initMapData.areacode = res.areaCode
initMapData.lnglat = res.lng + ',' + res.lat
initMapData.address = res.address
}
// 显示地图信息
nextTick(() => jeepayAmapRef.value.init(initMapData))
})
vdata.isShow = true
} else {
vdata.isShow = true // 立马展示弹层信息
// 显示地图信息
nextTick(() => jeepayAmapRef.value.init(initMapData))
}
}
// 设置地图标点
function handleOkFunc () { // 点击【确认】按钮事件
let mapData = jeepayAmapRef.value.getMapData()
// if(!mapData || mapData.areacode.length == 0 || !mapData.address || !mapData.lnglat){
// return $infoBox.message.error('请选择地理位置')
// }
// if(mapData.lnglat.indexOf(',') < 0){
// return $infoBox.message.error('请输入正确的经纬度')
// }
infoFormModel.value.validate().then(valid =>{
vdata.confirmLoading = true // 显示loading
vdata.saveObject.lng = mapData.lnglat.split(',')[0]
vdata.saveObject.lat = mapData.lnglat.split(',')[1]
vdata.saveObject.address = mapData.address
vdata.saveObject.areaCode = JSON.stringify(mapData.areacode)
// 请求HTTP
req.addOrUpdate(vdata.isAdd ? null: vdata.recordId, API_URL_MCH_STORE_LIST, vdata.saveObject).then(res => {
vdata.isShow = false
vdata.confirmLoading = false
props.callbackFunc() // 刷新列表
}).catch(valid =>{
vdata.confirmLoading = false
})
})
}
async function businessList(){
let res = await req.list(API_URL_MCH_APPLYMENT_LIST, {pageNumber:1,pageSize:-1,state:99,mchNo:vdata.saveObject.mchNo});
vdata.filteredAppInfoList = res.records
console.log(vdata.filteredAppInfoList,'vdatafilteredAppInfoList')
vdata.appList = res.records
}
const handleBankChange = (value) => {
if(!vdata.saveObject.mchNo){
return message.error('请先选择用户')
}
vdata.filteredAppInfoList = [];
vdata.appList.forEach((item,index) => {
if (item.mchShortName.includes(value) || item.applyId.includes(value)) {
vdata.filteredAppInfoList.push(vdata.appList[index]);
}
});
};
function getUserMchNo(e){
console.log(e,'--------')
vdata.saveObject.mchNo = e.mchNo
vdata.saveObject.mchName = e.mchName
businessList()
}
defineExpose({show})
</script>

View File

@@ -0,0 +1,189 @@
<template>
<a-drawer
v-model:visible="vdata.visible"
:title=" true ? '门店详情' : '' "
:body-style="{ paddingBottom: '80px' }"
width="40%"
@close="onClose"
>
<a-divider class="jeepay-m-divider" orientation="left" style="color: #1A66FF;">商户信息</a-divider>
<a-row justify="space-between" type="flex">
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商户全称">
{{ vdata.mchDetail.mchFullName }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商户简称">
{{ vdata.mchDetail.mchShortName }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="商户类型">
{{ vdata.mchDetail.merchantType == 1?'小微':vdata.mchDetail.merchantType == 2?'个体':vdata.mchDetail.merchantType == 1?'企业':'其他' }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="结算状态">
{{vdata.mchDetail.settlementType??"无"}}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="联系人姓名">
{{ vdata.mchDetail.contactName }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="联系人手机号">
{{ vdata.mchDetail.contactPhone }}
</a-descriptions-item>
</a-descriptions>
</a-col>
</a-row>
<a-divider class="jeepay-m-divider" orientation="left" style="color: #1A66FF;">门店信息</a-divider>
<a-row justify="space-between" type="flex">
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="门店编号">
{{ vdata.detailData['storeId'] }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="门店名称">
{{ vdata.detailData['storeName'] }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="门店logo">
<a-image v-if="vdata.detailData['storeLogo']" class="img" :src="vdata.detailData['storeLogo']" />
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="门店内景照">
<a-image v-if="vdata.detailData['storeInnerImg']" class="img" :src="vdata.detailData['storeInnerImg']" />
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="门头照">
<a-image v-if="vdata.detailData['storeOuterImg']" class="img" :src="vdata.detailData['storeOuterImg']" />
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="门店地址">
{{ vdata.detailData['address'] }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<!-- <a-col :sm="12">-->
<!-- <a-descriptions>-->
<!-- <a-descriptions-item label="代理商号">-->
<!-- {{ vdata.detailData['agentNo'] }}-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
<!-- </a-col>-->
<!-- <a-col :sm="12">-->
<!-- <a-descriptions>-->
<!-- <a-descriptions-item label="用户号">-->
<!-- {{ vdata.detailData['mchNo'] }}-->
<!-- </a-descriptions-item>-->
<!-- </a-descriptions>-->
<!-- </a-col>-->
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="联系人手机号">
{{ vdata.detailData['contactPhone'] }}
</a-descriptions-item>
</a-descriptions>
</a-col>
</a-row>
<a-row justify="start" type="flex">
<a-col :sm="24">
<a-form-item label="备注">
<a-input
v-model:value="vdata.detailData['remark']"
type="textarea"
disabled="disabled"
style="height: 50px"
/>
</a-form-item>
</a-col>
</a-row>
</a-drawer>
</template>
<script lang="ts" setup>
import {API_URL_MCH_APPLYMENT_LIST, API_URL_MCH_STORE_LIST, req} from '@/api/manage'
import { reactive } from 'vue'
const vdata:any = reactive({
btnLoading: false,
detailData: {}, // 数据对象
mchDetail:{},// 数据对象
recordId: null, // 更新对象ID
visible: false, // 是否显示弹层/抽屉
isvList: [], // 服务商下拉列表
isvName: '' // 服务商名称
})
function show (recordId) { // 弹层打开事件
vdata.detailData = {} // 数据清空
vdata.recordId = recordId
req.getById(API_URL_MCH_STORE_LIST, recordId).then(res => {
vdata.detailData = res
mchDetail()
})
vdata.visible = true
}
function onClose () {
vdata.visible = false
}
function mchDetail(){
req.getById(API_URL_MCH_APPLYMENT_LIST, vdata.detailData.mchApplyId).then(res => {
if(res.succResParameter){
const succResParameter = JSON.parse(res.succResParameter)
vdata.termNo = succResParameter.termNo
}else{
vdata.termNo = "无"
}
vdata.mchDetail = res
})
}
defineExpose({
show //抛出show函数给父组件
})
</script>
<style>
.img {
width: 50px;
height: 50px;
}
</style>

View File

@@ -0,0 +1,215 @@
<template>
<page-header-wrapper>
<a-card>
<JeepaySearchForm :searchFunc="searchFunc" :resetFunc="() => { vdata.searchData = {} }">
<JeepaySearchInfoInput v-model:value="vdata.searchData['mchNo']" placeholder="用户号" :textUpStyle="true" :mchNoAndName="true" showType="MCH" />
<jeepay-text-up v-model:value="vdata.searchData['mchApplyName']" :placeholder="'商户名称/商户号'" />
<jeepay-text-up v-model:value="vdata.searchData['storeId']" :placeholder="'门店名称/门店编号'" />
<jeepay-text-up v-model:value="vdata.searchData['ifName']" :placeholder="'支付通道'" />
<jeepay-text-up v-model:value="vdata.searchData['isvNo']" :placeholder="'渠道名称/渠道号'" />
<jeepay-text-up v-model:value="vdata.searchData['mchUserName']" :placeholder="'用户号/名称/手机号'" />
<jeepay-text-up v-model:value="vdata.searchData['mchServiceName']" :placeholder="'服务商号/服务商名称'" />
<jeepay-text-up v-model:value="vdata.searchData['bindAppIdName']" :placeholder="'应用名称/应用ID'" />
<jeepay-text-up v-model:value="vdata.searchData['contactPhone']" :placeholder="'联系人电话'" />
</JeepaySearchForm>
<!-- 列表渲染 -->
<JeepayTable
ref="infoTable"
:initData="false"
:reqTableDataFunc="reqTableDataFunc"
:tableColumns="tableColumns"
:searchData="vdata.searchData"
rowKey="storeId"
@btnLoadClose="vdata.btnLoading=false"
>
<template #topBtnSlot>
<div>
<a-button v-if="$access('ENT_MCH_STORE_ADD')" type="primary" class="mg-b-30" @click="addFunc"><plus-outlined />新建</a-button>
</div>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'storeName'">
<a @click="detailFunc(record.storeId)"><b>{{ record.storeName }}</b></a >
</template>
<template v-if="column.key === 'bindAppIdName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>应用名称{{record.bindAppIdName}}</span><br>
<span>应用ID{{record.bindAppId}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.bindAppIdName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'mchUserName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>用户名称{{record.mchUserName}}</span><br>
<span>用户手机号{{record.mchUserPhone}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.mchUserName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'mchServiceName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>服务商名称{{record.mchServiceName}}</span><br>
<span>服务商号{{record.agentNo}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.mchServiceName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'mchShortName'">
<a-tooltip class="my-tooltip" overlayClassName="tooltip-box-name">
<template #title>
<span>商户名称{{record.mchApplyName}}</span><br>
<span>商户号{{record.mchApplyId}}</span>
</template>
<div class="my-tooltip-title-box"> {{record.mchApplyName}}</div>
</a-tooltip>
</template>
<template v-if="column.key === 'defaultFlag'">
<a-tag v-if="record.defaultFlag===1" color="green"></a-tag>
<a-switch v-else :checked="record.defaultFlag===1?true:false" @change="(defaultFlag) => { return updateState(record.mchNo, record.storeId, defaultFlag)}" />
</template>
<template v-if="column.key === 'deviceNumber'">
<a href="javascript:void(0)" @click="deviceFunc(record.storeId)">{{ record.deviceNumber ? record.deviceNumber : 0 }}</a>
</template>
<template v-if="column.key === 'remark'">
<!-- <span @click="editFunc(record.storeId)" class="two-lines"-->
<!-- ><b></b>-->
<!-- <EditOutlined @click="editFunc(record.storeId)" />-->
<!-- </span>-->
<div class="my-tooltip-title-box"> {{record.remark != "" ? record.remark : "--" }}</div>
</template>
<template v-if="column.key === 'op'">
<!-- 操作列插槽 -->
<JeepayTableColumns>
<a-button v-if="$access('ENT_MCH_STORE_EDIT')" type="link" style="padding-right: 10px;padding-left: 10px;" @click="deviceFunc(record.storeId)">设备管理</a-button>
<a-button v-if="$access('ENT_MCH_STORE_EDIT')" type="link" style="padding-right: 10px; padding-left: 10px;text-align: center" @click="editFunc(record.storeId)" >修改</a-button >
<a-button v-if="$access('ENT_MCH_STORE_DELETE')" type="link" style="color: red" @click="delFunc(record.storeId)">删除</a-button>
</JeepayTableColumns>
</template>
</template>
</JeepayTable>
</a-card>
<!-- 新增 / 修改 页面组件 -->
<InfoAddOrEdit ref="infoAddOrEdit" :callbackFunc="searchFunc" />
<!-- 详情页面组件 -->
<InfoDetail ref="infoDetail" :callback-func="searchFunc" />
</page-header-wrapper>
</template>
<script lang="ts" setup>
import { API_URL_MCH_STORE_LIST, req, reqLoad } from '@/api/manage'
import InfoAddOrEdit from './AddOrEdit.vue'
import InfoDetail from './Detail.vue'
import { useRoute } from 'vue-router'
import { ref, onMounted, reactive, getCurrentInstance } from 'vue'
import router from '@/router'
// 获取全局函数
const { $infoBox,$access } = getCurrentInstance()!.appContext.config.globalProperties
const tableColumns = reactive([
{ title: "商户名称", key: "mchShortName", dataIndex: "mchShortName" },
{ title: "门店名称", key: "storeName", dataIndex: "storeName"},
{ title: "门店编号", dataIndex: "storeId"},
{ title: '用户名称', key: 'mchUserName', dataIndex: 'mchUserName' },
{ title: '服务商名称', key: 'mchServiceName', dataIndex: 'mchServiceName' },
// { title: "是否默认", key: "defaultFlag", align: "center" },
{ title: "所属应用",key: "bindAppIdName", dataIndex: "bindAppIdName" },
{ title: "联系电话", dataIndex: "contactPhone" },
{ title: "备注", key: "remark", dataIndex: "remark"},
{ title: "设备总数", key: "deviceNumber", dataIndex: "deviceNumber", align: "center" },
// { title: '蚂蚁店铺状态', key: 'alipayShopStatus', dataIndex: 'alipayShopStatus' },
{ title: "创建时间", dataIndex: "createdAt" },
{ key: "op", title: "操作", fixed: "right", align: "center" },
])
const infoDetail = ref()
const infoAddOrEdit = ref()
const infoTable = ref()
const vdata = reactive({
tableColumns: tableColumns,
searchData: {} as any,
btnLoading: false
})
const queryFunc = () => {
vdata.btnLoading = true
infoTable.value.refTable(true)
}
onMounted(() => {
vdata.searchData['mchNo'] = useRoute().query.mchNo
queryFunc()
})
// 请求table接口数据
function reqTableDataFunc (params) {
return req.list(API_URL_MCH_STORE_LIST, params)
}
function searchFunc() { // 点击【查询】按钮点击事件
vdata.btnLoading = true // 打开查询按钮的loading
infoTable.value.refTable(true)
}
function addFunc () { // 业务通用【新增】 函数
infoAddOrEdit.value.show(vdata.searchData['mchNo'])
}
function delFunc (recordId) { // 业务通用【删除】 函数
$infoBox.confirmDanger('确认删除?', '', () => {
return req.delById(API_URL_MCH_STORE_LIST, recordId).then(res => {
$infoBox.message.success('删除成功!')
infoTable.value.refTable(false)
}).catch(err => {})
})
}
function editFunc (recordId) { // 业务通用【修改】 函数
infoAddOrEdit.value.show(vdata.searchData['mchNo'], recordId)
}
function detailFunc (recordId: any) { // 门店详情页
infoDetail.value.show(recordId)
}
function updateState (mchNo, recordId, state) { // 【更新状态】
const title = state === 1 ? '确认[默认]该门店?' : '确认[取消默认]该门店?'
const content = '开启默认后则默认使用该门店配置,有且只能有一个默认门店。'
return new Promise<void>((resolve, reject) => {
$infoBox.confirmDanger(title, content, () => {
return reqLoad.updateById(API_URL_MCH_STORE_LIST, recordId, { mchNo: mchNo, defaultFlag: state }).then(res => {
searchFunc()
resolve()
}).catch(err => reject(err))
},
() => {
reject(new Error())
})
})
}
// 云喇叭配置
function speakerConfig (recordId: any) {
router.push({
path: '/store/speaker',
query: { storeId: recordId }
})
}
function deviceFunc(storeId){
router.push({
path: "/qrc",
query:{storeId:storeId}
});
}
// 云打印配置
function printerConfig (recordId: any) {
router.push({
path: '/store/printer',
query: { storeId: recordId }
})
}
</script>

View File

@@ -0,0 +1,239 @@
<template>
<a-drawer
v-model:visible="vdata.isShow"
:title="vdata.isAdd ? '新增广告' : '编辑广告'"
width="50%"
:mask-closable="false"
@close="vdata.isShow = false"
>
<a-form ref="infoFormModel" :model="vdata.saveObject" :label-col="{ span: 6 }" :wrapper-col="{ span: 15 }" :rules="rules">
<a-row justify="center" type="flex">
<a-col :span="24">
<a-form-item label="标题" name="title">
<a-input v-model:value="vdata.saveObject.title" />
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item label="状态" name="releaseState">
<a-radio-group v-model:value="vdata.saveObject['releaseState']">
<a-radio :value="1">
立即发布
</a-radio>
<a-radio :value="0">
手动发布
</a-radio>
</a-radio-group>
</a-form-item>
</a-col>
</a-row>
<a-divider orientation="left">轮播设置</a-divider>
<a-col :span="24">
<a-button
class="editable-add-btn"
style="margin-bottom: 8px;margin-left: 30px;"
type="dashed"
@click="handleAdd"
>
<PlusOutlined />新增
</a-button>
<a-table bordered :data-source="vdata.saveObject.infoList" :columns="columns">
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'imgUrl'">
<div class="editable-cell">
<div class="editable-cell-input-wrapper">
<JeepayUpload v-model:src="record.imgUrl" bizType="notice" listType="picture-card" />
</div>
</div>
</template>
<template v-else-if="column.dataIndex === 'sort'">
<div class="editable-cell-input-wrapper">
<a-input-number v-model:value="record.sort" :min="0" :max="10" />
</div>
</template>
<template v-else-if="column.dataIndex === 'linkUrl' && vdata.groupKey != 'faceAdvert'">
<div class="editable-cell-input-wrapper">
<a-input v-model:value="record.linkUrl" placeholder="请以http://或https://开头" />
</div>
</template>
<template v-else-if="column.dataIndex === 'operation'">
<a style="color: red" @click="onDelete(record)">删除</a>
</template>
</template>
</a-table>
</a-col>
<div v-if="vdata.isAdd">
<a-row v-if="vdata.groupKey == 'faceAdvert'" justify="center" type="flex">
<a-divider orientation="left">所属商户配置</a-divider>
<a-col :span="24">
<a-form-item label="发布商户" name="bindType">
<a-space>
<a-radio-group v-model:value="vdata.saveObject.bindType">
<a-radio :value="0">全部商户</a-radio>
<a-radio :value="1">指定商户</a-radio>
</a-radio-group>
<a-button v-if="vdata.saveObject.bindType == 1" type="primary" @click="showQrcSelectModel">选择商户</a-button>
</a-space>
</a-form-item>
</a-col>
<a-col v-if="vdata.saveObject.bindType == 1" :span="24">
<a-form-item label="所属商户" name="infoIds">
<a-textarea v-model:value="vdata.saveObject.infoIds" disabled />
</a-form-item>
</a-col>
</a-row>
</div>
</a-form>
<div class="drawer-btn-center">
<a-button :style="{ marginRight: '8px' }" @click="vdata.isShow = false"><close-outlined /> 取消</a-button>
<a-button type="primary" :loading="vdata.confirmLoading" @click="handleOkFunc"><check-outlined /> 保存</a-button>
</div>
</a-drawer>
<!-- 选择商户组件 -->
<JeepayModelSelectMchList ref="jeepayModelSelectMchRef" @selectFinishFunc="selectQrcFinishFunc" />
</template>
<script lang="ts" setup>
import { req, API_URL_ADVERT } from '@/api/manage'
import { reactive, ref, getCurrentInstance, computed } from 'vue'
const { $infoBox, $access } = getCurrentInstance()!.appContext.config.globalProperties
const infoFormModel = ref()
const jeepayModelSelectMchRef = ref()
const props = defineProps({
callbackFunc: { type: Function, default: () => { } }
})
const columns = [
{ title: '广告图片(建议尺寸800x1280px)', dataIndex: 'imgUrl', width: '35%', },
{ title: '轮播排序', dataIndex: 'sort', },
{ title: '操作', dataIndex: 'operation', },
]
const vdata = reactive({
confirmLoading: false, // 显示确定按钮loading图标
isAdd: true, // 新增 or 修改页面标识
isShow: false, // 是否显示弹层/抽屉
saveObject: {infoList: [{ imgUrl: '', linkUrl: '', sort: '' }]} as any, // 数据对象
recordId: null, // 更新对象ID
groupKey: '',
})
const rules = {
title: [{ required: true, message: '请输入广告标题', trigger: 'blur' }],
releaseState: [{ required: true, message: '请选择发布状态', trigger: 'blur' }],
imgUrl: [{ required: true, message: '请上传广告图片', trigger: 'blur' }],
linkUrl: [{ required: true, message: '请输入广告链接', trigger: 'blur' }],
content: [{ required: true, message: '请输入广告内容', trigger: 'blur' }],
bindType: [{ required: true, message: '请选择发布商户类型', trigger: 'blur' }],
infoIds: [{ required: true, message: '请选择要发布的商户', trigger: 'blur' }],
advertSort: [{ required: true, message: '请输入广告排序', trigger: 'blur' }],
changeTime: [{ required: true, message: '请输入轮播时间', trigger: 'blur' }],
}
function show(groupKey, recordId) { // 弹层打开事件
if (infoFormModel.value !== undefined) {
infoFormModel.value.resetFields()
}
vdata.groupKey = groupKey
vdata.isAdd = !recordId
// 数据恢复为默认数据
vdata.saveObject = { bindType: 0, releaseState: 1 }
vdata.saveObject.infoList = [{ imgUrl: '', linkUrl: '', sort: '' }]
vdata.saveObject.infoIds = ''
vdata.confirmLoading = false // 关闭loading
if (!vdata.isAdd) { // 修改信息 延迟展示弹层
vdata.recordId = recordId
req.getById(API_URL_ADVERT, recordId).then(res => {
vdata.saveObject = res
if (vdata.saveObject.appContent) {
vdata.saveObject.infoList = JSON.parse(vdata.saveObject.appContent)
}
})
}
vdata.isShow = true // 立马展示弹层信息
}
function handleOkFunc() { // 点击【确认】按钮事件
infoFormModel.value.validate().then(valid => {
vdata.confirmLoading = true // 显示loading
if (vdata.saveObject.infoList.length < 1) {
vdata.confirmLoading = false
return $infoBox.message.error('轮播配置不可为空')
}
vdata.saveObject.advertType = '1'
if (vdata.saveObject.infoList) {
var firstInfo = vdata.saveObject.infoList[0]
if (!firstInfo.imgUrl) {
vdata.confirmLoading = false
return $infoBox.message.error('轮播图不可为空')
}
vdata.saveObject.imgUrl = firstInfo.imgUrl
vdata.saveObject.linkUrl = firstInfo.linkUrl
}
vdata.saveObject.appContent = JSON.stringify(vdata.saveObject.infoList)
// 请求HTTP
req.addOrUpdate(vdata.isAdd ? null : vdata.recordId, API_URL_ADVERT, vdata.saveObject).then(res => {
vdata.isShow = false
vdata.confirmLoading = false
props.callbackFunc() // 刷新列表
}).catch(valid => {
vdata.confirmLoading = false
})
})
}
// 选择商户弹窗
function showQrcSelectModel() {
jeepayModelSelectMchRef.value.show(vdata.saveObject.infoIds, { advertFlag: '1' })
}
// 选择商户完成
function selectQrcFinishFunc(e) {
if (!e || e.length <= 0) {
return $infoBox.message.error('请选中要发布广告的商户!')
}
vdata.saveObject.infoIds = e.toString()
jeepayModelSelectMchRef.value.close()
}
const count = computed(() => vdata.saveObject.infoList.length + 1)
const onDelete = (item) => {
// vdata.saveObject.infoList = vdata.saveObject.infoList.filter(item => item.key !== key)
const index = vdata.saveObject.infoList.indexOf(item)
if (index != -1) {
vdata.saveObject.infoList.splice(index, 1)
}
}
const handleAdd = () => {
if (vdata.saveObject.appPlaceType == '1' && vdata.saveObject.infoList.length > 1) {
return $infoBox.message.error('卡片广告最多添加两条记录!')
}
const newData = { key: `${count.value}`, imgUrl: ``, sort: '', linkUrl: ``, }
vdata.saveObject.infoList.push(newData)
}
defineExpose({ show })
</script>
<style lang="less" scoped>
.ant-btn-dangerous {
position: absolute;
right: 50px;
}
.ant-row-space-between {
position: relative;
}
</style>

View File

@@ -0,0 +1,193 @@
<template>
<div style="background: #fff">
<a-tabs @change="selectTabs">
<a-tab-pane key="faceAdvert" tab="刷脸设备广告">
<div class="account-settings-info-view">
<page-header-wrapper>
<a-card>
<JeepaySearchForm :searchFunc="searchFunc" :resetFunc="() => { vdata.searchData = {} }">
<jeepay-text-up v-model:value="vdata.searchData['advertId']" :placeholder="'ID'" />
<jeepay-text-up v-model:value="vdata.searchData['title']" :placeholder="'标题'" />
<jeepay-text-up v-model:value="vdata.searchData['mchNo']" :placeholder="'用户号'" />
<a-form-item label="" class="table-search-item">
<a-select v-model:value="vdata.searchData['releaseState']" placeholder="发布状态">
<a-select-option value="">
全部
</a-select-option>
<a-select-option value="0">
待发布
</a-select-option>
<a-select-option value="1">
已发布
</a-select-option>
</a-select>
</a-form-item>
</JeepaySearchForm>
<!-- 列表渲染 -->
<JeepayTable
ref="infoTableFace"
:initData="false"
:reqTableDataFunc="reqTableDataFunc"
:tableColumns="tableColumnsFace"
:searchData="vdata.searchData"
rowKey="advertId"
:rowSelection="{ type: 'checkbox', onChange: infoTableSelectChangeFunc }"
@btnLoadClose="vdata.btnLoading=false"
>
<template #topBtnSlot>
<div>
<a-button v-if="$access('ENT_ADVERT_ADD')" type="primary" class="mg-b-30" @click="addFunc"><plus-outlined />新建</a-button>
<a-button v-if="$access('ENT_ADVERT_DEL')" type="danger" @click="deleteBatchFunc"><delete-outlined />批量删除</a-button>
</div>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'advertType'">
{{ record.advertType === 1?'刷脸设备广告':record.advertType === 2?'支付后广告':record.advertType === 3?$SYS_NAME_MAP.MCH_APP+'APP':$SYS_NAME_MAP.AGENT_APP+'APP' }}
</template>
<template v-if="column.key === 'releaseState'">
<JeepayTableColState :showSwitchType="true" :badgeTextArray="['已发布','待发布','未知']" :state="record.releaseState" :on-change="(releaseState) => { return updateState(record.advertId, releaseState)}" />
</template>
<template v-if="column.key === 'imgUrl'">
<a-image
:width="60"
:src="record.imgUrl"
/>
</template>
<template v-if="column.key === 'mchNo'">
<div v-if="record.mchNo">
用户号: {{ record.mchNo }} <br> 商户名:{{ record.infoName }}
</div>
<div v-else>全部商户</div>
</template>
<template v-if="column.key === 'op'">
<!-- 操作列插槽 -->
<JeepayTableColumns>
<a-button v-if="$access('ENT_ADVERT_EDIT')" type="link" style="padding-right: 10px;padding-left: 10px;" @click="editFunc(record.advertId)">修改</a-button>
<a-button v-if="$access('ENT_ADVERT_DEL')" type="link" style="color: red" @click="delFunc(record.advertId)">删除</a-button>
</JeepayTableColumns>
</template>
</template>
</JeepayTable>
</a-card>
</page-header-wrapper>
</div>
</a-tab-pane>
</a-tabs>
</div>
<!-- 新增 / 修改 页面组件 -->
<InfoAddOrEdit ref="infoAddOrEdit" :callbackFunc="searchFunc" />
</template>
<script setup lang="ts">
import { API_URL_ADVERT, req, $batchDelAdvert, reqLoad} from '@/api/manage'
import InfoAddOrEdit from './AddOrEdit.vue'
import { ref, reactive, getCurrentInstance, nextTick, onMounted } from 'vue'
const { $infoBox, $access, $SYS_NAME_MAP } = getCurrentInstance()!.appContext.config.globalProperties
const tableColumnsFace = reactive([
{ title: 'ID', dataIndex: 'advertId' },
{ title: '标题', key: 'title', dataIndex: 'title' },
{ title: '预览图', key: 'imgUrl', dataIndex: 'imgUrl' },
// { title: '广告类型', key: 'advertType', dataIndex: 'advertType' },
{ title: '用户号', key: 'mchNo', dataIndex: 'mchNo' },
{ title: '发布状态', key: 'releaseState', dataIndex: 'releaseState' },
{ title: '创建时间', dataIndex: 'createdAt'},
{ key: 'op',title: '操作', fixed: 'right', align: 'center'}
])
const vdata : any = reactive ({
btnLoading: false,
tableColumnsFace: tableColumnsFace,
searchData: {},
allotDeviceIdList: [], // 要划拨的设备ID划拨类型为 select-勾选划拨 时必填
groupKey: 'faceAdvert',
})
const infoTableFace = ref()
const infoAddOrEdit = ref()
// 请求table接口数据
function reqTableDataFunc (params) {
return req.list(API_URL_ADVERT, vdata.searchData)
}
onMounted(()=>{
searchFunc()
})
function searchFunc() { // 点击【查询】按钮点击事件
vdata.btnLoading = true // 打开查询按钮的loading
vdata.searchData.advertType = '1'
infoTableFace.value.refTable(true)
}
function addFunc () { // 业务通用【新增】 函数
infoAddOrEdit.value.show(vdata.groupKey)
}
function delFunc (recordId) { // 业务通用【删除】 函数
$infoBox.confirmDanger('确认删除?', '', () => {
$batchDelAdvert(recordId).then((res: any) => {
$infoBox.message.success('删除成功!')
infoTableFace.value.refTable(false)
}).catch(err => {})
})
}
function editFunc (recordId) { // 业务通用【修改】 函数
infoAddOrEdit.value.show(vdata.groupKey, recordId)
}
function selectTabs (key) { // 清空必填提示
if (key) {
vdata.groupKey = key
nextTick(() => {
searchFunc()
})
}
}
// 表格多选
function infoTableSelectChangeFunc(selectedRowKeys, selectedRows) {
vdata.selectRecordIdList = selectedRowKeys
}
function deleteBatchFunc() { // 批量删除
if (!vdata.selectRecordIdList || vdata.selectRecordIdList.length < 1) {
$infoBox.message.error('请选择广告配置')
} else {
$infoBox.confirmDanger('确认删除?', '', () => {
$batchDelAdvert(vdata.selectRecordIdList.join(',')).then((res: any) => {
searchFunc()
$infoBox.message.success('删除成功')
})
})
}
}
function updateState (recordId, state) { // 【更新状态】
return new Promise<void>((resolve, reject) => {
return reqLoad.updateById(API_URL_ADVERT, recordId, { state: state }).then(res => {
searchFunc()
resolve()
}).catch(err => reject(err))
})
}
</script>
<style lang="less" scoped>
.flex {
display:flex;
margin-bottom: 8px;
i {
margin-left: 5px;
}
}
.bottom-btn{
/deep/ div{
display: flex;
justify-content: center;
}
}
</style>

View File

@@ -0,0 +1,400 @@
<template>
<a-drawer
v-model:visible="vdata.isShow"
:title=" vdata.isAdd ? '新增操作员' : '修改操作员' "
placement="right"
:closable="true"
width="600"
:mask-closable="false"
@ok="handleOkFunc"
@close="onClose"
>
<!-- <a-modal :confirmLoading="confirmLoading"> -->
<a-form
ref="infoFormModel"
:model="vdata.saveObject"
layout="vertical"
:rules="rules"
style="padding-bottom:50px"
>
<a-row justify="space-between" type="flex">
<a-col :span="10">
<a-form-item label="用户登录名:" name="loginUsername">
<a-input v-model:value="vdata.saveObject['loginUsername']" :disabled="!vdata.isAdd" />
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="用户姓名:" name="realname">
<a-input v-model:value="vdata.saveObject['realname']" />
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="手机号:" name="telphone">
<a-input v-model:value="vdata.saveObject['telphone']" />
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="编号:" name="userNo">
<a-input v-model:value="vdata.saveObject['userNo']" />
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="请选择性别:" name="sex">
<a-radio-group v-model:value="vdata.saveObject['sex']">
<a-radio :value="1">
</a-radio>
<a-radio :value="2">
</a-radio>
</a-radio-group>
</a-form-item>
</a-col>
<!-- <a-col :span="10">
<a-form-item label="是否为超级管理员:" name="isAdmin">
<a-radio-group v-model:value="vdata.saveObject['isAdmin']">
<a-radio :value="1">
</a-radio>
<a-radio :value="0">
</a-radio>
</a-radio-group>
</a-form-item>
</a-col> -->
<a-col :span="10">
<a-form-item label="状态:" name="state">
<a-radio-group v-model:value="vdata.saveObject['state']">
<a-radio :value="1">
启用
</a-radio>
<a-radio :value="0">
停用
</a-radio>
</a-radio-group>
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="用户类型:" name="userType">
<a-select v-model:value="vdata.saveObject.userType" placeholder="请选择用户类型" @change="changeUserType">
<a-select-option :value="1">超级管理员</a-select-option>
<a-select-option :value="2">普通操作员</a-select-option>
<a-select-option :value="3">商户拓展员</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col v-if="vdata.saveObject.userType == 3" :span="10">
<a-form-item label="选择团队" name="provider">
<a-select v-model:value="vdata.saveObject.teamId" placeholder="请选择团队">
<a-select-option v-for="(item, key) in vdata.teamList" :key="key" v-model:value="item.teamId">
{{ item.teamName }}
</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col v-if="vdata.saveObject.userType == 3" :span="10">
<a-form-item label="是否队长" name="isTeamLeader">
<a-radio-group v-model:value="vdata.saveObject.isTeamLeader">
<a-radio :value="1">
</a-radio>
<a-radio :value="0">
</a-radio>
</a-radio-group>
</a-form-item>
</a-col>
</a-row>
<a-divider orientation="left">
<a-tag color="#FF4B33">
账户安全
</a-tag>
</a-divider>
<div v-if="vdata.isAdd">
<a-row justify="space-between" type="flex">
<a-col :span="24">
<a-form-item label="是否发送开通提醒">
<a-radio-group v-model:value="vdata.saveObject['isNotify']">
<a-radio :value="0"></a-radio>
<a-radio :value="1"></a-radio>
</a-radio-group>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="密码设置">
<a-radio-group v-model:value="vdata.saveObject['passwordType']">
<a-radio value="default">默认密码</a-radio>
<a-radio value="custom">自定义密码</a-radio>
</a-radio-group>
</a-form-item>
</a-col>
<a-col v-if="vdata.saveObject['passwordType'] == 'custom'" :span="12">
<a-form-item label="登录密码" name="loginPassword">
<a-input
v-model:value="vdata.saveObject['loginPassword']"
placeholder="请输入登录密码"
/>
</a-form-item>
<a-button type="primary" ghost @click="randomPassage(false, 6, 0)"><file-sync-outlined />随机生成密码</a-button>
</a-col>
</a-row>
</div>
<div v-else>
<div style="display:flex;flex-direction:row;">
<a-row justify="space-between" type="flex" style="width:100%">
<a-col :span="10">
<a-form-item v-if="vdata.resetIsShow" label="">
重置密码<a-checkbox v-model:checked="vdata.sysPassword.resetPass" />
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item v-if="vdata.sysPassword.resetPass" label="">
恢复默认密码<a-checkbox v-model:checked="vdata.sysPassword.defaultPass" @click="isResetPass" />
</a-form-item>
</a-col>
</a-row>
</div>
<div v-if="vdata.sysPassword.resetPass">
<div v-show="!vdata.sysPassword.defaultPass">
<a-row justify="space-between" type="flex">
<a-col :span="10">
<a-form-item label="新密码:" name="newPwd">
<a-input-password
v-model:value="vdata.newPwd"
autocomplete="new-password"
:disabled="vdata.sysPassword.defaultPass"
/>
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="确认新密码:" name="confirmPwd">
<a-input-password
v-model:value="vdata.sysPassword.confirmPwd"
autocomplete="new-password"
:disabled="vdata.sysPassword.defaultPass"
/>
</a-form-item>
</a-col>
</a-row>
</div>
</div>
</div>
<div class="drawer-btn-center">
<a-button :style="{ marginRight: '8px' }" @click="onClose">
<close-outlined />
取消
</a-button>
<a-button type="primary" :loading="vdata.confirmLoading" @click="handleOkFunc">
<check-outlined />
保存
</a-button>
</div>
</a-form>
</a-drawer>
</template>
<script setup lang="ts">
import { req, API_URL_SYS_USER_LIST, API_URL_SYS_USER_TEAM_LIST, $getPasswordRules } from '@/api/manage'
import { reactive,ref } from 'vue'
import { Base64 } from 'js-base64'
import { message } from 'ant-design-vue'
const props = defineProps ({
callbackFunc: { type: Function, default: () => ({}) }
})
const infoFormModel = ref()
const vdata = reactive ({
newPwd: '', // 新密码
resetIsShow: false, // 重置密码是否展现
passwordRules: /^$/, //密码规则
passwordRulesText: '', //密码规则提示文字
sysPassword: {
resetPass: false, // 重置密码
defaultPass: true, // 使用默认密码
confirmPwd: '' // 确认密码
},
loading: false, // 按钮上的loading
value: 1, // 单选框默认的值
teamList: [] as any, // 团队列表
confirmLoading: false, // 显示确定按钮loading图标
isAdd: true, // 新增 or 修改页面标识
isShow: false, // 是否显示弹层/抽屉
saveObject: {} as any, // 数据对象
recordId: null, // 更新对象ID
})
const rules = reactive({
realname: [{ required: true, message: '请输入用户姓名', trigger: 'blur' }],
telphone: [{ required: true, pattern: /^[1][0-9]{10}$/, message: '请输入正确的手机号码', trigger: 'blur' }],
userNo: [{ required: true, message: '请输入编号', trigger: 'blur' }],
userType: [{ required: true, message: '请选择用户类型', trigger: 'blur' }],
loginUsername: [] as any[],
newPwd: [{ required: false, trigger: 'blur' }, {
validator: (rule, value) => {
if (!vdata.sysPassword.defaultPass) {
if (!vdata.passwordRules.test(vdata.newPwd)) {
return Promise.reject(vdata.passwordRulesText)
} else if(vdata.newPwd !== vdata.sysPassword.confirmPwd) {
return Promise.reject('新密码与确认密码不一致')
} else return Promise.resolve()
} else {
return Promise.resolve()
}
}
}], // 新密码
confirmPwd: [{ required: false, trigger: 'blur' }, {
validator: (rule, value) => {
if (!vdata.sysPassword.defaultPass) {
// vdata.newPwd === vdata.sysPassword.confirmPwd ? callBack() : callBack('新密码与确认密码不一致')
if(!vdata.passwordRules.test(vdata.sysPassword.confirmPwd)){
return Promise.reject(vdata.passwordRulesText)
} else if(vdata.newPwd !== vdata.sysPassword.confirmPwd) {
return Promise.reject('新密码与确认密码不一致')
} else return Promise.resolve()
} else {
return Promise.resolve()
}
}
}] // 确认新密码
})
function getPasswordRules () { // 获取密码规则
$getPasswordRules().then(res => {
vdata.passwordRules = new RegExp(res.regexpRules)
vdata.passwordRulesText = res.errTips
})
}
defineExpose({show})
function show (recordId) { // 弹层打开事件
getPasswordRules()
if (infoFormModel.value !== undefined) {
infoFormModel.value.resetFields()
}
vdata.isAdd = !recordId
// 数据恢复为默认数据
vdata.saveObject = {isAdmin: 1, state: 1, sex: 1, passwordType: 'default', isNotify: 0, userType: 1, isTeamLeader: 0}
rules.loginUsername = []
vdata.confirmLoading = false // 关闭loading
req.list(API_URL_SYS_USER_TEAM_LIST, { 'pageSize': -1 }).then(res => { // 团队下拉选择列表
vdata.teamList = res.records
})
if (vdata.isAdd) {
rules.loginUsername.push({
required: true,
pattern: /^[a-zA-Z][a-zA-Z0-9]{5,17}$/,
message: '请输入字母开头长度为6-18位的登录名',
trigger: 'blur'
})
}
if (!vdata.isAdd) { // 修改信息 延迟展示弹层
vdata.resetIsShow = true // 展示重置密码板块
vdata.recordId = recordId
req.getById(API_URL_SYS_USER_LIST, recordId).then(res => { vdata.saveObject = res })
vdata.isShow = true
} else {
vdata.isShow = true // 立马展示弹层信息
}
}
function handleOkFunc () { // 点击【确认】按钮事件
infoFormModel.value.validate().then(valid=>{
vdata.loading = true // 打开按钮上的 loading
vdata.confirmLoading = true // 显示loading
if (vdata.isAdd) {
if(vdata.saveObject.passwordType == 'default' || !vdata.saveObject.loginPassword) {
vdata.saveObject.loginPassword = ''
}
if(vdata.saveObject.passwordType == 'custom' && !vdata.passwordRules.test(vdata.saveObject.loginPassword)) {
vdata.confirmLoading = false
return message.error(vdata.passwordRulesText)
}
req.add(API_URL_SYS_USER_LIST, vdata.saveObject).then(res => {
message.success('新增成功')
vdata.isShow = false
vdata.loading = false
props.callbackFunc() // 刷新列表
}).catch((res) => {
vdata.confirmLoading = false
})
} else {
vdata.sysPassword.confirmPwd = Base64.encode(vdata.sysPassword.confirmPwd)
Object.assign(vdata.saveObject, vdata.sysPassword) // 拼接对象
console.log(vdata.saveObject)
req.updateById(API_URL_SYS_USER_LIST, vdata.recordId, vdata.saveObject).then(res => {
message.success('修改成功')
vdata.isShow = false
props.callbackFunc() // 刷新列表
vdata.resetIsShow = false // 取消展示
vdata.sysPassword.resetPass = false
vdata.sysPassword.defaultPass = true// 是否使用默认密码默认为true
resetPassEmpty(vdata) // 清空密码
}).catch(res => {
vdata.confirmLoading = false
vdata.resetIsShow = false // 取消展示
vdata.sysPassword.resetPass = false
vdata.sysPassword.defaultPass = true// 是否使用默认密码默认为true
resetPassEmpty(vdata) // 清空密码
})
}
})
}
// 关闭抽屉
function onClose () {
vdata.isShow = false
vdata.resetIsShow = false // 取消重置密码板块展示
resetPassEmpty(vdata) // 清空密码
vdata.sysPassword.resetPass = false // 关闭密码输入
vdata.sysPassword.defaultPass = true// 是否使用默认密码默认为true
}
// 使用默认密码重置是否为true
function isResetPass () {
if (!vdata.sysPassword.defaultPass) {
vdata.newPwd = ''
vdata.sysPassword.confirmPwd = ''
}
}
// 保存后清空密码
function resetPassEmpty (that) {
vdata.newPwd = ''
vdata.sysPassword.confirmPwd = ''
}
// 切换操作员类型
function changeUserType() {
if (vdata.saveObject.userType == 1 || vdata.saveObject.userType == 2) {
vdata.saveObject.isTeamLeader = 0
vdata.saveObject.teamId = ''
}
}
function randomPassage(randomFlag, min, max) { // 生成6位随机密码
let str = ''
let range = min
const arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
// 随机产生
if (randomFlag) {
range = Math.round(Math.random() * (max - min)) + min
}
for (var i = 0; i < range; i++) {
var pos = Math.round(Math.random() * (arr.length - 1))
str += arr[ pos ]
}
vdata.saveObject['loginPassword'] = str
}
</script>

View File

@@ -0,0 +1,150 @@
<template>
<a-drawer
v-model:visible="vdata.visible"
:title=" true ? '操作员详情' : '' "
:body-style="{ paddingBottom: '80px' }"
width="40%"
@close="onClose"
>
<a-row justify="space-between" type="flex">
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="姓名">
{{ vdata.detailData.realname }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="手机号">
{{ vdata.detailData.telphone }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="登录名">
{{ vdata.detailData.loginUsername }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="编号">
{{ vdata.detailData.userNo }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="所属团队">
{{ vdata.detailData.teamName || '无' }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col v-if="vdata.detailData.teamId" :sm="12">
<a-descriptions>
<a-descriptions-item label="是否队长">
{{ vdata.detailData.isTeamLeader === 1 ? '是' : '否' }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="拓展员性别">
{{ vdata.detailData.sex === 1 ? '男' : vdata.detailData.sex === 2 ? '女' : '未知' }}
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="状态">
<a-tag :color="vdata.detailData['state'] === 1?'green':'volcano'">
{{ vdata.detailData.state === 0?'禁用':vdata.detailData.state === 1?'启用':'未知' }}
</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-col>
<a-col :sm="12">
<a-descriptions>
<a-descriptions-item label="创建时间">
{{ vdata.detailData.createdAt }}
</a-descriptions-item>
</a-descriptions>
</a-col>
</a-row>
<a-divider orientation="left" v-if="vdata.detailData.userType === 3">
<a-tag color="green">
数据统计
</a-tag>
</a-divider>
<a-tabs v-model:activeKey="vdata.activeKey" @change="changeDateTime" v-if="vdata.detailData.userType === 3">
<a-tab-pane key="currMonth" tab="本月"><StatDetail :data="vdata.statData" /></a-tab-pane>
<a-tab-pane key="prevMonth" tab="上月"><StatDetail :data="vdata.statData" /></a-tab-pane>
<a-tab-pane key="today" tab="今天"><StatDetail :data="vdata.statData" /></a-tab-pane>
<a-tab-pane key="yesterday" tab="昨天"><StatDetail :data="vdata.statData" /></a-tab-pane>
<a-tab-pane key="customDateTime" tab="自定义">
<a-range-picker v-model:value="vdata.customDateTimeValue" show-time @change="customDateTimeChange" />
<StatDetail :data="vdata.statData" />
</a-tab-pane>
</a-tabs>
</a-drawer>
</template>
<script lang="ts" setup>
import { API_URL_SYS_USER_LIST, req, $userChart } from '@/api/manage'
import { defineProps, reactive, toRaw } from 'vue'
import StatDetail from './StatDetail.vue'
const props = defineProps({
callbackFunc: { type: Function,default:null }
})
const vdata = reactive({
btnLoading: false,
detailData: {} as any, // 数据对象
recordId: null, // 更新对象ID
visible: false, // 是否显示弹层/抽屉
activeKey: 'currMonth',
customDateTimeValue: [] as any, // 自定义时间
statData: {} // 拓展员统计数据
})
function show (recordId) { // 弹层打开事件
vdata.recordId = recordId
req.getById(API_URL_SYS_USER_LIST, recordId).then(res => {
vdata.detailData = res
})
vdata.visible = true
changeDateTime()
}
function onClose () {
vdata.visible = false
}
function changeDateTime() {
if (vdata.activeKey != 'customDateTime') {
$userChart(vdata.recordId, vdata.activeKey).then(res => {
vdata.statData = res
})
}else {
customDateTimeChange()
}
}
function customDateTimeChange() {
if (vdata.customDateTimeValue) {
const queryDateRange = 'customDateTime_' + vdata.customDateTimeValue[0].format('YYYY-MM-DD HH:mm:ss') + '_' + vdata.customDateTimeValue[1].format('YYYY-MM-DD HH:mm:ss')
$userChart(vdata.recordId, queryDateRange).then(res => {
vdata.statData = res
})
}
}
defineExpose({
show //抛出show函数给父组件
})
</script>

View File

@@ -0,0 +1,107 @@
<template>
<a-drawer
v-model:visible="vdata.isShow"
title="分配角色"
width="30%"
:mask-closable="true"
@close="vdata.isShow = false"
>
<div>
<div :style="{ borderBottom: '1px solid #E9E9E9' }">
<a-checkbox
:checked="vdata.checkedVal.length != 0 && vdata.allRoleList.length === vdata.checkedVal.length"
:indeterminate="vdata.checkedVal.length != 0 && vdata.allRoleList.length != vdata.checkedVal.length"
@change="onCheckAllChange"
>
全选
</a-checkbox>
</div>
<br>
<a-checkbox-group v-model:value="vdata.checkedVal" :options="vdata.allRoleList" />
</div>
<div class="drawer-btn-center">
<a-button :style="{ marginRight: '8px' }" @click="vdata.isShow = false">
<close-outlined />
取消
</a-button>
<a-button type="primary" :loading="vdata.confirmLoading" @click="handleOkFunc">
<check-outlined />
保存
</a-button>
</div>
</a-drawer>
</template>
<script lang="ts" setup>
import { $uSysUserRoleRela, req, reqLoad, API_URL_ROLE_LIST, API_URL_USER_ROLE_RELA_LIST } from '@/api/manage'
import {ref,reactive} from 'vue'
import {message} from 'ant-design-vue'
const props =defineProps({
callbackFunc: { type: Function,default:null }
})
const vdata = reactive ({
confirmLoading: false, // 显示确定按钮loading图标
isShow: false, // 是否显示弹层/抽屉
recordId: null, // 更新对象ID
allRoleList: []as any[], // 全部的角色集合 {label: '', value: ''}
checkedVal: []as any[] // 已选择的数据集合
})
defineExpose({show})
function show (recordId) { // 弹层打开事件
// 重置数据
vdata.allRoleList = []
vdata.checkedVal = []
vdata.confirmLoading = false // 关闭loading
vdata.recordId = recordId
// 查询所有角色列表
reqLoad.list(API_URL_ROLE_LIST, { pageSize: -1 }).then(res => {
if (res.total <= 0) {
return message.error(`当前暂无角色,请先行添加`)
}
vdata.allRoleList = []
res.records.map(role => {
vdata.allRoleList.push({ label: role.roleName, value: role.roleId })
vdata.isShow = true
})
// 查询已分配的列表
req.list(API_URL_USER_ROLE_RELA_LIST, { pageSize: -1, userId: recordId }).then(relaRes => {
relaRes.records.map(rela => {
vdata.checkedVal.push(rela.roleId)
})
})
})
}
function handleOkFunc () { // 点击【确认】按钮事件
vdata.confirmLoading = true // 显示loading
$uSysUserRoleRela(vdata.recordId, vdata.checkedVal).then(res => {
message.success('更新成功!')
vdata.isShow = false
if (props.callbackFunc !== undefined) {
props.callbackFunc() // 刷新列表
}
}).catch(res => { vdata.confirmLoading = false }) // 恢复loading
}
function onCheckAllChange (e) { // 点击全选/非全选的状态更改
vdata.checkedVal = []
if (e.target.checked) { // 全选
vdata.allRoleList.map(role => { vdata.checkedVal.push(role.value) })
}
}
</script>

View File

@@ -0,0 +1,32 @@
<template>
<a-row>
<a-col :span="8">
<a-statistic title="商户总数" :value="data.mchAllCount" style="margin-right: 50px" />
</a-col>
<a-col :span="8">
<a-statistic title="新增商户数" :value="data.mchTodayAddCount" />
</a-col>
<a-col :span="8">
<a-statistic title="商户交易总额" :precision="2" :value="(data.payAllAmount/100).toFixed(2)" />
</a-col>
</a-row>
<a-row style="margin-top: 30px;">
<a-col :span="8">
<a-statistic title="入网商户总数" :value="data.mchOnNetCount" style="margin-right: 50px" />
</a-col>
<a-col :span="8">
<a-statistic title="新增入网商户数" :value="data.mchOnNetNewCount" />
</a-col>
<a-col :span="8">
<a-statistic title="商户交易额" :precision="2" :value="(data.payAmount/100).toFixed(2)" />
</a-col>
</a-row>
</template>
<script lang="ts" setup>
import { defineProps, reactive } from 'vue'
const props = defineProps({
data: { type: Object, default:null }
})
</script>

View File

@@ -0,0 +1,261 @@
<template>
<page-header-wrapper>
<a-card>
<JeepaySearchForm v-if="$access('ENT_UR_ROLE_SEARCH')" :searchConditionNum="6" :searchFunc="searchFunc" :resetFunc="() => { data.searchData= {} }">
<jeepay-text-up v-model:value="data.searchData['sysUserId']" :placeholder="'用户ID'" />
<jeepay-text-up v-model:value="data.searchData['realname']" :placeholder="'用户姓名'" />
<jeepay-text-up v-model:value="data.searchData['telphone']" :placeholder="'用户手机号'" />
<a-form-item label="" class="table-search-item">
<a-select v-model:value="data.searchData['userType']" placeholder="操作员类型">
<a-select-option value="">全部</a-select-option>
<a-select-option value="1">超级管理员</a-select-option>
<a-select-option value="2">普通操作员</a-select-option>
<a-select-option value="3">商户扩展员</a-select-option>
</a-select>
</a-form-item>
</JeepaySearchForm>
<!-- 列表渲染 -->
<JeepayTable
ref="infoTable"
:init-data="true"
:req-table-data-func="reqTableDataFunc"
:table-columns="tableColumns"
:search-data="data.searchData"
row-key="sysUserId"
@btnLoadClose="data.btnLoading=false"
>
<template #topBtnSlot>
<div>
<a-button
v-if="$access('ENT_UR_USER_ADD')"
type="primary"
class="mg-b-30"
@click="addFunc"
>
<plus-outlined />
新建
</a-button>
</div>
</template>
<template #bodyCell="{column,record}">
<template v-if="column.key === 'avatar'">
<a-avatar size="default" :src="record.avatarUrl" />
</template>
<template v-if="column.key === 'realname'">
{{ record.realname }}
<a-tag v-if="record.initUser" :key="record.initUser" color="green">
初始
</a-tag>
</template>
<template v-if="column.key === 'state'">
<JeepayTableColState :state="record.state" :show-switch-type="$access('ENT_UR_USER_EDIT')" :on-change="(state) => { return updateState(record.sysUserId, state)}" />
</template>
<template v-if="column.key === 'inviteCode'">
<span v-if="record.userType == 1 || record.userType == 3">
<a-button v-clipboard:copy="record.inviteCode" v-clipboard:success="() => $infoBox.message.success('邀请码已复制')" type="link" size="small">{{ record.inviteCode }}</a-button>
<info-circle-outlined style="cursor: pointer;" @click="showQrImgFunc(record)" />
</span>
<span v-else />
</template>
<template v-if="column.key === 'sex'">
<span v-if="record.sex == 1">男</span>
<span v-else-if="record.sex == 2">女</span>
<span v-else>未知</span>
</template>
<template v-if="column.key === 'userType'">
<span v-if="record.userType == 1">超级管理员</span>
<span v-else-if="record.userType == 2">普通操作员</span>
<span v-else-if="record.userType == 3">商户拓展员</span>
<span v-else>其他</span>
</template>
<template v-if="column.key === 'op'">
<!-- 操作列插槽 -->
<JeepayTableColumns>
<a-button v-if="$access('ENT_UR_USER_VIEW')" type="link" @click="detailFunc(record.sysUserId)">详情</a-button>
<a-button v-if="$access('ENT_UR_USER_UPD_ROLE') && record.userType == 2" type="link" @click="roleDistFun(record.sysUserId)">变更角色</a-button>
<a-button v-if="$access('ENT_UR_USER_EDIT')" type="link" @click="editFunc(record.sysUserId)">修改</a-button>
<a-button v-if="$access('ENT_UR_USER_MFA_DELETE') && (userStore.userInfo['userType'] == '1' && record.mfaBindState == '1')" style="color: red;" type="link" @click="mfaRelieve(record.sysUserId)">MFA解绑</a-button>
<a-button v-if="$access('ENT_UR_USER_LOGIN_LIMIT_DELETE')" style="color: red;" type="link" @click="deleteLoginLimit(record.sysUserId)">解除登录限制</a-button>
<a-button v-if="$access('ENT_UR_USER_DELETE')" type="link" style="color: red;margin-right: 20px;" @click="delFunc(record.sysUserId)">删除</a-button>
</JeepayTableColumns>
</template>
</template>
</JeepayTable>
</a-card>
<a-modal v-model:visible="data.codeVisible" title="邀请码" :footer="null" :width="600" @ok="() => data.codeVisible = false">
<div v-if="!data.inviteCode">
<span>无邀请码</span>
<a-button type="link" @click="setInviteCodeFunc(data.sysUserId)">点此修复</a-button>
</div>
<div v-else>
<div>
<span>邀请码:{{ data.inviteCode }}</span>
<a-button v-clipboard:copy="data.inviteCode" v-clipboard:success="() => $infoBox.message.success('邀请码已复制')" type="link">复制</a-button>
</div>
<div>
<div>
<span>商户注册链接:{{ data.inviteCodeUrl }}</span>
<a-button v-clipboard:copy="data.inviteCodeUrl" v-clipboard:success="() => $infoBox.message.success('复制成功')" type="link">复制</a-button>
</div>
<div>
<span>商户注册二维码:</span>
<div style="padding: 20px 0 20px 50px;"><QrcodeVue :value="data.inviteCodeUrl" :size="200" /></div>
</div>
</div>
<div v-if="data.sysType == 'PLATFORM' || data.sysType == 'AGENT'">
<div>
<span>代理商注册链接:{{ data.agentInviteCodeUrl }}</span>
<a-button v-clipboard:copy="data.agentInviteCodeUrl" v-clipboard:success="() => $infoBox.message.success('复制成功')" type="link">复制</a-button>
</div>
<div>
<span>代理商注册二维码:</span>
<div style="padding: 20px 0 20px 50px;"><QrcodeVue :value="data.agentInviteCodeUrl" :size="200" /></div>
</div>
</div>
</div>
</a-modal>
<!-- 新增 / 修改 页面组件 -->
<InfoAddOrEdit ref="infoAddOrEdit" :callback-func="searchFunc" />
<!-- 分配角色 页面组件 -->
<RoleDist ref="roleDist" />
<!-- 详情页面组件 -->
<InfoDetail ref="infoDetail" :callback-func="searchFunc" />
</page-header-wrapper>
</template>
<script setup lang="ts">
import { API_URL_SYS_USER_LIST, req, reqLoad, $deleteLoginLimit } from '@/api/manage'
import { useUserStore } from '@/store/modules/user'
import InfoAddOrEdit from './AddOrEdit.vue'
import RoleDist from './RoleDist.vue'
import InfoDetail from './Detail.vue'
import QrcodeVue from 'qrcode.vue'
import { reactive,ref,getCurrentInstance} from 'vue'
const { $infoBox,$access } = getCurrentInstance()!.appContext.config.globalProperties
const userStore = useUserStore()
const tableColumns = reactive([
{key:'avatar',title: '头像', },
{key:'realname', title: '姓名', dataIndex: 'realname' },
{key:'sysUserId', title: '用户ID', dataIndex: 'sysUserId',},
{key:'sex', title: '性别', dataIndex: 'sex',},
{key:'userNo', title: '编号', dataIndex: 'userNo', },
{key:'telphone', title: '手机号', dataIndex: 'telphone', },
{key:'userType' ,title: '操作员类型', dataIndex: 'userType', },
{key:'teamName' ,title: '团队', dataIndex: 'teamName' },
{key:'inviteCode' ,title: '邀请码', dataIndex: 'inviteCode', },
{key:'state', title: '状态', align: 'center' ,},
{key:'createdAt', title: '创建时间', dataIndex: 'createdAt',},
{key: 'op',title: '操作', fixed: 'right',align: 'center' }
])
const infoTable = ref()
const infoAddOrEdit =ref()
const infoDetail = ref()
const roleDist = ref()
const data : any =reactive ({
tableColumns: tableColumns,
searchData: {},
btnLoading: false
})
// 请求table接口数据
function reqTableDataFunc(params){
return req.list(API_URL_SYS_USER_LIST, params)
}
function searchFunc() { // 点击【查询】按钮点击事件
data.btnLoading = true // 打开查询按钮的loading
infoTable.value.refTable(true)
}
function addFunc () { // 业务通用【新增】 函数
infoAddOrEdit.value.show()
}
function editFunc (recordId) { // 业务通用【修改】 函数
console.log(infoAddOrEdit)
infoAddOrEdit.value.show(recordId)
}
function detailFunc (recordId: any) { // 商户详情页
infoDetail.value.show(recordId)
}
function setInviteCodeFunc (recordId: any) { // 修复邀请码
return req.updateById(API_URL_SYS_USER_LIST, recordId, { setInviteCode: true }).then(res => {
$infoBox.message.success('修复成功')
console.log(res)
showQrImgFunc(res)
infoTable.value.refTable(false)
})
}
function delFunc(recordId) { // 业务通用【删除】 函数
$infoBox.confirmDanger('确认删除?', '', () => {
return req.delById(API_URL_SYS_USER_LIST, recordId).then(res => {
$infoBox.message.success('删除成功!')
infoTable.value.refTable(false)
})
})
}
function roleDistFun(recordId) { // 【分配权限】 按钮点击事件
roleDist.value.show(recordId)
}
function updateState (recordId, state) { // 【更新状态】
const title = state === 1 ? '确认[启用]该用户?' : '确认[停用]该用户?'
const content = state === 1 ? '启用后用户可进行登陆等一系列操作' : '停用后该用户将立即退出系统并不可再次登陆'
return new Promise<void>((resolve, reject) => {
$infoBox.confirmDanger(title, content, () => {
return reqLoad.updateById(API_URL_SYS_USER_LIST, recordId, { state: state }).then(res => {
searchFunc()
resolve()
}).catch(err => reject(err))
},
() => {
reject(new Error())
})
})
}
// 显示二维码图片
function showQrImgFunc(record){
data.codeVisible = true
data.inviteCodeUrl = record.inviteCodeUrl
data.inviteCode = record.inviteCode
data.agentInviteCodeUrl = record.agentInviteCodeUrl
data.sysUserId = record.sysUserId
data.sysType = record.sysType
}
function onCopySuccess () {
$infoBox.message.success('复制成功')
}
function mfaRelieve(recordId) { // MFA解绑
$infoBox.confirmDanger('确认解绑吗?', '', () => {
return req.updateById(API_URL_SYS_USER_LIST, 'mfaRelieve', {recordId: recordId}).then(res => {
$infoBox.message.success('解绑成功!')
infoTable.value.refTable(false)
})
})
}
function deleteLoginLimit(recordId) { // 解除登录限制
$infoBox.confirmDanger('确认解除吗?', '', () => {
return $deleteLoginLimit(recordId).then(res => {
$infoBox.message.success('解除成功!')
infoTable.value.refTable(false)
})
})
}
</script>

View File

@@ -0,0 +1,133 @@
<template>
<a-drawer
v-model:visible="vdata.isShow"
:title=" vdata.isAdd ? '新增团队' : '修改团队' "
placement="right"
:closable="true"
width="600"
:mask-closable="false"
@ok="handleOkFunc"
@close="onClose"
>
<!-- <a-modal :confirmLoading="confirmLoading"> -->
<a-form
ref="infoFormModel"
:model="vdata.saveObject"
layout="vertical"
:rules="rules"
style="padding-bottom:50px"
>
<a-row justify="space-between" type="flex">
<a-col :span="10">
<a-form-item label="团队名:" name="teamName">
<a-input v-model:value="vdata.saveObject.teamName" />
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="团队编号:" name="teamNo">
<a-input v-model:value="vdata.saveObject.teamNo" />
</a-form-item>
</a-col>
<a-col :span="10">
<a-form-item label="统计周期" name="statRangeType">
<a-select v-model:value="vdata.saveObject.statRangeType" placeholder="请选择用户类型">
<a-select-option value="year"></a-select-option>
<a-select-option value="quarter">季度</a-select-option>
<a-select-option value="month"></a-select-option>
<a-select-option value="week"></a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
<div class="drawer-btn-center">
<a-button :style="{ marginRight: '8px' }" @click="onClose">
<close-outlined />
取消
</a-button>
<a-button type="primary" :loading="vdata.confirmLoading" @click="handleOkFunc">
<check-outlined />
保存
</a-button>
</div>
</a-form>
</a-drawer>
</template>
<script setup lang="ts">
import { req, API_URL_SYS_USER_TEAM_LIST } from '@/api/manage'
import { reactive,ref } from 'vue'
import { message } from 'ant-design-vue'
const props = defineProps ({
callbackFunc: { type: Function, default: () => ({}) }
})
const infoFormModel = ref()
const vdata = reactive ({
loading: false, // 按钮上的loading
confirmLoading: false, // 显示确定按钮loading图标
isAdd: true, // 新增 or 修改页面标识
isShow: false, // 是否显示弹层/抽屉
saveObject: {} as any, // 数据对象
recordId: null, // 更新对象ID
})
const rules = reactive({
teamName: [{ required: true, message: '请输入团队名', trigger: 'blur' }],
teamNo: [{ required: true, message: '请输入团队编号', trigger: 'blur' }],
statRangeType: [{ required: true, message: '请选择统计周期', trigger: 'blur' }]
})
defineExpose({ show })
function show (recordId) { // 弹层打开事件
if (infoFormModel.value !== undefined) {
infoFormModel.value.resetFields()
}
vdata.isAdd = !recordId
// 数据恢复为默认数据
// vdata.saveObject = {isAdmin: 1, state: 1, sex: 1, passwordType: 'default', isNotify: 0}
vdata.confirmLoading = false // 关闭loading
if (!vdata.isAdd) { // 修改信息 延迟展示弹层
vdata.recordId = recordId
req.getById(API_URL_SYS_USER_TEAM_LIST, recordId).then(res => { vdata.saveObject = res })
vdata.isShow = true
} else {
vdata.saveObject.statRangeType = 'year'
vdata.isShow = true // 立马展示弹层信息
}
}
function handleOkFunc () { // 点击【确认】按钮事件
infoFormModel.value.validate().then(valid=>{
vdata.loading = true // 打开按钮上的 loading
vdata.confirmLoading = true // 显示loading
if (vdata.isAdd) {
req.add(API_URL_SYS_USER_TEAM_LIST, vdata.saveObject).then(res => {
message.success('新增成功')
vdata.isShow = false
vdata.loading = false
props.callbackFunc() // 刷新列表
}).catch((res) => {
vdata.confirmLoading = false
})
} else {
req.updateById(API_URL_SYS_USER_TEAM_LIST, vdata.recordId, vdata.saveObject).then(res => {
message.success('修改成功')
vdata.isShow = false
props.callbackFunc() // 刷新列表
}).catch(res => {
vdata.confirmLoading = false
})
}
})
}
// 关闭抽屉
function onClose () {
vdata.isShow = false
}
</script>

Some files were not shown because too many files have changed in this diff Show More