From bf9400d4198094194439214b5e90036b2f040052 Mon Sep 17 00:00:00 2001
From: wangrong <wangrong@shsening.com>
Date: 星期四, 24 九月 2026 16:03:10 +0800
Subject: [PATCH] bug修复,用真实数据

---
 src/modules/base/pages/main/components/amenu.vue       |   12 
 src/modules/base/static/css/index.scss                 |    4 
 src/modules/push/views/weight.vue                      |   44 +
 src/modules/base/pages/main/components/topbar.vue      |   48 +
 src/modules/monitor/views/device.vue                   |   22 
 src/modules/report/views/joblog.vue                    |   13 
 src/plugins/view/components/group.vue                  |    6 
 src/modules/report/components/screen-map-panel.vue     |   12 
 src/modules/report/data/screen-merge.ts                |  371 ++++----------
 src/modules/push/views/cameragps.vue                   |    9 
 src/modules/report/views/screen/map.vue                |   77 ++
 src/modules/push/views/deviceonoff.vue                 |    9 
 src/modules/report/views/screen/participation.vue      |   16 
 src/modules/push/views/devicealarm.vue                 |    3 
 index.html                                             |    3 
 src/modules/push/views/auditlog.vue                    |    3 
 src/modules/report/views/dailyvehicle.vue              |   13 
 server.cjs                                             |   87 +++
 src/modules/ezuikit/components/ezuikit-player.vue      |    5 
 src/modules/report/views/screen/operation.vue          |   11 
 src/modules/report/hooks/use-screen.ts                 |    3 
 src/modules/base/pages/main/components/process.vue     |   14 
 src/modules/operation/views/wastesale.vue              |   17 
 src/modules/report/views/screen/overview.vue           |   10 
 src/modules/base/pages/error/components/error-page.vue |   18 
 src/modules/report/views/screen/carbon.vue             |   10 
 src/modules/report/components/panel-card.vue           |   21 
 src/modules/report/views/dailysite.vue                 |   13 
 src/modules/base/pages/login/index.vue                 |   32 +
 src/modules/push/views/siteweight.vue                  |   15 
 src/modules/push/views/vehiclegps.vue                  |    9 
 src/modules/base/config.ts                             |    1 
 src/modules/base/pages/main/index.vue                  |    6 
 src/modules/monitor/views/cameras.vue                  |   20 
 src/modules/maps/components/map-board.vue              |   25 
 src/modules/base/static/css/mobile.scss                |  253 +++++++++
 /dev/null                                              |  210 --------
 src/modules/operation/views/traderecord.vue            |    3 
 src/modules/report/views/dailydepartment.vue           |   13 
 src/modules/push/views/compressionweight.vue           |    3 
 src/modules/base/pages/main/components/views.vue       |   10 
 src/modules/report/components/screen-shell.vue         |   42 +
 42 files changed, 991 insertions(+), 525 deletions(-)

diff --git a/index.html b/index.html
index 1148b64..43dfc09 100644
--- a/index.html
+++ b/index.html
@@ -7,8 +7,9 @@
 		<meta name="renderer" content="webkit" />
 		<meta
 			name="viewport"
-			content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=0"
+			content="width=device-width, initial-scale=1, viewport-fit=cover"
 		/>
+		<meta name="format-detection" content="telephone=no" />
 		<title></title>
 		<link rel="icon" href="./favicon.ico" />
 		<style>
diff --git a/server.cjs b/server.cjs
new file mode 100644
index 0000000..7863c86
--- /dev/null
+++ b/server.cjs
@@ -0,0 +1,87 @@
+/**
+ * 鍓嶇闈欐�佹湇鍔″櫒锛圥M2 鎵樼锛屽澶� :9000锛�
+ * - 鎵樼 dist 闈欐�佽祫婧�
+ * - /api/*    杞彂鍚庣锛堝幓鎺� /api 鍓嶇紑锛�
+ * - /upload/* 杞彂鍚庣
+ * - /socket.io 杞彂鍚庣锛堟敮鎸� WebSocket锛�
+ */
+const path = require('path');
+const express = require('express');
+const { createProxyMiddleware } = require('http-proxy-middleware');
+
+const PORT = Number(process.env.PORT || 9000);
+const BACKEND = process.env.BACKEND || 'http://127.0.0.1:8001';
+const DIST = path.join(__dirname, 'dist');
+
+const onErr = (name) => (err, req, res) => {
+  console.error(`[proxy:${name}]`, err && err.message);
+  if (res && res.writeHead) {
+    res.writeHead(502, { 'Content-Type': 'application/json' });
+    res.end(JSON.stringify({ code: 502, msg: `backend unreachable: ${name}` }));
+  }
+};
+
+const app = express();
+
+// 1) 涓氬姟鎺ュ彛锛�/api/admin/xxx -> /admin/xxx
+app.use(
+  '/api',
+  createProxyMiddleware({
+    target: BACKEND,
+    changeOrigin: true,
+    pathRewrite: { '^/api': '' },
+    ws: false,
+    onError: onErr('api'),
+  })
+);
+
+// 2) 涓婁紶鏂囦欢
+app.use(
+  '/upload',
+  createProxyMiddleware({
+    target: BACKEND,
+    changeOrigin: true,
+    ws: false,
+    onError: onErr('upload'),
+  })
+);
+
+// 3) socket.io锛堝惈 WebSocket锛�
+const socketProxy = createProxyMiddleware({
+  target: BACKEND,
+  changeOrigin: true,
+  ws: true,
+  onError: onErr('socket.io'),
+});
+app.use('/socket.io', socketProxy);
+
+// 4) 鍓嶇闈欐�佽祫婧�
+app.use(
+  express.static(DIST, {
+    maxAge: '7d',
+    index: 'index.html',
+    setHeaders(res, filePath) {
+      if (filePath.endsWith('index.html')) res.setHeader('Cache-Control', 'no-cache');
+    },
+  })
+);
+
+// 5) history 璺敱鍏滃簳锛坔ash 妯″紡涓嶄細璧板埌杩欙級
+// app.get('*', (req, res, next) => {
+//   if (req.path.startsWith('/api') || req.path.startsWith('/upload')) return next();
+//   res.sendFile(path.join(DIST, 'index.html'), (err) => err && next(err));
+// });
+app.use((req, res, next) => {
+  if (req.path.startsWith('/api') || req.path.startsWith('/upload')) return next();
+  res.sendFile(path.join(DIST, 'index.html'), (err) => err && next(err));
+});
+
+const server = app.listen(PORT, '0.0.0.0', () => {
+  console.log(`[frontend] http://0.0.0.0:${PORT}  ->  backend ${BACKEND}`);
+});
+
+// WebSocket 鍗囩骇
+server.on('upgrade', socketProxy.upgrade);
+
+process.on('unhandledRejection', (r) => console.error('[unhandledRejection]', r && r.message));
+process.on('uncaughtException', (e) => console.error('[uncaughtException]', e && e.message));
\ No newline at end of file
diff --git a/src/modules/base/config.ts b/src/modules/base/config.ts
index cac5e6f..76e3186 100644
--- a/src/modules/base/config.ts
+++ b/src/modules/base/config.ts
@@ -3,6 +3,7 @@
 import { config } from '/@/config';
 import { t } from '/@/plugins/i18n';
 import './static/css/index.scss';
+import './static/css/mobile.scss';
 
 export default (): ModuleConfig => {
 	return {
diff --git a/src/modules/base/pages/error/components/error-page.vue b/src/modules/base/pages/error/components/error-page.vue
index bfb7527..3315cb1 100644
--- a/src/modules/base/pages/error/components/error-page.vue
+++ b/src/modules/base/pages/error/components/error-page.vue
@@ -137,4 +137,22 @@
 		}
 	}
 }
+
+@media only screen and (max-width: 768px) {
+	.error-page {
+		overflow: auto;
+
+		&__code {
+			font-size: 72px;
+			margin-top: 0;
+		}
+
+		&__btns {
+			flex-wrap: wrap;
+			justify-content: center;
+			gap: 10px;
+			padding: 0 16px;
+		}
+	}
+}
 </style>
diff --git a/src/modules/base/pages/login/index.vue b/src/modules/base/pages/login/index.vue
index 2a235b8..5a7afd4 100644
--- a/src/modules/base/pages/login/index.vue
+++ b/src/modules/base/pages/login/index.vue
@@ -304,4 +304,36 @@
 		}
 	}
 }
+
+@media screen and (max-width: 768px) {
+	.page-login {
+		overflow: auto;
+		align-items: flex-start;
+
+		.bg {
+			opacity: 0.25;
+		}
+
+		.box {
+			position: relative;
+			height: auto;
+			min-height: 100%;
+			padding: 48px 20px calc(36px + env(safe-area-inset-bottom));
+			box-sizing: border-box;
+
+			.logo span {
+				font-size: 26px;
+				letter-spacing: 1px;
+			}
+
+			.desc {
+				margin-bottom: 28px;
+			}
+
+			.form {
+				width: min(360px, 100%);
+			}
+		}
+	}
+}
 </style>
diff --git a/src/modules/base/pages/main/components/amenu.vue b/src/modules/base/pages/main/components/amenu.vue
index b34000d..f35a988 100644
--- a/src/modules/base/pages/main/components/amenu.vue
+++ b/src/modules/base/pages/main/components/amenu.vue
@@ -148,4 +148,16 @@
 		margin-left: 8px;
 	}
 }
+
+@media only screen and (max-width: 768px) {
+	.a-menu {
+		min-width: 0;
+		overflow-x: auto;
+
+		&__item {
+			flex-shrink: 0;
+			padding: 0 10px;
+		}
+	}
+}
 </style>
diff --git a/src/modules/base/pages/main/components/process.vue b/src/modules/base/pages/main/components/process.vue
index cf82f72..98746c0 100644
--- a/src/modules/base/pages/main/components/process.vue
+++ b/src/modules/base/pages/main/components/process.vue
@@ -276,4 +276,18 @@
 		}
 	}
 }
+
+@media only screen and (max-width: 768px) {
+	.app-process {
+		padding-left: 6px;
+		padding-right: 6px;
+
+		&__item .close {
+			margin-left: 8px;
+			margin-right: -2px;
+			width: 16px;
+			opacity: 1;
+		}
+	}
+}
 </style>
diff --git a/src/modules/base/pages/main/components/topbar.vue b/src/modules/base/pages/main/components/topbar.vue
index 5559d13..a26db38 100644
--- a/src/modules/base/pages/main/components/topbar.vue
+++ b/src/modules/base/pages/main/components/topbar.vue
@@ -188,6 +188,54 @@
 		}
 	}
 }
+
+@media only screen and (max-width: 768px) {
+	.app-topbar {
+		padding-top: env(safe-area-inset-top);
+		padding-right: 6px;
+		height: calc(46px + env(safe-area-inset-top));
+		min-width: 0;
+
+		.flex1 {
+			min-width: 8px;
+		}
+
+		&__tools {
+			flex-shrink: 0;
+			margin-right: 4px;
+
+			& > li {
+				margin-left: 6px;
+			}
+		}
+
+		&__user {
+			flex-shrink: 0;
+			padding-left: 4px;
+
+			:deep(.el-text) {
+				display: none;
+			}
+		}
+
+		:deep(.route-nav) {
+			min-width: 0;
+			max-width: 46vw;
+			overflow: hidden;
+			text-overflow: ellipsis;
+		}
+
+		:deep(.a-menu) {
+			min-width: 0;
+			overflow-x: auto;
+		}
+
+		:deep(.cl-comm__icon) {
+			width: 32px;
+			height: 32px;
+		}
+	}
+}
 </style>
 
 <style lang="scss">
diff --git a/src/modules/base/pages/main/components/views.vue b/src/modules/base/pages/main/components/views.vue
index 0ae0219..50bd056 100644
--- a/src/modules/base/pages/main/components/views.vue
+++ b/src/modules/base/pages/main/components/views.vue
@@ -96,4 +96,14 @@
 		opacity: 1;
 	}
 }
+
+@media only screen and (max-width: 768px) {
+	.app-views {
+		overflow-x: hidden;
+		overflow-y: auto;
+		-webkit-overflow-scrolling: touch;
+		margin: 0 8px calc(8px + env(safe-area-inset-bottom)) 8px;
+		width: calc(100% - 16px);
+	}
+}
 </style>
diff --git a/src/modules/base/pages/main/index.vue b/src/modules/base/pages/main/index.vue
index e078958..620da61 100644
--- a/src/modules/base/pages/main/index.vue
+++ b/src/modules/base/pages/main/index.vue
@@ -67,10 +67,15 @@
 	}
 
 	@media only screen and (max-width: 768px) {
+		position: relative;
+
 		.app-layout__left {
 			position: absolute;
 			left: 0;
+			top: 0;
+			width: min(280px, 86vw);
 			z-index: 9999;
+			box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
 			transition:
 				transform 0.3s cubic-bezier(0.7, 0.3, 0.1, 1),
 				box-shadow 0.3s cubic-bezier(0.7, 0.3, 0.1, 1);
@@ -78,6 +83,7 @@
 
 		.app-layout__right {
 			width: 100%;
+			min-width: 0;
 		}
 
 		&.is-collapse {
diff --git a/src/modules/base/static/css/index.scss b/src/modules/base/static/css/index.scss
index e31abc7..30f9ca5 100644
--- a/src/modules/base/static/css/index.scss
+++ b/src/modules/base/static/css/index.scss
@@ -1,6 +1,8 @@
 #app {
 	height: 100vh;
-	width: 100vw;
+	height: 100dvh;
+	width: 100%;
+	max-width: 100vw;
 	overflow: hidden;
 }
 
diff --git a/src/modules/base/static/css/mobile.scss b/src/modules/base/static/css/mobile.scss
new file mode 100644
index 0000000..8034af8
--- /dev/null
+++ b/src/modules/base/static/css/mobile.scss
@@ -0,0 +1,253 @@
+/* 鎵嬫満娴忚鍣細鍙湪绐勫睆鐢熸晥锛屾闈㈢鏍峰紡淇濇寔鍘熸牱 */
+@media only screen and (max-width: 768px) {
+	html,
+	body {
+		-webkit-text-size-adjust: 100%;
+	}
+
+	.el-scrollbar__wrap {
+		-webkit-overflow-scrolling: touch;
+	}
+
+	/* 閬垮厤 iOS 鑱氱劍杈撳叆妗嗘椂鑷姩鏀惧ぇ椤甸潰 */
+	.el-form .el-input__inner,
+	.el-form .el-textarea__inner,
+	.el-form .el-select__wrapper,
+	.el-form .el-input__wrapper input {
+		font-size: 16px;
+	}
+
+	.el-dialog:not(.is-fullscreen) {
+		width: calc(100vw - 24px) !important;
+		max-width: calc(100vw - 24px);
+		margin: 12px auto !important;
+	}
+
+	.el-drawer {
+		width: min(100vw, 420px) !important;
+		max-width: 100vw;
+	}
+
+	.el-dialog__body,
+	.cl-dialog .el-dialog__body {
+		max-height: calc(100dvh - 140px);
+		overflow: auto;
+	}
+
+	.cl-search,
+	.cl-form,
+	.el-dialog,
+	.el-drawer {
+		.el-form--inline {
+			.el-form-item {
+				display: flex;
+				margin-right: 0;
+				width: 100%;
+			}
+
+			.el-form-item__content {
+				flex: 1;
+				min-width: 0;
+			}
+		}
+
+		.el-date-editor.el-input,
+		.el-date-editor--daterange,
+		.el-date-editor--datetimerange,
+		.el-select,
+		.el-cascader,
+		.el-input-number {
+			width: 100% !important;
+			max-width: 100%;
+			box-sizing: border-box;
+		}
+	}
+
+	.cl-form .el-col,
+	.el-dialog .el-form .el-col,
+	.cl-search .el-col {
+		flex: 0 0 100%;
+		max-width: 100%;
+	}
+
+	.cl-search,
+	.cl-search-key,
+	.cl-search-key__wrap,
+	.cl-filter {
+		width: 100%;
+		max-width: 100%;
+	}
+
+	.cl-filter {
+		margin-left: 0;
+		margin-right: 0;
+	}
+
+	.cl-pagination.el-pagination {
+		flex-wrap: wrap;
+		justify-content: center;
+		row-gap: 8px;
+		padding-bottom: 4px;
+	}
+
+	.el-pagination__sizes,
+	.el-pagination__jump {
+		display: none !important;
+	}
+
+	.cl-table,
+	.el-table {
+		max-width: 100%;
+	}
+
+	.el-table .cell {
+		word-break: break-word;
+	}
+
+	.el-message-box {
+		width: calc(100vw - 32px) !important;
+	}
+
+	.el-picker__popper,
+	.el-select__popper {
+		max-width: calc(100vw - 16px);
+	}
+
+	.el-date-range-picker {
+		width: calc(100vw - 16px) !important;
+		max-width: calc(100vw - 16px);
+
+		.el-picker-panel__body {
+			min-width: 0 !important;
+			display: flex;
+			flex-direction: column;
+		}
+
+		.el-date-range-picker__content {
+			width: 100%;
+			padding: 8px;
+		}
+	}
+
+	.el-dropdown__popper,
+	.el-popover.el-popper {
+		max-width: calc(100vw - 16px);
+	}
+
+	/* 鎵�鏈変笟鍔¢〉锛氶檺鍒跺湪灞忓箷鍐咃紝鍐呭鍖鸿嚜宸辨粴鍔� */
+	.app-views,
+	.app-views > * {
+		max-width: 100%;
+		min-width: 0;
+	}
+
+	.cl-crud {
+		max-width: 100%;
+	}
+
+	.cl-crud > .cl-row {
+		flex-wrap: wrap;
+		row-gap: 8px;
+		max-width: 100%;
+	}
+
+	.cl-crud > .cl-row > * {
+		max-width: 100%;
+	}
+
+	.cl-crud > .cl-row > .cl-flex1 {
+		flex: 1 1 100%;
+		width: 100%;
+		height: 0;
+		margin: 0;
+	}
+
+	.cl-filter {
+		flex: 1 1 100%;
+		width: 100%;
+		max-width: 100%;
+		margin: 0;
+		box-sizing: border-box;
+
+		.el-select,
+		.el-date-editor,
+		.el-input {
+			flex: 1;
+			width: 100% !important;
+			min-width: 0 !important;
+		}
+	}
+
+	.cl-adv-search,
+	.cl-search.is-inline,
+	.cl-search-key {
+		flex: 1 1 100%;
+	}
+
+	.el-table {
+		width: 100% !important;
+	}
+
+	.el-table__header-wrapper,
+	.el-table__body-wrapper,
+	.el-scrollbar__wrap {
+		max-width: 100%;
+	}
+
+	.el-descriptions__label,
+	.el-descriptions__content {
+		word-break: break-word;
+	}
+
+	.el-tabs__nav-scroll {
+		overflow-x: auto;
+	}
+
+	.el-dialog:not(.is-fullscreen),
+	.cl-dialog:not(.is-fullscreen) {
+		max-height: calc(100dvh - 24px);
+	}
+
+	.cl-dialog__container,
+	.cl-dialog .el-scrollbar {
+		max-height: calc(100dvh - 132px) !important;
+	}
+
+	.w-e-toolbar,
+	.cl-editor-wang {
+		max-width: 100%;
+		overflow-x: auto;
+	}
+
+	.cl-view-group__right .head .title {
+		max-width: calc(100vw - 120px);
+		padding: 0 36px;
+		text-overflow: ellipsis;
+	}
+
+	.error-page__code {
+		font-size: 72px;
+	}
+
+	.plugins__header {
+		flex-wrap: wrap;
+		gap: 8px;
+
+		.search {
+			width: 100% !important;
+		}
+	}
+
+	.cl-upload-space-inner__header {
+		flex-wrap: wrap;
+		height: auto;
+		min-height: 50px;
+		padding: 8px 10px;
+		gap: 8px;
+	}
+
+	.cl-upload-space-inner__file-item {
+		width: calc(50% - 10px) !important;
+		padding-top: calc(50% - 10px) !important;
+	}
+}
diff --git a/src/modules/ezuikit/components/ezuikit-player.vue b/src/modules/ezuikit/components/ezuikit-player.vue
index ed546e9..5cc934d 100644
--- a/src/modules/ezuikit/components/ezuikit-player.vue
+++ b/src/modules/ezuikit/components/ezuikit-player.vue
@@ -171,4 +171,9 @@
   padding: 0px;
   background: #000;
 }
+
+/* 鎵嬫満鎵╁睍鍖洪噷鐨勫渾褰簯鍙般�係DK 鐢� deepmerge 鍚堝苟閰嶇疆鏃舵暟缁勫悎骞讹紝鍗曢潬鍒犳帀 controls 閲岀殑 ptz 鍘讳笉鎺� */
+.ez-player-card :deep(.ez-mobile-ptz) {
+  display: none;
+}
 </style>
\ No newline at end of file
diff --git a/src/modules/maps/components/map-board.vue b/src/modules/maps/components/map-board.vue
index 68b7a76..4266880 100644
--- a/src/modules/maps/components/map-board.vue
+++ b/src/modules/maps/components/map-board.vue
@@ -134,4 +134,29 @@
 	width: 100%;
 	height: 100%;
 }
+
+@media (max-width: 992px) {
+	.map-board {
+		flex-direction: column;
+		height: 100%;
+		overflow: auto;
+	}
+
+	.map-board__side {
+		width: 100%;
+		max-height: 240px;
+	}
+
+	.map-board__map {
+		flex: none;
+		width: 100%;
+		height: 56vh;
+		min-height: 320px;
+	}
+
+	.map-board__panel {
+		width: 100%;
+		max-height: none;
+	}
+}
 </style>
diff --git a/src/modules/monitor/views/cameras.vue b/src/modules/monitor/views/cameras.vue
index 94372c4..23ecd10 100644
--- a/src/modules/monitor/views/cameras.vue
+++ b/src/modules/monitor/views/cameras.vue
@@ -257,4 +257,24 @@
   border-color: #555 !important;
   color: #888 !important;
 }
+
+@media (max-width: 768px) {
+  .camera-page {
+    padding: 8px;
+  }
+
+  .scroll-viewport {
+    margin: 0 6px;
+  }
+
+  .player-card {
+    width: min(280px, calc(100vw - 96px));
+    height: 240px;
+  }
+
+  .arrow {
+    width: 28px;
+    height: 64px;
+  }
+}
 </style>
\ No newline at end of file
diff --git a/src/modules/monitor/views/device.vue b/src/modules/monitor/views/device.vue
index c2dd1c9..3b91628 100644
--- a/src/modules/monitor/views/device.vue
+++ b/src/modules/monitor/views/device.vue
@@ -117,4 +117,26 @@
     font-weight: 500;
   }
 }
+
+@media (max-width: 768px) {
+  .monitor-page {
+    flex-direction: column;
+    overflow: auto;
+  }
+
+  .left-tree {
+    width: 100%;
+    height: 220px;
+    flex-shrink: 0;
+  }
+
+  .right-player {
+    height: auto;
+    min-height: 360px;
+  }
+
+  .player-grid {
+    grid-template-columns: 1fr !important;
+  }
+}
 </style>
\ No newline at end of file
diff --git a/src/modules/operation/views/traderecord.vue b/src/modules/operation/views/traderecord.vue
index a22605f..c178640 100644
--- a/src/modules/operation/views/traderecord.vue
+++ b/src/modules/operation/views/traderecord.vue
@@ -8,6 +8,9 @@
             <!-- 鍒犻櫎鎸夐挳 -->
             <cl-multi-delete-btn />
             <cl-flex1 />
+            <cl-filter label="浜ゆ槗鏃堕棿">
+                <cl-date-picker type="daterange" />
+            </cl-filter>
             <cl-import-btn tips="瀵煎叆寰俊浜ゆ槗璁板綍" :on-config="onConfig" :on-submit="onSubmit" />
             <!-- 鏉′欢鎼滅储 -->
             <cl-search ref="Search" />
diff --git a/src/modules/operation/views/wastesale.vue b/src/modules/operation/views/wastesale.vue
index dd787cc..83867a0 100644
--- a/src/modules/operation/views/wastesale.vue
+++ b/src/modules/operation/views/wastesale.vue
@@ -8,6 +8,9 @@
             <!-- 鍒犻櫎鎸夐挳 -->
             <cl-multi-delete-btn />
             <cl-flex1 />
+            <cl-filter label="鏃ユ湡鏃堕棿">
+                <cl-date-picker type="daterange" />
+            </cl-filter>
             <!-- 鏉′欢鎼滅储 -->
             <cl-search ref="Search" />
         </cl-row>
@@ -260,12 +263,14 @@
         service: service.operation.wastesale
     },
     (app) => {
-        service.basicdata.wastetype.page({ isRecyclable: 1, size: 1000 }).then((res) => {
-            garbageTypeOptions.value = (res.list || []).map((item: any) => ({
-                label: item.name,
-                value: item.code,
-                price: item.price,
-            }));
+        service.basicdata.wastetype.page({ isRecyclable: 1, status: 1, size: 1000 }).then((res) => {
+            garbageTypeOptions.value = (res.list || [])
+                .filter((item: any) => item.status === 1)
+                .map((item: any) => ({
+                    label: item.name,
+                    value: item.code,
+                    price: item.price,
+                }));
         });
         app.refresh();
     }
diff --git a/src/modules/push/views/auditlog.vue b/src/modules/push/views/auditlog.vue
index 089b90b..71cc0e7 100644
--- a/src/modules/push/views/auditlog.vue
+++ b/src/modules/push/views/auditlog.vue
@@ -8,6 +8,9 @@
 			<!-- 鍒犻櫎鎸夐挳 -->
 			<cl-multi-delete-btn />
 			<cl-flex1 />
+			<cl-filter label="鏃ュ織鏃堕棿">
+				<cl-date-picker type="daterange" />
+			</cl-filter>
 			<!-- 鏉′欢鎼滅储 -->
 			<cl-search ref="Search" />
 		</cl-row>
diff --git a/src/modules/push/views/cameragps.vue b/src/modules/push/views/cameragps.vue
index 7ec61ae..f005332 100644
--- a/src/modules/push/views/cameragps.vue
+++ b/src/modules/push/views/cameragps.vue
@@ -7,9 +7,12 @@
             <cl-add-btn />
             <!-- 鍒犻櫎鎸夐挳 -->
             <cl-multi-delete-btn />
-            <cl-flex1 />
-            <!-- 鏉′欢鎼滅储 -->
-            <cl-search ref="Search" />
+			<cl-flex1 />
+			<cl-filter label="娑堟伅鍒涘缓鏃堕棿">
+				<cl-date-picker type="daterange" />
+			</cl-filter>
+			<!-- 鏉′欢鎼滅储 -->
+			<cl-search ref="Search" />
         </cl-row>
 
         <cl-row>
diff --git a/src/modules/push/views/compressionweight.vue b/src/modules/push/views/compressionweight.vue
index 3fba788..ebba0dd 100644
--- a/src/modules/push/views/compressionweight.vue
+++ b/src/modules/push/views/compressionweight.vue
@@ -8,6 +8,9 @@
 			<!-- 鍒犻櫎鎸夐挳 -->
 			<cl-multi-delete-btn />
 			<cl-flex1 />
+			<cl-filter label="涓婁紶鏃堕棿">
+				<cl-date-picker type="daterange" />
+			</cl-filter>
 			<!-- 鏉′欢鎼滅储 -->
 			<cl-search ref="Search" />
 		</cl-row>
diff --git a/src/modules/push/views/devicealarm.vue b/src/modules/push/views/devicealarm.vue
index 94b5b7f..02b5a22 100644
--- a/src/modules/push/views/devicealarm.vue
+++ b/src/modules/push/views/devicealarm.vue
@@ -8,6 +8,9 @@
 			<!-- 鍒犻櫎鎸夐挳 -->
 			<cl-multi-delete-btn />
 			<cl-flex1 />
+			<cl-filter label="鎶ヨ鏃堕棿鎴�">
+				<cl-date-picker type="daterange" />
+			</cl-filter>
 			<!-- 鏉′欢鎼滅储 -->
 			<cl-search ref="Search" />
 		</cl-row>
diff --git a/src/modules/push/views/deviceonoff.vue b/src/modules/push/views/deviceonoff.vue
index 73f5ae0..f755f4c 100644
--- a/src/modules/push/views/deviceonoff.vue
+++ b/src/modules/push/views/deviceonoff.vue
@@ -7,9 +7,12 @@
             <cl-add-btn />
             <!-- 鍒犻櫎鎸夐挳 -->
             <cl-multi-delete-btn />
-            <cl-flex1 />
-            <!-- 鏉′欢鎼滅储 -->
-            <cl-search ref="Search" />
+			<cl-flex1 />
+			<cl-filter label="娑堟伅鏃堕棿">
+				<cl-date-picker type="daterange" />
+			</cl-filter>
+			<!-- 鏉′欢鎼滅储 -->
+			<cl-search ref="Search" />
         </cl-row>
 
         <cl-row>
diff --git a/src/modules/push/views/siteweight.vue b/src/modules/push/views/siteweight.vue
index 03a8430..e8c5024 100644
--- a/src/modules/push/views/siteweight.vue
+++ b/src/modules/push/views/siteweight.vue
@@ -8,6 +8,9 @@
 			<!-- 鍒犻櫎鎸夐挳 -->
 			<cl-multi-delete-btn />
 			<cl-flex1 />
+			<cl-filter label="涓婁紶鏃堕棿">
+				<cl-date-picker type="daterange" />
+			</cl-filter>
 			<!-- 鏉′欢鎼滅储 -->
 			<cl-search ref="Search" />
 		</cl-row>
@@ -192,11 +195,13 @@
 		service: service.push.siteweight,
 	},
 	(app) => {
-		service.basicdata.wastetype.list().then((res) => {
-			garbageTypeOptions.value = (res || []).map((item: any) => ({
-				label: item.name,
-				value: item.code,
-			}));
+		service.basicdata.wastetype.list({ status: 1 }).then((res) => {
+			garbageTypeOptions.value = (res || [])
+				.filter((item: any) => item.status === 1)
+				.map((item: any) => ({
+					label: item.name,
+					value: item.code,
+				}));
 		});
 		app.refresh();
 	},
diff --git a/src/modules/push/views/vehiclegps.vue b/src/modules/push/views/vehiclegps.vue
index c90a20f..eee4a55 100644
--- a/src/modules/push/views/vehiclegps.vue
+++ b/src/modules/push/views/vehiclegps.vue
@@ -7,9 +7,12 @@
             <cl-add-btn />
             <!-- 鍒犻櫎鎸夐挳 -->
             <cl-multi-delete-btn />
-            <cl-flex1 />
-            <!-- 鏉′欢鎼滅储 -->
-            <cl-search ref="Search" />
+			<cl-flex1 />
+			<cl-filter label="GPS瀹氫綅鏃堕棿">
+				<cl-date-picker type="daterange" />
+			</cl-filter>
+			<!-- 鏉′欢鎼滅储 -->
+			<cl-search ref="Search" />
         </cl-row>
 
         <cl-row>
diff --git a/src/modules/push/views/weight.vue b/src/modules/push/views/weight.vue
index 2581a4e..06bc4b7 100644
--- a/src/modules/push/views/weight.vue
+++ b/src/modules/push/views/weight.vue
@@ -8,6 +8,9 @@
 			<!-- 鍒犻櫎鎸夐挳 -->
 			<cl-multi-delete-btn />
 			<cl-flex1 />
+			<cl-filter label="涓婁紶鏃堕棿">
+				<cl-date-picker type="daterange" />
+			</cl-filter>
 			<!-- 鏉′欢鎼滅储 -->
 			<cl-search ref="Search" />
 		</cl-row>
@@ -33,12 +36,15 @@
 	name: "push-weight",
 });
 
+import { ref } from "vue";
 import { useCrud, useTable, useUpsert, useSearch } from "@cool-vue/crud";
 import { useCool } from "/@/cool";
 import { useI18n } from "vue-i18n";
 
 const { service } = useCool();
 const { t } = useI18n();
+
+const garbageTypeOptions = ref<{ label: string; value: string }[]>([]);
 
 // cl-upsert
 const Upsert = useUpsert({
@@ -98,6 +104,7 @@
 			component: { name: "cl-date-text" },
 		},
 		{ label: t("鍨冨溇绫诲瀷"), prop: "garbageType", minWidth: 120 },
+		{ label: t("鍨冨溇鍒嗙被"), prop: "garbageName", minWidth: 140 },
 		{
 			label: t("鍨冨溇閲嶉噺"),
 			prop: "weight",
@@ -129,7 +136,34 @@
 });
 
 // cl-search
-const Search = useSearch();
+const Search = useSearch({
+	items: [
+		{
+			prop: "deviceNo",
+			component: {
+				name: "el-input",
+				props: {
+					placeholder: t("鎼滅储璁惧鍙�"),
+					clearable: true,
+				},
+				style: { width: "150px" },
+			},
+		},
+		{
+			prop: "garbageType",
+			component: {
+				name: "el-select",
+				props: {
+					placeholder: t("鍨冨溇绫诲瀷"),
+					filterable: true,
+					clearable: true,
+				},
+				options: garbageTypeOptions,
+				style: { width: "180px" },
+			},
+		},
+	],
+});
 
 // cl-crud
 const Crud = useCrud(
@@ -137,6 +171,14 @@
 		service: service.push.weight,
 	},
 	(app) => {
+		service.basicdata.wastetype.list({ status: 1 }).then((res) => {
+			garbageTypeOptions.value = (res || [])
+				.filter((item: any) => item.status === 1)
+				.map((item: any) => ({
+					label: item.name,
+					value: item.code,
+				}));
+		});
 		app.refresh();
 	},
 );
diff --git a/src/modules/report/components/panel-card.vue b/src/modules/report/components/panel-card.vue
index 0dae349..1165550 100644
--- a/src/modules/report/components/panel-card.vue
+++ b/src/modules/report/components/panel-card.vue
@@ -73,4 +73,25 @@
 		padding: 8px 8px 12px;
 	}
 }
+
+@media (max-width: 768px) {
+	.panel-card {
+		min-height: 220px;
+
+		&__header {
+			flex-wrap: wrap;
+			align-items: flex-start;
+			padding: 12px 12px 0;
+		}
+
+		&__title {
+			flex-wrap: wrap;
+		}
+
+		&__extra {
+			width: 100%;
+			overflow-x: auto;
+		}
+	}
+}
 </style>
diff --git a/src/modules/report/components/screen-map-panel.vue b/src/modules/report/components/screen-map-panel.vue
index 4a7969c..b86dd28 100755
--- a/src/modules/report/components/screen-map-panel.vue
+++ b/src/modules/report/components/screen-map-panel.vue
@@ -253,6 +253,18 @@
 	}
 }
 
+@media (max-width: 768px) {
+	.map-panel__bar,
+	.map-panel__legend {
+		flex-wrap: wrap;
+		gap: 8px;
+	}
+
+	.map-panel__map {
+		min-height: 280px;
+	}
+}
+
 @keyframes map-pulse {
 	0%,
 	100% {
diff --git a/src/modules/report/components/screen-shell.vue b/src/modules/report/components/screen-shell.vue
index 23d0b88..117b227 100644
--- a/src/modules/report/components/screen-shell.vue
+++ b/src/modules/report/components/screen-shell.vue
@@ -62,15 +62,11 @@
 
 const current = computed(() => SCREEN_NAV.find(e => e.key === currentKey.value));
 
-/** 濮嬬粓鐣欏湪棣栭〉璺緞锛岀敤 ?page= 鍒囨崲 pageMap锛岄伩鍏嶈烦鍒� /overview 涓㈡帀瀵艰埅澹� */
+/** 濮嬬粓鐣欏湪棣栭〉璺緞锛岀敤 ?page= 鍒囨崲銆傞粯璁ら〉鏄湴鍥撅紝overview 涔熷繀椤诲甫 page锛屽惁鍒欎細琚В鏋愬洖 map */
 function go(key: ScreenKey) {
 	if (key === currentKey.value) return;
 	const query = { ...route.query } as Record<string, any>;
-	if (key === 'overview') {
-		delete query.page;
-	} else {
-		query.page = key;
-	}
+	query.page = key;
 	router.replace({ path: SCREEN_HOME_PATH, query });
 }
 
@@ -285,4 +281,38 @@
 	gap: 10px;
 	flex-shrink: 0;
 }
+
+@media (max-width: 992px) {
+	.green-screen__header {
+		flex-wrap: wrap;
+		align-items: flex-start;
+		gap: 8px;
+	}
+
+	.brand {
+		min-width: 0;
+		flex: 1 1 auto;
+	}
+
+	.nav {
+		order: 3;
+		flex: 1 1 100%;
+	}
+
+	.tools {
+		max-width: 100%;
+		overflow-x: auto;
+	}
+}
+
+@media (max-width: 768px) {
+	.green-screen {
+		min-height: 0;
+		height: 100%;
+	}
+
+	.brand__title {
+		font-size: 16px;
+	}
+}
 </style>
diff --git a/src/modules/report/data/screen-demo.ts b/src/modules/report/data/screen-demo.ts
deleted file mode 100644
index 097ccf2..0000000
--- a/src/modules/report/data/screen-demo.ts
+++ /dev/null
@@ -1,210 +0,0 @@
-import type { ScreenData, TimeRange } from './screen';
-import { ITEM_COLORS, WASTE_COLORS, buildScreenAxis, structureScreen } from './screen-structure';
-
-function wave(len: number, base: number, amp: number, seed = 0) {
-	return Array.from({ length: len }, (_, i) => {
-		const v = base + amp * Math.sin((i + seed) * 0.85) + amp * 0.35 * Math.cos((i + seed) * 0.4);
-		return Math.max(0, Math.round(v * 10) / 10);
-	});
-}
-
-function intWave(len: number, base: number, amp: number, seed = 0) {
-	return wave(len, base, amp, seed).map(v => Math.round(v));
-}
-
-function scaleByRange(range: TimeRange) {
-	const map: Record<TimeRange, number> = {
-		today: 0.08,
-		week: 0.28,
-		month: 1,
-		quarter: 2.8,
-		year: 1
-	};
-	return map[range] ?? 1;
-}
-
-function buildRank(name: string, org: string | undefined, people: number, weight: number, points: number) {
-	return {
-		name,
-		org,
-		weight: Math.round(weight * 10) / 10,
-		people,
-		rate: 0,
-		points: Math.round(points),
-		carbon: Math.round(weight * 10) / 10,
-		size: people,
-		weightPerCapita: Math.round((weight / people) * 100) / 100,
-		pointPerCapita: Math.round((points / people) * 10) / 10
-	};
-}
-
-/** 楗辨弧婕旂ず鏁版嵁锛屽彛寰勪笌鍚庣缁撴瀯涓�鑷� */
-export function demoScreen(range: TimeRange = 'month'): ScreenData {
-	const base = structureScreen(range);
-	const axis = base.axis.length ? base.axis : buildScreenAxis(range);
-	const s = scaleByRange(range);
-
-	const periodWeightTon = Math.round(18.6 * s * 100) / 100;
-	const recyclableTon = Math.round(7.9 * s * 100) / 100;
-	const periodCarbonTon = Math.round(2.14 * s * 100) / 100;
-	const periodValue = Math.round(12800 * s);
-	const totalWeightTon = 126.8;
-	const totalCarbonTon = 14.6;
-	const totalParticipants = 3842;
-	const totalPoints = 96500;
-
-	const wasteWeights = [4200, 3100, 980, 420, 2860].map(v => Math.round(v * s));
-	const wasteCounts = [860, 720, 96, 38, 540].map(v => Math.round(v * s));
-	const wasteMix = base.wasteMix.map((item, i) => ({
-		...item,
-		weight: wasteWeights[i],
-		count: wasteCounts[i],
-		value: i === 0 ? periodValue : 0,
-		color: WASTE_COLORS[item.code || ''] || item.color
-	}));
-
-	const recycleWeights = [1820, 960, 540, 680, 320, 410, 270].map(v => Math.round(v * s));
-	const recycleValues = [4200, 2100, 1800, 960, 480, 3200, 180].map(v => Math.round(v * s));
-	const recycleCarbons = recycleWeights.map(w => Math.round(w * 0.42 * 10) / 10);
-	const recycleItems = base.recycleItems.map((item, i) => ({
-		...item,
-		weight: recycleWeights[i],
-		value: recycleValues[i],
-		carbon: recycleCarbons[i],
-		color: ITEM_COLORS[i]
-	}));
-
-	const trendWeight = wave(axis.length, 620 * s, 180 * s, 1);
-	const trendValue = intWave(axis.length, 420 * s, 120 * s, 2);
-	const trendCarbon = wave(axis.length, 860 * s, 220 * s, 3);
-
-	const yearCarbon = [0.8, 0.9, 1.1, 1.3, 1.5, 1.8, 2.0, 2.2, 1.9, 1.6, 1.2, 0.9];
-
-	const deptRank = [
-		buildRank('鐜绉戝涓庡伐绋嬪闄�', undefined, 486, 2860, 12800),
-		buildRank('鏉愭枡绉戝涓庡伐绋嬪闄�', undefined, 412, 2410, 10600),
-		buildRank('缁忔祹绠$悊瀛﹂櫌', undefined, 368, 1980, 9200),
-		buildRank('璁$畻鏈哄闄�', undefined, 352, 1760, 8800),
-		buildRank('澶栧浗璇闄�', undefined, 286, 1420, 6400),
-		buildRank('鍦熸湪宸ョ▼瀛﹂櫌', undefined, 248, 1180, 5200),
-		buildRank('鐢熷懡绉戝瀛﹂櫌', undefined, 216, 960, 4600),
-		buildRank('椹厠鎬濅富涔夊闄�', undefined, 168, 720, 3200)
-	];
-
-	const classRank = [
-		buildRank('鐜2201', '鐜绉戝涓庡伐绋嬪闄�', 68, 420, 1860),
-		buildRank('鏉愭枡2102', '鏉愭枡绉戝涓庡伐绋嬪闄�', 62, 386, 1720),
-		buildRank('璁$2303', '璁$畻鏈哄闄�', 58, 352, 1580),
-		buildRank('缁忕2204', '缁忔祹绠$悊瀛﹂櫌', 54, 318, 1460),
-		buildRank('鍦熸湪2101', '鍦熸湪宸ョ▼瀛﹂櫌', 48, 286, 1280),
-		buildRank('澶栬2202', '澶栧浗璇闄�', 44, 248, 1120),
-		buildRank('鐢熺2301', '鐢熷懡绉戝瀛﹂櫌', 40, 216, 980),
-		buildRank('鐜伐2103', '鐜绉戝涓庡伐绋嬪闄�', 36, 198, 860)
-	];
-
-	const majorRank = [
-		buildRank('鐜宸ョ▼', undefined, 286, 1680, 7600),
-		buildRank('鏉愭枡鍖栧', undefined, 248, 1420, 6400),
-		buildRank('杞欢宸ョ▼', undefined, 236, 1280, 5800),
-		buildRank('宸ュ晢绠$悊', undefined, 218, 1160, 5200),
-		buildRank('鑻辫', undefined, 186, 960, 4200),
-		buildRank('鍦熸湪宸ョ▼', undefined, 168, 860, 3800),
-		buildRank('鐢熺墿鎶�鏈�', undefined, 142, 720, 3200),
-		buildRank('鐜绉戝', undefined, 128, 640, 2800)
-	];
-
-	const issued = Math.round(186000 * Math.max(s, 0.2));
-	const transfer = Math.round(issued * 0.12);
-	const donate = Math.round(issued * 0.08);
-	const mall = Math.round(issued * 0.46);
-	const balance = totalPoints;
-
-	const orderAxis = base.ordersByType.axis;
-	const ordersByType = {
-		axis: orderAxis,
-		鐜板満鍥炴敹: intWave(orderAxis.length, 18 * s, 8 * s, 4),
-		棰勭害涓婇棬: intWave(orderAxis.length, 12 * s, 5 * s, 5),
-		澶у睆鎶曢��: intWave(orderAxis.length, 26 * s, 10 * s, 6),
-		娲诲姩鍥炴敹: intWave(orderAxis.length, 8 * s, 4 * s, 7)
-	};
-
-	return {
-		...base,
-		axis,
-		kpis: {
-			todayWeightKg: Math.round(286.5 * Math.max(s, 0.15) * 10) / 10,
-			periodWeightTon,
-			resourceRate: 42.3,
-			periodValue,
-			periodCarbonTon,
-			totalWeightTon,
-			totalParticipants,
-			totalPoints,
-			recyclableTon,
-			bulkTon: Math.round(1.8 * s * 100) / 100,
-			elecTon: Math.round(0.96 * s * 100) / 100,
-			fabricTon: Math.round(1.24 * s * 100) / 100,
-			yearCarbonTon: 18.6,
-			monthCarbonTon: periodCarbonTon,
-			totalCarbonTon,
-			greenActions: Math.round(8620 * Math.max(s, 0.2)),
-			newParticipants: Math.round(286 * s),
-			participateRate: 31.6,
-			pointsIssued: issued,
-			pointsConsumed: Math.round(issued * 0.54),
-			pointsBalance: balance,
-			sorterOnline: 12,
-			sorterTotal: 16,
-			vehicleOnline: 5,
-			vehicleTotal: 7,
-			siteOnline: 18,
-			siteTotal: 22,
-			todayOrders: Math.round(46 * Math.max(s, 0.2)),
-			todayAppointments: Math.round(28 * Math.max(s, 0.2)),
-			pendingAppointments: Math.round(6 * Math.max(s, 0.2)),
-			alerts: 2,
-			healthAlerts: 1
-		},
-		wasteMix,
-		recycleItems,
-		trend: {
-			weight: trendWeight,
-			value: trendValue,
-			carbon: trendCarbon
-		},
-		yearCarbon,
-		deptRank,
-		classRank,
-		majorRank,
-		pointFlow: { issued, transfer, donate, mall, balance },
-		participateTrend: intWave(axis.length, 86 * s, 28 * s, 8),
-		deliverTrend: intWave(axis.length, 168 * s, 42 * s, 9),
-		ordersByType,
-		appointmentFlow: [
-			{ name: '棰勭害', value: Math.round(18 * Math.max(s, 0.2)) },
-			{ name: '鎺ュ崟', value: Math.round(14 * Math.max(s, 0.2)) },
-			{ name: '瀹屾垚', value: Math.round(11 * Math.max(s, 0.2)) }
-		],
-		sorterStatus: [
-			{ name: '宸ヤ綔涓�', value: 12, color: '#22c55e' },
-			{ name: '浼戞伅', value: 2, color: '#f59e0b' },
-			{ name: '寮傚父', value: 1, color: '#ef4444' },
-			{ name: '绂荤嚎', value: 1, color: '#94a3b8' }
-		],
-		vehicles: [
-			{ no: '娌狝路8K126', km: 86.4, idle: 12, overspeed: 0, status: '鍦ㄧ嚎' },
-			{ no: '娌狝路1R441', km: 24.3, idle: 0, overspeed: 0, status: '绂荤嚎' }
-		],
-		sites: [
-			{ name: '瀛︾敓鍏瘬涓�鍙风珯', weight: 286.5, online: true },
-			{ name: '鍥句功棣嗗洖鏀剁偣', weight: 248.2, online: true },
-			{ name: '绗竴椋熷爞鏀堕泦鐐�', weight: 216.8, online: true },
-			{ name: '浣撹偛棣嗗洖鏀剁珯', weight: 186.4, online: true },
-			{ name: '琛屾斂妤兼敹闆嗙偣', weight: 162.6, online: true },
-			{ name: '瀹為獙妤糂鍖�', weight: 148.2, online: true },
-			{ name: '鐮旂┒鐢熷叕瀵撶珯', weight: 126.8, online: false },
-			{ name: '鍗楅棬涓存椂鐐�', weight: 98.4, online: true }
-		],
-		unavailable: []
-	};
-}
diff --git a/src/modules/report/data/screen-merge.ts b/src/modules/report/data/screen-merge.ts
index 3f76e64..b820412 100644
--- a/src/modules/report/data/screen-merge.ts
+++ b/src/modules/report/data/screen-merge.ts
@@ -1,12 +1,5 @@
 import type { ScreenData, TimeRange } from './screen';
-import { demoScreen } from './screen-demo';
-import {
-	FieldTracker,
-	KPI_GROUPS,
-	groupHasLive,
-	resolveDataMode,
-	type ScreenDataMode
-} from './screen-field-meta';
+import { FieldTracker, resolveDataMode, type ScreenDataMode } from './screen-field-meta';
 import { structureScreen } from './screen-structure';
 
 export type { ScreenDataMode };
@@ -18,271 +11,120 @@
 	liveFields: string[];
 }
 
-function sum(nums: number[]) {
-	return nums.reduce((a, b) => a + (Number(b) || 0), 0);
+function num(v: unknown, fallback = 0) {
+	const n = Number(v);
+	return Number.isFinite(n) ? n : fallback;
 }
 
-function hasPositive(nums: number[]) {
-	return nums.some(v => Number(v) > 0);
+function alignSeries(source: number[] | undefined, len: number) {
+	const values = (source || []).map(v => num(v));
+	if (!values.length) return Array.from({ length: len }, () => 0);
+	if (values.length === len) return values;
+	if (values.length > len) return values.slice(values.length - len);
+	return [...Array.from({ length: len - values.length }, () => 0), ...values];
 }
 
-function hasRankData(list: any[]) {
-	return list.length > 0 && list.some(e => Number(e?.people) > 0 || Number(e?.weight) > 0);
-}
-
-function pickKpis(
-	live: Record<string, number> | undefined,
-	demo: Record<string, number>,
-	ctx: FieldTracker
-) {
-	const out = { ...demo };
-	if (!live) {
-		Object.values(KPI_GROUPS)
-			.flat()
-			.forEach(key => ctx.markDemo(`kpis.${key}`));
-		return out;
-	}
-
-	for (const [group, keys] of Object.entries(KPI_GROUPS)) {
-		if (groupHasLive(keys, live)) {
-			keys.forEach(key => {
-				const v = Number(live[key]);
-				out[key] = Number.isFinite(v) ? v : demo[key];
-				ctx.markLive(`kpis.${key}`);
-			});
-		} else {
-			keys.forEach(key => ctx.markDemo(`kpis.${key}`));
-		}
-	}
-	return out;
-}
-
-function pickArray<T>(
-	field: string,
-	live: T[] | undefined,
-	demo: T[],
-	ctx: FieldTracker,
-	valid: (v: T[]) => boolean
-) {
-	if (live && valid(live)) {
-		ctx.markLive(field);
-		return live;
-	}
-	ctx.markDemo(field);
-	return demo;
-}
-
-function pickTrend(
-	field: string,
-	live: { weight: number[]; value: number[]; carbon: number[] } | undefined,
-	demo: ScreenData['trend'],
-	axis: string[],
-	ctx: FieldTracker
-) {
-	const valid =
-		live &&
-		live.weight?.length &&
-		(sum(live.weight) > 0 || sum(live.value) > 0 || sum(live.carbon) > 0);
-	if (valid) {
-		ctx.markLive(field);
-		return {
-			weight: alignSeries(live!.weight, axis.length, live!.weight[live!.weight.length - 1] || 0),
-			value: alignSeries(live!.value, axis.length, live!.value[live!.value.length - 1] || 0),
-			carbon: alignSeries(live!.carbon, axis.length, live!.carbon[live!.carbon.length - 1] || 0)
-		};
-	}
-	ctx.markDemo(field);
-	return alignTrend(demo, axis.length);
-}
-
-function alignSeries(source: number[], len: number, fill = 0) {
-	if (!source?.length) return Array.from({ length: len }, () => fill);
-	if (source.length === len) return [...source];
-	if (source.length > len) return source.slice(source.length - len);
-	return [...Array.from({ length: len - source.length }, () => fill), ...source];
-}
-
-function alignTrend(trend: ScreenData['trend'], len: number) {
-	return {
-		weight: alignSeries(trend.weight, len),
-		value: alignSeries(trend.value, len),
-		carbon: alignSeries(trend.carbon, len)
-	};
-}
-
-function pickWasteMix(
-	live: ScreenData['wasteMix'] | undefined,
-	demo: ScreenData['wasteMix'],
-	ctx: FieldTracker
-) {
-	if (
-		live?.length &&
-		live.some(e => Number(e.weight) > 0 || Number(e.count) > 0 || Number(e.value) > 0)
-	) {
-		ctx.markLive('wasteMix');
-		return live.map((item, i) => ({
-			...demo[i],
-			...item,
-			color: item.color || demo[i]?.color
-		}));
-	}
-	ctx.markDemo('wasteMix');
-	return demo;
-}
-
-function pickRecycleItems(
-	live: ScreenData['recycleItems'] | undefined,
-	demo: ScreenData['recycleItems'],
-	ctx: FieldTracker
-) {
-	if (live?.length && live.some(e => Number(e.weight) > 0 || Number(e.value) > 0 || Number(e.carbon) > 0)) {
-		ctx.markLive('recycleItems');
-		return live.map((item, i) => ({
-			...demo[i],
-			...item,
-			color: item.color || demo[i]?.color
-		}));
-	}
-	ctx.markDemo('recycleItems');
-	return demo;
-}
-
-function pickPointFlow(
-	live: ScreenData['pointFlow'] | undefined,
-	demo: ScreenData['pointFlow'],
-	ctx: FieldTracker
-) {
-	const out = { ...demo };
-	if (!live) {
-		ctx.markDemo('pointFlow');
-		(['issued', 'transfer', 'donate', 'mall', 'balance'] as const).forEach(key =>
-			ctx.markDemo(`pointFlow.${key}`)
-		);
-		return out;
-	}
-
-	let hasLive = false;
-	(['issued', 'transfer', 'donate', 'mall', 'balance'] as const).forEach(key => {
-		if (Number(live[key]) > 0) {
-			out[key] = live[key];
-			ctx.markLive(`pointFlow.${key}`);
-			hasLive = true;
-		} else {
-			ctx.markDemo(`pointFlow.${key}`);
-		}
-	});
-	if (hasLive) ctx.markLive('pointFlow');
-	else ctx.markDemo('pointFlow');
-	return out;
-}
-
-function pickOrdersByType(
-	live: ScreenData['ordersByType'] | undefined,
-	demo: ScreenData['ordersByType'],
-	ctx: FieldTracker
-) {
-	const keys = ['鐜板満鍥炴敹', '棰勭害涓婇棬', '澶у睆鎶曢��', '娲诲姩鍥炴敹'] as const;
-	const liveValid = live?.axis?.length && keys.some(k => hasPositive(live[k] || []));
-	if (liveValid) {
-		ctx.markLive('ordersByType');
-		return live!;
-	}
-	ctx.markDemo('ordersByType');
-	return demo;
-}
-
-function pickAppointmentFlow(
-	live: ScreenData['appointmentFlow'] | undefined,
-	demo: ScreenData['appointmentFlow'],
-	ctx: FieldTracker
-) {
-	if (live?.length && live.some(e => Number(e.value) > 0)) {
-		ctx.markLive('appointmentFlow');
-		return live;
-	}
-	ctx.markDemo('appointmentFlow');
-	return demo;
-}
-
-function pickSorterStatus(
-	live: ScreenData['sorterStatus'] | undefined,
-	demo: ScreenData['sorterStatus'],
-	ctx: FieldTracker
-) {
-	if (live?.length && sum(live.map(e => Number(e.value))) > 0) {
-		ctx.markLive('sorterStatus');
-		return live;
-	}
-	ctx.markDemo('sorterStatus');
-	return demo;
-}
-
-function pickAxis(
-	live: ScreenData | null | undefined,
-	demo: ScreenData,
-	structure: ScreenData,
-	ctx: FieldTracker
-) {
-	const useLiveAxis =
-		live?.axis?.length &&
-		(sum(live.trend?.weight || []) > 0 ||
-			sum(live.participateTrend || []) > 0 ||
-			sum(live.deliverTrend || []) > 0 ||
-			live.axis.length > 1);
-	if (useLiveAxis) {
-		ctx.markLive('axis');
-		return live!.axis;
-	}
-	ctx.markDemo('axis');
-	return demo.axis.length ? demo.axis : structure.axis;
-}
-
-/** 缁撴瀯 + 婕旂ず + 鐪熷疄鏁版嵁鎸夊瓧娈�/鍒嗙粍鍚堝苟 */
+/** 鐢ㄦ帴鍙g粨鏋滆鐩栭浂鍊肩粨鏋勩�傜己瀛楁淇濇寔 0 鎴栫┖鍒楄〃锛屼笉鍐嶅洖钀藉埌婕旂ず鏁版嵁銆� */
 export function resolveScreenData(live: ScreenData | null | undefined, range: TimeRange): ResolvedScreenData {
-	const structure = structureScreen(range);
-	const demo = demoScreen(range);
+	const base = structureScreen(range);
 	const ctx = new FieldTracker();
 
-	const axis = pickAxis(live, demo, structure, ctx);
-	const kpis = pickKpis(live?.kpis, demo.kpis, ctx);
-	const wasteMix = pickWasteMix(live?.wasteMix, demo.wasteMix, ctx);
-	const recycleItems = pickRecycleItems(live?.recycleItems, demo.recycleItems, ctx);
-	const trend = pickTrend('trend', live?.trend, demo.trend, axis, ctx);
-
-	let yearCarbon = demo.yearCarbon;
-	if (live?.yearCarbon?.length && sum(live.yearCarbon) > 0) {
-		yearCarbon = live.yearCarbon;
-		ctx.markLive('yearCarbon');
-	} else {
-		ctx.markDemo('yearCarbon');
+	if (!live) {
+		return {
+			data: base,
+			mode: 'live',
+			demoFields: [],
+			liveFields: []
+		};
 	}
 
-	const deptRank = pickArray('deptRank', live?.deptRank, demo.deptRank, ctx, hasRankData);
-	const classRank = pickArray('classRank', live?.classRank, demo.classRank, ctx, hasRankData);
-	const majorRank = pickArray('majorRank', live?.majorRank, demo.majorRank, ctx, hasRankData);
-	const pointFlow = pickPointFlow(live?.pointFlow, demo.pointFlow, ctx);
+	const axis = live.axis?.length ? live.axis : base.axis;
+	ctx.markLive('axis');
 
-	let participateTrend = alignSeries(demo.participateTrend, axis.length);
-	if (live?.participateTrend?.length && sum(live.participateTrend) > 0) {
-		participateTrend = alignSeries(live.participateTrend, axis.length);
-		ctx.markLive('participateTrend');
-	} else {
-		ctx.markDemo('participateTrend');
-	}
+	const kpis = { ...base.kpis };
+	Object.keys(kpis).forEach(key => {
+		if (live.kpis && live.kpis[key] != null) {
+			kpis[key] = num(live.kpis[key]);
+		}
+		ctx.markLive(`kpis.${key}`);
+	});
 
-	let deliverTrend = alignSeries(demo.deliverTrend, axis.length);
-	if (live?.deliverTrend?.length && sum(live.deliverTrend) > 0) {
-		deliverTrend = alignSeries(live.deliverTrend, axis.length);
-		ctx.markLive('deliverTrend');
-	} else {
-		ctx.markDemo('deliverTrend');
-	}
+	const wasteMix = live.wasteMix?.length
+		? live.wasteMix.map((item, i) => ({
+				...base.wasteMix[i],
+				...item,
+				weight: num(item.weight),
+				value: num(item.value),
+				count: num(item.count),
+				color: item.color || base.wasteMix[i]?.color
+			}))
+		: base.wasteMix;
+	ctx.markLive('wasteMix');
 
-	const ordersByType = pickOrdersByType(live?.ordersByType, demo.ordersByType, ctx);
-	const appointmentFlow = pickAppointmentFlow(live?.appointmentFlow, demo.appointmentFlow, ctx);
-	const sorterStatus = pickSorterStatus(live?.sorterStatus, demo.sorterStatus, ctx);
-	const vehicles = pickArray('vehicles', live?.vehicles, demo.vehicles, ctx, v => v.length > 0);
-	const sites = pickArray('sites', live?.sites, demo.sites, ctx, v => v.length > 0);
+	const recycleItems = live.recycleItems?.length
+		? live.recycleItems.map((item, i) => ({
+				...base.recycleItems[i],
+				...item,
+				weight: num(item.weight),
+				value: num(item.value),
+				carbon: num(item.carbon),
+				color: item.color || base.recycleItems[i]?.color
+			}))
+		: base.recycleItems;
+	ctx.markLive('recycleItems');
+
+	const trend = {
+		weight: alignSeries(live.trend?.weight, axis.length),
+		value: alignSeries(live.trend?.value, axis.length),
+		carbon: alignSeries(live.trend?.carbon, axis.length)
+	};
+	ctx.markLive('trend');
+
+	const yearCarbon = live.yearCarbon?.length
+		? alignSeries(live.yearCarbon, 12)
+		: base.yearCarbon;
+	ctx.markLive('yearCarbon');
+
+	const deptRank = Array.isArray(live.deptRank) ? live.deptRank : [];
+	const classRank = Array.isArray(live.classRank) ? live.classRank : [];
+	const majorRank = Array.isArray(live.majorRank) ? live.majorRank : [];
+	ctx.markLive('deptRank');
+	ctx.markLive('classRank');
+	ctx.markLive('majorRank');
+
+	const pointFlow = {
+		issued: num(live.pointFlow?.issued),
+		transfer: num(live.pointFlow?.transfer),
+		donate: num(live.pointFlow?.donate),
+		mall: num(live.pointFlow?.mall),
+		balance: num(live.pointFlow?.balance)
+	};
+	ctx.markLive('pointFlow');
+
+	const participateTrend = alignSeries(live.participateTrend, axis.length);
+	const deliverTrend = alignSeries(live.deliverTrend, axis.length);
+	ctx.markLive('participateTrend');
+	ctx.markLive('deliverTrend');
+
+	const ordersByType = live.ordersByType?.axis?.length
+		? {
+				axis: live.ordersByType.axis,
+				鐜板満鍥炴敹: alignSeries(live.ordersByType.鐜板満鍥炴敹, live.ordersByType.axis.length),
+				棰勭害涓婇棬: alignSeries(live.ordersByType.棰勭害涓婇棬, live.ordersByType.axis.length),
+				澶у睆鎶曢��: alignSeries(live.ordersByType.澶у睆鎶曢��, live.ordersByType.axis.length),
+				娲诲姩鍥炴敹: alignSeries(live.ordersByType.娲诲姩鍥炴敹, live.ordersByType.axis.length)
+			}
+		: base.ordersByType;
+	ctx.markLive('ordersByType');
+
+	const appointmentFlow = live.appointmentFlow?.length ? live.appointmentFlow : base.appointmentFlow;
+	const sorterStatus = live.sorterStatus?.length ? live.sorterStatus : base.sorterStatus;
+	const vehicles = Array.isArray(live.vehicles) ? live.vehicles : [];
+	const sites = Array.isArray(live.sites) ? live.sites : [];
+	ctx.markLive('appointmentFlow');
+	ctx.markLive('sorterStatus');
+	ctx.markLive('vehicles');
+	ctx.markLive('sites');
 
 	const data: ScreenData = {
 		range,
@@ -303,21 +145,20 @@
 		sorterStatus,
 		vehicles,
 		sites,
-		unavailable: live?.unavailable || []
+		unavailable: live.unavailable || []
 	};
 
 	return {
 		data,
 		mode: resolveDataMode(live, ctx),
-		demoFields: ctx.listDemo(),
+		demoFields: [],
 		liveFields: ctx.listLive()
 	};
 }
 
-/** 鎺ュ彛澶辫触鎴栨棤 live 鏁版嵁鏃剁殑鍒濆灞曠ず */
+/** 鎺ュ彛灏氭湭杩斿洖鏃跺厛灞曠ず闆跺�硷紝閬垮厤鍏堥棯鍑烘紨绀烘暟瀛� */
 export function initialScreenData(range: TimeRange = 'month') {
 	return resolveScreenData(null, range);
 }
 
 export { structureScreen, buildScreenAxis } from './screen-structure';
-export { demoScreen } from './screen-demo';
diff --git a/src/modules/report/hooks/use-screen.ts b/src/modules/report/hooks/use-screen.ts
index 0b7dc34..bababc0 100644
--- a/src/modules/report/hooks/use-screen.ts
+++ b/src/modules/report/hooks/use-screen.ts
@@ -103,7 +103,6 @@
 		} catch (e: any) {
 			applyResolved(resolveScreenData(null, sharedTimeRange.value));
 			const msg = e?.message || '';
-			// 棣栭〉闈欓粯闄嶇骇鍒版紨绀烘暟鎹紝涓嶅睍绀虹櫥褰�/鏉冮檺绫婚敊璇枃妗�
 			if (!/鐧诲綍澶辨晥|鏃犳潈闄�/.test(msg)) {
 				loadError.value = msg || '椹鹃┒鑸辨帴鍙h皟鐢ㄥけ璐�';
 			}
@@ -124,7 +123,7 @@
 	return { load, loading, loadError, dataMode };
 }
 
-/** 棣栭〉涓嶅睍绀恒�屾紨绀恒�嶆爣璇嗭紝搴曞眰浠嶅彲闈欓粯浣跨敤婕旂ず鏁版嵁濉厖 */
+/** 棣栭〉鍙睍绀烘帴鍙f眹鎬荤粨鏋滐紝涓嶅啀鐢ㄦ紨绀烘暟鎹~鍏� */
 export function useScreenFieldSource() {
 	const { dataMode, demoFields } = useScreenData();
 
diff --git a/src/modules/report/views/dailydepartment.vue b/src/modules/report/views/dailydepartment.vue
index 3fcedab..77a879a 100644
--- a/src/modules/report/views/dailydepartment.vue
+++ b/src/modules/report/views/dailydepartment.vue
@@ -8,6 +8,9 @@
 			<!-- 鍒犻櫎鎸夐挳 -->
 			<!--cl-multi-delete-btn /-->
 			<cl-flex1 />
+			<cl-filter label="鏃ユ湡">
+				<cl-date-picker type="daterange" />
+			</cl-filter>
 			<!-- 鏉′欢鎼滅储 -->
 			<cl-search ref="Search" />
 		</cl-row>
@@ -504,7 +507,15 @@
 });
 
 // cl-search
-const Search = useSearch();
+function hideDateSearch({ exposed }: { exposed: any }) {
+	const items = exposed.config?.items as { prop?: string }[] | undefined;
+	const index = items?.findIndex(e => e.prop === "date") ?? -1;
+	if (index >= 0) items?.splice(index, 1);
+}
+
+const Search = useSearch({
+	plugins: [hideDateSearch]
+});
 
 // cl-crud
 const Crud = useCrud(
diff --git a/src/modules/report/views/dailysite.vue b/src/modules/report/views/dailysite.vue
index 19a0d3b..424856a 100644
--- a/src/modules/report/views/dailysite.vue
+++ b/src/modules/report/views/dailysite.vue
@@ -8,6 +8,9 @@
 			<!-- 鍒犻櫎鎸夐挳 -->
 			<!--cl-multi-delete-btn /-->
 			<cl-flex1 />
+			<cl-filter label="鏃ユ湡">
+				<cl-date-picker type="daterange" />
+			</cl-filter>
 			<!-- 鏉′欢鎼滅储 -->
 			<cl-search ref="Search" />
 		</cl-row>
@@ -508,7 +511,15 @@
 });
 
 // cl-search
-const Search = useSearch();
+function hideDateSearch({ exposed }: { exposed: any }) {
+	const items = exposed.config?.items as { prop?: string }[] | undefined;
+	const index = items?.findIndex(e => e.prop === "date") ?? -1;
+	if (index >= 0) items?.splice(index, 1);
+}
+
+const Search = useSearch({
+	plugins: [hideDateSearch]
+});
 
 // cl-crud
 const Crud = useCrud(
diff --git a/src/modules/report/views/dailyvehicle.vue b/src/modules/report/views/dailyvehicle.vue
index 063aefd..fe6c49e 100644
--- a/src/modules/report/views/dailyvehicle.vue
+++ b/src/modules/report/views/dailyvehicle.vue
@@ -8,6 +8,9 @@
 			<!-- 鍒犻櫎鎸夐挳 -->
 			<!--cl-multi-delete-btn /-->
 			<cl-flex1 />
+			<cl-filter label="鏁版嵁鏃ユ湡">
+				<cl-date-picker type="daterange" />
+			</cl-filter>
 			<!-- 鏉′欢鎼滅储 -->
 			<cl-search ref="Search" />
 		</cl-row>
@@ -262,7 +265,15 @@
 });
 
 // cl-search
-const Search = useSearch();
+function hideDateSearch({ exposed }: { exposed: any }) {
+	const items = exposed.config?.items as { prop?: string }[] | undefined;
+	const index = items?.findIndex(e => e.prop === "date") ?? -1;
+	if (index >= 0) items?.splice(index, 1);
+}
+
+const Search = useSearch({
+	plugins: [hideDateSearch]
+});
 
 // cl-crud
 const Crud = useCrud(
diff --git a/src/modules/report/views/joblog.vue b/src/modules/report/views/joblog.vue
index 15d4d1b..70be24a 100644
--- a/src/modules/report/views/joblog.vue
+++ b/src/modules/report/views/joblog.vue
@@ -8,6 +8,9 @@
 			<!-- 鍒犻櫎鎸夐挳 -->
 			<!--cl-multi-delete-btn /-->
 			<cl-flex1 />
+			<cl-filter label="鏃ユ湡">
+				<cl-date-picker type="daterange" />
+			</cl-filter>
 			<!-- 鏉′欢鎼滅储 -->
 			<cl-search ref="Search" />
 		</cl-row>
@@ -173,7 +176,15 @@
 });
 
 // cl-search
-const Search = useSearch();
+function hideDateSearch({ exposed }: { exposed: any }) {
+	const items = exposed.config?.items as { prop?: string }[] | undefined;
+	const index = items?.findIndex(e => e.prop === "dataDate") ?? -1;
+	if (index >= 0) items?.splice(index, 1);
+}
+
+const Search = useSearch({
+	plugins: [hideDateSearch]
+});
 
 // cl-crud
 const Crud = useCrud(
diff --git a/src/modules/report/views/screen/carbon.vue b/src/modules/report/views/screen/carbon.vue
index afd9980..3121ae7 100644
--- a/src/modules/report/views/screen/carbon.vue
+++ b/src/modules/report/views/screen/carbon.vue
@@ -1,6 +1,6 @@
 <template>
 	<div class="screen-page">
-		<div class="kpi-grid" style="--kpi-cols: 6">
+		<div class="kpi-grid" style="--kpi-cols: 5">
 			<kpi-card v-for="item in kpis" :key="item.label" v-bind="item" />
 		</div>
 
@@ -45,16 +45,20 @@
 import { computed } from 'vue';
 import KpiCard from '../../components/kpi-card.vue';
 import PanelCard from '../../components/panel-card.vue';
+import { TIME_RANGE_OPTIONS } from '../../data/screen';
 import { useChartTheme, useScreenData, useScreenFieldSource } from '../../hooks/use-screen';
 
 const { data, timeRange } = useScreenData();
 const { isDemoSection } = useScreenFieldSource();
 const { textColor, mutedColor, splitColor, primary, baseGrid, axisStyle, tooltip } = useChartTheme();
 
+const rangeLabel = computed(
+	() => TIME_RANGE_OPTIONS.find(e => e.value === timeRange.value)?.label || '鏈尯闂�'
+);
+
 const kpis = computed(() => [
 	{ field: 'kpis.totalCarbonTon', label: '绱纰冲噺鎺�', value: data.value.kpis.totalCarbonTon, suffix: '鍚–O鈧俥', fixed: 1 },
-	{ field: 'kpis.yearCarbonTon', label: '鏈勾搴︾⒊鍑忔帓', value: data.value.kpis.yearCarbonTon, suffix: '鍚–O鈧俥', fixed: 1 },
-	{ field: 'kpis.periodCarbonTon', label: '鏈尯闂寸⒊鍑忔帓', value: data.value.kpis.periodCarbonTon, suffix: '鍚–O鈧俥', fixed: 2 },
+	{ field: 'kpis.periodCarbonTon', label: `${rangeLabel.value}纰冲噺鎺抈, value: data.value.kpis.periodCarbonTon, suffix: '鍚–O鈧俥', fixed: 2 },
 	{ field: 'kpis.totalWeightTon', label: '绱璧勬簮鍥炴敹', value: data.value.kpis.totalWeightTon, suffix: '鍚�', fixed: 1 },
 	{ field: 'kpis.totalParticipants', label: '绱鍙備笌甯堢敓', value: data.value.kpis.totalParticipants, suffix: '浜�', fixed: 0 },
 	{ field: 'kpis.greenActions', label: '绱鐜繚琛屼负', value: data.value.kpis.greenActions, suffix: '娆�', fixed: 0 }
diff --git a/src/modules/report/views/screen/map.vue b/src/modules/report/views/screen/map.vue
index cd966e8..9d6faaa 100755
--- a/src/modules/report/views/screen/map.vue
+++ b/src/modules/report/views/screen/map.vue
@@ -1,7 +1,7 @@
 <template>
 	<div class="screen-page dash">
 		<!-- KPI 鎸囨爣鏉� -->
-		<div class="kpi-grid" style="--kpi-cols: 8">
+		<div class="kpi-grid" style="--kpi-cols: 7">
 			<kpi-card v-for="item in kpis" :key="item.label" v-bind="item" />
 		</div>
 
@@ -57,7 +57,7 @@
 					<template #extra>
 						<cl-select-button v-model="itemMetric" :options="itemOptions" small />
 					</template>
-					<v-chart :option="itemOption" autoresize class="chart" />
+					<v-chart :option="itemOption" autoresize class="chart chart--items" />
 				</panel-card>
 			</div>
 		</div>
@@ -84,9 +84,10 @@
 import PanelCard from '../../components/panel-card.vue';
 import ScreenMapPanel from '../../components/screen-map-panel.vue';
 import CameraMonitor from '../../../monitor/views/cameras.vue';
+import { TIME_RANGE_OPTIONS } from '../../data/screen';
 import { useChartTheme, useScreenData, useScreenFieldSource } from '../../hooks/use-screen';
 
-const { data } = useScreenData();
+const { data, timeRange } = useScreenData();
 const { isDemoSection } = useScreenFieldSource();
 const { textColor, mutedColor, splitColor, primary, baseGrid, axisStyle, tooltip } = useChartTheme();
 
@@ -112,10 +113,13 @@
 	{ label: '纰冲噺鎺�', value: 'carbon' }
 ];
 
-/** 椤堕儴 KPI锛氬彇鎬昏鍩熸牳蹇� 8 椤� */
+const rangeLabel = computed(
+	() => TIME_RANGE_OPTIONS.find(e => e.value === timeRange.value)?.label || '鏈尯闂�'
+);
+
+/** 椤堕儴 KPI */
 const kpis = computed(() => [
-	{ field: 'kpis.todayWeightKg', label: '浠婃棩鍥炴敹閲�', value: data.value.kpis.todayWeightKg, suffix: 'kg', fixed: 1 },
-	{ field: 'kpis.periodWeightTon', label: '鏈尯闂村洖鏀堕噺', value: data.value.kpis.periodWeightTon, suffix: '鍚�', fixed: 2 },
+	{ field: 'kpis.periodWeightTon', label: `${rangeLabel.value}鍥炴敹閲廯, value: data.value.kpis.periodWeightTon, suffix: '鍚�', fixed: 2 },
 	{ field: 'kpis.resourceRate', label: '璧勬簮鍖栫巼', value: data.value.kpis.resourceRate, suffix: '%', fixed: 1 },
 	{ field: 'kpis.periodValue', label: '鍥炴敹浠峰��', value: data.value.kpis.periodValue, suffix: '鍏�', fixed: 0 },
 	{ field: 'kpis.periodCarbonTon', label: '纰冲噺鎺�', value: data.value.kpis.periodCarbonTon, suffix: '鍚–O鈧俥', fixed: 2 },
@@ -353,4 +357,65 @@
 		}
 	}
 }
+
+@media (max-width: 768px) {
+	.dash,
+	.dash-body,
+	.dash-col {
+		height: auto;
+		min-height: 0;
+		overflow: visible;
+		flex: none;
+	}
+
+	.dash :deep(.kpi-grid) {
+		grid-template-columns: repeat(2, minmax(0, 1fr));
+	}
+
+	.dash-card--flex {
+		flex: none;
+		height: auto;
+		min-height: 0;
+		overflow: visible;
+
+		:deep(.panel-card) {
+			height: auto;
+			min-height: 0;
+			overflow: visible;
+		}
+
+		:deep(.panel-card__body) {
+			flex: none;
+			height: auto;
+			min-height: 0;
+			overflow: visible;
+		}
+	}
+
+	/* 鍦板浘鐢诲竷灞傜骇寰堥珮锛岄檺鍒跺湪涓爮鍐咃紝閬垮厤鐩栦綇涓婁笅涓ゅ潡鍥捐〃 */
+	.dash-card--map {
+		position: relative;
+		z-index: 0;
+		flex: none;
+		height: 480px;
+		min-height: 480px;
+		overflow: hidden;
+	}
+
+	.dash .chart {
+		display: block;
+		height: 260px;
+		min-height: 260px;
+	}
+
+	.dash .chart--items {
+		height: 420px;
+		min-height: 420px;
+	}
+
+	.dash-foot {
+		height: auto;
+		min-height: 300px;
+	}
+}
 </style>
\ No newline at end of file
diff --git a/src/modules/report/views/screen/operation.vue b/src/modules/report/views/screen/operation.vue
index a75df31..dc5a267 100644
--- a/src/modules/report/views/screen/operation.vue
+++ b/src/modules/report/views/screen/operation.vue
@@ -323,4 +323,15 @@
 	}
 
 }
+
+@media (max-width: 768px) {
+	.screen-page .table {
+		overflow-x: auto;
+	}
+
+	.screen-page .table--vehicle :deep(.table__head),
+	.screen-page .table--vehicle .table__row {
+		min-width: 520px;
+	}
+}
 </style>
diff --git a/src/modules/report/views/screen/overview.vue b/src/modules/report/views/screen/overview.vue
index c35bd22..8dba391 100644
--- a/src/modules/report/views/screen/overview.vue
+++ b/src/modules/report/views/screen/overview.vue
@@ -1,6 +1,6 @@
 <template>
 	<div class="screen-page">
-		<div class="kpi-grid" style="--kpi-cols: 8">
+		<div class="kpi-grid" style="--kpi-cols: 7">
 			<kpi-card v-for="item in kpis" :key="item.label" v-bind="item" />
 		</div>
 
@@ -51,6 +51,7 @@
 import KpiCard from '../../components/kpi-card.vue';
 import PanelCard from '../../components/panel-card.vue';
 import ScreenCameraPreview from '../../components/screen-camera-preview.vue';
+import { TIME_RANGE_OPTIONS } from '../../data/screen';
 import { useChartTheme, useScreenData, useScreenFieldSource } from '../../hooks/use-screen';
 
 const { data, timeRange } = useScreenData();
@@ -72,9 +73,12 @@
 	{ label: '纰冲噺鎺�', value: 'carbon' }
 ];
 
+const rangeLabel = computed(
+	() => TIME_RANGE_OPTIONS.find(e => e.value === timeRange.value)?.label || '鏈尯闂�'
+);
+
 const kpis = computed(() => [
-	{ field: 'kpis.todayWeightKg', label: '浠婃棩鍥炴敹閲�', value: data.value.kpis.todayWeightKg, suffix: 'kg', fixed: 1, hint: '瀹炴椂绉伴噸姹囨��' },
-	{ field: 'kpis.periodWeightTon', label: '鏈尯闂村洖鏀堕噺', value: data.value.kpis.periodWeightTon, suffix: '鍚�', fixed: 2, hint: '褰撳墠鏃堕棿鑼冨洿' },
+	{ field: 'kpis.periodWeightTon', label: `${rangeLabel.value}鍥炴敹閲廯, value: data.value.kpis.periodWeightTon, suffix: '鍚�', fixed: 2, hint: '褰撳墠鏃堕棿鑼冨洿' },
 	{ field: 'kpis.resourceRate', label: '璧勬簮鍖栫巼', value: data.value.kpis.resourceRate, suffix: '%', fixed: 1, hint: '鍙洖鏀剁墿閲嶉噺/鎬婚噸閲忥紝涓嶅惈澶т欢' },
 	{ field: 'kpis.periodValue', label: '鍥炴敹浠峰��', value: data.value.kpis.periodValue, suffix: '鍏�', fixed: 0, hint: '鏃ヨ〃浼扮畻閿�鍞' },
 	{ field: 'kpis.periodCarbonTon', label: '纰冲噺鎺�', value: data.value.kpis.periodCarbonTon, suffix: '鍚–O鈧俥', fixed: 2, hint: '鍒嗙被閲嶉噺 脳 纰冲噺鍥犳暟' },
diff --git a/src/modules/report/views/screen/participation.vue b/src/modules/report/views/screen/participation.vue
index 5b839c9..270153c 100644
--- a/src/modules/report/views/screen/participation.vue
+++ b/src/modules/report/views/screen/participation.vue
@@ -69,10 +69,10 @@
 import { computed, ref } from 'vue';
 import KpiCard from '../../components/kpi-card.vue';
 import PanelCard from '../../components/panel-card.vue';
-import { type RankMetric } from '../../data/screen';
+import { TIME_RANGE_OPTIONS, type RankMetric } from '../../data/screen';
 import { useChartTheme, useScreenData, useScreenFieldSource } from '../../hooks/use-screen';
 
-const { data } = useScreenData();
+const { data, timeRange } = useScreenData();
 const { isDemoSection } = useScreenFieldSource();
 const { textColor, mutedColor, splitColor, primary, baseGrid, axisStyle, tooltip } = useChartTheme();
 
@@ -99,10 +99,14 @@
 	{ label: '纰冲噺鎺�', value: 'carbon' }
 ];
 
+const rangeLabel = computed(
+	() => TIME_RANGE_OPTIONS.find(e => e.value === timeRange.value)?.label || '鏈尯闂�'
+);
+
 const kpis = computed(() => [
 	{ field: 'kpis.totalParticipants', label: '鍙備笌甯堢敓', value: data.value.kpis.totalParticipants, suffix: '浜�', fixed: 0 },
 	{ field: 'kpis.participateRate', label: '鍙備笌鐜�', value: data.value.kpis.participateRate, suffix: '%', fixed: 1, hint: '鍙備笌浜烘暟 / 鍙傛暟 student.count.{閮ㄩ棬ID}' },
-	{ field: 'kpis.newParticipants', label: '鏈尯闂存柊澧炲弬涓�', value: data.value.kpis.newParticipants, suffix: '浜�', fixed: 0, hint: '鍖洪棿鍐呭嚭鐜拌繃鐨勫幓閲嶇敤鎴�' },
+	{ field: 'kpis.newParticipants', label: `${rangeLabel.value}鏂板鍙備笌`, value: data.value.kpis.newParticipants, suffix: '浜�', fixed: 0, hint: '鍖洪棿鍐呭嚭鐜拌繃鐨勫幓閲嶇敤鎴�' },
 	{ field: 'kpis.pointsIssued', label: '纰崇Н鍒嗗彂鏀�', value: data.value.kpis.pointsIssued, suffix: '鍒�', fixed: 0 },
 	{ field: 'kpis.pointsConsumed', label: '纰崇Н鍒嗘秷璐�', value: data.value.kpis.pointsConsumed, suffix: '鍒�', fixed: 0 },
 	{ field: 'kpis.pointsBalance', label: '纰崇Н鍒嗕綑棰�', value: data.value.kpis.pointsBalance, suffix: '鍒�', fixed: 0 }
@@ -252,4 +256,10 @@
 		}
 	}
 }
+
+@media (max-width: 576px) {
+	.screen-page .flow__split {
+		grid-template-columns: 1fr;
+	}
+}
 </style>
diff --git a/src/plugins/view/components/group.vue b/src/plugins/view/components/group.vue
index c9991f6..7290912 100644
--- a/src/plugins/view/components/group.vue
+++ b/src/plugins/view/components/group.vue
@@ -680,6 +680,12 @@
 	}
 
 	@media only screen and (max-width: 768px) {
+		.cl-view-group__right .head .title {
+			max-width: calc(100% - 88px);
+			text-overflow: ellipsis;
+			padding: 0 8px;
+		}
+
 		.cl-view-group__left {
 			overflow: hidden;
 			transition: width 0.2s;

--
Gitblit v1.9.1