From 1c3ae7bcad167c9a593f4634968a6e142d9b08f9 Mon Sep 17 00:00:00 2001
From: wangzhibo <wangzhibo@shsening.com>
Date: 星期四, 13 八月 2026 12:18:30 +0800
Subject: [PATCH] add 预约和回收订单

---
 src/modules/shop/locales/en.json                  |   71 +
 大件垃圾回收.txt                                        |   90 +
 .gitignore                                        |    3 
 src/modules/monitor/views/device.vue              |  108 +
 src/modules/basicdata/locales/zh-tw.json          |    7 
 src/modules/shop/views/recycleitem.vue            |  186 +++
 src/modules/shop/components/item-select.vue       |   36 
 src/modules/shop/locales/zh-tw.json               |   71 +
 src/modules/shop/views/recycleorderitem.vue       |  171 +++
 src/modules/monitor/components/site-tree.vue      |    5 
 src/modules/basicdata/locales/zh-cn.json          |    7 
 src/modules/ezuikit/components/ezuikit-player.vue |   19 
 src/modules/basicdata/locales/en.json             |    7 
 src/modules/maps/locales/en.json                  |   71 +
 src/modules/basicdata/views/sorter.vue            |   64 +
 src/modules/maps/locales/zh-tw.json               |   71 +
 src/modules/shop/views/transaction.vue            |  173 +++
 src/modules/monitor/views/capturetask.vue         |  183 +++
 src/modules/user/views/list.vue                   |  122 ++
 src/modules/basicdata/views/site.vue              |  239 +++-
 README.md                                         |    4 
 src/modules/maps/locales/zh-cn.json               |   71 +
 src/modules/shop/views/appointment.vue            |  282 +++++
 src/modules/shop/components/order-select.vue      |   66 +
 src/modules/shop/locales/zh-cn.json               |  117 ++
 src/modules/shop/components/order-detail.vue      |  303 ++++++
 src/modules/shop/views/recycleorder.vue           |  240 ++++
 src/modules/maps/views/layers.vue                 |   84 +
 src/modules/base/pages/main/components/slider.vue |    4 
 src/modules/user/locales/zh-cn.json               |   12 
 30 files changed, 2,735 insertions(+), 152 deletions(-)

diff --git a/.gitignore b/.gitignore
index f8755f8..797816c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,7 +2,8 @@
 node_modules/
 /dist/
 dist-ssr/
-build/
+build/cool/eps.d.ts
+build/cool/eps.json
 
 # Log files
 npm-debug.log*
diff --git a/README.md b/README.md
index d798c4f..0aa7b65 100644
--- a/README.md
+++ b/README.md
@@ -2,3 +2,7 @@
 
 婧愭棤蹇ф棤搴熸牎鍥�-鍓嶇
 
+
+浣跨敤缁勪欢 https://lvzhao1995.github.io/vue-tianditu/  闈� vue-tianditu
+
+pnpm add vue-tianditu2 
diff --git a/src/modules/base/pages/main/components/slider.vue b/src/modules/base/pages/main/components/slider.vue
index 635262c..7c97aa5 100644
--- a/src/modules/base/pages/main/components/slider.vue
+++ b/src/modules/base/pages/main/components/slider.vue
@@ -65,8 +65,8 @@
 		user-select: none;
 
 		img {
-			height: 24px;
-			width: 24px;
+			height: 40px;
+			width: 48px;
 		}
 
 		span {
diff --git a/src/modules/basicdata/locales/en.json b/src/modules/basicdata/locales/en.json
index 1a38a0c..d6d56c7 100644
--- a/src/modules/basicdata/locales/en.json
+++ b/src/modules/basicdata/locales/en.json
@@ -45,7 +45,14 @@
 "韬唤璇佸彿":"韬唤璇佸彿",
 "濮撳悕":"濮撳悕",
 "鎵嬫満鍙�":"鎵嬫満鍙�",
+"瀵嗙爜":"瀵嗙爜",
+"鎬у埆":"鎬у埆",
+"鐢�":"鐢�",
+"濂�":"濂�",
+"鏈煡":"鏈煡",
+"鐢熸棩":"鐢熸棩",
 "鍏ヨ亴鏃ユ湡":"鍏ヨ亴鏃ユ湡",
+"绂昏亴鏃ユ湡":"绂昏亴鏃ユ湡",
 "宸ヤ綔绔欑偣":"宸ヤ綔绔欑偣",
 "韬唤璇佹闈㈢収":"韬唤璇佹闈㈢収",
 "韬唤璇佸弽闈㈢収":"韬唤璇佸弽闈㈢収",
diff --git a/src/modules/basicdata/locales/zh-cn.json b/src/modules/basicdata/locales/zh-cn.json
index 1a38a0c..d6d56c7 100644
--- a/src/modules/basicdata/locales/zh-cn.json
+++ b/src/modules/basicdata/locales/zh-cn.json
@@ -45,7 +45,14 @@
 "韬唤璇佸彿":"韬唤璇佸彿",
 "濮撳悕":"濮撳悕",
 "鎵嬫満鍙�":"鎵嬫満鍙�",
+"瀵嗙爜":"瀵嗙爜",
+"鎬у埆":"鎬у埆",
+"鐢�":"鐢�",
+"濂�":"濂�",
+"鏈煡":"鏈煡",
+"鐢熸棩":"鐢熸棩",
 "鍏ヨ亴鏃ユ湡":"鍏ヨ亴鏃ユ湡",
+"绂昏亴鏃ユ湡":"绂昏亴鏃ユ湡",
 "宸ヤ綔绔欑偣":"宸ヤ綔绔欑偣",
 "韬唤璇佹闈㈢収":"韬唤璇佹闈㈢収",
 "韬唤璇佸弽闈㈢収":"韬唤璇佸弽闈㈢収",
diff --git a/src/modules/basicdata/locales/zh-tw.json b/src/modules/basicdata/locales/zh-tw.json
index 1a38a0c..d6d56c7 100644
--- a/src/modules/basicdata/locales/zh-tw.json
+++ b/src/modules/basicdata/locales/zh-tw.json
@@ -45,7 +45,14 @@
 "韬唤璇佸彿":"韬唤璇佸彿",
 "濮撳悕":"濮撳悕",
 "鎵嬫満鍙�":"鎵嬫満鍙�",
+"瀵嗙爜":"瀵嗙爜",
+"鎬у埆":"鎬у埆",
+"鐢�":"鐢�",
+"濂�":"濂�",
+"鏈煡":"鏈煡",
+"鐢熸棩":"鐢熸棩",
 "鍏ヨ亴鏃ユ湡":"鍏ヨ亴鏃ユ湡",
+"绂昏亴鏃ユ湡":"绂昏亴鏃ユ湡",
 "宸ヤ綔绔欑偣":"宸ヤ綔绔欑偣",
 "韬唤璇佹闈㈢収":"韬唤璇佹闈㈢収",
 "韬唤璇佸弽闈㈢収":"韬唤璇佸弽闈㈢収",
diff --git a/src/modules/basicdata/views/site.vue b/src/modules/basicdata/views/site.vue
index a326b25..fc1a3ca 100644
--- a/src/modules/basicdata/views/site.vue
+++ b/src/modules/basicdata/views/site.vue
@@ -53,94 +53,104 @@
 
 // cl-upsert
 const Upsert = useUpsert({
-	items: [
-		{
-			label: t("绔欑偣缂栫爜"),
-			prop: "businessId",
-			component: { name: "el-input", props: { clearable: true } },
-			span: 12,
-			required: true,
-		},
-		{
-			label: t("鍚嶇О"),
-			prop: "businessName",
-			component: { name: "el-input", props: { clearable: true } },
-			span: 12,
-			required: true,
-		},
-		{
-			label: t("閫夋嫨閮ㄩ棬"),
-			prop: "departmentId",
-			//component: { name: "el-input", props: { clearable: true } },
-			component: {
-				name: 'cl-dept-select',
-				props: {
-					multiple: false,      // 鍗曢�� / true 澶氶��
-					placeholder: "璇烽�夋嫨閮ㄩ棬",
-					checkStrictly: true,  // 鐖跺瓙鑺傜偣涓嶈仈鍔�
-					immediate: true       // 绔嬪嵆鍔犺浇閮ㄩ棬鏍�
-				}
-			},
-			required: true,
-		},
-		{
-			label: t("绔欑偣绫诲瀷"),
-			prop: "siteType",
-			component: {
-				name: "cl-select",
-				props: { options: dict.get("sitetype") },
-			},
-			value: "鏀堕泦鐐�",
-			span: 12,
-			required: true,
-		},
-		{
-			label: t("鍦板潃"),
-			prop: "address",
-			component: { name: "el-input", props: { clearable: true } },
-			span: 12,
-			required: true,
-		},
-		{
-			label: t("璐熻矗浜�"),
-			prop: "leader",
-			component: { name: "el-input", props: { clearable: true } },
-			span: 12,
-			required: true,
-		},
-		{
-			label: t("缁忓害"),
-			prop: "longitude",
-			hook: "number",
-			component: { name: "el-input-number", props: { min: 0 } },
-			span: 12,
-			required: true,
-		},
-		{
-			label: t("绾害"),
-			prop: "latitude",
-			hook: "number",
-			component: { name: "el-input-number", props: { min: 0 } },
-			span: 12,
-			required: true,
-		},
-		{ label: t("鐓х墖"), prop: "photo", component: { name: "cl-upload" } },
-		{
-			label: t("鏄惁鍚敤"),
-			prop: "status",
-			component: { name: "el-radio-group", options: options.status },
-			value: 1,
-			required: true,
-		},
-		{
-			label: t("澶囨敞"),
-			prop: "remark",
-			component: {
-				name: "el-input",
-				props: { type: "textarea", rows: 4 },
-			},
-		},
-	],
+  onOpen: (mode, data) => {
+    if (mode === 'update' && data?.departmentId) {
+      data.departmentId = Number(data.departmentId);
+    }
+    return data;
+  },
+
+  items: [
+    {
+      label: t("绔欑偣缂栫爜"),
+      prop: "businessId",
+      component: { name: "el-input", props: { clearable: true } },
+      span: 12,
+      required: true,
+    },
+    {
+      label: t("鍚嶇О"),
+      prop: "businessName",
+      component: { name: "el-input", props: { clearable: true } },
+      span: 12,
+      required: true,
+    },
+    {
+      label: t("閫夋嫨閮ㄩ棬"),
+      prop: "departmentId",
+      component: {
+        name: 'cl-dept-select',
+        props: {
+          multiple: false,
+          placeholder: "璇烽�夋嫨閮ㄩ棬",
+          checkStrictly: true,
+          immediate: true,
+          clearable: true,
+          defaultExpandAll: true,
+        },
+      },
+      value: undefined,
+      span: 12,
+      required: true,
+    },
+    {
+      label: t("绔欑偣绫诲瀷"),
+      prop: "siteType",
+      component: {
+        name: "cl-select",
+        props: { options: dict.get("sitetype") },
+      },
+      value: "鏀堕泦鐐�",
+      span: 12,
+      required: true,
+    },
+    {
+      label: t("鍦板潃"),
+      prop: "address",
+      component: { name: "el-input", props: { clearable: true } },
+      span: 12,
+      required: true,
+    },
+    {
+      label: t("璐熻矗浜�"),
+      prop: "leader",
+      component: { name: "el-input", props: { clearable: true } },
+      span: 12,
+      required: true,
+    },
+    {
+      label: t("缁忓害"),
+      prop: "longitude",
+      hook: "number",
+      component: { name: "el-input-number", props: { min: 0 } },
+      span: 12,
+      required: true,
+    },
+    {
+      label: t("绾害"),
+      prop: "latitude",
+      hook: "number",
+      component: { name: "el-input-number", props: { min: 0 } },
+      span: 12,
+      required: true,
+    },
+    { label: t("鐓х墖"), prop: "photo", component: { name: "cl-upload" } },
+    {
+      label: t("鏄惁鍚敤"),
+      prop: "status",
+      component: { name: "el-radio-group", options: options.status },
+      value: 1,
+      required: true,
+    },
+    {
+      label: t("澶囨敞"),
+      prop: "remark",
+      component: {
+        name: "el-input",
+        props: { type: "textarea", rows: 4 },
+      },
+    },
+  ],
 });
 
 // cl-table
@@ -208,7 +218,58 @@
 });
 
 // cl-search
-const Search = useSearch();
+const Search = useSearch({
+  items: [
+    {
+      label: t('褰掑睘鍗曚綅'),
+      prop: 'departmentId',
+      component: {
+        name: 'cl-dept-select',
+        props: {
+          placeholder: t('璇烽�夋嫨褰掑睘鍗曚綅'),
+          checkStrictly: true,   // 鐖跺瓙涓嶈仈鍔�
+          immediate: true,       // 鎸夊綋鍓嶇敤鎴锋潈闄愬姞杞�
+          clearable: true,
+        },
+      },
+    },
+    {
+      label: t('绔欑偣绫诲瀷'),
+      prop: 'siteType',
+      component: {
+        name: 'cl-select',
+        props: {
+          options: dict.get('sitetype'),
+          placeholder: t('璇烽�夋嫨绔欑偣绫诲瀷'),
+          clearable: true,
+        },
+      },
+    },
+    {
+      label: t('鐘舵��'),
+      prop: 'status',
+      component: {
+        name: 'cl-select',
+        props: {
+          options: options.status,
+          placeholder: t('璇烽�夋嫨鐘舵��'),
+          clearable: true,
+        },
+      },
+    },
+    {
+      label: t('鍏抽敭瀛�'),
+      prop: 'keyWord',
+      component: {
+        name: 'el-input',
+        props: {
+          placeholder: t('绔欑偣缂栫爜 / 鍚嶇О / 鍦板潃 / 璐熻矗浜�'),
+          clearable: true,
+        },
+      },
+    },
+  ],
+});
 
 // cl-crud
 const Crud = useCrud(
diff --git a/src/modules/basicdata/views/sorter.vue b/src/modules/basicdata/views/sorter.vue
index d45abeb..872fe81 100644
--- a/src/modules/basicdata/views/sorter.vue
+++ b/src/modules/basicdata/views/sorter.vue
@@ -45,12 +45,18 @@
 const workSiteOptions = ref<{ label: string; value: any }[]>([]);
 
 // 閫夐」
+
 const options = reactive({
-	status: [
-		{ label: t("鍦ㄨ亴"), value: 0 },
-		{ label: t("绂昏亴"), value: 1 },
-		{ label: t("绂佺敤"), value: 2 },
+	sex: [
+		{ label: t('鏈煡'), value: '鏈煡' },
+		{ label: t('鐢�'), value: '鐢�' },
+		{ label: t('濂�'), value: '濂�' }
 	],
+	status: [
+		{ label: t('鍦ㄨ亴'), value: 0 },
+		{ label: t('绂昏亴'), value: 1 },
+		{ label: t('绂佺敤'), value: 2 }
+	]
 });
 
 // cl-upsert
@@ -70,15 +76,50 @@
 			span: 12,
 			required: true,
 		},
+
 		{
-			label: t("鎵嬫満鍙�"),
+			label: t('鎵嬫満鍙�'),
 			prop: "phone",
 			component: { name: "el-input", props: { clearable: true } },
 			span: 12,
+			required: true
+		},
+		{
+			label: t('瀵嗙爜'),
+			prop: "password",
+			component: { name: "el-input", props: { clearable: true } },
+			span: 12,
+			required: true
+
+		},
+		{
+			label: t('鎬у埆'),
+			prop: "sex",
+			component: { name: "el-radio-group", options: options.sex },
+			value: 0,
+			required: true
+		},
+		{
+			label: t('鐢熸棩'),
+			prop: "birthday",
+			component: {
+				name: "el-date-picker",
+				props: { type: "date", valueFormat: "YYYY-MM-DD" }
+			},
+			span: 12
 		},
 		{
 			label: t("鍏ヨ亴鏃ユ湡"),
 			prop: "hireDate",
+			component: {
+				name: "el-date-picker",
+				props: { type: "date", valueFormat: "YYYY-MM-DD" },
+			},
+			span: 12,
+		},
+		{
+			label: t("绂昏亴鏃ユ湡"),
+			prop: "leaveDate",
 			component: {
 				name: "el-date-picker",
 				props: { type: "date", valueFormat: "YYYY-MM-DD" },
@@ -149,12 +190,24 @@
 const Table = useTable({
 	columns: [
 		{ type: "selection" },
+		{ label: t("鎵�灞炲崟浣�"), prop: "departmentName", minWidth: 120 },
 		{ label: t("韬唤璇佸彿"), prop: "businessId", minWidth: 140 },
 		{ label: t("濮撳悕"), prop: "businessName", minWidth: 140 },
 		{ label: t("鎵嬫満鍙�"), prop: "phone", minWidth: 140 },
+		{ label: t("鎬у埆"), prop: "sex", minWidth: 140 },
 		{
 			label: t("鍏ヨ亴鏃ユ湡"),
 			prop: "hireDate",
+			minWidth: 140,
+			sortable: "custom",
+			component: {
+				name: "cl-date-text",
+				props: { format: "YYYY-MM-DD" },
+			},
+		},
+		{
+			label: t("绂昏亴鏃ユ湡"),
+			prop: "leaveDate",
 			minWidth: 140,
 			sortable: "custom",
 			component: {
@@ -168,7 +221,6 @@
 			minWidth: 120,
 			dict: options.status,
 		},
-		{ label: t("鎵�灞炲崟浣�"), prop: "departmentName", minWidth: 120 },
 		{ label: t("宸ヤ綔绔欑偣"), prop: "workSiteName", minWidth: 120 },
 		{
 			label: t("韬唤璇佹闈㈢収"),
diff --git a/src/modules/ezuikit/components/ezuikit-player.vue b/src/modules/ezuikit/components/ezuikit-player.vue
index 44f00d7..5802dfe 100644
--- a/src/modules/ezuikit/components/ezuikit-player.vue
+++ b/src/modules/ezuikit/components/ezuikit-player.vue
@@ -99,6 +99,7 @@
   }
 };
 
+let playedOnce = false;
 const init = () => {
   if (player) {
     destroy();
@@ -109,9 +110,9 @@
     id: props.containerId,
     accessToken: props.accessToken,
     url: props.url,
-    autoplay: false,
+    autoplay: true,
     template: "pcLive",
-    height: 400,
+    height: 360,
     scaleMode: 1,
     staticPath: props.staticPath || "",
     env: {
@@ -127,6 +128,14 @@
       console.error("handleError", err);
     }
   });
+
+  player.on(EZUIKitPlayer.EVENTS.firstFrameDisplay, () => {
+    if (playedOnce) return;
+    playedOnce = true;
+    setTimeout(() => {
+      player?.pause();
+    }, 1000);
+  });  
 
   // 浜嬩欢鐩戝惉
   player.on(EZUIKitPlayer.EVENTS.videoInfo, (info: any) => {
@@ -178,6 +187,7 @@
   <div class="ez-player-card">
     <!-- ID鐢辩埗缁勪欢浼犲叆锛屼繚璇佸叏灞�鍞竴 -->
     <div :id="containerId" style="height: 400px;width:100%"></div>
+    <!-- 
     <div class="player-buttons" style="margin-top:8px;display:flex;gap:6px;flex-wrap:wrap">
       <button @click="init">init</button>
       <button @click="stop">stop</button>
@@ -187,14 +197,15 @@
       <button @click="capturePicture">鎴浘</button>
       <button @click="fullscreen">鍏ㄥ睆</button>
     </div>
+    -->
   </div>
 </template>
 
 <style scoped>
 .ez-player-card {
-  border: 1px solid #e4e7ed;
+  border: 0px solid #e4e7ed;
   border-radius: 6px;
-  padding: 8px;
+  padding: 0px;
   background: #000;
 }
 </style>
\ No newline at end of file
diff --git a/src/modules/maps/locales/en.json b/src/modules/maps/locales/en.json
new file mode 100644
index 0000000..ed49d34
--- /dev/null
+++ b/src/modules/maps/locales/en.json
@@ -0,0 +1,71 @@
+{
+"璁惧鍙�":"璁惧鍙�",
+"璁惧鍚嶇О":"璁惧鍚嶇О",
+"涓婁紶鏃堕棿":"涓婁紶鏃堕棿",
+"鍨冨溇绫诲瀷":"鍨冨溇绫诲瀷",
+"鍨冨溇鍚嶇О":"鍨冨溇鍚嶇О",
+"鍨冨溇閲嶉噺":"鍨冨溇閲嶉噺KG",
+"鍏朵粬鏁版嵁":"鍏朵粬鏁版嵁",
+"绔欑偣缂栧彿":"绔欑偣缂栧彿",
+"绔欑偣鍚嶇О":"绔欑偣鍚嶇О",
+"绔欑偣绫诲瀷":"绔欑偣绫诲瀷",
+"绉伴噸璁惧":"绉伴噸璁惧",
+"weight":"weight",
+"health_gps":"health_gps",
+"vehicle_gps":"vehicle_gps",
+"driver_anomaly":"driver_anomaly",
+"others":"others",
+"鏃ュ織鏃堕棿":"鏃ュ織鏃堕棿",
+"鎺ㄩ�佺被鍨�":"鎺ㄩ�佺被鍨�",
+"瀹㈡埛绔疘D":"瀹㈡埛绔疘D",
+"鍘熷璇锋眰鍐呭":"鍘熷璇锋眰鍐呭",
+"閿欒杩斿洖淇℃伅":"閿欒杩斿洖淇℃伅",
+"鍒涘缓鏃堕棿":"鍒涘缓鏃堕棿",
+"鏇存柊鏃堕棿":"鏇存柊鏃堕棿",
+"璁惧搴忓垪鍙�":"璁惧搴忓垪鍙�",
+"娑堟伅搴忓垪鍙�":"娑堟伅搴忓垪鍙�",
+"娑堟伅鍒涘缓鏃堕棿":"娑堟伅鍒涘缓鏃堕棿",
+"GPS瀹氫綅鏃堕棿":"GPS瀹氫綅鏃堕棿",
+"閫熷害":"閫熷害",
+"鏂瑰悜瑙�":"鏂瑰悜瑙�",
+"娴锋嫈楂樺害":"娴锋嫈楂樺害",
+"瀹氫綅绮惧害":"瀹氫綅绮惧害",
+"鏈夋晥瀹氫綅":"鏈夋晥瀹氫綅",
+"鏃犳晥瀹氫綅":"鏃犳晥瀹氫綅",
+"璁惧ID":"璁惧ID",
+"閫氶亾鍙�":"閫氶亾鍙�",
+"娑堟伅鍞竴ID":"娑堟伅鍞竴ID",
+"娑堟伅鏃堕棿":"娑堟伅鏃堕棿",
+"娑堟伅绫诲瀷":"娑堟伅绫诲瀷",
+"璁惧绫诲瀷":"璁惧绫诲瀷",
+"瀛愬簭鍒楀彿":"瀛愬簭鍒楀彿",
+"婧愮ID":"婧愮ID",
+"NAT鐨処P":"NAT鐨処P",
+"浜嬩欢鍙戠敓鏃堕棿":"浜嬩欢鍙戠敓鏃堕棿",
+"娉ㄥ唽鏃堕棿":"娉ㄥ唽鏃堕棿",
+"閲嶉噺":"閲嶉噺",
+"鍗曚环(鍏�/Kg)":"鍗曚环(鍏�/Kg)",
+"鎬讳环(鍏�)":"鎬讳环(鍏�)",
+"缁忔墜浜篒D":"缁忔墜浜篒D",
+"缁忔墜浜�":"缁忔墜浜�",
+"鏀惰喘鏂�":"鏀惰喘鏂�",
+"鏀堕泦鐐�":"鏀堕泦鐐�",
+"鏃ユ湡鏃堕棿":"鏃ユ湡鏃堕棿",
+"鎬讳环":"鎬讳环",
+"浜ゆ槗鏃堕棿":"浜ゆ槗鏃堕棿",
+"浜ゆ槗绫诲瀷":"浜ゆ槗绫诲瀷",
+"浜ゆ槗瀵规柟":"浜ゆ槗瀵规柟",
+"鍟嗗搧鍚嶇О":"鍟嗗搧鍚嶇О",
+"鏀舵敮绫诲瀷":"鏀舵敮绫诲瀷",
+"閲戦":"閲戦",
+"鏀粯鏂瑰紡":"鏀粯鏂瑰紡",
+"褰撳墠鐘舵��":"褰撳墠鐘舵��",
+"澶囨敞":"澶囨敞",
+"鏂囦欢璺緞":"鏂囦欢璺緞",
+"鏂囦欢鍚�":"鏂囦欢鍚�",
+"浜ゆ槗鍗曞彿":"浜ゆ槗鍗曞彿",
+"鍟嗘埛鍗曞彿":"鍟嗘埛鍗曞彿",
+"鏀跺叆":"鏀跺叆",
+"鏀嚭":"鏀嚭",
+"鍏朵粬":"鍏朵粬"
+}
\ No newline at end of file
diff --git a/src/modules/maps/locales/zh-cn.json b/src/modules/maps/locales/zh-cn.json
new file mode 100644
index 0000000..ed49d34
--- /dev/null
+++ b/src/modules/maps/locales/zh-cn.json
@@ -0,0 +1,71 @@
+{
+"璁惧鍙�":"璁惧鍙�",
+"璁惧鍚嶇О":"璁惧鍚嶇О",
+"涓婁紶鏃堕棿":"涓婁紶鏃堕棿",
+"鍨冨溇绫诲瀷":"鍨冨溇绫诲瀷",
+"鍨冨溇鍚嶇О":"鍨冨溇鍚嶇О",
+"鍨冨溇閲嶉噺":"鍨冨溇閲嶉噺KG",
+"鍏朵粬鏁版嵁":"鍏朵粬鏁版嵁",
+"绔欑偣缂栧彿":"绔欑偣缂栧彿",
+"绔欑偣鍚嶇О":"绔欑偣鍚嶇О",
+"绔欑偣绫诲瀷":"绔欑偣绫诲瀷",
+"绉伴噸璁惧":"绉伴噸璁惧",
+"weight":"weight",
+"health_gps":"health_gps",
+"vehicle_gps":"vehicle_gps",
+"driver_anomaly":"driver_anomaly",
+"others":"others",
+"鏃ュ織鏃堕棿":"鏃ュ織鏃堕棿",
+"鎺ㄩ�佺被鍨�":"鎺ㄩ�佺被鍨�",
+"瀹㈡埛绔疘D":"瀹㈡埛绔疘D",
+"鍘熷璇锋眰鍐呭":"鍘熷璇锋眰鍐呭",
+"閿欒杩斿洖淇℃伅":"閿欒杩斿洖淇℃伅",
+"鍒涘缓鏃堕棿":"鍒涘缓鏃堕棿",
+"鏇存柊鏃堕棿":"鏇存柊鏃堕棿",
+"璁惧搴忓垪鍙�":"璁惧搴忓垪鍙�",
+"娑堟伅搴忓垪鍙�":"娑堟伅搴忓垪鍙�",
+"娑堟伅鍒涘缓鏃堕棿":"娑堟伅鍒涘缓鏃堕棿",
+"GPS瀹氫綅鏃堕棿":"GPS瀹氫綅鏃堕棿",
+"閫熷害":"閫熷害",
+"鏂瑰悜瑙�":"鏂瑰悜瑙�",
+"娴锋嫈楂樺害":"娴锋嫈楂樺害",
+"瀹氫綅绮惧害":"瀹氫綅绮惧害",
+"鏈夋晥瀹氫綅":"鏈夋晥瀹氫綅",
+"鏃犳晥瀹氫綅":"鏃犳晥瀹氫綅",
+"璁惧ID":"璁惧ID",
+"閫氶亾鍙�":"閫氶亾鍙�",
+"娑堟伅鍞竴ID":"娑堟伅鍞竴ID",
+"娑堟伅鏃堕棿":"娑堟伅鏃堕棿",
+"娑堟伅绫诲瀷":"娑堟伅绫诲瀷",
+"璁惧绫诲瀷":"璁惧绫诲瀷",
+"瀛愬簭鍒楀彿":"瀛愬簭鍒楀彿",
+"婧愮ID":"婧愮ID",
+"NAT鐨処P":"NAT鐨処P",
+"浜嬩欢鍙戠敓鏃堕棿":"浜嬩欢鍙戠敓鏃堕棿",
+"娉ㄥ唽鏃堕棿":"娉ㄥ唽鏃堕棿",
+"閲嶉噺":"閲嶉噺",
+"鍗曚环(鍏�/Kg)":"鍗曚环(鍏�/Kg)",
+"鎬讳环(鍏�)":"鎬讳环(鍏�)",
+"缁忔墜浜篒D":"缁忔墜浜篒D",
+"缁忔墜浜�":"缁忔墜浜�",
+"鏀惰喘鏂�":"鏀惰喘鏂�",
+"鏀堕泦鐐�":"鏀堕泦鐐�",
+"鏃ユ湡鏃堕棿":"鏃ユ湡鏃堕棿",
+"鎬讳环":"鎬讳环",
+"浜ゆ槗鏃堕棿":"浜ゆ槗鏃堕棿",
+"浜ゆ槗绫诲瀷":"浜ゆ槗绫诲瀷",
+"浜ゆ槗瀵规柟":"浜ゆ槗瀵规柟",
+"鍟嗗搧鍚嶇О":"鍟嗗搧鍚嶇О",
+"鏀舵敮绫诲瀷":"鏀舵敮绫诲瀷",
+"閲戦":"閲戦",
+"鏀粯鏂瑰紡":"鏀粯鏂瑰紡",
+"褰撳墠鐘舵��":"褰撳墠鐘舵��",
+"澶囨敞":"澶囨敞",
+"鏂囦欢璺緞":"鏂囦欢璺緞",
+"鏂囦欢鍚�":"鏂囦欢鍚�",
+"浜ゆ槗鍗曞彿":"浜ゆ槗鍗曞彿",
+"鍟嗘埛鍗曞彿":"鍟嗘埛鍗曞彿",
+"鏀跺叆":"鏀跺叆",
+"鏀嚭":"鏀嚭",
+"鍏朵粬":"鍏朵粬"
+}
\ No newline at end of file
diff --git a/src/modules/maps/locales/zh-tw.json b/src/modules/maps/locales/zh-tw.json
new file mode 100644
index 0000000..ed49d34
--- /dev/null
+++ b/src/modules/maps/locales/zh-tw.json
@@ -0,0 +1,71 @@
+{
+"璁惧鍙�":"璁惧鍙�",
+"璁惧鍚嶇О":"璁惧鍚嶇О",
+"涓婁紶鏃堕棿":"涓婁紶鏃堕棿",
+"鍨冨溇绫诲瀷":"鍨冨溇绫诲瀷",
+"鍨冨溇鍚嶇О":"鍨冨溇鍚嶇О",
+"鍨冨溇閲嶉噺":"鍨冨溇閲嶉噺KG",
+"鍏朵粬鏁版嵁":"鍏朵粬鏁版嵁",
+"绔欑偣缂栧彿":"绔欑偣缂栧彿",
+"绔欑偣鍚嶇О":"绔欑偣鍚嶇О",
+"绔欑偣绫诲瀷":"绔欑偣绫诲瀷",
+"绉伴噸璁惧":"绉伴噸璁惧",
+"weight":"weight",
+"health_gps":"health_gps",
+"vehicle_gps":"vehicle_gps",
+"driver_anomaly":"driver_anomaly",
+"others":"others",
+"鏃ュ織鏃堕棿":"鏃ュ織鏃堕棿",
+"鎺ㄩ�佺被鍨�":"鎺ㄩ�佺被鍨�",
+"瀹㈡埛绔疘D":"瀹㈡埛绔疘D",
+"鍘熷璇锋眰鍐呭":"鍘熷璇锋眰鍐呭",
+"閿欒杩斿洖淇℃伅":"閿欒杩斿洖淇℃伅",
+"鍒涘缓鏃堕棿":"鍒涘缓鏃堕棿",
+"鏇存柊鏃堕棿":"鏇存柊鏃堕棿",
+"璁惧搴忓垪鍙�":"璁惧搴忓垪鍙�",
+"娑堟伅搴忓垪鍙�":"娑堟伅搴忓垪鍙�",
+"娑堟伅鍒涘缓鏃堕棿":"娑堟伅鍒涘缓鏃堕棿",
+"GPS瀹氫綅鏃堕棿":"GPS瀹氫綅鏃堕棿",
+"閫熷害":"閫熷害",
+"鏂瑰悜瑙�":"鏂瑰悜瑙�",
+"娴锋嫈楂樺害":"娴锋嫈楂樺害",
+"瀹氫綅绮惧害":"瀹氫綅绮惧害",
+"鏈夋晥瀹氫綅":"鏈夋晥瀹氫綅",
+"鏃犳晥瀹氫綅":"鏃犳晥瀹氫綅",
+"璁惧ID":"璁惧ID",
+"閫氶亾鍙�":"閫氶亾鍙�",
+"娑堟伅鍞竴ID":"娑堟伅鍞竴ID",
+"娑堟伅鏃堕棿":"娑堟伅鏃堕棿",
+"娑堟伅绫诲瀷":"娑堟伅绫诲瀷",
+"璁惧绫诲瀷":"璁惧绫诲瀷",
+"瀛愬簭鍒楀彿":"瀛愬簭鍒楀彿",
+"婧愮ID":"婧愮ID",
+"NAT鐨処P":"NAT鐨処P",
+"浜嬩欢鍙戠敓鏃堕棿":"浜嬩欢鍙戠敓鏃堕棿",
+"娉ㄥ唽鏃堕棿":"娉ㄥ唽鏃堕棿",
+"閲嶉噺":"閲嶉噺",
+"鍗曚环(鍏�/Kg)":"鍗曚环(鍏�/Kg)",
+"鎬讳环(鍏�)":"鎬讳环(鍏�)",
+"缁忔墜浜篒D":"缁忔墜浜篒D",
+"缁忔墜浜�":"缁忔墜浜�",
+"鏀惰喘鏂�":"鏀惰喘鏂�",
+"鏀堕泦鐐�":"鏀堕泦鐐�",
+"鏃ユ湡鏃堕棿":"鏃ユ湡鏃堕棿",
+"鎬讳环":"鎬讳环",
+"浜ゆ槗鏃堕棿":"浜ゆ槗鏃堕棿",
+"浜ゆ槗绫诲瀷":"浜ゆ槗绫诲瀷",
+"浜ゆ槗瀵规柟":"浜ゆ槗瀵规柟",
+"鍟嗗搧鍚嶇О":"鍟嗗搧鍚嶇О",
+"鏀舵敮绫诲瀷":"鏀舵敮绫诲瀷",
+"閲戦":"閲戦",
+"鏀粯鏂瑰紡":"鏀粯鏂瑰紡",
+"褰撳墠鐘舵��":"褰撳墠鐘舵��",
+"澶囨敞":"澶囨敞",
+"鏂囦欢璺緞":"鏂囦欢璺緞",
+"鏂囦欢鍚�":"鏂囦欢鍚�",
+"浜ゆ槗鍗曞彿":"浜ゆ槗鍗曞彿",
+"鍟嗘埛鍗曞彿":"鍟嗘埛鍗曞彿",
+"鏀跺叆":"鏀跺叆",
+"鏀嚭":"鏀嚭",
+"鍏朵粬":"鍏朵粬"
+}
\ No newline at end of file
diff --git a/src/modules/maps/views/layers.vue b/src/modules/maps/views/layers.vue
new file mode 100644
index 0000000..5bcbe23
--- /dev/null
+++ b/src/modules/maps/views/layers.vue
@@ -0,0 +1,84 @@
+<!--App.vue-->
+<template>
+    <div :class="state.copyright ? '' : 'hide-copyright'" class="mapDiv">
+        <tdt-map :center="state.center" :controls="state.controls" :zoom="state.zoom" :loadConfig="loadConfig">
+            <tdt-control :visible="state.visible" position="topright">
+            </tdt-control>
+        </tdt-map>
+    </div>
+</template>
+
+<script lang="ts" setup>
+import { reactive } from "vue";
+import { TdtMap, type LngLat } from "vue-tianditu2";
+
+const loadConfig = { v: "4.0", tk: "51915011aa0997aa2ddd7e38504fa8bc" };
+const state = reactive({
+    center: [121.280637, 32.125178] as LngLat,
+    zoom: 12,
+    visible: true,
+    copyright: false,
+    controls: [
+        "Zoom",
+        "Scale",
+        {
+            name: "MapType",
+            position: "topright",
+            mapTypes: [
+                {
+                    title: "鍦板浘", //鍦板浘鎺т欢涓婃墍瑕佹樉绀虹殑鍥惧眰鍚嶇О
+                    icon: "https://api.tianditu.gov.cn/v4.0/image/map/maptype/vector.png", //鍦板浘鎺т欢涓婃墍瑕佹樉绀虹殑鍥惧眰鍥炬爣锛堥粯璁ゅ浘鏍囧ぇ灏� 80x80锛�
+                    layer: "TMAP_NORMAL_MAP" //鍦板浘绫诲瀷锛屽湪鍘熷ぉ鍦板浘api涓互window.TMAP_NORMAL_MAP琛ㄧず锛屾澶勪负瀛楃涓�
+                },
+                {
+                    title: "鍗槦",
+                    icon: " https://api.tianditu.gov.cn/v4.0/image/map/maptype/satellite.png",
+                    layer: "TMAP_SATELLITE_MAP"
+                },
+                {
+                    title: "鍗槦娣峰悎",
+                    icon: "https://api.tianditu.gov.cn/v4.0/image/map/maptype/satellitepoi.png",
+                    layer: "TMAP_HYBRID_MAP"
+                }
+                // {
+                //   title: "鍦板舰",
+                //   icon: "https://api.tianditu.gov.cn/v4.0/image/map/maptype/terrain.png",
+                //   layer: "TMAP_TERRAIN_MAP"
+                // },
+                // {
+                //   title: "鍦板舰娣峰悎",
+                //   icon: "https://api.tianditu.gov.cn/v4.0/image/map/maptype/terrainpoi.png",
+                //   layer: "TMAP_TERRAIN_HYBRID_MAP"
+                // }
+            ]
+        },
+        {
+            name: "OverviewMap",
+            isOpen: true,
+            anchor: "bottomright"
+        },
+        {
+            name: "Copyright",
+            id: "custom",
+            content: `<div style="height:40px"><button class="demo-button">鑷畾涔夌殑鐗堟潈鎺т欢</button></div>`,
+            position: "bottomleft",
+            bounds: [
+                [113.52791, 23.21989],
+                [113.03352, 23.03045]
+            ]
+        }
+    ]
+});
+</script>
+
+<style>
+.mapDiv {
+    width: 100%;
+    height: 100%;
+    padding: 0%;
+}
+
+.hide-copyright :deep(.tdt-control-copyright.tdt-control > div:not(.tdt-control-copyright)) {
+    display: none;
+}
+</style>
\ No newline at end of file
diff --git a/src/modules/monitor/components/site-tree.vue b/src/modules/monitor/components/site-tree.vue
index efa4847..ab20d1d 100644
--- a/src/modules/monitor/components/site-tree.vue
+++ b/src/modules/monitor/components/site-tree.vue
@@ -104,11 +104,6 @@
 	selectId.value = item.a_id;
 	const numReg = /^\d+$/;
 	const isSite = !numReg.test(item.a_id);
-	if (!isSite) {
-		ElMessage.info(t('璇烽�夋嫨绔欑偣鑺傜偣鏌ョ湅鐩戞帶'));
-		emit('select', null);
-		return;
-	}
 	emit('select', item.a_id);
 }
 
diff --git a/src/modules/monitor/views/capturetask.vue b/src/modules/monitor/views/capturetask.vue
new file mode 100644
index 0000000..35b663f
--- /dev/null
+++ b/src/modules/monitor/views/capturetask.vue
@@ -0,0 +1,183 @@
+<template>
+	<cl-crud ref="Crud">
+		<cl-row>
+			<!-- 鍒锋柊鎸夐挳 -->
+			<cl-refresh-btn />
+			<!-- 鏂板鎸夐挳 -->
+			<cl-add-btn />
+			<!-- 鍒犻櫎鎸夐挳 -->
+			<cl-multi-delete-btn />
+			<cl-flex1 />
+			<!-- 鏉′欢鎼滅储 -->
+			<cl-search ref="Search" />
+		</cl-row>
+
+		<cl-row>
+			<!-- 鏁版嵁琛ㄦ牸 -->
+			<cl-table ref="Table" />
+		</cl-row>
+
+		<cl-row>
+			<cl-flex1 />
+			<!-- 鍒嗛〉鎺т欢 -->
+			<cl-pagination />
+		</cl-row>
+
+		<!-- 鏂板銆佺紪杈� -->
+		<cl-upsert ref="Upsert" />
+	</cl-crud>
+</template>
+
+<script lang="ts" setup>
+	defineOptions({
+		name: "monitor-capturetask"
+	});
+
+	import { useCrud, useTable, useUpsert, useSearch } from "@cool-vue/crud";
+	import { useCool } from "/@/cool";
+	import { useI18n } from "vue-i18n";
+	import { reactive } from "vue";
+
+	const { service } = useCool();
+	const { t } = useI18n();
+
+	// 閫夐」
+	const options = reactive({
+		status: [
+			{ label: t('PENDING'), value: 0 },
+			{ label: t('SUCCESS'), value: 1 },
+			{ label: t('FAILED'), value: 2 }
+		]
+	});
+
+	// cl-upsert
+	const Upsert = useUpsert({
+		items: [
+			{
+				label: t('璁惧搴忓垪鍙�'),
+				prop: "deviceSerial",
+				component: { name: "el-input", props: { clearable: true } },
+				span: 12,
+				required: true
+			},
+			{
+				label: t('閫氶亾鍙�'),
+				prop: "channelNo",
+				hook: "number",
+				component: { name: "el-input-number", props: { min: 0 } },
+				span: 12,
+				required: true
+			},
+			{
+				label: t('閫夋嫨鎶撳浘浠诲姟'),
+				prop: "captureTaskId",
+				component: { name: "el-input", props: { clearable: true } },
+				span: 12
+			},
+			{
+				label: t('閫夋嫨浜戠┖闂�'),
+				prop: "spaceId",
+				component: { name: "el-input", props: { clearable: true } },
+				span: 12
+			},
+			{
+				label: t('浜戠┖闂村浘鐗囬摼鎺�'),
+				prop: "cloudUrl",
+				component: { name: "cl-upload" }
+			},
+			{ label: t('鏈湴鍥剧墖閾炬帴'), prop: "localUrl", component: { name: "cl-upload" } },
+			{
+				label: t('鏈湴瀛樺偍璺緞'),
+				prop: "localPath",
+				component: { name: "el-input", props: { clearable: true } },
+				span: 12
+			},
+			{
+				label: t('鎶撴媿鐘舵��'),
+				prop: "status",
+				component: { name: "el-radio-group", options: options.status },
+				value: 0,
+				required: true
+			},
+			{
+				label: t('浠诲姟鎵ц鏃ユ湡'),
+				prop: "captureDate",
+				component: {
+					name: "el-date-picker",
+					props: { type: "date", valueFormat: "YYYY-MM-DD" }
+				},
+				span: 12
+			}
+		]
+	});
+
+	// cl-table
+	const Table = useTable({
+		columns: [
+			{ type: "selection" },
+			{ label: t('璁惧搴忓垪鍙�'), prop: "deviceSerial", minWidth: 140 },
+			{ label: t('閫氶亾鍙�'), prop: "channelNo", minWidth: 140, sortable: "custom" },
+			{ label: t('鎶撳浘浠诲姟ID'), prop: "captureTaskId", minWidth: 140 },
+			{ label: t('浜戠┖闂碔D'), prop: "spaceId", minWidth: 140 },
+			{
+				label: t('浜戠┖闂村浘鐗囬摼鎺�'),
+				prop: "cloudUrl",
+				minWidth: 100,
+				component: { name: "cl-image", props: { size: 60 } }
+			},
+			{
+				label: t('鏈湴鍥剧墖閾炬帴'),
+				prop: "localUrl",
+				minWidth: 100,
+				component: { name: "cl-image", props: { size: 60 } }
+			},
+			{ label: t('鏈湴瀛樺偍璺緞'), prop: "localPath", minWidth: 140 },
+			{
+				label: t('鎶撴媿鐘舵��'),
+				prop: "status",
+				minWidth: 120,
+				dict: options.status
+			},
+			{
+				label: t('浠诲姟鎵ц鏃ユ湡'),
+				prop: "captureDate",
+				minWidth: 140,
+				sortable: "custom",
+				component: { name: "cl-date-text", props: { format: "YYYY-MM-DD" } }
+			},
+			{
+				label: t('鍒涘缓鏃堕棿'),
+				prop: "createTime",
+				minWidth: 170,
+				sortable: "desc",
+				component: { name: "cl-date-text" }
+			},
+			{
+				label: t('鏇存柊鏃堕棿'),
+				prop: "updateTime",
+				minWidth: 170,
+				sortable: "custom",
+				component: { name: "cl-date-text" }
+			},
+			{ type: "op", buttons: ["edit", "delete"] }
+		]
+	});
+
+	// cl-search
+	const Search = useSearch();
+
+	// cl-crud
+	const Crud = useCrud(
+		{
+			service: service.monitor.capturetask
+		},
+		(app) => {
+			app.refresh();
+		}
+	);
+
+	// 鍒锋柊
+	function refresh(params?: any) {
+		Crud.value?.refresh(params);
+	}
+</script>
\ No newline at end of file
diff --git a/src/modules/monitor/views/device.vue b/src/modules/monitor/views/device.vue
index 75bd43c..99ef647 100644
--- a/src/modules/monitor/views/device.vue
+++ b/src/modules/monitor/views/device.vue
@@ -1,27 +1,23 @@
 <template>
-	<div class="monitor-page" style="height:100%;display:flex;gap:12px;">
-		<!-- 宸︿晶鏍戝舰 -->
-		<div class="left-tree" style="width:280px;border:1px solid var(--el-border-color);border-radius:6px;">
-			<site-tree @select="handleTreeSelect" />
-		</div>
+  <div class="monitor-page">
+    <!-- 宸︿晶鏍戝舰 -->
+    <div class="left-tree">
+      <site-tree @select="handleTreeSelect" />
+    </div>
 
-		<!-- 鍙充晶鐩戞帶鍖哄煙 -->
-		<div class="right-player" style="flex:1;border:1px solid var(--el-border-color);border-radius:6px;padding:12px;overflow:auto;">
-			<el-empty v-if="deviceList.length === 0" description="璇峰乏渚ч�夋嫨绔欑偣鍔犺浇鐩戞帶璁惧" />
-			<div class="player-grid" v-if="deviceList.length">
-				<div v-for="dev in deviceList" :key="dev.id" class="player-card">
-					<div class="title">{{ dev.iotName }}</div>
-					<EzuikitPlayer
-					v-if="ysToken"
-					:key="'p_' + dev.id + '_' + ysToken"
-					:container-id="'player_' + dev.id"
-					:access-token="ysToken"
-					:url="`ezopen://open.ys7.com/${dev.iotCode}/1.hd.live`"
-					/>
-				</div>
-			</div>
-		</div>
-	</div>
+    <!-- 鍙充晶鐩戞帶鍖哄煙 -->
+    <div class="right-player">
+      <el-empty v-if="deviceList.length === 0" description="璇峰乏渚ч�夋嫨绔欑偣鍔犺浇鐩戞帶璁惧" />
+      <div v-else class="player-grid" :style="gridStyle">
+        <div v-for="dev in deviceList" :key="dev.id" class="player-card">
+          <!-- <div class="title">{{ dev.iotName }}</div> -->
+          <ezuikit-player v-if="ysToken" :key="'p_' + dev.id + '_' + ysToken" :container-id="'player_' + dev.id"
+            :access-token="ysToken" :url="`ezopen://open.ys7.com/${dev.iotCode}/1.hd.live`"
+            :pause-at-first-frame="true" />
+        </div>
+      </div>
+    </div>
+  </div>
 </template>
 
 <script lang="ts" setup>
@@ -29,7 +25,7 @@
   name: 'monitor-device',
 });
 
-import { ref, onMounted } from 'vue';
+import { ref, computed, onMounted } from 'vue';
 import { useCool } from '/@/cool';
 import SiteTree from '../components/site-tree.vue';
 import EzuikitPlayer from '../../ezuikit/components/ezuikit-player.vue';
@@ -37,22 +33,34 @@
 const { service } = useCool();
 
 const deviceList = ref<any[]>([]);
-const ysToken = ref<string>('');
+const ysToken = ref('');
+
+// 鉁� 鏍规嵁璁惧鏁伴噺鑷姩璁$畻缃戞牸鍒楁暟
+const gridCols = computed(() => {
+  const n = deviceList.value.length;
+  if (n <= 1) return 1;
+  if (n <= 4) return 2;   // 2~4 涓� 鈫� 2 鍒�
+  if (n <= 6) return 3;   // 5~6 涓� 鈫� 3 鍒�
+  if (n <= 8) return 4;   // 7~8 涓� 鈫� 4 鍒�
+  return 4;
+});
+
+const gridStyle = computed(() => ({
+  gridTemplateColumns: `repeat(${gridCols.value}, 1fr)`,
+}));
 
 /** 鎷胯悿鐭� token */
 async function fetchYsToken() {
   if (ysToken.value) return; // 宸叉湁灏变笉鍐嶉噸澶嶆嬁
   try {
-    const res = await service.monitor.ys7.getToken();
-    ysToken.value = res.accessToken;
-  } catch (e) {
-    console.error('钀ょ煶 Token 鑾峰彇澶辫触', e);
+    const res = await service.base.sys.param.stringbykey({ key: 'ys7.accessToken' });
+    ysToken.value = res as string; // 鉁� 淇锛氳祴鍊肩粰 .value
+  } catch (e: any) {
+    console.error('钀ょ煶 Token 鑾峰彇澶辫触', e?.response?.data?.message || e.message);
   }
 }
 
-onMounted(() => {
-  fetchYsToken();
-});
+onMounted(fetchYsToken); // 鉁� 淇锛歰nMounted
 
 /** 鏍戣妭鐐圭偣鍑� */
 async function handleTreeSelect(businessId: string | number | null) {
@@ -68,9 +76,8 @@
 
   try {
     const res = await service.basicdata.iot.getYsDevices({ businessId });
-    deviceList.value = res || [];
-  } catch (err) {
-    console.error('鑾峰彇钀ょ煶璁惧鍒楄〃澶辫触', err);
+    deviceList.value = (res || []) as any[];
+  } catch {
     deviceList.value = [];
   }
 }
@@ -78,23 +85,36 @@
 
 <style lang="scss" scoped>
 .monitor-page {
-	height: 100%;
+  height: 100%;
+  display: flex;
+  gap: 12px;
 }
+
 .left-tree {
-	height: 100%;
+  width: 280px;
+  height: 100%;
+  border: 1px solid var(--el-border-color);
+  border-radius: 6px;
 }
+
 .right-player {
-	height: 100%;
+  flex: 1;
+  height: 100%;
+  border: 1px solid var(--el-border-color);
+  border-radius: 6px;
+  padding: 12px;
+  overflow: auto;
 }
+
 .player-grid {
-	display: grid;
-	grid-template-columns: repeat(auto-fill, minmax(440px, 1fr));
-	gap: 16px;
+  display: grid;
+  gap: 16px;
 }
+
 .player-card {
-	.title {
-		padding: 6px 0;
-		font-weight: 500;
-	}
+  .title {
+    padding: 6px 0;
+    font-weight: 500;
+  }
 }
 </style>
\ No newline at end of file
diff --git a/src/modules/shop/components/item-select.vue b/src/modules/shop/components/item-select.vue
new file mode 100644
index 0000000..6e19fec
--- /dev/null
+++ b/src/modules/shop/components/item-select.vue
@@ -0,0 +1,36 @@
+<template>
+    <cl-select-table v-model="value" :title="t('閫夋嫨璁㈠崟璇︽儏')" :service="service.shop.recycleitem" :columns="columns"
+        :multiple="multiple" :dict='{ "text": "itemname" }' pickerType="text" />
+</template>
+
+<script setup lang="ts">
+defineOptions({
+    name: "recycle-item-select"
+});
+
+import { useCool } from "/@/cool";
+import { useI18n } from "vue-i18n";
+import { CrudProps } from "/#/crud";
+import { ref, useModel } from "vue";
+
+const props = defineProps({
+    ...CrudProps,
+    modelValue: null,
+    multiple: Boolean
+});
+
+const { service } = useCool();
+const { t } = useI18n();
+
+const value = useModel(props, "modelValue");
+
+const columns = ref([
+    { label: t("璁㈠崟缂栧彿"), prop: "orderNo", minWidth: 140 },
+    { label: t("椤圭洰鍚嶇О"), prop: "itemName", minWidth: 140 },
+    { label: t("鍒嗙被ID"), prop: "categoryId", minWidth: 120 },
+    { label: t("閲嶉噺"), prop: "weight", minWidth: 140, sortable: "custom" },
+    { label: t("鍗曚环"), prop: "unitPrice", minWidth: 140, sortable: "custom" },
+    { label: t("閲戦"), prop: "amount", minWidth: 140, sortable: "custom" },
+    { label: t("纰崇Н鍒�"), prop: "carbonPoint", minWidth: 140, sortable: "custom" }
+]);
+</script>
diff --git a/src/modules/shop/components/order-detail.vue b/src/modules/shop/components/order-detail.vue
new file mode 100644
index 0000000..464d17b
--- /dev/null
+++ b/src/modules/shop/components/order-detail.vue
@@ -0,0 +1,303 @@
+<template>
+    <cl-form ref="Form">
+        <template #slot-order-detail>
+            <cl-crud padding="0">
+                <cl-row>
+                    <el-button type="success" @click="add">娣诲姞鍥炴敹鐗�</el-button>
+                    <el-button :disabled="Table?.selection.length === 0" type="danger" @click="remove()">
+                        鍒犻櫎
+                    </el-button>
+                </cl-row>
+
+                <cl-row>
+                    <cl-table ref="Table" :data="data" :auto-height="false">
+                        <!-- 鍥炴敹鐗╅�夋嫨 -->
+                        <template #column-itemName="{ scope }">
+                            <el-form-item :prop="`items.${scope.$index}.itemId`"
+                                :rules="{ required: true, message: '璇烽�夋嫨鍥炴敹鐗�' }">
+                                <el-select v-model="scope.row.itemId" filterable placeholder="璇烽�夋嫨鍥炴敹鐗�"
+                                    style="width: 100%" @change="onItemChange(scope.row)">
+                                    <el-option v-for="item in itemOptions" :key="item.itemId" :label="item.itemName"
+                                        :value="item.itemId" />
+                                </el-select>
+                            </el-form-item>
+                        </template>
+
+                        <!-- 閲嶉噺 -->
+                        <template #column-weight="{ scope }">
+                            <el-form-item :prop="`items.${scope.$index}.weight`"
+                                :rules="{ required: true, message: '閲嶉噺涓嶈兘涓虹┖' }">
+                                <el-input-number v-model="scope.row.weight" :min="0" :max="100000" :precision="2"
+                                    :controls="false" placeholder="閲嶉噺/浠舵暟" @change="calcRow(scope.row)" />
+                            </el-form-item>
+                        </template>
+
+                        <!-- 鍗曚环锛堝彧璇伙級 -->
+                        <template #column-unitPrice="{ scope }">
+                            <el-form-item>
+                                <el-input-number v-model="scope.row.unitPrice" :controls="false" :precision="2"
+                                    disabled />
+                            </el-form-item>
+                        </template>
+
+                        <!-- 閲戦锛堝彧璇伙級 -->
+                        <template #column-amount="{ scope }">
+                            <el-form-item>
+                                <el-input-number v-model="scope.row.amount" :controls="false" :precision="2" disabled />
+                            </el-form-item>
+                        </template>
+
+                        <!-- 纰崇Н鍒嗭紙鍙锛� -->
+                        <template #column-carbonPoint="{ scope }">
+                            <el-form-item>
+                                <el-input-number v-model="scope.row.carbonPoint" :controls="false" :precision="2"
+                                    disabled />
+                            </el-form-item>
+                        </template>
+
+                        <!-- 纰崇Н鍒嗭紙鍙锛� -->
+                        <template #column-category="{ scope }">
+                            <el-form-item>
+                                <el-input v-model="scope.row.category" :controls="false" disabled />
+                            </el-form-item>
+                        </template>
+
+                        <template #slot-btn="{ scope }">
+                            <el-icon class="del-btn" @click="remove(scope.row.uid)">
+                                <delete />
+                            </el-icon>
+                        </template>
+                    </cl-table>
+                </cl-row>
+            </cl-crud>
+        </template>
+    </cl-form>
+</template>
+
+<script lang="ts" setup name="order-detail">
+import { useForm, useTable } from "@cool-vue/crud";
+import { computed, ref, onMounted } from "vue";
+import { useCool } from "/@/cool";
+import { ElMessage } from "element-plus";
+import { isEmpty, find } from "lodash-es";
+import { Delete } from "@element-plus/icons-vue";
+import { uuid } from "/@/cool/utils";
+
+const { service } = useCool();
+const Form = useForm();
+
+// 鍥炴敹鐗╁瓧鍏�
+const itemOptions = ref<any[]>([]);
+
+const currentOrderNo = ref<string>();
+// 鏄庣粏鍒楄〃
+const list = ref<any[]>([]);
+
+// 琛ㄦ牸鏁版嵁
+const data = computed(() => list.value.filter((e) => !e.isDel));
+
+// 鎵撳紑
+async function open(orderNo: string) {
+    currentOrderNo.value = orderNo;
+    await refresh(orderNo);
+
+    Form.value?.open({
+        title: "鍥炴敹璁㈠崟鏄庣粏",
+        form: { items: list.value },
+        width: "80%",
+        items: [
+            {
+                prop: "items",
+                component: { name: "slot-order-detail" }
+            }
+        ],
+        on: {
+            submit(_, { close, done }) {
+                const delIds: number[] = [];
+                const add: any[] = [];
+                const update: any[] = [];
+
+                list.value.forEach((e) => {
+                    if (e.id) {
+                        if (e.isDel) {
+                            delIds.push(e.id);
+                        } else if (e.isEdit) {
+                            update.push(e);
+                        }
+                    } else if (!e.isDel) {
+                        add.push(e);
+                    }
+                });
+
+                /*
+                const tasks: Promise<any>[] = [];
+
+                if (!isEmpty(delIds)) {
+                    tasks.push(service.shop.recycleorderitem.delete({ ids: delIds }));
+                }
+
+                if (!isEmpty(update)) {
+                    update.forEach((e) => tasks.push(service.shop.recycleorderitem.update(e)));
+                }
+
+                if (!isEmpty(add)) {
+                    tasks.push(
+                        service.shop.recycleorderitem.add(
+                            add.map((e) => ({
+                                ...e,
+                                orderNo: currentOrderNo.value!
+                            }))
+                        )
+                    );
+                }
+
+                Promise.all(tasks)
+                    .then(() => {
+                        ElMessage.success("淇濆瓨鎴愬姛");
+                        close();
+                    })
+                    .catch((err) => {
+                        ElMessage.error(err.message);
+                        refresh(orderNo);
+                        done();
+                    });
+                    */
+
+                const items = list.value
+                    .filter(e => !e.isDel)
+                    .map(e => ({
+                        id: e.id,
+                        itemId: e.itemId,
+                        itemName: e.itemName,
+                        weight: e.weight,
+                        unitPrice: e.unitPrice,
+                        amount: e.amount,
+                        carbonPoint: e.carbonPoint,
+                        category: e.category
+                    }));
+
+                service.shop.recycleorder.saveWithSummary({
+                    orderNo: currentOrderNo.value!,
+                    items
+                })
+                    .then(() => {
+                        ElMessage.success("淇濆瓨鎴愬姛");
+                        close();
+                    })
+                    .catch((err) => {
+                        ElMessage.error(err.message);
+                        refresh(currentOrderNo.value!);
+                        done();
+                    });
+            }
+        }
+    });
+}
+
+// 鍒锋柊鏄庣粏
+async function refresh(orderNo: string) {
+    const [items, details] = await Promise.all([
+        service.shop.recycleitem.list({
+            status: 0,
+            order: "id",
+            sort: "asc"
+        }),
+        service.shop.recycleorderitem.list({
+            orderNo,
+            order: "id",
+            sort: "asc"
+        })
+    ]);
+
+    itemOptions.value = items;
+
+    list.value = details.map((e: any) => ({
+        ...e,
+        weight: Number(e.weight),
+        unitPrice: Number(e.unitPrice),
+        amount: Number(e.amount),
+        carbonPoint: Number(e.carbonPoint),
+        uid: e.id
+    }));
+}
+
+// 鏂板
+function add() {
+    list.value.push({
+        uid: uuid(),
+        itemId: "",
+        itemName: "",
+        category: "",
+        unit: "",
+        weight: 0,
+        unitPrice: 0,
+        amount: 0,
+        carbonPoint: 0,
+        isDel: false,     // 鉁� 琛ヤ笂
+        isEdit: false     // 鉁� 琛ヤ笂
+    });
+}
+
+// 鍒犻櫎
+function remove(uid?: string) {
+    const arr = uid ? [uid] : Table.value?.selection.map((e) => e.uid);
+    list.value.forEach((e) => {
+        if (arr?.includes(e.uid)) {
+            e.isDel = true;
+        }
+    });
+}
+
+// 閫夋嫨鍥炴敹鐗�
+function onItemChange(row: any) {
+    const item = find(itemOptions.value, { itemId: row.itemId });
+
+    if (item) {
+        row.itemName = item.itemName;
+        row.category = item.category;
+        row.unitPrice = Number(item.price);
+        row.carbonPoint = Number(item.carbonPoint);
+        row.unit = item.unit;
+    }
+
+    row.isEdit = true;
+    calcRow(row);
+}
+
+// 璁$畻閲戦
+function calcRow(row: any) {
+    row.amount = Number((row.weight * row.unitPrice).toFixed(2));
+    row.carbonPoint = Number((row.amount * 100).toFixed(2));
+}
+
+const Table = useTable({
+    columns: [
+        { type: "selection" },
+        { label: "鍥炴敹鐗�", prop: "itemName", minWidth: 180 },
+        { label: "閲嶉噺/浠舵暟", prop: "weight", minWidth: 120 },
+        { label: "鍗曚环", prop: "unitPrice", minWidth: 120 },
+        { label: "閲戦", prop: "amount", minWidth: 120 },
+        { label: "鍥芥爣鍒嗙被", prop: "category", minWidth: 120 },
+        { label: "纰崇Н鍒�", prop: "carbonPoint", minWidth: 120 },
+        { type: "op", width: 80, buttons: ["slot-btn"] }
+    ]
+});
+
+defineExpose({
+    open
+});
+</script>
+
+<style lang="scss" scoped>
+.el-form-item {
+    margin-bottom: 0;
+}
+
+.del-btn {
+    cursor: pointer;
+
+    &:hover {
+        color: red;
+    }
+}
+</style>
\ No newline at end of file
diff --git a/src/modules/shop/components/order-select.vue b/src/modules/shop/components/order-select.vue
new file mode 100644
index 0000000..be7a139
--- /dev/null
+++ b/src/modules/shop/components/order-select.vue
@@ -0,0 +1,66 @@
+<template>
+    <cl-select-table v-model="value" :title="t('閫夋嫨璁㈠崟绠$悊')" :service="service.shop.recycleorder" :columns="columns"
+        :multiple="multiple" :dict='{ "text": "username" }' pickerType="text" />
+</template>
+
+<script setup lang="ts">
+defineOptions({
+    name: "recycle-order-select"
+});
+
+import { useCool } from "/@/cool";
+import { useI18n } from "vue-i18n";
+import { CrudProps } from "/#/crud";
+import { reactive, ref, useModel } from "vue";
+
+const props = defineProps({
+    ...CrudProps,
+    modelValue: null,
+    multiple: Boolean
+});
+
+const { service } = useCool();
+const { t } = useI18n();
+
+const value = useModel(props, "modelValue");
+
+// 閫夐」
+const options = reactive({
+    orderType: [
+        { label: t("涓婇棬鍥炴敹"), value: 0 },
+        { label: t("绔欑偣鍥炴敹"), value: 1 },
+        { label: t("鎶曢�掑洖鏀�"), value: 2 }
+    ],
+    status: [
+        { label: t("寰呭鐞�"), value: 0 },
+        { label: t("杩涜涓�"), value: 1 },
+        { label: t("宸插畬鎴�"), value: 2 },
+        { label: t("宸插彇娑�"), value: 3 }
+    ]
+});
+
+const columns = ref([
+    { label: t("鎵嬫満鍙�"), prop: "userPhone", minWidth: 140 },
+    { label: t("鏄电О"), prop: "userName", minWidth: 140 },
+    { label: t("璁㈠崟缂栧彿"), prop: "orderNo", minWidth: 140 },
+    { label: t("鏍″洯鐢ㄦ埛ID"), prop: "campusUserId", minWidth: 120 },
+    {
+        label: t("璁㈠崟绫诲瀷"),
+        prop: "orderType",
+        minWidth: 120,
+        dict: options.orderType
+    },
+    { label: t("绔欑偣ID"), prop: "siteId", minWidth: 120 },
+    { label: t("鍒嗘嫞鍛業D"), prop: "sorterId", minWidth: 120 },
+    { label: t("鐘舵��"), prop: "status", minWidth: 120, dict: options.status },
+    { label: t("鎬婚噸閲�"), prop: "totalWeight", minWidth: 140, sortable: "custom" },
+    { label: t("鎬荤⒊绉垎"), prop: "totalCarbonPoint", minWidth: 140, sortable: "custom" },
+    {
+        label: t("瀹屾垚鏃堕棿"),
+        prop: "finishTime",
+        minWidth: 170,
+        sortable: "custom",
+        component: { name: "cl-date-text" }
+    }
+]);
+</script>
diff --git a/src/modules/shop/locales/en.json b/src/modules/shop/locales/en.json
new file mode 100644
index 0000000..ed49d34
--- /dev/null
+++ b/src/modules/shop/locales/en.json
@@ -0,0 +1,71 @@
+{
+"璁惧鍙�":"璁惧鍙�",
+"璁惧鍚嶇О":"璁惧鍚嶇О",
+"涓婁紶鏃堕棿":"涓婁紶鏃堕棿",
+"鍨冨溇绫诲瀷":"鍨冨溇绫诲瀷",
+"鍨冨溇鍚嶇О":"鍨冨溇鍚嶇О",
+"鍨冨溇閲嶉噺":"鍨冨溇閲嶉噺KG",
+"鍏朵粬鏁版嵁":"鍏朵粬鏁版嵁",
+"绔欑偣缂栧彿":"绔欑偣缂栧彿",
+"绔欑偣鍚嶇О":"绔欑偣鍚嶇О",
+"绔欑偣绫诲瀷":"绔欑偣绫诲瀷",
+"绉伴噸璁惧":"绉伴噸璁惧",
+"weight":"weight",
+"health_gps":"health_gps",
+"vehicle_gps":"vehicle_gps",
+"driver_anomaly":"driver_anomaly",
+"others":"others",
+"鏃ュ織鏃堕棿":"鏃ュ織鏃堕棿",
+"鎺ㄩ�佺被鍨�":"鎺ㄩ�佺被鍨�",
+"瀹㈡埛绔疘D":"瀹㈡埛绔疘D",
+"鍘熷璇锋眰鍐呭":"鍘熷璇锋眰鍐呭",
+"閿欒杩斿洖淇℃伅":"閿欒杩斿洖淇℃伅",
+"鍒涘缓鏃堕棿":"鍒涘缓鏃堕棿",
+"鏇存柊鏃堕棿":"鏇存柊鏃堕棿",
+"璁惧搴忓垪鍙�":"璁惧搴忓垪鍙�",
+"娑堟伅搴忓垪鍙�":"娑堟伅搴忓垪鍙�",
+"娑堟伅鍒涘缓鏃堕棿":"娑堟伅鍒涘缓鏃堕棿",
+"GPS瀹氫綅鏃堕棿":"GPS瀹氫綅鏃堕棿",
+"閫熷害":"閫熷害",
+"鏂瑰悜瑙�":"鏂瑰悜瑙�",
+"娴锋嫈楂樺害":"娴锋嫈楂樺害",
+"瀹氫綅绮惧害":"瀹氫綅绮惧害",
+"鏈夋晥瀹氫綅":"鏈夋晥瀹氫綅",
+"鏃犳晥瀹氫綅":"鏃犳晥瀹氫綅",
+"璁惧ID":"璁惧ID",
+"閫氶亾鍙�":"閫氶亾鍙�",
+"娑堟伅鍞竴ID":"娑堟伅鍞竴ID",
+"娑堟伅鏃堕棿":"娑堟伅鏃堕棿",
+"娑堟伅绫诲瀷":"娑堟伅绫诲瀷",
+"璁惧绫诲瀷":"璁惧绫诲瀷",
+"瀛愬簭鍒楀彿":"瀛愬簭鍒楀彿",
+"婧愮ID":"婧愮ID",
+"NAT鐨処P":"NAT鐨処P",
+"浜嬩欢鍙戠敓鏃堕棿":"浜嬩欢鍙戠敓鏃堕棿",
+"娉ㄥ唽鏃堕棿":"娉ㄥ唽鏃堕棿",
+"閲嶉噺":"閲嶉噺",
+"鍗曚环(鍏�/Kg)":"鍗曚环(鍏�/Kg)",
+"鎬讳环(鍏�)":"鎬讳环(鍏�)",
+"缁忔墜浜篒D":"缁忔墜浜篒D",
+"缁忔墜浜�":"缁忔墜浜�",
+"鏀惰喘鏂�":"鏀惰喘鏂�",
+"鏀堕泦鐐�":"鏀堕泦鐐�",
+"鏃ユ湡鏃堕棿":"鏃ユ湡鏃堕棿",
+"鎬讳环":"鎬讳环",
+"浜ゆ槗鏃堕棿":"浜ゆ槗鏃堕棿",
+"浜ゆ槗绫诲瀷":"浜ゆ槗绫诲瀷",
+"浜ゆ槗瀵规柟":"浜ゆ槗瀵规柟",
+"鍟嗗搧鍚嶇О":"鍟嗗搧鍚嶇О",
+"鏀舵敮绫诲瀷":"鏀舵敮绫诲瀷",
+"閲戦":"閲戦",
+"鏀粯鏂瑰紡":"鏀粯鏂瑰紡",
+"褰撳墠鐘舵��":"褰撳墠鐘舵��",
+"澶囨敞":"澶囨敞",
+"鏂囦欢璺緞":"鏂囦欢璺緞",
+"鏂囦欢鍚�":"鏂囦欢鍚�",
+"浜ゆ槗鍗曞彿":"浜ゆ槗鍗曞彿",
+"鍟嗘埛鍗曞彿":"鍟嗘埛鍗曞彿",
+"鏀跺叆":"鏀跺叆",
+"鏀嚭":"鏀嚭",
+"鍏朵粬":"鍏朵粬"
+}
\ No newline at end of file
diff --git a/src/modules/shop/locales/zh-cn.json b/src/modules/shop/locales/zh-cn.json
new file mode 100644
index 0000000..8c07c7f
--- /dev/null
+++ b/src/modules/shop/locales/zh-cn.json
@@ -0,0 +1,117 @@
+{
+"璁惧鍙�":"璁惧鍙�",
+"璁惧鍚嶇О":"璁惧鍚嶇О",
+"涓婁紶鏃堕棿":"涓婁紶鏃堕棿",
+"鍨冨溇绫诲瀷":"鍨冨溇绫诲瀷",
+"鍨冨溇鍚嶇О":"鍨冨溇鍚嶇О",
+"鍨冨溇閲嶉噺":"鍨冨溇閲嶉噺KG",
+"鍏朵粬鏁版嵁":"鍏朵粬鏁版嵁",
+"绔欑偣缂栧彿":"绔欑偣缂栧彿",
+"绔欑偣鍚嶇О":"绔欑偣鍚嶇О",
+"绔欑偣绫诲瀷":"绔欑偣绫诲瀷",
+"绉伴噸璁惧":"绉伴噸璁惧",
+"weight":"weight",
+"health_gps":"health_gps",
+"vehicle_gps":"vehicle_gps",
+"driver_anomaly":"driver_anomaly",
+"others":"others",
+"鏃ュ織鏃堕棿":"鏃ュ織鏃堕棿",
+"鎺ㄩ�佺被鍨�":"鎺ㄩ�佺被鍨�",
+"瀹㈡埛绔疘D":"瀹㈡埛绔疘D",
+"鍘熷璇锋眰鍐呭":"鍘熷璇锋眰鍐呭",
+"閿欒杩斿洖淇℃伅":"閿欒杩斿洖淇℃伅",
+"鍒涘缓鏃堕棿":"鍒涘缓鏃堕棿",
+"鏇存柊鏃堕棿":"鏇存柊鏃堕棿",
+"璁惧搴忓垪鍙�":"璁惧搴忓垪鍙�",
+"娑堟伅搴忓垪鍙�":"娑堟伅搴忓垪鍙�",
+"娑堟伅鍒涘缓鏃堕棿":"娑堟伅鍒涘缓鏃堕棿",
+"GPS瀹氫綅鏃堕棿":"GPS瀹氫綅鏃堕棿",
+"閫熷害":"閫熷害",
+"鏂瑰悜瑙�":"鏂瑰悜瑙�",
+"娴锋嫈楂樺害":"娴锋嫈楂樺害",
+"瀹氫綅绮惧害":"瀹氫綅绮惧害",
+"鏈夋晥瀹氫綅":"鏈夋晥瀹氫綅",
+"鏃犳晥瀹氫綅":"鏃犳晥瀹氫綅",
+"璁惧ID":"璁惧ID",
+"閫氶亾鍙�":"閫氶亾鍙�",
+"娑堟伅鍞竴ID":"娑堟伅鍞竴ID",
+"娑堟伅鏃堕棿":"娑堟伅鏃堕棿",
+"娑堟伅绫诲瀷":"娑堟伅绫诲瀷",
+"璁惧绫诲瀷":"璁惧绫诲瀷",
+"瀛愬簭鍒楀彿":"瀛愬簭鍒楀彿",
+"婧愮ID":"婧愮ID",
+"NAT鐨処P":"NAT鐨処P",
+"浜嬩欢鍙戠敓鏃堕棿":"浜嬩欢鍙戠敓鏃堕棿",
+"娉ㄥ唽鏃堕棿":"娉ㄥ唽鏃堕棿",
+"閲嶉噺":"閲嶉噺",
+"鍗曚环(鍏�/Kg)":"鍗曚环(鍏�/Kg)",
+"鎬讳环(鍏�)":"鎬讳环(鍏�)",
+"缁忔墜浜篒D":"缁忔墜浜篒D",
+"缁忔墜浜�":"缁忔墜浜�",
+"鏀惰喘鏂�":"鏀惰喘鏂�",
+"鏀堕泦鐐�":"鏀堕泦鐐�",
+"鏃ユ湡鏃堕棿":"鏃ユ湡鏃堕棿",
+"鎬讳环":"鎬讳环",
+"浜ゆ槗鏃堕棿":"浜ゆ槗鏃堕棿",
+"浜ゆ槗绫诲瀷":"浜ゆ槗绫诲瀷",
+"浜ゆ槗瀵规柟":"浜ゆ槗瀵规柟",
+"鍟嗗搧鍚嶇О":"鍟嗗搧鍚嶇О",
+"鏀舵敮绫诲瀷":"鏀舵敮绫诲瀷",
+"閲戦":"閲戦",
+"鏀粯鏂瑰紡":"鏀粯鏂瑰紡",
+"褰撳墠鐘舵��":"褰撳墠鐘舵��",
+"澶囨敞":"澶囨敞",
+"鏂囦欢璺緞":"鏂囦欢璺緞",
+"鏂囦欢鍚�":"鏂囦欢鍚�",
+"浜ゆ槗鍗曞彿":"浜ゆ槗鍗曞彿",
+"鍟嗘埛鍗曞彿":"鍟嗘埛鍗曞彿",
+"鏀跺叆":"鏀跺叆",
+"鏀嚭":"鏀嚭",
+"鍏朵粬":"鍏朵粬",
+"鍙洖鏀剁墿缂栫爜":"鍙洖鏀剁墿缂栫爜",
+"搴熷搧绫诲埆缂栫爜":"搴熷搧绫诲埆缂栫爜",
+"璁¢噺鍗曚綅":"璁¢噺鍗曚綅",
+"浠锋牸(鍏�)":"浠锋牸(鍏�)",
+"纰崇Н鍒�":"纰崇Н鍒�",
+"鍥哄簾缂栫爜":"鍥哄簾缂栫爜",
+"鍖呭惈鍝佺被":"鍖呭惈鍝佺被",
+"鐜板満":"鐜板満",
+"棰勭害":"棰勭害",
+"澶у睆":"澶у睆",
+"娲诲姩":"娲诲姩",
+"鎺ュ崟":"鎺ュ崟",
+"杩愯緭":"杩愯緭",
+"瀹屾垚":"瀹屾垚",
+"寰呭鐞�":"寰呭鐞�",
+"宸插畬鎴�":"宸插畬鎴�",
+"宸插彇娑�":"宸插彇娑�",
+"璁㈠崟缂栧彿":"璁㈠崟缂栧彿",
+"璁㈠崟绫诲瀷":"璁㈠崟绫诲瀷",
+"閫夋嫨瀛︽牎":"閫夋嫨瀛︽牎",
+"閫夋嫨绔欑偣":"閫夋嫨绔欑偣",
+"閫夋嫨鍒嗘嫞鍛�":"閫夋嫨鍒嗘嫞鍛�",
+"閫夋嫨棰勭害ID":"閫夋嫨棰勭害ID",
+"瀹屾垚鏃堕棿":"瀹屾垚鏃堕棿",
+"鏈烘瀯鍚嶇О":"鏈烘瀯鍚嶇О",
+"瀛﹀彿/宸ュ彿":"瀛﹀彿/宸ュ彿",
+"鍒嗘嫞鍛業D":"鍒嗘嫞鍛業D",
+"棰勭害ID":"棰勭害ID",
+"寰呭鏍�":"寰呭鏍�",
+"寰呮帴鍗�":"寰呮帴鍗�",
+"宸叉帴鍗�":"宸叉帴鍗�",
+"涓婇棬涓�":"涓婇棬涓�",
+"棰勭害缂栧彿":"棰勭害缂栧彿",
+"璇︾粏鍦板潃":"璇︾粏鍦板潃",
+"棰勭害鏃ユ湡":"棰勭害鏃ユ湡",
+"棰勮閲嶉噺":"棰勮閲嶉噺",
+"鐢ㄦ埛濮撳悕":"鐢ㄦ埛濮撳悕",
+"鍒嗘嫞鍛樺鍚�":"鍒嗘嫞鍛樺鍚�",
+"鍥炴敹璁㈠崟":"鍥炴敹璁㈠崟",
+"瀛︽牎ID":"瀛︽牎ID",
+"缁忓害":"缁忓害",
+"绾害":"绾害",
+"棰勭害鏃舵":"棰勭害鏃舵",
+"鎵�灞炴満鏋�":"鎵�灞炴満鏋�",
+"鍒嗘嫞鍛�":"鍒嗘嫞鍛�",
+"鍥炴敹璁㈠崟ID":"鍥炴敹璁㈠崟ID"
+}
\ No newline at end of file
diff --git a/src/modules/shop/locales/zh-tw.json b/src/modules/shop/locales/zh-tw.json
new file mode 100644
index 0000000..ed49d34
--- /dev/null
+++ b/src/modules/shop/locales/zh-tw.json
@@ -0,0 +1,71 @@
+{
+"璁惧鍙�":"璁惧鍙�",
+"璁惧鍚嶇О":"璁惧鍚嶇О",
+"涓婁紶鏃堕棿":"涓婁紶鏃堕棿",
+"鍨冨溇绫诲瀷":"鍨冨溇绫诲瀷",
+"鍨冨溇鍚嶇О":"鍨冨溇鍚嶇О",
+"鍨冨溇閲嶉噺":"鍨冨溇閲嶉噺KG",
+"鍏朵粬鏁版嵁":"鍏朵粬鏁版嵁",
+"绔欑偣缂栧彿":"绔欑偣缂栧彿",
+"绔欑偣鍚嶇О":"绔欑偣鍚嶇О",
+"绔欑偣绫诲瀷":"绔欑偣绫诲瀷",
+"绉伴噸璁惧":"绉伴噸璁惧",
+"weight":"weight",
+"health_gps":"health_gps",
+"vehicle_gps":"vehicle_gps",
+"driver_anomaly":"driver_anomaly",
+"others":"others",
+"鏃ュ織鏃堕棿":"鏃ュ織鏃堕棿",
+"鎺ㄩ�佺被鍨�":"鎺ㄩ�佺被鍨�",
+"瀹㈡埛绔疘D":"瀹㈡埛绔疘D",
+"鍘熷璇锋眰鍐呭":"鍘熷璇锋眰鍐呭",
+"閿欒杩斿洖淇℃伅":"閿欒杩斿洖淇℃伅",
+"鍒涘缓鏃堕棿":"鍒涘缓鏃堕棿",
+"鏇存柊鏃堕棿":"鏇存柊鏃堕棿",
+"璁惧搴忓垪鍙�":"璁惧搴忓垪鍙�",
+"娑堟伅搴忓垪鍙�":"娑堟伅搴忓垪鍙�",
+"娑堟伅鍒涘缓鏃堕棿":"娑堟伅鍒涘缓鏃堕棿",
+"GPS瀹氫綅鏃堕棿":"GPS瀹氫綅鏃堕棿",
+"閫熷害":"閫熷害",
+"鏂瑰悜瑙�":"鏂瑰悜瑙�",
+"娴锋嫈楂樺害":"娴锋嫈楂樺害",
+"瀹氫綅绮惧害":"瀹氫綅绮惧害",
+"鏈夋晥瀹氫綅":"鏈夋晥瀹氫綅",
+"鏃犳晥瀹氫綅":"鏃犳晥瀹氫綅",
+"璁惧ID":"璁惧ID",
+"閫氶亾鍙�":"閫氶亾鍙�",
+"娑堟伅鍞竴ID":"娑堟伅鍞竴ID",
+"娑堟伅鏃堕棿":"娑堟伅鏃堕棿",
+"娑堟伅绫诲瀷":"娑堟伅绫诲瀷",
+"璁惧绫诲瀷":"璁惧绫诲瀷",
+"瀛愬簭鍒楀彿":"瀛愬簭鍒楀彿",
+"婧愮ID":"婧愮ID",
+"NAT鐨処P":"NAT鐨処P",
+"浜嬩欢鍙戠敓鏃堕棿":"浜嬩欢鍙戠敓鏃堕棿",
+"娉ㄥ唽鏃堕棿":"娉ㄥ唽鏃堕棿",
+"閲嶉噺":"閲嶉噺",
+"鍗曚环(鍏�/Kg)":"鍗曚环(鍏�/Kg)",
+"鎬讳环(鍏�)":"鎬讳环(鍏�)",
+"缁忔墜浜篒D":"缁忔墜浜篒D",
+"缁忔墜浜�":"缁忔墜浜�",
+"鏀惰喘鏂�":"鏀惰喘鏂�",
+"鏀堕泦鐐�":"鏀堕泦鐐�",
+"鏃ユ湡鏃堕棿":"鏃ユ湡鏃堕棿",
+"鎬讳环":"鎬讳环",
+"浜ゆ槗鏃堕棿":"浜ゆ槗鏃堕棿",
+"浜ゆ槗绫诲瀷":"浜ゆ槗绫诲瀷",
+"浜ゆ槗瀵规柟":"浜ゆ槗瀵规柟",
+"鍟嗗搧鍚嶇О":"鍟嗗搧鍚嶇О",
+"鏀舵敮绫诲瀷":"鏀舵敮绫诲瀷",
+"閲戦":"閲戦",
+"鏀粯鏂瑰紡":"鏀粯鏂瑰紡",
+"褰撳墠鐘舵��":"褰撳墠鐘舵��",
+"澶囨敞":"澶囨敞",
+"鏂囦欢璺緞":"鏂囦欢璺緞",
+"鏂囦欢鍚�":"鏂囦欢鍚�",
+"浜ゆ槗鍗曞彿":"浜ゆ槗鍗曞彿",
+"鍟嗘埛鍗曞彿":"鍟嗘埛鍗曞彿",
+"鏀跺叆":"鏀跺叆",
+"鏀嚭":"鏀嚭",
+"鍏朵粬":"鍏朵粬"
+}
\ No newline at end of file
diff --git a/src/modules/shop/views/appointment.vue b/src/modules/shop/views/appointment.vue
new file mode 100644
index 0000000..b7fdf7d
--- /dev/null
+++ b/src/modules/shop/views/appointment.vue
@@ -0,0 +1,282 @@
+<template>
+    <cl-crud ref="Crud">
+        <cl-row>
+            <!-- 鍒锋柊鎸夐挳 -->
+            <cl-refresh-btn />
+            <!-- 鏂板鎸夐挳 -->
+            <cl-add-btn />
+            <!-- 鍒犻櫎鎸夐挳 -->
+            <cl-multi-delete-btn />
+            <cl-flex1 />
+            <!-- 鏉′欢鎼滅储 -->
+            <cl-search ref="Search" />
+        </cl-row>
+
+        <cl-row>
+            <!-- 鏁版嵁琛ㄦ牸 -->
+            <cl-table ref="Table" />
+        </cl-row>
+
+        <cl-row>
+            <cl-flex1 />
+            <!-- 鍒嗛〉鎺т欢 -->
+            <cl-pagination />
+        </cl-row>
+
+        <!-- 鏂板銆佺紪杈� -->
+        <cl-upsert ref="Upsert" />
+
+
+
+    </cl-crud>
+</template>
+
+<script lang="ts" setup>
+defineOptions({
+    name: "recycle-appointment"
+});
+
+import { useCrud, useTable, useUpsert, useSearch } from "@cool-vue/crud";
+import { useDict } from '/$/dict';
+import { useCool } from "/@/cool";
+import { useI18n } from "vue-i18n";
+import { reactive, computed } from "vue";
+
+const { service } = useCool();
+const { t } = useI18n();
+const { dict } = useDict();
+
+// 閫夐」
+const options = reactive({
+    status: [
+        { label: t('鏈煡'), value: '鏈煡' },
+        { label: t('寰呭鏍�'), value: '寰呭鏍�' },
+        { label: t('寰呮帴鍗�'), value: '寰呮帴鍗�' },
+        { label: t('宸叉帴鍗�'), value: '宸叉帴鍗�' },
+        { label: t('涓婇棬涓�'), value: '涓婇棬涓�' },
+        { label: t('宸插畬鎴�'), value: '宸插畬鎴�' },
+        { label: t('宸插彇娑�'), value: '宸插彇娑�' }
+    ]
+});
+console.log(
+    JSON.stringify(
+        dict.get('appointmentslot').value,
+        null,
+        2
+    )
+);
+const appointmentSlotOptions = computed(() =>
+    dict.get('appointmentslot').value.map(item => ({
+        label: item.name,
+        value: String(item.value)
+    }))
+);
+
+// cl-upsert
+const Upsert = useUpsert({
+    items: [
+        {
+            label: t('棰勭害缂栧彿'),
+            prop: "appointmentNo",
+            component: { name: "el-input", props: { clearable: true } },
+            span: 12,
+            required: true
+        },
+        {
+            label: t('鑱旂郴浜�'),
+            prop: "contactName",
+            component: { name: "el-input", props: { clearable: true } },
+            span: 12,
+            required: true
+        },
+        {
+            label: t('鑱旂郴鐢佃瘽'),
+            prop: "contactPhone",
+            component: { name: "el-input", props: { clearable: true } },
+            span: 12,
+            required: true
+        },
+        {
+            label: t('璇︾粏鍦板潃'),
+            prop: "address",
+            component: { name: "el-input", props: { type: "textarea", rows: 4 } },
+            required: true
+        },
+        {
+            label: t('缁忓害'),
+            prop: "longitude",
+            hook: "number",
+            component: { name: "el-input-number", props: { min: 0 } },
+            span: 12
+        },
+        {
+            label: t('绾害'),
+            prop: "latitude",
+            hook: "number",
+            component: { name: "el-input-number", props: { min: 0 } },
+            span: 12
+        },
+        {
+            label: t('棰勭害鏃ユ湡'),
+            prop: "appointmentDate",
+            component: {
+                name: "el-date-picker",
+                props: { type: "date", valueFormat: "YYYY-MM-DD" }
+            },
+            span: 12,
+            required: true
+        },
+        {
+            label: '棰勭害鏃堕棿娈�',
+            prop: 'appointmentSlot',
+            component: {
+                name: 'cl-select',
+                props: {
+                    options: appointmentSlotOptions
+                }
+            },
+            required: true
+        },
+        {
+            label: t('棰勮閲嶉噺'),
+            prop: "expectedWeight",
+            hook: "number",
+            component: { name: "el-input-number", props: { min: 0 } },
+            span: 12,
+            required: true
+        },
+        {
+            label: t('鐘舵��'),
+            prop: "status",
+            component: { name: "cl-select", props: { options: options.status } },
+            value: 0,
+            span: 12,
+            required: true
+        },
+        {
+            label: t("閫夋嫨瀛︽牎"),
+            prop: "departmentId",
+            component: {
+                name: 'cl-dept-select',
+                props: {
+                    multiple: false,      // 鍗曢�� / true 澶氶��
+                    placeholder: "璇烽�夋嫨瀛︽牎",
+                    checkStrictly: true,  // 鐖跺瓙鑺傜偣涓嶈仈鍔�
+                    immediate: true       // 绔嬪嵆鍔犺浇閮ㄩ棬鏍�
+                }
+            },
+            required: true,
+        },
+        {
+            label: t('鐢ㄦ埛ID'),
+            prop: "campusUserId",
+            component: { name: "el-input", props: { clearable: true } },
+            span: 12,
+            required: true
+        },
+        {
+            label: t('鐢ㄦ埛濮撳悕'),
+            prop: "campusUserName",
+            component: { name: "el-input", props: { clearable: true } },
+            span: 12,
+            required: true
+        },
+        {
+            label: t('鍒嗘嫞鍛業D'),
+            prop: "sorterId",
+            component: { name: "el-input", props: { clearable: true } },
+            span: 12
+        },
+        {
+            label: t('鍒嗘嫞鍛樺鍚�'),
+            prop: "sorterName",
+            component: { name: "el-input", props: { clearable: true } },
+            span: 12
+        },
+        {
+            label: t('鍥炴敹璁㈠崟'),
+            prop: "recycleOrderId",
+            component: { name: "el-input", props: { clearable: true } },
+            span: 12
+        }
+    ]
+});
+
+// cl-table
+const Table = useTable({
+    columns: [
+        { type: "selection" },
+        { label: t('鐢ㄦ埛ID'), prop: "campusUserId", minWidth: 120 },
+        { label: t('濮撳悕'), prop: "campusUserName", minWidth: 140 },
+        { label: t('棰勭害缂栧彿'), prop: "appointmentNo", minWidth: 140 },
+        { label: t('鑱旂郴浜�'), prop: "contactName", minWidth: 140 },
+        { label: t('鑱旂郴鐢佃瘽'), prop: "contactPhone", minWidth: 140 },
+        {
+            label: t('璇︾粏鍦板潃'),
+            prop: "address",
+            showOverflowTooltip: true,
+            minWidth: 200
+        },
+        /*
+        { label: t('缁忓害'), prop: "longitude", minWidth: 140, sortable: "custom" },
+        { label: t('绾害'), prop: "latitude", minWidth: 140, sortable: "custom" },
+        */
+        {
+            label: t('棰勭害鏃ユ湡'),
+            prop: "appointmentDate",
+            minWidth: 140,
+            sortable: "custom",
+            component: { name: "cl-date-text", props: { format: "YYYY-MM-DD" } }
+        },
+        { label: t('棰勭害鏃堕棿娈�'), prop: "appointmentSlot", minWidth: 120 },
+        {
+            label: t('棰勮閲嶉噺'),
+            prop: "expectedWeight",
+            minWidth: 140,
+            sortable: "custom"
+        },
+        { label: t('鐘舵��'), prop: "status", minWidth: 120, dict: options.status },
+        { label: t('鎵�灞炴満鏋�'), prop: "departmentName", minWidth: 120 },
+        { label: t('鍒嗘嫞鍛�'), prop: "sorterName", minWidth: 120 },
+        {
+            label: t('鍥炴敹璁㈠崟ID'),
+            prop: "recycleOrderId",
+            minWidth: 140,
+            sortable: "custom"
+        },
+        {
+            label: t('鍒涘缓鏃堕棿'),
+            prop: "createTime",
+            minWidth: 170,
+            sortable: "desc",
+            component: { name: "cl-date-text" }
+        },
+        {
+            label: t('鏇存柊鏃堕棿'),
+            prop: "updateTime",
+            minWidth: 170,
+            sortable: "custom",
+            component: { name: "cl-date-text" }
+        },
+        { type: "op", buttons: ["edit", "delete"] }
+    ]
+});
+
+// cl-search
+const Search = useSearch();
+
+// cl-crud
+const Crud = useCrud(
+    {
+        service: service.shop.recycleappointment
+    },
+    (app) => {
+        app.refresh();
+    }
+);
+
+// 鍒锋柊
+function refresh(params?: any) {
+    Crud.value?.refresh(params);
+}
+</script>
diff --git a/src/modules/shop/views/recycleitem.vue b/src/modules/shop/views/recycleitem.vue
new file mode 100644
index 0000000..8ee53d9
--- /dev/null
+++ b/src/modules/shop/views/recycleitem.vue
@@ -0,0 +1,186 @@
+<template>
+    <cl-crud ref="Crud">
+        <cl-row>
+            <!-- 鍒锋柊鎸夐挳 -->
+            <cl-refresh-btn />
+            <!-- 鏂板鎸夐挳 -->
+            <cl-add-btn />
+            <!-- 鍒犻櫎鎸夐挳 -->
+            <cl-multi-delete-btn />
+            <cl-flex1 />
+            <!-- 鏉′欢鎼滅储 -->
+            <cl-search ref="Search" />
+        </cl-row>
+
+        <cl-row>
+            <!-- 鏁版嵁琛ㄦ牸 -->
+            <cl-table ref="Table" />
+        </cl-row>
+
+        <cl-row>
+            <cl-flex1 />
+            <!-- 鍒嗛〉鎺т欢 -->
+            <cl-pagination />
+        </cl-row>
+
+        <!-- 鏂板銆佺紪杈� -->
+        <cl-upsert ref="Upsert" />
+    </cl-crud>
+</template>
+
+<script lang="ts" setup>
+defineOptions({
+    name: "recycle-recycleitem"
+});
+
+import { useCrud, useTable, useUpsert, useSearch } from "@cool-vue/crud";
+import { useCool } from "/@/cool";
+import { useI18n } from "vue-i18n";
+import { reactive } from "vue";
+
+const { service } = useCool();
+const { t } = useI18n();
+
+// 閫夐」
+const options = reactive({
+    status: [
+        { label: t('鍚敤'), value: 0, type: "danger" },
+        { label: t('绂佺敤'), value: 1, type: "success" }
+    ]
+});
+
+// cl-upsert
+const Upsert = useUpsert({
+    items: [
+        {
+            label: t('鍙洖鏀剁墿缂栫爜'),
+            prop: "itemId",
+            component: { name: "el-input", props: { clearable: true } },
+            span: 12,
+            required: true
+        },
+        {
+            label: t("閫夋嫨閮ㄩ棬"),
+            prop: "departmentId",
+            component: {
+                name: 'cl-dept-select',
+                props: {
+                    multiple: false,      // 鍗曢��
+                    placeholder: "璇烽�夋嫨閮ㄩ棬",
+                    checkStrictly: true,  // 鐖跺瓙鑺傜偣涓嶈仈鍔�
+                    immediate: true       // 绔嬪嵆鍔犺浇閮ㄩ棬鏍�
+                }
+            },
+            span: 12,
+            required: true,
+        },
+
+        {
+            label: t('鍚嶇О'),
+            prop: "itemName",
+            component: { name: "el-input", props: { clearable: true } },
+            span: 12,
+            required: true
+        },
+        {
+            label: t('鍖呭惈鍝佺被'),
+            prop: "including",
+            component: { name: "el-input", props: { clearable: true } },
+            span: 12
+        },
+        {
+            label: t('璁¢噺鍗曚綅'),
+            prop: "unit",
+            component: { name: "el-input", props: { clearable: true } },
+            span: 12,
+            required: true
+        },
+        {
+            label: t('浠锋牸(鍏�)'),
+            prop: "price",
+            hook: "number",
+            component: { name: "el-input-number", props: { min: 0 } },
+            span: 12,
+            required: true
+        },
+        {
+            label: t('纰崇Н鍒�'),
+            prop: "carbonPoint",
+            hook: "number",
+            component: { name: "el-input-number", props: { min: 0 } },
+            span: 12,
+            required: true
+        },
+        {
+            label: t('鍥哄簾缂栫爜'),
+            prop: "category",
+            component: { name: "el-input", props: { clearable: true } },
+            span: 12,
+            required: true
+        },
+        {
+            label: t('鐘舵��'),
+            prop: "status",
+            component: { name: "el-radio-group", options: options.status },
+            value: 0,
+            required: true
+        }
+    ]
+});
+
+// cl-table
+const Table = useTable({
+    columns: [
+        { type: "selection" },
+        { label: t('鍙洖鏀剁墿缂栫爜'), prop: "itemId", minWidth: 120 },
+        { label: t('鍚嶇О'), prop: "itemName", minWidth: 140 },
+        { label: t('鍖呭惈鍝佺被'), prop: "including", minWidth: 140 },
+        { label: t('鎵�灞炲崟浣�'), prop: "departmentName", minWidth: 140 },
+        { label: t('璁¢噺鍗曚綅'), prop: "unit", minWidth: 140 },
+        { label: t('浠锋牸(鍏�)'), prop: "price", minWidth: 140, sortable: "custom" },
+        { label: t('纰崇Н鍒�'), prop: "carbonPoint", minWidth: 140, sortable: "custom" },
+        { label: t('鍥哄簾缂栫爜'), prop: "category", minWidth: 120 },
+        {
+            label: t('鐘舵��'),
+            prop: "status",
+            minWidth: 100,
+            component: { name: "cl-switch" },
+            dict: options.status
+        },
+        /*{
+            label: t('鍒涘缓鏃堕棿'),
+            prop: "createTime",
+            minWidth: 170,
+            sortable: "desc",
+            component: { name: "cl-date-text" }
+        },
+        */
+        {
+            label: t('鏇存柊鏃堕棿'),
+            prop: "updateTime",
+            minWidth: 170,
+            sortable: "custom",
+            component: { name: "cl-date-text" }
+        },
+        { type: "op", buttons: ["edit", "delete"] }
+    ]
+});
+
+// cl-search
+const Search = useSearch();
+
+// cl-crud
+const Crud = useCrud(
+    {
+        service: service.shop.recycleitem
+    },
+    (app) => {
+        app.refresh();
+    }
+);
+
+// 鍒锋柊
+function refresh(params?: any) {
+    Crud.value?.refresh(params);
+}
+</script>
diff --git a/src/modules/shop/views/recycleorder.vue b/src/modules/shop/views/recycleorder.vue
new file mode 100644
index 0000000..9788baa
--- /dev/null
+++ b/src/modules/shop/views/recycleorder.vue
@@ -0,0 +1,240 @@
+<template>
+    <cl-crud ref="Crud">
+        <cl-row>
+            <!-- 鍒锋柊鎸夐挳 -->
+            <cl-refresh-btn />
+            <!-- 鏂板鎸夐挳 -->
+            <cl-add-btn />
+            <!-- 鍒犻櫎鎸夐挳 -->
+            <cl-multi-delete-btn />
+            <cl-flex1 />
+            <!-- 鏉′欢鎼滅储 -->
+            <cl-search ref="Search" />
+        </cl-row>
+
+        <cl-row>
+            <!-- 鏁版嵁琛ㄦ牸 -->
+            <cl-table ref="Table" />
+        </cl-row>
+
+        <cl-row>
+            <cl-flex1 />
+            <!-- 鍒嗛〉鎺т欢 -->
+            <cl-pagination />
+        </cl-row>
+
+        <!-- 鏂板銆佺紪杈� -->
+        <cl-upsert ref="Upsert" />
+
+        <!-- 鍟嗗搧瑙勬牸 -->
+        <order-detail :ref="setRefs('orderDetail')" />
+    </cl-crud>
+</template>
+
+<script lang="ts" setup>
+defineOptions({
+    name: "recycle-order"
+});
+
+import { useCrud, useTable, useUpsert, useSearch } from "@cool-vue/crud";
+import { useCool } from "/@/cool";
+import { useI18n } from "vue-i18n";
+import { reactive } from "vue";
+import OrderDetail from '../components/order-detail.vue';
+
+const { service, refs, setRefs } = useCool();
+
+const { t } = useI18n();
+
+// 閫夐」
+const options = reactive({
+    orderType: [
+        { label: t('鐜板満'), value: '鐜板満' },
+        { label: t('棰勭害'), value: '棰勭害' },
+        { label: t('澶у睆'), value: '澶у睆' },
+        { label: t('娲诲姩'), value: '娲诲姩' }
+    ],
+    status: [
+        { label: t('棰勭害'), value: '棰勭害' },
+        { label: t('鎺ュ崟'), value: '鎺ュ崟' },
+        { label: t('杩愯緭'), value: '杩愯緭' },
+        { label: t('瀹屾垚'), value: '瀹屾垚' },
+        { label: t('鍙栨秷'), value: '鍙栨秷' },
+        { label: t('鏈煡'), value: '鏈煡' },
+    ]
+});
+
+// cl-upsert
+const Upsert = useUpsert({
+    items: [
+        {
+            label: t('璁㈠崟缂栧彿'),
+            prop: "orderNo",
+            component: { name: "el-input", props: { clearable: true } },
+            span: 12,
+            required: true
+        },
+        {
+            label: t('璁㈠崟绫诲瀷'),
+            prop: "orderType",
+            component: { name: "el-radio-group", options: options.orderType },
+            value: "鐜板満",
+            required: true
+        },
+        {
+            label: t("閫夋嫨瀛︽牎"),
+            prop: "departmentId",
+            component: {
+                name: 'cl-dept-select',
+                props: {
+                    multiple: false,      // 鍗曢�� / true 澶氶��
+                    placeholder: "璇烽�夋嫨瀛︽牎",
+                    checkStrictly: true,  // 鐖跺瓙鑺傜偣涓嶈仈鍔�
+                    immediate: true       // 绔嬪嵆鍔犺浇閮ㄩ棬鏍�
+                }
+            },
+            required: true,
+        },
+        {
+            label: t('閫夋嫨鐢ㄦ埛'),
+            prop: "campusUserId",
+            component: { name: "el-input", props: { clearable: true } },
+            span: 12,
+            required: true
+        },
+        {
+            label: t('閫夋嫨绔欑偣'),
+            prop: "siteId",
+            component: { name: "el-input", props: { clearable: true } },
+            span: 12,
+            required: true
+        },
+        {
+            label: t('閫夋嫨鍒嗘嫞鍛�'),
+            prop: "sorterId",
+            component: { name: "el-input", props: { clearable: true } },
+            span: 12,
+            required: true
+        },
+        {
+            label: t('閫夋嫨棰勭害ID'),
+            prop: "appointmentId",
+            component: { name: "el-input", props: { clearable: true } },
+            span: 12
+        },
+        {
+            label: t('鐘舵��'),
+            prop: "status",
+            component: { name: "cl-select", props: { options: options.status } },
+            value: "鏈煡",
+            span: 12,
+            required: true
+        },
+        {
+            label: t('鎬婚噸閲�'),
+            prop: "totalWeight",
+            hook: "number",
+            component: { name: "el-input-number", props: { min: 0 } },
+            span: 12,
+            required: true
+        },
+        {
+            label: t('纰崇Н鍒�'),
+            prop: "carbonPoint",
+            hook: "number",
+            component: { name: "el-input-number", props: { min: 0 } },
+            span: 12,
+            required: true
+        },
+        {
+            label: t('瀹屾垚鏃堕棿'),
+            prop: "finishTime",
+            component: {
+                name: "el-date-picker",
+                props: { type: "datetime", valueFormat: "YYYY-MM-DD HH:mm:ss" }
+            },
+            span: 12
+        }
+    ]
+});
+
+// cl-table
+const Table = useTable({
+    columns: [
+        { type: "selection" },
+        { label: t('鏈烘瀯鍚嶇О'), prop: "departmentName", minWidth: 140 },
+        { label: t('鎵嬫満鍙�'), prop: "campusUserPhone", minWidth: 140 },
+        { label: t('鐢ㄦ埛鍚�'), prop: "campusUserName", minWidth: 140 },
+        { label: t('璁㈠崟缂栧彿'), prop: "orderNo", minWidth: 140 },
+        {
+            label: t('璁㈠崟绫诲瀷'),
+            prop: "orderType",
+            minWidth: 120,
+            dict: options.orderType
+        },
+        { label: t('瀛﹀彿/宸ュ彿'), prop: "campusUserId", minWidth: 120 },
+        { label: t('绔欑偣ID'), prop: "siteId", minWidth: 120 },
+        { label: t('鍒嗘嫞鍛業D'), prop: "sorterId", minWidth: 120 },
+        { label: t('棰勭害ID'), prop: "appointmentId", minWidth: 120 },
+        { label: t('鐘舵��'), prop: "status", minWidth: 120, dict: options.status },
+        { label: t('鎬婚噸閲�'), prop: "totalWeight", minWidth: 140, sortable: "custom" },
+        { label: t('纰崇Н鍒�'), prop: "carbonPoint", minWidth: 140, sortable: "custom" },
+        {
+            label: t('瀹屾垚鏃堕棿'),
+            prop: "finishTime",
+            minWidth: 170,
+            sortable: "custom",
+            component: { name: "cl-date-text" }
+        },
+        {
+            label: t('鍒涘缓鏃堕棿'),
+            prop: "createTime",
+            minWidth: 170,
+            sortable: "desc",
+            component: { name: "cl-date-text" }
+        },
+        /*{
+            label: t('鏇存柊鏃堕棿'),
+            prop: "updateTime",
+            minWidth: 170,
+            sortable: "custom",
+            component: { name: "cl-date-text" }
+        },
+        */
+        {
+            type: 'op',
+            width: 300,
+            buttons({ scope }) {
+                return [
+                    'edit',
+                    'delete',
+                    {
+                        label: '璇︽儏',
+                        onClick() {
+                            refs.orderDetail.open(scope.row.orderNo);
+                        }
+                    }
+                ];
+            }
+        }
+    ]
+});
+
+// cl-search
+const Search = useSearch();
+
+// cl-crud
+const Crud = useCrud(
+    {
+        service: service.shop.recycleorder
+    },
+    (app) => {
+        app.refresh();
+    }
+);
+
+// 鍒锋柊
+function refresh(params?: any) {
+    Crud.value?.refresh(params);
+}
+</script>
diff --git a/src/modules/shop/views/recycleorderitem.vue b/src/modules/shop/views/recycleorderitem.vue
new file mode 100644
index 0000000..fd18d1f
--- /dev/null
+++ b/src/modules/shop/views/recycleorderitem.vue
@@ -0,0 +1,171 @@
+<template>
+    <cl-crud ref="Crud">
+        <cl-row>
+            <!-- 鍒锋柊鎸夐挳 -->
+            <cl-refresh-btn />
+            <!-- 鏂板鎸夐挳 -->
+            <cl-add-btn />
+            <!-- 鍒犻櫎鎸夐挳 -->
+            <cl-multi-delete-btn />
+            <cl-flex1 />
+            <!-- 鏉′欢鎼滅储 -->
+            <cl-search ref="Search" />
+        </cl-row>
+
+        <cl-row>
+            <!-- 鏁版嵁琛ㄦ牸 -->
+            <cl-table ref="Table" />
+        </cl-row>
+
+        <cl-row>
+            <cl-flex1 />
+            <!-- 鍒嗛〉鎺т欢 -->
+            <cl-pagination />
+        </cl-row>
+
+        <!-- 鏂板銆佺紪杈� -->
+        <cl-upsert ref="Upsert" />
+    </cl-crud>
+</template>
+
+<script lang="ts" setup>
+defineOptions({
+    name: "recycle-recycleorderitem"
+});
+
+import { useCrud, useTable, useUpsert, useSearch } from "@cool-vue/crud";
+import { useCool } from "/@/cool";
+import { useI18n } from "vue-i18n";
+import { reactive } from "vue";
+import OrderSelect from "/$/recycle/components/order-select.vue";
+import ItemSelect from "/$/recycle/components/item-select.vue";
+
+const { service } = useCool();
+const { t } = useI18n();
+
+// 閫夐」
+const options = reactive({
+    status: [
+        { label: t('鍚敤'), value: 0, type: "danger" },
+        { label: t('绂佺敤'), value: 1, type: "success" }
+    ]
+});
+
+// cl-upsert
+const Upsert = useUpsert({
+    items: [
+        {
+            label: t('閫夋嫨璁㈠崟'),
+            prop: "orderId",
+            component: { vm: OrderSelect },
+            span: 12,
+            required: true
+        },
+        {
+            label: t('鍙洖鏀剁墿鍝佺紪鍙�'),
+            prop: "typeId",
+            component: { name: "el-input", props: { clearable: true } },
+            span: 12,
+            required: true
+        },
+        {
+            label: t('鍚嶇О'),
+            prop: "name",
+            component: { name: "el-input", props: { clearable: true } },
+            span: 12,
+            required: true
+        },
+        {
+            label: t('璁¢噺鍗曚綅'),
+            prop: "unit",
+            component: { name: "el-input", props: { clearable: true } },
+            span: 12,
+            required: true
+        },
+        {
+            label: t('浠锋牸(鍏�)'),
+            prop: "price",
+            hook: "number",
+            component: { name: "el-input-number", props: { min: 0 } },
+            span: 12,
+            required: true
+        },
+        {
+            label: t('纰崇Н鍒�'),
+            prop: "carbonPoint",
+            hook: "number",
+            component: { name: "el-input-number", props: { min: 0 } },
+            span: 12,
+            required: true
+        },
+        {
+            label: t('鍥哄簾缂栫爜'),
+            prop: "category",
+            component: { name: "el-input", props: { clearable: true } },
+            span: 12,
+            required: true
+        },
+        {
+            label: t('鐘舵��'),
+            prop: "status",
+            component: { name: "el-radio-group", options: options.status },
+            value: 0,
+            required: true
+        }
+    ]
+});
+
+// cl-table
+const Table = useTable({
+    columns: [
+        { type: "selection" },
+        { label: t('璁㈠崟缂栧彿'), prop: "orderNo", minWidth: 140 },
+        { label: t('鍙洖鏀剁墿缂栫爜'), prop: "typeId", minWidth: 120 },
+        { label: t('鍚嶇О'), prop: "name", minWidth: 140 },
+        { label: t('璁¢噺鍗曚綅'), prop: "unit", minWidth: 140 },
+        { label: t('浠锋牸(鍏�)'), prop: "price", minWidth: 140, sortable: "custom" },
+        { label: t('纰崇Н鍒�'), prop: "carbonPoint", minWidth: 140, sortable: "custom" },
+        { label: t('鍥哄簾缂栫爜'), prop: "category", minWidth: 120 },
+        {
+            label: t('鐘舵��'),
+            prop: "status",
+            minWidth: 100,
+            component: { name: "cl-switch" },
+            dict: options.status
+        },
+        {
+            label: t('鍒涘缓鏃堕棿'),
+            prop: "createTime",
+            minWidth: 170,
+            sortable: "desc",
+            component: { name: "cl-date-text" }
+        },
+        {
+            label: t('鏇存柊鏃堕棿'),
+            prop: "updateTime",
+            minWidth: 170,
+            sortable: "custom",
+            component: { name: "cl-date-text" }
+        },
+        { type: "op", buttons: ["edit", "delete"] }
+    ]
+});
+
+// cl-search
+const Search = useSearch();
+
+// cl-crud
+const Crud = useCrud(
+    {
+        service: service.shop.recycleorderitem
+    },
+    (app) => {
+        app.refresh();
+    }
+);
+
+// 鍒锋柊
+function refresh(params?: any) {
+    Crud.value?.refresh(params);
+}
+</script>
diff --git a/src/modules/shop/views/transaction.vue b/src/modules/shop/views/transaction.vue
new file mode 100644
index 0000000..55f022c
--- /dev/null
+++ b/src/modules/shop/views/transaction.vue
@@ -0,0 +1,173 @@
+<template>
+    <cl-crud ref="Crud">
+        <cl-row>
+            <!-- 鍒锋柊鎸夐挳 -->
+            <cl-refresh-btn />
+            <!-- 鏂板鎸夐挳 -->
+            <cl-add-btn />
+            <!-- 鍒犻櫎鎸夐挳 -->
+            <cl-multi-delete-btn />
+            <cl-flex1 />
+            <!-- 鏉′欢鎼滅储 -->
+            <cl-search ref="Search" />
+        </cl-row>
+
+        <cl-row>
+            <!-- 鏁版嵁琛ㄦ牸 -->
+            <cl-table ref="Table" />
+        </cl-row>
+
+        <cl-row>
+            <cl-flex1 />
+            <!-- 鍒嗛〉鎺т欢 -->
+            <cl-pagination />
+        </cl-row>
+
+        <!-- 鏂板銆佺紪杈� -->
+        <cl-upsert ref="Upsert" />
+    </cl-crud>
+</template>
+
+<script lang="ts" setup>
+defineOptions({
+    name: "finance-transaction"
+});
+
+import { useCrud, useTable, useUpsert, useSearch } from "@cool-vue/crud";
+import { useCool } from "/@/cool";
+import { useI18n } from "vue-i18n";
+import { reactive } from "vue";
+import UserSelect from "/$/user/components/user-select.vue";
+
+const { service } = useCool();
+const { t } = useI18n();
+
+// 閫夐」
+const options = reactive({
+    type: [
+        { label: t('鏀跺叆'), value: "鏀跺叆", type: "danger" },
+        { label: t('鏀嚭'), value: "鏀嚭", type: "success" }
+    ],
+    sourceType: [
+        { label: t('鍥炴敹'), value: '鍥炴敹' },
+        { label: t('鍟嗗煄'), value: '鍟嗗煄' },
+        { label: t('璧犻��'), value: '璧犻��' },
+        { label: t('鎺ュ彈'), value: '鎺ュ彈' },
+        { label: t('鎹愯禒'), value: '鎹愯禒' },
+        { label: t('绛惧埌'), value: '绛惧埌' },
+    ]
+});
+
+// cl-upsert
+const Upsert = useUpsert({
+    items: [
+        {
+            label: t('閫夋嫨鐢ㄦ埛'),
+            prop: "userId",
+            component: { vm: UserSelect },
+            required: true
+        },
+        {
+            label: t("閫夋嫨瀛︽牎"),
+            prop: "departmentId",
+            component: {
+                name: 'cl-dept-select',
+                props: {
+                    multiple: false,      // 鍗曢�� / true 澶氶��
+                    placeholder: "璇烽�夋嫨瀛︽牎",
+                    checkStrictly: true,  // 鐖跺瓙鑺傜偣涓嶈仈鍔�
+                    immediate: true       // 绔嬪嵆鍔犺浇閮ㄩ棬鏍�
+                }
+            },
+            required: true,
+        },
+        {
+            label: t('娴佹按绫诲瀷'),
+            prop: "type",
+            component: { name: "el-radio-group", options: options.type },
+            value: "鏀跺叆",
+            required: true
+        },
+        {
+            label: t('閲戦'),
+            prop: "amount",
+            hook: "number",
+            component: { name: "el-input-number", props: { min: 0 } },
+            span: 12,
+            required: true
+        },
+        {
+            label: t('鏉ユ簮绫诲瀷'),
+            prop: "sourceType",
+            component: { name: "cl-select", props: { options: options.sourceType } },
+            value: "鍥炴敹",
+            span: 12,
+            required: true
+        },
+        {
+            label: t('澶囨敞'),
+            prop: "remark",
+            component: { name: "el-input", props: { type: "textarea", rows: 4 } }
+        },
+        {
+            label: t('璁㈠崟ID'),
+            prop: "orderId",
+            component: { name: "el-input", props: { clearable: true } },
+            span: 12
+        }
+    ]
+});
+
+// cl-table
+const Table = useTable({
+    columns: [
+        { type: "selection" },
+        { label: t('鏄电О'), prop: "userName", minWidth: 140 },
+        { label: t('鎵�灞炴満鏋�'), prop: "departmentName", minWidth: 120 },
+        { label: t('娴佹按绫诲瀷'), prop: "type", minWidth: 120, dict: options.type },
+        { label: t('閲戦'), prop: "amount", minWidth: 140, sortable: "custom" },
+        {
+            label: t('鏉ユ簮绫诲瀷'),
+            prop: "sourceType",
+            minWidth: 120,
+            dict: options.sourceType
+        },
+        { label: t('澶囨敞'), prop: "remark", showOverflowTooltip: true, minWidth: 200 },
+        { label: t('璁㈠崟ID'), prop: "orderId", minWidth: 140 },
+        {
+            label: t('鍒涘缓鏃堕棿'),
+            prop: "createTime",
+            minWidth: 170,
+            sortable: "desc",
+            component: { name: "cl-date-text" }
+        },
+        /*{
+            label: t('鏇存柊鏃堕棿'),
+            prop: "updateTime",
+            minWidth: 170,
+            sortable: "custom",
+            component: { name: "cl-date-text" }
+        },
+        */
+        { type: "op", buttons: ["edit", "delete"] }
+    ]
+});
+
+// cl-search
+const Search = useSearch();
+
+// cl-crud
+const Crud = useCrud(
+    {
+        service: service.shop.transaction
+    },
+    (app) => {
+        app.refresh();
+    }
+);
+
+// 鍒锋柊
+function refresh(params?: any) {
+    Crud.value?.refresh(params);
+}
+</script>
diff --git a/src/modules/user/locales/zh-cn.json b/src/modules/user/locales/zh-cn.json
index 9c2cba2..91ac918 100644
--- a/src/modules/user/locales/zh-cn.json
+++ b/src/modules/user/locales/zh-cn.json
@@ -16,5 +16,15 @@
   "鐘舵��": "鐘舵��",
   "鍒涘缓鏃堕棿": "鍒涘缓鏃堕棿",
   "閫夋嫨鐢ㄦ埛": "閫夋嫨鐢ㄦ埛",
-  "濮撳悕": "濮撳悕"
+  "濮撳悕": "濮撳悕",
+  "瀛︾敓": "瀛︾敓",
+  "鑰佸笀": "鑰佸笀",
+  "瀛﹂櫌鍚嶇О": "瀛﹂櫌鍚嶇О",
+  "涓撲笟鍚嶇О": "涓撲笟鍚嶇О",
+  "鐝骇鍚嶇О": "鐝骇鍚嶇О",
+  "瀛﹂櫌ID": "瀛﹂櫌ID",
+  "涓撲笟ID": "涓撲笟ID",
+  "鐝骇ID": "鐝骇ID",
+  "纰崇Н鍒嗘�婚":"纰崇Н鍒嗘�婚",
+  "鍞竴鏍囪瘑":"鍞竴鏍囪瘑"
 }
\ No newline at end of file
diff --git a/src/modules/user/views/list.vue b/src/modules/user/views/list.vue
index f1b69bf..7912719 100644
--- a/src/modules/user/views/list.vue
+++ b/src/modules/user/views/list.vue
@@ -3,6 +3,8 @@
 		<cl-row>
 			<!-- 鍒锋柊鎸夐挳 -->
 			<cl-refresh-btn />
+			<!-- 鏂板鎸夐挳 -->
+			<cl-add-btn />
 			<!-- 鍒犻櫎鎸夐挳 -->
 			<cl-multi-delete-btn />
 			<cl-flex1 />
@@ -90,6 +92,20 @@
 			value: 2,
 			type: 'warning'
 		}
+	],
+	role: [
+		{
+			label: t('瀛︾敓'),
+			value: '瀛︾敓'
+		},
+		{
+			label: t('鑰佸笀'),
+			value: '鑰佸笀'
+		},
+		{
+			label: t('鏈煡'),
+			value: '鏈煡'
+		}
 	]
 });
 
@@ -101,8 +117,18 @@
 			width: 60
 		},
 		{
+			label: t('鎵�灞炲崟浣�'),
+			prop: 'departmentName',
+			minWidth: 150
+		},
+		{
 			label: t('鏄电О'),
 			prop: 'nickName',
+			minWidth: 150
+		},
+		{
+			label: t('鐢熸棩'),
+			prop: 'birthday',
 			minWidth: 150
 		},
 		{
@@ -119,9 +145,34 @@
 			minWidth: 120
 		},
 		{
+			label: t('瑙掕壊'),
+			prop: 'role',
+			minWidth: 120
+		},
+		{
 			label: t('鎬у埆'),
 			prop: 'gender',
 			dict: options.gender,
+			minWidth: 120
+		},
+		{
+			label: t('纰崇Н鍒嗘�婚'),
+			prop: 'carbonBalance',
+			minWidth: 120
+		},
+		{
+			label: t('瀛﹂櫌鍚嶇О'),
+			prop: 'collegeName',
+			minWidth: 120
+		},
+		{
+			label: t('涓撲笟鍚嶇О'),
+			prop: 'majorName',
+			minWidth: 120
+		},
+		{
+			label: t('鐝骇鍚嶇О'),
+			prop: 'className',
 			minWidth: 120
 		},
 		{
@@ -157,11 +208,37 @@
 			component: { name: 'cl-upload' }
 		},
 		{
+			label: t("閫夋嫨閮ㄩ棬"),
+			prop: "departmentId",
+			component: {
+				name: 'cl-dept-select',
+				props: {
+					multiple: false,      // 鍗曢�� / true 澶氶��
+					placeholder: "璇烽�夋嫨閮ㄩ棬",
+					checkStrictly: true,  // 鐖跺瓙鑺傜偣涓嶈仈鍔�
+					immediate: true       // 绔嬪嵆鍔犺浇閮ㄩ棬鏍�
+				}
+			},
+			required: true,
+		},
+		{
+			prop: 'unionid',
+			label: t('鍞竴鏍囪瘑'),
+			component: { name: 'el-input' },
+			required: true
+		},
+		{
 			prop: 'nickName',
 			label: t('鏄电О'),
 			component: { name: 'el-input' },
 			required: true
 		},
+		{
+			prop: 'birthday',
+			label: t('鐢熸棩'),
+			component: { name: 'el-input' }
+		},
+
 		{
 			prop: 'phone',
 			label: t('鎵嬫満鍙�'),
@@ -182,6 +259,51 @@
 			}
 		},
 		{
+			prop: 'role',
+			label: t('瑙掕壊'),
+			value: '瀛︾敓',
+			component: {
+				name: 'el-radio-group',
+				options: options.role
+			}
+		},
+		{
+			prop: 'carbonBalance',
+			label: t('纰崇Н鍒嗘�婚'),
+			value: 0,
+			component: { name: 'el-input' }
+		},
+		{
+			prop: 'collegeId',
+			label: t('瀛﹂櫌ID'),
+			component: { name: 'el-input' }
+		},
+		{
+			prop: 'collegeName',
+			label: t('瀛﹂櫌鍚嶇О'),
+			component: { name: 'el-input' }
+		},
+		{
+			prop: 'majorId',
+			label: t('涓撲笟ID'),
+			component: { name: 'el-input' }
+		},
+		{
+			prop: 'majorName',
+			label: t('涓撲笟鍚嶇О'),
+			component: { name: 'el-input' }
+		},
+		{
+			prop: 'classId',
+			label: t('鐝骇ID'),
+			component: { name: 'el-input' }
+		},
+		{
+			prop: 'className',
+			label: t('鐝骇鍚嶇О'),
+			component: { name: 'el-input' }
+		},
+		{
 			prop: 'status',
 			label: t('鐘舵��'),
 			value: 1,
diff --git "a/\345\244\247\344\273\266\345\236\203\345\234\276\345\233\236\346\224\266.txt" "b/\345\244\247\344\273\266\345\236\203\345\234\276\345\233\236\346\224\266.txt"
new file mode 100644
index 0000000..3050b8d
--- /dev/null
+++ "b/\345\244\247\344\273\266\345\236\203\345\234\276\345\233\236\346\224\266.txt"
@@ -0,0 +1,90 @@
+鐜板湪缁欎竴涓鏍$殑鍨冨溇鍥炴敹杩愯惀骞冲彴璁捐涓�涓ā鍧楋紝鍗冲垎鎷d汉鍛樼鐞嗘ā鍧椼��
+
+浜哄憳浠ヨ韩浠借瘉鍙风爜涓哄敮涓�鏍囪瘑锛屼互鎵嬫満鍙风爜涓虹櫥褰曡处鍙凤紝瀵嗙爜鍗曠嫭璁剧疆銆�
+
+
+
+涓氬姟鍔熻兘鍖呮嫭锛�
+
+鐧诲綍 鎵撳崱宸ヤ綔锛屾墦鍗′笅鐝紱
+
+gps浣嶇疆闄勫甫鍋ュ悍鏁版嵁锛屾瘡闂撮殧5鍒嗛挓锛� 鍋ュ悍鏁版嵁鍖呮嫭浣撴俯锛岃鍘嬶紝琛�姘�... 绛夈��
+
+瀹炴椂娑堟伅锛岀被浼间簬鍛婅鏁版嵁锛屾瘮濡備綋娓╁紓甯革紝琛�鍘嬪紓甯革紝琛�姘у紓甯哥瓑锛岃繖涓槸鍗虫椂鐨勶紝涓嶅彈鍒�5鍒嗛挓鐨勯檺鍒躲��
+
+
+
+鎵�鏈夌浉鍏崇殑琛ㄦ牸锛岄兘鍖呭惈韬唤璇佸彿鐮佸拰departmentId(departmentId 浠� sorter鍩烘湰淇℃伅琛ㄤ腑鑾峰彇)銆�
+
+
+
+杩樻湁涓�绫荤敤鎴锋槸瀛︽牎甯堢敓锛屼互宸ュ彿/瀛﹀彿涓哄敮涓�鏍囪瘑锛屽彟澶栧叾褰掑睘浜庝竴涓鏍¢櫌绯伙紝涓撲笟锛岀彮绾с��
+
+
+
+姣忎釜甯堢敓鏈変竴涓⒊绉垎balance琛紝鎶婂彲鍥炴敹鐗╅�佸埌鏀堕泦鐐规垨鑰呮槸棰勭害涓婇棬鍚庯紝鎶婂厬鎹㈡垚纰崇Н鍒嗭紝 浠庣⒊绉垎鍟嗗煄璐拱锛岃浆澧炵粰鍏朵粬浜猴紝鎴栬�呮棤鍋挎崘璧狅紝杩欎簺鏄⒊绉垎鐨勬秷鑰楄涓恒��
+
+
+
+鍚庢湡浼氳鎴愭寜鐓х彮绾э紝涓撲笟锛岄櫌绯伙紝鎬绘暟鍜屼汉鍧囩殑纰崇Н鍒嗘帓鍚嶇瓑銆�
+
+
+
+棰勭害涓婇棬鍥炴敹鍙洖鏀剁墿绫讳技浜庝竴涓鍗曠殑绠$悊锛屼粠棰勭害寮�濮嬶紝鍒版渶缁堢粨鏉熴��
+
+
+
+
+
+鍨冨溇鐨勫ぇ绫伙紝鍖呮嫭锛�
+
+鏈夋瘨鏈夊  \x6709\x6BD2\x6709\x5BB3
+
+婀垮瀮鍦�   \x6E7F\x5783\x573E
+
+鍙洖鏀�-搴熺焊寮�     \x53EF\x56DE\x6536\x002D\x5E9F\x7EB8\x5F20
+
+鍙洖鏀�-搴熷鏂�   \x53EF\x56DE\x6536\x002D\x5E9F\x5851\x6599
+
+鍙洖鏀�-搴熼噾灞�   \x53EF\x56DE\x6536\x002D\x5E9F\x91D1\x5C5E
+
+鍙洖鏀�-搴熺幓鐠�   \x53EF\x56DE\x6536\x002D\x5E9F\x73BB\x7483
+
+鍙洖鏀�-搴熺粐鐗�   \x53EF\x56DE\x6536\x002D\x5E9F\x7EC7\x7269\x0020
+
+鍙洖鏀�-搴熸棫鐢靛瓙浜у搧   \x53EF\x56DE\x6536\x002D\x5E9F\x65E7\x7535\x5B50\x4EA7\x54C1
+
+鍙洖鏀�-搴熸棫鐢垫簮鐢垫睜   \x53EF\x56DE\x6536\x002D\x5E9F\x65E7\x7535\x6E90\x7535\x6C60
+
+澶т欢鍨冨溇   \x5927\x4EF6\x5783\x573E
+
+骞插瀮鍦�   \x5E72\x5783\x573E
+
+
+
+浣嗘槸浠庡笀鐢熺殑瀛︽牎鍦烘櫙鍑哄彂锛屾妸鏃ュ父鑳借鍒扮殑涓�浜涘彲鍥炴敹鐗�-鐗╁搧 瀵瑰簲鍒拌繖浜涘ぇ绫伙紝浠庤�岃浆鎹㈡垚涓�濂楀熀纭�淇℃伅琛�/閰嶇疆琛紝鍗虫瘡绉嶇被鐨勫彲鍥炴敹鐗╃殑鍞环锛屾柟渚垮笀鐢熷拰鍒嗘嫞浜哄憳鐩镐簰浜嗚В銆�
+
+
+
+鏃犺鏄洖鏀跺彲鍥炴敹鐗�-涓哄笀鐢熷彂鏀剧⒊绉垎锛岃繕鏄喘涔扮⒊绉垎鐨勫鏍℃枃鍒涘皬鍟嗗搧锛岄兘鏄敱鍒嗘嫞浜哄憳鏉ユ壂鐮�(甯堢敓鐨勫敮涓�浜岀淮鐮�/瀛︽牎涓�鐮侀��)锛岃繖鏍风‘璁ゆ祦绋嬫槸ok銆�
+
+
+
+鏈�缁堣繕鑳芥寜鐓у垎鎷g偣锛屽垎鎷d汉鍛樻潵缁熻鍚勭被閲嶉噺锛屼环鍊肩瓑锛屼笂闂ㄥ洖鏀惰鍗曟暟閲忥紝閿�鍞枃鍒涘晢鍝佹暟閲忕瓑銆�
+
+
+
+鍨冨溇鍒嗘嫞鐐硅繕瑕佹斁涓�涓珛鏌卞紡鐨勫睆骞曪紝璁╁鐢熻嚜宸辨墦鍗�-灏辨槸鍙姇閫掍簡闈炲父灏戠殑浠峰�肩殑骞插瀮鍦炬垨鑰呭彲鍥炴敹鐗╋紝灏卞浐瀹氬彂鏀�5涓⒊绉垎锛屽氨涓嶇敤鍒嗘嫞浜哄憳鐨刟ndroid app 鏉ユ搷浣滐紝锛屽氨鐢卞ぇ灞忎笂鐨勬壂鐮佹満鍣ㄦ壂闈紝涔嬪悗寮瑰嚭涓�涓伃鍠滄垨鑰呮劅璋箣绫荤殑銆�
+
+
+
+
+
+浣犲厛甯垜鍒嗘瀽涓�浜涜繖浜涘疄浣撳叧绯伙紝璁捐涓�涓嬫暟鎹粨鏋勩��
+
+
+
+
+
+
+

--
Gitblit v1.9.1