性能优化: 项目页/看板/DAG/知识库/交付 加载提速
- /api/tasks 列表接口改用轻量序列化,去掉 output_text/error 大字段 (300KB -> 6KB, 详情接口 /api/tasks/<id> 仍返回完整数据) - /api/tasks/trash 与 /api/projects/<id>/dag 同步轻量化 - 前端同一项目内切换 tab 不再重复拉取项目/任务/Worker/事件, 复用 projCtx 缓存; 离开项目页时自动清缓存 - /api/projects/<id>/events 各来源加 LIMIT, 避免全表加载
This commit is contained in:
@@ -708,7 +708,7 @@ def tasks():
|
|||||||
visible = _visible_project_ids()
|
visible = _visible_project_ids()
|
||||||
if visible is not None:
|
if visible is not None:
|
||||||
rows = [r for r in rows if r['project_id'] in visible]
|
rows = [r for r in rows if r['project_id'] in visible]
|
||||||
return jsonify({'ok': True, 'data': [db.serialize_task(t) for t in rows]})
|
return jsonify({'ok': True, 'data': [db.serialize_task_light(t) for t in rows]})
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/tasks/<int:tid>', methods=['GET', 'PUT', 'DELETE'])
|
@app.route('/api/tasks/<int:tid>', methods=['GET', 'PUT', 'DELETE'])
|
||||||
@@ -779,7 +779,7 @@ def tasks_trash():
|
|||||||
visible = _visible_project_ids()
|
visible = _visible_project_ids()
|
||||||
if visible is not None:
|
if visible is not None:
|
||||||
rows = [r for r in rows if r['project_id'] in visible]
|
rows = [r for r in rows if r['project_id'] in visible]
|
||||||
return jsonify({'ok': True, 'data': [db.serialize_task(t) for t in rows]})
|
return jsonify({'ok': True, 'data': [db.serialize_task_light(t) for t in rows]})
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/tasks/<int:tid>/trash', methods=['POST'])
|
@app.route('/api/tasks/<int:tid>/trash', methods=['POST'])
|
||||||
@@ -1104,18 +1104,21 @@ def project_events(pid):
|
|||||||
limit = min(int(request.args.get('limit', 200)), 500)
|
limit = min(int(request.args.get('limit', 200)), 500)
|
||||||
rows = []
|
rows = []
|
||||||
# AI 主管动态
|
# AI 主管动态
|
||||||
for r in db.q('SELECT * FROM project_logs WHERE project_id=?', (pid,)):
|
for r in db.q('SELECT * FROM project_logs WHERE project_id=? ORDER BY id DESC LIMIT ?',
|
||||||
|
(pid, limit)):
|
||||||
rows.append({'id': f'p{r["id"]}', 'kind': 'autolog', 'level': r['level'],
|
rows.append({'id': f'p{r["id"]}', 'kind': 'autolog', 'level': r['level'],
|
||||||
'message': r['message'], 'created_at': r['created_at'], 'task_id': None,
|
'message': r['message'], 'created_at': r['created_at'], 'task_id': None,
|
||||||
'task_title': None})
|
'task_title': None})
|
||||||
# 任务日志(带任务标题,可定位)
|
# 任务日志(带任务标题,可定位)
|
||||||
for r in db.q('SELECT l.*, t.title AS task_title FROM task_logs l '
|
for r in db.q('SELECT l.*, t.title AS task_title FROM task_logs l '
|
||||||
'JOIN tasks t ON t.id=l.task_id WHERE t.project_id=? AND t.deleted=0', (pid,)):
|
'JOIN tasks t ON t.id=l.task_id WHERE t.project_id=? AND t.deleted=0 '
|
||||||
|
'ORDER BY l.id DESC LIMIT ?', (pid, limit)):
|
||||||
rows.append({'id': f'l{r["id"]}', 'kind': 'task', 'level': r['level'],
|
rows.append({'id': f'l{r["id"]}', 'kind': 'task', 'level': r['level'],
|
||||||
'message': r['message'], 'created_at': r['created_at'],
|
'message': r['message'], 'created_at': r['created_at'],
|
||||||
'task_id': r['task_id'], 'task_title': r['task_title']})
|
'task_id': r['task_id'], 'task_title': r['task_title']})
|
||||||
# 交付记录
|
# 交付记录
|
||||||
for r in db.q('SELECT * FROM project_deliverables WHERE project_id=?', (pid,)):
|
for r in db.q('SELECT * FROM project_deliverables WHERE project_id=? ORDER BY id DESC LIMIT ?',
|
||||||
|
(pid, limit)):
|
||||||
rows.append({'id': f'd{r["id"]}', 'kind': 'deliver', 'level': 'success',
|
rows.append({'id': f'd{r["id"]}', 'kind': 'deliver', 'level': 'success',
|
||||||
'message': f'📦 {r["note"]}({r["name"]})', 'created_at': r['created_at'],
|
'message': f'📦 {r["note"]}({r["name"]})', 'created_at': r['created_at'],
|
||||||
'task_id': None, 'task_title': None})
|
'task_id': None, 'task_title': None})
|
||||||
@@ -1275,7 +1278,7 @@ def project_dag(pid):
|
|||||||
rows = db.q('SELECT * FROM tasks WHERE project_id=? AND deleted=0 ORDER BY id', (pid,))
|
rows = db.q('SELECT * FROM tasks WHERE project_id=? AND deleted=0 ORDER BY id', (pid,))
|
||||||
nodes, edges, id_map = [], [], {}
|
nodes, edges, id_map = [], [], {}
|
||||||
for t in rows:
|
for t in rows:
|
||||||
node = db.serialize_task(t)
|
node = db.serialize_task_light(t)
|
||||||
nodes.append(node)
|
nodes.append(node)
|
||||||
id_map[t['id']] = node
|
id_map[t['id']] = node
|
||||||
for t in nodes:
|
for t in nodes:
|
||||||
|
|||||||
@@ -631,6 +631,16 @@ def serialize_task(t):
|
|||||||
return t
|
return t
|
||||||
|
|
||||||
|
|
||||||
|
def serialize_task_light(t):
|
||||||
|
"""轻量序列化(列表接口用):去掉大字段 output_text / error,
|
||||||
|
看板、DAG、回收站等列表视图不需要它们,可显著减小传输体积。
|
||||||
|
详情接口 /api/tasks/<id> 仍返回完整数据。"""
|
||||||
|
t = serialize_task(t)
|
||||||
|
t.pop('output_text', None)
|
||||||
|
t.pop('error', None)
|
||||||
|
return t
|
||||||
|
|
||||||
|
|
||||||
def get_setting(key, default=''):
|
def get_setting(key, default=''):
|
||||||
r = q('SELECT value FROM settings WHERE key=?', (key,), one=True)
|
r = q('SELECT value FROM settings WHERE key=?', (key,), one=True)
|
||||||
return r['value'] if r else default
|
return r['value'] if r else default
|
||||||
|
|||||||
+12
-1
@@ -100,6 +100,8 @@ function router() {
|
|||||||
const hash = location.hash.replace(/^#\//, '') || 'dashboard';
|
const hash = location.hash.replace(/^#\//, '') || 'dashboard';
|
||||||
const parts = hash.split('/');
|
const parts = hash.split('/');
|
||||||
const name = parts[0];
|
const name = parts[0];
|
||||||
|
// 离开项目页时丢弃项目缓存,避免下次进入用旧数据
|
||||||
|
if (name !== 'project') projCtx = null;
|
||||||
const fn = routes[name] || pageDashboard;
|
const fn = routes[name] || pageDashboard;
|
||||||
const navMap = {project:'projects', api:'api', settings:'settings', alerts:'alerts'};
|
const navMap = {project:'projects', api:'api', settings:'settings', alerts:'alerts'};
|
||||||
$$('#sidebar nav a').forEach(a => a.classList.toggle('active', a.dataset.route === (navMap[name] || name)));
|
$$('#sidebar nav a').forEach(a => a.classList.toggle('active', a.dataset.route === (navMap[name] || name)));
|
||||||
@@ -274,11 +276,20 @@ let projCtx = null; // {pid, p, tasks, workers, tab, autoLogs}
|
|||||||
let projAutoTimer = null;
|
let projAutoTimer = null;
|
||||||
|
|
||||||
async function pageProject([pid, tab]) {
|
async function pageProject([pid, tab]) {
|
||||||
|
tab = tab || 'kanban';
|
||||||
|
// 同一项目内切 tab:复用已加载的数据,不再重新拉项目/任务/Worker/事件
|
||||||
|
if (projCtx && String(projCtx.pid) === String(pid)) {
|
||||||
|
projCtx.tab = tab;
|
||||||
|
renderProjectShell();
|
||||||
|
renderActiveTab();
|
||||||
|
startProjAutoPoll(); // 重启轮询(router 已清掉定时器)
|
||||||
|
return;
|
||||||
|
}
|
||||||
const p = (await api(`/api/projects/${pid}`)).data;
|
const p = (await api(`/api/projects/${pid}`)).data;
|
||||||
const tasks = (await api(`/api/tasks?project_id=${pid}`)).data;
|
const tasks = (await api(`/api/tasks?project_id=${pid}`)).data;
|
||||||
const workers = (await api('/api/workers')).data;
|
const workers = (await api('/api/workers')).data;
|
||||||
const events = (await api(`/api/projects/${pid}/events`)).data;
|
const events = (await api(`/api/projects/${pid}/events`)).data;
|
||||||
projCtx = {pid, p, tasks, workers, tab: tab || 'kanban', events};
|
projCtx = {pid, p, tasks, workers, tab, events};
|
||||||
renderProjectShell();
|
renderProjectShell();
|
||||||
renderActiveTab();
|
renderActiveTab();
|
||||||
startProjAutoPoll();
|
startProjAutoPoll();
|
||||||
|
|||||||
Reference in New Issue
Block a user