Explorar o código

添加简易的导出Excel方法

MaiXinRong %!s(int64=5) %!d(string=hai) anos
pai
achega
9f098d1d2d

+ 20 - 1
app/controller/deal_bills_controller.js

@@ -105,10 +105,29 @@ module.exports = app => {
                     let fileName;
                     if (file === '签约清单导入格式.xls') {
                         fileName = this.app.baseDir + '/app/public/deal_bills/template.xls';
-                    } else {
+                    } else if (file === '签约清单.xlsx') {
                         const create_time = Date.parse(new Date()) / 1000;
                         fileName = this.app.baseDir + '/app/public/deal_bills/downloads/' + ctx.tender.id + '-' + create_time + '.xlsx';
+                        await this.ctx.helper.recursiveMkdirSync(this.app.baseDir + '/app/public/deal_bills/downloads');
                         // todo 导出签约清单Excel
+                        const setting = {
+                            header: ['清单编号', '名称', '单位', '数量', '单价', '金额'],
+                            width: [80, 150, 60, 80, 80, 80],
+                            hAlign: ['left', 'left', 'center', 'right', 'right', 'right'],
+                        };
+                        const dealBills = await ctx.service.dealBills.getAllDataByCondition({ where: { tender_id: ctx.tender.id } });
+                        const data = [];
+                        for (const db of dealBills) {
+                            data.push([db.code, db.name, db.unit, db.quantity, db.unit_price, db.total_price]);
+                        }
+                        const arraySheetData = this.ctx.helper.simpleXlsxSheetData(setting, data);
+                        console.log(arraySheetData);
+                        xlsx.writeFile({
+                            SheetNames: ['Sheet1'],
+                            Sheets: {
+                                'Sheet1': arraySheetData,
+                            },
+                        }, fileName);
                     }
                     ctx.body = await fs.readFileSync(fileName);
                 } catch (err) {

+ 35 - 4
app/extend/helper.js

@@ -490,11 +490,13 @@ module.exports = {
      * @returns {Promise<void>}
      */
     async recursiveMkdirSync(pathName) {
-        const upperPath = path.dirname(pathName);
-        if (!fs.existsSync(upperPath)) {
-            await this.recursiveMkdirSync(upperPath);
+        if (!fs.existsSync(pathName)) {
+            const upperPath = path.dirname(pathName);
+            if (!fs.existsSync(upperPath)) {
+                await this.recursiveMkdirSync(upperPath);
+            }
+            await fs.mkdirSync(pathName);
         }
-        await fs.mkdirSync(pathName);
     },
 
     /**
@@ -803,4 +805,33 @@ module.exports = {
             sms.send(mobiles, content);
         }
     },
+
+    /**
+     *
+     * @param setting
+     * @param data
+     * @returns {{} & any & {"!ref": string} & {"!cols"}}
+     */
+    simpleXlsxSheetData (setting, data) {
+        const headerStyle = {
+            font: {bold: true},
+            alignment: {horizontal: 'center'},
+        };
+        const sHeader = setting.header
+            .map((v, i) => Object.assign({}, {v: v, s: headerStyle, position: String.fromCharCode(65+i) + 1 }))
+            .reduce((prev, next) => Object.assign({}, prev, {[next.position]: {v: next.v, s: next.s}}), {});
+        const sData = data
+            .map((v, i) => v.map((k, j) => Object.assign({}, {
+                v: k,
+                s: {alignment: {horizontal: setting.hAlign[j]}},
+                position: String.fromCharCode(65+j) + (i+2) })))
+            .reduce((prev, next) => prev.concat(next))
+            .reduce((prev, next) => Object.assign({}, prev, {[next.position]: {v: next.v, s: next.s}}), {});
+        const output = Object.assign({}, sHeader, sData);
+        const outputPos = Object.keys(output);
+        const result = Object.assign({}, output,
+            {'!ref': outputPos[0] + ':' + outputPos[outputPos.length - 1]},
+            {'!cols': setting.width.map((w) => Object.assign({}, {wpx: w}))});
+        return result;
+    }
 };

+ 182 - 0
app/public/js/file-saver/FileSaver.js

@@ -0,0 +1,182 @@
+/* FileSaver.js
+ * A saveAs() FileSaver implementation.
+ * 1.3.8
+ * 2018-03-22 14:03:47
+ *
+ * By Eli Grey, https://eligrey.com
+ * License: MIT
+ *   See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
+ */
+
+/*global self */
+/*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */
+
+/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/src/FileSaver.js */
+
+var saveAs = saveAs || (function(view) {
+	"use strict";
+	// IE <10 is explicitly unsupported
+	if (typeof view === "undefined" || typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) {
+		return;
+	}
+	var
+		  doc = view.document
+		  // only get URL when necessary in case Blob.js hasn't overridden it yet
+		, get_URL = function() {
+			return view.URL || view.webkitURL || view;
+		}
+		, save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a")
+		, can_use_save_link = "download" in save_link
+		, click = function(node) {
+			var event = new MouseEvent("click");
+			node.dispatchEvent(event);
+		}
+		, is_safari = /constructor/i.test(view.HTMLElement) || view.safari
+		, is_chrome_ios =/CriOS\/[\d]+/.test(navigator.userAgent)
+		, setImmediate = view.setImmediate || view.setTimeout
+		, throw_outside = function(ex) {
+			setImmediate(function() {
+				throw ex;
+			}, 0);
+		}
+		, force_saveable_type = "application/octet-stream"
+		// the Blob API is fundamentally broken as there is no "downloadfinished" event to subscribe to
+		, arbitrary_revoke_timeout = 1000 * 40 // in ms
+		, revoke = function(file) {
+			var revoker = function() {
+				if (typeof file === "string") { // file is an object URL
+					get_URL().revokeObjectURL(file);
+				} else { // file is a File
+					file.remove();
+				}
+			};
+			setTimeout(revoker, arbitrary_revoke_timeout);
+		}
+		, dispatch = function(filesaver, event_types, event) {
+			event_types = [].concat(event_types);
+			var i = event_types.length;
+			while (i--) {
+				var listener = filesaver["on" + event_types[i]];
+				if (typeof listener === "function") {
+					try {
+						listener.call(filesaver, event || filesaver);
+					} catch (ex) {
+						throw_outside(ex);
+					}
+				}
+			}
+		}
+		, auto_bom = function(blob) {
+			// prepend BOM for UTF-8 XML and text/* types (including HTML)
+			// note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF
+			if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
+				return new Blob([String.fromCharCode(0xFEFF), blob], {type: blob.type});
+			}
+			return blob;
+		}
+		, FileSaver = function(blob, name, no_auto_bom) {
+			if (!no_auto_bom) {
+				blob = auto_bom(blob);
+			}
+			// First try a.download, then web filesystem, then object URLs
+			var
+				  filesaver = this
+				, type = blob.type
+				, force = type === force_saveable_type
+				, object_url
+				, dispatch_all = function() {
+					dispatch(filesaver, "writestart progress write writeend".split(" "));
+				}
+				// on any filesys errors revert to saving with object URLs
+				, fs_error = function() {
+					if ((is_chrome_ios || (force && is_safari)) && view.FileReader) {
+						// Safari doesn't allow downloading of blob urls
+						var reader = new FileReader();
+						reader.onloadend = function() {
+							var url = is_chrome_ios ? reader.result : reader.result.replace(/^data:[^;]*;/, 'data:attachment/file;');
+							var popup = view.open(url, '_blank');
+							if(!popup) view.location.href = url;
+							url=undefined; // release reference before dispatching
+							filesaver.readyState = filesaver.DONE;
+							dispatch_all();
+						};
+						reader.readAsDataURL(blob);
+						filesaver.readyState = filesaver.INIT;
+						return;
+					}
+					// don't create more object URLs than needed
+					if (!object_url) {
+						object_url = get_URL().createObjectURL(blob);
+					}
+					if (force) {
+						view.location.href = object_url;
+					} else {
+						var opened = view.open(object_url, "_blank");
+						if (!opened) {
+							// Apple does not allow window.open, see https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/WorkingwithWindowsandTabs/WorkingwithWindowsandTabs.html
+							view.location.href = object_url;
+						}
+					}
+					filesaver.readyState = filesaver.DONE;
+					dispatch_all();
+					revoke(object_url);
+				}
+			;
+			filesaver.readyState = filesaver.INIT;
+
+			if (can_use_save_link) {
+				object_url = get_URL().createObjectURL(blob);
+				setImmediate(function() {
+					save_link.href = object_url;
+					save_link.download = name;
+					click(save_link);
+					dispatch_all();
+					revoke(object_url);
+					filesaver.readyState = filesaver.DONE;
+				}, 0);
+				return;
+			}
+
+			fs_error();
+		}
+		, FS_proto = FileSaver.prototype
+		, saveAs = function(blob, name, no_auto_bom) {
+			return new FileSaver(blob, name || blob.name || "download", no_auto_bom);
+		}
+	;
+
+	// IE 10+ (native saveAs)
+	if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) {
+		return function(blob, name, no_auto_bom) {
+			name = name || blob.name || "download";
+
+			if (!no_auto_bom) {
+				blob = auto_bom(blob);
+			}
+			return navigator.msSaveOrOpenBlob(blob, name);
+		};
+	}
+
+	// todo: detect chrome extensions & packaged apps
+	//save_link.target = "_blank";
+
+	FS_proto.abort = function(){};
+	FS_proto.readyState = FS_proto.INIT = 0;
+	FS_proto.WRITING = 1;
+	FS_proto.DONE = 2;
+
+	FS_proto.error =
+	FS_proto.onwritestart =
+	FS_proto.onprogress =
+	FS_proto.onwrite =
+	FS_proto.onabort =
+	FS_proto.onerror =
+	FS_proto.onwriteend =
+		null;
+
+	return saveAs;
+}(
+	   typeof self !== "undefined" && self
+	|| typeof window !== "undefined" && window
+	|| this
+));

+ 15 - 2
app/public/js/ledger.js

@@ -1064,14 +1064,27 @@ $(document).ready(function() {
                 },
                 'importExcel': {
                     name: '导入分项清单Excel',
-                    icon: 'file-excel-o',
+                    icon: 'fa-file-excel-o',
                     disabled: function (key, opt) {
                         return readOnly;
                     },
                     callback: function (key, opt) {
                         $('#upload-ledger').modal('show');
                     }
-                }
+                },
+                // 'exportExcel': {
+                //     name: '导出表格数据',
+                //     icon: 'fa-file-excel-o',
+                //     callback: function (key, opt) {
+                //         const excelIo = new GC.Spread.Excel.IO();
+                //         const date = new Date();
+                //         const fileName = $('.text-truncate').attr('data-original-title') + '.xlsx';
+                //         const sJson = JSON.stringify(ledgerSpread.toJSON());
+                //         excelIo.save(sJson, function(blob) {
+                //             saveAs(blob, fileName);
+                //         });
+                //     }
+                // }
             }
         });
     } else {

+ 4 - 1
app/view/ledger/audit.ejs

@@ -67,7 +67,10 @@
                 <div class="resize-x" id="right-spr" r-Type="width" div1="#left-view" div2="#right-view" title="调整大小" a-type="percent"><!--调整左右高度条--></div>
                 <div class="tab-content">
                     <div id="deal-bills" class="tab-pane">
-                        <div class="sjs-bar-2"></div>
+                        <div class="sjs-bar-2">
+                            签约清单
+                            <a href="/tender/<%- ctx.tender.id %>/deal/download/签约清单.xlsx" class="btn btn-sm btn-primary" style="display: none">下载签约清单</a>
+                        </div>
                         <div class="sjs-sh-2" id="deal-bills-spread">
                         </div>
                     </div>

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

@@ -128,6 +128,7 @@
                         <div class="sjs-bar-4">
                             <div class="pb-1">
                                 <a href="#upload-deal" data-toggle="modal" data-target="#upload-deal" class="btn btn-sm btn-primary">上传签约清单</a>
+                                <a href="/tender/<%- ctx.tender.id %>/deal/download/签约清单.xlsx" class="btn btn-sm btn-primary" style="display: none">下载签约清单</a>
                             </div>
                         </div>
                         <div id="deal-bills-spread" class="sjs-sh-4">

+ 2 - 2
app/view/measure/stage_modal.ejs

@@ -14,11 +14,11 @@
                 </div>
                 <div class="form-group">
                     <label>计量年月</label>
-                    <input class="datepicker-here form-control form-control-sm" placeholder="点击选择年月" data-view="months" data-min-view="months" data-date-format="yyyy-MM" data-language="zh" type="text" name="date" autocomplete="off">
+                    <input class="datepicker-here form-control form-control-sm" autocomplete="off" placeholder="点击选择年月" data-view="months" data-min-view="months" data-date-format="yyyy-MM" data-language="zh" type="text" name="date" autocomplete="off">
                 </div>
                 <div class="form-group">
                     <label>开始-截止日期</label>
-                    <input class="datepicker-here form-control form-control-sm" placeholder="点击选择时间" data-range="true" data-multiple-dates-separator=" ~ " data-language="zh" type="text" name="period" autocomplete="off">
+                    <input class="datepicker-here form-control form-control-sm" autocomplete="off" placeholder="点击选择时间" data-range="true" data-multiple-dates-separator=" ~ " data-language="zh" type="text" name="period" autocomplete="off">
                 </div>
             </div>
             <div class="modal-footer">

+ 4 - 0
app/view/revise/info.ejs

@@ -154,6 +154,10 @@
                         </div>
                     </div>
                     <div id="deal-bills" class="tab-pane">
+                        <div class="sjs-bar-5">
+                            签约清单
+                            <a href="/tender/<%- ctx.tender.id %>/deal/download/签约清单.xlsx" class="btn btn-sm btn-primary" style="display: none">下载签约清单</a>
+                        </div>
                         <div id="deal-bills-spread" class="sjs-sh-5">
                         </div>
                     </div>

+ 2 - 0
config/web.js

@@ -123,8 +123,10 @@ const JsFiles = {
                     "/public/js/js-xlsx/xlsx.full.min.js",
                     "/public/js/js-xlsx/xlsx.utils.js",
                     "/public/js/spreadjs/sheets/gc.spread.sheets.all.10.0.1.min.js",
+                    "/public/js/spreadjs/sheets/interop/gc.spread.excelio.10.0.1.min.js",
                     "/public/js/decimal.min.js",
                     "/public/js/math.min.js",
+                    "/public/js/file-saver/FileSaver.js",
                 ],
                 mergeFiles: [
                     "/public/js/sub_menu.js",