浏览代码

Update 界面及功能根据20250430需求优化调整

yue 2 月之前
父节点
当前提交
7fb0944a32

+ 2 - 2
SourceCode/IntelligentRailwayCosting/.script/cmd/Mysql_BuildRcApp_1.0.3.run.xml → SourceCode/IntelligentRailwayCosting/.script/cmd/Mysql_BuildRcApp_1.0.4.run.xml

@@ -1,8 +1,8 @@
 <component name="ProjectRunConfigurationManager">
-  <configuration default="false" name="Mysql_BuildRcApp_1.0.3" type="docker-deploy" factoryName="dockerfile" server-name="104">
+  <configuration default="false" name="Mysql_BuildRcApp_1.0.4" type="docker-deploy" factoryName="dockerfile" server-name="104">
     <deployment type="dockerfile">
       <settings>
-        <option name="imageTag" value="railway_costing-app_mysql:1.0.3" />
+        <option name="imageTag" value="railway_costing-app_mysql:1.0.4" />
         <option name="buildOnly" value="true" />
         <option name="contextFolderPath" value="." />
         <option name="sourceFilePath" value="docker/Dockerfile" />

+ 1 - 0
SourceCode/IntelligentRailwayCosting/.script/init_mysql.sql

@@ -53,6 +53,7 @@ CREATE TABLE IF NOT EXISTS project_quota (
     ex_row VARCHAR(4000) COMMENT '该行内容',
     ex_unit VARCHAR(50) COMMENT 'excel中给出的单位',
     ex_amount FLOAT COMMENT 'excel中给出的数量',
+    is_change INT NOT NULL DEFAULT 1 COMMENT '是否变更(0:未变更,1:已变更)',
     send_status INT NOT NULL DEFAULT 0 COMMENT '推送状态(0:未推送,1:推送中 ,2:已推送, 3:推送失败, 4:已修改)',
     send_time DATETIME COMMENT '推送时间',
     send_error VARCHAR(4000) COMMENT '推送错误信息',

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

@@ -85,6 +85,7 @@ CREATE TABLE [dbo].[project_quota] (
     [ex_row] NVARCHAR(4000) NULL,
     [ex_unit] NVARCHAR(50) NULL,
     [ex_amount] FLOAT NULL,
+    [is_change] INT NOT NULL DEFAULT 1,
     [send_status] INT NOT NULL DEFAULT 0,
     [send_time] DATETIME NULL,
     [send_error] NVARCHAR(4000) NULL,

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

@@ -26,6 +26,7 @@ class ProjectQuotaDto(BaseModel):
     ex_row: Optional[str] = None
     ex_unit: Optional[str] = None
     ex_amount: Optional[float] = None
+    is_change: int = 1
     send_status: int = 0
     send_time: Optional[datetime] = None
     send_error: Optional[str] = None
@@ -56,6 +57,7 @@ class ProjectQuotaDto(BaseModel):
             ex_row=model.ex_row,
             ex_unit=model.ex_unit,
             ex_amount=model.ex_amount,
+            is_change=model.is_change,
             send_status=model.send_status,
             send_time=model.send_time,
             send_error=model.send_error,

+ 6 - 0
SourceCode/IntelligentRailwayCosting/app/core/models/project_quota.py

@@ -25,6 +25,12 @@ class ProjectQuotaModel(Base):
     ex_row = Column(String(5000), comment="该⾏内容,由逗号连接多个单元格得到")
     ex_unit = Column(String(50), comment="excel中给出的单位")
     ex_amount = Column(Float, comment="excel中给出的数量")
+    is_change = Column(
+        Integer,
+        nullable=False,
+        default=1,
+        comment="是否变更(0:未变更,1:已变更)",
+    )
     send_status = Column(
         Integer,
         nullable=False,

+ 1 - 1
SourceCode/IntelligentRailwayCosting/app/executor/task_sender.py

@@ -44,6 +44,7 @@ class TaskSender:
             self._logger.debug(f"开始推送定额输入[{quota.id}]")
             self._quota_store.update_send_status(quota.id, SendStatusEnum.SUCCESS.value)
             quota_input = QuotaInputDto.from_quota_dto(quota)
+            quota_input.project_name = f"{quota_input.project_name}{configs.app.ai_flag if quota.is_change else ''}"
             self._save_quota(quota_input, quota.project_id)
             # self._quota_store.update_send_status(quota.id, SendStatusEnum.SUCCESS.value)
             self._logger.debug(f"推送定额输入[{quota.id}]完成")
@@ -59,7 +60,6 @@ class TaskSender:
     def _save_quota(self, quota: QuotaInputDto, project_id: str):
         try:
             # data = self._quota_input_store.get_quota(project_id, quota.budget_id,quota.item_id,quota.quota_code)
-            quota.project_name = f"{quota.project_name}{configs.app.ai_flag}"
             if quota.quota_id and quota.quota_id > 0:
                 self._logger.debug(
                     f"修改定额输入[{quota.quota_id}]:{quota.project_name} {quota.quota_code} {quota.quota_id}"

+ 34 - 1
SourceCode/IntelligentRailwayCosting/app/routes/project_quota.py

@@ -34,7 +34,7 @@ def get_page_list(budget_id: int, project_id: str, item_code: str):
 )
 @Permission.authorize
 def get_quotas_by_task_paginated(
-    task_id: int, budget_id: int, project_id: str, item_code: str
+        task_id: int, budget_id: int, project_id: str, item_code: str
 ):
     try:
         data = request.get_json()
@@ -83,6 +83,39 @@ def save_quota():
         return ResponseBase.error(f"保存定额条目失败:{str(e)}")
 
 
+@project_quota_api.route("/edit", methods=["POST"])
+@Permission.authorize
+def edit_by_key():
+    try:
+        data = request.get_json()
+        id = data.get("id")
+        if not id:
+            return ResponseBase.error(f"未能获取到定额条目ID")
+        key = data.get("key")
+        value = data.get("val")
+        msg = quota_service.edit_by_key(id, key, value)
+        if msg:
+            return ResponseBase.error(msg)
+        return ResponseBase.success()
+    except Exception as e:
+        return ResponseBase.error(f"编辑定额条目失败:{str(e)}")
+
+
+@project_quota_api.route("/edit_quota_code_batch", methods=["POST"])
+@Permission.authorize
+def edit_quota_code_batch():
+    try:
+        data = request.get_json()
+        ids = data.get("ids", "")
+        quota_code = data.get("quota_code", "")
+        msg = quota_service.edit_quota_code_batch(ids, quota_code)
+        if msg:
+            return ResponseBase.error(msg)
+        return ResponseBase.success()
+    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):

+ 70 - 3
SourceCode/IntelligentRailwayCosting/app/services/project_quota.py

@@ -118,16 +118,13 @@ class ProjectQuotaService:
             # 业务验证
             if quota_dto.id == 0:
                 quota_dto = self.create_quota(quota_dto)
-
             else:
                 quota_dto = self.update_quota(quota_dto)
-
                 if quota_dto.send_status != SendStatusEnum.NEW.value:
                     self.update_send_status(quota_dto.id, SendStatusEnum.CHANGE.value)
             return quota_dto
         except Exception as e:
             self._logger.error(f"保存定额条目失败: {str(e)}")
-
             raise
 
     def create_quota(self, quota_dto: ProjectQuotaDto) -> ProjectQuotaDto:
@@ -207,6 +204,76 @@ class ProjectQuotaService:
             )
             raise
 
+    def edit_by_key(self, id: int, key: str, value: str):
+        try:
+            # 业务验证
+            # dict_data = {"id": id, key: value}
+
+            # quota_dto = ProjectQuotaDto(**dict_data)
+            quota_dto = self.get_quota_dto(id)
+            if not quota_dto:
+                raise ValueError("未查询到定额条目")
+            log_data = utils.to_str(quota_dto.to_dict())
+            setattr(quota_dto, key, value)
+            if quota_dto.is_change == 0 and key == "entry_name":
+                quota_dto.is_change = 0
+            else:
+                quota_dto.is_change = 1
+            # self.update_process_status(quota_dto.id,4)
+            LogRecordHelper.log_success(
+                OperationType.UPDATE,
+                OperationModule.QUOTA,
+                f"修改定额条目[{key}]: {value}",
+                log_data,
+                utils.to_str({"id": id, key: value}),
+            )
+            self.store.update_quota(quota_dto)
+            return None
+        except Exception as e:
+            self._logger.error(f"更新项目定额失败[{key}]: {value}, {str(e)}")
+            LogRecordHelper.log_fail(
+                OperationType.UPDATE,
+                OperationModule.QUOTA,
+                f"修改定额条目失败[{key}]: {value}, {str(e)}",
+                utils.to_str({"id": id, key: value}),
+            )
+            raise
+
+    def edit_quota_code_batch(self, ids: str, quota_code: str):
+        try:
+            if not ids:
+                return "请选择要变更的章节"
+            ids = ids.split(",")
+            msg = ""
+            for id in ids:
+                try:
+                    quota_dto = self.get_quota_dto(int(id))
+                    if not quota_dto:
+                        msg += f"{id}不存在;"
+                        continue
+                    quota_dto.quota_code = quota_code
+                    quota_dto.is_change = 1
+                    self.store.update_quota(quota_dto)
+                except Exception as e:
+                    msg += f"{id}: {str(e)};"
+            LogRecordHelper.log_fail(
+                OperationType.UPDATE,
+                OperationModule.QUOTA,
+                f"批量修改定额: {ids}",
+                utils.to_str({"ids": ids, "quota_code": quota_code}),
+            )
+            return None if msg == "" else msg
+        except Exception as e:
+            msg = f"批量修改定额失败: {str(e)}"
+            self._logger.error(msg)
+            LogRecordHelper.log_fail(
+                OperationType.UPDATE,
+                OperationModule.QUOTA,
+                f"批量修改定额失败: {str(e)}",
+                utils.to_str({"ids": ids, "quota_code": quota_code}),
+            )
+            return msg
+
     def delete_quota(self, quota_id: int) -> bool:
         """删除项目定额
 

+ 12 - 1
SourceCode/IntelligentRailwayCosting/app/stores/railway_costing_mysql/project_quota.py

@@ -115,7 +115,7 @@ class ProjectQuotaStore:
 
             # 分页
             query = (
-                query.order_by(ProjectQuotaModel.id.asc())
+                query.order_by(ProjectQuotaModel.item_code.asc())
                 .offset((page - 1) * page_size)
                 .limit(page_size)
             )
@@ -220,6 +220,7 @@ class ProjectQuotaStore:
                 ex_row=quota_dto.ex_row,
                 ex_unit=quota_dto.ex_unit,
                 ex_amount=quota_dto.ex_amount,
+                is_change=quota_dto.is_change,
                 send_status=SendStatusEnum.NEW.value,
                 send_time=quota_dto.send_time,
                 send_error=quota_dto.send_error,
@@ -256,6 +257,7 @@ class ProjectQuotaStore:
             quota.ex_row = quota_dto.ex_row
             quota.ex_unit = quota_dto.ex_unit
             quota.ex_amount = quota_dto.ex_amount
+            quota.is_change = quota_dto.is_change
             quota.send_status = quota_dto.send_status
             quota.send_error = None
             quota.updated_by = self.current_user.username
@@ -264,6 +266,15 @@ class ProjectQuotaStore:
             quota = db_session.merge(quota)
             return ProjectQuotaDto.from_model(quota)
 
+    def update_quota_change(self, quota_id: int):
+        quota = self.get_quota(quota_id)
+        if not quota:
+            raise Exception(f"定额条目[{quota_id}]不存在")
+        with db_helper.mysql_session(self._database) as db_session:
+            quota.is_change = 1
+            quota = db_session.merge(quota)
+            return ProjectQuotaDto.from_model(quota)
+
     def update_quota_chapter(
             self, quota_id: int, item_id: int, item_code: str
     ) -> Optional[ProjectQuotaDto]:

+ 45 - 18
SourceCode/IntelligentRailwayCosting/app/stores/railway_costing_sqlserver/project_quota.py

@@ -22,14 +22,14 @@ class ProjectQuotaStore:
         return self._current_user
 
     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,
-        send_status: Optional[int] = None,
+            self,
+            budget_id: int,
+            project_id: str,
+            item_code: str,
+            page: int = 1,
+            page_size: int = 10,
+            keyword: Optional[str] = None,
+            send_status: Optional[int] = None,
     ):
         """分页查询定额列表
 
@@ -81,15 +81,15 @@ class ProjectQuotaStore:
             return {"total": total_count, "data": quotas}
 
     def get_quotas_by_task_paginated(
-        self,
-        task_id: int,
-        budget_id,
-        project_id,
-        item_code,
-        page: int = 1,
-        page_size: int = 10,
-        keyword: Optional[str] = None,
-        send_status: Optional[int] = None,
+            self,
+            task_id: int,
+            budget_id,
+            project_id,
+            item_code,
+            page: int = 1,
+            page_size: int = 10,
+            keyword: Optional[str] = None,
+            send_status: Optional[int] = None,
     ):
         with db_helper.sqlserver_query_session(self._database) as db_session:
             query = db_session.query(ProjectQuotaModel).filter(
@@ -176,7 +176,7 @@ class ProjectQuotaStore:
         return ProjectQuotaDto.from_model(quota) if quota else None
 
     def get_quota_by_quota_input(
-        self, project_id: str, budget_id: int, quota_input_id: int
+            self, project_id: str, budget_id: int, quota_input_id: int
     ):
         with db_helper.sqlserver_query_session(self._database) as db_session:
             quota = (
@@ -211,6 +211,7 @@ class ProjectQuotaStore:
                 item_code=quota_dto.item_code,
                 quota_id=quota_dto.quota_id,
                 quota_code=quota_dto.quota_code,
+                quota_adjustment=quota_dto.quota_adjustment,
                 entry_name=quota_dto.entry_name,
                 units=quota_dto.units,
                 amount=quota_dto.amount,
@@ -220,6 +221,7 @@ class ProjectQuotaStore:
                 ex_unit=quota_dto.ex_unit,
                 ex_amount=quota_dto.ex_amount,
                 send_status=SendStatusEnum.NEW.value,
+                is_change=quota_dto.is_change,
                 send_time=quota_dto.send_time,
                 send_error=quota_dto.send_error,
                 created_by=quota_dto.created_by or self.current_user.username,
@@ -246,6 +248,7 @@ class ProjectQuotaStore:
         with db_helper.sqlserver_session(self._database) as db_session:
             quota.quota_id = quota_dto.quota_id
             quota.quota_code = quota_dto.quota_code
+            quota.quota_adjustment = quota_dto.quota_adjustment
             quota.entry_name = quota_dto.entry_name
             quota.units = quota_dto.units
             quota.amount = quota_dto.amount
@@ -254,6 +257,7 @@ class ProjectQuotaStore:
             quota.ex_row = quota_dto.ex_row
             quota.ex_unit = quota_dto.ex_unit
             quota.ex_amount = quota_dto.ex_amount
+            quota.is_change = 1
             quota.send_status = SendStatusEnum.CHANGE.value
             quota.send_error = None
             quota.updated_by = self.current_user.username
@@ -262,6 +266,29 @@ class ProjectQuotaStore:
             quota = db_session.merge(quota)
             return ProjectQuotaDto.from_model(quota)
 
+    def update_quota_change(self, quota_id: int):
+        quota = self.get_quota(quota_id)
+        if not quota:
+            raise Exception(f"定额条目[{quota_id}]不存在")
+        with db_helper.sqlserver_session(self._database) as db_session:
+            quota.is_change = 1
+            quota = db_session.merge(quota)
+            return ProjectQuotaDto.from_model(quota)
+
+    def update_quota_chapter(
+            self, quota_id: int, item_id: int, item_code: str
+    ) -> Optional[ProjectQuotaDto]:
+        quota = self.get_quota(quota_id)
+        if not quota:
+            raise Exception(f"定额条目[{quota_id}]不存在")
+        with db_helper.sqlserver_session(self._database) as db_session:
+            quota.item_id = item_id
+            quota.item_code = item_code
+            quota.updated_by = self.current_user.username
+            quota.updated_at = datetime.now()
+            quota = db_session.merge(quota)
+            return ProjectQuotaDto.from_model(quota)
+
     def delete_quota(self, quota_id: int) -> bool:
         """删除定额
 

+ 33 - 10
SourceCode/IntelligentRailwayCosting/app/views/static/base/css/styles.css

@@ -35,7 +35,8 @@
     color: var(--bs-primary);
 }
 
-body > .container, body > .container-fluid {
+body>.container,
+body>.container-fluid {
     padding: 0;
     margin: 0;
 }
@@ -47,11 +48,14 @@ body > .container, body > .container-fluid {
     min-height: 300px;
 }
 
-.table-box, .table-box .table {
+.table-box,
+.table-box .table {
     width: 100%;
+    table-layout: fixed;
 }
 
-.table-box th, .table-box td {
+.table-box th,
+.table-box td {
     text-align: center;
     vertical-align: middle;
     height: 40px;
@@ -64,26 +68,26 @@ body > .container, body > .container-fluid {
     height: 45px;
 }
 
-.table-box td > .link:hover {
+.table-box td>.link:hover {
     border-bottom: 2px solid;
 }
 
-.table-box td > span {
+.table-box td>span {
     font-size: 13px;
 }
 
-.table-box td > .btn {
+.table-box td>.btn {
     padding: calc(.2rem + 1px) calc(.4rem + 1px) !important;
     margin: 0 5px;
     --bs-btn-border-radius: .3rem;
 }
 
-.table-box td > .btn-icon {
+.table-box td>.btn-icon {
     width: 25px !important;
     height: 25px !important;
 }
 
-.table-box td > .link:hover {
+.table-box td>.link:hover {
     border-bottom: 2px solid;
 }
 
@@ -112,6 +116,23 @@ body > .container, body > .container-fluid {
     display: inline-block;
 }
 
+.table-box th .form-check,
+.table-box td .form-check {
+    display: inline-flex
+}
+
+.edit-td .edit-label.hide {
+    display: none;
+}
+
+.edit-td .edit-td-input {
+    display: none;
+}
+
+.edit-td .edit-td-input.show {
+    display: block;
+}
+
 .pagination-row {
     width: 100%;
     display: flex;
@@ -123,11 +144,13 @@ body > .container, body > .container-fluid {
     opacity: .5;
 }
 
-.form-check-input, .form-check-label {
+.form-check-input,
+.form-check-label {
     cursor: pointer;
 }
 
-.form-check-input:disabled, .form-check-input:disabled + .form-check-label {
+.form-check-input:disabled,
+.form-check-input:disabled+.form-check-label {
     cursor: not-allowed;
 }
 

+ 1 - 1
SourceCode/IntelligentRailwayCosting/app/views/static/base/js/select2tree.js

@@ -1,6 +1,6 @@
 (function ($) {
     $.fn.select2tree = function (options) {
-        console.log("select2tree")
+        // console.log("select2tree")
         const defaults = {
             language: "zh-CN",
             minimumResultsForSearch: -1

+ 158 - 22
SourceCode/IntelligentRailwayCosting/app/views/static/base/js/utils.js

@@ -17,7 +17,7 @@ function IwbAjax(opt) {
     if (opt.data === undefined) {
         opt.data = {}
     }
-    opt = $.extend({}, {isAlert: true,}, opt)
+    opt = $.extend({}, { isAlert: true, }, opt)
     fetch(opt.url, {
         method: opt.method,
         headers: opt.headers || {
@@ -27,7 +27,7 @@ function IwbAjax(opt) {
     })
         .then((response) => response.json())
         .then((data) => {
-            console.log(`[${opt.url}]返回数据:`, data)
+            // console.log(`[${opt.url}]返回数据:`, data)
             if (data.success) {
                 if (opt.isAlert) {
                     MsgSuccess('操作成功', data.message || "")
@@ -67,11 +67,12 @@ function IwbTable(table, opts, isReload) {
     const defaultOptions = {
         checkBox: false,
         idFiled: 'id',
+        isDragColumn: false,
         tableCheckboxName: 'table-id',
         selected_ids: new Set(),
         selected_rows: new Set(),
         rows: new Set(),
-        checkBoxWidth: 50,
+        checkBoxWidth: 40,
         checkBoxDisable: undefined,
         pageSize: 15,
         pageNum: 1,
@@ -91,7 +92,7 @@ function IwbTable(table, opts, isReload) {
         }
         $table.data('options', opt)
         loading()
-        const data = $.extend({}, opt.search, {pageNum: opt.pageNum, pageSize: opt.pageSize})
+        const data = $.extend({}, opt.search, { pageNum: opt.pageNum, pageSize: opt.pageSize })
         fetch(opt.url, {
             method: 'POST',
             headers: {
@@ -141,44 +142,58 @@ function IwbTable(table, opts, isReload) {
             check_all = true
         // console.log('渲染表格:', opt.selected_ids,rows, total)
         if (opt.checkBox) {
-            head_str += `<th style="${opt.checkBoxWidth ? `width: ${opt.checkBoxWidth};` : ``}">
+            head_str += `<th style="${opt.checkBoxWidth ? `width: ${opt.checkBoxWidth}px;` : ``}">
 						${checkBoxDiv.format('', `${opt.tableCheckboxName}_head`, '')}
 					</th>`
         }
         for (let i = 0; i < opt.columns.length; i++) {
             const col = opt.columns[i]
-            head_str += `<th style="${col.width ? `width: ${col.width};` : ``}${col.style ? `${col.style};` : ``}">${col.title}</th>`
+            const style = `${col.width ? `width: ${col.width};` : ``}${col.thStyle ? `${col.thStyle};` : ``}`
+            head_str += `<th style="${style}">${col.title}</th>`
         }
         if (rows && rows.length) {
             for (let i = 0; i < rows.length; i++) {
                 const row = rows[i]
-                body_str += '<tr>'
+                body_str += `<tr data-id="${row[opt.idFiled]}">`
                 if (opt.checkBox) {
                     const disabled_checkbox = opt.checkBoxDisable && opt.checkBoxDisable(row) ? 'disabled' : ''
                     if (opt.selected_ids.size && opt.selected_ids.has(row[opt.idFiled] + "")) {
                         if (disabled_checkbox === '') {
-                            body_str += `<td>${checkBoxDiv.format(row[opt.idFiled], `${opt.tableCheckboxName}`, `checked`)}</td>`
+                            body_str += `<td style="${opt.checkBoxWidth ? `width: ${opt.checkBoxWidth}px;` : ``}">${checkBoxDiv.format(row[opt.idFiled], `${opt.tableCheckboxName}`, `checked`)}</td>`
                         } else {
-                            body_str += `<td>${checkBoxDiv.format(row[opt.idFiled], `${opt.tableCheckboxName}`, `${disabled_checkbox}`)}</td>`
+                            body_str += `<td style="${opt.checkBoxWidth ? `width: ${opt.checkBoxWidth}px;` : ``}">${checkBoxDiv.format(row[opt.idFiled], `${opt.tableCheckboxName}`, `${disabled_checkbox}`)}</td>`
                         }
                     } else {
                         check_all = false
-                        body_str += `<td>${checkBoxDiv.format(row[opt.idFiled], `${opt.tableCheckboxName}`, `${disabled_checkbox}`)}</td>`
+                        body_str += `<td style="${opt.checkBoxWidth ? `width: ${opt.checkBoxWidth}px;` : ``}">${checkBoxDiv.format(row[opt.idFiled], `${opt.tableCheckboxName}`, `${disabled_checkbox}`)}</td>`
                     }
                 }
                 for (let j = 0; j < opt.columns.length; j++) {
-                    const col = opt.columns[j]
+                    const col = opt.columns[j],
+                        style = `${col.tdStyle ? `${col.tdStyle};` : ``}`,
+                        classStr = `${col.isEdit ? 'edit-td' : ''}`,
+                        col_data = row[col.data] === undefined || row[col.data] === null ? '-' : row[col.data]
+                    body_str += `<td class="${classStr}" style="${style}" data-key="${col.data}" data-val="${col_data}">`
+                    let str = ''
                     if (col.render) {
-                        body_str += `<td>${col.render(row, rows)}</td>`
+                        str += col.render(row, rows)
                     } else {
-                        body_str += `<td>${row[col.data] === undefined || row[col.data] === null ? '-' : row[col.data]}</td>`
+                        str += col_data
                     }
+                    if (col.isEdit) {
+                        body_str += col.render ? str : `<span class="edit-label"><span class="edit-text">${str}</span></span>`
+                        body_str += `<div class="edit-td-input"><input class="form-control form-control-sm" data-col="" value="${col_data}"/></div>`
+                    } else {
+                        body_str += str
+                    }
+                    body_str += `</td>`
                 }
                 body_str += '</tr>'
             }
         } else {
             body_str += '<tr><td colspan="' + opt.columns.length + '" class="text-center">暂无数据</td></tr>'
         }
+
         $tableBox.fadeOut(300, () => {
             let page_str = formatPage(opt.pageNum, opt.pageSize, total)
             $table.parent().find('.pagination-row').html(page_str)
@@ -222,10 +237,69 @@ function IwbTable(table, opts, isReload) {
             if (check_all) {
                 $tableBox.find(`input[name=${opt.tableCheckboxName}_head]`).prop('checked', true)
             }
+            $tableBox.find(`td.edit-td`).on("dblclick", function () {
+                const $this = $(this), $input = $this.find('.edit-td-input'), $label = $this.find('.edit-label')
+                // $tableBox.find(`.edit-td-input`).hide()
+                // $tableBox.find(`.edit-label`).show()
+                $(document).on("click.td-edit", function (event) {
+                    if ($(event.target).closest($this).length) {
+                        return
+                    }
+                    $(document).off("click.td-edit")
+                    $input.off("keyup.edit").hide();
+                    $label.show()
+                })
+                $label.hide()
+                $input.off("keyup.edit").on("keyup.edit", function (event) {
+                    if (event.keyCode === 13) {
+                        const val = $input.find('.form-control').val(), key = $this.data('key')
+                        if (!val) {
+                            MsgError('不能为空')
+                            return
+                        }
+                        if (val === $this.data('val')) {
+                            $input.off("keyup.edit").hide();
+                            $label.show()
+                            return
+                        }
+                        IwbAjax({
+                            url: opt.editUrl,
+                            data: {
+                                id: $this.closest('tr').data('id'),
+                                key: key,
+                                val: val
+                            },
+                            success: function (res) {
+                                if (res.success) {
+                                    $this.data('val', val)
+                                    $label.find('.edit-text').text(val)
+                                    if ($label.attr('data-bs-original-title')) {
+                                        $label.attr('data-bs-original-title', val)
+                                    }
+                                    $input.off("keyup.edit").hide();
+                                    $label.show()
+                                } else {
+                                    $input.val($this.data('val'))
+                                    MsgError(res.msg)
+                                }
+                            }
+                        })
+
+                    }
+                }).show()
+                $input.focus()
+            })
+
             $tableBox.fadeIn(500, function () {
                 $tableBox.find(`[data-bs-toggle="tooltip"]`).each(function () {
                     new bootstrap.Tooltip(this)
                 })
+                reSizeTdOneLine($table)
+
+                if (opt.isDragColumn) {
+                    dragTableColumn($table)
+                }
+
             })
         })
     }
@@ -237,14 +311,16 @@ function IwbTable(table, opts, isReload) {
 
         let str = '<div class=" d-flex align-items-center justify-content-center justify-content-md-start dt-toolbar">'
         // 每页显示条数选择
+
+        str += `<div class="dt-info"><strong> 共 ${total} 条</strong>,当前第 ${pageNum}/${totalPages} 页,每页</div>`
         str += `<div><select class="form-select form-select-solid form-select-sm" onchange="IwbTableChangePageSize(this)">`
-        ;[15, 25, 50, 100].forEach((size) => {
-            str += `<option value="${size}" ${pageSize === size ? ' selected' : ''}>${size}</option>`
-        })
-        str += '</select></div>'
+            ;[15, 25, 50, 100].forEach((size) => {
+                str += `<option value="${size}" ${pageSize === size ? ' selected' : ''}>${size}</option>`
+            })
+        str += '</select>条</div>'
+        str += '</div>'
 
         // 显示记录信息
-        str += `<div class="dt-info">当前第 ${pageNum}/${totalPages} 页  共 ${total} 条</div></div>`
 
         // 分页导航
         str += '<div class="d-flex align-items-center justify-content-center justify-content-md-end"><div class="dt-paging paging_simple_numbers"><nav aria-label="pagination"><ul class="pagination">'
@@ -296,6 +372,65 @@ function IwbTable(table, opts, isReload) {
         return str
     }
 
+    function dragTableColumn(table) {
+        const $table = $(table), opt = $table.data('options'), tableEl = $table[0], rows = tableEl.rows,
+            header = rows[0],
+            col_length = header.cells.length, col_start = opt.checkBox ? 1 : 0;
+        let self = { mouseDown: false }, tableX = header.clientWidth;
+
+        for (let i = 0; i < rows.length; i++) {
+            for (let j = col_start; j < col_length; j++) {
+                const col = rows[i].cells[j];
+                col.onmousedown = function (event) {
+                    if (self.mouseDown === true) {
+                        return;
+                    }
+                    self.el = this;
+                    self.th = header.cells[j];
+                    if (event.offsetX > this.offsetWidth - 10) {
+                        self.mouseDown = true;
+                        self.oldX = event.x;
+                        self.oldWidth = self.el.offsetWidth;
+                    }
+                };
+                col.onmousemove = function (event) {
+                    if (event.offsetX > this.offsetWidth - 10) {
+                        this.style.cursor = 'col-resize';
+                    } else {
+                        this.style.cursor = 'default';
+                    }
+                    if (self.mouseDown === true) {
+                        self.el.style.cursor = 'default';
+                        if (self.oldWidth + (event.x - self.oldX) > 0) {
+                            self.width = self.oldWidth + (event.x - self.oldX);
+                        }
+                        self.th.style.width = self.width + 'px';
+                        reSizeTdOneLine(tableEl)
+                        tableEl.style.width = tableX + (event.x - self.oldX) + 'px';
+                        self.el.style.cursor = 'col-resize';
+                    }
+                };
+            }
+        }
+
+        tableEl.onmouseup = function () {
+            if (self === undefined) {
+                self = this;
+            }
+            self.mouseDown = false;
+            self.el.style.cursor = 'default';
+            // tableX = header.clientWidth;
+            reSizeTdOneLine(tableEl)
+        };
+
+    }
+
+    function reSizeTdOneLine(table) {
+        $(table).find('td .one-line').each(function () {
+            $(this).width(`${$(this).parent().width() - 10}px`)
+        })
+    }
+
     ajaxTable(tableOptions)
 }
 
@@ -311,7 +446,7 @@ function IwbTableSearch(that) {
             delete search[$(el).attr('name')]
         }
     })
-    IwbTable($table, {search: search, pageNum: 1})
+    IwbTable($table, { search: search, pageNum: 1, selected_ids: new Set(), selected_rows: new Set() })
 }
 
 function IwbTableResetSearch(that) {
@@ -322,13 +457,13 @@ function IwbTableResetSearch(that) {
 
 function IwbTableJumpPage(that, pageNum) {
     const $table = $(that).closest('.table-box').find('.table')
-    IwbTable($table, {pageNum: pageNum})
+    IwbTable($table, { pageNum: pageNum })
 }
 
 function IwbTableChangePageSize(that) {
     const $table = $(that).closest('.table-box').find('.table')
     const pageSize = parseInt($(that).val())
-    IwbTable($table, {pageSize: pageSize})
+    IwbTable($table, { pageSize: pageSize })
 }
 
 function IwbTableGetSelectedIds(table) {
@@ -340,6 +475,7 @@ function IwbTableGetSelectedRows(table) {
     return $(table) && $(table).data('options') ? Array.from($(table).data('options').selected_rows).sort((a, b) => (Number(a[opt.idFiled]) || 0) - (Number(b[opt.idFiled]) || 0)) : []
 }
 
+
 function AddModal(modal, callback) {
     const $modal = $(modal)
     $modal.find('.modal-header .modal-title span.prefix').html('添加')
@@ -465,7 +601,7 @@ function Confirm(title, callback) {
         toast: false
     }
     Swal.fire(opts).then((result) => {
-        console.log("CONFIRM", result)
+        // console.log("CONFIRM", result)
         if (result.isConfirmed) {
             callback && callback()
         }

+ 44 - 44
SourceCode/IntelligentRailwayCosting/app/views/static/project/project.js

@@ -1,20 +1,20 @@
 
 const table = '#table',
-$modal = $("#modal");
+    $modal = $("#modal");
 $(function () {
     InitRangeDate($('#date'))
-    IwbTable(table,{
-        url:'/api/project/list',
-        columns:[
+    IwbTable(table, {
+        url: '/api/project/list',
+        columns: [
             {
-                title:'项目编号',
-                data:'project_id',
-                width:'240px'
+                title: '项目编号',
+                data: 'project_id',
+                width: '240px'
             },
             {
-                title:'项目名称',
-                data:'project_name',
-                width:'auto'
+                title: '项目名称',
+                data: 'project_name',
+                width: 'auto'
             },
             // {
             //     title:'项目简称',
@@ -22,25 +22,25 @@ $(function () {
             //     width:'150px'
             // },
             {
-                title:'设计阶段',
-                data:'design_stage',
-                width:'150px',
-                render:function (row) {
-                    if (!row.design_stage){
+                title: '设计阶段',
+                data: 'design_stage',
+                width: '150px',
+                render: function (row) {
+                    if (!row.design_stage) {
                         return '-'
                     }
-                    return `<span class="badge badge-${row.design_stage === '概算'?'primary':'info'}">${row.design_stage}</span>`
+                    return `<span class="badge badge-${row.design_stage === '概算' ? 'primary' : 'info'}">${row.design_stage}</span>`
                 }
             },
             {
-                title:'项目类型',
-                data:'project_type',
-                width:'150px',
-                render:function (row) {
-                    if (!row.project_type){
+                title: '项目类型',
+                data: 'project_type',
+                width: '150px',
+                render: function (row) {
+                    if (!row.project_type) {
                         return '-'
                     }
-                    return `<span class="badge badge-${row.project_type === '基本建设工程'?'primary':'info'}">${row.project_type}</span>`
+                    return `<span class="badge badge-${row.project_type === '基本建设工程' ? 'primary' : 'info'}">${row.project_type}</span>`
                 }
             },
             // {
@@ -49,20 +49,20 @@ $(function () {
             //     width:'auto'
             // },
             {
-                title:'项目负责人',
-                data:'project_manager',
-                width:'150px'
+                title: '项目负责人',
+                data: 'project_manager',
+                width: '150px'
             },
             {
-                title:'创建时间',
-                data:'create_time',
-                width:'180px'
+                title: '创建时间',
+                data: 'create_time',
+                width: '180px'
             },
             {
-                title:'操作',
-                width:'150px',
-                render:function (row) {
-                    let str=""
+                title: '操作',
+                width: '150px',
+                render: function (row) {
+                    let str = ""
                     str += `<span class="btn btn-light-primary" onclick="GoTo('/budget_info/${row.project_id}')">项目管理</span>`;
                     return str
                 }
@@ -71,16 +71,16 @@ $(function () {
     })
 
 });
-function Add(){
-    AddModal($modal,()=>{
+function Add() {
+    AddModal($modal, () => {
         $modal.find('#project_id').val('0');
     })
 }
 function Edit(id) {
-    EditModal($modal,()=>{
+    EditModal($modal, () => {
         IwbAjax_1({
-            url:`/api/project/get/${id}`,
-            success:data=>{
+            url: `/api/project/get/${id}`,
+            success: data => {
                 $modal.find('#project_id').val(data.data.id);
                 $modal.find('#project_name').val(data.data.project_name);
                 $modal.find('#project_desc').val(data.data.description);
@@ -90,21 +90,21 @@ function Edit(id) {
 }
 function SaveProject() {
     const projectId = $('#project_id').val();
-    const projectName= $('#project_name').val();
-    const projectDesc= $('#project_desc').val();
+    const projectName = $('#project_name').val();
+    const projectDesc = $('#project_desc').val();
     IwbAjax_2({
-        url:`/api/project/save/${projectId}`,
-        data:{
+        url: `/api/project/save/${projectId}`,
+        data: {
             project_name: projectName,
             description: projectDesc,
         },
     })
 }
 function Delete(id) {
-    Confirm('确定删除吗?',function () {
+    Confirm('确定删除吗?', function () {
         IwbAjax({
-            url:`/api/project/delete/${id}`,
-            table:table,
+            url: `/api/project/delete/${id}`,
+            table: table,
         })
     })
 }

+ 85 - 35
SourceCode/IntelligentRailwayCosting/app/views/static/project/quota_info.js

@@ -157,11 +157,12 @@ const nav_template = `<ul id="nav_tab" class="nav nav-tabs nav-line-tabs nav-lin
 										</div>
 									</form>
 								</div>
+								<div class="pagination-row"></div>
 								<table class="table table-striped table-bordered table-hover  table-rounded" id="table_{0}">
 								</table>
-								<div class="pagination-row"></div>
 							</div>`
 let table_add_quota_btn_template = `<button type="button" class="quota_add_btn btn btn-primary btn-sm" onclick="Send_Quota_Batch('{0}')">批量推送</button>`;
+table_add_quota_btn_template += `<button type="button" class="ms-5 btn btn-warning btn-sm" onclick="Edit_Quota_Code_Batch('{0}')">批量修改定额编号</button>`
 // table_add_quota_btn_template += `<button type="button" class="ms-5 btn btn-warning btn-sm" onclick="Edit_QuotaChapter(null,'{0}')">批量修改章节</button>`
 //`<button type="button" class="quota_add_btn btn btn-primary btn-sm" onclick="Add_Quota('{0}')">添加定额</button>`
 
@@ -169,7 +170,7 @@ const $modalQuota = $('#modal_quota'), $modalChapter = $('#modal_chapter')
 let $rightBox, $rightBoxHeader, $rightBoxBody, $taskBox, $quotaBox, budget_id, item_code, task_timer,
     task_query_count = 0
 
-console.log(`加载项目:${project_id}`)
+console.log(`加载项目:${project_id}${task_id ? `,任务ID:${task_id}。` : ''}`)
 InitBody()
 
 function clear() {
@@ -252,7 +253,7 @@ function BuildChapterInfo() {
                     url: `/api/project/chapter/${project_id}?c=${node?.data?.item_code || ''}&&t=${taskId}`,
                     success: res => {
                         if (res.success) {
-                            console.log('TREE', res.data)
+                            // console.log('TREE', res.data)
                             callback(res.data)
                         } else {
                             console.error(res.message)
@@ -279,7 +280,7 @@ function BuildChapterInfo() {
         inst.select_node(obj);
     })
     $tree.on('select_node.jstree', function (e, data) {
-        console.log('TREE_SELECTED', e, data)
+        // console.log('TREE_SELECTED', e, data)
         RenderRightBox(data.node?.data)
     })
     $tree.jstree(opt)
@@ -287,7 +288,7 @@ function BuildChapterInfo() {
 
 
 function RenderRightBox(data) {
-    console.log('RenderRightBox', arguments)
+    // console.log('RenderRightBox', arguments)
     $rightBoxBody.data('data', data)
     clear()
     $rightBox.find('input[name="budget_id"]').val(data.budget_id);
@@ -335,7 +336,7 @@ function BuildBudgetInfo() {
 }
 
 function RenderTabs(data) {
-    console.log('RenderTabs', data)
+    // console.log('RenderTabs', data)
     let str1 = '',
         str2 = ''
     if (data && data.length) {
@@ -353,7 +354,7 @@ function RenderTabs(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)
+        // console.log('TAB', e)
         const tab_id = $(e.target).data('id'),
             data = $("#body_box .right-box .box-body").data('data')
         budget_id = tab_id
@@ -365,7 +366,7 @@ function RenderTabs(data) {
 }
 
 function RenderQuotaTable(data) {
-    console.log('RenderQuotaTable', budget_id, data)
+    // console.log('RenderQuotaTable', budget_id, data)
     const $table = $quotaBox.find(`#table_${budget_id}`)
     LoadQuotaTable($table)
 }
@@ -374,66 +375,83 @@ function LoadQuotaTable(table) {
     const url = `${$quotaBox.data("table-url")}/${budget_id}/${project_id}/${item_code}`
     IwbTable(table, {
         url,
+        editUrl: '/api/quota/edit',
         checkBox: true,
+        isDragColumn: true,
         checkBoxDisable: (row) => {
             return row.send_status === 200
         },
         columns: [
+            // {
+            //     title: '序号',
+            //     data: 'id',
+            //     width: '80px',
+            // },
             {
-                title: '序号',
-                data: 'id',
-                width: '80px',
+                title: '条⽬编号',
+                data: 'item_code',
+                width: '200px',
+                tdStyle: "text-align:right;padding-right:15px;",
+                render: (row) => {
+                    return `<span class="one-line edit-label" data-bs-toggle="tooltip" data-bs-placement="top" title="${row.item_code}" ><span class="edit-text">${row.item_code}</span></span>`
+                }
             },
             {
                 title: '工程或费用项目名称',
                 data: 'entry_name',
-                width: '210px',
+                width: '255px',
+                isEdit: true,
                 render: (row) => {
-                    return `<span class="one-line mw-200px" data-bs-toggle="tooltip" data-bs-placement="top" title="${row.entry_name}" >${row.entry_name}</span>`
+                    return `<span class="one-line edit-label" data-bs-toggle="tooltip" data-bs-placement="top" title="${row.entry_name}" ><span class="edit-text">${row.entry_name}</span></span>`
                 }
             },
             {
                 title: '工程数量',
                 data: 'amount',
                 width: '100px',
+                isEdit: true,
             },
             {
                 title: '单位',
                 data: 'units',
                 width: '80px',
+                isEdit: true,
             },
             {
                 title: '定额编号',
                 data: 'quota_code',
                 width: '100px',
+                isEdit: true,
                 render: (row) => {
                     if (row.quota_id) {
-                        return `<span class="badge badge-light-danger" data-bs-toggle="tooltip" data-bs-placement="top" title="可覆盖系统数据:${row.quota_id}" >${row.quota_code}</span>`
+                        return `<span class="badge badge-light-danger edit-label" data-bs-toggle="tooltip" data-bs-placement="top" title="可覆盖系统数据:${row.quota_id}" ><span class="edit-text">${row.quota_code}</span></span>`
                     } else {
-                        return `<span class="badge badge-light-primary" data-bs-toggle="tooltip" data-bs-placement="top" title="未关联系统数据" >${row.quota_code}</span>`
-                    }
-                }
-            },
-            {
-                title: 'Excel文件',
-                data: 'ex_file',
-                width: '150px',
-                render: (row) => {
-                    const path = row.ex_file
-                    if (!path) {
-                        return '-'
+                        return `<span class="badge badge-light-primary edit-label" data-bs-toggle="tooltip" data-bs-placement="top" title="未关联系统数据" ><span class="edit-text">${row.quota_code}</span></span>`
                     }
-                    const names = path.split('/')
-                    const file_name = names[names.length - 1]
-                    return `<span class="one-line mw-150px"><a href="#" onclick="DownloadFile('/api/task/download?filename=${encodeURIComponent(path)}','${file_name}')"  class="link link-info px-2">${file_name}</a></span>`
                 }
             },
+            // {
+            //     title: 'Excel文件',
+            //     data: 'ex_file',
+            //     width: '150px',
+            //     render: (row) => {
+            //         const path = row.ex_file
+            //         if (!path) {
+            //             return '-'
+            //         }
+            //         const names = path.split('/')
+            //         const file_name = names[names.length - 1]
+            //         return `<span class="one-line mw-150px"><a href="#" onclick="DownloadFile('/api/task/download?filename=${encodeURIComponent(path)}','${file_name}')"  class="link link-info px-2">${file_name}</a></span>`
+            //     }
+            // },
             {
                 title: 'Excel整行内容',
                 data: 'ex_row',
-                width: 'auto',
+                width: '180px',
                 render: (row) => {
-                    return `<span class="one-line w-300px" data-bs-toggle="tooltip" data-bs-placement="top" title="${row.ex_row}" >${row.ex_row}</span>`
+                    const ex_row_arr = row.ex_row.split("|"),
+                        ex_row = ex_row_arr[ex_row_arr.length - 1]
+                    return `<span class="one-line" data-bs-toggle="tooltip" data-bs-placement="top" title="${row.ex_row}" >${ex_row}</span>`
                 }
             },
             {
@@ -680,7 +698,7 @@ function SendQuota(title, ids, budget_id, is_cover, is_html) {
         opts.html = `${title}`
     }
     Swal.fire(opts).then((result) => {
-        console.log("CONFIRM", result)
+        // console.log("CONFIRM", result)
         if (result.isConfirmed) {
             _send(false)
         } else if (result.isDenied) {
@@ -689,7 +707,7 @@ function SendQuota(title, ids, budget_id, is_cover, is_html) {
     });
 
     function _send(is_cover) {
-        console.log("is_cover", is_cover, ids)
+        // console.log("is_cover", is_cover, ids)
         IwbAjax({
             url: `/api/quota/start_send`,
             data: {
@@ -702,12 +720,44 @@ function SendQuota(title, ids, budget_id, is_cover, is_html) {
     }
 }
 
+function Edit_Quota_Code_Batch(budget_id) {
+    const ids = IwbTableGetSelectedIds('#table_' + budget_id).join(",")
+    if (!ids) {
+        MsgWarning('没有选择数据,请选择要修改的数据!')
+        return
+    }
+    Swal.fire({
+        title: '批量修改定额编号',
+        html: '<input id="batch_quota_code" class="form-control form-control-sm" placeholder="请输入定额编号"/>',
+        showCancelButton: true,
+        focusConfirm: false,
+        confirmButtonText: '修改',
+        cancelButtonText: '取消',
+        customClass: {
+            cancelButton: "btn btn-light",
+            confirmButton: "btn btn-primary"
+        },
+    }).then((result) => {
+        // console.log("CONFIRM", result)
+        if (result.isConfirmed) {
+            IwbAjax({
+                url: `/api/quota/edit_quota_code_batch`,
+                data: {
+                    ids,
+                    quota_code: $('#batch_quota_code').val()
+                },
+                table: `#table_${budget_id}`
+            })
+        }
+    });
+}
+
 function Edit_QuotaChapter(id, budget_id) {
     let ids = id
     if (!ids) {
         ids = IwbTableGetSelectedIds('#table_' + budget_id).join(",")
     }
-    console.log("Edit_QuotaChapter", ids)
+    // console.log("Edit_QuotaChapter", ids)
     EditModal($modalChapter, () => {
         $modalChapter.find('[name="budget_id"]').val(budget_id);
         $modalChapter.find('[name="id"]').val(ids);
@@ -734,7 +784,7 @@ function RenderChapterSelect() {
 }
 
 function FormatChapterOption(data) {
-    console.log('FormatChapterOption', data)
+    // console.log('FormatChapterOption', data)
     let str = ''
     if (data && data.length) {
         data.forEach(item => {