feat: 客服「成交与自动结算」配置页与指标列表

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-07-27 01:29:29 +08:00
parent db18eb4fa8
commit 5af0ee5f09
4 changed files with 186 additions and 2 deletions

View File

@@ -84,6 +84,11 @@ const routes = [
component: () => import('@/views/order/MerchantOrder.vue'), component: () => import('@/views/order/MerchantOrder.vue'),
meta: { title: '商家订单' } meta: { title: '商家订单' }
}, },
{
path: 'order/deal-config',
component: () => import('@/views/order/OrderDealConfig.vue'),
meta: { title: '成交与自动结算' }
},
{ {
path: 'order/detail/:id', path: 'order/detail/:id',
component: () => import('@/views/order/Detail.vue'), component: () => import('@/views/order/Detail.vue'),
@@ -323,6 +328,11 @@ const routes = [
component: () => import('@/views/club/RechargePageCopy.vue'), component: () => import('@/views/club/RechargePageCopy.vue'),
meta: { title: '充值页文案' } meta: { title: '充值页文案' }
}, },
{
path: 'miniapp/role-agreement',
component: () => import('@/views/miniapp/RoleAgreement.vue'),
meta: { title: '角色协议' }
},
// ========== 店铺管理(原有) ========== // ========== 店铺管理(原有) ==========
{ {
path: 'shop/list', path: 'shop/list',

View File

@@ -133,6 +133,8 @@ const KEFU_MENU_ROWS = [
{ page_id: 'assessment.record', parent_id: 'assessment', path: '/assessment/record', sort_order: 23, perm_codes: ['kaohepeizhi'] }, { page_id: 'assessment.record', parent_id: 'assessment', path: '/assessment/record', sort_order: 23, perm_codes: ['kaohepeizhi'] },
{ page_id: 'order.platform', parent_id: 'order', path: '/order/platform', sort_order: 31, perm_codes: ['002ab'] }, { page_id: 'order.platform', parent_id: 'order', path: '/order/platform', sort_order: 31, perm_codes: ['002ab'] },
{ page_id: 'order.merchant', parent_id: 'order', path: '/order/merchant', sort_order: 32, perm_codes: ['002ac', '002ab', '003aa'] }, { page_id: 'order.merchant', parent_id: 'order', path: '/order/merchant', sort_order: 32, perm_codes: ['002ac', '002ab', '003aa'] },
{ page_id: 'order.deal-config', parent_id: 'order', path: '/order/deal-config', sort_order: 33, perm_codes: ['002ab', '002ac', '003aa'] },
{ page_id: 'order.merchant', parent_id: 'order', path: '/order/merchant', sort_order: 32, perm_codes: ['002ac', '002ab', '003aa'] },
{ page_id: 'cross-platform.order', parent_id: 'cross-platform', path: '/cross-order', sort_order: 41, perm_codes: ['003aa'] }, { page_id: 'cross-platform.order', parent_id: 'cross-platform', path: '/cross-order', sort_order: 41, perm_codes: ['003aa'] },
{ page_id: 'cross-platform.settings', parent_id: 'cross-platform', path: '/cross-settings', sort_order: 42, perm_codes: ['003aa'] }, { page_id: 'cross-platform.settings', parent_id: 'cross-platform', path: '/cross-settings', sort_order: 42, perm_codes: ['003aa'] },
{ page_id: 'cross-platform.data', parent_id: 'cross-platform', path: '/cross-data', sort_order: 43, perm_codes: ['003aa'] }, { page_id: 'cross-platform.data', parent_id: 'cross-platform', path: '/cross-data', sort_order: 43, perm_codes: ['003aa'] },
@@ -166,6 +168,7 @@ const KEFU_MENU_ROWS = [
{ page_id: 'miniapp.dashou-exam', parent_id: 'miniapp', path: '/miniapp/dashou-exam', sort_order: 117, perm_codes: ['dsks666', '8080a'] }, { page_id: 'miniapp.dashou-exam', parent_id: 'miniapp', path: '/miniapp/dashou-exam', sort_order: 117, perm_codes: ['dsks666', '8080a'] },
{ page_id: 'miniapp.rank-reward', parent_id: 'miniapp', path: '/miniapp/rank-reward', sort_order: 118, perm_codes: ['phbj666', '8080a'] }, { page_id: 'miniapp.rank-reward', parent_id: 'miniapp', path: '/miniapp/rank-reward', sort_order: 118, perm_codes: ['phbj666', '8080a'] },
{ page_id: 'miniapp.recharge-copy', parent_id: 'miniapp', path: '/miniapp/recharge-copy', sort_order: 119, perm_codes: ['8080a'] }, { page_id: 'miniapp.recharge-copy', parent_id: 'miniapp', path: '/miniapp/recharge-copy', sort_order: 119, perm_codes: ['8080a'] },
{ page_id: 'miniapp.role-agreement', parent_id: 'miniapp', path: '/miniapp/role-agreement', sort_order: 120, perm_codes: ['xycfg001', '8080a'] },
{ page_id: 'miniapp.withdraw-settings', parent_id: 'miniapp', path: '/withdraw/settings', sort_order: 114, perm_codes: ['5500a', '5500b', '5500c'] }, { page_id: 'miniapp.withdraw-settings', parent_id: 'miniapp', path: '/withdraw/settings', sort_order: 114, perm_codes: ['5500a', '5500b', '5500c'] },
{ page_id: 'shop.list', parent_id: 'shop', path: '/shop/list', sort_order: 121, perm_codes: ['999a', '999b', '999c', '999d', '999e'] }, { page_id: 'shop.list', parent_id: 'shop', path: '/shop/list', sort_order: 121, perm_codes: ['999a', '999b', '999c', '999d', '999e'] },
{ page_id: 'shop.withdraw', parent_id: 'shop', path: '/shop/withdraw-apply', sort_order: 122, perm_codes: ['999a', '999b', '999c', '999d', '999e'] }, { page_id: 'shop.withdraw', parent_id: 'shop', path: '/shop/withdraw-apply', sort_order: 122, perm_codes: ['999a', '999b', '999c', '999d', '999e'] },
@@ -177,13 +180,13 @@ const KEFU_MENU_PARENT_IDS = {
admin: ['admin.role', 'admin.data-scope', 'admin.club-config', 'admin.order-grab-share', 'admin.user', 'admin.operation-log'], admin: ['admin.role', 'admin.data-scope', 'admin.club-config', 'admin.order-grab-share', 'admin.user', 'admin.operation-log'],
finance: ['finance.bankuai', 'finance.data', 'finance.fund-freeze-config', 'finance.fund-freeze-ledger', 'finance.fund-risk-stats'], finance: ['finance.bankuai', 'finance.data', 'finance.fund-freeze-config', 'finance.fund-freeze-ledger', 'finance.fund-risk-stats'],
assessment: ['assessment.examiner', 'assessment.config', 'assessment.record'], assessment: ['assessment.examiner', 'assessment.config', 'assessment.record'],
order: ['order.platform', 'order.merchant'], order: ['order.platform', 'order.merchant', 'order.deal-config'],
'cross-platform': ['cross-platform.order', 'cross-platform.settings', 'cross-platform.data', 'cross-platform.pre'], 'cross-platform': ['cross-platform.order', 'cross-platform.settings', 'cross-platform.data', 'cross-platform.pre'],
user: ['user.player', 'user.guanli', 'user.shangjia', 'user.zuzhang', 'user.identity-tag'], user: ['user.player', 'user.guanli', 'user.shangjia', 'user.zuzhang', 'user.identity-tag'],
withdraw: ['withdraw.audit', 'withdraw.data', 'withdraw.settings'], withdraw: ['withdraw.audit', 'withdraw.data', 'withdraw.settings'],
product: ['product.list', 'product.type-zone', 'product.turntable', 'product.data', 'product.fake-orders'], product: ['product.list', 'product.type-zone', 'product.turntable', 'product.data', 'product.fake-orders'],
member: ['member.list', 'member.data'], member: ['member.list', 'member.data'],
miniapp: ['miniapp.carousel', 'miniapp.assets', 'miniapp.popup', 'miniapp.leixing', 'miniapp.kaohe-tags', 'miniapp.dashou-exam', 'miniapp.rank-reward', 'miniapp.recharge-copy', 'miniapp.withdraw-settings'], miniapp: ['miniapp.carousel', 'miniapp.assets', 'miniapp.popup', 'miniapp.leixing', 'miniapp.kaohe-tags', 'miniapp.dashou-exam', 'miniapp.rank-reward', 'miniapp.recharge-copy', 'miniapp.role-agreement', 'miniapp.withdraw-settings'],
shop: ['shop.list', 'shop.withdraw', 'shop.products'], shop: ['shop.list', 'shop.withdraw', 'shop.products'],
}; };
@@ -429,6 +432,10 @@ export const JITUAN_API = {
fundFreezeConfig: '/jituan/houtai/fund-freeze-config', fundFreezeConfig: '/jituan/houtai/fund-freeze-config',
fundFreezeLedger: '/jituan/houtai/fund-freeze-ledger', fundFreezeLedger: '/jituan/houtai/fund-freeze-ledger',
fundFreezeRisk: '/jituan/houtai/fund-freeze-risk', fundFreezeRisk: '/jituan/houtai/fund-freeze-risk',
roleAgreement: '/jituan/houtai/role-agreement',
orderDealConfig: '/jituan/houtai/order-deal-config',
merchantDealStat: '/jituan/houtai/merchant-deal-stat',
dashouDealStat: '/jituan/houtai/dashou-deal-stat',
paymentChannelManage: '/jituan/houtai/payment-channel-manage', paymentChannelManage: '/jituan/houtai/payment-channel-manage',
clubCertUpload: '/jituan/houtai/club-cert-upload', clubCertUpload: '/jituan/houtai/club-cert-upload',
memberList: '/jituan/houtai/hthqhylb', memberList: '/jituan/houtai/hthqhylb',

View File

@@ -57,6 +57,7 @@
</template> </template>
<el-menu-item v-if="canShow('order.platform')" index="/order/platform">平台订单</el-menu-item> <el-menu-item v-if="canShow('order.platform')" index="/order/platform">平台订单</el-menu-item>
<el-menu-item v-if="canShow('order.merchant')" index="/order/merchant">商家订单</el-menu-item> <el-menu-item v-if="canShow('order.merchant')" index="/order/merchant">商家订单</el-menu-item>
<el-menu-item v-if="canShow('order.deal-config')" index="/order/deal-config">成交与自动结算</el-menu-item>
</el-sub-menu> </el-sub-menu>
<!-- 跨平台管理 --> <!-- 跨平台管理 -->
@@ -156,6 +157,7 @@
<el-menu-item v-if="canShow('miniapp.dashou-exam')" index="/miniapp/dashou-exam">打手接单考试</el-menu-item> <el-menu-item v-if="canShow('miniapp.dashou-exam')" index="/miniapp/dashou-exam">打手接单考试</el-menu-item>
<el-menu-item v-if="canShow('miniapp.rank-reward')" index="/miniapp/rank-reward">排行榜奖励</el-menu-item> <el-menu-item v-if="canShow('miniapp.rank-reward')" index="/miniapp/rank-reward">排行榜奖励</el-menu-item>
<el-menu-item v-if="canShow('miniapp.recharge-copy')" index="/miniapp/recharge-copy">充值页文案</el-menu-item> <el-menu-item v-if="canShow('miniapp.recharge-copy')" index="/miniapp/recharge-copy">充值页文案</el-menu-item>
<el-menu-item v-if="canShow('miniapp.role-agreement')" index="/miniapp/role-agreement">角色协议</el-menu-item>
<el-menu-item v-if="canShow('miniapp.withdraw-settings')" index="/withdraw/settings">提现与收款设置</el-menu-item> <el-menu-item v-if="canShow('miniapp.withdraw-settings')" index="/withdraw/settings">提现与收款设置</el-menu-item>
</el-sub-menu> </el-sub-menu>

View File

@@ -0,0 +1,165 @@
<template>
<div class="page" v-loading="loading">
<ClubScopeHint />
<div class="header">
<h2>成交指标与自动结算</h2>
<el-button @click="load">刷新</el-button>
</div>
<el-alert
type="warning"
:closable="false"
show-icon
title="默认全关。打开自动结算后,仅「本次开启之后新进入结算中」的订单会计时并到期自动完成;历史结算中老单永远不会被自动结。"
style="margin-bottom: 16px"
/>
<div v-if="form.club_id" class="club">当前俱乐部{{ form.club_id }}</div>
<el-card shadow="never" class="card">
<template #header>自动结算</template>
<el-form label-width="160px">
<el-form-item label="启用自动结算">
<el-switch v-model="form.auto_settle_enabled" />
</el-form-item>
<el-form-item label="平台单超时(小时)">
<el-input-number v-model="form.hours_platform" :min="1" :max="720" />
</el-form-item>
<el-form-item label="商家单超时(小时)">
<el-input-number v-model="form.hours_merchant" :min="1" :max="720" />
</el-form-item>
<el-form-item v-if="form.auto_settle_enabled_at" label="本次开启时间">
<span>{{ form.auto_settle_enabled_at }}</span>
</el-form-item>
</el-form>
</el-card>
<el-card shadow="never" class="card">
<template #header>成交 / 罚款指标统计</template>
<el-form label-width="160px">
<el-form-item label="启用指标统计">
<el-switch v-model="form.stats_enabled" />
<span class="tip">从开启时刻起记账不算历史单</span>
</el-form-item>
<el-form-item v-if="form.stats_since" label="统计起点">
<span>{{ form.stats_since }}</span>
</el-form-item>
</el-form>
</el-card>
<el-button type="primary" :loading="saving" @click="save">保存</el-button>
<el-card v-if="form.stats_enabled" shadow="never" class="card" style="margin-top:16px">
<template #header>商家指标本俱乐部</template>
<el-table :data="merchantList" size="small" max-height="360">
<el-table-column prop="merchant_uid" label="商家UID" width="120" />
<el-table-column prop="order_count" label="订单量" width="90" />
<el-table-column prop="deal_rate" label="成交率" width="100">
<template #default="{ row }">{{ pct(row.deal_rate) }}</template>
</el-table-column>
<el-table-column prop="fine_rate" label="罚款率" width="100">
<template #default="{ row }">{{ pct(row.fine_rate) }}</template>
</el-table-column>
<el-table-column prop="refund_rate" label="退款率" width="100">
<template #default="{ row }">{{ pct(row.refund_rate) }}</template>
</el-table-column>
<el-table-column prop="avg_deal_hours" label="平均成交时长(h)" min-width="120" />
</el-table>
</el-card>
<el-card v-if="form.stats_enabled" shadow="never" class="card">
<template #header>打手指标本俱乐部</template>
<el-table :data="dashouList" size="small" max-height="360">
<el-table-column prop="dashou_uid" label="打手UID" width="120" />
<el-table-column prop="order_count" label="接单量" width="90" />
<el-table-column prop="deal_rate" label="成交率" width="100">
<template #default="{ row }">{{ pct(row.deal_rate) }}</template>
</el-table-column>
<el-table-column prop="fined_rate" label="被罚款率" width="100">
<template #default="{ row }">{{ pct(row.fined_rate) }}</template>
</el-table-column>
</el-table>
</el-card>
</div>
</template>
<script setup>
import { reactive, ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import request from '@/utils/request'
import { JITUAN_API } from '@/utils/club-context'
import ClubScopeHint from '@/components/ClubScopeHint.vue'
const username = localStorage.getItem('username') || ''
const loading = ref(false)
const saving = ref(false)
const merchantList = ref([])
const dashouList = ref([])
const form = reactive({
club_id: '',
auto_settle_enabled: false,
auto_settle_enabled_at: '',
hours_platform: 48,
hours_merchant: 48,
stats_enabled: false,
stats_since: '',
})
function pct(v) {
const n = Number(v) || 0
return `${(n * 100).toFixed(2)}%`
}
async function load() {
loading.value = true
try {
const res = await request.post(JITUAN_API.orderDealConfig, { username, action: 'get' })
if (res.code === 0 || res.code === 200) {
Object.assign(form, res.data || {})
} else {
ElMessage.error(res.msg || '加载失败')
}
if (form.stats_enabled) {
const [m, d] = await Promise.all([
request.post(JITUAN_API.merchantDealStat, { username, action: 'list' }),
request.post(JITUAN_API.dashouDealStat, { username, action: 'list' }),
])
merchantList.value = m.data?.list || []
dashouList.value = d.data?.list || []
}
} finally {
loading.value = false
}
}
async function save() {
saving.value = true
try {
const res = await request.post(JITUAN_API.orderDealConfig, {
username,
action: 'save',
auto_settle_enabled: form.auto_settle_enabled,
hours_platform: form.hours_platform,
hours_merchant: form.hours_merchant,
stats_enabled: form.stats_enabled,
})
if (res.code === 0 || res.code === 200) {
Object.assign(form, res.data || {})
ElMessage.success('已保存')
load()
} else {
ElMessage.error(res.msg || '保存失败')
}
} finally {
saving.value = false
}
}
onMounted(load)
</script>
<style scoped>
.page { padding: 16px; }
.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
.club { margin-bottom: 12px; color: #64748b; font-size: 13px; }
.card { margin-bottom: 16px; }
.tip { margin-left: 12px; color: #94a3b8; font-size: 12px; }
</style>