Parcourir la source

Merge branch 'dev' of http://192.168.1.41:3000/maixinrong/Calculation into dev

Tony Kang il y a 5 mois
Parent
commit
5321c2e0db
42 fichiers modifiés avec 1055 ajouts et 97 suppressions
  1. 1 1
      app/base/base_controller.js
  2. 1 1
      app/const/page_show.js
  3. 8 4
      app/const/spread.js
  4. 80 8
      app/controller/file_controller.js
  5. 2 1
      app/controller/measure_controller.js
  6. 5 4
      app/lib/sum_load.js
  7. 4 4
      app/public/js/change_revise.js
  8. 3 3
      app/public/js/contract_detail.js
  9. 2 2
      app/public/js/ctrl_price.js
  10. 136 2
      app/public/js/file_detail.js
  11. 1 0
      app/public/js/file_list.js
  12. 414 0
      app/public/js/filing_manage.js
  13. 32 0
      app/public/js/filing_template.js
  14. 7 6
      app/public/js/ledger.js
  15. 9 7
      app/public/js/measure_compare.js
  16. 7 6
      app/public/js/revise.js
  17. 2 2
      app/public/js/revise_price.js
  18. 3 3
      app/public/js/se_yjcl.js
  19. 3 3
      app/public/js/sp_push.js
  20. 17 9
      app/public/js/stage.js
  21. 13 0
      app/public/js/sub_project.js
  22. 5 0
      app/router.js
  23. 27 0
      app/service/file.js
  24. 39 9
      app/service/filing.js
  25. 27 4
      app/service/filing_template.js
  26. 42 2
      app/service/filing_template_list.js
  27. 1 0
      app/service/sub_proj_permission.js
  28. 1 0
      app/view/file/file.ejs
  29. 29 1
      app/view/file/file_modal.ejs
  30. 1 0
      app/view/file/index.ejs
  31. 43 0
      app/view/file/manage.ejs
  32. 20 0
      app/view/file/manage_modal.ejs
  33. 11 1
      app/view/file/template.ejs
  34. 23 1
      app/view/file/template_modal.ejs
  35. 6 6
      app/view/layout/layout.ejs
  36. 4 4
      app/view/stage/change.ejs
  37. 1 1
      app/view/stage/modal.ejs
  38. 2 0
      app/view/stage/stage_sub_menu.ejs
  39. 2 1
      app/view/sub_proj/modal.ejs
  40. 1 1
      app/view/tender/tender_sub_menu.ejs
  41. 14 0
      config/web.js
  42. 6 0
      sql/update.sql

+ 1 - 1
app/base/base_controller.js

@@ -93,7 +93,7 @@ class BaseController extends Controller {
             const renderData = {
                 min: this.app.config.min,
                 content: viewString,
-                message: JSON.stringify(message),
+                message: message ? message : '',
                 modal: modalString,
                 dropDownMenu: data.dropDownMenu === undefined ? [] : data.dropDownMenu,
                 breadCrumb: data.breadCrumb === undefined ? '' : data.breadCrumb,

+ 1 - 1
app/const/page_show.js

@@ -28,7 +28,7 @@ const defaultSetting = {
     bwtz: 0,
     xxjd: 0,
     openMaterial: 1,
-    openIsolatePay: 0,
+    phasePay: 0,
     stageExtra: 1,
     closeExportPdf: 0,
     closeExportExcel: 0,

+ 8 - 4
app/const/spread.js

@@ -506,7 +506,8 @@ const BaseSpreadColSetting = {
             code: [{title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 145, formatter: '@', cellType: 'tree'}],
             b_code: [{title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 70, formatter: '@',}],
             name: [{title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 185, formatter: '@', }],
-            features: [{title: '项目特征', colSpan: '1', rowSpan: '2', field: 'features', hAlign: 0, width: 185, formatter: '@', cellType: 'ellipsisAutoTip' }],
+            // features: [{title: '项目特征', colSpan: '1', rowSpan: '2', field: 'features', hAlign: 0, width: 185, formatter: '@', cellType: 'ellipsisAutoTip' }],
+            features: [{title: '项目特征', colSpan: '1', rowSpan: '2', field: 'features', hAlign: 0, width: 185, formatter: '@', wordWrap: true, }],
             unit: [{title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 50, formatter: '@', cellType: 'unit'}],
             unit_price: [{title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number',}],
             dgn_qty: [
@@ -556,7 +557,8 @@ const BaseSpreadColSetting = {
             code: [{title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 145, formatter: '@', readOnly: true, cellType: 'tree'}],
             b_code: [{title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 70, formatter: '@', readOnly: true}],
             name: [{title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 185, formatter: '@', readOnly: true}],
-            features: [{title: '项目特征', colSpan: '1', rowSpan: '2', field: 'features', hAlign: 0, width: 185, formatter: '@', readOnly: true, cellType: 'ellipsisAutoTip' }],
+            // features: [{title: '项目特征', colSpan: '1', rowSpan: '2', field: 'features', hAlign: 0, width: 185, formatter: '@', readOnly: true, cellType: 'ellipsisAutoTip' }],
+            features: [{title: '项目特征', colSpan: '1', rowSpan: '2', field: 'features', hAlign: 0, width: 185, formatter: '@', readOnly: true, wordWrap: true, }],
             unit: [{title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@', readOnly: true, cellType: 'unit'}],
             unit_price: [{title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, readOnly: true, type: 'Number'}],
             tz_calc: [
@@ -641,7 +643,8 @@ const BaseSpreadColSetting = {
             code: [{title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 145, formatter: '@', cellType: 'tree'}],
             b_code: [{title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 70, formatter: '@',}],
             name: [{title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 185, formatter: '@', }],
-            features: [{title: '项目特征', colSpan: '1', rowSpan: '2', field: 'features', hAlign: 0, width: 185, formatter: '@', cellType: 'ellipsisAutoTip' }],
+            // features: [{title: '项目特征', colSpan: '1', rowSpan: '2', field: 'features', hAlign: 0, width: 185, formatter: '@', cellType: 'ellipsisAutoTip' }],
+            features: [{title: '项目特征', colSpan: '1', rowSpan: '2', field: 'features', hAlign: 0, width: 185, formatter: '@', wordWrap: true, }],
             unit: [{title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 50, formatter: '@', cellType: 'unit'}],
             unit_price: [{title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, type: 'Number',}],
             dgn_qty: [
@@ -691,7 +694,8 @@ const BaseSpreadColSetting = {
             code: [{title: '项目节编号', colSpan: '1', rowSpan: '2', field: 'code', hAlign: 0, width: 145, formatter: '@', readOnly: true, cellType: 'tree'}],
             b_code: [{title: '清单编号', colSpan: '1', rowSpan: '2', field: 'b_code', hAlign: 0, width: 70, formatter: '@', readOnly: true}],
             name: [{title: '名称', colSpan: '1', rowSpan: '2', field: 'name', hAlign: 0, width: 185, formatter: '@', readOnly: true}],
-            features: [{title: '项目特征', colSpan: '1', rowSpan: '2', field: 'features', hAlign: 0, width: 185, formatter: '@', readOnly: true, cellType: 'ellipsisAutoTip' }],
+            // features: [{title: '项目特征', colSpan: '1', rowSpan: '2', field: 'features', hAlign: 0, width: 185, formatter: '@', readOnly: true, cellType: 'ellipsisAutoTip' }],
+            features: [{title: '项目特征', colSpan: '1', rowSpan: '2', field: 'features', hAlign: 0, width: 185, formatter: '@', readOnly: true, wordWrap: true, }],
             unit: [{title: '单位', colSpan: '1', rowSpan: '2', field: 'unit', hAlign: 1, width: 60, formatter: '@', readOnly: true, cellType: 'unit'}],
             unit_price: [{title: '单价', colSpan: '1', rowSpan: '2', field: 'unit_price', hAlign: 2, width: 60, readOnly: true, type: 'Number'}],
             tz_calc: [

+ 80 - 8
app/controller/file_controller.js

@@ -13,7 +13,15 @@ const path = require('path');
 const advanceConst = require('../const/advance');
 
 module.exports = app => {
-    class BudgetController extends app.BaseController {
+    class FileController extends app.BaseController {
+
+        checkUnlock(ctx) {
+            if (ctx.subProject.lock_file) throw '管理员锁定中,暂无法编辑分类&文件,仅可查看';
+        }
+
+        checkLock(ctx) {
+            if (!ctx.subProject.lock_file) throw '请先锁定,再管理分类数据';
+        }
 
         /**
          * 概算投资
@@ -53,6 +61,7 @@ module.exports = app => {
                 renderData.categoryData = await ctx.service.category.getAllCategory(ctx.session.sessionProject.id);
                 renderData.canFiling = ctx.subProject.permission.file_permission.indexOf(ctx.service.subProjPermission.PermissionConst.file.filing.value) >= 0;
                 renderData.canUpload = ctx.subProject.permission.file_permission.indexOf(ctx.service.subProjPermission.PermissionConst.file.upload.value) >= 0;
+                renderData.canDelete = ctx.subProject.permission.file_permission.indexOf(ctx.service.subProjPermission.PermissionConst.file.delete.value) >= 0;
                 renderData.filingTypes = ctx.service.filing.analysisFilingType(renderData.filing);
                 await this.layout('file/file.ejs', renderData, 'file/file_modal.ejs');
             } catch (err) {
@@ -85,6 +94,7 @@ module.exports = app => {
 
         async addFiling(ctx) {
             try {
+                this.checkUnlock(ctx);
                 const data = JSON.parse(ctx.request.body.data);
                 const result = await ctx.service.filing.add(data);
                 ctx.body = { err: 0, msg: '', data: result };
@@ -95,6 +105,7 @@ module.exports = app => {
         }
         async delFiling(ctx) {
             try {
+                this.checkUnlock(ctx);
                 const data = JSON.parse(ctx.request.body.data);
                 const result = await ctx.service.filing.del(data);
                 ctx.body = { err: 0, msg: '', data: result };
@@ -105,6 +116,7 @@ module.exports = app => {
         }
         async saveFiling(ctx) {
             try {
+                this.checkUnlock(ctx);
                 const data = JSON.parse(ctx.request.body.data);
                 const result = await ctx.service.filing.save(data);
                 ctx.body = { err: 0, msg: '', data: result };
@@ -116,6 +128,7 @@ module.exports = app => {
 
         async moveFiling(ctx) {
             try {
+                this.checkUnlock(ctx);
                 const data = JSON.parse(ctx.request.body.data);
                 if (!data.id || !(data.tree_order >= 0)) throw '数据错误';
                 const result = await ctx.service.filing.move(data);
@@ -242,6 +255,17 @@ module.exports = app => {
                 ctx.ajaxErrorBody(error, '编辑附件失败');
             }
         }
+        async moveFile(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.id || !data.filingId) throw '缺少参数';
+                const result = await ctx.service.file.moveFile(data.id, data.filingId);
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch (error) {
+                this.log(error);
+                ctx.ajaxErrorBody(error, '编辑附件失败');
+            }
+        }
         async uploadBigFile(ctx) {
             try {
                 await this.checkCanUpload(ctx);
@@ -373,13 +397,13 @@ module.exports = app => {
             const defaultTemplate = await ctx.service.filingTemplateList.getOriginTemplate();
             ctx.redirect('/file/template/' + defaultTemplate.id);
         }
-
         async templateDetail(ctx) {
             try {
                 const renderData = {
                     jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.file.template),
                 };
                 renderData.templateList = await ctx.service.filingTemplateList.getAllTemplate(ctx.session.sessionProject.id);
+                renderData.shareTemplate = await ctx.service.filingTemplateList.getShareTemplate(ctx.session.sessionProject.id);
                 renderData.FtType = ctx.service.filingTemplateList.FtType;
                 renderData.template = renderData.templateList.find(x => { return x.id === ctx.params.id });
                 if (!renderData.template) throw '查看的资料模板不存在';
@@ -392,12 +416,13 @@ module.exports = app => {
                 ctx.redirect(this.menu.menu.dashboard.url);
             }
         }
-
         async saveTemplate(ctx) {
             try {
                 const id = ctx.query.id;
                 const name = ctx.request.body.name;
-                const [save, templateId] = await ctx.service.filingTemplateList.save(name, id);
+                const is_share = ctx.request.body.is_share ? parseInt(ctx.request.body.is_share) : undefined;
+                const share_id = ctx.request.body.share_id;
+                const [save, templateId] = share_id ? await ctx.service.filingTemplateList.copy(share_id) : await ctx.service.filingTemplateList.save(name, is_share, id);
                 if (!save) throw '保存数据失败';
                 ctx.redirect('/file/template/' + templateId);
             } catch(err) {
@@ -406,7 +431,6 @@ module.exports = app => {
                 ctx.redirect('/file/template');
             }
         }
-
         async resetTemplate(ctx) {
             try {
                 const id = ctx.query.id;
@@ -418,7 +442,6 @@ module.exports = app => {
                 ctx.redirect('/file/template');
             }
         }
-
         async delTemplate(ctx) {
             try {
                 const id = ctx.query.id;
@@ -435,7 +458,6 @@ module.exports = app => {
                 ctx.redirect('/file/template');
             }
         }
-
         async updateTemplate(ctx) {
             try {
                 const data = JSON.parse(ctx.request.body.data);
@@ -478,7 +500,57 @@ module.exports = app => {
                 ctx.ajaxErrorBody(err, '搜索文件失败');
             }
         }
+
+        async manage(ctx) {
+            try {
+                const renderData = {
+                    jsFiles: this.app.jsFiles.common.concat(this.app.jsFiles.file.manage),
+                };
+                renderData.filingData = await ctx.service.filing.getValidFiling(ctx.params.id, ctx.subProject.permission.filing_type);
+                await this.layout('file/manage.ejs', renderData, 'file/manage_modal.ejs');
+            } catch (err) {
+                ctx.log(err);
+                ctx.session.postError = err.toString();
+                ctx.redirect(this.menu.menu.dashboard.url);
+            }
+        }
+        async lockFiling(ctx) {
+            try {
+                await ctx.service.subProject.save({ id: ctx.subProject.id, lock_file: ctx.query.lock });
+                ctx.redirect(`/sp/${ctx.subProject.id}/fm`);
+            } catch(err) {
+                ctx.log(err);
+                ctx.postError(err, '资料归集分类锁定错误');
+                ctx.redirect(`/sp/${ctx.subProject.id}/fm`);
+            }
+        }
+        async manageUpdate(ctx) {
+            try {
+                this.checkLock(ctx);
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.updateType) throw '数据错误';
+                let result;
+                const updateData = JSON.parse(JSON.stringify(data));
+                delete updateData.updateType;
+                if (data.updateType === 'add') {
+                    result = await ctx.service.filing.add(updateData);
+                } else if (data.updateType === 'del') {
+                    result = await ctx.service.filing.del(updateData);
+                } else if (data.updateType === 'save') {
+                    result = await ctx.service.filing.save(updateData);
+                } else if (data.updateType === 'move') {
+                    if (!data.id || !(data.tree_order >= 0)) throw '数据错误';
+                    result = await ctx.service.filing.move(updateData);
+                } else if (data.updateType === 'multi' ) {
+                    result = await ctx.service.filing.multiUpdate(ctx.subProject.id, data.data);
+                }
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '修改失败');
+            }
+        }
     }
 
-    return BudgetController;
+    return FileController;
 };

+ 2 - 1
app/controller/measure_controller.js

@@ -270,7 +270,7 @@ module.exports = app => {
                 }
                 const zlj = JSON.parse(JSON.stringify(stdConst.zlj));
                 zlj.deal_bills_tp = ctx.tender.info.deal_param.zanLiePrice;
-                result.spec = {zlj: zlj, jrg: stdConst.jrg};
+                result.spec = { zlj: zlj, jrg: stdConst.jrg };
                 ctx.body = { err: 0, msg: '', data: result };
             } catch (err) {
                 this.log(err);
@@ -317,6 +317,7 @@ module.exports = app => {
             } catch (err) {
                 this.log(err);
                 console.log(err);
+                ctx.session.postError = err.toString();
                 ctx.redirect(ctx.request.header.referer);
             }
         }

+ 5 - 4
app/lib/sum_load.js

@@ -104,6 +104,7 @@ class loadGclBaseTree {
                 quantity: 0,
                 is_leaf: source.is_leaf,
                 hasPos: false,
+                sub_up: source.unit_price || 0,
             };
             this.keyNodeId += 1;
             parent.children.push(node);
@@ -278,7 +279,7 @@ class updateReviseGclTree extends loadGclBaseTree {
         }
     }
     gather(source, parent) {
-        const node =  this.ignoreParent ? this.addNodeWithoutParent(source) : this.addNode(source, parent);
+        const node = this.ignoreParent ? this.addNodeWithoutParent(source) : this.addNode(source, parent);
         node.deal_qty = this.ctx.helper.add(node.deal_qty, source.deal_qty);
         node.sgfh_qty = this.ctx.helper.add(node.sgfh_qty, source.sgfh_qty);
         node.qtcl_qty = this.ctx.helper.add(node.qtcl_qty, source.qtcl_qty);
@@ -489,7 +490,7 @@ class sumLoad {
 
     async loadGatherGcl(select, maxId, tenders, defaultData, subUp) {
         this.loadTree = new loadLedgerGclTree(this.ctx, {
-            parent: select, maxId, type: 'ledger', defaultData, subUp,
+            parent: select, maxId, type: 'ledger', defaultData, useSubUp: subUp,
         });
         for (const tender of tenders) {
             const billsData = await this.ctx.service.ledger.getData(tender.tid);
@@ -517,10 +518,10 @@ class sumLoad {
         return this.loadTree;
     }
 
-    async updateGatherGcl(select, maxId, tenders, defaultData) {
+    async updateGatherGcl(select, maxId, tenders, defaultData, subUp) {
         const ignoreParent = this.ctx.tender.info.fun_rela.sum_load.ignoreParent;
         this.loadTree = new updateReviseGclTree(this.ctx, {
-            parent: select, maxId, type: 'ledger', defaultData, ignoreParent,
+            parent: select, maxId, type: 'ledger', defaultData, ignoreParent, useSubUp: subUp,
         });
         const posterity = await this.ctx.service.reviseBills.getPosterityByParentId(this.ctx.tender.id, select.ledger_id);
         const pos = await this.ctx.service.revisePos.getData(this.ctx.tender.id);

+ 4 - 4
app/public/js/change_revise.js

@@ -706,7 +706,7 @@ $(document).ready(() => {
                 };
                 // 未改变值则不提交
                 const orgValue = node[col.field];
-                const newValue = trimInvalidChar(info.editingText);
+                const newValue = col.wordWrap ? info.editingText : trimInvalidChar(info.editingText);
                 if (orgValue == info.editingText || ((!orgValue || orgValue === '') && (newValue === ''))) {
                     return;
                 }
@@ -845,7 +845,7 @@ $(document).ready(() => {
                 for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
                     const curCol = info.cellRange.col + iCol;
                     const colSetting = info.sheet.zh_setting.cols[curCol];
-                    const value = trimInvalidChar(pasteData[iRow-filterRow][iCol]);
+                    const value = colSetting.wordWrap ? pasteData[iRow-filterRow][iCol] : trimInvalidChar(pasteData[iRow-filterRow][iCol]);
                     if (node.children && node.children.length > 0 && invalidFields.parent.indexOf(colSetting.field) >= 0) {
                         toastMessageUniq(hint.parent);
                         continue;
@@ -964,7 +964,7 @@ $(document).ready(() => {
                     for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
                         const curCol = info.cellRange.col + iCol;
                         const colSetting = info.sheet.zh_setting.cols[curCol];
-                        const value = trimInvalidChar(info.sheet.getText(curRow, curCol));
+                        const value = col.wordWrap ? info.sheet.getText(curRow, curCol) : trimInvalidChar(info.sheet.getText(curRow, curCol));
                         const lPos = pos.getLedgerPos(node.id);
                         if (lPos && lPos.length > 0) {
                             if (value === '' && colSetting.field === 'b_code') {
@@ -2157,7 +2157,7 @@ $(document).ready(() => {
                     const colSetting = info.sheet.zh_setting.cols[curCol];
                     if (!colSetting) continue;
 
-                    posData[colSetting.field] = trimInvalidChar(info.sheet.getText(curRow, curCol));
+                    posData[colSetting.field] = colSetting.wordWrap ? info.sheet.getText(curRow, curCol) : trimInvalidChar(info.sheet.getText(curRow, curCol));
                     if (posData.id && colSetting.type === 'Number' && sortData[curRow].settle_status === settleStatus.finish) {
                         bPaste = false;
                         toastMessageUniq(hint.settle);

+ 3 - 3
app/public/js/contract_detail.js

@@ -569,7 +569,7 @@ $(document).ready(function() {
                 };
                 // 未改变值则不提交
                 const orgValue = node[col.field];
-                const newValue = trimInvalidChar(info.editingText);
+                const newValue = col.wordWrap ? info.editingText : trimInvalidChar(info.editingText);
                 if (orgValue == info.editingText || ((!orgValue || orgValue === '') && (newValue === ''))) {
                     return;
                 }
@@ -612,7 +612,7 @@ $(document).ready(function() {
                 for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
                     const curCol = info.cellRange.col + iCol;
                     const colSetting = info.sheet.zh_setting.cols[curCol];
-                    const value = trimInvalidChar(pasteData[iRow-filterRow][iCol]);
+                    const value = colSetting.wordWrap ? pasteData[iRow-filterRow][iCol] : trimInvalidChar(pasteData[iRow-filterRow][iCol]);
                     data[colSetting.field] = value;
                     bPaste = true;
                 }
@@ -652,7 +652,7 @@ $(document).ready(function() {
                     for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
                         const curCol = info.cellRange.col + iCol;
                         const colSetting = info.sheet.zh_setting.cols[curCol];
-                        const value = trimInvalidChar(info.sheet.getText(curRow, curCol));
+                        const value = colSetting.wordWrap ? info.sheet.getText(curRow, curCol) : trimInvalidChar(info.sheet.getText(curRow, curCol));
                         data[colSetting.field] = value;
                         bPaste = true;
                     }

+ 2 - 2
app/public/js/ctrl_price.js

@@ -288,7 +288,7 @@ $(document).ready(() => {
                 const data = { id: node.id, tid: node.tid, tree_id: node.tree_id };
                 // 未改变值则不提交
                 const orgValue = node[col.field];
-                const newValue = trimInvalidChar(info.editingText);
+                const newValue = col.wordWrap ? info.editingText : trimInvalidChar(info.editingText);
                 if (orgValue == info.editingText || ((!orgValue || orgValue === '') && (newValue === ''))) return;
 
                 if (node.b_code && invalidFields.gcl.indexOf(col.field) >=0) {
@@ -349,7 +349,7 @@ $(document).ready(() => {
                 for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
                     const curCol = info.cellRange.col + iCol;
                     const colSetting = info.sheet.zh_setting.cols[curCol];
-                    const value = trimInvalidChar(pasteData[iRow-filterRow][iCol]);
+                    const value = colSetting.wordWrap ? pasteData[iRow-filterRow][iCol] : trimInvalidChar(pasteData[iRow-filterRow][iCol]);
                     if (node.children && node.children.length > 0 && invalidFields.parent.indexOf(colSetting.field) >= 0) {
                         toastMessageUniq(hint.parent);
                         continue;

+ 136 - 2
app/public/js/file_detail.js

@@ -91,11 +91,12 @@ $(document).ready(function() {
             this.loadFiling();
         }
         _getFileNameHtml(file) {
+            const moveHtml = file.canEdit ? `<a href="javascript: void(0);" class="mr-1" name="move-file" fid="${file.id}"><i class="fa fa-exchange fa-fw"></i></a>` : '';
             const editHtml = file.canEdit ? `<a href="javascript: void(0);" class="mr-1" name="edit-file" fid="${file.id}"><i class="fa fa-pencil fa-fw"></i></a>` : '';
             const viewHtml = file.viewpath ? `<a href="${file.viewpath}" class="mr-1" target="_blank"><i class="fa fa-eye fa-fw"></i></a>` : '';
             const downHtml = `<a href="javascript: void(0);" onclick="AliOss.downloadFile('${file.filepath}', '${file.filename + file.fileext}')" class="mr-1"><i class="fa fa-download fa-fw"></i></a>`;
-            const delHtml = file.canEdit ? `<a href="javascript: void(0);" class="mr-1 text-danger" name="del-file" fid="${file.id}"><i class="fa fa-trash-o fa-fw"></i></a>` : '';
-            return `<div class="d-flex justify-content-between align-items-center table-file"><div name="filename" fid="${file.id}">${file.filename}${file.fileext}</div><div class="btn-group-table" style="display: none;">${editHtml}${viewHtml}${downHtml}${delHtml}</div></div>`;
+            const delHtml = file.canEdit || canDelete ? `<a href="javascript: void(0);" class="mr-1 text-danger" name="del-file" fid="${file.id}"><i class="fa fa-trash-o fa-fw"></i></a>` : '';
+            return `<div class="d-flex justify-content-between align-items-center table-file"><div name="filename" fid="${file.id}">${file.filename}${file.fileext}</div><div class="btn-group-table" style="display: none;">${moveHtml}${editHtml}${viewHtml}${downHtml}${delHtml}</div></div>`;
         }
         _getEditFileNameHtml(file) {
             const inputHtml = `<input type="text" class="form-control form-control-sm form-control-width" maxlength="100" value="${file.filename + file.fileext}" fid="${file.id}">`;
@@ -330,6 +331,26 @@ $(document).ready(function() {
                 if (callback) callback();
             });
         }
+        moveFile(file, filingId, callback) {
+            if (file.filing_id === filingId) {
+                callback();
+                return;
+            }
+            postData('file/move', { id: file.id, filingId: filingId }, function(data) {
+                const orgFiling = filingObj.findFiling(file.filing_id);
+                filingObj.updateFilingFileCount(orgFiling, orgFiling.source_node.file_count - 1);
+                const fIndex = orgFiling.source_node.files.findIndex(x => { return x.id === file.id });
+                if (fIndex >= 0) orgFiling.source_node.files.splice(fIndex, 1);
+                const targetFiling = filingObj.findFiling(filingId);
+                filingObj.updateFilingFileCount(targetFiling, targetFiling.source_node.file_count + 1);
+                if (file.filing_id === filingObj.curFiling.source_node.id) {
+                    filingObj.refreshPages();
+                    filingObj.refreshFilesTable();
+                }
+                fileSearch.refreshSearchFile(data.file);
+                callback();
+            });
+        }
         async setCurFiling(node) {
             filingObj.curFiling = node;
             filingObj.curPage = 1;
@@ -726,6 +747,110 @@ $(document).ready(function() {
 
         td.html(filingObj._getFileNameHtml(file));
     });
+    class MoveFileObj {
+        constructor() {
+            const self = this;
+            this.setting = {
+                treeId: 'moveTree',
+                view: {
+                    selectedMulti: false,
+                    showTitle: true,
+                },
+                data: {
+                    simpleData: {
+                        idKey: 'id',
+                        pIdKey: 'tree_pid',
+                        rootPId: '-1',
+                        enable: true,
+                    },
+                    key: { title: 'tips' },
+                },
+                edit: {
+                    enable: false,
+                },
+            };
+            this.moveTree = null;
+            $('#move-file2-ok').click(function() {
+                const targetFiling = self.moveTree.getSelectedNodes()[0];
+                if (!targetFiling) {
+                    toastr.warning('请选择目标分类');
+                    return;
+                }
+                if (targetFiling.children && targetFiling.children.length > 0) {
+                    toastr.warning('请选择最底层目标分类');
+                    return;
+                }
+                filingObj.moveFile(self.file, targetFiling.id, function() {
+                    $('#move-file2').modal('hide');
+                });
+            });
+            // 显示层次
+            (function (select) {
+                $(select).click(function () {
+                    const tag = $(this).attr('tag');
+                    setTimeout(() => {
+                        showWaitingView();
+                        switch (tag) {
+                            case "1":
+                            case "2":
+                            case "3":
+                            case "4":
+                                self.expandByLevel(parseInt(tag));
+                                break;
+                            case "last":
+                                self.expandByCustom(() => { return true; });
+                                break;
+                        }
+                        closeWaitingView();
+                    }, 100);
+                });
+            })('a[name=mf2-showLevel]');
+        }
+        expandByLevel(level) {
+            this.expandByCustom(x => {
+                return x.level + 1 < level;
+            })
+        }
+        expandByCustom(fun) {
+            const self = this;
+            const expandChildren = function(children) {
+                for (const child of children) {
+                    if (!child.children || child.children.length === 0) continue;
+                    const expand = fun(child);
+                    self.moveTree.expandNode(child, expand, false, false);
+                    expandChildren(child.children);
+                }
+            };
+            const nodes = this.moveTree.getNodes();
+            expandChildren(nodes);
+        }
+        initMoveTree() {
+            if (this.moveTree) $.fn.zTree.destroy(this.setting.treeId);
+            const sortNodes = filingObj.dragTree.nodes.map(x => {
+                const result = {
+                    id: x.id,
+                    tree_pid: x.tree_pid,
+                    name: x.name,
+                    spid: x.spid,
+                    tips: x.tips || '',
+                };
+                if (x.is_fixed) result.isParent = true;
+                return result;
+            });
+            this.moveTree = $.fn.zTree.init($('#moveFile'), this.setting, sortNodes);
+        }
+        show (file) {
+            this.file = file;
+            this.initMoveTree();
+            $('#move-file2').modal('show');
+        }
+    }
+    const moveFileObj = new MoveFileObj();
+    $('body').on('click', "a[name=move-file]", function() {
+        const id = this.getAttribute('fid');
+        const file = filingObj.curFiling.source_node.files.find(x => { return x.id === id });
+        moveFileObj.show(file);
+    });
     // $('body').on('blur', "[name=filename] input[fid]", function() {
     //     filingObj.renameFile(this.getAttribute('fid'), this.value);
     // });
@@ -1325,6 +1450,15 @@ $(document).ready(function() {
                 $('#search-list').html(fileSearch.getResultHtml(result.list));
             });
         }
+        refreshSearchFile(file) {
+            const sfile = this.searchResult.find(x => { return x.id === file.id; });
+            if (sfile) {
+                for (const prop in file) {
+                    if (prop === 'id') continue;
+                    sfile[prop] = file[prop];
+                }
+            }
+        }
         removeSearchFile(fid){
             $(`tr[fid=search_${fid}]`, '#search').remove();
             const index = this.searchResult.findIndex(x => { return x.id === fid; });

+ 1 - 0
app/public/js/file_list.js

@@ -56,6 +56,7 @@ $(document).ready(() => {
                 } else {
                     html.push(`<td>`);
                     if (node.manage_permission.indexOf(1) >= 0) html.push('<button class="btn btn-outline-primary btn-sm" data-target="#select-rela" name="del" onclick="showModal(this);">关联标段</button>');
+                    if (canManage) html.push(`<a href="/sp/${node.id}/fm" class="btn btn-outline-primary btn-sm ml-1">管理分类</a>`);
                     html.push('</td>');
                 }
                 return html.join('');

+ 414 - 0
app/public/js/filing_manage.js

@@ -0,0 +1,414 @@
+$(document).ready(function() {
+    autoFlashHeight();
+    $('#filing').height($(".sjs-height-0").height() - $('#add-slibing').parent().parent().height() - 10);
+    class FilingObj {
+        constructor(setting) {
+            // 原始数据整理后的树结构,用来整理zTree显示
+            this.dragTree = createDragTree({
+                id: 'id',
+                pid: 'tree_pid',
+                level: 'tree_level',
+                order: 'tree_order',
+                rootId: '-1'
+            });
+            // 界面显示的zTree
+            this.setting = setting;
+            this.filingTree = null;
+            $('#filing').height($(".sjs-height-0").height()-$('.d-flex',".sjs-height-0").height() - 10);
+        }
+        _loadFilingSourceNode() {
+            const self = this;
+            const loadChildren = function(children) {
+                for (const child of children) {
+                    if (child.children && child.children.length > 0) loadChildren(child.children);
+                    child.source_node = self.dragTree.getItems(child.id);
+                }
+            };
+            const nodes = this.filingTree.getNodes();
+            loadChildren(nodes);
+        }
+        calcTotalFileCount() {
+            this.dragTree.recursiveFun(this.dragTree.children, x => {
+                if (x.children && x.children.length > 0) {
+                    x.total_file_count = x.children.reduce((pre, c) => {
+                        return pre + c.total_file_count
+                    }, 0);
+                } else {
+                    x.total_file_count = x.file_count || 0;
+                }
+            });
+        }
+        loadFiling() {
+            if (this.filingTree) $.fn.zTree.destroy(this.setting.treeId);
+            const sortNodes = this.dragTree.nodes.map(x => {
+                const result = {
+                    id: x.id,
+                    tree_pid: x.tree_pid,
+                    name: x.name + (x.total_file_count > 0 ? `(${x.total_file_count})` : ''),
+                    spid: x.spid,
+                };
+                return result;
+            });
+            this.filingTree = $.fn.zTree.init($('#filing'), this.setting, sortNodes);
+            this._loadFilingSourceNode();
+            const curCache = getLocalCache(this.curFilingKey);
+            const curNode = curCache ? this.filingTree.getNodeByParam('id', curCache) : null;
+            if (curNode){
+                this.filingTree.selectNode(curNode);
+                filingObj.setCurFiling(curNode);
+            }
+        }
+        analysisFiling(data) {
+            this.dragTree.loadDatas(data);
+            this.calcTotalFileCount();
+            this.loadFiling();
+        }
+        addSiblingFiling(node) {
+            const self = this;
+            postData(`${window.location.pathname}/update`, { updateType: 'add', tree_pid: node.tree_pid, tree_pre_id: node.id }, function(result) {
+                const refreshData = self.dragTree.loadPostData(result);
+                const newNode = refreshData.create[0];
+                const nodes = self.filingTree.addNodes(node.getParentNode(), node.getIndex() + 1, [{ id: newNode.id, tree_pid: newNode.tree_pid, name: newNode.name, spid: newNode.spid }]);
+                nodes[0].source_node = newNode;
+            });
+        }
+        addChildFiling(node) {
+            const self = this;
+            postData(`${window.location.pathname}/update`, { updateType: 'add', tree_pid: node.id }, function(result) {
+                const refreshData = self.dragTree.loadPostData(result);
+                const newNode = refreshData.create[0];
+                const nodes = self.filingTree.addNodes(node, -1, [{ id: newNode.id, tree_pid: newNode.tree_pid, name: newNode.name, spid: newNode.spid}]);
+                nodes[0].source_node = newNode;
+            });
+        }
+        delFiling(node, callback) {
+            if (node.file_count > 0) return;
+            const parent = node.getParentNode();
+            const self = this;
+            postData(`${window.location.pathname}/update`, { updateType: 'del', id: node.id }, function(result) {
+                self.dragTree.loadPostData(result);
+                self.filingTree.removeNode(node);
+                self.calcTotalFileCount();
+                if (parent) {
+                    const path = parent.getPath();
+                    for (const p of path) {
+                        p.name = p.source_node.name + (p.source_node.total_file_count > 0 ? `(${p.source_node.total_file_count})` : '');
+                        filingObj.filingTree.updateNode(p);
+                    }
+                }
+                if (callback) callback();
+            });
+        }
+        async renameFiling(node, newName) {
+            const result = await postDataAsync(`${window.location.pathname}/update`, { updateType:'save', id: node.id, name: newName });
+            node.source_node.name = newName;
+            node.name = node.source_node.name + (node.source_node.total_file_count > 0 ? `(${node.source_node.total_file_count})` : '');
+            return result;
+        }
+        async setCurFiling(node) {
+            filingObj.curFiling = node;
+        }
+        moveFiling(node, tree_pid, tree_order) {
+            if (node.file_count > 0) return;
+            if (tree_pid === node.source_node.tree_pid && tree_order === node.source_node.tree_order) return;
+
+            const self = this;
+            postData(`${window.location.pathname}/update`, { updateType: 'move', id: node.id, tree_pid, tree_order }, function(result) {
+                const refresh = self.dragTree.loadPostData(result);
+                self.calcTotalFileCount();
+                const updated = [];
+                for (const u of refresh.update) {
+                    if (!u) continue;
+                    const node = self.filingTree.getNodeByParam('id', u.id);
+                    if (node) {
+                        const path = node.getPath();
+                        for (const p of path) {
+                            if (updated.indexOf(p.id) >= 0) continue;
+
+                            p.name = p.source_node.name + (p.source_node.total_file_count > 0 ? `(${p.source_node.total_file_count})` : '');
+                            filingObj.filingTree.updateNode(p);
+                            updated.push(p.id);
+                        }
+                    }
+                }
+            });
+        }
+        batchUpdateFiling(data, callback) {
+            const self = this;
+            postData(`${window.location.pathname}/update`, data, function(result) {
+                self.analysisFiling(result);
+                if (callback) callback();
+            })
+        }
+    }
+    const levelTreeSetting = {
+        treeId: 'filing',
+        view: {
+            selectedMulti: false,
+            showIcon: false,
+        },
+        data: {
+            simpleData: {
+                idKey: 'id',
+                pIdKey: 'tree_pid',
+                rootPId: '-1',
+                enable: true,
+            }
+        },
+        edit: {
+            enable: !readOnly,
+            showRemoveBtn: !readOnly,
+            showRenameBtn: !readOnly,
+            renameTitle: '编辑',
+            removeTitle: '删除',
+            drag: {
+                isCopy: false,
+                isMove: false,
+                pre: false,
+                next: false,
+                inner: false,
+            },
+            editNameSelectAll: true,
+        },
+        callback: {
+            onClick: async function (e, key, node) {
+                if (filingObj.curFiling && filingObj.curFiling.id === node.id) return;
+
+                filingObj.setCurFiling(node);
+            },
+            beforeRename: async function(key, node, newName, isCancel) {
+                if (!isCancel) await filingObj.renameFiling(node, newName);
+                return true;
+            },
+            beforeRemove: function(key, node, isCancel) {
+                filingObj.delFiling(node, function() {
+                    $('#del-filing').modal('hide');
+                });
+                return false;
+            },
+            beforeDrop: function(key, nodes, target, moveType, isCopy) {
+                if (readOnly) return false;
+                if (!target) return false;
+
+                const order = nodes[0].getIndex() + 1;
+                const targetOrder = target.getIndex() + 1;
+                const targetParent = target.getParentNode();
+                const targetMax = targetParent ? targetParent.children.length : filingObj.dragTree.children.length;
+                if (moveType === 'prev') {
+                    if (target.tree_pid === nodes[0].tree_pid) {
+                        if (targetOrder > order) {
+                            filingObj.moveFiling(nodes[0], target.tree_pid, targetOrder === 1 ? 1 : targetOrder - 1);
+                        } else {
+                            filingObj.moveFiling(nodes[0], target.tree_pid, targetOrder === 1 ? 1 : targetOrder);
+                        }
+                    } else {
+                        filingObj.moveFiling(nodes[0], target.tree_pid, targetOrder === 1 ? 1 : targetOrder);
+                    }
+                } else if (moveType === 'next') {
+                    if (target.tree_pid === nodes[0].tree_pid) {
+                        if (targetOrder < order) {
+                            filingObj.moveFiling(nodes[0], target.tree_pid, targetOrder === targetMax ? targetMax : targetOrder + 1);
+                        } else {
+                            filingObj.moveFiling(nodes[0], target.tree_pid, targetOrder === targetMax ? targetMax : targetOrder);
+                        }
+                    } else {
+                        filingObj.moveFiling(nodes[0], target.tree_pid, targetOrder + 1);
+                    }
+                } else if (moveType === 'inner') {
+                    filingObj.moveFiling(nodes[0], target.tree_id, targetMax + 1);
+                }
+            }
+        }
+    };
+    const filingObj = new FilingObj(levelTreeSetting);
+    filingObj.analysisFiling(filingData);
+    $('#add-slibing').click(() => {
+        if (!filingObj.curFiling) return;
+        filingObj.addSiblingFiling(filingObj.curFiling);
+    });
+    $('#add-child').click(() => {
+        if (!filingObj.curFiling) return;
+        filingObj.addChildFiling(filingObj.curFiling);
+    });
+
+    class MultiObj {
+        constructor(setting) {
+            this.modal = $(`#${setting.modal}`);
+            this.spread = SpreadJsObj.createNewSpread($(`#${setting.spread}`)[0]);
+            this.sheet = this.spread.getActiveSheet();
+            this.spreadSetting = {
+                cols: [
+                    { title: '名称', colSpan: '1', rowSpan: '1', field: 'name', hAlign: 0, width: 250, formatter: '@', readOnly: true, cellType: 'tree' },
+                    { title: '文件数', colSpan: '1', rowSpan: '1', field: 'file_count', hAlign: 1, width: 50, readOnly: true },
+                    { title: '固定', colSpan: '1', rowSpan: '1', field: 'is_fixed', hAlign: 1, width: 50, cellType: 'checkbox' },
+                    { title: '提示', colSpan: '1', rowSpan: '1', field: 'tips', hAlign: 0, width: 280, formatter: '@', wordWrap: 1 },
+                ],
+                emptyRows: 0,
+                headRows: 1,
+                headRowHeight: [32],
+                defaultRowHeight: 21,
+                headerFont: '12px 微软雅黑',
+                font: '12px 微软雅黑',
+            };
+            SpreadJsObj.initSheet(this.sheet, this.spreadSetting);
+            this.checkTree = createNewPathTree('base', {
+                id: 'tree_id',
+                pid: 'tree_pid',
+                order: 'order',
+                level: 'level',
+                isLeaf: 'is_leaf',
+                fullPath: 'full_path',
+                rootId: -1,
+            });
+            const self = this;
+            this.modal.bind('shown.bs.modal', function() {
+                self.spread.refresh();
+            });
+            this.spread.bind(spreadNS.Events.ButtonClicked, function(e, info) {
+                if (!info.sheet.zh_setting) return;
+                const sheet = info.sheet, cellType = sheet.getCellType(info.row, info.col);
+                if (cellType instanceof  spreadNS.CellTypes.CheckBox) {
+                    if (sheet.isEditing()) sheet.endEdit(true);
+                }
+                const col = info.sheet.zh_setting.cols[info.col];
+                if (col.field !== 'is_fixed') return;
+                const tree = self.checkTree;
+                const node = tree.nodes[info.row];
+                if (node.level <= 1 && node.is_fixed) {
+                    toastr.warning('顶层节点不可取消固定');
+                    SpreadJsObj.reLoadRowsData(info.sheet, [info.row]);
+                    return;
+                }
+
+                const row = [];
+                if (!node.is_fixed) {
+                    const relas = [];
+                    if (node.level > 1) {
+                        const parents = tree.getAllParents(node);
+                        for (const p of parents) {
+                            relas.push(...p.children);
+                            if (p.level === 1) relas.push(p);
+                        }
+                    } else {
+                        relas.push(node);
+                    }
+                    for (const p of relas) {
+                        p.is_fixed = true;
+                        row.push(tree.nodes.indexOf(p));
+                    }
+                } else {
+                    const parent = tree.getParent(node);
+                    const posterity = tree.getPosterity(parent);
+                    for (const p of posterity) {
+                        p.is_fixed = false;
+                        row.push(tree.nodes.indexOf(p));
+                    }
+                }
+                SpreadJsObj.reLoadRowsData(info.sheet, row);
+            });
+            this.spread.bind(spreadNS.Events.EditEnded, function(e, info) {
+                if (!info.sheet.zh_setting) return;
+                const col = info.sheet.zh_setting.cols[info.col];
+                const node = SpreadJsObj.getSelectObject(info.sheet);
+                node[col.field] = trimInvalidChar(info.editingText);
+                SpreadJsObj.reLoadRowData(info.sheet, info.row)
+            });
+            $(`#${setting.modal}-ok`).click(function() {
+                try {
+                    const data = self.getMultiUpdateData();
+                    filingObj.batchUpdateFiling(data, function() {
+                        window.location.reload();
+                    });
+                } catch(err) {
+                    toastr.error(err.stack ? '保存配置数据错误' : err);
+                }
+            });
+        }
+        reCalcFilingType() {
+            for (const node of this.checkTree.nodes) {
+                delete node.new_filing_type;
+                node.is_fixed = node.is_fixed ? 1 : 0;
+                if (node.file_count > 0 && node.is_fixed !== node.org_is_fixed) {
+                    throw `【${node.name}】下已存在文件,不可修改是否为固定节点`;
+                }
+            }
+            const sftIndex = [];
+            const getNewSft = function () {
+                let i = 1;
+                while(sftIndex[i]) {
+                    i++;
+                }
+                return i;
+            };
+            for (const node of this.checkTree.nodes) {
+                if (node.file_count) {
+                    node.new_filing_type = node.filing_type;
+                } else if (node.is_fixed) {
+                    node.new_filing_type = getNewSft();
+                } else {
+                    const parent = this.checkTree.getParent(node);
+                    if (!parent) return [false, '顶层节点必须为固定节点'];
+                    node.new_filing_type = parent.new_filing_type;
+                }
+                sftIndex[node.new_filing_type] = node;
+            }
+        }
+        getMultiUpdateData() {
+            this.reCalcFilingType();
+            const data = [];
+            const getUpdateData = function(children) {
+                for (const [i, node] of children.entries()) {
+                    data.push({ id: node.id, is_fixed: node.is_fixed, filing_type: node.new_filing_type, tree_order: i + 1, tips: node.tips || '' });
+                    // data.push({ id: node.id, is_fixed: node.is_fixed, filing_type: node.filing_type, file_count: node.file_count, new_filing_type: node.new_filing_type, tree_order: i + 1, tips: node.tips || '' });
+                    if (node.children) getUpdateData(node.children);
+                }
+            };
+            getUpdateData(this.checkTree.children);
+            return {updateType: 'multi', data};
+        }
+        _convertData(sourceTree) {
+            const data = [];
+            for (const node of sourceTree.nodes) {
+                const parent = node.tree_pid === '-1' ? undefined : data.find(x => { return x.id === node.tree_pid; });
+                const child = sourceTree.nodes.find(x => { return x.tree_pid === node.id; });
+                data.push({
+                    id: node.id,
+                    tree_id: data.length + 1,
+                    tree_pid: parent ? parent.tree_id : -1,
+                    order: node.tree_order + 1,
+                    level: node.tree_level,
+                    is_leaf: !child,
+                    full_path: '',
+                    name: node.name,
+                    org_is_fixed: node.is_fixed,
+                    is_fixed: node.is_fixed,
+                    filing_type: node.filing_type,
+                    tips: node.tips,
+                    file_count: node.file_count,
+                });
+            }
+            return data;
+        }
+        calculateFileCount(arr) {
+            let count = 0;
+            for (const a of arr) {
+                if (a.children && a.children.length > 0) a.file_count = this.calculateFileCount(a.children);
+                count = count + a.file_count;
+            }
+            return count;
+        }
+        reload(sourceTree) {
+            this.checkTree.loadDatas(this._convertData(sourceTree));
+            this.calculateFileCount(this.checkTree.children);
+            SpreadJsObj.loadSheetData(this.sheet, SpreadJsObj.DataType.Tree, this.checkTree);
+        }
+        show() {
+            this.modal.modal('show');
+        }
+    }
+    let multiObj = new MultiObj({ modal: 'multi-set', spread: 'multi-spread' });
+    $('#multi-setting').click(() => {
+        multiObj.reload(filingObj.dragTree);
+        multiObj.show();
+    });
+});

+ 32 - 0
app/public/js/filing_template.js

@@ -269,6 +269,16 @@ $(document).ready(function() {
             '</div>');
         $(`.table-file[tempId=${tempId}]`).html(html.join(''));
     });
+    $('body').on('click', 'a[name=shareTemplate]', function(e){
+        e.stopPropagation();
+        const tempId = $(this).parents('.table-file').attr('tempId');
+        hiddenSubmit('/file/template/save?id='+tempId, 'is_share', 1);
+    });
+    $('body').on('click', 'a[name=shareOffTemplate]', function(e){
+        e.stopPropagation();
+        const tempId = $(this).parents('.table-file').attr('tempId');
+        hiddenSubmit('/file/template/save?id='+tempId, 'is_share', 0);
+    });
 
     class MultiObj {
         constructor(setting) {
@@ -462,4 +472,26 @@ $(document).ready(function() {
             }
         });
     });
+    $('#import-share-template').click(() => {
+        const check = $('[name=sst-check]');
+        for (const c of check) {
+            c.checked = false;
+        }
+        $('#select-share-template').modal('show');
+    });
+    $('[name=sst-check]').change(function() {
+        const check = $('[name=sst-check]');
+        for (const c of check) {
+            if (c.value !== this.value) c.checked = false;
+        }
+        // if (val.length > 1) val.shift();
+    });
+    $('#sst-ok').click(function() {
+        const tempId = $('[name=sst-check]:checked').val();
+        if (!tempId) {
+            toastr.warning('请选择要导入的模板');
+            return;
+        }
+        hiddenSubmit('/file/template/save', 'share_id', tempId);
+    });
 });

+ 7 - 6
app/public/js/ledger.js

@@ -547,7 +547,8 @@ $(document).ready(function() {
                 };
                 // 未改变值则不提交
                 const orgValue = node[col.field];
-                const newValue = trimInvalidChar(info.editingText);
+                if (info.editingText === null) info.editingText = '';
+                const newValue = col.wordWrap ? info.editingText : trimInvalidChar(info.editingText);
                 if (orgValue == info.editingText || ((!orgValue || orgValue === '') && (newValue === ''))) {
                     return;
                 }
@@ -640,7 +641,7 @@ $(document).ready(function() {
                         for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
                             const curCol = info.cellRange.col + iCol;
                             const colSetting = info.sheet.zh_setting.cols[curCol];
-                            const value = trimInvalidChar(info.sheet.getText(curRow, curCol));
+                            const value = col.wordWrap ? info.sheet.getText(curRow, curCol) : trimInvalidChar(info.sheet.getText(curRow, curCol));
                             const lPos = pos.getLedgerPos(node.id);
                             if (lPos && lPos.length > 0) {
                                 if (value === '' && colSetting.field === 'b_code') {
@@ -742,7 +743,7 @@ $(document).ready(function() {
                 for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
                     const curCol = info.cellRange.col + iCol;
                     const colSetting = info.sheet.zh_setting.cols[curCol];
-                    const value = trimInvalidChar(pasteData[iRow-filterRow][iCol]);
+                    const value = colSetting.wordWrap ? pasteData[iRow-filterRow][iCol] : trimInvalidChar(pasteData[iRow-filterRow][iCol]);
                     const lPos = pos.getLedgerPos(node.id);
                     if (node.children && node.children.length > 0 && invalidFields.parent.indexOf(colSetting.field) >= 0) {
                         toastMessageUniq(hint.parent);
@@ -847,7 +848,7 @@ $(document).ready(function() {
                             const style = sheet.getStyle(iRow, iCol);
                             if (!style.locked) {
                                 const colSetting = sheet.zh_setting.cols[iCol];
-                                data[colSetting.field] = null;
+                                data[colSetting.field] = colSetting.type === 'Number' ? 0 : '';
                                 const exprInfo = getExprInfo(colSetting.field);
                                 if (exprInfo) {
                                     data[exprInfo.expr] = '';
@@ -1998,7 +1999,7 @@ $(document).ready(function() {
                 const posData = info.sheet.zh_data ? info.sheet.zh_data[info.row] : null;
                 const col = info.sheet.zh_setting.cols[info.col];
                 const orgText = posData ? posData[col.field] : null;
-                const newText = trimInvalidChar(info.editingText);
+                const newText = col.wordWrap ? info.editingText : trimInvalidChar(info.editingText);
                 if (orgText === newText || ((!orgText || orgText === '') && (newText === ''))) {
                     return;
                 }
@@ -2236,7 +2237,7 @@ $(document).ready(function() {
                         const colSetting = info.sheet.zh_setting.cols[curCol];
                         if (!colSetting) continue;
 
-                        posData[colSetting.field] = trimInvalidChar(info.sheet.getText(curRow, curCol));
+                        posData[colSetting.field] = colSetting.wordWrap ? info.sheet.getText(curRow, curCol) : trimInvalidChar(info.sheet.getText(curRow, curCol));
                         if (colSetting.type === 'Number') {
                             const num = _.toNumber(posData[colSetting.field]);
                             if (num) {

+ 9 - 7
app/public/js/measure_compare.js

@@ -185,7 +185,7 @@ const chapterSpreadSetting = {
         {title: '台账金额', colSpan: '1', rowSpan: '1', field: 'total_price', hAlign: 2, width: 80, type: 'Number'},
     ],
     extraCols: [
-        {title: '%s\n金额', colSpan: '1', rowSpan: '1', field: '{%s}_tp{%d}', hAlign: 2, width: 80, type: 'Number', },
+        {title: '%s\n金额', colSpan: '1', rowSpan: '1', field: '{%s}_tp{%d}', hAlign: 2, width: 100, type: 'Number', },
     ],
     specExtraCols: [
         {title: '合计金额', colSpan: '1', rowSpan: '1', field: 'sum_{%s}_tp', hAlign: 2, width: 80, type: 'Number', },
@@ -267,6 +267,7 @@ function calculateStagePosData(datas) {
 }
 
 $(document).ready(() => {
+    let spec;
     autoFlashHeight();
     initSpreadSettingWithRoles([]);
     const billsSpread = SpreadJsObj.createNewSpread($('#bills-spread')[0]);
@@ -375,7 +376,8 @@ $(document).ready(() => {
         gclGatherData = gclGatherModel.gatherGclData();
         SpreadJsObj.loadSheetData(gclSheet, SpreadJsObj.DataType.Data, gclGatherData);
         loadLeafXmjData(0);
-        const chapterData = gclGatherModel.gatherChapterData(chapter, result.spec, ['deal_tp', 'total_price', 'sum_contract_tp', 'sum_qc_tp', 'sum_gather_tp']);
+        spec = result.spec;
+        const chapterData = gclGatherModel.gatherChapterData(chapter, spec, ['deal_tp', 'total_price', 'sum_contract_tp', 'sum_qc_tp', 'sum_gather_tp']);
         SpreadJsObj.loadSheetData(chapterSheet, SpreadJsObj.DataType.Data, chapterData);
     }, null, true);
     function loadPosData(iRow) {
@@ -394,7 +396,6 @@ $(document).ready(() => {
         SpreadJsObj.resetTopAndSelect(leafXmjSheet);
     }
     billsSheet.bind(spreadNS.Events.SelectionChanged, function (e, info) {
-        console.log(SpreadJsObj.getSelectObject(info.sheet));
         if (info.newSelections) {
             const iNewRow = info.newSelections[0].row;
             if (info.oldSelections) {
@@ -426,14 +427,14 @@ $(document).ready(() => {
     });
     const compareStages = [];
     $('#select-qi-ok').click(function () {
-        function refreshView (data) {
+        function refreshView () {
             const gatherField = ['deal_tp', 'total_price', 'sum_contract_tp', 'sum_qc_tp', 'sum_gather_tp'];
             compareStages.length = 0;
             for (let order = 0, iLength = trs.length; order < iLength; order++) {
                 const tr = trs[order];
                 if ($('input', tr)[0].checked) {
                     compareStages.push(order + 1);
-                    gatherField.push(`contract_tp${order}`, `qc_tp${order}`, `gather_tp${order}`);
+                    gatherField.push(`contract_tp${order + 1}`, `qc_tp${order + 1}`, `gather_tp${order + 1}`);
                 }
             }
             // setLocalCache(cCacheKey, compareStages.join(','));
@@ -453,7 +454,7 @@ $(document).ready(() => {
             SpreadJsObj.loadSheetData(gclSheet, SpreadJsObj.DataType.Data, gclGatherData);
             loadLeafXmjData(0);
 
-            const chapterData = gclGatherModel.gatherChapterData(chapter, data.spec, gatherField);
+            const chapterData = gclGatherModel.gatherChapterData(chapter, spec, gatherField);
             SpreadJsObj.reLoadSheetHeader(chapterSheet);
             SpreadJsObj.loadSheetData(chapterSheet, SpreadJsObj.DataType.Data, chapterData);
         }
@@ -469,6 +470,7 @@ $(document).ready(() => {
         }
         if (loadData.length > 0) {
             postData(window.location.pathname + '/load', {stages: loadData}, function (result) {
+                spec = result.spec;
                 for (const aData of result.stages) {
                     calculateStageLedgerData(aData.bills);
                     cTree.loadMinorData(aData.bills, aData.order + '', ['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'gather_qty', 'gather_tp'], ['contract_tp', 'qc_tp', 'gather_tp']);
@@ -478,7 +480,7 @@ $(document).ready(() => {
                 cTree.reCalcSumData(['contract_qty', 'contract_tp', 'qc_qty', 'qc_tp', 'gather_qty', 'gather_tp'], showData);
                 treeCalc.calculateAll(cTree);
                 cPos.reCalcSumData(['contract_qty', 'qc_qty', 'gather_qty'], showData);
-                refreshView(result);
+                refreshView();
                 $('#select-qi').modal('hide');
             }, null, true);
         } else {

+ 7 - 6
app/public/js/revise.js

@@ -532,7 +532,8 @@ $(document).ready(() => {
                 };
                 // 未改变值则不提交
                 const orgValue = node[col.field];
-                const newValue = trimInvalidChar(info.editingText);
+                if (info.editingText === null) info.editingText = '';
+                const newValue = col.wordWrap ? info.editingText : trimInvalidChar(info.editingText);
                 if (orgValue == info.editingText || ((!orgValue || orgValue === '') && (newValue === ''))) {
                     return;
                 }
@@ -667,7 +668,7 @@ $(document).ready(() => {
                 for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
                     const curCol = info.cellRange.col + iCol;
                     const colSetting = info.sheet.zh_setting.cols[curCol];
-                    const value = trimInvalidChar(pasteData[iRow-filterRow][iCol]);
+                    const value = colSetting.wordWrap ? pasteData[iRow-filterRow][iCol] : trimInvalidChar(pasteData[iRow-filterRow][iCol]);
                     if (node.children && node.children.length > 0 && invalidFields.parent.indexOf(colSetting.field) >= 0) {
                         toastMessageUniq(hint.parent);
                         continue;
@@ -776,7 +777,7 @@ $(document).ready(() => {
                     for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
                         const curCol = info.cellRange.col + iCol;
                         const colSetting = info.sheet.zh_setting.cols[curCol];
-                        const value = trimInvalidChar(info.sheet.getText(curRow, curCol));
+                        const value = colSetting.wordWrap ? info.sheet.getText(curRow, curCol) : trimInvalidChar(info.sheet.getText(curRow, curCol));
                         const lPos = pos.getLedgerPos(node.id);
                         if (lPos && lPos.length > 0) {
                             if (value === '' && colSetting.field === 'b_code') {
@@ -855,7 +856,7 @@ $(document).ready(() => {
                         return;
                     }
 
-                    data[col.field] = null;
+                    data[col.field] = col.type === 'Number' ? 0 : '';
                     const exprInfo = getExprInfo(col.field);
                     if (exprInfo) {
                         data[exprInfo.expr] = '';
@@ -1700,7 +1701,7 @@ $(document).ready(() => {
             const posData = info.sheet.zh_data ? info.sheet.zh_data[info.row] : null;
             const col = info.sheet.zh_setting.cols[info.col];
             const orgText = posData ? posData[col.field] : null;
-            const newText = trimInvalidChar(info.editingText);
+            const newText = col.wordWrap ? info.editingText : trimInvalidChar(info.editingText);
             if (orgText === newText || ((!orgText || orgText === '') && (newText === ''))) return;
 
             const node = posSpreadObj.billsNode;
@@ -1951,7 +1952,7 @@ $(document).ready(() => {
                     const colSetting = info.sheet.zh_setting.cols[curCol];
                     if (!colSetting) continue;
 
-                    posData[colSetting.field] = trimInvalidChar(info.sheet.getText(curRow, curCol));
+                    posData[colSetting.field] = colSetting.wordWrap ? info.sheet.getText(curRow, curCol) : trimInvalidChar(info.sheet.getText(curRow, curCol));
                     if (posData.id && colSetting.type === 'Number' && sortData[curRow].settle_status === settleStatus.finish) {
                         bPaste = false;
                         toastMessageUniq(hint.settle);

+ 2 - 2
app/public/js/revise_price.js

@@ -359,7 +359,7 @@ $(document).ready(() => {
             const col = info.sheet.zh_setting.cols[info.col];
             const data = { update: { id: node.id, org_price: node.org_price } };
             const oldValue = node ? node[col.field] : null;
-            const newValue = trimInvalidChar(info.editingText);
+            const newValue = col.wordWrap ? info.editingText : trimInvalidChar(info.editingText);
             if (oldValue == info.editingText || ((!oldValue || oldValue === '') && (newValue === ''))) {
                 SpreadJsObj.reLoadRowData(info.sheet, info.row);
                 return;
@@ -399,7 +399,7 @@ $(document).ready(() => {
                 for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
                     const curCol = info.cellRange.col + iCol;
                     const colSetting = setting.cols[curCol];
-                    const value = trimInvalidChar(pasteData[iRow][iCol]);
+                    const value = col.wordWrap ? pasteData[iRow][iCol] : trimInvalidChar(pasteData[iRow][iCol]);
                     if (colSetting.type === 'Number') {
                         const num = _.toNumber(value);
                         if (num) {

+ 3 - 3
app/public/js/se_yjcl.js

@@ -222,7 +222,7 @@ $(document).ready(() => {
                     data.update.id = node.id;
 
                     const oldValue = node ? node[col.field] : null;
-                    const newValue = trimInvalidChar(info.editingText);
+                    const newValue = col.wordWrap ? info.editingText : trimInvalidChar(info.editingText);
                     if (oldValue == info.editingText || ((!oldValue || oldValue === '') && (newValue === ''))) {
                         SpreadJsObj.reLoadRowData(info.sheet, info.row);
                         return;
@@ -236,7 +236,7 @@ $(document).ready(() => {
                     }
                     data.add = {};
                     data.add.m_order = maxOrder + 1;
-                    data.add.name = trimInvalidChar(info.editingText);
+                    data.add.name = col.wordWrap ? info.editingText : trimInvalidChar(info.editingText);
                 }
 
                 postData(window.location.pathname + '/update', data, function (result) {
@@ -300,7 +300,7 @@ $(document).ready(() => {
                     for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
                         const curCol = info.cellRange.col + iCol;
                         const colSetting = setting.cols[curCol];
-                        const value = trimInvalidChar(pasteData[iRow][iCol]);
+                        const value = colSetting.wordWrap ? pasteData[iRow][iCol] : trimInvalidChar(pasteData[iRow][iCol]);
 
                         if (colSetting.field === 'name' && (!value || value === '')) {
                             toastMessageUniq(hint.name);

+ 3 - 3
app/public/js/sp_push.js

@@ -237,7 +237,7 @@ $(document).ready(() => {
                 data.update.id = node.id;
 
                 const oldValue = node ? node[col.field] : null;
-                const newValue = trimInvalidChar(info.editingText);
+                const newValue = col.wordWrap ? info.editingText : trimInvalidChar(info.editingText);
                 if (oldValue == info.editingText || ((!oldValue || oldValue === '') && (newValue === ''))) {
                     SpreadJsObj.reLoadRowData(info.sheet, info.row);
                     return;
@@ -246,7 +246,7 @@ $(document).ready(() => {
             } else {
                 data.add = {};
                 data.add.push_order = info.sheet.zh_data.length + 1;
-                data.add[col.field] = trimInvalidChar(info.editingText);
+                data.add[col.field] = col.wordWrap ? info.editingText : trimInvalidChar(info.editingText);
             }
             // 更新至服务器
             postData('push/update', data, function (result) {
@@ -303,7 +303,7 @@ $(document).ready(() => {
                 for (let iCol = 0; iCol < info.cellRange.colCount; iCol++) {
                     const curCol = info.cellRange.col + iCol;
                     const colSetting = setting.cols[curCol];
-                    const value = trimInvalidChar(pasteData[iRow][iCol]);
+                    const value = colSetting.wordWrap ? pasteData[iRow][iCol] : trimInvalidChar(pasteData[iRow][iCol]);
 
                     if (colSetting.type === 'Number') {
                         const num = _.toNumber(value);

+ 17 - 9
app/public/js/stage.js

@@ -659,13 +659,19 @@ $(document).ready(() => {
                 toastr.error('查询变更令有误,请刷新页面后重试');
             }
         }
+        _matchPos(match, change) {
+            return match && this.callData.pos
+                ? (change.gcl_id === this.callData.bills.id && change.b_bwmx === this.callData.pos.name)
+                : true;
+        }
+        _matchBillsParent(match, change) {
+            return match ? (!change.b_bwmx || change.b_bwmx === this.callData.leafXmj.name) : true;
+        }
         _filterChange(filterEmpty, matchPosName) {
             this.displayChanges = [];
             for (const c of this.changes) {
                 const filterVisible = filterEmpty ? (c.vamount ? !checkZero(c.vamount) : false) : true;
-                const matchVisible = matchPosName && this.callData.pos
-                    ? (c.gcl_id === this.callData.bills.id && c.b_bwmx === this.callData.pos.name)
-                    : true;
+                const matchVisible = this.callData.pos ? this._matchPos(matchPosName, c) : this._matchBillsParent(matchPosName, c);
                 if ((filterVisible && matchVisible) || (c.org_uamount)) {
                     this.displayChanges.push(c);
                 }
@@ -675,11 +681,12 @@ $(document).ready(() => {
         }
         loadChanges(data) {
             this.callData = data;
-            if (this.callData.pos) {
-                $('#matchPos').parent().show();
-            } else {
-                $('#matchPos').parent().hide();
-            }
+            // if (this.callData.pos) {
+            //     $('#matchPos').parent().show();
+            // } else {
+            //     $('#matchPos').parent().hide();
+            // }
+            $('#matchPos').parent().show();
             const self = this;
             $('#b-code-hint').text('当前变更清单:' + data.bills.b_code);
             postData(window.location.pathname + '/valid-change', data, function (result) {
@@ -725,7 +732,8 @@ $(document).ready(() => {
                 const nodePos = stagePos.getLedgerPos(data.id);
                 if (nodePos && nodePos.length > 0) return;
                 const minus = col.field === 'qc_minus_qty' ? 1 : 0;
-                changesObj.loadChanges({bills: data, minus, noValue: minus});
+                const leafXmj = stageTree.getLeafXmjParent(data);
+                changesObj.loadChanges({bills: data, minus, noValue: minus, leafXmj: leafXmj});
                 break;
             default: return;
         }

+ 13 - 0
app/public/js/sub_project.js

@@ -481,6 +481,10 @@ $(document).ready(function() {
             html.push(`<td><div class="custom-control custom-checkbox mb-2">
                         <input type="checkbox" ptype="file" sptype="upload" id="fileupload${mem.uid}" uid="${mem.uid}" class="custom-control-input" ${(fileUpload ? 'checked' : '')}>
                         <label class="custom-control-label" for="fileupload${mem.uid}"></label></div></td>`);
+            const fileDelete = mem.file_permission.indexOf(permissionConst.file.delete.value) >= 0;
+            html.push(`<td><div class="custom-control custom-checkbox mb-2">
+                        <input type="checkbox" ptype="file" sptype="delete" id="filedelete${mem.uid}" uid="${mem.uid}" class="custom-control-input" ${(fileDelete ? 'checked' : '')}>
+                        <label class="custom-control-label" for="filedelete${mem.uid}"></label></div></td>`);
             const fileEdit = mem.file_permission.indexOf(permissionConst.file.filing.value) >= 0;
             html.push(`<td><div class="custom-control custom-checkbox mb-2">
                         <input type="checkbox" ptype="file" sptype="filing" uid="${mem.uid}" id="fileedit${mem.uid}" class="custom-control-input" ${(fileEdit ? 'checked' : '')}>
@@ -560,6 +564,12 @@ $(document).ready(function() {
                     mem.file_permission.push(parseInt(permissionConst.file.view.value));
                     $(`#fileview${id}`)[0].checked = true;
                 }
+            } else if (pType === 'file' && spType === 'delete') {
+                mem.file_permission.push(parseInt(permissionConst.file.delete.value));
+                if (mem.file_permission.indexOf(permissionConst.file.view.value) < 0) {
+                    mem.file_permission.push(parseInt(permissionConst.file.view.value));
+                    $(`#fileview${id}`)[0].checked = true;
+                }
             } else if (pType === 'file' && spType === 'filing') {
                 mem.file_permission.push(parseInt(permissionConst.file.filing.value));
                 if (mem.file_permission.indexOf(permissionConst.file.view.value) < 0) {
@@ -579,8 +589,11 @@ $(document).ready(function() {
                 mem.file_permission = [];
                 $(`#fileupload${id}`)[0].checked = false;
                 $(`#fileedit${id}`)[0].checked = false;
+                $(`#filedelete${id}`)[0].checked = false;
             } else if (pType === 'file' && spType === 'upload') {
                 mem.file_permission.splice(mem.file_permission.indexOf(permissionConst.file.upload.value), 1);
+            } else if (pType === 'file' && spType === 'delete') {
+                mem.file_permission.splice(mem.file_permission.indexOf(permissionConst.file.delete.value), 1);
             } else if (pType === 'file' && spType === 'filing') {
                 mem.file_permission.splice(mem.file_permission.indexOf(permissionConst.file.filing.value), 1);
             } else if (pType === 'manage' && spType === 'rela') {

+ 5 - 0
app/router.js

@@ -911,10 +911,15 @@ module.exports = app => {
     app.post('/sp/:id/file/upload/big', sessionAuth, subProjectCheck, 'fileController.uploadBigFile');
     app.post('/sp/:id/file/del', sessionAuth, subProjectCheck, 'fileController.delFile');
     app.post('/sp/:id/file/save', sessionAuth, subProjectCheck, 'fileController.saveFile');
+    app.post('/sp/:id/file/move', sessionAuth, subProjectCheck, 'fileController.moveFile');
     app.post('/sp/:id/file/rela', sessionAuth, subProjectCheck, 'fileController.relaFile');
     app.post('/sp/:id/file/rela/tender', sessionAuth, subProjectCheck, 'fileController.loadValidRelaTender');
     app.post('/sp/:id/file/rela/files', sessionAuth, subProjectCheck, 'fileController.loadRelaFiles');
     app.post('/sp/:id/file/search', sessionAuth, subProjectCheck, 'fileController.search');
+    // 资料归集-管理员编辑
+    app.get('/sp/:id/fm', sessionAuth, projectManagerCheck, subProjectCheck, 'fileController.manage');
+    app.post('/sp/:id/lock-file', sessionAuth, projectManagerCheck, subProjectCheck, 'fileController.lockFiling');
+    app.post('/sp/:id/fm/update', sessionAuth, projectManagerCheck, subProjectCheck, 'fileController.manageUpdate');
 
     // 支付审批
     app.get('/payment', sessionAuth, 'paymentController.index');

+ 27 - 0
app/service/file.js

@@ -80,6 +80,11 @@ module.exports = app => {
 
             const fileDatas = await this.getAllDataByCondition({ where: { id: files } });
             const filing = await this.ctx.service.filing.getDataById(fileDatas[0].filing_id);
+            if (this.ctx.subProject.permission.file_permission.indexOf(this.ctx.service.subProjPermission.PermissionConst.file.delete.value) < 0) {
+                for (const file of fileDatas) {
+                    if (file.user_id !== this.ctx.session.sessionUser.accountId) throw '无权删除文件';
+                }
+            }
             const result = {};
 
             const conn = await this.db.beginTransaction();
@@ -135,6 +140,28 @@ module.exports = app => {
             return updateData;
         }
 
+        async moveFile(id, filing_id) {
+            const file = await this.getDataById(id);
+            if (!file) throw '文件不存在';
+            if (file.user_id !== this.ctx.session.sessionUser.accountId) throw '您无权编辑该文件';
+            const orgFiling = await this.ctx.service.filing.getDataById(file.filing_id);
+            const filing = await this.ctx.service.filing.getDataById(filing_id);
+            if (!filing) throw '目标分类不存在';
+
+            const conn = await this.db.beginTransaction();
+            try {
+                const updateFile = { id: file.id, filing_type: filing.filing_type, filing_id: filing.id };
+                await conn.update(this.tableName, updateFile);
+                const updateFiling = [{id: orgFiling.id, file_count: orgFiling.file_count - 1}, {id: filing.id, file_count: filing.file_count + 1}];
+                await conn.updateRows(this.ctx.service.filing.tableName, updateFiling);
+                await conn.commit();
+                return {file: updateFile, filing: updateFiling};
+            } catch(err) {
+                await conn.rollback();
+                throw err;
+            }
+        }
+
         async search(filing_type, keyword, limit = 1000) {
             if (!filing_type || filing_type.length === 0 || !keyword) return [];
             const sql = `SELECT * FROM ${this.tableName}` +

+ 39 - 9
app/service/filing.js

@@ -22,6 +22,7 @@ const filingType = [
     { value: 11, name: '生产技术准备、试运行文件' },
     { value: 12, name: '竣工验收文件' },
 ];
+const maxFilingType = 12;
 
 module.exports = app => {
     class Filing extends app.BaseService {
@@ -153,14 +154,20 @@ module.exports = app => {
             const max = filterNames.reduce((pre, cur) => { return Math.max(pre, cur); }, 0);
             return max >= 0 ? name + (max + 1) : name;
         }
+        async getNewFilingType(spid) {
+            const max = await this.db.queryOne(`SELECT filing_type FROM ${this.tableName} WHERE spid = '${spid}' ORDER BY filing_type DESC`);
+            return max && max.filing_type ? max.filing_type + 1 : maxFilingType + 1;
+        }
 
         async add(data) {
             const parent = await this.getDataById(data.tree_pid);
-            if (!parent) throw '添加数据结构错误';
-            if (parent.file_count > 0) throw `分类【${parent.name}】下存在文件,不可添加子分类`;
+            // 允许管理员添加顶层
+            // if (!parent) throw '添加数据结构错误';
+            if (parent && parent.file_count > 0) throw `分类【${parent.name}】下存在文件,不可添加子分类`;
 
-            const sibling = await this.getAllDataByCondition({ where: { tree_pid: parent.id }, orders: [['tree_order', 'asc']]});
+            const sibling = await this.getAllDataByCondition({ where: { spid: this.ctx.subProject.id, tree_pid: parent ? parent.id : rootId }, orders: [['tree_order', 'asc']]});
             const preChild = data.tree_pre_id ? sibling.find(x => { return x.id === data.tree_pre_id; }) : null;
+            const filing_type = parent ? parent.filing_type : await this.getNewFilingType(this.ctx.subProject.id);
 
             const conn = await this.db.beginTransaction();
             try {
@@ -170,11 +177,11 @@ module.exports = app => {
                 const sessionProject = this.ctx.session.sessionProject;
 
                 const tree_order = preChild ? preChild.tree_order + 1 : (sibling.length > 0 ? sibling[sibling.length - 1].tree_order + 1 : 1);
-                const name = await this.getNewName(parent.spid);
+                const name = await this.getNewName(this.ctx.subProject.id);
                 const insertData = {
-                    id: this.uuid.v4(), spid: parent.spid, add_user_id: sessionUser.accountId,
-                    tree_pid: parent.id, tree_level: parent.tree_level + 1, tree_order,
-                    name, filing_type: parent.filing_type,
+                    id: this.uuid.v4(), spid: this.ctx.subProject.id, add_user_id: sessionUser.accountId,
+                    tree_pid: parent ? parent.id : rootId, tree_level: parent ? parent.tree_level + 1 : 1, tree_order,
+                    name, filing_type: filing_type, is_fixed: parent ? 0 : 1
                 };
                 const operate = await conn.insert(this.tableName, insertData);
                 if (operate.affectedRows === 0) throw '新增文件夹失败';
@@ -196,7 +203,7 @@ module.exports = app => {
         }
         async save(data) {
             const filing = await this.getDataById(data.id);
-            this._checkFixed(filing);
+            // this._checkFixed(filing);
 
             const result = await this.db.update(this.tableName, data);
             if (result.affectedRows > 0) {
@@ -207,7 +214,7 @@ module.exports = app => {
         }
         async del(data) {
             const filing = await this.getDataById(data.id);
-            this._checkFixed(filing);
+            // this._checkFixed(filing);
 
             const posterity = await this.getPosterityData(data.id);
             const delData = posterity.map(x => {return { id: x.id, is_deleted: 1 }; });
@@ -285,6 +292,29 @@ module.exports = app => {
             return { update: [updateData, ...posterityUpdateData, ...siblingUpdateData] };
         }
 
+        async multiUpdate(spid, data) {
+            if (!data || data.length === 0) throw '提交数据格式错误';
+
+            const sourceData = await this.getAllDataByCondition({ where: { spid } });
+
+            const validFields = ['id', 'is_fixed', 'name', 'filing_type', 'tree_order', 'tips'];
+            const updateData = [];
+            for (const d of data) {
+                if (!d.id) throw '提交数据格式错误';
+                const sd = sourceData.find(x => { return x.id === d.id; });
+                if (!sd) throw '提交数据格式错误';
+
+                const nd = {};
+                for (const prop in d) {
+                    if (validFields.indexOf(prop) < 0) continue;
+                    nd[prop] = d[prop];
+                }
+                updateData.push(nd);
+            }
+            await this.db.updateRows(this.tableName, updateData);
+            return await this.getAllDataByCondition({ where: { spid } });
+        }
+
         async sumFileCount(spid) {
             const result = await this.db.queryOne(`SELECT SUM(file_count) AS file_count FROM ${this.tableName} WHERE spid = '${spid}' and is_deleted = 0`);
             return result.file_count;

+ 27 - 4
app/service/filing_template.js

@@ -40,15 +40,38 @@ module.exports = app => {
             this.tableName = 'filing_template';
         }
 
-        async initTemplate(transaction, template) {
-            const insertData = [];
+        getDefaultTemplateData(templateId) {
+            const result = [];
             for (const [i, f] of filingType.entries()) {
-                insertData.push({
-                    id: this.uuid.v4(), temp_id: template.id, tree_pid: -1, tree_level: 1, tree_order: i + 1,
+                result.push({
+                    id: this.uuid.v4(), temp_id: templateId, tree_pid: -1, tree_level: 1, tree_order: i + 1,
                     add_user_id: this.ctx.session.sessionUser.accountId, is_fixed: true,
                     name: f.name, filing_type: f.value,
                 });
             }
+            return result;
+        }
+
+        getCopyTemplateData(templateId, templateData) {
+            templateData.sort((x, y) => { return x.tree_level - y.tree_level; });
+            const insertData = [];
+            for (const d of templateData) {
+                if (d.id === undefined || d.tree_pid === undefined || d.tree_order === undefined || d.tree_level === undefined || d.name === undefined || d.is_fixed === undefined || d.filing_type === undefined) {
+                    throw '数据格式有误';
+                }
+                const parent = insertData.find(x => { return x.org_id === d.tree_pid });
+                insertData.push({
+                    id: this.uuid.v4(), temp_id: templateId, add_user_id: this.ctx.session.sessionUser.accountId,
+                    tree_pid: parent ? parent.id : rootId, tree_level: parent ? parent.tree_level + 1 : 1, tree_order: d.tree_order,
+                    name: d.name, tips: d.tips, filing_type: d.filing_type, is_fixed: parent ? d.is_fixed : 1, org_id: d.id,
+                });
+            }
+            insertData.forEach(x => { delete x.org_id; });
+            return insertData;
+        }
+
+        async initTemplate(transaction, template, templateData) {
+            const insertData = templateData ? this.getCopyTemplateData(template.id, templateData) : this.getDefaultTemplateData(template.id);
             if (transaction) {
                 await transaction.insert(this.tableName, insertData);
             } else {

+ 42 - 2
app/service/filing_template_list.js

@@ -38,6 +38,14 @@ module.exports = app => {
             return await this.db.query(sql, [pid]);
         }
 
+        async getShareTemplate(pid) {
+            const sql = `SELECT ftl.*, p.name as project_name, pa.name as user_name FROM ${this.tableName} ftl 
+                            LEFT JOIN ${this.ctx.service.projectAccount.tableName} pa ON ftl.user_id = pa.id
+                            LEFT JOIN ${this.ctx.service.project.tableName} p ON ftl.project_id = p.id  
+                            WHERE ftl.is_share = 1 and ftl.project_id <> ?`;
+            return await this.db.query(sql, [pid]);
+        }
+
         /**
          * 保存/新增数据
          *
@@ -45,9 +53,12 @@ module.exports = app => {
          * @param {String} id - 存在则为保存,反之新增
          * @return {boolean} - 操作结果
          */
-        async save(name, id = '') {
+        async save(name, is_share, id = '') {
             if (id) {
-                const result = await this.defaultUpdate({ id, name });
+                const updateData = { id };
+                if (name !== undefined) updateData.name = name;
+                if (is_share !== undefined) updateData.is_share = is_share;
+                const result = await this.defaultUpdate(updateData);
                 return [result.affectedRows > 0, id];
             } else {
                 const conn = await this.db.beginTransaction();
@@ -70,6 +81,35 @@ module.exports = app => {
         }
 
         /**
+         * 拷贝模板
+         *
+         * @param {string} shareId - 拷贝模板的id
+         * @return {Boolean} - 操作结果
+         */
+        async copy(shareId) {
+            const shareTemplate = await this.getDataById(shareId);
+            if (!shareTemplate.is_share) throw '模板已取消共享';
+            const shareTemplateData = await this.ctx.service.filingTemplate.getData(shareId);
+
+            const conn = await this.db.beginTransaction();
+            try {
+                const newTemplate = {
+                    id: this.uuid.v4(), project_id: this.ctx.session.sessionProject.id,
+                    user_id: this.ctx.session.sessionUser.accountId,
+                    name: shareTemplate.name || '拷贝XXX模板库', ft_type: FtType.add,
+                };
+                await conn.insert(this.tableName, newTemplate);
+                await this.ctx.service.filingTemplate.initTemplate(conn, newTemplate, shareTemplateData);
+                await conn.commit();
+                return [true, newTemplate.id];
+            } catch (err) {
+                this.ctx.log(err);
+                await conn.rollback();
+                return [false, err.stack ? '拷贝模板数据错误' : ''];
+            }
+        }
+
+        /**
          * 删除模板
          *
          * @param {string} id - 删除的id

+ 1 - 0
app/service/sub_proj_permission.js

@@ -29,6 +29,7 @@ module.exports = app => {
                 file: {
                     view: { title: '查看', value: 1 },
                     upload: { title: '上传/引用', value: 2 },
+                    delete: { title: '删除文件', value: 4 },
                     filing: { title: '文件类别编辑', value: 3 },
                 },
                 manage: {

+ 1 - 0
app/view/file/file.ejs

@@ -106,4 +106,5 @@
     const filing = JSON.parse(unescape('<%- escape(JSON.stringify(filing)) %>'));
     const category = JSON.parse(unescape('<%- escape(JSON.stringify(categoryData)) %>'));
     const whiteList = JSON.parse('<%- JSON.stringify(ctx.app.config.multipart.whitelist) %>');
+    const canDelete = <%- canDelete %>;
 </script>

+ 29 - 1
app/view/file/file_modal.ejs

@@ -203,4 +203,32 @@
         </div>
     </div>
 </div>
-<% } %>
+<% } %>
+<div class="modal fade" id="move-file2" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">选择分类</h5>
+            </div>
+            <div class="modal-body">
+                <div class="btn-group">
+                    <button type="button" class="btn btn-sm  text-primary dropdown-toggle" data-toggle="dropdown" id="mf2-zhankai" aria-expanded="false">显示层级</button>
+                    <div class="dropdown-menu" aria-labelledby="mf2-zhankai" x-placement="bottom-start" style="position: absolute; transform: translate3d(0px, 21px, 0px); top: 0px; left: 0px; will-change: transform;">
+                        <a class="dropdown-item" name="mf2-showLevel" tag="1" href="javascript: void(0);">第一层</a>
+                        <a class="dropdown-item" name="mf2-showLevel" tag="2" href="javascript: void(0);">第二层</a>
+                        <a class="dropdown-item" name="mf2-showLevel" tag="3" href="javascript: void(0);">第三层</a>
+                        <a class="dropdown-item" name="mf2-showLevel" tag="4" href="javascript: void(0);">第四层</a>
+                        <a class="dropdown-item" name="mf2-showLevel" tag="last" href="javascript: void(0);">最底层</a>
+                    </div>
+                </div>
+                <div class="modal-height-300">
+                    <ul id="moveFile" class="ztree"></ul>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-primary btn-sm" id="move-file2-ok">确定添加</button>
+            </div>
+        </div>
+    </div>
+</div>

+ 1 - 0
app/view/file/index.ejs

@@ -28,4 +28,5 @@
     autoFlashHeight();
     const projectList = JSON.parse(unescape('<%- escape(JSON.stringify(projectList)) %>'));
     const category = JSON.parse(unescape('<%- escape(JSON.stringify(categoryData)) %>'));
+    const canManage = <%- ctx.session.sessionUser.is_admin %>;
 </script>

+ 43 - 0
app/view/file/manage.ejs

@@ -0,0 +1,43 @@
+<div class="panel-content">
+    <div class="panel-title fluid">
+        <div class="title-main d-flex justify-content-between">
+            <div class="d-flex">
+                资料归集/<%- ctx.subProject.name %>
+                (当前状态:<span class="<%- ctx.subProject.lock_file ? 'text-success' : 'text-warning'%>"><%- ctx.subProject.lock_file ? '锁定' : '未锁定'%></span>)
+                <div class="d-flex">
+                    <div class="alert alert-warning p-1 mt-1"><i class="fa Example of exclamation-circle fa-exclamation-circle "></i> 请先锁定再编辑;锁定时其他人不可操作资料归集,仅可查看;编辑完成后需解锁。</div>
+                    <form class="ml-2" method="POST" action="/sp/<%- ctx.subProject.id %>/lock-file?lock=<%- ctx.subProject.lock_file ? 0 : 1 %>">
+                        <input type="hidden" name="_csrf_j" value="<%= ctx.csrf %>" />
+                        <button class="btn btn-sm btn-primary"><%- ctx.subProject.lock_file ? '解锁' : '锁定' %></button>
+                    </form>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="content-wrap">
+        <div class="c-body">
+            <div class="sjs-height-0">
+                <div>
+                    <% if (ctx.subProject.lock_file) { %>
+                    <div class="d-flex flex-row">
+                        <div class="p-2">
+                            <a href="javascript: void(0);" class="btn btn-sm btn-light text-primary" id="add-slibing"><i class="fa fa-plus" aria-hidden="true"></i> 同层</a>
+                            <a href="javascript: void(0);" class="btn btn-sm btn-light text-primary" id="add-child"><i class="fa fa-plus" aria-hidden="true"></i> 子项</a>
+                        </div>
+                        <div class="ml-auto p-2">
+                            <a href="javascript: void(0);" class="btn btn-sm btn-primary" id="multi-setting">附加配置</a>
+                        </div>
+                    </div>
+                    <% } %>
+                    <div>
+                        <ul id="filing" class="ztree" style="overflow: auto"></ul>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+<script>
+    const readOnly = <%- !ctx.subProject.lock_file %>;
+    const filingData = JSON.parse('<%- JSON.stringify(filingData) %>');
+</script>

+ 20 - 0
app/view/file/manage_modal.ejs

@@ -0,0 +1,20 @@
+<div class="modal fade" id="multi-set" data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title" id="tender-select-title">更多配置</h5>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                <div class="modal-height-300" id="multi-spread">
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">取消</button>
+                <button class="btn btn-sm btn-primary" id="multi-set-ok">确定</button>
+            </div>
+        </div>
+    </div>
+</div>

+ 11 - 1
app/view/file/template.ejs

@@ -19,9 +19,18 @@
                             <% for (const temp of templateList) { %>
                             <dd class="list-group-item <%- (temp.id === template.id ? 'bg-warning' : '')%>">
                                 <div class="d-flex justify-content-between align-items-center table-file" tempId="<%- temp.id %>">
-                                    <div><%if (temp.ft_type === FtType.org) { %><span class="text-success mr-1" style="border: 1px solid">默认</span><% } %><%- temp.name %>%></div>
+                                    <div>
+                                        <% if (temp.ft_type === FtType.org) { %><span class="text-success mr-1" style="border: 1px solid">默认</span><% } %>
+                                        <% if (temp.is_share) { %><span class="text-primary mr-1" style="border: 1px solid">分享</span><% } %>
+                                        <%- temp.name %>%>
+                                    </div>
                                     <% if (temp.ft_type) { %>
                                     <div class="btn-group-table" style="display: none;">
+                                        <% if (temp.is_share) { %>
+                                        <a href="javascript: void(0);" class="mr-1" data-toggle="tooltip" data-placement="bottom" data-original-title="取消分享" name="shareOffTemplate"><i class="fa fa-chain-broken fa-fw"></i></a>
+                                        <% } else { %>
+                                        <a href="javascript: void(0);" class="mr-1" data-toggle="tooltip" data-placement="bottom" data-original-title="分享" name="shareTemplate"><i class="fa fa-chain fa-fw"></i></a>
+                                        <% } %>
                                         <a href="javascript: void(0);" class="mr-1" data-toggle="tooltip" data-placement="bottom" data-original-title="编辑" name="renameTemplate"><i class="fa fa-pencil fa-fw"></i></a>
                                         <a href="javascript: void(0);" class="mr-1" data-toggle="tooltip" data-placement="bottom" data-original-title="删除" name="delTemplate"><i class="fa fa-trash-o fa-fw text-danger"></i></a>
                                     </div>
@@ -46,6 +55,7 @@
                         </form>
                         <div class="ml-auto p-2">
                             <a href="javascript: void(0);" class="btn btn-sm btn-primary" id="import-template">导入</a>
+                            <a href="javascript: void(0);" class="btn btn-sm btn-primary" id="import-share-template">拷贝分享模板库</a>
                             <a href="javascript: void(0);" class="btn btn-sm btn-primary" id="export-template">导出</a>
                             <a href="javascript: void(0);" class="btn btn-sm btn-primary" id="multi-setting">附加配置</a>
                         </div>

+ 23 - 1
app/view/file/template_modal.ejs

@@ -1,4 +1,3 @@
-<% include ../shares/select_file_modal.ejs %>
 <div class="modal fade" id="multi-set" data-backdrop="static">
     <div class="modal-dialog modal-lg" role="document">
         <div class="modal-content">
@@ -18,4 +17,27 @@
             </div>
         </div>
     </div>
+</div>
+<div class="modal fade" id="select-share-template" data-backdrop="static">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">选择分享模板库</h5>
+            </div>
+            <div class="modal-body">
+                <div class="form-group modal-height-300 scroll-y">
+                    <table class="table table-sm table-bordered table-hover">
+                        <tr class="text-center"><th>模板库名称</th><th>所属项目</th><th>分享人</th><th>选择</th></tr>
+                        <% for (const st of shareTemplate) { %>
+                        <tr><td><%- st.name %></td><td><%- st.project_name %></td><td class="text-center"><%- st.user_name %></td><td class="text-center"><input type="checkbox" name="sst-check" value="<%- st.id %>"></td></tr>
+                        <% } %>
+                    </table>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+                <button type="button" class="btn btn-primary btn-sm" id="sst-ok">确认</button>
+            </div>
+        </div>
+    </div>
 </div>

+ 6 - 6
app/view/layout/layout.ejs

@@ -67,12 +67,12 @@
         "showMethod": "fadeIn",
         "hideMethod": "fadeOut"
     };
-    let toastInfo = '<%- message %>';
-    try {
-        toastInfo = toastInfo !== '' && toastInfo !== 'null' ? JSON.parse(toastInfo) : '';
-    } catch (error) {
-        toastInfo = '';
-    }
+    const toastInfo = JSON.parse(unescape('<%- escape(JSON.stringify(message)) %>'));
+    // try {
+    //     toastInfo = toastInfo !== '' && toastInfo !== 'null' ? JSON.parse(toastInfo) : '';
+    // } catch (error) {
+    //     toastInfo = '';
+    // }
 
     if (toastInfo !== '') {
         toastr[toastInfo.type](toastInfo.message);

+ 4 - 4
app/view/stage/change.ejs

@@ -52,13 +52,13 @@
                     </div>
                     <div class="tab-content">
                         <div class="tab-pane active" id="qingdan">
-                            <div class="row">
-                                <div class="col-6" id="left-view">
+                            <div class="sp-wrap d-flex">
+                                <div class="c-body" id="left-view" style="width: 50%">
                                     <div class="sp-wrap" id="bills-spread">
                                     </div>
                                 </div>
-                                <div class="col-6" id="right-view">
-                                    <div id="bills-resize" class="resize-x" r-Type="width" div1="#left-spread" div2="#right-view" store-id="stage-change-bills" store-version="1.0.0" min="150"></div>
+                                <div class="c-body" id="right-view" style="width: 50%">
+                                    <div id="bills-resize" class="resize-x" r-Type="width" div1="#left-view" div2="#right-view" store-id="stage-change-bills" store-version="1.0.0" min="150"></div>
                                     <div class="tab-content">
                                         <div class="tab-pane active" id="tz">
                                             <div class="sp-wrap" id="pos-spread">

+ 1 - 1
app/view/stage/modal.ejs

@@ -46,7 +46,7 @@
                             <li class="nav-item">
                                 <p class="mb-0 mt-2 mr-3" id="b-code-hint">当前变更清单:201-1-1</p>
                             </li>
-                            <li class="nav-item">
+                            <li class="nav-item mr-2">
                                 <div class="custom-control custom-checkbox my-2">
                                     <input class="custom-control-input" id="filterEmpty" checked="" type="checkbox">
                                     <label class="custom-control-label" for="filterEmpty">显示可变更数量为0项 </label>

+ 2 - 0
app/view/stage/stage_sub_menu.ejs

@@ -33,6 +33,7 @@
             </ul>
         </div>
         <% } %>
+        <% if (!ctx.session.sessionProject.page_show.phasePay) { %>
         <div class="nav-box">
             <ul class="nav-list list-unstyled">
                 <li class="<% if (ctx.url === '/tender/' + ctx.tender.id + '/measure/stage/' + ctx.stage.order + '/pay') { %>active<% } %>">
@@ -40,6 +41,7 @@
                 </li>
             </ul>
         </div>
+        <% } %>
         <% if (ctx.tender.data.measure_type === 1 && ctx.session.sessionProject.page_show.bwtz) { %>
         <div class="nav-box">
             <ul class="nav-list list-unstyled">

+ 2 - 1
app/view/sub_proj/modal.ejs

@@ -154,7 +154,7 @@
                             <th rowspan="2" class="align-middle">成员名称</th>
                             <th rowspan="2" class="align-middle">职位</th>
                             <th colspan="2">动态投资</th>
-                            <th colspan="3">资料归集</th>
+                            <th colspan="4">资料归集</th>
                             <th rowspan="2" class="align-middle">关联标段</th>
                             <th rowspan="2" class="align-middle">移除</th>
                         </tr>
@@ -163,6 +163,7 @@
                             <th>编辑</th>
                             <th>查看</th>
                             <th>上传/引用</th>
+                            <th>删除文件</th>
                             <th>编辑分类</th>
                         </tr>
                         </thead>

+ 1 - 1
app/view/tender/tender_sub_menu.ejs

@@ -33,7 +33,7 @@
             </ul>
         </div>
         <div class="nav-box">
-            <% if (ctx.session.sessionProject.page_show.openIsolatePay) { %>
+            <% if (ctx.session.sessionProject.page_show.phasePay) { %>
             <ul class="nav-list list-unstyled">
                 <li <% if (ctx.url.indexOf('/tender/' + ctx.tender.id + '/pay') !== -1) { %>class="active"<% } %>><a href="/tender/<%- ctx.tender.id %>/pay" class="h3"><i class="fa fa-pie-chart fa-fw"></i> <span>合同支付</span></a></li>
             </ul>

+ 14 - 0
config/web.js

@@ -1319,6 +1319,20 @@ const JsFiles = {
                 ],
                 mergeFile: 'filing_template',
             },
+            manage: {
+                files: [
+                    '/public/js/ztree/jquery.ztree.core.js', '/public/js/ztree/jquery.ztree.exedit.js',
+                    '/public/js/spreadjs/sheets/v11/gc.spread.sheets.all.11.2.2.min.js',
+                    '/public/js/file-saver/FileSaver.js',
+                ],
+                mergeFiles: [
+                    '/public/js/spreadjs_rela/spreadjs_zh.js',
+                    '/public/js/path_tree.js',
+                    '/public/js/shares/drag_tree.js',
+                    '/public/js/filing_manage.js',
+                ],
+                mergeFile: 'filing_manage',
+            }
         },
         budget: {
             list: {

+ 6 - 0
sql/update.sql

@@ -13,6 +13,12 @@ ADD COLUMN `c_mode` tinyint(1) NULL DEFAULT 0 COMMENT '变更清单模式' AFTER
 ALTER TABLE `zh_change_ledger`
 ADD COLUMN `features` varchar(1000) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT '' COMMENT '项目特征' AFTER `memo`;
 
+ALTER TABLE `zh_sub_project`
+ADD COLUMN `lock_file` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '资料归集,是否锁定' AFTER `filing_template_name`;
+
+ALTER TABLE `zh_filing_template_list`
+ADD COLUMN `is_share` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否分享' AFTER `memo`;
+
 ------------------------------------
 -- 表数据
 ------------------------------------