小程序配置增加自动提现多商户管理;解冻成功后联动刷新详情余额。
新增商户列表/保存/证书上传入口(8080a);解冻面板回写余额并提示变动明细。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -67,6 +67,8 @@ const props = defineProps({
|
||||
isEditMode: { type: Boolean, default: false },
|
||||
})
|
||||
|
||||
const emit = defineEmits(['unfrozen'])
|
||||
|
||||
const username = computed(() => localStorage.getItem('username') || '')
|
||||
|
||||
const info = reactive({
|
||||
@@ -153,10 +155,14 @@ const handleUnfreeze = async () => {
|
||||
beizhu: unfreezeRemark.value,
|
||||
})
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('解冻成功')
|
||||
const tip = res.data?.unfreeze_result
|
||||
? `解冻成功:余额 ${res.data.unfreeze_result.balance_before} → ${res.data.unfreeze_result.balance_after} 元`
|
||||
: '解冻成功'
|
||||
ElMessage.success(tip)
|
||||
unfreezeAmount.value = 0
|
||||
unfreezeRemark.value = ''
|
||||
await load()
|
||||
emit('unfrozen', res.data || {})
|
||||
} else {
|
||||
ElMessage.error(res.msg || '解冻失败')
|
||||
}
|
||||
|
||||
@@ -170,6 +170,110 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="自动提现商户号" name="transfer_mch">
|
||||
<el-alert
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="shop-hint"
|
||||
title="仅影响用户「收款」发起的微信商家转账。按优先级从小到大尝试;某号失败(余额不足/限额等)自动换下一号。发起成功后打款单会强制记录 mch_id,回调按该记录处理。"
|
||||
/>
|
||||
<div class="asset-toolbar">
|
||||
<el-button type="primary" @click="openMchDialog()">新增商户</el-button>
|
||||
<el-button @click="loadMchList">刷新</el-button>
|
||||
<el-text type="info" size="small">证书目录:{{ mchCertBase || '加载后显示' }}</el-text>
|
||||
</div>
|
||||
<el-table :data="mchList" v-loading="mchLoading" border>
|
||||
<el-table-column prop="priority" label="优先级" width="80" />
|
||||
<el-table-column prop="mch_id" label="商户号" min-width="120" />
|
||||
<el-table-column prop="name" label="备注" min-width="120" />
|
||||
<el-table-column prop="appid" label="AppID" min-width="140" />
|
||||
<el-table-column label="启用" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.enabled ? 'success' : 'info'" size="small">{{ row.enabled ? '是' : '否' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="私钥" width="90">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.private_key_exists ? 'success' : 'danger'" size="small">
|
||||
{{ row.private_key_exists ? '已上传' : '缺失' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="平台公钥" width="90">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.platform_pub_key_exists ? 'success' : 'warning'" size="small">
|
||||
{{ row.platform_pub_key_exists ? '已上传' : '可选' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="openMchDialog(row)">编辑</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-dialog v-model="mchDialogVisible" :title="mchForm.id ? '编辑商户' : '新增商户'" width="680px" destroy-on-close>
|
||||
<el-form label-width="140px">
|
||||
<el-form-item label="商户号" required>
|
||||
<el-input v-model="mchForm.mch_id" :disabled="!!mchForm.id" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注名称">
|
||||
<el-input v-model="mchForm.name" />
|
||||
</el-form-item>
|
||||
<el-form-item label="小程序 AppID" required>
|
||||
<el-input v-model="mchForm.appid" />
|
||||
</el-form-item>
|
||||
<el-form-item label="APIv3 密钥" required>
|
||||
<el-input v-model="mchForm.api_v3_key" type="password" show-password :placeholder="mchForm.id ? '留空不修改' : '必填'" />
|
||||
</el-form-item>
|
||||
<el-form-item label="证书序列号" required>
|
||||
<el-input v-model="mchForm.cert_serial_no" />
|
||||
</el-form-item>
|
||||
<el-form-item label="转账场景 ID">
|
||||
<el-input v-model="mchForm.transfer_scene_id" placeholder="1005" />
|
||||
</el-form-item>
|
||||
<el-form-item label="回调 URL">
|
||||
<el-input v-model="mchForm.notify_url" placeholder="可与默认相同" />
|
||||
</el-form-item>
|
||||
<el-form-item label="优先级">
|
||||
<el-input-number v-model="mchForm.priority" :min="1" :max="9999" />
|
||||
<el-text type="info" size="small" style="margin-left:8px">数字越小越优先</el-text>
|
||||
</el-form-item>
|
||||
<el-form-item label="启用">
|
||||
<el-switch v-model="mchForm.enabled" />
|
||||
</el-form-item>
|
||||
<el-form-item label="私钥路径">
|
||||
<el-input v-model="mchForm.private_key_path" placeholder="上传后自动填,也可手写服务器绝对路径" />
|
||||
<el-upload
|
||||
:show-file-list="false"
|
||||
accept=".pem"
|
||||
:http-request="(opt) => uploadMchFile(opt, 'private_key')"
|
||||
style="margin-top:8px"
|
||||
>
|
||||
<el-button type="primary" link>上传 apiclient_key.pem</el-button>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
<el-form-item label="平台公钥路径">
|
||||
<el-input v-model="mchForm.platform_pub_key_path" placeholder="上传后自动填" />
|
||||
<el-upload
|
||||
:show-file-list="false"
|
||||
accept=".pem"
|
||||
:http-request="(opt) => uploadMchFile(opt, 'platform_pub_key')"
|
||||
style="margin-top:8px"
|
||||
>
|
||||
<el-button type="primary" link>上传 pub_key.pem</el-button>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="mchDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="mchSaving" @click="saveMch">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</div>
|
||||
@@ -209,6 +313,147 @@ const assetList = ref([])
|
||||
const assetLoading = ref(false)
|
||||
const assetSaving = ref(false)
|
||||
|
||||
const mchLoading = ref(false)
|
||||
const mchSaving = ref(false)
|
||||
const mchList = ref([])
|
||||
const mchCertBase = ref('')
|
||||
const mchDialogVisible = ref(false)
|
||||
const mchForm = reactive({
|
||||
id: null,
|
||||
mch_id: '',
|
||||
name: '',
|
||||
appid: '',
|
||||
api_v3_key: '',
|
||||
cert_serial_no: '',
|
||||
transfer_scene_id: '1005',
|
||||
notify_url: '',
|
||||
priority: 100,
|
||||
enabled: true,
|
||||
private_key_path: '',
|
||||
platform_pub_key_path: '',
|
||||
})
|
||||
|
||||
const loadMchList = async () => {
|
||||
mchLoading.value = true
|
||||
try {
|
||||
const res = await request.post('/peizhi/wxmchlb', { username: username.value })
|
||||
if (res.code === 403) {
|
||||
hasPermission.value = false
|
||||
return
|
||||
}
|
||||
if (res.code === 0 && res.data) {
|
||||
mchList.value = res.data.list || []
|
||||
mchCertBase.value = res.data.cert_base_dir || ''
|
||||
} else {
|
||||
ElMessage.error(res.msg || '加载商户失败')
|
||||
}
|
||||
} catch {
|
||||
ElMessage.error('网络错误')
|
||||
} finally {
|
||||
mchLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const openMchDialog = (row) => {
|
||||
if (row) {
|
||||
Object.assign(mchForm, {
|
||||
id: row.id,
|
||||
mch_id: row.mch_id,
|
||||
name: row.name || '',
|
||||
appid: row.appid || '',
|
||||
api_v3_key: '',
|
||||
cert_serial_no: row.cert_serial_no || '',
|
||||
transfer_scene_id: row.transfer_scene_id || '1005',
|
||||
notify_url: row.notify_url || '',
|
||||
priority: row.priority ?? 100,
|
||||
enabled: !!row.enabled,
|
||||
private_key_path: row.private_key_path || '',
|
||||
platform_pub_key_path: row.platform_pub_key_path || '',
|
||||
})
|
||||
} else {
|
||||
Object.assign(mchForm, {
|
||||
id: null,
|
||||
mch_id: '',
|
||||
name: '',
|
||||
appid: sysForm.weixin_appid || '',
|
||||
api_v3_key: '',
|
||||
cert_serial_no: '',
|
||||
transfer_scene_id: '1005',
|
||||
notify_url: '',
|
||||
priority: 100,
|
||||
enabled: true,
|
||||
private_key_path: '',
|
||||
platform_pub_key_path: '',
|
||||
})
|
||||
}
|
||||
mchDialogVisible.value = true
|
||||
}
|
||||
|
||||
const saveMch = async () => {
|
||||
if (!mchForm.mch_id || !mchForm.appid || !mchForm.cert_serial_no) {
|
||||
ElMessage.warning('请填写商户号、AppID、证书序列号')
|
||||
return
|
||||
}
|
||||
if (!mchForm.id && !mchForm.api_v3_key) {
|
||||
ElMessage.warning('新建必须填写 APIv3 密钥')
|
||||
return
|
||||
}
|
||||
mchSaving.value = true
|
||||
try {
|
||||
const payload = {
|
||||
username: username.value,
|
||||
id: mchForm.id,
|
||||
mch_id: mchForm.mch_id,
|
||||
name: mchForm.name,
|
||||
appid: mchForm.appid,
|
||||
cert_serial_no: mchForm.cert_serial_no,
|
||||
transfer_scene_id: mchForm.transfer_scene_id || '1005',
|
||||
notify_url: mchForm.notify_url,
|
||||
priority: mchForm.priority,
|
||||
enabled: mchForm.enabled,
|
||||
private_key_path: mchForm.private_key_path,
|
||||
platform_pub_key_path: mchForm.platform_pub_key_path,
|
||||
}
|
||||
if (mchForm.api_v3_key) payload.api_v3_key = mchForm.api_v3_key
|
||||
const res = await request.post('/peizhi/wxmchbj', payload)
|
||||
if (res.code === 0) {
|
||||
ElMessage.success(res.msg || '保存成功')
|
||||
mchDialogVisible.value = false
|
||||
await loadMchList()
|
||||
} else {
|
||||
ElMessage.error(res.msg || '保存失败')
|
||||
}
|
||||
} catch {
|
||||
ElMessage.error('网络错误')
|
||||
} finally {
|
||||
mchSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const uploadMchFile = async (opt, fileType) => {
|
||||
if (!mchForm.mch_id) {
|
||||
ElMessage.warning('请先填写商户号')
|
||||
return
|
||||
}
|
||||
const formData = new FormData()
|
||||
formData.append('username', username.value)
|
||||
formData.append('mch_id', mchForm.mch_id)
|
||||
formData.append('file_type', fileType)
|
||||
formData.append('file', opt.file)
|
||||
try {
|
||||
const res = await request.post('/peizhi/wxmchscwj', formData)
|
||||
if (res.code === 0 && res.data) {
|
||||
ElMessage.success('上传成功')
|
||||
if (fileType === 'private_key') mchForm.private_key_path = res.data.saved_path
|
||||
if (fileType === 'platform_pub_key') mchForm.platform_pub_key_path = res.data.saved_path
|
||||
} else {
|
||||
ElMessage.error(res.msg || '上传失败')
|
||||
}
|
||||
} catch {
|
||||
ElMessage.error('上传失败')
|
||||
}
|
||||
}
|
||||
|
||||
const fullUrl = (path) => {
|
||||
if (!path) return ''
|
||||
if (path.startsWith('http')) return path
|
||||
@@ -358,6 +603,7 @@ const uploadAsset = async ({ file }, row) => {
|
||||
onMounted(() => {
|
||||
loadSysConfig()
|
||||
loadAssets()
|
||||
loadMchList()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -279,6 +279,7 @@
|
||||
title="管事分红"
|
||||
:is-edit-mode="true"
|
||||
class="freeze-section"
|
||||
@unfrozen="onFreezeUnfrozen"
|
||||
/>
|
||||
|
||||
<!-- 手动修改余额弹窗 -->
|
||||
@@ -495,6 +496,17 @@ const fetchGuanliDetail = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const onFreezeUnfrozen = async (payload) => {
|
||||
const ur = payload?.unfreeze_result
|
||||
if (ur) {
|
||||
const after = parseFloat(ur.balance_after)
|
||||
if (!Number.isNaN(after)) {
|
||||
balanceForm.yue = after
|
||||
}
|
||||
}
|
||||
await fetchGuanliDetail()
|
||||
}
|
||||
|
||||
// 进入编辑模式:备份原始数据
|
||||
const enterEditMode = () => {
|
||||
originalData = {
|
||||
|
||||
@@ -354,6 +354,7 @@
|
||||
:leixing="1"
|
||||
title="打手佣金"
|
||||
:is-edit-mode="true"
|
||||
@unfrozen="onFreezeUnfrozen"
|
||||
/>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
@@ -362,6 +363,7 @@
|
||||
:leixing="5"
|
||||
title="打手押金"
|
||||
:is-edit-mode="true"
|
||||
@unfrozen="onFreezeUnfrozen"
|
||||
/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -487,6 +489,24 @@ const fetchDetail = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 解冻后同步上方「可提现余额/押金」展示(冻结面板与详情卡片区联动)
|
||||
const onFreezeUnfrozen = async (payload) => {
|
||||
const ur = payload?.unfreeze_result
|
||||
if (ur) {
|
||||
const after = parseFloat(ur.balance_after)
|
||||
if (!Number.isNaN(after)) {
|
||||
if (payload.leixing === 5) {
|
||||
playerInfo.value.yajin = after
|
||||
if (isEditMode.value) editData.value.yajin = after
|
||||
} else if (payload.leixing === 1) {
|
||||
playerInfo.value.yue = after
|
||||
if (isEditMode.value) editData.value.yue = after
|
||||
}
|
||||
}
|
||||
}
|
||||
await fetchDetail()
|
||||
}
|
||||
|
||||
// 编辑模式
|
||||
const toggleEditMode = () => {
|
||||
editData.value = JSON.parse(JSON.stringify(playerInfo.value))
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
title="商家余额"
|
||||
:is-edit-mode="true"
|
||||
class="freeze-section"
|
||||
@unfrozen="onFreezeUnfrozen"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -240,6 +241,17 @@ const fetchMerchantDetail = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const onFreezeUnfrozen = async (payload) => {
|
||||
const ur = payload?.unfreeze_result
|
||||
if (ur) {
|
||||
const after = parseFloat(ur.balance_after)
|
||||
if (!Number.isNaN(after) && merchant.value) {
|
||||
merchant.value.yue = after
|
||||
}
|
||||
}
|
||||
await fetchMerchantDetail()
|
||||
}
|
||||
|
||||
// 修改余额
|
||||
const openBalanceDialog = () => {
|
||||
balanceForm.value = { action: 'add_balance', amount: null }
|
||||
|
||||
Reference in New Issue
Block a user