ソースを参照

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

TonyKang 4 年 前
コミット
7750cf6cfe

+ 2 - 0
app/controller/stage_controller.js

@@ -324,6 +324,8 @@ module.exports = app => {
                             responseData.data.dealBills = await ctx.service.dealBills.getAllDataByCondition({
                                 where: { tender_id: this.ctx.tender.id },
                             });
+                        case 'tag':
+                            responseData.data.tags = await ctx.service.ledgerTag.getDatas(ctx.tender.id, ctx.stage.id);
                             break;
                     }
                 }

+ 29 - 12
app/public/css/main.css

@@ -65,7 +65,21 @@ font-size: .875rem;
 }
 .bs-popover-auto[x-placement^="bottom"] .arrow::after, .bs-popover-bottom .arrow::after{
   border-bottom-color:#000;
-}/*
+}
+.custom-control-warning-input:checked ~ .custom-control-warning-label::before{
+  border-color:#da9500 ;
+  background-color:#da9500 
+}
+.custom-control-warning-label{
+  color:#da9500;
+}
+.custom-control-label::before{
+  top:.15rem;
+}
+.custom-control-label::after{
+  top:.15rem;
+}
+/*
 .btn.disabled, .btn:disabled {
   opacity:.4
 }*/
@@ -144,6 +158,12 @@ font-size: .875rem;
 .border-secondary-50{
   border:1px solid #ccc;
 }
+.input-group-cancel{
+  position: absolute;
+  margin-left: -15px;
+  margin-top: 2px;
+  font-size: 14px
+}
 /*在谷歌下移除input[number]的上下箭头*/
 input.nospin[type='number']::-webkit-outer-spin-button,
 input.nospin[type='number']::-webkit-inner-spin-button{
@@ -198,7 +218,7 @@ input.nospin[type="number"]{-moz-appearance:textfield;}
 .sjs-height-4,.sjs-height-5,.sjs-height-6,.sjs-option-height{
   overflow: auto;
 }
-.sjs-bar-1,.sjs-bar-2,.sjs-bar-3,.sjs-bar-4{
+.sjs-bar-1,.sjs-bar-2,.sjs-bar-3,.sjs-bar-4,.sjs-bar-5{
   height:30px;
   padding-top:3px;
 }
@@ -453,6 +473,9 @@ input.nospin[type="number"]{-moz-appearance:textfield;}
 .border-bottom-1 {
   border-bottom:1px solid #dee2e6;
 }
+.save-confirm {
+  position:absolute;
+}
 /*滚动*/
 .scrollbar-auto {
     overflow-y: auto;
@@ -1021,12 +1044,6 @@ legend {
     right:0;
     top:35px;
   }
-.custom-control-label::before{
-  top:.1rem;
-}
-.custom-control-label::after{
-  top:.1rem;
-}
 .form-control-sm{
   height:calc(1.4125rem + 2px);
 }
@@ -1218,9 +1235,9 @@ overflow-y: auto;
 .context-menu-icon.context-menu-icon--fa.text-info::before {
     color: #17a2b8;!important;
 }
-.context-menu-icon.context-menu-icon--fa span{
-     color: #fff;!important;
- }
-.context-menu-icon.context-menu-hover.context-menu-icon--fa.text-success span{
+.context-menu-icon.context-menu-icon--fa.fa-tag span{
+    color: #2f2f2f;!important;
+}
+.context-menu-icon.context-menu-hover.context-menu-icon--fa.fa-tag span{
     color: #fff;!important;
 }

+ 21 - 1
app/public/css/main_s.css

@@ -65,7 +65,21 @@ font-size: .875rem;
 }
 .bs-popover-auto[x-placement^="bottom"] .arrow::after, .bs-popover-bottom .arrow::after{
   border-bottom-color:#000;
-}/*
+}
+.custom-control-warning-input:checked ~ .custom-control-warning-label::before{
+  border-color:#da9500 ;
+  background-color:#da9500 
+}
+.custom-control-warning-label{
+  color:#da9500;
+}
+.custom-control-label::before{
+  top:.15rem;
+}
+.custom-control-label::after{
+  top:.15rem;
+}
+/*
 .btn.disabled, .btn:disabled {
   opacity:.4
 }*/
@@ -144,6 +158,12 @@ font-size: .875rem;
 .border-secondary-50{
   border:1px solid #ccc;
 }
+.input-group-cancel{
+  position: absolute;
+  margin-left: -15px;
+  margin-top: 2px;
+  font-size: 14px
+}
 /*在谷歌下移除input[number]的上下箭头*/
 input.nospin[type='number']::-webkit-outer-spin-button,
 input.nospin[type='number']::-webkit-inner-spin-button{

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

@@ -70,6 +70,10 @@ $(document).ready(function() {
     const billsTag = $.billsTag({
         selector: '#bills-tag',
         relaSpread: ledgerSpread,
+        updateUrl: window.location.pathname + '/tag',
+        afterModify: function (nodes) {
+            SpreadJsObj.repaintNodesRowHeader(ledgerSpread.getActiveSheet(), nodes);
+        },
         afterLocated:  function () {
             posOperationObj.loadCurPosData();
         },
@@ -311,6 +315,7 @@ $(document).ready(function() {
                     const rows = [];
                     for (const u of data.update) {
                         rows.push(tree.nodes.indexOf(u));
+                        billsTag.refreshBillsTagView(u);
                     }
                     SpreadJsObj.reLoadRowsData(sheet, rows);
                 }
@@ -1000,9 +1005,10 @@ $(document).ready(function() {
                 getTagHtml: function (index, data) {
                     if (!data) return;
                     const getHtml = function (list) {
+                        if (!list || list.length === 0) return '';
                         const html = [];
                         for (const l of list) {
-                            html.push('<div class="row">');
+                            html.push('<div class="row mr-1">');
                             html.push(`<div class="col-auto pr-1 ${l.tagClass}">`, '<i class="fa fa-tag"></i>', '</div>');
                             html.push('<div class="col p-0">', '<p>', l.comment, '</p>', '</div>');
                             html.push('</div>');
@@ -1189,9 +1195,11 @@ $(document).ready(function() {
     //     const $obj = $('<div>' + item.name +)
     // };
     // 右键菜单
+    let addTagShare = true;
     const billsContextMenuOptions = {
         selector: '#ledger-spread',
         build: function ($trigger, e) {
+            addTagShare = true;
             const target = SpreadJsObj.safeRightClickSelection($trigger, e, ledgerSpread);
             return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
         },
@@ -1579,13 +1587,25 @@ $(document).ready(function() {
     billsContextMenuOptions.items.tag = {
         name: '书签',
         items: {
+            tagShare: {
+                name: '参与人可见',
+                type: 'checkbox',
+                selected: true,
+                events: {
+                    change: function () {
+                        addTagShare = this.checked;
+                    }
+                }
+            },
+            tagSpr: '--------------',
             tagPrimary: {
                 icon: 'fa-tag text-primary mt-2 mb-2',
                 name: '靛青',
-                callback: function (key, opt) {
+                callback: function (key, opt, menu, e) {
                     const node = SpreadJsObj.getSelectObject(ledgerSpread.getActiveSheet());
-                    postData(window.location.pathname + '/tag', {add: { color: '#007bff', lid: node.id }}, function (data) {
+                    postData(window.location.pathname + '/tag', {add: { color: '#007bff', lid: node.id, share: addTagShare }}, function (data) {
                         if (data.add) data.add.node = node;
+                        billsTag.updateDatasAndShow(data);
                         SpreadJsObj.repaintNodesRowHeader(ledgerSpread.getActiveSheet(), node);
                     });
                 },
@@ -1595,7 +1615,7 @@ $(document).ready(function() {
                 name: '果绿',
                 callback: function (key, opt) {
                     const node = SpreadJsObj.getSelectObject(ledgerSpread.getActiveSheet());
-                    postData(window.location.pathname + '/tag', {add: { color: '#28a745', lid: node.id }}, function (data) {
+                    postData(window.location.pathname + '/tag', {add: { color: '#28a745', lid: node.id, share: addTagShare }}, function (data) {
                         if (data.add) data.add.node = node;
                         billsTag.updateDatasAndShow(data);
                         SpreadJsObj.repaintNodesRowHeader(ledgerSpread.getActiveSheet(), node);
@@ -1607,7 +1627,7 @@ $(document).ready(function() {
                 name: '朱砂',
                 callback: function (key, opt) {
                     const node = SpreadJsObj.getSelectObject(ledgerSpread.getActiveSheet());
-                    postData(window.location.pathname + '/tag', {add: { color: '#dc3545', lid: node.id }}, function (data) {
+                    postData(window.location.pathname + '/tag', {add: { color: '#dc3545', lid: node.id, share: addTagShare }}, function (data) {
                         if (data.add) data.add.node = node;
                         billsTag.updateDatasAndShow(data);
                         SpreadJsObj.repaintNodesRowHeader(ledgerSpread.getActiveSheet(), node);
@@ -1619,7 +1639,7 @@ $(document).ready(function() {
                 name: '姜黄',
                 callback: function (key, opt) {
                     const node = SpreadJsObj.getSelectObject(ledgerSpread.getActiveSheet());
-                    postData(window.location.pathname + '/tag', {add: { color: '#da9500', lid: node.id }}, function (data) {
+                    postData(window.location.pathname + '/tag', {add: { color: '#da9500', lid: node.id, share: addTagShare }}, function (data) {
                         if (data.add) data.add.node = node;
                         billsTag.updateDatasAndShow(data);
                         SpreadJsObj.repaintNodesRowHeader(ledgerSpread.getActiveSheet(), node);
@@ -1631,7 +1651,7 @@ $(document).ready(function() {
                 name: '天蓝',
                 callback: function (key, opt) {
                     const node = SpreadJsObj.getSelectObject(ledgerSpread.getActiveSheet());
-                    postData(window.location.pathname + '/tag', {add: { color: '#17a2b8', lid: node.id }}, function (data) {
+                    postData(window.location.pathname + '/tag', {add: { color: '#17a2b8', lid: node.id, share: addTagShare }}, function (data) {
                         if (data.add) data.add.node = node;
                         billsTag.updateDatasAndShow(data);
                         SpreadJsObj.repaintNodesRowHeader(ledgerSpread.getActiveSheet(), node);

+ 185 - 22
app/public/js/shares/cs_tools.js

@@ -603,7 +603,7 @@ const showSelectTab = function(select, spread, afterShow) {
         ];
         const obj = $(setting.selector);
         const html = [], pageLength = 15;
-        let billsTags = [], classIndexes = [], billsIndexes = {};
+        let billsTags = [], classIndexes = [], billsIndexes = {}, curShow = [];
         html.push('<div class="sjs-bar d-flex justify-content-between">');
         // 下拉过滤
         html.push('<div class="dropdown mr-2">');
@@ -618,8 +618,10 @@ const showSelectTab = function(select, spread, afterShow) {
         html.push('</div>', '</div>');
         // 搜索框
         html.push('<div class="input-group input-group-sm">');
-        html.push('<input type="text" class="form-control" placeholder="可查找 项目节编号 / 清单编号 /名称">');
-        html.push('<div class="input-group-append">', '<button class="btn btn-outline-secondary" type="button" id="bills-tag-search">搜索</button>', '</div>');
+        html.push('<input type="text" class="form-control" placeholder="可查找 项目节编号 / 清单编号 /名称" id="bills-tag-keyword">');
+        html.push('<div class="input-group-append">', '<div class="input-group-cancel">',
+            '<a href="javascript: void(0);" id="bills-tag-clear" class="text-danger"><i class="fa fa-times-circle" title="移除搜索结果"></i></a>', '</div>',
+            '<button class="btn btn-outline-secondary" type="button" id="bills-tag-search">搜索</button>', '</div>');
         html.push('</div>');
         html.push('</div>');
         // 书签列表
@@ -627,16 +629,55 @@ const showSelectTab = function(select, spread, afterShow) {
         obj.html(html.join(''));
 
         const clearViewTags = function () {
-            const viewTags = $('[name=billsTag]', obj);
+            const viewTags = $('.tag-item', obj);
             if (viewTags && viewTags.length > 0) viewTags.remove();
             billsTags.forEach(x => {x.display = false});
         };
 
-        const getTagDisplayHtml = function(tag) {
+        const getTagEditHtml = function(tag) {
             const tagClass = classIndexes.find(x => {return x.color === tag.color});
             const tagHtml = [];
+            tagHtml.push('<div name="tag-edit">');
             tagHtml.push('<div class="card-header p-2"><div class="dropdown">');
-            tagHtml.push(`<a class="pull-left mr-2" href="" id="dropdownMenuButton" data-toggle="dropdown" aria-expanded="false"><i class="fa fa-tag ${tagClass.tagClass}" title="修改书签颜色"></i></a>`);
+            tagHtml.push(`<a class="pull-left mr-2" href="javascript: void(0);" id="tag-change-color" tag-color="${tag.color}" data-toggle="dropdown" aria-expanded="false"><i class="fa fa-tag ${tagClass.tagClass}" title="修改书签颜色"></i></a>`);
+            // 下拉选择颜色
+            tagHtml.push('<div class="dropdown-menu" aria-labelledby="tag-change-color" style="min-width:60px">',
+                '<a class="dropdown-item text-primary" href="javascript: void(0);" name="tag-color"><i class="fa fa-tint"></i></a>',
+                '<a class="dropdown-item text-success " href="javascript: void(0);" name="tag-color"><i class="fa fa-tint"></i></a>',
+                '<a class="dropdown-item text-danger " href="javascript: void(0);" name="tag-color"><i class="fa fa-tint"></i></a>',
+                '<a class="dropdown-item text-warning " href="javascript: void(0);" name="tag-color"><i class="fa fa-tint"></i></a>',
+                '<a class="dropdown-item text-info " href="javascript: void(0);" name="tag-color"><i class="fa fa-tint"></i></a>', '</div>');
+            tagHtml.push('</div>');
+            tag.node && tagHtml.push((tag.node.code || '') + (tag.node.b_code || ''), ' / ', tag.node.name || '');
+            tagHtml.push('</div>');
+
+            tagHtml.push('<div class="card-body p-2">');
+            tagHtml.push('<p class="card-text">', '<textarea class="form-control" id="tag-comment">', tag.comment, '</textarea>', '</p>');
+
+            tagHtml.push('<div class="d-flex justify-content-between">');
+            // 参与人可见
+            tagHtml.push('<div class="custom-control custom-switch mr-2">');
+            tagHtml.push('<input type="checkbox" class="custom-control-input custom-control-warning-input" id="tag-share"', tag.share ? 'checked' : '', '>');
+            tagHtml.push('<label class="custom-control-label custom-control-warning-label" for="tag-share" data-toggle="tooltip" data-placement="bottom" title="" data-original-title="所有参与台帐审批管理的用户都可以看到这条书签"><i class="fa fa-users"></i> 参与者可见</label>');
+            tagHtml.push('</div>');
+            // 编辑按钮
+            tagHtml.push('<div>', '<button type="button" class="btn btn-sm btn-outline-danger mr-3" id="tag-del"><i class="fa fa-close"></i>  删除书签</button>',
+                '<button type="button" class="btn btn-sm btn-outline-success mr-1" id="tag-edit-ok"><i class="fa fa-check"></i> 确定</button>',
+                '<button type="button" class="btn btn-sm btn-outline-secondary" id="tag-edit-cancel">取消</button>', '</div>');
+            tagHtml.push('</div>');
+
+            tagHtml.push('</div>');
+
+            tagHtml.push('</div>');
+            return tagHtml.join('');
+        };
+
+        const getTagDisplayHtml = function (tag) {
+            const tagClass = classIndexes.find(x => {return x.color === tag.color});
+            const tagHtml = [];
+            tagHtml.push('<div name="tag-view">');
+            tagHtml.push('<div class="card-header p-2"><div class="dropdown">');
+            tagHtml.push(`<div class="pull-left mr-2"><i class="fa fa-tag ${tagClass.tagClass}"></i></div>`);
             tagHtml.push('</div>');
             tag.node && tagHtml.push((tag.node.code || '') + (tag.node.b_code || ''), ' / ', tag.node.name || '');
             if (tag.share) {
@@ -645,19 +686,51 @@ const showSelectTab = function(select, spread, afterShow) {
             tagHtml.push('<div class="pull-right edit-tag-btn">');
             const lid = tag.node ? tag.node.ledger_id : -1;
             tagHtml.push(`<a class="mr-1" name="bills-tag-locate" href="javascript: void(0);" lid="${lid}"><i class="fa fa-crosshairs"></i> 定位</a>`);
-            if (tag.uid === userID) tagHtml.push('<a href="javascript: void(0);"><i class="fa fa-edit"></i> 编辑</a>');
+            if (tag.uid === userID) tagHtml.push(`<a href="javascript: void(0);" name="bills-tag-edit" tag-id="${tag.id}"><i class="fa fa-edit"></i> 编辑</a>`);
             tagHtml.push('</div>');
             tagHtml.push('<div class="card-body p-2">', '<p class="card-text">', tag.comment, '</p>', '</div>');
+            tagHtml.push('</div>');
             return tagHtml.join('');
         };
 
+        const searchTagsAndShow = function () {
+            const keyword = $('#bills-tag-keyword').val();
+            const filterClass = $('#bills-tag-filter')[0].classList;
+            const tagClass = filterClass.length > 2 ? filterClass[2] : null;
+            const ci = tagClass ? classIndexes.find(x => {return x.tagClass === tagClass}) : null;
+            curShow = billsTags.filter(x => {
+                if (ci && ci.color !== x.color) return false;
+                if (!keyword) return true;
+                if (x.node.code && x.node.code.indexOf(keyword) >= 0) return true;
+                if (x.node.b_code && x.node.b_code.indexOf(keyword) >= 0) return true;
+                if (x.node.name && x.node.name.indexOf(keyword) >= 0) return true;
+                return false;
+            });
+            reloadViewTags();
+        };
+
+        const refreshTagView = function (tag) {
+            const obj = $('#bills-tag-' + tag.id);
+            if (obj && obj.length > 0) {
+                obj.html(getTagDisplayHtml(tag));
+            }
+        };
+
+        const refreshBillsTagView = function (bills) {
+            const bi = billsIndexes[bills.id] || [];
+
+            for (const tag of bi) {
+                refreshTagView(tag);
+            }
+        };
+
         const reviewTag = function (tag, isTop = false) {
             const obj = $('#bills-tag-' + tag.id);
             if (obj && obj.length > 0) {
                 obj.html(getTagDisplayHtml(tag));
             } else {
                 const objHtml = [];
-                objHtml.push(`<div class="card border-primary my-2 tag-item" id="bills-tag-${tag.id}">`);
+                objHtml.push(`<div class="card border-primary my-2 tag-item" id="bills-tag-${tag.id}" tag-id="${tag.id}">`);
                 objHtml.push(getTagDisplayHtml(tag));
                 objHtml.push('</div>');
                 if (isTop) {
@@ -669,9 +742,9 @@ const showSelectTab = function(select, spread, afterShow) {
             tag.display = true;
         };
 
-        const loadViewTags = function (tags) {
+        const loadViewTags = function () {
             let showCount = 0;
-            for (const t of tags) {
+            for (const t of curShow) {
                 if (showCount >= pageLength) continue;
                 if (t.display) continue;
                 reviewTag(t);
@@ -679,6 +752,12 @@ const showSelectTab = function(select, spread, afterShow) {
             }
         };
 
+        const reloadViewTags = function () {
+            clearViewTags();
+            $("#bills-tag-lis").scrollTop(0);
+            loadViewTags();
+        };
+
         const _addToBillsIndex = function(data, isTop = false) {
             let bi = billsIndexes[data.lid];
             if (!bi) {
@@ -695,28 +774,23 @@ const showSelectTab = function(select, spread, afterShow) {
                 billsTags.push(d);
                 _addToBillsIndex(d);
             }
-            for (const ci of classIndexes) {
-                ci.tags = billsTags.filter(x => {return x.color === ci.color});
-            }
-            clearViewTags();
-            loadViewTags(billsTags);
+            curShow = billsTags;
+            reloadViewTags();
         };
 
         const updateDatas = function (data) {
             const refresh = {};
             if (data.add) {
                 billsTags.push(data.add);
-                const tagClass = classIndexes.find(x => {return x.color === data.add.color});
-                if (tagClass) tagClass.tags.push(data.add);
                 _addToBillsIndex(data.add, true);
                 refresh.add = data.add;
             }
             if (data.del) {
-                const delTag = billsTags.find(x => {return x.id === data.del.id});
+                const delTag = billsTags.find(x => {return x.id === data.del});
                 billsTags.splice(billsTags.indexOf(delTag), 1);
-                const tagClass = classIndexes.find(x => {x.color = delTag.color});
-                if (tagClass) tagClass.splice(tagClass.indexOf(delTag), -1);
-                refresh.del = data.del
+                const bi = billsIndexes[delTag.node.id];
+                bi.splice(bi.indexOf(delTag), 1);
+                refresh.del = delTag;
             }
             if (data.update) {
                 const updateTag = billsTags.find(x => {return x.id === data.update.id});
@@ -729,10 +803,21 @@ const showSelectTab = function(select, spread, afterShow) {
         };
 
         const updateDatasAndShow = function (data) {
+            const relaBills = [];
             const refresh = updateDatas(data);
             if (refresh.add) {
                 reviewTag(refresh.add, true);
+                relaBills.push(refresh.add.node);
             }
+            if (refresh.del) {
+                $('#bills-tag-' + refresh.del.id).remove();
+                relaBills.push(refresh.del.node);
+            }
+            if (refresh.update) {
+                refreshTagView(refresh.update);
+                relaBills.push(refresh.update.node);
+            }
+            return relaBills;
         };
 
         const show = function () {
@@ -757,7 +842,85 @@ const showSelectTab = function(select, spread, afterShow) {
             SpreadJsObj.locateTreeNode(setting.relaSpread.getActiveSheet(), lid);
             setting.afterLocated && setting.afterLocated();
         });
+        $('body').on('click', '[name=bills-tag-edit]', function () {
+            const tagId = this.getAttribute('tag-id');
+            const tag = billsTags.find(x => {return x.id == tagId});
+            if (tag) {
+                const obj = $('#bills-tag-' + tag.id);
+                $('[name=tag-view]', obj).hide();
+                obj.append(getTagEditHtml(tag));
+            }
+        });
+        $('body').on('click', '#tag-edit-cancel', function () {
+            const obj = $('[name=tag-edit]').parent();
+            $('[name=tag-edit]').remove();
+            $('[name=tag-view]', obj).show();
+        });
+        $('body').on('click', '#tag-del', function () {
+            const obj = $('[name=tag-edit]').parent();
+            postData(setting.updateUrl, {del: parseInt(obj.attr('tag-id'))}, function (result) {
+                if (!result.del) return;
+
+                const bills = updateDatasAndShow(result);
+                setting.afterModify && setting.afterModify(bills);
+            });
+        });
+        $('body').on('click', '#tag-edit-ok', function () {
+            const obj = $('[name=tag-edit]').parent();
+            const data = {
+                id: parseInt(obj.attr('tag-id')),
+                share: $('#tag-share')[0].checked,
+                comment: $('#tag-comment').val(),
+                color: $('#tag-change-color').attr('tag-color'),
+            };
+            postData(setting.updateUrl, {update: data}, function (result) {
+                if (!result.update) return;
+
+                const bills = updateDatasAndShow(result);
+                setting.afterModify && setting.afterModify(bills);
+            });
+        });
+        $('body').on('click', '[name=tag-color]', function () {
+            const tagClass = this.classList[1];
+            const ci = classIndexes.find(tc => {return tc.tagClass === tagClass});
+            const tcc = $('#tag-change-color');
+            tcc.attr('tag-color', ci.color);
+            tcc.find('i').attr('class', 'fa fa-tag ' + tagClass);
+        });
+        $('body').on('click', '[tagType]', function () {
+            const tagClass = this.getAttribute('tagType');
+            if (tagClass === 'all') {
+                $('#bills-tag-filter').attr('class', 'fa fa-list-ol');
+            } else {
+                $('#bills-tag-filter').attr('class', 'fa fa-tag ' + tagClass);
+            }
+            searchTagsAndShow();
+        });
+        // 防抖
+        function debounce(fun, delay) {
+            let timer = null;
+            return function () {
+                if (timer) {
+                    clearTimeout(timer);
+                }
+                timer = setTimeout(fun, delay);
+            }
+        }
+        $('#bills-tag-list').bind('scroll', debounce(function (e) {
+            const obj = $('#bills-tag-list');
+            var sum = obj[0].scrollHeight;
+            if (sum <= obj.scrollTop() + obj.height()) {
+                loadViewTags();
+            }
+        }, 300));
+        $('#bills-tag-clear').bind('click', () => {
+            if (!$('#bills-tag-keyword').val()) return;
+            $('#bills-tag-keyword').val('');
+            searchTagsAndShow();
+        });
+        $('#bills-tag-search').bind('click', () => {searchTagsAndShow();});
+        $('#bills-tag-keyword').bind('keydown', e => {if (e.keyCode === 13) searchTagsAndShow();});
 
-        return { loadDatas, updateDatasAndShow, show, getBillsTagsColor, getBillsTagsInfo }
+        return { loadDatas, updateDatasAndShow, show, getBillsTagsColor, getBillsTagsInfo, refreshBillsTagView, }
     }
 })(jQuery);

+ 46 - 5
app/public/js/spreadjs_rela/spreadjs_zh.js

@@ -2384,17 +2384,58 @@ const SpreadJsObj = {
                 spreadNS.CellTypes.RowHeader.prototype.paint.apply(this, [canvas, '', x, y, w , h, style, options]);
                 spreadNS.CellTypes.Text.prototype.paint.apply(this, [canvas, value, x, y, w - this.indent, h, style, options]);
                 const node = SpreadJsObj.getRowObject(options.sheet, options.row);
-                let sel = options.sheet.getSelections();
-                sel = sel ? sel[0] : null;
-                const actualStyle = options.sheet.getActualStyle(options.row, options.col, options.sheetArea, false);
-                if (options.row === 1) console.log(actualStyle);
-                const backColor = style.backColor || (sel && options.row >= sel.row && options.row < sel.row + sel.rowCount ? '#dddfe1' : '#e9ecef');
+                let backColor = style.backColor;
+                if (!backColor) {
+                    const tag = options.sheet.getTag(options.row, options.col, options.sheetArea);
+                    if (tag && tag === 'hover') backColor = '#dddfe1';
+                    if (!backColor) {
+                        let sel = options.sheet.getSelections();
+                        sel = sel ? sel[0] : null;
+                        backColor = (sel && options.row >= sel.row && options.row < sel.row + sel.rowCount ? '#dddfe1' : '#e9ecef');
+                    }
+                }
                 let color = this.getTagColor(options.row, node);
                 color = color instanceof Array ? color : [color];
                 for (let i = color.length - 1; i >= 0; i--) {
                     drawCircle2(canvas, x + w - this.indent + 5 + i*5 , y + h/2, 6, backColor, color[i]);
                 }
             };
+            /**
+             * 获取点击信息
+             * @param {Number} x
+             * @param {Number} y
+             * @param {Object} cellStyle
+             * @param {Object} cellRect
+             * @param {Object} context
+             * @returns {{x: *, y: *, row: *, col: *|boolean|*[]|number|{}|UE.dom.dtd.col, cellStyle: *, cellRect: *, sheet: *|StyleSheet, sheetArea: *}}
+             */
+            proto.getHitInfo = function (x, y, cellStyle, cellRect, context) {
+                return {
+                    x: x,
+                    y: y,
+                    row: context.row,
+                    col: context.col,
+                    cellStyle: cellStyle,
+                    cellRect: cellRect,
+                    sheet: context.sheet,
+                    sheetArea: context.sheetArea,
+                    ctx: context.sheet.getParent().xs,
+                };
+            };
+            /**
+             * 鼠标进入单元格事件 - 显示悬浮提示
+             * @param {Object} hitinfo - 见getHitInfo返回值
+             */
+            proto.processMouseEnter = function (hitinfo) {
+                hitinfo.sheet.setTag(hitinfo.row, hitinfo.col, 'hover', hitinfo.sheetArea);
+            };
+            /**
+             * 鼠标移出单元格事件 - 隐藏悬浮提示
+             * @param {Object} hitinfo - 见getHitInfo返回值
+             */
+            proto.processMouseLeave = function (hitinfo) {
+                hitinfo.sheet.setTag(hitinfo.row, hitinfo.col, undefined, hitinfo.sheetArea);
+            };
             return new CircleTagCellType();
         },
     },

+ 130 - 6
app/public/js/stage.js

@@ -572,6 +572,32 @@ $(document).ready(() => {
     sjsSettingObj.setPropValue(ledgerSpreadSetting, ['gxby'], 'getValue', getGxbyText);
     sjsSettingObj.setPropValue(ledgerSpreadSetting, ['dagl'], 'getValue', getDaglText);
     if (thousandth) sjsSettingObj.setTpThousandthFormat(ledgerSpreadSetting);
+    ledgerSpreadSetting.rowHeader = [
+        {
+            rowHeaderType: 'tag',
+            setting: {
+                getColor: function (index, data) {
+                    if (!data) return;
+                    return billsTag.getBillsTagsColor(data.id);
+                },
+                getTagHtml: function (index, data) {
+                    if (!data) return;
+                    const getHtml = function (list) {
+                        if (!list || list.length === 0) return '';
+                        const html = [];
+                        for (const l of list) {
+                            html.push('<div class="row mr-1">');
+                            html.push(`<div class="col-auto pr-1 ${l.tagClass}">`, '<i class="fa fa-tag"></i>', '</div>');
+                            html.push('<div class="col p-0">', '<p>', l.comment, '</p>', '</div>');
+                            html.push('</div>');
+                        }
+                        return html.join('');
+                    };
+                    return getHtml(billsTag.getBillsTagsInfo(data.id));
+                }
+            },
+        },
+    ];
     SpreadJsObj.initSheet(slSpread.getActiveSheet(), ledgerSpreadSetting);
     slSpread.getActiveSheet().frozenColumnCount(5);
     slSpread.getActiveSheet().options.frozenlineColor = '#93b5e4';
@@ -617,6 +643,21 @@ $(document).ready(() => {
     sjsSettingObj.setPropValue(posSpreadSetting, ['dagl'], 'getValue', getDaglText);
     SpreadJsObj.initSheet(spSpread.getActiveSheet(), posSpreadSetting);
 
+    const billsTag = $.billsTag({
+        selector: '#bills-tag',
+        relaSpread: slSpread,
+        updateUrl: window.location.pathname + '/tag',
+        afterModify: function (nodes) {
+            SpreadJsObj.repaintNodesRowHeader(slSpread.getActiveSheet(), nodes);
+        },
+        afterLocated:  function () {
+            stagePosSpreadObj.loadCurPosData();
+        },
+        afterShow: function () {
+            slSpread.refresh();
+            if (spSpread) spSpread.refresh();
+        },
+    });
     const errorList = $.cs_errorList({
         tabSelector: '#error-list-tab',
         selector: '#error-list',
@@ -630,7 +671,6 @@ $(document).ready(() => {
             if (spSpread) spSpread.refresh();
         },
     });
-
     const checkList = $.ledger_checkList({
         id: 'check-list',
         tabSelector: '#check-list-tab',
@@ -1201,9 +1241,11 @@ $(document).ready(() => {
         });
     }
     stageTreeSpreadObj.loadExprToInput(slSpread.getActiveSheet());
+    let addTagShare = true;
     $.contextMenu({
         selector: '#stage-ledger',
         build: function ($trigger, e) {
+            addTagShare = true;
             const target = SpreadJsObj.safeRightClickSelection($trigger, e, slSpread);
             return target.hitTestType === spreadNS.SheetArea.viewport || target.hitTestType === spreadNS.SheetArea.rowHeader;
         },
@@ -1240,6 +1282,82 @@ $(document).ready(() => {
                     }
                 },
             },
+            tag: {
+                name: '书签',
+                items: {
+                    tagShare: {
+                        name: '参与人可见',
+                        type: 'checkbox',
+                        selected: true,
+                        events: {
+                            change: function () {
+                                addTagShare = this.checked;
+                            }
+                        }
+                    },
+                    tagSpr: '--------------',
+                    tagPrimary: {
+                        icon: 'fa-tag text-primary mt-2 mb-2',
+                        name: '靛青',
+                        callback: function (key, opt, menu, e) {
+                            const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+                            postData(window.location.pathname + '/tag', {add: { color: '#007bff', lid: node.id, share: addTagShare }}, function (data) {
+                                if (data.add) data.add.node = node;
+                                billsTag.updateDatasAndShow(data);
+                                SpreadJsObj.repaintNodesRowHeader(slSpread.getActiveSheet(), node);
+                            });
+                        },
+                    },
+                    tagSuccess: {
+                        icon: 'fa-tag text-success mt-2 mb-2',
+                        name: '果绿',
+                        callback: function (key, opt) {
+                            const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+                            postData(window.location.pathname + '/tag', {add: { color: '#28a745', lid: node.id, share: addTagShare }}, function (data) {
+                                if (data.add) data.add.node = node;
+                                billsTag.updateDatasAndShow(data);
+                                SpreadJsObj.repaintNodesRowHeader(slSpread.getActiveSheet(), node);
+                            });
+                        },
+                    },
+                    tagDanger: {
+                        icon: 'fa-tag text-danger mt-2 mb-2',
+                        name: '朱砂',
+                        callback: function (key, opt) {
+                            const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+                            postData(window.location.pathname + '/tag', {add: { color: '#dc3545', lid: node.id, share: addTagShare }}, function (data) {
+                                if (data.add) data.add.node = node;
+                                billsTag.updateDatasAndShow(data);
+                                SpreadJsObj.repaintNodesRowHeader(slSpread.getActiveSheet(), node);
+                            });
+                        },
+                    },
+                    tagWarning: {
+                        icon: 'fa-tag text-warning mt-2 mb-2',
+                        name: '姜黄',
+                        callback: function (key, opt) {
+                            const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+                            postData(window.location.pathname + '/tag', {add: { color: '#da9500', lid: node.id, share: addTagShare }}, function (data) {
+                                if (data.add) data.add.node = node;
+                                billsTag.updateDatasAndShow(data);
+                                SpreadJsObj.repaintNodesRowHeader(slSpread.getActiveSheet(), node);
+                            });
+                        },
+                    },
+                    tagInfo: {
+                        icon: 'fa-tag text-info mt-2 mb-2',
+                        name: '天蓝',
+                        callback: function (key, opt) {
+                            const node = SpreadJsObj.getSelectObject(slSpread.getActiveSheet());
+                            postData(window.location.pathname + '/tag', {add: { color: '#17a2b8', lid: node.id, share: addTagShare }}, function (data) {
+                                if (data.add) data.add.node = node;
+                                billsTag.updateDatasAndShow(data);
+                                SpreadJsObj.repaintNodesRowHeader(slSpread.getActiveSheet(), node);
+                            });
+                        },
+                    }
+                },
+            },
         }
     });
 
@@ -1656,7 +1774,7 @@ $(document).ready(() => {
     });
 
     // 加载计量单元数据 - 暂时统一加载,如有需要,切换成动态加载并缓存
-    postData(window.location.pathname + '/load', { filter: 'ledger;pos;detail;change' }, function (result) {
+    postData(window.location.pathname + '/load', { filter: 'ledger;pos;detail;change;tag' }, function (result) {
         // 加载树结构
         stageTree.loadDatas(result.ledgerData);
         // stageTree.loadCurStageData(curStageData);
@@ -1672,6 +1790,11 @@ $(document).ready(() => {
         // 加载中间计量
         stageIm.init(stage, imType, tenderInfo.decimal);
         stageIm.loadData(result.ledgerData, result.posData, result.detailData, result.changeData);
+
+        for (const t of result.tags) {
+            t.node = stageTree.datas.find(x => {return x.id === t.lid});
+        }
+        billsTag.loadDatas(result.tags);
         errorList.loadHisErrorData();
         checkList.loadHisCheckData();
     }, null, true);
@@ -3046,6 +3169,7 @@ $(document).ready(() => {
                             if (changeBills.gcl_id) {
                                 const node = stageTree.nodes.find(x => {return x.id === changeBills.gcl_id});
                                 SpreadJsObj.locateTreeNode(slSpread.getActiveSheet(), node.ledger_id);
+                                stagePosSpreadObj.loadCurPosData();
                             } else {
                                 const cb = {
                                     b_code: changeBills.code || '',
@@ -3057,10 +3181,10 @@ $(document).ready(() => {
                                     if (node.children && node.children.length > 0) continue;
 
                                     const b = {
-                                        b_code: bills.b_code || '',
-                                        name: bills.name || '',
-                                        unit: bills.unit || '',
-                                        unit_price: bills.unit_price || 0,
+                                        b_code: node.b_code || '',
+                                        name: node.name || '',
+                                        unit: node.unit || '',
+                                        unit_price: node.unit_price || 0,
                                     };
                                     if (_.isMatch(cb, b)) {
                                         SpreadJsObj.locateTreeNode(slSpread.getActiveSheet(), node.ledger_id);

+ 1 - 1
app/router.js

@@ -438,5 +438,5 @@ module.exports = app => {
 
     // 书签
     app.post('/tender/:id/ledger/tag', sessionAuth, tenderCheck, uncheckTenderCheck, 'tenderController.billsTag');
-    app.post('/tender/:id/ledger/measure/stage/:order/tag', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'tenderController.billsTag');
+    app.post('/tender/:id/measure/stage/:order/tag', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'tenderController.billsTag');
 };

+ 12 - 9
app/service/ledger_tag.js

@@ -31,11 +31,14 @@ module.exports = app => {
          * @returns {Promise<void>}
          */
         async getDatas(tid, sid = -1) {
-            return await this.db.select(this.tableName, {
-                where: {tid: tid, sid: -1},
-                columns: ['id', 'uid', 'lid', 'share', 'color', 'comment'],
-                orders: [['create_time', 'desc']],
-            });
+            // return await this.db.select(this.tableName, {
+            //     where: {tid: tid, sid: -1},
+            //     columns: ['id', 'uid', 'lid', 'share', 'color', 'comment'],
+            //     orders: [['create_time', 'desc']],
+            // });
+            const sql = 'SELECT id, uid, lid, share, color, comment FROM ' + this.tableName +
+                '  WHERE tid = ? and sid = ? and (uid = ? or share) ORDER BY create_time DESC';
+            return await this.db.query(sql, [tid, sid, this.ctx.session.sessionUser.accountId]);
         }
 
         /**
@@ -68,7 +71,7 @@ module.exports = app => {
         }
 
         async _delTag(id) {
-            const tag = this.getDataById(id);
+            const tag = await this.getDataById(id);
             if (tag.uid !== this.ctx.session.sessionUser.accountId) throw '您无权删除该数据';
 
             await this.deleteById(id);
@@ -76,13 +79,13 @@ module.exports = app => {
         }
 
         async _updateTag(data) {
-            const tag = this.getDataById(data.id);
-            if (tag.uid !== this.ctx.session.sessionUser.accountId) throw '您无权删除该数据';
+            const tag = await this.getDataById(data.id);
+            if (tag.uid !== this.ctx.session.sessionUser.accountId) throw '您无权修改该数据';
 
             this._filterInvalidField(data);
             data.modify_time = new Date();
             data.id = tag.id;
-            const result = await this.db.udpate(this.tableName, data);
+            const result = await this.db.update(this.tableName, data);
             if (result.affectedRows === 1) return data;
         }
 

+ 0 - 1
app/view/ledger/explode.ejs

@@ -151,7 +151,6 @@
                         </div>
                     </div>
                     <div id="bills-tag" class="tab-pane">
-
                     </div>
                     <div id="error-list" class="tab-pane">
                     </div>

+ 5 - 0
app/view/stage/index.ejs

@@ -534,6 +534,8 @@
                         <div class="sjs-bottom" id="ccb-spread">
                         </div>
                     </div>
+                    <div id="bills-tag" class="tab-pane">
+                    </div>
                     <div id="error-list" class="tab-pane">
                     </div>
                     <div id="check-list" class="tab-pane">
@@ -560,6 +562,9 @@
                     <a class="nav-link" content="#checked-change" href="javascript: void(0);">变更令</a>
                 </li>
                 <li class="nav-item">
+                    <a class="nav-link" content="#bills-tag" href="javascript: void(0);">书签</a>
+                </li>
+                <li class="nav-item">
                     <a class="nav-link" content="#check-list" id="check-list-tab" href="javascript: void(0);" style="display: none;">检查错误</a>
                 </li>
                 <li class="nav-item">