Files
Vue3/src/views/admin/ClubConfig.vue
XingQue fa6b456630 feat: 俱乐部配置页展示 APIv2 mch_key 与 APIv3 api_v3_key 及配置警告
与后端 club-manage 对齐,区分小程序收款密钥与提现转账密钥。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-10 21:34:32 +08:00

305 lines
9.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="club-config-page">
<el-alert
type="warning"
:closable="false"
show-icon
title="俱乐部主数据与小程序/支付/GoEasy 密钥"
description="仅超级管理员可编辑。修改后影响该俱乐部小程序登录、支付回调、IM 等,请谨慎操作。"
class="tip"
/>
<div class="toolbar">
<el-select v-model="currentClubId" placeholder="选择俱乐部" @change="loadClub" style="width: 200px">
<el-option v-for="c in clubList" :key="c.club_id" :label="c.name" :value="c.club_id" />
</el-select>
<el-button type="success" @click="openCreateDialog">新建俱乐部</el-button>
<el-button type="primary" :loading="saving" @click="saveClub">保存配置</el-button>
<el-button :icon="Refresh" circle @click="loadClub" :loading="loading" />
</div>
<el-dialog v-model="createVisible" title="新建俱乐部 / 小程序" width="480px">
<el-form label-width="120px">
<el-form-item label="俱乐部 ID" required>
<el-input v-model="createForm.new_club_id" placeholder="如 xy、al与小程序 club-config 一致)" />
</el-form-item>
<el-form-item label="展示名称" required>
<el-input v-model="createForm.name" placeholder="如:逍遥梦俱乐部" />
</el-form-item>
<el-form-item label="小程序 AppID">
<el-input v-model="createForm.wx_appid" placeholder="新建小程序的 AppID" />
</el-form-item>
<el-form-item label="复制模板">
<el-select v-model="createForm.from_club" style="width: 100%">
<el-option v-for="c in clubList" :key="c.club_id" :label="c.name" :value="c.club_id" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="createVisible = false">取消</el-button>
<el-button type="primary" :loading="creating" @click="createClub">创建</el-button>
</template>
</el-dialog>
<el-form v-loading="loading" :model="form" label-width="140px" class="config-form">
<el-divider content-position="left">小程序微信</el-divider>
<el-form-item label="wx_appid">
<el-input v-model="form.wx_appid" placeholder="小程序 AppID" />
</el-form-item>
<el-form-item label="wx_secret">
<el-input v-model="form.wx_secret" type="password" show-password placeholder="小程序 Secret" />
</el-form-item>
<el-divider content-position="left">微信支付同一商户号V2 收款 / V3 提现 密钥分开配</el-divider>
<el-form-item label="商户号 mch_id">
<el-input v-model="form.mch_id" />
</el-form-item>
<el-form-item label="pay_app_id">
<el-input v-model="form.pay_app_id" placeholder="可与 wx_appid 相同,或留空" />
</el-form-item>
<el-form-item label="APIv2 密钥 mch_key">
<el-input
v-model="form.mch_key"
type="password"
show-password
placeholder="小程序 JSAPI 收款(会员/点单/押金等)"
/>
<div class="field-help">微信商户平台 API安全 设置APIv2密钥与下方 APIv3 不是同一个</div>
</el-form-item>
<el-form-item label="APIv3 密钥 api_v3_key">
<el-input
v-model="form.api_v3_key"
type="password"
show-password
placeholder="自动提现 / 转账到零钱 / 回调解密"
/>
<div class="field-help">微信商户平台 API安全 设置APIv3密钥仅用于提现不用于小程序收款</div>
</el-form-item>
<el-alert
v-if="payWarnings.length"
type="warning"
:closable="false"
show-icon
class="pay-warn"
:title="payWarnings.join('')"
/>
<el-form-item label="cert_serial_no">
<el-input v-model="form.cert_serial_no" placeholder="APIv3 商户证书序列号(提现签名用)" />
</el-form-item>
<el-form-item label="private_key_path">
<el-input v-model="form.private_key_path" placeholder="服务器上 apiclient_key.pem 路径(提现签名用)" />
</el-form-item>
<el-form-item label="platform_cert_dir">
<el-input v-model="form.platform_cert_dir" placeholder="微信平台证书目录(提现回调验签用)" />
</el-form-item>
<el-divider content-position="left">服务号</el-divider>
<el-form-item label="official_appid">
<el-input v-model="form.official_appid" />
</el-form-item>
<el-form-item label="official_secret">
<el-input v-model="form.official_secret" type="password" show-password />
</el-form-item>
<el-form-item label="official_token">
<el-input v-model="form.official_token" />
</el-form-item>
<el-form-item label="encoding_aes_key">
<el-input v-model="form.encoding_aes_key" />
</el-form-item>
<el-divider content-position="left">GoEasy / OSS / 域名</el-divider>
<el-form-item label="goeasy_appkey">
<el-input v-model="form.goeasy_appkey" />
</el-form-item>
<el-form-item label="goeasy_secret">
<el-input v-model="form.goeasy_secret" type="password" show-password />
</el-form-item>
<el-form-item label="oss_prefix">
<el-input v-model="form.oss_prefix" placeholder="同一 COS 桶内目录前缀,如 xq/" />
</el-form-item>
<el-form-item label="h5_domain">
<el-input v-model="form.h5_domain" placeholder="商家 H5 域名" />
</el-form-item>
<el-form-item label="template_id">
<el-input v-model="form.template_id" />
</el-form-item>
<el-form-item label="展示名称">
<el-input v-model="form.name" />
</el-form-item>
</el-form>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { Refresh } from '@element-plus/icons-vue'
import request from '@/utils/request'
import { JITUAN_API } from '@/utils/club-context'
const loading = ref(false)
const saving = ref(false)
const creating = ref(false)
const createVisible = ref(false)
const clubList = ref([])
const currentClubId = ref('xq')
const createForm = reactive({
new_club_id: '',
name: '',
wx_appid: '',
from_club: 'xq',
})
const payWarnings = ref([])
const form = reactive({
name: '',
wx_appid: '',
wx_secret: '',
mch_id: '',
pay_app_id: '',
mch_key: '',
api_v3_key: '',
cert_serial_no: '',
private_key_path: '',
platform_cert_dir: '',
official_appid: '',
official_secret: '',
official_token: '',
encoding_aes_key: '',
goeasy_appkey: '',
goeasy_secret: '',
oss_prefix: '',
h5_domain: '',
template_id: '',
template_max_per_minute: 950,
sort_order: 0,
status: 1,
})
const phone = localStorage.getItem('username')
const loadClubList = async () => {
const res = await request.post(JITUAN_API.clubManage, { phone, action: 'list' })
if (res.code === 0) {
clubList.value = res.data || []
if (!currentClubId.value && clubList.value.length) {
currentClubId.value = clubList.value[0].club_id
}
}
}
const loadClub = async () => {
if (!currentClubId.value) return
loading.value = true
try {
const res = await request.post(JITUAN_API.clubManage, {
phone,
action: 'get',
club_id: currentClubId.value,
})
if (res.code === 0 && res.data) {
Object.assign(form, res.data)
payWarnings.value = (res.data.pay_config && res.data.pay_config.warnings) || []
} else {
ElMessage.error(res.msg || '加载失败')
}
} catch {
ElMessage.error('加载俱乐部配置失败,请确认已部署最新后端且账号为超级管理员')
} finally {
loading.value = false
}
}
const saveClub = async () => {
saving.value = true
try {
const res = await request.post(JITUAN_API.clubManage, {
phone,
action: 'update',
club_id: currentClubId.value,
...form,
})
if (res.code === 0) {
ElMessage.success('保存成功')
await loadClubList()
} else {
ElMessage.error(res.msg || '保存失败')
}
} catch {
ElMessage.error('保存失败')
} finally {
saving.value = false
}
}
const openCreateDialog = () => {
createForm.new_club_id = ''
createForm.name = ''
createForm.wx_appid = ''
createForm.from_club = currentClubId.value || 'xq'
createVisible.value = true
}
const createClub = async () => {
if (!createForm.new_club_id || !createForm.name) {
ElMessage.warning('请填写俱乐部 ID 和名称')
return
}
creating.value = true
try {
const res = await request.post(JITUAN_API.clubManage, {
phone,
action: 'create',
new_club_id: createForm.new_club_id.trim(),
name: createForm.name.trim(),
wx_appid: createForm.wx_appid.trim(),
from_club: createForm.from_club,
})
if (res.code === 0) {
ElMessage.success('俱乐部创建成功,请继续填写密钥并保存')
createVisible.value = false
await loadClubList()
currentClubId.value = createForm.new_club_id.trim()
await loadClub()
} else {
ElMessage.error(res.msg || '创建失败')
}
} catch {
ElMessage.error('创建失败,请确认账号有 000001 权限且后端已部署')
} finally {
creating.value = false
}
}
onMounted(async () => {
await loadClubList()
await loadClub()
})
</script>
<style scoped>
.club-config-page {
padding: 8px 4px;
}
.tip {
margin-bottom: 16px;
}
.toolbar {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 20px;
}
.config-form {
max-width: 720px;
}
.field-help {
font-size: 12px;
color: #909399;
line-height: 1.5;
margin-top: 4px;
}
.pay-warn {
margin-bottom: 16px;
}
</style>