diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..c75f02a --- /dev/null +++ b/.env.production @@ -0,0 +1,2 @@ +# 生产环境 +VITE_APP_BASE_API = '/prod-api' \ No newline at end of file diff --git a/components.d.ts b/components.d.ts index 666a7cd..07ba1be 100644 --- a/components.d.ts +++ b/components.d.ts @@ -11,72 +11,53 @@ declare module 'vue' { AButton: typeof import('ant-design-vue/es')['Button'] ACol: typeof import('ant-design-vue/es')['Col'] AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider'] - ActionButton: typeof import('./src/components/common/ActionButton/ActionButton.vue')['default'] ActionButtons: typeof import('./src/components/common/ActionButtons/index.vue')['default'] + ADescriptions: typeof import('ant-design-vue/es')['Descriptions'] + ADescriptionsItem: typeof import('ant-design-vue/es')['DescriptionsItem'] AForm: typeof import('ant-design-vue/es')['Form'] AFormItem: typeof import('ant-design-vue/es')['FormItem'] AInput: typeof import('ant-design-vue/es')['Input'] AInputGroup: typeof import('ant-design-vue/es')['InputGroup'] AInputPassword: typeof import('ant-design-vue/es')['InputPassword'] - AList: typeof import('ant-design-vue/es')['List'] - AListItem: typeof import('ant-design-vue/es')['ListItem'] AModal: typeof import('ant-design-vue/es')['Modal'] - ARadio: typeof import('ant-design-vue/es')['Radio'] - ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup'] + ARangePicker: typeof import('ant-design-vue/es')['RangePicker'] ARow: typeof import('ant-design-vue/es')['Row'] ASelect: typeof import('ant-design-vue/es')['Select'] ASelectOption: typeof import('ant-design-vue/es')['SelectOption'] ASpace: typeof import('ant-design-vue/es')['Space'] + ASwitch: typeof import('ant-design-vue/es')['Switch'] ATable: typeof import('ant-design-vue/es')['Table'] - ATextarea: typeof import('ant-design-vue/es')['Textarea'] - CameraModal: typeof import('./src/components/CameraModal/index.vue')['default'] CameraStatus: typeof import('./src/components/CameraStatus/index.vue')['default'] ExecutionResult: typeof import('./src/components/common/ExecutionResult/index.vue')['default'] ILucideActivity: typeof import('~icons/lucide/activity')['default'] ILucideCamera: typeof import('~icons/lucide/camera')['default'] - ILucideClock: typeof import('~icons/lucide/clock')['default'] ILucideCpu: typeof import('~icons/lucide/cpu')['default'] ILucideDatabase: typeof import('~icons/lucide/database')['default'] ILucideEdit: typeof import('~icons/lucide/edit')['default'] - ILucideFileText: typeof import('~icons/lucide/file-text')['default'] ILucideInbox: typeof import('~icons/lucide/inbox')['default'] - ILucideLink: typeof import('~icons/lucide/link')['default'] - ILucideLinkOff: typeof import('~icons/lucide/link-off')['default'] - ILucideLinOff: typeof import('~icons/lucide/lin-off')['default'] ILucideLoader: typeof import('~icons/lucide/loader')['default'] ILucideLock: typeof import('~icons/lucide/lock')['default'] - ILucideLogIn: typeof import('~icons/lucide/log-in')['default'] - ILucideLoOff: typeof import('~icons/lucide/lo-off')['default'] - ILucideLucideDatabase: typeof import('~icons/lucide/lucide-database')['default'] ILucideMonitor: typeof import('~icons/lucide/monitor')['default'] ILucidePackage: typeof import('~icons/lucide/package')['default'] - ILucidePlug: typeof import('~icons/lucide/plug')['default'] - ILucidePlugOff: typeof import('~icons/lucide/plug-off')['default'] ILucideRadio: typeof import('~icons/lucide/radio')['default'] ILucideRefreshCw: typeof import('~icons/lucide/refresh-cw')['default'] ILucideSave: typeof import('~icons/lucide/save')['default'] + ILucideSearch: typeof import('~icons/lucide/search')['default'] ILucideShieldCheck: typeof import('~icons/lucide/shield-check')['default'] ILucideSquarePen: typeof import('~icons/lucide/square-pen')['default'] ILucideTrash2: typeof import('~icons/lucide/trash2')['default'] - ILucideUnlink: typeof import('~icons/lucide/unlink')['default'] - ILucideUnlinkOff: typeof import('~icons/lucide/unlink-off')['default'] ILucideUser: typeof import('~icons/lucide/user')['default'] ILucideWifi: typeof import('~icons/lucide/wifi')['default'] ILucideX: typeof import('~icons/lucide/x')['default'] ILucideZap: typeof import('~icons/lucide/zap')['default'] - MesModal: typeof import('./src/components/MesModal/index.vue')['default'] + LmsStatus: typeof import('./src/components/LmsStatus/index.vue')['default'] MesStatus: typeof import('./src/components/MesStatus/index.vue')['default'] - NetworkModal: typeof import('./src/components/NetworkModal/index.vue')['default'] NetworkStatus: typeof import('./src/components/NetworkStatus/index.vue')['default'] - PlcModal: typeof import('./src/components/PlcModal/index.vue')['default'] PlcStatus: typeof import('./src/components/PlcStatus/index.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] - Settings: typeof import('./src/components/Settings/index.vue')['default'] SettingsModal: typeof import('./src/components/SettingsModal/index.vue')['default'] SSELogs: typeof import('./src/components/SSELogs/index.vue')['default'] - SseModal: typeof import('./src/components/SseModal/index.vue')['default'] SseStatus: typeof import('./src/components/SseStatus/index.vue')['default'] - XxxModal: typeof import('./src/components/xxxModal/index.vue')['default'] } } diff --git a/global.d.ts b/global.d.ts index 0ddb5ba..bc8a495 100644 --- a/global.d.ts +++ b/global.d.ts @@ -2,3 +2,4 @@ declare module '@/components/*'; declare module '@/views/*'; declare module '@/api/*'; +declare module '@/App.vue'; diff --git a/package.json b/package.json index 990ab09..ddf61fd 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "rd_mes_front_hmi", "private": true, - "version": "0.0.0", + "version": "0.0.1", "type": "module", "scripts": { "dev": "vite --open", - "build": "vue-tsc -b && vite build", + "build": "vite build", "preview": "vite preview" }, "dependencies": { diff --git a/src/App.vue b/src/App.vue index e642be4..f6c5ecd 100644 --- a/src/App.vue +++ b/src/App.vue @@ -6,4 +6,5 @@ \ No newline at end of file diff --git a/src/api/common/model.d.ts b/src/api/common/model.d.ts new file mode 100644 index 0000000..6b23e40 --- /dev/null +++ b/src/api/common/model.d.ts @@ -0,0 +1,7 @@ +export interface ApiResponse { + code?: number; + msg?: string; + data?: T; + rows?: T[]; + total?: number; +} diff --git a/src/api/data/index.ts b/src/api/data/index.ts new file mode 100644 index 0000000..3d1fffc --- /dev/null +++ b/src/api/data/index.ts @@ -0,0 +1,20 @@ +import request from '../request'; +import type { QueryParams } from './model'; + +// 获取 L1 数据 +export function getL1Data(params: QueryParams) { + return request({ + url: '/jinghua/l1Data/list', + method: 'get', + params + }) +} + +// 获取 L4 数据 +export function getL4Data(params: QueryParams) { + return request({ + url: '/jinghua/l4Data/list', + method: 'get', + params + }) +} \ No newline at end of file diff --git a/src/api/data/model.d.ts b/src/api/data/model.d.ts new file mode 100644 index 0000000..20a2451 --- /dev/null +++ b/src/api/data/model.d.ts @@ -0,0 +1,9 @@ +export interface QueryParams { + pageNum?: number; + pageSize?: number; + orderByColumn?: string; + isAsc?: boolean; + qrCode?: string; + createTimeBegin?: string; + createTimeEnd?: string; +} \ No newline at end of file diff --git a/src/api/lms/index.ts b/src/api/lms/index.ts index 069e933..8b2825d 100644 --- a/src/api/lms/index.ts +++ b/src/api/lms/index.ts @@ -4,7 +4,7 @@ import type { LmsWorkMode } from './model'; // 获取 LMS 工作模式 export const fetchLmsWorkMode = () => { return request({ - url: '/jinghua/mes/work-mode', + url: '/jinghua/work-mode', method: 'get', }) } @@ -12,7 +12,7 @@ export const fetchLmsWorkMode = () => { // 更新 LMS 工作模式 export const updateLmsWorkMode = (workMode: LmsWorkMode) => { return request({ - url: `/jinghua/mes/work-mode/${workMode}`, + url: `/jinghua/work-mode/${workMode}`, method: 'put', }) } diff --git a/src/api/request.ts b/src/api/request.ts index 33aec82..174ccc3 100644 --- a/src/api/request.ts +++ b/src/api/request.ts @@ -1,7 +1,9 @@ import axios from 'axios'; -import { Modal, notification } from 'ant-design-vue'; import router from '@/router'; import { getToken } from '@/utils/auth'; +import { Modal, notification } from 'ant-design-vue'; +import type { AxiosRequestConfig } from "axios"; +import type { ApiResponse } from "@/api/common/model"; const errCodeMap: { [key: string]: string } = { '403': '当前操作没有权限', @@ -12,7 +14,7 @@ const errCodeMap: { [key: string]: string } = { // 创建axios实例 const service = axios.create({ baseURL: import.meta.env.VITE_APP_BASE_API as string, - timeout: 10000 + timeout: 10000, }); // 请求拦截器 @@ -36,37 +38,46 @@ service.interceptors.request.use( service.interceptors.response.use( response => { console.log(response) - // 未设置状态码则默认成功状态 const code = response.data.code || 200; + const data = response.data; - if (code === 200) { - return response.data ?? response; - } else if (code === 401) { - Modal.error({ - title: '系统提示', - content: '登录状态已过期,请重新登录', - onOk: () => { - router.push('/login') - } - }) - } else { - const codeStr = String(code); - const errMsg = errCodeMap[codeStr] || response.data.msg || errCodeMap['default']; - notification.error({ - message: '请求错误', - description: errMsg, - }); - return Promise.reject(response.data); + switch (code) { + case 200: + return data ?? response; + case 401: + Modal.error({ + title: '系统提示', + content: '登录状态已过期,请重新登录', + onOk: () => { + router.push('/login') + } + }) + return Promise.reject(data); + default: + const errMsg = errCodeMap[code] || data.msg || errCodeMap['default']; + notification.error({ + message: '请求错误', + description: errMsg, + }); + return Promise.reject(data); } }, error => { + error.message = error.code === "ECONNABORTED" ? '请求超时,请稍后重试' : error.message; + // 网络/服务器错误统一处理 notification.error({ - message: '网络错误', - description: error.message || '请求失败', + message: "网络错误", + description: error.message, }); return Promise.reject(error); } ); -export default service; \ No newline at end of file +// 类型检查 +function request(config: AxiosRequestConfig): Promise> { + return service(config) as unknown as Promise>; +} + +export default request; +// export default service; \ No newline at end of file diff --git a/src/assets/styles/_ant-design.scss b/src/assets/styles/_ant-design.scss index 70db675..09898bf 100644 --- a/src/assets/styles/_ant-design.scss +++ b/src/assets/styles/_ant-design.scss @@ -1,153 +1,9 @@ @use './variables' as *; -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} -.container { - width: 100%; - height: 100%; - background-color: #000; - color: #fff; - display: flex; - flex-direction: column; -} - -.info-row { - height: 5vh; - min-height: 5vh; - - &.title { - height: 6vh; - min-height: 6vh; - } - - .title { - display: flex; - justify-content: center; - align-items: center; - font-size: $title-font-size; - } -} - -.logList-row { - flex: 1 1 auto; // 修改 - min-height: 0; // 新增 - - .logList-col { - height: 100%; - padding: 0; - } - - .logList { - overflow-y: auto; - } -} - - -.ant-col { - border-right: 2px solid #fff; - border-bottom: 2px solid #fff; - font-size: $content-font-size; - - &.option { - display: flex; - justify-content: center; - align-items: center; - padding: 5px 3px 3px; - } - - &.subtitle, - &.text, - &.label { - text-align: center; - padding: 0 1rem; - line-height: 5vh; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - &.logList-col { - padding: 2px 2px 0 2px; - } -} - -.ant-btn, -.ant-btn.ant-btn-sm { - width: 100%; - height: 100%; - border-radius: 0; - background-color: #d6d6d6; - color: #000; - font-size: $content-font-size; - overflow: hidden; - &:hover { - border: 1px solid #000; - color: #fff; - background-color: #000; - font-size: 1vw; - } - &:active { - transform: scale(.9); - transition: 100ms; - background-color: #252525; - } -} - -// 隐藏空列表 -:deep(.ant-list-empty-text) { - display: none; -} - -// 列表 -.ant-list { - height: 100%; - width: 100%; - padding-right: 0 !important; - - .ant-list-item { - padding: 4px 12px; - color: #fff; - font-size: $log-font-size; - } -} - -// 下拉框 -.ant-select { - width: 100%; - height: 100%; - display: block; - margin-top: 1px; - - :deep(.ant-select-selector) { - height: 100%; - border: none; - border-radius: 0; - background-color: unset; - } - :deep(.ant-select-selection-search-input) { - height: 100% !important; - } - :deep(.ant-select-selection-item) { - display: flex; - justify-content: center; - align-items: center; - font-size: $content-font-size; - color: #adff2f; - } - :deep(.ant-select-selection-placeholder) { - display: flex; - justify-content: center; - align-items: center; - font-size: $content-font-size; - color: #888; - } - - :deep(.ant-select-suffix svg) { - width: 1.5em; - height: 1.5em; - color: #fff; - } +// 解决外部图标无法居中问题 +.ant-btn:has(svg) { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 4px; } \ No newline at end of file diff --git a/src/assets/styles/index.scss b/src/assets/styles/index.scss index edbe955..4832f10 100644 --- a/src/assets/styles/index.scss +++ b/src/assets/styles/index.scss @@ -1,2 +1,3 @@ @forward './base'; -@forward './variables'; \ No newline at end of file +@forward './variables'; +@forward './ant-design'; \ No newline at end of file diff --git a/src/components/CameraStatus/index.vue b/src/components/CameraStatus/index.vue index a8f94e1..7122896 100644 --- a/src/components/CameraStatus/index.vue +++ b/src/components/CameraStatus/index.vue @@ -41,7 +41,7 @@ defineExpose({ \ No newline at end of file diff --git a/src/components/MesStatus/index.vue b/src/components/MesStatus/index.vue index ab25e98..ad54277 100644 --- a/src/components/MesStatus/index.vue +++ b/src/components/MesStatus/index.vue @@ -42,7 +42,7 @@ defineExpose({ diff --git a/src/views/L1-data-list/types.ts b/src/views/L1-data-list/types.ts new file mode 100644 index 0000000..7094c58 --- /dev/null +++ b/src/views/L1-data-list/types.ts @@ -0,0 +1,58 @@ +export interface L1Data { + /** 加工信息ID */ + processInfoId: number; + + /** 产品状态: 0-无, 1-有 */ + productStatus: 0 | 1; + + /** 良品标记: 0-不良品, 1-良品 */ + goodFlag: 0 | 1; + + /** 加工记忆: 0-未开工, 1-已加工 */ + processMemory: 0 | 1; + + /** 功能SW: 0-无效, 1-有效 */ + functionSw: 0 | 1; + + /** 轴号 */ + axisNumber: number; + + /** 上骨架时间: 年, 00~99 */ + loadSkeletonYear: number; + + /** 上骨架时间: 月日, 0101~1231 */ + loadSkeletonMonthDay: number; + + /** 上骨架时间: 时分, 0000~2459 */ + loadSkeletonHourMin: number; + + /** 上骨架时间: 秒, 00~59 */ + loadSkeletonSecond: number; + + /** 插端子压力值 */ + plugTerminalPressure: number; + + /** 插端子高度值 */ + plugTerminalHeight: number; + + /** 电阻值 */ + resistance: number; + + /** 电感LS值 */ + inductorLs: number; + + /** 电感Q值 */ + inductorQ: number; + + /** 耐压R值 */ + pressureResistanceR: number; + + /** 耐压I值 */ + pressureResistanceI: number; + + /** 视觉结果: 0-NG, 1-OK */ + visualResult: 0 | 1; + + /** 创建时间 */ + createTime: string; // ISO 8601 格式,如 "2025-09-23T12:34:56" +} \ No newline at end of file diff --git a/src/views/L4-data-list/data.ts b/src/views/L4-data-list/data.ts new file mode 100644 index 0000000..a71b687 --- /dev/null +++ b/src/views/L4-data-list/data.ts @@ -0,0 +1,69 @@ +import type { TableColumnType, TableColumnsType } from "ant-design-vue"; + +const INDEX_COLUMN: TableColumnType = { + key: "index", + title: "序号", + width: 60, + fixed: true, + align: "center", +} as const; + +// 定义操作列配置 +const ACTION_COLUMN: TableColumnType = { + key: "action", + title: "操作", + width: 120, + ellipsis: true, + fixed: "right", + align: "center", +} as const; + +// 使用 Record 类型确保键值对的安全性 +export const fields: Record = { + processInfoId: "加工信息ID", + pltNo: "PLT No", + processF1: "加工F1", + processF2: "加工F2", + goodProductF1: "良品F1", + goodProductF2: "良品F2", + electricalResult: "电气检测结果", + engraveResult: "印字检测结果", + qrCode: "二维码", + qrCodeLevel: "二维码等级", + pressure15Riveting: "压力1_5#_铆接", + height15Riveting: "高度1_5#_铆接", + pressure25Magnet1: "压力2_5#_磁石1", + height25Magnet1: "高度2_5#_磁石1", + pressure36Magnet2: "压力3_6#_磁石2", + height36Magnet2: "高度3_6#_磁石2", + torque47AxisInsert: "扭矩4_7#_轴旋入", + height47AxisInsert: "高度4_7#_轴旋入", + pressure58LowerCase: "压力5_8#_下壳装入", + height58LowerCase: "高度5_8#_下壳装入", + pressure69UpperCase: "压力6_9#_上壳装入", + height69UpperCase: "高度6_9#_上壳装入", + height79HeightCheck: "高度7_9#_高度检测", + pressure79Laser: "压力7_9#_激光", + height89Laser: "高度8_9#_激光", + value19DcrUpper: "数値1_9#_DCR(上)", + value29DcrLower: "数値2_9#_DCR(下)", + value39LcrUpperLs: "数値3_9#_LCR(上)LS", + value49LcrLowerQ: "数値4_9#_LCR(下)Q", + value59LcrLowerLs: "数値5_9#_LCR(下)LS", + value69LcrLowerQ: "数値6_9#_LCR(下)Q", + value79IrR: "数値7_9#_IR R", + value89IrI: "数値8_9#_IR I", + createTime: "创建时间" +} as const; + +// 导出完整的列配置 +export const columns: TableColumnsType = [ + INDEX_COLUMN, + ...Object.entries(fields).map(([key, title]) => ({ + key, + title, + width: 150, + ellipsis: true, + })), + ACTION_COLUMN, +]; diff --git a/src/views/L4-data-list/index.vue b/src/views/L4-data-list/index.vue index e69de29..9f7f561 100644 --- a/src/views/L4-data-list/index.vue +++ b/src/views/L4-data-list/index.vue @@ -0,0 +1,336 @@ + + + + + \ No newline at end of file diff --git a/src/views/L4-data-list/types.ts b/src/views/L4-data-list/types.ts new file mode 100644 index 0000000..7de23d3 --- /dev/null +++ b/src/views/L4-data-list/types.ts @@ -0,0 +1,106 @@ +export interface L4Data { + /** 加工信息ID */ + processInfoId: number; + + /** PLT No */ + pltNo: number; + + /** 加工F1, 0:未加工 */ + processF1: 0 | 1; + + /** 加工F2, 1:已加工 */ + processF2: 0 | 1; + + /** 良品F1, 0:不良品 */ + goodProductF1: 0 | 1; + + /** 良品F2, 1:良品 */ + goodProductF2: 0 | 1; + + /** + * 电气检测结果 + * 0-DCR不良, 1-LCR不良, 3-IR不良, 7-良品 + */ + electricalResult: 0 | 1 | 3 | 7; + + /** 印字检测结果, 0-良品, 1-不良品 */ + engraveResult: 0 | 1; + + /** 二维码 */ + qrCode: string; + + /** 二维码等级 */ + qrCodeLevel: string; + + /** 压力1_5#_铆接 */ + pressure15Riveting: number; + + /** 高度1_5#_铆接 */ + height15Riveting: number; + + /** 压力2_5#_磁石1 */ + pressure25Magnet1: number; + + /** 高度2_5#_磁石1 */ + height25Magnet1: number; + + /** 压力3_6#_磁石2 */ + pressure36Magnet2: number; + + /** 高度3_6#_磁石2 */ + height36Magnet2: number; + + /** 扭矩4_7#_轴旋入 */ + torque47AxisInsert: number; + + /** 高度4_7#_轴旋入 */ + height47AxisInsert: number; + + /** 压力5_8#_下壳装入 */ + pressure58LowerCase: number; + + /** 高度5_8#_下壳装入 */ + height58LowerCase: number; + + /** 压力6_9#_上壳装入 */ + pressure69UpperCase: number; + + /** 高度6_9#_上壳装入 */ + height69UpperCase: number; + + /** 高度7_9#_高度检测 */ + height79HeightCheck: number; + + /** 压力7_9#_激光 */ + pressure79Laser: number; + + /** 高度8_9#_激光 */ + height89Laser: number; + + /** 数値1_9#_DCR(上) */ + value19DcrUpper: number; + + /** 数値2_9#_DCR(下) */ + value29DcrLower: number; + + /** 数値3_9#_LCR(上)LS */ + value39LcrUpperLs: number; + + /** 数値4_9#_LCR(下)Q */ + value49LcrLowerQ: number; + + /** 数値5_9#_LCR(下)LS */ + value59LcrLowerLs: number; + + /** 数値6_9#_LCR(下)Q */ + value69LcrLowerQ: number; + + /** 数値7_9#_IR R */ + value79IrR: number; + + /** 数値8_9#_IR I */ + value89IrI: number; + + /** 创建时间 (ISO 8601) */ + createTime: string; +} \ No newline at end of file diff --git a/src/views/index.vue b/src/views/index.vue index 39d3d92..db3f484 100644 --- a/src/views/index.vue +++ b/src/views/index.vue @@ -3,6 +3,7 @@ import { ref, onMounted, onBeforeUnmount, reactive, nextTick } from 'vue'; import { useRealTime } from '@/utils/dateUtils'; import { checkOrderNumberApi, addProcessInfoApi } from '@/api/detect'; import type { Rule } from 'ant-design-vue/es/form'; +import { message } from 'ant-design-vue'; // 检测设备表单规则 const rules: Record = { @@ -44,23 +45,9 @@ const detectFormRef = ref(); const packageFormRef = ref(); // 执行结果日志 -const executionLogs = ref([ - '14:25:32 - 开始检测流程 SN-A538-09-2023-00018', - '14:25:33 - 读取产品参数完成', - '14:25:36 - 尺寸检测: 通过 (0.15mm)', - '14:25:36 - 电压检测: 通过 (3.25V)', - '14:25:37 - 精度检测: 通过 (98.7%)', - '14:25:38 - 检测结果: 合格' -]); +const executionLogs = ref([]); -const packageLogs = ref([ - '14:25:45 - 包装流程: SN-A538-09-2023-00018', - '14:25:46 - 读取产品标签完成', - '14:25:49 - 包装材料准备完成', - '14:25:52 - 产品包装完成', - '14:25:53 - 更新库存: 已包装 (18/25)', - '14:25:54 - 包装结果: 正常' -]); +const packageLogs = ref([]); // 滚动到检测设备日志底部的函数 const scrollToExecutionBottom = () => { @@ -262,6 +249,7 @@ const saveDetectingEdit = () => { fixtureCode: detectForm.fixtureCode }).then(res => { console.log(res) + message.success('保存成功'); }) }).catch(() => { console.log('检测设备表单验证失败'); @@ -350,6 +338,9 @@ onBeforeUnmount(() => { :on-mes-event="handleMESEvent" :on-sse-message="handleSseMessage" /> + + +