YueYunyun 3 months ago
parent
commit
01e89c89e8
100 changed files with 3178 additions and 203 deletions
  1. 18 0
      SourceCode/IntelligentRailwayCosting/.script/init.sql
  2. 26 0
      SourceCode/IntelligentRailwayCosting/app/__init__.py
  3. 51 10
      SourceCode/IntelligentRailwayCosting/app/config.yml
  4. 3 0
      SourceCode/IntelligentRailwayCosting/app/core/api/__init__.py
  5. 65 0
      SourceCode/IntelligentRailwayCosting/app/core/api/response.py
  6. 37 0
      SourceCode/IntelligentRailwayCosting/app/core/api/table_response.py
  7. 16 0
      SourceCode/IntelligentRailwayCosting/app/core/configs/__init__.py
  8. 35 0
      SourceCode/IntelligentRailwayCosting/app/core/configs/ai_config.py
  9. 48 0
      SourceCode/IntelligentRailwayCosting/app/core/configs/ai_fastgpt_config.py
  10. 27 0
      SourceCode/IntelligentRailwayCosting/app/core/configs/app_config.py
  11. 60 0
      SourceCode/IntelligentRailwayCosting/app/core/configs/config.py
  12. 17 0
      SourceCode/IntelligentRailwayCosting/app/core/configs/database_config.py
  13. 8 0
      SourceCode/IntelligentRailwayCosting/app/core/dtos/__init__.py
  14. 136 0
      SourceCode/IntelligentRailwayCosting/app/core/dtos/chapter.py
  15. 41 0
      SourceCode/IntelligentRailwayCosting/app/core/dtos/log.py
  16. 84 0
      SourceCode/IntelligentRailwayCosting/app/core/dtos/project.py
  17. 103 0
      SourceCode/IntelligentRailwayCosting/app/core/dtos/quota_input.py
  18. 72 0
      SourceCode/IntelligentRailwayCosting/app/core/dtos/total_budget_info.py
  19. 127 0
      SourceCode/IntelligentRailwayCosting/app/core/dtos/total_budget_item.py
  20. 24 0
      SourceCode/IntelligentRailwayCosting/app/core/dtos/tree.py
  21. 33 0
      SourceCode/IntelligentRailwayCosting/app/core/dtos/user.py
  22. 2 0
      SourceCode/IntelligentRailwayCosting/app/core/enum/__init__.py
  23. 36 0
      SourceCode/IntelligentRailwayCosting/app/core/enum/log.py
  24. 2 0
      SourceCode/IntelligentRailwayCosting/app/core/log/__init__.py
  25. 86 0
      SourceCode/IntelligentRailwayCosting/app/core/log/log_record.py
  26. 9 0
      SourceCode/IntelligentRailwayCosting/app/core/models/__init__.py
  27. 46 0
      SourceCode/IntelligentRailwayCosting/app/core/models/chapter.py
  28. 27 0
      SourceCode/IntelligentRailwayCosting/app/core/models/log.py
  29. 1 1
      SourceCode/IntelligentRailwayCosting/app/core/models/project.py
  30. 61 0
      SourceCode/IntelligentRailwayCosting/app/core/models/quota_input.py
  31. 1 1
      SourceCode/IntelligentRailwayCosting/app/core/models/team.py
  32. 41 0
      SourceCode/IntelligentRailwayCosting/app/core/models/total_budget_info.py
  33. 48 0
      SourceCode/IntelligentRailwayCosting/app/core/models/total_budget_item.py
  34. 2 2
      SourceCode/IntelligentRailwayCosting/app/core/models/user.py
  35. 4 0
      SourceCode/IntelligentRailwayCosting/app/core/user_session/__init__.py
  36. 27 68
      SourceCode/IntelligentRailwayCosting/app/core/user_session/current_user.py
  37. 26 0
      SourceCode/IntelligentRailwayCosting/app/core/user_session/permission.py
  38. 12 53
      SourceCode/IntelligentRailwayCosting/app/core/user_session/user_session.py
  39. 7 9
      SourceCode/IntelligentRailwayCosting/app/main.py
  40. 0 0
      SourceCode/IntelligentRailwayCosting/app/models/__init__.py
  41. 10 0
      SourceCode/IntelligentRailwayCosting/app/routes/__init__.py
  42. 58 0
      SourceCode/IntelligentRailwayCosting/app/routes/auth.py
  43. 33 0
      SourceCode/IntelligentRailwayCosting/app/routes/log.py
  44. 63 0
      SourceCode/IntelligentRailwayCosting/app/routes/project.py
  45. 4 0
      SourceCode/IntelligentRailwayCosting/app/services/__init__.py
  46. 37 0
      SourceCode/IntelligentRailwayCosting/app/services/log.py
  47. 86 0
      SourceCode/IntelligentRailwayCosting/app/services/project.py
  48. 19 10
      SourceCode/IntelligentRailwayCosting/app/services/user.py
  49. 4 0
      SourceCode/IntelligentRailwayCosting/app/stores/__init__.py
  50. 124 0
      SourceCode/IntelligentRailwayCosting/app/stores/budget.py
  51. 117 0
      SourceCode/IntelligentRailwayCosting/app/stores/log.py
  52. 101 0
      SourceCode/IntelligentRailwayCosting/app/stores/project.py
  53. 14 16
      SourceCode/IntelligentRailwayCosting/app/stores/user.py
  54. 18 0
      SourceCode/IntelligentRailwayCosting/app/tools/db_helper/__init__.py
  55. 8 6
      SourceCode/IntelligentRailwayCosting/app/tools/db_helper/base.py
  56. 6 2
      SourceCode/IntelligentRailwayCosting/app/tools/db_helper/mysql_helper.py
  57. 21 10
      SourceCode/IntelligentRailwayCosting/app/tools/db_helper/sqlserver_helper.py
  58. 7 15
      SourceCode/IntelligentRailwayCosting/app/tools/utils/file_helper.py
  59. 8 0
      SourceCode/IntelligentRailwayCosting/app/views/__init__.py
  60. 18 0
      SourceCode/IntelligentRailwayCosting/app/views/log.py
  61. 15 0
      SourceCode/IntelligentRailwayCosting/app/views/login.py
  62. 16 0
      SourceCode/IntelligentRailwayCosting/app/views/project.py
  63. 33 0
      SourceCode/IntelligentRailwayCosting/app/views/static/account/login.css
  64. 56 0
      SourceCode/IntelligentRailwayCosting/app/views/static/account/login.js
  65. 4 0
      SourceCode/IntelligentRailwayCosting/app/views/static/base/css/style.bundle.css
  66. 76 0
      SourceCode/IntelligentRailwayCosting/app/views/static/base/css/styles.css
  67. 0 0
      SourceCode/IntelligentRailwayCosting/app/views/static/base/js/scripts.bundle.js
  68. 312 0
      SourceCode/IntelligentRailwayCosting/app/views/static/base/js/utils.js
  69. 0 0
      SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/datatables/datatables.bundle.css
  70. 20 0
      SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/datatables/datatables.bundle.js
  71. BIN
      SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/@fortawesome/fa-brands-400.ttf
  72. BIN
      SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/@fortawesome/fa-brands-400.woff2
  73. BIN
      SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/@fortawesome/fa-regular-400.ttf
  74. BIN
      SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/@fortawesome/fa-regular-400.woff2
  75. BIN
      SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/@fortawesome/fa-solid-900.ttf
  76. BIN
      SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/@fortawesome/fa-solid-900.woff2
  77. BIN
      SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/@fortawesome/fa-v4compatibility.ttf
  78. BIN
      SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/@fortawesome/fa-v4compatibility.woff2
  79. BIN
      SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/bootstrap-icons/bootstrap-icons.woff
  80. BIN
      SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/bootstrap-icons/bootstrap-icons.woff2
  81. BIN
      SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/keenicons/keenicons-duotone.eot
  82. 85 0
      SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/keenicons/keenicons-duotone.svg
  83. BIN
      SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/keenicons/keenicons-duotone.ttf
  84. BIN
      SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/keenicons/keenicons-duotone.woff
  85. BIN
      SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/keenicons/keenicons-outline.eot
  86. 14 0
      SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/keenicons/keenicons-outline.svg
  87. BIN
      SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/keenicons/keenicons-outline.ttf
  88. BIN
      SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/keenicons/keenicons-outline.woff
  89. BIN
      SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/keenicons/keenicons-solid.eot
  90. 20 0
      SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/keenicons/keenicons-solid.svg
  91. BIN
      SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/keenicons/keenicons-solid.ttf
  92. BIN
      SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/keenicons/keenicons-solid.woff
  93. BIN
      SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/line-awesome/la-brands-400.eot
  94. 16 0
      SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/line-awesome/la-brands-400.svg
  95. BIN
      SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/line-awesome/la-brands-400.ttf
  96. BIN
      SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/line-awesome/la-brands-400.woff
  97. BIN
      SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/line-awesome/la-brands-400.woff2
  98. BIN
      SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/line-awesome/la-regular-400.eot
  99. 286 0
      SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/line-awesome/la-regular-400.svg
  100. BIN
      SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/line-awesome/la-regular-400.ttf

+ 18 - 0
SourceCode/IntelligentRailwayCosting/.script/init.sql

@@ -0,0 +1,18 @@
+CREATE DATABASE IF NOT EXISTS iwb_railway_costing_v1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
+USE iwb_railway_costing_v1;
+
+-- 创建日志表
+CREATE TABLE IF NOT EXISTS sys_log (
+    id INT AUTO_INCREMENT PRIMARY KEY,
+    username VARCHAR(255) NOT NULL COMMENT '用户名',
+    operation_type VARCHAR(50) NOT NULL COMMENT '操作类型',
+    operation_desc VARCHAR(1000) COMMENT '操作描述',
+    operation_result TINYINT COMMENT '操作结果(0:失败, 1:成功)',
+    operation_module VARCHAR(100) COMMENT '操作模块',
+    operation_data TEXT COMMENT '操作数据',
+    data_changes TEXT COMMENT '数据变更记录',
+    operation_ip VARCHAR(50) COMMENT '操作IP',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    INDEX idx_username (username),
+    INDEX idx_created_at (created_at)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='操作日志表';

+ 26 - 0
SourceCode/IntelligentRailwayCosting/app/__init__.py

@@ -0,0 +1,26 @@
+from flask import Flask
+from routes.auth import login_manager
+from routes import register_api
+from views import register_views
+
+
+def create_app():
+    app = Flask(__name__, static_folder='views/static')
+    app.secret_key = "1qwe2iwb3vber"
+    app.config['JSON_AS_ASCII'] = False
+
+    login_manager.init_app(app)
+
+    # 注册所有蓝图
+    register_api(app)
+    register_views(app)
+
+    # 注册自定义过滤器
+    for rule in app.url_map.iter_rules():
+        route = {
+            'endpoint': rule.endpoint,
+            'methods': sorted(rule.methods),
+            'path': str(rule)
+        }
+        print(f"URL [{str(rule)}] ==> endpoint: {rule.endpoint}, methods: {sorted(rule.methods)}")
+    return app

+ 51 - 10
SourceCode/IntelligentRailwayCosting/app/config.yml

@@ -1,3 +1,8 @@
+app:
+  # 应用名称
+  name: '铁路造价智能化工具'
+  version: '2024' # 应用版本 2020|2024
+  user_version: true
 db:
   # SQL Server 配置
   # SQL Server 2008:'{SQL Server}' 或 '{SQL Server Native Client 10.0}'
@@ -7,13 +12,20 @@ db:
   # 在Windows系统的ODBC数据源管理器中查看已安装的驱动程序,选择相应的驱动名称
   # 在开始菜单的列表里面找到"Windows管理工具"打开, 然后点开里面的"ODBC数据源"。
   # 打开以后,点开上方"驱动程序"。 就可以看到系统所安装的ODBC驱动程序
-  sqlserver_mian:
+  sqlserver_mian_2024:
     driver: '{ODBC Driver 17 for SQL Server}'
     server: shvber.com,50535
     username: sa
     password: Iwb2017
     database: Iwb_RecoData2024
     trusted_connection: false
+  sqlserver_mian_2020:
+    driver: '{ODBC Driver 17 for SQL Server}'
+    server: shvber.com,50535
+    username: sa
+    password: Iwb2017
+    database: Iwb_RecoData2020
+    trusted_connection: false
   Iwb_RecoData2024:
     driver: '{ODBC Driver 17 for SQL Server}'
     server: shvber.com,50535
@@ -21,18 +33,47 @@ db:
     password: Iwb2017
     database: Iwb_RecoData2024
     trusted_connection: false
+  Iwb_RecoData2020:
+    driver: '{ODBC Driver 17 for SQL Server}'
+    server: shvber.com,50535
+    username: sa
+    password: Iwb2017
+    database: Iwb_RecoData2020
+    trusted_connection: false
   # MySQL 配置
   mysql_main:
-    db: iwb_data
-    host: localhost
-    port: 3306
+    db: iwb_railway_costing_v1
+    host: 192.168.0.81
+    port: 3307
     user: root
-    password: your_password
+    password: Iwb-2024
     charset: utf8mb4
   # MySQL 示例数据库配置
-  example_db:
-    host: localhost
-    port: 3306
-    user: example_user
-    password: example_password
+  iwb_railway_costing_v1:
+    db: iwb_railway_costing_v1
+    host: 192.168.0.81
+    port: 3307
+    user: root
+    password: Iwb-2024
     charset: utf8mb4
+ai:
+  api_key: sk-febca8fea4a247f096cedeea9f185520
+  api_url: https://dashscope.aliyuncs.com/compatible-mode/v1
+  model: qwen-plus
+  max_tokens: 1024
+fastgpt_ai:
+  api_url: http://192.168.0.104:8020/api
+  api_key: fastgpt-o4CF7Pu1FRTvHjWFqeNcClBS6ApyflNfkBGXo9p51fuBMAX1L0erU8yz8
+  apps:
+    app_01_2020:
+      api_url: http://192.168.0.104:8020/api
+      api_key: fastgpt-o4CF7Pu1FRTvHjWFqeNcClBS6ApyflNfkBGXo9p51fuBMAX1L0erU8yz8
+    app_02_2020:
+      api_url: http://192.168.0.104:8020/api
+      api_key: fastgpt-o4CF7Pu1FRTvHjWFqeNcClBS6ApyflNfkBGXo9p51fuBMAX1L0erU8yz8
+    app_01_2024:
+      api_url: http://192.168.0.104:8020/api
+      api_key: fastgpt-o4CF7Pu1FRTvHjWFqeNcClBS6ApyflNfkBGXo9p51fuBMAX1L0erU8yz8
+    app_02_2024:
+      api_url: http://192.168.0.104:8020/api
+      api_key: fastgpt-o4CF7Pu1FRTvHjWFqeNcClBS6ApyflNfkBGXo9p51fuBMAX1L0erU8yz8

+ 3 - 0
SourceCode/IntelligentRailwayCosting/app/core/api/__init__.py

@@ -0,0 +1,3 @@
+from .response import ResponseBase
+from .table_response import TableResponse
+

+ 65 - 0
SourceCode/IntelligentRailwayCosting/app/core/api/response.py

@@ -0,0 +1,65 @@
+from typing import Dict, Any, Optional, List
+from flask import jsonify, Response
+
+
+class ResponseBase:
+    """统一的API响应结构"""
+
+    @staticmethod
+    def success(data: Optional[Any] = None, message: str = "操作成功") -> Response:
+        """成功响应
+        Args:
+            data: 响应数据
+            message: 响应消息
+        Returns:
+            Dict: 统一的成功响应格式
+        """
+        response = {
+            "success": True,
+            "code": 200,
+            "message": message
+        }
+        if data is not None:
+            response["data"] = data
+        return jsonify(response)
+
+    @staticmethod
+    def error(message: str = "操作失败", code: int = 400, data: Optional[Any] = None) -> Response:
+        """错误响应
+        Args:
+            message: 错误消息
+            code: 错误码
+            data: 错误详细信息
+        Returns:
+            Dict: 统一的错误响应格式
+        """
+        response = {
+            "success": False,
+            "code": code,
+            "message": message
+        }
+        if data is not None:
+            response["data"] = data
+        return jsonify(response)
+
+    @staticmethod
+    def json_response(success: bool = True, code: int = 200, message: str = "", data: Optional[Any] = None) -> Response:
+        """自定义响应
+        Args:
+            success: 是否成功
+            code: 状态码
+            message: 响应消息
+            data: 响应数据
+        Returns:
+            Dict: 统一的响应格式
+        """
+        response = {
+            "success": success,
+            "code": code,
+            "message": message
+        }
+        if data is not None:
+            response["data"] = data
+        return jsonify(response)
+
+

+ 37 - 0
SourceCode/IntelligentRailwayCosting/app/core/api/table_response.py

@@ -0,0 +1,37 @@
+from typing import Dict, Any, Optional, List
+from flask import jsonify, Response
+from .response import ResponseBase
+
+class TableResponse(ResponseBase):
+    """表格数据响应结构"""
+
+    @staticmethod
+    def success(rows: List[Dict] = None, total: int = 0, message: str = "操作成功") -> Response:
+        """表格数据成功响应
+        Args:
+            rows: 表格数据行列表
+            total: 数据总条数
+            message: 响应消息
+        Returns:
+            Dict: 统一的表格数据响应格式
+        """
+        return ResponseBase.success(
+            data={
+                "rows": rows or [],
+                "total": total
+            },
+            message=message
+        )
+
+    @staticmethod
+    def error(message: str = "操作失败", code: int = 400, **kwargs) -> Response:
+        """表格数据错误响应
+        Args:
+            message: 错误消息
+            code: 错误码
+        Returns:
+            Response: 统一的错误响应格式
+            :param code:
+            :param message:
+        """
+        return ResponseBase.error(message=message, code=code)

+ 16 - 0
SourceCode/IntelligentRailwayCosting/app/core/configs/__init__.py

@@ -0,0 +1,16 @@
+import os
+from .config import config
+
+# 导出配置实例和配置对象
+__all__ = ['config', 'app', 'database', 'ai', 'fastgpt_ai']
+
+_path = os.environ.get("CONFIG_PATH", os.path.join(os.path.dirname(__file__), "../..", "config.yml"))
+
+# 初始化配置
+config.load_config(_path)
+# 导出配置对象的快捷方式
+app = config.app
+database = config.database
+ai= config.ai
+fastgpt_ai = config.fastgpt_ai
+

+ 35 - 0
SourceCode/IntelligentRailwayCosting/app/core/configs/ai_config.py

@@ -0,0 +1,35 @@
+class AIConfig:
+    """AI应用配置实体类"""
+    def __init__(self):
+        self._api_url = None
+        self._api_key = None
+        self._model = None
+        self._max_tokens = None
+
+    @property
+    def api_url(self):
+        return self._api_url
+
+    @property
+    def api_key(self):
+        return self._api_key
+
+    @property
+    def model(self):
+        return self._model
+
+    @property
+    def max_tokens(self):
+        return self._max_tokens
+
+    def update_config(self, config):
+        """更新AI应用配置
+        
+        Args:
+            config: AI应用配置字典
+        """
+        self._api_url = config.get('api_url')
+        self._api_key = config.get('api_key')
+        self._model = config.get('model')
+        self._max_tokens = config.get('max_tokens')
+

+ 48 - 0
SourceCode/IntelligentRailwayCosting/app/core/configs/ai_fastgpt_config.py

@@ -0,0 +1,48 @@
+from core.configs.ai_config import AIConfig
+
+
+class FastGPTAIConfig:
+    """FastGPT AI配置管理类"""
+    _api_url = None
+    _api_key = None
+    _apps = {}
+
+    @property
+    def api_url(self):
+        return self._api_url
+
+    @property
+    def api_key(self):
+        return self._api_key
+
+    @property
+    def apps(self):
+        return self._apps
+
+    def __getitem__(self, app_key: str):
+        """支持字典式访问应用配置
+
+        Args:
+            app_key: 应用配置键名
+
+        Returns:
+            AIConfig: 应用配置实例
+        """
+        return self._apps.get(app_key)
+
+    def update_config(self, config):
+        """更新FastGPT AI配置
+
+        Args:
+            config: FastGPT AI配置字典
+        """
+        self._api_url = config.get('api_url')
+        self._api_key = config.get('api_key')
+        # 更新apps配置
+        apps_config = config.get('apps')
+        # 更新apps配置
+        self._apps = {}
+        for app_key, app_config in apps_config.items():
+            app = AIConfig()
+            app.update_config(app_config)
+            self._apps[app_key] = app

+ 27 - 0
SourceCode/IntelligentRailwayCosting/app/core/configs/app_config.py

@@ -0,0 +1,27 @@
+class AppConfig:
+    """应用配置管理类"""
+    _name = None
+    _version = None
+    _user_version = None
+
+    @property
+    def name(self):
+        return self._name
+
+    @property
+    def version(self):
+        return self._version
+
+    @property
+    def user_version(self)->bool:
+        return self._user_version
+
+    def update_config(self, config):
+        """更新应用配置
+        
+        Args:
+            config: 应用配置字典
+        """
+        self._name = config.get('name')
+        self._version = config.get('version')
+        self._user_version = config.get('user_version')

+ 60 - 0
SourceCode/IntelligentRailwayCosting/app/core/configs/config.py

@@ -0,0 +1,60 @@
+
+import os
+import yaml
+from .app_config import AppConfig
+from .database_config import DatabaseConfig
+from .ai_config import AIConfig
+from .ai_fastgpt_config import FastGPTAIConfig
+
+class Config:
+    """配置管理类"""
+    _instance = None
+    _config = None
+    app = None
+    database = None
+    ai = None
+    fastgpt_ai = None
+
+    def __new__(cls):
+        if cls._instance is None:
+            cls._instance = super().__new__(cls)
+            cls.app = AppConfig()
+            cls.database = DatabaseConfig()
+            cls.ai = AIConfig()
+            cls.fastgpt_ai = FastGPTAIConfig()
+        return cls._instance
+
+    @classmethod
+    def load_config(cls, path=None):
+        """加载配置文件
+        
+        Args:
+            path: 配置文件路径,如果为None则使用默认路径
+        """
+        if path is None:
+            path = os.environ.get("CONFIG_PATH", os.path.join(os.path.dirname(__file__), "../..", "config.yml"))
+
+        with open(path, 'r', encoding='utf-8') as f:
+            cls._config = yaml.safe_load(f)
+            # 初始化应用配置
+            cls.app.update_config(cls._config.get('app', {}))
+            # 初始化数据库配置
+            cls.database.update_config(cls._config.get('db', {}))
+            # 初始化AI配置
+            cls.ai.update_config(cls._config.get('ai', {}))
+            # 初始化FastGPT AI配置
+            cls.fastgpt_ai.update_config(cls._config.get('fastgpt_ai', {}))
+
+    @classmethod
+    def reload(cls, path=None):
+        """重新加载配置
+        Args:
+            path: 配置文件路径,如果为None则使用默认路径
+        """
+        cls._config = None
+        cls.load_config(path)
+
+# 创建全局配置实例
+config = Config()
+
+  

+ 17 - 0
SourceCode/IntelligentRailwayCosting/app/core/configs/database_config.py

@@ -0,0 +1,17 @@
+class DatabaseConfig:
+    """数据库配置管理类"""
+    _configs = {}
+
+    def __getitem__(self, key):
+        return self._configs.get(key, {})
+
+    def get(self, key, default=None):
+        return self._configs.get(key, default)
+
+    def update_config(self, configs):
+        """更新数据库配置
+        
+        Args:
+            configs: 数据库配置字典
+        """
+        self._configs = configs

+ 8 - 0
SourceCode/IntelligentRailwayCosting/app/core/dtos/__init__.py

@@ -0,0 +1,8 @@
+from .log import LogDto
+
+from .user import UserDto
+from .project import ProjectDto
+from .total_budget_info import TotalBudgetInfoDto
+from .total_budget_item import TotalBudgetItemDto
+from .chapter import ChapterDto
+from .quota_input import QuotaInputDto

+ 136 - 0
SourceCode/IntelligentRailwayCosting/app/core/dtos/chapter.py

@@ -0,0 +1,136 @@
+from typing import Optional, List
+from pydantic import BaseModel
+from ..models.chapter import ChapterModel
+from ..models.total_budget_item import TotalBudgetItemModel
+
+class ChapterDto(BaseModel):
+    # 章节表字段
+    item_id: int
+    item_code: Optional[str] = None
+    default_first_page: Optional[str] = None
+    chapter: Optional[str] = None
+    section: Optional[str] = None
+    project_name: Optional[str] = None
+    unit: Optional[str] = None
+    # is_locked: Optional[str] = None
+    # unit2: Optional[str] = None
+    item_type: Optional[str] = None
+    # project_code: Optional[str] = None
+    # cooperation_fee_code: Optional[str] = None
+    # formula_code: Optional[str] = None
+    # material_price_diff_code: Optional[str] = None
+    # calculation_formula: Optional[str] = None
+    # selected_labor_cost: Optional[int] = None
+    # shift_labor_cost: Optional[int] = None
+    # rate_scheme: Optional[str] = None
+    # transport_scheme: Optional[str] = None
+    # summary_method: Optional[str] = None
+    # cumulative_coefficient: Optional[float] = None
+    # display_range: Optional[str] = None
+    # cost_category: Optional[str] = None
+    # auto_calculate_quantity: Optional[bool] = None
+    # comprehensive_display: Optional[bool] = None
+    # list_code: Optional[str] = None
+    # sub_division_feature: Optional[str] = None
+    # quantity_calculation_rule: Optional[str] = None
+    # work_content: Optional[str] = None
+    # note: Optional[str] = None
+    # seat_count: Optional[int] = None
+    # quota_book_number: Optional[str] = None
+    # quota_section_number: Optional[str] = None
+    # professional_name: Optional[str] = None
+    # tax_category: Optional[str] = None
+
+    # 总概算条目字段
+    budget_id: Optional[int] = None
+    # project_quantity1: Optional[float] = None
+    # project_quantity2: Optional[float] = None
+    # budget_value: Optional[float] = None
+    # budget_index1: Optional[float] = None
+    # budget_index2: Optional[float] = None
+    # construction_cost: Optional[float] = None
+    # installation_cost: Optional[float] = None
+    # equipment_cost: Optional[float] = None
+    # other_cost: Optional[float] = None
+    # transport_unit_price: Optional[float] = None
+    # parameter_adjustment: Optional[str] = None
+    # project_quantity1_input: Optional[str] = None
+    # project_quantity2_input: Optional[str] = None
+    # installation_sub_item: Optional[str] = None
+    # tax_rate: Optional[float] = None
+    # bridge_type: Optional[str] = None
+    # electricity_price_category: Optional[int] = None
+    # tax: Optional[float] = None
+
+    @classmethod
+    def from_model(cls, chapter_model: ChapterModel, budget_item_model: Optional[TotalBudgetItemModel] = None) -> 'ChapterDto':
+        """从数据库模型创建DTO对象"""
+        dto = cls(
+            # 章节表字段
+            item_id=chapter_model.item_id,
+            item_code=chapter_model.item_code,
+            default_first_page=chapter_model.default_first_page,
+            chapter=chapter_model.chapter,
+            section=chapter_model.section,
+            project_name=chapter_model.project_name,
+            unit=chapter_model.unit,
+            # is_locked=chapter_model.is_locked,
+            # unit2=chapter_model.unit2,
+            item_type=chapter_model.item_type,
+            # project_code=chapter_model.project_code,
+            # cooperation_fee_code=chapter_model.cooperation_fee_code,
+            # formula_code=chapter_model.formula_code,
+            # material_price_diff_code=chapter_model.material_price_diff_code,
+            # calculation_formula=chapter_model.calculation_formula,
+            # selected_labor_cost=chapter_model.selected_labor_cost,
+            # shift_labor_cost=chapter_model.shift_labor_cost,
+            # rate_scheme=chapter_model.rate_scheme,
+            # transport_scheme=chapter_model.transport_scheme,
+            # summary_method=chapter_model.summary_method,
+            # cumulative_coefficient=chapter_model.cumulative_coefficient,
+            # display_range=chapter_model.display_range,
+            # cost_category=chapter_model.cost_category,
+            # auto_calculate_quantity=chapter_model.auto_calculate_quantity,
+            # comprehensive_display=chapter_model.comprehensive_display,
+            # list_code=chapter_model.list_code,
+            # sub_division_feature=chapter_model.sub_division_feature,
+            # quantity_calculation_rule=chapter_model.quantity_calculation_rule,
+            # work_content=chapter_model.work_content,
+            # note=chapter_model.note,
+            # seat_count=chapter_model.seat_count,
+            # quota_book_number=chapter_model.quota_book_number,
+            # quota_section_number=chapter_model.quota_section_number,
+            # professional_name=chapter_model.professional_name,
+            # tax_category=chapter_model.tax_category
+        )
+
+        # 如果提供了总概算条目模型,则添加相关字段
+        if budget_item_model:
+            dto.budget_id = budget_item_model.budget_id
+            # dto.project_quantity1 = budget_item_model.project_quantity1
+            # dto.project_quantity2 = budget_item_model.project_quantity2
+            # dto.budget_value = budget_item_model.budget_value
+            # dto.budget_index1 = budget_item_model.budget_index1
+            # dto.budget_index2 = budget_item_model.budget_index2
+            # dto.construction_cost = budget_item_model.construction_cost
+            # dto.installation_cost = budget_item_model.installation_cost
+            # dto.equipment_cost = budget_item_model.equipment_cost
+            # dto.other_cost = budget_item_model.other_cost
+            # dto.transport_unit_price = budget_item_model.transport_unit_price
+            # dto.parameter_adjustment = budget_item_model.parameter_adjustment
+            # dto.project_quantity1_input = budget_item_model.project_quantity1_input
+            # dto.project_quantity2_input = budget_item_model.project_quantity2_input
+            # dto.installation_sub_item = budget_item_model.installation_sub_item
+            # dto.tax_rate = budget_item_model.tax_rate
+            # dto.bridge_type = budget_item_model.bridge_type
+            # dto.electricity_price_category = budget_item_model.electricity_price_category
+            # dto.tax = budget_item_model.tax
+
+        return dto
+
+    def to_dict(self) -> dict:
+        """转换为字典格式"""
+        return self.model_dump()
+
+    class Config:
+        from_attributes = True

+ 41 - 0
SourceCode/IntelligentRailwayCosting/app/core/dtos/log.py

@@ -0,0 +1,41 @@
+from pydantic import BaseModel
+from typing import Optional
+from datetime import datetime
+from ..models import LogModel
+
+class LogDto(BaseModel):
+    """日志信息DTO"""
+    id: Optional[int] = None
+    username: str
+    operation_type: str
+    operation_desc: str
+    operation_result: str
+    operation_module: str
+    operation_data: Optional[str] = None
+    data_changes: Optional[str] = None
+    operation_ip: str
+    created_at: datetime
+
+    @classmethod
+    def from_model(cls, model: LogModel) -> 'LogDto':
+        """从数据库模型创建DTO对象"""
+        return cls(
+            id=model.id,
+            username=model.username,
+            operation_type=model.operation_type,
+            operation_desc=model.operation_desc,
+            operation_result=model.operation_result,
+            operation_module=model.operation_module,
+            operation_data=model.operation_data,
+            data_changes=model.data_changes,
+            operation_ip=model.operation_ip,
+            created_at=model.created_at
+        )
+
+    def to_dict(self) -> dict:
+        """转换为字典格式"""
+        return self.model_dump()
+
+    class Config:
+        from_attributes = True
+ 

+ 84 - 0
SourceCode/IntelligentRailwayCosting/app/core/dtos/project.py

@@ -0,0 +1,84 @@
+from pydantic import BaseModel
+from typing import Optional
+from datetime import datetime
+from ..models import ProjectModel
+
+class ProjectDto(BaseModel):
+    """项目信息DTO"""
+    project_id: Optional[str] = None
+    project_name: str
+    project_manager: Optional[str] = None
+    design_stage: Optional[str] = None
+    project_desc: Optional[str] = None
+    short_name: Optional[str] = None
+    project_type: Optional[str] = None
+    project_version: Optional[str] = None
+    unit: Optional[str] = None
+    create_time: Optional[datetime] = None
+    # compilation_method: Optional[str] = None
+    # compilation_scope: Optional[str] = None
+    # total_engineering: Optional[float] = None
+    # total_budget: Optional[float] = None
+    # budget_index: Optional[float] = None
+    # standard_quota: Optional[str] = None
+    # train_transport_standard: Optional[str] = None
+    # material_library: Optional[str] = None
+    # work_shift_library: Optional[str] = None
+    # equipment_library: Optional[str] = None
+    # review_status: Optional[int] = None
+    # years_to_construction: Optional[int] = None
+    # project_password: Optional[str] = None
+    # railway_grade: Optional[str] = None
+    # main_line_count: Optional[int] = None
+    # traction_type: Optional[str] = None
+    # blocking_mode: Optional[str] = None
+    # station_count: Optional[str] = None
+    # target_speed: Optional[int] = None
+    # print_compilation_review: Optional[bool] = None
+    # unit_conversion: Optional[bool] = None
+    # completion_status: Optional[str] = None
+
+    @classmethod
+    def from_model(cls, model: ProjectModel) -> 'ProjectDto':
+        """从数据库模型创建DTO对象"""
+        return cls(
+            project_id=model.project_id,
+            project_name=model.project_name,
+            project_manager=model.project_manager,
+            design_stage=model.design_stage,
+            project_desc=model.project_description,
+            short_name=model.short_name,
+            project_type=model.project_type,
+            project_version=model.project_version,
+            unit=model.unit,
+            create_time=model.create_time,
+            # compilation_method=model.compilation_method,
+            # compilation_scope=model.compilation_scope,
+            # total_engineering=model.total_engineering,
+            # total_budget=model.total_budget,
+            # budget_index=model.budget_index,
+            # standard_quota=model.standard_quota,
+            # train_transport_standard=model.train_transport_standard,
+            # material_library=model.material_library,
+            # work_shift_library=model.work_shift_library,
+            # equipment_library=model.equipment_library,
+            # review_status=model.review_status,
+            # years_to_construction=model.years_to_construction,
+            # project_password=model.project_password,
+            # railway_grade=model.railway_grade,
+            # main_line_count=model.main_line_count,
+            # traction_type=model.traction_type,
+            # blocking_mode=model.blocking_mode,
+            # station_count=model.station_count,
+            # target_speed=model.target_speed,
+            # print_compilation_review=model.print_compilation_review,
+            # unit_conversion=model.unit_conversion,
+            # completion_status=model.completion_status
+        )
+
+    def to_dict(self) -> dict:
+        """转换为字典格式"""
+        return self.model_dump()
+
+    class Config:
+        from_attributes = True

+ 103 - 0
SourceCode/IntelligentRailwayCosting/app/core/dtos/quota_input.py

@@ -0,0 +1,103 @@
+from pydantic import BaseModel
+from typing import Optional
+from ..models.quota_input import QuotaInputModel
+
+class QuotaInputDto(BaseModel):
+    """定额输入DTO"""
+    quota_id: Optional[int] = None
+    budget_id: int
+    item_id: int
+    quota_code: str
+    sequence_number: Optional[int] = None
+    project_name: Optional[str] = None
+    unit: Optional[str] = None
+    project_quantity: Optional[float] = None
+    project_quantity_input: Optional[str] = None
+    quota_adjustment: Optional[str] = None
+    unit_price: Optional[float] = None
+    compilation_unit_price: Optional[float] = None
+    total_price: Optional[float] = None
+    compilation_total_price: Optional[float] = None
+    unit_weight: Optional[float] = None
+    total_weight: Optional[float] = None
+    labor_cost: Optional[float] = None
+    compilation_labor_cost: Optional[float] = None
+    material_cost: Optional[float] = None
+    compilation_material_cost: Optional[float] = None
+    deduct_material_cost: Optional[float] = None
+    compilation_deduct_material_cost: Optional[float] = None
+    mechanical_cost: Optional[float] = None
+    compilation_mechanical_cost: Optional[float] = None
+    equipment_cost: Optional[float] = None
+    compilation_equipment_cost: Optional[float] = None
+    transport_cost: Optional[float] = None
+    compilation_transport_cost: Optional[float] = None
+    quota_workday: Optional[float] = None
+    total_workday: Optional[float] = None
+    workday_salary: Optional[float] = None
+    compilation_workday_salary: Optional[float] = None
+    quota_mechanical_workday: Optional[float] = None
+    total_mechanical_workday: Optional[float] = None
+    mechanical_workday_salary: Optional[float] = None
+    compilation_mechanical_workday_salary: Optional[float] = None
+    compiler: Optional[str] = None
+    modify_date: Optional[str] = None
+    quota_consumption: Optional[str] = None
+    basic_quota: Optional[str] = None
+    quota_comprehensive_unit_price: Optional[float] = None
+    quota_comprehensive_total_price: Optional[float] = None
+
+    @classmethod
+    def from_model(cls, model: QuotaInputModel) -> 'QuotaInputDto':
+        """从数据库模型创建DTO对象"""
+        return cls(
+            quota_id=model.quota_id,
+            budget_id=model.budget_id,
+            item_id=model.item_id,
+            quota_code=model.quota_code,
+            sequence_number=model.sequence_number,
+            project_name=model.project_name,
+            unit=model.unit,
+            project_quantity=model.project_quantity,
+            project_quantity_input=model.project_quantity_input,
+            quota_adjustment=model.quota_adjustment,
+            unit_price=model.unit_price,
+            compilation_unit_price=model.compilation_unit_price,
+            total_price=model.total_price,
+            compilation_total_price=model.compilation_total_price,
+            unit_weight=model.unit_weight,
+            total_weight=model.total_weight,
+            labor_cost=model.labor_cost,
+            compilation_labor_cost=model.compilation_labor_cost,
+            material_cost=model.material_cost,
+            compilation_material_cost=model.compilation_material_cost,
+            deduct_material_cost=model.deduct_material_cost,
+            compilation_deduct_material_cost=model.compilation_deduct_material_cost,
+            mechanical_cost=model.mechanical_cost,
+            compilation_mechanical_cost=model.compilation_mechanical_cost,
+            equipment_cost=model.equipment_cost,
+            compilation_equipment_cost=model.compilation_equipment_cost,
+            transport_cost=model.transport_cost,
+            compilation_transport_cost=model.compilation_transport_cost,
+            quota_workday=model.quota_workday,
+            total_workday=model.total_workday,
+            workday_salary=model.workday_salary,
+            compilation_workday_salary=model.compilation_workday_salary,
+            quota_mechanical_workday=model.quota_mechanical_workday,
+            total_mechanical_workday=model.total_mechanical_workday,
+            mechanical_workday_salary=model.mechanical_workday_salary,
+            compilation_mechanical_workday_salary=model.compilation_mechanical_workday_salary,
+            compiler=model.compiler,
+            modify_date=model.modify_date,
+            quota_consumption=model.quota_consumption,
+            basic_quota=model.basic_quota,
+            quota_comprehensive_unit_price=model.quota_comprehensive_unit_price,
+            quota_comprehensive_total_price=model.quota_comprehensive_total_price
+        )
+
+    def to_dict(self) -> dict:
+        """转换为字典格式"""
+        return self.model_dump()
+
+    class Config:
+        from_attributes = True

+ 72 - 0
SourceCode/IntelligentRailwayCosting/app/core/dtos/total_budget_info.py

@@ -0,0 +1,72 @@
+from pydantic import BaseModel
+from typing import Optional
+from ..models.total_budget_info import TotalBudgetInfoModel
+
+class TotalBudgetInfoDto(BaseModel):
+    """总概算信息DTO"""
+
+    budget_id: Optional[int] = None
+    budget_code: str
+    # compilation_scope: Optional[str] = None
+    # project_quantity: float
+    # unit: str
+    # budget_value: Optional[float] = None
+    # budget_index: Optional[float] = None
+    # price_diff_coefficient: str
+    # price_diff_area: str
+    # ending_scheme: str
+    # material_cost_scheme: str
+    # mechanical_cost_scheme: str
+    # equipment_cost_scheme: str
+    # labor_cost_scheme: str
+    # compilation_status: int
+    # train_interference_count: Optional[int] = None
+    # train_interference_10_count: Optional[int] = None
+    # deduct_supplied_materials: Optional[int] = None
+    # auto_calculate_quantity: Optional[bool] = None
+    # mechanical_depreciation_adjustment: Optional[float] = None
+    # construction_supervision_group: Optional[int] = None
+    # construction_management_group: Optional[int] = None
+    # survey_group: Optional[int] = None
+    # design_group: Optional[int] = None
+    # compilation_scope_group: Optional[int] = None
+    # enable_total_budget_group: Optional[int] = None
+
+    @classmethod
+    def from_model(cls, model: TotalBudgetInfoModel) -> 'TotalBudgetInfoDto':
+        """从数据库模型创建DTO对象"""
+        return cls(
+            budget_id=model.budget_id,
+            budget_code=model.budget_code,
+            # compilation_scope=model.compilation_scope,
+            # project_quantity=model.project_quantity,
+            # unit=model.unit,
+            # budget_value=model.budget_value,
+            # budget_index=model.budget_index,
+            # price_diff_coefficient=model.price_diff_coefficient,
+            # price_diff_area=model.price_diff_area,
+            # ending_scheme=model.ending_scheme,
+            # material_cost_scheme=model.material_cost_scheme,
+            # mechanical_cost_scheme=model.mechanical_cost_scheme,
+            # equipment_cost_scheme=model.equipment_cost_scheme,
+            # labor_cost_scheme=model.labor_cost_scheme,
+            # compilation_status=model.compilation_status,
+            # train_interference_count=model.train_interference_count,
+            # train_interference_10_count=model.train_interference_10_count,
+            # deduct_supplied_materials=model.deduct_supplied_materials,
+            # auto_calculate_quantity=model.auto_calculate_quantity,
+            # mechanical_depreciation_adjustment=model.mechanical_depreciation_adjustment,
+            # construction_supervision_group=model.construction_supervision_group,
+            # construction_management_group=model.construction_management_group,
+            # survey_group=model.survey_group,
+            # design_group=model.design_group,
+            # compilation_scope_group=model.compilation_scope_group,
+            # enable_total_budget_group=model.enable_total_budget_group
+        )
+
+    def to_dict(self) -> dict:
+        """转换为字典格式"""
+        return self.model_dump()
+
+    class Config:
+        from_attributes = True

+ 127 - 0
SourceCode/IntelligentRailwayCosting/app/core/dtos/total_budget_item.py

@@ -0,0 +1,127 @@
+from pydantic import BaseModel
+from typing import Optional
+from ..models.total_budget_item import TotalBudgetItemModel
+
+class TotalBudgetItemDto(BaseModel):
+    """总概算条目DTO"""
+    budget_id: int
+    item_id: int
+    # project_quantity1: Optional[float] = None
+    # project_quantity2: Optional[float] = None
+    # budget_value: Optional[float] = None
+    # budget_index1: Optional[float] = None
+    # budget_index2: Optional[float] = None
+    # construction_cost: Optional[float] = None
+    # installation_cost: Optional[float] = None
+    # equipment_cost: Optional[float] = None
+    # other_cost: Optional[float] = None
+    # selected_labor_cost: Optional[int] = None
+    # shift_labor_cost: Optional[int] = None
+    # rate_scheme: Optional[str] = None
+    # formula_code: Optional[str] = None
+    # transport_scheme: Optional[str] = None
+    # transport_unit_price: Optional[float] = None
+    # parameter_adjustment: Optional[str] = None
+    # calculation_formula: Optional[str] = None
+    # unit1: Optional[str] = None
+    # unit2: Optional[str] = None
+    # project_quantity1_input: Optional[str] = None
+    # project_quantity2_input: Optional[str] = None
+    # seat_count: Optional[int] = None
+    # installation_sub_item: Optional[str] = None
+    # cooperation_fee_code: Optional[str] = None
+    # tax_category: Optional[str] = None
+    # tax_rate: Optional[float] = None
+    # bridge_type: Optional[str] = None
+    # electricity_price_category: Optional[int] = None
+    # tax: Optional[float] = None
+    # 从ChapterModel添加的字段
+    item_code: Optional[str] = None
+    chapter: Optional[str] = None
+    section: Optional[str] = None
+    project_name: Optional[str] = None
+    unit: Optional[str] = None
+    item_type: Optional[str] = None
+    # project_code: Optional[str] = None
+    # material_price_diff_code: Optional[str] = None
+    # summary_method: Optional[str] = None
+    # cumulative_coefficient: Optional[float] = None
+    # display_range: Optional[str] = None
+    # cost_category: Optional[str] = None
+    # auto_calculate_quantity: Optional[bool] = None
+    # comprehensive_display: Optional[bool] = None
+    # list_code: Optional[str] = None
+    # sub_division_feature: Optional[str] = None
+    # quantity_calculation_rule: Optional[str] = None
+    # work_content: Optional[str] = None
+    # note: Optional[str] = None
+    # quota_book_number: Optional[str] = None
+    # quota_section_number: Optional[str] = None
+    # professional_name: Optional[str] = None
+
+    @classmethod
+    def from_model(cls, model: TotalBudgetItemModel) -> 'TotalBudgetItemDto':
+        """从数据库模型创建DTO对象"""
+        return cls(
+            budget_id=model.budget_id,
+            item_id=model.item_id,
+            # project_quantity1=model.project_quantity1,
+            # project_quantity2=model.project_quantity2,
+            # budget_value=model.budget_value,
+            # budget_index1=model.budget_index1,
+            # budget_index2=model.budget_index2,
+            # construction_cost=model.construction_cost,
+            # installation_cost=model.installation_cost,
+            # equipment_cost=model.equipment_cost,
+            # other_cost=model.other_cost,
+            # selected_labor_cost=model.selected_labor_cost,
+            # shift_labor_cost=model.shift_labor_cost,
+            # rate_scheme=model.rate_scheme,
+            # formula_code=model.formula_code,
+            # transport_scheme=model.transport_scheme,
+            # transport_unit_price=model.transport_unit_price,
+            # parameter_adjustment=model.parameter_adjustment,
+            # calculation_formula=model.calculation_formula,
+            # unit1=model.unit1,
+            # unit2=model.unit2,
+            # project_quantity1_input=model.project_quantity1_input,
+            # project_quantity2_input=model.project_quantity2_input,
+            # seat_count=model.seat_count,
+            # installation_sub_item=model.installation_sub_item,
+            # cooperation_fee_code=model.cooperation_fee_code,
+            # tax_category=model.tax_category,
+            # tax_rate=model.tax_rate,
+            # bridge_type=model.bridge_type,
+            # electricity_price_category=model.electricity_price_category,
+            # tax=model.tax,
+            # 从ChapterModel添加的字段映射
+            item_code=model.item_code,
+            chapter=model.chapter,
+            section=model.section,
+            project_name=model.project_name,
+            unit=model.unit,
+            item_type=model.item_type,
+            # project_code=model.chapter.project_code if model.chapter else None,
+            # material_price_diff_code=model.chapter.material_price_diff_code if model.chapter else None,
+            # summary_method=model.chapter.summary_method if model.chapter else None,
+            # cumulative_coefficient=model.chapter.cumulative_coefficient if model.chapter else None,
+            # display_range=model.chapter.display_range if model.chapter else None,
+            # cost_category=model.chapter.cost_category if model.chapter else None,
+            # auto_calculate_quantity=model.chapter.auto_calculate_quantity if model.chapter else None,
+            # comprehensive_display=model.chapter.comprehensive_display if model.chapter else None,
+            # list_code=model.chapter.list_code if model.chapter else None,
+            # sub_division_feature=model.chapter.sub_division_feature if model.chapter else None,
+            # quantity_calculation_rule=model.chapter.quantity_calculation_rule if model.chapter else None,
+            # work_content=model.chapter.work_content if model.chapter else None,
+            # note=model.chapter.note if model.chapter else None,
+            # quota_book_number=model.chapter.quota_book_number if model.chapter else None,
+            # quota_section_number=model.chapter.quota_section_number if model.chapter else None,
+            # professional_name=model.chapter.professional_name if model.chapter else None
+        )
+
+    def to_dict(self) -> dict:
+        """转换为字典格式"""
+        return self.model_dump()
+
+    class Config:
+        from_attributes = True

+ 24 - 0
SourceCode/IntelligentRailwayCosting/app/core/dtos/tree.py

@@ -0,0 +1,24 @@
+
+
+class TreeDto:
+    id: str = None
+    text: str =None
+    children: bool = False
+
+    def __init__(self, node_id: str, parent: str,text: str, children: bool=False,data: dict=None):
+        self.id = node_id
+        self.parent = parent
+        self.text = text
+        self.children = children
+        self.data = data
+
+
+    def to_dict(self):
+        data = {
+            "id": self.id,
+            "parent": self.parent,
+            "text": self.text,
+            "children": self.children,
+            "data": self.data._asdict() if hasattr(self.data, '_asdict') else self.data
+        }
+        return data

+ 33 - 0
SourceCode/IntelligentRailwayCosting/app/core/dtos/user.py

@@ -0,0 +1,33 @@
+from pydantic import BaseModel
+from typing import Optional
+from ..models import UserModel
+
+class UserDto(BaseModel):
+    """用户信息DTO"""
+    id: Optional[int] = None
+    username: str
+    specialty: Optional[str] = None
+    order_number: Optional[int] = None
+    item_range: Optional[str] = None
+    project_supplement: Optional[int] = None
+    auth_supplement_quota: Optional[int] = None
+
+    @classmethod
+    def from_model(cls, model: UserModel) -> 'UserDto':
+        """从数据库模型创建DTO对象"""
+        return cls(
+            id=model.id,
+            username=model.username,
+            specialty=model.specialty,
+            order_number=model.order_number,
+            item_range=model.item_range,
+            project_supplement=model.project_supplement,
+            auth_supplement_quota=model.auth_supplement_quota
+        )
+
+    def to_dict(self) -> dict:
+        """转换为字典格式"""
+        return self.model_dump()
+
+    class Config:
+        from_attributes = True

+ 2 - 0
SourceCode/IntelligentRailwayCosting/app/core/enum/__init__.py

@@ -0,0 +1,2 @@
+from .log import OperationType,OperationModule,OperationResult
+

+ 36 - 0
SourceCode/IntelligentRailwayCosting/app/core/enum/log.py

@@ -0,0 +1,36 @@
+from enum import Enum
+
+class OperationResult(Enum):
+    SUCCESS = "0"
+    FAILURE = "1"
+
+    @staticmethod
+    def get_name(status: int):
+        return OperationResult.get_dict().get(status, "未知")
+
+    @staticmethod
+    def get_dict() -> dict:
+        """获取状态值和对应的中文名称的字典映射"""
+        return {
+            OperationResult.SUCCESS.value: "成功",
+            OperationResult.FAILURE.value: "失败",
+        }
+
+
+class OperationType(Enum):
+    """操作类型枚举"""
+    LOGIN = "登录"
+    LOGOUT = "注销"
+    CREATE = "新增"
+    UPDATE = "修改"
+    DELETE = "删除"
+    PROCESS_TASK = "开始任务"
+    PROCESS = "处理数据"
+    SEND = "发送数据"
+
+class OperationModule(Enum):
+    """操作模块枚举"""
+    ACCOUNT = "账户"
+    PROJECT = "项目"
+    SUB_PROJECT = "工程"
+    SUB_PROJECT_DETAIL = "工程明细"

+ 2 - 0
SourceCode/IntelligentRailwayCosting/app/core/log/__init__.py

@@ -0,0 +1,2 @@
+from .log_record import LogRecordHelper
+

+ 86 - 0
SourceCode/IntelligentRailwayCosting/app/core/log/log_record.py

@@ -0,0 +1,86 @@
+from flask import request, session
+from typing import Optional
+from services import LogService
+from core.enum import OperationType,OperationModule,OperationResult
+
+class LogRecordHelper:
+    _log_service = LogService()
+
+    @staticmethod
+    def get_client_ip() -> str:
+        """获取客户端IP地址"""
+        if request:
+            if 'X-Forwarded-For' in request.headers:
+                return request.headers['X-Forwarded-For'].split(',')[0]
+            return request.remote_addr
+        return ''
+
+    @staticmethod
+    def log_success(operation_type: OperationType,
+                    operation_module: Optional[OperationModule],
+                    operation_desc: str,
+                    operation_data: Optional[str] = None,
+                    data_changes: Optional[str] = None,
+                    username: Optional[str] = None)-> None:
+        LogRecordHelper.add_log(
+            operation_type=operation_type,
+            operation_desc=operation_desc,
+            operation_module=operation_module,
+            operation_result=OperationResult.SUCCESS,
+            operation_data=operation_data,
+            data_changes=data_changes,
+            username=username
+        )
+
+    @staticmethod
+    def log_fail(operation_type: OperationType,
+                 operation_module: Optional[OperationModule],
+                 operation_desc: str,
+                 operation_data: Optional[str] = None,
+                 data_changes: Optional[str] = None,
+                 username: Optional[str] = None)-> None:
+        LogRecordHelper.add_log(
+            operation_type=operation_type,
+            operation_desc=operation_desc,
+            operation_module=operation_module,
+            operation_result=OperationResult.FAILURE,
+            operation_data=operation_data,
+            data_changes=data_changes,
+            username=username
+        )
+
+    @staticmethod
+    def add_log(operation_type: OperationType,
+                operation_desc: str,
+                operation_module: Optional[OperationModule],
+                operation_result: OperationResult,
+                operation_data: Optional[str] = None,
+                data_changes: Optional[str] = None,
+                username: Optional[str] = None) -> None:
+        """添加操作日志
+
+        Args:
+            operation_type: 操作类型(枚举值)
+            operation_desc: 操作描述
+            operation_module: 操作模块(枚举值,可选)
+            operation_result: 操作结果(0:失败, 1:成功)
+            operation_data: 操作数据(可选)
+            data_changes: 数据变更记录(可选)
+            username: 用户名(可选,默认从session获取)
+        """
+        LogRecordHelper._log_service.add_operation_log(
+            username=username or session.get('username'),
+            operation_type=operation_type.value,
+            operation_desc=operation_desc,
+            operation_module=operation_module.value if operation_module else None,
+            operation_result=operation_result.value,
+            operation_data=operation_data,
+            operation_ip=LogRecordHelper.get_client_ip(),
+            data_changes=data_changes
+        )
+
+
+
+
+
+

+ 9 - 0
SourceCode/IntelligentRailwayCosting/app/core/models/__init__.py

@@ -0,0 +1,9 @@
+from .log import LogModel
+
+from .user import UserModel
+from .team import TeamModel
+from .project import ProjectModel
+from .total_budget_info import TotalBudgetInfoModel
+from .total_budget_item import TotalBudgetItemModel
+from .chapter import ChapterModel
+from .quota_input import QuotaInputModel

+ 46 - 0
SourceCode/IntelligentRailwayCosting/app/core/models/chapter.py

@@ -0,0 +1,46 @@
+from sqlalchemy import Column, String, Integer, Float, Text, Boolean
+from sqlalchemy.ext.declarative import declarative_base
+
+Base = declarative_base()
+
+class ChapterModel(Base):
+    __tablename__ = '章节表'
+
+    item_id = Column('条目序号', Integer, primary_key=True, autoincrement=True)
+    item_code = Column('条目编号', String(255))
+    default_first_page = Column('默认首页', String(1))
+    chapter = Column('章别', String(50))
+    section = Column('节号', String(50))
+    project_name = Column('工程或费用项目名称', String(255))
+    unit = Column('单位', String(20))
+    is_locked = Column('锁定', String(1))
+    unit2 = Column('单位2', String(20))
+    item_type = Column('条目类型', String(50))
+    project_code = Column('工程代码', String(50))
+    cooperation_fee_code = Column('配合费代码', String(50))
+    formula_code = Column('公式代码', String(50))
+    material_price_diff_code = Column('材料价差号', String(50))
+    calculation_formula = Column('计算公式', Text)
+    selected_labor_cost = Column('选用工费', Integer)
+    shift_labor_cost = Column('台班工费', Integer)
+    rate_scheme = Column('费率方案', String(50))
+    transport_scheme = Column('运输方案', String(50))
+    summary_method = Column('汇总方式', String(2))
+    cumulative_coefficient = Column('累计系数', Float)
+    display_range = Column('显示范围', String(50))
+    cost_category = Column('费用类别', String(10))
+    auto_calculate_quantity = Column('自动计算工程量', Boolean)
+    comprehensive_display = Column('综合显示', Boolean)
+    list_code = Column('清单编码', String(255))
+    sub_division_feature = Column('子目划分特征', String(50))
+    quantity_calculation_rule = Column('工程量计算规则', Text)
+    work_content = Column('工作内容', Text)
+    note = Column('附注', Text)
+    seat_count = Column('座数', Integer)
+    quota_book_number = Column('定额书号', String(100))
+    quota_section_number = Column('定额节号', String(100))
+    professional_name = Column('专业名称', String(50))
+    tax_category = Column('税金类别', String(50))
+
+    def __repr__(self):
+        return f"<Chapter(item_id={self.item_id}, item_code='{self.item_code}')>"

+ 27 - 0
SourceCode/IntelligentRailwayCosting/app/core/models/log.py

@@ -0,0 +1,27 @@
+from sqlalchemy import Column, Integer, String, Text, DateTime, Index
+from sqlalchemy.ext.declarative import declarative_base
+from datetime import datetime
+
+Base = declarative_base()
+
+class LogModel(Base):
+    __tablename__ = 'sys_log'
+
+    id = Column(Integer, primary_key=True, autoincrement=True)
+    username = Column(String(255), nullable=False, comment='用户名')
+    operation_type = Column(String(50), nullable=False, comment='操作类型')
+    operation_desc = Column(String(1000), comment='操作描述')
+    operation_result = Column(Integer, comment='操作结果(0:失败, 1:成功)')
+    operation_module = Column(String(100), comment='操作模块')
+    operation_data = Column(Text, comment='操作数据')
+    data_changes = Column(Text, comment='数据变更记录')
+    operation_ip = Column(String(50), comment='操作IP')
+    created_at = Column(DateTime, nullable=False, default=datetime.now, comment='创建时间')
+
+    __table_args__ = (
+        Index('idx_username', 'username'),
+        Index('idx_created_at', 'created_at')
+    )
+
+    def __repr__(self):
+        return f"<Log(id='{self.id}', username='{self.username}', operation_type='{self.operation_type}')"

+ 1 - 1
SourceCode/IntelligentRailwayCosting/app/models/project.py → SourceCode/IntelligentRailwayCosting/app/core/models/project.py

@@ -3,7 +3,7 @@ from sqlalchemy.ext.declarative import declarative_base
 
 Base = declarative_base()
 
-class Project(Base):
+class ProjectModel(Base):
     __tablename__ = '项目信息'
 
     project_id = Column('项目编号', String(30), primary_key=True)

+ 61 - 0
SourceCode/IntelligentRailwayCosting/app/core/models/quota_input.py

@@ -0,0 +1,61 @@
+from sqlalchemy import Column, String, Integer, Float, Text, ForeignKey
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import relationship
+from .total_budget_info import TotalBudgetInfoModel
+from .chapter import ChapterModel
+
+Base = declarative_base()
+
+class QuotaInputModel(Base):
+    __tablename__ = '定额输入'
+
+    quota_id = Column('定额序号', Integer, primary_key=True, autoincrement=True)
+    # budget_id = Column('总概算序号', Integer, ForeignKey('总概算信息.总概算序号'), nullable=False)
+    # item_id = Column('条目序号', Integer, ForeignKey('章节表.条目序号'), nullable=False)
+    budget_id = Column('总概算序号', Integer, nullable=False)
+    item_id = Column('条目序号', Integer, nullable=False)
+    quota_code = Column('定额编号', String(255), nullable=False)
+    sequence_number = Column('顺号', Integer)
+    project_name = Column('工程或费用项目名称', String(255))
+    unit = Column('单位', String(20))
+    project_quantity = Column('工程数量', Float)
+    project_quantity_input = Column('工程数量输入', Text)
+    quota_adjustment = Column('定额调整', Text)
+    unit_price = Column('单价', Float)
+    compilation_unit_price = Column('编制期单价', Float)
+    total_price = Column('合价', Float)
+    compilation_total_price = Column('编制期合价', Float)
+    unit_weight = Column('单重', Float)
+    total_weight = Column('合重', Float)
+    labor_cost = Column('人工费', Float)
+    compilation_labor_cost = Column('编制期人工费', Float)
+    material_cost = Column('材料费', Float)
+    compilation_material_cost = Column('编制期材料费', Float)
+    deduct_material_cost = Column('扣料费', Float)
+    compilation_deduct_material_cost = Column('编制期扣料费', Float)
+    mechanical_cost = Column('机械使用费', Float)
+    compilation_mechanical_cost = Column('编制期机械使用费', Float)
+    equipment_cost = Column('设备费', Float)
+    compilation_equipment_cost = Column('编制期设备费', Float)
+    transport_cost = Column('运杂费', Float)
+    compilation_transport_cost = Column('编制期运杂费', Float)
+    quota_workday = Column('定额工日', Float)
+    total_workday = Column('工日合计', Float)
+    workday_salary = Column('工日工资', Float)
+    compilation_workday_salary = Column('编制期工日工资', Float)
+    quota_mechanical_workday = Column('定额机械工日', Float)
+    total_mechanical_workday = Column('机械工合计', Float)
+    mechanical_workday_salary = Column('机械工日工资', Float)
+    compilation_mechanical_workday_salary = Column('编制期机械工日工资', Float)
+    compiler = Column('编制人', String(50))
+    modify_date = Column('修改日期', String(50))
+    quota_consumption = Column('定额消耗', Text)
+    basic_quota = Column('基本定额', String(255))
+    quota_comprehensive_unit_price = Column('定额综合单价', Float)
+    quota_comprehensive_total_price = Column('定额综合合价', Float)
+
+    # budget_info = relationship('TotalBudgetInfoModel')
+    # chapter = relationship('ChapterModel')
+
+    def __repr__(self):
+        return f"<QuotaInput(quota_id={self.quota_id}, quota_code='{self.quota_code}')>"

+ 1 - 1
SourceCode/IntelligentRailwayCosting/app/models/team.py → SourceCode/IntelligentRailwayCosting/app/core/models/team.py

@@ -3,7 +3,7 @@ from sqlalchemy.ext.declarative import declarative_base
 
 Base = declarative_base()
 
-class Team(Base):
+class TeamModel(Base):
     __tablename__ = '团队人员'
 
     project_id = Column('项目编号', String(30), primary_key=True)

+ 41 - 0
SourceCode/IntelligentRailwayCosting/app/core/models/total_budget_info.py

@@ -0,0 +1,41 @@
+from sqlalchemy import Column, String, Integer, Float, Boolean
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import relationship
+
+Base = declarative_base()
+
+class TotalBudgetInfoModel(Base):
+    __tablename__ = '总概算信息'
+
+
+    budget_id = Column('总概算序号', Integer, primary_key=True, autoincrement=True)
+    budget_code = Column('总概算编号', String(50), nullable=False)
+    compilation_scope = Column('编制范围', String(255))
+    project_quantity = Column('工程数量', Float, nullable=False)
+    unit = Column('单位', String(20), nullable=False)
+    budget_value = Column('概算价值', Float)
+    budget_index = Column('概算指标', Float)
+    price_diff_coefficient = Column('价差系数', String(50), nullable=False)
+    price_diff_area = Column('价差区号', String(20), nullable=False)
+    ending_scheme = Column('结尾方案', String(50), nullable=False)
+    material_cost_scheme = Column('材料费方案', String(50), nullable=False)
+    mechanical_cost_scheme = Column('机械费方案', String(50), nullable=False)
+    equipment_cost_scheme = Column('设备费方案', String(50), nullable=False)
+    labor_cost_scheme = Column('工费方案', String(50), nullable=False)
+    compilation_status = Column('编制状态', Integer, nullable=False)
+    train_interference_count = Column('行车干扰次数', Integer)
+    train_interference_10_count = Column('行干10号工次数', Integer)
+    deduct_supplied_materials = Column('扣甲供料', Integer)
+    auto_calculate_quantity = Column('是否自动计算工程量', Boolean)
+    mechanical_depreciation_adjustment = Column('机械折旧费调差系数', Float)
+    construction_supervision_group = Column('施工监理分组', Integer)
+    construction_management_group = Column('建设管理分组', Integer)
+    survey_group = Column('勘察分组', Integer)
+    design_group = Column('设计分组', Integer)
+    compilation_scope_group = Column('编制范围分组', Integer)
+    enable_total_budget_group = Column('启用总概算分组', Integer)
+    
+    # items = relationship('TotalBudgetItemModel', back_populates='budget_info', lazy='dynamic')
+
+    def __repr__(self):
+        return f"<TotalBudgetInfo(budget_id={self.budget_id}, budget_code='{self.budget_code}')>"

+ 48 - 0
SourceCode/IntelligentRailwayCosting/app/core/models/total_budget_item.py

@@ -0,0 +1,48 @@
+from sqlalchemy import Column, String, Integer, Float, Text, ForeignKey
+from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.orm import relationship
+
+Base = declarative_base()
+
+class TotalBudgetItemModel(Base):
+    __tablename__ = '总概算条目'
+    
+    # budget_id = Column('总概算序号', Integer, ForeignKey('总概算信息.总概算序号'), primary_key=True)
+    # item_id = Column('条目序号', Integer, ForeignKey('章节表.条目序号'), primary_key=True)
+    budget_id = Column('总概算序号', Integer, primary_key=True)
+    item_id = Column('条目序号', Integer, primary_key=True)
+    project_quantity1 = Column('工程数量1', Float)
+    project_quantity2 = Column('工程数量2', Float)
+    budget_value = Column('概算价值', Float)
+    budget_index1 = Column('概算指标1', Float)
+    budget_index2 = Column('概算指标2', Float)
+    construction_cost = Column('建筑工程费', Float)
+    installation_cost = Column('安装工程费', Float)
+    equipment_cost = Column('设备工器具', Float)
+    other_cost = Column('其他费', Float)
+    selected_labor_cost = Column('选用工费', Integer)
+    shift_labor_cost = Column('台班工费', Integer)
+    rate_scheme = Column('费率方案', String(50))
+    formula_code = Column('公式代码', String(50))
+    transport_scheme = Column('运输方案', String(50))
+    transport_unit_price = Column('运输单价', Float)
+    parameter_adjustment = Column('参数调整', Text)
+    calculation_formula = Column('计算公式', Text)
+    unit1 = Column('单位1', String(20))
+    unit2 = Column('单位2', String(20))
+    project_quantity1_input = Column('工程数量1输入', String(100))
+    project_quantity2_input = Column('工程数量2输入', String(100))
+    seat_count = Column('座数', Integer)
+    installation_sub_item = Column('安装子目', String(500))
+    cooperation_fee_code = Column('配合费代码', String(50))
+    tax_category = Column('税金类别', String(50))
+    tax_rate = Column('税率', Float)
+    bridge_type = Column('桥梁类型', String(50))
+    electricity_price_category = Column('电价分类', Integer)
+    tax = Column('税金', Float)
+
+    # budget_info = relationship('TotalBudgetInfoModel', back_populates='items')
+    # chapter = relationship('ChapterModel')
+
+    def __repr__(self):
+        return f"<TotalBudgetItem(budget_id={self.budget_id}, item_id={self.item_id})>"

+ 2 - 2
SourceCode/IntelligentRailwayCosting/app/models/user.py → SourceCode/IntelligentRailwayCosting/app/core/models/user.py

@@ -3,7 +3,7 @@ from sqlalchemy.ext.declarative import declarative_base
 
 Base = declarative_base()
 
-class User(Base):
+class UserModel(Base):
     __tablename__ = '系统用户'
 
     id = Column('序号', Integer, primary_key=True)
@@ -12,7 +12,7 @@ class User(Base):
     password = Column('用户密码', String(20))
     specialty = Column('专业名称', String(50))
     auth_supplement_quota = Column('授权补充定额', Integer)
-    item_range_30 = Column('条目范围30', Text)
+    item_range = Column('条目范围30', Text)
     project_supplement = Column('项目补充', Integer)
 
     def __repr__(self):

+ 4 - 0
SourceCode/IntelligentRailwayCosting/app/core/user_session/__init__.py

@@ -0,0 +1,4 @@
+from .user_session import UserSession
+from .current_user import CurrentUser
+from .permission import Permission
+

+ 27 - 68
SourceCode/IntelligentRailwayCosting/app/core/user_session/current_user.py

@@ -1,23 +1,40 @@
 from dataclasses import dataclass
 from typing import Optional, List
 from flask_login import UserMixin
+from core.dtos import UserDto
 
 @dataclass
 class CurrentUser(UserMixin):
     """当前用户信息结构体"""
-    user_id: Optional[int] = None
-    username: Optional[str] = None
-    name: Optional[str] = None
-    roles: List[str] = None
+    _user_id: Optional[int] = None
+    _username: Optional[str] = None
+    _item_range: Optional[str] = None
+    _specialty: Optional[str] = None
+    # _auth_supplement_quota: Optional[str] = None
+    # _project_supplement: Optional[str] = None
 
-    def __post_init__(self):
-        """初始化角色列表"""
-        if self.roles is None:
-            self.roles = []
+
+    def __init__(self, user_id: Optional[int] = None, username: Optional[str] = None, item_range: Optional[str] = None, specialty: Optional[str] = None):
+        self._user_id = user_id
+        self._username = username
+        self._item_range = item_range
+        self._specialty = specialty
 
     def get_id(self):
+        return self.user_id
+    @property
+    def user_id(self):
         """实现Flask-Login要求的get_id方法"""
-        return str(self.user_id) if self.user_id else None
+        return str(self._user_id) if self._user_id else None
+    @property
+    def username(self):
+        return self._username
+    @property
+    def item_range(self):
+        return self._item_range
+    @property
+    def specialty(self):
+        return self._specialty
 
     @property
     def is_authenticated(self) -> bool:
@@ -28,14 +45,6 @@ class CurrentUser(UserMixin):
         """
         return self.user_id is not None and self.username is not None
 
-    @property
-    def is_super_admin(self) -> bool:
-        """检查用户是否为超级管理员
-
-        Returns:
-            bool: 如果用户是超级管理员返回True,否则返回False
-        """
-        return 'super_admin' in self.roles
 
     @property
     def is_admin(self) -> bool:
@@ -44,56 +53,6 @@ class CurrentUser(UserMixin):
         Returns:
             bool: 如果用户是超级管理员返回True,否则返回False
         """
-        return self.username == 'admin' or 'admin' in self.roles
-
-    @property
-    def is_sys(self) -> bool:
-        """检查用户是否为系统管理员
-
-        Returns:
-            bool: 如果用户是系统管理员返回True,否则返回False
-        """
-        return self.is_admin or 'sys' in self.roles
+        return self.username == 'admin'
 
-    @property
-    def is_edit(self) -> bool:
-        """检查用户是否有编辑权限
 
-        Returns:
-            bool: 如果用户有编辑权限返回True,否则返回False
-        """
-        return self.is_admin or 'edit' in self.roles
-
-
-    def has_role(self, role: str) -> bool:
-        """检查用户是否拥有指定角色
-
-        Args:
-            role (str): 角色名称
-
-        Returns:
-            bool: 如果用户拥有指定角色返回True,否则返回False
-        """
-        return role in self.roles
-    
-    def has_any_role(self, roles: List[str]) -> bool:
-        """检查用户是否拥有指定角色列表中的任意一个角色
-
-        Args:
-            roles (List[str]): 角色名称列表
-
-        Returns:
-            bool: 如果用户拥有指定角色列表中的任意一个角色返回True,否则返回False
-        """
-        return any(role in self.roles for role in roles)
-    
-    def has_all_roles(self, roles: List[str]) -> bool:
-        """检查用户是否拥有指定角色列表中的所有角色
-
-        Args:
-            roles (List[str]): 角色名称列表
-
-        Returns:
-            bool: 如果用户拥有指定角色列表中的所有角色返回True,否则返回False
-        """
-        return all(role in self.roles for role in roles)

+ 26 - 0
SourceCode/IntelligentRailwayCosting/app/core/user_session/permission.py

@@ -0,0 +1,26 @@
+from flask import redirect, url_for, request
+from functools import wraps
+from .user_session import UserSession
+
+class Permission:
+    @staticmethod
+    def authorize(f):
+        @wraps(f)
+        def decorated_function(*args, **kwargs):
+            current_user = UserSession.get_current_user()
+            if not current_user.is_authenticated:
+                return redirect(url_for('auth.login', next=request.url))
+            return f(*args, **kwargs)
+
+        return decorated_function
+
+    # @staticmethod
+    # def edit(f):
+    #     @wraps(f)
+    #     def decorated_function(*args, **kwargs):
+    #         current_user = UserSession.get_current_user()
+    #         if not current_user.is_authenticated:
+    #             return redirect(url_for('auth.login', next=request.url))
+    #         if not current_user.is_admin:
+    #             return redirect(url_for('auth.login', next=request.url))
+    #         return f(*args, **kwargs)

+ 12 - 53
SourceCode/IntelligentRailwayCosting/app/core/user_session/user_session.py

@@ -1,25 +1,18 @@
 from flask import session
 from typing import Optional
 from .current_user import CurrentUser
+from core.dtos import UserDto
 
 
 class UserSession:
     """用户会话管理类"""
 
     @staticmethod
-    def set_user(user_id: int, username: str, name: str, roles: Optional[list] = None) -> None:
-        """设置用户登录状态
-
-        Args:
-            user_id (int): 用户ID
-            username (str): 用户名
-            name (str): 用户姓名
-            roles (Optional[list], optional): 用户角色列表. Defaults to None.
-        """
-        session['user_id'] = user_id
-        session['username'] = username
-        session['name'] = name
-        session['roles'] = roles or []
+    def set_user(user: UserDto) -> None:
+        session['user_id'] = user.id
+        session['username'] = user.username
+        session['item_range'] = user.item_range
+        session['specialty'] = user.specialty
 
     @staticmethod
     def get_current_user() -> CurrentUser:
@@ -31,10 +24,10 @@ class UserSession:
         return CurrentUser(
             user_id=session.get('user_id'),
             username=session.get('username'),
-            name=session.get('name'),
-            roles=session.get('roles', [])
-        )
-    
+            item_range=session.get('item_range'),
+            specialty=session.get('specialty'),
+            )
+
     @staticmethod
     def get_current_username() -> Optional[str]:
         """获取当前登录用户名
@@ -68,9 +61,8 @@ class UserSession:
         """清除用户登录状态"""
         session.pop('user_id', None)
         session.pop('username', None)
-        session.pop('name', None)
-        session.pop('roles', None)
-    
+        session.pop('user', None)
+
     @staticmethod
     def is_logged_in() -> bool:
         """检查用户是否已登录
@@ -89,36 +81,3 @@ class UserSession:
         """
         return session.get('username') == 'admin'
     
-    @staticmethod
-    def get_current_roles() -> list:
-        """获取当前用户角色列表
-
-        Returns:
-            list: 返回用户角色列表,未登录则返回空列表
-        """
-        return session.get('roles', [])
-
-    @staticmethod
-    def has_role(role: str) -> bool:
-        """检查当前用户是否拥有指定角色
-
-        Args:
-            role (str): 角色名称
-
-        Returns:
-            bool: 如果用户拥有指定角色返回True,否则返回False
-        """
-        return role in session.get('roles', [])
-
-    @staticmethod
-    def has_all_roles(roles: list) -> bool:
-        """检查当前用户是否拥有指定角色列表中的所有角色
-
-        Args:
-            roles (list): 角色名称列表
-
-        Returns:
-            bool: 如果用户拥有指定角色列表中的所有角色返回True,否则返回False
-        """
-        user_roles = session.get('roles', [])
-        return all(role in user_roles for role in roles)

+ 7 - 9
SourceCode/IntelligentRailwayCosting/app/main.py

@@ -1,13 +1,11 @@
-from services.user import UserService
-def main():
-    user_service = UserService()
-    user = user_service.get_user_by_id(1)
-    if user:
-        print(user.username)
-        print(user.password)
+from  app import create_app
+import tools.utils as utils
+logger = utils.get_logger()
 
-    print(user)
-    pass
+def main():
+    logger.info("程序启动")
+    app = create_app()
+    app.run(host='0.0.0.0', port=5124)  # 指定HTTP端口为5124
 
 if __name__ == '__main__':
     main()

+ 0 - 0
SourceCode/IntelligentRailwayCosting/app/models/__init__.py


+ 10 - 0
SourceCode/IntelligentRailwayCosting/app/routes/__init__.py

@@ -0,0 +1,10 @@
+from routes.auth import auth_api
+from routes.log import log_api
+from routes.project import project_api
+
+def register_api(app):
+    url_prefix = '/api'
+    # API蓝图注册
+    app.register_blueprint(auth_api, url_prefix=f"{url_prefix}/auth")
+    app.register_blueprint(log_api, url_prefix=f"{url_prefix}/log")
+    app.register_blueprint(project_api, url_prefix=f"{url_prefix}/project")

+ 58 - 0
SourceCode/IntelligentRailwayCosting/app/routes/auth.py

@@ -0,0 +1,58 @@
+from flask import Blueprint, request, jsonify, session
+from flask_login import LoginManager, login_user, logout_user
+
+from core.log import  LogRecordHelper
+from core.enum import OperationType,OperationModule
+from core.api import  ResponseBase
+from core.user_session import  UserSession,CurrentUser
+from services import  UserService
+
+auth_api = Blueprint('auth_api', __name__)
+user_service = UserService()
+
+login_manager = LoginManager()
+login_manager.login_view = 'auth_api.login'
+login_manager.login_message = '请先登录'
+
+@login_manager.user_loader
+def load_user(user_id):
+    user = user_service.get_user_by_id(user_id)
+    if user:
+        current_user = UserSession.get_current_user()
+        return current_user
+    return None
+
+
+@auth_api.route('/login', methods=['POST'])
+def login():
+    username = request.json.get('username')
+    password = request.json.get('password')
+
+    # 调用UserService进行用户验证
+    user, msg = user_service.authenticate_user(username, password)
+    if user:
+        # 创建CurrentUser对象并登录
+        current_user = CurrentUser(user_id=user.id)
+        login_user(current_user)
+        UserSession.set_user(user)
+        # 记录日志
+        LogRecordHelper.log_success(OperationType.LOGIN, OperationModule.ACCOUNT, '用户登录成功', username=user.username)
+
+        return ResponseBase.success({
+            'user_id': user.id,
+            'username': user.username
+        })
+    else:
+        # 登录失败,返回错误信息
+        return ResponseBase.error(msg)
+
+
+@auth_api.route('/logout', methods=['POST'])
+def logout():
+    username = session.get('username')
+    if username:
+        logout_user()
+        UserSession.clear_user()
+        LogRecordHelper.log_success(OperationType.LOGIN, OperationModule.ACCOUNT, '用户退出登录', username=username)
+        return jsonify(ResponseBase.success())
+    return jsonify(ResponseBase.error('用户未登录'))

+ 33 - 0
SourceCode/IntelligentRailwayCosting/app/routes/log.py

@@ -0,0 +1,33 @@
+from flask import Blueprint, request
+from core.user_session import Permission
+from core.api import  ResponseBase,TableResponse
+from services import  LogService
+
+log_api = Blueprint('log_api', __name__)
+log_service = LogService()
+
+@log_api.route('/list', methods=['POST'])
+@Permission.authorize
+def get_page_list():
+    try:
+        data = request.get_json()
+        page = int(data.get('pageNum', 1))
+        per_page = int(data.get('pageSize', 10))
+        username = data.get('username')
+        operation_type = data.get('operationType')
+        operation_module = data.get('operationModule')
+        operation_result = data.get('operationResult')
+        operation_result = int(operation_result) if operation_result else None
+        start_date = None
+        end_date = None
+        date = data.get('date')
+        if date:
+            date = date.split(' - ')
+            start_date = date[0]+' 00:00:00'
+            end_date = date[1]+" 23:59:59"
+
+        logs, total_count = log_service.get_logs_paginated(page, per_page, username, operation_type, operation_module, operation_result,
+                                                           start_date, end_date)
+        return TableResponse.success(logs, total_count)
+    except Exception as e:
+        return ResponseBase.error(f'获取日志失败:{str(e)}')

+ 63 - 0
SourceCode/IntelligentRailwayCosting/app/routes/project.py

@@ -0,0 +1,63 @@
+from flask import Blueprint, request
+from core.user_session import Permission
+from core.api import  ResponseBase,TableResponse
+from services import  ProjectService
+
+project_api = Blueprint('project_api', __name__)
+project_srvice = ProjectService()
+
+@project_api.route('/list', methods=['POST'])
+@Permission.authorize
+def get_page_list():
+    try:
+        data = request.get_json()
+        page = int(data.get('pageNum', 1))
+        per_page = int(data.get('pageSize', 10))
+        keyword = data.get('keyword')
+        start_date = None
+        end_date = None
+        date = data.get('date')
+        if date:
+            date = date.split(' - ')
+            start_date = date[0]+' 00:00:00'
+            end_date = date[1]+" 23:59:59"
+
+        projects, total_count = project_srvice.get_projects_paginated(page, per_page, keyword, start_date, end_date)
+        return TableResponse.success(projects, total_count)
+    except Exception as e:
+        return ResponseBase.error(f'获取项目失败:{str(e)}')
+
+
+@project_api.route('/budget/<project_id>', methods=['POST'])
+@Permission.authorize
+def get_budget_info(project_id:str):
+    try:
+        data,msg = project_srvice.get_budget_info(project_id)
+        if not data:
+            return ResponseBase.error(msg)
+        return ResponseBase.success(data)
+    except Exception as e:
+        return ResponseBase.error(f'获取项目概算信息失败:{str(e)}')
+
+@project_api.route('/budget-item/top/<budget_id>/<project_id>', methods=['POST'])
+@Permission.authorize
+def get_budget_top_items(budget_id:str,project_id:str):
+    try:
+        data,msg = project_srvice.get_top_budget_items(budget_id, project_id)
+        if not data:
+            return ResponseBase.error(msg)
+        return ResponseBase.success(data)
+    except Exception as e:
+        return ResponseBase.error(f'获取项目概算条目失败:{str(e)}')
+
+@project_api.route('/budget-item/<budget_id>/<project_id>', methods=['POST'])
+@Permission.authorize
+def get_budget_items(budget_id:str,project_id:str):
+    item_code = request.args.get('c', None)
+    try:
+        data,msg = project_srvice.get_budget_items(budget_id, project_id, item_code)
+        if not data:
+            return ResponseBase.error(msg)
+        return ResponseBase.success(data)
+    except Exception as e:
+        return ResponseBase.error(f'获取项目概算子条目失败:{str(e)}')

+ 4 - 0
SourceCode/IntelligentRailwayCosting/app/services/__init__.py

@@ -0,0 +1,4 @@
+from .user import UserService
+from .log import LogService
+from .project import ProjectService
+

+ 37 - 0
SourceCode/IntelligentRailwayCosting/app/services/log.py

@@ -0,0 +1,37 @@
+from datetime import datetime,timedelta
+from core.dtos import LogDto
+from stores import LogStore
+
+class LogService:
+    def __init__(self):
+        self.log_store = LogStore()
+        pass
+
+    def get_logs_paginated(self, page:int, page_size:int, username:str, operation_type:str, operation_module:str, operation_result:int, start_time:str, end_time:str):
+        # 处理开始时间和结束时间
+        start_datetime = None
+        end_datetime = None
+        try:
+            if start_time:
+                start_datetime = datetime.strptime(start_time, '%Y-%m-%d').replace(hour=0, minute=0, second=0)
+            if end_time:
+                end_datetime = datetime.strptime(end_time, '%Y-%m-%d').replace(hour=0, minute=0, second=0)
+                # 使用timedelta来处理结束时间加一天
+                end_datetime = end_datetime + timedelta(days=1)
+        except ValueError as e:
+            raise ValueError(f"日期格式错误,请使用YYYY-MM-DD格式: {str(e)}")
+        data =  self.log_store.query_logs(page,page_size,username,operation_type,operation_module,operation_result,start_datetime,end_datetime)
+        return [LogDto.from_model(item).to_dict() for  item in data.get('data',[])], data.get('total',0)
+
+
+    def add_operation_log(self,
+                          username:str,
+                          operation_type:str,
+                          operation_module:str,
+                          operation_desc:str,
+                          operation_result:int,
+                          operation_data:str = None,
+                          data_changes:str = None,
+                          operation_ip:str = None,
+                          ):
+        self.log_store.insert_log(username,operation_type,operation_desc,operation_result,operation_module,operation_data,data_changes,operation_ip)

+ 86 - 0
SourceCode/IntelligentRailwayCosting/app/services/project.py

@@ -0,0 +1,86 @@
+from typing import Optional
+from datetime import datetime, timedelta
+
+from core.dtos import TotalBudgetInfoDto, TotalBudgetItemDto
+from core.dtos.project import ProjectDto
+from core.dtos.tree import TreeDto
+from stores import ProjectStore,BudgetStore
+from tools import db_helper
+
+
+class ProjectService:
+
+    def __init__(self):
+        self._project_store = ProjectStore()
+        self._budget_store =None
+
+    def get_projects_paginated(self, page: int, page_size: int, keyword: Optional[str] = None,
+        start_time: Optional[str] = None,
+        end_time: Optional[str] = None, can_edit:Optional[int]=0,):
+        
+        # 处理开始时间和结束时间
+        start_datetime = None
+        end_datetime = None
+        try:
+            if start_time:
+                start_datetime = datetime.strptime(start_time, '%Y-%m-%d').replace(hour=0, minute=0, second=0)
+            if end_time:
+                end_datetime = datetime.strptime(end_time, '%Y-%m-%d').replace(hour=0, minute=0, second=0)
+                # 使用timedelta来处理结束时间加一天
+                end_datetime = end_datetime + timedelta(days=1)
+        except ValueError as e:
+            raise ValueError(f"日期格式错误,请使用YYYY-MM-DD格式: {str(e)}")
+
+        data =  self._project_store.get_user_projects(page, page_size, keyword, start_datetime, end_datetime, can_edit)
+        return [ProjectDto.from_model(item).to_dict() for item in data.get('data',[])],data.get('total',0)
+
+    def get_budget_info(self, project_id: str):
+        db_session, msg = self._create_project_db_session(project_id)
+        if not db_session:
+            return None, msg
+        budget_store = BudgetStore(db_session)
+        data = budget_store.get_budget_info()
+        return [TotalBudgetInfoDto.from_model(item).to_dict() for item in data],""
+
+    def get_top_budget_items(self, budget_id: str, project_id: str):
+        if not budget_id:
+            return None,' budget_id不能为空'
+        db_session,msg = self._create_project_db_session(project_id)
+        if not db_session:
+            return None, msg
+        budget_store = BudgetStore(db_session)
+        items = budget_store.get_top_budget_items(budget_id)
+        return [TotalBudgetItemDto.from_model(item).to_dict() for item in items],""
+
+    def get_budget_items(self, budget_id: str, project_id: str, item_code:str):
+        if not budget_id:
+            return None,'budget_id不能为空'
+        db_session,msg = self._create_project_db_session(project_id)
+        if not db_session:
+            return None, msg
+        budget_store = BudgetStore(db_session)
+        data_list = []
+        if not item_code:
+            items =  budget_store.get_top_budget_items(budget_id)
+        else:
+            items = budget_store.get_child_budget_items(budget_id,item_code)
+        parent = "#"
+        if item_code:
+            item = budget_store.get_budget_item_by_item_code(budget_id,item_code)
+            parent = item.item_id
+        for item in items:
+            text = f"{item.chapter}  {item.project_name}" if item.chapter else ( f"{item.section}  {item.project_name}" if item.section else item.project_name)
+            data_list.append(TreeDto(item.item_id,parent,text,item.children_count>0,item).to_dict())
+        return data_list,""
+
+    def _create_project_db_session(self,project_id:str):
+        if not project_id:
+            return None,'project_id不能为空'
+        if not self._project_store.get(project_id):
+            return None,'项目不存在'
+        if not project_id.startswith('Reco'):
+            return None,'项目id格式错误'
+        db_session = db_helper.create_sqlServer_session(project_id)
+        if not db_session:
+            return None,'数据库连接失败'
+        return db_session,""

+ 19 - 10
SourceCode/IntelligentRailwayCosting/app/services/user.py

@@ -1,23 +1,32 @@
 from stores.user import UserStore
-from models.user import User
-
+from core.dtos import UserDto
 class UserService:
     def __init__(self):
         self.user_store = UserStore()
 
-    def get_user_by_id(self, user_id: int) -> User:
+    def get_user_by_id(self, user_id: int) -> UserDto | None:
         user = self.user_store.get_user_by_id(user_id)
-        return user
+        if user:
+            return UserDto.from_model(user)
+        return None
 
-    def get_user_by_username(self, username: str) -> User:
+    def get_user_by_username(self, username: str) -> UserDto | None:
         user = self.user_store.get_user_by_username(username)
-        return user
+        if user:
+            return UserDto.from_model(user)
+        return None
 
-    def authenticate_user(self, username: str, password: str) -> User:
+    def authenticate_user(self, username: str, password: str):
+        user = self.user_store.get_user_by_username(username)
+        if not user:
+            return None,"用户不存在"
         user = self.user_store.authenticate_user(username, password)
-        return user
+        if user:
+            return UserDto.from_model(user),""
+        return None,"密码错误"
 
-    def get_all_users(self) -> list[User]:
+    def get_all_users(self) -> list[UserDto]:
         users = self.user_store.get_all_users()
-        return users
+        user_list = [UserDto.from_model(user) for  user in users]
+        return user_list
 

+ 4 - 0
SourceCode/IntelligentRailwayCosting/app/stores/__init__.py

@@ -0,0 +1,4 @@
+from .user import UserStore
+from .log import LogStore
+from .project import ProjectStore
+from .budget import BudgetStore

+ 124 - 0
SourceCode/IntelligentRailwayCosting/app/stores/budget.py

@@ -0,0 +1,124 @@
+from typing import Optional
+from sqlalchemy.orm import Session, aliased
+from sqlalchemy import and_, or_, asc, case, func
+
+from core.models import TotalBudgetInfoModel,TotalBudgetItemModel,ChapterModel
+from core.dtos import TotalBudgetInfoDto, TotalBudgetItemDto
+
+
+class BudgetStore:
+    def __init__(self, db_session: Session ):
+        if db_session is None:
+            raise Exception("db_session is None")
+        self.db_session = db_session
+
+    def get_budget_info(self):
+        budgets = self.db_session.query(TotalBudgetInfoModel).all()
+        if budgets is None:
+            return None
+        return budgets
+
+    def get_budget_item_by_item_code(self, budget_id: str,item_code: str):
+        budget = self.db_session.query( 
+            TotalBudgetItemModel.budget_id,
+            TotalBudgetItemModel.item_id,
+            ChapterModel.item_code,
+            ChapterModel.chapter,
+            ChapterModel.section,
+            ChapterModel.project_name,
+            ChapterModel.item_type,
+            ChapterModel.unit,)\
+            .join(ChapterModel,ChapterModel.item_id == TotalBudgetItemModel.item_id)\
+            .filter(and_(TotalBudgetItemModel.budget_id == budget_id,ChapterModel.item_code == item_code))\
+            .first()
+        if budget is None:
+            return None
+        return budget
+
+    def _build_budget_items_query(self, budget_id: str):
+        # 创建父节点和子节点的别名
+        parent = aliased(ChapterModel, name='parent')
+        child = aliased(ChapterModel, name='child')
+
+        # 子查询:计算每个节点的直接子节点数量
+        children_count = self.db_session.query(
+            parent.item_code.label('parent_code'),
+            func.count(child.item_code).label('child_count')
+        ).outerjoin(
+            child,
+            or_(
+                # 匹配形如01-01的格式
+                child.item_code.like(parent.item_code + '-__'),
+                # 匹配形如0101的格式
+                child.item_code.like(parent.item_code + '__')
+            )
+        ).group_by(parent.item_code).subquery()
+
+        return (self.db_session.query(
+            TotalBudgetItemModel.budget_id,
+            TotalBudgetItemModel.item_id,
+            ChapterModel.item_code,
+            ChapterModel.chapter,
+            ChapterModel.section,
+            ChapterModel.project_name,
+            ChapterModel.item_type,
+            ChapterModel.unit,
+            func.coalesce(children_count.c.child_count, 0).label('children_count')
+        ).distinct()
+        .join(ChapterModel, ChapterModel.item_id == TotalBudgetItemModel.item_id)
+        .outerjoin(children_count, children_count.c.parent_code == ChapterModel.item_code)
+        ).filter(TotalBudgetItemModel.budget_id == budget_id)
+
+    def get_top_budget_items(self, budget_id: str):
+        query = self._build_budget_items_query(budget_id)
+        query = query.filter(ChapterModel.item_code.like('__'))\
+            .order_by(ChapterModel.item_code)
+        items = query.all()
+        return items
+
+    def get_child_budget_items(self, budget_id: str, parent_item_code: str):
+        # 构建子节点的模式:支持两种格式
+        # 1. 父级编号后跟-和两位数字(如:01-01)
+        # 2. 父级编号直接跟两位数字(如:0101)
+        pattern_with_dash = f'{parent_item_code}-__'
+        pattern_without_dash = f'{parent_item_code}__'
+        
+        query = self._build_budget_items_query(budget_id)\
+            .filter(or_(ChapterModel.item_code.like(pattern_with_dash),
+                       ChapterModel.item_code.like(pattern_without_dash)))\
+            .order_by(ChapterModel.item_code)
+        items = query.all()
+        return items
+
+
+    def get_top_budget_items_by_budget_id(self, budget_id: str):
+        query = ((self.db_session.query(
+            TotalBudgetItemModel.budget_id,
+            TotalBudgetItemModel.item_id,
+            ChapterModel.item_code,
+            ChapterModel.chapter,
+            ChapterModel.section,
+            ChapterModel.project_name,
+            ChapterModel.item_type,
+            ChapterModel.unit,
+        ).distinct().join(ChapterModel, ChapterModel.item_id == TotalBudgetItemModel.item_id))
+             .filter(TotalBudgetItemModel.budget_id == budget_id)
+             .filter(or_(ChapterModel.item_code==0,ChapterModel.chapter is not None,ChapterModel.section is not None))
+        )
+        items = query.order_by(TotalBudgetItemModel.item_id.asc()).all()
+        return items
+
+    def get_budget_items_children(self, budget_id: str, item_code: str):
+        query = ((self.db_session.query(
+            TotalBudgetItemModel.budget_id,
+            TotalBudgetItemModel.item_id,
+            ChapterModel.item_code,
+            ChapterModel.chapter,
+            ChapterModel.section,
+            ChapterModel.project_name,
+            ChapterModel.item_type,
+            ChapterModel.unit,
+        ).distinct().join(ChapterModel, ChapterModel.item_id == TotalBudgetItemModel.item_id))
+             .filter(TotalBudgetItemModel.budget_id == budget_id)
+             .filter(or_(ChapterModel.item_code.startswith(item_code)))
+        )

+ 117 - 0
SourceCode/IntelligentRailwayCosting/app/stores/log.py

@@ -0,0 +1,117 @@
+from typing import List, Optional, Dict, Any
+from datetime import datetime
+from sqlalchemy import and_,  desc
+from sqlalchemy.orm import Session
+import tools.db_helper as db_helper
+
+from core.models import LogModel
+
+class LogStore:
+    def __init__(self, db_session: Session=None):
+        self.db_session = db_session or db_helper.create_mysql_session()
+
+    def query_logs(
+        self,
+        page: int = 1,
+        page_size: int = 10,
+        username: Optional[str] = None,
+        operation_type: Optional[str] = None,
+        operation_module: Optional[str] = None,
+        operation_result: Optional[int] = None,
+        start_time: Optional[datetime] = None,
+        end_time: Optional[datetime] = None
+    ) -> Dict[str, Any]:
+        """
+        分页查询日志记录
+        :param page: 页码
+        :param page_size: 每页记录数
+        :param username: 用户名(支持模糊查询)
+        :param operation_type: 操作类型
+        :param operation_module: 操作模块
+        :param operation_result: 操作结果
+        :param start_time: 开始时间
+        :param end_time: 结束时间
+        :return: 包含总记录数和日志列表的字典
+        """
+        query = self.db_session.query(LogModel)
+        
+        # 构建查询条件
+        conditions = []
+        if username:
+            conditions.append(LogModel.username.like(f'%{username}%'))
+        if operation_type:
+            conditions.append(LogModel.operation_type == operation_type)
+        if operation_module:
+            conditions.append(LogModel.operation_module == operation_module)
+        if operation_result is not None:
+            conditions.append(LogModel.operation_result == operation_result)
+        if start_time:
+            conditions.append(LogModel.created_at >= start_time)
+        if end_time:
+            conditions.append(LogModel.created_at < end_time)
+        
+        if conditions:
+            query = query.filter(and_(*conditions))
+        
+        # 计算总记录数
+        total = query.count()
+        
+        # 分页并按创建时间倒序排序
+        logs = query.order_by(desc(LogModel.created_at))\
+            .offset((page - 1) * page_size)\
+            .limit(page_size)\
+            .all()
+        
+        return {
+            'total': total,
+            'data': logs
+        }
+    
+    def insert_log(
+        self,
+        username: str,
+        operation_type: str,
+        operation_desc: Optional[str] = None,
+        operation_result: Optional[int] = None,
+        operation_module: Optional[str] = None,
+        operation_data: Optional[str] = None,
+        data_changes: Optional[str] = None,
+        operation_ip: Optional[str] = None
+    ) -> LogModel:
+        """
+        插入单条日志记录
+        :param username: 用户名
+        :param operation_type: 操作类型
+        :param operation_desc: 操作描述
+        :param operation_result: 操作结果
+        :param operation_module: 操作模块
+        :param operation_data: 操作数据
+        :param data_changes: 数据变更记录
+        :param operation_ip: 操作IP
+        :return: 创建的日志记录
+        """
+        log = LogModel(
+            username=username,
+            operation_type=operation_type,
+            operation_desc=operation_desc,
+            operation_result=operation_result,
+            operation_module=operation_module,
+            operation_data=operation_data,
+            data_changes=data_changes,
+            operation_ip=operation_ip
+        )
+        
+        self.db_session.add(log)
+        self.db_session.commit()
+        return log
+    
+    def batch_insert_logs(self, logs: List[Dict[str, Any]]) -> List[LogModel]:
+        """
+        批量插入日志记录
+        :param logs: 日志记录列表
+        :return: 创建的日志记录列表
+        """
+        log_models = [LogModel(**log) for log in logs]
+        self.db_session.add_all(log_models)
+        self.db_session.commit()
+        return log_models

+ 101 - 0
SourceCode/IntelligentRailwayCosting/app/stores/project.py

@@ -0,0 +1,101 @@
+from sqlalchemy import and_, or_
+from sqlalchemy.orm import Session
+from datetime import datetime
+from typing import Optional
+
+import tools.db_helper as db_helper
+from core.dtos import ProjectDto
+from core.models.project import ProjectModel
+from core.models.team import TeamModel
+from core.user_session import UserSession
+
+class ProjectStore:
+    def __init__(self, db_session: Session = None):
+        self.db_session = db_session or db_helper.create_sqlServer_session()
+
+    def get_user_projects(
+        self,
+        page: int = 1,
+        page_size: int = 10,
+        keyword: Optional[str] = None,
+        start_time: Optional[datetime] = None,
+        end_time: Optional[datetime] = None,
+        can_edit:Optional[int]=0,
+    ):
+        """
+        分页查询用户有权限的项目列表
+        
+        Args:
+            page: 页码,从1开始
+            page_size: 每页数量
+            keyword: 关键字(模糊查询)
+            start_time: 开始时间
+            end_time: 结束时间
+            can_edit: 是否只显示可编辑的项目
+            
+        Returns:
+            Tuple[total_count, projects]
+        """
+        # 构建基础查询
+        query = (self.db_session.query(
+            ProjectModel.project_id,
+            ProjectModel.project_name,
+            ProjectModel.project_manager,
+            ProjectModel.design_stage,
+            ProjectModel.project_description,
+            ProjectModel.short_name,
+            ProjectModel.project_version,
+            ProjectModel.project_type,
+            ProjectModel.unit,
+            ProjectModel.create_time,
+        ) .distinct())
+        user = UserSession.get_current_user()
+        if not user.is_admin:
+            query = query.outerjoin(TeamModel, ProjectModel.project_id == TeamModel.project_id)
+            if can_edit:
+                query = query.filter(
+                    or_(ProjectModel.project_manager == user.username,
+                        and_(TeamModel.name == user.username, TeamModel.compilation_status == can_edit)
+                        )
+                    )
+            else:
+                query = query.filter(or_(ProjectModel.project_manager == user.username,TeamModel.name == user.username))
+        
+        # 添加编辑权限过滤
+        # if can_edit:
+        #     query = query.filter(or_(ProjectModel.project_manager == user.username,TeamModel.compilation_status == 1))
+        #
+        # 添加过滤条件
+        if keyword:
+            query = query.filter(or_(
+                ProjectModel.project_id.like(f'%{keyword}%'),
+                ProjectModel.project_name.like(f'%{keyword}%'),
+                ProjectModel.project_manager.like(f'%{keyword}%'),
+                ProjectModel.project_description.like(f'%{keyword}%'),
+                ProjectModel.short_name.like(f'%{keyword}%')
+            ))
+        
+        if start_time:
+            query = query.filter(ProjectModel.create_time >= start_time)
+            
+        if end_time:
+            query = query.filter(ProjectModel.create_time < end_time)
+            
+        # 获取总记录数
+        total_count = query.count()
+        
+        # 分页并按创建时间倒序排序
+        projects = query.order_by(ProjectModel.create_time.desc())\
+            .offset((page - 1) * page_size)\
+            .limit(page_size)\
+            .all()
+
+        return {
+            'total': total_count,
+            'data': projects
+        }
+
+    def get(self,project_id:str):
+        data = self.db_session.query(ProjectModel).filter(ProjectModel.project_id == project_id).first()
+        return ProjectDto.from_model(data).to_dict()
+

+ 14 - 16
SourceCode/IntelligentRailwayCosting/app/stores/user.py

@@ -1,31 +1,29 @@
 from sqlalchemy.orm import Session
 from typing import Optional, List
-from models.user import User
-from tools.db_helper.sqlserver_helper import SQLServerHelper
+
+import tools.db_helper  as db_helper
+from core.models import UserModel
 
 class UserStore:
-    def __init__(self, session: Session = None):
-        if session is None:
-            session_maker = SQLServerHelper().get_session_maker("db.sqlserver_mian")
-            self.session = session_maker()
-        else:
-            self.session = session
-
-    def get_user_by_id(self, user_id: int) -> Optional[User]:
+    def __init__(self, db_session: Session = None):
+        self.db_session = db_session or db_helper.create_sqlServer_session()
+
+
+    def get_user_by_id(self, user_id: int) -> Optional[UserModel]:
         """根据用户ID获取用户信息"""
-        return self.session.query(User).filter(User.id == user_id).first()
+        return self.db_session.query(UserModel).filter(UserModel.id == user_id).first()
 
-    def get_user_by_username(self, username: str) -> Optional[User]:
+    def get_user_by_username(self, username: str) -> Optional[UserModel]:
         """根据用户名获取用户信息"""
-        return self.session.query(User).filter(User.username == username).first()
+        return self.db_session.query(UserModel).filter(UserModel.username == username).first()
 
-    def get_all_users(self) -> List[User]:
+    def get_all_users(self) -> List[UserModel]:
         """获取所有用户列表"""
-        return self.session.query(User).all()
+        return self.db_session.query(UserModel)
 
 
 
-    def authenticate_user(self, username: str, password: str) -> Optional[User]:
+    def authenticate_user(self, username: str, password: str) -> Optional[UserModel]:
         """用户认证"""
         user = self.get_user_by_username(username)
         if user and user.password == password:

+ 18 - 0
SourceCode/IntelligentRailwayCosting/app/tools/db_helper/__init__.py

@@ -0,0 +1,18 @@
+from sqlalchemy.orm import Session
+
+from .mysql_helper import MySQLHelper
+from .sqlserver_helper import SQLServerHelper
+
+
+def create_sqlServer_session(database:str=None)->Session:
+    return SQLServerHelper().get_session_maker(database)()
+
+def create_mysql_session(database:str=None)->Session:
+    return MySQLHelper().get_session_maker(database)()
+
+__all__ = [
+    'MySQLHelper',
+    'SQLServerHelper',
+    'create_sqlServer_session',
+    'create_mysql_session'
+]

+ 8 - 6
SourceCode/IntelligentRailwayCosting/app/tools/db_helper/base.py

@@ -1,7 +1,7 @@
 from typing import Dict, Optional, Any, List, Tuple, Generator
 from contextlib import contextmanager
 import threading
-import tools.utils as utils
+import core.configs as configs
 from sqlalchemy.orm import sessionmaker, declarative_base
 
 # 创建基础模型类
@@ -10,7 +10,7 @@ Base = declarative_base()
 class DBHelper:
     _instance = None
     _lock = threading.Lock()
-    _main_config_key = ""
+    _main_database_name = ""
     def __new__(cls, *args, **kwargs):
         with cls._lock:
             if cls._instance is None:
@@ -58,15 +58,16 @@ class DBHelper:
         if database in self._config_cache:
             return self._config_cache[database]
         
-        db_config = utils.get_config_object(f"db.{database}")
+        db_config = configs.database[database]
         if db_config:
             self._config_cache[database] = db_config
             return db_config
         
-        main_config = utils.get_config_object(self._main_config_key)
+        main_config = configs.database[self._main_database_name]
         if not main_config:
             raise Exception(f"未找到数据库 {database} 的配置,且main_config配置不存在")
-        
+        main_config['database'] = database
+        main_config['db'] = database
         self._config_cache[database] = main_config
         return main_config
     
@@ -134,7 +135,7 @@ class DBHelper:
         """
         raise NotImplementedError("子类必须实现get_engine方法")
     
-    def get_session_maker(self, database: str, config: Optional[Dict[str, Any]] = None) -> sessionmaker:
+    def get_session_maker(self, database: str=None, config: Optional[Dict[str, Any]] = None) -> sessionmaker:
         """获取或创建会话工厂
         
         Args:
@@ -144,6 +145,7 @@ class DBHelper:
         Returns:
             会话工厂实例
         """
+        database = database or self._main_database_name
         if database in self._sessions:
             return self._sessions[database]
         

+ 6 - 2
SourceCode/IntelligentRailwayCosting/app/tools/db_helper/mysql_helper.py

@@ -16,9 +16,13 @@ class MySQLHelper(DBHelper):
             'port': 3306,
             'user': '',
             'password': '',
-            'charset': 'utf8mb4'
+            'charset': 'utf8mb4',
+            'pool_size': 5,
+            'max_overflow': 10,
+            'pool_timeout': 30,
+            'pool_recycle': 3600
         }
-        self._main_config_key = "db.mysql_main"
+        self._main_database_name = "mysql_main"
 
     def get_engine(self, database: str, config: Optional[Dict[str, Any]] = None) -> Engine:
         """获取或创建数据库引擎

+ 21 - 10
SourceCode/IntelligentRailwayCosting/app/tools/db_helper/sqlserver_helper.py

@@ -1,5 +1,5 @@
 from typing import Dict, Optional, Any, List, Tuple
-
+import core.configs as configs
 from sqlalchemy import create_engine, text
 from sqlalchemy.engine import Engine
 from sqlalchemy.orm import sessionmaker
@@ -19,8 +19,20 @@ class SQLServerHelper(DBHelper):
             'password': '',
             'trusted_connection': 'yes'
         }
-        self._main_config_key = "db.sqlserver_mian"
+        self._pool_config = {
+            'pool_size': 10,  # 增加初始连接数
+            'max_overflow': 20,  # 增加最大溢出连接数
+            'pool_timeout': 30,
+            'pool_recycle': 1800,  # 每30分钟回收连接
+            'pool_pre_ping': True,  # 启用连接健康检查
+            'connect_args': {
+                'connect_timeout': 60,
+                'connect_retries': 5,  # 增加重试次数
+                'connect_retry_interval': 3  # 减少重试间隔
+            }
+        }
 
+        self._main_database_name = f"sqlserver_mian_{configs.app.version}" if configs.app.user_version else "sqlserver_mian"
     def _build_connection_string(self, database: str, config: Optional[Dict[str, str]] = None) -> str:
         """构建连接字符串"""
         conn_config = self._default_config.copy()
@@ -43,7 +55,8 @@ class SQLServerHelper(DBHelper):
         conn_parts = [
             f"DRIVER={conn_config['driver']}",
             f"SERVER={conn_config['server']}",
-            f"DATABASE={conn_config['database'] if 'database' in conn_config else database}"
+            f"DATABASE={conn_config['database'] if 'database' in conn_config else database}",
+            "CHARSET=UTF-8"
         ]
         conn_parts.extend(auth_params)
         
@@ -57,13 +70,11 @@ class SQLServerHelper(DBHelper):
         """获取或创建数据库引擎"""
         if database not in self._engines:
             conn_str = self._build_connection_string(database, config)
-            self._engines[database] = create_engine(
-                conn_str,
-                pool_size=5,
-                max_overflow=10,
-                pool_timeout=30,
-                pool_recycle=1800
-            )
+            engine = create_engine(conn_str, **self._pool_config)
+            # 预热连接池
+            with engine.connect() as conn:
+                conn.execute(text("SELECT 1"))
+            self._engines[database] = engine
         return self._engines[database]
 
     def execute_query(self, database: str, query: str, params: Optional[Dict[str, Any]] = None) -> List[Tuple]:

+ 7 - 15
SourceCode/IntelligentRailwayCosting/app/tools/utils/file_helper.py

@@ -41,7 +41,7 @@ class FileHelper:
         path = os.path.join(file_path, file_name)
         path = path.replace("\\", "/")
         path = path.replace("//", "/")
-        # 10个不同的 User-Agent
+        # 10个不同的 UserModel-Agent
         user_agents = [
             "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
             "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Safari/605.1.15",
@@ -55,7 +55,7 @@ class FileHelper:
             "Mozilla/5.0 (Linux; Android 11; SM-G973F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Mobile Safari/537.36",
         ]
 
-        # 根据文件名长度选择一个 User-Agent
+        # 根据文件名长度选择一个 UserModel-Agent
         ua_index = len(file_name) % len(user_agents)
         # 解析 file_url 获取 Referer
         parsed_url = urlparse(file_url)
@@ -63,7 +63,7 @@ class FileHelper:
             "//download.", "//www."
         )
         headers = {
-            "User-Agent": user_agents[ua_index],
+            "UserModel-Agent": user_agents[ua_index],
             "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
             "Accept-Encoding": "gzip, deflate, br",
             "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7",
@@ -104,13 +104,9 @@ class FileHelper:
                                     f"  删除目录及其内容: {dir_path}"
                                 )
                             except PermissionError:
-                                utils.get_logger().error(
-                                    f"  权限错误,无法删除目录: {dir_path}"
-                                )
+                                utils.get_logger().error(f"  权限错误,无法删除目录: {dir_path}")
                             except Exception as e:
-                                utils.get_logger().error(
-                                    f"  删除目录失败: {dir_path}。Exception: {e}"
-                                )
+                                utils.get_logger().error(f"  删除目录失败: {dir_path}。Exception: {e}")
                     except ValueError:
                         # 如果目录名称不符合 %Y-%m/%d 格式,跳过
                         continue
@@ -157,13 +153,9 @@ class FileHelper:
                                     f"  Report 删除目录及其内容: {dir_path}"
                                 )
                             except PermissionError:
-                                utils.get_logger().error(
-                                    f"  Report 权限错误,无法删除目录: {dir_path}"
-                                )
+                                utils.get_logger().error(f"  Report 权限错误,无法删除目录: {dir_path}")
                             except Exception as e:
-                                utils.get_logger().error(
-                                    f"  Report 删除目录失败: {dir_path}。Exception: {e}"
-                                )
+                                utils.get_logger().error(f"  Report 删除目录失败: {dir_path}。Exception: {e}")
                     except ValueError:
                         # 如果目录名称不符合 %Y-%m/%d 格式,跳过
                         continue

+ 8 - 0
SourceCode/IntelligentRailwayCosting/app/views/__init__.py

@@ -0,0 +1,8 @@
+from views.login import auth_bp
+from views.project import project_bp
+from views.log import log_bp
+
+def register_views(app):
+    app.register_blueprint(auth_bp)
+    app.register_blueprint(project_bp)
+    app.register_blueprint(log_bp)

+ 18 - 0
SourceCode/IntelligentRailwayCosting/app/views/log.py

@@ -0,0 +1,18 @@
+from flask import Blueprint, render_template
+from core.enum import OperationType,OperationModule,OperationResult
+from core.user_session import Permission
+
+log_bp = Blueprint('log', __name__, template_folder='templates')
+
+@log_bp.route('/log', methods=['GET'])
+@Permission.authorize
+def index():
+    operation_type_list = [op.value for op in OperationType]
+    operation_module_list = [op.value for op in OperationModule]
+    operation_result_list = OperationResult.get_dict()
+    return render_template('log/index.html',
+                           page_active='log',
+                           operation_type_list=operation_type_list,
+                           operation_module_list=operation_module_list,
+                           operation_result_list=operation_result_list,
+                           )

+ 15 - 0
SourceCode/IntelligentRailwayCosting/app/views/login.py

@@ -0,0 +1,15 @@
+from flask import Blueprint, render_template
+from flask_login import logout_user
+from core.user_session import UserSession
+from core.configs import config
+
+auth_bp = Blueprint('auth', __name__, template_folder='templates')
+
+@auth_bp.route('/login', methods=['GET'])
+def login():
+    # 如果用户已登录,重定向到首页
+    # if 'user_id' in session:
+    #     return redirect(url_for('project.index'))
+    logout_user()
+    UserSession.clear_user()
+    return render_template('account/login.html',app_name=config.app.name)

+ 16 - 0
SourceCode/IntelligentRailwayCosting/app/views/project.py

@@ -0,0 +1,16 @@
+from flask import Blueprint, render_template
+from core.user_session import Permission
+from stores.project import ProjectStore
+
+project_bp = Blueprint('project', __name__, template_folder='templates')
+project_store = ProjectStore()
+@project_bp.route('/', methods=['GET'])
+@Permission.authorize
+def index():
+   return render_template('project/index.html',page_active='project')
+
+@project_bp.route('/budget_info/<project_id>', methods=['GET'])
+@Permission.authorize
+def budget_info(project_id:str):
+    project = project_store.get(project_id)
+    return render_template('project/budget_info.html',page_active='project',project=project)

+ 33 - 0
SourceCode/IntelligentRailwayCosting/app/views/static/account/login.css

@@ -0,0 +1,33 @@
+body {
+    background-color: #f8f9fa;
+    height: 100vh;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+.container, .container-fluid{
+    justify-content: center;
+    display: flex;
+}
+.login-container {
+    background-color: white;
+    padding: 2rem;
+    border-radius: 10px;
+    box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
+    width: 100%;
+    max-width: 400px;
+    margin-top: -10%;
+}
+.login-title {
+    text-align: center;
+    color: #333;
+    margin-bottom: 2rem;
+}
+.form-floating {
+    margin-bottom: 1rem;
+}
+.btn-login {
+    width: 100%;
+    padding: 0.8rem;
+    font-size: 1.1rem;
+}

+ 56 - 0
SourceCode/IntelligentRailwayCosting/app/views/static/account/login.js

@@ -0,0 +1,56 @@
+;(function () {
+    'use strict'
+    const form = document.getElementById('loginForm')
+    form.addEventListener('submit', async function (event) {
+        event.preventDefault()
+        if (!form.checkValidity()) {
+            event.stopPropagation()
+            form.classList.add('was-validated')
+            return
+        }
+
+        const formData = {
+            username: document.getElementById('username').value,
+            password: document.getElementById('password').value,
+        }
+
+        try {
+            const response = await fetch('/api/auth/login', {
+                method: 'POST',
+                headers: {
+                    'Content-Type': 'application/json',
+                },
+                body: JSON.stringify(formData),
+            })
+            const data = await response.json()
+            if (data.success) {
+                // 获取URL参数中的redirect_url
+                const urlParams = new URLSearchParams(window.location.search);
+                const redirectUrl = urlParams.get('redirect_url');
+                if (redirectUrl) {
+                    window.location.href =  redirectUrl;
+                } else {
+                    window.location.href = `${window.location.protocol}//${window.location.host}/`
+                }
+            } else {
+                showAlert('danger', data.error || '登录失败,请检查用户名和密码')
+            }
+        } catch (error) {
+            console.error('登录请求失败:', error)
+            showAlert('danger', '网络错误,请稍后重试')
+        }
+    })
+
+    function showAlert(type, message) {
+        const alertContainer = document.getElementById('alert-container')
+        const alertDiv = document.createElement('div')
+        alertDiv.className = `alert alert-${type} alert-dismissible fade show`
+        alertDiv.setAttribute('role', 'alert')
+        alertDiv.innerHTML = `
+            ${message}
+            <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
+        `
+        alertContainer.innerHTML = ''
+        alertContainer.appendChild(alertDiv)
+    }
+})()

File diff suppressed because it is too large
+ 4 - 0
SourceCode/IntelligentRailwayCosting/app/views/static/base/css/style.bundle.css


+ 76 - 0
SourceCode/IntelligentRailwayCosting/app/views/static/base/css/styles.css

@@ -0,0 +1,76 @@
+:root {
+    --bs-app-header-height: 64px;
+    --bs-app-header-height-actual: 64px;
+}
+*{
+    margin: 0;
+    padding: 0;
+    box-sizing: border-box;
+}
+
+
+
+body > .container, body > .container-fluid{
+    padding: 0;
+    margin: 0;
+}
+.header{
+    display: flex;
+    align-items: center;
+}
+.header h3{
+    margin-bottom: 0;
+    padding: 3px 15px;
+    border-right: 2px solid #666;
+}
+.table-box{
+    position: relative;
+    width: 100%;
+    height: 100%;
+    min-height: 300px;
+}
+.table-box, .table-box .table {
+    width: 100%;
+}
+.table-box th,.table-box td{
+    text-align: center;
+    vertical-align: middle;
+    height: 40px;
+    padding: 3px 5px;
+}
+.table-box th{
+    font-weight: 600;
+    font-size: 16px;
+    height: 45px;
+}
+.table-box td > .btn{
+    padding: calc(.2rem + 1px) calc(.6rem + 1px)!important;
+    margin: 0 5px;
+    font-size: 12px;
+    --bs-btn-border-radius: .3rem;
+}
+.table-box td > .link:hover{
+    border-bottom: 2px solid;
+}
+
+.table-box .table-loading{
+    position: absolute;
+    top:0;
+    bottom: 0;
+    left:0;
+    right: 0;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    /*background: rgba(0,0,0,.15);*/
+    min-height: 300px;
+}
+.table-box .table-loading span{
+    font-size: 16px;
+    color: #666;
+}
+.pagination-row{
+    width: 100%;
+    display: flex;
+    justify-content: space-between;
+}

File diff suppressed because it is too large
+ 0 - 0
SourceCode/IntelligentRailwayCosting/app/views/static/base/js/scripts.bundle.js


+ 312 - 0
SourceCode/IntelligentRailwayCosting/app/views/static/base/js/utils.js

@@ -0,0 +1,312 @@
+function GoTo(url, isNewWindow) {
+	if (isNewWindow || isNewWindow === undefined) {
+		window.open(url)
+	} else {
+		window.location.href = url
+	}
+}
+
+function IwbAjax(opt) {
+	opt = opt || {}
+	if (!opt.url) {
+		alert('请传入url')
+	}
+	if (opt.method === undefined) {
+		opt.method = 'POST'
+	}
+	if (opt.data === undefined) {
+		opt.data = {}
+	}
+	opt = $.extend(
+		{},
+		{
+			isAlert: true,
+		},
+		opt
+	)
+	fetch(opt.url, {
+		method: opt.method,
+		headers: {
+			'Content-Type': 'application/json',
+		},
+		body: JSON.stringify(opt.data),
+	})
+		.then((response) => response.json())
+		.then((data) => {
+			if (data.success) {
+				if (opt.isAlert) {
+					alert('操作成功')
+				}
+				if (opt.table) {
+					IwbTable($(opt.table))
+				}
+				if (opt.modal) {
+					$(opt.modal).modal('hide')
+				}
+				opt.success && opt.success(data)
+			} else {
+				console.error(opt.url, data.message)
+				if ((opt.isAlert && opt.isAlertError === undefined) || opt.isAlertError) {
+					alert(data.message)
+				}
+			}
+		})
+}
+
+function IwbAjax_1(opt) {
+	opt.isAlert = false
+	opt.isAlertError = true
+	IwbAjax(opt)
+}
+function IwbAjax_2(opt, modal, table) {
+	opt.modal = modal || '#modal'
+	opt.table = table || '#table'
+	IwbAjax(opt)
+}
+function IwbTable(table, opts) {
+	const $table = $(table)
+	const $tableBox = $table.closest('.table-box')
+	if (table.length === 0) return
+	const options = $table.data('options')
+	const defaultOptions = {
+		pageSize: 15,
+		pageNum: 1,
+		search: {
+			keyword: '',
+		},
+	}
+	const tableOptions = $.extend({}, options || defaultOptions, opts || {})
+	let isSearch = false
+	function ajaxTable(opt) {
+		if (isSearch) {
+			return
+		}
+		$table.data('options', opt)
+		loading()
+		const data = $.extend({}, opt.search, { pageNum: opt.pageNum, pageSize: opt.pageSize })
+		fetch(opt.url, {
+			method: 'POST',
+			headers: {
+				'Content-Type': 'application/json',
+			},
+			body: JSON.stringify(data),
+		})
+			.then((response) => response.json())
+			.then((res) => {
+				if (res.success) {
+					renderTable(res.data.rows, res.data.total)
+				} else {
+					renderTable([], 0)
+					console.error('加载表格出错:', res.message, opt.url)
+				}
+			})
+			.catch((error) => {
+				console.error(error)
+			})
+			.finally(() => {
+				clearLoading()
+			})
+	}
+	function loading() {
+		isSearch = true
+		$tableBox.append(`<div class="table-loading"><span>正在加载中...</span></div>`)
+	}
+	function clearLoading() {
+		isSearch = false
+		$tableBox.find('.table-loading').remove()
+	}
+	function renderTable(rows, total) {
+		const opt = $table.data('options')
+		let head_str = '',
+			body_str = ''
+		for (let i = 0; i < opt.columns.length; i++) {
+			const col = opt.columns[i]
+			head_str += `<th style="${col.width ? `width: ${col.width};` : ``}${col.style ? `${col.style};` : ``}">${col.title}</th>`
+		}
+		if (rows && rows.length) {
+			for (let i = 0; i < rows.length; i++) {
+				const row = rows[i]
+				body_str += '<tr>'
+				for (let j = 0; j < opt.columns.length; j++) {
+					const col = opt.columns[j]
+					if (col.render) {
+						body_str += `<td>${col.render(row, rows)}</td>`
+					} else {
+						body_str += `<td>${row[col.data] || '-'}</td>`
+					}
+				}
+				body_str += '</tr>'
+			}
+		} else {
+			body_str += '<tr><td colspan="' + opt.columns.length + '" class="text-center">暂无数据</td></tr>'
+		}
+		$tableBox.fadeOut(300, () => {
+			let page_str = formatPage(opt.pageNum, opt.pageSize, total)
+			$table.parent().find('.pagination-row').html(page_str)
+			$table.html(`<thead><tr>${head_str}</tr></thead><tbody>${body_str}</tbody>`)
+			$tableBox.fadeIn(500)
+		})
+	}
+
+	function formatPage(pageNum, pageSize, total) {
+		const totalPages = Math.ceil(total / pageSize)
+		// const startIndex = (pageNum - 1) * pageSize + 1
+		// const endIndex = Math.min(pageNum * pageSize, total)
+
+		let str = '<div class=" d-flex align-items-center justify-content-center justify-content-md-start dt-toolbar">'
+		// 每页显示条数选择
+		str += `<div><select class="form-select form-select-solid form-select-sm" onchange="IwbTableChangePageSize(this)">`
+		;[15, 25, 50, 100].forEach((size) => {
+			str += `<option value="${size}" ${pageSize === size ? ' selected' : ''}>${size}</option>`
+		})
+		str += '</select></div>'
+
+		// 显示记录信息
+		str += `<div class="dt-info">当前第 ${pageNum}/${totalPages} 页  共 ${total} 条</div></div>`
+
+		// 分页导航
+		str += '<div class="d-flex align-items-center justify-content-center justify-content-md-end"><div class="dt-paging paging_simple_numbers"><nav aria-label="pagination"><ul class="pagination">'
+
+		// 上一页按钮
+		if (pageNum > 1) {
+			str += '<li class="dt-paging-button page-item"><button class="page-link previous" onclick="IwbTableJumpPage(this,' + (pageNum - 1) + ')"><i class="previous"></i></button></li>'
+		} else {
+			str += '<li class="dt-paging-button page-item disabled"><button class="page-link previous"><i class="previous"></i></button></li>'
+		}
+
+		// 计算显示的页码范围
+		let startPage = Math.max(1, pageNum - 2)
+		let endPage = Math.min(totalPages, pageNum + 2)
+
+		// 显示第一页和省略号
+		if (startPage > 1) {
+			str += '<li class="dt-paging-button page-item"><button class="page-link" onclick="IwbTableJumpPage(this,1)">1</button></li>'
+			if (startPage > 2) {
+				str += '<li class="dt-paging-button page-item disabled"><button class="page-link ellipsis" tabindex="-1">…</button></li>'
+			}
+		}
+
+		// 显示页码
+		for (let i = startPage; i <= endPage; i++) {
+			if (i === pageNum) {
+				str += `<li class="dt-paging-button page-item active"><button class="page-link" onclick="IwbTableJumpPage(this,${i})">${i}</button></li>`
+			} else {
+				str += `<li class="dt-paging-button page-item"><button class="page-link" onclick="IwbTableJumpPage(this,${i})">${i}</button></li>`
+			}
+		}
+
+		// 显示最后一页和省略号
+		if (endPage < totalPages) {
+			if (endPage < totalPages - 1) {
+				str += '<li class="dt-paging-button page-item disabled"><button class="page-link ellipsis" tabindex="-1">…</button></li>'
+			}
+			str += `<li class="dt-paging-button page-item"><button class="page-link" onclick="IwbTableJumpPage(this,${totalPages})">${totalPages}</button></li>`
+		}
+
+		// 下一页按钮
+		if (pageNum < totalPages) {
+			str += '<li class="dt-paging-button page-item"><button class="page-link next" onclick="IwbTableJumpPage(this,' + (pageNum + 1) + ')"><i class="next"></i></button></li>'
+		} else {
+			str += '<li class="dt-paging-button page-item disabled"><button class="page-link next"><i class="next"></i></button></li>'
+		}
+
+		str += '</ul></nav></div></div>'
+		return str
+	}
+	ajaxTable(tableOptions)
+}
+function IwbTableSearch(that) {
+	const $search = $(that).closest('form.search-box')
+	const $table = $(that).closest('.table-box').find('.table')
+	const search = $table.data('options').search || {}
+	$search.find('.form-control,.form-select').each((i, el) => {
+		const v = $(el).val()
+		if (v.trim() !== '') {
+			search[$(el).attr('name')] = v
+		} else {
+			delete search[$(el).attr('name')]
+		}
+	})
+	IwbTable($table, { search: search, pageNum: 1 })
+}
+function IwbTableResetSearch(that) {
+	const $search = $(that).closest('form.search-box')
+	$search[0].reset()
+	IwbTableSearch(that)
+}
+function IwbTableJumpPage(that, pageNum) {
+	const $table = $(that).closest('.table-box').find('.table')
+	IwbTable($table, { pageNum: pageNum })
+}
+function IwbTableChangePageSize(that) {
+	const $table = $(that).closest('.table-box').find('.table')
+	const pageSize = parseInt($(that).val())
+	IwbTable($table, { pageSize: pageSize })
+}
+
+function AddModal(modal, callback) {
+	const $modal = $(modal)
+	$modal.find('.modal-header .modal-title span.prefix').html('添加')
+	$modal.find('.modal-body .form-control').val('')
+	callback && callback($modal)
+	$modal.modal('show')
+}
+function EditModal(modal, callback) {
+	const $modal = $(modal)
+	$modal.find('.modal-header .modal-title span.prefix').html('修改')
+	callback && callback($modal)
+	$modal.modal('show')
+}
+
+function Confirm(title, callback) {
+	if (confirm(title)) {
+		callback && callback()
+	}
+}
+
+function ConfirmUrl(title, url, table) {
+	if (confirm(title)) {
+		IwbAjax({
+			url: url,
+			table: table || '#table',
+		})
+	}
+}
+
+function ChangeHeadMenu(menu) {
+	$('#header_menu .menu-item').removeClass('here')
+	$('#header_menu ' + menu).addClass('here')
+}
+
+function DownloadFile(url, fileName) {
+	fetch(url)
+		.then((response) => {
+			if (!response.ok) {
+				return response.json().then((err) => {
+					throw new Error(err.message || '下载失败')
+				})
+			}
+			return response.blob()
+		})
+		.then((blob) => {
+			const downloadUrl = window.URL.createObjectURL(blob)
+			const a = document.createElement('a')
+			a.href = downloadUrl
+			a.download = fileName
+			document.body.appendChild(a)
+			a.click()
+			window.URL.revokeObjectURL(downloadUrl)
+			document.body.removeChild(a)
+		})
+		.catch((error) => {
+			alert(error.message)
+		})
+}
+
+// 添加字符串序列化方法
+String.prototype.format = function () {
+	let args = arguments
+	return this.replace(/{(\d+)}/g, function (match, index) {
+		return typeof args[index] != 'undefined' ? args[index] : match
+	})
+}

File diff suppressed because it is too large
+ 0 - 0
SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/datatables/datatables.bundle.css


File diff suppressed because it is too large
+ 20 - 0
SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/datatables/datatables.bundle.js


BIN
SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/@fortawesome/fa-brands-400.ttf


BIN
SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/@fortawesome/fa-brands-400.woff2


BIN
SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/@fortawesome/fa-regular-400.ttf


BIN
SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/@fortawesome/fa-regular-400.woff2


BIN
SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/@fortawesome/fa-solid-900.ttf


BIN
SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/@fortawesome/fa-solid-900.woff2


BIN
SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/@fortawesome/fa-v4compatibility.ttf


BIN
SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/@fortawesome/fa-v4compatibility.woff2


BIN
SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/bootstrap-icons/bootstrap-icons.woff


BIN
SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/bootstrap-icons/bootstrap-icons.woff2


BIN
SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/keenicons/keenicons-duotone.eot


File diff suppressed because it is too large
+ 85 - 0
SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/keenicons/keenicons-duotone.svg


BIN
SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/keenicons/keenicons-duotone.ttf


BIN
SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/keenicons/keenicons-duotone.woff


BIN
SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/keenicons/keenicons-outline.eot


File diff suppressed because it is too large
+ 14 - 0
SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/keenicons/keenicons-outline.svg


BIN
SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/keenicons/keenicons-outline.ttf


BIN
SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/keenicons/keenicons-outline.woff


BIN
SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/keenicons/keenicons-solid.eot


File diff suppressed because it is too large
+ 20 - 0
SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/keenicons/keenicons-solid.svg


BIN
SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/keenicons/keenicons-solid.ttf


BIN
SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/keenicons/keenicons-solid.woff


BIN
SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/line-awesome/la-brands-400.eot


File diff suppressed because it is too large
+ 16 - 0
SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/line-awesome/la-brands-400.svg


BIN
SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/line-awesome/la-brands-400.ttf


BIN
SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/line-awesome/la-brands-400.woff


BIN
SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/line-awesome/la-brands-400.woff2


BIN
SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/line-awesome/la-regular-400.eot


File diff suppressed because it is too large
+ 286 - 0
SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/line-awesome/la-regular-400.svg


BIN
SourceCode/IntelligentRailwayCosting/app/views/static/base/plugins/fonts/line-awesome/la-regular-400.ttf


Some files were not shown because too many files changed in this diff