vue实现通用导出(excel,csv, pdf文件)组件 前端导出

后台管理项目中经常使用文件导入导出,故封装了一个通用table的导出组件的实现

思路 使用 Dropdown 控件选择导出类型 触发导出
 
导出的table 数据类型
 1 tableColumns: [
 2         {
 3           title: '序号',
 4           key: 'Ordinal',
 5           align: 'center'
 6         },
 7         {
 8           title: '产品编号',
 9           key: 'ProductNo',
10           align: 'left'
11         }
12 ]
13 tableData: [{Ordinal:1,ProductNo:'1234',ProductDesc:'1232222'}]

 

导出文件大部分情况下是后端处理,有时候我们只需要js处理 该怎么做呢?
1.导出CSV
    首先实现导出CSV格式 
    拼接 csv格式其实就是个纯文本文件,中间使用逗号或者换行符进行拼接
    这里使用 json2cvs这个包 需要npm 安装 npm install json2csv  -s
    下载方式 
        IE浏览器 不支持a标签进行下载,会打开url 故
        对于微软系浏览器(IE和Edge)和非微软系列浏览器采用两种不同的方式进行下载
        IE和Edge 采用了  navigator.msSaveBlob 方法 此方法为IE10及以上特有,IE10以下勿采用
        非微软浏览器 使用a标签的click事件进行下载
 关键代码
 1 try {
 2         const result = json2csv.parse(rows, {
 3           fields: fields,
 4           excelStrings: true
 5         });
 6         if (this.MyBrowserIsIE()) {
 7           // IE10以及Edge浏览器
 8           var BOM = "\uFEFF";
 9                   // 文件转Blob格式
10           var csvData = new Blob([BOM + result], { type: "text/csv" });
11           navigator.msSaveBlob(csvData, `${fileName}.csv`);
12         } else {
13           let csvContent = "data:text/csv;charset=utf-8,\uFEFF" + result;
14           // 非ie 浏览器
15           this.createDownLoadClick(csvContent, `${fileName}.csv`);
16         }
17       } catch (err) {
18         alert(err);
19       }
1   //创建a标签下载
2     createDownLoadClick(content, fileName) {
3       const link = document.createElement("a");
4       link.href = encodeURI(content);
5       link.download = fileName;
6       document.body.appendChild(link);
7       link.click();
8       document.body.removeChild(link);
9     },
 1 // 判断是否IE浏览器
 2     MyBrowserIsIE() {
 3       let isIE = false;
 4       if (
 5         navigator.userAgent.indexOf("compatible") > -1 &&
 6         navigator.userAgent.indexOf("MSIE") > -1
 7       ) {
 8         // ie浏览器
 9         isIE = true;
10       }
11       if (navigator.userAgent.indexOf("Trident") > -1) {
12         // edge 浏览器
13         isIE = true;
14       }
15       return isIE;
16     },
2.导出Excel类型文件
 
  导出excel借鉴了iview-admin 自带的excel操作js(需要npm安装 xlsx)npm install xlsx -s
需要导出的地方调用excel.export_array_to_excel函数即可
1 const param = {
2 title: titles,
3 key: keys,
4 data: this.exportData,
5 autoWidth: true,
6 filename: this.exportFileName
7 };
8 excel.export_array_to_excel(param);

完整excel操作js代码如下 excel.js

  1 /* eslint-disable */
  2 import XLSX from 'xlsx';
  3 
  4 function auto_width(ws, data) {
  5   /*set worksheet max width per col*/
  6   const colWidth = data.map(row => row.map(val => {
  7     /*if null/undefined*/
  8     if (val == null) {
  9       return {
 10         'wch': 10
 11       };
 12     }
 13     /*if chinese*/
 14     else if (val.toString().charCodeAt(0) > 255) {
 15       return {
 16         'wch': val.toString().length * 2
 17       };
 18     } else {
 19       return {
 20         'wch': val.toString().length
 21       };
 22     }
 23   }))
 24   /*start in the first row*/
 25   let result = colWidth[0];
 26   for (let i = 1; i < colWidth.length; i++) {
 27     for (let j = 0; j < colWidth[i].length; j++) {
 28       if (result[j]['wch'] < colWidth[i][j]['wch']) {
 29         result[j]['wch'] = colWidth[i][j]['wch'];
 30       }
 31     }
 32   }
 33   ws['!cols'] = result;
 34 }
 35 
 36 function json_to_array(key, jsonData) {
 37   return jsonData.map(v => key.map(j => {
 38     return v[j]
 39   }));
 40 }
 41 
 42 // fix data,return string
 43 function fixdata(data) {
 44   let o = ''
 45   let l = 0
 46   const w = 10240
 47   for (; l < data.byteLength / w; ++l) o += String.fromCharCode.apply(null, new Uint8Array(data.slice(l * w, l * w + w)))
 48   o += String.fromCharCode.apply(null, new Uint8Array(data.slice(l * w)))
 49   return o
 50 }
 51 
 52 // get head from excel file,return array
 53 function get_header_row(sheet) {
 54   const headers = []
 55   const range = XLSX.utils.decode_range(sheet['!ref'])
 56   let C
 57   const R = range.s.r /* start in the first row */
 58   for (C = range.s.c; C <= range.e.c; ++C) { /* walk every column in the range */
 59     var cell = sheet[XLSX.utils.encode_cell({
 60       c: C,
 61       r: R
 62     })] /* find the cell in the first row */
 63     var hdr = 'UNKNOWN ' + C // <-- replace with your desired default
 64     if (cell && cell.t) hdr = XLSX.utils.format_cell(cell)
 65     headers.push(hdr)
 66   }
 67   return headers
 68 }
 69 
 70 export const export_table_to_excel = (id, filename) => {
 71   const table = document.getElementById(id);
 72   const wb = XLSX.utils.table_to_book(table);
 73   XLSX.writeFile(wb, filename);
 74 
 75   /* the second way */
 76   // const table = document.getElementById(id);
 77   // const wb = XLSX.utils.book_new();
 78   // const ws = XLSX.utils.table_to_sheet(table);
 79   // XLSX.utils.book_append_sheet(wb, ws, filename);
 80   // XLSX.writeFile(wb, filename);
 81 }
 82 
 83 export const export_json_to_excel = ({
 84   data,
 85   key,
 86   title,
 87   filename,
 88   autoWidth
 89 }) => {
 90   const wb = XLSX.utils.book_new();
 91   data.unshift(title);
 92   const ws = XLSX.utils.json_to_sheet(data, {
 93     header: key,
 94     skipHeader: true
 95   });
 96   if (autoWidth) {
 97     const arr = json_to_array(key, data);
 98     auto_width(ws, arr);
 99   }
100   XLSX.utils.book_append_sheet(wb, ws, filename);
101   XLSX.writeFile(wb, filename + '.xlsx');
102 }
103 
104 // 导出不带有汉字标题的execel内容
105 export const export_array_to_excel = ({
106   key,
107   data,
108   title,
109   filename,
110   autoWidth
111 }) => {
112   const wb = XLSX.utils.book_new();
113   const arr = json_to_array(key, data);
114   arr.unshift(title)
115   const ws = XLSX.utils.aoa_to_sheet(arr);
116   if (autoWidth) {
117     auto_width(ws, arr);
118   }
119   XLSX.utils.book_append_sheet(wb, ws, filename);
120   XLSX.writeFile(wb, filename + '.xlsx');
121 }
122 
123 // 导出带有汉字标题的execel内容
124 export const export_array_to_excel2 = ({
125   key,
126   data,
127   title,
128   filename,
129   autoWidth
130 }) => {
131   const wb = XLSX.utils.book_new();
132   const arr = json_to_array(key, data);
133   arr.unshift(key)
134   arr.unshift(title)
135   const ws = XLSX.utils.aoa_to_sheet(arr);
136   if (autoWidth) {
137     auto_width(ws, arr);
138   }
139   XLSX.utils.book_append_sheet(wb, ws, filename);
140   XLSX.writeFile(wb, filename + '.xlsx');
141 }
142 
143 export const read = (data, type) => {
144   /* if type == 'base64' must fix data first */
145   // const fixedData = fixdata(data)
146   // const workbook = XLSX.read(btoa(fixedData), { type: 'base64' })
147   const workbook = XLSX.read(data, {
148     type: type
149   });
150   const firstSheetName = workbook.SheetNames[0];
151   const worksheet = workbook.Sheets[firstSheetName];
152   const header = get_header_row(worksheet);
153   const results = XLSX.utils.sheet_to_json(worksheet);
154   return {
155     header,
156     results
157   };
158 }
159 
160 export const readesxle = async (file, header, jsointitle) => {
161   return new Promise(function (resolve, reject) {
162     const resultdata = {
163       ErrCode: "9",
164       ErrText: '导入文件格式不正确。',
165       Rows: []
166     }
167     const fileExt = file.name.split('.').pop().toLocaleLowerCase()
168     if (fileExt === 'xlsx' || fileExt === 'xls') {
169       const reader = new FileReader();
170 
171       const thisXLSX = XLSX;
172       const thisheader = header;
173       const thisjsointitle = jsointitle;
174       reader.readAsArrayBuffer(file)
175       reader.onloadstart = e => {}
176       // reader.onprogress = e => {
177       //   this.progressPercent = Math.round(e.loaded / e.total * 100)
178       // }
179       reader.onerror = e => {
180         resultdata.ErrText = '文件读取出错';
181         resultdata.ErrCode = "1";
182         resolve(resultdata);
183       }
184       reader.onload = e => {
185         const data = e.target.result
186         const
187           workbook = thisXLSX.read(data, {
188             type: "array"
189           });
190         let tempFlag = true;
191 
192         const firstSheetName = workbook.SheetNames[0];
193         const worksheet = workbook.Sheets[firstSheetName];
194         const sheetsheader = get_header_row(worksheet);
195         const sheetarray = thisXLSX.utils.sheet_to_json(worksheet);
196 
197         thisheader.forEach((item, index) => {
198           if (sheetsheader.findIndex(x => x == item) == -1) {
199             tempFlag = false
200           }
201         });
202         if (tempFlag) {
203           let sheetresult = [];
204           for (let i = 0; i < sheetarray.length; i++) {
205             sheetresult.push({});
206             for (let j = 0; j < thisheader.length; j++) {
207               if (sheetarray[i][thisheader[j]] == undefined || sheetarray[i][thisheader[j]] == null)
208                 sheetresult[i][thisjsointitle[j]] = "";
209               else
210                 sheetresult[i][thisjsointitle[j]] = sheetarray[i][thisheader[j]];
211             }
212           }
213           resultdata.ErrCode = "0";
214           resultdata.EErrText = "文件导入成功";
215           resultdata.Rows = sheetresult;
216         } else {
217           resultdata.ErrCode = "1";
218           resultdata.EErrText = "导入文件格式不正确。";
219           resultdata.Rows = [];
220         }
221         resolve(resultdata);
222       }
223     } else {
224       resultdata.ErrCode = "1";
225       resultdata.ErrText = '文件:' + file.name + '不是EXCEL文件,请选择后缀为.xlsx或者.xls的EXCEL文件。';
226       resolve(resultdata);
227     }
228   })
229 }
230 
231 export default {
232   export_table_to_excel,
233   export_array_to_excel,
234   export_json_to_excel,
235   export_array_to_excel2,
236   read,
237   readesxle
238 }
excel.js
3.导出pdf
 
        最开始使用jspdf 包 把 需要导出的table使用 canvas生成图片,然后把图片插入pdf内,但是这种方式不容易控制,并且生成的pdf清晰度不高,如果直接写pdf又会产生对中文支持的不友好,后采用前后端配合生成pdf文件并导出
使用blob的方式 后端返回文件流前端 接收并下载
主要代码如下
 1 //思路 webapi返回二进制的文件流 js 通过Blob接收并转换成pdf文件下载
 2 this.$axios({
 3 method: "post",
 4 Prefix: "",
 5 data: {
 6 ExCode: "IRAP_RPT_DownLoadFile",
 7 fileName: this.exportFileName,
 8 access_token: this.$cookies.get("access_token"),
 9 valueKeys: valueKeys, //"Product,Version,Description",
10 labeNames: labeNames, // "产品,版本,描述",
11 tableData: tableData
12 }
13 // responseType:'blob'
14 })
15 .then(response => {
16 // base64字符串转 byte[]
17 var bstr = atob(response.data.FileInfo),
18 n = bstr.length,
19 u8arr = new Uint8Array(n);
20 while (n--) {
21 u8arr[n] = bstr.charCodeAt(n);
22 }
23 // 转blob
24 var blob = new Blob([u8arr], {
25 type: `application/pdf;charset-UTF-8`
26 });
27 
28 if (this.MyBrowserIsIE()) {
29 // IE10以及Edge浏览器
30 var BOM = "\uFEFF";
31 // 传入 Blob 对象
32 navigator.msSaveBlob(blob, `${this.exportFileName}.pdf`);
33 } else {
34 // 非ie 浏览器
35 let content = window.URL.createObjectURL(blob);
36 this.createDownLoadClick(content, `${this.exportFileName}.pdf`);
37 }
38 })
39 .catch(err => {
40 console.log(err);
41 });
因为公司接口通用规范我这里返回的是文件的base64字符串
如果后台直接返回了二进制的文件流 就不用再次进行转换 并且需要加上responseType:'blob'这句
 
后台接口采用C# webapi 的方式主要代码如下
public string DownLoadFile(string clientID, string msgFormat, string inParam)
        {
            dynamic res = new System.Dynamic.ExpandoObject();
            try
            {
                dynamic dn = inParam.GetSimpleObjectFromJson();
                string token = dn.access_token.ToString();
                // 解析Json 字符串中的数组然后 转实体对象
                string fileName = dn.fileName;
                string lbelObj = dn.labeNames;
                string valueKeyObj = dn.valueKeys;
                object[] tableObj = dn.tableData;
                string tableJson = JsonConvert.SerializeObject(tableObj);
                string[] valueKeys = valueKeyObj.Split(',');
                string[] labeNames = lbelObj.Split(',');
                //string[] valueKeys = new string[] { "Product", "Version",  "Description" }; ;
                //string[] labeNames = new string[] { "产品", "版本", "描述" }; ;
                DataTable tblDatas = new DataTable("Datas");
                //string jsonStr =  "[{\"Product\":\"1\",\"Version\":\"1\",\"Description\":\"1\"}]";
                tblDatas = ToDataTableTwo(tableJson);
                PDFHelper pdfHelper = new PDFHelper();
                byte[] array = pdfHelper.ExportPDF(fileName, labeNames, tblDatas,  valueKeys);
                // 文件byte数组转base64字符串
                string str = Convert.ToBase64String(array);;
                byte[] bt = array.ToArray();
                res.ErrCode = 0;
                res.ErrText = "文件生成成功";
                res.FileInfo = str;
                return JsonConvert.SerializeObject(res);
            }
            catch (Exception ex)
            {
                //throw new Exception(ex.Message);
                res.ErrCode = 9999;
                res.ErrText = "Excel导入异常:" + ex.Message;
                return JsonConvert.SerializeObject(res);
            }
        }

C# pdf生成 使用itextsharp

 

完整js前台代码

  1 <template>
  2   <div style="float:right;padding-left:5px;">
  3     <Dropdown @on-click="btnExport" placement="bottom-start">
  4       <Button type="primary">导出
  5         <Icon type="ios-arrow-down"></Icon>
  6       </Button>
  7       <DropdownMenu slot="list">
  8         <template v-for="(item,index) in exportList" >
  9           <DropdownItem :key="`${_uid}_${index}`" :name="item.ExportID">{{ item.ExportName }}</DropdownItem>
 10         </template>
 11       </DropdownMenu>
 12     </Dropdown>
 13   </div>
 14 </template>
 15 <script>
 16 import json2csv from "json2csv";
 17 import excel from "../../Common/libs/excel";
 18 export default {
 19   data() {
 20     return {
 21       exportList: [
 22         {
 23           ExportName: "1-导出Excel",
 24           ExportID: "Excel"
 25         },
 26         {
 27           ExportName: "2-导出Csv",
 28           ExportID: "Csv"
 29         },
 30         {
 31           ExportName: "3-导出Pdf",
 32           ExportID: "Pdf"
 33         }
 34       ]
 35     };
 36   },
 37   props: {
 38     exportData: {
 39       type: Array,
 40       default: []
 41     },
 42     isPagination: {
 43       type: Boolean,
 44       default: false
 45     },
 46     exportColumns: {
 47       type: Array,
 48       default: []
 49     },
 50     exportFileName: {
 51       type: String,
 52       default: ""
 53     }
 54   },
 55   watch: {},
 56   computed: {},
 57   mounted() {
 58     // this.getRPTExportType() 初始化导出类型
 59   },
 60   methods: {
 61     // 下拉菜单改变时事件
 62     handleExportTypeChange(param) {
 63       // change 事件改变的时候提交给父组建 参数 value
 64       this.$emit("myHandleExportTypeChange", param);
 65     },
 66     // 判断是否IE浏览器
 67     MyBrowserIsIE() {
 68       let isIE = false;
 69       if (
 70         navigator.userAgent.indexOf("compatible") > -1 &&
 71         navigator.userAgent.indexOf("MSIE") > -1
 72       ) {
 73         // ie浏览器
 74         isIE = true;
 75       }
 76       if (navigator.userAgent.indexOf("Trident") > -1) {
 77         // edge 浏览器
 78         isIE = true;
 79       }
 80       return isIE;
 81     },
 82     //创建a标签下载
 83     createDownLoadClick(content, fileName) {
 84       const link = document.createElement("a");
 85       link.href = encodeURI(content);
 86       link.download = fileName;
 87       document.body.appendChild(link);
 88       link.click();
 89       document.body.removeChild(link);
 90     },
 91     btnExport(fileType) {
 92       debugger;
 93       // 如果是分页的
 94       if (this.isPagination == true) {
 95         // 向父组件提交一个需要重新传数据的方法 类型name
 96         this.$emit("myHandleRepeatExprot", fileType);
 97       } else {
 98         // 不分页直接导出
 99         this.exportFile(fileType);
100       }
101     },
102     exportFile(fileType) {
103       if (this.exportData.length == 0) {
104         this.$Message.error("不允许导出空表格");
105         return false;
106       }
107       // 循环数组
108       this.exportData.forEach((item, index) => {
109         let temp = {};
110         // 循环数组中的对象 当传入数据为undefined的时候 赋空
111         Object.keys(item).forEach(function(key) {
112           if (item[key] == undefined) {
113             item[key] == " ";
114             temp[key] = "";
115           } else {
116             temp[key] = item[key];
117           }
118         });
119         this.exportData[index] = temp;
120       });
121       let titles = []; // 导出内容的中文标题
122       let keys = []; // 导出内容的英文标题
123       this.exportColumns.forEach((item, index) => {
124         if (item.key != "handle") {
125           // 操作列定义为 handle
126           titles.push(item.title);
127           keys.push(item.key);
128         }
129       });
130       if (fileType == "Excel") {
131         const param = {
132           title: titles,
133           key: keys,
134           data: this.exportData,
135           autoWidth: true,
136           filename: this.exportFileName
137         };
138         excel.export_array_to_excel(param);
139       } else if (fileType == "Csv") {
140         this.exportCsv(
141           this.exportData,
142           this.exportColumns,
143           keys,
144           this.exportFileName
145         );
146       } else {
147         // 导出pdf
148         let tempArray = [];
149         let labels = titles.join(","); // title拼接成一个字符串
150         let keys2 = keys.join(","); // 拼接字符串
151         let row0 = this.exportData[0]; //导出数组内容的第一行
152         var rowKeys = [];
153         for (var p1 in row0) {
154           // 数组
155           if (row0.hasOwnProperty(p1)) {
156             rowKeys.push(p1); // table内容的 key
157           }
158         }
159         // keys 和 table的第一列的key比较 取table中不存在的列 为了赋空值
160         var diffArray = keys.filter(key => !rowKeys.includes(key));
161 
162         let tableData = [];
163         // 循环数组
164         this.exportData.forEach((rowItem, index) => {
165           let temp = rowItem;
166           diffArray.forEach((keyItem, index) => {
167             temp[keyItem] = " ";
168           });
169           tableData.push(temp);
170         });
171         this.exportPdfFile(keys2, labels, tableData);
172       }
173     },
174     // row data里面的每一个对象 keys 传入的需要导出列数组
175     // 筛选 需要导出的数据内容
176     /// 例如  keys ["Ordinal","Code"] row 有{Ordinal:1,Code:2,Name:3} 返回新对象{Ordinal:1,Code:2}
177     getRow(row, keys) {
178       let obj = {};
179       keys.forEach(col => {
180         debugger;
181         let val = row[col];
182         obj[col] = val;
183       });
184       return obj;
185     },
186     exportCsv(data, columns, keys, fileName) {
187       //导出的数据行集合
188       const rows = data.map(t => this.getRow(t, keys));
189       //导出的数据列标题
190       var fields = [];
191       columns.forEach(t => {
192         if (t.key != "handle") {
193           // 操作列定义为handle
194           let temp = {
195             value: t.key,
196             label: t.title
197           };
198           fields.push(temp);
199         }
200       });
201       try {
202         const result = json2csv.parse(rows, {
203           fields: fields,
204           excelStrings: true
205         });
206         if (this.MyBrowserIsIE()) {
207           // IE10以及Edge浏览器
208           var BOM = "\uFEFF";
209           var csvData = new Blob([BOM + result], { type: "text/csv" });
210           navigator.msSaveBlob(csvData, `${fileName}.csv`);
211         } else {
212           let csvContent = "data:text/csv;charset=utf-8,\uFEFF" + result;
213           // 非ie 浏览器
214           this.createDownLoadClick(csvContent, `${fileName}.csv`);
215         }
216       } catch (err) {
217         alert(err);
218       }
219     },
220     exportPdfFile(valueKeys, labeNames, tableData) {
221       let rexportPdfFile = {
222         ExCode: "DownLoadFile",
223         fileName: this.exportFileName,
224         access_token: this.$cookies.get("access_token"),
225         valueKeys: valueKeys, // 例如 "Product,Version,Description",
226         labeNames: labeNames, // 例如 "产品,版本,描述",
227         tableData: tableData
228       };
229       //思路 webapi返回二进制的文件流 js 通过Blob接收并转换成pdf文件下载
230       this.$axios({
231         method: "post",
232         Prefix: "",
233         data: {
234           ExCode: "IRAP_RPT_DownLoadFile",
235           fileName: this.exportFileName,
236           access_token: this.$cookies.get("access_token"),
237           valueKeys: valueKeys, //"Product,Version,Description",
238           labeNames: labeNames, // "产品,版本,描述",
239           tableData: tableData
240         }
241         // responseType:'blob'
242       })
243         .then(response => {
244           // base64字符串转 byte[]
245           var bstr = atob(response.data.FileInfo),
246             n = bstr.length,
247             u8arr = new Uint8Array(n);
248           while (n--) {
249             u8arr[n] = bstr.charCodeAt(n);
250           }
251           // 转blob
252           var blob = new Blob([u8arr], {
253             type: `application/pdf;charset-UTF-8`
254           });
255 
256           if (this.MyBrowserIsIE()) {
257             // IE10以及Edge浏览器
258             var BOM = "\uFEFF";
259             // 传入 Blob 对象
260             navigator.msSaveBlob(blob, `${this.exportFileName}.pdf`);
261           } else {
262             // 非ie 浏览器
263             let content = window.URL.createObjectURL(blob);
264             this.createDownLoadClick(content, `${this.exportFileName}.pdf`);
265           }
266         })
267         .catch(err => {
268           console.log(err);
269         });
270     },
271     getRPTExportType() {
272       this.$axios({
273         method: "post",
274         Prefix: "",
275         data: {
276           ExCode: "IRAP_RPT_ExportType",
277           access_token: this.$cookies.get("access_token")
278         }
279       }).then(response => {
280           if (response.data.ErrCode == 0) {
281             this.exportList = response.data.Rows
282           } else {
283             this.$Message.error(response.data.ErrText)
284           }
285         })
286         .catch(err => {
287           console.log(err);
288         });
289     }
290   }
291 };
292 </script>
vue通用导出组件

父组件调用代码

1 <MyExportType :exportFileName='`test`' v-on:myHandleRepeatExprot="myRepeatExprot"
2             :isPagination="isPagination" :exportData="exportData" :exportColumns="exportColumns" ref="MyExportType"></MyExportType>

 

如果父组件分页的需要导出所有未分页的数据 需要再次调用查询table数据的接口并且给exportData赋值

   async myRepeatExprot(name) {
        // 查询所有
        await this.geBTtResult(1)
        // 调用子组件的导出事件
        await this.$refs.MyExportType.exportFile(name)
      },

否则 未分页或者导出当前页 直接导出即可 不需要通过父组件调用子组件事件

 第一次写博文,有点紧张和言语不清忘见量,如果此文章对您有些许帮助,请给作者一些鼓励,或者留下您的意见

                                                             wexin                                      alipay

                                                          

 

 
posted @ 2019-01-22 21:24  木阳  阅读(36266)  评论(0编辑  收藏  举报