告别ASP.NET操作EXCEL的烦恼(总结篇)——放到首页奢侈下

      公元19XX年前,关于EXCEL的操作就如滔滔江水,连绵不绝,真正操作EXCEL我也是从去年下半年开始的,有些比较复杂的年度报表之类的,做起来也有点费力,不过还是都能画出来了,关于EXCEL的报表导出,考虑到导出耗时的问题我主要采用AJAX来做的,分别捕捉几个起止状态,给客户端提示3个状态:正在检索数据。。。---》准备导出数据。。。(只是从数据库成功取出,还没有读写excel文件)--》正在读写文件--》导出数据成功,当然如果哪一过程出错,都有对应的提示,只所以想到写这篇文章,主要是因为今年有个系统的部分EXCEL的操作也让我做,顺便结合之前操作EXCEL的经验作一下总结,可能也算不上什么,对于绝大多数来说也没什么技术含量,网上一搜一大把,但我想还是有必要总结一下,至少能给园子里的新手些许帮助,OK,Let's Go...

   一. 程序操作EXCEL的应用主要还是在统计报表方面,您可能会考虑读EXCEL模板,也可能会考虑没必要读模板,其实读不读模板都能达到一样的效果,看实际情况而用了。
       1. 读模板的话,首先模板存放在某个路径下,根据模板把从数据库里取出的数据写回EXCEL然后生成一个新的EXCEL存放都另一个路径以供下载,模板不变。
          我这里的EXCEL操作主要是在VS2005里的,VS2003也可以的,不过没怎么研究03里的操作(文章最后我会把05的示例下载地址贴上 那个demo里之前打包忘了放了一个生成数据的文件,刚放进去了,不加也是可以运行的,还有模板文件的数据稍微过滤了下重新放了部分对照看下)vs05中操作EXCEL直接引用.NET自带的COM组件,添加后项目的bin目录下会自动出现
     
Interop.Excel.dll这个DLL(需安装office2003 excel,下面的说明及示例都是基于office2003的,版本不同调用可能会不一样)
页面的命名空间引用 using Excel;
下面是调用模板的一段代码
 1  #region 使用模板导出Excel表
 2                 case "ReportByTemp":
 3                     {
 4 
 5                         DataView dv = Cache["ReportByTemp"as DataView;
 6                         //建立一个Excel.Application的新进程
 7                         Excel.Application app = new Excel.Application();
 8                         if (app == null)
 9                         {
10                             return;
11                         }
12                         app.Visible = false;
13                         app.UserControl = true;
14                         Workbooks workbooks = app.Workbooks;
15                         _Workbook workbook = workbooks.Add(template_path + "\\EXCEL测试模板.xls");//这里的Add方法里的参数就是模板的路径
16                         Sheets sheets = workbook.Worksheets;
17                         _Worksheet worksheet = (_Worksheet)sheets.get_Item(1);//模板只有一个sheet表
18                         if (worksheet == null)
19                         {
20                             return;
21                         }
22 
23                         int rowNum = 0;
24                         for (int i = 0; i < dv.Count; i++)
25                         {
26                             rowNum = i + 1;
27                             worksheet.Cells[3 + i, 1= rowNum;
28                             worksheet.Cells[3 + i, 2= dv[i].Row[0].ToString();
29                             worksheet.Cells[3 + i, 3= dv[i].Row[1].ToString();
30 
31                             excelOperate.SetBold(worksheet, worksheet.Cells[3 + i, 1], worksheet.Cells[3 + i, 1]); //黑体
32                             excelOperate.SetHAlignCenter(worksheet, worksheet.Cells[3 + i, 1], worksheet.Cells[3 + i, 3]);//居中
33                             worksheet.get_Range(worksheet.Cells[3 + i, 1], worksheet.Cells[3 + i, 3]).Borders.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.Black);
34 
35                         }
36 
37                         tick = DateTime.Now.Ticks.ToString();
38                         save_path = temp_path + "\\" + tick + ".xls";
39                         workbook.SaveAs(save_path, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Excel.XlSaveAsAccessMode.xlNoChange, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value);
40                         excelOperate.Dispose(worksheet, workbook, app);//关闭Excel进程
41 
42                     }
43                     break;
44                 #endregion
效果如下:

       
       2. 不读模板的话,调用的时候其实会继承一个空白模板,然后写入数据,程序画表头,最终达到一样的效果,程序如下:
 1  #region 不使用模板生成Excel表
 2                 case "ReportByNone":
 3                     {
 4 
 5                         DataView dv = Cache["ReportByNone"as DataView;
 6                         //建立一个Excel.Application的新进程
 7                         Excel.Application app = new Excel.Application();
 8                         if (app == null)
 9                         {
10                             return;
11                         }
12                         app.Visible = false;
13                         app.UserControl = true;
14                         Workbooks workbooks = app.Workbooks;
15                         _Workbook workbook = workbooks.Add(XlWBATemplate.xlWBATWorksheet);//这里的Add方法里的参数就相当于继承了一个空模板(暂这样理解吧)
16                         Sheets sheets = workbook.Worksheets;
17                         _Worksheet worksheet = (_Worksheet)sheets.get_Item(1);
18                         if (worksheet == null)
19                         {
20                             return;
21                         }
22 
23                         worksheet.get_Range(worksheet.Cells[11], worksheet.Cells[13]).Merge(Missing.Value); //横向合并
24                         worksheet.get_Range(worksheet.Cells[11], worksheet.Cells[11]).Value2 = "导出EXCEL测试一";
25                         excelOperate.SetBold(worksheet, worksheet.Cells[11], worksheet.Cells[11]); //黑体
26                         excelOperate.SetHAlignCenter(worksheet, worksheet.Cells[11], worksheet.Cells[11]);//居中
27                         excelOperate.SetBgColor(worksheet, worksheet.Cells[11], worksheet.Cells[11], System.Drawing.Color.Red);//背景色
28                         excelOperate.SetFontSize(worksheet, worksheet.Cells[11], worksheet.Cells[11], 16);//字体大小
29                         excelOperate.SetRowHeight(worksheet, worksheet.Cells[11], worksheet.Cells[11], 32.25);//行高
30                         worksheet.get_Range(worksheet.Cells[11], worksheet.Cells[11]).Borders.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.Black);//黑色连续边框
31 
32                         worksheet.Cells[21= "序号";
33                         worksheet.Cells[22= "公司";
34                         worksheet.Cells[23= "部门";
35                         excelOperate.SetBold(worksheet, worksheet.Cells[21], worksheet.Cells[23]); //黑体
36                         worksheet.get_Range(worksheet.Cells[21], worksheet.Cells[23]).Borders.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.Black);
37                         excelOperate.SetHAlignRight(worksheet, worksheet.Cells[21], worksheet.Cells[23]);
38                         excelOperate.SetBgColor(worksheet, worksheet.Cells[21], worksheet.Cells[23], System.Drawing.Color.Silver);//背景色
39                         int rowNum = 0;
40                         for (int i = 0; i < dv.Count; i++)
41                         {
42                             rowNum = i + 1;
43                             worksheet.Cells[3 + i, 1= rowNum;
44                             worksheet.Cells[3 + i, 2= dv[i].Row[0].ToString();
45                             worksheet.Cells[3 + i, 3= dv[i].Row[1].ToString();
46 
47                             excelOperate.SetBold(worksheet, worksheet.Cells[3 + i, 1], worksheet.Cells[3 + i, 1]); //黑体
48                             excelOperate.SetHAlignCenter(worksheet, worksheet.Cells[3 + i, 1], worksheet.Cells[3 + i, 3]);//居中
49                             worksheet.get_Range(worksheet.Cells[3 + i, 1], worksheet.Cells[3 + i, 3]).Borders.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.Black);//设置边框颜色,不然打印预览,会非常不雅观
50 
51                         }
52                         excelOperate.SetColumnWidth(worksheet, "A"10);
53                         excelOperate.SetColumnWidth(worksheet, "B"20);
54                         excelOperate.SetColumnWidth(worksheet, "C"20);
55                         worksheet.Name = "导出EXCEL测试一";
56 
57                         tick = DateTime.Now.Ticks.ToString();
58                         save_path = temp_path + "\\"+ tick + ".xls";
59                         workbook.SaveAs(save_path, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Excel.XlSaveAsAccessMode.xlNoChange, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value);
60                         excelOperate.Dispose(worksheet, workbook, app);//关闭Excel进程
61 
62                     }
63                     break;
64 
65                 #endregion
效果如下:


以上我给了两个最简单的操作说明,下面详细说一下对于一些稍微复杂的报表的生成处理

      二. 对于复杂的EXCEL报表的生成处理,无非是纵向合并相同的数据行及嵌套纵向合并等一些操作,下面就几个具有针对性的报表作下说明.
            1.要生成相对复杂的EXCEL表,在从数据库取数据时,要注意先按照合理的要求排好序,有时候可能order by后面要跟好几个字段,而且这几个字段谁先谁后也要注意,因为这些会直接影响报表呈现的效果,比如你的EXCEL表要按月份统计国内外的项目,显示出来的时候要多个项目相同的人连续,那么排序就可能要这样order by 月份,项目类别,用户ID,项目ID(这是写好的视图,基于视图来检索的),这个排序的字段顺序就不能变了,变了的话就不太好生成想要的形式了,如下图:

这个也是动态画的,用了个简单的模板,模板就一个表头,没多大意义,除非表头很复杂而且在列表中不需要重画,考虑模板就比较好,向上面那个一月份国际的和其它月份的都是需要重画表头的。至于合并,如果不是嵌套的合并,我们可以在向模板循环写数据的时候直接控制,比如下面一个简单的写法:
 1  for (i = 0; i < table.Rows.Count; i++)
 2                             {
 3                                 bidName = table.Rows[index]["BIDNAME"].ToString();
 4                                 if (table.Rows[i]["BIDNAME"].ToString() == bidName)
 5                                 {
 6                                     projNum++;
 7                                     worksheet.Cells[5 + i, 2= table.Rows[i]["PROJNO"];
 8                                     worksheet.Cells[5 + i, 3= table.Rows[i]["PROJNAME"];
 9                                     worksheet.Cells[5 + i, 4= table.Rows[i]["STAT_DATE"];
10                                     worksheet.Cells[5 + i, 5= table.Rows[i]["PROJTYPE"];
11                                     worksheet.Cells[5 + i, 6= table.Rows[i]["CONTENT"];
12                                     worksheet.Cells[5 + i, 7= table.Rows[i]["OPENDT"];
13                                     worksheet.Cells[5 + i, 8= table.Rows[i]["OPENADDRESS"];
14                                     worksheet.Cells[5 + i, 9= table.Rows[i]["REV_DATE"];
15                                     worksheet.Cells[5 + i, 10= table.Rows[i]["BID_UNIT"];
16                                     worksheet.Cells[5 + i, 11= table.Rows[i]["AGT_AMOUNT"];
17                                     worksheet.Cells[5 + i, 12= table.Rows[i]["CURRENCY"+ ":" + table.Rows[i]["BIDSER_AMOUNT"];
18                                     worksheet.Cells[5 + i, 13= table.Rows[i]["SENDDATE"];
19                                     worksheet.Cells[5 + i, 14= table.Rows[i]["CURRENCY"+ ":" + table.Rows[i]["BIDPRICE"];
20                                     worksheet.Cells[5 + i, 15= table.Rows[i]["BOOKAMOUNT"];
21                                     worksheet.Cells[5 + i, 16= table.Rows[i]["CURRENCY"+ ":" + table.Rows[i]["BAIL_AMOUNT"];
22                                     worksheet.Cells[5 + i, 17= table.Rows[i]["USERNAME"];
23                                     worksheet.Cells[5 + i, 18= table.Rows[i]["SECOND_USER"];
24                                     worksheet.Cells[5 + i, 19= "";
25                                     worksheet.get_Range(worksheet.Cells[5 + i, 1], worksheet.Cells[5 + i, 19]).Borders.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.Black);
26                                     continue;
27                                 }
28 
29                                 worksheet.get_Range(worksheet.Cells[5 + rowid, 1], worksheet.Cells[5 + i - 11]).Merge(Missing.Value); //将第一列按投标单位合并
30                                 worksheet.get_Range(worksheet.Cells[5 + rowid, 1], worksheet.Cells[5 + rowid, 1]).Value2 = bidName + "(" + projNum.ToString() + "个项目)";//合并后的单元格内容
合并单元格的时候也要注意一个问题,就是合并的单元格必须是为空的,不然在执行合并时,会提示“合并后的单元格的值将丢失”,具体不这样提示的,大致是这个意思,一般我们合并都单元格相同的内容,在合并前我们先保存那个值,再清空后合并,上面的代码中把worksheet.Cell[5+rowid,1]这里系列的单元格的值空出来了,没写数据,而且最后合并了再写值,避免了去循环清空。
     2.嵌套的合并向上面那样做可能控制比较麻烦,而且思路可能很混乱,我们可以考虑先循环填充所有的数据,在循环出来要合并的列,比如像下面的这张表

先循环填充数据,如下:
 1  int index = 0, rownum = 0;
 2                             string ProjNo = "";
 3                             for (i = 0; i < table.Rows.Count; i++)
 4                             {
 5                                 ProjNo = table.Rows[index]["PROJNO"].ToString();
 6                                 if (table.Rows[i]["PROJNO"].ToString() == ProjNo)
 7                                 {
 8                                     wksheet.Cells[3 + i, 1= rownum + 1;
 9                                     wksheet.Cells[3 + i, 2= "'" + table.Rows[i]["PROJNO"];   //加上单引号保证以0开头的字符原样输出
10                                     wksheet.Cells[3 + i, 3= "'" + table.Rows[i]["PROJNAME"];
11                                     wksheet.Cells[3 + i, 4= "'" + table.Rows[i]["PA_NAME"];
12                                     wksheet.Cells[3 + i, 5= "'" + table.Rows[i]["BIDER_NAME"];
13                                     wksheet.Cells[3 + i, 6= table.Rows[i]["BAIL_AMOUNT"];
14                                     wksheet.Cells[3 + i, 7= table.Rows[i]["NOT_BACK"];
15                                     wksheet.get_Range(wksheet.Cells[3 + i, 1], wksheet.Cells[3 + i, 7]).Borders.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.Black);
16                                     continue;
17                                 }
18 
19                                 index = i;
20                                 rownum++;
21                                 i--;
22 
23                             }
下面合并前三列相同内容的单元:
 1  //合并前三列操作
 2                             int m = 1, rowid = 3, k;
 3                             string projName = "";
 4                             for (k = 3; k <= i + 2; k++)
 5                             {
 6                                 if (Convert.ToInt32(wksheet.get_Range(wksheet.Cells[k, 1], wksheet.Cells[k, 1]).Value2) == m)
 7                                 {
 8                                     ProjNo = wksheet.get_Range(wksheet.Cells[k, 2], wksheet.Cells[k, 2]).Value2.ToString();
 9                                     projName = wksheet.get_Range(wksheet.Cells[k, 3], wksheet.Cells[k, 3]).Value2.ToString();
10                                     wksheet.get_Range(wksheet.Cells[k, 1], wksheet.Cells[k, 1]).Value2 = "";
11                                     wksheet.get_Range(wksheet.Cells[k, 2], wksheet.Cells[k, 2]).Value2 = "";
12                                     wksheet.get_Range(wksheet.Cells[k, 3], wksheet.Cells[k, 3]).Value2 = "";
13                                     continue;
14                                 }
15                                 wksheet.get_Range(wksheet.Cells[rowid, 1], wksheet.Cells[k - 11]).Merge(Missing.Value);
16                                 wksheet.get_Range(wksheet.Cells[rowid, 1], wksheet.Cells[rowid, 1]).Value2 = m;
17 
18                                 wksheet.get_Range(wksheet.Cells[rowid, 2], wksheet.Cells[k - 12]).Merge(Missing.Value);
19                                 wksheet.get_Range(wksheet.Cells[rowid, 2], wksheet.Cells[rowid, 2]).Value2 = "'" + ProjNo;
20 
21                                 wksheet.get_Range(wksheet.Cells[rowid, 3], wksheet.Cells[k - 13]).Merge(Missing.Value);
22                                 wksheet.get_Range(wksheet.Cells[rowid, 3], wksheet.Cells[rowid, 3]).Value2 = "'" + projName;
23 
24                                 m++;
25                                 rowid = k;
26                                 k--;
27                             }
28                             //跳出循环后合并最后一个招标项目
29 
30                             wksheet.get_Range(wksheet.Cells[rowid, 1], wksheet.Cells[k - 11]).Merge(Missing.Value);
31                             wksheet.get_Range(wksheet.Cells[rowid, 1], wksheet.Cells[rowid, 1]).Value2 = m;
32 
33                             wksheet.get_Range(wksheet.Cells[rowid, 2], wksheet.Cells[k - 12]).Merge(Missing.Value);
34                             wksheet.get_Range(wksheet.Cells[rowid, 2], wksheet.Cells[rowid, 2]).Value2 = "'" + ProjNo;
35 
36                             wksheet.get_Range(wksheet.Cells[rowid, 3], wksheet.Cells[k - 13]).Merge(Missing.Value);
37                             wksheet.get_Range(wksheet.Cells[rowid, 3], wksheet.Cells[rowid, 3]).Value2 = "'" + projName;
下面合并标段列
 1  //合并标段列
 2 
 3                             index = 0; rowid = 3//重置变量
 4                             string pa_name = string.Empty; //标段名称
 5                             for (k = 3; k <= i + 2; k++)
 6                             {
 7                                 pa_name = table.Rows[index]["PA_NAME"].ToString();
 8                                 if (wksheet.get_Range(wksheet.Cells[k, 4], wksheet.Cells[k, 4]).Value2.ToString() == pa_name)
 9                                 {
10                                     wksheet.get_Range(wksheet.Cells[k, 4], wksheet.Cells[k, 4]).Value2 = "";
11                                     continue;
12                                 }
13                                 wksheet.get_Range(wksheet.Cells[rowid, 4], wksheet.Cells[k - 14]).Merge(Missing.Value);
14                                 wksheet.get_Range(wksheet.Cells[rowid, 4], wksheet.Cells[rowid, 4]).Value2 = "'" + pa_name;
15                                 index = k - 3;
16                                 rowid = k;
17                                 k--;
18 
19                             }
20                             //退出循环时合并最后一个项目的标段
21                             wksheet.get_Range(wksheet.Cells[rowid, 4], wksheet.Cells[k - 14]).Merge(Missing.Value);
22                             wksheet.get_Range(wksheet.Cells[rowid, 4], wksheet.Cells[rowid, 4]).Value2 = "'" + pa_name;
23                             tick = DateTime.Now.ToString("yyyyMMddhhmmss");
24                             save_path = temp_path + "\\" + tick + "保证金收退情况表.xls";
25                             Session["BailBackID"= tick + "保证金收退情况表.xls";
26                             Session["_BailBack"= "true";
27                             workbook.SaveAs(save_path, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Excel.XlSaveAsAccessMode.xlNoChange, Missing.Value, Missing.Value, Missing.Value, Missing.Value, Missing.Value);
28                             excelOperate.Dispose(worksheet, workbook, app);//关闭Excel进程
29                             //DownLoad(save_path);
30                             //Page_Close();
当然,上面的操作中会进行好几次循环,在性能方面不太可取,园子里的兄弟也许会有更好的方法,小弟不吝赐教了
下面我们看下几个效果图:





(注意:这里提示的导出数据是指从数据库成功取出数据,还没有操作EXCEL对象,刚开始已经说过了,当然这个提示文字换成其它的也可以)




整个过程采用AJAX提示的,一来不刷新,二来导出时间比较长的话,可以给客户一个良好的体验效果,否可,用户一点导出按钮,半天没反应也没提示,客户就觉得怎么这么慢的,是不是你们程序有问题,指责一大堆,有了这么些交互提示信息,让客户多等几分钟也能承受。

     3.生成的表格包含多个sheet的操作,比如下面一种情况


绘制这张表的要求是根据选择某年的几月到几月,生成这个几个月的一个综合情况的sheet,然后分别生成这几个月的单独的sheet表,生成上面表的模板,包含两个sheet ,一个综合月份的sheet和一个单独月份的sheet,因为单独月份的sheet表现形式都是一样的,我们可以根据选择的月份个数Copy几个sheet就可以了
 1  Workbooks workbooks = app.Workbooks;
 2 
 3                             _Workbook workbook = workbooks.Add(template_path + "\\招标单位年度招标情况逐月统计表.xls");
 4                             Sheets sheets = workbook.Worksheets;
 5                             _Worksheet Yearsheet = (_Worksheet)sheets.get_Item(1);
 6                             _Worksheet worksheet = (_Worksheet)sheets.get_Item(2);
 7                             if (worksheet == null)
 8                             {
 9                                 return;
10                             }
11                             for (int i = 1; i < monthCount; i++)
12                                 worksheet.Copy(Missing.Value, workbook.Worksheets[2]);//月统计工作薄

Yearsheet的操作就不说了,和前面几个一样操作,关键是月份的sheet的生成,其实就是循环操作get_Item(i),代码如下
 1  //////////////////////////////////////每月详细统计////////////////////////////////////
 2 
 3                             int item_id = 2;
 4                             rowNum = 0; book_Amount = 0; index = 0;
 5                             bid_Amount = ""; bidser_Amount = ""; agent_Amount = 0;//清空变量
 6                             _Worksheet ws = null;
 7                             for (int i = 0; i < tableMM.Rows.Count; i++)
 8                             {
 9                                 rowNum++;
10                                 Month = tableMM.Rows[index]["DATE_MONTH"].ToString();
11                                 if (tableMM.Rows[i]["DATE_MONTH"].ToString() == Month)
12                                 {
13                                     ws = (_Worksheet)sheets.get_Item(item_id);
14                                     ws.Cells[3 + rowNum - 11= rowNum;
15                                     ws.Cells[3 + rowNum - 12= tableMM.Rows[i]["PROJNO"];
16                                     ws.Cells[3 + rowNum - 13= tableMM.Rows[i]["PROJNAME"];
17                                     ws.Cells[3 + rowNum - 14= tableMM.Rows[i]["BID_TYPE"];
18                                     ws.Cells[3 + rowNum - 15= tableMM.Rows[i]["BID_MODE"];
19                                     ws.Cells[3 + rowNum - 16= tableMM.Rows[i]["OPENDT"];
20                                     ws.Cells[3 + rowNum - 17= tableMM.Rows[i]["OPENADDRESS"];
21                                     ws.Cells[3 + rowNum - 18= tableMM.Rows[i]["BID_UNIT"];
22                                     ws.Cells[3 + rowNum - 19= tableMM.Rows[i]["NOTICE_NO"].ToString().Replace("神华国贸""");
23                                     ws.Cells[3 + rowNum - 110= tableMM.Rows[i]["BOOKAMOUNT"];
24                                     ws.Cells[3 + rowNum - 111= tableMM.Rows[i]["BIDPRICE"+ "(万" + tableMM.Rows[i]["CURRENCY"+ ")";
25                                     ws.Cells[3 + rowNum - 112= tableMM.Rows[i]["BIDSER_AMOUNT"+ "(万" + tableMM.Rows[i]["CURRENCY"]+")";
26                                     ws.Cells[3 + rowNum - 113= tableMM.Rows[i]["AGT_AMOUNT"];
27                                     ws.Cells[3 + rowNum - 114= "";
28                                     ws.get_Range(ws.Cells[3 + rowNum - 11], ws.Cells[3 + rowNum - 114]).Borders.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.Black);
29                                     continue;
30                                 }
31 
32                                 ws.Cells[11= year + "" + bidName + GetMonth(Month) + "月份招标项目情况一览表";
33 
34                                 //每月合计
35                                 sql = " SELECT CURRENCY, NVL(SUM(BIDPRICE),0) AS BIDPRICE,NVL(SUM(BOOKAMOUNT),0) AS BOOKAMOUNT,NVL(SUM(BIDSER_AMOUNT),0) AS BIDSER_AMOUNT,NVL(SUM(AGT_AMOUNT),0) AS AGT_AMOUNT FROM IBS_V_BID_MONTHLY_STAT" + SqlFilter +
36                                            " AND DATE_YEAR ='" + year + "' AND COMPANY_ID=" + biderID + " AND DATE_MONTH ='" + Month + "'" +
37                                            " GROUP BY CURRENCY";
38                                 System.Data.DataTable dt1 = OracleHelper.RetDataTable(sql);
39                                 for (int m = 0; m < dt1.Rows.Count; m++)
40                                 {
41                                     bid_Amount += dt1.Rows[m]["BIDPRICE"+ "(万"+dt1.Rows[m]["CURRENCY"+ ")\r\t";
42                                     book_Amount += float.Parse(dt1.Rows[m]["BOOKAMOUNT"].ToString());
43                                     bidser_Amount += dt1.Rows[m]["BIDSER_AMOUNT"+ "(万" + dt1.Rows[m]["CURRENCY"+ ")\r\t";
44                                     agent_Amount += float.Parse(dt1.Rows[m]["AGT_AMOUNT"].ToString());
45                                 }
46 
47                                 ws.Cells[3 + rowNum - 13= "合  计";
48                                 ws.Cells[3 + rowNum - 110= book_Amount;
49                                 ws.Cells[3 + rowNum - 111= bid_Amount;
50                                 ws.Cells[3 + rowNum - 112= bidser_Amount;
51                                 ws.Cells[3 + rowNum - 113= agent_Amount;
52                                 ws.get_Range(ws.Cells[3 + rowNum - 11], ws.Cells[3 + rowNum - 114]).Borders.Color = System.Drawing.ColorTranslator.ToOle(System.Drawing.Color.Black);
53                                 ws.Name = GetMM(Month);
54 
55                                 item_id++;
56                                 index = i; //汇总下一个月份的招标项目
57                                 i--;
58                                 rowNum = 0; book_Amount = 0;
59                                 bid_Amount = ""; bidser_Amount = ""; agent_Amount = 0;//清空变量
60                             }
61 
62                             //跳出循环时进行最后一个月份的项目汇总

用的是oracle数据库,所以上面那个sql语句。。。 呵呵

============================================================================================
上面大致说得就差不多了,因为是不断循环的什么的,可能对于大的数据量读写来说,比较好性能,如果大家有什么更好的方法,可以指点下,为了弥补等待时间过长,所以才结合了AJAX来处理。

最后我把做的一个小demo的链接帖出来给大家,还有一些空模板和对应生成的数据表给大家对照看下,尤其相对复杂一些的表画应该是能画出来的,主要看大家采用什么样的方法,能少循环一次就尽量少循环,呵呵~~~
   EXCEL模板读写说明
   http://www.justlike.com.cn/upfiles/template_xls.rar
   http://www.justlike.com.cn/upfiles/ExcelFiles.rar
   http://www.justlike.com.cn/upfiles/ExcelReportDemo.rar
(说明:最后弹出下载文件的一个页面一直想让其自动关掉,但是不行,如果不关掉,再点导出,不会弹出下载框,实际的处理中我们可以在导出旁边放个下载按钮,就像上面的效果图里那样,当然可以点导出的时候让其在网页中直接打开,点下载的时候再弹出下载框,但是直接打开的话,文件需要生成在虚拟目录下,不太安全,呵呵~~,看实际情况处理了)

==========================================================================================
今天补充说明下,关于那个调用ajax回调的效果,有个地方用到了所谓的“ajax嵌套调用”,如下

 1  function ExcelReportCallback(resp)
 2  {
 3       if(resp.value == "OK")
 4       {
 5            
 6              $('tipMsg').innerHTML = "<img border=\"0\" src=\"images/s_progressbar.gif\"><font color=#FF0000 style=font-weight:bold>准备导出数据,请稍等</font>";
 7              setTimeout("RedirectUrl()",1000);//延时体验  
 8       }
 9       else
10        if(resp.value == "NO")
11        {
12           $('tipMsg').innerHTML = "<font color=#FF0000 style=font-weight:bold>没有找到符合该查询条件的数据</font>";
13           $('btnExcel').disabled = false;
14        }
15       else
16       {
17           $('tipMsg').innerHTML = "<font color=#FF0000 style=font-weight:bold>警告:导出数据出错</font>";
18           $('btnExcel').disabled = false;
19       }
20        
21       
22  }

 

 1 function RedirectUrl()
 2  {
 3      $('tipMsg').innerHTML = "<img border=\"0\" src=\"images/ajaxloading.gif\"><font color=#7fffd4 style=font-weight:bold>正在读写报表文件,请稍后</font>";
 4         var ajax = new ajax_request("ExcelReport.aspx?flag=ReportByTemp&"+Math.random(), """", ReportCallback); 
 5         function ReportCallback(resp)
 6         {
 7             if(resp.value != "Error" && resp.value !="")
 8             {
 9                $('btnExcel').disabled = false;
10                $('tipMsg').innerHTML = "<font color=#FF0000 style=font-weight:bold>数据导出成功!</font>";
11                Open("XLS_DownLoad.aspx?path="+resp.value);//window.location.href = resp.value;//
12             }
13             else
14             {
15                $('btnExcel').disabled = false;
16                $('tipMsg').innerHTML = "<font color=#FF0000 style=font-weight:bold>文件读写出错,请检查文件模板是否存在或对文件是否有读写权限!</font>";
17             }
18         }
19 
20  }
21  
22  function Open(url) 
23 {
24     window.open(url,'newwindow','height=1,width=1,top=1500,left=1500,toolbar=no,menubar=no,scrollbars=yes,location=no,status=no')
25 }

ExcelReportCallback(resp)原本是一个回调函数,但是里面调用了一个RedirectUrl()方法,这个方法又包含了一个回调函数,这样就形成了回调的嵌套,之所以这么做,是因为,第一个回调是处理从数据库取出数据成功与否,如果成功了跳转到画EXCEL的页面,这样的话会出现一个空白页等生成好后出现下载框,后来觉得是否可以嵌套一个回调来继续一次异步操作,这样就不会出现长时间等待的空白页面了,而是生成好EXCEL后返回地址,或者可以返回一个文件名到XLS_DownLoad.aspx页面直接下载,但是XLS_DownLoad.aspx也是要出现的,我尝试过让下载后这个页面自动关闭,无赖做不到,所以把Open()方法里的数据值调得让页面不显示,但是状态栏还是有显示的。

到这里算是写完了,决定奢侈下,放到首页下:),总觉得首页的文章只有高手才能放,而且放到首页也是一种奢侈,希望对园子里的某些人有一定的帮助吧~~



 












 

posted @ 2008-04-13 20:30 peace 阅读(8970) 评论(82)  编辑 收藏 网摘

  回复  引用  查看    
#1楼2008-04-13 20:34 | 谦虚的天下      
很好,很实用,对某些人,某些时候!
  回复  引用  查看    
#2楼2008-04-13 20:35 | OneCool      
不用安装EXCEL就能导出EXCEL开源不开源的多了去了。为什么还调用EXCEL导出呢?
  回复  引用    
#3楼2008-04-13 20:35 | 刘荣华![未注册用户]
很不错。呵呵

不过Excel模板的实际内容还是有必要过滤一下吧。
不牵扯到客户的机密么?

  回复  引用  查看    
#4楼[楼主]2008-04-13 20:39 | peace      
--引用--------------------------------------------------
OneCool: 不用安装EXCEL就能导出EXCEL开源不开源的多了去了。为什么还调用EXCEL导出呢?
--------------------------------------------------------
我前面也说过了,的确是很多,一搜一大把,看你选择怎么用,怎么做出好的效果了

  回复  引用  查看    
#5楼[楼主]2008-04-13 20:41 | peace      
@刘荣华!
应该是要过滤下的,打包的时候忘了,不过我相信园子里的同志们,而且这些也只是测试的内容,关系应该不大的

  回复  引用  查看    
#6楼2008-04-13 20:50 | OneCool      
我测试一下那个例子,第一次正常导出了。再点一次把我的当前打开的EXCEL给杀掉了。不知道为什么,也可能是我操作不当。
  回复  引用  查看    
#7楼[楼主]2008-04-13 20:57 | peace      
--引用--------------------------------------------------
OneCool: 我测试一下那个例子,第一次正常导出了。再点一次把我的当前打开的EXCEL给杀掉了。不知道为什么,也可能是我操作不当。
--------------------------------------------------------
是的 导出成功后会吧EXCEL.EXE进程都杀掉,释放资源,不然服务器上的任务管理器里有很多的EXCEL.EXE进程,也考虑过只杀掉当前的EXCEL进程,感觉都不怎么成功,干脆都杀掉了,这个在服务器生成,都杀掉影响也不大

  回复  引用  查看    
#8楼2008-04-13 21:06 | OneCool      
呵呵,这个我理解,问题是第二次的时候杀了,应该起来一个新的啊。否则就导不出了。我是用vs2005打开调试运行的。
  回复  引用  查看    
#9楼[楼主]2008-04-13 21:11 | peace      
不是导不出 已经到出了 只是那个导出的空白页面没关闭 要手动下,
每次导出时 任务栏有个XLS_DownLoad.aspx页面 你把这个关闭了 就行了
我上面有说明哦
window.open(url,'newwindow','height=200,width=300,top=500,left=500,toolbar=no,menubar=no,scrollbars=yes,location=no,status=no')
里面的数据调整下,你就能看的很清楚了

  回复  引用  查看    
#10楼2008-04-13 21:12 | 晓江工作室      
@楼上
所以最好的方法是不依赖与Office Excel(即无需excel.exe)导出Excel文件,毕竟操作第三方线程,其控制有一定的局限性。

  回复  引用  查看    
#11楼[楼主]2008-04-13 21:16 | peace      
@晓江工作室
恩 也是的 有时间会再看下

  回复  引用  查看    
#12楼2008-04-13 21:17 | OneCool      
不错。学习了。
  回复  引用  查看    
#13楼2008-04-13 22:11 | 代码乱了      
博主写得很详细,赞一个。
操作上,MS自己的Com组件的确是方便些,不过在ASP.net中调用估计性能会有很大的影响,毕竟不是一个人在用啊,而EXcel这个组件很占系统资源啊,我觉得用在Winform中比较合适,在ASP。net下操作的话也有很多成熟的二进制读写的东东,而且资源占用小,不需装Excel。

  回复  引用  查看    
#14楼2008-04-13 22:37 | liong      
多谢分享 代码里能去掉前面的行号就好了
  回复  引用  查看    
#15楼[楼主]2008-04-13 22:53 | peace      
不客气了 园子里很多同志写出的东西比这个有价值得多了 都是相互学习了~~
  回复  引用  查看    
#16楼2008-04-13 23:02 | 怪怪      
好多0....
  回复  引用  查看    
#17楼2008-04-13 23:17 | TT.Net      
很实用。收藏先
  回复  引用    
#18楼2008-04-13 23:55 | doublog[未注册用户]
为什么不直接写成html然后在生成为xls呢?占资源不是更小?

我刚用这种办法来生成word 当然我现在做的表格都是简单的,不知道复杂了会不会有问题

刚写了篇博客说这个
http://hi.doublog.com/master/blog/item/174/

  回复  引用  查看    
#19楼2008-04-14 00:09 | Kevin Li      
的确好用
  回复  引用  查看    
#20楼2008-04-14 00:21 | lixiong      
最好不要这样做
在server side比如IIS和ASP.NET使用Office Automation官方不支持,容易发生安全问题和死锁

  回复  引用    
#21楼2008-04-14 00:30 | jasduke[未注册用户]
好文章,支持先。
  回复  引用  查看    
#22楼[楼主]2008-04-14 07:48 | peace      
@doublog
简单的生成html后输出,是比较方便的,复杂些的就不太好了

  回复  引用  查看    
#23楼[楼主]2008-04-14 07:49 | peace      
@lixiong
是的,有时就生成不了,要重新配置过权限什么的才行

  回复  引用  查看    
#24楼[楼主]2008-04-14 07:52 | peace      
http://www.justlike.com.cn/upfiles/template_xls.rar" target="_new">http://www.justlike.com.cn/upfiles/template_xls.rar
http://www.justlike.com.cn/upfiles/ExcelFiles.rar" target="_new">http://www.justlike.com.cn/upfiles/ExcelFiles.rar
两个链接的模板内容我删掉了,考虑到应该过滤下的,多多少少还是有点不好,虽说是测试数据,一会我重新放上去下

  回复  引用  查看    
#25楼2008-04-14 08:02 | 天生俪姿      
感觉不错。收藏了
  回复  引用  查看    
#26楼2008-04-14 08:17 | 专研.NET      
期待楼主把源码发出来,学习ing...
  回复  引用  查看    
#27楼2008-04-14 08:41 | 李战      
http://www.cnblogs.com/Emoticons/yoyocici/223852199.gif" alt="" />路过。
  回复  引用  查看    
#28楼[楼主]2008-04-14 08:45 | peace      
@专研.NET
其实所有的源码我那个例子里都包含了,只是生成复杂的你要程序里控制了啊
那些生成的表格样例我刚删掉了,我把表格内容过滤下和对应的生成代码下班后整理下重新打个包放上去下

  回复  引用  查看    
#29楼2008-04-14 08:54 | Cure      
还应该给生成的excel表格加上密码,防止用户拿到客户端后篡改数据,想想看如果从数据库提取数据而不按照此数据出报表,那么这个打印功能的安全性是非常差的。
  回复  引用  查看    
#30楼2008-04-14 08:55 | Cure      
忘了说一句,加上密码,而且还要设置表格中的每一个格子都只能选择,但是不能复制粘贴和修改,删除。
  回复  引用  查看    
#31楼[楼主]2008-04-14 09:10 | peace      
@Cure
原本也没有加密的意思,你这么一说确实是的,要做到完美安全可以抽时间再写下的

  回复  引用    
#32楼2008-04-14 09:12 | 12312312443[未注册用户]
  回复  引用  查看    
#33楼2008-04-14 09:36 | 风海迷沙      
用reportviewer,还直观还能导出pdf
  回复  引用  查看    
#34楼2008-04-14 09:40 | 汉广      
不错,
以前都是把GridView直接输出到HtmlTextWriter了,很方便,呵呵。

  回复  引用  查看    
#35楼2008-04-14 11:22 | Solog      
收藏了,为啥首页就必需放很NB的才行呢,对大家有用就行.
LZ的很实用.支持下子.

  回复  引用  查看    
#36楼2008-04-14 11:24 | Solog      
@OneCool
介绍个.

  回复  引用  查看    
#37楼2008-04-14 13:25 | OneCool      
@Solog
gembox 不开源,可下载试用版,好象限150行,50列,网上有破解
aspose.cell 不开源,网上有破解
smartexcel 开源,国产
Koogra 开源



google一下很多的,前面两个还是不错的,可以读写。



  回复  引用  查看    
#38楼2008-04-14 13:29 | 江水滔滔      
一般企業內部用這個比較多.如果是互連網上.那考慮的就要比較多了.以前公司作這個專門做了對比.
其實用xlst +xml也可以做到這樣的效果,不過我是沒有做出來,比較慚愧!
學習了!

  回复  引用  查看    
#39楼2008-04-14 13:35 | 阳光沙滩海岸线      
mark^
有用得着的时候~

  回复  引用  查看    
#40楼2008-04-14 15:27 | sheepchang      
我也来顶下。呵呵。。。
  回复  引用  查看    
#41楼[楼主]2008-04-14 16:03 | peace      
@sheepchang
呵呵 那有几个地方,也是按你提供的方法来的了

  回复  引用  查看    
#42楼2008-04-14 16:07 | BookSir Genius      
--引用--------------------------------------------------
peace: --引用--------------------------------------------------
OneCool: 我测试一下那个例子,第一次正常导出了。再点一次把我的当前打开的EXCEL给杀掉了。不知道为什么,也可能是我操作不当。
--------------------------------------------------------
是的 导出成功后会吧EXCEL.EXE进程都杀掉,释放资源,不然服务器上的任务管理器里有很多的EXCEL.EXE进程,也考虑过只杀掉当前的EXCEL进程,感觉都不怎么成功,干脆都杀掉了,这个在服务器生成,都杀掉影响也不大
--------------------------------------------------------
不觉得这个方法会误杀吗?假设服务器正有人打开EXCEL,正在辛苦地工作着,结果差几行了,把人家的工作成果给杀了,虽然在服务器上用EXCEL的机率不多,但还是存在的。

还有多个用户在导一报表,都会启动一EXCEL进程,统统杀掉,某一用户导完了,其它用户就郁闷了。

我还是不推荐用COM方式来导出EXCEL报表,而且耗资源且还要装个EXCEL。

  回复  引用  查看    
#43楼[楼主]2008-04-14 16:50 | peace      
--引用--------------------------------------------------
BookSir Genius: --引用--------------------------------------------------
peace: --引用--------------------------------------------------
OneCool: 我测试一下那个例子,第一次正常导出了。再点一次把我的当前打开的EXCEL给杀掉了。不知道为什么,也可能是我操作不当。
--------------------------------------------------------
是的 导出成功后会吧EXCEL.EXE进程都杀掉,释放资源,不然服务器上的任务管理器里有很多的EXCEL.EXE进程,也考虑过只杀掉当前的EXCEL进程,感觉都不怎么成功,干脆都杀掉了,这个在服务器生成,都杀掉影响也不大
--------------------------------------------------------
不觉得这个方法会误杀吗?假设服务器正有人打开EXCEL,正在辛苦地工作着,结果差几行了,把人家的工作成果给杀了,虽然在服务器上用EXCEL的机率不多,但还是存在的。

还有多个用户在导一报表,都会启动一EXCEL进程,统统杀掉,某一用户导完了,其它用户就郁闷了。

我还是不推荐用COM方式来导出EXCEL报表,而且耗资源且还要装个EXCEL。
--------------------------------------------------------
这种情况的确是存在的,但是对于我们所为客户做的项目中影响不是很大,可以只杀掉当前进程.但是有时候又不成功

  回复  引用  查看    
#44楼2008-04-14 17:27 | 针式个人知识库管理      
尽可能使用一次性的数据写入,而不是一个一个单元格写,配合COM+使用性能更佳
  回复  引用  查看    
#45楼2008-04-14 19:38 | 马可香蕉      
不错,学习
  回复  引用  查看    
#46楼2008-04-14 21:38 | Agan@CN      
  回复  引用  查看    
#47楼[楼主]2008-04-15 01:23 | peace      
--引用--------------------------------------------------
专研.NET: 期待楼主把源码发出来,学习ing...
--------------------------------------------------------
demo里有个文件打包忘了放了,就是那个DataAccess的CS文件放进去了,数据源自己换了,那就是所有的了

  回复  引用    
#48楼2008-04-15 14:03 | poop[未注册用户]
无条件的支持你一下,楼主!不错^V^
  回复  引用    
#49楼2008-04-22 17:05 | BabyStar[未注册用户]
期待楼主源码...学习学习!!!
  回复  引用    
#50楼2008-04-22 19:54 | esports52[未注册用户]
非常不错!
  回复  引用  查看    
#51楼[楼主]2008-04-22 20:16 | peace      
@BabyStar
源码都在第三个包里面啊,你是说生成复杂格式的那段代码吗,那些按照我说的思路就行了,都可以实现的了

  回复  引用  查看    
#52楼2008-05-16 10:27 | 想爱就去爱吧      
very very good
  回复  引用    
#53楼2008-06-03 21:53 | yfan[未注册用户]
如何下载demo?
  回复  引用  查看    
#54楼[楼主]2008-06-05 00:52 | peace      
  回复  引用  查看    
#55楼2008-09-01 15:36 | 清云星空      
你好,下载demo失败,是链接做了改动了吗?谢谢!
  回复  引用  查看    
#56楼[楼主]2008-09-01 19:01 | peace      
--引用--------------------------------------------------
清云星空: 你好,下载demo失败,是链接做了改动了吗?谢谢!
--------------------------------------------------------
下载的链接服务器罢工了,一会我放到博客园上好了

  回复  引用  查看    
#58楼[楼主]2008-09-01 19:08 | peace      
  回复  引用    
#59楼2008-09-01 21:45 | 清云[未注册用户]
--引用--------------------------------------------------
peace: --引用--------------------------------------------------
清云星空: 你好,下载demo失败,是链接做了改动了吗?谢谢!
--------------------------------------------------------
下载的链接服务器罢工了,一会我放到博客园上好了
--------------------------------------------------------
楼主有新地址了吗,如果放了麻烦给个链接,谢谢!

  回复  引用    
#60楼2008-10-11 17:22 | kakachao[未注册用户]
为什么我导出的时候数字是很长一串,比如说身份证号码,但是在EXcel里面显示的不正常,我设置了单位格式后最后号码的最后几个数字却变成了000
我用的ASP.net SQL2005数据库!

  回复  引用  查看    
#61楼[楼主]2008-10-12 10:48 | peace      
如果你是用的模板的话,把宽度调大一点,另外如果有些特殊的数据(如001可能只显示1)要想原样输出,可以在赋值的时候加上单引号,如a = "'"+b(假设b为001),这是其中的一种处理方法,还有其它的可以设置下模板什么的
  回复  引用    
#62楼2008-10-24 09:41 | 有名小菜[未注册用户]
厉害
受益匪浅

小弟刚接触这块

  回复  引用  查看    
#63楼2008-11-05 09:42 | yatasoft      
特来顶你!!
  回复  引用    
#64楼2008-11-18 11:28 | rain86914[未注册用户]
这个方法在 64 位下好像不能用吧?那么请问在 64 下想要是实现同样的效果该怎么办呢?望不吝赐教
  回复  引用  查看    
#65楼[楼主]2008-11-18 11:44 | peace      
应该是可以的 下载的DEMO能运行的话应该是没问题的 报什么错?操作EXCEL对象如果是服务器的话要在DCOM里配权限的 下面这个链接对于访问拒绝或权限说得比比较清楚,也给了解决方法
http://blog.csdn.net/wzhibin/archive/2007/10/09/1816690.aspx" target="_new">http://blog.csdn.net/wzhibin/archive/2007/10/09/1816690.aspx

  回复  引用    
#66楼2008-11-20 21:31 | 天上一乌[未注册用户]
楼主,是不是只能在服务器上面运行,我在客户端运行例子的时候最后报“文件读写出错,请检查文件模板是否存在或对文件是否有读写权限”
  回复  引用  查看    
#67楼[楼主]2008-11-21 23:03 | peace      
本地也是可以的 一般本地也不需要配什么权限的
  回复  引用  查看    
#68楼2008-12-03 15:03 | 程序缘      
赞叹师兄,功德无量。
  回复  引用  查看    
#69楼[楼主]2008-12-03 15:10 | peace      
--引用--------------------------------------------------
程序缘: 赞叹师兄,功德无量。
--------------------------------------------------------
不敢当:)

  回复  引用    
#70楼2008-12-09 13:21 | Levi[未注册用户]
你好,我现在在新的网页中打开导出的Excel,但是一直不知道怎么修改?我自己修该总是提示没有权限:就是window.location报错!期待你的回答!谢谢
  回复  引用  查看    
#71楼[楼主]2008-12-09 18:29 | peace      
在新的网页中打开?意思是不提示下载框吗?如果是用的我的demo的话,要不弹出提示框,需要把生成的文件保存到虚拟目录下才行,然后把整个路径返回,就可以了,如果确实有权限什么问题的 参考下http://blog.csdn.net/wzhibin/archive/2007/10/09/1816690.aspx" target="_new">http://blog.csdn.net/wzhibin/archive/2007/10/09/1816690.aspx
  回复  引用    
#72楼2008-12-16 23:26 | 皮皮08[未注册用户]
服务器端用excel自动化对象非常不可取,效率低、占用资源高、进程杀不干净,稍微多几个客户访问或者报表操作频繁几天未重启服务器的话,很容易把服务器搞崩溃!!!!!

建议用aspose.cells组件,好不好自己用过、对比过才知道,性能、速度都好上几个数量级

  回复  引用  查看    
#73楼[楼主]2008-12-17 08:52 | peace      
@皮皮08
谢谢!
我试用过Aspose.word组件,aspose.cells是很好的,再做类似的话,我会选择Aspose

  回复  引用  查看    
#74楼2009-02-23 14:37 | .....      
错误:
文件读写出错,请检查文件模板是否存在或对文件是否有读写权限
================================
请教楼主还要配置什么东西
<add key="path" value="http://localhost/ExcelReport/xls_files/"/>" target="_new">http://localhost/ExcelReport/xls_files/"/>
已经改成我电脑上的端口了

  回复  引用    
#76楼2009-03-19 15:06 | 清風[未注册用户]
請問哈:
這里的excel表樣式設計中的excelOperate 對象是哪來的呢?

  回复  引用    
#77楼2009-03-19 15:17 | 清風[未注册用户]
樓主,我看到你的源碼了,所以我之前提的問題解決了,謝謝
  回复  引用    
#78楼2009-03-28 08:46 | ssibre[未注册用户]
对你的崇拜,就像滔滔江水连绵不绝。
  回复  引用    
#79楼2009-03-29 11:38 | peace未登陆[未注册用户]
--引用--------------------------------------------------
ssibre: 对你的崇拜,就像滔滔江水连绵不绝。
--------------------------------------------------------
惭愧~

  回复  引用    
#80楼2009-05-20 14:39 | taiks[未注册用户]
你好!有沒有辦法加上備注呢!!
謝謝!!

  回复  引用  查看    
#81楼2009-06-05 10:11 | FredChen      
谢谢分享。



发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 1151520




相关文章:

相关链接: