refactor: 租户切换器useTenantOptions收拢三页
新建 composables/useTenantOptions.ts:会话/配置/统计三页各自的 tenants/tenantsLoading/tenantsError/loadTenants 拷贝收拢为单一 composable。两种形态: - 默认(会话/配置页):loadTenants 成功选中首个租户 + bootstrapTenantContext()(原两页 onMounted 公共体:超管拉列表/ 租户管理员固定自己/无 slug 防御不动),选中后的 watch 仍留各页 (重置逻辑页面各异); - selectFirst: false(统计页):纯下拉数据源,activeSlug 以 tenantFilter 别名解构('' = 全部租户合并视图),模板零改动。 dashboard 控制台是租户卡片墙(非下拉切换),不在此列。 composable 显式 import vue API——vitest 不跑 unplugin-auto-import。 新增 8 个 composable 测试(mock api/auth store,不装 pinia)。 vitest 164 passed(156+8);build 通过;dts 行尾漂移已还原。 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -488,7 +488,8 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useAuthStore } from '@/store/modules/auth'
|
||||
import { fetchTenantDetail, fetchTenantList, type TenantDetailResponse, type TenantListItem } from '@/api/tenants'
|
||||
import { fetchTenantDetail, type TenantDetailResponse } from '@/api/tenants'
|
||||
import { useTenantOptions } from '@/composables/useTenantOptions'
|
||||
import {
|
||||
fetchTenantConfig,
|
||||
normalizeBrandGroup,
|
||||
@@ -560,31 +561,19 @@
|
||||
() => GROUP_NAV.find((g) => g.key === activeGroup.value)?.label ?? ''
|
||||
)
|
||||
|
||||
// ---- 租户上下文(超管可切换;租户管理员固定自己——会话页同款) ------------------------
|
||||
// ---- 租户上下文(超管可切换;租户管理员固定自己——useTenantOptions 收拢) ----------------
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const isSuper = computed(() => authStore.role === 'super')
|
||||
|
||||
const tenants = ref<TenantListItem[]>([])
|
||||
const tenantsLoading = ref(false)
|
||||
const tenantsError = ref('')
|
||||
const activeSlug = ref('')
|
||||
|
||||
async function loadTenants(): Promise<void> {
|
||||
tenantsLoading.value = true
|
||||
tenantsError.value = ''
|
||||
try {
|
||||
const res = await fetchTenantList()
|
||||
tenants.value = res.items
|
||||
if (res.items.length > 0) {
|
||||
activeSlug.value = res.items[0].slug
|
||||
}
|
||||
} catch (error) {
|
||||
tenantsError.value = error instanceof Error ? error.message : '租户列表加载失败'
|
||||
} finally {
|
||||
tenantsLoading.value = false
|
||||
}
|
||||
}
|
||||
const {
|
||||
tenants,
|
||||
tenantsLoading,
|
||||
tenantsError,
|
||||
activeSlug,
|
||||
loadTenants,
|
||||
isSuper,
|
||||
bootstrapTenantContext
|
||||
} = useTenantOptions()
|
||||
|
||||
// ---- 配置状态(品牌表单 + 整份快照——Task 7b 起提示词/词表 pane 消费快照组) ----------
|
||||
|
||||
@@ -902,12 +891,7 @@
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (isSuper.value) {
|
||||
loadTenants() // 成功后置 activeSlug → watch 触发首载
|
||||
} else if (authStore.tenantSlug) {
|
||||
activeSlug.value = authStore.tenantSlug // watch 触发首载
|
||||
}
|
||||
// 租户管理员无 tenantSlug:模板已出防御告警,不加载
|
||||
bootstrapTenantContext()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -370,7 +370,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import { useAuthStore } from '@/store/modules/auth'
|
||||
import { fetchTenantList, type TenantListItem } from '@/api/tenants'
|
||||
import { useTenantOptions } from '@/composables/useTenantOptions'
|
||||
import {
|
||||
INTENT_OPTIONS,
|
||||
PUSH_STATUS_OPTIONS,
|
||||
@@ -394,30 +394,17 @@
|
||||
defineOptions({ name: 'ConversationList' })
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const isSuper = computed(() => authStore.role === 'super')
|
||||
|
||||
// ---- 租户上下文(超管可切换;租户管理员固定自己) --------------------------------
|
||||
const tenants = ref<TenantListItem[]>([])
|
||||
const tenantsLoading = ref(false)
|
||||
const tenantsError = ref('')
|
||||
const activeSlug = ref('')
|
||||
|
||||
async function loadTenants(): Promise<void> {
|
||||
tenantsLoading.value = true
|
||||
tenantsError.value = ''
|
||||
try {
|
||||
const res = await fetchTenantList()
|
||||
tenants.value = res.items
|
||||
// 默认选中第一个租户(选中后由 watch(activeSlug) 触发首载)
|
||||
if (res.items.length > 0) {
|
||||
activeSlug.value = res.items[0].slug
|
||||
}
|
||||
} catch (error) {
|
||||
tenantsError.value = error instanceof Error ? error.message : '租户列表加载失败'
|
||||
} finally {
|
||||
tenantsLoading.value = false
|
||||
}
|
||||
}
|
||||
// ---- 租户上下文(超管可切换;租户管理员固定自己——useTenantOptions 收拢) --------------
|
||||
const {
|
||||
tenants,
|
||||
tenantsLoading,
|
||||
tenantsError,
|
||||
activeSlug,
|
||||
loadTenants,
|
||||
isSuper,
|
||||
bootstrapTenantContext
|
||||
} = useTenantOptions()
|
||||
|
||||
// ---- 页签 -----------------------------------------------------------------------
|
||||
const activeTab = ref<'list' | 'pushLogs'>('list')
|
||||
@@ -663,12 +650,7 @@
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (isSuper.value) {
|
||||
loadTenants() // 成功后置 activeSlug → watch 触发首载
|
||||
} else if (authStore.tenantSlug) {
|
||||
activeSlug.value = authStore.tenantSlug // watch 触发首载
|
||||
}
|
||||
// 租户管理员无 tenantSlug:模板已出防御告警,不加载
|
||||
bootstrapTenantContext()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -198,7 +198,7 @@
|
||||
import type { EChartsOption } from '@/plugins/echarts'
|
||||
import { useAuthStore } from '@/store/modules/auth'
|
||||
import { fetchPlatformStats, fetchTenantStats, type StatsResponse } from '@/api/stats'
|
||||
import { fetchTenantList, type TenantListItem } from '@/api/tenants'
|
||||
import { useTenantOptions } from '@/composables/useTenantOptions'
|
||||
import { conversationSourceLabel } from '@/api/conversations'
|
||||
import { STATS_RANGE_OPTIONS, resolveStatsRange, type StatsRangeFilter } from '@/utils/statsRange'
|
||||
import {
|
||||
@@ -215,7 +215,6 @@
|
||||
defineOptions({ name: 'StatsOverview' })
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const isSuper = computed(() => authStore.role === 'super')
|
||||
|
||||
// ---- 筛选:时间档 + 自定义区间 + 租户(超管) --------------------------------------
|
||||
|
||||
@@ -224,22 +223,16 @@
|
||||
/** 当前筛选 → 查询参数;null = 自定义区间不完整(不发请求,保留上一份数据) */
|
||||
const params = computed(() => resolveStatsRange(rangeFilter.value, customRange.value))
|
||||
|
||||
const tenantFilter = ref('') // '' = 全部租户合并视图
|
||||
const tenants = ref<TenantListItem[]>([])
|
||||
const tenantsLoading = ref(false)
|
||||
const tenantsError = ref('')
|
||||
|
||||
async function loadTenants(): Promise<void> {
|
||||
tenantsLoading.value = true
|
||||
tenantsError.value = ''
|
||||
try {
|
||||
tenants.value = (await fetchTenantList()).items
|
||||
} catch (error) {
|
||||
tenantsError.value = error instanceof Error ? error.message : '租户列表加载失败'
|
||||
} finally {
|
||||
tenantsLoading.value = false
|
||||
}
|
||||
}
|
||||
// 纯下拉数据源(selectFirst: false):tenantFilter = activeSlug 改名,
|
||||
// '' = 全部租户合并视图,切换只进 reload 的查询参数——useTenantOptions 收拢
|
||||
const {
|
||||
tenants,
|
||||
tenantsLoading,
|
||||
tenantsError,
|
||||
loadTenants,
|
||||
isSuper,
|
||||
activeSlug: tenantFilter
|
||||
} = useTenantOptions({ selectFirst: false })
|
||||
|
||||
// ---- 统计取数(epoch 防筛选快速切换的过期响应竞写) --------------------------------
|
||||
|
||||
|
||||
Reference in New Issue
Block a user