国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

【babel+小程序】記“編寫babel插件”與“通過語法解析替換小程序路由表”的經(jīng)歷

李昌杰 / 1118人閱讀

摘要:而掃描各個(gè)模塊并合并路由表的腳本非常簡(jiǎn)單,讀寫文件就了。編寫插件之前先要理解抽象語法樹這個(gè)概念。的解析器,的配置。編寫腳本識(shí)別字段思路首先獲取到源代碼是類單文件的語法。獲取內(nèi)的字段,并替換成已生成的路由表。

話不多說先上圖,簡(jiǎn)要說明一下干了些什么事。圖可能太模糊,可以點(diǎn)svg看看

背景

最近公司開展了小程序的業(yè)務(wù),派我去負(fù)責(zé)這一塊的業(yè)務(wù),其中需要處理的一個(gè)問題是接入我們web開發(fā)的傳統(tǒng)架構(gòu)--模塊化開發(fā)
我們來詳細(xì)說一下模塊化開發(fā)具體是怎么樣的。
我們的git工作流采用的是git flow。一個(gè)項(xiàng)目會(huì)拆分成幾個(gè)模塊,然后一人負(fù)責(zé)一個(gè)模塊(對(duì)應(yīng)git flow的一個(gè)feature)獨(dú)立開發(fā)。模塊開發(fā)并與后端聯(lián)通后再合并至develop進(jìn)行集成測(cè)試,后續(xù)經(jīng)過一系列測(cè)試再發(fā)布版本。
目錄結(jié)構(gòu)大體如圖所示,一個(gè)模塊包含了他自己的pages / components / assets / model / mixins / apis / routes / scss等等。

這種開發(fā)模式的好處不言而喻,每個(gè)人都可以并行開發(fā),大大提升開發(fā)速度。這次就是要移植這種開發(fā)模式到小程序中。

目標(biāo)

背景說完了,那么來明確一下我們的目標(biāo)。
我采用的是wepy框架,類vue語法的開發(fā),開發(fā)體驗(yàn)非常棒。在vue中,一個(gè)組件就是單文件,包含了js、html、css。wepy采用vue的語法,但由與vue稍稍有點(diǎn)區(qū)別,wepy的組件分為三種--wepy.app類,wepy.page類,wepy.component類。
對(duì)應(yīng)到我們的目錄結(jié)構(gòu)中,每個(gè)模塊實(shí)際上就是一系列的page組件。要組合這一系列的模塊,那么很簡(jiǎn)單,我們要做的就是把這一系列page的路由掃描成一個(gè)路由表,然后插入到小程序的入口--app.json中。對(duì)應(yīng)wepy框架那即是app.wpy中的pages字段。

掃描路由表

第一步!先得到所有pages的路由并綜合成一個(gè)路由表
我的方案是,在每個(gè)模塊中新建一份routes文件,相當(dāng)于注冊(cè)每個(gè)需要插入到入口的page的路由,不需要接入業(yè)務(wù)的page就不用注冊(cè)啦。是不是很熟悉呢,對(duì)的,就是參考vue-router的注冊(cè)語法。

//routes.js
module.exports = [
    {
        name: "home-detail",//TODO: name先占位,后續(xù)再嘗試通過讀name跳轉(zhuǎn)某頁(yè)
        page: "detail",//需要接入入口的page的文件名。例如這里是index.wpy。相對(duì)于src/的路徑就是`modules/${moduleName}/pages/index`。
    },
    {
        name: "home-index",
        page: "index",
        meta: {
            weight: 100//這里加了一個(gè)小功能,因?yàn)樾〕绦蛑付╬ages數(shù)組的第一項(xiàng)為首頁(yè),后續(xù)我會(huì)通過這個(gè)權(quán)重字段來給pages路由排序。權(quán)重越高位置越前。
        }
    }
]

而掃描各個(gè)模塊并合并路由表的腳本非常簡(jiǎn)單,讀寫文件就ok了。

const fs = require("fs")
const path = require("path")

const routeDest = path.join(__dirname, "../src/config/routes.js")
const modulesPath = path.join(__dirname, "../src/modules")

let routes = []

fs.readdirSync(modulesPath).forEach(module => {
    if(module.indexOf(".DS_Store") > -1) return 

    const route = require(`${modulesPath}/${module}/route`)
    route.forEach(item => {
        item.page = `modules/${module}/pages/${item.page.match(//?(.*)/)[1]}`
    })
    routes = routes.concat(route)
})

fs.writeFileSync(routeDest,`module.exports = ${JSON.stringify(routes)}`, e => {
    console.log(e)
})

路由排序策略

const strategies = {
    sortByWeight(routes) {
        routes.sort((a, b) => {
            a.meta = a.meta || {}
            b.meta = b.meta || {}

            const weightA = a.meta.weight || 0
            const weightB = b.meta.weight || 0

            return weightB - weightA
        })
        return routes
    }
}

最后得出路由表

const Strategies = require("../src/lib/routes-model")
const routes = Strategies.sortByWeight(require("../src/config/routes"))
const pages = routes.map(item => item.page)
console.log(pages)//["modules/home/pages/index", "modules/home/pages/detail"]
替換路由數(shù)組

So far so good...問題來了,如何替換入口文件中的路由數(shù)組。我如下做了幾步嘗試。

直接引入

我第一感覺就是,這不很簡(jiǎn)單嗎?在wepy編譯之前,先跑腳本得出路由表,再import這份路由表就得了。

import routes from "./routes"
export default class extends wepy.app {
  config = {
    pages: routes,//["modules/home/pages/index"]
    window: {
      backgroundTextStyle: "light",
      navigationBarBackgroundColor: "#fff",
      navigationBarTitleText: "大家好我是渣渣輝",
      navigationBarTextStyle: "black"
    }
  }
//...
}

然而這樣小程序肯定會(huì)炸啦,pages字段的值必須是靜態(tài)的,在小程序運(yùn)行之前就配置好,動(dòng)態(tài)引入是不行的!不信的話諸君可以試試。那么就是說,劃重點(diǎn)---我們必須在wepy編譯之前再預(yù)編譯一次---事先替換掉pages字段的值!

正則匹配替換

既然要事先替換,那就是要精準(zhǔn)定位pages字段的值,然后再替換掉。難點(diǎn)在于如果精準(zhǔn)定位pages字段的值呢?
最撈然而最快的方法:正則匹配。
事先定好編碼規(guī)范,在pages字段的值的前后添加/* __ROUTES__ */的注釋

腳本如下:

const fs = require("fs")
const path = require("path")
import routes from "./routes"

function replace(source, arr) {
    const matchResult = source.match(//* __ROUTE__ */([sS]*)/* __ROUTE__ *//)
    if(!matchResult) {
        throw new Error("必須包含/* __ROUTE__ */標(biāo)記注釋")
    }
    const str = arr.reduce((pre, next, index, curArr) => {
        return pre += `"${curArr[index]}", `
    }, "")
    return source.replace(matchResult[1], str)
}

const entryFile = path.join(__dirname, "../src/app.wpy")
let entry = fs.readFileSync(entryFile, {encoding: "UTF-8"})

entry = replace(entry, routes)

fs.writeFileSync(entryFile, entry)

app.wpy的變化如下:

//before
export default class extends wepy.app {
  config = {
    pages: [
    /* __ROUTE__ */
    /* __ROUTE__ */
    ],
    window: {
      backgroundTextStyle: "light",
      navigationBarBackgroundColor: "#fff",
      navigationBarTitleText: "大家好我是渣渣輝",
      navigationBarTextStyle: "black"
    }
  }
//...
}
//after
export default class extends wepy.app {
  config = {
    pages: [
/* __ROUTE__ */"modules/home/pages/index", /* __ROUTE__ */
    ],
    window: {
      backgroundTextStyle: "light",
      navigationBarBackgroundColor: "#fff",
      navigationBarTitleText: "大家好我是渣渣輝",
      navigationBarTextStyle: "black"
    }
  }
//...
}

行吧,也總算跑通了。因?yàn)轫?xiàng)目很趕,所以先用這個(gè)方案開發(fā)了一個(gè)半星期。開發(fā)完之后總覺得這種方案太難受,于是密謀著換另一種各精準(zhǔn)的自動(dòng)的方案。。。

babel插件替換全局常量 1.思路

想必大家肯定很熟悉這種模式

let host = "http://www.tanwanlanyue.com/"
if(process.env.NODE_ENV === "production"){
    host = "http://www.zhazhahui.com/"
}

通過這種只在編譯過程中存在的全局常量,我們可以做很多值的匹配。
因?yàn)閣epy已經(jīng)預(yù)編譯了一層,在框架內(nèi)的業(yè)務(wù)代碼是讀取不了process.env.NODE_ENV的值。我就想著要不做一個(gè)類似于webpack的DefinePlugin的babel插件吧。具體的思路是babel編譯過程中訪問ast時(shí)匹配需要替換的標(biāo)識(shí)符或者表達(dá)式,然后替換掉相應(yīng)的值。例如:
In

export default class extends wepy.app {
  config = {
    pages: __ROUTE__,
    window: {
      backgroundTextStyle: "light",
      navigationBarBackgroundColor: "#fff",
      navigationBarTitleText: "大家好我是渣渣輝",
      navigationBarTextStyle: "black"
    }
  }
//...
}

Out

export default class extends wepy.app {
  config = {
    pages: [
        "modules/home/pages/index",
    ],
    window: {
      backgroundTextStyle: "light",
      navigationBarBackgroundColor: "#fff",
      navigationBarTitleText: "大家好我是渣渣輝",
      navigationBarTextStyle: "black"
    }
  }
//...
}
2.學(xué)習(xí)如何編寫babel插件

在這里先要給大家推薦幾份學(xué)習(xí)資料:
首先是babel官網(wǎng)推薦的這份迷你編譯器的代碼,讀完之后基本能理解編譯器做的三件事:解析,轉(zhuǎn)換,生成的過程了。
其次是編寫B(tài)abel插件入門手冊(cè)。基本涵蓋了編寫插件的方方面面,不過由于babel幾個(gè)工具文檔的缺失,在寫插件的時(shí)候需要去翻查代碼中的注釋閱讀api用法。
然后是大殺器AST轉(zhuǎn)換器--astexplorer.net。我們來看一下,babel的解析器--babylon的文檔,涵蓋的節(jié)點(diǎn)類型這么多,腦繪一張AST樹不現(xiàn)實(shí)。我在編寫腳本的時(shí)候會(huì)先把代碼放在轉(zhuǎn)換器內(nèi)生成AST樹,再一步一步走。

編寫babel插件之前先要理解抽象語法樹這個(gè)概念。編譯器做的事可以總結(jié)為:解析,轉(zhuǎn)換,生成。具體的概念解釋去看入門手冊(cè)可能會(huì)更好。這里講講我自己的一些理解。

解析包括詞法分析與語法分析。
解析過程吧。其實(shí)按我的理解(不知道這樣合適不合適= =)抽象語法樹跟DOM樹其實(shí)很類似。詞法分析有點(diǎn)像是把html解析成一個(gè)一個(gè)的dom節(jié)點(diǎn)的過程,語法分析則有點(diǎn)像是將dom節(jié)點(diǎn)描述成dom樹。

轉(zhuǎn)換過程是編譯器最復(fù)雜邏輯最集中的地方。首先要理解“樹形遍歷”與“訪問者模式”兩個(gè)概念。

“樹形遍歷”如手冊(cè)中所舉例子:
假設(shè)有這么一段代碼:

function square(n) {
  return n * n;
}

那么有如下的樹形結(jié)構(gòu):

- FunctionDeclaration
  - Identifier (id)
  - Identifier (params[0])
  - BlockStatement (body)
    - ReturnStatement (body)
      - BinaryExpression (argument)
        - Identifier (left)
        - Identifier (right)

進(jìn)入FunctionDeclaration

進(jìn)入Identifier (id)

走到盡頭

退出Identifier (id)

進(jìn)入Identifier (params[0])

走到盡頭

退出Identifier (params[0])

進(jìn)入BlockStatement (body)

進(jìn)入 ReturnStatement (body)

進(jìn)入 BinaryExpression (argument)

進(jìn)入 Identifier (left)

退出 Identifier (left)

進(jìn)入 Identifier (right)

退出 Identifier (right)

退出 BinaryExpression (argument)

退出 ReturnStatement (body)

退出BlockStatement (body)

“訪問者模式”則可以理解為,進(jìn)入一個(gè)節(jié)點(diǎn)時(shí)被調(diào)用的方法。例如有如下的訪問者:

const idVisitor = {
  Identifier() {//在進(jìn)行樹形遍歷的過程中,節(jié)點(diǎn)為標(biāo)識(shí)符時(shí),訪問者就會(huì)被調(diào)用
    console.log("visit an Identifier")
  }
}

結(jié)合樹形遍歷來看,就是說每個(gè)訪問者有進(jìn)入、退出兩次機(jī)會(huì)來訪問一個(gè)節(jié)點(diǎn)。
而我們這個(gè)替換常量的插件的關(guān)鍵之處就是在于,訪問節(jié)點(diǎn)時(shí),通過識(shí)別節(jié)點(diǎn)為我們的目標(biāo),然后替換他的值!

3.動(dòng)手寫插件

話不多說,直接上代碼。這里要用到的一個(gè)工具是babel-types,用來檢查節(jié)點(diǎn)。

難度其實(shí)并不大,主要工作在于熟悉如何匹配目標(biāo)節(jié)點(diǎn)。如匹配memberExpression時(shí)使用matchesPattern方法,匹配標(biāo)識(shí)符則直接檢查節(jié)點(diǎn)的name等等套路。最終成品及用法可以見我的github

const memberExpressionMatcher = (path, key) => path.matchesPattern(key)//復(fù)雜表達(dá)式的匹配條件
const identifierMatcher = (path, key) => path.node.name === key//標(biāo)識(shí)符的匹配條件

const replacer = (path, value, valueToNode) => {//替換操作的工具函數(shù)
    path.replaceWith(valueToNode(value))

    if(path.parentPath.isBinaryExpression()){//轉(zhuǎn)換父節(jié)點(diǎn)的二元表達(dá)式,如:var isProp = __ENV__ === "production"   ===>   var isProp = true
        const result = path.parentPath.evaluate()
        if(result.confident){
            path.parentPath.replaceWith(valueToNode(result.value))
        }
    }
}

export default function ({ types: t }){//這里需要用上babel-types這個(gè)工具
    return {
        visitor: {
            MemberExpression(path, { opts: params }){//匹配復(fù)雜表達(dá)式
                Object.keys(params).forEach(key => {//遍歷Options
                    if(memberExpressionMatcher(path, key)){
                        replacer(path, params[key], t.valueToNode)
                    }
                })
            },
        
            Identifier(path, { opts: params }){//匹配標(biāo)識(shí)符
                Object.keys(params).forEach(key => {//遍歷Options
                    if(identifierMatcher(path, key)){
                        replacer(path, params[key], t.valueToNode)
                    }           
                })
            },
        }        
    }
}
4.結(jié)果

當(dāng)然啦,這塊插件不可以寫在wepy.config.js中配置。因?yàn)楸仨氃趙epy編譯之前執(zhí)行我們的編譯腳本,替換pages字段。所以的方案是在跑wepy build --watch
之前跑我們的編譯腳本,具體操作是引入babel-core來轉(zhuǎn)換代碼

const babel = require("babel-core")
//...省略獲取app.wpy過程,待會(huì)會(huì)談到。
//...省略編寫visitor過程,語法跟編寫插件略有一點(diǎn)點(diǎn)不同。
const result = babel.transform(code, {
    parserOpts: {//babel的解析器,babylon的配置。記得加入classProperties,否則會(huì)無法解析app.wpy的類語法
        sourceType: "module",
        plugins: ["classProperties"]
    },
    plugins: [
        [{
            visitor: myVistor//使用我們寫的訪問者
        }, {
            __ROUTES__: pages//替換成我們的pages數(shù)組
        }],
    ],
})

當(dāng)然最終我們是轉(zhuǎn)換成功啦,這個(gè)插件也用上了生產(chǎn)環(huán)境。但是后來沒有采用這方案替換pages字段。暫時(shí)只替換了__ENV__: process.env.NODE_ENV__VERSION__: version兩個(gè)常量。
為什么呢?
因?yàn)槊看尉幾g之后標(biāo)識(shí)符__ROUTES__都會(huì)被轉(zhuǎn)換成我們的路由表,那么下次我想替換的時(shí)候難道要手動(dòng)刪掉然后再加上__ROUTES__嗎?我當(dāng)然不會(huì)干跟我們自動(dòng)化工程化的思想八字不合的事情啦。
不過寫完這個(gè)插件之后收獲還是挺大的,基本了解該如何通過編譯器尋找并替換我們的目標(biāo)節(jié)點(diǎn)了。

編寫babel腳本識(shí)別pages字段 1.思路

首先獲取到源代碼:app.wpy是類vue單文件的語法。js都在script標(biāo)簽內(nèi),那么怎么獲取這部分代碼呢?又正則?不好吧,太撈了。通過閱讀wepy-cli的源碼,使用xmldom這個(gè)庫(kù)來解析,獲取script標(biāo)簽內(nèi)的代碼。

編寫訪問者遍歷并替換節(jié)點(diǎn):首先是找到繼承自wepy.app的類,再找到config字段,最后匹配key為pages的對(duì)象的值。最后替換目標(biāo)節(jié)點(diǎn)

babel轉(zhuǎn)換為代碼后,通過讀寫文件替換目標(biāo)代碼。大業(yè)已成!done!

2.成果

最終腳本:

/**
 * @author zhazheng
 * @description 在wepy編譯前預(yù)編譯。獲取app.wpy內(nèi)的pages字段,并替換成已生成的路由表。
 */
const babel = require("babel-core")
const t = require("babel-types")

//1.引入路由
const Strategies = require("../src/lib/routes-model")
const routes = Strategies.sortByWeight(require("../src/config/routes"))
const pages = routes.map(item => item.page)

//2.解析script標(biāo)簽內(nèi)的js,獲取code
const xmldom = require("xmldom")
const fs = require("fs")
const path = require("path")

const appFile = path.join(__dirname, "../src/app.wpy")
const fileContent = fs.readFileSync(appFile, { encoding: "UTF-8" })
let xml = new xmldom.DOMParser().parseFromString(fileContent)

function getCodeFromScript(xml){
    let code = ""
    Array.prototype.slice.call(xml.childNodes || []).forEach(child => {
        if(child.nodeName === "script"){
            Array.prototype.slice.call(child.childNodes || []).forEach(c => {
                code += c.toString()
            })
        }
    })
    return code
}
const code = getCodeFromScript(xml)

// 3.在遍歷ast樹的過程中,嵌套三層visitor去尋找節(jié)點(diǎn)
//3.1.找class,父類為wepy.app
const appClassVisitor = {
    Class: {
        enter(path, state) {
            const classDeclaration = path.get("superClass")
            if(classDeclaration.matchesPattern("wepy.app")){
                path.traverse(configVisitor, state)
            }
        }
    }
}
//3.2.找config
const configVisitor = {
    ObjectExpression: {
        enter(path, state){
            const expr = path.parentPath.node
            if(expr.key && expr.key.name === "config"){
                path.traverse(pagesVisitor, state)
            }
        }
    }
}
//3.3.找pages,并替換
const pagesVisitor = {
    ObjectProperty: {
        enter(path, { opts }){
            const isPages = path.node.key.name === "pages"
            if(isPages){
                path.node.value = t.valueToNode(opts.value)
            }
        }
    }
}

// 4.轉(zhuǎn)換并生成code
const result = babel.transform(code, {
    parserOpts: {
        sourceType: "module",
        plugins: ["classProperties"]
    },
    plugins: [
        [{
            visitor: appClassVisitor
        }, {
            value: pages
        }],
    ],
})

// 5.替換源代碼
fs.writeFileSync(appFile, fileContent.replace(code, result.code))
3.使用方法

只需要在執(zhí)行wepy build --watch之前先執(zhí)行這份腳本,就可自動(dòng)替換路由表,自動(dòng)化操作。監(jiān)聽文件變動(dòng),增加模塊時(shí)自動(dòng)重新跑腳本,更新路由表,開發(fā)體驗(yàn)一流~

結(jié)語

把代碼往更自動(dòng)化更工程化的方向?qū)懀@樣的過程收獲還是挺大的。但是確實(shí)這份腳本仍有不足之處,起碼匹配節(jié)點(diǎn)這部分的代碼是不大嚴(yán)謹(jǐn)?shù)摹?br>另外插播一份廣告
我司風(fēng)變科技正招聘前端開發(fā):

應(yīng)屆、一年經(jīng)驗(yàn),熟悉Vue的前端小鮮肉

三年經(jīng)驗(yàn)的前端大佬

我!們!都!想!要!
我們開發(fā)團(tuán)隊(duì)不僅代碼寫的好,而且男程序員還擁有著100%的脫單率!!快來加入我們吧!

郵箱:nicolas_refn@foxmail.com

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/107233.html

相關(guān)文章

  • Babel 插件原理理解深入

    摘要:抽象語法樹是怎么生成的談到這點(diǎn),就要說到計(jì)算機(jī)是怎么讀懂我們的代碼的。需要注意什么狀態(tài)狀態(tài)是抽象語法樹轉(zhuǎn)換的敵人,狀態(tài)管理會(huì)不斷牽扯我們的精力,而且?guī)缀跛心銓?duì)狀態(tài)的假設(shè),總是會(huì)有一些未考慮到的語法最終證明你的假設(shè)是錯(cuò)誤的。 現(xiàn)在談到 babel 肯定大家都不會(huì)感覺到陌生,雖然日常開發(fā)中很少會(huì)直接接觸到它,但它已然成為了前端開發(fā)中不可或缺的工具,不僅可以讓開發(fā)者可以立即使用 ES 規(guī)范...

    draveness 評(píng)論0 收藏0
  • 從0到1:微信程序自選股項(xiàng)目回憶錄

    摘要:小程序自選股項(xiàng)目團(tuán)隊(duì)在長(zhǎng)達(dá)五個(gè)月的時(shí)間里經(jīng)歷了太多不為人知的故事,不知通宵了多少個(gè)夜晚,只為等待小龍宣布號(hào)小程序正式發(fā)布的到來。第一次被微信開放平臺(tái)開發(fā)二組團(tuán)隊(duì)小程序項(xiàng)目團(tuán)隊(duì)當(dāng)小白鼠各種實(shí)驗(yàn)新特性。。 導(dǎo)語:我很喜歡小龍的一句話,微信因你而變。是的,把事情做到極致的時(shí)候,這個(gè)世界就會(huì)因你而變。 小程序自選股項(xiàng)目團(tuán)隊(duì)在長(zhǎng)達(dá)五個(gè)月的時(shí)間里經(jīng)歷了太多不為人知的故事, 不知通宵了多少個(gè)夜晚,只...

    feng409 評(píng)論0 收藏0
  • 前端最強(qiáng)面經(jīng)匯總

    摘要:獲取的對(duì)象范圍方法獲取的是最終應(yīng)用在元素上的所有屬性對(duì)象即使沒有代碼,也會(huì)把默認(rèn)的祖宗八代都顯示出來而只能獲取元素屬性中的樣式。因此對(duì)于一個(gè)光禿禿的元素,方法返回對(duì)象中屬性值如果有就是據(jù)我測(cè)試不同環(huán)境結(jié)果可能有差異而就是。 花了很長(zhǎng)時(shí)間整理的前端面試資源,喜歡請(qǐng)大家不要吝嗇star~ 別只收藏,點(diǎn)個(gè)贊,點(diǎn)個(gè)star再走哈~ 持續(xù)更新中……,可以關(guān)注下github 項(xiàng)目地址 https:...

    wangjuntytl 評(píng)論0 收藏0
  • 錨點(diǎn)定位

    摘要:錨點(diǎn)定位雷佳音編輯雷佳音,年月日出生于遼寧省鞍山市,中國(guó)內(nèi)地男演員,年畢業(yè)于上海戲劇學(xué)院表演系。年月借話劇個(gè)人獲得有話劇界奧斯卡之稱的第屆佐臨獎(jiǎng)最具潛力新人獎(jiǎng)。年月日,雷佳音現(xiàn)身電影超時(shí)空同居開機(jī)儀式舉行。 錨點(diǎn)定位雷佳音 編輯雷佳音,1983年8月29日出生于遼寧省鞍山市,中國(guó)內(nèi)地男演員,2006年畢業(yè)于上海戲劇學(xué)院表演系。2010年,因飾演電視劇《杜拉拉升職記》里的約翰常而...

    ivydom 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<