0a2ce614306f517929ee939c04b09df0ca4ea2c0..e5e76412f1a20e8aed95614cbd7bf2b638cda2cc
4 天以前 zhang
1
e5e764 对比 | 目录
4 天以前 zhang
1
5a6a6c 对比 | 目录
1个文件已删除
16个文件已修改
8个文件已添加
1306 ■■■■■ 已修改文件
component/component-Influxdb/src/main/java/com/zy/component/influxdb/service/InfluxDBService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-cv/src/main/java/com/zy/asrs/service/JobService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-cv/src/main/java/com/zy/asrs/service/impl/DevpServiceImpl.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-cv/src/main/java/com/zy/asrs/service/impl/JobServiceImpl.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-cv/src/main/java/com/zy/asrs/service/impl/WmsMainServiceImpl.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-cv/src/main/java/com/zy/asrs/service/impl/WrkLastnoServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-cv/src/main/java/com/zy/core/operation/handler/AppleLocOperationHandler.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-cv/src/main/java/com/zy/core/operation/handler/FakeUserOperationHandler.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-cv/src/main/java/com/zy/core/operation/handler/SendTaskOperationHandler.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-cv/src/main/resources/application.yml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-cv/src/main/resources/mapper/JobMapper.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-cv/src/main/webapp/views/pipeline.html 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/dashboard.html 392 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/pom.xml 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/java/com/zy/acs/hex/HexApplication.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/java/com/zy/acs/hex/config/WebMvcConfig.java 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/java/com/zy/acs/hex/controller/DeviceLogController.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/java/com/zy/acs/hex/controller/RouterController.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/java/com/zy/acs/hex/controller/TestController.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/java/com/zy/acs/hex/domain/Device.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/java/com/zy/acs/hex/influxdb/task/InfluxDbScheduler.java 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/java/com/zy/acs/hex/utils/HttpGo.java 322 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/resources/application.yml 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/webapp/views/index.html 165 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
zy-acs-hex/src/main/webapp/views/login.html 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
component/component-Influxdb/src/main/java/com/zy/component/influxdb/service/InfluxDBService.java
@@ -29,8 +29,6 @@
    @Autowired
    private InfluxDBClient influxDBClient;
    /**
     * 写入数据
     *
zy-acs-cv/src/main/java/com/zy/asrs/service/JobService.java
@@ -12,7 +12,7 @@
    Job getJobByBarcodeAndJobSts(String barcode, Integer jobSts);
    Job getJobByBarcode(String barcode);
    Job getJobByBarcode(String barcode, Integer jobSts);
    Job getJobByJobNo(Integer jobNo);
zy-acs-cv/src/main/java/com/zy/asrs/service/impl/DevpServiceImpl.java
@@ -19,6 +19,8 @@
            if (de != null) {
                de.setSqlData(devp);
                baseMapper.updateById(de);
            }else {
                baseMapper.insert(devp);
            }
        }
    }
zy-acs-cv/src/main/java/com/zy/asrs/service/impl/JobServiceImpl.java
@@ -41,8 +41,8 @@
    }
    @Override
    public Job getJobByBarcode(String barcode) {
        return baseMapper.getJobByBarcode(barcode);
    public Job getJobByBarcode(String barcode, Integer jobSts) {
        return baseMapper.getJobByBarcodeAndJobSts(barcode,jobSts);
    }
    @Override
zy-acs-cv/src/main/java/com/zy/asrs/service/impl/WmsMainServiceImpl.java
@@ -74,4 +74,11 @@
        }
        return null;
    }
    public static void main(String[] args) {
        String s= "{\"msg\":\"Success\",\"code\":200,\"data\":{\"locNo\":\"A102400201\",\"batchNo\":\"TK2603104428\",\"taskNo\":\"TK2603104428\"}}";
        //System.out.println(JSON.parseObject(s).getString("data"));
        ApplyInRepsonseDto applyInRepsonseDto = JSONObject.parseObject(JSON.parseObject(s).getString("data"), ApplyInRepsonseDto.class);
        System.out.println(applyInRepsonseDto);
    }
}
zy-acs-cv/src/main/java/com/zy/asrs/service/impl/WrkLastnoServiceImpl.java
@@ -37,7 +37,7 @@
        do {
            workNo = workNo >= eNo ? sNo : workNo + 1;
        } while (jobService.getJobByJobNo(workNo) == null);
        } while (jobService.getJobByJobNo(workNo) != null);
        if (workNo > 0) {
            wrkLastno.setWrkNo(workNo);
zy-acs-cv/src/main/java/com/zy/core/operation/handler/AppleLocOperationHandler.java
@@ -85,18 +85,21 @@
                        }
                        // 9991是空板,9992是满板
                        if (staProtocol.getWorkNo() >= 9991 && staProtocol.getWorkNo() <= 9992) {
                            Job job = jobService.getJobByBarcode(barcode);
                            Job job = jobService.getJobByBarcode(barcode ,ConveyorStateType.INBOUND.getStatus());
                            // 申请入库
                            if (job == null || (job != null && job.getJobSts() == ConveyorStateType.CLEARSIGNAL.getStatus())) {
                                ApplyInRepsonseDto locOfWms = wmsMainService.getLocOfWms(applyIn(barcode, inSta.getStaNo() + "", staProtocol));
                                if (locOfWms != null) {
                                    staProtocol.setWorkNo(job.getJobNo());
                                    Integer workNo = getWorkNo();
                                    staProtocol.setWorkNo(workNo);
                                    staProtocol.setStaNo(inSta.getTargetSta());
                                    if (MessageQueue.offer(SlaveType.Devp, devp.getId(), new Task(TaskType.WRITE, staProtocol))) {
                                        if (!jobService.insert(initJob(locOfWms, barcode, inSta.getTargetSta() + ""))) {
                                        if (!jobService.insert(initJob(locOfWms, barcode, workNo,inSta.getTargetSta() + ""))) {
                                            throw new CoolException("更新输送线任务失败," + " - " + staProtocol.getWorkNo());
                                        }
                                        log.info("入库前进:{},{}", staProtocol.getWorkNo(), inSta.getTargetSta());
                                    }else {
                                        log.info("下发失败:{},{}", staProtocol.getWorkNo(), inSta.getTargetSta());
                                    }
                                } else {
                                    log.info("WMS未返回库位信息,条码:{},站点:{}", barcode, inSta.getStaNo());
@@ -112,14 +115,15 @@
        }
    }
    private Job initJob(ApplyInRepsonseDto locOfWms, String barcode, String staNo) {
    private Job initJob(ApplyInRepsonseDto locOfWms, String barcode,Integer workNo, String staNo) {
        Job  job = new Job();
        job.setLoc(locOfWms.getLocNo());
        job.setTaskNo(locOfWms.getTaskNo());
        job.setBatchNo(locOfWms.getBatchNo());
        job.setBarcode(barcode);
        job.setStaNo(staNo);
        job.setJobNo(getWorkNo());
        job.setJobNo(workNo);
        job.setJobSts(ConveyorStateType.INBOUND.getStatus());
        job.setWmsTime(new Date());
        return job;
zy-acs-cv/src/main/java/com/zy/core/operation/handler/FakeUserOperationHandler.java
@@ -60,7 +60,7 @@
                return;
            }
            if (staProtocol.getWorkNo() > 0 && staProtocol.isAutoing()) {
                Job jobByWorkNo = jobService.getJobByJobNo(staProtocol.getWorkNo());
                Job jobByWorkNo = jobService.getJobByJobNoAndJobSts(staProtocol.getWorkNo(),ConveyorStateType.OUTBOUND.getStatus());
                if (jobByWorkNo != null && jobByWorkNo.getJobSts() == ConveyorStateType.OUTBOUND.getStatus()) {
                    staProtocol.setWorkNo(9992);
                    staProtocol.setStaNo(1005);
zy-acs-cv/src/main/java/com/zy/core/operation/handler/SendTaskOperationHandler.java
@@ -73,8 +73,8 @@
                        continue;
                    }
                    if (staProtocol.isAutoing()) {
                        Job job = jobService.getJobByJobNo(staProtocol.getWorkNo());
                        if (job != null && job.getJobSts() == ConveyorStateType.INBOUND.getStatus()) {
                        Job job = jobService.getJobByJobNoAndJobSts(staProtocol.getWorkNo(),ConveyorStateType.INBOUND.getStatus());
                        if (job != null  ) {
                            if (ctuMainService.sendTask(process(job))) {
                                job.setJobSts(ConveyorStateType.SENDTASK.getStatus());
                                job.setRcsTime(new Date());
zy-acs-cv/src/main/resources/application.yml
@@ -10,7 +10,7 @@
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    #url: jdbc:mysql://127.0.0.1:3306/rcs_ctu_stable?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    url: jdbc:mysql://192.168.133.173:3306/cv?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    url: jdbc:mysql://127.0.0.1:3306/cv?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: xltys1995
  mvc:
@@ -18,7 +18,7 @@
logging:
  file:
    path: stock/out/cv/logs
    path: /stock/out/cv/logs
wms:
  url: 10.10.10.220:8081
zy-acs-cv/src/main/resources/mapper/JobMapper.xml
@@ -61,6 +61,7 @@
        select *
        from cv_job
        where barcode = #{barcode}
          and job_sts = #{jobSts}
        order by id desc limit 1
    </select>
zy-acs-cv/src/main/webapp/views/pipeline.html
@@ -841,6 +841,7 @@
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-primary" id="save">保存</button>
                    <button type="button" class="btn btn-secondary" id="clear">清除</button>
                    <button type="button" class="btn btn-secondary" id="cancel">取消</button>
                </div>
            </div>
@@ -1090,6 +1091,12 @@
            });
        });
        // 清除任务号和目标站点
        $(document).on('click', '#clear', function () {
            $('#workNo').val(0);
            $('#staNo').val(0);
        });
        // 取消站点信息修改
        $(document).on('click', '#cancel', function () {
            closeModal();
zy-acs-hex/dashboard.html
New file
@@ -0,0 +1,392 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>机器人数据监控</title>
    <!-- 引入 layui-vue 样式 -->
    <link href="https://cdn.jsdelivr.net/npm/layui-vue@2.1.0/dist/index.css" rel="stylesheet">
    <!-- 引入 Vue 3 -->
    <script src="https://cdn.jsdelivr.net/npm/vue@3.3.4/dist/vue.global.min.js"></script>
    <!-- 引入 layui-vue -->
    <script src="https://cdn.jsdelivr.net/npm/layui-vue@2.1.0/dist/index.js"></script>
    <!-- 引入 Chart.js -->
    <script src="/static/js/chart.umd.min.js"></script>
    <!-- 引入 Font Awesome -->
    <link href="/static/css/font-awesome.min.css" rel="stylesheet">
    <style>
        body {
            font-family: 'Inter', sans-serif;
            background-color: #f5f7fa;
        }
        .layui-card {
            margin-bottom: 16px;
        }
        .layui-card-header {
            font-weight: 600;
        }
        .status-online {
            color: #10b981;
        }
        .status-offline {
            color: #ef4444;
        }
        .chart-container {
            height: 300px;
        }
        .realtime-container {
            height: 160px;
            overflow-y: auto;
        }
    </style>
</head>
<body>
    <div id="app">
        <!-- 顶部导航栏 -->
        <lay-header height="60px" bg-color="#fff" shadow>
            <template #left>
                <div class="flex items-center space-x-2">
                    <i class="fa fa-android text-2xl" style="color: #3b82f6"></i>
                    <h1 class="text-xl font-bold" style="color: #1e293b">机器人数据监控</h1>
                </div>
            </template>
            <template #right>
                <div class="flex items-center space-x-4">
                    <lay-input placeholder="搜索机器人..." prefix-icon="search" style="width: 200px"></lay-input>
                    <lay-button type="primary" @click="refreshData">
                        <i class="fa fa-refresh mr-1"></i> 刷新
                    </lay-button>
                </div>
            </template>
        </lay-header>
        <!-- 主内容区 -->
        <lay-container style="padding: 20px">
            <!-- 状态概览 -->
            <lay-row :gutter="16">
                <lay-col :span="6">
                    <lay-card shadow>
                        <div class="flex items-center justify-between">
                            <div>
                                <p style="color: #64748b; font-size: 14px">总机器人数</p>
                                <h3 style="font-size: 24px; font-weight: bold; color: #1e293b">24</h3>
                            </div>
                            <div style="width: 48px; height: 48px; border-radius: 50%; background-color: #dbeafe; display: flex; align-items: center; justify-content: center">
                                <i class="fa fa-microchip text-xl" style="color: #3b82f6"></i>
                            </div>
                        </div>
                    </lay-card>
                </lay-col>
                <lay-col :span="6">
                    <lay-card shadow>
                        <div class="flex items-center justify-between">
                            <div>
                                <p style="color: #64748b; font-size: 14px">在线机器人</p>
                                <h3 style="font-size: 24px; font-weight: bold; color: #10b981">18</h3>
                            </div>
                            <div style="width: 48px; height: 48px; border-radius: 50%; background-color: #d1fae5; display: flex; align-items: center; justify-content: center">
                                <i class="fa fa-check-circle text-xl" style="color: #10b981"></i>
                            </div>
                        </div>
                    </lay-card>
                </lay-col>
                <lay-col :span="6">
                    <lay-card shadow>
                        <div class="flex items-center justify-between">
                            <div>
                                <p style="color: #64748b; font-size: 14px">离线机器人</p>
                                <h3 style="font-size: 24px; font-weight: bold; color: #ef4444">6</h3>
                            </div>
                            <div style="width: 48px; height: 48px; border-radius: 50%; background-color: #fee2e2; display: flex; align-items: center; justify-content: center">
                                <i class="fa fa-exclamation-circle text-xl" style="color: #ef4444"></i>
                            </div>
                        </div>
                    </lay-card>
                </lay-col>
                <lay-col :span="6">
                    <lay-card shadow>
                        <div class="flex items-center justify-between">
                            <div>
                                <p style="color: #64748b; font-size: 14px">今日数据量</p>
                                <h3 style="font-size: 24px; font-weight: bold; color: #f59e0b">1.2k</h3>
                            </div>
                            <div style="width: 48px; height: 48px; border-radius: 50%; background-color: #fef3c7; display: flex; align-items: center; justify-content: center">
                                <i class="fa fa-database text-xl" style="color: #f59e0b"></i>
                            </div>
                        </div>
                    </lay-card>
                </lay-col>
            </lay-row>
            <!-- 数据图表 -->
            <lay-row :gutter="16" style="margin-top: 16px">
                <lay-col :span="12">
                    <lay-card shadow>
                        <template #header>
                            <div class="flex justify-between items-center">
                                <h2 style="font-size: 16px; font-weight: 600; color: #1e293b">上行数据趋势</h2>
                                <div class="flex space-x-2">
                                    <lay-button size="sm" type="primary">小时</lay-button>
                                    <lay-button size="sm">天</lay-button>
                                    <lay-button size="sm">周</lay-button>
                                </div>
                            </div>
                        </template>
                        <div class="chart-container">
                            <canvas ref="upDataChart"></canvas>
                        </div>
                    </lay-card>
                </lay-col>
                <lay-col :span="12">
                    <lay-card shadow>
                        <template #header>
                            <div class="flex justify-between items-center">
                                <h2 style="font-size: 16px; font-weight: 600; color: #1e293b">下行数据趋势</h2>
                                <div class="flex space-x-2">
                                    <lay-button size="sm" type="primary">小时</lay-button>
                                    <lay-button size="sm">天</lay-button>
                                    <lay-button size="sm">周</lay-button>
                                </div>
                            </div>
                        </template>
                        <div class="chart-container">
                            <canvas ref="downDataChart"></canvas>
                        </div>
                    </lay-card>
                </lay-col>
            </lay-row>
            <!-- 设备数据表格 -->
            <lay-card shadow style="margin-top: 16px">
                <template #header>
                    <div class="flex justify-between items-center">
                        <h2 style="font-size: 16px; font-weight: 600; color: #1e293b">机器人数据列表</h2>
                        <div class="flex space-x-2">
                            <lay-button size="sm">
                                <i class="fa fa-filter mr-1"></i> 筛选
                            </lay-button>
                            <lay-button size="sm">
                                <i class="fa fa-download mr-1"></i> 导出
                            </lay-button>
                        </div>
                    </div>
                </template>
                <lay-table :data="devices" :height="400">
                    <lay-table-column prop="id" label="设备ID" width="120"></lay-table-column>
                    <lay-table-column prop="name" label="设备名称" width="150"></lay-table-column>
                    <lay-table-column prop="status" label="状态" width="100">
                        <template #default="{ row }">
                            <lay-badge v-if="row.status === 'online'" type="success">在线</lay-badge>
                            <lay-badge v-else type="danger">离线</lay-badge>
                        </template>
                    </lay-table-column>
                    <lay-table-column prop="upData" label="上行数据" width="120"></lay-table-column>
                    <lay-table-column prop="downData" label="下行数据" width="120"></lay-table-column>
                    <lay-table-column prop="lastComm" label="最后通信" width="150"></lay-table-column>
                    <lay-table-column label="操作" width="150">
                        <template #default="{ row }">
                            <lay-button size="sm" type="primary" style="margin-right: 8px">
                                <i class="fa fa-eye"></i>
                            </lay-button>
                            <lay-button size="sm" type="warning" style="margin-right: 8px">
                                <i class="fa fa-edit"></i>
                            </lay-button>
                            <lay-button size="sm" type="danger">
                                <i class="fa fa-trash"></i>
                            </lay-button>
                        </template>
                    </lay-table-column>
                </lay-table>
                <div class="flex justify-between items-center mt-4">
                    <p style="color: #64748b; font-size: 14px">显示 1-10 条,共 24 条</p>
                    <lay-pagination
                        v-model:current="currentPage"
                        v-model:limit="pageSize"
                        :total="total"
                        :limits="[10, 20, 50, 100]"
                        layout="prev, pager, next, jumper, sizes, total"
                    ></lay-pagination>
                </div>
            </lay-card>
            <!-- 实时数据更新 -->
            <lay-card shadow style="margin-top: 16px">
                <template #header>
                    <h2 style="font-size: 16px; font-weight: 600; color: #1e293b">实时数据更新</h2>
                </template>
                <div class="realtime-container p-2 border border-gray-200 rounded-lg">
                    <div v-for="(item, index) in realtimeData" :key="index" class="py-1 border-b border-gray-100">
                        <span style="color: #64748b; font-size: 12px">{{ item.timestamp }}</span>
                        <span style="color: #1e293b; margin-left: 10px">{{ item.message }}</span>
                    </div>
                </div>
            </lay-card>
        </lay-container>
        <!-- 页脚 -->
        <lay-footer height="60px" bg-color="#fff" shadow>
            <div class="text-center" style="color: #64748b; font-size: 14px">
                © 2026 机器人数据监控系统 | 版本 1.0.0
            </div>
        </lay-footer>
    </div>
    <script>
        const { createApp, ref, onMounted } = Vue;
        const app = createApp({
            components: {
                LayHeader: layui.LayHeader,
                LayContainer: layui.LayContainer,
                LayRow: layui.LayRow,
                LayCol: layui.LayCol,
                LayCard: layui.LayCard,
                LayInput: layui.LayInput,
                LayButton: layui.LayButton,
                LayTable: layui.LayTable,
                LayTableColumn: layui.LayTableColumn,
                LayBadge: layui.LayBadge,
                LayPagination: layui.LayPagination,
                LayFooter: layui.LayFooter
            },
            setup() {
                // 模拟机器人数据
                const devices = ref([
                    { id: 'ROB-001', name: '配送机器人1号', status: 'online', upData: '2.4KB', downData: '0.8KB', lastComm: '2分钟前' },
                    { id: 'ROB-002', name: '配送机器人2号', status: 'online', upData: '1.8KB', downData: '0.5KB', lastComm: '5分钟前' },
                    { id: 'ROB-003', name: '巡检机器人1号', status: 'offline', upData: '0KB', downData: '0KB', lastComm: '2小时前' },
                    { id: 'ROB-004', name: '配送机器人3号', status: 'online', upData: '3.2KB', downData: '1.2KB', lastComm: '1分钟前' },
                    { id: 'ROB-005', name: '巡检机器人2号', status: 'online', upData: '1.5KB', downData: '0.6KB', lastComm: '3分钟前' },
                    { id: 'ROB-006', name: '配送机器人4号', status: 'offline', upData: '0KB', downData: '0KB', lastComm: '5小时前' },
                    { id: 'ROB-007', name: '巡检机器人3号', status: 'online', upData: '2.1KB', downData: '0.9KB', lastComm: '4分钟前' },
                    { id: 'ROB-008', name: '配送机器人5号', status: 'online', upData: '2.8KB', downData: '1.1KB', lastComm: '2分钟前' },
                    { id: 'ROB-009', name: '巡检机器人4号', status: 'offline', upData: '0KB', downData: '0KB', lastComm: '1天前' },
                    { id: 'ROB-010', name: '配送机器人6号', status: 'online', upData: '1.9KB', downData: '0.7KB', lastComm: '6分钟前' }
                ]);
                // 分页数据
                const currentPage = ref(1);
                const pageSize = ref(10);
                const total = ref(24);
                // 实时数据
                const realtimeData = ref([]);
                // 图表引用
                const upDataChart = ref(null);
                const downDataChart = ref(null);
                // 刷新数据
                const refreshData = () => {
                    console.log('刷新数据');
                    // 这里可以添加实际的刷新逻辑
                };
                // 初始化图表
                const initCharts = () => {
                    // 上行数据图表
                    const upCtx = upDataChart.value.getContext('2d');
                    new Chart(upCtx, {
                        type: 'line',
                        data: {
                            labels: ['00:00', '03:00', '06:00', '09:00', '12:00', '15:00', '18:00', '21:00'],
                            datasets: [{
                                label: '上行数据 (KB)',
                                data: [12, 19, 15, 25, 22, 30, 28, 35],
                                borderColor: '#3b82f6',
                                backgroundColor: 'rgba(59, 130, 246, 0.1)',
                                tension: 0.4,
                                fill: true
                            }]
                        },
                        options: {
                            responsive: true,
                            maintainAspectRatio: false,
                            plugins: {
                                legend: {
                                    display: false
                                }
                            },
                            scales: {
                                y: {
                                    beginAtZero: true
                                }
                            }
                        }
                    });
                    // 下行数据图表
                    const downCtx = downDataChart.value.getContext('2d');
                    new Chart(downCtx, {
                        type: 'line',
                        data: {
                            labels: ['00:00', '03:00', '06:00', '09:00', '12:00', '15:00', '18:00', '21:00'],
                            datasets: [{
                                label: '下行数据 (KB)',
                                data: [5, 8, 6, 12, 10, 15, 13, 18],
                                borderColor: '#10b981',
                                backgroundColor: 'rgba(16, 185, 129, 0.1)',
                                tension: 0.4,
                                fill: true
                            }]
                        },
                        options: {
                            responsive: true,
                            maintainAspectRatio: false,
                            plugins: {
                                legend: {
                                    display: false
                                }
                            },
                            scales: {
                                y: {
                                    beginAtZero: true
                                }
                            }
                        }
                    });
                };
                // 模拟实时数据更新
                const simulateRealtimeData = () => {
                    const messages = [
                        'ROB-001 配送机器人1号: 运行中,位置: A1区',
                        'ROB-002 配送机器人2号: 待机中,位置: B2区',
                        'ROB-004 配送机器人3号: 充电中,电量: 85%',
                        'ROB-005 巡检机器人2号: 巡检中,已完成3/5任务',
                        'ROB-007 巡检机器人3号: 待机中,位置: C3区',
                        'ROB-008 配送机器人5号: 运行中,位置: D4区'
                    ];
                    setInterval(() => {
                        const message = messages[Math.floor(Math.random() * messages.length)];
                        const timestamp = new Date().toLocaleTimeString();
                        realtimeData.value.unshift({ timestamp, message });
                        // 限制显示条数
                        if (realtimeData.value.length > 20) {
                            realtimeData.value.pop();
                        }
                    }, 2000);
                };
                // 页面加载完成后初始化
                onMounted(() => {
                    initCharts();
                    simulateRealtimeData();
                });
                return {
                    devices,
                    currentPage,
                    pageSize,
                    total,
                    realtimeData,
                    upDataChart,
                    downDataChart,
                    refreshData
                };
            }
        });
        app.mount('#app');
    </script>
</body>
</html>
zy-acs-hex/pom.xml
@@ -10,6 +10,8 @@
    </parent>
    <artifactId>zy-acs-hex</artifactId>
    <version>1.0.0</version>
    <packaging>war</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -64,6 +66,12 @@
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>4.12.0</version>
        </dependency>
    </dependencies>
    <build>
zy-acs-hex/src/main/java/com/zy/acs/hex/HexApplication.java
@@ -3,14 +3,19 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling;
@EnableScheduling
@ComponentScan(basePackages = {"com.zy.component", "com.zy.acs"})
@SpringBootApplication
public class HexApplication {
public class HexApplication extends SpringBootServletInitializer {
    public static void main(String[] args) {
        SpringApplication.run(HexApplication.class, args);
    }
}
zy-acs-hex/src/main/java/com/zy/acs/hex/config/WebMvcConfig.java
New file
@@ -0,0 +1,69 @@
package com.zy.acs.hex.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.AsyncHandlerInterceptor;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * WebMvc配置, 拦截器、资源映射等都在此配置
 *
 * @author vincent
 * @since 2019-06-12 10:11:16
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    /**
     * token通过header传递的名称
     */
    public static final String TOKEN_HEADER_NAME = "Authorization";
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(getAsyncHandlerInterceptor())
                .addPathPatterns("/**")
        ;
    }
    @Bean
    public AsyncHandlerInterceptor getAsyncHandlerInterceptor() {
        return new AsyncHandlerInterceptor(){
            @Override
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
               cors(response);
                return true;
            }
        };
    }
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 配置静态资源处理器
        registry.addResourceHandler("/static/**")
                .addResourceLocations("/static/");
        // 配置视图文件处理器
        registry.addResourceHandler("/views/**")
                .addResourceLocations("/views/");
    }
    public static void cors(HttpServletResponse response){
        // 跨域设置
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "*");
        response.setHeader("Access-Control-Allow-Headers", "*");
        response.setHeader("Access-Control-Expose-Headers", TOKEN_HEADER_NAME);
    }
}
zy-acs-hex/src/main/java/com/zy/acs/hex/controller/DeviceLogController.java
New file
@@ -0,0 +1,37 @@
package com.zy.acs.hex.controller;
import com.zy.acs.common.domain.mq.DeviceMessage;
import com.zy.acs.framework.common.R;
import com.zy.component.influxdb.service.InfluxDBService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@Slf4j
@RequestMapping(value = "/deviceLog")
public class DeviceLogController {
    @Autowired
    private InfluxDBService influxDBService;
    /**
     * 查询最新的十条数据
     *
     * @return
     */
    @GetMapping(value = "/query")
    @ResponseBody
    public R query() {
        List<DeviceMessage> deviceMessages = influxDBService.queryPoints("select * from device order by time desc limit 10", DeviceMessage.class);
        return R.ok(deviceMessages);
    }
}
zy-acs-hex/src/main/java/com/zy/acs/hex/controller/RouterController.java
New file
@@ -0,0 +1,37 @@
package com.zy.acs.hex.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletResponse;
/**
 * Created by vincent on 2019-07-30
 */
@Controller
public class RouterController {
    @RequestMapping("/")
    public void index(HttpServletResponse response) {
        try {
            response.sendRedirect(  "/views/index.html");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    @RequestMapping("/login")
    public void login(HttpServletResponse response) {
        try {
            response.sendRedirect(  "/views/login.html");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}
zy-acs-hex/src/main/java/com/zy/acs/hex/controller/TestController.java
@@ -1,7 +1,7 @@
package com.zy.acs.hex.controller;
import com.zy.acs.common.domain.mq.DeviceMessage;
import com.zy.acs.hex.constant.RabbitConstant;
import com.zy.acs.hex.domain.Device;
import com.zy.component.influxdb.service.InfluxDBService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
@@ -31,10 +31,9 @@
     */
    @GetMapping(value = "/test1")
    public void sendTest1() {
        Device device = new Device();
        DeviceMessage device = new DeviceMessage("121212121212");
        //device.setEvent("online");
        //device.setDeviceId("123");
        device.setProtocol("212121212121212");
        String router = RabbitConstant.ROUTING_KEY_UP.replaceFirst("\\*", "123").replaceFirst("\\*", "online");
        rabbitTemplate.convertAndSend(RabbitConstant.TOPIC_EXCHANGE, router, device);
    }
@@ -69,7 +68,7 @@
    @GetMapping(value = "/query2")
    @ResponseBody
    public Object queryTest2() {
        return influxDBService.queryPoints("select * from device order by time desc limit 10", Device.class);
        return influxDBService.queryPoints("select * from device order by time desc limit 10", DeviceMessage.class);
    }
}
zy-acs-hex/src/main/java/com/zy/acs/hex/domain/Device.java
File was deleted
zy-acs-hex/src/main/java/com/zy/acs/hex/influxdb/task/InfluxDbScheduler.java
New file
@@ -0,0 +1,85 @@
package com.zy.acs.hex.influxdb.task;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.zy.acs.hex.utils.HttpGo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Component
public class InfluxDbScheduler {
    @Value("${influxdb3.createDatabaseUrl}")
    private String createDatabaseUrl;
    @Value("${influxdb3.database}")
    private String databaseName;
    @Value("${influxdb3.token}")
    private String token;
    @Value("${influxdb3.retention-period}")
    private String retentionPeriod;
    private static Long timeoutSeconds = 30L;
    private HttpGo http;
    @PostConstruct
    public void init() {
        this.http = HttpGo.builder()
                .connectTimeout(Duration.ofSeconds(timeoutSeconds))
                .readTimeout(Duration.ofSeconds(timeoutSeconds))
                .build();
        createDatabase();
    }
    public void createDatabase() {
        // headers
        Map<String, String> headers = new HashMap<>();
        headers.put("Authorization", "Bearer " + token);
        headers.put("Content-Type", "application/json;charset=UTF-8");
        try {
            HttpGo.HttpResponse response = this.http.get(createDatabaseUrl + "?format=json", headers, null);
            if (!isExist(response.body())) {
                Map<String, String> parames = new HashMap<>();
                parames.put("db", databaseName);
                parames.put("retention-period", retentionPeriod);
                HttpGo.HttpResponse postResponse = this.http.postJson(createDatabaseUrl, headers, JSON.toJSONString(parames));
                log.info("是否创建数据库:{}", postResponse);
            }else {
                log.info("数据库:{}", response.body());
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    private boolean isExist(String databases) {
        JSONArray objects = JSON.parseArray(databases);
        for (Object object : objects) {
            JSONObject obj = (JSONObject) object;
            if (obj.getString("iox::database").equals(databaseName)) {
                return true;
            }
        }
        return false;
    }
}
zy-acs-hex/src/main/java/com/zy/acs/hex/utils/HttpGo.java
New file
@@ -0,0 +1,322 @@
package com.zy.acs.hex.utils;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
 * Minimal OkHttp wrapper: GET / POST only.
 *
 * - fluent API (get / postJson / postForm)
 * - default singleton instance (thread-safe)
 * - per-request headers + default headers
 * - simple response wrapper with tookMs
 * - optional trust-all SSL (ONLY for internal test)
 */
@Slf4j
public final class HttpGo {
    private static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
    private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
    private final OkHttpClient client;
    private final Map<String, String> defaultHeaders;
    private HttpGo(OkHttpClient client, Map<String, String> defaultHeaders) {
        this.client = Objects.requireNonNull(client, "client");
        this.defaultHeaders = defaultHeaders == null
                ? Collections.emptyMap()
                : Collections.unmodifiableMap(new LinkedHashMap<>(defaultHeaders));
    }
    /** Shared default instance (safe SSL by default). */
    public static HttpGo defaults() {
        return Holder.DEFAULT;
    }
    public static Builder builder() {
        return new Builder();
    }
    // ===================== GET =====================
    public HttpResponse get(String url) throws IOException {
        return get(url, null, null);
    }
    public HttpResponse get(String url, Map<String, String> queryParams) throws IOException {
        return get(url, null, queryParams);
    }
    public HttpResponse get(String url, Map<String, String> headers, Map<String, String> queryParams) throws IOException {
        HttpUrl parsed = HttpUrl.parse(url);
        if (parsed == null) throw new IllegalArgumentException("Invalid url: " + url);
        HttpUrl.Builder ub = parsed.newBuilder();
        if (queryParams != null) {
            queryParams.forEach((k, v) -> {
                if (k != null && v != null) ub.addQueryParameter(k, v);
            });
        }
        Request.Builder rb = new Request.Builder().url(ub.build()).get();
        applyHeaders(rb, headers);
        return execute(rb.build());
    }
    // ===================== POST =====================
    /** POST JSON string payload (null/blank -> "{}"). */
    public HttpResponse postJson(String url, String json) throws IOException {
        return postJson(url, null, json);
    }
    /** POST JSON string payload (null/blank -> "{}"). */
    public HttpResponse postJson(String url, Map<String, String> headers, String json) throws IOException {
        String payload = (json == null || json.trim().isEmpty()) ? "{}" : json;
        RequestBody body = RequestBody.create(payload, JSON);
        Request.Builder rb = new Request.Builder().url(url).post(body);
        applyHeaders(rb, headers);
        // ensure content-type unless caller overrides
        if (rb.build().header("Content-Type") == null) {
            rb.header("Content-Type", "application/json; charset=utf-8");
        }
        return execute(rb.build());
    }
    /** POST x-www-form-urlencoded fields. */
    public HttpResponse postForm(String url, Map<String, String> formFields) throws IOException {
        return postForm(url, null, formFields);
    }
    /** POST x-www-form-urlencoded fields. */
    public HttpResponse postForm(String url, Map<String, String> headers, Map<String, String> formFields) throws IOException {
        FormBody.Builder fb = new FormBody.Builder(DEFAULT_CHARSET);
        if (formFields != null) {
            formFields.forEach((k, v) -> {
                if (k != null && v != null) fb.add(k, v);
            });
        }
        Request.Builder rb = new Request.Builder().url(url).post(fb.build());
        applyHeaders(rb, headers);
        return execute(rb.build());
    }
    // ===================== Internals =====================
    private HttpResponse execute(Request request) throws IOException {
        long start = System.nanoTime();
        try (Response resp = client.newCall(request).execute()) {
            long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
            String body = null;
            ResponseBody rb = resp.body();
            if (rb != null) body = rb.string(); // one-shot
            return new HttpResponse(resp.code(), resp.headers(), body, tookMs);
        } catch (IOException e) {
            log.error("HttpGo request failed: {} {}", request.method(), request.url(), e);
            throw e;
        }
    }
    private void applyHeaders(Request.Builder rb, Map<String, String> headers) {
        // defaults first, then per-request overrides
        if (defaultHeaders != null) {
            defaultHeaders.forEach((k, v) -> {
                if (k != null && v != null) rb.header(k, v);
            });
        }
        if (headers != null) {
            headers.forEach((k, v) -> {
                if (k != null && v != null) rb.header(k, v);
            });
        }
    }
    private static final class Holder {
        private static final HttpGo DEFAULT = HttpGo.builder().build();
    }
    // ===================== Response =====================
    public static final class HttpResponse {
        private final int statusCode;
        private final Headers headers;
        private final String body;
        private final long tookMs;
        public HttpResponse(int statusCode, Headers headers, String body, long tookMs) {
            this.statusCode = statusCode;
            this.headers = headers == null ? new Headers.Builder().build() : headers;
            this.body = body;
            this.tookMs = tookMs;
        }
        public int statusCode() { return statusCode; }
        public Headers headers() { return headers; }
        public String body() { return body; }
        public long tookMs() { return tookMs; }
        public boolean is2xx() {
            return statusCode >= 200 && statusCode < 300;
        }
        public String header(String name) {
            return headers.get(name);
        }
        @Override
        public String toString() {
            return "HttpResponse{status=" + statusCode + ", tookMs=" + tookMs
                    + ", bodyLen=" + (body == null ? 0 : body.length()) + "}";
        }
    }
    // ===================== Builder =====================
    public static final class Builder {
        private Duration connectTimeout = Duration.ofSeconds(10);
        private Duration readTimeout = Duration.ofSeconds(20);
        private Duration writeTimeout = Duration.ofSeconds(20);
        private boolean trustAllSsl = false;
        private final Map<String, String> defaultHeaders = new LinkedHashMap<>();
        public Builder defaultHeader(String name, String value) {
            if (name != null && value != null) defaultHeaders.put(name, value);
            return this;
        }
        public Builder connectTimeout(Duration d) {
            if (d != null) connectTimeout = d;
            return this;
        }
        public Builder readTimeout(Duration d) {
            if (d != null) readTimeout = d;
            return this;
        }
        public Builder writeTimeout(Duration d) {
            if (d != null) writeTimeout = d;
            return this;
        }
        /** Trust ALL certificates. ONLY for internal testing/self-signed endpoints. */
        public Builder trustAllSsl(boolean enable) {
            this.trustAllSsl = enable;
            return this;
        }
        public HttpGo build() {
            OkHttpClient.Builder cb = new OkHttpClient.Builder()
                    .connectTimeout(connectTimeout.toMillis(), TimeUnit.MILLISECONDS)
                    .readTimeout(readTimeout.toMillis(), TimeUnit.MILLISECONDS)
                    .writeTimeout(writeTimeout.toMillis(), TimeUnit.MILLISECONDS);
            if (trustAllSsl) {
                TrustAll trustAll = new TrustAll();
                cb.sslSocketFactory(trustAll.sslSocketFactory, trustAll.trustManager)
                        .hostnameVerifier((hostname, session) -> true);
            }
            return new HttpGo(cb.build(), defaultHeaders);
        }
    }
    // ===================== Trust-all SSL helper =====================
    private static final class TrustAll {
        final X509TrustManager trustManager;
        final SSLSocketFactory sslSocketFactory;
        TrustAll() {
            try {
                this.trustManager = new X509TrustManager() {
                    @Override public void checkClientTrusted(X509Certificate[] chain, String authType) { }
                    @Override public void checkServerTrusted(X509Certificate[] chain, String authType) { }
                    @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
                };
                SSLContext sslContext = SSLContext.getInstance("TLS");
                sslContext.init(null, new TrustManager[]{trustManager}, new SecureRandom());
                this.sslSocketFactory = sslContext.getSocketFactory();
            } catch (Exception e) {
                throw new IllegalStateException("Failed to init trust-all SSL", e);
            }
        }
    }
    // ======================== tools ========================
    public String buildUrl(String host, Integer port, String path) {
        return buildUrl(host, port, path, false);
    }
    public String buildUrl(String host, Integer port, String path, boolean ssl) {
        String p = (path == null) ? "" : (path.startsWith("/") ? path : ("/" + path));
        return (ssl ? "https" : "http") + "://" + host + ":" + port + p;
    }
    // ===================== Demo (main) =====================
    public static void main(String[] args) throws Exception {
        HttpGo http = HttpGo.builder()
                .connectTimeout(Duration.ofSeconds(8))
                .readTimeout(Duration.ofSeconds(15))
                .defaultHeader("User-Agent", "HttpGo/1.0")
                // .trustAllSsl(true) // ONLY if you really need it
                .build();
        // 1) GET with query params + per-request headers
        String getUrl = "https://httpbin.org/get";
        Map<String, String> query = new HashMap<>();
        query.put("q", "vincent");
        query.put("page", "1");
        Map<String, String> headers = new HashMap<>();
        headers.put("X-Trace-Id", "trace-001");
        HttpResponse r1 = http.get(getUrl, headers, query);
        System.out.println("GET status=" + r1.statusCode() + ", tookMs=" + r1.tookMs());
        System.out.println(r1.body());
        // 2) POST JSON
        String postUrl = "https://httpbin.org/post";
        String json = "{\"name\":\"Vincent\",\"role\":\"engineer\"}";
        Map<String, String> postHeaders = new HashMap<>();
        postHeaders.put("X-Trace-Id", "trace-002");
        HttpResponse r2 = http.postJson(postUrl, postHeaders, json);
        System.out.println("POST(JSON) status=" + r2.statusCode() + ", tookMs=" + r2.tookMs());
        System.out.println(r2.body());
        // 3) POST Form
        Map<String, String> form = new HashMap<>();
        form.put("username", "vincent");
        form.put("password", "123456");
        HttpResponse r3 = http.postForm(postUrl, null, form);
        System.out.println("POST(Form) status=" + r3.statusCode() + ", tookMs=" + r3.tookMs());
        System.out.println(r3.body());
    }
}
zy-acs-hex/src/main/resources/application.yml
@@ -5,7 +5,7 @@
    name: rcs-hex
  # RabbitMQ配置
  rabbitmq:
    host: 192.168.133.173
    host: localhost
    port: 5672
    username: root
    password: xltys1995
@@ -26,7 +26,10 @@
#  --add-opens java.base/java.nio=ALL-UNNAMED
influxdb3:
  enabled: true
  url: http://192.168.133.173:8181
  #token: apiv3_Jx1SvmBMV_kikGhc4eZJQbeGmNYN7KX1GdpoR9MClkKzMxSJ0MPKM_O2Xt3o1hVyRikMmlxZ_h9zfy6ybC5Idg
  url: http://localhost:8181
  token: apiv3_Jx1SvmBMV_kikGhc4eZJQbeGmNYN7KX1GdpoR9MClkKzMxSJ0MPKM_O2Xt3o1hVyRikMmlxZ_h9zfy6ybC5Idg
  database: rcs
  token: apiv3_116RKycNhxbf62Nys4zthC05aRD-aidzhEpEpLtsFuedhJTaCtVklNrzHs9LHxBWMuzDclBHVgToGoQuWGiIIA
  # 虚拟机的token
  #token: apiv3_116RKycNhxbf62Nys4zthC05aRD-aidzhEpEpLtsFuedhJTaCtVklNrzHs9LHxBWMuzDclBHVgToGoQuWGiIIA
  retention-period: 30d
  createDatabaseUrl: ${influxdb3.url}/api/v3/configure/database
zy-acs-hex/src/main/webapp/views/index.html
New file
@@ -0,0 +1,165 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>机器人日志系统</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body {
            font-family: Arial, sans-serif;
            background-color: #f0f2f5;
        }
        .header {
            background-color: #1890ff;
            color: white;
            padding: 15px 20px;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
        .header h1 {
            font-size: 20px;
        }
        .header button {
            background-color: transparent;
            color: white;
            border: 1px solid white;
            padding: 5px 15px;
            border-radius: 4px;
            cursor: pointer;
        }
        .header button:hover {
            background-color: rgba(255,255,255,0.1);
        }
        .container {
            padding: 20px;
        }
        .refresh-btn {
            background-color: #1890ff;
            color: white;
            border: none;
            padding: 8px 16px;
            border-radius: 4px;
            cursor: pointer;
            margin-bottom: 20px;
        }
        .refresh-btn:hover {
            background-color: #40a9ff;
        }
        table {
            width: 100%;
            border-collapse: collapse;
            background-color: white;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        th, td {
            padding: 12px;
            text-align: left;
            border-bottom: 1px solid #ddd;
        }
        th {
            background-color: #f5f5f5;
            font-weight: bold;
        }
        tr:hover {
            background-color: #f5f5f5;
        }
        .loading {
            text-align: center;
            padding: 20px;
            color: #666;
        }
        .error {
            text-align: center;
            padding: 20px;
            color: red;
        }
    </style>
</head>
<body>
    <div class="header">
        <h1>机器人上下行日志</h1>
        <button id="logoutBtn">登出</button>
    </div>
    <div class="container">
        <button class="refresh-btn" id="refreshBtn">刷新数据</button>
        <div id="loading" class="loading">加载中...</div>
        <div id="error" class="error" style="display: none;"></div>
        <table id="logTable" style="display: none;">
            <thead>
                <tr>
                    <th>时间</th>
                    <th>设备ID</th>
                    <th>消息类型</th>
                    <th>消息内容</th>
                </tr>
            </thead>
            <tbody id="logTableBody">
            </tbody>
        </table>
    </div>
    <script>
        // 检查登录状态
        function checkLogin() {
            if (!localStorage.getItem('loggedIn')) {
                window.location.href = '/login';
            }
        }
        // 登出功能
        document.getElementById('logoutBtn').addEventListener('click', function() {
            localStorage.removeItem('loggedIn');
            window.location.href = '/login';
        });
        // 加载日志数据
        function loadLogData() {
            document.getElementById('loading').style.display = 'block';
            document.getElementById('error').style.display = 'none';
            document.getElementById('logTable').style.display = 'none';
            fetch('/deviceLog/query')
                .then(response => response.json())
                .then(data => {
                    document.getElementById('loading').style.display = 'none';
                    if (data && data.length > 0) {
                        document.getElementById('logTable').style.display = 'table';
                        const tbody = document.getElementById('logTableBody');
                        tbody.innerHTML = '';
                        data.forEach(item => {
                            const row = document.createElement('tr');
                            row.innerHTML = `
                                <td>${item.time || '-'}</td>
                                <td>${item.deviceId || '-'}</td>
                                <td>${item.messageType || '-'}</td>
                                <td>${item.messageContent || '-'}</td>
                            `;
                            tbody.appendChild(row);
                        });
                    } else {
                        document.getElementById('error').textContent = '暂无日志数据';
                        document.getElementById('error').style.display = 'block';
                    }
                })
                .catch(error => {
                    document.getElementById('loading').style.display = 'none';
                    document.getElementById('error').textContent = '加载数据失败: ' + error.message;
                    document.getElementById('error').style.display = 'block';
                });
        }
        // 刷新按钮点击事件
        document.getElementById('refreshBtn').addEventListener('click', loadLogData);
        // 页面加载时检查登录状态并加载数据
        checkLogin();
        loadLogData();
    </script>
</body>
</html>
zy-acs-hex/src/main/webapp/views/login.html
New file
@@ -0,0 +1,104 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>登录 - 机器人日志系统</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body {
            font-family: Arial, sans-serif;
            background-color: #f0f2f5;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
        }
        .login-container {
            background-color: white;
            padding: 40px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            width: 100%;
            max-width: 400px;
        }
        h2 {
            text-align: center;
            margin-bottom: 30px;
            color: #333;
        }
        .form-group {
            margin-bottom: 20px;
        }
        label {
            display: block;
            margin-bottom: 8px;
            color: #666;
        }
        input {
            width: 100%;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-size: 16px;
        }
        button {
            width: 100%;
            padding: 12px;
            background-color: #1890ff;
            color: white;
            border: none;
            border-radius: 4px;
            font-size: 16px;
            cursor: pointer;
            margin-top: 10px;
        }
        button:hover {
            background-color: #40a9ff;
        }
        .error-message {
            color: red;
            margin-top: 10px;
            text-align: center;
        }
    </style>
</head>
<body>
    <div class="login-container">
        <h2>机器人日志系统</h2>
        <form id="loginForm">
            <div class="form-group">
                <label for="username">用户名</label>
                <input type="text" id="username" name="username" required>
            </div>
            <div class="form-group">
                <label for="password">密码</label>
                <input type="password" id="password" name="password" required>
            </div>
            <button type="submit">登录</button>
            <div id="errorMessage" class="error-message"></div>
        </form>
    </div>
    <script>
        document.getElementById('loginForm').addEventListener('submit', function(e) {
            e.preventDefault();
            const username = document.getElementById('username').value;
            const password = document.getElementById('password').value;
            // 简单的登录验证(实际项目中应该调用后端API)
            if (username === 'admin' && password === 'admin123') {
                // 存储登录状态
                localStorage.setItem('loggedIn', 'true');
                // 跳转到主页面
                window.location.href = '/';
            } else {
                document.getElementById('errorMessage').textContent = '用户名或密码错误';
            }
        });
    </script>
</body>
</html>