132 lines
14 KiB
Vue
132 lines
14 KiB
Vue
<template>
|
|
<div class="sw-page">
|
|
<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="新商务 / SDK 商务账号" /></el-form-item>
|
|
<el-form-item label="渠道名称"><el-input v-model="query.keyword" clearable placeholder="渠道名称 / 账号 / 负责人" /></el-form-item>
|
|
<el-form-item label="手机号"><el-input v-model="query.phone" clearable placeholder="支持部分号码" /></el-form-item>
|
|
<el-form-item label="结算周期"><el-select v-model="query.settlementDays" clearable placeholder="全部周期" style="width:130px"><el-option label="1 天(日结)" :value="1" /><el-option label="7 天(周结)" :value="7" /></el-select></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 label="有效端"><el-select v-model="query.effective" clearable placeholder="全部" style="width:120px"><el-option label="是" :value="true" /><el-option label="否" :value="false" /></el-select></el-form-item>
|
|
<el-form-item label="注册时间"><el-date-picker v-model="createdDates" type="daterange" value-format="YYYY-MM-DD" start-placeholder="注册开始时间" end-placeholder="注册结束时间" style="width:290px" /></el-form-item>
|
|
<el-form-item label="登录时间"><el-date-picker v-model="loginDates" type="daterange" value-format="YYYY-MM-DD" start-placeholder="登录开始时间" end-placeholder="登录结束时间" style="width:290px" /></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>
|
|
<div class="channel-status-summary" role="status">
|
|
<div v-for="card in statusCards" :key="card.label" class="channel-status-item">
|
|
<el-tooltip content="统计当前筛选结果的全部渠道,正常、锁定、解除合作三类互斥,合计等于渠道数量。"><span>{{ card.label }}</span></el-tooltip>
|
|
<strong :class="card.type">{{ card.value == null ? '—' : channelCount(card.value) }}</strong>
|
|
</div>
|
|
<span class="channel-status-hint">按当前搜索结果统计</span>
|
|
</div>
|
|
<el-alert v-if="!available" class="mb-4" type="warning" :closable="false" title="渠道数据尚未接入。" />
|
|
<el-alert v-if="error" class="mb-4" type="error" :closable="false" :title="error" />
|
|
<el-table scrollbar-always-on v-loading="loading" :data="rows" :row-key="row => `${row.sourceDb}:${row.id}`" @sort-change="sortChanged">
|
|
<el-table-column prop="platformName" label="所属平台" width="104"><template #header><field-label label="所属平台" /></template><template #default="{ row }"><platform-tag :code="row.sourceDb" :label="row.platformName" /></template></el-table-column>
|
|
<el-table-column prop="id" label="ID" min-width="90" class-name="sw-col-meta" ><template #header><field-label label="ID" /></template></el-table-column>
|
|
<el-table-column prop="account" label="渠道名称" class-name="sw-col-identity" min-width="150" show-overflow-tooltip ><template #header><field-label label="渠道名称" /></template></el-table-column>
|
|
<el-table-column label="渠道层级" min-width="110"><template #header><field-label label="渠道层级" /></template><template #default="{ row }"><level-tag :level="row.promoteLevel" channel /></template></el-table-column>
|
|
<el-table-column prop="busierDisplayName" label="归属商务" min-width="200" show-overflow-tooltip ><template #header><field-label label="归属商务" /></template></el-table-column>
|
|
<el-table-column label="上级渠道" min-width="130" show-overflow-tooltip><template #header><field-label label="上级渠道" /></template><template #default="{ row }">{{ row.parentName || '—' }}</template></el-table-column>
|
|
<el-table-column label="负责人" min-width="100"><template #header><field-label label="负责人" /></template><template #default="{ row }">{{ row.realName || '—' }}</template></el-table-column>
|
|
<el-table-column prop="phone" label="手机号" min-width="130" class-name="sw-col-meta"><template #header><field-label label="手机号" /></template><template #default="{ row }">{{ row.phone || '—' }}</template></el-table-column>
|
|
<el-table-column label="结算周期" width="110"><template #header><field-label label="结算周期" /></template><template #default="{ row }">{{ row.settlementDays == null ? '—' : `${row.settlementDays} 天` }}</template></el-table-column>
|
|
<el-table-column label="渠道类型" min-width="100"><template #header><field-label label="渠道类型" /></template><template #default="{ row }"><el-tag v-if="row.registerType === 0 || row.registerType === 1" size="small" :type="row.registerType === 1 ? 'warning' : 'info'" effect="plain">{{ row.registerType === 1 ? '公会' : '个人' }}</el-tag><span v-else class="sw-empty-value">—</span></template></el-table-column>
|
|
<el-table-column label="推广游戏" min-width="115"><template #header><field-label label="推广游戏" /></template><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 prop="playerCount" label="玩家数量" min-width="110" align="right" class-name="sw-col-number" sortable="custom" :sort-orders="['descending','ascending',null]"><template #header><field-label label="玩家数量" /></template><template #default="{ row }"><metric-value :value="row.playerCount" /></template></el-table-column>
|
|
<el-table-column prop="totalPayAmount" label="累计充值" min-width="150" align="right" class-name="sw-col-number" sortable="custom" :sort-orders="['descending','ascending',null]"><template #header><field-label label="累计充值" /></template><template #default="{ row }"><metric-value kind="money" :value="row.totalPayAmount" /></template></el-table-column>
|
|
<el-table-column prop="createdAt" label="注册时间" min-width="150" sortable="custom" :sort-orders="['descending','ascending',null]" class-name="sw-col-meta"><template #header><field-label label="注册时间" /></template><template #default="{ row }">{{ dateText(row.createdAt) }}</template></el-table-column>
|
|
<el-table-column label="最后登录时间" min-width="150" class-name="sw-col-meta"><template #header><field-label label="最后登录时间" /></template><template #default="{ row }">{{ row.lastLoginAt ? dateText(row.lastLoginAt) : '—' }}</template></el-table-column>
|
|
<el-table-column label="有效端" width="100"><template #header><field-label label="有效端" /></template><template #default="{ row }"><el-tag size="small" :type="row.effective ? 'success' : 'info'">{{ row.effective ? '是' : '否' }}</el-tag></template></el-table-column>
|
|
<el-table-column label="达标时间" min-width="120" class-name="sw-col-meta"><template #header><field-label label="达标时间" /></template><template #default="{ row }">{{ dateText(row.qualifiedAt) }}</template></el-table-column>
|
|
<el-table-column label="状态" min-width="110"><template #header><field-label label="状态" /></template><template #default="{ row }"><el-tag size="small" :type="{ 0: 'success', '-1': 'danger', '-2': 'info' }[row.lockStatus] || 'info'" effect="plain">{{ statuses[row.lockStatus] ?? row.lockStatus ?? '—' }}</el-tag></template></el-table-column>
|
|
<el-table-column label="操作" width="90" fixed="right"><template #default="{ row }"><el-button type="primary" link @click="openProfile(row)">画像</el-button></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" />
|
|
<channel-profile-drawer v-model="profileOpen" :channel="profileChannel" />
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import FieldLabel from './components/FieldLabel.vue'
|
|
import { computed, onMounted, onBeforeUnmount, onDeactivated, onActivated, reactive, ref } from 'vue'
|
|
import { createLatestRequest } from '@/utils/latestRequest'
|
|
import { ElMessage } from 'element-plus'
|
|
import { useUserStore } from '@/pinia/modules/user'
|
|
import { useBtnAuth } from '@/utils/btnAuth'
|
|
import { listChannels } from '@/api/channel'
|
|
import { channelCount } from './channel-display'
|
|
import PlatformTag from './components/PlatformTag.vue'
|
|
import LevelTag from './components/LevelTag.vue'
|
|
import MetricValue from './components/MetricValue.vue'
|
|
import { dateText } from './performance-display'
|
|
import AddChannelDrawer from './components/AddChannelDrawer.vue'
|
|
import ChannelGameDrawer from './components/ChannelGameDrawer.vue'
|
|
import ChannelProfileDrawer from './components/ChannelProfileDrawer.vue'
|
|
|
|
defineOptions({ name: 'SwChannelList' })
|
|
const user = useUserStore()
|
|
const buttons = useBtnAuth()
|
|
const canAdd = computed(() => Number(user.userInfo.authorityId) === 888 || Number(user.userInfo.level) === 3 && !!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: '', phone: '', settlementDays: undefined, promoteLevel: undefined, registerType: undefined, lockStatus: undefined, effective: undefined, sortField: '', sortOrder: '' })
|
|
const createdDates = ref([]), loginDates = ref([]), statusCounts = ref(null), error = ref('')
|
|
const statusCards = computed(() => [{ label: '渠道数量', type: 'total', value: statusCounts.value ? total.value : null }, { label: '正常渠道', type: 'normal', value: statusCounts.value?.normal }, { label: '锁定渠道', type: 'locked', value: statusCounts.value?.locked }, { label: '解除合作', type: 'terminated', value: statusCounts.value?.terminated }])
|
|
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)
|
|
const profileOpen = ref(false)
|
|
const profileChannel = ref(null)
|
|
const openProfile = row => { profileChannel.value = row; profileOpen.value = true }
|
|
let requestVersion = 0
|
|
const requests = createLatestRequest()
|
|
let interrupted = false
|
|
const load = async () => {
|
|
const version = ++requestVersion
|
|
const request = requests.start()
|
|
if (!sources.value.length) { rows.value = []; total.value = 0; statusCounts.value = null; error.value = ''; available.value = true; loading.value = false; return }
|
|
loading.value = true; error.value = ''
|
|
try {
|
|
const res = await listChannels({ ...query, sourceDb: sources.value.join(','), busierAccount: query.busierAccount.trim(), keyword: query.keyword.trim(), phone: query.phone.trim(), createdBegin: createdDates.value?.[0] || '', createdEnd: createdDates.value?.[1] || '', loginBegin: loginDates.value?.[0] || '', loginEnd: loginDates.value?.[1] || '' }, { signal: request.signal })
|
|
if (version !== requestVersion) return
|
|
if (res.code === 0) { rows.value = res.data.rows; total.value = res.data.total; statusCounts.value = res.data.available === false ? null : res.data.statusCounts; available.value = res.data.available !== false }
|
|
else { rows.value = []; total.value = 0; statusCounts.value = null; error.value = res.msg || '渠道查询失败' }
|
|
} catch { if (version === requestVersion) { rows.value = []; total.value = 0; statusCounts.value = null; error.value = '渠道查询失败,请重试' } } finally { if (version === requestVersion) loading.value = false }
|
|
}
|
|
const sortChanged = ({ prop, order }) => { query.sortField = order ? prop : ''; query.sortOrder = order === 'ascending' ? 'asc' : 'desc'; return search() }
|
|
const search = () => { query.pageNum = 1; if (!sources.value.length) ElMessage.warning('请选择平台'); return load() }
|
|
const openGames = row => { selected.value = row; gamesOpen.value = true }
|
|
onMounted(load)
|
|
const cancel = () => { ++requestVersion; requests.cancel(); loading.value = false }
|
|
onBeforeUnmount(cancel)
|
|
onDeactivated(() => { interrupted = loading.value; cancel() })
|
|
onActivated(() => { if (interrupted) { interrupted = false; load() } })
|
|
</script>
|
|
<style scoped>
|
|
.channel-status-summary { display: flex; align-items: center; flex-wrap: wrap; gap: 12px 28px; padding: 12px 16px; margin-bottom: 14px; border-radius: 6px; background: var(--el-fill-color-light); }
|
|
.channel-status-item { display: inline-flex; align-items: baseline; gap: 8px; }
|
|
.channel-status-summary span { color: var(--el-text-color-secondary); font-size: 13px; }
|
|
.channel-status-summary strong { font-size: 20px; font-variant-numeric: tabular-nums; }
|
|
.channel-status-summary .total { color: var(--el-color-primary); }
|
|
.channel-status-summary .normal { color: var(--el-color-success); }
|
|
.channel-status-summary .locked { color: var(--el-color-warning); }
|
|
.channel-status-summary .terminated { color: var(--el-color-danger); }
|
|
.channel-status-hint { margin-left: auto; }
|
|
</style>
|