希望广大cnblogs的朋友多多支持
我在CSDN的BLOG地址请点这里 --->>> http://blog.csdn.net/adamxx
posted @ 2007-04-07 16:58 adamxx 阅读(248) 评论(0) 编辑
The Stars ...My Destination
posts - 48, comments - 39, trackbacks - 10, articles - 1 |
||
|
累了一天了,终于把blog从csdn搬到这里了,有一部分是RSS导入过来的,可能暂时只有摘要,我会copy过来的.
希望广大cnblogs的朋友多多支持 我在CSDN的BLOG地址请点这里 --->>> http://blog.csdn.net/adamxx posted @ 2007-04-07 16:58 adamxx 阅读(248) 评论(0) 编辑 Adobe Apollo 初步研究这几天在一家公司实习,上班不安逸,还是在学校好玩,才去的时候老板叫我学一下Flex,昨天又叫我去关注一下Apollo.正在郁闷,又学这个又学那个的.当我了解到Apollo技术的时候,就被他深深的吸引了.下面是网上对Apollo的一些评论,然后我会讲解一个使用apollo做一个简单记事本的例子.
时下,关于Apollo的消息被炒得沸沸扬扬。其实结合部分国内外blogger的探讨,我认为,Apollo的革命并非技术上的,而是商业上的。Apollo的技术封装让现有的技术得到了极大的保留,设计师和开发工程师们可以方便地将自己的代码平移到新的平台上,并且发布、部署的方式比之前的任何一款开发工具都方便得多。 Apollo带来的革命,号称“Desktop2.0”。其内容从Flash, HTML/CSS/JS, 到PDF,几乎涵盖了时下最“流行”的Web内容载体。此外,“可离线”应用模式能让用户更加安全、舒适的进行工作和娱乐。用户们不必再抱怨因网络故障而造成的信息损失了,而且也可以借助本地资源更好地节省带宽和其他“紧张而充满麻烦”的网络资源。 把Web2.0应用搬回家,估计会让Apollo的Start-up们又有不少copy case可以做了。从商业的角度来讲,Apollo创造的价值远胜于给用户带来用户体验上的提升。对于终端用户来说,他们想要获得一份Web-Desktop应用,都要经过下载、安装、建立连接的过程,他们不会关心你的程序是由什么开发的。站在产品的角度上,只要能进入桌面,文件系统的支持以及本地各种服务、应用的支持都不是问题,更有许多成熟的框架和接口能让UI层直接调用GPU指令来构建绚丽的应用。然而,Apollo应用却让以往只属于.net/Java/C++程序员和企业能达到的目标对于一名普通的WebDeveloper或者一家小型的网站来说也“易如反掌”!多年以来旁人对WebDeveloper比.net/Java/C++程序员差的舆论必然会得到缓解?抛开一些“浮躁”、“效率不济”等Web程序员和Web产品的“通病”不谈,Web应用的一大优势是:其产品往往是各类软件产品中面对用户群最多、最广,变动频率最高的产品。优秀的Web产品的用户体验必然是经得住考验的,尤其是此类产品往往都拥有较高的“用户体验开发效率”,即单位时间工作成本增量所带来的用户体验提升会更高。 Apollo的价值在于,大大地提高了产品Designer和Developer的“开发体验”,从战略角度来讲,Apollo应用将会有更广大的开发者基础。 对于采用Apollo应用程序部署产品线的企业而言,桌面应用程序更是避免了Marketing中的“Clutter”问题:一部分通过习惯使用搜索引擎来寻找服务的用户不会再被搜索引擎“领到”一个“满是您竞争对手的结果列表页”了,这样您的用户发现您的同行业竞争对手的机会就会大大降低。 除了不如直接在页面中打开方便、下载后的安全隐患之外,Apollo也有很多其他的争议。其中最“有趣的”的无外乎,有人认为Apollo并非直接与微软为敌,而是与Firefox3竞争……
以上信息来自于Apollo开发的商业前景及革命(上):http://www.awflasher.com/blog/archives/834 到底什么是Apollo呢? 我迫不及待的download了关于Apollo 开发的一系列资源,其中包括: apollo runtime, apollo_win_alpha1_031907.msi apollo sdk, apollo_sdk_alpha1_031907.zip flex extensions for apollo, fb_apollo_extensions_win_alpha1_031907.exe 这3个是最重要的吧,其实sdk可以不用,但我做了一个记事本的例子用到了他. 当然Flex Builder 2也是需要的,当然你也可以选择eclipse,但我觉得还要配置,麻烦死了. 所有东西都可以从adobe官网上免费下载,现在版本是alpha1. 当你安装了fb_apollo_extensions_win_alpha1_031907.exe后,就可以在FB2上开发apollo应用程序了,我做了一个简单的记事本程序,给大家瞧瞧. 在fb2中新建一个apollo project就可以开始了.整个界面用mxml来搭建,是不是很爽,这用我们可以做出很漂亮很漂亮的界面来. 记事本的编辑框我用的<mx:TextArea>这个东西直接支持右键菜单(复制粘贴剪切删除),而apollo到目前这个版本并没有给出menu和toolbar之类的组件,不过在desktop2.0可以不需要这些东西了吧. 现在的关键问题是我们如何打开和保存一个文件,幸运的是在这个版本中,adobe已经给出了file io api,在adobe提供的Apollo for Adobe Flex Developers一书中第4章做了较详细的介绍.我也简单说一下,如果想做更多的了解请参见: http://www.cnblogs.com/adamxx/archive/2007/04/07/703627.html
我们需要声明一个File对象,他将引用一个文件,这和java,c#的方式很相似
![]() private function readText(name:String):String { file = File.appResourceDirectory; file = file.resolve(name); var stream:FileStream = new FileStream(); stream.open(file,FileMode.READ); var txt:String = stream.readUTFBytes(stream.bytesAvailable); stream.close(); return txt; }
这个函数的作用是从文本文件中读出文本,参数name是文本文件的路径.file的声明是 var file:File = File.appResourceDirectory; File.appResourceDirectory引用了一个默认的目录,这是在该工程的bin目录下.如果在resolve中只给出了文件名,那么他将在这个默认的目录下查找该文件.其中还包括了:
File.appResourceDirectory : application被安装到的目录 然后,我们在初始化了一个FileStream对象, FileStream打开文件. stream.open(file,FileMode.READ); 第2个参数说明了打开方式,其中包括: FileMode.APPEND 只写模式,所有被写的数据都会附加到文件的最后 Upon opening, any nonexistent file is created. 于是调用stream.readUTFBytes可以按UTF-8编码读取文件. 最后stream.close(); 这个没什么好说的,和java,c#都一个样子. 同理还需要一个写文件的函数 ![]() private function writeText(name:String,txt:String):void { file = file.resolve(name); var stream:FileStream = new FileStream(); stream.open(file,FileMode.WRITE); stream.writeUTFBytes(txt); stream.close(); }现在我们来用这2个函数来做一个简单的记事本. 这里有一个问题,我们需要一个对话框,用来保存和打开文件.在apollo sdk中提供了这样的组件,打开apollo_sdk_alpha1_031907.zip\samples\Apollo Components,其下包括 DirectorySelectionPanel.mxml FileOpenPanel.mxml FileSavePanel.mxml 把后面2个添加到我们工程中(当然也可以直接粘代码),或许你需要做一写修改,比如把一下英文名字该成中文的,不过在apollo中显示中文效果不大理想.另外,还需要一写更改 [Bindable] 这段代码可以在FileOpenPanel.mxml中找到,作用是一个Filter,除了有所有文件的选择外,就只能打开.mxml和.as,下面是我修改后的 [Bindable] 好了,我们应该怎么使用这2个组件呢.他们都包裹一个静态的函数show,原形如下 public static function show(directory:File = null):FileOpenPanel 看起来有点象Alert.但是我们需要他返回一个文件的地址,应该怎么做呢? ![]() private function OnClickOpen():void {![]() var openDialog:FileOpenPanel = FileOpenPanel.show();![]() openDialog.addEventListener(FileEvent.SELECT, fileOpenPanel_selectHandler);![]() }![]()
当触发select事件后(就是选中文件点确定),将交给fileOpenPanel_selectHandler函数处理,这和java一模一样. ![]() private function fileOpenPanel_selectHandler(event:FileEvent):void {![]() this.path = event.file.nativePath;![]() this.title = event.file.name;![]() this.txtArea.text = readText(event.file.nativePath);![]() }![]()
整个程序的关键部分就这些了,save和saveas都和这个差不多. 原代码下载: http://files.cnblogs.com/adamxx/flex0704071150_First%20Apollo.zip posted @ 2007-04-07 11:48 adamxx 阅读(661) 评论(2) 编辑 Apollo的file I/O API这是翻译的Apollo for Adobe Flex Developer一书第4章Usingthe File System API 原文地址:http://www.nshen.net/blog/article.asp?id=480 简译自某本书的第4章样章,翻译不准确的请指正 : Apollo的file I/O API允许在用户的电脑上读写文件或文件夹。 file I/O API 包含以下功能: 1 . 创建、删除文件或文件夹 2 . 复制、移动文件或文件夹 3 . 列出文件夹的内容 4 . 取得文件或文件夹的系统信息 5 . 读写二进制文件 6 . 读写文本文件 7 . Serialize and deseialize ActionScript objects 安全模型 Apollo 将提供一个完成的安全模型来管理本地资源,比如文件系统,但在Apollo alpha 1 build这个安全模型还没实现。 访问文件和目录 Apollo applications可以运行在若干平台上,包括Windows 和Mac OS . Apollo file API 使用统一平台的代码语法,所以你不需要为任何特殊的操作系统写代码。 例如,路径在Mac OS和Windows中表现是不同的, 典型的路径在Mac OS上是 /Users/joe/Documents/test.txt 在Windows上是 C:\Documents and Settings\joe\My Documents\test.txt 然而这些你都可以使用相同的Apollo组件,类,方法,属性来访问任意一个操作系统 一个ActionScript的File对象是一个文件或目录的指针 File类有一个静态属性指向用户的文档文件夹,由于不同的操作系统,所以具体的目录也不同 trace(File.documentsDirectory.nativePath)
// On Windows: C:\Documents and Settings\joe\MyDocuments // On Mac OS : /Users/joe/Documents 一但一个File object指向了一个目录,你就可以使用resolve()方法修改指向到一个子目录或者文件 例如下边的代码创建一个文件夹在用户文档目录: var newDir:File=File.documentsDirectory;
newDir=newDir.resolve("ApolloTest"); newDir.createDirectory(); File object 可以指向一个文件或一个目录,即使这个文件或目录并不存在,就像上边的例子一样,我们指向了一个并不存在,但想去创建的一个目录。 常见目录的File Class静态属性 File.appStorageDirectory : 每个Apollo application 都被分配一个唯一的storage目录,这是一个绝好的地方存储一些这个app需要处理的,但用户不需要看到的文件,比如一些log文件,缓存文件,和一些引用的文件 File.appResourceDirectory : application被安装到的目录 File.currentDirectory : 顾名思义不翻译了 File.desktopDirectory : 同上 File.documentsDirectory : 上边讲过,是文档文件夹 File.userDirectory : 这是用户的home directory 例如在Mac OS上是 User/ 在Windows上是 c:\\Document and Settings\username url属性:平台无关的(platform-independent)字符串返回文件或文件夹的位置 例如: var directory:File = File.userDirectory;
trace(directory.url) // on Windows: file:///C:/Documents% // on Mac OS: file:///Users 相反,nativePath属性:返回的是Windows 或是 Mac OS 唯一平台的. 例如:下边代码指定Windows目录下的文件 var file:File = new File( );
file.nativePath = "c:/ApolloTest/surprise.txt"; 然而一般情况下更好的方式还是使用上边提到的静态属性(例如File.appStorageDirectory)指向操作系统上已知目录, 然后使用resolve()方法创建一个相对的目录或文件,例如下边的代码 var logFile:File = File.appStorageDirectory;
logFile = logFile.resolve("log.txt"); 使用storage目录来存储你的应用程序以后需要访问的文件,但是用户并不需要知道这些。 URI scheme file:///c:/ApolloTest/test.txt 除了这种常见的URI scheme以外还 支持2种URI scheme app-storage :指出application的storage目录,就像下边这样 var logFile:File = File.appStorageDirectory;
logFile = logFile.resolve("log.txt"); trace(logFile.url); // app-storage:/log.txt app-resource :指出application的安装目录,像下边这样 var installDir:File = new File( );
installDir.url = "app-resource:/"; installDir = installDir.resolve("HelloWorld-app.xml"); trace(installDir.url); // app-resource:/HelloWorld-app.xml 不过最常见的还是 file : File对象的url属性返回一个标准的file URI scheme var file:File = File.documentsDirectory;
file = file.resolve("ApolloTest/test.txt"); trace(file.url); // On Windows: // file:///C:/Documents%20and %20Settings/ ... /test.txt // On Mac OS: // file:///Users/userName/Documents/ ... /test.txt 方法的同步异步版本: File类和FileStram类的一些方法,有同步和异步两个版本,比如File.copyFile 和File.copyFileAsync 同步的版本的方法不放弃操作直到file操作完成。异步版本的方法在后台运行,允许ActionScript过程同时发生。 直到异步文件操作完成,一个event被广播给listeners告诉他们操作完成了 这里有一个使用同步copyTo()方法来copy文件的例子 var file1:File = File.documentsDirectory.
resolve("ApolloTest/test.txt"); var file2:File = File.documentsDirectory. resolve("ApolloTest/copy of test.txt"); file1.copyTo(file2); trace("Not output until the file is copied."); 这里还有一个使用异步copyToAsync()方法来copy文件的例子 var file1:File = File.documentsDirectory.
resolve("ApolloTest/test.txt"); var file2:File = File.documentsDirectory. resolve("ApolloTest/copy of test.txt"); file1.copyToAsync(file2); file1.addEventListener(Event.COMPLETE, completeHandler); trace("This line executes before the complete event."); trace("So does this line."); private function completeHandler(event:Event):void { trace("Done."); } 下边列出File类的异步方法(所有的异步方法都一个同步方法的副本) 方法 事件 copyToAsync( ) complete, ioError deleteDirectoryAsync( ) complete, ioError deleteFileAsync( ) complete, ioError listDirectoryAsync( ) directoryListing, ioError moveToAsync( ) complete, ioError moveToTrashAsync( ) complete, ioError 当你打开一个文件,无论用FileStream对象的open()还是openAsync()方法,首先都是同步打开文件操作,然后异步打开操作。更多的信息请看这章后边的“The open( ) and openAsync( ) Methods” 当你需要在文件操作期间使用ActionScropt程序的时候(比如进度条动画)你可以使用异步版本的方法。 例如当你写一个小的文件(1兆或更小)你可以使用FileStream对象的open() (同步版本的方法)方法,但当你写的文件比较大 或不知道文件大小的时候你可以使用异步方法openAsync(); 想了解更多异步方法,请看Programming ActionScript 3.0 的“Handling Events”那章 你可以在这里找到他: http://livedocs.macromedia.com/flex/2/docs/Part5_ProgAS.html 读目录的内容 File.listDirectory()方法返回指定目录的文件或文件夹的File object数组 例如下边的代码列出桌面文件夹的内容: var directory:File = File.desktopDirectory;
var contents:Array = directory.listDirectory( ); for (var i:uint = 0; i < contents.length; i++) { if (contents[i].isDirectory) { trace(contents[i].name); } else { trace(contents[i].name, contents[i].size, "bytes"); } } 它只会列出指定文件夹根目录的文件和文件夹,不会递归查找子文件夹。你当然也可以写代码来遍历子文件夹 但如果你真这么做了,也许最好使用File.listDirectoryAsync()方法,这样在列表的同时可以做些显示进度条之类的事了 更多请看第5章的"Getting a Directory Listing" 取得文件信息 File类包含了许多关于文件或目录的属性 属性 描述 exists 状态,这个文件或文件夹是否存在,这是个非常用有用的检查,例如,在你试图读或写或移动删除某个文件之前检查一下 是否存在 isDirectory 状态,判断这个File object是否是一个文件夹(true)还是一个文件(false)。你将在试图使用文件夹专有操作之前(例 如listDirectory()方法)检查一下这个file object是不是一个文件夹 isHidden 状态,这个文件或文件夹是否隐藏 nativePath Notes,这个文件或文件夹操作系统特有的路径(system-specific path ) parent Notes,这个File实例的父目录 url Notes,该文件或文件夹操作系统无关的路径(system-independent path). File类还从FileReference类继承了一些有用的属性 属性 描述 creationDate 文件或文件夹的创建日期 modificationDate 文件或文件夹最后修改的日期 name 文件或文件夹的名字 size 文件大小以 bytes为单位. 复制、移动文件与文件夹 File.copyTo()和File.moveTo()方法复制或移动一个文件或文件夹到指定的新位置。例如 下边的代码复制用户文档目录子目录 Apollo Test文件夹下的test.txt 到application storage目录下的UserData子目录: var file1:File = File.documentsDirectory.resolve("Apollo
Test/test.txt"); var destination:File = File.appStorageDirectory. resolve("User Data"); destination.createDirectory( ); var file2:File = destination.resolve("test.txt"); file1.copyTo(file2); 注意,调用File.createDirectory()方法是为了确保目标文件夹存在。 如果复制或移动操作将要很长时间,你可能需要调用File.copyToAsync()和File.moveToAsync()方法 所有的这些方法都包含一个clobber参数,你可以把这个参数设置为true来允许overwrite现有的文件,这个参数默认是false的 创建文件和文件夹 File类的File.createTempFile()和File.createTempDirectory()静态方法允许你创建一个临时的文件或文件夹。Apollo确保这个临时文件或文件夹是新的唯一的。例如下边代码创建一个临时文件: var bufferStorage:File = File.createTempFile( );
当你关闭一个apollo application时候,临时文件和文件夹不会自动删除,所以你一般时候需要在关闭application之前删除临时文件夹。更多信息看下一节删除文件和文件夹 File.createDirectory()方法允许你在File object指定的位置上创建一个目录 var directory = File.documentsDirectory;
directory = directory.resolve("ApolloTest"); 当你打开一个可写的FileStream object时,目录自动被创建。更多内容下面 删除文件和文件夹 File.deleteFile()方法永久删除一个文件,File.deleteDirectory()方法永久删除一个文件夹。 File.moveToTransh()方法允许你移动文件或文件夹到系统的垃圾回收站 所有的这些方法也有一个异步的副本 读写文件 FileStream类提供方法来读写文件 这里是读写文件的一般过程: 1 . 建立一个File object指向这个你要读写的文件。 不会的到上边找 2 . 建立一个FileStream对象 例如 var stream:FileStream= new FileStream(); 3. 调用FileStream.open()或FileStream.openAsync()方法,传递file object参数和一个fileMode参数,例如 stream.open(file,FileMode.READ) FileMode的相关信息"文件打开模式"章节会讲到 4. 如果你调用FileStream.openAsync()方法,那么建立一个适当的监听函数。详细下边章节会讲到 5.对你的数据调用适当的读写方法。 详细下边的“读和写方法”会讲 6.关闭文件,使用FileStream.close()方法。例如: stream.close() 3,4,5步后边会详细讲,这里是一个同步读utf-8文本文件的例子 var file:File = File.appStorageDirectory;
file = file.resolve("settings.xml"); var stream:FileStream = new FileStream( ); stream.open(file, FileMode.READ); var data:String = stream.readUTFBytes(stream. bytesAvailable); stream.close( ); 还有一个异步读同样数据的例子 var file:File = File.appStorageDirectory;
file = file.resolve("settings.xml"); var stream:FileStream = new FileStream( ); stream.openAsync(file, FileMode.READ); stream.addEventListener(Event.COMPLETE, readData); var data:String; private function readData(event:Event):void { data = stream.readUTFBytes(stream.bytesAvailable); stream.close( ); } open()和openAsync()方法 在你读或写文件之前你需要先打开这个文件 当你用FileStream.openAsync()方法打开文件的时候,这个打开是异步的,你需要注册个事件监听者来监视这个过程。 FileStream.open()方法以同步的方式打开文件,如果你的application打开文件使用同步方法,所有后续的读写操作会同步进行 下边的例子中stream.open(),stream.writeUTFBytes(), 和 stream.close()将在下次调用之前全部完成。 var newFile:File = File.documentsDirectory;
file = file.resolve("ApolloTest/test.txt"); 68 | Chapter 4: Using the File System API var stream:FileStream = new FileStream( ) stream.open(file, FileMode.WRITE); stream.writeUTFBytes("This is some sample text."); stream.close( ); 这种同步操作好处在于只写较少的代码就能完成任务,坏处就是如果操作时间很长那么后续的As代码会被延时执行。所以,如果你 操作一个较大的文件,或者打开文件在较慢的网络上共享,你应该考虑使用FileStream.openAsync()方法 当你使用openAsync()方法时,下边的所有过程全部为异步的: 文件关闭(Closing the file) 当文件关闭时 FileStream object广播一个close事件 读数据到缓存(Reading data into the read buffer) 当数据被读时FileStream object广播 progress事件,然后当所有数据读完时候广播一个complete事件,一旦数据被读完后,调用一个读方法(例如 readBytes())读数据就是同步过程了 I/O 错误 FileStream object在遇到错误时候广播一个 ioError 的事件。有很多原因会出现这种情况 例如 尝试去打开一个不存在的文件或尝试写一个已经locked的文件。然而一些错误比如尝试去读的文件还没有open,将会抛出异常(而不 会广播 ioError事件)因为Apollo runtime能立刻察觉到错。 在你调用FileStream.openAsync()方法之前,你的application需要建立一些事件监听函数来处理这些他们感兴趣的事件。 下边的例子使用异步读模式打开一个文件。当这个文件被打开后,complete事件将被广播出去(除非有错误的时候会广播ioError事件) 然后completeHandler()处理函数中调用FileStream.readBytes()方法,这将开始读取文件为一个bytesArray,在异步模式,当所有的bytes都被读完后,complete事件将被广播出去: var file:File = File.documentsDirectory.resolve("ApolloTest/test.txt");
var stream:FileStream = new FileStream( ); stream.addEventListener(ProgressEvent.PROGRESS,progressHandler); stream.addEventListener(Event.COMPLETE, completeHandler); stream.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler); stream.addEventListener(Event.CLOSE, closeHandler); stream.openAsync(file, FileMode.READ); var data:ByteArray = new ByteArray( ); private function progressHandler(event:ProgressEvent):void { trace(stream.bytesAvailable, "bytes read."); } private function completeHandler(event: Event):void { data = stream.readBytes(stream.bytesAvailable); stream.close( ); } private function ioErrorHandler(event:IOErrorEvent):void { trace("An I/O error was encountered."); } private function closeHandler(event: Event):void { trace("File closed."); } 文件打开模式 FileStream.open( ) 方法和 FileStream.openAsync( )方法都需要两个参数:需要打开的文件,和fileMode参数,fileMode参数是一个定义FileStream object对象能力的字符串。fileMode参数可能的值都被定义在FileMode类的衡量中了 例如,下边的代码同步打开一个文件写操作,不能读 stream.open(file, FileMode.WRITE); 这里是FileMode类的衡量以及他们的意思 FileMode.APPEND 只写模式,所有被写的数据都会附加到文件的最后 Upon opening, any nonexistent file is created. FileMode.READ 只读模式,file必须存在 (missing files are not created). FileMode.UPDATE 读写模式,数据可以写在文件的任何位置或者附加到尾部。Upon opening, any nonexistent file is created. FileMode.WRITE 只写模式,如果文件不存在,将会创建新的文件,如果存在将会被覆盖 读和写方法 FileStream类包含一堆读和写的方法,每个和数据被读写的格式对应。例如,你可以使用readUTFBytes()和WriteUTFBytes ()方法读写一个bytes Array,也可以readByte()和writeByte()方法一次读写一个byte,总而言之有25个读和写的方法。详细的信息要看Apollo Alpha 1发布的ActionScript 3.0 Language Reference。 读写文本数据也许看起来价值不高,你可以在文件中编码文本readUTFBytes()和writeUTFBytes()方法提供读写UTF-8格式的文本。 readMultiByte( ) 和writeMultiByte( )方法允许你为文件指定不同的字符编码。 Thereare other factors to consider as well. For example, a UTF file may start with a UTF byte order mark (BOM) character, which defines the UTF encoding and the byte order (or “endianness”) of the data. 更多信息, 看 Apollo Developer’s Guide (http://www.adobe.com/go/apollodocs).的“Data formats, and choosing the read and write methods to use” 章节 More Information For examples of reading and writing files, see the following sections in Chapter 5: • “Writing a Text File from a String” • “Reading a Text File into a String” • “Encoding Bitmap Data into PNG or JPEG Format and Writing It to the File System” • “Serializing and De-Serializing ActionScript Objects to the File System” posted @ 2007-04-07 11:03 adamxx 阅读(780) 评论(1) 编辑 |
||