feat(channels): aggregate by channel with platform sections + rowspan table
Switch the user-facing 'Available Channels' view from "one row per platform" to "one channel row-group with N platform sections". Backend: userAvailableChannel now holds Platforms []section instead of being exploded. buildPlatformSections replaces explodeChannelByPlatform with the same per-platform grouping logic. Frontend: drop the DataTable wrapper for this view and write a four-column grid table (渠道名 / 平台 / 分组 / 支持模型) where the channel name only renders on the first platform row of each channel — visual rowspan without hacking DataTable. - api/channels.ts: UserChannelPlatformSection + platforms[] - AvailableChannelsTable: rewritten as native grid (header + per- channel section with hover row highlight) - AvailableChannelsView: search now filters platforms sub-array; channel-name / description hits still keep the whole channel - i18n: add availableChannels.columns.platform (zh/en)
This commit is contained in:
@@ -34,7 +34,7 @@
|
||||
|
||||
<template #table>
|
||||
<AvailableChannelsTable
|
||||
:columns="columns"
|
||||
:columns="columnLabels"
|
||||
:rows="filteredChannels"
|
||||
:loading="loading"
|
||||
pricing-key-prefix="availableChannels.pricing"
|
||||
@@ -65,22 +65,37 @@ const channels = ref<UserAvailableChannel[]>([])
|
||||
const loading = ref(false)
|
||||
const searchQuery = ref('')
|
||||
|
||||
const columns = computed(() => [
|
||||
{ key: 'name', label: t('availableChannels.columns.name') },
|
||||
{ key: 'groups', label: t('availableChannels.columns.groups') },
|
||||
{ key: 'supported_models', label: t('availableChannels.columns.supportedModels') }
|
||||
])
|
||||
const columnLabels = computed(() => ({
|
||||
name: t('availableChannels.columns.name'),
|
||||
platform: t('availableChannels.columns.platform'),
|
||||
groups: t('availableChannels.columns.groups'),
|
||||
supportedModels: t('availableChannels.columns.supportedModels'),
|
||||
}))
|
||||
|
||||
/**
|
||||
* 搜索过滤:
|
||||
* - 命中渠道名/描述 → 整个渠道(所有 platforms)都保留
|
||||
* - 否则按 platform/group/model 维度在 sections 里过滤,保留有匹配的 section
|
||||
* - 所有 sections 都不匹配时,渠道本身被过滤掉
|
||||
*/
|
||||
const filteredChannels = computed(() => {
|
||||
const q = searchQuery.value.trim().toLowerCase()
|
||||
if (!q) return channels.value
|
||||
return channels.value.filter((ch) => {
|
||||
if (ch.name.toLowerCase().includes(q)) return true
|
||||
if ((ch.description || '').toLowerCase().includes(q)) return true
|
||||
if (ch.groups.some((g) => g.name.toLowerCase().includes(q))) return true
|
||||
if (ch.supported_models.some((m) => m.name.toLowerCase().includes(q))) return true
|
||||
return false
|
||||
})
|
||||
return channels.value
|
||||
.map((ch) => {
|
||||
const nameHit = ch.name.toLowerCase().includes(q)
|
||||
const descHit = (ch.description || '').toLowerCase().includes(q)
|
||||
if (nameHit || descHit) return ch
|
||||
const matchingSections = ch.platforms.filter(
|
||||
(p) =>
|
||||
p.platform.toLowerCase().includes(q) ||
|
||||
p.groups.some((g) => g.name.toLowerCase().includes(q)) ||
|
||||
p.supported_models.some((m) => m.name.toLowerCase().includes(q)),
|
||||
)
|
||||
if (matchingSections.length === 0) return null
|
||||
return { ...ch, platforms: matchingSections }
|
||||
})
|
||||
.filter((ch): ch is UserAvailableChannel => ch !== null)
|
||||
})
|
||||
|
||||
async function loadChannels() {
|
||||
|
||||
Reference in New Issue
Block a user