# Alchemy第十周教程 **Published by:** [xiaoge](https://paragraph.com/@xiaoge/) **Published on:** 2022-11-12 **URL:** https://paragraph.com/@xiaoge/alchemy-10 ## Content 准备开始需要注意:每次修改或者覆盖代码需要按ctrl+s保存代码)进入 https://replit.com/ 登入你的账号看下图设置创建成功以后在shell里输入如下代码,其中week10为你的项目名字,可以随意取npx create-next-app road-to-lens 输入成功以后,提示 'Ok to processed',输入y,等待下载项目所需的安装包并创建项目。提示这个就可以下一步了然后我们在Shell里输入下方代码切换到项目所在路径下。cd road-to-lens 在shell输入下方代码,安装graphql(看好路径)npm install @apollo/client graphql 有下方提示后即安装成功在shell输入下方代码运行项目验证npm run dev 有下图右上角的显示后就是验证成功接下来下一步在 index.js 页面上使用 Lens 推荐的配置文件尝试 Apollo GraphQL新建apollo-client.js 我们在当前项目新建一个apollo-client.js创建完文件后点击打开然后输入下方代码// ./apollo-client.js import { ApolloClient, InMemoryCache } from "@apollo/client"; const client = new ApolloClient({ uri: "https://api.lens.dev", cache: new InMemoryCache(), }); export default client; 修改我们的/pages/_app.js 先找到文件打开然后输入下方代码:// pages/_app.js import '../styles/globals.css' import { ApolloProvider } from "@apollo/client"; import client from "../apollo-client"; function MyApp({ Component, pageProps }) { return ( <ApolloProvider client={client}> <Component {...pageProps} /> </ApolloProvider> ); } export default MyApp 修改/pages/index.js先找到文件打开输入下方代码:import { useQuery, gql } from "@apollo/client"; import recommendedProfilesQuery from '../queries/recommendedProfilesQuery.js'; import Profile from '../components/Profile.js'; export default function Home() { const {loading, error, data} = useQuery(recommendedProfilesQuery); if (loading) return 'Loading..'; if (error) return `Error! ${error.message}`; return ( <div> {data.recommendedProfiles?.map((profile, index) => { console.log(`Profile ${index}:`, profile); return <Profile key={profile.id} profile={profile} displayFullProfile={false} />; })} </div> ) } 新建查询jsmkdir queries touch queries/recommendedProfilesQuery.js 先点加号新建一个Shell.先定位目录输入下方代码:cd road-to-lens 然后输入下方命令一共两条,会创建一个新的文件夹和文件(如果使用VScode第二条 touch代码需要在CMD使用)mkdir queries touch queries/recommendedProfilesQuery.js 将下面的代码写入recommendedProfilesQuery.js// queries/recommendedProfilesQuery.js import {gql} from '@apollo/client'; export default gql` query RecommendedProfiles { recommendedProfiles { id name bio attributes { displayType traitType key value } followNftAddress metadata isDefault picture { ... on NftImage { contractAddress tokenId uri verified } ... on MediaSet { original { url mimeType } } __typename } handle coverPicture { ... on NftImage { contractAddress tokenId uri verified } ... on MediaSet { original { url mimeType } } __typename } ownedBy dispatcher { address canUseRelay } stats { totalFollowers totalFollowing totalPosts totalComments totalMirrors totalPublications totalCollects } followModule { ... on FeeFollowModuleSettings { type amount { asset { symbol name decimals address } value } recipient } ... on ProfileFollowModuleSettings { type } ... on RevertFollowModuleSettings { type } } } } `; 新建组件js和上一步一样(不需要新建Shell)直接输入下方代码(如果使用VScode第二条 touch代码需要在CMD使用)mkdir components touch components/Profile.js 同样创建了一个文件夹和一个文件将下面代码写入Profile.js// components/Profile.js import Link from "next/link"; export default function Profile(props) { const profile = props.profile; // When displayFullProfile is true, we show more info. const displayFullProfile = props.displayFullProfile; return ( <div className="p-8"> <Link href={`/profile/${profile.id}`}> <div className="max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl"> <div className="md:flex"> <div className="md:shrink-0"> {profile.picture ? ( <img src={ profile.picture.original ? profile.picture.original.url : profile.picture.uri } className="h-48 w-full object-cover md:h-full md:w-48" /> ) : ( <div style={{ backgrondColor: "gray", }} className="h-48 w-full object-cover md:h-full md:w-48" /> )} </div> <div className="p-8"> <div className="uppercase tracking-wide text-sm text-indigo-500 font-semibold"> {profile.handle} {displayFullProfile && " (" + profile.name + ")"} </div> <div className="block mt-1 text-sm leading-tight font-medium text-black hover:underline"> {profile.bio} </div> <div className="mt-2 text-sm text-slate-900">{profile.ownedBy}</div> <p className="mt-2 text-xs text-slate-500"> following: {profile.stats.totalFollowing} followers:{" "} {profile.stats.totalFollowers} </p> </div> </div> </div> </Link> </div> ); } 再次运行下方代码验证项目出现选择直接按回车npm run dev 验证成功后右上角会出现一张图片随机的我们将项目运行起来出现下面的结果说明你已经离成功不远了,但是现在页面的样式太丑了,让我们来优化下。优化页面样式输入下方命令记得先创建一个新的Shell窗口,然后定位路径路径代码:cd road-to-lens 安装 Tailwindnpm install -D tailwindcss postcss autoprefixer 当你执行完下面这个命令,系统会给你生成一个tailwind.config.jsnpx tailwindcss init -p 出现选择项直接回车出现下方提示后输入第二个代码,同样出现选项直接回车会生成一个文件在生成的tailwind.config.js中写入以下代码:// tailwind.config.js module.exports = { content: [ "./pages/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}", ], theme: { extend: {}, }, plugins: [], } 接着我们找到styles/globals.css文件用以下内容替换文件内容。html, body { padding: 0; margin: 0; font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; } a { color: inherit; text-decoration: none; } * { box-sizing: border-box; } @media (prefers-color-scheme: dark) { html { color-scheme: dark; } body { color: white; background: black; } } /* ./styles/globals.css */ @tailwind base; @tailwind components; @tailwind utilities; 再次验证一下大概会出现这种界面 验证代码:npm run dev 然后我们再pages下面新建一个profile文件夹,然后在下面新建一个[id].js文件,这个页面用来显示用户的详情页然后打开文件输入以下代码:import { useQuery, useMutation } from "@apollo/client"; import { useRouter } from "next/router"; import fetchProfileQuery from "../../queries/fetchProfileQuery.js"; import Profile from "../../components/Profile.js"; import Post from "../../components/Post.js"; export default function ProfilePage() { const router = useRouter(); const { id } = router.query; console.log("fetching profile for", id); const { loading, error, data } = useQuery(fetchProfileQuery, { variables: { request: { profileId: id }, publicationsRequest: { profileId: id, publicationTypes: ["POST"], }, }, }); if (loading) return "Loading.."; if (error) return `Error! ${error.message}`; return ( <div className="flex flex-col p-8 items-center"> <Profile profile={data.profile} displayFullProfile={true} /> {data.publications.items.map((post, idx) => { return <Post key={idx} post={post}/>; })} </div> ); } 然后我们在queries文件夹下面新增文件fetchProfileQuery.js,这个用来获取用户详情数据打开文件后输入以下代码:import { gql } from "@apollo/client"; export default gql` query ( $request: SingleProfileQueryRequest! $publicationsRequest: PublicationsQueryRequest! ) { publications( request: $publicationsRequest) { items { __typename ... on Post { ...PostFields } ... on Comment { ...CommentFields } ... on Mirror { ...MirrorFields } } pageInfo { prev next totalCount } } profile(request: $request) { id name bio attributes { displayType traitType key value } followNftAddress metadata isDefault picture { ... on NftImage { contractAddress tokenId uri verified } ... on MediaSet { original { url mimeType } } __typename } handle coverPicture { ... on NftImage { contractAddress tokenId uri verified } ... on MediaSet { original { url mimeType } } __typename } ownedBy dispatcher { address canUseRelay } stats { totalFollowers totalFollowing totalPosts totalComments totalMirrors totalPublications totalCollects } followModule { ... on FeeFollowModuleSettings { type amount { asset { symbol name decimals address } value } recipient } ... on ProfileFollowModuleSettings { type } ... on RevertFollowModuleSettings { type } } } } fragment MediaFields on Media { url mimeType } fragment ProfileFields on Profile { id name bio attributes { displayType traitType key value } isFollowedByMe isFollowing(who: null) followNftAddress metadata isDefault handle picture { ... on NftImage { contractAddress tokenId uri verified } ... on MediaSet { original { ...MediaFields } } } coverPicture { ... on NftImage { contractAddress tokenId uri verified } ... on MediaSet { original { ...MediaFields } } } ownedBy dispatcher { address } stats { totalFollowers totalFollowing totalPosts totalComments totalMirrors totalPublications totalCollects } followModule { ... on FeeFollowModuleSettings { type amount { asset { name symbol decimals address } value } recipient } ... on ProfileFollowModuleSettings { type } ... on RevertFollowModuleSettings { type } } } fragment PublicationStatsFields on PublicationStats { totalAmountOfMirrors totalAmountOfCollects totalAmountOfComments } fragment MetadataOutputFields on MetadataOutput { name description content media { original { ...MediaFields } } attributes { displayType traitType value } } fragment Erc20Fields on Erc20 { name symbol decimals address } fragment CollectModuleFields on CollectModule { __typename ... on FreeCollectModuleSettings { type followerOnly contractAddress } ... on FeeCollectModuleSettings { type amount { asset { ...Erc20Fields } value } recipient referralFee } ... on LimitedFeeCollectModuleSettings { type collectLimit amount { asset { ...Erc20Fields } value } recipient referralFee } ... on LimitedTimedFeeCollectModuleSettings { type collectLimit amount { asset { ...Erc20Fields } value } recipient referralFee endTimestamp } ... on RevertCollectModuleSettings { type } ... on TimedFeeCollectModuleSettings { type amount { asset { ...Erc20Fields } value } recipient referralFee endTimestamp } } fragment PostFields on Post { id profile { ...ProfileFields } stats { ...PublicationStatsFields } metadata { ...MetadataOutputFields } createdAt collectModule { ...CollectModuleFields } referenceModule { ... on FollowOnlyReferenceModuleSettings { type } } appId hidden mirrors(by: null) hasCollectedByMe } fragment MirrorBaseFields on Mirror { id profile { ...ProfileFields } stats { ...PublicationStatsFields } metadata { ...MetadataOutputFields } createdAt collectModule { ...CollectModuleFields } referenceModule { ... on FollowOnlyReferenceModuleSettings { type } } appId hidden hasCollectedByMe } fragment MirrorFields on Mirror { ...MirrorBaseFields mirrorOf { ... on Post { ...PostFields } ... on Comment { ...CommentFields } } } fragment CommentBaseFields on Comment { id profile { ...ProfileFields } stats { ...PublicationStatsFields } metadata { ...MetadataOutputFields } createdAt collectModule { ...CollectModuleFields } referenceModule { ... on FollowOnlyReferenceModuleSettings { type } } appId hidden mirrors(by: null) hasCollectedByMe } fragment CommentFields on Comment { ...CommentBaseFields mainPost { ... on Post { ...PostFields } ... on Mirror { ...MirrorBaseFields mirrorOf { ... on Post { ...PostFields } ... on Comment { ...CommentMirrorOfFields } } } } } fragment CommentMirrorOfFields on Comment { ...CommentBaseFields mainPost { ... on Post { ...PostFields } ... on Mirror { ...MirrorBaseFields } } } `; 然后我们在components下面新建一个Post.js打开文件后输入下方代码// components/Post.js export default function Post(props) { const post = props.post; return ( <div className="p-8"> <div className="max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl"> <div className="md:flex"> <div className="p-8"> <p className="mt-2 text-xs text-slate-500 whitespace-pre-line"> {post.metadata.content} </p> </div> </div> </div> </div> ); } 验证任务是否完成在浏览器打开下方框选的链接打开后如下显示.(图片加载不出来是正常的)然后随便点一个人的资料进入. 类似下方显示就可以了发布 ## Publication Information - [xiaoge](https://paragraph.com/@xiaoge/): Publication homepage - [All Posts](https://paragraph.com/@xiaoge/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@xiaoge): Subscribe to updates - [Twitter](https://twitter.com/Chrish9010): Follow on Twitter