用deepseek生成的 canvas版的类excel组件

 用deepseek生成的在浏览器中运行的 canvas版的类excel简单组件,可以添加行,添加列,加载示例数据,可以对单元格进行编辑;可以调整行高,列高等。可以在此基础上不断完善和添加功能。

一、运行效果

二、源代码 

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Canvas Excel表格</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }
        
        body {
            background: linear-gradient(135deg, #1a2a6c, #b21f1f, #1a2a6c);
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            padding: 20px;
        }
        
        .container {
            width: 100%;
            max-width: 1200px;
            background: rgba(255, 255, 255, 0.95);
            border-radius: 12px;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
            overflow: hidden;
        }
        
        header {
            background: linear-gradient(to right, #2c3e50, #4a6491);
            color: white;
            padding: 20px;
            text-align: center;
            border-bottom: 2px solid #3498db;
        }
        
        h1 {
            font-size: 2.5rem;
            margin-bottom: 10px;
        }
        
        .subtitle {
            font-size: 1.1rem;
            opacity: 0.9;
        }
        
        .main-content {
            display: flex;
            padding: 20px;
        }
        
        .controls {
            width: 250px;
            background: #f8f9fa;
            padding: 20px;
            border-right: 1px solid #ddd;
            border-radius: 8px 0 0 8px;
        }
        
        .control-group {
            margin-bottom: 25px;
        }
        
        h3 {
            color: #2c3e50;
            margin-bottom: 15px;
            padding-bottom: 8px;
            border-bottom: 2px solid #3498db;
        }
        
        .btn {
            display: block;
            width: 100%;
            padding: 12px;
            margin: 10px 0;
            background: linear-gradient(to right, #3498db, #2c3e50);
            color: white;
            border: none;
            border-radius: 6px;
            cursor: pointer;
            font-size: 16px;
            transition: all 0.3s;
        }
        
        .btn:hover {
            transform: translateY(-2px);
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
        }
        
        .btn:active {
            transform: translateY(0);
        }
        
        .table-container {
            flex: 1;
            padding: 20px;
            position: relative;
            overflow: hidden;
            background: #fff;
        }
        
        #shinersheet {
            border: 1px solid #ddd;
            box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
            background: white;
            cursor: cell;
        }
        
        #cellEditor {
            position: absolute;
            border: 2px solid #3498db;
            padding: 5px;
            font-size: 16px;
            display: none;
            z-index: 100;
            box-shadow: 0 0 10px rgba(52, 152, 219, 0.5);
        }
        
        .info-panel {
            padding: 15px;
            background: #f8f9fa;
            border-top: 1px solid #ddd;
            font-size: 14px;
            color: #555;
            display: flex;
            justify-content: space-between;
        }
        
        .status {
            display: flex;
            align-items: center;
        }
        
        .status-indicator {
            width: 12px;
            height: 12px;
            border-radius: 50%;
            background: #2ecc71;
            margin-right: 8px;
        }
        
        .key-shortcuts {
            display: flex;
            gap: 15px;
        }
        
        .key {
            display: flex;
            align-items: center;
            gap: 5px;
        }
        
        .key kbd {
            background: #e0e0e0;
            padding: 3px 7px;
            border-radius: 4px;
            border: 1px solid #ccc;
            font-size: 12px;
        }
        
        footer {
            text-align: center;
            padding: 15px;
            color: #fff;
            background: rgba(0, 0, 0, 0.7);
        }
        
        .highlight {
            background: linear-gradient(to right, #3498db, #2c3e50);
            color: white;
            padding: 3px 6px;
            border-radius: 4px;
        }
    </style>
</head>
<body>
    <div class="container">
        <header>
            <h1>Canvas Excel表格组件</h1>
            <p class="subtitle">支持单元格编辑和行列大小调整功能</p>
        </header>
        
        <div class="main-content">
            <div class="controls">
                <div class="control-group">
                    <h3>表格操作</h3>
                    <button id="addRowBtn" class="btn">添加行 (+)</button>
                    <button id="addColBtn" class="btn">添加列 (+)</button>
                    <button id="clearBtn" class="btn">清空表格</button>
                </div>
                
                <div class="control-group">
                    <h3>示例数据</h3>
                    <button id="sampleDataBtn" class="btn">加载示例数据</button>
                </div>
                
                <div class="control-group">
                    <h3>使用说明</h3>
                    <ul style="padding-left: 20px; color: #555;">
                        <li>单击单元格进行编辑</li>
                        <li>按Enter键保存内容</li>
                        <li>拖拽行/列分隔线调整大小</li>
                        <li>使用方向键导航单元格</li>
                    </ul>
                </div>
            </div>
            
            <div class="table-container">
                <canvas id="shinersheet" width="800" height="500"></canvas>
                <input type="text" id="cellEditor">
            </div>
        </div>
        
        <div class="info-panel">
            <div class="status">
                <div class="status-indicator"></div>
                <span>就绪</span>
            </div>
            <div class="key-shortcuts">
                <div class="key"><kbd>Enter</kbd> 保存编辑</div>
                <div class="key"><kbd>Esc</kbd> 取消编辑</div>
                <div class="key"><kbd>←↑→↓</kbd> 导航单元格</div>
            </div>
        </div>
        
        <footer>
            <p>使用HTML5 Canvas开发的Excel风格表格组件 | 支持单元格编辑和行列调整</p>
        </footer>
    </div>
    
    <script>
        // 表格组件主类
        class Shinersheet {
            constructor(canvasId, editorId) {
                this.canvas = document.getElementById(canvasId);
                this.ctx = this.canvas.getContext('2d');
                this.cellEditor = document.getElementById(editorId);
                
                // 表格配置
                this.config = {
                    rowHeight: 30,
                    colWidth: 100,
                    headerHeight: 40,
                    headerWidth: 50,
                    rowCount: 15,
                    colCount: 8,
                    cellPadding: 8,
                    headerBg: '#3498db',
                    headerTextColor: '#fff',
                    gridColor: '#ddd',
                    cellBg: '#fff',
                    cellTextColor: '#333',
                    selectedCellBorder: '#e74c3c'
                };
                
                // 表格数据
                this.data = [];
                this.initializeData();
                
                // 当前选中单元格
                this.selectedCell = { row: 0, col: 0 };
                
                // 行列尺寸数组
                this.rowHeights = Array(this.config.rowCount).fill(this.config.rowHeight);
                this.colWidths = Array(this.config.colCount).fill(this.config.colWidth);
                
                // 调整行列状态
                this.resizing = {
                    row: -1,
                    col: -1,
                    startY: 0,
                    startX: 0
                };
                
                // 绑定事件
                this.bindEvents();
                
                // 初始化渲染
                this.render();
            }
            
            // 初始化表格数据
            initializeData() {
                this.data = [];
                for (let i = 0; i < this.config.rowCount; i++) {
                    this.data[i] = [];
                    for (let j = 0; j < this.config.colCount; j++) {
                        this.data[i][j] = '';
                        
                    }
                }
            }
            
            // 绑定事件处理函数
            bindEvents() {
                this.canvas.addEventListener('click', this.handleClick.bind(this));
                this.canvas.addEventListener('dblclick', this.handleDoubleClick.bind(this));
                this.canvas.addEventListener('mousedown', this.handleMouseDown.bind(this));
                this.canvas.addEventListener('mousemove', this.handleMouseMove.bind(this));
                this.canvas.addEventListener('mouseup', this.handleMouseUp.bind(this));
                
                // 单元格编辑器事件
                this.cellEditor.addEventListener('keydown', this.handleEditorKeydown.bind(this));
                this.cellEditor.addEventListener('blur', this.finishEditing.bind(this));
                
                // 键盘导航
                window.addEventListener('keydown', this.handleKeydown.bind(this));
            }
            
            // 处理画布点击
            handleClick(e) {
                const rect = this.canvas.getBoundingClientRect();
                const x = e.clientX - rect.left;
                const y = e.clientY - rect.top;
                
                // 检查是否点击了行标题
                if (y > 0 && y < this.config.headerHeight && x > 0 && x < this.config.headerWidth) {
                    return; // 点击了左上角标题
                }
                
                // 检查是否点击了列标题
                if (y > 0 && y < this.config.headerHeight && x > this.config.headerWidth) {
                    const col = this.getColAtX(x);
                    if (col >= 0) {
                        this.selectedCell.col = col;
                        this.render();
                    }
                    return;
                }
                
                // 检查是否点击了行标题
                if (x > 0 && x < this.config.headerWidth && y > this.config.headerHeight) {
                    const row = this.getRowAtY(y);
                    if (row >= 0) {
                        this.selectedCell.row = row;
                        this.render();
                    }
                    return;
                }
                
                // 检查是否点击了单元格
                const row = this.getRowAtY(y);
                const col = this.getColAtX(x);
                
                if (row >= 0 && col >= 0) {
                    this.selectedCell = { row, col };
                    this.render();
                }
            }
            
            // 处理画布双击(开始编辑)
            handleDoubleClick(e) {
                const rect = this.canvas.getBoundingClientRect();
                const x = e.clientX - rect.left;
                const y = e.clientY - rect.top;
                
                const row = this.getRowAtY(y);
                const col = this.getColAtX(x);
                
                if (row >= 0 && col >= 0) {
                    this.selectedCell = { row, col };
                    this.startEditing();
                }
            }
            
            // 处理鼠标按下(开始调整行高/列宽)
            handleMouseDown(e) {
                const rect = this.canvas.getBoundingClientRect();
                const x = e.clientX - rect.left;
                const y = e.clientY - rect.top;
                
                // 检查是否在列分隔线上
                if (y < this.config.headerHeight) {
                    for (let col = 0; col < this.colWidths.length; col++) {
                        const colX = this.getColX(col);
                        if (Math.abs(x - (colX + this.colWidths[col])) < 5) {
                            this.resizing.col = col;
                            this.resizing.startX = x;
                            this.canvas.style.cursor = 'col-resize';
                            return;
                        }
                    }
                }
                
                // 检查是否在行分隔线上
                if (x < this.config.headerWidth) {
                    for (let row = 0; row < this.rowHeights.length; row++) {
                        const rowY = this.getRowY(row);
                        if (Math.abs(y - (rowY + this.rowHeights[row])) < 5) {
                            this.resizing.row = row;
                            this.resizing.startY = y;
                            this.canvas.style.cursor = 'row-resize';
                            return;
                        }
                    }
                }
            }
            
            // 处理鼠标移动(调整行高/列宽)
            handleMouseMove(e) {
                const rect = this.canvas.getBoundingClientRect();
                const x = e.clientX - rect.left;
                const y = e.clientY - rect.top;
                
                // 调整列宽
                if (this.resizing.col >= 0) {
                    const delta = x - this.resizing.startX;
                    const newWidth = Math.max(30, this.colWidths[this.resizing.col] + delta);
                    this.colWidths[this.resizing.col] = newWidth;
                    this.resizing.startX = x;
                    this.render();
                    return;
                }
                
                // 调整行高
                if (this.resizing.row >= 0) {
                    const delta = y - this.resizing.startY;
                    const newHeight = Math.max(20, this.rowHeights[this.resizing.row] + delta);
                    this.rowHeights[this.resizing.row] = newHeight;
                    this.resizing.startY = y;
                    this.render();
                    return;
                }
                
                // 设置鼠标样式
                if (y < this.config.headerHeight) {
                    for (let col = 0; col < this.colWidths.length; col++) {
                        const colX = this.getColX(col);
                        if (Math.abs(x - (colX + this.colWidths[col])) < 5) {
                            this.canvas.style.cursor = 'col-resize';
                            return;
                        }
                    }
                }
                
                if (x < this.config.headerWidth) {
                    for (let row = 0; row < this.rowHeights.length; row++) {
                        const rowY = this.getRowY(row);
                        if (Math.abs(y - (rowY + this.rowHeights[row])) < 5) {
                            this.canvas.style.cursor = 'row-resize';
                            return;
                        }
                    }
                }
                
                this.canvas.style.cursor = 'default';
            }
            
            // 处理鼠标释放(结束调整)
            handleMouseUp(e) {
                this.resizing.row = -1;
                this.resizing.col = -1;
                this.canvas.style.cursor = 'default';
            }
            
            // 处理编辑器按键
            handleEditorKeydown(e) {
                if (e.key === 'Enter') {
                    this.finishEditing();
                } else if (e.key === 'Escape') {
                    this.cellEditor.style.display = 'none';
                }
            }
            
            // 处理键盘导航
            handleKeydown(e) {
                if (this.cellEditor.style.display === 'block') return;
                
                switch (e.key) {
                    case 'ArrowUp':
                        if (this.selectedCell.row > 0) {
                            this.selectedCell.row--;
                            this.render();
                        }
                        e.preventDefault();
                        break;
                    case 'ArrowDown':
                        if (this.selectedCell.row < this.rowHeights.length - 1) {
                            this.selectedCell.row++;
                            this.render();
                        }
                        e.preventDefault();
                        break;
                    case 'ArrowLeft':
                        if (this.selectedCell.col > 0) {
                            this.selectedCell.col--;
                            this.render();
                        }
                        e.preventDefault();
                        break;
                    case 'ArrowRight':
                        if (this.selectedCell.col < this.colWidths.length - 1) {
                            this.selectedCell.col++;
                            this.render();
                        }
                        e.preventDefault();
                        break;
                    case 'Enter':
                        this.startEditing();
                        e.preventDefault();
                        break;
                }
            }
            
            // 开始编辑单元格
            startEditing() {
                const { row, col } = this.selectedCell;
                const cellX = this.getColX(col);
                const cellY = this.getRowY(row);
                const cellWidth = this.colWidths[col];
                const cellHeight = this.rowHeights[row];
                
                this.cellEditor.value = this.data[row][col] || '';
                this.cellEditor.style.display = 'block';
                this.cellEditor.style.left = (this.canvas.offsetLeft + cellX) + 'px';
                this.cellEditor.style.top = (this.canvas.offsetTop + cellY) + 'px';
                this.cellEditor.style.width = (cellWidth - 2) + 'px';
                this.cellEditor.style.height = (cellHeight - 2) + 'px';
                this.cellEditor.focus();
                this.cellEditor.select();
            }
            
            // 结束编辑
            finishEditing() {
                const { row, col } = this.selectedCell;
                this.data[row][col] = this.cellEditor.value;
                this.cellEditor.style.display = 'none';
                this.render();
            }
            
            // 获取指定列起始X坐标
            getColX(col) {
                let x = this.config.headerWidth;
                for (let i = 0; i < col; i++) {
                    x += this.colWidths[i];
                }
                return x;
            }
            
            // 获取指定行起始Y坐标
            getRowY(row) {
                let y = this.config.headerHeight;
                for (let i = 0; i < row; i++) {
                    y += this.rowHeights[i];
                }
                return y;
            }
            
            // 根据Y坐标获取行索引
            getRowAtY(y) {
                if (y < this.config.headerHeight) return -1;
                
                let currentY = this.config.headerHeight;
                for (let row = 0; row < this.rowHeights.length; row++) {
                    currentY += this.rowHeights[row];
                    if (y < currentY) {
                        return row;
                    }
                }
                return -1;
            }
            
            // 根据X坐标获取列索引
            getColAtX(x) {
                if (x < this.config.headerWidth) return -1;
                
                let currentX = this.config.headerWidth;
                for (let col = 0; col < this.colWidths.length; col++) {
                    currentX += this.colWidths[col];
                    if (x < currentX) {
                        return col;
                    }
                }
                return -1;
            }
            
            // 添加新行
            addRow() {
                this.rowHeights.push(this.config.rowHeight);
                const newRow = Array(this.colWidths.length).fill('');
                this.data.push(newRow);
                this.render();
            }
            
            // 添加新列
            addColumn() {
                this.colWidths.push(this.config.colWidth);
                for (let row = 0; row < this.data.length; row++) {
                    this.data[row].push('');
                }
                this.render();
            }
            
            // 清空表格
            clearTable() {
                if (confirm('确定要清空表格数据吗?')) {
                    this.initializeData();
                    this.render();
                }
            }
            
            // 加载示例数据
            loadSampleData() {
                this.data = [
                    ['产品', '一月', '二月', '三月', '季度总计'],
                    ['笔记本电脑', '120', '150', '180', '=SUM(B2:D2)'],
                    ['智能手机', '200', '220', '250', '=SUM(B3:D3)'],
                    ['平板电脑', '80', '90', '110', '=SUM(B4:D4)'],
                    ['季度总计', '=SUM(B2:B4)', '=SUM(C2:C4)', '=SUM(D2:D4)', '=SUM(E2:E4)'],
                    ['', '', '', '', ''],
                    ['平均销量', '=AVERAGE(B2:B4)', '=AVERAGE(C2:C4)', '=AVERAGE(D2:D4)', '=AVERAGE(E2:E4)']
                ];
                
                // 更新行列计数
                this.rowHeights = Array(this.data.length).fill(this.config.rowHeight);
                this.colWidths = Array(this.data[0].length).fill(this.config.colWidth);
                
                this.render();
            }
            
            // 渲染表格
            render() {
                const ctx = this.ctx;
                const canvas = this.canvas;
                
                // 清除画布
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                
                // 计算画布所需尺寸
                const totalWidth = this.config.headerWidth + this.colWidths.reduce((a, b) => a + b, 0);
                const totalHeight = this.config.headerHeight + this.rowHeights.reduce((a, b) => a + b, 0);
                
                // 更新画布尺寸
                canvas.width = totalWidth;
                canvas.height = totalHeight;
                
                // 绘制左上角标题
                ctx.fillStyle = this.config.headerBg;
                ctx.fillRect(0, 0, this.config.headerWidth, this.config.headerHeight);
                
                ctx.strokeStyle = this.config.gridColor;
                ctx.lineWidth = 1;
                ctx.strokeRect(0, 0, this.config.headerWidth, this.config.headerHeight);
                
                // 绘制列标题
                let x = this.config.headerWidth;
                for (let col = 0; col < this.colWidths.length; col++) {
                    const width = this.colWidths[col];
                    
                    ctx.fillStyle = this.config.headerBg;
                    ctx.fillRect(x, 0, width, this.config.headerHeight);
                    
                    ctx.strokeStyle = this.config.gridColor;
                    ctx.strokeRect(x, 0, width, this.config.headerHeight);
                    
                    // 列标题文本
                    ctx.fillStyle = this.config.headerTextColor;
                    ctx.textAlign = 'center';
                    ctx.textBaseline = 'middle';
                    ctx.font = 'bold 14px Arial';
                    ctx.fillText(String.fromCharCode(65 + col), x + width / 2, this.config.headerHeight / 2);
                    
                    x += width;
                }
                
                // 绘制行标题
                let y = this.config.headerHeight;
                for (let row = 0; row < this.rowHeights.length; row++) {
                    const height = this.rowHeights[row];
                    
                    ctx.fillStyle = this.config.headerBg;
                    ctx.fillRect(0, y, this.config.headerWidth, height);
                    
                    ctx.strokeStyle = this.config.gridColor;
                    ctx.strokeRect(0, y, this.config.headerWidth, height);
                    
                    // 行标题文本
                    ctx.fillStyle = this.config.headerTextColor;
                    ctx.textAlign = 'center';
                    ctx.textBaseline = 'middle';
                    ctx.font = 'bold 14px Arial';
                    ctx.fillText((row + 1).toString(), this.config.headerWidth / 2, y + height / 2);
                    
                    y += height;
                }
                
                // 绘制单元格
                y = this.config.headerHeight;
                for (let row = 0; row < this.rowHeights.length; row++) {
                    const rowHeight = this.rowHeights[row];
                    
                    x = this.config.headerWidth;
                    for (let col = 0; col < this.colWidths.length; col++) {
                        const colWidth = this.colWidths[col];
                        
                        // 绘制单元格背景
                        ctx.fillStyle = this.config.cellBg;
                        ctx.fillRect(x, y, colWidth, rowHeight);
                        
                        // 绘制单元格边框
                        ctx.strokeStyle = this.config.gridColor;
                        ctx.strokeRect(x, y, colWidth, rowHeight);
                        
                        // 绘制单元格文本
                        const cellValue = this.data[row]?.[col] || '';
                        ctx.fillStyle = this.config.cellTextColor;
                        ctx.textAlign = 'left';
                        ctx.textBaseline = 'middle';
                        ctx.font = '14px Arial';
                        
                        // 文本截断处理
                        const maxWidth = colWidth - this.config.cellPadding * 2;
                        let displayText = cellValue;
                        let textWidth = ctx.measureText(displayText).width;
                        
                        if (textWidth > maxWidth) {
                            // 简单截断处理
                            while (textWidth > maxWidth && displayText.length > 0) {
                                displayText = displayText.substring(0, displayText.length - 1);
                                textWidth = ctx.measureText(displayText + '...').width;
                            }
                            displayText += '...';
                        }
                        
                        ctx.fillText(displayText, x + this.config.cellPadding, y + rowHeight / 2);
                        
                        // 高亮选中单元格
                        if (row === this.selectedCell.row && col === this.selectedCell.col) {
                            ctx.strokeStyle = this.config.selectedCellBorder;
                            ctx.lineWidth = 2;
                            ctx.strokeRect(x + 1, y + 1, colWidth - 2, rowHeight - 2);
                        }
                        
                        x += colWidth;
                    }
                    y += rowHeight;
                }
            }
        }

        // 页面加载完成后初始化
        document.addEventListener('DOMContentLoaded', () => {
            const shinersheet = new Shinersheet('shinersheet', 'cellEditor');
            
            // 绑定按钮事件
            document.getElementById('addRowBtn').addEventListener('click', () => shinersheet.addRow());
            document.getElementById('addColBtn').addEventListener('click', () => shinersheet.addColumn());
            document.getElementById('clearBtn').addEventListener('click', () => shinersheet.clearTable());
            document.getElementById('sampleDataBtn').addEventListener('click', () => shinersheet.loadSampleData());
        });
    </script>
</body>
</html>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值