查看: 97|回复: 0

座次图生成工具

[复制链接]

89

主题

8

回帖

31

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
31
发表于 2025-9-26 20:59:29 | 显示全部楼层 |阅读模式
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.     <title>会议座次图生成器</title>
  7.     <!-- 引入html2canvas和jsPDF库 -->
  8.     <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
  9.     <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
  10.     <!-- 引入xlsx库用于解析Excel -->
  11.     <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
  12.     <style>
  13.         * {
  14.             box-sizing: border-box;
  15.             margin: 0;
  16.             padding: 0;
  17.         }
  18.         body {
  19.             font-family: 'Microsoft YaHei', sans-serif;
  20.             padding: 20px;
  21.             background-color: #f5f5f5;
  22.         }
  23.         .container {
  24.             display: flex;
  25.             flex-direction: column;
  26.             gap: 20px;
  27.             max-width: 1600px;
  28.             margin: 0 auto;
  29.         }
  30.         .content-wrapper {
  31.             display: flex;
  32.             gap: 20px;
  33.         }
  34.         .controls {
  35.             flex: 1;
  36.             background: white;
  37.             padding: 20px;
  38.             border-radius: 8px;
  39.             box-shadow: 0 2px 10px rgba(0,0,0,0.1);
  40.         }
  41.         .seating-and-recording {
  42.             flex: 3;
  43.             display: flex;
  44.             gap: 20px;
  45.         }
  46.         .seating-area {
  47.             flex: 2;
  48.         }
  49.         .recording-area {
  50.             flex: 1;
  51.             background: white;
  52.             padding: 20px;
  53.             border-radius: 8px;
  54.             box-shadow: 0 2px 10px rgba(0,0,0,0.1);
  55.             min-width: 300px;
  56.         }
  57.         h1, h2, h3 {
  58.             color: #333;
  59.         }
  60.         h1 {
  61.             text-align: center;
  62.             margin-bottom: 20px;
  63.         }
  64.         .form-group {
  65.             margin-bottom: 15px;
  66.         }
  67.         label {
  68.             display: block;
  69.             margin-bottom: 5px;
  70.             font-weight: bold;
  71.         }
  72.         input, button, textarea {
  73.             width: 100%;
  74.             padding: 8px;
  75.             border: 1px solid #ddd;
  76.             border-radius: 4px;
  77.         }
  78.         button {
  79.             background-color: #4CAF50;
  80.             color: white;
  81.             border: none;
  82.             cursor: pointer;
  83.             font-weight: bold;
  84.             margin-top: 5px;
  85.             transition: background-color 0.3s;
  86.         }
  87.         button:hover {
  88.             background-color: #45a049;
  89.         }
  90.         .button-secondary {
  91.             background-color: #2196F3;
  92.         }
  93.         .button-secondary:hover {
  94.             background-color: #0b7dda;
  95.         }
  96.         .button-danger {
  97.             background-color: #f44336;
  98.         }
  99.         .button-danger:hover {
  100.             background-color: #d32f2f;
  101.         }
  102.         .participants-list {
  103.             margin-top: 20px;
  104.             background: #fff;
  105.             border: 1px solid #ddd;
  106.             border-radius: 4px;
  107.             min-height: 200px;
  108.             max-height: 300px;
  109.             overflow-y: auto;
  110.             padding: 10px;
  111.         }
  112.         .participant-item {
  113.             padding: 8px;
  114.             margin-bottom: 5px;
  115.             background-color: #f9f9f9;
  116.             border: 1px solid #eee;
  117.             border-radius: 4px;
  118.             cursor: move;
  119.         }
  120.         .participant-info {
  121.             font-size: 12px;
  122.             color: #666;
  123.         }
  124.         .seating-chart {
  125.             background: white;
  126.             padding: 20px;
  127.             border-radius: 8px;
  128.             box-shadow: 0 2px 10px rgba(0,0,0,0.1);
  129.             overflow: auto;
  130.         }
  131.         table {
  132.             border-collapse: collapse;
  133.             width: 100%;
  134.         }
  135.         td {
  136.             min-width: 100px;
  137.             height: 60px;
  138.             border: 1px solid #ddd;
  139.             text-align: center;
  140.             position: relative;
  141.             background-color: #f9f9f9;
  142.         }
  143.         .aisle {
  144.             background-color: #e0f7fa !important;
  145.             width: 40px;
  146.         }
  147.         .aisle-label {
  148.             position: absolute;
  149.             bottom: 2px;
  150.             right: 2px;
  151.             font-size: 10px;
  152.             color: #999;
  153.         }
  154.         .seat-number {
  155.             position: absolute;
  156.             top: 2px;
  157.             left: 2px;
  158.             font-size: 10px;
  159.             color: #999;
  160.         }
  161.         .occupied {
  162.             background-color: #e8f5e9;
  163.             font-weight: bold;
  164.         }
  165.         .row-label, .col-label {
  166.             background-color: #f5f5f5;
  167.             font-weight: bold;
  168.         }
  169.         .instructions {
  170.             margin-top: 20px;
  171.             padding: 15px;
  172.             background-color: #fff8e1;
  173.             border-left: 4px solid #ffc107;
  174.             border-radius: 4px;
  175.         }
  176.         .input-hint {
  177.             font-size: 12px;
  178.             color: #666;
  179.             margin-top: 4px;
  180.         }
  181.         .assignment-list {
  182.             margin-top: 15px;
  183.             max-height: 400px;
  184.             overflow-y: auto;
  185.             border: 1px solid #ddd;
  186.             border-radius: 4px;
  187.             padding: 10px;
  188.             background-color: #f9f9f9;
  189.         }
  190.         .assignment-item {
  191.             padding: 8px;
  192.             border-bottom: 1px solid #eee;
  193.             background-color: white;
  194.             margin-bottom: 5px;
  195.             border-radius: 4px;
  196.         }
  197.         .export-options {
  198.             display: flex;
  199.             flex-direction: column;
  200.             gap: 10px;
  201.             margin-top: 15px;
  202.         }
  203.         .export-btn {
  204.             width: 100%;
  205.         }
  206.          
  207.         /* 响应式设计 */
  208.         [url=home.php?mod=space&uid=945662]@media[/url] (max-width: 1200px) {
  209.             .content-wrapper {
  210.                 flex-direction: column;
  211.             }
  212.             .seating-and-recording {
  213.                 flex-direction: column;
  214.             }
  215.         }
  216.     </style>
  217. </head>
  218. <body>
  219.     <h1>会议座次图生成器</h1>
  220.      
  221.     <div class="container">
  222.         <div class="content-wrapper">
  223.             <div class="controls">
  224.                 <div class="form-group">
  225.                     <label for="rows">行数:</label>
  226.                     <input type="number" id="rows", min="1", max="20", value="5">
  227.                 </div>
  228.                 <div class="form-group">
  229.                     <label for="cols">列数:</label>
  230.                     <input type="number" id="cols", min="1", max="20", value="6">
  231.                 </div>
  232.                  
  233.                 <div class="form-group">
  234.                     <label for="aisles">过道位置 (在第几列后添加过道,用逗号分隔):</label>
  235.                     <input type="text" id="aisles", placeholder="例如: 2,4" value="2,4">
  236.                     <div class="input-hint">请输入要在其后添加过道的列号,用逗号分隔。例如:"2,4" 表示在第2列和第4列后添加过道</div>
  237.                 </div>
  238.                  
  239.                 <button id="generate-btn">生成座次表</button>
  240.                  
  241.            
  242.             </div>
  243.             
  244.             <div class="seating-and-recording">
  245.                 <div class="seating-area">
  246.                     <div class="seating-chart" id="seating-chart-container">
  247.                         <table id="seating-table">
  248.                             <!-- 座次表将通过JavaScript动态生成 -->
  249.                         </table>
  250.                     </div>
  251.                      
  252.                     <div class="export-options">
  253.                         <button id="export-png" class="export-btn button-secondary">导出为PNG图片</button>
  254.                         <button id="export-pdf" class="export-btn button-secondary">导出为PDF</button>
  255.                     </div>
  256.                      
  257.                     <div class="instructions">
  258.                         <h3>使用说明:</h3>
  259.                         <p>1. 设置行数、列数和过道位置后点击"生成座次表"</p>
  260.                         <p>2. 过道位置可以使用逗号分隔输入多个列号,例如:"2,4"表示在第2列和第4列后添加过道</p>
  261.                         <p>3. 上传Excel文件导入参会人员名单(第一列为姓名,第二列为单位)</p>
  262.                         <p>4. 从左侧名单区域拖拽人员到座位上进行安排</p>
  263.                         <p>5. 右侧区域实时显示座位分配记录</p>
  264.                         <p>6. 可以随时调整布局并重新生成座次表</p>
  265.                         <p>7. 使用下方按钮导出座次图为PNG图片或PDF文件</p>
  266.                     </div>
  267.                 </div>
  268.                  
  269.                 <div class="recording-area">
  270.                     <h2>座位分配记录</h2>
  271.                     <div class="assignment-list" id="assignment-container">
  272.                         <p>尚未进行座位分配</p>
  273.                     </div>
  274.                      
  275.                     <div class="export-options">
  276.                         <button id="export-json" class="export-btn button-secondary">导出为JSON</button>
  277.                         <button id="export-csv" class="export-btn button-secondary">导出为CSV</button>
  278.                         <button id="clear-assignments" class="export-btn button-danger">清空分配记录</button>
  279.                     </div>
  280.                     <div class="form-group">
  281.                         <label for="excel-file">导入参会人员Excel文件:</label>
  282.                         <input type="file" id="excel-file" accept=".xlsx, .xls">
  283.                         <div class="input-hint">请上传Excel文件,第一列为姓名,第二列为单位</div>
  284.                     </div>
  285.                  
  286.                     <div class="participants-list" id="participants-container">
  287.                         <p>拖拽人员到座位上进行安排</p>
  288.                     </div>
  289.                 </div>
  290.                  
  291.             </div>
  292.         </div>
  293.     </div>

  294.     <script>
  295.         // 全局变量定义
  296.         let participants = []; // 存储参会人员信息,格式: {name: "姓名", unit: "单位"}
  297.         let draggedParticipant = null;
  298.         let seatAssignments = {}; // 存储座位分配信息,格式: {seatId: {name: "姓名", unit: "单位"}}
  299.          
  300.         // DOM元素引用
  301.         const generateBtn = document.getElementById('generate-btn');
  302.         const excelFileInput = document.getElementById('excel-file');
  303.         const exportJsonBtn = document.getElementById('export-json');
  304.         const exportCsvBtn = document.getElementById('export-csv');
  305.         const clearAssignmentsBtn = document.getElementById('clear-assignments');
  306.         const exportPngBtn = document.getElementById('export-png');
  307.         const exportPdfBtn = document.getElementById('export-pdf');
  308.         const participantsContainer = document.getElementById('participants-container');
  309.         const seatingTable = document.getElementById('seating-table');
  310.         const assignmentContainer = document.getElementById('assignment-container');
  311.         const seatingChartContainer = document.getElementById('seating-chart-container');
  312.          
  313.         // 事件监听器设置
  314.         function setupEventListeners() {
  315.             generateBtn.addEventListener('click', generateSeatingChart);
  316.             excelFileInput.addEventListener('change', handleExcelFile);
  317.             exportJsonBtn.addEventListener('click', exportToJson);
  318.             exportCsvBtn.addEventListener('click', exportToCsv);
  319.             clearAssignmentsBtn.addEventListener('click', clearAssignments);
  320.             exportPngBtn.addEventListener('click', exportToPng);
  321.             exportPdfBtn.addEventListener('click', exportToPdf);
  322.         }
  323.          
  324.         // 处理Excel文件
  325.         function handleExcelFile(event) {
  326.             const file = event.target.files[0];
  327.             if (!file) return;
  328.             
  329.             const reader = new FileReader();
  330.             reader.onload = function(e) {
  331.                 const data = new Uint8Array(e.target.result);
  332.                 const workbook = XLSX.read(data, {type: 'array'});
  333.                  
  334.                 // 获取第一个工作表
  335.                 const firstSheetName = workbook.SheetNames[0];
  336.                 const worksheet = workbook.Sheets[firstSheetName];
  337.                  
  338.                 // 将工作表转换为JSON
  339.                 const jsonData = XLSX.utils.sheet_to_json(worksheet, {header: ['name', 'unit'], range: 1});
  340.                  
  341.                 // 处理导入的数据
  342.                 participants = jsonData.filter(item => item.name && item.name.trim() !== '');
  343.                  
  344.                 // 更新参会人员列表显示
  345.                 updateParticipantsDisplay();
  346.             };
  347.             reader.readAsArrayBuffer(file);
  348.         }
  349.          
  350.         // 更新参会人员列表显示
  351.         function updateParticipantsDisplay() {
  352.             participantsContainer.innerHTML = '';
  353.             
  354.             if (participants.length === 0) {
  355.                 participantsContainer.innerHTML = '<p>暂无参会人员</p>';
  356.                 return;
  357.             }
  358.             
  359.             // 添加参会人员到可拖拽列表
  360.             participants.forEach(person => {
  361.                 // 检查该人员是否已被分配座位
  362.                 let isAssigned = false;
  363.                 for (const seatId in seatAssignments) {
  364.                     if (seatAssignments[seatId].name === person.name &&
  365.                         seatAssignments[seatId].unit === person.unit) {
  366.                         isAssigned = true;
  367.                         break;
  368.                     }
  369.                 }
  370.                  
  371.                 // 如果未被分配,添加到可拖拽列表
  372.                 if (!isAssigned) {
  373.                     const participantItem = document.createElement('div');
  374.                     participantItem.classList.add('participant-item');
  375.                      
  376.                     // 显示姓名和单位
  377.                     participantItem.innerHTML = `
  378.                         <div>${person.name}</div>
  379.                         <div class="participant-info">${person.unit || '无单位信息'}</div>
  380.                     `;
  381.                      
  382.                     participantItem.draggable = true;
  383.                      
  384.                     participantItem.addEventListener('dragstart', function() {
  385.                         draggedParticipant = {
  386.                             name: person.name,
  387.                             unit: person.unit
  388.                         };
  389.                     });
  390.                      
  391.                     participantsContainer.appendChild(participantItem);
  392.                 }
  393.             });
  394.             
  395.             if (participantsContainer.children.length === 0) {
  396.                 participantsContainer.innerHTML = '<p>所有人员已分配座位</p>';
  397.             }
  398.         }
  399.          
  400.         // 生成座次表
  401.         function generateSeatingChart() {
  402.             console.log("生成座次表按钮被点击");
  403.             
  404.             const rows = parseInt(document.getElementById('rows').value);
  405.             const cols = parseInt(document.getElementById('cols').value);
  406.             const aislesInput = document.getElementById('aisles').value;
  407.             
  408.             // 解析过道位置
  409.             const aislePositions = [];
  410.             if (aislesInput.trim() !== '') {
  411.                 aislesInput.split(',').forEach(pos => {
  412.                     const num = parseInt(pos.trim());
  413.                     if (!isNaN(num) && num > 0 && num <= cols) {
  414.                         aislePositions.push(num);
  415.                     }
  416.                 });
  417.             }
  418.             
  419.             // 排序过道位置
  420.             aislePositions.sort((a, b) => a - b);
  421.             
  422.             // 清空现有表格
  423.             seatingTable.innerHTML = '';
  424.             
  425.             // 计算总列数(包括过道)
  426.             const totalCols = cols + aislePositions.length;
  427.             
  428.             // 创建表头行(列标签)
  429.             const headerRow = document.createElement('tr');
  430.             const cornerCell = document.createElement('td');
  431.             cornerCell.classList.add('col-label');
  432.             headerRow.appendChild(cornerCell);
  433.             
  434.             let colCounter = 1;
  435.             for (let j = 1; j <= cols + aislePositions.length; j++) {
  436.                 // 检查这个位置是否需要放置过道
  437.                 const isAislePosition = aislePositions.length > 0 &&
  438.                                       aislePositions[0] === colCounter;
  439.                  
  440.                 const colHeader = document.createElement('td');
  441.                 colHeader.classList.add('col-label');
  442.                  
  443.                 if (isAislePosition) {
  444.                     colHeader.textContent = '过道';
  445.                     colHeader.classList.add('aisle');
  446.                     aislePositions.shift(); // 移除已处理的过道
  447.                 } else {
  448.                     //colHeader.textContent = `列 ${colCounter}`;
  449.                     colHeader.textContent = `${colCounter}`;
  450.                     colCounter++;
  451.                 }
  452.                  
  453.                 headerRow.appendChild(colHeader);
  454.             }
  455.             seatingTable.appendChild(headerRow);
  456.             
  457.             // 创建座位行
  458.             for (let i = 0; i < rows; i++) {
  459.                 const row = document.createElement('tr');
  460.                  
  461.                 // 添加行标签
  462.                 const rowLabel = document.createElement('td');
  463.                 rowLabel.classList.add('row-label');
  464.                 //rowLabel.textContent = `行 ${i + 1}`;
  465.                 rowLabel.textContent = `${i + 1}`;
  466.                 row.appendChild(rowLabel);
  467.                  
  468.                 // 重置过道位置数组
  469.                 const currentAislePositions = [];
  470.                 if (document.getElementById('aisles').value.trim() !== '') {
  471.                     document.getElementById('aisles').value.split(',').forEach(pos => {
  472.                         const num = parseInt(pos.trim());
  473.                         if (!isNaN(num) && num > 0 && num <= cols) {
  474.                             currentAislePositions.push(num);
  475.                         }
  476.                     });
  477.                 }
  478.                 currentAislePositions.sort((a, b) => a - b);
  479.                  
  480.                 let colCounter = 1;
  481.                 for (let j = 1; j <= cols + currentAislePositions.length; j++) {
  482.                     // 检查这个位置是否需要放置过道
  483.                     const isAislePosition = currentAislePositions.length > 0 &&
  484.                                           currentAislePositions[0] === colCounter;
  485.                      
  486.                     if (isAislePosition) {
  487.                         const aisle = document.createElement('td');
  488.                         aisle.classList.add('aisle');
  489.                         
  490.                         const aisleLabel = document.createElement('div');
  491.                         aisleLabel.classList.add('aisle-label');
  492.                         aisleLabel.textContent = '过道';
  493.                         aisle.appendChild(aisleLabel);
  494.                         
  495.                         row.appendChild(aisle);
  496.                         currentAislePositions.shift(); // 移除已处理的过道
  497.                     } else {
  498.                         const seat = document.createElement('td');
  499.                         const seatId = `${i+1}行${colCounter}列`;
  500.                         seat.dataset.row = i;
  501.                         seat.dataset.col = colCounter;
  502.                         seat.dataset.seatId = seatId;
  503.                         
  504.                         // 添加座位编号
  505.                         const seatNumber = document.createElement('div');
  506.                         seatNumber.classList.add('seat-number');
  507.                         seatNumber.textContent = seatId;
  508.                         seat.appendChild(seatNumber);
  509.                         
  510.                         // 检查此座位是否已有分配
  511.                         if (seatAssignments[seatId]) {
  512.                             seat.textContent = seatAssignments[seatId].name;
  513.                             seat.classList.add('occupied');
  514.                             seat.appendChild(seatNumber);
  515.                         }
  516.                         
  517.                         // 添加拖放事件监听
  518.                         seat.addEventListener('dragover', function(e) {
  519.                             e.preventDefault();
  520.                         });
  521.                         
  522.                         seat.addEventListener('drop', function(e) {
  523.                             e.preventDefault();
  524.                             if (draggedParticipant) {
  525.                                 const seatId = this.dataset.seatId;
  526.                                 this.textContent = draggedParticipant.name;
  527.                                 this.classList.add('occupied');
  528.                                  
  529.                                 // 保留座位编号
  530.                                 const seatNum = this.querySelector('.seat-number');
  531.                                 if (seatNum) {
  532.                                     this.appendChild(seatNum);
  533.                                 }
  534.                                  
  535.                                 // 记录座位分配
  536.                                 seatAssignments[seatId] = {
  537.                                     name: draggedParticipant.name,
  538.                                     unit: draggedParticipant.unit
  539.                                 };
  540.                                  
  541.                                 // 更新分配记录显示
  542.                                 updateAssignmentDisplay();
  543.                                  
  544.                                 // 从参会人员列表中移除已安排的人员
  545.                                 updateParticipantsDisplay();
  546.                                  
  547.                                 draggedParticipant = null;
  548.                             }
  549.                         });
  550.                         
  551.                         row.appendChild(seat);
  552.                         colCounter++;
  553.                     }
  554.                 }
  555.                  
  556.                 seatingTable.appendChild(row);
  557.             }
  558.             
  559.             // 更新分配记录显示
  560.             updateAssignmentDisplay();
  561.         }
  562.          
  563.         // 更新分配记录显示
  564.         function updateAssignmentDisplay() {
  565.             assignmentContainer.innerHTML = '';
  566.             
  567.             if (Object.keys(seatAssignments).length === 0) {
  568.                 assignmentContainer.innerHTML = '<p>尚未进行座位分配</p>';
  569.                 return;
  570.             }
  571.             
  572.             // 按座位编号排序
  573.             const sortedSeats = Object.keys(seatAssignments).sort((a, b) => {
  574.                 const [aRow, aCol] = a.split('-').map(Number);
  575.                 const [bRow, bCol] = b.split('-').map(Number);
  576.                 return aRow - bRow || aCol - bCol;
  577.             });
  578.             
  579.             sortedSeats.forEach(seatId => {
  580.                 const assignmentItem = document.createElement('div');
  581.                 assignmentItem.classList.add('assignment-item');
  582.                 assignmentItem.innerHTML = `
  583.                     <div><strong>座位 ${seatId}:</strong> ${seatAssignments[seatId].name}</div>
  584.                     <div class="participant-info">${seatAssignments[seatId].unit || '无单位信息'}</div>
  585.                 `;
  586.                 assignmentContainer.appendChild(assignmentItem);
  587.             });
  588.         }
  589.          
  590.         // 导出为JSON
  591.         function exportToJson() {
  592.             if (Object.keys(seatAssignments).length === 0) {
  593.                 alert('没有座位分配数据可导出');
  594.                 return;
  595.             }
  596.             
  597.             const dataStr = JSON.stringify(seatAssignments, null, 2);
  598.             const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
  599.             
  600.             const exportFileDefaultName = '座位分配.json';
  601.             
  602.             const linkElement = document.createElement('a');
  603.             linkElement.setAttribute('href', dataUri);
  604.             linkElement.setAttribute('download', exportFileDefaultName);
  605.             linkElement.click();
  606.         }
  607.          
  608.         // 导出为CSV
  609.         function exportToCsv() {
  610.             if (Object.keys(seatAssignments).length === 0) {
  611.                 alert('没有座位分配数据可导出');
  612.                 return;
  613.             }
  614.             
  615.             // 按座位编号排序
  616.             const sortedSeats = Object.keys(seatAssignments).sort((a, b) => {
  617.                 const [aRow, aCol] = a.split('-').map(Number);
  618.                 const [bRow, bCol] = b.split('-').map(Number);
  619.                 return aRow - bRow || aCol - bCol;
  620.             });
  621.             
  622.             let csvContent = "座位编号,姓名,单位\n";
  623.             
  624.             sortedSeats.forEach(seatId => {
  625.                 csvContent += `"${seatId}","${seatAssignments[seatId].name}","${seatAssignments[seatId].unit || ''}"\n`;
  626.             });
  627.             
  628.             const encodedUri = 'data:text/csv;charset=utf-8,' + encodeURIComponent(csvContent);
  629.             const linkElement = document.createElement('a');
  630.             linkElement.setAttribute('href', encodedUri);
  631.             linkElement.setAttribute('download', '座位分配.csv');
  632.             linkElement.click();
  633.         }
  634.          
  635.         // 导出为PNG图片
  636.         function exportToPng() {
  637.             // 检查是否有座次表
  638.             if (seatingTable.rows.length === 0) {
  639.                 alert('请先生成座次表');
  640.                 return;
  641.             }
  642.             
  643.             // 使用html2canvas将座次表转换为图片
  644.             html2canvas(seatingChartContainer, {
  645.                 scale: 2, // 提高导出图片的质量
  646.                 logging: false,
  647.                 useCORS: true,
  648.                 allowTaint: true
  649.             }).then(canvas => {
  650.                 // 创建下载链接
  651.                 const link = document.createElement('a');
  652.                 link.download = '会议座次图.png';
  653.                 link.href = canvas.toDataURL('image/png');
  654.                 link.click();
  655.             }).catch(err => {
  656.                 console.error('导出图片失败:', err);
  657.                 alert('导出图片失败,请重试');
  658.             });
  659.         }
  660.          
  661.         // 导出为PDF
  662.         function exportToPdf() {
  663.             // 检查是否有座次表
  664.             if (seatingTable.rows.length === 0) {
  665.                 alert('请先生成座次表');
  666.                 return;
  667.             }
  668.             
  669.             // 使用html2canvas将座次表转换为图片
  670.             html2canvas(seatingChartContainer, {
  671.                 scale: 2, // 提高导出图片的质量
  672.                 logging: false,
  673.                 useCORS: true,
  674.                 allowTaint: true
  675.             }).then(canvas => {
  676.                 // 创建PDF文档
  677.                 const { jsPDF } = window.jspdf;
  678.                 const pdf = new jsPDF({
  679.                     orientation: 'landscape', // 横向
  680.                     unit: 'mm'
  681.                 });
  682.                  
  683.                 // 计算PDF页面尺寸
  684.                 const imgData = canvas.toDataURL('image/png');
  685.                 const imgWidth = 280; // A4横向宽度(297mm)减去边距
  686.                 const imgHeight = canvas.height * imgWidth / canvas.width;
  687.                  
  688.                 // 添加图片到PDF
  689.                 pdf.addImage(imgData, 'PNG', 10, 10, imgWidth, imgHeight);
  690.                  
  691.                 // 保存PDF文件
  692.                 pdf.save('会议座次图.pdf');
  693.             }).catch(err => {
  694.                 console.error('导出PDF失败:', err);
  695.                 alert('导出PDF失败,请重试');
  696.             });
  697.         }
  698.          
  699.         // 清空分配记录
  700.         function clearAssignments() {
  701.             if (confirm('确定要清空所有座位分配记录吗?')) {
  702.                 seatAssignments = {};
  703.                 updateAssignmentDisplay();
  704.                 // 重新生成座次表以清除座位上的分配
  705.                 generateSeatingChart();
  706.                 // 重新导入参会人员以恢复所有人员到可拖拽列表
  707.                 updateParticipantsDisplay();
  708.             }
  709.         }
  710.          
  711.         // 页面加载完成后初始化
  712.         document.addEventListener('DOMContentLoaded', function() {
  713.             console.log("DOM已加载完成");
  714.             setupEventListeners();
  715.             generateSeatingChart();
  716.         });
  717.     </script>
  718. </body>
  719. </html>
复制代码


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表