ai_member_xiaoyan/output/L1-S2-U21-L1_组件配置.html

255 lines
19 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>L1-S2-U21-L1 寻人启事 - 组件配置</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:#f0f2f5;color:#1a1a2e;padding:24px}
.toolbar{position:sticky;top:0;z-index:100;background:#fff;border-radius:10px;padding:16px 20px;margin-bottom:20px;box-shadow:0 2px 8px rgba(0,0,0,.1);display:flex;align-items:center;gap:16px;flex-wrap:wrap}
.toolbar h1{font-size:20px;flex:1}
.btn{padding:10px 20px;border:none;border-radius:8px;font-size:14px;font-weight:600;cursor:pointer;transition:all .2s}
.btn-push{background:#0984e3;color:#fff}
.btn-push:hover{background:#0773c5}
.btn-push:disabled{background:#b2bec3;cursor:not-allowed}
.btn-refresh{background:#00b894;color:#fff}
.btn-refresh:hover{background:#00a381}
.status{font-size:13px;padding:6px 12px;border-radius:6px}
.status-ok{background:#d4edda;color:#155724}
.status-err{background:#f8d7da;color:#721c24}
.status-info{background:#d1ecf1;color:#0c5460}
.summary{background:#fff;border-radius:10px;padding:16px 20px;margin-bottom:20px;box-shadow:0 1px 3px rgba(0,0,0,.06);display:flex;gap:24px;flex-wrap:wrap}
.summary-item{text-align:center}
.summary-num{font-size:28px;font-weight:700;color:#0984e3}
.summary-label{font-size:12px;color:#888}
.card{background:#fff;border-radius:10px;padding:20px;margin-bottom:14px;box-shadow:0 1px 3px rgba(0,0,0,.06);border-left:4px solid #0984e3;transition:border-color .3s}
.card.modified{border-left-color:#f39c12}
.card-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;flex-wrap:wrap;gap:8px}
.card-type{font-weight:700;font-size:15px;color:#0984e3}
.card-meta{font-size:12px;color:#888}
.card-body{font-size:13px;line-height:1.8}
.field{margin-bottom:4px}
.field-label{font-weight:600;color:#555}
.field-value{color:#333}
.config-textarea{width:100%;min-height:120px;background:#f8f9fb;border:1px solid #e8eaed;border-radius:6px;padding:12px;font-family:"SF Mono",Menlo,Consolas,monospace;font-size:12px;line-height:1.7;resize:vertical;margin-top:8px;color:#2d3436}
.config-textarea:focus{outline:none;border-color:#0984e3;box-shadow:0 0 0 3px rgba(9,132,227,.1)}
.tag{display:inline-block;background:#e8eaed;color:#666;padding:2px 8px;border-radius:4px;font-size:11px;margin-right:4px}
.tag-k{background:#fff3cd;color:#b8860b}
.tag-id{background:#d1ecf1;color:#0c5460}
.modal{display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.5);z-index:1000;justify-content:center;align-items:center}
.modal.show{display:flex}
.modal-content{background:#fff;border-radius:12px;padding:24px;max-width:500px;width:90%;max-height:80vh;overflow-y:auto}
.modal h3{margin-bottom:12px}
.modal pre{background:#2d3436;color:#dfe6e9;padding:12px;border-radius:8px;font-size:12px;overflow-x:auto;white-space:pre-wrap;max-height:300px;overflow-y:auto}
.modal .btn{margin-top:12px}
.progress{display:none;margin-top:8px;font-size:12px;color:#888}
.progress.show{display:block}
</style>
</head>
<body>
<div class="toolbar">
<h1>L1-S2-U21-L1 寻人启事</h1>
<span id="status" class="status status-info">共 16 个组件</span>
<button class="btn btn-push" id="btnPush" onclick="pushToFeishu()">📤 推送到飞书</button>
<button class="btn btn-refresh" onclick="location.reload()">🔄 刷新</button>
<div class="progress" id="progress"></div>
</div>
<div class="summary">
<div class="summary-item"><div class="summary-num" id="totalCount">16</div><div class="summary-label">组件总数</div></div>
<div class="summary-item"><div class="summary-num" id="modifiedCount">0</div><div class="summary-label">已修改</div></div>
</div>
<div id="cards"></div>
<div class="modal" id="modal">
<div class="modal-content">
<h3 id="modalTitle">推送结果</h3>
<pre id="modalBody"></pre>
<button class="btn btn-push" onclick="document.getElementById('modal').classList.remove('show')">关闭</button>
</div>
</div>
<script>
const SPREADSHEET_TOKEN = "Qv55sAjZHhzF3ttnSJQc2sfMn8f";
const SHEET_ID = "wMQVyV";
const COMPONENTS = [{"row": 31, "type": "图片单选", "id": "1221101", "config_info": "S1主线", "role": "Sally", "en_text": "Logi wears a blue baseball cap.", "config": "【任务标题】选择棒球帽\n\n【情境引入】\nSally: Logi wears a blue baseball cap.\n\n【互动内容】\n题干Choose Logi's $baseball cap$.\n选项\n00\n正确00\n\n【互动反馈】\n正确无\n错误Sally: Look again. Is that a blue baseball cap?\n\n【后置对话】\n无", "knowledge": "baseball cap", "desc": ""}, {"row": 37, "type": "图片单选", "id": "1221102", "config_info": "S2主线", "role": "Sally", "en_text": "He wears glasses.", "config": "【任务标题】选择眼镜\n\n【情境引入】\nSally: Logi wears glasses.\n\n【互动内容】\n题干Choose Logi's $glasses$.\n选项\n00\n正确00\n\n【互动反馈】\n正确无\n错误Sally: Hmm... Those are not his glasses.\n\n【后置对话】\n无", "knowledge": "glasses", "desc": ""}, {"row": 43, "type": "图片单选", "id": "1221103", "config_info": "S3主线", "role": "Sally", "en_text": "And white shoes.", "config": "【任务标题】选择鞋子\n\n【情境引入】\n无\n\n【互动内容】\n题干Choose Logi's $shoes$.\n选项\n00\n正确00\n\n【互动反馈】\n正确Sally: Got it?\n错误Sally: Are those white shoes?\n\n【后置对话】\nBen: Got it!", "knowledge": "shoe", "desc": ""}, {"row": 58, "type": "对话朗读-配图", "id": "1221104", "config_info": "S4主线", "role": "User", "en_text": "No. He wears a blue cap.", "config": "【任务标题】辨认棒球帽\n【资源配置】\n图片时机情境引入 互动内容\n\n【情境引入】\nUser: Hmm... No.\n\n【互动内容】\nUser: He wears a blue cap.(朗读)\n\n\n【后置对话】\n无", "knowledge": "wear", "desc": ""}, {"row": 71, "type": "对话朗读", "id": "1221105", "config_info": "S5主线", "role": "User", "en_text": "Is it a baseball cap?", "config": "【任务标题】确认棒球帽\n【资源配置】\n图片时机情境引入 互动内容\n\n【情境引入】\nVicky: I found a blue hat!\n\n【互动内容】\nUser: Is it a baseball cap?(朗读)\n\n\n【后置对话】\n无", "knowledge": "baseball cap", "desc": ""}, {"row": 80, "type": "对话挖空-配图", "id": "1221106", "config_info": "S6主线", "role": "User", "en_text": "This is not Logi's cap.", "config": "【任务标题】辨认帽子\n【资源配置】\n【资源配置】\n图片时机情境引入 互动内容 互动反馈\n\n【情境引入】\nUser: You're right. This is not Logi's cap.\n\n【互动内容】\n题目He ___ a baseball cap.\n选项1wears正确\n选项2bears\n\n【互动反馈】\n正确无\n错误Vicky: No bears here!\n\n【后置对话】\n空", "knowledge": "... wear(s)...", "desc": ""}, {"row": 90, "type": "图片单选", "id": "1221107", "config_info": "S7主线", "role": "", "en_text": "", "config": "【任务标题】找到棒球帽\n\n\n【情境引入】\n无\n\n【互动内容】\n题干Find the $baseball cap$.\n选项\n00\n01\n02\n正确00\n\n【互动反馈】\n正确无\n错误Ben: Is that really a baseball cap?\n\n【后置对话】\n无", "knowledge": "baseball cap", "desc": "【教研图】蓝色棒球帽挂在树枝上点击图片上的Baseball cap"}, {"row": 107, "type": "对话挖空-配图", "id": "1221108", "config_info": "S8主线", "role": "User", "en_text": "Logi wears glasses, not sunglasses.", "config": "【任务标题】辨认眼镜\n【资源配置】\n图片时机互动内容 互动反馈\n\n【情境引入】\n无\n\n【互动内容】\n题目Logi wears ___, not sunglasses.\n选项1glasses正确\n选项2grass\n\n【互动反馈】\n正确无\n错误Vicky: Logi can't wear grass!\n\n【后置对话】\n无", "knowledge": "glasses", "desc": "【教研图】黑色墨镜和Logi的照片的对比图"}, {"row": 113, "type": "对话朗读", "id": "1221109", "config_info": "S9主线", "role": "User", "en_text": "These are not his glasses.", "config": "【任务标题】辨认眼镜\n【资源配置】\n图片时机互动内容\n\n【情境引入】\n无\n\n【互动内容】\nUser: These are not his glasses.(朗读)\n\n【后置对话】\n无", "knowledge": "This is / These are not...", "desc": ""}, {"row": 132, "type": "对话朗读-配图", "id": "1221110", "config_info": "S10主线", "role": "User", "en_text": "A shoe!", "config": "【任务标题】辨认鞋子\n【资源配置】\n图片时机互动内容\n\n【情境引入】\n无\n\n【互动内容】\nUser: A shoe!(朗读)\n\n\n【后置对话】\nVicky: But Logi wears white shoes.", "knowledge": "shoe", "desc": "【教研图】棕色鞋子和Logi的照片的对比图"}, {"row": 138, "type": "对话朗读--配图", "id": "1221111", "config_info": "S11主线", "role": "User", "en_text": "This is not a white shoe.", "config": "【任务标题】辨认白鞋\n【资源配置】\n图片时机互动内容\n\n【情境引入】\nUser: You are right.\n\n【互动内容】\nUser: This is not a white shoe.(朗读)\n\n\n【后置对话】\n无", "knowledge": "This is / These are not...", "desc": ""}, {"row": 149, "type": "对话选择-配图", "id": "1221112", "config_info": "S12主线", "role": "", "en_text": "", "config": "【任务标题】汇报发现\n【资源配置】\n图片时机互动内容 互动反馈\n\n【情境引入】Sally: What did you find?\n\n【互动内容】\n要求选择正确的回复\n选项音频\n选项1We found his baseball cap.(正确)\n- 反馈 Sally: Great news!\n选项2We found his white hat.\n- 反馈 Vicky: Wait, Are you sure?\n\n【后置对话】\n无", "knowledge": "baseball cap", "desc": "【教研图】蓝色棒球帽、墨镜、棕色的鞋"}, {"row": 157, "type": "对话选择-配图", "id": "1221113", "config_info": "S13主线", "role": "User", "en_text": "We found sunglasses.", "config": "【任务标题】汇报眼镜\n【资源配置】\n图片时机情境引入 互动内容 互动反馈\n\n【情境引入】\nSally: Great news! And?\n\n【互动内容】\n要求选择正确的回复\n选项音频\n选项1We found his sunglasses.\n- 反馈 Sally: Really? He doesn't have sunglasses.\n选项2We found his glasses.(正确)\n- 反馈 Sally: Oh, I see.\n\n【后置对话】\n无", "knowledge": "This is / These are not...", "desc": ""}, {"row": 164, "type": "对话选择-配图", "id": "1221114", "config_info": "S14主线", "role": "User", "en_text": "And a brown shoe.", "config": "【任务标题】汇报鞋子\n【资源配置】\n图片时机互动内容 互动反馈\n\n【情境引入】\n无\n\n【互动内容】\n要求选择正确的回复\n选项音频\n选项1And a brown shoe.(正确)\n- 反馈 Sally: A brown shoe?\n选项2And a white shoe.\n- 反馈 Sally: That must be Logi's!\n\n【后置对话】\n无", "knowledge": "shoe", "desc": ""}, {"row": 195, "type": "图片单选", "id": "1221115", "config_info": "S15主线", "role": "", "en_text": "", "config": "【任务标题】选出棒球帽\n【资源配置】\n图片时机互动内容\n\n【情境引入】\nUser: Here you are!\n\n【互动内容】\n题干Choose the $baseball cap$.\n选项\n00\n01\n02\n正确00\n\n【互动反馈】\n正确无\n错误Logi: That's not my baseball cap.\n\n【后置对话】\n无", "knowledge": "baseball cap", "desc": "【教研图】蓝色棒球帽、墨镜、棕色鞋子选出图片上的baseball cap"}, {"row": 221, "type": "听力拖拽", "id": "1221116", "config_info": "S17主线", "role": "", "en_text": "", "config": "导览配置\n【任务标题】\n找回Logi的物品\n\n【任务背景】\nLogi为什么弄丢了棒球帽鞋子和眼镜快来听听他在树林里的遭遇吧\n\n【通关知识】\n单词\nwear v. 穿着\nbaseball cap n. 棒球帽\nglasses n. 眼镜\nshoe n. 鞋子\n\n互动配置\n【开场语】\nLogi: Thank you for finding my things!\nLogi: Let me tell you what happened.\n\n【听力文本】\n# 文本 1\nLogi: I lost my way in the woods.\nLogi: A bird takes my glasses! \nLogi: I cannot see clearly.\nLogi: My shoe is in the mud.\nLogi: Oh, where is my shoe? I can't find it!\nLogi: The wind blows. My baseball cap flies up, up, up. \nLogi: My baseball cap is on the tree branch!\n\n\n【结束语】\nLogi: Now I can wear my baseball cap, glasses and shoes.\nLogi: Thank you, my friends!\n\n【题目信息】\n#多空选择\n选项图片编号00,01,02,03,04\n答案图片编号00,01,02\n\n【学习过程】\n句子 1\nA bird takes my glasses! 【glasses】\n\n句子 2\nMy shoe is in the mud.【shoe】\n\n句子 4\nMy baseball cap is on the tree branch!【baseball cap】", "knowledge": "wear \nbaseball cap\nglasses\nshoe", "desc": "将对应的baseball cap、glasses、shoe拖到对应位置上"}];
let originalConfigs = {};
let modifiedRows = new Set();
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
function renderCards() {
const container = document.getElementById('cards');
container.innerHTML = '';
COMPONENTS.forEach((c, idx) => {
originalConfigs[c.row] = c.config;
const card = document.createElement('div');
card.className = 'card';
card.id = 'card-' + c.row;
let html = '<div class="card-header">';
html += '<span class="card-type">#' + (idx+1) + ' ' + escapeHtml(c.type) + '</span>';
html += '<span class="card-meta">Row ' + c.row + ' | <span class="tag tag-id">ID: ' + escapeHtml(c.id) + '</span> | ' + escapeHtml(c.config_info) + '</span>';
html += '</div>';
html += '<div class="card-body">';
html += '<div class="field"><span class="field-label">角色:</span><span class="field-value">' + escapeHtml(c.role) + '</span></div>';
html += '<div class="field"><span class="field-label">英文台词:</span><span class="field-value">' + escapeHtml(c.en_text) + '</span></div>';
if (c.knowledge) {
html += '<div class="field"><span class="tag tag-k">知识点: ' + escapeHtml(c.knowledge) + '</span></div>';
}
if (c.desc && c.desc !== 'None') {
html += '<div class="field"><span class="field-label">剧情描述:</span><span class="field-value">' + escapeHtml(c.desc) + '</span></div>';
}
html += '<textarea class="config-textarea" id="config-' + c.row + '" data-row="' + c.row + '" oninput="onConfigChange(' + c.row + ')">' + escapeHtml(c.config) + '</textarea>';
html += '</div>';
card.innerHTML = html;
container.appendChild(card);
});
}
function onConfigChange(row) {
const textarea = document.getElementById('config-' + row);
const card = document.getElementById('card-' + row);
const current = textarea.value;
if (current !== originalConfigs[row]) {
modifiedRows.add(row);
card.classList.add('modified');
} else {
modifiedRows.delete(row);
card.classList.remove('modified');
}
document.getElementById('modifiedCount').textContent = modifiedRows.size;
}
async function pushToFeishu() {
if (modifiedRows.size === 0) {
showModal('提示', '没有检测到修改,无需推送。');
return;
}
const btn = document.getElementById('btnPush');
const progress = document.getElementById('progress');
const status = document.getElementById('status');
btn.disabled = true;
btn.textContent = '推送中...';
progress.classList.add('show');
status.className = 'status status-info';
status.textContent = '正在推送...';
const valueRanges = [];
const rows = Array.from(modifiedRows).sort((a,b) => a-b);
for (const row of rows) {
const config = document.getElementById('config-' + row).value;
valueRanges.push({
range: SHEET_ID + '!F' + row + ':F' + row,
values: [[config]]
});
}
// Get a fresh token
let token;
try {
const tokenResp = await fetch('https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
app_id: 'cli_a931175d41799cc7',
app_secret: 'Iw2vEfbjT6GtV0GhbxbZqfQ4nAPtbR14'
})
});
const tokenData = await tokenResp.json();
token = tokenData.tenant_access_token;
} catch(e) {
showModal('推送失败', '无法获取飞书 Token请检查网络连接。\n\n错误' + e.message);
btn.disabled = false;
btn.textContent = '📤 推送到飞书';
progress.classList.remove('show');
status.className = 'status status-err';
status.textContent = '推送失败';
return;
}
// Batch update (max 50 per batch)
const batchSize = 50;
let successCount = 0;
let failCount = 0;
const errors = [];
for (let i = 0; i < valueRanges.length; i += batchSize) {
const batch = valueRanges.slice(i, i + batchSize);
progress.textContent = '推送第 ' + (i+1) + '-' + Math.min(i+batchSize, valueRanges.length) + ' / ' + valueRanges.length + ' 行...';
try {
const resp = await fetch('https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/' + SPREADSHEET_TOKEN + '/values_batch_update', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/json'
},
body: JSON.stringify({valueRanges: batch})
});
const data = await resp.json();
if (data.code === 0) {
successCount += batch.length;
// Update original configs
for (const vr of batch) {
const rowMatch = vr.range.match(/F(\d+)/);
if (rowMatch) {
const row = parseInt(rowMatch[1]);
originalConfigs[row] = vr.values[0][0];
modifiedRows.delete(row);
const card = document.getElementById('card-' + row);
if (card) card.classList.remove('modified');
}
}
} else {
failCount += batch.length;
errors.push('批次 ' + (Math.floor(i/batchSize)+1) + ': ' + (data.msg || '未知错误'));
}
} catch(e) {
failCount += batch.length;
errors.push('批次 ' + (Math.floor(i/batchSize)+1) + ': ' + e.message);
}
}
btn.disabled = false;
btn.textContent = '📤 推送到飞书';
progress.classList.remove('show');
document.getElementById('modifiedCount').textContent = modifiedRows.size;
if (failCount === 0) {
status.className = 'status status-ok';
status.textContent = '✅ 推送成功!' + successCount + ' 行已更新';
showModal('推送成功', '已成功更新 ' + successCount + ' 行组件配置到飞书文档。');
} else {
status.className = 'status status-err';
status.textContent = '⚠️ 部分失败:' + successCount + ' 成功, ' + failCount + ' 失败';
showModal('推送结果', '成功:' + successCount + ' 行\n失败' + failCount + ' 行\n\n错误详情\n' + errors.join('\n'));
}
}
function showModal(title, body) {
document.getElementById('modalTitle').textContent = title;
document.getElementById('modalBody').textContent = body;
document.getElementById('modal').classList.add('show');
}
// Init
renderCards();
</script>
</body>
</html>