Node仿Tree指定層級輸出樹形文件目錄結(jié)構(gòu)

前言

這段時間空余時間蠻少,后來特地騰出晚上的時間來開發(fā)自己的玩具拔创。 今天講的是為玩具所開發(fā)的一個小模塊的一個功能利诺。 具體來說它是一個仿Tree命令能夠羅列給定目錄的樹形結(jié)構(gòu)。而且我的做法是開發(fā)一個命令行工具剩燥,但這里先不提慢逾,我們就專注這個目錄列表功能。 需要輸出相同的結(jié)構(gòu)。我們先來看下需要達(dá)到的效果

正文

我們先來探討一下如何獲得目錄結(jié)構(gòu)侣滩。因為我們最終需要的是給路徑->獲得目錄結(jié)構(gòu)->數(shù)據(jù)處理(輸出/另外操作)

在Node里有這么幾個API

fs.readdir(path, callback)

該方法是 readdir(3) 的異步執(zhí)行版本口注,用于讀取一個目錄的內(nèi)容。callback 接收兩個參數(shù) (err, files)君珠,其中 files 是一個數(shù)組寝志,數(shù)組成員為當(dāng)前目錄下的文件名,不包含 . 和 ..策添。

fs.readdirSync(path)

該方法是 readdir(3) 的同步執(zhí)行版本澈段,返回一個不包含 . 和 .. 的文件名數(shù)組。

...這里就列兩個舰攒,其他的請自行去官網(wǎng)查API0-0

一開始我試圖用異步方法來獲取败富,但是最后因為沒處理好Promise和其它異步操作,導(dǎo)致最后數(shù)據(jù)沒有存儲下來摩窃。因此現(xiàn)在我們講的是用同步方式獲取兽叮。異步方式也將在這個模塊寫完之后再去寫一版本。

基本上網(wǎng)上的方法都是遞歸調(diào)用方法來獲取整個目錄結(jié)構(gòu)猾愿。

這里也不例外鹦聪。不過我們需要注意我們需要的將整個目錄關(guān)系都緩存在一個Object里。而且因為我需要指定層級目錄輸出蒂秘。因此每個文件/目錄還將有一個屬性就是deep泽本,它作為一個告訴調(diào)用者該目錄層級的深度。如果是0姻僧,說明是指定目錄的根目錄下同級的文件/目錄规丽。依次類推。

當(dāng)然這里就直接貼出代碼

_getAllNames: function(level, dir) {

? ? ? ? var filesNameArr = []

? ? ? ? let cur = 0

? ? ? ? ? ? // 用個hash隊列保存每個目錄的深度

? ? ? ? var mapDeep = {}

? ? ? ? mapDeep[dir] = 0

? ? ? ? ? ? // 先遍歷一遍給其建立深度索引

? ? ? ? function getMap(dir, curIndex) {

? ? ? ? ? ? var files = fs.readdirSync(dir) //同步拿到文件目錄下的所有文件名

? ? ? ? ? ? files.map(function(file) {

? ? ? ? ? ? ? ? //var subPath = path.resolve(dir, file) //拼接為絕對路徑

? ? ? ? ? ? ? ? var subPath = path.join(dir, file) //拼接為相對路徑

? ? ? ? ? ? ? ? var stats = fs.statSync(subPath) //拿到文件信息對象

? ? ? ? ? ? ? ? ? ? // 必須過濾掉node_modules文件夾

? ? ? ? ? ? ? ? if (file != 'node_modules') {

? ? ? ? ? ? ? ? ? ? mapDeep[file] = curIndex + 1

? ? ? ? ? ? ? ? ? ? if (stats.isDirectory()) { //判斷是否為文件夾類型

? ? ? ? ? ? ? ? ? ? ? ? return getMap(subPath, mapDeep[file]) //遞歸讀取文件夾

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? //console.log(subPath)

? ? ? ? ? ? })

? ? ? ? }

? ? ? ? getMap(dir, mapDeep[dir])

? ? ? ? ? ? //console.log(mapDeep)

? ? ? ? function readdirs(dir, folderName,myroot) {

? ? ? ? ? ? var result = { //構(gòu)造文件夾數(shù)據(jù)

? ? ? ? ? ? ? ? path: dir,

? ? ? ? ? ? ? ? name: path.basename(path),

? ? ? ? ? ? ? ? type: 'directory',

? ? ? ? ? ? ? ? deep: mapDeep[folderName]

? ? ? ? ? ? }

? ? ? ? ? ? var files = fs.readdirSync(dir) //同步拿到文件目錄下的所有文件名

? ? ? ? ? ? result.children = files.map(function(file) {

? ? ? ? ? ? ? ? //var subPath = path.resolve(dir, file) //拼接為絕對路徑

? ? ? ? ? ? ? ? var subPath = path.join(dir, file) //拼接為相對路徑

? ? ? ? ? ? ? ? var stats = fs.statSync(subPath) //拿到文件信息對象

? ? ? ? ? ? ? ? ? ? //console.log(subPath)

? ? ? ? ? ? ? ? if (stats.isDirectory()) { //判斷是否為文件夾類型

? ? ? ? ? ? ? ? ? ? // console.log(mapDeep[file])

? ? ? ? ? ? ? ? ? ? return readdirs(subPath, file,file) //遞歸讀取文件夾

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? return { //構(gòu)造文件數(shù)據(jù)

? ? ? ? ? ? ? ? ? ? path: subPath,

? ? ? ? ? ? ? ? ? ? name: file,

? ? ? ? ? ? ? ? ? ? type: 'file'

? ? ? ? ? ? ? ? }

? ? ? ? ? ? })

? ? ? ? ? ? return result //返回數(shù)據(jù)

? ? ? ? }

? ? ? ? filesNameArr.push(readdirs(dir, dir))

? ? ? ? return filesNameArr

? ? },

過濾掉node_modules是必須的撇贺,因為開發(fā)中這個目錄一直存在赌莺。。松嘶。是個極大干擾源艘狭。

這里我代碼都加了注釋應(yīng)該很好理解。

我們來輸出下緩存的目錄結(jié)構(gòu)

{ path: './',

? name: '[object Object]',

? type: 'directory',

? deep: 0,

? children:

? [ { path: '.DS_Store', name: '.DS_Store', type: 'file' },

? ? { path: '.babelrc', name: '.babelrc', type: 'file' },

? ? { path: '.gitignore', name: '.gitignore', type: 'file' },

? ? { path: 'README.MD', name: 'README.MD', type: 'file' },

? ? { path: 'bin',

? ? ? name: '[object Object]',

? ? ? type: 'directory',

? ? ? deep: 1,

? ? ? children: [Object] },

? ? { path: 'lib',

? ? ? name: '[object Object]',

? ? ? type: 'directory',

? ? ? deep: 1,

? ? ? children: [Object] },

? ? { path: 'package.json', name: 'package.json', type: 'file' },

? ? { path: 'test',

? ? ? name: '[object Object]',

? ? ? type: 'directory',

? ? ? deep: 1,

? ? ? children: [Object] } ] }

[ { path: './',

? ? name: '[object Object]',

? ? type: 'directory',

? ? deep: 0,

? ? children:

? ? [ [Object],

? ? ? [Object],

? ? ? [Object],

? ? ? [Object],

? ? ? [Object],

? ? ? [Object],

? ? ? [Object],

? ? ? [Object] ] } ]

針對./這個當(dāng)前目錄的路徑翠订,程序返回上面的結(jié)構(gòu)巢音。 這樣我們就得到了第一步最基礎(chǔ)的數(shù)據(jù)。

現(xiàn)在第一個步驟是為了模仿Tree工具輸出命令尽超。

這里給個例子參考:

├── .DS_Store

├── .babelrc

├── .gitignore

├── README.MD

├── bin

│?? ├── .DS_Store

│?? ├── folderTree.js

│?? ├── lib2

│?? │?? ├── .DS_Store

│?? │?? └── testa

│?? └── testb

│ ?? ? └── .DS_Store

├── lib

│?? ├── .DS_Store

│?? ├── folderFactory.js

│?? └── testlib

│ ?? ? └── testlibfile.js

├── package.json

└── test

?? ├── .DS_Store

?? ├── index.js

? ? └── testFolder

??? ?? ├── .DS_Store

??? ?? ├── a

??? ?? ├── b

? ?? ? └── c

它有幾個注意點官撼,一個是每當(dāng)你的文件或者目錄是當(dāng)前層級最后一個,那么輸出└──前綴如果是普通的輸出├──?前綴橙弱。 如果是多層級歧寺,那么最前面依舊需要│?來進(jìn)行上下層級的聯(lián)系燥狰。

而且還有很多小細(xì)節(jié)需要處理棘脐。

一開始最好的解決方法其實是遞歸斜筐。但是我覺得有點復(fù)雜,我想試下迭代方法蛀缝。最后結(jié)果如開頭的效果顷链。基本上如果根目錄中最后一個file是目錄類型屈梁。那么它是正常的嗤练。但是如果根目錄中最后一個文件是文件類型類型。我這段迭代并沒有進(jìn)行處理在讶。

我們先來看下這個有缺陷的代碼:

? ? var stack = [obj[0]]

? ? ? ? var isFinal = false

? ? ? ? function printFolder(arr, folderName, isLastFolder) {

? ? ? ? ? ? if (arr.deep <= level) {

? ? ? ? ? ? ? ? for (var i = 0; i < arr.children.length; i++) {

? ? ? ? ? ? ? ? ? ? var isLastFile = i == arr.children.length - 1 ? true : false

? ? ? ? ? ? ? ? ? ? var isRootLast = i == arr.children.length - 1

? ? ? ? ? ? ? ? ? ? isFinal = isFinal == true ? true :? arr.deep == 0 && arr.children[i].type == 'directory' && i == arr.children.length-1

? ? ? ? ? ? ? ? ? ? printFile(arr.children[i], folderName, arr.deep, isLastFile, isLastFolder,isFinal)

? ? ? ? ? ? ? ? ? ? if (arr.children[i].type == 'directory') {

? ? ? ? ? ? ? ? ? ? ? ? //console.log('directory')

? ? ? ? ? ? ? ? ? ? ? ? var t = arr.children[i].path

? ? ? ? ? ? ? ? ? ? ? ? if (i == arr.children.length - 1) {

? ? ? ? ? ? ? ? ? ? ? ? ? ? printFolder(arr.children[i], t, true)

? ? ? ? ? ? ? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? ? ? ? ? ? ? printFolder(arr.children[i], t, false)

? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? function printFile(file, folderName, deep, isLastFile, isLastFolder) {

? ? ? ? ? ? if (file[0] != '.') {

? ? ? ? ? ? ? ? // console.log("Folder:"+folderName)

? ? ? ? ? ? ? ? // console.log(deep)

? ? ? ? ? ? ? ? //console.log(isLastFile)

? ? ? ? ? ? ? ? //console.log(isLastFolder)

? ? ? ? ? ? ? ? var name = file.path.replace(folderName + '/', '')

? ? ? ? ? ? ? ? //console.log(isFinal)

? ? ? ? ? ? ? ? if (deep == 0) {

? ? ? ? ? ? ? ? ? ? if (isLastFile) {

? ? ? ? ? ? ? ? ? ? ? ? console.log('└── ' + name)

? ? ? ? ? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? ? ? ? ? console.log('├── ' + name)

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? if (deep > 0) {

? ? ? ? ? ? ? ? ? ? if (!isLastFolder) {

? ? ? ? ? ? ? ? ? ? ? ? if (!isLastFile) {

? ? ? ? ? ? ? ? ? ? ? ? ? ? console.log('│?? '.repeat(deep) + '├── ' + name)

? ? ? ? ? ? ? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? ? ? ? ? ? ? console.log('│?? '.repeat(deep) + '└── ' + name)

? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? ? ? ? ? if (!isLastFile) {

? ? ? ? ? ? ? ? ? ? ? ? ? ? console.log(' ?? '.repeat(deep) + '├── ' + name)

? ? ? ? ? ? ? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? ? ? ? ? if(isFinal){

? ? ? ? ? ? ? ? ? ? ? ? ? ? console.log('? ? '.repeat(deep) + '└── ' + name)

? ? ? ? ? ? ? ? ? ? ? ? }else{

? ? ? ? ? ? ? ? ? ? ? ? ? var str = '? ? '.repeat(deep) + '└── ' + name

? ? ? ? ? ? ? ? ? ? ? ? ? var temp = str.split('')

? ? ? ? ? ? ? ? ? ? ? ? ? temp[0] = '│'

? ? ? ? ? ? ? ? ? ? ? ? ? ? console.log(temp.join(''))

? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? printFolder(obj[0], '', false)

? ? ? ? console.log('目錄及文件羅列完畢')

我做了很多判斷煞抬,但還是低估了一個目錄它可能的復(fù)雜性(目錄中有空目錄,目錄中有空文件构哺,文件和目錄的多個嵌套等等)革答。因為我開發(fā)的時候是根據(jù)幾個example來進(jìn)行調(diào)節(jié)判斷。當(dāng)我最后寫完曙强,重新再去測試幾個目錄的時候發(fā)現(xiàn)出現(xiàn)上述的file缺陷残拐。 如圖:?

因此需要改寫為遞歸方式。我們不可能根據(jù)別的屬性來進(jìn)行判斷碟嘴。因此我們需要依賴的還是之前的那份目錄結(jié)構(gòu)緩存溪食。

這里也直接貼源碼:

_showList(obj, level) {

? ? ? ? var sourceStruct = obj[0]

? ? ? ? var dirCount = 0

? ? ? ? var fileCount = 0

? ? ? ? ? ? // 字符集

? ? ? ? var charSet = {

? ? ? ? ? ? 'node': '├── ', //節(jié)點

? ? ? ? ? ? 'pipe': '│? ', // 上下鏈接

? ? ? ? ? ? 'last': '└── ', // 最后的file或folder需要回勾

? ? ? ? ? ? 'indent': '? ? ' // 縮進(jìn)

? ? ? ? };

? ? ? ? function log(file, depth, parentHasNextSibling) {

? ? ? ? ? // console.log("log:")

? ? ? ? ? ? if (!parentHasNextSibling && depth.length > 1) {

? ? ? ? ? ? ? ? // Replace a pipe with an indent if the parent does not have a next sibling.

? ? ? ? ? ? ? ? depth[depth.length - 2] = charSet.indent;

? ? ? ? ? ? }

? ? ? ? ? ? if(file.lastIndexOf('/') > -1){

? ? ? ? ? ? ? ? file = file.substring(file.lastIndexOf('/')+1)

? ? ? ? ? ? }

? ? ? ? ? ? console.log(depth.join('') + file);

? ? ? ? }

? ? ? ? // 由于已經(jīng)有緩存數(shù)據(jù)了,因此對數(shù)據(jù)進(jìn)行遍歷搜索

? ? ? ? // 如果type 是file 就不需要繼續(xù)

? ? ? ? // 如果type 是directory 對臨時數(shù)組

? ? ? ? function walk(path, depth, parentHasNextSibling) {

? ? ? ? ? //? console.log(path)

? ? ? ? ? ? var childrenLen = path.length - 1

? ? ? ? ? // console.log(childrenLen)

? ? ? ? ? ? var loop = true

? ? ? ? ? ? if (depth.length >= level) {

? ? ? ? ? ? ? ? loop = false

? ? ? ? ? ? }

? ? ? ? ? ? if (loop) {

? ? ? ? ? ? ? ? path.forEach(function walkChildren(child, index) {

? ? ? ? ? ? ? ? ? ? var newdepth = !!depth ? depth.slice(0) : []

? ? ? ? ? ? ? ? ? ? var isLast = (index >= childrenLen)

? ? ? ? ? ? ? ? ? ? if (isLast) {

? ? ? ? ? ? ? ? ? ? ? ? newdepth.push(charSet.last)

? ? ? ? ? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? ? ? ? ? newdepth.push(charSet.node)

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? if(child.type == "file"){

? ? ? ? ? ? ? ? ? ? ? log(child.name, newdepth, parentHasNextSibling)

? ? ? ? ? ? ? ? ? ? }else{

? ? ? ? ? ? ? ? ? ? ? log(child.path, newdepth, parentHasNextSibling)

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? if (child.type == "directory") {

? ? ? ? ? ? ? ? ? ? ? ? var childPath = child.children

? ? ? ? ? ? ? ? ? ? ? ? if (!isLast) {

? ? ? ? ? ? ? ? ? ? ? ? ? ? newdepth.pop()

? ? ? ? ? ? ? ? ? ? ? ? ? ? newdepth.push(charSet.pipe)

? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? ? ? walk(childPath, newdepth, !isLast)

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? })

? ? ? ? ? ? ? ? loop = !loop

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? walk(sourceStruct.children, [])

? ? ? ? //console.log(sourceStruct)

? ? ? ? console.log('level:' + level)

? ? ? ? console.log('目錄及文件羅列完畢')

? ? },


這里只需要判斷文件類型娜扇,利用遞歸的特性將每個文件的路徑存于newpath错沃,然后判斷長度,如果大于level就跳過雀瓢。具體注釋都在源碼里了捎废。

結(jié)尾

第一個模塊總算完成,四月第一篇文章居然在中旬也算對得起拖延癥了~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末致燥,一起剝皮案震驚了整個濱河市登疗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌嫌蚤,老刑警劉巖辐益,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異脱吱,居然都是意外死亡智政,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門箱蝠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來续捂,“玉大人垦垂,你說我怎么就攤上這事⊙榔埃” “怎么了劫拗?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長矾克。 經(jīng)常有香客問我页慷,道長,這世上最難降的妖魔是什么胁附? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任酒繁,我火速辦了婚禮,結(jié)果婚禮上控妻,老公的妹妹穿的比我還像新娘州袒。我一直安慰自己,他們只是感情好弓候,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布郎哭。 她就那樣靜靜地躺著,像睡著了一般弓叛。 火紅的嫁衣襯著肌膚如雪彰居。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天撰筷,我揣著相機(jī)與錄音陈惰,去河邊找鬼。 笑死毕籽,一個胖子當(dāng)著我的面吹牛抬闯,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播关筒,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼溶握,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蒸播?” 一聲冷哼從身側(cè)響起睡榆,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎袍榆,沒想到半個月后胀屿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡包雀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年宿崭,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片才写。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡葡兑,死狀恐怖奖蔓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情讹堤,我是刑警寧澤吆鹤,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站蜕劝,受9級特大地震影響檀头,放射性物質(zhì)發(fā)生泄漏轰异。R本人自食惡果不足惜岖沛,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望搭独。 院中可真熱鬧婴削,春花似錦、人聲如沸牙肝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽配椭。三九已至虫溜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間股缸,已是汗流浹背衡楞。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留敦姻,地道東北人瘾境。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像镰惦,于是被迫代替她去往敵國和親迷守。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理旺入,服務(wù)發(fā)現(xiàn)兑凿,斷路器,智...
    卡卡羅2017閱讀 134,661評論 18 139
  • 單例模式 適用場景:可能會在場景中使用到對象茵瘾,但只有一個實例礼华,加載時并不主動創(chuàng)建,需要時才創(chuàng)建 最常見的單例模式龄捡,...
    Obeing閱讀 2,073評論 1 10
  • 第2章 基本語法 2.1 概述 基本句法和變量 語句 JavaScript程序的執(zhí)行單位為行(line)卓嫂,也就是一...
    悟名先生閱讀 4,149評論 0 13
  • 1.創(chuàng)建文件夾 !/bin/sh mkdir -m 777 "%%1" 2.創(chuàng)建文件 !/bin/sh touch...
    BigJeffWang閱讀 10,064評論 3 53
  • 1.查看可以被刪除的untracked files git clean -f -n 2.刪除untracked f...
    frank_kk閱讀 15,645評論 0 0