站点添加筛选功能

This commit is contained in:
jxxghp
2025-07-01 11:38:00 +08:00
parent b01421aa94
commit 569bc3c8ec
5 changed files with 213 additions and 31 deletions

View File

@@ -24,10 +24,11 @@ const { t } = useI18n()
const cardProps = defineProps({
site: Object as PropType<Site>,
data: Object as PropType<SiteUserData>,
stats: Object as PropType<SiteStatistic>,
})
// 定义触发的自定义事件
const emit = defineEmits(['update', 'remove'])
const emit = defineEmits(['update', 'remove', 'refresh-stats'])
// 确认框
const createConfirm = useConfirm()
@@ -56,9 +57,6 @@ const resourceDialog = ref(false)
// 用户数据弹窗
const siteUserDataDialog = ref(false)
// 站点使用统计
const siteStats = ref<SiteStatistic>({})
// 查询站点图标
async function getSiteIcon() {
try {
@@ -84,16 +82,8 @@ async function testSite() {
testButtonText.value = t('site.testConnectivity')
testButtonDisable.value = false
getSiteStats()
} catch (error) {
console.error(error)
}
}
// 查询站点使用统计
async function getSiteStats() {
try {
siteStats.value = await api.get(`site/statistic/${cardProps.site?.domain}`)
// 测试完成后刷新统计数据
emit('refresh-stats', cardProps.site?.domain)
} catch (error) {
console.error(error)
}
@@ -140,16 +130,17 @@ async function deleteSiteInfo() {
// 根据站点状态显示不同的状态图标
const statColor = computed(() => {
if (isNullOrEmptyObject(siteStats.value)) {
if (!cardProps.stats || isNullOrEmptyObject(cardProps.stats)) {
return 'secondary'
}
if (siteStats.value?.lst_state == 1) {
if (cardProps.stats?.lst_state === 1) {
return 'error'
} else if (siteStats.value?.lst_state == 0) {
if (!siteStats.value?.seconds) return 'secondary'
if (siteStats.value?.seconds >= 5) return 'warning'
} else if (cardProps.stats?.lst_state === 0) {
if (!cardProps.stats?.seconds) return 'secondary'
if (cardProps.stats?.seconds >= 5) return 'warning'
return 'success'
}
return 'secondary'
})
// 数据百分比计算
@@ -185,19 +176,20 @@ function saveSite() {
// 更新站点Cookie UA后的回调
function onSiteCookieUpdated() {
siteCookieDialog.value = false
getSiteStats()
// Cookie更新后刷新统计数据
emit('refresh-stats', cardProps.site?.domain)
}
// 资源浏览弹窗关闭后的回调
function onSiteResourceDone() {
resourceDialog.value = false
getSiteStats()
// 资源操作完成后刷新统计数据
emit('refresh-stats', cardProps.site?.domain)
}
// 装载时查询站点图标
onMounted(() => {
getSiteIcon()
getSiteStats()
})
</script>

View File

@@ -19,6 +19,11 @@ export default {
noData: 'No data',
noContent: 'No relevant content found',
all: 'All',
active: 'Active',
inactive: 'Inactive',
filter: 'Filter',
noMatchingData: 'No matching data',
tryChangingFilters: 'Try changing filters',
default: 'Default',
name: 'Name',
create: 'Create',
@@ -884,6 +889,10 @@ export default {
testing: 'Testing ...',
testSuccess: '{name} connectivity test successful, ready to use!',
testFailed: '{name} connectivity test failed: {message}',
connectionNormal: 'Connection Normal',
connectionSlow: 'Connection Slow',
connectionFailed: 'Connection Failed',
connectionUnknown: 'Connection Unknown',
deleteConfirm: 'Are you sure you want to delete this site?',
deleteSuccess: '{name} deleted successfully!',
deleteFailed: '{name} deletion failed: {message}',

View File

@@ -19,6 +19,11 @@ export default {
noData: '暂无数据',
noContent: '没有找到相关内容',
all: '全部',
active: '激活',
inactive: '未激活',
filter: '筛选',
noMatchingData: '没有符合条件的数据',
tryChangingFilters: '请尝试更改筛选条件',
default: '默认',
name: '名称',
create: '新建',
@@ -881,6 +886,10 @@ export default {
testing: '测试中 ...',
testSuccess: '{name} 连通性测试成功,可正常使用!',
testFailed: '{name} 连通性测试失败:{message}',
connectionNormal: '连接正常',
connectionSlow: '连接缓慢',
connectionFailed: '连接失败',
connectionUnknown: '连接未知',
deleteConfirm: '是否确认删除站点?',
deleteSuccess: '{name} 删除成功!',
deleteFailed: '{name} 删除失败:{message}',

View File

@@ -19,6 +19,11 @@ export default {
noData: '暫無數據',
noContent: '沒有找到相關內容',
all: '全部',
active: '激活',
inactive: '未激活',
filter: '篩選',
noMatchingData: '沒有符合條件的數據',
tryChangingFilters: '請嘗試更改篩選條件',
default: '默認',
name: '名稱',
create: '新建',
@@ -880,6 +885,10 @@ export default {
testing: '測試中 ...',
testSuccess: '{name} 連通性測試成功,可正常使用!',
testFailed: '{name} 連通性測試失敗:{message}',
connectionNormal: '連接正常',
connectionSlow: '連接緩慢',
connectionFailed: '連接失敗',
connectionUnknown: '連接未知',
deleteConfirm: '是否確認刪除站點?',
deleteSuccess: '{name} 刪除成功!',
deleteFailed: '{name} 刪除失敗:{message}',

View File

@@ -22,6 +22,9 @@ const siteList = ref<Site[]>([])
// 站点数据列表
const userDataList = ref<SiteUserData[]>([])
// 站点统计数据列表
const siteStatsList = ref<{ [domain: string]: any }>({})
// 是否刷新过
const isRefreshed = ref(false)
@@ -31,6 +34,44 @@ const loading = ref(false)
// 新增站点对话框
const siteAddDialog = ref(false)
// 筛选相关
const filterMenu = ref(false)
const filterOption = ref('all') // all, active, inactive, connected, slow, failed, unknown
// 筛选选项
const filterOptions = computed(() => [
{ value: 'all', label: t('common.all'), icon: 'mdi-format-list-bulleted' },
{ value: 'active', label: t('common.active'), icon: 'mdi-check-circle', color: 'success' },
{ value: 'inactive', label: t('common.inactive'), icon: 'mdi-stop-circle', color: 'error' },
{ value: 'connected', label: t('site.connectionNormal'), icon: 'mdi-wifi', color: 'success' },
{ value: 'slow', label: t('site.connectionSlow'), icon: 'mdi-wifi-strength-2', color: 'warning' },
{ value: 'failed', label: t('site.connectionFailed'), icon: 'mdi-wifi-off', color: 'error' },
{ value: 'unknown', label: t('site.connectionUnknown'), icon: 'mdi-help-circle', color: 'secondary' },
])
// 筛选后的站点列表
const filteredSiteList = computed(() => {
if (filterOption.value === 'all') {
return siteList.value
}
return siteList.value.filter(site => {
if (filterOption.value === 'active') {
return site.is_active
} else if (filterOption.value === 'inactive') {
return !site.is_active
} else if (['connected', 'slow', 'failed', 'unknown'].includes(filterOption.value)) {
const connectionStatus = getConnectionStatus(site.domain)
return connectionStatus === filterOption.value
}
return true
})
})
// 当前筛选选项的显示信息
const currentFilter = computed(() => {
return filterOptions.value.find(option => option.value === filterOption.value)
})
// 获取站点列表数据
async function fetchData() {
try {
@@ -38,6 +79,8 @@ async function fetchData() {
siteList.value = await api.get('site/')
loading.value = false
isRefreshed.value = true
// 获取站点列表后,获取统计数据
await fetchSiteStats()
} catch (error) {
console.error(error)
}
@@ -52,10 +95,54 @@ async function fetchUserData() {
}
}
// 获取站点统计数据
async function fetchSiteStats() {
try {
// 使用批量接口一次性获取所有站点统计数据
const response = await api.get('site/statistic')
const stats = response.data || response
// 将数组转换为以domain为键的对象
const statsMap: { [domain: string]: any } = {}
if (Array.isArray(stats)) {
stats.forEach((stat: any) => {
if (stat.domain) {
statsMap[stat.domain] = stat
}
})
}
siteStatsList.value = statsMap
} catch (error) {
console.error('Failed to fetch site statistics:', error)
siteStatsList.value = {}
}
}
// 根据站点统计数据判断连接状态
function getConnectionStatus(domain: string) {
const stats = siteStatsList.value[domain]
if (!stats || Object.keys(stats).length === 0) {
return 'unknown'
}
if (stats.lst_state === 1) {
return 'failed'
} else if (stats.lst_state === 0) {
if (!stats.seconds) return 'unknown'
if (stats.seconds >= 5) return 'slow'
return 'connected'
}
return 'unknown'
}
// 保存站点排序
async function savaSitesPriority() {
// 只在显示全部站点时允许排序
if (filterOption.value !== 'all') {
return
}
// 重新排序
const priorities = siteList.value.map((site, index) => ({ id: site.id, pri: index + 1 }))
const priorities = filteredSiteList.value.map((site, index) => ({ id: site.id, pri: index + 1 }))
try {
const result: { [key: string]: any } = await api.post('site/priorities', priorities)
if (result.success) {
@@ -71,12 +158,39 @@ function getUserData(domain: string) {
return userDataList.value.find(userData => userData.domain === domain)
}
// 根据站点域名获取统计数据
function getSiteStats(domain: string) {
return siteStatsList.value[domain] || {}
}
// 处理站点统计数据刷新请求
async function handleRefreshStats(domain?: string) {
if (domain) {
// 刷新特定站点的统计数据
try {
const stats = await api.get(`site/statistic/${domain}`)
siteStatsList.value[domain] = stats
} catch (error) {
console.error(`Failed to refresh stats for ${domain}:`, error)
}
} else {
// 刷新所有站点统计数据
await fetchSiteStats()
}
}
// 更新站点事件时
function onSiteSave() {
siteAddDialog.value = false
fetchData()
}
// 选择筛选选项
function selectFilter(value: string) {
filterOption.value = value
filterMenu.value = false
}
// 加载时获取数据
onBeforeMount(() => {
fetchData()
@@ -101,28 +215,77 @@ useDynamicButton({
<template>
<div class="card-list-container">
<!-- 页面标题 -->
<VPageContentTitle :title="t('navItems.siteManager')" />
<!-- 页面标题和筛选按钮 -->
<div class="d-flex justify-space-between align-center mb-4">
<VPageContentTitle :title="t('navItems.siteManager')" class="mb-0" />
<!-- 筛选按钮 -->
<VMenu v-model="filterMenu" offset-y :close-on-content-click="false" location="bottom end">
<template #activator="{ props }">
<VBtn
v-bind="props"
:icon="display.smAndDown.value"
:variant="filterOption === 'all' ? 'text' : 'tonal'"
:color="currentFilter?.color"
>
<VIcon :icon="currentFilter?.icon || 'mdi-filter'" />
<span v-if="!display.smAndDown.value" class="ml-2">
{{ currentFilter?.label }}
</span>
<VIcon v-if="!display.smAndDown.value" icon="mdi-chevron-down" class="ml-1" />
</VBtn>
</template>
<!-- 筛选菜单 -->
<VCard min-width="200">
<VList>
<VListSubheader>{{ t('common.filter') }}</VListSubheader>
<VListItem
v-for="option in filterOptions"
:key="option.value"
:active="filterOption === option.value"
@click="selectFilter(option.value)"
>
<template #prepend>
<VIcon :icon="option.icon" :color="option.color" />
</template>
<VListItemTitle>{{ option.label }}</VListItemTitle>
<template #append>
<VIcon v-if="filterOption === option.value" icon="mdi-check" color="primary" />
</template>
</VListItem>
</VList>
</VCard>
</VMenu>
</div>
<LoadingBanner v-if="!isRefreshed" class="mt-12" />
<draggable
v-if="siteList.length > 0"
v-model="siteList"
v-if="filteredSiteList.length > 0"
v-model="filteredSiteList"
@end="savaSitesPriority"
handle=".cursor-move"
item-key="id"
tag="div"
:component-data="{ 'class': 'grid gap-4 grid-site-card px-2' }"
:disabled="filterOption !== 'all'"
>
<template #item="{ element }">
<SiteCard :site="element" :data="getUserData(element.domain)" @remove="fetchData" @update="fetchData" />
<SiteCard
:site="element"
:data="getUserData(element.domain)"
:stats="getSiteStats(element.domain)"
@remove="fetchData"
@update="fetchData"
@refresh-stats="handleRefreshStats"
/>
</template>
</draggable>
</div>
<NoDataFound
v-if="siteList.length === 0 && isRefreshed"
v-if="filteredSiteList.length === 0 && isRefreshed"
error-code="404"
:error-title="t('site.noSites')"
:error-description="t('site.sitesWillBeShownHere')"
:error-title="filterOption === 'all' ? t('site.noSites') : t('common.noMatchingData')"
:error-description="filterOption === 'all' ? t('site.sitesWillBeShownHere') : t('common.tryChangingFilters')"
/>
<!-- 新增站点按钮 -->
<VFab