解决高亮度显示内容问题(onFocus)
<script language="JScript">
function MyFocus(obj)
{
obj.value += '';
var rng = obj.createTextRange();
rng.moveStart("character", obj.value.length);
rng.collapse(true);
rng.select();
}
</script>
<input>
<input name=test value="abcdfdf" onfocus="MyFocus(this)">
posted @
2006-12-19 15:12 Stellar.He 阅读(184) |
评论 (0) |
编辑 收藏
Javascript调用:
function openFile() {
var openDocObj = new ActiveXObject("SharePoint.OpenDocuments.1");
}
1)在web上编集,创建office文档的前题
1 Tomcat
步骤A,B
A 在web.xml文档中加入如下等代码
<mime-mapping>
<extension>xls</extension>
<mime-type>application/vnd.ms-excel</mime-type>
</mime-mapping>
<mime-mapping>
<extension>doc</extension>
<mime-type>application/msword</mime-type>
</mime-mapping>
说明:但excel却不是从IE里打开的,这和IE的设置有关.
B.在D:\Tomcat42\webapps\webdav\WEB-INF\web.xml将注释放开
<init-param>
<param-name>readonly</param-name>
<param-value>false</param-value>
</init-param>
说明:所要修改的文件必须放在webdav这个目录下面.并具要有修改的权限.
这存在数据安全的问题.
posted @
2006-12-19 15:06 Stellar.He 阅读(1132) |
评论 (1) |
编辑 收藏
问题一, 如果你家附近有一家餐厅,东西又贵又难吃,桌上还爬着蟑螂,你会因为它很近 很方便,就一而再、再而三地光临吗? 回答:你一定会说,这是什么烂问题,谁那么笨,花钱买罪受?可同样的情况换个场合,自己或许就做类似的蠢事。不少男女都曾经抱怨过他们的情人或配偶品性不端,三心二意,不负责任。明知
在一起没 什么好的结果,怨恨已经比爱还多,但却“不知道为什么”还是要和他搅和下去,分不了 手。说穿了,只是为了不甘,为了习惯,这不也和光临餐厅一样?
——做人,为什么要过于执著?!
问题二, 如果你不小心丢掉100块钱,只知道它好像丢在某个你走过的地方,你会花200块 钱的车费去把那100块找回来吗? 回答:一个超级愚蠢的问题。
可是,相似的事情却在人生中不断发生。做错了一件事,明知自己有问题,却*也不肯认 错,反而花加倍的时间来找藉口,让别人对自己的印象大打折扣。被人骂了一句话,却花了无数时间难过,道理相同。为一件事情发火,不惜损人不利已,不惜血本,不惜时间, 只为报复,不也一样无聊? 失去一个人的感情,明知一切已无法挽回,却还是那么伤心,而且一伤心就是好几年,还要借酒浇愁,形销骨立。其实这样一点用也没有,只是损失更多。
——做人,干吗为难自己?!
问题三, 你会因为打开报纸发现每天都有车祸,就不敢出门吗? 回答:这是个什么烂问题?当然不会,那叫因噎废食。
然而,有不少人却曾说:现在的离婚率那么高,让我都不敢谈恋爱了。说得还挺理所当然 。也有不少女人看到有关的诸多报道,就对自己的另一半忧心忡忡,这不也是类似的反应?所谓乐观,就是得相信:虽然道路多艰险,我还是那个会平安过马路的人,只要我小心 一点,不必害怕过马路。
——做人,先要相信自己。
问题四, 你相信每个人随便都可以成功立业吗? 回答:当然不会相信。
但据观察,有人总是在听完成功人士绞尽脑汁的建议,比如说,多读书,多练习之后,问 了另一个问题?那不是很难? 我们都想在3分钟内学好英文,在5分钟内解决所有难题,难道成功是那么容易的吗?改变当然是难的。成功只因不怕困难,所以才能出类拔萃。 有一次坐在出租车上,听见司机看到自己前后都是高档车,兀自感叹:“唉,为什么别人 那么有钱,我的钱这么难赚?” 我心血来潮,问他:“你认为世上有什么钱是好赚的?”他答不出来,过了半晌才说:好 像都是别人的钱比较好赚。 其实任何一个成功者都是艰辛取得。我们实在不该抱怨命运。
——做人,依靠自己!
问题五, 你认为完全没有打过篮球的人,可以当很好的篮球教练吗? 回答:当然不可能,外行不可能领导内行。
可是,有许多人,对某个行业完全不了解,只听到那个行业好**,就马上开起业来了。 我看过对穿着没有任何口味、或根本不在乎穿着的人,梦想却是开间服装店;不知道电脑怎么开机的人,却想在网上**,结果道听途说,却不反省自己是否专业能力不足,只抱 怨时不我与。
——做人,量力而行。
问题六, 相似但不相同的问题:你是否认为,篮球教练不上篮球场,闭着眼睛也可以主导 一场完美的胜利? 回答:有病啊,当然是不可能的。
可是却有不少朋友,自己没有时间打理,却拼命投资去开咖啡馆,开餐厅,开自己根本不 懂的公司,火烧屁股一样急着把辛苦积攒的积蓄花掉,去当一个稀里糊涂的投资人。亏的总是比赚的多,却觉得自己是因为运气不好,而不是想法出了问题。
——做人,记得反省自己。
问题七,] 你宁可永远后悔,也不愿意试一试自己能否转败为胜? 解答:恐怕没有人会说:“对,我就是这样的孬种”吧。
然而,我们却常常在不该打退堂鼓时拼命打退堂鼓,为了恐惧失败而不敢尝试成功。 以关颖珊赢得2000年世界花样滑冰冠军时的精彩表现为例:她一心想赢得第一名,然而在最后一场比赛前,她的总积分只排名第三位,在最后的自选曲项目上,她选择了突破,而 不是少出错。在4分钟的长曲中,结合了最高难度的三周跳,并且还大胆地连跳了两次。她也可能会败得很难看,但是她毕竟成功了。 她说:“因为我不想等到失败,才后悔自己还有潜力没发挥。” 一个中国伟人曾说;胜利的希望和有利情况的恢复,往往产生于再坚持一下的努力之中。
——做人,何妨放手一搏。
问题八, 你的时间无限,长生不老,所以最想做的事,应该无限延期? 回答:不,傻瓜才会这样认为。
然而我们却常说,等我老了,要去环游世界;等我退休,就要去做想做的事情;等孩子长 大了,我就可以…… 我们都以为自己有无限的时间与精力。其实我们可以一步一步实现理想,不必在等待中徒耗生命。如果现在就能一步一步努力接近,我们就不会活了半生,却出现自己最不想看到 的结局。
——做人,要活在当下。
posted @
2006-12-19 15:02 Stellar.He 阅读(155) |
评论 (0) |
编辑 收藏
oracle和sqlserver互訪
by HuiYi_Love from ITPUB
oracle和sqlserver互訪!
前几天由於工作的原因查找了oracle中查找sqlserver數据的資料,現測試成功,整理一下貼出!
要求:從Oracle中能取SqlServer的數据
環境:
OracleDb: Linux + Oracle9i Enterprise Edition Release 9.0.1.1.1 - Production IP:192.168.1.52(TOPPROD)
MSQLDB: Windows2000 + SqlServer2000 IP:192.168.1.50(ERPSQL),測試用戶:sa/pass 測試數据表:EK.ACPTA
网關: WindowsXp + Oracle9i Enterprise Edition Release 9.0.1.1.1 - Production IP:192.168.1.221(S0504027),因為暫在測試階段,所以网關裝在我用的机器,网關可以裝在MSQLDB上
1.通過ODBC通用方式聯接
代码:
// A. 安裝HS部件
// 默認情況下HS部件是安裝的,查詢視圖 SYS.HS_BASE_CAPS 可得出有沒有安裝此部件!
// B. 配置ODBC
// 在"系統DNS"中配置"ODBC FOR SQLSERVER",例如:[ERPSQL]
// C. 配置TNSNAMES.ORA,路徑:ORACLE_HOME\NETWORK\ADMIN,這一步應該在ORACLEDB(192.168.1.52)上配置!
Lnk2sql = # tnsName
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.1.221)(PORT = 1521)) # 网關IP
)
(CONNECT_DATA =
(SID = hs4sql) #SID,要和監听器裡的SID一致!
)
(HS=OK)
// D. 配置listener.ora,路徑:ORACLE_HOME\NETWORK\ADMIN
LISTENER =
(DESCRIPTION_LIST =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC0))
)
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.1.221)(PORT = 1521))
)
)
)
SID_LIST_LISTENER =
(SID_LIST =
(SID_DESC = # 這一段為加入的
(SID_NAME = hs4sql)
(ORACLE_HOME = D:oracleora9i)
(PROGRAM = hsodbc) # 要使用的HS服務程序.
)
)
// E. 重啟監听器服務
// F. 編輯ORACLE_HOME\HS\ADMIN內init.ora,這裡是iniths4sql.ora,因為上面的SID=hs4sql
HS_FDS_CONNECT_INFO = ERPSQL # B中設置的ODBC名稱
HS_FDS_TRACE_LEVEL = 0
// G. 創建DB LINK,以及測試
SQL> create database link ora2sql connect to "sa" identified by "pass" using 'Lnk2sql';
Database link created
SQL> select ta001,ta002 from acpta@ora2sql where rownum<5;
TA001 TA002
----- -----------
S710 20020306001
S710 20020315001
S710 20020325001
S710 20020326001
------------
2.通過"透明网關"方式聯接
代码:
// A. 安裝透明网關,在安裝時選擇自定義安裝,安裝TRANSPARENT GATEWAY FOR SQLSERVER 組件,安裝成功後會產生oracle_homeora90\tg4msql目錄!
// B. 配置TNSNAMES.ORA,路徑:ORACLE_HOME\NETWORK\ADMIN,這一步應該在ORACLEDB(192.168.1.52)上配置!
TG4MSQL = # tnsName
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.1.221)(PORT = 1521)) # 网關IP
)
(CONNECT_DATA =
(SID = tg4msql ) #SID,要和監听器裡的SID一致!
)
(HS=OK)
)
// C. 配置listener.ora,路徑:ORACLE_HOME\NETWORK\ADMIN
LISTENER =
(DESCRIPTION_LIST =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC0))
)
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.1.221)(PORT = 1521))
)
)
)
SID_LIST_LISTENER =
(SID_LIST =
(SID_DESC =
(GLOBAL_DBNAME = tg4msql)
(SID_NAME = tg4msql)
(ORACLE_HOME = D:oracleora9i)
(PROGRAM= tg4msql)
)
)
// D. 重啟監听器服務
// E. 編輯ORACLE_HOME\TG4MSQL\ADMIN內init.ora,這裡是inittg4msql.ora,因為上面的SID=tg4msql
#HS_FDS_CONNECT_INFO="SERVER=ERPSQL;DATABASE=EK",好多人說用這行可以,我用這行的時候出現了不能打開鏈接的錯誤,改下面一行就沒問題了!
HS_FDS_CONNECT_INFO=ERPSQL.EK
HS_FDS_TRACE_LEVEL=OFF
HS_FDS_RECOVERY_ACCOUNT=RECOVER
HS_FDS_RECOVERY_PWD=RECOVER
// F. 創建DB LINK,以及測試
SQL> create database link msql2 connect to "sa" identified by "pass" using 'TG4MSQL';
Database link created
SQL> select ta001,ta002 from acpta@msql2 where rownum<5;
TA001 TA002
----- -----------
S710 20020306001
S710 20020315001
S710 20020325001
S710 20020326001
--------
代码:
-- 不知什么原因,感覺"通用方式"比"透明网關速度快一點"
SQL> set timing on
SQL> select ta001,ta002 from acpta@ora2sql where rownum<10;
TA001 TA002
----- -----------
S710 20020306001
S710 20020315001
S710 20020325001
S710 20020326001
S710 20020328001
S710 20020329001
S710 20020419001
S710 20020422001
S710 20020425001
9 rows selected
Executed in 0.047 seconds
SQL> select ta001,ta002 from acpta@msql2 where rownum<10;
TA001 TA002
----- -----------
S710 20020306001
S710 20020315001
S710 20020325001
S710 20020326001
S710 20020328001
S710 20020329001
S710 20020419001
S710 20020422001
S710 20020425001
9 rows selected
Executed in 52.281 seconds
--------
3.SQLSERVER訪問ORACLE
環境:windowsxp + sqlserver2000 + Oracle9i Enterprise Edition Release 9.0.1.1.1 - Production IP:192.168.1.221
代码:
// A. 添加ODBC,OdbcName=DB,OracleSid=DB
// B. 執行
sp_addlinkedserver 'LIORA', 'Oracle', 'MSDAORA', 'DB'
GO
EXEC sp_addlinkedsrvlogin @rmtsrvname='LIORA',@useself='false',@locallogin='sa',@rmtuser='SYSTEM',@rmtpassword='MANAGER'
select top 10 topic,info from LIORA..SYSTEM.HELP
topic info
-------------------------------------------------- --------------------------------------------------------------------------------
@ NULL
@ @ ("at" sign)
@ -------------
@ NULL
@ Runs the SQL*Plus statements in the specified command file. The command
@ file can be called from the local file system or from a web server.
@ NULL
@ @ {uri|file_name[.ext]} [arg...]
@ NULL
@ where uri supports HTTP, FTP and gopher protocols in the form:
(影響 10 個資料列)
原文引用 :
你可以使用这个链接引用该篇文章 http://publishblog.blogchina.com/blog/tb.b?diaryID=5059542
posted @
2006-12-19 10:41 Stellar.He 阅读(409) |
评论 (0) |
编辑 收藏
//开始 15位到18位的身份证号转换
//身份证号码由十七位数字本体码和一位校验码组成,排列顺序从左至右依次为:
//六位数地址码、八位数字的出生日期码、三位数字的顺序码和一位数字的校验码
public String change18ID(String ID15){
String ID18="";
if(ID15.length()==18){
ID18=ID15.toUpperCase();
}
if(ID15.length()==15){
int[] w={7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2,1};
char[] A={'1','0','X','9','8','7','6','5','4','3','2'};
String ID17=ID15.substring(0,6)+"19"+ID15.substring(6,15);
int[] ID17Array;
ID17Array=new int[17];
for(int i=0;i<17;i++){
ID17Array[i]=Integer.parseInt(ID17.substring(i,i+1));
}
int s=0;
for(int i=0;i<17;i++){
s=s+ID17Array[i]*w[i];
}
s=s%11;
ID18=ID17+A[s];
}
return ID18.trim();
}
//结束 15位到18位的身份证号转换
public String change15ID(String ID18){
String ID15="";
if(ID18.length()==15) ID15=ID18;
if(ID18.length()==18){
ID15=ID18.substring(0,6)+ID18.substring(8,17);
}
return ID15.trim();
}
posted @
2006-12-13 16:03 Stellar.He 阅读(2185) |
评论 (2) |
编辑 收藏
<script language="Javascript">
function KeyDown(){
//alert(event.keyCode);
if ((event.keyCode==116)|| //屏蔽 F5 刷新键
(event.ctrlKey && event.keyCode==82)){ //Ctrl + R
event.keyCode=0;
event.returnValue=false;
}
}
document.onkeydown=KeyDown;
</script>
posted @
2006-08-08 10:33 Stellar.He 阅读(739) |
评论 (0) |
编辑 收藏
String HanDigiStr[] = new String[]{"零","壹","贰","叁","肆","伍","陆","柒","捌","玖"};
String HanDiviStr[] = new String[]{"","拾","佰","仟","万","拾","佰","仟","亿",
"拾","佰","仟","万","拾","佰","仟","亿",
"拾","佰","仟","万","拾","佰","仟" };
/**
* 输入字符串必须正整数,只允许前导空格(必须右对齐),不宜有前导零
* @param NumStr
* @return
* */
String PositiveIntegerToHanStr(String NumStr)
{
String RMBStr = "";
boolean lastzero = false;
boolean hasvalue= false; // 亿、万进位前有数值标记
int len,n;
len = NumStr.length();
if( len > 15 ) return "数值过大!";
for(int i=len-1;i>=0;i--) {
if( NumStr.charAt(len-i-1)==' ' ) continue;
n = NumStr.charAt(len-i-1) - '0';
if( n<0 || n>9 ) return "输入含非数字字符!";
if( n!=0 ) {
if( lastzero ) RMBStr += HanDigiStr[0]; // 若干零后若跟非零值,只显示一个零
// 除了亿万前的零不带到后面
//if( !( n==1 && (i%4)==1 && (lastzero || i==len-1) ) ) // 如十进位前有零也不发壹音用此行
if( !( n==1 && (i%4)==1 && i==len-1 ) ) // 十进位处于第一位不发壹音
RMBStr += HanDigiStr[n];
RMBStr += HanDiviStr[i]; // 非零值后加进位,个位为空
hasvalue = true; // 置万进位前有值标记
}else {
if( (i%8)==0 || ((i%8)==4 && hasvalue) ) // 亿万之间必须有非零值方显示万
RMBStr += HanDiviStr[i]; // “亿”或“万”
}
if( i%8==0 ) hasvalue = false ; // 万进位前有值标记逢亿复位
lastzero = (n==0) && (i%4!=0);
}
if( RMBStr.length()==0 ) return HanDigiStr[0]; // 输入空字符或"0",返回"零"
return RMBStr;
}
/**
*
* @param val
* @return
* */
public String NumToRMBStr(double val)
{
String SignStr ="" ;
String TailStr ="";
long fraction, integer;
int jiao,fen;
if( val<0 ) {
val = -val;
SignStr = "负";
}
if(val > 99999999999999.999 || val <-99999999999999.999 ) return "数值位数过大!";
// 四舍五入到分
long temp = Math.round(val*100);
integer = temp/100;
fraction = temp%100;
jiao = (int)fraction/10;
fen = (int)fraction%10;
if( jiao==0 && fen==0 ) {
TailStr = "整";
}
else {
TailStr = HanDigiStr[jiao];
if( jiao!=0 )
TailStr += "角";
if( integer==0 && jiao==0 ) // 零元后不写零几分
TailStr = "";
if( fen!=0 )
TailStr += HanDigiStr[fen] + "分";
}
// 下一行可用于非正规金融场合,0.03只显示“叁分”而不是“零元叁分”
// if( !integer ) return SignStr+TailStr;
return SignStr+PositiveIntegerToHanStr(String.valueOf(integer) )+"元"+TailStr;
}
posted @
2006-08-01 11:37 Stellar.He 阅读(562) |
评论 (1) |
编辑 收藏
作者:wldandan 来源:www.matrix.org.cn 发布时间:2006-02-22 17:55:09.217
最近刚做完一个项目,用Struts1.1做的。从不懂,到熟练使用,都靠参考CSDN的一些文档。但是文章上讲的并不一定适合自己,所以我把我自己做的一些东西拿上来给大家看看,互相交流一下。如果您有跟好的方法,可以和我联系。
MSN:whw_dream (AT) hotmail.com
Struts的文件上传
本文用的是Struts1.1的org.apache.struts.upload.FormFile类。很方便,不用自己写。也不用写一个jsp调用jspsmartupload就可以搞定。
选择上传文件页面:selfile.jsp
--------------------------------------------------------------------------------
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html"%>
<html:html>
<html:form action="/uploadsAction.do" enctype="multipart/form-data">
<html:file property="theFile"/>
<html:submit/>
</html:form>
</html:html>
--------------------------------------------------------------------------------
UpLoadAction.java
--------------------------------------------------------------------------------
import java.io.*;
import javax.servlet.http.*;
import org.apache.struts.action.*;
import org.apache.struts.upload.FormFile;
/**
* <p>Title:UpLoadAction</p>
* <p>Description: QRRSMMS </p>
* <p>Copyright: Copyright (c) 2004 jiahansoft</p>
* <p>Company: jiahansoft</p>
* @author wanghw
* @version 1.0
*/
public class UpLoadAction extends Action {
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
if (form instanceof uploadsForm) {//如果form是uploadsForm
String encoding = request.getCharacterEncoding();
if ((encoding != null) && (encoding.equalsIgnoreCase("utf-8")))
{
response.setContentType("text/html; charset=gb2312");//如果没有指定编码,编码格式为gb2312
}
UpLoadForm theForm = (UpLoadForm ) form;
FormFile file = theForm.getTheFile();//取得上传的文件
try {
InputStream stream = file.getInputStream();//把文件读入
String filePath = request.getRealPath("/");//取当前系统路径
// filePath = request.getRealPath(request.getRequestURI()); //取当前系统路径
ByteArrayOutputStream baos = new ByteArrayOutputStream();
OutputStream bos = new FileOutputStream(filePath + "/" +
file.getFileName());//建立一个上传文件的输出流
//System.out.println(filePath+"/"+file.getFileName());
int bytesRead = 0;
byte[] buffer = new byte[8192];
while ( (bytesRead = stream.read(buffer, 0, 8192)) != -1) {
bos.write(buffer, 0, bytesRead);//将文件写入服务器
}
bos.close();
stream.close();
}catch(Exception e){
System.err.print(e);
}
//request.setAttribute("dat",file.getFileName());
return mapping.findForward("display");
}
return null;
}
}
--------------------------------------------------------------------------------
UpLoadForm.java
--------------------------------------------------------------------------------
import javax.servlet.http.HttpServletRequest;
import org.apache.struts.action.*;
import org.apache.struts.upload.*;
/**
* <p>Title:UpLoadForm</p>
* <p>Description: QRRSMMS </p>
* <p>Copyright: Copyright (c) 2004 jiahansoft</p>
* <p>Company: jiahansoft</p>
* @author wanghw
* @version 1.0
*/
public class UpLoadForm extends ActionForm {
public static final String ERROR_PROPERTY_MAX_LENGTH_EXCEEDED = "org.apache.struts.webapp.upload.MaxLengthExceeded";
protected FormFile theFile;
public FormFile getTheFile() {
return theFile;
}
public void setTheFile(FormFile theFile) {
this.theFile = theFile;
}
public ActionErrors validate(ActionMapping mapping, HttpServletRequest request)
{
ActionErrors errors = null;
//has the maximum length been exceeded?
Boolean maxLengthExceeded = (Boolean)
request.getAttribute(MultipartRequestHandler.ATTRIBUTE_MAX_LENGTH_EXCEEDED);
if ((maxLengthExceeded != null) && (maxLengthExceeded.booleanValue()))
{
errors = new ActionErrors();
errors.add(ERROR_PROPERTY_MAX_LENGTH_EXCEEDED, new ActionError("maxLengthExceeded"));
}
return errors;
}
}
//这是相对应的form,还有其他属性可以设置,具体可以参考struts的上传例子。
--------------------------------------------------------------------------------
struts-config.xml
--------------------------------------------------------------------------------
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
<struts-config>
<form-beans>
<form-bean name="uploadsForm" type="UpLoadForm" />
</form-beans>
<action-mappings>
<action name="uploadsForm" type="UpLoadAction" path="/uploadsAction">
<forward name="display" path="/display.jsp" />
</action>
</action-mappings>
</struts-config>
<!--display.jsp就是随便写一个成功页-->
posted @
2006-08-01 11:17 Stellar.He 阅读(263) |
评论 (1) |
编辑 收藏
// 全选,通过复选框操作
//checkid列表复选框name
<input type="checkbox" name="checkall" value="1" onclick="CheckAll()">
<input type="checkbox" name="checkid" class="checkbox1" value="">
function CheckAll()
{
for (var i=0;i<document.getElementsByName("checkid").length;i++)
{
var e = document.getElementsByName("checkid")[i];
if (e.name != "checkall") {
e.checked = document.all.checkall.checked;
}
}
}
posted @
2006-05-17 14:37 Stellar.He 阅读(186) |
评论 (0) |
编辑 收藏