Administrator
2025-08-19 7804a7fa8eff2d3086eb32bb7a2fadb9fdcb36ab
feat(mall): 双击删除图片功能

- 在平台轮播图添加和详情页面添加双击删除图片功能
- 优化图片上传逻辑,确保删除后重新上传图片
- 调整 OrderOvertimeJob 类的条件注解
3 files modified
3 files added
991 ■■■■■ changed files
src/main/java/cc/mrbird/febs/mall/quartz/OrderOvertimeJob.java 2 ●●● patch | view | raw | blame | history
src/main/resources/static/febs/quant/okxCandle.html 289 ●●●●● patch | view | raw | blame | history
src/main/resources/static/febs/quant/okxWebsocketChannels.html 381 ●●●●● patch | view | raw | blame | history
src/main/resources/static/febs/quant/okxWebsocketMonitor.html 180 ●●●●● patch | view | raw | blame | history
src/main/resources/templates/febs/views/modules/banner/platformBannerAdd.html 69 ●●●●● patch | view | raw | blame | history
src/main/resources/templates/febs/views/modules/banner/platformBannerDetail.html 70 ●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/quartz/OrderOvertimeJob.java
@@ -24,7 +24,7 @@
 **/
@Slf4j
@Component
@ConditionalOnProperty(prefix = "system", name = "job", havingValue = "true")
@ConditionalOnProperty(prefix = "system", name = "job", havingValue = "false")
public class OrderOvertimeJob {
    @Autowired
src/main/resources/static/febs/quant/okxCandle.html
New file
@@ -0,0 +1,289 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>OKX 标记价格K线频道</title>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns"></script>
    <script src="https://cdn.jsdelivr.net/npm/chartjs-chart-financial"></script>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
        }
        .channel {
            margin-bottom: 20px;
            padding: 15px;
            border: 1px solid #ccc;
            border-radius: 5px;
        }
        .channel h3 {
            margin-top: 0;
        }
        .status {
            font-weight: bold;
        }
        .connected {
            color: green;
        }
        .disconnected {
            color: red;
        }
        .messages {
            height: 300px;
            overflow-y: scroll;
            border: 1px solid #eee;
            padding: 10px;
            margin-top: 10px;
            background-color: #f9f9f9;
        }
        .chart-container {
            width: 100%;
            height: 300px;
            margin-top: 20px;
        }
        .mode-toggle {
            margin: 10px 0;
            padding: 10px;
            background-color: #f0f0f0;
            border-radius: 5px;
        }
        .mode-toggle button {
            margin: 0 5px;
            padding: 5px 10px;
            border: 1px solid #ccc;
            background-color: #fff;
            cursor: pointer;
        }
        .mode-toggle button.active {
            background-color: #007bff;
            color: white;
        }
    </style>
</head>
<body>
    <h1>OKX 标记价格K线频道</h1>
    <div class="mode-toggle">
        <span>当前模式:</span>
        <button id="demoModeBtn" class="active" onclick="switchMode('demo')">模拟盘 (wspap)</button>
        <button id="liveModeBtn" onclick="switchMode('live')">实盘 (ws)</button>
    </div>
    <div class="channel" id="markPriceCandleChannel">
        <h3>标记价格K线频道</h3>
        <p>URL: <span id="markPriceCandleUrl">wss://wspap.okx.com:8443/ws/v5/business</span></p>
        <p>状态: <span class="status disconnected" id="markPriceCandleStatus">未连接</span></p>
        <button onclick="toggleConnection()">连接/断开</button>
        <button onclick="clearMessages()">清除消息</button>
        <div class="chart-container">
            <canvas id="markPriceCandleChart"></canvas>
        </div>
        <div class="messages" id="markPriceCandleMessages"></div>
    </div>
    <script>
        // 存储WebSocket连接对象
        let connection = null;
        // 存储各模式的URL
        const urls = {
            demo: 'wss://wspap.okx.com:8443/ws/v5/business',
            live: 'wss://ws.okx.com:8443/ws/v5/business'
        };
        // 当前模式,默认为模拟盘
        let currentMode = 'demo';
        // 标记价格K线数据
        const markPriceCandleData = [];
        const markPriceCandleTimes = [];
        let markPriceCandleChart = null;
        // 初始化图表
        function initChart() {
            const ctx = document.getElementById('markPriceCandleChart').getContext('2d');
            markPriceCandleChart = new Chart(ctx, {
                type: 'line',
                data: {
                    labels: [],
                    datasets: [{
                        label: 'ETH-USDT Mark Price',
                        data: [],
                        borderColor: 'rgb(75, 192, 192)',
                        tension: 0.1
                    }]
                },
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    scales: {
                        x: {
                            type: 'time',
                            time: {
                                unit: 'minute'
                            }
                        },
                        y: {
                            beginAtZero: false
                        }
                    }
                }
            });
        }
        // 更新图表
        function updateChart() {
            if (markPriceCandleChart) {
                // 为折线图准备数据
                const times = markPriceCandleData.map(item => item.x);
                const prices = markPriceCandleData.map(item => item.c); // 使用收盘价
                markPriceCandleChart.data.labels = times;
                markPriceCandleChart.data.datasets[0].data = prices;
                markPriceCandleChart.update();
            }
        }
        // 连接/断开WebSocket连接
        function toggleConnection() {
            if (connection) {
                // 如果已经连接,则断开连接
                connection.close();
                connection = null;
                document.getElementById('markPriceCandleStatus').textContent = '未连接';
                document.getElementById('markPriceCandleStatus').className = 'status disconnected';
            } else {
                // 如果未连接,则建立连接
                connectToChannel();
            }
        }
        // 清除消息
        function clearMessages() {
            const messagesDiv = document.getElementById('markPriceCandleMessages');
            messagesDiv.innerHTML = '';
            // 清除图表数据
            markPriceCandleData.length = 0;
            updateChart();
        }
        // 切换模式
        function switchMode(mode) {
            // 更新当前模式
            currentMode = mode;
            // 更新按钮状态
            document.getElementById('demoModeBtn').classList.toggle('active', mode === 'demo');
            document.getElementById('liveModeBtn').classList.toggle('active', mode === 'live');
            // 更新URL显示
            document.getElementById('markPriceCandleUrl').textContent = urls[mode];
        }
        // 连接到频道
        function connectToChannel() {
            const ws = new WebSocket(urls[currentMode]);
            ws.onopen = function() {
                console.log('标记价格K线频道连接成功');
                document.getElementById('markPriceCandleStatus').textContent = '已连接';
                document.getElementById('markPriceCandleStatus').className = 'status connected';
                // 订阅标记价格K线数据 (BTC-USD-190628, 1分钟K线)
                const subscribeMsg = {
                    op: 'subscribe',
                    args: [
                        {
                            channel: 'mark-price-candle1m',
                            instId: 'ETH-USDT'
                        }
                    ]
                };
                ws.send(JSON.stringify(subscribeMsg));
                // 初始化图表
                if (!markPriceCandleChart) {
                    initChart();
                }
            };
            ws.onmessage = function(event) {
                const message = event.data;
                console.log('标记价格K线频道收到消息:', message);
                // 在页面上显示消息
                const messagesDiv = document.getElementById('markPriceCandleMessages');
                const messageElement = document.createElement('div');
                // 尝试解析JSON消息并格式化显示
                try {
                    const jsonData = JSON.parse(message);
                    // 检查是否为标记价格K线数据
                    if (jsonData.arg && jsonData.arg.channel && jsonData.arg.channel.startsWith('mark-price-candle') &&
                        jsonData.arg.instId === 'ETH-USDT' && jsonData.data && jsonData.data.length > 0) {
                        // 处理标记价格K线数据
                        // 标记价格K线数据格式: [ts, o, h, l, c]
                        // ts: 开始时间, o: 开盘价, h: 最高价, l: 最低价, c: 收盘价
                        const candleData = jsonData.data[0];
                        const ts = parseInt(candleData[0]); // 时间戳
                        const o = parseFloat(candleData[1]); // 开盘价
                        const h = parseFloat(candleData[2]); // 最高价
                        const l = parseFloat(candleData[3]); // 最低价
                        const c = parseFloat(candleData[4]); // 收盘价
                        // 添加时间戳到时间数组
                        markPriceCandleTimes.push(ts);
                        // 添加K线数据到数组
                        markPriceCandleData.push({
                            x: ts,
                            o: o,
                            h: h,
                            l: l,
                            c: c
                        });
                        // 限制数据点数量,只保留最近的30个
                        if (markPriceCandleData.length > 30) {
                            markPriceCandleData.shift();
                            markPriceCandleTimes.shift();
                        }
                        // 更新图表
                        updateChart();
                        console.log('标记价格K线数据:', candleData);
                    }
                    messageElement.innerHTML = `<strong>[${new Date().toLocaleTimeString()}]</strong> ${JSON.stringify(jsonData, null, 2)}`;
                } catch (e) {
                    // 如果不是JSON格式,则直接显示
                    messageElement.innerHTML = `<strong>[${new Date().toLocaleTimeString()}]</strong> ${message}`;
                }
                messagesDiv.appendChild(messageElement);
                messagesDiv.scrollTop = messagesDiv.scrollHeight;
            };
            ws.onerror = function(error) {
                console.error('标记价格K线频道连接错误:', error);
                document.getElementById('markPriceCandleStatus').textContent = '连接错误';
                document.getElementById('markPriceCandleStatus').className = 'status disconnected';
            };
            ws.onclose = function() {
                console.log('标记价格K线频道连接已关闭');
                document.getElementById('markPriceCandleStatus').textContent = '连接已关闭';
                document.getElementById('markPriceCandleStatus').className = 'status disconnected';
                connection = null;
            };
            connection = ws;
        }
    </script>
</body>
</html>
src/main/resources/static/febs/quant/okxWebsocketChannels.html
New file
@@ -0,0 +1,381 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>OKX WebSocket 频道连接</title>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns"></script>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
        }
        .channel {
            margin-bottom: 20px;
            padding: 15px;
            border: 1px solid #ccc;
            border-radius: 5px;
        }
        .channel h3 {
            margin-top: 0;
        }
        .status {
            font-weight: bold;
        }
        .connected {
            color: green;
        }
        .disconnected {
            color: red;
        }
        .messages {
            height: 200px;
            overflow-y: scroll;
            border: 1px solid #eee;
            padding: 10px;
            margin-top: 10px;
            background-color: #f9f9f9;
        }
        .chart-container {
            width: 100%;
            height: 300px;
            margin-top: 20px;
        }
        .mode-toggle {
            margin: 10px 0;
            padding: 10px;
            background-color: #f0f0f0;
            border-radius: 5px;
        }
        .mode-toggle button {
            margin: 0 5px;
            padding: 5px 10px;
            border: 1px solid #ccc;
            background-color: #fff;
            cursor: pointer;
        }
        .mode-toggle button.active {
            background-color: #007bff;
            color: white;
        }
    </style>
</head>
<body>
    <h1>OKX WebSocket 频道连接</h1>
    <div class="mode-toggle">
        <span>当前模式:</span>
        <button id="demoModeBtn" class="active" onclick="switchMode('demo')">模拟盘 (wspap)</button>
        <button id="liveModeBtn" onclick="switchMode('live')">实盘 (ws)</button>
    </div>
    <div class="channel" id="publicChannel">
        <h3>公共频道</h3>
        <p>URL: <input type="text" id="publicUrl" value="wss://wspap.okx.com:8443/ws/v5/public" style="width: 300px;"></p>
        <p>状态: <span class="status disconnected" id="publicStatus">未连接</span></p>
        <button onclick="toggleConnection('public')">连接/断开</button>
        <button onclick="clearMessages('public')">清除消息</button>
        <div class="chart-container">
            <canvas id="ethUsdtChart"></canvas>
        </div>
        <div class="messages" id="publicMessages"></div>
    </div>
    <div class="channel" id="privateChannel">
        <h3>私有频道</h3>
        <p>URL: <input type="text" id="privateUrl" value="wss://wspap.okx.com:8443/ws/v5/private" style="width: 300px;"></p>
        <p>状态: <span class="status disconnected" id="privateStatus">未连接</span></p>
        <button onclick="toggleConnection('private')">连接/断开</button>
        <div class="messages" id="privateMessages"></div>
    </div>
    <div class="channel" id="businessChannel">
        <h3>业务频道</h3>
        <p>URL: <input type="text" id="businessUrl" value="wss://wspap.okx.com:8443/ws/v5/business" style="width: 300px;"></p>
        <p>状态: <span class="status disconnected" id="businessStatus">未连接</span></p>
        <button onclick="toggleConnection('business')">连接/断开</button>
        <div class="messages" id="businessMessages"></div>
    </div>
    <div class="channel" id="markPriceCandleChannel">
        <h3>标记价格K线频道</h3>
        <p>URL: <input type="text" id="markPriceCandleUrl" value="wss://wspap.okx.com:8443/ws/v5/business" style="width: 300px;"></p>
        <p>状态: <span class="status disconnected" id="markPriceCandleStatus">未连接</span></p>
        <button onclick="toggleConnection('markPriceCandle')">连接/断开</button>
        <div class="messages" id="markPriceCandleMessages"></div>
    </div>
    <script>
        // 存储WebSocket连接对象
        const connections = {
            public: null,
            private: null,
            business: null,
            markPriceCandle: null
        };
        // 存储各频道的URL
        const urls = {
            demo: {
                public: 'wss://wspap.okx.com:8443/ws/v5/public',
                private: 'wss://wspap.okx.com:8443/ws/v5/private',
                business: 'wss://wspap.okx.com:8443/ws/v5/business'
            },
            live: {
                public: 'wss://ws.okx.com:8443/ws/v5/public',
                private: 'wss://ws.okx.com:8443/ws/v5/private',
                business: 'wss://ws.okx.com:8443/ws/v5/business'
            }
        };
        // 为markPriceCandle频道添加URL配置
        urls.demo.markPriceCandle = urls.demo.business;
        urls.live.markPriceCandle = urls.live.business;
        // 当前模式,默认为模拟盘
        let currentMode = 'demo';
        // ETH-USDT 价格数据
        const ethUsdtPrices = [];
        const ethUsdtTimes = [];
        let ethUsdtChart = null;
        // 初始化图表
        function initChart() {
            const ctx = document.getElementById('ethUsdtChart').getContext('2d');
            ethUsdtChart = new Chart(ctx, {
                type: 'line',
                data: {
                    labels: ethUsdtTimes,
                    datasets: [{
                        label: 'ETH-USDT Price',
                        data: ethUsdtPrices,
                        borderColor: 'rgb(75, 192, 192)',
                        backgroundColor: 'rgba(75, 192, 192, 0.2)',
                        tension: 0.1,
                        fill: false
                    }]
                },
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    scales: {
                        x: {
                            type: 'time',
                            time: {
                                unit: 'minute'
                            }
                        },
                        y: {
                            beginAtZero: false
                        }
                    }
                }
            });
        }
        // 更新图表
        function updateChart() {
            if (ethUsdtChart) {
                // 对于折线图,我们需要重新设置labels和data并调用update
                ethUsdtChart.data.labels = ethUsdtTimes;
                ethUsdtChart.data.datasets[0].data = ethUsdtPrices;
                ethUsdtChart.update();
            }
        }
        // 连接/断开WebSocket连接
        function toggleConnection(channel) {
            if (connections[channel]) {
                // 如果已经连接,则断开连接
                connections[channel].close();
                connections[channel] = null;
                document.getElementById(`${channel}Status`).textContent = '未连接';
                document.getElementById(`${channel}Status`).className = 'status disconnected';
            } else {
                // 如果未连接,则建立连接
                connectToChannel(channel);
            }
        }
        // 清除指定频道的消息
        function clearMessages(channel) {
            const messagesDiv = document.getElementById(`${channel}Messages`);
            messagesDiv.innerHTML = '';
            // 清除图表数据
            if (channel === 'public') {
                ethUsdtPrices.length = 0;
                ethUsdtTimes.length = 0;
                updateChart();
            }
        }
        // 切换模式
        function switchMode(mode) {
            // 更新当前模式
            currentMode = mode;
            // 更新按钮状态
            document.getElementById('demoModeBtn').classList.toggle('active', mode === 'demo');
            document.getElementById('liveModeBtn').classList.toggle('active', mode === 'live');
            // 更新所有频道的URL输入框的值
            document.querySelectorAll('.channel').forEach(channelDiv => {
                const channelId = channelDiv.id.replace('Channel', '');
                // 更新输入框的值而不是替换整个元素
                const urlInput = document.getElementById(`${channelId}Url`);
                if (urlInput && urls[mode] && urls[mode][channelId]) {
                    urlInput.value = urls[mode][channelId];
                }
            });
        }
        // 连接到指定频道
        function connectToChannel(channel) {
            // 获取用户输入的URL
            let url;
            switch(channel) {
                case 'public':
                    url = document.getElementById('publicUrl').value;
                    break;
                case 'private':
                    url = document.getElementById('privateUrl').value;
                    break;
                case 'business':
                    url = document.getElementById('businessUrl').value;
                    break;
                case 'markPriceCandle':
                    url = document.getElementById('markPriceCandleUrl').value;
                    break;
                default:
                    url = urls[currentMode][channel];
            }
            const ws = new WebSocket(url);
            ws.onopen = function() {
                console.log(`${channel}频道连接成功`);
                document.getElementById(`${channel}Status`).textContent = '已连接';
                document.getElementById(`${channel}Status`).className = 'status connected';
                // 发送订阅消息
                if (channel === 'public') {
                    const subscribeMsg = {
                        op: 'subscribe',
                        args: [
                            {
                                channel: 'tickers',
                                instId: 'ETH-USDT'
                            }
                        ]
                    };
                    ws.send(JSON.stringify(subscribeMsg));
                    // 初始化图表
                    if (!ethUsdtChart) {
                        initChart();
                    }
                } else if (channel === 'markPriceCandle') {
                    // 订阅标记价格K线数据 (BTC-USD-190628, 1分钟K线)
                    const subscribeMsg = {
                        op: 'subscribe',
                        args: [
                            {
                                channel: 'mark-price-candle1m',
                                instId: 'ETH-USDT'
                            }
                        ]
                    };
                    ws.send(JSON.stringify(subscribeMsg));
                }
            };
            ws.onmessage = function(event) {
                const message = event.data;
                console.log(`${channel}频道收到消息:`, message);
                // 在页面上显示消息
                const messagesDiv = document.getElementById(`${channel}Messages`);
                const messageElement = document.createElement('div');
                // 尝试解析JSON消息并格式化显示
                try {
                    const jsonData = JSON.parse(message);
                    // 检查是否为ETH-USDT的ticker数据
                    if (jsonData.arg && jsonData.arg.instId === 'ETH-USDT' && jsonData.data && jsonData.data.length > 0) {
                        const price = parseFloat(jsonData.data[0].last);
                        const time = new Date();
                        // 添加价格和时间到数组
                        ethUsdtPrices.push(price);
                        ethUsdtTimes.push(time);
                        // 限制数据点数量,只保留最近的1000个
                        // 为了保持图表连续性,我们采用滑动窗口的方式
                        if (ethUsdtPrices.length > 1000) {
                            // 移除最旧的数据点
                            ethUsdtPrices.shift();
                            ethUsdtTimes.shift();
                            // 为了保持图表连续性,我们需要保留最后一个数据点作为下一个数据段的起始点
                            // 这样可以避免图表出现断裂
                            // 当数据点超过1000个时,我们重新构建数据集,确保连续性
                            if (ethUsdtPrices.length > 1) {
                                // 保留最后一个数据点作为新数据段的起始点
                                const lastPrice = ethUsdtPrices[ethUsdtPrices.length - 1];
                                const lastTime = ethUsdtTimes[ethUsdtTimes.length - 1];
                                // 清空数组,但保留最后一个数据点
                                ethUsdtPrices.length = 0;
                                ethUsdtTimes.length = 0;
                                // 将最后一个数据点作为新数据段的起始点
                                ethUsdtPrices.push(lastPrice);
                                ethUsdtTimes.push(lastTime);
                            }
                        }
                        // 更新图表
                        updateChart();
                    }
                    // 检查是否为标记价格K线数据
                    if (jsonData.arg && jsonData.arg.channel && jsonData.arg.channel.startsWith('mark-price-candle') &&
                        jsonData.arg.instId === 'BTC-USD-190628' && jsonData.data && jsonData.data.length > 0) {
                        // 这里可以处理标记价格K线数据
                        // 标记价格K线数据格式: [ts, o, h, l, c]
                        // ts: 开始时间, o: 开盘价, h: 最高价, l: 最低价, c: 收盘价
                        console.log('标记价格K线数据:', jsonData.data[0]);
                    }
                    messageElement.innerHTML = `<strong>[${new Date().toLocaleTimeString()}]</strong> ${JSON.stringify(jsonData, null, 2)}`;
                } catch (e) {
                    // 如果不是JSON格式,则直接显示
                    messageElement.innerHTML = `<strong>[${new Date().toLocaleTimeString()}]</strong> ${message}`;
                }
                messagesDiv.appendChild(messageElement);
                messagesDiv.scrollTop = messagesDiv.scrollHeight;
            };
            ws.onerror = function(error) {
                console.error(`${channel}频道连接错误:`, error);
                document.getElementById(`${channel}Status`).textContent = '连接错误';
                document.getElementById(`${channel}Status`).className = 'status disconnected';
            };
            ws.onclose = function() {
                console.log(`${channel}频道连接已关闭`);
                document.getElementById(`${channel}Status`).textContent = '连接已关闭';
                document.getElementById(`${channel}Status`).className = 'status disconnected';
                connections[channel] = null;
            };
            connections[channel] = ws;
        }
    </script>
</body>
</html>
src/main/resources/static/febs/quant/okxWebsocketMonitor.html
New file
@@ -0,0 +1,180 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>OKX WebSocket消息监控</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
            background-color: #f5f5f5;
        }
        .container {
            max-width: 1200px;
            margin: 0 auto;
            background-color: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        h1 {
            color: #333;
            text-align: center;
        }
        .controls {
            margin: 20px 0;
            text-align: center;
        }
        .controls input, .controls button {
            margin: 5px;
            padding: 8px 15px;
            border-radius: 4px;
            border: 1px solid #ddd;
        }
        .controls button {
            background-color: #007bff;
            color: white;
            cursor: pointer;
        }
        .controls button:hover {
            background-color: #0056b3;
        }
        #messageDisplay {
            background-color: #f8f9fa;
            border: 1px solid #ddd;
            border-radius: 4px;
            padding: 15px;
            min-height: 300px;
            max-height: 600px;
            overflow-y: auto;
            white-space: pre-wrap;
            font-family: monospace;
        }
        .message {
            margin-bottom: 10px;
            padding: 10px;
            border-radius: 4px;
            background-color: #e9ecef;
        }
        .timestamp {
            font-size: 0.8em;
            color: #6c757d;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>OKX WebSocket消息监控</h1>
        <div class="controls">
            <label for="refreshInterval">刷新间隔(秒):</label>
            <input type="number" id="refreshInterval" min="1" max="60" value="5">
            <button onclick="startAutoRefresh()">开始自动刷新</button>
            <button onclick="stopAutoRefresh()">停止自动刷新</button>
            <button onclick="refreshMessage()">手动刷新</button>
            <button onclick="clearMessages()">清除消息</button>
        </div>
        <div id="messageDisplay">
            <p>等待接收消息...</p>
        </div>
    </div>
    <script>
        let refreshIntervalId;
        let messageHistory = [];
        // 获取最新消息
        function refreshMessage() {
            fetch('/quant/okx/latestMessage')
                .then(response => response.json())
                .then(data => {
                    if (data.code === 200 && data.data) {
                        displayMessage(data.data);
                    }
                })
                .catch(error => {
                    console.error('Error fetching message:', error);
                    displayMessage('Error: ' + error.message);
                });
        }
        // 显示消息
        function displayMessage(message) {
            const display = document.getElementById('messageDisplay');
            const timestamp = new Date().toLocaleString();
            // 如果是新消息,添加到历史记录
            if (!messageHistory.includes(message)) {
                messageHistory.push({
                    timestamp: timestamp,
                    content: message
                });
                // 限制历史记录数量
                if (messageHistory.length > 100) {
                    messageHistory.shift();
                }
            }
            // 更新显示
            display.innerHTML = '';
            messageHistory.forEach(msg => {
                const messageElement = document.createElement('div');
                messageElement.className = 'message';
                messageElement.innerHTML = `
                    <div class="timestamp">${msg.timestamp}</div>
                    <div>${formatJson(msg.content)}</div>
                `;
                display.appendChild(messageElement);
            });
            // 滚动到底部
            display.scrollTop = display.scrollHeight;
        }
        // 格式化JSON
        function formatJson(jsonString) {
            try {
                const obj = JSON.parse(jsonString);
                return JSON.stringify(obj, null, 2);
            } catch (e) {
                return jsonString;
            }
        }
        // 开始自动刷新
        function startAutoRefresh() {
            stopAutoRefresh(); // 先停止之前的定时器
            const interval = document.getElementById('refreshInterval').value * 1000;
            refreshIntervalId = setInterval(refreshMessage, interval);
            refreshMessage(); // 立即获取一次消息
        }
        // 停止自动刷新
        function stopAutoRefresh() {
            if (refreshIntervalId) {
                clearInterval(refreshIntervalId);
                refreshIntervalId = null;
            }
        }
        // 清除消息
        function clearMessages() {
            messageHistory = [];
            document.getElementById('messageDisplay').innerHTML = '<p>消息已清除</p>';
        }
        // 页面加载完成后开始自动刷新
        window.onload = function() {
            startAutoRefresh();
        };
        // 页面关闭前停止定时器
        window.onbeforeunload = function() {
            stopAutoRefresh();
        };
    </script>
</body>
</html>
src/main/resources/templates/febs/views/modules/banner/platformBannerAdd.html
@@ -47,6 +47,7 @@
                                                <blockquote class="layui-elem-quote layui-quote-nm" style="margin-top: 10px;">
                                                    <div class="layui-upload-list" id="bannerImgUpload"></div>
                                                </blockquote>
                                                <div class="layui-word-aux">双击图片删除</div>
                                            </div>
                                        </div>
                                    </div>
@@ -60,27 +61,28 @@
                                    </div>
                                </div>
                                <div class="layui-row layui-col-space10 layui-form-item">
                                    <div class="layui-col-lg6">
                                        <label class="layui-form-label">背景图片:</label>
                                        <div class="layui-input-block">
                                            <div class="layui-upload">
                                                <button type="button" class="layui-btn layui-btn-normal layui-btn" id="bannerImgUploadButtonBack">上传</button>
                                                <blockquote class="layui-elem-quote layui-quote-nm" style="margin-top: 10px;">
                                                    <div class="layui-upload-list" id="bannerImgUploadBack"></div>
                                                </blockquote>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                                <div class="layui-row layui-col-space10 layui-form-item febs-hide">
                                    <div class="layui-col-lg6">
                                        <label class="layui-form-label">背景图片链接:</label>
                                        <div class="layui-input-block">
                                            <input type="text" id="imageBackUrl" name="imageBackUrl" autocomplete="off" class="layui-input" readonly>
                                        </div>
                                    </div>
                                </div>
<!--                                <div class="layui-row layui-col-space10 layui-form-item">-->
<!--                                    <div class="layui-col-lg6">-->
<!--                                        <label class="layui-form-label">背景图片:</label>-->
<!--                                        <div class="layui-input-block">-->
<!--                                            <div class="layui-upload">-->
<!--                                                <button type="button" class="layui-btn layui-btn-normal layui-btn" id="bannerImgUploadButtonBack">上传</button>-->
<!--                                                <blockquote class="layui-elem-quote layui-quote-nm" style="margin-top: 10px;">-->
<!--                                                    <div class="layui-upload-list" id="bannerImgUploadBack"></div>-->
<!--                                                </blockquote>-->
<!--                                                <div class="layui-word-aux">双击图片删除</div>-->
<!--                                            </div>-->
<!--                                        </div>-->
<!--                                    </div>-->
<!--                                </div>-->
<!--                                <div class="layui-row layui-col-space10 layui-form-item febs-hide">-->
<!--                                    <div class="layui-col-lg6">-->
<!--                                        <label class="layui-form-label">背景图片链接:</label>-->
<!--                                        <div class="layui-input-block">-->
<!--                                            <input type="text" id="imageBackUrl" name="imageBackUrl" autocomplete="off" class="layui-input" readonly>-->
<!--                                        </div>-->
<!--                                    </div>-->
<!--                                </div>-->
                                <div class="layui-row layui-col-space10 layui-form-item">
                                    <div class="layui-col-lg6">
@@ -191,6 +193,9 @@
            }
            ,done: function(res){
                $("#imageUrl").val(res.data.src);
                imgUnBind(".single-image");
                imgSingleBind("#imageUrl");
            }
        });
@@ -208,8 +213,30 @@
            }
            ,done: function(res){
                $("#imageBackUrl").val(res.data.src);
                imgUnBind(".single-image");
                imgSingleBind("#imageBackUrl");
            }
        });
        function imgSingleBind(idName) {
            $(".single-image").each(function(index, element) {
                $(this).on("dblclick", function() {
                    var imgThumb = $(".single-image")[index];
                    $(imgThumb).remove();
                    $(idName).val("");
                    imgUnBind(".single-image");
                    imgSingleBind();
                });
            })
        }
        function imgUnBind(className) {
            $(className).each(function() {
                $(this).unbind('dblclick');
            })
        }
        formSelects.render();
        form.on('submit(banner-add-form-submit)', function (data) {
            febs.post(ctx + 'admin/banner/platformBannerAdds', data.field, function () {
src/main/resources/templates/febs/views/modules/banner/platformBannerDetail.html
@@ -49,6 +49,7 @@
                                                <blockquote class="layui-elem-quote layui-quote-nm" style="margin-top: 10px;">
                                                    <div class="layui-upload-list" id="bannerImgUpload"></div>
                                                </blockquote>
                                                <div class="layui-word-aux">双击图片删除</div>
                                            </div>
                                        </div>
                                    </div>
@@ -62,27 +63,28 @@
                                    </div>
                                </div>
                                <div class="layui-row layui-col-space10 layui-form-item">
                                    <div class="layui-col-lg6">
                                        <label class="layui-form-label">背景图片:</label>
                                        <div class="layui-input-block">
                                            <div class="layui-upload">
                                                <button type="button" class="layui-btn layui-btn-normal layui-btn" id="bannerImgUploadButtonBack">上传</button>
                                                <blockquote class="layui-elem-quote layui-quote-nm" style="margin-top: 10px;">
                                                    <div class="layui-upload-list" id="bannerImgUploadBack"></div>
                                                </blockquote>
                                            </div>
                                        </div>
                                    </div>
                                </div>
                                <div class="layui-row layui-col-space10 layui-form-item febs-hide">
                                    <div class="layui-col-lg6">
                                        <label class="layui-form-label">背景图片链接:</label>
                                        <div class="layui-input-block">
                                            <input type="text" id="imageBackUrl" name="imageBackUrl" autocomplete="off" class="layui-input" readonly>
                                        </div>
                                    </div>
                                </div>
<!--                                <div class="layui-row layui-col-space10 layui-form-item">-->
<!--                                    <div class="layui-col-lg6">-->
<!--                                        <label class="layui-form-label">背景图片:</label>-->
<!--                                        <div class="layui-input-block">-->
<!--                                            <div class="layui-upload">-->
<!--                                                <button type="button" class="layui-btn layui-btn-normal layui-btn" id="bannerImgUploadButtonBack">上传</button>-->
<!--                                                <blockquote class="layui-elem-quote layui-quote-nm" style="margin-top: 10px;">-->
<!--                                                    <div class="layui-upload-list" id="bannerImgUploadBack"></div>-->
<!--                                                </blockquote>-->
<!--                                                <div class="layui-word-aux">双击图片删除</div>-->
<!--                                            </div>-->
<!--                                        </div>-->
<!--                                    </div>-->
<!--                                </div>-->
<!--                                <div class="layui-row layui-col-space10 layui-form-item febs-hide">-->
<!--                                    <div class="layui-col-lg6">-->
<!--                                        <label class="layui-form-label">背景图片链接:</label>-->
<!--                                        <div class="layui-input-block">-->
<!--                                            <input type="text" id="imageBackUrl" name="imageBackUrl" autocomplete="off" class="layui-input" readonly>-->
<!--                                        </div>-->
<!--                                    </div>-->
<!--                                </div>-->
                                <div class="layui-row layui-col-space10 layui-form-item">
                                    <div class="layui-col-lg6">
@@ -178,6 +180,9 @@
            }
            ,done: function(res){
                $("#imageUrl").val(res.data.src);
                imgUnBind(".single-image");
                imgSingleBind("#imageUrl");
            }
        });
@@ -195,8 +200,29 @@
            }
            ,done: function(res){
                $("#imageBackUrl").val(res.data.src);
                imgUnBind(".single-image");
                imgSingleBind("#imageBackUrl");
            }
        });
        function imgSingleBind(idName) {
            $(".single-image").each(function(index, element) {
                $(this).on("dblclick", function() {
                    var imgThumb = $(".single-image")[index];
                    $(imgThumb).remove();
                    $(idName).val("");
                    imgUnBind(".single-image");
                    imgSingleBind(idName);
                });
            })
        }
        function imgUnBind(className) {
            $(className).each(function() {
                $(this).unbind('dblclick');
            })
        }
        form.render();
        //(下拉框)
@@ -233,9 +259,11 @@
            $('#bannerImgUpload').append('<img src="' + banner.imageUrl + '" alt="" class="layui-upload-img single-image" style="width: 130px">');
            $("#imageUrl").val(banner.imageUrl);
            imgSingleBind("#imageUrl");
            $('#bannerImgUploadBack').append('<img src="' + banner.imageBackUrl + '" alt="" class="layui-upload-img single-image" style="width: 130px">');
            $("#imageBackUrl").val(banner.imageBackUrl);
            imgSingleBind("#imageBackUrl");
        }
        form.on('submit(banner-info-form-submit)', function (data) {