support custom sheet

This commit is contained in:
Julian Freeman
2026-03-11 12:58:38 -04:00
parent fe88af6183
commit 47ee2d98e9
3 changed files with 41 additions and 16 deletions

View File

@@ -11,24 +11,40 @@
function doPost(e) { function doPost(e) {
try { try {
var data = JSON.parse(e.postData.contents); var data = JSON.parse(e.postData.contents);
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(); var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet;
// 1. Get or Create the target sheet
if (data.sheetName) {
sheet = ss.getSheetByName(data.sheetName);
if (!sheet) {
sheet = ss.insertSheet(data.sheetName);
}
} else {
sheet = ss.getActiveSheet();
}
// Get headers // 2. Get headers
var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0]; var lastCol = sheet.getLastColumn();
if (headers[0] === "" && sheet.getLastColumn() === 1) { var headers = [];
// New sheet, set headers from JSON keys if (lastCol > 0) {
headers = sheet.getRange(1, 1, 1, lastCol).getValues()[0];
}
// 3. If sheet is empty, set headers from JSON keys
if (headers.length === 0 || headers[0] === "") {
headers = Object.keys(data); headers = Object.keys(data);
sheet.appendRow(headers); sheet.appendRow(headers);
} }
// Map data to headers // 4. Map data to headers
var row = headers.map(function(header) { var row = headers.map(function(header) {
return data[header] || "N/A"; return data[header] !== undefined ? data[header] : "N/A";
}); });
sheet.appendRow(row); sheet.appendRow(row);
return ContentService.createTextOutput(JSON.stringify({ status: "success" })) return ContentService.createTextOutput(JSON.stringify({ status: "success", sheet: sheet.getName() }))
.setMimeType(ContentService.MimeType.JSON); .setMimeType(ContentService.MimeType.JSON);
} catch (error) { } catch (error) {
@@ -37,7 +53,6 @@ function doPost(e) {
} }
} }
// Handle preflight (for some environments, though 'no-cors' usually avoids this)
function doOptions(e) { function doOptions(e) {
return ContentService.createTextOutput("") return ContentService.createTextOutput("")
.setMimeType(ContentService.MimeType.TEXT); .setMimeType(ContentService.MimeType.TEXT);

View File

@@ -41,7 +41,8 @@
<div class="pt-4 border-t border-gray-100 space-y-3"> <div class="pt-4 border-t border-gray-100 space-y-3">
<label class="block text-xs font-bold text-gray-600 mb-1">预设管理</label> <label class="block text-xs font-bold text-gray-600 mb-1">预设管理</label>
<div class="space-y-2.5"> <div class="space-y-2.5">
<input type="text" id="newPresetName" class="w-full border border-gray-300 rounded px-2 py-1.5 text-sm outline-none" placeholder="预设名称 (如:手机)"> <input type="text" id="newPresetName" class="w-full border border-gray-300 rounded px-2 py-1.5 text-sm outline-none" placeholder="预设名称 (如:提取笔记本)">
<input type="text" id="newPresetSheet" class="w-full border border-gray-300 rounded px-2 py-1.5 text-sm outline-none" placeholder="目标工作表名称 (如:笔记本清单)">
<textarea id="newPresetFields" class="w-full border border-gray-300 rounded px-2 py-1.5 text-sm outline-none h-16" placeholder="提取字段 (用逗号分隔,如:品牌, 型号, 价格)"></textarea> <textarea id="newPresetFields" class="w-full border border-gray-300 rounded px-2 py-1.5 text-sm outline-none h-16" placeholder="提取字段 (用逗号分隔,如:品牌, 型号, 价格)"></textarea>
<button id="addPreset" class="w-full bg-indigo-50 text-indigo-700 text-xs py-2 rounded hover:bg-indigo-100 transition-colors font-semibold border border-indigo-200">添加新预设</button> <button id="addPreset" class="w-full bg-indigo-50 text-indigo-700 text-xs py-2 rounded hover:bg-indigo-100 transition-colors font-semibold border border-indigo-200">添加新预设</button>
</div> </div>

View File

@@ -10,6 +10,7 @@ const resultsArea = document.getElementById('results');
// Preset Management Elements // Preset Management Elements
const newPresetName = document.getElementById('newPresetName'); const newPresetName = document.getElementById('newPresetName');
const newPresetSheet = document.getElementById('newPresetSheet');
const newPresetFields = document.getElementById('newPresetFields'); const newPresetFields = document.getElementById('newPresetFields');
const addPresetBtn = document.getElementById('addPreset'); const addPresetBtn = document.getElementById('addPreset');
const presetList = document.getElementById('presetList'); const presetList = document.getElementById('presetList');
@@ -22,8 +23,8 @@ const configContent = document.getElementById('configContent');
const configChevron = document.getElementById('configChevron'); const configChevron = document.getElementById('configChevron');
const DEFAULT_PRESETS = [ const DEFAULT_PRESETS = [
{ id: 'p1', name: '提取笔记本', fields: '品牌, CPU, 内存, 存储, 价格, URL' }, { id: 'p1', name: '提取笔记本', sheetName: '笔记本', fields: '品牌, CPU, 内存, 存储, 价格, URL' },
{ id: 'p2', name: '提取外设', fields: '品牌, 型号, 连接方式, 电池寿命, 价格, URL' } { id: 'p2', name: '提取外设', sheetName: '外设', fields: '品牌, 型号, 连接方式, 电池寿命, 价格, URL' }
]; ];
let currentPresets = []; let currentPresets = [];
@@ -68,15 +69,17 @@ saveConfigBtn.addEventListener('click', () => {
// Preset Management Logic // Preset Management Logic
addPresetBtn.addEventListener('click', async () => { addPresetBtn.addEventListener('click', async () => {
const name = newPresetName.value.trim(); const name = newPresetName.value.trim();
const sheetName = newPresetSheet.value.trim();
const fields = newPresetFields.value.trim(); const fields = newPresetFields.value.trim();
if (!name || !fields) return alert('请输入名称和字段'); if (!name || !fields) return alert('请输入名称和字段');
const { userPresets = DEFAULT_PRESETS } = await chrome.storage.local.get('userPresets'); const { userPresets = DEFAULT_PRESETS } = await chrome.storage.local.get('userPresets');
const newPreset = { id: Date.now().toString(), name, fields }; const newPreset = { id: Date.now().toString(), name, sheetName, fields };
currentPresets = [...userPresets, newPreset]; currentPresets = [...userPresets, newPreset];
await chrome.storage.local.set({ userPresets: currentPresets }); await chrome.storage.local.set({ userPresets: currentPresets });
newPresetName.value = ''; newPresetName.value = '';
newPresetSheet.value = '';
newPresetFields.value = ''; newPresetFields.value = '';
renderPresets(currentPresets); renderPresets(currentPresets);
}); });
@@ -99,7 +102,7 @@ function renderPresets(presets) {
item.className = 'flex items-center justify-between bg-white p-3 mb-2 rounded-lg border border-gray-200 shadow-sm transition-all hover:border-indigo-200'; item.className = 'flex items-center justify-between bg-white p-3 mb-2 rounded-lg border border-gray-200 shadow-sm transition-all hover:border-indigo-200';
item.innerHTML = ` item.innerHTML = `
<div class="flex-1 overflow-hidden mr-3"> <div class="flex-1 overflow-hidden mr-3">
<div class="text-xs font-bold text-gray-800 truncate">${preset.name}</div> <div class="text-xs font-bold text-gray-800 truncate">${preset.name} ${preset.sheetName ? `<span class="text-indigo-500 ml-1">#${preset.sheetName}</span>` : ''}</div>
<div class="text-[11px] text-gray-400 truncate mt-1 leading-tight">${preset.fields}</div> <div class="text-[11px] text-gray-400 truncate mt-1 leading-tight">${preset.fields}</div>
</div> </div>
<button class="flex items-center justify-center w-7 h-7 rounded-full text-gray-300 hover:text-red-500 hover:bg-red-50 transition-all focus:outline-none border-none bg-transparent cursor-pointer" title="删除预设"> <button class="flex items-center justify-center w-7 h-7 rounded-full text-gray-300 hover:text-red-500 hover:bg-red-50 transition-all focus:outline-none border-none bg-transparent cursor-pointer" title="删除预设">
@@ -130,7 +133,7 @@ runPresetBtn.addEventListener('click', () => {
if (preset) handleExtraction('preset', preset); if (preset) handleExtraction('preset', preset);
}); });
sendCustomBtn.addEventListener('click', () => handleExtraction('custom', { fields: customInput.value })); sendCustomBtn.addEventListener('click', () => handleExtraction('custom', { fields: customInput.value, sheetName: '' }));
async function handleExtraction(type, presetObj) { async function handleExtraction(type, presetObj) {
const { geminiApiKey, googleScriptUrl, geminiModel } = await chrome.storage.local.get(['geminiApiKey', 'googleScriptUrl', 'geminiModel']); const { geminiApiKey, googleScriptUrl, geminiModel } = await chrome.storage.local.get(['geminiApiKey', 'googleScriptUrl', 'geminiModel']);
@@ -170,7 +173,13 @@ Title: ${pageData.title}`;
if (googleScriptUrl) { if (googleScriptUrl) {
updateStatus('保存至表格...', 'bg-purple-500 text-white'); updateStatus('保存至表格...', 'bg-purple-500 text-white');
await sendToGoogleScript(googleScriptUrl, { ...cleanedJson, preset_type: presetObj.name || 'custom', source_url: pageData.url, timestamp: new Date().toISOString() }); await sendToGoogleScript(googleScriptUrl, {
...cleanedJson,
preset_type: presetObj.name || 'custom',
sheetName: presetObj.sheetName || '',
source_url: pageData.url,
timestamp: new Date().toISOString()
});
updateStatus('成功', 'bg-green-500 text-white'); updateStatus('成功', 'bg-green-500 text-white');
} else { } else {
updateStatus('完成 (未配置脚本链接)', 'bg-yellow-500 text-white'); updateStatus('完成 (未配置脚本链接)', 'bg-yellow-500 text-white');