YueYunyun 3 months ago
parent
commit
4fd7c9f752

+ 2 - 0
.gitignore

@@ -114,3 +114,5 @@ crashlytics-build.properties
 doc/data/
 logs/
 temp_files/
+/SourceCode/IntelligentRailwayCosting/SQL
+Doc/DB

+ 1 - 1
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 '项目编号',
-    budget_id int NOT NULL COMMENT '概算序号',
+    budget_id int NOT NULL DEFAULT 0 COMMENT '概算序号',
     item_id int NOT NULL COMMENT '条目序号',
     item_code VARCHAR(255) NOT NULL COMMENT '条目编号',
     file_path text COMMENT '文件路径',

+ 1 - 1
SourceCode/IntelligentRailwayCosting/.script/init_sqlserver.sql

@@ -11,7 +11,7 @@ CREATE TABLE project_task (
     task_name NVARCHAR(255) NOT NULL, -- 任务名称
     task_desc NVARCHAR(1000), -- 任务描述
     project_id NVARCHAR(50) NOT NULL, -- 项目编号
-    budget_id INT NOT NULL, -- 概算序号
+    budget_id INT NOT NULL DEFAULT 0, -- 概算序号
     item_id INT NOT NULL, -- 条目序号
     item_code NVARCHAR(255) NOT NULL, -- 条目编号
     file_path TEXT, -- 文件路径

+ 1 - 1
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
-    budget_id: int
+    budget_id: Optional[int] = 0
     item_id: int
     item_code: Optional[str] = None
     file_path: Optional[str] = None

+ 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='项目编号')
-    budget_id = Column(Integer, nullable=False, comment='概算序号')
+    budget_id = Column(Integer, comment='概算序号')
     item_id = Column(Integer, nullable=False, comment='条目序号')
     item_code = Column(String(255), nullable=False, comment='条目编号')
     file_path = Column(Text, comment='文件路径')

+ 4 - 5
SourceCode/IntelligentRailwayCosting/app/executor/collector.py

@@ -1,13 +1,12 @@
 import tools.utils as utils
 from core.dtos import ProjectTaskDto, ProjectQuotaDto
-from stores import ProjectTaskStore,ProjectQuotaStore,BudgetStore
-from tools import db_helper
+from stores import ProjectTaskStore,ProjectQuotaStore,ChapterStore
 
 
 class Collector:
     def __init__(self):
         self._logger = utils.get_logger()
-        self._budget_store = BudgetStore()
+        self._chapter_store = ChapterStore()
         self._task_store = ProjectTaskStore()
         self._quota_store = ProjectQuotaStore()
 
@@ -54,7 +53,7 @@ class Collector:
         try:
             self._logger.debug(f"开始调用接口:{task.task_name}")
 
-            data = self._budget_store.get_all_budget_items_not_children(task.project_id,task.budget_id, task.item_code)
+            data = self._chapter_store.get_all_chapter_items_not_children(task.project_id, task.item_code)
             # print(len(data))
             # for item in data:
             #     print(item)
@@ -82,7 +81,7 @@ class Collector:
             self._logger.debug(f"开始插入数据:{task.task_name}")
             for item in data:
                 quota =  ProjectQuotaDto(
-                    budget_id=task.budget_id,
+                    budget_id=item['budget_id'],
                     project_id=task.project_id,
                     item_code=item['item_code'],
                     item_id=item['item_id'],

+ 16 - 5
SourceCode/IntelligentRailwayCosting/app/routes/project.py

@@ -27,7 +27,6 @@ def get_page_list():
     except Exception as e:
         return ResponseBase.error(f'获取项目失败:{str(e)}')
 
-
 @project_api.route('/budget/<project_id>', methods=['POST'])
 @Permission.authorize
 def get_budget_info(project_id:str):
@@ -40,14 +39,26 @@ def get_budget_info(project_id:str):
         return ResponseBase.error(f'获取项目概算信息失败:{str(e)}')
 
 
-@project_api.route('/budget-item/<budget_id>/<project_id>', methods=['POST'])
+@project_api.route('/chapter/<project_id>', methods=['POST'])
 @Permission.authorize
-def get_budget_items(budget_id:str,project_id:str):
+def get_chapter_items(project_id:str):
     item_code = request.args.get('c', None)
     try:
-        data,msg = project_srvice.get_budget_items(budget_id, project_id, item_code)
+        data,msg = project_srvice.get_chapter_items(project_id, item_code)
         if not data:
             return ResponseBase.error(msg)
         return ResponseBase.success(data)
     except Exception as e:
-        return ResponseBase.error(f'获取项目概算子条目失败:{str(e)}')
+        return ResponseBase.error(f'获取项目章节条目失败:{str(e)}')
+
+# @project_api.route('/budget-item/<budget_id>/<project_id>', methods=['POST'])
+# @Permission.authorize
+# def get_budget_items(budget_id:str,project_id:str):
+#     item_code = request.args.get('c', None)
+#     try:
+#         data,msg = project_srvice.get_budget_items(budget_id, project_id, item_code)
+#         if not data:
+#             return ResponseBase.error(msg)
+#         return ResponseBase.success(data)
+#     except Exception as e:
+#         return ResponseBase.error(f'获取项目概算子条目失败:{str(e)}')

+ 21 - 7
SourceCode/IntelligentRailwayCosting/app/routes/project_task.py

@@ -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>/<item_code>', methods=['POST'])
+@project_task_api.route('/list/<project_id>/<item_code>', methods=['POST'])
 @Permission.authorize
-def get_page_list(budget_id:int,project_id:str,item_code:str):
+def get_page_list(project_id:str,item_code:str):
     try:
         data = request.get_json()
         page = int(data.get('pageNum', 1))
@@ -19,11 +19,27 @@ def get_page_list(budget_id:int,project_id:str,item_code:str):
         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_code, page, per_page, keyword, collect_status, process_status, send_status)
+        task, total_count = task_service.get_tasks_paginated( 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)}')
 
+# @project_task_api.route('/list/<int:budget_id>/<project_id>/<item_code>', methods=['POST'])
+# @Permission.authorize
+# def get_page_list_bk(budget_id:int,project_id:str,item_code:str):
+#     try:
+#         data = request.get_json()
+#         page = int(data.get('pageNum', 1))
+#         per_page = int(data.get('pageSize', 10))
+#         keyword = data.get('keyword')
+#         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_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)}')
+
 @project_task_api.route('/get/<int:task_id>', methods=['POST'])
 @Permission.authorize
 def get_task(task_id:int):
@@ -39,7 +55,6 @@ def save_task(task_id:int):
     try:
         # 从请求中获取表单数据
         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')
@@ -49,11 +64,10 @@ def save_task(task_id:int):
         # 获取上传的文件
         files = request.files.getlist('files')
         # 验证必要参数
-        if not all([budget_id, project_id, task_name]):
-            return ResponseBase.error('缺少必要参数:budget_id、project_id、task_name')
+        if not all([ project_id, task_name]):
+            return ResponseBase.error('缺少必要参数:project_id、task_name')
         # 构建任务DTO
         task_dto = ProjectTaskDto(
-            budget_id=budget_id,
             item_id=item_id,
             project_id=project_id,
             item_code=item_code,

+ 27 - 2
SourceCode/IntelligentRailwayCosting/app/services/project.py

@@ -5,7 +5,7 @@ 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 stores import ProjectStore,BudgetStore,ChapterStore
 
 
 class ProjectService:
@@ -13,6 +13,7 @@ class ProjectService:
     def __init__(self):
         self._project_store = ProjectStore()
         self._budget_store = BudgetStore()
+        self._chapter_store = ChapterStore()
 
     def get_projects_paginated(self, page: int, page_size: int, keyword: Optional[str] = None,
         start_time: Optional[str] = None,
@@ -48,7 +49,31 @@ class ProjectService:
         items = self._budget_store.get_top_budget_items(project_id,budget_id)
         return [TotalBudgetItemDto.from_model(item).to_dict() for item in items],""
 
-    def get_budget_items(self, budget_id: str, project_id: str, item_code:str):
+    def get_chapter_items(self, project_id: str, item_code:str):
+        msg = self._check_project_db_exit(project_id)
+        if msg:
+            return None, msg
+        data_list = []
+        if not item_code:
+            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 =  self._chapter_store.get_top_chapter_items(project_id,team_item_code)
+        else:
+            items = self._chapter_store.get_child_chapter_items(project_id,item_code)
+        parent = "#"
+        if item_code:
+            item = self._chapter_store.get_chapter_item_by_item_code(project_id,item_code)
+            parent = item.item_id
+        for item in items:
+            text = f"第{item.chapter}章、{item.project_name}" if item.chapter else ( f"{item.section}  {item.project_name}" if item.section else item.project_name)
+            data_list.append(TreeDto(item.item_id,parent,text,item.children_count>0,item).to_dict())
+        return data_list,""
+
+    def get_budget_items(self, budget_id: int, project_id: str, item_code:str):
         if not budget_id:
             return None,'budget_id不能为空'
         msg = self._check_project_db_exit(project_id)

+ 35 - 1
SourceCode/IntelligentRailwayCosting/app/services/project_task.py

@@ -13,7 +13,41 @@ class ProjectTaskService:
         self.store = ProjectTaskStore()
         self._logger = utils.get_logger()
 
-    def get_tasks_paginated(self, budget_id: int, project_id: str, item_code: str, page: int = 1, page_size: int = 10,
+    def get_tasks_paginated(self, 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: 项目编号
+            item_code: 条目编号
+            page: 页码
+            page_size: 每页数量
+            keyword: 关键字
+            collect_status: 采集状态
+            process_status: 处理状态
+            send_status: 发送状态
+
+        Returns:
+            dict: 包含总数和任务列表的字典
+        """
+        try:
+            data =  self.store.get_tasks_paginated(
+                project_id=project_id,
+                item_code=item_code,
+                page=page,
+                page_size=page_size,
+                keyword=keyword,
+                collect_status=collect_status,
+                process_status=process_status,
+                send_status=send_status
+            )
+            return [ProjectTaskDto.from_model(task).to_dict() for task in data.get('data',[])],data.get('total',0)
+        except Exception as e:
+            self._logger.error(f"获取项目任务列表失败: {str(e)}")
+            raise
+
+    def get_tasks_paginated_bk(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):
         """获取项目任务列表

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

@@ -3,5 +3,6 @@ from stores.railway_costing_mysql import LogStore,ProjectQuotaStore,ProjectTaskS
 from .user import UserStore
 from .project import ProjectStore
 from .budget import BudgetStore
+from .chapter import ChapterStore
 from .quota_input import QuotaInputStore
 

+ 2 - 2
SourceCode/IntelligentRailwayCosting/app/stores/budget.py

@@ -1,4 +1,4 @@
-from sqlalchemy.orm import Session, aliased
+from sqlalchemy.orm import  aliased
 from sqlalchemy import and_, or_,  func
 
 from core.models import TotalBudgetInfoModel,TotalBudgetItemModel,ChapterModel
@@ -20,7 +20,7 @@ class BudgetStore:
                 return None
             return budgets
 
-    def get_budget_item_by_item_code(self, project_id: str, budget_id: str,item_code: str):
+    def get_budget_item_by_item_code(self, project_id: str, budget_id: int,item_code: str):
         self._database=project_id
         with db_helper.sqlserver_query_session(self._database) as db_session:
             budget = db_session.query(

+ 125 - 0
SourceCode/IntelligentRailwayCosting/app/stores/chapter.py

@@ -0,0 +1,125 @@
+from sqlalchemy.orm import  aliased
+from sqlalchemy import or_,  func
+
+from core.models import ChapterModel
+from tools import db_helper
+
+class ChapterStore:
+    def __init__(self):
+        self._database = None
+        self._db_session = None
+
+
+    def get_chapter_item_by_item_code(self, project_id: str, item_code: str):
+        self._database=project_id
+        with db_helper.sqlserver_query_session(self._database) as db_session:
+            chapter = db_session.query(
+                ChapterModel.item_id,
+                ChapterModel.item_code,
+                ChapterModel.chapter,
+                ChapterModel.section,
+                ChapterModel.project_name,
+                ChapterModel.item_type,
+                ChapterModel.unit,)\
+                .filter(ChapterModel.item_code == item_code)\
+                .first()
+            if chapter is None:
+                return None
+            return chapter
+
+    def _build_children_count_subquery(self, model_class):
+        # 创建父节点和子节点的别名
+        parent = aliased(model_class, name='parent')
+        child = aliased(model_class, name='child')
+
+        # 子查询:计算每个节点的直接子节点数量
+        return self.db_session.query(
+            parent.item_code.label('parent_code'),
+            func.count(child.item_code).label('child_count')
+        ).outerjoin(
+            child,
+            or_(
+                # 匹配形如01-01的格式
+                child.item_code.like(parent.item_code + '-__'),
+                # 匹配形如0101的格式
+                child.item_code.like(parent.item_code + '__')
+            )
+        ).group_by(parent.item_code).subquery()
+
+    def _build_chapter_items_query(self):
+        # 子查询:计算每个节点的直接子节点数量
+        children_count = self._build_children_count_subquery(ChapterModel)
+
+        return (self.db_session.query(
+            ChapterModel.item_id,
+            ChapterModel.item_code,
+            ChapterModel.chapter,
+            ChapterModel.section,
+            ChapterModel.project_name,
+            ChapterModel.item_type,
+            ChapterModel.unit,
+            func.coalesce(children_count.c.child_count, 0).label('children_count')
+        )
+        .outerjoin(children_count, children_count.c.parent_code == ChapterModel.item_code)
+        .distinct()
+        )
+
+
+    def get_top_chapter_items(self, project_id: str, item_code: list[str]=None):
+        self._database=project_id
+        with db_helper.sqlserver_query_session(self._database) as self.db_session:
+            query = self._build_chapter_items_query()
+
+            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
+
+    def get_child_chapter_items(self, project_id: str, parent_item_code: str):
+        # 构建子节点的模式:支持两种格式
+        # 1. 父级编号后跟-和两位数字(如:01-01)
+        # 2. 父级编号直接跟两位数字(如:0101)
+        pattern_with_dash = f'{parent_item_code}-__'
+        pattern_without_dash = f'{parent_item_code}__'
+        self._database = project_id
+        with db_helper.sqlserver_query_session(self._database) as self.db_session:
+            query = self._build_chapter_items_query()\
+                .filter(or_(ChapterModel.item_code.like(pattern_with_dash),
+                           ChapterModel.item_code.like(pattern_without_dash)))\
+                .order_by(ChapterModel.item_code)
+            items = query.all()
+            return items
+
+    def get_all_chapter_items_not_children(self, project_id: str, item_code: str):
+        self._database = project_id
+        with db_helper.sqlserver_query_session(self._database) as self.db_session:
+            # 添加叶子节点过滤条件,使用复用的子查询方法
+            children_count = self._build_children_count_subquery(ChapterModel)
+            query =  (self.db_session.query(
+                    ChapterModel.item_id,
+                    ChapterModel.item_code,
+                    ChapterModel.chapter,
+                    ChapterModel.section,
+                    ChapterModel.project_name,
+                    ChapterModel.item_type,
+                    ChapterModel.unit,
+                    func.coalesce(children_count.c.child_count, 0).label('children_count')
+                )
+                .outerjoin(children_count, children_count.c.parent_code == ChapterModel.item_code)
+                .distinct()
+                )
+
+            query = query.filter(func.coalesce(children_count.c.child_count, 0) == 0)
+            # 如果指定了item_code,添加前缀匹配条件
+            if item_code:
+                pattern_with_dash = f'{item_code}-%'
+                query = query.filter(ChapterModel.item_code.like(pattern_with_dash))
+            query = query.order_by(ChapterModel.item_code)
+            items = query.all()
+            return items
+
+

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

@@ -7,10 +7,22 @@
     padding: 0;
     box-sizing: border-box;
 }
-.app-container{
-    padding: 0 10px;
+.app-container {
+    padding: 0!important;
+}
+.app-body-header{
+    display: flex;
+    align-items: center;
+    padding: 0 30px;
+    border-bottom: 2px solid var(--bs-border-color);
+}
+.app-body-header h3{
+    margin-bottom: 0;
+    padding: 3px 15px;
+    border-right: 2px solid #666;
 }
 .app-footer{
+    border-top: 2px solid var(--bs-border-color);
     background: #f9f9f9;
 }
 .nav-tabs.nav-line-tabs .nav-link.active {
@@ -21,15 +33,8 @@ body > .container, body > .container-fluid{
     padding: 0;
     margin: 0;
 }
-.header{
-    display: flex;
-    align-items: center;
-}
-.header h3{
-    margin-bottom: 0;
-    padding: 3px 15px;
-    border-right: 2px solid #666;
-}
+
+.header
 .table-box{
     position: relative;
     width: 100%;

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

@@ -77,6 +77,8 @@ function IwbTable(table, opts,isReload) {
 	const options = isReload ? defaultOptions : ($table.data('options') || defaultOptions)
 	const tableOptions = $.extend({}, options, opts || {})
 	let isSearch = false
+	$table.data('init',true)
+
 	function ajaxTable(opt) {
 		if (isSearch) {
 			return

+ 5 - 0
SourceCode/IntelligentRailwayCosting/app/views/static/project/budget.css

@@ -1,3 +1,8 @@
+
+#body_box{
+    width: 100%;
+    padding: 0 15px;
+}
 .project-box{
     width: 100%;
 }

+ 335 - 293
SourceCode/IntelligentRailwayCosting/app/views/static/project/budget_info.js

@@ -2,92 +2,67 @@ const table = '#table',
 	$modal = $('#modal'),
 	$modalQuota = $('#modal_quota')
 console.log(`加载项目:${project_id}`)
-const nav_tab_template = `
+InitBody()
+$(function () {
+	BuildChapterInfo()
+	InitFileUpload()
+})
+
+
+
+const nav_template = `<ul id="nav_tab" class="nav nav-tabs nav-line-tabs nav-line-tabs-2x fs-6"></ul><div class="tab-content" id="tab_content" style="height: calc(100% - 80px);"></div>`,
+	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 w-300px 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 d-none badge-primary fs-5 me-5"></span>
-												<div class="d-flex">
-													<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>
-											<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>
-															<option value="4">数据变更</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>
-															<option value="4">数据变更</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>
-															<option value="4">数据变更</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>
+	tab_content_template = `<div class="tab-pane h-100" id="iwb_tab_{0}" role="tabpanel">{1}</div>`,
+	table_add_task_btn_template = `<button type="button" class="task_add_btn btn btn-primary btn-sm" onclick="Add('{0}')">添加任务</button>`,
+	table_add_quota_btn_template = `<button type="button" class="quota_add_btn btn btn-primary btn-sm" onclick="Add_Quota('{0}')">添加定额</button>`,
+	table_collect_select_template = `<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>
+												<option value="4">数据变更</option>
+											</select>`,
+	table_template = `<div class="table-box table-responsive" data-id="{0}" id="table_box_{0}">
+								<div class="d-flex justify-content-between my-5">
+									<div class="">{1}</div>
+									<form class="search-box d-flex">
+										<div class="d-flex">
+											{2}
+											<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>
+												<option value="4">数据变更</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>
+												<option value="4">数据变更</option>
+											</select>
+											<input type="text" class="form-control form-control-sm w-200px" placeholder="请输入关键字" name="keyword" />
 										</div>
-									</div>
-								</div>`
-let _fileUploadDropzone = null
-$(function () {
-	GetBudgetInfo()
-	InitFileUpload()
-})
+										<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>`
+
+let _fileUploadDropzone = null;
+
 function InitFileUpload(){
 	const id = "#modal";
 	const dropzone = document.querySelector(id);
@@ -134,50 +109,47 @@ function InitFileUpload(){
 	// 	console.log('removedfile', file,_files)
 	// })
 }
-function GetBudgetInfo() {
-	IwbAjax_1({
-		url: `/api/project/budget/${project_id}`,
-		success: function (res) {
-			if (res.success) {
-				RenderTabs(res.data)
-			}else{
-				console.error(res.message)
-			}
-		},
-	})
-}
-
-function RenderTabs(data){
-	console.log('RenderTabs', data)
-	let str1 = '',
-		str2 = ''
-	if(data && data.length){
-		for (let i = 0; i < data.length; i++) {
-			const item = data[i]
-			str1 += nav_tab_template.format(item.budget_id, item.budget_code)
-			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)=>{
-		console.log('TAB', e)
-		const tab_id = $(e.target).data('id')
-		GetBudgetItems(tab_id)
-	})
- 	const firstTab = new bootstrap.Tab($tab.eq(0))
-	firstTab.show()
+function InitBody(){
+	const h= $('.app-wrapper').height()  - $('#breadcrumb_header').height() - $('.app-footer').height() -5
+	$('#body_box').html(`<div class="d-flex flex-row project-box w-100">
+						<div class="flex-row-auto h-100 left-box">
+							<div class="tree-dom w-300px h-100 overflow-auto" id="js-tree"></div>
+						</div>
+						<div class="flex-row-fluid flex-column right-box" style="display: none">
+							<div class="my-2 d-flex align-items-center box-header mt-5 flex-column-auto">
+								<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="fw-bolder me-5 title fs-2"></span>
+								<span class="badge d-none badge-primary fs-5 me-5"></span>
+								<div class="d-flex">
+									<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>
+							<div class="box-body  flex-column-fluid">
+								<div class="task w-100 h-100" style="display: none"></div>
+								<div class="quota w-100 h-100" style="display: none"></div>
+							</div>
+						</div>
+					</div>`)
+	$('#body_box .project-box').height(h)
 }
-
-function GetBudgetItems(id) {
-	const $tree = $(`#js-tree_${id}`)
-	// console.log('GetBudgetItems', id,$tree)
+function BuildChapterInfo(){
+	const $tree = $(`#js-tree`)
 	const opt = {
 		core: {
 			themes: {
@@ -190,7 +162,7 @@ function GetBudgetItems(id) {
 			data: function (node, callback) {
 				// console.log('TREE_NODE', node)
 				IwbAjax_1({
-					url: `/api/project/budget-item/${id}/${project_id}?c=${node?.data?.item_code || ''}`,
+					url: `/api/project/chapter/${project_id}?c=${node?.data?.item_code || ''}`,
 					success: res => {
 						if (res.success) {
 							console.log('TREE', res.data)
@@ -221,48 +193,51 @@ function GetBudgetItems(id) {
 	})
 	$tree.on('select_node.jstree', function (e, data) {
 		console.log('TREE_SELECTED', e, data)
-		RenderTabCondent(data.node?.data)
+		RenderRightBox(data.node?.data)
 	})
 	$tree.jstree(opt)
 }
-
-function RenderTabCondent(data) {
-	// console.log('RenderTabCondent', data)
-	const $tableBox = $(`#table_box_${data.budget_id}`),
-			$table = $(`#table_${data.budget_id}`)
+function RenderRightBox(data){
+	console.log('RenderRightBox', arguments)
+	const $rightBox= $('#body_box .right-box'),
+		$rightBoxHeader = $('#body_box .right-box .box-header'),
+		$rightBoxBody = $('#body_box .right-box .box-body'),
+		$taskBox = $rightBoxBody.find('.task'),
+		$quotaBox = $rightBoxBody.find('.quota')
+	$rightBoxBody.data('data',data)
+	$rightBox.find('input[name="budget_id"]').val(data.budget_id);
+	$rightBox.find('input[name="project_id"]').val(project_id);
+	$rightBox.find('input[name="item_id"]').val(data.item_id);
+	$rightBox.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)
-		}
-	})
+	$rightBoxHeader.find('.title').text(title)
+	$rightBoxHeader.find('[name="table_radio"]').prop("disabled",false).off('change.iwb')
+	$rightBoxHeader.find('[name="table_radio"]').on('change.iwb',function(){
+				const val = $(this).val()
+				if (val === 'quota') {
+					_quotaNavTab(data)
+				} else {
+					_taskTable(data)
+				}
+			})
 	if(data.children_count>0||data.chapter){
-		$tableBox.find('.table-title .title').text(title)
-		$tableBox.find('.table-title .badge').text('任务列表').removeClass('badge-success').addClass('badge-primary')
-		$tableBox.find('#task_radio').prop("checked",true)
-		_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)
+		_renderTask(data)
+	}else{
+		_renderQuota(data)
+	}
+	$rightBox.show()
+	function _renderTask(data){
+		$rightBoxHeader.find('.badge').text('任务列表').removeClass('badge-success').addClass('badge-primary')
+		$rightBoxHeader.find('#task_radio').prop("checked",true)
+		const budget_id =0
+		$taskBox.html(table_template.format(budget_id,table_add_task_btn_template.format(budget_id),table_collect_select_template))
+		_taskTable(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()
+	function _taskTable(data){
+		$quotaBox.hide()
+		const $table = $taskBox.find('.table')
 		IwbTable($table, {
-				url: `/api/task/list/${data.budget_id}/${project_id}/${data.item_code}`,
+				url: `/api/task/list/${project_id}/${data.item_code}`,
 				columns: [
 					{
 						title: '任务编号',
@@ -339,147 +314,220 @@ function RenderTabCondent(data) {
 						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>`
+								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})"><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>`
+								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})"><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>`
+									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})"><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>`
+									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})"><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>`
+										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})"><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>`
+										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})"><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>`
+										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})"><i class="ki-duotone ki-send fs-1"><span class="path1"></span><span class="path2"></span></i></button>`
 									} else if (row.send_status === 4) {
-										str += `<button type="button" class="btn btn-icon btn-sm btn-light-info" 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>`
+										str += `<button type="button" class="btn btn-icon btn-sm btn-light-info" data-bs-toggle="tooltip" data-bs-placement="top" title="重新发送" onclick="ReStartSendTask(${row.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>`
+									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})"><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 === 4) {
-									str += `<button type="button" class="btn btn-icon btn-sm btn-light-info" 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>`
+									str += `<button type="button" class="btn btn-icon btn-sm btn-light-info" data-bs-toggle="tooltip" data-bs-placement="top" title="重新处理" onclick="ReStartProcessTask(${row.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-danger" data-bs-toggle="tooltip" data-bs-placement="top" title="重新采集" onclick="ReStartCollectTask(${row.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 === 4) {
-								str += `<button type="button" class="btn btn-icon btn-sm btn-light-info" 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-info" data-bs-toggle="tooltip" data-bs-placement="top" title="重新采集" onclick="ReStartCollectTask(${row.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}, ${data.budget_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}, ${data.budget_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>`
+							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)
+		setTimeout(function () {$taskBox.show()},500)
+	}
+	function _renderQuota(data){
+		$rightBoxHeader.find('.badge').text('定额输入明细').removeClass('badge-primary').addClass('badge-success')
+		$rightBoxHeader.find('#task_radio').prop("disabled",true)
+		$rightBoxHeader.find('#quota_radio').prop("checked",true)
+		_quotaNavTab(data)
+	}
+	function _quotaNavTab(data){
+		$taskBox.hide()
+		if(!$quotaBox.find('#nav_tab').length){
+			$quotaBox.html(nav_template)
+			BuildBudgetInfo()
+		}else{
+			const data = $quotaBox.find('#nav_tab').data('budget-info') || {}
+			$quotaBox.html(nav_template)
+			RenderTabs(data)
+		}
+		// const $tab = $('#nav_tab li button[data-bs-toggle="tab"]')
+		// if ($tab.length) {
+		// 	const firstTab = new bootstrap.Tab($tab.eq(0))
+		// 	firstTab.show()
+		// }
+		setTimeout(function () {$quotaBox.show()},500)
 	}
-	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: '序号',
-					data: 'id',
-					width: '100px',
-				},
-				{
-					title: '工程或费用项目名称',
-					data: 'project_name',
-					width: 'auto',
-				},
-				{
-					title: '工程数量',
-					data: 'project_quantity',
-					width: '100px',
-				},
-				{
-					title: '单位',
-					data: 'unit',
-					width: '100px',
-				},
-				{
-					title: '定额编号',
-					data: 'quota_code',
-					width: '100px',
-				},
-				{
-					title: '状态',
-					data: 'status',
-					width: '150px',
-					render: (row) => {
-						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>`
-						}else if (row.process_status === 4){
-							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>`
-						}else if (row.send_status === 4){
-							str+= `<span class="badge badge-danger ms-3">数据变更</span>`
-						}
 
-						return str
+}
+function BuildBudgetInfo() {
+	IwbAjax_1({
+		url: `/api/project/budget/${project_id}`,
+		success: function (res) {
+			if (res.success) {
+				RenderTabs(res.data)
+			}else{
+				console.error(res.message)
+			}
+		},
+	})
+}
+function RenderTabs(data){
+	console.log('RenderTabs', data)
+	let str1 = '',
+		str2 = ''
+	if(data && data.length){
+		for (let i = 0; i < data.length; i++) {
+			const item = data[i]
+			str1 += nav_tab_template.format(item.budget_id, item.budget_code)
+			const tableStr = table_template.format(item.budget_id,table_add_quota_btn_template.format(item.budget_id),"")
+			const tabContent = tab_content_template.format(item.budget_id, tableStr)
+			// console.log('TAB_CONTENT', tabContent)
+			str2 += tabContent
+		}
+	}
+	const $tab = $('#nav_tab'),$content =$('#tab_content')
+	$tab.html(str1).data('budget-info', data)
+	$content.html(str2)
+	const $tab_btn = $tab.find('li button[data-bs-toggle="tab"]')
+	$tab_btn.on('shown.bs.tab',(e)=>{
+		console.log('TAB', e)
+		const tab_id = $(e.target).data('id'),
+			data = $("#body_box .right-box .box-body").data('data')
+		//
+		RenderQuotaTable(tab_id,data)
+	})
+	const firstTab = new bootstrap.Tab($tab_btn.eq(0))
+	firstTab.show()
+
+
+}
+function RenderQuotaTable(budget_id,data){
+	console.log('RenderQuotaTable', budget_id, data)
+	const $quotaBox = $('#body_box .right-box .box-body .quota')
+	const $table = $quotaBox.find(`#table_${budget_id}`)
+	IwbTable($table,{
+		url:`/api/quota/list/${budget_id}/${project_id}/${data.item_code}`,
+		columns: [
+			{
+				title: '序号',
+				data: 'id',
+				width: '100px',
+			},
+			{
+				title: '工程或费用项目名称',
+				data: 'project_name',
+				width: 'auto',
+			},
+			{
+				title: '工程数量',
+				data: 'project_quantity',
+				width: '100px',
+			},
+			{
+				title: '单位',
+				data: 'unit',
+				width: '100px',
+			},
+			{
+				title: '定额编号',
+				data: 'quota_code',
+				width: '100px',
+			},
+			{
+				title: '状态',
+				data: 'status',
+				width: '150px',
+				render: (row) => {
+					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>`
+					}else if (row.process_status === 4){
+						str+= `<span class="badge badge-danger">数据变更</span>`
 					}
-				},
-				{
-					title: '操作',
-					data: 'id',
-					width: '160px',
-					render: (row) => {
-						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.send_status === 4) {
-								str += `<button type="button" class="btn btn-icon btn-sm btn-light-info" 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.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>`
-						} else if (row.process_status === 4) {
-							str += `<button type="button" class="btn btn-icon btn-sm btn-light-info" 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+=`<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}, ${data.budget_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}, ${data.budget_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
+					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>`
+					}else if (row.send_status === 4){
+						str+= `<span class="badge badge-danger ms-3">数据变更</span>`
 					}
-				},
-			]
-		}, true)
-	}
 
+					return str
+				}
+			},
+			{
+				title: '操作',
+				data: 'id',
+				width: '160px',
+				render: (row) => {
+					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}, ${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}, ${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}, ${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}, ${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}, ${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 === 4) {
+							str += `<button type="button" class="btn btn-icon btn-sm btn-light-info" data-bs-toggle="tooltip" data-bs-placement="top" title="重新发送" onclick="ReStartSendQuota(${row.id}, ${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="ReStartProcessQuota(${row.id}, ${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 === 4) {
+						str += `<button type="button" class="btn btn-icon btn-sm btn-light-info" data-bs-toggle="tooltip" data-bs-placement="top" title="重新处理" onclick="ReStartProcessQuota(${row.id}, ${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+=`<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}, ${budget_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}, ${budget_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 Add(budget_id) {
+function SetBudgetData($el){
+	const $tableBox = $(`#body_box .right-box`)
+	$el.find('[name="budget_id"').val($tableBox.find('input[name="budget_id"]').val());
+	$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 Add() {
 	_fileUploadDropzone.removeAllFiles()
 	AddModal($modal, () => {
 		$modal.find('[name="task_id"]').val('0');
 		$modal.find('#delete_file_box').hide();
 		$modal.find('[name="delete_file"]').prop('checked',false)
-		SetBudgetData($modal,budget_id)
+		SetBudgetData($modal)
 	})
 }
 
@@ -510,13 +558,6 @@ function Edit(id) {
     })
 }
 
-function SetBudgetData($el,budget_id){
-	const $tableBox = $(`#table_box_${budget_id}`)
-	$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
 		formData = new FormData(),
@@ -550,41 +591,42 @@ function SaveProject() {
 		headers:{},
         body:formData,
 		modal:"#modal",
-		table:`#table_${budget_id}`
+		table:`#table_0`
     })
 }
 
-function Delete(id,budget_id){
-	ConfirmUrl('确定删除吗?',`/api/task/delete/${id}`,`#table_${budget_id}`)
+function Delete(id){
+	ConfirmUrl('确定删除吗?',`/api/task/delete/${id}`,`#table_0`)
 }
 
-function StartCollectTask(id,budget_id){
-	ConfirmUrl('确定开始采集吗?',`/api/task/start_collect/${id}`,`#table_${budget_id}`)
+function StartCollectTask(id){
+	ConfirmUrl('确定开始采集吗?',`/api/task/start_collect/${id}`,`#table_0`)
 }
-function ReStartCollectTask(id,budget_id){
-	ConfirmUrl('确定重新开始采集吗?',`/api/task/start_collect/${id}`,`#table_${budget_id}`)
+function ReStartCollectTask(id){
+	ConfirmUrl('确定重新开始采集吗?',`/api/task/start_collect/${id}`,`#table_0`)
 }
 
-function StartProcessTask(id,budget_id){
-	ConfirmUrl('确定开始处理吗?',`/api/task/start_process/${id}`,`#table_${budget_id}`)
+function StartProcessTask(id){
+	ConfirmUrl('确定开始处理吗?',`/api/task/start_process/${id}`,`#table_0`)
 }
 
-function ReStartProcessTask(id,budget_id){
-	ConfirmUrl('确定重新开始处理吗?',`/api/task/start_process/${id}`,`#table_${budget_id}`)
+function ReStartProcessTask(id){
+	ConfirmUrl('确定重新开始处理吗?',`/api/task/start_process/${id}`,`#table_0`)
 }
 
-function StartSendTask(id,budget_id){
-	ConfirmUrl('确定开始发送吗?',`/api/task/start_send/${id}`,`#table_${budget_id}`)
+function StartSendTask(id){
+	ConfirmUrl('确定开始发送吗?',`/api/task/start_send/${id}`,`#table_0`)
 }
-function ReStartSendTask(id,budget_id){
-	ConfirmUrl('确定重新开始发送吗?',`/api/task/start_send/${id}`,`#table_${budget_id}`)
+function ReStartSendTask(id){
+	ConfirmUrl('确定重新开始发送吗?',`/api/task/start_send/${id}`,`#table_0`)
 }
 
 function Add_Quota(budget_id,) {
 	AddModal($modalQuota, () => {
+		SetBudgetData($modalQuota)
 		$modalQuota.find('[name="quota_id"]').val('0');
 		$modalQuota.find('[name="task_id"]').val('0');
-		SetBudgetData($modalQuota, budget_id)
+		$modalQuota.find('[name="budget_id"]').val(budget_id);
 	})
 }
 

+ 2 - 4
SourceCode/IntelligentRailwayCosting/app/views/templates/project/budget_info.html

@@ -3,16 +3,14 @@
 <link rel="stylesheet" href="{{ url_for('static', filename='project/budget.css') }}" />
 {% endblock %} {% block page_content %}
 
-<div class="header my-5" id="breadcrumb_header">
+<div class="app-body-header h-50px" id="breadcrumb_header">
 	<h3>概算信息</h3>
 	<ol class="breadcrumb breadcrumb-dot text-muted fs-6 fw-semibold ms-5">
 		<li class="breadcrumb-item"><a href="{{ url_for('project.index') }}" class="">项目管理</a></li>
 		<li class="breadcrumb-item text-muted">概算信息</li>
 	</ol>
 </div>
-<ul id="nav_tab" class="nav nav-tabs nav-line-tabs nav-line-tabs-2x fs-6"></ul>
-
-<div class="tab-content" id="tab_content" style="height: calc(100% - 80px);"></div>
+<div class="box w-100" id="body_box"></div>
 <div class="modal fade" id="modal" tabindex="-1" aria-hidden="true">
 	<div class="modal-dialog modal-dialog-centered">
 		<div class="modal-content rounded">

+ 19 - 17
SourceCode/IntelligentRailwayCosting/app/views/templates/project/index.html

@@ -3,30 +3,32 @@
 {% block title %}项目管理{% endblock %}
 
 {% block page_content %}
- <div class="header my-5">
+ <div class="app-body-header h-50px">
     <h3>项目管理</h3>
     <ol class="breadcrumb breadcrumb-dot text-muted fs-6 fw-semibold ms-5">
         <li class="breadcrumb-item text-muted">项目管理</li>
     </ol>
 </div>
-<div class="table-box table-responsive">
-    <div class="d-flex justify-content-between mb-5 mx-10">
-        <div>
-            <button class="btn btn-primary btn-sm" onclick="Add()">添加</button>
-        </div>
-        <form class="search-box d-flex">
-            <div class="d-flex">
-                <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 class="mx-10 my-5">
+    <div class="table-box table-responsive">
+        <div class="d-flex justify-content-between mb-5 mx-10">
+            <div>
+                <button class="btn btn-primary btn-sm" onclick="Add()">添加</button>
             </div>
-        </form>
+            <form class="search-box d-flex">
+                <div class="d-flex">
+                    <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">
+        </table>
+        <div class="pagination-row"></div>
     </div>
-    <table class="table table-striped table-bordered table-hover  table-rounded" id="table">
-    </table>
-    <div class="pagination-row"></div>
 </div>
 <div class="modal fade" id="modal" tabindex="-1" aria-hidden="true">
     <div class="modal-dialog modal-dialog-centered">