YueYunyun 3 miesięcy temu
rodzic
commit
5f4c94cac3
31 zmienionych plików z 1135 dodań i 368 usunięć
  1. 5 4
      SourceCode/IntelligentRailwayCosting/.script/init.sql
  2. 5 0
      SourceCode/IntelligentRailwayCosting/app/__init__.py
  3. 3 0
      SourceCode/IntelligentRailwayCosting/app/config.yml
  4. 4 2
      SourceCode/IntelligentRailwayCosting/app/core/dtos/project_quota.py
  5. 5 5
      SourceCode/IntelligentRailwayCosting/app/core/dtos/project_task.py
  6. 2 2
      SourceCode/IntelligentRailwayCosting/app/core/models/project_quota.py
  7. 1 1
      SourceCode/IntelligentRailwayCosting/app/core/models/project_task.py
  8. 1 1
      SourceCode/IntelligentRailwayCosting/app/core/models/team.py
  9. 8 1
      SourceCode/IntelligentRailwayCosting/app/core/user_session/user_session.py
  10. 28 0
      SourceCode/IntelligentRailwayCosting/app/executor/__init__.py
  11. 16 0
      SourceCode/IntelligentRailwayCosting/app/executor/collector.py
  12. 43 0
      SourceCode/IntelligentRailwayCosting/app/executor/processor.py
  13. 40 0
      SourceCode/IntelligentRailwayCosting/app/executor/sender.py
  14. 58 4
      SourceCode/IntelligentRailwayCosting/app/routes/project_quota.py
  15. 102 14
      SourceCode/IntelligentRailwayCosting/app/routes/project_task.py
  16. 1 1
      SourceCode/IntelligentRailwayCosting/app/services/log.py
  17. 10 3
      SourceCode/IntelligentRailwayCosting/app/services/project.py
  18. 79 17
      SourceCode/IntelligentRailwayCosting/app/services/project_quota.py
  19. 97 20
      SourceCode/IntelligentRailwayCosting/app/services/project_task.py
  20. 5 1
      SourceCode/IntelligentRailwayCosting/app/stores/__init__.py
  21. 8 5
      SourceCode/IntelligentRailwayCosting/app/stores/budget.py
  22. 1 1
      SourceCode/IntelligentRailwayCosting/app/stores/log.py
  23. 6 1
      SourceCode/IntelligentRailwayCosting/app/stores/project.py
  24. 22 8
      SourceCode/IntelligentRailwayCosting/app/stores/project_quota.py
  25. 46 39
      SourceCode/IntelligentRailwayCosting/app/stores/project_task.py
  26. 11 2
      SourceCode/IntelligentRailwayCosting/app/views/static/base/css/styles.css
  27. 14 6
      SourceCode/IntelligentRailwayCosting/app/views/static/base/js/utils.js
  28. 437 222
      SourceCode/IntelligentRailwayCosting/app/views/static/project/budget_info.js
  29. 1 1
      SourceCode/IntelligentRailwayCosting/app/views/templates/account/login.html
  30. 1 1
      SourceCode/IntelligentRailwayCosting/app/views/templates/log/index.html
  31. 75 6
      SourceCode/IntelligentRailwayCosting/app/views/templates/project/budget_info.html

+ 5 - 4
SourceCode/IntelligentRailwayCosting/.script/init.sql

@@ -7,7 +7,7 @@ CREATE TABLE IF NOT EXISTS project_task (
     task_name VARCHAR(255) NOT NULL COMMENT '任务名称',
     task_desc VARCHAR(1000) COMMENT '任务描述',
     project_id VARCHAR(50) NOT NULL COMMENT '项目编号',
-    build_id int NOT NULL COMMENT '概算序号',
+    budget_id int NOT NULL COMMENT '概算序号',
     item_id int NOT NULL COMMENT '条目序号',
     item_code VARCHAR(255) NOT NULL COMMENT '条目编号',
     file_path text COMMENT '文件路径',
@@ -28,7 +28,7 @@ CREATE TABLE IF NOT EXISTS project_task (
     updated_by VARCHAR(50) COMMENT '更新人',
     updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
     INDEX idx_project_id (project_id),
-    INDEX idx_build_id (build_id),
+    INDEX idx_budget_id (budget_id),
     INDEX idx_item_id (item_id),
     INDEX idx_item_code (item_code),
     INDEX idx_created_at (created_at)
@@ -37,8 +37,9 @@ CREATE TABLE IF NOT EXISTS project_task (
 -- 创建项目任务表
 CREATE TABLE IF NOT EXISTS project_quota (
     id INT AUTO_INCREMENT PRIMARY KEY,
+    task_id int NOT NULL COMMENT '任务编号',
     project_id VARCHAR(50) NOT NULL COMMENT '项目编号',
-    build_id int NOT NULL COMMENT '概算序号',
+    budget_id int NOT NULL COMMENT '概算序号',
     item_id int NOT NULL COMMENT '条目序号',
     item_code VARCHAR(255) NOT NULL COMMENT '条目编号',
     quota_code VARCHAR(50) COMMENT '定额编号',
@@ -66,7 +67,7 @@ CREATE TABLE IF NOT EXISTS project_quota (
     updated_by VARCHAR(50) COMMENT '更新人',
     updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
     INDEX idx_project_id (project_id),
-    INDEX idx_build_id (build_id),
+    INDEX idx_budget_id (budget_id),
     INDEX idx_item_id (item_id),
     INDEX idx_item_code (item_code),
     INDEX idx_created_at (created_at)

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

@@ -19,6 +19,11 @@ def create_app():
     app = Flask(__name__, static_folder='views/static')
     app.secret_key = "1qwe2iwb3vber"
     app.config['JSON_AS_ASCII'] = False
+    app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024  # 限制上传文件大小为16MB
+    app.config['UPLOAD_EXTENSIONS'] = ['.jpg', '.png', '.pdf', '.doc', '.docx', '.xls', '.xlsx']  # 允许的文件类型
+    app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
+    app.config['PRESERVE_CONTEXT_ON_EXCEPTION'] = False
+    app.config['REQUEST_BODY_LIMIT'] = '16MB'
     app.json_provider_class = CustomJSONProvider
 
     login_manager.init_app(app)

+ 3 - 0
SourceCode/IntelligentRailwayCosting/app/config.yml

@@ -77,3 +77,6 @@ fastgpt_ai:
     app_02_2024:
       api_url: http://192.168.0.104:8020/api
       api_key: fastgpt-o4CF7Pu1FRTvHjWFqeNcClBS6ApyflNfkBGXo9p51fuBMAX1L0erU8yz8
+
+file:
+  source_path: './temp_files'

+ 4 - 2
SourceCode/IntelligentRailwayCosting/app/core/dtos/project_quota.py

@@ -6,8 +6,9 @@ from core.models import ProjectQuotaModel
 class ProjectQuotaDto(BaseModel):
     """项目定额DTO"""
     id: Optional[int] = None
+    task_id:int
     project_id: str
-    build_id: int
+    budget_id: int
     item_id: int
     item_code: str
     quota_code: Optional[str] = None
@@ -40,8 +41,9 @@ class ProjectQuotaDto(BaseModel):
         """从数据库模型创建DTO对象"""
         return cls(
             id=model.id,
+            task_id=model.task_id,
             project_id=model.project_id,
-            build_id=model.build_id,
+            budget_id=model.budget_id,
             item_id=model.item_id,
             item_code=model.item_code,
             quota_code=model.quota_code,

+ 5 - 5
SourceCode/IntelligentRailwayCosting/app/core/dtos/project_task.py

@@ -9,7 +9,7 @@ class ProjectTaskDto(BaseModel):
     task_name: str
     task_desc: Optional[str] = None
     project_id: str
-    build_id: int
+    budget_id: int
     item_id: int
     item_code: Optional[str] = None
     file_path: Optional[str] = None
@@ -38,7 +38,7 @@ class ProjectTaskDto(BaseModel):
             task_name=model.task_name,
             task_desc=model.task_desc,
             project_id=model.project_id,
-            build_id=model.build_id,
+            budget_id=model.budget_id,
             item_id=model.item_id,
             item_code=model.item_code,
             file_path=model.file_path,
@@ -63,8 +63,8 @@ class ProjectTaskDto(BaseModel):
     @classmethod
     def from_dict(cls, data: dict) -> 'ProjectTaskDto':
         """从字典创建DTO对象"""
-        if 'budget_id' in data and 'build_id' not in data:
-            data['build_id'] = data.pop('budget_id')
+        if 'budget_id' in data and 'budget_id' not in data:
+            data['budget_id'] = data.pop('budget_id')
         return cls(**data)
 
     def to_dict(self) -> dict:
@@ -72,6 +72,6 @@ class ProjectTaskDto(BaseModel):
         return self.model_dump()
 
     def get_path(self):
-        return f"{self.project_id}_{self.build_id}_{self.item_id}_{self.id}"
+        return f"{self.project_id}_{self.budget_id}_{self.item_id}_{self.id}"
     class Config:
         from_attributes = True

+ 2 - 2
SourceCode/IntelligentRailwayCosting/app/core/models/project_quota.py

@@ -6,10 +6,10 @@ Base = declarative_base()
 
 class ProjectQuotaModel(Base):
     __tablename__ = 'project_quota'
-
     id = Column(Integer, primary_key=True, autoincrement=True)
+    task_id = Column(Integer, nullable=False, comment='任务编号')
     project_id = Column(String(50), nullable=False, comment='项目编号')
-    build_id = Column(Integer, nullable=False, comment='概算序号')
+    budget_id = Column(Integer, nullable=False, comment='概算序号')
     item_id = Column(Integer, nullable=False, comment='条目序号')
     item_code = Column(String(255), nullable=False, comment='条目编号')
     quota_code = Column(String(50), comment='定额编号')

+ 1 - 1
SourceCode/IntelligentRailwayCosting/app/core/models/project_task.py

@@ -10,7 +10,7 @@ class ProjectTaskModel(Base):
     task_name = Column(String(255), nullable=False, comment='任务名称')
     task_desc = Column(String(1000), comment='任务描述')
     project_id = Column(String(50), nullable=False, comment='项目编号')
-    build_id = Column(Integer, nullable=False, comment='概算序号')
+    budget_id = Column(Integer, nullable=False, comment='概算序号')
     item_id = Column(Integer, nullable=False, comment='条目序号')
     item_code = Column(String(255), nullable=False, comment='条目编号')
     file_path = Column(Text, comment='文件路径')

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

@@ -9,7 +9,7 @@ class TeamModel(Base):
     project_id = Column('项目编号', String(30), primary_key=True)
     name = Column('姓名', String(50), primary_key=True)
     operation_permission = Column('操作权限', String(2000))
-    item_number = Column('条目编号', Text)
+    item_code = Column('条目编号', Text)
     compilation_status = Column('编制状态', Integer)
 
     def __repr__(self):

+ 8 - 1
SourceCode/IntelligentRailwayCosting/app/core/user_session/user_session.py

@@ -1,4 +1,4 @@
-from flask import session
+from flask import session, has_request_context
 from typing import Optional
 from .current_user import CurrentUser
 from core.dtos import UserDto
@@ -21,6 +21,13 @@ class UserSession:
         Returns:
             CurrentUser: 返回当前用户信息结构体
         """
+        if not has_request_context():
+            return CurrentUser(
+                user_id=None,
+                username=None,
+                item_range=None,
+                specialty=None
+            )
         return CurrentUser(
             user_id=session.get('user_id'),
             username=session.get('username'),

+ 28 - 0
SourceCode/IntelligentRailwayCosting/app/executor/__init__.py

@@ -0,0 +1,28 @@
+from .collector import Collector
+from .processor import Processor
+from .sender import Sender
+from core.dtos import ProjectTaskDto, ProjectQuotaDto
+
+
+def collect_task(task:ProjectTaskDto):
+    return Collector().collect(task)
+
+def process_task(task:ProjectTaskDto):
+    return Processor().process(task)
+
+def send_task(task:ProjectTaskDto):
+    return Sender().send(task)
+
+def process_quota(quota:ProjectQuotaDto):
+    return Processor().process_quota(quota)
+
+def send_quota(quota:ProjectQuotaDto):
+    return Sender().send_quota(quota)
+
+__all__=[
+    'collect_task',
+    'process_task',
+    'send_task',
+    'process_quota',
+    'send_quota'
+ ]

+ 16 - 0
SourceCode/IntelligentRailwayCosting/app/executor/collector.py

@@ -0,0 +1,16 @@
+import tools.utils as utils
+from core.dtos import ProjectTaskDto
+
+
+class Collector:
+    def __init__(self):
+        self._logger = utils.get_logger()
+
+    def collect(self,task:ProjectTaskDto):
+        try:
+            self._logger.info(f"开始采集任务:{task.task_name}")
+            self._logger.info(f"采集任务:{task.task_name}完成")
+            return None
+        except Exception as e:
+            self._logger.error(f"采集任务:{task.task_name}失败,原因:{e}")
+            return f"采集失败,原因:{e}"

+ 43 - 0
SourceCode/IntelligentRailwayCosting/app/executor/processor.py

@@ -0,0 +1,43 @@
+
+import tools.utils as utils
+from core.dtos import ProjectTaskDto, ProjectQuotaDto
+from stores import ProjectQuotaStore
+
+class Processor:
+    def __init__(self):
+        self._logger = utils.get_logger()
+        self._quota_store = ProjectQuotaStore()
+
+    def process(self,task:ProjectTaskDto):
+        try:
+            self._logger.info(f"开始处理任务:{task.task_name}")
+            data_list = self._quota_store.get_quotas_by_task_id(task.id)
+            error_count = 0
+            for quota in data_list:
+                quota_dto = ProjectQuotaDto.from_model(quota)
+                msg = self.process_quota(quota_dto)
+                if msg:
+                    error_count += 1
+                    continue
+            self._logger.info(f"处理任务:{task.task_name}完成,{error_count}项错误/共{len(data_list)}项")
+            return None
+        except Exception as e:
+            self._logger.error(f"处理任务:{task.task_name}失败,原因:{e}")
+            return f"处理失败,原因:{e}"
+
+    def process_quota(self,quota:ProjectQuotaDto):
+        try:
+            self._logger.info(f"开始处理定额:{quota.id}")
+            self._quota_store.update_process_status(quota.id,1)
+
+            self._quota_store.update_process_status(quota.id,2)
+            self._logger.info(f"处理定额:{quota.id}完成")
+            return None
+        except Exception as e:
+            msg = f"处理失败,原因:{e}"
+            self._logger.error(f"处理定额:{quota.id},{msg}")
+            self._quota_store.update_process_status(quota.id,3, msg)
+            return msg
+
+    def _call_ai(self):
+        pass

+ 40 - 0
SourceCode/IntelligentRailwayCosting/app/executor/sender.py

@@ -0,0 +1,40 @@
+import tools.utils as utils
+from core.dtos import ProjectTaskDto, ProjectQuotaDto
+from stores import ProjectQuotaStore
+
+
+class Sender:
+    def __init__(self):
+        self._logger = utils.get_logger()
+        self._quota_store = ProjectQuotaStore()
+
+    def send(self,task:ProjectTaskDto):
+        try:
+            self._logger.info(f"开始发送任务:{task.task_name}")
+            error_count = 0
+            data_list = self._quota_store.get_quotas_by_task_id(task.id,True)
+            for data in data_list:
+                msg = self._quota_store.update_send_status(data.id,1)
+                if msg:
+                    error_count+=1
+                    continue
+            self._logger.info(f"发送任务:{task.task_name}完成,{error_count}项错误/共{len(data_list)}项")
+            return None
+        except Exception as e:
+            self._logger.error(f"发送任务:{task.task_name}失败,原因:{e}")
+            return f"发送失败,原因:{e}"
+
+
+    def send_quota(self,quota:ProjectQuotaDto):
+        try:
+            self._logger.info(f"开始发送定额:{quota.id}")
+            self._quota_store.update_send_status(quota.id, 1)
+
+            self._quota_store.update_send_status(quota.id, 2)
+            self._logger.info(f"发送定额:{quota.id}完成")
+            return None
+        except Exception as e:
+            msg = f"发送失败,原因:{e}"
+            self._logger.error(f"发送定额:{quota.id},{msg}")
+            self._quota_store.update_process_status(quota.id, 3, msg)
+            return msg

+ 58 - 4
SourceCode/IntelligentRailwayCosting/app/routes/project_quota.py

@@ -1,5 +1,6 @@
 from flask import Blueprint, request
 
+from core.dtos import ProjectQuotaDto
 from core.user_session import Permission
 from core.api import  ResponseBase,TableResponse
 from services import  ProjectQuotaService
@@ -7,9 +8,9 @@ from services import  ProjectQuotaService
 project_quota_api = Blueprint('project_quota_api', __name__)
 quota_service = ProjectQuotaService()
 
-@project_quota_api.route('/list/<int:budget_id>/<project_id>/<int:item_id>', methods=['GET'])
+@project_quota_api.route('/list/<int:budget_id>/<project_id>/<item_code>', methods=['POST'])
 @Permission.authorize
-def get_page_list(budget_id:int,project_id:str,item_id:int):
+def get_page_list(budget_id:int,project_id:str,item_code:str):
     try:
         data = request.get_json()
         page = int(data.get('pageNum', 1))
@@ -17,9 +18,62 @@ def get_page_list(budget_id:int,project_id:str,item_id:int):
         keyword = data.get('keyword')
         process_status = int(data.get('process_status')) if data.get('process_status') else None
         send_status = int(data.get('send_status')) if data.get('send_status') else None
-        data,count = quota_service.get_quotas(budget_id, project_id, item_id, page, per_page, keyword, process_status, send_status)
+        data,count = quota_service.get_quotas_paginated(budget_id, project_id, item_code, page, per_page, keyword, process_status, send_status)
         return TableResponse.success(data,count)
     except Exception as e:
-        return ResponseBase.error(f'获取项目失败:{str(e)}')
+        return ResponseBase.error(f'获取定额条目列表失败:{str(e)}')
+
+@project_quota_api.route('/get/<int:quota_id>', methods=['POST'])
+@Permission.authorize
+def get_quota(quota_id:int):
+    try:
+        data = quota_service.get_quota_dto(quota_id)
+        return ResponseBase.success(data)
+    except Exception as e:
+        return ResponseBase.error(f'获取定额条目失败:{str(e)}')
+
+@project_quota_api.route('/save/<int:quota_id>', methods=['POST'])
+@Permission.authorize
+def save_quota(quota_id:int):
+    try:
+        data = request.get_json()
+        quota_dto = ProjectQuotaDto(**data)
+        quota_dto.id = quota_id
+        need_process = data.get('need_process',False)
+        quota_dto = quota_service.save_quota(quota_dto,need_process)
+        return ResponseBase.success(quota_dto)
+    except Exception as e:
+        return ResponseBase.error(f'保存定额条目失败:{str(e)}')
+
+@project_quota_api.route('/delete/<int:quota_id>', methods=['POST'])
+@Permission.authorize
+def delete_quota(quota_id:int):
+    try:
+        quota_service.delete_quota(quota_id)
+        return ResponseBase.success()
+    except Exception as e:
+        return ResponseBase.error(f'删除定额条目失败:{str(e)}')
+
+@project_quota_api.route('/start_process/<int:quota_id>', methods=['POST'])
+@Permission.authorize
+def start_process(quota_id:int):
+    try:
+        msg = quota_service.start_process(quota_id)
+        if msg:
+            return ResponseBase.error(msg)
+        return ResponseBase.success()
+    except Exception as e:
+        return ResponseBase.error(f'启动定额条目失败:{str(e)}')
+
+@project_quota_api.route('/start_send/<int:quota_id>', methods=['POST'])
+@Permission.authorize
+def start_send(quota_id:int):
+    try:
+        msg = quota_service.start_send(quota_id)
+        if msg:
+            return ResponseBase.error(msg)
+        return ResponseBase.success()
+    except Exception as e:
+        return ResponseBase.error(f'启动定额条目失败:{str(e)}')
 
 

+ 102 - 14
SourceCode/IntelligentRailwayCosting/app/routes/project_task.py

@@ -1,5 +1,5 @@
-from flask import Blueprint, request
-
+from flask import Blueprint, request,send_from_directory
+import os
 from core.dtos import ProjectTaskDto
 from core.user_session import Permission
 from core.api import  ResponseBase,TableResponse
@@ -8,9 +8,9 @@ from services import  ProjectTaskService
 project_task_api = Blueprint('project_task_api', __name__)
 task_service = ProjectTaskService()
 
-@project_task_api.route('/list/<int:budget_id>/<project_id>/<int:item_id>', methods=['POST'])
+@project_task_api.route('/list/<int:budget_id>/<project_id>/<item_code>', methods=['POST'])
 @Permission.authorize
-def get_page_list(budget_id:int,project_id:str,item_id:int):
+def get_page_list(budget_id:int,project_id:str,item_code:str):
     try:
         data = request.get_json()
         page = int(data.get('pageNum', 1))
@@ -19,13 +19,14 @@ def get_page_list(budget_id:int,project_id:str,item_id:int):
         collect_status = int(data.get('collect_status')) if data.get('collect_status') else None
         process_status = int(data.get('process_status')) if data.get('process_status') else None
         send_status = int(data.get('send_status')) if data.get('send_status') else None
-        task, total_count = task_service.get_tasks_paginated(budget_id, project_id, item_id, page, per_page, keyword, collect_status, process_status, send_status)
+        task, total_count = task_service.get_tasks_paginated(budget_id, project_id, item_code, page, per_page, keyword, collect_status, process_status, send_status)
         return TableResponse.success(task, total_count)
     except Exception as e:
-        return ResponseBase.error(f'获取项目失败:{str(e)}')
+        return ResponseBase.error(f'获取任务列表失败:{str(e)}')
 
 @project_task_api.route('/get/<int:task_id>', methods=['POST'])
-def get(task_id:int):
+@Permission.authorize
+def get_task(task_id:int):
     try:
         task = task_service.get_task_dto(task_id)
         return ResponseBase.success(task.to_dict())
@@ -33,14 +34,101 @@ def get(task_id:int):
         return ResponseBase.error(f'获取项目失败:{str(e)}')
 
 @project_task_api.route('/save/<int:task_id>', methods=['POST'])
+@Permission.authorize
 def save_task(task_id:int):
     try:
-        data = request.get_json()
-        task_dto = ProjectTaskDto.from_dict(data)
-        delete_old = data.get('delete_old', False)
-        files= request.files.getlist('files')
-        files = files if files else []
-        task = task_service.save_task(task_id, task_dto, files, delete_old)
+        # 从请求中获取表单数据
+        form_data = request.form.to_dict()
+        budget_id = int(form_data.get('budget_id')) if form_data.get('budget_id') else None
+        item_id = int(form_data.get('item_id')) if form_data.get('item_id') else None
+        project_id = form_data.get('project_id')
+        item_code = form_data.get('item_code')
+        task_name = form_data.get('task_name')
+        task_desc = form_data.get('task_desc')
+        delete_file = form_data.get('delete_file', 'false').lower() == 'true'
+        # 获取上传的文件
+        files = request.files.getlist('files')
+        # 验证必要参数
+        if not all([budget_id, project_id, task_name]):
+            return ResponseBase.error('缺少必要参数:budget_id、project_id、task_name')
+        # 构建任务DTO
+        task_dto = ProjectTaskDto(
+            budget_id=budget_id,
+            item_id=item_id,
+            project_id=project_id,
+            item_code=item_code,
+            task_name=task_name,
+            task_desc=task_desc,
+            file_path=None
+        )
+        
+        # 保存任务
+        task = task_service.save_task(task_id, task_dto, files, delete_file)
         return ResponseBase.success(task.to_dict())
+    except ValueError as ve:
+        return ResponseBase.error(f'参数格式错误:{str(ve)}')
+    except Exception as e:
+        return ResponseBase.error(f'保存任务失败:{str(e)}')
+
+@project_task_api.route('/delete/<int:task_id>', methods=['POST'])
+@Permission.authorize
+def delete_task(task_id:int):
+    try:
+        task = task_service.delete_task(task_id)
+        return ResponseBase.success(task)
+    except Exception as e:
+        return ResponseBase.error(f'删除任务失败:{str(e)}')
+
+@project_task_api.route('/download', methods=['POST'])
+@Permission.authorize
+def download_file():
+    filename = request.args.get('filename', type=str)
+    if not filename:
+        return ResponseBase.error('文件名不能为空')
+    try:
+        # 安全处理文件名
+        pure_filename = os.path.basename(filename)
+        safe_filename = os.path.basename(pure_filename)
+        path = filename.replace(safe_filename, '')
+        upload_folder = os.path.abspath(os.path.join(os.getcwd(), path))
+        if not os.path.exists(upload_folder):
+            return ResponseBase.error('项目目录不存在')
+        full_path = os.path.join(upload_folder, safe_filename)
+        if not os.path.exists(full_path):
+            return ResponseBase.error('文件不存在')
+    except Exception as e:
+        return ResponseBase.error(f'非法文件路径{str(e)}')
+    return send_from_directory(upload_folder.replace('\\', '/'), safe_filename, as_attachment=True)
+
+@project_task_api.route('/start_collect/<int:task_id>', methods=['POST'])
+@Permission.authorize
+def start_collect(task_id:int):
+    try:
+        msg = task_service.start_collect(task_id)
+        if msg:
+            return ResponseBase.error(msg)
+        return ResponseBase.success('启动采集成功')
+    except Exception as e:
+        return ResponseBase.error(f'启动采集失败:{str(e)}')
+
+@project_task_api.route('/start_process/<int:task_id>', methods=['POST'])
+@Permission.authorize
+def start_process(task_id:int):
+    try:
+        msg = task_service.start_process(task_id)
+        if msg:
+            return ResponseBase.error(msg)
+        return ResponseBase.success('启动处理成功')
+    except Exception as e:
+        return ResponseBase.error(f'启动处理失败:{str(e)}')
+
+@project_task_api.route('/start_send/<int:task_id>', methods=['POST'])
+@Permission.authorize
+def start_send(task_id:int):
+    try:
+        msg = task_service.start_send(task_id)
+        if msg:
+            return ResponseBase.error(msg)
+        return ResponseBase.success('启动发送成功')
     except Exception as e:
-        return ResponseBase.error(f'保存项目失败:{str(e)}')
+        return ResponseBase.error(f'启动发送失败:{str(e)}')

+ 1 - 1
SourceCode/IntelligentRailwayCosting/app/services/log.py

@@ -20,7 +20,7 @@ class LogService:
                 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)
+        data =  self.log_store.query_logs_paginated(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)
 
 

+ 10 - 3
SourceCode/IntelligentRailwayCosting/app/services/project.py

@@ -4,6 +4,7 @@ from datetime import datetime, timedelta
 from core.dtos import TotalBudgetInfoDto, TotalBudgetItemDto
 from core.dtos.project import ProjectDto
 from core.dtos.tree import TreeDto
+from core.user_session import UserSession
 from stores import ProjectStore,BudgetStore
 from tools import db_helper
 
@@ -31,7 +32,7 @@ class ProjectService:
         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)
+        data =  self._project_store.get_user_projects_paginated(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):
@@ -61,7 +62,13 @@ class ProjectService:
         budget_store = BudgetStore(db_session)
         data_list = []
         if not item_code:
-            items =  budget_store.get_top_budget_items(budget_id)
+            team_item_code = None
+            current_user = UserSession.get_current_user()
+            if not current_user.is_admin:
+                team_item_code_str = self._project_store.get_team_project_item_code(project_id,current_user.username)
+                if team_item_code_str:
+                    team_item_code = None if team_item_code_str == 'None' or team_item_code_str == '0' or team_item_code_str == ''  else team_item_code_str.split(',')
+            items =  budget_store.get_top_budget_items(budget_id,team_item_code)
         else:
             items = budget_store.get_child_budget_items(budget_id,item_code)
         parent = "#"
@@ -69,7 +76,7 @@ class ProjectService:
             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)
+            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,""
 

+ 79 - 17
SourceCode/IntelligentRailwayCosting/app/services/project_quota.py

@@ -1,22 +1,25 @@
 from typing import Optional
-from datetime import datetime
 
+import tools.utils as utils,threading
 from core.dtos import ProjectQuotaDto
+from core.models import ProjectQuotaModel
 from stores.project_quota import ProjectQuotaStore
+import executor
 
 class ProjectQuotaService:
     def __init__(self):
         self.store = ProjectQuotaStore()
+        self._logger = utils.get_logger()
 
-    def get_quotas(self, build_id: int, project_id: str, item_id: int, page: int = 1, page_size: int = 10,
-                   keyword: Optional[str] = None, process_status: Optional[int] = None,
-                   send_status: Optional[int] = None):
+    def get_quotas_paginated(self, budget_id: int, project_id: str, item_code: str, page: int = 1, page_size: int = 10,
+                             keyword: Optional[str] = None, process_status: Optional[int] = None,
+                             send_status: Optional[int] = None):
         """获取项目定额列表
 
         Args:
-            build_id: 概算序号
+            budget_id: 概算序号
             project_id: 项目编号
-            item_id: 条目序
+            item_code: 条目编
             page: 页码
             page_size: 每页数量
             keyword: 关键字
@@ -27,10 +30,10 @@ class ProjectQuotaService:
             dict: 包含总数和定额列表的字典
         """
         try:
-            data = self.store.get_quotas(
-                build_id=build_id,
+            data = self.store.get_quotas_paginated(
+                budget_id=budget_id,
                 project_id=project_id,
-                item_id=item_id,
+                item_code=item_code,
                 page=page,
                 page_size=page_size,
                 keyword=keyword,
@@ -44,7 +47,13 @@ class ProjectQuotaService:
             print(f"获取项目定额列表失败: {str(e)}")
             raise
 
-    def get_quota(self, quota_id: int) -> Optional[ProjectQuotaDto]:
+    def get_quota_dto(self, quota_id: int):
+        try:
+            return self.store.get_quota_dto(quota_id)
+        except Exception as e:
+            self._logger.error(f"获取定额条目DTO失败: {str(e)}")
+            raise
+    def get_quota(self, quota_id: int) -> Optional[ProjectQuotaModel]:
         """获取单个项目定额
 
         Args:
@@ -56,7 +65,22 @@ class ProjectQuotaService:
         try:
             return self.store.get_quota(quota_id)
         except Exception as e:
-            print(f"获取项目定额失败: {str(e)}")
+            self._logger.error(f"获取定额条目失败: {str(e)}")
+            raise
+
+    def save_quota(self, quota_dto: ProjectQuotaDto,need_process:bool=False) -> Optional[ProjectQuotaDto]:
+        """保存定额"""
+        try:
+            # 业务验证
+            if  quota_dto.id == 0:
+                quota_dto = self.create_quota(quota_dto)
+            else:
+                quota_dto = self.update_quota(quota_dto)
+            if need_process:
+                self.start_process(quota_dto.id)
+            return quota_dto
+        except Exception as e:
+            self._logger.error(f"保存定额条目失败: {str(e)}")
             raise
 
     def create_quota(self, quota_dto: ProjectQuotaDto) -> ProjectQuotaDto:
@@ -70,12 +94,12 @@ class ProjectQuotaService:
         """
         try:
             # 业务验证
-            if not quota_dto.project_id or not quota_dto.build_id:
+            if not quota_dto.project_id or not quota_dto.budget_id:
                 raise ValueError("项目编号和概算序号不能为空")
 
             return self.store.create_quota(quota_dto)
         except Exception as e:
-            print(f"创建项目定额失败: {str(e)}")
+            self._logger.error(f"创建项目定额失败: {str(e)}")
             raise
 
     def update_quota(self, quota_dto: ProjectQuotaDto) -> Optional[ProjectQuotaDto]:
@@ -94,7 +118,7 @@ class ProjectQuotaService:
 
             return self.store.update_quota(quota_dto)
         except Exception as e:
-            print(f"更新项目定额失败: {str(e)}")
+            self._logger.error(f"更新项目定额失败: {str(e)}")
             raise
 
     def delete_quota(self, quota_id: int) -> bool:
@@ -109,7 +133,7 @@ class ProjectQuotaService:
         try:
             return self.store.delete_quota(quota_id)
         except Exception as e:
-            print(f"删除项目定额失败: {str(e)}")
+            self._logger.error(f"删除项目定额失败: {str(e)}")
             raise
 
     def update_process_status(self, quota_id: int, status: int, err: str = None) -> bool:
@@ -126,7 +150,7 @@ class ProjectQuotaService:
         try:
             return self.store.update_process_status(quota_id, status, err)
         except Exception as e:
-            print(f"更新项目定额处理状态失败: {str(e)}")
+            self._logger.error(f"更新项目定额处理状态失败: {str(e)}")
             raise
 
     def update_send_status(self, quota_id: int, status: int, err: str = None) -> bool:
@@ -143,5 +167,43 @@ class ProjectQuotaService:
         try:
             return self.store.update_send_status(quota_id, status, err)
         except Exception as e:
-            print(f"更新项目定额发送状态失败: {str(e)}")
+            self._logger.error(f"更新项目定额发送状态失败: {str(e)}")
+            raise
+
+    def start_process(self, quota_id: int) -> Optional[str]:
+        """启动处理"""
+        quota = self.get_quota_dto(quota_id)
+        if quota:
+            self.update_process_status(quota_id, 1)
+            thread = threading.Thread(target=self._process_quota, args=(quota,))
+            thread.start()
+        else:
+            return "定额条目没有查询到"
+
+    def _process_quota(self, quota: ProjectQuotaDto):
+        try:
+           msg = executor.process_quota(quota)
+           if not msg:
+               self.start_send(quota.quota_id)
+        except Exception as e:
+            self._logger.error(f"处理定额条目失败: {str(e)}")
+            self.update_process_status(quota.quota_id, 3, str(e))
+            raise
+
+    def start_send(self, quota_id: int) -> Optional[str]:
+        """启动发送"""
+        quota = self.get_quota_dto(quota_id)
+        if quota:
+            self.update_send_status(quota_id, 1)
+            thread = threading.Thread(target=self._send_quota, args=(quota,))
+            thread.start()
+        else:
+            return "定额条目没有查询到"
+
+    def _send_quota(self, quota: ProjectQuotaDto):
+        try:
+            executor.send_quota(quota)
+        except Exception as e:
+            self._logger.error(f"发送定额条目失败: {str(e)}")
+            self.update_send_status(quota.quota_id, 3, str(e))
             raise

+ 97 - 20
SourceCode/IntelligentRailwayCosting/app/services/project_task.py

@@ -1,25 +1,27 @@
 from typing import Optional
 
-import tools.utils as utils, os
+import tools.utils as utils, os, threading
 from core.log.log_record import LogRecordHelper
 from core.enum import OperationModule,OperationType
 from core.dtos import ProjectTaskDto
+from core.models import ProjectTaskModel
 from stores.project_task import ProjectTaskStore
+import executor
 
 class ProjectTaskService:
     def __init__(self):
         self.store = ProjectTaskStore()
         self._logger = utils.get_logger()
 
-    def get_tasks_paginated(self, build_id: int, project_id: str, item_id: int, page: int = 1, page_size: int = 10,
+    def get_tasks_paginated(self, budget_id: int, project_id: str, item_code: str, page: int = 1, page_size: int = 10,
                             keyword: Optional[str] = None, collect_status: Optional[int] = None,
                             process_status: Optional[int] = None, send_status: Optional[int] = None):
         """获取项目任务列表
 
         Args:
             project_id: 项目编号
-            build_id: 概算序号
-            item_id: 条目序
+            budget_id: 概算序号
+            item_code: 条目编
             page: 页码
             page_size: 每页数量
             keyword: 关键字
@@ -31,10 +33,10 @@ class ProjectTaskService:
             dict: 包含总数和任务列表的字典
         """
         try:
-            data =  self.store.get_tasks(
-                build_id=build_id,
+            data =  self.store.get_tasks_paginated(
+                budget_id=budget_id,
                 project_id=project_id,
-                item_id=item_id,
+                item_code=item_code,
                 page=page,
                 page_size=page_size,
                 keyword=keyword,
@@ -44,30 +46,30 @@ class ProjectTaskService:
             )
             return [ProjectTaskDto.from_model(task).to_dict() for task in data.get('data',[])],data.get('total',0)
         except Exception as e:
-            print(f"获取项目任务列表失败: {str(e)}")
+            self._logger.error(f"获取项目任务列表失败: {str(e)}")
             raise
 
-    def get_task(self, task_id: int) -> Optional[ProjectTaskDto]:
+    def get_task(self, task_id: int) -> Optional[ProjectTaskModel]:
         """获取单个项目任务
 
         Args:
             task_id: 任务ID
 
         Returns:
-            Optional[ProjectTaskDto]: 项目任务DTO对象
+            Optional[ProjectTaskModel]: 项目任务
         """
         try:
             task = self.store.get_task(task_id)
             return task
         except Exception as e:
-            print(f"获取项目任务失败: {str(e)}")
+            self._logger.error(f"获取项目任务失败: {str(e)}")
             raise
     def get_task_dto(self, task_id: int):
         try:
             task = self.store.get_task_dto(task_id)
             return task
         except Exception as e:
-            print(f"获取项目任务失败: {str(e)}")
+            self._logger.error(f"获取项目任务失败: {str(e)}")
             raise
 
     def save_task(self, task_id: int, task_dto: ProjectTaskDto, files:list, delete_old: bool = False):
@@ -115,7 +117,7 @@ class ProjectTaskService:
     def _process_file_upload(self, task: ProjectTaskDto, files: list, delete_old: bool) -> str:
         """处理文件上传流程"""
         base_path = utils.get_config_value('file.source_path', './temp_files')
-        task_dir = os.path.join(base_path, f'{task.get_path()}')
+        task_dir = os.path.join(base_path, f'upload_files/{task.get_path()}')
         os.makedirs(task_dir, exist_ok=True)
         self._logger.info(f"保存处理文件,项目ID:{task.project_id},任务ID:{task.id}")
         if delete_old:
@@ -165,14 +167,14 @@ class ProjectTaskService:
         """
         try:
             # 业务验证
-            if not task_dto.project_id or not task_dto.build_id:
+            if not task_dto.project_id or not task_dto.budget_id:
                 raise ValueError("项目编号和概算序号不能为空")
             if not task_dto.task_name:
                 raise ValueError("任务名称不能为空")
 
             return self.store.create_task(task_dto)
         except Exception as e:
-            print(f"创建项目任务失败: {str(e)}")
+            self._logger.error(f"创建项目任务失败: {str(e)}")
             raise
 
     def _update_task(self, task_dto: ProjectTaskDto) -> Optional[ProjectTaskDto]:
@@ -193,7 +195,7 @@ class ProjectTaskService:
 
             return self.store.update_task(task_dto)
         except Exception as e:
-            print(f"更新项目任务失败: {str(e)}")
+            self._logger.error(f"更新项目任务失败: {str(e)}")
             raise
 
     def delete_task(self, task_id: int) -> bool:
@@ -208,7 +210,7 @@ class ProjectTaskService:
         try:
             return self.store.delete_task(task_id)
         except Exception as e:
-            print(f"删除项目任务失败: {str(e)}")
+            self._logger.error(f"删除项目任务失败: {str(e)}")
             raise
 
     def update_collect_status(self, task_id: int, status: int, err: str = None) -> bool:
@@ -225,7 +227,7 @@ class ProjectTaskService:
         try:
             return self.store.update_collect_status(task_id, status, err)
         except Exception as e:
-            print(f"更新项目任务采集状态失败: {str(e)}")
+            self._logger.error(f"更新项目任务采集状态失败: {str(e)}")
             raise
 
     def update_process_status(self, task_id: int, status: int, err: str = None) -> bool:
@@ -242,7 +244,7 @@ class ProjectTaskService:
         try:
             return self.store.update_process_status(task_id, status, err)
         except Exception as e:
-            print(f"更新项目任务处理状态失败: {str(e)}")
+            self._logger.error(f"更新项目任务处理状态失败: {str(e)}")
             raise
 
     def update_send_status(self, task_id: int, status: int, err: str = None) -> bool:
@@ -259,5 +261,80 @@ class ProjectTaskService:
         try:
             return self.store.update_send_status(task_id, status, err)
         except Exception as e:
-            print(f"更新项目任务发送状态失败: {str(e)}")
+            self._logger.error(f"更新项目任务发送状态失败: {str(e)}")
+            raise
+
+    def start_collect(self,task_id:int):
+        task = self.store.get_task_dto(task_id)
+        if task:
+            if not task.file_path or  task.file_path.strip() == '':
+                return '没有上传文件'
+            if task.collect_status == 1:
+                return  '正在采集中'
+            self.update_collect_status(task_id,1)
+            thread = threading.Thread(target=self._collect_task, args=(task,))
+            thread.start()
+            return None
+        else:
+            return '没有查询到任务'
+
+    def _collect_task(self,task:ProjectTaskDto):
+        try:
+            msg = executor.collect_task(task)
+            if msg:
+                self.update_collect_status(task.id,3,msg)
+            else:
+                self.update_collect_status(task.id,2)
+                self.start_process(task.id)
+        except Exception as e:
+            self._logger.error(f"采集项目任务失败: {str(e)}")
+            raise
+
+    def start_process(self,task_id:int):
+        task = self.store.get_task_dto(task_id)
+        if task:
+            if task.collect_status != 2:
+                return '还未采集完成'
+            if task.process_status == 1:
+                return  '正在处理中'
+            self.update_process_status(task_id,1)
+            thread = threading.Thread(target=self._process_task, args=(task,))
+            thread.start()
+            return None
+        else:
+            return '没有查询到任务'
+
+    def _process_task(self,task:ProjectTaskDto):
+        try:
+           msg = executor.process_task(task)
+           if msg:
+                self.update_process_status(task.id,3,msg)
+           else:
+                self.update_process_status(task.id,2)
+                self.start_send(task.id)
+        except Exception as e:
+            self._logger.error(f"处理项目任务失败: {str(e)}")
+            raise
+    def start_send(self,task_id:int):
+        task = self.store.get_task_dto(task_id)
+        if task:
+            if task.process_status != 2:
+                return '还未处理完成'
+            if task.send_status == 1:
+                return  '正在发送中'
+            self.update_send_status(task_id,1)
+            thread = threading.Thread(target=self._send_task, args=(task,))
+            thread.start()
+            return None
+        else:
+            return '没有查询到任务'
+    def _send_task(self,task:ProjectTaskDto):
+        try:
+            msg = executor.send_task(task)
+            if msg:
+                self.update_send_status(task.id,3,msg)
+            else:
+                self.update_send_status(task.id,2)
+        except Exception as e:
+            self._logger.error(f"发送项目任务失败: {str(e)}")
             raise

+ 5 - 1
SourceCode/IntelligentRailwayCosting/app/stores/__init__.py

@@ -1,4 +1,8 @@
-from .user import UserStore
 from .log import LogStore
+from .project_quota import ProjectQuotaStore
+from .project_task import ProjectTaskStore
+
+from .user import UserStore
 from .project import ProjectStore
 from .budget import BudgetStore
+

+ 8 - 5
SourceCode/IntelligentRailwayCosting/app/stores/budget.py

@@ -69,11 +69,15 @@ class BudgetStore:
         .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):
+    def get_top_budget_items(self, budget_id: str, item_code: list[str]=None):
         query = self._build_budget_items_query(budget_id)
-        query = query.filter(ChapterModel.item_code.like('__'))\
-            .filter(ChapterModel.chapter.is_not(None))\
-            .order_by(ChapterModel.item_code)
+
+        if item_code:
+            query = query.filter(ChapterModel.item_code.in_(item_code))
+        else:
+            query = query.filter(ChapterModel.item_code.like('__'))\
+                .filter(ChapterModel.chapter.is_not(None))
+        query = query.order_by(ChapterModel.item_code)
         items = query.all()
         return items
 
@@ -91,7 +95,6 @@ class BudgetStore:
         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,

+ 1 - 1
SourceCode/IntelligentRailwayCosting/app/stores/log.py

@@ -10,7 +10,7 @@ class LogStore:
     def __init__(self, db_session: Session=None):
         self.db_session = db_session or db_helper.create_mysql_session()
 
-    def query_logs(
+    def query_logs_paginated(
         self,
         page: int = 1,
         page_size: int = 10,

+ 6 - 1
SourceCode/IntelligentRailwayCosting/app/stores/project.py

@@ -13,7 +13,7 @@ class ProjectStore:
     def __init__(self, db_session: Session = None):
         self.db_session = db_session or db_helper.create_sqlServer_session()
 
-    def get_user_projects(
+    def get_user_projects_paginated(
         self,
         page: int = 1,
         page_size: int = 10,
@@ -95,6 +95,11 @@ class ProjectStore:
             'data': projects
         }
 
+    def get_team_project_item_code(self,project_id:str, user_name:str):
+        data = self.db_session.query(TeamModel.item_code).filter(and_(TeamModel.project_id == project_id,TeamModel.name == user_name)).first()
+        return data[0] if data else None
+
+
     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()

+ 22 - 8
SourceCode/IntelligentRailwayCosting/app/stores/project_quota.py

@@ -19,11 +19,11 @@ class ProjectQuotaStore:
             self._current_user = UserSession.get_current_user()
         return self._current_user
 
-    def get_quotas(
+    def get_quotas_paginated(
         self,
-        build_id: int,
+        budget_id: int,
         project_id: str,
-        item_id: int,
+        item_code: str,
         page: int = 1,
         page_size: int = 10,
         keyword: Optional[str] = None,
@@ -36,8 +36,8 @@ class ProjectQuotaStore:
             page: 页码,从1开始
             page_size: 每页数量
             project_id: 项目编号
-            build_id: 概算序号
-            item_id: 条目序号
+            budget_id: 概算序号
+            item_code: 条目序号
             keyword: 关键字
             process_status: 处理状态
             send_status: 发送状态
@@ -51,8 +51,8 @@ class ProjectQuotaStore:
         conditions = [
             ProjectQuotaModel.is_del == 0,
             ProjectQuotaModel.project_id == project_id,
-            ProjectQuotaModel.build_id == build_id,
-            ProjectQuotaModel.item_id == item_id
+            ProjectQuotaModel.budget_id == budget_id,
+            ProjectQuotaModel.item_code.like(f"{item_code}%")
         ]
         if keyword:
             conditions.append(or_(
@@ -79,6 +79,20 @@ class ProjectQuotaStore:
             'data': quotas
         }
 
+    def get_quotas_by_task_id(self,task_id:int, with_quota_code:bool=False):
+        query = self.db_session.query(ProjectQuotaModel).filter(
+            and_(
+                ProjectQuotaModel.task_id == task_id,
+                ProjectQuotaModel.is_del == 0
+            )
+        )
+        if with_quota_code:
+            query = query.filter(and_(ProjectQuotaModel.quota_code!=None,ProjectQuotaModel.quota_code!='') )
+        quotas = query.all()
+        return quotas
+
+
+
     def get_quota(self, quota_id: int) -> Optional[ProjectQuotaModel]:
         """根据ID查询定额
 
@@ -119,7 +133,7 @@ class ProjectQuotaStore:
         """
         quota = ProjectQuotaModel(
             project_id=quota_dto.project_id,
-            build_id=quota_dto.build_id,
+            budget_id=quota_dto.budget_id,
             item_id=quota_dto.item_id,
             item_code=quota_dto.item_code,
             quota_code=quota_dto.quota_code,

+ 46 - 39
SourceCode/IntelligentRailwayCosting/app/stores/project_task.py

@@ -20,11 +20,11 @@ class ProjectTaskStore:
             self._current_user = UserSession.get_current_user()
         return self._current_user
 
-    def get_tasks(
+    def get_tasks_paginated(
         self,
-        build_id: int,
+        budget_id: int,
         project_id: str,
-        item_id: int,
+        item_code: str,
         page: int = 1,
         page_size: int = 10,
         keyword: Optional[str] = None,
@@ -38,8 +38,8 @@ class ProjectTaskStore:
             page: 页码,从1开始
             page_size: 每页数量
             project_id: 项目编号
-            build_id: 概算序号
-            item_id: 条目序
+            budget_id: 概算序号
+            item_code: 条目编
             keyword: 关键字
             collect_status: 采集状态
             process_status: 处理状态
@@ -48,38 +48,45 @@ class ProjectTaskStore:
         Returns:
 
         """
-        query = self.db_session.query(ProjectTaskModel)
-
-        # 构建查询条件
-        conditions = [
-            ProjectTaskModel.is_del == 0,
-            ProjectTaskModel.project_id == project_id,
-            ProjectTaskModel.build_id == build_id,
-            ProjectTaskModel.item_id == item_id
-        ]
-        if keyword:
-            conditions.append(ProjectTaskModel.task_name.like(f'%{keyword}%'))
-        if collect_status is not None:
-            conditions.append(ProjectTaskModel.collect_status == collect_status)
-        if process_status is not None:
-            conditions.append(ProjectTaskModel.process_status == process_status)
-        if send_status is not None:
-            conditions.append(ProjectTaskModel.send_status == send_status)
-
-        query = query.filter(and_(*conditions))
-
-        # 计算总数
-        total_count = query.count()
-
-        # 分页
-        query = query.offset((page - 1) * page_size).limit(page_size)
-
-        tasks = query.all()
-
-        return {
-            'total': total_count,
-            'data': tasks
-        }
+        try:
+            query = self.db_session.query(ProjectTaskModel)
+
+            # 构建查询条件
+            conditions = [
+                ProjectTaskModel.is_del == 0,
+                ProjectTaskModel.project_id == project_id,
+                ProjectTaskModel.budget_id == budget_id,
+                ProjectTaskModel.item_code.like(f"{item_code}%")
+            ]
+            if keyword:
+                conditions.append(ProjectTaskModel.task_name.like(f'%{keyword}%'))
+            if collect_status is not None:
+                conditions.append(ProjectTaskModel.collect_status == collect_status)
+            if process_status is not None:
+                conditions.append(ProjectTaskModel.process_status == process_status)
+            if send_status is not None:
+                conditions.append(ProjectTaskModel.send_status == send_status)
+
+            query = query.filter(and_(*conditions))
+
+            # 计算总数
+            total_count = query.count()
+
+            # 分页
+            query = query.offset((page - 1) * page_size).limit(page_size)
+
+            tasks = query.all()
+
+            return {
+                'total': total_count,
+                'data': tasks
+            }
+        except Exception as e:
+            self.db_session.rollback()
+            raise e
+        finally:
+            self.db_session.close()
+
     def get_task(self, task_id: int) -> Optional[ProjectTaskModel]:
         task = self.db_session.query(ProjectTaskModel).filter(
             and_(
@@ -114,7 +121,7 @@ class ProjectTaskStore:
             task_name=task_dto.task_name,
             task_desc=task_dto.task_desc,
             project_id=task_dto.project_id,
-            build_id=task_dto.build_id,
+            budget_id=task_dto.budget_id,
             item_id=task_dto.item_id,
             item_code=task_dto.item_code,
             file_path=task_dto.file_path,
@@ -144,7 +151,7 @@ class ProjectTaskStore:
         task.task_name = task_dto.task_name
         task.task_desc = task_dto.task_desc
         # task.project_id = task_dto.project_id
-        # task.build_id = task_dto.build_id
+        # task.budget_id = task_dto.budget_id
         # task.item_id = task_dto.item_id
         # task.item_code = task_dto.item_code
         task.file_path = task_dto.file_path

+ 11 - 2
SourceCode/IntelligentRailwayCosting/app/views/static/base/css/styles.css

@@ -54,11 +54,14 @@ body > .container, body > .container-fluid{
     border-bottom: 2px solid;
 }
 .table-box td > .btn{
-    padding: calc(.2rem + 1px) calc(.6rem + 1px)!important;
+    padding: calc(.2rem + 1px) calc(.4rem + 1px)!important;
     margin: 0 5px;
-    font-size: 12px;
     --bs-btn-border-radius: .3rem;
 }
+.table-box td > .btn-icon{
+    width: 25px!important;
+    height: 25px!important;
+}
 .table-box td > .link:hover{
     border-bottom: 2px solid;
 }
@@ -83,4 +86,10 @@ body > .container, body > .container-fluid{
     width: 100%;
     display: flex;
     justify-content: space-between;
+}
+.form-check-input,.form-check-label{
+    cursor: pointer;
+}
+.form-check-input:disabled, .form-check-input:disabled + .form-check-label{
+    cursor: not-allowed;
 }

+ 14 - 6
SourceCode/IntelligentRailwayCosting/app/views/static/base/js/utils.js

@@ -26,10 +26,10 @@ function IwbAjax(opt) {
 	)
 	fetch(opt.url, {
 		method: opt.method,
-		headers: {
+		headers:opt.headers || {
 			'Content-Type': 'application/json',
 		},
-		body: JSON.stringify(opt.data),
+		body: opt.body || JSON.stringify(opt.data),
 	})
 		.then((response) => response.json())
 		.then((data) => {
@@ -96,7 +96,10 @@ function IwbTable(table, opts,isReload) {
 				if (res.success) {
 					opt.callBeforeRender && opt.callBeforeRender($table,res.data.rows)
 					renderTable(res.data.rows, res.data.total)
-					setTimeout(() => {opt.callAfterRender && opt.callAfterRender($table,res.data.rows)},1000)
+					setTimeout(() => {
+
+						opt.callAfterRender && opt.callAfterRender($table,res.data.rows)
+					},1000)
 				} else {
 					renderTable([], 0)
 					console.error('加载表格出错:', res.message, opt.url)
@@ -146,7 +149,11 @@ function IwbTable(table, opts,isReload) {
 			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)
+			$tableBox.fadeIn(500,function (){
+				$tableBox.find(`[data-bs-toggle="tooltip"]`).each(function (){
+					new bootstrap.Tooltip(this)
+				})
+			})
 		})
 	}
 
@@ -281,8 +288,9 @@ function ChangeHeadMenu(menu) {
 }
 
 function DownloadFile(url, fileName) {
-	fetch(url)
-		.then((response) => {
+	fetch(url,{
+		method:'POST'
+	}).then((response) => {
 			if (!response.ok) {
 				return response.json().then((err) => {
 					throw new Error(err.message || '下载失败')

+ 437 - 222
SourceCode/IntelligentRailwayCosting/app/views/static/project/budget_info.js

@@ -1,65 +1,134 @@
 const table = '#table',
-	$modal = $('#modal')
-console.log(project_id)
+	$modal = $('#modal'),
+	$modalQuota = $('#modal_quota')
+console.log(`加载项目:${project_id}`)
 const nav_tab_template = `
 		<li class="nav-item" data-id="{0}">
 			<button type="button" class="nav-link {2} btn-light-primary btn-active-color-primary" data-id="{0}"  data-bs-toggle="tab" data-bs-target="#iwb_tab_{0}">{1}</button>
 		</li>`,
 	tab_content_template = `<div class="tab-pane h-100 fade" id="iwb_tab_{0}" role="tabpanel">{1}</div>`,
-	table_template = `
-<div class="d-flex flex-row h-100 project-box">
-	<div class="flex-row-auto h-100 left-box">
-		<div class="tree-dom h-100 overflow-auto" id="js-tree_{0}"></div>
-	</div>
-	<div class="flex-row-fluid right-box"> 
-		<div class="table-box table-responsive" id="table_box_{0}" style="display: none">
-			<section class="d-none">
-				<input type="hidden" name="budget_id" value="{0}">
-				<input type="hidden" name="project_id" value="">
-				<input type="hidden" name="item_id" value="">
-				<input type="hidden" name="item_code" value="">
-			</section>
-			<span class="my-3 fs-3 table-title mt-5"><span class="fw-bolder me-5 title"></span><span class="badge badge-primary">定额任务列表</span></span>
-			<div class="d-flex justify-content-between my-5">
-				<div>
-					<button type="button" class="btn btn-primary btn-sm" onclick="Add('{0}')">添加任务</button>
-				</div>
-				<form class="search-box d-flex">
-					<div class="d-flex">
-						<select class="form-select form-select-sm me-5" name="process_status">
-							<option value="">全部处理状态</option>
-							<option value="0">未处理</option>
-							<option value="1">处理中</option>
-							<option value="2">已处理</option>
-							<option value="3">处理失败</option>
-						</select>
-						<select class="form-select form-select-sm me-5" name="send_status">
-							<option value="">全部发送状态</option>
-							<option value="0">未发送</option>
-							<option value="1">发送中</option>
-							<option value="2">已发送</option>
-							<option value="3">发送失败</option>
-						</select>
-						<input type="text" class="form-control form-control-sm w-200px" placeholder="请输入关键字" name="keyword" />
-					</div>
-					<div class="btn-group ms-5">
-						<button type="button" class="btn btn-primary btn-sm" onclick="IwbTableSearch(this)">查询</button>
-						<button type="button" class="btn btn-danger btn-sm" onclick="IwbTableResetSearch(this)">重置</button>
-					</div>
-				</form>
-			</div>
-			<table class="table table-striped table-bordered table-hover  table-rounded" id="table_{0}">
-			</table>
-			<div class="pagination-row"></div>
-		</section>
-	</div>
-</div>`
-
+	table_template = `<div class="d-flex flex-row h-100 project-box">
+									<div class="flex-row-auto h-100 left-box">
+										<div class="tree-dom h-100 overflow-auto" id="js-tree_{0}"></div>
+									</div>
+									<div class="flex-row-fluid right-box">
+										<div class="table-box table-responsive" id="table_box_{0}" style="display: none">
+											<section class="d-none">
+												<input type="hidden" name="budget_id" value="{0}">
+												<input type="hidden" name="project_id" value="">
+												<input type="hidden" name="item_id" value="">
+												<input type="hidden" name="item_code" value="">
+											</section>
+											<div class="my-2  d-flex align-items-center table-title mt-5">
+												<span class="fw-bolder me-5 title fs-2"></span>
+<!--												<span class="badge badge-primary fs-2">定额任务列表</span>-->
+												<div class="form-check form-check-custom form-check-primary form-check-solid">
+													<input class="form-check-input" name="table_radio" type="radio" value="task"  id="task_radio"/>
+													<label class="form-check-label fw-bolder text-primary" for="task_radio">
+														任务列表
+													</label>
+												</div>
+												<div class="form-check form-check-custom form-check-success form-check-solid ms-5">
+													<input class="form-check-input" name="table_radio" type="radio" value="quota"  id="quota_radio"/>
+													<label class="form-check-label fw-bolder text-success" for="quota_radio">
+														定额输入
+													</label>
+												</div>
+											</div>
+											<div class="d-flex justify-content-between my-5">
+												<div class="">
+													<button type="button" id="task_add_btn" class="btn btn-primary btn-sm" onclick="Add('{0}')">添加任务</button>
+													<button type="button" id="quota_add_btn" class="btn btn-primary btn-sm" onclick="Add_Quota('{0}')">添加定额</button>
+												</div>
+											
+												<form class="search-box d-flex">
+													<div class="d-flex">
+														<select class="form-select form-select-sm me-5" name="collect_status">
+															<option value="">全部采集状态</option>
+															<option value="0">未采集</option>
+															<option value="1">采集中</option>
+															<option value="2">已采集</option>
+															<option value="3">采集失败</option>
+														</select>
+														<select class="form-select form-select-sm me-5" name="process_status">
+															<option value="">全部处理状态</option>
+															<option value="0">未处理</option>
+															<option value="1">处理中</option>
+															<option value="2">已处理</option>
+															<option value="3">处理失败</option>
+														</select>
+														<select class="form-select form-select-sm me-5" name="send_status">
+															<option value="">全部发送状态</option>
+															<option value="0">未发送</option>
+															<option value="1">发送中</option>
+															<option value="2">已发送</option>
+															<option value="3">发送失败</option>
+														</select>
+														<input type="text" class="form-control form-control-sm w-200px" placeholder="请输入关键字" name="keyword" />
+													</div>
+													<div class="btn-group ms-5">
+														<button type="button" class="btn btn-primary btn-sm" onclick="IwbTableSearch(this)">查询</button>
+														<button type="button" class="btn btn-danger btn-sm" onclick="IwbTableResetSearch(this)">重置</button>
+													</div>
+												</form>
+											</div>
+											<table class="table table-striped table-bordered table-hover  table-rounded" id="table_{0}">
+											</table>
+											<div class="pagination-row"></div>
+										</div>
+									</div>
+								</div>`
+let _fileUploadDropzone = null
 $(function () {
 	GetBudgetInfo()
-	// setTimeout(function () {GetBudgetItems(1)},1000)
+	InitFileUpload()
 })
-
+function InitFileUpload(){
+	const id = "#modal";
+	const dropzone = document.querySelector(id);
+	// set the preview element template
+	const previewNode = dropzone.querySelector(".dropzone-item");
+	previewNode.id = "";
+	const previewTemplate = previewNode.parentNode.innerHTML;
+	previewNode.parentNode.removeChild(previewNode);
+	_fileUploadDropzone = new Dropzone(id, {
+		url:"/",
+		parallelUploads: 10,
+		previewTemplate: previewTemplate,
+		autoProcessQueue: false, //关闭自动上传功能
+		maxFiles: 5,
+		maxFilesize: 10, // MB
+		previewsContainer: id + " .dropzone-items", //
+    	clickable: id + " .dropzone-select", //
+		acceptedFiles: '.csv,.xls,.xlsx',
+		accept: function(file, done) {
+			console.log('DONE', file, done)
+			if(file.type.includes('csv') || file.type.includes('.xls') || file.type.includes('sheet')){
+				done()
+			}else{
+				done('文件类型非法,只允许.csv,.xls,.xlsx文件')
+			}
+		},
+		dictInvalidFileType: '文件类型非法,只允许.csv,.xls,.xlsx文件',
+		// dictRemoveFileConfirmation: '确定移除文件吗?',
+		dictFileTooBig: '文件[{{filesize}}M]过大,最大允许{{maxFilesize}}M。',
+		dictMaxFilesExceeded: '最多只能上传5个,每个文件不超过10M',
+	});
+	_fileUploadDropzone.on("addedfile", function (file) {
+		const dropzoneItems = dropzone.querySelectorAll('.dropzone-item');
+		dropzoneItems.forEach(dropzoneItem => {
+			dropzoneItem.style.display = '';
+		});
+		if(_fileUploadDropzone.getAcceptedFiles().findIndex(item=>item.name===file.name)>=0){
+			alert('文件已存在')
+			_fileUploadDropzone.removeFile(file);
+		}
+	});
+	// _fileUploadDropzone.on("removedfile", function (file) {
+	// 	_files = _files.filter(item=>item.name!==file.name)
+	// 	console.log('removedfile', file,_files)
+	// })
+}
 function GetBudgetInfo() {
 	IwbAjax_1({
 		url: `/api/project/budget/${project_id}`,
@@ -81,11 +150,15 @@ function RenderTabs(data){
 		for (let i = 0; i < data.length; i++) {
 			const item = data[i]
 			str1 += nav_tab_template.format(item.budget_id, item.budget_code)
-			str2 += tab_content_template.format(item.budget_id, table_template.format(item.budget_id))
+			const tableStr = table_template.format(item.budget_id)
+			const tabContent = tab_content_template.format(item.budget_id, tableStr)
+			// console.log('TAB_CONTENT', tabContent)
+			str2 += tabContent
 		}
 	}
 	$('#nav_tab').html(str1)
 	const h= $('.app-main .app-container').height() - $('#nav_tab').height() - $('#breadcrumb_header').height()
+
 	$('#tab_content').html(str2).height(h)
 	const $tab = $('#nav_tab li button[data-bs-toggle="tab"]')
 	$tab.on('shown.bs.tab',(e)=>{
@@ -147,81 +220,189 @@ function GetBudgetItems(id) {
 
 function RenderTabCondent(data) {
 	// console.log('RenderTabCondent', data)
-	if(data.chapter){
-		const $tableBox = $(`#table_box_${data.budget_id}`),
+	const $tableBox = $(`#table_box_${data.budget_id}`),
 			$table = $(`#table_${data.budget_id}`)
-		$tableBox.find('.table-title .title').text(`${data.chapter}、${data.project_name}`)
-		$tableBox.show()
-		$tableBox.find('input[name="budget_id"]').val(data.budget_id);
-		$tableBox.find('input[name="project_id"]').val(project_id);
-		$tableBox.find('input[name="item_id"]').val(data.item_id);
-		$tableBox.find('input[name="item_code"]').val(data.item_code);
-
+	const title = data.chapter ?`${data.chapter}、${data.project_name}`: data.section ?`${data.section}.${data.project_name}`: data.project_name
+	$tableBox.find('input[name="budget_id"]').val(data.budget_id);
+	$tableBox.find('input[name="project_id"]').val(project_id);
+	$tableBox.find('input[name="item_id"]').val(data.item_id);
+	$tableBox.find('input[name="item_code"]').val(data.item_code);
+	$tableBox.find('[name="table_radio"]').prop("disabled",false).off('change.iwb')
+	$tableBox.find('[name="table_radio"]').on('change.iwb',function(){
+		const val = $(this).val()
+		if (val === 'quota') {
+			_quotaTable($table,data)
+		} else {
+			_taskTable($table,data)
+		}
+	})
+	if(data.children_count>0){
+		$tableBox.find('.table-title .title').text(title)
+		// $tableBox.find('.table-title .badge').text('任务列表').removeClass('badge-success').addClass('badge-primary')
+		_taskTable($table,data)
+	} else {
+		$tableBox.find('.table-title .title').text(title)
+		// $tableBox.find('.table-title .badge').text('定额输入明细').removeClass('badge-primary').addClass('badge-success')
+		$tableBox.find('#task_radio').prop("disabled",true)
+		$tableBox.find('#quota_radio').prop("checked",true)
+		_quotaTable($table,data)
+	}
+	$tableBox.show()
+	function _taskTable($table,data){
+		$tableBox.find('[name="collect_status"]').show()
+		$tableBox.find('#quota_add_btn').hide()
+		$tableBox.find('#task_add_btn').show()
 		IwbTable($table, {
-			url: `/api/task/list/${data.budget_id}/${project_id}/${data.item_id}`,
+				url: `/api/task/list/${data.budget_id}/${project_id}/${data.item_code}`,
+				columns: [
+					{
+						title: '任务编号',
+						data: 'id',
+						width: '100px',
+					},
+					{
+						title: '任务名称',
+						data: 'task_name',
+						width: '240px',
+					},
+					{
+						title: '文件数据',
+						data: 'file_data',
+						width: 'auto',
+						render: (row) => {
+							let str = ``
+							const file_paths = row.file_path ? row.file_path.split(',') : []
+							if(file_paths.length){
+								for (let i = 0; i < file_paths.length; i++) {
+									const path = file_paths[i]
+									const names = path.split('/')
+									const file_name = names[names.length - 1]
+									str += `<a href="#" onclick="DownloadFile('/api/task/download?filename=${encodeURIComponent(path)}','${file_name}')"  class="link link-info px-2">${file_name}</a>`
+								}
+							}else{
+								str ="-"
+							}
+							return str
+						}
+					},
+					{
+						title: '任务状态',
+						data: 'task_status',
+						width: '220px',
+						render: (row) => {
+							let str = ``
+							if (row.collect_status === 0) {
+								str += `<span class="badge badge-light-primary">未采集</span>`
+							} else if (row.collect_status === 1){
+								str += `<span class="badge badge-light-warning">采集中</span>`
+							} else if (row.collect_status === 2){
+								str += `<span class="badge badge-light-success">采集完成</span>`
+								if (row.process_status === 0) {
+									str += `<span class="badge badge-light-primary">未处理</span>`
+								} else if (row.process_status === 1){
+									str += `<span class="badge badge-light-warning">处理中</span>`
+								} else if (row.process_status === 2){
+									str += `<span class="badge badge-light-success">已处理</span>`
+									if (row.send_status === 0) {
+										str += `<span class="badge badge-light-primary">未发送</span>`
+									} else if (row.send_status === 1){
+										str += `<span class="badge badge-light-warning">发送中</span>`
+									} else if (row.send_status === 2){
+										str += `<span class="badge badge-light-success">已发送</span>`
+									} else if (row.send_status === 3){
+										str += `<span class="badge badge-light-danger">发送失败</span>`
+									}
+								} else if (row.process_status === 3){
+									str += `<span class="badge badge-light-danger">处理失败</span>`
+								}
+							} else if (row.collect_status === 3){
+								str += `<span class="badge badge-light-danger">采集失败</span>`
+							}
+
+							return str
+						}
+					},
+					{
+						title: '操作',
+						data: 'id',
+						width: '200px',
+						render: (row) => {
+							let str = ``
+							if (row.collect_status === 0) {
+								str += `<button type="button" class="btn btn-icon btn-sm btn-light-primary" data-bs-toggle="tooltip" data-bs-placement="top" title="开始任务" onclick="StartCollectTask(${row.id}, ${data.budget_id})"><i class="ki-duotone ki-add-notepad fs-1"><span class="path1"></span><span class="path2"></span><span class="path3"></span><span class="path4"></span></i></button>`
+							} else if (row.collect_status === 2) {
+								str += `<button type="button" class="btn btn-icon btn-sm btn-light-warning" data-bs-toggle="tooltip" data-bs-placement="top" title="重新采集" onclick="ReStartCollectTask(${row.id}, ${data.budget_id})"><i class="ki-duotone ki-add-notepad fs-1"><span class="path1"></span><span class="path2"></span><span class="path3"></span><span class="path4"></span></i></button>`
+								if (row.process_status === 0) {
+									str += `<button type="button" class="btn btn-icon btn-sm btn-light-primary" data-bs-toggle="tooltip" data-bs-placement="top" title="开始处理" onclick="StartProcessTask(${row.id}, ${data.budget_id})"><i class="ki-duotone ki-book-square fs-1"><span class="path1"></span><span class="path2"></span><span class="path3"></span></i></button>`
+								} else if (row.process_status === 2) {
+									str += `<button type="button" class="btn btn-icon btn-sm btn-light-warning" data-bs-toggle="tooltip" data-bs-placement="top" title="重新处理" onclick="ReStartProcessTask(${row.id}, ${data.budget_id})"><i class="ki-duotone ki-book-square fs-1"><span class="path1"></span><span class="path2"></span><span class="path3"></span></i></button>`
+									if (row.send_status === 0) {
+										str += `<button type="button" class="btn btn-icon btn-sm btn-light-primary" data-bs-toggle="tooltip" data-bs-placement="top" title="开始发送" onclick="StartSendTask(${row.id}, ${data.budget_id})"><i class="ki-duotone ki-send fs-1"><span class="path1"></span><span class="path2"></span></i></button>`
+									} else if (row.send_status === 2) {
+										str += `<button type="button" class="btn btn-icon btn-sm btn-light-warning" data-bs-toggle="tooltip" data-bs-placement="top" title="重新发送" onclick="ReStartSendTask(${row.id}, ${data.budget_id})"><i class="ki-duotone ki-send fs-1"><span class="path1"></span><span class="path2"></span></i></button>`
+									} else if (row.send_status === 3) {
+										str += `<button type="button" class="btn btn-icon btn-sm btn-light-danger" data-bs-toggle="tooltip" data-bs-placement="top" title="重新发送" onclick="ReStartSendTask(${row.id}, ${data.budget_id})"><i class="ki-duotone ki-send fs-1"><span class="path1"></span><span class="path2"></span></i></button>`
+									}
+								} else if (row.process_status === 3) {
+									str += `<button type="button" class="btn btn-icon btn-sm btn-light-danger" data-bs-toggle="tooltip" data-bs-placement="top" title="重新处理" onclick="ReStartProcessTask(${row.id}, ${data.budget_id})"><i class="ki-duotone ki-book-square fs-1"><span class="path1"></span><span class="path2"></span><span class="path3"></span></i></button>`
+								}
+							} else if (row.collect_status === 3) {
+								str += `<button type="button" class="btn btn-icon btn-sm btn-light-danger" data-bs-toggle="tooltip" data-bs-placement="top" title="重新采集" onclick="ReStartCollectTask(${row.id}, ${data.budget_id})"><i class="ki-duotone ki-add-notepad fs-1"><span class="path1"></span><span class="path2"></span><span class="path3"></span><span class="path4"></span></i></button>`
+							}
+							str+=`<button type="button" class="btn btn-icon btn-sm btn-light-primary" data-bs-toggle="tooltip" data-bs-placement="top" title="编辑" onclick="Edit(${row.id})"><i class="ki-duotone ki-message-edit fs-1"><span class="path1"></span><span class="path2"></span></i></button>`
+							str+=`<button type="button" class="btn btn-icon btn-sm btn-light-danger"  data-bs-toggle="tooltip" data-bs-placement="top" title="删除" onclick="Delete(${row.id})"><i class="ki-duotone ki-trash-square fs-1"><span class="path1"></span><span class="path2"></span><span class="path3"></span><span class="path4"></span></i></button>`
+
+							return str
+						}
+					},
+				]
+			}, true)
+	}
+	function _quotaTable($table,data){
+		$tableBox.find('[name="collect_status"]').val('').hide()
+		$tableBox.find('#task_add_btn').hide()
+		$tableBox.find('#quota_add_btn').show()
+		IwbTable($table,{
+			url:`/api/quota/list/${data.budget_id}/${project_id}/${data.item_code}`,
 			columns: [
 				{
-					title: '任务编号',
+					title: '号',
 					data: 'id',
 					width: '100px',
 				},
 				{
-					title: '任务名称',
-					data: 'task_name',
-					width: '240px',
+					title: '工程或费用项目名称',
+					data: 'project_name',
+					width: 'auto',
 				},
 				{
-					title: '文件数据',
-					data: 'file_data',
-					width: 'auto',
-					render: (row) => {
-						let str = ``
-						const file_paths = row.file_path ? row.file_path.split(',') : []
-						if(file_paths.length){
-							for (let i = 0; i < file_paths.length; i++) {
-								const path = file_paths[i]
-								const names = path.split('/')
-								const file_name = names[names.length - 1]
-								str += `<a href="#" onclick="DownloadFile('/api/task/download?filename=${encodeURIComponent(path)}','${file_name}')"  class="link link-info px-2">${file_name}</a>`
-							}
-						}else{
-							str ="-"
-						}
-						return str
-					}
+					title: '单位',
+					data: 'unit',
+					width: '100px',
 				},
 				{
-					title: '任务状态',
-					data: 'task_status',
-					width: '220px',
+					title: '状态',
+					data: 'task_desc',
+					width: '150px',
 					render: (row) => {
-						let str = ``
-						if (row.collect_status === 0) {
-							str += `<span class="badge badge-light-primary">未采集</span>`
-						} else if (row.collect_status === 1){
-							str += `<span class="badge badge-light-warning">采集中</span>`
-						} else if (row.collect_status === 2){
-							str += `<span class="badge badge-light-success">采集完成</span>`
-							if (row.process_status === 0) {
-								str += `<span class="badge badge-light-primary">未处理</span>`
-							} else if (row.process_status === 1){
-								str += `<span class="badge badge-light-warning">处理中</span>`
-							} else if (row.process_status === 2){
-								str += `<span class="badge badge-light-success">已处理</span>`
-								if (row.send_status === 0) {
-									str += `<span class="badge badge-light-primary">未发送</span>`
-								} else if (row.send_status === 1){
-									str += `<span class="badge badge-light-warning">发送中</span>`
-								} else if (row.send_status === 2){
-									str += `<span class="badge badge-light-success">已发送</span>`
-								} else if (row.send_status === 3){
-									str += `<span class="badge badge-light-danger">发送失败</span>`
-								}
-							} else if (row.process_status === 3){
-								str += `<span class="badge badge-light-danger">处理失败</span>`
-							}
-						} else if (row.collect_status === 3){
-							str += `<span class="badge badge-light-danger">采集失败</span>`
+						let str=''
+						if(row.process_status === 0){
+							str+= `<span class="badge badge-primary">未处理</span>`
+						}else if (row.process_status === 1){
+							str+= `<span class="badge badge-warning">处理中</span>`
+						}else if (row.process_status === 2){
+							str+= `<span class="badge badge-success">已处理</span>`
+						}else if (row.process_status === 3){
+							str+= `<span class="badge badge-danger">处理失败</span>`
+						}
+						if(row.send_status === 0){
+							str+= `<span class="badge badge-primary ms-3">未发送</span>`
+						}else if (row.send_status === 1){
+							str+= `<span class="badge badge-warning ms-3">发送中</span>`
+						}else if (row.send_status === 2){
+							str+= `<span class="badge badge-success ms-3">已发送</span>`
+						}else if (row.send_status === 3){
+							str+= `<span class="badge badge-danger ms-3">发送失败</span>`
 						}
 
 						return str
@@ -232,93 +413,44 @@ function RenderTabCondent(data) {
 					data: 'id',
 					width: '160px',
 					render: (row) => {
-						let str = ``
-						str += `<button type="button" class="btn btn-primary btn-sm" data-kt-menu="true" data-kt-menu-trigger="click" data-kt-menu-placement="bottom-end" data-kt-menu-flip="top-end">
-                                操作
-                                <span class="svg-icon fs-5 m-0">
-                                    <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24px" height="24px" viewBox="0 0 24 24" version="1.1">
-                                        <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-                                            <polygon points="0 0 24 0 24 24 0 24"></polygon>
-                                            <path d="M6.70710678,15.7071068 C6.31658249,16.0976311 5.68341751,16.0976311 5.29289322,15.7071068 C4.90236893,15.3165825 4.90236893,14.6834175 5.29289322,14.2928932 L11.2928932,8.29289322 C11.6714722,7.91431428 12.2810586,7.90106866 12.6757246,8.26284586 L18.6757246,13.7628459 C19.0828436,14.1360383 19.1103465,14.7686056 18.7371541,15.1757246 C18.3639617,15.5828436 17.7313944,15.6103465 17.3242754,15.2371541 L12.0300757,10.3841378 L6.70710678,15.7071068 Z" fill="currentColor" fill-rule="nonzero" transform="translate(12.000003, 11.999999) rotate(-180.000000) translate(-12.000003, -11.999999)"></path>
-                                        </g>
-                                    </svg>
-                                </span>
-                            </button>
-						  <div class="menu menu-sub menu-sub-dropdown menu-column menu-rounded menu-gray-600 menu-state-bg-light-primary fw-bold fs-7 w-125px px-3 py-1" data-kt-menu="true">
-							`
-						const template = `<div class="menu-item py-1 px-1"><button type="button" class="btn btn-sm btn-light-{0} w-100" onclick="{1}(${row.id},${data.budget_id})">{2}</button></div>`
-						if (row.collect_status === 0) {
-							str += template.format('primary', 'StartCollect', '开始采集')
-						} else if (row.collect_status === 2) {
-							str += template.format('warning', 'ReStartCollect', '重新采集')
-							if (row.process_status === 0) {
-								str += template.format('primary', 'StartProcess', '开始处理')
-							} else if (row.process_status === 2) {
-								str += template.format('warning', 'ReStartProcess', '重新处理')
-								if (row.send_status === 0) {
-									str += template.format('primary', 'StartSend', '开始发送')
-								} else if (row.send_status === 2) {
-									str += template.format('warning', 'ReStartSend', '重新发送')
-								} else if (row.send_status === 3) {
-									str += template.format('danger', 'ReStartSend', '重新发送')
-								}
-							} else if (row.process_status === 3) {
-								str += template.format('danger', 'ReStartProcess', '重新处理')
+						let str=''
+						if (row.process_status === 0) {
+							str += `<button type="button" class="btn btn-icon btn-sm btn-light-primary" data-bs-toggle="tooltip" data-bs-placement="top" title="开始处理" onclick="StartProcessQuota(${row.id}, ${data.budget_id})"><i class="ki-duotone ki-book-square fs-1"><span class="path1"></span><span class="path2"></span><span class="path3"></span></i></button>`
+						} else if (row.process_status === 2) {
+							str += `<button type="button" class="btn btn-icon btn-sm btn-light-warning" data-bs-toggle="tooltip" data-bs-placement="top" title="重新处理" onclick="ReStartProcessQuota(${row.id}, ${data.budget_id})"><i class="ki-duotone ki-book-square fs-1"><span class="path1"></span><span class="path2"></span><span class="path3"></span></i></button>`
+							if (row.send_status === 0) {
+								str += `<button type="button" class="btn btn-icon btn-sm btn-light-primary" data-bs-toggle="tooltip" data-bs-placement="top" title="开始发送" onclick="StartSendQuota(${row.id}, ${data.budget_id})"><i class="ki-duotone ki-send fs-1"><span class="path1"></span><span class="path2"></span></i></button>`
+							} else if (row.send_status === 2) {
+								str += `<button type="button" class="btn btn-icon btn-sm btn-light-warning" data-bs-toggle="tooltip" data-bs-placement="top" title="重新发送" onclick="ReStartSendQuota(${row.id}, ${data.budget_id})"><i class="ki-duotone ki-send fs-1"><span class="path1"></span><span class="path2"></span></i></button>`
+							} else if (row.send_status === 3) {
+								str += `<button type="button" class="btn btn-icon btn-sm btn-light-danger" data-bs-toggle="tooltip" data-bs-placement="top" title="重新发送" onclick="ReStartSendQuota(${row.id}, ${data.budget_id})"><i class="ki-duotone ki-send fs-1"><span class="path1"></span><span class="path2"></span></i></button>`
 							}
-						} else if (row.collect_status === 3) {
-							str += template.format('danger', 'ReStartCollect', '重新采集')
+						} else if (row.process_status === 3) {
+							str += `<button type="button" class="btn btn-icon btn-sm btn-light-danger" data-bs-toggle="tooltip" data-bs-placement="top" title="重新处理" onclick="ReStartProcessQuota(${row.id}, ${data.budget_id})"><i class="ki-duotone ki-book-square fs-1"><span class="path1"></span><span class="path2"></span><span class="path3"></span></i></button>`
 						}
-						str += template.format('primary', 'Edit', '编辑')
-						str += template.format('danger', 'Delete', '删除')
-						// if (row.collect_status === 0) {
-						// 	str += `<button type="button" class="btn btn-sm btn-light-primary" onclick="StartCollect(${row.id}, ${data.budget_id})">开始任务</button>`
-						// } else if (row.collect_status === 2) {
-						// 	str += `<button type="button" class="btn btn-sm btn-light-warning" onclick="ReStartCollect(${row.id}, ${data.budget_id})">重新采集</button>`
-						// 	if (row.process_status === 0) {
-						// 		str += `<button type="button" class="btn btn-sm btn-light-primary" onclick="StartProcess(${row.id}, ${data.budget_id})">开始处理</button>`
-						// 	} else if (row.process_status === 2) {
-						// 		str += `<button type="button" class="btn btn-sm btn-light-warning" onclick="ReStartProcess(${row.id}, ${data.budget_id})">重新处理</button>`
-						// 		if (row.send_status === 0) {
-						// 			str += `<button type="button" class="btn btn-sm btn-light-primary" onclick="StartSend(${row.id}, ${data.budget_id})">开始发送</button>`
-						// 		} else if (row.send_status === 2) {
-						// 			str += `<button type="button" class="btn btn-sm btn-light-warning" onclick="ReStartSend(${row.id}, ${data.budget_id})">重新发送</button>`
-						// 		} else if (row.send_status === 3) {
-						// 			str += `<button type="button" class="btn btn-sm btn-light-danger" onclick="ReStartSend(${row.id}, ${data.budget_id})">重新发送</button>`
-						// 		}
-						// 	} else if (row.process_status === 3) {
-						// 		str += `<button type="button" class="btn btn-sm btn-light-danger" onclick="ReStartProcess(${row.id}, ${data.budget_id})">重新处理</button>`
-						// 	}
-						// } else if (row.collect_status === 3) {
-						// 	str += `<button type="button" class="btn btn-sm btn-light-danger" onclick="ReStartCollect(${row.id}, ${data.budget_id})">重新采集</button>`
-						// }
-						// str += `<button type="button" class="btn btn-sm btn-light-info" onclick="GoTo('')">详情</button>`
-						// str += `<button type="button" class="btn btn-sm btn-light-primary" onclick="Edit(${row.id}, ${data.budget_id})">编辑</button>`
-						// str += `<button type="button" class="btn btn-sm btn-light-danger" onclick="Delete(${row.id}, ${data.budget_id})">删除</button>`
-						str+= `</div>`
-						str += `<button type="button" class="btn btn-sm btn-light-primary ms-5" onclick="GoTo('')">详情</button></div>`
-
+						str+=`<button type="button" class="btn btn-icon btn-sm btn-light-primary" data-bs-toggle="tooltip" data-bs-placement="top" title="编辑" onclick="Edit_Quota(${row.id})"><i class="ki-duotone ki-message-edit fs-1"><span class="path1"></span><span class="path2"></span></i></button>`
+						str+=`<button type="button" class="btn btn-icon btn-sm btn-light-danger"  data-bs-toggle="tooltip" data-bs-placement="top" title="删除" onclick="Delete_Quota(${row.id})"><i class="ki-duotone ki-trash-square fs-1"><span class="path1"></span><span class="path2"></span><span class="path3"></span><span class="path4"></span></i></button>`
 						return str
 					}
 				},
-			],
-			callAfterRender:()=>{
-				KTMenu.createInstances(`#table_${data.budget_id} [data-kt-menu="true"]`)
-			}
+			]
 		}, true)
-	} else {
-
 	}
 
 }
 
 function Add(budget_id) {
+	_fileUploadDropzone.removeAllFiles()
 	AddModal($modal, () => {
-		$modal.find('#task_id').val('0');
-		SetBudgetData(budget_id)
+		$modal.find('[name="task_id"]').val('0');
+		$modal.find('#delete_file_box').hide();
+
+		SetBudgetData($modal,budget_id)
 	})
 }
 
 function Edit(id) {
+	_fileUploadDropzone.removeAllFiles()
     EditModal($modal,()=>{
         IwbAjax_1({
             url:`/api/task/get/${id}`,
@@ -328,46 +460,59 @@ function Edit(id) {
 					return
 				}
 				const data = res.data
+				$modal.find('#delete_file_box').show();
 				// SetBudgetData(budget_id)
-        		$modal.find('#task_id').val(data.id);
-				$modal.find('#budget_id').val(data.budget_id);
-				$modal.find('#project_id').val(data.project_id);
-				$modal.find('#item_id').val(data.item_id);
-				$modal.find('#item_code').val(data.item_code);
-                $modal.find('#task_name').val(data.task_name);
-                $modal.find('#task_desc').val(data.task_desc);
-
+        		$modal.find('[name="task_id"]').val(data.id);
+				$modal.find('[name="budget_id"]').val(data.budget_id);
+				$modal.find('[name="project_id"]').val(data.project_id);
+				$modal.find('[name="item_id"]').val(data.item_id);
+				$modal.find('[name="item_code"]').val(data.item_code);
+                $modal.find('[name="task_name"]').val(data.task_name);
+                $modal.find('[name="task_desc"]').val(data.task_desc);
             }
         })
     })
 }
 
-function SetBudgetData(budget_id){
+function SetBudgetData($el,budget_id){
 	const $tableBox = $(`#table_box_${budget_id}`)
-	$modal.find('#budget_id').val(budget_id);
-	$modal.find('#project_id').val($tableBox.find('input[name="project_id"]').val());
-	$modal.find('#item_id').val($tableBox.find('input[name="item_id"]').val());
-	$modal.find('#item_code').val($tableBox.find('input[name="item_code"]').val());
+	$el.find('[name="budget_id"').val(budget_id);
+	$el.find('[name="project_id"]').val($tableBox.find('input[name="project_id"]').val());
+	$el.find('[name="item_id"]').val($tableBox.find('input[name="item_id"]').val());
+	$el.find('[name="item_code"]').val($tableBox.find('input[name="item_code"]').val());
 }
 function SaveProject() {
     const
-		budget_id = $modal.find('#budget_id').val(),
-		item_id = $modal.find('#item_id').val(),
-		item_code = $modal.find('#item_code').val(),
-		project_id = $modal.find('#project_id').val(),
-		task_id=  $modal.find('#task_id').val(),
-		task_name = $modal.find('#task_name').val(),
-		task_desc = $modal.find('#task_desc').val()
+		formData = new FormData(),
+		budget_id = $modal.find('[name="budget_id"]').val(),
+		item_id = $modal.find('[name="item_id"]').val(),
+		item_code = $modal.find('[name="item_code"]').val(),
+		project_id = $modal.find('[name="project_id"]').val(),
+		task_id=  $modal.find('[name="task_id"]').val(),
+		task_name = $modal.find('[name="task_name"]').val(),
+		task_desc = $modal.find('[name="task_desc"]').val(),
+		delete_file = $modal.find('[name="delete_file"]').checked ? 'true':'false',
+		files = _fileUploadDropzone.getAcceptedFiles();
+	// console.log("FILES",files)
+
+	if(files.length>0){
+		files.forEach((file) => {
+			formData.append('files', file)
+		})
+	}
+	formData.append('budget_id', budget_id)
+	formData.append('item_id', item_id)
+	formData.append('item_code', item_code)
+	formData.append('project_id', project_id)
+	formData.append('task_id', task_id)
+	formData.append('task_name', task_name)
+	formData.append('task_desc', task_desc)
+	formData.append('delete_old', delete_file)
+
     IwbAjax({
         url:`/api/task/save/${task_id}`,
-        data:{
-			budget_id,
-			project_id,
-			item_id,
-			item_code,
-            task_name,
-            task_desc,
-        },
+		headers:{},
+        body:formData,
 		modal:"#modal",
 		table:`#table_${budget_id}`
     })
@@ -377,24 +522,94 @@ function Delete(id,budget_id){
 	ConfirmUrl('确定删除吗?',`/api/task/delete/${id}`,`#table_${budget_id}`)
 }
 
-function StartCollect(id,budget_id){
+function StartCollectTask(id,budget_id){
 	ConfirmUrl('确定开始采集吗?',`/api/task/start_collect/${id}`,`#table_${budget_id}`)
 }
-function ReStartCollect(id,budget_id){
+function ReStartCollectTask(id,budget_id){
 	ConfirmUrl('确定重新开始采集吗?',`/api/task/start_collect/${id}`,`#table_${budget_id}`)
 }
 
-function StartProcess(id,budget_id){
+function StartProcessTask(id,budget_id){
 	ConfirmUrl('确定开始处理吗?',`/api/task/start_process/${id}`,`#table_${budget_id}`)
 }
 
-function ReStartProcess(id,budget_id){
+function ReStartProcessTask(id,budget_id){
 	ConfirmUrl('确定重新开始处理吗?',`/api/task/start_process/${id}`,`#table_${budget_id}`)
 }
 
-function StartSend(id,budget_id){
+function StartSendTask(id,budget_id){
 	ConfirmUrl('确定开始发送吗?',`/api/task/start_send/${id}`,`#table_${budget_id}`)
 }
-function ReStartSend(id,budget_id){
+function ReStartSendTask(id,budget_id){
 	ConfirmUrl('确定重新开始发送吗?',`/api/task/start_send/${id}`,`#table_${budget_id}`)
-}
+}
+
+function Add_Quota(budget_id) {
+	AddModal($modalQuota, () => {
+		$modalQuota.find('#quota_id').val('0');
+		SetBudgetData($modalQuota, budget_id)
+	})
+}
+
+function Edit_Quota(id) {
+    EditModal($modalQuota,()=>{
+        IwbAjax_1({
+            url:`/api/task/get/${id}`,
+            success:res=>{
+				if(!res.success){
+					console.error(res.message)
+					return
+				}
+				const data = res.data
+				// SetBudgetData(budget_id)
+        		$modalQuota.find('#quota_id').val(data.id);
+				$modalQuota.find('[name="budget_id"]').val(data.budget_id);
+				$modalQuota.find('[name="project_id"]').val(data.project_id);
+				$modalQuota.find('[name="item_id"]').val(data.item_id);
+				$modalQuota.find('[name="item_code"]').val(data.item_code);
+                $modalQuota.find('[name="project_name"]').val(data.project_name);
+
+            }
+        })
+    })
+}
+
+function SaveQuota(){
+	const quota_id = $modalQuota.find('#quota_id').val(),
+		budget_id = $modalQuota.find('[name="budget_id"]').val(),
+		project_id = $modalQuota.find('[name="project_id"]').val(),
+		item_id = $modalQuota.find('[name="item_id"]').val(),
+		item_code = $modalQuota.find('[name="item_code"]').val(),
+		project_name = $modalQuota.find('[name="project_name"]').val()
+	IwbAjax({
+		url:`/api/quota/save/${quota_id}`,
+		body:{
+			budget_id,
+			project_id,
+			item_id,
+			item_code,
+			project_name
+		},
+		modal:$modalQuota,
+		table:`#table_${budget_id}`
+	})
+
+
+}
+
+function Delete_Quota(id,budget_id){
+	ConfirmUrl('确定删除吗?',`/api/quota/delete/${id}`,`#table_${budget_id}`)
+}
+
+function StartProcessQuota(id,budget_id){
+	ConfirmUrl('确定开始处理吗?',`/api/quota/start_process/${id}`,`#table_${budget_id}`)
+}
+function ReStartProcessQuota(id,budget_id){
+	ConfirmUrl('确定重新开始处理吗?',`/api/quota/start_process/${id}`,`#table_${budget_id}`)
+}
+function StartSendQuota(id,budget_id){
+	ConfirmUrl('确定开始发送吗?',`/api/quota/start_send/${id}`,`#table_${budget_id}`)
+}
+function ReStartSendQuota(id,budget_id){
+	ConfirmUrl('确定重新开始发送吗?',`/api/quota/start_send/${id}`,`#table_${budget_id}`)
+}

+ 1 - 1
SourceCode/IntelligentRailwayCosting/app/views/templates/account/login.html

@@ -15,7 +15,7 @@
 			<div class="invalid-feedback">请输入用户名、邮箱或手机号</div>
 		</div>
 		<div class="form-floating">
-			<input type="password" class="form-control" id="password" name="password" placeholder="密码" required />
+			<input type="password" class="form-control" id="password" name="password" placeholder="密码"/>
 			<label for="password">密码</label>
 			<div class="invalid-feedback">请输入密码</div>
 		</div>

+ 1 - 1
SourceCode/IntelligentRailwayCosting/app/views/templates/log/index.html

@@ -164,7 +164,7 @@
                         render: function (row) {
                             const operation_data = row.operation_data ? row.operation_data : "";
                             const data_changes = row.data_changes ? (operation_data? '\n': '')+ row.data_changes : "";
-                            return `<span class="w-100" title='${operation_data ? operation_data:""}${data_changes ? data_changes:""}'>${row.operation_desc}</span>`;
+                            return `<span class="w-100" data-bs-toggle="tooltip" data-bs-placement="top"  title='${operation_data ? operation_data:""}${data_changes ? data_changes:""}'>${row.operation_desc}</span>`;
                         }
                     },
                     {

+ 75 - 6
SourceCode/IntelligentRailwayCosting/app/views/templates/project/budget_info.html

@@ -17,7 +17,7 @@
 	<div class="modal-dialog modal-dialog-centered">
 		<div class="modal-content rounded">
 			<div class="modal-header">
-				<h3 class="modal-title" id="changePasswordModalLabel"><span class="prefix"></span>任务</h3>
+				<h3 class="modal-title"><span class="prefix"></span>任务</h3>
 				<div class="btn btn-sm btn-icon btn-active-color-primary" data-bs-dismiss="modal">
 					<i class="ki-duotone ki-cross fs-1">
 						<span class="path1"></span>
@@ -28,11 +28,11 @@
 			<div class="modal-body">
 				<form>
 					<div class="form-group">
-						<input type="hidden" id="budget_id" value="">
-						<input type="hidden" id="project_id" value="">
-						<input type="hidden" id="item_id" value="">
-						<input type="hidden" id="item_code" value="">
-						<input type="hidden" id="task_id" value="">
+						<input type="hidden" name="budget_id" value="">
+						<input type="hidden" name="project_id" value="">
+						<input type="hidden" name="item_id" value="">
+						<input type="hidden" name="item_code" value="">
+						<input type="hidden" name="task_id" value="">
 						<div class="fv-row form-group mb-3">
 							<label for="task_name" class="form-label required">任务名称</label>
 							<input type="text" class="form-control" name="task_name" id="task_name" placeholder="请输入" required />
@@ -41,6 +41,41 @@
 							<label for="task_desc" class="form-label">任务详情</label>
 							<textarea type="text" class="form-control" name="task_desc" id="task_desc" placeholder="请输入"></textarea>
 						</div>
+						<div class="form-check mb-3" id="delete_file_box">
+						 	<input class="form-check-input" type="checkbox" value="" id="delete_file" name="delete_file"/>
+							<label class="form-check-label ms-5 text-primary " for="delete_file">
+								删除原数据文件
+							</label>
+						</div>
+						<div class="fv-row">
+							<div id="file_upload_dropzone">
+								<div class="dropzone">
+									<div class="dz-message dropzone-select">
+										<i class="ki-duotone ki-file-up fs-3x text-primary"><span class="path1"></span><span class="path2"></span></i>
+										<div class="ms-4">
+											<h3 class="fs-5 fw-bold text-gray-900 mb-1">将文件拖放到此处或单击上传。</h3>
+											<span class="fs-7 fw-semibold text-gray-500">最多可上传 5 个文件,文件最大10M</span>
+										</div>
+									</div>
+								</div>
+								<div class="dropzone dropzone-queue">
+									<div class="dropzone-items wm-200px">
+										<div class="dropzone-item" style="display:none">
+											<div class="dropzone-file">
+												<div class="dropzone-filename" title="some_image_file_name.jpg">
+													<span data-dz-name>some_image_file_name.jpg</span>
+													<strong>(<span data-dz-size>340kb</span>)</strong>
+												</div>
+												<div class="dropzone-error" data-dz-errormessage></div>
+											</div>
+											<div class="dropzone-toolbar">
+												<span class="dropzone-delete" data-dz-remove><i class="bi bi-x fs-1"></i></span>
+											</div>
+										</div>
+									</div>
+								</div>
+							</div>
+						</div>
 					</div>
 				</form>
 			</div>
@@ -51,6 +86,40 @@
 		</div>
 	</div>
 </div>
+<div class="modal fade" id="modal_quota" tabindex="-1" aria-hidden="true">
+	<div class="modal-dialog modal-dialog-centered">
+		<div class="modal-content rounded">
+			<div class="modal-header">
+				<h3 class="modal-title"><span class="prefix"></span>定额</h3>
+				<div class="btn btn-sm btn-icon btn-active-color-primary" data-bs-dismiss="modal">
+					<i class="ki-duotone ki-cross fs-1">
+						<span class="path1"></span>
+						<span class="path2"></span>
+					</i>
+				</div>
+			</div>
+			<div class="modal-body">
+				<form>
+					<div class="form-group">
+						<input type="hidden" name="budget_id" value="">
+						<input type="hidden" name="project_id" value="">
+						<input type="hidden" name="item_id" value="">
+						<input type="hidden" name="item_code" value="">
+						<input type="hidden" name="task_id" value="">
+						<div class="fv-row form-group mb-3">
+							<label for="task_name" class="form-label required">项目名称</label>
+							<input type="text" class="form-control" name="project_name" id="project_name" placeholder="请输入" required />
+						</div>
+					</div>
+				</form>
+			</div>
+			<div class="modal-footer">
+				<button type="button" class="btn btn-light" data-bs-dismiss="modal">取消</button>
+				<button type="button" class="btn btn-primary" onclick="SaveQuota()">保存</button>
+			</div>
+		</div>
+	</div>
+</div>
 {% endblock %} {% block page_scripts %}
 <script>
 	ChangeHeadMenu('#{{page_active}}_menu')