diff --git a/Configure.json b/Configure.json index 34bfd96..989c6c9 100644 --- a/Configure.json +++ b/Configure.json @@ -234,6 +234,8 @@ "/网优日常优化数据文档/日常性能报表/{maxyear}年/300表" ], "year_dir_regex": "(?P\\d{4})年", + "month_dir_regex": "(?P\\d{1,2})月", + "day_dir_regex": "(?P\\d{1,2})日", "file_name_regex": "^Result_300_.*\\.zip$", "file_time_regex": "(?P\\d{14})(?=\\.zip$)" }, diff --git a/app/api/routers/config.py b/app/api/routers/config.py index 4eb55e9..9e60568 100644 --- a/app/api/routers/config.py +++ b/app/api/routers/config.py @@ -107,6 +107,8 @@ async def update_cell_data_remote_config(config: dict[str, Any] = Body(...)): mysql=current.mysql, scan_paths=current.scan_paths, year_dir_regex=current.year_dir_regex, + month_dir_regex=current.month_dir_regex, + day_dir_regex=current.day_dir_regex, file_name_regex=current.file_name_regex, file_time_regex=current.file_time_regex, mapping=current.mapping, @@ -124,6 +126,8 @@ async def update_cell_data_mysql_config(config: dict[str, Any] = Body(...)): mysql=MySQLConfig.from_dict(config, default_dbname="celldata"), scan_paths=current.scan_paths, year_dir_regex=current.year_dir_regex, + month_dir_regex=current.month_dir_regex, + day_dir_regex=current.day_dir_regex, file_name_regex=current.file_name_regex, file_time_regex=current.file_time_regex, mapping=current.mapping, @@ -160,6 +164,8 @@ async def update_cell_data_settings(config: dict[str, Any] = Body(...)): mysql=current.mysql, scan_paths=config.get("scan_paths", current.scan_paths), year_dir_regex=str(config.get("year_dir_regex", current.year_dir_regex)), + month_dir_regex=str(config.get("month_dir_regex", current.month_dir_regex)), + day_dir_regex=str(config.get("day_dir_regex", current.day_dir_regex)), file_name_regex=str(config.get("file_name_regex", current.file_name_regex)), file_time_regex=str(config.get("file_time_regex", current.file_time_regex)), mapping=config.get("mapping", current.mapping), @@ -302,7 +308,7 @@ def _validate_cell_data_settings(config: dict[str, Any]) -> dict[str, Any]: if not isinstance(scan_paths, list) or not any(str(path).strip() for path in scan_paths): return {"success": False, "message": "请至少配置一个扫描路径"} - for key in ("year_dir_regex", "file_name_regex", "file_time_regex"): + for key in ("year_dir_regex", "month_dir_regex", "day_dir_regex", "file_name_regex", "file_time_regex"): try: re.compile(str(config.get(key, ""))) except re.error as exc: diff --git a/app/config.py b/app/config.py index 0015601..205bd81 100644 --- a/app/config.py +++ b/app/config.py @@ -80,6 +80,8 @@ DEFAULT_CELL_DATA_SCAN_PATHS = [ "/网优日常优化数据文档/日常性能报表/{maxyear}年/300表", ] DEFAULT_CELL_DATA_YEAR_DIR_REGEX = r"(?P\d{4})年" +DEFAULT_CELL_DATA_MONTH_DIR_REGEX = r"(?P\d{1,2})月" +DEFAULT_CELL_DATA_DAY_DIR_REGEX = r"(?P\d{1,2})日" DEFAULT_CELL_DATA_FILE_NAME_REGEX = r"^Result_300_.*\.zip$" DEFAULT_CELL_DATA_FILE_TIME_REGEX = r"(?P\d{14})(?=\.zip$)" DEFAULT_CELL_DATA_MAPPING: Dict[str, Any] = { @@ -435,6 +437,8 @@ class CellDataConfig: mysql: MySQLConfig = field(default_factory=lambda: MySQLConfig(dbname="celldata")) scan_paths: List[str] = field(default_factory=lambda: list(DEFAULT_CELL_DATA_SCAN_PATHS)) year_dir_regex: str = DEFAULT_CELL_DATA_YEAR_DIR_REGEX + month_dir_regex: str = DEFAULT_CELL_DATA_MONTH_DIR_REGEX + day_dir_regex: str = DEFAULT_CELL_DATA_DAY_DIR_REGEX file_name_regex: str = DEFAULT_CELL_DATA_FILE_NAME_REGEX file_time_regex: str = DEFAULT_CELL_DATA_FILE_TIME_REGEX mapping: Dict[str, Any] = field(default_factory=lambda: deepcopy(DEFAULT_CELL_DATA_MAPPING)) @@ -456,6 +460,8 @@ class CellDataConfig: mysql=self.mysql.normalized(), scan_paths=scan_paths, year_dir_regex=str(self.year_dir_regex or DEFAULT_CELL_DATA_YEAR_DIR_REGEX).strip() or DEFAULT_CELL_DATA_YEAR_DIR_REGEX, + month_dir_regex=str(self.month_dir_regex or DEFAULT_CELL_DATA_MONTH_DIR_REGEX).strip() or DEFAULT_CELL_DATA_MONTH_DIR_REGEX, + day_dir_regex=str(self.day_dir_regex or DEFAULT_CELL_DATA_DAY_DIR_REGEX).strip() or DEFAULT_CELL_DATA_DAY_DIR_REGEX, file_name_regex=str(self.file_name_regex or DEFAULT_CELL_DATA_FILE_NAME_REGEX).strip() or DEFAULT_CELL_DATA_FILE_NAME_REGEX, file_time_regex=str(self.file_time_regex or DEFAULT_CELL_DATA_FILE_TIME_REGEX).strip() or DEFAULT_CELL_DATA_FILE_TIME_REGEX, mapping=mapping, @@ -468,6 +474,8 @@ class CellDataConfig: "mysql": normalized.mysql.to_dict(include_password=include_password), "scan_paths": normalized.scan_paths, "year_dir_regex": normalized.year_dir_regex, + "month_dir_regex": normalized.month_dir_regex, + "day_dir_regex": normalized.day_dir_regex, "file_name_regex": normalized.file_name_regex, "file_time_regex": normalized.file_time_regex, "mapping": normalized.mapping, @@ -484,6 +492,8 @@ class CellDataConfig: mysql=MySQLConfig.from_dict(mysql_data, default_dbname="celldata") if isinstance(mysql_data, dict) else default.mysql, scan_paths=data.get("scan_paths", default.scan_paths) if isinstance(data.get("scan_paths", default.scan_paths), list) else default.scan_paths, year_dir_regex=str(data.get("year_dir_regex", default.year_dir_regex)), + month_dir_regex=str(data.get("month_dir_regex", default.month_dir_regex)), + day_dir_regex=str(data.get("day_dir_regex", default.day_dir_regex)), file_name_regex=str(data.get("file_name_regex", default.file_name_regex)), file_time_regex=str(data.get("file_time_regex", default.file_time_regex)), mapping=data.get("mapping", default.mapping) if isinstance(data.get("mapping", default.mapping), dict) else default.mapping, diff --git a/app/services/cell_data.py b/app/services/cell_data.py index b162cef..90110c4 100644 --- a/app/services/cell_data.py +++ b/app/services/cell_data.py @@ -56,6 +56,8 @@ class CellDataProcessor: self.logger = logger self.downloader = RemoteDataDownloader(self.config.remote_data, self._log) self.year_dir_re = re.compile(self.config.year_dir_regex) + self.month_dir_re = re.compile(self.config.month_dir_regex) + self.day_dir_re = re.compile(self.config.day_dir_regex) self.file_name_re = re.compile(self.config.file_name_regex) self.file_time_re = re.compile(self.config.file_time_regex) @@ -135,26 +137,27 @@ class CellDataProcessor: path = self._replace_date_placeholders(str(template).replace("\\", "/").strip()) if not path.startswith("/"): path = self._join_remote_path(self.config.remote_data.remote_dir, path) - if "{maxyear}" not in path: - return path - parts = [part for part in path.split("/") if part] - maxyear_index = next((index for index, part in enumerate(parts) if "{maxyear}" in part), -1) - if maxyear_index < 0: - return path - parent = "/" + "/".join(parts[:maxyear_index]) if maxyear_index else "/" - year_segment = parts[maxyear_index] - years: list[int] = [] - for entry in self._list_dir(parent): - if entry["type"] != "directory": + replacements = [ + ("{maxyear}", "year", self.year_dir_re, "年份"), + ("{maxmonth}", "month", self.month_dir_re, "月份"), + ("{maxday}", "day", self.day_dir_re, "日期"), + ] + for token, group_name, pattern, label in replacements: + token_index = next((index for index, part in enumerate(parts) if token in part), -1) + if token_index < 0: continue - match = self.year_dir_re.search(str(entry["name"])) - if match: - years.append(int(match.group("year"))) - if not years: - raise RuntimeError(f"未找到年份目录: {parent}") - max_year = str(max(years)) - parts[maxyear_index] = year_segment.replace("{maxyear}", max_year) + parent = "/" + "/".join(parts[:token_index]) if token_index else "/" + values: list[int] = [] + for entry in self._list_dir(parent): + if entry["type"] != "directory": + continue + match = pattern.search(str(entry["name"])) + if match: + values.append(int(match.group(group_name))) + if not values: + raise RuntimeError(f"未找到{label}目录: {parent}") + parts[token_index] = parts[token_index].replace(token, str(max(values))) return "/" + "/".join(parts) def _replace_date_placeholders(self, path: str) -> str: diff --git a/docs/project_context.md b/docs/project_context.md index 1478fe9..431064a 100644 --- a/docs/project_context.md +++ b/docs/project_context.md @@ -784,3 +784,14 @@ - 远程数据源页只保留容量数据源与 CellData 数据源两张连接配置卡片,宽屏下并排展示;CellData 的扫描路径、正则和映射 JSON 收纳到「规则设置」弹窗。 - CellData 规则弹窗包含扫描路径列表、路径说明入口、高级正则和 Monaco JSON 编辑器,支持格式化、校验、恢复默认映射和保存规则。 - 验证:`frontend` `npm run build` 通过;构建产物已清理。 + +## 2026-06-25:精简 CellData 数据源路径配置 + +- CellData 数据源卡片去掉“远程目录”输入,连接根目录固定走 `/`;实际数据位置统一由「规则设置」中的扫描路径模板决定,避免两个路径概念混淆。 +- 扫描路径占位符扩展支持 `{maxmonth}`、`{maxday}`,并新增 `month_dir_regex`、`day_dir_regex` 高级正则配置;说明弹窗同步补充相关说明。 +- 验证:`python -m compileall -q app` 通过;`frontend` `npm run build` 通过;构建产物与 Python 缓存已清理。 + +## 2026-06-25:CellData 扫描路径回到数据源卡片 + +- 扫描路径属于文件来源配置,已移回 CellData 数据源卡片中展示和维护;「规则设置」弹窗只保留高级正则与映射 JSON,避免弹窗承担过多基础配置。 +- 验证:`frontend` `npm run build` 通过;构建产物已清理。 diff --git a/frontend/src/components/SettingsPanel.vue b/frontend/src/components/SettingsPanel.vue index eec4c2c..6d83457 100644 --- a/frontend/src/components/SettingsPanel.vue +++ b/frontend/src/components/SettingsPanel.vue @@ -290,8 +290,31 @@ - - + +
+
+ 支持路径模板和多目录组合 + + + +
+
+
+ + + + +
+
+ + + 添加 + +
@@ -629,6 +652,8 @@

占位符

{maxyear}:扫描同级年份目录并取最大年份。

+

{maxmonth}:扫描同级月份目录并取最大月份。

+

{maxday}:扫描同级日期目录并取最大日期。

{yyyy}:当前年份。

{yyyymm}:当前年月。

{yyyymmdd}:当前日期。

@@ -657,31 +682,6 @@ @after-leave="disposeCellDataEditor" >
-
-
- 扫描路径 - - - -
-
-
- - - - -
-
- - - 添加 - -
-
高级匹配 @@ -693,11 +693,21 @@ + + + + + + + + + + - + @@ -859,6 +869,8 @@ const cellDataRemoteForm = reactive({ const cellDataRegexForm = reactive({ year_dir_regex: '(?P\\\\d{4})年', + month_dir_regex: '(?P\\\\d{1,2})月', + day_dir_regex: '(?P\\\\d{1,2})日', file_name_regex: '^Result_300_.*\\\\.zip$', file_time_regex: '(?P\\\\d{14})(?=\\\\.zip$)' }); @@ -1032,6 +1044,8 @@ async function loadConfig() { Object.assign(cellDataMysqlForm, normalizeCellDataMysqlConfig(config.cell_data?.mysql)); cellDataScanPaths.value = normalizeCellDataScanPaths(config.cell_data?.scan_paths); cellDataRegexForm.year_dir_regex = config.cell_data?.year_dir_regex || '(?P\\\\d{4})年'; + cellDataRegexForm.month_dir_regex = config.cell_data?.month_dir_regex || '(?P\\\\d{1,2})月'; + cellDataRegexForm.day_dir_regex = config.cell_data?.day_dir_regex || '(?P\\\\d{1,2})日'; cellDataRegexForm.file_name_regex = config.cell_data?.file_name_regex || '^Result_300_.*\\\\.zip$'; cellDataRegexForm.file_time_regex = config.cell_data?.file_time_regex || '(?P\\\\d{14})(?=\\\\.zip$)'; cellDataMappingText.value = JSON.stringify(config.cell_data?.mapping || {}, null, 2); @@ -1254,6 +1268,8 @@ function getCellDataSettingsPayload() { return { scan_paths: normalizeCellDataScanPaths(cellDataScanPaths.value), year_dir_regex: cellDataRegexForm.year_dir_regex, + month_dir_regex: cellDataRegexForm.month_dir_regex, + day_dir_regex: cellDataRegexForm.day_dir_regex, file_name_regex: cellDataRegexForm.file_name_regex, file_time_regex: cellDataRegexForm.file_time_regex, mapping diff --git a/frontend/src/types.ts b/frontend/src/types.ts index ba31b2c..e566cde 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -151,6 +151,8 @@ export interface CellDataConfig { }; scan_paths: string[]; year_dir_regex: string; + month_dir_regex: string; + day_dir_regex: string; file_name_regex: string; file_time_regex: string; mapping: Record;