mirror of
https://github.com/hi2shark/nazhua.git
synced 2026-01-17 17:50:43 +08:00
✨ 新增列表的排序功能
This commit is contained in:
parent
93f66cb42c
commit
586f1dd063
5187
package-lock.json
generated
Normal file
5187
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
@ -11,12 +11,12 @@
|
|||||||
"lint": "eslint ."
|
"lint": "eslint ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.13.2",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"echarts": "^5.5.1",
|
"echarts": "^5.5.1",
|
||||||
"flag-icons": "^7.2.3",
|
"flag-icons": "^7.2.3",
|
||||||
"font-logos": "^1.3.0",
|
"font-logos": "^1.3.0",
|
||||||
"remixicon": "^4.6.0",
|
"remixicon": "^4.7.0",
|
||||||
"uniqolor": "^1.1.1",
|
"uniqolor": "^1.1.1",
|
||||||
"vue": "^3.5.12",
|
"vue": "^3.5.12",
|
||||||
"vue-echarts": "^7.0.3",
|
"vue-echarts": "^7.0.3",
|
||||||
@ -24,19 +24,19 @@
|
|||||||
"vuex": "^4.1.0"
|
"vuex": "^4.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.24.9",
|
"@babel/core": "^7.28.5",
|
||||||
"@babel/eslint-parser": "^7.24.8",
|
"@babel/eslint-parser": "^7.24.8",
|
||||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.7",
|
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.7",
|
||||||
"@babel/plugin-proposal-optional-chaining": "^7.21.0",
|
"@babel/plugin-proposal-optional-chaining": "^7.21.0",
|
||||||
"@vitejs/plugin-vue": "^5.1.4",
|
"@vitejs/plugin-vue": "^5.2.4",
|
||||||
"@vue/eslint-config-airbnb": "^7.0.0",
|
"@vue/eslint-config-airbnb": "^7.0.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"eslint": "^8.34.0",
|
"eslint": "^8.57.1",
|
||||||
"eslint-plugin-vue": "^9.9.0",
|
"eslint-plugin-vue": "^9.33.0",
|
||||||
"sass": "^1.81.0",
|
"sass": "^1.81.0",
|
||||||
"vite": "^5.4.10",
|
"vite": "^6.4.1",
|
||||||
"vite-plugin-babel": "^1.2.0",
|
"vite-plugin-babel": "^1.3.2",
|
||||||
"vite-plugin-eslint": "^1.8.1",
|
"vite-plugin-eslint": "^1.8.1",
|
||||||
"vite-svg-loader": "^5.1.0"
|
"vite-svg-loader": "^5.1.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -11,7 +11,7 @@ window.$$nazhuaConfig = {
|
|||||||
// showLantern: true, // 是否显示灯笼
|
// showLantern: true, // 是否显示灯笼
|
||||||
enableInnerSearch: true, // 启用内部搜索
|
enableInnerSearch: true, // 启用内部搜索
|
||||||
// listServerItemTypeToggle: true, // 服务器列表项类型切换
|
// listServerItemTypeToggle: true, // 服务器列表项类型切换
|
||||||
listServerItemType: 'row', // 服务器列表项类型 card/row/status row列表模式移动端自动切换至card
|
// listServerItemType: 'server-status', // 服务器列表项类型 card/row/server-status row列表模式移动端自动切换至card
|
||||||
// serverStatusColumnsTpl: null, // 服务器状态列配置模板
|
// serverStatusColumnsTpl: null, // 服务器状态列配置模板
|
||||||
// listServerStatusType: 'progress', // 服务器状态类型--列表
|
// listServerStatusType: 'progress', // 服务器状态类型--列表
|
||||||
// listServerRealTimeShowLoad: true, // 列表显示服务器实时负载
|
// listServerRealTimeShowLoad: true, // 列表显示服务器实时负载
|
||||||
@ -31,6 +31,7 @@ window.$$nazhuaConfig = {
|
|||||||
// hideListItemBill: false, // 隐藏列表项的账单信息
|
// hideListItemBill: false, // 隐藏列表项的账单信息
|
||||||
hideListItemLink: true, // 隐藏列表项的购买链接
|
hideListItemLink: true, // 隐藏列表项的购买链接
|
||||||
// hideFilter: false, // 隐藏筛选
|
// hideFilter: false, // 隐藏筛选
|
||||||
|
// hideSort: false, // 隐藏排序
|
||||||
// hideTag: false, // 隐藏标签
|
// hideTag: false, // 隐藏标签
|
||||||
// hideDotBG: true, // 隐藏框框里面的点点背景
|
// hideDotBG: true, // 隐藏框框里面的点点背景
|
||||||
// monitorRefreshTime: 10, // 监控刷新时间间隔,单位s(秒), 0为不刷新,为保证不频繁请求源站,最低生效值为10s
|
// monitorRefreshTime: 10, // 监控刷新时间间隔,单位s(秒), 0为不刷新,为保证不频繁请求源站,最低生效值为10s
|
||||||
|
|||||||
@ -30,7 +30,7 @@ function useCdnCss(item) {
|
|||||||
if (import.meta.env.VITE_USE_CDN) {
|
if (import.meta.env.VITE_USE_CDN) {
|
||||||
Object.entries({
|
Object.entries({
|
||||||
remixicon: {
|
remixicon: {
|
||||||
jsdelivr: 'https://cdn.jsdelivr.net/npm/remixicon@4.6.0/fonts/remixicon.css',
|
jsdelivr: 'https://cdn.jsdelivr.net/npm/remixicon@4.7.0/fonts/remixicon.css',
|
||||||
cdnjs: 'https://cdnjs.cloudflare.com/ajax/libs/remixicon/4.2.0/remixicon.css',
|
cdnjs: 'https://cdnjs.cloudflare.com/ajax/libs/remixicon/4.2.0/remixicon.css',
|
||||||
},
|
},
|
||||||
flagIcons: {
|
flagIcons: {
|
||||||
|
|||||||
341
src/views/components/server-list/server-sort-box.vue
Normal file
341
src/views/components/server-list/server-sort-box.vue
Normal file
@ -0,0 +1,341 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="server-sort-box"
|
||||||
|
:class="{
|
||||||
|
'server-sort-box--light-background': lightBackground,
|
||||||
|
'server-sort-box--mobile-hide': !mobileShow,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
ref="triggerRef"
|
||||||
|
class="sort-select-wrapper"
|
||||||
|
@click="toggleDropdown"
|
||||||
|
>
|
||||||
|
<div class="sort-select-selected">
|
||||||
|
<span class="sort-select-selected-value">{{ selectedLabel }}</span>
|
||||||
|
<span
|
||||||
|
class="sort-select-selected-icon"
|
||||||
|
@click.stop="toggleOrder"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-if="activeValue.order === 'desc'"
|
||||||
|
class="ri-arrow-down-line"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
class="ri-arrow-up-line"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 下拉菜单 -->
|
||||||
|
<Teleport to="body">
|
||||||
|
<server-sort-dropdown-menu
|
||||||
|
ref="dropdownMenuRef"
|
||||||
|
:visible="isDropdownOpen"
|
||||||
|
:options="options"
|
||||||
|
:active-value="activeValue.prop"
|
||||||
|
:dropdown-style="dropdownStyle"
|
||||||
|
:light-background="lightBackground"
|
||||||
|
:is-mobile="isMobile"
|
||||||
|
@select="handleSelectItem"
|
||||||
|
/>
|
||||||
|
</Teleport>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
/**
|
||||||
|
* 过滤栏
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
computed,
|
||||||
|
ref,
|
||||||
|
onMounted,
|
||||||
|
onUnmounted,
|
||||||
|
nextTick,
|
||||||
|
} from 'vue';
|
||||||
|
import config from '@/config';
|
||||||
|
import ServerSortDropdownMenu from './server-sort-dropdown-menu.vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
prop: 'DisplayIndex',
|
||||||
|
order: 'desc',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
acceptEmpty: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
mobileShow: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emits = defineEmits([
|
||||||
|
'update:modelValue',
|
||||||
|
'change',
|
||||||
|
]);
|
||||||
|
|
||||||
|
const lightBackground = computed(() => config.nazhua.lightBackground);
|
||||||
|
|
||||||
|
// 设备检测(用于判断是否小屏,小屏时居中显示)
|
||||||
|
const isMobile = ref(window.innerWidth < 768);
|
||||||
|
|
||||||
|
// PC端下拉菜单相关
|
||||||
|
const isDropdownOpen = ref(false);
|
||||||
|
const triggerRef = ref(null);
|
||||||
|
const dropdownMenuRef = ref(null);
|
||||||
|
const dropdownStyle = ref({});
|
||||||
|
|
||||||
|
const activeValue = computed({
|
||||||
|
get: () => props.modelValue,
|
||||||
|
set: (val) => {
|
||||||
|
emits('update:modelValue', val);
|
||||||
|
emits('change', val);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取当前选中项的label
|
||||||
|
const selectedLabel = computed(() => {
|
||||||
|
const selectedOption = props.options.find((opt) => opt.value === activeValue.value.prop);
|
||||||
|
return selectedOption ? selectedOption.label : '排序';
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新下拉菜单位置
|
||||||
|
function updateDropdownPosition() {
|
||||||
|
if (!triggerRef.value || !dropdownMenuRef.value) return;
|
||||||
|
|
||||||
|
// 使用 nextTick 确保 DOM 已更新
|
||||||
|
nextTick(() => {
|
||||||
|
const dropdownRef = dropdownMenuRef.value?.dropdownRef;
|
||||||
|
|
||||||
|
if (!dropdownRef) return;
|
||||||
|
|
||||||
|
// 小屏设备:居中显示
|
||||||
|
if (isMobile.value) {
|
||||||
|
dropdownStyle.value = {
|
||||||
|
position: 'fixed',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
visibility: 'visible',
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 大屏设备:相对定位
|
||||||
|
const triggerRect = triggerRef.value.getBoundingClientRect();
|
||||||
|
|
||||||
|
// 先设置一个初始位置,确保元素在视口中可见
|
||||||
|
let top = triggerRect.bottom + 8;
|
||||||
|
let { left } = triggerRect;
|
||||||
|
|
||||||
|
// 设置初始位置
|
||||||
|
dropdownStyle.value = {
|
||||||
|
position: 'fixed',
|
||||||
|
top: `${top}px`,
|
||||||
|
left: `${left}px`,
|
||||||
|
visibility: 'hidden', // 先隐藏,避免闪烁
|
||||||
|
};
|
||||||
|
|
||||||
|
// 再次使用 nextTick 确保样式已应用
|
||||||
|
nextTick(() => {
|
||||||
|
const dropdownRect = dropdownRef.getBoundingClientRect();
|
||||||
|
|
||||||
|
// 防止超出右边界
|
||||||
|
if (left + dropdownRect.width > window.innerWidth) {
|
||||||
|
left = window.innerWidth - dropdownRect.width - 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 防止超出下边界,如果超出则向上展开
|
||||||
|
if (top + dropdownRect.height > window.innerHeight) {
|
||||||
|
top = triggerRect.top - dropdownRect.height - 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 防止超出左边界
|
||||||
|
if (left < 10) {
|
||||||
|
left = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新最终位置并显示
|
||||||
|
dropdownStyle.value = {
|
||||||
|
position: 'fixed',
|
||||||
|
top: `${top}px`,
|
||||||
|
left: `${left}px`,
|
||||||
|
visibility: 'visible',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换下拉菜单显示状态
|
||||||
|
function toggleDropdown(event) {
|
||||||
|
event.stopPropagation(); // 阻止事件冒泡,防止立即被 handleDocumentClick 关闭
|
||||||
|
isDropdownOpen.value = !isDropdownOpen.value;
|
||||||
|
if (isDropdownOpen.value) {
|
||||||
|
nextTick(() => {
|
||||||
|
updateDropdownPosition();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换升序/降序
|
||||||
|
function toggleOrder(event) {
|
||||||
|
event.stopPropagation(); // 阻止事件冒泡,避免触发下拉菜单
|
||||||
|
if (!activeValue.value.prop) return; // 如果没有选中排序字段,则不切换
|
||||||
|
|
||||||
|
activeValue.value = {
|
||||||
|
prop: activeValue.value.prop,
|
||||||
|
order: activeValue.value.order === 'desc' ? 'asc' : 'desc',
|
||||||
|
};
|
||||||
|
emits('change', activeValue.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PC端选择项
|
||||||
|
function handleSelectItem(item) {
|
||||||
|
if (activeValue.value.prop === item.value) {
|
||||||
|
if (props.acceptEmpty) {
|
||||||
|
activeValue.value = {
|
||||||
|
prop: '',
|
||||||
|
order: 'desc',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
activeValue.value = {
|
||||||
|
prop: item.value,
|
||||||
|
order: activeValue.value.order || 'desc',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
isDropdownOpen.value = false;
|
||||||
|
emits('change', activeValue.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击外部关闭下拉菜单
|
||||||
|
function handleDocumentClick(event) {
|
||||||
|
if (!isDropdownOpen.value) return;
|
||||||
|
|
||||||
|
const dropdownRef = dropdownMenuRef.value?.dropdownRef;
|
||||||
|
|
||||||
|
if (
|
||||||
|
triggerRef.value
|
||||||
|
&& !triggerRef.value.contains(event.target)
|
||||||
|
&& dropdownRef
|
||||||
|
&& !dropdownRef.contains(event.target)
|
||||||
|
) {
|
||||||
|
isDropdownOpen.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 窗口resize处理
|
||||||
|
function handleResize() {
|
||||||
|
isMobile.value = window.innerWidth < 768;
|
||||||
|
|
||||||
|
// 如果下拉菜单打开,更新位置
|
||||||
|
if (isDropdownOpen.value) {
|
||||||
|
nextTick(() => {
|
||||||
|
updateDropdownPosition();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
document.addEventListener('click', handleDocumentClick);
|
||||||
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
document.removeEventListener('click', handleDocumentClick);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.server-sort-box {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
padding: 0 var(--list-padding);
|
||||||
|
gap: 8px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
&--mobile-hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PC端触发元素
|
||||||
|
.sort-select-wrapper {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-select-selected {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 36px;
|
||||||
|
padding: 0 15px;
|
||||||
|
line-height: 1.2;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: rgba(#000, 0.3);
|
||||||
|
transition: all 0.3s linear;
|
||||||
|
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
height: 30px;
|
||||||
|
padding: 0 10px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: rgba(#000, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-select-selected-value {
|
||||||
|
color: #fff;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-select-selected-icon {
|
||||||
|
margin-left: 8px;
|
||||||
|
color: #fff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 2px 4px;
|
||||||
|
border-radius: 3px;
|
||||||
|
transition: all 0.2s linear;
|
||||||
|
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(#fff, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: rgba(#fff, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PC端浅色背景样式
|
||||||
|
&--light-background {
|
||||||
|
.sort-select-selected {
|
||||||
|
background: rgba(#000, 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
145
src/views/components/server-list/server-sort-dropdown-menu.vue
Normal file
145
src/views/components/server-list/server-sort-dropdown-menu.vue
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-show="visible"
|
||||||
|
ref="dropdownRef"
|
||||||
|
class="server-sort-select-dropdown"
|
||||||
|
:class="{
|
||||||
|
'server-sort-select-dropdown--light-background': lightBackground,
|
||||||
|
'server-sort-select-dropdown--mobile': isMobile,
|
||||||
|
}"
|
||||||
|
:style="dropdownStyle"
|
||||||
|
>
|
||||||
|
<div class="sort-select-options">
|
||||||
|
<div
|
||||||
|
v-for="item in options"
|
||||||
|
:key="item.value"
|
||||||
|
class="server-sort-item"
|
||||||
|
:class="{
|
||||||
|
active: activeValue === item.value,
|
||||||
|
}"
|
||||||
|
:title="item?.title || false"
|
||||||
|
@click.stop="handleSelect(item, $event)"
|
||||||
|
>
|
||||||
|
<span class="option-label">{{ item.label }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
visible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
activeValue: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
dropdownStyle: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
lightBackground: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
isMobile: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const emits = defineEmits(['select']);
|
||||||
|
|
||||||
|
const dropdownRef = ref(null);
|
||||||
|
|
||||||
|
function handleSelect(item, event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
emits('select', item);
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
dropdownRef,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.server-sort-select-dropdown {
|
||||||
|
z-index: 500;
|
||||||
|
background: rgba(#000, 0.8);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 10px;
|
||||||
|
min-width: 150px;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||||
|
|
||||||
|
// 小屏居中显示样式
|
||||||
|
&--mobile {
|
||||||
|
min-width: 280px;
|
||||||
|
max-width: 90vw;
|
||||||
|
max-height: 70vh;
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-select-options {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-sort-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 36px;
|
||||||
|
padding: 0 15px;
|
||||||
|
line-height: 1.2;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: rgba(#000, 0.3);
|
||||||
|
transition: all 0.3s linear;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.option-label {
|
||||||
|
color: #fff;
|
||||||
|
font-weight: bold;
|
||||||
|
transition: all 0.3s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.option-label {
|
||||||
|
color: var(--option-high-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: var(--option-high-color-active);
|
||||||
|
|
||||||
|
.option-label {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 浅色背景样式
|
||||||
|
.server-sort-select-dropdown--light-background {
|
||||||
|
.server-sort-item {
|
||||||
|
background: rgba(#000, 0.5);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(#000, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: var(--option-high-color-active);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
128
src/views/composable/server-sort.js
Normal file
128
src/views/composable/server-sort.js
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
/**
|
||||||
|
* 服务器排序选项
|
||||||
|
*/
|
||||||
|
export const serverSortOptions = () => [{
|
||||||
|
label: '排序值',
|
||||||
|
value: 'DisplayIndex',
|
||||||
|
}, {
|
||||||
|
label: '主机名称',
|
||||||
|
value: 'Name',
|
||||||
|
}, {
|
||||||
|
label: '国家地区',
|
||||||
|
value: 'Host.CountryCode',
|
||||||
|
}, {
|
||||||
|
label: '系统平台',
|
||||||
|
value: 'Host.Platform',
|
||||||
|
}, {
|
||||||
|
label: '在线时长',
|
||||||
|
value: 'Host.BootTime',
|
||||||
|
}, {
|
||||||
|
label: '入网速度',
|
||||||
|
value: 'State.NetInSpeed',
|
||||||
|
}, {
|
||||||
|
label: '出网速度',
|
||||||
|
value: 'State.NetOutSpeed',
|
||||||
|
}, {
|
||||||
|
label: '入网流量',
|
||||||
|
value: 'State.NetInTransfer',
|
||||||
|
}, {
|
||||||
|
label: '出网流量',
|
||||||
|
value: 'State.NetOutTransfer',
|
||||||
|
}, {
|
||||||
|
label: '合计流量',
|
||||||
|
value: '$.TotalTransfer',
|
||||||
|
}, {
|
||||||
|
label: 'TCP连接',
|
||||||
|
value: 'State.TcpConnCount',
|
||||||
|
}, {
|
||||||
|
label: 'UDP连接',
|
||||||
|
value: 'State.UdpConnCount',
|
||||||
|
}, {
|
||||||
|
label: '总连接数',
|
||||||
|
value: '$.TotalConnCount',
|
||||||
|
}, {
|
||||||
|
label: '1分钟负载',
|
||||||
|
value: 'State.Load1',
|
||||||
|
}, {
|
||||||
|
label: 'CPU占用',
|
||||||
|
value: 'State.CPU',
|
||||||
|
}, {
|
||||||
|
label: '核心数量',
|
||||||
|
value: '$.CPU',
|
||||||
|
}, {
|
||||||
|
label: '内存占用',
|
||||||
|
value: 'State.MemUsed',
|
||||||
|
}, {
|
||||||
|
label: '内存大小',
|
||||||
|
value: 'Host.MemTotal',
|
||||||
|
}, {
|
||||||
|
label: '交换占用',
|
||||||
|
value: 'State.SwapUsed',
|
||||||
|
}, {
|
||||||
|
label: '交换大小',
|
||||||
|
value: 'Host.SwapTotal',
|
||||||
|
}, {
|
||||||
|
label: '硬盘占用',
|
||||||
|
value: 'State.DiskUsed',
|
||||||
|
}, {
|
||||||
|
label: '硬盘大小',
|
||||||
|
value: 'Host.DiskTotal',
|
||||||
|
}];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务器排序处理
|
||||||
|
*/
|
||||||
|
export function serverSortHandler(a, b, sortby, order) {
|
||||||
|
let aValue;
|
||||||
|
let bValue;
|
||||||
|
const hasDot = sortby.includes('.');
|
||||||
|
if (!hasDot) {
|
||||||
|
aValue = a[sortby];
|
||||||
|
bValue = b[sortby];
|
||||||
|
} else {
|
||||||
|
const [sortby1, sortby2] = sortby.split('.');
|
||||||
|
if (sortby1 !== '$') {
|
||||||
|
switch (sortby2) {
|
||||||
|
case 'BootTime':
|
||||||
|
{
|
||||||
|
const currentTime = Date.now();
|
||||||
|
aValue = currentTime - a.Host.BootTime * 1000;
|
||||||
|
bValue = currentTime - b.Host.BootTime * 1000;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
aValue = a[sortby1][sortby2];
|
||||||
|
bValue = b[sortby1][sortby2];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (sortby2) {
|
||||||
|
case 'TotalTransfer':
|
||||||
|
{
|
||||||
|
aValue = a.State.NetInTransfer + a.State.NetOutTransfer;
|
||||||
|
bValue = b.State.NetInTransfer + b.State.NetOutTransfer;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'TotalConnCount':
|
||||||
|
{
|
||||||
|
aValue = a.State.TcpConnCount + a.State.UdpConnCount;
|
||||||
|
bValue = b.State.TcpConnCount + b.State.UdpConnCount;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'CPU':
|
||||||
|
{
|
||||||
|
aValue = a.Host.CPU.length;
|
||||||
|
bValue = b.Host.CPU.length;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (order === 'desc') {
|
||||||
|
return bValue - aValue;
|
||||||
|
}
|
||||||
|
return aValue - bValue;
|
||||||
|
}
|
||||||
@ -30,6 +30,11 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="right-box">
|
<div class="right-box">
|
||||||
|
<server-sort-box
|
||||||
|
v-if="showSort"
|
||||||
|
v-model="sortData"
|
||||||
|
:options="sortOptions"
|
||||||
|
/>
|
||||||
<server-option-box
|
<server-option-box
|
||||||
v-if="onlineOptions.length"
|
v-if="onlineOptions.length"
|
||||||
v-model="filterFormData.online"
|
v-model="filterFormData.online"
|
||||||
@ -122,11 +127,17 @@ import validate from '@/utils/validate';
|
|||||||
|
|
||||||
import WorldMap from '@/components/world-map/world-map.vue';
|
import WorldMap from '@/components/world-map/world-map.vue';
|
||||||
import ServerOptionBox from './components/server-list/server-option-box.vue';
|
import ServerOptionBox from './components/server-list/server-option-box.vue';
|
||||||
|
import ServerSortBox from './components/server-list/server-sort-box.vue';
|
||||||
import ServerListWarp from './components/server-list/server-list-warp.vue';
|
import ServerListWarp from './components/server-list/server-list-warp.vue';
|
||||||
import ServerCardItem from './components/server-list/card/server-list-item.vue';
|
import ServerCardItem from './components/server-list/card/server-list-item.vue';
|
||||||
import ServerRowItem from './components/server-list/row/server-list-item.vue';
|
import ServerRowItem from './components/server-list/row/server-list-item.vue';
|
||||||
import ServerStatusMain from './components/server-list/server-status/main.vue';
|
import ServerStatusMain from './components/server-list/server-status/main.vue';
|
||||||
|
|
||||||
|
import {
|
||||||
|
serverSortOptions,
|
||||||
|
serverSortHandler,
|
||||||
|
} from './composable/server-sort';
|
||||||
|
|
||||||
const store = useStore();
|
const store = useStore();
|
||||||
const worldMapWidth = ref();
|
const worldMapWidth = ref();
|
||||||
const windowWidth = ref(window.innerWidth);
|
const windowWidth = ref(window.innerWidth);
|
||||||
@ -272,6 +283,16 @@ const listTypeOptions = computed(() => [{
|
|||||||
icon: 'ri-server-line',
|
icon: 'ri-server-line',
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序处理
|
||||||
|
*/
|
||||||
|
const showSort = computed(() => config.nazhua.hideSort !== true);
|
||||||
|
const sortData = ref({
|
||||||
|
prop: 'DisplayIndex',
|
||||||
|
order: 'desc',
|
||||||
|
});
|
||||||
|
const sortOptions = computed(() => serverSortOptions());
|
||||||
|
|
||||||
const filterServerList = computed(() => {
|
const filterServerList = computed(() => {
|
||||||
const fields = {};
|
const fields = {};
|
||||||
const locationMap = {};
|
const locationMap = {};
|
||||||
@ -332,6 +353,7 @@ const filterServerList = computed(() => {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
list.sort((a, b) => serverSortHandler(a, b, sortData.value.prop, sortData.value.order));
|
||||||
return {
|
return {
|
||||||
fields,
|
fields,
|
||||||
list,
|
list,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user