亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > JavaScript > 正文

CascadeView級聯組件實現思路詳解(分離思想和單鏈表)

2019-11-20 10:16:33
字體:
來源:轉載
供稿:網友

本文介紹自己最近做省市級聯的類似的級聯功能的實現思路,為了盡可能地做到職責分離跟表現與行為分離,這個功能拆分成了2個組件并用到了單鏈表來實現關鍵的級聯邏輯,下一段有演示效果的gif圖。雖然這是個很常見的功能,但是本文的實現邏輯清晰,代碼好理解,脫離了省市級聯這樣的語義,考慮了表現與行為的分離,希望本文的內容能夠為你的工作帶來一些參考的價值,歡迎閱讀和指正。

Cascade 級聯操作

CascadeType. PERSIST 級聯持久化 ( 保存 ) 操作

CascadeType. MERGE 級聯更新 ( 合并 ) 操作

CascadeType. REFRESH 級聯刷新操作,只會查詢獲取操作

CascadeType. REMOVE 級聯刪除操作

CascadeType. ALL 級聯以上全部操作

Fetch 抓取是否延遲加載,默認情況一的方為立即加載,多的一方為延遲加載

mappedBy 關系維護

mappedBy= "parentid" 表示在children 類中的 parentid 屬性來維護關系,這個名稱必須和children 類中的 parentid屬性名稱完全一致才行。

另外需要注意,parent類中的集合類型必須是List或者Set,不能設置為ArrayList,否則會報錯

演示效果(代碼下載,注:該效果需要http才能運行,另外效果中的數據是模擬數據,并不是后臺真實返回的,所以看到的省市縣的下拉數據都是一樣的):

注:本文用到了前面幾篇相關博客的技術實現,如果有需要的話可以點擊下面的鏈接前去了解:

1)詳解Javascript的繼承實現:提供一個class.js,用來定義javascript的類和構建類的繼承關系;

2)jquery技巧之讓任何組件都支持類似DOM的事件管理:提供一個eventBase.js,用來給任意組件實例提供類似DOM的事件管理功能;

3)對jquery的ajax進行二次封裝以及ajax緩存代理組件:AjaxCache:提供ajax.js和ajaxCache.js,簡化jquery的ajax調用,以及對請求進行客戶端的緩存代理。

下面先來詳細了解下這個功能的要求。

1. 功能分析

以包含三個級聯項的級聯組件來說明這個功能:

1)每個級聯項可能需要一個用作輸入提示的option:

這種情況每個級聯項的數據列表中都能選擇一個空的option(就是輸入提示的那個):

也可能不需要用作輸入提示的option:

這種情況每個級聯項的數據列表中只能選數據option,選不到空的option:

2)如果當前這個頁面是從數據庫中查詢出來跟級聯組件對應的字段有值,那么就把查詢出來的值回顯到級聯組件上:

如果查詢出來的對應字段沒有值,那么就按第1)點需求描述的2種情況顯示。

3)各個級聯項在數據結構上構成單鏈表的關系,后一個級聯項的數據列表,跟前一個級聯項所選擇的數據有關聯的。

4)考慮到性能方面的問題,各個級聯項的數據列表都采用ajax異步加載顯示。

5)在級聯組件初始化完成以后,自動加載第一個級聯項的列表。

6)當前一個級聯項發生改變時,清空后面所有直接或間接關聯的級聯項的數據列表,同時如果前一個級聯項改變后的值不為空則自動加載跟它直接關聯的下一個級聯項的數據列表。清空級聯項的數據列表時要注意:如果級聯項需要顯示輸入提示的option,在清空的時候得保留該option。

7)要充分考慮性能問題,避免重復加載。

8)考慮到表單提交的問題,當級聯組件任意級聯項發生改變后,得把級聯組件所選的值體現到一個隱藏的文本域內,方便把級聯組件的值通過該文本域提交到后臺。

功能大致如上。

2. 實現思路

1)數據結構

級聯組件跟別的組件不太一樣的是,它跟后臺的數據有一些依賴,我考慮的比較好實現的數據結構是:

{"id": 1,"text": "北京市","code": 110000,"parentId": 0},{"id": 2,"text": "河北省","code": 220000,"parentId": 0},{"id": 3,"text": "河南省","code": 330000,"parentId": 0}

id是數據的唯一標識,數據之間的關聯關系通過parentId來構建,text,code這種都屬于普通的業務字段。如果按這個數據結構,我們查詢級聯項數據列表的接口就會變得很簡單:

//查第一個級聯項的列表/api/cascade?parentId=0//根據第一個級聯項選的值,查第二個級聯項的列表/api/cascade?parentId=1//根據第二個級聯項選的值,查第三個級聯項的列表/api/cascade?parentId=4

這個結構對于后臺來說也很好處理,雖然在結構上它們是一種樹形的表結構,但是查詢都是單層的,所以很好實現。

從前面的查詢演示也能夠看出,這個結構能夠很方便地幫我們把數據查詢的接口和參數統一成一個,這對于組件開發來說是一個很方便的事情。我們從后臺拿到這個數據結構之后,把每一條數據解析成一個option,如<option value=”北京市” data-param-value=”1”>北京市</option>,這樣既能完成數據列表的下拉顯示,還能通過select這個表單元素的作用收集到當前級聯項所選中的值,最后當級聯項發生改變的時候,還能夠獲取到選中的option,把它上面存儲的data-param-value的值作為parentId這個參數,去加載下一個級聯項的列表。這也是級聯組件數據查詢和解析的思路。

但是這里面還需要考慮的是靈活性的問題,在實際的項目中,可能級聯組件的數據結構是按id parentId這種類似的關聯關系定義的,但是它們的字段不一定是叫id parentId text code,很有可能是別的字段。也就是說:在把數據解析成option的時候,option的text還有value到底用什么字段來解析,以及data-param-value這個屬性的用什么字段的值,都是不確定的;還有查詢數據時用的參數名稱parentId也不能是死的,有的時候如果后臺人員先寫好了查詢接口,用了別的名稱,你不可能要求人家去改他的參數名稱,因為他那邊是需要編譯再部署的,相比前端更麻煩一些;還有parentId=0這個0值也是不能固定,因為實際項目中第一層的數據的parentid有可能是空,也有可能是-1。這些東西都得設計成option,一方面提供默認值,同時留給外部根據實際情況來設置,比如本文最終的實現中這個option都是這樣定義的:

textField: 'text', //返回的數據中要在<option>元素內顯示的字段名稱
valueField: 'text', //返回的數據中要設置在<option>元素的value上的字段名稱
paramField: 'id', //當調用數據查詢接口時,要傳遞給后臺的數據對應的字段名稱
paramName: 'parentId', //當調用數據查詢接口時,跟在url后面傳遞數據的參數名
defaultParam: '', //當查詢第一個級聯項時,傳遞給后臺的值,一般是0,'',或者-1等,表示要查詢第上層的數據

2)html結構

根據前面的功能分析的第1條,級聯組件的初始html結構有2種:

<ul id="licenseLocation-view" class="cascade-view clearfix"><li><select class="form-control"><option value="">請選擇省份</option></select></li><li><select class="form-control"><option value="">請選擇城市</option></select></li><li><select class="form-control"><option value="">請選擇區縣</option></select></li></ul>

<ul id="companyLocation-view" class="cascade-view clearfix"><li><select class="form-control"></select></li><li><select class="form-control"></select></li><li><select class="form-control"></select></li></ul>

這兩個結構唯一的區別就在于是否配置了用作輸入提示的option。另外需要注意的是如果需要這個空的option,一定得把value屬性設置成空,否則這個空的option在表單提交的時候會把option的提示信息提交到后臺。

這兩個結構最關鍵的是select元素,跟ul和li沒有任何關系,ul跟li是為了UI而用到的;select元素沒有任何語義,不用去標識哪個是省份,哪個是城市,哪個是區縣。從功能上來說,一個select代表一個級聯項,這些select在哪定義都不重要,我們只要告訴級聯組件,它的級聯項由哪些select元素構成就行了,唯一需要額外告訴組件的就是這些select元素的先后關系,但是這個通常都是用元素在html中的默認順序來控制的。這個結構能夠幫助我們把組件的功能盡可能地做到表現與行為分離。

3)職責分離和單鏈表的運用

從前面的部分也差不多能看出來了,這個級聯組件如果按職責劃分,可以分成兩個核心的組件,一個負責整體功能和內部級聯項的管理(CascadeView),另一個負責級聯項的功能實現(CascadeItem)。另外為了更方便地實現級聯的邏輯,我們只需要把所有的級聯項通過鏈表連起來,通過發布-訂閱模式,后一個級聯項訂閱前一個級聯項發生改變的消息;當前面的級聯項發生改變的時候,發布消息,通知后面的級聯項去處理相關邏輯;通過鏈表的作用,這個消息可能可以一直傳遞到最后一個級聯項為止。用圖來描述的話,大致就是這個樣子:

我們需要做的就是控制好消息的發布跟傳遞。

4)表單提交

為了能夠方便地將級聯組件的值提交到后臺,可以把整個級聯組件當成一個整體,對外提供一個onChanged事件,外部可通過這個事件獲取所有級聯項的值。由于存在多個級聯項,所以在發布onChanged這個事件時,只能在任意級聯項發生改變的時候,都去觸發這個事件。

5)ajax緩存

在這個組件里面得考慮兩個層級的ajax緩存,第一個是組件這一層級的,比如我把第一個級聯項切換到了北京,這個時候第二個級聯項就把北京的數據加載出來了,然后我把第一個級聯項從北京切換到河北再切換到北京,這個時候第二個級聯項要顯示的還是北京的關聯數據列表,如果我們在第一次加載這個列表的時候就把它的數據緩存下來了,那么這次就不用發起ajax請求了;第二個是ajax請求這一層級的,假如頁面上有多個級聯組件,我先把第一個級聯組件的第一個級聯項切換到北京,瀏覽器發起一個ajax請求加載數據,當我再把第二個級聯組件的第一個級聯項切換到北京的時候,瀏覽器還會再發一個請求去加載數據,如果我把第一個組件第一次ajax請求的返回的數據,先緩存起來,當第二個組件,用同樣的參數請求同樣的接口時,直接拿之前緩存覺得結果返回,這樣也能減少一次ajax請求。第二個層級的ajax緩存依賴上文《對jquery的ajax進行二次封裝以及ajax緩存代理組件:AjaxCache》,對于組件來說,它內部只實現了第一個層級的緩存,但是它不用考慮第二個層級的緩存,因為第二個層級的緩存實現對它來說是透明的,它不知道它用到的ajax組件有緩存的功能。

3. 實現細節

最終的實現包含了三個組件,CascadeView、CascadeItem、CascadePublicDefaults,前面兩個是組件的核心,最后一個只是用來定義一些option,它的作用在CascadeItem的注釋里面有詳細的描述。另外在下面的代碼中有非常詳細的注釋解釋了一些關鍵代碼的作用,結合著前面的需求來看代碼,應該還是比較容易理解的。我以前傾向于用文字來解釋一些實現細節,后來我慢慢覺得這種方式有點費力不討好,第一是細節層面的語言不好組織,有的時候言不達意,明明想把一件事情解釋清楚,結果反而弄得更加迷糊,至少我自己看自己寫的東西就會這樣的感觸;第二是本身開發人員都具有閱讀源碼的能力,而且大部分積極的開發人員都愿意通過琢磨別人的代碼來理解實現思路;所以我改用注釋的方式來說明實現細節:)

CascadePublicDefaults:

define(function () {return {url: '',//數據查詢接口textField: 'text', //返回的數據中要在<option>元素內顯示的字段名稱valueField: 'text', //返回的數據中要設置在<option>元素的value上的字段名稱paramField: 'id', //當調用數據查詢接口時,要傳遞給后臺的數據對應的字段名稱paramName: 'parentId', //當調用數據查詢接口時,跟在url后面傳遞數據的參數名defaultParam: '', //當查詢第一個級聯項時,傳遞給后臺的值,一般是0,'',或者-1等,表示要查詢第上層的數據keepFirstOption: true, //是否保留第一個option(用作輸入提示,如:請選擇省份),如果為true,在重新加載級聯項時,不會清除默認的第一個optionresolveAjax: function (res) {return res;}//因為級聯項在加載數據的時候會發異步請求,這個回調用來解析異步請求返回的響應}});

CascadeView:

define(function (require, exports, module) {var $ = require('jquery');var Class = require('mod/class');var EventBase = require('mod/eventBase');var PublicDefaults = require('mod/cascadePublicDefaults');var CascadeItem = require('mod/cascadeItem');/*** PublicDefaults的作用見CascadeItem組件內的注釋*/var DEFAULTS = $.extend({}, PublicDefaults, {$elements: undefined, //級聯項jq對象的數組,元素在數據中的順序代表級聯的先后順序valueSeparator: ',', //獲取所有級聯項的值時使用的分隔符,如果是英文逗號,返回的值形如 北京市,區,朝陽區values: '', //用valueSeparator分隔的字符串,表示初始時各個select的值onChanged: $.noop //當任意級聯項的值發生改變的時候會觸發這個事件});var CascadeView = Class({instanceMembers: {init: function (options) {//通過this.base調用父類EventBase的init方法this.base();var opts = this.options = this.getOptions(options),items = this.items = [],that = this,$elements = opts.$elements,values = opts.values.split(opts.valueSeparator);this.on('changed.cascadeView', $.proxy(opts.onChanged, this));$elements && $elements.each(function (i) {var $el = $(this);//實例化CascadeItem組件,并把每個實例的prevItem屬性指向前一個實例//第一個prevItem屬性設置為undefinedvar cascadeItem = new CascadeItem($el, $.extend(that.getItemOptions(), {prevItem: i == 0 ? undefined : items[i - 1],value: $.trim(values[i])}));items.push(cascadeItem);//每個級聯項實例發生改變都會觸發CascadeView組件的changed事件//外部可在這個回調內處理業務邏輯//比如將所有級聯項的值設置到一個隱藏域里面,用于表單提交cascadeItem.on('changed.cascadeItem', function () {that.trigger('changed.cascadeView', that.getValue());});});//初始化完成自動加載第一個級聯項items.length && items[0].load();},getOptions: function (options) {return $.extend({}, this.getDefaults(), options);},getDefaults: function () {return DEFAULTS;},getItemOptions: function () {var opts = {}, _options = this.options;for (var i in PublicDefaults) {if (PublicDefaults.hasOwnProperty(i) && i in _options) {opts[i] = _options[i];}}return opts;},//獲取所有級聯項的值,是一個用valueSeparator分隔的字符串//為空的級聯項的值不會返回getValue: function () {var value = [];this.items.forEach(function (item) {var val = $.trim(item.getValue());val != '' && value.push(val);});return value.join(this.options.valueSeparator);}},extend: EventBase});return CascadeView;});

CascadeItem:

define(function (require, exports, module) {var $ = require('jquery');var Class = require('mod/class');var EventBase = require('mod/eventBase');var PublicDefaults = require('mod/cascadePublicDefaults');var AjaxCache = require('mod/ajaxCache');//這是一個可緩存的Ajax組件var Ajax = new AjaxCache();/*** 有一部分option定義在PublicDefaults里面,因為CascadeItem組件不會被外部直接使用* 外部用的是CascadeView組件,所以有一部分的option必須變成公共的,在CascadeView組件也定義一次* 外部通過CascadeView組件傳遞所有的option* CascadeView內部實例化CascadeItem的時候,再把PublicDefaults內的option傳遞給CascadeItem*/var DEFAULTS = $.extend({}, PublicDefaults, {prevItem: undefined, // 指向前一個級聯項value: '' //初始時顯示的value});var CascadeItem = Class({instanceMembers: {init: function ($el, options) {//通過this.base調用父類EventBase的init方法this.base($el);this.$el = $el;this.options = this.getOptions(options);this.prevItem = this.options.prevItem; //前一個級聯項this.hasContent = false;//這個變量用來控制是否需要重新加載數據this.cache = {};//用來緩存數據var that = this;//代理select元素的change事件$el.on('change', function () {that.trigger('changed.cascadeItem');});//當前一個級聯項的值發生改變的時候,根據需要做清空和重新加載數據的處理this.prevItem && this.prevItem.on('changed.cascadeItem', function () {//只要前一個的值發生改變并且自身有內容的時候,就得清空內容that.hasContent && that.clear();//如果不是第一個級聯項,同時前一個級聯項沒有選中有效的option時,就不處理if (that.prevItem && $.trim(that.prevItem.getValue()) == '') return;that.load();});var value = $.trim(this.options.value);value !== '' && this.one('render.cascadeItem', function () {//設置初始值that.$el.val(value.split(','));//通知后面的級聯項做清空和重新加載數據的處理that.trigger('changed.cascadeItem');});},getOptions: function (options) {return $.extend({}, this.getDefaults(), options);},getDefaults: function () {return DEFAULTS;},clear: function () {var $el = this.$el;$el.val('');if (this.options.keepFirstOption) {//保留第一個option$el.children().filter(':gt(0)').remove();} else {//清空全部$el.html('');}//通知后面的級聯項做清空和重新加載數據的處理this.trigger('changed.cascadeItem');this.hasContent = false;//表示內容為空},load: function () {var opts = this.options,paramValue,that = this,dataKey;//dataKey是在cache緩存時用的鍵名//由于第一個級聯項的數據是頂層數據,所以在緩存的時候用的是固定且唯一的鍵:root//其它級聯項的數據緩存時用的鍵名跟前一個選擇的option有關if (!this.prevItem) {paramValue = opts.defaultParam;dataKey = 'root';} else {paramValue = this.prevItem.getParamValue();dataKey = paramValue;}//先看數據緩存中有沒有加載過的數據,有就直接顯示出來,避免Ajaxif (dataKey in this.cache) {this.render(this.cache[dataKey]);} else {var params = {};params[opts.paramName] = paramValue;Ajax.get(opts.url, params).done(function (res) {//resolveAjax這個回調用來在外部解析ajax返回的數據//它需要返回一個data數組var data = opts.resolveAjax(res);if (data) {that.cache[dataKey] = data;that.render(data);}});}},render: function (data) {var html = [],opts = this.options;data.forEach(function (item) {html.push(['<option value="',item[opts.valueField],'" data-param-value="',//將paramField對應的值存放在option的data-param-value屬性上item[opts.paramField],'">',item[opts.textField],'</option>'].join(''));});//采用append的方式動態添加,避免影響第一個option//最后還要把value設置為空this.$el.append(html.join('')).val('');this.hasContent = true;//表示有內容this.trigger('render.cascadeItem');},getValue: function () {return this.$el.val();},getParamValue: function () {return this.$el.find('option:selected').data('paramValue');}},extend: EventBase});return CascadeItem;});

4. demo說明

演示代碼的結構:

其中框起來的就是演示的相關部分。html/regist.html是演示效果的頁面,js/app/regist.js是演示效果的入口js:

define(function (require, exports, module) {  var $ = require('jquery');  var CascadeView = require('mod/cascadeView');  function publicSetCascadeView(fieldName, opts) {    this.cascadeView = new CascadeView({      $elements: $('#' + fieldName + '-view').find('select'),      url: '../api/cascade.json',      onChanged: this.onChanged,      values: opts.values,      keepFirstOption: this.keepFirstOption,      resolveAjax: function (res) {        if (res.code == 200) {          return res.data;        }      }    });  }  var LOCATION_VIEWS = {    licenseLocation: {      $input: $('input[name="licenseLocation"]'),      keepFirstOption: true,      setCascadeView: publicSetCascadeView,      onChanged: function(e, value){        LOCATION_VIEWS.licenseLocation.$input.val(value);      }    },    companyLocation: {      $input: $('input[name="companyLocation"]'),      keepFirstOption: false,      setCascadeView: publicSetCascadeView,      onChanged: function(e, value){        LOCATION_VIEWS.companyLocation.$input.val(value);      }    }  };  LOCATION_VIEWS.licenseLocation.setCascadeView('licenseLocation', {    values: LOCATION_VIEWS.licenseLocation.$input.val()  });  LOCATION_VIEWS.companyLocation.setCascadeView('companyLocation', {    values: LOCATION_VIEWS.companyLocation.$input.val()  });});

注意以上代碼中LOCATION_VIEWS這個變量的作用,因為頁面上有多個級聯組件,這個變量其實是通過策略模式,把各個組件的相關的東西都用一種類似的方式管理起來而已。如果不這么做的話,很容易產生重復代碼;這種形式也比較有利于在入口文件這種處理業務邏輯的地方,進行一些業務邏輯的分離與封裝。

5. others

這估計是在現在公司寫的最后一篇博客,過兩天就得去新單位去上班了,不確定還能否有這么多空余的時間來記錄平常的工作思路,但是好歹已經培養了寫博客的習慣,將來沒時間也會擠出時間來的。今年的目標主要是拓寬知識面,提高代碼質量,后續的博客更多還是在組件化開發這個類別上,希望以后能夠得到大家的繼續關注武林網網站!

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美成人免费播放| 一区二区国产精品视频| 欧美极度另类性三渗透| 日韩有码视频在线| 亚洲第一福利网站| 久久欧美在线电影| 欧美激情区在线播放| 国产精品一区二区久久久久| 亚洲一区第一页| 日韩国产中文字幕| 国产精品天天狠天天看| 国产精品一区二区在线| 欧美一级在线亚洲天堂| 亚洲精品久久久久中文字幕二区| 久久综合久久88| 日韩精品中文字幕久久臀| 国产精品草莓在线免费观看| 日韩av网站大全| 国产亚洲欧美日韩一区二区| 亚洲伊人成综合成人网| 国产精品自在线| 久久高清视频免费| 精品视频久久久| 91香蕉国产在线观看| 97在线视频免费观看| 18性欧美xxxⅹ性满足| 欧美专区国产专区| 欧美中文在线字幕| 神马久久桃色视频| 国产精品夜色7777狼人| 亚洲人成自拍网站| 国产精品色午夜在线观看| 97在线看免费观看视频在线观看| 日韩视频欧美视频| 亚洲午夜精品久久久久久性色| 欧美天堂在线观看| 美日韩精品免费视频| 欧美裸体xxxx| 欧美日韩美女在线观看| 国产精品久久久久久久久久| 国产不卡在线观看| 欧美亚洲视频一区二区| 亚洲人成77777在线观看网| 精品久久久一区二区| 欧美色另类天堂2015| 亚洲欧美在线第一页| 日本韩国在线不卡| 性视频1819p久久| 成人国产在线激情| 亚洲国产精品va在看黑人| 精品偷拍一区二区三区在线看| 成人黄色片网站| 26uuu另类亚洲欧美日本一| 精品国产依人香蕉在线精品| 亚洲成人av中文字幕| 91精品国产综合久久香蕉的用户体验| 午夜精品福利在线观看| 日韩电影中文字幕一区| 精品视频在线观看日韩| 日本91av在线播放| 97精品国产aⅴ7777| 国产精品国产三级国产专播精品人| 久久人人爽人人爽爽久久| 国产成人拍精品视频午夜网站| 国产精品嫩草影院一区二区| 日本一区二区在线免费播放| 自拍偷拍亚洲在线| 国产亚洲成精品久久| 自拍偷拍亚洲精品| 国产精品一区二区女厕厕| 日韩黄色av网站| 久久免费视频在线观看| 国产亚洲欧美另类中文| 中文字幕在线看视频国产欧美| 青青久久av北条麻妃黑人| 久99九色视频在线观看| 日韩欧美999| 91国自产精品中文字幕亚洲| 91在线观看免费高清| 91精品国产综合久久香蕉| 亚洲3p在线观看| 亚洲第一视频网站| 欧美人交a欧美精品| 亚洲国产精品国自产拍av秋霞| 国内成人精品视频| 91亚洲精品一区二区| 欧美一级在线亚洲天堂| 懂色av中文一区二区三区天美| 日韩风俗一区 二区| 91精品国产91久久久久久| 国产精品久久久久久久久久久不卡| 97色伦亚洲国产| 自拍亚洲一区欧美另类| 91免费版网站入口| 爱福利视频一区| 日韩在线一区二区三区免费视频| 91av在线免费观看视频| 久久久综合av| 成人字幕网zmw| 国产精品久久久久9999| 91国产视频在线| 海角国产乱辈乱精品视频| 色在人av网站天堂精品| 亚洲专区中文字幕| 狠狠爱在线视频一区| 国产一区二区动漫| www.亚洲一二| 欧美性生交大片免网| 久久久久北条麻妃免费看| 国产91精品高潮白浆喷水| 日本成人黄色片| 中文字幕在线日韩| 亚洲国产91色在线| 久久精品国产成人精品| 亚洲va久久久噜噜噜久久天堂| 亚洲成人黄色网| 在线观看精品国产视频| 2019国产精品自在线拍国产不卡| 91久久精品美女高潮| 日韩在线视频国产| 97超级碰在线看视频免费在线看| 国产伦精品免费视频| 国产视频精品一区二区三区| 久久电影一区二区| 亚洲精品一区av在线播放| 欧美日韩国产色| 亚洲自拍小视频| 26uuu亚洲国产精品| 最新91在线视频| 日韩精品视频在线观看免费| 久久久久久久久久久人体| 亚洲老司机av| 综合国产在线视频| 亚洲另类图片色| 国产香蕉一区二区三区在线视频| 欧美专区在线视频| 国产精品欧美激情在线播放| 色偷偷综合社区| 亚洲第一视频网| 欧美美女15p| 国产精品久久久久久搜索| 日韩av在线免费观看| 色无极亚洲影院| 国产精品久久久久久av下载红粉| 视频在线观看99| 欧美高清在线观看| 欧美午夜精品久久久久久浪潮| 91精品国产乱码久久久久久蜜臀| 国产拍精品一二三| 欧美一级视频在线观看| 亚洲男人天堂九九视频| 精品成人乱色一区二区| 美女撒尿一区二区三区| 俺去了亚洲欧美日韩| 欧美日韩亚洲系列| 日韩网站免费观看| 日韩中文字幕在线观看| 日韩在线观看免费网站| 狠狠躁夜夜躁人人躁婷婷91| 亚洲男人的天堂网站| 国产欧美精品日韩| 久久深夜福利免费观看| 欧美激情图片区| 在线国产精品视频|