feat: 初步开发一些特性

This commit is contained in:
meishibiezb
2026-05-01 21:13:16 +08:00
parent a25145638f
commit a6b86934af
6 changed files with 527 additions and 107 deletions

View File

@@ -1,6 +1,37 @@
import { showPageMarker } from "@/components/page-marker";
import { PinCollector } from "#imports";
export default defineContentScript({
matches: ['*://*.google.com/*'],
matches: ['*://*.huaban.com/*'],
runAt: 'document_idle',
main() {
console.log('Hello content.');
sayHello();
const collector = new PinCollector();
// 首次加载自动收集并存储
const pins = collector.collect();
if (pins.length > 0) {
collector.saveToStorage();
}
// 允许 popup 主动触发重新收集
browser.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg.type === 'COLLECT_PINS') {
const updatedPins = collector.collect();
sendResponse({ success: true, count: updatedPins.length });
return true; // 异步响应
}
});
},
});
function sayHello() {
console.log('检测到花瓣网!', window.location.href);
console.log('这个插件是一个用于自动收集图片的插件。');
showPageMarker({
text: '检测到花瓣网!插件已启动',
position: 'top-right',
backgroundColor: 'rgba(34, 139, 34, 0.9)',
closable: true, // 用户可点击 × 关闭
autoRemoveSeconds: 10,
});
}

View File

@@ -1,24 +1,90 @@
import './style.css';
import typescriptLogo from '@/assets/typescript.svg';
import wxtLogo from '/wxt.svg';
import { setupCounter } from '@/components/counter';
import type { PinData } from '../../components/pin-collector';
document.querySelector<HTMLDivElement>('#app')!.innerHTML = `
<div>
<a href="https://wxt.dev" target="_blank">
<img src="${wxtLogo}" class="logo" alt="WXT logo" />
</a>
<a href="https://www.typescriptlang.org/" target="_blank">
<img src="${typescriptLogo}" class="logo vanilla" alt="TypeScript logo" />
</a>
<h1>WXT + TypeScript</h1>
<div class="card">
<button id="counter" type="button"></button>
<div class="popup">
<h2>🎨 画板收集器</h2>
<div class="actions">
<button id="collectBtn">📥 收集</button>
<button id="clearBtn">🗑 清空</button>
</div>
<p class="read-the-docs">
Click on the WXT and TypeScript logos to learn more
</p>
<div id="status"></div>
<div id="pinList"></div>
</div>
`;
setupCounter(document.querySelector<HTMLButtonElement>('#counter')!);
// 按钮事件
document.getElementById('collectBtn')!.addEventListener('click', async () => {
showStatus('收集中...', 'info');
try {
const [tab] = await browser.tabs.query({ active: true, currentWindow: true });
if (!tab?.id) {
showStatus('无法获取当前标签页', 'error');
return;
}
const response = await browser.tabs.sendMessage(tab.id, {
type: 'COLLECT_PINS',
});
if (response?.success) {
await loadAndRender();
showStatus(`✅ 已收集 ${response.count} 个 Pin`, 'success');
}
} catch {
showStatus('请在花瓣网页面使用此功能', 'error');
}
});
document.getElementById('clearBtn')!.addEventListener('click', async () => {
await browser.storage.local.remove('collectedPins');
renderPins([]);
showStatus('已清空', 'info');
});
// 首次打开 popup 时加载并渲染
loadAndRender();
// ---- 辅助函数 ----
async function loadAndRender(): Promise<void> {
const result = (await browser.storage.local.get('collectedPins')) as {
collectedPins?: PinData[];
};
const pins: PinData[] = result.collectedPins || [];
renderPins(pins);
}
function renderPins(pins: PinData[]): void {
const listEl = document.getElementById('pinList')!;
if (pins.length === 0) {
listEl.innerHTML = '<p class="empty">暂无收集的图片</p>';
return;
}
listEl.innerHTML = pins
.map(
(pin) => `
<div class="pin-item">
<img src="${pin.imgSrc}" loading="lazy" alt="${escapeHtml(pin.alt)}" />
<div class="pin-info">
<div class="pin-author">${escapeHtml(pin.author || '未知作者')} · ${escapeHtml(pin.time || '')}</div>
<div class="pin-tags">${pin.tags.map(t => `#${escapeHtml(t)}`).join(' ')}</div>
<a class="pin-link" href="https://huaban.com${pin.url}" target="_blank">查看原图</a>
</div>
</div>
`
)
.join('');
}
function showStatus(msg: string, type: 'info' | 'success' | 'error'): void {
const statusEl = document.getElementById('status')!;
statusEl.textContent = msg;
statusEl.className = `status ${type}`;
setTimeout(() => (statusEl.textContent = ''), 3000);
}
function escapeHtml(str: string): string {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}

View File

@@ -1,97 +1,128 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
width: 360px;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 13px;
color: #333;
}
.popup {
padding: 12px;
}
h2 {
margin: 0 0 10px;
font-size: 16px;
}
.actions {
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #54bc4ae0);
}
.logo.vanilla:hover {
filter: drop-shadow(0 0 2em #3178c6aa);
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
gap: 8px;
margin-bottom: 10px;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
flex: 1;
padding: 6px 0;
border: none;
border-radius: 4px;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
font-size: 13px;
font-weight: 500;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
#collectBtn {
background: #4caf50;
color: #fff;
}
#collectBtn:hover {
background: #43a047;
}
#clearBtn {
background: #eee;
color: #555;
}
#clearBtn:hover {
background: #ddd;
}
.status {
text-align: center;
padding: 4px 0;
margin-bottom: 8px;
border-radius: 4px;
font-size: 12px;
}
.status.success {
background: #e8f5e9;
color: #2e7d32;
}
.status.error {
background: #ffebee;
color: #c62828;
}
.status.info {
background: #e3f2fd;
color: #1565c0;
}
#pinList {
max-height: 400px;
overflow-y: auto;
}
.empty {
text-align: center;
color: #999;
padding: 20px 0;
}
.pin-item {
display: flex;
gap: 8px;
margin-bottom: 10px;
padding: 8px;
border: 1px solid #eee;
border-radius: 6px;
background: #fafafa;
}
.pin-item img {
width: 60px;
height: 60px;
object-fit: cover;
border-radius: 4px;
flex-shrink: 0;
}
.pin-info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
justify-content: center;
gap: 3px;
}
.pin-author {
font-size: 12px;
color: #555;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.pin-tags {
font-size: 11px;
color: #888;
}
.pin-link {
font-size: 11px;
color: #1976d2;
text-decoration: none;
}
.pin-link:hover {
text-decoration: underline;
}