Bläddra i källkod

地图录入功能 ok

laiguoran 3 år sedan
förälder
incheckning
cccc280993

+ 3 - 0
app/controller/tender_controller.js

@@ -1247,6 +1247,9 @@ module.exports = app => {
                     case 'del-map':
                         await ctx.service.tenderMap.deleteById(data.id);
                         break;
+                    case 'save-map':
+                        await ctx.service.tenderMap.saveMap(data.mapData);
+                        break;
                     default:break;
                 }
                 ctx.body = { err: 0, msg: '', data: info };

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 88 - 0
app/public/js/map/turf.min.js


+ 8 - 1
app/service/tender_map.js

@@ -32,7 +32,14 @@ module.exports = app => {
             return await this.db.insert(this.tableName, data);
         }
 
-        async saveMap(id, updateData) {
+        async saveMap(mapData) {
+            const id = mapData.id;
+            const updateData = {
+                name: mapData.name,
+                color: mapData.color,
+                map_json: mapData.map_json,
+                center: mapData.center,
+            };
             return await this.db.update(this.tableName, updateData, { where: { id } });
         }
     }

+ 319 - 27
app/view/tender/detail_modal.ejs

@@ -2025,7 +2025,6 @@
             </div>
             <div class="modal-footer">
                 <button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</button>
-                <!--<button type="button" class="btn btn-sm btn-primary" >确认修改</button>-->
                 <button type="button" class="btn btn-sm btn-success" >刷新显示</button>
             </div>
         </div>
@@ -2038,26 +2037,15 @@
                 <h5 class="modal-title">地图路线</h5>
             </div>
             <div class="modal-body">
-                <input class="form-control form-control-sm mb-1" type="text" value="路线1" placeholder="请输入路线名称">
-                <input class="form-control form-control-sm mb-1" type="text" id="color-select" value="#ff0000" placeholder="请选择路线颜色">
-                <!--<table class="table table-bordered table-sm">-->
-                    <!--<tr><th>坐标/备注</th><th>N(X)</th><th>E(Y)</th><th>色值(选填)</th></tr>-->
-                    <!--<tr><td>K0+000</td><td>324234.22</td><td>121212.09</td><td>#ff6501</td></tr>-->
-                    <!--<tr><td>K1+356</td><td>9861253.22</td><td>28234234.09</td><td></td></tr>-->
-                    <!--<tr><td>K2+534</td><td>651278.22</td><td>64122.09</td><td></td></tr>-->
-                    <!--<tr><td>&nbsp;</td><td>&nbsp; </td><td></td><td></td></tr>-->
-                    <!--<tr><td>&nbsp;</td><td>&nbsp; </td><td></td><td></td></tr>-->
-                    <!--<tr><td>&nbsp;</td><td> &nbsp;</td><td></td><td></td></tr>-->
-                    <!--<tr><td>&nbsp;</td><td> &nbsp;</td><td></td><td></td></tr>-->
-                <!--</table>-->
+                <input class="form-control form-control-sm mb-1" type="text" id="map-name" value="" placeholder="请输入路线名称">
+                <input class="form-control form-control-sm mb-1" type="text" id="map-color" value="" placeholder="请选择路线颜色">
                 <div class="modal-height-300" style="overflow-y: auto">
                     <div id="map-spread" style="height: 297px; width: 465px;"></div>
                 </div>
             </div>
             <div class="modal-footer">
-                <a href="#bd-set-11-1" class="btn btn-sm btn-secondary" data-toggle="modal" data-target="#bd-set-11-1" data-dismiss="modal">关闭</a>
-                <!--<a href="#bd-set-11" class="btn btn-sm btn-primary" data-toggle="modal" data-target="#bd-set-11-1" data-dismiss="modal">确认修改</a>-->
-                <button type="button" class="btn btn-sm btn-primary" id="save-map" data-mid="">确认修改</button>
+                <button class="btn btn-sm btn-secondary save-map" data-close="1" data-mid="">关闭</button>
+                <button type="button" class="btn btn-sm btn-primary save-map" data-close="0" data-mid="">确认修改</button>
             </div>
         </div>
     </div>
@@ -2074,12 +2062,30 @@
             </div>
             <div class="modal-footer">
                 <a href="#bd-set-11-1" class="btn btn-sm btn-secondary" data-toggle="modal" data-target="#bd-set-11-1" data-dismiss="modal">关闭</a>
-                <!--<button type="button" class="btn btn-sm btn-primary" >确认修改</button>-->
                 <button type="button" class="btn btn-sm btn-danger" id="del-map" data-mid="">确认删除</button>
             </div>
         </div>
     </div>
 </div>
+<!--关闭退出编辑-->
+<div class="modal fade" id="bd-set-11-4" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">关闭路线编辑</h5>
+            </div>
+            <div class="modal-body">
+                当前路线数据发生过修改,是否保存修改数据并退出?
+            </div>
+            <div class="modal-footer">
+                <a href="javascript:void(0)" class="btn btn-sm btn-secondary" data-dismiss="modal">关闭</a>
+                <button id="back-set-11-1" class="btn btn-sm btn-danger">直接退出</button>
+                <button type="button" class="btn btn-sm btn-primary save-map" data-colse="0" data-mid="">保存并退出</button>
+            </div>
+        </div>
+    </div>
+</div>
+<script src="/public/js/map/turf.min.js"></script>
 <script>
     const hadMap = parseInt(<%- hadMap %>);
     const tenderMapList = JSON.parse(unescape('<%- escape(JSON.stringify(tenderMapList)) %>'));
@@ -2108,7 +2114,7 @@
                 toastr.warning('未更改当前设置,无需提交');
             }
         })
-        $('#color-select').colorpicker();
+        $('#map-color').colorpicker();
         $('#add-map').click(function () {
             const num = $('#map-table tr').length;
             const name = '路线' + (num+1);
@@ -2130,13 +2136,14 @@
                 $('#bd-set-11-1').modal('show');
             });
         });
+        const mapSpread = SpreadJsObj.createNewSpread($('#map-spread')[0]);
         const mapSpreadSetting = {
             cols: [
-                {title: '坐标/备注', colSpan: '1', rowSpan: '1', field: 'b_code', hAlign: 0, width: 100, formatter: '@'},
-                {title: '经度', colSpan: '1', rowSpan: '1', field: 'x', hAlign: 2, width: 120, type: 'Number'},
-                {title: '纬度', colSpan: '1', rowSpan: '1', field: 'y', hAlign: 2, width: 120, type: 'Number'},
+                {title: '坐标/备注', colSpan: '1', rowSpan: '1', field: 'tip', hAlign: 0, width: 100, formatter: '@'},
+                {title: '经度', colSpan: '1', rowSpan: '1', field: 'lng', hAlign: 2, width: 120, type: 'number'},
+                {title: '纬度', colSpan: '1', rowSpan: '1', field: 'lat', hAlign: 2, width: 120, type: '@'},
             ],
-            emptyRows: 20,
+            emptyRows: 5,
             headRows: 1,
             headRowHeight: [25, 25],
             defaultRowHeight: 21,
@@ -2144,20 +2151,305 @@
             font: '12px 微软雅黑',
             readOnly: false,
         };
-        const mapSpread = SpreadJsObj.createNewSpread($('#map-spread')[0]);
         SpreadJsObj.initSheet(mapSpread.getActiveSheet(), mapSpreadSetting);
         $('body').on('click', '#bd-set-11-1 .save-map-btn', function () {
             const id = parseInt($(this).data('mid'));
-            $('#save-map').attr('data-mid', id);
+            $('.save-map').attr('data-mid', id);
             // 加载sjs
             const mapInfo = _.find(tenderMapList, { id });
             console.log(mapInfo);
-            SpreadJsObj.loadSheetData(mapSpread.getActiveSheet(), SpreadJsObj.DataType.Data, (mapInfo.map_json ? mapInfo.map_json : []));
+            // mapInfo.map_json = [{ tip: 'K100~101', lng: 123.45, lat: 23.45}];
+            $('#map-name').val(mapInfo.name);
+            $('#map-color').val(mapInfo.color);
+            SpreadJsObj.loadSheetData(mapSpread.getActiveSheet(), SpreadJsObj.DataType.Data, (mapInfo.map_json ? JSON.parse(unescape(escape(mapInfo.map_json))) : []));
         });
-        $('#save-map').on('click', function () {
+        const xPattern = /^-?(\d{1,2}(\.\d{1,8})?|1[0-7]\d(\.\d{1,8})?|180)$/;
+        const yPattern = /^-?(\d(\.\d{1,8})?|[1-8]\d(\.\d{1,8})?|90)$/;
+        const mapSpreadObj = {
+            del: function (row) {
+                const select = SpreadJsObj.getSelectObject(mapSpread.getActiveSheet());
+                const delSel = mapSpread.getActiveSheet().getSelections()[0];
+                mapSpread.getActiveSheet().deleteRows(delSel.row, delSel.rowCount);
+                const sel = mapSpread.getActiveSheet().getSelections();
+                mapSpread.getActiveSheet().setSelection(0, 0, 1, 1);
+            },
+            batchAdd: function(num, site = mapSpread.getActiveSheet().getRowCount()) {
+                mapSpread.getActiveSheet().addRows(site, parseInt(num));
+            },
+            editEnding: function (e, info) {
+                if (info.sheet.zh_setting) {
+                    const curRow = info.row;
+                    info.sheet.zh_data[curRow] = {
+                        tip: info.sheet.getText(curRow, 0),
+                        x: info.sheet.getText(curRow, 1) !== '' ? _.toNumber(info.sheet.getText(curRow, 1)) : '',
+                        y: info.sheet.getText(curRow, 2) !== '' ? _.toNumber(info.sheet.getText(curRow, 2)) : '',
+                    }
+                }
+            },
+            editEnded: function (e, info) {
+                if (info.sheet.zh_setting) {
+                    const select = SpreadJsObj.getSelectObject(info.sheet);
+                    const col = info.sheet.zh_setting.cols[info.col];
+                    const validText = info.editingText ? info.editingText.replace('\n', '') : '';
+                    if (validText === '') {
+                        return;
+                    }
+                    if (col.field === 'lng' && !xPattern.test(validText)) {
+                        toastr.error('请输入正常范围内的经度(-180 ~ 180)');
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                    if (col.field === 'lat' && !yPattern.test(validText)) {
+                        toastr.error('请输入正常范围内的纬度(-90 ~ 90)');
+                        SpreadJsObj.reLoadRowData(info.sheet, info.row);
+                        return;
+                    }
+                }
+            },
+            clipboardPasting(e, info) {
+                const range = info.cellRange;
+                for (let iRow = 0; iRow < range.rowCount; iRow++) {
+                    const curRow = range.row + iRow;
+                    info.sheet.zh_data[curRow] = {
+                        tip: info.sheet.getText(curRow, 0),
+                        x: info.sheet.getText(curRow, 1) !== '' ? _.toNumber(info.sheet.getText(curRow, 1)) : '',
+                        y: info.sheet.getText(curRow, 2) !== '' ? _.toNumber(info.sheet.getText(curRow, 2)) : '',
+                    }
+                }
+            },
+            clipboardPasted(e, info) {
+                const hint = {
+                    cellError: {type: 'error', msg: '粘贴内容超出了表格范围'},
+                    xExpr: {type: 'error', msg: '请粘贴正常范围内的经度(-180 ~ 180)'},
+                    yExpr: {type: 'error', msg: '请粘贴正常范围内的纬度(-90 ~ 90)'},
+                };
+                const range = info.cellRange;
+                console.log(range);
+                if (range.col + range.colCount > 3) {
+                    toastMessageUniq(hint.cellError);
+                    SpreadJsObj.reLoadSheetHeader(mapSpread.getActiveSheet());
+                    // SpreadJsObj.reLoadSheetData(mapSpread.getActiveSheet());
+                    // continue;
+                }
+                for (let iRow = 0; iRow < range.rowCount; iRow++) {
+                    let bPaste = true;
+                    const curRow = range.row + iRow;
+                    for (let iCol = 0; iCol < range.colCount; iCol++) {
+                        const curCol = range.col + iCol;
+                        const colSetting = info.sheet.zh_setting.cols[curCol];
+                        const validText = info.sheet.getText(curRow, curCol);
+                        if (validText === '') {
+                            continue;
+                        }
+                        if (colSetting.field === 'lng' && !xPattern.test(validText)) {
+                            toastMessageUniq(hint.xExpr);
+                            bPaste = false;
+                            continue;
+                        }
+                        if (colSetting.field === 'lat' && !yPattern.test(validText)) {
+                            toastMessageUniq(hint.yExpr);
+                            bPaste = false;
+                            continue;
+                        }
+                    }
+                    if (!bPaste) {
+                        SpreadJsObj.reLoadRowData(info.sheet, curRow);
+                    }
+                }
+            },
+        }
+        mapSpread.bind(spreadNS.Events.EditEnding, mapSpreadObj.editEnding);
+        mapSpread.bind(spreadNS.Events.EditEnded, mapSpreadObj.editEnded);
+        mapSpread.bind(spreadNS.Events.ClipboardPasting, mapSpreadObj.clipboardPasting);
+        mapSpread.bind(spreadNS.Events.ClipboardPasted, mapSpreadObj.clipboardPasted);
+        let batchInsertObj;
+        $.contextMenu.types.batchInsert = function (item, opt, root) {
+            const self = this;
+            if ($.isFunction(item.icon)) {
+                item._icon = item.icon.call(this, this, $t, key, item);
+            } else {
+                if (typeof(item.icon) === 'string' && item.icon.substring(0, 3) === 'fa-') {
+                    // to enable font awesome
+                    item._icon = root.classNames.icon + ' ' + root.classNames.icon + '--fa fa ' + item.icon;
+                } else {
+                    item._icon = root.classNames.icon + ' ' + root.classNames.icon + '-' + item.icon;
+                }
+            }
+            this.addClass(item._icon);
+            const $obj = $('<div>' + item.name + '<input class="text-right ml-1 mr-1" type="tel" max="100" min="1" value="' + item.value + '" style="width: 30px; height: 18px; padding-right: 4px;">行</div>')
+                .appendTo(this);
+            const $input = $obj.find('input');
+            const event = () => {
+                if (self.hasClass('context-menu-disabled')) return;
+                item.batchInsert($input[0], root);
+            };
+            $obj.on('click', event).keypress(function (e) {if (e.keyCode === 13) { event(); }});
+            $input.click((e) => {e.stopPropagation();})
+                .keyup((e) => {if (e.keyCode === 13) item.batchInsert($input[0], root);})
+                .on('input', function () {this.value = this.value.replace(/[^\d]/g, '');});
+        };
+        // 右键菜单
+        $.contextMenu({
+            selector: '#map-spread',
+            build: function ($trigger, e) {
+                const target = SpreadJsObj.safeRightClickSelection($trigger, e, mapSpread);
+                return target.hitTestType === GC.Spread.Sheets.SheetArea.viewport || target.hitTestType === GC.Spread.Sheets.SheetArea.rowHeader;
+            },
+            items: {
+                'batchInsert': {
+                    name: '批量新增',
+                    type: 'batchInsert',
+                    value: '1',
+                    icon: 'fa-sign-in',
+                    batchInsert: function (obj, root) {
+                        if (_.toNumber(obj.value) > _.toNumber(obj.max)) {
+                            obj.value = obj.max;
+                            toastr.warning('批量添加不可多于' + obj.max);
+                        } else if(_.toNumber(obj.value) < _.toNumber(obj.min)) {
+                            obj.value = obj.min;
+                            toastr.warning('批量添加不可少于' + obj.min);
+                        } else {
+                            mapSpreadObj.batchAdd(obj.value);
+                            root.$menu.trigger('contextmenu:hide');
+                        }
+                    },
+                },
+                'batchInsert2': {
+                    name: '批量插入',
+                    type: 'batchInsert',
+                    value: '1',
+                    icon: 'fa-sign-in',
+                    batchInsert: function (obj, root) {
+                        if (_.toNumber(obj.value) > _.toNumber(obj.max)) {
+                            obj.value = obj.max;
+                            toastr.warning('批量插入不可多于' + obj.max);
+                        } else if(_.toNumber(obj.value) < _.toNumber(obj.min)) {
+                            obj.value = obj.min;
+                            toastr.warning('批量插入不可少于' + obj.min);
+                        } else {
+                            const select = SpreadJsObj.getSelectObject(mapSpread.getActiveSheet());
+                            const sel = mapSpread.getActiveSheet().getSelections()[0];
+                            mapSpreadObj.batchAdd(obj.value, sel.row+1);
+                            root.$menu.trigger('contextmenu:hide');
+                        }
+                    },
+                    disabled: function (key, opt) {
+                        const select = SpreadJsObj.getSelectObject(mapSpread.getActiveSheet());
+                        const sel = mapSpread.getActiveSheet().getSelections()[0];
+                        if (sel.row !== undefined && sel.rowCount === 1) {
+                            return false;
+                        } else {
+                            return true;
+                        }
+                    }
+                },
+                'delete': {
+                    name: '删除',
+                    icon: 'fa-remove',
+                    callback: function (key, opt) {
+                        mapSpreadObj.del();
+                    },
+                    disabled: function (key, opt) {
+                        const select = SpreadJsObj.getSelectObject(mapSpread.getActiveSheet());
+                        const sel = mapSpread.getActiveSheet().getSelections()[0];
+                        if (sel.row !== undefined) {
+                            return false;
+                        } else {
+                            return true;
+                        }
+                    }
+                },
+            }
+        });
+
+        $('.save-map').on('click', function () {
             const id = parseInt($(this).attr('data-mid'));
-            console.log(id);
+            const is_close = parseInt($(this).attr('data-close'));
+            // 获取sjs值
+            const mapData = [];
+            const sheet = mapSpread.getActiveSheet();
+            let pass = true;
+            let wrongRow = [];
+            for (let i = 0; i < sheet.getRowCount(); i++) {
+                const rowData = {
+                    tip: sheet.getText(i, 0),
+                    lng: sheet.getText(i, 1),
+                    lat: sheet.getText(i, 2),
+                }
+                if ((rowData.tip !== '' && rowData.lng === '' && rowData.lat === '') ||
+                    (rowData.lng !== '' && rowData.lat === '') ||
+                    (rowData.lng === '' && rowData.lat !== '')) {
+                    pass = false;
+                    wrongRow.push(i+1);
+                } else if (rowData.lng !== '' && rowData.lat !== '') {
+                    rowData.lng = _.toNumber(rowData.lng);
+                    rowData.lat = _.toNumber(rowData.lat);
+                    mapData.push(rowData);
+                }
+            }
+            if (!is_close) {
+                const colorPattern = /(^#[0-9a-fA-F]{6}$)|(^#[0-9a-fA-F]{3}$)/g;
+                if (!colorPattern.test($('#map-color').val())) {
+                    $('#bd-set-11-4').modal('hide');
+                    toastr.error('请选择或输入正确的颜色编码');
+                    return;
+                }
+            }
+            if (!pass) {
+                const str = wrongRow.join('、');
+                // $('#bd-set-11-4').modal('hide');
+                toastr.error('第 ' + str + ' 行请填写完整的经纬度数据或清空该行或删除该行');
+                return;
+            }
+            // 获取路线绝对中心点
+            let centerPoints = { lng: '', lat: '' };
+            if (mapData.length > 0) {
+                const points = [];
+                for (const m of mapData) {
+                    points.push(turf.point([m.lng, m.lat]));
+                }
+                const features = turf.featureCollection(points);
+                const center = turf.center(features);
+                if (center && center.geometry && center.geometry.coordinates && center.geometry.coordinates.length === 2) {
+                    centerPoints.lng = _.round(center.geometry.coordinates[0], 4);
+                    centerPoints.lat = _.round(center.geometry.coordinates[1], 4);
+                }
+            }
+            const oneMapData = {
+                id,
+                name: $('#map-name').val(),
+                color: $('#map-color').val(),
+                map_json: mapData.length > 0 ? JSON.stringify(mapData) : null,
+                center: centerPoints.lng !== '' ? JSON.stringify(centerPoints) : null,
+            }
+            const mapInfo = _.find(tenderMapList, { id });
+            oneMapData.create_time = mapInfo.create_time;
+            oneMapData.tid = mapInfo.tid;
+            // 比较对象是否一致,一致则不提交,不一致则提交,关闭提示是否保存修改
+            if (!_.isEqual(oneMapData, mapInfo)) {
+                console.log(oneMapData, mapInfo);
+                if (!is_close) {
+                    postData('/tender/' + tenderId + '/map/save', { type: 'save-map', mapData: oneMapData }, function (result) {
+                        const index = _.findIndex(tenderMapList, { id });
+                        tenderMapList.splice(index, 1, oneMapData);
+                        $('#map-table tr').eq(index).children('td').eq(0).text(oneMapData.name);
+                        $('#bd-set-11-4').modal('hide');
+                        $('#bd-set-11-2').modal('hide');
+                        $('#bd-set-11-1').modal('show');
+                    });
+                } else {
+                    $('#bd-set-11-4').modal('show');
+                }
+            } else {
+                $('#bd-set-11-2').modal('hide');
+                $('#bd-set-11-1').modal('show');
+            }
         });
+        $('#back-set-11-1').on('click', function () {
+            $('#bd-set-11-4').modal('hide');
+            $('#bd-set-11-2').modal('hide');
+            $('#bd-set-11-1').modal('show');
+        })
     })
 </script>
 <% } %>