소스 검색

feat: 通道管理重构、支持多语言

vera_min 1 개월 전
부모
커밋
f3a8911d1c

+ 1 - 1
src/i18n/lang/en.ts

@@ -212,7 +212,7 @@ export default {
 			editServer: "Edit Service",
 			newServer: "Add Service",
 			channelManagement: "Channel Management",
-			channelManagement1: "Channel Management",
+			channelManagement1: "Channel",
 			editChannel: "Edit Channel",
 			newChannel: "Add Channel",
 			channelDetail: "Channel Details",

+ 42 - 1
src/i18n/pages/iotmanagerI18n/en.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-30 22:20:10
+ * @LastEditTime: 2025-08-31 12:30:16
  * @FilePath: /sagoo-admin-ui/src/i18n/pages/iotmanagerI18n/en.ts
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
@@ -492,5 +492,46 @@ export default {
 		"editStatus": "Edit Status",
 		"unknown": "Unknown",
 		"serverDetail": "Server Detail"
+	},
+	// 通道管理
+	channel: {
+		channelTitle: "Name",
+		channelTitlePlaceholder: "Please enter channel name",
+		channelNumber: "Reg Code",
+		channelNumberPlaceholder: "Please enter registration code",
+		addChannel: "Add Channel",
+		deviceAddress: "Device Address",
+		deviceAddressPlaceholder: "Please enter device address",
+		deleteChannelMessage: "This operation will permanently delete channel: {name}, continue?",
+		addDeviceChannel: "Add Device Channel",
+		save: "Save",
+		deviceChannelDetail: "Device Channel Details",
+		channelInfo: "Channel Information",
+		task: "Task",
+		addTask: "Add Task",
+		title: "Title",
+		interval: "Schedule Interval",
+		forwardFormat: "Forward Format",
+		topic: "MQTT Topic",
+		template: "Template",
+		stream: "Channel Stream",
+		start: "Start",
+		stop: "Stop",
+		clear: "Clear",
+		download: "Download Message",
+		deleteTaskMessage: "Are you sure to delete the task named: {name}?",
+		connectSuccess: "Connection Successful",
+		request: "Request",
+		response: "Response",
+		connectError1: "Connection Error: Server may not support real-time data stream",
+		connectError2: "Unable to establish real-time connection, please check server configuration",
+		clearLog: "Clear Stream",
+		taskDetail: "Task Details",
+		titlePlaceholder: "Please enter title",
+		intervalPlaceholder: "Please enter schedule interval",
+		encodingPlaceholder: "Please select forward format",
+		publishTopicPlaceholder: "Please enter MQTT topic",
+		templatePlaceholder: "Please select device template",
+		deviceTemplate: "Device Template"
 	}
 };

+ 39 - 10
src/i18n/pages/iotmanagerI18n/zh-cn.ts

@@ -2,18 +2,10 @@
  * @Author: vera_min vera_min@163.com
  * @Date: 2025-08-28 16:18:16
  * @LastEditors: vera_min vera_min@163.com
- * @LastEditTime: 2025-08-30 23:06:53
+ * @LastEditTime: 2025-08-31 12:28:59
  * @FilePath: /sagoo-admin-ui-dev/src/i18n/pages/iotmanagerI18n/zh-cn.ts
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
-/*
- * @Author: vera_min vera_min@163.com
- * @Date: 2025-08-05 12:42:31
- * @LastEditors: vera_min vera_min@163.com
- * @LastEditTime: 2025-08-30 22:09:47
- * @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
- */
 // 定义内容
 export default {
 	dashboard: {
@@ -503,6 +495,43 @@ export default {
 	},
 	// 通道管理
 	channel: {
-		channelName: "通道名称"
+		channelTitle: "通道名称",
+		channelTitlePlaceholder: "请输入通道名称",
+		channelNumber: "注册码",
+		channelNumberPlaceholder: "请输入注册码",
+		addChannel: "新增通道",
+		deviceAddress: "设备地址",
+		deviceAddressPlaceholder: "请输入设备地址",
+		deleteChannelMessage: "此操作将永久删除通道:{name}, 是否继续?",
+		addDeviceChannel: "新增设备通道",
+		save: "保 存",
+		deviceChannelDetail: "设备通道详情",
+		channelInfo: "通道信息",
+		task: "任务",
+		addTask: "新增任务",
+		title: "标题",
+		interval: "调度周期",
+		forwardFormat: "转发格式",
+		topic: "mqtt主题",
+		template: "模板",
+		stream: "通道码流",
+		start: "开始",
+		stop: "停止",
+		clear: "清空",
+		download: "下载报文",
+		deleteTaskMessage: "是否确认删除任务名称为:{name} 的数据项?",
+		connectSuccess: "连接成功",
+		request: "请求",
+		response: "应答",
+		connectError1: "连接错误: 服务器可能不支持实时数据流",
+		connectError2: "无法建立实时连接,请检查服务器配置",
+		clearLog: "清空码流",
+		taskDetail: "任务详情",
+		titlePlaceholder: "请输入标题",
+		intervalPlaceholder: "请输入调度周期",
+		encodingPlaceholder: "请选择转发格式",
+		publishTopicPlaceholder: "请输入mqtt主题",
+		templatePlaceholder: "请选择设备模板",
+		deviceTemplate: "设备模板"
 	}
 }

+ 42 - 1
src/i18n/pages/iotmanagerI18n/zh-tw.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-30 22:21:06
+ * @LastEditTime: 2025-08-31 12:28:13
  * @FilePath: /sagoo-admin-ui/src/i18n/pages/iotmanager/zh-tw.ts
  * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  */
@@ -492,5 +492,46 @@ export default {
 		"editStatus": "修改狀態",
 		"unknown": "未知",
 		"serverDetail": "伺服器詳情"
+	},
+	// 通道管理
+	channel: {
+    channelTitle: "通道名稱",
+    channelTitlePlaceholder: "請輸入通道名稱",
+    channelNumber: "註冊碼",
+    channelNumberPlaceholder: "請輸入註冊碼",
+    addChannel: "新增通道",
+    deviceAddress: "設備地址",
+    deviceAddressPlaceholder: "請輸入設備地址",
+    deleteChannelMessage: "此操作將永久刪除通道:{name},是否繼續?",
+    addDeviceChannel: "新增設備通道",
+    save: "保 存",
+    deviceChannelDetail: "設備通道詳情",
+    channelInfo: "通道資訊",
+    task: "任務",
+    addTask: "新增任務",
+    title: "標題",
+    interval: "調度週期",
+    forwardFormat: "轉發格式",
+    topic: "mqtt主題",
+    template: "模板",
+    stream: "通道碼流",
+    start: "開始",
+    stop: "停止",
+    clear: "清空",
+    download: "下載報文",
+    deleteTaskMessage: "是否確認刪除任務名稱為:{name} 的資料項?",
+    connectSuccess: "連接成功",
+    request: "請求",
+    response: "應答",
+    connectError1: "連接錯誤:伺服器可能不支援即時資料流",
+    connectError2: "無法建立即時連接,請檢查伺服器配置",
+    clearLog: "清空碼流",
+    taskDetail: "任務詳情",
+    titlePlaceholder: "請輸入標題",
+    intervalPlaceholder: "請輸入調度週期",
+    encodingPlaceholder: "請選擇轉發格式",
+    publishTopicPlaceholder: "請輸入mqtt主題",
+    templatePlaceholder: "請選擇設備模板",
+    deviceTemplate: "設備模板"
 	}
 };

+ 245 - 200
src/views/iot/device/channel/component/detail.vue

@@ -1,62 +1,82 @@
 <template>
-	<el-dialog title="设备通道详情" v-model="dialogVisible" width="900px" :before-close="closeDialog" :close-on-click-modal="false">
+	<!-- 设备通道详情 -->
+	<el-dialog :title="$t('message.channel.deviceChannelDetail')" v-model="dialogVisible" width="900px" :before-close="closeDialog" :close-on-click-modal="false">
 		<div class="page-full" style="height: 60vh;">
 			<el-tabs v-model="activeName">
-				<el-tab-pane label="通道信息" name="1">
-					<el-form ref="dataForm" :rules="rules" :model="temp" label-position="left" label-width="120px" style="width: 90%; margin: 0 auto">
-						<el-form-item label="通道名称" prop="title">
-							<el-input v-model="temp.title" disabled placeholder="请输入通道名称" />
+				<!-- 通道信息 -->
+				<el-tab-pane :label="$t('message.channel.channelInfo')" name="1">
+					<el-form ref="dataForm" label-position="right" :model="temp" :label-width="currentLocale === 'en' ? '120px' : '80px'" style="width: 100%;">
+						<!-- 通道名称 -->
+						<el-form-item :label="$t('message.channel.channelTitle')" prop="title">
+							<!-- 请输入通道名称 -->
+							<el-input v-model="temp.title" disabled :placeholder="$t('message.channel.channelTitlePlaceholder')" />
 						</el-form-item>
-						<el-form-item label="注册码" prop="number">
-							<el-input v-model="temp.number" disabled placeholder="请输入注册码" />
+						<!-- 注册码 -->
+						<el-form-item :label="$t('message.channel.channelNumber')" prop="number">
+							<!-- 请输入注册码 -->
+							<el-input v-model="temp.number" disabled :placeholder="$t('message.channel.channelNumberPlaceholder')" />
 						</el-form-item>
-						<el-form-item label="设备地址" prop="slaveId">
-							<el-input v-model.number="temp.slaveId" disabled placeholder="请输入设备地址" />
+						<!-- 设备地址 -->
+						<el-form-item :label="$t('message.channel.deviceAddress')" prop="slaveId">
+							<!-- 请输入设备地址 -->
+							<el-input v-model.number="temp.slaveId" disabled :placeholder="$t('message.channel.deviceAddressPlaceholder')" />
 						</el-form-item>
 						<!-- <el-form-item label="调度周期(秒)" prop="interval">
             <el-input v-model="temp.interval" placeholder="请输入调度周期" />
           </el-form-item> -->
-						<el-form-item label="" prop="">
-							<div align="right">
-								<el-button @click="closeDialog"> 取 消 </el-button>
-								<!-- <el-button type="primary" @click="updateData()"> 保 存 </el-button> -->
-							</div>
-						</el-form-item>
 					</el-form>
 				</el-tab-pane>
-				<el-tab-pane label="任务" name="2">
+				<!-- 任务 -->
+				<el-tab-pane :label="$t('message.channel.task')" name="2">
 					<div class="filter-container">
-						<el-button class="filter-item" type="primary" icon="el-icon-circle-plus-outline" @click="handleCreate"> 添加任务 </el-button>
+						<!-- 新增任务 -->
+						<el-button class="filter-item" type="primary" @click="handleCreate">
+							<el-icon>
+								<ele-FolderAdd />
+							</el-icon>
+							{{ $t('message.channel.addTask') }}
+						</el-button>
 					</div>
-
 					<el-table :key="tableKey" v-loading="listLoading" :data="taskList" border fit highlight-current-row style="width: 100%">
-						<el-table-column label="标题" prop="Job.title" align="center"></el-table-column>
-						<el-table-column label="调度周期" prop="Job.interval" align="center"></el-table-column>
-						<el-table-column label="转发格式" prop="encoding" align="center">
+						<!-- 标题 -->
+						<el-table-column :label="$t('message.channel.title')" prop="Job.title" align="center"></el-table-column>
+						<!-- 调度周期 -->
+						<el-table-column min-width="130" show-overflow-tooltip :label="$t('message.channel.interval')" prop="Job.interval" align="center"></el-table-column>
+						<!-- 转发格式 -->
+						<el-table-column min-width="130" show-overflow-tooltip  :label="$t('message.channel.forwardFormat')" prop="encoding" align="center">
 							<template #default="{ row }">
 								{{ getCodingLabel(row) }}
 							</template>
 						</el-table-column>
-						<el-table-column label="mqtt主题" prop="Job.publishTopic" align="center"></el-table-column>
-						<el-table-column label="模板" prop="Template.title" align="center"></el-table-column>
-						<el-table-column label="操作" align="center" width="200">
-							<template #default="{ row, $index }">
-								<el-button type="primary" size="small" @click="handleUpdate(row)"> 详情 </el-button>
-								<el-button v-if="row.status != 'deleted'" size="small" type="danger" @click="handleDelete(row)"> 删除 </el-button>
+						<!-- mqtt主题 -->
+						<el-table-column min-width="130" show-overflow-tooltip  :label="$t('message.channel.topic')" prop="Job.publishTopic" align="center"></el-table-column>
+						<!-- 模板 -->
+						<el-table-column min-width="130" show-overflow-tooltip :label="$t('message.channel.template')" prop="Template.title" align="center"></el-table-column>
+						<el-table-column :label="$t('message.tableI18nColumn.operation')" align="center" width="160">
+							<template #default="{ row }">
+								<!-- 详情 -->
+								<el-button type="primary" size="small" @click="handleUpdate(row)"> {{ $t('message.tableI18nAction.detail') }} </el-button>
+								<!-- 删除 -->
+								<el-button v-if="row.status != 'deleted'" size="small" type="danger" @click="handleDelete(row)"> {{ $t('message.tableI18nAction.delete') }} </el-button>
 							</template>
 						</el-table-column>
 					</el-table>
 
 					<pagination v-if="total > 0" :total="total" v-model:page="listQuery.page" v-model:limit="listQuery.size" @pagination="getList()" />
-					<TaskDialog ref="taskDialog" :formatOptions="formatOptions" @finish="getList()" />
+					<TaskDialog ref="taskDialogRef" :formatOptions="formatOptions" @finish="getList()" />
 				</el-tab-pane>
-				<el-tab-pane label="通道码流" name="3">
+				<!-- 通道码流 -->
+				<el-tab-pane :label="$t('message.channel.channelTitle')" name="3">
 					<div>
-						<el-button :type="evsrc ? 'info' : 'primary'" @click="openEv()">开始</el-button>
-						<el-button :type="evsrc ? 'primary' : 'info'" :disabled="!evsrc" @click="closeEv()">停止</el-button>
-						<el-button type="defualt" @click="clearLog()">清空</el-button>
-						<el-button type="info" style="margin-left: 150px" @click="downloadLog()">下载报文</el-button>
-						<ul id="logContainer" ref="logContainer"></ul>
+						<!-- 开始 -->
+						<el-button :type="evsrc ? 'info' : 'primary'" @click="openEv()">{{ $t('message.channel.start') }}</el-button>
+						<!-- 停止 -->
+						<el-button :type="evsrc ? 'primary' : 'info'" :disabled="!evsrc" @click="closeEv()">{{ $t('message.channel.stop') }}</el-button>
+						<!-- 清空 -->
+						<el-button type="defualt" @click="clearLog()">{{ $t('message.channel.clear') }}</el-button>
+						<!-- 下载报文 -->
+						<el-button type="info" style="margin-left: 150px" @click="downloadLog()">{{ $t('message.channel.download') }}</el-button>
+						<ul id="logContainer" ref="logContainerRef"></ul>
 					</div>
 				</el-tab-pane>
 			</el-tabs>
@@ -64,183 +84,199 @@
 	</el-dialog>
 </template>
 
-<script lang="ts">
+<script lang="ts" setup>
+import { ref, watch, computed } from 'vue';
 import { ElMessage, ElMessageBox } from 'element-plus';
 import api from '/@/api/device/modbus';
 import { getOtherServersOrigin } from '/@/utils/origin';
 import TaskDialog from './taskDialog.vue';
+import { useI18n } from 'vue-i18n';
+// 国际化
+const { locale, t } = useI18n();
 
-export default {
-	components: { TaskDialog },
-	data() {
-		return {
-			temp: {
-				title: '',
-				number: '',
-				templateNumber: '',
-				slaveId: '',
-				interval: '',
-			},
-			rules: {
-				title: [{ required: true, message: '请输入通道名称', trigger: 'blur' }],
-				number: [{ required: true, message: '请输入注册码', trigger: 'blur' }],
-				slaveId: [{ required: true, message: '请输入设备地址', trigger: 'blur' }],
-				templateNumber: [{ required: true, message: '请输入设备模板', trigger: 'change' }],
-			},
-			dialogVisible: false,
-			activeName: '1',
-			taskList: [],
-			listLoading: false,
-			total: 0,
-			tableKey: 0,
-			listQuery: {
-				page: 1,
-				size: 10,
-			},
-			evsrc: null as any,
-			count: 0,
-			templateOptions: [],
-			formatOptions: [],
-		};
-	},
-	watch: {
-		activeName: 'handleTabClick',
-	},
+const currentLocale = computed(() => locale.value);
+const taskDialogRef = ref();
+const logContainerRef = ref();
+const dialogVisible = ref(false);
+const activeName = ref('1');
+const taskList = ref([]);
+const listLoading = ref(false);
+const total = ref(0);
+const tableKey = ref(0);
+const listQuery = ref({
+	page: 1,
+	size: 10,
+});
+const evsrc = ref<any>(null);
+const count = ref(0);
+const formatOptions = ref([]);
+const temp = ref({
+	title: '',
+	number: '',
+	templateNumber: '',
+	slaveId: '',
+	interval: '',
+})
+
+const open = (row: any) => {
+	temp.value = { ...row };
+	getDict();
+	dialogVisible.value = true;
+}
+const downloadLog = () => {
+	window.open(getOtherServersOrigin(import.meta.env.VITE_MODBUS_API) + '/debug/export_message?number=' + temp.value.number);
+}
+const closeDialog = () => {
+	dialogVisible.value = false;
+	activeName.value = '1';
+	closeEv();
+};
+const handleFilter = () => {
+	listQuery.value.page = 1;
+	getList();
+};
+// 获取字典数据
+const getDict = () => {
+	api.getDict({ code: 'forwardFormat' }).then((data: any) => {
+		formatOptions.value = data.list || [];
+	});
+};
 
-	methods: {
-		open(row: any) {
-			this.temp = { ...row };
-			this.getDict();
-			this.dialogVisible = true;
-		},
-		downloadLog() {
-			window.open(getOtherServersOrigin(import.meta.env.VITE_MODBUS_API) + '/debug/export_message?number=' + this.temp.number);
-		},
-		closeDialog() {
-			this.dialogVisible = false;
-			this.activeName = '1';
-			(this.$refs['dataForm'] as any).resetFields();
-			this.closeEv();
-		},
-		updateData() {
-			(this.$refs['dataForm'] as any).validate((valid: boolean) => {
-				if (valid) {
-					const tempData = Object.assign({}, this.temp);
-					api.channel.editDevice(tempData).then(() => {
-						this.$emit('getList');
-						this.closeDialog();
-						ElMessage.success('操作成功!');
-					});
+// 获取任务list
+const getList = () => {
+	listLoading.value = true;
+	api.task.getList(listQuery.value)
+	.then((res: any) => {
+		taskList.value = res.list || [];
+		total.value = res.Total;
+	})
+	.finally(() => {
+		listLoading.value = false;
+	});
+}
+const handleDelete = (row: any) => {
+	ElMessageBox.confirm(
+		// '是否确认删除任务名称为"' + row.Job.title + '"的数据项?'
+		t('message.channel.deleteTaskMessage', {name: row.Job.title}),
+		// 提示
+		t('message.tableI18nConfirm.deleteTitle'),
+		{
+			// 确认
+			confirmButtonText: t('message.tableI18nConfirm.confirmText'),
+			// 取消
+			cancelButtonText: t('message.tableI18nConfirm.cancelText'),
+			type: 'warning',
+		}
+	)
+		.then(function () {
+			return api.task.deleteDeviceJob({ number: row.Job.number });
+		})
+		.then(() => {
+			handleFilter();
+			// 删除成功!
+			ElMessage.success(t('message.tableI18nConfirm.deleteSuccess'));
+		})
+		.catch(function () { });
+};
+const handleCreate = () => {
+	taskDialogRef.value.openDialog({
+		dialogStatusText: 'create',
+		deviceNumber: temp.value.number,
+	});
+};
+const handleUpdate = (row: any) => {
+	taskDialogRef.value.openDialog({
+		dialogStatusText: 'update',
+		row,
+		deviceNumber: temp.value.number,
+	});
+};
+
+// 码流
+const initEv = () => {
+	if (evsrc.value) return;
+	const url = `${getOtherServersOrigin(import.meta.env.VITE_MODBUS_API)}/debug?number=${temp.value.number}`;
+    
+	try {
+		evsrc.value = new EventSource(url);
+		
+		evsrc.value.onopen = (event: any) => {
+			// 连接成功
+				logContainerRef.value?.insertAdjacentHTML('beforeEnd', `<li style="color: #67C23A;">${t('message.channel.connectSuccess')}...</li>`);
+		};
+		
+		evsrc.value.onmessage = (ev: any) => {
+				try {
+						let obj = JSON.parse(ev.data);
+						if (obj.deviceId === temp.value.number) {
+							let color = obj.type === 'request' ? '#F56C6C' : '#409EFF';
+								// 请求/应答
+								let content = `${obj.type === 'request' ? `t('message.channel.request'):` : `t('message.channel.response'):`} ${obj.msg}`;
+								logContainerRef.value?.insertAdjacentHTML('afterbegin', `<li style="color: ${color}">${content}</li>`);
+						}
+				} catch (_e) {
+						console.log('解析消息错误: ', _e);
+				}
+		};
+		
+		evsrc.value.onerror = (event: any) => {
+			// 连接错误: 服务器可能不支持实时数据流
+				logContainerRef.value?.insertAdjacentHTML('beforeEnd', `<li style="color: #F56C6C;">${t('message.channel.connectError1')}</li>`);
+				// 自动关闭连接
+				if (evsrc.value) {
+						evsrc.value.close();
+						evsrc.value = null;
 				}
-			});
-		},
-		// tab 切换
-		handleTabClick() {
-			this.closeEv(false);
-			if (this.activeName === '2') {
+		};
+			
+	} catch (error) {
+		console.error('创建 EventSource 失败:', error);
+		// 无法建立实时连接,请检查服务器配置
+		ElMessage.error(t('message.channel.connectError2'));
+	}
+}
+
+const openEv = () => {
+	// 开始
+	(logContainerRef.value as any).insertAdjacentHTML('beforeEnd', `<li style="color: #000;">${t('message.channel.start')}...</li>`);
+	initEv();
+}
+const closeEv = (log = true) => {
+	evsrc.value && evsrc.value.close();
+	evsrc.value = null;
+	count.value = 0;
+	// this.clearLog()
+	if (log) {
+		// 停止
+		(logContainerRef.value as any).insertAdjacentHTML('beforeEnd', `<li style="color: #000;">${t('message.channel.stop')}...</li>`);
+	}
+}
+		// 清空码流
+const clearLog = () => {
+	(logContainerRef.value as any).innerHTML = '';
+}
+const getCodingLabel = (row: any) => {
+	const item = formatOptions.value.find((item: any) => item.value === row.Job.encoding) as any;
+	return item.title;
+}
+
+watch(
+	() => activeName.value,
+	(val) => {
+		if (val) {
+			closeEv(false);
+			if (activeName.value === '2') {
 				// 任务
-				this.getList();
-			} else if (this.activeName === '3') {
+				getList();
+			} else if (activeName.value === '3') {
 				// 码流
 				// this.initEv()
 			}
-		},
-		handleFilter() {
-			this.listQuery.page = 1;
-			this.getList();
-		},
-		// 获取字典数据
-		getDict() {
-			api.getDict({ code: 'forwardFormat' }).then((data: any) => {
-				this.formatOptions = data.list || [];
-			});
-		},
-		// 获取任务list
-		getList() {
-			this.listLoading = true;
-			api.task.getList(this.listQuery)
-				.then((res: any) => {
-					this.taskList = res.list || [];
-					this.total = res.Total;
-				})
-				.finally(() => {
-					this.listLoading = false;
-				});
-		},
-		handleDelete(row: any) {
-			ElMessageBox.confirm('是否确认删除任务名称为"' + row.Job.title + '"的数据项?', '警告', {
-				confirmButtonText: '确定',
-				cancelButtonText: '取消',
-				type: 'warning',
-			})
-				.then(function () {
-					return api.task.deleteDeviceJob({ number: row.Job.number });
-				})
-				.then(() => {
-					this.handleFilter();
-					ElMessage.success('删除成功!');
-				})
-				.catch(function () { });
-		},
-		handleCreate() {
-			(this.$refs.taskDialog as any).openDialog({
-				dialogStatus: 'create',
-				deviceNumber: this.temp.number,
-			});
-		},
-		handleUpdate(row: any) {
-			(this.$refs.taskDialog as any).openDialog({
-				dialogStatus: 'update',
-				row,
-				deviceNumber: this.temp.number,
-			});
-		},
-		// 码流
-		initEv() {
-			if (this.evsrc) return;
-			this.evsrc = new EventSource(`${getOtherServersOrigin(import.meta.env.VITE_MODBUS_API)}/debug?number=${this.temp.number}`);
-			let that = this;
-			this.evsrc.onmessage = function (ev: any) {
-				try {
-					let obj = JSON.parse(ev.data);
-					if (obj.deviceId === that.temp.number) {
-						let color = obj.type === 'request' ? '#F56C6C' : '#409EFF';
-						let content = `${obj.type === 'request' ? '请求:' : '应答:'} ${obj.msg}`;
-						this.$refs.logContainer.insertAdjacentHTML('afterbegin', `<li style="color: ${color}">${content}</li>`);
-					}
-				} catch (_e) {
-					// @ts-ignore
-					// console.log('error', e);
-				}
-			};
-			// this.evsrc.onerror = function (_ev: any) {
-			// @ts-ignore
-			// console.log('readyState = ' + ev.currentTarget.readyState);
-			// };
-		},
-		openEv() {
-			(this.$refs.logContainer as any).insertAdjacentHTML('beforeEnd', `<li style="color: #000;">开始...</li>`);
-			this.initEv();
-		},
-		closeEv(log = true) {
-			this.evsrc && this.evsrc.close();
-			this.evsrc = null;
-			this.count = 0;
-			// this.clearLog()
-			if (log) {
-				(this.$refs.logContainer as any).insertAdjacentHTML('beforeEnd', `<li style="color: #000;">停止...</li>`);
-			}
-		},
-		// 清空码流
-		clearLog() {
-			(this.$refs.logContainer as any).innerHTML = '';
-		},
-		getCodingLabel(row: any) {
-			const item = this.formatOptions.find((item: any) => item.value === row.Job.encoding) as any;
-			return item.title;
-		},
-	},
-};
+		}
+	}
+)
+
+defineExpose({ open });
 </script>
 
 <style lang="scss" scoped>
@@ -265,4 +301,13 @@ export default {
 	background-color: #f2f2f2;
 	line-height: 22px;
 }
+
+[data-theme='dark'] {
+
+	#logContainer  {
+		background-color: transparent;
+		color: #fff;
+		border-color: #333333;
+	}
+}
 </style>

+ 104 - 68
src/views/iot/device/channel/component/edit.vue

@@ -1,80 +1,116 @@
+<!--
+ * @Author: vera_min vera_min@163.com
+ * @Date: 2025-08-28 16:22:30
+ * @LastEditors: vera_min vera_min@163.com
+ * @LastEditTime: 2025-08-31 12:32:34
+ * @FilePath: /sagoo-admin-ui-pro2/src/views/iot/device/channel/component/edit.vue
+ * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
+-->
 <template>
-	<el-dialog title="添加设备通道" v-model="dialogVisible" width="600px" :before-close="closeDialog" :close-on-click-modal="false">
-		<el-form ref="dataForm" :rules="rules" :model="temp" label-position="left" label-width="120px" style="width: 90%; margin: 0 auto">
-			<el-form-item label="通道名称" prop="title">
-				<el-input v-model.trim="temp.title" placeholder="请输入通道名称" />
+	<!-- 新增设备通道 -->
+	<el-dialog :title="$t('message.channel.addDeviceChannel')" v-model="dialogVisible" width="600px" :before-close="closeDialog" :close-on-click-modal="false">
+		<el-form ref="formRef" :rules="rules" :model="temp" label-position="left" label-width="120px" style="width: 90%; margin: 0 auto">
+			<!-- 通道名称 -->
+			<el-form-item :label="$t('message.channel.channelTitle')" prop="title">
+				<!-- 请输入通道名称 -->
+				<el-input v-model.trim="temp.title" :placeholder="$t('message.channel.channelTitlePlaceholder')" />
 			</el-form-item>
-			<el-form-item label="注册码" prop="number">
-				<el-input v-model.trim="temp.number" placeholder="请输入注册码" />
+			<!-- 注册码 -->
+			<el-form-item :label="$t('message.channel.channelNumber')" prop="number">
+				<!-- 请输入注册码 -->
+				<el-input v-model.trim="temp.number" :placeholder="$t('message.channel.channelNumberPlaceholder')" />
 			</el-form-item>
-			<el-form-item label="设备地址" prop="slaveId">
-				<el-input v-model.number="temp.slaveId" placeholder="请输入设备地址" />
+			<!-- 设备地址 -->
+			<el-form-item :label="$t('message.channel.deviceAddress')" prop="slaveId">
+				<!-- 请输入设备地址 -->
+				<el-input v-model.number="temp.slaveId" :placeholder="$t('message.channel.deviceAddressPlaceholder')" />
 			</el-form-item>
 		</el-form>
-		<template #footer class="dialog-footer">
-			<el-button @click="closeDialog()"> 取 消 </el-button>
-			<el-button type="primary" @click="createData()"> 保 存 </el-button>
+		<template #footer>
+			<span class="dialog-footer">
+				<!-- 取消 -->
+				<el-button @click="closeDialog()">{{ $t('message.formI18nButton.cancel') }}</el-button>
+				<!-- 保存 -->
+				<el-button type="primary" @click="createData()">{{$t('message.channel.save')}}</el-button>
+			</span>
 		</template>
 	</el-dialog>
 </template>
-<script lang="ts">
+<script lang="ts" setup>
+import { ref, computed, unref } from 'vue';
 import api from '/@/api/device/modbus';
 import { ElMessage } from 'element-plus';
-export default {
-	data() {
-		return {
-			temp: {
-				title: '',
-				number: '',
-				templateNumber: '',
-				slaveId: '',
-				interval: '',
-			},
-			rules: {
-				title: [{ required: true, message: '请输入通道名称', trigger: 'blur' }],
-				number: [{ required: true, message: '请输入注册码', trigger: 'blur' }],
-				slaveId: [{ required: true, message: '请输入设备地址', trigger: 'blur' }],
-			},
-			dialogVisible: false,
-			listLoading: false,
-			templateOptions: [],
-		};
-	},
+import { useI18n } from 'vue-i18n';
+// 国际化
+const { locale, t } = useI18n();
 
-	methods: {
-		open() {
-			// this.getList();
-			this.dialogVisible = true;
-		},
-		closeDialog() {
-			(this.$refs.dataForm as any).resetFields();
-			this.dialogVisible = false;
-		},
-		// 获取模板数据
-		getList() {
-			this.listLoading = true;
-			api.channel
-				.getList({ page: 1, size: 50 })
-				.then((data: any) => {
-					this.templateOptions = data.list || [];
-				})
-				.finally(() => {
-					this.listLoading = false;
-				});
-		},
-		createData() {
-			(this.$refs['dataForm'] as any).validate((valid: boolean) => {
-				if (valid) {
-					api.channel.addDevice(this.temp).then(() => {
-						this.$emit('getList');
-						this.closeDialog();
-						ElMessage.success('操作成功!');
-					});
-				}
-			});
-		},
-	},
-};
-</script>
+const currentLocale = computed(() => locale.value);
+const temp = ref({
+	title: '',
+	number: '',
+	templateNumber: '',
+	slaveId: '',
+	interval: '',
+})
+const emit = defineEmits(['handleUpdate']);
+const formRef = ref<HTMLElement | null>(null)
+const dialogVisible = ref(false);
+// const listLoading = ref(false);
+// const templateOptions = ref([]);
+const rules = computed(() => ({
+	// 请输入通道名称
+	title: [{ required: true, message: t('message.channel.channelTitlePlaceholder'), trigger: 'blur' }],
+	// 请输入注册码
+	number: [{ required: true, message: t('message.channel.channelNumberPlaceholder'), trigger: 'blur' }],
+	// 请输入设备地址
+	slaveId: [{ required: true, message: t('message.channel.deviceAddressPlaceholder'), trigger: 'blur' }],
+}))
 
-<style lang="scss" scoped></style>
+const resetForm = () => {
+	temp.value = {
+		title: '',
+		number: '',
+		templateNumber: '',
+		slaveId: '',
+		interval: '',
+	}
+}
+
+const open = () => {
+	resetForm()
+	dialogVisible.value = true;
+}
+const closeDialog = () => {
+	// (dataForm.value as any).resetFields();
+	dialogVisible.value = false;
+}
+// 获取模板数据
+// const getList = () => {
+// 	listLoading.value = true;
+// 	api.channel.getList({ page: 1, size: 50 })
+// 	.then((data: any) => {
+// 		templateOptions.value = data.list || [];
+// 	})
+// 	.finally(() => {
+// 		listLoading.value = false;
+// 	});
+// }
+const createData = () => {
+	const formWrap = unref(formRef) as any
+	if (!formWrap) return
+	(formWrap as any).validate((valid: boolean) => {
+		if (!valid) return false;
+		api.channel.addDevice(temp.value).then(() => {
+			// getList();
+			closeDialog();
+			emit('handleUpdate')
+			// 操作成功
+			ElMessage.success(t('message.tableI18nConfirm.handleSuccess'));
+		});
+	});
+}
+
+defineExpose({
+	open
+})
+</script>

+ 147 - 115
src/views/iot/device/channel/component/taskDialog.vue

@@ -1,139 +1,171 @@
 <template>
 	<el-dialog :title="textMap[dialogStatus]" v-model="dialogVisible" width="500px" :before-close="closeDialog" append-to-body :close-on-click-modal="false">
-		<el-form ref="dataForm" :rules="rules" :model="temp" label-width="80px">
-			<el-form-item label="标题" prop="title">
-				<el-input v-model="temp.title" placeholder="请输入标题" />
+		<el-form ref="formRef" :rules="rules" :model="temp" :label-width="currentLocale === 'en' ? '130px' : '80px'">
+			<!-- 标题 -->
+			<el-form-item :label="$t('message.channel.title')" prop="title">
+				<!-- 请输入标题 -->
+				<el-input v-model="temp.title" :placeholder="$t('message.channel.titlePlaceholder')" />
 			</el-form-item>
-			<el-form-item label="调度周期" prop="interval">
-				<el-input v-model="temp.interval" placeholder="请输入调度周期" />
+			<!-- 调度周期 -->
+			<el-form-item :label="$t('message.channel.interval')" prop="interval">
+				<!-- 请输入调度周期 -->
+				<el-input v-model="temp.interval" :placeholder="$t('message.channel.intervalPlaceholder')" />
 			</el-form-item>
-			<el-form-item label="转发格式" prop="encoding">
-				<el-select v-model="temp.encoding" placeholder="请选择转发格式" style="width: 100%">
+			<!-- 转发格式 -->
+			<el-form-item :label="$t('message.channel.forwardFormat')" prop="encoding">
+				<!-- 请选择转发格式 -->
+				<el-select v-model="temp.encoding" :placeholder="$t('message.channel.encodingPlaceholder')" style="width: 100%">
 					<el-option v-for="item in formatOptions" :key="item.value" :label="item.title" :value="item.value"> </el-option>
 				</el-select>
 			</el-form-item>
-			<el-form-item label="mqtt主题" prop="publishTopic">
-				<el-input v-model="temp.publishTopic" placeholder="请输入mqtt主题" />
+			<!-- mqtt主题 -->
+			<el-form-item :label="$t('message.channel.topic')" prop="publishTopic">
+				<!-- 请输入mqtt主题 -->
+				<el-input v-model="temp.publishTopic" :placeholder="$t('message.channel.publishTopicPlaceholder')" />
 			</el-form-item>
-			<el-form-item label="设备模板" prop="templateNumber">
-				<el-select v-model="temp.templateNumber" filterable placeholder="请选择设备模板" style="width: 100%">
+			<!-- 设备模板 -->
+				<el-form-item :label="$t('message.channel.deviceTemplate')" prop="templateNumber">
+					<!-- 请选择设备模板 -->
+				<el-select v-model="temp.templateNumber" filterable :placeholder="$t('message.channel.templatePlaceholder')" style="width: 100%">
 					<el-option v-for="item in templateOptions" :key="item.number" :label="item.title" :value="item.number"> </el-option>
 				</el-select>
 			</el-form-item>
 		</el-form>
-		<template #footer class="dialog-footer">
-			<el-button @click="closeDialog"> 取 消 </el-button>
-			<el-button type="primary" @click="dialogStatus === 'create' ? createData() : updateData()"> 保 存 </el-button>
+		<template #footer>
+			<span class="dialog-footer">
+				<!-- 取 消 -->
+				<el-button @click="closeDialog">{{ $t('message.formI18nButton.cancel') }}</el-button>
+				<!-- 保 存 -->
+				<el-button type="primary" @click="dialogStatus === 'create' ? createData() : updateData()">{{ $t('message.channel.save') }}</el-button>
+			</span>
 		</template>
 	</el-dialog>
 </template>
 
-<script lang="ts">
+<script lang="ts" setup>
+import { ref, watch, computed, unref } from 'vue';
 import { PropType } from 'vue';
 import api from '/@/api/device/modbus';
 import { ElMessage } from 'element-plus';
+import { useI18n } from 'vue-i18n';
+// 国际化
+const { locale, t } = useI18n();
 
-export default {
-	components: {},
-	props: {
-		formatOptions: {
-			default: () => [],
-			type: Array as PropType<any[]>
-		},
-	},
-	data() {
-		return {
-			temp: {
-				title: '',
-				interval: '20s',
-				encoding: 'json',
-				publishTopic: '',
-				deviceNumber: '',
-				number: '',
-				templateNumber: '',
-			},
-			rules: {
-				title: [{ required: true, message: '请输入标题', trigger: 'change' }],
-				publishTopic: [{ required: true, message: '请输入mqtt主题', trigger: 'change' }],
-				templateNumber: [{ required: true, message: '请选择设备模板', trigger: 'change' }],
-			},
-			dialogVisible: false,
-			listLoading: false,
-			dialogStatus: 'update' as 'update' | 'create',
-			textMap: {
-				update: '任务详情',
-				create: '添加任务',
-			},
-			templateOptions: [] as any[],
-		};
+const currentLocale = computed(() => locale.value);
+const props = defineProps({
+	formatOptions: {
+		default: () => [],
+		type: Array as PropType<any[]>
 	},
+});
+const emit = defineEmits(['finish']);
+const formRef = ref()
+const temp = ref({
+	title: '',
+	interval: '20s',
+	encoding: 'json',
+	publishTopic: '',
+	deviceNumber: '',
+	number: '',
+	templateNumber: '',
+});
+const dialogVisible = ref(false);
+const listLoading = ref(false);
+const dialogStatus = ref('');
+const textMap = ref<any>({
+	// 任务详情
+	update: t('message.channel.taskDetail'),
+	// 新增任务
+	create: t('message.channel.addTask'),
+});
+const templateOptions = ref<any>([]);
 
-	methods: {
-		openDialog({ dialogStatus, row, deviceNumber }: any) {
-			this.dialogStatus = dialogStatus;
-			this.temp.deviceNumber = deviceNumber;
-			if (row) {
-				// this.temp = { ...row }
-				this.temp.number = row.Job.number;
-				this.temp.title = row.Job.title;
-				this.temp.interval = row.Job.interval;
-				this.temp.encoding = row.Job.encoding;
-				this.temp.publishTopic = row.Job.publishTopic;
-				this.temp.templateNumber = row?.Template?.number;
-			}
-			// this.getDict()
-			this.getTemplateList();
-			this.dialogVisible = true;
-		},
-		closeDialog() {
-			this.dialogVisible = false;
-			this.temp = {
-				title: '',
-				interval: '20s',
-				encoding: 'json',
-				publishTopic: '',
-				deviceNumber: '',
-				number: '',
-				templateNumber: '',
-			};
-			(this.$refs['dataForm'] as any).resetFields();
-		},
-		// 获取模板数据
-		getTemplateList() {
-			this.listLoading = true;
-			api.template
-				.getList({ page: 1, size: 50 })
-				.then((res: any) => {
-					this.templateOptions = res.list || [];
-				})
-				.finally(() => {
-					this.listLoading = false;
-				});
-		},
-		createData() {
-			(this.$refs['dataForm'] as any).validate((valid: boolean) => {
-				if (valid) {
-					api.task.addDeviceJob(this.temp).then(() => {
-						this.$emit('finish');
-						this.closeDialog();
-						ElMessage.success('操作成功!');
-					});
-				}
-			});
-		},
-		updateData() {
-			(this.$refs['dataForm'] as any).validate((valid: boolean) => {
-				if (valid) {
-					api.task.editDeviceJob(this.temp).then(() => {
-						this.$emit('finish');
-						this.closeDialog();
-						ElMessage.success('操作成功!');
-					});
-				}
-			});
-		},
-	},
+const rules = computed(() => ({
+	// 请输入标题
+	title: [{ required: true, message: t('message.channel.titlePlaceholder'), trigger: 'blur' }],
+	// 请输入mqtt主题
+	publishTopic: [{ required: true, message: t('message.channel.publishTopicPlaceholder'), trigger: 'blur' }],
+	// 请选择设备模板
+	templateNumber: [{ required: true, message: t('message.channel.templatePlaceholder'), trigger: 'blur' }],
+}));
+const openDialog = ({ dialogStatusText, row, deviceNumber }: any) => {
+	dialogStatus.value = dialogStatusText;
+	console.log('dialogStatus.value', dialogStatusText)
+
+	temp.value.deviceNumber = deviceNumber;
+	if (row) {
+		// this.temp = { ...row }
+		temp.value.number = row.Job.number;
+		temp.value.title = row.Job.title;
+		temp.value.interval = row.Job.interval;
+		temp.value.encoding = row.Job.encoding;
+		temp.value.publishTopic = row.Job.publishTopic;
+		temp.value.templateNumber = row?.Template?.number;
+	}
+	// this.getDict()
+	getTemplateList();
+	dialogVisible.value = true;
+};
+
+const closeDialog = () => {
+	dialogVisible.value = false;
+	
+	// 先重置表单验证状态
+	const formWrap = unref(formRef) as any;
+	if (formWrap) {
+			formWrap.resetFields();
+			formWrap.clearValidate();
+	}
+	
+	// 再重置数据
+	temp.value = {
+			title: '',
+			interval: '20s',
+			encoding: 'json',
+			publishTopic: '',
+			deviceNumber: '',
+			number: '',
+			templateNumber: '',
+	};
+};
+// 获取模板数据
+const getTemplateList = () => {
+	listLoading.value = true;
+	api.template
+		.getList({ page: 1, size: 50 })
+		.then((res: any) => {
+			templateOptions.value = res.list || [];
+		})
+		.finally(() => {
+			listLoading.value = false;
+		});
+};
+const createData = () => {
+	const formWrap = unref(formRef) as any
+	if (!formWrap) return
+	(formWrap as any).validate((valid: boolean) => {
+		if (!valid) return false;
+		api.task.addDeviceJob(temp.value).then(() => {
+			emit('finish');
+			closeDialog();
+			// 操作成功
+			ElMessage.success(t('message.tableI18nConfirm.handleSuccess'));
+		});
+	});
+};
+const updateData = () => {
+	const formWrap = unref(formRef) as any
+	if (!formWrap) return
+	(formWrap as any).validate((valid: boolean) => {
+		if (!valid) return false;
+		api.task.editDeviceJob(temp.value).then(() => {
+			emit('finish');
+			closeDialog();
+			// 操作成功
+			ElMessage.success(t('message.tableI18nConfirm.handleSuccess'));
+		});
+	});
 };
-</script>
 
-<style lang="scss" scoped></style>
+defineExpose({ openDialog });
+</script>

+ 51 - 22
src/views/iot/device/channel/index.vue

@@ -3,61 +3,80 @@
 		<el-card shadow="never">
 			<div class="search">
 				<el-form :model="params" inline ref="queryRef" @keyup.enter.native="getList(1)">
-					<el-form-item label="通道名称" prop="title">
-						<el-input v-model="params.title" placeholder="请输入通道名称" clearable style="width: 240px" />
+					<!-- 通道名称 -->
+					<el-form-item :label="$t('message.channel.channelTitle')" prop="title">
+						<!-- 请输入通道名称 -->
+						<el-input v-model="params.title" :placeholder="$t('message.channel.channelTitlePlaceholder')" clearable style="width: 240px" />
 					</el-form-item>
-					<el-form-item label="注册码" prop="number">
-						<el-input v-model="params.number" placeholder="请输入注册码" clearable style="width: 240px" />
+					<!-- 注册码 -->
+					<el-form-item :label="$t('message.channel.channelNumber')" prop="number">
+						<!-- 请输入注册码 -->
+						<el-input v-model="params.number" :placeholder="$t('message.channel.channelNumberPlaceholder')" 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.formI18nButton.query') }}
 						</el-button>
+						<!-- 重置 -->
 						<el-button @click="resetQuery()">
 							<el-icon>
 								<ele-Refresh />
 							</el-icon>
-							重置
+							{{ $t('message.formI18nButton.reset') }}
 						</el-button>
+						<!-- 新增通道 -->
 						<el-button type="primary" @click="addOrEdit()" v-auth="'add'">
 							<el-icon>
 								<ele-FolderAdd />
 							</el-icon>
-							新增通道
+							{{ $t('message.channel.addChannel') }}
 						</el-button>
 					</el-form-item>
 				</el-form>
 			</div>
 			<el-table :data="tableData" style="width: 100%" v-loading="loading">
-				<el-table-column type="index" label="序号" width="80" align="center" />
-				<el-table-column prop="title" label="通道名称" align="center" show-overflow-tooltip></el-table-column>
-				<el-table-column prop="number" label="注册码" align="center" show-overflow-tooltip></el-table-column>
-				<el-table-column prop="slaveId" label="设备地址" align="center" show-overflow-tooltip></el-table-column>
-				<el-table-column label="操作" width="100" align="center">
+				<!-- 序号 -->
+				<el-table-column type="index" :label="$t('message.tableI18nColumn.index')" width="80" align="center" />
+				<!-- 通道名称 -->
+				<el-table-column prop="title" :label="$t('message.channel.channelTitle')" align="center" show-overflow-tooltip></el-table-column>
+				<!-- 注册码 -->
+				<el-table-column prop="number" :label="$t('message.channel.channelNumber')" align="center" show-overflow-tooltip></el-table-column>
+				<!-- 设备地址 -->
+				<el-table-column prop="slaveId" :label="$t('message.channel.deviceAddress')" align="center" show-overflow-tooltip></el-table-column>
+				<!-- 操作 -->
+				<el-table-column :label="$t('message.tableI18nColumn.operation')" :width="currentLocale === 'en' ? 120 : 100" align="center">
 					<template #default="scope">
-						<el-button size="small" text type="primary" v-auth="'detail'" @click="viewDetail(scope.row)">详情</el-button>
-						<el-button size="small" text type="info" v-auth="'del'" @click="onDel(scope.row)">删除</el-button>
+						<!-- 详情 -->
+						<el-button size="small" text type="primary" v-auth="'detail'" @click="viewDetail(scope.row)">{{ $t('message.tableI18nAction.detail') }}</el-button>
+						<!-- 删除 -->
+						<el-button size="small" text type="info" v-auth="'del'" @click="onDel(scope.row)">{{ $t('message.tableI18nAction.delete') }}</el-button>
 					</template>
 				</el-table-column>
 			</el-table>
 			<pagination v-if="params.total" :total="params.total" v-model:page="params.page" v-model:limit="params.size" @pagination="getList()" />
 
-			<EditForm ref="editFormRef" @getList="getList(1)"></EditForm>
+			<EditForm  ref="editFormRef" @handleUpdate="getList(1)"></EditForm>
 			<detailForm ref="detailFormRef" @getList="getList(1)"></detailForm>
 		</el-card>
 	</div>
 </template>
 
 <script lang="ts" setup>
-import { ref } from 'vue';
+import { ref, computed } from 'vue';
 import EditForm from './component/edit.vue';
 import detailForm from './component/detail.vue';
 import api from '/@/api/device/modbus';
 import { ElMessageBox, ElMessage } from 'element-plus';
 import { useSearch } from '/@/hooks/useCommonModbus';
+import { useI18n } from 'vue-i18n';
+// 国际化
+const { locale, t } = useI18n();
+
+const currentLocale = computed(() => locale.value);
 
 const editFormRef = ref();
 const detailFormRef = ref();
@@ -81,13 +100,23 @@ const resetQuery = () => {
 };
 
 const onDel = (row: any) => {
-	ElMessageBox.confirm(`此操作将删除通道:“${row.title}”,是否继续?`, '提示', {
-		confirmButtonText: '确认',
-		cancelButtonText: '取消',
-		type: 'warning',
-	}).then(async () => {
+	// 此操作将删除通道:“${row.title}”,是否继续?
+	ElMessageBox.confirm(
+		t("message.channel.deleteChannelMessage", {name: row.title}),
+		// `此操作将删除通道:“${row.title}”,是否继续?`,
+		// 提示
+		t("message.tableI18nConfirm.deleteTitle"),
+		{
+			// 删除
+			confirmButtonText: t("message.tableI18nConfirm.confirmText"),
+			// 取消
+			cancelButtonText: t("message.tableI18nConfirm.cancelText"),
+			type: 'warning',
+		}
+	).then(async () => {
 		await api.channel.deleteDevice({ number: row.number });
-		ElMessage.success('删除成功');
+		// 删除成功
+		ElMessage.success(t("message.tableI18nConfirm.deleteSuccess"));
 		getList(1);
 	});
 };