| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <title>批量更新案例封面为家装图片</title>
- <style>
- body { font-family: -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif; background: #f5f7fb; margin: 0; }
- .container { max-width: 980px; margin: 32px auto; background: #fff; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,.06); overflow: hidden; }
- .header { padding: 24px 28px; background: linear-gradient(135deg,#667eea,#764ba2); color: #fff; }
- .header h1 { margin: 0 0 6px; font-size: 20px; }
- .header p { margin: 0; opacity: .9; font-size: 13px; }
- .content { padding: 22px 28px; }
- .btn { display: inline-flex; align-items: center; gap: 8px; padding: 12px 18px; border: 0; border-radius: 10px; cursor: pointer; font-weight: 600; }
- .btn-primary { background: linear-gradient(135deg,#667eea,#764ba2); color: #fff; }
- .btn-secondary { background: #eef2ff; color: #3730a3; }
- .btn:disabled { opacity: .6; cursor: not-allowed; }
- .stat { display:flex; gap:12px; margin: 16px 0 6px; }
- .stat .card { flex:1; background:#f9fafb; border-radius:10px; padding:12px 14px; }
- .card h4 { margin:0 0 2px; font-size:13px; color:#6b7280; }
- .card div { font-size:22px; font-weight:700; }
- .log { background:#0b1220; color:#bcd1ff; border-radius:10px; padding:14px; height: 240px; overflow:auto; font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size: 12px; }
- .grid { display:grid; grid-template-columns: repeat(auto-fill, minmax(180px,1fr)); gap:12px; margin-top:16px; }
- .item { background:#f8fafc; border-radius:10px; overflow:hidden; border:1px solid #e5e7eb; }
- .item img { width:100%; height:120px; object-fit:cover; display:block; }
- .item .meta { padding:8px 10px; font-size:12px; color:#374151; }
- </style>
- </head>
- <body>
- <div class="container">
- <div class="header">
- <h1>批量更新案例封面为家装图片</h1>
- <p>优先使用已上传图片;若没有,则使用默认家装设计图片(Unsplash)。</p>
- </div>
- <div class="content">
- <div style="display:flex; gap:10px; align-items:center; margin-bottom:14px;">
- <button id="run" class="btn btn-primary">🚀 开始更新封面图片</button>
- <button id="preview" class="btn btn-secondary">👀 仅预览将被更新的案例</button>
- </div>
- <div class="stat">
- <div class="card"><h4>总案例</h4><div id="total">-</div></div>
- <div class="card"><h4>需更新</h4><div id="need">-</div></div>
- <div class="card"><h4>已成功</h4><div id="ok">0</div></div>
- <div class="card"><h4>失败</h4><div id="fail">0</div></div>
- </div>
- <div class="log" id="log"></div>
- <div class="grid" id="grid"></div>
- </div>
- </div>
- <script type="module">
- const logEl = document.getElementById('log');
- const gridEl = document.getElementById('grid');
- const totalEl = document.getElementById('total');
- const needEl = document.getElementById('need');
- const okEl = document.getElementById('ok');
- const failEl = document.getElementById('fail');
- const defaultInteriorImages = [
- 'https://images.unsplash.com/photo-1600210492486-724fe5c67fb0?w=1200&h=800&fit=crop',
- 'https://images.unsplash.com/photo-1600607687939-ce8a6c25118c?w=1200&h=800&fit=crop',
- 'https://images.unsplash.com/photo-1600566753190-17f0baa2a6c3?w=1200&h=800&fit=crop',
- 'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?w=1200&h=800&fit=crop',
- 'https://images.unsplash.com/photo-1600566753086-00f18fb6b3ea?w=1200&h=800&fit=crop',
- 'https://images.unsplash.com/photo-1600573472550-8090b5e0745e?w=1200&h=800&fit=crop',
- 'https://images.unsplash.com/photo-1600585154526-990dced4db0d?w=1200&h=800&fit=crop',
- 'https://images.unsplash.com/photo-1600566752355-35792bedcfea?w=1200&h=800&fit=crop',
- 'https://images.unsplash.com/photo-1600585154154-1eab6d02deae?w=1200&h=800&fit=crop',
- 'https://images.unsplash.com/photo-1600585153820-98d9849a432d?w=1200&h=800&fit=crop',
- 'https://images.unsplash.com/photo-1505693416388-ac5ce068fe85?w=1200&h=800&fit=crop',
- 'https://images.unsplash.com/photo-1505691723518-36a5ac3b2dfe?w=1200&h=800&fit=crop',
- 'https://images.unsplash.com/photo-1505691938895-1758d7feb511?w=1200&h=800&fit=crop',
- 'https://images.unsplash.com/photo-1579632652768-1c0cbe20a3c1?w=1200&h=800&fit=crop',
- 'https://images.unsplash.com/photo-1565182999561-18d7f0b2b4e1?w=1200&h=800&fit=crop'
- ];
- const sleep = ms => new Promise(r => setTimeout(r, ms));
- const log = (...args) => { logEl.textContent += `[${new Date().toLocaleTimeString()}] ` + args.join(' ') + '\n'; logEl.scrollTop = logEl.scrollHeight; };
- const isPlaceholder = (url='') => !url || url.includes('placeholder') || url.endsWith('.svg') || url.includes('assets/images');
- const pickImages = (count=4) => {
- const start = Math.floor(Math.random() * defaultInteriorImages.length);
- return Array.from({length: count}).map((_,i) => defaultInteriorImages[(start+i)%defaultInteriorImages.length]);
- };
- async function fetchCases(Parse) {
- const cid = localStorage.getItem('company');
- if (!cid) throw new Error('未发现公司ID (localStorage.company)');
- const q = new Parse.Query('Case');
- q.equalTo('company', { __type: 'Pointer', className: 'Company', objectId: cid });
- q.notEqualTo('isDeleted', true);
- q.limit(1000);
- const list = await q.find();
- return list;
- }
- function needsUpdate(caseObj) {
- const cover = caseObj.get('coverImage') || '';
- const imgs = caseObj.get('images') || [];
- const hasRealUploaded = Array.isArray(imgs) && imgs.some(u => u && !isPlaceholder(u) && u.startsWith('http'));
- // 需要更新的条件:封面是占位符/空;或者没有任何真实图片
- return isPlaceholder(cover) || !hasRealUploaded;
- }
- async function preview() {
- try {
- const Parse = await import('https://cdn.skypack.dev/fmode-ng/parse').then(m => m.FmodeParse.with('nova'));
- const cases = await fetchCases(Parse);
- totalEl.textContent = String(cases.length);
- const targets = cases.filter(needsUpdate);
- needEl.textContent = String(targets.length);
- log(`📊 找到 ${cases.length} 个案例,其中 ${targets.length} 个需要更新`);
- gridEl.innerHTML = '';
- targets.slice(0, 30).forEach((c, idx) => {
- const img = (c.get('images') || []).find(u => u && !isPlaceholder(u)) || defaultInteriorImages[idx % defaultInteriorImages.length];
- const div = document.createElement('div');
- div.className = 'item';
- div.innerHTML = `<img src="${img}" alt="case"/><div class="meta">${c.get('name') || c.id}</div>`;
- gridEl.appendChild(div);
- });
- } catch (e) {
- log('❌ 预览失败:', e.message || e);
- }
- }
- async function run() {
- const btn = document.getElementById('run');
- btn.disabled = true;
- try {
- const Parse = await import('https://cdn.skypack.dev/fmode-ng/parse').then(m => m.FmodeParse.with('nova'));
- const cases = await fetchCases(Parse);
- totalEl.textContent = String(cases.length);
- const targets = cases.filter(needsUpdate);
- needEl.textContent = String(targets.length);
- log(`🚀 开始更新 ${targets.length} 个案例封面...`);
- let ok = 0, fail = 0, idx = 0;
- for (const c of targets) {
- try {
- const imgs = c.get('images') || [];
- const realImgs = imgs.filter(u => u && !isPlaceholder(u) && u.startsWith('http'));
- const useImgs = realImgs.length > 0 ? realImgs : pickImages(4);
- const cover = useImgs[0];
- c.set('coverImage', cover);
- if (realImgs.length === 0) {
- c.set('images', useImgs);
- }
- await c.save();
- ok++;
- okEl.textContent = String(ok);
- idx++;
- if (idx % 3 === 0) await sleep(200); // 温和些
- log(`✅ 已更新: ${c.get('name') || c.id}`);
- } catch (err) {
- fail++;
- failEl.textContent = String(fail);
- log('❌ 更新失败: ', (c.get && c.get('name')) || c.id, '-', err.message || err);
- }
- }
- log(`🎉 完成!成功 ${ok},失败 ${fail}`);
- await preview();
- } catch (e) {
- log('❌ 执行失败:', e.message || e);
- } finally {
- btn.disabled = false;
- }
- }
- document.getElementById('preview').addEventListener('click', preview);
- document.getElementById('run').addEventListener('click', run);
- // 自动预览一次
- preview();
- </script>
- </body>
- </html>
|