Преглед изворни кода

关联台账,中间计量,附件相关

MaiXinRong пре 3 година
родитељ
комит
aeb9a77d15

+ 1 - 1
app/controller/stage_controller.js

@@ -780,7 +780,7 @@ module.exports = app => {
                     }
                     }
                 }
                 }
                 const baseInfo = JSON.parse(parts.field.base);
                 const baseInfo = JSON.parse(parts.field.base);
-                const result = await ctx.service.stageDetailAtt.addFiles(baseInfo, uploadFiles);
+                const result = await ctx.service.stageDetailAtt.addFiles(this.ctx.stage, baseInfo, uploadFiles);
                 ctx.body = { err: 0, mgs: '', data: result };
                 ctx.body = { err: 0, mgs: '', data: result };
             } catch (err) {
             } catch (err) {
                 console.log(err);
                 console.log(err);

+ 102 - 0
app/controller/stage_rela_controller.js

@@ -12,6 +12,8 @@ const auditConst = require('../const/audit').stage;
 const spreadSetting = require('../lib/spread_setting');
 const spreadSetting = require('../lib/spread_setting');
 const measureType = require('../const/tender').measureType;
 const measureType = require('../const/tender').measureType;
 const tenderConst = require('../const/tender');
 const tenderConst = require('../const/tender');
+const sendToWormhole = require('stream-wormhole');
+const path = require('path');
 
 
 module.exports = app => {
 module.exports = app => {
 
 
@@ -154,6 +156,7 @@ module.exports = app => {
         async load(ctx) {
         async load(ctx) {
             try {
             try {
                 const relaStage = await this.ctx.service.stageRela.getDataById(ctx.params.trid);
                 const relaStage = await this.ctx.service.stageRela.getDataById(ctx.params.trid);
+                const stage = await this.ctx.service.stage.getDataById(relaStage.rela_sid);
                 const data = JSON.parse(ctx.request.body.data);
                 const data = JSON.parse(ctx.request.body.data);
                 const filter = data.filter.split(';');
                 const filter = data.filter.split(';');
                 const responseData = { err: 0, msg: '', data: {} };
                 const responseData = { err: 0, msg: '', data: {} };
@@ -167,6 +170,7 @@ module.exports = app => {
                             break;
                             break;
                         case 'detail':
                         case 'detail':
                             responseData.data.detailData = await ctx.service.stageDetail.getLastestStageData(relaStage.rela_tid, relaStage.rela_sid);
                             responseData.data.detailData = await ctx.service.stageDetail.getLastestStageData(relaStage.rela_tid, relaStage.rela_sid);
+                            responseData.data.detailAtt = await this.ctx.service.stageDetailAtt.getStageData(relaStage.rela_sid, stage.im_type);
                             break;
                             break;
                         case 'change':
                         case 'change':
                             responseData.data.changeData = await ctx.service.stageChange.getLastestAllStageData(relaStage.rela_tid, relaStage.rela_sid);
                             responseData.data.changeData = await ctx.service.stageChange.getLastestAllStageData(relaStage.rela_tid, relaStage.rela_sid);
@@ -185,6 +189,104 @@ module.exports = app => {
                 ctx.body = { err: 1, msg: err.toString(), data: null };
                 ctx.body = { err: 1, msg: err.toString(), data: null };
             }
             }
         }
         }
+
+
+        async uploadImFile(ctx) {
+            let stream;
+            try {
+                const relaStage = await this.ctx.service.stageRela.getDataById(ctx.params.trid);
+                const stage = await this.ctx.service.stage.getDataById(relaStage.rela_sid);
+                const parts = ctx.multipart({ autoFields: true });
+                let index = 0;
+                const create_time = Date.parse(new Date()) / 1000;
+                stream = await parts();
+                const uploadFiles = [];
+                while (stream !== undefined) {
+                    if (!stream.filename) throw '未发现上传文件!';
+
+                    const fileInfo = path.parse(stream.filename);
+                    const dirName = path.join('app', 'public', 'upload', relaStage.tid.toString(), 'im');
+                    const fileName = `${ctx.session.sessionUser.accountId}_${create_time}_${index}${fileInfo.ext}`;
+
+                    // 保存文件
+                    await ctx.helper.saveStreamFile(stream, path.join(this.app.baseDir, dirName, fileName));
+                    await sendToWormhole(stream);
+
+                    // 插入到stage_pay对应的附件列表中
+                    uploadFiles.push({
+                        file_id: this.ctx.app.uuid.v4(),
+                        filename: fileInfo.name,
+                        fileext: fileInfo.ext,
+                        filesize: Array.isArray(parts.field.size) ? parts.field.size[index] : parts.field.size,
+                        filepath: path.join(dirName, fileName),
+                        uid: ctx.session.sessionUser.accountId,
+                        in_time: ctx.moment(create_time * 1000).format('YYYY-MM-DD'),
+                        renew: true,
+                    });
+                    ++index;
+                    if (Array.isArray(parts.field.size) && index < parts.field.size.length) {
+                        stream = await parts();
+                    } else {
+                        stream = undefined;
+                    }
+                }
+                const baseInfo = JSON.parse(parts.field.base);
+                const result = await ctx.service.stageDetailAtt.addFiles(stage, baseInfo, uploadFiles);
+                ctx.body = { err: 0, mgs: '', data: result };
+            } catch (err) {
+                console.log(err);
+                // 失败需要消耗掉stream 以防卡死
+                if (stream) await sendToWormhole(stream);
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '上传附件失败');
+            }
+        }
+
+        async deleteImFile(ctx) {
+            try {
+                const data = JSON.parse(ctx.request.body.data);
+                if (!data.uuid || !data.file_id) throw '数据错误';
+
+                const result = await ctx.service.stageDetailAtt.delFiles(data.uuid, data.file_id);
+                ctx.body = { err: 0, msg: '', data: result };
+            } catch (err) {
+                ctx.log(err);
+                ctx.ajaxErrorBody(err, '删除附件失败');
+            }
+        }
+
+        async downloadImFile(ctx) {
+            try {
+                const uuid = ctx.query.im_id;
+                const file_id = ctx.query.file_id;
+                if (!uuid || !file_id) throw '数据错误';
+
+                const fileInfo = await ctx.service.stageDetailAtt.getFiles(uuid, file_id);
+                if (!fileInfo) throw '文件不存在';
+
+                const fileName = path.join(this.app.baseDir, fileInfo.filepath);
+                // 解决中文无法下载问题
+                const userAgent = (ctx.request.header['user-agent'] || '').toLowerCase();
+                let disposition = '';
+                if (userAgent.indexOf('msie') >= 0 || userAgent.indexOf('chrome') >= 0) {
+                    disposition = 'attachment; filename=' + encodeURIComponent(fileInfo.filename + fileInfo.fileext);
+                } else if (userAgent.indexOf('firefox') >= 0) {
+                    disposition = 'attachment; filename*="utf8\'\'' + encodeURIComponent(fileInfo.filename + fileInfo.fileext) + '"';
+                } else {
+                    /* safari等其他非主流浏览器只能自求多福了 */
+                    disposition = 'attachment; filename=' + new Buffer(fileInfo.filename + fileInfo.fileext).toString('binary');
+                }
+                ctx.response.set({
+                    'Content-Type': 'application/octet-stream',
+                    'Content-Disposition': disposition,
+                    'Content-Length': fileInfo.filesize,
+                });
+                ctx.body = await fs.createReadStream(fileName);
+            } catch (err) {
+                ctx.log(err);
+                ctx.postError(err, '下载文件失败');
+            }
+        }
     }
     }
 
 
 
 

+ 1 - 0
app/lib/rpt_data_analysis.js

@@ -662,6 +662,7 @@ const gatherChapter = {
             return (x.visible !== undefined && x.visible !== null) ? x.visible : true;
             return (x.visible !== undefined && x.visible !== null) ? x.visible : true;
 
 
         });
         });
+        console.log(data[fieldsKey[0].table]);
     },
     },
 };
 };
 const join = {
 const join = {

+ 93 - 1
app/public/js/sr_detail.js

@@ -545,7 +545,7 @@ $(document).ready(() => {
         SpreadJsObj.resetTopAndSelect(spSpread.getActiveSheet());
         SpreadJsObj.resetTopAndSelect(spSpread.getActiveSheet());
         // 加载中间计量
         // 加载中间计量
         stageIm.init(relaStage, imType, tenderInfo.decimal);
         stageIm.init(relaStage, imType, tenderInfo.decimal);
-        stageIm.loadData4Rela(result.ledgerData, result.posData, result.detailData, result.changeData);
+        stageIm.loadData4Rela(result.ledgerData, result.posData, result.detailData, result.changeData, result.detailAtt);
     }, null, true);
     }, null, true);
     spSpread.bind(spreadNS.Events.SelectionChanged, stagePosSpreadObj.selectionChanged);
     spSpread.bind(spreadNS.Events.SelectionChanged, stagePosSpreadObj.selectionChanged);
 
 
@@ -754,6 +754,12 @@ $(document).ready(() => {
                         title: relaStage.im_type === imType.tz.value ? '本期计量金额' : '本期计量数量',
                         title: relaStage.im_type === imType.tz.value ? '本期计量金额' : '本期计量数量',
                         colSpan: '1', rowSpan: '1', field: 'jl', hAlign: 2, width: 85, formatter: '@'
                         colSpan: '1', rowSpan: '1', field: 'jl', hAlign: 2, width: 85, formatter: '@'
                     },
                     },
+                    {
+                        title: '附件', colSpan: '1', rowSpan: '1', field: 'attachment', hAlign: 0, width: 60, readOnly: true, cellType: 'imageBtn',
+                        normalImg: '#rela-file-icon', hoverImg: '#rela-file-hover', getValue: function (data) {
+                            return data.attachment ? data.attachment.length : 0;
+                        }
+                    },
                 ],
                 ],
                 headRows: 1,
                 headRows: 1,
                 emptyRows: 0,
                 emptyRows: 0,
@@ -764,6 +770,10 @@ $(document).ready(() => {
                 font: '12px 微软雅黑',
                 font: '12px 微软雅黑',
                 readOnly: true,
                 readOnly: true,
                 selectedBackColor: '#fffacd',
                 selectedBackColor: '#fffacd',
+                imageClick: function (data) {
+                    self.makeAttTable(data);
+                    $('#im-file').modal('show');
+                }
             };
             };
             this.spread = SpreadJsObj.createNewSpread(obj[0]);
             this.spread = SpreadJsObj.createNewSpread(obj[0]);
             this.sheet = this.spread.getActiveSheet();
             this.sheet = this.spread.getActiveSheet();
@@ -782,8 +792,90 @@ $(document).ready(() => {
 
 
             this._initImTypeSetRela();
             this._initImTypeSetRela();
             this._initLocateRela();
             this._initLocateRela();
+            this._initAttRela();
             this.reBuildImData();
             this.reBuildImData();
         }
         }
+        makeAttTable (data) {
+            let html = [];
+            if (data.attachment) {
+                for (const att of data.attachment) {
+                    const delHtml = (parseInt(att.uid) === userID && (att.renew || stage.status !== auditConst.status.checked))
+                        ? '<a class="delete-att text-danger" href="javascript:void(0);" data-imid="'+ data.att_uuid +'" data-attid="'+ att.file_id +'" title="删除"><i class="fa fa-remove "></i></a>'
+                        : '';
+                    html.push('<tr><td style="width: 200px">' + att.filename + att.fileext + '</td><td>' + att.username + '</td><td>' + att.in_time + '</td>',
+                        '<td><a href="'+ window.location.pathname + '/im-file/download?im_id='+ data.att_uuid +'&file_id='+ att.file_id +'" title="下载"><i class="fa fa-download "></i></a> ',
+                        delHtml, '</td></tr>');
+                }
+            }
+            $('#im-attList').html(html.join(''));
+        }
+        _initAttRela() {
+            // 上传附件
+            const self = this;
+            $('#upload-im-file').change(function () {
+                const files = this.files;
+                const sels = self.sheet.getSelections();
+                const select = SpreadJsObj.getSelectObject(self.sheet);
+                if (!select) return;
+
+                const formData = new FormData();
+                const baseInfo = {};
+                if (select.att_uuid) {
+                    baseInfo.uuid = select.att_uuid;
+                } else {
+                    baseInfo.im_type = select.im_type;
+                    baseInfo.lid = select.lid;
+                    baseInfo.pid = select.pid;
+                    baseInfo.code = select.code;
+                    baseInfo.name = select.name;
+                    baseInfo.unit = select.unit;
+                    baseInfo.unit_price = select.unit_price;
+                    baseInfo.pos_name = select.pos_name;
+                }
+                formData.append('base', JSON.stringify(baseInfo));
+
+                for (const file of files) {
+                    if (file === undefined) {
+                        toast('未选择上传文件!', 'error');
+                        return false;
+                    }
+                    const filesize = file.size;
+                    if (filesize > 30 * 1024 * 1024) {
+                        toast('存在上传文件大小过大!', 'error');
+                        return false;
+                    }
+                    const fileext = '.' + file.name.toLowerCase().split('.').splice(-1)[0];
+                    if (whiteList.indexOf(fileext) === -1) {
+                        toast('只能上传指定格式的附件!', 'error');
+                        return false;
+                    }
+                    formData.append('size', filesize);
+                    formData.append('file[]', file);
+                }
+                postDataWithFile(window.location.pathname + '/im-file/upload', formData, function (data) {
+                    stageIm.loadUpdateDetailAtt(data);
+                    SpreadJsObj.reLoadRowData(self.sheet, sels[0].row);
+                    self.makeAttTable(select);
+                    $('#upload-im-file').val('');
+                });
+            });
+
+            // 删除附件
+            $('body').on('click', '.delete-att' ,function () {
+                const sels = self.sheet.getSelections();
+                const select = SpreadJsObj.getSelectObject(self.sheet);
+                if (!select) return;
+
+                const uuid = $(this).attr('data-imid');
+                const file_id = $(this).attr('data-attid');
+
+                postData(window.location.pathname + '/im-file/del', { uuid, file_id }, function (result) {
+                    stageIm.loadUpdateDetailAtt(result);
+                    SpreadJsObj.reLoadRowData(self.sheet, sels[0].row);
+                    self.makeAttTable(select);
+                });
+            });
+        }
         _initImTypeSetRela() {
         _initImTypeSetRela() {
             const self = this;
             const self = this;
             const gatherConfirmPopover = {
             const gatherConfirmPopover = {

+ 1 - 1
app/public/js/stage.js

@@ -2711,7 +2711,7 @@ $(document).ready(() => {
                 }
                 }
             }
             }
             $('#im-attList').html(html.join(''));
             $('#im-attList').html(html.join(''));
-        };
+        }
         _initAttRela() {
         _initAttRela() {
             // 上传附件
             // 上传附件
             const self = this;
             const self = this;

+ 3 - 0
app/router.js

@@ -346,6 +346,9 @@ module.exports = app => {
     app.post('/tender/:id/measure/stage/:order/rela/update', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageRelaController.update');
     app.post('/tender/:id/measure/stage/:order/rela/update', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageRelaController.update');
     app.get('/tender/:id/measure/stage/:order/rela/detail/:trid', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageRelaController.detail');
     app.get('/tender/:id/measure/stage/:order/rela/detail/:trid', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageRelaController.detail');
     app.post('/tender/:id/measure/stage/:order/rela/detail/:trid/load', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageRelaController.load');
     app.post('/tender/:id/measure/stage/:order/rela/detail/:trid/load', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageRelaController.load');
+    app.post('/tender/:id/measure/stage/:order/rela/detail/:trid/im-file/del', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageRelaController.deleteImFile');
+    app.post('/tender/:id/measure/stage/:order/rela/detail/:trid/im-file/upload', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageRelaController.uploadImFile');
+    app.get('/tender/:id/measure/stage/:order/rela/detail/:trid/im-file/download', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageRelaController.downloadImFile');
 
 
     // 期审批管理
     // 期审批管理
     app.get('/tender/:id/measure/stage/:order/manager', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.manager');
     app.get('/tender/:id/measure/stage/:order/manager', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.manager');

+ 6 - 6
app/service/stage_detail_att.js

@@ -33,19 +33,19 @@ module.exports = app => {
                     case ImTypeConst.zl.value:
                     case ImTypeConst.zl.value:
                     case ImTypeConst.bw.value:
                     case ImTypeConst.bw.value:
                         return this.getDataByCondition({
                         return this.getDataByCondition({
-                            im_type: field.im_type, sid: this.ctx.stage.id,
+                            im_type: field.im_type, sid: field.sid,
                             lid: field.lid, code: field.code, name: field.name, unit: field.unit, unit_price: field.unit_price,
                             lid: field.lid, code: field.code, name: field.name, unit: field.unit, unit_price: field.unit_price,
                             pid: field.pid, pos_name: field.pos_name
                             pid: field.pid, pos_name: field.pos_name
                         });
                         });
                     case ImTypeConst.tz.value:
                     case ImTypeConst.tz.value:
                         return this.getDataByCondition({
                         return this.getDataByCondition({
-                            im_type: field.im_type, sid: this.ctx.stage.id,
+                            im_type: field.im_type, sid: field.sid,
                             lid: field.lid, code: field.code, name: field.name, unit: field.unit,
                             lid: field.lid, code: field.code, name: field.name, unit: field.unit,
                             pid: field.pid, pos_name: field.pos_name
                             pid: field.pid, pos_name: field.pos_name
                         });
                         });
                     case ImTypeConst.bb.value:
                     case ImTypeConst.bb.value:
                         return this.getDataByCondition({
                         return this.getDataByCondition({
-                            im_type: field.im_type, sid: this.ctx.stage.id,
+                            im_type: field.im_type, sid: field.sid,
                             lid: field.lid, name: field.name, unit: field.unit,
                             lid: field.lid, name: field.name, unit: field.unit,
                             pid: field.pid, pos_name: field.pos_name
                             pid: field.pid, pos_name: field.pos_name
                         });
                         });
@@ -88,8 +88,8 @@ module.exports = app => {
             return result;
             return result;
         }
         }
 
 
-        async addFiles(field, files) {
-            const detailAtt = await this.getDetailAtt(field);
+        async addFiles(stage, field, files) {
+            const detailAtt = await this.getDetailAtt({ sid: stage.id, ...field });
             if (detailAtt) {
             if (detailAtt) {
                 detailAtt && this._analysisDetailAtt(detailAtt);
                 detailAtt && this._analysisDetailAtt(detailAtt);
                 for (const f of files) {
                 for (const f of files) {
@@ -100,7 +100,7 @@ module.exports = app => {
                 return detailAtt;
                 return detailAtt;
             } else {
             } else {
                 const insertData = {
                 const insertData = {
-                    tid: this.ctx.tender.id, sid: this.ctx.stage.id, sorder: this.ctx.stage.order,
+                    tid: stage.tid, sid: stage.id, sorder: stage.order,
                     uuid: this.uuid.v4(), im_type: field.im_type,
                     uuid: this.uuid.v4(), im_type: field.im_type,
                     lid: field.lid, code: field.code, name: field.name, unit: field.unit, unit_price: field.unit_price,
                     lid: field.lid, code: field.code, name: field.name, unit: field.unit, unit_price: field.unit_price,
                     pid: field.pid, pos_name: field.pos_name,
                     pid: field.pid, pos_name: field.pos_name,

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

@@ -485,10 +485,6 @@
         <img src="/public/images/file_clip.png" id="rela-file-icon">
         <img src="/public/images/file_clip.png" id="rela-file-icon">
         <img src="/public/images/file_clip_hover.png" id="rela-file-hover">
         <img src="/public/images/file_clip_hover.png" id="rela-file-hover">
     </div>
     </div>
-    <div style="display: none">
-        <img src="/public/images/file_clip.png" id="rela-file-icon" />
-        <img src="/public/images/file_clip_hover.png" id="rela-file-hover" />
-    </div>
 </div>
 </div>
 <script src="/public/js/moment/moment.min.js"></script>
 <script src="/public/js/moment/moment.min.js"></script>
 <script>
 <script>

+ 30 - 0
app/view/stage_rela/detail_modal.ejs

@@ -18,4 +18,34 @@
             </div>
             </div>
         </div>
         </div>
     </div>
     </div>
+</div>
+<!--中间计量 附件-->
+<div class="modal fade" id="im-file" data-backdrop="static">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title">附件</h5>
+            </div>
+            <div class="modal-body">
+                <div class="form-group">
+                    <% if (!ctx.tender.isTourist || (ctx.tender.isTourist && ctx.tender.touristPermission.file) || stage.filePermission) { %>
+                    <label for="formGroupExampleInput">大小限制:30MB,支持<span data-toggle="tooltip" data-placement="bottom" title="doc,docx,xls,xlsx,ppt,pptx,pdf">office等文档格式</span>、<span data-toggle="tooltip" data-placement="bottom" title="jpg,png,bmp">图片格式</span>、<span data-toggle="tooltip" data-placement="bottom" title="rar,zip">压缩包格式</span></label>
+                    <input type="file" class="" id="upload-im-file" multiple>
+                    <% } %>
+                </div>
+                <div class="modal-height-500" style="overflow:auto;">
+                    <table class="table table-sm table-bordered" style="word-break:break-all; table-layout: fixed">
+                        <thead>
+                        <tr><th width="240">文件名</th><th>上传人</th><th>上传时间</th><th width="40">操作</th></tr>
+                        </thead>
+                        <tbody id="im-attList">
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">关闭</button>
+            </div>
+        </div>
+    </div>
 </div>
 </div>