From 9dc63e2c21919b13b993fe9190f8e44ef1b6fa4f Mon Sep 17 00:00:00 2001
From: jxxghp
Date: Sat, 6 Jun 2026 08:45:45 +0800
Subject: [PATCH] feat(dashboard): integrate GridStack for enhanced layout
management
- Added GridStack for dynamic dashboard layout with drag-and-drop functionality.
- Introduced new properties for DashboardItem to support row configuration.
- Enhanced ContentToggleSettingsDialog with reset functionality.
- Updated localization files for new dashboard features.
- Refactored dashboard components to utilize GridStack for layout rendering.
- Improved responsiveness and styling for dashboard elements.
- Removed deprecated draggable component in favor of GridStack.
---
package.json | 1 +
src/api/types.ts | 2 +
.../dialog/ContentToggleSettingsDialog.vue | 16 +
src/components/misc/DashboardElement.vue | 6 -
src/locales/en-US.ts | 5 +-
src/locales/zh-CN.ts | 5 +-
src/locales/zh-TW.ts | 5 +-
src/pages/dashboard.vue | 636 ++++++++++++++++--
src/views/dashboard/AnalyticsCpu.vue | 11 +-
.../dashboard/AnalyticsMediaStatistic.vue | 11 +-
src/views/dashboard/AnalyticsMemory.vue | 11 +-
src/views/dashboard/AnalyticsNetwork.vue | 11 +-
src/views/dashboard/AnalyticsProcesses.vue | 3 -
src/views/dashboard/AnalyticsScheduler.vue | 11 +-
src/views/dashboard/AnalyticsSpeed.vue | 9 +-
src/views/dashboard/AnalyticsStorage.vue | 9 +-
.../dashboard/AnalyticsWeeklyOverview.vue | 11 +-
src/views/dashboard/MediaServerLatest.vue | 10 +-
src/views/dashboard/MediaServerLibrary.vue | 3 -
src/views/dashboard/MediaServerPlaying.vue | 3 -
yarn.lock | 5 +
21 files changed, 671 insertions(+), 113 deletions(-)
diff --git a/package.json b/package.json
index e8c82368..1d3ce9a7 100644
--- a/package.json
+++ b/package.json
@@ -49,6 +49,7 @@
"dayjs": "^1.11.13",
"express": "^4.21.2",
"express-http-proxy": "^2.1.1",
+ "gridstack": "^12.6.0",
"http-proxy-middleware": "^3.0.0",
"js-cookie": "^3.0.5",
"lodash-es": "^4.17.21",
diff --git a/src/api/types.ts b/src/api/types.ts
index 8886d358..45b8b31e 100644
--- a/src/api/types.ts
+++ b/src/api/types.ts
@@ -702,6 +702,8 @@ export interface DashboardItem {
attrs: { [key: string]: any }
// col列数
cols: { [key: string]: number }
+ // Grid行数
+ rows?: number
// 页面元素
elements: RenderProps[]
// 渲染方式
diff --git a/src/components/dialog/ContentToggleSettingsDialog.vue b/src/components/dialog/ContentToggleSettingsDialog.vue
index dd87e160..555b953c 100644
--- a/src/components/dialog/ContentToggleSettingsDialog.vue
+++ b/src/components/dialog/ContentToggleSettingsDialog.vue
@@ -16,6 +16,8 @@ const props = withDefaults(
items: UnknownRecord[]
labelGetter?: (item: UnknownRecord) => string
modelValue?: boolean
+ resetIcon?: string
+ resetText?: string
selectAllText?: string
selectNoneText?: string
showBulkActions?: boolean
@@ -28,6 +30,8 @@ const props = withDefaults(
elevated: false,
labelGetter: undefined,
modelValue: true,
+ resetIcon: 'mdi-restore',
+ resetText: '',
selectAllText: '',
selectNoneText: '',
showBulkActions: false,
@@ -38,6 +42,7 @@ const props = withDefaults(
const emit = defineEmits<{
(event: 'close'): void
+ (event: 'reset'): void
(event: 'save', payload: { elevated: boolean; enabled: Record }): void
(event: 'update:elevated', value: boolean): void
(event: 'update:modelValue', value: boolean): void
@@ -99,6 +104,11 @@ function setAllItems(value: boolean) {
})
}
+// 触发调用方提供的重置动作。
+function triggerResetAction() {
+ emit('reset')
+}
+
// 提交通用内容开关设置。
function submitSettings() {
emit('save', {
@@ -147,6 +157,12 @@ function submitSettings() {
+
+
+
+
+ {{ props.resetText }}
+
{{ props.selectAllText }}
diff --git a/src/components/misc/DashboardElement.vue b/src/components/misc/DashboardElement.vue
index b639232b..66fe1292 100644
--- a/src/components/misc/DashboardElement.vue
+++ b/src/components/misc/DashboardElement.vue
@@ -148,17 +148,11 @@ onUnmounted(() => {
-
- mdi-drag
-
-
- mdi-drag
-
{{ props.config?.attrs?.title ?? props.config?.name }}
diff --git a/src/locales/en-US.ts b/src/locales/en-US.ts
index 1f2110ba..d0f531b3 100644
--- a/src/locales/en-US.ts
+++ b/src/locales/en-US.ts
@@ -898,7 +898,10 @@ export default {
latest: 'Recently Added',
settings: 'Dashboard Settings',
chooseContent: 'Choose content to display',
- adaptiveHeight: 'Adaptive Component Height',
+ editLayout: 'Edit Layout',
+ exitEditMode: 'Done Editing',
+ resetLayout: 'Reset Layout',
+ dragHandle: 'Drag Component',
current: 'Current',
episodes: 'Episodes',
users: 'Users',
diff --git a/src/locales/zh-CN.ts b/src/locales/zh-CN.ts
index 506a6eb1..e1eaddd4 100644
--- a/src/locales/zh-CN.ts
+++ b/src/locales/zh-CN.ts
@@ -894,7 +894,10 @@ export default {
latest: '最近添加',
settings: '设置仪表板',
chooseContent: '选择您想在页面显示的内容',
- adaptiveHeight: '自适应组件高度',
+ editLayout: '编辑布局',
+ exitEditMode: '完成编辑',
+ resetLayout: '恢复默认布局',
+ dragHandle: '拖动组件',
current: '当前',
episodes: '剧集',
users: '用户',
diff --git a/src/locales/zh-TW.ts b/src/locales/zh-TW.ts
index 01e16799..531893db 100644
--- a/src/locales/zh-TW.ts
+++ b/src/locales/zh-TW.ts
@@ -894,7 +894,10 @@ export default {
latest: '最近添加',
settings: '設置儀表板',
chooseContent: '選擇您想在頁面顯示的內容',
- adaptiveHeight: '自適應組件高度',
+ editLayout: '編輯佈局',
+ exitEditMode: '完成編輯',
+ resetLayout: '恢復默認佈局',
+ dragHandle: '拖動組件',
current: '當前',
episodes: '劇集',
users: '用戶',
diff --git a/src/pages/dashboard.vue b/src/pages/dashboard.vue
index 8b2af7c7..4ec30f57 100644
--- a/src/pages/dashboard.vue
+++ b/src/pages/dashboard.vue
@@ -1,17 +1,21 @@
-
-
-
-
-
-
-
+
-
-
-
+
+
+
-
+
+
diff --git a/src/views/dashboard/AnalyticsCpu.vue b/src/views/dashboard/AnalyticsCpu.vue
index 634aa0bb..25a9431d 100644
--- a/src/views/dashboard/AnalyticsCpu.vue
+++ b/src/views/dashboard/AnalyticsCpu.vue
@@ -135,11 +135,8 @@ useKeepAliveRefresh(refresh)
-
+
-
- mdi-drag
-
CPU
@@ -150,3 +147,9 @@ useKeepAliveRefresh(refresh)
+
+
diff --git a/src/views/dashboard/AnalyticsMediaStatistic.vue b/src/views/dashboard/AnalyticsMediaStatistic.vue
index 9b1a109e..04ea3cdb 100644
--- a/src/views/dashboard/AnalyticsMediaStatistic.vue
+++ b/src/views/dashboard/AnalyticsMediaStatistic.vue
@@ -56,11 +56,8 @@ onActivated(() => {
-
+
-
- mdi-drag
-
{{ t('dashboard.mediaStatistic') }}
@@ -88,3 +85,9 @@ onActivated(() => {
+
+
diff --git a/src/views/dashboard/AnalyticsMemory.vue b/src/views/dashboard/AnalyticsMemory.vue
index e69c942e..961a024b 100644
--- a/src/views/dashboard/AnalyticsMemory.vue
+++ b/src/views/dashboard/AnalyticsMemory.vue
@@ -140,11 +140,8 @@ useKeepAliveRefresh(refresh)
-
+
-
- mdi-drag
-
{{ t('dashboard.memory') }}
@@ -155,3 +152,9 @@ useKeepAliveRefresh(refresh)
+
+
diff --git a/src/views/dashboard/AnalyticsNetwork.vue b/src/views/dashboard/AnalyticsNetwork.vue
index 638c339a..f12fc2d1 100644
--- a/src/views/dashboard/AnalyticsNetwork.vue
+++ b/src/views/dashboard/AnalyticsNetwork.vue
@@ -173,11 +173,8 @@ useKeepAliveRefresh(refresh)
-
+
-
- mdi-drag
-
{{ t('dashboard.network') }}
@@ -197,3 +194,9 @@ useKeepAliveRefresh(refresh)
+
+
diff --git a/src/views/dashboard/AnalyticsProcesses.vue b/src/views/dashboard/AnalyticsProcesses.vue
index 7308eca2..7f0df30f 100644
--- a/src/views/dashboard/AnalyticsProcesses.vue
+++ b/src/views/dashboard/AnalyticsProcesses.vue
@@ -43,9 +43,6 @@ useDataRefresh(
-
- mdi-drag
-
{{ t('dashboard.processes.title') }}
diff --git a/src/views/dashboard/AnalyticsScheduler.vue b/src/views/dashboard/AnalyticsScheduler.vue
index 18248286..5bb1027a 100644
--- a/src/views/dashboard/AnalyticsScheduler.vue
+++ b/src/views/dashboard/AnalyticsScheduler.vue
@@ -46,16 +46,13 @@ useDataRefresh(
-
+
-
- mdi-drag
-
{{ t('dashboard.scheduler') }}
-
+
@@ -94,6 +91,10 @@ useDataRefresh(
--v-card-list-gap: 1.5rem;
}
+.dashboard-work-card {
+ min-block-size: 352px;
+}
+
.card-list::-webkit-scrollbar {
display: none;
}
diff --git a/src/views/dashboard/AnalyticsSpeed.vue b/src/views/dashboard/AnalyticsSpeed.vue
index eb208708..f5361871 100644
--- a/src/views/dashboard/AnalyticsSpeed.vue
+++ b/src/views/dashboard/AnalyticsSpeed.vue
@@ -89,11 +89,8 @@ const { loading } = useDataRefresh(
-
+
-
- mdi-drag
-
{{ t('dashboard.realTimeSpeed') }}
@@ -131,4 +128,8 @@ const { loading } = useDataRefresh(
.card-list {
--v-card-list-gap: 1rem;
}
+
+.dashboard-work-card {
+ min-block-size: 352px;
+}
diff --git a/src/views/dashboard/AnalyticsStorage.vue b/src/views/dashboard/AnalyticsStorage.vue
index bbbc6b15..12b769ff 100644
--- a/src/views/dashboard/AnalyticsStorage.vue
+++ b/src/views/dashboard/AnalyticsStorage.vue
@@ -49,13 +49,10 @@ onActivated(() => {
-
+
-
- mdi-drag
-
{{ t('dashboard.storage') }}
@@ -90,4 +87,8 @@ onActivated(() => {
inset-block-end: 2rem;
inset-inline-end: 2rem;
}
+
+.dashboard-summary-card {
+ min-block-size: 160px;
+}
diff --git a/src/views/dashboard/AnalyticsWeeklyOverview.vue b/src/views/dashboard/AnalyticsWeeklyOverview.vue
index 65a2db19..d98639da 100644
--- a/src/views/dashboard/AnalyticsWeeklyOverview.vue
+++ b/src/views/dashboard/AnalyticsWeeklyOverview.vue
@@ -133,11 +133,8 @@ onActivated(() => {
-
+
-
- mdi-drag
-
{{ t('dashboard.weeklyOverview') }}
@@ -156,3 +153,9 @@ onActivated(() => {
+
+
diff --git a/src/views/dashboard/MediaServerLatest.vue b/src/views/dashboard/MediaServerLatest.vue
index 0b2ffab6..67912468 100644
--- a/src/views/dashboard/MediaServerLatest.vue
+++ b/src/views/dashboard/MediaServerLatest.vue
@@ -60,11 +60,8 @@ onActivated(() => {
-
+
-
- mdi-drag
-
{{ t('dashboard.latest') }} - {{ name }}
@@ -90,6 +87,11 @@ onActivated(() => {
diff --git a/src/views/dashboard/MediaServerLibrary.vue b/src/views/dashboard/MediaServerLibrary.vue
index 5deb3301..f399095c 100644
--- a/src/views/dashboard/MediaServerLibrary.vue
+++ b/src/views/dashboard/MediaServerLibrary.vue
@@ -65,9 +65,6 @@ onActivated(() => {
-
- mdi-drag
-
{{ t('dashboard.library') }}
diff --git a/src/views/dashboard/MediaServerPlaying.vue b/src/views/dashboard/MediaServerPlaying.vue
index 3dc77fe7..f3fffafa 100644
--- a/src/views/dashboard/MediaServerPlaying.vue
+++ b/src/views/dashboard/MediaServerPlaying.vue
@@ -65,9 +65,6 @@ onActivated(() => {
-
- mdi-drag
-
{{ t('dashboard.playing') }}
diff --git a/yarn.lock b/yarn.lock
index f08483b5..3b0434a9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4721,6 +4721,11 @@ graphemer@^1.4.0:
resolved "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz"
integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
+gridstack@^12.6.0:
+ version "12.6.0"
+ resolved "https://registry.yarnpkg.com/gridstack/-/gridstack-12.6.0.tgz#acfd8c036b202304712c0562078c86ed2ab6e83f"
+ integrity sha512-dUrqsormSybFn/2P4Dz8AgprftKD5e/IiV7UmC0XLQU+G+/WtkAeFiCSNLoAGhPDXoJ/O61Xtj3gljY/Ds83yQ==
+
has-bigints@^1.0.2:
version "1.1.0"
resolved "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz"