mirror of
https://github.com/gl-inet/glkvm-cloud.git
synced 2026-10-04 12:41:42 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 30a132cb4e | |||
| 52117f044b | |||
| 884f537ae8 | |||
| 807bab3e2a | |||
| a1823096cc | |||
| b1aa8e3cae | |||
| 3d75a87140 | |||
| d237847f0d | |||
| 59021535b7 |
Vendored
+1
@@ -3,6 +3,7 @@
|
||||
"ddns",
|
||||
"glkvm",
|
||||
"repassword",
|
||||
"sortablejs",
|
||||
"webrtc"
|
||||
]
|
||||
}
|
||||
@@ -118,13 +118,67 @@ func (h *DeviceHandler) ListDevices(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// Parse sort parameters: sortBy and order
|
||||
sortBy := strings.TrimSpace(c.Query("sortBy")) // id, ip, mac, connectedTime, description, ddns
|
||||
sortOrder := strings.TrimSpace(c.Query("order")) // asc, desc (default: asc)
|
||||
|
||||
ascending := true
|
||||
if strings.EqualFold(sortOrder, "desc") {
|
||||
ascending = false
|
||||
}
|
||||
|
||||
sort.SliceStable(items, func(i, j int) bool {
|
||||
// Online devices always come first regardless of sort field/order
|
||||
oi := items[i].Status == device.StatusOnline
|
||||
oj := items[j].Status == device.StatusOnline
|
||||
if oi != oj {
|
||||
return oi
|
||||
}
|
||||
return items[i].Ddns < items[j].Ddns
|
||||
|
||||
// Secondary sort by the requested field
|
||||
// cmp: -1 means i<j, 0 means equal, 1 means i>j
|
||||
var cmp int
|
||||
switch sortBy {
|
||||
case "id":
|
||||
switch {
|
||||
case items[i].ID < items[j].ID:
|
||||
cmp = -1
|
||||
case items[i].ID > items[j].ID:
|
||||
cmp = 1
|
||||
}
|
||||
case "ip":
|
||||
cmp = strings.Compare(items[i].IP, items[j].IP)
|
||||
case "mac":
|
||||
cmp = strings.Compare(items[i].Mac, items[j].Mac)
|
||||
case "connectedTime":
|
||||
var ti, tj int64
|
||||
if items[i].LastSeenAt != nil {
|
||||
ti = *items[i].LastSeenAt
|
||||
}
|
||||
if items[j].LastSeenAt != nil {
|
||||
tj = *items[j].LastSeenAt
|
||||
}
|
||||
switch {
|
||||
case ti < tj:
|
||||
cmp = -1
|
||||
case ti > tj:
|
||||
cmp = 1
|
||||
}
|
||||
case "description":
|
||||
cmp = strings.Compare(items[i].Description, items[j].Description)
|
||||
case "ddns":
|
||||
cmp = strings.Compare(items[i].Ddns, items[j].Ddns)
|
||||
default:
|
||||
cmp = strings.Compare(items[i].Ddns, items[j].Ddns)
|
||||
}
|
||||
|
||||
if cmp == 0 {
|
||||
return false // equal, preserve original order
|
||||
}
|
||||
if ascending {
|
||||
return cmp < 0
|
||||
}
|
||||
return cmp > 0
|
||||
})
|
||||
|
||||
out := make([]dto.Device, 0, len(items))
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package server
|
||||
|
||||
const RttysVersion = "5.2.0"
|
||||
const KVMCloudVersion = "v2.2.0"
|
||||
const KVMCloudVersion = "v2.3.0"
|
||||
|
||||
var (
|
||||
GitCommit = ""
|
||||
BuildTime = ""
|
||||
GitCommit = ""
|
||||
BuildTime = ""
|
||||
)
|
||||
|
||||
+2
-1
@@ -25,12 +25,13 @@
|
||||
"ant-design-vue": "^4.2.6",
|
||||
"axios": "^1.9.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"gl-web-main": "^1.0.0",
|
||||
"gl-web-main": "1.0.2",
|
||||
"js-cookie": "^3.0.5",
|
||||
"jsencrypt": "^3.3.2",
|
||||
"pinia": "^3.0.2",
|
||||
"sass": "^1.89.0",
|
||||
"simple-keyboard": "3.8.69",
|
||||
"sortablejs": "^1.15.7",
|
||||
"vite-plugin-remove-console": "^2.2.0",
|
||||
"vue": "^3.5.13",
|
||||
"vue-clipboard3": "2.0.0",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @Author: shufei.han
|
||||
* @Date: 2025-06-11 11:48:02
|
||||
* @LastEditors: LPY
|
||||
* @LastEditTime: 2026-02-04 09:32:31
|
||||
* @LastEditTime: 2026-03-09 12:08:45
|
||||
* @FilePath: \glkvm-cloud\ui\src\api\device.ts
|
||||
* @Description: 设备相关API
|
||||
*/
|
||||
@@ -10,7 +10,7 @@ import { ExecuteCommandParams, type DeviceInfo } from '@/models/device'
|
||||
import request, { httpService } from './request'
|
||||
|
||||
/** 获取设备列表 */
|
||||
export const getDeviceListApi = (params?: { groupId: number }) => {
|
||||
export const getDeviceListApi = (params?: { groupId: number, sortBy?: string, order?: 'asc' | 'desc' }) => {
|
||||
return request<{ items: DeviceInfo[]}>({
|
||||
url: '/api/devices',
|
||||
params,
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -2,7 +2,7 @@
|
||||
* @Author: LPY
|
||||
* @Date: 2025-05-30 10:18:18
|
||||
* @LastEditors: LPY
|
||||
* @LastEditTime: 2026-01-05 14:22:12
|
||||
* @LastEditTime: 2026-03-10 11:52:27
|
||||
* @FilePath: \glkvm-cloud\ui\src\hooks\useLocalStorage.ts
|
||||
* @Description: 存储hook
|
||||
*/
|
||||
@@ -20,6 +20,8 @@ export enum LocalStorageKeys {
|
||||
SIDEBAR_MANUAL_CONTROL_KEY = 'sidebar-manual-control',
|
||||
/** 版本号 */
|
||||
VERSION = 'version',
|
||||
/** 设备列表列表顺序 */
|
||||
DEVICE_LIST_COLUMNS_KEY = 'device-list-columns',
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -27,7 +29,7 @@ export enum LocalStorageKeys {
|
||||
* @param key 需要使用哪种数据
|
||||
* @param {T} initValue 如果没有存储,则返回的初始值
|
||||
*/
|
||||
export function useLocalStorage <T extends {toString: () => string}> (
|
||||
export function useLocalStorage <T> (
|
||||
key: LocalStorageKeys,
|
||||
initValue: T = null,
|
||||
transform: (value: string) => T = (value) => value as unknown as T,
|
||||
@@ -36,9 +38,15 @@ export function useLocalStorage <T extends {toString: () => string}> (
|
||||
const storageValue = ref<T>(initValue)
|
||||
/** 获取本地存储的值 */
|
||||
const getValue = () => {
|
||||
const storageData = localStorage.getItem(key)
|
||||
// 特殊兼容以前的版本直接存储字符串的情况
|
||||
let storageData: T = null
|
||||
try {
|
||||
storageData = JSON.parse(localStorage.getItem(key))
|
||||
} catch {
|
||||
storageData = localStorage.getItem(key) as unknown as T
|
||||
}
|
||||
if (storageData !== null) {
|
||||
return transform(storageData)
|
||||
return transform(storageData as unknown as string)
|
||||
}
|
||||
else {
|
||||
return initValue
|
||||
@@ -46,7 +54,7 @@ export function useLocalStorage <T extends {toString: () => string}> (
|
||||
}
|
||||
/** 设置本地存储的值 */
|
||||
const setValue = (value: T) => {
|
||||
localStorage.setItem(key, value?.toString())
|
||||
localStorage.setItem(key, JSON.stringify(value))
|
||||
}
|
||||
/** 清除本地存储的值 */
|
||||
const removeValue = () => {
|
||||
|
||||
@@ -124,7 +124,9 @@
|
||||
"ipNotCorrect": "IP address is incorrect",
|
||||
"portNotCorrect": "Port is incorrect",
|
||||
"remoteWeb": "Remote Web",
|
||||
"linuxTips": "Supports OpenWrt, Raspberry PI, Ubuntu, CentOS, etc."
|
||||
"linuxTips": "Supports OpenWrt, Raspberry PI, Ubuntu, CentOS, etc.",
|
||||
"customColumns": "Custom Columns",
|
||||
"dragColumnTips": "You can customize your device list. Drag the button on the right side of the following items to adjust the display order, or use the checkboxes to control the display or hide of certain columns."
|
||||
},
|
||||
"user": {
|
||||
"user": "User",
|
||||
|
||||
@@ -124,7 +124,9 @@
|
||||
"ipNotCorrect": "IP 地址错误",
|
||||
"portNotCorrect": "端口号错误",
|
||||
"remoteWeb": "远程 Web",
|
||||
"linuxTips": "支持 OpenWrt、树莓派、Ubuntu、CentOS 等"
|
||||
"linuxTips": "支持 OpenWrt、树莓派、Ubuntu、CentOS 等",
|
||||
"customColumns": "自定义列",
|
||||
"dragColumnTips": "您可以自定义您的设备列表,拖动下列项右侧的按钮来调整显示顺序,也可以通过复选框来控制显示或隐藏某些列。"
|
||||
},
|
||||
"user": {
|
||||
"user": "用户",
|
||||
|
||||
@@ -26,6 +26,8 @@ export interface DeviceQuery {
|
||||
searchText: string
|
||||
deviceGroupId: number
|
||||
onlyShowUnassigned: boolean
|
||||
sortBy?: string
|
||||
order?: 'asc' | 'desc'
|
||||
}
|
||||
|
||||
/** 执行命令参数 */
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @Author: LPY
|
||||
* @Date: 2025-05-30 10:54:44
|
||||
* @LastEditors: LPY
|
||||
* @LastEditTime: 2026-02-03 10:22:56
|
||||
* @LastEditTime: 2026-03-09 14:30:29
|
||||
* @FilePath: \glkvm-cloud\ui\src\projectInitialize\loadAdvComponent.ts
|
||||
* @Description: 加载Ant 组件
|
||||
*/
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
Select,
|
||||
Tabs,
|
||||
Radio,
|
||||
Popover,
|
||||
} from 'ant-design-vue'
|
||||
|
||||
export default function (app: any) {
|
||||
@@ -46,4 +47,5 @@ export default function (app: any) {
|
||||
app.use(Select)
|
||||
app.use(Tabs)
|
||||
app.use(Radio)
|
||||
app.use(Popover)
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
* @Author: LPY
|
||||
* @Date: 2025-05-30 09:37:06
|
||||
* @LastEditors: LPY
|
||||
* @LastEditTime: 2026-01-05 14:35:02
|
||||
* @LastEditTime: 2026-03-10 11:44:32
|
||||
* @FilePath: \glkvm-cloud\ui\src\stores\modules\app.ts
|
||||
* @Description: app相关状态存储
|
||||
*/
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @Author: shufei.han
|
||||
* @Date: 2025-06-10 16:46:00
|
||||
* @LastEditors: LPY
|
||||
* @LastEditTime: 2026-02-28 09:21:41
|
||||
* @LastEditTime: 2026-03-09 12:17:03
|
||||
* @FilePath: \glkvm-cloud\ui\src\stores\modules\device.ts
|
||||
* @Description: 设备有关的状态管理
|
||||
*/
|
||||
@@ -36,6 +36,10 @@ export const useDeviceStore = defineStore('device', () => {
|
||||
onlyShowUnassigned: false,
|
||||
/** 这个字段存储是否有设备,因为UI上没有设备和没有筛选出来的设备是对应不同的展示画面的 */
|
||||
hasDevice: false,
|
||||
/** 排序字段 */
|
||||
sortBy: undefined,
|
||||
/** 排序方式 */
|
||||
order: undefined,
|
||||
})
|
||||
|
||||
const pageLink = ref(new PageLink({ size: DEVICE_VIEW_PAGE_SIZE }))
|
||||
@@ -49,6 +53,8 @@ export const useDeviceStore = defineStore('device', () => {
|
||||
searchText: state.searchText?.replaceAll(':','').toLowerCase(),
|
||||
deviceGroupId: state.deviceGroupId,
|
||||
onlyShowUnassigned: state.onlyShowUnassigned,
|
||||
sortBy: state.sortBy,
|
||||
order: state.order,
|
||||
}
|
||||
return query
|
||||
})
|
||||
@@ -66,7 +72,11 @@ export const useDeviceStore = defineStore('device', () => {
|
||||
try {
|
||||
console.log('getDeviceList', computedDeviceQuery.value)
|
||||
!isPolling && (state.getDeviceLoading = true)
|
||||
const res = await getDeviceListApi()
|
||||
const res = await getDeviceListApi({
|
||||
groupId: computedDeviceQuery.value.deviceGroupId,
|
||||
sortBy: computedDeviceQuery.value.sortBy,
|
||||
order: computedDeviceQuery.value.order,
|
||||
})
|
||||
console.log(res)
|
||||
|
||||
if (isGetAll) {
|
||||
|
||||
@@ -60,6 +60,16 @@ a {
|
||||
padding: 10px 12px !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 增加类名,可以让列的表头左对齐(例如筛选按钮紧挨着列标题)
|
||||
.custom-table-header-cell-to-left {
|
||||
.ant-table-column-sorters {
|
||||
justify-content: flex-start !important;
|
||||
}
|
||||
.ant-table-column-title {
|
||||
flex: unset !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.text-nowrap {
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
<!--
|
||||
* @Author: LPY
|
||||
* @Date: 2026-03-09 14:27:32
|
||||
* @LastEditors: LPY
|
||||
* @LastEditTime: 2026-03-10 11:48:15
|
||||
* @FilePath: \glkvm-cloud\ui\src\views\device\components\components\customColumns.vue
|
||||
* @Description: 自定义table列组件
|
||||
-->
|
||||
<template>
|
||||
<APopover trigger="click" placement="bottom" :arrow="false" overlayClassName="custom-columns-popper" @openChange="handleOpenChange">
|
||||
<template #content>
|
||||
<div class="top-tips">
|
||||
<BaseText type="head-m">{{ $t('device.customColumns') }}</BaseText>
|
||||
<GlSvg name="gl-icon-help" tooltip :size="20">{{ $t('device.dragColumnTips') }}</GlSvg>
|
||||
</div>
|
||||
<div class="dividing-line"></div>
|
||||
<div ref="customColumnsBoxRef" class="custom-columns-box">
|
||||
<div v-for="item in clonedColumns" :key="item.key" class="custom-columns-item" :style="{'cursor': dragging ? 'grabbing' : undefined}">
|
||||
<div class="custom-columns-item-left">
|
||||
<ACheckbox :checked="item.show" @change="handleColumnVisibilityChange(item)"></ACheckbox>
|
||||
<BaseText v-ellipsis class="title">{{ item.title }}</BaseText>
|
||||
</div>
|
||||
<div class="custom-columns-item-right">
|
||||
<GlSvg name="gl-icon-grip-dots-vertical-regular"></GlSvg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<GlSvg name="gl-icon-gear-regular" :size="20" class="custom-columns-icon"></GlSvg>
|
||||
</APopover>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { nextTick, ref } from 'vue'
|
||||
import { GlSvg } from 'gl-web-main/components'
|
||||
import Sortable from 'sortablejs'
|
||||
import { LocalStorageKeys, useLocalStorage } from '@/hooks/useLocalStorage'
|
||||
import { TableColumnType } from 'ant-design-vue'
|
||||
|
||||
// Avoid deep type instantiation by defining only the properties you use
|
||||
interface CustomTableColumnType extends TableColumnType {
|
||||
show?: boolean
|
||||
title?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
storageName: LocalStorageKeys
|
||||
columns: CustomTableColumnType[]
|
||||
completeColumns: CustomTableColumnType[]
|
||||
}>(), {
|
||||
})
|
||||
|
||||
const emits = defineEmits<{
|
||||
(e: 'change', value: TableColumnType[]): void
|
||||
}>()
|
||||
|
||||
const customColumnsBoxRef = ref<HTMLDivElement>()
|
||||
|
||||
const dragging = ref(false)
|
||||
|
||||
const clonedColumns = ref<CustomTableColumnType[]>([])
|
||||
|
||||
const initDragFn = () => {
|
||||
// 注册拖拽元素
|
||||
Sortable.create(customColumnsBoxRef.value, {
|
||||
group: 'columns',
|
||||
animation: 150,
|
||||
draggable: '.custom-columns-item',
|
||||
dragClass: 'custom-columns-item-dragging',
|
||||
forceFallback: true,
|
||||
handle: '.custom-columns-item-right',
|
||||
onStart () {
|
||||
dragging.value = true
|
||||
},
|
||||
onEnd: (evt) => handleSortEnd(evt),
|
||||
})
|
||||
}
|
||||
|
||||
const handleSortEnd = ({
|
||||
oldIndex,
|
||||
newIndex,
|
||||
from,
|
||||
to,
|
||||
}) => {
|
||||
dragging.value = false
|
||||
console.log(oldIndex, newIndex, from, to)
|
||||
const movedColumn = clonedColumns.value[oldIndex]
|
||||
clonedColumns.value.splice(oldIndex, 1)
|
||||
clonedColumns.value.splice(newIndex, 0, movedColumn)
|
||||
emits('change', clonedColumns.value)
|
||||
useLocalStorage(props.storageName).setValue(clonedColumns.value)
|
||||
}
|
||||
|
||||
const handleColumnVisibilityChange = (column: CustomTableColumnType) => {
|
||||
column.show = !column.show
|
||||
// 找到对应的列并更新show属性
|
||||
emits('change', clonedColumns.value)
|
||||
useLocalStorage(props.storageName).setValue(clonedColumns.value)
|
||||
}
|
||||
|
||||
const handleOpenChange = (open: boolean) => {
|
||||
if (open) {
|
||||
// 使用 nextTick 确保 DOM 已渲染
|
||||
nextTick(() => {
|
||||
if (customColumnsBoxRef.value) {
|
||||
initDragFn()
|
||||
|
||||
// 初始化数据,若有存储,则以存储为准,否则使用 completeColumns 的默认值
|
||||
const storedColumns = useLocalStorage(props.storageName).getValue() as CustomTableColumnType[] | null
|
||||
if (storedColumns) {
|
||||
clonedColumns.value = storedColumns
|
||||
} else {
|
||||
clonedColumns.value = props.completeColumns.map(col => ({
|
||||
...col,
|
||||
show: props.columns.find(c => c.key === col.key)?.show ?? true,
|
||||
}))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.top-tips {
|
||||
width: 200px;
|
||||
padding: 0 8px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.dividing-line {
|
||||
height: 1px;
|
||||
background-color: var(--gl-color-line-divider1);
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.custom-columns-box {
|
||||
width: 200px;
|
||||
.custom-columns-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
border-radius: 4px;
|
||||
height: 36px;
|
||||
padding: 0 8px;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--gl-color-bg-item-hover);
|
||||
}
|
||||
.custom-columns-item-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.title {
|
||||
max-width: 134px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.custom-columns-item-right {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
cursor: grabbing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.custom-columns-icon {
|
||||
display: inline-block;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
margin-right: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.custom-columns-item-dragging {
|
||||
box-shadow: 0px 3px 14px 0px rgba(0,0,0,0.25);
|
||||
opacity: 1 !important;
|
||||
background-color: var(--gl-color-bg-surface1);
|
||||
}
|
||||
</style>
|
||||
@@ -2,7 +2,7 @@
|
||||
* @Author: shufei.han
|
||||
* @Date: 2025-06-11 12:04:48
|
||||
* @LastEditors: LPY
|
||||
* @LastEditTime: 2026-02-06 10:42:16
|
||||
* @LastEditTime: 2026-03-10 11:47:57
|
||||
* @FilePath: \glkvm-cloud\ui\src\views\device\components\deviceListView.vue
|
||||
* @Description:
|
||||
-->
|
||||
@@ -26,7 +26,13 @@
|
||||
</ASelect>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="flex">
|
||||
<CustomColumns
|
||||
:columns="deviceColumns"
|
||||
:completeColumns="deviceCompleteColumns"
|
||||
:storageName="LocalStorageKeys.DEVICE_LIST_COLUMNS_KEY"
|
||||
@change="handleDeviceColumnsChange"
|
||||
/>
|
||||
<BaseButton size="middle" style="margin-right: 12px;" @click="refresh">{{ $t('common.refresh') }}</BaseButton>
|
||||
<BaseButton size="middle" style="margin-right: 12px;" @click="executeCommand">{{ $t('device.executeCommand') }}</BaseButton>
|
||||
<BaseButton
|
||||
@@ -46,6 +52,7 @@
|
||||
:columns="deviceColumns"
|
||||
rowKey="id"
|
||||
:rowSelection="{ selectedRowKeys: state.selectedRowKeys, onChange: onSelectChange }"
|
||||
@change="tableChange"
|
||||
>
|
||||
<template #mac="{ record }">
|
||||
{{ macAddressFormatter(record.mac) }}
|
||||
@@ -165,14 +172,14 @@ import BasePagination from '@/components/base/basePagination.vue'
|
||||
import BaseTable from '@/components/base/baseTable.vue'
|
||||
import { t } from '@/hooks/useLanguage'
|
||||
import { useDeviceStore } from '@/stores/modules/device'
|
||||
import { message, type TableColumnType } from 'ant-design-vue'
|
||||
import { message, TableProps, type TableColumnType } from 'ant-design-vue'
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import ExecuteCommandDialog from './executeCommandDialog.vue'
|
||||
import { DeviceInfo, DeviceStatusEnum, ExecuteCommandFormData } from '@/models/device'
|
||||
import CommandResponseDialog from './commandResponseDialog.vue'
|
||||
import { BaseDropdownSelect, BaseTag, GlSvg } from 'gl-web-main/components'
|
||||
import EditDescriptionDialog from './editDescriptionDialog.vue'
|
||||
import { baseCustomModal, macAddressFormatter, SelectOptions } from 'gl-web-main'
|
||||
import { baseCustomModal, deepClone, macAddressFormatter, SelectOptions } from 'gl-web-main'
|
||||
import { reqDeleteDevice, reqDeviceGroupListOptions } from '@/api/device'
|
||||
import AddDeviceDialog from './addDeviceDialog.vue'
|
||||
import MoveToDeviceGroupDialog from './moveToDeviceGroupDialog.vue'
|
||||
@@ -180,21 +187,33 @@ import { PermissionEnum } from '@/models/permission'
|
||||
import { hasPermission } from '@/utils/permission'
|
||||
import AccessDeviceWebDialog from './accessDeviceWebDialog.vue'
|
||||
import dayjs from 'dayjs'
|
||||
import CustomColumns from './components/customColumns.vue'
|
||||
import { LocalStorageKeys, useLocalStorage } from '@/hooks/useLocalStorage'
|
||||
|
||||
const deviceStore = useDeviceStore()
|
||||
|
||||
const deviceColumns = computed<TableColumnType[]>(() => {
|
||||
return [
|
||||
{title: t('device.deviceID'), dataIndex: 'ddns', ellipsis: true},
|
||||
{title: t('device.IPAddress'), dataIndex: 'ip', ellipsis: true},
|
||||
{title: t('device.mac'), dataIndex: 'mac', ellipsis: true},
|
||||
{title: t('device.status'), dataIndex: 'status', ellipsis: true},
|
||||
{title: t('device.connectedTime'), dataIndex: 'connectedTime', ellipsis: true},
|
||||
{title: t('user.associatedDeviceGroup'), dataIndex: 'deviceGroupName', ellipsis: true, width: 190},
|
||||
{title: t('device.description'), dataIndex: 'description'},
|
||||
{title: t('common.action'), dataIndex: 'action', width: 270},
|
||||
]
|
||||
})
|
||||
const deviceColumns = ref<TableColumnType[]>([
|
||||
{title: t('device.deviceID'), dataIndex: 'ddns', key: 'ddns', ellipsis: true,
|
||||
sorter: true, customHeaderCell: () => {return {class: 'custom-table-header-cell-to-left'}},
|
||||
},
|
||||
{title: t('device.IPAddress'), dataIndex: 'ip', key: 'ip', ellipsis: true,
|
||||
sorter: true, customHeaderCell: () => {return {class: 'custom-table-header-cell-to-left'}},
|
||||
},
|
||||
{title: t('device.mac'), dataIndex: 'mac', key: 'mac', ellipsis: true,
|
||||
sorter: true, customHeaderCell: () => {return {class: 'custom-table-header-cell-to-left'}},
|
||||
},
|
||||
{title: t('device.status'), dataIndex: 'status', key: 'status', ellipsis: true},
|
||||
{title: t('device.connectedTime'), dataIndex: 'connectedTime', key: 'connectedTime', ellipsis: true,
|
||||
sorter: true, customHeaderCell: () => {return {class: 'custom-table-header-cell-to-left'}},
|
||||
},
|
||||
{title: t('user.associatedDeviceGroup'), dataIndex: 'deviceGroupName', key: 'deviceGroupName', ellipsis: true, width: 190},
|
||||
{title: t('device.description'), dataIndex: 'description', key: 'description',
|
||||
sorter: true, customHeaderCell: () => {return {class: 'custom-table-header-cell-to-left'}},
|
||||
},
|
||||
{title: t('common.action'), dataIndex: 'action', key: 'action', width: 270},
|
||||
])
|
||||
|
||||
const deviceCompleteColumns = deepClone(deviceColumns.value)
|
||||
|
||||
const page = computed({
|
||||
get: () => deviceStore.pageLink.page,
|
||||
@@ -228,6 +247,14 @@ const onSelectChange = (selectedRowKeys: Key[], selectedRows: DeviceInfo[]) => {
|
||||
state.selectedRows = selectedRows
|
||||
}
|
||||
|
||||
const tableChange: TableProps['onChange'] = (pagination, filters, sorter: any) => {
|
||||
console.log('params', pagination, filters, sorter)
|
||||
if (sorter) {
|
||||
deviceStore.state.sortBy = sorter.field as string
|
||||
deviceStore.state.order = sorter.order === 'ascend' ? 'asc' : 'desc'
|
||||
}
|
||||
}
|
||||
|
||||
/** 计算时间 */
|
||||
// const calculateWithDuration = (connected: number, isMilliseconds: boolean = false) => {
|
||||
// const time = isMilliseconds ? connected / 1000 : connected
|
||||
@@ -389,7 +416,35 @@ const getDeviceGroupListOptions = async () => {
|
||||
state.groupList = res.data.items
|
||||
}
|
||||
|
||||
getDeviceGroupListOptions()
|
||||
const handleDeviceColumnsChange = (columns: TableColumnType[]) => {
|
||||
deviceColumns.value = columns.filter(col => (col as any).show)
|
||||
// 兼容JSON.parse后丢失的函数等属性,重新赋值customHeaderCell属性
|
||||
deviceColumns.value.forEach((col: any) => {
|
||||
const completeCol = deviceCompleteColumns.find(c => c.key === col.key)
|
||||
if (completeCol) {
|
||||
col.customHeaderCell = completeCol.customHeaderCell
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const init = () => {
|
||||
getDeviceGroupListOptions()
|
||||
|
||||
const storedColumns = useLocalStorage(LocalStorageKeys.DEVICE_LIST_COLUMNS_KEY).getValue()
|
||||
if (storedColumns) {
|
||||
const parsedColumns = storedColumns as Array<TableColumnType & { show: boolean }>
|
||||
// 兼容JSON.parse后丢失的函数等属性,重新赋值customHeaderCell属性
|
||||
parsedColumns.forEach((col: any) => {
|
||||
const completeCol = deviceCompleteColumns.find(c => c.key === col.key)
|
||||
if (completeCol) {
|
||||
col.customHeaderCell = completeCol.customHeaderCell
|
||||
}
|
||||
})
|
||||
deviceColumns.value = parsedColumns.filter(col => (col as any).show)
|
||||
}
|
||||
}
|
||||
|
||||
init()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"strictNullChecks": false,
|
||||
"composite": true,
|
||||
"noEmit": true,
|
||||
"moduleResolution": "bundler",
|
||||
"lib": ["ES2015", "ES2017", "ES2018", "DOM", "ES2021"],
|
||||
"verbatimModuleSyntax": false,
|
||||
"baseUrl": ".",
|
||||
|
||||
+655
-627
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user