feat: 迁移渠道列表新增和游戏累计分布
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
import service from '@/utils/request'
|
||||
|
||||
export const listChannels = params => service({ url: '/sw/promote/list', method: 'get', params })
|
||||
export const channelBindOptions = sourceDb => service({ url: '/sw/promote/bindOptions', method: 'get', params: { sourceDb } })
|
||||
export const createChannel = data => service({ url: '/sw/promote', method: 'post', data })
|
||||
export const channelGames = params => service({ url: '/sw/promote/game-distribution', method: 'get', params })
|
||||
@@ -56,6 +56,7 @@
|
||||
"/src/view/sw/AlertRules.vue": "SwAlertRules",
|
||||
"/src/view/sw/BusinessList.vue": "SwBusinessList",
|
||||
"/src/view/sw/ChannelActivationList.vue": "SwChannelActivationList",
|
||||
"/src/view/sw/ChannelList.vue": "SwChannelList",
|
||||
"/src/view/sw/components/BusinessBindings.vue": "SwBusinessBindings",
|
||||
"/src/view/sw/components/BusinessChannels.vue": "SwBusinessChannels",
|
||||
"/src/view/system/security/forceChangePassword.vue": "ForceChangePassword",
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="kra-search-box">
|
||||
<el-form :model="query" :inline="true" @submit.prevent="search">
|
||||
<el-form-item label="所属平台"><el-select v-model="sources" 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.busierAccount" clearable placeholder="商务账号" /></el-form-item>
|
||||
<el-form-item label="渠道账号"><el-input v-model="query.keyword" clearable placeholder="渠道账号" /></el-form-item>
|
||||
<el-form-item label="渠道层级"><el-select v-model="query.promoteLevel" clearable placeholder="全部层级" style="width: 140px"><el-option v-for="(label, value) in levels" :key="value" :label="label" :value="Number(value)" /></el-select></el-form-item>
|
||||
<el-form-item label="渠道类型"><el-select v-model="query.registerType" clearable placeholder="全部类型" style="width: 140px"><el-option label="个人" :value="0" /><el-option label="公会" :value="1" /></el-select></el-form-item>
|
||||
<el-form-item label="状态"><el-select v-model="query.lockStatus" clearable placeholder="全部状态" style="width: 140px"><el-option v-for="(label, value) in statuses" :key="value" :label="label" :value="Number(value)" /></el-select></el-form-item>
|
||||
<el-form-item><el-button type="primary" icon="search" native-type="submit">搜索</el-button></el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="kra-table-box">
|
||||
<div class="kra-btn-list"><el-button v-if="canAdd" type="primary" icon="plus" @click="adding = true">添加渠道</el-button></div>
|
||||
<el-alert v-if="!available" class="mb-4" type="warning" :closable="false" title="渠道数据尚未接入。" />
|
||||
<el-table v-loading="loading" :data="rows" :row-key="row => `${row.sourceDb}:${row.id}`">
|
||||
<el-table-column prop="platformName" label="所属平台" min-width="100" />
|
||||
<el-table-column prop="id" label="ID" min-width="90" />
|
||||
<el-table-column prop="account" label="渠道账号" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="渠道层级" min-width="110"><template #default="{ row }"><el-tag effect="plain">{{ levels[row.promoteLevel] || row.promoteLevel || '—' }}</el-tag></template></el-table-column>
|
||||
<el-table-column prop="busierDisplayName" label="归属商务" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column label="上级渠道" min-width="130" show-overflow-tooltip><template #default="{ row }">{{ row.parentName || '—' }}</template></el-table-column>
|
||||
<el-table-column label="负责人" min-width="100"><template #default="{ row }">{{ row.realName || '—' }}</template></el-table-column>
|
||||
<el-table-column label="渠道类型" min-width="100"><template #default="{ row }">{{ { 0: '个人', 1: '公会' }[row.registerType] ?? row.registerType ?? '—' }}</template></el-table-column>
|
||||
<el-table-column label="推广游戏" min-width="115"><template #default="{ row }"><el-button v-if="row.gameCount != null" link type="primary" @click="openGames(row)">{{ channelCount(row.gameCount) }} 款</el-button><span v-else>未接入</span></template></el-table-column>
|
||||
<el-table-column label="玩家数量" min-width="130"><template #default="{ row }">{{ channelCount(row.playerCount) }}</template></el-table-column>
|
||||
<el-table-column label="累计充值" min-width="160"><template #default="{ row }">{{ channelMoney(row.totalPayAmount) }}</template></el-table-column>
|
||||
<el-table-column label="状态" min-width="110"><template #default="{ row }"><el-tag :type="{ 0: 'info', '-1': 'danger', '-2': 'warning' }[row.lockStatus] || 'info'" effect="plain">{{ statuses[row.lockStatus] ?? row.lockStatus ?? '—' }}</el-tag></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>
|
||||
<add-channel-drawer v-model="adding" @success="search" />
|
||||
<channel-game-drawer v-model="gamesOpen" :channel="selected" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useUserStore } from '@/pinia/modules/user'
|
||||
import { useBtnAuth } from '@/utils/btnAuth'
|
||||
import { listChannels } from '@/api/channel'
|
||||
import { channelCount, channelMoney } from './channel-display'
|
||||
import AddChannelDrawer from './components/AddChannelDrawer.vue'
|
||||
import ChannelGameDrawer from './components/ChannelGameDrawer.vue'
|
||||
|
||||
defineOptions({ name: 'SwChannelList' })
|
||||
const user = useUserStore()
|
||||
const buttons = useBtnAuth()
|
||||
const canAdd = computed(() => Number(user.userInfo.authorityId) === 888 || !!buttons['sw:promote:add'])
|
||||
const platforms = { lx: '龙翔', hw: '汇玩', yq: '游趣' }
|
||||
const levels = { 1: '一级渠道', 2: '二级渠道', 3: '三级渠道' }
|
||||
const statuses = { 0: '正常', '-1': '已锁定', '-2': '解除合作' }
|
||||
const sources = ref(Object.keys(platforms))
|
||||
const query = reactive({ pageNum: 1, pageSize: 10, busierAccount: '', keyword: '', promoteLevel: undefined, registerType: undefined, lockStatus: undefined })
|
||||
const rows = ref([])
|
||||
const total = ref(0)
|
||||
const available = ref(true)
|
||||
const loading = ref(false)
|
||||
const adding = ref(false)
|
||||
const gamesOpen = ref(false)
|
||||
const selected = ref(null)
|
||||
let requestVersion = 0
|
||||
const load = async () => {
|
||||
const version = ++requestVersion
|
||||
if (!sources.value.length) { rows.value = []; total.value = 0; available.value = true; loading.value = false; return }
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await listChannels({ ...query, sourceDb: sources.value.join(','), busierAccount: query.busierAccount.trim(), keyword: query.keyword.trim() })
|
||||
if (version !== requestVersion) 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 === requestVersion) { rows.value = []; total.value = 0 } } finally { if (version === requestVersion) loading.value = false }
|
||||
}
|
||||
const search = () => { query.pageNum = 1; if (!sources.value.length) ElMessage.warning('请选择平台'); return load() }
|
||||
const openGames = row => { selected.value = row; gamesOpen.value = true }
|
||||
onMounted(load)
|
||||
</script>
|
||||
@@ -0,0 +1,8 @@
|
||||
export const channelCount = value => value == null ? '未接入' : String(value).replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
||||
export const channelMoney = value => {
|
||||
if (value == null) return '未接入'
|
||||
const text = String(value)
|
||||
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)}`
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<el-drawer v-model="visible" append-to-body destroy-on-close title="添加渠道" size="min(520px, 100vw)" :close-on-click-modal="!saving" :close-on-press-escape="!saving" :show-close="!saving" @closed="reset">
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-position="top" :disabled="saving">
|
||||
<el-form-item label="所属平台" prop="sourceDb"><el-select v-model="form.sourceDb" placeholder="请选择所属平台" clearable @change="loadOptions"><el-option v-for="(label, value) in platforms" :key="value" :label="label" :value="value" /></el-select></el-form-item>
|
||||
<el-form-item label="游戏商务" prop="platformBusierId"><el-select v-model="form.platformBusierId" placeholder="请选择游戏商务" filterable clearable :disabled="!form.sourceDb" :loading="optionsLoading"><el-option v-for="item in options" :key="item.id" :label="item.account" :value="item.id" /></el-select></el-form-item>
|
||||
<el-form-item label="推广类型"><el-select v-model="form.registerType"><el-option label="个人" :value="0" /><el-option label="企业" :value="1" /></el-select></el-form-item>
|
||||
<el-form-item label="渠道账号" prop="account"><el-input v-model="form.account" placeholder="中文/英文/数字,4-30位" maxlength="30" clearable /></el-form-item>
|
||||
<el-form-item label="渠道密码" prop="password"><el-input v-model="form.password" type="password" show-password placeholder="6-15位" maxlength="15" autocomplete="new-password" /></el-form-item>
|
||||
</el-form>
|
||||
<el-alert type="info" :closable="false" title="创建后该渠道归属到所选游戏商务名下,由其拓展并管理玩家。" />
|
||||
<template #footer><el-button :disabled="saving" @click="visible = false">取消</el-button><el-button type="primary" :loading="saving" @click="save">确认添加</el-button></template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { channelBindOptions, createChannel } from '@/api/channel'
|
||||
const props = defineProps({ modelValue: Boolean })
|
||||
const emit = defineEmits(['update:modelValue', 'success'])
|
||||
const visible = computed({ get: () => props.modelValue, set: value => emit('update:modelValue', value) })
|
||||
const platforms = { lx: '龙翔', hw: '汇玩', yq: '游趣' }
|
||||
const initial = () => ({ sourceDb: '', platformBusierId: undefined, registerType: 0, account: '', password: '' })
|
||||
const form = ref(initial())
|
||||
const formRef = ref()
|
||||
const options = ref([])
|
||||
const optionsLoading = ref(false)
|
||||
const saving = ref(false)
|
||||
let optionVersion = 0
|
||||
const rules = {
|
||||
sourceDb: [{ required: true, message: '请选择所属平台', trigger: 'change' }],
|
||||
platformBusierId: [{ required: true, message: '请选择游戏商务', trigger: 'change' }],
|
||||
account: [{ required: true, message: '请输入渠道账号', trigger: 'blur' }, { validator: (_rule, value, callback) => /^[\u4e00-\u9fa5a-zA-Z0-9]{4,30}$/.test(value?.trim() || '') ? callback() : callback(new Error('支持中文、英文、数字;长度 4-30 位')), trigger: 'blur' }],
|
||||
password: [{ required: true, message: '请输入渠道密码', trigger: 'blur' }, { min: 6, max: 15, message: '密码长度需在 6 到 15 位', trigger: 'blur' }]
|
||||
}
|
||||
const reset = () => { ++optionVersion; form.value = initial(); options.value = []; optionsLoading.value = false }
|
||||
const loadOptions = async () => {
|
||||
const version = ++optionVersion
|
||||
form.value.platformBusierId = undefined
|
||||
options.value = []
|
||||
if (!form.value.sourceDb) { optionsLoading.value = false; return }
|
||||
optionsLoading.value = true
|
||||
try {
|
||||
const res = await channelBindOptions(form.value.sourceDb)
|
||||
if (version !== optionVersion || res.code !== 0) return
|
||||
const unique = new Map(res.data.map(item => [item.platformBusierId, { id: item.platformBusierId, account: item.platformBusierAccount?.trim() || String(item.platformBusierId) }]))
|
||||
options.value = [...unique.values()]
|
||||
} finally { if (version === optionVersion) optionsLoading.value = false }
|
||||
}
|
||||
const save = async () => {
|
||||
if (saving.value || !await formRef.value.validate().catch(() => false)) return
|
||||
saving.value = true
|
||||
try {
|
||||
const res = await createChannel({ ...form.value, account: form.value.account.trim() })
|
||||
if (res.code === 0) { ElMessage.success(res.msg || '添加成功,数据同步到列表可能有延迟'); visible.value = false; emit('success') }
|
||||
} finally { saving.value = false }
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<el-drawer v-model="visible" append-to-body destroy-on-close :title="`推广游戏数据 — ${channel?.nickname || channel?.account || ''}`" size="min(560px, 100vw)" @closed="reset">
|
||||
<p class="mb-4">该渠道在各推广游戏的注册与充值分布(累计口径)。</p>
|
||||
<el-alert v-if="error" type="warning" :closable="false" :title="error" />
|
||||
<el-table v-else v-loading="loading" :data="games">
|
||||
<el-table-column label="游戏" min-width="140" show-overflow-tooltip><template #default="{ row }">{{ row.gameName || '—' }}</template></el-table-column>
|
||||
<el-table-column label="玩家数量" min-width="110"><template #default="{ row }">{{ channelCount(row.registerCount) }}</template></el-table-column>
|
||||
<el-table-column label="充值金额" min-width="150"><template #default="{ row }">{{ channelMoney(row.payAmount) }}</template></el-table-column>
|
||||
</el-table>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { channelGames } from '@/api/channel'
|
||||
import { channelCount, channelMoney } from '../channel-display'
|
||||
const props = defineProps({ modelValue: Boolean, channel: { type: Object, default: null } })
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const visible = computed({ get: () => props.modelValue, set: value => emit('update:modelValue', value) })
|
||||
const games = ref([])
|
||||
const loading = ref(false)
|
||||
const error = ref('')
|
||||
let requestVersion = 0
|
||||
const reset = () => { ++requestVersion; games.value = []; error.value = ''; loading.value = false }
|
||||
const load = async () => {
|
||||
reset()
|
||||
const version = ++requestVersion
|
||||
if (!props.channel) return
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await channelGames({ sourceDb: props.channel.sourceDb, promoteId: props.channel.id, period: 'total' })
|
||||
if (version !== requestVersion) return
|
||||
if (res.code === 0) games.value = res.data
|
||||
else error.value = res.msg || '推广游戏数据读取失败'
|
||||
} catch { if (version === requestVersion) error.value = '推广游戏数据读取失败,请重试' } finally { if (version === requestVersion) loading.value = false }
|
||||
}
|
||||
watch(() => [props.modelValue, props.channel], ([open]) => { if (open) void load() })
|
||||
</script>
|
||||
Reference in New Issue
Block a user