feat(config):
style(ui): 全面重构用户界面样式

- 引入新的配色方案和设计系统变量
- 更新字体家族,使用 Fira Sans 和 Noto Sans SC
- 重新设计页面布局和组件样式
- 添加响应式设计优化
- 改进按钮、表格、表单等UI元素的视觉效果

feat(tasks): 添加任务级别浏览器配置选项

- 在任务配置中增加独立的浏览器开启/关闭选项
- 支持任务继承全局浏览器设置
- 在任务列表中显示浏览器配置状态
- 实现任务级别的 useBrowser 字段管理
```
This commit is contained in:
2026-03-10 17:58:09 +08:00
parent 4f504447a1
commit b9270428db
6 changed files with 699 additions and 134 deletions

View File

@@ -767,23 +767,29 @@ function generateScraperResultsHtml(results) {
const failResults = results.filter(r => r.error);
const generatedAt = new Date().toLocaleString('zh-CN');
// 把所有成功来源的 items 展开,附带来源信息
// Flatten all successful source items into one table.
const allRows = [];
for (const r of successResults) {
const items = r.data?.result || [];
const items = r.data?.results || r.data?.result || [];
for (const item of items) {
const hasAmount = typeof item.amount_yuan === 'number' || !!item.amount;
const amountText =
typeof item.amount_yuan === 'number'
? `${item.amount_yuan.toLocaleString('zh-CN')} CNY`
: (item.amount || 'N/A');
allRows.push({
section: [r.section, r.subsection].filter(Boolean).join(' · ') || r.city || '-',
type: r.type || '-',
title: item.title || '-',
section: [r.section, r.subsection].filter(Boolean).join(' / ') || r.city || '-',
type: item.type || r.type || '-',
title: item.project_name || item.title || '-',
date: item.date || '-',
amount: item.amount || '未公开',
url: item.url || '',
amount: amountText,
hasAmount,
url: item.target_link || item.url || '',
});
}
}
// 按日期降序排列
allRows.sort((a, b) => {
if (a.date === b.date) return 0;
return a.date > b.date ? -1 : 1;
@@ -802,7 +808,7 @@ function generateScraperResultsHtml(results) {
</td>
<td style="padding:9px 12px;border-bottom:1px solid #eaecf5;font-size:13px;max-width:320px;">${row.title}</td>
<td style="padding:9px 12px;border-bottom:1px solid #eaecf5;white-space:nowrap;font-size:13px;color:#555;">${row.date}</td>
<td style="padding:9px 12px;border-bottom:1px solid #eaecf5;white-space:nowrap;font-size:13px;font-weight:600;color:${row.amount === '未公开' ? '#aaa' : '#e67e22'};">${row.amount}</td>
<td style="padding:9px 12px;border-bottom:1px solid #eaecf5;white-space:nowrap;font-size:13px;font-weight:600;color:${row.hasAmount ? '#e67e22' : '#aaa'};">${row.amount}</td>
<td style="padding:9px 12px;border-bottom:1px solid #eaecf5;text-align:center;">
${row.url
? `<a href="${row.url}" target="_blank" style="color:#667eea;font-size:12px;text-decoration:none;white-space:nowrap;">查看 →</a>`
@@ -851,7 +857,7 @@ function generateScraperResultsHtml(results) {
<div style="font-size:12px;color:#888;margin-top:2px;">成功来源</div>
</div>
<div style="flex:1;padding:16px 24px;text-align:center;border-right:1px solid #eaecf5;">
<div style="font-size:28px;font-weight:700;color:#e67e22;">${allRows.filter(r => r.amount && r.amount !== '未公开').length}</div>
<div style="font-size:28px;font-weight:700;color:#e67e22;">${allRows.filter(r => r.hasAmount).length}</div>
<div style="font-size:12px;color:#888;margin-top:2px;">有金额</div>
</div>
<div style="flex:1;padding:16px 24px;text-align:center;">

View File

@@ -46,10 +46,11 @@ function appendResult(result) {
// ========== 任务执行 ==========
async function runTask(task, agentCfg) {
const useBrowser = typeof task.useBrowser === 'boolean' ? task.useBrowser : agentCfg.useBrowser;
console.log(`[定时任务][Agent] ${task.city}:开始执行`);
const { results } = await runAgentTask(task.prompt, {
baseUrl: agentCfg.baseUrl,
useBrowser: agentCfg.useBrowser,
useBrowser,
pollInterval: agentCfg.pollInterval,
timeout: agentCfg.timeout,
logPrefix: `[定时任务][Agent][${task.city}]`,

View File

@@ -27,6 +27,10 @@ function saveConfig(cfg) {
writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2), 'utf-8');
}
function normalizeUseBrowser(value) {
return value === true || value === 'true';
}
// ========== 抓取结果存取 ==========
function readResults() {
@@ -130,6 +134,7 @@ app.post('/api/tasks', (req, res) => {
city: req.body.city || '',
prompt: req.body.prompt || '',
enabled: req.body.enabled !== false,
useBrowser: normalizeUseBrowser(req.body.useBrowser),
};
cfg.tasks.push(item);
saveConfig(cfg);
@@ -144,7 +149,11 @@ app.put('/api/tasks/:id', (req, res) => {
const cfg = readConfig();
const idx = (cfg.tasks || []).findIndex(t => t.id === req.params.id);
if (idx === -1) return res.status(404).json({ success: false, error: '未找到该配置' });
cfg.tasks[idx] = { ...cfg.tasks[idx], ...req.body, id: req.params.id };
const patch = { ...req.body };
if (Object.prototype.hasOwnProperty.call(patch, 'useBrowser')) {
patch.useBrowser = normalizeUseBrowser(patch.useBrowser);
}
cfg.tasks[idx] = { ...cfg.tasks[idx], ...patch, id: req.params.id };
saveConfig(cfg);
res.json({ success: true, data: cfg.tasks[idx] });
} catch (e) {
@@ -173,11 +182,12 @@ let runningStatus = null; // { taskId, city, startTime, current, total, finished
async function runTask(task) {
const cfg = readConfig();
const agentCfg = cfg.agent || {};
const useBrowser = typeof task.useBrowser === 'boolean' ? task.useBrowser : agentCfg.useBrowser;
console.log(`[Agent] ${task.city}:开始执行`);
const { results } = await runAgentTask(task.prompt, {
baseUrl: agentCfg.baseUrl,
useBrowser: agentCfg.useBrowser,
useBrowser,
pollInterval: agentCfg.pollInterval,
timeout: agentCfg.timeout,
logPrefix: `[Agent][${task.city}]`,