|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710 |
- <!DOCTYPE html>
- <html>
-
- <head>
- <title>Excel 编辑器</title>
- <style>
- body {
- margin: 0;
- padding: 0;
- font-family: Arial, sans-serif;
- font-size: 12px;
- background-color: #f0f0f0;
- color: #333;
-
- }
-
- .container {
- max-width: 100%;
- }
-
- .toolbar {
- position: sticky;
- top: 0;
- background: #fff;
- padding: 10px 0;
- margin-bottom: 15px;
- border-bottom: 1px solid #ddd;
- z-index: 100;
- display: flex;
- gap: 10px;
- align-items: center;
- }
-
- .file-controls {
- display: flex;
- gap: 10px;
- }
-
- .column-controls {
- margin-left: 20px;
- display: flex;
- gap: 10px;
- flex-wrap: wrap;
- align-items: center;
- }
-
- .column-checkbox {
- display: inline-flex;
- align-items: center;
- gap: 4px;
- padding: 4px 8px;
- background: #f5f5f5;
- border-radius: 4px;
- cursor: pointer;
- }
-
- .column-checkbox:hover {
- background: #e8e8e8;
- }
-
- button {
- padding: 6px 12px;
- background: #4CAF50;
- color: white;
- border: none;
- border-radius: 4px;
- cursor: pointer;
- }
-
- button:hover {
- background: #45a049;
- }
-
- input[type="file"] {
- padding: 6px;
- border: 1px solid #ddd;
- border-radius: 4px;
- }
-
- table {
- border-collapse: collapse;
- width: 100%;
- background: white;
- font-size: 12px;
- }
-
- th,
- td {
- border: 1px solid #ddd;
- padding: 0;
- vertical-align: top;
- }
-
- th {
- background: #f5f5f5;
- position: sticky;
- top: 60px;
- z-index: 90;
- }
-
- textarea {
- width: 100%;
- min-height: 100px;
- resize: vertical;
- padding: 0;
- border: 0;
- border-radius: 4px;
- font-size: 12px;
- box-sizing: border-box;
- }
-
- .index-column {
- width: 60px;
- text-align: center;
- background-color: #f5f5f5;
- position: sticky;
- left: 0;
- z-index: 80;
- }
-
- th.index-column {
- z-index: 91;
- }
-
- .image-upload-container {
- margin-top: 5px;
- }
-
- .preview-button {
- margin-top: 5px;
- background: #2196F3;
- }
-
- .preview-button:hover {
- background: #1976D2;
- }
-
- .preview-modal {
- display: none;
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: rgba(0, 0, 0, 0.7);
- z-index: 1000;
- }
-
- .preview-content {
- position: relative;
- width: 80%;
- height: 80%;
- margin: 5% auto;
- background: white;
- padding: 20px;
- overflow: auto;
- border-radius: 8px;
- }
-
- .close-preview {
- position: absolute;
- right: 20px;
- top: 10px;
- font-size: 24px;
- cursor: pointer;
- }
-
- .h5-preview-content {
- max-width: 375px;
- margin: 0 auto;
- background: #fff;
- }
-
- .h5-preview-content img {
- max-width: 100%;
- height: auto;
- }
-
- .hidden-column {
- display: none;
- }
-
- .quick-actions {
- margin-left: 20px;
- display: flex;
- gap: 10px;
- }
-
- .help-button {
- background: #607D8B;
- color: white;
- border: none;
- border-radius: 50%;
- width: 24px;
- height: 24px;
- cursor: pointer;
- font-weight: bold;
- }
-
- .help-modal {
- display: none;
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: rgba(0, 0, 0, 0.7);
- z-index: 1000;
- }
-
- .help-content {
- position: relative;
- width: 60%;
- max-height: 80%;
- margin: 5% auto;
- background: white;
- padding: 20px;
- border-radius: 8px;
- overflow-y: auto;
- }
-
- .shortcut-key {
- background: #f0f0f0;
- padding: 2px 6px;
- border-radius: 3px;
- font-family: monospace;
- }
-
- .status-bar {
- position: fixed;
- bottom: 0;
- left: 0;
- right: 0;
- background: #f5f5f5;
- padding: 8px 20px;
- border-top: 1px solid #ddd;
- display: flex;
- justify-content: space-between;
- z-index: 100;
- }
-
- .unsaved-changes {
- color: #f44336;
- font-weight: bold;
- }
-
- .save-reminder {
- position: fixed;
- bottom: 40px;
- right: 20px;
- background: #ff9800;
- color: white;
- padding: 10px 20px;
- border-radius: 4px;
- display: none;
- animation: bounce 1s infinite;
- }
-
- @keyframes bounce {
-
- 0%,
- 100% {
- transform: translateY(0);
- }
-
- 50% {
- transform: translateY(-5px);
- }
- }
-
- .search-box {
- padding: 6px 12px;
- border: 1px solid #ddd;
- border-radius: 4px;
- margin-right: 10px;
- }
-
- .highlight {
- background-color: #fff176;
- }
-
- .column-preset {
- padding: 4px 8px;
- background: #e3f2fd;
- border: 1px solid #90caf9;
- border-radius: 4px;
- cursor: pointer;
- margin-right: 10px;
- }
- </style>
- </head>
-
- <body>
- <div class="container">
- <div class="toolbar">
- <div class="file-controls">
- <input type="file" id="fileInput" accept=".xlsx">
- <button onclick="uploadFile()">上传</button>
- <button onclick="saveChanges()" id="saveButton">保存</button>
- <button class="help-button" onclick="showHelp()">?</button>
- </div>
- <div class="quick-actions">
- <input type="text" class="search-box" placeholder="搜索内容..." onkeyup="searchContent(this.value)">
- <button onclick="toggleCommonColumns()">常用列</button>
- <button onclick="toggleAllColumns()">显示/隐藏所有列</button>
- </div>
-
- </div>
- <div class="column-controls" id="columnControls">
- <!-- 列控制复选框 -->
- </div>
- <div id="tableContainer"></div>
- </div>
-
- <!-- 帮助模态框 -->
- <div id="helpModal" class="help-modal">
- <div class="help-content">
- <span class="close-preview" onclick="closeHelp()">×</span>
- <h2>使用帮助</h2>
- <h3>快捷键</h3>
- <ul>
- <li><span class="shortcut-key">Ctrl + S</span> - 保存更改</li>
- <li><span class="shortcut-key">Ctrl + F</span> - 搜索内容</li>
- <li><span class="shortcut-key">Tab</span> - 切换到下一个单元格</li>
- <li><span class="shortcut-key">Shift + Tab</span> - 切换到上一个单元格</li>
- </ul>
- <h3>常用功能</h3>
- <ul>
- <li>点击"常用列"可以快速显示常用编辑的列</li>
- <li>使用搜索框可以快速定位内容</li>
- <li>双击单元格可以快速编辑</li>
- <li>图片上传支持多选</li>
- </ul>
- <h3>注意事项</h3>
- <ul>
- <li>有未保存的更改时会有提醒</li>
- <li>建议定期保存更改</li>
- <li>图片上传完成后请等待提示再继续操作</li>
- </ul>
- </div>
- </div>
-
- <div class="status-bar">
- <span id="statusText">就绪</span>
- <span id="unsavedChanges" class="unsaved-changes" style="display: none;">有未保存的更改</span>
- </div>
-
- <div id="saveReminder" class="save-reminder">
- 请记得保存更改!
- </div>
-
- <div id="previewModal" class="preview-modal">
- <div class="preview-content">
- <span class="close-preview" onclick="closePreview()">×</span>
- <div id="previewContent"></div>
- </div>
- </div>
-
- <script src="https://gosspublic.alicdn.com/aliyun-oss-sdk-6.16.0.min.js"></script>
- <script>
- let headers = [];
- let data = [];
- let columnVisibility = {};
- let hasUnsavedChanges = false;
- const commonColumns = ['SKU商品描述(PC)', 'SKU商品描述(h5)', 'SKU商品名称'];
-
- function initColumnControls() {
- const container = document.getElementById('columnControls');
- container.innerHTML = headers.map(header => `
- <label class="column-checkbox">
- <input type="checkbox"
- checked
- onchange="toggleColumn('${header}')"
- data-column="${header}">
- ${header}
- </label>
- `).join('');
-
- // 初始化列可见性状态
- headers.forEach(header => {
- columnVisibility[header] = true;
- });
- }
-
- function toggleColumn(header) {
- columnVisibility[header] = !columnVisibility[header];
- const cells = document.querySelectorAll(`[data-column="${header}"]`);
- cells.forEach(cell => {
- if (cell.tagName === 'INPUT') {
- // 复选框状态
- cell.checked = columnVisibility[header];
- } else {
- // 表格单元格
- cell.classList.toggle('hidden-column');
- }
- });
- }
-
- function renderTable() {
- const container = document.getElementById('tableContainer');
- let html = '<table><tr>';
-
- html += '<th class="index-column">序号</th>';
- headers.forEach(header => {
- html += `<th data-column="${header}" ${!columnVisibility[header] ? 'class="hidden-column"' : ''}>${header}</th>`;
- });
- html += '</tr>';
-
- data.forEach((row, rowIndex) => {
- html += '<tr>';
- html += `<td class="index-column">${rowIndex + 1}</td>`;
- headers.forEach(header => {
- html += `<td data-column="${header}" ${!columnVisibility[header] ? 'class="hidden-column"' : ''}>
- <textarea
- data-row="${rowIndex}"
- data-header="${header}"
- onchange="updateData(${rowIndex}, '${header}', this.value)"
- >${row[header] || ''}</textarea>
- ${header === 'SKU商品描述(PC)' ? `
- <div class="image-upload-container">
- <input type="file" accept="image/*"
- onchange="uploadImage(this, ${rowIndex}, '${header}')" multiple>
- </div>
- <button class="preview-button" onclick="previewContent(${rowIndex}, '${header}', 'pc')">
- 预览
- </button>
- ` : ''}
- ${header === 'SKU商品描述(h5)' ? `
- <button class="preview-button" onclick="previewContent(${rowIndex}, '${header}', 'h5')">
- 预览
- </button>
- ` : ''}
- </td>`;
- });
- html += '</tr>';
- });
-
- html += '</table>';
- container.innerHTML = html;
- }
-
- async function uploadFile() {
- const fileInput = document.getElementById('fileInput');
- const file = fileInput.files[0];
- if (!file) return;
-
- const formData = new FormData();
- formData.append('excelFile', file);
-
- const response = await fetch('/upload', {
- method: 'POST',
- body: formData
- });
- const result = await response.json();
-
- headers = result.headers;
- data = result.data;
-
- // 初始化列控制
- initColumnControls();
- renderTable();
- }
-
- function updateData(rowIndex, header, value) {
- data[rowIndex][header] = value;
- hasUnsavedChanges = true;
- document.getElementById('unsavedChanges').style.display = 'block';
- document.getElementById('saveReminder').style.display = 'block';
- updateStatus('有未保存的更改');
- }
-
- async function saveChanges() {
- updateStatus('正在保存...');
- try {
- const response = await fetch('/save', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({ headers, data })
- });
-
- const result = await response.json();
-
- if (!response.ok) {
- if (result.message) {
- alert(result.message);
- } else {
- alert('保存失败:' + (result.error || '未知错误'));
- }
- return;
- }
-
- window.location.href = result.downloadUrl;
- hasUnsavedChanges = false;
- document.getElementById('unsavedChanges').style.display = 'none';
- document.getElementById('saveReminder').style.display = 'none';
- updateStatus('保存成功');
- } catch (error) {
- updateStatus('保存失败:' + error.message);
- }
- }
-
- async function uploadImage(input, rowIndex, header) {
- const files = input.files;
- if (!files.length) return;
-
- try {
- updateStatus('正在上传图片...');
-
- const formData = new FormData();
- Array.from(files).forEach(file => {
- formData.append('images', file);
- });
-
- const response = await fetch('/upload-images', {
- method: 'POST',
- body: formData
- });
-
- if (!response.ok) {
- throw new Error('上传失败');
- }
-
- const results = await response.json();
-
- // 生成PC描述HTML
- const pcImageHtml = results.map(result =>
- `<p><img src="${result.url}" title="${result.name}"></p>`
- ).join('\n');
-
- // 生成H5描述JSON
- const h5ImageJson = results.map(result => ({
- type: "image",
- content: result.url,
- checked: false,
- edit_checked: false
- }));
-
- // 更新PC描述
- const pcTextarea = input.parentElement.previousElementSibling;
- const currentPcValue = pcTextarea.value || '';
- pcTextarea.value = currentPcValue + (currentPcValue ? '\n' : '') + pcImageHtml;
- updateData(rowIndex, header, pcTextarea.value);
-
- // 更新H5描述
- const h5Header = 'SKU商品描述(h5)';
- const currentH5Value = data[rowIndex][h5Header] || '[]';
- let h5Data;
- try {
- h5Data = JSON.parse(currentH5Value);
- if (!Array.isArray(h5Data)) h5Data = [];
- } catch {
- h5Data = [];
- }
-
- // 合并新的图片数据
- h5Data.push(...h5ImageJson);
-
- // 更新H5数据
- const h5Value = JSON.stringify(h5Data);
- updateData(rowIndex, h5Header, h5Value);
-
- // 更新表格显示
- const h5Textarea = document.querySelector(`textarea[data-row="${rowIndex}"][data-header="${h5Header}"]`);
- if (h5Textarea) {
- h5Textarea.value = h5Value;
- }
-
- // 清空文件输入框
- input.value = '';
-
- updateStatus('图片上传成功');
-
- } catch (error) {
- console.error('Upload error:', error);
- alert('上传图片失败:' + error.message);
- updateStatus('图片上传失败');
- }
- }
-
- function previewContent(rowIndex, header, type) {
- const content = data[rowIndex][header] || '';
- const modal = document.getElementById('previewModal');
- const previewContent = document.getElementById('previewContent');
-
- if (type === 'pc') {
- // PC预览直接显示HTML
- previewContent.innerHTML = content;
- } else if (type === 'h5') {
- // H5预览需要解析JSON并生成预览内容
- try {
- const h5Data = JSON.parse(content);
- const h5Html = `
- <div class="h5-preview-content">
- ${h5Data.map(item => {
- if (item.type === 'image') {
- return `<img src="${item.content}" alt="">`;
- }
- return '';
- }).join('')}
- </div>
- `;
- previewContent.innerHTML = h5Html;
- } catch (error) {
- previewContent.innerHTML = '预览失败:无效的JSON格式';
- }
- }
-
- modal.style.display = 'block';
- }
-
- function closePreview() {
- document.getElementById('previewModal').style.display = 'none';
- }
-
- // 点击模态框背景关闭预览
- document.getElementById('previewModal').addEventListener('click', function (e) {
- if (e.target === this) {
- closePreview();
- }
- });
-
- // 添加快捷键支持
- document.addEventListener('keydown', function (e) {
- if (e.ctrlKey && e.key === 's') {
- e.preventDefault();
- saveChanges();
- }
- if (e.ctrlKey && e.key === 'f') {
- e.preventDefault();
- document.querySelector('.search-box').focus();
- }
- });
-
- // 搜索功能
- function searchContent(searchText) {
- if (!searchText) {
- clearHighlights();
- return;
- }
-
- const textareas = document.querySelectorAll('textarea');
- textareas.forEach(textarea => {
- const content = textarea.value.toLowerCase();
- if (content.includes(searchText.toLowerCase())) {
- textarea.classList.add('highlight');
- textarea.parentElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
- } else {
- textarea.classList.remove('highlight');
- }
- });
- }
-
- function clearHighlights() {
- document.querySelectorAll('.highlight').forEach(el => {
- el.classList.remove('highlight');
- });
- }
-
- // 常用列切换
- function toggleCommonColumns() {
- headers.forEach(header => {
- const isCommon = commonColumns.includes(header);
- columnVisibility[header] = isCommon;
- const checkbox = document.querySelector(`input[data-column="${header}"]`);
- if (checkbox) {
- checkbox.checked = isCommon;
- }
- });
- renderTable();
- }
-
- // 全部列切换
- function toggleAllColumns() {
- const allVisible = headers.every(header => columnVisibility[header]);
- headers.forEach(header => {
- columnVisibility[header] = !allVisible;
- const checkbox = document.querySelector(`input[data-column="${header}"]`);
- if (checkbox) {
- checkbox.checked = !allVisible;
- }
- });
- renderTable();
- }
-
- function updateStatus(text) {
- document.getElementById('statusText').textContent = text;
- }
-
- // 帮助功能
- function showHelp() {
- document.getElementById('helpModal').style.display = 'block';
- }
-
- function closeHelp() {
- document.getElementById('helpModal').style.display = 'none';
- }
-
- // 离开页面提醒
- window.addEventListener('beforeunload', function (e) {
- if (hasUnsavedChanges) {
- e.preventDefault();
- e.returnValue = '有未保存的更改,确定要离开吗?';
- }
- });
- </script>
- </body>
-
- </html>
|