<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Python+Dash快速web應(yīng)用開發(fā):回調(diào)交互篇(下)

          共 13827字,需瀏覽 28分鐘

           ·

          2021-02-13 20:31


          添加微信號(hào)"CNFeffery"加入技術(shù)交流群

          ?

          本文示例代碼已上傳至我的Github倉庫https://github.com/CNFeffery/DataScienceStudyNotes

          ?

          1 簡介

          這是我的系列教程「Python+Dash快速web應(yīng)用開發(fā)」的第五期,在上一期的文章中,我們針對(duì)Dash中有關(guān)回調(diào)的一些技巧性的特性進(jìn)行了介紹,使得我們可以更愉快地為Dash應(yīng)用編寫回調(diào)交互功能。

          而今天的文章作為「回調(diào)交互」系統(tǒng)性內(nèi)容的最后一期,我將帶大家get一些Dash中實(shí)際應(yīng)用效果驚人的「高級(jí)回調(diào)特性」,系好安全帶,我們起飛~

          圖1

          2?Dash中的高級(jí)回調(diào)特性

          2.1?控制部分回調(diào)輸出不更新

          在很多應(yīng)用場景下,我們給某個(gè)回調(diào)函數(shù)綁定了多個(gè)Output(),這時(shí)如果這些Output()并不是每次觸發(fā)回調(diào)都需要被更新,那么就可以根據(jù)Input()值的不同,來配合dash.no_update作為對(duì)應(yīng)Output()的返回值,從而實(shí)現(xiàn)部分Output()不更新,譬如下面的例子:

          ?

          app1.py

          ?
          import?dash
          import?dash_bootstrap_components?as?dbc
          import?dash_html_components?as?html
          from?dash.dependencies?import?Input,?Output
          import?time

          app?=?dash.Dash(__name__)

          app.layout?=?html.Div(
          ????dbc.Container(
          ????????[
          ????????????html.Br(),
          ????????????html.Br(),
          ????????????html.Br(),
          ????????????dbc.Row(
          ????????????????dbc.Col(
          ????????????????????dbc.Button('按鈕',
          ???????????????????????????????color='primary',
          ???????????????????????????????id='button',
          ???????????????????????????????n_clicks=0)
          ????????????????)
          ????????????),
          ????????????html.Br(),
          ????????????dbc.Row(
          ????????????????[
          ????????????????????dbc.Col('尚未觸發(fā)',?id='record-1'),
          ????????????????????dbc.Col('尚未觸發(fā)',?id='record-2'),
          ????????????????????dbc.Col('尚未觸發(fā)',?id='record-n')
          ????????????????]
          ????????????)
          ????????]
          ????)
          )


          @app.callback(
          ????[Output('record-1',?'children'),
          ?????Output('record-2',?'children'),
          ?????Output('record-n',?'children'),
          ?????],
          ????Input('button',?'n_clicks'),
          ????prevent_initial_call=True
          )
          def?record_click_event(n_clicks):
          ????if?n_clicks?==?1:
          ????????return?(
          ????????????'第1次點(diǎn)擊:{}'.format(time.strftime('%H:%M:%S',?time.localtime(time.time()))),
          ????????????dash.no_update,
          ????????????dash.no_update
          ????????)

          ????elif?n_clicks?==?2:
          ????????return?(
          ????????????dash.no_update,
          ????????????'第2次點(diǎn)擊:{}'.format(time.strftime('%H:%M:%S',?time.localtime(time.time()))),
          ????????????dash.no_update
          ????????)

          ????elif?n_clicks?>=?3:
          ????????return?(
          ????????????dash.no_update,
          ????????????dash.no_update,
          ????????????'第3次及以上點(diǎn)擊:{}'.format(time.strftime('%H:%M:%S',?time.localtime(time.time()))),
          ????????)


          if?__name__?==?'__main__':
          ????app.run_server(debug=True)
          圖2

          可以觀察到,我們根據(jù)n_clicks數(shù)值的不同,在對(duì)應(yīng)各個(gè)Output()返回值中對(duì)符合條件的部件進(jìn)行更新,其他的都用dash.no_update來代替,從而實(shí)現(xiàn)了局部更新,非常實(shí)用且簡單。

          2.2 基于模式匹配的回調(diào)

          這是Dash在1.11.0版本開始引入的新特性,它所實(shí)現(xiàn)的功能是將多個(gè)部件綁定組織在同一個(gè)id屬性下,這聽起來有一點(diǎn)抽象,我們先從一個(gè)形象的例子來出發(fā):

          假如我們要開發(fā)一個(gè)簡單的「記賬」應(yīng)用,它通過第一排若干Input()部件及一個(gè)Button()部件來記錄并提交每筆賬對(duì)應(yīng)的相關(guān)信息,并且在最下方輸出已記錄賬目金額之和:

          ?

          app2.py

          ?
          import?dash
          import?dash_bootstrap_components?as?dbc
          import?dash_core_components?as?dcc
          import?dash_html_components?as?html
          from?dash.dependencies?import?Input,?Output,?State,?ALL
          import?re

          app?=?dash.Dash(__name__)

          app.layout?=?html.Div(
          ????[
          ????????html.Br(),
          ????????html.Br(),
          ????????dbc.Container(
          ????????????dbc.Row(
          ????????????????[
          ????????????????????dbc.Col(
          ????????????????????????dbc.InputGroup(
          ????????????????????????????[
          ????????????????????????????????dbc.InputGroupAddon("金額",?addon_type="prepend"),
          ????????????????????????????????dbc.Input(
          ????????????????????????????????????id='account-amount',
          ????????????????????????????????????placeholder='請輸入金額',
          ????????????????????????????????????type="number",
          ????????????????????????????????),
          ????????????????????????????????dbc.InputGroupAddon("元",?addon_type="append"),
          ????????????????????????????],
          ????????????????????????),
          ????????????????????????width=5
          ????????????????????),
          ????????????????????dbc.Col(
          ????????????????????????dcc.Dropdown(
          ????????????????????????????id='account-type',
          ????????????????????????????options=[
          ????????????????????????????????{'label':?'生活開銷',?'value':?'生活開銷'},
          ????????????????????????????????{'label':?'人情往來',?'value':?'人情往來'},
          ????????????????????????????????{'label':?'醫(yī)療保健',?'value':?'醫(yī)療保健'},
          ????????????????????????????????{'label':?'旅游休閑',?'value':?'旅游休閑'},
          ????????????????????????????],
          ????????????????????????????placeholder='請選擇類型:'
          ????????????????????????),
          ????????????????????????width=5
          ????????????????????),
          ????????????????????dbc.Col(
          ????????????????????????dbc.Button('提交記錄',?id='account-submit'),
          ????????????????????????width=2
          ????????????????????)
          ????????????????]
          ????????????)
          ????????),
          ????????html.Br(),
          ????????dbc.Container([],?id='account-record-container'),
          ????????dbc.Container('暫無記錄!',?id='account-record-sum')
          ????]
          )


          @app.callback(
          ????Output('account-record-container',?'children'),
          ????Input('account-submit',?'n_clicks'),
          ????[State('account-record-container',?'children'),
          ?????State('account-amount',?'value'),
          ?????State('account-type',?'value')],
          ????prevent_initial_call=True
          )
          def?update_account_records(n_clicks,?children,?account_amount,?account_type):
          ????'''
          ????用于處理每一次的記賬輸入并渲染前端記錄
          ????'''

          ????if?account_amount?and?account_type:
          ????????children.append(dbc.Row(
          ????????????dbc.Col(
          ????????????????'【{}】類開銷【{}】元'.format(account_type,?account_amount)
          ????????????),
          ????????????#?以字典形式定義id
          ????????????id={'type':?'single-account_record',?'index':?children.__len__()}
          ????????))

          ????????return?children


          @app.callback(
          ????Output('account-record-sum',?'children'),
          ????Input({'type':?'single-account_record',?'index':?ALL},?'children'),
          ????prevent_initial_call=True
          )
          def?refresh_account_sum(children):
          ????'''
          ????對(duì)多部件集合single-account_record下所有賬目記錄進(jìn)行求和
          ????'''

          ????return?'賬本總開銷:{}'.format(sum([int(re.findall('\d+',
          ?????????????????????????????????????????????????child['props']['children'])[0])
          ??????????????????????????????????for?child?in?children]))

          if?__name__?==?'__main__':
          ????app.run_server(debug=True)
          圖3

          上面這個(gè)應(yīng)用中,體現(xiàn)出的「模式匹配」內(nèi)容即為開頭從dash.dependencies引入的ALL,它是Dash「模式匹配」中的一種模式,而我們在回調(diào)函數(shù)update_account_records()中為已有記賬記錄追加新紀(jì)錄時(shí),使用到:

          #?以字典形式定義id
          id={'type':?'single-account_record',?'index':?children.__len__()}

          這里不同于以前我們采取的id=某個(gè)字符串的定義方法,換成字典之后,其type鍵值對(duì)用來記錄唯一id信息,每一次新紀(jì)錄追加時(shí)type值都相等,因?yàn)樗鼈儽唤M織為「同id部件集合」,而鍵值對(duì)index則用于在type值相同的一個(gè)部件集合下,區(qū)分出不同的獨(dú)立部件元素。

          因?yàn)閷鹘y(tǒng)的「唯一id部件」替換成「同id部件集合」,所以我們后面的回調(diào)函數(shù)refresh_account_sum()的輸入元素只需要定義單個(gè)Input()即可,再在函數(shù)內(nèi)部按照不同的index值取出需要的集合內(nèi)各成員記錄值,非常便于我們書寫出簡練清爽的Dash代碼,便于之后進(jìn)一步的修改與重構(gòu)。

          你可以通過最下面打印出的每次refresh_account_sum()所接收到的children參數(shù)json格式結(jié)果來弄清我是如何在return值的地方取出歷史記賬金額并計(jì)算的。

          而除了上面介紹的一股腦返回所有集合內(nèi)成員部件的ALL模式之外,還有另一種更有針對(duì)性的MATCH模式,它應(yīng)用于結(jié)合內(nèi)成員部件可交互輸入值的情況,譬如下面這個(gè)簡單的例子,我們定義一個(gè)簡單的用于查詢省份行政代碼的應(yīng)用,配合MATCH模式來實(shí)現(xiàn)彼此成對(duì)獨(dú)立輸出:

          ?

          app3.py

          ?
          import?dash
          import?dash_bootstrap_components?as?dbc
          import?dash_html_components?as?html
          from?dash.dependencies?import?Input,?Output,?State,?MATCH
          import?dash_core_components?as?dcc

          app?=?dash.Dash(__name__)

          app.layout?=?html.Div(
          ????[
          ????????html.Br(),
          ????????html.Br(),
          ????????html.Br(),
          ????????dbc.Container(
          ????????????[
          ????????????????dbc.Row(
          ????????????????????dbc.Col(
          ????????????????????????dbc.Button('新增查詢',?id='add-item',?outline=True)
          ????????????????????)
          ????????????????),
          ????????????????html.Hr()
          ????????????]
          ????????),
          ????????dbc.Container([],?id='query-container')
          ????]
          )

          region2code?=?{
          ????'北京市':?'110000000000',
          ????'重慶市':?'500000000000',
          ????'安徽省':?'340000000000'
          }


          @app.callback(
          ????Output('query-container',?'children'),
          ????Input('add-item',?'n_clicks'),
          ????State('query-container',?'children'),
          ????prevent_initial_call=True
          )
          def?add_query_item(n_clicks,?children):
          ????children.append(
          ????????dbc.Row(
          ????????????[
          ????????????????dbc.Col(
          ????????????????????[
          ????????????????????????#?生成index相同的dropdown部件與文字輸出部件
          ????????????????????????dcc.Dropdown(id={'type':?'select-province',?'index':?children.__len__()},
          ?????????????????????????????????????options=[{'label':?label,?'value':?label}?for?label?in?region2code.keys()],
          ?????????????????????????????????????placeholder='選擇省份:'),
          ????????????????????????html.P('請輸入要查詢的省份!',?id={'type':?'code-output',?'index':?children.__len__()})
          ????????????????????]
          ????????????????)
          ????????????]
          ????????)
          ????)

          ????return?children

          @app.callback(
          ????Output({'type':?'code-output',?'index':?MATCH},?'children'),
          ????Input({'type':?'select-province',?'index':?MATCH},?'value')
          )
          def?refresh_code_output(value):

          ????if?value:
          ????????return?region2code[value]
          ????else:
          ????????return?dash.no_update

          if?__name__?==?'__main__':
          ????app.run_server(debug=True)
          圖4

          可以看到,在refresh_code_output()前應(yīng)用MATCH模式匹配后,我們點(diǎn)擊某個(gè)部件時(shí),只有跟它index匹配的部件才會(huì)打印出相對(duì)應(yīng)的輸出,非常的方便~

          2.3 多輸入情況下獲取部件觸發(fā)情況

          在很多應(yīng)用場景下,我們的某個(gè)回調(diào)可能擁有多個(gè)Input輸入,但學(xué)過前面的內(nèi)容我們已經(jīng)清楚,不管有幾個(gè)Input,只要其中有一個(gè)部件其輸入屬性發(fā)生變化,都會(huì)觸發(fā)本輪回調(diào),但是如果我們就想知道究竟是「哪個(gè)」Input觸發(fā)了本輪回調(diào)該怎么辦呢?

          這在Dash中可以通過dash.callback_context來方便的實(shí)現(xiàn),它只能在回調(diào)函數(shù)中被執(zhí)行,從而獲取回調(diào)過程的諸多上下文信息,先從下面這個(gè)簡單的例子出發(fā)看看dash.callback_context到底給我們帶來了哪些有價(jià)值的信息:

          ?

          app4.py

          ?
          import?dash
          import?dash_html_components?as?html
          import?dash_bootstrap_components?as?dbc
          from?dash.dependencies?import?Input,?Output
          import?json

          app?=?dash.Dash(__name__)

          app.layout?=?html.Div(
          ????dbc.Container(
          ????????[
          ????????????html.Br(),
          ????????????html.Br(),
          ????????????html.Br(),
          ????????????dbc.Row(
          ????????????????[
          ????????????????????dbc.Col(dbc.Button('A',?id='A',?n_clicks=0)),
          ????????????????????dbc.Col(dbc.Button('B',?id='B',?n_clicks=0)),
          ????????????????????dbc.Col(dbc.Button('C',?id='C',?n_clicks=0))
          ????????????????]
          ????????????),
          ????????????dbc.Row(
          ????????????????[
          ????????????????????dbc.Col(html.P('按鈕A未點(diǎn)擊',?id='A-output')),
          ????????????????????dbc.Col(html.P('按鈕B未點(diǎn)擊',?id='B-output')),
          ????????????????????dbc.Col(html.P('按鈕C未點(diǎn)擊',?id='C-output'))
          ????????????????]
          ????????????),
          ????????????dbc.Row(
          ????????????????dbc.Col(
          ????????????????????html.Pre(id='raw-json')
          ????????????????)
          ????????????)
          ????????]
          ????)
          )


          @app.callback(
          ????[Output('A-output',?'children'),
          ?????Output('B-output',?'children'),
          ?????Output('C-output',?'children'),
          ?????Output('raw-json',?'children')],
          ????[Input('A',?'n_clicks'),
          ?????Input('B',?'n_clicks'),
          ?????Input('C',?'n_clicks')],
          ????prevent_initial_call=True
          )
          def?refresh_output(A_n_clicks,?B_n_clicks,?C_n_clicks):

          ????#?獲取本輪回調(diào)狀態(tài)下的上下文信息
          ????ctx?=?dash.callback_context

          ????#?取出對(duì)應(yīng)State、最近一次觸發(fā)部件以及Input信息
          ????ctx_msg?=?json.dumps({
          ????????'states':?ctx.states,
          ????????'triggered':?ctx.triggered,
          ????????'inputs':?ctx.inputs
          ????},?indent=2)

          ????return?A_n_clicks,?B_n_clicks,?C_n_clicks,?ctx_msg

          if?__name__?==?'__main__':
          ????app.run_server(debug=True)
          圖5

          可以看到,我們安插在回調(diào)函數(shù)里的dash.callback_context幫我們記錄了從訪問Dash開始,到最近一次執(zhí)行回調(diào)期間,對(duì)應(yīng)回調(diào)的輸入輸出信息變化情況、最近一次觸發(fā)信息,非常的實(shí)用,可以支撐起很多復(fù)雜應(yīng)用場景。

          2.4 在瀏覽器端執(zhí)行回調(diào)過程

          Dash雖然很方便,使得我們可以完全不用書寫js代碼就可以實(shí)現(xiàn)各種回調(diào)交互,但把所有的交互響應(yīng)計(jì)算過程都交給服務(wù)端來做,省事倒是很省事,但會(huì)給服務(wù)器帶來不小的計(jì)算和網(wǎng)絡(luò)傳輸壓力。

          因此很多容易頻繁觸發(fā)且與主要的數(shù)值計(jì)算無關(guān)的交互行為,完全可以搬到瀏覽器端執(zhí)行,既快速又不吃服務(wù)器的計(jì)算資源,這也是當(dāng)初JavaScript被發(fā)明的一個(gè)重要原因,而在Dash中,也為略懂js的用戶提供了在瀏覽器端執(zhí)行一些回調(diào)的貼心功能。

          從一個(gè)很簡單的點(diǎn)擊按鈕,實(shí)現(xiàn)部分網(wǎng)頁內(nèi)容的打開與關(guān)閉出發(fā),這里我們提前使用到dbc.Collapse部件,用于將所包含的網(wǎng)頁內(nèi)容與其它按鈕部件的點(diǎn)擊行為進(jìn)行綁定:

          ?

          app5.py

          ?
          import?dash
          import?dash_bootstrap_components?as?dbc
          import?dash_html_components?as?html
          from?dash.dependencies?import?Input,?Output,?State

          app?=?dash.Dash(__name__)

          app.layout?=?html.Div(
          ????dbc.Container(
          ????????[
          ????????????html.Br(),
          ????????????html.Br(),
          ????????????html.Br(),
          ????????????dbc.Button('服務(wù)端回調(diào)',?id='server-button'),
          ????????????dbc.Collapse('服務(wù)端折疊內(nèi)容',?id='server-collapse'),
          ????????????html.Hr(),
          ????????????dbc.Button('瀏覽器端回調(diào)',?id='browser-button'),
          ????????????dbc.Collapse('瀏覽器端折疊內(nèi)容',?id='browser-collapse'),
          ????????]
          ????)
          )


          @app.callback(
          ????Output('server-collapse',?'is_open'),
          ????Input('server-button',?'n_clicks'),
          ????State('server-collapse',?'is_open'),
          ????prevent_initial_call=True
          )
          def?server_callback(n_clicks,?is_open):
          ????return?not?is_open

          #?在dash中定義瀏覽器端回調(diào)函數(shù)的特殊格式
          app.clientside_callback(
          ????"""
          ????function(n_clicks,?is_open)?{
          ????????return?!is_open;
          ????}
          ????"""
          ,
          ????Output('browser-collapse',?'is_open'),
          ????Input('browser-button',?'n_clicks'),
          ????State('browser-collapse',?'is_open'),
          ????prevent_initial_call=True
          )

          if?__name__?==?'__main__':
          ????app.run_server(debug=True)

          可以看到,服務(wù)端回調(diào)我們照常寫,而瀏覽器端回調(diào)通過傳入一個(gè)非常簡單的js函數(shù),在每次回調(diào)時(shí)接受輸入并輸出is_open的邏輯反值,從而實(shí)現(xiàn)了折疊內(nèi)容的打開與關(guān)閉切換:

          function(n_clicks,?is_open)?{
          ????????return?!is_open;
          }

          便實(shí)現(xiàn)了瀏覽器端回調(diào)!

          圖6

          而如果你想要執(zhí)行的瀏覽器端js回調(diào)函數(shù)代碼有點(diǎn)長,還可以按照下圖格式,把你的大段js回調(diào)函數(shù)代碼放置于assets目錄下對(duì)應(yīng)路徑里的js腳本中:

          圖7

          接著再在dash中按照下列格式編寫關(guān)聯(lián)輸入輸出與上述js回調(diào)的簡短語句即可:

          app.clientside_callback(
          ????ClientsideFunction(
          ????????namespace='命名空間名稱',
          ????????function_name='對(duì)應(yīng)js回調(diào)函數(shù)名'
          ????),
          ????'''
          ????按順序組織你的Output、Input以及State...?...
          ????'''

          )

          下面我們直接以大家喜聞樂見的數(shù)據(jù)可視化頂級(jí)框架echarts為例,來寫一個(gè)根據(jù)不同輸入值切換渲染出的圖表類型,「注意」請從官網(wǎng)把依賴的echarts.min.js下載到我們的assets路徑下對(duì)應(yīng)位置,它會(huì)在我們的Dash應(yīng)用啟動(dòng)時(shí)與所有assets下的資源一起自動(dòng)被載入到瀏覽器中:

          ?

          app6.py

          ?
          import?dash
          import?dash_bootstrap_components?as?dbc
          import?dash_html_components?as?html
          import?dash_core_components?as?dcc
          from?dash.dependencies?import?Input,?Output,?ClientsideFunction

          app?=?dash.Dash(__name__)

          #?編寫一個(gè)根據(jù)dropdown不同輸入值切換對(duì)應(yīng)圖表類型的小應(yīng)用
          app.layout?=?html.Div(
          ????dbc.Container(
          ????????[
          ????????????html.Br(),
          ????????????dbc.Row(
          ????????????????dbc.Col(
          ????????????????????dcc.Dropdown(
          ????????????????????????id='chart-type',
          ????????????????????????options=[
          ????????????????????????????{'label':?'折線圖',?'value':?'折線圖'},
          ????????????????????????????{'label':?'堆積面積圖',?'value':?'堆積面積圖'},
          ????????????????????????],
          ????????????????????????value='折線圖'
          ????????????????????),
          ????????????????????width=3
          ????????????????)
          ????????????),
          ????????????html.Br(),
          ????????????dbc.Row(
          ????????????????dbc.Col(
          ????????????????????html.Div(
          ????????????????????????html.Div(
          ????????????????????????????id='main',
          ????????????????????????????style={
          ????????????????????????????????'height':?'100%',
          ????????????????????????????????'width':?'100%'
          ????????????????????????????}
          ????????????????????????),
          ????????????????????????style={
          ????????????????????????????'width':?'800px',
          ????????????????????????????'height':?'500px'
          ????????????????????????}
          ????????????????????)
          ????????????????)
          ????????????)
          ????????]
          ????)
          )

          app.clientside_callback(
          ????#?關(guān)聯(lián)自編js腳本中的相應(yīng)回調(diào)函數(shù)
          ????ClientsideFunction(
          ????????namespace='clientside',
          ????????function_name='switch_chart'
          ????),
          ????Output('main',?'children'),
          ????Input('chart-type',?'value')
          )

          if?__name__?==?'__main__':
          ????app.run_server(debug=True)

          圖8

          效果十分驚人,從此我們使用Dash不僅僅可以使用Python生態(tài)的工具,還可以配合對(duì)前端內(nèi)容支持更好的js,起飛!


          至此我們的Dash回調(diào)交互三部曲已結(jié)束,接下來的文章我將開始帶大家遨游豐富的各種Dash前端部件,涵蓋了網(wǎng)頁部件、數(shù)據(jù)可視化圖表以及地圖可視化等內(nèi)容,敬請期待這場奇妙之旅吧~

          以上就是本文的全部內(nèi)容,歡迎在評(píng)論區(qū)與我進(jìn)行討論。

          加入知識(shí)星球【我們談?wù)摂?shù)據(jù)科學(xué)】

          300+小伙伴一起學(xué)習(xí)!






          · 推薦閱讀?·

          妙啊,速來get這9個(gè)jupyter實(shí)用技巧!

          Python+Dash快速web應(yīng)用開發(fā):回調(diào)交互篇(中)

          在模仿中精進(jìn)數(shù)據(jù)可視化07:星球研究所大壩分布可視化



          瀏覽 113
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  在线高清无码不卡 | 亚洲天码33 | 亚洲日韩欧美激情 | 天天干天天操青青草 | 国产又粗又硬视频 |