feat: 数据管理列宽可调/行编辑删除/模板导入,CellData 处理日志细化

- 数据表列宽可拖拽 + 新增「操作」列(编辑/删除该行)
- 工具栏新增「模板」「导入」:CSV 按模板字段校验,不一致则导入失败
- 后端 DatabaseManager/MetrixWarehouse 增加行更新/删除/CSV 导入 + 对应路由
- CellData 解压/解析/写库日志细化(逐文件、分批进度)
- 修复 ScriptPanel monaco mysql 深层导入缺类型声明导致的 vue-tsc 构建失败
This commit is contained in:
2026-06-26 05:32:06 +08:00
parent 639bae694d
commit f0bbc77763
7 changed files with 543 additions and 15 deletions
+40
View File
@@ -9,6 +9,7 @@ query_table / truncate_table / drop_table / drop_all_tables / execute_sql。
"""
from __future__ import annotations
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
from app.config import AppConfig
@@ -34,6 +35,10 @@ def _quote_value(value: str) -> str:
return "'" + str(value).replace("\\", "\\\\").replace("'", "''") + "'"
def _sql_literal(value: Any) -> str:
return "NULL" if value is None else _quote_value(str(value))
class MetrixWarehouse:
"""用 Metrix 数据库 API 实现 DatabaseManager 的只读/管理子集。"""
@@ -153,6 +158,41 @@ class MetrixWarehouse:
self._run(drop_sql)
return {"success": True, "dropped_count": len(tables), "tables": tables}
def _row_where(self, identifier: Dict[str, Any]) -> str:
parts = [
f"{_quote_ident(col)} IS NULL" if val is None else f"{_quote_ident(col)} = {_sql_literal(val)}"
for col, val in identifier.items()
]
return " AND ".join(parts) if parts else "1 = 0"
def update_row(self, table_name: str, identifier: Dict[str, Any], values: Dict[str, Any]) -> int:
if not values:
return 0
set_clause = ", ".join(f"{_quote_ident(col)} = {_sql_literal(val)}" for col, val in values.items())
sql = f"UPDATE {_quote_ident(table_name)} SET {set_clause} WHERE {self._row_where(identifier)} LIMIT 1"
ok, result = self.execute_sql(sql)
if not ok:
raise RuntimeError(str(result))
return int(result.get("affected_rows", 0)) if isinstance(result, dict) else 0
def delete_row(self, table_name: str, identifier: Dict[str, Any]) -> int:
sql = f"DELETE FROM {_quote_ident(table_name)} WHERE {self._row_where(identifier)} LIMIT 1"
ok, result = self.execute_sql(sql)
if not ok:
raise RuntimeError(str(result))
return int(result.get("affected_rows", 0)) if isinstance(result, dict) else 0
def import_csv(self, file_path: str, table_name: str) -> int:
"""走 Metrix 平台导入 API 追加导入到已存在的表(字段映射由平台按列名处理)。"""
job_id = self.client.import_csv(
self.conn_id, table_name, Path(file_path), mode="append",
database=self.database, create_table=False,
)
job = self.client.wait_job(job_id)
if job.get("status") != "success":
raise RuntimeError(job.get("error_code") or job.get("status") or "导入失败")
return int(job.get("row_count") or 0)
def execute_sql(self, sql: str) -> Tuple[bool, Any]:
try:
res = self.client.run_script(self.conn_id, content=sql, database=self.database, run_timeout=600)