整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          「干貨」帶你認識 flask ajax 異步請求

          「干貨」帶你認識 flask ajax 異步請求

          創: 志學Python 志學Python

          01 服務端與客戶端

          迄今為止,在我遵循的傳統服務器端模型中,有一個客戶端(由用戶驅動的Web瀏覽器)向應用服務器發出HTTP請求。請求可以簡單地請求HTML頁面,例如當你單擊“個人主頁”鏈接時,或者它可以觸發一個操作,例如在編輯你的個人信息之后單擊提交按鈕。在這兩種類型的請求中,服務器通過直接發送新的網頁或通過發送重定向來完成請求。然后客戶端用新的頁面替換當前頁面。只要用戶停留在應用的網站上,該周期就會重復。在這種模式下,服務器完成所有工作,而客戶端只顯示網頁并接受用戶輸入

          有一種不同的模式,客戶端扮演更積極的角色。在這個模式中,客戶端向服務器發出一個請求,服務器響應一個網頁,但與前面的情況不同,并不是所有的頁面數據都是HTML,頁面中也有部分代碼,通常用Javascript編寫。一旦客戶端收到該頁面,它就會顯示HTML部分,并執行代碼。從那時起,你就擁有了一個可以獨立工作的活動客戶端,而無需與服務器進行聯系或只有很少聯系。在嚴格的客戶端應用中,整個應用通過初始頁面請求下載到客戶端,然后應用完全在客戶端上運行,只有在查詢或者變更數據時才與服務器聯系。這種類型的應用稱為單頁應用(SPAs)

          大多數應用是這兩種模式的混合,并結合了兩者的技術特點。我的Microblog應用主要是服務器端應用,但今天我將添加一些客戶端操作。為了實時翻譯用戶動態,客戶端瀏覽器將異步請求發送到服務器,服務器將響應該請求而不會導致頁面刷新。然后客戶端將動態地將翻譯插入當前頁面。這種技術被稱為Ajax,這是Asynchronous JavaScript和XML的簡稱(盡管現在XML常常被JSON取代)

          02 實時翻譯工作流

          由于使用了Flask-Babel,本應用對外語有很好的支持,可以支持盡可能多的語言,只要我找到了對應的譯文。但是遺漏了一個元素,用戶將會用他們自己的語言發表動態,所以用戶很可能會用應用未知的語言發表動態。自動翻譯的質量大多數情況下不怎么樣,但在,如果你只想對另一種語言的文本了解其基本含義,這已經足夠了

          這正是Ajax大展身手的好機會!設想主頁或發現頁面可能會顯示若干用戶動態,其中一些可能是外語。如果我使用傳統的服務器端技術實現翻譯,則翻譯請求會導致原始頁面被替換為新頁面。事實是,要求翻譯諸多用戶動態中的一條,并不是一個足夠大的動作來要求整個頁面的更新,如果翻譯文本可以被動態地插入到原始文本下方,而剩下的頁面保持原樣,則用戶體驗更加出色

          實施實時自動翻譯需要幾個步驟。首先,我需要一種方法來識別要翻譯的文本的源語言。我還需要知道每個用戶的首選語言,因為我想僅為使用其他語言發表的動態顯示“翻譯”鏈接。當提供翻譯鏈接并且用戶點擊它時,我需要將Ajax請求發送到服務器,服務器將聯系第三方翻譯API。一旦服務器發送了帶有翻譯文本的響應,客戶端JavaScript代碼將動態地將該文本插入到頁面中。你一定注意到了,這里有一些特殊的問題。我將逐一審視這些問題。

          03語言識別


          第一個問題是確定一條用戶動態的語言。這不是一門精確的科學,因為不能確保監測結果絕對正確,但是對于大多數情況,自動檢測的效果相當好。在Python中,有一個稱為guess_language的語言檢測庫,還算好用。這個軟件包的原始版本相當陳舊,從未被移植到Python 3,因此我將安裝支持Python 2和3的派生版本:


          (venv) $ pip install guess-language_spirit

          計劃是將每條用戶動態提供給這個包,以嘗試確定語言。由于做這種分析有點費時,我不想每次把帖子呈現給頁面時重復這項工作。我要做的是在提交時為帖子設置源語言。檢測到的語言將被存儲在post表中。

          第一步,添加language字段到Post模型:

          app/models.py:添加監測到的語言到Post模型:

          class Post(db.Model): # ... language=db.Column(db.String(5))

          你一定還記得,每當數據庫模型發生變化時,都需要生成數據庫遷移:


          (venv) $ flask db migrate -m "add language to posts"INFO [alembic.runtime.migration] Context impl SQLiteImpl.INFO [alembic.runtime.migration] Will assume non-transactional DDL.INFO [alembic.autogenerate.compare] Detected added column 'post.language' Generating migrations/versions/2b017edaa91f_add_language_to_posts.py ... done

          然后將遷移應用到數據庫:

          (venv) $ flask db upgradeINFO [alembic.runtime.migration] Context impl SQLiteImpl.INFO [alembic.runtime.migration] Will assume non-transactional DDL.INFO [alembic.runtime.migration] Upgrade ae346256b650 -> 2b017edaa91f, add language to posts


          我現在可以在提交帖子時檢測并存儲語言:

          app/routes.py:為新的用戶動態保存語言字段

          from guess_language import guess_language
          @app.route('/', methods=['GET', 'POST'])@app.route('/index', methods=['GET', 'POST'])@login_requireddef index(): form=PostForm() if form.validate_on_submit(): language=guess_language(form.post.data) if language=='UNKNOWN' or len(language) > 5: language='' post=Post(body=form.post.data, author=current_user, language=language) # ...

          有了這個變更,每次發表動態時,都會通過guess_language函數測試文本來嘗試確定語言。如果語言監測為未知,或者如果我得到意想不到的長字符串的結果,我會將一個空字符串保存到數據庫中以安全地使用它。我將采用約定,將任何將把語言設置為空字符串的帖子假定為未知語言。

          04展示一個 ‘翻譯’鏈接

          第二步很簡單。我現在要做的是在任何不是當前用戶的首選語言的用戶動態下,添加一個“翻譯”鏈接

          app/templates/_post.html:給用戶動態添加翻譯鏈接

           {% if post.language and post.language !=g.locale %} <br><br> <a href="#">{{ _('Translate') }}</a> {% endif %}


          我在_post.html子模板中執行此操作,以便此功能出現在顯示用戶動態的任何頁面上。翻譯鏈接只會出現在檢測到語言種類的動態下,并且必須滿足的條件是,這種語言與用Flask-Babel的localeselector裝飾器裝飾的函數選擇的語言不匹配。回想一下第十三章所選語言環境存儲為g.locale。鏈接文本需要以Flask-Babel可以翻譯的方式添加,所以我在定義它時使用了_()函數

          請注意,我還沒有關聯此鏈接的操作。首先,我想弄清楚如何進行實際的翻譯

          05使用第三方‘翻譯’服務

          兩種主要的翻譯服務是Google Cloud Translation API和Microsoft Translator Text API。兩者都是付費服務,但微軟為低頻少量的翻譯提供了免費的入門級選項。谷歌過去提供免費翻譯服務,但現在,即使是最低層次的服務也需要付費。因為我希望能夠在不產生費用的情況下嘗試翻譯,我將實施Microsoft的解決方案。

          在使用Microsoft Translator API之前,你需要先獲得微軟云服務Azure的帳戶。你可以選擇免費套餐,但在注冊過程中系統會要求你提供信用卡號,但在你保持該級別的服務時,你的卡不會被收取費用。

          獲得Azure帳戶后,轉到Azure門戶并單擊左上角的“New”按鈕,然后鍵入或選擇“Translator Text API”。當你點擊“Create”按鈕時,將看到一個表單,并可以在其中定義一個新的翻譯器資源,然后將其添加到你的帳戶中。你可以在下面看到我是如何完成表單的:

          當你再次點擊“Create”按鈕時,翻譯器API資源將被添加到你的帳戶中。幾秒鐘之后,你將在頂欄中收到通知,說明部署了翻譯器資源。點擊通知中的“Go to resource”按鈕,然后點擊左側欄上的“Keys”選項。你現在將看到兩個Key,分別標記為“Key 1”和“Key 2”。將其中一個Key復制到剪貼板,然后將其設置到終端的環境變量中(如果使用的是Microsoft Windows,請用set替換export):

          from guess_language import guess_language
          @app.route('/', methods=['GET', 'POST'])@app.route('/index', methods=['GET', 'POST'])@login_requireddef index(): form=PostForm() if form.validate_on_submit(): language=guess_language(form.post.data) if language=='UNKNOWN' or len(language) > 5: language='' post=Post(body=form.post.data, author=current_user, language=language) # ...

          該Key用于驗證翻譯服務,因此需要將其添加到應用配置中:

          config.py: 添加Microsoft Translator API key到配置中

          class Config(object): # ... MS_TRANSLATOR_KEY=os.environ.get('MS_TRANSLATOR_KEY')

          與很多配置值一樣,我更喜歡將它們安裝在環境變量中,并從那里將它們導入到Flask配置中。對于允許訪問第三方服務的密鑰或密碼等敏感信息,這一點尤為重要。你絕對不想在代碼中明確寫出它們。

          Microsoft Translator API是一個接受HTTP請求的Web服務。Python中有若干HTTP客戶端,但最常用和最簡單的就是requests包。所以讓我們將其安裝到虛擬環境中:

           {% if post.language and post.language !=g.locale %} <br><br> <a href="#">{{ _('Translate') }}</a> {% endif %}

          在下面,你可以看到我使用Microsoft Translator API編寫翻譯文本的功能。我來新增一個app/translate.py模塊:

          app/translate.py:文本翻譯函數

          import jsonimport requestsfrom flask_babel import _from app import app
          def translate(text, source_language, dest_language): if 'MS_TRANSLATOR_KEY' not in app.config or \ not app.config['MS_TRANSLATOR_KEY']: return _('Error: the translation service is not configured.') auth={'Ocp-Apim-Subscription-Key': app.config['MS_TRANSLATOR_KEY']} r=requests.get('https://api.microsofttranslator.com/v2/Ajax.svc' '/Translate?text={}&from={}&to={}'.format( text, source_language, dest_language), headers=auth) if r.status_code !=200: return _('Error: the translation service failed.') return json.loads(r.content.decode('utf-8-sig'))

          該函數定義需要翻譯的文本、源語言和目標語言為參數,并返回翻譯后文本的字符串。它首先檢查配置中是否存在翻譯服務的Key,如果不存在,則會返回錯誤。錯誤也是一個字符串,所以從外部看,這將看起來像翻譯文本。這可確保在出現錯誤時用戶將看到有意義的錯誤消息。

          requests包中的get()方法向作為第一個參數給定的URL發送一個帶有GET方法的HTTP請求。我使用*/v2/Ajax.svc/Translate* URL,它是翻譯服務中的一個端點,它將翻譯內容荷載為JSON返回。文本、源語言和目標語言都需要在URL中分別命名為text,from和to作為查詢字符串參數。要使用該服務進行身份驗證,我需要將我添加到配置中的Key傳遞給該服務。該Key需要在名為Ocp-Apim-Subscription-Key的自定義HTTP頭中給出。我創建了auth字典,然后將它通過headers參數傳遞給requests。

          requests.get()方法返回一個響應對象,它包含了服務提供的所有細節。我首先需要檢查和確認狀態碼是200,這是成功請求的代碼。如果我得到任何其他代碼,我就知道發生了錯誤,所以在這種情況下,我返回一個錯誤字符串。如果狀態碼是200,那么響應的主體就有一個帶有翻譯的JSON編碼字符串,所以我需要做的就是使用Python標準庫中的json.loads()函數將JSON解碼為我可以使用的Python字符串。響應對象的content屬性包含作為字節對象的響應的原始主體,該屬性是UTF-8編碼的字符序列,需要先進行解碼,然后發送給json.loads()。

          下面你可以看到一個Python控制臺會話,我演示了如何使用新的translate()函數:

          >>> from app.translate import translate>>> translate('Hi, how are you today?', 'en', 'es') # English to Spanish'Hola, ?cómo estás hoy?'>>> translate('Hi, how are you today?', 'en', 'de') # English to German'Are Hallo, how you heute?'>>> translate('Hi, how are you today?', 'en', 'it') # English to Italian'Ciao, come stai oggi?'>>> translate('Hi, how are you today?', 'en', 'fr') # English to French"Salut, comment allez-vous aujourd'hui ?"

          06來自服務器的 Ajax

          我將從實現服務器端部分開始。當用戶單擊動態下方顯示的翻譯鏈接時,將向服務器發出異步HTTP請求。我將在下一節中向你展示如何執行此操作,因此現在我將專注于實現服務器處理此請求的操作。

          異步(Ajax)請求類似于我在應用中創建的路由和視圖函數,唯一的區別是它不返回HTML或重定向,而是返回數據,格式為XML或更常見的JSON。你可以在下面看到翻譯視圖函數,該函數調用Microsoft Translator API,然后返回JSON格式的翻譯文本:

          app/routes.py:文本翻譯視圖函數

          from flask import jsonifyfrom app.translate import translate
          @app.route('/translate', methods=['POST'])@login_requireddef translate_text(): return jsonify({'text': translate(request.form['text'], request.form['source_language'], request.form['dest_language'])})

          如你所見,相當簡單。我以POST請求的形式實現了這條路由。關于什么時候使用GET或POST(或者還沒有見過的其他請求方法),真的沒有絕對的規則。由于客戶端將發送數據,因此我決定使用POST請求,因為它與提交表單數據的請求類似。 request.form屬性是Flask用提交中包含的所有數據暴露的字典。當我使用Web表單工作時,我不需要查看request.form,因為Flask-WTF可以為我工作,但在這種情況下,實際上沒有Web表單,所以我必須直接訪問數據。

          所以我在這個函數中做的是調用上一節中的translate()函數,直接從通過請求提交的數據中傳遞三個參數。將結果合并到單個鍵text下的字典中,字典作為參數傳遞給Flask的jsonify()函數,該函數將字典轉換為JSON格式的有效載荷。 jsonify()返回的值是將被發送回客戶端的HTTP響應。

          例如,如果客戶希望將字符串“Hello,World!”翻譯成西班牙語,則來自該請求的響應將具有以下有效載荷:


          { "text": "Hola, Mundo!" }

          07 來自客戶端的 Ajax

          因此,現在服務器能夠通過*/translate* URL提供翻譯,當用戶單擊我上面添加的“翻譯”鏈接時,我需要調用此URL,傳遞需要翻譯的文本、源語言和目標語言。如果你不熟悉在瀏覽器中使用JavaScript,這將是一個很好的學習機會

          在瀏覽器中使用JavaScript時,當前顯示的頁面在內部被表示為文檔對象模型(DOM)。這是一個引用頁面中所有元素的層次結構。在此上下文中運行的JavaScript代碼可以更改DOM以觸發頁面中的更改

          我們首先需要討論的是,在瀏覽器中運行的JavaScript代碼如何獲取需要發送到服務器中運行的翻譯函數的三個參數。為了獲得文本,我需要找到包含用戶動態正文的DOM內的節點并獲取它的內容。為了便于識別包含用戶動態的DOM節點,我將為它們附加一個唯一的ID。如果你查看*_post.html*模板,則呈現用戶動態正文的行只會讀取{{post.body}}。我要做的是將這些內容包裝在一個<span>元素中。這不會在視覺上改變任何東西,但它給了我一個可以插入標識符的地方:

          app/templates/_post.html:給每條用戶動態添加ID

           <span id="post{{ post.id }}">{{ post.body }}</span>

          這將為每條用戶動態分配一個唯一標識符,格式為post1,post2等,其中數字與每條用戶動態的數據庫標識符相匹配。現在每條用戶動態都有一個唯一的標識符,給定一個ID值,我可以使用jQuery定位<span>元素并提取其中的文本。例如,如果我想獲得ID為123的用戶動態的文本,我可以這樣做:

          $('#post123').text()

          這里的$符號是jQuery庫提供的函數的名稱。這個庫被Bootstrap使用,所以它已經被Flask-Bootstrap包含。 #是jQuery使用的“選擇器”語法的一部分,這意味著接下來是元素的ID

          我也希望有一個地方可以在我從服務器收到翻譯文本后插入翻譯文本。我要做的是將“翻譯”鏈接替換為翻譯文本,因此我還需要為該節點提供唯一標識符:

          app/templates/_post.html:為翻譯鏈接添加ID

          <span id="translation{{ post.id }}"> <a href="#">{{ _('Translate') }}</a></span>

          因此,現在對于一個給定的用戶動態ID,我有一個用于用戶動態的post <ID>節點和一個對應的translation <ID>節點,我可以在用翻譯后的文本替換翻譯鏈接時用到它們

          下一步是編寫一個可以完成所有翻譯工作的函數。該函數將利用輸入和輸出DOM節點以及源語言和目標語言,向服務器發出攜帶必須的三個參數的異步請求,并在服務器響應后用翻譯后的文本替換翻譯鏈接。這聽起來像很多工作,但實現相當簡單:

          app/templates/base.html:客戶端翻譯函數

          {% block scripts %} ... <script> function translate(sourceElem, destElem, sourceLang, destLang) { $(destElem).html('<img src="{{ url_for('static', filename='loading.gif') }}">'); $.post('/translate', { text: $(sourceElem).text(), source_language: sourceLang, dest_language: destLang }).done(function(response) { $(destElem).text(response['text']) }).fail(function() { $(destElem).text("{{ _('Error: Could not contact server.') }}"); }); }</script>{% endblock %}

          前兩個參數是用戶動態和翻譯鏈接節點的唯一ID,后兩個參數是源語言和目標語言代碼

          該函數從一個很好的接觸開始:它添加一個加載器替換翻譯鏈接,以便用戶知道翻譯正在進行中。這是通過使用$(destElem).html()函數完成的,它用基于<img>元素的新HTML內容替換定義為翻譯鏈接的原始HTML。對于加載器,我將使用一個小的動畫GIF,它已添加到Flask為靜態文件保留的app/static目錄中。為了生成引用這個圖像的URL,我使用url_for()函數,傳遞特殊的路由名稱static并給出圖像的文件名作為參數。你可以在本章的下載包中找到loading.gif圖像

          現在我用一個優雅的加載器代替了翻譯鏈接,以便用戶知道要等待翻譯出現。下一步是將POST請求發送到我在前一節中定義的*/translate* URL。為此,我也將使用jQuery,本處使用$ .post()函數。這個函數以一種類似于瀏覽器提交Web表單的格式向服務器提交數據,這很方便,因為它允許Flask將這些數據合并到request.form字典中。 $ .post()的參數是兩個,第一個是發送請求的URL,第二個是包含服務器期望的三個數據項的字典(或者稱之為對象,因為這些是在JavaScript中調用的

          你可能知道JavaScript對回調函數(或者稱為promises的更高級的回調形式)友好。現在要做的就是說明一旦這個請求完成并且瀏覽器接收到響應,我想完成的事情。在JavaScript中沒有需要等待的事情,一切都是異步。我需要做的是提供一個回調函數,瀏覽器在接收到響應時調用它。而且,為了使所有內容盡可能健壯,我想指出在出現錯誤的情況下該怎么做,以作為處理錯誤的第二個回調函數。有幾種方法可以指定這些回調,但在這種情況下,使用promises可以使代碼更加清晰。語法如下:

          $.post(<url>, <data>).done(function(response) { // success callback}).fail(function() { // error callback})

          promise語法允許將$ .post()調用的返回值“傳入”回調函數作為參數。在成功回調中,我所需要做的就是使用翻譯后的文本調用$(destElem).text(),該文本在字典中text鍵下。在出現錯誤的情況下,我也是這樣做的,但是我顯示的文本是一條通用的錯誤消息,我會確保它會作為可翻譯的文本編入基礎模板中

          所以現在唯一剩下的就是通過用戶點擊翻譯鏈接來觸發具有正確參數的translate()函數。存在若干方法可以做到這一點,我要做的是將該函數的調用嵌入鏈接的href屬性中:

          app/templates/_post.html:翻譯鏈接處理器

          <span id="translation{{ post.id }}"> <a href="javascript:translate( '#post{{ post.id }}', '#translation{{ post.id }}', '{{ post.language }}', '{{ g.locale }}');">{{ _('Translate') }}</a></span>

          鏈接的href元素可以接受任何JavaScript代碼,如果它帶有javascript:前綴的話,那么這是一種方便的方式來調用翻譯函數。因為這個鏈接將在客戶端請求頁面時在服務器端渲染,所以我可以使用{{}}表達式來為函數生成四個參數。每條用戶動態都有自己的翻譯鏈接,以及其唯一生成的參數。 post <ID>和translation <ID>需要渲染具體的ID,它們都需要在被使用時加上#前綴

          現在實時翻譯功能已經完成!如果你在環境中設置了有效的Microsoft Translator API Key,則現在應該能夠觸發翻譯。假設你的瀏覽器設置為偏好英語,則需要使用其他語言撰寫文章以查看“翻譯”鏈接。下面你可以看到一個例子:

          在本章中,我介紹了一些需要翻譯成應用支持的所有語言的新文本,因此有必要更新翻譯目錄:


          (venv) $ flask translate update

          對于你自己的項目,需要編輯每個語言存儲庫中的messages.po文件以包含這些新測試的翻譯,不過我已經在本章的下載包或GitHub存儲庫中創建了西班牙語翻譯。

          要完成新的翻譯,還需要執行編譯:


          (venv) $ flask translate compile

          最后,我自己是一名從事了多年開發的Python老程序員,辭職目前在做自己的Python私人定制課程,今年年初我花了一個月整理了一份最適合2019年學習的Python學習干貨,可以送給每一位喜歡Python的小伙伴,想要獲取的可以關注我的頭條號并在后臺私信我:01,即可免費獲取。

          文出自APICloud官方論壇,感謝論壇版主 gp3098的分享。

          之前直接把模板寫在頁面底部的script標簽內的,但是現在不同。

          使用了doT.js配合api的loadData方法,整個頁面就是模板。

          以前打開frame或者window的時候一直不明白url和data怎么配合,一直以為data只能加載到一些靜態的頁面,沒有其他用法。

          學習了doT.js的一些高級用法,能夠更好的搭建多頁面程序。

          在打開新頁面的時候先通過dot渲染一個頁面然后通過frame或者win的方法來加載html代碼。

          //數據渲染到frame的內容區域//默認只渲染第一頁
           function renderData(currid, tag, page, size, sort, order) {
           var currentSort=$api.dom('.screen a.on');
           var param={
           id: currid || tag.dataset.id,
           page: 1,
           size: 10,
           sort: sort || currentSort.dataset.sort,
           order: order || currentSort.dataset.order,
           }
           //從文件讀取兩個模板
           var template=loadfile('widget://mall/components/goodsList_frame.html');
           var template2=loadfile('widget://mall/components/goodsitem.html');
           // var template=loadfile('widget://mall/components/goods.html');
           var def={
           debug: true,
           content: template2, //第二個模板用于重復使用 在加載更多內容時候使用
           urlparam: param,
           };
           var tempFn=doT.template(template, undefined, def); //生成渲染模板的函數
           api.cancelAjax({
           tag: ajaxtag1
           });
           // console.warn(parseUrl(param));//用來解析url,把json的url變成字符串形式
           ajaxtag1=$api.get(DOMAIN + '/ajax/goodslist?' + parseUrl(param), function(ret, err) {
           console.warn(JSON.stringify(ret));
           var html=tempFn(ret);
           // 要檢查frame的真實內容在html這里!!!
           // console.warn(html);
           //通過loadData方式把頁面加載到frame中,通過參數改變,每次重新加載frame內容
           api.loadData({
           frameName: 'goods_list',
           url: 'widget://mall/components/', //要加載的頁面,data內的css、js的路徑的根路徑!!!
           data: html //dot渲染出來的html頁面 成為frame的內容,生成的frame頁面整個頁面都可以用dot模板語法因為整個頁面就是模板
           });
           }, 'json');
           }
          復制代碼
          

          這里的loadfile是官方的api方法

          有同步和異步兩種,都進行封裝過,但感覺封裝得不夠好,只展現一個同步,不然代碼不完整。

          function loadfile(url) {
           return api.readFile({
           sync: true,
           path: url || '',
           });
          }
          復制代碼
          

          還有官方的$api.get方法是不會返回tag用來取消ajax請求的

          //json轉url參數
          var parseUrl=function(urlparam) {
           return Object.keys(urlparam).map(function(key) {
           return encodeURIComponent(key) + "=" + encodeURIComponent(urlparam[key]);
           }).join("&");
          }
          復制代碼
          


          模板1

          var template=loadfile('widget://mall/components/goodsList_frame.html');//對應的模板文件在下面
          復制代碼
          
          <!DOCTYPE html>
          <html lang="en">
          <head>
           <meta charset="UTF-8">
           <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
           <link rel="stylesheet" href="../css/all.css">
           <title>商品二級分類 搜索 內容</title>
          </head>
          <body>
           {{#def.header}}
           <div class="product_list clearfix content" data-id="{{#def.urlparam.id}}" data-sort="{{#def.urlparam.sort}}" data-order="{{#def.urlparam.order}}" data-page="{{#def.urlparam.page}}" data-size="{{#def.urlparam.size}}">
           <!-- 這里的def.content預編譯對應的在上一個頁面的def內, -->
           <!-- 而content內的html我也單獨提取出來放到一個頁面內了,方便重復調用 -->
           {{#def.content }}
           </div>
           {{#def.footer}}
           <!-- 這里只是說有這樣的用法,但是不代表一定要加header或者footer,因為api框架的原因, -->
           <!-- 我會在上一個頁面內加載header也就是window里面,然后自適應高度頭部 -->
           <!-- 底部有時候是用tablayout寫,所以也用不到 -->
           
          </body>
          <script type="text/javascript" src="../js/doT.min.js"></script>
          <script type="text/javascript" src="../js/api.js"> </script>
          <script src="../js/main.js"></script>
          <script src="../js/goodsList_frame.js" charset="utf-8"></script>
          <!-- 加一個script標簽用來放原生js也是可以的,但是為了防止dot模板沖突,建議還是放在文件里通過引用的方式來加載,css也是一樣 -->
          </html>
          復制代碼
          


          模板 2

          文出自APICloud官方論壇

          qiniuLive 封裝了七牛直播云服務平臺的移動端開放 SDK。該模塊包括視頻流采集和視頻流播放兩部分

          iOS連麥流程圖:

          Android連麥流程圖:

          以下部分代碼,僅供參考-


          主站蜘蛛池模板: 日韩成人无码一区二区三区 | 国产亚洲福利一区二区免费看| 色综合久久一区二区三区| 台湾无码AV一区二区三区| 国产精品亚洲一区二区三区在线观看 | 亚洲宅男精品一区在线观看| 中文字幕一区在线观看| 高清在线一区二区| 久久精品成人一区二区三区| 91精品国产一区| 色窝窝无码一区二区三区成人网站 | 国产精品美女一区二区| 精品少妇ay一区二区三区| 色噜噜狠狠一区二区三区果冻 | 国产乱码精品一区二区三| 国产av夜夜欢一区二区三区| 日韩一区二区a片免费观看| 亚洲av成人一区二区三区观看在线| 日本免费一区二区三区 | 精品一区二区高清在线观看| 日韩在线视频一区二区三区| 日本一区二区三区日本免费| 无码一区二区三区AV免费| 男人的天堂av亚洲一区2区| 日韩av片无码一区二区不卡电影| 国产欧美色一区二区三区| 日美欧韩一区二去三区| 福利一区在线视频| 精品人妻一区二区三区毛片| 国产亚洲自拍一区| 色欲AV蜜臀一区二区三区| 精品无码av一区二区三区| 最新欧美精品一区二区三区| 欧美成人aaa片一区国产精品| 日韩精品一区二区三区中文版 | 欧洲无码一区二区三区在线观看| 日本免费一区二区在线观看| 国产精品一区二区AV麻豆| 国产精品亚洲高清一区二区 | 国产成人av一区二区三区不卡| 国产日韩视频一区|