什么是 Last.fm?

Last.fm是一個廣受歡迎的在線音樂信息服務平臺,您可以在這里獲取藝術家、專輯、熱門歌曲等詳細信息。在創建用戶資料后,該平臺會根據您的收聽習慣推薦歌曲。對于這個應用程序,您將使用Last.fm提供的專輯搜索API。

先決條件

要構建此應用程序,您需要滿足以下條件:

快速開始

如果您想跳過詳細信息并快速入門,您可以在 GitHub 上找到該應用程序。您可以直接將其安裝在 Contentful 空間上,也可以在您的計算機上本地運行它。

要學習從頭開始構建應用程序,請繼續閱讀。

引導應用程序

您可以為您的 Contentful 空間創建應用程序,以自定義編輯體驗、集成外部服務等等。

要為您的 Contentful 空間創建應用程序,您將使用 App Framework。App Framework 提供了創建應用程序所需的所有工具。它提供的包允許您與 API 交互、與各種位置交互等。

您可以使用 CLI 在計算機上創建應用程序,方法是運行以下命令來引導應用程序。

npx create-contentful-app

注意: 默認情況下,上述命令將在 TypeScript 中引導項目。如果要使用 JavaScript,請使用 flag。--javascript

存儲庫準備就緒后,您可以通過從項目目錄運行以下命令來啟動本地服務器。

npm start

如果您導航到 localhost:3000,您將看到一條警告消息,指出您無法在 Contentful 之外查看應用程序。

在下一節中,您將學習如何在 Contentful 中創建應用程序,并在 Contentful 中與應用程序交互。

在 Contentful 中創建應用程序

既然您已經在本地運行了項目,下一步就是創建應用程序并在Contentful中配置其應用程序定義。應用程序定義將允許您選擇位置、主機URL以及其他應用程序參數。

登錄到 Contentful 空間,然后從 應用程序 下拉列表中選擇 管理應用程序。接下來,單擊左上角的 Manage app definitions,然后單擊 Create app 按鈕。

在 App name 字段中輸入應用程序的名稱,然后單擊 Create app。這將創建一個新應用程序,并將您帶到應用程序的定義頁面。

由于您將在開發過程中在本地運行應用程序,因此在 App definition (應用程序定義) 屏幕上,在 frontend 字段中輸入 http://localhost:3000。

接下來,在 Locations (位置) 下,選擇 App configuration (應用程序配置) 屏幕。應用程序配置位置允許您從用戶那里獲取配置詳細信息,例如,設置 Last.fm API 密鑰。

您還需要選擇 Entry field > JSON 對象。這是允許用戶與應用程序交互并存儲信息的位置。

單擊 Save 按鈕以保存應用程序定義。您的應用程序定義應與下圖相同。

應用詳細信息

要在 Contentful 空間上安裝應用程序,請單擊操作,然后選擇安裝到空間

從 Select a space 下拉列表中選擇您的空間,并從 Select an environment 下拉列表中選擇環境。授權應用程序,您將看到默認的 App config 頁面。

在下一節中,您將了解如何自定義 App configuration location。

自定義 App 配置位置

如果您在應用程序定義中進行了配置,那么應用程序配置位置將是用戶安裝應用程序后與之交互的第一個屏幕。用戶可以在此處提供詳細信息,例如 API 密鑰、他們想要獲取的屬性等。

要自定義 App configuration location(應用程序配置位置),請打開該文件。要添加表單,請將其中的代碼替換為以下代碼。src/locations/ConfigScreen.tsxreturn()

<Flex flexDirection="column" className={css({ margin: '80px', maxWidth: '800px' })}>
<Form>
<FormControl>
<FormControl.Label>API Key</FormControl.Label>
<TextInput
value={parameters.apiKey}
type='text'
onChange={(e) => setParameters({...parameters, apiKey:e.target.value})}
/>
</FormControl>
</Form>
</Flex>

上面的代碼使用了 Forma 36 中的 、 、 和 組件。更新導入以使用這些組件。FlexFormFormControlTextInput

您還可以觀察到 value 屬性從 獲取值。TextInputparameters.apiKey

parameters 是已為您定義的類型的狀態。AppInstallationParameters

要充分利用 TypeScript,請更新 .你的應用 AppInstallationParameters 應如下所示。AppInstallationParameters

export interface AppInstallationParameters {
apiKey: string | undefined;
}

最終代碼應如下所示。

import React, { useCallback, useState, useEffect } from 'react';
import { AppExtensionSDK } from '@contentful/app-sdk';
import {Form, FormControl, Flex, TextInput } from '@contentful/f36-components';
import { css } from 'emotion';
import { useSDK } from '@contentful/react-apps-toolkit';

export interface AppInstallationParameters {
apiKey: string | undefined;
}

const ConfigScreen = () => {
const [parameters, setParameters] = useState<AppInstallationParameters>({apiKey: ''});
const sdk = useSDK<AppExtensionSDK>();

const onConfigure = useCallback(async () => {
const currentState = await sdk.app.getCurrentState();

return {
parameters,
targetState: currentState,
};
}, [parameters, sdk]);

useEffect(() => {
sdk.app.onConfigure(() => onConfigure());
}, [sdk, onConfigure]);

useEffect(() => {
(async () => {
const currentParameters: AppInstallationParameters | null = await sdk.app.getParameters();

if (currentParameters) {
setParameters(currentParameters);
}
sdk.app.setReady();
})();
}, [sdk]);

return (
<Flex flexDirection="column" className={css({ margin: '80px', maxWidth: '800px' })}>
<Form>
<FormControl>
<FormControl.Label>API Key</FormControl.Label>
<TextInput
value={parameters.apiKey}
type='text'
onChange={(e) => setParameters({...parameters, apiKey:e.target.value})}
/>
</FormControl>
</Form>
</Flex>
);
};

export default ConfigScreen;

更深入地了解代碼超出了本文的范圍。如果您有興趣了解更多信息,可以閱讀官方文檔。

但以下是代碼作用的快速摘要:當應用程序首次安裝時,用戶會看到此表單。用戶在此處輸入其API密鑰。當用戶點擊“安裝”按鈕時,API密鑰會被存儲在參數對象中。這樣,您就可以從其他任何位置引用該值。

保存代碼,您現在將在 App configuration (應用程序配置) 位置查看表單。輸入您的 Last.fm API 密鑰,然后點擊 Install(安裝)。

您的應用程序現已安裝并可供使用!

Last.fm

在要添加唱片集數據的內容類型中,創建一個 JSON 對象類型的新字段。確保在 Appearance (外觀) 選項卡中選擇您的應用。

Last.fm

為該內容類型創建一個新條目,您應該會看到默認的 JSON 編輯器。您將在下一節中了解如何自定義此字段。

自定義 Entry Field 位置

在配置 App definition 時,您選擇了 App configuration 作為 Entry 字段位置。

在上一節中,您自定義了 App configuration location(應用程序配置位置)。在本節中,您將自定義 Entry field location 以顯示用戶輸入和渲染數據。

對于此應用程序,用戶將輸入他們想要添加的相冊名稱。該應用程序將從 API 返回所有相關相冊,并在對話框組件中向用戶顯示它們。用戶選擇相冊,應用程序保存相冊信息。

與 App configuration location(應用程序配置位置)類似,您可以自定義文件中的 Entry field (src/locations/Field.tsx) 位置。打開文件并將 return 語句替換為以下代碼。

return (
<>
<Form onSubmit={()=>openDialog()}>
<FormControl>
<FormControl.Label isRequired>Album name</FormControl.Label>

<TextInput type='text' onChange={(e) => setAlbumSearch(e.target.value)} isRequired/>
</FormControl>
<FormControl>
<Button type='submit' variant='primary'>Search</Button>
</FormControl>
</Form>
</>
)

更新導入以包括FormFormControlTextInputButton組件。

如果您仔細觀察代碼,則會看到一個albumSearch state 和一個 openDialogfunction。按如下方albumSearch式初始化狀態。確保更新useState hook 的導入。

const [albumSearch, setAlbumSearch] = useState<string>('');

接下來,按如下方式定義openDialog函數。

const openDialog = async () => {
const album = await sdk.dialogs.openCurrentApp({
width: 700,
parameters: {
albumName: albumSearch
},
title: "Album Search",
allowHeightOverflow:true,
shouldCloseOnEscapePress: true,
shouldCloseOnOverlayClick: true
})
}

上述函數將打開一個對話框組件,該組件從 Last.fm API 返回結果。你正在傳遞albumName參數,該參數在Dialog組件中使用。

您的最終代碼應如下所示。

import React, {useState} from 'react';
import { Form, FormControl, TextInput, Button } from '@contentful/f36-components';
import { FieldExtensionSDK } from '@contentful/app-sdk';
import { useSDK } from '@contentful/react-apps-toolkit';

const Field = () => {
const sdk = useSDK<FieldExtensionSDK>();
const [albumSearch, setAlbumSearch] = useState<string>('');
const openDialog = async () => {
const album = await sdk.dialogs.openCurrentApp({
width: 700,
parameters: {
albumName: albumSearch
},
title: "Album Search",
allowHeightOverflow:true,
shouldCloseOnEscapePress: true,
shouldCloseOnOverlayClick: true
})
}

return (
<>
<Form onSubmit={()=>openDialog()}>
<FormControl>
<FormControl.Label isRequired>Album name</FormControl.Label>

<TextInput type='text' onChange={(e) => setAlbumSearch(e.target.value)} isRequired/>
</FormControl>
<FormControl>
<Button type='submit' variant='primary'>Search</Button>
</FormControl>
</Form>
</>
)
};

export default Field;

保存代碼,應用程序將熱重載。您的字段現在將具有一個輸入字段和一個類似于下圖的按鈕。

last.fm

嘗試提交表單,對話框將呈現。Marty,相信你已經知道下一步是什么了— 自定義對話組件。

獲取數據并自定義對話組件

在本節中,您將學習如何自定義對話組件。此組件將從 Last.fm API 獲取數據,并將其呈現給用戶。

打開src/locations/Dialog.tsx文件和以下 TypeScript 接口。

export interface Album {
 name:       string;
 artist:     string;
 url:        string;
 image:      Image[];
 streamable: string;
 mbid:       string;
}

export interface Image {
 "#text": string;
 size:    string;
}

接下來,聲明 Album 類型的狀態相冊。您將 API 調用的結果存儲在 album 狀態中。您可以使用 API 密鑰從 Last.fm 獲取數據。由于您在安裝應用程序時已經設置了 API 密鑰,因此您將使用該 API 密鑰。銷毀apiKey fromsdk.parameters.installation

const [album,setAlbum] = useState<Album[] | undefined>();

const {apiKey} = sdk.parameters.installation;

現在,您已經擁有了狀態和 API 密鑰,您將定義一個函數,該函數將從 Last.fm API 獲取數據。復制并粘貼以下函數代碼。

const fetchData = async (albumName: string) => {
   const response = await fetch(https://ws.audioscrobbler.com/2.0/?method=album.search&album=${albumName}&api_key=${apiKey}&format=json)    const {results} = await response.json();    setAlbum(results.albummatches.album);  }

在上面的代碼中,您正在使用 fetch API 從 API 獲取結果。在查詢參數中,您傳遞了專輯名稱 (albumNameapi) 和 API 密鑰 (Key)。

從 API 獲得響應后,您可以使用json()方法解析 promise 并銷毀 results 數組。最后,使用所需的結果更新專輯狀態的值。

該應用程序從您之前創建的輸入字段中獲取搜索查詢。用戶提交表單后,fetchData將呈現對話框。該函數應在組件渲染后立即執行。

因此,添加一個useEffect鉤子,并調用從輸入字段fetchData傳遞專輯名稱的函數。下面是執行此操作的代碼。

useEffect(()=>{
   // @ts-expect-error
   fetchData(sdk.parameters.invocation.albumName)
 },[sdk.parameters.invocation])

每次用戶搜索新專輯時,您的組件都會獲取數據。但是,該組件仍然不呈現數據。將 return 替換為以下代碼,以在 dialog 組件中顯示數據。

if(!album){
return <Spinner size="large" />
}
return (
<Stack fullWidth>
<EntityList style={{
width: '100%'
}}>
{
album.map((item,i)=>{
return(<EntityList.Item
key={i}
title={item.name}
thumbnailUrl={item.image[1]['#text']}
onClick={()=> sdk.close({
name: item.name,
image: item.image[2]['#text']
})}
/>)
})
}
</EntityList>
</Stack>
);

讓我們了解一下上面的代碼中發生了什么。使用 IF 語句,您首先檢查相冊是否包含任何值。如果應用程序正在獲取數據,則專輯狀態為 null,用戶將看到一個微調器。但是一旦數據被獲取,結果就會被渲染。

onClick組件的EntityList.Item屬性將關閉對話框組件,并將所選影集的名稱和圖像信息發送回字段位置。

在繼續下一部分之前,請將掛鉤useAutoResizer()添加到對話組件中。這個 hook 處理組件的大小調整。您的對話組件應如下所示。

import React, { useEffect, useState } from 'react';
import { Spinner, Stack, EntityList } from '@contentful/f36-components';
import { DialogExtensionSDK } from '@contentful/app-sdk';
import { useAutoResizer, useSDK } from '@contentful/react-apps-toolkit';

export interface Album {
name: string;
artist: string;
url: string;
image: Image[];
streamable: string;
mbid: string;
}

export interface Image {
"#text": string;
size: string;
}

const Dialog = () => {
const sdk = useSDK<DialogExtensionSDK>();
useAutoResizer();

const [album,setAlbum] = useState<Album[] | undefined>();

const {apiKey} = sdk.parameters.installation;

const fetchData = async (albumName: string) => {
const response = await fetch(https://ws.audioscrobbler.com/2.0/?method=album.search&album=${albumName}&api_key=${apiKey}&format=json) const {results} = await response.json(); setAlbum(results.albummatches.album); } useEffect(()=>{ // @ts-expect-error fetchData(sdk.parameters.invocation.albumName) },[sdk.parameters.invocation]) if(!album){ return <Spinner size="large" /> } return ( <Stack fullWidth> <EntityList style={{ width: '100%' }}> { album.map((item,i)=>{ return(<EntityList.Item key={i} title={item.name} thumbnailUrl={item.image[1]['#text']} onClick={()=> sdk.close({ name: item.name, image: item.image[2]['#text'] })} />) }) } </EntityList> </Stack> ); }; export default Dialog;

在 Contentful 中存儲相冊數據

使用您的應用,用戶可以搜索他們最喜歡的專輯。用戶從搜索結果中選擇相冊后,應用程序既不會向用戶顯示相冊,也不會將數據存儲在 Contentful 中。

在本節中,您將更新 Field 組件以呈現所選數據并將其存儲在 Contentful 中。

打開src/locations/Field.tsx文件并聲明接口Album

interface Album {
name: string,
image: string
}

接下來,從@contentful/react-apps-toolkit包中導入useFieldValue鉤子,并按如下方式聲明albumData

const [albumData, setAlbumData] = useFieldValue<Album | null>()

更新openDialog函數,并將albumData的值設置為album

const openDialog = async () => {
const album = await sdk.dialogs.openCurrentApp({
width: 700,
parameters: {
albumName: albumSearch
},
title: "Album Search",
allowHeightOverflow:true,
shouldCloseOnEscapePress: true,
shouldCloseOnOverlayClick: true
})
if(album){
setAlbumData(album);
}
}

當用戶從列表中選擇相冊時,對話框將關閉,并返回相冊名稱和相冊圖像。上面的代碼將此對象值存儲在字段中。用戶可能會關閉對話框而不選擇相冊。IF 語句可以解決這個問題。

最后一步是更新我們的組件以顯示選定的相冊。在 closing 標簽的末尾添加以下Form代碼。

{
       albumData && (
         <AssetCard
             type='image'
             title={albumData.name}
             src={albumData.image}
             actions={[
               <MenuItemkey="remove"onClick={()=>setAlbumData(null)}>Remove</MenuItem>
             ]}
           />
       )
     }

在這里,您再次檢查albumData是否包含任何數據。如果它包含從對話框組件返回的數據,您將使用Forma 36的AssetCard組件來渲染它。您還添加了一個操作,允許用戶刪除選定的專輯。

您應該添加的最后一件事是useAutoResizer鉤子。您的字段位置(Field location)的最終代碼應該如下所示:

import React, {useState} from 'react';
import { Form, FormControl, TextInput, Button, AssetCard, MenuItem } from '@contentful/f36-components';
import { FieldExtensionSDK } from '@contentful/app-sdk';
import { useAutoResizer, useFieldValue, useSDK } from '@contentful/react-apps-toolkit';

interface Album {
name: string,
image: string
}

const Field = () => {
const sdk = useSDK<FieldExtensionSDK>();
useAutoResizer();
const [albumSearch, setAlbumSearch] = useState<string>('');
const [albumData, setAlbumData] = useFieldValue<Album | null>()
const openDialog = async () => {
const album = await sdk.dialogs.openCurrentApp({
width: 700,
parameters: {
albumName: albumSearch
},
title: "Album Search",
allowHeightOverflow:true,
shouldCloseOnEscapePress: true,
shouldCloseOnOverlayClick: true
})
if(album){
setAlbumData(album);
}
}

return (
<>
<Form onSubmit={()=>openDialog()}>
<FormControl>
<FormControl.Label isRequired>Album name</FormControl.Label>

<TextInput type='text' onChange={(e) => setAlbumSearch(e.target.value)} isRequired/>
</FormControl>
<FormControl>
<Button type='submit' variant='primary'>Search</Button>
</FormControl>
</Form>
{
albumData && (
<AssetCard
type='image'
title={albumData.name}
src={albumData.image}
actions={[
<MenuItem key="remove" onClick={()=>setAlbumData(null)}>Remove</MenuItem>
]}
/>
)
}
</>
)
};

export default Field;

嘗試搜索其他影集。您的應用程序現在將提取數據、顯示搜索結果并呈現所選相冊。

專輯名稱

下一步是什么?

在本文中,您創建了一個與外部 API 集成的 Contentful,并允許您添加內容。

現在,您的應用程序已準備就緒,請托管它以供您的團隊使用。您可以將應用程序托管在 Contentful 或 Netlify、Vercel 等外部服務上。按照文檔了解如何托管您的應用程序。

您可以在開發者展示中查看社區創建的大量應用程序。如果您創建的應用程序可能會使其他人受益,請考慮提交。

最后,請隨時在 Twitter 上與我聯系,分享您最喜歡的音樂劇、談論 Contentful 或提出問題。

原文來源:https://www.contentful.com/blog/build-custom-contentful-app-last-fm-api/

熱門推薦
一個賬號試用1000+ API
助力AI無縫鏈接物理世界 · 無需多次注冊
3000+提示詞助力AI大模型
和專業工程師共享工作效率翻倍的秘密
返回頂部
上一篇
探索視頻水印:技術、優勢和最佳實踐
下一篇
最佳網頁抓取 API:Scrapestack 與 ScrapingBee
国内精品久久久久影院日本,日本中文字幕视频,99久久精品99999久久,又粗又大又黄又硬又爽毛片
91老师片黄在线观看| 色综合天天在线| 在线观看国产精品网站| 国产女人18水真多18精品一级做| 国产日韩精品一区| 日韩欧美国产精品| 久久精品亚洲麻豆av一区二区 | 国产精品久久久久久久午夜片| 日韩一区二区不卡| 免费看欧美女人艹b| 精品奇米国产一区二区三区| 亚洲欧美aⅴ...| 秋霞国产午夜精品免费视频| 2023国产精华国产精品| 国产一区视频网站| 欧美精品一区二区高清在线观看 | 风间由美性色一区二区三区| 中文av字幕一区| 91网址在线看| 男男视频亚洲欧美| 国产精品成人在线观看| 99re这里都是精品| 一区二区三区欧美日韩| 日韩女优毛片在线| 色综合天天做天天爱| 麻豆传媒一区二区三区| 国产精品白丝在线| 久久久久国产精品人| 在线观看www91| 成人av资源在线| 六月丁香婷婷久久| 亚洲欧美日韩国产手机在线| 久久综合九色综合欧美98| 日本二三区不卡| 波多野结衣欧美| 国产91富婆露脸刺激对白| 免费久久99精品国产| 一区二区三区高清| 亚洲欧美激情视频在线观看一区二区三区 | 亚洲福利视频一区| 国产精品久久久久久久午夜片| 欧美一级欧美三级| 欧美绝品在线观看成人午夜影视| www.亚洲色图.com| 成人福利视频在线| 国产成人av在线影院| 国产在线不卡视频| 韩国成人精品a∨在线观看| 日韩国产在线一| 秋霞影院一区二区| 九九九久久久精品| 黄页视频在线91| 国产一区二区不卡在线| 国产精品影音先锋| 成人免费视频免费观看| 成人激情小说乱人伦| av亚洲精华国产精华精| a美女胸又www黄视频久久| 成人黄色综合网站| 在线观看亚洲专区| 日韩视频在线你懂得| 久久综合丝袜日本网| 国产精品嫩草影院com| 中文字幕亚洲欧美在线不卡| 亚洲精品成人悠悠色影视| 久久久久久电影| 久久九九全国免费| 国产99久久久国产精品潘金 | 精品视频色一区| 日韩久久免费av| 亚洲综合在线免费观看| 亚洲最新视频在线播放| 久久网站热最新地址| 日韩欧美国产一二三区| 日日摸夜夜添夜夜添国产精品| 91麻豆精品秘密| 国产成人午夜高潮毛片| 亚洲二区视频在线| 精品久久一区二区三区| 日韩在线卡一卡二| 欧美午夜精品一区| 在线观看成人小视频| 国产1区2区3区精品美女| 高清国产一区二区| 欧美色中文字幕| 久久精品一区二区三区不卡 | 日韩午夜中文字幕| 国产一区91精品张津瑜| wwwwxxxxx欧美| 亚洲欧洲制服丝袜| 波多野结衣在线aⅴ中文字幕不卡| 精品国产乱码久久久久久图片 | 亚洲欧美中日韩| 欧美理论电影在线| 欧美一区二区三区免费在线看 | 成人动漫一区二区三区| 国产91高潮流白浆在线麻豆| 91福利精品视频| 久久精品亚洲麻豆av一区二区| 亚洲一级不卡视频| 成人国产精品视频| 欧美熟乱第一页| 制服丝袜中文字幕亚洲| 日本一区二区在线不卡| 视频一区二区中文字幕| 菠萝蜜视频在线观看一区| 久久久青草青青国产亚洲免观| 午夜视频久久久久久| 色视频一区二区| 亚洲精品国产无天堂网2021| www.av亚洲| 亚洲精品你懂的| 91美女精品福利| 亚洲欧美日韩一区二区| av一区二区三区| 亚洲免费成人av| 欧美性大战久久久| 无码av中文一区二区三区桃花岛| 色综合天天综合色综合av| 一区二区三区欧美日| 欧美日韩精品电影| 日本最新不卡在线| 精品伦理精品一区| 东方欧美亚洲色图在线| ...av二区三区久久精品| 成人av电影免费在线播放| 国产欧美综合在线| 91麻豆国产福利在线观看| 亚洲国产精品久久一线不卡| 欧美一区在线视频| 国产在线一区二区| 中文字幕av一区 二区| 91久久香蕉国产日韩欧美9色| 爽好多水快深点欧美视频| 日韩欧美色综合| 成人黄色777网| 亚洲国产乱码最新视频| 精品动漫一区二区三区在线观看| 国产精品一区二区x88av| 自拍偷拍国产精品| 欧美成人激情免费网| 91麻豆6部合集magnet| 免费成人在线影院| 亚洲你懂的在线视频| 91精品国产综合久久香蕉麻豆| 岛国一区二区三区| 免播放器亚洲一区| 一区二区三区精品| 久久久久国产一区二区三区四区 | 99久久精品费精品国产一区二区| 一区二区三区色| 久久婷婷国产综合精品青草| 在线视频你懂得一区| 国产成人精品免费看| 视频一区视频二区在线观看| 国产精品美女久久久久久| 欧美一区二区日韩一区二区| 色综合天天综合网天天看片| 韩日精品视频一区| 亚洲国产欧美另类丝袜| 国产jizzjizz一区二区| 久久网站最新地址| 色呦呦国产精品| 亚洲国产aⅴ成人精品无吗| 极品少妇一区二区三区精品视频| 一区二区日韩电影| 亚洲午夜日本在线观看| 亚洲成va人在线观看| 最近日韩中文字幕| 国产精品国产自产拍高清av | 亚洲美女精品一区| 国产精品视频yy9299一区| 久久精品在线免费观看| 精品国产免费一区二区三区四区 | 成人久久18免费网站麻豆| 成人免费av资源| av中文字幕不卡| 91丨porny丨首页| 91麻豆国产福利精品| 欧美影院午夜播放| 一本久久精品一区二区| 一本大道久久a久久精二百| 91亚洲资源网| 欧美日韩国产欧美日美国产精品| 欧美挠脚心视频网站| 久久久久亚洲蜜桃| 欧美tk—视频vk| 久久久久久久av麻豆果冻| 久久精品亚洲一区二区三区浴池| 欧美精品一区视频| 国产精品卡一卡二卡三| 一区二区三区在线播| 亚洲高清在线视频| 麻豆成人av在线| 成人av影视在线观看| 欧美亚男人的天堂| 精品国产一区二区三区久久影院 | 26uuu国产一区二区三区| 国产精品女主播在线观看| 亚洲高清视频在线|