filer.js 实现写入文件操作

EverWHL 2016-01-18 01:47:22
有人用过filer.js写入文件操作吗?请求大神知道。官网下的demo本机都运行不了,我是直接打开的。
官网地址:https://github.com/ebidel/filer.js
...全文
458 20 打赏 收藏 转发到动态 举报
写回复
用AI写文章
20 条回复
切换为时间正序
请发表友善的回复…
发表回复
EverWHL 2016-01-21
  • 打赏
  • 举报
回复
不管怎么说,也是实现了功能,现在结贴把。
scscms太阳光 2016-01-21
  • 打赏
  • 举报
回复
看你这么努力再回复下: 需要显示中文可以这样: console.log(String.fromCharCode.apply(null, new Uint8Array(this.result)));//二进制转字符(ES6范围) 改为:
var decodedString = decodeURIComponent(escape(String.fromCharCode.apply(null,new Uint8Array(this.result))));
console.log(decodedString);
并附上字符串转Uint8Array函数
    function stringToUint(string) {
        var string = btoa(unescape(encodeURIComponent(string))),
            charList = string.split(''),
            uintArray = [];
        for (var i = 0; i < charList.length; i++) {
            uintArray.push(charList[i].charCodeAt(0));
        }
        return new Uint8Array(uintArray);
    }
scscms太阳光 2016-01-20
  • 打赏
  • 举报
回复
不能照抄代码。明显一些错误的需要改正。
   var filer = new Filer();
    filer.init({persistent: true, size: 2 * 1024 * 1024}, function (fs) {
    }, errorHandler);
    function errorHandler(e) {
        var msg = '';
        switch (e.code) {
            case FileError.QUOTA_EXCEEDED_ERR:
                msg = 'QUOTA_EXCEEDED_ERR';
                break;
            case FileError.NOT_FOUND_ERR:
                msg = 'NOT_FOUND_ERR';
                break;
            case FileError.SECURITY_ERR:
                msg = 'SECURITY_ERR';
                break;
            case FileError.INVALID_MODIFICATION_ERR:
                msg = 'INVALID_MODIFICATION_ERR';
                break;
            case FileError.INVALID_STATE_ERR:
                msg = 'INVALID_STATE_ERR';
                break;
            default:
                msg = 'Unknown Error';
                break;
        }
        console.log('Error: ' + msg);
    }
    //这里需要定时器,原因是初始化需要时间。官方的例子用的是异步。
    setTimeout(function () {
        filer.cd('/', function (entries) {
            //写入
            filer.write('myFile.txt', {data: '1234567890我们', type: 'text/plain'},
                    function (fileEntry, fileWriter) {
                        console.log("成功" + fileEntry.toURL());//毛都没生成!
                        //读取
                        filer.open('myFile.txt', function (file) {
                            // Use FileReader to read file.
                            var reader = new FileReader();
                            reader.onload = function (e) {
                                //console.log(this.result.byteLength);//返回二进制长度
                                console.log(String.fromCharCode.apply(null, new Uint8Array(this.result)));//二进制转字符(ES6范围)
                            };
                            reader.readAsArrayBuffer(file);
                        }, errorHandler);
                    }, errorHandler
            );
        }, errorHandler);
    }, 2000);
EverWHL 2016-01-20
  • 打赏
  • 举报
回复
非常感谢@xzy21com等和大家的帮助。 现在自己照着demo里面一些已经实现了,现在把代码贴出来大家一起讨论一下。 刚才我试了一下您的代码,就是发现输出的中文是乱码,不过功能更已经实现了。 对了 上面的中文教程我感觉是在原生的js,像open,wirte这些方法感觉又像是又包装了一层。 不过还有一些疑问, 1.不知道为啥需要cd / 一下 而在我本机先cd/ 然后把cd/ 去掉, 删了浏览器缓存。 直接write也没问题,再别人电脑上还必须cd/一下 这点比较奇怪,还有打开的时候不需要cd/也是可以打开的。 好了,下面是一些代码。

//写入独出文件
    function wirteTest(){
        console.log("开始写....");
        var filer = new Filer();
        function errorHandler(e) {
            console.log('Error' + e.name);
        }
        filer.init({persistent: true, size: 2 * 1024 * 1024}, function (fs) {
        }, errorHandler);
        setTimeout(function () {
            filer.cd('/', function (entries) {
                //写入
                filer.write('questionTest.html', {data: res, type: 'text/plain'},
                        function (fileEntry, fileWriter) {
                            console.log("写入成功" + fileEntry.toURL());
                            var file_name = fileEntry.name;
                            console.log("文件名字为==" + file_name);
                            //var fileWin = self.open(fileEntry.toURL());
                        }, errorHandler
                );
            }, errorHandler);
        }, 1000);

        //读取
        setTimeout(function () {
            //读取
            filer.open('questionTest.html', function(file) {
                var reader = new FileReader();
                reader.onload = function(e) {
                    console.log("开始读...");
                    console.log(e.target.result);
                    console.log("读入成功...");
                };
                reader.readAsText(file);
            }, errorHandler);
        }, 2000);
    }
functionsub 2016-01-19
  • 打赏
  • 举报
回复
没说完,接着说。

要是能这样,整个网络不是都乱套了。

去研究了一下,FILE API下面说正题:
A web app can request access to a sandboxed file system by calling

看清楚,sandboxed file system,也就是沙盒文件系统。

追寻了一下,实际操作完成后是不会生成实体文件的,所有文件的创建记录什么的都是在chrome的APPDATA文件夹里。

对应目录大概是:
C:\Users\用户名AppData\Local\Google\Chrome\User Data\Default\File System
里面有几个文件夹,其中Origins文件夹下的000003.log内容大概是这样:

可以看到这里每个域名前面都有个编号000,001,002
而在File System统计目录下就有000,001,002文件夹里面的各种 000003.log文件,打开后内容是这样的:


虽然是乱码啦,但勉强能看明白吧?

剩下的就没什么好说的了,简单一句话就是:不是你想的那样!

functionsub 2016-01-19
  • 打赏
  • 举报
回复
想太多了吧?你还想跟windows可执行程序一样在你电脑里的某个目录下生成实体文件吗?
Go 旅城通票 2016-01-19
  • 打赏
  • 举报
回复
引用 6 楼 EverWHL 的回复:
两个大神,能给我写个列子,就是创建并写入文件。感激不进...
你是要在硬盘上创建文件?这个好像不行。。这个是模拟的,用的是本地存储来模拟的,实际硬盘上没有写入文件,我用下载的压缩包里面的demos运行你的
引用 9 楼 EverWHL 的回复:
虽说提示alert成功了,但是不知道生成到哪里去了,本机没看见有这个文件 [quote=引用 7 楼 xzy21com 的回复:]
<script>
  var filer = new Filer();
  filer.init({persistent: true, size: 1024 * 1024}, function(fs) {},onError);
  function onError(e) {
    console.log('Error' + e.name);
  }
  setTimeout(function(){
    filer.cd('/', function(entries) {     
		filer.write('111.txt', {data: '1234567890', type: 'text/plain'},
		  function(fileEntry, fileWriter) {
			alert("成功");//毛都没生成!
		  },onError
		);
    }, onError);
  },1000);
</script>
[/quote] 写不到系统硬盘的,应高用localstorage来模拟写入删除的,实际并不是操作系统硬盘上的文件
EverWHL 2016-01-19
  • 打赏
  • 举报
回复
虽说提示alert成功了,但是不知道生成到哪里去了,本机没看见有这个文件
引用 7 楼 xzy21com 的回复:
<script>
  var filer = new Filer();
  filer.init({persistent: true, size: 1024 * 1024}, function(fs) {},onError);
  function onError(e) {
    console.log('Error' + e.name);
  }
  setTimeout(function(){
    filer.cd('/', function(entries) {     
		filer.write('111.txt', {data: '1234567890', type: 'text/plain'},
		  function(fileEntry, fileWriter) {
			alert("成功");//毛都没生成!
		  },onError
		);
    }, onError);
  },1000);
</script>
EverWHL 2016-01-19
  • 打赏
  • 举报
回复
是什么都没生成,所以希望用过的大神能给个生成文件的列子,感激不进
引用 7 楼 xzy21com 的回复:
<script>
  var filer = new Filer();
  filer.init({persistent: true, size: 1024 * 1024}, function(fs) {},onError);
  function onError(e) {
    console.log('Error' + e.name);
  }
  setTimeout(function(){
    filer.cd('/', function(entries) {     
		filer.write('111.txt', {data: '1234567890', type: 'text/plain'},
		  function(fileEntry, fileWriter) {
			alert("成功");//毛都没生成!
		  },onError
		);
    }, onError);
  },1000);
</script>
EverWHL 2016-01-19
  • 打赏
  • 举报
回复
@functionsub @showbo @xzy21com 我现在还有个页面用的就是api上面给的方法,但是报错,说BlobBuilder没有这个方法。大家再集思广益.谢谢!!

function onInitFs(fs) {
 
  fs.root.getFile('log.txt', {create: true}, function(fileEntry) {
 
    // Create a FileWriter object for our FileEntry (log.txt).
    fileEntry.createWriter(function(fileWriter) {
 
      fileWriter.onwriteend = function(e) {
        console.log('Write completed.');
      };
 
      fileWriter.onerror = function(e) {
        console.log('Write failed: ' + e.toString());
      };
 
      // Create a new Blob and write it to log.txt.
      var bb = new (); // Note: window.WebKitBlobBuilder in Chrome 12.
      bb.append('Lorem Ipsum');
      fileWriter.write(bb.getBlob('text/plain'));
 
    }, errorHandler);
 
  }, errorHandler);
 
}
 
window.requestFileSystem(window.TEMPORARY, 1024*1024, onInitFs, errorHandler);
functionsub 2016-01-19
  • 打赏
  • 举报
回复


内容好像是放在红框里的文件里了。名字是类似000000,0000001这样的。
EverWHL 2016-01-19
  • 打赏
  • 举报
回复
对了,大家说我的思路现在应该没什么大问题把,先写一个,然后再读下。嘿嘿
EverWHL 2016-01-19
  • 打赏
  • 举报
回复
好的,我在我本地也找到您说的文件了。可能是我理解错了,确实不能在本地硬盘上写入文件。
但是不知道它这生成的文件搁在哪里了,不知道是不是放到缓存里面了,我看了下浏览器里面的东西又什么都没有。
什么Cookies等等的。
我现在的思路是这样,既然不能写入本地文件,假如现在写的东西在浏览器的缓存里面,那我就写完之后再读取写到浏览器的文件内容。我现在就开始试试咯,希望大家的继续帮我集思广益的,由衷感谢。
咱们CSDN大论坛还是很有爱的。@xzy21com @showbo @functionsub 非常感谢



引用 12 楼 functionsub 的回复:
没说完,接着说。

要是能这样,整个网络不是都乱套了。

去研究了一下,FILE API下面说正题:
A web app can request access to a sandboxed file system by calling

看清楚,sandboxed file system,也就是沙盒文件系统。

追寻了一下,实际操作完成后是不会生成实体文件的,所有文件的创建记录什么的都是在chrome的APPDATA文件夹里。

对应目录大概是:
C:\Users\用户名AppData\Local\Google\Chrome\User Data\Default\File System
里面有几个文件夹,其中Origins文件夹下的000003.log内容大概是这样:

可以看到这里每个域名前面都有个编号000,001,002
而在File System统计目录下就有000,001,002文件夹里面的各种 000003.log文件,打开后内容是这样的:


虽然是乱码啦,但勉强能看明白吧?

剩下的就没什么好说的了,简单一句话就是:不是你想的那样!
scscms太阳光 2016-01-18
  • 打赏
  • 举报
回复
<script>
  var filer = new Filer();
  filer.init({persistent: true, size: 1024 * 1024}, function(fs) {},onError);
  function onError(e) {
    console.log('Error' + e.name);
  }
  setTimeout(function(){
    filer.cd('/', function(entries) {     
		filer.write('111.txt', {data: '1234567890', type: 'text/plain'},
		  function(fileEntry, fileWriter) {
			alert("成功");//毛都没生成!
		  },onError
		);
    }, onError);
  },1000);
</script>
EverWHL 2016-01-18
  • 打赏
  • 举报
回复
两个大神,能给我写个列子,就是创建并写入文件。感激不进...
EverWHL 2016-01-18
  • 打赏
  • 举报
回复
现在demo可以访问了,但是现在我自己写的功能是想写入文件缺不好用。请大神指导

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="chrome=1" />
<title>HTML5 Filesystem Playground</title>
</head>
<body>
11
<script src="../src/filer.js"></script>
<script>
var filer = new Filer();
filer.init({persistent: false, size: 1024 * 1024}, function(fs) {
filer.size == Filer.DEFAULT_FS_SIZE
filer.isOpen == true
filer.fs == fs
}, onError);
function onError(e) {
console.log('Error' + e.name);
}
filer.write('111.txt', {data: '1234567890', type: 'text/plain'},
function(fileEntry, fileWriter) {
alert("成功");
},
onError
);
</script>
</body>
</html>

谷歌浏览器错误如下:说是初始化有问题,可是我是照着说明上写的呢。谢谢大家帮忙看下

EverWHL 2016-01-18
  • 打赏
  • 举报
回复
引用 2 楼 xzy21com 的回复:
注意必须是chrome浏览器,同时必须有服务器(或IIS虚拟主机类)来访问,并同意浏览器执行组件。
嗯 是的, 我是用的谷歌浏览器访问的。 但是说的必须有服务器(或IIS虚拟主机类)来访问 是什么意思 是必须要放到服务器应用件里面吗?例如:tomcat这样的容器吗
Go 旅城通票 2016-01-18
  • 打赏
  • 举报
回复
引用 2 楼 xzy21com 的回复:
注意必须是chrome浏览器,同时必须有服务器(或IIS虚拟主机类)来访问,并同意浏览器执行组件。
+++++ 需要搭建服务器通过http协议访问,而不是直接拖进浏览器file查看
scscms太阳光 2016-01-18
  • 打赏
  • 举报
回复
注意必须是chrome浏览器,同时必须有服务器(或IIS虚拟主机类)来访问,并同意浏览器执行组件。
EverWHL 2016-01-18
  • 打赏
  • 举报
回复
没人知道吗?顶起
1.在过去一年,小编写过一个批量上传图片的例子,那个例子是基于百度编辑器改造的,用起来必须依赖百度编辑器,所以小编就又弄了一个,就是今天要介绍的了,uploadify上传的了前端用flash,javascript编写,后端兼容java,php,.net等语言 2.首先在把“uploadify”文件夹放入项目中 3.页面上面引入 <script type="text/javascript" src="resource/js/uploadify/main.js">js中写入以下配置: $(document).ready(function(){ var setting={ "id": "uploadify",//绑定的input的ID "swf": 'resource/js/uploadify/uploadify.swf',//[必须设置]swf的路径 "uploader": "site/txtOrTopicImageUpload!txtOrTopicImageUpload.action?uid="+uid,//[必须设置]上传文件触发的url "auto":true,//文件选择完成后,是否自动上传 "buttonText":'+上传图片',//上传按钮的文字 "height": 40,//上传按钮的高和宽 "width": 100, "buttonCursor": 'pointer',//上传鼠标hover后Cursor的形状 //"cancelImage": "resource/js/uploadify/uploadify-cancel.png",//[必须设置]取消图片的路径 //"checkExisting":'/uploader/uploadify-check-existing.php',//检查上传文件是否存,触发的url,返回1/0 "debug": false,//debug模式开/关,打开后会显示debug时的信息 "fileObjName":'file', "fileSizeLimit" : 3*1024*1024,//文件的极限大小,以字节为单位,0为不限制。1MB:1*1024*1024 "fileTypeDesc": '图片',//允许上传的文件类型的描述,在弹出的文件选择框里会显示 "fileTypeExts": '*.jpg;*.bmp;*.gif;*.png',//允许上传的文件类型,限制弹出文件选择框里能选择的文件 "method": 'post',//和后台交互的方式:post/get "multi": true,//是否能选择多个文件 "queueID": 'None_none',//显示上传文件队列的元素id,可以简单用一个div来显示 "queueSizeLimit" : 10,//队列中允许的最大文件数目 "progressData" : 'all', // 'percentage''speed''all'//队列中显示文件上传进度的方式:all-上传速度+百分比,percentage-百分比,speed-上传速度 "removeCompleted" : true,//上传成功后的文件,是否在队列中自动删除 "removeTimeout": 0, "requeueErrors" : true, "postData": {},//和后台交互时,附加的参数 "preventCaching" : true, "transparent": true, "successTimeout" : 30,//上传时的timeout "uploadLimit":0,//能同时上传的文件数目 "onUploadSuccess" : function(file,data,response) {//上传完成时触发(每个文件触发一次) var a =eval("("+data+")");   imgArray[imageIndex]=a.result; showImges(imageIndex); $("#loading").hide(); }, "onDialogClose": function(queueData){ var result=countArray(imgArray),a=queueData.queueLength; if(a+result>image_size){ jDialog("一次最多上传10张图片!"); for(var s in queueData.files){ $("#uploadify").uploadify("cancel",s); } return; }if(a!=0){ $("#loading").show(); } } }; $("#uploadify").uploadify(setting); }); 5.下面是参数的详细配置: 使用: //绑定的界面元素 $("#gallery").uploadify({ 设置参数,参数如下. }); 设置的属性: id: jQuery(this).attr('id'),//绑定的input的ID swf: 'http://www.static-xxx.nu/uploader/uploadify.swf',//[必须设置]swf的路径 uploader: '/uploadify/galleri.php',//[必须设置]上传文件触发的url auto:false,//文件选择完成后,是否自动上传 buttonText:'Välj Filer',//上传按钮的文字 height: 30,//上传按钮的高和宽 width: 120, buttonCursor: 'pointer',//上传鼠标hover后Cursor的形状 cancelImage: 'http://www.static-xxx.nu/uploadify-cancel.png',//[必须设置]取消图片的路径 checkExisting:'/uploader/uploadify-check-existing.php',//检查上传文件是否存,触发的url,返回1/0 debug: true,//debug模式开/关,打开后会显示debug时的信息 fileObjName:'file', fileSizeLimit : 0,//文件的极限大小,以字节为单位,0为不限制。1MB:1*1024*1024 fileTypeDesc: 'Bild JPG',//允许上传的文件类型的描述,在弹出的文件选择框里会显示 fileTypeExts: '*.jpg',//允许上传的文件类型,限制弹出文件选择框里能选择的文件 method: 'post',//和后台交互的方式:post/get multi: true,//是否能选择多个文件 queueID: 'fileQueue',//显示上传文件队列的元素id,可以简单用一个div来显示 queueSizeLimit : 999,//队列中允许的最大文件数目 progressData : 'all', // 'percentage''speed''all'//队列中显示文件上传进度的方式:all-上传速度+百分比,percentage-百分比,speed-上传速度 removeCompleted : true,//上传成功后的文件,是否在队列中自动删除 removeTimeout: 3, requeueErrors : true, postData: {},//和后台交互时,附加的参数 preventCaching : true, transparent: true, successTimeout : 30,//上传时的timeout uploadLimit:999//能同时上传的文件数目 设置的事件: onDialogClose : function(swfuploadifyQueue) {//当文件选择对话框关闭时触发   if( swfuploadifyQueue.filesErrored > 0 ){   alert( '添加至队列时有'   +swfuploadifyQueue.filesErrored   +'个文件发生错误n'   +'错误信息:'   +swfuploadifyQueue.errorMsg   +'n选定的文件数:'   +swfuploadifyQueue.filesSelected   +'n成功添加至队列的文件数:'   +swfuploadifyQueue.filesQueued   +'n队列中的总文件数量:'   +swfuploadifyQueue.queueLength);   } } onDialogOpen : function() {//当选择文件对话框打开时触发   alert( 'Open!'); } onSelect : function(file) {//当每个文件添加至队列后触发   alert( 'id: ' + file.id   + ' - 索引: ' + file.index   + ' - 文件名: ' + file.name   + ' - 文件大小: ' + file.size   + ' - 类型: ' + file.type   + ' - 创建日期: ' + file.creationdate   + ' - 修改日期: ' + file.modificationdate   + ' - 文件状态: ' + file.filestatus); } onSelectError : function(file,errorCode,errorMsg) {//当文件选定发生错误时触发   alert( 'id: ' + file.id   + ' - 索引: ' + file.index   + ' - 文件名: ' + file.name   + ' - 文件大小: ' + file.size   + ' - 类型: ' + file.type   + ' - 创建日期: ' + file.creationdate   + ' - 修改日期: ' + file.modificationdate   + ' - 文件状态: ' + file.filestatus   + ' - 错误代码: ' + errorCode   + ' - 错误信息: ' + errorMsg); } onQueueComplete : function(stats) {//当队列中的所有文件全部完成上传时触发   alert( '成功上传的文件数: ' + stats.successful_uploads   + ' - 上传出错的文件数: ' + stats.upload_errors   + ' - 取消上传的文件数: ' + stats.upload_cancelled   + ' - 出错的文件数' + stats.queue_errors); } onUploadComplete : function(file,swfuploadifyQueue) {//队列中的每个文件上传完成时触发一次   alert( 'id: ' + file.id   + ' - 索引: ' + file.index   + ' - 文件名: ' + file.name   + ' - 文件大小: ' + file.size   + ' - 类型: ' + file.type   + ' - 创建日期: ' + file.creationdate   + ' - 修改日期: ' + file.modificationdate   + ' - 文件状态: ' + file.filestatus   + ' - 出错的文件数: ' + swfuploadifyQueue.filesErrored   + ' - 错误信息: ' + swfuploadifyQueue.errorMsg   + ' - 要添加至队列的数量: ' + swfuploadifyQueue.filesSelected   + ' - 添加至对立的数量: ' + swfuploadifyQueue.filesQueued   + ' - 队列长度: ' + swfuploadifyQueue.queueLength); } onUploadError : function(file,errorCode,errorMsg,errorString,swfuploadifyQueue) {//上传文件出错是触发(每个出错文件触发一次)   alert( 'id: ' + file.id   + ' - 索引: ' + file.index   + ' - 文件名: ' + file.name   + ' - 文件大小: ' + file.size   + ' - 类型: ' + file.type   + ' - 创建日期: ' + file.creationdate   + ' - 修改日期: ' + file.modificationdate   + ' - 文件状态: ' + file.filestatus   + ' - 错误代码: ' + errorCode   + ' - 错误描述: ' + errorMsg   + ' - 简要错误描述: ' + errorString   + ' - 出错的文件数: ' + swfuploadifyQueue.filesErrored   + ' - 错误信息: ' + swfuploadifyQueue.errorMsg   + ' - 要添加至队列的数量: ' + swfuploadifyQueue.filesSelected   + ' - 添加至对立的数量: ' + swfuploadifyQueue.filesQueued   + ' - 队列长度: ' + swfuploadifyQueue.queueLength); } onUploadProgress : function(file,fileBytesLoaded,fileTotalBytes, queueBytesLoaded,swfuploadifyQueueUploadSize) {//上传进度发生变更时触发 alert( 'id: ' + file.id   + ' - 索引: ' + file.index   + ' - 文件名: ' + file.name   + ' - 文件大小: ' + file.size   + ' - 类型: ' + file.type   + ' - 创建日期: ' + file.creationdate   + ' - 修改日期: ' + file.modificationdate   + ' - 文件状态: ' + file.filestatus   + ' - 当前文件已上传: ' + fileBytesLoaded   + ' - 当前文件大小: ' + fileTotalBytes   + ' - 队列已上传: ' + queueBytesLoaded   + ' - 队列大小: ' + swfuploadifyQueueUploadSize); } onUploadStart: function(file) {//上传开始时触发(每个文件触发一次)   alert( 'id: ' + file.id   + ' - 索引: ' + file.index   + ' - 文件名: ' + file.name   + ' - 文件大小: ' + file.size   + ' - 类型: ' + file.type   + ' - 创建日期: ' + file.creationdate   + ' - 修改日期: ' + file.modificationdate   + ' - 文件状态: ' + file.filestatus ); } onUploadSuccess : function(file,data,response) {//上传完成时触发(每个文件触发一次)   alert( 'id: ' + file.id   + ' - 索引: ' + file.index   + ' - 文件名: ' + file.name   + ' - 文件大小: ' + file.size   + ' - 类型: ' + file.type   + ' - 创建日期: ' + file.creationdate   + ' - 修改日期: ' + file.modificationdate   + ' - 文件状态: ' + file.filestatus   + ' - 服务器端消息: ' + data   + ' - 是否上传成功: ' + response); }

87,907

社区成员

发帖
与我相关
我的任务
社区描述
Web 开发 JavaScript
社区管理员
  • JavaScript
  • 无·法
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧