浏览代码

feat: 计量台账-附件下载优化

lanjianrong 4 年之前
父节点
当前提交
6382aec064
共有 7 个文件被更改,包括 857 次插入702 次删除
  1. 36 0
      app/controller/stage_controller.js
  2. 10 0
      app/public/css/main.css
  3. 57 4
      app/public/js/stage.js
  4. 1 0
      app/router.js
  5. 144 95
      app/service/stage_att.js
  6. 608 603
      app/view/stage/index.ejs
  7. 1 0
      package.json

+ 36 - 0
app/controller/stage_controller.js

@@ -1519,6 +1519,42 @@ module.exports = app => {
         }
 
         /**
+         * 批量下载 - 压缩成zip文件返回
+         * @param {Object} ctx - 全局上下文
+         */
+        async downloadZip(ctx) {
+            const fileIds = JSON.parse(ctx.request.query.fileIds);
+            const zipFilename = `${ctx.tender.data.name}-计量台账-${ctx.params.order}-附件.zip`;
+            const zipPath = `app/public/upload/${ctx.tender.id}/stage/${zipFilename}`;
+            const size = await ctx.service.stageAtt.compressedFile(fileIds, zipPath);
+
+            // 解决中文无法下载问题
+            const userAgent = (ctx.request.header['user-agent'] || '').toLowerCase();
+            let disposition = '';
+            if (userAgent.indexOf('msie') >= 0 || userAgent.indexOf('chrome') >= 0) {
+                disposition = 'attachment; filename=' + encodeURIComponent(zipFilename);
+            } else if (userAgent.indexOf('firefox') >= 0) {
+                disposition = 'attachment; filename*="utf8\'\'' + encodeURIComponent(zipFilename) + '"';
+            } else {
+                /* safari等其他非主流浏览器只能自求多福了 */
+                disposition = 'attachment; filename=' + new Buffer(zipFilename).toString('binary');
+            }
+            ctx.response.set({
+                'Content-Type': 'application/octet-stream',
+                'Content-Disposition': disposition,
+                'Content-Length': size,
+            });
+            // const readStream = fs.readFileSync(path.resolve(this.app.baseDir, zipPath));
+            // ctx.body = fs.readFileSync(path.resolve(this.app.baseDir, zipPath));
+            ctx.body = fs.readFileSync(path.resolve(this.app.baseDir, zipPath));
+            // readStream.on('close', () => {
+
+            // });
+            if (fs.existsSync(path.resolve(this.app.baseDir, zipPath))) {
+                fs.unlinkSync(path.resolve(this.app.baseDir, zipPath));
+            }
+        }
+        /**
          * 合同支付上传附件
          * @param {Object} ctx - egg全局变量
          * @return {void}

+ 10 - 0
app/public/css/main.css

@@ -1146,3 +1146,13 @@ overflow-y: auto;
 .fold-card {
   display: none;
 }
+.att-file-btn {
+  display: none;
+}
+
+#alllist-table tr:hover .att-file-btn{
+  display: block;
+}
+#nodelist-table tr:hover .att-file-btn{
+  display: block;
+}

+ 57 - 4
app/public/js/stage.js

@@ -113,8 +113,9 @@ function getAllList(currPageNum = 1) {
     // 当前页附件内容
     const currPageAttData = attData.slice((currPageNum-1)*pageCount, currPageNum*pageCount);
     let html = '';
+    // '/tender/' + tender.id + '/measure/stage/' + stage.order + '/download/file/' + att.id
     for(const att of currPageAttData) {
-        html += '<tr ><td><a href="javascript:void(0)" file-id="'+ att.id +'">'+ att.filename + att.fileext +'</a></td><td>'+ att.username +'</td></tr>';
+        html += `<tr><td width="25"><input type="checkbox" class="check-file" file-id=${att.id}></td><td><a href="javascript:void(0)" file-id=${att.id}>${att.filename}${att.fileext}</a></td><td width="50"><a href="/tender/${tender.id}/measure/stage/${stage.order}/download/file/${att.id}" class="att-file-btn">下载</a></td><td>${att.username}</td></td>`
     }
     $('#alllist-table').html(html);
     $('#alllist-table').on('click', 'tr', function() {
@@ -127,7 +128,8 @@ function getNodeList(node) {
     let html = '';
     for(const att of attData) {
         if (node === att.lid) {
-            html += '<tr><td><a href="javascript:void(0)" file-id="'+ att.id +'">'+ att.filename + att.fileext +'</a></td><td>'+ att.username +'</td></tr>';
+            // html += '<tr><td><a href="javascript:void(0)" file-id="'+ att.id +'">'+ att.filename + att.fileext +'</a></td><td>'+ att.username +'</td></tr>';
+            html += `<tr><td width="25"><input type="checkbox" class="check-file" file-id=${att.id}></td><td><a href="javascript:void(0)" file-id=${att.id}>${att.filename}${att.fileext}</a></td><td width="50"><a href="/tender/${tender.id}/measure/stage/${stage.order}/download/file/${att.id}" class="att-file-btn">下载</a></td><td>${att.username}</td></td>`
         }
     }
     $('#nodelist-table').html(html);
@@ -3161,7 +3163,7 @@ $(document).ready(() => {
             $('#show-att tr').eq(0).children('td').text(att.filename + att.fileext);
             const name = att.code !== null && att.code !== '' ? att.code : (att.b_code !== null ? att.b_code : '');
             $('#show-att tr').eq(1).children('td').text($.trim(name + ' ' + att.lname));
-            $('#show-att tr').eq(2).find('a').attr('href', '/tender/' + tender.id + '/measure/stage/' + stage.order + '/download/file/' + att.id);
+            // $('#show-att tr').eq(2).find('a').attr('href', '/tender/' + tender.id + '/measure/stage/' + stage.order + '/download/file/' + att.id);
             // $('#show-att tr').eq(2).find('a').attr('href', att.filepath);
             $('#show-att tr').eq(3).children('td').eq(0).text(att.username);
             $('#show-att tr').eq(3).children('td').eq(1).text(att.in_time);
@@ -3251,7 +3253,7 @@ $(document).ready(() => {
                 $('#show-att tr').eq(0).children('td').text(data.filename + data.fileext);
                 const name = data.code !== null && data.code !== '' ? data.code : (data.b_code !== null ? data.b_code : '');
                 $('#show-att tr').eq(1).children('td').text($.trim(name + ' ' + data.lname));
-                $('#show-att tr').eq(2).find('a').attr('href', '/tender/' + tender.id + '/measure/stage/' + stage.order + '/download/file/' + data.id);
+                // $('#show-att tr').eq(2).find('a').attr('href', '/tender/' + tender.id + '/measure/stage/' + stage.order + '/download/file/' + data.id);
                 $('#show-att tr').eq(3).children('td').eq(0).text(data.username);
                 $('#show-att tr').eq(3).children('td').eq(1).text(data.in_time);
                 $('#show-att tr').eq(4).children('td').text(data.remark);
@@ -3335,6 +3337,57 @@ $(document).ready(() => {
             $('#showAttachment').hide();
         }
     });
+
+    // 批量下载
+    $('#bach-download').click(function() {
+        const fileIds = []
+        $('.tab-pane.active .list-table .check-file:checked').each(function() {
+            const fileId = $(this).attr('file-id')
+            fileId && fileIds.push(fileId)
+        })
+        console.log('fileIds', fileIds)
+
+        if (fileIds.length) {
+            const url = `/tender/${tender.id}/measure/stage/${stage.order}/download/compresse-file?fileIds=${JSON.stringify(fileIds)}`
+            $('#zipDown').attr('href', url)
+            $("#zipDown")[0].click()
+            // 执行请求
+            // postData('/tender/' + tender.id + '/measure/stage/' + stage.order + '/download/compresse-file', { fileIds }, function(data) {
+            //     console.log(data)
+            // })
+            // $.ajax({
+            //     type:"get",
+            //     // url: '/tender/' + tender.id + '/measure/stage/' + stage.order + '/download/compresse-file',
+            //     url: `/tender/${tender.id}/measure/stage/${stage.order}/download/compresse-file?fileids=${JSON.stringify(fileids)}`,
+            //     dataType: 'json',
+            //     cache: false,
+            //     // 告诉jQuery不要去设置Content-Type请求头
+            //     contentType: false,
+            //     // 告诉jQuery不要去处理发送的数据
+            //     processData: false,
+            //     timeout: 60000,
+            // })
+        }
+    })
+
+    // 监听附件check是否选中
+    $('.list-table').on('click', '.check-file', function() {
+        const checkedList = $(this).parents('.list-table').children().find('input:checked')
+        const childs = $(this).parents('.list-table').children().length
+        const checkBox = $(this).parents('.list-table').parent().find('.check-all-file')
+        if (checkedList.length === childs) {
+            checkBox.prop("checked", true)
+        } else {
+            checkBox.prop("checked", false)
+        }
+    })
+    $('.check-all-file').click(function() {
+        const isCheck = $(this).is(':checked')
+        $(this).parents('table').find('.list-table').each(function() {
+            $(this).find('input:checkbox').prop("checked", isCheck)
+        })
+    })
+
     // 显示层次
     (function (select, sheet) {
         $(select).click(function () {

+ 1 - 0
app/router.js

@@ -217,6 +217,7 @@ module.exports = app => {
     app.post('/tender/:id/measure/stage/:order/delete/file', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.deleteFile');
     app.post('/tender/:id/measure/stage/:order/save/file', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.saveFile');
     app.post('/tender/:id/measure/stage/:order/check/file', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.checkFile');
+    app.get('/tender/:id/measure/stage/:order/download/compresse-file', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.downloadZip');
 
     // 中间计量
     app.get('/tender/:id/measure/stage/:order/detail', sessionAuth, tenderCheck, uncheckTenderCheck, stageCheck, 'stageController.detail');

+ 144 - 95
app/service/stage_att.js

@@ -1,95 +1,144 @@
-'use strict';
-
-/**
- *
- *  附件
- * @author Ellisran
- * @date 2019/1/11
- * @version
- */
-
-module.exports = app => {
-    class StageAtt extends app.BaseService {
-        /**
-         * 构造函数
-         *
-         * @param {Object} ctx - egg全局变量
-         * @return {void}
-         */
-        constructor(ctx) {
-            super(ctx);
-            this.tableName = 'stage_attachment';
-        }
-
-        /**
-         * 添加附件
-         * @param {Object} postData - 表单信息
-         * @param {Object} fileData - 文件信息
-         * @param {int} uid - 上传者id
-         * @return {void}
-         */
-        async save(postData, fileData, uid) {
-            const data = {
-                lid: postData.lid,
-                uid,
-                remark: '',
-            };
-            Object.assign(data, fileData);
-            const result = await this.db.insert(this.tableName, data);
-            return result;
-        }
-
-        /**
-         * 添加附件
-         * @param {Object} postData - 表单信息
-         * @param {Object} fileData - 文件信息
-         * @param {int} uid - 上传者id
-         * @return {void}
-         */
-        async updateByID(postData, fileData) {
-            delete postData.size;
-            const data = {};
-            Object.assign(data, fileData);
-            Object.assign(data, postData);
-            const result = await this.db.update(this.tableName, data);
-            return result.affectedRows === 1;
-        }
-
-        /**
-         * 获取所有附件
-         * @param {int} tid - 标段id
-         * @param {int} sid - 当前期数
-         * @return {void}
-         */
-        async getDataByTenderIdAndStageId(tid, sid) {
-            const { ctx } = this;
-            const sql = 'SELECT att.id, att.lid, att.uid, att.filename, att.fileext, att.filesize, att.re_upload, att.remark, att.in_time,' +
-                ' pa.name as `username`, leg.name as `lname`, leg.code as `code`, leg.ledger_id as `ledger_id`, leg.b_code as `b_code`' +
-                ' FROM ?? AS att,?? AS pa,?? AS leg' +
-                ' WHERE leg.id = att.lid AND pa.id = att.uid AND att.tid = ? AND att.sid = ? ORDER BY att.id DESC';
-            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.ctx.service.ledger.tableName, tid, sid];
-            return await this.db.query(sql, sqlParam);
-        }
-
-        /**
-         * 获取单个附件
-         * @param {int} tid - 标段id
-         * @param {int} sid - 当前期数
-         * @return {void}
-         */
-        async getDataByFid(id) {
-            const { ctx } = this;
-            const sql = 'SELECT att.id, att.lid, att.uid, att.filepath, att.filename, att.re_upload, att.fileext, att.filesize, att.remark, att.in_time,' +
-                ' pa.name as `username`, leg.name as `lname`, leg.code as `code`, leg.ledger_id as `ledger_id`,leg.b_code as `b_code`' +
-                ' FROM ?? AS att,?? AS pa,?? AS leg' +
-                ' WHERE leg.id = att.lid AND pa.id = att.uid AND att.id = ? ORDER BY att.in_time DESC';
-            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.ctx.service.ledger.tableName, id];
-            const result = await this.db.queryOne(sql, sqlParam);
-            if (!ctx.helper.canPreview(result.fileext)) result.filepath = `/tender/${ctx.tender.id}/measure/stage/${ctx.params.order}/download/file/${result.id}`;
-            else result.filepath = result.filepath.replace(/^app|\/app/, '');
-            return result;
-        }
-    }
-
-    return StageAtt;
-};
+'use strict';
+
+/**
+ *
+ *  附件
+ * @author Ellisran
+ * @date 2019/1/11
+ * @version
+ */
+
+const archiver = require('archiver');
+const path = require('path');
+const fs = require('fs');
+module.exports = app => {
+    class StageAtt extends app.BaseService {
+        /**
+         * 构造函数
+         *
+         * @param {Object} ctx - egg全局变量
+         * @return {void}
+         */
+        constructor(ctx) {
+            super(ctx);
+            this.tableName = 'stage_attachment';
+        }
+
+        /**
+         * 添加附件
+         * @param {Object} postData - 表单信息
+         * @param {Object} fileData - 文件信息
+         * @param {int} uid - 上传者id
+         * @return {void}
+         */
+        async save(postData, fileData, uid) {
+            const data = {
+                lid: postData.lid,
+                uid,
+                remark: '',
+            };
+            Object.assign(data, fileData);
+            const result = await this.db.insert(this.tableName, data);
+            return result;
+        }
+
+        /**
+         * 添加附件
+         * @param {Object} postData - 表单信息
+         * @param {Object} fileData - 文件信息
+         * @param {int} uid - 上传者id
+         * @return {void}
+         */
+        async updateByID(postData, fileData) {
+            delete postData.size;
+            const data = {};
+            Object.assign(data, fileData);
+            Object.assign(data, postData);
+            const result = await this.db.update(this.tableName, data);
+            return result.affectedRows === 1;
+        }
+
+        /**
+         * 获取所有附件
+         * @param {int} tid - 标段id
+         * @param {int} sid - 当前期数
+         * @return {void}
+         */
+        async getDataByTenderIdAndStageId(tid, sid) {
+            const { ctx } = this;
+            const sql = 'SELECT att.id, att.lid, att.uid, att.filename, att.fileext, att.filesize, att.re_upload, att.remark, att.in_time,' +
+                ' pa.name as `username`, leg.name as `lname`, leg.code as `code`, leg.ledger_id as `ledger_id`, leg.b_code as `b_code`' +
+                ' FROM ?? AS att,?? AS pa,?? AS leg' +
+                ' WHERE leg.id = att.lid AND pa.id = att.uid AND att.tid = ? AND att.sid = ? ORDER BY att.id DESC';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.ctx.service.ledger.tableName, tid, sid];
+            return await this.db.query(sql, sqlParam);
+        }
+
+        /**
+         * 获取单个附件
+         * @param {int} tid - 标段id
+         * @param {int} sid - 当前期数
+         * @return {void}
+         */
+        async getDataByFid(id) {
+            const { ctx } = this;
+            const sql = 'SELECT att.id, att.lid, att.uid, att.filepath, att.filename, att.re_upload, att.fileext, att.filesize, att.remark, att.in_time,' +
+                ' pa.name as `username`, leg.name as `lname`, leg.code as `code`, leg.ledger_id as `ledger_id`,leg.b_code as `b_code`' +
+                ' FROM ?? AS att,?? AS pa,?? AS leg' +
+                ' WHERE leg.id = att.lid AND pa.id = att.uid AND att.id = ? ORDER BY att.in_time DESC';
+            const sqlParam = [this.tableName, this.ctx.service.projectAccount.tableName, this.ctx.service.ledger.tableName, id];
+            const result = await this.db.queryOne(sql, sqlParam);
+            if (!ctx.helper.canPreview(result.fileext)) result.filepath = `/tender/${ctx.tender.id}/measure/stage/${ctx.params.order}/download/file/${result.id}`;
+            else result.filepath = result.filepath.replace(/^app|\/app/, '');
+            return result;
+        }
+
+        /**
+         * 将文件压缩成zip,并返回zip文件的路径
+         * @param {array} fileIds - 文件数组id
+         * @param {string} zipPath - 压缩文件存储路径
+         * @return {string} 压缩后的zip文件路径
+         */
+        async compressedFile(fileIds, zipPath) {
+            this.initSqlBuilder();
+            this.sqlBuilder.setAndWhere('id', {
+                value: fileIds,
+                operate: 'in',
+            });
+            const [sql, sqlParam] = this.sqlBuilder.build(this.tableName);
+            const files = await this.db.query(sql, sqlParam);
+            // const paths = files.map(item => {
+            //     return { name: item.filename + item.fileext, path: item.filepath }
+            // })
+            return new Promise(resolve => {
+                // 每次开一个新的archiver
+                const ziparchiver = archiver('zip');
+                const outputPath = fs.createWriteStream(path.resolve(this.app.baseDir, zipPath));
+                ziparchiver.pipe(outputPath);
+                files.forEach(item => {
+                    ziparchiver.file(item.filepath, { name: item.filename + item.fileext });
+                });
+
+                // 存档警告
+                ziparchiver.on('warning', function(err) {
+                    if (err.code === 'ENOENT') {
+                        console.warn('stat故障和其他非阻塞错误');
+                    } else {
+                        throw err;
+                    }
+                });
+
+                // 存档出错
+                ziparchiver.on('error', function(err) {
+                    console.log(err);
+                    throw err;
+                });
+                ziparchiver.finalize();
+                outputPath.on('close', () => {
+                    resolve(ziparchiver.pointer());
+                });
+            });
+        }
+    }
+    return StageAtt;
+};

文件差异内容过多而无法显示
+ 608 - 603
app/view/stage/index.ejs


+ 1 - 0
package.json

@@ -6,6 +6,7 @@
   "dependencies": {
     "@alicloud/pop-core": "^1.7.9",
     "ali-rds": "^3.3.0",
+    "archiver": "^5.0.2",
     "atob": "^2.1.2",
     "bignumber.js": "^8.1.1",
     "decimal.js": "^10.2.0",