Parcourir la source

Merge branch 'professional2' into feature-workflow

kagg886 il y a 1 mois
Parent
commit
5fad2efa11
100 fichiers modifiés avec 7721 ajouts et 2992 suppressions
  1. 2 0
      .env.golocal
  2. 113 95
      src/api/datahub/index.ts
  3. 1 1
      src/api/device/index.ts
  4. 1 1
      src/components/vue3cron/vue3cron.vue
  5. 24 3
      src/i18n/index.ts
  6. 34 32
      src/i18n/lang/en.ts
  7. 2 1
      src/i18n/lang/zh-cn.ts
  8. 2 1
      src/i18n/lang/zh-tw.ts
  9. 96 0
      src/i18n/pages/dateCenter/en.ts
  10. 96 0
      src/i18n/pages/dateCenter/zh-cn.ts
  11. 96 0
      src/i18n/pages/dateCenter/zh-tw.ts
  12. 248 8
      src/i18n/pages/iotmanagerI18n/en.ts
  13. 251 8
      src/i18n/pages/iotmanagerI18n/zh-cn.ts
  14. 250 10
      src/i18n/pages/iotmanagerI18n/zh-tw.ts
  15. 553 0
      src/i18n/pages/projects/en.ts
  16. 553 0
      src/i18n/pages/projects/zh-cn.ts
  17. 553 0
      src/i18n/pages/projects/zh-tw.ts
  18. 172 0
      src/i18n/pages/property/en.ts
  19. 172 0
      src/i18n/pages/property/zh-cn.ts
  20. 172 0
      src/i18n/pages/property/zh-tw.ts
  21. 13 8
      src/i18n/pages/tableI18n/en.ts
  22. 8 3
      src/i18n/pages/tableI18n/zh-cn.ts
  23. 7 2
      src/i18n/pages/tableI18n/zh-tw.ts
  24. 0 1
      src/layout/navMenu/vertical.vue
  25. 1 1
      src/theme/element.scss
  26. 8 8
      src/utils/auth.ts
  27. 4 4
      src/utils/authDirective.ts
  28. 3 3
      src/utils/colDirective.ts
  29. 7 3
      src/utils/origin.ts
  30. 1 1
      src/views/apihub/component/edit.vue
  31. 23 12
      src/views/iot/cascade/deviceList.vue
  32. 26 14
      src/views/iot/cascade/index.vue
  33. 6 6
      src/views/iot/device/category/component/addOrEdit.vue
  34. 1 1
      src/views/iot/device/category/index.vue
  35. 1 1
      src/views/iot/device/instance/component/chart.vue
  36. 65 36
      src/views/iot/device/instance/detail.vue
  37. 119 45
      src/views/iot/device/instance/index.vue
  38. 40 24
      src/views/iot/device/product/component/dataParse.vue
  39. 21 10
      src/views/iot/device/product/component/deviceIn.vue
  40. 412 345
      src/views/iot/device/product/component/editAttr.vue
  41. 68 36
      src/views/iot/device/product/component/editEvent.vue
  42. 202 240
      src/views/iot/device/product/component/editFun.vue
  43. 54 31
      src/views/iot/device/product/component/editOption.vue
  44. 107 66
      src/views/iot/device/product/component/editPro.vue
  45. 145 74
      src/views/iot/device/product/component/editTab.vue
  46. 42 25
      src/views/iot/device/product/component/typeItem.vue
  47. 45 63
      src/views/iot/device/product/detail.vue
  48. 2 2
      src/views/iot/network/server/component/list.vue
  49. 2 2
      src/views/iot/network/tunnel/component/list.vue
  50. 1 1
      src/views/iot/operate/remoteconf/index.vue
  51. 33 32
      src/views/iot/projects/detail/device.vue
  52. 5 5
      src/views/iot/projects/detail/index.vue
  53. 8 8
      src/views/iot/projects/detail/info.vue
  54. 32 31
      src/views/iot/projects/detail/scene.vue
  55. 28 26
      src/views/iot/projects/detail/topo.vue
  56. 34 32
      src/views/iot/projects/detail/video.vue
  57. 22 18
      src/views/iot/projects/filter/attrEdit.vue
  58. 18 32
      src/views/iot/projects/filter/bindDevice.vue
  59. 11 9
      src/views/iot/projects/filter/bindDeviceForm.vue
  60. 39 25
      src/views/iot/projects/filter/detail.vue
  61. 10 3
      src/views/iot/projects/filter/edit-dialog.vue
  62. 17 14
      src/views/iot/projects/filter/edit.vue
  63. 35 32
      src/views/iot/projects/filter/index.vue
  64. 3 3
      src/views/iot/projects/list/edit-dialog.vue
  65. 62 57
      src/views/iot/projects/list/edit.vue
  66. 29 38
      src/views/iot/projects/list/index.vue
  67. 2 2
      src/views/iot/projects/screen/BaseinfoVue.vue
  68. 11 11
      src/views/iot/projects/screen/InfoVue.vue
  69. 5 9
      src/views/iot/projects/screen/LineChart.vue
  70. 1 1
      src/views/iot/projects/screen/VideoVue.vue
  71. 89 98
      src/views/iot/property/attribute/edit.vue
  72. 23 21
      src/views/iot/property/attribute/index.vue
  73. 13 9
      src/views/iot/property/deviceMap/index.vue
  74. 6 3
      src/views/iot/property/dossier/component/from.vue
  75. 68 70
      src/views/iot/property/dossier/edit.vue
  76. 22 20
      src/views/iot/property/dossier/index.vue
  77. 51 34
      src/views/iot/property/relationship/edit.vue
  78. 13 11
      src/views/iot/property/relationship/filter.vue
  79. 56 73
      src/views/iot/property/relationship/index.vue
  80. 3 3
      src/views/modules/assess/component/addItem.vue
  81. 3 3
      src/views/modules/assess/component/editItem.vue
  82. 3 3
      src/views/modules/assess/index.vue
  83. 5 6
      src/views/modules/iotCard/platformManage/addOrEditItem.vue
  84. 44 11
      src/views/system/api/index.vue
  85. 173 0
      src/views/system/datahub/indicator/component/data.vue
  86. 62 0
      src/views/system/datahub/indicator/component/detail.vue
  87. 409 0
      src/views/system/datahub/indicator/component/edit.vue
  88. 163 0
      src/views/system/datahub/indicator/index.vue
  89. 106 101
      src/views/system/datahub/source/component/edit.vue
  90. 446 439
      src/views/system/datahub/source/component/editNode.vue
  91. 24 5
      src/views/system/datahub/source/component/list.vue
  92. 400 400
      src/views/system/datahub/source/detail.vue
  93. 46 59
      src/views/system/datahub/source/index.vue
  94. 134 0
      src/views/system/datahub/tags/component/edit.vue
  95. 122 0
      src/views/system/datahub/tags/index.vue
  96. 1 1
      src/views/system/manage/blacklist/index.vue
  97. 1 1
      src/views/system/manage/dept/index.vue
  98. 1 1
      src/views/system/manage/org/index.vue
  99. 2 2
      src/views/system/manage/role/index.vue
  100. 2 2
      src/views/system/menu/index.vue

+ 2 - 0
.env.golocal

@@ -6,5 +6,7 @@ VITE_ROUTER_MODE = 'hash'
 
 VITE_SERVER_ORIGIN = ''
 
+VITE_MODBUS_API = ':8110'
+VITE_ICE104_API = ':8310'
 VITE_RULE_SERVER_URL = ':9090'
 VITE_MEDIA_SERVER_URL = ':8080'

+ 113 - 95
src/api/datahub/index.ts

@@ -10,106 +10,124 @@ import { get, post, del, put, file } from '/@/utils/request';
 
 export default {
 
-   common: {
-      getList: (params: object) => get('/source/search', params),
-      add: (data: object) => post('/source/api/add', data),
-      delete: (ids: number) => del('/source/del', { ids }),
-      edit: (data: object) => put('/source/api/edit', data),
-      detail: (sourceId: number) => get('/source/detail', { sourceId }),
-      deploy: (data: object) => post('/source/deploy', data),
-      undeploy: (data: object) => post('/source/undeploy', data),
-      api: (sourceId: number) => get('/source/api/get', { sourceId }),
-      devadd: (data: object) => post('/source/device/add', data),
-      devedit: (data: object) => put('/source/device/edit', data),
-      devapi: (sourceId: number) => get('/source/device/get', { sourceId }),
-      getdevList: (params: object) => get('/product/device/list', params),
-      getdata: (params: object) => get('/source/getdata', params),
-      getLists: (params: object) => get('/source/list', params),
-      copy: (params: object) => post('/source/copy', params),
+  common: {
+    getList: (params: object) => get('/source/search', params),
+    add: (data: object) => post('/source/api/add', data),
+    delete: (ids: number) => del('/source/del', { ids }),
+    edit: (data: object) => put('/source/api/edit', data),
+    detail: (sourceId: number) => get('/source/detail', { sourceId }),
+    deploy: (data: object) => post('/source/deploy', data),
+    undeploy: (data: object) => post('/source/undeploy', data),
+    api: (sourceId: number) => get('/source/api/get', { sourceId }),
+    devadd: (data: object) => post('/source/device/add', data),
+    devedit: (data: object) => put('/source/device/edit', data),
+    devapi: (sourceId: number) => get('/source/device/get', { sourceId }),
+    getdevList: (params: object) => get('/product/device/list', params),
+    getdata: (params: object) => get('/source/getdata', params),
+    getLists: (params: object) => get('/source/list', params),
+    copy: (params: object) => post('/source/copy', params),
 
-      dbadd: (data: object) => post('/source/db/add', data),
-      dbedit: (data: object) => put('/source/db/edit', data),
-      getfields: (sourceId: number) => get('/source/db/fields', { sourceId }),
+    dbadd: (data: object) => post('/source/db/add', data),
+    dbedit: (data: object) => put('/source/db/edit', data),
+    getfields: (sourceId: number) => get('/source/db/fields', { sourceId }),
 
-      devdb: (sourceId: number) => get('/source/db/get', { sourceId }),
+    devdb: (sourceId: number) => get('/source/db/get', { sourceId }),
 
-   },
+  },
 
-   node: {
-      getList: (params: object) => get('/source/node/list', params),
-      add: (data: object) => post('/source/node/add', data),
-      delete: (nodeId: number) => del('/source/node/del', { nodeId }),
-      edit: (data: object) => put('/source/node/edit', data),
-      getpropertyList: (params: object) => get('/product/tsl/property/all', params),
-   },
+  node: {
+    getList: (params: object) => get('/source/node/list', params),
+    add: (data: object) => post('/source/node/add', data),
+    delete: (nodeId: number) => del('/source/node/del', { nodeId }),
+    edit: (data: object) => put('/source/node/edit', data),
+    getpropertyList: (params: object) => get('/product/tsl/property/all', params),
+  },
 
-   template: {
-      getList: (params: object) => get('/source/template/search', params),
-      add: (data: object) => post('/source/template/add', data),
-      delete: (ids: number) => del('/source/template/del', { ids }),
-      edit: (data: object) => put('/source/template/edit', data),
-      detail: (id: string) => get('/source/template/detail', { id }),
-      allList: (params: object) => get('/source/template/list', params), // 获取所有已发布列表
-      getdata: (params: object) => get('/source/template/getdata', params),
-      getDictData: (params: object) => get('/common/dict/data/getDictData', params),
-      cityTree: (params: object) => get('/common/city/tree', params),
-      copy: (params: object) => post('/source/template/copy', params),
-      relation_check: (id: number) => get('/source/template/relation_check', { id }),
-      source_list: (id: number) => get('/source/template/source_list', { id }),
-      aggregate_from: (id: number) => get('/source/template/aggregate_from', { id }),
-      relation: (data: object) => post('/source/template/relation', data),
-      aggregate: (data: object) => post('/source/template/aggregate', data),
-   },
+  template: {
+    getList: (params: object) => get('/source/template/search', params),
+    add: (data: object) => post('/source/template/add', data),
+    delete: (ids: number) => del('/source/template/del', { ids }),
+    edit: (data: object) => put('/source/template/edit', data),
+    detail: (id: string) => get('/source/template/detail', { id }),
+    allList: (params: object) => get('/source/template/list', params), // 获取所有已发布列表
+    getdata: (params: object) => get('/source/template/getdata', params),
+    getDictData: (params: object) => get('/common/dict/data/getDictData', params),
+    cityTree: (params: object) => get('/common/city/tree', params),
+    copy: (params: object) => post('/source/template/copy', params),
+    relation_check: (id: number) => get('/source/template/relation_check', { id }),
+    source_list: (id: number) => get('/source/template/source_list', { id }),
+    aggregate_from: (id: number) => get('/source/template/aggregate_from', { id }),
+    relation: (data: object) => post('/source/template/relation', data),
+    aggregate: (data: object) => post('/source/template/aggregate', data),
+  },
 
-   tnode: {
-      getList: (params: object) => get('/source/template/node/list', params),
-      add: (data: object) => post('/source/template/node/add', data),
-      delete: (id: number) => del('/source/template/node/del', { id }),
-      edit: (data: object) => put('/source/template/node/edit', data),
-      deploy: (data: object) => post('/source/template/deploy', data),
-      undeploy: (data: object) => post('/source/template/undeploy', data),
-   },
+  tnode: {
+    getList: (params: object) => get('/source/template/node/list', params),
+    add: (data: object) => post('/source/template/node/add', data),
+    delete: (id: number) => del('/source/template/node/del', { id }),
+    edit: (data: object) => put('/source/template/node/edit', data),
+    deploy: (data: object) => post('/source/template/deploy', data),
+    undeploy: (data: object) => post('/source/template/undeploy', data),
+  },
 
-   weather: {
-      getCityWeatherList: () => get('/envirotronics/weather/cityWeatherList'),
-      getWhichCityWeather: (params: object) => get('/envirotronics/weather/getInfoById', params),
-      getTemperatureEchartById: (params: object) => get('/envirotronics/weather/getTemperatureEchartById', params),
-      getWindpowerEchartById: (params: object) => get('/envirotronics/weather/getWindpowerEchartById', params),
-      getCityWeatherHistory: (params: object) => get('/envirotronics/weather/GetCityWeatherHistory', params),
-      getCityWeatherHistoryExport: (params: object) => file('/envirotronics/weather/GetCityWeatherHistoryExport', params),
-   },
-   statistics: {
-      getStatisticsChartData: (params: object) => get('/statistics/bar/chart/data', params),
-      getStatisticsLineChartData: (params: object) => get('/statistics/broken/line/data', params),
-      getStatisticsTotalData: (params: object) => get('/statistics/city/data', params),
-      getStatisticsPieData: (params: object) => get('/statistics/tempering/ratio/data', params),
-      getStatisticsOverview: (params: object) => get('/statistics/overview', params),
-   },
-   iotManage: {
-      getOverviewData: () => get('/thing/overview'),
-      getAlarmList: (params: object) => get('/alarm/log/list', params),
-      getAlarmDetail: (id: number) => get('/alarm/log/detail', { id }),
-      getAlarmHandle: (data: object) => post('/alarm/log/handle', data),
-      // 设备消息总量本年统计
-      deviceDataTotalCount: (dateType: 'year' | 'month' | 'day') => get('/analysis/deviceDataTotalCount', { dateType }),
-      // 设备在线离线及总数统计
-      deviceOnlineOfflineCount: () => get('/analysis/deviceOnlineOfflineCount'),
-      // 本年度每月设备消息量统计 
-      deviceDataCount: (dateType: 'year' | 'month') => get('/analysis/deviceDataCount', { dateType }),
-      // 按年度每月设备告警数统计
-      deviceAlertCountByYearMonth: (year = '2023') => get('/analysis/deviceAlertCountByYearMonth', { year }),
-      // 按告警级别统计
-      deviceAlarmLevelCount: (dateType: 'year' | 'month' | 'day', date: string) => get('/analysis/deviceAlarmLevelCount', { dateType, date }),
-      // 产品数量统计
-      productCount: () => get('/analysis/productCount'),
-   },
-   // 计算指标管理
-   calculationIndicator: {
-      getList: (params: object) => get('/compute/list', params),
-      add: (data: object) => post('/compute/add', data),
-      delete: (id: number) => del('/compute/del', { id }),
-      edit: (data: object) => put('/compute/edit', data),
-      deploy: (data: object) => put('/compute/publish', data),
-      checkDeploy: (params: object) => get('/compute/checkComputeIndexDeploy', params)
-   },
+  weather: {
+    getCityWeatherList: () => get('/envirotronics/weather/cityWeatherList'),
+    getWhichCityWeather: (params: object) => get('/envirotronics/weather/getInfoById', params),
+    getTemperatureEchartById: (params: object) => get('/envirotronics/weather/getTemperatureEchartById', params),
+    getWindpowerEchartById: (params: object) => get('/envirotronics/weather/getWindpowerEchartById', params),
+    getCityWeatherHistory: (params: object) => get('/envirotronics/weather/GetCityWeatherHistory', params),
+    getCityWeatherHistoryExport: (params: object) => file('/envirotronics/weather/GetCityWeatherHistoryExport', params),
+  },
+  statistics: {
+    getStatisticsChartData: (params: object) => get('/statistics/bar/chart/data', params),
+    getStatisticsLineChartData: (params: object) => get('/statistics/broken/line/data', params),
+    getStatisticsTotalData: (params: object) => get('/statistics/city/data', params),
+    getStatisticsPieData: (params: object) => get('/statistics/tempering/ratio/data', params),
+    getStatisticsOverview: (params: object) => get('/statistics/overview', params),
+  },
+  iotManage: {
+    getOverviewData: () => get('/thing/overview'),
+    getAlarmList: (params: object) => get('/alarm/log/list', params),
+    getAlarmDetail: (id: number) => get('/alarm/log/detail', { id }),
+    getAlarmHandle: (data: object) => post('/alarm/log/handle', data),
+    // 设备消息总量本年统计
+    deviceDataTotalCount: (dateType: 'year' | 'month' | 'day') => get('/analysis/deviceDataTotalCount', { dateType }),
+    // 设备在线离线及总数统计
+    deviceOnlineOfflineCount: () => get('/analysis/deviceOnlineOfflineCount'),
+    // 本年度每月设备消息量统计 
+    deviceDataCount: (dateType: 'year' | 'month') => get('/analysis/deviceDataCount', { dateType }),
+    // 按年度每月设备告警数统计
+    deviceAlertCountByYearMonth: (year = '2023') => get('/analysis/deviceAlertCountByYearMonth', { year }),
+    // 按告警级别统计
+    deviceAlarmLevelCount: (dateType: 'year' | 'month' | 'day', date: string) => get('/analysis/deviceAlarmLevelCount', { dateType, date }),
+    // 产品数量统计
+    productCount: () => get('/analysis/productCount'),
+  },
+  // 计算指标管理
+  calculationIndicator: {
+    getList: (params: object) => get('/compute/list', params),
+    add: (data: object) => post('/compute/add', data),
+    delete: (id: number) => del('/compute/del', { id }),
+    edit: (data: object) => put('/compute/edit', data),
+    deploy: (data: object) => put('/compute/publish', data),
+    checkDeploy: (params: object) => get('/compute/checkComputeIndexDeploy', params)
+  },
+  tags: {
+    getTree: (data: object) => get('/tag/tree', data),
+    add: (data: object) => post('/tag/add', data),
+    edit: (data: object) => put('/tag/edit', data),
+    detail: (id: number) => get('/tag/detail', { id }),
+    del: (id: number) => del('/tag/del', { id }),
+  },
+  indicator: {
+    getList: (params: object) => get('/indicator/list', params),
+    data: (params: object) => get('/indicator/data', params),
+    getData: (params: object) => get('/indicator/getData', params),
+    detail: (code: string) => get('/indicator/detail', { code }),
+    add: (data: object) => post('/indicator/add', data),
+    del: (code: string) => del('/indicator/del', { code }),
+    edit: (data: object) => put('/indicator/edit', data),
+    publish: (code: string) => post('/indicator/publish', { code }),
+    unpublish: (code: string) => post('/indicator/unpublish', { code }),
+  }
 }

+ 1 - 1
src/api/device/index.ts

@@ -114,7 +114,7 @@ export default {
     setDeviceStatus: (data: object) => post('/product/device/setDeviceStatus', data),
     import: (data: object) => post('/product/device/import', data),
     export: (data: object) => file('/product/device/export', data),
-    
+    syncStatus: () => get('/product/device/syncStatus'),
   },
   dev_asset: {
     all: (params: object) => get('/asset/asset/all', params),

+ 1 - 1
src/components/vue3cron/vue3cron.vue

@@ -1,7 +1,7 @@
 
 <template>
   <div class="vue3-cron-div">
-    <el-button class="language" type="text">
+    <el-button class="language" text type="primary">
     </el-button>
     <el-tabs type="border-card">
       <el-tab-pane>

+ 24 - 3
src/i18n/index.ts

@@ -34,6 +34,18 @@ import pagesIotcardZhcn from './pages/iotCard/zh-cn';
 import pagesIotcardEn from './pages/iotCard/en';
 import pagesIotcardZhtw from './pages/iotCard/zh-tw';
 
+import pagesProjectsZhcn from './pages/projects/zh-cn';
+import pagesProjectsEn from './pages/projects/en';
+import pagesProjectsZhtw from './pages/projects/zh-tw';
+
+import pagesPropertyZhcn from './pages/property/zh-cn';
+import pagesPropertyEn from './pages/property/en';
+import pagesPropertyZhtw from './pages/property/zh-tw';
+
+import pagesDateCenterZhcn from './pages/dateCenter/zh-cn';
+import pagesDateCenterEn from './pages/dateCenter/en';
+import pagesDateCenterZhtw from './pages/dateCenter/zh-tw';
+
 // 定义语言国际化内容
 /**
  * 说明:
@@ -49,7 +61,10 @@ const messages = {
 			...pagesFormI18nZhcn,
 			...pagesTable18nZhcn,
 			...pagesIotmanagerZhcn,
-			iotCard: pagesIotcardZhcn
+			iotCard: pagesIotcardZhcn,
+			projects: pagesProjectsZhcn,
+			property: pagesPropertyZhcn,
+			dateCenter: pagesDateCenterZhcn
 		}
 	},
 	[enLocale.name]: {
@@ -60,7 +75,10 @@ const messages = {
 			...pagesFormI18nEn,
 			...pagesTable18nEn,
 			...pagesIotmanagerEn,
-			iotCard: pagesIotcardEn
+			iotCard: pagesIotcardEn,
+			projects: pagesProjectsEn,
+			property: pagesPropertyEn,
+			dateCenter: pagesDateCenterEn
 		},	
 	},
 	[zhtwLocale.name]: {
@@ -71,7 +89,10 @@ const messages = {
 			...pagesFormI18nZhtw,
 			...pagesTable18nZhtw,
 			...pagesIotmanagerZhtw,
-			iotCard: pagesIotcardZhtw
+			iotCard: pagesIotcardZhtw,
+			projects: pagesProjectsZhtw,
+			property: pagesPropertyZhtw,
+			dateCenter: pagesDateCenterZhtw
 		},
 	},
 };

+ 34 - 32
src/i18n/lang/en.ts

@@ -193,7 +193,7 @@ export default {
 		iotmanager: {
 			title: "IoT Management",
 			dashboard: "IoT Overview",
-			deviceManager: "Device Management",
+			deviceManager: "Device",
 			productClass: "Product Category",
 			product: "Product",
 			productDetail: "Product Details",
@@ -201,13 +201,13 @@ export default {
 			device1: "Device",
 			deviceDetail: "Device Details",
 			deviceDetail1: "Device Details",
-			configuration: "Configuration Management",
+			configuration: "Configuration",
 			configurationList: "Configuration List",
 			screen: "Dashboard Design",
 			ruleEngine: "Rule Engine",
 			ruleInstance: "Rule Orchestration",
 			gridComponent: "Grid Component",
-			serverManager: "Server Management",
+			serverManager: "Server",
 			serverDetail: "Service Details",
 			editServer: "Edit Service",
 			newServer: "Add Service",
@@ -217,11 +217,11 @@ export default {
 			newChannel: "Add Channel",
 			channelDetail: "Channel Details",
 			dataCollection: "Data Collection",
-			templateManagement: "Template Management",
+			templateManagement: "Template",
 			dataAnalysis: "Data Analysis",
 			indicatorTrend: "Indicator Trend",
 			indicatorAggregation: "Indicator Aggregation",
-			certificateManagement: "Certificate Management",
+			certificateManagement: "Certificate",
 			certificateList: "Certificate List",
 			alarmCenter: "Alarm Center",
 			alarmStatistics: "Alarm Statistics",
@@ -235,23 +235,23 @@ export default {
 			sceneDetail: "Scene Details",
 			sceneRecord: "Scene Record",
 			sceneRecordDetail: "Record Details",
-			operationManagement: "Operation Management",
+			operationManagement: "Operation",
 			remoteConfiguration: "Remote Configuration",
 			otaUpgrade: "OTA Upgrade",
 			dataAnalysis1: "Data Analysis",
-			moduleManagement: "Module Management",
-			upgradePackageManagement: "Upgrade Package Management",
+			moduleManagement: "Module",
+			upgradePackageManagement: "Upgrade Package",
 			upgradePackageDetail: "Details",
 			notificationService: "Notification Service",
 			notificationLog: "Notification Log",
 			notificationConfiguration: "Notification Configuration",
 			notificationConfigurationManagement: "Notification Configuration Management",
-			cascadeManagement: "Cascade Management",
+			cascadeManagement: "Cascade",
 		},
 		iotCard: {
 			title: "IoT Card",
 			homePage: "Home Page",
-			iotCardManagement: "IoT Card Management",
+			iotCardManagement: "IoT Card",
 			iotCardDetail: "IoT Card Details",
 			platformAccess: "Platform Access",
 		},
@@ -264,8 +264,8 @@ export default {
 			deviceTree: "Device Tree",
 		},
 		assetManagement: {
-			title: "Asset Management",
-			deviceArchiveManagement: "Device Archive Management",
+			title: "Asset",
+			deviceArchiveManagement: "Device Profile",
 			deviceArchiveProperty: "Device Archive Property",
 			assetRelationship: "Asset Relationship",
 			deviceMap: "Device Map",
@@ -277,6 +277,8 @@ export default {
 			indexManagement: "Index Management",
 			dataModeling: "Data Modeling",
 			dataSourceManagement: "Data Source Management",
+			tags: "Tags",
+			indicator: "Indicator",
 		},
 		developmentTools: {
 			title: "Development Tools",
@@ -297,12 +299,12 @@ export default {
 			streamPush: "Stream Push",
 		},
 		systemManagement: {
-			title: "System Management",
-			regionManagement: "Region Management",
-			organizationManagement: "Organization Management",
-			positionManagement: "Position Management",
-			roleManagement: "Role Management",
-			userManagement: "User Management",
+			title: "System",
+			regionManagement: "Region",
+			organizationManagement: "Organization",
+			positionManagement: "Position",
+			roleManagement: "Role",
+			userManagement: "User",
 			systemMonitoring: "System Monitoring",
 			apiDocument: "API Document",
 			serviceMonitoring: "Service Monitoring",
@@ -315,19 +317,19 @@ export default {
 			systemLog: "System Log",
 			blacklist: "Blacklist",
 			configuration: "System Configuration",
-			configurationManagement: "Configuration Management",
-			pluginManagement: "Plugin Management",
-			applicationManagement: "Application Management",
-			menuManagement: "Menu Management",
-			parameterManagement: "Parameter Management",
-			dictionaryManagement: "Dictionary Management",
-			dictionaryDataManagement: "Dictionary Data Management",
+			configurationManagement: "Configuration",
+			pluginManagement: "Plugin",
+			applicationManagement: "Application",
+			menuManagement: "Menu",
+			parameterManagement: "Parameter",
+			dictionaryManagement: "Dictionary",
+			dictionaryDataManagement: "Dictionary Data",
 			scheduledTask: "Scheduled Task",
-			apiManagement: "API Management",
-			cityManagement: "City Management",
+			apiManagement: "API",
+			cityManagement: "City",
 			remoteUpgrade: "Remote Upgrade",
 			authorizationConfiguration: "Authorization Configuration",
-			tenantManagement: "Tenant Management",
+			tenantManagement: "Tenant",
 			// experimentalFunction: "Experimental Function",
 			// functionDevelopment: "Function Development",
 			// pageDevelopment: "Page Development",
@@ -352,13 +354,13 @@ export default {
 			apiDevelopment: "API Development",
 			apiDefinition: "API Definition",
 
-			dataSourceManagement: "Data Source Management",
-			clientManagement: "Client Management",
-			pluginManagement: "Plugin Management",
+			dataSourceManagement: "Data Source",
+			clientManagement: "Client",
+			pluginManagement: "Plugin",
 
 		},
 		policyManagement: {
-			title: "Policy Management",
+			title: "Policy",
 			policyControl: "Policy Control",
 		}
 	}

+ 2 - 1
src/i18n/lang/zh-cn.ts

@@ -279,7 +279,8 @@ export default {
 			indexManagement: "指数管理",
 			dataModeling: "数据建模",
 			dataSourceManagement: "数据源管理",
-
+			tags: "标签管理",
+			indicator: "指标管理",
 		},
 		developmentTools: {
 			title: "开发工具",

+ 2 - 1
src/i18n/lang/zh-tw.ts

@@ -286,7 +286,8 @@ export default {
 			indexManagement: "指數管理",
 			dataModeling: "數據建模",
 			dataSourceManagement: "數據源管理",
-
+			tags: "標籤管理",
+			indicator: "指標管理",
 		},
 		developmentTools: {
 			title: "開發工具",

+ 96 - 0
src/i18n/pages/dateCenter/en.ts

@@ -0,0 +1,96 @@
+export default {
+  tabs: {
+    info: 'Source Info',
+    nodes: 'Data Nodes',
+    viewData: 'View Data',
+    baseInfo: 'Basic Info',
+    ruleExpr: 'Rule Expression',
+    reqParams: 'Request Params',
+    sourceConfig: 'Source Config',
+  },
+  labels: {
+    sourceNameTitle: 'Source Name:',
+    sourceStatus: 'Status',
+    sourceKey: 'Source Key',
+    sourceName: 'Source Name',
+    sourceDesc: 'Source Description',
+    sourceFrom: 'Source From',
+    deviceKey: 'Device Key',
+    productKey: 'Product Key',
+    dbType: 'Source From',
+    host: 'Host',
+    port: 'Port',
+    user: 'Username',
+    password: 'Password',
+    dbName: 'Database Name',
+    tableName: 'Table Name',
+    pk: 'Primary Key',
+    num: 'Batch Size',
+    cronExpression: 'Cron Expression',
+    expression: 'Expression',
+    param: 'Param',
+    method: 'Method',
+    url: 'URL',
+    interval: 'Update Interval',
+    paramType: 'Param Type',
+    paramTitle: 'Param Title',
+    paramKey: 'Param Key',
+    paramValue: 'Param Value',
+  },
+  columns: {
+    id: 'ID',
+    key: 'Key',
+    name: 'Name',
+    dataType: 'Data Type',
+    value: 'Value Items',
+    createdAt: 'Created At',
+    action: 'Actions',
+    sourceId: 'ID',
+    from: 'Source Type',
+  },
+  actions: {
+    publish: 'Publish',
+    disable: 'Disable',
+    add: 'Add',
+    edit: 'Edit',
+    delete: 'Delete',
+    detail: 'Detail',
+    records: 'Records',
+    search: 'Search',
+    createSource: 'New Source',
+    batchDelete: 'Batch Delete',
+    copy: 'Copy',
+  },
+  status: {
+    published: 'Published',
+    unpublished: 'Unpublished',
+    all: 'All',
+  },
+  options: {
+    all: 'All',
+    api: 'API Import',
+    db: 'Database',
+    file: 'File',
+    device: 'Device',
+    yes: 'Yes',
+    no: 'No',
+  },
+  messages: {
+    opSuccess: 'Operation succeeded',
+    deleteConfirmSelected: 'Are you sure to delete selected data?',
+    deleteNodeConfirm: 'This will permanently delete node: "{name}". Continue?',
+    pleaseSelectToDelete: 'Please select data to delete.',
+    tip: 'Tip',
+    confirm: 'Confirm',
+    cancel: 'Cancel',
+    deleteSuccess: 'Deleted successfully',
+    copyConfirm: 'Confirm to copy this data?',
+    copySuccess: 'Copied successfully',
+    batchDeleteConfirm: 'Confirm to batch delete these data?',
+    deleteRowConfirm: 'This will delete: "{name}". Continue?',
+  },
+  placeholders: {
+    input: 'Please enter',
+    sourceType: 'Source Type',
+  },
+};

+ 96 - 0
src/i18n/pages/dateCenter/zh-cn.ts

@@ -0,0 +1,96 @@
+export default {
+  tabs: {
+    info: '数据源信息',
+    nodes: '数据节点',
+    viewData: '查看数据',
+    baseInfo: '基本信息',
+    ruleExpr: '规则表达式',
+    reqParams: '请求参数',
+    sourceConfig: '数据源配置',
+  },
+  labels: {
+    sourceNameTitle: '数据源名称:',
+    sourceStatus: '状态',
+    sourceKey: '数据源标识',
+    sourceName: '数据源名称',
+    sourceDesc: '数据源描述',
+    sourceFrom: '数据来源',
+    deviceKey: '设备key',
+    productKey: '产品key',
+    dbType: '数据来源',
+    host: '主机地址',
+    port: '端口号',
+    user: '用户名',
+    password: '密码',
+    dbName: '数据库名称',
+    tableName: '表名称',
+    pk: '主键字段',
+    num: '每次获取数量',
+    cronExpression: '任务表达式',
+    expression: '表达式',
+    param: '参数',
+    method: '请求方法',
+    url: '请求地址',
+    interval: '更新时间',
+    paramType: '参数类型',
+    paramTitle: '参数标题',
+    paramKey: '参数名',
+    paramValue: '参数值',
+  },
+  columns: {
+    id: 'ID',
+    key: '数据标识',
+    name: '数据名称',
+    dataType: '数据类型',
+    value: '数据取值项',
+    createdAt: '创建时间',
+    action: '操作',
+    sourceId: 'ID',
+    from: '数据源类型',
+  },
+  actions: {
+    publish: '发布',
+    disable: '停用',
+    add: '添加',
+    edit: '修改',
+    delete: '删除',
+    detail: '详情',
+    records: '数据记录',
+    search: '查询',
+    createSource: '新增数据源',
+    batchDelete: '批量删除',
+    copy: '复制',
+  },
+  status: {
+    published: '已发布',
+    unpublished: '未发布',
+    all: '全部',
+  },
+  options: {
+    all: '全部',
+    api: 'api导入',
+    db: '数据库',
+    file: '文件',
+    device: '设备',
+    yes: '是',
+    no: '否',
+  },
+  messages: {
+    opSuccess: '操作成功',
+    deleteConfirmSelected: '你确定要删除所选数据?',
+    deleteNodeConfirm: '此操作将永久删除数据节点:“{name}”,是否继续?',
+    pleaseSelectToDelete: '请选择要删除的数据。',
+    tip: '提示',
+    confirm: '确认',
+    cancel: '取消',
+    deleteSuccess: '删除成功',
+    copyConfirm: '确定要复制该数据吗?',
+    copySuccess: '复制成功',
+    batchDeleteConfirm: '是否确认要批量删除这些数据吗?',
+    deleteRowConfirm: '此操作将删除:“{name}”,是否继续?',
+  },
+  placeholders: {
+    input: '请输入',
+    sourceType: '数据源类型',
+  },
+};

+ 96 - 0
src/i18n/pages/dateCenter/zh-tw.ts

@@ -0,0 +1,96 @@
+export default {
+  tabs: {
+    info: '資料源資訊',
+    nodes: '資料節點',
+    viewData: '查看資料',
+    baseInfo: '基本資訊',
+    ruleExpr: '規則表示式',
+    reqParams: '請求參數',
+    sourceConfig: '資料源配置',
+  },
+  labels: {
+    sourceNameTitle: '資料源名稱:',
+    sourceStatus: '狀態',
+    sourceKey: '資料源標識',
+    sourceName: '資料源名稱',
+    sourceDesc: '資料源描述',
+    sourceFrom: '資料來源',
+    deviceKey: '設備 key',
+    productKey: '產品 key',
+    dbType: '資料來源',
+    host: '主機地址',
+    port: '埠號',
+    user: '使用者名',
+    password: '密碼',
+    dbName: '資料庫名稱',
+    tableName: '表名稱',
+    pk: '主鍵欄位',
+    num: '每次獲取數量',
+    cronExpression: '排程表示式',
+    expression: '表示式',
+    param: '參數',
+    method: '請求方法',
+    url: '請求地址',
+    interval: '更新間隔',
+    paramType: '參數類型',
+    paramTitle: '參數標題',
+    paramKey: '參數名',
+    paramValue: '參數值',
+  },
+  columns: {
+    id: 'ID',
+    key: '資料標識',
+    name: '資料名稱',
+    dataType: '資料型別',
+    value: '取值項',
+    createdAt: '建立時間',
+    action: '操作',
+    sourceId: 'ID',
+    from: '資料源類型',
+  },
+  actions: {
+    publish: '發布',
+    disable: '停用',
+    add: '新增',
+    edit: '修改',
+    delete: '刪除',
+    detail: '詳情',
+    records: '資料記錄',
+    search: '查詢',
+    createSource: '新增資料源',
+    batchDelete: '批次刪除',
+    copy: '複製',
+  },
+  status: {
+    published: '已發布',
+    unpublished: '未發布',
+    all: '全部',
+  },
+  options: {
+    all: '全部',
+    api: 'API 導入',
+    db: '資料庫',
+    file: '檔案',
+    device: '設備',
+    yes: '是',
+    no: '否',
+  },
+  messages: {
+    opSuccess: '操作成功',
+    deleteConfirmSelected: '確定要刪除所選資料嗎?',
+    deleteNodeConfirm: '此操作將永久刪除資料節點:「{name}」,是否繼續?',
+    pleaseSelectToDelete: '請選擇要刪除的資料。',
+    tip: '提示',
+    confirm: '確認',
+    cancel: '取消',
+    deleteSuccess: '刪除成功',
+    copyConfirm: '確定要複製該資料嗎?',
+    copySuccess: '複製成功',
+    batchDeleteConfirm: '是否確認要批次刪除這些資料?',
+    deleteRowConfirm: '此操作將刪除:「{name}」,是否繼續?',
+  },
+  placeholders: {
+    input: '請輸入',
+    sourceType: '資料源類型',
+  },
+};

+ 248 - 8
src/i18n/pages/iotmanagerI18n/en.ts

@@ -33,7 +33,40 @@ export default {
 	},
 	device: {
 		product: "Product",
-		producDetail: {
+		baseType: "Basic Type",
+		extensionType: "Extension Type",
+		enable: "Enable",
+		disable: "Disable",
+		fullScreen: "Full Screen",
+		exitFullScreen: "Exit Full Screen",
+		tip1: "Please enter input parameters as a string. If it's an object string, it will be automatically converted to an object during execution",
+		tip2: "Execution results are displayed here",
+		tip3: "Save Script",
+		tip4: "Debug",
+		tip5: "Below are pre-built empty methods. Please do not modify the function names, just write the function content directly inside",
+		tip6: "This is device function call response data parsing",
+		tip7: "Write data processing code here",
+		tip8: "This is device function call send data parsing",
+		tip9: "Write data processing code here",
+		tip10: "Please enter executable script first",
+		runStatus: "Run Status",
+		deviceInfo: "Device Info",
+		thingModel: "Thing Model",
+		deviceFunction: "Device Function",
+		logManagement: "Log Management",
+		topicList: "Topic List",
+		deviceArchive: "Device Profile",
+		deviceStatus: "Device Status",
+		dataTime: "Data Time",
+		linkProtocol: "Link Protocol",
+		firmwareVersion: "Firmware Version",
+		registryTime: "Registration Time",
+		address: "Address",
+		deviceIdentifier: "Device Identifier",
+		deviceTimeout: "Device Timeout",
+		unitSecond: "s",
+		update: "Update",
+		productDetail: {
 			productInfo: "Product Information",
 			// Switch Status
 			enable: "ON",
@@ -72,6 +105,20 @@ export default {
 			deviceAccess: "Device Access",
 			dataParsing: "Data Parsing"
 		},
+		dialogI18n: {
+			addPro: "Add Product",
+			editPro: "Edit Product",
+			addPropertyDefinition: "Add Property Definition",
+			editPropertyDefinition: "Edit Property Definition",
+			editFunctionDefinition: "Edit Function Definition",
+			addFunctionDefinition: "Add Function Definition",
+			editOption: "Edit Option",
+			addOption: "Add Option",
+			editEventDefinition: "Edit Event Definition",
+			addEventDefinition: "Add Event Definition",
+			editTagDefinition: "Edit Tag Definition",
+			addTagDefinition: "Add Tag Definition"
+		},
 		tableI18nColumn: {
 			categoryName: "Category Name",
 			desc: "Description",
@@ -82,6 +129,10 @@ export default {
 			transportProtocol: "Transport Protocol",
 			deviceType: "Device Type",
 			status: "Status",
+			deviceName: "Device Name",
+			deviceType2: "Device Type",
+			lastOnlineTime: "Last Online Time",
+			desc2: "Description"
 		},
 		tableI18nAlarmType: {
 		},
@@ -96,27 +147,175 @@ export default {
 		tableI18nAction: {
 			addCategory: "Add Category",
 			editCategory: "Edit Category",
-			deviceManagement: "Device Management"
+			deviceManagement: "Device Management",
+			addProductCategory: "Add Product Category",
 		},
 		formI18nLabel: {
 			keyword: "Keyword",
 			type: "Type",
-			status: "Status"
+			status: "Status",
+			productKey: "Product Key",
+			productName: "Product Name",
+			productImage: "Product Image",
+			productCategory: "Product Category",
+			messageProtocol: "Message Protocol",
+			transportProtocol: "Transport Protocol",
+			authType: "Auth Type",
+			authUser: "Auth User",
+			authPasswd: "Auth Passwd",
+			accessToken: "Aceess Token",
+			certificateId: "Certificate Id",
+			deviceType: "Device Type",
+			desc: "Product Description",
+			propertyIdentifier: "Property Identifier",
+			propertyName: "Property Name",
+			dataType: "Data Type",
+			precision: "Precision",
+			unit: "Unit",
+			maxLength: "Max Length",
+			timeFormat: "Time Format",
+			booleanValue: "Boolean Value",
+			enumItems: "Enum Items",
+			jsonObject: "JSON Object",
+			parameterIdentifier: "Parameter Identifier",
+			parameterName: "Parameter Name",
+			elementType: "Element Type",
+			isReadOnly: "Is Read Only",
+			propertyDescription: "Description",
+			functionKey: "Function Key",
+			functionName: "Function Name",
+			functionInput: "Function Input",
+			functionOutput: "Function Output",
+			functionDescription: "Description",
+			parameterDescription: "Description",
+			eventKey: "Event Key",
+			eventName: "Event Name",
+			eventType: "Event Level",
+			eventDescription: "Description",
+			valueRange: "Value Range",
+			objectProperty: "Object Property",
+			tagKey: "Tag Key",
+			tagName: "Tag Name",
+			tagDescription: "Tag Description",
+			authDescription: "Authentication Description",
+			linkInfo: "Connection Information",
+			authConfig: "Authentication Configuration",
+			contactAdmin: "Please contact administrator",
+			productBind: "Associated Product",
+			deptIds: "Affiliated Organization",
+			deviceTag: "Device Tag"
 		},
 		formI18nPlaceholder: {
 			keyword: "Enter name or identifier",
 			type: "Type",
-			status: "Status"
+			status: "Status",
+			productKey: "Enter product identifier",
+			productName: "Enter product name",
+			productCategory: "Select category",
+			messageProtocol: "Select message protocol",
+			transportProtocol: "Select transport protocol",
+			authUser: "Enter username",
+			authPasswd: "Enter password",
+			accessToken: "Enter Aceess Token",
+			certificateId: "Select certificate",
+			desc: "Enter product description",
+			propertyIdentifier: "Enter property identifier",
+			propertyName: "Enter property name",
+			selectDataType: "Select data type",
+			inputPrecision: "Enter precision",
+			inputUnit: "Enter unit",
+			inputMaxLength: "Enter max length",
+			inputTimeFormat: "Enter time format",
+			inputTrueText: "Enter true text",
+			inputBooleanValue: "Enter boolean value",
+			inputFalseText: "Enter false text",
+			inputEnumText: "Enter enum text",
+			inputEnumValue: "Enter enum value",
+			selectElementType: "Select element type",
+			inputDescription: "Enter property description",
+			functionKey: "Enter function key",
+			functionName: "Enter function name",
+			functionDescription: "Enter function description",
+			parameterIdentifier: "Enter parameter identifier",
+			parameterName: "Enter parameter name",
+			parameterDescription: "Enter parameter description",
+			eventKey: "Enter event key",
+			eventName: "Enter event name",
+			eventDescription: "Enter event description",
+			valueRangeMin: "Minimum value",
+			valueRangeMax: "Maximum value",
+			propertyIdentifier1: "Property identifier",
+			propertyName1: "Property name",
+			tagKey: "Tag Key",
+			tagName: "Tag Name",
+			tagDescription: "Tag Description",
+			inputParameter: "Please enter parameters",
+			productBind: "Select product",
+			deptIds: "Select affiliated organization",
+			deviceTag: "Enter tag"
 		},
 		formI18nOption: {
 			device: "Device",
 			gateway: "Gateway",
 			subDevice: "Sub Device",
 			on: "Enabled",
-			off: "Disabled"
+			off: "Disabled",
+			readonly: "Read Only",
+			readWrite: "Read/Write",
+			info: "Information",
+			warning: "Warning",
+			fault: "Fault",
+			online: "Online",
+			offline: "Offline",
 		},
 		formI18nButton: {
-			addProduct: "Add Product"
+			addProduct: "Add Product",
+			addParameter: "Add Parameter",
+			collapse: "Collapse",
+			showMoreFilter: "More Filter",
+			batchEnable: "Batch Enable",
+			batchDisable: "Batch Disable",
+			importDevice: "Import Device",
+			exportDevice: "Export Device",
+			batchCheckStatus: "Batch Check Status"
+		},
+		rules: {
+			productName: "Product name cannot be empty",
+			productNameMax32: "Product name cannot exceed 32 characters",
+			productNameValidator: "Product name cannot contain spaces",
+			productKey: "Product identifier cannot be empty",
+			productKeyValidator: "Product identifier cannot contain spaces",
+			messageProtocol: "Message protocol cannot be empty",
+			transportProtocol: "Access method cannot be empty",
+			categoryId: "Product category cannot be empty",
+			deviceType: "Device type cannot be empty",
+			// 属性定义相关验证规则
+			propertyName: "Property name cannot be empty",
+			propertyNameMax32: "Property name cannot exceed 32 characters",
+			propertyNameValidator: "Property name cannot contain spaces",
+			propertyKey: "Property identifier cannot be empty",
+			accessMode: "Please select access mode",
+			dataType: "Please select data type",
+			functionName: "Function name cannot be empty",
+			functionNameMax32: "Function name cannot exceed 32 characters",
+			functionNameValidator: "Function name cannot contain spaces",
+			functionKey: "Function identifier cannot be empty",
+			functionType: "Please select data type",
+			parameterName: "Parameter name cannot be empty",
+			parameterNameMax32: "Parameter name cannot exceed 32 characters",
+			parameterNameValidator: "Parameter name cannot contain spaces",
+			parameterKey: "Parameter identifier cannot be empty",
+			eventName: "Event name cannot be empty",
+			eventNameMax32: "Event name cannot exceed 32 characters",
+			eventNameValidator: "Event name cannot contain spaces",
+			eventKey: "Event identifier cannot be empty",
+			eventType: "Please select event type",
+			tagKeyName: "Tag name cannot be empty",
+			tagKeyMax32: "Tag name cannot exceed 32 characters",
+			tagKeyValidator: "Tag name cannot contain spaces",
+			tagKey: "Tag identifier cannot be empty",
+			tagAccessMode: "Please select access mode",
+			tagType: "Please select data type"
 		}
 	},
 	alarm: {
@@ -140,5 +339,46 @@ export default {
 			processed: "Processed",
 			ignored: "Ignored"
 		}
-	}
-};
+	},
+	// 级联管理
+	cascade: {
+		dialogI18n: {
+			deviceList: " - Device List",
+		},
+		tableI18nColumn: {
+			subPlatform: "Sub Platform",
+			address: "Address",
+			device: "Device",
+			status: "Status",
+			lastUpdateTime: "Last Update Time",
+			operation: "Operation",
+			identifier: "Identifier",
+			deviceName: "Device Name",
+			deviceType: "Device Type",
+			belongProduct: "Product",
+			lastOnlineTime: "Last Online Time"
+		},
+		tableI18nStatus: {
+			online: "Online",
+			offline: "Offline",
+			disabled: "Disabled"
+		},
+		tableI18nConfirm: {
+		},
+		tableI18nAction: {
+			viewDevice: "View Device",
+			detail: "Detail",
+			sync: "Sync"
+		},
+		formI18nLabel: {
+			subPlatformTotal: "Sub Platform",
+			deviceTotal: "Total Devices"
+		},
+		formI18nPlaceholder: {
+		},
+		formI18nOption: {
+		},
+		formI18nButton: {
+		}
+	},
+};

+ 251 - 8
src/i18n/pages/iotmanagerI18n/zh-cn.ts

@@ -2,7 +2,7 @@
  * @Author: vera_min vera_min@163.com
  * @Date: 2025-08-05 12:42:31
  * @LastEditors: vera_min vera_min@163.com
- * @LastEditTime: 2025-08-08 23:12:36
+ * @LastEditTime: 2025-08-09 16:07:54
  * @FilePath: /sagoo-admin-ui/src/i18n/pages/iotmanagerI18n/zh-cn.ts
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
@@ -33,7 +33,40 @@ export default {
 	},
 	device: {
 		product: "产品",
-		producDetail: {
+		baseType: "基础类型",
+		extensionType: "扩展类型",
+		enable: "启用",
+		disable: "停用",
+		fullScreen: "全屏",
+		exitFullScreen: "退出全屏",
+		tip1: "请输入入参,以字符串的方式,如果是对象字符串会在执行时自动转换为对象再执行",
+		tip2: "此处显示执行结果",
+		tip3: "保存脚本",
+		tip4: "调试",
+		tip5: "下面是预制的空的方法,请不要修改函数名称,直接在内部编写函数即可",
+		tip6: "此处是设备功能调用应答数据解析",
+		tip7: "此处编写对数据的处理",
+		tip8: "此处是设备功能调用发送数据解析",
+		tip9: "此处编写对数据的处理",
+		tip10: "请先输入可执行脚本",
+		runStatus: "运行状态",
+		deviceInfo: "设备信息",
+		thingModel: "物模型",
+		deviceFunction: "设备功能",
+		logManagement: "日志管理",
+		topicList: "Topic列表",
+		deviceArchive: "设备档案",
+		deviceStatus: "设备状态",
+		dataTime: "数据时间",
+		linkProtocol: "链接协议",
+		firmwareVersion: "固件版本",
+		registryTime: "注册时间",
+		address: "详细地址",
+		deviceIdentifier: "设备标识",
+		deviceTimeout: "设备超时时间",
+		unitSecond: "秒",
+		update: "更新",
+		productDetail: {
 			productInfo: "产品信息",
 			// 开关状态
 			enable: "启用",
@@ -72,6 +105,20 @@ export default {
 			deviceAccess: "设备接入",
 			dataParsing: "数据解析"
 		},
+		dialogI18n: {
+			addPro: "新增产品",
+			editPro: "编辑产品",
+			addPropertyDefinition: "新增属性定义",
+			editPropertyDefinition: "修改属性定义",
+			editFunctionDefinition: "修改功能定义",
+			addFunctionDefinition: "新增功能定义",
+			editOption: "编辑参数",
+			addOption: "新增参数",	
+			editEventDefinition: "编辑事件定义",
+			addEventDefinition: "新增事件定义",
+			editTagDefinition: "编辑标签定义",
+			addTagDefinition: "新增标签定义"
+		},
 		tableI18nColumn: {
 			categoryName: "分类名称",
 			desc: "描述",
@@ -82,6 +129,10 @@ export default {
 			transportProtocol: "接入方式",
 			deviceType: "类型",
 			status: "状态",
+			deviceName: "设备名称",
+			deviceType2: "设备类型",
+			lastOnlineTime: "最后上线时间",
+			desc2: "说明"
 		},
 		tableI18nAlarmType: {
 		},
@@ -97,25 +148,175 @@ export default {
 			addCategory: "新增分类",
 			editCategory: "编辑分类",
 			deviceManagement: "设备管理",
+			addProductCategory: "新增产品分类",
 		},
 		formI18nLabel: {
 			keyword: "关键字",
 			type: "类型",
-			status: "状态"
+			status: "状态",
+			productKey: "产品标识",
+			productName: "产品名称",
+			productImage: "产品图片",
+			productCategory: "产品分类",
+			messageProtocol: "消息协议",
+			transportProtocol: "接入方式",
+			authType: "认证方式",
+			authUser: "用户名",
+			authPasswd: "密码",
+			accessToken: "Aceess Token",
+			certificateId: "认证证书",
+			deviceType: "设备类型",
+			desc: "产品描述",
+			propertyIdentifier: "属性定义标识",
+			propertyName: "属性定义名称",
+			dataType: "数据类型",
+			precision: "精度",
+			unit: "单位",
+			maxLength: "最大长度",
+			timeFormat: "时间格式",
+			booleanValue: "布尔值",
+			enumItems: "枚举项",
+			jsonObject: "JSON对象",
+			parameterIdentifier: "参数标识",
+			parameterName: "参数名称",
+			elementType: "元素类型",
+			isReadOnly: "是否只读",
+			propertyDescription: "属性定义描述",
+			functionKey: "功能定义标识",
+			functionName: "功能定义名称",
+			functionInput: "输入参数",
+			functionOutput: "输出参数",
+			functionDescription: "功能定义描述",
+			parameterDescription: "参数描述",
+			eventKey: "事件定义标识",
+			eventName: "事件定义名称",
+			eventType: "事件类型",
+			eventDescription: "事件定义描述",
+			valueRange: "取值范围",
+			objectProperty: "对象属性",
+			tagKey: "标签定义标识",
+			tagName: "标签定义名称",
+			tagDescription: "标签定义描述",
+			authDescription: "认证说明",
+			linkInfo: "链接信息",
+			authConfig: "认证配置",
+			contactAdmin: "请联系管理员",
+			productBind: "所属产品",
+			deptIds: "所属组织",
+			deviceTag: "设备标签"
 		},
 		formI18nPlaceholder: {
 			keyword: "输入名称或标识",
 			type: "类型",
-			status: "启用状态"
+			status: "启用状态",
+			productKey: "请输入产品标识",
+			productName: "请输入产品名称",
+			productCategory: "请选择分类",
+			messageProtocol: "请选择消息协议",
+			transportProtocol: "请选择接入方式",
+			authUser: "请输入用户名",
+			authPasswd: "请输入密码",
+			accessToken: "请输入Aceess Token",
+			certificateId: "请选择认证证书",
+			desc: "请输入产品描述",
+			propertyIdentifier: "请输入属性定义标识",
+			propertyName: "请输入属性定义名称",
+			selectDataType: "请选择数据类型",
+			inputPrecision: "请输入精度",
+			inputUnit: "请输入单位",
+			inputMaxLength: "请输入最大长度",
+			inputTimeFormat: "请输入时间格式",
+			inputTrueText: "请输入true时显示的文字",
+			inputBooleanValue: "请输入布尔值",
+			inputFalseText: "请输入false时显示的文字",
+			inputEnumText: "请输入枚举文本",
+			inputEnumValue: "请输入枚举值",
+			selectElementType: "请选择元素类型",
+			inputDescription: "请输入属性定义描述",
+			functionKey: "请输入功能定义标识",
+			functionName: "请输入功能定义名称",
+			functionDescription: "请输入功能定义描述",
+			parameterIdentifier: "请输入参数标识",
+			parameterName: "请输入参数名称",
+			parameterDescription: "请输入参数描述", 
+			eventKey: "请输入事件定义标识",
+			eventName: "请输入事件定义名称",
+			eventDescription: "请输入事件定义描述",
+			valueRangeMin: "最小值",
+			valueRangeMax: "最大值",
+			propertyIdentifier1: "属性标识",
+			propertyName1: "属性名称",
+			tagKey: "请输入标签定义标识",
+			tagName: "请输入标签定义名称",
+			tagDescription: "请输入标签定义描述",
+			inputParameter: "请输入参数",
+			productBind: "选择产品",
+			deptIds: "请选择所属组织",
+			deviceTag: "请输入标签"
 		},
 		formI18nOption: {
 			device: "设备",
 			gateway: "网关",
 			subDevice: "子设备",
 			on: "已启用",
-			off: "未启用"
+			off: "未启用",
+			readonly: "只读",
+			readWrite: "读写",
+			info: "信息",
+			warning: "告警",
+			fault: "故障",
+			online: "在线",
+			offline: "离线",
+		},
+		formI18nButton: {
+			addProduct: "新增产品",
+			addParameter: "新增参数",
+			collapse: "收起",
+			showMoreFilter: "更多筛选",
+			batchEnable: "批量启用",
+			batchDisable: "批量禁用",
+			importDevice: "导入设备",
+			exportDevice: "导出设备",
+			batchCheckStatus: "批量检测状态"
+		},
+		rules: {
+			productName: "产品名称不能为空",
+			productNameMax32: "产品名称不能超过32个字符",
+			productNameValidator: "产品名称不能包含空格",
+			productKey: "产品标识不能为空",
+			productKeyValidator: "产品标识不能包含空格",
+			messageProtocol: "消息协议不能为空",
+			transportProtocol: "接入方式不能为空",
+			categoryId: "产品分类不能为空",
+			deviceType: "设备类型不能为空",
+			// 属性定义相关验证规则
+			propertyName: "属性定义名称不能为空",
+			propertyNameMax32: "属性定义名称不能超过32个字符",
+			propertyNameValidator: "属性定义名称不能包含空格",
+			propertyKey: "属性定义标识不能为空",
+			accessMode: "请选择是否只读",
+			dataType: "请选择数据类型",
+			functionName: "功能定义名称不能为空",
+			functionNameMax32: "功能定义名称不能超过32个字符",
+			functionNameValidator: "功能定义名称不能包含空格",
+			functionKey: "功能定义标识不能为空",
+			functionType: "请选择数据类型",
+			parameterName: "参数名称不能为空",
+			parameterNameMax32: "参数名称不能超过32个字符",
+			parameterNameValidator: "参数名称不能包含空格",
+			parameterKey: "参数标识不能为空",
+			eventName: "事件定义名称不能为空",
+			eventNameMax32: "事件定义名称不能超过32个字符",
+			eventNameValidator: "事件定义名称不能包含空格", 
+			eventKey: "事件定义标识不能为空",
+			eventType: "请选择数据类型",
+			tagKeyName: "标签定义名称不能为空",
+			tagKeyMax32: "标签定义名称不能超过32个字符",
+			tagKeyValidator: "标签定义名称不能包含空格",
+			tagKey: "标签定义标识不能为空",
+			tagAccessMode: "请选择是否只读",
+			tagType: "请选择数据类型"
 		}
-			
 	},
 	alarm: {
 		tableI18nColumn: {
@@ -138,5 +339,47 @@ export default {
 			processed: "已处理",
 			ignored: "已忽略"
 		}
-	}
-};
+	},
+	// 级联管理
+	cascade: {
+		dialogI18n: {
+			deviceList: "-设备列表",
+		},
+		tableI18nColumn: {
+			subPlatform: "子平台",
+			address: "地址",
+			device: "设备",
+			status: "状态",
+			lastUpdateTime: "最后更新时间",
+			operation: "操作",
+			identifier: "标识",
+			deviceName: "设备名称",
+			deviceType: "设备类型",
+			belongProduct: "所属产品",
+			lastOnlineTime: "最后上线时间"
+			
+		},
+		tableI18nStatus: {
+			online: "在线",
+			offline: "离线",
+			disabled: "未启用"
+		},
+		tableI18nConfirm: {
+		},
+		tableI18nAction: {
+			viewDevice: "查看设备",
+			detail: "详情",
+			sync: "同步"
+		},
+		formI18nLabel: {
+			subPlatformTotal: "子平台",
+			deviceTotal: "设备总数"
+		},
+		formI18nPlaceholder: {
+		},
+		formI18nOption: {
+		},
+		formI18nButton: {
+		}
+	},
+};

+ 250 - 10
src/i18n/pages/iotmanagerI18n/zh-tw.ts

@@ -33,7 +33,40 @@ export default {
 	},
 	device: {
 		product: "產品",
-		producDetail: {
+		baseType: "基礎類型",
+		extensionType: "擴展類型",
+		enable: "啟用",
+		disable: "停用",
+		fullScreen: "全屏",
+		exitFullScreen: "退出全屏",
+		tip1: "請輸入入參,以字符串的方式,如果是對象字符串會在執行時自動轉換為對象再執行",
+		tip2: "此處顯示執行結果",
+		tip3: "保存腳本",
+		tip4: "調試",
+		tip5: "下面是預製的空的方法,請不要修改函數名稱,直接在內部編寫函數即可",
+		tip6: "此處是設備功能調用應答數據解析",
+		tip7: "此處編寫對數據的處理",
+		tip8: "此處是設備功能調用發送數據解析",
+		tip9: "此處編寫對數據的處理",
+		tip10: "請先輸入可執行腳本",
+		runStatus: "運行狀態",
+		deviceInfo: "設備信息",
+		thingModel: "物模型",
+		deviceFunction: "設備功能",
+		logManagement: "日誌管理",
+		topicList: "Topic列表",
+		deviceArchive: "設備檔案",
+		deviceStatus: "設備狀態",
+		dataTime: "資料時間",
+		linkProtocol: "鏈接協議",
+		firmwareVersion: "固件版本",
+		registryTime: "註冊時間",
+		address: "詳細地址",
+		deviceIdentifier: "設備標識",
+		deviceTimeout: "設備超時時間",
+		unitSecond: "秒",
+		update: "更新",
+		productDetail: {
 			productInfo: "產品資訊",
 			// 開關狀態
 			enable: "啟用",
@@ -72,6 +105,20 @@ export default {
 			deviceAccess: "設備接入",
 			dataParsing: "資料解析"
 		},
+		dialogI18n: {
+			addPro: "新增產品",
+			editPro: "編輯產品",
+			addPropertyDefinition: "新增屬性定義",
+			editPropertyDefinition: "編輯屬性定義",
+			editFunctionDefinition: "編輯功能定義",
+			addFunctionDefinition: "新增功能定義",
+			editOption: "編輯參數",
+			addOption: "新增參數",
+			editEventDefinition: "編輯事件定義",
+			addEventDefinition: "新增事件定義",
+			editTagDefinition: "編輯標籤定義",
+			addTagDefinition: "新增標籤定義"
+		},
 		tableI18nColumn: {
 			categoryName: "分類名稱",
 			desc: "描述",
@@ -81,7 +128,11 @@ export default {
 			category: "分類",
 			transportProtocol: "接入方式",
 			deviceType: "類型",
-			status: "狀態"
+			status: "狀態",
+			deviceName: "設備名稱",
+			deviceType2: "設備類型",
+			lastOnlineTime: "最後上線時間",
+			desc2: "說明"
 		},
 		tableI18nAlarmType: {
 		},
@@ -94,29 +145,177 @@ export default {
 			deleteProductMessage: "此操作將永久刪除產品:{name},是否繼續?"
 		},
 		tableI18nAction: {
-			addCategory: "添加分類",
+			addCategory: "新增分類",
 			editCategory: "編輯分類",
-			deviceManagement: "設備管理"
+			deviceManagement: "設備管理",
+			addProductCategory: "新增產品分類",
 		},
 		formI18nLabel: {
 			keyword: "關鍵字",
 			type: "類型",
-			status: "狀態"
+			status: "狀態",
+			productKey: "產品標識",
+			productName: "產品名稱",
+			productImage: "產品圖片",
+			productCategory: "產品分類",
+			messageProtocol: "消息協議",
+			transportProtocol: "接入方式",
+			authType: "認證方式",
+			authUser: "用戶名",
+			authPasswd: "密碼",
+			accessToken: "Aceess Token",
+			certificateId: "認證證書",
+			deviceType: "設備類型",
+			desc: "產品描述",
+			propertyIdentifier: "屬性定義標識",
+			propertyName: "屬性定義名稱",
+			dataType: "數據類型",
+			precision: "精度",
+			unit: "單位",
+			maxLength: "最大長度",
+			timeFormat: "時間格式",
+			booleanValue: "布林值",
+			enumItems: "枚舉項",
+			jsonObject: "JSON物件",
+			parameterIdentifier: "參數標識",
+			parameterName: "參數名稱",
+			elementType: "元素類型",
+			isReadOnly: "是否唯讀",
+			propertyDescription: "屬性定義描述",
+			functionKey: "功能定義標識",
+			functionName: "功能定義名稱",
+			functionInput: "輸入參數",
+			functionOutput: "輸出參數",
+			functionDescription: "功能定義描述",
+			parameterDescription: "參數描述",
+			eventKey: "事件定義標識",
+			eventName: "事件定義名稱",
+			eventType: "事件類型",
+			eventDescription: "事件定義描述",
+			valueRange: "取值範圍",
+			objectProperty: "對象屬性",
+			tagKey: "標籤定義標識",
+			tagName: "標籤定義名稱",
+			tagDescription: "標籤定義描述",
+			authDescription: "認證說明",
+			linkInfo: "鏈接信息",
+			authConfig: "認證配置",
+			contactAdmin: "請聯繫管理員",
+			productBind: "所屬產品",
+			deptIds: "所屬組織",
+			deviceTag: "設備標籤"
 		},
 		formI18nPlaceholder: {
 			keyword: "輸入名稱或標識",
 			type: "類型",
-			status: "啟用狀態"
+			status: "啟用狀態",
+			productKey: "請輸入產品標識",
+			productName: "請輸入產品名稱",
+			productCategory: "請選擇分類",
+			messageProtocol: "請選擇消息協議",
+			transportProtocol: "請選擇接入方式",
+			authUser: "請輸入用戶名",
+			authPasswd: "請輸入密碼",
+			accessToken: "請輸入Aceess Token",
+			certificateId: "請選擇認證證書",
+			desc: "請輸入產品描述",
+			propertyIdentifier: "請輸入屬性定義標識",
+			propertyName: "請輸入屬性定義名稱",
+			selectDataType: "請選擇數據類型",
+			inputPrecision: "請輸入精度",
+			inputUnit: "請輸入單位",
+			inputMaxLength: "請輸入最大長度",
+			inputTimeFormat: "請輸入時間格式",
+			inputTrueText: "請輸入true時顯示的文字",
+			inputBooleanValue: "請輸入布林值",
+			inputFalseText: "請輸入false時顯示的文字",
+			inputEnumText: "請輸入枚舉文本",
+			inputEnumValue: "請輸入枚舉值",
+			selectElementType: "請選擇元素類型",
+			inputDescription: "請輸入屬性定義描述",
+			functionKey: "請輸入功能定義標識",
+			functionName: "請輸入功能定義名稱",
+			functionDescription: "請輸入功能定義描述",
+			parameterIdentifier: "請輸入參數標識",
+			parameterName: "請輸入參數名稱",
+			parameterDescription: "請輸入參數描述",
+			eventKey: "請輸入事件定義標識",
+			eventName: "請輸入事件定義名稱",
+			eventDescription: "請輸入事件定義描述",
+			valueRangeMin: "最小值",
+			valueRangeMax: "最大值",
+			propertyIdentifier1: "屬性標識",
+			propertyName1: "屬性名稱",
+			tagKey: "請輸入標籤定義標識",
+			tagName: "請輸入標籤定義名稱",
+			tagDescription: "請輸入標籤定義描述",
+			inputParameter: "請輸入參數",
+			productBind: "選擇產品",
+			deptIds: "請選擇所属組織",
+			deviceTag: "請輸入標籤"
 		},
 		formI18nOption: {
 			device: "設備",
 			gateway: "網關",
 			subDevice: "子設備",
 			on: "已啟用",
-			off: "未啟用"
+			off: "未啟用",
+			readonly: "唯讀",
+			readWrite: "讀寫",
+			info: "資訊",
+			warning: "告警",
+			fault: "故障",
+			online: "在線",
+			offline: "離線",
 		},
 		formI18nButton: {
-			addProduct: "新增產品"
+			addProduct: "新增產品",
+			addParameter: "新增參數",
+			collapse: "收起",
+			showMoreFilter: "更多篩選",
+			batchEnable: "批量啟用",
+			batchDisable: "批量禁用",
+			importDevice: "导入设备",
+			exportDevice: "导出设备",
+			batchCheckStatus: "批量檢查狀態"
+		},
+		rules: {
+			productName: "產品名稱不能為空",
+			productNameMax32: "產品名稱不能超過32個字符",
+			productNameValidator: "產品名稱不能包含空格",
+			productKey: "產品標識不能為空",
+			productKeyValidator: "產品標識不能包含空格",
+			messageProtocol: "消息協議不能為空",
+			transportProtocol: "接入方式不能為空",
+			categoryId: "產品分類不能為空",
+			deviceType: "設備類型不能為空",
+			// 屬性定義相關驗證規則
+			propertyName: "屬性定義名稱不能為空",
+			propertyNameMax32: "屬性定義名稱不能超過32個字符",
+			propertyNameValidator: "屬性定義名稱不能包含空格",
+			propertyKey: "屬性定義標識不能為空",
+			accessMode: "請選擇是否唯讀",
+			dataType: "請選擇資料類型",
+			functionName: "功能定義名稱不能為空",
+			functionNameMax32: "功能定義名稱不能超過32個字符",
+			functionNameValidator: "功能定義名稱不能包含空格",
+			functionKey: "功能定義標識不能為空",
+			functionType: "請選擇資料類型",
+			parameterName: "參數名稱不能為空",
+			parameterNameMax32: "參數名稱不能超過32個字符",
+			parameterNameValidator: "參數名稱不能包含空格",
+			parameterKey: "參數標識不能為空",
+			eventName: "事件定義名稱不能為空",
+			eventNameMax32: "事件定義名稱不能超過32個字符",
+			eventNameValidator: "事件定義名稱不能包含空格",
+			eventKey: "事件定義標識不能為空",
+			eventType: "請選擇事件類型",
+			tagKeyName: "標籤定義名稱不能為空",
+			tagKeyMax32: "標籤定義名稱不能超過32個字符",
+			tagKeyValidator: "標籤定義名稱不能包含空格",
+			tagKey: "標籤定義標識不能為空",
+			tagAccessMode: "請選擇是否只讀",
+			tagType: "請選擇資料類型"
 		}
 	},
 	alarm: {
@@ -140,5 +339,46 @@ export default {
 			processed: "已處理",
 			ignored: "已忽略"
 		}
-	}
-};
+	},
+	// 级联管理
+	cascade: {
+		dialogI18n: {
+			deviceList: "-設備列表",
+		},
+		tableI18nColumn: {
+			subPlatform: "子平台",
+			address: "地址",
+			device: "設備",
+			status: "狀態",
+			lastUpdateTime: "最後更新時間",
+			operation: "操作",
+			identifier: "標識",
+			deviceName: "設備名稱",
+			deviceType: "設備類型",
+			belongProduct: "所屬產品",
+			lastOnlineTime: "最後上線時間"
+		},
+		tableI18nStatus: {
+			online: "在線",
+			offline: "離線",
+			disabled: "未啟用"
+		},
+		tableI18nConfirm: {
+		},
+		tableI18nAction: {
+			viewDevice: "查看設備",
+			detail: "詳情",
+			sync: "同步"
+		},
+		formI18nLabel: {
+			subPlatformTotal: "子平台",
+			deviceTotal: "設備總數"
+		},
+		formI18nPlaceholder: {
+		},
+		formI18nOption: {
+		},
+		formI18nButton: {
+		}
+	},
+};

+ 553 - 0
src/i18n/pages/projects/en.ts

@@ -0,0 +1,553 @@
+export default {
+  list: {
+    search: {
+      channelPlaceholder: 'Search channel merchant',
+      statusPlaceholder: 'Please select format',
+      query: 'Search',
+      reset: 'Reset'
+    },
+    addOrEdit: {
+      form: {
+        name: 'Project Name',
+        addressCode: 'Region',
+        addressDetail: 'Address Detail',
+        channelMerchants: 'Channel Merchant',
+        customName: 'Key Customer',
+        repairCompany: 'Maintenance Company',
+        repairMobile: 'Maintenance Phone'
+      },
+      placeholders: {
+        select: 'Please select',
+        name: 'Enter project name',
+        addressDetail: 'Enter address detail',
+        channelMerchants: 'Enter channel merchant',
+        customName: 'Enter customer name',
+        repairCompany: 'Enter maintenance company',
+        repairMobile: 'Enter maintenance phone'
+      },
+      buttons: {
+        cancel: 'Cancel',
+        confirm: 'Confirm'
+      },
+      validation: {
+        name: 'Please enter project name',
+        addressCode: 'Please select region',
+        addressDetail: 'Please enter address detail',
+        channelMerchants: 'Please enter channel merchant',
+        customName: 'Please enter customer name',
+        repairCompany: 'Please enter maintenance company',
+        repairMobile: 'Please enter maintenance phone'
+      },
+      messages: {
+        addSuccess: 'Added successfully',
+        editSuccess: 'Edited successfully'
+      }
+    },
+    status: {
+      all: 'All status',
+      enabled: 'Enabled',
+      disabled: 'Disabled'
+    },
+    actions: {
+      add: 'Add',
+      delete: 'Delete',
+      enable: 'Enable',
+      disable: 'Disable'
+    },
+    table: {
+      columns: {
+        projectStatus: 'Project Status',
+        projectName: 'Project Name',
+        address: 'Province/City/District/County',
+        addressDetail: 'Address Detail',
+        channelMerchants: 'Channel Merchant',
+        customName: 'Key Customer',
+        repairCompany: 'Maintenance Company',
+        updatedAt: 'Updated At',
+        actions: 'Actions'
+      },
+      actions: {
+        detail: 'Detail',
+        edit: 'Edit',
+        delete: 'Delete'
+      }
+    },
+    messages: {
+      operationSuccess: 'Operation succeeded',
+      deleteConfirmSingle: 'This will delete project: “{name}”. Continue?',
+      deleteConfirmMultiple: 'This will delete {count} projects',
+      tip: 'Prompt',
+      confirm: 'Confirm',
+      cancel: 'Cancel',
+      deleteSuccess: 'Deleted successfully'
+    }
+  },
+  editDialog: {
+    title: {
+      add: 'Add Project',
+      edit: 'Edit Project'
+    }
+  },
+  detail: {
+    tabs: {
+      overview: 'Overview',
+      devices: 'Gateways & Devices',
+      scene: 'Scene Linkage',
+      topo: 'Topology Apps',
+      video: 'Video Surveillance'
+    },
+    device: {
+      actions: {
+        add: 'Add Device',
+        cancel: 'Cancel',
+        confirm: 'Confirm'
+      },
+      table: {
+        columns: {
+          key: 'Key',
+          name: 'Device Name',
+          deviceType: 'Device Type',
+          productName: 'Product',
+          status: 'Status',
+          lastOnlineTime: 'Last Online Time',
+          desc: 'Description',
+          actions: 'Actions'
+        },
+        actions: {
+          detail: 'Device Details',
+          unbind: 'Unbind'
+        },
+        statusTags: {
+          offline: 'Offline',
+          online: 'Online',
+          inactive: 'Inactive'
+        }
+      },
+      dialog: {
+        title: 'Add Device'
+      },
+      form: {
+        labels: {
+          product: 'Product',
+          device: 'Device'
+        },
+        placeholders: {
+          product: 'Select product',
+          device: 'Select device'
+        }
+      },
+      messages: {
+        selectDeviceFirst: 'Please select a device first',
+        addSuccess: 'Added successfully',
+        deleteConfirm: 'Are you sure to delete?',
+        tip: 'Prompt',
+        confirm: 'Confirm',
+        cancel: 'Cancel',
+        unbindSuccess: 'Unbound successfully'
+      }
+    },
+    info: {
+      actions: {
+        edit: 'Edit'
+      },
+      descriptions: {
+        name: 'Project Name',
+        customName: 'Key Customer',
+        address: 'Region',
+        channelMerchants: 'Channel Merchant',
+        repairCompany: 'Maintenance Company',
+        repairMobile: 'Maintenance Phone',
+        addressDetail: 'Address Detail'
+      }
+    },
+    scene: {
+      actions: {
+        add: 'Add Scene',
+        cancel: 'Cancel',
+        confirm: 'Confirm'
+      },
+      table: {
+        columns: {
+          id: 'ID',
+          name: 'Scene Name',
+          sceneType: 'Trigger Type',
+          status: 'Status',
+          description: 'Description',
+          createdAt: 'Created At',
+          actions: 'Actions'
+        },
+        actions: {
+          detail: 'Scene Details',
+          unbind: 'Unbind'
+        },
+        statusTags: {
+          enabled: 'Enabled',
+          disabled: 'Disabled'
+        },
+        sceneTypeTags: {
+          device: 'Device Trigger',
+          manual: 'Manual Trigger',
+          timer: 'Timer Trigger'
+        }
+      },
+      dialog: {
+        title: 'Add Scene'
+      },
+      form: {
+        labels: {
+          scene: 'Scene'
+        },
+        placeholders: {
+          scene: 'Select scene'
+        }
+      },
+      messages: {
+        selectSceneFirst: 'Please select a scene first',
+        addSuccess: 'Added successfully',
+        unbindConfirm: 'Are you sure to unbind this scene?',
+        tip: 'Prompt',
+        confirm: 'Confirm',
+        cancel: 'Cancel',
+        unbindSuccess: 'Unbound successfully'
+      }
+    },
+    topo: {
+      actions: {
+        add: 'Add Topology',
+        cancel: 'Cancel',
+        confirm: 'Confirm'
+      },
+      table: {
+        columns: {
+          id: 'ID',
+          name: 'Topology Name',
+          createdAt: 'Created At',
+          updatedAt: 'Updated At',
+          actions: 'Actions'
+        },
+        actions: {
+          preview: 'Preview',
+          edit: 'Edit Topology',
+          unbind: 'Unbind'
+        }
+      },
+      dialog: {
+        title: 'Add Topology'
+      },
+      form: {
+        labels: {
+          topo: 'Topology'
+        },
+        placeholders: {
+          topo: 'Select topology'
+        }
+      },
+      messages: {
+        selectTopoFirst: 'Please select a topology first',
+        addSuccess: 'Added successfully',
+        unbindConfirm: 'Are you sure to unbind this topology?',
+        tip: 'Prompt',
+        confirm: 'Confirm',
+        cancel: 'Cancel',
+        unbindSuccess: 'Unbound successfully'
+      }
+    },
+    video: {
+      actions: {
+        add: 'Add Video',
+        cancel: 'Cancel',
+        confirm: 'Confirm',
+        openInNewWindow: 'Open in new window'
+      },
+      table: {
+        columns: {
+          deviceName: 'Device Name',
+          deviceId: 'Device ID',
+          channelName: 'Channel Name',
+          model: 'Model',
+          manufacturer: 'Manufacturer',
+          liveStatus: 'Status',
+          keepAliveTime: 'Last Heartbeat',
+          registerTime: 'Register Time',
+          updateAt: 'Updated At',
+          actions: 'Actions'
+        },
+        actions: {
+          view: 'View Video',
+          unbind: 'Unbind'
+        }
+      },
+      dialog: {
+        addTitle: 'Add Video',
+        previewTitle: 'Preview Video'
+      },
+      form: {
+        labels: {
+          video: 'Video'
+        },
+        placeholders: {
+          video: 'Select video'
+        }
+      },
+      messages: {
+        selectVideoFirst: 'Please select a video first',
+        addSuccess: 'Added successfully',
+        unbindConfirm: 'Are you sure to unbind this video?',
+        tip: 'Prompt',
+        confirm: 'Confirm',
+        cancel: 'Cancel',
+        unbindSuccess: 'Unbound successfully'
+      },
+      liveStatusTags: {
+        idle: 'Idle',
+        inviting: 'Inviting',
+        streaming: 'Streaming'
+      }
+    }
+  },
+  screen: {
+    video: {
+      selects: {
+        project: 'Select Project'
+      }
+    },
+    info: {
+      title: 'Project Info',
+      selects: {
+        placeholder: 'Please select'
+      },
+      labels: {
+        projectName: 'Project Name:',
+        projectAddress: 'Project Address:'
+      },
+      units: {
+        squareMeter: 'm²',
+        device: 'units'
+      },
+      cards: {
+        coverageArea: 'Coverage Area',
+        deviceCount: 'Device Count',
+        maintainPhone: 'Maintenance Phone'
+      },
+      empty: {
+        dash: '-'
+      }
+    },
+    baseinfo: {
+      title: 'Basic Info',
+      selects: {
+        placeholder: 'Please select'
+      }
+    },
+    lineChart: {
+      title: 'Device Status',
+      date: {
+        rangeSeparator: 'to',
+        startPlaceholder: 'Start Time',
+        endPlaceholder: 'End Time'
+      },
+      selects: {
+        properties: 'Select properties'
+      }
+    }
+  },
+  filter: {
+    index: {
+      search: {
+        namePlaceholder: 'Search template name',
+        deviceKeyPlaceholder: 'Search device key',
+        statusPlaceholder: 'Select forward format',
+        query: 'Query',
+        reset: 'Reset'
+      },
+      status: {
+        all: 'All',
+        enabled: 'Enabled',
+        disabled: 'Disabled'
+      },
+      actions: {
+        add: 'Add',
+        delete: 'Delete'
+      },
+      table: {
+        columns: {
+          name: 'Template Name',
+          code: 'Template Code',
+          updatedAt: 'Updated At',
+          status: 'Status',
+          actions: 'Actions'
+        },
+        actions: {
+          detail: 'Details',
+          edit: 'Edit',
+          delete: 'Delete',
+          viewBind: 'View & Bind Devices'
+        }
+      },
+      switch: {
+        active: 'On',
+        inactive: 'Off'
+      },
+      messages: {
+        deleteConfirmSingle: 'This will delete template: “{name}”. Continue?',
+        deleteConfirmMultiple: 'This will delete {count} templates',
+        tip: 'Tip',
+        warn: 'Warning',
+        confirm: 'Confirm',
+        cancel: 'Cancel',
+        deleteSuccess: 'Deleted successfully',
+        enableActionText: 'Enable',
+        disableActionText: 'Disable',
+        statusChangeConfirm: 'Confirm to {action} template: 【{name}】?',
+        statusChangeSuccess: '{action} succeeded'
+      }
+    },
+    edit: {
+      form: {
+        name: 'Template Name'
+      },
+      placeholders: {
+        name: 'Enter template name'
+      },
+      actions: {
+        cancel: 'Cancel',
+        confirm: 'Confirm'
+      },
+      rules: {
+        required: 'Required'
+      },
+      messages: {
+        success: 'Operation succeeded'
+      }
+    },
+    editDialog: {
+      title: {
+        add: 'Add Template',
+        edit: 'Edit Template'
+      }
+    },
+    detail: {
+      dialog: {
+        title: 'Template Details'
+      },
+      sections: {
+        templateInfo: 'Device Data Filter Template Info',
+        customAttr: 'Custom Attributes'
+      },
+      descriptions: {
+        templateName: 'Template Name',
+        templateCode: 'Template Code',
+        updatedAt: 'Updated At'
+      },
+      actions: {
+        addCustomAttr: 'Add Custom Attribute'
+      },
+      table: {
+        columns: {
+          name: 'Attribute Name',
+          key: 'Attribute Code',
+          unit: 'Unit',
+          updatedAt: 'Updated At',
+          isVisible: 'Visible',
+          actions: 'Actions'
+        },
+        actions: {
+          edit: 'Edit',
+          delete: 'Delete'
+        }
+      },
+      switch: {
+        active: 'On',
+        inactive: 'Off'
+      },
+      messages: {
+        warn: 'Warning',
+        tip: 'Prompt',
+        confirm: 'Confirm',
+        cancel: 'Cancel',
+        deleteSuccess: 'Deleted successfully',
+        deleteConfirmSingle: 'This will delete attribute: “{name}”. Continue?',
+        deleteConfirmMultiple: 'This will delete {count} attributes',
+        confirmSetVisible: 'Confirm to set attribute "{name}" as visible?',
+        confirmSetInvisible: 'Confirm to set attribute "{name}" as invisible?',
+        setVisibleSuccess: 'Set visible successfully',
+        setInvisibleSuccess: 'Set invisible successfully'
+      }
+    }
+    ,
+    bindDevice: {
+      dialog: {
+        title: 'Bind Device'
+      },
+      sections: {
+        templateInfo: 'Device Data Filter Template Info',
+        bindDevice: 'Bind Device'
+      },
+      descriptions: {
+        templateName: 'Template Name',
+        templateCode: 'Template Code',
+        updatedAt: 'Updated At'
+      },
+      actions: {
+        bindDevice: 'Bind Device'
+      },
+      table: {
+        columns: {
+          name: 'Device Name',
+          key: 'Device Key',
+          productName: 'Product',
+          actions: 'Actions'
+        },
+        actions: {
+          unbind: 'Unbind'
+        }
+      },
+      messages: {
+        unbindSuccess: 'Unbound successfully'
+      }
+    },
+    bindDeviceForm: {
+      dialog: {
+        title: 'Bind Device'
+      },
+      table: {
+        columns: {
+          name: 'Device Name',
+          key: 'Device Key',
+          productName: 'Product'
+        }
+      },
+      actions: {
+        cancel: 'Cancel',
+        bind: 'Bind Device'
+      },
+      messages: {
+        bindSuccess: 'Operation successful'
+      }
+    },
+    attrEdit: {
+      titleEdit: 'Edit Attribute',
+      titleAdd: 'Add Attribute',
+      labels: {
+        name: 'Attribute Name',
+        key: 'Attribute KEY',
+        unit: 'Unit'
+      },
+      placeholders: {
+        name: 'Enter attribute name',
+        key: 'Enter attribute code',
+        unit: 'Enter unit'
+      },
+      actions: {
+        cancel: 'Cancel',
+        confirm: 'Confirm'
+      },
+      rules: {
+        required: 'Required'
+      },
+      messages: {
+        success: 'Operation succeeded'
+      }
+    }
+  }
+};

+ 553 - 0
src/i18n/pages/projects/zh-cn.ts

@@ -0,0 +1,553 @@
+export default {
+  list: {
+    search: {
+      channelPlaceholder: '渠道商搜索',
+      statusPlaceholder: '请选择转发格式',
+      query: '查询',
+      reset: '重置'
+    },
+    addOrEdit: {
+      form: {
+        name: '项目名称',
+        addressCode: '地区',
+        addressDetail: '详细地址',
+        channelMerchants: '渠道商',
+        customName: '关键客户',
+        repairCompany: '维修公司',
+        repairMobile: '维修电话'
+      },
+      placeholders: {
+        select: '请选择',
+        name: '输入项目名称',
+        addressDetail: '输入详细地址',
+        channelMerchants: '输入渠道商',
+        customName: '输入客户名称',
+        repairCompany: '输入维修公司',
+        repairMobile: '输入维修电话'
+      },
+      buttons: {
+        cancel: '取消',
+        confirm: '确定'
+      },
+      validation: {
+        name: '请输入项目名称',
+        addressCode: '请选择地区',
+        addressDetail: '请输入详细地址',
+        channelMerchants: '请输入渠道商',
+        customName: '请输入客户名称',
+        repairCompany: '请输入维修公司',
+        repairMobile: '请输入维修电话'
+      },
+      messages: {
+        addSuccess: '新增成功',
+        editSuccess: '编辑成功'
+      }
+    },
+    status: {
+      all: '全部状态',
+      enabled: '正常',
+      disabled: '禁用'
+    },
+    actions: {
+      add: '新增',
+      delete: '删除',
+      enable: '启用',
+      disable: '禁用'
+    },
+    table: {
+      columns: {
+        projectStatus: '项目状态',
+        projectName: '项目名称',
+        address: '省/市/区/县',
+        addressDetail: '详细地址',
+        channelMerchants: '渠道商',
+        customName: '关键客户',
+        repairCompany: '维修公司',
+        updatedAt: '更新时间',
+        actions: '操作'
+      },
+      actions: {
+        detail: '详情',
+        edit: '编辑',
+        delete: '删除'
+      }
+    },
+    messages: {
+      operationSuccess: '操作成功',
+      deleteConfirmSingle: '此操作将项目:“{name}”,是否继续?',
+      deleteConfirmMultiple: '此操作将{count}个项目',
+      tip: '提示',
+      confirm: '确认',
+      cancel: '取消',
+      deleteSuccess: '删除成功'
+    }
+  },
+  editDialog: {
+    title: {
+      add: '新增项目',
+      edit: '编辑项目'
+    }
+  },
+  detail: {
+    tabs: {
+      overview: '项目概况',
+      devices: '网关和设备',
+      scene: '场景联动',
+      topo: '组态应用',
+      video: '视频监控'
+    },
+    device: {
+      actions: {
+        add: '添加设备',
+        cancel: '取消',
+        confirm: '确定'
+      },
+      table: {
+        columns: {
+          key: '标识',
+          name: '设备名称',
+          deviceType: '设备类型',
+          productName: '所属产品',
+          status: '状态',
+          lastOnlineTime: '最后上线时间',
+          desc: '说明',
+          actions: '操作'
+        },
+        actions: {
+          detail: '设备详情',
+          unbind: '解绑'
+        },
+        statusTags: {
+          offline: '离线',
+          online: '在线',
+          inactive: '未启用'
+        }
+      },
+      dialog: {
+        title: '添加设备'
+      },
+      form: {
+        labels: {
+          product: '产品',
+          device: '设备'
+        },
+        placeholders: {
+          product: '选择产品',
+          device: '选择设备'
+        }
+      },
+      messages: {
+        selectDeviceFirst: '请先选择设备',
+        addSuccess: '添加成功',
+        deleteConfirm: '确定要删除?',
+        tip: '提示',
+        confirm: '确认',
+        cancel: '取消',
+        unbindSuccess: '解绑成功'
+      }
+    },
+    info: {
+      actions: {
+        edit: '编辑'
+      },
+      descriptions: {
+        name: '项目名称',
+        customName: '关键客户',
+        address: '地区',
+        channelMerchants: '渠道商',
+        repairCompany: '维修公司',
+        repairMobile: '维修电话',
+        addressDetail: '详细地址'
+      }
+    },
+    scene: {
+      actions: {
+        add: '添加场景',
+        cancel: '取消',
+        confirm: '确定'
+      },
+      table: {
+        columns: {
+          id: 'ID',
+          name: '场景名称',
+          sceneType: '触发方式',
+          status: '状态',
+          description: '场景描述',
+          createdAt: '创建时间',
+          actions: '操作'
+        },
+        actions: {
+          detail: '场景详情',
+          unbind: '解绑'
+        },
+        statusTags: {
+          enabled: '启用',
+          disabled: '禁用'
+        },
+        sceneTypeTags: {
+          device: '设备触发',
+          manual: '手动触发',
+          timer: '定时触发'
+        }
+      },
+      dialog: {
+        title: '添加场景'
+      },
+      form: {
+        labels: {
+          scene: '场景'
+        },
+        placeholders: {
+          scene: '选择场景'
+        }
+      },
+      messages: {
+        selectSceneFirst: '请先选择场景',
+        addSuccess: '添加成功',
+        unbindConfirm: '确定要解绑该场景?',
+        tip: '提示',
+        confirm: '确认',
+        cancel: '取消',
+        unbindSuccess: '解绑成功'
+      }
+    },
+    topo: {
+      actions: {
+        add: '添加组态',
+        cancel: '取消',
+        confirm: '确定'
+      },
+      table: {
+        columns: {
+          id: 'ID',
+          name: '组态图名称',
+          createdAt: '创建时间',
+          updatedAt: '更新时间',
+          actions: '操作'
+        },
+        actions: {
+          preview: '预览',
+          edit: '编辑组态图',
+          unbind: '解绑'
+        }
+      },
+      dialog: {
+        title: '添加组态'
+      },
+      form: {
+        labels: {
+          topo: '组态'
+        },
+        placeholders: {
+          topo: '选择组态'
+        }
+      },
+      messages: {
+        selectTopoFirst: '请先选择组态',
+        addSuccess: '添加成功',
+        unbindConfirm: '确定要解绑该组态?',
+        tip: '提示',
+        confirm: '确认',
+        cancel: '取消',
+        unbindSuccess: '解绑成功'
+      }
+    },
+    video: {
+      actions: {
+        add: '添加监控',
+        cancel: '取消',
+        confirm: '确定',
+        openInNewWindow: '新窗口预览'
+      },
+      table: {
+        columns: {
+          deviceName: '设备名称',
+          deviceId: '设备ID',
+          channelName: '通道名称',
+          model: '型号',
+          manufacturer: '厂商',
+          liveStatus: '状态',
+          keepAliveTime: '最后心跳时间',
+          registerTime: '注册时间',
+          updateAt: '更新时间',
+          actions: '操作'
+        },
+        actions: {
+          view: '查看监控',
+          unbind: '解绑'
+        }
+      },
+      dialog: {
+        addTitle: '添加监控',
+        previewTitle: '预览监控'
+      },
+      form: {
+        labels: {
+          video: '监控'
+        },
+        placeholders: {
+          video: '选择监控'
+        }
+      },
+      messages: {
+        selectVideoFirst: '请先选择监控',
+        addSuccess: '添加成功',
+        unbindConfirm: '确定要解绑该监控?',
+        tip: '提示',
+        confirm: '确认',
+        cancel: '取消',
+        unbindSuccess: '解绑成功'
+      },
+      liveStatusTags: {
+        idle: '空闲',
+        inviting: '调用中',
+        streaming: '拉流中'
+      }
+    }
+  },
+  screen: {
+    video: {
+      selects: {
+        project: '选择项目'
+      }
+    },
+    info: {
+      title: '项目信息',
+      selects: {
+        placeholder: '请选择'
+      },
+      labels: {
+        projectName: '项目名称:',
+        projectAddress: '项目地址:'
+      },
+      units: {
+        squareMeter: 'm²',
+        device: '台'
+      },
+      cards: {
+        coverageArea: '覆盖面积',
+        deviceCount: '设备台数',
+        maintainPhone: '维保电话'
+      },
+      empty: {
+        dash: '-'
+      }
+    },
+    baseinfo: {
+      title: '基本信息',
+      selects: {
+        placeholder: '请选择'
+      }
+    },
+    lineChart: {
+      title: '设备状态',
+      date: {
+        rangeSeparator: '至',
+        startPlaceholder: '开始时间',
+        endPlaceholder: '结束时间'
+      },
+      selects: {
+        properties: '请选择'
+      }
+    }
+  },
+  filter: {
+    index: {
+      search: {
+        namePlaceholder: '模板名称搜索',
+        deviceKeyPlaceholder: '设备key搜索',
+        statusPlaceholder: '请选择转发格式',
+        query: '查询',
+        reset: '重置'
+      },
+      status: {
+        all: '全部状态',
+        enabled: '正常',
+        disabled: '禁用'
+      },
+      actions: {
+        add: '新增',
+        delete: '删除'
+      },
+      table: {
+        columns: {
+          name: '模板名称',
+          code: '模板编码',
+          updatedAt: '更新时间',
+          status: '状态',
+          actions: '操作'
+        },
+        actions: {
+          detail: '详情',
+          edit: '编辑',
+          delete: '删除',
+          viewBind: '查看及绑定设备'
+        }
+      },
+      switch: {
+        active: '启',
+        inactive: '禁'
+      },
+      messages: {
+        deleteConfirmSingle: '此操作将模板:“{name}”,是否继续?',
+        deleteConfirmMultiple: '此操作将{count}个模板',
+        tip: '提示',
+        warn: '警告',
+        confirm: '确认',
+        cancel: '取消',
+        deleteSuccess: '删除成功',
+        enableActionText: '启用',
+        disableActionText: '停用',
+        statusChangeConfirm: '确认要{action}模板:【{name}】吗?',
+        statusChangeSuccess: '{action}成功'
+      }
+    },
+    edit: {
+      form: {
+        name: '模板名称'
+      },
+      placeholders: {
+        name: '输入模板名称'
+      },
+      actions: {
+        cancel: '取消',
+        confirm: '确定'
+      },
+      rules: {
+        required: '不能为空'
+      },
+      messages: {
+        success: '操作成功'
+      }
+    },
+    editDialog: {
+      title: {
+        add: '新增模板',
+        edit: '编辑模板'
+      }
+    },
+    detail: {
+      dialog: {
+        title: '模板详情'
+      },
+      sections: {
+        templateInfo: '设备数据过滤器模板信息',
+        customAttr: '自定义属性'
+      },
+      descriptions: {
+        templateName: '模板名称',
+        templateCode: '模板编码',
+        updatedAt: '更新时间'
+      },
+      actions: {
+        addCustomAttr: '添加自定义属性'
+      },
+      table: {
+        columns: {
+          name: '属性名称',
+          key: '属性编码',
+          unit: '单位',
+          updatedAt: '更新时间',
+          isVisible: '是否可见',
+          actions: '操作'
+        },
+        actions: {
+          edit: '编辑',
+          delete: '删除'
+        }
+      },
+      switch: {
+        active: '启',
+        inactive: '禁'
+      },
+      messages: {
+        warn: '警告',
+        tip: '提示',
+        confirm: '确认',
+        cancel: '取消',
+        deleteSuccess: '删除成功',
+        deleteConfirmSingle: '此操作将属性:“{name}”,是否继续?',
+        deleteConfirmMultiple: '此操作将{count}个属性',
+        confirmSetVisible: '确认要设置可见属性:【{name}】吗?',
+        confirmSetInvisible: '确认要设置不可见属性:【{name}】吗?',
+        setVisibleSuccess: '设置可见成功',
+        setInvisibleSuccess: '设置不可见成功'
+      }
+    }
+    ,
+    bindDevice: {
+      dialog: {
+        title: '绑定设备'
+      },
+      sections: {
+        templateInfo: '设备数据过滤器模板信息',
+        bindDevice: '绑定设备'
+      },
+      descriptions: {
+        templateName: '模板名称',
+        templateCode: '模板编码',
+        updatedAt: '更新时间'
+      },
+      actions: {
+        bindDevice: '绑定设备'
+      },
+      table: {
+        columns: {
+          name: '设备名称',
+          key: '设备编码',
+          productName: '所属产品',
+          actions: '操作'
+        },
+        actions: {
+          unbind: '解绑'
+        }
+      },
+      messages: {
+        unbindSuccess: '解绑成功'
+      }
+    },
+    bindDeviceForm: {
+      dialog: {
+        title: '绑定设备'
+      },
+      table: {
+        columns: {
+          name: '设备名称',
+          key: '设备编码',
+          productName: '所属产品'
+        }
+      },
+      actions: {
+        cancel: '取消',
+        bind: '绑定设备'
+      },
+      messages: {
+        bindSuccess: '操作成功'
+      }
+    },
+    attrEdit: {
+      titleEdit: '编辑属性',
+      titleAdd: '新增属性',
+      labels: {
+        name: '属性名称',
+        key: '属性KEY',
+        unit: '单位'
+      },
+      placeholders: {
+        name: '输入属性名称',
+        key: '输入属性编码',
+        unit: '输入单位'
+      },
+      actions: {
+        cancel: '取消',
+        confirm: '确定'
+      },
+      rules: {
+        required: '不能为空'
+      },
+      messages: {
+        success: '操作成功'
+      }
+    }
+  }
+};

+ 553 - 0
src/i18n/pages/projects/zh-tw.ts

@@ -0,0 +1,553 @@
+export default {
+  list: {
+    search: {
+      channelPlaceholder: '渠道商搜尋',
+      statusPlaceholder: '請選擇轉發格式',
+      query: '查詢',
+      reset: '重置'
+    },
+    addOrEdit: {
+      form: {
+        name: '項目名稱',
+        addressCode: '地區',
+        addressDetail: '詳細地址',
+        channelMerchants: '渠道商',
+        customName: '關鍵客戶',
+        repairCompany: '維修公司',
+        repairMobile: '維修電話'
+      },
+      placeholders: {
+        select: '請選擇',
+        name: '輸入項目名稱',
+        addressDetail: '輸入詳細地址',
+        channelMerchants: '輸入渠道商',
+        customName: '輸入客戶名稱',
+        repairCompany: '輸入維修公司',
+        repairMobile: '輸入維修電話'
+      },
+      buttons: {
+        cancel: '取消',
+        confirm: '確定'
+      },
+      validation: {
+        name: '請輸入項目名稱',
+        addressCode: '請選擇地區',
+        addressDetail: '請輸入詳細地址',
+        channelMerchants: '請輸入渠道商',
+        customName: '請輸入客戶名稱',
+        repairCompany: '請輸入維修公司',
+        repairMobile: '請輸入維修電話'
+      },
+      messages: {
+        addSuccess: '新增成功',
+        editSuccess: '編輯成功'
+      }
+    },
+    status: {
+      all: '全部狀態',
+      enabled: '正常',
+      disabled: '禁用'
+    },
+    actions: {
+      add: '新增',
+      delete: '刪除',
+      enable: '啟用',
+      disable: '禁用'
+    },
+    table: {
+      columns: {
+        projectStatus: '項目狀態',
+        projectName: '項目名稱',
+        address: '省/市/區/縣',
+        addressDetail: '詳細地址',
+        channelMerchants: '渠道商',
+        customName: '關鍵客戶',
+        repairCompany: '維修公司',
+        updatedAt: '更新時間',
+        actions: '操作'
+      },
+      actions: {
+        detail: '詳情',
+        edit: '編輯',
+        delete: '刪除'
+      }
+    },
+    messages: {
+      operationSuccess: '操作成功',
+      deleteConfirmSingle: '此操作將項目:「{name}」,是否繼續?',
+      deleteConfirmMultiple: '此操作將{count}個項目',
+      tip: '提示',
+      confirm: '確認',
+      cancel: '取消',
+      deleteSuccess: '刪除成功'
+    }
+  },
+  editDialog: {
+    title: {
+      add: '新增項目',
+      edit: '編輯項目'
+    }
+  },
+  detail: {
+    tabs: {
+      overview: '項目概況',
+      devices: '網關與設備',
+      scene: '場景聯動',
+      topo: '組態應用',
+      video: '視頻監控'
+    },
+    device: {
+      actions: {
+        add: '添加設備',
+        cancel: '取消',
+        confirm: '確定'
+      },
+      table: {
+        columns: {
+          key: '標識',
+          name: '設備名稱',
+          deviceType: '設備類型',
+          productName: '所屬產品',
+          status: '狀態',
+          lastOnlineTime: '最後上線時間',
+          desc: '說明',
+          actions: '操作'
+        },
+        actions: {
+          detail: '設備詳情',
+          unbind: '解綁'
+        },
+        statusTags: {
+          offline: '離線',
+          online: '在線',
+          inactive: '未啟用'
+        }
+      },
+      dialog: {
+        title: '添加設備'
+      },
+      form: {
+        labels: {
+          product: '產品',
+          device: '設備'
+        },
+        placeholders: {
+          product: '選擇產品',
+          device: '選擇設備'
+        }
+      },
+      messages: {
+        selectDeviceFirst: '請先選擇設備',
+        addSuccess: '添加成功',
+        deleteConfirm: '確定要刪除?',
+        tip: '提示',
+        confirm: '確認',
+        cancel: '取消',
+        unbindSuccess: '解綁成功'
+      }
+    },
+    info: {
+      actions: {
+        edit: '編輯'
+      },
+      descriptions: {
+        name: '項目名稱',
+        customName: '關鍵客戶',
+        address: '地區',
+        channelMerchants: '渠道商',
+        repairCompany: '維修公司',
+        repairMobile: '維修電話',
+        addressDetail: '詳細地址'
+      }
+    },
+    scene: {
+      actions: {
+        add: '添加場景',
+        cancel: '取消',
+        confirm: '確定'
+      },
+      table: {
+        columns: {
+          id: 'ID',
+          name: '場景名稱',
+          sceneType: '觸發方式',
+          status: '狀態',
+          description: '場景描述',
+          createdAt: '創建時間',
+          actions: '操作'
+        },
+        actions: {
+          detail: '場景詳情',
+          unbind: '解綁'
+        },
+        statusTags: {
+          enabled: '啟用',
+          disabled: '禁用'
+        },
+        sceneTypeTags: {
+          device: '設備觸發',
+          manual: '手動觸發',
+          timer: '定時觸發'
+        }
+      },
+      dialog: {
+        title: '添加場景'
+      },
+      form: {
+        labels: {
+          scene: '場景'
+        },
+        placeholders: {
+          scene: '選擇場景'
+        }
+      },
+      messages: {
+        selectSceneFirst: '請先選擇場景',
+        addSuccess: '添加成功',
+        unbindConfirm: '確定要解綁該場景?',
+        tip: '提示',
+        confirm: '確認',
+        cancel: '取消',
+        unbindSuccess: '解綁成功'
+      }
+    },
+    topo: {
+      actions: {
+        add: '添加組態',
+        cancel: '取消',
+        confirm: '確定'
+      },
+      table: {
+        columns: {
+          id: 'ID',
+          name: '組態圖名稱',
+          createdAt: '創建時間',
+          updatedAt: '更新時間',
+          actions: '操作'
+        },
+        actions: {
+          preview: '預覽',
+          edit: '編輯組態圖',
+          unbind: '解綁'
+        }
+      },
+      dialog: {
+        title: '添加組態'
+      },
+      form: {
+        labels: {
+          topo: '組態'
+        },
+        placeholders: {
+          topo: '選擇組態'
+        }
+      },
+      messages: {
+        selectTopoFirst: '請先選擇組態',
+        addSuccess: '添加成功',
+        unbindConfirm: '確定要解綁該組態?',
+        tip: '提示',
+        confirm: '確認',
+        cancel: '取消',
+        unbindSuccess: '解綁成功'
+      }
+    },
+    video: {
+      actions: {
+        add: '添加監控',
+        cancel: '取消',
+        confirm: '確定',
+        openInNewWindow: '新窗口預覽'
+      },
+      table: {
+        columns: {
+          deviceName: '設備名稱',
+          deviceId: '設備ID',
+          channelName: '通道名稱',
+          model: '型號',
+          manufacturer: '廠商',
+          liveStatus: '狀態',
+          keepAliveTime: '最後心跳時間',
+          registerTime: '註冊時間',
+          updateAt: '更新時間',
+          actions: '操作'
+        },
+        actions: {
+          view: '查看監控',
+          unbind: '解綁'
+        }
+      },
+      dialog: {
+        addTitle: '添加監控',
+        previewTitle: '預覽監控'
+      },
+      form: {
+        labels: {
+          video: '監控'
+        },
+        placeholders: {
+          video: '選擇監控'
+        }
+      },
+      messages: {
+        selectVideoFirst: '請先選擇監控',
+        addSuccess: '添加成功',
+        unbindConfirm: '確定要解綁該監控?',
+        tip: '提示',
+        confirm: '確認',
+        cancel: '取消',
+        unbindSuccess: '解綁成功'
+      },
+      liveStatusTags: {
+        idle: '空閒',
+        inviting: '調用中',
+        streaming: '拉流中'
+      }
+    }
+  },
+  screen: {
+    video: {
+      selects: {
+        project: '選擇項目'
+      }
+    },
+    info: {
+      title: '項目信息',
+      selects: {
+        placeholder: '請選擇'
+      },
+      labels: {
+        projectName: '項目名稱:',
+        projectAddress: '項目地址:'
+      },
+      units: {
+        squareMeter: 'm²',
+        device: '台'
+      },
+      cards: {
+        coverageArea: '覆蓋面積',
+        deviceCount: '設備台數',
+        maintainPhone: '維保電話'
+      },
+      empty: {
+        dash: '-'
+      }
+    },
+    baseinfo: {
+      title: '基本信息',
+      selects: {
+        placeholder: '請選擇'
+      }
+    },
+    lineChart: {
+      title: '設備狀態',
+      date: {
+        rangeSeparator: '至',
+        startPlaceholder: '開始時間',
+        endPlaceholder: '結束時間'
+      },
+      selects: {
+        properties: '請選擇'
+      }
+    }
+  },
+  filter: {
+    index: {
+      search: {
+        namePlaceholder: '模板名稱搜尋',
+        deviceKeyPlaceholder: '設備 key 搜尋',
+        statusPlaceholder: '請選擇轉發格式',
+        query: '查詢',
+        reset: '重置'
+      },
+      status: {
+        all: '全部狀態',
+        enabled: '正常',
+        disabled: '禁用'
+      },
+      actions: {
+        add: '新增',
+        delete: '刪除'
+      },
+      table: {
+        columns: {
+          name: '模板名稱',
+          code: '模板編碼',
+          updatedAt: '更新時間',
+          status: '狀態',
+          actions: '操作'
+        },
+        actions: {
+          detail: '詳情',
+          edit: '編輯',
+          delete: '刪除',
+          viewBind: '查看及綁定設備'
+        }
+      },
+      switch: {
+        active: '啟',
+        inactive: '禁'
+      },
+      messages: {
+        deleteConfirmSingle: '此操作將刪除模板:「{name}」,是否繼續?',
+        deleteConfirmMultiple: '此操作將刪除 {count} 個模板',
+        tip: '提示',
+        warn: '警告',
+        confirm: '確認',
+        cancel: '取消',
+        deleteSuccess: '刪除成功',
+        enableActionText: '啟用',
+        disableActionText: '停用',
+        statusChangeConfirm: '確認要{action}模板:【{name}】嗎?',
+        statusChangeSuccess: '{action}成功'
+      }
+    },
+    edit: {
+      form: {
+        name: '模板名稱'
+      },
+      placeholders: {
+        name: '輸入模板名稱'
+      },
+      actions: {
+        cancel: '取消',
+        confirm: '確定'
+      },
+      rules: {
+        required: '不能為空'
+      },
+      messages: {
+        success: '操作成功'
+      }
+    },
+    editDialog: {
+      title: {
+        add: '新增模板',
+        edit: '編輯模板'
+      }
+    },
+    detail: {
+      dialog: {
+        title: '模板詳情'
+      },
+      sections: {
+        templateInfo: '設備數據過濾器模板資訊',
+        customAttr: '自定義屬性'
+      },
+      descriptions: {
+        templateName: '模板名稱',
+        templateCode: '模板編碼',
+        updatedAt: '更新時間'
+      },
+      actions: {
+        addCustomAttr: '添加自定義屬性'
+      },
+      table: {
+        columns: {
+          name: '屬性名稱',
+          key: '屬性編碼',
+          unit: '單位',
+          updatedAt: '更新時間',
+          isVisible: '是否可見',
+          actions: '操作'
+        },
+        actions: {
+          edit: '編輯',
+          delete: '刪除'
+        }
+      },
+      switch: {
+        active: '啟',
+        inactive: '禁'
+      },
+      messages: {
+        warn: '警告',
+        tip: '提示',
+        confirm: '確認',
+        cancel: '取消',
+        deleteSuccess: '刪除成功',
+        deleteConfirmSingle: '此操作將屬性:「{name}」,是否繼續?',
+        deleteConfirmMultiple: '此操作將刪除 {count} 個屬性',
+        confirmSetVisible: '確認要設置可見屬性:【{name}】嗎?',
+        confirmSetInvisible: '確認要設置不可見屬性:【{name}】嗎?',
+        setVisibleSuccess: '設置可見成功',
+        setInvisibleSuccess: '設置不可見成功'
+      }
+    }
+    ,
+    bindDevice: {
+      dialog: {
+        title: '綁定設備'
+      },
+      sections: {
+        templateInfo: '設備數據過濾器模板信息',
+        bindDevice: '綁定設備'
+      },
+      descriptions: {
+        templateName: '模板名稱',
+        templateCode: '模板編碼',
+        updatedAt: '更新時間'
+      },
+      actions: {
+        bindDevice: '綁定設備'
+      },
+      table: {
+        columns: {
+          name: '設備名稱',
+          key: '設備編碼',
+          productName: '所屬產品',
+          actions: '操作'
+        },
+        actions: {
+          unbind: '解綁'
+        }
+      },
+      messages: {
+        unbindSuccess: '解綁成功'
+      }
+    },
+    bindDeviceForm: {
+      dialog: {
+        title: '綁定設備'
+      },
+      table: {
+        columns: {
+          name: '設備名稱',
+          key: '設備編碼',
+          productName: '所屬產品'
+        }
+      },
+      actions: {
+        cancel: '取消',
+        bind: '綁定設備'
+      },
+      messages: {
+        bindSuccess: '操作成功'
+      }
+    },
+    attrEdit: {
+      titleEdit: '編輯屬性',
+      titleAdd: '新增屬性',
+      labels: {
+        name: '屬性名稱',
+        key: '屬性KEY',
+        unit: '單位'
+      },
+      placeholders: {
+        name: '輸入屬性名稱',
+        key: '輸入屬性編碼',
+        unit: '輸入單位'
+      },
+      actions: {
+        cancel: '取消',
+        confirm: '確定'
+      },
+      rules: {
+        required: '不能為空'
+      },
+      messages: {
+        success: '操作成功'
+      }
+    }
+  }
+};

+ 172 - 0
src/i18n/pages/property/en.ts

@@ -0,0 +1,172 @@
+export default {
+  attribute: {
+    dialog: {
+      editTitle: 'Edit Device Archive Attribute',
+      addTitle: 'Add Device Archive Attribute',
+    },
+    form: {
+      keyword: 'Keyword',
+      keywordPlaceholder: 'Please enter keyword',
+      productKey: 'Product',
+      productPlaceholder: 'Please select product',
+      fieldName: 'Field Name',
+      fieldNamePlaceholder: 'Please enter field name',
+      fieldTitle: 'Field Title',
+      fieldTitlePlaceholder: 'Please enter field title',
+      fieldType: 'Field Type',
+      typePlaceholder: 'Please select field type',
+      fieldDesc: 'Field Description',
+      fieldDescPlaceholder: 'Please enter field description',
+    },
+    actions: {
+      add: 'Add Attribute',
+      save: 'Save',
+    },
+    columns: {
+      fieldName: 'Field Name',
+      fieldTitle: 'Field Title',
+      fieldType: 'Field Type',
+      createdAt: 'Created At',
+    },
+    types: {
+      input: 'Input',
+      textarea: 'Textarea',
+      date: 'Date',
+      file: 'Upload Image',
+    },
+    valid: {
+      nameRequired: 'Field name is required',
+      titleRequired: 'Field title is required',
+      productRequired: 'Product is required',
+      typeRequired: 'Field type is required',
+    },
+    messages: {
+      tips: 'Tips',
+      confirm: 'Confirm',
+      cancel: 'Cancel',
+      deleteSuccess: 'Deleted successfully',
+      saveSuccess: 'Saved successfully',
+      batchDeleteConfirm: 'Are you sure to batch delete these data?',
+      deleteItemConfirm: 'Are you sure to delete the item with name: “{name}”?',
+    },
+  },
+  dossier: {
+    dialog: {
+      editTitle: 'Edit Device Archive',
+      addTitle: 'Add Device Archive',
+    },
+    form: {
+      keyword: 'Name',
+      keywordPlaceholder: 'Please enter name',
+      productKey: 'Select Product',
+      productPlaceholder: 'Please select product',
+      deviceKey: 'Select Device',
+      devicePlaceholder: 'Please select device',
+      deviceName: 'Device Name',
+      deviceNamePlaceholder: 'Please enter device name',
+      deviceNumber: 'Device Code',
+      deviceNumberPlaceholder: 'Please enter device code',
+      deptId: 'Department',
+      deptPlaceholder: 'Please select department',
+      deviceCategory: 'Device Type',
+      deviceCategoryPlaceholder: 'Please enter device type',
+      installTime: 'Install Time',
+      datePlaceholder: 'Please select date',
+      dividerCustom: 'Custom Attributes',
+      inputPlaceholder: 'Please enter {title}',
+    },
+    actions: {
+      save: 'Confirm',
+    },
+    columns: {
+      deviceName: 'Device Name',
+      deviceKey: 'Device KEY',
+      deviceNumber: 'Device Code',
+      deviceCategory: 'Device Type',
+      installTime: 'Install Time',
+    },
+    valid: {
+      productRequired: 'Product is required',
+      deviceNameRequired: 'Device name is required',
+      deviceRequired: 'Device is required',
+    },
+    messages: {
+      tips: 'Tips',
+      confirm: 'Confirm',
+      cancel: 'Cancel',
+      deleteSuccess: 'Deleted successfully',
+      saveSuccess: 'Operation successful',
+      batchDeleteConfirm: 'Are you sure to batch delete these data?',
+      deleteItemConfirm: 'Are you sure to delete the item with name: “{name}”?',
+    },
+  },
+  relationship: {
+    dialog: {
+      editTitle: 'Edit Asset Relationship',
+      addTitle: 'Add Asset Relationship',
+      filterTitle: 'Set Filter Conditions',
+    },
+    form: {
+      keyword: 'Name',
+      keywordPlaceholder: 'Please enter name',
+      chooseRole: 'Role',
+      chooseRolePlaceholder: 'Please select role',
+      selectedDevices: 'Selected Devices',
+    },
+    valid: {
+      roleRequired: 'Role is required',
+      deviceRequired: 'Device is required',
+      fieldRequiredTitle: '{title} is required',
+    },
+    actions: {
+      add: 'Add Asset Relationship',
+      unbind: 'Unbind',
+    },
+    columns: {
+      deviceName: 'Device Name',
+      deviceKey: 'Device KEY',
+      deviceNumber: 'Device Code',
+      deviceCategory: 'Device Type',
+      installTime: 'Install Time',
+      status: 'Status',
+    },
+    switch: {
+      enableShort: 'On',
+      disableShort: 'Off',
+    },
+    status: {
+      enable: 'Enable',
+      disable: 'Disable',
+    },
+    messages: {
+      tips: 'Tips',
+      warning: 'Warning',
+      confirm: 'Confirm',
+      cancel: 'Cancel',
+      statusChangeConfirm: 'Are you sure to {text}?',
+      statusChangeSuccess: '{text} successfully',
+      saveSuccess: 'Operation successful',
+      unbindConfirm: 'Are you sure to unbind?',
+      unbindSuccess: 'Unbound successfully',
+      batchUnbindConfirm: 'Are you sure to batch unbind these data?',
+    },
+  },
+  deviceMap: {
+    form: {
+      selectProductPlaceholder: 'Select Product',
+    },
+    messages: {
+      noCoordsWarning: 'There are no devices with coordinates for this product!',
+    },
+    popup: {
+      todayAlarmTotal: 'Total alarms today',
+      todayUnhandled: 'Unhandled alarms today',
+      todayHandled: 'Handled alarms today',
+      codeLabel: 'Code: ',
+      lastOnlineTimeLabel: 'Last online time: ',
+      addressLabel: 'Address: ',
+      deviceTypeGateway: 'Gateway',
+      deviceTypeDevice: 'Device',
+    },
+  },
+};

+ 172 - 0
src/i18n/pages/property/zh-cn.ts

@@ -0,0 +1,172 @@
+export default {
+  attribute: {
+    dialog: {
+      editTitle: '编辑设备档案属性',
+      addTitle: '新增设备档案属性',
+    },
+    form: {
+      keyword: '关键词',
+      keywordPlaceholder: '请输入关键词',
+      productKey: '所属产品',
+      productPlaceholder: '请选择产品',
+      fieldName: '字段名称',
+      fieldNamePlaceholder: '请输入字段名称',
+      fieldTitle: '字段标题',
+      fieldTitlePlaceholder: '请输入字段标题',
+      fieldType: '字段类型',
+      typePlaceholder: '请选择字段类型',
+      fieldDesc: '字段描述',
+      fieldDescPlaceholder: '请输入字段描述',
+    },
+    actions: {
+      add: '新增属性',
+      save: '保存',
+    },
+    columns: {
+      fieldName: '字段名',
+      fieldTitle: '字段标题',
+      fieldType: '字段类型',
+      createdAt: '创建时间',
+    },
+    types: {
+      input: '输入框',
+      textarea: '文本框',
+      date: '日期',
+      file: '上传图片',
+    },
+    valid: {
+      nameRequired: '字段名称不能为空',
+      titleRequired: '字段标题不能为空',
+      productRequired: '所属产品不能为空',
+      typeRequired: '字段类型不能为空',
+    },
+    messages: {
+      tips: '提示',
+      confirm: '确认',
+      cancel: '取消',
+      deleteSuccess: '删除成功',
+      saveSuccess: '保存成功',
+      batchDeleteConfirm: '是否确认要批量删除这些数据吗?',
+      deleteItemConfirm: '是否确认删除名称为:“{name}”的数据项?',
+    },
+  },
+  dossier: {
+    dialog: {
+      editTitle: '编辑设备档案',
+      addTitle: '新增设备档案',
+    },
+    form: {
+      keyword: '名称',
+      keywordPlaceholder: '请输入名称',
+      productKey: '选择产品',
+      productPlaceholder: '请选择产品',
+      deviceKey: '选择设备',
+      devicePlaceholder: '请选择设备',
+      deviceName: '设备名称',
+      deviceNamePlaceholder: '请输入设备名称',
+      deviceNumber: '设备编码',
+      deviceNumberPlaceholder: '请输入设备编码',
+      deptId: '所属部门',
+      deptPlaceholder: '请选择所属部门',
+      deviceCategory: '设备类型',
+      deviceCategoryPlaceholder: '请输入设备类型',
+      installTime: '安装时间',
+      datePlaceholder: '请选择时间',
+      dividerCustom: '自定义属性',
+      inputPlaceholder: '请输入{title}',
+    },
+    actions: {
+      save: '确定',
+    },
+    columns: {
+      deviceName: '设备名称',
+      deviceKey: '设备KEY',
+      deviceNumber: '设备编码',
+      deviceCategory: '设备类型',
+      installTime: '安装时间',
+    },
+    valid: {
+      productRequired: '所属产品不能为空',
+      deviceNameRequired: '设备名称不能为空',
+      deviceRequired: '设备不能为空',
+    },
+    messages: {
+      tips: '提示',
+      confirm: '确认',
+      cancel: '取消',
+      deleteSuccess: '删除成功',
+      saveSuccess: '操作成功',
+      batchDeleteConfirm: '是否确认要批量删除这些数据吗?',
+      deleteItemConfirm: '是否确认删除名称为:“{name}”的数据项?',
+    },
+  },
+  relationship: {
+    dialog: {
+      editTitle: '编辑资产关系',
+      addTitle: '新增资产关系',
+      filterTitle: '设置筛选条件',
+    },
+    form: {
+      keyword: '名称',
+      keywordPlaceholder: '请输入名称',
+      chooseRole: '选择角色',
+      chooseRolePlaceholder: '请选择角色',
+      selectedDevices: '已选设备',
+    },
+    valid: {
+      roleRequired: '角色不能为空',
+      deviceRequired: '设备不能为空',
+      fieldRequiredTitle: '{title}不能为空',
+    },
+    actions: {
+      add: '新增资产关系',
+      unbind: '解绑',
+    },
+    columns: {
+      deviceName: '设备名称',
+      deviceKey: '设备KEY',
+      deviceNumber: '设备编码',
+      deviceCategory: '设备类型',
+      installTime: '安装时间',
+      status: '状态',
+    },
+    switch: {
+      enableShort: '启',
+      disableShort: '禁',
+    },
+    status: {
+      enable: '启用',
+      disable: '停用',
+    },
+    messages: {
+      tips: '提示',
+      warning: '警告',
+      confirm: '确定',
+      cancel: '取消',
+      statusChangeConfirm: '确认要【{text}】吗?',
+      statusChangeSuccess: '{text}成功',
+      saveSuccess: '操作成功',
+      unbindConfirm: '是否确认解绑',
+      unbindSuccess: '解绑成功',
+      batchUnbindConfirm: '是否确认要批量解绑这些数据吗?',
+    },
+  },
+  deviceMap: {
+    form: {
+      selectProductPlaceholder: '选择产品',
+    },
+    messages: {
+      noCoordsWarning: '该产品不存在有坐标的设备!',
+    },
+    popup: {
+      todayAlarmTotal: '今日报警总数',
+      todayUnhandled: '今日未处理报警',
+      todayHandled: '今日已处理报警',
+      codeLabel: '编号:',
+      lastOnlineTimeLabel: '最后在线时间:',
+      addressLabel: '地址:',
+      deviceTypeGateway: '网关',
+      deviceTypeDevice: '设备',
+    },
+  },
+};

+ 172 - 0
src/i18n/pages/property/zh-tw.ts

@@ -0,0 +1,172 @@
+export default {
+  attribute: {
+    dialog: {
+      editTitle: '編輯設備檔案屬性',
+      addTitle: '新增設備檔案屬性',
+    },
+    form: {
+      keyword: '關鍵詞',
+      keywordPlaceholder: '請輸入關鍵詞',
+      productKey: '所屬產品',
+      productPlaceholder: '請選擇產品',
+      fieldName: '欄位名稱',
+      fieldNamePlaceholder: '請輸入欄位名稱',
+      fieldTitle: '欄位標題',
+      fieldTitlePlaceholder: '請輸入欄位標題',
+      fieldType: '欄位類型',
+      typePlaceholder: '請選擇欄位類型',
+      fieldDesc: '欄位描述',
+      fieldDescPlaceholder: '請輸入欄位描述',
+    },
+    actions: {
+      add: '新增屬性',
+      save: '保存',
+    },
+    columns: {
+      fieldName: '欄位名',
+      fieldTitle: '欄位標題',
+      fieldType: '欄位類型',
+      createdAt: '建立時間',
+    },
+    types: {
+      input: '輸入框',
+      textarea: '文字框',
+      date: '日期',
+      file: '上傳圖片',
+    },
+    valid: {
+      nameRequired: '欄位名稱不能為空',
+      titleRequired: '欄位標題不能為空',
+      productRequired: '所屬產品不能為空',
+      typeRequired: '欄位類型不能為空',
+    },
+    messages: {
+      tips: '提示',
+      confirm: '確認',
+      cancel: '取消',
+      deleteSuccess: '刪除成功',
+      saveSuccess: '保存成功',
+      batchDeleteConfirm: '是否確認要批量刪除這些資料?',
+      deleteItemConfirm: '是否確認刪除名稱為:「{name}」的資料項?',
+    },
+  },
+  dossier: {
+    dialog: {
+      editTitle: '編輯設備檔案',
+      addTitle: '新增設備檔案',
+    },
+    form: {
+      keyword: '名稱',
+      keywordPlaceholder: '請輸入名稱',
+      productKey: '選擇產品',
+      productPlaceholder: '請選擇產品',
+      deviceKey: '選擇設備',
+      devicePlaceholder: '請選擇設備',
+      deviceName: '設備名稱',
+      deviceNamePlaceholder: '請輸入設備名稱',
+      deviceNumber: '設備編碼',
+      deviceNumberPlaceholder: '請輸入設備編碼',
+      deptId: '所屬部門',
+      deptPlaceholder: '請選擇所屬部門',
+      deviceCategory: '設備類型',
+      deviceCategoryPlaceholder: '請輸入設備類型',
+      installTime: '安裝時間',
+      datePlaceholder: '請選擇時間',
+      dividerCustom: '自定義屬性',
+      inputPlaceholder: '請輸入{title}',
+    },
+    actions: {
+      save: '確定',
+    },
+    columns: {
+      deviceName: '設備名稱',
+      deviceKey: '設備KEY',
+      deviceNumber: '設備編碼',
+      deviceCategory: '設備類型',
+      installTime: '安裝時間',
+    },
+    valid: {
+      productRequired: '所屬產品不能為空',
+      deviceNameRequired: '設備名稱不能為空',
+      deviceRequired: '設備不能為空',
+    },
+    messages: {
+      tips: '提示',
+      confirm: '確認',
+      cancel: '取消',
+      deleteSuccess: '刪除成功',
+      saveSuccess: '操作成功',
+      batchDeleteConfirm: '是否確認要批量刪除這些資料?',
+      deleteItemConfirm: '是否確認刪除名稱為:「{name}」的資料項?',
+    },
+  },
+  relationship: {
+    dialog: {
+      editTitle: '編輯資產關係',
+      addTitle: '新增資產關係',
+      filterTitle: '設置篩選條件',
+    },
+    form: {
+      keyword: '名稱',
+      keywordPlaceholder: '請輸入名稱',
+      chooseRole: '選擇角色',
+      chooseRolePlaceholder: '請選擇角色',
+      selectedDevices: '已選設備',
+    },
+    valid: {
+      roleRequired: '角色不能為空',
+      deviceRequired: '設備不能為空',
+      fieldRequiredTitle: '{title}不能為空',
+    },
+    actions: {
+      add: '新增資產關係',
+      unbind: '解绑',
+    },
+    columns: {
+      deviceName: '設備名稱',
+      deviceKey: '設備KEY',
+      deviceNumber: '設備編碼',
+      deviceCategory: '設備類型',
+      installTime: '安裝時間',
+      status: '狀態',
+    },
+    switch: {
+      enableShort: '啟',
+      disableShort: '禁',
+    },
+    status: {
+      enable: '啟用',
+      disable: '停用',
+    },
+    messages: {
+      tips: '提示',
+      warning: '警告',
+      confirm: '確定',
+      cancel: '取消',
+      statusChangeConfirm: '確認要【{text}】嗎?',
+      statusChangeSuccess: '{text}成功',
+      saveSuccess: '操作成功',
+      unbindConfirm: '是否確認解绑',
+      unbindSuccess: '解绑成功',
+      batchUnbindConfirm: '是否確認要批量解绑這些資料嗎?',
+    },
+  },
+  deviceMap: {
+    form: {
+      selectProductPlaceholder: '選擇產品',
+    },
+    messages: {
+      noCoordsWarning: '該產品不存在有座標的設備!',
+    },
+    popup: {
+      todayAlarmTotal: '今日報警總數',
+      todayUnhandled: '今日未處理報警',
+      todayHandled: '今日已處理報警',
+      codeLabel: '編號:',
+      lastOnlineTimeLabel: '最後上線時間:',
+      addressLabel: '地址:',
+      deviceTypeGateway: '網關',
+      deviceTypeDevice: '設備',
+    },
+  },
+};

+ 13 - 8
src/i18n/pages/tableI18n/en.ts

@@ -2,7 +2,7 @@
  * @Author: vera_min vera_min@163.com
  * @Date: 2025-08-06 01:16:57
  * @LastEditors: vera_min vera_min@163.com
- * @LastEditTime: 2025-08-06 14:37:43
+ * @LastEditTime: 2025-08-09 13:10:35
  * @FilePath: /sagoo-admin-ui/src/i18n/pages/tableI18n/en.ts
  * @Description: 表格国际化 - 英文
  */
@@ -10,6 +10,7 @@
 export default {
 	tableI18nColumn: {
 		id: "ID",
+		index: "Index",
 		operation: "Operation",
 		des: "Description",
 	},
@@ -17,18 +18,22 @@ export default {
 		deleteTitle: "Confirm",
 		confirmText: "Delete",
 		cancelText: "Cancel",
-		deleteSuccess: "Delete Success",
-		addSuccess: "Create Success",
-		addFailed: "Create Failed",
-		editSuccess: "Edit Success",
+		deleteSuccess: "Deleted successfully",
+		addSuccess: "Created successfully",
+		addFailed: "Failed to create",
+		editSuccess: "Updated successfully",
 		deleteSelectedMessage: "Are you sure you want to delete the selected data?",
-		selectDataFirst: "Please select the data to delete first."
+		selectDataFirst: "Please select the data to delete first.",
+		saveSuccess: "Saved successfully"
 	},
 	tableI18nAction: {
 		detail: "Detail",
 		handle: "Handle",
 		add: "Add",
 		edit: "Edit",
-		delete: "Delete"
+		delete: "Delete",
+		cancel: "Cancel",
+		query: "Query",
+		more: "More"
 	}
-};
+};

+ 8 - 3
src/i18n/pages/tableI18n/zh-cn.ts

@@ -10,6 +10,7 @@
 export default {
 	tableI18nColumn: {
 		id: "ID",
+		index: "序号",
 		operation: "操作",
 		des: "描述",
 	},
@@ -22,13 +23,17 @@ export default {
 		addFailed: "新增失败",
 		editSuccess: "编辑成功",
 		deleteSelectedMessage: "你确定要删除所选数据?",
-		selectDataFirst: "请选择要删除的数据。"
+		selectDataFirst: "请选择要删除的数据。",
+		saveSuccess: "保存成功"
 	},
 	tableI18nAction: {
 		detail: "详情",
 		handle: "处理",
 		add: "新增",
 		edit: "编辑",
-		delete: "删除"
+		delete: "删除",
+		cancel: "取 消",
+		query: "查询",
+		more: "更多"
 	}
-};
+};

+ 7 - 2
src/i18n/pages/tableI18n/zh-tw.ts

@@ -10,6 +10,7 @@
 export default {
 	tableI18nColumn: {
 		id: "ID",
+		index: "序號",
 		operation: "操作",
 		des: "描述",
 	},
@@ -23,12 +24,16 @@ export default {
 		editSuccess: "編輯成功",
 		deleteSelectedMessage: "你確定要刪除所選數據?",
 		selectDataFirst: "請選擇要刪除的數據。",
+		saveSuccess: "保存成功"
 	},
 	tableI18nAction: {
 		detail: "詳情",
 		handle: "處理",
 		add: "新增",
 		edit: "編輯",
-		delete: "刪除"
+		delete: "刪除",
+		cancel: "取 消",
+		query: "查詢",
+		more: "更多"
 	}
-};
+};

+ 0 - 1
src/layout/navMenu/vertical.vue

@@ -62,7 +62,6 @@ export default defineComponent({
 		});
 		// 获取父级菜单数据
 		const menuLists = computed(() => {
-			console.log(props.menuList)
 			return <any>props.menuList;
 		});
 		// 获取布局配置信息

+ 1 - 1
src/theme/element.scss

@@ -215,7 +215,7 @@
 }
 .el-dialog__body {
 	max-height: calc(90vh - 111px) !important;
-	min-height: 50vh !important;
+	min-height: 200px !important;
 	overflow-y: auto;
 	overflow-x: hidden;
 }

+ 8 - 8
src/utils/auth.ts

@@ -16,23 +16,23 @@ export function removeToken() {
 
 export function setSystemInfo(data: any) {
   localStorage.setItem('sysinfo', JSON.stringify(data));
-  // 使用的事base64加密的,解决之后的值  sysPasswordChangePeriod + "|" + isSecurityControlEnabled + "|" + isRsaEnabled+ "|" + _tag+ "|" + isSSOEnabled
+  // 使用的事base64加密的,解决之后的值  
   // 顺序是,密码变更周期,是否启动安全控制,是否启用rsa,中间你需要根据 | 切割一下
   // console.log(window.atob(data.target).split('|'))
-  const [sysPasswordChangePeriod, isSecurityControlEnabled, isRsaEnabled, _tag, isSSOEnabled, isEnterprise, btnEnabled, colEnabled, uploadFileWay] = window.atob(data.target).split('|')
+  const { passwordChangePeriod, isSecurityControlEnabled, isRsaEnabled, isSsoEnabled, isEnterprise, buttonSwitch, columnSwitch, uploadFileWay } = JSON.parse(window.atob(data.target))
   // isEnterprise 是否为企业版,0代表专业版  1代表企业版
   // 安全开关是否开启 按钮权限,列表权限,rsa权限在开启安全权限下才使用
-  console.log('安全开关是否开启', isSecurityControlEnabled)
-  console.log('密码是否加密', isRsaEnabled)
+  // console.log('安全开关是否开启', isSecurityControlEnabled)
+  // console.log('密码是否加密', isRsaEnabled)
   const enabled = Number(isSecurityControlEnabled)
   sessionStorage.setItem('isSecurityControlEnabled', enabled ? '1' : '');
   sessionStorage.setItem('isRsaEnabled', (enabled && Number(isRsaEnabled)) ? '1' : '');
-  sessionStorage.setItem('sysPasswordChangePeriod', sysPasswordChangePeriod);
-  sessionStorage.setItem('isSSOEnabled', Number(isSSOEnabled) ? '1' : '');
+  sessionStorage.setItem('sysPasswordChangePeriod', passwordChangePeriod);
+  sessionStorage.setItem('isSSOEnabled', Number(isSsoEnabled) ? '1' : '');
   sessionStorage.setItem('isEnterprise', Number(isEnterprise) ? '1' : '');
 
-  localStorage.setItem('btnNoAuth', (enabled && Number(btnEnabled)) ? '' : '1');
-  localStorage.setItem('colNoAuth', (enabled && Number(colEnabled)) ? '' : '1');
+  localStorage.setItem('btnNoAuth', (enabled && Number(buttonSwitch)) ? '' : '1');
+  localStorage.setItem('colNoAuth', (enabled && Number(columnSwitch)) ? '' : '1');
   localStorage.setItem('uploadFileWay', uploadFileWay || '0');
 }
 

+ 4 - 4
src/utils/authDirective.ts

@@ -18,7 +18,7 @@ export function authDirective(app: App) {
 			if (buttons.includes(allPermissions)) return
 
 			// 不显示该dom
-			if (!buttons.includes(binding.value)) el.parentNode.removeChild(el)
+			if (!buttons.includes(binding.value)) el.parentNode?.removeChild(el)
 			// 设置为disabled
 			// if (!buttons.includes(binding.value)) el.classList.add('v-disabled')
 		},
@@ -26,7 +26,7 @@ export function authDirective(app: App) {
 	app.directive('noauth', {
 		mounted(el, binding) {
 			const buttons = <string[]>router.currentRoute.value.meta.buttons
-			if (buttons.includes(binding.value)) el.parentNode.removeChild(el)
+			if (buttons.includes(binding.value)) el.parentNode?.removeChild(el)
 		},
 	});
 	// 多个权限验证,满足一个则显示(v-auths="[xxx,xxx]")
@@ -41,7 +41,7 @@ export function authDirective(app: App) {
 					if (val === v) flag = true;
 				});
 			});
-			if (!flag) el.parentNode.removeChild(el);
+			if (!flag) el.parentNode?.removeChild(el);
 			// if (!flag) el.classList.add('v-disabled')
 		},
 	});
@@ -51,7 +51,7 @@ export function authDirective(app: App) {
 			if (localStorage.btnNoAuth) return
 			const buttons = <string[]>router.currentRoute.value.meta.buttons
 			if (buttons.includes(allPermissions)) return
-			!smallInBig(buttons, binding.value) && el.parentNode.removeChild(el)
+			!smallInBig(buttons, binding.value) && el.parentNode?.removeChild(el)
 			// !smallInBig(buttons, binding.value) && el.classList.add('v-disabled')
 		},
 	});

+ 3 - 3
src/utils/colDirective.ts

@@ -16,7 +16,7 @@ export function colDirective(app: App) {
 			if (localStorage.colNoAuth) return
 			const columns = <string[]>router.currentRoute.value.meta.columns
 			if (columns.includes(allPermissions)) return
-			if (!columns.includes(binding.value)) el.parentNode.removeChild(el)
+			if (!columns.includes(binding.value)) el.parentNode?.removeChild(el)
 		},
 	});
 	// 多个权限验证,满足一个则显示(v-cols="[xxx,xxx]")
@@ -31,7 +31,7 @@ export function colDirective(app: App) {
 					if (val === v) flag = true;
 				});
 			});
-			if (!flag) el.parentNode.removeChild(el);
+			if (!flag) el.parentNode?.removeChild(el);
 		},
 	});
 	// 多个权限验证,全部满足则显示(v-col-all="[xxx,xxx]")
@@ -40,7 +40,7 @@ export function colDirective(app: App) {
 			if (localStorage.colNoAuth) return
 			const columns = <string[]>router.currentRoute.value.meta.columns
 			if (columns.includes(allPermissions)) return
-			!smallInBig(columns, binding.value) && el.parentNode.removeChild(el)
+			!smallInBig(columns, binding.value) && el.parentNode?.removeChild(el)
 		},
 	});
 }

+ 7 - 3
src/utils/origin.ts

@@ -28,9 +28,13 @@ export default function getOrigin(urlStr: string = '', type: string = 'http') {
 
 // 如果 modbus服务  ice104协议网关服务 指数管理服务
 export function getOtherServersOrigin(urlStr: string = '') {
-  const origin = import.meta.env.VITE_SERVER_ORIGIN || window.location.origin
-  const nginxProxy = import.meta.env.VITE_NGINX_PROXY
-  return origin + nginxProxy + urlStr
+  if (urlStr?.startsWith(':')) {
+    return window.location.protocol + '//' + window.location.hostname + urlStr
+  } else {
+    const nginxProxy = import.meta.env.VITE_NGINX_PROXY
+    const origin = getOnlyPartOrigin(import.meta.env.VITE_SERVER_ORIGIN)
+    return origin + nginxProxy + urlStr
+  }
 }
 
 // 规则引擎

+ 1 - 1
src/views/apihub/component/edit.vue

@@ -14,7 +14,7 @@
         <span class="dialog-title">{{ formData.id ? '编辑API' : '新增API' }}</span>
         <div class="dialog-tools">
           <el-button
-            type="text"
+            text type="primary"
             @click="toggleFullscreen"
           >
             <el-icon :size="20">

+ 23 - 12
src/views/iot/cascade/deviceList.vue

@@ -1,24 +1,35 @@
 <template>
-	<el-dialog v-model="visible" :title="name + '-设备列表'" width="1000px" destroy-on-close>
+	<!-- name + '-设备列表' -->
+	<el-dialog v-model="visible" :title="name + $t('message.cascade.dialogI18n.deviceList')" width="1000px" destroy-on-close>
 		<div class="device-list">
 			<el-table :data="tableData" style="width: 100%" v-loading="loading">
-				<el-table-column label="标识" prop="key" min-width="150" show-overflow-tooltip>
+				<!-- 标识 -->
+				<el-table-column :label="$t('message.cascade.tableI18nColumn.identifier')" prop="key" min-width="150" show-overflow-tooltip>
 					<template #default="{ row }">
 						<copy :text="row.key"></copy>
 					</template>
 				</el-table-column>
-				<el-table-column label="设备名称" prop="name" min-width="160" show-overflow-tooltip />
-				<el-table-column label="设备类型" prop="product.deviceType" min-width="100" align="center" show-overflow-tooltip />
-				<el-table-column label="所属产品" prop="productName" min-width="120" align="center" show-overflow-tooltip />
-				<el-table-column prop="status" label="状态" min-width="80" align="center">
+				<!-- 设备名称 -->
+				<el-table-column :label="$t('message.cascade.tableI18nColumn.deviceName')" prop="name" min-width="160" show-overflow-tooltip />
+				<!-- 设备类型 -->
+				<el-table-column :label="$t('message.cascade.tableI18nColumn.deviceType')" prop="product.deviceType" min-width="100" align="center" show-overflow-tooltip />
+				<!-- 所属产品 -->
+				<el-table-column :label="$t('message.cascade.tableI18nColumn.belongProduct')" prop="productName" min-width="120" align="center" show-overflow-tooltip />
+				<!-- 状态 -->
+				<el-table-column prop="status" :label="$t('message.cascade.tableI18nColumn.status')" min-width="80" align="center">
 					<template #default="scope">
-						<el-tag type="danger" size="small" v-if="scope.row.status == 1">离线</el-tag>
-						<el-tag type="success" size="small" v-if="scope.row.status == 2">在线</el-tag>
-						<el-tag type="info" size="small" v-if="scope.row.status == 0">未启用</el-tag>
+						<!-- 离线 -->
+						<el-tag type="danger" size="small" v-if="scope.row.status == 1">{{$t('message.cascade.tableI18nStatus.offline')}}</el-tag>
+						<!-- 在线 -->
+						<el-tag type="success" size="small" v-if="scope.row.status == 2">{{$t('message.cascade.tableI18nStatus.online')}}</el-tag>
+						<!-- 未启用 -->
+						<el-tag type="info" size="small" v-if="scope.row.status == 0">{{$t('message.cascade.tableI18nStatus.disabled')}}</el-tag>
 					</template>
 				</el-table-column>
-				<el-table-column prop="lastOnlineTime" label="最后上线时间" align="center" width="160"></el-table-column>
-				<el-table-column label="操作" width="100" align="center">
+				<!-- 最后上线时间 -->
+				<el-table-column prop="lastOnlineTime" :label="$t('message.cascade.tableI18nColumn.lastOnlineTime')" align="center" width="160"></el-table-column>
+				<!-- 操作 -->
+				<el-table-column :label="$t('message.tableI18nColumn.operation')" width="100" align="center">
 					<template #default="scope">
 						<router-link
 							:to="'/iotmanager/device/instance/' + scope.row.key"
@@ -26,7 +37,7 @@
 							style="font-size: 12px; color: #409eff"
 							v-auth="'detail'"
 						>
-							<span>详情</span>
+							<span>{{ $t('message.tableI18nAction.detail') }}</span>
 						</router-link>
 					</template>
 				</el-table-column>

+ 26 - 14
src/views/iot/cascade/index.vue

@@ -2,41 +2,47 @@
   <div class="page-full">
     <div class="search flex-row mb-4 gap-4">
       <el-card shadow="never" class="home-card-top-part flex1">
+        <!-- 子平台 -->
         <div class="top">
-          <div class="label">子平台</div>
+          <div class="label">{{ $t("message.cascade.formI18nLabel.subPlatformTotal") }}</div>
           <span class="font30">{{ count.PlatformTotal }}</span>
         </div>
         <div class="divider"></div>
         <div class="card-bottom">
+          <!-- 在线 -->
           <div class="flex" style="gap: 10px">
             <img src="/@/assets/ok.svg" alt="" class="icon" />
-            <span class="info" :style="{ color: '#3cd357' }">在线</span>
+            <span class="info" :style="{ color: '#3cd357' }">{{ $t("message.cascade.tableI18nStatus.online") }}</span>
             <div class="num">{{ count.PlatformOnline }}</div>
           </div>
           <div class="split"></div>
+          <!-- 离线 -->
           <div class="flex" style="gap: 10px">
             <img src="/@/assets/stop.svg" alt="" class="icon" />
-            <span class="info" :style="{ color: '#FFBB73' }">离线</span>
+            <span class="info" :style="{ color: '#FFBB73' }">{{ $t("message.cascade.tableI18nStatus.offline") }}</span>
             <div class="num">{{ count.PlatformOffline }}</div>
           </div>
         </div>
       </el-card>
       <el-card shadow="never" class="home-card-top-part flex1">
+        <!-- 设备总数 -->
         <div class="top">
-          <div class="label">设备总数</div>
+          <div class="label">{{ $t("message.cascade.formI18nLabel.deviceTotal") }}</div>
           <span class="font30">{{ count.DeviceTotal }}</span>
         </div>
         <div class="divider"></div>
         <div class="card-bottom">
+          <!-- 在线 -->
           <div class="flex" style="gap: 10px">
             <img src="/@/assets/ok.svg" alt="" class="icon" />
-            <span class="info" :style="{ color: '#3cd357' }">在线</span>
+            <span class="info" :style="{ color: '#3cd357' }">{{ $t("message.cascade.tableI18nStatus.online") }}</span>
             <div class="num">{{ count.DeviceOnline }}</div>
           </div>
           <div class="split"></div>
+          <!-- 离线 -->
           <div class="flex" style="gap: 10px">
             <img src="/@/assets/stop.svg" alt="" class="icon" />
-            <span class="info" :style="{ color: '#FFBB73' }">离线</span>
+            <span class="info" :style="{ color: '#FFBB73' }">{{ $t("message.cascade.tableI18nStatus.offline") }}</span>
             <div class="num">{{ count.DeviceOffline }}</div>
           </div>
         </div>
@@ -44,18 +50,24 @@
     </div>
     <el-card shadow="never" class="page-full-part">
       <el-table :data="tableData" style="width: 100%" row-key="id" v-loading="loading">
-        <el-table-column type="index" label="序号" width="100" align="center"></el-table-column>
-        <el-table-column prop="name" label="子平台" show-overflow-tooltip v-col="'name'" align="center"></el-table-column>
-        <el-table-column prop="address" label="地址" v-col="'address'" align="center"></el-table-column>
-        <el-table-column prop="deviceOnline" label="设备" v-col="'deviceOnline'" align="center">
+        <!-- 序号 -->
+        <el-table-column type="index" :label="$t('message.tableI18nColumn.index')" width="100" align="center"></el-table-column>
+        <!-- 子平台 -->
+        <el-table-column prop="name" :label="$t('message.cascade.tableI18nColumn.subPlatform')" show-overflow-tooltip v-col="'name'" align="center"></el-table-column>
+        <!-- 地址 -->
+        <el-table-column prop="address" :label="$t('message.cascade.tableI18nColumn.address')" v-col="'address'" align="center"></el-table-column>
+        <!-- 设备 -->
+        <el-table-column prop="deviceOnline" :label="$t('message.cascade.tableI18nColumn.device')" v-col="'deviceOnline'" align="center">
           <template #default="{ row }">{{ row.deviceOnline }}/{{ row.deviceOnline + row.deviceOffline }} </template>
         </el-table-column>
-        <el-table-column prop="status" label="状态" v-col="'status'" :formatter="(_row: any, a: any) => (a ? '在线' : '离线')" align="center"></el-table-column>
-        <el-table-column prop="lastTime" label="最后更新时间" width="180" align="center" v-col="'lastTime'"></el-table-column>
-        <el-table-column label="操作" width="160" v-col="'handle'" align="center">
+        <el-table-column prop="status" :label="$t('message.cascade.tableI18nColumn.status')" v-col="'status'" :formatter="(_row: any, a: any) => (a ? '在线' : '离线')" align="center"></el-table-column>
+        <!-- 最后更新时间 -->
+        <el-table-column prop="lastTime" :label="$t('message.cascade.tableI18nColumn.lastUpdateTime')" width="180" align="center" v-col="'lastTime'"></el-table-column>
+        <!-- 操作 -->
+        <el-table-column :label="$t('message.tableI18nColumn.operation')" width="160" v-col="'handle'" align="center">
           <template #default="{ row }">
             <!-- <el-button size="small" text type="warning" v-auth="'sync'" :loading="row.loading" @click="sync(row)">同步</el-button> -->
-            <el-button size="small" text type="primary" v-auth="'detail'" @click="viewDeviceList(row)">查看设备</el-button>
+            <el-button v-auth="'detail'" size="small" text type="primary"  @click="viewDeviceList(row)">{{ $t('message.cascade.tableI18nAction.viewDevice') }}</el-button>
           </template>
         </el-table-column>
       </el-table>

+ 6 - 6
src/views/iot/device/category/component/addOrEdit.vue

@@ -5,7 +5,7 @@
 				<el-row :gutter="35">
 					<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
 						<!-- 上级分类 -->
-						<el-form-item :label="$t('message.formI18nLabel.parentCategory')" :label-width="currentLocale === 'en' ? '120px' : '90px'">
+						<el-form-item :label="$t('message.formI18nLabel.parentCategory')" :label-width="currentLocale == 'en' ? '120px' : '90px'">
 							<el-cascader :options="deptData" :props="{ checkStrictly: true, emitPath: false, value: 'id', label: 'name' }" :placeholder="$t('message.formI18nPlaceholder.parentCategory')" clearable class="w100" v-model="ruleForm.parentId">
 								<template #default="{ node, data }">
 									<span>{{ data.name }}</span>
@@ -16,7 +16,7 @@
 					</el-col>
 					<el-col :xs="24" :sm="12" :md="16" :lg="16" :xl="16">
 						<!-- 分类名称 -->
-						<el-form-item :label="$t('message.formI18nLabel.categoryName')" prop="name" :label-width="currentLocale === 'en' ? '120px' : '90px'">
+						<el-form-item :label="$t('message.formI18nLabel.categoryName')" prop="name" :label-width="currentLocale == 'en' ? '120px' : '90px'">
 							<el-input v-model="ruleForm.name" :placeholder="$t('message.formI18nPlaceholder.categoryName')" clearable></el-input>
 						</el-form-item>
 					</el-col>
@@ -28,13 +28,13 @@
 					</el-col>
 					<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
 						<!-- 分类标识 -->
-						<el-form-item :label="$t('message.formI18nLabel.categoryKey')" prop="key" :label-width="currentLocale === 'en' ? '120px' : '90px'">
+						<el-form-item :label="$t('message.formI18nLabel.categoryKey')" prop="key" :label-width="currentLocale == 'en' ? '120px' : '90px'">
 							<el-input v-model="ruleForm.key" :placeholder="$t('message.formI18nPlaceholder.categoryKey')" clearable></el-input>
 						</el-form-item>
 					</el-col>
 					<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
 						<!-- 描述 -->
-						<el-form-item :label="$t('message.formI18nLabel.desc')" prop="desc" :label-width="currentLocale === 'en' ? '120px' : '90px'">
+						<el-form-item :label="$t('message.formI18nLabel.desc')" prop="desc" :label-width="currentLocale == 'en' ? '120px' : '90px'">
 							<el-input v-model="ruleForm.desc" type="textarea" :placeholder="$t('message.formI18nPlaceholder.desc')" maxlength="150"></el-input>
 						</el-form-item>
 					</el-col>
@@ -42,8 +42,8 @@
 			</el-form>
 			<template #footer>
 				<span class="dialog-footer">
-					<el-button @click="onCancel">{{ $t('message.formI18nButton.cancel') }}</el-button>
-					<el-button type="primary" @click="onSubmit" :loading="loading">{{ ruleForm.id ? $t('message.formI18nButton.edit') : $t('message.formI18nButton.add') }}</el-button>
+					<el-button @click="onCancel">{{ $t('message.tableI18nAction.cancel‌') }}</el-button>
+					<el-button type="primary" @click="onSubmit" :loading="loading">{{ ruleForm.id ? $t('message.tableI18nAction.edit') : $t('message.tableI18nAction.add') }}</el-button>
 				</span>
 			</template>
 		</el-dialog>

+ 1 - 1
src/views/iot/device/category/index.vue

@@ -33,7 +33,7 @@
         <el-table-column :label="$t('message.tableI18nColumn.operation')" align="center" width="140" fixed="right">
           <template #default="scope">
             <!-- 新增 -->
-            <el-button size="small" type="text" @click="onOpenAdd(scope.row)" v-auth="'add'">{{ $t('message.tableI18nAction.add') }}</el-button>
+            <el-button size="small" text type="primary" @click="onOpenAdd(scope.row)" v-auth="'add'">{{ $t('message.tableI18nAction.add') }}</el-button>
             <!-- 修改 -->
             <el-button size="small" text type="warning" @click="onOpenEdit(scope.row)" v-auth="'edit'">{{ $t('message.tableI18nAction.edit') }}</el-button>
             <!-- 删除 -->

+ 1 - 1
src/views/iot/device/instance/component/chart.vue

@@ -5,7 +5,7 @@
 				<div class="dialog-header">
 					<h4 :id="titleId" :class="titleClass">{{ data.name + `(${data.key})` }}</h4>
 					<div class="dialog-header-actions">
-						<el-button type="text" @click="toggleFullscreen" class="fullscreen-btn">
+						<el-button text type="primary" @click="toggleFullscreen" class="fullscreen-btn">
 							<i class="iconfont" :class="!isFullscreen ? 'icon-fullscreen' : 'icon-tuichuquanping'"></i>
 						</el-button>
 					</div>

+ 65 - 36
src/views/iot/device/instance/detail.vue

@@ -2,34 +2,41 @@
   <div class="page bg page-full">
     <div class="content">
       <div class="cont_box" style="align-items: center">
-        <div class="title">设备:{{ detail.name }}</div>
-        <el-tag v-if="areaData.status === 0" type="info" style="margin-left: 20px">未启用</el-tag>
-        <el-tag v-else-if="areaData.status === 1" type="danger" style="margin-left: 20px">离线</el-tag>
-        <el-tag v-else-if="areaData.status === 2" type="success" style="margin-left: 20px">在线</el-tag>
+        <!-- 设备 -->
+        <div class="title">{{$t('message.device.formI18nOption.device')}}:{{ detail.name }}</div>
+        <!-- 未启用 -->
+        <el-tag v-if="areaData.status === 0" type="info" style="margin-left: 20px">{{ $t('message.device.formI18nOption.off') }}</el-tag>
+        <!-- 离线 -->
+        <el-tag v-else-if="areaData.status === 1" type="danger" style="margin-left: 20px">{{ $t('message.device.formI18nOption.offline') }}</el-tag>
+        <!-- 在线 -->
+        <el-tag v-else-if="areaData.status === 2" type="success" style="margin-left: 20px">{{ $t('message.device.formI18nOption.online') }}</el-tag>
       </div>
     </div>
 
     <div class="content-box page-full-part page-full">
       <el-tabs v-model="activeName" @tab-click="handleClick">
-        <el-tab-pane label="运行状态" name="3">
+        <!-- 运行状态 -->
+        <el-tab-pane :label="$t('message.device.runStatus')" name="3">
           <div style="display: flex; flex-wrap: wrap">
             <div class="ant-card">
               <div class="ant-card-body">
                 <div class="cardflex">
-                  <div>设备状态</div>
+                  <div>{{ $t('message.device.deviceStatus') }}</div>
                   <div @click="getrunData()" style="cursor: pointer">
                     <el-icon style="font-size: 18px">
                       <ele-Refresh />
                     </el-icon>
                   </div>
                 </div>
-
-                <div class="statusname" v-if="areaData.status == 0">未启用</div>
-                <div class="statusname" v-if="areaData.status == 1">离线</div>
-                <div class="statusname" v-if="areaData.status == 2">在线</div>
+                <!-- 未启用 -->
+                <div class="statusname" v-if="areaData.status == 0">{{ $t('message.device.formI18nOption.off') }}</div>
+                <!-- 离线 -->
+                <div class="statusname" v-if="areaData.status == 1">{{ $t('message.device.formI18nOption.offline') }}</div>
+                <!-- 在线 -->
+                <div class="statusname" v-if="areaData.status == 2">{{ $t('message.device.formI18nOption.online') }}</div>
                 <div class="cardflex comtest">
-                  <div>数据时间</div>
-                  <div>{{ areaData.lastOnlineTime || "未启用" }}</div>
+                  <div>{{$t('message.device.dataTime')}}</div>
+                  <div>{{ areaData.lastOnlineTime || $t('message.device.formI18nOption.off') }}</div>
                 </div>
               </div>
             </div>
@@ -69,33 +76,46 @@
             </div>
           </div>
         </el-tab-pane>
-        <el-tab-pane label="设备信息" name="1">
+        <!-- 设备信息 -->
+        <el-tab-pane :label="$t('message.device.deviceInfo')" name="1">
           <div class="pro-box">
-            <div class="protitle">设备信息</div>
+            <div class="protitle">{{$t('message.device.deviceInfo')}}</div>
             <div>
-              <el-button size="small" type="primary" v-auth="'edit'" @click="onOpenEditDic(detail)">编辑</el-button>
+              <el-button size="small" type="primary" v-auth="'edit'" @click="onOpenEditDic(detail)">{{ $t('message.tableI18nAction.edit') }}</el-button>
             </div>
           </div>
 
           <el-descriptions class="margin-top" :column="3" border>
-            <el-descriptions-item label="设备标识">
+            <!-- 设备标识 -->
+            <el-descriptions-item :label="$t('message.device.deviceIdentifier')">
               <copy :text="detail.key"></copy>
             </el-descriptions-item>
-            <el-descriptions-item label="设备名称">{{ detail.name }}</el-descriptions-item>
-            <el-descriptions-item label="消息协议">{{ prodetail.messageProtocol }}</el-descriptions-item>
-            <el-descriptions-item label="产品标识">
+            <!-- 设备名称 -->
+            <el-descriptions-item :label="$t('message.device.tableI18nColumn.deviceName')">{{ detail.name }}</el-descriptions-item>
+            <!-- 消息协议 -->
+            <el-descriptions-item :label="$t('message.device.formI18nLabel.messageProtocol')">{{ prodetail.messageProtocol }}</el-descriptions-item>
+            <!-- 产品标识 -->
+            <el-descriptions-item :label="$t('message.device.formI18nLabel.productKey')">
               <copy :text="prodetail.key"></copy>
             </el-descriptions-item>
-            <el-descriptions-item label="产品名称">
+            <!-- 产品名称 -->
+            <el-descriptions-item :label="$t('message.device.formI18nLabel.productName')">
               <router-link :to="'/iotmanager/device/product/detail/' + prodetail.key" class="link-type">{{ detail.productName }} </router-link>
             </el-descriptions-item>
-            <el-descriptions-item label="链接协议">{{ prodetail.transportProtocol }}</el-descriptions-item>
-            <el-descriptions-item label="设备类型">{{ prodetail.deviceType }}</el-descriptions-item>
-            <el-descriptions-item label="固件版本">{{ detail.version }}</el-descriptions-item>
-            <el-descriptions-item label="注册时间">{{ detail.registryTime }}</el-descriptions-item>
-            <el-descriptions-item label="最后上线时间">{{ detail.lastOnlineTime || "" }}</el-descriptions-item>
-            <el-descriptions-item label="详细地址">{{ detail.address }}</el-descriptions-item>
-            <el-descriptions-item label="说明">{{ detail.desc }}</el-descriptions-item>
+            <!-- 链接协议 -->
+            <el-descriptions-item :label="$t('message.device.linkProtocol')">{{ prodetail.transportProtocol }}</el-descriptions-item>
+            <!-- 设备类型 -->
+            <el-descriptions-item :label="$t('message.device.tableI18nColumn.deviceType2')">{{ prodetail.deviceType }}</el-descriptions-item>
+            <!-- 固件版本 -->
+            <el-descriptions-item :label="$t('message.device.firmwareVersion')">{{ detail.version }}</el-descriptions-item>
+            <!-- 注册时间 -->
+            <el-descriptions-item :label="$t('message.device.registryTime')">{{ detail.registryTime }}</el-descriptions-item>
+            <!-- 最后上线时间 -->
+            <el-descriptions-item :label="$t('message.device.tableI18nColumn.lastOnlineTime')">{{ detail.lastOnlineTime || "" }}</el-descriptions-item>
+            <!-- 详细地址 -->
+            <el-descriptions-item :label="$t('message.device.address')">{{ detail.address }}</el-descriptions-item>
+            <!-- 说明 -->
+            <el-descriptions-item :label="$t('message.device.tableI18nColumn.desc2')">{{ detail.desc }}</el-descriptions-item>
             <el-descriptions-item :label="item.name" v-for="(item, index) in detail.tags" :key="index">{{ item.value }}</el-descriptions-item>
             <!-- <el-descriptions-item label="标签">
             <div class="capsule-wrapper">
@@ -108,15 +128,20 @@
           </el-descriptions>
           <div class="flex" style="margin-top: 20px">
             <el-input type="number" style="width: 380px; margin-right: 20px" v-model.number="detail.onlineTimeout">
-              <template #prepend>设备超时时间</template>
-              <template #append>秒</template>
+              <!-- 设备超时时间 -->
+              <template #prepend>{{$t('message.device.deviceTimeout')}}</template>
+              <!-- 秒 -->
+              <template #append>{{ $t('message.device.unitSecond') }}</template>
             </el-input>
+            <!-- 更新 -->
             <el-button type="primary" @click="onlineTimeoutUpdate">
-              <el-icon style="font-size: 18px"><ele-Refresh /></el-icon>更新</el-button
-            >
+              <el-icon style="font-size: 18px"><ele-Refresh /></el-icon>
+              {{ $t('message.device.update') }}
+            </el-button>
           </div>
         </el-tab-pane>
-        <el-tab-pane label="物模型" name="2">
+        <!-- 物模型 -->
+        <el-tab-pane :label="$t('message.device.thingModel')" name="2">
           <el-tabs type="border-card" v-model="activetab" @tab-click="wuhandleClick">
             <el-tab-pane label="属性定义" name="attr">
               <div class="wu-title">
@@ -243,10 +268,12 @@
           </el-tabs>
           <pagination v-show="tableData.total > 0" :total="tableData.total" v-model:page="tableData.param.pageNum" v-model:limit="tableData.param.pageSize" @pagination="getList()" />
         </el-tab-pane>
-        <el-tab-pane label="设备功能" name="5">
+        <!-- 设备功能 -->
+        <el-tab-pane :label="$t('message.device.deviceFunction')" name="5">
           <functionCom :device-key="detail.key" :product-key="prodetail.key" v-if="detail.key && prodetail.key && activeName === '5'"></functionCom>
         </el-tab-pane>
-        <el-tab-pane label="日志管理" name="4">
+        <!-- 日志管理 -->
+        <el-tab-pane :label="$t('message.device.logManagement')" name="4">
           <div class="system-user-search mb15">
             <el-form :model="logtableData.param" ref="logqueryRef" inline label-width="68px">
               <el-form-item label="日志类型" prop="types">
@@ -287,7 +314,8 @@
 
           <pagination v-show="logtableData.total > 0" :total="logtableData.total" v-model:page="logtableData.param.pageNum" v-model:limit="logtableData.param.pageSize" @pagination="getlog" />
         </el-tab-pane>
-        <el-tab-pane label="Topic列表" name="topic">
+        <!-- Topic列表 -->
+        <el-tab-pane :label="$t('message.device.topicList')" name="topic">
           SagooMqtt协议 ,涉及的topic如下:
           <el-table style="width: 100%; margin-top: 20px" :data="topicData" border>
             <el-table-column label="描述" prop="info" width="250" />
@@ -339,7 +367,8 @@
             <pagination v-show="deviceTableData.total > 0" :total="deviceTableData.total" v-model:page="deviceTableData.param.pageNum" v-model:limit="deviceTableData.param.pageSize" @pagination="getDeviceTableData" />
           </div>
         </el-tab-pane>
-        <el-tab-pane label="设备档案" name="7" v-if="deviceAssetData">
+        <!-- 设备档案 -->
+        <el-tab-pane :label="$t('message.device.deviceArchive')" name="7" v-if="deviceAssetData">
           <el-form label-width="110px">
             <div class="pro-box">
               <div class="protitle">设备档案</div>

+ 119 - 45
src/views/iot/device/instance/index.vue

@@ -4,36 +4,51 @@
       <div class="system-user-search mb15">
         <el-form :model="tableData.param" ref="queryRef" inline class="search-form">
           <div class="search-conditions">
-            <el-form-item label="关键字" prop="keyWord">
-              <el-input v-model="tableData.param.keyWord" placeholder="输入名称或标识" clearable style="width: 200px" @keyup.enter.native="typeList" />
+            <!-- 关键字 -->
+            <el-form-item :label="$t('message.device.formI18nLabel.keyword')" prop="keyWord">
+              <!-- 输入名称或标识 -->
+              <el-input v-model="tableData.param.keyWord" :placeholder="$t('message.device.formI18nPlaceholder.keyword')" clearable style="width: 200px" @keyup.enter.native="typeList" />
             </el-form-item>
             <!--          <el-form-item label="标识" prop="key">-->
             <!--            <el-input v-model="tableData.param.key" placeholder="请输入设备标识" clearable style="width: 150px" @keyup.enter.native="typeList" />-->
             <!--          </el-form-item>-->
-            <el-form-item label="状态" prop="status">
-              <el-select v-model="tableData.param.status" placeholder="状态" clearable style="width: 80px">
-                <el-option label="在线" :value="2" />
-                <el-option label="离线" :value="1" />
-                <el-option label="未启用" :value="0" />
+            <!-- 状态 -->
+            <el-form-item :label="$t('message.device.formI18nLabel.status')" prop="status">
+              <!-- 状态 -->
+              <el-select v-model="tableData.param.status" :placeholder="$t('message.device.formI18nLabel.status')" clearable style="width: 88px">
+                <!-- 在线 -->
+                <el-option :label="$t('message.device.formI18nOption.online')" :value="2" />
+                <!-- 离线 -->
+                <el-option :label="$t('message.device.formI18nOption.offline')" :value="1" />
+                <!-- 未启用 -->
+                <el-option :label="$t('message.device.formI18nOption.off')" :value="0" />
               </el-select>
             </el-form-item>
 
             <template v-if="showMoreFilter">
-              <el-form-item label="所属产品" prop="productKey">
-                <el-select v-model="tableData.param.productKey" style="width: 120px" filterable clearable placeholder="选择产品">
+              <!-- 所属产品 -->
+              <el-form-item :label="$t('message.device.formI18nLabel.productBind')" prop="productKey">
+                <!-- 选择产品 -->
+                <el-select v-model="tableData.param.productKey" style="width: 120px" filterable clearable :placeholder="$t('message.device.formI18nLabel.productBind')">
                   <el-option v-for="item in productData" :key="item.key" :label="item.name" :value="item.key" value-key="id"> </el-option>
                 </el-select>
               </el-form-item>
 
-              <el-form-item label="设备类型" prop="deviceTypes">
-                <el-select v-model="tableData.param.deviceTypes" multiple style="width: 260px" clearable placeholder="设备类型">
-                  <el-option label="设备" value="设备" />
-                  <el-option label="网关" value="网关" />
-                  <el-option label="子设备" value="子设备" />
+              <!-- 设备类型 -->
+              <el-form-item :label="$t('message.device.formI18nLabel.deviceType')" prop="deviceTypes">
+                <el-select v-model="tableData.param.deviceTypes" multiple style="width: 260px" clearable :placeholder="$t('message.device.formI18nLabel.deviceType')">
+                  <!-- 设备 -->
+                  <el-option :label="$t('message.device.formI18nOption.device')" value="设备" />
+                  <!-- 网关 -->
+                  <el-option :label="$t('message.device.formI18nOption.gateway')" value="网关" />
+                  <!-- 子设备 -->
+                  <el-option :label="$t('message.device.formI18nOption.subDevice')" value="子设备" />
                 </el-select>
               </el-form-item>
 
-              <el-form-item label="所属组织" prop="deptIds">
+              <!-- 所属组织 -->
+              <el-form-item :label="$t('message.device.formI18nLabel.deptIds')" prop="deptIds">
+                <!-- 请选择所属组织 -->
                 <el-cascader
                   v-model="tableData.param.deptIds"
                   :options="deptOptions"
@@ -47,21 +62,23 @@
                   }"
                   style="width: 200px"
                   clearable
-                  placeholder="请选择所属组织"
+                  :placeholder="$t('message.device.formI18nPlaceholder.deptIds')"
                   collapse-tags
                   collapse-tags-tooltip
                   filterable
                 />
               </el-form-item>
 
-              <el-form-item label="设备标签" prop="tags">
+              <!-- 设备标签 -->
+              <el-form-item :label="$t('message.device.formI18nLabel.deviceTag')" prop="tags">
+                <!-- 请输入标签 -->
                 <el-select
                   v-model="tableData.param.tags"
                   multiple
                   filterable
                   allow-create
                   default-first-option
-                  placeholder="请输入标签"
+                  :placeholder="$t('message.device.formI18nPlaceholder.deviceTag')"
                   style="width: 260px"
                   clearable
                 >
@@ -77,7 +94,8 @@
           <div class="search-actions">
             <el-form-item>
               <el-button link type="primary" @click="showMoreFilter = !showMoreFilter">
-                {{ showMoreFilter ? '收起' : '更多筛选' }}
+                <!-- 收起/更多筛选 -->
+                {{ showMoreFilter ? $t('message.device.formI18nButton.collapse') : $t('message.device.formI18nButton.showMoreFilter') }}
                 <el-icon class="el-icon--right">
                   <component :is="showMoreFilter ? 'ArrowUp' : 'ArrowDown'" />
                 </el-icon>
@@ -85,11 +103,12 @@
             </el-form-item>
 
             <el-form-item>
+              <!-- 查询 -->
               <el-button type="primary" class="ml10" @click="typeList">
                 <el-icon>
                   <ele-Search />
                 </el-icon>
-                查询
+                {{ $t('message.tableI18nAction.query') }}
               </el-button>
               <!-- <el-button @click="resetQuery(queryRef)">
                 <el-icon>
@@ -97,56 +116,72 @@
                 </el-icon>
                 重置
               </el-button> -->
+              <!-- 新增 -->
               <el-button type="primary" class="ml10" @click="onOpenAddDic" v-auth="'add'">
                 <el-icon>
                   <ele-FolderAdd />
                 </el-icon>
-                新增
+                {{ $t('message.tableI18nAction.add') }}
               </el-button>
+              <!-- 删除 -->
               <el-button type="info" class="ml10" @click="onRowDel()" v-auth="'del'">
                 <el-icon>
                   <ele-Delete />
                 </el-icon>
-                删除
+                {{ $t('message.tableI18nAction.delete') }}
               </el-button>
 
               <el-dropdown>
+                <!-- 更多 -->
                 <el-button type="danger" class="ml10">
-                  更多
+                  {{ $t('message.tableI18nAction.more') }}
                   <el-icon class="el-icon--right"><arrow-down /></el-icon>
                 </el-button>
                 <template #dropdown>
                   <el-dropdown-menu>
                     <el-dropdown-item>
+                      <!-- 批量启用 -->
                       <el-button type="success" @click="setDeviceStatus1()">
                         <el-icon>
                           <ele-Open />
                         </el-icon>
-                        批量启用
+                        {{ $t('message.device.formI18nButton.batchEnable') }}
                       </el-button>
                     </el-dropdown-item>
                     <el-dropdown-item>
+                      <!-- 批量禁用 -->
                       <el-button type="warning" @click="setDeviceStatus0()">
                         <el-icon>
                           <ele-TurnOff />
                         </el-icon>
-                        批量禁用
+                        {{ $t('message.device.formI18nButton.batchDisable') }}
                       </el-button>
                     </el-dropdown-item>
                     <el-dropdown-item>
+                      <!-- 导入设备 -->
                       <el-button @click="onOpenexcelDic('upload')">
                         <el-icon>
                           <ele-Upload />
                         </el-icon>
-                        导入设备
+                        {{ $t('message.device.formI18nButton.importDevice') }}
                       </el-button>
                     </el-dropdown-item>
                     <el-dropdown-item>
+                      <!-- 导出设备 -->
                       <el-button @click="onOpenexcelDic('down')">
                         <el-icon>
                           <ele-Download />
                         </el-icon>
-                        导出设备
+                        {{ $t('message.device.formI18nButton.exportDevice') }}
+                      </el-button>
+                    </el-dropdown-item>
+                    <el-dropdown-item>
+                      <!-- 批量检测状态 -->
+                      <el-button @click="batchCheckStatus()">
+                        <el-icon>
+                          <ele-Refresh />
+                        </el-icon>
+                        {{ $t('message.device.formI18nButton.batchCheckStatus') }}
                       </el-button>
                     </el-dropdown-item>
                   </el-dropdown-menu>
@@ -158,33 +193,48 @@
       </div>
       <el-table :data="tableData.data" style="width: 100%" @selection-change="handleSelectionChange" v-loading="tableData.loading">
         <el-table-column type="selection" width="55" align="center" />
-        <el-table-column label="标识" prop="key" min-width="150" show-overflow-tooltip v-col="'key'">
+        <!-- 标识 -->
+        <el-table-column :label="$t('message.device.tableI18nColumn.key')" prop="key" min-width="150" show-overflow-tooltip v-col="'key'">
           <template #default="{ row }">
             <copy :text="row.key"></copy>
           </template>
         </el-table-column>
-        <el-table-column label="设备名称" prop="name" min-width="160" show-overflow-tooltip v-col="'name'" />
-        <el-table-column label="设备类型" prop="product.deviceType" min-width="100" align="center" show-overflow-tooltip v-col="'deviceType'" />
-        <el-table-column label="所属产品" prop="productName" min-width="120" align="center" show-overflow-tooltip v-col="'productName'" />
-        <el-table-column prop="status" label="状态" min-width="80" align="center" v-col="'status'">
+        <!-- 设备名称 -->
+        <el-table-column :label="$t('message.device.tableI18nColumn.deviceName')" prop="name" min-width="160" show-overflow-tooltip v-col="'name'" />
+        <!-- 设备类型 -->
+        <el-table-column :label="$t('message.device.tableI18nColumn.deviceType2')" prop="product.deviceType" min-width="120" align="center" show-overflow-tooltip v-col="'deviceType'" />
+        <!-- 所属产品 -->
+        <el-table-column :label="$t('message.device.formI18nLabel.productBind')" prop="productName" min-width="160" align="center" show-overflow-tooltip v-col="'productName'" />
+        <!-- 状态 -->
+        <el-table-column prop="status" :label="$t('message.device.tableI18nColumn.status')" min-width="90" align="center" v-col="'status'">
           <template #default="scope">
-            <el-tag type="danger" size="small" v-if="scope.row.status == 1">离线</el-tag>
-            <el-tag type="success" size="small" v-if="scope.row.status == 2">在线</el-tag>
-            <el-tag type="info" size="small" v-if="scope.row.status == 0">未启用</el-tag>
+            <!-- 离线 -->
+            <el-tag type="danger" size="small" v-if="scope.row.status == 1">{{ $t('message.device.formI18nOption.offline') }}</el-tag>
+            <!-- 在线 -->
+            <el-tag type="success" size="small" v-if="scope.row.status == 2">{{ $t('message.device.formI18nOption.online') }}</el-tag>
+            <!-- 未启用 -->
+            <el-tag type="info" size="small" v-if="scope.row.status == 0">{{ $t('message.device.formI18nOption.off') }}</el-tag>
           </template>
         </el-table-column>
-        <el-table-column prop="lastOnlineTime" label="最后上线时间" align="center" width="160" v-col="'lastOnlineTime'"></el-table-column>
-        <el-table-column prop="desc" label="说明" show-overflow-tooltip v-col="'desc'"></el-table-column>
-
-        <el-table-column label="操作" width="180" align="center" fixed="right">
+        <!-- 最后上线时间  -->
+        <el-table-column prop="lastOnlineTime" :label="$t('message.device.tableI18nColumn.lastOnlineTime')" align="center" width="160" v-col="'lastOnlineTime'"></el-table-column>
+        <!-- 说明 -->
+        <el-table-column prop="desc" :label="$t('message.device.tableI18nColumn.desc2')" min-width="110" show-overflow-tooltip v-col="'desc'"></el-table-column>
+        <!-- 操作 -->
+        <el-table-column :label="$t('message.tableI18nColumn.operation')" width="200" align="center" fixed="right">
           <template #default="scope">
+            <!-- 详情 -->
             <router-link :to="'/iotmanager/device/instance/' + scope.row.key" class="link-type" style="padding-right: 12px;font-size: 12px;color: #409eff;" v-auth="'detail'">
-              <span>详情</span>
+              <span>{{ $t('message.tableI18nAction.detail') }}</span>
             </router-link>
-            <el-button size="small" text type="warning" @click="onOpenEditDic(scope.row)" v-auth="'edit'">修改</el-button>
-            <el-button size="small" text type="success" @click="onActionStatus(scope.row)" v-if="scope.row.status == 0" v-auth="'status'">启用</el-button>
-            <el-button size="small" text type="primary" @click="onActionStatus(scope.row)" v-if="scope.row.status > 0" v-auth="'status'">停用</el-button>
-            <el-button size="small" text type="info" @click="onRowDel(scope.row)" v-auth="'del'">删除</el-button>
+            <!-- 修改 -->
+            <el-button size="small" text type="warning" @click="onOpenEditDic(scope.row)" v-auth="'edit'">{{ $t('message.tableI18nAction.edit') }}</el-button>
+            <!-- 启用 -->
+            <el-button size="small" text type="success" @click="onActionStatus(scope.row)" v-if="scope.row.status == 0" v-auth="'status'">{{ $t('message.device.enable') }}</el-button>
+            <!-- 停用 -->
+            <el-button size="small" text type="primary" @click="onActionStatus(scope.row)" v-if="scope.row.status > 0" v-auth="'status'">{{ $t('message.device.disable') }}</el-button>
+            <!-- 删除 -->
+            <el-button size="small" text type="info" @click="onRowDel(scope.row)" v-auth="'del'">{{ $t('message.tableI18nAction.delete') }}</el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -306,6 +356,18 @@ export default defineComponent({
       excelDicRef.value.openDialog(type);
     };
 
+    const batchCheckStatus = () => {
+      const loading = ElMessage({
+        message: '同步中,请稍后...',
+        type: 'info',
+        duration: 0,
+      });
+      api.device.syncStatus().then(() => {
+        ElMessage.success('同步成功');
+        typeList();
+      }).finally(() => loading.close());
+    };
+
     //批量启用
     const setDeviceStatus1 = (row?: TableDataRow) => {
       let keys: string[] = [];
@@ -315,9 +377,11 @@ export default defineComponent({
         keys = state.keys;
       }
       if (keys.length === 0) {
+        // 请选择要操作的数据。
         ElMessage.error('请选择要操作的数据。');
         return;
       }
+      // 确认要批量启用这些设备吗?
       ElMessageBox.confirm("确认要批量启用这些设备吗?", '提示', {
         confirmButtonText: '确认',
         cancelButtonText: '取消',
@@ -326,6 +390,7 @@ export default defineComponent({
         .then(() => {
           batchLoading.value = true
           api.device.setDeviceStatus({ ids: keys, status: 1 }).then(() => {
+            // 启用成功
             ElMessage.success('启用成功');
             typeList();
           }).finally(() => batchLoading.value = false)
@@ -342,9 +407,11 @@ export default defineComponent({
         keys = state.keys;
       }
       if (keys.length === 0) {
+        // 请选择要操作的数据。
         ElMessage.error('请选择要操作的数据。');
         return;
       }
+      // 确认要批量禁用这些设备吗?
       ElMessageBox.confirm("确认要批量禁用这些设备吗?", '提示', {
         confirmButtonText: '确认',
         cancelButtonText: '取消',
@@ -353,6 +420,7 @@ export default defineComponent({
         .then(() => {
           batchLoading.value = true
           api.device.setDeviceStatus({ ids: keys, status: 0 }).then(() => {
+            // 禁用成功
             ElMessage.success('禁用成功');
             typeList();
           }).finally(() => batchLoading.value = false)
@@ -361,15 +429,18 @@ export default defineComponent({
     }
     // 删除产品
     const onRowDel = (row?: TableDataRow) => {
+      // 你确定要删除所选数据?
       let msg = '你确定要删除所选数据?';
       let keys: string[] = [];
       if (row) {
+        // 此操作将永久删除设备:"${row.name}",是否继续?
         msg = `此操作将永久删除设备:"${row.name}",是否继续?`;
         keys = [row.key];
       } else {
         keys = state.keys;
       }
       if (keys.length === 0) {
+        // 请选择要删除的数据。
         ElMessage.error('请选择要删除的数据。');
         return;
       }
@@ -380,6 +451,7 @@ export default defineComponent({
       })
         .then(() => {
           api.instance.del(keys).then(() => {
+            // 删除成功
             ElMessage.success('删除成功');
             typeList();
           });
@@ -408,11 +480,13 @@ export default defineComponent({
       if (item.status == 0) {
         api.instance.devdeploy(item.key).then((res: any) => {
           typeList();
+          // 操作成功
           ElMessage.success(res.message || '操作成功');
         });
       } else {
         api.instance.devundeploy(item.key).then((res: any) => {
           typeList();
+          // 操作成功
           ElMessage.success(res.message || '操作成功');
         });
       }
@@ -432,10 +506,10 @@ export default defineComponent({
     };
 
     return {
-
       addDicRef,
       excelDicRef,
       onOpenexcelDic,
+      batchCheckStatus,
       editDicRef,
       detailRef,
       queryRef,

+ 40 - 24
src/views/iot/device/product/component/dataParse.vue

@@ -1,8 +1,10 @@
 <template>
 	<div class="data-parse-wrapper" style="position: relative" ref="wrapperRef">
 		<div class="full-screen-btn">
-			<el-icon v-if="!isFullScreen" @click="toggleFullScreen" title="全屏"><FullScreen /></el-icon>
-			<el-icon v-else @click="toggleFullScreen" title="退出全屏"><Close /></el-icon>
+			<!-- fullScreen -->
+			<el-icon v-if="!isFullScreen" @click="toggleFullScreen" :title="$t('message.device.fullScreen')"><FullScreen /></el-icon>
+			<!-- exitFullScreen -->
+			<el-icon v-else @click="toggleFullScreen" :title="$t('message.device.exitFullScreen')"><Close /></el-icon>
 		</div>
 		<div class="flex" style="align-items: stretch">
 			<codeEditor class="params flex1" ref="mirrorRef" style="height: calc(100vh - 310px)" mode="javascript" :content="script || emptyFunction"></codeEditor>
@@ -11,48 +13,61 @@
 					<el-radio-button label="parse">parse</el-radio-button>
 					<el-radio-button label="send">send</el-radio-button>
 				</el-radio-group>
+				<!-- 请输入入参,以字符串的方式,如果是对象字符串会在执行时自动转换为对象再执行 -->
 				<el-input
 					class="input"
 					v-model="inputData"
 					type="textarea"
-					placeholder="请输入入参,以字符串的方式,如果是对象字符串会在执行时自动转换为对象再执行"
+					:placeholder="$t('message.device.tip1')"
 				></el-input>
-				<el-input class="input" v-model="outputData" type="textarea" readonly placeholder="此处显示执行结果"></el-input>
+				<!-- 此处显示执行结果 -->
+				<el-input class="input" v-model="outputData" type="textarea" readonly :placeholder="$t('message.device.tip2')"></el-input>
 			</div>
 		</div>
-		<el-button type="primary" style="margin-top: 20px" v-auth="'save'" @click="saveCode">保存脚本</el-button>
-		<el-button type="primary" style="margin-top: 20px" v-auth="'debug'" :loading="runing" @click="mock">调试</el-button>
+		<!-- 保存脚本 -->
+		<el-button type="primary" style="margin-top: 20px" v-auth="'save'" @click="saveCode">{{ $t('message.device.tip3') }}</el-button>
+		<!-- 调试 -->
+		<el-button type="primary" style="margin-top: 20px" v-auth="'debug'" :loading="runing" @click="mock">{{ $t('message.device.tip4') }}</el-button>
 	</div>
 </template>
 
 <script lang="ts" setup>
-import { onMounted, ref, onUnmounted } from 'vue'
+import { onMounted, ref, onUnmounted, computed } from 'vue'
 import codeEditor from '/@/components/codeEditor/index.vue'
 import { ElMessage } from 'element-plus'
 import api from '/@/api/device'
 import { useRoute } from 'vue-router'
 import { FullScreen, Close } from '@element-plus/icons-vue'
+import { useI18n } from 'vue-i18n'
+const { t } = useI18n()
+// tip5: "下面是预制的空的方法,请不要修改函数名称,直接在内部编写函数即可",
+// tip6: "此处是设备功能调用应答数据解析",
+// tip7: "此处编写对数据的处理",
+// tip8: "此处是设备功能调用发送数据解析",
+// tip9: "此处编写对数据的处理",
+// tip10: "请先输入可执行脚本",
+const emptyFunction = computed(() => 
+`
+	// ${t('message.device.tip5')}
 
-const emptyFunction = `
-// 下面是预制的空的方法,请不要修改函数名称,直接在内部编写函数即可
-
-// 此处是设备功能调用应答数据解析
-function parse (data) {
+	// ${t('message.device.tip6')}
+	function parse (data) {
 
-	// 此处编写对数据的处理
+		// ${t('message.device.tip7')}
 
-	return data
-}
+		return data
+	}
 
 
-// 此处是设备功能调用发送数据解析
-function send (data) {
+	// ${t('message.device.tip8')}
+	function send (data) {
 
-	// 此处编写对数据的处理
+		// ${t('message.device.tip9')}
 
-	return data
-}
+		return data
+	}
 `
+);
 
 const route = useRoute()
 
@@ -149,7 +164,7 @@ function toSave(data: string) {
 			scriptInfo: data,
 		})
 		.then(() => {
-			ElMessage.success('保存成功')
+			ElMessage.success(t('message.tableI18nConfirm.saveSuccess'))
 			emit('updateScript', data)
 		})
 }
@@ -176,13 +191,14 @@ async function mock() {
 	outputData.value = ''
 
 	await validate()
-
-	if (!inputData.value) return ElMessage.error('请输入参数')
+  // 请输入参数
+	if (!inputData.value) return ElMessage.error(t('message.device.formI18nPlaceholder.inputParameter'))
 
 	const funStr = mirrorRef.value.getValue()
 
 	if (funStr === '') {
-		return ElMessage.error('请先输入可执行脚本')
+		// 请先输入可执行脚本
+		return ElMessage.error(t('message.device.tip10'))
 	}
 
 	// 远程调试

+ 21 - 10
src/views/iot/device/product/component/deviceIn.vue

@@ -1,35 +1,46 @@
 <template>
-	<div class="title">接入方式</div>
+	<!-- 接入方式 -->
+	<div class="title">{{ $t('message.device.formI18nLabel.transportProtocol') }}</div>
 	<div class="text">{{ data.name }}</div>
-	<div class="title">消息协议</div>
+	<!-- 消息协议 -->
+	<div class="title">{{ $t('message.device.formI18nLabel.messageProtocol') }}</div>
 	<div class="text">{{ data.protocol }}</div>
-	<div class="title">认证说明</div>
+	<!-- 认证说明 -->
+	<div class="title">{{ $t('message.device.formI18nLabel.authDescription') }}</div>
 	<div class="text">{{ data.description }}</div>
-	<div class="title">链接信息</div>
+	<!-- 链接信息 -->
+	<div class="title">{{ $t('message.device.formI18nLabel.linkInfo') }}</div>
 	<div class="text">{{ data.link }}</div>
-	<div class="title">认证配置</div>
+	<!-- 认证配置 -->
+	<div class="title">{{ $t('message.device.formI18nLabel.authConfig') }}</div>
 
-	<template v-if="!isAdmin">请联系管理员</template>
+	<!-- 请联系管理员 -->
+	<template v-if="!isAdmin">{{ $t('message.device.formI18nLabel.contactAdmin') }}</template>
 	<template v-else-if="data.authType === 1 || data.authType === 2">
-		<el-form-item label="认证方式" prop="authType" label-width="80px" style="margin-bottom: 0;">
+		<!-- 认证方式 -->
+		<el-form-item :label="$t('message.device.formI18nLabel.authType')" prop="authType" label-width="80px" style="margin-bottom: 0;">
 			{{ data.authType === 1 ? 'Basic' : 'AccessToken' }}
 		</el-form-item>
 		<template v-if="data.authType === 1">
-			<el-form-item label="用户名" prop="authUser" label-width="80px" style="margin-bottom: 0;">
+			<!-- 用户名 -->
+			<el-form-item :label="$t('message.device.formI18nLabel.authUser')" prop="authUser" label-width="80px" style="margin-bottom: 0;">
 				{{ data.authUser }}
 			</el-form-item>
-			<el-form-item label="密码" prop="authPasswd" label-width="80px">
+			<!-- 密码 -->
+			<el-form-item :label="$t('message.device.formI18nLabel.authPasswd')" prop="authPasswd" label-width="80px">
 				{{ data.authPasswd }}
 			</el-form-item>
 		</template>
 		<template v-else>
+			<!-- Aceess Token -->
 			<el-form-item label="Aceess Token" prop="accessToken">
 				{{ data.accessToken }}
 			</el-form-item>
 		</template>
 	</template>
 	<template v-else-if="data.authType === 3">
-		<el-form-item label="认证证书" prop="certificateName">
+		<!-- 认证证书 -->
+		<el-form-item :label="$t('message.device.formI18nLabel.certificateId')" prop="certificateName">
 			{{ data.certificateName }}
 		</el-form-item>
 	</template>

+ 412 - 345
src/views/iot/device/product/component/editAttr.vue

@@ -1,58 +1,105 @@
 <template>
 	<div class="system-edit-dic-container">
-		<el-dialog :title="(ruleForm.id !== 0 ? '修改' : '添加') + '属性定义'" v-model="isShowDialog" width="769px">
-			<el-form :model="ruleForm" ref="formRef" :rules="rules" label-width="120px">
-				<el-form-item label="属性定义标识" prop="key">
-					<el-input v-model="ruleForm.key" placeholder="请输入属性定义标识" :disabled="ruleForm.id !== 0 ? true : false" />
+		<!-- 新增属性定义/编辑属性定义 -->
+		<el-dialog
+			:title="ruleForm.id !== 0 ? $t('message.device.dialogI18n.editPropertyDefinition') : $t('message.device.dialogI18n.addPropertyDefinition')"
+			v-model="isShowDialog"
+			width="769px"
+		>
+			<!-- 根据当前语言设置label-width -->
+			<el-form :model="ruleForm" ref="formRef" :rules="rules" :label-width="currentLocale == 'en' ? '150px' : '120px'">
+				<!-- 属性定义标识 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.propertyIdentifier')" prop="key">
+					<!-- 请输入属性定义标识 -->
+					<el-input
+						v-model="ruleForm.key"
+						:placeholder="$t('message.device.formI18nPlaceholder.propertyIdentifier')"
+						:disabled="ruleForm.id !== 0 ? true : false"
+					/>
 				</el-form-item>
-				<el-form-item label="属性定义名称" prop="name">
-					<el-input v-model.trim="ruleForm.name" placeholder="请输入属性定义名称" />
+				<!-- 属性定义名称 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.propertyName')" prop="name">
+					<!-- 请输入属性定义名称 -->
+					<el-input v-model.trim="ruleForm.name" :placeholder="$t('message.device.formI18nPlaceholder.propertyName')" />
 				</el-form-item>
 
-				<el-form-item label="数据类型" prop="type">
-					<el-select v-model="valueType.type" placeholder="请选择数据类型" @change="seletChange" :disabled="ruleForm.id !== 0 ? true : false">
-						<el-option-group v-for="group in typeData" :key="group.label" :label="group.label">
-							<el-option v-for="item in group.options" :key="item.type" :label="item.title" :value="item.type" />
-						</el-option-group>
-					</el-select>
+				<!-- 数据类型 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.dataType')" prop="type">
+					<template v-if="typeData.length > 0">
+						<!-- 请选择数据类型 -->
+						<el-select
+							v-model="valueType.type"
+							:placeholder="$t('message.device.formI18nPlaceholder.selectDataType')"
+							@change="seletChange"
+							:disabled="ruleForm.id !== 0 ? true : false"
+						>
+							<el-option-group v-for="group in typeData" :key="group.label" :label="group.label">
+								<el-option v-for="item in group.options" :key="item.type" :label="item.title" :value="item.type" />
+							</el-option-group>
+						</el-select>
+					</template>
 				</el-form-item>
 
 				<!--根据数据类型输出不同表单-->
 
-				<el-form-item label="精度" prop="decimals" v-if="type == 'float' || type == 'double'">
-					<el-input v-model="valueType.decimals" placeholder="请输入精度" />
+				<!-- 精度 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.precision')" prop="decimals" v-if="type == 'float' || type == 'double'">
+					<!-- 请输入精度 -->
+					<el-input v-model="valueType.decimals" :placeholder="$t('message.device.formI18nPlaceholder.inputPrecision')" />
 				</el-form-item>
 
-				<el-form-item label="单位" prop="unit" v-if="type == 'int' || type == 'long' || type == 'float' || type == 'double'">
-					<el-input v-model="valueType.unit" placeholder="请输入单位" />
+				<!-- 单位 -->
+				<el-form-item
+					:label="$t('message.device.formI18nLabel.unit')"
+					prop="unit"
+					v-if="type == 'int' || type == 'long' || type == 'float' || type == 'double'"
+				>
+					<!-- 请输入单位 -->
+					<el-input v-model="valueType.unit" :placeholder="$t('message.device.formI18nPlaceholder.inputUnit')" />
 				</el-form-item>
 
-				<el-form-item label="最大长度" prop="maxLength" v-if="type == 'string'">
-					<el-input v-model="valueType.maxLength" placeholder="请输入最大长度" />
+				<!-- 最大长度 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.maxLength')" prop="maxLength" v-if="type == 'string'">
+					<!-- 请输入最大长度 -->
+					<el-input v-model="valueType.maxLength" :placeholder="$t('message.device.formI18nPlaceholder.inputMaxLength')" />
 				</el-form-item>
 
-				<el-form-item label="时间格式" prop="maxLength" v-if="type == 'date'">
-					<el-input v-model="valueType.maxLength" placeholder="请输入时间格式" />
+				<!-- 时间格式 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.timeFormat')" prop="maxLength" v-if="type == 'date'">
+					<!-- 请输入时间格式 -->
+					<el-input v-model="valueType.maxLength" :placeholder="$t('message.device.formI18nPlaceholder.inputTimeFormat')" />
 				</el-form-item>
 
-				<el-form-item label="布尔值" prop="trueText" v-if="type == 'boolean'">
+				<!-- 布尔值 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.booleanValue')" prop="trueText" v-if="type == 'boolean'">
 					<div class="input-box flex-row">
-						<el-input v-model="valueType.trueText" placeholder="请输入true时显示的文字" /><span style="margin: 0px 10px">~</span>
-						<el-input v-model="valueType.trueValue" placeholder="请输入布尔值" disabled />
+						<!-- 请输入true时显示的文字 -->
+						<el-input v-model="valueType.trueText" :placeholder="$t('message.device.formI18nPlaceholder.inputTrueText')" /><span
+							style="margin: 0px 10px"
+							>~</span
+						>
+						<!-- 请输入布尔值 -->
+						<el-input v-model="valueType.trueValue" :placeholder="$t('message.device.formI18nPlaceholder.inputBooleanValue')" disabled />
 					</div>
 
 					<div class="input-box flex-row">
-						<el-input v-model="valueType.falseText" placeholder="请输入false时显示的文字" /> <span style="margin: 0px 10px">~</span>
-						<el-input v-model="valueType.falseValue" placeholder="请输入布尔值" disabled />
+						<!-- 请输入false时显示的文字 -->
+						<el-input v-model="valueType.falseText" :placeholder="$t('message.device.formI18nPlaceholder.inputFalseText')" />
+						<span style="margin: 0px 10px">~</span>
+						<!-- 请输入布尔值 -->
+						<el-input v-model="valueType.falseValue" :placeholder="$t('message.device.formI18nPlaceholder.inputBooleanValue')" disabled />
 					</div>
 				</el-form-item>
 
-				<el-form-item label="枚举项" prop="maxLength" v-if="type == 'enum'">
+				<!-- 枚举项 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.enumItems')" prop="maxLength" v-if="type == 'enum'">
 					<div class="input-box flex-row" v-for="(item, index) in enumdata" :key="index">
-						<el-input v-model="item.text" placeholder="请输入枚举文本" /><span style="margin: 0px 10px"><el-icon>
-								<Right />
-							</el-icon></span>
-						<el-input v-model="item.value" placeholder="请输入枚举值" />
+						<!-- 请输入枚举文本 -->
+						<el-input v-model="item.text" :placeholder="$t('message.device.formI18nPlaceholder.inputEnumText')" /><span style="margin: 0px 10px"
+							><el-icon> <Right /> </el-icon
+						></span>
+						<!-- 请输入枚举值 -->
+						<el-input v-model="item.value" :placeholder="$t('message.device.formI18nPlaceholder.inputEnumValue')" />
 						<div class="input-option">
 							<el-icon @click="addEnum" v-if="index == 0">
 								<Plus />
@@ -64,15 +111,20 @@
 					</div>
 				</el-form-item>
 
-				<el-form-item label="JSON对象" prop="maxLength" v-if="type == 'object'">
+				<el-form-item :label="$t('message.device.formI18nLabel.jsonObject')" prop="maxLength" v-if="type == 'object'">
 					<div v-for="(item, index) in jsondata" :key="index" class="jslist">
 						<div class="jsonlist">
-							<div>参数标识:{{ item.key }}</div>
-							<div>参数名称:{{ item.name }}</div>
-							<div>数据类型:{{ item.valueType.type }}</div>
+							<!-- 参数标识:{{ item.key }} -->
+							<div>{{ $t('message.device.formI18nLabel.parameterIdentifier') }}:{{ item.key }}</div>
+							<!-- 参数名称:{{ item.name }} -->
+							<div>{{ $t('message.device.formI18nLabel.parameterName') }}:{{ item.name }}</div>
+							<!-- 数据类型:{{ item.valueType.type }} -->
+							<div>{{ $t('message.device.formI18nLabel.dataType') }}:{{ item.valueType.type }}</div>
 							<div class="jsonoption">
-								<el-link type="primary" @click="editjson(index)">编辑</el-link>
-								<el-link type="primary" @click="deljson(index)">删除</el-link>
+								<!-- 编辑 -->
+								<el-link type="primary" @click="editjson(index)">{{ $t('message.tableI18nAction.edit') }}</el-link>
+								<!-- 删除 -->
+								<el-link type="primary" @click="deljson(index)">{{ $t('message.tableI18nAction.delete') }}</el-link>
 							</div>
 						</div>
 					</div>
@@ -82,54 +134,81 @@
 							<el-icon>
 								<Plus />
 							</el-icon>
-							<div>添加参数</div>
+							<!-- 添加参数 -->
+							<div>{{ $t('message.device.formI18nButton.addParameter') }}</div>
 						</div>
 					</div>
 				</el-form-item>
 
 				<div v-if="type == 'array'">
-					<el-form-item label="元素类型" prop="types">
-						<el-select v-model="elementType.type" placeholder="请选择元素类型" @change="seletChanges">
+					<!-- 元素类型 -->
+					<el-form-item :label="$t('message.device.formI18nLabel.elementType')" prop="types">
+						<!-- 请选择元素类型 -->
+						<el-select v-model="elementType.type" :placeholder="$t('message.device.formI18nPlaceholder.selectElementType')" @change="seletChanges">
 							<el-option-group v-for="group in typeData" :key="group.label" :label="group.label">
 								<el-option v-for="item in group.options" :key="item.type" :label="item.title" :value="item.type" :disabled="item.type == 'array'" />
 							</el-option-group>
 						</el-select>
 					</el-form-item>
 
-					<el-form-item label="精度" prop="decimals" v-if="types == 'float' || types == 'double'">
-						<el-input v-model="elementType.decimals" placeholder="请输入精度" />
+					<!-- 精度 -->
+					<el-form-item :label="$t('message.device.formI18nLabel.precision')" prop="decimals" v-if="types == 'float' || types == 'double'">
+						<!-- 请输入精度 -->
+						<el-input v-model="elementType.decimals" :placeholder="$t('message.device.formI18nPlaceholder.inputPrecision')" />
 					</el-form-item>
 
-					<el-form-item label="单位" prop="unit" v-if="types == 'int' || types == 'long' || types == 'float' || types == 'double'">
-						<el-input v-model="elementType.unit" placeholder="请输入单位" />
+					<!-- 单位 -->
+					<el-form-item
+						:label="$t('message.device.formI18nLabel.unit')"
+						prop="unit"
+						v-if="types == 'int' || types == 'long' || types == 'float' || types == 'double'"
+					>
+						<!-- 请输入单位 -->
+						<el-input v-model="elementType.unit" :placeholder="$t('message.device.formI18nPlaceholder.inputUnit')" />
 					</el-form-item>
 
-					<el-form-item label="最大长度" prop="maxLength" v-if="types == 'string'">
-						<el-input v-model="elementType.maxLength" placeholder="请输入最大长度" />
+					<!-- 最大长度 -->
+					<el-form-item :label="$t('message.device.formI18nLabel.maxLength')" prop="maxLength" v-if="types == 'string'">
+						<!-- 请输入最大长度 -->
+						<el-input v-model="elementType.maxLength" :placeholder="$t('message.device.formI18nPlaceholder.inputMaxLength')" />
 					</el-form-item>
 
-					<el-form-item label="时间格式" prop="maxLength" v-if="types == 'date'">
-						<el-input v-model="elementType.maxLength" placeholder="请输入时间格式" />
+					<!-- 时间格式 -->
+					<el-form-item :label="$t('message.device.formI18nLabel.timeFormat')" prop="maxLength" v-if="types == 'date'">
+						<!-- 请输入时间格式 -->
+						<el-input v-model="elementType.maxLength" :placeholder="$t('message.device.formI18nPlaceholder.inputTimeFormat')" />
 					</el-form-item>
 
-					<el-form-item label="布尔值" prop="maxLength" v-if="types == 'boolean'">
+					<!-- 布尔值 -->
+					<el-form-item :label="$t('message.device.formI18nLabel.booleanValue')" prop="maxLength" v-if="types == 'boolean'">
 						<div class="input-box flex-row">
-							<el-input v-model="elementType.trueText" placeholder="请输入true时显示的文字" /><span style="margin: 0px 10px">~</span>
-							<el-input v-model="elementType.trueValue" placeholder="请输入布尔值" disabled />
+							<!-- 请输入true时显示的文字 -->
+							<el-input v-model="elementType.trueText" :placeholder="$t('message.device.formI18nPlaceholder.inputTrueText')" /><span
+								style="margin: 0px 10px"
+								>~</span
+							>
+							<!-- 请输入布尔值 -->
+							<el-input v-model="elementType.trueValue" :placeholder="$t('message.device.formI18nPlaceholder.inputBooleanValue')" disabled />
 						</div>
 
 						<div class="input-box flex-row">
-							<el-input v-model="elementType.falseText" placeholder="请输入false时显示的文字" /> <span style="margin: 0px 10px">~</span>
-							<el-input v-model="elementType.falseValue" placeholder="请输入布尔值" disabled />
+							<!-- 请输入false时显示的文字 -->
+							<el-input v-model="elementType.falseText" :placeholder="$t('message.device.formI18nPlaceholder.inputFalseText')" />
+							<span style="margin: 0px 10px">~</span>
+							<!-- 请输入布尔值 -->
+							<el-input v-model="elementType.falseValue" :placeholder="$t('message.device.formI18nPlaceholder.inputBooleanValue')" disabled />
 						</div>
 					</el-form-item>
 
-					<el-form-item label="枚举项" prop="maxLength" v-if="types == 'enum'">
+					<!-- 枚举项 -->
+					<el-form-item :label="$t('message.device.formI18nLabel.enumItems')" prop="maxLength" v-if="types == 'enum'">
 						<div class="input-box flex-row" v-for="(item, index) in enumdata" :key="index">
-							<el-input v-model="item.text" placeholder="请输入枚举文本" /><span style="margin: 0px 10px"><el-icon>
-									<Right />
-								</el-icon></span>
-							<el-input v-model="item.value" placeholder="请输入枚举值" />
+							<!-- 请输入枚举文本 -->
+							<el-input v-model="item.text" :placeholder="$t('message.device.formI18nPlaceholder.inputEnumText')" /><span style="margin: 0px 10px"
+								><el-icon> <Right /> </el-icon
+							></span>
+							<!-- 请输入枚举值 -->
+							<el-input v-model="item.value" :placeholder="$t('message.device.formI18nPlaceholder.inputEnumValue')" />
 							<div class="input-option">
 								<el-icon @click="addEnum" v-if="index == 0">
 									<Plus />
@@ -142,16 +221,20 @@
 					</el-form-item>
 				</div>
 
-
-				<el-form-item label="JSON对象" prop="maxLength" v-if="types == 'object'">
+				<el-form-item :label="$t('message.device.formI18nLabel.jsonObject')" prop="maxLength" v-if="types == 'object'">
 					<div v-for="(item, index) in jsondata" :key="index" class="jslist">
 						<div class="jsonlist">
-							<div>参数标识:{{ item.key }}</div>
-							<div>参数名称:{{ item.name }}</div>
-							<div>数据类型:{{ item.valueType.type }}</div>
+							<!-- 参数标识:{{ item.key }} -->
+							<div>{{ $t('message.device.formI18nLabel.parameterIdentifier') }}:{{ item.key }}</div>
+							<!-- 参数名称:{{ item.name }} -->
+							<div>{{ $t('message.device.formI18nLabel.parameterName') }}:{{ item.name }}</div>
+							<!-- 数据类型:{{ item.valueType.type }} -->
+							<div>{{ $t('message.device.formI18nLabel.dataType') }}:{{ item.valueType.type }}</div>
 							<div class="jsonoption">
-								<el-link type="primary" @click="editjson(index)">编辑</el-link>
-								<el-link type="primary" @click="deljson(index)">删除</el-link>
+								<!-- 编辑 -->
+								<el-link type="primary" @click="editjson(index)">{{ $t('message.tableI18nAction.edit') }}</el-link>
+								<!-- 删除 -->
+								<el-link type="primary" @click="deljson(index)">{{ $t('message.tableI18nAction.delete') }}</el-link>
 							</div>
 						</div>
 					</div>
@@ -161,27 +244,36 @@
 							<el-icon>
 								<Plus />
 							</el-icon>
-							<div>添加参数</div>
+							<!-- 添加参数 -->
+							<div>{{ $t('message.tableI18nAction.addParameter') }}</div>
 						</div>
 					</div>
 				</el-form-item>
 
 				<!--根据数据类型输出不同表单-->
-
-				<el-form-item label="是否只读" prop="accessMode">
+				<!-- 是否只读 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.isReadOnly')" prop="accessMode">
 					<el-radio-group v-model="ruleForm.accessMode">
-						<el-radio :label="1">只读</el-radio>
-						<el-radio :label="0">读写</el-radio>
+						<!-- 1:只读 -->
+						<el-radio :label="1">{{ $t('message.device.formI18nOption.readonly') }}</el-radio>
+						<!-- 0:读写 -->
+						<el-radio :label="0">{{ $t('message.device.formI18nOption.readWrite') }}</el-radio>
 					</el-radio-group>
 				</el-form-item>
-				<el-form-item label="属性定义描述" prop="desc">
-					<el-input v-model="ruleForm.desc" type="textarea" placeholder="请输入属性定义描述"></el-input>
+				<!-- 属性定义描述 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.propertyDescription')" prop="desc">
+					<!-- 请输入属性定义描述 -->
+					<el-input v-model="ruleForm.desc" type="textarea" :placeholder="$t('message.device.formI18nPlaceholder.inputDescription')"></el-input>
 				</el-form-item>
 			</el-form>
 			<template #footer>
 				<span class="dialog-footer">
-					<el-button @click="onCancel">取 消</el-button>
-					<el-button type="primary" @click="onSubmit">{{ ruleForm.id !== 0 ? '修 改' : '添 加' }}</el-button>
+					<!-- 取消 -->
+					<el-button @click="onCancel">{{ $t('message.tableI18nAction.cancel') }}</el-button>
+					<!-- 编辑/添加 -->
+					<el-button type="primary" @click="onSubmit">{{
+						ruleForm.id !== 0 ? $t('message.tableI18nAction.edit') : $t('message.tableI18nAction.add')
+					}}</el-button>
 				</span>
 			</template>
 		</el-dialog>
@@ -189,32 +281,23 @@
 	</div>
 </template>
 
-<script lang="ts">
-import { reactive, toRefs, defineComponent, ref, unref } from 'vue';
-import api from '/@/api/device';
-import { Plus, Minus, Right } from '@element-plus/icons-vue';
-import EditOption from './editOption.vue';
-import { ElMessage } from 'element-plus';
-import { validateNoSpace } from '/@/utils/validator';
-
-interface RuleFormState {
-	id: number;
-	productKey: string;
-  deviceKey: string;
-	name: string;
-	dictType: string;
-	valueType: Object;
-	status: number;
-	desc: string;
-}
-interface DicState {
-	isShowDialog: boolean;
-	ruleForm: RuleFormState;
-	typeData: RuleFormState[];
-	rules: {};
-}
+<script lang="ts" setup>
+import { computed, ref, unref, toRaw } from 'vue'
+import api from '/@/api/device'
+import { Plus, Minus, Right } from '@element-plus/icons-vue'
+import EditOption from './editOption.vue'
+import { ElMessage } from 'element-plus'
+import { validateNoSpace } from '/@/utils/validator'
+import { useI18n } from 'vue-i18n'
+
+// 国际化
+const { locale, t } = useI18n()
 
-const valueType = {
+const currentLocale = computed(() => locale.value)
+
+const emit = defineEmits(['typeList'])
+
+const valueTypeModule = {
 	type: '',
 	maxLength: '',
 	trueText: '是',
@@ -223,268 +306,254 @@ const valueType = {
 	falseValue: false,
 }
 
-export default defineComponent({
-	name: 'deviceEditPro',
-	components: { Plus, Minus, Right, EditOption },
-	setup(prop, { emit }) {
-		const formRef = ref<HTMLElement | null>(null);
-		const editOptionRef = ref();
-		const state = reactive<DicState>({
-			isShowDialog: false,
-			typeData: [], //
-			type: '',
-			types: '',
-			productKey: '',
-			valueType: JSON.parse(JSON.stringify(valueType)),
-			elementType: JSON.parse(JSON.stringify(valueType)),
-			enumdata: [
-				{
-					text: '',
-					value: '',
-				},
-			],
-
-			jsondata: [],
-
-			ruleForm: {
-				id: 0,
-				productKey: '',
-        deviceKey: '',
-				name: '',
-				key: '',
-				transportProtocol: '',
-				accessMode: 1,
-				status: 1,
-				valueType: JSON.parse(JSON.stringify(valueType)),
-				desc: '',
-			},
-			rules: {
-				name: [{ required: true, message: '属性定义名称不能为空', trigger: 'blur' },
-				{ max: 32, message: '属性定义名称不能超过32个字符', trigger: 'blur' },
-				{ validator: validateNoSpace, message: '属性定义名称不能包含空格', trigger: 'blur' }
-				],
-				key: [{ required: true, message: '属性定义标识不能为空', trigger: 'blur' }],
-				accessMode: [{ required: true, message: '请选择是否只读', trigger: 'blur' }],
-				type: [{ required: true, message: '请选择数据类型', trigger: 'blur' }],
-			},
-		});
-
-		// 打开弹窗
-		const openDialog = (row: RuleFormState | null, productKey: string | null) => {
-      resetForm();
-
-			api.product.getDataType({ status: -1 }).then((res: any) => {
-				const datat = Object.values(res.dataType);
-				datat.forEach((item, index) => {
-					if (index == 0) {
-						datat[index]['label'] = '基础类型';
-						datat[index]['options'] = item;
-					} else {
-						datat[index]['label'] = '扩展类型';
-						datat[index]['options'] = item;
-					}
-				});
-				state.typeData = datat || [];
-			});
-			state.ruleForm = row;
-			if (row.valueType) {
-				state.ruleForm = row;
-
-				state.productKey = productKey;
-        state.deviceKey = row.deviceKey;
-        state.valueType = row.valueType;
-				state.ruleForm.valueType.type = row.valueType.type;
-				state.ruleForm.type = row.valueType.type;
-				state.type = row.valueType.type;
-				state.ruleForm.accessMode = row.accessMode;
-				if (row.valueType.elementType) {
-					state.elementType = row.valueType.elementType;
-					state.types = row.valueType.elementType.type;
-				}
-
-				if (row.type == 'enum') {
-					state.enumdata = row.valueType.elements;
-				}
-
-				if (row.type == 'object') {
-					state.jsondata = JSON.parse(JSON.stringify(row.valueType.properties));
-				}
-
-				if (row.type == 'array' && state.types == 'enum') {
-					state.enumdata = row.valueType.elementType.elements
-				}
-				if (row.type == 'array' && state.types == 'object') {
-					state.jsondata = JSON.parse(JSON.stringify(row.valueType.elementType.properties));
-				}
+const formRef = ref<HTMLElement | null>(null)
+const editOptionRef = ref()
+const rules = computed(() => ({
+	name: [
+		// 属性定义名称不能为空
+		{ required: true, message: t('message.device.rules.propertyName'), trigger: 'blur' },
+		// 属性定义名称不能超过32个字符
+		{ max: 32, message: t('message.device.rules.propertyNameMax32'), trigger: 'blur' },
+		// 属性定义名称不能包含空格
+		{ validator: validateNoSpace, message: t('message.device.rules.propertyNameValidator'), trigger: 'blur' },
+	],
+	// 属性定义标识不能为空
+	key: [{ required: true, message: t('message.device.rules.propertyKey'), trigger: 'blur' }],
+	// 请选择是否只读
+	accessMode: [{ required: true, message: t('message.device.rules.accessMode'), trigger: 'blur' }],
+	// 请选择数据类型
+	type: [{ required: true, message: t('message.device.rules.selectDataType'), trigger: 'blur' }],
+}))
+const ruleForm = ref<any>({
+	id: 0,
+	productKey: '',
+	deviceKey: '',
+	name: '',
+	key: '',
+	transportProtocol: '',
+	accessMode: 1,
+	status: 1,
+	valueType: JSON.parse(JSON.stringify(valueTypeModule)),
+	desc: '',
+})
+
+const jsondata = ref<any>([])
+const isShowDialog = ref(false)
+const elementType = ref(JSON.parse(JSON.stringify(valueTypeModule)))
+const valueType = ref(JSON.parse(JSON.stringify(valueTypeModule)))
+const typeData = ref([])
+const enumdata = ref([
+	{
+		text: '',
+		value: '',
+	},
+])
+const type = ref('')
+const types = ref('')
+const productKey = ref<any>('')
+
+// 打开弹窗
+const openDialog = (row: any, productKeys: string | null) => {
+	resetForm()
+
+	api.product.getDataType({ status: -1 }).then((res: any) => {
+		const datat: any = Object.values(res.dataType)
+		datat.forEach((item: any, index: any) => {
+			if (index == 0) {
+				datat[index]['label'] = t('message.device.baseType')
+				datat[index]['options'] = item
+			} else {
+				datat[index]['label'] = t('message.device.extensionType')
+				datat[index]['options'] = item
 			}
+		})
+		typeData.value = datat || []
+	})
+	ruleForm.value = row
+	if (row.valueType) {
+		ruleForm.value = row
+
+		productKey.value = productKeys
+		// deviceKey.value = row.deviceKey
+		valueType.value = row.valueType
+		ruleForm.value.valueType.type = row.valueType.type
+		ruleForm.value.type = row.valueType.type
+		type.value = row.valueType.type
+		ruleForm.value.accessMode = row.accessMode
+		if (row.valueType.elementType) {
+			elementType.value = row.valueType.elementType
+			types.value = row.valueType.elementType.type
+		}
 
+		if (row.type == 'enum') {
+			enumdata.value = row.valueType.elements
+		}
 
-			state.isShowDialog = true;
-		};
-		const resetForm = () => {
-			state.ruleForm = {
-				name: '',
-				key: '',
-				transportProtocol: '',
-				accessMode: 1,
-				status: 1,
-				valueType: JSON.parse(JSON.stringify(valueType)),
-				desc: '',
-
-			};
-			state.type = '';
-			state.types = '';
-			state.valueType = JSON.parse(JSON.stringify(valueType));
-			state.elementType = JSON.parse(JSON.stringify(valueType));
-			state.jsondata = [];
-			state.enumdata = [{
-				text: '',
-				value: '',
-			},];
-		};
-
-		const seletChange = (val) => {
-			state.type = val;
-			state.ruleForm.type = val;
-		};
-		const seletChanges = (val) => {
-			state.types = val;
-		};
-
-		const addEnum = () => {
-			state.enumdata.push({
-				text: '',
-				value: '',
-			});
-		};
-		const delEnum = (index) => {
-			state.enumdata.splice(index, 1);
-		};
-
-		const editjson = (index) => {
-
-			editOptionRef.value.openDialog(state.jsondata[index]);
+		if (row.type == 'object') {
+			jsondata.value = JSON.parse(JSON.stringify(row.valueType.properties))
+		}
 
+		if (row.type == 'array' && types.value == 'enum') {
+			enumdata.value = row.valueType.elementType.elements
 		}
-		const deljson = (index) => {
-			state.jsondata.splice(index, 1);
+		if (row.type == 'array' && types.value == 'object') {
+			jsondata.value = JSON.parse(JSON.stringify(row.valueType.elementType.properties))
 		}
+	}
 
-		const addJson = () => {
-			editOptionRef.value.openDialog({ productKey: '', id: 0 });
-		};
-		const getOptionData = (data) => {
-			state.jsondata.push(data);
-		};
-		// 关闭弹窗
-		const closeDialog = () => {
-			state.isShowDialog = false;
-		};
-		// 取消
-		const onCancel = () => {
-			closeDialog();
-		};
-		// 新增
-		const onSubmit = () => {
-			const formWrap = unref(formRef) as any;
-			if (!formWrap) return;
-			formWrap.validate((valid: boolean) => {
-				if (valid) {
-					if (state.ruleForm.id !== 0) {
-						//修改
-						if (state.type == 'enum') {
-							state.valueType.elements = state.enumdata;
-						}
+	isShowDialog.value = true
+}
+const resetForm = () => {
+	ruleForm.value = {
+		name: '',
+		key: '',
+		transportProtocol: '',
+		accessMode: 1,
+		status: 1,
+		valueType: JSON.parse(JSON.stringify(valueType.value)),
+		desc: '',
+	}
+	type.value = ''
+	types.value = ''
+	valueType.value = JSON.parse(JSON.stringify(valueType.value))
+	elementType.value = JSON.parse(JSON.stringify(valueType.value))
+	jsondata.value = []
+	enumdata.value = [
+		{
+			text: '',
+			value: '',
+		},
+	]
+}
 
-						if (state.type == 'object') {
-							state.valueType.properties = state.jsondata;
-						}
+const seletChange = (val: string) => {
+	type.value = val
+	ruleForm.value.type = val
+}
+const seletChanges = (val: string) => {
+	types.value = val
+}
 
-						if (state.type == 'array') {
-							state.valueType.elementType = state.elementType;
-							//如果是选中数组,并选择了枚举
-							if (state.types == 'enum') {
-								state.valueType.elementType = {
-									elements: state.enumdata,
-									type: 'enum'
-								}
-							}
-							//如果是选中数组,并选择了object
-							if (state.types == 'object') {
-								state.valueType.elementType = {
-									properties: state.jsondata,
-									type: 'object'
-								}
-							}
-						}
+const addEnum = () => {
+	enumdata.value.push({
+		text: '',
+		value: '',
+	})
+}
+const delEnum = (index: number) => {
+	enumdata.value.splice(index, 1)
+}
 
-						state.ruleForm.valueType = state.valueType;
-						state.ruleForm.productKey = state.productKey
-						api.model.propertyedit(state.ruleForm).then(() => {
-							ElMessage.success('属性定义类型修改成功');
-							closeDialog(); // 关闭弹窗
-							emit('typeList');
-						});
-					} else {
-						//添加
-						if (state.type == 'enum') {
-							state.valueType.elements = state.enumdata;
-						}
+const editjson = (index: number) => {
+	editOptionRef.value.openDialog(jsondata.value[index])
+}
+const deljson = (index: number) => {
+	jsondata.value.splice(index, 1)
+}
 
-						if (state.type == 'object') {
-							state.valueType.properties = state.jsondata;
-						}
+const addJson = () => {
+	editOptionRef.value.openDialog({ productKey: '', id: 0 })
+}
+const getOptionData = (data: any) => {
+	jsondata.value.push(data)
+}
+// 关闭弹窗
+const closeDialog = () => {
+	isShowDialog.value = false
+}
+// 取消
+const onCancel = () => {
+	closeDialog()
+}
+// 新增
+const onSubmit = () => {
+	const formWrap = unref(formRef) as any
+	if (!formWrap) return
+	formWrap.validate((valid: boolean) => {
+		if (valid) {
+			if (ruleForm.value.id !== 0) {
+				//修改
+				if (type.value == 'enum') {
+					valueType.value.elements = enumdata.value
+				}
+
+				if (type.value == 'object') {
+					valueType.value.properties = jsondata.value
+				}
 
-						if (state.type == 'array') {
-							state.valueType.elementType = state.elementType;
-							//如果是选中数组,并选择了枚举
-							if (state.types == 'enum') {
-								state.valueType.elementType = {
-									elements: state.enumdata,
-									type: 'enum'
-								}
-							}
-							//如果是选中数组,并选择了object
-							if (state.types == 'object') {
-								state.valueType.elementType = {
-									properties: state.jsondata,
-									type: 'object'
-								}
-							}
+				if (type.value == 'array') {
+					valueType.value.elementType = elementType.value
+					//如果是选中数组,并选择了枚举
+					if (types.value == 'enum') {
+						valueType.value.elementType = {
+							elements: enumdata.value,
+							type: 'enum',
 						}
+					}
+					//如果是选中数组,并选择了object
+					if (types.value == 'object') {
+						valueType.value.elementType = {
+							properties: jsondata.value,
+							type: 'object',
+						}
+					}
+				}
+
+				ruleForm.value.valueType = valueType.value
+				ruleForm.value.productKey = productKey.value
+				api.model.propertyedit(ruleForm.value).then(() => {
+					ElMessage.success(t('message.tableI18nConfirm.editSuccess'))
+					closeDialog() // 关闭弹窗
+					emit('typeList')
+				})
+			} else {
+				//添加
+				if (type.value == 'enum') {
+					valueType.value.elements = enumdata.value
+				}
+
+				if (type.value == 'object') {
+					valueType.value.properties = jsondata.value
+				}
 
-						state.ruleForm.valueType = state.valueType;
-						api.model.propertyadd(state.ruleForm).then(() => {
-							ElMessage.success('属性定义类型添加成功');
-							closeDialog(); // 关闭弹窗
-							emit('typeList');
-						});
+				if (type.value == 'array') {
+					valueType.value.elementType = elementType.value
+					//如果是选中数组,并选择了枚举
+					if (types.value == 'enum') {
+						valueType.value.elementType = {
+							elements: enumdata.value,
+							type: 'enum',
+						}
+					}
+					//如果是选中数组,并选择了object
+					if (types.value == 'object') {
+						valueType.value.elementType = {
+							properties: jsondata.value,
+							type: 'object',
+						}
 					}
 				}
-			});
-		};
-
-		return {
-			editOptionRef,
-			getOptionData,
-			openDialog,
-			deljson,
-			editjson,
-			addEnum,
-			delEnum,
-			addJson,
-			seletChange,
-			seletChanges,
-			closeDialog,
-			onCancel,
-			onSubmit,
-			formRef,
-			...toRefs(state),
-		};
-	},
-});
+				// 确保 ruleForm.value.valueType 不为空,否则初始化为空对象
+				if (!ruleForm.value.valueType) {
+					ruleForm.value.valueType = {}
+				}
+				Object.assign(ruleForm.value.valueType, valueType.value)
+
+				// 深拷贝确保响应式对象正确序列化
+				const submitData = JSON.parse(JSON.stringify(ruleForm.value))
+				api.model.propertyadd(submitData).then(() => {
+					ElMessage.success(t('message.tableI18nConfirm.addSuccess'))
+					closeDialog() // 关闭弹窗
+					emit('typeList')
+				})
+			}
+		}
+	})
+}
+
+defineExpose({ openDialog })
 </script>
-<style scoped>
+<style scoped lang="scss">
 .input-box {
 	display: flex;
 	flex-direction: row;
@@ -524,8 +593,6 @@ export default defineComponent({
 	justify-content: space-between;
 }
 
-.jsonoption {}
-
 .jsonoption a {
 	margin: 0px 10px;
 }

+ 68 - 36
src/views/iot/device/product/component/editEvent.vue

@@ -1,31 +1,46 @@
 <template>
 	<div class="system-edit-dic-container">
-		<el-dialog :title="(ruleForm.id !== 0 ? '修改' : '添加') + '事件定义'" v-model="isShowDialog" width="769px">
-			<el-form :model="ruleForm" ref="formRef" :rules="rules" label-width="120px">
-				<el-form-item label="事件定义标识" prop="key">
-					<el-input v-model="ruleForm.key" placeholder="请输入事件定义标识" :disabled="ruleForm.id !== 0 ? true : false" />
+		<!-- 编辑事件定义 / 新增事件定义 -->
+		<el-dialog :title="ruleForm.id !== 0 ? $t('message.device.dialogI18n.editEventDefinition') : $t('message.device.dialogI18n.addEventDefinition')" v-model="isShowDialog" width="769px">
+			<el-form :model="ruleForm" ref="formRef" :rules="rules" :label-width="currentLocale == 'en' ? '150px' : '120px'">
+				<!-- 事件定义标识 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.eventKey')" prop="key">
+					<!-- 请输入事件定义标识 -->
+					<el-input v-model="ruleForm.key" :placeholder="$t('message.device.formI18nPlaceholder.eventKey')" :disabled="ruleForm.id !== 0 ? true : false" />
 				</el-form-item>
-				<el-form-item label="事件定义名称" prop="name">
-					<el-input v-model="ruleForm.name" placeholder="请输入事件定义名称" />
+				<!-- 事件定义名称 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.eventName')" prop="name">
+					<!-- 请输入事件定义名称 -->
+					<el-input v-model="ruleForm.name" :placeholder="$t('message.device.formI18nPlaceholder.eventName')" />
 				</el-form-item>
 
-				<el-form-item label="事件类型" prop="level">
+				<!-- 事件类型 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.eventType')" prop="level">
 					<el-radio-group v-model="ruleForm.level">
-						<el-radio :label="0">信息</el-radio>
-						<el-radio :label="1">告警</el-radio>
-						<el-radio :label="2">故障</el-radio>
+						<!-- 信息 -->
+						<el-radio :label="0">{{ $t('message.device.formI18nOption.info') }}</el-radio>
+						<!-- 告警 -->
+						<el-radio :label="1">{{ $t('message.device.formI18nOption.warning') }}</el-radio>
+						<!-- 故障 -->
+						<el-radio :label="2">{{ $t('message.device.formI18nOption.fault') }}</el-radio>
 					</el-radio-group>
 				</el-form-item>
 
-				<el-form-item label="输出参数" prop="maxLength">
+				<!-- 输出参数 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.functionOutput')" prop="maxLength">
 					<div v-for="(item, index) in outputsdata" :key="index" class="jslist">
 						<div class="jsonlist">
-							<div>参数标识:{{ item.key }}</div>
-							<div>参数名称:{{ item.name }}</div>
-							<div>数据类型:{{ item.valueType.type }}</div>
+							<!-- 参数标识 -->
+							<div>{{ $t('message.device.formI18nLabel.parameterIdentifier') }}:{{ item.key }}</div>
+							<!-- 参数名称 -->
+							<div>{{ $t('message.device.formI18nLabel.parameterName') }}:{{ item.name }}</div>
+							<!-- 数据类型 -->
+							<div>{{ $t('message.device.formI18nLabel.dataType') }}:{{ item.valueType.type }}</div>
 							<div class="jsonoption">
-								<el-link type="primary" @click="editjson(index, 'fun')">编辑</el-link>
-								<el-link type="primary" @click="deljson(index, 'fun')">删除</el-link>
+								<!-- 编辑 -->
+								<el-link type="primary" @click="editjson(index, 'fun')">{{ $t('message.tableI18nAction.edit') }}</el-link>
+								<!-- 删除 -->
+								<el-link type="primary" @click="deljson(index, 'fun')">{{ $t('message.tableI18nAction.delete') }}</el-link>
 							</div>
 						</div>
 					</div>
@@ -35,19 +50,24 @@
 							<el-icon>
 								<Plus />
 							</el-icon>
-							<div>添加参数</div>
+							<!-- 添加参数 -->
+							<div>{{ $t('message.device.formI18nButton.addParameter') }}</div>
 						</div>
 					</div>
 				</el-form-item>
 
-				<el-form-item label="事件定义描述	" prop="desc">
-					<el-input v-model="ruleForm.desc" type="textarea" placeholder="请输入事件定义描述"></el-input>
+				<!-- 事件定义描述 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.eventDescription')" prop="desc">
+					<!-- 请输入事件定义描述 -->
+					<el-input v-model="ruleForm.desc" type="textarea" :placeholder="$t('message.device.formI18nPlaceholder.eventDescription')"></el-input>
 				</el-form-item>
 			</el-form>
 			<template #footer>
 				<span class="dialog-footer">
-					<el-button @click="onCancel">取 消</el-button>
-					<el-button type="primary" @click="onSubmit">{{ ruleForm.id !== 0 ? '修 改' : '添 加' }}</el-button>
+					<!-- 取消 -->
+					<el-button @click="onCancel">{{ $t('message.tableI18nAction.cancel') }}</el-button>
+					<!-- 编辑 / 新增 -->
+					<el-button type="primary" @click="onSubmit">{{ ruleForm.id !== 0 ? $t('message.tableI18nAction.edit') : $t('message.tableI18nAction.add') }}</el-button>
 				</span>
 			</template>
 		</el-dialog>
@@ -56,12 +76,13 @@
 </template>
 
 <script lang="ts">
-import { reactive, toRefs, defineComponent, ref, unref } from 'vue';
+import { reactive, toRefs, defineComponent, ref, unref, computed } from 'vue';
 import api from '/@/api/device';
 import { Plus, Minus, Right } from '@element-plus/icons-vue';
 import EditOption from './editOption.vue';
 import { validateNoSpace } from '/@/utils/validator';
 import { ElMessage } from 'element-plus';
+import { useI18n } from 'vue-i18n';
 
 interface RuleFormState {
 	id?: number;
@@ -85,7 +106,7 @@ interface DicState {
 	enumdata: any[];
 	outputsdata: any[];
 	elementType: any;
-	rules: any;
+	// rules: any;
 }
 
 const form = {
@@ -105,6 +126,24 @@ export default defineComponent({
 	setup(prop, { emit }) {
 		const formRef = ref<HTMLElement | null>(null);
 		const editOptionRef = ref();
+		const { t, locale } = useI18n();
+		const currentLocale = computed(() => locale.value);	
+		const rules = computed(() => {
+			return {
+				name: [
+					// 事件定义名称不能为空
+					{ required: true, message: t('message.device.rules.eventName'), trigger: 'blur' },
+					// 事件定义名称不能超过32个字符
+					{ max: 32, message: t('message.device.rules.eventNameMax32'), trigger: 'blur' },
+					// 事件定义名称不能包含空格
+					{ validator: validateNoSpace, message: t('message.device.rules.eventNameValidator'), trigger: 'blur' }
+				],
+				// 事件定义标识不能为空
+				key: [{ required: true, message: t('message.device.rules.eventKey'), trigger: 'blur' }],
+				// 请选择数据类型
+				type: [{ required: true, message: t('message.device.rules.eventType'), trigger: 'blur' }],
+			}
+		});
 		const state = reactive<DicState>({
 			isShowDialog: false,
 			typeData: [], //
@@ -123,16 +162,7 @@ export default defineComponent({
 			],
 			jsondata: [],
 			outputsdata: [],
-			ruleForm: JSON.parse(JSON.stringify(form)),
-			rules: {
-				name: [
-					{ required: true, message: '事件定义名称不能为空', trigger: 'blur' },
-					{ max: 32, message: '事件定义名称不能超过32个字符', trigger: 'blur' },
-					{ validator: validateNoSpace, message: '事件定义名称不能包含空格', trigger: 'blur' }
-				],
-				key: [{ required: true, message: '事件定义标识不能为空', trigger: 'blur' }],
-				type: [{ required: true, message: '请选择数据类型', trigger: 'blur' }],
-			},
+			ruleForm: JSON.parse(JSON.stringify(form))
 		});
 
 		// 打开弹窗
@@ -143,9 +173,11 @@ export default defineComponent({
 				const datat: any[] = Object.values(res.dataType);
 				datat.forEach((item, index) => {
 					if (index == 0) {
+						// 基础类型
 						datat[index]['label'] = '基础类型';
 						datat[index]['options'] = item;
 					} else {
+						// 扩展类型
 						datat[index]['label'] = '扩展类型';
 						datat[index]['options'] = item;
 					}
@@ -236,6 +268,7 @@ export default defineComponent({
 					}
 
 					theApi(state.ruleForm).then(() => {
+						// 事件定义类型操作成功
 						ElMessage.success('事件定义类型操作成功');
 						closeDialog(); // 关闭弹窗
 						emit('typeList');
@@ -245,6 +278,8 @@ export default defineComponent({
 		};
 
 		return {
+			rules,
+			currentLocale,
 			editOptionRef,
 			getOptionData,
 			openDialog,
@@ -303,9 +338,6 @@ export default defineComponent({
 	flex-direction: row;
 	justify-content: space-between;
 }
-
-.jsonoption {}
-
 .jsonoption a {
 	margin: 0px 10px;
 }

+ 202 - 240
src/views/iot/device/product/component/editFun.vue

@@ -1,23 +1,34 @@
 <template>
 	<div class="system-edit-dic-container">
-		<el-dialog :title="(ruleForm.id !== 0 ? '修改' : '添加') + '功能定义'" v-model="isShowDialog" width="769px">
+		<!-- 新增功能定义/编辑功能定义 -->
+		<el-dialog :title="ruleForm.id !== 0 ? $t('message.device.dialogI18n.editFunctionDefinition') : $t('message.device.dialogI18n.addFunctionDefinition')" v-model="isShowDialog" width="769px">
 			<el-form :model="ruleForm" ref="formRef" :rules="rules" label-width="120px">
-				<el-form-item label="功能定义标识" prop="key">
-					<el-input v-model="ruleForm.key" placeholder="请输入功能定义标识" :disabled="ruleForm.id !== 0 ? true : false" />
+				<!-- 功能定义标识 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.functionKey')" prop="key">
+					<!-- 请输入功能定义标识 -->
+					<el-input v-model="ruleForm.key" :placeholder="$t('message.device.formI18nPlaceholder.functionKey')" :disabled="ruleForm.id !== 0 ? true : false" />
 				</el-form-item>
-				<el-form-item label="功能定义名称" prop="name">
-					<el-input v-model="ruleForm.name" placeholder="请输入功能定义名称" />
+				<!-- 功能定义名称 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.functionName')" prop="name">
+					<!-- 请输入功能定义名称 -->
+					<el-input v-model="ruleForm.name" :placeholder="$t('message.device.formI18nPlaceholder.functionName')" />
 				</el-form-item>
 
-				<el-form-item label="输入参数" prop="maxLength">
+				<!-- 输入参数 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.functionInput')" prop="maxLength">
 					<div v-for="(item, index) in inputsdata" :key="index" class="jslist">
 						<div class="jsonlist">
-							<div>参数标识:{{ item.key }}</div>
-							<div>参数名称:{{ item.name }}</div>
-							<div>数据类型:{{ item.valueType.type }}</div>
+							<!-- 参数标识 -->
+							<div>{{ $t('message.device.formI18nLabel.parameterIdentifier') }}:{{ item.key }}</div>
+							<!-- 参数名称 -->
+							<div>{{ $t('message.device.formI18nLabel.parameterName') }}:{{ item.name }}</div>
+							<!-- 数据类型 -->
+							<div>{{ $t('message.device.formI18nLabel.dataType') }}:{{ item.valueType.type }}</div>
 							<div class="jsonoption">
-								<el-link type="primary" @click="editjson(index, 'fun')">编辑</el-link>
-								<el-link type="primary" @click="deljson(index, 'fun')">删除</el-link>
+								<!-- 编辑 -->
+								<el-link type="primary" @click="editjson(index, 'fun')">{{ $t('message.tableI18nAction.edit') }}</el-link>
+								<!-- 删除 -->
+								<el-link type="primary" @click="deljson(index, 'fun')">{{ $t('message.tableI18nAction.delete') }}</el-link>
 							</div>
 						</div>
 					</div>
@@ -27,20 +38,27 @@
 							<el-icon>
 								<Plus />
 							</el-icon>
-							<div>添加参数</div>
+							<!-- 添加参数 -->
+							<div>{{ $t('message.device.formI18nButton.addParameter') }}</div>
 						</div>
 					</div>
 				</el-form-item>
 
-				<el-form-item label="输出参数" prop="">
+				<!-- 输出参数 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.functionOutput')" prop="">
 					<div v-for="(item, index) in outputsdata" :key="index" class="jslist">
 						<div class="jsonlist">
-							<div>参数标识:{{ item.key }}</div>
-							<div>参数名称:{{ item.name }}</div>
-							<div>数据类型:{{ item.valueType.type }}</div>
+							<!-- 参数标识 -->
+							<div>{{ $t('message.device.formI18nLabel.parameterIdentifier') }}:{{ item.key }}</div>
+							<!-- 参数名称 -->
+							<div>{{ $t('message.device.formI18nLabel.parameterName') }}:{{ item.name }}</div>
+							<!-- 数据类型 -->
+							<div>{{ $t('message.device.formI18nLabel.dataType') }}:{{ item.valueType.type }}</div>
 							<div class="jsonoption">
-								<el-link type="primary" @click="editjsonOut(index)">编辑</el-link>
-								<el-link type="primary" @click="deljsonOut(index, 'fun')">删除</el-link>
+								<!-- 编辑 -->
+								<el-link type="primary" @click="editjsonOut(index)">{{ $t('message.tableI18nAction.edit') }}</el-link>
+								<!-- 删除 -->
+								<el-link type="primary" @click="deljsonOut(index, 'fun')">{{ $t('message.tableI18nAction.delete') }}</el-link>
 							</div>
 						</div>
 					</div>
@@ -50,19 +68,23 @@
 							<el-icon>
 								<Plus />
 							</el-icon>
-							<div>添加参数</div>
+							<!-- 添加参数 -->
+							<div>{{ $t('message.device.formI18nButton.addParameter') }}</div>
 						</div>
 					</div>
 				</el-form-item>
-
-				<el-form-item label="功能定义描述	" prop="desc">
-					<el-input v-model="ruleForm.desc" type="textarea" placeholder="请输入功能定义描述"></el-input>
+				<!-- 功能定义描述 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.functionDescription')" prop="desc">
+					<!-- 请输入功能定义描述 -->
+					<el-input v-model="ruleForm.desc" type="textarea" :placeholder="$t('message.device.formI18nPlaceholder.functionDescription')"></el-input>
 				</el-form-item>
 			</el-form>
 			<template #footer>
 				<span class="dialog-footer">
-					<el-button @click="onCancel">取 消</el-button>
-					<el-button type="primary" @click="onSubmit">{{ ruleForm.id !== 0 ? '修 改' : '添 加' }}</el-button>
+					<!-- 取消 -->
+					<el-button @click="onCancel">{{ $t('message.tableI18nAction.cancel') }}</el-button>
+					<!-- 编辑 / 新增 -->
+					<el-button type="primary" @click="onSubmit">{{ ruleForm.id !== 0 ? $t('message.tableI18nAction.edit') : $t('message.tableI18nAction.add') }}</el-button>
 				</span>
 			</template>
 		</el-dialog>
@@ -71,42 +93,29 @@
 	</div>
 </template>
 
-<script lang="ts">
-import { reactive, toRefs, defineComponent, ref, unref } from 'vue';
-import api from '/@/api/device';
-import { Plus, Minus, Right } from '@element-plus/icons-vue';
-import EditOption from './editOption.vue';
-import { validateNoSpace } from '/@/utils/validator';
+<script lang="ts" setup>
+import { reactive, toRefs, defineComponent, ref, unref, computed } from 'vue'
+import api from '/@/api/device'
+import { Plus, Minus, Right } from '@element-plus/icons-vue'
+import EditOption from './editOption.vue'
+import { validateNoSpace } from '/@/utils/validator'
+import { ElMessage } from 'element-plus'
+import { useI18n } from 'vue-i18n';
 
-import { ElMessage } from 'element-plus';
+const { locale, t } = useI18n();
 
 interface RuleFormState {
-	id?: number;
-	key: string;
-	productKey: string;
-	name: string;
-	type: string;
-	dictType: string;
-	valueType: Object;
-	inputs: any;
-	outputs: any;
-	status: number;
-	desc: string;
-}
-interface DicState {
-	isShowDialog: boolean;
-	productKey: string;
-	type: string;
-	types: string;
-	ruleForm: RuleFormState;
-	typeData: any[];
-	inputsdata: any[];
-	outputsdata: any[];
-	jsondata: any[];
-	elementType: any;
-	enumdata: any;
-	valueType: any;
-	rules: {};
+	id?: number
+	key: string
+	productKey: string
+	name: string
+	type: string
+	dictType: string
+	valueType: Object
+	inputs: any
+	outputs: any
+	status: number
+	desc: string
 }
 
 const form = {
@@ -125,199 +134,154 @@ const form = {
 	desc: '',
 }
 
-export default defineComponent({
-	name: 'deviceEditPro',
-	components: { Plus, Minus, Right, EditOption },
-	setup(prop, { emit }) {
-		const formRef = ref<HTMLElement | null>(null);
-		const editOptionRef = ref();
-		const editOptionOutRef = ref();
-		const state = reactive<DicState>({
-			isShowDialog: false,
-			typeData: [], //
-			type: '',
-			types: '',
-			productKey: '',
-			valueType: {
-				type: '',
-				maxLength: '',
-				trueText: '是',
-				trueValue: 'true',
-				falseText: '否',
-				falseValue: 'false',
-			},
-			elementType: {
-				type: '',
-				maxLength: '',
-			},
-			enumdata: [
-				{
-					text: '',
-					value: '',
-				},
-			],
-			jsondata: [],
-			inputsdata: [],
-			outputsdata: [],
-			ruleForm: JSON.parse(JSON.stringify(form)),
-			rules: {
-				name: [{ required: true, message: '功能定义名称不能为空', trigger: 'blur' },
-				{ max: 32, message: '功能定义名称不能超过32个字符', trigger: 'blur' },
-				{ validator: validateNoSpace, message: '功能定义名称不能包含空格', trigger: 'blur' }
-				],
-				key: [{ required: true, message: '功能定义标识不能为空', trigger: 'blur' }],
-				type: [{ required: true, message: '请选择数据类型', trigger: 'blur' }],
-			},
-		});
-
-		// 打开弹窗
-		const openDialog = (row: RuleFormState, productKey: string) => {
-			resetForm();
-			state.ruleForm = row;
-			state.productKey = productKey;
-			state.inputsdata = row.inputs || [];
-			state.outputsdata = row.outputs || [];
-			state.isShowDialog = true;
-
-		};
-		const resetForm = () => {
-			state.ruleForm = JSON.parse(JSON.stringify(form))
-			state.type = '';
-			state.types = '';
-			state.inputsdata = [];
-			state.outputsdata = [];
-			state.elementType = [];
-			state.valueType = {};
-		};
-
-		const seletChange = (val: string) => {
-			state.type = val;
-			state.ruleForm.type = val;
+const emit = defineEmits(['typeList'])
 
-		};
-		const seletChanges = (val: string) => {
-			state.types = val;
-		};
+const formRef = ref<HTMLElement | null>(null)
+const editOptionRef = ref()
+const editOptionOutRef = ref()
+const isShowDialog = ref(false)
+const type = ref('')
+const types = ref('')
+const productKey = ref('')
+// const valueType = ref<any>({
+// 	type: '',
+// 	maxLength: '',
+// 	trueText: '是',
+// 	trueValue: 'true',
+// 	falseText: '否',
+// 	falseValue: 'false',
+// })
+const elementType = ref({
+	type: '',
+	maxLength: '',
+})
+const jsondata = ref<any>([])
+const inputsdata = ref<any>([])
+const outputsdata = ref<any>([])
+const ruleForm = ref(JSON.parse(JSON.stringify(form)))
+const rules = computed(() => ({
+	name: [
+		// 功能定义名称不能为空
+		{ required: true, message: t('message.device.rules.functionName'), trigger: 'blur' },
+		// 功能定义名称不能超过32个字符
+		{ max: 32, message: t('message.device.rules.functionNameMax32'), trigger: 'blur' },
+		// 功能定义名称不能包含空格
+		{ validator: validateNoSpace, message: t('message.device.rules.functionNameValidator'), trigger: 'blur' },
+	],
+	// 功能定义标识不能为空
+	key: [{ required: true, message: t('message.device.rules.functionKey'), trigger: 'blur' }],
+	// 请选择数据类型
+	type: [{ required: true, message: t('message.device.rules.functionType'), trigger: 'blur' }],
+}))
 
-		const addEnum = () => {
-			state.enumdata.push({
-				text: '',
-				value: '',
-			});
-		};
-		const delEnum = (index: number) => {
-			state.enumdata.splice(index, 1);
-		};
+// 打开弹窗
+const openDialog = (row: RuleFormState, productKeyData: string) => {
+	resetForm()
+	ruleForm.value = row
+	productKey.value = productKeyData
+	inputsdata.value = row.inputs || []
+	outputsdata.value = row.outputs || []
+	isShowDialog.value = true
+}
+const resetForm = () => {
+	ruleForm.value = JSON.parse(JSON.stringify(form))
+	type.value = ''
+	types.value = ''
+	inputsdata.value = []
+	outputsdata.value = []
+	elementType.value = {
+		type: '',
+		maxLength: '',
+	}
+}
 
-		const editjson = (index: number, type: string) => {
-			if (type == 'fun') {
-				editOptionRef.value.openDialog(state.inputsdata[index]);
-			} else {
-				editOptionRef.value.openDialog(state.jsondata[index]);
 
-			}
-		}
+const editjson = (index: number, type: string) => {
+	if (type == 'fun') {
+		editOptionRef.value.openDialog(inputsdata.value[index])
+	} else {
+		editOptionRef.value.openDialog(jsondata.value[index])
+	}
+}
 
-		const deljson = (index: number, type: string) => {
-			if (type == 'fun') {
-				state.inputsdata.splice(index, 1);
-			} else {
-				state.jsondata.splice(index, 1);
-			}
-		};
+const deljson = (index: number, type: string) => {
+	if (type == 'fun') {
+		inputsdata.value.splice(index, 1)
+	} else {
+		jsondata.value.splice(index, 1)
+	}
+}
 
-		const deljsonOut = (index: number, type: string) => {
-			if (type == 'fun') {
-				state.outputsdata.splice(index, 1);
-			} else {
-				state.outputsdata.splice(index, 1);
-			}
-		};
+const deljsonOut = (index: number, type: string) => {
+	if (type == 'fun') {
+		outputsdata.value.splice(index, 1)
+	} else {
+		outputsdata.value.splice(index, 1)
+	}
+}
 
-		const editjsonOut = (index: number) => {
-			editOptionOutRef.value.openDialog(state.outputsdata[index]);
-		}
+const editjsonOut = (index: number) => {
+	editOptionOutRef.value.openDialog(outputsdata.value[index])
+}
 
-		const addJson = (type: string) => {
-			editOptionRef.value.openDialog({ productKey: '', id: 0, type_data: type });
-		};
+const addJson = (type: string) => {
+	editOptionRef.value.openDialog({ productKey: '', id: 0, type_data: type })
+}
 
-		const addJsonOut = (type: string) => {
-			editOptionOutRef.value.openDialog({ productKey: '', id: 0, type_data: type });
-		};
+const addJsonOut = (type: string) => {
+	editOptionOutRef.value.openDialog({ productKey: '', id: 0, type_data: type })
+}
 
-		const getOptionData = (data: any, type_data: any) => {
-			if (type_data == 'fun') {
-				state.inputsdata.push(data);
-			} else {
-				state.jsondata.push(data);
-			}
-		};
-		const getOptionDataOut = (data: any, type_data: any) => {
-			if (type_data == 'fun') {
-				state.outputsdata.push(data);
+const getOptionData = (data: any, type_data: any) => {
+	if (type_data == 'fun') {
+		inputsdata.value.push(data)
+	} else {
+		jsondata.value.push(data)
+	}
+}
+const getOptionDataOut = (data: any, type_data: any) => {
+	if (type_data == 'fun') {
+		outputsdata.value.push(data)
+	} else {
+		outputsdata.value.push(data)
+	}
+}
+// 关闭弹窗
+const closeDialog = () => {
+	isShowDialog.value = false
+}
+// 取消
+const onCancel = () => {
+	closeDialog()
+}
+// 新增
+const onSubmit = () => {
+	const formWrap = unref(formRef) as any
+	if (!formWrap) return
+	formWrap.validate((valid: boolean) => {
+		if (valid) {
+			ruleForm.value.inputs = inputsdata.value
+			ruleForm.value.outputs = outputsdata.value
+			if (ruleForm.value.id !== 0) {
+				ruleForm.value.productKey = productKey.value
+				api.model.functionedit(ruleForm.value).then(() => {
+					// 编辑成功
+					ElMessage.success(t('message.tableI18nConfirm.editSuccess'))
+					closeDialog() // 关闭弹窗
+					emit('typeList')
+				})
 			} else {
-				state.outputsdata.push(data);
+				api.model.functionadd(ruleForm.value).then(() => {
+					// 新增成功
+					ElMessage.success(t('message.tableI18nConfirm.addSuccess'))
+					closeDialog() // 关闭弹窗
+					emit('typeList')
+				})
 			}
-		};
-		// 关闭弹窗
-		const closeDialog = () => {
-			state.isShowDialog = false;
-		};
-		// 取消
-		const onCancel = () => {
-			closeDialog();
-		};
-		// 新增
-		const onSubmit = () => {
-			const formWrap = unref(formRef) as any;
-			if (!formWrap) return;
-			formWrap.validate((valid: boolean) => {
-				if (valid) {
-					state.ruleForm.inputs = state.inputsdata;
-					state.ruleForm.outputs = state.outputsdata;
-					if (state.ruleForm.id !== 0) {
-						state.ruleForm.productKey = state.productKey;
-						api.model.functionedit(state.ruleForm).then(() => {
-							ElMessage.success('功能定义类型修改成功');
-							closeDialog(); // 关闭弹窗
-							emit('typeList');
-						});
-					} else {
-						api.model.functionadd(state.ruleForm).then(() => {
-							ElMessage.success('功能定义类型添加成功');
-							closeDialog(); // 关闭弹窗
-							emit('typeList');
-						});
-					}
-				}
-			});
-		};
-
-		return {
-			editOptionRef,
-			editOptionOutRef,
-			getOptionData,
-			getOptionDataOut,
-			openDialog,
-			editjson,
-			editjsonOut,
-			deljson,
-			deljsonOut,
-			addEnum,
-			delEnum,
-			addJson,
-			addJsonOut,
-			seletChange,
-			seletChanges,
-			closeDialog,
-			onCancel,
-			onSubmit,
-			formRef,
-			...toRefs(state),
-		};
-	},
-});
+		}
+	})
+}
+defineExpose({ openDialog })
 </script>
 <style scoped>
 .input-box {
@@ -359,8 +323,6 @@ export default defineComponent({
 	justify-content: space-between;
 }
 
-.jsonoption {}
-
 .jsonoption a {
 	margin: 0px 10px;
 }

+ 54 - 31
src/views/iot/device/product/component/editOption.vue

@@ -1,16 +1,23 @@
 <template>
 	<div class="system-edit-dic-container">
-		<el-dialog :title="(typeof ruleForm.valueType !== 'undefined' ? '修改' : '添加') + '参数'" v-model="isShowDialog" width="769px">
-			<el-form :model="ruleForm" ref="formRef" :rules="rules" label-width="120px">
-				<el-form-item label="参数标识" prop="key">
-					<el-input v-model="ruleForm.key" placeholder="请输入参数标识" />
+		<!-- 编辑参数 / 新增参数 -->
+		<el-dialog :title="typeof ruleForm.valueType !== 'undefined' ? $t('message.device.dialogI18n.editOption') : $t('message.device.dialogI18n.addOption')" v-model="isShowDialog" width="769px">
+			<el-form :model="ruleForm" ref="formRef" :rules="rules" :label-width="currentLocale == 'en' ? '150px' : '120px'">
+				<!-- 参数标识 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.parameterIdentifier')" prop="key">
+					<!-- 请输入参数标识 -->
+					<el-input v-model="ruleForm.key" :placeholder="$t('message.device.formI18nPlaceholder.parameterIdentifier')" />
 				</el-form-item>
-				<el-form-item label="参数名称" prop="name">
-					<el-input v-model="ruleForm.name" placeholder="请输入参数名称" />
+				<!-- 参数名称 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.parameterName')" prop="name">
+					<!-- 请输入参数名称 -->
+					<el-input v-model="ruleForm.name" :placeholder="$t('message.device.formI18nPlaceholder.parameterName')" />
 				</el-form-item>
 
-				<el-form-item label="数据类型" prop="type">
-					<el-select v-model="valueType.type" placeholder="请选择数据类型" @change="seletChange">
+				<!-- 数据类型 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.dataType')" prop="type">
+					<!-- 请选择数据类型 -->
+					<el-select v-model="valueType.type" :placeholder="$t('message.device.formI18nPlaceholder.dataType')" @change="seletChange">
 						<el-option-group v-for="group in typeData" :key="group.label" :label="group.label">
 							<el-option v-for="item in group.options" :key="item.type" :label="item.title" :value="item.type" />
 						</el-option-group>
@@ -19,8 +26,10 @@
 
 				<TypeItem :valueType="valueType" :typeData="typeData"></TypeItem>
 				<div v-if="type == 'array'">
-					<el-form-item label="元素类型" prop="type">
-						<el-select v-model="elementType.type" placeholder="请选择元素类型" @change="seletChanges">
+				<!-- 元素类型 -->
+					<el-form-item :label="$t('message.device.formI18nLabel.elementType')" prop="type">
+						<!-- 请选择元素类型 -->
+						<el-select v-model="elementType.type" :placeholder="$t('message.device.formI18nPlaceholder.elementType')" @change="seletChanges">
 							<el-option-group v-for="group in typeData" :key="group.label" :label="group.label">
 								<el-option v-for="item in group.options" :key="item.type" :label="item.title" :value="item.type" :disabled="['array', 'enum'].includes(item.type)" />
 							</el-option-group>
@@ -28,15 +37,18 @@
 					</el-form-item>
 					<TypeItem :valueType="elementType" :typeData="typeData"></TypeItem>
 				</div>
-
-				<el-form-item label="参数描述	" prop="desc">
-					<el-input v-model="ruleForm.desc" type="textarea" placeholder="请输入参数描述"></el-input>
+				<!-- 参数描述 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.parameterDescription')" prop="desc">
+					<!-- 请输入参数描述 -->
+					<el-input v-model="ruleForm.desc" type="textarea" :placeholder="$t('message.device.formI18nPlaceholder.parameterDescription')"></el-input>
 				</el-form-item>
 			</el-form>
 			<template #footer>
 				<span class="dialog-footer">
-					<el-button @click="onCancel">取 消</el-button>
-					<el-button type="primary" @click="onSubmit">{{ typeof ruleForm.valueType !== 'undefined' ? '修 改' : '添 加' }}</el-button>
+					<!-- 取 消 -->
+					<el-button @click="onCancel">{{ $t('message.tableI18nAction.cancel') }}</el-button>
+					<!-- 编辑 / 新增 -->
+					<el-button type="primary" @click="onSubmit">{{ typeof ruleForm.valueType !== 'undefined' ? $t('message.tableI18nAction.edit') : $t('message.tableI18nAction.add') }}</el-button>
 				</span>
 			</template>
 		</el-dialog>
@@ -44,16 +56,16 @@
 </template>
 
 <script lang="ts">
-import { reactive, toRefs, defineComponent, ref, unref } from 'vue';
+import { reactive, toRefs, defineComponent, ref, unref, computed } from 'vue';
 import api from '/@/api/device';
 import TypeItem from './typeItem.vue';
 import { Plus, Minus, Right } from '@element-plus/icons-vue';
 import { ElMessage } from 'element-plus';
 import { validateNoSpace } from '/@/utils/validator';
+import { useI18n } from 'vue-i18n';
 
 interface stateType {
 	isShowDialog: boolean
-	rules: any
 	ruleForm: any
 	valueType: any
 	typeData: any
@@ -100,7 +112,22 @@ export default defineComponent({
 	components: { Plus, Minus, Right, TypeItem },
 	setup(prop, { emit }) {
 		const formRef = ref<HTMLElement | null>(null);
-
+		const { locale, t } = useI18n();
+		const currentLocale = computed(() => locale.value)
+		const rules = computed(() => ({
+			name: [
+				// 参数名称不能为空
+				{ required: true, message: t('message.device.rules.parameterName'), trigger: 'blur' },
+				// 参数名称不能超过32个字符
+				{ max: 32, message: t('message.device.rules.parameterNameMax32'), trigger: 'blur' },
+				//  参数名称不能包含空格
+				{ validator: validateNoSpace, message: t('message.device.rules.parameterNameValidator'), trigger: 'blur' }
+			],
+			// 参数标识不能为空
+			key: [{ required: true, message: t('message.device.rules.parameterKey'), trigger: 'blur' }],
+			// 请选择是否只读
+			accessMode: [{ required: true, message: t('message.device.rules.accessMode'), trigger: 'blur' }],
+		}));
 		const state = reactive<stateType>({
 			isShowDialog: false,
 			typeData: [], //
@@ -124,16 +151,7 @@ export default defineComponent({
 				status: 1,
 				valueType: {},
 				desc: '',
-			},
-			rules: {
-				name: [
-					{ required: true, message: '参数名称不能为空', trigger: 'blur' },
-					{ max: 32, message: '参数名称不能超过32个字符', trigger: 'blur' },
-					{ validator: validateNoSpace, message: '参数名称不能包含空格', trigger: 'blur' }
-				],
-				key: [{ required: true, message: '参数标识不能为空', trigger: 'blur' }],
-				accessMode: [{ required: true, message: '请选择是否只读', trigger: 'blur' }],
-			},
+			}
 		});
 
 		// 打开弹窗
@@ -144,10 +162,12 @@ export default defineComponent({
 				const datat: any = Object.values(res.dataType);
 				datat.forEach((item: any, index: number) => {
 					if (index == 0) {
-						datat[index]['label'] = '基础类型';
+						// 基础类型
+						datat[index]['label'] = t('message.device.baseType');
 						datat[index]['options'] = item;
 					} else {
-						datat[index]['label'] = '扩展类型';
+						// 基础类型
+						datat[index]['label'] = t('message.device.extensionType');
 						datat[index]['options'] = item;
 					}
 				});
@@ -235,7 +255,8 @@ export default defineComponent({
 						}
 
 						state.ruleForm.valueType = state.valueType;
-						ElMessage.success('参数类型添加成功');
+						// 新增成功
+						ElMessage.success(t('message.tableI18nConfirm.addSuccess'));
 						closeDialog(); // 关闭弹窗
 						emit('typeList', state.ruleForm, state.ruleForm.type_data);
 					}
@@ -244,6 +265,8 @@ export default defineComponent({
 		};
 
 		return {
+			currentLocale,
+			rules,
 			openDialog,
 			addEnum,
 			delEnum,

+ 107 - 66
src/views/iot/device/product/component/editPro.vue

@@ -1,19 +1,26 @@
 <template>
   <div class="system-edit-dic-container">
-    <el-dialog :title="(ruleForm.id !== 0 ? '修改' : '添加') + '产品'" v-model="isShowDialog" width="769px">
-      <el-form :model="ruleForm" ref="formRef" :rules="rules" label-width="100px">
-        <el-form-item label="产品标识" prop="key">
-          <el-input v-model="ruleForm.key" placeholder="请输入产品标识" :disabled="ruleForm.id" />
+    <!-- 编辑产品/新增产品 -->
+    <el-dialog :title="ruleForm.id !== 0 ? $t('message.device.dialogI18n.editPro') : $t('message.device.dialogI18n.addPro')" v-model="isShowDialog" width="769px">
+      <el-form :model="ruleForm" ref="formRef" :rules="rules"  :label-width="currentLocale == 'en' ? '150px' : '120px'">
+        <!-- 产品标识 -->
+        <el-form-item :label="$t('message.device.formI18nLabel.productKey')" prop="key">
+          <!-- 请输入产品标识 -->
+          <el-input v-model="ruleForm.key" :placeholder="$t('message.device.formI18nPlaceholder.productKey')" :disabled="ruleForm.id" />
         </el-form-item>
-        <el-form-item label="产品名称" prop="name">
-          <el-input v-model="ruleForm.name" placeholder="请输入产品名称" />
+        <!-- 产品名称 -->
+        <el-form-item :label="$t('message.device.formI18nLabel.productName')" prop="name">
+          <!-- 请输入产品名称 -->
+          <el-input v-model="ruleForm.name" :placeholder="$t('message.device.formI18nPlaceholder.productName')" />
         </el-form-item>
-        <el-form-item label="产品图片" prop="imageUrl">
+        <!-- 产品图片 -->
+        <el-form-item :label="$t('message.device.formI18nLabel.productImage')" prop="imageUrl">
           <uploadVue :img="imageUrl" @set-img="handleAvatarSuccess"></uploadVue>
         </el-form-item>
-
-        <el-form-item label="产品分类" prop="categoryId">
-          <el-cascader :options="cateData" :props="{ checkStrictly: true, emitPath: false, value: 'id', label: 'name' }" placeholder="请选择分类" class="w" clearable v-model="ruleForm.categoryId">
+        <!-- 产品分类 -->
+        <el-form-item :label="$t('message.device.formI18nLabel.productCategory')" prop="categoryId">
+          <!-- 请选择分类 -->
+          <el-cascader :options="cateData" :props="{ checkStrictly: true, emitPath: false, value: 'id', label: 'name' }" :placeholder="$t('message.device.formI18nPlaceholder.productCategory')" clearable v-model="ruleForm.categoryId">
             <template #default="{ node, data }">
               <span>{{ data.name }}</span>
               <span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
@@ -21,80 +28,100 @@
           </el-cascader>
 
           <!-- 添加产品分类 -->
-          <el-button type="success" @click="onOpenAddCategory()" style="margin-left: 5px">添加产品分类</el-button>
+          <el-button type="success" @click="onOpenAddCategory()" style="margin-left: 5px">{{ $t('message.device.tableI18nAction.addProductCategory') }}</el-button>
         </el-form-item>
-
-        <el-form-item label="消息协议" prop="messageProtocol">
-          <el-select v-model="ruleForm.messageProtocol" placeholder="请选择消息协议">
+        <!-- 消息协议 -->
+        <el-form-item :label="$t('message.device.formI18nLabel.messageProtocol')" prop="messageProtocol">
+          <!-- 请选择消息协议 -->
+          <el-select v-model="ruleForm.messageProtocol" :placeholder="$t('message.device.formI18nPlaceholder.messageProtocol')">
             <el-option v-for="dict in messageData" :key="dict.types" :label="dict.title" :value="dict.name"> </el-option>
             <!-- 增加系统默认的mqtt选项 -->
             <el-option label="Sagoo Mqtt" value="SagooMqtt"> </el-option>
           </el-select>
         </el-form-item>
-
-        <el-form-item label="接入方式" prop="transportProtocol">
-          <el-select v-model="ruleForm.transportProtocol" placeholder="请选择接入方式" @change="transportProtocolChange">
+        <!-- 接入方式 -->
+        <el-form-item :label="$t('message.device.formI18nLabel.transportProtocol')" prop="transportProtocol">
+          <!-- 请选择接入方式 -->
+          <el-select v-model="ruleForm.transportProtocol" :placeholder="$t('message.device.formI18nPlaceholder.transportProtocol')" @change="transportProtocolChange">
             <el-option v-for="dict in network_server_type" :key="dict.value" :label="dict.label" :value="dict.value"> </el-option>
           </el-select>
         </el-form-item>
-        <!-- 1,mqtt协议设备的认证
-	在添加设备的页面,增加MQTT服务协议设备的认证方式的处理。
-	当传输协议选择为:MQTT服务的时候,下面出现 认证方式的下拉列,分别为Basic、AccessToken接入两个下拉选项。
-	Basic方式:
-	选择这个方式的时候:页面出现,用户名及密码输入框。
-	AccessToken接入方式:
-	选择这个方式的时候:页面出现 Aceess Token的输入框
-	2,TCP及其它协议认证
-	这些配合【网络组件】功能中涉及到的设备通讯协议认证的处理。如果涉及到证书的,需要调用【证书管理】功能维护的证书列表。 -->
-        <!-- 设备认证、网络组件服务增加认证方式authType(1=Basic,2=AccessToken,3=证书)
-				涉及接口:产品添加、编辑、扩展信息更新;设备添加、编辑;网络组件服务添加、编辑 -->
+        <!-- 
+          1,mqtt协议设备的认证
+            在添加设备的页面,增加MQTT服务协议设备的认证方式的处理。
+            当传输协议选择为:MQTT服务的时候,下面出现 认证方式的下拉列,分别为Basic、AccessToken接入两个下拉选项。
+            Basic方式:
+            选择这个方式的时候:页面出现,用户名及密码输入框。
+            AccessToken接入方式:
+            选择这个方式的时候:页面出现 Aceess Token的输入框
+          2,TCP及其它协议认证
+          这些配合【网络组件】功能中涉及到的设备通讯协议认证的处理。如果涉及到证书的,需要调用【证书管理】功能维护的证书列表。 
+        -->
+        <!-- 
+          设备认证、网络组件服务增加认证方式authType(1=Basic,2=AccessToken,3=证书)
+          涉及接口:产品添加、编辑、扩展信息更新;设备添加、编辑;网络组件服务添加、编辑 
+        -->
         <!-- 设备认证 -->
         <template v-if="ruleForm.authType === 1 || ruleForm.authType === 2">
-          <el-form-item label="认证方式" prop="">
+          <!-- 认证方式 -->
+          <el-form-item :label="$t('message.device.formI18nLabel.authType')" prop="">
             <el-radio-group v-model="ruleForm.authType">
               <el-radio :label="1">Basic</el-radio>
               <el-radio :label="2">AccessToken</el-radio>
             </el-radio-group>
           </el-form-item>
           <template v-if="ruleForm.authType === 1">
-            <el-form-item label="用户名" prop="authUser">
-              <el-input v-model="ruleForm.authUser" placeholder="请输入用户名" />
+            <!-- 用户名 -->
+            <el-form-item :label="$t('message.device.formI18nLabel.authUser')" prop="authUser">
+              <el-input v-model="ruleForm.authUser" :placeholder="$t('message.device.formI18nPlaceholder.authUser')" />
             </el-form-item>
-            <el-form-item label="密码" prop="authPasswd">
-              <el-input type="password" v-model="ruleForm.authPasswd" placeholder="请输入密码" />
+            <!-- 密码 -->
+            <el-form-item :label="$t('message.device.formI18nLabel.authPasswd')" prop="authPasswd">
+              <el-input type="password" v-model="ruleForm.authPasswd" :placeholder="$t('message.device.formI18nPlaceholder.authPasswd')" />
             </el-form-item>
           </template>
           <template v-else>
-            <el-form-item label="Aceess Token" prop="accessToken">
-              <el-input v-model="ruleForm.accessToken" placeholder="请输入Aceess Token" />
+            <!-- Aceess Token -->
+            <el-form-item :label="$t('message.device.formI18nLabel.accessToken')" prop="accessToken">
+              <!-- 请输入Aceess Token -->
+              <el-input v-model="ruleForm.accessToken" :placeholder="$t('message.device.formI18nPlaceholder.accessToken')" />
             </el-form-item>
           </template>
         </template>
         <template v-else-if="ruleForm.authType === 3">
-          <el-form-item label="认证证书" prop="certificateId">
-            <el-select v-model="ruleForm.certificateId" placeholder="请选择证书">
+          <!-- 认证证书 -->
+          <el-form-item :label="$t('message.device.formI18nLabel.certificateId')" prop="certificateId">
+            <!-- 请选择证书 -->
+            <el-select v-model="ruleForm.certificateId" :placeholder="$t('message.device.formI18nPlaceholder.certificateId')">
               <el-option v-for="cert in certList" :key="cert.id" :label="cert.name" :value="cert.id"> </el-option>
             </el-select>
           </el-form-item>
         </template>
-
-        <el-form-item label="设备类型" prop="deviceType">
+        <!-- 设备类型 -->
+        <el-form-item :label="$t('message.device.formI18nLabel.deviceType')" prop="deviceType">
           <el-radio-group v-model="ruleForm.deviceType">
-            <el-radio label="设备">设备</el-radio>
-            <el-radio label="网关">网关</el-radio>
+            <!-- 设备 -->
+            <el-radio label="设备">{{ $t('message.device.formI18nOption.device') }}</el-radio>
+            <!-- 网关 -->
+            <el-radio label="网关">{{ $t('message.device.formI18nOption.gateway') }}</el-radio>
             <!-- <el-radio label="设备">直连设备</el-radio>
             <el-radio label="网关">网关设备</el-radio> -->
-            <el-radio label="子设备">子设备</el-radio>
+            <!-- 子设备 -->
+            <el-radio label="子设备">{{ $t('message.device.formI18nOption.subDevice') }}</el-radio>
           </el-radio-group>
         </el-form-item>
-        <el-form-item label="产品描述	" prop="desc">
-          <el-input v-model="ruleForm.desc" type="textarea" placeholder="请输入产品描述"></el-input>
+        <!-- 产品描述 -->
+        <el-form-item :label="$t('message.device.formI18nLabel.desc')" prop="desc">
+          <!-- 请输入产品描述 -->
+          <el-input v-model="ruleForm.desc" type="textarea" :placeholder="$t('message.device.formI18nPlaceholder.desc')"></el-input>
         </el-form-item>
       </el-form>
       <template #footer>
         <span class="dialog-footer">
-          <el-button @click="onCancel">取 消</el-button>
-          <el-button type="primary" @click="onSubmit" :loading="submitLoading">{{ ruleForm.id !== 0 ? "修 改" : "添 加" }}</el-button>
+          <!-- 取 消 -->
+          <el-button @click="onCancel">{{ $t('message.tableI18nAction.cancel') }}</el-button>
+          <!-- 编辑 / 添加 -->
+          <el-button type="primary" @click="onSubmit" :loading="submitLoading">{{ ruleForm.id ? $t('message.tableI18nAction.edit') : $t('message.tableI18nAction.add') }}</el-button>
         </span>
       </template>
     </el-dialog>
@@ -103,7 +130,7 @@
 </template>
 
 <script lang="ts">
-import { reactive, toRefs, defineComponent, ref, unref, getCurrentInstance } from "vue";
+import { reactive, toRefs, defineComponent, ref, unref, getCurrentInstance, computed } from "vue";
 import api from "/@/api/device";
 import certApi from "/@/api/certificateManagement";
 import uploadVue from "/@/components/upload/index.vue";
@@ -112,6 +139,8 @@ import { validateNoSpace } from "/@/utils/validator";
 import { ElMessage, UploadProps } from "element-plus";
 import getOrigin from "/@/utils/origin";
 import EditCategory from "/@/views/iot/device/category/component/addOrEdit.vue";
+import { useI18n } from 'vue-i18n';
+
 
 interface RuleFormState {
   id: number;
@@ -138,7 +167,6 @@ interface DicState {
   messageData: any[];
   network_protocols: any[];
   tranData: any[];
-  rules: {};
   imageUrl: string;
   singleImg: string;
 }
@@ -174,6 +202,32 @@ export default defineComponent({
     const certList = ref<any[]>([]);
     const submitLoading = ref(false);
     const editCategoryRef = ref();
+    const { locale, t } = useI18n();
+    const currentLocale = computed(() => locale.value);	
+    const rules = computed(() => ({
+        name: [
+          // 产品名称不能为空
+          { required: true, message: t("message.device.rules.productName"), trigger: "change" },
+          // 产品名称不能超过32个字符
+          { max: 32, message: t("message.device.rules.productNameMax32"), trigger: "change" },
+          // 产品名称不能包含空格
+          { validator: validateNoSpace, message: t("message.device.rules.productNameValidator"), trigger: "change" },
+        ],
+        key: [
+          // 产品标识不能为空
+          { required: true, message: t("message.device.rules.productKey"), trigger: "change" },
+          // 产品标识不能包含空格
+          { validator: validateNoSpace, message: t("message.device.rules.productKeyValidator"), trigger: "change" },
+        ],
+        // 消息协议不能为空
+        messageProtocol: [{ required: true, message: t("message.device.rules.messageProtocol"), trigger: "change" }],
+        // 接入方式不能为空
+        transportProtocol: [{ required: true, message: t("message.device.rules.transportProtocol"), trigger: "change" }],
+        // 产品分类不能为空
+        categoryId: [{ required: true, message: t("message.device.rules.categoryId"), trigger: "change" }],
+        // 设备类型不能为空
+        deviceType: [{ required: true, message: t("message.device.rules.deviceType"), trigger: "change" }],
+      }))
 
     const state = reactive<DicState>({
       isShowDialog: false,
@@ -186,22 +240,7 @@ export default defineComponent({
       singleImg: baseURL + "/product/icon/upload",
       ruleForm: {
         ...form,
-      },
-      rules: {
-        name: [
-          { required: true, message: "产品名称不能为空", trigger: "change" },
-          { max: 32, message: "产品名称不能超过32个字符", trigger: "change" },
-          { validator: validateNoSpace, message: "产品名称不能包含空格", trigger: "change" },
-        ],
-        key: [
-          { required: true, message: "产品标识不能为空", trigger: "change" },
-          { validator: validateNoSpace, message: "产品标识不能包含空格", trigger: "change" },
-        ],
-        messageProtocol: [{ required: true, message: "消息协议不能为空", trigger: "change" }],
-        transportProtocol: [{ required: true, message: "接入方式不能为空", trigger: "change" }],
-        categoryId: [{ required: true, message: "产品分类不能为空", trigger: "change" }],
-        deviceType: [{ required: true, message: "设备类型不能为空", trigger: "change" }],
-      },
+      }
     });
 
     const handleAvatarSuccess: UploadProps["onSuccess"] = (response) => {
@@ -282,7 +321,7 @@ export default defineComponent({
             api.product
               .edit(state.ruleForm)
               .then(() => {
-                ElMessage.success("产品修改成功");
+                ElMessage.success(t("message.tableI18nConfirm.editSuccess"));
                 closeDialog(); // 关闭弹窗
                 emit("typeList");
               })
@@ -292,7 +331,7 @@ export default defineComponent({
             api.product
               .add(state.ruleForm)
               .then(() => {
-                ElMessage.success("产品添加成功");
+                ElMessage.success(t("message.tableI18nConfirm.addSuccess"));
                 closeDialog(); // 关闭弹窗
                 emit("typeList");
               })
@@ -313,6 +352,8 @@ export default defineComponent({
     };
 
     return {
+      rules,
+      currentLocale,
       transportProtocolChange,
       submitLoading,
       certList,

+ 145 - 74
src/views/iot/device/product/component/editTab.vue

@@ -1,16 +1,23 @@
 <template>
 	<div class="system-edit-dic-container">
-		<el-dialog :title="(ruleForm.id !== 0 ? '修改' : '添加') + '标签定义'" v-model="isShowDialog" width="769px">
+		<!-- 编辑标签定义/新增标签定义 -->
+		<el-dialog :title="ruleForm.id !== 0 ? $t('message.device.dialogI18n.editTagDefinition') : $t('message.device.dialogI18n.addTagDefinition')" v-model="isShowDialog" width="769px">
 			<el-form :model="ruleForm" ref="formRef" :rules="rules" label-width="120px">
-				<el-form-item label="标签定义标识" prop="key">
-					<el-input v-model="ruleForm.key" placeholder="请输入标签定义标识" :disabled="ruleForm.id !== 0 ? true : false" />
+				<!-- 标签定义标识 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.tagKey')" prop="key">
+					<!-- 请输入标签定义标识 -->
+					<el-input v-model="ruleForm.key" :placeholder="$t('message.device.formI18nPlaceholder.tagKey')" :disabled="ruleForm.id !== 0 ? true : false" />
 				</el-form-item>
-				<el-form-item label="标签定义名称" prop="name">
-					<el-input v-model="ruleForm.name" placeholder="请输入标签定义名称" />
+				<!-- 标签定义名称 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.tagName')" prop="name">
+					<!-- 请输入标签定义名称 -->
+					<el-input v-model="ruleForm.name" :placeholder="$t('message.device.formI18nPlaceholder.tagName')" />
 				</el-form-item>
 
-				<el-form-item label="数据类型" prop="type">
-					<el-select v-model="valueType.type" placeholder="请选择数据类型" @change="seletChange">
+				<!-- 数据类型 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.dataType')" prop="type">
+					<!-- 请选择数据类型 -->
+					<el-select v-model="valueType.type" :placeholder="$t('message.device.formI18nPlaceholder.selectDataType')" @change="seletChange">
 						<el-option-group v-for="group in typeData" :key="group.label" :label="group.label">
 							<el-option v-for="item in group.options" :key="item.type" :label="item.title" :value="item.type" />
 						</el-option-group>
@@ -19,40 +26,56 @@
 
 				<!--根据数据类型输出不同表单-->
 
-				<el-form-item label="精度" prop="decimals" v-if="type == 'float' || type == 'double'">
-					<el-input v-model="valueType.decimals" placeholder="请输入精度" />
+				<!-- 精度 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.precision')" prop="decimals" v-if="type == 'float' || type == 'double'">
+					<!-- 请输入精度 -->
+					<el-input v-model="valueType.decimals" :placeholder="$t('message.device.formI18nPlaceholder.inputPrecision')" />
 				</el-form-item>
 
-				<el-form-item label="单位" prop="unit" v-if="type == 'int' || type == 'long' || type == 'float' || type == 'double'">
-					<el-input v-model="valueType.unit" placeholder="请输入单位" />
+				<!-- 单位 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.unit')" prop="unit" v-if="type == 'int' || type == 'long' || type == 'float' || type == 'double'">
+					<!-- 请输入单位 -->
+					<el-input v-model="valueType.unit" :placeholder="$t('message.device.formI18nPlaceholder.inputUnit')" />
 				</el-form-item>
 
-				<el-form-item label="最大长度" prop="maxLength" v-if="type == 'string'">
-					<el-input v-model="valueType.maxLength" placeholder="请输入最大长度" />
+					<!-- 最大长度 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.maxLength')" prop="maxLength" v-if="type == 'string'">
+					<!-- 请输入最大长度 -->
+					<el-input v-model="valueType.maxLength" :placeholder="$t('message.device.formI18nPlaceholder.inputMaxLength')" />
 				</el-form-item>
 
-				<el-form-item label="时间格式" prop="maxLength" v-if="type == 'date'">
-					<el-input v-model="valueType.maxLength" placeholder="请输入时间格式" />
+				<!-- 时间格式 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.timeFormat')" prop="maxLength" v-if="type == 'date'">
+					<!-- 请输入时间格式 -->
+					<el-input v-model="valueType.maxLength" :placeholder="$t('message.device.formI18nPlaceholder.inputTimeFormat')" />
 				</el-form-item>
 
-				<el-form-item label="布尔值11" prop="trueText" v-if="type == 'boolean'">
+				<!-- 布尔值 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.booleanValue')" prop="trueText" v-if="type == 'boolean'">
 					<div class="input-box flex-row">
-						<el-input v-model="valueType.trueText" placeholder="请输入true时显示的文字" /><span style="margin: 0px 10px">~</span>
-						<el-input v-model="valueType.trueValue" placeholder="请输入布尔值" disabled />
+						<!-- 请输入true时显示的文字 -->
+						<el-input v-model="valueType.trueText" :placeholder="$t('message.device.formI18nPlaceholder.inputTrueText')" /><span style="margin: 0px 10px">~</span>
+						<!-- 请输入布尔值 -->
+						<el-input v-model="valueType.trueValue" :placeholder="$t('message.device.formI18nPlaceholder.inputBooleanValue')" disabled />
 					</div>
 
 					<div class="input-box flex-row">
-						<el-input v-model="valueType.falseText" placeholder="请输入false时显示的文字" /> <span style="margin: 0px 10px">~</span>
-						<el-input v-model="valueType.falseValue" placeholder="请输入布尔值" disabled />
+						<!-- 请输入false时显示的文字 -->
+						<el-input v-model="valueType.falseText" :placeholder="$t('message.device.formI18nPlaceholder.inputFalseText')" /> <span style="margin: 0px 10px">~</span>
+						<!-- 请输入布尔值 -->
+						<el-input v-model="valueType.falseValue" :placeholder="$t('message.device.formI18nPlaceholder.inputBooleanValue')" disabled />
 					</div>
 				</el-form-item>
 
-				<el-form-item label="枚举项" prop="maxLength" v-if="type == 'enum'">
+				<!-- 枚举项 -->	
+				<el-form-item :label="$t('message.device.formI18nLabel.enumValue')" prop="maxLength" v-if="type == 'enum'">
 					<div class="input-box flex-row" v-for="(item, index) in enumdata" :key="index">
-						<el-input v-model="item.text" placeholder="请输入枚举文本" /><span style="margin: 0px 10px"><el-icon>
+						<!-- 请输入枚举文本 -->
+						<el-input v-model="item.text" :placeholder="$t('message.device.formI18nPlaceholder.inputEnumText')" /><span style="margin: 0px 10px"><el-icon>
 								<Right />
 							</el-icon></span>
-						<el-input v-model="item.value" placeholder="请输入枚举值" />
+						<!-- 请输入枚举值 -->
+						<el-input v-model="item.value" :placeholder="$t('message.device.formI18nPlaceholder.inputEnumValue')" />
 						<div class="input-option">
 							<el-icon @click="addEnum" v-if="index == 0">
 								<Plus />
@@ -64,14 +87,17 @@
 					</div>
 				</el-form-item>
 
-				<el-form-item label="JSON对象" prop="maxLength" v-if="type == 'object'">
+				<!-- JSON对象 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.jsonObject')" prop="maxLength" v-if="type == 'object'">
 					<div v-for="(item, index) in jsondata" :key="index" class="jslist">
 						<div class="jsonlist">
-							<div>参数名称:</div>
+							<!-- 参数名称 -->
+							<div>{{$t('message.device.formI18nLabel.jsonObject')}}:</div>
 							<div style="width: 60%">{{ item.name }}</div>
 							<div class="jsonoption">
 								<!-- <el-link type="primary">编辑</el-link> -->
-								<el-link type="primary" @click="deljson(index)">删除</el-link>
+								<!-- 删除 -->
+								<el-link type="primary" @click="deljson(index)">{{ $t('message.tableI18nAction.delete') }}</el-link>
 							</div>
 						</div>
 					</div>
@@ -81,54 +107,73 @@
 							<el-icon>
 								<Plus />
 							</el-icon>
-							<div>添加参数</div>
+							<!-- 添加参数 -->
+							<div>{{ $t('message.device.formI18nButton.addParameter') }}</div>
 						</div>
 					</div>
 				</el-form-item>
 
 				<div v-if="type == 'array'">
-					<el-form-item label="元素类型" prop="types">
-						<el-select v-model="elementType.type" placeholder="请选择元素类型" @change="seletChanges">
+					<!-- 元素类型 -->
+					<el-form-item :label="$t('message.device.formI18nLabel.elementType')" prop="types">
+						<!-- 请选择元素类型 -->
+						<el-select v-model="elementType.type" :placeholder="$t('message.device.formI18nPlaceholder.selectElementType')" @change="seletChanges">
 							<el-option-group v-for="group in typeData" :key="group.label" :label="group.label">
 								<el-option v-for="item in group.options" :key="item.type" :label="item.title" :value="item.type" :disabled="item.type == 'array'" />
 							</el-option-group>
 						</el-select>
 					</el-form-item>
 
-					<el-form-item label="精度" prop="decimals" v-if="types == 'float' || types == 'double'">
-						<el-input v-model="elementType.decimals" placeholder="请输入精度" />
+					<!-- 精度 -->
+					<el-form-item :label="$t('message.device.formI18nLabel.precision')" prop="decimals" v-if="types == 'float' || types == 'double'">
+						<!-- 请输入精度 -->
+						<el-input v-model="elementType.decimals" :placeholder="$t('message.device.formI18nPlaceholder.inputPrecision')" />
 					</el-form-item>
 
-					<el-form-item label="单位" prop="unit" v-if="types == 'int' || types == 'long' || types == 'float' || types == 'double'">
-						<el-input v-model="elementType.unit" placeholder="请输入单位" />
+					<!-- 单位 -->
+					<el-form-item :label="$t('message.device.formI18nLabel.unit')" prop="unit" v-if="types == 'int' || types == 'long' || types == 'float' || types == 'double'">
+						<!-- 请输入单位 -->
+						<el-input v-model="elementType.unit" :placeholder="$t('message.device.formI18nPlaceholder.inputUnit')" />
 					</el-form-item>
 
-					<el-form-item label="最大长度" prop="maxLength" v-if="types == 'string'">
-						<el-input v-model="elementType.maxLength" placeholder="请输入最大长度" />
+					<!-- 最大长度 -->
+					<el-form-item :label="$t('message.device.formI18nLabel.maxLength')" prop="maxLength" v-if="types == 'string'">
+						<!-- 请输入最大长度 -->
+						<el-input v-model="elementType.maxLength" :placeholder="$t('message.device.formI18nPlaceholder.inputMaxLength')" />
 					</el-form-item>
 
-					<el-form-item label="时间格式" prop="maxLength" v-if="types == 'date'">
-						<el-input v-model="elementType.maxLength" placeholder="请输入时间格式" />
+					<!-- 时间格式 -->
+					<el-form-item :label="$t('message.device.formI18nLabel.timeFormat')" prop="maxLength" v-if="types == 'date'">
+						<!-- 请输入时间格式 -->
+						<el-input v-model="elementType.maxLength" :placeholder="$t('message.device.formI18nPlaceholder.inputTimeFormat')" />
 					</el-form-item>
 
-					<el-form-item label="布尔值" prop="maxLength" v-if="types == 'boolean'">
+					<!-- 布尔值 -->
+					<el-form-item :label="$t('message.device.formI18nLabel.booleanValue')" prop="maxLength" v-if="types == 'boolean'">
 						<div class="input-box flex-row">
-							<el-input v-model="elementType.trueText" placeholder="请输入true时显示的文字" /><span style="margin: 0px 10px">~</span>
-							<el-input v-model="elementType.trueValue" placeholder="请输入布尔值" disabled />
+							<!-- 请输入true时显示的文字 -->
+							<el-input v-model="elementType.trueText" :placeholder="$t('message.device.formI18nPlaceholder.inputTrueText')" /><span style="margin: 0px 10px">~</span>
+							<!-- 请输入布尔值 -->
+							<el-input v-model="elementType.trueValue" :placeholder="$t('message.device.formI18nPlaceholder.inputBooleanValue')" disabled />
 						</div>
 
 						<div class="input-box flex-row">
-							<el-input v-model="elementType.falseText" placeholder="请输入false时显示的文字" /> <span style="margin: 0px 10px">~</span>
-							<el-input v-model="elementType.falseValue" placeholder="请输入布尔值" disabled />
+							<!-- 请输入false时显示的文字 -->
+							<el-input v-model="elementType.falseText" :placeholder="$t('message.device.formI18nPlaceholder.inputFalseText')" /> <span style="margin: 0px 10px">~</span>
+							<!-- 请输入布尔值 -->
+							<el-input v-model="elementType.falseValue" :placeholder="$t('message.device.formI18nPlaceholder.inputBooleanValue')" disabled />
 						</div>
 					</el-form-item>
 
-					<el-form-item label="枚举项" prop="maxLength" v-if="types == 'enum'">
+					<!-- 枚举项 -->
+					<el-form-item :label="$t('message.device.formI18nLabel.enumValue')" prop="maxLength" v-if="types == 'enum'">
 						<div class="input-box flex-row" v-for="(item, index) in enumdata" :key="index">
-							<el-input v-model="item.text" placeholder="请输入枚举文本" /><span style="margin: 0px 10px"><el-icon>
+							<!-- 请输入枚举文本 -->
+							<el-input v-model="item.text" :placeholder="$t('message.device.formI18nPlaceholder.inputEnumText')" /><span style="margin: 0px 10px"><el-icon>
 									<Right />
 								</el-icon></span>
-							<el-input v-model="item.value" placeholder="请输入枚举值" />
+								<!-- 请输入枚举文本 -->
+							<el-input v-model="item.value" :placeholder="$t('message.device.formI18nPlaceholder.inputEnumValue')" />
 							<div class="input-option">
 								<el-icon @click="addEnum" v-if="index == 0">
 									<Plus />
@@ -141,14 +186,18 @@
 					</el-form-item>
 				</div>
 
-				<el-form-item label="JSON对象" prop="maxLength" v-if="types == 'object'">
+				<!-- JSON对象 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.jsonObject')" prop="maxLength" v-if="types == 'object'">
 					<div v-for="(item, index) in jsondata" :key="index" class="jslist">
 						<div class="jsonlist">
-							<div>参数名称:</div>
+							<!-- 参数名称 -->
+							<div>{{$t('message.device.formI18nLabel.parameterName')}}:</div>
 							<div style="width: 60%">{{ item.name }}</div>
 							<div class="jsonoption">
-								<el-link type="primary">编辑</el-link>
-								<el-link type="primary">删除</el-link>
+								<!-- 编辑 -->
+								<el-link type="primary">{{ $t('message.tableI18nAction.edit') }}</el-link>
+								<!-- 删除 -->
+								<el-link type="primary">{{ $t('message.tableI18nAction.delete') }}</el-link>
 							</div>
 						</div>
 					</div>
@@ -158,27 +207,36 @@
 							<el-icon>
 								<Plus />
 							</el-icon>
-							<div>添加参数</div>
+							<!-- 添加参数 -->
+							<div>{{ $t('message.device.formI18nButton.addParameter') }}</div>
 						</div>
 					</div>
 				</el-form-item>
 
 				<!--根据数据类型输出不同表单-->
 
-				<el-form-item label="是否只读" prop="accessMode">
+				<!-- 是否只读 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.isReadOnly')" prop="accessMode">
 					<el-radio-group v-model="ruleForm.accessMode">
-						<el-radio :label="1">只读</el-radio>
-						<el-radio :label="0">读写</el-radio>
+						<!-- 只读 -->
+						<el-radio :label="1">{{ $t('message.device.formI18nOption.readonly') }}</el-radio>
+						<!-- 读写 -->
+						<el-radio :label="0">{{ $t('message.device.formI18nOption.readWrite') }}</el-radio>
 					</el-radio-group>
 				</el-form-item>
-				<el-form-item label="标签定义描述	" prop="desc">
-					<el-input v-model="ruleForm.desc" type="textarea" placeholder="请输入标签定义描述"></el-input>
+
+				<!-- 标签定义描述 -->
+				<el-form-item :label="$t('message.device.formI18nLabel.tagDescription')" prop="desc">
+					<!-- 请输入标签定义描述 -->
+					<el-input v-model="ruleForm.desc" type="textarea" :placeholder="$t('message.device.formI18nPlaceholder.tagDescription')"></el-input>
 				</el-form-item>
 			</el-form>
 			<template #footer>
 				<span class="dialog-footer">
-					<el-button @click="onCancel">取 消</el-button>
-					<el-button type="primary" @click="onSubmit">{{ ruleForm.id !== 0 ? '修 改' : '添 加' }}</el-button>
+					<!-- 取消 -->
+					<el-button @click="onCancel">{{ $t('message.tableI18nAction.cancel') }}</el-button>
+					<!-- 新增/编辑 -->
+					<el-button type="primary" @click="onSubmit">{{ ruleForm.id !== 0 ? $t('message.tableI18nAction.edit') : $t('message.tableI18nAction.add') }}</el-button>
 				</span>
 			</template>
 		</el-dialog>
@@ -187,12 +245,12 @@
 </template>
 
 <script lang="ts">
-import { reactive, toRefs, defineComponent, ref, unref } from 'vue';
+import { reactive, toRefs, defineComponent, ref, unref, computed } from 'vue';
 import api from '/@/api/device';
 import { Plus, Minus, Right } from '@element-plus/icons-vue';
 import EditOption from './editOption.vue';
 import { validateNoSpace } from '/@/utils/validator';
-
+import { useI18n } from 'vue-i18n';
 import { ElMessage } from 'element-plus';
 
 interface RuleFormState {
@@ -219,7 +277,7 @@ interface DicState {
 	jsondata: any;
 	typeData: any[];
 	enumdata: any[];
-	rules: {};
+	// rules: {};
 }
 
 const ruleForm = {
@@ -245,6 +303,23 @@ export default defineComponent({
 	setup(prop, { emit }) {
 		const formRef = ref<HTMLElement | null>(null);
 		const editOptionRef = ref();
+		const { t, locale } = useI18n();
+		const rules = computed(() => ({
+			name: [
+				// 标签定义名称不能为空
+				{ required: true, message: t('message.device.rules.tagKeyName'), trigger: 'blur' },
+				// 标签定义名称不能超过32个字符
+				{ max: 32, message: t('message.device.rules.tagKeyMax32'), trigger: 'blur' },
+				// 标签定义名称不能包含空格
+				{ validator: validateNoSpace, message: t('message.device.rules.tagKeyValidator'), trigger: 'blur' }
+			],
+			// 标签定义标识不能为空
+			key: [{ required: true, message: t('message.device.rules.tagKey'), trigger: 'blur' }],
+			// 请选择是否只读
+			accessMode: [{ required: true, message: t('message.device.rules.tagAccessMode'), trigger: 'blur' }],
+			// 请选择数据类型
+			type: [{ required: true, message: t('message.device.rules.tagType'), trigger: 'blur' }],
+		}))
 		const state = reactive<DicState>({
 			isShowDialog: false,
 			typeData: [], //
@@ -273,16 +348,7 @@ export default defineComponent({
 			jsondata: [],
 
 			ruleForm: JSON.parse(JSON.stringify(ruleForm)),
-			rules: {
-				name: [
-					{ required: true, message: '标签定义名称不能为空', trigger: 'blur' },
-					{ max: 32, message: '标签定义名称不能超过32个字符', trigger: 'blur' },
-					{ validator: validateNoSpace, message: '标签定义名称不能包含空格', trigger: 'blur' }
-				],
-				key: [{ required: true, message: '标签定义标识不能为空', trigger: 'blur' }],
-				accessMode: [{ required: true, message: '请选择是否只读', trigger: 'blur' }],
-				type: [{ required: true, message: '请选择数据类型', trigger: 'blur' }],
-			},
+
 		});
 
 		// 打开弹窗
@@ -293,10 +359,12 @@ export default defineComponent({
 				const datat = Object.values(res.dataType) as any[];
 				datat.forEach((item, index) => {
 					if (index == 0) {
-						datat[index]['label'] = '基础类型';
+						// 基础类型
+						datat[index]['label'] = t('message.device.baseType');
 						datat[index]['options'] = item;
 					} else {
-						datat[index]['label'] = '扩展类型';
+						// 扩展类型
+						datat[index]['label'] = t('message.device.extendType');
 						datat[index]['options'] = item;
 					}
 				});
@@ -423,7 +491,8 @@ export default defineComponent({
 						state.ruleForm.productKey = state.productKey;
 
 						api.model.tagedit(state.ruleForm).then(() => {
-							ElMessage.success('标签定义类型修改成功');
+							// 标签定义类型修改成功
+							ElMessage.success(t('message.tableI18nConfirm.editSuccess'));
 							closeDialog(); // 关闭弹窗
 							emit('typeList');
 						});
@@ -456,7 +525,8 @@ export default defineComponent({
 
 						state.ruleForm.valueType = state.valueType;
 						api.model.tagadd(state.ruleForm).then(() => {
-							ElMessage.success('标签定义类型添加成功');
+							// 标签定义类型添加成功
+							ElMessage.success(t('message.tableI18nConfirm.addSuccess'));
 							closeDialog(); // 关闭弹窗
 							emit('typeList');
 						});
@@ -466,6 +536,7 @@ export default defineComponent({
 		};
 
 		return {
+			rules,
 			editOptionRef,
 			getOptionData,
 			openDialog,

+ 42 - 25
src/views/iot/device/product/component/typeItem.vue

@@ -2,49 +2,62 @@
   <div class="type-item">
     <template v-if="['int', 'long', 'float', 'double'].includes(valueType.type)">
       <div class="flex-row" style="margin-bottom: 0;">
-        <el-form-item label="取值范围" prop="min" class="flex1">
-          <el-input v-model="valueType.min" type="number" @input="setNull(valueType, 'min', $event)" placeholder="最小值" />
+        <!-- 取值范围 -->
+        <el-form-item :label="$t('message.device.formI18nLabel.valueRange')" prop="min" class="flex1">
+          <!-- 最小值 -->
+          <el-input v-model="valueType.min" type="number" @input="setNull(valueType, 'min', $event)" :placeholder="$t('message.device.formI18nPlaceholder.valueRangeMin')" />
         </el-form-item>
         <div class="split" style="margin-bottom: 20px;">~</div>
         <el-form-item prop="max" class="flex1" label-width="0">
-          <el-input v-model="valueType.max" type="number" @input="setNull(valueType, 'max', $event)" placeholder="最大值" />
+          <!-- 最大值 -->
+          <el-input v-model="valueType.max" type="number" @input="setNull(valueType, 'max', $event)" :placeholder="$t('message.device.formI18nPlaceholder.valueRangeMax')" />
         </el-form-item>
       </div>
-      <el-form-item label="单位" prop="unit">
-        <el-input v-model="valueType.unit" placeholder="请输入单位" />
+      <!-- 单位 -->
+      <el-form-item :label="$t('message.device.formI18nLabel.unit')" prop="unit">
+        <!-- 请输入单位 -->
+        <el-input v-model="valueType.unit" :placeholder="$t('message.device.formI18nPlaceholder.inputUnit')" />
       </el-form-item>
     </template>
-
-    <el-form-item label="精度" prop="decimals" v-if="['float', 'double'].includes(valueType.type)">
-      <el-input v-model="valueType.decimals" type="number" placeholder="请输入精度" />
+    <!-- 精度 -->
+    <el-form-item :label="$t('message.device.formI18nLabel.precision')" prop="decimals" v-if="['float', 'double'].includes(valueType.type)">
+      <!-- 请输入精度 -->
+      <el-input v-model="valueType.decimals" type="number" :placeholder="$t('message.device.formI18nPlaceholder.inputPrecision')" />
     </el-form-item>
 
-
-    <el-form-item label="最大长度" prop="maxLength" v-if="valueType.type === 'string'">
-      <el-input v-model="valueType.maxLength" type="number" placeholder="请输入最大长度" />
+    <!-- 最大长度 -->
+    <el-form-item :label="$t('message.device.formI18nLabel.maxLength')" prop="maxLength" v-if="valueType.type === 'string'">
+      <!-- 请输入最大长度 -->
+      <el-input v-model="valueType.maxLength" type="number" :placeholder="$t('message.device.formI18nPlaceholder.inputMaxLength')" />
     </el-form-item>
-
-    <el-form-item label="布尔值" v-else-if="valueType.type === 'boolean'">
+    <!-- 布尔值-->
+    <el-form-item :label="$t('message.device.formI18nLabel.booleanValue')" v-else-if="valueType.type === 'boolean'">
       <div class="input-box flex-row">
-        <el-input v-model="valueType.trueText" placeholder="请输入true时显示的文字" /><span style="margin: 0px 10px">~</span>
-        <el-input v-model="valueType.trueValue" placeholder="请输入布尔值" disabled />
+        <!-- 请输入true时显示的文字 -->
+        <el-input v-model="valueType.trueText" :laceholder="$t('message.device.formI18nPlaceholder.inputTrueText')" /><span style="margin: 0px 10px">~</span>
+        <!-- 请输入布尔值 -->
+        <el-input v-model="valueType.trueValue" :placeholder="$t('message.device.formI18nPlaceholder.inputBooleanValue')" disabled />
       </div>
 
       <div class="input-box flex-row">
-        <el-input v-model="valueType.falseText" placeholder="请输入false时显示的文字" /> <span style="margin: 0px 10px">~</span>
-        <el-input v-model="valueType.falseValue" placeholder="请输入布尔值" disabled />
+        <!-- 请输入false时显示的文字 -->
+        <el-input v-model="valueType.falseText" :placeholder="$t('message.device.formI18nPlaceholder.inputFalseText')" /> <span style="margin: 0px 10px">~</span>
+        <!-- 请输入布尔值 -->
+        <el-input v-model="valueType.falseValue" :placeholder="$t('message.device.formI18nPlaceholder.inputBooleanValue')" disabled />
       </div>
     </el-form-item>
-
-    <el-form-item label="枚举项" prop="" v-else-if="valueType.type === 'enum'">
+    <!-- 枚举项 -->
+    <el-form-item :label="$t('message.device.formI18nLabel.enumItems')" prop="" v-else-if="valueType.type === 'enum'">
       <div class="input-box flex-row" v-for="(item, index) in valueType.elements" :key="index">
-        <el-input v-model="item.text" placeholder="请输入枚举文本" />
+        <!-- 请输入枚举文本 -->
+        <el-input v-model="item.text" :placeholder="$t('message.device.formI18nPlaceholder.inputEnumText')" />
         <span style="margin: 0px 10px">
           <el-icon>
             <Right />
           </el-icon>
         </span>
-        <el-input v-model="item.value" placeholder="请输入枚举值" />
+        <!-- 请输入枚举值 -->
+        <el-input v-model="item.value" :placeholder="$t('message.device.formI18nPlaceholder.inputEnumValue')" />
         <div class="input-option">
           <el-icon @click="addEnum" v-if="index == 0">
             <Plus />
@@ -55,12 +68,16 @@
         </div>
       </div>
     </el-form-item>
-    <el-form-item label="对象属性" prop="" v-else-if="valueType.type === 'object'">
+    <!-- 对象属性 -->
+    <el-form-item :label="$t('message.device.formI18nLabel.objectProperty')" prop="" v-else-if="valueType.type === 'object'">
       <div class="w-full" v-for="(item, index) in valueType.properties" :key="index">
         <div class="flex-row">
-          <el-input v-model="item.key" placeholder="属性标识" class="flex1" />
-          <el-input v-model="item.name" placeholder="属性名称" class="flex1" />
-          <el-select v-model="item.valueType.type" placeholder="请选择元素类型" style="width: 140px;">
+          <!-- 属性标识 -->
+          <el-input v-model="item.key" :placeholder="$t('message.device.formI18nPlaceholder.propertyIdentifier1')" class="flex1" />
+          <!-- 属性名称 -->
+          <el-input v-model="item.name" :placeholder="$t('message.device.formI18nPlaceholder.propertyName1')" class="flex1" />
+          <!-- 请选择元素类型 -->
+          <el-select v-model="item.valueType.type" :placeholder="$t('message.device.formI18nPlaceholder.selectElementType')" style="width: 140px;">
             <el-option-group v-for="group in typeData" :key="group.label" :label="group.label">
               <el-option v-for="item in group.options" :key="item.type" :label="item.title" :value="item.type" :disabled="['array', 'object', 'enum', 'date'].includes(item.type)" />
             </el-option-group>

+ 45 - 63
src/views/iot/device/product/detail.vue

@@ -5,40 +5,40 @@
 				<!-- 产品 -->
 				<div class="title">{{ `${$t('message.device.product')}:${detail.name}` }}</div>
 				<!-- <el-tag :type="developer_status == 1 ? 'success' : 'danger'" style="margin:0 20px;">{{ developer_status == 1 ? '已发布' : '未发布' }}</el-tag> -->
-				<el-switch v-auth="'startOrStop'" style="margin:0 20px;" v-model="developer_status" inline-prompt :active-value="1" :inactive-value="0" :active-text="$t('message.device.producDetail.enable')" :inactive-text="$t('message.device.producDetail.disable')" @change="CkOption"></el-switch>
+				<el-switch v-auth="'startOrStop'" style="margin:0 20px;" v-model="developer_status" inline-prompt :active-value="1" :inactive-value="0" :active-text="$t('message.device.productDetail.enable')" :inactive-text="$t('message.device.productDetail.disable')" @change="CkOption"></el-switch>
 			</div>
 		</div>
 
 		<el-tabs v-model="activeName" style="padding: 0 20px;" @tab-click="handleClick">
 			<!-- 产品信息 -->
-			<el-tab-pane :label="$t('message.device.producDetail.productInfo')" name="1">
+			<el-tab-pane :label="$t('message.device.productDetail.productInfo')" name="1">
 				<div class="pro-box">
-					<div class="protitle">{{$t('message.device.producDetail.productInfo')}}</div>
+					<div class="protitle">{{$t('message.device.productDetail.productInfo')}}</div>
 					<!-- 编辑 -->
 					<el-button type="" :icon="Edit" class="buttonedit" v-auth="'edit'" @click="onOpenEditDic(detail)">{{ $t('message.tableI18nAction.edit') }}</el-button>
 				</div>
 
 				<el-descriptions class="margin-top" :column="3" border>
 					<!-- 产品标识 -->
-					<el-descriptions-item :label="$t('message.device.producDetail.productIdentifier')"><copy :text="detail.key"></copy></el-descriptions-item>
+					<el-descriptions-item :label="$t('message.device.productDetail.productIdentifier')"><copy :text="detail.key"></copy></el-descriptions-item>
 					<!-- 产品分类 -->
-					<el-descriptions-item :label="$t('message.device.producDetail.productCategory')">{{ detail.categoryName }}</el-descriptions-item>
+					<el-descriptions-item :label="$t('message.device.productDetail.productCategory')">{{ detail.categoryName }}</el-descriptions-item>
 					<!-- 设备类型 -->
-					<el-descriptions-item :label="$t('message.device.producDetail.deviceType')">{{ detail.deviceType }}</el-descriptions-item>
+					<el-descriptions-item :label="$t('message.device.productDetail.deviceType')">{{ detail.deviceType }}</el-descriptions-item>
 					<!-- 产品图片 -->
-					<el-descriptions-item :label="$t('message.device.producDetail.productImage')">
+					<el-descriptions-item :label="$t('message.device.productDetail.productImage')">
 						<el-image style="width: 80px; height: 80px" :src="detail.icon" :previewSrcList="[detail.icon]" fit="contain">
 							<template #error>
 								<div class="image-slot">
 									<ele-Picture style="width: 30px;" />
 									<!-- 加载失败 -->
-									{{ $t('message.device.producDetail.loadFailed') }}
+									{{ $t('message.device.productDetail.loadFailed') }}
 								</div>
 							</template>
 						</el-image>
 					</el-descriptions-item>
 					<!-- 消息协议 -->
-					<el-descriptions-item :label="$t('message.device.producDetail.messageProtocol')">{{ detail.messageProtocol }}</el-descriptions-item>
+					<el-descriptions-item :label="$t('message.device.productDetail.messageProtocol')">{{ detail.messageProtocol }}</el-descriptions-item>
 					<!-- 接入方式 -->
 					<el-descriptions-item :label="$t('message.device.tableI18nColumn.transportProtocol')">{{ detail.transportProtocol }}</el-descriptions-item>
 					<!-- 描述 -->
@@ -46,12 +46,12 @@
 				</el-descriptions>
 			</el-tab-pane>
 			<!-- 物模型 -->
-			<el-tab-pane :label="$t('message.device.producDetail.thingModel')" name="2">
+			<el-tab-pane :label="$t('message.device.productDetail.thingModel')" name="2">
 				<el-tabs type="border-card" v-model="activetab" @tab-click="wuhandleClick">
 					<!-- 属性定义 -->
-					<el-tab-pane :label="$t('message.device.producDetail.propertyDefinition')" name="attr">
+					<el-tab-pane :label="$t('message.device.productDetail.propertyDefinition')" name="attr">
 						<div class="wu-title">
-							<div class="title">{{$t('message.device.producDetail.propertyDefinition')}}</div>
+							<div class="title">{{$t('message.device.productDetail.propertyDefinition')}}</div>
 							<!-- 新增 -->
 							<div>
 								<el-button size="small" type="primary" v-auth="'edit'" @click="onOpenEditAttr()">{{$t('message.tableI18nAction.add')}}</el-button>
@@ -60,38 +60,38 @@
 
 						<el-table style="width: 100%" :data="tableData.data" v-loading="tableData.loading" v-if="activetab == 'attr'">
 							<!-- 属性标识 -->
-							<el-table-column :label="$t('message.device.producDetail.propertyIdentifier')" align="center" prop="key" />
+							<el-table-column :label="$t('message.device.productDetail.propertyIdentifier')" align="center" prop="key" />
 							<!-- 属性名称 -->
-							<el-table-column :label="$t('message.device.producDetail.propertyName')" prop="name" show-overflow-tooltip />
+							<el-table-column :label="$t('message.device.productDetail.propertyName')" prop="name" show-overflow-tooltip />
 							<!-- 数据类型 -->
-							<el-table-column prop="valueType" :label="$t('message.device.producDetail.dataType')" width="100" align="center">
+							<el-table-column prop="valueType" :label="$t('message.device.productDetail.dataType')" width="100" align="center">
 								<template #default="scope">
 									<span>{{ scope.row.valueType.type }}</span>
 								</template>
 							</el-table-column>
 							<!-- 精度 -->
-							<el-table-column prop="decimals" :label="$t('message.device.producDetail.precision')" :width="currentLang == 'en' ? '100' : '60'" align="center">
+							<el-table-column prop="decimals" :label="$t('message.device.productDetail.precision')" :width="currentLang == 'en' ? '100' : '60'" align="center">
 								<template #default="scope">
 									<span>{{ scope.row.valueType.decimals }}</span>
 								</template>
 							</el-table-column>
 							<!-- 单位 -->
-							<el-table-column prop="unit" :label="$t('message.device.producDetail.unit')" width="60" align="center">
+							<el-table-column prop="unit" :label="$t('message.device.productDetail.unit')" width="60" align="center">
 								<template #default="scope">
 									<span>{{ scope.row.valueType.unit }}</span>
 								</template>
 							</el-table-column>
 							<!-- 是否只读 -->
-							<el-table-column prop="accessMode" :label="$t('message.device.producDetail.readOnly')" width="120" align="center">
+							<el-table-column prop="accessMode" :label="$t('message.device.productDetail.readOnly')" width="120" align="center">
 								<template #default="scope">
 									<!-- 只读 -->
-									<el-tag type="info" size="small" v-if="scope.row.accessMode">{{ $t('message.device.producDetail.readonly') }}</el-tag>
+									<el-tag type="info" size="small" v-if="scope.row.accessMode">{{ $t('message.device.productDetail.readonly') }}</el-tag>
 									<!-- 读写 -->
-									<el-tag type="success" size="small" v-else>{{ $t('message.device.producDetail.readWrite') }}</el-tag>
+									<el-tag type="success" size="small" v-else>{{ $t('message.device.productDetail.readWrite') }}</el-tag>
 								</template>
 							</el-table-column>
 							<!-- 说明 -->
-							<el-table-column :label="$t('message.device.producDetail.remark')" prop="desc" show-overflow-tooltip />
+							<el-table-column :label="$t('message.device.productDetail.remark')" prop="desc" show-overflow-tooltip />
 							<!-- 操作 -->
 							<el-table-column :label="$t('message.tableI18nColumn.operation')" width="120" align="center" fixed="right">
 								<template #default="scope">
@@ -102,9 +102,9 @@
 						</el-table>
 					</el-tab-pane>
 					<!-- 功能定义 -->
-					<el-tab-pane :label="$t('message.device.producDetail.functionDefinition')" name="fun">
+					<el-tab-pane :label="$t('message.device.productDetail.functionDefinition')" name="fun">
 						<div class="wu-title">
-							<div class="title">{{$t('message.device.producDetail.functionDefinition')}}</div>
+							<div class="title">{{$t('message.device.productDetail.functionDefinition')}}</div>
 							<!-- 新增 -->
 							<div>
 								<el-button size="small" type="primary" v-auth="'add'" @click="onOpenEditFun()">{{$t('message.tableI18nAction.add')}}</el-button>
@@ -112,8 +112,8 @@
 						</div>
 
 						<el-table style="width: 100%" :data="tableData.data" v-if="activetab == 'fun'">
-							<el-table-column :label="$t('message.device.producDetail.functionIdentifier')" align="center" prop="key" />
-							<el-table-column :label="$t('message.device.producDetail.name')" prop="name" show-overflow-tooltip />
+							<el-table-column :label="$t('message.device.productDetail.functionIdentifier')" align="center" prop="key" />
+							<el-table-column :label="$t('message.device.productDetail.name')" prop="name" show-overflow-tooltip />
 							<!-- 描述 -->
 							<el-table-column :label="$t('message.tableI18nColumn.des')" prop="desc" show-overflow-tooltip />
 							<!-- 操作 -->
@@ -128,9 +128,9 @@
 						</el-table>
 					</el-tab-pane>
 					<!-- 事件定义 -->
-					<el-tab-pane :label="$t('message.device.producDetail.eventDefinition')" name="event">
+					<el-tab-pane :label="$t('message.device.productDetail.eventDefinition')" name="event">
 						<div class="wu-title">
-							<div class="title">{{$t('message.device.producDetail.eventDefinition')}}</div>
+							<div class="title">{{$t('message.device.productDetail.eventDefinition')}}</div>
 							<div>
 								<el-button size="small" type="primary" v-auth="'add'" @click="onOpenEditEvent()">{{$t('message.tableI18nAction.add')}}</el-button>
 							</div>
@@ -138,18 +138,18 @@
 
 						<el-table style="width: 100%" :data="tableData.data" v-if="activetab == 'event'">
 							<!-- 事件标识 -->
-							<el-table-column :label="$t('message.device.producDetail.eventIdentifier')" align="center" prop="key" />
+							<el-table-column :label="$t('message.device.productDetail.eventIdentifier')" align="center" prop="key" />
 							<!-- 事件名称 -->
-							<el-table-column :label="$t('message.device.producDetail.name')" prop="name" show-overflow-tooltip />
+							<el-table-column :label="$t('message.device.productDetail.name')" prop="name" show-overflow-tooltip />
 							<!-- 事件级别 -->
-							<el-table-column prop="level" :label="$t('message.device.producDetail.eventLevel')" width="120" align="center">
+							<el-table-column prop="level" :label="$t('message.device.productDetail.eventLevel')" width="120" align="center">
 								<template #default="scope">
 									<!-- 普通 -->
-									<el-tag size="small" v-if="scope.row.level == 0">{{ $t('message.device.producDetail.normal') }}</el-tag>
+									<el-tag size="small" v-if="scope.row.level == 0">{{ $t('message.device.productDetail.normal') }}</el-tag>
 									<!-- 警告 -->
-									<el-tag type="warning" size="small" v-if="scope.row.level == 1">{{ $t('message.device.producDetail.warning') }}</el-tag>
+									<el-tag type="warning" size="small" v-if="scope.row.level == 1">{{ $t('message.device.productDetail.warning') }}</el-tag>
 									<!-- 紧急 -->
-									<el-tag type="danger" size="small" v-if="scope.row.level == 2">{{ $t('message.device.producDetail.urgent') }}</el-tag>
+									<el-tag type="danger" size="small" v-if="scope.row.level == 2">{{ $t('message.device.productDetail.urgent') }}</el-tag>
 								</template>
 							</el-table-column>
 							<!-- 描述 -->
@@ -166,10 +166,10 @@
 						</el-table>
 					</el-tab-pane>
 					<!-- 标签定义 -->
-					<el-tab-pane :label="$t('message.device.producDetail.tagDefinition')" name="tab">
+					<el-tab-pane :label="$t('message.device.productDetail.tagDefinition')" name="tab">
 						<div class="wu-title">
 							<!-- 标签定义 -->
-							<div class="title">{{ $t('message.device.producDetail.tagDefinition') }}</div>
+							<div class="title">{{ $t('message.device.productDetail.tagDefinition') }}</div>
 							<!-- 添加 -->
 							<div>
 								<el-button size="small" type="primary" v-auth="'add'" @click="onOpenEditTab()">{{ $t('message.tableI18nAction.add') }}</el-button>
@@ -178,22 +178,22 @@
 
 						<el-table style="width: 100%" :data="tableData.data" v-if="activetab == 'tab'">
 							<!-- 标签标识 -->
-							<el-table-column :label="$t('message.device.producDetail.propertyIdentifier')" align="center" prop="key" />
+							<el-table-column :label="$t('message.device.productDetail.propertyIdentifier')" align="center" prop="key" />
 							<!-- 属性名称 -->
-							<el-table-column :label="$t('message.device.producDetail.propertyName')" prop="name" show-overflow-tooltip />
+							<el-table-column :label="$t('message.device.productDetail.propertyName')" prop="name" show-overflow-tooltip />
 							<!-- 数据类型 -->
-							<el-table-column prop="valueType" :label="$t('message.device.producDetail.dataType')" width="120" align="center">
+							<el-table-column prop="valueType" :label="$t('message.device.productDetail.dataType')" width="120" align="center">
 								<template #default="scope">
 									<span>{{ scope.row.valueType.type }}</span>
 								</template>
 							</el-table-column>
 							<!-- 是否只读 -->
-							<el-table-column prop="accessMode" :label="$t('message.device.producDetail.readOnly')" width="120" align="center">
+							<el-table-column prop="accessMode" :label="$t('message.device.productDetail.readOnly')" width="120" align="center">
 								<template #default="scope">
 									<!-- 只读 -->
-									<el-tag type="info" size="small" v-if="scope.row.accessMode">{{ $t('message.device.producDetail.readonly') }}</el-tag>
+									<el-tag type="info" size="small" v-if="scope.row.accessMode">{{ $t('message.device.productDetail.readonly') }}</el-tag>
 									<!-- 读写 -->
-									<el-tag type="success" size="small" v-else>{{ $t('message.device.producDetail.readWrite') }}</el-tag>
+									<el-tag type="success" size="small" v-else>{{ $t('message.device.productDetail.readWrite') }}</el-tag>
 								</template>
 							</el-table-column>
 							<!-- 描述 -->
@@ -216,7 +216,7 @@
 							<!-- 导入物模型 -->
 							<el-button size="small">
 								<el-icon> <ele-Upload /> </el-icon>
-								{{ $t('message.device.producDetail.importModel') }}
+								{{ $t('message.device.productDetail.importModel') }}
 							</el-button>
 						</el-upload>
 						<!-- 导出物模型 -->
@@ -224,18 +224,18 @@
 							<el-icon>
 								<ele-Download />
 							</el-icon>
-							{{ $t('message.device.producDetail.exportModel') }}
+							{{ $t('message.device.productDetail.exportModel') }}
 						</el-button>
 					</div>
 				</div>
 				<pagination v-show="tableData.total > 0" :total="tableData.total" v-model:page="tableData.param.pageNum" v-model:limit="tableData.param.pageSize" @pagination="getList()" />
 			</el-tab-pane>
 			<!-- 设备接入 -->
-			<el-tab-pane :label="$t('message.device.producDetail.deviceAccess')" name="3">
+			<el-tab-pane :label="$t('message.device.productDetail.deviceAccess')" name="3">
 				<deviceIn></deviceIn>
 			</el-tab-pane>
 			<!-- 数据解析 -->
-			<el-tab-pane :label="$t('message.device.producDetail.dataParsing')" name="4" lazy>
+			<el-tab-pane :label="$t('message.device.productDetail.dataParsing')" name="4" lazy>
 				<dataParse v-if="activeName === '4'" :script="detail.scriptInfo" @updateScript="updateScript">
 				</dataParse>
 			</el-tab-pane>
@@ -324,24 +324,6 @@ const currentLang = computed(() => locale.value);
 				dateRange: [],
 			},
 		});
-		// const state = reactive<TableDataState>({
-		// 	activeName: '1', // 分类数据
-		// 	activetab: 'attr', // 分类数据
-		// 	detail: {},
-		// 	developer_status: 0,
-		// 	tableData: {
-		// 		data: [],
-		// 		total: 0,
-		// 		loading: false,
-		// 		param: {
-		// 			pageNum: 1,
-		// 			productKey: route.params?.id,
-		// 			pageSize: 20,
-		// 			status: '',
-		// 			dateRange: [],
-		// 		},
-		// 	},
-		// });
 
 		onMounted(() => {
 			productDetail()

+ 2 - 2
src/views/iot/network/server/component/list.vue

@@ -16,12 +16,12 @@
     <el-table-column align="center" label="操作" width="200" v-col="'auth'">
       <template #default="scope">
 
-        <el-button @click="toDetail(scope.row.id)" size="small" type="text" v-auth="'detail'">详情</el-button>
+        <el-button @click="toDetail(scope.row.id)" size="small" text type="primary" v-auth="'detail'">详情</el-button>
         <el-button size="small" link key="info" type="info" v-auth="'edit'" @click="toEdit(scope.row.id)">编辑</el-button>
 
         <el-popover placement="bottom" :width="170" trigger="click">
           <template #reference>
-            <el-button size="small" type="text" class="more-btn" @click="isShowMore = !isShowMore" v-auth="'more'">更多
+            <el-button size="small" text type="primary" class="more-btn" @click="isShowMore = !isShowMore" v-auth="'more'">更多
               <i style="margin-left: 2px;" :class="isShowMore ? 'fa fa-angle-down' : 'fa fa-angle-up'"></i>
             </el-button>
           </template>

+ 2 - 2
src/views/iot/network/tunnel/component/list.vue

@@ -13,11 +13,11 @@
     </el-table-column>
     <el-table-column align="center" label="操作" v-col="'auth'" width="160">
       <template #default="scope">
-        <el-button @click="toDetail(scope.row.id)" size="small" v-auth="'detail'" type="text">详情</el-button>
+        <el-button @click="toDetail(scope.row.id)" size="small" v-auth="'detail'" text type="primary">详情</el-button>
         <el-button size="small" link key="info" type="info" v-auth="'edit'" @click="toEdit(scope.row.id)">编辑</el-button>
         <el-popover placement="bottom" :width="160" trigger="click">
           <template #reference>
-            <el-button size="small" type="text" v-auth="'more'" class="more-btn" @click="isShowMore = !isShowMore">更多
+            <el-button size="small" text type="primary" v-auth="'more'" class="more-btn" @click="isShowMore = !isShowMore">更多
               <i style="margin-left: 2px;" :class="isShowMore ? 'fa fa-angle-down' : 'fa fa-angle-up'"></i>
             </el-button>
           </template>

+ 1 - 1
src/views/iot/operate/remoteconf/index.vue

@@ -53,7 +53,7 @@
           <el-table-column prop="gmtCreate" label="版本更新时间"></el-table-column>
           <el-table-column label="操作" width="200">
             <template #default="scope">
-              <el-button size="small" type="text" @click="look(scope.row, scope.$index)">查看</el-button>
+              <el-button size="small" text type="primary" @click="look(scope.row, scope.$index)">查看</el-button>
             </template>
           </el-table-column>
         </el-table>

+ 33 - 32
src/views/iot/projects/detail/device.vue

@@ -1,49 +1,49 @@
 <template>
   <div class="tab-content h-full">
-    <div class="subtitle"><span></span> <el-button type="primary" v-auth="'add'" size="small" @click="addDevice()">添加设备</el-button></div>
+    <div class="subtitle"><span></span> <el-button type="primary" v-auth="'add'" size="small" @click="addDevice()">{{ $t('message.projects.detail.device.actions.add') }}</el-button></div>
     <el-table :data="tableData" style="width: 100%" v-loading="loading" max-height="calc(100vh - 280px)">
-      <el-table-column label="标识" prop="key" min-width="150" show-overflow-tooltip v-col="'key'">
+      <el-table-column :label="$t('message.projects.detail.device.table.columns.key')" prop="key" min-width="150" show-overflow-tooltip>
         <template #default="{ row }">
           <copy :text="row.key"></copy>
         </template>
       </el-table-column>
-      <el-table-column label="设备名称" prop="name" min-width="160" show-overflow-tooltip v-col="'name'" />
-      <el-table-column label="设备类型" prop="product.deviceType" min-width="100" align="center" show-overflow-tooltip v-col="'deviceType'" />
-      <el-table-column label="所属产品" prop="productName" min-width="120" align="center" show-overflow-tooltip v-col="'productName'" />
-      <el-table-column prop="status" label="状态" min-width="80" align="center" v-col="'status'">
+      <el-table-column :label="$t('message.projects.detail.device.table.columns.name')" prop="name" min-width="160" show-overflow-tooltip />
+      <el-table-column :label="$t('message.projects.detail.device.table.columns.deviceType')" prop="product.deviceType" min-width="100" align="center" show-overflow-tooltip />
+      <el-table-column :label="$t('message.projects.detail.device.table.columns.productName')" prop="productName" min-width="120" align="center" show-overflow-tooltip />
+      <el-table-column prop="status" :label="$t('message.projects.detail.device.table.columns.status')" min-width="80" align="center">
         <template #default="scope">
-          <el-tag type="info" size="small" v-if="scope.row.status == 1">离线</el-tag>
-          <el-tag type="success" size="small" v-if="scope.row.status == 2">在线</el-tag>
-          <el-tag type="info" size="small" v-if="scope.row.status == 0">未启用</el-tag>
+          <el-tag type="info" size="small" v-if="scope.row.status == 1">{{ $t('message.projects.detail.device.table.statusTags.offline') }}</el-tag>
+          <el-tag type="success" size="small" v-if="scope.row.status == 2">{{ $t('message.projects.detail.device.table.statusTags.online') }}</el-tag>
+          <el-tag type="info" size="small" v-if="scope.row.status == 0">{{ $t('message.projects.detail.device.table.statusTags.inactive') }}</el-tag>
         </template>
       </el-table-column>
-      <el-table-column prop="lastOnlineTime" label="最后上线时间" align="center" width="160" v-col="'lastOnlineTime'"></el-table-column>
-      <el-table-column prop="desc" label="说明" show-overflow-tooltip v-col="'desc'"></el-table-column>
-      <el-table-column label="操作" width="120" align="center">
+      <el-table-column prop="lastOnlineTime" :label="$t('message.projects.detail.device.table.columns.lastOnlineTime')" align="center" width="160"></el-table-column>
+      <el-table-column prop="desc" :label="$t('message.projects.detail.device.table.columns.desc')" show-overflow-tooltip></el-table-column>
+      <el-table-column :label="$t('message.projects.detail.device.table.columns.actions')" width="170" align="center">
         <template #default="scope">
-          <el-button size="small" text type="primary" v-auth="'detail'" @click="$router.push('/iotmanager/device/instance/' + scope.row.key)">设备详情</el-button>
-          <el-button size="small" text type="warning" v-auth="'del'" @click="onDel(scope.row)">解绑</el-button>
+          <el-button size="small" text type="primary" v-auth="'detail'" @click="$router.push('/iotmanager/device/instance/' + scope.row.key)">{{ $t('message.projects.detail.device.table.actions.detail') }}</el-button>
+          <el-button size="small" text type="warning" v-auth="'del'" @click="onDel(scope.row)">{{ $t('message.projects.detail.device.table.actions.unbind') }}</el-button>
         </template>
       </el-table-column>
     </el-table>
     <pagination v-if="params.total" :total="params.total" v-model:page="params.pageNum" v-model:limit="params.pageSize" @pagination="getList()" />
-    <el-dialog title="添加设备" v-model="isShowDialog" width="400">
+    <el-dialog :title="$t('message.projects.detail.device.dialog.title')" v-model="isShowDialog" width="400">
       <el-form v-if="isShowDialog">
-        <el-form-item label="产品">
-          <el-select v-model="form.productKey" @change="getDivices" filterable placeholder="选择产品" :clearable="false" style="width: 100%">
+        <el-form-item :label="$t('message.projects.detail.device.form.labels.product')">
+          <el-select v-model="form.productKey" @change="getDivices" filterable :placeholder="$t('message.projects.detail.device.form.placeholders.product')" :clearable="false" style="width: 100%">
             <el-option v-for="item in productList" :key="item.value" :label="item.label" :value="item.value" />
           </el-select>
         </el-form-item>
-        <el-form-item label="设备" style="margin-bottom: 0;">
-          <el-select v-model="form.resourcesKey" filterable placeholder="选择设备" :clearable="false" style="width: 100%">
+        <el-form-item :label="$t('message.projects.detail.device.form.labels.device')" style="margin-bottom: 0;">
+          <el-select v-model="form.resourcesKey" filterable :placeholder="$t('message.projects.detail.device.form.placeholders.device')" :clearable="false" style="width: 100%">
             <el-option v-for="item in deviceList" :key="item.value" :label="item.label" :value="item.value" />
           </el-select>
         </el-form-item>
       </el-form>
       <template #footer>
         <span class="dialog-footer">
-          <el-button @click="onCancel">取 消</el-button>
-          <el-button type="primary" @click="onSubmit">确 定</el-button>
+          <el-button @click="onCancel">{{ $t('message.projects.detail.device.actions.cancel') }}</el-button>
+          <el-button type="primary" @click="onSubmit">{{ $t('message.projects.detail.device.actions.confirm') }}</el-button>
         </span>
       </template>
     </el-dialog>
@@ -57,20 +57,21 @@ import api from '/@/api/projects';
 import { useSearch } from '/@/hooks/useCommon';
 import { useRoute } from 'vue-router';
 import { ElMessage, ElMessageBox } from 'element-plus';
+import { useI18n } from 'vue-i18n';
 const route = useRoute();
+const { t } = useI18n();
 
 const props = defineProps({ resourcesTypes: Number })
 const isShowDialog = ref(false)
 const productList = ref<any[]>([])
 const deviceList = ref<any[]>([])
-const resourcesTypes = props.resourcesTypes
 const projectsCode = route.params.id
 
 const baseForm = {
   productKey: '',
   resourcesKey: '',
   projectsCode,
-  resourcesTypes
+  resourcesTypes: props.resourcesTypes
 }
 
 const form = reactive({
@@ -84,7 +85,7 @@ getSourceList();
 
 function getSourceList() {
   api.getProjectResourcesByCode({ projectsCode }).then((res: any) => {
-    params.keys = (res || []).filter((item: any) => item.resourcesTypes === resourcesTypes).map((item: any) => item.resourcesKey)
+    params.keys = (res || []).filter((item: any) => item.resourcesTypes === props.resourcesTypes).map((item: any) => item.resourcesKey)
     if (!params.keys.length) {
       params.pageNum = 1
       tableData.value = []
@@ -107,11 +108,11 @@ function onCancel() {
 }
 
 function onSubmit() {
-  if (!form.resourcesKey) return ElMessage('请先选择设备')
-  api.bindResources(form).then((res: any) => {
+  if (!form.resourcesKey) return ElMessage(t('message.projects.detail.device.messages.selectDeviceFirst'))
+  api.bindResources(form).then(() => {
     onCancel()
     getSourceList()
-    ElMessage.success('添加成功')
+    ElMessage.success(t('message.projects.detail.device.messages.addSuccess'))
   })
 }
 
@@ -120,18 +121,18 @@ function addDevice() {
 }
 
 function onDel(row: any) {
-  ElMessageBox.confirm(`确定要删除?`, '提示', {
-    confirmButtonText: '确认',
-    cancelButtonText: '取消',
+  ElMessageBox.confirm(t('message.projects.detail.device.messages.deleteConfirm'), t('message.projects.detail.device.messages.tip'), {
+    confirmButtonText: t('message.projects.detail.device.messages.confirm'),
+    cancelButtonText: t('message.projects.detail.device.messages.cancel'),
     type: 'warning',
   }).then(() => {
     api.unbindResources({
       projectsCode,
-      resourcesTypes,
+      resourcesTypes: props.resourcesTypes,
       resourcesKey: row.key
     }).then(() => {
       getSourceList()
-      ElMessage.success('解绑成功')
+      ElMessage.success(t('message.projects.detail.device.messages.unbindSuccess'))
     })
   });
 }

+ 5 - 5
src/views/iot/projects/detail/index.vue

@@ -2,19 +2,19 @@
   <div class="page">
     <el-card shadow="never" class="small-padding">
       <el-tabs :model-value="'0'" size="small" class="h-full">
-        <el-tab-pane label="项目概况" name="0" lazy>
+        <el-tab-pane :label="$t('message.projects.detail.tabs.overview')" name="0" lazy>
           <InfoVue></InfoVue>
         </el-tab-pane>
-        <el-tab-pane label="网关和设备" name="1" lazy>
+        <el-tab-pane :label="$t('message.projects.detail.tabs.devices')" name="1" lazy>
           <DeviceVue :resourcesTypes="1"></DeviceVue>
         </el-tab-pane>
-        <el-tab-pane label="场景联动" name="4" lazy>
+        <el-tab-pane :label="$t('message.projects.detail.tabs.scene')" name="4" lazy>
           <SceneVue :resourcesTypes="4"></SceneVue>
         </el-tab-pane>
-        <el-tab-pane label="组态应用" name="2" lazy>
+        <el-tab-pane :label="$t('message.projects.detail.tabs.topo')" name="2" lazy>
           <TopoVue :resourcesTypes="2"></TopoVue>
         </el-tab-pane>
-        <el-tab-pane label="视频监控" name="3" lazy>
+        <el-tab-pane :label="$t('message.projects.detail.tabs.video')" name="3" lazy>
           <VideoVue :resourcesTypes="3"></VideoVue>
         </el-tab-pane>
       </el-tabs>

+ 8 - 8
src/views/iot/projects/detail/info.vue

@@ -1,14 +1,14 @@
 <template>
   <div class="tab-content" v-loading="laoding">
-    <div class="subtitle"><span></span> <el-button type="primary" v-auth="'edit'" size="small" @click="edit()">编辑</el-button></div>
+    <div class="subtitle"><span></span> <el-button type="primary" v-auth="'edit'" size="small" @click="edit()">{{ $t('message.projects.detail.info.actions.edit') }}</el-button></div>
     <el-descriptions class="margin-top" :column="2" border>
-      <el-descriptions-item label="项目名称">{{ data.name }} </el-descriptions-item>
-      <el-descriptions-item label="关键客户">{{ data.customName }} </el-descriptions-item>
-      <el-descriptions-item label="地区">{{ data.address ? JSON.parse(data.address)?.name : '' }} </el-descriptions-item>
-      <el-descriptions-item label="渠道商">{{ data.channelMerchants }} </el-descriptions-item>
-      <el-descriptions-item label="维修公司">{{ data.repairCompany }} </el-descriptions-item>
-      <el-descriptions-item label="维修电话">{{ data.repairMobile }} </el-descriptions-item>
-      <el-descriptions-item label="详细地址">{{ data.addressDetail }} </el-descriptions-item>
+      <el-descriptions-item :label="$t('message.projects.detail.info.descriptions.name')">{{ data.name }} </el-descriptions-item>
+      <el-descriptions-item :label="$t('message.projects.detail.info.descriptions.customName')">{{ data.customName }} </el-descriptions-item>
+      <el-descriptions-item :label="$t('message.projects.detail.info.descriptions.address')">{{ data.address ? JSON.parse(data.address)?.name : '' }} </el-descriptions-item>
+      <el-descriptions-item :label="$t('message.projects.detail.info.descriptions.channelMerchants')">{{ data.channelMerchants }} </el-descriptions-item>
+      <el-descriptions-item :label="$t('message.projects.detail.info.descriptions.repairCompany')">{{ data.repairCompany }} </el-descriptions-item>
+      <el-descriptions-item :label="$t('message.projects.detail.info.descriptions.repairMobile')">{{ data.repairMobile }} </el-descriptions-item>
+      <el-descriptions-item :label="$t('message.projects.detail.info.descriptions.addressDetail')">{{ data.addressDetail }} </el-descriptions-item>
     </el-descriptions>
     <EditForm ref="editFormRef" @getList="getInfo()"></EditForm>
   </div>

+ 32 - 31
src/views/iot/projects/detail/scene.vue

@@ -1,45 +1,45 @@
 <template>
   <div class="tab-content h-full">
-    <div class="subtitle"><span></span> <el-button type="primary" v-auth="'add'" size="small" @click="addDevice()">添加场景</el-button></div>
+    <div class="subtitle"><span></span> <el-button type="primary" v-auth="'add'" size="small" @click="addDevice()">{{ $t('message.projects.detail.scene.actions.add') }}</el-button></div>
     <el-table :data="tableData" style="width: 100%" v-loading="loading" max-height="calc(100vh - 280px)">
-      <el-table-column prop="id" label="ID" min-width="100" show-overflow-tooltip></el-table-column>
-      <el-table-column prop="name" label="场景名称" show-overflow-tooltip></el-table-column>
-      <el-table-column prop="sceneType" label="触发方式" align="center">
+      <el-table-column prop="id" :label="$t('message.projects.detail.scene.table.columns.id')" min-width="100" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="name" :label="$t('message.projects.detail.scene.table.columns.name')" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="sceneType" :label="$t('message.projects.detail.scene.table.columns.sceneType')" align="center">
         <template #default="scope">
-          <el-tag size="small" v-if="scope.row.sceneType == 'device'">设备触发</el-tag>
-          <el-tag size="small" v-if="scope.row.sceneType == 'manual'">手动触发</el-tag>
-          <el-tag size="small" v-if="scope.row.sceneType == 'timer'">定时触发</el-tag>
+          <el-tag size="small" v-if="scope.row.sceneType == 'device'">{{ $t('message.projects.detail.scene.table.sceneTypeTags.device') }}</el-tag>
+          <el-tag size="small" v-if="scope.row.sceneType == 'manual'">{{ $t('message.projects.detail.scene.table.sceneTypeTags.manual') }}</el-tag>
+          <el-tag size="small" v-if="scope.row.sceneType == 'timer'">{{ $t('message.projects.detail.scene.table.sceneTypeTags.timer') }}</el-tag>
         </template>
       </el-table-column>
 
-      <el-table-column prop="status" label="状态" align="center">
+      <el-table-column prop="status" :label="$t('message.projects.detail.scene.table.columns.status')" align="center">
         <template #default="scope">
-          <el-tag size="small" type="success" v-if="scope.row.status == 1">启用</el-tag>
-          <el-tag size="small" type="info" v-if="scope.row.status == 0">禁用</el-tag>
+          <el-tag size="small" type="success" v-if="scope.row.status == 1">{{ $t('message.projects.detail.scene.table.statusTags.enabled') }}</el-tag>
+          <el-tag size="small" type="info" v-if="scope.row.status == 0">{{ $t('message.projects.detail.scene.table.statusTags.disabled') }}</el-tag>
         </template>
       </el-table-column>
-      <el-table-column prop="description" label="场景描述" show-overflow-tooltip></el-table-column>
-      <el-table-column prop="createdAt" label="创建时间" width="160" align="center"></el-table-column>
-      <el-table-column label="操作" width="120" align="center">
+      <el-table-column prop="description" :label="$t('message.projects.detail.scene.table.columns.description')" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="createdAt" :label="$t('message.projects.detail.scene.table.columns.createdAt')" width="160" align="center"></el-table-column>
+      <el-table-column :label="$t('message.projects.detail.scene.table.columns.actions')" width="150" align="center">
         <template #default="scope">
-          <el-button size="small" text type="primary" v-auth="'detail'" @click="$router.push('/iotmanager/scene/manage/' + scope.row.id)">场景详情</el-button>
-          <el-button size="small" text type="warning" v-auth="'del'" @click="onDel(scope.row)">解绑</el-button>
+          <el-button size="small" text type="primary" v-auth="'detail'" @click="$router.push('/iotmanager/scene/manage/' + scope.row.id)">{{ $t('message.projects.detail.scene.table.actions.detail') }}</el-button>
+          <el-button size="small" text type="warning" v-auth="'del'" @click="onDel(scope.row)">{{ $t('message.projects.detail.scene.table.actions.unbind') }}</el-button>
         </template>
       </el-table-column>
     </el-table>
     <pagination v-if="params.total" :total="params.total" v-model:page="params.pageNum" v-model:limit="params.pageSize" @pagination="getList()" />
-    <el-dialog title="添加场景" v-model="isShowDialog" width="400">
+    <el-dialog :title="$t('message.projects.detail.scene.dialog.title')" v-model="isShowDialog" width="400">
       <el-form v-if="isShowDialog">
-        <el-form-item label="场景" style="margin-bottom: 0;">
-          <el-select v-model="form.resourcesKey" filterable placeholder="选择场景" :clearable="false" style="width: 100%">
+        <el-form-item :label="$t('message.projects.detail.scene.form.labels.scene')" style="margin-bottom: 0;">
+          <el-select v-model="form.resourcesKey" filterable :placeholder="$t('message.projects.detail.scene.form.placeholders.scene')" :clearable="false" style="width: 100%">
             <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
           </el-select>
         </el-form-item>
       </el-form>
       <template #footer>
         <span class="dialog-footer">
-          <el-button @click="onCancel">取 消</el-button>
-          <el-button type="primary" @click="onSubmit">确 定</el-button>
+          <el-button @click="onCancel">{{ $t('message.projects.detail.scene.actions.cancel') }}</el-button>
+          <el-button type="primary" @click="onSubmit">{{ $t('message.projects.detail.scene.actions.confirm') }}</el-button>
         </span>
       </template>
     </el-dialog>
@@ -53,18 +53,19 @@ import api from '/@/api/projects';
 import { useSearch } from '/@/hooks/useCommon';
 import { useRoute } from 'vue-router';
 import { ElMessage, ElMessageBox } from 'element-plus';
+import { useI18n } from 'vue-i18n';
 const route = useRoute();
+const { t } = useI18n();
 
 const props = defineProps({ resourcesTypes: Number })
 const isShowDialog = ref(false)
 const options = ref<any[]>([])
-const resourcesTypes = props.resourcesTypes
 const projectsCode = route.params.id
 
 const baseForm = {
   resourcesKey: '',
   projectsCode,
-  resourcesTypes
+  resourcesTypes: props.resourcesTypes
 }
 
 const form = reactive({
@@ -78,7 +79,7 @@ getSourceList();
 
 function getSourceList() {
   api.getProjectResourcesByCode({ projectsCode }).then((res: any) => {
-    params.ids = (res || []).filter((item: any) => item.resourcesTypes === resourcesTypes).map((item: any) => item.resourcesKey)
+    params.ids = (res || []).filter((item: any) => item.resourcesTypes === props.resourcesTypes).map((item: any) => item.resourcesKey)
     if (!params.ids.length) {
       params.pageNum = 1
       tableData.value = []
@@ -101,11 +102,11 @@ function onCancel() {
 }
 
 function onSubmit() {
-  if (!form.resourcesKey) return ElMessage('请先选择场景')
-  api.bindResources(form).then((res: any) => {
+  if (!form.resourcesKey) return ElMessage(t('message.projects.detail.scene.messages.selectSceneFirst'))
+  api.bindResources(form).then(() => {
     onCancel()
     getSourceList()
-    ElMessage.success('添加成功')
+    ElMessage.success(t('message.projects.detail.scene.messages.addSuccess'))
   })
 }
 
@@ -114,18 +115,18 @@ function addDevice() {
 }
 
 function onDel(row: any) {
-  ElMessageBox.confirm(`确定要解绑该场景?`, '提示', {
-    confirmButtonText: '确认',
-    cancelButtonText: '取消',
+  ElMessageBox.confirm(t('message.projects.detail.scene.messages.unbindConfirm'), t('message.projects.detail.scene.messages.tip'), {
+    confirmButtonText: t('message.projects.detail.scene.messages.confirm'),
+    cancelButtonText: t('message.projects.detail.scene.messages.cancel'),
     type: 'warning',
   }).then(() => {
     api.unbindResources({
       projectsCode,
-      resourcesTypes,
+      resourcesTypes: props.resourcesTypes,
       resourcesKey: row.id
     }).then(() => {
       getSourceList()
-      ElMessage.success('解绑成功')
+      ElMessage.success(t('message.projects.detail.scene.messages.unbindSuccess'))
     })
   });
 }

+ 28 - 26
src/views/iot/projects/detail/topo.vue

@@ -1,32 +1,32 @@
 <template>
   <div class="tab-content h-full">
-    <div class="subtitle"><span></span> <el-button type="primary" v-auth="'add'" size="small" @click="addDevice()">添加组态</el-button></div>
+    <div class="subtitle"><span></span> <el-button type="primary" v-auth="'add'" size="small" @click="addDevice()">{{ $t('message.projects.detail.topo.actions.add') }}</el-button></div>
     <el-table :data="tableData" style="width: 100%" v-loading="loading" max-height="calc(100vh - 280px)">
-      <el-table-column prop="id" label="ID" width="100" show-overflow-tooltip></el-table-column>
-      <el-table-column prop="name" label="组态图名称" show-overflow-tooltip></el-table-column>
-      <el-table-column prop="createdAt" label="创建时间" min-width="100" align="center"></el-table-column>
-      <el-table-column prop="updatedAt" label="更新时间" min-width="100" align="center"></el-table-column>
-      <el-table-column label="操作" width="170" align="center">
+      <el-table-column prop="id" :label="$t('message.projects.detail.topo.table.columns.id')" width="100" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="name" :label="$t('message.projects.detail.topo.table.columns.name')" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="createdAt" :label="$t('message.projects.detail.topo.table.columns.createdAt')" min-width="100" align="center"></el-table-column>
+      <el-table-column prop="updatedAt" :label="$t('message.projects.detail.topo.table.columns.updatedAt')" min-width="100" align="center"></el-table-column>
+      <el-table-column :label="$t('message.projects.detail.topo.table.columns.actions')" width="210" align="center">
         <template #default="scope">
-          <el-button size="small" text type="primary" v-auth="'detail'" v-if="!scope.row.folderName" @click="view(scope.row)">预览</el-button>
-          <el-button size="small" text type="primary" v-auth="'edit'" @click="edit(scope.row)">编辑组态图</el-button>
-          <el-button size="small" text type="warning" v-auth="'del'" @click="onDel(scope.row)">解绑</el-button>
+          <el-button size="small" text type="primary" v-auth="'detail'" v-if="!scope.row.folderName" @click="view(scope.row)">{{ $t('message.projects.detail.topo.table.actions.preview') }}</el-button>
+          <el-button size="small" text type="primary" v-auth="'edit'" @click="edit(scope.row)">{{ $t('message.projects.detail.topo.table.actions.edit') }}</el-button>
+          <el-button size="small" text type="warning" v-auth="'del'" @click="onDel(scope.row)">{{ $t('message.projects.detail.topo.table.actions.unbind') }}</el-button>
         </template>
       </el-table-column>
     </el-table>
     <pagination v-if="params.total" :total="params.total" v-model:page="params.pageNum" v-model:limit="params.pageSize" @pagination="getList()" />
-    <el-dialog title="添加组态" v-model="isShowDialog" width="400">
+    <el-dialog :title="$t('message.projects.detail.topo.dialog.title')" v-model="isShowDialog" width="400">
       <el-form v-if="isShowDialog">
-        <el-form-item label="组态" style="margin-bottom: 0;">
-          <el-select v-model="form.resourcesKey" filterable placeholder="选择组态" :clearable="false" style="width: 100%">
+        <el-form-item :label="$t('message.projects.detail.topo.form.labels.topo')" style="margin-bottom: 0;">
+          <el-select v-model="form.resourcesKey" filterable :placeholder="$t('message.projects.detail.topo.form.placeholders.topo')" :clearable="false" style="width: 100%">
             <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
           </el-select>
         </el-form-item>
       </el-form>
       <template #footer>
         <span class="dialog-footer">
-          <el-button @click="onCancel">取 消</el-button>
-          <el-button type="primary" @click="onSubmit">确 定</el-button>
+          <el-button @click="onCancel">{{ $t('message.projects.detail.topo.actions.cancel') }}</el-button>
+          <el-button type="primary" @click="onSubmit">{{ $t('message.projects.detail.topo.actions.confirm') }}</el-button>
         </span>
       </template>
     </el-dialog>
@@ -34,25 +34,27 @@
 </template>
 
 <script lang="ts" setup>
-import { reactive, ref } from 'vue';
+import { reactive, ref, toRef } from 'vue';
 import topoApi from '/@/api/configuration';
 import api from '/@/api/projects';
 import { useSearch } from '/@/hooks/useCommon';
 import { useRoute } from 'vue-router';
 import { ElMessage, ElMessageBox } from 'element-plus';
+import { useI18n } from 'vue-i18n';
 import getOrigin from '/@/utils/origin'
 const route = useRoute();
+const { t } = useI18n();
 
 const props = defineProps({ resourcesTypes: Number })
 const isShowDialog = ref(false)
 const options = ref<any[]>([])
-const resourcesTypes = props.resourcesTypes
+const resourcesTypes = toRef(props, 'resourcesTypes')
 const projectsCode = route.params.id
 
 const baseForm = {
   resourcesKey: '',
   projectsCode,
-  resourcesTypes
+  resourcesTypes: resourcesTypes.value
 }
 
 const form = reactive({
@@ -66,7 +68,7 @@ getSourceList();
 
 function getSourceList() {
   api.getProjectResourcesByCode({ projectsCode }).then((res: any) => {
-    params.ids = (res || []).filter((item: any) => item.resourcesTypes === resourcesTypes).map((item: any) => item.resourcesKey)
+    params.ids = (res || []).filter((item: any) => item.resourcesTypes === resourcesTypes.value).map((item: any) => item.resourcesKey)
     if (!params.ids.length) {
       params.pageNum = 1
       tableData.value = []
@@ -88,11 +90,11 @@ function onCancel() {
 }
 
 function onSubmit() {
-  if (!form.resourcesKey) return ElMessage('请先选择组态')
-  api.bindResources(form).then((res: any) => {
+  if (!form.resourcesKey) return ElMessage(t('message.projects.detail.topo.messages.selectTopoFirst'))
+  api.bindResources(form).then(() => {
     onCancel()
     getSourceList()
-    ElMessage.success('添加成功')
+    ElMessage.success(t('message.projects.detail.topo.messages.addSuccess'))
   })
 }
 
@@ -116,18 +118,18 @@ const edit = (row: any) => {
 };
 
 function onDel(row: any) {
-  ElMessageBox.confirm(`确定要解绑该组态?`, '提示', {
-    confirmButtonText: '确认',
-    cancelButtonText: '取消',
+  ElMessageBox.confirm(t('message.projects.detail.topo.messages.unbindConfirm'), t('message.projects.detail.topo.messages.tip'), {
+    confirmButtonText: t('message.projects.detail.topo.messages.confirm'),
+    cancelButtonText: t('message.projects.detail.topo.messages.cancel'),
     type: 'warning',
   }).then(() => {
     api.unbindResources({
       projectsCode,
-      resourcesTypes,
+      resourcesTypes: resourcesTypes.value,
       resourcesKey: row.id
     }).then(() => {
       getSourceList()
-      ElMessage.success('解绑成功')
+      ElMessage.success(t('message.projects.detail.topo.messages.unbindSuccess'))
     })
   });
 }

+ 34 - 32
src/views/iot/projects/detail/video.vue

@@ -1,42 +1,42 @@
 <template>
   <div class="tab-content h-full">
-    <div class="subtitle"><span></span> <el-button type="primary" v-auth="'add'" size="small" @click="addDevice()">添加监控</el-button></div>
+    <div class="subtitle"><span></span> <el-button type="primary" v-auth="'add'" size="small" @click="addDevice()">{{ $t('message.projects.detail.video.actions.add') }}</el-button></div>
     <el-table :data="tableData" style="width: 100%" v-loading="loading" max-height="calc(100vh - 280px)">
-      <el-table-column prop="name" label="设备名称" min-width="130" align="center" show-overflow-tooltip></el-table-column>
-      <el-table-column prop="deviceId" label="设备ID" min-width="210" align="center" show-overflow-tooltip></el-table-column>
-      <el-table-column prop="name" label="通道名称" min-width="140" align="center" show-overflow-tooltip></el-table-column>
-      <el-table-column prop="model" label="型号" width="100" align="center" show-overflow-tooltip></el-table-column>
-      <el-table-column prop="manufacturer" label="厂商" width="100" align="center" show-overflow-tooltip></el-table-column>
-      <el-table-column prop="liveStatus" label="状态" width="100" align="center" :formatter="formatLiveStatus" show-overflow-tooltip></el-table-column>
-      <el-table-column prop="keepAliveTime" label="最后心跳时间" :formatter="formatTime" width="170" align="center" show-overflow-tooltip></el-table-column>
-      <el-table-column prop="registerTime" label="注册时间" :formatter="formatTime" width="170" align="center" show-overflow-tooltip></el-table-column>
-      <el-table-column prop="updateAt" label="更新时间" :formatter="formatTime" width="170" align="center" show-overflow-tooltip></el-table-column>
-      <el-table-column label="操作" width="120" align="center" fixed="right">
+      <el-table-column prop="name" :label="$t('message.projects.detail.video.table.columns.deviceName')" min-width="130" align="center" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="deviceId" :label="$t('message.projects.detail.video.table.columns.deviceId')" min-width="210" align="center" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="name" :label="$t('message.projects.detail.video.table.columns.channelName')" min-width="140" align="center" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="model" :label="$t('message.projects.detail.video.table.columns.model')" width="100" align="center" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="manufacturer" :label="$t('message.projects.detail.video.table.columns.manufacturer')" width="100" align="center" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="liveStatus" :label="$t('message.projects.detail.video.table.columns.liveStatus')" width="100" align="center" :formatter="formatLiveStatus" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="keepAliveTime" :label="$t('message.projects.detail.video.table.columns.keepAliveTime')" :formatter="formatTime" width="170" align="center" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="registerTime" :label="$t('message.projects.detail.video.table.columns.registerTime')" :formatter="formatTime" width="170" align="center" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="updateAt" :label="$t('message.projects.detail.video.table.columns.updateAt')" :formatter="formatTime" width="170" align="center" show-overflow-tooltip></el-table-column>
+      <el-table-column :label="$t('message.projects.detail.video.table.columns.actions')" width="120" align="center" fixed="right">
         <template #default="scope">
-          <el-button size="small" text type="primary" v-auth="'detail'" @click="view(scope.row)">查看监控</el-button>
-          <el-button size="small" text type="warning" v-auth="'del'" @click="onDel(scope.row)">解绑</el-button>
+          <el-button size="small" text type="primary" v-auth="'detail'" @click="view(scope.row)">{{ $t('message.projects.detail.video.table.actions.view') }}</el-button>
+          <el-button size="small" text type="warning" v-auth="'del'" @click="onDel(scope.row)">{{ $t('message.projects.detail.video.table.actions.unbind') }}</el-button>
         </template>
       </el-table-column>
     </el-table>
-    <el-dialog title="添加监控" v-model="isShowDialog" width="400">
+    <el-dialog :title="$t('message.projects.detail.video.dialog.addTitle')" v-model="isShowDialog" width="400">
       <el-form v-if="isShowDialog">
-        <el-form-item label="监控" style="margin-bottom: 0">
-          <el-select v-model="form.resourcesKey" filterable placeholder="选择监控" :clearable="false" style="width: 100%">
+        <el-form-item :label="$t('message.projects.detail.video.form.labels.video')" style="margin-bottom: 0">
+          <el-select v-model="form.resourcesKey" filterable :placeholder="$t('message.projects.detail.video.form.placeholders.video')" :clearable="false" style="width: 100%">
             <el-option v-for="item in options" :disabled="item.disabled" :key="item.value" :label="item.label" :value="item.value" />
           </el-select>
         </el-form-item>
       </el-form>
       <template #footer>
         <span class="dialog-footer">
-          <el-button @click="onCancel">取 消</el-button>
-          <el-button type="primary" @click="onSubmit">确 定</el-button>
+          <el-button @click="onCancel">{{ $t('message.projects.detail.video.actions.cancel') }}</el-button>
+          <el-button type="primary" @click="onSubmit">{{ $t('message.projects.detail.video.actions.confirm') }}</el-button>
         </span>
       </template>
     </el-dialog>
-    <el-dialog title="预览监控" v-model="isShowPreviewDialog" width="90vh">
+    <el-dialog :title="$t('message.projects.detail.video.dialog.previewTitle')" v-model="isShowPreviewDialog" width="90vh">
       <template #header>
         <span class="dialog-footer">
-          <el-button type="primary" size="small" @click="openToNewWindow">新窗口预览</el-button>
+          <el-button type="primary" size="small" @click="openToNewWindow">{{ $t('message.projects.detail.video.actions.openInNewWindow') }}</el-button>
         </span>
       </template>
       <iframe v-if="isShowPreviewDialog" :src="previewUrl" style="width: 100%; height: 50vh"></iframe>
@@ -45,13 +45,15 @@
 </template>
 
 <script lang="ts" setup>
-import { reactive, ref } from "vue";
+import { reactive, ref, toRef } from "vue";
 import api from "/@/api/projects";
 import { useRoute } from "vue-router";
 import { dayjs, ElMessage, ElMessageBox } from "element-plus";
 import axios from "axios";
 import { getMediaOrigin } from "/@/utils/origin";
+import { useI18n } from 'vue-i18n';
 const route = useRoute();
+const { t } = useI18n();
 
 const props = defineProps({ resourcesTypes: Number });
 const isShowDialog = ref(false);
@@ -61,13 +63,13 @@ const previewUrl = ref("");
 const options = ref<any[]>([]);
 const tableData = ref<any[]>([]);
 const channels = ref<any[]>([]);
-const resourcesTypes = props.resourcesTypes!;
+const resourcesTypes = toRef(props, 'resourcesTypes');
 const projectsCode = route.params.id;
 
 const baseForm = {
   resourcesKey: "",
   projectsCode,
-  resourcesTypes,
+  resourcesTypes: resourcesTypes.value,
 };
 
 const form = reactive({
@@ -78,7 +80,7 @@ getOptions();
 
 function getSourceList() {
   api.getProjectResourcesByCode({ projectsCode }).then((res: any) => {
-    const ids = (res || []).filter((item: any) => item.resourcesTypes === resourcesTypes).map((item: any) => item.resourcesKey);
+    const ids = (res || []).filter((item: any) => item.resourcesTypes === resourcesTypes.value).map((item: any) => item.resourcesKey);
 
     const tableDataList: any[] = [];
 
@@ -126,11 +128,11 @@ function onCancel() {
 }
 
 function onSubmit() {
-  if (!form.resourcesKey) return ElMessage("请先选择监控");
+  if (!form.resourcesKey) return ElMessage(t('message.projects.detail.video.messages.selectVideoFirst'));
   api.bindResources(form).then(() => {
     onCancel();
     getSourceList();
-    ElMessage.success("添加成功");
+    ElMessage.success(t('message.projects.detail.video.messages.addSuccess'));
   });
 }
 
@@ -149,20 +151,20 @@ function openToNewWindow() {
 }
 
 function onDel(row: any) {
-  ElMessageBox.confirm(`确定要解绑该监控?`, "提示", {
-    confirmButtonText: "确认",
-    cancelButtonText: "取消",
+  ElMessageBox.confirm(t('message.projects.detail.video.messages.unbindConfirm'), t('message.projects.detail.video.messages.tip'), {
+    confirmButtonText: t('message.projects.detail.video.messages.confirm'),
+    cancelButtonText: t('message.projects.detail.video.messages.cancel'),
     type: "warning",
   }).then(() => {
     api
       .unbindResources({
         projectsCode,
-        resourcesTypes,
+        resourcesTypes: resourcesTypes.value,
         resourcesKey: row.deviceId + "-" + row.channelId,
       })
       .then(() => {
         getSourceList();
-        ElMessage.success("解绑成功");
+        ElMessage.success(t('message.projects.detail.video.messages.unbindSuccess'));
       });
   });
 }
@@ -173,6 +175,6 @@ function formatTime(row: any, column: any) {
 
 function formatLiveStatus(row: any, column: any) {
   // 0 代表空闲,1 代表正在调用 invite,2 代表正在拉流
-  return row[column.property] === 0 ? "空闲" : row[column.property] === 1 ? "调用中" : "拉流中";
+  return row[column.property] === 0 ? t('message.projects.detail.video.liveStatusTags.idle') : row[column.property] === 1 ? t('message.projects.detail.video.liveStatusTags.inviting') : t('message.projects.detail.video.liveStatusTags.streaming');
 }
 </script>

+ 22 - 18
src/views/iot/projects/filter/attrEdit.vue

@@ -1,18 +1,18 @@
 <template>
-  <el-dialog class="api-edit" v-model="showDialog" :title="`${formData?.id ? '编辑属性' : '新增属性'}`" width="500px" :close-on-click-modal="false" :close-on-press-escape="false">
-    <el-form ref="formRef" :model="formData" :rules="ruleForm" label-width="80px">
-      <el-form-item label="属性名称" prop="name">
-        <el-input v-model.trim="formData.name" placeholder="输入属性名称" />
+  <el-dialog class="api-edit" v-model="showDialog" :title="formData?.id ? $t('message.projects.filter.attrEdit.titleEdit') : $t('message.projects.filter.attrEdit.titleAdd')" width="500px" :close-on-click-modal="false" :close-on-press-escape="false">
+    <el-form ref="formRef" :model="formData" :rules="ruleForm" label-width="120px">
+      <el-form-item :label="$t('message.projects.filter.attrEdit.labels.name')" prop="name">
+        <el-input v-model.trim="formData.name" :placeholder="$t('message.projects.filter.attrEdit.placeholders.name')" />
       </el-form-item>
-      <el-form-item label="属性KEY" prop="key">
-        <el-input v-model.trim="formData.key" placeholder="输入属性名称" />
+      <el-form-item :label="$t('message.projects.filter.attrEdit.labels.key')" prop="key">
+        <el-input v-model.trim="formData.key" :placeholder="$t('message.projects.filter.attrEdit.placeholders.key')" />
       </el-form-item>
-      <el-form-item label="单位" prop="unit">
-        <el-input v-model.trim="formData.unit" placeholder="输入属性名称" />
+      <el-form-item :label="$t('message.projects.filter.attrEdit.labels.unit')" prop="unit">
+        <el-input v-model.trim="formData.unit" :placeholder="$t('message.projects.filter.attrEdit.placeholders.unit')" />
       </el-form-item>
       <el-form-item label="" prop="">
-        <el-button @click="cancle">取消</el-button>
-        <el-button type="primary" @click="onSubmit">确定</el-button>
+        <el-button @click="cancel">{{ $t('message.projects.filter.attrEdit.actions.cancel') }}</el-button>
+        <el-button type="primary" @click="onSubmit">{{ $t('message.projects.filter.attrEdit.actions.confirm') }}</el-button>
       </el-form-item>
     </el-form>
   </el-dialog>
@@ -20,14 +20,16 @@
 
 <script lang="ts" setup>
 import api from '/@/api/projects';
-import { ref, reactive } from 'vue';
+import { ref, reactive, computed } from 'vue';
 import { ruleRequired } from '/@/utils/validator';
 import { ElMessage } from 'element-plus';
+import { useI18n } from 'vue-i18n';
 
 const emit = defineEmits(['getList']);
 
 const showDialog = ref(false);
 const formRef = ref();
+const { t } = useI18n();
 
 const baseForm = {
   id: undefined,
@@ -41,11 +43,13 @@ const formData = reactive({
   ...baseForm,
 });
 
-const ruleForm = {
-  name: [ruleRequired('不能为空')],
-  key: [ruleRequired('不能为空')],
-  unit: [ruleRequired('不能为空')],
-};
+const ruleForm = computed(() => {
+  return {
+    name: [ruleRequired(t('message.projects.filter.attrEdit.rules.required'))],
+    key: [ruleRequired(t('message.projects.filter.attrEdit.rules.required'))],
+    unit: [ruleRequired(t('message.projects.filter.attrEdit.rules.required'))],
+  };
+});
 
 const onSubmit = async () => {
   await formRef.value.validate();
@@ -54,7 +58,7 @@ const onSubmit = async () => {
 
   await theApi(formData);
 
-  ElMessage.success('操作成功');
+  ElMessage.success(t('message.projects.filter.attrEdit.messages.success'));
   // resetForm();
   showDialog.value = false;
   emit('getList');
@@ -78,7 +82,7 @@ const open = async (code: string, row?: any) => {
   }
 };
 
-function cancle() {
+function cancel() {
   showDialog.value = false;
 }
 

+ 18 - 32
src/views/iot/projects/filter/bindDevice.vue

@@ -1,20 +1,20 @@
 <template>
-  <el-dialog title="绑定设备" v-model="isShowDialog" width="1200">
-    <el-divider content-position="left" style="margin-top: 0;">设备数据过滤器模板信息</el-divider>
+  <el-dialog :title="$t('message.projects.filter.bindDevice.dialog.title')" v-model="isShowDialog" width="1200">
+    <el-divider content-position="left" style="margin-top: 0;">{{ $t('message.projects.filter.bindDevice.sections.templateInfo') }}</el-divider>
     <el-descriptions :column="3" border>
-      <el-descriptions-item label="模板名称">{{ data.name }}</el-descriptions-item>
-      <el-descriptions-item label="模板编码">{{ data.code }}</el-descriptions-item>
-      <el-descriptions-item label="更新时间">{{ data.updatedAt }}</el-descriptions-item>
+      <el-descriptions-item :label="$t('message.projects.filter.bindDevice.descriptions.templateName')">{{ data.name }}</el-descriptions-item>
+      <el-descriptions-item :label="$t('message.projects.filter.bindDevice.descriptions.templateCode')">{{ data.code }}</el-descriptions-item>
+      <el-descriptions-item :label="$t('message.projects.filter.bindDevice.descriptions.updatedAt')">{{ data.updatedAt }}</el-descriptions-item>
     </el-descriptions>
-    <el-divider content-position="left" style="margin-top: 30px;">绑定设备</el-divider>
-    <div class="flex-row"><span></span> <el-button type="primary" size="small" v-auth="'add'" @click="addOrEdit(data.code)">绑定设备</el-button></div>
+    <el-divider content-position="left" style="margin-top: 30px;">{{ $t('message.projects.filter.bindDevice.sections.bindDevice') }}</el-divider>
+    <div class="flex-row"><span></span> <el-button type="primary" size="small" v-auth="'add'" @click="addOrEdit(data.code)">{{ $t('message.projects.filter.bindDevice.actions.bindDevice') }}</el-button></div>
     <el-table :data="tableData" style="width: 100%;margin-top: 20px;" max-height="50vh" v-loading="loading">
-      <el-table-column prop="name" label="设备名称" show-overflow-tooltip></el-table-column>
-      <el-table-column prop="key" label="设备编码" align="center" show-overflow-tooltip></el-table-column>
-      <el-table-column prop="productName" label="所属产品" align="center"></el-table-column>
-      <el-table-column label="操作" width="120" align="center" fixed="right">
+      <el-table-column prop="name" :label="$t('message.projects.filter.bindDevice.table.columns.name')" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="key" :label="$t('message.projects.filter.bindDevice.table.columns.key')" align="center" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="productName" :label="$t('message.projects.filter.bindDevice.table.columns.productName')" align="center"></el-table-column>
+      <el-table-column :label="$t('message.projects.filter.bindDevice.table.columns.actions')" width="120" align="center" fixed="right">
         <template #default="{ row }">
-          <el-button size="small" text type="danger" v-auth="'del'" @click="del(row.key)">解绑</el-button>
+          <el-button size="small" text type="danger" v-auth="'del'" @click="del(row.key)">{{ $t('message.projects.filter.bindDevice.table.actions.unbind') }}</el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -25,19 +25,21 @@
 
 <script lang="ts" setup>
 import { computed, ref } from 'vue';
+import { useI18n } from 'vue-i18n';
 import api from '/@/api/projects';
 import bindDeviceForm from './bindDeviceForm.vue';
 import { useSearch } from '/@/hooks/useCommon';
-import { ElMessageBox, ElMessage } from 'element-plus';
+import { ElMessage } from 'element-plus';
 
 const { params, tableData, getList, loading } = useSearch<any[]>(api.template.getBindDevices, '', { code: '' });
+const { t } = useI18n();
 
 const isShowDialog = ref(false);
 const editFormRef = ref();
 const data = ref<any>({});
 
 const hasBindKeys = computed(() => {
-  return tableData.value.map(item => item.key);
+  return tableData.value.map((item: any) => item.key);
 });
 
 function show(row: any) {
@@ -51,27 +53,11 @@ const addOrEdit = (code: string, row?: any) => {
   editFormRef.value.open(code, row);
 };
 
-const handleStatusChange = (row: any) => {
-  let text = row.isVisible === 1 ? '设置可见' : '设置不可见';
-  ElMessageBox.confirm('确认要' + text + '属性:【' + row.name + '】吗?', '警告', {
-    confirmButtonText: '确定',
-    cancelButtonText: '取消',
-    type: 'warning',
-  })
-    .then(function () {
-      return api.template.attr.editIsVisible({ id: row.id, isVisible: row.isVisible });
-    })
-    .then(() => {
-      ElMessage.success(text + '成功');
-    })
-    .catch(function () {
-      row.isVisible = row.isVisible === 0 ? 1 : 0;
-    });
-};
+
 
 const del = async (code: string) => {
   await api.template.unBindDevices({ code: params.code, deviceKeys: [code] });
-  ElMessage.success('删除成功');
+  ElMessage.success(t('message.projects.filter.bindDevice.messages.unbindSuccess'));
   getList(1);
 };
 

+ 11 - 9
src/views/iot/projects/filter/bindDeviceForm.vue

@@ -1,17 +1,17 @@
 <template>
-  <el-dialog class="api-edit" v-model="showDialog" title="绑定设备" width="800px" :close-on-click-modal="false" :close-on-press-escape="false">
+  <el-dialog class="api-edit" v-model="showDialog" :title="$t('message.projects.filter.bindDeviceForm.dialog.title')" width="900px" :close-on-click-modal="false" :close-on-press-escape="false">
     <el-table :data="tableData" v-if="showDialog" style="width: 100%" row-key="key" @selection-change="handleSelectionChange" v-loading="loading">
       <el-table-column type="selection" reserve-selection :selectable="(row: any) => !hasBindKeys.includes(row.key)" width="55" align="center" />
-      <el-table-column prop="name" label="设备名称" show-overflow-tooltip></el-table-column>
-      <el-table-column prop="key" label="设备编码" align="center" show-overflow-tooltip></el-table-column>
-      <el-table-column prop="productName" label="所属产品" align="center"></el-table-column>
+      <el-table-column prop="name" :label="$t('message.projects.filter.bindDeviceForm.table.columns.name')" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="key" :label="$t('message.projects.filter.bindDeviceForm.table.columns.key')" align="center" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="productName" :label="$t('message.projects.filter.bindDeviceForm.table.columns.productName')" align="center"></el-table-column>
     </el-table>
     <div class="flex-row">
       <pagination v-if="params.total" :total="params.total" v-model:page="params.pageNum" v-model:limit="params.pageSize" @pagination="getList()" />
     </div>
     <div style="float: right">
-      <el-button @click="cancle">取消</el-button>
-      <el-button type="primary" @click="onSubmit">绑定设备</el-button>
+      <el-button @click="cancel">{{ $t('message.projects.filter.bindDeviceForm.actions.cancel') }}</el-button>
+      <el-button type="primary" @click="onSubmit">{{ $t('message.projects.filter.bindDeviceForm.actions.bind') }}</el-button>
     </div>
   </el-dialog>
 </template>
@@ -19,7 +19,8 @@
 <script lang="ts" setup>
 import api from '/@/api/projects';
 import deviceApi from '/@/api/device';
-import { ref, reactive } from 'vue';
+import { ref } from 'vue';
+import { useI18n } from 'vue-i18n';
 import { ElMessage } from 'element-plus';
 import { useSearch } from '/@/hooks/useCommon';
 
@@ -32,6 +33,7 @@ defineProps({
   },
 });
 
+const { t } = useI18n();
 const showDialog = ref(false);
 const code = ref('');
 const keys = ref<any[]>([]);
@@ -47,7 +49,7 @@ const onSubmit = async () => {
 
   await api.template.bindDevices({ code: code.value, deviceKeys: keys.value });
 
-  ElMessage.success('操作成功');
+  ElMessage.success(t('message.projects.filter.bindDeviceForm.messages.bindSuccess'));
   // resetForm();
   showDialog.value = false;
   emit('getList');
@@ -60,7 +62,7 @@ const open = async (templateseCode: string) => {
   getList();
 };
 
-function cancle() {
+function cancel() {
   showDialog.value = false;
 }
 

+ 39 - 25
src/views/iot/projects/filter/detail.vue

@@ -1,28 +1,28 @@
 <template>
-  <el-dialog title="模板详情" v-model="isShowDialog" width="1200">
-    <el-divider content-position="left" style="margin-top: 0;">设备数据过滤器模板信息</el-divider>
+  <el-dialog :title="$t('message.projects.filter.detail.dialog.title')" v-model="isShowDialog" width="1200">
+    <el-divider content-position="left" style="margin-top: 0;">{{ $t('message.projects.filter.detail.sections.templateInfo') }}</el-divider>
     <el-descriptions :column="3" border>
-      <el-descriptions-item label="模板名称">{{ data.name }}</el-descriptions-item>
-      <el-descriptions-item label="模板编码">{{ data.code }}</el-descriptions-item>
-      <el-descriptions-item label="更新时间">{{ data.updatedAt }}</el-descriptions-item>
+      <el-descriptions-item :label="$t('message.projects.filter.detail.descriptions.templateName')">{{ data.name }}</el-descriptions-item>
+      <el-descriptions-item :label="$t('message.projects.filter.detail.descriptions.templateCode')">{{ data.code }}</el-descriptions-item>
+      <el-descriptions-item :label="$t('message.projects.filter.detail.descriptions.updatedAt')">{{ data.updatedAt }}</el-descriptions-item>
     </el-descriptions>
-    <el-divider content-position="left" style="margin-top: 30px;">自定义属性</el-divider>
-    <div class="flex-row"><span></span> <el-button type="primary" size="small" v-auth="'add'" @click="addOrEdit(data.code)">添加自定义属性</el-button></div>
+    <el-divider content-position="left" style="margin-top: 30px;">{{ $t('message.projects.filter.detail.sections.customAttr') }}</el-divider>
+    <div class="flex-row"><span></span> <el-button type="primary" size="small" v-auth="'add'" @click="addOrEdit(data.code)">{{ $t('message.projects.filter.detail.actions.addCustomAttr') }}</el-button></div>
     <el-table :data="tableData" style="width: 100%;margin-top: 20px;" v-loading="loading">
-      <el-table-column prop="name" label="属性名称" show-overflow-tooltip></el-table-column>
-      <el-table-column prop="key" label="属性编码" align="center" show-overflow-tooltip></el-table-column>
-      <el-table-column prop="unit" label="单位" align="center" show-overflow-tooltip></el-table-column>
-      <el-table-column prop="updatedAt" label="更新时间" align="center"></el-table-column>
-      <el-table-column prop="isVisible" label="是否可见" align="center">
+      <el-table-column prop="name" :label="$t('message.projects.filter.detail.table.columns.name')" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="key" :label="$t('message.projects.filter.detail.table.columns.key')" align="center" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="unit" :label="$t('message.projects.filter.detail.table.columns.unit')" align="center" show-overflow-tooltip></el-table-column>
+      <el-table-column prop="updatedAt" :label="$t('message.projects.filter.detail.table.columns.updatedAt')" align="center"></el-table-column>
+      <el-table-column prop="isVisible" :label="$t('message.projects.filter.detail.table.columns.isVisible')" align="center">
         <template #default="scope">
-          <el-switch v-model="scope.row.isVisible" inline-prompt :active-value="1" :inactive-value="0" active-text="启" inactive-text="禁" @change="handleStatusChange(scope.row)">
+          <el-switch v-model="scope.row.isVisible" inline-prompt :active-value="1" :inactive-value="0" :active-text="$t('message.projects.filter.detail.switch.active')" :inactive-text="$t('message.projects.filter.detail.switch.inactive')" @change="handleStatusChange(scope.row)">
           </el-switch>
         </template>
       </el-table-column>
-      <el-table-column label="操作" width="120" align="center" fixed="right">
+      <el-table-column :label="$t('message.projects.filter.detail.table.columns.actions')" width="120" align="center" fixed="right">
         <template #default="{ row }">
-          <el-button size="small" text type="warning" v-auth="'edit'" @click="addOrEdit(data.code, row)">编辑</el-button>
-          <el-button size="small" text type="danger" v-auth="'del'" @click="del(row)">删除</el-button>
+          <el-button size="small" text type="warning" v-auth="'edit'" @click="addOrEdit(data.code, row)">{{ $t('message.projects.filter.detail.table.actions.edit') }}</el-button>
+          <el-button size="small" text type="danger" v-auth="'del'" @click="del(row)">{{ $t('message.projects.filter.detail.table.actions.delete') }}</el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -37,6 +37,7 @@ import api from '/@/api/projects';
 import attrEdit from './attrEdit.vue';
 import { useSearch } from '/@/hooks/useCommon';
 import { ElMessageBox, ElMessage } from 'element-plus';
+import { useI18n } from 'vue-i18n';
 
 const { params, tableData, getList, loading } = useSearch<any[]>(api.template.attr.list, 'Data', { keyWord: '', templateseCode: '' });
 
@@ -44,6 +45,7 @@ const isShowDialog = ref(false);
 const editFormRef = ref();
 const ids = ref([]);
 const data = ref<any>({});
+const { t } = useI18n();
 
 function show(row: any) {
   data.value = { ...row }
@@ -57,17 +59,25 @@ const addOrEdit = (code: string, row?: any) => {
 };
 
 const handleStatusChange = (row: any) => {
-  let text = row.isVisible === 1 ? '设置可见' : '设置不可见';
-  ElMessageBox.confirm('确认要' + text + '属性:【' + row.name + '】吗?', '警告', {
-    confirmButtonText: '确定',
-    cancelButtonText: '取消',
+  const isOn = row.isVisible === 1;
+  const title = t('message.projects.filter.detail.messages.warn');
+  const confirmText = isOn
+    ? t('message.projects.filter.detail.messages.confirmSetVisible', { name: row.name })
+    : t('message.projects.filter.detail.messages.confirmSetInvisible', { name: row.name });
+  ElMessageBox.confirm(confirmText, title, {
+    confirmButtonText: t('message.projects.filter.detail.messages.confirm'),
+    cancelButtonText: t('message.projects.filter.detail.messages.cancel'),
     type: 'warning',
   })
     .then(function () {
       return api.template.attr.editIsVisible({ id: row.id, isVisible: row.isVisible });
     })
     .then(() => {
-      ElMessage.success(text + '成功');
+      ElMessage.success(
+        isOn
+          ? t('message.projects.filter.detail.messages.setVisibleSuccess')
+          : t('message.projects.filter.detail.messages.setInvisibleSuccess')
+      );
     })
     .catch(function () {
       row.isVisible = row.isVisible === 0 ? 1 : 0;
@@ -75,13 +85,17 @@ const handleStatusChange = (row: any) => {
 };
 
 const del = (row?: any) => {
-  ElMessageBox.confirm(row ? `此操作将属性:“${row.name}”,是否继续?` : `此操作将${ids.value.length}个属性`, '提示', {
-    confirmButtonText: '确认',
-    cancelButtonText: '取消',
+  const title = t('message.projects.filter.detail.messages.tip');
+  const message = row
+    ? t('message.projects.filter.detail.messages.deleteConfirmSingle', { name: row.name })
+    : t('message.projects.filter.detail.messages.deleteConfirmMultiple', { count: ids.value.length });
+  ElMessageBox.confirm(message, title, {
+    confirmButtonText: t('message.projects.filter.detail.messages.confirm'),
+    cancelButtonText: t('message.projects.filter.detail.messages.cancel'),
     type: 'warning',
   }).then(async () => {
     await api.template.attr.del(row ? [row.id] : ids.value);
-    ElMessage.success('删除成功');
+    ElMessage.success(t('message.projects.filter.detail.messages.deleteSuccess'));
     getList(1);
   });
 };

+ 10 - 3
src/views/iot/projects/filter/edit-dialog.vue

@@ -1,6 +1,13 @@
 <template>
-  <el-dialog class="api-edit" v-model="showDialog" :title="`${formData?.id ? '编辑模板' : '新增模板'}`" width="500px" :close-on-click-modal="false" :close-on-press-escape="false">
-    <EditVue ref="editRef" v-if="showDialog" @getList="getList" @cancle="cancle"></EditVue>
+  <el-dialog
+    class="api-edit"
+    v-model="showDialog"
+    :title="formData?.id ? $t('message.projects.filter.editDialog.title.edit') : $t('message.projects.filter.editDialog.title.add')"
+    width="500px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+  >
+    <EditVue ref="editRef" v-if="showDialog" @getList="getList" @cancel="cancel"></EditVue>
   </el-dialog>
 </template>
 
@@ -27,7 +34,7 @@ function getList() {
   showDialog.value = false;
 }
 
-function cancle() {
+function cancel() {
   showDialog.value = false;
 }
 

+ 17 - 14
src/views/iot/projects/filter/edit.vue

@@ -1,25 +1,27 @@
 <template>
-  <el-form ref="formRef" :model="formData" :rules="ruleForm" label-width="80px">
-    <el-form-item label="模板名称" prop="name">
-      <el-input v-model.trim="formData.name" placeholder="输入模板名称" />
+  <el-form ref="formRef" :model="formData" :rules="ruleForm" label-width="120px">
+    <el-form-item :label="$t('message.projects.filter.edit.form.name')" prop="name">
+      <el-input v-model.trim="formData.name" :placeholder="$t('message.projects.filter.edit.placeholders.name')" />
     </el-form-item>
     <el-form-item label="" prop="">
-      <el-button @click="cancle">取消</el-button>
-      <el-button type="primary" @click="onSubmit">确定</el-button>
+      <el-button @click="cancel">{{ $t('message.projects.filter.edit.actions.cancel') }}</el-button>
+      <el-button type="primary" @click="onSubmit">{{ $t('message.projects.filter.edit.actions.confirm') }}</el-button>
     </el-form-item>
   </el-form>
 </template>
 
 <script lang="ts" setup>
-import { ref, reactive, nextTick } from 'vue';
+import { ref, reactive, computed } from 'vue';
 import api from '/@/api/projects';
 import { ruleRequired } from '/@/utils/validator';
 import { ElMessage } from 'element-plus';
+import { useI18n } from 'vue-i18n';
 
-const emit = defineEmits(['getList', 'cancle']);
+const emit = defineEmits(['getList', 'cancel']);
 
 const showDialog = ref(false);
 const formRef = ref();
+const { t } = useI18n();
 
 const baseForm = {
   id: undefined,
@@ -30,9 +32,11 @@ const formData = reactive({
   ...baseForm,
 });
 
-const ruleForm = {
-  name: [ruleRequired('不能为空')],
-};
+const ruleForm = computed(() => {
+  return {
+    name: [ruleRequired(t('message.projects.filter.edit.rules.required'))],
+  };
+});
 
 const onSubmit = async () => {
   await formRef.value.validate();
@@ -41,7 +45,7 @@ const onSubmit = async () => {
 
   await theApi(formData);
 
-  ElMessage.success('操作成功');
+  ElMessage.success(t('message.projects.filter.edit.messages.success'));
   // resetForm();
   showDialog.value = false;
   emit('getList');
@@ -52,15 +56,14 @@ const resetForm = async () => {
   formRef.value && formRef.value.resetFields();
 };
 
-const cancle = () => {
-  emit('cancle');
+const cancel = () => {
+  emit('cancel');
 };
 
 const open = async (row?: any) => {
   resetForm();
   showDialog.value = true;
   if (row) {
-    console.log(row)
     Object.assign(formData, { ...row });
   }
 };

+ 35 - 32
src/views/iot/projects/filter/index.vue

@@ -4,16 +4,16 @@
       <div class="search">
         <el-form inline>
           <el-form-item>
-            <el-input v-model="params.keyWord" style="width: 200px" placeholder="模板名称搜索" @keyup.enter.native="getList(1)" clearable> </el-input>
+            <el-input v-model="params.keyWord" style="width: 200px" :placeholder="$t('message.projects.filter.index.search.namePlaceholder')" @keyup.enter.native="getList(1)" clearable> </el-input>
           </el-form-item>
           <el-form-item>
-            <el-input v-model="params.deviceKey" style="width: 200px" placeholder="设备key搜索" @keyup.enter.native="getList(1)" clearable> </el-input>
+            <el-input v-model="params.deviceKey" style="width: 200px" :placeholder="$t('message.projects.filter.index.search.deviceKeyPlaceholder')" @keyup.enter.native="getList(1)" clearable> </el-input>
           </el-form-item>
           <el-form-item>
-            <el-select v-model="params.status" placeholder="请选择转发格式" style="width: 100px">
-              <el-option label="全部状态" :value="-1"> </el-option>
-              <el-option label="正常" :value="1"> </el-option>
-              <el-option label="禁用" :value="0"> </el-option>
+            <el-select v-model="params.status" :placeholder="$t('message.projects.filter.index.search.statusPlaceholder')" style="width: 100px">
+              <el-option :label="$t('message.projects.filter.index.status.all')" :value="-1"> </el-option>
+              <el-option :label="$t('message.projects.filter.index.status.enabled')" :value="1"> </el-option>
+              <el-option :label="$t('message.projects.filter.index.status.disabled')" :value="0"> </el-option>
             </el-select>
           </el-form-item>
           <el-form-item>
@@ -21,13 +21,13 @@
               <el-icon>
                 <ele-Search />
               </el-icon>
-              查询
+              {{ $t('message.projects.filter.index.search.query') }}
             </el-button>
             <el-button @click="resetQuery()">
               <el-icon>
                 <ele-Refresh />
               </el-icon>
-              重置
+              {{ $t('message.projects.filter.index.search.reset') }}
             </el-button>
           </el-form-item>
           <el-form-item>
@@ -35,33 +35,33 @@
               <el-icon>
                 <ele-FolderAdd />
               </el-icon>
-              新增
+              {{ $t('message.projects.filter.index.actions.add') }}
             </el-button>
             <el-button type="danger" v-auth="'del'" :disabled="!ids.length" @click="del()">
               <el-icon>
                 <ele-Delete />
               </el-icon>
-              删除
+              {{ $t('message.projects.filter.index.actions.delete') }}
             </el-button>
           </el-form-item>
         </el-form>
       </div>
       <el-table :data="tableData" style="width: 100%" @selection-change="handleSelectionChange" row-key="id" v-loading="loading">
         <el-table-column type="selection" v-col="'selection'" width="55" align="center" />
-        <el-table-column prop="name" v-col="'name'" label="模板名称" show-overflow-tooltip></el-table-column>
-        <el-table-column prop="code" v-col="'code'" label="模板编码" align="center" show-overflow-tooltip></el-table-column>
-        <el-table-column prop="updatedAt" v-col="'updatedAt'" label="更新时间" align="center"></el-table-column>
-        <el-table-column prop="status" v-col="'status'" label="状态" align="center">
+        <el-table-column prop="name" v-col="'name'" :label="$t('message.projects.filter.index.table.columns.name')" show-overflow-tooltip></el-table-column>
+        <el-table-column prop="code" v-col="'code'" :label="$t('message.projects.filter.index.table.columns.code')" align="center" show-overflow-tooltip></el-table-column>
+        <el-table-column prop="updatedAt" v-col="'updatedAt'" :label="$t('message.projects.filter.index.table.columns.updatedAt')" align="center"></el-table-column>
+        <el-table-column prop="status" v-col="'status'" :label="$t('message.projects.filter.index.table.columns.status')" align="center">
           <template #default="scope">
-            <el-switch v-model="scope.row.status" inline-prompt :active-value="1" :inactive-value="0" active-text="启" inactive-text="禁" @change="handleStatusChange(scope.row)"> </el-switch>
+            <el-switch v-model="scope.row.status" inline-prompt :active-value="1" :inactive-value="0" :active-text="$t('message.projects.filter.index.switch.active')" :inactive-text="$t('message.projects.filter.index.switch.inactive')" @change="handleStatusChange(scope.row)"> </el-switch>
           </template>
         </el-table-column>
-        <el-table-column label="操作" width="220" align="center" fixed="right" v-col="'handle'">
+        <el-table-column :label="$t('message.projects.filter.index.table.columns.actions')" width="220" align="center" fixed="right" v-col="'handle'">
           <template #default="{ row }">
-            <el-button size="small" text type="primary" v-auth="'detail'" @click="$refs.detailRef?.show(row)">详情</el-button>
-            <el-button size="small" text type="warning" v-auth="'edit'" @click="addOrEdit(row)">编辑</el-button>
-            <el-button size="small" text type="danger" v-auth="'del'" @click="del(row)">删除</el-button>
-            <el-button size="small" text type="primary" v-auth="'viewbind'" @click="$refs.bindDeviceRef?.show(row)">查看及绑定设备</el-button>
+            <el-button size="small" text type="primary" v-auth="'detail'" @click="detailRef?.show(row)">{{ $t('message.projects.filter.index.table.actions.detail') }}</el-button>
+            <el-button size="small" text type="warning" v-auth="'edit'" @click="addOrEdit(row)">{{ $t('message.projects.filter.index.table.actions.edit') }}</el-button>
+            <el-button size="small" text type="danger" v-auth="'del'" @click="del(row)">{{ $t('message.projects.filter.index.table.actions.delete') }}</el-button>
+            <el-button size="small" text type="primary" v-auth="'viewbind'" @click="bindDeviceRef?.show(row)">{{ $t('message.projects.filter.index.table.actions.viewBind') }}</el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -81,10 +81,13 @@ import EditForm from "./edit-dialog.vue";
 import detailVue from "./detail.vue";
 import bindDevice from "./bindDevice.vue";
 import { ref } from "vue";
+import { useI18n } from 'vue-i18n';
 
-const editFormRef = ref();
-const bindDeviceRef = ref();
-const detailRef = ref();
+const { t } = useI18n();
+
+const editFormRef = ref<any>();
+const bindDeviceRef = ref<any>();
+const detailRef = ref<any>();
 const ids = ref<number[]>([]);
 
 const { params, tableData, getList, loading, resetQuery } = useSearch<any[]>(api.template.list, "Data", { keyWord: "", deviceKey: "" });
@@ -106,29 +109,29 @@ const handleSelectionChange = (selections: any[]) => {
 };
 
 const del = (row?: any) => {
-  ElMessageBox.confirm(row ? `此操作将模板:“${row.name}”,是否继续?` : `此操作将${ids.value.length}个模板`, "提示", {
-    confirmButtonText: "确认",
-    cancelButtonText: "取消",
+  ElMessageBox.confirm(row ? t('message.projects.filter.index.messages.deleteConfirmSingle', { name: row.name }) : t('message.projects.filter.index.messages.deleteConfirmMultiple', { count: ids.value.length }), t('message.projects.filter.index.messages.tip'), {
+    confirmButtonText: t('message.projects.filter.index.messages.confirm'),
+    cancelButtonText: t('message.projects.filter.index.messages.cancel'),
     type: "warning",
   }).then(async () => {
     await api.template.del(row ? [row.id] : ids.value);
-    ElMessage.success("删除成功");
+    ElMessage.success(t('message.projects.filter.index.messages.deleteSuccess'));
     getList(1);
   });
 };
 
 const handleStatusChange = (row: any) => {
-  let text = row.status === 1 ? "启用" : "停用";
-  ElMessageBox.confirm("确认要" + text + "模板:【" + row.name + "】吗?", "警告", {
-    confirmButtonText: "确定",
-    cancelButtonText: "取消",
+  const text = row.status === 1 ? t('message.projects.filter.index.messages.enableActionText') : t('message.projects.filter.index.messages.disableActionText');
+  ElMessageBox.confirm(t('message.projects.filter.index.messages.statusChangeConfirm', { action: text, name: row.name }), t('message.projects.filter.index.messages.warn'), {
+    confirmButtonText: t('message.projects.filter.index.messages.confirm'),
+    cancelButtonText: t('message.projects.filter.index.messages.cancel'),
     type: "warning",
   })
     .then(function () {
       return api.template.editStatus({ id: row.id, status: row.status });
     })
     .then(() => {
-      ElMessage.success(text + "成功");
+      ElMessage.success(t('message.projects.filter.index.messages.statusChangeSuccess', { action: text }));
     })
     .catch(function () {
       row.status = row.status === 0 ? 1 : 0;

+ 3 - 3
src/views/iot/projects/list/edit-dialog.vue

@@ -1,6 +1,6 @@
 <template>
-  <el-dialog class="api-edit" v-model="showDialog" :title="`${formData?.id ? '编辑项目' : '新增项目'}`" width="600px" :close-on-click-modal="false" :close-on-press-escape="false">
-    <EditVue ref="editRef" v-if="showDialog" @getList="getList" @cancle="cancle"></EditVue>
+  <el-dialog class="api-edit" v-model="showDialog" :title="formData?.id ? $t('message.projects.editDialog.title.edit') : $t('message.projects.editDialog.title.add')" width="600px" :close-on-click-modal="false" :close-on-press-escape="false">
+    <EditVue ref="editRef" v-if="showDialog" @getList="getList" @cancel="cancel"></EditVue>
   </el-dialog>
 </template>
 
@@ -27,7 +27,7 @@ function getList() {
   showDialog.value = false;
 }
 
-function cancle() {
+function cancel() {
   showDialog.value = false;
 }
 

+ 62 - 57
src/views/iot/projects/list/edit.vue

@@ -1,90 +1,95 @@
 <template>
-  <el-form ref="formRef" :model="formData" :rules="ruleForm" label-width="80px">
-    <el-form-item label="项目名称" prop="name">
-      <el-input v-model.trim="formData.name" placeholder="输入项目名称" />
+  <el-form ref="formRef" :model="formData" :rules="ruleForm" :label-width="store.state.themeConfig.themeConfig.globalI18n === 'en' ? '170px' : '100px'">
+    <el-form-item :label="$t('message.projects.list.addOrEdit.form.name')" prop="name">
+      <el-input v-model.trim="formData.name" :placeholder="$t('message.projects.list.addOrEdit.placeholders.name')" />
     </el-form-item>
-    <el-form-item label="地区" prop="addressCode">
-      <el-cascader :options="data" :props="{ emitPath: true, value: 'code', label: 'name' }" placeholder="请选择" clearable class="w100" v-model="formData.addressCode">
-      </el-cascader>
+    <el-form-item :label="$t('message.projects.list.addOrEdit.form.addressCode')" prop="addressCode">
+      <el-cascader :options="data" :props="{ emitPath: true, value: 'code', label: 'name' }" :placeholder="$t('message.projects.list.addOrEdit.placeholders.select')" clearable class="w100" v-model="formData.addressCode"> </el-cascader>
     </el-form-item>
-    <el-form-item label="详细地址" prop="addressDetail">
-      <el-input v-model.trim="formData.addressDetail" placeholder="输入详细地址" />
+    <el-form-item :label="$t('message.projects.list.addOrEdit.form.addressDetail')" prop="addressDetail">
+      <el-input v-model.trim="formData.addressDetail" :placeholder="$t('message.projects.list.addOrEdit.placeholders.addressDetail')" />
     </el-form-item>
-    <el-form-item label="渠道商" prop="channelMerchants">
-      <el-input v-model.trim="formData.channelMerchants" placeholder="输入渠道商" />
+    <el-form-item :label="$t('message.projects.list.addOrEdit.form.channelMerchants')" prop="channelMerchants">
+      <el-input v-model.trim="formData.channelMerchants" :placeholder="$t('message.projects.list.addOrEdit.placeholders.channelMerchants')" />
     </el-form-item>
-    <el-form-item label="关键客户" prop="customName">
-      <el-input v-model.trim="formData.customName" placeholder="输入客户名称" />
+    <el-form-item :label="$t('message.projects.list.addOrEdit.form.customName')" prop="customName">
+      <el-input v-model.trim="formData.customName" :placeholder="$t('message.projects.list.addOrEdit.placeholders.customName')" />
     </el-form-item>
-    <el-form-item label="维修公司" prop="repairCompany">
-      <el-input v-model.trim="formData.repairCompany" placeholder="输入维修公司" />
+    <el-form-item :label="$t('message.projects.list.addOrEdit.form.repairCompany')" prop="repairCompany">
+      <el-input v-model.trim="formData.repairCompany" :placeholder="$t('message.projects.list.addOrEdit.placeholders.repairCompany')" />
     </el-form-item>
-    <el-form-item label="维修电话" prop="repairMobile">
-      <el-input v-model.trim="formData.repairMobile" placeholder="输入维修电话" />
+    <el-form-item :label="$t('message.projects.list.addOrEdit.form.repairMobile')" prop="repairMobile">
+      <el-input v-model.trim="formData.repairMobile" :placeholder="$t('message.projects.list.addOrEdit.placeholders.repairMobile')" />
     </el-form-item>
     <el-form-item label="" prop="">
-      <el-button @click="cancle">取消</el-button>
-      <el-button type="primary" @click="onSubmit">确定</el-button>
+      <el-button @click="cancel">{{ $t("message.projects.list.addOrEdit.buttons.cancel") }}</el-button>
+      <el-button type="primary" @click="onSubmit">{{ $t("message.projects.list.addOrEdit.buttons.confirm") }}</el-button>
     </el-form-item>
   </el-form>
 </template>
 
 <script lang="ts" setup>
-import { ref, reactive, nextTick } from 'vue';
-import api from '/@/api/projects';
-import { ruleRequired } from '/@/utils/validator';
-import { ElMessage } from 'element-plus';
-import data from 'province-city-china/dist/level.json'
+import { ref, reactive, nextTick, computed } from "vue";
+import api from "/@/api/projects";
+import { ruleRequired } from "/@/utils/validator";
+import { ElMessage } from "element-plus";
+import data from "province-city-china/dist/level.json";
+import { useI18n } from "vue-i18n";
+import { useStore } from "/@/store/index";
 
-const emit = defineEmits(['getList', 'cancle']);
+const store = useStore();
+
+const emit = defineEmits(["getList", "cancel"]);
 
 const showDialog = ref(false);
 const formRef = ref();
+const { t } = useI18n();
 
 // 从省市区的的树形结构中获取省市区名称
 function getNames(code: string) {
-  const provinceItem = data.find((item: any) => item.province === code.substring(0, 2))
-  const provinceName = provinceItem.name
-  const cityItem = provinceItem.children.find((item: any) => item.city === code.substring(2, 4))
-  const cityName = cityItem.name
+  const provinceItem = data.find((item: any) => item.province === code.substring(0, 2));
+  const provinceName = provinceItem.name;
+  const cityItem = provinceItem.children.find((item: any) => item.city === code.substring(2, 4));
+  const cityName = cityItem.name;
 
   if (!cityItem.children) {
-    const areaItem = provinceItem.children.find((item: any) => item.area === code.substring(4, 6))
-    const areaName = areaItem.name
-    return `${provinceName}/${areaName}`
-
+    const areaItem = provinceItem.children.find((item: any) => item.area === code.substring(4, 6));
+    const areaName = areaItem.name;
+    return `${provinceName}/${areaName}`;
   } else {
-    const areaItem = cityItem.children.find((item: any) => item.area === code.substring(4, 6))
-    const areaName = areaItem.name
-    return `${provinceName}/${cityName}/${areaName}`
+    const areaItem = cityItem.children.find((item: any) => item.area === code.substring(4, 6));
+    const areaName = areaItem.name;
+    return `${provinceName}/${cityName}/${areaName}`;
   }
 }
 
 const baseForm = {
   id: undefined,
-  address: '',
+  address: "",
   addressCode: [],
-  name: '',
-  addressDetail: '',
-  channelMerchants: '',
-  customName: '',
-  repairCompany: '',
-  repairMobile: '',
+  name: "",
+  addressDetail: "",
+  channelMerchants: "",
+  customName: "",
+  repairCompany: "",
+  repairMobile: "",
 };
 
 const formData = reactive({
   ...baseForm,
 });
 
-const ruleForm = {
-  name: [ruleRequired('不能为空')],
-  addressCode: [ruleRequired('不能为空')],
-  addressDetail: [ruleRequired('不能为空')],
-  channelMerchants: [ruleRequired('不能为空')],
-  customName: [ruleRequired('不能为空')],
-  repairCompany: [ruleRequired('不能为空')],
-  repairMobile: [ruleRequired('不能为空')],
-};
+const ruleForm = computed(() => {
+  return {
+    name: [ruleRequired(t("message.projects.list.addOrEdit.validation.name"))],
+    addressCode: [ruleRequired(t("message.projects.list.addOrEdit.validation.addressCode"))],
+    addressDetail: [ruleRequired(t("message.projects.list.addOrEdit.validation.addressDetail"))],
+    channelMerchants: [ruleRequired(t("message.projects.list.addOrEdit.validation.channelMerchants"))],
+    customName: [ruleRequired(t("message.projects.list.addOrEdit.validation.customName"))],
+    repairCompany: [ruleRequired(t("message.projects.list.addOrEdit.validation.repairCompany"))],
+    repairMobile: [ruleRequired(t("message.projects.list.addOrEdit.validation.repairMobile"))],
+  };
+});
 
 const onSubmit = async () => {
   await formRef.value.validate();
@@ -92,17 +97,17 @@ const onSubmit = async () => {
   const { addressCode } = formData;
   formData.address = JSON.stringify({
     name: getNames(addressCode[2] || addressCode[1]),
-    code: addressCode
-  })
+    code: addressCode,
+  });
 
   const theApi = formData.id ? api.edit : api.add;
 
   await theApi(formData);
 
-  ElMessage.success('操作成功');
+  ElMessage.success(t(`message.projects.list.addOrEdit.messages.${formData.id ? "editSuccess" : "addSuccess"}`));
   // resetForm();
   showDialog.value = false;
-  emit('getList');
+  emit("getList");
 };
 
 const resetForm = async () => {
@@ -110,8 +115,8 @@ const resetForm = async () => {
   formRef.value && formRef.value.resetFields();
 };
 
-const cancle = () => {
-  emit('cancle');
+const cancel = () => {
+  emit("cancel");
 };
 
 const open = async (row?: any) => {

+ 29 - 38
src/views/iot/projects/list/index.vue

@@ -4,14 +4,14 @@
       <div class="search">
         <el-form inline>
           <el-form-item>
-            <el-input v-model="params.channelMerchants" style="width: 200px;" placeholder="渠道商搜索" @keyup.enter.native="getList(1)" clearable>
+            <el-input v-model="params.channelMerchants" style="width: 200px;" :placeholder="$t('message.projects.list.search.channelPlaceholder')" @keyup.enter.native="getList(1)" clearable>
             </el-input>
           </el-form-item>
           <el-form-item>
-            <el-select v-model="params.status" placeholder="请选择转发格式" style="width: 100px">
-              <el-option label="全部状态" :value="-1"> </el-option>
-              <el-option label="正常" :value="1"> </el-option>
-              <el-option label="禁用" :value="0"> </el-option>
+            <el-select v-model="params.status" :placeholder="$t('message.projects.list.search.statusPlaceholder')" style="width: 100px">
+              <el-option :label="$t('message.projects.list.status.all')" :value="-1"> </el-option>
+              <el-option :label="$t('message.projects.list.status.enabled')" :value="1"> </el-option>
+              <el-option :label="$t('message.projects.list.status.disabled')" :value="0"> </el-option>
             </el-select>
           </el-form-item>
           <el-form-item>
@@ -19,13 +19,13 @@
               <el-icon>
                 <ele-Search />
               </el-icon>
-              查询
+              {{ $t('message.projects.list.search.query') }}
             </el-button>
             <el-button @click="resetQuery()">
               <el-icon>
                 <ele-Refresh />
               </el-icon>
-              重置
+              {{ $t('message.projects.list.search.reset') }}
             </el-button>
           </el-form-item>
           <el-form-item>
@@ -33,33 +33,33 @@
               <el-icon>
                 <ele-FolderAdd />
               </el-icon>
-              新增
+              {{ $t('message.projects.list.actions.add') }}
             </el-button>
             <el-button type="danger" v-auth="'del'" :disabled="!ids.length" @click="del()">
               <el-icon>
                 <ele-Delete />
               </el-icon>
-              删除
+              {{ $t('message.projects.list.actions.delete') }}
             </el-button>
           </el-form-item>
         </el-form>
       </div>
       <el-table :data="tableData" style="width: 100%" @selection-change="handleSelectionChange" row-key="id" v-loading="loading">
         <el-table-column type="selection" width="55" align="center" />
-        <el-table-column prop="status" label="项目状态" width="100" :formatter="(row: any) => row.status ? '正常' : '禁用'" align="center"></el-table-column>
-        <el-table-column prop="name" v-col="'name'" label="项目名称" min-width="120" show-overflow-tooltip></el-table-column>
-        <el-table-column prop="address" v-col="'address'" label="省/市/区/县" :formatter="(row: any) => JSON.parse(row.address)?.name" min-width="200" align="center"></el-table-column>
-        <el-table-column prop="addressDetail" v-col="'addressDetail'" label="详细地址" min-width="100" align="center"></el-table-column>
-        <el-table-column prop="channelMerchants" v-col="'channelMerchants'" label="渠道商" min-width="100" align="center"></el-table-column>
-        <el-table-column prop="customName" v-col="'customName'" label="关键客户" min-width="100" align="center"></el-table-column>
-        <el-table-column prop="repairCompany" v-col="'repairCompany'" label="维修公司" min-width="100" align="center"></el-table-column>
-        <el-table-column prop="updatedAt" v-col="'updatedAt'" label="更新时间" width="165" align="center"></el-table-column>
-        <el-table-column label="操作" width="160" v-col="'handle'" align="center" fixed="right">
+        <el-table-column prop="status" :label="$t('message.projects.list.table.columns.projectStatus')" width="120" :formatter="(row: any) => row.status ? $t('message.projects.list.status.enabled') : $t('message.projects.list.status.disabled')" align="center"></el-table-column>
+        <el-table-column prop="name" v-col="'name'" :label="$t('message.projects.list.table.columns.projectName')" min-width="120" show-overflow-tooltip></el-table-column>
+        <el-table-column prop="address" v-col="'address'" :label="$t('message.projects.list.table.columns.address')" :formatter="(row: any) => JSON.parse(row.address)?.name" min-width="230" align="center"></el-table-column>
+        <el-table-column prop="addressDetail" v-col="'addressDetail'" :label="$t('message.projects.list.table.columns.addressDetail')" min-width="140" align="center"></el-table-column>
+        <el-table-column prop="channelMerchants" v-col="'channelMerchants'" :label="$t('message.projects.list.table.columns.channelMerchants')" min-width="150" align="center"></el-table-column>
+        <el-table-column prop="customName" v-col="'customName'" :label="$t('message.projects.list.table.columns.customName')" min-width="150" align="center"></el-table-column>
+        <el-table-column prop="repairCompany" v-col="'repairCompany'" :label="$t('message.projects.list.table.columns.repairCompany')" min-width="190" align="center"></el-table-column>
+        <el-table-column prop="updatedAt" v-col="'updatedAt'" :label="$t('message.projects.list.table.columns.updatedAt')" width="165" align="center"></el-table-column>
+        <el-table-column :label="$t('message.projects.list.table.columns.actions')" width="200" v-col="'handle'" align="center" fixed="right">
           <template #default="{ row }">
-            <el-button size="small" text style="margin-left: 0;" type="primary" v-auth="'detail'" @click="$router.push('/iotmanager/projects/list/' + row.code)">详情</el-button>
-            <el-button size="small" text type="warning" v-auth="'edit'" @click="addOrEdit(row)">编辑</el-button>
-            <el-button size="small" text type="primary" v-auth="'startOrStop'" @click="editStatus(row)">{{ row.status ? '禁用' : '启用' }}</el-button>
-            <el-button size="small" text type="danger" v-auth="'del'" @click="del(row)">删除</el-button>
+            <el-button size="small" text style="margin-left: 0;" type="primary" v-auth="'detail'" @click="$router.push('/iotmanager/projects/list/' + row.code)">{{ $t('message.projects.list.table.actions.detail') }}</el-button>
+            <el-button size="small" text type="warning" v-auth="'edit'" @click="addOrEdit(row)">{{ $t('message.projects.list.table.actions.edit') }}</el-button>
+            <el-button size="small" text type="primary" v-auth="'startOrStop'" @click="editStatus(row)">{{ row.status ? $t('message.projects.list.actions.disable') : $t('message.projects.list.actions.enable') }}</el-button>
+            <el-button size="small" text type="danger" v-auth="'del'" @click="del(row)">{{ $t('message.projects.list.table.actions.delete') }}</el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -73,9 +73,10 @@
 import api from '/@/api/projects';
 import { useSearch } from '/@/hooks/useCommon';
 import { ElMessageBox, ElMessage } from 'element-plus';
-import getOrigin from '/@/utils/origin'
 import EditForm from './edit-dialog.vue';
 import { ref } from 'vue';
+import { useI18n } from 'vue-i18n';
+const { t } = useI18n();
 
 const editFormRef = ref();
 const ids = ref<number[]>([]);
@@ -84,11 +85,6 @@ const { params, tableData, getList, loading, resetQuery } = useSearch<any[]>(api
 
 getList();
 
-function getTokenUrl(url: string) {
-  const tokenUrl = import.meta.env.VITE_TOPO_URL
-  return getOrigin(tokenUrl + url)
-}
-
 const addOrEdit = (row?: any) => {
   if (row) {
     editFormRef.value.open(row);
@@ -99,7 +95,7 @@ const addOrEdit = (row?: any) => {
 };
 const editStatus = (row: any) => {
   api.editStatus({ id: row.id, status: row.status ? 0 : 1 }).then(() => {
-    ElMessage.success('操作成功')
+    ElMessage.success(t('message.projects.list.messages.operationSuccess'))
     getList()
   })
 };
@@ -109,19 +105,14 @@ const handleSelectionChange = (selections: any[]) => {
   ids.value = selections.map((item) => item.id);
 };
 
-const edit = (row: any) => {
-  const url = getTokenUrl('#/editor/' + row.id);
-  window.open(url);
-};
-
 const del = (row?: any) => {
-  ElMessageBox.confirm(row ? `此操作将项目:“${row.name}”,是否继续?` : `此操作将${ids.value.length}个项目`, '提示', {
-    confirmButtonText: '确认',
-    cancelButtonText: '取消',
+  ElMessageBox.confirm(row ? t('message.projects.list.messages.deleteConfirmSingle', { name: row.name }) : t('message.projects.list.messages.deleteConfirmMultiple', { count: ids.value.length }), t('message.projects.list.messages.tip'), {
+    confirmButtonText: t('message.projects.list.messages.confirm'),
+    cancelButtonText: t('message.projects.list.messages.cancel'),
     type: 'warning',
   }).then(async () => {
     await api.del(row ? [row.id] : ids.value);
-    ElMessage.success('删除成功');
+    ElMessage.success(t('message.projects.list.messages.deleteSuccess'));
     getList(1);
   });
 };

+ 2 - 2
src/views/iot/projects/screen/BaseinfoVue.vue

@@ -3,9 +3,9 @@
     <div class="select-device title flex-row">
       <div class="flex">
         <img src="/@/assets/project/project-icon2.svg" class="icon" />
-        基本信息
+        {{ $t('message.projects.screen.baseinfo.title') }}
       </div>
-      <el-select style="width: 15vw" v-model="deviceKey" @change="deviceChnage" placeholder="请选择">
+      <el-select style="width: 15vw" v-model="deviceKey" @change="deviceChnage" :placeholder="$t('message.projects.screen.baseinfo.selects.placeholder')">
         <el-option v-for="row in deviceList" :key="row.key" :label="row.name" :value="row.key"></el-option>
       </el-select>
     </div>

+ 11 - 11
src/views/iot/projects/screen/InfoVue.vue

@@ -3,19 +3,19 @@
     <div class="title flex-row">
       <div class="flex">
         <img src="/@/assets/project/project-icon1.svg" class="icon" />
-        项目信息
+        {{ $t('message.projects.screen.info.title') }}
       </div>
-      <el-select style="width: 15vw" v-model="project" @change="projectChange" placeholder="请选择">
+      <el-select style="width: 15vw" v-model="project" @change="projectChange" :placeholder="$t('message.projects.screen.info.selects.placeholder')">
         <el-option v-for="row in projectList" :key="row.id" :label="row.name" :value="row.code"></el-option>
       </el-select>
     </div>
     <div class="flex-row">
       <div class="flex">
-        <div class="label">项目名称:</div>
+        <div class="label">{{ $t('message.projects.screen.info.labels.projectName') }}</div>
         <div class="val">{{ projectData?.name }}</div>
       </div>
       <div class="flex">
-        <div class="label">项目地址:</div>
+        <div class="label">{{ $t('message.projects.screen.info.labels.projectAddress') }}</div>
         <div class="val">{{ projectData?.addressDetail }}</div>
       </div>
     </div>
@@ -23,25 +23,25 @@
       <div class="info-content">
         <div class="count-card">
           <img src="/@/assets/project/project-icon7.svg" class="icon" />
-          <div class="count">{{ projectData?.heatingArea }} <span class="unit"></span></div>
-          <div class="text">覆盖面积</div>
+          <div class="count">{{ projectData?.heatingArea }} <span class="unit">{{ $t('message.projects.screen.info.units.squareMeter') }}</span></div>
+          <div class="text">{{ $t('message.projects.screen.info.cards.coverageArea') }}</div>
         </div>
         <div class="count-card">
           <img src="/@/assets/project/project-icon8.svg" class="icon" />
-          <div class="count">{{ projectData?.deviceCount }} <span class="unit"></span></div>
-          <div class="text">设备台数</div>
+          <div class="count">{{ projectData?.deviceCount }} <span class="unit">{{ $t('message.projects.screen.info.units.device') }}</span></div>
+          <div class="text">{{ $t('message.projects.screen.info.cards.deviceCount') }}</div>
         </div>
         <div class="count-card">
           <img src="/@/assets/project/project-icon9.svg" class="icon" />
-          <div class="count">{{ projectData?.repairMobile || "-" }}</div>
-          <div class="text">维保电话</div>
+          <div class="count">{{ projectData?.repairMobile || $t('message.projects.screen.info.empty.dash') }}</div>
+          <div class="text">{{ $t('message.projects.screen.info.cards.maintainPhone') }}</div>
         </div>
       </div>
     </section>
   </div>
 </template>
 <script setup lang="ts">
-import { reactive, ref, inject, watch, Ref } from "vue";
+import { ref, inject, watch, Ref } from "vue";
 
 const emit = defineEmits(["change"]);
 

+ 5 - 9
src/views/iot/projects/screen/LineChart.vue

@@ -4,7 +4,7 @@
       <div class="flex">
         <img src="/@/assets/project/project-icon4.svg" v-if="index" class="icon" />
         <img src="/@/assets/project/project-icon3.svg" v-else class="icon" />
-        设备状态
+        {{ $t('message.projects.screen.lineChart.title') }}
       </div>
       <div class="flex">
         <el-date-picker
@@ -12,9 +12,9 @@
           v-model="params.timeRange"
           :clearable="false"
           type="datetimerange"
-          range-separator="至"
-          start-placeholder="开始时间"
-          end-placeholder="结束时间"
+          :range-separator="$t('message.projects.screen.lineChart.date.rangeSeparator')"
+          :start-placeholder="$t('message.projects.screen.lineChart.date.startPlaceholder')"
+          :end-placeholder="$t('message.projects.screen.lineChart.date.endPlaceholder')"
           format="MM-DD HH:mm"
           value-format="YYYY-MM-DD HH:mm:00"
           date-format="YYYY/MM/DD"
@@ -22,7 +22,7 @@
           @change="getChartData"
           style="width: 230px; margin-right: 10px"
         />
-        <el-select style="width: 150px" v-model="params.properties" multiple collapse-tags @change="getChartData" placeholder="请选择">
+        <el-select style="width: 150px" v-model="params.properties" multiple collapse-tags @change="getChartData" :placeholder="$t('message.projects.screen.lineChart.selects.properties')">
           <el-option v-for="row in options" :key="row.key" :label="row.name" :value="row.key"></el-option>
         </el-select>
       </div>
@@ -83,7 +83,6 @@ function getData(code: string) {
   options.value = [];
 
   deviceCode = code;
-  console.log(code);
 
   if (!code) return chartRef.value.draw();
 
@@ -141,7 +140,4 @@ function getChartData() {
   white-space: nowrap;
   justify-content: space-between;
 }
-
-.line {
-}
 </style>

+ 1 - 1
src/views/iot/projects/screen/VideoVue.vue

@@ -2,7 +2,7 @@
   <div class="project-card">
     <section class="video" v-loading="loading">
       <div class="title">
-        <el-select v-model="params.project" @change="projectChange" placeholder="选择项目" style="width: 300px">
+        <el-select v-model="params.project" @change="projectChange" :placeholder="$t('message.projects.screen.video.selects.project')" style="width: 300px">
           <el-option v-for="row in projectList" :key="row.id" :label="row.name" :value="row.code"></el-option>
         </el-select>
         <!-- <el-select v-model="params.type" placeholder="" style="width: 9vw; margin-left: 1.6vw">

+ 89 - 98
src/views/iot/property/attribute/edit.vue

@@ -1,123 +1,114 @@
 <template>
-	<el-dialog class="api-edit" v-model="showDialog" :title="`${formData.id ? '编辑设备档案属性' : '新增设备档案属性'}`" width="800px" :close-on-click-modal="false" :close-on-press-escape="false">
-		<el-form class="inline-form" ref="formRef" :model="formData" :rules="ruleForm" label-width="120px">
-			<el-form-item label="所属产品" prop="productKey">
-				<el-select v-model="formData.productKey" placeholder="请选择产品" class="w100" disabled>
-					<el-option v-for="item in productData" :key="item.key" :label="item.name" :value="item.key">
-						<span style="float: left">{{ item.name }}</span>
-						<span style="float: right; font-size: 13px">{{ item.key }}</span>
-					</el-option>
-				</el-select>
-			</el-form-item>
-			<el-form-item label="字段名称" prop="name">
-				<el-input v-model.trim="formData.name" placeholder="请输入字段名称" />
-			</el-form-item>
-			<el-form-item label="字段标题" prop="title">
-				<el-input v-model.trim="formData.title" placeholder="请输入字段标题" />
-			</el-form-item>
-			<el-form-item label="字段类型" prop="types">
-				<el-select v-model="formData.types" placeholder="请字段类型" class="w100" clearable>
-					<el-option v-for="item in typesData" :key="item.value" :label="item.name" :value="item.value">
-					</el-option>
-				</el-select>
-			</el-form-item>
-
-			<el-form-item label="字段描述" prop="desc">
-				<el-input v-model.trim="formData.desc" placeholder="请输入字段描述" />
-			</el-form-item>
-		</el-form>
-
-		<template #footer>
-			<div class="dialog-footer">
-				<el-button @click="showDialog = false">取消</el-button>
-				<el-button type="primary" @click="onSubmit">确定</el-button>
-			</div>
-		</template>
-	</el-dialog>
+  <el-dialog class="api-edit" v-model="showDialog" :title="formData.id ? $t('message.property.attribute.dialog.editTitle') : $t('message.property.attribute.dialog.addTitle')" width="800px" :close-on-click-modal="false" :close-on-press-escape="false">
+    <el-form class="inline-form" ref="formRef" :model="formData" :rules="ruleForm" label-width="120px">
+      <el-form-item :label="$t('message.property.attribute.form.productKey')" prop="productKey">
+        <el-select v-model="formData.productKey" :placeholder="$t('message.property.attribute.form.productPlaceholder')" class="w100" disabled>
+          <el-option v-for="item in productData" :key="item.key" :label="item.name" :value="item.key">
+            <span style="float: left">{{ item.name }}</span>
+            <span style="float: right; font-size: 13px">{{ item.key }}</span>
+          </el-option>
+        </el-select>
+      </el-form-item>
+      <el-form-item :label="$t('message.property.attribute.form.fieldName')" prop="name">
+        <el-input v-model.trim="formData.name" :placeholder="$t('message.property.attribute.form.fieldNamePlaceholder')" />
+      </el-form-item>
+      <el-form-item :label="$t('message.property.attribute.form.fieldTitle')" prop="title">
+        <el-input v-model.trim="formData.title" :placeholder="$t('message.property.attribute.form.fieldTitlePlaceholder')" />
+      </el-form-item>
+      <el-form-item :label="$t('message.property.attribute.form.fieldType')" prop="types">
+        <el-select v-model="formData.types" :placeholder="$t('message.property.attribute.form.typePlaceholder')" class="w100" clearable>
+          <el-option v-for="item in typesData" :key="item.value" :label="t('message.property.attribute.types.' + item.value)" :value="item.value"> </el-option>
+        </el-select>
+      </el-form-item>
+
+      <el-form-item :label="$t('message.property.attribute.form.fieldDesc')" prop="desc">
+        <el-input v-model.trim="formData.desc" :placeholder="$t('message.property.attribute.form.fieldDescPlaceholder')" />
+      </el-form-item>
+    </el-form>
+
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="showDialog = false">{{ $t("message.tableI18nAction.cancel") }}</el-button>
+        <el-button type="primary" @click="onSubmit">{{ $t("message.property.attribute.actions.save") }}</el-button>
+      </div>
+    </template>
+  </el-dialog>
 </template>
 
 <script lang="ts" setup>
-import { ref, reactive, nextTick } from 'vue'
-import api from '/@/api/device'
-import { ruleRequired } from '/@/utils/validator'
-import { ElMessage } from 'element-plus'
+import { ref, reactive, nextTick, computed } from "vue";
+import api from "/@/api/device";
+import { ruleRequired } from "/@/utils/validator";
+import { ElMessage } from "element-plus";
+import { useI18n } from "vue-i18n";
 
-const emit = defineEmits(['getList'])
+const emit = defineEmits(["getList"]);
 
-const showDialog = ref(false)
-const formRef = ref()
+const showDialog = ref(false);
+const formRef = ref();
 const productData = ref();
-const typesData = ref([{
-	value: 'input',
-	name: '输入框',
-}, {
-	value: 'textarea',
-	name: '文本框',
-}, {
-	value: 'date',
-	name: '日期',
-}, {
-	value: 'file',
-	name: '上传图片',
-}])
+const typesData = ref([{ value: "input" }, { value: "textarea" }, { value: "date" }, { value: "file" }]);
 const baseForm = {
-	id: undefined,
-	name: '',
-	productKey: '',
-	title: '',
-	types: '',
-	desc: '',
-}
+  id: undefined,
+  name: "",
+  productKey: "",
+  title: "",
+  types: "",
+  desc: "",
+};
 
 const formData = reactive({
-	...baseForm,
-})
-
-const ruleForm = {
-	name: [ruleRequired('字段名称不能为空')],
-	title: [ruleRequired('字段标题不能为空')],
-	productKey: [ruleRequired('所属产品不能为空')],
-	types: [ruleRequired('字段类型不能为空')],
-}
+  ...baseForm,
+});
 
+const { t } = useI18n();
+
+const ruleForm = computed(() => {
+  return {
+    name: [ruleRequired(t("message.property.attribute.valid.nameRequired"))],
+    title: [ruleRequired(t("message.property.attribute.valid.titleRequired"))],
+    productKey: [ruleRequired(t("message.property.attribute.valid.productRequired"))],
+    types: [ruleRequired(t("message.property.attribute.valid.typeRequired"))],
+  };
+});
 
 const onSubmit = async () => {
-	await formRef.value.validate()
+  await formRef.value.validate();
 
-	const theApi = formData.id ? api.dev_asset_metadata.edit : api.dev_asset_metadata.add
+  const theApi = formData.id ? api.dev_asset_metadata.edit : api.dev_asset_metadata.add;
 
-	await theApi(formData)
+  await theApi(formData);
 
-	ElMessage.success('操作成功')
-	resetForm()
-	showDialog.value = false
-	emit('getList')
-}
+  ElMessage.success(t("message.property.attribute.messages.saveSuccess"));
+  resetForm();
+  showDialog.value = false;
+  emit("getList");
+};
 
 const resetForm = async () => {
-	Object.assign(formData, { ...baseForm })
-	formRef.value && formRef.value.resetFields()
-}
+  Object.assign(formData, { ...baseForm });
+  formRef.value && formRef.value.resetFields();
+};
 
 const open = async (row: any, productInfo: any) => {
-	resetForm()
-	showDialog.value = true
-	nextTick(() => {
-		Object.assign(formData, { ...row })
-
-		//获取 所有的产品
-		api.product.getLists({}).then((res: any) => {
-			productData.value = res.product
-		})
-
-		formData.productKey = productInfo.key
-	})
-}
+  resetForm();
+  showDialog.value = true;
+  nextTick(() => {
+    Object.assign(formData, { ...row });
+
+    //获取 所有的产品
+    api.product.getLists({}).then((res: any) => {
+      productData.value = res.product;
+    });
+
+    formData.productKey = productInfo.key;
+  });
+};
 
-defineExpose({ open })
+defineExpose({ open });
 </script>
 <style scoped lang="scss">
 .demo-form-inline .el-input {
-	--el-input-width: 320px;
+  --el-input-width: 320px;
 }
-</style>
+</style>

+ 23 - 21
src/views/iot/property/attribute/index.vue

@@ -1,15 +1,15 @@
 <template>
 	<div class="page page-full border bg padding">
 		<el-form inline ref="queryRef" @keyup.enter="getList(1)">
-			<el-form-item label="关键字:" prop="keyWord">
-				<el-input v-model="params.keyWord" placeholder="请输入关键字" clearable style="width: 240px" />
+			<el-form-item :label="$t('message.property.attribute.form.keyword') + ':'" prop="keyWord">
+				<el-input v-model="params.keyWord" :placeholder="$t('message.property.attribute.form.keywordPlaceholder')" clearable style="width: 240px" />
 			</el-form-item>
 			<el-form-item>
 				<el-button type="primary" class="ml10" @click="getList(1)">
 					<el-icon>
 						<ele-Search />
 					</el-icon>
-					查询
+					{{ $t('message.tableI18nAction.query') }}
 				</el-button>
 			</el-form-item>
 			<el-form-item>
@@ -17,13 +17,13 @@
 					<el-icon>
 						<ele-FolderAdd />
 					</el-icon>
-					新增属性
+					{{ $t('message.property.attribute.actions.add') }}
 				</el-button>
 				<el-button type="info" @click="batchdel()" v-auth="'batchdel'">
 					<el-icon>
 						<ele-FolderAdd />
 					</el-icon>
-					删除
+					{{ $t('message.tableI18nAction.delete') }}
 				</el-button>
 			</el-form-item>
 		</el-form>
@@ -55,15 +55,15 @@
 				<div class="page page-full">
 					<el-table :data="tableData" @selection-change="handleSelectionChange" style="width: 100%" row-key="id" v-loading="loading">
 						<el-table-column type="selection" width="55" align="center" />
-						<el-table-column prop="id" v-col="'id'" label="ID" min-width="100" show-overflow-tooltip></el-table-column>
-						<el-table-column prop="name" v-col="'name'" label="字段名称" show-overflow-tooltip></el-table-column>
-						<el-table-column prop="title" v-col="'title'" label="字段标题" show-overflow-tooltip></el-table-column>
-						<el-table-column prop="types" v-col="'types'" label="字段类型" show-overflow-tooltip></el-table-column>
-						<el-table-column prop="createdAt" v-col="'createdAt'" label="创建时间" width="160" align="center"></el-table-column>
-						<el-table-column label="操作" width="200" align="center">
+						<el-table-column prop="id" v-col="'id'" :label="$t('message.tableI18nColumn.id')" min-width="100" show-overflow-tooltip></el-table-column>
+						<el-table-column prop="name" v-col="'name'" :label="$t('message.property.attribute.columns.fieldName')" show-overflow-tooltip></el-table-column>
+						<el-table-column prop="title" v-col="'title'" :label="$t('message.property.attribute.columns.fieldTitle')" show-overflow-tooltip></el-table-column>
+						<el-table-column prop="types" v-col="'types'" :label="$t('message.property.attribute.columns.fieldType')" show-overflow-tooltip></el-table-column>
+						<el-table-column prop="createdAt" v-col="'createdAt'" :label="$t('message.property.attribute.columns.createdAt')" width="160" align="center"></el-table-column>
+						<el-table-column :label="$t('message.tableI18nColumn.operation')" width="200" align="center">
 							<template #default="scope">
-								<el-button size="small" text v-auth="'edit'" type="warning" @click="addOrEdit(scope.row)">编辑</el-button>
-								<el-button size="small" text v-auth="'del'" type="info" @click="del(scope.row)">删除</el-button>
+								<el-button size="small" text v-auth="'edit'" type="warning" @click="addOrEdit(scope.row)">{{ $t('message.tableI18nAction.edit') }}</el-button>
+								<el-button size="small" text v-auth="'del'" type="info" @click="del(scope.row)">{{ $t('message.tableI18nAction.delete') }}</el-button>
 							</template>
 						</el-table-column>
 					</el-table>
@@ -87,6 +87,7 @@ import { useSearch } from '/@/hooks/useCommon'
 import { Folder } from '@element-plus/icons-vue'
 
 import { ElMessageBox, ElMessage } from 'element-plus'
+import { useI18n } from 'vue-i18n'
 import EditForm from './edit.vue'
 interface Tree {
 	label: string
@@ -100,6 +101,7 @@ const defaultProps = {
 }
 
 const queryRef = ref()
+const { t } = useI18n()
 const router = useRouter()
 const productData = ref([])
 const mergedData = ref()
@@ -197,24 +199,24 @@ const buildTree = (category: any, productData: any) => {
 	return treeNode
 }
 const batchdel = () => {
-	ElMessageBox.confirm('是否确认要批量删除这些数据吗?', '提示', {
-		confirmButtonText: '确认',
-		cancelButtonText: '取消',
+	ElMessageBox.confirm(t('message.property.attribute.messages.batchDeleteConfirm'), t('message.property.attribute.messages.tips'), {
+		confirmButtonText: t('message.property.attribute.messages.confirm'),
+		cancelButtonText: t('message.property.attribute.messages.cancel'),
 		type: 'warning',
 	}).then(async () => {
 		await device.dev_asset_metadata.delete({ ids: ids.value })
-		ElMessage.success('删除成功')
+		ElMessage.success(t('message.property.attribute.messages.deleteSuccess'))
 		getList()
 	})
 }
 const del = (row: any) => {
-	ElMessageBox.confirm('是否确认删除名称为:"' + row.name + '"的数据项?', '提示', {
-		confirmButtonText: '确认',
-		cancelButtonText: '取消',
+	ElMessageBox.confirm(t('message.property.attribute.messages.deleteItemConfirm', { name: row.name }), t('message.property.attribute.messages.tips'), {
+		confirmButtonText: t('message.property.attribute.messages.confirm'),
+		cancelButtonText: t('message.property.attribute.messages.cancel'),
 		type: 'warning',
 	}).then(async () => {
 		await device.dev_asset_metadata.delete({ ids: row.id })
-		ElMessage.success('删除成功')
+		ElMessage.success(t('message.property.attribute.messages.deleteSuccess'))
 		getList()
 	})
 }

+ 13 - 9
src/views/iot/property/deviceMap/index.vue

@@ -6,7 +6,7 @@
 			filterable
 			clearable
 			:loading="loading"
-			placeholder="选择产品"
+			:placeholder="$t('message.property.deviceMap.form.selectProductPlaceholder')"
 			@change="handleProductChange"
 		>
 			<el-option v-for="item in productData" :key="item.key" :label="item.name" :value="item.key" value-key="id"> </el-option>
@@ -18,11 +18,13 @@
 <script lang="ts" setup>
 import { initMap, MapStyleJson } from '/@/utils/map'
 import { onMounted, ref, watch } from 'vue'
+import { useI18n } from 'vue-i18n'
 import api from '/@/api/device'
 import { ElMessage } from 'element-plus'
 import { useStore } from '/@/store/index'
 
 const store = useStore()
+const { t } = useI18n()
 
 let BMapGL: any = null
 let map: any = null
@@ -77,7 +79,7 @@ async function setMarker() {
 		const list = (device || []).filter((row: any) => row.lat && row.lng)
 
 		if (!list.length) {
-			ElMessage.warning('该产品在不存在有坐标的设备!')
+			ElMessage.warning(t('message.property.deviceMap.messages.noCoordsWarning'))
 			return map.centerAndZoom('四川', 5)
 		}
 
@@ -97,7 +99,9 @@ async function setMarker() {
 			map.addOverlay(marker)
 
 			marker.addEventListener('click', function () {
-				const deviceType = row.product.deviceType === '网关' ? '网关' : '设备'
+				const isGateway = row.product.deviceType === '网关'
+				const deviceTypeText = isGateway ? t('message.property.deviceMap.popup.deviceTypeGateway') : t('message.property.deviceMap.popup.deviceTypeDevice')
+				const addrLabel = t('message.property.deviceMap.popup.addressLabel')
 				// api.screen.alarmCount(row.key).then(({ handled, number, unhandled }: any) => {
 				const number = 0
 				const unhandled = 0
@@ -111,30 +115,30 @@ async function setMarker() {
 							? `
           <div class="map-hover-nums">
             <div class="map-hover-nums-item">
-              <div class="map-hover-nums-label">今日报警总数</div>
+              <div class="map-hover-nums-label">${t('message.property.deviceMap.popup.todayAlarmTotal')}</div>
               <div class="map-hover-nums-value">${number}</div>
             </div>
             <div class="map-hover-nums-item">
-              <div class="map-hover-nums-label">今日未处理报警</div>
+              <div class="map-hover-nums-label">${t('message.property.deviceMap.popup.todayUnhandled')}</div>
               <div class="map-hover-nums-value">${unhandled}</div>
             </div>
             <div class="map-hover-nums-item">
-              <div class="map-hover-nums-label">今日已处理报警</div>
+              <div class="map-hover-nums-label">${t('message.property.deviceMap.popup.todayHandled')}</div>
               <div class="map-hover-nums-value">${handled}</div>
             </div>
           </div>`
 							: ''
 					}
             <div class="map-hover-row-item">
-              <div class="map-hover-label">编号:</div>
+              <div class="map-hover-label">${t('message.property.deviceMap.popup.codeLabel')}</div>
               <div class="map-hover-value">${row.key}</div>
             </div>
             <div class="map-hover-row-item">
-              <div class="map-hover-label">最后在线时间:</div>
+              <div class="map-hover-label">${t('message.property.deviceMap.popup.lastOnlineTimeLabel')}</div>
               <div class="map-hover-value">${row.lastOnlineTime || '-'}</div>
             </div>
             <div class="map-hover-row-item">
-              <div class="map-hover-label">${deviceType}地址:</div>
+              <div class="map-hover-label">${deviceTypeText} ${addrLabel}</div>
               <div class="map-hover-value">${row.address || '-'}</div>
             </div>
           </div>

+ 6 - 3
src/views/iot/property/dossier/component/from.vue

@@ -4,7 +4,7 @@
 		<div v-for="(item, index) in dataList" :key="index">
 
       <el-form-item :label="item.title + ':'" :prop="item.name" class="form-item" v-if="item.types === 'input'">
-        <el-input v-model="formData[item.name]" :placeholder="'请输入' + item.title" @input="saveData()" :readonly="disable" />
+        <el-input v-model="formData[item.name]" :placeholder="t('message.property.dossier.form.inputPlaceholder', { title: item.title })" @input="saveData()" :readonly="disable" />
       </el-form-item>
 
       <el-form-item :label="item.title + ':'" :prop="item.name" class="form-item" v-if="item.types === 'textarea'">
@@ -12,13 +12,13 @@
       </el-form-item>
 
       <el-form-item v-if="item.types === 'date'" :label="item.title + ':'">
-        <el-date-picker v-model="formData[item.name]" :default-value="item.value" type="date" value-format="YYYY-MM-DD" placeholder="请选择时间" class="w100" clearable @change="saveData()" :readonly="disable" />
+        <el-date-picker v-model="formData[item.name]" :default-value="item.value" type="date" value-format="YYYY-MM-DD" :placeholder="t('message.property.dossier.form.datePlaceholder')" class="w100" clearable @change="saveData()" :readonly="disable" />
       </el-form-item>
 
       <el-form-item :label="item.title + ':'" prop="path" v-if="item.types === 'file'">
         <el-upload class="avatar-uploader" :action="uploadUrl" :headers="headers" :show-file-list="false" :on-success="customCallback(item.name)" :disabled="disable">
           <img v-if="formData[item.name]" :src="formData[item.name]" class="avatar" />
-          <el-icon v-else class="avatar-uploader-icon" v-if="!disable">
+          <el-icon v-else-if="!disable" class="avatar-uploader-icon">
             <Plus />
           </el-icon>
         </el-upload>
@@ -33,6 +33,7 @@ import {onMounted, defineComponent, reactive, toRefs} from 'vue'
 import getOrigin from '/@/utils/origin'
 import { Plus } from '@element-plus/icons-vue'
 import { getToken } from "/@/utils/auth";
+import { useI18n } from 'vue-i18n'
 
 interface FromState {
   dataList: any,
@@ -53,6 +54,7 @@ export default defineComponent({
     }
   },
   setup(prop, { emit }) {
+    const { t } = useI18n()
     const uploadUrl = getOrigin('/common/singleFile')
     const headers = {
       Authorization: 'Bearer ' + getToken(),
@@ -102,6 +104,7 @@ export default defineComponent({
       uploadUrl,
       customCallback,
       saveData,
+      t,
       ...toRefs(state),
     };
   },

+ 68 - 70
src/views/iot/property/dossier/edit.vue

@@ -1,11 +1,10 @@
 <template>
-  <el-dialog class="api-edit" v-model="showDialog" :title="`${formData.id ? '编辑设备档案' : '新增设备档案'}`" width="800px" :close-on-click-modal="false" :close-on-press-escape="false">
+  <el-dialog class="api-edit" v-model="showDialog" :title="formData.id ? t('message.property.dossier.dialog.editTitle') : t('message.property.dossier.dialog.addTitle')" width="800px" :close-on-click-modal="false" :close-on-press-escape="false">
     <el-form class="inline-form" ref="formRef" :model="formData" :rules="ruleForm" label-width="120px">
-
       <el-row>
         <el-col :span="12">
-          <el-form-item label="选择产品" prop="productKey">
-            <el-select v-model="formData.productKey" placeholder="请选择产品" class="w100" disabled>
+          <el-form-item :label="t('message.property.dossier.form.productKey')" prop="productKey">
+            <el-select v-model="formData.productKey" :placeholder="t('message.property.dossier.form.productPlaceholder')" class="w100" disabled>
               <el-option v-for="item in productData" :key="item.key" :label="item.name" :value="item.key">
                 <span style="float: left">{{ item.name }}</span>
                 <span style="float: right; font-size: 13px">{{ item.key }}</span>
@@ -15,8 +14,8 @@
         </el-col>
 
         <el-col :span="12">
-          <el-form-item label="选择设备" prop="deviceKey">
-            <el-select v-model="formData.deviceKey" placeholder="请选择设备" class="w100" filterable clearable @change="handleSelectionChange">
+          <el-form-item :label="t('message.property.dossier.form.deviceKey')" prop="deviceKey">
+            <el-select v-model="formData.deviceKey" :placeholder="t('message.property.dossier.form.devicePlaceholder')" class="w100" filterable clearable @change="handleSelectionChange">
               <el-option v-for="item in deviceList" :key="item.key" :label="item.name" :value="item.key">
                 <span style="float: left">{{ item.name }}</span>
                 <span style="float: right; font-size: 13px">{{ item.key }}</span>
@@ -28,22 +27,22 @@
 
       <el-row>
         <el-col :span="12">
-          <el-form-item label="设备名称" prop="deviceName">
-            <el-input v-model.trim="formData.deviceName" placeholder="请输入设备名称" disabled />
+          <el-form-item :label="t('message.property.dossier.form.deviceName')" prop="deviceName">
+            <el-input v-model.trim="formData.deviceName" :placeholder="t('message.property.dossier.form.deviceNamePlaceholder')" disabled />
           </el-form-item>
         </el-col>
 
         <el-col :span="12">
-          <el-form-item label="设备编码" prop="deviceNumber">
-            <el-input v-model.trim="formData.deviceNumber" placeholder="请输入设备编码" />
+          <el-form-item :label="t('message.property.dossier.form.deviceNumber')" prop="deviceNumber">
+            <el-input v-model.trim="formData.deviceNumber" :placeholder="t('message.property.dossier.form.deviceNumberPlaceholder')" />
           </el-form-item>
         </el-col>
       </el-row>
 
       <el-row>
         <el-col :span="12">
-          <el-form-item label="所属部门" prop="deptId">
-            <el-cascader :options="deptData" :props="{ checkStrictly: true, emitPath: false, value: 'deptId', label: 'deptName' }" placeholder="请选择所属部门" clearable class="w100" v-model="formData.deptId">
+          <el-form-item :label="t('message.property.dossier.form.deptId')" prop="deptId">
+            <el-cascader :options="deptData" :props="{ checkStrictly: true, emitPath: false, value: 'deptId', label: 'deptName' }" :placeholder="t('message.property.dossier.form.deptPlaceholder')" clearable class="w100" v-model="formData.deptId">
               <template #default="{ node, data }">
                 <span>{{ data.deptName }}</span>
                 <span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
@@ -53,101 +52,102 @@
         </el-col>
 
         <el-col :span="12">
-          <el-form-item label="设备类型">
-            <el-input v-model.trim="formData.deviceCategory" placeholder="请输入设备类型" />
+          <el-form-item :label="t('message.property.dossier.form.deviceCategory')">
+            <el-input v-model.trim="formData.deviceCategory" :placeholder="t('message.property.dossier.form.deviceCategoryPlaceholder')" />
           </el-form-item>
         </el-col>
       </el-row>
 
       <el-row>
         <el-col :span="12">
-          <el-form-item label="安装时间">
-            <el-date-picker v-model="formData.installTime" type="date" value-format="YYYY-MM-DD" placeholder="请选择时间" class="w100" clearable />
+          <el-form-item :label="t('message.property.dossier.form.installTime')">
+            <el-date-picker v-model="formData.installTime" type="date" value-format="YYYY-MM-DD" :placeholder="t('message.property.dossier.form.datePlaceholder')" class="w100" clearable />
           </el-form-item>
         </el-col>
       </el-row>
 
-      <el-divider content-position="left" v-if="Datalist">自定义属性</el-divider>
+      <el-divider content-position="left" v-if="Datalist">{{ t("message.property.dossier.form.dividerCustom") }}</el-divider>
       <FromData :DataList="Datalist" @SetSaveData="SetSaveData" v-if="Datalist && Datalist.length > 0"></FromData>
-
     </el-form>
     <template #footer>
       <div class="dialog-footer">
-        <el-button @click="showDialog = false">取消</el-button>
-        <el-button type="primary" @click="onSubmit">确定</el-button>
+        <el-button @click="showDialog = false">{{ t("message.property.dossier.messages.cancel") }}</el-button>
+        <el-button type="primary" @click="onSubmit">{{ t("message.property.dossier.messages.confirm") }}</el-button>
       </div>
     </template>
   </el-dialog>
 </template>
 
 <script lang="ts" setup>
-import { ref, reactive, nextTick } from 'vue';
-import api from '/@/api/device'
-import system from '/@/api/system';
-import FromData from './component/from.vue';
+import { ref, reactive, nextTick, computed } from "vue";
+import api from "/@/api/device";
+import system from "/@/api/system";
+import FromData from "./component/from.vue";
 
-import { ruleRequired } from '/@/utils/validator';
-import { ElMessage } from 'element-plus';
+import { ruleRequired } from "/@/utils/validator";
+import { ElMessage } from "element-plus";
+import { useI18n } from "vue-i18n";
 
-const emit = defineEmits(['getList']);
+const emit = defineEmits(["getList"]);
+const { t } = useI18n();
 
 const showDialog = ref(false);
 const formRef = ref();
-const orgData = ref();
-const deviceList = ref();
-const productData = ref();
-const deptData = ref();
-const Datalist = ref();
-const newData = ref([]);
+const orgData = ref<any[]>([]);
+const deviceList = ref<Array<{ key: string; name: string }>>([]);
+const productData = ref<any[]>([]);
+const deptData = ref<any[]>([]);
+const Datalist = ref<any[]>([]);
 const baseForm = {
   id: undefined,
-  productKey: '',
-  deviceKey: '',
-  deviceName: '',
+  productKey: "",
+  deviceKey: "",
+  deviceName: "",
   area: "",
-  deviceNumber: '',
-  deviceCategory: '',
-  installTime: '',
-  deptId: '',
+  deviceNumber: "",
+  deviceCategory: "",
+  installTime: "",
+  deptId: "",
   data: [],
-
 };
 
-
 const SetSaveData = (data: any) => {
   formData.data = data;
-}
+};
 const formData = reactive({
   ...baseForm,
 });
 
-const ruleForm = {
-  productKey: [ruleRequired('所属产品不能为空')],
-  deviceName: [ruleRequired('设备名称不能为空')],
-  deviceKey: [ruleRequired('设备不能为空')],
-};
-const handleSelectionChange = (value: any) => {
-  const selectedOption = deviceList.value.find(option => option.key === value);
+const ruleForm = computed(() => {
+  return {
+    productKey: [ruleRequired(t("message.property.dossier.valid.productRequired"))],
+    deviceName: [ruleRequired(t("message.property.dossier.valid.deviceNameRequired"))],
+    deviceKey: [ruleRequired(t("message.property.dossier.valid.deviceRequired"))],
+  };
+});
+
+const handleSelectionChange = (value: string) => {
+  const selectedOption = deviceList.value.find((option: { key: string; name: string }) => option.key === value);
   if (selectedOption) {
     formData.deviceName = selectedOption.name;
   } else {
-    formData.deviceName = '';
+    formData.deviceName = "";
   }
-}
+};
 
 const onSubmit = async () => {
   await formRef.value.validate();
-  const theApi = !formData.id || formData.id === '0' ? api.dev_asset.add : api.dev_asset.edit;
+  const theApi = !formData.id || formData.id === "0" ? api.dev_asset.add : api.dev_asset.edit;
   await theApi(formData);
-  ElMessage.success('操作成功');
+  ElMessage.success(t("message.property.dossier.messages.saveSuccess"));
   resetForm();
   showDialog.value = false;
-  emit('getList');
+  emit("getList");
 };
 
 const resetForm = async () => {
   Object.assign(formData, { ...baseForm });
-  Datalist.value = ''
+  Datalist.value = [];
   formRef.value && formRef.value.resetFields();
 };
 
@@ -158,7 +158,7 @@ const getIdByKey = (key: string) => {
     }
   }
   return null; // 如果没有找到匹配的key,则返回null(或者其他合适的值)
-}
+};
 
 const open = async (row: any, productInfo: any) => {
   resetForm();
@@ -178,13 +178,13 @@ const open = async (row: any, productInfo: any) => {
         productInfo = {
           id: getIdByKey(row.productKey),
           key: row.productKey,
-        }
+        };
       }
       //根据产品ID获取设备列表
       api.device.allList({ productKey: productInfo.key }).then((resd: any) => {
-        deviceList.value = resd.device || [];
+        deviceList.value = (resd.device || []) as Array<{ key: string; name: string }>;
       });
-    })
+    });
 
     //获取部门
     api.dept.getList({ status: -1 }).then((res: any) => {
@@ -195,26 +195,24 @@ const open = async (row: any, productInfo: any) => {
     });
 
     if (row.id) {
-
       api.dev_asset.detail({ deviceKey: row.deviceKey }).then((resde: any) => {
         Object.assign(formData, { ...resde });
-        formData.productKey = row.productKey
-        const newArray = resde.data.map(obj => {
+        formData.productKey = row.productKey;
+        const newArray = (resde.data as any[]).map((obj: any) => {
           const { name, value, ...rest } = obj;
           const newObj = { name, value, ...rest };
-          newObj[name] = value ? value : '';
+          newObj[name] = value ? value : "";
           return newObj;
         });
-        Datalist.value = newArray
+        Datalist.value = newArray;
       });
     } else {
       //获取档案属性
-      api.dev_asset_metadata.detail({ productKey: productInfo.key }).then((res: any) => {
-        const sortedArray = res.sort((a, b) => a.id - b.id);
+      api.dev_asset_metadata.detail({ productKey: productInfo.key }).then((res: any[]) => {
+        const sortedArray = res.sort((a: any, b: any) => a.id - b.id);
         Datalist.value = sortedArray || [];
       });
-      formData.productKey = productInfo.key
-
+      formData.productKey = productInfo.key;
     }
   });
 };
@@ -225,4 +223,4 @@ defineExpose({ open });
 .demo-form-inline .el-input {
   --el-input-width: 320px;
 }
-</style>
+</style>

+ 22 - 20
src/views/iot/property/dossier/index.vue

@@ -1,8 +1,8 @@
 <template>
 	<div class="page page-full border bg padding">
 		<el-form inline ref="queryRef" @keyup.enter="getList(1)">
-			<el-form-item label="名称" prop="keyWord">
-				<el-input v-model="params.keyWord" placeholder="请输入名称" clearable style="width: 240px" />
+			<el-form-item :label="$t('message.property.dossier.form.keyword') + ':'" prop="keyWord">
+				<el-input v-model="params.keyWord" :placeholder="$t('message.property.dossier.form.keywordPlaceholder')" clearable style="width: 240px" />
 			</el-form-item>
 
 			<el-form-item>
@@ -10,7 +10,7 @@
 					<el-icon>
 						<ele-Search />
 					</el-icon>
-					查询
+					{{ $t('message.tableI18nAction.query') }}
 				</el-button>
 			</el-form-item>
 			<el-form-item>
@@ -18,7 +18,7 @@
 					<el-icon>
 						<ele-FolderAdd />
 					</el-icon>
-					删除
+					{{ $t('message.tableI18nAction.delete') }}
 				</el-button>
 			</el-form-item>
 		</el-form>
@@ -42,15 +42,15 @@
 				<div class="page page-full">
 					<el-table :data="tableData" style="width: 100%" @selection-change="handleSelectionChange" row-key="id" v-loading="loading">
 						<el-table-column type="selection" width="55" align="center" />
-						<el-table-column prop="deviceName" v-col="'deviceName'" label="设备名称" min-width="100" show-overflow-tooltip></el-table-column>
-						<el-table-column prop="deviceKey" v-col="'deviceKey'" label="设备KEY" show-overflow-tooltip></el-table-column>
-						<el-table-column prop="deviceNumber" v-col="'deviceNumber'" label="设备编码" show-overflow-tooltip></el-table-column>
-						<el-table-column prop="deviceCategory" v-col="'deviceCategory'" label="设备类型" show-overflow-tooltip></el-table-column>
-						<el-table-column prop="installTime" v-col="'installTime'" label="安装时间" width="160" align="center"></el-table-column>
-						<el-table-column label="操作" width="120" align="center">
+						<el-table-column prop="deviceName" v-col="'deviceName'" :label="$t('message.property.dossier.columns.deviceName')" min-width="100" show-overflow-tooltip />
+						<el-table-column prop="deviceKey" v-col="'deviceKey'" :label="$t('message.property.dossier.columns.deviceKey')" show-overflow-tooltip />
+						<el-table-column prop="deviceNumber" v-col="'deviceNumber'" :label="$t('message.property.dossier.columns.deviceNumber')" show-overflow-tooltip />
+						<el-table-column prop="deviceCategory" v-col="'deviceCategory'" :label="$t('message.property.dossier.columns.deviceCategory')" show-overflow-tooltip />
+						<el-table-column prop="installTime" v-col="'installTime'" :label="$t('message.property.dossier.columns.installTime')" width="160" align="center" />
+						<el-table-column :label="$t('message.tableI18nColumn.operation')" width="160" align="center">
 							<template #default="scope">
-								<el-button size="small" text type="warning" v-auth="'edit'" @click="addOrEdit(scope.row)">编辑</el-button>
-								<el-button size="small" text type="info" v-auth="'del'" @click="del(scope.row)">删除</el-button>
+								<el-button size="small" text type="warning" v-auth="'edit'" @click="addOrEdit(scope.row)">{{ $t('message.tableI18nAction.edit') }}</el-button>
+								<el-button size="small" text type="info" v-auth="'del'" @click="del(scope.row)">{{ $t('message.tableI18nAction.delete') }}</el-button>
 							</template>
 						</el-table-column>
 					</el-table>
@@ -69,6 +69,7 @@ import { Folder } from '@element-plus/icons-vue'
 
 import { ElMessageBox, ElMessage } from 'element-plus'
 import EditForm from './edit.vue'
+import { useI18n } from 'vue-i18n'
 interface Tree {
 	label: string
 	children?: Tree[]
@@ -80,6 +81,7 @@ const defaultProps = {
 }
 
 const queryRef = ref()
+const { t } = useI18n()
 const productData = ref([])
 const mergedData = ref()
 const cateData = ref()
@@ -177,25 +179,25 @@ const buildTree = (category: any, productData: any) => {
 }
 
 const batchdel = () => {
-	ElMessageBox.confirm('是否确认要批量删除这些数据吗?', '提示', {
-		confirmButtonText: '确认',
-		cancelButtonText: '取消',
+	ElMessageBox.confirm(t('message.property.dossier.messages.batchDeleteConfirm'), t('message.property.dossier.messages.tips'), {
+		confirmButtonText: t('message.property.dossier.messages.confirm'),
+		cancelButtonText: t('message.property.dossier.messages.cancel'),
 		type: 'warning',
 	}).then(async () => {
 		await device.dev_asset.delete({ deviceKeys: deviceKeys.value })
-		ElMessage.success('删除成功')
+		ElMessage.success(t('message.property.dossier.messages.deleteSuccess'))
 		getList()
 	})
 }
 
 const del = (row: any) => {
-	ElMessageBox.confirm('是否确认删除名称为:"' + row.deviceName + '"的数据项?', '提示', {
-		confirmButtonText: '确认',
-		cancelButtonText: '取消',
+	ElMessageBox.confirm(t('message.property.dossier.messages.deleteItemConfirm', { name: row.deviceName }), t('message.property.dossier.messages.tips'), {
+		confirmButtonText: t('message.property.dossier.messages.confirm'),
+		cancelButtonText: t('message.property.dossier.messages.cancel'),
 		type: 'warning',
 	}).then(async () => {
 		await device.dev_asset.delete({ deviceKeys: [row.deviceKey] })
-		ElMessage.success('删除成功')
+		ElMessage.success(t('message.property.dossier.messages.deleteSuccess'))
 		getList()
 	})
 }

+ 51 - 34
src/views/iot/property/relationship/edit.vue

@@ -1,50 +1,63 @@
 <template>
-  <el-dialog class="api-edit" v-model="showDialog" :title="`${formData.id ? '编辑资产关系' : '新增资产关系'}`" width="800px" :close-on-click-modal="false" :close-on-press-escape="false">
+  <el-dialog class="api-edit" v-model="showDialog" :title="$t(formData.id ? 'message.property.relationship.dialog.editTitle' : 'message.property.relationship.dialog.addTitle')" width="800px" :close-on-click-modal="false" :close-on-press-escape="false">
     <div class="dialog-wrapper" v-if="showDialog">
       <el-form class="inline-form" ref="formRef" :model="formData" :rules="ruleForm" label-width="auto">
-        <el-form-item label="选择角色" prop="roleId">
-          <el-cascader :options="roleData" :props="{
-            checkStrictly: true, multiple: false, emitPath: false, value: 'id', label: 'name'
-          }" placeholder="请选择角色" clearable class="w100" disabled v-model="formData.roleId">
+        <el-form-item :label="$t('message.property.relationship.form.chooseRole')" prop="roleId">
+          <el-cascader
+            :options="roleData"
+            :props="{
+              checkStrictly: true,
+              multiple: false,
+              emitPath: false,
+              value: 'id',
+              label: 'name',
+            }"
+            :placeholder="$t('message.property.relationship.form.chooseRolePlaceholder')"
+            clearable
+            class="w100"
+            disabled
+            v-model="formData.roleId"
+          >
             <template #default="{ node, data }">
               <span>{{ data.name }}</span>
               <span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
             </template>
           </el-cascader>
         </el-form-item>
-        <el-form-item label="已选设备" prop="deviceKeys">
-          <el-tag v-for="name in assetName" class="mr-2">{{ name }} </el-tag>
+        <el-form-item :label="$t('message.property.relationship.form.selectedDevices')" prop="deviceKeys">
+          <el-tag v-for="name in assetName" :key="name" class="mr-2">{{ name }} </el-tag>
         </el-form-item>
       </el-form>
       <el-table :data="tableData" style="width: 100%" row-key="id" @selection-change="handleSelectionChange" v-loading="loading">
         <el-table-column type="selection" reserve-selection width="55" align="center" />
-        <el-table-column prop="deviceName" label="设备名称" min-width="100" show-overflow-tooltip></el-table-column>
-        <el-table-column prop="deviceKey" label="设备KEY" show-overflow-tooltip></el-table-column>
-        <el-table-column prop="deviceNumber" label="设备编码" show-overflow-tooltip></el-table-column>
-        <el-table-column prop="deviceCategory" label="设备类型" show-overflow-tooltip></el-table-column>
-        <el-table-column prop="installTime" label="安装时间" width="160" align="center"></el-table-column>
+        <el-table-column prop="deviceName" :label="$t('message.property.relationship.columns.deviceName')" min-width="100" show-overflow-tooltip></el-table-column>
+        <el-table-column prop="deviceKey" :label="$t('message.property.relationship.columns.deviceKey')" show-overflow-tooltip></el-table-column>
+        <el-table-column prop="deviceNumber" :label="$t('message.property.relationship.columns.deviceNumber')" show-overflow-tooltip></el-table-column>
+        <el-table-column prop="deviceCategory" :label="$t('message.property.relationship.columns.deviceCategory')" show-overflow-tooltip></el-table-column>
+        <el-table-column prop="installTime" :label="$t('message.property.relationship.columns.installTime')" width="160" align="center"></el-table-column>
       </el-table>
       <pagination v-if="params.total" :total="params.total" v-model:page="params.pageNum" v-model:limit="params.pageSize" @pagination="getList()" />
     </div>
     <template #footer>
       <div class="dialog-footer">
-        <el-button @click="showDialog = false">取消</el-button>
-        <el-button type="primary" @click="onSubmit">确定</el-button>
+        <el-button @click="showDialog = false">{{ $t("message.property.relationship.messages.cancel") }}</el-button>
+        <el-button type="primary" @click="onSubmit">{{ $t("message.property.relationship.messages.confirm") }}</el-button>
       </div>
     </template>
   </el-dialog>
 </template>
 
 <script lang="ts" setup>
-import { ref, reactive, nextTick } from 'vue';
-import api from '/@/api/device'
-import system from '/@/api/system';
-import { useSearch } from '/@/hooks/useCommon'
-import { ruleRequired } from '/@/utils/validator';
-import { ElMessage } from 'element-plus';
-import { deepClone } from '/@/utils/other';
+import { ref, reactive, computed } from "vue";
+import { useI18n } from "vue-i18n";
+import api from "/@/api/device";
+import system from "/@/api/system";
+import { useSearch } from "/@/hooks/useCommon";
+import { ruleRequired } from "/@/utils/validator";
+import { ElMessage } from "element-plus";
+import { deepClone } from "/@/utils/other";
 
-const emit = defineEmits(['getList']);
+const emit = defineEmits(["getList"]);
 
 const showDialog = ref(false);
 const formRef = ref();
@@ -52,19 +65,23 @@ const roleData = ref([]);
 const assetName = ref<string[]>([]);
 const baseForm = {
   id: undefined,
-  roleId: '',
+  roleId: "",
   deviceKeys: [],
 };
 
 const formData = reactive<any>(deepClone(baseForm));
 
-const ruleForm = {
-  roleId: [ruleRequired('角色不能为空')],
-  deviceKeys: [ruleRequired('设备不能为空')],
-};
+const { t } = useI18n();
+
+const ruleForm = computed(() => {
+  return {
+    roleId: [ruleRequired(t("message.property.relationship.valid.roleRequired"))],
+    deviceKeys: [ruleRequired(t("message.property.relationship.valid.deviceRequired"))],
+  };
+});
 
-const { params, tableData, getList, loading } = useSearch<any[]>(api.dev_asset.getList, 'Data', { pageSize: 10 })
-getList()
+const { params, tableData, getList, loading } = useSearch<any[]>(api.dev_asset.getList, "Data", { pageSize: 10 });
+getList();
 
 const handleSelectionChange = (selection: any[]) => {
   formData.deviceKeys = selection.map((item) => item.deviceKey);
@@ -74,15 +91,15 @@ const handleSelectionChange = (selection: any[]) => {
 const onSubmit = async () => {
   await formRef.value.validate();
   await api.assetRelationship.bind(formData);
-  ElMessage.success('操作成功');
+  ElMessage.success(t("message.property.relationship.messages.saveSuccess"));
   resetForm();
   showDialog.value = false;
-  emit('getList');
+  emit("getList");
 };
 
 const resetForm = async () => {
   Object.assign(formData, deepClone(baseForm));
-  assetName.value = []
+  assetName.value = [];
   formRef.value && formRef.value.resetFields();
 };
 
@@ -93,7 +110,7 @@ const open = async (roleId: string) => {
   //获取所有的角色
   system.role.getList({ status: 1 }).then((resp: any) => {
     roleData.value = resp;
-  })
+  });
 };
 
 defineExpose({ open });
@@ -102,4 +119,4 @@ defineExpose({ open });
 .demo-form-inline .el-input {
   --el-input-width: 320px;
 }
-</style>
+</style>

+ 13 - 11
src/views/iot/property/relationship/filter.vue

@@ -1,35 +1,37 @@
 <template>
-  <el-dialog class="api-edit" v-model="showDialog" title="设置筛选条件" width="800px" :close-on-click-modal="false" :close-on-press-escape="false">
+  <el-dialog class="api-edit" v-model="showDialog" :title="$t('message.property.relationship.dialog.filterTitle')" width="800px" :close-on-click-modal="false" :close-on-press-escape="false">
     <el-form class="inline-form" ref="formRef" label-width="auto">
-      <el-form-item v-for="(item, i) in ruleList" :key="item.id" :label="item.title" :prop="item.name">
+      <el-form-item v-for="item in ruleList" :key="item.id" :label="item.title" :prop="item.name">
         <template #label>
           <el-checkbox v-model="item.isCheck">{{ item.title }}</el-checkbox>
         </template>
-        <el-input v-if="item.types === 'input'" v-model="item.value" :disabled="!item.isCheck" placeholder="请输入" />
-        <el-input v-else-if="item.types === 'textarea'" v-model="item.value" :disabled="!item.isCheck" type="textarea" placeholder="请输入" />
-        <el-date-picker v-else-if="item.types === 'date'" v-model="item.value" :disabled="!item.isCheck" type="date" value-format="YYYY-MM-DD" placeholder="请选择时间" class="w100" clearable />
+        <el-input v-if="item.types === 'input'" v-model="item.value" :disabled="!item.isCheck" :placeholder="$t('message.property.dossier.form.inputPlaceholder', { title: item.title })" />
+        <el-input v-else-if="item.types === 'textarea'" v-model="item.value" :disabled="!item.isCheck" type="textarea" :placeholder="$t('message.property.dossier.form.inputPlaceholder', { title: item.title })" />
+        <el-date-picker v-else-if="item.types === 'date'" v-model="item.value" :disabled="!item.isCheck" type="date" value-format="YYYY-MM-DD" :placeholder="$t('message.property.dossier.form.datePlaceholder')" class="w100" clearable />
       </el-form-item>
       <el-form-item v-if="ruleList.length % 2 === 1"> </el-form-item>
     </el-form>
     <template #footer>
       <div class="dialog-footer">
-        <el-button @click="showDialog = false">取消</el-button>
-        <el-button type="primary" @click="onSubmit">确定</el-button>
+        <el-button @click="showDialog = false">{{ $t('message.property.relationship.messages.cancel') }}</el-button>
+        <el-button type="primary" @click="onSubmit">{{ $t('message.property.relationship.messages.confirm') }}</el-button>
       </div>
     </template>
   </el-dialog>
 </template>
 
 <script lang="ts" setup>
-import { ref, reactive, nextTick } from 'vue';
+import { ref, reactive } from 'vue';
+import { useI18n } from 'vue-i18n';
 import api from '/@/api/device'
 
 import { ElMessage } from 'element-plus';
 
 const emit = defineEmits(['getList']);
+const { t } = useI18n();
 const showDialog = ref(false);
 const formRef = ref();
-const ruleList = ref([]);
+const ruleList = ref<any[]>([]);
 const baseForm = {
   id: undefined,
 };
@@ -43,7 +45,7 @@ const onSubmit = async () => {
     const isNull = item.isCheck && !item.value
     if (isNull) {
       ElMessage.closeAll()
-      ElMessage.warning(`${item.title}不能为空`)
+      ElMessage.warning(t('message.property.relationship.valid.fieldRequiredTitle', { title: item.title }))
     }
     return isNull
   })
@@ -57,7 +59,7 @@ const onSubmit = async () => {
   })
 
   await api.assetRelationship.editRuleInfo({ id: formData.id, ruleInfo });
-  ElMessage.success('操作成功');
+  ElMessage.success(t('message.property.relationship.messages.saveSuccess'));
   resetForm();
   showDialog.value = false;
   emit('getList');

+ 56 - 73
src/views/iot/property/relationship/index.vue

@@ -1,15 +1,15 @@
 <template>
 	<div class="page page-full border bg padding">
 		<el-form inline ref="queryRef" @keyup.enter="getList(1)">
-			<el-form-item label="名称" prop="keyWord">
-				<el-input v-model="params.keyWord" placeholder="请输入名称" clearable style="width: 240px" />
+			<el-form-item :label="$t('message.property.relationship.form.keyword') + ':'" prop="keyWord">
+				<el-input v-model="params.keyWord" :placeholder="$t('message.property.relationship.form.keywordPlaceholder')" clearable style="width: 240px" />
 			</el-form-item>
 			<el-form-item>
 				<el-button type="primary" class="ml10" @click="getList(1)">
 					<el-icon>
 						<ele-Search />
 					</el-icon>
-					查询
+					{{ $t('message.tableI18nAction.query') }}
 				</el-button>
 			</el-form-item>
 			<el-form-item>
@@ -17,20 +17,20 @@
 					<el-icon>
 						<ele-FolderAdd />
 					</el-icon>
-					新增资产关系
+					{{ $t('message.property.relationship.actions.add') }}
 				</el-button>
 				<el-button type="info" @click="batchUnbind()" v-auth="'batchUnbind'" :disabled="!ids.length">
 					<el-icon>
 						<ele-FolderAdd />
 					</el-icon>
-					解绑
+					{{ $t('message.property.relationship.actions.unbind') }}
 				</el-button>
 			</el-form-item>
 		</el-form>
 		<div class="page page-full-part flex-row gap-4">
 			<el-card style="width: 250px;" shadow="never">
 				<el-tree :data="treeData" :props="defaultProps" accordion default-expand-all @node-click="handleNodeClick" :node-key="'id'" highlight-current>
-					<template #default="{ node, data }">
+					<template #default="{ data }">
 						<div class="custom-tree-node">
 							<span class="tree-label">{{ data.name }}</span>
 						</div>
@@ -41,21 +41,20 @@
 				<div class="page page-full">
 					<el-table :data="tableData" style="width: 100%" @selection-change="handleSelectionChange" row-key="id" v-loading="loading">
 						<el-table-column type="selection" width="55" align="center" />
-						<el-table-column prop="assetInfo.deviceName" v-col="'deviceName'" label="设备名称" min-width="100" show-overflow-tooltip></el-table-column>
-						<el-table-column prop="assetInfo.deviceKey" v-col="'deviceKey'" label="设备KEY" min-width="90" show-overflow-tooltip></el-table-column>
-						<el-table-column prop="assetInfo.deviceNumber" v-col="'deviceNumber'" label="设备编码" show-overflow-tooltip></el-table-column>
-						<el-table-column prop="assetInfo.deviceCategory" v-col="'deviceCategory'" label="设备类型" show-overflow-tooltip></el-table-column>
-						<el-table-column prop="assetInfo.installTime" v-col="'installTime'" label="安装时间" width="160" align="center"></el-table-column>
-						<el-table-column prop="status" label="状态" width="70" v-col="'status'" align="center">
+						<el-table-column prop="assetInfo.deviceName" v-col="'deviceName'" :label="$t('message.property.relationship.columns.deviceName')" min-width="100" show-overflow-tooltip></el-table-column>
+						<el-table-column prop="assetInfo.deviceKey" v-col="'deviceKey'" :label="$t('message.property.relationship.columns.deviceKey')" min-width="90" show-overflow-tooltip></el-table-column>
+						<el-table-column prop="assetInfo.deviceNumber" v-col="'deviceNumber'" :label="$t('message.property.relationship.columns.deviceNumber')" show-overflow-tooltip></el-table-column>
+						<el-table-column prop="assetInfo.deviceCategory" v-col="'deviceCategory'" :label="$t('message.property.relationship.columns.deviceCategory')" show-overflow-tooltip></el-table-column>
+						<el-table-column prop="assetInfo.installTime" v-col="'installTime'" :label="$t('message.property.relationship.columns.installTime')" width="160" align="center"></el-table-column>
+						<el-table-column prop="status" :label="$t('message.property.relationship.columns.status')" width="70" v-col="'status'" align="center">
 							<template #default="scope">
-								<el-switch v-model="scope.row.status" v-auth="'change-status'" inline-prompt :active-value="1" :inactive-value="0" active-text="启" inactive-text="禁" @change="handleStatusChange(scope.row)">
+								<el-switch v-model="scope.row.status" v-auth="'change-status'" inline-prompt :active-value="1" :inactive-value="0" :active-text="$t('message.property.relationship.switch.enableShort')" :inactive-text="$t('message.property.relationship.switch.disableShort')" @change="handleStatusChange(scope.row)">
 								</el-switch>
 							</template>
 						</el-table-column>
-						<el-table-column label="操作" width="170" v-col="'handle'" align="center">
+						<el-table-column :label="$t('message.tableI18nColumn.operation')" width="170" v-col="'handle'" align="center">
 							<template #default="scope">
-								<el-button size="small" text type="warning" v-auth="'unbind'" @click="unbind(scope.row)">解绑</el-button>
-								<!-- <el-button size="small" text type="primary" v-auth="'set'" @click="set(scope.row)">设置筛选条件</el-button> -->
+								<el-button size="small" text type="warning" v-auth="'unbind'" @click="unbind(scope.row)">{{ $t('message.property.relationship.actions.unbind') }}</el-button>
 							</template>
 						</el-table-column>
 					</el-table>
@@ -69,15 +68,17 @@
 </template>
 
 <script lang="ts" setup>
+import { useI18n } from 'vue-i18n'
+import { ref } from 'vue'
 import api from '/@/api/system'
 import device from '/@/api/device'
 import { useSearch } from '/@/hooks/useCommon'
-
-import { ElMessageBox, ElMessage } from 'element-plus'
 import EditForm from './edit.vue'
 import FilterVue from './filter.vue'
+import { ElMessageBox, ElMessage } from 'element-plus'
+
+const { t } = useI18n()
 
-import { ref, onMounted } from 'vue'
 const defaultProps = {
 	children: 'children',
 	label: 'name',
@@ -95,6 +96,7 @@ api.role.getList({ status: 1 }).then((res: any) => {
 
 const { params, tableData, getList, loading } = useSearch<any[]>(device.assetRelationship.getList, 'Data', { keyWord: '', roleId: -1 })
 getList()
+
 const handleSelectionChange = (selection: any[]) => {
 	ids.value = selection.map((item) => item.id);
 };
@@ -103,83 +105,64 @@ const addOrEdit = () => {
 	editFormRef.value.open(params.roleId)
 }
 
-const set = (row: any) => {
-	filterRef.value.open(row)
-}
-
 const handleNodeClick = (data: any) => {
 	params.roleId = data.id
 	getList()
 }
 
-// const batchdel = () => {
-// 	ElMessageBox.confirm('是否确认要批量删除这些数据吗?', '提示', {
-// 		confirmButtonText: '确认',
-// 		cancelButtonText: '取消',
-// 		type: 'warning',
-// 	}).then(async () => {
-// 		await device.assetRelationship.delete(ids.value)
-// 		ElMessage.success('删除成功')
-// 		getList()
-// 	})
-// }
-
-// const del = (row: any) => {
-// 	ElMessageBox.confirm('是否确认删除该设备档案', '提示', {
-// 		confirmButtonText: '确认',
-// 		cancelButtonText: '取消',
-// 		type: 'warning',
-// 	}).then(async () => {
-// 		await device.assetRelationship.delete([row.id])
-// 		ElMessage.success('删除成功')
-// 		getList()
-// 	})
-// }
-
-// 状态修改
 const handleStatusChange = (row: any) => {
-	let text = row.status === 1 ? '启用' : '停用';
-	ElMessageBox.confirm('确认要【' + text + '】吗?', '警告', {
-		confirmButtonText: '确定',
-		cancelButtonText: '取消',
-		type: 'warning',
-	})
+	const text = row.status === 1 ? t('message.property.relationship.status.enable') : t('message.property.relationship.status.disable')
+	ElMessageBox.confirm(
+		t('message.property.relationship.messages.statusChangeConfirm', { text }),
+		t('message.property.relationship.messages.warning'),
+		{
+			confirmButtonText: t('message.property.relationship.messages.confirm'),
+			cancelButtonText: t('message.property.relationship.messages.cancel'),
+			type: 'warning',
+		}
+	)
 		.then(function () {
-			return device.assetRelationship.editStatus(row.id, row.status);
+			return device.assetRelationship.editStatus(row.id, row.status)
 		})
 		.then(() => {
-			ElMessage.success(text + '成功');
+			ElMessage.success(t('message.property.relationship.messages.statusChangeSuccess', { text }))
 		})
 		.catch(function () {
-			row.status = row.status === 0 ? 1 : 0;
-		});
-};
+			row.status = row.status === 0 ? 1 : 0
+		})
+}
 
 const unbind = (row: any) => {
-	ElMessageBox.confirm('是否确认解绑', '提示', {
-		confirmButtonText: '确认',
-		cancelButtonText: '取消',
-		type: 'warning',
-	}).then(async () => {
+	ElMessageBox.confirm(
+		t('message.property.relationship.messages.unbindConfirm'),
+		t('message.property.relationship.messages.tips'),
+		{
+			confirmButtonText: t('message.property.relationship.messages.confirm'),
+			cancelButtonText: t('message.property.relationship.messages.cancel'),
+			type: 'warning',
+		}
+	).then(async () => {
 		await device.assetRelationship.unBind([row.id])
-		ElMessage.success('解绑成功')
+		ElMessage.success(t('message.property.relationship.messages.unbindSuccess'))
 		getList()
 	})
 }
 
 const batchUnbind = () => {
-	ElMessageBox.confirm('是否确认要批量解绑这些数据吗?', '提示', {
-		confirmButtonText: '确认',
-		cancelButtonText: '取消',
-		type: 'warning',
-	}).then(async () => {
+	ElMessageBox.confirm(
+		t('message.property.relationship.messages.batchUnbindConfirm'),
+		t('message.property.relationship.messages.tips'),
+		{
+			confirmButtonText: t('message.property.relationship.messages.confirm'),
+			cancelButtonText: t('message.property.relationship.messages.cancel'),
+			type: 'warning',
+		}
+	).then(async () => {
 		await device.assetRelationship.unBind(ids.value)
-		ElMessage.success('解绑成功')
+		ElMessage.success(t('message.property.relationship.messages.unbindSuccess'))
 		getList()
 	})
 }
-
-// getCateList()
 </script>
 <style scoped lang="scss">
 .custom-tree-node {

+ 3 - 3
src/views/modules/assess/component/addItem.vue

@@ -33,9 +33,9 @@
 				</el-table-column>
 				<el-table-column align="center" label="操作" width="160">
 					<template #default="scope">
-						<el-button size="small" type="text" @click="onOpenEditSign(scope.row)">编辑</el-button>
-						<el-button size="small" type="text" @click="onRowDel(scope.row)">删除</el-button>
-						<el-button size="small" type="text" @click="onRowDetail(scope.row)">任务接口</el-button>
+						<el-button size="small" text type="primary" @click="onOpenEditSign(scope.row)">编辑</el-button>
+						<el-button size="small" text type="primary" @click="onRowDel(scope.row)">删除</el-button>
+						<el-button size="small" text type="primary" @click="onRowDetail(scope.row)">任务接口</el-button>
 					</template>
 				</el-table-column>
 			</el-table>

+ 3 - 3
src/views/modules/assess/component/editItem.vue

@@ -27,9 +27,9 @@
 				</el-table-column>
 				<el-table-column align="center" label="操作" width="160">
 					<template #default="scope">
-						<el-button size="small" type="text" @click="onOpenEditSign(scope.row, scope.$index)">编辑</el-button>
-						<el-button size="small" type="text" @click="onRowDel(scope.row, scope.$index)">删除</el-button>
-						<el-button size="small" type="text" @click="onRowDetail(scope.row)">任务接口</el-button>
+						<el-button size="small" text type="primary" @click="onOpenEditSign(scope.row, scope.$index)">编辑</el-button>
+						<el-button size="small" text type="primary" @click="onRowDel(scope.row, scope.$index)">删除</el-button>
+						<el-button size="small" text type="primary" @click="onRowDetail(scope.row)">任务接口</el-button>
 					</template>
 				</el-table-column>
 			</el-table>

+ 3 - 3
src/views/modules/assess/index.vue

@@ -21,9 +21,9 @@
         <el-table-column align="left" prop="explain" v-col="'explain'" label="描述" show-overflow-tooltip></el-table-column>
         <el-table-column align="center" label="操作" width="180" v-col="'handle'">
           <template #default="scope">
-            <el-button size="small" type="text" @click="onOpenEditItem(scope.row)" v-auth="'edit'">编辑</el-button>
-            <el-button size="small" type="text" @click="onRowDel(scope.row)" v-auth="'del'">删除</el-button>
-            <el-button size="small" type="text" @click="onOpenDetailItem(scope.row)" v-auth="'detail'">详细信息</el-button>
+            <el-button size="small" text type="primary" @click="onOpenEditItem(scope.row)" v-auth="'edit'">编辑</el-button>
+            <el-button size="small" text type="primary" @click="onRowDel(scope.row)" v-auth="'del'">删除</el-button>
+            <el-button size="small" text type="primary" @click="onOpenDetailItem(scope.row)" v-auth="'detail'">详细信息</el-button>
           </template>
         </el-table-column>
       </el-table>

+ 5 - 6
src/views/modules/iotCard/platformManage/addOrEditItem.vue

@@ -38,7 +38,7 @@
     <template #footer>
       <span class="dialog-footer">
         <el-button @click="onCancel">{{ $t('message.iotCard.platformManage.addOrEdit.buttons.cancel') }}</el-button>
-        <el-button :loading="btnLoading" type="primary" @click="onSubmit">{{ $t('message.iotCard.platformManage.addOrEdit.buttons.confirm') }}</el-button>
+        <el-button :loading="btnLoading" type="primary" @click="onSubmit()">{{ $t('message.iotCard.platformManage.addOrEdit.buttons.confirm') }}</el-button>
       </span>
     </template>
   </el-dialog>
@@ -46,7 +46,7 @@
 
 <script lang="ts" setup>
 import api from '/@/api/modules/iotCard';
-import { ref, unref } from 'vue';
+import { ref, unref, computed } from 'vue';
 import { ElMessage } from 'element-plus';
 import { useI18n } from 'vue-i18n';
 
@@ -73,16 +73,15 @@ const ruleForm = ref({
   ...baseForm
 })
 
-const rules = ref({
+const rules = computed(() => ({
   types: [{ required: true, message: t('message.iotCard.platformManage.addOrEdit.validation.platformType'), trigger: 'change' }],
   name: [{ required: true, message: t('message.iotCard.platformManage.addOrEdit.validation.name'), trigger: 'change' }],
   userId: [{ required: true, message: t('message.iotCard.platformManage.addOrEdit.validation.userId'), trigger: 'change' }],
   // password: [{ required: true, message: '请输入密码', trigger: 'change' }],
   appSecret: [{ required: true, message: t('message.iotCard.platformManage.addOrEdit.validation.secretKey'), trigger: 'change' }],
   appKey: [{ required: true, message: t('message.iotCard.platformManage.addOrEdit.validation.appId'), trigger: 'change' }],
-  restUrl: [{ required: true, message: t('message.iotCard.platformManage.addOrEdit.validation.interfaceUrl'), trigger: 'change' }]
-})
-
+  restUrl: [{ required: true, message: t('message.iotCard.platformManage.addOrEdit.validation.interfaceUrl'), trigger: 'change' }],
+}))
 /**
  * 新增
  */

+ 44 - 11
src/views/system/api/index.vue

@@ -142,17 +142,50 @@ const onDel = (row: ApiRow) => {
 };
 
 // const autoAddList = [
-// 	{
-// 		menuIds: [240],
-// 		name: "插件测试",
-// 		types: 2,
-// 		apiTypes: "IOT",
-// 		address: "/api/v1/plugin/test",
-// 		method: "POST",
-// 		remark: "",
-// 		status: 1,
-// 		parentId: 708,
-// 	},
+//   {
+//     menuIds: [241],
+//     name: "标签新增",
+//     types: 2,
+//     apiTypes: "IOT",
+//     address: "/api/v1/tag/add",
+//     method: "POST",
+//     remark: "",
+//     status: 1,
+//     parentId: 748,
+//   },
+//   {
+//     menuIds: [241],
+//     name: "标签删除",
+//     types: 2,
+//     apiTypes: "IOT",
+//     address: "/api/v1/tag/del",
+//     method: "DELETE",
+//     remark: "",
+//     status: 1,
+//     parentId: 748,
+//   },
+//   {
+//     menuIds: [241],
+//     name: "标签修改",
+//     types: 2,
+//     apiTypes: "IOT",
+//     address: "/api/v1/tag/edit",
+//     method: "PUT",
+//     remark: "",
+//     status: 1,
+//     parentId: 748,
+//   },
+//   {
+//     menuIds: [241],
+//     name: "标签详情",
+//     types: 2,
+//     apiTypes: "IOT",
+//     address: "/api/v1/tag/detail",
+//     method: "GET",
+//     remark: "",
+//     status: 1,
+//     parentId: 748,
+//   },
 // ];
 
 // function autoAddApi() {

+ 173 - 0
src/views/system/datahub/indicator/component/data.vue

@@ -0,0 +1,173 @@
+<template>
+  <el-dialog v-model="visible" :title="`指标数据 - ${title}`" width="850px" :close-on-click-modal="false" destroy-on-close>
+    <div v-if="visible">
+      <el-form :inline="true" class="toolbar">
+        <el-form-item>
+          <el-input v-model="query.searchValue" placeholder="输入指标值或原始值" clearable style="width: 160px" />
+        </el-form-item>
+        <el-form-item label="时间范围">
+          <el-date-picker v-model="query.dateRange" type="datetimerange" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss" style="width: 380px" />
+        </el-form-item>
+        <!-- <el-form-item label="维度筛选">
+          <el-select v-model="dimensionSelectedText" style="width: 120px" placeholder="全部维度" />
+        </el-form-item> -->
+        <el-form-item label="">
+          <el-button type="primary" :icon="Filter" @click="fetchList(1)">筛选</el-button>
+          <!-- <el-button @click="exportData">导出</el-button> -->
+        </el-form-item>
+      </el-form>
+
+      <el-table :data="list" style="width: 100%" v-loading="loading" max-height="50vh">
+        <el-table-column label="时间" align="center" prop="collectionTime" width="180" />
+        <el-table-column v-if="detail.computeStrategy !== 'aggregation'" prop="value" :label="`指标值${detail.unit ? ' (' + detail.unit + ')' : ''}`" min-width="140" align="center" />
+        <template v-else>
+          <el-table-column prop="maxValue" :label="`最大值${detail.unit ? ' (' + detail.unit + ')' : ''}`" align="center" />
+          <el-table-column prop="minValue" :label="`最小值${detail.unit ? ' (' + detail.unit + ')' : ''}`" align="center" />
+          <el-table-column prop="avgValue" :label="`平均值${detail.unit ? ' (' + detail.unit + ')' : ''}`" align="center" />
+          <el-table-column prop="sumValue" :label="`总和${detail.unit ? ' (' + detail.unit + ')' : ''}`" align="center" />
+          <el-table-column prop="countValue" label="数量值" align="center" />
+        </template>
+        <!-- <el-table-column label="原始值" width="120" align="left">
+          <template #default="scope">{{ scope.row.rawValue ?? scope.row.originValue ?? "-" }}</template>
+        </el-table-column> -->
+        <!-- <el-table-column label="监测点" prop="monitorPoint" width="120" align="left"> </el-table-column>
+        <el-table-column label="深度" prop="depth" width="120" align="left"> </el-table-column>
+        <el-table-column label="设备" prop="device" width="120" align="left"> </el-table-column>
+        <el-table-column v-for="k in dimKeys" :key="k" :label="dimNameMap[k] || k" min-width="120" show-overflow-tooltip>
+          <template #default="scope">{{ (scope.row.dimensions && scope.row.dimensions[k]) ?? scope.row[k] ?? "-" }}</template>
+        </el-table-column> -->
+      </el-table>
+
+      <div class="pager flex-end">
+        <el-pagination background layout="prev, pager, next, ->, total, sizes" :page-sizes="[20, 50, 100, 200]" :total="total" :page-size="query.pageSize" :current-page="query.pageNum" @current-change="(p:number)=>fetchList(p)" @size-change="(s:number)=>{query.pageSize=s;fetchList(1)}" />
+      </div>
+    </div>
+  </el-dialog>
+</template>
+
+<script lang="ts" setup>
+import { reactive, ref, computed } from "vue";
+import { ElMessage } from "element-plus";
+import { Filter } from "@element-plus/icons-vue";
+import api from "/@/api/datahub";
+
+const visible = ref(false);
+const loading = ref(false);
+const code = ref("");
+const title = ref("");
+const detail = reactive<any>({}); // 详情含 unit/维度等
+const list = ref<any[]>([]);
+const total = ref(0);
+
+// 查询参数(对应后端文档)
+const query = reactive({
+  searchValue: "",
+  keyWord: "",
+  year: "",
+  accurate: "h",
+  dateRange: [],
+  pageNum: 1,
+  pageSize: 20,
+});
+
+const dimensionFilters = reactive<Record<string, any>>({});
+
+const dimKeys = computed(() => {
+  const set = new Set<string>();
+  list.value.forEach((row: any) => {
+    if (row?.dimensions && typeof row.dimensions === "object") {
+      Object.keys(row.dimensions).forEach((k) => set.add(k));
+    }
+  });
+  return Array.from(set);
+});
+const dimNameMap = computed<Record<string, string>>(() => {
+  const map: Record<string, string> = {};
+  (detail.dimensions || []).forEach((d: any) => {
+    map[d.key || d.name] = d.name || d.key;
+  });
+  return map;
+});
+const dimensionSelectedText = computed(() => {
+  const entries = Object.entries(dimensionFilters).filter(([, v]) => v !== "" && v !== undefined && v !== null);
+  if (!entries.length) return "全部维度";
+  return entries.map(([k, v]) => `${dimNameMap.value[k] || k}:${v}`).join(";");
+});
+
+function buildParams() {
+  const params: any = {
+    ...query,
+    code: code.value,
+  };
+  // 维度 JSON 字符串
+  const dims: Record<string, any> = {};
+  Object.entries(dimensionFilters).forEach(([k, v]) => {
+    if (v !== "" && v !== undefined && v !== null) dims[k] = v;
+  });
+  if (Object.keys(dims).length) params.dimensions = JSON.stringify(dims);
+  return params;
+}
+
+function fetchDetail() {
+  const getDetail = (api.indicator as any).detail || (api.indicator as any).data;
+  return getDetail(code.value).then((res: any) => {
+    const data = res?.data || res?.Info || res || {};
+    Object.assign(detail, data);
+  });
+}
+
+function fetchList(p?: number) {
+  if (typeof p === "number") query.pageNum = p;
+  loading.value = true;
+  const params = buildParams();
+  api.indicator
+    .getData(params)
+    .then((res: any) => {
+      // 兼容不同返回格式
+      const data = res?.data ?? res?.Info ?? res ?? {};
+      const rows = data?.list ?? data?.rows ?? data?.records ?? data ?? [];
+      list.value = Array.isArray(rows) ? rows : [];
+      total.value = res?.total ?? data?.total ?? data?.count ?? list.value.length ?? 0;
+    })
+    .finally(() => (loading.value = false));
+}
+
+function open(row: any) {
+  code.value = row?.code || "";
+  title.value = `${row?.name || "-"} (${code.value})`;
+  visible.value = true;
+  // 清空筛选
+  Object.assign(query, { searchValue: "", keyWord: "", year: "", startTime: "", endTime: "", accurate: "", accurateRanges: "", orderBy: "", pageNum: 1, pageSize: 20 });
+  Object.keys(dimensionFilters).forEach((k) => delete (dimensionFilters as any)[k]);
+  fetchDetail().then(() => fetchList(1));
+}
+
+function exportData() {
+  ElMessage.info("导出功能请在接口确定后接入");
+}
+
+defineExpose({ open });
+</script>
+
+<style lang="scss" scoped>
+.sub-title {
+  color: var(--el-text-color-secondary);
+}
+.toolbar {
+  margin-top: 10px;
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+  align-items: center;
+}
+.pager {
+  margin-top: 12px;
+  display: flex;
+  justify-content: flex-end;
+  align-items: center;
+}
+.empty-tip {
+  color: var(--el-text-color-secondary);
+  text-align: center;
+}
+</style>

+ 62 - 0
src/views/system/datahub/indicator/component/detail.vue

@@ -0,0 +1,62 @@
+<template>
+  <el-dialog v-model="visible" :title="`指标详情 - ${detail.name || '-'} (${detail.code || code})`" width="720px" :close-on-click-modal="false">
+    <div v-if="visible">
+      <el-descriptions :column="1" border class="mt16">
+        <el-descriptions-item label="指标名称">{{ detail.name || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="指标描述">{{ detail.description || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="指标类型">
+          <el-tag size="small">{{ detail.typeName || "-" }}</el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="单位">{{ detail.unit || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="计算公式">
+          <span class="mono">{{ detail.formula || "-" }}</span>
+        </el-descriptions-item>
+        <el-descriptions-item label="维度数">{{ detail.dimensions?.length }} 个</el-descriptions-item>
+        <el-descriptions-item label="状态">
+          <el-tag :type="detail.status == '1' ? 'success' : 'info'">{{ detail.status == "1" ? "已发布" : "未发布" }}</el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="指标描述">{{ detail.description || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="创建时间">{{ detail.createdAt || detail.createTime || "-" }}</el-descriptions-item>
+        <el-descriptions-item label="创建人">{{ detail.createdBy || "-" }}</el-descriptions-item>
+      </el-descriptions>
+    </div>
+    <template #footer>
+      <el-button @click="visible = false">关闭</el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script lang="ts" setup>
+import { reactive, ref } from "vue";
+import api from "/@/api/datahub";
+
+const visible = ref(false);
+const code = ref("");
+const detail = reactive<any>({});
+
+function open(row: any) {
+  code.value = row?.code || "";
+  // Object.keys(detail).forEach((k) => delete (detail as any)[k]);
+  visible.value = true;
+  if (!code.value) return;
+  api.indicator.detail(code.value).then((res: any) => {
+    const data = res?.data || res?.Info || res || {};
+    Object.assign(detail, data);
+    detail.typeName = row.tagInfo?.[0]?.name || "-";
+  });
+}
+
+defineExpose({ open });
+</script>
+
+<style lang="scss" scoped>
+.sub-title {
+  color: var(--el-text-color-secondary);
+}
+.mt16 {
+  margin-top: 16px;
+}
+.mono {
+  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+}
+</style>

+ 409 - 0
src/views/system/datahub/indicator/component/edit.vue

@@ -0,0 +1,409 @@
+<template>
+  <div class="indicator-edit">
+    <el-dialog v-model="visible" :title="isEdit ? '编辑指标' : '新增指标'" width="800px" :close-on-click-modal="false" destroy-on-close>
+      <div v-if="visible">
+        <el-tabs v-model="activeTab">
+          <el-tab-pane label="基本信息" name="base">
+            <el-form ref="formRef" :model="form" :rules="rules" label-width="110px" class="pt10">
+              <div class="row">
+                <el-form-item label="指标编码" prop="code" class="flex1">
+                  <el-input v-model.trim="form.code" :disabled="isEdit" placeholder="请输入唯一编码,如 IND001" clearable />
+                </el-form-item>
+                <el-form-item label="指标名称" prop="name" class="flex1">
+                  <el-input v-model.trim="form.name" placeholder="请输入指标名称" clearable />
+                </el-form-item>
+              </div>
+              <div class="row">
+                <el-form-item label="指标类型" prop="type" class="flex1">
+                  <el-cascader :options="typeOptions" :props="{ checkStrictly: true, emitPath: false, value: 'code', label: 'name' }" placeholder="请选择指标类型" clearable class="w100" v-model="form.type">
+                    <template #default="{ node, data }">
+                      <span>{{ data.name }}</span>
+                      <span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
+                    </template>
+                  </el-cascader>
+                </el-form-item>
+                <el-form-item label="单位" prop="unit" class="flex1">
+                  <el-input v-model.trim="form.unit" placeholder="请输入计量单位(如 mg/L、%)" clearable />
+                </el-form-item>
+              </div>
+              <div class="row">
+                <el-form-item label="数据建模KEY" prop="dataTemplateKey" class="flex1">
+                  <el-input v-model.trim="form.dataTemplateKey" placeholder="请输入数据建模KEY" clearable />
+                </el-form-item>
+                <el-form-item label="计算策略" prop="computeStrategy" class="flex1">
+                  <el-select v-model="form.computeStrategy" placeholder="请选择计算策略" clearable style="width: 100%">
+                    <el-option label="聚合计算" value="aggregation" />
+                    <el-option label="累计计算" value="accumulation" />
+                    <el-option label="数学计算" value="mathematical" />
+                  </el-select>
+                </el-form-item>
+              </div>
+              <div class="row">
+                <el-form-item label="计算模式" prop="calculationMode" class="flex1">
+                  <el-radio-group v-model="form.calculationMode">
+                    <el-radio label="demand">按需计算</el-radio>
+                    <el-radio label="schedule">定时计算</el-radio>
+                  </el-radio-group>
+                </el-form-item>
+                <el-form-item v-if="form.calculationMode === 'schedule'" label="计算周期" prop="calculationSchedule" class="flex1">
+                  <el-input v-model.trim="form.calculationSchedule" placeholder="请输入cron或表达式" clearable />
+                </el-form-item>
+              </div>
+              <el-form-item label="指标描述" prop="description">
+                <el-input v-model.trim="form.description" type="textarea" :rows="3" placeholder="请输入指标描述" />
+              </el-form-item>
+            </el-form>
+          </el-tab-pane>
+
+          <el-tab-pane label="计算公式" name="formula">
+            <div class="section-title">计算公式</div>
+            <el-input v-model="form.formula" type="textarea" :rows="4" placeholder="请输入计算公式,例如:COD = (V1 - V2) × C × 8 × 1000 / V" />
+            <div class="mt10 text-gray">支持数学运算符(+、-、×、÷)、括号、函数等,系统会自动识别公式中的参数</div>
+
+            <div class="section-title mt20 flex-between">
+              <span>公式参数</span>
+              <el-button type="success" @click="openParamDialog()">+ 添加参数</el-button>
+            </div>
+
+            <el-table :data="form.formulaParams" border style="width: 100%">
+              <el-table-column type="index" label="序号" width="60" align="center" />
+              <el-table-column label="参数名称" prop="name" align="center" />
+              <el-table-column label="参数编码" prop="code" align="center"/>
+              <el-table-column label="参数值" prop="values" align="center"/>
+              <el-table-column label="参数描述" prop="description" align="center"/>
+              <el-table-column label="操作" width="120" align="center">
+                <template #default="scope">
+                  <el-button size="small" text type="primary" @click="openParamDialog(scope.row, scope.$index)">编辑</el-button>
+                  <el-button size="small" text type="danger" @click="removeParam(scope.$index)">删除</el-button>
+                </template>
+              </el-table-column>
+            </el-table>
+            <div v-if="!form.formulaParams.length" class="empty-tip">暂无参数,请添加公式参数</div>
+          </el-tab-pane>
+
+          <el-tab-pane label="维度设置" name="dimension">
+            <div class="section-title flex-between">
+              <span>维度列表</span>
+              <el-button type="success" @click="openDimDialog()">+ 添加维度</el-button>
+            </div>
+
+            <el-table :data="form.dimensions" border style="width: 100%">
+              <el-table-column type="index" label="序号" width="60" align="center" />
+              <el-table-column label="维度名称" prop="name" align="center"/>
+              <el-table-column label="维度标识" prop="code" align="center"/>
+              <el-table-column label="维度值类型" prop="valueType" align="center">
+                <template #default="scope">{{ formatValueType(scope.row.valueType) }}</template>
+              </el-table-column>
+              <el-table-column label="维度值" prop="values" align="center"/>
+              <el-table-column label="维度描述" prop="description" align="center"/>
+              <el-table-column label="操作" width="120" align="center">
+                <template #default="scope">
+                  <el-button size="small" text type="primary" @click="openDimDialog(scope.row, scope.$index)">编辑</el-button>
+                  <el-button size="small" text type="danger" @click="removeDim(scope.$index)">删除</el-button>
+                </template>
+              </el-table-column>
+            </el-table>
+            <div v-if="!form.dimensions.length" class="empty-tip">暂无维度,请添加分析维度</div>
+          </el-tab-pane>
+        </el-tabs>
+      </div>
+
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="onCancel">取消</el-button>
+          <el-button type="primary" @click="onSave">保存</el-button>
+        </span>
+      </template>
+    </el-dialog>
+
+    <!-- 添加/编辑 参数 -->
+    <el-dialog v-model="paramDialog.visible" title="添加参数" width="520px" :close-on-click-modal="false">
+      <el-form :model="paramDialog.form" ref="paramFormRef" :rules="paramRules" label-width="90px">
+        <el-form-item label="参数名称" prop="name">
+          <el-input v-model.trim="paramDialog.form.name" placeholder="请输入参数名称" />
+        </el-form-item>
+        <el-form-item label="参数编码" prop="code">
+          <el-input v-model.trim="paramDialog.form.code" placeholder="请输入参数编码" />
+        </el-form-item>
+        <el-form-item label="参数值" prop="values">
+          <el-input v-model.trim="paramDialog.form.values" placeholder="请输入参数值" />
+        </el-form-item>
+        <el-form-item label="参数描述" prop="description">
+          <el-input v-model.trim="paramDialog.form.description" type="textarea" placeholder="请输入参数描述" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="paramDialog.visible = false">取消</el-button>
+        <el-button type="primary" @click="confirmParam">添加</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 添加/编辑 维度 -->
+    <el-dialog v-model="dimDialog.visible" title="添加维度" width="520px" :close-on-click-modal="false">
+      <el-form :model="dimDialog.form" ref="dimFormRef" :rules="dimRules" label-width="100px">
+        <el-form-item label="维度名称" prop="name">
+          <el-input v-model.trim="dimDialog.form.name" placeholder="请输入维度名称" />
+        </el-form-item>
+        <el-form-item label="维度标识" prop="code">
+          <el-input v-model.trim="dimDialog.form.code" placeholder="请输入维度标识,如 time, location" />
+        </el-form-item>
+        <el-form-item label="维度值类型" prop="valueType">
+          <el-select v-model="dimDialog.form.valueType" placeholder="请选择值类型" style="width: 100%">
+            <el-option label="字符串" value="string" />
+            <el-option label="数值" value="number" />
+            <el-option label="布尔" value="boolean" />
+            <el-option label="时间" value="datetime" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="维度值" prop="values">
+          <el-input v-if="dimDialog.form.valueType === 'string'" v-model.trim="dimDialog.form.values" placeholder="请输入维度值" />
+          <el-input v-else-if="dimDialog.form.valueType === 'number'" type="number" v-model.trim="dimDialog.form.values" placeholder="请输入维度值" />
+          <el-radio-group v-else-if="dimDialog.form.valueType === 'boolean'" v-model="dimDialog.form.values">
+            <el-radio :label="true">是</el-radio>
+            <el-radio :label="false">否</el-radio>
+          </el-radio-group>
+          <el-date-picker v-else-if="dimDialog.form.valueType === 'datetime'" type="datetime" format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss" v-model="dimDialog.form.values" placeholder="请选择维度值" />
+        </el-form-item>
+        <el-form-item label="维度描述" prop="description">
+          <el-input v-model.trim="dimDialog.form.description" type="textarea" placeholder="请输入维度描述" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="dimDialog.visible = false">取消</el-button>
+        <el-button type="primary" @click="confirmDim">添加</el-button>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { reactive, ref } from "vue";
+import { ElMessage } from "element-plus";
+import api from "/@/api/datahub";
+import apiSystem from "/@/api/system";
+
+const emit = defineEmits(["update"]);
+
+const visible = ref(false);
+const isEdit = ref(false);
+const activeTab = ref<"base" | "formula" | "dimension">("base");
+
+type FormulaParam = { name: string; code: string; values: string; description?: string };
+type Dimension = { name: string; code: string; valueType: "string" | "number" | "boolean" | "datetime"; values: string; description?: string };
+
+const formRef = ref();
+const form = reactive({
+  code: "",
+  name: "",
+  description: "",
+  type: "",
+  unit: "",
+  dataTemplateKey: "",
+  formula: "",
+  formulaParams: [] as FormulaParam[],
+  computeStrategy: "",
+  calculationMode: "demand",
+  calculationSchedule: "",
+  dimensions: [] as Dimension[],
+});
+
+const rules = {
+  code: [{ required: true, message: "请输入指标编码", trigger: "blur" }],
+  name: [{ required: true, message: "请输入指标名称", trigger: "blur" }],
+  type: [{ required: true, message: "请选择指标类型", trigger: "change" }],
+  unit: [{ required: true, message: "请输入单位", trigger: "blur" }],
+  calculationMode: [{ required: true, message: "请选择计算模式", trigger: "change" }],
+  calculationSchedule: [{ validator: (_: any, v: string, cb: any) => (form.calculationMode === "schedule" && !v ? cb(new Error("请输入计算周期")) : cb()), trigger: "blur" }],
+};
+
+const typeOptions = ref([]);
+
+apiSystem.getInfoByKey("sys.indicator.type.tagCode").then((res: any) => {
+  const tagCode = res?.data?.configValue || "HARDWARE";
+  api.tags.getTree({}).then((res: any) => {
+    typeOptions.value = (res.data || []).find((item: any) => item.code === tagCode)?.children || [];
+  });
+});
+
+const paramDialog = reactive({
+  visible: false,
+  index: -1,
+  form: { name: "", code: "", values: "", description: "" } as FormulaParam,
+});
+const paramFormRef = ref();
+const paramRules = {
+  name: [{ required: true, message: "请输入参数名称", trigger: "blur" }],
+  code: [{ required: true, message: "请输入参数编码", trigger: "blur" }],
+  values: [{ required: true, message: "请输入参数值", trigger: "blur" }],
+};
+
+const dimDialog = reactive({
+  visible: false,
+  index: -1,
+  form: { name: "", code: "", valueType: "string", values: "", description: "" } as Dimension,
+});
+const dimFormRef = ref();
+const dimRules = {
+  name: [{ required: true, message: "请输入维度名称", trigger: "blur" }],
+  code: [{ required: true, message: "请输入维度标识", trigger: "blur" }],
+  valueType: [{ required: true, message: "请选择值类型", trigger: "change" }],
+};
+
+function resetForm() {
+  Object.assign(form, {
+    code: "",
+    name: "",
+    description: "",
+    type: "",
+    unit: "",
+    dataTemplateKey: "",
+    formula: "",
+    formulaParams: [],
+    computeStrategy: "",
+    calculationMode: "demand",
+    calculationSchedule: "",
+    dimensions: [],
+  });
+  activeTab.value = "base";
+}
+
+function openDialog(row?: any) {
+  resetForm();
+  isEdit.value = !!row;
+  if (row?.code) {
+    // 获取详情
+    api.indicator.detail(row.code).then((res: any) => {
+      const data = res?.data || res || row;
+      Object.assign(form, {
+        id: data.id || row.id,
+        code: data.code || row.code,
+        name: data.name,
+        description: data.description,
+        type: data.type,
+        unit: data.unit,
+        dataTemplateKey: data.dataTemplateKey,
+        formula: data.formula,
+        formulaParams: data.formulaParams || [],
+        computeStrategy: data.computeStrategy,
+        calculationMode: data.calculationMode || "demand",
+        calculationSchedule: data.calculationSchedule || "",
+        dimensions: data.dimensions || [],
+      });
+    });
+  }
+  visible.value = true;
+}
+
+function onCancel() {
+  visible.value = false;
+}
+
+function onSave() {
+  formRef.value.validate((valid: boolean) => {
+    if (!valid) {
+      activeTab.value = "base";
+      return;
+    }
+    const payload = JSON.parse(JSON.stringify(form));
+    const apiFn = isEdit.value ? api.indicator.edit : api.indicator.add;
+    apiFn(payload).then(() => {
+      ElMessage.success("保存成功");
+      visible.value = false;
+      emit("update");
+    });
+  });
+}
+
+function openParamDialog(row?: FormulaParam, index?: number) {
+  paramDialog.index = typeof index === "number" ? index : -1;
+  paramDialog.form = { name: row?.name || "", code: row?.code || "", values: row?.values || "", description: row?.description || "" };
+  paramDialog.visible = true;
+}
+
+function confirmParam() {
+  paramFormRef.value.validate((valid: boolean) => {
+    if (!valid) return;
+    if (paramDialog.index > -1) {
+      form.formulaParams.splice(paramDialog.index, 1, { ...paramDialog.form });
+    } else {
+      form.formulaParams.push({ ...paramDialog.form });
+    }
+    paramDialog.visible = false;
+  });
+}
+
+function removeParam(i: number) {
+  form.formulaParams.splice(i, 1);
+}
+
+function openDimDialog(row?: Dimension, index?: number) {
+  dimDialog.index = typeof index === "number" ? index : -1;
+  dimDialog.form = {
+    name: row?.name || "",
+    code: row?.code || "",
+    valueType: (row?.valueType as any) || "string",
+    values: row?.values || "",
+    description: row?.description || "",
+  };
+  dimDialog.visible = true;
+}
+
+function confirmDim() {
+  dimFormRef.value.validate((valid: boolean) => {
+    if (!valid) return;
+    if (dimDialog.index > -1) {
+      form.dimensions.splice(dimDialog.index, 1, { ...dimDialog.form });
+    } else {
+      form.dimensions.push({ ...dimDialog.form });
+    }
+    dimDialog.visible = false;
+  });
+}
+
+function removeDim(i: number) {
+  form.dimensions.splice(i, 1);
+}
+
+function formatValueType(v: string) {
+  const map: any = { string: "字符串", number: "数值", boolean: "布尔", datetime: "时间" };
+  return map[v] || v || "-";
+}
+
+defineExpose({ openDialog });
+</script>
+
+<style lang="scss" scoped>
+.row {
+  display: flex;
+  gap: 16px;
+  .flex1 {
+    flex: 1;
+  }
+}
+.section-title {
+  margin: 10px 0;
+  font-weight: 600;
+}
+.flex-between {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.mt10 {
+  margin-top: 10px;
+}
+.mt20 {
+  margin-top: 20px;
+}
+.pt10 {
+  padding-top: 10px;
+}
+.text-gray {
+  color: var(--el-text-color-secondary);
+}
+.empty-tip {
+  text-align: center;
+  color: var(--el-text-color-secondary);
+  padding: 16px 0;
+}
+</style>

+ 163 - 0
src/views/system/datahub/indicator/index.vue

@@ -0,0 +1,163 @@
+<template>
+  <div class="page">
+    <el-card shadow="never">
+      <el-form :model="params" inline ref="queryRef" @keyup.enter="getList">
+        <el-form-item>
+          <el-input v-model="params.keyword" placeholder="输入指标名称、编码或描述" style="width: 300px" clearable />
+        </el-form-item>
+        <el-form-item>
+          <el-cascader :options="typeOptions" :props="{ checkStrictly: true, emitPath: false, value: 'code', label: 'name' }" placeholder="请选择指标类型" clearable class="w100" v-model="params.type">
+            <template #default="{ node, data }">
+              <span>{{ data.name }}</span>
+              <span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
+            </template>
+          </el-cascader>
+        </el-form-item>
+        <el-form-item>
+          <el-select v-model="params.status" placeholder="全部状态" clearable style="width: 160px">
+            <el-option value="-1" label="全部状态" />
+            <el-option value="0" label="未发布" />
+            <el-option value="1" label="已发布" />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" class="ml10" @click="getList">
+            <el-icon><ele-Search /></el-icon>
+            查询
+          </el-button>
+          <el-button type="primary" class="ml10" @click="addOrEdit()">
+            <el-icon><ele-FolderAdd /></el-icon>
+            新增指标
+          </el-button>
+        </el-form-item>
+      </el-form>
+
+      <el-table :data="tableData" style="width: 100%" row-key="code" v-loading="loading">
+        <el-table-column label="指标编号" prop="code" align="left" min-width="200">
+          <template #default="scope">
+            <el-link type="primary" :underline="false">{{ scope.row.code }}</el-link>
+          </template>
+        </el-table-column>
+        <el-table-column label="指标名称" prop="name" min-width="200" show-overflow-tooltip />
+        <el-table-column label="指标类型" prop="type" width="120" align="center">
+          <template #default="scope">
+            <el-tag size="small">{{ scope.row.tagInfo?.[0]?.name || "-" }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="单位" prop="unit" width="100" align="center" />
+        <el-table-column label="维度数" align="center" width="100">
+          <template #default="scope">
+            <el-tag size="small" type="info">{{ (scope.row.dimensions?.length) || 0 }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="状态" prop="status" width="100" align="center">
+          <template #default="scope">
+            <el-tag size="small" :type="scope.row.status == '1' ? 'success' : 'danger'">{{ scope.row.status == "1" ? "已发布" : "未发布" }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="createdAt" label="创建时间" align="center" width="180" />
+        <el-table-column label="操作" align="center" width="220" fixed="right">
+          <template #default="scope">
+            <el-button size="small" text type="primary" v-if="scope.row.status == '0'" @click="publish(scope.row)">发布</el-button>
+            <el-button size="small" text type="warning" v-else @click="unpublish(scope.row)">取消发布</el-button>
+            <el-button size="small" text type="primary" @click="openDetail(scope.row)">详情</el-button>
+            <el-button size="small" text type="success" @click="openData(scope.row)">数据</el-button>
+            <el-button size="small" text type="primary" @click="addOrEdit(scope.row)">编辑</el-button>
+            <el-button size="small" text type="danger" @click="onRowDel(scope.row)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination v-if="params.total" :total="params.total" v-model:page="params.pageNum" v-model:limit="params.pageSize" @pagination="getList()" />
+    </el-card>
+
+    <EditIndicator ref="editFormRef" :typeOptions="typeOptions" @update="getList" />
+    <DetailDialog ref="detailRef" />
+    <DataDialog ref="dataRef" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref } from "vue";
+import { ElMessageBox, ElMessage } from "element-plus";
+import api from "/@/api/datahub";
+import { useSearch } from "/@/hooks/useCommon";
+import EditIndicator from "./component/edit.vue";
+import DetailDialog from "./component/detail.vue";
+import DataDialog from "./component/data.vue";
+import apiSystem from "/@/api/system";
+
+const editFormRef = ref();
+const detailRef = ref();
+const dataRef = ref();
+const queryRef = ref();
+
+const typeOptions = ref([]);
+
+apiSystem.getInfoByKey("sys.indicator.type.tagCode").then((res: any) => {
+  const tagCode = res?.data?.configValue || "HARDWARE";
+  api.tags.getTree({}).then((res: any) => {
+    typeOptions.value = (res.data || []).find((item: any) => item.code === tagCode)?.children || [];
+  });
+});
+
+const { params, tableData, getList, loading } = useSearch(api.indicator.getList, "data", {
+  keyword: "",
+  type: "",
+  status: "-1",
+});
+
+getList();
+
+const publish = (row?: any) => {
+  api.indicator.publish(row.code).then(() => {
+    ElMessage.success("发布成功");
+    getList();
+  });
+};
+
+const unpublish = (row?: any) => {
+  api.indicator.unpublish(row.code).then(() => {
+    ElMessage.success("取消发布成功");
+    getList();
+  });
+};
+
+const addOrEdit = (row?: any) => {
+  editFormRef.value.openDialog(row);
+};
+const openDetail = (row: any) => {
+  detailRef.value.open(row);
+};
+const openData = (row: any) => {
+  dataRef.value.open(row);
+};
+
+const onRowDel = (row: any) => {
+  ElMessageBox.confirm(`此操作将永久删除指标:${row.name}(${row.code}),是否继续?`, "提示", {
+    confirmButtonText: "删除",
+    cancelButtonText: "取消",
+    type: "warning",
+  }).then(() => {
+    api.indicator.del(row.code).then(() => {
+      ElMessage.success("删除成功");
+      getList();
+    });
+  });
+};
+
+const formatStatus = (s: string) => {
+  if (s === "enabled") return "启用";
+  if (s === "draft") return "草稿";
+  if (s === "disabled") return "停用";
+  return s || "-";
+};
+const statusTagType = (s: string) => {
+  if (s === "enabled") return "success";
+  if (s === "draft") return "info";
+  if (s === "disabled") return "warning";
+  return "info";
+};
+</script>
+
+<style lang="scss" scoped></style>

+ 106 - 101
src/views/system/datahub/source/component/edit.vue

@@ -2,46 +2,46 @@
 	<el-dialog
 		class="api-edit"
 		v-model="showDialog"
-		:title="`${formData.sourceId ? '编辑数据源' : '新增数据源'}`"
+		:title="`${formData.sourceId ? t('message.dateCenter.actions.edit') : t('message.dateCenter.actions.createSource')}`"
 		width="800px"
 		:close-on-click-modal="false"
 		:close-on-press-escape="false"
 	>
-		<el-form class="inline-form" ref="formRef" :model="formData" :rules="ruleForm" label-width="120px">
-			<el-form-item label="数据源标识" prop="key">
-				<el-input v-model="formData.key" placeholder="请输入数据源名称" :disabled="formData.sourceId" />
+		<el-form class="inline-form" ref="formRef" :model="formData" :rules="ruleForm" label-width="140px">
+			<el-form-item :label="t('message.dateCenter.labels.sourceKey')" prop="key">
+				<el-input v-model="formData.key" :placeholder="`${t('message.dateCenter.placeholders.input')} ${t('message.dateCenter.labels.sourceKey')}`" :disabled="formData.sourceId" />
 			</el-form-item>
-			<el-form-item label="数据源名称" prop="name">
-				<el-input v-model="formData.name" placeholder="请输入数据源名称" />
+			<el-form-item :label="t('message.dateCenter.labels.sourceName')" prop="name">
+				<el-input v-model="formData.name" :placeholder="`${t('message.dateCenter.placeholders.input')} ${t('message.dateCenter.labels.sourceName')}`" />
 			</el-form-item>
-			<el-form-item label="描述" prop="desc">
-				<el-input v-model="formData.desc" type="textarea" placeholder="请输入内容"></el-input>
+			<el-form-item :label="t('message.dateCenter.labels.sourceDesc')" prop="desc">
+				<el-input v-model="formData.desc" type="textarea" :placeholder="t('message.dateCenter.placeholders.input')"></el-input>
 			</el-form-item>
 
-			<el-form-item label="数据来源" prop="from" v-if="!formData.sourceId">
+			<el-form-item :label="t('message.dateCenter.labels.sourceFrom')" prop="from" v-if="!formData.sourceId">
 				<el-radio-group v-model="formData.from">
-					<el-radio :label="1">api导入</el-radio>
-					<el-radio :label="4">设备或产品</el-radio>
-					<el-radio :label="2">数据库</el-radio>
+					<el-radio :label="1">{{ t('message.dateCenter.options.api') }}</el-radio>
+					<el-radio :label="4">{{ t('message.dateCenter.options.device') }}</el-radio>
+					<el-radio :label="2">{{ t('message.dateCenter.options.db') }}</el-radio>
 				</el-radio-group>
 			</el-form-item>
-			<el-divider content-position="left">数据源配置</el-divider>
+			<el-divider content-position="left">{{ t('message.dateCenter.tabs.sourceConfig') }}</el-divider>
 
 			<div v-if="formData.from == 1">
-				<el-form-item label="请求方法" prop="config.method">
-					<el-select v-model="formData.config.method" :rules="ruleForm['config.method']" placeholder="请选择请求方法">
+				<el-form-item :label="t('message.dateCenter.labels.method')" prop="config.method">
+					<el-select v-model="formData.config.method" :rules="ruleForm['config.method']" :placeholder="`${t('message.dateCenter.placeholders.input')} ${t('message.dateCenter.labels.method')}`">
 						<el-option v-for="item in methodData" :key="item.value" :label="item.label" :value="item.value" />
 					</el-select>
 				</el-form-item>
 
-				<el-form-item label="请求地址" prop="config.url">
-					<el-input v-model="formData.config.url" placeholder="请输入请求地址" :rules="ruleForm['config.url']" />
+				<el-form-item :label="t('message.dateCenter.labels.url')" prop="config.url">
+					<el-input v-model="formData.config.url" :placeholder="`${t('message.dateCenter.placeholders.input')} ${t('message.dateCenter.labels.url')}`" :rules="ruleForm['config.url']" />
 				</el-form-item>
 
-				<el-form-item label="定时请求" prop="config.cronExpression">
+				<el-form-item :label="t('message.dateCenter.labels.interval')" prop="config.cronExpression">
 					<div style="display: flex">
-						<el-input v-model="formData.config.cronExpression" placeholder="请输入cron表达式" :rules="ruleForm['config.cronExpression']" />
-						<el-button type="success" @click="showCron('config')" style="margin-left: 5px">设置</el-button>
+						<el-input v-model="formData.config.cronExpression" :placeholder="`${t('message.dateCenter.placeholders.input')} cron`" :rules="ruleForm['config.cronExpression']" />
+						<el-button type="success" @click="showCron('config')" style="margin-left: 5px">{{ t('message.dateCenter.actions.edit') }}</el-button>
 					</div>
 				</el-form-item>
 
@@ -59,42 +59,43 @@
 							</div>
 
 							<div style="display: flex">
-								<el-divider content-position="left">请求参数</el-divider>
+								<el-divider content-position="left">{{ t('message.dateCenter.tabs.reqParams') }}</el-divider>
 							</div>
 							<div class="content-f" v-for="(aaa, bbb) in item" :key="bbb">
-								<el-select v-model="aaa.type" placeholder="参数类型" style="width: 320px">
+								<el-select v-model="aaa.type" :placeholder="t('message.dateCenter.labels.paramType')" style="width: 320px">
 									<el-option v-for="item in paramData" :key="item.value" :label="item.label" :value="item.value" />
 								</el-select>
-								<el-input v-model="aaa.name" placeholder="请输入参数标题" style="width: 320px" />
-								<el-input v-model="aaa.key" placeholder="请输入参标识" style="width: 320px" />
-								<el-input v-model="aaa.value" placeholder="请输入参数值" style="width: 320px" />
+								<el-input v-model="aaa.name" :placeholder="`${t('message.dateCenter.placeholders.input')} ${t('message.dateCenter.labels.paramTitle')}`" style="width: 320px" />
+								<el-input v-model="aaa.key" :placeholder="`${t('message.dateCenter.placeholders.input')} ${t('message.dateCenter.labels.paramKey')}`" style="width: 320px" />
+								<el-input v-model="aaa.value" :placeholder="`${t('message.dateCenter.placeholders.input')} ${t('message.dateCenter.labels.paramValue')}`" style="width: 320px" />
 								<div class="conicon">
 									<el-icon @click="delParamss(index, bbb)">
 										<Delete />
 									</el-icon>
 								</div>
 							</div>
-							<el-button type="primary" class="addbutton" @click="addParams(index)">增加</el-button>
+							<el-button type="primary" class="addbutton" @click="addParams(index)">{{ t('message.dateCenter.actions.add') }}</el-button>
 							<div style=""></div>
 						</div>
 					</div>
 				</div>
-				<el-button type="success" class="addbutton" @click="addParamss">增加分组</el-button>
+				<el-button type="success" class="addbutton" @click="addParamss">{{ t('message.dateCenter.actions.add') }}</el-button>
 			</div>
 			<div v-if="formData.from == 4">
-				<el-form-item label="设备或产品">
+				<el-form-item :label="t('message.dateCenter.labels.sourceFrom')">
 					<el-radio-group v-model="deviceOrProduct" @change=";(formData.devconfig.productKey = ''), (formData.devconfig.deviceKey = '')">
-						<el-radio label="device">设备</el-radio>
-						<el-radio label="product">产品</el-radio>
+						<el-radio label="device">{{ t('message.dateCenter.options.device') }}</el-radio>
+						<el-radio label="product">{{ t('message.dateCenter.labels.productKey') }}</el-radio>
 					</el-radio-group>
 				</el-form-item>
-				<el-form-item label="选择设备" v-if="deviceOrProduct == 'device'" prop="devconfig.deviceKey">
+				<el-form-item :label="t('message.dateCenter.labels.deviceKey')" v-if="deviceOrProduct == 'device'" prop="devconfig.deviceKey">
 					<el-select
 						v-model="formData.devconfig.deviceKey"
 						:rules="ruleForm['devconfig.deviceKey']"
 						filterable
-						placeholder="请选择设备"
+						:placeholder="`${t('message.dateCenter.placeholders.input')} ${t('message.dateCenter.labels.deviceKey')}`"
 						@change="formData.devconfig.productKey = ''"
+						style="width: 100%"
 					>
 						<el-option v-for="item in sourceData" :key="item.key" :label="item.name" :value="item.key">
 							<span style="float: left">{{ item.name }}</span>
@@ -102,13 +103,14 @@
 						</el-option>
 					</el-select>
 				</el-form-item>
-				<el-form-item label="选择产品" v-if="deviceOrProduct == 'product'" prop="devconfig.productKey">
+				<el-form-item :label="t('message.dateCenter.labels.productKey')" v-if="deviceOrProduct == 'product'" prop="devconfig.productKey">
 					<el-select
 						v-model="formData.devconfig.productKey"
 						:rules="ruleForm['devconfig.productKey']"
 						filterable
-						placeholder="请选择产品"
+						:placeholder="`${t('message.dateCenter.placeholders.input')} ${t('message.dateCenter.labels.productKey')}`"
 						@change="formData.devconfig.deviceKey = ''"
+						style="width: 100%"
 					>
 						<el-option v-for="item in producList" :key="item.key" :label="item.name" :value="item.key">
 							<span style="float: left">{{ item.name }}</span>
@@ -119,7 +121,7 @@
 			</div>
 
 			<div v-if="formData.from == 2">
-				<el-form-item label="数据来源" prop="tabconfig.type">
+				<el-form-item :label="t('message.dateCenter.labels.dbType')" prop="tabconfig.type">
 					<el-radio-group v-model="formData.tabconfig.type" :rules="ruleForm['tabconfig.type']">
 						<el-radio label="mysql">mysql</el-radio>
 						<el-radio label="mssql">mssql</el-radio>
@@ -127,31 +129,31 @@
 				</el-form-item>
 
 				<div class="inline">
-					<el-form-item label="主机地址" prop="tabconfig.host">
-						<el-input v-model="formData.tabconfig.host" placeholder="请输入主机地址" :rules="ruleForm['tabconfig.host']" />
+					<el-form-item :label="t('message.dateCenter.labels.host')" prop="tabconfig.host">
+						<el-input v-model="formData.tabconfig.host" :placeholder="`${t('message.dateCenter.placeholders.input')} ${t('message.dateCenter.labels.host')}`" :rules="ruleForm['tabconfig.host']" />
 					</el-form-item>
 
-					<el-form-item label="端口号" prop="tabconfig.port">
-						<el-input v-model="formData.tabconfig.port" placeholder="请输入端口号" :rules="ruleForm['tabconfig.port']" />
+					<el-form-item :label="t('message.dateCenter.labels.port')" prop="tabconfig.port">
+						<el-input v-model="formData.tabconfig.port" :placeholder="`${t('message.dateCenter.placeholders.input')} ${t('message.dateCenter.labels.port')}`" :rules="ruleForm['tabconfig.port']" />
 					</el-form-item>
 				</div>
 				<div class="inline">
-					<el-form-item label="用户名" prop="tabconfig.user">
-						<el-input v-model="formData.tabconfig.user" placeholder="请输入用户名" :rules="ruleForm['tabconfig.user']" />
+					<el-form-item :label="t('message.dateCenter.labels.user')" prop="tabconfig.user">
+						<el-input v-model="formData.tabconfig.user" :placeholder="`${t('message.dateCenter.placeholders.input')} ${t('message.dateCenter.labels.user')}`" :rules="ruleForm['tabconfig.user']" />
 					</el-form-item>
 
-					<el-form-item label="密码" prop="tabconfig.passwd">
-						<el-input v-model="formData.tabconfig.passwd" placeholder="请输入密码" :rules="ruleForm['tabconfig.passwd']" />
+					<el-form-item :label="t('message.dateCenter.labels.password')" prop="tabconfig.passwd">
+						<el-input v-model="formData.tabconfig.passwd" :placeholder="`${t('message.dateCenter.placeholders.input')} ${t('message.dateCenter.labels.password')}`" :rules="ruleForm['tabconfig.passwd']" />
 					</el-form-item>
 				</div>
 
-				<el-form-item label="数据库名称" prop="tabconfig.dbName">
-					<el-input v-model="formData.tabconfig.dbName" placeholder="请输入数据库名称" :rules="ruleForm['tabconfig.dbName']" />
+				<el-form-item :label="t('message.dateCenter.labels.dbName')" prop="tabconfig.dbName">
+					<el-input v-model="formData.tabconfig.dbName" :placeholder="`${t('message.dateCenter.placeholders.input')} ${t('message.dateCenter.labels.dbName')}`" :rules="ruleForm['tabconfig.dbName']" />
 				</el-form-item>
 
-				<el-form-item label="执行方式" prop="tabconfig.queryType">
+				<el-form-item :label="t('message.dateCenter.labels.expression')" prop="tabconfig.queryType">
 					<el-radio-group v-model="formData.tabconfig.queryType" :rules="ruleForm['tabconfig.queryType']">
-						<el-radio label="tableName">数据表</el-radio>
+						<el-radio label="tableName">{{ t('message.dateCenter.columns.value') }}</el-radio>
 						<el-radio label="sql">Sql</el-radio>
 					</el-radio-group>
 				</el-form-item>
@@ -160,22 +162,22 @@
 					<el-input
 						v-model="formData.tabconfig.tableName"
 						type="textarea"
-						:placeholder="formData.tabconfig.queryType == 'sql' ? '请输入sql语句' : '请输入表名称'"
+						:placeholder="formData.tabconfig.queryType == 'sql' ? `${t('message.dateCenter.placeholders.input')} sql` : `${t('message.dateCenter.placeholders.input')} ${t('message.dateCenter.labels.tableName')}`"
 					/>
 				</el-form-item>
 
-				<el-form-item label="主键字段" prop="tabconfig.pk">
-					<el-input v-model="formData.tabconfig.pk" placeholder="请输入主键字段" :rules="ruleForm['tabconfig.pk']" />
+				<el-form-item :label="t('message.dateCenter.labels.pk')" prop="tabconfig.pk">
+					<el-input v-model="formData.tabconfig.pk" :placeholder="`${t('message.dateCenter.placeholders.input')} ${t('message.dateCenter.labels.pk')}`" :rules="ruleForm['tabconfig.pk']" />
 				</el-form-item>
 
-				<el-form-item label="每次获取数量" prop="tabconfig.num">
-					<el-input v-model="formData.tabconfig.num" placeholder="请输入每次获取数量" :rules="ruleForm['tabconfig.num']" />
+				<el-form-item :label="t('message.dateCenter.labels.num')" prop="tabconfig.num">
+					<el-input v-model="formData.tabconfig.num" :placeholder="`${t('message.dateCenter.placeholders.input')} ${t('message.dateCenter.labels.num')}`" :rules="ruleForm['tabconfig.num']" />
 				</el-form-item>
 
-				<el-form-item label="任务表达式" prop="tabconfig.cronExpression">
+				<el-form-item :label="t('message.dateCenter.labels.cronExpression')" prop="tabconfig.cronExpression">
 					<div style="display: flex">
-						<el-input v-model="formData.tabconfig.cronExpression" placeholder="请输入cron任务表达式" :rules="ruleForm['tabconfig.cronExpression']" />
-						<el-button type="success" @click="showCron('tabconfig')" style="margin-left: 5px">设置</el-button>
+						<el-input v-model="formData.tabconfig.cronExpression" :placeholder="`${t('message.dateCenter.placeholders.input')} cron`" :rules="ruleForm['tabconfig.cronExpression']" />
+						<el-button type="success" @click="showCron('tabconfig')" style="margin-left: 5px">{{ t('message.dateCenter.actions.edit') }}</el-button>
 					</div>
 				</el-form-item>
 			</div>
@@ -183,30 +185,31 @@
 
 		<template #footer>
 			<div class="dialog-footer">
-				<el-button @click="onTest" type="warning" v-if="sourceId > 0">测试</el-button>
+				<el-button @click="onTest" type="warning" v-if="sourceId > 0">{{ t('message.dateCenter.actions.search') }}</el-button>
 
-				<el-button @click="showDialog = false">取消</el-button>
-				<el-button type="primary" @click="onSubmit">确定</el-button>
+				<el-button @click="showDialog = false">{{ t('message.dateCenter.messages.cancel') }}</el-button>
+				<el-button type="primary" @click="onSubmit">{{ t('message.dateCenter.messages.confirm') }}</el-button>
 			</div>
 		</template>
 	</el-dialog>
-	<el-dialog v-model="dialogVisible" title="返回Json数据" width="30%">
+	<el-dialog v-model="dialogVisible" :title="'Json'" width="30%">
 		<JsonViewer :value="jsonData" boxed sort theme="jv-dark" @click="onKeyclick" />
 
 		<template #footer>
 			<span class="dialog-footer">
-				<el-button @click="dialogVisible = false">关闭</el-button>
+				<el-button @click="dialogVisible = false">{{ t('message.dateCenter.messages.cancel') }}</el-button>
 			</span>
 		</template>
 	</el-dialog>
 
-	<el-dialog v-model="cronShow" title="选择Cron规则" width="60%">
+	<el-dialog v-model="cronShow" :title="'Cron'" width="60%">
 		<vue3cron @handlelisten="handlelisten" :type="crontype" @close="cronclose"></vue3cron>
 	</el-dialog>
 </template>
 
 <script lang="ts" setup>
-import { ref, reactive, nextTick } from 'vue'
+import { ref, reactive, nextTick, computed } from 'vue'
+import { useI18n } from 'vue-i18n'
 import api from '/@/api/datahub'
 import deviceApi from '/@/api/device'
 import { ruleRequired } from '/@/utils/validator'
@@ -219,14 +222,15 @@ import { Delete, CircleClose } from '@element-plus/icons-vue'
 const emit = defineEmits(['typeList'])
 
 const showDialog = ref(false)
+const { t } = useI18n()
 const deviceOrProduct = ref('device')
 const dialogVisible = ref(false)
 const cronShow = ref(false)
 const formRef = ref()
 const jsonData = ref()
 const crontype = ref()
-const sourceData = ref([])
-const producList = ref([])
+const sourceData = ref<Array<{ key: string; name: string }>>([])
+const producList = ref<Array<{ key: string; name: string }>>([])
 const sourceId = ref()
 const methodData = ref([
 	{
@@ -241,15 +245,15 @@ const methodData = ref([
 
 const paramData = ref([
 	{
-		lable: 'header',
+		label: 'header',
 		value: 'header',
 	},
 	{
-		lable: 'body',
+		label: 'body',
 		value: 'body',
 	},
 	{
-		lable: 'param',
+		label: 'param',
 		value: 'param',
 	},
 ])
@@ -354,40 +358,41 @@ const formData = reactive({
 	...baseForm,
 })
 
-const ruleForm = {
-	key: [ruleRequired('数据源标识不能为空')],
-	name: [ruleRequired('数据源名称不能为空')],
-	from: [ruleRequired('数据源类型不能为空')],
-
-	'config.method': [
-		{ required: true, message: '请求方法不能为空', trigger: 'change' },
-		{
-			validator: (rule: any, value: string, callback: any) => {
-				if (formData.from === 1 && !formData.config.method) {
-					callback(new Error('请求方法不能为空'))
-				} else {
-					callback()
-				}
+const ruleForm = computed(() => {
+	return {
+		key: [ruleRequired(`${t('message.dateCenter.labels.sourceKey')}${t('message.dateCenter.placeholders.input')}`)],
+		name: [ruleRequired(`${t('message.dateCenter.labels.sourceName')}${t('message.dateCenter.placeholders.input')}`)],
+		from: [ruleRequired(`${t('message.dateCenter.labels.sourceFrom')}${t('message.dateCenter.placeholders.input')}`)],
+		'config.method': [
+			{ required: true, message: `${t('message.dateCenter.labels.method')}${t('message.dateCenter.placeholders.input')}` , trigger: 'change' },
+			{
+				validator: (rule: any, value: string, callback: any) => {
+					if (formData.from === 1 && !formData.config.method) {
+						callback(new Error(`${t('message.dateCenter.labels.method')}${t('message.dateCenter.placeholders.input')}`))
+					} else {
+						callback()
+					}
+				},
+				trigger: 'change',
 			},
-			trigger: 'change',
-		},
-	],
-	'config.url': [ruleRequired('请求地址不能为空')],
-	'config.cronExpression': [ruleRequired('定时请求不能为空')],
-	'devconfig.deviceKey': [ruleRequired('请选择设备')],
-	'devconfig.productKey': [ruleRequired('请选择产品')],
-	'tabconfig.type': [ruleRequired('请选择数据来源')],
-	'tabconfig.host': [ruleRequired('请输入主机地址')],
-	'tabconfig.port': [ruleRequired('请输入端口号')],
-	'tabconfig.user': [ruleRequired('请输入用户名')],
-	'tabconfig.passwd': [ruleRequired('请输入密码')],
-	'tabconfig.dbName': [ruleRequired('请输入数据库名称')],
-	'tabconfig.queryType': [ruleRequired('请选择执行方式')],
-	'tabconfig.tableName': [ruleRequired('该项不能为空')],
-	'tabconfig.pk': [ruleRequired('请输入主键字段')],
-	'tabconfig.num': [ruleRequired('请输入每次获取数量')],
-	'tabconfig.cronExpression': [ruleRequired('请输入cron任务表达式')],
-}
+		],
+		'config.url': [ruleRequired(`${t('message.dateCenter.labels.url')}${t('message.dateCenter.placeholders.input')}`)],
+		'config.cronExpression': [ruleRequired(`${t('message.dateCenter.labels.interval')}${t('message.dateCenter.placeholders.input')}`)],
+		'devconfig.deviceKey': [ruleRequired(`${t('message.dateCenter.labels.deviceKey')}${t('message.dateCenter.placeholders.input')}`)],
+		'devconfig.productKey': [ruleRequired(`${t('message.dateCenter.labels.productKey')}${t('message.dateCenter.placeholders.input')}`)],
+		'tabconfig.type': [ruleRequired(`${t('message.dateCenter.labels.dbType')}${t('message.dateCenter.placeholders.input')}`)],
+		'tabconfig.host': [ruleRequired(`${t('message.dateCenter.labels.host')}${t('message.dateCenter.placeholders.input')}`)],
+		'tabconfig.port': [ruleRequired(`${t('message.dateCenter.labels.port')}${t('message.dateCenter.placeholders.input')}`)],
+		'tabconfig.user': [ruleRequired(`${t('message.dateCenter.labels.user')}${t('message.dateCenter.placeholders.input')}`)],
+		'tabconfig.passwd': [ruleRequired(`${t('message.dateCenter.labels.password')}${t('message.dateCenter.placeholders.input')}`)],
+		'tabconfig.dbName': [ruleRequired(`${t('message.dateCenter.labels.dbName')}${t('message.dateCenter.placeholders.input')}`)],
+		'tabconfig.queryType': [ruleRequired(`${t('message.dateCenter.labels.expression')}${t('message.dateCenter.placeholders.input')}`)],
+		'tabconfig.tableName': [ruleRequired(`${t('message.dateCenter.placeholders.input')}`)],
+		'tabconfig.pk': [ruleRequired(`${t('message.dateCenter.labels.pk')}${t('message.dateCenter.placeholders.input')}`)],
+		'tabconfig.num': [ruleRequired(`${t('message.dateCenter.labels.num')}${t('message.dateCenter.placeholders.input')}`)],
+		'tabconfig.cronExpression': [ruleRequired(`${t('message.dateCenter.labels.cronExpression')}${t('message.dateCenter.placeholders.input')}`)],
+	}
+})
 const getDevData = () => {
 	api.common.getdevList({}).then((res: any) => {
 		sourceData.value = res.device
@@ -414,7 +419,7 @@ const onSubmit = async () => {
 		}
 		const theApi = sourceId.value ? api.common.edit : api.common.add
 		await theApi(form)
-		ElMessage.success('操作成功')
+		ElMessage.success(t('message.dateCenter.messages.opSuccess'))
 		resetForm()
 		showDialog.value = false
 		emit('typeList')

+ 446 - 439
src/views/system/datahub/source/component/editNode.vue

@@ -1,96 +1,97 @@
 <template>
-	<div class="system-edit-dic-container">
-		<el-dialog :title="(ruleForm.nodeId !== 0 ? '修改' : '添加') + '数据节点'" v-model="isShowDialog" width="769px">
-			<el-form :model="ruleForm" ref="formRef" :rules="rules" label-width="110px">
-				<el-form-item label="数据节点标识" prop="key">
-					<el-input v-model="ruleForm.key" placeholder="请输入数据节点名称" :disabled="detail.lockKey == 1 && ruleForm.nodeId !== 0" />
-				</el-form-item>
-				<el-form-item label="数据节点名称" prop="name">
-					<el-input v-model="ruleForm.name" placeholder="请输入数据节点名称" />
-				</el-form-item>
-
-				<el-form-item label="是否主键" prop="isPk">
-					<el-radio-group v-model="ruleForm.isPk">
-						<el-radio :label="0">否</el-radio>
-						<el-radio :label="1">是</el-radio>
-					</el-radio-group>
-				</el-form-item>
-
-				<el-form-item label="数据类型" prop="dataType" v-if="detail.from == 1 || detail.from == 2">
-					<el-select v-model="ruleForm.dataType" filterable placeholder="请选择数据类型">
-						<el-option v-for="item in tabData" :key="item.value" :label="item.label" :value="item.value" />
-					</el-select>
-				</el-form-item>
-
-				<el-form-item label="取值项" prop="value" v-if="detail.from == 1">
-					<el-input v-model="ruleForm.value" placeholder="请输入取值项" class="w-35" /><el-button type="success" @click="onTest">选择值</el-button>
-				</el-form-item>
-
-
-				<el-form-item label="取值项" prop="value" v-if="detail.from == 4">
-					<el-select v-model="ruleForm.value" filterable placeholder="请选择数取值项" @change="getNodeList">
-						<el-option v-for="item in propertyData" :key="item.key" :label="item.name" :value="item.key">
-							<span style="float: left">{{ item.key }}</span>
-							<span style="float: right; font-size: 13px">{{ item.name }}</span>
-						</el-option>
-					</el-select>
-				</el-form-item>
-
-
-				<el-form-item label="取值项" prop="value" v-if="detail.from == 2">
-					<el-select v-model="ruleForm.value" filterable placeholder="请选择数取值项" @change="getDbList">
-						<el-option v-for="item in dbData" :key="item.Name" :label="item.Comment" :value="item.Name">
-							<span style="float: left">{{ item.Name }}</span>
-							<span style="float: right; font-size: 13px">{{ item.Comment }}</span>
-						</el-option>
-					</el-select>
-				</el-form-item>
-
-
-
-				<el-divider content-position="left">规则表达式</el-divider>
-
-				<div v-for="(item, index) in rule" :key="index">
-					<el-form-item label="正则表达式">
-						<el-input v-model="item.expression" placeholder="请输入规则表达式" />
-					</el-form-item>
-
-					<el-form-item label="替换内容">
-						<el-input v-model="item.replace" placeholder="请输入替换内容" class="w-35" />
-						<!-- <el-input v-model="rule[index].params.name" placeholder="请输入键值" class="w-35" />
-						<el-input v-model="rule[index].params.value" placeholder="请输入值" class="w-35" /> -->
-						<div class="conicon">
-							<el-icon @click="delRule(index)" v-if="index > 0">
-								<Delete />
-							</el-icon>
-						</div>
-					</el-form-item>
-				</div>
-				<div style="padding: 10px">
-					<el-button type="primary" class="addbutton" @click="addRule">增加</el-button>
-				</div>
-			</el-form>
-			<template #footer>
-				<span class="dialog-footer">
-					<el-button @click="onCancel">取 消</el-button>
-					<el-button type="primary" @click="onSubmit">{{ ruleForm.nodeId !== 0 ? '修 改' : '添 加' }}</el-button>
-				</span>
-			</template>
-		</el-dialog>
-
-		<el-dialog v-model="dialogVisible" title="点击蓝色key值进行选择" width="40%">
-			<jsontree :data="jsonPathData" @handlePath="handlePath"></jsontree>
-			<template #footer>
-				<span class="dialog-footer">
-					<el-button @click="dialogVisible = false">关闭</el-button>
-				</span>
-			</template>
-		</el-dialog>
-	</div>
+    <div class="system-edit-dic-container">
+        <el-dialog :title="(ruleForm.nodeId !== 0 ? t('message.dateCenter.actions.edit') : t('message.dateCenter.actions.add')) + ' ' + t('message.dateCenter.tabs.nodes')" v-model="isShowDialog" width="769px">
+            <el-form :model="ruleForm" ref="formRef" :rules="rules" label-width="110px">
+                <el-form-item :label="t('message.dateCenter.columns.key')" prop="key">
+                    <el-input v-model="ruleForm.key" :placeholder="`${t('message.dateCenter.placeholders.input')} ${t('message.dateCenter.columns.key')}`" :disabled="detail.lockKey == 1 && ruleForm.nodeId !== 0" />
+                </el-form-item>
+                <el-form-item :label="t('message.dateCenter.columns.name')" prop="name">
+                    <el-input v-model="ruleForm.name" :placeholder="`${t('message.dateCenter.placeholders.input')} ${t('message.dateCenter.columns.name')}`" />
+                </el-form-item>
+
+                <el-form-item :label="t('message.dateCenter.labels.pk')" prop="isPk">
+                    <el-radio-group v-model="ruleForm.isPk">
+                        <el-radio :label="0">{{ t('message.dateCenter.options.no') }}</el-radio>
+                        <el-radio :label="1">{{ t('message.dateCenter.options.yes') }}</el-radio>
+                    </el-radio-group>
+                </el-form-item>
+
+                <el-form-item :label="t('message.dateCenter.columns.dataType')" prop="dataType" v-if="detail.from == 1 || detail.from == 2">
+                    <el-select v-model="ruleForm.dataType" filterable :placeholder="`${t('message.dateCenter.placeholders.input')} ${t('message.dateCenter.columns.dataType')}`">
+                        <el-option v-for="item in tabData" :key="item.value" :label="item.label" :value="item.value" />
+                    </el-select>
+                </el-form-item>
+
+                <el-form-item :label="t('message.dateCenter.columns.value')" prop="value" v-if="detail.from == 1">
+                    <el-input v-model="ruleForm.value" :placeholder="`${t('message.dateCenter.placeholders.input')} ${t('message.dateCenter.columns.value')}`" class="w-35" /><el-button type="success" @click="onTest">{{ t('message.dateCenter.actions.search') }}</el-button>
+                </el-form-item>
+
+
+                <el-form-item :label="t('message.dateCenter.columns.value')" prop="value" v-if="detail.from == 4">
+                    <el-select v-model="ruleForm.value" filterable :placeholder="`${t('message.dateCenter.placeholders.input')} ${t('message.dateCenter.columns.value')}`" @change="getNodeList">
+                        <el-option v-for="item in propertyData" :key="item.key" :label="item.name" :value="item.key">
+                            <span style="float: left">{{ item.key }}</span>
+                            <span style="float: right; font-size: 13px">{{ item.name }}</span>
+                        </el-option>
+                    </el-select>
+                </el-form-item>
+
+
+                <el-form-item :label="t('message.dateCenter.columns.value')" prop="value" v-if="detail.from == 2">
+                    <el-select v-model="ruleForm.value" filterable :placeholder="`${t('message.dateCenter.placeholders.input')} ${t('message.dateCenter.columns.value')}`" @change="getDbList">
+                        <el-option v-for="item in dbData" :key="item.Name" :label="item.Comment" :value="item.Name">
+                            <span style="float: left">{{ item.Name }}</span>
+                            <span style="float: right; font-size: 13px">{{ item.Comment }}</span>
+                        </el-option>
+                    </el-select>
+                </el-form-item>
+
+
+
+                <el-divider content-position="left">{{ t('message.dateCenter.tabs.ruleExpr') }}</el-divider>
+
+                <div v-for="(item, index) in rule" :key="index">
+                    <el-form-item :label="t('message.dateCenter.labels.expression')">
+                        <el-input v-model="item.expression" :placeholder="`${t('message.dateCenter.placeholders.input')} ${t('message.dateCenter.labels.expression')}`" />
+                    </el-form-item>
+
+                    <el-form-item label="">
+                        <el-input v-model="item.replace" :placeholder="t('message.dateCenter.placeholders.input')" class="w-35" />
+                        <!-- <el-input v-model="rule[index].params.name" placeholder="请输入键值" class="w-35" />
+                        <el-input v-model="rule[index].params.value" placeholder="请输入值" class="w-35" /> -->
+                        <div class="conicon">
+                            <el-icon @click="delRule(index)" v-if="index > 0">
+                                <Delete />
+                            </el-icon>
+                        </div>
+                    </el-form-item>
+                </div>
+                <div style="padding: 10px">
+                    <el-button type="primary" class="addbutton" @click="addRule">{{ t('message.dateCenter.actions.add') }}</el-button>
+                </div>
+            </el-form>
+            <template #footer>
+                <span class="dialog-footer">
+                    <el-button @click="onCancel">{{ t('message.dateCenter.messages.cancel') }}</el-button>
+                    <el-button type="primary" @click="onSubmit">{{ t('message.dateCenter.messages.confirm') }}</el-button>
+                </span>
+            </template>
+        </el-dialog>
+
+        <el-dialog v-model="dialogVisible" :title="t('message.dateCenter.actions.search')" width="40%">
+            <jsontree :data="jsonPathData" @handlePath="handlePath"></jsontree>
+            <template #footer>
+                <span class="dialog-footer">
+                    <el-button @click="dialogVisible = false">{{ t('message.dateCenter.messages.cancel') }}</el-button>
+                </span>
+            </template>
+        </el-dialog>
+    </div>
 </template>
 
 <script lang="ts">
 import { reactive, toRefs, defineComponent, ref, unref } from 'vue';
+import { useI18n } from 'vue-i18n';
 import api from '/@/api/datahub';
 import 'vue3-json-viewer/dist/index.css';
 import jsontree from '/@/components/jsontree/index.vue';
@@ -98,363 +99,369 @@ import jsontree from '/@/components/jsontree/index.vue';
 import { ElMessage } from 'element-plus';
 import { Delete, } from '@element-plus/icons-vue';
 
+interface RuleItem {
+    expression: string;
+    replace: string;
+}
 interface RuleFormState {
-	nodeId: number;
-	name: string;
-	from: number;
-	isPk: number;
-	key: string;
-	dataType: string;
-	value: string;
-	description: string;
-	status: number;
+    nodeId: number;
+    name: string;
+    from: number;
+    isPk: number;
+    key: string;
+    dataType: string;
+    value: string;
+    description: string;
+    status?: number;
+    rule: RuleItem[] | string;
+    sourceId?: number;
 }
 interface DicState {
-	isShowDialog: boolean;
-	ruleForm: RuleFormState;
-	rules: {};
+    isShowDialog: boolean;
+    dialogVisible: boolean;
+    jsonPathData: any[];
+    jsonData: any;
+    propertyData: any[];
+    dbData: any[];
+    config: Record<string, any>;
+    detail: Record<string, any>;
+    tabData: Array<{ label: string; value: string }>;
+    ruledata: RuleItem[];
+    rule: RuleItem[];
+    ruleForm: RuleFormState;
+    rules: Record<string, any>;
 }
 
 export default defineComponent({
-	name: 'Edit',
-	components: { Delete, jsontree },
-
-	setup(prop, { emit }) {
-		const formRef = ref<HTMLElement | null>(null);
-		const state = reactive<DicState>({
-			isShowDialog: false,
-			dialogVisible: false,
-			jsonPathData: [],
-			jsonData: '',
-			propertyData: [],
-			dbData: [],
-			config: {},
-			detail: {},
-			tabData: [
-				{
-					label: 'int',
-					value: 'int',
-				},
-				{
-					label: 'long',
-					value: 'long',
-				},
-				{
-					label: 'float',
-					value: 'float',
-				},
-				{
-					label: 'double',
-					value: 'double',
-				},
-				{
-					label: 'string',
-					value: 'string',
-				},
-				{
-					label: 'boolean',
-					value: 'boolean',
-				},
-				{
-					label: 'date',
-					value: 'date',
-				},
-			],
-			ruledata: [
-				{
-					expression: '',
-					replace: '',
-					// params: {
-					// 	name: '',
-					// 	value: '',
-					// },
-				},
-			],
-			rule: [
-				{
-					expression: '',
-					replace: '',
-					// params: {
-					// 	name: '',
-					// 	value: '',
-					// },
-				},
-			],
-
-			ruleForm: {
-				nodeId: 0,
-				isPk: 0,
-				name: '',
-				key: '',
-				dataType: '',
-				value: '',
-				rule: [],
-
-				description: '',
-			},
-			rules: {
-				key: [{ required: true, message: '数据节点标识不能为空', trigger: 'blur' }],
-				name: [{ required: true, message: '数据节点名称不能为空', trigger: 'blur' }],
-				isPk: [{ required: true, message: '请选择是否主键', trigger: 'blur' }],
-				dataType: [{ required: true, message: '数据节点类型不能为空', trigger: 'blur' }],
-				value: [{ required: true, message: '数据节点取值项不能为空', trigger: 'blur' }],
-			},
-		});
-
-		const onTest = () => {
-			if (state.detail.from == 1) {
-				api.common.api(state.detail.sourceId).then((res: any) => {
-					state.jsonData = JSON.parse(res.data);
-
-					var jsonstr = jsonPath([], state.jsonData, '');
-					state.jsonPathData = jsonstr;
-					state.dialogVisible = true;
-				});
-			} else if (state.detail.from == 4) {
-				api.common.devapi(state.detail.sourceId).then((res: any) => {
-					state.jsonData = JSON.parse(res.data);
-
-					var jsonstr = jsonPath([], state.jsonData, '');
-					state.jsonPathData = jsonstr;
-					state.dialogVisible = true;
-				});
-			}
-		};
-
-		const handlePath = (path) => {
-			let data = path.slice(1);
-
-			state.ruleForm.value = data;
-			state.dialogVisible = false;
-		};
-
-		const delRule = (index) => {
-			state.rule.splice(index, 1);
-		};
-
-		const addRule = () => {
-			state.rule.push({
-				expression: '',
-				replace: '',
-				// params: {
-				// 	name: '',
-				// 	value: '',
-				// },
-			});
-		};
-		// 打开弹窗
-		const openDialog = (row: RuleFormState | null) => {
-			resetForm();
-
-			if (row?.nodeId) {
-				state.ruleForm = row;
-
-				var data = JSON.parse(row.rule);
-				data.forEach((item, index) => {
-					state.rule[index].expression = item.expression;
-					state.rule[index].replace = item.replace;
-					// state.rule[index].params.name = Object.keys(item.params);
-					// state.rule[index].params.value = item.params[Object.keys(item.params)];
-				});
-			}
-
-			api.common.detail(row.sourceId).then((res: any) => {
-				state.detail = res.data;
-				if (res.data.from == 4) {
-					//propertyData
-					api.node.getpropertyList({ productKey: res.data.deviceConfig.productKey }).then((re: any) => {
-						state.propertyData = re;
-					});
-				}
-				if (res.data.from == 2) {
-					//propertyData
-					api.common.getfields(row.sourceId).then((re: any) => {
-						state.dbData = re.data;
-					});
-				}
-			});
-
-			state.ruleForm = row;
-			state.isShowDialog = true;
-		};
-		const resetForm = () => {
-			state.ruleForm = {
-				nodeId: 0,
-				name: '',
-				isPk: 0,
-				from: 1,
-				key: '',
-				rule: [],
-
-				description: '',
-			};
-		};
-		// 关闭弹窗
-		const closeDialog = () => {
-			state.isShowDialog = false;
-		};
-		// 取消
-		const onCancel = () => {
-			closeDialog();
-		};
-		// 新增
-		const onSubmit = () => {
-			const formWrap = unref(formRef) as any;
-			if (!formWrap) return;
-			formWrap.validate((valid: boolean) => {
-				if (valid) {
-					//修改rule数据
-					// state.rule.forEach((item, index) => {
-					// 	item.params[item.params.name] = item.params.value;
-					// 	delete item.params.name;
-					// 	delete item.params.value;
-					// });
-
-					state.ruleForm.rule = state.rule;
-
-					if (state.ruleForm.nodeId !== 0) {
-						//修改
-						api.node.edit(state.ruleForm).then(() => {
-							ElMessage.success('数据节点类型修改成功');
-							closeDialog(); // 关闭弹窗
-							emit('typeList');
-						});
-					} else {
-						//添加
-
-						api.node.add(state.ruleForm).then(() => {
-							ElMessage.success('数据节点类型添加成功');
-							closeDialog(); // 关闭弹窗
-							emit('typeList');
-						});
-					}
-				}
-			});
-		};
-
-		const getNodeList = (e) => {
-			state.propertyData.forEach((item) => {
-				if (item.key === e) {
-					state.ruleForm.dataType = item.valueType.type;
-				}
-			});
-		};
-
-		const getDbList = (e) => {
-			state.ruleForm.dataType = state.dbData[e].Type;
-		}
-
-		const onKeyclick = () => {
-		};
-
-		const jsonPath = (arr, json, basePath) => {
-			// 生成jsonpath
-			const type = validateType(json);
-
-			if (type === 'object') {
-				for (let key in json) {
-					const item = {
-						key,
-						path: `${basePath}.${key}`,
-					};
-					const childType = validateType(json[key]);
-					item.type = childType;
-					if (childType === 'object' || childType === 'array') {
-						item.leaf = true;
-						item.children = [];
-						jsonPath(item.children, json[key], item.path);
-					} else {
-						item.leaf = false;
-						item.value = json[key];
-					}
-					arr.push(item);
-				}
-			} else if (type === 'array') {
-				json.forEach((item, index) => {
-					const childType = validateType(item);
-					const obj = {
-						key: index,
-					};
-					obj.type = childType;
-					obj.path = `${basePath}.${index}`;
-					if (childType === 'object' || childType === 'array') {
-						(obj.leaf = true), (obj.children = []);
-						jsonPath(obj.children, item, obj.path);
-					} else {
-						obj.value = item;
-						obj.leaf = false;
-					}
-					arr.push(obj);
-				});
-			}
-
-			return arr;
-		};
-
-		// 校验JSON数据类型
-		const validateType = (val) => {
-			let type = typeof val;
-			if (type === 'object') {
-				if (Array.isArray(val)) {
-					return 'array';
-				} else if (val === null) {
-					return 'null';
-				} else {
-					return 'object';
-				}
-			} else {
-				switch (type) {
-					case 'boolean':
-						return 'boolean';
-					case 'string':
-						return 'string';
-					case 'number':
-						return 'number';
-					default:
-						return 'error';
-				}
-			}
-		};
-
-		const getOrgIdArr = (parents, childNode, treeData) => {
-			for (var key in treeData) {
-				// 父节点查询条件
-				if (key === childNode) {
-					// 如果找到结果,保存当前节点
-					parents.push(key);
-					break;
-				} else {
-					if (treeData[key] instanceof Object) {
-						parents.push(key);
-
-						//没找到,遍历该节点的子节点
-						getOrgIdArr(parents, childNode, treeData[key]);
-					}
-				}
-			}
-
-			return parents;
-		};
-
-		return {
-			jsonPath,
-			handlePath,
-			validateType,
-			getNodeList,
-			getDbList,
-			onKeyclick,
-			getOrgIdArr,
-			addRule,
-			onTest,
-			delRule,
-			openDialog,
-			closeDialog,
-			onCancel,
-			onSubmit,
-			formRef,
-			...toRefs(state),
-		};
-	},
+    name: 'Edit',
+    components: { Delete, jsontree },
+
+    setup(prop, { emit }) {
+        const { t } = useI18n();
+        const formRef = ref<HTMLElement | null>(null);
+        const state = reactive<DicState>({
+            isShowDialog: false,
+            dialogVisible: false,
+            jsonPathData: [],
+            jsonData: '',
+            propertyData: [],
+            dbData: [],
+            config: {},
+            detail: {},
+            tabData: [
+                {
+                    label: 'int',
+                    value: 'int',
+                },
+                {
+                    label: 'long',
+                    value: 'long',
+                },
+                {
+                    label: 'float',
+                    value: 'float',
+                },
+                {
+                    label: 'double',
+                    value: 'double',
+                },
+                {
+                    label: 'string',
+                    value: 'string',
+                },
+                {
+                    label: 'boolean',
+                    value: 'boolean',
+                },
+                {
+                    label: 'date',
+                    value: 'date',
+                },
+            ],
+            ruledata: [
+                {
+                    expression: '',
+                    replace: '',
+                },
+            ],
+            rule: [
+                {
+                    expression: '',
+                    replace: '',
+                },
+            ],
+
+            ruleForm: {
+                nodeId: 0,
+                isPk: 0,
+                name: '',
+                from: 1,
+                key: '',
+                dataType: '',
+                value: '',
+                rule: [],
+
+                description: '',
+            },
+            rules: {
+                key: [{ required: true, message: `${t('message.dateCenter.columns.key')}${t('message.dateCenter.placeholders.input')}`, trigger: 'blur' }],
+                name: [{ required: true, message: `${t('message.dateCenter.columns.name')}${t('message.dateCenter.placeholders.input')}`, trigger: 'blur' }],
+                isPk: [{ required: true, message: `${t('message.dateCenter.labels.pk')}${t('message.dateCenter.placeholders.input')}`, trigger: 'blur' }],
+                dataType: [{ required: true, message: `${t('message.dateCenter.columns.dataType')}${t('message.dateCenter.placeholders.input')}`, trigger: 'blur' }],
+                value: [{ required: true, message: `${t('message.dateCenter.columns.value')}${t('message.dateCenter.placeholders.input')}`, trigger: 'blur' }],
+            },
+        });
+
+        const onTest = () => {
+            if (state.detail.from == 1) {
+                api.common.api(state.detail.sourceId).then((res: any) => {
+                    state.jsonData = JSON.parse(res.data);
+
+                    var jsonstr = jsonPath([], state.jsonData, '');
+                    state.jsonPathData = jsonstr;
+                    state.dialogVisible = true;
+                });
+            } else if (state.detail.from == 4) {
+                api.common.devapi(state.detail.sourceId).then((res: any) => {
+                    state.jsonData = JSON.parse(res.data);
+
+                    var jsonstr = jsonPath([], state.jsonData, '');
+                    state.jsonPathData = jsonstr;
+                    state.dialogVisible = true;
+                });
+            }
+        };
+
+        const handlePath = (path: string) => {
+            let data = path.slice(1);
+
+            state.ruleForm.value = data;
+            state.dialogVisible = false;
+        };
+
+        const delRule = (index: number) => {
+            state.rule.splice(index, 1);
+        };
+
+        const addRule = () => {
+            state.rule.push({
+                expression: '',
+                replace: '',
+            });
+        };
+        // 打开弹窗
+        const openDialog = (row: any | null) => {
+            resetForm();
+
+            if (row && row.nodeId) {
+                state.ruleForm = row as RuleFormState;
+
+                const data = JSON.parse(row.rule);
+                data.forEach((item: any, index: number) => {
+                    state.rule[index].expression = item.expression;
+                    state.rule[index].replace = item.replace;
+                });
+            }
+
+            api.common.detail(row?.sourceId).then((res: any) => {
+                state.detail = res.data;
+                if (res.data.from == 4) {
+                    //propertyData
+                    api.node.getpropertyList({ productKey: res.data.deviceConfig.productKey }).then((re: any) => {
+                        state.propertyData = re;
+                    });
+                }
+                if (res.data.from == 2) {
+                    //propertyData
+                    api.common.getfields(row?.sourceId).then((re: any) => {
+                        state.dbData = re.data;
+                    });
+                }
+            });
+
+            state.ruleForm = (row || state.ruleForm) as RuleFormState;
+            state.isShowDialog = true;
+        };
+        const resetForm = () => {
+            state.ruleForm = {
+                nodeId: 0,
+                name: '',
+                isPk: 0,
+                from: 1,
+                key: '',
+                dataType: '',
+                value: '',
+                rule: [],
+                description: '',
+            };
+        };
+        // 关闭弹窗
+        const closeDialog = () => {
+            state.isShowDialog = false;
+        };
+        // 取消
+        const onCancel = () => {
+            closeDialog();
+        };
+        // 新增
+        const onSubmit = () => {
+            const formWrap = unref(formRef) as any;
+            if (!formWrap) return;
+            formWrap.validate((valid: boolean) => {
+                if (valid) {
+                    //修改rule数据
+                    // state.rule.forEach((item, index) => {
+                    // 	item.params[item.params.name] = item.params.value;
+                    // 	delete item.params.name;
+                    // 	delete item.params.value;
+                    // });
+
+                    state.ruleForm.rule = state.rule;
+
+                    if (state.ruleForm.nodeId !== 0) {
+                        //修改
+                        api.node.edit(state.ruleForm).then(() => {
+                            ElMessage.success(t('message.dateCenter.messages.opSuccess'));
+                            closeDialog(); // 关闭弹窗
+                            emit('typeList');
+                        });
+                    } else {
+                        //添加
+
+                        api.node.add(state.ruleForm).then(() => {
+                            ElMessage.success(t('message.dateCenter.messages.opSuccess'));
+                            closeDialog(); // 关闭弹窗
+                            emit('typeList');
+                        });
+                    }
+                }
+            });
+        };
+
+        const getNodeList = (e: any) => {
+            state.propertyData.forEach((item) => {
+                if (item.key === e) {
+                    state.ruleForm.dataType = item.valueType.type;
+                }
+            });
+        };
+
+        const getDbList = (e: any) => {
+            state.ruleForm.dataType = state.dbData[e].Type;
+        }
+
+        const onKeyclick = () => {
+        };
+
+        const jsonPath = (arr: any[], json: any, basePath: string) => {
+            // 生成jsonpath
+            const type = validateType(json);
+
+            if (type === 'object') {
+                for (let key in json) {
+                    const item: any = {
+                        key,
+                        path: `${basePath}.${key}`,
+                    };
+                    const childType = validateType(json[key]);
+                    item.type = childType;
+                    if (childType === 'object' || childType === 'array') {
+                        item.leaf = true;
+                        item.children = [];
+                        jsonPath(item.children, json[key], item.path);
+                    } else {
+                        item.leaf = false;
+                        item.value = json[key];
+                    }
+                    arr.push(item);
+                }
+            } else if (type === 'array') {
+                json.forEach((item: any, index: number) => {
+                    const childType = validateType(item);
+                    const obj: any = {
+                        key: index,
+                    };
+                    obj.type = childType;
+                    obj.path = `${basePath}.${index}`;
+                    if (childType === 'object' || childType === 'array') {
+                        (obj.leaf = true), (obj.children = []);
+                        jsonPath(obj.children, item, obj.path);
+                    } else {
+                        obj.value = item;
+                        obj.leaf = false;
+                    }
+                    arr.push(obj);
+                });
+            }
+
+            return arr;
+        };
+
+        // 校验JSON数据类型
+        const validateType = (val: any) => {
+            let type = typeof val;
+            if (type === 'object') {
+                if (Array.isArray(val)) {
+                    return 'array';
+                } else if (val === null) {
+                    return 'null';
+                } else {
+                    return 'object';
+                }
+            } else {
+                switch (type) {
+                    case 'boolean':
+                        return 'boolean';
+                    case 'string':
+                        return 'string';
+                    case 'number':
+                        return 'number';
+                    default:
+                        return 'error';
+                }
+            }
+        };
+
+        const getOrgIdArr = (parents: any[], childNode: any, treeData: Record<string, any>) => {
+            for (var key in treeData) {
+                // 父节点查询条件
+                if (key === childNode) {
+                    // 如果找到结果,保存当前节点
+                    parents.push(key);
+                    break;
+                } else {
+                    if (treeData[key] instanceof Object) {
+                        parents.push(key);
+
+                        //没找到,遍历该节点的子节点
+                        getOrgIdArr(parents, childNode, treeData[key]);
+                    }
+                }
+            }
+
+            return parents;
+        };
+
+        return {
+            jsonPath,
+            handlePath,
+            validateType,
+            getNodeList,
+            getDbList,
+            onKeyclick,
+            getOrgIdArr,
+            addRule,
+            onTest,
+            delRule,
+            openDialog,
+            closeDialog,
+            onCancel,
+            onSubmit,
+            formRef,
+            ...toRefs(state),
+            t,
+        };
+    },
 });
 </script>
 <style scoped>

+ 24 - 5
src/views/system/datahub/source/component/list.vue

@@ -3,7 +3,7 @@
 		<el-dialog v-model="isShowDialog" :show-close="false" width="75%" :fullscreen="dialogFullScreen">
 			<template #header="{ close, titleId, titleClass }">
 				<div class="my-header">
-					<h4 :id="titleId" :class="titleClass">数据记录</h4>
+					<h4 :id="titleId" :class="titleClass">{{ t('message.dateCenter.actions.records') }}</h4>
 					<div>
 						<i class="iconfont " :class="!dialogFullScreen ? 'icon-fullscreen' : 'icon-tuichuquanping'" @click="quanping" style="font-size: 22px;cursor: pointer;"></i>
 						<i class="el-icon" @click="close" style="font-size: 22px;cursor: pointer;    margin-left: 10px; position: relative; top: 3px;"><svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" data-v-029747aa="">
@@ -22,7 +22,7 @@
 							{{ item }}
 						</div>
 						<div>
-							<span v-if="item == 'created_at'">时间</span>
+							<span v-if="item == 'created_at'">{{ t('message.dateCenter.columns.createdAt') }}</span>
 							{{ titleData[item] }}
 						</div>
 					</template>
@@ -35,17 +35,35 @@
 
 <script lang="ts">
 import { reactive, toRefs, defineComponent, ref } from 'vue';
+import { useI18n } from 'vue-i18n';
 import { Close } from '@element-plus/icons-vue';
 
 import api from '/@/api/datahub';
 
+interface TableParam {
+    pageNum: number;
+    pageSize: number;
+    sourceId?: number;
+}
+interface TableData<T> {
+    data: T[];
+    total: number;
+    loading: boolean;
+    param: TableParam;
+}
 interface DicState {
-	isShowDialog: boolean;
+    isShowDialog: boolean;
+    dialogFullScreen: boolean;
+    titleData: Record<string, string>;
+    jsonsData: any[];
+    jData: string[];
+    tableData: TableData<Record<string, any>>;
 }
 
 export default defineComponent({
 	name: 'deviceEditPro',
 	setup() {
+		const { t } = useI18n();
 		const formRef = ref<HTMLElement | null>(null);
 		const state = reactive<DicState>({
 			isShowDialog: false,
@@ -81,7 +99,7 @@ export default defineComponent({
 				const jsonData = JSON.parse(res.data);
 				state.tableData.data = jsonData;
 				state.jData = Object.keys(jsonData[0]);
-				state.jData.forEach((item, index) => {
+				state.jData.forEach((item: string, index: number) => {
 					state.jsonsData[index] = jsonData[item];
 				});
 
@@ -89,7 +107,7 @@ export default defineComponent({
 			}).finally(() => (state.tableData.loading = false));
 
 			api.node.getList(state.tableData.param).then((res: any) => {
-				res.list.forEach((item) => {
+				res.list.forEach((item: { key: string; name: string }) => {
 					state.titleData[item.key] = item.name;
 				});
 			});
@@ -128,6 +146,7 @@ export default defineComponent({
 			onCancel,
 			formRef,
 			...toRefs(state),
+			t,
 		};
 	},
 });

+ 400 - 400
src/views/system/datahub/source/detail.vue

@@ -1,492 +1,492 @@
 <template>
-	<div class="page page-full">
-		<el-card shadow="never">
-			<div class="content">
-				<div class="cont_box">
-					<div class="title">数据源名称:{{ detail.name }}</div>
-					<div class="pro-status"><span :class="developer_status == 1 ? 'on' : 'off'"></span>{{ developer_status == 1 ? '已发布' : '未发布' }}</div>
-					<div class="pro-option" v-auth="'startOrStop'" @click="CkOption">{{ developer_status == 1 ? '停用' : '发布' }}</div>
-				</div>
-			</div>
-			<!--  page-full page-full-part -->
-			<div class="content-box">
-				<el-tabs v-model="activeName" @tab-click="handleClick">
-					<el-tab-pane label="数据源信息" name="1">
-						<el-form label-width="110px" inline>
-							<el-divider content-position="left">基本信息</el-divider>
-
-							<el-form-item label="数据源标识:">
-								{{ detail.key }}
-							</el-form-item>
-
-							<el-form-item label="数据源名称:">
-								{{ detail.name }}
-							</el-form-item>
-							<el-form-item label="数据源描述:">
-								{{ detail.desc }}
-							</el-form-item>
-							<el-form-item label="数据来源:">
-								<span v-if="detail.from == 1">api导入</span>
-								<span v-if="detail.from == 2">数据库</span>
-								<span v-if="detail.from == 3">文件</span>
-								<span v-if="detail.from == 4">设备</span>
-							</el-form-item>
-
-
-							<div v-if="detail.from == 4">
-								<el-divider content-position="left">数据源配置</el-divider>
-								<el-form-item label="设备key:" prop="deviceKey">
-									{{ detail.deviceConfig.deviceKey }}
-								</el-form-item>
-								<el-form-item label="产品key:" prop="productKey">
-									{{ detail.deviceConfig.productKey }}
-								</el-form-item>
-							</div>
-
-							<div v-if="detail.from == 2">
-								<el-divider content-position="left">数据源配置</el-divider>
-								<el-form-item label="数据来源:" prop="type">
-									{{ detail.dbConfig.type }}
-								</el-form-item>
-
-								<el-form-item label="主机地址:" prop="host">
-									{{ detail.dbConfig.host }}
-								</el-form-item>
-
-								<el-form-item label="端口号:" prop="port">
-									{{ detail.dbConfig.port }}
-								</el-form-item>
-
-								<el-form-item label="用户名:" prop="user">
-									{{ detail.dbConfig.user }}
-								</el-form-item>
-								<el-form-item label="密码:" prop="passwd">
-									{{ detail.dbConfig.passwd }}
-								</el-form-item>
-								<el-form-item label="数据库名称:" prop="dbName">
-									{{ detail.dbConfig.dbName }}
-								</el-form-item>
-
-								<el-form-item label="表名称:" prop="tableName">
-									{{ detail.dbConfig.tableName }}
-								</el-form-item>
-								<el-form-item label="主键字段:" prop="pk">
-									{{ detail.dbConfig.pk }}
-								</el-form-item>
-
-								<el-form-item label="每次获取数量:" prop="num">
-									{{ detail.dbConfig.num }}
-								</el-form-item>
-
-								<el-form-item label="任务表达式:" prop="cronExpression">
-									{{ detail.dbConfig.cronExpression }}
-								</el-form-item>
-
-							</div>
-							<div v-if="detail.from == 1">
-
-								<el-divider content-position="left">规则表达式</el-divider>
-
-								<div v-for="(item, index) in rule" :key="index">
-									<el-form-item label="表达式:">
-										{{ item.expression }}
-									</el-form-item>
-
-									<el-form-item label="参数:"> {{ item.params.name }}~ {{ item.params.value }} </el-form-item>
-
-									<el-divider content-position="left">数据源配置</el-divider>
-
-									<el-form-item label="请求方法:" prop="method">
-										{{ config.method }}
-									</el-form-item>
-									<el-form-item label="请求地址:" prop="method">
-										{{ config.url }}
-									</el-form-item>
-									<el-form-item label="更新时间:" prop="method">
-										{{ config.interval }}
-										{{ config.intervalUnit }}
-									</el-form-item>
-
-									<el-divider content-position="left">请求参数</el-divider>
-
-									<div class="content-f" v-for="(item, index) in requestParams" :key="index" style="    border: 1px solid #d9d9d9; padding: 10px;margin-bottom: 10px;">
-
-										<div v-for="(aa) in item" :key="aa.type">
-											<el-form-item label="参数类型:">
-												{{ aa.type }}
-											</el-form-item>
-											<el-form-item label="参数标题:">
-												{{ aa.name }}
-											</el-form-item>
-											<el-form-item label="参数名:">
-												{{ aa.key }}
-											</el-form-item>
-											<el-form-item label="参数值:">
-												{{ aa.value }}
-											</el-form-item>
-										</div>
-									</div>
-								</div>
-
-							</div>
-						</el-form>
-					</el-tab-pane>
-
-					<el-tab-pane label="数据节点" name="2">
-						<div class="flex-row flex-end mb-4">
-							<el-button type="primary" size="small" @click="onOpenEdit()" v-if="developer_status == 0" v-auth="'add'">添加</el-button>
-						</div>
-						<el-table :data="tableData.data" style="width: 100%" v-loading="tableData.loading">
-							<el-table-column label="ID" align="center" prop="nodeId" width="100" v-col="'id'" />
-							<el-table-column label="数据标识" prop="key" show-overflow-tooltip v-col="'key'" />
-							<el-table-column label="数据名称" prop="name" show-overflow-tooltip v-col="'name'" />
-							<el-table-column label="数据类型" prop="dataType" show-overflow-tooltip v-col="'dataType'" />
-							<el-table-column label="数据取值项" prop="value" min-width="120" show-overflow-tooltip v-col="'value'" />
-							<el-table-column prop="createdAt" label="创建时间" min-width="160" align="center" v-col="'createdAt'" width="180"></el-table-column>
-							<el-table-column label="操作" width="200" align="center" fixed="right">
-								<template #default="scope">
-									<el-button size="small" text type="warning" @click="onOpenEdit1(scope.row)" v-if="developer_status == 0" v-auth="'edit'">修改</el-button>
-									<el-button size="small" text type="danger" @click="onRowDel(scope.row)" v-if="developer_status == 0" v-auth="'del'">删除</el-button>
-								</template>
-							</el-table-column>
-						</el-table>
-						<pagination v-show="tableData.total > 0" :total="tableData.total" v-model:page="tableData.param.pageNum" @pagination="typeList" />
-					</el-tab-pane>
-					<el-tab-pane label="查看数据" name="3">
-						<JsonViewer :value="jsonData" boxed sort theme="jv-dark" @click="onKeyclick" />
-					</el-tab-pane>
-				</el-tabs>
-			</div>
-			<EditDic ref="editDicRef" @typeList="typeList" />
-		</el-card>
-	</div>
-</template>            
+  <div class="page page-full">
+    <el-card shadow="never">
+      <div class="content">
+        <div class="cont_box">
+          <div class="title">{{ t('message.dateCenter.labels.sourceNameTitle') }}{{ detail.name }}</div>
+          <div class="pro-status"><span :class="developer_status == 1 ? 'on' : 'off'"></span>{{ t(developer_status == 1 ? 'message.dateCenter.status.published' : 'message.dateCenter.status.unpublished') }}</div>
+          <div class="pro-option" v-auth="'startOrStop'" @click="CkOption">{{ t(developer_status == 1 ? 'message.dateCenter.actions.disable' : 'message.dateCenter.actions.publish') }}</div>
+        </div>
+      </div>
+      <!--  page-full page-full-part -->
+      <div class="content-box">
+        <el-tabs v-model="activeName" @tab-click="handleClick">
+          <el-tab-pane :label="t('message.dateCenter.tabs.info')" name="1">
+            <el-form label-width="110px" inline>
+              <el-divider content-position="left">{{ t('message.dateCenter.tabs.baseInfo') }}</el-divider>
+
+              <el-form-item :label="t('message.dateCenter.labels.sourceKey') + ':'">
+                {{ detail.key }}
+              </el-form-item>
+
+              <el-form-item :label="t('message.dateCenter.labels.sourceName') + ':'">
+                {{ detail.name }}
+              </el-form-item>
+              <el-form-item :label="t('message.dateCenter.labels.sourceDesc') + ':'">
+                {{ detail.desc }}
+              </el-form-item>
+              <el-form-item :label="t('message.dateCenter.labels.sourceFrom') + ':'">
+                <span v-if="detail.from == 1">{{ t('message.dateCenter.options.api') }}</span>
+                <span v-if="detail.from == 2">{{ t('message.dateCenter.options.db') }}</span>
+                <span v-if="detail.from == 3">{{ t('message.dateCenter.options.file') }}</span>
+                <span v-if="detail.from == 4">{{ t('message.dateCenter.options.device') }}</span>
+              </el-form-item>
+
+              <div v-if="detail.from == 4">
+                <el-divider content-position="left">{{ t('message.dateCenter.tabs.sourceConfig') }}</el-divider>
+                <el-form-item :label="t('message.dateCenter.labels.deviceKey') + ':'" prop="deviceKey">
+                  {{ detail.deviceConfig.deviceKey }}
+                </el-form-item>
+                <el-form-item :label="t('message.dateCenter.labels.productKey') + ':'" prop="productKey">
+                  {{ detail.deviceConfig.productKey }}
+                </el-form-item>
+              </div>
+
+              <div v-if="detail.from == 2">
+                <el-divider content-position="left">{{ t('message.dateCenter.tabs.sourceConfig') }}</el-divider>
+                <el-form-item :label="t('message.dateCenter.labels.dbType') + ':'" prop="type">
+                  {{ detail.dbConfig.type }}
+                </el-form-item>
+
+                <el-form-item :label="t('message.dateCenter.labels.host') + ':'" prop="host">
+                  {{ detail.dbConfig.host }}
+                </el-form-item>
+
+                <el-form-item :label="t('message.dateCenter.labels.port') + ':'" prop="port">
+                  {{ detail.dbConfig.port }}
+                </el-form-item>
+
+                <el-form-item :label="t('message.dateCenter.labels.user') + ':'" prop="user">
+                  {{ detail.dbConfig.user }}
+                </el-form-item>
+                <el-form-item :label="t('message.dateCenter.labels.password') + ':'" prop="passwd">
+                  {{ detail.dbConfig.passwd }}
+                </el-form-item>
+                <el-form-item :label="t('message.dateCenter.labels.dbName') + ':'" prop="dbName">
+                  {{ detail.dbConfig.dbName }}
+                </el-form-item>
+
+                <el-form-item :label="t('message.dateCenter.labels.tableName') + ':'" prop="tableName">
+                  {{ detail.dbConfig.tableName }}
+                </el-form-item>
+                <el-form-item :label="t('message.dateCenter.labels.pk') + ':'" prop="pk">
+                  {{ detail.dbConfig.pk }}
+                </el-form-item>
+
+                <el-form-item :label="t('message.dateCenter.labels.num') + ':'" prop="num">
+                  {{ detail.dbConfig.num }}
+                </el-form-item>
+
+                <el-form-item :label="t('message.dateCenter.labels.cronExpression') + ':'" prop="cronExpression">
+                  {{ detail.dbConfig.cronExpression }}
+                </el-form-item>
+              </div>
+              <div v-if="detail.from == 1">
+                <el-divider content-position="left">{{ t('message.dateCenter.tabs.ruleExpr') }}</el-divider>
+
+                <div v-for="(item, index) in rule" :key="index">
+                  <el-form-item :label="t('message.dateCenter.labels.expression') + ':'">
+                    {{ item.expression }}
+                  </el-form-item>
+
+                  <el-form-item :label="t('message.dateCenter.labels.param') + ':'"> {{ item.params.name }}~ {{ item.params.value }} </el-form-item>
+
+                  <el-divider content-position="left">{{ t('message.dateCenter.tabs.sourceConfig') }}</el-divider>
+
+                  <el-form-item :label="t('message.dateCenter.labels.method') + ':'" prop="method">
+                    {{ config.method }}
+                  </el-form-item>
+                  <el-form-item :label="t('message.dateCenter.labels.url') + ':'" prop="method">
+                    {{ config.url }}
+                  </el-form-item>
+                  <el-form-item :label="t('message.dateCenter.labels.interval') + ':'" prop="method">
+                    {{ config.interval }}
+                    {{ config.intervalUnit }}
+                  </el-form-item>
+
+                  <el-divider content-position="left">{{ t('message.dateCenter.tabs.reqParams') }}</el-divider>
+
+                  <div class="content-f" v-for="(item, index) in requestParams" :key="index" style="border: 1px solid #d9d9d9; padding: 10px; margin-bottom: 10px">
+                    <div v-for="aa in item" :key="aa.type">
+                      <el-form-item :label="t('message.dateCenter.labels.paramType') + ':'">
+                        {{ aa.type }}
+                      </el-form-item>
+                      <el-form-item :label="t('message.dateCenter.labels.paramTitle') + ':'">
+                        {{ aa.name }}
+                      </el-form-item>
+                      <el-form-item :label="t('message.dateCenter.labels.paramKey') + ':'">
+                        {{ aa.key }}
+                      </el-form-item>
+                      <el-form-item :label="t('message.dateCenter.labels.paramValue') + ':'">
+                        {{ aa.value }}
+                      </el-form-item>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </el-form>
+          </el-tab-pane>
+
+          <el-tab-pane :label="t('message.dateCenter.tabs.nodes')" name="2">
+            <div class="flex-row flex-end mb-4">
+              <el-button type="primary" size="small" @click="onOpenEdit()" v-if="developer_status == 0" v-auth="'add'">{{ t('message.dateCenter.actions.add') }}</el-button>
+            </div>
+            <el-table :data="tableData.data" style="width: 100%" v-loading="tableData.loading">
+              <el-table-column :label="t('message.dateCenter.columns.id')" align="center" prop="nodeId" width="100" v-col="'id'" />
+              <el-table-column :label="t('message.dateCenter.columns.key')" prop="key" show-overflow-tooltip v-col="'key'" />
+              <el-table-column :label="t('message.dateCenter.columns.name')" prop="name" show-overflow-tooltip v-col="'name'" />
+              <el-table-column :label="t('message.dateCenter.columns.dataType')" prop="dataType" show-overflow-tooltip v-col="'dataType'" />
+              <el-table-column :label="t('message.dateCenter.columns.value')" prop="value" min-width="120" show-overflow-tooltip v-col="'value'" />
+              <el-table-column prop="createdAt" :label="t('message.dateCenter.columns.createdAt')" min-width="160" align="center" v-col="'createdAt'" width="180"></el-table-column>
+              <el-table-column :label="t('message.dateCenter.columns.action')" width="200" align="center" fixed="right">
+                <template #default="scope">
+                  <el-button size="small" text type="warning" @click="onOpenEdit1(scope.row)" v-if="developer_status == 0" v-auth="'edit'">{{ t('message.dateCenter.actions.edit') }}</el-button>
+                  <el-button size="small" text type="danger" @click="onRowDel(scope.row)" v-if="developer_status == 0" v-auth="'del'">{{ t('message.dateCenter.actions.delete') }}</el-button>
+                </template>
+              </el-table-column>
+            </el-table>
+            <pagination v-show="tableData.total > 0" :total="tableData.total" v-model:page="tableData.param.pageNum" @pagination="typeList" />
+          </el-tab-pane>
+          <el-tab-pane :label="t('message.dateCenter.tabs.viewData')" name="3">
+            <JsonViewer :value="jsonData" boxed sort theme="jv-dark" @click="onKeyclick" />
+          </el-tab-pane>
+        </el-tabs>
+      </div>
+      <EditDic ref="editDicRef" @typeList="typeList" />
+    </el-card>
+  </div>
+</template>
 <script lang="ts">
-import { toRefs, reactive, onMounted, ref, defineComponent } from 'vue';
-import { Edit } from '@element-plus/icons-vue';
-import { ElMessageBox, ElMessage } from 'element-plus';
-import { useRoute } from 'vue-router';
+import { toRefs, reactive, onMounted, ref, defineComponent } from "vue";
+import { useI18n } from 'vue-i18n';
+import { Edit } from "@element-plus/icons-vue";
+import { ElMessageBox, ElMessage } from "element-plus";
+import { useRoute } from "vue-router";
 import "vue3-json-viewer/dist/index.css";
 
-import EditDic from './component/editNode.vue';
-import api from '/@/api/datahub';
+import EditDic from "./component/editNode.vue";
+import api from "/@/api/datahub";
 
 interface TableDataState {
-	ids: number[];
-	tableData: {
-		data: [];
-		total: number;
-		loading: boolean;
-		param: {
-			pageNum: number;
-			pageSize: number;
-			name: string;
-			deviceType: string;
-			status: string;
-			dateRange: string[];
-		};
-	};
+  ids: number[];
+  tableData: {
+    data: [];
+    total: number;
+    loading: boolean;
+    param: {
+      pageNum: number;
+      pageSize: number;
+      name: string;
+      deviceType: string;
+      status: string;
+      dateRange: string[];
+    };
+  };
 }
 export default defineComponent({
-	name: 'dataDetail',
-	components: { EditDic },
-	setup() {
-		const editDicRef = ref();
-
-		const route = useRoute();
-		const state = reactive<TableDataState>({
-			config: {},
-			jsonData: {},
-			ruledata: [
-				{
-					expression: '',
-					params: {},
-				},
-			],
-			rule: [
-				{
-					expression: '',
-					params: {
-						name: '',
-						value: '',
-					},
-				},
-			],
-			requestParams: [
-				{
-					type: '',
-					key: '',
-					name: '',
-					value: '',
-				},
-			],
-
-			isShowDialog: false,
-			detail: [],
-			activeName: '1',
-			developer_status: 0,
-			tableData: {
-				data: [],
-				total: 0,
-				loading: false,
-				param: {
-					pageNum: 1,
-					pageSize: 20,
-					sourceId: route.params && route.params.sourceId,
-					status: '',
-					dateRange: [],
-				},
-			},
-		});
-
-		onMounted(() => {
-			const ids = route.params && route.params.sourceId;
-			api.common.detail(ids).then((res: any) => {
-				state.detail = res.data;
-				state.developer_status = res.data.status;
-				state.config = res.data.apiConfig;
-				state.requestParams = res.data.apiConfig.requestParams;
-			});
-
-			typeList();
-		});
-
-		const typeList = () => {
-			state.tableData.loading = true;
-			api.node.getList(state.tableData.param).then((res: any) => {
-				state.tableData.data = res.list;
-				state.tableData.total = res.Total;
-			}).finally(() => (state.tableData.loading = false));
-		};
-
-		const CkOption = () => {
-			if (state.developer_status == 1) {
-				api.common.undeploy({ sourceId: route.params.sourceId }).then(() => {
-					ElMessage.success('操作成功');
-					state.developer_status = 0;
-				});
-			} else {
-				api.common.deploy({ sourceId: route.params.sourceId }).then(() => {
-					ElMessage.success('操作成功');
-					state.developer_status = 1;
-				});
-			}
-		};
-		const handleClick = (tab: any) => {
-			if (tab.props.name == 3) {
-				if (state.detail.from == 1) {
-					api.common.api(state.detail.sourceId).then((res: any) => {
-						state.jsonData = JSON.parse(res.data);
-					})
-				} else if (state.detail.from == 4) {
-					api.common.devapi(state.detail.sourceId).then((res: any) => {
-						state.jsonData = JSON.parse(res.data);
-					})
-				} else if (state.detail.from == 2) {
-					api.common.devdb(state.detail.sourceId).then((res: any) => {
-						state.jsonData = JSON.parse(res.data);
-					})
-				}
-			}
-		};
-
-		const onRowDel = (row: any) => {
-			let msg = '你确定要删除所选数据?';
-			let ids: number[] = [];
-			if (row) {
-				msg = `此操作将永久删除数据节点:“${row.name}”,是否继续?`;
-				ids = row.nodeId;
-			} else {
-				ids = state.ids;
-			}
-			if (ids.length === 0) {
-				ElMessage.error('请选择要删除的数据。');
-				return;
-			}
-			ElMessageBox.confirm(msg, '提示', {
-				confirmButtonText: '确认',
-				cancelButtonText: '取消',
-				type: 'warning',
-			})
-				.then(() => {
-					api.node.delete(ids).then(() => {
-						ElMessage.success('删除成功');
-						typeList();
-					});
-				})
-		};
-
-		// 打开修改数据源弹窗
-		const onOpenEdit = () => {
-			editDicRef.value.openDialog({ sourceId: route.params.sourceId, nodeId: 0, isPk: 0 });
-		};
-		const onOpenEdit1 = (row: any) => {
-			editDicRef.value.openDialog(row);
-		};
-
-		return {
-			Edit,
-			editDicRef,
-			typeList,
-			onRowDel,
-			onOpenEdit,
-			onOpenEdit1,
-			handleClick,
-			CkOption,
-			...toRefs(state),
-		};
-	},
+  name: "dataDetail",
+  components: { EditDic },
+  setup() {
+    const { t } = useI18n();
+    const editDicRef = ref();
+
+    const route = useRoute();
+    const state = reactive<TableDataState>({
+      config: {},
+      jsonData: {},
+      ruledata: [
+        {
+          expression: "",
+          params: {},
+        },
+      ],
+      rule: [
+        {
+          expression: "",
+          params: {
+            name: "",
+            value: "",
+          },
+        },
+      ],
+      requestParams: [
+        {
+          type: "",
+          key: "",
+          name: "",
+          value: "",
+        },
+      ],
+
+      isShowDialog: false,
+      detail: [],
+      activeName: "1",
+      developer_status: 0,
+      tableData: {
+        data: [],
+        total: 0,
+        loading: false,
+        param: {
+          pageNum: 1,
+          pageSize: 20,
+          sourceId: route.params && route.params.sourceId,
+          status: "",
+          dateRange: [],
+        },
+      },
+    });
+
+    onMounted(() => {
+      const ids = route.params && route.params.sourceId;
+      api.common.detail(ids).then((res: any) => {
+        state.detail = res.data;
+        state.developer_status = res.data.status;
+        state.config = res.data.apiConfig;
+        state.requestParams = res.data.apiConfig.requestParams;
+      });
+
+      typeList();
+    });
+
+    const typeList = () => {
+      state.tableData.loading = true;
+      api.node
+        .getList(state.tableData.param)
+        .then((res: any) => {
+          state.tableData.data = res.list;
+          state.tableData.total = res.Total;
+        })
+        .finally(() => (state.tableData.loading = false));
+    };
+
+    const CkOption = () => {
+      if (state.developer_status == 1) {
+        api.common.undeploy({ sourceId: route.params.sourceId }).then(() => {
+          ElMessage.success(t('message.dateCenter.messages.opSuccess'));
+          state.developer_status = 0;
+        });
+      } else {
+        api.common.deploy({ sourceId: route.params.sourceId }).then(() => {
+          ElMessage.success(t('message.dateCenter.messages.opSuccess'));
+          state.developer_status = 1;
+        });
+      }
+    };
+    const handleClick = (tab: any) => {
+      if (tab.props.name == 3) {
+        if (state.detail.from == 1) {
+          api.common.api(state.detail.sourceId).then((res: any) => {
+            state.jsonData = JSON.parse(res.data);
+          });
+        } else if (state.detail.from == 4) {
+          api.common.devapi(state.detail.sourceId).then((res: any) => {
+            state.jsonData = JSON.parse(res.data);
+          });
+        } else if (state.detail.from == 2) {
+          api.common.devdb(state.detail.sourceId).then((res: any) => {
+            state.jsonData = JSON.parse(res.data);
+          });
+        }
+      }
+    };
+
+    const onRowDel = (row: any) => {
+      let msg = t('message.dateCenter.messages.deleteConfirmSelected');
+      let ids: number[] = [];
+      if (row) {
+        msg = t('message.dateCenter.messages.deleteNodeConfirm', { name: row.name });
+        ids = row.nodeId;
+      } else {
+        ids = state.ids;
+      }
+      if (ids.length === 0) {
+        ElMessage.error(t('message.dateCenter.messages.pleaseSelectToDelete'));
+        return;
+      }
+      ElMessageBox.confirm(msg, t('message.dateCenter.messages.tip'), {
+        confirmButtonText: t('message.dateCenter.messages.confirm'),
+        cancelButtonText: t('message.dateCenter.messages.cancel'),
+        type: "warning",
+      }).then(() => {
+        api.node.delete(ids).then(() => {
+          ElMessage.success(t('message.dateCenter.messages.deleteSuccess'));
+          typeList();
+        });
+      });
+    };
+
+    // 打开修改数据源弹窗
+    const onOpenEdit = () => {
+      editDicRef.value.openDialog({ sourceId: route.params.sourceId, nodeId: 0, isPk: 0 });
+    };
+    const onOpenEdit1 = (row: any) => {
+      editDicRef.value.openDialog(row);
+    };
+
+    return {
+      Edit,
+      t,
+      editDicRef,
+      typeList,
+      onRowDel,
+      onOpenEdit,
+      onOpenEdit1,
+      handleClick,
+      CkOption,
+      ...toRefs(state),
+    };
+  },
 });
 </script>
 <style scoped>
 .jv-jv-dark.boxed {
-	border: 1px solid #eee;
+  border: 1px solid #eee;
 }
 .content {
-	width: 100%;
+  width: 100%;
 }
 
 .content-box {
-	width: 100%;
+  width: 100%;
 }
 
 .cont_box {
-	display: flex;
+  display: flex;
 }
 
 .cont_box .title {
-	font-size: 18px;
+  font-size: 18px;
 }
 
 .cont_box .pro-status {
-	line-height: 30px;
-	margin-left: 30px;
+  line-height: 30px;
+  margin-left: 30px;
 }
 
 .cont_box .pro-status .on {
-	background: #52c41a;
+  background: #52c41a;
 }
 
 .cont_box .pro-status .off {
-	background: #c41a1a;
+  background: #c41a1a;
 }
 
 .cont_box .pro-status span {
-	position: relative;
-	top: -1px;
-	display: inline-block;
-	width: 6px;
-	height: 6px;
-	vertical-align: middle;
-	border-radius: 50%;
-	margin-right: 5px;
+  position: relative;
+  top: -1px;
+  display: inline-block;
+  width: 6px;
+  height: 6px;
+  vertical-align: middle;
+  border-radius: 50%;
+  margin-right: 5px;
 }
 
 .cont_box .pro-option {
-	line-height: 30px;
-	margin-left: 10px;
-	color: #1890ff;
-	cursor: pointer;
+  line-height: 30px;
+  margin-left: 10px;
+  color: #1890ff;
+  cursor: pointer;
 }
 
 .content-box .pro-box {
-	display: flex;
-	padding: 10px;
+  display: flex;
+  padding: 10px;
 }
 
 .content-box .pro-box .protitle {
-	font-size: 18px;
-	font-weight: bold;
-	line-height: 35px;
+  font-size: 18px;
+  font-weight: bold;
+  line-height: 35px;
 }
 
 .content-box .pro-box .buttonedit {
-	border: 0px;
-	color: #1890ff;
+  border: 0px;
+  color: #1890ff;
 }
 
 table {
-	border-collapse: collapse;
-	text-indent: initial;
-	border-spacing: 2px;
+  border-collapse: collapse;
+  text-indent: initial;
+  border-spacing: 2px;
 }
 
 tbody {
-	box-sizing: border-box;
-	display: table-row-group;
-	vertical-align: middle;
-	border-color: inherit;
+  box-sizing: border-box;
+  display: table-row-group;
+  vertical-align: middle;
+  border-color: inherit;
 }
 
 tr {
-	display: table-row;
-	vertical-align: inherit;
-	border-color: inherit;
+  display: table-row;
+  vertical-align: inherit;
+  border-color: inherit;
 }
 
 .ant-descriptions-view {
-	width: 100%;
-	overflow: hidden;
-	border-radius: 4px;
+  width: 100%;
+  overflow: hidden;
+  border-radius: 4px;
 }
 
 .ant-descriptions-view {
-	border: 1px solid #e8e8e8;
+  border: 1px solid #e8e8e8;
 }
 
 .ant-descriptions-view table {
-	width: 100%;
-	table-layout: fixed;
+  width: 100%;
+  table-layout: fixed;
 }
 
-.ant-descriptions-view>table {
-	table-layout: auto;
+.ant-descriptions-view > table {
+  table-layout: auto;
 }
 
 .ant-descriptions-row {
-	border-bottom: 1px solid #e8e8e8;
+  border-bottom: 1px solid #e8e8e8;
 }
 
 .ant-descriptions-item-label {
-	color: rgba(0, 0, 0, 0.85);
-	font-weight: 400;
-	font-size: 14px;
-	line-height: 1.5;
+  color: rgba(0, 0, 0, 0.85);
+  font-weight: 400;
+  font-size: 14px;
+  line-height: 1.5;
 }
 
 .ant-descriptions-item-label {
-	padding: 16px 24px;
-	border-right: 1px solid #e8e8e8;
+  padding: 16px 24px;
+  border-right: 1px solid #e8e8e8;
 }
 
 .ant-descriptions-item-label {
-	background-color: #fafafa;
+  background-color: #fafafa;
 }
 
 .ant-descriptions-item-content {
-	padding: 16px 24px;
-	border-right: 1px solid #e8e8e8;
-	display: table-cell;
-	color: rgba(0, 0, 0, 0.65);
-	font-size: 14px;
-	line-height: 1.5;
+  padding: 16px 24px;
+  border-right: 1px solid #e8e8e8;
+  display: table-cell;
+  color: rgba(0, 0, 0, 0.65);
+  font-size: 14px;
+  line-height: 1.5;
 }
 
 .wu-box {
-	border: #e8e8e8 solid 1px;
-	padding: 20px;
-	width: 100%;
+  border: #e8e8e8 solid 1px;
+  padding: 20px;
+  width: 100%;
 }
 
 .wu-box .wu-title {
-	display: flex;
-	flex-direction: row;
-	justify-content: space-between;
-	padding: 20px;
-	border-bottom: #e8e8e8 1px solid;
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  padding: 20px;
+  border-bottom: #e8e8e8 1px solid;
 }
 
 .wu-box .wu-title .title {
-	font-size: 18px;
+  font-size: 18px;
 }
-</style>
+</style>

+ 46 - 59
src/views/system/datahub/source/index.vue

@@ -2,14 +2,14 @@
 	<div class="page">
 		<el-card shadow="never">
 			<el-form inline ref="queryRef">
-				<el-form-item label="数据源标识" prop="name">
-					<el-input v-model="params.key" placeholder="请输入" clearable style="width: 140px" @keyup.enter.native="getList" />
+				<el-form-item :label="$t('message.dateCenter.labels.sourceKey')" prop="name">
+					<el-input v-model="params.key" :placeholder="$t('message.dateCenter.placeholders.input')" clearable style="width: 140px" @keyup.enter.native="getList" />
 				</el-form-item>
-				<el-form-item label="数据源名称" prop="name">
-					<el-input v-model="params.name" placeholder="请输入" clearable style="width: 140px" @keyup.enter.native="getList" />
+				<el-form-item :label="$t('message.dateCenter.labels.sourceName')" prop="name">
+					<el-input v-model="params.name" :placeholder="$t('message.dateCenter.placeholders.input')" clearable style="width: 140px" @keyup.enter.native="getList" />
 				</el-form-item>
 				<el-form-item label="" prop="from">
-					<el-select v-model="params.from" placeholder="数据源类型" @keyup.enter.native="getList" style="width: 120px;">
+					<el-select v-model="params.from" :placeholder="$t('message.dateCenter.placeholders.sourceType')" @keyup.enter.native="getList" style="width: 120px;">
 						<el-option v-for="item in typeData" :key="item.value" :label="item.label" :value="item.value" />
 					</el-select>
 				</el-form-item>
@@ -19,51 +19,51 @@
 						<el-icon>
 							<ele-Search />
 						</el-icon>
-						查询
+						{{ $t('message.dateCenter.actions.search') }}
 					</el-button>
 					<el-button v-auth="'add'" type="primary" class="ml10" @click="onOpenAdd">
 						<el-icon>
 							<ele-FolderAdd />
 						</el-icon>
-						新增数据源
+						{{ $t('message.dateCenter.actions.createSource') }}
 					</el-button>
 					<el-button type="info" class="ml10" @click="batchdel()" v-auth="'del'">
 						<el-icon>
 							<ele-Delete />
 						</el-icon>
-						批量删除
+						{{ $t('message.dateCenter.actions.batchDelete') }}
 					</el-button>
 				</el-form-item>
 			</el-form>
 			<el-table :data="tableData" style="width: 100%" @selection-change="handleSelectionChange" row-key="id" v-loading="loading">
 				<el-table-column type="selection" width="55" align="center" />
-				<el-table-column label="ID" align="center" prop="sourceId" width="100" v-col="'sourceId'" />
-				<el-table-column label="数据源标识" prop="key" show-overflow-tooltip v-col="'key'" />
-				<el-table-column label="数据源名称" prop="name" show-overflow-tooltip v-col="'name'" />
-				<el-table-column prop="from" label="数据源类型" width="160" align="center" v-col="'from'">
+				<el-table-column :label="$t('message.dateCenter.columns.sourceId')" align="center" prop="sourceId" width="100" v-col="'sourceId'" />
+				<el-table-column :label="$t('message.dateCenter.labels.sourceKey')" prop="key" show-overflow-tooltip v-col="'key'" />
+				<el-table-column :label="$t('message.dateCenter.labels.sourceName')" prop="name" show-overflow-tooltip v-col="'name'" />
+				<el-table-column prop="from" :label="$t('message.dateCenter.columns.from')" width="160" align="center" v-col="'from'">
 					<template #default="scope">
-						<span v-if="scope.row.from == 1">api导入</span>
-						<span v-if="scope.row.from == 2">数据库</span>
-						<span v-if="scope.row.from == 3">文件</span>
-						<span v-if="scope.row.from == 4">设备</span>
+						<span v-if="scope.row.from == 1">{{ $t('message.dateCenter.options.api') }}</span>
+						<span v-if="scope.row.from == 2">{{ $t('message.dateCenter.options.db') }}</span>
+						<span v-if="scope.row.from == 3">{{ $t('message.dateCenter.options.file') }}</span>
+						<span v-if="scope.row.from == 4">{{ $t('message.dateCenter.options.device') }}</span>
 					</template>
 				</el-table-column>
-				<el-table-column prop="status" label="状态" width="100" align="center" v-col="'status'">
+				<el-table-column prop="status" :label="$t('message.dateCenter.labels.sourceStatus')" width="120" align="center" v-col="'status'">
 					<template #default="scope">
-						<el-tag type="success" size="small" v-if="scope.row.status == 1">已发布</el-tag>
-						<el-tag type="info" size="small" v-if="scope.row.status == 0">未发布</el-tag>
+						<el-tag type="success" size="small" v-if="scope.row.status == 1">{{ $t('message.dateCenter.status.published') }}</el-tag>
+						<el-tag type="info" size="small" v-if="scope.row.status == 0">{{ $t('message.dateCenter.status.unpublished') }}</el-tag>
 					</template>
 				</el-table-column>
 
-				<el-table-column label="操作" width="200" align="center" fixed="right">
+				<el-table-column :label="$t('message.dateCenter.columns.action')" width="200" align="center" fixed="right">
 					<template #default="scope">
 						<router-link :to="'/datahub/source/' + scope.row.sourceId" class="link-type" style="padding-right: 12px; font-size: 12px; color: #409eff" v-auth="'detail'">
-							<span>详情</span>
+							<span>{{ $t('message.dateCenter.actions.detail') }}</span>
 						</router-link>
-						<el-button size="small" text type="success" @click="onOpenList(scope.row)" v-if="scope.row.status == 1" v-auth="'detail'">数据记录</el-button>
-						<el-button size="small" text type="warning" @click="onOpenEdit(scope.row)" v-if="scope.row.status == 0" v-auth="'edit'">修改</el-button>
-						<el-button size="small" text type="info" @click="del(scope.row)" v-if="scope.row.status == 0" v-auth="'del'">删除</el-button>
-						<el-button size="small" text type="primary" @click="copy(scope.row)" v-auth="'copy'">复制</el-button>
+						<el-button size="small" text type="success" @click="onOpenList(scope.row)" v-if="scope.row.status == 1" v-auth="'detail'">{{ $t('message.dateCenter.actions.records') }}</el-button>
+						<el-button size="small" text type="warning" @click="onOpenEdit(scope.row)" v-if="scope.row.status == 0" v-auth="'edit'">{{ $t('message.dateCenter.actions.edit') }}</el-button>
+						<el-button size="small" text type="info" @click="del(scope.row)" v-if="scope.row.status == 0" v-auth="'del'">{{ $t('message.dateCenter.actions.delete') }}</el-button>
+						<el-button size="small" text type="primary" @click="copy(scope.row)" v-auth="'copy'">{{ $t('message.dateCenter.actions.copy') }}</el-button>
 					</template>
 				</el-table-column>
 			</el-table>
@@ -80,31 +80,18 @@ import { useSearch } from '/@/hooks/useCommon'
 import { ElMessageBox, ElMessage } from 'element-plus'
 import EditDic from './component/edit.vue'
 import ListDic from './component/list.vue'
-import { ref, onMounted } from 'vue'
+import { ref, onMounted, computed } from 'vue'
+import { useI18n } from 'vue-i18n'
+const { t } = useI18n()
 const queryRef = ref()
 const editDicRef = ref()
 const listDicRef = ref()
-const typeData = ref([
-	{
-		label: '全部',
-		value: '-1',
-	},
-	{
-		label: 'api导入',
-		value: '1',
-	},
-	{
-		label: '数据库',
-		value: '2',
-	},
-	{
-		label: '文件',
-		value: '3',
-	},
-	{
-		label: '设备',
-		value: '4',
-	},
+const typeData = computed(() => [
+	{ label: t('message.dateCenter.options.all'), value: '-1' },
+	{ label: t('message.dateCenter.options.api'), value: '1' },
+	{ label: t('message.dateCenter.options.db'), value: '2' },
+	{ label: t('message.dateCenter.options.file'), value: '3' },
+	{ label: t('message.dateCenter.options.device'), value: '4' },
 ])
 const ids = ref()
 const { params, tableData, getList, loading } = useSearch<any[]>(api.common.getList, 'list', { keyWord: '' })
@@ -125,14 +112,14 @@ const onOpenEdit = async (row?: any) => {
 }
 //复制数据
 const copy = (row: any) => {
-	ElMessageBox.confirm('确定要复制该数据吗?', '提示', {
-		confirmButtonText: '确认',
-		cancelButtonText: '取消',
+	ElMessageBox.confirm(t('message.dateCenter.messages.copyConfirm'), t('message.dateCenter.messages.tip'), {
+		confirmButtonText: t('message.dateCenter.messages.confirm'),
+		cancelButtonText: t('message.dateCenter.messages.cancel'),
 		type: 'warning',
 	})
 		.then(() => {
 			api.common.copy({ sourceId: row.sourceId }).then(() => {
-				ElMessage.success('复制成功')
+				ElMessage.success(t('message.dateCenter.messages.copySuccess'))
 				getList()
 			})
 		})
@@ -142,24 +129,24 @@ const handleSelectionChange = (selection: any[]) => {
 	ids.value = selection.map((item) => item.sourceId)
 }
 const batchdel = () => {
-	ElMessageBox.confirm('是否确认要批量删除这些数据吗?', '提示', {
-		confirmButtonText: '确认',
-		cancelButtonText: '取消',
+	ElMessageBox.confirm(t('message.dateCenter.messages.batchDeleteConfirm'), t('message.dateCenter.messages.tip'), {
+		confirmButtonText: t('message.dateCenter.messages.confirm'),
+		cancelButtonText: t('message.dateCenter.messages.cancel'),
 		type: 'warning',
 	}).then(async () => {
 		await api.common.delete(ids.value)
-		ElMessage.success('删除成功')
+		ElMessage.success(t('message.dateCenter.messages.deleteSuccess'))
 		getList()
 	})
 }
 const del = (row: any) => {
-	ElMessageBox.confirm(`此操作将删除:“${row.name}”,是否继续?`, '提示', {
-		confirmButtonText: '确认',
-		cancelButtonText: '取消',
+	ElMessageBox.confirm(t('message.dateCenter.messages.deleteRowConfirm', { name: row.name }), t('message.dateCenter.messages.tip'), {
+		confirmButtonText: t('message.dateCenter.messages.confirm'),
+		cancelButtonText: t('message.dateCenter.messages.cancel'),
 		type: 'warning',
 	}).then(async () => {
 		await api.common.delete(row.sourceId)
-		ElMessage.success('删除成功')
+		ElMessage.success(t('message.dateCenter.messages.deleteSuccess'))
 		getList(1)
 	})
 }

+ 134 - 0
src/views/system/datahub/tags/component/edit.vue

@@ -0,0 +1,134 @@
+<template>
+  <div class="system-edit-dept-container">
+    <el-dialog :title="(formData.id ? '修改' : '添加') + '标签'" v-model="isShowDialog" width="600px">
+      <el-form ref="formRef" :model="formData" :rules="rules" v-if="isShowDialog" label-width="90px">
+        <div class="flex-row">
+          <el-form-item label="标签名称" prop="name" class="flex1">
+            <el-input v-model.trim="formData.name" show-word-limit placeholder="请输入标签名称" clearable></el-input>
+          </el-form-item>
+          <el-form-item label="英文标识" prop="code" class="flex1">
+            <el-input v-model.trim="formData.code" show-word-limit placeholder="请输入英文标识" clearable></el-input>
+          </el-form-item>
+        </div>
+        <div class="flex-row">
+          <el-form-item label="父级标签" prop="parentId" class="flex1">
+            <el-cascader :options="treeData" :props="{ checkStrictly: true, emitPath: false, value: 'id', label: 'name' }" placeholder="请选择父级标签" clearable class="w100" v-model="formData.parentId">
+              <template #default="{ node, data }">
+                <span>{{ data.name }}</span>
+                <span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
+              </template>
+            </el-cascader>
+          </el-form-item>
+          <el-form-item label="标签颜色" prop="color" class="flex1">
+            <el-select v-model.trim="formData.color" placeholder="请选择标签颜色" clearable style="width: 100%">
+              <el-option v-for="item in colorList" :label="item.label" :key="item.value" :value="item.value">
+                <template #default>
+                  <div class="flex items-center">
+                    <div class="color" :style="{ backgroundColor: item.color }"></div>
+                    {{ item.label }}
+                  </div>
+                </template>
+              </el-option>
+            </el-select>
+          </el-form-item>
+        </div>
+        <el-form-item label="描述" prop="description">
+          <el-input v-model="formData.description" type="textarea" show-word-limit placeholder="请输入描述" clearable></el-input>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <span class="dialog-footer">
+          <el-button @click="closeDialog">取 消</el-button>
+          <el-button type="primary" @click="onSubmit">{{ formData.id ? "修 改" : "添 加" }}</el-button>
+        </span>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { reactive, ref } from "vue";
+import api from "/@/api/datahub";
+import { ElMessage } from "element-plus";
+
+const emit = defineEmits(["update"]);
+
+defineProps({
+  colorList: {
+    type: Array as () => { value: string; label: string; color: string }[],
+    default: () => [],
+  },
+});
+
+const baseForm = {
+  id: null,
+  parentId: -1, // 父级标签
+  name: "", // 标签名称
+  code: "", // 标签英文标识
+  color: "", // 标签颜色
+  description: "", // 标签描述
+};
+
+const isShowDialog = ref(false);
+const treeData = ref([]);
+const formRef = ref();
+const formData = reactive({ ...baseForm });
+const rules = {
+  name: [{ required: true, message: "标签名称不能为空", trigger: "blur" }],
+  code: [{ required: true, message: "标签英文标识不能为空", trigger: "blur" }],
+  color: [{ required: true, message: "标签颜色不能为空", trigger: "blur" }],
+  description: [{ required: true, message: "标签描述不能为空", trigger: "blur" }],
+};
+
+// 打开弹窗
+const openDialog = (row: any) => {
+  resetForm();
+
+  api.tags.getTree({}).then((res: any) => {
+    treeData.value = res?.data || [];
+  });
+
+  if (row) {
+    Object.assign(formData, { ...row });
+  } else {
+    Object.assign(formData, baseForm);
+  }
+  isShowDialog.value = true;
+};
+// 关闭弹窗
+const closeDialog = () => {
+  isShowDialog.value = false;
+};
+// 新增
+const onSubmit = () => {
+  formRef.value.validate((valid: boolean) => {
+    if (valid) {
+      const submitData = JSON.parse(JSON.stringify(formData));
+      if (!submitData.parentId) {
+        submitData.parentId = -1;
+      }
+
+      const theApi = formData.id ? api.tags.edit : api.tags.add;
+
+      theApi(submitData).then(() => {
+        ElMessage.success("添加成功");
+        closeDialog(); // 关闭弹窗
+        emit("update");
+      });
+    }
+  });
+};
+const resetForm = () => {
+  Object.assign(formData, baseForm);
+};
+
+defineExpose({ openDialog });
+</script>
+<style lang="scss" scoped>
+.color {
+  width: 12px;
+  height: 12px;
+  border-radius: 50%;
+  margin-right: 5px;
+}
+</style>

+ 122 - 0
src/views/system/datahub/tags/index.vue

@@ -0,0 +1,122 @@
+<template>
+  <div class="page">
+    <el-card shadow="never">
+      <el-form :model="param" inline ref="queryRef" @keyup.enter="getData">
+        <el-form-item label="" prop="name">
+          <el-input v-model="param.name" placeholder="搜索标签名称或英文标识" style="width: 250px" clearable />
+          <el-button type="primary" class="ml10" @click="getData">
+            <el-icon>
+              <ele-Search />
+            </el-icon>
+            查询
+          </el-button>
+          <el-button type="primary" class="ml10" @click="addOrEdit()" v-auth="'add'">
+            <el-icon>
+              <ele-FolderAdd />
+            </el-icon>
+            新增标签
+          </el-button>
+        </el-form-item>
+      </el-form>
+      <el-table :data="tableData" style="width: 100%" row-key="id" default-expand-all :tree-props="{ children: 'children', hasChildren: 'hasChildren' }" v-loading="loading">
+        <el-table-column prop="name" v-col="'name'" label="标签名称" min-width="180" show-overflow-tooltip> </el-table-column>
+        <el-table-column prop="code" v-col="'code'" label="英文标识" align="center" width="120">
+          <template #default="scope">
+            <el-tag type="primary" size="small">{{ scope.row.code }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="color" v-col="'color'" label="颜色" align="center" width="120">
+          <template #default="scope">
+            <div class="flex" style="justify-content: center">
+              <div class="color" :style="{ backgroundColor: colorMap.get(scope.row.color)?.color }"></div>
+              {{ colorMap.get(scope.row.color)?.label }}
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="description" v-col="'description'" label="描述" align="center" show-overflow-tooltip min-width="180"></el-table-column>
+        <el-table-column prop="createdAt" v-col="'createdAt'" label="创建时间" align="center" width="180"></el-table-column>
+        <el-table-column le-column label="操作" align="center" width="140" v-col="'handle'">
+          <template #default="scope">
+            <template v-if="scope.row.name !== '硬件设施'">
+              <el-button size="small" text type="warning" @click="addOrEdit(scope.row)" v-auth="'edit'">修改</el-button>
+              <el-button size="small" text type="info" @click="onTabelRowDel(scope.row)" v-auth="'del'">删除</el-button>
+            </template>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-card>
+    <EditDept ref="editDeptRef" :colorList="colorList" @update="getData" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive, onMounted } from "vue";
+import { ElMessageBox, ElMessage } from "element-plus";
+import EditDept from "./component/edit.vue";
+import api from "/@/api/datahub";
+
+const colorList = ref([
+  { value: "red", label: "红色", color: "#FF0000" },
+  { value: "green", label: "绿色", color: "#00FF00" },
+  { value: "blue", label: "蓝色", color: "#0000FF" },
+  { value: "yellow", label: "黄色", color: "#E8A92A" },
+  { value: "purple", label: "紫色", color: "#9A4FF0" },
+  { value: "pink", label: "粉色", color: "#E7408C" },
+  { value: "indigo", label: "靛蓝", color: "#525EE9" },
+  { value: "gray", label: "灰色", color: "#606774" },
+  { value: "orange", label: "橙色", color: "#F86724" },
+  { value: "cyan", label: "青色", color: "#00C853" },
+]);
+
+const colorMap = new Map();
+colorList.value.forEach((item) => {
+  colorMap.set(item.value, item);
+});
+
+const editDeptRef = ref();
+const tableData = ref([]);
+const loading = ref(false);
+const param = reactive({
+  name: "",
+});
+
+const getData = () => {
+  loading.value = true;
+  api.tags
+    .getTree(param)
+    .then((res: any) => {
+      tableData.value = res?.data || [];
+    })
+    .finally(() => (loading.value = false));
+};
+
+// 打开新增菜单弹窗
+const addOrEdit = (row?: any) => {
+  editDeptRef.value.openDialog(row);
+};
+// 删除当前行
+const onTabelRowDel = (row: any) => {
+  ElMessageBox.confirm(`此操作将永久删除标签:${row.name}, 是否继续?`, "提示", {
+    confirmButtonText: "删除",
+    cancelButtonText: "取消",
+    type: "warning",
+  }).then(() => {
+    api.tags.del(row.id).then(() => {
+      ElMessage.success("删除成功");
+      getData();
+    });
+  });
+};
+// 页面加载时
+onMounted(() => {
+  getData();
+});
+</script>
+<style lang="scss" scoped>
+.color {
+  width: 12px;
+  height: 12px;
+  border-radius: 50%;
+  margin-right: 5px;
+}
+</style>

+ 1 - 1
src/views/system/manage/blacklist/index.vue

@@ -62,7 +62,7 @@
             <el-button size="small" text type="info" @click="onRowDel(scope.row)" v-auth="'del'">删除</el-button>
             <el-popover placement="bottom" :width="154" trigger="click">
               <template #reference>
-                <el-button size="small" type="text" class="more-btn" @click="isShowMore = !isShowMore" v-auth="'more'">更多
+                <el-button size="small" text type="primary" class="more-btn" @click="isShowMore = !isShowMore" v-auth="'more'">更多
                   <i style="margin-left: 2px;" :class="isShowMore ? 'fa fa-angle-down' : 'fa fa-angle-up'"></i>
                 </el-button>
               </template>

+ 1 - 1
src/views/system/manage/dept/index.vue

@@ -45,7 +45,7 @@
         <el-table-column prop="createdAt" label="创建时间" v-col="'createdAt'" align="center" min-width="180"></el-table-column>
         <el-table-column label="操作" align="center" width="140" v-col="'handle'">
           <template #default="scope">
-            <el-button size="small" type="text" @click="onOpenAddDept(scope.row)" v-auth="'add'">新增</el-button>
+            <el-button size="small" text type="primary" @click="onOpenAddDept(scope.row)" v-auth="'add'">新增</el-button>
             <el-button size="small" text type="warning" @click="onOpenEditDept(scope.row)" v-auth="'edit'">修改</el-button>
             <el-button size="small" text type="info" @click="onTabelRowDel(scope.row)" v-auth="'del'">删除</el-button>
           </template>

+ 1 - 1
src/views/system/manage/org/index.vue

@@ -48,7 +48,7 @@
         <el-table-column prop="createdAt" label="创建时间" align="center" min-width="180" v-col="'createdAt'"></el-table-column>
         <el-table-column label="操作" align="center" v-col="'handle'" width="140">
           <template #default="scope">
-            <el-button size="small" type="text" @click="onOpenAddDept(scope.row)" v-auth="'add'">新增</el-button>
+            <el-button size="small" text type="primary" @click="onOpenAddDept(scope.row)" v-auth="'add'">新增</el-button>
             <el-button size="small" text type="warning" @click="onOpenEditDept(scope.row)" v-auth="'edit'">修改</el-button>
             <el-button size="small" text type="info" @click="onTabelRowDel(scope.row)" v-auth="'del'">删除</el-button>
           </template>

+ 2 - 2
src/views/system/manage/role/index.vue

@@ -46,12 +46,12 @@
       <el-table-column prop="createdAt" v-col="'createdAt'" label="创建时间" width="170" align="center"></el-table-column>
       <el-table-column label="操作" width="220" v-col="'handle'" align="center" fixed="right">
         <template #default="scope">
-          <el-button size="small" type="text" @click="onOpenEditRole(scope.row)" v-auth="'edit'">修改</el-button>
+          <el-button size="small" text type="primary" @click="onOpenEditRole(scope.row)" v-auth="'edit'">修改</el-button>
           <el-button size="small" text type="info" @click="onRowDel(scope.row)" v-auth="'del'">删除</el-button>
           <el-button size="small" text type="success" @click="permission(scope.row)" v-auth="'role-premission'">角色权限</el-button>
           <el-button size="small" text type="info" @click="dataPermission(scope.row)" v-auth="'data-premission'">数据权限</el-button>
           <!-- <el-dropdown size="small">
-              <el-button type="text" size="small" style="margin-top:1px;margin-left:10px">更多
+              <el-button text type="primary" size="small" style="margin-top:1px;margin-left:10px">更多
                 <el-icon>
                   <ele-ArrowDown />
                 </el-icon>

+ 2 - 2
src/views/system/menu/index.vue

@@ -59,11 +59,11 @@
 			</el-table-column>
 			<el-table-column label="操作" v-col="'handle'" width="200" align="center" fixed="right">
 				<template #default="scope">
-					<el-button v-if="!scope.row.menuType" size="small" type="text" @click="onOpenAddMenu(scope.row)" v-auth="'add'">新增</el-button>
+					<el-button v-if="!scope.row.menuType" size="small" text type="primary" @click="onOpenAddMenu(scope.row)" v-auth="'add'">新增</el-button>
 					<el-button size="small" text type="warning" @click="onOpenEditMenu(scope.row)" v-auth="'edit'">修改</el-button>
 					<el-button size="small" text type="info" @click="onTabelRowDel(scope.row)" v-auth="'del'">删除</el-button>
 					<el-dropdown v-if="scope.row.menuType">
-						<el-button type="text" size="small" style="margin-top: 1px; margin-left: 10px" v-auth="'more'"
+						<el-button text type="primary" size="small" style="margin-top: 1px; margin-left: 10px" v-auth="'more'"
 							>更多
 							<el-icon>
 								<ele-ArrowDown />

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff