Explorar o código

资料归集,分类管理

MaiXinRong hai 5 meses
pai
achega
0f3e56c2da

+ 63 - 2
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 '请先锁定,再管理分类数据';
+        }
 
         /**
          * 概算投资
@@ -85,6 +93,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 +104,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 +115,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 +127,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);
@@ -489,7 +501,56 @@ 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 {
+                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;
 };

+ 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;

+ 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();
+    });
+});

+ 5 - 5
app/public/js/ledger.js

@@ -547,7 +547,7 @@ $(document).ready(function() {
                 };
                 // 未改变值则不提交
                 const orgValue = node[col.field];
-                const newValue = col.field === 'features' ? info.editingText : trimInvalidChar(info.editingText);
+                const newValue = col.wordWrap ? info.editingText : trimInvalidChar(info.editingText);
                 if (orgValue == info.editingText || ((!orgValue || orgValue === '') && (newValue === ''))) {
                     return;
                 }
@@ -640,7 +640,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 +742,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);
@@ -1998,7 +1998,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 +2236,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) {

+ 5 - 5
app/public/js/revise.js

@@ -532,7 +532,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;
                 }
@@ -667,7 +667,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 +776,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') {
@@ -1700,7 +1700,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 +1951,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);

+ 4 - 0
app/router.js

@@ -916,6 +916,10 @@ module.exports = app => {
     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');

+ 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;

+ 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>

+ 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: {

+ 3 - 0
sql/update.sql

@@ -13,6 +13,9 @@ 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 `calculation`.`zh_sub_project`
+ADD COLUMN `lock_file` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '资料归集,是否锁定' AFTER `filing_template_name`;
+
 ------------------------------------
 -- 表数据
 ------------------------------------