feat: 迁移渠道激活列表和绑定期间流水

This commit is contained in:
Yvan
2026-11-03 20:53:25 +08:00
parent 838c2c496f
commit 8908430a89
28 changed files with 1180 additions and 9 deletions
+4
View File
@@ -0,0 +1,4 @@
import service from '@/utils/request'
export const listChannelActivations = (params) => service({ url: '/sw/promoteActivePool/bindList', method: 'get', params })
export const getActivationSpend = (id) => service({ url: '/sw/promoteActivePool/bindSpendDetail', method: 'get', params: { id } })
+1
View File
@@ -55,6 +55,7 @@
"/src/view/sw/AlertList.vue": "SwAlertList",
"/src/view/sw/AlertRules.vue": "SwAlertRules",
"/src/view/sw/BusinessList.vue": "SwBusinessList",
"/src/view/sw/ChannelActivationList.vue": "SwChannelActivationList",
"/src/view/sw/components/BusinessBindings.vue": "SwBusinessBindings",
"/src/view/sw/components/BusinessChannels.vue": "SwBusinessChannels",
"/src/view/system/security/forceChangePassword.vue": "ForceChangePassword",
+109
View File
@@ -0,0 +1,109 @@
<template>
<div>
<div class="kra-search-box">
<el-form :model="query" :inline="true" @submit.prevent="search">
<el-form-item label="所属平台"><el-select v-model="query.platformNames" multiple collapse-tags clearable placeholder="选择平台" style="width: 210px"><el-option v-for="(label, value) in platforms" :key="value" :label="label" :value="value" /></el-select></el-form-item>
<el-form-item label="渠道账号"><el-input v-model="query.promoteAccount" clearable placeholder="渠道账号" /></el-form-item>
<el-form-item label="绑定商务"><el-input v-model="query.busierAccount" clearable placeholder="绑定商务" /></el-form-item>
<el-form-item><el-button icon="refresh" @click="reset">重置</el-button><el-button type="primary" icon="search" native-type="submit">查询</el-button></el-form-item>
</el-form>
</div>
<div class="kra-table-box">
<el-alert v-if="!available" class="mb-4" type="warning" :closable="false" title="OA 渠道激活数据源尚未接入或暂不可用。" />
<el-table v-loading="loading" :data="rows" row-key="id">
<el-table-column prop="platformTitle" label="所属平台" min-width="100" />
<el-table-column prop="promoteAccount" label="渠道名称" min-width="140" show-overflow-tooltip />
<el-table-column prop="busierAccount" label="绑定商务" min-width="130" show-overflow-tooltip />
<el-table-column label="绑定日期" min-width="120"><template #default="{ row }">{{ date(row.bindAt) }}</template></el-table-column>
<el-table-column label="解绑时间" min-width="120"><template #default="{ row }">{{ date(row.unbindAt) }}</template></el-table-column>
<el-table-column label="累计流水金额" min-width="160"><template #default="{ row }">{{ currency(row.accumAmount) }}</template></el-table-column>
<el-table-column label="状态" width="110"><template #default="{ row }"><el-tag effect="plain" :type="statusTone(row.status)">{{ row.statusText || row.status || '—' }}</el-tag></template></el-table-column>
<el-table-column label="操作" width="120" fixed="right"><template #default="{ row }"><el-button v-if="row.status !== 'rejected' && row.statusText !== '已驳回'" link type="primary" @click="openFlow(row)">流水明细</el-button><span v-else>—</span></template></el-table-column>
</el-table>
<div class="kra-pagination"><el-pagination v-model:current-page="query.pageNum" v-model:page-size="query.pageSize" :total="total" :page-sizes="[10, 20, 50]" layout="total, sizes, prev, pager, next, jumper" @size-change="search" @current-change="load" /></div>
</div>
<el-dialog v-model="dialog" title="流水明细" width="min(640px, 95vw)" append-to-body destroy-on-close @closed="clearDetail">
<div v-loading="flowLoading">
<el-descriptions :column="1">
<el-descriptions-item label="所属平台">{{ selected?.platformTitle || '—' }}</el-descriptions-item>
<el-descriptions-item label="渠道名称">{{ selected?.promoteAccount || '—' }}</el-descriptions-item>
<el-descriptions-item label="绑定期间">{{ period }}</el-descriptions-item>
</el-descriptions>
<el-alert v-if="flowError" type="warning" :title="flowError" :closable="false" />
<template v-else-if="detail">
<el-alert class="mb-4" type="info" title="仅统计绑定期间产生的流水。" :closable="false" />
<el-table :data="detail.list" max-height="320">
<el-table-column prop="platformTitle" label="所属平台" min-width="100" />
<el-table-column prop="gameName" label="游戏名称" min-width="160" show-overflow-tooltip />
<el-table-column label="累计金额" min-width="150" align="right"><template #default="{ row }">{{ currency(row.totalAmount) }}</template></el-table-column>
</el-table>
<p class="mt-4 text-right">绑定期间累计流水:<strong>{{ currency(detail.totalAmount) }}</strong></p>
</template>
</div>
<template #footer><el-button @click="dialog = false">关闭</el-button></template>
</el-dialog>
</div>
</template>
<script setup>
import { computed, onMounted, reactive, ref } from 'vue'
import { listChannelActivations, getActivationSpend } from '@/api/activation'
defineOptions({ name: 'SwChannelActivationList' })
const platforms = { lx: '龙翔', hw: '汇玩', yq: '游趣' }
const query = reactive({ pageNum: 1, pageSize: 10, platformNames: Object.keys(platforms), promoteAccount: '', busierAccount: '' })
const rows = ref([])
const total = ref(0)
const available = ref(true)
const loading = ref(false)
const dialog = ref(false)
const flowLoading = ref(false)
const flowError = ref('')
const selected = ref(null)
const detail = ref(null)
let listVersion = 0
let detailVersion = 0
const date = value => value ? value.slice(0, 10) : '—'
// 金额按字符串分组,保留超出 JavaScript 精确整数范围的金额。
const currency = value => {
const text = String(value ?? '0').trim()
if (!/^-?\d+(\.\d+)?$/.test(text)) return '—'
const [whole, fraction = ''] = text.split('.')
return `¥${whole.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}.${fraction.padEnd(2, '0').slice(0, 2)}`
}
const statusTone = status => ({ pending: 'info', in_progress: 'warning', reached: 'success', rejected: 'danger', unbound: 'info' })[status] || 'info'
const period = computed(() => {
const start = detail.value?.bindAt || selected.value?.bindAt
const end = detail.value?.endAt || selected.value?.endAt
if (!start) return '—'
return end ? `${date(start)} 至 ${date(end)}` : `${date(start)} 起`
})
const load = async () => {
const version = ++listVersion
if (!query.platformNames.length) { rows.value = []; total.value = 0; available.value = true; loading.value = false; return }
loading.value = true
try {
const res = await listChannelActivations({ ...query, platformNames: query.platformNames.join(','), promoteAccount: query.promoteAccount.trim(), busierAccount: query.busierAccount.trim() })
if (version !== listVersion) return
if (res.code === 0) { rows.value = res.data.rows; total.value = res.data.total; available.value = res.data.available !== false }
else { rows.value = []; total.value = 0 }
} catch { if (version === listVersion) { rows.value = []; total.value = 0 } } finally { if (version === listVersion) loading.value = false }
}
const search = () => { query.pageNum = 1; return load() }
const reset = () => { Object.assign(query, { platformNames: Object.keys(platforms), promoteAccount: '', busierAccount: '' }); return search() }
const clearDetail = () => { ++detailVersion; detail.value = null; selected.value = null; flowError.value = ''; flowLoading.value = false }
const openFlow = async row => {
clearDetail()
const version = ++detailVersion
selected.value = row
dialog.value = true
flowLoading.value = true
try {
const res = await getActivationSpend(row.id)
if (version !== detailVersion) return
if (res.code === 0) detail.value = res.data
else flowError.value = res.msg || '流水明细读取失败'
} catch { if (version === detailVersion) flowError.value = '流水明细读取失败,请重试' } finally { if (version === detailVersion) flowLoading.value = false }
}
onMounted(load)
</script>